diff options
Diffstat (limited to 'src')
27 files changed, 515 insertions, 541 deletions
diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index ef469b4e1e7..8f0bf1e384d 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, HealthScalingExpansion, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, 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, VehicleId, mingold, maxgold, AIName, MovementType, InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template 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, HealthScalingExpansion, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_class, type, type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template 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_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_GAMEOBJECT_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM gameobject WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? ORDER BY order_", CONNECTION_SYNCH); diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h index 3a10429f462..a42d20c3cfb 100644 --- a/src/server/game/Accounts/RBAC.h +++ b/src/server/game/Accounts/RBAC.h @@ -577,7 +577,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 1bc61659e83..80f775e410a 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -52,15 +52,6 @@ #include "WorldPacket.h" #include <G3D/g3dmath.h> -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->GetFlags2() & ITEM_FLAG2_DONT_IGNORE_BUY_PRICE || !ExtendedCost; @@ -996,10 +987,9 @@ bool Creature::isCanInteractWithBattleMaster(Player* player, bool msg) const return true; } -bool Creature::isCanTrainingAndResetTalentsOf(Player* player) const +bool Creature::CanResetTalents(Player* player) const { return player->getLevel() >= 15 - && GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS && player->getClass() == GetCreatureTemplate()->trainer_class; } @@ -2556,11 +2546,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 c50853724b0..320cb16ad12 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -103,7 +103,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma /// @todo Rename these properly bool isCanInteractWithBattleMaster(Player* player, bool msg) const; - bool isCanTrainingAndResetTalentsOf(Player* player) const; + bool CanResetTalents(Player* player) const; bool CanCreatureAttack(Unit const* victim, bool force = true) const; bool IsImmunedToSpell(SpellInfo const* spellInfo) const override; // override Unit::IsImmunedToSpell bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const override; // override Unit::IsImmunedToSpellEffect @@ -152,8 +152,6 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, 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 e7fea1304a1..4b62e3ba138 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -328,9 +328,7 @@ struct TC_GAME_API CreatureTemplate uint32 unit_flags3; // enum UnitFlags3 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) @@ -595,39 +593,4 @@ struct VendorItemData } }; -#define MAX_TRAINERSPELL_ABILITY_REQS 3 - -struct TrainerSpell -{ - TrainerSpell() : SpellID(0), MoneyCost(0), ReqSkillLine(0), ReqSkillRank(0), ReqLevel(0), Index(0) - { - for (uint8 i = 0; i < MAX_TRAINERSPELL_ABILITY_REQS; ++i) - ReqAbility[i] = 0; - } - - uint32 SpellID; - uint32 MoneyCost; - uint32 ReqSkillLine; - uint32 ReqSkillRank; - uint32 ReqLevel; - uint32 ReqAbility[MAX_TRAINERSPELL_ABILITY_REQS]; - uint32 Index; - - // helpers - bool IsCastable() const { return ReqAbility[0] != SpellID; } -}; - -typedef std::unordered_map<uint32 /*spellid*/, TrainerSpell> 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; -}; - #endif // CreatureData_h__ diff --git a/src/server/game/Entities/Creature/GossipDef.cpp b/src/server/game/Entities/Creature/GossipDef.cpp index d19e9c19a0e..8848fb5aa44 100644 --- a/src/server/game/Entities/Creature/GossipDef.cpp +++ b/src/server/game/Entities/Creature/GossipDef.cpp @@ -135,12 +135,13 @@ void GossipMenu::AddMenuItem(uint32 menuId, uint32 menuItemId, uint32 sender, ui } } -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 @@ -170,6 +171,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(); diff --git a/src/server/game/Entities/Creature/GossipDef.h b/src/server/game/Entities/Creature/GossipDef.h index b3e58081400..0916c88083a 100644 --- a/src/server/game/Entities/Creature/GossipDef.h +++ b/src/server/game/Entities/Creature/GossipDef.h @@ -49,7 +49,7 @@ enum Gossip_Option GOSSIP_OPTION_STABLEPET = 14, //UNIT_NPC_FLAG_STABLE (4194304) GOSSIP_OPTION_ARMORER = 15, //UNIT_NPC_FLAG_ARMORER (4096) GOSSIP_OPTION_UNLEARNTALENTS = 16, //UNIT_NPC_FLAG_TRAINER (16) (bonus option for GOSSIP_OPTION_TRAINER) - GOSSIP_OPTION_UNLEARNPETTALENTS = 17, //UNIT_NPC_FLAG_TRAINER (16) (bonus option for GOSSIP_OPTION_TRAINER) + GOSSIP_OPTION_UNLEARNPETTALENTS_OLD = 17, // deprecated GOSSIP_OPTION_LEARNDUALSPEC = 18, //UNIT_NPC_FLAG_TRAINER (16) (bonus option for GOSSIP_OPTION_TRAINER) GOSSIP_OPTION_OUTDOORPVP = 19, //added by code (option for outdoor pvp creatures) GOSSIP_OPTION_MAX @@ -146,6 +146,8 @@ struct GossipMenuItemData { uint32 GossipActionMenuId; // MenuId of the gossip triggered by this action uint32 GossipActionPoi; + + uint32 TrainerId; }; // need an ordered container @@ -175,7 +177,7 @@ class TC_GAME_API GossipMenu 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 { return uint32(_menuItems.size()); } bool Empty() const { return _menuItems.empty(); } @@ -201,6 +203,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(); diff --git a/src/server/game/Entities/Creature/Trainer.cpp b/src/server/game/Entities/Creature/Trainer.cpp new file mode 100644 index 00000000000..013d8c73453 --- /dev/null +++ b/src/server/game/Entities/Creature/Trainer.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2008-2017 TrinityCore <http://www.trinitycore.org/> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "Trainer.h" +#include "Creature.h" +#include "NPCPackets.h" +#include "Player.h" +#include "SpellInfo.h" +#include "SpellMgr.h" + +namespace Trainer +{ + + Trainer::Trainer(uint32 id, Type type, std::string greeting, std::vector<Spell> spells) : _id(id), _type(type), _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); + + WorldPackets::NPC::TrainerList trainerList; + trainerList.TrainerGUID = npc->GetGUID(); + trainerList.TrainerType = AsUnderlyingType(_type); + trainerList.TrainerID = _id; + trainerList.Greeting = GetGreeting(locale); + trainerList.Spells.reserve(_spells.size()); + for (Spell const& trainerSpell : _spells) + { + if (!player->IsSpellFitByClassAndRace(trainerSpell.SpellId)) + continue; + + trainerList.Spells.emplace_back(); + WorldPackets::NPC::TrainerListSpell& trainerListSpell = trainerList.Spells.back(); + trainerListSpell.SpellID = trainerSpell.SpellId; + trainerListSpell.MoneyCost = int32(trainerSpell.MoneyCost * reputationDiscount); + trainerListSpell.ReqSkillLine = trainerSpell.ReqSkillLine; + trainerListSpell.ReqSkillRank = trainerSpell.ReqSkillRank; + std::copy(trainerSpell.ReqAbility.begin(), trainerSpell.ReqAbility.end(), trainerListSpell.ReqAbility.begin()); + trainerListSpell.Usable = AsUnderlyingType(GetSpellState(player, &trainerSpell)); + trainerListSpell.ReqLevel = trainerSpell.ReqLevel; + } + + player->SendDirectMessage(trainerList.Write()); + } + + void Trainer::TeachSpell(Creature const* npc, Player* player, uint32 spellId) const + { + Spell const* trainerSpell = GetSpell(spellId); + if (!trainerSpell || !CanTeachSpell(player, trainerSpell)) + { + SendTeachFailure(npc, player, spellId, FailReason::Unavailable); + return; + } + + float reputationDiscount = player->GetReputationPriceDiscount(npc); + int64 moneyCost = int64(trainerSpell->MoneyCost * reputationDiscount); + if (!player->HasEnoughMoney(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->CastSpellId) + player->CastSpell(player, trainerSpell->CastSpellId, true); + else + player->LearnSpell(trainerSpell->SpellId, false); + } + + 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 additional spell requirement + for (auto const& requirePair : sSpellMgr->GetSpellsRequiredForSpellBounds(trainerSpell->SpellId)) + if (!player->HasSpell(requirePair.second)) + return SpellState::Unavailable; + + return SpellState::Available; + } + + 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()); + } + + 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..82c1096fa67 --- /dev/null +++ b/src/server/game/Entities/Creature/Trainer.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008-2017 TrinityCore <http://www.trinitycore.org/> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef Trainer_h__ +#define Trainer_h__ + +#include "Common.h" +#include <array> +#include <string> +#include <vector> + +class Creature; +class ObjectMgr; +class Player; + +namespace Trainer +{ + enum class Type : uint32 + { + None = 0, + Talent = 1, + Tradeskill = 2, + Pet = 3, + }; + + enum class SpellState : uint8 + { + Known = 0, + Available = 1, + Unavailable = 2 + }; + + enum class FailReason : uint32 + { + Unavailable = 0, + NotEnoughMoney = 1 + }; + + struct Spell + { + uint32 SpellId = 0; + uint32 MoneyCost = 0; + uint32 ReqSkillLine = 0; + uint32 ReqSkillRank = 0; + std::array<uint32, 3> ReqAbility = { }; + uint8 ReqLevel = 0; + + uint32 CastSpellId = 0; + }; + + class Trainer + { + public: + Trainer(uint32 id, Type type, std::string greeting, std::vector<Spell> spells); + + void SendSpells(Creature const* npc, Player const* player, LocaleConstant locale) const; + void TeachSpell(Creature const* npc, Player* player, uint32 spellId) 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; + std::string const& GetGreeting(LocaleConstant locale) const; + + friend class ObjectMgr; + void AddGreetingLocale(LocaleConstant locale, std::string greeting); + + uint32 _id; + Type _type; + std::vector<Spell> _spells; + std::array<std::string, TOTAL_LOCALES> _greeting; + }; +} + +#endif // Trainer_h__ diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 5c0bba68298..1510ac718d9 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -2270,19 +2270,19 @@ void Object::ForceValuesUpdateAtIndex(uint32 i) AddToObjectUpdateIfNeeded(); } -void WorldObject::SendMessageToSet(WorldPacket const* data, bool self) +void WorldObject::SendMessageToSet(WorldPacket const* data, bool self) const { if (IsInWorld()) SendMessageToSetInRange(data, GetVisibilityRange(), self); } -void WorldObject::SendMessageToSetInRange(WorldPacket const* data, float dist, bool /*self*/) +void WorldObject::SendMessageToSetInRange(WorldPacket const* data, float dist, bool /*self*/) const { Trinity::MessageDistDeliverer notifier(this, data, dist); Cell::VisitWorldObjects(this, notifier, dist); } -void WorldObject::SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) +void WorldObject::SendMessageToSet(WorldPacket const* 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 d01c4f6cd9e..7b21be71a39 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -464,9 +464,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 const* data, bool self); - virtual void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self); - virtual void SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr); + virtual void SendMessageToSet(WorldPacket const* data, bool self) const; + virtual void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) const; + virtual void SendMessageToSet(WorldPacket const* 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 351f75b47e7..682c8624392 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -3648,74 +3648,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_TRAINERSPELL_ABILITY_REQS; ++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_TRAINERSPELL_ABILITY_REQS; ++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 (prevSpell && !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_TRAINERSPELL_ABILITY_REQS; ++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 * @@ -5913,7 +5845,7 @@ bool Player::UpdatePosition(float x, float y, float z, float orientation, bool t return true; } -void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) +void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) const { if (self) GetSession()->SendPacket(data); @@ -5922,7 +5854,7 @@ void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool s Cell::VisitWorldObjects(this, notifier, dist); } -void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self, bool own_team_only) +void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self, bool own_team_only) const { if (self) GetSession()->SendPacket(data); @@ -5931,7 +5863,7 @@ void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool s Cell::VisitWorldObjects(this, notifier, dist); } -void Player::SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) +void Player::SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) const { if (skipped_rcvr != this) GetSession()->SendPacket(data); @@ -9340,27 +9272,6 @@ void Player::SendRespecWipeConfirm(ObjectGuid const& guid, uint32 cost) const GetSession()->SendPacket(respecWipeConfirm.Write()); } -void Player::ResetPetTalents() -{ - /* TODO: 6.x remove/update pet talents - // This needs another gossip option + NPC text as a confirmation. - // The confirmation gossip listid has the text: "Yes, please do." - Pet* pet = GetPet(); - - if (!pet || pet->getPetType() != HUNTER_PET || pet->m_usedTalentCount == 0) - return; - - CharmInfo* charmInfo = pet->GetCharmInfo(); - if (!charmInfo) - { - TC_LOG_ERROR("entities.player", "Object (%s) is considered pet-like but doesn't have a charminfo!", pet->GetGUID().ToString().c_str()); - return; - } - pet->resetTalents(); - SendTalentsInfoData(true); - */ -} - /*********************************************************/ /*** STORAGE SYSTEM ***/ /*********************************************************/ @@ -14167,10 +14078,10 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool for (GossipMenuItemsContainer::const_iterator itr = menuItemBounds.first; itr != menuItemBounds.second; ++itr) { - bool canTalk = true; if (!sConditionMgr->IsObjectMeetToConditions(this, source, itr->second.Conditions)) continue; + bool canTalk = true; if (Creature* creature = source->ToCreature()) { if (!(itr->second.OptionNpcflag & npcflags)) @@ -14199,11 +14110,7 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool canTalk = false; break; case GOSSIP_OPTION_UNLEARNTALENTS: - if (!creature->isCanTrainingAndResetTalentsOf(this)) - 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 (!creature->CanResetTalents(this)) canTalk = false; break; case GOSSIP_OPTION_TAXIVENDOR: @@ -14221,16 +14128,8 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool case GOSSIP_OPTION_QUESTGIVER: canTalk = false; break; - case GOSSIP_OPTION_TRAINER: - if (getClass() != creature->GetCreatureTemplate()->trainer_class && creature->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS) - { - TC_LOG_ERROR("sql.sql", "GOSSIP_OPTION_TRAINER:: Player %s (%s) request wrong gossip menu: %u with wrong class: %u at Creature: %s (Entry: %u, Trainer Class: %u)", - GetName().c_str(), GetGUID().ToString().c_str(), menu->GetGossipMenu().GetMenuId(), getClass(), - creature->GetName().c_str(), creature->GetEntry(), creature->GetCreatureTemplate()->trainer_class); - canTalk = false; - } - break; case GOSSIP_OPTION_GOSSIP: + case GOSSIP_OPTION_TRAINER: case GOSSIP_OPTION_SPIRITGUIDE: case GOSSIP_OPTION_INNKEEPER: case GOSSIP_OPTION_BANKER: @@ -14297,7 +14196,7 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool } menu->GetGossipMenu().AddMenuItem(itr->second.OptionIndex, itr->second.OptionIcon, strOptionText, 0, itr->second.OptionType, strBoxText, itr->second.BoxMoney, itr->second.BoxCoded); - menu->GetGossipMenu().AddGossipMenuItemData(itr->second.OptionIndex, itr->second.ActionMenuId, itr->second.ActionPoiId); + menu->GetGossipMenu().AddGossipMenuItemData(itr->second.OptionIndex, itr->second.ActionMenuId, itr->second.ActionPoiId, itr->second.TrainerId); } } } @@ -14398,7 +14297,7 @@ void Player::OnGossipSelect(WorldObject* source, uint32 gossipListId, uint32 men GetSession()->SendStablePet(guid); break; case GOSSIP_OPTION_TRAINER: - GetSession()->SendTrainerList(guid); + GetSession()->SendTrainerList(guid, menuItemData->TrainerId); break; case GOSSIP_OPTION_LEARNDUALSPEC: break; @@ -14406,10 +14305,6 @@ void Player::OnGossipSelect(WorldObject* source, uint32 gossipListId, uint32 men PlayerTalkClass->SendCloseGossip(); SendRespecWipeConfirm(guid, sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST) ? 0 : GetNextResetTalentsCost()); break; - case GOSSIP_OPTION_UNLEARNPETTALENTS: - PlayerTalkClass->SendCloseGossip(); - ResetPetTalents(); - break; case GOSSIP_OPTION_TAXIVENDOR: GetSession()->SendTaxiMenu(source->ToCreature()); break; diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 6a8d1a09419..b96a44eb310 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -242,14 +242,6 @@ typedef std::unordered_map<uint32, PlayerCurrency> PlayerCurrenciesMap; typedef std::unordered_map<uint32 /*instanceId*/, time_t/*releaseTime*/> 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, @@ -1506,7 +1498,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void SetBindPoint(ObjectGuid guid) const; void SendRespecWipeConfirm(ObjectGuid const& guid, uint32 cost) const; - void ResetPetTalents(); void RegenerateAll(); void Regenerate(Powers power); void RegenerateHealth(); @@ -1576,7 +1567,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> bool HasSpell(uint32 spell) const override; bool HasActiveSpell(uint32 spell) const; // show in spellbook SpellInfo const* GetCastSpellInfo(SpellInfo const* spellInfo) const override; - TrainerSpellState GetTrainerSpellState(TrainerSpell const* trainer_spell) const; bool IsSpellFitByClassAndRace(uint32 spell_id) const; bool IsNeedCastPassiveSpellAtLearn(SpellInfo const* spellInfo) const; bool IsCurrentSpecMasterySpell(SpellInfo const* spellInfo) const; @@ -1852,10 +1842,10 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> bool UpdatePosition(const Position &pos, bool teleport = false) override { return UpdatePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teleport); } void UpdateUnderwaterState(Map* m, float x, float y, float z) override; - void SendMessageToSet(WorldPacket const* data, bool self) override { SendMessageToSetInRange(data, GetVisibilityRange(), self); } - void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) override; - void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self, bool own_team_only); - void SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) override; + void SendMessageToSet(WorldPacket const* data, bool self) const override { SendMessageToSetInRange(data, GetVisibilityRange(), self); } + void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) const override; + void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self, bool own_team_only) const; + void SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) const override; Corpse* GetCorpse() const; void SpawnCorpseBones(bool triggerSave = true); @@ -2347,6 +2337,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> SceneMgr& GetSceneMgr() { return m_sceneMgr; } RestMgr& GetRestMgr() const { return *_restMgr; } + uint32 GetCurrentTrainerId() const { return _currentTrainerId; } + void SetCurrentTrainerId(uint32 trainerId) { _currentTrainerId = trainerId; } protected: // Gamemaster whisper whitelist GuidList WhisperList; @@ -2698,6 +2690,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void _InitHonorLevelOnLoadFromDB(uint32 /*honor*/, uint32 /*honorLevel*/, uint32 /*prestigeLevel*/); std::unique_ptr<RestMgr> _restMgr; + + uint32 _currentTrainerId; }; TC_GAME_API void AddItemsSetItem(Player* player, Item* item); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 8d8371e28d4..4d1c88b2a8b 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -12743,7 +12743,7 @@ void Unit::SendCancelSpellVisualKit(uint32 id) SendMessageToSet(cancelSpellVisualKit.Write(), true); } -void Unit::SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration) +void Unit::SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration) const { WorldPackets::Spells::PlaySpellVisualKit playSpellVisualKit; playSpellVisualKit.Unit = GetGUID(); diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index b782ccfd288..cd56f883e7a 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1324,7 +1324,7 @@ class TC_GAME_API Unit : public WorldObject void SendPlaySpellVisual(ObjectGuid const& target, uint32 spellVisualId, uint16 missReason, uint16 reflectStatus, float travelSpeed, bool speedAsTime = false); void SendPlaySpellVisual(Position const& targetPosition, float o, uint32 spellVisualId, uint16 missReason, uint16 reflectStatus, float travelSpeed, bool speedAsTime = false); void SendCancelSpellVisualKit(uint32 id); - 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 89fb1ab8a01..2efdbfb015b 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -19,6 +19,7 @@ #include "ObjectMgr.h" #include "ArenaTeamMgr.h" #include "Chat.h" +#include "Containers.h" #include "DatabaseEnv.h" #include "DB2Stores.h" #include "DisableMgr.h" @@ -285,8 +286,6 @@ ObjectMgr::~ObjectMgr() for (CacheVendorItemContainer::iterator itr = _cacheVendorItemStore.begin(); itr != _cacheVendorItemStore.end(); ++itr) itr->second.Clear(); - _cacheTrainerSpellStore.clear(); - for (DungeonEncounterContainer::iterator itr =_dungeonEncounterStore.begin(); itr != _dungeonEncounterStore.end(); ++itr) for (DungeonEncounterList::iterator encounterItr = itr->second.begin(); encounterItr != itr->second.end(); ++encounterItr) delete *encounterItr; @@ -425,15 +424,15 @@ void ObjectMgr::LoadCreatureTemplates() "modelid4, name, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, " // 20 21 22 23 24 25 26 27 28 29 30 "faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, " - // 31 32 33 34 35 36 37 38 39 40 - "unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_type, trainer_class, trainer_race, type, " - // 41 42 43 44 45 46 47 48 49 50 51 + // 31 32 33 34 35 36 37 38 + "unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_class, 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, " - // 52 53 54 55 56 57 58 59 60 61 62 63 64 + // 50 51 52 53 54 55 56 57 58 59 60 61 62 "spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, " - // 65 66 67 68 69 70 71 72 73 + // 63 64 65 66 67 68 69 70 71 "InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, " - // 74 75 76 77 78 79 + // 72 73 74 75 76 77 "RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template"); if (!result) @@ -504,42 +503,40 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields) creatureTemplate.unit_flags3 = fields[34].GetUInt32(); creatureTemplate.dynamicflags = fields[35].GetUInt32(); creatureTemplate.family = CreatureFamily(fields[36].GetUInt8()); - creatureTemplate.trainer_type = uint32(fields[37].GetUInt8()); - creatureTemplate.trainer_class = uint32(fields[38].GetUInt8()); - creatureTemplate.trainer_race = uint32(fields[39].GetUInt8()); - creatureTemplate.type = uint32(fields[40].GetUInt8()); - creatureTemplate.type_flags = fields[41].GetUInt32(); - creatureTemplate.type_flags2 = fields[42].GetUInt32(); - creatureTemplate.lootid = fields[43].GetUInt32(); - creatureTemplate.pickpocketLootId = fields[44].GetUInt32(); - creatureTemplate.SkinLootId = fields[45].GetUInt32(); + creatureTemplate.trainer_class = 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(); for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) - creatureTemplate.resistance[i] = fields[46 + i - 1].GetInt16(); + creatureTemplate.resistance[i] = fields[44 + i - 1].GetInt16(); for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i) - creatureTemplate.spells[i] = fields[52 + i].GetUInt32(); - - creatureTemplate.VehicleId = fields[60].GetUInt32(); - creatureTemplate.mingold = fields[61].GetUInt32(); - creatureTemplate.maxgold = fields[62].GetUInt32(); - creatureTemplate.AIName = fields[63].GetString(); - creatureTemplate.MovementType = uint32(fields[64].GetUInt8()); - creatureTemplate.InhabitType = uint32(fields[65].GetUInt8()); - creatureTemplate.HoverHeight = fields[66].GetFloat(); - creatureTemplate.ModHealth = fields[67].GetFloat(); - creatureTemplate.ModHealthExtra = fields[68].GetFloat(); - creatureTemplate.ModMana = fields[69].GetFloat(); - creatureTemplate.ModManaExtra = fields[70].GetFloat(); - creatureTemplate.ModArmor = fields[71].GetFloat(); - creatureTemplate.ModDamage = fields[72].GetFloat(); - creatureTemplate.ModExperience = fields[73].GetFloat(); - creatureTemplate.RacialLeader = fields[74].GetBool(); - creatureTemplate.movementId = fields[75].GetUInt32(); - creatureTemplate.RegenHealth = fields[76].GetBool(); - creatureTemplate.MechanicImmuneMask = fields[77].GetUInt32(); - creatureTemplate.flags_extra = fields[78].GetUInt32(); - creatureTemplate.ScriptID = GetScriptId(fields[79].GetString()); + creatureTemplate.spells[i] = fields[50 + i].GetUInt32(); + + creatureTemplate.VehicleId = fields[58].GetUInt32(); + creatureTemplate.mingold = fields[59].GetUInt32(); + creatureTemplate.maxgold = fields[60].GetUInt32(); + creatureTemplate.AIName = fields[61].GetString(); + creatureTemplate.MovementType = uint32(fields[62].GetUInt8()); + creatureTemplate.InhabitType = uint32(fields[63].GetUInt8()); + creatureTemplate.HoverHeight = fields[64].GetFloat(); + creatureTemplate.ModHealth = fields[65].GetFloat(); + creatureTemplate.ModHealthExtra = fields[66].GetFloat(); + creatureTemplate.ModMana = fields[67].GetFloat(); + creatureTemplate.ModManaExtra = fields[68].GetFloat(); + creatureTemplate.ModArmor = fields[69].GetFloat(); + creatureTemplate.ModDamage = fields[70].GetFloat(); + creatureTemplate.ModExperience = fields[71].GetFloat(); + creatureTemplate.RacialLeader = fields[72].GetBool(); + creatureTemplate.movementId = fields[73].GetUInt32(); + creatureTemplate.RegenHealth = fields[74].GetBool(); + creatureTemplate.MechanicImmuneMask = fields[75].GetUInt32(); + creatureTemplate.flags_extra = fields[76].GetUInt32(); + creatureTemplate.ScriptID = GetScriptId(fields[77].GetString()); } void ObjectMgr::LoadCreatureTemplateAddons() @@ -772,24 +769,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo) 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).", @@ -953,9 +932,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo) if (cInfo->RangeAttackTime == 0) const_cast<CreatureTemplate*>(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); @@ -8494,118 +8470,126 @@ 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) +void ObjectMgr::LoadTrainers() { - if (ID >= TRINITY_TRAINER_START_REF) - return; + uint32 oldMSTime = getMSTime(); - 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; - } + // For reload case + _trainers.clear(); - if (!(cInfo->npcflag & UNIT_NPC_FLAG_TRAINER)) + std::unordered_map<int32, std::vector<Trainer::Spell>> spellsByTrainer; + if (QueryResult trainerSpellsResult = WorldDatabase.Query("SELECT TrainerId, SpellId, MoneyCost, ReqSkillLine, ReqSkillRank, ReqAbility1, ReqAbility2, ReqAbility3, ReqLevel FROM trainer_spell")) { - TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains entries for a creature template (ID: %u) without any trainer flag, ignoring", ID); - return; - } + do + { + Field* fields = trainerSpellsResult->Fetch(); - 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; - } + 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(); - 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; - } + 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; + } - TrainerSpellData& data = _cacheTrainerSpellStore[ID]; + 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, spell.SpellId, trainerId); + continue; + } - TrainerSpell& trainerSpell = data.spellList[SpellID]; - trainerSpell.SpellID = SpellID; - trainerSpell.MoneyCost = MoneyCost; - trainerSpell.ReqSkillLine = ReqSkillLine; - trainerSpell.ReqSkillRank = ReqSkillRank; - trainerSpell.ReqLevel = ReqLevel; - trainerSpell.Index = Index; + 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, spell.SpellId, trainerId); + allReqValid = false; + } + } - if (!trainerSpell.ReqLevel) - trainerSpell.ReqLevel = spellinfo->SpellLevel; + if (!allReqValid) + continue; - // calculate learned spell for profession case when stored cast-spell - trainerSpell.ReqAbility[0] = SpellID; - for (SpellEffectInfo const* effect : spellinfo->GetEffectsForDifficulty(DIFFICULTY_NONE)) - { - if (!effect || effect->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 (effect->TargetA.GetTarget() != 0 && effect->TargetA.GetTarget() != TARGET_UNIT_TARGET_ALLY - && effect->TargetA.GetTarget() != TARGET_UNIT_TARGET_ANY && effect->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; - } + for (SpellEffectInfo const* spellEffect : spellInfo->GetEffectsForDifficulty(DIFFICULTY_NONE)) + { + if (spellEffect->IsEffect(SPELL_EFFECT_LEARN_SPELL)) + { + spell.CastSpellId = spell.SpellId; + spell.SpellId = spellEffect->TriggerSpell; + break; + } + } - trainerSpell.ReqAbility[effect->EffectIndex] = effect->TriggerSpell; + spellsByTrainer[trainerId].push_back(spell); - if (trainerSpell.ReqAbility[effect->EffectIndex]) - { - SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainerSpell.ReqAbility[effect->EffectIndex]); - if (learnedSpellInfo && learnedSpellInfo->IsProfession()) - data.trainerType = 2; - } + } while (trainerSpellsResult->NextRow()); } - return; -} - -void ObjectMgr::LoadTrainerSpell() -{ - uint32 oldMSTime = getMSTime(); + if (QueryResult trainersResult = WorldDatabase.Query("SELECT Id, Type, Greeting FROM trainer")) + { + do + { + Field* fields = trainersResult->Fetch(); + uint32 trainerId = fields[0].GetUInt32(); + Trainer::Type trainerType = Trainer::Type(fields[1].GetUInt8()); + std::string greeting = fields[2].GetString(); + std::vector<Trainer::Spell> spells; + auto spellsItr = spellsByTrainer.find(trainerId); + if (spellsItr != spellsByTrainer.end()) + { + spells = std::move(spellsItr->second); + spellsByTrainer.erase(spellsItr); + } - // For reload case - _cacheTrainerSpellStore.clear(); + _trainers.emplace(std::piecewise_construct, std::forward_as_tuple(trainerId), std::forward_as_tuple(trainerId, trainerType, std::move(greeting), std::move(spells))); - 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"); + } while (trainersResult->NextRow()); + } - if (!result) + for (auto const& unusedSpells : spellsByTrainer) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 Trainers. DB table `npc_trainer` is empty!"); - - return; + 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); + } } - uint32 count = 0; - - do + if (QueryResult trainerLocalesResult = WorldDatabase.Query("SELECT Id, locale, Greeting_lang FROM trainer_locale")) { - Field* fields = result->Fetch(); + do + { + Field* fields = trainerLocalesResult->Fetch(); + uint32 trainerId = fields[0].GetUInt32(); + std::string localeName = fields[1].GetString(); - 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(); + LocaleConstant locale = GetLocaleByName(localeName); + if (locale == LOCALE_enUS) + continue; - AddSpellToTrainer(ID, SpellID, MoneyCost, ReqSkillLine, ReqSkillRank, ReqLevel, Index); + 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()); - ++count; + } while (trainerLocalesResult->NextRow()); } - while (result->NextRow()); - TC_LOG_INFO("server.loading", ">> Loaded %d Trainers in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); + TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " Trainers in %u ms", _trainers.size(), GetMSTimeDiffToNow(oldMSTime)); } int ObjectMgr::LoadReferenceVendor(int32 vendor, int32 item, uint8 referenceType, std::set<uint32> *skip_vendors) @@ -8748,9 +8732,19 @@ void ObjectMgr::LoadGossipMenuItems() _gossipMenuItemsStore.clear(); QueryResult result = WorldDatabase.Query( - // 0 1 2 3 4 5 6 7 8 9 10 11 12 - "SELECT menu_id, id, option_icon, option_text, OptionBroadcastTextID, option_id, npc_option_npcflag, action_menu_id, action_poi_id, box_coded, box_money, box_text, BoxBroadcastTextID " - "FROM gossip_menu_option ORDER BY menu_id, id"); + // 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) { @@ -8766,12 +8760,12 @@ void ObjectMgr::LoadGossipMenuItems() GossipMenuItems gMenuItem; - gMenuItem.MenuId = fields[0].GetUInt16(); - gMenuItem.OptionIndex = fields[1].GetUInt16(); - gMenuItem.OptionIcon = fields[2].GetUInt32(); + gMenuItem.MenuId = fields[0].GetUInt32(); + gMenuItem.OptionIndex = 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].GetUInt64(); gMenuItem.ActionMenuId = fields[7].GetUInt32(); gMenuItem.ActionPoiId = fields[8].GetUInt32(); @@ -8779,10 +8773,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.OptionIndex, 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.OptionIndex, gMenuItem.OptionIcon); gMenuItem.OptionIcon = GOSSIP_ICON_CHAT; } @@ -8790,17 +8785,17 @@ void ObjectMgr::LoadGossipMenuItems() { if (!sBroadcastTextStore.LookupEntry(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.OptionIndex, 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.OptionIndex, 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.OptionIndex, 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.OptionIndex, 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 action_poi_id %u, ignoring", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.ActionPoiId); + TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for MenuId %u, OptionIndex %u use non-existing action_poi_id %u, ignoring", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.ActionPoiId); gMenuItem.ActionPoiId = 0; } @@ -8808,11 +8803,17 @@ void ObjectMgr::LoadGossipMenuItems() { if (!sBroadcastTextStore.LookupEntry(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.OptionIndex, 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.OptionIndex, 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.OptionIndex, gMenuItem.TrainerId); + gMenuItem.TrainerId = 0; + } + _gossipMenuItemsStore.insert(GossipMenuItemsContainer::value_type(gMenuItem.MenuId, gMenuItem)); ++count; } @@ -8821,6 +8822,11 @@ void ObjectMgr::LoadGossipMenuItems() TC_LOG_INFO("server.loading", ">> Loaded %u gossip_menu_option entries in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } +Trainer::Trainer const* ObjectMgr::GetTrainer(uint32 trainerId) const +{ + return Trinity::Containers::MapGetValuePtr(_trainers, trainerId); +} + 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 18bfaec87cb..678d0df64e6 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -31,6 +31,7 @@ #include "Position.h" #include "QuestDef.h" #include "SharedDefines.h" +#include "Trainer.h" #include "VehicleDefines.h" #include <map> #include <unordered_map> @@ -662,6 +663,7 @@ struct GossipMenuItems uint32 BoxMoney; std::string BoxText; uint32 BoxBroadcastTextId; + uint32 TrainerId; ConditionContainer Conditions; }; @@ -737,7 +739,6 @@ typedef std::pair<GraveYardContainer::const_iterator, GraveYardContainer::const_ typedef std::pair<GraveYardContainer::iterator, GraveYardContainer::iterator> GraveYardMapBoundsNonConst; typedef std::unordered_map<uint32, VendorItemData> CacheVendorItemContainer; -typedef std::unordered_map<uint32, TrainerSpellData> CacheTrainerSpellContainer; typedef std::unordered_map<uint32, std::string> RealmNameContainer; @@ -1163,8 +1164,7 @@ class TC_GAME_API ObjectMgr void LoadGossipMenuItems(); void LoadVendors(); - void LoadTrainerSpell(); - void AddSpellToTrainer(uint32 entry, uint32 spell, uint32 spellCost, uint32 reqSkill, uint32 reqSkillValue, uint32 reqLevel, uint32 Index); + void LoadTrainers(); void LoadTerrainPhaseInfo(); void LoadTerrainSwapDefaults(); @@ -1377,14 +1377,7 @@ 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; VendorItemData const* GetNpcVendorItemList(uint32 entry) const { @@ -1664,7 +1657,7 @@ class TC_GAME_API ObjectMgr TrinityStringContainer _trinityStringStore; CacheVendorItemContainer _cacheVendorItemStore; - CacheTrainerSpellContainer _cacheTrainerSpellStore; + std::unordered_map<uint32, Trainer::Trainer> _trainers; std::set<uint32> _difficultyEntries[MAX_CREATURE_DIFFICULTIES]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate std::set<uint32> _hasDifficultyEntries[MAX_CREATURE_DIFFICULTIES]; // already loaded creatures with difficulty 1 values, used in CheckCreatureTemplate diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h index 2de7397ac14..0e5c2350736 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -119,18 +119,18 @@ namespace Trinity struct TC_GAME_API MessageDistDeliverer { - WorldObject* i_source; + WorldObject const* i_source; WorldPacket const* i_message; float i_distSq; uint32 team; Player const* skipped_receiver; - MessageDistDeliverer(WorldObject* src, WorldPacket const* msg, float dist, bool own_team_only = false, Player const* skipped = NULL) + MessageDistDeliverer(WorldObject const* src, WorldPacket const* 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/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index 82f7336f0e4..57ad947e1ef 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -44,6 +44,7 @@ #include "ScriptMgr.h" #include "SpellInfo.h" #include "SpellMgr.h" +#include "Trainer.h" #include "WorldPacket.h" enum StableResultCode @@ -89,16 +90,10 @@ void WorldSession::SendShowMailBox(ObjectGuid guid) void WorldSession::HandleTrainerListOpcode(WorldPackets::NPC::Hello& packet) { - SendTrainerList(packet.Unit); + TC_LOG_INFO("network", "%s sent legacy gossipless trainer hello for unit %s, no trainer list available", GetPlayerInfo().c_str(), packet.Unit.ToString().c_str()); } -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) +void WorldSession::SendTrainerList(ObjectGuid guid, uint32 trainerId) { Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_TRAINER); if (!unit) @@ -111,92 +106,23 @@ void WorldSession::SendTrainerList(ObjectGuid guid, const std::string& strTitle, if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - TrainerSpellData const* trainer_spells = unit->GetTrainerSpells(); - if (!trainer_spells) + Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(trainerId); + if (!trainer) { - TC_LOG_DEBUG("network", "WORLD: SendTrainerList - Training spells not found for %s", guid.ToString().c_str()); + TC_LOG_DEBUG("network", "WORLD: SendTrainerList - trainer spells not found for trainer %s id %d", guid.ToString().c_str(), trainerId); return; } - WorldPackets::NPC::TrainerList packet; - packet.TrainerGUID = guid; - packet.TrainerType = trainer_spells->trainerType; - packet.Greeting = strTitle; - - // reputation discount - float fDiscountMod = _player->GetReputationPriceDiscount(unit); - - packet.Spells.reserve(trainer_spells->spellList.size()); - 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; - for (uint8 i = 0; i < MAX_TRAINERSPELL_ABILITY_REQS; ++i) - { - if (!tSpell->ReqAbility[i]) - continue; - if (!_player->IsSpellFitByClassAndRace(tSpell->ReqAbility[i])) - { - valid = false; - break; - } - } - - if (!valid) - continue; - - TrainerSpellState state = _player->GetTrainerSpellState(tSpell); - - WorldPackets::NPC::TrainerListSpell spell; - spell.SpellID = tSpell->SpellID; - spell.MoneyCost = floor(tSpell->MoneyCost * fDiscountMod); - spell.ReqSkillLine = tSpell->ReqSkillLine; - spell.ReqSkillRank = tSpell->ReqSkillRank; - spell.ReqLevel = tSpell->ReqLevel; - spell.Usable = (state == TRAINER_SPELL_GREEN_DISABLED ? TRAINER_SPELL_GREEN : state); - - uint8 maxReq = 0; - for (uint8 i = 0; i < MAX_TRAINERSPELL_ABILITY_REQS; ++i) - { - if (!tSpell->ReqAbility[i]) - continue; - - if (uint32 prevSpellId = sSpellMgr->GetPrevSpellInChain(tSpell->ReqAbility[i])) - { - spell.ReqAbility[maxReq] = prevSpellId; - ++maxReq; - } - - if (maxReq == 2) - break; - - SpellsRequiringSpellMapBounds spellsRequired = sSpellMgr->GetSpellsRequiredForSpellBounds(tSpell->ReqAbility[i]); - for (SpellsRequiringSpellMap::const_iterator itr2 = spellsRequired.first; itr2 != spellsRequired.second && maxReq < MAX_TRAINERSPELL_ABILITY_REQS; ++itr2) - { - spell.ReqAbility[maxReq] = itr2->second; - ++maxReq; - } - - if (maxReq == 2) - break; - } - - packet.Spells.push_back(spell); - } - - SendPacket(packet.Write()); + _player->SetCurrentTrainerId(trainerId); + trainer->SendSpells(unit, _player, GetSessionDbLocaleIndex()); } void WorldSession::HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet) { TC_LOG_DEBUG("network", "WORLD: Received CMSG_TRAINER_BUY_SPELL %s, learn spell id is: %i", packet.TrainerGUID.ToString().c_str(), packet.SpellID); - Creature* trainer = GetPlayer()->GetNPCIfCanInteractWith(packet.TrainerGUID, UNIT_NPC_FLAG_TRAINER); - if (!trainer) + 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; @@ -206,70 +132,14 @@ void WorldSession::HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpel 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(packet.TrainerGUID, packet.SpellID, 0); + if (_player->GetCurrentTrainerId() != packet.TrainerID) return; - } - // not found, cheat? - TrainerSpell const* trainerSpell = trainer_spells->Find(packet.SpellID); - if (!trainerSpell) - { - SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 0); + Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(packet.TrainerID); + if (!npc) return; - } - // can't be learn, cheat? Or double learn with lags... - if (_player->GetTrainerSpellState(trainerSpell) != TRAINER_SPELL_GREEN) - { - SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 0); - return; - } - - // apply reputation discount - uint32 nSpellCost = uint32(floor(trainerSpell->MoneyCost * _player->GetReputationPriceDiscount(trainer))); - - // check money requirement - if (!_player->HasEnoughMoney(uint64(nSpellCost))) - { - SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 1); - return; - } - - _player->ModifyMoney(-int64(nSpellCost)); - - trainer->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(packet.SpellID, false); -} - -void WorldSession::SendTrainerBuyFailed(ObjectGuid trainerGUID, uint32 spellID, int32 trainerFailedReason) -{ - WorldPackets::NPC::TrainerBuyFailed trainerBuyFailed; - trainerBuyFailed.TrainerGUID = trainerGUID; - trainerBuyFailed.SpellID = spellID; // should be same as in packet from client - trainerBuyFailed.TrainerFailedReason = trainerFailedReason; // 1 == "Not enough money for trainer service." 0 == "Trainer service %d unavailable." - SendPacket(trainerBuyFailed.Write()); + trainer->TeachSpell(npc, _player, packet.SpellID); } void WorldSession::HandleGossipHelloOpcode(WorldPackets::NPC::Hello& packet) diff --git a/src/server/game/Handlers/SkillHandler.cpp b/src/server/game/Handlers/SkillHandler.cpp index b3dc78e3484..cbc49c50c22 100644 --- a/src/server/game/Handlers/SkillHandler.cpp +++ b/src/server/game/Handlers/SkillHandler.cpp @@ -19,6 +19,7 @@ #include "WorldSession.h" #include "Common.h" #include "DB2Stores.h" +#include "GossipDef.h" #include "Log.h" #include "ObjectAccessor.h" #include "Pet.h" @@ -65,7 +66,10 @@ void WorldSession::HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRe return; } - if (!unit->isCanTrainingAndResetTalentsOf(_player)) + if (!unit->CanResetTalents(_player)) + return; + + if (!_player->PlayerTalkClass->GetGossipMenu().HasMenuItemType(GOSSIP_OPTION_UNLEARNTALENTS)) return; // remove fake death diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 4724f8fd1f2..fd7acfb72db 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -164,7 +164,7 @@ enum Races #define RACEMASK_HORDE RACEMASK_ALL_PLAYABLE & ~RACEMASK_ALLIANCE // Class value is index in ChrClasses.dbc -enum Classes +enum Classes : uint8 { CLASS_NONE = 0, CLASS_WARRIOR = 1, @@ -3801,13 +3801,8 @@ enum LockType enum TrainerType { 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 (6.0.2.18988) enum CreatureType { diff --git a/src/server/game/Server/Packets/NPCPackets.h b/src/server/game/Server/Packets/NPCPackets.h index d0d8a42beeb..f5affe0f484 100644 --- a/src/server/game/Server/Packets/NPCPackets.h +++ b/src/server/game/Server/Packets/NPCPackets.h @@ -145,11 +145,11 @@ namespace WorldPackets WorldPacket const* Write() override; - std::string Greeting; - int32 TrainerType = 0; ObjectGuid TrainerGUID; + int32 TrainerType = 0; int32 TrainerID = 1; std::vector<TrainerListSpell> Spells; + std::string Greeting; }; class ShowBank final : public ServerPacket diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index ae51478180e..6899bf97b95 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -971,8 +971,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(ObjectGuid guid, uint32 trainerId); void SendListInventory(ObjectGuid guid); void SendShowBank(ObjectGuid guid); bool CanOpenMailBox(ObjectGuid guid); @@ -1342,7 +1341,6 @@ class TC_GAME_API WorldSession void HandleStableRevivePet(WorldPacket& recvPacket); void HandleStableSwapPet(WorldPacket& recvPacket); void HandleStableSwapPetCallback(uint32 petId, PreparedQueryResult result); - void SendTrainerBuyFailed(ObjectGuid trainerGUID, uint32 spellID, int32 trainerFailedReason); void HandleCanDuel(WorldPackets::Duel::CanDuel& packet); void HandleDuelResponseOpcode(WorldPackets::Duel::DuelResponse& duelResponse); diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 3b8f118a827..5258e1532f0 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -607,9 +607,9 @@ uint32 SpellMgr::GetSpellWithRank(uint32 spell_id, uint32 rank, bool strict) con return spell_id; } -SpellRequiredMapBounds SpellMgr::GetSpellsRequiredForSpellBounds(uint32 spell_id) const +Trinity::IteratorPair<SpellRequiredMap::const_iterator> 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 2d8b4326454..c3d493357c1 100644 --- a/src/server/game/Spells/SpellMgr.h +++ b/src/server/game/Spells/SpellMgr.h @@ -23,6 +23,7 @@ #include "Define.h" #include "Duration.h" +#include "IteratorPair.h" #include "SharedDefines.h" #include "Util.h" @@ -664,7 +665,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<SpellRequiredMap::const_iterator> 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 7d57d7de22c..78212696331 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1937,17 +1937,17 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading GameTeleports..."); sObjectMgr->LoadGameTele(); + TC_LOG_INFO("server.loading", "Loading Trainers..."); + sObjectMgr->LoadTrainers(); // must be after load CreatureTemplate + TC_LOG_INFO("server.loading", "Loading Gossip menu..."); sObjectMgr->LoadGossipMenu(); TC_LOG_INFO("server.loading", "Loading Gossip menu options..."); - sObjectMgr->LoadGossipMenuItems(); + sObjectMgr->LoadGossipMenuItems(); // must be after LoadTrainers 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 + sObjectMgr->LoadVendors(); // must be after load CreatureTemplate and ItemTemplate 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 48a7106fc2d..fa494b6d709 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, "" }, @@ -157,6 +156,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, "" }, { "support", rbac::RBAC_PERM_COMMAND_RELOAD_SUPPORT_SYSTEM, true, &HandleReloadSupportSystemCommand, "" }, + { "trainer", rbac::RBAC_PERM_COMMAND_RELOAD_TRAINER, true, &HandleReloadTrainerCommand, "" }, { "trinity_string", rbac::RBAC_PERM_COMMAND_RELOAD_TRINITY_STRING, true, &HandleReloadTrinityStringCommand, "" }, { "warden_action", rbac::RBAC_PERM_COMMAND_RELOAD_WARDEN_ACTION, true, &HandleReloadWardenactionCommand, "" }, { "waypoint_scripts", rbac::RBAC_PERM_COMMAND_RELOAD_WAYPOINT_SCRIPTS, true, &HandleReloadWpScriptsCommand, "" }, @@ -242,7 +242,7 @@ public: static bool HandleReloadAllNpcCommand(ChatHandler* handler, const char* 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"); @@ -691,11 +691,13 @@ public: return true; } - static bool HandleReloadNpcTrainerCommand(ChatHandler* handler, const char* /*args*/) + static bool HandleReloadTrainerCommand(ChatHandler* handler, const char* /*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(); + handler->SendGlobalGMSysMessage("DB table `trainer` reloaded."); + handler->SendGlobalGMSysMessage("DB table `trainer_locale` reloaded."); + handler->SendGlobalGMSysMessage("DB table `trainer_spell` reloaded."); return true; } |