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,
|
||||
CommandReloadMillingLootTemplate = 670,
|
||||
CommandReloadNpcSpellclickSpells = 671,
|
||||
CommandReloadNpcTrainer = 672,
|
||||
CommandReloadTrainer = 672,
|
||||
CommandReloadNpcVendor = 673,
|
||||
CommandReloadPageText = 674,
|
||||
CommandReloadPickpocketingLootTemplate = 675,
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Framework.Constants
|
||||
Stablepet = 14, //Unit_Npc_Flag_Stable (4194304)
|
||||
Armorer = 15, //Unit_Npc_Flag_Armorer (4096)
|
||||
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)
|
||||
Outdoorpvp = 19, //Added By Code (Option For Outdoor Pvp Creatures)
|
||||
Max
|
||||
|
||||
@@ -571,17 +571,22 @@ namespace Framework.Constants
|
||||
|
||||
public enum TrainerType
|
||||
{
|
||||
Class = 0,
|
||||
Mounts = 1,
|
||||
None = 0,
|
||||
Talent = 1,
|
||||
Tradeskills = 2,
|
||||
Pets = 3
|
||||
}
|
||||
public enum TrainerSpellState
|
||||
{
|
||||
Gray = 0,
|
||||
Green = 1,
|
||||
Red = 2,
|
||||
GreenDisabled = 10
|
||||
Known = 0,
|
||||
Available = 1,
|
||||
Unavailable = 2,
|
||||
}
|
||||
|
||||
public enum TrainerFailReason
|
||||
{
|
||||
Unavailable = 0,
|
||||
NotEnoughMoney = 1
|
||||
}
|
||||
|
||||
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.DEL_CREATURE, "DELETE FROM creature WHERE guid = ?");
|
||||
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_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_");
|
||||
|
||||
@@ -527,15 +527,6 @@ namespace Game.Chat
|
||||
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)]
|
||||
static bool HandleReloadNpcVendorCommand(StringArguments args, CommandHandler handler)
|
||||
{
|
||||
@@ -885,6 +876,17 @@ namespace Game.Chat
|
||||
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)]
|
||||
static bool HandleReloadVehicleAccessoryCommand(StringArguments args, CommandHandler handler)
|
||||
{
|
||||
@@ -1051,7 +1053,7 @@ namespace Game.Chat
|
||||
{
|
||||
if (args != null) // will be reloaded from all_gossips
|
||||
{
|
||||
HandleReloadNpcTrainerCommand(null, handler);
|
||||
HandleReloadTrainerCommand(null, handler);
|
||||
HandleReloadNpcVendorCommand(null, handler);
|
||||
HandleReloadPointsOfInterestCommand(null, handler);
|
||||
HandleReloadSpellClickSpellsCommand(null, handler);
|
||||
|
||||
@@ -860,11 +860,9 @@ namespace Game.Entities
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool isCanTrainingAndResetTalentsOf(Player player)
|
||||
public bool CanResetTalents(Player player)
|
||||
{
|
||||
return player.getLevel() >= 10
|
||||
&& GetCreatureTemplate().TrainerType == TrainerType.Class
|
||||
&& player.GetClass() == GetCreatureTemplate().TrainerClass;
|
||||
return player.getLevel() >= 15 && player.GetClass() == GetCreatureTemplate().TrainerClass;
|
||||
}
|
||||
|
||||
public void SetTextRepeatId(byte textGroup, byte id)
|
||||
@@ -2457,11 +2455,6 @@ namespace Game.Entities
|
||||
return vCount.count;
|
||||
}
|
||||
|
||||
public TrainerSpellData GetTrainerSpells()
|
||||
{
|
||||
return Global.ObjectMgr.GetNpcTrainerSpells(GetEntry());
|
||||
}
|
||||
|
||||
public override string GetName(LocaleConstant locale_idx = LocaleConstant.enUS)
|
||||
{
|
||||
if (locale_idx != LocaleConstant.enUS)
|
||||
|
||||
@@ -58,9 +58,7 @@ namespace Game.Entities
|
||||
public uint UnitFlags3;
|
||||
public uint DynamicFlags;
|
||||
public CreatureFamily Family;
|
||||
public TrainerType TrainerType;
|
||||
public Class TrainerClass;
|
||||
public Race TrainerRace;
|
||||
public CreatureType CreatureType;
|
||||
public CreatureTypeFlags TypeFlags;
|
||||
public uint TypeFlags2;
|
||||
@@ -386,38 +384,4 @@ namespace Game.Entities
|
||||
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 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);
|
||||
|
||||
// Find a free new id - script case
|
||||
if (menuItemId == -1)
|
||||
if (optionIndex == -1)
|
||||
{
|
||||
menuItemId = 0;
|
||||
optionIndex = 0;
|
||||
if (!_menuItems.Empty())
|
||||
{
|
||||
foreach (var item in _menuItems)
|
||||
{
|
||||
if (item.Key > menuItemId)
|
||||
if (item.Key > optionIndex)
|
||||
break;
|
||||
|
||||
menuItemId = (int)item.Key + 1;
|
||||
optionIndex = (int)item.Key + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,8 @@ namespace Game.Misc
|
||||
menuItem.BoxMessage = boxMessage;
|
||||
menuItem.BoxMoney = boxMoney;
|
||||
|
||||
_menuItems[(uint)menuItemId] = menuItem;
|
||||
_menuItems[(uint)optionIndex] = menuItem;
|
||||
return (uint)optionIndex;
|
||||
}
|
||||
|
||||
/// <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.
|
||||
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();
|
||||
|
||||
itemData.GossipActionMenuId = gossipActionMenuId;
|
||||
itemData.GossipActionPoi = gossipActionPoi;
|
||||
itemData.TrainerId = trainerId;
|
||||
|
||||
_menuItemData[menuItemId] = itemData;
|
||||
_menuItemData[optionIndex] = itemData;
|
||||
}
|
||||
|
||||
public uint GetMenuItemSender(uint menuItemId)
|
||||
@@ -162,6 +165,15 @@ namespace Game.Misc
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasMenuItemType(uint optionType)
|
||||
{
|
||||
foreach (var menuItemPair in _menuItems)
|
||||
if (menuItemPair.Value.OptionType == optionType)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ClearMenu()
|
||||
{
|
||||
_menuItems.Clear();
|
||||
@@ -796,6 +808,7 @@ namespace Game.Misc
|
||||
{
|
||||
public uint GossipActionMenuId; // MenuId of the gossip triggered by this action
|
||||
public uint GossipActionPoi;
|
||||
public uint TrainerId;
|
||||
}
|
||||
|
||||
public struct NpcTextData
|
||||
@@ -829,6 +842,7 @@ namespace Game.Misc
|
||||
public uint BoxMoney;
|
||||
public string BoxText;
|
||||
public uint BoxBroadcastTextId;
|
||||
public uint TrainerId;
|
||||
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;
|
||||
|
||||
uint _currentTrainerId;
|
||||
|
||||
//Combat
|
||||
int[] baseRatingValue = new int[(int)CombatRating.Max];
|
||||
public float[][] m_auraBaseMod = new float[(int)BaseModGroup.End][];
|
||||
|
||||
@@ -1034,6 +1034,9 @@ namespace Game.Entities
|
||||
|
||||
public RestMgr GetRestMgr() { return _restMgr; }
|
||||
|
||||
public uint GetCurrentTrainerId() { return _currentTrainerId; }
|
||||
public void SetCurrentTrainerId(uint trainerId) { _currentTrainerId = trainerId; }
|
||||
|
||||
public bool IsAdvancedCombatLoggingEnabled() { return _advancedCombatLoggingEnabled; }
|
||||
public void SetAdvancedCombatLogging(bool enabled) { _advancedCombatLoggingEnabled = enabled; }
|
||||
|
||||
@@ -1093,26 +1096,6 @@ namespace Game.Entities
|
||||
|
||||
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()
|
||||
{
|
||||
@@ -2352,10 +2335,10 @@ namespace Game.Entities
|
||||
|
||||
foreach (var menuItems in menuItemBounds)
|
||||
{
|
||||
bool canTalk = true;
|
||||
if (!Global.ConditionMgr.IsObjectMeetToConditions(this, source, menuItems.Conditions))
|
||||
continue;
|
||||
|
||||
bool canTalk = true;
|
||||
GameObject go = source.ToGameObject();
|
||||
Creature creature = source.ToCreature();
|
||||
if (creature)
|
||||
@@ -2384,11 +2367,7 @@ namespace Game.Entities
|
||||
canTalk = false;
|
||||
break;
|
||||
case GossipOption.Unlearntalents:
|
||||
if (!creature.isCanTrainingAndResetTalentsOf(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)
|
||||
if (!creature.CanResetTalents(this))
|
||||
canTalk = false;
|
||||
break;
|
||||
case GossipOption.Taxivendor:
|
||||
@@ -2407,11 +2386,6 @@ namespace Game.Entities
|
||||
canTalk = false;
|
||||
break;
|
||||
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.Spiritguide:
|
||||
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().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);
|
||||
break;
|
||||
case GossipOption.Trainer:
|
||||
GetSession().SendTrainerList(guid);
|
||||
GetSession().SendTrainerList(guid, menuItemData.TrainerId);
|
||||
break;
|
||||
case GossipOption.Learndualspec:
|
||||
break;
|
||||
@@ -2588,10 +2562,6 @@ namespace Game.Entities
|
||||
PlayerTalkClass.SendCloseGossip();
|
||||
SendRespecWipeConfirm(guid, GetNextResetTalentsCost());
|
||||
break;
|
||||
case GossipOption.Unlearnpettalents:
|
||||
PlayerTalkClass.SendCloseGossip();
|
||||
ResetPetTalents();
|
||||
break;
|
||||
case GossipOption.Taxivendor:
|
||||
GetSession().SendTaxiMenu(source.ToCreature());
|
||||
break;
|
||||
@@ -5484,76 +5454,6 @@ namespace Game.Entities
|
||||
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()
|
||||
{
|
||||
if (!m_teleport_options.HasAnyFlag(TeleportToOptions.Seamless))
|
||||
|
||||
@@ -148,6 +148,7 @@
|
||||
<Compile Include="Entities\AreaTrigger\AreaTriggerTemplate.cs" />
|
||||
<Compile Include="Entities\Conversation.cs" />
|
||||
<Compile Include="Entities\Creature\CreatureData.cs" />
|
||||
<Compile Include="Entities\Creature\Trainer.cs" />
|
||||
<Compile Include="Entities\GameObject\GameObjectData.cs" />
|
||||
<Compile Include="Entities\Player\CinematicManager.cs" />
|
||||
<Compile Include="Entities\Player\CollectionManager.cs" />
|
||||
|
||||
+171
-165
@@ -631,11 +631,16 @@ namespace Game
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
|
||||
gossipMenuItemsStorage.Clear();
|
||||
|
||||
SQLResult result = DB.World.Query(
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
"SELECT menu_id, id, option_icon, option_text, OptionBroadcastTextID, option_id, npc_option_npcflag, action_menu_id, action_poi_id, box_coded, box_money, box_text, BoxBroadcastTextID " +
|
||||
"FROM gossip_menu_option ORDER BY menu_id, id");
|
||||
|
||||
// 0 1 2 3 4 5 6
|
||||
SQLResult result = DB.World.Query("SELECT o.MenuId, o.OptionIndex, o.OptionIcon, o.OptionText, o.OptionBroadcastTextId, o.OptionType, o.OptionNpcflag, " +
|
||||
// 7 8 9 10 11 12 13
|
||||
"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())
|
||||
{
|
||||
@@ -650,7 +655,7 @@ namespace Game
|
||||
|
||||
gMenuItem.MenuId = result.Read<uint>(0);
|
||||
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.OptionBroadcastTextId = result.Read<uint>(4);
|
||||
gMenuItem.OptionType = (GossipOption)result.Read<uint>(5);
|
||||
@@ -661,10 +666,11 @@ namespace Game
|
||||
gMenuItem.BoxMoney = result.Read<uint>(10);
|
||||
gMenuItem.BoxText = result.Read<string>(11);
|
||||
gMenuItem.BoxBroadcastTextId = result.Read<uint>(12);
|
||||
gMenuItem.TrainerId = result.Read<uint>(13);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -672,17 +678,17 @@ namespace Game
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -690,11 +696,17 @@ namespace Game
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
++count;
|
||||
|
||||
@@ -1703,15 +1715,15 @@ namespace Game
|
||||
"modelid4, name, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, " +
|
||||
//20 21 22 23 24 25 26 27 28 29 30
|
||||
"faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, " +
|
||||
//31 32 33 34 35 36 37 38 39 40
|
||||
"unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_type, trainer_class, trainer_race, type, " +
|
||||
//41 42 43 44 45 46 47 48 49 50 51
|
||||
//31 32 33 34 35 36 37 38
|
||||
"unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_class, type, " +
|
||||
//39 40 41 42 43 44 45 46 47 48 49
|
||||
"type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, " +
|
||||
//52 53 54 55 56 57 58 59 60 61 62 63 64
|
||||
//50 51 52 53 54 55 56 57 58 59 60 61 62
|
||||
"spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, " +
|
||||
//65 66 67 68 69 70 71 72 73
|
||||
//63 64 65 66 67 68 69 70 71
|
||||
"InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, " +
|
||||
//74 75 76 77 78 79
|
||||
//72 73 74 75 76 77
|
||||
"RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template");
|
||||
|
||||
if (result.IsEmpty())
|
||||
@@ -1777,42 +1789,40 @@ namespace Game
|
||||
creature.UnitFlags3 = fields.Read<uint>(34);
|
||||
creature.DynamicFlags = fields.Read<uint>(35);
|
||||
creature.Family = (CreatureFamily)fields.Read<byte>(36);
|
||||
creature.TrainerType = (TrainerType)fields.Read<byte>(37);
|
||||
creature.TrainerClass = (Class)fields.Read<byte>(38);
|
||||
creature.TrainerRace = (Race)fields.Read<byte>(39);
|
||||
creature.CreatureType = (CreatureType)fields.Read<uint>(40);
|
||||
creature.TypeFlags = (CreatureTypeFlags)fields.Read<uint>(41);
|
||||
creature.TypeFlags2 = fields.Read<uint>(42);
|
||||
creature.LootId = fields.Read<uint>(43);
|
||||
creature.PickPocketId = fields.Read<uint>(44);
|
||||
creature.SkinLootId = fields.Read<uint>(45);
|
||||
creature.TrainerClass = (Class)fields.Read<byte>(37);
|
||||
creature.CreatureType = (CreatureType)fields.Read<uint>(38);
|
||||
creature.TypeFlags = (CreatureTypeFlags)fields.Read<uint>(39);
|
||||
creature.TypeFlags2 = fields.Read<uint>(40);
|
||||
creature.LootId = fields.Read<uint>(41);
|
||||
creature.PickPocketId = fields.Read<uint>(42);
|
||||
creature.SkinLootId = fields.Read<uint>(43);
|
||||
|
||||
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)
|
||||
creature.Spells[i] = fields.Read<uint>(52 + i);
|
||||
creature.Spells[i] = fields.Read<uint>(50 + i);
|
||||
|
||||
creature.VehicleId = fields.Read<uint>(60);
|
||||
creature.MinGold = fields.Read<uint>(61);
|
||||
creature.MaxGold = fields.Read<uint>(62);
|
||||
creature.AIName = fields.Read<string>(63);
|
||||
creature.MovementType = fields.Read<uint>(64);
|
||||
creature.InhabitType = (InhabitType)fields.Read<uint>(65);
|
||||
creature.HoverHeight = fields.Read<float>(66);
|
||||
creature.ModHealth = fields.Read<float>(67);
|
||||
creature.ModHealthExtra = fields.Read<float>(68);
|
||||
creature.ModMana = fields.Read<float>(69);
|
||||
creature.ModManaExtra = fields.Read<float>(70);
|
||||
creature.ModArmor = fields.Read<float>(71);
|
||||
creature.ModDamage = fields.Read<float>(72);
|
||||
creature.ModExperience = fields.Read<float>(73);
|
||||
creature.RacialLeader = fields.Read<bool>(74);
|
||||
creature.MovementId = fields.Read<uint>(75);
|
||||
creature.RegenHealth = fields.Read<bool>(76);
|
||||
creature.MechanicImmuneMask = fields.Read<uint>(77);
|
||||
creature.FlagsExtra = (CreatureFlagsExtra)fields.Read<uint>(78);
|
||||
creature.ScriptID = GetScriptId(fields.Read<string>(79));
|
||||
creature.VehicleId = fields.Read<uint>(58);
|
||||
creature.MinGold = fields.Read<uint>(59);
|
||||
creature.MaxGold = fields.Read<uint>(60);
|
||||
creature.AIName = fields.Read<string>(61);
|
||||
creature.MovementType = fields.Read<uint>(62);
|
||||
creature.InhabitType = (InhabitType)fields.Read<uint>(63);
|
||||
creature.HoverHeight = fields.Read<float>(64);
|
||||
creature.ModHealth = fields.Read<float>(65);
|
||||
creature.ModHealthExtra = fields.Read<float>(66);
|
||||
creature.ModMana = fields.Read<float>(67);
|
||||
creature.ModManaExtra = fields.Read<float>(68);
|
||||
creature.ModArmor = fields.Read<float>(69);
|
||||
creature.ModDamage = fields.Read<float>(70);
|
||||
creature.ModExperience = fields.Read<float>(71);
|
||||
creature.RacialLeader = fields.Read<bool>(72);
|
||||
creature.MovementId = fields.Read<uint>(73);
|
||||
creature.RegenHealth = fields.Read<bool>(74);
|
||||
creature.MechanicImmuneMask = fields.Read<uint>(75);
|
||||
creature.FlagsExtra = (CreatureFlagsExtra)fields.Read<uint>(76);
|
||||
creature.ScriptID = GetScriptId(fields.Read<string>(77));
|
||||
|
||||
creatureTemplateStorage.Add(entry, creature);
|
||||
}
|
||||
@@ -2382,18 +2392,6 @@ namespace Game
|
||||
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)
|
||||
{
|
||||
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)
|
||||
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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
public void LoadTrainerSpell()
|
||||
public void LoadTrainers()
|
||||
{
|
||||
var time = 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");
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
|
||||
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!");
|
||||
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)
|
||||
do
|
||||
{
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
TrainerSpell spell = new TrainerSpell();
|
||||
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)
|
||||
{
|
||||
SpellInfo learnedSpellInfo = Global.SpellMgr.GetSpellInfo(trainerSpell.ReqAbility[effect.EffectIndex]);
|
||||
if (learnedSpellInfo != null && learnedSpellInfo.IsProfession())
|
||||
data.trainerType = 2;
|
||||
}
|
||||
if (spell.ReqSkillLine != 0 && !CliDB.SkillLineStorage.ContainsKey(spell.ReqSkillLine))
|
||||
{
|
||||
Log.outError(LogFilter.Sql, $"Table `trainer_spell` references non-existing skill (ReqSkillLine: {spell.ReqSkillLine}) for TrainerId {spell.SpellId} and SpellId {trainerId}, ignoring");
|
||||
continue;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
@@ -3488,10 +3494,6 @@ namespace Game
|
||||
{
|
||||
return creatureModelStorage.LookupByKey(modelId);
|
||||
}
|
||||
public TrainerSpellData GetNpcTrainerSpells(uint entry)
|
||||
{
|
||||
return cacheTrainerSpellStorage.LookupByKey(entry);
|
||||
}
|
||||
public NpcText GetNpcText(uint textId)
|
||||
{
|
||||
return _npcTextStorage.LookupByKey(textId);
|
||||
@@ -4585,6 +4587,10 @@ namespace Game
|
||||
{
|
||||
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)
|
||||
{
|
||||
VendorItemData vList = cacheVendorItemStorage[entry];
|
||||
@@ -9095,7 +9101,7 @@ namespace Game
|
||||
Dictionary<ObjectGuid, ObjectGuid> linkedRespawnStorage = new Dictionary<ObjectGuid, ObjectGuid>();
|
||||
Dictionary<uint, CreatureBaseStats> creatureBaseStatsStorage = new Dictionary<uint, CreatureBaseStats>();
|
||||
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>[] _hasDifficultyEntries = new List<uint>[SharedConst.MaxCreatureDifficulties]; // already loaded creatures with difficulty 1 values, used in CheckCreatureTemplate
|
||||
Dictionary<uint, NpcText> _npcTextStorage = new Dictionary<uint, NpcText>();
|
||||
|
||||
+16
-135
@@ -56,20 +56,15 @@ namespace Game
|
||||
[WorldPacketHandler(ClientOpcodes.TrainerList)]
|
||||
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)
|
||||
{
|
||||
string str = Global.ObjectMgr.GetCypherString(CypherStrings.NpcTainerHello);
|
||||
SendTrainerList(guid, str, index);
|
||||
}
|
||||
void SendTrainerList(ObjectGuid guid, string title, uint index = 0)
|
||||
public void SendTrainerList(ObjectGuid guid, uint trainerId)
|
||||
{
|
||||
Creature unit = GetPlayer().GetNPCIfCanInteractWith(guid, NPCFlags.Trainer);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -77,92 +72,24 @@ namespace Game
|
||||
if (GetPlayer().HasUnitState(UnitState.Died))
|
||||
GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
|
||||
|
||||
TrainerSpellData trainer_spells = unit.GetTrainerSpells();
|
||||
if (trainer_spells == null)
|
||||
Trainer trainer = Global.ObjectMgr.GetTrainer(trainerId);
|
||||
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;
|
||||
}
|
||||
|
||||
TrainerList packet = new TrainerList();
|
||||
packet.TrainerGUID = guid;
|
||||
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);
|
||||
_player.SetCurrentTrainerId(trainerId);
|
||||
trainer.SendSpells(unit, _player, GetSessionDbLocaleIndex());
|
||||
}
|
||||
|
||||
[WorldPacketHandler(ClientOpcodes.TrainerBuySpell)]
|
||||
void HandleTrainerBuySpell(TrainerBuySpell packet)
|
||||
{
|
||||
Creature trainer = _player.GetNPCIfCanInteractWith(packet.TrainerGUID, NPCFlags.Trainer);
|
||||
if (trainer == null)
|
||||
Creature npc = _player.GetNPCIfCanInteractWith(packet.TrainerGUID, NPCFlags.Trainer);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -170,64 +97,18 @@ namespace Game
|
||||
if (_player.HasUnitState(UnitState.Died))
|
||||
_player.RemoveAurasByType(AuraType.FeignDeath);
|
||||
|
||||
// check race for mount trainers
|
||||
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)
|
||||
if (_player.GetCurrentTrainerId() != packet.TrainerID)
|
||||
return;
|
||||
|
||||
// check present spell in trainer spell list
|
||||
var trainerSpells = trainer.GetTrainerSpells();
|
||||
if (trainerSpells == null)
|
||||
{
|
||||
SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 0);
|
||||
Trainer trainer = Global.ObjectMgr.GetTrainer(packet.TrainerID);
|
||||
if (trainer == null)
|
||||
return;
|
||||
}
|
||||
|
||||
var trainerSpell = trainerSpells.spellList.LookupByKey(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);
|
||||
trainer.TeachSpell(npc, _player, packet.SpellID);
|
||||
}
|
||||
|
||||
void SendTrainerBuyFailed(ObjectGuid trainerGUID, uint spellID, uint trainerFailedReason)
|
||||
void SendTrainerBuyFailed(ObjectGuid trainerGUID, uint spellID, TrainerFailReason trainerFailedReason)
|
||||
{
|
||||
TrainerBuyFailed trainerBuyFailed = new TrainerBuyFailed();
|
||||
trainerBuyFailed.TrainerGUID = trainerGUID;
|
||||
|
||||
@@ -68,6 +68,12 @@ namespace Game
|
||||
return;
|
||||
}
|
||||
|
||||
if (!unit.CanResetTalents(_player))
|
||||
return;
|
||||
|
||||
if (!_player.PlayerTalkClass.GetGossipMenu().HasMenuItemType((uint)GossipOption.Unlearntalents))
|
||||
return;
|
||||
|
||||
// remove fake death
|
||||
if (GetPlayer().HasUnitState(UnitState.Died))
|
||||
GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
|
||||
|
||||
@@ -170,11 +170,11 @@ namespace Game.Network.Packets
|
||||
_worldPacket.WriteString(Greeting);
|
||||
}
|
||||
|
||||
public string Greeting;
|
||||
public int TrainerType = 0;
|
||||
public ObjectGuid TrainerGUID;
|
||||
public int TrainerType;
|
||||
public int TrainerID = 1;
|
||||
public List<TrainerListSpell> Spells = new List<TrainerListSpell>();
|
||||
public string Greeting;
|
||||
}
|
||||
|
||||
public class ShowBank : ServerPacket
|
||||
@@ -276,7 +276,7 @@ namespace Game.Network.Packets
|
||||
|
||||
public ObjectGuid TrainerGUID;
|
||||
public uint SpellID;
|
||||
public uint TrainerFailedReason;
|
||||
public TrainerFailReason TrainerFailedReason;
|
||||
}
|
||||
|
||||
class RequestStabledPets : ClientPacket
|
||||
@@ -344,11 +344,11 @@ namespace Game.Network.Packets
|
||||
|
||||
public class TrainerListSpell
|
||||
{
|
||||
public int SpellID;
|
||||
public int MoneyCost;
|
||||
public int ReqSkillLine;
|
||||
public int ReqSkillRank;
|
||||
public int[] ReqAbility = new int[SharedConst.MaxTrainerspellAbilityReqs];
|
||||
public uint SpellID;
|
||||
public uint MoneyCost;
|
||||
public uint ReqSkillLine;
|
||||
public uint ReqSkillRank;
|
||||
public uint[] ReqAbility = new uint[SharedConst.MaxTrainerspellAbilityReqs];
|
||||
public TrainerSpellState Usable;
|
||||
public byte ReqLevel;
|
||||
}
|
||||
|
||||
@@ -702,17 +702,17 @@ namespace Game
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loading GameTeleports...");
|
||||
Global.ObjectMgr.LoadGameTele();
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loading Trainers...");
|
||||
Global.ObjectMgr.LoadTrainers(); // must be after load CreatureTemplate
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loading Gossip menu...");
|
||||
Global.ObjectMgr.LoadGossipMenu();
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loading Gossip menu options...");
|
||||
Global.ObjectMgr.LoadGossipMenuItems();
|
||||
Global.ObjectMgr.LoadGossipMenuItems(); // must be after LoadTrainers
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loading Vendors...");
|
||||
Global.ObjectMgr.LoadVendors(); // must be after load CreatureTemplate and ItemTemplate
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loading Trainers...");
|
||||
Global.ObjectMgr.LoadTrainerSpell();
|
||||
Global.ObjectMgr.LoadVendors(); // must be after load CreatureTemplate and ItemTemplate
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loading Waypoints...");
|
||||
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]
|
||||
class npc_experience : CreatureScript
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user