mirror of
https://github.com/azerothcore/azerothcore-wotlk.git
synced 2025-11-10 12:24:22 +08:00
Merge 21b2ae3aed0660e10e59ffb3617cf025f33f49c4 into ec274182a2a0e72d2f3eb2d5118a6d8fe95b02ef
This commit is contained in:
commit
fac88e9441
@ -0,0 +1,49 @@
|
||||
--
|
||||
DROP TABLE IF EXISTS `trainer`;
|
||||
CREATE TABLE `trainer` (
|
||||
`Id` INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
`Type` TINYINT UNSIGNED DEFAULT 2 NOT NULL,
|
||||
`Requirement` MEDIUMINT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
`Greeting` MEDIUMTEXT NULL,
|
||||
`VerifiedBuild` INT DEFAULT 0 NULL,
|
||||
CONSTRAINT trainer_pk PRIMARY KEY (Id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
DROP TABLE IF EXISTS `trainer_locale`;
|
||||
CREATE TABLE `trainer_locale` (
|
||||
`Id` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`locale` varchar(4) NOT NULL,
|
||||
`Greeting_lang` MEDIUMTEXT NULL,
|
||||
`VerifiedBuild` INT DEFAULT 0 NULL,
|
||||
CONSTRAINT `trainer_locale_pk` PRIMARY KEY (`Id`,`locale`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
DROP TABLE IF EXISTS `trainer_spell`;
|
||||
CREATE TABLE `trainer_spell` (
|
||||
`TrainerId` int unsigned DEFAULT 0 NOT NULL,
|
||||
`SpellId` int unsigned DEFAULT 0 NOT NULL,
|
||||
`MoneyCost` int unsigned DEFAULT 0 NOT NULL,
|
||||
`ReqSkillLine` int unsigned DEFAULT 0 NOT NULL,
|
||||
`ReqSkillRank` int unsigned DEFAULT 0 NOT NULL,
|
||||
`ReqAbility1` int unsigned DEFAULT 0 NOT NULL,
|
||||
`ReqAbility2` int unsigned DEFAULT 0 NOT NULL,
|
||||
`ReqAbility3` int unsigned DEFAULT 0 NOT NULL,
|
||||
`ReqLevel` tinyint unsigned DEFAULT 0 NOT NULL,
|
||||
`VerifiedBuild` int DEFAULT 0 NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
DROP TABLE IF EXISTS `creature_default_trainer`;
|
||||
CREATE TABLE `creature_default_trainer` (
|
||||
`CreatureId` int unsigned NOT NULL,
|
||||
`TrainerId` int unsigned DEFAULT 0 NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- Drop unused table
|
||||
DROP TABLE IF EXISTS `npc_trainer`;
|
||||
|
||||
-- Drop removed columns
|
||||
ALTER TABLE `creature_template`
|
||||
DROP `trainer_type`,
|
||||
DROP `trainer_spell`,
|
||||
DROP `trainer_class`,
|
||||
DROP `trainer_race`;
|
||||
7989
data/sql/updates/pending_db_world/rev_1759002573451317909.sql
Normal file
7989
data/sql/updates/pending_db_world/rev_1759002573451317909.sql
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,40 @@
|
||||
--
|
||||
-- Truesilver gauntlets require skill 225, not 245
|
||||
UPDATE `trainer_spell` SET `ReqSkillRank`=225 WHERE `TrainerId`=124 AND `SpellId`=9954;
|
||||
|
||||
-- Woo Ping polearm training requires level 20
|
||||
UPDATE `trainer_spell` SET `ReqLevel`=20 WHERE `TrainerId`=53 AND `SpellId`=200;
|
||||
|
||||
-- Guvan should train priest spells past level 6
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=11 WHERE `CreatureId`=16502;
|
||||
|
||||
-- Portal to Theramore and Stonard should be trainable at 35
|
||||
UPDATE `trainer_spell` SET `ReqLevel`=35 WHERE `TrainerId` IN (28, 29) AND `SpellId` IN (49361, 49360);
|
||||
|
||||
-- Add missing ReqAbility1 for some Gnomish and Goblin Engineering spells
|
||||
UPDATE `trainer_spell` SET `ReqAbility1`=20219 WHERE `SpellId` IN (12759, 12895, 12897, 12899, 12902, 12903, 12905, 12906, 12907, 30568, 30570);
|
||||
UPDATE `trainer_spell` SET `ReqAbility1`=20222 WHERE `SpellId` IN (8895, 12715, 12717, 12718, 12754, 12755, 12758, 12760, 12908, 30558, 30560);
|
||||
|
||||
-- Shattrath Bookshelves should not train Northrend recipes
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=58 WHERE `CreatureId`=33609;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=66 WHERE `CreatureId`=33608;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=70 WHERE `CreatureId`=33616;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=112 WHERE `CreatureId`=33614;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=79 WHERE `CreatureId`=33617;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=120 WHERE `CreatureId`=33615;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=95 WHERE `CreatureId`=33610;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=91 WHERE `CreatureId`=33612;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=62 WHERE `CreatureId`=33611;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=73 WHERE `CreatureId`=33613;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=101 WHERE `CreatureId`=33618;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=77 WHERE `CreatureId`=33619;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=99 WHERE `CreatureId`=33623;
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=83 WHERE `CreatureId`=33621;
|
||||
|
||||
-- Hamanar Shattrath Jewelcrafting trainer should not be able to learn grand master jewelcrafter.
|
||||
UPDATE `creature_default_trainer` SET `TrainerId`=112 WHERE `CreatureId`=19063;
|
||||
|
||||
-- make Flying Carpet available from TBC Tailoring trainers
|
||||
DELETE FROM `trainer_spell` WHERE `TrainerId`=73 AND `SpellId` = 60969;
|
||||
INSERT INTO `trainer_spell` (`TrainerId`, `SpellId`, `MoneyCost`, `ReqSkillLine`, `ReqSkillRank`, `ReqAbility1`, `ReqAbility2`, `ReqAbility3`, `ReqLevel`, `VerifiedBuild`) VALUES
|
||||
(73, 60969, 105000, 197, 300, 34090, 0, 0, 0, 0);
|
||||
@ -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, security, help FROM command", CONNECTION_SYNCH);
|
||||
PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, DamageModifier, 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, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, 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, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, 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 id1 = ? OR id2 = ? OR id3 = ?", CONNECTION_SYNCH);
|
||||
|
||||
@ -80,15 +80,6 @@ std::string CreatureMovementData::ToString() const
|
||||
return str.str();
|
||||
}
|
||||
|
||||
TrainerSpell const* TrainerSpellData::Find(uint32 spell_id) const
|
||||
{
|
||||
TrainerSpellMap::const_iterator itr = spellList.find(spell_id);
|
||||
if (itr != spellList.end())
|
||||
return &itr->second;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool VendorItemData::RemoveItem(uint32 item_id)
|
||||
{
|
||||
bool found = false;
|
||||
@ -1268,52 +1259,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->IsClass((Classes)GetCreatureTemplate()->trainer_class, CLASS_CONTEXT_CLASS_TRAINER);
|
||||
}
|
||||
|
||||
bool Creature::IsValidTrainerForPlayer(Player* player, uint32* npcFlags /*= nullptr*/) const
|
||||
{
|
||||
if (!IsTrainer())
|
||||
{
|
||||
Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(GetEntry());
|
||||
if (!trainer)
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (m_creatureInfo->trainer_type)
|
||||
{
|
||||
case TRAINER_TYPE_CLASS:
|
||||
case TRAINER_TYPE_PETS:
|
||||
if (m_creatureInfo->trainer_class && !player->IsClass((Classes)m_creatureInfo->trainer_class, CLASS_CONTEXT_CLASS_TRAINER))
|
||||
{
|
||||
if (npcFlags)
|
||||
*npcFlags &= ~UNIT_NPC_FLAG_TRAINER_CLASS;
|
||||
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case TRAINER_TYPE_MOUNTS:
|
||||
if (m_creatureInfo->trainer_race && m_creatureInfo->trainer_race != player->getRace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case TRAINER_TYPE_TRADESKILLS:
|
||||
if (m_creatureInfo->trainer_spell && !player->HasSpell(m_creatureInfo->trainer_spell))
|
||||
{
|
||||
if (npcFlags)
|
||||
*npcFlags &= ~UNIT_NPC_FLAG_TRAINER_PROFESSION;
|
||||
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
return player->GetLevel() >= 10 && trainer->IsTrainerValidForPlayer(player);
|
||||
}
|
||||
|
||||
Player* Creature::GetLootRecipient() const
|
||||
@ -3147,11 +3099,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
|
||||
{
|
||||
|
||||
@ -103,8 +103,7 @@ public:
|
||||
|
||||
///// @todo RENAME THIS!!!!!
|
||||
bool isCanInteractWithBattleMaster(Player* player, bool msg) const;
|
||||
bool isCanTrainingAndResetTalentsOf(Player* player) const;
|
||||
[[nodiscard]] bool IsValidTrainerForPlayer(Player* player, uint32* npcFlags = nullptr) const;
|
||||
bool CanResetTalents(Player* player) const;
|
||||
bool CanCreatureAttack(Unit const* victim, bool skipDistCheck = false) const;
|
||||
void LoadSpellTemplateImmunity();
|
||||
bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr) override;
|
||||
@ -203,8 +202,6 @@ public:
|
||||
uint32 GetVendorItemCurrentCount(VendorItem const* vItem);
|
||||
uint32 UpdateVendorItemCurrentCount(VendorItem const* vItem, uint32 used_count);
|
||||
|
||||
[[nodiscard]] TrainerSpellData const* GetTrainerSpells() const;
|
||||
|
||||
[[nodiscard]] CreatureTemplate const* GetCreatureTemplate() const { return m_creatureInfo; }
|
||||
[[nodiscard]] CreatureData const* GetCreatureData() const { return m_creatureData; }
|
||||
void SetDetectionDistance(float dist){ m_detectionDistance = dist; }
|
||||
|
||||
@ -215,10 +215,6 @@ struct CreatureTemplate
|
||||
uint32 unit_flags2; // enum UnitFlags2 mask values
|
||||
uint32 dynamicflags;
|
||||
uint32 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;
|
||||
@ -503,39 +499,6 @@ struct VendorItemCount
|
||||
|
||||
typedef std::list<VendorItemCount> VendorItemCounts;
|
||||
|
||||
struct TrainerSpell
|
||||
{
|
||||
TrainerSpell()
|
||||
{
|
||||
for (unsigned int & i : learnedSpell)
|
||||
i = 0;
|
||||
}
|
||||
|
||||
uint32 spell{0};
|
||||
uint32 spellCost{0};
|
||||
uint32 reqSkill{0};
|
||||
uint32 reqSkillValue{0};
|
||||
uint32 reqLevel{0};
|
||||
uint32 learnedSpell[3];
|
||||
uint32 reqSpell{0};
|
||||
|
||||
// helpers
|
||||
[[nodiscard]] bool IsCastable() const { return learnedSpell[0] != spell; }
|
||||
};
|
||||
|
||||
typedef std::unordered_map<uint32 /*spellid*/, TrainerSpell> TrainerSpellMap;
|
||||
|
||||
struct TrainerSpellData
|
||||
{
|
||||
TrainerSpellData() = default;
|
||||
~TrainerSpellData() { spellList.clear(); }
|
||||
|
||||
TrainerSpellMap spellList;
|
||||
uint32 trainerType{0}; // 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.
|
||||
[[nodiscard]] TrainerSpell const* Find(uint32 spell_id) const;
|
||||
};
|
||||
|
||||
struct CreatureSpellCooldown
|
||||
{
|
||||
CreatureSpellCooldown() = default;
|
||||
|
||||
262
src/server/game/Entities/Creature/Trainer.cpp
Normal file
262
src/server/game/Entities/Creature/Trainer.cpp
Normal file
@ -0,0 +1,262 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by the
|
||||
* Free Software Foundation; either version 3 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 Affero 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* npc, Player* 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;
|
||||
|
||||
SpellInfo const* trainerSpellInfo = sSpellMgr->AssertSpellInfo(trainerSpell.SpellId);
|
||||
|
||||
bool primaryProfessionFirstRank = false;
|
||||
for (SpellEffectInfo const& spellEffectInfo : trainerSpellInfo->GetEffects())
|
||||
{
|
||||
if (!spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL))
|
||||
continue;
|
||||
|
||||
SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(spellEffectInfo.TriggerSpell);
|
||||
if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank())
|
||||
primaryProfessionFirstRank = true;
|
||||
}
|
||||
|
||||
trainerList.Spells.emplace_back();
|
||||
WorldPackets::NPC::TrainerListSpell& trainerListSpell = trainerList.Spells.back();
|
||||
trainerListSpell.SpellID = trainerSpell.SpellId;
|
||||
trainerListSpell.Usable = AsUnderlyingType(GetSpellState(player, &trainerSpell));
|
||||
trainerListSpell.MoneyCost = int32(trainerSpell.MoneyCost * reputationDiscount);
|
||||
trainerListSpell.PointCost[0] = 0; // spells don't cost talent points
|
||||
trainerListSpell.PointCost[1] = (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* npc, Player* player, uint32 spellId)
|
||||
{
|
||||
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); // 53 SpellCastDirected
|
||||
npc->SendPlaySpellImpact(player->GetGUID(), 362); // 113 EmoteSalute
|
||||
|
||||
// learn explicitly or cast explicitly
|
||||
if (trainerSpell->IsCastable())
|
||||
player->CastSpell(player, trainerSpell->SpellId, true);
|
||||
else
|
||||
player->learnSpell(trainerSpell->SpellId, false);
|
||||
|
||||
SendTeachSucceeded(npc, player, spellId);
|
||||
}
|
||||
|
||||
Spell const* Trainer::GetSpell(uint32 spellId) const
|
||||
{
|
||||
auto itr = std::find_if(_spells.begin(), _spells.end(), [spellId](Spell const& trainerSpell)
|
||||
{
|
||||
return trainerSpell.SpellId == spellId;
|
||||
});
|
||||
|
||||
if (itr != _spells.end())
|
||||
return &(*itr);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Trainer::CanTeachSpell(Player const* player, Spell const* trainerSpell)
|
||||
{
|
||||
SpellState state = GetSpellState(player, trainerSpell);
|
||||
if (state != SpellState::Available)
|
||||
return false;
|
||||
|
||||
SpellInfo const* trainerSpellInfo = sSpellMgr->AssertSpellInfo(trainerSpell->SpellId);
|
||||
|
||||
for (SpellEffectInfo const& spellEffectInfo : trainerSpellInfo->GetEffects())
|
||||
{
|
||||
if (!spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL))
|
||||
continue;
|
||||
|
||||
SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(spellEffectInfo.TriggerSpell);
|
||||
if (learnedSpellInfo && learnedSpellInfo->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 (SpellEffectInfo const& spellEffectInfo : sSpellMgr->AssertSpellInfo(trainerSpell->SpellId)->GetEffects())
|
||||
{
|
||||
if (!spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL))
|
||||
continue;
|
||||
|
||||
hasLearnSpellEffect = true;
|
||||
if (!player->HasSpell(spellEffectInfo.TriggerSpell))
|
||||
knowsAllLearnedSpells = false;
|
||||
|
||||
if (uint32 previousRankSpellId = sSpellMgr->GetPrevSpellInChain(spellEffectInfo.TriggerSpell))
|
||||
if (!player->HasSpell(previousRankSpellId))
|
||||
return SpellState::Unavailable;
|
||||
}
|
||||
|
||||
if (!hasLearnSpellEffect)
|
||||
{
|
||||
if (uint32 previousRankSpellId = sSpellMgr->GetPrevSpellInChain(trainerSpell->SpellId))
|
||||
if (!player->HasSpell(previousRankSpellId))
|
||||
return SpellState::Unavailable;
|
||||
}
|
||||
else if (knowsAllLearnedSpells)
|
||||
return SpellState::Known;
|
||||
|
||||
// check additional spell requirement
|
||||
for (auto const& requirePair : sSpellMgr->GetSpellsRequiredForSpellBounds(trainerSpell->SpellId))
|
||||
if (!player->HasSpell(requirePair.second))
|
||||
return SpellState::Unavailable;
|
||||
|
||||
return SpellState::Available;
|
||||
}
|
||||
|
||||
bool Trainer::IsTrainerValidForPlayer(Player const* player) const
|
||||
{
|
||||
if (!GetTrainerRequirement())
|
||||
return true;
|
||||
|
||||
switch (GetTrainerType())
|
||||
{
|
||||
case Type::Class:
|
||||
case Type::Pet:
|
||||
// check class for class trainers
|
||||
return player->getClass() == GetTrainerRequirement();
|
||||
case Type::Mount:
|
||||
// check race for mount trainers
|
||||
return player->getRace() == GetTrainerRequirement();
|
||||
case Type::Tradeskill:
|
||||
// check spell for profession trainers
|
||||
return player->HasSpell(GetTrainerRequirement());
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Trainer::SendTeachFailure(Creature const* npc, Player const* player, uint32 spellId, FailReason reason) const
|
||||
{
|
||||
WorldPackets::NPC::TrainerBuyFailed trainerBuyFailed;
|
||||
trainerBuyFailed.TrainerGUID = npc->GetGUID();
|
||||
trainerBuyFailed.SpellID = spellId;
|
||||
trainerBuyFailed.TrainerFailedReason = AsUnderlyingType(reason);
|
||||
player->SendDirectMessage(trainerBuyFailed.Write());
|
||||
}
|
||||
|
||||
void Trainer::SendTeachSucceeded(Creature const* npc, Player const* player, uint32 spellId) const
|
||||
{
|
||||
WorldPackets::NPC::TrainerBuySucceeded trainerBuySucceeded;
|
||||
trainerBuySucceeded.TrainerGUID = npc->GetGUID();
|
||||
trainerBuySucceeded.SpellID = spellId;
|
||||
player->SendDirectMessage(trainerBuySucceeded.Write());
|
||||
}
|
||||
|
||||
std::string const& Trainer::GetGreeting(LocaleConstant locale) const
|
||||
{
|
||||
if (_greeting[locale].empty())
|
||||
return _greeting[DEFAULT_LOCALE];
|
||||
|
||||
return _greeting[locale];
|
||||
}
|
||||
|
||||
void Trainer::AddGreetingLocale(LocaleConstant locale, std::string greeting)
|
||||
{
|
||||
_greeting[locale] = std::move(greeting);
|
||||
}
|
||||
}
|
||||
97
src/server/game/Entities/Creature/Trainer.h
Normal file
97
src/server/game/Entities/Creature/Trainer.h
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by the
|
||||
* Free Software Foundation; either version 3 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 Affero 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 AC_GAME_API Spell
|
||||
{
|
||||
uint32 SpellId = 0;
|
||||
uint32 MoneyCost = 0;
|
||||
uint32 ReqSkillLine = 0;
|
||||
uint32 ReqSkillRank = 0;
|
||||
std::array<uint32, 3> ReqAbility = { };
|
||||
uint8 ReqLevel = 0;
|
||||
|
||||
[[nodiscard]] bool IsCastable() const;
|
||||
};
|
||||
|
||||
class AC_GAME_API Trainer
|
||||
{
|
||||
public:
|
||||
Trainer(uint32 trainerId, Type type, uint32 requirement, std::string greeting, std::vector<Spell> spells);
|
||||
|
||||
[[nodiscard]] Spell const* GetSpell(uint32 spellId) const;
|
||||
[[nodiscard]] std::vector<Spell> const& GetSpells() const { return _spells; }
|
||||
void SendSpells(Creature* npc, Player* player, LocaleConstant locale) const;
|
||||
bool CanTeachSpell(Player const* player, Spell const* trainerSpell);
|
||||
void TeachSpell(Creature* npc, Player* player, uint32 spellId);
|
||||
|
||||
[[nodiscard]] Type GetTrainerType() const { return _type; }
|
||||
[[nodiscard]] uint32 GetTrainerRequirement() const { return _requirement; }
|
||||
bool IsTrainerValidForPlayer(Player const* player) const;
|
||||
|
||||
private:
|
||||
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;
|
||||
[[nodiscard]] 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__
|
||||
@ -77,6 +77,7 @@
|
||||
#include "StringConvert.h"
|
||||
#include "TicketMgr.h"
|
||||
#include "Tokenize.h"
|
||||
#include "Trainer.h"
|
||||
#include "Transport.h"
|
||||
#include "Unit.h"
|
||||
#include "UpdateData.h"
|
||||
@ -2120,11 +2121,6 @@ Creature* Player::GetNPCIfCanInteractWith(ObjectGuid const& guid, uint32 npcflag
|
||||
if (!creature->IsWithinDistInMap(this, INTERACTION_DISTANCE))
|
||||
return nullptr;
|
||||
|
||||
// pussywizard: many npcs have missing conditions for class training and rogue trainer can for eg. train dual wield to a shaman :/ too many to change in sql and watch in the future
|
||||
// pussywizard: this function is not used when talking, but when already taking action (buy spell, reset talents, show spell list)
|
||||
if (npcflagmask & (UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_TRAINER_CLASS) && creature->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS && !IsClass((Classes)creature->GetCreatureTemplate()->trainer_class, CLASS_CONTEXT_CLASS_TRAINER))
|
||||
return nullptr;
|
||||
|
||||
return creature;
|
||||
}
|
||||
|
||||
@ -3906,74 +3902,6 @@ bool Player::HasActiveSpell(uint32 spell) const
|
||||
return (itr != m_spells.end() && itr->second->State != PLAYERSPELL_REMOVED && itr->second->Active && itr->second->IsInSpec(m_activeSpec));
|
||||
}
|
||||
|
||||
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->learnedSpell[i])
|
||||
continue;
|
||||
|
||||
if (!HasSpell(trainer_spell->learnedSpell[i]))
|
||||
{
|
||||
hasSpell = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// known spell
|
||||
if (hasSpell)
|
||||
return TRAINER_SPELL_GRAY;
|
||||
|
||||
// check skill requirement
|
||||
if (trainer_spell->reqSkill && GetBaseSkillValue(trainer_spell->reqSkill) < trainer_spell->reqSkillValue)
|
||||
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->learnedSpell[i])
|
||||
continue;
|
||||
|
||||
// check race/class requirement
|
||||
if (!IsSpellFitByClassAndRace(trainer_spell->learnedSpell[i]))
|
||||
return TRAINER_SPELL_RED;
|
||||
|
||||
if (uint32 prevSpell = sSpellMgr->GetPrevSpellInChain(trainer_spell->learnedSpell[i]))
|
||||
{
|
||||
// check prev.rank requirement
|
||||
if (prevSpell && !HasSpell(prevSpell))
|
||||
return TRAINER_SPELL_RED;
|
||||
}
|
||||
|
||||
SpellsRequiringSpellMapBounds spellsRequired = sSpellMgr->GetSpellsRequiredForSpellBounds(trainer_spell->learnedSpell[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 proffesions avalible is disabled
|
||||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||||
{
|
||||
if (!trainer_spell->learnedSpell[i])
|
||||
continue;
|
||||
SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainer_spell->learnedSpell[i]);
|
||||
if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank() && (GetFreePrimaryProfessionPoints() == 0))
|
||||
return TRAINER_SPELL_GREEN_DISABLED;
|
||||
}
|
||||
|
||||
return TRAINER_SPELL_GREEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a character from the database
|
||||
*
|
||||
@ -14431,6 +14359,18 @@ bool Player::CanSeeVendor(Creature const* creature) const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Player::CanSeeTrainer(Creature const* creature) const
|
||||
{
|
||||
if (!creature->HasNpcFlag(UNIT_NPC_FLAG_TRAINER))
|
||||
return true;
|
||||
|
||||
if (auto trainer = sObjectMgr->GetTrainer(creature->GetEntry()))
|
||||
if (!trainer || !trainer->IsTrainerValidForPlayer(this))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Player::BuildPlayerTalentsInfoData(WorldPacket* data)
|
||||
{
|
||||
*data << uint32(GetFreeTalentPoints()); // unspentTalentPoints
|
||||
|
||||
@ -210,14 +210,6 @@ struct SpellCooldown
|
||||
typedef std::map<uint32, SpellCooldown> SpellCooldowns;
|
||||
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,
|
||||
@ -1684,7 +1676,6 @@ public:
|
||||
void SendRemoveControlBar();
|
||||
[[nodiscard]] bool HasSpell(uint32 spell) const override;
|
||||
[[nodiscard]] bool HasActiveSpell(uint32 spell) const; // show in spellbook
|
||||
TrainerSpellState GetTrainerSpellState(TrainerSpell const* trainer_spell) const;
|
||||
[[nodiscard]] bool IsSpellFitByClassAndRace(uint32 spell_id) const;
|
||||
bool IsNeedCastPassiveSpellAtLearn(SpellInfo const* spellInfo) const;
|
||||
|
||||
@ -2550,6 +2541,8 @@ public:
|
||||
//bool isActiveObject() const { return true; }
|
||||
bool CanSeeSpellClickOn(Creature const* creature) const;
|
||||
[[nodiscard]] bool CanSeeVendor(Creature const* creature) const;
|
||||
[[nodiscard]] bool CanSeeTrainer(Creature const* creature) const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool AnyVendorOptionAvailable(uint32 menuId, Creature const* creature) const;
|
||||
public:
|
||||
|
||||
@ -90,15 +90,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:
|
||||
@ -117,11 +117,16 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool
|
||||
canTalk = false;
|
||||
break;
|
||||
case GOSSIP_OPTION_TRAINER:
|
||||
if (!creature->IsValidTrainerForPlayer(this))
|
||||
{
|
||||
Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
|
||||
if (!trainer || !trainer->IsTrainerValidForPlayer(this))
|
||||
{
|
||||
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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case GOSSIP_OPTION_GOSSIP:
|
||||
if (creature->isVendorWithIconSpeak())
|
||||
{
|
||||
@ -328,7 +333,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))
|
||||
|
||||
@ -20737,7 +20737,7 @@ void Unit::PatchValuesUpdate(ByteBuffer& valuesUpdateBuf, BuildValuesCachePosPoi
|
||||
appendValue &= ~UNIT_NPC_FLAG_VENDOR_MASK;
|
||||
}
|
||||
|
||||
if (!creature->IsValidTrainerForPlayer(target, &appendValue))
|
||||
if (!target->CanSeeTrainer(creature))
|
||||
appendValue &= ~UNIT_NPC_FLAG_TRAINER;
|
||||
|
||||
valuesUpdateBuf.put(posPointers.UnitNPCFlagsPos, appendValue);
|
||||
|
||||
@ -342,8 +342,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;
|
||||
@ -523,19 +521,19 @@ void ObjectMgr::LoadCreatureTemplates()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
QueryResult result = WorldDatabase.Query("SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, "
|
||||
// 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
"gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, "
|
||||
// 23 24 25 26 27 28 29 30 31 32 33 34
|
||||
"DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, trainer_type, trainer_spell, "
|
||||
// 35 36 37 38 39 40 41
|
||||
"trainer_class, trainer_race, type, type_flags, lootid, pickpocketloot, skinloot, "
|
||||
// 42 43 44 45 46 47 48 49 50 51
|
||||
// 23 24 25 26 27 28 29 30 31 32
|
||||
"DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, "
|
||||
// 33 34 35 36 37
|
||||
"type, type_flags, lootid, pickpocketloot, skinloot, "
|
||||
// 38 39 40 41 42 43 44 45 46 47
|
||||
"PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, "
|
||||
// 52 53 54 55 56 57 58 59 60 61 62 63
|
||||
// 48 49 50 51 52 53 54 55 56 57 58 59
|
||||
"ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, "
|
||||
// 64 65 66
|
||||
// 60 61 62
|
||||
"spell_school_immune_mask, flags_extra, ScriptName "
|
||||
"FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId ORDER BY entry DESC;");
|
||||
|
||||
@ -635,15 +633,11 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook)
|
||||
creatureTemplate.unit_flags2 = fields[30].Get<uint32>();
|
||||
creatureTemplate.dynamicflags = fields[31].Get<uint32>();
|
||||
creatureTemplate.family = uint32(fields[32].Get<uint8>());
|
||||
creatureTemplate.trainer_type = uint32(fields[33].Get<uint8>());
|
||||
creatureTemplate.trainer_spell = fields[34].Get<uint32>();
|
||||
creatureTemplate.trainer_class = uint32(fields[35].Get<uint8>());
|
||||
creatureTemplate.trainer_race = uint32(fields[36].Get<uint8>());
|
||||
creatureTemplate.type = uint32(fields[37].Get<uint8>());
|
||||
creatureTemplate.type_flags = fields[38].Get<uint32>();
|
||||
creatureTemplate.lootid = fields[39].Get<uint32>();
|
||||
creatureTemplate.pickpocketLootId = fields[40].Get<uint32>();
|
||||
creatureTemplate.SkinLootId = fields[41].Get<uint32>();
|
||||
creatureTemplate.type = uint32(fields[33].Get<uint8>());
|
||||
creatureTemplate.type_flags = fields[34].Get<uint32>();
|
||||
creatureTemplate.lootid = fields[35].Get<uint32>();
|
||||
creatureTemplate.pickpocketLootId = fields[36].Get<uint32>();
|
||||
creatureTemplate.SkinLootId = fields[37].Get<uint32>();
|
||||
|
||||
for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
|
||||
{
|
||||
@ -655,49 +649,49 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook)
|
||||
creatureTemplate.spells[i] = 0;
|
||||
}
|
||||
|
||||
creatureTemplate.PetSpellDataId = fields[42].Get<uint32>();
|
||||
creatureTemplate.VehicleId = fields[43].Get<uint32>();
|
||||
creatureTemplate.mingold = fields[44].Get<uint32>();
|
||||
creatureTemplate.maxgold = fields[45].Get<uint32>();
|
||||
creatureTemplate.AIName = fields[46].Get<std::string>(); // stopped here, fix it
|
||||
creatureTemplate.MovementType = uint32(fields[47].Get<uint8>());
|
||||
if (!fields[48].IsNull())
|
||||
creatureTemplate.PetSpellDataId = fields[38].Get<uint32>();
|
||||
creatureTemplate.VehicleId = fields[39].Get<uint32>();
|
||||
creatureTemplate.mingold = fields[40].Get<uint32>();
|
||||
creatureTemplate.maxgold = fields[41].Get<uint32>();
|
||||
creatureTemplate.AIName = fields[42].Get<std::string>();
|
||||
creatureTemplate.MovementType = uint32(fields[43].Get<uint8>());
|
||||
if (!fields[44].IsNull())
|
||||
{
|
||||
creatureTemplate.Movement.Ground = static_cast<CreatureGroundMovementType>(fields[48].Get<uint8>());
|
||||
}
|
||||
|
||||
creatureTemplate.Movement.Swim = fields[49].Get<bool>();
|
||||
creatureTemplate.Movement.Swim = fields[45].Get<bool>();
|
||||
if (!fields[46].IsNull())
|
||||
{
|
||||
creatureTemplate.Movement.Flight = static_cast<CreatureFlightMovementType>(fields[46].Get<uint8>());
|
||||
}
|
||||
|
||||
creatureTemplate.Movement.Rooted = fields[47].Get<bool>();
|
||||
if (!fields[48].IsNull())
|
||||
{
|
||||
creatureTemplate.Movement.Chase = static_cast<CreatureChaseMovementType>(fields[48].Get<uint8>());
|
||||
}
|
||||
if (!fields[49].IsNull())
|
||||
{
|
||||
creatureTemplate.Movement.Random = static_cast<CreatureRandomMovementType>(fields[49].Get<uint8>());
|
||||
}
|
||||
if (!fields[50].IsNull())
|
||||
{
|
||||
creatureTemplate.Movement.Flight = static_cast<CreatureFlightMovementType>(fields[50].Get<uint8>());
|
||||
creatureTemplate.Movement.InteractionPauseTimer = fields[50].Get<uint32>();
|
||||
}
|
||||
|
||||
creatureTemplate.Movement.Rooted = fields[51].Get<bool>();
|
||||
if (!fields[52].IsNull())
|
||||
{
|
||||
creatureTemplate.Movement.Chase = static_cast<CreatureChaseMovementType>(fields[52].Get<uint8>());
|
||||
}
|
||||
if (!fields[53].IsNull())
|
||||
{
|
||||
creatureTemplate.Movement.Random = static_cast<CreatureRandomMovementType>(fields[53].Get<uint8>());
|
||||
}
|
||||
if (!fields[54].IsNull())
|
||||
{
|
||||
creatureTemplate.Movement.InteractionPauseTimer = fields[54].Get<uint32>();
|
||||
}
|
||||
|
||||
creatureTemplate.HoverHeight = fields[55].Get<float>();
|
||||
creatureTemplate.ModHealth = fields[56].Get<float>();
|
||||
creatureTemplate.ModMana = fields[57].Get<float>();
|
||||
creatureTemplate.ModArmor = fields[58].Get<float>();
|
||||
creatureTemplate.ModExperience = fields[59].Get<float>();
|
||||
creatureTemplate.RacialLeader = fields[60].Get<bool>();
|
||||
creatureTemplate.movementId = fields[61].Get<uint32>();
|
||||
creatureTemplate.RegenHealth = fields[62].Get<bool>();
|
||||
creatureTemplate.MechanicImmuneMask = fields[63].Get<uint32>();
|
||||
creatureTemplate.SpellSchoolImmuneMask = fields[64].Get<uint8>();
|
||||
creatureTemplate.flags_extra = fields[65].Get<uint32>();
|
||||
creatureTemplate.ScriptID = GetScriptId(fields[66].Get<std::string>());
|
||||
creatureTemplate.HoverHeight = fields[51].Get<float>();
|
||||
creatureTemplate.ModHealth = fields[52].Get<float>();
|
||||
creatureTemplate.ModMana = fields[53].Get<float>();
|
||||
creatureTemplate.ModArmor = fields[54].Get<float>();
|
||||
creatureTemplate.ModExperience = fields[55].Get<float>();
|
||||
creatureTemplate.RacialLeader = fields[56].Get<bool>();
|
||||
creatureTemplate.movementId = fields[57].Get<uint32>();
|
||||
creatureTemplate.RegenHealth = fields[58].Get<bool>();
|
||||
creatureTemplate.MechanicImmuneMask = fields[59].Get<uint32>();
|
||||
creatureTemplate.SpellSchoolImmuneMask = fields[60].Get<uint8>();
|
||||
creatureTemplate.flags_extra = fields[61].Get<uint32>();
|
||||
creatureTemplate.ScriptID = GetScriptId(fields[62].Get<std::string>());
|
||||
|
||||
// useful if the creature template load is being triggered from outside this class
|
||||
if (triggerHook)
|
||||
@ -1037,30 +1031,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo)
|
||||
cInfo->Entry, cInfo->family, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->family);
|
||||
}
|
||||
|
||||
if (cInfo->trainer_class != difficultyInfo->trainer_class)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `trainer_class` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cInfo->trainer_race != difficultyInfo->trainer_race)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `trainer_race` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cInfo->trainer_type != difficultyInfo->trainer_type)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `trainer_type` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cInfo->trainer_spell != difficultyInfo->trainer_spell)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `trainer_spell` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cInfo->type != difficultyInfo->type)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Creature (Entry: {}, type {}) has different `type` in difficulty {} mode (Entry: {}, type {}).",
|
||||
@ -1157,9 +1127,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)
|
||||
LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong trainer type {}.", cInfo->Entry, cInfo->trainer_type);
|
||||
|
||||
if (cInfo->speed_walk == 0.0f)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong value ({}) in speed_walk, set to 1.", cInfo->Entry, cInfo->speed_walk);
|
||||
@ -9310,130 +9277,158 @@ void ObjectMgr::LoadMailLevelRewards()
|
||||
LOG_INFO("server.loading", " ");
|
||||
}
|
||||
|
||||
void ObjectMgr::AddSpellToTrainer(uint32 entry, uint32 spell, uint32 spellCost, uint32 reqSkill, uint32 reqSkillValue, uint32 reqLevel, uint32 reqSpell)
|
||||
{
|
||||
if (entry >= ACORE_TRAINER_START_REF)
|
||||
return;
|
||||
|
||||
CreatureTemplate const* cInfo = GetCreatureTemplate(entry);
|
||||
if (!cInfo)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `npc_trainer` contains an entry for a non-existing creature template (Entry: {}), ignoring", entry);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(cInfo->npcflag & UNIT_NPC_FLAG_TRAINER))
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `npc_trainer` contains an entry for a creature template (Entry: {}) without trainer flag, ignoring", entry);
|
||||
return;
|
||||
}
|
||||
|
||||
SpellInfo const* spellinfo = sSpellMgr->GetSpellInfo(spell);
|
||||
if (!spellinfo)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `npc_trainer` contains an entry (Entry: {}) for a non-existing spell (Spell: {}), ignoring", entry, spell);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SpellMgr::ComputeIsSpellValid(spellinfo))
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `npc_trainer` contains an entry (Entry: {}) for a broken spell (Spell: {}), ignoring", entry, spell);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetTalentSpellCost(spell))
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `npc_trainer` contains an entry (Entry: {}) for a non-existing spell (Spell: {}) which is a talent, ignoring", entry, spell);
|
||||
return;
|
||||
}
|
||||
|
||||
if (reqSpell && !sSpellMgr->GetSpellInfo(reqSpell))
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `npc_trainer` contains an entry (Entry: {}) for a non-existing reqSpell (Spell: {}), ignoring", entry, reqSpell);
|
||||
return;
|
||||
}
|
||||
|
||||
TrainerSpellData& data = _cacheTrainerSpellStore[entry];
|
||||
|
||||
TrainerSpell& trainerSpell = data.spellList[spell];
|
||||
trainerSpell.spell = spell;
|
||||
trainerSpell.spellCost = spellCost;
|
||||
trainerSpell.reqSkill = reqSkill;
|
||||
trainerSpell.reqSkillValue = reqSkillValue;
|
||||
trainerSpell.reqLevel = reqLevel;
|
||||
trainerSpell.reqSpell = reqSpell;
|
||||
|
||||
if (!trainerSpell.reqLevel)
|
||||
trainerSpell.reqLevel = spellinfo->SpellLevel;
|
||||
|
||||
// calculate learned spell for profession case when stored cast-spell
|
||||
trainerSpell.learnedSpell[0] = spell;
|
||||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||||
{
|
||||
if (spellinfo->Effects[i].Effect != SPELL_EFFECT_LEARN_SPELL)
|
||||
continue;
|
||||
if (trainerSpell.learnedSpell[0] == spell)
|
||||
trainerSpell.learnedSpell[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)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `npc_trainer` has spell {} for trainer entry {} with learn effect which has incorrect target type, ignoring learn effect!", spell, entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
trainerSpell.learnedSpell[i] = spellinfo->Effects[i].TriggerSpell;
|
||||
|
||||
if (trainerSpell.learnedSpell[i])
|
||||
{
|
||||
SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainerSpell.learnedSpell[i]);
|
||||
if (learnedSpellInfo && learnedSpellInfo->IsProfession())
|
||||
data.trainerType = 2;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void ObjectMgr::LoadTrainerSpell()
|
||||
void ObjectMgr::LoadTrainers()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
// For reload case
|
||||
_cacheTrainerSpellStore.clear();
|
||||
_trainers.clear();
|
||||
|
||||
QueryResult result = WorldDatabase.Query("SELECT b.ID, a.SpellID, a.MoneyCost, a.ReqSkillLine, a.ReqSkillRank, a.ReqLevel, a.ReqSpell FROM npc_trainer AS a "
|
||||
"INNER JOIN npc_trainer AS b ON a.ID = -(b.SpellID) "
|
||||
"UNION SELECT * FROM npc_trainer WHERE SpellID > 0");
|
||||
|
||||
if (!result)
|
||||
std::unordered_map<int32, std::vector<Trainer::Spell>> spellsByTrainer;
|
||||
if (QueryResult trainerSpellsResult = WorldDatabase.Query("SELECT TrainerId, SpellId, MoneyCost, ReqSkillLine, ReqSkillRank, ReqAbility1, ReqAbility2, ReqAbility3, ReqLevel FROM trainer_spell"))
|
||||
{
|
||||
LOG_WARN("server.loading", ">> Loaded 0 Trainers. DB table `npc_trainer` is empty!");
|
||||
LOG_INFO("server.loading", " ");
|
||||
return;
|
||||
do
|
||||
{
|
||||
Field* fields = trainerSpellsResult->Fetch();
|
||||
|
||||
Trainer::Spell spell;
|
||||
uint32 trainerId = fields[0].Get<uint32>();
|
||||
spell.SpellId = fields[1].Get<uint32>();
|
||||
spell.MoneyCost = fields[2].Get<uint32>();
|
||||
spell.ReqSkillLine = fields[3].Get<uint32>();
|
||||
spell.ReqSkillRank = fields[4].Get<uint32>();
|
||||
spell.ReqAbility[0] = fields[5].Get<uint32>();
|
||||
spell.ReqAbility[1] = fields[6].Get<uint32>();
|
||||
spell.ReqAbility[2] = fields[7].Get<uint32>();
|
||||
spell.ReqLevel = fields[8].Get<uint8>();
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell.SpellId);
|
||||
if (!spellInfo)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing spell (SpellId: {}) for TrainerId {}, ignoring", spell.SpellId, trainerId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GetTalentSpellCost(spell.SpellId))
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing spell (SpellId: {}) which is a talent, for TrainerId {}, ignoring", spell.SpellId, trainerId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (spell.ReqSkillLine && !sSkillLineStore.LookupEntry(spell.ReqSkillLine))
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing skill (ReqSkillLine: {}) for TrainerId {} and SpellId {}, ignoring",
|
||||
spell.ReqSkillLine, spell.SpellId, trainerId);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool allReqValid = true;
|
||||
for (std::size_t i = 0; i < spell.ReqAbility.size(); ++i)
|
||||
{
|
||||
uint32 requiredSpell = spell.ReqAbility[i];
|
||||
if (requiredSpell && !sSpellMgr->GetSpellInfo(requiredSpell))
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing spell (ReqAbility {} : {}) for TrainerId {} and SpellId {}, ignoring",
|
||||
i + 1, requiredSpell, trainerId, spell.SpellId);
|
||||
allReqValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allReqValid)
|
||||
continue;
|
||||
|
||||
spellsByTrainer[trainerId].push_back(spell);
|
||||
} while (trainerSpellsResult->NextRow());
|
||||
}
|
||||
|
||||
uint32 count = 0;
|
||||
|
||||
do
|
||||
if (QueryResult trainersResult = WorldDatabase.Query("SELECT Id, Type, Requirement, Greeting FROM trainer"))
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
do
|
||||
{
|
||||
Field* fields = trainersResult->Fetch();
|
||||
|
||||
uint32 entry = fields[0].Get<uint32>();
|
||||
uint32 spell = fields[1].Get<uint32>();
|
||||
uint32 spellCost = fields[2].Get<uint32>();
|
||||
uint32 reqSkill = fields[3].Get<uint16>();
|
||||
uint32 reqSkillValue = fields[4].Get<uint16>();
|
||||
uint32 reqLevel = fields[5].Get<uint8>();
|
||||
uint32 reqSpell = fields[6].Get<uint32>();
|
||||
uint32 trainerId = fields[0].Get<uint32>();
|
||||
Trainer::Type trainerType = Trainer::Type(fields[1].Get<uint8>());
|
||||
uint32 requirement = fields[2].Get<uint32>();
|
||||
std::string greeting = fields[3].Get<std::string>();
|
||||
std::vector<Trainer::Spell> spells;
|
||||
auto spellsItr = spellsByTrainer.find(trainerId);
|
||||
if (spellsItr != spellsByTrainer.end())
|
||||
{
|
||||
spells = std::move(spellsItr->second);
|
||||
spellsByTrainer.erase(spellsItr);
|
||||
}
|
||||
|
||||
AddSpellToTrainer(entry, spell, spellCost, reqSkill, reqSkillValue, reqLevel, reqSpell);
|
||||
_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());
|
||||
}
|
||||
|
||||
++count;
|
||||
} while (result->NextRow());
|
||||
for (auto const& unusedSpells : spellsByTrainer)
|
||||
{
|
||||
for (Trainer::Spell const& unusedSpell : unusedSpells.second)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing trainer (TrainerId: {}) for SpellId {}, ignoring", unusedSpells.first, unusedSpell.SpellId);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded {} Trainers in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||||
LOG_INFO("server.loading", " ");
|
||||
if (QueryResult trainerLocalesResult = WorldDatabase.Query("SELECT Id, locale, Greeting_lang FROM trainer_locale"))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = trainerLocalesResult->Fetch();
|
||||
uint32 trainerId = fields[0].Get<uint32>();
|
||||
std::string localeName = fields[1].Get<std::string>();
|
||||
|
||||
LocaleConstant locale = GetLocaleByName(localeName);
|
||||
if (locale == LOCALE_enUS)
|
||||
continue;
|
||||
|
||||
if (Trainer::Trainer* trainer = Acore::Containers::MapGetValuePtr(_trainers, trainerId))
|
||||
trainer->AddGreetingLocale(locale, fields[2].Get<std::string>());
|
||||
else
|
||||
LOG_ERROR("sql.sql", "Table `trainer_locale` references non-existing trainer (TrainerId: {}) for locale %s, ignoring",
|
||||
trainerId, localeName.c_str());
|
||||
} while (trainerLocalesResult->NextRow());
|
||||
}
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded {} Trainers in {} ms", _trainers.size(), GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
void ObjectMgr::LoadCreatureDefaultTrainers()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
_creatureDefaultTrainers.clear();
|
||||
|
||||
if (QueryResult result = WorldDatabase.Query("SELECT CreatureId, TrainerId FROM creature_default_trainer"))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 creatureId = fields[0].Get<uint32>();
|
||||
uint32 trainerId = fields[1].Get<uint32>();
|
||||
|
||||
if (!GetCreatureTemplate(creatureId))
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `creature_default_trainer` references non-existing creature template (CreatureId: %u), ignoring", creatureId);
|
||||
continue;
|
||||
}
|
||||
|
||||
_creatureDefaultTrainers[creatureId] = trainerId;
|
||||
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded {} default trainers in {} ms", _creatureDefaultTrainers.size(), GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
Trainer::Trainer* ObjectMgr::GetTrainer(uint32 creatureId)
|
||||
{
|
||||
auto itr = _creatureDefaultTrainers.find(creatureId);
|
||||
if (itr != _creatureDefaultTrainers.end())
|
||||
return Acore::Containers::MapGetValuePtr(_trainers, itr->second);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ObjectMgr::LoadReferenceVendor(int32 vendor, int32 item, std::set<uint32>* skip_vendors)
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
#include "ObjectDefines.h"
|
||||
#include "QuestDef.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Trainer.h"
|
||||
#include "VehicleDefines.h"
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
@ -664,7 +665,6 @@ typedef std::unordered_map<uint32, QuestPOIVector> QuestPOIContainer;
|
||||
typedef std::map<std::pair<uint32, uint8>, QuestGreeting> QuestGreetingContainer;
|
||||
|
||||
typedef std::unordered_map<uint32, VendorItemData> CacheVendorItemContainer;
|
||||
typedef std::unordered_map<uint32, TrainerSpellData> CacheTrainerSpellContainer;
|
||||
|
||||
typedef std::vector<uint32> CreatureCustomIDsContainer;
|
||||
|
||||
@ -1106,8 +1106,8 @@ public:
|
||||
void LoadGossipMenuItems();
|
||||
|
||||
void LoadVendors();
|
||||
void LoadTrainerSpell();
|
||||
void AddSpellToTrainer(uint32 entry, uint32 spell, uint32 spellCost, uint32 reqSkill, uint32 reqSkillValue, uint32 reqLevel, uint32 reqSpell);
|
||||
void LoadTrainers();
|
||||
void LoadCreatureDefaultTrainers();
|
||||
|
||||
std::string GeneratePetName(uint32 entry);
|
||||
std::string GeneratePetNameLocale(uint32 entry, LocaleConstant locale);
|
||||
@ -1364,14 +1364,7 @@ public:
|
||||
bool AddGameTele(GameTele& data);
|
||||
bool DeleteGameTele(std::string_view name);
|
||||
|
||||
[[nodiscard]] TrainerSpellData const* GetNpcTrainerSpells(uint32 entry) const
|
||||
{
|
||||
CacheTrainerSpellContainer::const_iterator iter = _cacheTrainerSpellStore.find(entry);
|
||||
if (iter == _cacheTrainerSpellStore.end())
|
||||
return nullptr;
|
||||
|
||||
return &iter->second;
|
||||
}
|
||||
Trainer::Trainer* GetTrainer(uint32 creatureId);
|
||||
|
||||
[[nodiscard]] VendorItemData const* GetNpcVendorItemList(uint32 entry) const
|
||||
{
|
||||
@ -1617,7 +1610,8 @@ private:
|
||||
PointOfInterestLocaleContainer _pointOfInterestLocaleStore;
|
||||
|
||||
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
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
#include "DatabaseEnv.h"
|
||||
#include "GameGraveyard.h"
|
||||
#include "Language.h"
|
||||
#include "NPCPackets.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Pet.h"
|
||||
@ -29,6 +30,7 @@
|
||||
#include "ScriptMgr.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "Trainer.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "WorldSession.h"
|
||||
#include <cmath>
|
||||
@ -76,28 +78,48 @@ 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 = GetAcoreString(LANG_NPC_TAINER_HELLO);
|
||||
SendTrainerList(guid, str);
|
||||
}
|
||||
|
||||
void WorldSession::SendTrainerList(ObjectGuid guid, const std::string& strTitle)
|
||||
{
|
||||
LOG_DEBUG("network", "WORLD: SendTrainerList");
|
||||
|
||||
Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_TRAINER);
|
||||
if (!unit)
|
||||
Creature* npc = GetPlayer()->GetNPCIfCanInteractWith(packet.Unit, UNIT_NPC_FLAG_TRAINER);
|
||||
if (!npc)
|
||||
{
|
||||
LOG_DEBUG("network", "WORLD: SendTrainerList - Unit ({}) not found or you can not interact with him.", guid.ToString());
|
||||
LOG_DEBUG("network", "WorldSession: SendTrainerList - {} not found or you can not interact with him.", packet.Unit.ToString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
SendTrainerList(npc);
|
||||
}
|
||||
|
||||
void WorldSession::SendTrainerList(Creature* npc)
|
||||
{
|
||||
// remove fake death
|
||||
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
|
||||
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
|
||||
|
||||
Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(npc->GetEntry());
|
||||
if (!trainer)
|
||||
{
|
||||
LOG_DEBUG("network", "WorldSession: SendTrainerList - trainer spells not found for {}", npc->GetGUID().ToString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!trainer->IsTrainerValidForPlayer(_player))
|
||||
{
|
||||
LOG_DEBUG("network", "WorldSession: SendTrainerList - trainer {} not valid for player {}", npc->GetGUID().ToString().c_str(), GetPlayerInfo().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
trainer->SendSpells(npc, _player, GetSessionDbLocaleIndex());
|
||||
}
|
||||
|
||||
void WorldSession::HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet)
|
||||
{
|
||||
LOG_DEBUG("network", "WORLD: Received CMSG_TRAINER_BUY_SPELL {}, learn spell id is: {}", packet.TrainerGUID.ToString().c_str(), packet.SpellID);
|
||||
|
||||
Creature* npc = GetPlayer()->GetNPCIfCanInteractWith(packet.TrainerGUID, UNIT_NPC_FLAG_TRAINER);
|
||||
if (!npc)
|
||||
{
|
||||
LOG_DEBUG("network", "WORLD: HandleTrainerBuySpellOpcode - {} not found or you can not interact with him.", packet.TrainerGUID.ToString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -105,169 +127,11 @@ void WorldSession::SendTrainerList(ObjectGuid guid, const std::string& strTitle)
|
||||
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
|
||||
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
|
||||
|
||||
CreatureTemplate const* ci = unit->GetCreatureTemplate();
|
||||
|
||||
if (!ci)
|
||||
{
|
||||
LOG_DEBUG("network", "WORLD: SendTrainerList - ({}) NO CREATUREINFO!", guid.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
TrainerSpellData const* trainer_spells = unit->GetTrainerSpells();
|
||||
if (!trainer_spells)
|
||||
{
|
||||
LOG_DEBUG("network", "WORLD: SendTrainerList - Training spells not found for creature ({})", guid.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
WorldPacket data(SMSG_TRAINER_LIST, 8 + 4 + 4 + trainer_spells->spellList.size() * 38 + strTitle.size() + 1);
|
||||
data << guid;
|
||||
data << uint32(trainer_spells->trainerType);
|
||||
|
||||
std::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->learnedSpell[i])
|
||||
continue;
|
||||
if (!_player->IsSpellFitByClassAndRace(tSpell->learnedSpell[i]))
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(tSpell->learnedSpell[i]);
|
||||
if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank())
|
||||
primary_prof_first_rank = true;
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
continue;
|
||||
|
||||
if (tSpell->reqSpell && !_player->HasSpell(tSpell->reqSpell))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TrainerSpellState state = _player->GetTrainerSpellState(tSpell);
|
||||
|
||||
data << uint32(tSpell->spell); // learned spell (or cast-spell in profession case)
|
||||
data << uint8(state == TRAINER_SPELL_GREEN_DISABLED ? TRAINER_SPELL_GREEN : state);
|
||||
data << uint32(std::floor(tSpell->spellCost * 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->reqSkill);
|
||||
data << uint32(tSpell->reqSkillValue);
|
||||
//prev + req or req + 0
|
||||
uint8 maxReq = 0;
|
||||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||||
{
|
||||
if (!tSpell->learnedSpell[i])
|
||||
continue;
|
||||
if (uint32 prevSpellId = sSpellMgr->GetPrevSpellInChain(tSpell->learnedSpell[i]))
|
||||
{
|
||||
data << uint32(prevSpellId);
|
||||
++maxReq;
|
||||
}
|
||||
if (maxReq == 3)
|
||||
break;
|
||||
SpellsRequiringSpellMapBounds spellsRequired = sSpellMgr->GetSpellsRequiredForSpellBounds(tSpell->learnedSpell[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);
|
||||
}
|
||||
|
||||
void WorldSession::HandleTrainerBuySpellOpcode(WorldPacket& recvData)
|
||||
{
|
||||
ObjectGuid guid;
|
||||
uint32 spellId = 0;
|
||||
|
||||
recvData >> guid >> spellId;
|
||||
|
||||
Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_TRAINER);
|
||||
if (!unit)
|
||||
{
|
||||
LOG_DEBUG("network", "WORLD: HandleTrainerBuySpellOpcode - Unit ({}) not found or you can not interact with him.", guid.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
// remove fake death
|
||||
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
|
||||
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
|
||||
|
||||
// check present spell in trainer spell list
|
||||
TrainerSpellData const* trainer_spells = unit->GetTrainerSpells();
|
||||
if (!trainer_spells)
|
||||
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(npc->GetEntry());
|
||||
if (!trainer)
|
||||
return;
|
||||
|
||||
// not found, cheat?
|
||||
TrainerSpell const* trainer_spell = trainer_spells->Find(spellId);
|
||||
if (!trainer_spell)
|
||||
return;
|
||||
|
||||
if (trainer_spell->reqSpell && !_player->HasSpell(trainer_spell->reqSpell))
|
||||
{
|
||||
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(std::floor(trainer_spell->spellCost * _player->GetReputationPriceDiscount(unit)));
|
||||
|
||||
// check money requirement
|
||||
if (!_player->HasEnoughMoney(nSpellCost))
|
||||
return;
|
||||
|
||||
_player->ModifyMoney(-int32(nSpellCost));
|
||||
|
||||
unit->SendPlaySpellVisual(179); // 53 SpellCastDirected
|
||||
unit->SendPlaySpellImpact(_player->GetGUID(), 362); // 113 EmoteSalute
|
||||
|
||||
// learn explicitly or cast explicitly
|
||||
if (trainer_spell->IsCastable())
|
||||
_player->CastSpell(_player, trainer_spell->spell, true);
|
||||
else
|
||||
_player->learnSpell(spellId);
|
||||
|
||||
WorldPacket data(SMSG_TRAINER_BUY_SUCCEEDED, 12);
|
||||
data << 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)
|
||||
|
||||
@ -68,7 +68,7 @@ void WorldSession::HandleTalentWipeConfirmOpcode(WorldPacket& recvData)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!unit->isCanTrainingAndResetTalentsOf(_player))
|
||||
if (!unit->CanResetTalents(_player))
|
||||
return;
|
||||
|
||||
// remove fake death
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
#include "ItemPackets.h"
|
||||
#include "LFGPackets.h"
|
||||
#include "MiscPackets.h"
|
||||
#include "NPCPackets.h"
|
||||
#include "PetPackets.h"
|
||||
#include "QueryPackets.h"
|
||||
#include "TotemPackets.h"
|
||||
|
||||
69
src/server/game/Server/Packets/NPCPackets.cpp
Normal file
69
src/server/game/Server/Packets/NPCPackets.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by the
|
||||
* Free Software Foundation; either version 3 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 Affero 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.append(spell.PointCost.data(), spell.PointCost.size());
|
||||
_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;
|
||||
}
|
||||
103
src/server/game/Server/Packets/NPCPackets.h
Normal file
103
src/server/game/Server/Packets/NPCPackets.h
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by the
|
||||
* Free Software Foundation; either version 3 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 Affero 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::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:
|
||||
explicit Hello(WorldPacket&& packet) : ClientPacket(std::move(packet)) { }
|
||||
|
||||
void Read() override;
|
||||
|
||||
ObjectGuid Unit;
|
||||
};
|
||||
|
||||
struct TrainerListSpell
|
||||
{
|
||||
int32 SpellID = 0;
|
||||
uint8 Usable = 0;
|
||||
int32 MoneyCost = 0;
|
||||
std::array<int32, 2> PointCost = { }; // compared with PLAYER_CHARACTER_POINTS in Lua
|
||||
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:
|
||||
explicit 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__
|
||||
@ -198,6 +198,12 @@ namespace WorldPackets
|
||||
class ArenaTeam;
|
||||
class CalendarComplain;
|
||||
}
|
||||
|
||||
namespace NPC
|
||||
{
|
||||
class Hello;
|
||||
class TrainerBuySpell;
|
||||
}
|
||||
}
|
||||
|
||||
enum AccountDataType
|
||||
@ -478,8 +484,7 @@ public:
|
||||
//void SendTestCreatureQueryOpcode(uint32 entry, ObjectGuid guid, uint32 testvalue);
|
||||
void SendNameQueryOpcode(ObjectGuid guid);
|
||||
|
||||
void SendTrainerList(ObjectGuid guid);
|
||||
void SendTrainerList(ObjectGuid guid, std::string const& strTitle);
|
||||
void SendTrainerList(Creature* npc);
|
||||
void SendListInventory(ObjectGuid guid, uint32 vendorEntry = 0);
|
||||
void SendShowBank(ObjectGuid guid);
|
||||
bool CanOpenMailBox(ObjectGuid guid);
|
||||
@ -785,8 +790,8 @@ public: // opcodes handlers
|
||||
void SendActivateTaxiReply(ActivateTaxiReply reply);
|
||||
|
||||
void HandleTabardVendorActivateOpcode(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);
|
||||
|
||||
@ -605,9 +605,9 @@ uint32 SpellMgr::GetSpellWithRank(uint32 spell_id, uint32 rank, bool strict) con
|
||||
return spell_id;
|
||||
}
|
||||
|
||||
SpellRequiredMapBounds SpellMgr::GetSpellsRequiredForSpellBounds(uint32 spell_id) const
|
||||
Acore::IteratorPair<SpellRequiredMap::const_iterator> SpellMgr::GetSpellsRequiredForSpellBounds(uint32 spell_id) const
|
||||
{
|
||||
return mSpellReq.equal_range(spell_id);
|
||||
return Acore::Containers::MapEqualRange(mSpellReq, spell_id);
|
||||
}
|
||||
|
||||
SpellsRequiringSpellMapBounds SpellMgr::GetSpellsRequiringSpellBounds(uint32 spell_id) const
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
|
||||
#include "Common.h"
|
||||
#include "Log.h"
|
||||
#include "IteratorPair.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "Unit.h"
|
||||
|
||||
@ -669,7 +670,7 @@ public:
|
||||
[[nodiscard]] uint32 GetSpellWithRank(uint32 spell_id, uint32 rank, bool strict = false) const;
|
||||
|
||||
// Spell Required table
|
||||
[[nodiscard]] SpellRequiredMapBounds GetSpellsRequiredForSpellBounds(uint32 spell_id) const;
|
||||
[[nodiscard]] Acore::IteratorPair<SpellRequiredMap::const_iterator>GetSpellsRequiredForSpellBounds(uint32 spell_id) const;
|
||||
[[nodiscard]] SpellsRequiringSpellMapBounds GetSpellsRequiringSpellBounds(uint32 spell_id) const;
|
||||
[[nodiscard]] bool IsSpellRequiringSpell(uint32 spellid, uint32 req_spellid) const;
|
||||
|
||||
|
||||
@ -766,6 +766,12 @@ void World::SetInitialWorldSettings()
|
||||
LOG_INFO("server.loading", "Loading GameTeleports...");
|
||||
sObjectMgr->LoadGameTele();
|
||||
|
||||
LOG_INFO("server.loading", "Loading Trainers..."); // must be after LoadCreatureTemplates
|
||||
sObjectMgr->LoadTrainers();
|
||||
|
||||
LOG_INFO("server.loading", "Loading Creature default trainers...");
|
||||
sObjectMgr->LoadCreatureDefaultTrainers();
|
||||
|
||||
LOG_INFO("server.loading", "Loading Gossip Menu...");
|
||||
sObjectMgr->LoadGossipMenu();
|
||||
|
||||
@ -775,9 +781,6 @@ void World::SetInitialWorldSettings()
|
||||
LOG_INFO("server.loading", "Loading Vendors...");
|
||||
sObjectMgr->LoadVendors(); // must be after load CreatureTemplate and ItemTemplate
|
||||
|
||||
LOG_INFO("server.loading", "Loading Trainers...");
|
||||
sObjectMgr->LoadTrainerSpell(); // must be after load CreatureTemplate
|
||||
|
||||
LOG_INFO("server.loading", "Loading Waypoints...");
|
||||
sWaypointMgr->Load();
|
||||
|
||||
|
||||
@ -129,7 +129,7 @@ public:
|
||||
{ "mail_server_template", HandleReloadMailServerTemplateCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
{ "milling_loot_template", HandleReloadLootTemplatesMillingCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
{ "npc_spellclick_spells", HandleReloadSpellClickSpellsCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
{ "npc_trainer", HandleReloadNpcTrainerCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
{ "trainer", HandleReloadTrainerCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
{ "npc_vendor", HandleReloadNpcVendorCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
{ "game_event_npc_vendor", HandleReloadGameEventNPCVendorCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
{ "page_text", HandleReloadPageTextsCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
@ -254,7 +254,7 @@ public:
|
||||
|
||||
static bool HandleReloadAllNpcCommand(ChatHandler* handler)
|
||||
{
|
||||
HandleReloadNpcTrainerCommand(handler);
|
||||
HandleReloadTrainerCommand(handler);
|
||||
HandleReloadNpcVendorCommand(handler);
|
||||
HandleReloadPointsOfInterestCommand(handler);
|
||||
HandleReloadSpellClickSpellsCommand(handler);
|
||||
@ -746,11 +746,15 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleReloadNpcTrainerCommand(ChatHandler* handler)
|
||||
static bool HandleReloadTrainerCommand(ChatHandler* handler)
|
||||
{
|
||||
LOG_INFO("server.loading", "Reloading `npc_trainer` Table!");
|
||||
sObjectMgr->LoadTrainerSpell();
|
||||
handler->SendGlobalGMSysMessage("DB table `npc_trainer` reloaded.");
|
||||
LOG_INFO("server.loading", "Reloading `trainer` Tables!");
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -709,7 +709,7 @@ public:
|
||||
switch (action)
|
||||
{
|
||||
case GOSSIP_ACTION_TRAIN:
|
||||
player->GetSession()->SendTrainerList(creature->GetGUID());
|
||||
player->GetSession()->SendTrainerList(creature);
|
||||
break;
|
||||
case GOSSIP_ACTION_TRADE:
|
||||
player->GetSession()->SendListInventory(creature->GetGUID());
|
||||
|
||||
@ -514,7 +514,7 @@ public:
|
||||
player->GetSession()->SendListInventory(creature->GetGUID());
|
||||
break;
|
||||
case GOSSIP_ACTION_TRAIN:
|
||||
player->GetSession()->SendTrainerList(creature->GetGUID());
|
||||
player->GetSession()->SendTrainerList(creature);
|
||||
break;
|
||||
//Learn Alchemy
|
||||
case GOSSIP_ACTION_INFO_DEF + 1:
|
||||
@ -707,7 +707,7 @@ public:
|
||||
player->GetSession()->SendListInventory(creature->GetGUID());
|
||||
break;
|
||||
case GOSSIP_ACTION_TRAIN:
|
||||
player->GetSession()->SendTrainerList(creature->GetGUID());
|
||||
player->GetSession()->SendTrainerList(creature);
|
||||
break;
|
||||
//Learn Armor/Weapon
|
||||
case GOSSIP_ACTION_INFO_DEF + 1:
|
||||
@ -1019,7 +1019,7 @@ public:
|
||||
switch (action)
|
||||
{
|
||||
case GOSSIP_ACTION_TRAIN:
|
||||
player->GetSession()->SendTrainerList(creature->GetGUID());
|
||||
player->GetSession()->SendTrainerList(creature);
|
||||
break;
|
||||
case GOSSIP_MENU_UNLEARN_CONFIRM_DRAGONSCALE:
|
||||
AddGossipItemFor(player, GOSSIP_MENU_UNLEARN_CONFIRM_DRAGONSCALE, GOSSIP_MENU_OPTION_CONFIRM_UNLEARN_DRAGONSCALE, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1, DoMedUnlearnCost(player));
|
||||
@ -1121,7 +1121,7 @@ public:
|
||||
player->GetSession()->SendListInventory(creature->GetGUID());
|
||||
break;
|
||||
case GOSSIP_ACTION_TRAIN:
|
||||
player->GetSession()->SendTrainerList(creature->GetGUID());
|
||||
player->GetSession()->SendTrainerList(creature);
|
||||
break;
|
||||
//Learn Tailor
|
||||
case GOSSIP_ACTION_INFO_DEF + 1:
|
||||
|
||||
@ -2623,16 +2623,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
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user