Rewrite trainer handling to properly support multiple trainers on the same creature
This commit is contained in:
@@ -578,7 +578,7 @@ namespace Framework.Constants
|
|||||||
CommandReloadMailLootTemplate = 669,
|
CommandReloadMailLootTemplate = 669,
|
||||||
CommandReloadMillingLootTemplate = 670,
|
CommandReloadMillingLootTemplate = 670,
|
||||||
CommandReloadNpcSpellclickSpells = 671,
|
CommandReloadNpcSpellclickSpells = 671,
|
||||||
CommandReloadNpcTrainer = 672,
|
CommandReloadTrainer = 672,
|
||||||
CommandReloadNpcVendor = 673,
|
CommandReloadNpcVendor = 673,
|
||||||
CommandReloadPageText = 674,
|
CommandReloadPageText = 674,
|
||||||
CommandReloadPickpocketingLootTemplate = 675,
|
CommandReloadPickpocketingLootTemplate = 675,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace Framework.Constants
|
|||||||
Stablepet = 14, //Unit_Npc_Flag_Stable (4194304)
|
Stablepet = 14, //Unit_Npc_Flag_Stable (4194304)
|
||||||
Armorer = 15, //Unit_Npc_Flag_Armorer (4096)
|
Armorer = 15, //Unit_Npc_Flag_Armorer (4096)
|
||||||
Unlearntalents = 16, //Unit_Npc_Flag_Trainer (16) (Bonus Option For Trainer)
|
Unlearntalents = 16, //Unit_Npc_Flag_Trainer (16) (Bonus Option For Trainer)
|
||||||
Unlearnpettalents = 17, //Unit_Npc_Flag_Trainer (16) (Bonus Option For Trainer)
|
Unlearnpettalents_Old = 17, // deprecated
|
||||||
Learndualspec = 18, //Unit_Npc_Flag_Trainer (16) (Bonus Option For Trainer)
|
Learndualspec = 18, //Unit_Npc_Flag_Trainer (16) (Bonus Option For Trainer)
|
||||||
Outdoorpvp = 19, //Added By Code (Option For Outdoor Pvp Creatures)
|
Outdoorpvp = 19, //Added By Code (Option For Outdoor Pvp Creatures)
|
||||||
Max
|
Max
|
||||||
|
|||||||
@@ -571,17 +571,22 @@ namespace Framework.Constants
|
|||||||
|
|
||||||
public enum TrainerType
|
public enum TrainerType
|
||||||
{
|
{
|
||||||
Class = 0,
|
None = 0,
|
||||||
Mounts = 1,
|
Talent = 1,
|
||||||
Tradeskills = 2,
|
Tradeskills = 2,
|
||||||
Pets = 3
|
Pets = 3
|
||||||
}
|
}
|
||||||
public enum TrainerSpellState
|
public enum TrainerSpellState
|
||||||
{
|
{
|
||||||
Gray = 0,
|
Known = 0,
|
||||||
Green = 1,
|
Available = 1,
|
||||||
Red = 2,
|
Unavailable = 2,
|
||||||
GreenDisabled = 10
|
}
|
||||||
|
|
||||||
|
public enum TrainerFailReason
|
||||||
|
{
|
||||||
|
Unavailable = 0,
|
||||||
|
NotEnoughMoney = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ChatMsg : uint
|
public enum ChatMsg : uint
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace Framework.Database
|
|||||||
PrepareStatement(WorldStatements.SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?");
|
PrepareStatement(WorldStatements.SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?");
|
||||||
PrepareStatement(WorldStatements.DEL_CREATURE, "DELETE FROM creature WHERE guid = ?");
|
PrepareStatement(WorldStatements.DEL_CREATURE, "DELETE FROM creature WHERE guid = ?");
|
||||||
PrepareStatement(WorldStatements.SEL_COMMANDS, "SELECT name, permission, help FROM command");
|
PrepareStatement(WorldStatements.SEL_COMMANDS, "SELECT name, permission, help FROM command");
|
||||||
PrepareStatement(WorldStatements.SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, modelid4, name, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_type, trainer_class, trainer_race, type, type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template WHERE entry = ?");
|
PrepareStatement(WorldStatements.SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, modelid4, name, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_class, type, type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template WHERE entry = ?");
|
||||||
PrepareStatement(WorldStatements.SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?");
|
PrepareStatement(WorldStatements.SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?");
|
||||||
PrepareStatement(WorldStatements.SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ?");
|
PrepareStatement(WorldStatements.SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ?");
|
||||||
PrepareStatement(WorldStatements.SEL_GAMEOBJECT_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM gameobject WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? ORDER BY order_");
|
PrepareStatement(WorldStatements.SEL_GAMEOBJECT_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM gameobject WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? ORDER BY order_");
|
||||||
|
|||||||
@@ -527,15 +527,6 @@ namespace Game.Chat
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("npc_trainer", RBACPermissions.CommandReloadNpcTrainer, true)]
|
|
||||||
static bool HandleReloadNpcTrainerCommand(StringArguments args, CommandHandler handler)
|
|
||||||
{
|
|
||||||
Log.outInfo(LogFilter.Server, "Re-Loading `npc_trainer` Table!");
|
|
||||||
Global.ObjectMgr.LoadTrainerSpell();
|
|
||||||
handler.SendGlobalGMSysMessage("DB table `npc_trainer` reloaded.");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Command("npc_vendor", RBACPermissions.CommandReloadNpcVendor, true)]
|
[Command("npc_vendor", RBACPermissions.CommandReloadNpcVendor, true)]
|
||||||
static bool HandleReloadNpcVendorCommand(StringArguments args, CommandHandler handler)
|
static bool HandleReloadNpcVendorCommand(StringArguments args, CommandHandler handler)
|
||||||
{
|
{
|
||||||
@@ -885,6 +876,17 @@ namespace Game.Chat
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Command("trainer", RBACPermissions.CommandReloadTrainer, true)]
|
||||||
|
static bool HandleReloadTrainerCommand(StringArguments args, CommandHandler handler)
|
||||||
|
{
|
||||||
|
Log.outInfo(LogFilter.Misc, "Re-Loading `trainer` Table!");
|
||||||
|
Global.ObjectMgr.LoadTrainers();
|
||||||
|
handler.SendGlobalGMSysMessage("DB table `trainer` reloaded.");
|
||||||
|
handler.SendGlobalGMSysMessage("DB table `trainer_locale` reloaded.");
|
||||||
|
handler.SendGlobalGMSysMessage("DB table `trainer_spell` reloaded.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
[Command("vehicle_accessory", RBACPermissions.CommandReloadVehicleAccesory, true)]
|
[Command("vehicle_accessory", RBACPermissions.CommandReloadVehicleAccesory, true)]
|
||||||
static bool HandleReloadVehicleAccessoryCommand(StringArguments args, CommandHandler handler)
|
static bool HandleReloadVehicleAccessoryCommand(StringArguments args, CommandHandler handler)
|
||||||
{
|
{
|
||||||
@@ -1051,7 +1053,7 @@ namespace Game.Chat
|
|||||||
{
|
{
|
||||||
if (args != null) // will be reloaded from all_gossips
|
if (args != null) // will be reloaded from all_gossips
|
||||||
{
|
{
|
||||||
HandleReloadNpcTrainerCommand(null, handler);
|
HandleReloadTrainerCommand(null, handler);
|
||||||
HandleReloadNpcVendorCommand(null, handler);
|
HandleReloadNpcVendorCommand(null, handler);
|
||||||
HandleReloadPointsOfInterestCommand(null, handler);
|
HandleReloadPointsOfInterestCommand(null, handler);
|
||||||
HandleReloadSpellClickSpellsCommand(null, handler);
|
HandleReloadSpellClickSpellsCommand(null, handler);
|
||||||
|
|||||||
@@ -860,11 +860,9 @@ namespace Game.Entities
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool isCanTrainingAndResetTalentsOf(Player player)
|
public bool CanResetTalents(Player player)
|
||||||
{
|
{
|
||||||
return player.getLevel() >= 10
|
return player.getLevel() >= 15 && player.GetClass() == GetCreatureTemplate().TrainerClass;
|
||||||
&& GetCreatureTemplate().TrainerType == TrainerType.Class
|
|
||||||
&& player.GetClass() == GetCreatureTemplate().TrainerClass;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetTextRepeatId(byte textGroup, byte id)
|
public void SetTextRepeatId(byte textGroup, byte id)
|
||||||
@@ -2457,11 +2455,6 @@ namespace Game.Entities
|
|||||||
return vCount.count;
|
return vCount.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrainerSpellData GetTrainerSpells()
|
|
||||||
{
|
|
||||||
return Global.ObjectMgr.GetNpcTrainerSpells(GetEntry());
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GetName(LocaleConstant locale_idx = LocaleConstant.enUS)
|
public override string GetName(LocaleConstant locale_idx = LocaleConstant.enUS)
|
||||||
{
|
{
|
||||||
if (locale_idx != LocaleConstant.enUS)
|
if (locale_idx != LocaleConstant.enUS)
|
||||||
|
|||||||
@@ -58,9 +58,7 @@ namespace Game.Entities
|
|||||||
public uint UnitFlags3;
|
public uint UnitFlags3;
|
||||||
public uint DynamicFlags;
|
public uint DynamicFlags;
|
||||||
public CreatureFamily Family;
|
public CreatureFamily Family;
|
||||||
public TrainerType TrainerType;
|
|
||||||
public Class TrainerClass;
|
public Class TrainerClass;
|
||||||
public Race TrainerRace;
|
|
||||||
public CreatureType CreatureType;
|
public CreatureType CreatureType;
|
||||||
public CreatureTypeFlags TypeFlags;
|
public CreatureTypeFlags TypeFlags;
|
||||||
public uint TypeFlags2;
|
public uint TypeFlags2;
|
||||||
@@ -386,38 +384,4 @@ namespace Game.Entities
|
|||||||
m_items.Clear();
|
m_items.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TrainerSpell
|
|
||||||
{
|
|
||||||
public uint SpellID;
|
|
||||||
public uint MoneyCost;
|
|
||||||
public uint ReqSkillLine;
|
|
||||||
public uint ReqSkillRank;
|
|
||||||
public uint ReqLevel;
|
|
||||||
public uint[] ReqAbility = new uint[SharedConst.MaxTrainerspellAbilityReqs];
|
|
||||||
public uint Index;
|
|
||||||
|
|
||||||
// helpers
|
|
||||||
public bool IsCastable()
|
|
||||||
{
|
|
||||||
return ReqAbility[0] != SpellID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TrainerSpellData
|
|
||||||
{
|
|
||||||
public TrainerSpellData()
|
|
||||||
{
|
|
||||||
trainerType = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary<uint, TrainerSpell> spellList = new Dictionary<uint, TrainerSpell>();
|
|
||||||
public uint 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.
|
|
||||||
|
|
||||||
public TrainerSpell Find(uint spell_id)
|
|
||||||
{
|
|
||||||
return spellList.LookupByKey(spell_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,22 +30,22 @@ namespace Game.Misc
|
|||||||
{
|
{
|
||||||
public class GossipMenu
|
public class GossipMenu
|
||||||
{
|
{
|
||||||
public void AddMenuItem(int menuItemId, GossipOptionIcon icon, string message, uint sender, uint action, string boxMessage, uint boxMoney, bool coded = false)
|
public uint AddMenuItem(int optionIndex, GossipOptionIcon icon, string message, uint sender, uint action, string boxMessage, uint boxMoney, bool coded = false)
|
||||||
{
|
{
|
||||||
Contract.Assert(_menuItems.Count <= SharedConst.MaxGossipMenuItems);
|
Contract.Assert(_menuItems.Count <= SharedConst.MaxGossipMenuItems);
|
||||||
|
|
||||||
// Find a free new id - script case
|
// Find a free new id - script case
|
||||||
if (menuItemId == -1)
|
if (optionIndex == -1)
|
||||||
{
|
{
|
||||||
menuItemId = 0;
|
optionIndex = 0;
|
||||||
if (!_menuItems.Empty())
|
if (!_menuItems.Empty())
|
||||||
{
|
{
|
||||||
foreach (var item in _menuItems)
|
foreach (var item in _menuItems)
|
||||||
{
|
{
|
||||||
if (item.Key > menuItemId)
|
if (item.Key > optionIndex)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
menuItemId = (int)item.Key + 1;
|
optionIndex = (int)item.Key + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,8 @@ namespace Game.Misc
|
|||||||
menuItem.BoxMessage = boxMessage;
|
menuItem.BoxMessage = boxMessage;
|
||||||
menuItem.BoxMoney = boxMoney;
|
menuItem.BoxMoney = boxMoney;
|
||||||
|
|
||||||
_menuItems[(uint)menuItemId] = menuItem;
|
_menuItems[(uint)optionIndex] = menuItem;
|
||||||
|
return (uint)optionIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -124,18 +125,20 @@ namespace Game.Misc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add menu item with existing method. Menu item id -1 is also used in ADD_GOSSIP_ITEM macro.
|
// Add menu item with existing method. Menu item id -1 is also used in ADD_GOSSIP_ITEM macro.
|
||||||
AddMenuItem(-1, item.OptionIcon, strOptionText, sender, action, strBoxText, item.BoxMoney, item.BoxCoded);
|
uint optionIndex = AddMenuItem(-1, item.OptionIcon, strOptionText, sender, action, strBoxText, item.BoxMoney, item.BoxCoded);
|
||||||
|
AddGossipMenuItemData(optionIndex, item.ActionMenuId, item.ActionPoiId, item.TrainerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddGossipMenuItemData(uint menuItemId, uint gossipActionMenuId, uint gossipActionPoi)
|
public void AddGossipMenuItemData(uint optionIndex, uint gossipActionMenuId, uint gossipActionPoi, uint trainerId)
|
||||||
{
|
{
|
||||||
GossipMenuItemData itemData = new GossipMenuItemData();
|
GossipMenuItemData itemData = new GossipMenuItemData();
|
||||||
|
|
||||||
itemData.GossipActionMenuId = gossipActionMenuId;
|
itemData.GossipActionMenuId = gossipActionMenuId;
|
||||||
itemData.GossipActionPoi = gossipActionPoi;
|
itemData.GossipActionPoi = gossipActionPoi;
|
||||||
|
itemData.TrainerId = trainerId;
|
||||||
|
|
||||||
_menuItemData[menuItemId] = itemData;
|
_menuItemData[optionIndex] = itemData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint GetMenuItemSender(uint menuItemId)
|
public uint GetMenuItemSender(uint menuItemId)
|
||||||
@@ -162,6 +165,15 @@ namespace Game.Misc
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasMenuItemType(uint optionType)
|
||||||
|
{
|
||||||
|
foreach (var menuItemPair in _menuItems)
|
||||||
|
if (menuItemPair.Value.OptionType == optionType)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void ClearMenu()
|
public void ClearMenu()
|
||||||
{
|
{
|
||||||
_menuItems.Clear();
|
_menuItems.Clear();
|
||||||
@@ -796,6 +808,7 @@ namespace Game.Misc
|
|||||||
{
|
{
|
||||||
public uint GossipActionMenuId; // MenuId of the gossip triggered by this action
|
public uint GossipActionMenuId; // MenuId of the gossip triggered by this action
|
||||||
public uint GossipActionPoi;
|
public uint GossipActionPoi;
|
||||||
|
public uint TrainerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct NpcTextData
|
public struct NpcTextData
|
||||||
@@ -829,6 +842,7 @@ namespace Game.Misc
|
|||||||
public uint BoxMoney;
|
public uint BoxMoney;
|
||||||
public string BoxText;
|
public string BoxText;
|
||||||
public uint BoxBroadcastTextId;
|
public uint BoxBroadcastTextId;
|
||||||
|
public uint TrainerId;
|
||||||
public List<Condition> Conditions = new List<Condition>();
|
public List<Condition> Conditions = new List<Condition>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Framework.Constants;
|
||||||
|
using Game.Network.Packets;
|
||||||
|
using Game.Spells;
|
||||||
|
|
||||||
|
namespace Game.Entities
|
||||||
|
{
|
||||||
|
public class TrainerSpell
|
||||||
|
{
|
||||||
|
public uint SpellId;
|
||||||
|
public uint MoneyCost;
|
||||||
|
public uint ReqSkillLine;
|
||||||
|
public uint ReqSkillRank;
|
||||||
|
public Array<uint> ReqAbility = new Array<uint>(3);
|
||||||
|
public byte ReqLevel;
|
||||||
|
|
||||||
|
public uint CastSpellId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Trainer
|
||||||
|
{
|
||||||
|
public Trainer(uint id, TrainerType type, string greeting, List<TrainerSpell> spells)
|
||||||
|
{
|
||||||
|
_id = id;
|
||||||
|
_type = type;
|
||||||
|
_spells = spells;
|
||||||
|
|
||||||
|
_greeting[(int)LocaleConstant.enUS] = greeting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendSpells(Creature npc, Player player, LocaleConstant locale)
|
||||||
|
{
|
||||||
|
float reputationDiscount = player.GetReputationPriceDiscount(npc);
|
||||||
|
|
||||||
|
TrainerList trainerList = new TrainerList();
|
||||||
|
trainerList.TrainerGUID = npc.GetGUID();
|
||||||
|
trainerList.TrainerType = (int)_type;
|
||||||
|
trainerList.TrainerID = (int)_id;
|
||||||
|
trainerList.Greeting = GetGreeting(locale);
|
||||||
|
|
||||||
|
foreach (TrainerSpell trainerSpell in _spells)
|
||||||
|
{
|
||||||
|
if (!player.IsSpellFitByClassAndRace(trainerSpell.SpellId))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
TrainerListSpell trainerListSpell = new TrainerListSpell();
|
||||||
|
trainerListSpell.SpellID = trainerSpell.SpellId;
|
||||||
|
trainerListSpell.MoneyCost = (uint)(trainerSpell.MoneyCost * reputationDiscount);
|
||||||
|
trainerListSpell.ReqSkillLine = trainerSpell.ReqSkillLine;
|
||||||
|
trainerListSpell.ReqSkillRank = trainerSpell.ReqSkillRank;
|
||||||
|
trainerListSpell.ReqAbility = trainerSpell.ReqAbility.ToArray();
|
||||||
|
trainerListSpell.Usable = GetSpellState(player, trainerSpell);
|
||||||
|
trainerListSpell.ReqLevel = trainerSpell.ReqLevel;
|
||||||
|
trainerList.Spells.Add(trainerListSpell);
|
||||||
|
}
|
||||||
|
|
||||||
|
player.SendPacket(trainerList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TeachSpell(Creature npc, Player player, uint spellId)
|
||||||
|
{
|
||||||
|
TrainerSpell trainerSpell = GetSpell(spellId);
|
||||||
|
if (trainerSpell == null || !CanTeachSpell(player, trainerSpell))
|
||||||
|
{
|
||||||
|
SendTeachFailure(npc, player, spellId, TrainerFailReason.Unavailable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float reputationDiscount = player.GetReputationPriceDiscount(npc);
|
||||||
|
long moneyCost = (long)(trainerSpell.MoneyCost * reputationDiscount);
|
||||||
|
if (!player.HasEnoughMoney(moneyCost))
|
||||||
|
{
|
||||||
|
SendTeachFailure(npc, player, spellId, TrainerFailReason.NotEnoughMoney);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.ModifyMoney(-moneyCost);
|
||||||
|
|
||||||
|
npc.SendPlaySpellVisualKit(179, 0, 0); // 53 SpellCastDirected
|
||||||
|
player.SendPlaySpellVisualKit(362, 1, 0); // 113 EmoteSalute
|
||||||
|
|
||||||
|
// learn explicitly or cast explicitly
|
||||||
|
if (trainerSpell.CastSpellId != 0)
|
||||||
|
player.CastSpell(player, trainerSpell.CastSpellId, true);
|
||||||
|
else
|
||||||
|
player.LearnSpell(trainerSpell.SpellId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TrainerSpell GetSpell(uint spellId)
|
||||||
|
{
|
||||||
|
return _spells.Find(trainerSpell => trainerSpell.SpellId == spellId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanTeachSpell(Player player, TrainerSpell trainerSpell)
|
||||||
|
{
|
||||||
|
TrainerSpellState state = GetSpellState(player, trainerSpell);
|
||||||
|
if (state != TrainerSpellState.Available)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SpellInfo trainerSpellInfo = Global.SpellMgr.GetSpellInfo(trainerSpell.SpellId);
|
||||||
|
if (trainerSpellInfo.IsPrimaryProfessionFirstRank() && player.GetFreePrimaryProfessionPoints() == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrainerSpellState GetSpellState(Player player, TrainerSpell trainerSpell)
|
||||||
|
{
|
||||||
|
if (player.HasSpell(trainerSpell.SpellId))
|
||||||
|
return TrainerSpellState.Known;
|
||||||
|
|
||||||
|
// check race/class requirement
|
||||||
|
if (!player.IsSpellFitByClassAndRace(trainerSpell.SpellId))
|
||||||
|
return TrainerSpellState.Unavailable;
|
||||||
|
|
||||||
|
// check skill requirement
|
||||||
|
if (trainerSpell.ReqSkillLine != 0 && player.GetBaseSkillValue((SkillType)trainerSpell.ReqSkillLine) < trainerSpell.ReqSkillRank)
|
||||||
|
return TrainerSpellState.Unavailable;
|
||||||
|
|
||||||
|
foreach (uint reqAbility in trainerSpell.ReqAbility)
|
||||||
|
if (reqAbility != 0 && !player.HasSpell(reqAbility))
|
||||||
|
return TrainerSpellState.Unavailable;
|
||||||
|
|
||||||
|
// check level requirement
|
||||||
|
if (player.getLevel() < trainerSpell.ReqLevel)
|
||||||
|
return TrainerSpellState.Unavailable;
|
||||||
|
|
||||||
|
// check additional spell requirement
|
||||||
|
foreach (var spellId in Global.SpellMgr.GetSpellsRequiredForSpellBounds(trainerSpell.SpellId))
|
||||||
|
if (!player.HasSpell(spellId))
|
||||||
|
return TrainerSpellState.Unavailable;
|
||||||
|
|
||||||
|
return TrainerSpellState.Available;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendTeachFailure(Creature npc, Player player, uint spellId, TrainerFailReason reason)
|
||||||
|
{
|
||||||
|
TrainerBuyFailed trainerBuyFailed = new TrainerBuyFailed();
|
||||||
|
trainerBuyFailed.TrainerGUID = npc.GetGUID();
|
||||||
|
trainerBuyFailed.SpellID = spellId;
|
||||||
|
trainerBuyFailed.TrainerFailedReason = reason;
|
||||||
|
player.SendPacket(trainerBuyFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetGreeting(LocaleConstant locale)
|
||||||
|
{
|
||||||
|
if (_greeting[(int)locale].IsEmpty())
|
||||||
|
return _greeting[(int)LocaleConstant.enUS];
|
||||||
|
|
||||||
|
return _greeting[(int)locale];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddGreetingLocale(LocaleConstant locale, string greeting)
|
||||||
|
{
|
||||||
|
_greeting[(int)locale] = greeting;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint _id;
|
||||||
|
TrainerType _type;
|
||||||
|
List<TrainerSpell> _spells;
|
||||||
|
Array<string> _greeting = new Array<string>((int)LocaleConstant.Total);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,6 +140,8 @@ namespace Game.Entities
|
|||||||
|
|
||||||
RestMgr _restMgr;
|
RestMgr _restMgr;
|
||||||
|
|
||||||
|
uint _currentTrainerId;
|
||||||
|
|
||||||
//Combat
|
//Combat
|
||||||
int[] baseRatingValue = new int[(int)CombatRating.Max];
|
int[] baseRatingValue = new int[(int)CombatRating.Max];
|
||||||
public float[][] m_auraBaseMod = new float[(int)BaseModGroup.End][];
|
public float[][] m_auraBaseMod = new float[(int)BaseModGroup.End][];
|
||||||
|
|||||||
@@ -1034,6 +1034,9 @@ namespace Game.Entities
|
|||||||
|
|
||||||
public RestMgr GetRestMgr() { return _restMgr; }
|
public RestMgr GetRestMgr() { return _restMgr; }
|
||||||
|
|
||||||
|
public uint GetCurrentTrainerId() { return _currentTrainerId; }
|
||||||
|
public void SetCurrentTrainerId(uint trainerId) { _currentTrainerId = trainerId; }
|
||||||
|
|
||||||
public bool IsAdvancedCombatLoggingEnabled() { return _advancedCombatLoggingEnabled; }
|
public bool IsAdvancedCombatLoggingEnabled() { return _advancedCombatLoggingEnabled; }
|
||||||
public void SetAdvancedCombatLogging(bool enabled) { _advancedCombatLoggingEnabled = enabled; }
|
public void SetAdvancedCombatLogging(bool enabled) { _advancedCombatLoggingEnabled = enabled; }
|
||||||
|
|
||||||
@@ -1093,26 +1096,6 @@ namespace Game.Entities
|
|||||||
|
|
||||||
m_temporaryUnsummonedPetNumber = 0;
|
m_temporaryUnsummonedPetNumber = 0;
|
||||||
}
|
}
|
||||||
public void ResetPetTalents()
|
|
||||||
{
|
|
||||||
/* ODO: 6.x remove/update pet talents
|
|
||||||
// This needs another gossip option + NPC text as a confirmation.
|
|
||||||
// The confirmation gossip listid has the text: "Yes, please do."
|
|
||||||
Pet pet = GetPet();
|
|
||||||
|
|
||||||
if (!pet || pet.getPetType() != PetType.Hunter || pet.m_usedTalentCount == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CharmInfo charmInfo = pet.GetCharmInfo();
|
|
||||||
if (charmInfo == null)
|
|
||||||
{
|
|
||||||
Log.outError(LogFilter.Player, "Object (GUID: {0} TypeId: {1}) is considered pet-like but doesn't have a charminfo!", pet.GetGUID().ToString(), pet.GetTypeId());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pet.resetTalents();
|
|
||||||
SendTalentsInfoData(true);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsPetNeedBeTemporaryUnsummoned()
|
public bool IsPetNeedBeTemporaryUnsummoned()
|
||||||
{
|
{
|
||||||
@@ -2352,10 +2335,10 @@ namespace Game.Entities
|
|||||||
|
|
||||||
foreach (var menuItems in menuItemBounds)
|
foreach (var menuItems in menuItemBounds)
|
||||||
{
|
{
|
||||||
bool canTalk = true;
|
|
||||||
if (!Global.ConditionMgr.IsObjectMeetToConditions(this, source, menuItems.Conditions))
|
if (!Global.ConditionMgr.IsObjectMeetToConditions(this, source, menuItems.Conditions))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
bool canTalk = true;
|
||||||
GameObject go = source.ToGameObject();
|
GameObject go = source.ToGameObject();
|
||||||
Creature creature = source.ToCreature();
|
Creature creature = source.ToCreature();
|
||||||
if (creature)
|
if (creature)
|
||||||
@@ -2384,11 +2367,7 @@ namespace Game.Entities
|
|||||||
canTalk = false;
|
canTalk = false;
|
||||||
break;
|
break;
|
||||||
case GossipOption.Unlearntalents:
|
case GossipOption.Unlearntalents:
|
||||||
if (!creature.isCanTrainingAndResetTalentsOf(this))
|
if (!creature.CanResetTalents(this))
|
||||||
canTalk = false;
|
|
||||||
break;
|
|
||||||
case GossipOption.Unlearnpettalents:
|
|
||||||
if (GetPet() == null || GetPet().getPetType() != PetType.Hunter || GetPet().m_spells.Count <= 1 || creature.GetCreatureTemplate().TrainerType != TrainerType.Pets || creature.GetCreatureTemplate().TrainerClass != Class.Hunter)
|
|
||||||
canTalk = false;
|
canTalk = false;
|
||||||
break;
|
break;
|
||||||
case GossipOption.Taxivendor:
|
case GossipOption.Taxivendor:
|
||||||
@@ -2407,11 +2386,6 @@ namespace Game.Entities
|
|||||||
canTalk = false;
|
canTalk = false;
|
||||||
break;
|
break;
|
||||||
case GossipOption.Trainer:
|
case GossipOption.Trainer:
|
||||||
if (GetClass() != creature.GetCreatureTemplate().TrainerClass && creature.GetCreatureTemplate().TrainerType == TrainerType.Class)
|
|
||||||
Log.outError(LogFilter.Sql, "GOSSIP_OPTION_TRAINER. Player {0} (GUID: {1}) request wrong gossip menu: {2} with wrong class: {3} at Creature: {4} (Entry: {5}, Trainer Class: {6})",
|
|
||||||
GetName(), GetGUID().ToString(), menu.GetGossipMenu().GetMenuId(), GetClass(), creature.GetName(), creature.GetEntry(), creature.GetCreatureTemplate().TrainerClass);
|
|
||||||
canTalk = false;
|
|
||||||
break;
|
|
||||||
case GossipOption.Gossip:
|
case GossipOption.Gossip:
|
||||||
case GossipOption.Spiritguide:
|
case GossipOption.Spiritguide:
|
||||||
case GossipOption.Innkeeper:
|
case GossipOption.Innkeeper:
|
||||||
@@ -2482,7 +2456,7 @@ namespace Game.Entities
|
|||||||
}
|
}
|
||||||
|
|
||||||
menu.GetGossipMenu().AddMenuItem((int)menuItems.OptionIndex, menuItems.OptionIcon, strOptionText, 0, (uint)menuItems.OptionType, strBoxText, menuItems.BoxMoney, menuItems.BoxCoded);
|
menu.GetGossipMenu().AddMenuItem((int)menuItems.OptionIndex, menuItems.OptionIcon, strOptionText, 0, (uint)menuItems.OptionType, strBoxText, menuItems.BoxMoney, menuItems.BoxCoded);
|
||||||
menu.GetGossipMenu().AddGossipMenuItemData(menuItems.OptionIndex, menuItems.ActionMenuId, menuItems.ActionPoiId);
|
menu.GetGossipMenu().AddGossipMenuItemData(menuItems.OptionIndex, menuItems.ActionMenuId, menuItems.ActionPoiId, menuItems.TrainerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2580,7 +2554,7 @@ namespace Game.Entities
|
|||||||
GetSession().SendStablePet(guid);
|
GetSession().SendStablePet(guid);
|
||||||
break;
|
break;
|
||||||
case GossipOption.Trainer:
|
case GossipOption.Trainer:
|
||||||
GetSession().SendTrainerList(guid);
|
GetSession().SendTrainerList(guid, menuItemData.TrainerId);
|
||||||
break;
|
break;
|
||||||
case GossipOption.Learndualspec:
|
case GossipOption.Learndualspec:
|
||||||
break;
|
break;
|
||||||
@@ -2588,10 +2562,6 @@ namespace Game.Entities
|
|||||||
PlayerTalkClass.SendCloseGossip();
|
PlayerTalkClass.SendCloseGossip();
|
||||||
SendRespecWipeConfirm(guid, GetNextResetTalentsCost());
|
SendRespecWipeConfirm(guid, GetNextResetTalentsCost());
|
||||||
break;
|
break;
|
||||||
case GossipOption.Unlearnpettalents:
|
|
||||||
PlayerTalkClass.SendCloseGossip();
|
|
||||||
ResetPetTalents();
|
|
||||||
break;
|
|
||||||
case GossipOption.Taxivendor:
|
case GossipOption.Taxivendor:
|
||||||
GetSession().SendTaxiMenu(source.ToCreature());
|
GetSession().SendTaxiMenu(source.ToCreature());
|
||||||
break;
|
break;
|
||||||
@@ -5484,76 +5454,6 @@ namespace Game.Entities
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrainerSpellState GetTrainerSpellState(TrainerSpell trainer_spell)
|
|
||||||
{
|
|
||||||
if (trainer_spell == null)
|
|
||||||
return TrainerSpellState.Red;
|
|
||||||
|
|
||||||
bool hasSpell = true;
|
|
||||||
for (var i = 0; i < SharedConst.MaxTrainerspellAbilityReqs; ++i)
|
|
||||||
{
|
|
||||||
if (trainer_spell.ReqAbility[i] == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!HasSpell(trainer_spell.ReqAbility[i]))
|
|
||||||
{
|
|
||||||
hasSpell = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// known spell
|
|
||||||
if (hasSpell)
|
|
||||||
return TrainerSpellState.Gray;
|
|
||||||
|
|
||||||
// check skill requirement
|
|
||||||
if (trainer_spell.ReqSkillLine != 0 && GetBaseSkillValue((SkillType)trainer_spell.ReqSkillLine) < trainer_spell.ReqSkillRank)
|
|
||||||
return TrainerSpellState.Red;
|
|
||||||
|
|
||||||
// check level requirement
|
|
||||||
if (getLevel() < trainer_spell.ReqLevel)
|
|
||||||
return TrainerSpellState.Red;
|
|
||||||
|
|
||||||
for (var i = 0; i < SharedConst.MaxTrainerspellAbilityReqs; ++i)
|
|
||||||
{
|
|
||||||
if (trainer_spell.ReqAbility[i] == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// check race/class requirement
|
|
||||||
if (!IsSpellFitByClassAndRace(trainer_spell.ReqAbility[i]))
|
|
||||||
return TrainerSpellState.Red;
|
|
||||||
|
|
||||||
uint prevSpell = Global.SpellMgr.GetPrevSpellInChain(trainer_spell.ReqAbility[i]);
|
|
||||||
if (prevSpell != 0)
|
|
||||||
{
|
|
||||||
// check prev.rank requirement
|
|
||||||
if (!HasSpell(prevSpell))
|
|
||||||
return TrainerSpellState.Red;
|
|
||||||
}
|
|
||||||
|
|
||||||
var spellsRequired = Global.SpellMgr.GetSpellsRequiredForSpellBounds(trainer_spell.ReqAbility[i]);
|
|
||||||
foreach (var spellId in spellsRequired)
|
|
||||||
{
|
|
||||||
// check additional spell requirement
|
|
||||||
if (!HasSpell(spellId))
|
|
||||||
return TrainerSpellState.Red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check primary prof. limit
|
|
||||||
// first rank of primary profession spell when there are no proffesions avalible is disabled
|
|
||||||
for (var i = 0; i < SharedConst.MaxTrainerspellAbilityReqs; ++i)
|
|
||||||
{
|
|
||||||
if (trainer_spell.ReqAbility[i] == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
SpellInfo learnedSpellInfo = Global.SpellMgr.GetSpellInfo(trainer_spell.ReqAbility[i]);
|
|
||||||
if (learnedSpellInfo != null && learnedSpellInfo.IsPrimaryProfessionFirstRank() && (GetFreePrimaryProfessionPoints() == 0))
|
|
||||||
return TrainerSpellState.GreenDisabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TrainerSpellState.Green;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SendInitialPacketsBeforeAddToMap()
|
public void SendInitialPacketsBeforeAddToMap()
|
||||||
{
|
{
|
||||||
if (!m_teleport_options.HasAnyFlag(TeleportToOptions.Seamless))
|
if (!m_teleport_options.HasAnyFlag(TeleportToOptions.Seamless))
|
||||||
|
|||||||
@@ -148,6 +148,7 @@
|
|||||||
<Compile Include="Entities\AreaTrigger\AreaTriggerTemplate.cs" />
|
<Compile Include="Entities\AreaTrigger\AreaTriggerTemplate.cs" />
|
||||||
<Compile Include="Entities\Conversation.cs" />
|
<Compile Include="Entities\Conversation.cs" />
|
||||||
<Compile Include="Entities\Creature\CreatureData.cs" />
|
<Compile Include="Entities\Creature\CreatureData.cs" />
|
||||||
|
<Compile Include="Entities\Creature\Trainer.cs" />
|
||||||
<Compile Include="Entities\GameObject\GameObjectData.cs" />
|
<Compile Include="Entities\GameObject\GameObjectData.cs" />
|
||||||
<Compile Include="Entities\Player\CinematicManager.cs" />
|
<Compile Include="Entities\Player\CinematicManager.cs" />
|
||||||
<Compile Include="Entities\Player\CollectionManager.cs" />
|
<Compile Include="Entities\Player\CollectionManager.cs" />
|
||||||
|
|||||||
+171
-165
@@ -631,11 +631,16 @@ namespace Game
|
|||||||
uint oldMSTime = Time.GetMSTime();
|
uint oldMSTime = Time.GetMSTime();
|
||||||
|
|
||||||
gossipMenuItemsStorage.Clear();
|
gossipMenuItemsStorage.Clear();
|
||||||
|
|
||||||
SQLResult result = DB.World.Query(
|
// 0 1 2 3 4 5 6
|
||||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12
|
SQLResult result = DB.World.Query("SELECT o.MenuId, o.OptionIndex, o.OptionIcon, o.OptionText, o.OptionBroadcastTextId, o.OptionType, o.OptionNpcflag, " +
|
||||||
"SELECT menu_id, id, option_icon, option_text, OptionBroadcastTextID, option_id, npc_option_npcflag, action_menu_id, action_poi_id, box_coded, box_money, box_text, BoxBroadcastTextID " +
|
// 7 8 9 10 11 12 13
|
||||||
"FROM gossip_menu_option ORDER BY menu_id, id");
|
"oa.ActionMenuId, oa.ActionPoiId, ob.BoxCoded, ob.BoxMoney, ob.BoxText, ob.BoxBroadcastTextId, ot.TrainerId " +
|
||||||
|
"FROM gossip_menu_option o " +
|
||||||
|
"LEFT JOIN gossip_menu_option_action oa ON o.MenuId = oa.MenuId AND o.OptionIndex = oa.OptionIndex " +
|
||||||
|
"LEFT JOIN gossip_menu_option_box ob ON o.MenuId = ob.MenuId AND o.OptionIndex = ob.OptionIndex " +
|
||||||
|
"LEFT JOIN gossip_menu_option_trainer ot ON o.MenuId = ot.MenuId AND o.OptionIndex = ot.OptionIndex " +
|
||||||
|
"ORDER BY o.MenuId, o.OptionIndex");
|
||||||
|
|
||||||
if (result.IsEmpty())
|
if (result.IsEmpty())
|
||||||
{
|
{
|
||||||
@@ -650,7 +655,7 @@ namespace Game
|
|||||||
|
|
||||||
gMenuItem.MenuId = result.Read<uint>(0);
|
gMenuItem.MenuId = result.Read<uint>(0);
|
||||||
gMenuItem.OptionIndex = result.Read<uint>(1);
|
gMenuItem.OptionIndex = result.Read<uint>(1);
|
||||||
gMenuItem.OptionIcon = (GossipOptionIcon)result.Read<uint>(2);
|
gMenuItem.OptionIcon = (GossipOptionIcon)result.Read<byte>(2);
|
||||||
gMenuItem.OptionText = result.Read<string>(3);
|
gMenuItem.OptionText = result.Read<string>(3);
|
||||||
gMenuItem.OptionBroadcastTextId = result.Read<uint>(4);
|
gMenuItem.OptionBroadcastTextId = result.Read<uint>(4);
|
||||||
gMenuItem.OptionType = (GossipOption)result.Read<uint>(5);
|
gMenuItem.OptionType = (GossipOption)result.Read<uint>(5);
|
||||||
@@ -661,10 +666,11 @@ namespace Game
|
|||||||
gMenuItem.BoxMoney = result.Read<uint>(10);
|
gMenuItem.BoxMoney = result.Read<uint>(10);
|
||||||
gMenuItem.BoxText = result.Read<string>(11);
|
gMenuItem.BoxText = result.Read<string>(11);
|
||||||
gMenuItem.BoxBroadcastTextId = result.Read<uint>(12);
|
gMenuItem.BoxBroadcastTextId = result.Read<uint>(12);
|
||||||
|
gMenuItem.TrainerId = result.Read<uint>(13);
|
||||||
|
|
||||||
if (gMenuItem.OptionIcon >= GossipOptionIcon.Max)
|
if (gMenuItem.OptionIcon >= GossipOptionIcon.Max)
|
||||||
{
|
{
|
||||||
Log.outError(LogFilter.Sql, "Table gossip_menu_option for menu {0}, id {1} has unknown icon id {2}. Replacing with GOSSIPICONCHAT", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.OptionIcon);
|
Log.outError(LogFilter.Sql, $"Table gossip_menu_option for MenuId {gMenuItem.MenuId}, OptionIndex {gMenuItem.OptionIndex} has unknown icon id {gMenuItem.OptionIcon}. Replacing with GOSSIPICONCHAT");
|
||||||
gMenuItem.OptionIcon = GossipOptionIcon.Chat;
|
gMenuItem.OptionIcon = GossipOptionIcon.Chat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,17 +678,17 @@ namespace Game
|
|||||||
{
|
{
|
||||||
if (!CliDB.BroadcastTextStorage.ContainsKey(gMenuItem.OptionBroadcastTextId))
|
if (!CliDB.BroadcastTextStorage.ContainsKey(gMenuItem.OptionBroadcastTextId))
|
||||||
{
|
{
|
||||||
Log.outError(LogFilter.Sql, "Table `gossip_menu_option` for menu {0}, id {1} has non-existing or incompatible OptionBroadcastTextId {2}, ignoring.", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.OptionBroadcastTextId);
|
Log.outError(LogFilter.Sql, $"Table `gossip_menu_option` for MenuId {gMenuItem.MenuId}, OptionIndex {gMenuItem.OptionIndex} has non-existing or incompatible OptionBroadcastTextId {gMenuItem.OptionBroadcastTextId}, ignoring.");
|
||||||
gMenuItem.OptionBroadcastTextId = 0;
|
gMenuItem.OptionBroadcastTextId = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gMenuItem.OptionType >= GossipOption.Max)
|
if (gMenuItem.OptionType >= GossipOption.Max)
|
||||||
Log.outError(LogFilter.Sql, "Table gossip_menu_option for menu {0}, id {1} has unknown option id {2}. Option will not be used", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.OptionType);
|
Log.outError(LogFilter.Sql, $"Table gossip_menu_option for MenuId {gMenuItem.MenuId}, OptionIndex {gMenuItem.OptionIndex} has unknown option id {gMenuItem.OptionType}. Option will not be used");
|
||||||
|
|
||||||
if (gMenuItem.ActionPoiId != 0 && GetPointOfInterest(gMenuItem.ActionPoiId) == null)
|
if (gMenuItem.ActionPoiId != 0 && GetPointOfInterest(gMenuItem.ActionPoiId) == null)
|
||||||
{
|
{
|
||||||
Log.outError(LogFilter.Sql, "Table gossip_menu_option for menu {0}, id {1} use non-existing actionpoiid {2}, ignoring", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.ActionPoiId);
|
Log.outError(LogFilter.Sql, $"Table gossip_menu_option for MenuId {gMenuItem.MenuId}, OptionIndex {gMenuItem.OptionIndex} use non-existing actionpoiid {gMenuItem.ActionPoiId}, ignoring");
|
||||||
gMenuItem.ActionPoiId = 0;
|
gMenuItem.ActionPoiId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,11 +696,17 @@ namespace Game
|
|||||||
{
|
{
|
||||||
if (!CliDB.BroadcastTextStorage.ContainsKey(gMenuItem.BoxBroadcastTextId))
|
if (!CliDB.BroadcastTextStorage.ContainsKey(gMenuItem.BoxBroadcastTextId))
|
||||||
{
|
{
|
||||||
Log.outError(LogFilter.Sql, "Table `gossip_menu_option` for menu {0}, id {1} has non-existing or incompatible BoxBroadcastTextId {2}, ignoring.", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.BoxBroadcastTextId);
|
Log.outError(LogFilter.Sql, $"Table `gossip_menu_option` for MenuId {gMenuItem.MenuId}, OptionIndex {gMenuItem.OptionIndex} has non-existing or incompatible BoxBroadcastTextId {gMenuItem.BoxBroadcastTextId}, ignoring.");
|
||||||
gMenuItem.BoxBroadcastTextId = 0;
|
gMenuItem.BoxBroadcastTextId = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gMenuItem.TrainerId != 0 && GetTrainer(gMenuItem.TrainerId) == null)
|
||||||
|
{
|
||||||
|
Log.outError(LogFilter.Sql, $"Table `gossip_menu_option_trainer` for MenuId {gMenuItem.MenuId}, OptionIndex {gMenuItem.OptionIndex} use non-existing TrainerId {gMenuItem.TrainerId}, ignoring");
|
||||||
|
gMenuItem.TrainerId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
gossipMenuItemsStorage.Add(gMenuItem.MenuId, gMenuItem);
|
gossipMenuItemsStorage.Add(gMenuItem.MenuId, gMenuItem);
|
||||||
++count;
|
++count;
|
||||||
|
|
||||||
@@ -1703,15 +1715,15 @@ namespace Game
|
|||||||
"modelid4, name, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, " +
|
"modelid4, name, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, " +
|
||||||
//20 21 22 23 24 25 26 27 28 29 30
|
//20 21 22 23 24 25 26 27 28 29 30
|
||||||
"faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, " +
|
"faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, " +
|
||||||
//31 32 33 34 35 36 37 38 39 40
|
//31 32 33 34 35 36 37 38
|
||||||
"unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_type, trainer_class, trainer_race, type, " +
|
"unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_class, type, " +
|
||||||
//41 42 43 44 45 46 47 48 49 50 51
|
//39 40 41 42 43 44 45 46 47 48 49
|
||||||
"type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, " +
|
"type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, " +
|
||||||
//52 53 54 55 56 57 58 59 60 61 62 63 64
|
//50 51 52 53 54 55 56 57 58 59 60 61 62
|
||||||
"spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, " +
|
"spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, " +
|
||||||
//65 66 67 68 69 70 71 72 73
|
//63 64 65 66 67 68 69 70 71
|
||||||
"InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, " +
|
"InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, " +
|
||||||
//74 75 76 77 78 79
|
//72 73 74 75 76 77
|
||||||
"RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template");
|
"RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template");
|
||||||
|
|
||||||
if (result.IsEmpty())
|
if (result.IsEmpty())
|
||||||
@@ -1777,42 +1789,40 @@ namespace Game
|
|||||||
creature.UnitFlags3 = fields.Read<uint>(34);
|
creature.UnitFlags3 = fields.Read<uint>(34);
|
||||||
creature.DynamicFlags = fields.Read<uint>(35);
|
creature.DynamicFlags = fields.Read<uint>(35);
|
||||||
creature.Family = (CreatureFamily)fields.Read<byte>(36);
|
creature.Family = (CreatureFamily)fields.Read<byte>(36);
|
||||||
creature.TrainerType = (TrainerType)fields.Read<byte>(37);
|
creature.TrainerClass = (Class)fields.Read<byte>(37);
|
||||||
creature.TrainerClass = (Class)fields.Read<byte>(38);
|
creature.CreatureType = (CreatureType)fields.Read<uint>(38);
|
||||||
creature.TrainerRace = (Race)fields.Read<byte>(39);
|
creature.TypeFlags = (CreatureTypeFlags)fields.Read<uint>(39);
|
||||||
creature.CreatureType = (CreatureType)fields.Read<uint>(40);
|
creature.TypeFlags2 = fields.Read<uint>(40);
|
||||||
creature.TypeFlags = (CreatureTypeFlags)fields.Read<uint>(41);
|
creature.LootId = fields.Read<uint>(41);
|
||||||
creature.TypeFlags2 = fields.Read<uint>(42);
|
creature.PickPocketId = fields.Read<uint>(42);
|
||||||
creature.LootId = fields.Read<uint>(43);
|
creature.SkinLootId = fields.Read<uint>(43);
|
||||||
creature.PickPocketId = fields.Read<uint>(44);
|
|
||||||
creature.SkinLootId = fields.Read<uint>(45);
|
|
||||||
|
|
||||||
for (var i = (int)SpellSchools.Holy; i < (int)SpellSchools.Max; ++i)
|
for (var i = (int)SpellSchools.Holy; i < (int)SpellSchools.Max; ++i)
|
||||||
creature.Resistance[i] = fields.Read<int>(46 + i - 1);
|
creature.Resistance[i] = fields.Read<int>(44 + i - 1);
|
||||||
|
|
||||||
for (var i = 0; i < SharedConst.MaxCreatureSpells; ++i)
|
for (var i = 0; i < SharedConst.MaxCreatureSpells; ++i)
|
||||||
creature.Spells[i] = fields.Read<uint>(52 + i);
|
creature.Spells[i] = fields.Read<uint>(50 + i);
|
||||||
|
|
||||||
creature.VehicleId = fields.Read<uint>(60);
|
creature.VehicleId = fields.Read<uint>(58);
|
||||||
creature.MinGold = fields.Read<uint>(61);
|
creature.MinGold = fields.Read<uint>(59);
|
||||||
creature.MaxGold = fields.Read<uint>(62);
|
creature.MaxGold = fields.Read<uint>(60);
|
||||||
creature.AIName = fields.Read<string>(63);
|
creature.AIName = fields.Read<string>(61);
|
||||||
creature.MovementType = fields.Read<uint>(64);
|
creature.MovementType = fields.Read<uint>(62);
|
||||||
creature.InhabitType = (InhabitType)fields.Read<uint>(65);
|
creature.InhabitType = (InhabitType)fields.Read<uint>(63);
|
||||||
creature.HoverHeight = fields.Read<float>(66);
|
creature.HoverHeight = fields.Read<float>(64);
|
||||||
creature.ModHealth = fields.Read<float>(67);
|
creature.ModHealth = fields.Read<float>(65);
|
||||||
creature.ModHealthExtra = fields.Read<float>(68);
|
creature.ModHealthExtra = fields.Read<float>(66);
|
||||||
creature.ModMana = fields.Read<float>(69);
|
creature.ModMana = fields.Read<float>(67);
|
||||||
creature.ModManaExtra = fields.Read<float>(70);
|
creature.ModManaExtra = fields.Read<float>(68);
|
||||||
creature.ModArmor = fields.Read<float>(71);
|
creature.ModArmor = fields.Read<float>(69);
|
||||||
creature.ModDamage = fields.Read<float>(72);
|
creature.ModDamage = fields.Read<float>(70);
|
||||||
creature.ModExperience = fields.Read<float>(73);
|
creature.ModExperience = fields.Read<float>(71);
|
||||||
creature.RacialLeader = fields.Read<bool>(74);
|
creature.RacialLeader = fields.Read<bool>(72);
|
||||||
creature.MovementId = fields.Read<uint>(75);
|
creature.MovementId = fields.Read<uint>(73);
|
||||||
creature.RegenHealth = fields.Read<bool>(76);
|
creature.RegenHealth = fields.Read<bool>(74);
|
||||||
creature.MechanicImmuneMask = fields.Read<uint>(77);
|
creature.MechanicImmuneMask = fields.Read<uint>(75);
|
||||||
creature.FlagsExtra = (CreatureFlagsExtra)fields.Read<uint>(78);
|
creature.FlagsExtra = (CreatureFlagsExtra)fields.Read<uint>(76);
|
||||||
creature.ScriptID = GetScriptId(fields.Read<string>(79));
|
creature.ScriptID = GetScriptId(fields.Read<string>(77));
|
||||||
|
|
||||||
creatureTemplateStorage.Add(entry, creature);
|
creatureTemplateStorage.Add(entry, creature);
|
||||||
}
|
}
|
||||||
@@ -2382,18 +2392,6 @@ namespace Game
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cInfo.TrainerRace != difficultyInfo.TrainerRace)
|
|
||||||
{
|
|
||||||
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has different `trainer_race` in difficulty {1} mode (Entry: {2}).", cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cInfo.TrainerType != difficultyInfo.TrainerType)
|
|
||||||
{
|
|
||||||
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has different `trainer_type` in difficulty {1} mode (Entry: {2}).", cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cInfo.CreatureType != difficultyInfo.CreatureType)
|
if (cInfo.CreatureType != difficultyInfo.CreatureType)
|
||||||
{
|
{
|
||||||
Log.outError(LogFilter.Sql, "Creature (Entry: {0}, type: {1}) has different `type` in difficulty {2} mode (Entry: {3}, type: {4}).",
|
Log.outError(LogFilter.Sql, "Creature (Entry: {0}, type: {1}) has different `type` in difficulty {2} mode (Entry: {3}, type: {4}).",
|
||||||
@@ -2551,9 +2549,6 @@ namespace Game
|
|||||||
if (cInfo.RangeAttackTime == 0)
|
if (cInfo.RangeAttackTime == 0)
|
||||||
cInfo.RangeAttackTime = SharedConst.BaseAttackTime;
|
cInfo.RangeAttackTime = SharedConst.BaseAttackTime;
|
||||||
|
|
||||||
if (cInfo.Npcflag.HasAnyFlag(NPCFlags.Trainer) && (uint)cInfo.TrainerType >= 4)
|
|
||||||
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong trainer type {1}.", cInfo.Entry, cInfo.TrainerType);
|
|
||||||
|
|
||||||
if (cInfo.SpeedWalk == 0.0f)
|
if (cInfo.SpeedWalk == 0.0f)
|
||||||
{
|
{
|
||||||
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong value ({1}) in speed_walk, set to 1.", cInfo.Entry, cInfo.SpeedWalk);
|
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong value ({1}) in speed_walk, set to 1.", cInfo.Entry, cInfo.SpeedWalk);
|
||||||
@@ -2906,110 +2901,121 @@ namespace Game
|
|||||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} npc texts in {1} ms", _npcTextStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime));
|
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} npc texts in {1} ms", _npcTextStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadTrainerSpell()
|
public void LoadTrainers()
|
||||||
{
|
{
|
||||||
var time = Time.GetMSTime();
|
uint oldMSTime = Time.GetMSTime();
|
||||||
SQLResult result = DB.World.Query("SELECT b.ID, a.SpellID, a.MoneyCost, a.ReqSkillLine, a.ReqSkillRank, a.Reqlevel, a.Index FROM npc_trainer AS a " +
|
|
||||||
"INNER JOIN npc_trainer AS b ON a.ID = -(b.SpellID) UNION SELECT * FROM npc_trainer WHERE SpellID > 0");
|
|
||||||
|
|
||||||
if (result.IsEmpty())
|
// For reload case
|
||||||
|
_trainers.Clear();
|
||||||
|
|
||||||
|
MultiMap<uint, TrainerSpell> spellsByTrainer = new MultiMap<uint, TrainerSpell>();
|
||||||
|
SQLResult trainerSpellsResult = DB.World.Query("SELECT TrainerId, SpellId, MoneyCost, ReqSkillLine, ReqSkillRank, ReqAbility1, ReqAbility2, ReqAbility3, ReqLevel FROM trainer_spell");
|
||||||
|
if (!trainerSpellsResult.IsEmpty())
|
||||||
{
|
{
|
||||||
Log.outError(LogFilter.ServerLoading, "Loaded 0 Trainers. DB table `npc_trainer` is empty!");
|
do
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint count = 0;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
uint ID = result.Read<uint>(0);
|
|
||||||
uint SpellID = result.Read<uint>(1);
|
|
||||||
uint MoneyCost = result.Read<uint>(2);
|
|
||||||
uint ReqSkillLine = result.Read<ushort>(3);
|
|
||||||
uint ReqSkillRank = result.Read<ushort>(4);
|
|
||||||
uint Reqlevel = result.Read<byte>(5);
|
|
||||||
uint Index = result.Read<byte>(6);
|
|
||||||
|
|
||||||
AddSpellToTrainer(ID, SpellID, MoneyCost, ReqSkillLine, ReqSkillRank, Reqlevel, Index);
|
|
||||||
count++;
|
|
||||||
} while (result.NextRow());
|
|
||||||
|
|
||||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Trainers in {1} ms", count, Time.GetMSTimeDiffToNow(time));
|
|
||||||
}
|
|
||||||
void AddSpellToTrainer(uint ID, uint SpellID, uint MoneyCost, uint ReqSkillLine, uint ReqSkillRank, uint Reqlevel, uint Index)
|
|
||||||
{
|
|
||||||
if (ID >= 200000)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CreatureTemplate cInfo = GetCreatureTemplate(ID);
|
|
||||||
if (cInfo == null)
|
|
||||||
{
|
|
||||||
Log.outError(LogFilter.Sql, "Table `npc_trainer` contains entries for a non-existing creature template (ID: {0}), ignoring", ID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cInfo.Npcflag.HasAnyFlag(NPCFlags.Trainer))
|
|
||||||
{
|
|
||||||
Log.outError(LogFilter.Sql, "Table `npc_trainer` contains entries for a creature template (ID: {0}) without trainer flag, ignoring", ID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SpellInfo spellinfo = Global.SpellMgr.GetSpellInfo(SpellID);
|
|
||||||
if (spellinfo == null)
|
|
||||||
{
|
|
||||||
Log.outError(LogFilter.Sql, "Table `npc_trainer` contains an ID ({0}) for a non-existing spell (Spell: {1}), ignoring", ID, SpellID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Global.SpellMgr.IsSpellValid(spellinfo))
|
|
||||||
{
|
|
||||||
Log.outError(LogFilter.Sql, "Table `npc_trainer` contains an ID ({0}) for a broken spell (Spell: {1}), ignoring", ID, SpellID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cacheTrainerSpellStorage.LookupByKey(ID) == null)
|
|
||||||
cacheTrainerSpellStorage.Add(ID, new TrainerSpellData());
|
|
||||||
|
|
||||||
TrainerSpellData data = cacheTrainerSpellStorage[ID];
|
|
||||||
TrainerSpell trainerSpell = new TrainerSpell();
|
|
||||||
trainerSpell.SpellID = SpellID;
|
|
||||||
trainerSpell.MoneyCost = MoneyCost;
|
|
||||||
trainerSpell.ReqSkillLine = ReqSkillLine;
|
|
||||||
trainerSpell.ReqSkillRank = ReqSkillRank;
|
|
||||||
trainerSpell.ReqLevel = Reqlevel;
|
|
||||||
trainerSpell.Index = Index;
|
|
||||||
|
|
||||||
if (trainerSpell.ReqLevel == 0)
|
|
||||||
trainerSpell.ReqLevel = spellinfo.SpellLevel;
|
|
||||||
|
|
||||||
// calculate learned spell for profession case when stored cast-spell
|
|
||||||
trainerSpell.ReqAbility[0] = SpellID;
|
|
||||||
|
|
||||||
foreach (SpellEffectInfo effect in spellinfo.GetEffectsForDifficulty(Difficulty.None))
|
|
||||||
{
|
|
||||||
if (effect == null || effect.Effect != SpellEffectName.LearnSpell)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (trainerSpell.ReqAbility[0] == SpellID)
|
|
||||||
trainerSpell.ReqAbility[0] = 0;
|
|
||||||
// player must be able to cast spell on himself
|
|
||||||
if (effect.TargetA.GetTarget() != 0 && effect.TargetA.GetTarget() != Targets.UnitAlly
|
|
||||||
&& effect.TargetA.GetTarget() != Targets.UnitAny && effect.TargetA.GetTarget() != Targets.UnitCaster)
|
|
||||||
{
|
{
|
||||||
Log.outError(LogFilter.Sql, "Table `npc_trainer` has spell {0} for trainer entry {1} with learn effect which has incorrect target type, ignoring learn effect!", SpellID, ID);
|
TrainerSpell spell = new TrainerSpell();
|
||||||
continue;
|
uint trainerId = trainerSpellsResult.Read<uint>(0);
|
||||||
}
|
spell.SpellId = trainerSpellsResult.Read<uint>(1);
|
||||||
|
spell.MoneyCost = trainerSpellsResult.Read<uint>(2);
|
||||||
|
spell.ReqSkillLine = trainerSpellsResult.Read<uint>(3);
|
||||||
|
spell.ReqSkillRank = trainerSpellsResult.Read<uint>(4);
|
||||||
|
spell.ReqAbility[0] = trainerSpellsResult.Read<uint>(5);
|
||||||
|
spell.ReqAbility[1] = trainerSpellsResult.Read<uint>(6);
|
||||||
|
spell.ReqAbility[2] = trainerSpellsResult.Read<uint>(7);
|
||||||
|
spell.ReqLevel = trainerSpellsResult.Read<byte>(8);
|
||||||
|
|
||||||
trainerSpell.ReqAbility[effect.EffectIndex] = effect.TriggerSpell;
|
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell.SpellId);
|
||||||
|
if (spellInfo == null)
|
||||||
|
{
|
||||||
|
Log.outError(LogFilter.Sql, $"Table `trainer_spell` references non-existing spell (SpellId: {spell.SpellId}) for TrainerId {trainerId}, ignoring");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (trainerSpell.ReqAbility[effect.EffectIndex] != 0)
|
if (spell.ReqSkillLine != 0 && !CliDB.SkillLineStorage.ContainsKey(spell.ReqSkillLine))
|
||||||
{
|
{
|
||||||
SpellInfo learnedSpellInfo = Global.SpellMgr.GetSpellInfo(trainerSpell.ReqAbility[effect.EffectIndex]);
|
Log.outError(LogFilter.Sql, $"Table `trainer_spell` references non-existing skill (ReqSkillLine: {spell.ReqSkillLine}) for TrainerId {spell.SpellId} and SpellId {trainerId}, ignoring");
|
||||||
if (learnedSpellInfo != null && learnedSpellInfo.IsProfession())
|
continue;
|
||||||
data.trainerType = 2;
|
}
|
||||||
}
|
|
||||||
|
bool allReqValid = true;
|
||||||
|
for (var i = 0; i < spell.ReqAbility.Count; ++i)
|
||||||
|
{
|
||||||
|
uint requiredSpell = spell.ReqAbility[i];
|
||||||
|
if (requiredSpell != 0 && !Global.SpellMgr.HasSpellInfo(requiredSpell))
|
||||||
|
{
|
||||||
|
Log.outError(LogFilter.Sql, $"Table `trainer_spell` references non-existing spell (ReqAbility {i + 1}: {requiredSpell}) for TrainerId {spell.SpellId} and SpellId {trainerId}, ignoring");
|
||||||
|
allReqValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allReqValid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (SpellEffectInfo spellEffect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
|
||||||
|
{
|
||||||
|
if (spellEffect.IsEffect(SpellEffectName.LearnSpell))
|
||||||
|
{
|
||||||
|
spell.CastSpellId = spell.SpellId;
|
||||||
|
spell.SpellId = spellEffect.TriggerSpell;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spellsByTrainer.Add(trainerId, spell);
|
||||||
|
|
||||||
|
} while (trainerSpellsResult.NextRow());
|
||||||
}
|
}
|
||||||
data.spellList[SpellID] = trainerSpell;
|
|
||||||
return;
|
SQLResult trainersResult = DB.World.Query("SELECT Id, Type, Greeting FROM trainer");
|
||||||
|
if (!trainersResult.IsEmpty())
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
uint trainerId = trainersResult.Read<uint>(0);
|
||||||
|
TrainerType trainerType = (TrainerType)trainersResult.Read<byte>(1);
|
||||||
|
string greeting = trainersResult.Read<string>(2);
|
||||||
|
List<TrainerSpell> spells = new List<TrainerSpell>();
|
||||||
|
var spellList = spellsByTrainer.LookupByKey(trainerId);
|
||||||
|
if (spellList != null)
|
||||||
|
{
|
||||||
|
spells = spellList;
|
||||||
|
spellsByTrainer.Remove(trainerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
_trainers.Add(trainerId, new Trainer(trainerId, trainerType, greeting, spells));
|
||||||
|
|
||||||
|
} while (trainersResult.NextRow());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var unusedSpells in spellsByTrainer)
|
||||||
|
{
|
||||||
|
Log.outError(LogFilter.Sql, $"Table `trainer_spell` references non-existing trainer (TrainerId: {unusedSpells.Key}) for SpellId {unusedSpells.Value.SpellId}, ignoring");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SQLResult trainerLocalesResult = DB.World.Query("SELECT Id, locale, Greeting_lang FROM trainer_locale");
|
||||||
|
if (!trainerLocalesResult.IsEmpty())
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
uint trainerId = trainerLocalesResult.Read<uint>(0);
|
||||||
|
string localeName = trainerLocalesResult.Read<string>(1);
|
||||||
|
|
||||||
|
LocaleConstant locale = Extensions.ToEnum<LocaleConstant>(localeName);
|
||||||
|
if (locale == LocaleConstant.enUS)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Trainer trainer = _trainers.LookupByKey(trainerId);
|
||||||
|
if (trainer != null)
|
||||||
|
trainer.AddGreetingLocale(locale, trainerLocalesResult.Read<String>(2));
|
||||||
|
else
|
||||||
|
Log.outError(LogFilter.Sql, $"Table `trainer_locale` references non-existing trainer (TrainerId: {trainerId}) for locale {localeName}, ignoring");
|
||||||
|
|
||||||
|
} while (trainerLocalesResult.NextRow());
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.outInfo(LogFilter.ServerLoading, $"Loaded {_trainers.Count} Trainers in {Time.GetMSTimeDiffToNow(oldMSTime)} ms");
|
||||||
}
|
}
|
||||||
public void LoadVendors()
|
public void LoadVendors()
|
||||||
{
|
{
|
||||||
@@ -3488,10 +3494,6 @@ namespace Game
|
|||||||
{
|
{
|
||||||
return creatureModelStorage.LookupByKey(modelId);
|
return creatureModelStorage.LookupByKey(modelId);
|
||||||
}
|
}
|
||||||
public TrainerSpellData GetNpcTrainerSpells(uint entry)
|
|
||||||
{
|
|
||||||
return cacheTrainerSpellStorage.LookupByKey(entry);
|
|
||||||
}
|
|
||||||
public NpcText GetNpcText(uint textId)
|
public NpcText GetNpcText(uint textId)
|
||||||
{
|
{
|
||||||
return _npcTextStorage.LookupByKey(textId);
|
return _npcTextStorage.LookupByKey(textId);
|
||||||
@@ -4585,6 +4587,10 @@ namespace Game
|
|||||||
{
|
{
|
||||||
return ItemTemplateStorage;
|
return ItemTemplateStorage;
|
||||||
}
|
}
|
||||||
|
public Trainer GetTrainer(uint trainerId)
|
||||||
|
{
|
||||||
|
return _trainers.LookupByKey(trainerId);
|
||||||
|
}
|
||||||
public void AddVendorItem(uint entry, uint item, int maxcount, uint incrtime, uint extendedCost, ItemVendorType type, bool persist = true)
|
public void AddVendorItem(uint entry, uint item, int maxcount, uint incrtime, uint extendedCost, ItemVendorType type, bool persist = true)
|
||||||
{
|
{
|
||||||
VendorItemData vList = cacheVendorItemStorage[entry];
|
VendorItemData vList = cacheVendorItemStorage[entry];
|
||||||
@@ -9095,7 +9101,7 @@ namespace Game
|
|||||||
Dictionary<ObjectGuid, ObjectGuid> linkedRespawnStorage = new Dictionary<ObjectGuid, ObjectGuid>();
|
Dictionary<ObjectGuid, ObjectGuid> linkedRespawnStorage = new Dictionary<ObjectGuid, ObjectGuid>();
|
||||||
Dictionary<uint, CreatureBaseStats> creatureBaseStatsStorage = new Dictionary<uint, CreatureBaseStats>();
|
Dictionary<uint, CreatureBaseStats> creatureBaseStatsStorage = new Dictionary<uint, CreatureBaseStats>();
|
||||||
Dictionary<uint, VendorItemData> cacheVendorItemStorage = new Dictionary<uint, VendorItemData>();
|
Dictionary<uint, VendorItemData> cacheVendorItemStorage = new Dictionary<uint, VendorItemData>();
|
||||||
Dictionary<uint, TrainerSpellData> cacheTrainerSpellStorage = new Dictionary<uint, TrainerSpellData>();
|
Dictionary<uint, Trainer> _trainers = new Dictionary<uint, Trainer>();
|
||||||
List<uint>[] _difficultyEntries = new List<uint>[SharedConst.MaxCreatureDifficulties]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate
|
List<uint>[] _difficultyEntries = new List<uint>[SharedConst.MaxCreatureDifficulties]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate
|
||||||
List<uint>[] _hasDifficultyEntries = new List<uint>[SharedConst.MaxCreatureDifficulties]; // already loaded creatures with difficulty 1 values, used in CheckCreatureTemplate
|
List<uint>[] _hasDifficultyEntries = new List<uint>[SharedConst.MaxCreatureDifficulties]; // already loaded creatures with difficulty 1 values, used in CheckCreatureTemplate
|
||||||
Dictionary<uint, NpcText> _npcTextStorage = new Dictionary<uint, NpcText>();
|
Dictionary<uint, NpcText> _npcTextStorage = new Dictionary<uint, NpcText>();
|
||||||
|
|||||||
+16
-135
@@ -56,20 +56,15 @@ namespace Game
|
|||||||
[WorldPacketHandler(ClientOpcodes.TrainerList)]
|
[WorldPacketHandler(ClientOpcodes.TrainerList)]
|
||||||
void HandleTrainerList(Hello packet)
|
void HandleTrainerList(Hello packet)
|
||||||
{
|
{
|
||||||
SendTrainerList(packet.Unit);
|
Log.outInfo(LogFilter.Network, $"{GetPlayerInfo()} sent legacy gossipless trainer hello for unit {packet.Unit.ToString()}, no trainer list available");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendTrainerList(ObjectGuid guid, uint index = 0)
|
public void SendTrainerList(ObjectGuid guid, uint trainerId)
|
||||||
{
|
|
||||||
string str = Global.ObjectMgr.GetCypherString(CypherStrings.NpcTainerHello);
|
|
||||||
SendTrainerList(guid, str, index);
|
|
||||||
}
|
|
||||||
void SendTrainerList(ObjectGuid guid, string title, uint index = 0)
|
|
||||||
{
|
{
|
||||||
Creature unit = GetPlayer().GetNPCIfCanInteractWith(guid, NPCFlags.Trainer);
|
Creature unit = GetPlayer().GetNPCIfCanInteractWith(guid, NPCFlags.Trainer);
|
||||||
if (unit == null)
|
if (unit == null)
|
||||||
{
|
{
|
||||||
Log.outDebug(LogFilter.Network, "WORLD: SendTrainerList - {0} not found or you can not interact with him.", guid.ToString());
|
Log.outDebug(LogFilter.Network, $"WORLD: SendTrainerList - {guid.ToString()} not found or you can not interact with him.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,92 +72,24 @@ namespace Game
|
|||||||
if (GetPlayer().HasUnitState(UnitState.Died))
|
if (GetPlayer().HasUnitState(UnitState.Died))
|
||||||
GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
|
GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
|
||||||
|
|
||||||
TrainerSpellData trainer_spells = unit.GetTrainerSpells();
|
Trainer trainer = Global.ObjectMgr.GetTrainer(trainerId);
|
||||||
if (trainer_spells == null)
|
if (trainer == null)
|
||||||
{
|
{
|
||||||
Log.outDebug(LogFilter.Network, "WORLD: SendTrainerList - Training spells not found for {0}", guid.ToString());
|
Log.outDebug(LogFilter.Network, $"WORLD: SendTrainerList - trainer spells not found for trainer {guid.ToString()} id {trainerId}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrainerList packet = new TrainerList();
|
_player.SetCurrentTrainerId(trainerId);
|
||||||
packet.TrainerGUID = guid;
|
trainer.SendSpells(unit, _player, GetSessionDbLocaleIndex());
|
||||||
packet.TrainerType = (int)trainer_spells.trainerType;
|
|
||||||
packet.Greeting = title;
|
|
||||||
|
|
||||||
// reputation discount
|
|
||||||
float fDiscountMod = GetPlayer().GetReputationPriceDiscount(unit);
|
|
||||||
|
|
||||||
foreach (var tSpell in trainer_spells.spellList.Values)
|
|
||||||
{
|
|
||||||
if (index != 0 && tSpell.Index != index)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
bool valid = true;
|
|
||||||
for (var i = 0; i < SharedConst.MaxTrainerspellAbilityReqs; i++)
|
|
||||||
{
|
|
||||||
if (tSpell.ReqAbility[i] == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!GetPlayer().IsSpellFitByClassAndRace(tSpell.ReqAbility[i]))
|
|
||||||
{
|
|
||||||
valid = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!valid)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
TrainerSpellState state = GetPlayer().GetTrainerSpellState(tSpell);
|
|
||||||
|
|
||||||
TrainerListSpell spell = new TrainerListSpell();
|
|
||||||
spell.SpellID = (int)tSpell.SpellID;
|
|
||||||
spell.MoneyCost = (int)Math.Floor(tSpell.MoneyCost * fDiscountMod);
|
|
||||||
spell.ReqSkillLine = (int)tSpell.ReqSkillLine;
|
|
||||||
spell.ReqSkillRank = (int)tSpell.ReqSkillRank;
|
|
||||||
spell.ReqLevel = (byte)tSpell.ReqLevel;
|
|
||||||
spell.Usable = (state == TrainerSpellState.GreenDisabled ? TrainerSpellState.Green : state);
|
|
||||||
|
|
||||||
byte maxReq = 0;
|
|
||||||
for (var i = 0; i < SharedConst.MaxTrainerspellAbilityReqs; ++i)
|
|
||||||
{
|
|
||||||
if (tSpell.ReqAbility[i] == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
uint prevSpellId = Global.SpellMgr.GetPrevSpellInChain(tSpell.ReqAbility[i]);
|
|
||||||
if (prevSpellId != 0)
|
|
||||||
{
|
|
||||||
spell.ReqAbility[maxReq] = (int)prevSpellId;
|
|
||||||
++maxReq;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxReq == 2)
|
|
||||||
break;
|
|
||||||
|
|
||||||
var spellsRequired = Global.SpellMgr.GetSpellsRequiredForSpellBounds(tSpell.ReqAbility[i]);
|
|
||||||
for (var c = 0; c < spellsRequired.Count && maxReq < SharedConst.MaxTrainerspellAbilityReqs; ++c)
|
|
||||||
{
|
|
||||||
spell.ReqAbility[maxReq] = (int)spellsRequired[c];
|
|
||||||
++maxReq;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxReq == 2)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
packet.Spells.Add(spell);
|
|
||||||
}
|
|
||||||
|
|
||||||
SendPacket(packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[WorldPacketHandler(ClientOpcodes.TrainerBuySpell)]
|
[WorldPacketHandler(ClientOpcodes.TrainerBuySpell)]
|
||||||
void HandleTrainerBuySpell(TrainerBuySpell packet)
|
void HandleTrainerBuySpell(TrainerBuySpell packet)
|
||||||
{
|
{
|
||||||
Creature trainer = _player.GetNPCIfCanInteractWith(packet.TrainerGUID, NPCFlags.Trainer);
|
Creature npc = _player.GetNPCIfCanInteractWith(packet.TrainerGUID, NPCFlags.Trainer);
|
||||||
if (trainer == null)
|
if (npc == null)
|
||||||
{
|
{
|
||||||
Log.outDebug(LogFilter.Network, "WORLD: HandleTrainerBuySpell - {0} not found or you can not interact with him.", packet.TrainerGUID.ToString());
|
Log.outDebug(LogFilter.Network, $"WORLD: HandleTrainerBuySpell - {packet.TrainerGUID.ToString()} not found or you can not interact with him.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,64 +97,18 @@ namespace Game
|
|||||||
if (_player.HasUnitState(UnitState.Died))
|
if (_player.HasUnitState(UnitState.Died))
|
||||||
_player.RemoveAurasByType(AuraType.FeignDeath);
|
_player.RemoveAurasByType(AuraType.FeignDeath);
|
||||||
|
|
||||||
// check race for mount trainers
|
if (_player.GetCurrentTrainerId() != packet.TrainerID)
|
||||||
if (trainer.GetCreatureTemplate().TrainerType == TrainerType.Mounts)
|
|
||||||
{
|
|
||||||
Race trainerRace = trainer.GetCreatureTemplate().TrainerRace;
|
|
||||||
if (trainerRace != 0)
|
|
||||||
if (_player.GetRace() != trainerRace)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check class for class trainers
|
|
||||||
if (_player.GetClass() != trainer.GetCreatureTemplate().TrainerClass && trainer.GetCreatureTemplate().TrainerType == TrainerType.Class)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// check present spell in trainer spell list
|
// check present spell in trainer spell list
|
||||||
var trainerSpells = trainer.GetTrainerSpells();
|
Trainer trainer = Global.ObjectMgr.GetTrainer(packet.TrainerID);
|
||||||
if (trainerSpells == null)
|
if (trainer == null)
|
||||||
{
|
|
||||||
SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 0);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var trainerSpell = trainerSpells.spellList.LookupByKey(packet.SpellID);
|
trainer.TeachSpell(npc, _player, packet.SpellID);
|
||||||
if (trainerSpell == null)
|
|
||||||
{
|
|
||||||
SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// can't be learn, cheat? Or double learn with lags...
|
|
||||||
if (_player.GetTrainerSpellState(trainerSpell) != TrainerSpellState.Green)
|
|
||||||
{
|
|
||||||
SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply reputation discount
|
|
||||||
uint nSpellCost = (uint)(Math.Floor((double)trainerSpell.MoneyCost) * _player.GetReputationPriceDiscount(trainer));
|
|
||||||
|
|
||||||
// check money requirement
|
|
||||||
if (!_player.HasEnoughMoney(nSpellCost))
|
|
||||||
{
|
|
||||||
SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_player.ModifyMoney(-nSpellCost);
|
|
||||||
|
|
||||||
trainer.SendPlaySpellVisualKit(179, 0, 0); // 53 SpellCastDirected
|
|
||||||
_player.SendPlaySpellVisualKit(362, 1, 0); // 113 EmoteSalute
|
|
||||||
|
|
||||||
// learn explicitly or cast explicitly
|
|
||||||
if (trainerSpell.IsCastable())
|
|
||||||
_player.CastSpell(GetPlayer(), trainerSpell.SpellID, true);
|
|
||||||
else
|
|
||||||
_player.LearnSpell(packet.SpellID, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendTrainerBuyFailed(ObjectGuid trainerGUID, uint spellID, uint trainerFailedReason)
|
void SendTrainerBuyFailed(ObjectGuid trainerGUID, uint spellID, TrainerFailReason trainerFailedReason)
|
||||||
{
|
{
|
||||||
TrainerBuyFailed trainerBuyFailed = new TrainerBuyFailed();
|
TrainerBuyFailed trainerBuyFailed = new TrainerBuyFailed();
|
||||||
trainerBuyFailed.TrainerGUID = trainerGUID;
|
trainerBuyFailed.TrainerGUID = trainerGUID;
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ namespace Game
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!unit.CanResetTalents(_player))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_player.PlayerTalkClass.GetGossipMenu().HasMenuItemType((uint)GossipOption.Unlearntalents))
|
||||||
|
return;
|
||||||
|
|
||||||
// remove fake death
|
// remove fake death
|
||||||
if (GetPlayer().HasUnitState(UnitState.Died))
|
if (GetPlayer().HasUnitState(UnitState.Died))
|
||||||
GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
|
GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
|
||||||
|
|||||||
@@ -170,11 +170,11 @@ namespace Game.Network.Packets
|
|||||||
_worldPacket.WriteString(Greeting);
|
_worldPacket.WriteString(Greeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Greeting;
|
|
||||||
public int TrainerType = 0;
|
|
||||||
public ObjectGuid TrainerGUID;
|
public ObjectGuid TrainerGUID;
|
||||||
|
public int TrainerType;
|
||||||
public int TrainerID = 1;
|
public int TrainerID = 1;
|
||||||
public List<TrainerListSpell> Spells = new List<TrainerListSpell>();
|
public List<TrainerListSpell> Spells = new List<TrainerListSpell>();
|
||||||
|
public string Greeting;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ShowBank : ServerPacket
|
public class ShowBank : ServerPacket
|
||||||
@@ -276,7 +276,7 @@ namespace Game.Network.Packets
|
|||||||
|
|
||||||
public ObjectGuid TrainerGUID;
|
public ObjectGuid TrainerGUID;
|
||||||
public uint SpellID;
|
public uint SpellID;
|
||||||
public uint TrainerFailedReason;
|
public TrainerFailReason TrainerFailedReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RequestStabledPets : ClientPacket
|
class RequestStabledPets : ClientPacket
|
||||||
@@ -344,11 +344,11 @@ namespace Game.Network.Packets
|
|||||||
|
|
||||||
public class TrainerListSpell
|
public class TrainerListSpell
|
||||||
{
|
{
|
||||||
public int SpellID;
|
public uint SpellID;
|
||||||
public int MoneyCost;
|
public uint MoneyCost;
|
||||||
public int ReqSkillLine;
|
public uint ReqSkillLine;
|
||||||
public int ReqSkillRank;
|
public uint ReqSkillRank;
|
||||||
public int[] ReqAbility = new int[SharedConst.MaxTrainerspellAbilityReqs];
|
public uint[] ReqAbility = new uint[SharedConst.MaxTrainerspellAbilityReqs];
|
||||||
public TrainerSpellState Usable;
|
public TrainerSpellState Usable;
|
||||||
public byte ReqLevel;
|
public byte ReqLevel;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -702,17 +702,17 @@ namespace Game
|
|||||||
Log.outInfo(LogFilter.ServerLoading, "Loading GameTeleports...");
|
Log.outInfo(LogFilter.ServerLoading, "Loading GameTeleports...");
|
||||||
Global.ObjectMgr.LoadGameTele();
|
Global.ObjectMgr.LoadGameTele();
|
||||||
|
|
||||||
|
Log.outInfo(LogFilter.ServerLoading, "Loading Trainers...");
|
||||||
|
Global.ObjectMgr.LoadTrainers(); // must be after load CreatureTemplate
|
||||||
|
|
||||||
Log.outInfo(LogFilter.ServerLoading, "Loading Gossip menu...");
|
Log.outInfo(LogFilter.ServerLoading, "Loading Gossip menu...");
|
||||||
Global.ObjectMgr.LoadGossipMenu();
|
Global.ObjectMgr.LoadGossipMenu();
|
||||||
|
|
||||||
Log.outInfo(LogFilter.ServerLoading, "Loading Gossip menu options...");
|
Log.outInfo(LogFilter.ServerLoading, "Loading Gossip menu options...");
|
||||||
Global.ObjectMgr.LoadGossipMenuItems();
|
Global.ObjectMgr.LoadGossipMenuItems(); // must be after LoadTrainers
|
||||||
|
|
||||||
Log.outInfo(LogFilter.ServerLoading, "Loading Vendors...");
|
Log.outInfo(LogFilter.ServerLoading, "Loading Vendors...");
|
||||||
Global.ObjectMgr.LoadVendors(); // must be after load CreatureTemplate and ItemTemplate
|
Global.ObjectMgr.LoadVendors(); // must be after load CreatureTemplate and ItemTemplate
|
||||||
|
|
||||||
Log.outInfo(LogFilter.ServerLoading, "Loading Trainers...");
|
|
||||||
Global.ObjectMgr.LoadTrainerSpell();
|
|
||||||
|
|
||||||
Log.outInfo(LogFilter.ServerLoading, "Loading Waypoints...");
|
Log.outInfo(LogFilter.ServerLoading, "Loading Waypoints...");
|
||||||
Global.WaypointMgr.Load();
|
Global.WaypointMgr.Load();
|
||||||
|
|||||||
@@ -1699,21 +1699,6 @@ namespace Scripts.World.NpcSpecial
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Script]
|
|
||||||
class npc_pet_trainer : ScriptedAI
|
|
||||||
{
|
|
||||||
public npc_pet_trainer(Creature creature) : base(creature) { }
|
|
||||||
|
|
||||||
public override void sGossipSelect(Player player, uint menuId, uint gossipListId)
|
|
||||||
{
|
|
||||||
if (menuId == GossipMenus.MenuIdPetUnlearn && gossipListId == GossipMenus.OptionIdPleaseDo)
|
|
||||||
{
|
|
||||||
player.ResetPetTalents();
|
|
||||||
player.CLOSE_GOSSIP_MENU();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Script]
|
[Script]
|
||||||
class npc_experience : CreatureScript
|
class npc_experience : CreatureScript
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user