diff options
Diffstat (limited to 'src')
29 files changed, 790 insertions, 537 deletions
diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index 085b2a5f283..5cc0c92d7aa 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -78,7 +78,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, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, trainer_type, trainer_spell, trainer_class, trainer_race, type, type_flags, 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, ManaModifier, 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, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, 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, ManaModifier, 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 cbf0dec3f64..c9dfc573e1e 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 cf0cb59a91a..da4bc617fea 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -72,15 +72,6 @@ std::string CreatureMovementData::ToString() const VendorItemCount::VendorItemCount(uint32 _item, uint32 _count) : itemId(_item), count(_count), lastIncrementTime(GameTime::GetGameTime()) { } -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; @@ -1191,11 +1182,13 @@ 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() >= 10 - && GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS - && player->getClass() == GetCreatureTemplate()->trainer_class; + Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(GetEntry()); + if (!trainer) + return false; + + return player->getLevel() >= 10 && trainer->IsTrainerValidForPlayer(player); } Player* Creature::GetLootRecipient() const @@ -2706,11 +2699,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 05e2e32c6ef..48d3a89012d 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -118,7 +118,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; void LoadTemplateImmunities(); bool IsImmunedToSpell(SpellInfo const* spellInfo, WorldObject const* caster) const override; @@ -177,8 +177,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 29100324590..86f8f211d32 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -151,10 +151,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_spell; - uint32 trainer_class; - uint32 trainer_race; uint32 type; // enum CreatureType values uint32 type_flags; // enum CreatureTypeFlags mask values uint32 lootid; @@ -358,36 +354,4 @@ struct VendorItemData } }; -struct TrainerSpell -{ - TrainerSpell() : SpellID(0), MoneyCost(0), ReqSkillLine(0), ReqSkillRank(0), ReqLevel(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]; - - // 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/Trainer.cpp b/src/server/game/Entities/Creature/Trainer.cpp new file mode 100644 index 00000000000..79e496ce3bc --- /dev/null +++ b/src/server/game/Entities/Creature/Trainer.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2008-2018 TrinityCore <https://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 +{ + 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<Spell> 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); + + WorldPackets::NPC::TrainerList trainerList; + trainerList.TrainerGUID = npc->GetGUID(); + trainerList.TrainerType = AsUnderlyingType(_type); + trainerList.Greeting = GetGreeting(locale); + trainerList.Spells.reserve(_spells.size()); + for (Spell const& trainerSpell : _spells) + { + if (!player->IsSpellFitByClassAndRace(trainerSpell.SpellId)) + continue; + + bool primaryProfessionFirstRank = false; + for (int32 reqAbility : trainerSpell.ReqAbility) + { + SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(reqAbility); + 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.ProfessionDialog = (primaryProfessionFirstRank && (player->GetFreePrimaryProfessionPoints() > 0) ? 1 : 0); + trainerListSpell.ProfessionButton = (primaryProfessionFirstRank ? 1 : 0); + trainerListSpell.ReqLevel = trainerSpell.ReqLevel; + trainerListSpell.ReqSkillLine = trainerSpell.ReqSkillLine; + trainerListSpell.ReqSkillRank = trainerSpell.ReqSkillRank; + 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->HasEnoughMoney(moneyCost)) + { + SendTeachFailure(npc, player, spellId, FailReason::NotEnoughMoney); + return; + } + + player->ModifyMoney(-moneyCost); + + npc->SendPlaySpellVisual(179); + npc->SendPlaySpellImpact(player->GetGUID(), 362); + + // 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 + { + // check class for class trainers + if (player->getClass() != GetTrainerRequirement() && (GetTrainerType() == Type::Class || GetTrainerType() == Type::Pet)) + return false; + + // check race for mount trainers + if (player->getRace() != GetTrainerRequirement() && GetTrainerType() == Type::Mount) + return false; + + // check spell for profession trainers + if (!player->HasSpell(GetTrainerRequirement()) && GetTrainerRequirement() != 0 && GetTrainerType() == Type::Tradeskill) + return false; + + 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..852b3e613be --- /dev/null +++ b/src/server/game/Entities/Creature/Trainer.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2008-2018 TrinityCore <https://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 <vector> + +class Creature; +class ObjectMgr; +class Player; + +namespace Trainer +{ + enum class Type : uint32 + { + Class = 0, + Mount = 1, + Tradeskill = 2, + Pet = 3 + }; + + enum class SpellState : uint8 + { + Available = 0, + Unavailable = 1, + Known = 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<uint32, 3> ReqAbility = { }; + uint8 ReqLevel = 0; + + bool IsCastable() const; + }; + + class Trainer + { + public: + Trainer(uint32 trainerId, Type type, uint32 requirement, 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; + + 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<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 a9a9c78c0bb..3e6a080ac5b 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1741,19 +1741,19 @@ void Unit::BuildHeartBeatMsg(WorldPacket* data) const BuildMovementPacket(data); } -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 76824c2719e..a40765d1f0d 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -344,9 +344,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 7de2017eb4b..3068b969bbd 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -85,6 +85,7 @@ #include "SpellMgr.h" #include "TicketMgr.h" #include "TradeData.h" +#include "Trainer.h" #include "Transport.h" #include "UpdateData.h" #include "UpdateFieldFlags.h" @@ -3962,74 +3963,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 * @@ -6331,7 +6264,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) SendDirectMessage(data); @@ -6340,7 +6273,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) SendDirectMessage(data); @@ -6349,7 +6282,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) SendDirectMessage(data); @@ -14116,15 +14049,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) && !(getLevel() < sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL)))) canTalk = false; break; case GOSSIP_OPTION_UNLEARNTALENTS: - if (!creature->isCanTrainingAndResetTalentsOf(this)) + if (!creature->CanResetTalents(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 (!GetPet() || GetPet()->getPetType() != HUNTER_PET || GetPet()->m_spells.size() <= 1 || !creature->CanResetTalents(this)) canTalk = false; break; case GOSSIP_OPTION_TAXIVENDOR: @@ -14143,13 +14076,16 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool canTalk = false; break; case GOSSIP_OPTION_TRAINER: - if (getClass() != creature->GetCreatureTemplate()->trainer_class && creature->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS) + { + Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(creature->GetEntry()); + if (!trainer || !trainer->IsTrainerValidForPlayer(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); + TC_LOG_ERROR("sql.sql", "GOSSIP_OPTION_TRAINER:: Player %s (GUID: %u) requested wrong gossip menu: %u at Creature: %s (Entry: %u)", + GetName().c_str(), GetGUID().GetCounter(), menu->GetGossipMenu().GetMenuId(), creature->GetName().c_str(), creature->GetEntry()); canTalk = false; } // no break; + } case GOSSIP_OPTION_GOSSIP: case GOSSIP_OPTION_SPIRITGUIDE: case GOSSIP_OPTION_INNKEEPER: @@ -14329,7 +14265,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()); break; case GOSSIP_OPTION_LEARNDUALSPEC: if (GetSpecsCount() == 1 && getLevel() >= sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL)) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 3577eea21e4..fc5db7a3726 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -155,14 +155,6 @@ typedef std::unordered_set<SpellModifier*> SpellModContainer; typedef std::unordered_map<uint32 /*instanceId*/, time_t/*releaseTime*/> InstanceTimeMap; -enum TrainerSpellState -{ - TRAINER_SPELL_GREEN = 0, - TRAINER_SPELL_RED = 1, - TRAINER_SPELL_GRAY = 2, - TRAINER_SPELL_GREEN_DISABLED = 10 // custom value, not send to client: formally green but learn not allowed -}; - enum ActionButtonUpdateState { ACTIONBUTTON_UNCHANGED = 0, @@ -1401,7 +1393,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> 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); @@ -1661,10 +1652,10 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidData> const& liquidData) override; void AtExitCombat() 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); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 0acd39162c8..d02bc63073d 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -11785,7 +11785,7 @@ void Unit::SetAuraStack(uint32 spellId, Unit* target, uint32 stack) aura->SetStackAmount(stack); } -void Unit::SendPlaySpellVisual(uint32 id) +void Unit::SendPlaySpellVisual(uint32 id) const { WorldPacket data(SMSG_PLAY_SPELL_VISUAL, 8 + 4); data << uint64(GetGUID()); @@ -11793,7 +11793,7 @@ void Unit::SendPlaySpellVisual(uint32 id) SendMessageToSet(&data, true); } -void Unit::SendPlaySpellImpact(ObjectGuid guid, uint32 id) +void Unit::SendPlaySpellImpact(ObjectGuid guid, uint32 id) const { WorldPacket data(SMSG_PLAY_SPELL_IMPACT, 8 + 4); data << uint64(guid); // target diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index c588ef426f5..0a4445d5fb7 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1081,8 +1081,8 @@ 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 SendPlaySpellVisual(uint32 id); - void SendPlaySpellImpact(ObjectGuid guid, uint32 id); + void SendPlaySpellVisual(uint32 id) const; + void SendPlaySpellImpact(ObjectGuid guid, uint32 id) const; void DeMorph(); diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 80f823f9beb..ff0bc2938c0 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -367,15 +367,13 @@ void ObjectMgr::LoadCreatureTemplates() "modelid4, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, " // 21 22 23 24 25 26 27 28 29 30 "scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, " - // 31 32 33 34 35 36 37 - "dynamicflags, family, trainer_type, trainer_spell, trainer_class, trainer_race, type, " - // 38 39 40 41 42 43 44 45 46 47 48 - "type_flags, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, " - // 49 50 51 52 53 54 55 56 57 58 59 60 61 - "spell2, spell3, spell4, spell5, spell6, spell7, spell8, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, " - // 62 63 64 65 66 67 68 69 70 + // 31 32 33 34 35 36 37 38 39 40 41 42 43 + "dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, " + // 44 45 46 47 48 49 50 51 52 53 54 55 56 57 + "spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, " + // 58 59 60 61 62 63 64 65 66 "ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, DamageModifier, " - // 71 72 73 74 75 76 77 78 + // 67 68 69 70 71 72 73 74 "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"); @@ -439,54 +437,50 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields) creatureTemplate.unit_flags2 = fields[30].GetUInt32(); creatureTemplate.dynamicflags = fields[31].GetUInt32(); creatureTemplate.family = CreatureFamily(fields[32].GetUInt8()); - creatureTemplate.trainer_type = fields[33].GetUInt8(); - creatureTemplate.trainer_spell = fields[34].GetUInt32(); - creatureTemplate.trainer_class = fields[35].GetUInt8(); - creatureTemplate.trainer_race = fields[36].GetUInt8(); - creatureTemplate.type = fields[37].GetUInt8(); - creatureTemplate.type_flags = fields[38].GetUInt32(); - creatureTemplate.lootid = fields[39].GetUInt32(); - creatureTemplate.pickpocketLootId = fields[40].GetUInt32(); - creatureTemplate.SkinLootId = fields[41].GetUInt32(); + creatureTemplate.type = fields[33].GetUInt8(); + creatureTemplate.type_flags = fields[34].GetUInt32(); + creatureTemplate.lootid = fields[35].GetUInt32(); + creatureTemplate.pickpocketLootId = fields[36].GetUInt32(); + creatureTemplate.SkinLootId = fields[37].GetUInt32(); for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) - creatureTemplate.resistance[i] = fields[42 + i - 1].GetInt16(); + creatureTemplate.resistance[i] = fields[38 + i - 1].GetInt16(); for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i) - creatureTemplate.spells[i] = fields[48 + i].GetUInt32(); - - creatureTemplate.PetSpellDataId = fields[56].GetUInt32(); - creatureTemplate.VehicleId = fields[57].GetUInt32(); - creatureTemplate.mingold = fields[58].GetUInt32(); - creatureTemplate.maxgold = fields[59].GetUInt32(); - creatureTemplate.AIName = fields[60].GetString(); - creatureTemplate.MovementType = fields[61].GetUInt8(); - if (!fields[62].IsNull()) - creatureTemplate.Movement.Ground = static_cast<CreatureGroundMovementType>(fields[62].GetUInt8()); - - if (!fields[63].IsNull()) - creatureTemplate.Movement.Swim = fields[63].GetBool(); - - if (!fields[64].IsNull()) - creatureTemplate.Movement.Flight = static_cast<CreatureFlightMovementType>(fields[64].GetUInt8()); - - if (!fields[65].IsNull()) - creatureTemplate.Movement.Rooted = fields[65].GetBool(); - - creatureTemplate.HoverHeight = fields[66].GetFloat(); - creatureTemplate.ModHealth = fields[67].GetFloat(); - creatureTemplate.ModMana = 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.SpellSchoolImmuneMask = fields[76].GetUInt32(); - creatureTemplate.flags_extra = fields[77].GetUInt32(); - creatureTemplate.ScriptID = GetScriptId(fields[78].GetString()); + creatureTemplate.spells[i] = fields[44 + i].GetUInt32(); + + creatureTemplate.PetSpellDataId = fields[52].GetUInt32(); + creatureTemplate.VehicleId = fields[53].GetUInt32(); + creatureTemplate.mingold = fields[54].GetUInt32(); + creatureTemplate.maxgold = fields[55].GetUInt32(); + creatureTemplate.AIName = fields[56].GetString(); + creatureTemplate.MovementType = fields[57].GetUInt8(); + if (!fields[58].IsNull()) + creatureTemplate.Movement.Ground = static_cast<CreatureGroundMovementType>(fields[58].GetUInt8()); + + if (!fields[59].IsNull()) + creatureTemplate.Movement.Swim = fields[59].GetBool(); + + if (!fields[60].IsNull()) + creatureTemplate.Movement.Flight = static_cast<CreatureFlightMovementType>(fields[60].GetUInt8()); + + if (!fields[61].IsNull()) + creatureTemplate.Movement.Rooted = fields[61].GetBool(); + + creatureTemplate.HoverHeight = fields[62].GetFloat(); + creatureTemplate.ModHealth = fields[63].GetFloat(); + creatureTemplate.ModMana = fields[64].GetFloat(); + creatureTemplate.ModArmor = fields[65].GetFloat(); + creatureTemplate.ModDamage = fields[66].GetFloat(); + creatureTemplate.ModExperience = fields[67].GetFloat(); + creatureTemplate.RacialLeader = fields[68].GetBool(); + + creatureTemplate.movementId = fields[69].GetUInt32(); + creatureTemplate.RegenHealth = fields[70].GetBool(); + creatureTemplate.MechanicImmuneMask = fields[71].GetUInt32(); + creatureTemplate.SpellSchoolImmuneMask = fields[72].GetUInt32(); + creatureTemplate.flags_extra = fields[73].GetUInt32(); + creatureTemplate.ScriptID = GetScriptId(fields[74].GetString()); } void ObjectMgr::LoadCreatureTemplateAddons() @@ -687,39 +681,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->trainer_spell != difficultyInfo->trainer_spell) - { - TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has different `trainer_spell` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, 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).", @@ -912,9 +873,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); @@ -8702,122 +8660,149 @@ 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) +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 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 SpellID (%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 SpellID (%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; + } - if (GetTalentSpellCost(SpellID)) - { - TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains an ID (%u) for a non-existing SpellID (%u) which is a talent, ignoring", ID, SpellID); - return; - } + 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; + } - 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; + 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; - if (!trainerSpell.ReqLevel) - trainerSpell.ReqLevel = spellinfo->SpellLevel; + spellsByTrainer[trainerId].push_back(spell); + } while (trainerSpellsResult->NextRow()); + } - // calculate learned spell for profession case when stored cast-spell - trainerSpell.ReqAbility[0] = SpellID; - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + if (QueryResult trainersResult = WorldDatabase.Query("SELECT Id, Type, Requirement, Greeting FROM trainer")) { - 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) + do { - 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; - } + Field* fields = trainersResult->Fetch(); + + 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<Trainer::Spell> spells; + auto spellsItr = spellsByTrainer.find(trainerId); + if (spellsItr != spellsByTrainer.end()) + { + spells = std::move(spellsItr->second); + spellsByTrainer.erase(spellsItr); + } - trainerSpell.ReqAbility[i] = spellinfo->Effects[i].TriggerSpell; + _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()); + } - if (trainerSpell.ReqAbility[i]) + for (auto const& unusedSpells : spellsByTrainer) + { + for (Trainer::Spell const& unusedSpell : unusedSpells.second) { - SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainerSpell.ReqAbility[i]); - if (learnedSpellInfo && learnedSpellInfo->IsProfession()) - data.trainerType = 2; + TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing trainer (TrainerId: %u) for SpellId %u, ignoring", unusedSpells.first, unusedSpell.SpellId); } } - return; -} - -void ObjectMgr::LoadTrainerSpell() -{ - uint32 oldMSTime = getMSTime(); + 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(); - // For reload case - _cacheTrainerSpellStore.clear(); + LocaleConstant locale = GetLocaleByName(localeName); + if (locale == LOCALE_enUS) + continue; - QueryResult result = WorldDatabase.Query("SELECT b.ID, a.SpellID, a.MoneyCost, a.ReqSkillLine, a.ReqSkillRank, a.ReqLevel 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 (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()); + } - if (!result) - { - TC_LOG_ERROR("sql.sql", ">> Loaded 0 Trainers. DB table `npc_trainer` is empty!"); + TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " Trainers in %u ms", _trainers.size(), GetMSTimeDiffToNow(oldMSTime)); +} - return; - } +void ObjectMgr::LoadCreatureDefaultTrainers() +{ + uint32 oldMSTime = getMSTime(); - uint32 count = 0; + _creatureDefaultTrainers.clear(); - do + if (QueryResult result = WorldDatabase.Query("SELECT CreatureId, TrainerId FROM creature_default_trainer")) { - Field* fields = result->Fetch(); + do + { + Field* fields = result->Fetch(); + uint32 creatureId = fields[0].GetUInt32(); + uint32 trainerId = fields[1].GetUInt32(); - 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(); + if (!GetCreatureTemplate(creatureId)) + { + TC_LOG_ERROR("sql.sql", "Table `creature_default_trainer` references non-existing creature template (CreatureId: %u), ignoring", creatureId); + continue; + } - AddSpellToTrainer(ID, SpellID, MoneyCost, ReqSkillLine, ReqSkillRank, ReqLevel); + _creatureDefaultTrainers[creatureId] = trainerId; - ++count; + } while (result->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 " default trainers in %u ms", _creatureDefaultTrainers.size(), GetMSTimeDiffToNow(oldMSTime)); } uint32 ObjectMgr::LoadReferenceVendor(int32 vendor, int32 item, std::set<uint32>* skip_vendors) @@ -9022,6 +9007,15 @@ void ObjectMgr::LoadGossipMenuItems() 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 creatureId) const +{ + auto itr = _creatureDefaultTrainers.find(creatureId); + if (itr != _creatureDefaultTrainers.end()) + return Trinity::Containers::MapGetValuePtr(_trainers, itr->second); + + return nullptr; +} + void ObjectMgr::AddVendorItem(uint32 entry, uint32 item, int32 maxcount, uint32 incrtime, uint32 extendedCost, bool persist /*= true*/) { VendorItemData& vList = _cacheVendorItemStore[entry]; diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 7c5fa315d3d..8568e72a601 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 <map> #include <unordered_map> @@ -828,7 +829,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; enum SkillRangeType { @@ -1215,8 +1215,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); + void LoadTrainers(); + void LoadCreatureDefaultTrainers(); void InitializeQueriesData(QueryDataGroup mask); @@ -1456,14 +1456,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 creatureId) const; VendorItemData const* GetNpcVendorItemList(uint32 entry) const { @@ -1685,7 +1678,8 @@ class TC_GAME_API ObjectMgr TrinityStringContainer _trinityStringStore; CacheVendorItemContainer _cacheVendorItemStore; - CacheTrainerSpellContainer _cacheTrainerSpellStore; + std::unordered_map<uint32, Trainer::Trainer> _trainers; + std::unordered_map<uint32, uint32> _creatureDefaultTrainers; std::set<uint32> _difficultyEntries[MAX_DIFFICULTY - 1]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate std::set<uint32> _hasDifficultyEntries[MAX_DIFFICULTY - 1]; // 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 5297d21b104..5e606e67ef1 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -116,19 +116,19 @@ namespace Trinity struct TC_GAME_API MessageDistDeliverer { - WorldObject* i_source; + WorldObject const* i_source; WorldPacket const* i_message; uint32 i_phaseMask; 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 = nullptr) + 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_phaseMask(src->GetPhaseMask()), 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 5b50075d2fd..dbbf3b6fe03 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -29,6 +29,7 @@ #include "Language.h" #include "Log.h" #include "Map.h" +#include "NPCPackets.h" #include "Opcodes.h" #include "ObjectMgr.h" #include "Pet.h" @@ -38,6 +39,7 @@ #include "ScriptMgr.h" #include "SpellInfo.h" #include "SpellMgr.h" +#include "Trainer.h" #include "World.h" #include "WorldPacket.h" @@ -114,135 +116,45 @@ 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) -{ - std::string str = GetTrinityString(LANG_NPC_TAINER_HELLO); - SendTrainerList(guid, str); -} - -void WorldSession::SendTrainerList(ObjectGuid guid, const std::string& strTitle) -{ - Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_TRAINER); - if (!unit) + 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; } + if (sObjectMgr->GetTrainer(npc->GetEntry())) + SendTrainerList(npc); + else + TC_LOG_DEBUG("network", "WorldSession::SendTrainerList - Creature id %u has no trainer data.", npc->GetEntry()); +} + +void WorldSession::SendTrainerList(Creature* npc) +{ // remove fake death 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(npc->GetEntry()); + if (!trainer) { - TC_LOG_DEBUG("network", "WORLD: SendTrainerList - Training spells not found for %s", guid.ToString().c_str()); + TC_LOG_DEBUG("network", "WorldSession: SendTrainerList - trainer spells not found for %s", npc->GetGUID().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); - - size_t count_pos = data.wpos(); - data << uint32(trainer_spells->spellList.size()); - - // reputation discount - float fDiscountMod = _player->GetReputationPriceDiscount(unit); - 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; - - 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 << 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 - 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 == 3) - 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 == 3) - break; - } - while (maxReq < 3) - { - data << uint32(0); - ++maxReq; - } - - ++count; - } - - data << strTitle; - - data.put<uint32>(count_pos, count); - SendPacket(&data); + trainer->SendSpells(npc, _player, GetSessionDbLocaleIndex()); } -void WorldSession::HandleTrainerBuySpellOpcode(WorldPacket& recvData) +void WorldSession::HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet) { - ObjectGuid guid; - uint32 spellId = 0; + TC_LOG_DEBUG("network", "WORLD: Received CMSG_TRAINER_BUY_SPELL %s, learn spell id is: %u", packet.TrainerGUID.ToString().c_str(), packet.SpellID); - recvData >> guid >> 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) + 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.", guid.ToString().c_str()); + TC_LOG_DEBUG("network", "WORLD: HandleTrainerBuySpellOpcode - %s not found or you can not interact with him.", packet.TrainerGUID.ToString().c_str()); return; } @@ -250,54 +162,11 @@ void WorldSession::HandleTrainerBuySpellOpcode(WorldPacket& recvData) 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) - return; - - // not found, cheat? - TrainerSpell const* trainer_spell = trainer_spells->Find(spellId); - if (!trainer_spell) - return; - - // can't be learn, cheat? Or double learn with lags... - if (_player->GetTrainerSpellState(trainer_spell) != TRAINER_SPELL_GREEN) - return; - - // apply reputation discount - uint32 nSpellCost = uint32(floor(trainer_spell->MoneyCost * _player->GetReputationPriceDiscount(trainer))); - - // check money requirement - if (!_player->HasEnoughMoney(nSpellCost)) + Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(npc->GetEntry()); + if (!trainer) return; - _player->ModifyMoney(-int32(nSpellCost)); - - trainer->SendPlaySpellVisual(179); // 53 SpellCastDirected - trainer->SendPlaySpellImpact(_player->GetGUID(), 362); // 113 EmoteSalute - - // learn explicitly or cast explicitly - if (trainer_spell->IsCastable()) - _player->CastSpell(_player, trainer_spell->SpellID, true); - else - _player->LearnSpell(spellId, false); - - WorldPacket data(SMSG_TRAINER_BUY_SUCCEEDED, 12); - data << uint64(guid); - data << uint32(spellId); // should be same as in packet from client - SendPacket(&data); + trainer->TeachSpell(npc, _player, packet.SpellID); } void WorldSession::HandleGossipHelloOpcode(WorldPacket& recvData) diff --git a/src/server/game/Handlers/SkillHandler.cpp b/src/server/game/Handlers/SkillHandler.cpp index 0e9d45e1aca..b254b891a4a 100644 --- a/src/server/game/Handlers/SkillHandler.cpp +++ b/src/server/game/Handlers/SkillHandler.cpp @@ -71,7 +71,7 @@ void WorldSession::HandleTalentWipeConfirmOpcode(WorldPacket& recvData) return; } - if (!unit->isCanTrainingAndResetTalentsOf(_player)) + if (!unit->CanResetTalents(_player)) return; // remove fake death diff --git a/src/server/game/Server/Packets/AllPackets.h b/src/server/game/Server/Packets/AllPackets.h index 63cd9c0ca4c..17bcce8f09a 100644 --- a/src/server/game/Server/Packets/AllPackets.h +++ b/src/server/game/Server/Packets/AllPackets.h @@ -18,6 +18,7 @@ #ifndef AllPackets_h__ #define AllPackets_h__ +#include "NPCPackets.h" #include "QueryPackets.h" #include "QuestPackets.h" #include "SpellPackets.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..bef58fabd02 --- /dev/null +++ b/src/server/game/Server/Packets/NPCPackets.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2008-2018 TrinityCore <https://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 "NPCPackets.h" + +void WorldPackets::NPC::Hello::Read() +{ + _worldPacket >> Unit; +} + +WorldPacket const* WorldPackets::NPC::TrainerList::Write() +{ + _worldPacket << TrainerGUID; + _worldPacket << int32(TrainerType); + + _worldPacket << int32(Spells.size()); + for (TrainerListSpell const& spell : Spells) + { + _worldPacket << int32(spell.SpellID); + _worldPacket << uint8(spell.Usable); + _worldPacket << int32(spell.MoneyCost); + _worldPacket << int32(spell.ProfessionDialog); + _worldPacket << int32(spell.ProfessionButton); + _worldPacket << uint8(spell.ReqLevel); + _worldPacket << int32(spell.ReqSkillLine); + _worldPacket << int32(spell.ReqSkillRank); + _worldPacket.append(spell.ReqAbility.data(), spell.ReqAbility.size()); + } + + _worldPacket << Greeting; + + return &_worldPacket; +} + +void WorldPackets::NPC::TrainerBuySpell::Read() +{ + _worldPacket >> TrainerGUID; + _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..cdbf7072a8a --- /dev/null +++ b/src/server/game/Server/Packets/NPCPackets.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2008-2018 TrinityCore <https://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 NPCPackets_h__ +#define NPCPackets_h__ + +#include "Packet.h" +#include "ObjectGuid.h" +#include <array> + +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<int32, 3> ReqAbility = { }; + }; + + class TrainerList final : public ServerPacket + { + public: + TrainerList() : ServerPacket(SMSG_TRAINER_LIST) { } + + WorldPacket const* Write() override; + + ObjectGuid TrainerGUID; + int32 TrainerType = 0; + std::vector<TrainerListSpell> 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 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 540bc239128..2891b486201 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -74,6 +74,11 @@ class RBACData; namespace WorldPackets { + namespace NPC + { + class Hello; + class TrainerBuySpell; + } namespace Query { class QueryCreature; @@ -348,8 +353,7 @@ class TC_GAME_API WorldSession void SendNameQueryOpcode(ObjectGuid guid); - void SendTrainerList(ObjectGuid guid); - void SendTrainerList(ObjectGuid guid, std::string const& strTitle); + void SendTrainerList(Creature* npc); void SendListInventory(ObjectGuid guid); void SendShowBank(ObjectGuid guid); bool CanOpenMailBox(ObjectGuid guid); @@ -652,8 +656,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 a5a9f4d33e4..9fe210e493b 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -297,9 +297,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 5dddc5f13e8..595e186b6cd 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" @@ -607,7 +608,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 13c3b7b161c..4c3498c98b0 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1927,18 +1927,21 @@ 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(); 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 - 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 eae194b1044..e5732634b56 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -124,7 +124,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_target_position", rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_TARGET_POSITION, true, &HandleReloadSpellTargetPositionCommand, "" }, { "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, "" }, + { "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, "" }, @@ -238,7 +238,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"); @@ -711,11 +711,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 e0b27a40642..1ab3815c9c0 100644 --- a/src/server/scripts/Northrend/zone_storm_peaks.cpp +++ b/src/server/scripts/Northrend/zone_storm_peaks.cpp @@ -157,7 +157,7 @@ public: switch (action) { case GOSSIP_ACTION_TRAIN: - player->GetSession()->SendTrainerList(me->GetGUID()); + player->GetSession()->SendTrainerList(me); 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 a31fc1ee1f6..9601ae2d583 100644 --- a/src/server/scripts/World/npc_professions.cpp +++ b/src/server/scripts/World/npc_professions.cpp @@ -495,7 +495,7 @@ public: player->GetSession()->SendListInventory(me->GetGUID()); break; case GOSSIP_ACTION_TRAIN: - player->GetSession()->SendTrainerList(me->GetGUID()); + player->GetSession()->SendTrainerList(me); break; //Learn Armor/Weapon case GOSSIP_ACTION_INFO_DEF + 1: @@ -899,7 +899,7 @@ public: player->GetSession()->SendListInventory(me->GetGUID()); break; case GOSSIP_ACTION_TRAIN: - player->GetSession()->SendTrainerList(me->GetGUID()); + player->GetSession()->SendTrainerList(me); break; //Unlearn Leather case GOSSIP_ACTION_INFO_DEF + 1: @@ -1041,7 +1041,7 @@ public: player->GetSession()->SendListInventory(me->GetGUID()); break; case GOSSIP_ACTION_TRAIN: - player->GetSession()->SendTrainerList(me->GetGUID()); + player->GetSession()->SendTrainerList(me); break; //Learn Tailor case GOSSIP_ACTION_INFO_DEF + 1: diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h index b27f2f108d9..5a7be809ea8 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -2621,16 +2621,6 @@ enum LockType LOCKTYPE_OPEN_FROM_VEHICLE = 21 }; -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 { |