Rewrite trainer handling to properly support multiple trainers on the same creature

This commit is contained in:
hondacrx
2017-07-27 17:29:22 -04:00
parent 0d8729ae32
commit 0aa7df2f53
18 changed files with 434 additions and 508 deletions
+1 -1
View File
@@ -578,7 +578,7 @@ namespace Framework.Constants
CommandReloadMailLootTemplate = 669,
CommandReloadMillingLootTemplate = 670,
CommandReloadNpcSpellclickSpells = 671,
CommandReloadNpcTrainer = 672,
CommandReloadTrainer = 672,
CommandReloadNpcVendor = 673,
CommandReloadPageText = 674,
CommandReloadPickpocketingLootTemplate = 675,
+1 -1
View File
@@ -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
+11 -6
View File
@@ -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_");
+12 -10
View File
@@ -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);
+2 -9
View File
@@ -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)
-36
View File
@@ -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);
}
}
}
+23 -9
View File
@@ -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>();
}
+167
View File
@@ -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);
}
}
+2
View File
@@ -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][];
+7 -107
View File
@@ -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))
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+6
View File
@@ -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);
+8 -8
View File
@@ -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;
}
+5 -5
View File
@@ -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();
-15
View File
@@ -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
{