diff --git a/sql/updates/world/custom/custom_2020_01_12_00_world.sql b/sql/updates/world/custom/custom_2020_01_12_00_world.sql new file mode 100644 index 00000000000..9c5ebb3a1c2 --- /dev/null +++ b/sql/updates/world/custom/custom_2020_01_12_00_world.sql @@ -0,0 +1,117 @@ +-- +-- Table structure for table `creature_default_trainer` +-- +DROP TABLE IF EXISTS `creature_default_trainer`; +CREATE TABLE `creature_default_trainer` ( + `CreatureId` int(11) UNSIGNED NOT NULL, + `TrainerId` int(11) UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY (`CreatureId`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +UPDATE `creature_template` SET `ScriptName`='' WHERE `ScriptName`='npc_multi_profession_trainer'; + +-- +-- Table structure for table `gossip_menu_option_action` +-- +DROP TABLE IF EXISTS `gossip_menu_option_action`; +CREATE TABLE `gossip_menu_option_action` ( + `MenuId` int(11) UNSIGNED NOT NULL DEFAULT '0', + `OptionIndex` int(11) UNSIGNED NOT NULL DEFAULT '0', + `ActionMenuId` int(11) UNSIGNED NOT NULL DEFAULT '0', + `ActionPoiId` int(11) UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY (`MenuId`,`OptionIndex`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- Table structure for table `gossip_menu_option_box` +-- +DROP TABLE IF EXISTS `gossip_menu_option_box`; +CREATE TABLE `gossip_menu_option_box` ( + `MenuId` int(11) UNSIGNED NOT NULL DEFAULT '0', + `OptionIndex` int(11) UNSIGNED NOT NULL DEFAULT '0', + `BoxCoded` tinyint(3) UNSIGNED NOT NULL DEFAULT '0', + `BoxMoney` int(11) UNSIGNED NOT NULL DEFAULT '0', + `BoxText` text, + `BoxBroadcastTextId` int(11) UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY (`MenuId`,`OptionIndex`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- Table structure for table `gossip_menu_option_trainer` +-- +DROP TABLE IF EXISTS `gossip_menu_option_trainer`; +CREATE TABLE `gossip_menu_option_trainer` ( + `MenuId` int(11) UNSIGNED NOT NULL DEFAULT '0', + `OptionIndex` int(11) UNSIGNED NOT NULL DEFAULT '0', + `TrainerId` int(11) UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY (`MenuId`,`OptionIndex`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +INSERT INTO `gossip_menu_option_action` SELECT `MenuID`,`OptionID`,`ActionMenuID`,`ActionPoiID` FROM `gossip_menu_option` WHERE `ActionMenuID`!=0 OR `ActionPoiID`!=0; +INSERT INTO `gossip_menu_option_box` SELECT `MenuID`,`OptionID`,`BoxCoded`,`BoxMoney`,`BoxText`,`BoxBroadcastTextID` FROM `gossip_menu_option` WHERE `BoxCoded`!=0 OR `BoxMoney`!=0 OR LENGTH(`BoxText`)>0; + +ALTER TABLE `gossip_menu_option` DROP PRIMARY KEY; +ALTER TABLE `gossip_menu_option` + CHANGE `MenuID` `MenuId` int(11) UNSIGNED NOT NULL DEFAULT '0' FIRST, + CHANGE `OptionID` `OptionIndex` int(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `MenuId`, + CHANGE `OptionIcon` `OptionIcon` tinyint(3) UNSIGNED NOT NULL DEFAULT '0' AFTER `OptionIndex`, + CHANGE `OptionText` `OptionText` text AFTER `OptionIcon`, + CHANGE `OptionBroadcastTextID` `OptionBroadcastTextId` int(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `OptionText`, + CHANGE `OptionType` `OptionType` int(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `OptionBroadcastTextId`, + CHANGE `OptionNpcflag` `OptionNpcflag` bigint(20) UNSIGNED NOT NULL DEFAULT '0' AFTER `OptionType`, + DROP `ActionMenuID`, + DROP `ActionPoiID`, + DROP `BoxCoded`, + DROP `BoxMoney`, + DROP `BoxText`, + DROP `BoxBroadcastTextID`; +ALTER TABLE `gossip_menu_option` ADD PRIMARY KEY (`MenuId`,`OptionIndex`); + +-- +-- Table structure for table `trainer` +-- +DROP TABLE IF EXISTS `trainer`; +CREATE TABLE `trainer` ( + `Id` int(10) UNSIGNED NOT NULL DEFAULT '0', + `Type` tinyint(2) UNSIGNED NOT NULL DEFAULT '2', + `Requirement` mediumint(8) UNSIGNED NOT NULL DEFAULT '0', + `Greeting` TEXT, + `VerifiedBuild` SMALLINT (5) DEFAULT '0', + PRIMARY KEY (`Id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- Table structure for table `trainer_locale` +-- +DROP TABLE IF EXISTS `trainer_locale`; +CREATE TABLE `trainer_locale` ( + `Id` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `locale` VARCHAR(4) NOT NULL, + `Greeting_lang` text, + `VerifiedBuild` SMALLINT(5) DEFAULT '0', + PRIMARY KEY (`Id`,`locale`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- Table structure for table `trainer_spell` +-- +DROP TABLE IF EXISTS `trainer_spell`; +CREATE TABLE `trainer_spell` ( + `TrainerId` int(10) UNSIGNED NOT NULL DEFAULT '0', + `SpellId` int(10) UNSIGNED NOT NULL DEFAULT '0', + `MoneyCost` int(10) UNSIGNED NOT NULL DEFAULT '0', + `ReqSkillLine` int(10) UNSIGNED NOT NULL DEFAULT '0', + `ReqSkillRank` int(10) UNSIGNED NOT NULL DEFAULT '0', + `ReqAbility1` int(10) UNSIGNED NOT NULL DEFAULT '0', + `ReqAbility2` int(10) UNSIGNED NOT NULL DEFAULT '0', + `ReqAbility3` int(10) UNSIGNED NOT NULL DEFAULT '0', + `ReqLevel` tinyint(3) UNSIGNED NOT NULL DEFAULT '0', + `VerifiedBuild` SMALLINT (5) DEFAULT '0', + PRIMARY KEY (`TrainerId`,`SpellId`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `npc_trainer`; + +DELETE FROM `command` WHERE `name` IN ('reload npc_trainer','reload trainer'); +INSERT INTO `command` (`name`,`permission`,`help`) VALUES +('reload trainer', 672, 'Syntax: .reload trainer\nReloads trainer, trainer_locale and trainer_spell tables.'); diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index 53eef4841d6..961363bb49f 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -77,7 +77,7 @@ void WorldDatabaseConnection::DoPrepareStatements() PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_DEL_CREATURE, "DELETE FROM creature WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_SEL_COMMANDS, "SELECT name, permission, help FROM command", CONNECTION_SYNCH); - PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, modelid4, name, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, exp_unk, faction, npcflag, speed_walk, speed_run, scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, trainer_type, trainer_class, trainer_race, type, type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH); + PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, modelid4, name, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, exp_unk, faction, npcflag, speed_walk, speed_run, scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_ITEM_TEMPLATE_BY_NAME, "SELECT entry FROM item_template WHERE name = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ?", CONNECTION_SYNCH); diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h index 46c699febf0..d9522b2334f 100644 --- a/src/server/game/Accounts/RBAC.h +++ b/src/server/game/Accounts/RBAC.h @@ -569,7 +569,7 @@ enum RBACPermissions RBAC_PERM_COMMAND_RELOAD_MAIL_LOOT_TEMPLATE = 669, RBAC_PERM_COMMAND_RELOAD_MILLING_LOOT_TEMPLATE = 670, RBAC_PERM_COMMAND_RELOAD_NPC_SPELLCLICK_SPELLS = 671, - RBAC_PERM_COMMAND_RELOAD_NPC_TRAINER = 672, + RBAC_PERM_COMMAND_RELOAD_TRAINER = 672, RBAC_PERM_COMMAND_RELOAD_NPC_VENDOR = 673, RBAC_PERM_COMMAND_RELOAD_PAGE_TEXT = 674, RBAC_PERM_COMMAND_RELOAD_PICKPOCKETING_LOOT_TEMPLATE = 675, diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 58348d9891f..549c02a4e47 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -73,15 +73,6 @@ std::string CreatureMovementData::ToString() const return str.str(); } -TrainerSpell const* TrainerSpellData::Find(uint32 spell_id) const -{ - TrainerSpellMap::const_iterator itr = spellList.find(spell_id); - if (itr != spellList.end()) - return &itr->second; - - return nullptr; -} - bool VendorItem::IsGoldRequired(ItemTemplate const* pProto) const { return (pProto->Flags2 & ITEM_FLAG2_DONT_IGNORE_BUY_PRICE) || !ExtendedCost; @@ -1118,14 +1109,24 @@ bool Creature::isCanInteractWithBattleMaster(Player* player, bool msg) const return true; } -bool Creature::isCanTrainingAndResetTalentsOf(Player* player) const +bool Creature::CanResetTalents(Player* player, bool pet) const { - return player->getLevel() >= 10 && IsClassTrainerOf(player); + Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(sObjectMgr->GetCreatureDefaultTrainer(GetEntry())); + if (!trainer) + return false; + + return player->getLevel() >= 10 && + (trainer->GetTrainerType() == (pet ? Trainer::Type::Pet : Trainer::Type::Class)) && + trainer->IsTrainerValidForPlayer(player); } bool Creature::IsClassTrainerOf(Player const* player) const { - return GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS && player->getClass() == GetCreatureTemplate()->trainer_class; + Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(sObjectMgr->GetCreatureDefaultTrainer(GetEntry())); + if (!trainer) + return false; + + return trainer->GetTrainerType() == Trainer::Type::Class && trainer->IsTrainerValidForPlayer(player); } Player* Creature::GetLootRecipient() const @@ -2755,11 +2756,6 @@ uint32 Creature::UpdateVendorItemCurrentCount(VendorItem const* vItem, uint32 us return vCount->count; } -TrainerSpellData const* Creature::GetTrainerSpells() const -{ - return sObjectMgr->GetNpcTrainerSpells(GetEntry()); -} - // overwrite WorldObject function for proper name localization std::string const & Creature::GetNameForLocaleIdx(LocaleConstant loc_idx) const { diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index b59adad8425..f758c9ff8c0 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -106,7 +106,7 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma /// @todo Rename these properly bool isCanInteractWithBattleMaster(Player* player, bool msg) const; - bool isCanTrainingAndResetTalentsOf(Player* player) const; + bool CanResetTalents(Player* player, bool pet) const; bool IsClassTrainerOf(Player const* player) const; bool CanCreatureAttack(Unit const* victim, bool force = true) const; void LoadTemplateImmunities(); @@ -157,8 +157,6 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma uint32 GetVendorItemCurrentCount(VendorItem const* vItem); uint32 UpdateVendorItemCurrentCount(VendorItem const* vItem, uint32 used_count); - TrainerSpellData const* GetTrainerSpells() const; - CreatureTemplate const* GetCreatureTemplate() const { return m_creatureInfo; } CreatureData const* GetCreatureData() const { return m_creatureData; } CreatureAddon const* GetCreatureAddon() const; diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index 760944a1606..639bd0dfcd3 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -152,9 +152,6 @@ struct TC_GAME_API CreatureTemplate uint32 unit_flags2; // enum UnitFlags2 mask values uint32 dynamicflags; CreatureFamily family; // enum CreatureFamily values (optional) - uint32 trainer_type; - uint32 trainer_class; - uint32 trainer_race; uint32 type; // enum CreatureType values uint32 type_flags; // enum CreatureTypeFlags mask values uint32 type_flags2; // unknown enum, only set for 4 creatures (with value 1) @@ -365,39 +362,6 @@ struct VendorItemData } }; -struct TrainerSpell -{ - TrainerSpell() : SpellID(0), MoneyCost(0), ReqSkillLine(0), ReqSkillRank(0), ReqLevel(0), Index(0) - { - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - ReqAbility[i] = 0; - } - - uint32 SpellID; - uint32 MoneyCost; - uint32 ReqSkillLine; - uint32 ReqSkillRank; - uint32 ReqLevel; - uint32 ReqAbility[3]; - uint32 Index; - - // helpers - bool IsCastable() const { return ReqAbility[0] != SpellID; } -}; - -typedef std::unordered_map TrainerSpellMap; - -struct TC_GAME_API TrainerSpellData -{ - TrainerSpellData() : trainerType(0) { } - ~TrainerSpellData() { spellList.clear(); } - - TrainerSpellMap spellList; - uint32 trainerType; // trainer type based at trainer spells, can be different from creature_template value. - // req. for correct show non-prof. trainers like weaponmaster, allowed values 0 and 2. - TrainerSpell const* Find(uint32 spell_id) const; -}; - typedef std::unordered_map CreatureSparringTemplateMap; #endif // CreatureData_h__ diff --git a/src/server/game/Entities/Creature/GossipDef.cpp b/src/server/game/Entities/Creature/GossipDef.cpp index 66d2ddcb59a..6395c91cb6a 100644 --- a/src/server/game/Entities/Creature/GossipDef.cpp +++ b/src/server/game/Entities/Creature/GossipDef.cpp @@ -26,7 +26,6 @@ GossipMenu::GossipMenu() { _menuId = 0; _locale = DEFAULT_LOCALE; - _senderGUID.Clear(); } GossipMenu::~GossipMenu() @@ -34,27 +33,27 @@ GossipMenu::~GossipMenu() ClearMenu(); } -void GossipMenu::AddMenuItem(int32 menuItemId, uint8 icon, std::string const& message, uint32 sender, uint32 action, std::string const& boxMessage, uint32 boxMoney, bool coded /*= false*/) +uint32 GossipMenu::AddMenuItem(int32 optionIndex, uint8 icon, std::string const& message, uint32 sender, uint32 action, std::string const& boxMessage, uint32 boxMoney, bool coded /*= false*/) { ASSERT(_menuItems.size() <= GOSSIP_MAX_MENU_ITEMS); // Find a free new id - script case - if (menuItemId == -1) + if (optionIndex == -1) { - menuItemId = 0; + optionIndex = 0; if (!_menuItems.empty()) { for (GossipMenuItemContainer::const_iterator itr = _menuItems.begin(); itr != _menuItems.end(); ++itr) { - if (int32(itr->first) > menuItemId) + if (int32(itr->first) > optionIndex) break; - menuItemId = itr->first + 1; + optionIndex = itr->first + 1; } } } - GossipMenuItem& menuItem = _menuItems[menuItemId]; + GossipMenuItem& menuItem = _menuItems[optionIndex]; menuItem.MenuItemIcon = icon; menuItem.Message = message; @@ -63,6 +62,7 @@ void GossipMenu::AddMenuItem(int32 menuItemId, uint8 icon, std::string const& me menuItem.OptionType = action; menuItem.BoxMessage = boxMessage; menuItem.BoxMoney = boxMoney; + return optionIndex; } /** @@ -124,16 +124,18 @@ void GossipMenu::AddMenuItem(uint32 menuId, uint32 menuItemId, uint32 sender, ui } /// Add menu item with existing method. Menu item id -1 is also used in ADD_GOSSIP_ITEM macro. - AddMenuItem(-1, itr->second.OptionIcon, strOptionText, sender, action, strBoxText, itr->second.BoxMoney, itr->second.BoxCoded); + uint32 optionIndex = AddMenuItem(-1, itr->second.OptionID, strOptionText, sender, action, strBoxText, itr->second.BoxMoney, itr->second.BoxCoded); + AddGossipMenuItemData(optionIndex, itr->second.ActionMenuID, itr->second.ActionPoiID, itr->second.TrainerId); } } -void GossipMenu::AddGossipMenuItemData(uint32 menuItemId, uint32 gossipActionMenuId, uint32 gossipActionPoi) +void GossipMenu::AddGossipMenuItemData(uint32 optionIndex, uint32 gossipActionMenuId, uint32 gossipActionPoi, uint32 trainerId) { - GossipMenuItemData& itemData = _menuItemData[menuItemId]; + GossipMenuItemData& itemData = _menuItemData[optionIndex]; itemData.GossipActionMenuId = gossipActionMenuId; itemData.GossipActionPoi = gossipActionPoi; + itemData.TrainerId = trainerId; } uint32 GossipMenu::GetMenuItemSender(uint32 menuItemId) const @@ -163,6 +165,15 @@ bool GossipMenu::IsMenuItemCoded(uint32 menuItemId) const return itr->second.IsCoded; } +bool GossipMenu::HasMenuItemType(uint32 optionType) const +{ + for (auto const& menuItemPair : _menuItems) + if (menuItemPair.second.OptionType == optionType) + return true; + + return false; +} + void GossipMenu::ClearMenu() { _menuItems.clear(); @@ -188,7 +199,8 @@ void PlayerMenu::ClearMenus() void PlayerMenu::SendGossipMenu(uint32 titleTextId, ObjectGuid objectGUID) { - _gossipMenu.SetSenderGUID(objectGUID); + _interactionData.Reset(); + _interactionData.SourceGuid = objectGUID; WorldPacket data(SMSG_GOSSIP_MESSAGE, 100); // guess size data << uint64(objectGUID); @@ -246,7 +258,7 @@ void PlayerMenu::SendGossipMenu(uint32 titleTextId, ObjectGuid objectGUID) void PlayerMenu::SendCloseGossip() { - _gossipMenu.SetSenderGUID(ObjectGuid::Empty); + _interactionData.Reset(); WorldPacket data(SMSG_GOSSIP_COMPLETE, 0); _session->SendPacket(&data); diff --git a/src/server/game/Entities/Creature/GossipDef.h b/src/server/game/Entities/Creature/GossipDef.h index 203efb63c8e..38b23f882b5 100644 --- a/src/server/game/Entities/Creature/GossipDef.h +++ b/src/server/game/Entities/Creature/GossipDef.h @@ -146,6 +146,8 @@ struct GossipMenuItemData { uint32 GossipActionMenuId; // MenuId of the gossip triggered by this action uint32 GossipActionPoi; + + uint32 TrainerId; }; // need an ordered container @@ -165,17 +167,15 @@ class TC_GAME_API GossipMenu GossipMenu(); ~GossipMenu(); - void AddMenuItem(int32 menuItemId, uint8 icon, std::string const& message, uint32 sender, uint32 action, std::string const& boxMessage, uint32 boxMoney, bool coded = false); + uint32 AddMenuItem(int32 menuItemId, uint8 icon, std::string const& message, uint32 sender, uint32 action, std::string const& boxMessage, uint32 boxMoney, bool coded = false); void AddMenuItem(uint32 menuId, uint32 menuItemId, uint32 sender, uint32 action); void SetMenuId(uint32 menu_id) { _menuId = menu_id; } uint32 GetMenuId() const { return _menuId; } - void SetSenderGUID(ObjectGuid guid) { _senderGUID = guid; } - ObjectGuid GetSenderGUID() const { return _senderGUID; } void SetLocale(LocaleConstant locale) { _locale = locale; } LocaleConstant GetLocale() const { return _locale; } - void AddGossipMenuItemData(uint32 menuItemId, uint32 gossipActionMenuId, uint32 gossipActionPoi); + void AddGossipMenuItemData(uint32 optionIndex, uint32 gossipActionMenuId, uint32 gossipActionPoi, uint32 trainerId); uint32 GetMenuItemCount() const { @@ -208,6 +208,7 @@ class TC_GAME_API GossipMenu uint32 GetMenuItemSender(uint32 menuItemId) const; uint32 GetMenuItemAction(uint32 menuItemId) const; bool IsMenuItemCoded(uint32 menuItemId) const; + bool HasMenuItemType(uint32 optionType) const; void ClearMenu(); @@ -220,7 +221,6 @@ class TC_GAME_API GossipMenu GossipMenuItemContainer _menuItems; GossipMenuItemDataContainer _menuItemData; uint32 _menuId; - ObjectGuid _senderGUID; LocaleConstant _locale; }; @@ -254,6 +254,21 @@ class TC_GAME_API QuestMenu QuestMenuItemList _questMenuItems; }; +class InteractionData +{ +public: + InteractionData() { Reset(); } + + void Reset() + { + SourceGuid.Clear(); + TrainerId = 0; + } + + ObjectGuid SourceGuid; + uint32 TrainerId; +}; + class TC_GAME_API PlayerMenu { public: @@ -262,6 +277,7 @@ class TC_GAME_API PlayerMenu GossipMenu& GetGossipMenu() { return _gossipMenu; } QuestMenu& GetQuestMenu() { return _questMenu; } + InteractionData& GetInteractionData() { return _interactionData; } bool Empty() const { return _gossipMenu.Empty() && _questMenu.Empty(); } @@ -293,5 +309,6 @@ class TC_GAME_API PlayerMenu GossipMenu _gossipMenu; QuestMenu _questMenu; WorldSession* _session; + InteractionData _interactionData; }; #endif diff --git a/src/server/game/Entities/Creature/Trainer.cpp b/src/server/game/Entities/Creature/Trainer.cpp new file mode 100644 index 00000000000..c2cf1ee823a --- /dev/null +++ b/src/server/game/Entities/Creature/Trainer.cpp @@ -0,0 +1,256 @@ +/* + * 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 "Trainer.h" +#include "Creature.h" +#include "NPCPackets.h" +#include "Player.h" +#include "SpellInfo.h" +#include "SpellMgr.h" + +namespace Trainer +{ + bool Spell::IsCastable() const + { + return sSpellMgr->AssertSpellInfo(SpellId)->HasEffect(SPELL_EFFECT_LEARN_SPELL); + } + + Trainer::Trainer(uint32 trainerId, Type type, uint32 requirement, std::string greeting, std::vector spells) : _trainerId(trainerId), _type(type), _requirement(requirement), _spells(std::move(spells)) + { + _greeting[DEFAULT_LOCALE] = std::move(greeting); + } + + void Trainer::SendSpells(Creature const* npc, Player const* player, LocaleConstant locale) const + { + float reputationDiscount = player->GetReputationPriceDiscount(npc); + bool canLearnPrimaryProfession = player->GetFreePrimaryProfessionPoints() > 0; + + WorldPackets::NPC::TrainerList trainerList; + trainerList.TrainerGUID = npc->GetGUID(); + trainerList.TrainerType = AsUnderlyingType(_type); + trainerList.TrainerID = _trainerId; + trainerList.Greeting = GetGreeting(locale); + trainerList.Spells.reserve(_spells.size()); + for (Spell const& trainerSpell : _spells) + { + if (!player->IsSpellFitByClassAndRace(trainerSpell.SpellId)) + continue; + + SpellInfo const* trainerSpellInfo = sSpellMgr->AssertSpellInfo(trainerSpell.SpellId); + + bool primaryProfessionFirstRank = false; + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (trainerSpellInfo->Effects[i].Effect != SPELL_EFFECT_LEARN_SPELL) + continue; + + SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainerSpellInfo->Effects[i].TriggerSpell); + if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank()) + primaryProfessionFirstRank = true; + } + + trainerList.Spells.emplace_back(); + WorldPackets::NPC::TrainerListSpell& trainerListSpell = trainerList.Spells.back(); + trainerListSpell.SpellID = trainerSpell.SpellId; + trainerListSpell.Usable = AsUnderlyingType(GetSpellState(player, &trainerSpell)); + trainerListSpell.MoneyCost = int32(trainerSpell.MoneyCost * reputationDiscount); + trainerListSpell.ReqLevel = trainerSpell.ReqLevel; + trainerListSpell.ReqSkillLine = trainerSpell.ReqSkillLine; + trainerListSpell.ReqSkillRank = trainerSpell.ReqSkillRank; + trainerListSpell.ProfessionDialog = primaryProfessionFirstRank; + trainerListSpell.ProfessionButton = primaryProfessionFirstRank ? canLearnPrimaryProfession : false; + std::copy(trainerSpell.ReqAbility.begin(), trainerSpell.ReqAbility.end(), trainerListSpell.ReqAbility.begin()); + } + + player->SendDirectMessage(trainerList.Write()); + } + + void Trainer::TeachSpell(Creature const* npc, Player* player, uint32 spellId) const + { + if (!IsTrainerValidForPlayer(player)) + return; + + Spell const* trainerSpell = GetSpell(spellId); + if (!trainerSpell) + { + SendTeachFailure(npc, player, spellId, FailReason::Unavailable); + return; + } + + if (!CanTeachSpell(player, trainerSpell)) + { + SendTeachFailure(npc, player, spellId, FailReason::NotEnoughSkill); + return; + } + + float reputationDiscount = player->GetReputationPriceDiscount(npc); + int32 moneyCost = int32(trainerSpell->MoneyCost * reputationDiscount); + if (player->GetMoney() < uint64(moneyCost)) + { + SendTeachFailure(npc, player, spellId, FailReason::NotEnoughMoney); + return; + } + + player->ModifyMoney(-moneyCost); + npc->SendPlaySpellVisualKit(179, 0, 0); // 53 SpellCastDirected + player->SendPlaySpellVisualKit(362, 1, 0); // 113 EmoteSalute + + // learn explicitly or cast explicitly + if (trainerSpell->IsCastable()) + player->CastSpell(player, trainerSpell->SpellId, true); + else + player->LearnSpell(trainerSpell->SpellId, false); + + SendTeachSucceeded(npc, player, spellId); + } + + Spell const* Trainer::GetSpell(uint32 spellId) const + { + auto itr = std::find_if(_spells.begin(), _spells.end(), [spellId](Spell const& trainerSpell) + { + return trainerSpell.SpellId == spellId; + }); + + if (itr != _spells.end()) + return &(*itr); + + return nullptr; + } + + bool Trainer::CanTeachSpell(Player const* player, Spell const* trainerSpell) const + { + SpellState state = GetSpellState(player, trainerSpell); + if (state != SpellState::Available) + return false; + + SpellInfo const* trainerSpellInfo = sSpellMgr->AssertSpellInfo(trainerSpell->SpellId); + if (trainerSpellInfo->IsPrimaryProfessionFirstRank() && !player->GetFreePrimaryProfessionPoints()) + return false; + + return true; + } + + SpellState Trainer::GetSpellState(Player const* player, Spell const* trainerSpell) const + { + if (player->HasSpell(trainerSpell->SpellId)) + return SpellState::Known; + + // check race/class requirement + if (!player->IsSpellFitByClassAndRace(trainerSpell->SpellId)) + return SpellState::Unavailable; + + // check skill requirement + if (trainerSpell->ReqSkillLine && player->GetBaseSkillValue(trainerSpell->ReqSkillLine) < trainerSpell->ReqSkillRank) + return SpellState::Unavailable; + + for (int32 reqAbility : trainerSpell->ReqAbility) + if (reqAbility && !player->HasSpell(reqAbility)) + return SpellState::Unavailable; + + // check level requirement + if (player->getLevel() < trainerSpell->ReqLevel) + return SpellState::Unavailable; + + // check ranks + bool hasLearnSpellEffect = false; + bool knowsAllLearnedSpells = true; + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(trainerSpell->SpellId); + if (!spellInfo || spellInfo->Effects[i].Effect != SPELL_EFFECT_LEARN_SPELL) + continue; + + hasLearnSpellEffect = true; + if (!player->HasSpell(spellInfo->Effects[i].TriggerSpell)) + knowsAllLearnedSpells = false; + + if (uint32 previousRankSpellId = sSpellMgr->GetPrevSpellInChain(spellInfo->Effects[i].TriggerSpell)) + if (!player->HasSpell(previousRankSpellId)) + return SpellState::Unavailable; + } + + if (!hasLearnSpellEffect) + { + if (uint32 previousRankSpellId = sSpellMgr->GetPrevSpellInChain(trainerSpell->SpellId)) + if (!player->HasSpell(previousRankSpellId)) + return SpellState::Unavailable; + } + else if (knowsAllLearnedSpells) + return SpellState::Known; + + // check additional spell requirement + for (auto const& requirePair : sSpellMgr->GetSpellsRequiredForSpellBounds(trainerSpell->SpellId)) + if (!player->HasSpell(requirePair.second)) + return SpellState::Unavailable; + + return SpellState::Available; + } + + bool Trainer::IsTrainerValidForPlayer(Player const* player) const + { + if (!GetTrainerRequirement()) + return true; + + switch (GetTrainerType()) + { + case Type::Class: + case Type::Pet: + // check class for class trainers + return player->getClass() == GetTrainerRequirement(); + case Type::Mount: + // check race for mount trainers + return player->getRace() == GetTrainerRequirement(); + case Type::Tradeskill: + // check spell for profession trainers + return player->HasSpell(GetTrainerRequirement()); + default: + break; + } + + return true; + } + + void Trainer::SendTeachFailure(Creature const* npc, Player const* player, uint32 spellId, FailReason reason) const + { + WorldPackets::NPC::TrainerBuyFailed trainerBuyFailed; + trainerBuyFailed.TrainerGUID = npc->GetGUID(); + trainerBuyFailed.SpellID = spellId; + trainerBuyFailed.TrainerFailedReason = AsUnderlyingType(reason); + player->SendDirectMessage(trainerBuyFailed.Write()); + } + + void Trainer::SendTeachSucceeded(Creature const* npc, Player const* player, uint32 spellId) const + { + WorldPackets::NPC::TrainerBuySucceeded trainerBuySucceeded; + trainerBuySucceeded.TrainerGUID = npc->GetGUID(); + trainerBuySucceeded.SpellID = spellId; + player->SendDirectMessage(trainerBuySucceeded.Write()); + } + + std::string const& Trainer::GetGreeting(LocaleConstant locale) const + { + if (_greeting[locale].empty()) + return _greeting[DEFAULT_LOCALE]; + + return _greeting[locale]; + } + + void Trainer::AddGreetingLocale(LocaleConstant locale, std::string greeting) + { + _greeting[locale] = std::move(greeting); + } +} diff --git a/src/server/game/Entities/Creature/Trainer.h b/src/server/game/Entities/Creature/Trainer.h new file mode 100644 index 00000000000..09f34fcff35 --- /dev/null +++ b/src/server/game/Entities/Creature/Trainer.h @@ -0,0 +1,96 @@ +/* + * 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 . + */ + +#ifndef Trainer_h__ +#define Trainer_h__ + +#include "Common.h" +#include +#include + +class Creature; +class ObjectMgr; +class Player; + +namespace Trainer +{ + enum class Type : uint32 + { + Class = 0, + Mount = 1, + Tradeskill = 2, + Pet = 3 + }; + + enum class SpellState : uint8 + { + Known = 0, + Available = 1, + Unavailable = 2 + }; + + enum class FailReason : uint32 + { + Unavailable = 0, + NotEnoughMoney = 1, + NotEnoughSkill = 2 + }; + + struct Spell + { + uint32 SpellId = 0; + uint32 MoneyCost = 0; + uint32 ReqSkillLine = 0; + uint32 ReqSkillRank = 0; + std::array ReqAbility = { }; + uint8 ReqLevel = 0; + + bool IsCastable() const; + }; + + class Trainer + { + public: + Trainer(uint32 trainerId, Type type, uint32 requirement, std::string greeting, std::vector spells); + + void SendSpells(Creature const* npc, Player const* player, LocaleConstant locale) const; + void TeachSpell(Creature const* npc, Player* player, uint32 spellId) const; + + Type GetTrainerType() const { return _type; } + uint32 GetTrainerRequirement() const { return _requirement; } + bool IsTrainerValidForPlayer(Player const* player) const; + + private: + Spell const* GetSpell(uint32 spellId) const; + bool CanTeachSpell(Player const* player, Spell const* trainerSpell) const; + SpellState GetSpellState(Player const* player, Spell const* trainerSpell) const; + void SendTeachFailure(Creature const* npc, Player const* player, uint32 spellId, FailReason reason) const; + void SendTeachSucceeded(Creature const* npc, Player const* player, uint32 spellId) const; + std::string const& GetGreeting(LocaleConstant locale) const; + + friend ObjectMgr; + void AddGreetingLocale(LocaleConstant locale, std::string greeting); + + uint32 _trainerId; + Type _type; + uint32 _requirement; + std::vector _spells; + std::array _greeting; + }; +} + +#endif // Trainer_h__ diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 1b5085920e1..57ce15bb7f2 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1974,19 +1974,19 @@ void Object::ForceValuesUpdateAtIndex(uint32 i) AddToObjectUpdateIfNeeded(); } -void WorldObject::SendMessageToSet(WorldPacket* data, bool self) +void WorldObject::SendMessageToSet(WorldPacket* data, bool self) const { if (IsInWorld()) SendMessageToSetInRange(data, GetVisibilityRange(), self); } -void WorldObject::SendMessageToSetInRange(WorldPacket* data, float dist, bool /*self*/) +void WorldObject::SendMessageToSetInRange(WorldPacket* data, float dist, bool /*self*/) const { Trinity::MessageDistDeliverer notifier(this, data, dist); Cell::VisitWorldObjects(this, notifier, dist); } -void WorldObject::SendMessageToSet(WorldPacket* data, Player const* skipped_rcvr) +void WorldObject::SendMessageToSet(WorldPacket* data, Player const* skipped_rcvr) const { Trinity::MessageDistDeliverer notifier(this, data, GetVisibilityRange(), false, skipped_rcvr); Cell::VisitWorldObjects(this, notifier, GetVisibilityRange()); diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 2a59ab1e695..40cd9f02ac0 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -367,9 +367,9 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation virtual void CleanupsBeforeDelete(bool finalCleanup = true); // used in destructor or explicitly before mass creature delete to remove cross-references to already deleted units - virtual void SendMessageToSet(WorldPacket* data, bool self); - virtual void SendMessageToSetInRange(WorldPacket* data, float dist, bool self); - virtual void SendMessageToSet(WorldPacket* data, Player const* skipped_rcvr); + virtual void SendMessageToSet(WorldPacket* data, bool self) const; + virtual void SendMessageToSetInRange(WorldPacket* data, float dist, bool self) const; + virtual void SendMessageToSet(WorldPacket* data, Player const* skipped_rcvr) const; virtual uint8 getLevelForTarget(WorldObject const* /*target*/) const { return 1; } diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index a3ddb302e57..671164dd9a9 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -89,6 +89,7 @@ #include "SpellMgr.h" #include "TicketMgr.h" #include "TradeData.h" +#include "Trainer.h" #include "Transport.h" #include "UpdateData.h" #include "UpdateFieldFlags.h" @@ -3085,6 +3086,53 @@ void Player::SendInitialSpells() SendDirectMessage(&data); } +void Player::SendUnlearnSpells() +{ + WorldPacket data(SMSG_SEND_UNLEARN_SPELLS, 4 + 4 * m_spells.size()); + + uint32 spellCount = 0; + size_t countPos = data.wpos(); + data << uint32(spellCount); // spell count placeholder + + for (PlayerSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr) + { + if (itr->second->state == PLAYERSPELL_REMOVED) + continue; + + if (itr->second->active || itr->second->disabled) + continue; + + auto skillLineAbilities = sSpellMgr->GetSkillLineAbilityMapBounds(itr->first); + if (skillLineAbilities.first == skillLineAbilities.second) + continue; + + bool hasSupercededSpellInfoInClient = false; + for (auto boundsItr = skillLineAbilities.first; boundsItr != skillLineAbilities.second; ++boundsItr) + { + if (boundsItr->second->forward_spellid) + { + hasSupercededSpellInfoInClient = true; + break; + } + } + + if (hasSupercededSpellInfoInClient) + continue; + + uint32 nextRank = sSpellMgr->GetNextSpellInChain(itr->first); + if (!nextRank || !HasSpell(nextRank)) + continue; + + data << uint32(itr->first); + + ++spellCount; + } + + data.put(countPos, spellCount); // write real count value + + SendDirectMessage(&data); +} + void Player::RemoveMail(uint32 id) { for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr) @@ -3232,6 +3280,31 @@ bool Player::AddTalent(uint32 spellId, uint8 spec, bool learning) return false; } +static bool IsUnlearnSpellsPacketNeededForSpell(uint32 spellId) +{ + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(spellId); + if (spellInfo->IsRanked() && !spellInfo->IsStackableWithRanks()) + { + auto skillLineAbilities = sSpellMgr->GetSkillLineAbilityMapBounds(spellId); + if (skillLineAbilities.first != skillLineAbilities.second) + { + bool hasSupercededSpellInfoInClient = false; + for (auto boundsItr = skillLineAbilities.first; boundsItr != skillLineAbilities.second; ++boundsItr) + { + if (boundsItr->second->forward_spellid) + { + hasSupercededSpellInfoInClient = true; + break; + } + } + + return !hasSupercededSpellInfoInClient; + } + } + + return false; +} + bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading /*= false*/, uint32 fromSkill /*= 0*/) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); @@ -3331,6 +3404,8 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent { if (next_active_spell_id) SendSupercededSpell(spellId, next_active_spell_id); + if (IsUnlearnSpellsPacketNeededForSpell(spellId)) + SendUnlearnSpells(); else { WorldPacket data(SMSG_REMOVED_SPELL, 4); @@ -3408,8 +3483,10 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent newspell->dependent = dependent; newspell->disabled = disabled; + bool needsUnlearnSpellsPacket = false; + // replace spells in action bars and spellbook to bigger rank if only one spell rank must be accessible - if (newspell->active && !newspell->disabled && !spellInfo->IsStackableWithRanks() && spellInfo->IsRanked() != 0) + if (newspell->active && !newspell->disabled && !spellInfo->IsStackableWithRanks() && spellInfo->IsRanked()) { for (PlayerSpellMap::iterator itr2 = m_spells.begin(); itr2 != m_spells.end(); ++itr2) { @@ -3427,7 +3504,10 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent if (spellInfo->IsHighRankOf(i_spellInfo)) { if (IsInWorld()) // not send spell (re-/over-)learn packets at loading + { SendSupercededSpell(itr2->first, spellId); + needsUnlearnSpellsPacket = needsUnlearnSpellsPacket || IsUnlearnSpellsPacketNeededForSpell(itr2->first); + } // mark old spell as disable (SMSG_SUPERCEDED_SPELL replace it in client by new) itr2->second->active = false; @@ -3438,7 +3518,10 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent else { if (IsInWorld()) // not send spell (re-/over-)learn packets at loading + { SendSupercededSpell(spellId, itr2->first); + needsUnlearnSpellsPacket = needsUnlearnSpellsPacket || IsUnlearnSpellsPacketNeededForSpell(spellId); + } // mark new spell as disable (not learned yet for client and will not learned) newspell->active = false; @@ -3452,6 +3535,9 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent m_spells[spellId] = newspell; + if (needsUnlearnSpellsPacket) + SendUnlearnSpells(); + // return false if spell disabled if (newspell->disabled) return false; @@ -3788,6 +3874,7 @@ void Player::RemoveSpell(uint32 spell_id, bool disabled, bool learn_low_rank) // activate lesser rank in spellbook/action bar, and cast it if need bool prev_activate = false; + bool needsUnlearnSpellsPacket = false; if (uint32 prev_id = sSpellMgr->GetPrevSpellInChain(spell_id)) { @@ -3820,6 +3907,7 @@ void Player::RemoveSpell(uint32 spell_id, bool disabled, bool learn_low_rank) { // downgrade spell ranks in spellbook and action bar SendSupercededSpell(spell_id, prev_id); + needsUnlearnSpellsPacket = IsUnlearnSpellsPacketNeededForSpell(prev_id); prev_activate = true; } } @@ -3850,6 +3938,9 @@ void Player::RemoveSpell(uint32 spell_id, bool disabled, bool learn_low_rank) if (sWorld->getBoolConfig(CONFIG_OFFHAND_CHECK_AT_SPELL_UNLEARN)) AutoUnequipOffhandIfNeed(); + if (needsUnlearnSpellsPacket) + SendUnlearnSpells(); + // remove from spell book if not replaced by lesser rank if (!prev_activate) { @@ -4100,74 +4191,6 @@ bool Player::HasActiveSpell(uint32 spell) const itr->second->active && !itr->second->disabled); } -TrainerSpellState Player::GetTrainerSpellState(TrainerSpell const* trainer_spell) const -{ - if (!trainer_spell) - return TRAINER_SPELL_RED; - - bool hasSpell = true; - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - { - if (!trainer_spell->ReqAbility[i]) - continue; - - if (!HasSpell(trainer_spell->ReqAbility[i])) - { - hasSpell = false; - break; - } - } - // known spell - if (hasSpell) - return TRAINER_SPELL_GRAY; - - // check skill requirement - if (trainer_spell->ReqSkillLine && GetBaseSkillValue(trainer_spell->ReqSkillLine) < trainer_spell->ReqSkillRank) - return TRAINER_SPELL_RED; - - // check level requirement - if (getLevel() < trainer_spell->ReqLevel) - return TRAINER_SPELL_RED; - - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - { - if (!trainer_spell->ReqAbility[i]) - continue; - - // check race/class requirement - if (!IsSpellFitByClassAndRace(trainer_spell->ReqAbility[i])) - return TRAINER_SPELL_RED; - - if (uint32 prevSpell = sSpellMgr->GetPrevSpellInChain(trainer_spell->ReqAbility[i])) - { - // check prev.rank requirement - if (!HasSpell(prevSpell)) - return TRAINER_SPELL_RED; - } - - SpellsRequiringSpellMapBounds spellsRequired = sSpellMgr->GetSpellsRequiredForSpellBounds(trainer_spell->ReqAbility[i]); - for (SpellsRequiringSpellMap::const_iterator itr = spellsRequired.first; itr != spellsRequired.second; ++itr) - { - // check additional spell requirement - if (!HasSpell(itr->second)) - return TRAINER_SPELL_RED; - } - } - - // check primary prof. limit - // first rank of primary profession spell when there are no professions available is disabled - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - { - if (!trainer_spell->ReqAbility[i]) - continue; - SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainer_spell->ReqAbility[i]); - if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank() && (GetFreePrimaryProfessionPoints() == 0)) - return TRAINER_SPELL_GREEN_DISABLED; - } - - return TRAINER_SPELL_GREEN; -} - /** * Deletes a character from the database * @@ -5629,26 +5652,21 @@ bool Player::UpdateSkill(uint32 skill_id, uint32 step) if (!max || !value || value >= max) return false; - if (value < max) - { - uint32 new_value = value + step; - if (new_value > max) - new_value = max; + uint32 new_value = value + step; + if (new_value > max) + new_value = max; - SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, new_value); - if (itr->second.uState != SKILL_NEW) - itr->second.uState = SKILL_CHANGED; + SetSkillRank(itr->second.pos, new_value); + if (itr->second.uState != SKILL_NEW) + itr->second.uState = SKILL_CHANGED; - UpdateSkillEnchantments(skill_id, value, new_value); - UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, skill_id); + UpdateSkillEnchantments(skill_id, value, new_value); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, skill_id); - if (skill_id == SKILL_ARCHAEOLOGY) - _archaeology->Update(); + if (skill_id == SKILL_ARCHAEOLOGY) + _archaeology->Update(); - return true; - } - - return false; + return true; } inline int SkillGainChance(uint32 SkillValue, uint32 GrayLevel, uint32 GreenLevel, uint32 YellowLevel) @@ -5920,7 +5938,7 @@ void Player::UpdateSkillsForLevel() for (SkillStatusMap::iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end(); ++itr) { - if (itr->second.uState == SKILL_DELETED) + if (itr->second.uState == SKILL_DELETED || !GetSkillRank(itr->second.pos)) continue; uint32 pskill = itr->first; @@ -5942,8 +5960,8 @@ void Player::UpdateSkillsForLevel() /// update only level dependent max skill values if (max != 1) { - SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, maxSkill); - SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, maxSkill); + SetSkillRank(itr->second.pos, maxSkill); + SetSkillMaxRank(itr->second.pos, maxSkill); if (itr->second.uState != SKILL_NEW) itr->second.uState = SKILL_CHANGED; } @@ -5954,7 +5972,7 @@ void Player::UpdateWeaponsSkillsToMaxSkillsForLevel() { for (SkillStatusMap::iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end(); ++itr) { - if (itr->second.uState == SKILL_DELETED) + if (itr->second.uState == SKILL_DELETED || !GetSkillRank(itr->second.pos)) continue; uint32 pskill = itr->first; @@ -5975,7 +5993,7 @@ void Player::UpdateWeaponsSkillsToMaxSkillsForLevel() if (max > 1) { - SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, max); + SetSkillRank(itr->second.pos, max); if (itr->second.uState != SKILL_NEW) itr->second.uState = SKILL_CHANGED; @@ -5983,6 +6001,21 @@ void Player::UpdateWeaponsSkillsToMaxSkillsForLevel() } } +void Player::InitializeSkillFields() +{ + uint32 i = 0; + for (SkillLineEntry const* skillLine : sSkillLineStore) + { + if (GetSkillRaceClassInfo(skillLine->id, getRace(), getClass())) + { + SetSkillLineId(i, skillLine->id); + mSkillStatus.insert(SkillStatusMap::value_type(skillLine->id, SkillStatusData(i, SKILL_UNCHANGED))); + if (++i >= PLAYER_MAX_SKILLS) + break; + } + } +} + // This functions sets a skill line value (and adds if doesn't exist yet) // To "remove" a skill line, set it's values to zero void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal) @@ -5996,9 +6029,7 @@ void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal) //has skill if (itr != mSkillStatus.end() && itr->second.uState != SKILL_DELETED) { - uint16 field = itr->second.pos / 2; - uint8 offset = itr->second.pos & 1; // itr->second.pos % 2 - currVal = GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset); + currVal = GetSkillRank(itr->second.pos); if (newVal) { // if skill value is going down, update enchantments before setting the new value @@ -6006,10 +6037,10 @@ void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal) UpdateSkillEnchantments(id, currVal, newVal); // update step - SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, step); + SetSkillStep(itr->second.pos, step); // update value - SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, newVal); - SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, maxVal); + SetSkillRank(itr->second.pos, newVal); + SetSkillMaxRank(itr->second.pos, maxVal); if (itr->second.uState != SKILL_NEW) itr->second.uState = SKILL_CHANGED; @@ -6031,24 +6062,20 @@ void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal) //remove enchantments needing this skill UpdateSkillEnchantments(id, currVal, 0); // clear skill fields - SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, 0); - SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, 0); - SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, 0); - SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, 0); - SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, 0); - SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, 0); + SetSkillStep(itr->second.pos, 0); + SetSkillRank(itr->second.pos, 0); + SetSkillMaxRank(itr->second.pos, 0); + SetSkillTempBonus(itr->second.pos, 0); + SetSkillPermBonus(itr->second.pos, 0); // mark as deleted or simply remove from map if not saved yet if (itr->second.uState != SKILL_NEW) itr->second.uState = SKILL_DELETED; - else - mSkillStatus.erase(itr); // remove all spells that related to this skill - for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j) - if (SkillLineAbilityEntry const* pAbility = sSkillLineAbilityStore.LookupEntry(j)) - if (pAbility->skillId == id) - RemoveSpell(sSpellMgr->GetFirstSpellInChain(pAbility->spellId)); + for (SkillLineAbilityEntry const* skillLineAbilities : sSkillLineAbilityStore) + if (skillLineAbilities->skillId == id) + RemoveSpell(sSpellMgr->GetFirstSpellInChain(skillLineAbilities->spellId)); // Clear profession lines if (GetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1) == id) @@ -6061,73 +6088,86 @@ void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal) _archaeology->UnLearn(); } } - else if (newVal) //add + else //add { - currVal = 0; - for (uint32 i = 0; i < PLAYER_MAX_SKILLS; ++i) + // Check if the player already has a skill, otherwise pick a empty skill slot if available + uint8 skillSlot = itr != mSkillStatus.end() ? itr->second.pos : 0; + if (!skillSlot) { - uint16 field = i / 2; - uint8 offset = i & 1; // i % 2 - - if (!GetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset)) + for (uint32 i = 0; i < PLAYER_MAX_SKILLS; ++i) { - SkillLineEntry const* skillEntry = sSkillLineStore.LookupEntry(id); - if (!skillEntry) + if (!GetUInt16Value(PLAYER_SKILL_LINEID_0 + i / 2, i % 1)) { - TC_LOG_ERROR("misc", "Skill not found in SkillLineStore: skill #%u", id); - return; + skillSlot = i; + break; } - - SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, id); - if (skillEntry->categoryId == SKILL_CATEGORY_PROFESSION) - { - if (!GetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1)) - SetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1, id); - else if (!GetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1 + 1)) - SetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1 + 1, id); - } - - SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, step); - SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, newVal); - SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, maxVal); - - UpdateSkillEnchantments(id, currVal, newVal); - - // insert new entry or update if not deleted old entry yet - if (itr != mSkillStatus.end()) - { - itr->second.pos = i; - itr->second.uState = SKILL_CHANGED; - } - else - mSkillStatus.insert(SkillStatusMap::value_type(id, SkillStatusData(i, SKILL_NEW))); - - // apply skill bonuses - SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, 0); - SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, 0); - // temporary bonuses - AuraEffectList const& mModSkill = GetAuraEffectsByType(SPELL_AURA_MOD_SKILL); - for (AuraEffectList::const_iterator j = mModSkill.begin(); j != mModSkill.end(); ++j) - if ((*j)->GetMiscValue() == int32(id)) - (*j)->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true); - - // permanent bonuses - AuraEffectList const& mModSkillTalent = GetAuraEffectsByType(SPELL_AURA_MOD_SKILL_TALENT); - for (AuraEffectList::const_iterator j = mModSkillTalent.begin(); j != mModSkillTalent.end(); ++j) - if ((*j)->GetMiscValue() == int32(id)) - (*j)->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true); - - // archaeology skill learned - if (id == SKILL_ARCHAEOLOGY) - _archaeology->Learn(); - - // Learn all spells for skill - LearnSkillRewardedSpells(id, newVal); - UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, id); - UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL, id); - return; } } + + if (!skillSlot) + { + TC_LOG_ERROR("misc", "Tried to add skill #%u but the player cannot have additional skills", id); + return; + } + + SkillLineEntry const* skillEntry = sSkillLineStore.LookupEntry(id); + if (!skillEntry) + { + TC_LOG_ERROR("misc", "Skill not found in SkillLineStore: skill #%u", id); + return; + } + + if (skillEntry->categoryId == SKILL_CATEGORY_PROFESSION) + { + if (!GetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1)) + SetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1, id); + else if (!GetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1 + 1)) + SetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1 + 1, id); + } + + if (itr == mSkillStatus.end()) + SetSkillLineId(skillSlot, id); + + SetSkillStep(skillSlot, step); + SetSkillRank(skillSlot, newVal); + SetSkillMaxRank(skillSlot, maxVal); + + // apply skill bonuses + SetSkillTempBonus(skillSlot, 0); + SetSkillPermBonus(skillSlot, 0); + + UpdateSkillEnchantments(id, 0, newVal); + + if (itr != mSkillStatus.end()) + itr->second.uState = SKILL_CHANGED; + else + mSkillStatus.insert(SkillStatusMap::value_type(id, SkillStatusData(skillSlot, SKILL_NEW))); + + if (newVal) + { + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, id); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL, id); + + // temporary bonuses + AuraEffectList const& mModSkill = GetAuraEffectsByType(SPELL_AURA_MOD_SKILL); + for (AuraEffectList::const_iterator j = mModSkill.begin(); j != mModSkill.end(); ++j) + if ((*j)->GetMiscValue() == int32(id)) + (*j)->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true); + + // permanent bonuses + AuraEffectList const& mModSkillTalent = GetAuraEffectsByType(SPELL_AURA_MOD_SKILL_TALENT); + for (AuraEffectList::const_iterator j = mModSkillTalent.begin(); j != mModSkillTalent.end(); ++j) + if ((*j)->GetMiscValue() == int32(id)) + (*j)->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true); + + // archaeology skill learned + if (id == SKILL_ARCHAEOLOGY) + _archaeology->Learn(); + + // Learn all spells for skill + LearnSkillRewardedSpells(id, newVal); + } + return; } if (Guild* guild = GetGuild()) @@ -6140,7 +6180,7 @@ bool Player::HasSkill(uint32 skill) const return false; SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); - return (itr != mSkillStatus.end() && itr->second.uState != SKILL_DELETED); + return (itr != mSkillStatus.end() && itr->second.uState != SKILL_DELETED && GetSkillRank(itr->second.pos)); } uint16 Player::GetSkillStep(uint16 skill) const @@ -6149,7 +6189,7 @@ uint16 Player::GetSkillStep(uint16 skill) const return 0; SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); - if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !GetSkillRank(itr->second.pos)) return 0; return GetUInt16Value(PLAYER_SKILL_STEP_0 + itr->second.pos / 2, itr->second.pos & 1); @@ -6161,7 +6201,7 @@ uint16 Player::GetSkillValue(uint32 skill) const return 0; SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); - if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !GetSkillRank(itr->second.pos)) return 0; uint16 field = itr->second.pos / 2; @@ -6179,7 +6219,7 @@ uint16 Player::GetMaxSkillValue(uint32 skill) const return 0; SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); - if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !GetSkillRank(itr->second.pos)) return 0; uint16 field = itr->second.pos / 2; @@ -6197,7 +6237,7 @@ uint16 Player::GetPureMaxSkillValue(uint32 skill) const return 0; SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); - if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || GetSkillRank(itr->second.pos)) return 0; uint16 field = itr->second.pos / 2; @@ -6212,7 +6252,7 @@ uint16 Player::GetBaseSkillValue(uint32 skill) const return 0; SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); - if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !GetSkillRank(itr->second.pos)) return 0; uint16 field = itr->second.pos / 2; @@ -6229,7 +6269,7 @@ uint16 Player::GetPureSkillValue(uint32 skill) const return 0; SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); - if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !GetSkillRank(itr->second.pos)) return 0; uint16 field = itr->second.pos / 2; @@ -6244,7 +6284,7 @@ int16 Player::GetSkillPermBonusValue(uint32 skill) const return 0; SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); - if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !GetSkillRank(itr->second.pos)) return 0; uint16 field = itr->second.pos / 2; @@ -6259,7 +6299,7 @@ int16 Player::GetSkillTempBonusValue(uint32 skill) const return 0; SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); - if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !GetSkillRank(itr->second.pos)) return 0; uint16 field = itr->second.pos / 2; @@ -6420,7 +6460,7 @@ bool Player::UpdatePosition(float x, float y, float z, float orientation, bool t return true; } -void Player::SendMessageToSetInRange(WorldPacket* data, float dist, bool self) +void Player::SendMessageToSetInRange(WorldPacket* data, float dist, bool self) const { if (self) SendDirectMessage(data); @@ -6429,7 +6469,7 @@ void Player::SendMessageToSetInRange(WorldPacket* data, float dist, bool self) Cell::VisitWorldObjects(this, notifier, dist); } -void Player::SendMessageToSetInRange(WorldPacket* data, float dist, bool self, bool own_team_only) +void Player::SendMessageToSetInRange(WorldPacket* data, float dist, bool self, bool own_team_only) const { if (self) SendDirectMessage(data); @@ -6438,7 +6478,7 @@ void Player::SendMessageToSetInRange(WorldPacket* data, float dist, bool self, b Cell::VisitWorldObjects(this, notifier, dist); } -void Player::SendMessageToSet(WorldPacket* data, Player const* skipped_rcvr) +void Player::SendMessageToSet(WorldPacket* data, Player const* skipped_rcvr) const { if (skipped_rcvr != this) SendDirectMessage(data); @@ -14331,15 +14371,15 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool } case GOSSIP_OPTION_LEARNDUALSPEC: case GOSSIP_OPTION_DUALSPEC_INFO: - if (!(GetSpecsCount() == 1 && creature->isCanTrainingAndResetTalentsOf(this) && !(getLevel() < sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL)))) + if (!(GetSpecsCount() == 1 && creature->CanResetTalents(this, false) && !(getLevel() < sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL)))) canTalk = false; break; case GOSSIP_OPTION_UNLEARNTALENTS: - if (!creature->isCanTrainingAndResetTalentsOf(this)) + if (!creature->CanResetTalents(this, false)) canTalk = false; break; case GOSSIP_OPTION_UNLEARNPETTALENTS: - if (!GetPet() || GetPet()->getPetType() != HUNTER_PET || GetPet()->m_spells.size() <= 1 || creature->GetCreatureTemplate()->trainer_type != TRAINER_TYPE_PETS || creature->GetCreatureTemplate()->trainer_class != CLASS_HUNTER) + if (!GetPet() || GetPet()->getPetType() != HUNTER_PET || GetPet()->m_spells.size() <= 1 || !creature->CanResetTalents(this, true)) canTalk = false; break; case GOSSIP_OPTION_TAXIVENDOR: @@ -14358,13 +14398,6 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool canTalk = false; break; case GOSSIP_OPTION_TRAINER: - if (creature->GetCreatureTemplate()->trainer_type != TRAINER_TYPE_TRADESKILLS && !creature->IsClassTrainerOf(this)) - { - TC_LOG_ERROR("sql.sql", "GOSSIP_OPTION_TRAINER:: Player %s (GUID: %u) requested wrong gossip menu: %u with wrong class: %u at Creature: %s (Entry: %u, Trainer Class: %u)", - GetName().c_str(), GetGUID().GetCounter(), menu->GetGossipMenu().GetMenuId(), getClass(), creature->GetName().c_str(), creature->GetEntry(), creature->GetCreatureTemplate()->trainer_class); - canTalk = false; - } - // no break; case GOSSIP_OPTION_GOSSIP: case GOSSIP_OPTION_SPIRITGUIDE: case GOSSIP_OPTION_INNKEEPER: @@ -14432,7 +14465,7 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool } 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); + menu->GetGossipMenu().AddGossipMenuItemData(itr->second.OptionID, itr->second.ActionMenuID, itr->second.ActionPoiID, itr->second.TrainerId); } } } @@ -14544,7 +14577,7 @@ void Player::OnGossipSelect(WorldObject* source, uint32 gossipListId, uint32 men GetSession()->SendStablePet(guid); break; case GOSSIP_OPTION_TRAINER: - GetSession()->SendTrainerList(guid); + GetSession()->SendTrainerList(source->ToCreature(), menuItemData->TrainerId); break; case GOSSIP_OPTION_LEARNDUALSPEC: if (GetSpecsCount() == 1 && getLevel() >= sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL)) @@ -17479,6 +17512,7 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder* holder) if (!_LoadHomeBind(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_HOME_BIND))) return false; + InitializeSkillFields(); InitPrimaryProfessions(); // to max set before any spell loaded // init saved position, and fix it later if problematic @@ -23806,10 +23840,7 @@ void Player::SendInitialPacketsBeforeAddToMap() SendDirectMessage(&data); SendInitialSpells(); - - data.Initialize(SMSG_SEND_UNLEARN_SPELLS, 4); - data << uint32(0); // count, for (count) uint32; - SendDirectMessage(&data); + SendUnlearnSpells(); SendInitialActionButtons(); m_reputationMgr->SendInitialReputations(); @@ -24149,7 +24180,7 @@ void Player::LearnDefaultSkill(uint32 skillId, uint16 rank) else if (getClass() == CLASS_DEATH_KNIGHT) skillValue = std::min(std::max({ uint16(1), uint16((getLevel() - 1) * 5) }), maxValue); - SetSkill(skillId, rank, skillValue, maxValue); + SetSkill(skillId, 1, skillValue, maxValue); break; } default: @@ -24286,6 +24317,55 @@ void Player::LearnSkillRewardedSpells(uint32 skillId, uint32 skillValue) } } +uint32 Player::GetSkillRank(uint8 pos) const +{ + uint16 field = pos / 2; + uint8 offset = pos & 1; + + return int32(GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset)); +} + +void Player::SetSkillLineId(uint8 pos, uint16 value) +{ + uint16 field = pos / 2; + uint8 offset = pos & 1; + SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, value); +} + +void Player::SetSkillStep(uint8 pos, uint16 value) +{ + uint16 field = pos / 2; + uint8 offset = pos & 1; + SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, value); +} +void Player::SetSkillRank(uint8 pos, uint16 value) +{ + uint16 field = pos / 2; + uint8 offset = pos & 1; + SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, value); +} + +void Player::SetSkillMaxRank(uint8 pos, uint16 value) +{ + uint16 field = pos / 2; + uint8 offset = pos & 1; + SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, value); +} + +void Player::SetSkillTempBonus(uint8 pos, uint16 value) +{ + uint16 field = pos / 2; + uint8 offset = pos & 1; + SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, value); +} + +void Player::SetSkillPermBonus(uint8 pos, uint16 value) +{ + uint16 field = pos / 2; + uint8 offset = pos & 1; + SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, value); +} + void Player::SendAurasForTarget(Unit* target) const { if (!target || target->GetVisibleAuras()->empty()) // speedup things @@ -24620,6 +24700,10 @@ bool Player::IsSpellFitByClassAndRace(uint32 spell_id) const if (_spell_idx->second->classmask && (_spell_idx->second->classmask & classmask) == 0) continue; + // skip wrong class and race skill saved in SkillRaceClassInfo.dbc + if (!GetSkillRaceClassInfo(_spell_idx->second->skillId, getRace(), getClass())) + continue; + return true; } @@ -26031,13 +26115,19 @@ void Player::_LoadSkills(PreparedQueryResult result) // 0 1 2 // SetPQuery(PLAYER_LOGIN_QUERY_LOADSKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = '%u'", GUID_LOPART(m_guid)); - uint32 count = 0; uint8 professionCount = 0; std::unordered_map loadedSkillValues; if (result) { do { + if (mSkillStatus.size() >= PLAYER_MAX_SKILLS) // client limit + { + TC_LOG_ERROR("entities.player", "Player::_LoadSkills: Player '%s' (%s) has more than %u skills.", + GetName().c_str(), GetGUID().ToString().c_str(), PLAYER_MAX_SKILLS); + break; + } + Field* fields = result->Fetch(); uint16 skill = fields[0].GetUInt16(); uint16 value = fields[1].GetUInt16(); @@ -26068,29 +26158,13 @@ void Player::_LoadSkills(PreparedQueryResult result) break; } - if (value == 0) - { - TC_LOG_ERROR("entities.player", "Player::_LoadSkills: Player '%s' (%s) has skill %u with value 0, deleted.", - GetName().c_str(), GetGUID().ToString().c_str(), skill); + auto skillItr = mSkillStatus.find(skill); + if (skillItr == mSkillStatus.end()) + skillItr = mSkillStatus.insert(SkillStatusMap::value_type(skill, SkillStatusData(mSkillStatus.size(), SKILL_UNCHANGED))).first; - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_SKILL); - - stmt->setUInt32(0, GetGUID().GetCounter()); - stmt->setUInt16(1, skill); - - CharacterDatabase.Execute(stmt); - - continue; - } - - uint16 field = count / 2; - uint8 offset = count & 1; - - SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, skill); uint16 step = 0; - SkillLineEntry const* skillLine = sSkillLineStore.LookupEntry(rcEntry->SkillId); - if (skillLine) + if (SkillLineEntry const* skillLine = sSkillLineStore.LookupEntry(rcEntry->SkillId)) { if (skillLine->categoryId == SKILL_CATEGORY_SECONDARY) step = max / 75; @@ -26104,26 +26178,17 @@ void Player::_LoadSkills(PreparedQueryResult result) } } - SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, step); - SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, value); - SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, max); - SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, 0); - SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, 0); + SetSkillLineId(skillItr->second.pos, skill); + SetSkillStep(skillItr->second.pos, step); + SetSkillRank(skillItr->second.pos, value); + SetSkillMaxRank(skillItr->second.pos, max); + SetSkillTempBonus(skillItr->second.pos, 0); + SetSkillPermBonus(skillItr->second.pos, 0); - mSkillStatus.insert(SkillStatusMap::value_type(skill, SkillStatusData(count, SKILL_UNCHANGED))); loadedSkillValues[skill] = value; if (skill == SKILL_ARCHAEOLOGY) _archaeology->Initialize(); - - ++count; - - if (count >= PLAYER_MAX_SKILLS) // client limit - { - TC_LOG_ERROR("entities.player", "Player::_LoadSkills: Player '%s' (%s) has more than %u skills.", - GetName().c_str(), GetGUID().ToString().c_str(), PLAYER_MAX_SKILLS); - break; - } } while (result->NextRow()); } @@ -26132,21 +26197,8 @@ void Player::_LoadSkills(PreparedQueryResult result) for (auto& skill : loadedSkillValues) LearnSkillRewardedSpells(skill.first, skill.second); - for (; count < PLAYER_MAX_SKILLS; ++count) - { - uint16 field = count / 2; - uint8 offset = count & 1; - if (HasSkill(SKILL_FIST_WEAPONS)) SetSkill(SKILL_FIST_WEAPONS, 0, GetSkillValue(SKILL_UNARMED), GetMaxSkillValueForLevel()); - - SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, 0); - SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, 0); - SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, 0); - SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, 0); - SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, 0); - SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, 0); - } } InventoryResult Player::CanEquipUniqueItem(Item* pItem, uint8 eslot, uint32 limit_count) const diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index bd8de16687a..c81ce85f63d 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -218,14 +218,6 @@ typedef std::unordered_map PlayerCurrenciesMap; typedef std::unordered_map InstanceTimeMap; -enum TrainerSpellState -{ - TRAINER_SPELL_GRAY = 0, - TRAINER_SPELL_GREEN = 1, - TRAINER_SPELL_RED = 2, - TRAINER_SPELL_GREEN_DISABLED = 10 // custom value, not send to client: formally green but learn not allowed -}; - enum ActionButtonUpdateState { ACTIONBUTTON_UNCHANGED = 0, @@ -1595,13 +1587,13 @@ class TC_GAME_API Player : public Unit, public GridObject void SendRemoveControlBar() const; bool HasSpell(uint32 spell) const override; bool HasActiveSpell(uint32 spell) const; // show in spellbook - TrainerSpellState GetTrainerSpellState(TrainerSpell const* trainer_spell) const; bool IsSpellFitByClassAndRace(uint32 spell_id) const; bool HandlePassiveSpellLearn(SpellInfo const* spellInfo); bool IsCurrentSpecMasterySpell(SpellInfo const* spellInfo) const; void SendProficiency(ItemClass itemClass, uint32 itemSubclassMask) const; void SendInitialSpells(); + void SendUnlearnSpells(); bool AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading = false, uint32 fromSkill = 0); void LearnSpell(uint32 spell_id, bool dependent, uint32 fromSkill = 0); void RemoveSpell(uint32 spell_id, bool disabled = false, bool learn_low_rank = true); @@ -1878,10 +1870,10 @@ class TC_GAME_API Player : public Unit, public GridObject bool UpdatePosition(Position const& pos, bool teleport = false) override { return UpdatePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teleport); } void ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional const& liquidData) override; - void SendMessageToSet(WorldPacket* data, bool self) override { SendMessageToSetInRange(data, GetVisibilityRange(), self); } - void SendMessageToSetInRange(WorldPacket* data, float dist, bool self) override; - void SendMessageToSetInRange(WorldPacket* data, float dist, bool self, bool own_team_only); - void SendMessageToSet(WorldPacket* data, Player const* skipped_rcvr) override; + void SendMessageToSet(WorldPacket* data, bool self) const override { SendMessageToSetInRange(data, GetVisibilityRange(), self); } + void SendMessageToSetInRange(WorldPacket* data, float dist, bool self) const override; + void SendMessageToSetInRange(WorldPacket* data, float dist, bool self, bool own_team_only) const; + void SendMessageToSet(WorldPacket* data, Player const* skipped_rcvr) const override; Corpse* GetCorpse() const; void SpawnCorpseBones(bool triggerSave = true); @@ -1918,6 +1910,7 @@ class TC_GAME_API Player : public Unit, public GridObject typedef std::list JoinedChannelsList; JoinedChannelsList const& GetJoinedChannels() const { return m_channels; } + void InitializeSkillFields(); void SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal); uint16 GetMaxSkillValue(uint32 skill) const; // max + perm. bonus + temp bonus uint16 GetPureMaxSkillValue(uint32 skill) const; // max @@ -1930,6 +1923,14 @@ class TC_GAME_API Player : public Unit, public GridObject bool HasSkill(uint32 skill) const; void LearnSkillRewardedSpells(uint32 skillId, uint32 skillValue); + uint32 GetSkillRank(uint8 pos) const; + void SetSkillLineId(uint8 pos, uint16 value); + void SetSkillStep(uint8 pos, uint16 value); + void SetSkillRank(uint8 pos, uint16 value); + void SetSkillMaxRank(uint8 pos, uint16 value); + void SetSkillTempBonus(uint8 pos, uint16 value); + void SetSkillPermBonus(uint8 pos, uint16 value); + WorldLocation& GetTeleportDest() { return m_teleport_dest; } Transport* GetTeleportTransport() const { return m_teleport_transport; } void ResetTeleportTransport() { m_teleport_transport = nullptr; } @@ -2391,7 +2392,6 @@ class TC_GAME_API Player : public Unit, public GridObject void SetTransportSpawnID(uint32 spawnId) { _transportSpawnID = spawnId; } void ResetHolyPowerRegenerationTimer() { m_holyPowerRegenTimerCount = 0; } - protected: // Gamemaster whisper whitelist GuidList WhisperList; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index e75bdd7fd29..b679d5b7e2d 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -12562,7 +12562,7 @@ void Unit::SetAuraStack(uint32 spellId, Unit* target, uint32 stack) aura->SetStackAmount(stack); } -void Unit::SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration) +void Unit::SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration) const { ObjectGuid guid = GetGUID(); diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 35e1cb14261..9203894a9bf 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1287,7 +1287,7 @@ class TC_GAME_API Unit : public WorldObject Aura* AddAura(uint32 spellId, Unit* target); Aura* AddAura(SpellInfo const* spellInfo, uint8 effMask, Unit* target); void SetAuraStack(uint32 spellId, Unit* target, uint32 stack); - void SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration); + void SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration) const; void DeMorph(); diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 77cbed73a49..e8d8a9c5284 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -276,7 +276,7 @@ ObjectMgr::~ObjectMgr() for (CacheVendorItemContainer::iterator itr = _cacheVendorItemStore.begin(); itr != _cacheVendorItemStore.end(); ++itr) itr->second.Clear(); - _cacheTrainerSpellStore.clear(); + _creatureDefaultTrainers.clear(); _graveyardOrientations.clear(); for (DungeonEncounterContainer::iterator itr =_dungeonEncounterStore.begin(); itr != _dungeonEncounterStore.end(); ++itr) @@ -435,17 +435,15 @@ void ObjectMgr::LoadCreatureTemplates() "modelid4, name, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, exp_unk, faction, npcflag, speed_walk, speed_run, " // 23 24 25 26 27 28 29 30 31 32 "scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, " - // 33 34 35 36 37 38 - "dynamicflags, family, trainer_type, trainer_class, trainer_race, type, " - // 39 40 41 42 43 44 45 46 47 48 49 - "type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, " - // 50 51 52 53 54 55 56 57 58 59 60 61 62 63 + // 33 34 35 36 37 38 39 40 41 42 43 44 45 46 + "dynamicflags, family, type, type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, " + // 47 48 49 50 51 52 53 54 55 56 57 58 59 60 "spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, " - // 64 65 66 67 68 69 70 71 72 + // 61 62 63 64 65 66 67 68 69 "ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, " - // 73 74 75 76 77 78 79 80 + // 70 71 72 73 74 75 76 77 "ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, " - // 81 82 + // 78 79 "flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId"); if (!result) @@ -512,56 +510,53 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields) creatureTemplate.unit_flags2 = fields[32].GetUInt32(); creatureTemplate.dynamicflags = fields[33].GetUInt32(); creatureTemplate.family = CreatureFamily(uint32(fields[34].GetUInt8())); - creatureTemplate.trainer_type = uint32(fields[35].GetUInt8()); - creatureTemplate.trainer_class = uint32(fields[36].GetUInt8()); - creatureTemplate.trainer_race = uint32(fields[37].GetUInt8()); - creatureTemplate.type = uint32(fields[38].GetUInt8()); - creatureTemplate.type_flags = fields[39].GetUInt32(); - creatureTemplate.type_flags2 = fields[40].GetUInt32(); - creatureTemplate.lootid = fields[41].GetUInt32(); - creatureTemplate.pickpocketLootId = fields[42].GetUInt32(); - creatureTemplate.SkinLootId = fields[43].GetUInt32(); + creatureTemplate.type = uint32(fields[35].GetUInt8()); + creatureTemplate.type_flags = fields[36].GetUInt32(); + creatureTemplate.type_flags2 = fields[37].GetUInt32(); + creatureTemplate.lootid = fields[38].GetUInt32(); + creatureTemplate.pickpocketLootId = fields[39].GetUInt32(); + creatureTemplate.SkinLootId = fields[40].GetUInt32(); for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) - creatureTemplate.resistance[i] = fields[44 + i - 1].GetInt16(); + creatureTemplate.resistance[i] = fields[41 + i - 1].GetInt16(); for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i) - creatureTemplate.spells[i] = fields[50 + i].GetUInt32(); + creatureTemplate.spells[i] = fields[47 + i].GetUInt32(); - creatureTemplate.PetSpellDataId = fields[58].GetUInt32(); - creatureTemplate.VehicleId = fields[59].GetUInt32(); - creatureTemplate.mingold = fields[60].GetUInt32(); - creatureTemplate.maxgold = fields[61].GetUInt32(); - creatureTemplate.AIName = fields[62].GetString(); - creatureTemplate.MovementType = uint32(fields[63].GetUInt8()); + creatureTemplate.PetSpellDataId = fields[55].GetUInt32(); + creatureTemplate.VehicleId = fields[56].GetUInt32(); + creatureTemplate.mingold = fields[57].GetUInt32(); + creatureTemplate.maxgold = fields[58].GetUInt32(); + creatureTemplate.AIName = fields[59].GetString(); + creatureTemplate.MovementType = uint32(fields[60].GetUInt8()); if (!fields[64].IsNull()) - creatureTemplate.Movement.Ground = static_cast(fields[64].GetUInt8()); + creatureTemplate.Movement.Ground = static_cast(fields[61].GetUInt8()); if (!fields[65].IsNull()) - creatureTemplate.Movement.Swim = fields[65].GetBool(); + creatureTemplate.Movement.Swim = fields[62].GetBool(); if (!fields[66].IsNull()) - creatureTemplate.Movement.Flight = static_cast(fields[66].GetUInt8()); + creatureTemplate.Movement.Flight = static_cast(fields[63].GetUInt8()); if (!fields[67].IsNull()) - creatureTemplate.Movement.Rooted = fields[67].GetBool(); + creatureTemplate.Movement.Rooted = fields[64].GetBool(); - creatureTemplate.HoverHeight = fields[68].GetFloat(); - creatureTemplate.ModHealth = fields[69].GetFloat(); - creatureTemplate.ModHealthExtra = fields[70].GetFloat(); - creatureTemplate.ModMana = fields[71].GetFloat(); - creatureTemplate.ModManaExtra = fields[72].GetFloat(); - creatureTemplate.ModArmor = fields[73].GetFloat(); - creatureTemplate.ModDamage = fields[74].GetFloat(); - creatureTemplate.ModExperience = fields[75].GetFloat(); + creatureTemplate.HoverHeight = fields[65].GetFloat(); + creatureTemplate.ModHealth = fields[66].GetFloat(); + creatureTemplate.ModHealthExtra = fields[67].GetFloat(); + creatureTemplate.ModMana = fields[68].GetFloat(); + creatureTemplate.ModManaExtra = fields[69].GetFloat(); + creatureTemplate.ModArmor = fields[70].GetFloat(); + creatureTemplate.ModDamage = fields[71].GetFloat(); + creatureTemplate.ModExperience = fields[72].GetFloat(); - creatureTemplate.RacialLeader = fields[76].GetBool(); - creatureTemplate.movementId = fields[77].GetUInt32(); - creatureTemplate.RegenHealth = fields[78].GetBool(); - creatureTemplate.MechanicImmuneMask = fields[79].GetUInt32(); - creatureTemplate.SpellSchoolImmuneMask = fields[80].GetUInt32(); - creatureTemplate.flags_extra = fields[81].GetUInt32(); - creatureTemplate.ScriptID = GetScriptId(fields[82].GetCString()); + creatureTemplate.RacialLeader = fields[73].GetBool(); + creatureTemplate.movementId = fields[74].GetUInt32(); + creatureTemplate.RegenHealth = fields[75].GetBool(); + creatureTemplate.MechanicImmuneMask = fields[76].GetUInt32(); + creatureTemplate.SpellSchoolImmuneMask = fields[77].GetUInt32(); + creatureTemplate.flags_extra = fields[78].GetUInt32(); + creatureTemplate.ScriptID = GetScriptId(fields[79].GetCString()); } void ObjectMgr::LoadCreatureTemplateAddons() @@ -836,33 +831,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo) cInfo->family, cInfo->DifficultyEntry[diff]); } - if (cInfo->trainer_class != difficultyInfo->trainer_class) - { - TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, trainer_class: %u) has different `trainer_class` in difficulty %u mode (Entry: %u, trainer_class: %u).", - cInfo->Entry, cInfo->trainer_class, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->trainer_class); - TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `trainer_class`=%u WHERE `entry`=%u;", - cInfo->trainer_class, cInfo->DifficultyEntry[diff]); - continue; - } - - if (cInfo->trainer_race != difficultyInfo->trainer_race) - { - TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, trainer_race: %u) has different `trainer_race` in difficulty %u mode (Entry: %u, trainer_race: %u).", - cInfo->Entry, cInfo->trainer_race, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->trainer_race); - TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `trainer_race`=%u WHERE `entry`=%u;", - cInfo->trainer_race, cInfo->DifficultyEntry[diff]); - continue; - } - - if (cInfo->trainer_type != difficultyInfo->trainer_type) - { - TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, trainer_type: %u) has different `trainer_type` in difficulty %u mode (Entry: %u, trainer_type: %u).", - cInfo->Entry, cInfo->trainer_type, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->trainer_type); - TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `trainer_type`=%u WHERE `entry`=%u;", - cInfo->trainer_type, cInfo->DifficultyEntry[diff]); - continue; - } - if (cInfo->type != difficultyInfo->type) { TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, type: %u) has different `type` in difficulty %u mode (Entry: %u, type: %u).", @@ -1055,9 +1023,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo) if (cInfo->RangeAttackTime == 0) const_cast(cInfo)->RangeAttackTime = BASE_ATTACK_TIME; - if ((cInfo->npcflag & UNIT_NPC_FLAG_TRAINER) && cInfo->trainer_type >= MAX_TRAINER_TYPE) - TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has wrong trainer type %u.", cInfo->Entry, cInfo->trainer_type); - if (cInfo->speed_walk == 0.0f) { TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has wrong value (%f) in speed_walk, set to 1.", cInfo->Entry, cInfo->speed_walk); @@ -8928,124 +8893,183 @@ void ObjectMgr::LoadMailLevelRewards() TC_LOG_INFO("server.loading", ">> Loaded %u level dependent mail rewards in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } -void ObjectMgr::AddSpellToTrainer(uint32 ID, uint32 SpellID, uint32 MoneyCost, uint32 ReqSkillLine, uint32 ReqSkillRank, uint32 ReqLevel, uint32 Index) -{ - if (ID >= TRINITY_TRAINER_START_REF) - return; - - CreatureTemplate const* cInfo = GetCreatureTemplate(ID); - if (!cInfo) - { - TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains entries for a non-existing creature template (ID: %u), ignoring", ID); - return; - } - - if (!(cInfo->npcflag & UNIT_NPC_FLAG_TRAINER)) - { - TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains entries for a creature template (ID: %u) without any trainer flag, ignoring", ID); - return; - } - - SpellInfo const* spellinfo = sSpellMgr->GetSpellInfo(SpellID); - if (!spellinfo) - { - TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains an ID (%u) for a non-existing spell (Spell: %u), ignoring", ID, SpellID); - return; - } - - if (!SpellMgr::IsSpellValid(spellinfo)) - { - TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains an ID (%u) for a broken spell (Spell: %u), ignoring", ID, SpellID); - return; - } - - if (GetTalentSpellCost(SpellID)) - { - TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains an ID (%u) for a non-existing spell (Spell: %u) which is a talent, ignoring", ID, SpellID); - return; - } - - TrainerSpellData& data = _cacheTrainerSpellStore[ID]; - - TrainerSpell& trainerSpell = data.spellList[SpellID]; - trainerSpell.SpellID = SpellID; - trainerSpell.MoneyCost = MoneyCost; - trainerSpell.ReqSkillLine = ReqSkillLine; - trainerSpell.ReqSkillRank = ReqSkillRank; - trainerSpell.ReqLevel = ReqLevel; - trainerSpell.Index = Index; - - if (!trainerSpell.ReqLevel) - trainerSpell.ReqLevel = spellinfo->SpellLevel; - - // calculate learned spell for profession case when stored cast-spell - trainerSpell.ReqAbility[0] = SpellID; - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - { - if (spellinfo->Effects[i].Effect != SPELL_EFFECT_LEARN_SPELL) - continue; - if (trainerSpell.ReqAbility[0] == SpellID) - trainerSpell.ReqAbility[0] = 0; - // player must be able to cast spell on himself - if (spellinfo->Effects[i].TargetA.GetTarget() != 0 && spellinfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ALLY - && spellinfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ANY && spellinfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_CASTER) - { - TC_LOG_ERROR("sql.sql", "Table `npc_trainer` has spell %u for trainer entry %u with learn effect which has incorrect target type, ignoring learn effect!", SpellID, ID); - continue; - } - - trainerSpell.ReqAbility[i] = spellinfo->Effects[i].TriggerSpell; - - if (trainerSpell.ReqAbility[i]) - { - SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainerSpell.ReqAbility[i]); - if (learnedSpellInfo && learnedSpellInfo->IsProfession()) - data.trainerType = 2; - } - } - - return; -} - -void ObjectMgr::LoadTrainerSpell() +void ObjectMgr::LoadTrainers() { uint32 oldMSTime = getMSTime(); // For reload case - _cacheTrainerSpellStore.clear(); + _trainers.clear(); - QueryResult result = WorldDatabase.Query("SELECT b.ID, a.SpellID, a.MoneyCost, a.ReqSkillLine, a.ReqSkillRank, a.Reqlevel, a.Index FROM npc_trainer AS a " - "INNER JOIN npc_trainer AS b ON a.ID = -(b.SpellID) " - "UNION SELECT * FROM npc_trainer WHERE SpellID > 0"); - - if (!result) + std::unordered_map> spellsByTrainer; + if (QueryResult trainerSpellsResult = WorldDatabase.Query("SELECT TrainerId, SpellId, MoneyCost, ReqSkillLine, ReqSkillRank, ReqAbility1, ReqAbility2, ReqAbility3, ReqLevel FROM trainer_spell")) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 Trainers. DB table `npc_trainer` is empty!"); + do + { + Field* fields = trainerSpellsResult->Fetch(); - return; + Trainer::Spell spell; + uint32 trainerId = fields[0].GetUInt32(); + spell.SpellId = fields[1].GetUInt32(); + spell.MoneyCost = fields[2].GetUInt32(); + spell.ReqSkillLine = fields[3].GetUInt32(); + spell.ReqSkillRank = fields[4].GetUInt32(); + spell.ReqAbility[0] = fields[5].GetUInt32(); + spell.ReqAbility[1] = fields[6].GetUInt32(); + //spell.ReqAbility[2] = fields[7].GetUInt32(); + spell.ReqLevel = fields[8].GetUInt8(); + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell.SpellId); + if (!spellInfo) + { + TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing spell (SpellId: %u) for TrainerId %u, ignoring", spell.SpellId, trainerId); + continue; + } + + if (GetTalentSpellCost(spell.SpellId)) + { + TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing spell (SpellId: %u) which is a talent, for TrainerId %u, ignoring", spell.SpellId, trainerId); + continue; + } + + if (spell.ReqSkillLine && !sSkillLineStore.LookupEntry(spell.ReqSkillLine)) + { + TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing skill (ReqSkillLine: %u) for TrainerId %u and SpellId %u, ignoring", + spell.ReqSkillLine, trainerId, spell.SpellId); + continue; + } + + bool allReqValid = true; + for (std::size_t i = 0; i < spell.ReqAbility.size(); ++i) + { + uint32 requiredSpell = spell.ReqAbility[i]; + if (requiredSpell && !sSpellMgr->GetSpellInfo(requiredSpell)) + { + TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing spell (ReqAbility" SZFMTD ": %u) for TrainerId %u and SpellId %u, ignoring", + i + 1, requiredSpell, trainerId, spell.SpellId); + allReqValid = false; + } + } + + if (!allReqValid) + continue; + + spellsByTrainer[trainerId].push_back(spell); + } while (trainerSpellsResult->NextRow()); } - uint32 count = 0; - - do + if (QueryResult trainersResult = WorldDatabase.Query("SELECT Id, Type, Requirement, Greeting FROM trainer")) { - Field* fields = result->Fetch(); + do + { + Field* fields = trainersResult->Fetch(); - uint32 ID = fields[0].GetUInt32(); - uint32 SpellID = fields[1].GetUInt32(); - uint32 MoneyCost = fields[2].GetUInt32(); - uint32 ReqSkillLine = fields[3].GetUInt16(); - uint32 ReqSkillRank = fields[4].GetUInt16(); - uint32 ReqLevel = fields[5].GetUInt8(); - uint32 Index = fields[6].GetUInt8(); + uint32 trainerId = fields[0].GetUInt32(); + Trainer::Type trainerType = Trainer::Type(fields[1].GetUInt8()); + uint32 requirement = fields[2].GetUInt32(); + std::string greeting = fields[3].GetString(); + std::vector spells; + auto spellsItr = spellsByTrainer.find(trainerId); + if (spellsItr != spellsByTrainer.end()) + { + spells = std::move(spellsItr->second); + spellsByTrainer.erase(spellsItr); + } - AddSpellToTrainer(ID, SpellID, MoneyCost, ReqSkillLine, ReqSkillRank, ReqLevel, Index); + switch (trainerType) + { + case Trainer::Type::Class: + case Trainer::Type::Pet: + if (requirement && !sChrClassesStore.LookupEntry(requirement)) + { + TC_LOG_ERROR("sql.sql", "Table `trainer` references non-existing class requirement %u for TrainerId %u, ignoring", requirement, trainerId); + continue; + } + break; + case Trainer::Type::Mount: + if (requirement && !sChrRacesStore.LookupEntry(requirement)) + { + TC_LOG_ERROR("sql.sql", "Table `trainer` references non-existing race requirement %u for TrainerId %u, ignoring", requirement, trainerId); + continue; + } + break; + case Trainer::Type::Tradeskill: + if (requirement && !sSpellMgr->GetSpellInfo(requirement)) + { + TC_LOG_ERROR("sql.sql", "Table `trainer` references non-existing spell requirement %u for TrainerId %u, ignoring", requirement, trainerId); + continue; + } + break; + default: + break; + } - ++count; + _trainers.emplace(std::piecewise_construct, std::forward_as_tuple(trainerId), std::forward_as_tuple(trainerId, trainerType, requirement, std::move(greeting), std::move(spells))); + } while (trainersResult->NextRow()); } - while (result->NextRow()); - TC_LOG_INFO("server.loading", ">> Loaded %d Trainers in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); + for (auto const& unusedSpells : spellsByTrainer) + { + for (Trainer::Spell const& unusedSpell : unusedSpells.second) + { + TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing trainer (TrainerId: %u) for SpellId %u, ignoring", unusedSpells.first, unusedSpell.SpellId); + } + } + + if (QueryResult trainerLocalesResult = WorldDatabase.Query("SELECT Id, locale, Greeting_lang FROM trainer_locale")) + { + do + { + Field* fields = trainerLocalesResult->Fetch(); + uint32 trainerId = fields[0].GetUInt32(); + std::string localeName = fields[1].GetString(); + + LocaleConstant locale = GetLocaleByName(localeName); + if (locale == LOCALE_enUS) + continue; + + if (Trainer::Trainer* trainer = Trinity::Containers::MapGetValuePtr(_trainers, trainerId)) + trainer->AddGreetingLocale(locale, fields[2].GetString()); + else + TC_LOG_ERROR("sql.sql", "Table `trainer_locale` references non-existing trainer (TrainerId: %u) for locale %s, ignoring", + trainerId, localeName.c_str()); + } while (trainerLocalesResult->NextRow()); + } + + TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " Trainers in %u ms", _trainers.size(), GetMSTimeDiffToNow(oldMSTime)); +} + +void ObjectMgr::LoadCreatureDefaultTrainers() +{ + uint32 oldMSTime = getMSTime(); + + _creatureDefaultTrainers.clear(); + + if (QueryResult result = WorldDatabase.Query("SELECT CreatureId, TrainerId FROM creature_default_trainer")) + { + do + { + Field* fields = result->Fetch(); + uint32 creatureId = fields[0].GetUInt32(); + uint32 trainerId = fields[1].GetUInt32(); + + if (!GetCreatureTemplate(creatureId)) + { + TC_LOG_ERROR("sql.sql", "Table `creature_default_trainer` references non-existing creature template (CreatureId: %u), ignoring", creatureId); + continue; + } + + if (!GetTrainer(trainerId)) + { + TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing trainer (TrainerId: %u) for CreatureId %u, ignoring", trainerId, creatureId); + continue; + } + + _creatureDefaultTrainers[creatureId] = trainerId; + + } while (result->NextRow()); + } + + TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " default trainers in %u ms", _creatureDefaultTrainers.size(), GetMSTimeDiffToNow(oldMSTime)); } int ObjectMgr::LoadReferenceVendor(int32 vendor, int32 item, uint8 type, std::set *skip_vendors) @@ -9184,9 +9208,19 @@ void ObjectMgr::LoadGossipMenuItems() _gossipMenuItemsStore.clear(); QueryResult result = WorldDatabase.Query( - // 0 1 2 3 4 5 6 7 8 9 10 11 12 - "SELECT MenuID, OptionID, OptionIcon, OptionText, OptionBroadcastTextID, OptionType, OptionNpcFlag, ActionMenuID, ActionPoiID, BoxCoded, BoxMoney, BoxText, BoxBroadcastTextID " - "FROM gossip_menu_option ORDER BY MenuID, OptionID"); + // 0 1 2 3 4 5 6 + "SELECT o.MenuId, o.OptionIndex, o.OptionIcon, o.OptionText, o.OptionBroadcastTextId, o.OptionType, o.OptionNpcflag, " + // 7 8 + "oa.ActionMenuId, oa.ActionPoiId, " + // 9 10 11 12 + "ob.BoxCoded, ob.BoxMoney, ob.BoxText, ob.BoxBroadcastTextId, " + // 13 + "ot.TrainerId " + "FROM gossip_menu_option o " + "LEFT JOIN gossip_menu_option_action oa ON o.MenuId = oa.MenuId AND o.OptionIndex = oa.OptionIndex " + "LEFT JOIN gossip_menu_option_box ob ON o.MenuId = ob.MenuId AND o.OptionIndex = ob.OptionIndex " + "LEFT JOIN gossip_menu_option_trainer ot ON o.MenuId = ot.MenuId AND o.OptionIndex = ot.OptionIndex " + "ORDER BY o.MenuId, o.OptionIndex"); if (!result) { @@ -9200,12 +9234,12 @@ void ObjectMgr::LoadGossipMenuItems() GossipMenuItems gMenuItem; - gMenuItem.MenuID = fields[0].GetUInt16(); - gMenuItem.OptionID = fields[1].GetUInt16(); - gMenuItem.OptionIcon = fields[2].GetUInt32(); + gMenuItem.MenuID = fields[0].GetUInt32(); + gMenuItem.OptionID = fields[1].GetUInt32(); + gMenuItem.OptionIcon = fields[2].GetUInt8(); gMenuItem.OptionText = fields[3].GetString(); gMenuItem.OptionBroadcastTextID = fields[4].GetUInt32(); - gMenuItem.OptionType = fields[5].GetUInt8(); + gMenuItem.OptionType = fields[5].GetUInt32(); gMenuItem.OptionNpcFlag = fields[6].GetUInt32(); gMenuItem.ActionMenuID = fields[7].GetUInt32(); gMenuItem.ActionPoiID = fields[8].GetUInt32(); @@ -9213,10 +9247,11 @@ void ObjectMgr::LoadGossipMenuItems() gMenuItem.BoxMoney = fields[10].GetUInt32(); gMenuItem.BoxText = fields[11].GetString(); gMenuItem.BoxBroadcastTextID = fields[12].GetUInt32(); + gMenuItem.TrainerId = fields[13].GetUInt32(); if (gMenuItem.OptionIcon >= GOSSIP_ICON_MAX) { - TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u has unknown icon id %u. Replacing with GOSSIP_ICON_CHAT", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.OptionIcon); + TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for MenuId %u, OptionIndex %u has unknown icon id %u. Replacing with GOSSIP_ICON_CHAT", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.OptionIcon); gMenuItem.OptionIcon = GOSSIP_ICON_CHAT; } @@ -9224,17 +9259,17 @@ void ObjectMgr::LoadGossipMenuItems() { if (!GetBroadcastText(gMenuItem.OptionBroadcastTextID)) { - TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u has non-existing or incompatible OptionBroadcastTextID %u, ignoring.", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.OptionBroadcastTextID); + TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for MenuId %u, OptionIndex %u has non-existing or incompatible OptionBroadcastTextID %u, ignoring.", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.OptionBroadcastTextID); gMenuItem.OptionBroadcastTextID = 0; } } if (gMenuItem.OptionType >= GOSSIP_OPTION_MAX) - TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u has unknown option id %u. Option will not be used", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.OptionType); + TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for MenuId %u, OptionIndex %u has unknown option id %u. Option will not be used", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.OptionType); if (gMenuItem.ActionPoiID && !GetPointOfInterest(gMenuItem.ActionPoiID)) { - TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u use non-existing ActionPoiID %u, ignoring", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.ActionPoiID); + TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for MenuId %u, OptionIndex %u use non-existing ActionPoiID %u, ignoring", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.ActionPoiID); gMenuItem.ActionPoiID = 0; } @@ -9242,17 +9277,37 @@ void ObjectMgr::LoadGossipMenuItems() { if (!GetBroadcastText(gMenuItem.BoxBroadcastTextID)) { - TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u has non-existing or incompatible BoxBroadcastTextID %u, ignoring.", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.BoxBroadcastTextID); + TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for MenuId %u, OptionIndex %u has non-existing or incompatible BoxBroadcastTextID %u, ignoring.", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.BoxBroadcastTextID); gMenuItem.BoxBroadcastTextID = 0; } } + if (gMenuItem.TrainerId && !GetTrainer(gMenuItem.TrainerId)) + { + TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option_trainer` for MenuId %u, OptionIndex %u use non-existing TrainerId %u, ignoring", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.TrainerId); + gMenuItem.TrainerId = 0; + } + _gossipMenuItemsStore.insert(GossipMenuItemsContainer::value_type(gMenuItem.MenuID, gMenuItem)); } while (result->NextRow()); TC_LOG_INFO("server.loading", ">> Loaded %u gossip_menu_option entries in %u ms", uint32(_gossipMenuItemsStore.size()), GetMSTimeDiffToNow(oldMSTime)); } +Trainer::Trainer const* ObjectMgr::GetTrainer(uint32 trainerId) const +{ + return Trinity::Containers::MapGetValuePtr(_trainers, trainerId); +} + +uint32 ObjectMgr::GetCreatureDefaultTrainer(uint32 creatureId) const +{ + auto itr = _creatureDefaultTrainers.find(creatureId); + if (itr != _creatureDefaultTrainers.end()) + return itr->second; + + return 0; +} + void ObjectMgr::AddVendorItem(uint32 entry, uint32 item, int32 maxcount, uint32 incrtime, uint32 extendedCost, uint8 type, bool persist /*= true*/) { VendorItemData& vList = _cacheVendorItemStore[entry]; diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index c6b14866c50..a7b7cf247c8 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -33,6 +33,7 @@ #include "Position.h" #include "QuestDef.h" #include "SharedDefines.h" +#include "Trainer.h" #include "VehicleDefines.h" #include #include @@ -740,6 +741,7 @@ struct GossipMenuItems uint32 BoxMoney; std::string BoxText; uint32 BoxBroadcastTextID; + uint32 TrainerId; ConditionContainer Conditions; }; @@ -809,7 +811,6 @@ typedef std::pair GraveyardMapBoundsNonConst; typedef std::unordered_map CacheVendorItemContainer; -typedef std::unordered_map CacheTrainerSpellContainer; enum SkillRangeType { @@ -1212,8 +1213,8 @@ class TC_GAME_API ObjectMgr void LoadGossipMenuItems(); void LoadVendors(); - void LoadTrainerSpell(); - void AddSpellToTrainer(uint32 ID, uint32 SpellID, uint32 MoneyCost, uint32 ReqSkillLine, uint32 ReqSkillRank, uint32 ReqLevel, uint32 Index); + void LoadTrainers(); + void LoadCreatureDefaultTrainers(); void LoadPhases(); void UnloadPhaseConditions(); @@ -1458,14 +1459,8 @@ class TC_GAME_API ObjectMgr bool AddGameTele(GameTele& data); bool DeleteGameTele(std::string const& name); - TrainerSpellData const* GetNpcTrainerSpells(uint32 entry) const - { - CacheTrainerSpellContainer::const_iterator iter = _cacheTrainerSpellStore.find(entry); - if (iter == _cacheTrainerSpellStore.end()) - return nullptr; - - return &iter->second; - } + Trainer::Trainer const* GetTrainer(uint32 trainerId) const; + uint32 GetCreatureDefaultTrainer(uint32 creatureId) const; VendorItemData const* GetNpcVendorItemList(uint32 entry) const { @@ -1728,7 +1723,8 @@ class TC_GAME_API ObjectMgr TrinityStringContainer _trinityStringStore; CacheVendorItemContainer _cacheVendorItemStore; - CacheTrainerSpellContainer _cacheTrainerSpellStore; + std::unordered_map _trainers; + std::unordered_map _creatureDefaultTrainers; GraveyardOrientationContainer _graveyardOrientations; diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h index 9c832949530..80314d4c0fa 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -117,18 +117,18 @@ namespace Trinity struct TC_GAME_API MessageDistDeliverer { - WorldObject* i_source; + WorldObject const* i_source; WorldPacket* i_message; float i_distSq; uint32 team; Player const* skipped_receiver; - MessageDistDeliverer(WorldObject* src, WorldPacket* msg, float dist, bool own_team_only = false, Player const* skipped = nullptr) + MessageDistDeliverer(WorldObject const* src, WorldPacket* msg, float dist, bool own_team_only = false, Player const* skipped = nullptr) : i_source(src), i_message(msg), i_distSq(dist * dist) , team(0) , skipped_receiver(skipped) { if (own_team_only) - if (Player* player = src->ToPlayer()) + if (Player const* player = src->ToPlayer()) team = player->GetTeam(); } diff --git a/src/server/game/Handlers/MailHandler.cpp b/src/server/game/Handlers/MailHandler.cpp index 33e0a1aaca9..4b207d5f2c3 100644 --- a/src/server/game/Handlers/MailHandler.cpp +++ b/src/server/game/Handlers/MailHandler.cpp @@ -21,6 +21,7 @@ #include "CharacterCache.h" #include "DatabaseEnv.h" #include "DBCStores.h" +#include "GossipDef.h" #include "GameTime.h" #include "Guild.h" #include "GuildMgr.h" @@ -456,7 +457,7 @@ void WorldSession::HandleMailReturnToSender(WorldPacket& recvData) recvData >> mailId; recvData.read_skip(); // original sender GUID for return to, not used - if (!CanOpenMailBox(mailbox)) + if (!CanOpenMailBox(_player->PlayerTalkClass->GetInteractionData().SourceGuid)) return; Player* player = _player; @@ -765,6 +766,10 @@ void WorldSession::HandleGetMailList(WorldPacket& recvData) data.put(0, realCount); // this will display warning about undelivered mail to player if realCount > mailsCount data.put(4, mailsCount); // set real send mails to client + + player->PlayerTalkClass->GetInteractionData().Reset(); + player->PlayerTalkClass->GetInteractionData().SourceGuid = mailbox; + SendPacket(&data); // recalculate m_nextMailDelivereTime and unReadMails diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index e2e6abe5edc..8fba604b690 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -106,7 +106,7 @@ void WorldSession::HandleGossipSelectOptionOpcode(WorldPacket& recvData) recvData >> code; // Prevent cheating on C++ scripted menus - if (_player->PlayerTalkClass->GetGossipMenu().GetSenderGUID() != guid) + if (_player->PlayerTalkClass->GetInteractionData().SourceGuid != guid) return; Creature* unit = nullptr; diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index a010132d881..dfb75f42bba 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -28,6 +28,7 @@ #include "Language.h" #include "Log.h" #include "Map.h" +#include "NPCPackets.h" #include "ObjectMgr.h" #include "Opcodes.h" #include "Pet.h" @@ -37,6 +38,7 @@ #include "ScriptMgr.h" #include "SpellInfo.h" #include "SpellMgr.h" +#include "Trainer.h" #include "World.h" #include "WorldPacket.h" @@ -103,210 +105,66 @@ void WorldSession::SendShowMailBox(ObjectGuid guid) SendPacket(&data); } -void WorldSession::HandleTrainerListOpcode(WorldPacket& recvData) +void WorldSession::HandleTrainerListOpcode(WorldPackets::NPC::Hello& packet) { - ObjectGuid guid; - - recvData >> guid; - SendTrainerList(guid); -} - -void WorldSession::SendTrainerList(ObjectGuid guid, uint32 index) -{ - std::string str = GetTrinityString(LANG_NPC_TAINER_HELLO); - SendTrainerList(guid, str, index); -} - -void WorldSession::SendTrainerList(ObjectGuid guid, const std::string& strTitle, uint32 index) -{ - TC_LOG_DEBUG("network", "WORLD: SendTrainerList"); - - Creature* trainer = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_TRAINER); - if (!trainer) + Creature* npc = GetPlayer()->GetNPCIfCanInteractWith(packet.Unit, UNIT_NPC_FLAG_TRAINER); + if (!npc) { - TC_LOG_DEBUG("network", "WORLD: SendTrainerList - %s not found or you can not interact with him.", guid.ToString().c_str()); + TC_LOG_DEBUG("network", "WorldSession::SendTrainerList - %s not found or you can not interact with him.", packet.Unit.ToString().c_str()); return; } - // remove fake death - if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) - GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - - TrainerSpellData const* trainer_spells = trainer->GetTrainerSpells(); - if (!trainer_spells) - { - TC_LOG_DEBUG("network", "WORLD: SendTrainerList - Training spells not found for %s", guid.ToString().c_str()); - return; - } - - WorldPacket data(SMSG_TRAINER_LIST, 8+4+4+trainer_spells->spellList.size()*38 + strTitle.size()+1); - data << guid; - data << uint32(trainer_spells->trainerType); - data << uint32(1); // different value for each trainer, also found in CMSG_TRAINER_BUY_SPELL - - size_t count_pos = data.wpos(); - data << uint32(trainer_spells->spellList.size()); - - // reputation discount - float fDiscountMod = _player->GetReputationPriceDiscount(trainer); - bool can_learn_primary_prof = GetPlayer()->GetFreePrimaryProfessionPoints() > 0; - - uint32 count = 0; - for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr) - { - TrainerSpell const* tSpell = &itr->second; - - if (index && tSpell->Index != index) - continue; - - bool valid = true; - bool primary_prof_first_rank = false; - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - { - if (!tSpell->ReqAbility[i]) - continue; - if (!_player->IsSpellFitByClassAndRace(tSpell->ReqAbility[i])) - { - valid = false; - break; - } - SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(tSpell->ReqAbility[i]); - if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank()) - primary_prof_first_rank = true; - } - if (!valid) - continue; - - TrainerSpellState state = _player->GetTrainerSpellState(tSpell); - - data << uint32(tSpell->SpellID); // learned spell (or cast-spell in profession case) - data << uint8(state == TRAINER_SPELL_GREEN_DISABLED ? TRAINER_SPELL_GREEN : state); - data << uint32(floor(tSpell->MoneyCost * fDiscountMod)); - - data << uint8(tSpell->ReqLevel); - data << uint32(tSpell->ReqSkillLine); - data << uint32(tSpell->ReqSkillRank); - //prev + req or req + 0 - uint8 maxReq = 0; - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - { - if (!tSpell->ReqAbility[i]) - continue; - if (uint32 prevSpellId = sSpellMgr->GetPrevSpellInChain(tSpell->ReqAbility[i])) - { - data << uint32(prevSpellId); - ++maxReq; - } - if (maxReq == 2) - break; - SpellsRequiringSpellMapBounds spellsRequired = sSpellMgr->GetSpellsRequiredForSpellBounds(tSpell->ReqAbility[i]); - for (SpellsRequiringSpellMap::const_iterator itr2 = spellsRequired.first; itr2 != spellsRequired.second && maxReq < 3; ++itr2) - { - data << uint32(itr2->second); - ++maxReq; - } - if (maxReq == 2) - break; - } - while (maxReq < 2) - { - data << uint32(0); - ++maxReq; - } - - data << uint32(primary_prof_first_rank && can_learn_primary_prof ? 1 : 0); - // primary prof. learn confirmation dialog - data << uint32(primary_prof_first_rank ? 1 : 0); // must be equal prev. field to have learn button in enabled state - - ++count; - } - - data << strTitle; - - data.put(count_pos, count); - SendPacket(&data); -} - -void WorldSession::HandleTrainerBuySpellOpcode(WorldPacket& recvData) -{ - ObjectGuid guid; - uint32 spellId; - uint32 trainerId; - - recvData >> guid >> trainerId >> spellId; - TC_LOG_DEBUG("network", "WORLD: Received CMSG_TRAINER_BUY_SPELL %s, learn spell id is: %u", guid.ToString().c_str(), spellId); - - Creature* trainer = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_TRAINER); - if (!trainer) - { - TC_LOG_DEBUG("network", "WORLD: HandleTrainerBuySpellOpcode - %s not found or you can not interact with him.", guid.ToString().c_str()); - return; - } - - // remove fake death - if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) - GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - - // check race for mount trainers - if (trainer->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_MOUNTS) - { - if (uint32 trainerRace = trainer->GetCreatureTemplate()->trainer_race) - if (_player->getRace() != trainerRace) - return; - } - - // check class for class trainers - if (_player->getClass() != trainer->GetCreatureTemplate()->trainer_class && trainer->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS) - return; - - // check present spell in trainer spell list - TrainerSpellData const* trainer_spells = trainer->GetTrainerSpells(); - if (!trainer_spells) - { - SendTrainerBuyFailed(guid, spellId, 0); - return; - } - - // not found, cheat? - TrainerSpell const* trainer_spell = trainer_spells->Find(spellId); - if (!trainer_spell) - { - SendTrainerBuyFailed(guid, spellId, 0); - return; - } - - // can't be learn, cheat? Or double learn with lags... - if (_player->GetTrainerSpellState(trainer_spell) != TRAINER_SPELL_GREEN) - { - SendTrainerBuyFailed(guid, spellId, 0); - return; - } - - // apply reputation discount - uint32 nSpellCost = uint32(floor(trainer_spell->MoneyCost * _player->GetReputationPriceDiscount(trainer))); - - // check money requirement - if (!_player->HasEnoughMoney(uint64(nSpellCost))) - { - SendTrainerBuyFailed(guid, spellId, 1); - return; - } - - _player->ModifyMoney(-int64(nSpellCost)); - - _player->SendPlaySpellVisualKit(179, 0, 0); // 53 SpellCastDirected - _player->SendPlaySpellVisualKit(362, 1, 0); // 113 EmoteSalute - - // learn explicitly or cast explicitly - if (trainer_spell->IsCastable()) - _player->CastSpell(_player, trainer_spell->SpellID, true); + if (uint32 trainerId = sObjectMgr->GetCreatureDefaultTrainer(npc->GetEntry())) + SendTrainerList(npc, trainerId); else - _player->LearnSpell(spellId, false); + TC_LOG_DEBUG("network", "WorldSession::SendTrainerList - Creature id %u has no trainer data.", npc->GetEntry()); +} - WorldPacket data(SMSG_TRAINER_BUY_SUCCEEDED, 12); - data << uint64(guid); - data << uint32(spellId); - SendPacket(&data); +void WorldSession::SendTrainerList(Creature* npc, uint32 trainerId) +{ + // remove fake death + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) + GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + + Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(trainerId); + if (!trainer) + { + TC_LOG_DEBUG("network", "WorldSession::SendTrainerList - trainer spells not found for trainer %s id %d", npc->GetGUID().ToString().c_str(), trainerId); + return; + } + + _player->PlayerTalkClass->GetInteractionData().Reset(); + _player->PlayerTalkClass->GetInteractionData().SourceGuid = npc->GetGUID(); + _player->PlayerTalkClass->GetInteractionData().TrainerId = trainerId; + trainer->SendSpells(npc, _player, GetSessionDbLocaleIndex()); +} + +void WorldSession::HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet) +{ + TC_LOG_DEBUG("network", "WORLD: Received CMSG_TRAINER_BUY_SPELL %s, learn spell id is: %u", packet.TrainerGUID.ToString().c_str(), packet.SpellID); + + Creature* npc = GetPlayer()->GetNPCIfCanInteractWith(packet.TrainerGUID, UNIT_NPC_FLAG_TRAINER); + if (!npc) + { + TC_LOG_DEBUG("network", "WORLD: HandleTrainerBuySpellOpcode - %s not found or you can not interact with him.", packet.TrainerGUID.ToString().c_str()); + return; + } + + // remove fake death + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) + GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + + if (_player->PlayerTalkClass->GetInteractionData().SourceGuid != packet.TrainerGUID) + return; + + if (_player->PlayerTalkClass->GetInteractionData().TrainerId != uint32(packet.TrainerID)) + return; + + Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(packet.TrainerID); + if (!trainer) + return; + + trainer->TeachSpell(npc, _player, packet.SpellID); } void WorldSession::SendTrainerBuyFailed(uint64 guid, uint32 spellId, uint32 reason) diff --git a/src/server/game/Handlers/SkillHandler.cpp b/src/server/game/Handlers/SkillHandler.cpp index e6114b29ee1..8beb346f7e5 100644 --- a/src/server/game/Handlers/SkillHandler.cpp +++ b/src/server/game/Handlers/SkillHandler.cpp @@ -90,7 +90,7 @@ void WorldSession::HandleTalentWipeConfirmOpcode(WorldPacket& recvData) return; } - if (!unit->isCanTrainingAndResetTalentsOf(_player)) + if (!unit->CanResetTalents(_player, false)) return; // remove fake death diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 6cd2f9a368a..5bd37927220 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -2984,16 +2984,6 @@ enum LockType LOCKTYPE_ARCHAEOLOGY = 22, }; -enum TrainerType // this is important type for npcs! -{ - TRAINER_TYPE_CLASS = 0, - TRAINER_TYPE_MOUNTS = 1, // on blizz it's 2 - TRAINER_TYPE_TRADESKILLS = 2, - TRAINER_TYPE_PETS = 3 -}; - -#define MAX_TRAINER_TYPE 4 - // CreatureType.dbc enum CreatureType { diff --git a/src/server/game/Server/Packets/AllPackets.h b/src/server/game/Server/Packets/AllPackets.h index f35b7b8973d..5cb643d10fd 100644 --- a/src/server/game/Server/Packets/AllPackets.h +++ b/src/server/game/Server/Packets/AllPackets.h @@ -21,6 +21,7 @@ #include "AuthenticationPackets.h" #include "GuildPackets.h" #include "LFGPackets.h" +#include "NPCPackets.h" #include "QuestPackets.h" #include "SystemPackets.h" diff --git a/src/server/game/Server/Packets/NPCPackets.cpp b/src/server/game/Server/Packets/NPCPackets.cpp new file mode 100644 index 00000000000..2c8dbe4d144 --- /dev/null +++ b/src/server/game/Server/Packets/NPCPackets.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2008-2018 TrinityCore + * + * 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 "NPCPackets.h" + +void WorldPackets::NPC::Hello::Read() +{ + _worldPacket >> Unit; +} + +WorldPacket const* WorldPackets::NPC::TrainerList::Write() +{ + _worldPacket << TrainerGUID; + _worldPacket << uint32(TrainerType); + _worldPacket << uint32(TrainerID); + + _worldPacket << uint32(Spells.size()); + for (TrainerListSpell const& spell : Spells) + { + _worldPacket << uint32(spell.SpellID); + _worldPacket << uint8(spell.Usable); + _worldPacket << uint32(spell.MoneyCost); + _worldPacket << uint8(spell.ReqLevel); + _worldPacket << uint32(spell.ReqSkillLine); + _worldPacket << uint32(spell.ReqSkillRank); + _worldPacket.append(spell.ReqAbility.data(), spell.ReqAbility.size()); + _worldPacket << uint32(spell.ProfessionDialog); + _worldPacket << uint32(spell.ProfessionButton); + } + + _worldPacket << Greeting; + + return &_worldPacket; +} + +void WorldPackets::NPC::TrainerBuySpell::Read() +{ + _worldPacket >> TrainerGUID; + _worldPacket >> TrainerID; + _worldPacket >> SpellID; +} + +WorldPacket const* WorldPackets::NPC::TrainerBuyFailed::Write() +{ + _worldPacket << TrainerGUID; + _worldPacket << int32(SpellID); + _worldPacket << int32(TrainerFailedReason); + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::NPC::TrainerBuySucceeded::Write() +{ + _worldPacket << TrainerGUID; + _worldPacket << int32(SpellID); + + return &_worldPacket; +} diff --git a/src/server/game/Server/Packets/NPCPackets.h b/src/server/game/Server/Packets/NPCPackets.h new file mode 100644 index 00000000000..d4c89133597 --- /dev/null +++ b/src/server/game/Server/Packets/NPCPackets.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2008-2018 TrinityCore + * + * 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 . + */ + +#ifndef NPCPackets_h__ +#define NPCPackets_h__ + +#include "Packet.h" +#include "ObjectGuid.h" +#include + +namespace WorldPackets +{ + namespace NPC + { + // CMSG_BANKER_ACTIVATE + // CMSG_BATTLEMASTER_HELLO + // CMSG_BINDER_ACTIVATE + // CMSG_GOSSIP_HELLO + // CMSG_LIST_INVENTORY + // CMSG_TRAINER_LIST + class Hello final : public ClientPacket + { + public: + Hello(WorldPacket&& packet) : ClientPacket(std::move(packet)) { } + + void Read() override; + + ObjectGuid Unit; + }; + + struct TrainerListSpell + { + int32 SpellID = 0; + uint8 Usable = 0; + int32 MoneyCost = 0; + int32 ProfessionDialog = 0; + int32 ProfessionButton = 0; + uint8 ReqLevel = 0; + int32 ReqSkillLine = 0; + int32 ReqSkillRank = 0; + std::array ReqAbility = { }; + }; + + class TrainerList final : public ServerPacket + { + public: + TrainerList() : ServerPacket(SMSG_TRAINER_LIST, 150) { } + + WorldPacket const* Write() override; + + ObjectGuid TrainerGUID; + int32 TrainerType = 0; + int32 TrainerID = 0; + std::vector Spells; + std::string Greeting; + }; + + class TrainerBuySpell final : public ClientPacket + { + public: + TrainerBuySpell(WorldPacket&& packet) : ClientPacket(CMSG_TRAINER_BUY_SPELL, std::move(packet)) { } + + void Read() override; + + ObjectGuid TrainerGUID; + int32 TrainerID = 0; + int32 SpellID = 0; + }; + + class TrainerBuyFailed final : public ServerPacket + { + public: + TrainerBuyFailed() : ServerPacket(SMSG_TRAINER_BUY_FAILED, 8 + 4 + 4) { } + + WorldPacket const* Write() override; + + ObjectGuid TrainerGUID; + int32 SpellID = 0; + int32 TrainerFailedReason = 0; + }; + + class TrainerBuySucceeded final : public ServerPacket + { + public: + TrainerBuySucceeded() : ServerPacket(SMSG_TRAINER_BUY_SUCCEEDED, 8 + 4) { } + + WorldPacket const* Write() override; + + ObjectGuid TrainerGUID; + int32 SpellID = 0; + }; + } +} + +#endif // NPCPackets_h__ diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 61bbcb43554..c652b90779a 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -124,6 +124,12 @@ namespace WorldPackets class RequestGuildRewardsList; } + namespace NPC + { + class Hello; + class TrainerBuySpell; + } + namespace Quest { class QuestGiverAcceptQuest; @@ -427,8 +433,7 @@ class TC_GAME_API WorldSession void SendNameQueryOpcode(ObjectGuid guid); - void SendTrainerList(ObjectGuid guid, uint32 index = 0); - void SendTrainerList(ObjectGuid guid, std::string const& strTitle, uint32 index = 0); + void SendTrainerList(Creature* npc, uint32 trainerId); void SendListInventory(ObjectGuid guid); void SendShowBank(ObjectGuid guid); bool CanOpenMailBox(ObjectGuid guid); @@ -783,8 +788,8 @@ class TC_GAME_API WorldSession void HandleTabardVendorActivateOpcode(WorldPacket& recvPacket); void HandleBankerActivateOpcode(WorldPacket& recvPacket); void HandleBuyBankSlotOpcode(WorldPacket& recvPacket); - void HandleTrainerListOpcode(WorldPacket& recvPacket); - void HandleTrainerBuySpellOpcode(WorldPacket& recvPacket); + void HandleTrainerListOpcode(WorldPackets::NPC::Hello& packet); + void HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet); void HandlePetitionShowListOpcode(WorldPacket& recvPacket); void HandleGossipHelloOpcode(WorldPacket& recvPacket); void HandleGossipSelectOptionOpcode(WorldPacket& recvPacket); diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 6641a650b43..c8f970eb452 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -296,9 +296,9 @@ uint32 SpellMgr::GetSpellWithRank(uint32 spell_id, uint32 rank, bool strict) con return spell_id; } -SpellRequiredMapBounds SpellMgr::GetSpellsRequiredForSpellBounds(uint32 spell_id) const +Trinity::IteratorPair SpellMgr::GetSpellsRequiredForSpellBounds(uint32 spell_id) const { - return mSpellReq.equal_range(spell_id); + return Trinity::Containers::MapEqualRange(mSpellReq, spell_id); } SpellsRequiringSpellMapBounds SpellMgr::GetSpellsRequiringSpellBounds(uint32 spell_id) const diff --git a/src/server/game/Spells/SpellMgr.h b/src/server/game/Spells/SpellMgr.h index dd478868da2..071e7b74ddd 100644 --- a/src/server/game/Spells/SpellMgr.h +++ b/src/server/game/Spells/SpellMgr.h @@ -22,6 +22,7 @@ #include "Define.h" #include "Duration.h" +#include "IteratorPair.h" #include "SharedDefines.h" #include "Util.h" @@ -606,7 +607,7 @@ class TC_GAME_API SpellMgr uint32 GetSpellWithRank(uint32 spell_id, uint32 rank, bool strict = false) const; // Spell Required table - SpellRequiredMapBounds GetSpellsRequiredForSpellBounds(uint32 spell_id) const; + Trinity::IteratorPair GetSpellsRequiredForSpellBounds(uint32 spell_id) const; SpellsRequiringSpellMapBounds GetSpellsRequiringSpellBounds(uint32 spell_id) const; bool IsSpellRequiringSpell(uint32 spellid, uint32 req_spellid) const; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 8c6df0c0bf1..88d1fc75baa 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -2052,6 +2052,12 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading GameTeleports..."); sObjectMgr->LoadGameTele(); + TC_LOG_INFO("server.loading", "Loading Trainers..."); // must be after LoadCreatureTemplates + sObjectMgr->LoadTrainers(); + + TC_LOG_INFO("server.loading", "Loading Creature default trainers..."); + sObjectMgr->LoadCreatureDefaultTrainers(); + TC_LOG_INFO("server.loading", "Loading Gossip menu..."); sObjectMgr->LoadGossipMenu(); @@ -2061,9 +2067,6 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading Vendors..."); sObjectMgr->LoadVendors(); // must be after load CreatureTemplate and ItemTemplate - TC_LOG_INFO("server.loading", "Loading Trainers..."); - sObjectMgr->LoadTrainerSpell(); // must be after load CreatureTemplate - TC_LOG_INFO("server.loading", "Loading Waypoints..."); sWaypointMgr->Load(); diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index dce19bbc7ef..3d3336a2773 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -122,7 +122,6 @@ public: { "mail_loot_template", rbac::RBAC_PERM_COMMAND_RELOAD_MAIL_LOOT_TEMPLATE, true, &HandleReloadLootTemplatesMailCommand, "" }, { "milling_loot_template", rbac::RBAC_PERM_COMMAND_RELOAD_MILLING_LOOT_TEMPLATE, true, &HandleReloadLootTemplatesMillingCommand, "" }, { "npc_spellclick_spells", rbac::RBAC_PERM_COMMAND_RELOAD_NPC_SPELLCLICK_SPELLS, true, &HandleReloadSpellClickSpellsCommand, "" }, - { "npc_trainer", rbac::RBAC_PERM_COMMAND_RELOAD_NPC_TRAINER, true, &HandleReloadNpcTrainerCommand, "" }, { "npc_vendor", rbac::RBAC_PERM_COMMAND_RELOAD_NPC_VENDOR, true, &HandleReloadNpcVendorCommand, "" }, { "page_text", rbac::RBAC_PERM_COMMAND_RELOAD_PAGE_TEXT, true, &HandleReloadPageTextsCommand, "" }, { "pickpocketing_loot_template", rbac::RBAC_PERM_COMMAND_RELOAD_PICKPOCKETING_LOOT_TEMPLATE, true, &HandleReloadLootTemplatesPickpocketingCommand, "" }, @@ -156,6 +155,7 @@ public: { "spell_threats", rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_THREATS, true, &HandleReloadSpellThreatsCommand, "" }, { "spell_group_stack_rules", rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_GROUP_STACK_RULES, true, &HandleReloadSpellGroupStackRulesCommand, "" }, { "trinity_string", rbac::RBAC_PERM_COMMAND_RELOAD_TRINITY_STRING, true, &HandleReloadTrinityStringCommand, "" }, + { "trainer", rbac::RBAC_PERM_COMMAND_RELOAD_TRAINER, true, &HandleReloadTrainerCommand, "" }, { "warden_action", rbac::RBAC_PERM_COMMAND_RELOAD_WARDEN_ACTION, true, &HandleReloadWardenactionCommand, "" }, { "waypoint_scripts", rbac::RBAC_PERM_COMMAND_RELOAD_WAYPOINT_SCRIPTS, true, &HandleReloadWpScriptsCommand, "" }, { "waypoint_data", rbac::RBAC_PERM_COMMAND_RELOAD_WAYPOINT_DATA, true, &HandleReloadWpCommand, "" }, @@ -237,7 +237,7 @@ public: static bool HandleReloadAllNpcCommand(ChatHandler* handler, char const* args) { if (*args != 'a') // will be reloaded from all_gossips - HandleReloadNpcTrainerCommand(handler, "a"); + HandleReloadTrainerCommand(handler, "a"); HandleReloadNpcVendorCommand(handler, "a"); HandleReloadPointsOfInterestCommand(handler, "a"); HandleReloadSpellClickSpellsCommand(handler, "a"); @@ -704,11 +704,15 @@ public: return true; } - static bool HandleReloadNpcTrainerCommand(ChatHandler* handler, char const* /*args*/) + static bool HandleReloadTrainerCommand(ChatHandler* handler, char const* /*args*/) { - TC_LOG_INFO("misc", "Re-Loading `npc_trainer` Table!"); - sObjectMgr->LoadTrainerSpell(); - handler->SendGlobalGMSysMessage("DB table `npc_trainer` reloaded."); + TC_LOG_INFO("misc", "Re-Loading `trainer` Table!"); + sObjectMgr->LoadTrainers(); + sObjectMgr->LoadCreatureDefaultTrainers(); + handler->SendGlobalGMSysMessage("DB table `trainer` reloaded."); + handler->SendGlobalGMSysMessage("DB table `trainer_locale` reloaded."); + handler->SendGlobalGMSysMessage("DB table `trainer_spell` reloaded."); + handler->SendGlobalGMSysMessage("DB table `creature_default_trainer` reloaded."); return true; } diff --git a/src/server/scripts/Northrend/zone_storm_peaks.cpp b/src/server/scripts/Northrend/zone_storm_peaks.cpp index b7948cb990e..261d8e9a1f6 100644 --- a/src/server/scripts/Northrend/zone_storm_peaks.cpp +++ b/src/server/scripts/Northrend/zone_storm_peaks.cpp @@ -118,7 +118,8 @@ class npc_injured_goblin : public CreatureScript enum RoxiRamrocket { SPELL_MECHANO_HOG = 60866, - SPELL_MEKGINEERS_CHOPPER = 60867 + SPELL_MEKGINEERS_CHOPPER = 60867, + TRAINER_ID_ROXI_RAMROCKET = 102 }; class npc_roxi_ramrocket : public CreatureScript @@ -156,7 +157,7 @@ class npc_roxi_ramrocket : public CreatureScript switch (action) { case GOSSIP_ACTION_TRAIN: - player->GetSession()->SendTrainerList(me->GetGUID()); + player->GetSession()->SendTrainerList(me, TRAINER_ID_ROXI_RAMROCKET); break; case GOSSIP_ACTION_TRADE: player->GetSession()->SendListInventory(me->GetGUID()); diff --git a/src/server/scripts/World/npc_professions.cpp b/src/server/scripts/World/npc_professions.cpp index 2c74196795e..00d1ea31f0c 100644 --- a/src/server/scripts/World/npc_professions.cpp +++ b/src/server/scripts/World/npc_professions.cpp @@ -169,6 +169,7 @@ enum SpecializationTrainers N_TRAINER_HAMMER = 11191, // Lilith the Lithe N_TRAINER_AXE = 11192, // Kilram N_TRAINER_SWORD = 11193, // Seril Scourgebane + TRAINER_ID_BLACKSMITHING = 80, /* Leatherworking */ N_TRAINER_DRAGON1 = 7866, // Peter Galen @@ -177,11 +178,13 @@ enum SpecializationTrainers N_TRAINER_ELEMENTAL2 = 7869, // Brumn Winterhoof N_TRAINER_TRIBAL1 = 7870, // Caryssia Moonhunter N_TRAINER_TRIBAL2 = 7871, // Se'Jib + TRAINER_ID_LEATHERWORKING = 103, /* Tailoring */ N_TRAINER_SPELLFIRE = 22213, // Gidge Spellweaver N_TRAINER_MOONCLOTH = 22208, // Nasmara Moonsong N_TRAINER_SHADOWEAVE = 22212, // Andrion Darkspinner + TRAINER_ID_TAILORING = 117, }; /*### @@ -552,7 +555,7 @@ public: player->GetSession()->SendListInventory(me->GetGUID()); break; case GOSSIP_ACTION_TRAIN: - player->GetSession()->SendTrainerList(me->GetGUID()); + player->GetSession()->SendTrainerList(me, TRAINER_ID_BLACKSMITHING); break; //Learn Armor/Weapon case GOSSIP_ACTION_INFO_DEF + 1: @@ -826,7 +829,7 @@ class npc_prof_leather : public CreatureScript player->GetSession()->SendListInventory(creature->GetGUID()); break; case GOSSIP_ACTION_TRAIN: - player->GetSession()->SendTrainerList(creature->GetGUID()); + player->GetSession()->SendTrainerList(me, TRAINER_ID_LEATHERWORKING); break; //Unlearn Leather case GOSSIP_ACTION_INFO_DEF + 1: @@ -973,7 +976,7 @@ class npc_prof_tailor : public CreatureScript player->GetSession()->SendListInventory(me->GetGUID()); break; case GOSSIP_ACTION_TRAIN: - player->GetSession()->SendTrainerList(me->GetGUID()); + player->GetSession()->SendTrainerList(me, TRAINER_ID_TAILORING); break; //Learn Tailor case GOSSIP_ACTION_INFO_DEF + 1: @@ -1083,100 +1086,10 @@ enum MultiProfessionTrainer GOSSIP_MENU_TAILORING = 12199 }; -/*### -# start menu multi profession trainer -###*/ - -class npc_multi_profession_trainer : public CreatureScript -{ -public: - npc_multi_profession_trainer() : CreatureScript("npc_multi_profession_trainer") { } - - struct npc_multi_profession_trainerAI : public ScriptedAI - { - npc_multi_profession_trainerAI(Creature* creature) : ScriptedAI(creature) { } - - bool GossipSelect(Player* player, uint32 menuId, uint32 gossipListId) override - { - switch (gossipListId) - { - case GOSSIP_OPTION_ALCHEMY: - case GOSSIP_OPTION_BLACKSMITHING: - case GOSSIP_OPTION_ENCHANTING: - case GOSSIP_OPTION_ENGINEERING: - case GOSSIP_OPTION_HERBALISM: - case GOSSIP_OPTION_INSCRIPTION: - case GOSSIP_OPTION_JEWELCRAFTING: - case GOSSIP_OPTION_LEATHERWORKING: - case GOSSIP_OPTION_MINING: - case GOSSIP_OPTION_SKINNING: - case GOSSIP_OPTION_TAILORING: - SendTrainerList(player, gossipListId); - break; - case GOSSIP_OPTION_MULTI: - { - switch (menuId) - { - case GOSSIP_MENU_HERBALISM: - SendTrainerList(player, GOSSIP_OPTION_HERBALISM); - break; - case GOSSIP_MENU_MINING: - SendTrainerList(player, GOSSIP_OPTION_MINING); - break; - case GOSSIP_MENU_SKINNING: - SendTrainerList(player, GOSSIP_OPTION_SKINNING); - break; - case GOSSIP_MENU_ALCHEMY: - SendTrainerList(player, GOSSIP_OPTION_ALCHEMY); - break; - case GOSSIP_MENU_BLACKSMITHING: - SendTrainerList(player, GOSSIP_OPTION_BLACKSMITHING); - break; - case GOSSIP_MENU_ENCHANTING: - SendTrainerList(player, GOSSIP_OPTION_ENCHANTING); - break; - case GOSSIP_MENU_ENGINEERING: - SendTrainerList(player, GOSSIP_OPTION_ENGINEERING); - break; - case GOSSIP_MENU_INSCRIPTION: - SendTrainerList(player, GOSSIP_OPTION_INSCRIPTION); - break; - case GOSSIP_MENU_JEWELCRAFTING: - SendTrainerList(player, GOSSIP_OPTION_JEWELCRAFTING); - break; - case GOSSIP_MENU_LEATHERWORKING: - SendTrainerList(player, GOSSIP_OPTION_LEATHERWORKING); - break; - case GOSSIP_MENU_TAILORING: - SendTrainerList(player, GOSSIP_OPTION_TAILORING); - break; - default: - break; - } - } - default: - break; - } - return false; - } - - void SendTrainerList(Player* player, uint32 Index) - { - player->GetSession()->SendTrainerList(me->GetGUID(), Index + 1); - } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return new npc_multi_profession_trainerAI(creature); - } -}; - void AddSC_npc_professions() { new npc_prof_blacksmith(); new npc_engineering_tele_trinket(); new npc_prof_leather(); new npc_prof_tailor(); - new npc_multi_profession_trainer(); }