Files
CypherCore/Game/Globals/ObjectManager.cs
T

9927 lines
460 KiB
C#

/*
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Framework.Collections;
using Framework.Constants;
using Framework.Database;
using Framework.GameMath;
using Game.Conditions;
using Game.DataStorage;
using Game.Entities;
using Game.Mails;
using Game.Maps;
using Game.Misc;
using Game.Scripting;
using Game.Spells;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Runtime.InteropServices;
namespace Game
{
public sealed class ObjectManager : Singleton<ObjectManager>
{
ObjectManager()
{
for (var i = 0; i < SharedConst.MaxCreatureDifficulties; ++i)
{
_difficultyEntries[i] = new List<uint>();
_hasDifficultyEntries[i] = new List<uint>();
}
lang_description = new LanguageDesc[]
{
new LanguageDesc(Language.Addon, 0, 0 ),
new LanguageDesc(Language.Universal, 0, 0 ),
new LanguageDesc(Language.Orcish, 669, SkillType.LangOrcish ),
new LanguageDesc(Language.Darnassian, 671, SkillType.LangDarnassian ),
new LanguageDesc(Language.Taurahe, 670, SkillType.LangTaurahe ),
new LanguageDesc(Language.Dwarvish, 672, SkillType.LangDwarven ),
new LanguageDesc(Language.Common, 668, SkillType.LangCommon ),
new LanguageDesc(Language.Demonic, 815, SkillType.LangDemonTongue ),
new LanguageDesc(Language.Titan, 816, SkillType.LangTitan ),
new LanguageDesc(Language.Thalassian, 813, SkillType.LangThalassian ),
new LanguageDesc(Language.Draconic, 814, SkillType.LangDraconic ),
new LanguageDesc(Language.Kalimag, 817, SkillType.LangOldTongue ),
new LanguageDesc(Language.Gnomish, 7340, SkillType.LangGnomish ),
new LanguageDesc(Language.Troll, 7341, SkillType.LangTroll ),
new LanguageDesc(Language.Gutterspeak, 17737, SkillType.LangForsaken ),
new LanguageDesc(Language.Draenei, 29932, SkillType.LangDraenei ),
new LanguageDesc(Language.Zombie, 0, 0 ),
new LanguageDesc(Language.GnomishBinary, 0, 0 ),
new LanguageDesc(Language.GoblinBinary, 0, 0 ),
new LanguageDesc(Language.Worgen, 69270, SkillType.LangGilnean ),
new LanguageDesc(Language.Goblin, 69269, SkillType.LangGoblin ),
new LanguageDesc(Language.PandarenNeutral, 108127, SkillType.LangPandarenNeutral ),
new LanguageDesc(Language.PandarenAlliance, 108130, SkillType.LangPandarenAlliance ),
new LanguageDesc(Language.PandarenHorde, 108131, SkillType.LangPandarenHorde ),
};
}
//Static Methods
public static bool NormalizePlayerName(ref string name)
{
if (name.IsEmpty())
return false;
//CultureInfo cultureInfo = Thread.CurrentThread.CurrentCulture;
//TextInfo textInfo = cultureInfo.TextInfo;
//str = textInfo.ToTitleCase(str);
name = name.ToLower();
var charArray = name.ToCharArray();
charArray[0] = char.ToUpper(charArray[0]);
name = new string(charArray);
return true;
}
public static ExtendedPlayerName ExtractExtendedPlayerName(string name)
{
int pos = name.IndexOf('-');
if (pos != -1)
return new ExtendedPlayerName(name.Substring(0, pos), name.Substring(pos + 1));
else
return new ExtendedPlayerName(name, "");
}
public static LanguageDesc GetLanguageDescByID(Language lang)
{
for (byte i = 0; i < lang_description.Length; ++i)
{
if (lang_description[i].lang_id == lang)
return lang_description[i];
}
return null;
}
static LanguageType GetRealmLanguageType(bool create)
{
switch ((RealmZones)WorldConfig.GetIntValue(WorldCfg.RealmZone))
{
case RealmZones.Unknown: // any language
case RealmZones.Development:
case RealmZones.TestServer:
case RealmZones.QaServer:
return LanguageType.Any;
case RealmZones.UnitedStates: // extended-Latin
case RealmZones.Oceanic:
case RealmZones.LatinAmerica:
case RealmZones.English:
case RealmZones.German:
case RealmZones.French:
case RealmZones.Spanish:
return LanguageType.ExtendenLatin;
case RealmZones.Korea: // East-Asian
case RealmZones.Taiwan:
case RealmZones.China:
return LanguageType.EastAsia;
case RealmZones.Russian: // Cyrillic
return LanguageType.Cyrillic;
default:
return create ? LanguageType.BasicLatin : LanguageType.Any; // basic-Latin at create, any at login
}
}
public static uint ChooseDisplayId(CreatureTemplate cinfo, CreatureData data = null)
{
// Load creature model (display id)
if (data != null && data.displayid != 0)
return data.displayid;
if (!cinfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.Trigger))
return cinfo.GetRandomValidModelId();
// Triggers by default receive the invisible model
return cinfo.GetFirstInvisibleModel();
}
public static void ChooseCreatureFlags(CreatureTemplate cInfo, out ulong npcFlag, out uint unitFlags, out uint unitFlags2, out uint unitFlags3, out uint dynamicFlags, CreatureData data = null)
{
npcFlag = (ulong)cInfo.Npcflag;
unitFlags = (uint)cInfo.UnitFlags;
unitFlags2 = cInfo.UnitFlags2;
unitFlags3 = cInfo.UnitFlags3;
dynamicFlags = cInfo.DynamicFlags;
if (data != null)
{
if (data.npcflag != 0)
npcFlag = data.npcflag;
if (data.unit_flags != 0)
unitFlags = data.unit_flags;
if (data.unit_flags2 != 0)
unitFlags2 = data.unit_flags2;
if (data.unit_flags3 != 0)
unitFlags3 = data.unit_flags3;
if (data.dynamicflags != 0)
dynamicFlags = data.dynamicflags;
}
}
public static ObjectGuid GetPlayerGUIDByName(string name)
{
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUID_BY_NAME);
stmt.AddValue(0, name);
SQLResult result = DB.Characters.Query(stmt);
if (!result.IsEmpty())
return ObjectGuid.Create(HighGuid.Player, result.Read<ulong>(0));
return ObjectGuid.Empty;
}
public static bool GetPlayerNameByGUID(ObjectGuid guid, out string name)
{
name = "";
Player player = Global.ObjAccessor.FindConnectedPlayer(guid);
if (player)
{
name = player.GetName();
return true;
}
CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(guid);
if (characterInfo == null)
return false;
name = characterInfo.Name;
return true;
}
public static bool GetPlayerNameAndClassByGUID(ObjectGuid guid, out string name, out byte _class)
{
name = "";
_class = 0;
Player player = Global.ObjAccessor.FindConnectedPlayer(guid);
if (player)
{
name = player.GetName();
_class = (byte)player.GetClass();
return true;
}
CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(guid);
if (characterInfo != null)
{
name = characterInfo.Name;
_class = (byte)characterInfo.ClassID;
return true;
}
return false;
}
public static Team GetPlayerTeamByGUID(ObjectGuid guid)
{
CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(guid);
if (characterInfo != null)
return Player.TeamForRace(characterInfo.RaceID);
return 0;
}
public static uint GetPlayerAccountIdByGUID(ObjectGuid guid)
{
CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(guid);
if (characterInfo != null)
return characterInfo.AccountId;
return 0;
}
public static ResponseCodes CheckPlayerName(string name, LocaleConstant locale, bool create = false)
{
if (name.Length > 12)
return ResponseCodes.CharNameTooLong;
uint minName = WorldConfig.GetUIntValue(WorldCfg.MinPlayerName);
if (name.Length < minName)
return ResponseCodes.CharNameTooShort;
uint strictMask = WorldConfig.GetUIntValue(WorldCfg.StrictPlayerNames);
if (!IsValidString(name, strictMask, false, create))
return ResponseCodes.CharNameMixedLanguages;
name = name.ToLower();
for (int i = 2; i < name.Length; ++i)
if (name[i] == name[i - 1] && name[i] == name[i - 2])
return ResponseCodes.CharNameThreeConsecutive;
return Global.DB2Mgr.ValidateName(name, locale);
}
public static PetNameInvalidReason CheckPetName(string name)
{
if (name.Length > 12)
return PetNameInvalidReason.TooLong;
uint minName = WorldConfig.GetUIntValue(WorldCfg.MinPetName);
if (name.Length < minName)
return PetNameInvalidReason.TooShort;
uint strictMask = WorldConfig.GetUIntValue(WorldCfg.StrictPetNames);
if (!IsValidString(name, strictMask, false))
return PetNameInvalidReason.MixedLanguages;
return PetNameInvalidReason.Success;
}
public static bool IsValidCharterName(string name)
{
if (name.Length > 24)
return false;
uint minName = WorldConfig.GetUIntValue(WorldCfg.MinCharterName);
if (name.Length < minName)
return false;
uint strictMask = WorldConfig.GetUIntValue(WorldCfg.StrictCharterNames);
return IsValidString(name, strictMask, true);
}
public static void AddLocaleString(string value, LocaleConstant locale, StringArray data)
{
if (!string.IsNullOrEmpty(value))
data[(int)locale] = value;
}
public static void GetLocaleString(StringArray data, LocaleConstant locale, ref string value)
{
if (data.Length > (int)locale && !string.IsNullOrEmpty(data[(int)locale]))
value = data[(int)locale];
}
static bool IsValidString(string str, uint strictMask, bool numericOrSpace, bool create = false)
{
if (strictMask == 0) // any language, ignore realm
{
if (IsCultureString(LanguageType.BasicLatin, str, numericOrSpace))
return true;
if (IsCultureString(LanguageType.ExtendenLatin, str, numericOrSpace))
return true;
if (IsCultureString(LanguageType.Cyrillic, str, numericOrSpace))
return true;
if (IsCultureString(LanguageType.EastAsia, str, numericOrSpace))
return true;
return false;
}
if (Convert.ToBoolean(strictMask & 0x2)) // realm zone specific
{
LanguageType lt = GetRealmLanguageType(create);
if (lt.HasAnyFlag(LanguageType.ExtendenLatin))
{
if (IsCultureString(LanguageType.BasicLatin, str, numericOrSpace))
return true;
if (IsCultureString(LanguageType.ExtendenLatin, str, numericOrSpace))
return true;
}
if (lt.HasAnyFlag(LanguageType.Cyrillic))
if (IsCultureString(LanguageType.Cyrillic, str, numericOrSpace))
return true;
if (lt.HasAnyFlag(LanguageType.EastAsia))
if (IsCultureString(LanguageType.EastAsia, str, numericOrSpace))
return true;
}
if (Convert.ToBoolean(strictMask & 0x1)) // basic Latin
{
if (IsCultureString(LanguageType.BasicLatin, str, numericOrSpace))
return true;
}
return false;
}
static bool IsCultureString(LanguageType culture, string str, bool numericOrSpace)
{
foreach (var wchar in str)
{
if (numericOrSpace && (char.IsNumber(wchar) || char.IsWhiteSpace(wchar)))
return true;
switch (culture)
{
case LanguageType.BasicLatin:
if (wchar >= 'a' && wchar <= 'z') // LATIN SMALL LETTER A - LATIN SMALL LETTER Z
return true;
if (wchar >= 'A' && wchar <= 'Z') // LATIN CAPITAL LETTER A - LATIN CAPITAL LETTER Z
return true;
return false;
case LanguageType.ExtendenLatin:
if (wchar >= 0x00C0 && wchar <= 0x00D6) // LATIN CAPITAL LETTER A WITH GRAVE - LATIN CAPITAL LETTER O WITH DIAERESIS
return true;
if (wchar >= 0x00D8 && wchar <= 0x00DE) // LATIN CAPITAL LETTER O WITH STROKE - LATIN CAPITAL LETTER THORN
return true;
if (wchar == 0x00DF) // LATIN SMALL LETTER SHARP S
return true;
if (wchar >= 0x00E0 && wchar <= 0x00F6) // LATIN SMALL LETTER A WITH GRAVE - LATIN SMALL LETTER O WITH DIAERESIS
return true;
if (wchar >= 0x00F8 && wchar <= 0x00FE) // LATIN SMALL LETTER O WITH STROKE - LATIN SMALL LETTER THORN
return true;
if (wchar >= 0x0100 && wchar <= 0x012F) // LATIN CAPITAL LETTER A WITH MACRON - LATIN SMALL LETTER I WITH OGONEK
return true;
if (wchar == 0x1E9E) // LATIN CAPITAL LETTER SHARP S
return true;
return false;
case LanguageType.Cyrillic:
if (wchar >= 0x0410 && wchar <= 0x044F) // CYRILLIC CAPITAL LETTER A - CYRILLIC SMALL LETTER YA
return true;
if (wchar == 0x0401 || wchar == 0x0451) // CYRILLIC CAPITAL LETTER IO, CYRILLIC SMALL LETTER IO
return true;
return false;
case LanguageType.EastAsia:
if (wchar >= 0x1100 && wchar <= 0x11F9) // Hangul Jamo
return true;
if (wchar >= 0x3041 && wchar <= 0x30FF) // Hiragana + Katakana
return true;
if (wchar >= 0x3131 && wchar <= 0x318E) // Hangul Compatibility Jamo
return true;
if (wchar >= 0x31F0 && wchar <= 0x31FF) // Katakana Phonetic Ext.
return true;
if (wchar >= 0x3400 && wchar <= 0x4DB5) // CJK Ideographs Ext. A
return true;
if (wchar >= 0x4E00 && wchar <= 0x9FC3) // Unified CJK Ideographs
return true;
if (wchar >= 0xAC00 && wchar <= 0xD7A3) // Hangul Syllables
return true;
if (wchar >= 0xFF01 && wchar <= 0xFFEE) // Halfwidth forms
return true;
return false;
}
}
return false;
}
//General
public void LoadCypherStrings()
{
var time = Time.GetMSTime();
CypherStringStorage.Clear();
SQLResult result = DB.World.Query("SELECT entry, content_default, content_loc1, content_loc2, content_loc3, content_loc4, content_loc5, content_loc6, content_loc7, content_loc8 FROM trinity_string");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 CypherStrings. DB table `trinity_string` is empty.");
Global.WorldMgr.StopNow();
return;
}
uint count = 0;
do
{
uint entry = result.Read<uint>(0);
CypherStringStorage[entry] = new StringArray((int)SharedConst.DefaultLocale + 1);
count++;
for (var i = SharedConst.DefaultLocale; i >= 0; --i)
AddLocaleString(result.Read<string>((int)i + 1).ConvertFormatSyntax(), i, CypherStringStorage[entry]);
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} CypherStrings in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
public void LoadRaceAndClassExpansionRequirements()
{
uint oldMSTime = Time.GetMSTime();
_raceExpansionRequirementStorage.Clear();
// 0 1
SQLResult result = DB.World.Query("SELECT raceID, expansion FROM `race_expansion_requirement`");
if (!result.IsEmpty())
{
uint count = 0;
do
{
byte raceID = result.Read<byte>(0);
byte expansion = result.Read<byte>(1);
ChrRacesRecord raceEntry = CliDB.ChrRacesStorage.LookupByKey(raceID);
if (raceEntry == null)
{
Log.outError(LogFilter.Sql, "Race {0} defined in `race_expansion_requirement` does not exists, skipped.", raceID);
continue;
}
if (expansion >= (int)Expansion.Max)
{
Log.outError(LogFilter.Sql, "Race {0} defined in `race_expansion_requirement` has incorrect expansion {1}, skipped.", raceID, expansion);
continue;
}
_raceExpansionRequirementStorage[raceID] = expansion;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} race expansion requirements in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
else
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 race expansion requirements. DB table `race_expansion_requirement` is empty.");
oldMSTime = Time.GetMSTime();
_classExpansionRequirementStorage.Clear();
// 0 1
result = DB.World.Query("SELECT classID, expansion FROM `class_expansion_requirement`");
if (!result.IsEmpty())
{
uint count = 0;
do
{
byte classID = result.Read<byte>(0);
byte expansion = result.Read<byte>(1);
ChrClassesRecord classEntry = CliDB.ChrClassesStorage.LookupByKey(classID);
if (classEntry == null)
{
Log.outError(LogFilter.Sql, "Class {0} defined in `class_expansion_requirement` does not exists, skipped.", classID);
continue;
}
if (expansion >= (int)Expansion.Max)
{
Log.outError(LogFilter.Sql, "Class {0} defined in `class_expansion_requirement` has incorrect expansion {1}, skipped.", classID, expansion);
continue;
}
_classExpansionRequirementStorage[classID] = expansion;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} class expansion requirements in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
else
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 class expansion requirements. DB table `class_expansion_requirement` is empty.");
}
public void LoadRealmNames()
{
uint oldMSTime = Time.GetMSTime();
_realmNameStorage.Clear();
// 0 1
SQLResult result = DB.Login.Query("SELECT id, name FROM `realmlist`");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 realm names. DB table `realmlist` is empty.");
return;
}
uint count = 0;
do
{
uint realm = result.Read<uint>(0);
string realmName = result.Read<string>(1);
_realmNameStorage[realm] = realmName;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} realm names in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public string GetCypherString(uint entry, LocaleConstant locale = LocaleConstant.enUS)
{
if (!CypherStringStorage.ContainsKey(entry))
{
Log.outError(LogFilter.Sql, "Cypher string entry {0} not found in DB.", entry);
return "<Error>";
}
var cs = CypherStringStorage[entry];
if (cs.Length > (int)locale && !string.IsNullOrEmpty(cs[(int)locale]))
return cs[(int)locale];
return cs[(int)SharedConst.DefaultLocale];
}
public string GetCypherString(CypherStrings cmd, LocaleConstant locale = LocaleConstant.enUS)
{
return GetCypherString((uint)cmd, locale);
}
public string GetRealmName(uint realm)
{
return _realmNameStorage.LookupByKey(realm);
}
public bool GetRealmName(uint realmId, ref string name, ref string normalizedName)
{
var realmName = _realmNameStorage.LookupByKey(realmId);
if (realmName != null)
{
name = realmName;
normalizedName = realmName.Normalize();
return true;
}
return false;
}
public Dictionary<byte, byte> GetRaceExpansionRequirements() { return _raceExpansionRequirementStorage; }
public Expansion GetRaceExpansionRequirement(Race race)
{
if (_raceExpansionRequirementStorage.ContainsKey((byte)race))
return (Expansion)_raceExpansionRequirementStorage[(byte)race];
return Expansion.Classic;
}
public Dictionary<byte, byte> GetClassExpansionRequirements() { return _classExpansionRequirementStorage; }
public Expansion GetClassExpansionRequirement(Class class_)
{
var exp = _classExpansionRequirementStorage.LookupByKey(class_);
if (_classExpansionRequirementStorage.ContainsKey((byte)class_))
return (Expansion)_classExpansionRequirementStorage[(byte)class_];
return Expansion.Classic;
}
//Gossip
public void LoadGossipMenu()
{
uint oldMSTime = Time.GetMSTime();
gossipMenusStorage.Clear();
SQLResult result = DB.World.Query("SELECT entry, text_id FROM gossip_menu");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 gossip_menu entries. DB table `gossip_menu` is empty!");
return;
}
uint count = 0;
do
{
GossipMenus gMenu = new GossipMenus();
gMenu.entry = result.Read<uint>(0);
gMenu.text_id = result.Read<uint>(1);
if (GetNpcText(gMenu.text_id) == null)
{
Log.outError(LogFilter.Sql, "Table gossip_menu entry {0} are using non-existing textid {1}", gMenu.entry, gMenu.text_id);
continue;
}
gossipMenusStorage.Add(gMenu.entry, gMenu);
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gossip_menu entries in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadGossipMenuItems()
{
uint oldMSTime = Time.GetMSTime();
gossipMenuItemsStorage.Clear();
// 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())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 gossip_menu_option entries. DB table `gossip_menu_option` is empty!");
return;
}
uint count = 0;
do
{
GossipMenuItems gMenuItem = new GossipMenuItems();
gMenuItem.MenuId = result.Read<uint>(0);
gMenuItem.OptionIndex = result.Read<uint>(1);
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);
gMenuItem.OptionNpcflag = (NPCFlags)result.Read<ulong>(6);
gMenuItem.ActionMenuId = result.Read<uint>(7);
gMenuItem.ActionPoiId = result.Read<uint>(8);
gMenuItem.BoxCoded = result.Read<bool>(9);
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 MenuId {gMenuItem.MenuId}, OptionIndex {gMenuItem.OptionIndex} has unknown icon id {gMenuItem.OptionIcon}. Replacing with GOSSIPICONCHAT");
gMenuItem.OptionIcon = GossipOptionIcon.Chat;
}
if (gMenuItem.OptionBroadcastTextId != 0)
{
if (!CliDB.BroadcastTextStorage.ContainsKey(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 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 MenuId {gMenuItem.MenuId}, OptionIndex {gMenuItem.OptionIndex} use non-existing actionpoiid {gMenuItem.ActionPoiId}, ignoring");
gMenuItem.ActionPoiId = 0;
}
if (gMenuItem.BoxBroadcastTextId != 0)
{
if (!CliDB.BroadcastTextStorage.ContainsKey(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;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gossip_menu_option entries in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadPointsOfInterest()
{
uint oldMSTime = Time.GetMSTime();
pointsOfInterestStorage.Clear(); // need for reload case
// 0 1 2 3 4 5 6
SQLResult result = DB.World.Query("SELECT ID, PositionX, PositionY, Icon, Flags, Importance, Name FROM points_of_interest");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 Points of Interest definitions. DB table `points_of_interest` is empty.");
return;
}
uint count = 0;
do
{
uint id = result.Read<uint>(0);
PointOfInterest POI = new PointOfInterest();
POI.ID = id;
POI.Pos = new Vector2(result.Read<float>(1), result.Read<float>(2));
POI.Icon = result.Read<uint>(3);
POI.Flags = result.Read<uint>(4);
POI.Importance = result.Read<uint>(5);
POI.Name = result.Read<string>(6);
if (!GridDefines.IsValidMapCoord(POI.Pos.X, POI.Pos.Y))
{
Log.outError(LogFilter.Sql, "Table `points_of_interest` (ID: {0}) have invalid coordinates (PositionX: {1} PositionY: {2}), ignored.", id, POI.Pos.X, POI.Pos.Y);
continue;
}
pointsOfInterestStorage[id] = POI;
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Points of Interest definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public List<GossipMenus> GetGossipMenusMapBounds(uint uiMenuId)
{
return gossipMenusStorage.LookupByKey(uiMenuId);
}
public List<GossipMenuItems> GetGossipMenuItemsMapBounds(uint uiMenuId)
{
return gossipMenuItemsStorage.LookupByKey(uiMenuId);
}
public PointOfInterest GetPointOfInterest(uint id)
{
return pointsOfInterestStorage.LookupByKey(id);
}
public void LoadGraveyardZones()
{
uint oldMSTime = Time.GetMSTime();
GraveYardStorage.Clear(); // need for reload case
// 0 1 2
SQLResult result = DB.World.Query("SELECT ID, GhostZone, faction FROM graveyard_zone");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 graveyard-zone links. DB table `graveyard_zone` is empty.");
return;
}
uint count = 0;
do
{
++count;
uint safeLocId = result.Read<uint>(0);
uint zoneId = result.Read<uint>(1);
Team team = (Team)result.Read<uint>(2);
WorldSafeLocsRecord entry = CliDB.WorldSafeLocsStorage.LookupByKey(safeLocId);
if (entry == null)
{
Log.outError(LogFilter.Sql, "Table `graveyard_zone` has a record for not existing graveyard (WorldSafeLocs.dbc id) {0}, skipped.", safeLocId);
continue;
}
AreaTableRecord areaEntry = CliDB.AreaTableStorage.LookupByKey(zoneId);
if (areaEntry == null)
{
Log.outError(LogFilter.Sql, "Table `graveyard_zone` has a record for not existing zone id ({0}), skipped.", zoneId);
continue;
}
if (team != 0 && team != Team.Horde && team != Team.Alliance)
{
Log.outError(LogFilter.Sql, "Table `graveyard_zone` has a record for non player faction ({0}), skipped.", team);
continue;
}
if (!AddGraveYardLink(safeLocId, zoneId, team, false))
Log.outError(LogFilter.Sql, "Table `graveyard_zone` has a duplicate record for Graveyard (ID: {0}) and Zone (ID: {1}), skipped.", safeLocId, zoneId);
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} graveyard-zone links in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
WorldSafeLocsRecord GetDefaultGraveYard(Team team)
{
if (team == Team.Horde)
return CliDB.WorldSafeLocsStorage.LookupByKey(10);
else if (team == Team.Alliance)
return CliDB.WorldSafeLocsStorage.LookupByKey(4);
else return null;
}
public WorldSafeLocsRecord GetClosestGraveYard(float x, float y, float z, uint MapId, Team team)
{
// search for zone associated closest graveyard
uint zoneId = Global.MapMgr.GetZoneId(MapId, x, y, z);
if (zoneId == 0)
{
if (z > -500)
{
Log.outError(LogFilter.Server, "ZoneId not found for map {0} coords ({1}, {2}, {3})", MapId, x, y, z);
return GetDefaultGraveYard(team);
}
}
// Simulate std. algorithm:
// found some graveyard associated to (ghost_zone, ghost_map)
//
// if mapId == graveyard.mapId (ghost in plain zone or city or Battleground) and search graveyard at same map
// then check faction
// if mapId != graveyard.mapId (ghost in instance) and search any graveyard associated
// then check faction
var range = GraveYardStorage.LookupByKey(zoneId);
MapRecord mapEntry = CliDB.MapStorage.LookupByKey(MapId);
// not need to check validity of map object; MapId _MUST_ be valid here
if (range.Empty() && !mapEntry.IsBattlegroundOrArena())
{
if (zoneId != 0) // zone == 0 can't be fixed, used by bliz for bugged zones
Log.outError(LogFilter.Sql, "Table `game_graveyard_zone` incomplete: Zone {0} Team {1} does not have a linked graveyard.", zoneId, team);
return GetDefaultGraveYard(team);
}
// at corpse map
bool foundNear = false;
float distNear = 10000;
WorldSafeLocsRecord entryNear = null;
// at entrance map for corpse map
bool foundEntr = false;
float distEntr = 10000;
WorldSafeLocsRecord entryEntr = null;
// some where other
WorldSafeLocsRecord entryFar = null;
foreach (var data in range)
{
WorldSafeLocsRecord entry = CliDB.WorldSafeLocsStorage.LookupByKey(data.safeLocId);
if (entry == null)
{
Log.outError(LogFilter.Sql, "Table `game_graveyard_zone` has record for not existing graveyard (WorldSafeLocs.dbc id) {0}, skipped.", data.safeLocId);
continue;
}
// skip enemy faction graveyard
// team == 0 case can be at call from .neargrave
if (data.team != 0 && team != 0 && data.team != (uint)team)
continue;
// find now nearest graveyard at other map
if (MapId != entry.MapID)
{
// if find graveyard at different map from where entrance placed (or no entrance data), use any first
if (mapEntry == null
|| mapEntry.CorpseMapID < 0
|| mapEntry.CorpseMapID != entry.MapID
|| (mapEntry.CorpsePos.X == 0 && mapEntry.CorpsePos.Y == 0))
{
// not have any corrdinates for check distance anyway
entryFar = entry;
continue;
}
// at entrance map calculate distance (2D);
float dist2 = (entry.Loc.X - mapEntry.CorpsePos.X) * (entry.Loc.X - mapEntry.CorpsePos.X)
+ (entry.Loc.Y - mapEntry.CorpsePos.Y) * (entry.Loc.Y - mapEntry.CorpsePos.Y);
if (foundEntr)
{
if (dist2 < distEntr)
{
distEntr = dist2;
entryEntr = entry;
}
}
else
{
foundEntr = true;
distEntr = dist2;
entryEntr = entry;
}
}
// find now nearest graveyard at same map
else
{
float dist2 = (entry.Loc.X - x) * (entry.Loc.X - x) + (entry.Loc.Y - y) * (entry.Loc.Y - y) + (entry.Loc.Z - z) * (entry.Loc.Z - z);
if (foundNear)
{
if (dist2 < distNear)
{
distNear = dist2;
entryNear = entry;
}
}
else
{
foundNear = true;
distNear = dist2;
entryNear = entry;
}
}
}
if (entryNear != null)
return entryNear;
if (entryEntr != null)
return entryEntr;
return entryFar;
}
public GraveYardData FindGraveYardData(uint id, uint zoneId)
{
var range = GraveYardStorage.LookupByKey(zoneId);
foreach (var data in range)
{
if (data.safeLocId == id)
return data;
}
return null;
}
public bool AddGraveYardLink(uint id, uint zoneId, Team team, bool persist = true)
{
if (FindGraveYardData(id, zoneId) != null)
return false;
// add link to loaded data
GraveYardData data = new GraveYardData();
data.safeLocId = id;
data.team = (uint)team;
GraveYardStorage.Add(zoneId, data);
// add link to DB
if (persist)
{
PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.INS_GRAVEYARD_ZONE);
stmt.AddValue(0, id);
stmt.AddValue(1, zoneId);
stmt.AddValue(2, team);
DB.World.Execute(stmt);
}
return true;
}
public void RemoveGraveYardLink(uint id, uint zoneId, Team team, bool persist = false)
{
var range = GraveYardStorage.LookupByKey(zoneId);
if (range.Empty())
{
Log.outError(LogFilter.Sql, "Table `game_graveyard_zone` incomplete: Zone {0} Team {1} does not have a linked graveyard.", zoneId, team);
return;
}
bool found = false;
foreach (var data in range)
{
// skip not matching safezone id
if (data.safeLocId != id)
continue;
// skip enemy faction graveyard at same map (normal area, city, or Battleground)
// team == 0 case can be at call from .neargrave
if (data.team != 0 && team != 0 && data.team != (uint)team)
continue;
found = true;
break;
}
// no match, return
if (!found)
return;
// remove from links
GraveYardStorage.Remove(zoneId);
// remove link from DB
if (persist)
{
PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_GRAVEYARD_ZONE);
stmt.AddValue(0, id);
stmt.AddValue(1, zoneId);
stmt.AddValue(2, team);
DB.World.Execute(stmt);
}
}
//Scripts
public void LoadScriptNames()
{
uint oldMSTime = Time.GetMSTime();
scriptNamesStorage.Add("");
SQLResult result = DB.World.Query(
"SELECT DISTINCT(ScriptName) FROM Battleground_template WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM creature WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM creature_template WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM criteria_data WHERE ScriptName <> '' AND type = 11 " +
"UNION SELECT DISTINCT(ScriptName) FROM gameobject WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM gameobject_template WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM item_script_names WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM areatrigger_scripts WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM areatrigger_template WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM spell_script_names WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM transports WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM game_weather WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM conditions WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM outdoorpvp_template WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(ScriptName) FROM scene_template WHERE ScriptName <> '' " +
"UNION SELECT DISTINCT(script) FROM instance_template WHERE script <> ''");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded empty set of Script Names!");
return;
}
uint count = 1;
do
{
scriptNamesStorage.Add(result.Read<string>(0));
++count;
}
while (result.NextRow());
scriptNamesStorage.Sort();
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Script Names in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadAreaTriggerScripts()
{
uint oldMSTime = Time.GetMSTime();
areaTriggerScriptStorage.Clear(); // need for reload case
SQLResult result = DB.World.Query("SELECT entry, ScriptName FROM areatrigger_scripts");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 areatrigger scripts. DB table `areatrigger_scripts` is empty.");
return;
}
uint count = 0;
do
{
uint id = result.Read<uint>(0);
string scriptName = result.Read<string>(1);
AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(id);
if (atEntry == null)
{
Log.outError(LogFilter.Sql, "Area trigger (Id:{0}) does not exist in `AreaTrigger.dbc`.", id);
continue;
}
++count;
areaTriggerScriptStorage[id] = GetScriptId(scriptName);
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} areatrigger scripts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
void LoadScripts(ScriptsType type)
{
uint oldMSTime = Time.GetMSTime();
var scripts = GetScriptsMapByType(type);
if (scripts == null)
return;
string tableName = GetScriptsTableNameByType(type);
if (string.IsNullOrEmpty(tableName))
return;
if (Global.MapMgr.IsScriptScheduled()) // function cannot be called when scripts are in use.
return;
Log.outInfo(LogFilter.ServerLoading, "Loading {0}...", tableName);
scripts.Clear(); // need for reload support
bool isSpellScriptTable = (type == ScriptsType.Spell);
// 0 1 2 3 4 5 6 7 8 9
SQLResult result = DB.World.Query("SELECT id, delay, command, datalong, datalong2, dataint, x, y, z, o{0} FROM {1}", isSpellScriptTable ? ", effIndex" : "", tableName);
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 script definitions. DB table `{0}` is empty!", tableName);
return;
}
uint count = 0;
do
{
ScriptInfo tmp = new ScriptInfo();
tmp.type = type;
tmp.id = result.Read<uint>(0);
if (isSpellScriptTable)
tmp.id |= result.Read<uint>(10) << 24;
tmp.delay = result.Read<uint>(1);
tmp.command = (ScriptCommands)result.Read<uint>(2);
unsafe
{
tmp.Raw.nData[0] = result.Read<uint>(3);
tmp.Raw.nData[1] = result.Read<uint>(4);
tmp.Raw.nData[2] = (uint)result.Read<int>(5);
tmp.Raw.fData[0] = result.Read<float>(6);
tmp.Raw.fData[1] = result.Read<float>(7);
tmp.Raw.fData[2] = result.Read<float>(8);
tmp.Raw.fData[3] = result.Read<float>(9);
}
// generic command args check
switch (tmp.command)
{
case ScriptCommands.Talk:
{
if (tmp.Talk.ChatType > ChatMsg.RaidBossWhisper)
{
Log.outError(LogFilter.Sql, "Table `{0}` has invalid talk type (datalong = {1}) in SCRIPT_COMMAND_TALK for script id {2}",
tableName, tmp.Talk.ChatType, tmp.id);
continue;
}
if (!CliDB.BroadcastTextStorage.ContainsKey((uint)tmp.Talk.TextID))
{
Log.outError(LogFilter.Sql, "Table `{0}` has invalid talk text id (dataint = {1}) in SCRIPT_COMMAND_TALK for script id {2}",
tableName, tmp.Talk.TextID, tmp.id);
continue;
}
break;
}
case ScriptCommands.Emote:
{
if (!CliDB.EmotesStorage.ContainsKey(tmp.Emote.EmoteID))
{
Log.outError(LogFilter.Sql, "Table `{0}` has invalid emote id (datalong = {1}) in SCRIPT_COMMAND_EMOTE for script id {2}",
tableName, tmp.Emote.EmoteID, tmp.id);
continue;
}
break;
}
case ScriptCommands.TeleportTo:
{
if (!CliDB.MapStorage.ContainsKey(tmp.TeleportTo.MapID))
{
Log.outError(LogFilter.Sql, "Table `{0}` has invalid map (Id: {1}) in SCRIPT_COMMAND_TELEPORT_TO for script id {2}",
tableName, tmp.TeleportTo.MapID, tmp.id);
continue;
}
if (!GridDefines.IsValidMapCoord(tmp.TeleportTo.DestX, tmp.TeleportTo.DestY, tmp.TeleportTo.DestZ, tmp.TeleportTo.Orientation))
{
Log.outError(LogFilter.Sql, "Table `{0}` has invalid coordinates (X: {1} Y: {2} Z: {3} O: {4}) in SCRIPT_COMMAND_TELEPORT_TO for script id {5}",
tableName, tmp.TeleportTo.DestX, tmp.TeleportTo.DestY, tmp.TeleportTo.DestZ, tmp.TeleportTo.Orientation, tmp.id);
continue;
}
break;
}
case ScriptCommands.QuestExplored:
{
Quest quest = GetQuestTemplate(tmp.QuestExplored.QuestID);
if (quest == null)
{
Log.outError(LogFilter.Sql, "Table `{0}` has invalid quest (ID: {1}) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id {2}",
tableName, tmp.QuestExplored.QuestID, tmp.id);
continue;
}
if (!quest.HasSpecialFlag(QuestSpecialFlags.ExplorationOrEvent))
{
Log.outError(LogFilter.Sql, "Table `{0}` has quest (ID: {1}) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id {2}, but quest not have flag QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT in quest flags. Script command or quest flags wrong. Quest modified to require objective.",
tableName, tmp.QuestExplored.QuestID, tmp.id);
// this will prevent quest completing without objective
quest.SetSpecialFlag(QuestSpecialFlags.ExplorationOrEvent);
// continue; - quest objective requirement set and command can be allowed
}
if (tmp.QuestExplored.Distance > SharedConst.DefaultVisibilityDistance)
{
Log.outError(LogFilter.Sql, "Table `{0}` has too large distance ({1}) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id {2}",
tableName, tmp.QuestExplored.Distance, tmp.id);
continue;
}
if (tmp.QuestExplored.Distance != 0 && tmp.QuestExplored.Distance > SharedConst.DefaultVisibilityDistance)
{
Log.outError(LogFilter.Sql, "Table `{0}` has too large distance ({1}) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id {2}, max distance is {3} or 0 for disable distance check",
tableName, tmp.QuestExplored.Distance, tmp.id, SharedConst.DefaultVisibilityDistance);
continue;
}
if (tmp.QuestExplored.Distance != 0 && tmp.QuestExplored.Distance < SharedConst.InteractionDistance)
{
Log.outError(LogFilter.Sql, "Table `{0}` has too small distance ({1}) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id {2}, min distance is {3} or 0 for disable distance check",
tableName, tmp.QuestExplored.Distance, tmp.id, SharedConst.InteractionDistance);
continue;
}
break;
}
case ScriptCommands.KillCredit:
{
if (GetCreatureTemplate(tmp.KillCredit.CreatureEntry) == null)
{
Log.outError(LogFilter.Sql, "Table `{0}` has invalid creature (Entry: {1}) in SCRIPT_COMMAND_KILL_CREDIT for script id {2}",
tableName, tmp.KillCredit.CreatureEntry, tmp.id);
continue;
}
break;
}
case ScriptCommands.RespawnGameobject:
{
GameObjectData data = GetGOData(tmp.RespawnGameObject.GOGuid);
if (data == null)
{
Log.outError(LogFilter.Sql, "Table `{0}` has invalid gameobject (GUID: {1}) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id {2}",
tableName, tmp.RespawnGameObject.GOGuid, tmp.id);
continue;
}
GameObjectTemplate info = GetGameObjectTemplate(data.id);
if (info == null)
{
Log.outError(LogFilter.Sql, "Table `{0}` has gameobject with invalid entry (GUID: {1} Entry: {2}) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id {3}",
tableName, tmp.RespawnGameObject.GOGuid, data.id, tmp.id);
continue;
}
if (info.type == GameObjectTypes.FishingNode || info.type == GameObjectTypes.FishingHole || info.type == GameObjectTypes.Door ||
info.type == GameObjectTypes.Button || info.type == GameObjectTypes.Trap)
{
Log.outError(LogFilter.Sql, "Table `{0}` have gameobject type ({1}) unsupported by command SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id {2}",
tableName, info.entry, tmp.id);
continue;
}
break;
}
case ScriptCommands.TempSummonCreature:
{
if (!GridDefines.IsValidMapCoord(tmp.TempSummonCreature.PosX, tmp.TempSummonCreature.PosY, tmp.TempSummonCreature.PosZ, tmp.TempSummonCreature.Orientation))
{
Log.outError(LogFilter.Sql, "Table `{0}` has invalid coordinates (X: {1} Y: {2} Z: {3} O: {4}) in SCRIPT_COMMAND_TEMP_SUMMON_CREATURE for script id {5}",
tableName, tmp.TempSummonCreature.PosX, tmp.TempSummonCreature.PosY, tmp.TempSummonCreature.PosZ, tmp.TempSummonCreature.Orientation, tmp.id);
continue;
}
if (GetCreatureTemplate(tmp.TempSummonCreature.CreatureEntry) == null)
{
Log.outError(LogFilter.Sql, "Table `{0}` has invalid creature (Entry: {1}) in SCRIPT_COMMAND_TEMP_SUMMON_CREATURE for script id {2}",
tableName, tmp.TempSummonCreature.CreatureEntry, tmp.id);
continue;
}
break;
}
case ScriptCommands.OpenDoor:
case ScriptCommands.CloseDoor:
{
GameObjectData data = GetGOData(tmp.ToggleDoor.GOGuid);
if (data == null)
{
Log.outError(LogFilter.Sql, "Table `{0}` has invalid gameobject (GUID: {1}) in {2} for script id {3}",
tableName, tmp.ToggleDoor.GOGuid, tmp.command, tmp.id);
continue;
}
GameObjectTemplate info = GetGameObjectTemplate(data.id);
if (info == null)
{
Log.outError(LogFilter.Sql, "Table `{0}` has gameobject with invalid entry (GUID: {1} Entry: {2}) in {3} for script id {4}",
tableName, tmp.ToggleDoor.GOGuid, data.id, tmp.command, tmp.id);
continue;
}
if (info.type != GameObjectTypes.Door)
{
Log.outError(LogFilter.Sql, "Table `{0}` has gameobject type ({1}) non supported by command {2} for script id {3}",
tableName, info.entry, tmp.command, tmp.id);
continue;
}
break;
}
case ScriptCommands.RemoveAura:
{
if (Global.SpellMgr.GetSpellInfo(tmp.RemoveAura.SpellID) == null)
{
Log.outError(LogFilter.Sql, "Table `{0}` using non-existent spell (id: {1}) in SCRIPT_COMMAND_REMOVE_AURA for script id {2}",
tableName, tmp.RemoveAura.SpellID, tmp.id);
continue;
}
if (Convert.ToBoolean((int)tmp.RemoveAura.Flags & ~0x1)) // 1 bits (0, 1)
{
Log.outError(LogFilter.Sql, "Table `{0}` using unknown flags in datalong2 ({1}) in SCRIPT_COMMAND_REMOVE_AURA for script id {2}",
tableName, tmp.RemoveAura.Flags, tmp.id);
continue;
}
break;
}
case ScriptCommands.CastSpell:
{
if (Global.SpellMgr.GetSpellInfo(tmp.CastSpell.SpellID) == null)
{
Log.outError(LogFilter.Sql, "Table `{0}` using non-existent spell (id: {1}) in SCRIPT_COMMAND_CAST_SPELL for script id {2}",
tableName, tmp.CastSpell.SpellID, tmp.id);
continue;
}
if ((int)tmp.CastSpell.Flags > 4) // targeting type
{
Log.outError(LogFilter.Sql, "Table `{0}` using unknown target in datalong2 ({1}) in SCRIPT_COMMAND_CAST_SPELL for script id {2}",
tableName, tmp.CastSpell.Flags, tmp.id);
continue;
}
if ((int)tmp.CastSpell.Flags != 4 && Convert.ToBoolean(tmp.CastSpell.CreatureEntry & ~0x1)) // 1 bit (0, 1)
{
Log.outError(LogFilter.Sql, "Table `{0}` using unknown flags in dataint ({1}) in SCRIPT_COMMAND_CAST_SPELL for script id {2}",
tableName, tmp.CastSpell.CreatureEntry, tmp.id);
continue;
}
else if ((int)tmp.CastSpell.Flags == 4 && GetCreatureTemplate((uint)tmp.CastSpell.CreatureEntry) == null)
{
Log.outError(LogFilter.Sql, "Table `{0}` using invalid creature entry in dataint ({1}) in SCRIPT_COMMAND_CAST_SPELL for script id {2}",
tableName, tmp.CastSpell.CreatureEntry, tmp.id);
continue;
}
break;
}
case ScriptCommands.CreateItem:
{
if (GetItemTemplate(tmp.CreateItem.ItemEntry) == null)
{
Log.outError(LogFilter.Sql, "Table `{0}` has nonexistent item (entry: {1}) in SCRIPT_COMMAND_CREATE_ITEM for script id {2}",
tableName, tmp.CreateItem.ItemEntry, tmp.id);
continue;
}
if (tmp.CreateItem.Amount == 0)
{
Log.outError(LogFilter.Sql, "Table `{0}` SCRIPT_COMMAND_CREATE_ITEM but amount is {1} for script id {2}",
tableName, tmp.CreateItem.Amount, tmp.id);
continue;
}
break;
}
case ScriptCommands.PlayAnimkit:
{
if (!CliDB.AnimKitStorage.ContainsKey(tmp.PlayAnimKit.AnimKitID))
{
Log.outError(LogFilter.Sql, "Table `{0}` has invalid AnimKid id (datalong = {1}) in SCRIPT_COMMAND_PLAY_ANIMKIT for script id {2}",
tableName, tmp.PlayAnimKit.AnimKitID, tmp.id);
continue;
}
break;
}
default:
break;
}
if (!scripts.ContainsKey(tmp.id))
scripts[tmp.id] = new MultiMap<uint, ScriptInfo>();
scripts[tmp.id].Add(tmp.delay, tmp);
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} script definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadSpellScripts()
{
LoadScripts(ScriptsType.Spell);
// check ids
foreach (var script in sSpellScripts)
{
uint spellId = script.Key & 0x00FFFFFF;
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId);
if (spellInfo == null)
{
Log.outError(LogFilter.Sql, "Table `spell_scripts` has not existing spell (Id: {0}) as script id", spellId);
continue;
}
byte i = (byte)((script.Key >> 24) & 0x000000FF);
//check for correct spellEffect
SpellEffectInfo effect = spellInfo.GetEffect(i);
if (effect != null && (effect.Effect == 0 || (effect.Effect != SpellEffectName.ScriptEffect && effect.Effect != SpellEffectName.Dummy)))
Log.outError(LogFilter.Sql, "Table `spell_scripts` - spell {0} effect {1} is not SPELL_EFFECT_SCRIPT_EFFECT or SPELL_EFFECT_DUMMY", spellId, i);
}
}
public void LoadEventScripts()
{
LoadScripts(ScriptsType.Event);
List<uint> evt_scripts = new List<uint>();
// Load all possible script entries from gameobjects
foreach (var go in gameObjectTemplateStorage)
{
uint eventId = go.Value.GetEventScriptId();
if (eventId != 0)
evt_scripts.Add(eventId);
}
// Load all possible script entries from spells
foreach (var spell in Global.SpellMgr.GetSpellInfoStorage().Values)
{
foreach (SpellEffectInfo effect in spell.GetEffectsForDifficulty(Difficulty.None))
{
if (effect == null)
continue;
if (effect.Effect == SpellEffectName.SendEvent)
if (effect.MiscValue != 0)
evt_scripts.Add((uint)effect.MiscValue);
}
}
foreach (var path_idx in CliDB.TaxiPathNodesByPath)
{
for (uint node_idx = 0; node_idx < path_idx.Value.Length; ++node_idx)
{
TaxiPathNodeRecord node = path_idx.Value[node_idx];
if (node.ArrivalEventID != 0)
evt_scripts.Add(node.ArrivalEventID);
if (node.DepartureEventID != 0)
evt_scripts.Add(node.DepartureEventID);
}
}
// Then check if all scripts are in above list of possible script entries
foreach (var script in sEventScripts)
{
var id = evt_scripts.Find(p => p == script.Key);
if (id == 0)
Log.outError(LogFilter.Sql, "Table `event_scripts` has script (Id: {0}) not referring to any gameobject_template type 10 data2 field, type 3 data6 field, type 13 data 2 field or any spell effect {1}",
script.Key, SpellEffectName.SendEvent);
}
}
//Load WP Scripts
public void LoadWaypointScripts()
{
LoadScripts(ScriptsType.Waypoint);
List<uint> actionSet = new List<uint>();
foreach (var script in sWaypointScripts)
actionSet.Add(script.Key);
PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_DATA_ACTION);
SQLResult result = DB.World.Query(stmt);
if (!result.IsEmpty())
{
do
{
uint action = result.Read<uint>(0);
actionSet.Remove(action);
}
while (result.NextRow());
}
foreach (var id in actionSet)
Log.outError(LogFilter.Sql, "There is no waypoint which links to the waypoint script {0}", id);
}
public void LoadSpellScriptNames()
{
uint oldMSTime = Time.GetMSTime();
spellScriptsStorage.Clear(); // need for reload case
SQLResult result = DB.World.Query("SELECT spell_id, ScriptName FROM spell_script_names");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell script names. DB table `spell_script_names` is empty!");
return;
}
uint count = 0;
do
{
int spellId = result.Read<int>(0);
string scriptName = result.Read<string>(1);
bool allRanks = false;
if (spellId < 0)
{
allRanks = true;
spellId = -spellId;
}
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)spellId);
if (spellInfo == null)
{
Log.outError(LogFilter.Sql, "Scriptname: `{0}` spell (Id: {1}) does not exist.", scriptName, spellId);
continue;
}
if (allRanks)
{
if (!spellInfo.IsRanked())
Log.outError(LogFilter.Sql, "Scriptname: `{0}` spell (Id: {1}) has no ranks of spell.", scriptName, spellId);
if (spellInfo.GetFirstRankSpell().Id != spellId)
{
Log.outError(LogFilter.Sql, "Scriptname: `{0}` spell (Id: {1}) is not first rank of spell.", scriptName, spellId);
continue;
}
while (spellInfo != null)
{
spellScriptsStorage.Add(spellInfo.Id, GetScriptId(scriptName));
spellInfo = spellInfo.GetNextRankSpell();
}
}
else
{
if (spellInfo.IsRanked())
Log.outError(LogFilter.Sql, "Scriptname: `{0}` spell (Id: {1}) is ranked spell. Perhaps not all ranks are assigned to this script.", scriptName, spellId);
spellScriptsStorage.Add(spellInfo.Id, GetScriptId(scriptName));
}
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell script names in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void ValidateSpellScripts()
{
uint oldMSTime = Time.GetMSTime();
if (spellScriptsStorage.Empty())
{
Log.outInfo(LogFilter.ServerLoading, "Validated 0 scripts.");
return;
}
uint count = 0;
foreach (var script in spellScriptsStorage.ToList())
{
SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(script.Key);
Dictionary<SpellScriptLoader, uint> SpellScriptLoaders = Global.ScriptMgr.CreateSpellScriptLoaders(script.Key);
foreach (var pair in SpellScriptLoaders)
{
SpellScript spellScript = pair.Key.GetSpellScript();
bool valid = true;
if (spellScript == null)
{
Log.outError(LogFilter.Scripts, "Functions GetSpellScript() of script `{0}` do not return object - script skipped", GetScriptName(pair.Value));
valid = false;
}
if (spellScript != null)
{
spellScript._Init(pair.Key.GetName(), spellEntry.Id);
spellScript._Register();
if (!spellScript._Validate(spellEntry))
valid = false;
}
if (!valid)
spellScriptsStorage.Remove(pair.Value);
}
Dictionary<AuraScriptLoader, uint> AuraScriptLoaders = Global.ScriptMgr.CreateAuraScriptLoaders(script.Key);
foreach (var pair in AuraScriptLoaders)
{
AuraScript auraScript = pair.Key.GetAuraScript();
bool valid = true;
if (auraScript == null)
{
Log.outError(LogFilter.Scripts, "Functions GetAuraScript() of script `{0}` do not return object - script skipped", GetScriptName(pair.Value));
valid = false;
}
if (auraScript != null)
{
auraScript._Init(pair.Key.GetName(), spellEntry.Id);
auraScript._Register();
if (!auraScript._Validate(spellEntry))
valid = false;
}
if (!valid)
spellScriptsStorage.Remove(pair.Value);
}
++count;
}
Log.outInfo(LogFilter.ServerLoading, "Validated {0} scripts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public List<uint> GetSpellScriptsBounds(uint spellId)
{
return spellScriptsStorage.LookupByKey(spellId);
}
public string GetScriptName(uint id)
{
return id < scriptNamesStorage.Count ? scriptNamesStorage[(int)id] : "";
}
public uint GetScriptId(string name)
{
// use binary search to find the script name in the sorted vector
// assume "" is the first element
if (string.IsNullOrEmpty(name))
return 0;
if (!scriptNamesStorage.Contains(name))
return 0;
return (uint)scriptNamesStorage.IndexOf(name);
}
public uint GetAreaTriggerScriptId(uint triggerid)
{
return areaTriggerScriptStorage.LookupByKey(triggerid);
}
public Dictionary<uint, MultiMap<uint, ScriptInfo>> GetScriptsMapByType(ScriptsType type)
{
switch (type)
{
case ScriptsType.Spell:
return sSpellScripts;
case ScriptsType.Event:
return sEventScripts;
case ScriptsType.Waypoint:
return sWaypointScripts;
default:
return null;
}
}
public string GetScriptsTableNameByType(ScriptsType type)
{
switch (type)
{
case ScriptsType.Spell:
return "spell_scripts";
case ScriptsType.Event:
return "event_scripts";
case ScriptsType.Waypoint:
return "waypoint_scripts";
default:
return "";
}
}
//Creatures
public void LoadCreatureTemplates()
{
var time = Time.GetMSTime();
// 0 1 2 3 4 5 6 7 8
SQLResult result = DB.World.Query("SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, " +
//9 10 11 12 13 14 15 16 17 18 19
"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
"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, " +
//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, " +
//63 64 65 66 67 68 69 70 71
"InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, " +
//72 73 74 75 76 77
"RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 creatures. DB table `creature_template` is empty.");
return;
}
uint count = 0;
do
{
LoadCreatureTemplate(result.GetFields());
++count;
} while (result.NextRow());
// Checking needs to be done after loading because of the difficulty self referencing
foreach (var template in creatureTemplateStorage.Values)
CheckCreatureTemplate(template);
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature definitions in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
public void LoadCreatureTemplate(SQLFields fields)
{
uint entry = fields.Read<uint>(0);
CreatureTemplate creature = new CreatureTemplate();
creature.Entry = entry;
for (var i = 0; i < SharedConst.MaxCreatureDifficulties; ++i)
creature.DifficultyEntry[i] = fields.Read<uint>(1 + i);
for (var i = 0; i < 2; ++i)
creature.KillCredit[i] = fields.Read<uint>(4 + i);
creature.ModelId1 = fields.Read<uint>(6);
creature.ModelId2 = fields.Read<uint>(7);
creature.ModelId3 = fields.Read<uint>(8);
creature.ModelId4 = fields.Read<uint>(9);
creature.Name = fields.Read<string>(10);
creature.FemaleName = fields.Read<string>(11);
creature.SubName = fields.Read<string>(12);
creature.IconName = fields.Read<string>(13);
creature.GossipMenuId = fields.Read<uint>(14);
creature.Minlevel = fields.Read<short>(15);
creature.Maxlevel = fields.Read<short>(16);
creature.HealthScalingExpansion = fields.Read<int>(17);
creature.RequiredExpansion = fields.Read<uint>(18);
creature.VignetteID = fields.Read<uint>(19);
creature.Faction = fields.Read<uint>(20);
creature.Npcflag = (NPCFlags)fields.Read<uint>(21);
creature.SpeedWalk = fields.Read<float>(22);
creature.SpeedRun = fields.Read<float>(23);
creature.Scale = fields.Read<float>(24);
creature.Rank = (CreatureEliteType)fields.Read<uint>(25);
creature.DmgSchool = fields.Read<uint>(26);
creature.BaseAttackTime = fields.Read<uint>(27);
creature.RangeAttackTime = fields.Read<uint>(28);
creature.BaseVariance = fields.Read<float>(29);
creature.RangeVariance = fields.Read<float>(30);
creature.UnitClass = fields.Read<uint>(31);
creature.UnitFlags = (UnitFlags)fields.Read<uint>(32);
creature.UnitFlags2 = fields.Read<uint>(33);
creature.UnitFlags3 = fields.Read<uint>(34);
creature.DynamicFlags = fields.Read<uint>(35);
creature.Family = (CreatureFamily)fields.Read<byte>(36);
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>(44 + i - 1);
for (var i = 0; i < SharedConst.MaxCreatureSpells; ++i)
creature.Spells[i] = fields.Read<uint>(50 + i);
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);
}
public void LoadCreatureTemplateAddons()
{
var time = Time.GetMSTime();
// 0 1 2 3 4 5 6 7 8 9
SQLResult result = DB.World.Query("SELECT entry, path_id, mount, bytes1, bytes2, emote, aiAnimKit, movementAnimKit, meleeAnimKit, auras FROM creature_template_addon");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 creature template addon definitions. DB table `creature_template_addon` is empty.");
return;
}
uint count = 0;
do
{
uint entry = result.Read<uint>(0);
if (GetCreatureTemplate(entry) == null)
{
Log.outError(LogFilter.Sql, "Creature template (Entry: {0}) does not exist but has a record in `creature_template_addon`", entry);
continue;
}
CreatureAddon creatureAddon = new CreatureAddon();
creatureAddon.path_id = result.Read<uint>(1);
creatureAddon.mount = result.Read<uint>(2);
creatureAddon.bytes1 = result.Read<uint>(3);
creatureAddon.bytes2 = result.Read<uint>(4);
creatureAddon.emote = result.Read<uint>(5);
creatureAddon.aiAnimKit = result.Read<ushort>(6);
creatureAddon.movementAnimKit = result.Read<ushort>(7);
creatureAddon.meleeAnimKit = result.Read<ushort>(8);
var tokens = new StringArray(result.Read<string>(9), ' ');
creatureAddon.auras = new uint[tokens.Length];
byte i = 0;
for (var c = 0; c < tokens.Length; ++c)
{
string id = tokens[c].Trim().Replace(",", "");
if (!uint.TryParse(id, out uint spellId))
continue;
SpellInfo AdditionalSpellInfo = Global.SpellMgr.GetSpellInfo(spellId);
if (AdditionalSpellInfo == null)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong spell {1} defined in `auras` field in `creature_template_addon`.", entry, spellId);
continue;
}
if (AdditionalSpellInfo.HasAura(Difficulty.None, AuraType.ControlVehicle))
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has SPELL_AURA_CONTROL_VEHICLE aura {1} defined in `auras` field in `creature_template_addon`.", entry, spellId);
if (creatureAddon.auras.Contains(spellId))
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has duplicate aura (spell {1}) in `auras` field in `creature_template_addon`.", entry, spellId);
continue;
}
creatureAddon.auras[i++] = spellId;
}
if (creatureAddon.mount != 0)
{
if (CliDB.CreatureDisplayInfoStorage.LookupByKey(creatureAddon.mount) == null)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid displayInfoId ({1}) for mount defined in `creature_template_addon`", entry, creatureAddon.mount);
creatureAddon.mount = 0;
}
}
if (!CliDB.EmotesStorage.ContainsKey(creatureAddon.emote))
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid emote ({1}) defined in `creatureaddon`.", entry, creatureAddon.emote);
creatureAddon.emote = 0;
}
if (creatureAddon.aiAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(creatureAddon.aiAnimKit))
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid aiAnimKit ({1}) defined in `creature_template_addon`.", entry, creatureAddon.aiAnimKit);
creatureAddon.aiAnimKit = 0;
}
if (creatureAddon.movementAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(creatureAddon.movementAnimKit))
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid movementAnimKit ({1}) defined in `creature_template_addon`.", entry, creatureAddon.movementAnimKit);
creatureAddon.movementAnimKit = 0;
}
if (creatureAddon.meleeAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(creatureAddon.meleeAnimKit))
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid meleeAnimKit ({1}) defined in `creature_template_addon`.", entry, creatureAddon.meleeAnimKit);
creatureAddon.meleeAnimKit = 0;
}
creatureTemplateAddonStorage.Add(entry, creatureAddon);
count++;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature template addons in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
public void LoadCreatureAddons()
{
var time = Time.GetMSTime();
// 0 1 2 3 4 5 6 7 8 9
SQLResult result = DB.World.Query("SELECT guid, path_id, mount, bytes1, bytes2, emote, aiAnimKit, movementAnimKit, meleeAnimKit, auras FROM creature_addon");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 creature addon definitions. DB table `creature_addon` is empty.");
return;
}
uint count = 0;
do
{
ulong guid = result.Read<ulong>(0);
CreatureData creData = GetCreatureData(guid);
if (creData == null)
{
Log.outError(LogFilter.Sql, "Creature (GUID: {0}) does not exist but has a record in `creatureaddon`", guid);
continue;
}
CreatureAddon creatureAddon = new CreatureAddon();
creatureAddon.path_id = result.Read<uint>(1);
if (creData.movementType == (byte)MovementGeneratorType.Waypoint && creatureAddon.path_id == 0)
{
creData.movementType = (byte)MovementGeneratorType.Idle;
Log.outError(LogFilter.Sql, "Creature (GUID {0}) has movement type set to WAYPOINTMOTIONTYPE but no path assigned", guid);
}
creatureAddon.mount = result.Read<uint>(2);
creatureAddon.bytes1 = result.Read<uint>(3);
creatureAddon.bytes2 = result.Read<uint>(4);
creatureAddon.emote = result.Read<uint>(5);
creatureAddon.aiAnimKit = result.Read<ushort>(6);
creatureAddon.movementAnimKit = result.Read<ushort>(7);
creatureAddon.meleeAnimKit = result.Read<ushort>(8);
var tokens = new StringArray(result.Read<string>(9), ' ');
byte i = 0;
creatureAddon.auras = new uint[tokens.Length];
for (var c = 0; c < tokens.Length; ++c)
{
string id = tokens[c].Trim().Replace(",", "");
if (!uint.TryParse(id, out uint spellId))
continue;
SpellInfo AdditionalSpellInfo = Global.SpellMgr.GetSpellInfo(spellId);
if (AdditionalSpellInfo == null)
{
Log.outError(LogFilter.Sql, "Creature (GUID: {0}) has wrong spell {1} defined in `auras` field in `creatureaddon`.", guid, spellId);
continue;
}
if (AdditionalSpellInfo.HasAura(Difficulty.None, AuraType.ControlVehicle))
Log.outError(LogFilter.Sql, "Creature (GUID: {0}) has SPELL_AURA_CONTROL_VEHICLE aura {1} defined in `auras` field in `creature_addon`.", guid, spellId);
if (creatureAddon.auras.Contains(spellId))
{
Log.outError(LogFilter.Sql, "Creature (GUID: {0}) has duplicate aura (spell {1}) in `auras` field in `creature_addon`.", guid, spellId);
continue;
}
creatureAddon.auras[i++] = spellId;
}
if (creatureAddon.mount != 0)
{
if (!CliDB.CreatureDisplayInfoStorage.ContainsKey(creatureAddon.mount))
{
Log.outError(LogFilter.Sql, "Creature (GUID: {0}) has invalid displayInfoId ({1}) for mount defined in `creatureaddon`", guid, creatureAddon.mount);
creatureAddon.mount = 0;
}
}
if (!CliDB.EmotesStorage.ContainsKey(creatureAddon.emote))
{
Log.outError(LogFilter.Sql, "Creature (GUID: {0}) has invalid emote ({1}) defined in `creatureaddon`.", guid, creatureAddon.emote);
creatureAddon.emote = 0;
}
if (creatureAddon.aiAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(creatureAddon.aiAnimKit))
{
Log.outError(LogFilter.Sql, "Creature (Guid: {0}) has invalid aiAnimKit ({1}) defined in `creature_addon`.", guid, creatureAddon.aiAnimKit);
creatureAddon.aiAnimKit = 0;
}
if (creatureAddon.movementAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(creatureAddon.movementAnimKit))
{
Log.outError(LogFilter.Sql, "Creature (Guid: {0}) has invalid movementAnimKit ({1}) defined in `creature_addon`.", guid, creatureAddon.movementAnimKit);
creatureAddon.movementAnimKit = 0;
}
if (creatureAddon.meleeAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(creatureAddon.meleeAnimKit))
{
Log.outError(LogFilter.Sql, "Creature (Guid: {0}) has invalid meleeAnimKit ({1}) defined in `creature_addon`.", guid, creatureAddon.meleeAnimKit);
creatureAddon.meleeAnimKit = 0;
}
creatureAddonStorage.Add(guid, creatureAddon);
count++;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature addons in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
public void LoadCreatureQuestItems()
{
uint oldMSTime = Time.GetMSTime();
// 0 1 2
SQLResult result = DB.World.Query("SELECT CreatureEntry, ItemId, Idx FROM creature_questitem ORDER BY Idx ASC");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creature quest items. DB table `creature_questitem` is empty.");
return;
}
uint count = 0;
do
{
uint entry = result.Read<uint>(0);
uint item = result.Read<uint>(1);
uint idx = result.Read<uint>(2);
if (!creatureTemplateStorage.ContainsKey(entry))
{
Log.outError(LogFilter.Sql, "Table `creature_questitem` has data for nonexistent creature (entry: {0}, idx: {1}), skipped", entry, idx);
continue;
}
if (!CliDB.ItemStorage.ContainsKey(item))
{
Log.outError(LogFilter.Sql, "Table `creature_questitem` has nonexistent item (ID: {0}) in creature (entry: {1}, idx: {2}), skipped", item, entry, idx);
continue;
};
_creatureQuestItemStorage.Add(entry, item);
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature quest items in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadEquipmentTemplates()
{
var time = Time.GetMSTime();
// 0 1 2 3 4
SQLResult result = DB.World.Query("SELECT CreatureID, ID, ItemID1, AppearanceModID1, ItemVisual1, " +
//5 6 7 8 9 10
"ItemID2, AppearanceModID2, ItemVisual2, ItemID3, AppearanceModID3, ItemVisual3 " +
"FROM creature_equip_template");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 creature equipment templates. DB table `creature_equip_template` is empty!");
return;
}
uint count = 0;
do
{
uint entry = result.Read<uint>(0);
if (GetCreatureTemplate(entry) == null)
{
Log.outError(LogFilter.Sql, "Creature template (CreatureID: {0}) does not exist but has a record in `creature_equip_template`", entry);
continue;
}
uint id = result.Read<uint>(1);
EquipmentInfo equipmentInfo = new EquipmentInfo();
for (var i = 0; i < SharedConst.MaxEquipmentItems; ++i)
{
equipmentInfo.Items[i].ItemId = result.Read<uint>(2 + i * 3);
equipmentInfo.Items[i].AppearanceModId = result.Read<ushort>(3 + i * 3);
equipmentInfo.Items[i].ItemVisual = result.Read<ushort>(4 + i * 3);
if (equipmentInfo.Items[i].ItemId == 0)
continue;
var dbcItem = CliDB.ItemStorage.LookupByKey(equipmentInfo.Items[i].ItemId);
if (dbcItem == null)
{
Log.outError(LogFilter.Sql, "Unknown item (ID: {0}) in creature_equip_template.ItemID{1} for CreatureID = {2}, forced to 0.",
equipmentInfo.Items[i].ItemId, i + 1, entry);
equipmentInfo.Items[i].ItemId = 0;
continue;
}
if (Global.DB2Mgr.GetItemModifiedAppearance(equipmentInfo.Items[i].ItemId, equipmentInfo.Items[i].AppearanceModId) == null)
{
Log.outError(LogFilter.Sql, "Unknown item appearance for (ID: {0}, AppearanceModID: {1}) pair in creature_equip_template.ItemID{2} creature_equip_template.AppearanceModID{3} " +
"for CreatureID: {4} and ID: {5}, forced to default.",
equipmentInfo.Items[i].ItemId, equipmentInfo.Items[i].AppearanceModId, i + 1, i + 1, entry, id);
ItemModifiedAppearanceRecord defaultAppearance = Global.DB2Mgr.GetDefaultItemModifiedAppearance(equipmentInfo.Items[i].ItemId);
if (defaultAppearance != null)
equipmentInfo.Items[i].AppearanceModId = defaultAppearance.AppearanceModID;
else
equipmentInfo.Items[i].AppearanceModId = 0;
continue;
}
if (dbcItem.inventoryType != InventoryType.Weapon &&
dbcItem.inventoryType != InventoryType.Shield &&
dbcItem.inventoryType != InventoryType.Ranged &&
dbcItem.inventoryType != InventoryType.Weapon2Hand &&
dbcItem.inventoryType != InventoryType.WeaponMainhand &&
dbcItem.inventoryType != InventoryType.WeaponOffhand &&
dbcItem.inventoryType != InventoryType.Holdable &&
dbcItem.inventoryType != InventoryType.Thrown &&
dbcItem.inventoryType != InventoryType.RangedRight)
{
Log.outError(LogFilter.Sql, "Item (ID {0}) in creature_equip_template.ItemID{1} for CreatureID = {2} is not equipable in a hand, forced to 0.",
equipmentInfo.Items[i].ItemId, i + 1, entry);
equipmentInfo.Items[i].ItemId = 0;
}
}
equipmentInfoStorage.Add(entry, Tuple.Create(id, equipmentInfo));
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} equipment templates in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
public void LoadCreatureClassLevelStats()
{
var time = Time.GetMSTime();
creatureBaseStatsStorage.Clear();
// 0 1 2 3 4 5 6 7 8 9 10 11
SQLResult result = DB.World.Query("SELECT level, class, basemana, basearmor, attackpower, rangedattackpower, damage_base, damage_exp1, damage_exp2, damage_exp3, damage_exp4, damage_exp5 FROM creature_classlevelstats");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 creature base stats. DB table `creature_classlevelstats` is empty.");
return;
}
uint count = 0;
do
{
byte Level = result.Read<byte>(0);
byte _class = result.Read<byte>(1);
if (_class == 0 || ((1 << (_class - 1)) & (int)Class.ClassMaskAllCreatures) == 0)
Log.outError(LogFilter.Sql, "Creature base stats for level {0} has invalid class {1}", Level, _class);
CreatureBaseStats stats = new CreatureBaseStats();
for (var i = 0; i < (int)Expansion.Max; ++i)
{
stats.BaseHealth[i] = (uint)CliDB.GetGameTableColumnForClass(CliDB.NpcTotalHpGameTable[i].GetRow(Level), (Class)_class);
stats.BaseDamage[i] = CliDB.GetGameTableColumnForClass(CliDB.NpcDamageByClassGameTable[i].GetRow(Level), (Class)_class);
if (stats.BaseDamage[i] < 0.0f)
{
Log.outError(LogFilter.Sql, "Creature base stats for class {0}, level {1} has invalid negative base damage[{2}] - set to 0.0", _class, Level, i);
stats.BaseDamage[i] = 0.0f;
}
}
stats.BaseMana = result.Read<uint>(2);
stats.BaseArmor = result.Read<uint>(3);
stats.AttackPower = result.Read<ushort>(4);
stats.RangedAttackPower = result.Read<ushort>(5);
creatureBaseStatsStorage.Add(MathFunctions.MakePair16(Level, _class), stats);
++count;
} while (result.NextRow());
foreach (var creatureTemplate in creatureTemplateStorage.Values)
{
for (short lvl = creatureTemplate.Minlevel; lvl <= creatureTemplate.Maxlevel; ++lvl)
{
if (creatureBaseStatsStorage.LookupByKey(MathFunctions.MakePair16((uint)lvl, creatureTemplate.UnitClass)) == null)
Log.outError(LogFilter.Sql, "Missing base stats for creature class {0} level {1}", creatureTemplate.UnitClass, lvl);
}
}
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature base stats in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
public void LoadCreatureModelInfo()
{
var time = Time.GetMSTime();
SQLResult result = DB.World.Query("SELECT DisplayID, BoundingRadius, CombatReach, DisplayID_Other_Gender FROM creature_model_info");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 creature model definitions. DB table `creaturemodelinfo` is empty.");
return;
}
// List of model FileDataIDs that the client treats as invisible stalker
uint[] trigggerCreatureModelFileID = { 124640, 124641, 124642, 343863, 439302 };
uint count = 0;
do
{
uint displayId = result.Read<uint>(0);
var creatureDisplay = CliDB.CreatureDisplayInfoStorage.LookupByKey(displayId);
if (creatureDisplay == null)
{
Log.outError(LogFilter.Sql, "Table `creature_model_info` has a non-existent DisplayID (ID: {0}). Skipped.", displayId);
continue;
}
CreatureModelInfo modelInfo = new CreatureModelInfo();
modelInfo.BoundingRadius = result.Read<float>(1);
modelInfo.CombatReach = result.Read<float>(2);
modelInfo.DisplayIdOtherGender = result.Read<uint>(3);
modelInfo.gender = (sbyte)creatureDisplay.Gender;
// Checks
if (modelInfo.gender == (sbyte)Gender.Unknown)
modelInfo.gender = (sbyte)Gender.Male;
if (modelInfo.DisplayIdOtherGender != 0 && !CliDB.CreatureDisplayInfoStorage.ContainsKey(modelInfo.DisplayIdOtherGender))
{
Log.outError(LogFilter.Sql, "Table `creature_model_info` has a non-existent DisplayID_Other_Gender (ID: {0}) being used by DisplayID (ID: {1}).", modelInfo.DisplayIdOtherGender, displayId);
modelInfo.DisplayIdOtherGender = 0;
}
if (modelInfo.CombatReach < 0.1f)
modelInfo.CombatReach = SharedConst.DefaultCombatReach;
CreatureModelDataRecord modelData = CliDB.CreatureModelDataStorage.LookupByKey(creatureDisplay.ModelID);
if (modelData != null)
{
for (uint i = 0; i < 5; ++i)
{
if (modelData.FileDataID == trigggerCreatureModelFileID[i])
{
modelInfo.IsTrigger = true;
break;
}
}
}
creatureModelStorage.Add(displayId, modelInfo);
count++;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature model based info in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
public void CheckCreatureTemplate(CreatureTemplate cInfo)
{
if (cInfo == null)
return;
bool ok = true; // bool to allow continue outside this loop
for (uint diff = 0; diff < SharedConst.MaxCreatureDifficulties && ok; ++diff)
{
if (cInfo.DifficultyEntry[diff] == 0)
continue;
ok = false; // will be set to true at the end of this loop again
CreatureTemplate difficultyInfo = GetCreatureTemplate(cInfo.DifficultyEntry[diff]);
if (difficultyInfo == null)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has `difficulty_entry_{1}`={2} but creature entry {3} does not exist.",
cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff], cInfo.DifficultyEntry[diff]);
continue;
}
bool ok2 = true;
for (uint diff2 = 0; diff2 < SharedConst.MaxCreatureDifficulties && ok2; ++diff2)
{
ok2 = false;
if (_difficultyEntries[diff2].Contains(cInfo.Entry))
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) is listed as `difficulty_entry_{1}` of another creature, but itself lists {2} in `difficulty_entry_{3}`.",
cInfo.Entry, diff2 + 1, cInfo.DifficultyEntry[diff], diff + 1);
continue;
}
if (_difficultyEntries[diff2].Contains(cInfo.DifficultyEntry[diff]))
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) already listed as `difficulty_entry_{1}` for another entry.", cInfo.DifficultyEntry[diff], diff2 + 1);
continue;
}
if (_hasDifficultyEntries[diff2].Contains(cInfo.DifficultyEntry[diff]))
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has `difficulty_entry_{1}`={2} but creature entry {3} has itself a value in `difficulty_entry_{4}`.",
cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff], cInfo.DifficultyEntry[diff], diff2 + 1);
continue;
}
ok2 = true;
}
if (!ok2)
continue;
if (cInfo.HealthScalingExpansion > difficultyInfo.HealthScalingExpansion)
{
Log.outError(LogFilter.Sql, "Creature (Id: {0}, Expansion {1}) has different `HealthScalingExpansion` in difficulty {2} mode (Id: {3}, Expansion: {4}).",
cInfo.Entry, cInfo.HealthScalingExpansion, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.HealthScalingExpansion);
}
if (cInfo.Minlevel > difficultyInfo.Minlevel)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}, minlevel: {1}) has lower `minlevel` in difficulty {2} mode (Entry: {3}, minlevel: {4}).",
cInfo.Entry, cInfo.Minlevel, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.Minlevel);
}
if (cInfo.Maxlevel > difficultyInfo.Maxlevel)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}, maxlevel: {1}) has lower `maxlevel` in difficulty {2} mode (Entry: {3}, maxlevel: {4}).",
cInfo.Entry, cInfo.Maxlevel, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.Maxlevel);
}
if (cInfo.Faction != difficultyInfo.Faction)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}, faction: {1}) has different `faction` in difficulty {2} mode (Entry: {3}, faction: {4}).",
cInfo.Entry, cInfo.Faction, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.Faction);
}
if (cInfo.UnitClass != difficultyInfo.UnitClass)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}, class: {1}) has different `unit_class` in difficulty {2} mode (Entry: {3}, class: {4}).",
cInfo.Entry, cInfo.UnitClass, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.UnitClass);
continue;
}
if (cInfo.Npcflag != difficultyInfo.Npcflag)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has different `npcflag` in difficulty {1} mode (Entry: {2}).", cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]);
Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `npcflag`=`npcflag`^{0} WHERE `entry`={1};", cInfo.Npcflag ^ difficultyInfo.Npcflag, cInfo.DifficultyEntry[diff]);
continue;
}
if (cInfo.DmgSchool != difficultyInfo.DmgSchool)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}, `dmgschool`: {1}) has different `dmgschool` in difficulty {2} mode (Entry: {3}, `dmgschool`: {4}).",
cInfo.Entry, cInfo.DmgSchool, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.DmgSchool);
Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `dmgschool`={0} WHERE `entry`={1};", cInfo.DmgSchool, cInfo.DifficultyEntry[diff]);
}
if (cInfo.UnitFlags2 != difficultyInfo.UnitFlags2)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}, `unit_flags2`: {1}) has different `unit_flags2` in difficulty {2} mode (Entry: {3}, `unit_flags2`: {4}).",
cInfo.Entry, cInfo.UnitFlags2, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.UnitFlags2);
Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `unit_flags2`=`unit_flags2`^{0} WHERE `entry`={1};", cInfo.UnitFlags2 ^ difficultyInfo.UnitFlags2, cInfo.DifficultyEntry[diff]);
}
if (cInfo.Family != difficultyInfo.Family)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}, family: {1}) has different `family` in difficulty {2} mode (Entry: {3}, family: {4}).",
cInfo.Entry, cInfo.Family, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.Family);
}
if (cInfo.TrainerClass != difficultyInfo.TrainerClass)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has different `trainer_class` 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}).",
cInfo.Entry, cInfo.CreatureType, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.CreatureType);
}
if (cInfo.VehicleId == 0 && difficultyInfo.VehicleId != 0)
{
Log.outError(LogFilter.Sql, "Non-vehicle Creature (Entry: {0}, VehicleId: {1}) has `VehicleId` set in difficulty {2} mode (Entry: {3}, VehicleId: {4}).",
cInfo.Entry, cInfo.VehicleId, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.VehicleId);
}
if (cInfo.RegenHealth != difficultyInfo.RegenHealth)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}, RegenHealth: {1}) has different `RegenHealth` in difficulty {2} mode (Entry: {3}, RegenHealth: {4}).",
cInfo.Entry, cInfo.RegenHealth, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.RegenHealth);
Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `RegenHealth`={0} WHERE `entry`={1};", cInfo.RegenHealth, cInfo.DifficultyEntry[diff]);
}
uint differenceMask = cInfo.MechanicImmuneMask & (~difficultyInfo.MechanicImmuneMask);
if (differenceMask != 0)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}, mechanic_immune_mask: {1}) has weaker immunities in difficulty {2} mode (Entry: {3}, mechanic_immune_mask: {4}).",
cInfo.Entry, cInfo.MechanicImmuneMask, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.MechanicImmuneMask);
Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `mechanic_immune_mask`=`mechanic_immune_mask`|{0} WHERE `entry`={1};", differenceMask, cInfo.DifficultyEntry[diff]);
}
differenceMask = (uint)((cInfo.FlagsExtra ^ difficultyInfo.FlagsExtra) & (~CreatureFlagsExtra.InstanceBind));
if (differenceMask != 0)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}, flags_extra: {1}) has different `flags_extra` in difficulty {2} mode (Entry: {3}, flags_extra: {4}).",
cInfo.Entry, cInfo.FlagsExtra, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.FlagsExtra);
Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `flags_extra`=`flags_extra`^{0} WHERE `entry`={1};", differenceMask, cInfo.DifficultyEntry[diff]);
}
if (difficultyInfo.AIName.IsEmpty())
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists difficulty {1} mode entry {2} with `AIName` filled in. `AIName` of difficulty 0 mode creature is always used instead.",
cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]);
continue;
}
if (difficultyInfo.ScriptID != 0)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists difficulty {1} mode entry {2} with `ScriptName` filled in. `ScriptName` of difficulty 0 mode creature is always used instead.",
cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]);
continue;
}
_hasDifficultyEntries[diff].Add(cInfo.Entry);
_difficultyEntries[diff].Add(cInfo.DifficultyEntry[diff]);
ok = true;
}
if (!CliDB.FactionTemplateStorage.ContainsKey(cInfo.Faction))
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has non-existing faction template ({1}).", cInfo.Entry, cInfo.Faction);
// used later for scale
CreatureDisplayInfoRecord displayScaleEntry = null;
if (cInfo.ModelId1 != 0)
{
CreatureDisplayInfoRecord displayEntry = CliDB.CreatureDisplayInfoStorage.LookupByKey(cInfo.ModelId1);
if (displayEntry == null)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists non-existing Modelid1 id ({1}), this can crash the client.", cInfo.Entry, cInfo.ModelId1);
cInfo.ModelId1 = 0;
}
else
displayScaleEntry = displayEntry;
CreatureModelInfo modelInfo = GetCreatureModelInfo(cInfo.ModelId1);
if (modelInfo == null)
Log.outError(LogFilter.Sql, "No model data exist for `Modelid1` = {0} listed by creature (Entry: {1}).", cInfo.ModelId1, cInfo.Entry);
}
if (cInfo.ModelId2 != 0)
{
CreatureDisplayInfoRecord displayEntry = CliDB.CreatureDisplayInfoStorage.LookupByKey(cInfo.ModelId2);
if (displayEntry == null)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists non-existing Modelid2 id ({1}), this can crash the client.", cInfo.Entry, cInfo.ModelId2);
cInfo.ModelId2 = 0;
}
else if (displayScaleEntry == null)
displayScaleEntry = displayEntry;
CreatureModelInfo modelInfo = GetCreatureModelInfo(cInfo.ModelId2);
if (modelInfo == null)
Log.outError(LogFilter.Sql, "No model data exist for `Modelid2` = {0} listed by creature (Entry: {1}).", cInfo.ModelId2, cInfo.Entry);
}
if (cInfo.ModelId3 != 0)
{
CreatureDisplayInfoRecord displayEntry = CliDB.CreatureDisplayInfoStorage.LookupByKey(cInfo.ModelId3);
if (displayEntry == null)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists non-existing Modelid3 id ({1}), this can crash the client.", cInfo.Entry, cInfo.ModelId3);
cInfo.ModelId3 = 0;
}
else if (displayScaleEntry == null)
displayScaleEntry = displayEntry;
CreatureModelInfo modelInfo = GetCreatureModelInfo(cInfo.ModelId3);
if (modelInfo == null)
Log.outError(LogFilter.Sql, "No model data exist for `Modelid3` = {0} listed by creature (Entry: {1}).", cInfo.ModelId3, cInfo.Entry);
}
if (cInfo.ModelId4 != 0)
{
CreatureDisplayInfoRecord displayEntry = CliDB.CreatureDisplayInfoStorage.LookupByKey(cInfo.ModelId4);
if (displayEntry == null)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists non-existing Modelid4 id ({1}), this can crash the client.", cInfo.Entry, cInfo.ModelId4);
cInfo.ModelId4 = 0;
}
else if (displayScaleEntry == null)
displayScaleEntry = displayEntry;
CreatureModelInfo modelInfo = GetCreatureModelInfo(cInfo.ModelId4);
if (modelInfo == null)
Log.outError(LogFilter.Sql, "No model data exist for `Modelid4` = {0} listed by creature (Entry: {1}).", cInfo.ModelId4, cInfo.Entry);
}
if (displayScaleEntry == null)
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) does not have any existing display id in Modelid1/Modelid2/Modelid3/Modelid4.", cInfo.Entry);
for (int k = 0; k < SharedConst.MaxCreatureKillCredit; ++k)
{
if (cInfo.KillCredit[k] != 0)
{
if (GetCreatureTemplate(cInfo.KillCredit[k]) == null)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists non-existing creature entry {1} in `KillCredit{2}`.", cInfo.Entry, cInfo.KillCredit[k], k + 1);
cInfo.KillCredit[k] = 0;
}
}
}
if (cInfo.UnitClass == 0 || ((1 << ((int)cInfo.UnitClass - 1)) & (int)Class.ClassMaskAllCreatures) == 0)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid unit_class ({1}) in creature_template. Set to 1 (UNIT_CLASS_WARRIOR).", cInfo.Entry, cInfo.UnitClass);
cInfo.UnitClass = (uint)Class.Warrior;
}
if (cInfo.DmgSchool >= (uint)SpellSchools.Max)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid spell school value ({1}) in `dmgschool`.", cInfo.Entry, cInfo.DmgSchool);
cInfo.DmgSchool = (uint)SpellSchools.Normal;
}
if (cInfo.BaseAttackTime == 0)
cInfo.BaseAttackTime = SharedConst.BaseAttackTime;
if (cInfo.RangeAttackTime == 0)
cInfo.RangeAttackTime = SharedConst.BaseAttackTime;
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);
cInfo.SpeedWalk = 1.0f;
}
if (cInfo.SpeedRun == 0.0f)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong value ({1}) in speed_run, set to 1.14286.", cInfo.Entry, cInfo.SpeedRun);
cInfo.SpeedRun = 1.14286f;
}
if (cInfo.CreatureType != 0 && !CliDB.CreatureTypeStorage.ContainsKey((uint)cInfo.CreatureType))
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid creature type ({1}) in `type`.", cInfo.Entry, cInfo.CreatureType);
cInfo.CreatureType = CreatureType.Humanoid;
}
// must exist or used hidden but used in data horse case
if (cInfo.Family != 0 && !CliDB.CreatureFamilyStorage.ContainsKey(cInfo.Family) && cInfo.Family != CreatureFamily.HorseCustom)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid creature family ({1}) in `family`.", cInfo.Entry, cInfo.Family);
cInfo.Family = CreatureFamily.None;
}
if (cInfo.InhabitType <= 0 || cInfo.InhabitType > InhabitType.Anywhere)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong value ({1}) in `InhabitType`, creature will not correctly walk/swim/fly.", cInfo.Entry, cInfo.InhabitType);
cInfo.InhabitType = InhabitType.Anywhere;
}
if (cInfo.HoverHeight < 0.0f)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong value ({1}) in `HoverHeight`", cInfo.Entry, cInfo.HoverHeight);
cInfo.HoverHeight = 1.0f;
}
if (cInfo.VehicleId != 0)
{
if (!CliDB.VehicleStorage.ContainsKey(cInfo.VehicleId))
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has a non-existing VehicleId ({1}). This *WILL* cause the client to freeze!", cInfo.Entry, cInfo.VehicleId);
cInfo.VehicleId = 0;
}
}
for (byte j = 0; j < SharedConst.MaxCreatureSpells; ++j)
{
if (cInfo.Spells[j] != 0 && !Global.SpellMgr.HasSpellInfo(cInfo.Spells[j]))
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has non-existing Spell{1} ({2}), set to 0.", cInfo.Entry, j + 1, cInfo.Spells[j]);
cInfo.Spells[j] = 0;
}
}
if (cInfo.MovementType >= (uint)MovementGeneratorType.MaxDB)
{
Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong movement generator type ({1}), ignored and set to IDLE.", cInfo.Entry, cInfo.MovementType);
cInfo.MovementType = (uint)MovementGeneratorType.Idle;
}
/// if not set custom creature scale then load scale from CreatureDisplayInfo.dbc
if (cInfo.Scale <= 0.0f)
{
if (displayScaleEntry != null)
cInfo.Scale = displayScaleEntry.CreatureModelScale;
else
cInfo.Scale = 1.0f;
}
if (cInfo.HealthScalingExpansion < (int)Expansion.LevelCurrent || cInfo.HealthScalingExpansion > ((int)Expansion.Max - 1))
{
Log.outError(LogFilter.Sql, "Table `creature_template` lists creature (Id: {0}) with invalid `HealthScalingExpansion` {1}. Ignored and set to 0.", cInfo.Entry, cInfo.HealthScalingExpansion);
cInfo.HealthScalingExpansion = 0;
}
if (cInfo.RequiredExpansion > (int)(Expansion.Max - 1))
{
Log.outError(LogFilter.Sql, "Table `creature_template` lists creature (Entry: {0}) with `RequiredExpansion` {1}. Ignored and set to 0.", cInfo.Entry, cInfo.RequiredExpansion);
cInfo.RequiredExpansion = 0;
}
uint badFlags = (uint)(cInfo.FlagsExtra & ~CreatureFlagsExtra.DBAllowed);
if (badFlags != 0)
{
Log.outError(LogFilter.Sql, "Table `creature_template` lists creature (Entry: {0}) with disallowed `flags_extra` {1}, removing incorrect flag.", cInfo.Entry, badFlags);
cInfo.FlagsExtra &= CreatureFlagsExtra.DBAllowed;
}
// -1, as expansion, is used in CreatureDifficulty.db2 for
// auto-updating the levels of creatures having their expansion
// set to that value to the current expansion's max leveling level
if (cInfo.HealthScalingExpansion == (int)Expansion.LevelCurrent)
{
cInfo.Minlevel = (short)(WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) + cInfo.Minlevel);
cInfo.Maxlevel = (short)(WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) + cInfo.Maxlevel);
cInfo.HealthScalingExpansion = (int)Expansion.WarlordsOfDraenor;
}
if (cInfo.Minlevel < 1 || cInfo.Minlevel > SharedConst.StrongMaxLevel)
{
Log.outError(LogFilter.Sql, "Creature (ID: {0}): MinLevel {1} is not within [1, 255], value has been set to 1.", cInfo.Entry, cInfo.Minlevel);
cInfo.Minlevel = 1;
}
if (cInfo.Maxlevel < 1 || cInfo.Maxlevel > SharedConst.StrongMaxLevel)
{
Log.outError(LogFilter.Sql, "Creature (ID: {0}): MaxLevel {1} is not within [1, 255], value has been set to 1.", cInfo.Entry, cInfo.Maxlevel);
cInfo.Maxlevel = 1;
}
cInfo.ModDamage *= Creature._GetDamageMod(cInfo.Rank);
}
public void LoadLinkedRespawn()
{
uint oldMSTime = Time.GetMSTime();
linkedRespawnStorage.Clear();
// 0 1 2
SQLResult result = DB.World.Query("SELECT guid, linkedGuid, linkType FROM linked_respawn ORDER BY guid ASC");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 linked respawns. DB table `linked_respawn` is empty.");
return;
}
do
{
ulong guidLow = result.Read<ulong>(0);
ulong linkedGuidLow = result.Read<ulong>(1);
byte linkType = result.Read<byte>(2);
ObjectGuid guid = ObjectGuid.Empty;
ObjectGuid linkedGuid = ObjectGuid.Empty;
bool error = false;
switch ((CreatureLinkedRespawnType)linkType)
{
case CreatureLinkedRespawnType.CreatureToCreature:
{
CreatureData slave = GetCreatureData(guidLow);
if (slave == null)
{
Log.outError(LogFilter.Sql, "Couldn't get creature data for GUIDLow {0}", guidLow);
error = true;
break;
}
CreatureData master = GetCreatureData(linkedGuidLow);
if (master == null)
{
Log.outError(LogFilter.Sql, "Couldn't get creature data for GUIDLow {0}", linkedGuidLow);
error = true;
break;
}
MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid);
if (map == null || !map.Instanceable() || (master.mapid != slave.mapid))
{
Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow);
error = true;
break;
}
if (!Convert.ToBoolean(master.spawnMask & slave.spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
{
Log.outError(LogFilter.Sql, "LinkedRespawn: Creature '{0}' linking to '{1}' with not corresponding spawnMask", guidLow, linkedGuidLow);
error = true;
break;
}
guid = ObjectGuid.Create(HighGuid.Creature, slave.mapid, slave.id, guidLow);
linkedGuid = ObjectGuid.Create(HighGuid.Creature, master.mapid, master.id, linkedGuidLow);
break;
}
case CreatureLinkedRespawnType.CreatureToGO:
{
CreatureData slave = GetCreatureData(guidLow);
if (slave == null)
{
Log.outError(LogFilter.Sql, "Couldn't get creature data for GUIDLow {0}", guidLow);
error = true;
break;
}
GameObjectData master = GetGOData(linkedGuidLow);
if (master == null)
{
Log.outError(LogFilter.Sql, "Couldn't get gameobject data for GUIDLow {0}", linkedGuidLow);
error = true;
break;
}
MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid);
if (map == null || !map.Instanceable() || (master.mapid != slave.mapid))
{
Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow);
error = true;
break;
}
if (!Convert.ToBoolean(master.spawnMask & slave.spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
{
Log.outError(LogFilter.Sql, "LinkedRespawn: Creature '{0}' linking to '{1}' with not corresponding spawnMask", guidLow, linkedGuidLow);
error = true;
break;
}
guid = ObjectGuid.Create(HighGuid.Creature, slave.mapid, slave.id, guidLow);
linkedGuid = ObjectGuid.Create(HighGuid.GameObject, master.mapid, master.id, linkedGuidLow);
break;
}
case CreatureLinkedRespawnType.GOToGO:
{
GameObjectData slave = GetGOData(guidLow);
if (slave == null)
{
Log.outError(LogFilter.Sql, "Couldn't get gameobject data for GUIDLow {0}", guidLow);
error = true;
break;
}
GameObjectData master = GetGOData(linkedGuidLow);
if (master == null)
{
Log.outError(LogFilter.Sql, "Couldn't get gameobject data for GUIDLow {0}", linkedGuidLow);
error = true;
break;
}
MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid);
if (map == null || !map.Instanceable() || (master.mapid != slave.mapid))
{
Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow);
error = true;
break;
}
if (!Convert.ToBoolean(master.spawnMask & slave.spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
{
Log.outError(LogFilter.Sql, "LinkedRespawn: Creature '{0}' linking to '{1}' with not corresponding spawnMask", guidLow, linkedGuidLow);
error = true;
break;
}
guid = ObjectGuid.Create(HighGuid.GameObject, slave.mapid, slave.id, guidLow);
linkedGuid = ObjectGuid.Create(HighGuid.GameObject, master.mapid, master.id, linkedGuidLow);
break;
}
case CreatureLinkedRespawnType.GOToCreature:
{
GameObjectData slave = GetGOData(guidLow);
if (slave == null)
{
Log.outError(LogFilter.Sql, "Couldn't get gameobject data for GUIDLow {0}", guidLow);
error = true;
break;
}
CreatureData master = GetCreatureData(linkedGuidLow);
if (master == null)
{
Log.outError(LogFilter.Sql, "Couldn't get creature data for GUIDLow {0}", linkedGuidLow);
error = true;
break;
}
MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid);
if (map == null || !map.Instanceable() || (master.mapid != slave.mapid))
{
Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow);
error = true;
break;
}
if (!Convert.ToBoolean(master.spawnMask & slave.spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
{
Log.outError(LogFilter.Sql, "LinkedRespawn: Creature '{0}' linking to '{1}' with not corresponding spawnMask", guidLow, linkedGuidLow);
error = true;
break;
}
guid = ObjectGuid.Create(HighGuid.GameObject, slave.mapid, slave.id, guidLow);
linkedGuid = ObjectGuid.Create(HighGuid.Creature, master.mapid, master.id, linkedGuidLow);
break;
}
}
if (!error)
linkedRespawnStorage[guid] = linkedGuid;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} linked respawns in {1} ms", linkedRespawnStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadNPCText()
{
uint oldMSTime = Time.GetMSTime();
_npcTextStorage.Clear();
SQLResult result = DB.World.Query("SELECT ID, Probability0, Probability1, Probability2, Probability3, Probability4, Probability5, Probability6, Probability7, " +
"BroadcastTextID0, BroadcastTextID1, BroadcastTextID2, BroadcastTextID3, BroadcastTextID4, BroadcastTextID5, BroadcastTextID6, BroadcastTextID7 FROM npc_text");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 npc texts, table is empty!");
return;
}
do
{
uint textID = result.Read<uint>(0);
if (textID == 0)
{
Log.outError(LogFilter.Sql, "Table `npc_text` has record wit reserved id 0, ignore.");
continue;
}
NpcText npcText = new NpcText();
for (int i = 0; i < SharedConst.MaxNpcTextOptions; i++)
{
npcText.Data[i].Probability = result.Read<float>(1 + i);
npcText.Data[i].BroadcastTextID = result.Read<uint>(9 + i);
}
for (int i = 0; i < SharedConst.MaxNpcTextOptions; i++)
{
if (npcText.Data[i].BroadcastTextID != 0)
{
if (!CliDB.BroadcastTextStorage.ContainsKey(npcText.Data[i].BroadcastTextID))
{
Log.outError(LogFilter.Sql, "NPCText (Id: {0}) has a non-existing or incompatible BroadcastText (ID: {1}, Index: {2})", textID, npcText.Data[i].BroadcastTextID, i);
npcText.Data[i].BroadcastTextID = 0;
}
}
}
for (byte i = 0; i < SharedConst.MaxNpcTextOptions; i++)
{
if (npcText.Data[i].Probability > 0 && npcText.Data[i].BroadcastTextID == 0)
{
Log.outError(LogFilter.Sql, "NPCText (ID: {0}) has a probability (Index: {1}) set, but no BroadcastTextID to go with it", textID, i);
npcText.Data[i].Probability = 0;
}
}
_npcTextStorage[textID] = npcText;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} npc texts in {1} ms", _npcTextStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadTrainers()
{
uint oldMSTime = Time.GetMSTime();
// 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())
{
do
{
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);
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 (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());
}
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()
{
var time = Time.GetMSTime();
// For reload case
cacheVendorItemStorage.Clear();
List<uint> skipvendors = new List<uint>();
SQLResult result = DB.World.Query("SELECT entry, item, maxcount, incrtime, ExtendedCost, type FROM npc_vendor ORDER BY entry, slot ASC");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 Vendors. DB table `npc_vendor` is empty!");
return;
}
uint count = 0;
do
{
uint entry = result.Read<uint>(0);
int itemid = result.Read<int>(1);
// if item is a negative, its a reference
if (itemid < 0)
count += LoadReferenceVendor((int)entry, -itemid, 0, skipvendors);
else
{
int maxcount = result.Read<int>(2);
uint incrtime = result.Read<uint>(3);
uint ExtendedCost = result.Read<uint>(4);
ItemVendorType type = (ItemVendorType)result.Read<byte>(5);
if (!IsVendorItemValid(entry, (uint)itemid, maxcount, incrtime, ExtendedCost, type, null, skipvendors))
continue;
if (cacheVendorItemStorage.LookupByKey(entry) == null)
cacheVendorItemStorage.Add(entry, new VendorItemData());
cacheVendorItemStorage[entry].AddItem((uint)itemid, maxcount, incrtime, ExtendedCost, type);
++count;
}
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Vendors in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
uint LoadReferenceVendor(int vendor, int item, byte type, List<uint> skip_vendors)
{
// find all items from the reference vendor
PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_NPC_VENDOR_REF);
stmt.AddValue(0, item);
stmt.AddValue(1, type);
SQLResult result = DB.World.Query(stmt);
if (result.IsEmpty())
return 0;
uint count = 0;
do
{
int item_id = result.Read<int>(0);
// if item is a negative, its a reference
if (item_id < 0)
count += LoadReferenceVendor(vendor, -item_id, type, skip_vendors);
else
{
int maxcount = result.Read<int>(1);
uint incrtime = result.Read<uint>(2);
uint ExtendedCost = result.Read<uint>(3);
ItemVendorType _type = (ItemVendorType)result.Read<byte>(4);
if (!IsVendorItemValid((uint)vendor, (uint)item_id, maxcount, incrtime, ExtendedCost, _type, null, skip_vendors))
continue;
VendorItemData vList = cacheVendorItemStorage[(uint)vendor];
vList.AddItem((uint)item_id, maxcount, incrtime, ExtendedCost, _type);
++count;
}
} while (result.NextRow());
return count;
}
public void LoadCreatures()
{
var time = Time.GetMSTime();
// 0 1 2 3 4 5 6 7 8 9 10
SQLResult result = DB.World.Query("SELECT creature.guid, id, map, modelid, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, spawndist, " +
// 11 12 13 14 15 16 17 18 19 20 21
"currentwaypoint, curhealth, curmana, MovementType, spawnMask, eventEntry, pool_entry, creature.npcflag, creature.unit_flags, creature.unit_flags2, creature.unit_flags3, " +
// 22 23 24 25
"creature.dynamicflags, creature.phaseid, creature.phasegroup, creature.ScriptName " +
"FROM creature LEFT OUTER JOIN game_event_creature ON creature.guid = game_event_creature.guid LEFT OUTER JOIN pool_creature ON creature.guid = pool_creature.guid");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 creatures. DB table `creature` is empty.");
return;
}
Dictionary<uint, uint> spawnMasks = new Dictionary<uint, uint>();
foreach (var mapDifficultyPair in Global.DB2Mgr.GetMapDifficulties())
{
foreach (var difficultyPair in mapDifficultyPair.Value)
{
if (!spawnMasks.ContainsKey(mapDifficultyPair.Key))
spawnMasks[mapDifficultyPair.Key] = 0;
spawnMasks[mapDifficultyPair.Key] |= (uint)(1 << (int)difficultyPair.Key);
}
}
uint count = 0;
do
{
ulong guid = result.Read<ulong>(0);
uint entry = result.Read<uint>(1);
CreatureTemplate cInfo = GetCreatureTemplate(entry);
if (cInfo == null)
{
Log.outError(LogFilter.Sql, "Table `creature` has creature (GUID: {0}) with non existing creature entry {1}, skipped.", guid, entry);
continue;
}
CreatureData data = new CreatureData();
data.id = entry;
data.mapid = result.Read<ushort>(2);
data.displayid = result.Read<uint>(3);
data.equipmentId = result.Read<int>(4);
data.posX = result.Read<float>(5);
data.posY = result.Read<float>(6);
data.posZ = result.Read<float>(7);
data.orientation = result.Read<float>(8);
data.spawntimesecs = result.Read<uint>(9);
data.spawndist = result.Read<float>(10);
data.currentwaypoint = result.Read<uint>(11);
data.curhealth = result.Read<uint>(12);
data.curmana = result.Read<uint>(13);
data.movementType = result.Read<byte>(14);
data.spawnMask = result.Read<uint>(15);
short gameEvent = result.Read<short>(16);
uint PoolId = result.Read<uint>(17);
data.npcflag = result.Read<ulong>(18);
data.unit_flags = result.Read<uint>(19);
data.unit_flags2 = result.Read<uint>(20);
data.unit_flags3 = result.Read<uint>(21);
data.dynamicflags = result.Read<uint>(22);
data.phaseid = result.Read<uint>(23);
data.phaseGroup = result.Read<uint>(24);
data.ScriptId = GetScriptId(result.Read<string>(25));
if (data.ScriptId == 0)
data.ScriptId = cInfo.ScriptID;
var mapEntry = CliDB.MapStorage.LookupByKey(data.mapid);
if (mapEntry == null)
{
Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0}) that spawned at not existed map (Id: {1}), skipped.", guid, data.mapid);
continue;
}
//if (!_transportMaps.Contains(data.mapid) && (spawnMasks.ContainsKey(data.mapid) && Convert.ToBoolean(data.spawnMask & ~spawnMasks[data.mapid])))
//Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0}) that have wrong spawn mask {1} including not supported difficulty modes for map (Id: {2}) spawnMasks[data.mapid]: {3}.", guid, data.spawnMask, data.mapid, spawnMasks[data.mapid]);
bool ok = true;
for (uint diff = 0; diff < SharedConst.MaxCreatureDifficulties && ok; ++diff)
{
if (_difficultyEntries[diff].Contains(data.id))
{
Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0}) that listed as difficulty {1} template (entry: {2}) in `creaturetemplate`, skipped.", guid, diff + 1, data.id);
ok = false;
}
}
if (!ok)
continue;
// -1 random, 0 no equipment,
if (data.equipmentId != 0)
{
if (GetEquipmentInfo(data.id, data.equipmentId) == null)
{
Log.outError(LogFilter.Sql, "Table `creature` have creature (Entry: {0}) with equipmentid {1} not found in table `creatureequiptemplate`, set to no equipment.", data.id, data.equipmentId);
data.equipmentId = 0;
}
}
if (cInfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.InstanceBind))
{
if (!mapEntry.IsDungeon())
Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `creature_template`.`flagsextra` including CREATUREFLAGEXTRAINSTANCEBIND " +
"but creature are not in instance.", guid, data.id);
}
if (data.spawndist < 0.0f)
{
Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `spawndist`< 0, set to 0.", guid, data.id);
data.spawndist = 0.0f;
}
else if (data.movementType == (byte)MovementGeneratorType.Random)
{
if (MathFunctions.fuzzyEq(data.spawndist, 0.0f))
{
Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `MovementType`=1 (random movement) but with `spawndist`=0, replace by idle movement type (0).", guid, data.id);
data.movementType = (byte)MovementGeneratorType.Idle;
}
}
else if (data.movementType == (byte)MovementGeneratorType.Idle)
{
if (data.spawndist != 0.0f)
{
Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `MovementType`=0 (idle) have `spawndist`<>0, set to 0.", guid, data.id);
data.spawndist = 0.0f;
}
}
if (Math.Abs(data.orientation) > 2 * MathFunctions.PI)
{
Log.outError(LogFilter.Sql, "Table `creature` has creature (GUID: {0} Entry: {1}) with abs(`orientation`) > 2*PI (orientation is expressed in radians), normalized.", guid, data.id);
data.orientation = Position.NormalizeOrientation(data.orientation);
}
data.phaseMask = 1;
if (data.phaseGroup != 0 && data.phaseid != 0)
{
Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with both `phaseid` and `phasegroup` set, `phasegroup` set to 0", guid, data.id);
data.phaseGroup = 0;
}
if (data.phaseid != 0)
{
if (!CliDB.PhaseStorage.ContainsKey(data.phaseid))
{
Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `phaseid` {2} does not exist, set to 0", guid, data.id, data.phaseid);
data.phaseid = 0;
}
}
if (data.phaseGroup != 0)
{
if (Global.DB2Mgr.GetPhasesForGroup(data.phaseGroup).Empty())
{
Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `phasegroup` {2} does not exist, set to 0", guid, data.id, data.phaseGroup);
data.phaseGroup = 0;
}
}
if (WorldConfig.GetBoolValue(WorldCfg.CalculateCreatureZoneAreaData))
{
uint zoneId = 0;
uint areaId = 0;
Global.MapMgr.GetZoneAndAreaId(out zoneId, out areaId, data.mapid, data.posX, data.posY, data.posZ);
PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_ZONE_AREA_DATA);
stmt.AddValue(0, zoneId);
stmt.AddValue(1, areaId);
stmt.AddValue(2, guid);
DB.World.Execute(stmt);
}
// Add to grid if not managed by the game event or pool system
if (gameEvent == 0 && PoolId == 0)
AddCreatureToGrid(guid, data);
creatureDataStorage[guid] = data;
count++;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creatures in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
public void AddCreatureToGrid(ulong guid, CreatureData data)
{
uint mask = data.spawnMask;
for (byte i = 0; mask != 0; i++, mask >>= 1)
{
if (Convert.ToBoolean(mask & 1))
{
CellCoord cellCoord = GridDefines.ComputeCellCoord(data.posX, data.posY);
var cellguids = CreateCellObjectGuids(data.mapid, i, cellCoord);
cellguids.creatures.Add(guid);
}
}
}
public void RemoveCreatureFromGrid(ulong guid, CreatureData data)
{
uint mask = data.spawnMask;
for (byte i = 0; mask != 0; i++, mask >>= 1)
{
if (Convert.ToBoolean(mask & 1))
{
CellCoord cellCoord = GridDefines.ComputeCellCoord(data.posX, data.posY);
CellObjectGuids cellguids = GetCellObjectGuids(data.mapid, i, cellCoord.GetId());
if (cellguids == null)
return;
cellguids.creatures.Remove(guid);
}
}
}
public ulong AddCreatureData(uint entry, uint team, uint mapId, float x, float y, float z, float o, uint spawntimedelay)
{
CreatureTemplate cInfo = GetCreatureTemplate(entry);
if (cInfo == null)
return 0;
uint level = cInfo.Minlevel == cInfo.Maxlevel ? (uint)cInfo.Minlevel : RandomHelper.URand(cInfo.Minlevel, cInfo.Maxlevel); // Only used for extracting creature base stats
CreatureBaseStats stats = GetCreatureBaseStats(level, cInfo.UnitClass);
Map map = Global.MapMgr.CreateBaseMap(mapId);
if (!map)
return 0;
ulong guid = GenerateCreatureSpawnId();
CreatureData data = NewOrExistCreatureData(guid);
data.id = entry;
data.mapid = (ushort)mapId;
data.displayid = 0;
data.equipmentId = 0;
data.posX = x;
data.posY = y;
data.posZ = z;
data.orientation = o;
data.spawntimesecs = spawntimedelay;
data.spawndist = 0;
data.currentwaypoint = 0;
data.curhealth = stats.GenerateHealth(cInfo);
data.curmana = stats.GenerateMana(cInfo);
data.movementType = (byte)cInfo.MovementType;
data.spawnMask = 1;
data.phaseMask = PhaseMasks.Normal;
data.dbData = false;
data.npcflag = (uint)cInfo.Npcflag;
data.unit_flags = (uint)cInfo.UnitFlags;
data.dynamicflags = cInfo.DynamicFlags;
AddCreatureToGrid(guid, data);
// We use spawn coords to spawn
if (!map.Instanceable() && !map.IsRemovalGrid(x, y))
{
Creature creature = new Creature();
if (!creature.LoadCreatureFromDB(guid, map))
{
Log.outError(LogFilter.Server, "AddCreature: Cannot add creature entry {0} to map", entry);
return 0;
}
}
return guid;
}
public List<uint> GetCreatureQuestItemList(uint id)
{
return _creatureQuestItemStorage.LookupByKey(id);
}
public CreatureAddon GetCreatureAddon(ulong lowguid)
{
return creatureAddonStorage.LookupByKey(lowguid);
}
public CreatureTemplate GetCreatureTemplate(uint entry)
{
return creatureTemplateStorage.LookupByKey(entry);
}
public CreatureAddon GetCreatureTemplateAddon(uint entry)
{
return creatureTemplateAddonStorage.LookupByKey(entry);
}
public Dictionary<uint, CreatureTemplate> GetCreatureTemplates()
{
return creatureTemplateStorage;
}
public CreatureData GetCreatureData(ulong guid)
{
return creatureDataStorage.LookupByKey(guid);
}
public ObjectGuid GetLinkedRespawnGuid(ObjectGuid guid)
{
var retGuid = linkedRespawnStorage.LookupByKey(guid);
if (retGuid.IsEmpty())
return ObjectGuid.Empty;
return retGuid;
}
public bool SetCreatureLinkedRespawn(ulong guidLow, ulong linkedGuidLow)
{
if (guidLow == 0)
return false;
CreatureData master = GetCreatureData(guidLow);
ObjectGuid guid = ObjectGuid.Create(HighGuid.Creature, master.mapid, master.id, guidLow);
PreparedStatement stmt;
if (linkedGuidLow == 0) // we're removing the linking
{
linkedRespawnStorage.Remove(guid);
stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_CRELINKED_RESPAWN);
stmt.AddValue(0, guidLow);
DB.World.Execute(stmt);
return true;
}
CreatureData slave = GetCreatureData(linkedGuidLow);
if (slave == null)
{
Log.outError(LogFilter.Sql, "Creature '{0}' linking to non-existent creature '{1}'.", guidLow, linkedGuidLow);
return false;
}
MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid);
if (map == null || !map.Instanceable() || (master.mapid != slave.mapid))
{
Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow);
return false;
}
if (!Convert.ToBoolean(master.spawnMask & slave.spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
{
Log.outError(LogFilter.Sql, "LinkedRespawn: Creature '{0}' linking to '{1}' with not corresponding spawnMask", guidLow, linkedGuidLow);
return false;
}
ObjectGuid linkedGuid = ObjectGuid.Create(HighGuid.Creature, slave.mapid, slave.id, linkedGuidLow);
linkedRespawnStorage[guid] = linkedGuid;
stmt = DB.World.GetPreparedStatement(WorldStatements.REP_CREATURE_LINKED_RESPAWN);
stmt.AddValue(0, guidLow);
stmt.AddValue(1, linkedGuidLow);
DB.World.Execute(stmt);
return true;
}
public CreatureData NewOrExistCreatureData(ulong guid)
{
if (!creatureDataStorage.ContainsKey(guid))
creatureDataStorage[guid] = new CreatureData();
return creatureDataStorage[guid];
}
public void DeleteCreatureData(ulong guid)
{
CreatureData data = GetCreatureData(guid);
if (data != null)
RemoveCreatureFromGrid(guid, data);
creatureDataStorage.Remove(guid);
}
public CreatureBaseStats GetCreatureBaseStats(uint level, uint unitClass)
{
var stats = creatureBaseStatsStorage.LookupByKey(MathFunctions.MakePair16(level, unitClass));
if (stats != null)
return stats;
return new DefaultCreatureBaseStats();
}
public CreatureModelInfo GetCreatureModelRandomGender(ref uint displayID)
{
CreatureModelInfo modelInfo = GetCreatureModelInfo(displayID);
if (modelInfo == null)
return null;
// If a model for another gender exists, 50% chance to use it
if (modelInfo.DisplayIdOtherGender != 0 && RandomHelper.NextDouble() == 0)
{
CreatureModelInfo minfotmp = GetCreatureModelInfo(modelInfo.DisplayIdOtherGender);
if (minfotmp == null)
Log.outError(LogFilter.Sql, "Model (Entry: {0}) has modelidothergender {1} not found in table `creaturemodelinfo`. ", displayID, modelInfo.DisplayIdOtherGender);
else
{
// DisplayID changed
displayID = modelInfo.DisplayIdOtherGender;
return minfotmp;
}
}
return modelInfo;
}
public CreatureModelInfo GetCreatureModelInfo(uint modelId)
{
return creatureModelStorage.LookupByKey(modelId);
}
public NpcText GetNpcText(uint textId)
{
return _npcTextStorage.LookupByKey(textId);
}
//GameObjects
public void LoadGameObjectTemplate()
{
var time = Time.GetMSTime();
foreach (GameObjectsRecord db2go in CliDB.GameObjectsStorage.Values)
{
GameObjectTemplate go = new GameObjectTemplate();
go.entry = db2go.Id;
go.type = db2go.Type;
go.displayId = db2go.DisplayID;
go.name = db2go.Name[Global.WorldMgr.GetDefaultDbcLocale()];
go.size = db2go.Size;
unsafe
{
fixed (int* b = go.Raw.data)
{
for (byte x = 0; x < db2go.Data.Length; ++x)
b[x] = db2go.Data[x];
}
}
go.RequiredLevel = 0;
go.ScriptId = 0;
gameObjectTemplateStorage[db2go.Id] = go;
}
// 0 1 2 3 4 5 6 7
SQLResult result = DB.World.Query("SELECT entry, type, displayId, name, IconName, castBarCaption, unk1, size, " +
//8 9 10 11 12 13 14 15 16 17 18 19 20
"Data0, Data1, Data2, Data3, Data4, Data5, Data6, Data7, Data8, Data9, Data10, Data11, Data12, " +
//21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
"Data13, Data14, Data15, Data16, Data17, Data18, Data19, Data20, Data21, Data22, Data23, Data24, Data25, Data26, Data27, Data28, " +
//37 38 39 40 41 42 43
"Data29, Data30, Data31, Data32, RequiredLevel, AIName, ScriptName FROM gameobject_template");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 gameobject definitions. DB table `gameobject_template` is empty.");
}
else
{
uint count = 0;
do
{
uint entry = result.Read<uint>(0);
GameObjectTemplate got = new GameObjectTemplate();
got.entry = entry;
got.type = (GameObjectTypes)result.Read<uint>(1);
got.displayId = result.Read<uint>(2);
got.name = result.Read<string>(3);
got.IconName = result.Read<string>(4);
got.castBarCaption = result.Read<string>(5);
got.unk1 = result.Read<string>(6);
got.size = result.Read<float>(7);
unsafe
{
fixed (int* b = got.Raw.data)
{
for (byte x = 0; x < SharedConst.MaxGOData; ++x)
b[x] = result.Read<int>(8 + x);
}
}
got.RequiredLevel = result.Read<int>(41);
got.AIName = result.Read<string>(42);
got.ScriptId = Global.ObjectMgr.GetScriptId(result.Read<string>(43));
switch (got.type)
{
case GameObjectTypes.Door: //0
if (got.Door.open != 0)
CheckGOLockId(got, got.Door.open, 1);
CheckGONoDamageImmuneId(got, got.Door.noDamageImmune, 3);
break;
case GameObjectTypes.Button: //1
if (got.Button.open != 0)
CheckGOLockId(got, got.Button.open, 1);
CheckGONoDamageImmuneId(got, got.Button.noDamageImmune, 4);
break;
case GameObjectTypes.QuestGiver: //2
if (got.QuestGiver.open != 0)
CheckGOLockId(got, got.QuestGiver.open, 0);
CheckGONoDamageImmuneId(got, got.QuestGiver.noDamageImmune, 5);
break;
case GameObjectTypes.Chest: //3
if (got.Chest.open != 0)
CheckGOLockId(got, got.Chest.open, 0);
CheckGOConsumable(got, got.Chest.consumable, 3);
if (got.Chest.linkedTrap != 0) // linked trap
CheckGOLinkedTrapId(got, got.Chest.linkedTrap, 7);
break;
case GameObjectTypes.Trap: //6
if (got.Trap.open != 0)
CheckGOLockId(got, got.Trap.open, 0);
break;
case GameObjectTypes.Chair: //7
CheckAndFixGOChairHeightId(got, ref got.Chair.chairheight, 1);
break;
case GameObjectTypes.SpellFocus: //8
if (got.SpellFocus.spellFocusType != 0)
{
if (!CliDB.SpellFocusObjectStorage.ContainsKey(got.SpellFocus.spellFocusType))
Log.outError(LogFilter.Sql, "GameObject (Entry: {0} GoType: {1}) have data0={2} but SpellFocus (Id: {3}) not exist.",
entry, got.type, got.SpellFocus.spellFocusType, got.SpellFocus.spellFocusType);
}
if (got.SpellFocus.linkedTrap != 0) // linked trap
CheckGOLinkedTrapId(got, got.SpellFocus.linkedTrap, 2);
break;
case GameObjectTypes.Goober: //10
if (got.Goober.open != 0)
CheckGOLockId(got, got.Goober.open, 0);
CheckGOConsumable(got, got.Goober.consumable, 3);
if (got.Goober.pageID != 0) // pageId
{
if (GetPageText(got.Goober.pageID) == null)
Log.outError(LogFilter.Sql, "GameObject (Entry: {0} GoType: {1}) have data7={2} but PageText (Entry {3}) not exist.", entry, got.type, got.Goober.pageID, got.Goober.pageID);
}
CheckGONoDamageImmuneId(got, got.Goober.noDamageImmune, 11);
if (got.Goober.linkedTrap != 0) // linked trap
CheckGOLinkedTrapId(got, got.Goober.linkedTrap, 12);
break;
case GameObjectTypes.AreaDamage: //12
if (got.AreaDamage.open != 0)
CheckGOLockId(got, got.AreaDamage.open, 0);
break;
case GameObjectTypes.Camera: //13
if (got.Camera.open != 0)
CheckGOLockId(got, got.Camera.open, 0);
break;
case GameObjectTypes.MapObjTransport: //15
{
if (got.MoTransport.taxiPathID != 0)
{
if (got.MoTransport.taxiPathID >= CliDB.TaxiPathNodesByPath.Count || CliDB.TaxiPathNodesByPath[got.MoTransport.taxiPathID].Empty())
Log.outError(LogFilter.Sql, "GameObject (Entry: {0} GoType: {1}) have data0={2} but TaxiPath (Id: {3}) not exist.",
entry, got.type, got.MoTransport.taxiPathID, got.MoTransport.taxiPathID);
}
int transportMap = got.MoTransport.SpawnMap;
if (transportMap != 0)
_transportMaps.Add((ushort)transportMap);
break;
}
case GameObjectTypes.SpellCaster: //22
// always must have spell
CheckGOSpellId(got, got.SpellCaster.spell, 0);
break;
case GameObjectTypes.FlagStand: //24
if (got.FlagStand.open != 0)
CheckGOLockId(got, got.FlagStand.open, 0);
CheckGONoDamageImmuneId(got, got.FlagStand.noDamageImmune, 5);
break;
case GameObjectTypes.FishingHole: //25
if (got.FishingHole.open != 0)
CheckGOLockId(got, got.FishingHole.open, 4);
break;
case GameObjectTypes.FlagDrop: //26
if (got.FlagDrop.open != 0)
CheckGOLockId(got, got.FlagDrop.open, 0);
CheckGONoDamageImmuneId(got, got.FlagDrop.noDamageImmune, 3);
break;
case GameObjectTypes.BarberChair: //32
CheckAndFixGOChairHeightId(got, ref got.BarberChair.chairheight, 0);
if (got.BarberChair.SitAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(got.BarberChair.SitAnimKit))
{
Log.outError(LogFilter.Sql, "GameObject (Entry: {0} GoType: {1}) have data2 = {2} but AnimKit.dbc (Id: {3}) not exist, set to 0.",
entry, got.type, got.BarberChair.SitAnimKit, got.BarberChair.SitAnimKit);
got.BarberChair.SitAnimKit = 0;
}
break;
case GameObjectTypes.GarrisonBuilding:
{
int transportMap = got.garrisonBuilding.SpawnMap;
if (transportMap != 0)
_transportMaps.Add((ushort)transportMap);
}
break;
case GameObjectTypes.GatheringNode:
if (got.GatheringNode.open != 0)
CheckGOLockId(got, got.GatheringNode.open, 0);
if (got.GatheringNode.linkedTrap != 0)
CheckGOLinkedTrapId(got, got.GatheringNode.linkedTrap, 20);
break;
}
gameObjectTemplateStorage[entry] = got;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} game object templates in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
}
public void LoadGameObjectTemplateAddons()
{
uint oldMSTime = Time.GetMSTime();
// 0 1 2 3 4
SQLResult result = DB.World.Query("SELECT entry, faction, flags, mingold, maxgold FROM gameobject_template_addon");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 gameobject template addon definitions. DB table `gameobject_template_addon` is empty.");
return;
}
uint count = 0;
do
{
uint entry = result.Read<uint>(0);
GameObjectTemplate got = Global.ObjectMgr.GetGameObjectTemplate(entry);
if (got == null)
{
Log.outError(LogFilter.Sql, "GameObject template (Entry: {0}) does not exist but has a record in `gameobject_template_addon`", entry);
continue;
}
GameObjectTemplateAddon gameObjectAddon = new GameObjectTemplateAddon();
gameObjectAddon.faction = result.Read<ushort>(1);
gameObjectAddon.flags = result.Read<uint>(2);
gameObjectAddon.mingold = result.Read<uint>(3);
gameObjectAddon.maxgold = result.Read<uint>(4);
// checks
if (gameObjectAddon.faction != 0 && !CliDB.FactionTemplateStorage.ContainsKey(gameObjectAddon.faction))
Log.outError(LogFilter.Sql, "GameObject (Entry: {0}) has invalid faction ({1}) defined in `gameobject_template_addon`.", entry, gameObjectAddon.faction);
if (gameObjectAddon.maxgold > 0)
{
switch (got.type)
{
case GameObjectTypes.Chest:
case GameObjectTypes.FishingHole:
break;
default:
Log.outError(LogFilter.Sql, "GameObject (Entry {0} GoType: {1}) cannot be looted but has maxgold set in `gameobject_template_addon`.", entry, got.type);
break;
}
}
_gameObjectTemplateAddonStore[entry] = gameObjectAddon;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} game object template addons in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadGameobjects()
{
var time = Time.GetMSTime();
// 0 1 2 3 4 5 6
SQLResult result = DB.World.Query("SELECT gameobject.guid, id, map, position_x, position_y, position_z, orientation, " +
// 7 8 9 10 11 12 13 14 15 16
"rotation0, rotation1, rotation2, rotation3, spawntimesecs, animprogress, state, spawnMask, eventEntry, pool_entry, " +
// 17 18 19
"phaseid, phasegroup, ScriptName " +
"FROM gameobject LEFT OUTER JOIN game_event_gameobject ON gameobject.guid = game_event_gameobject.guid " +
"LEFT OUTER JOIN pool_gameobject ON gameobject.guid = pool_gameobject.guid");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 gameobjects. DB table `gameobject` is empty.");
return;
}
uint count = 0;
// build single time for check spawnmask
Dictionary<uint, uint> spawnMasks = new Dictionary<uint, uint>();
foreach (var mapDifficultyPair in Global.DB2Mgr.GetMapDifficulties())
{
foreach (var difficultyPair in mapDifficultyPair.Value)
{
if (!spawnMasks.ContainsKey(mapDifficultyPair.Key))
spawnMasks[mapDifficultyPair.Key] = 0;
spawnMasks[mapDifficultyPair.Key] |= (uint)(1 << (int)difficultyPair.Key);
}
}
do
{
ulong guid = result.Read<ulong>(0);
uint entry = result.Read<uint>(1);
GameObjectTemplate gInfo = GetGameObjectTemplate(entry);
if (gInfo == null)
{
Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0}) with non existing gameobject entry {1}, skipped.", guid, entry);
continue;
}
if (gInfo.displayId == 0)
{
switch (gInfo.type)
{
case GameObjectTypes.Trap:
case GameObjectTypes.SpellFocus:
break;
default:
Log.outError(LogFilter.Sql, "Gameobject (GUID: {0} Entry {1} GoType: {2}) doesn't have a displayId ({3}), not loaded.", guid, entry, gInfo.type, gInfo.displayId);
break;
}
}
if (gInfo.displayId != 0 && !CliDB.GameObjectDisplayInfoStorage.ContainsKey(gInfo.displayId))
{
Log.outError(LogFilter.Sql, "Gameobject (GUID: {0} Entry {1} GoType: {2}) has an invalid displayId ({3}), not loaded.", guid, entry, gInfo.type, gInfo.displayId);
continue;
}
GameObjectData data = new GameObjectData();
data.id = entry;
data.mapid = result.Read<ushort>(2);
data.posX = result.Read<float>(3);
data.posY = result.Read<float>(4);
data.posZ = result.Read<float>(5);
data.orientation = result.Read<float>(6);
data.rotation.X = result.Read<float>(7);
data.rotation.Y = result.Read<float>(8);
data.rotation.Z = result.Read<float>(9);
data.rotation.W = result.Read<float>(10);
data.spawntimesecs = result.Read<int>(11);
var mapEntry = CliDB.MapStorage.LookupByKey(data.mapid);
if (mapEntry == null)
{
Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) spawned on a non-existed map (Id: {2}), skip", guid, data.id, data.mapid);
continue;
}
if (data.spawntimesecs == 0 && gInfo.IsDespawnAtAction())
{
Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with `spawntimesecs` (0) value, but the gameobejct is marked as despawnable at action.", guid, data.id);
}
data.animprogress = result.Read<uint>(12);
data.artKit = 0;
uint gostate = result.Read<uint>(13);
if (gostate >= (uint)GameObjectState.Max)
{
if (gInfo.type != GameObjectTypes.Transport || gostate > (int)GameObjectState.TransportActive + SharedConst.MaxTransportStopFrames)
{
Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid `state` ({2}) value, skip", guid, data.id, gostate);
continue;
}
}
data.go_state = (GameObjectState)gostate;
data.spawnMask = result.Read<uint>(14);
//if (!_transportMaps.Contains(data.mapid) && (spawnMasks.ContainsKey(data.mapid) && Convert.ToBoolean(data.spawnMask & ~spawnMasks[data.mapid])))
//Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) that has wrong spawn mask {2} including not supported difficulty modes for map (Id: {3}), skip",
//guid, data.id, data.spawnMask, data.mapid);
short gameEvent = result.Read<sbyte>(15);
uint PoolId = result.Read<uint>(16);
data.phaseid = result.Read<uint>(17);
data.phaseGroup = result.Read<uint>(18);
if (data.phaseGroup != 0 && data.phaseid != 0)
{
Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with both `phaseid` and `phasegroup` set, `phasegroup` set to 0", guid, data.id);
data.phaseGroup = 0;
}
if (data.phaseid != 0)
{
if (!CliDB.PhaseStorage.ContainsKey(data.phaseid))
{
Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `phaseid` {2} does not exist, set to 0", guid, data.id, data.phaseid);
data.phaseid = 0;
}
}
if (data.phaseGroup != 0)
{
if (Global.DB2Mgr.GetPhasesForGroup(data.phaseGroup).Empty())
{
Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `phaseGroup` {2} does not exist, set to 0", guid, data.id, data.phaseGroup);
data.phaseGroup = 0;
}
}
data.ScriptId = GetScriptId(result.Read<string>(19));
if (data.ScriptId == 0)
data.ScriptId = gInfo.ScriptId;
if (Math.Abs(data.orientation) > 2 * MathFunctions.PI)
{
Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with abs(`orientation`) > 2*PI (orientation is expressed in radians), normalized.", guid, data.id);
data.orientation = Position.NormalizeOrientation(data.orientation);
}
if (data.rotation.X < -1.0f || data.rotation.X > 1.0f)
{
Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationX ({1}) value, skip", guid, data.id, data.rotation.X);
continue;
}
if (data.rotation.Y < -1.0f || data.rotation.Y > 1.0f)
{
Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationY ({2}) value, skip", guid, data.id, data.rotation.Y);
continue;
}
if (data.rotation.Z < -1.0f || data.rotation.Z > 1.0f)
{
Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationZ ({3}) value, skip", guid, data.id, data.rotation.Z);
continue;
}
if (data.rotation.W < -1.0f || data.rotation.W > 1.0f)
{
Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationW ({4}) value, skip", guid, data.id, data.rotation.W);
continue;
}
if (!GridDefines.IsValidMapCoord(data.mapid, data.posX, data.posY, data.posZ, data.orientation))
{
Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid coordinates, skip", guid, data.id);
continue;
}
data.phaseMask = 1;
if (WorldConfig.GetBoolValue(WorldCfg.CalculateGameobjectZoneAreaData))
{
uint zoneId = 0;
uint areaId = 0;
Global.MapMgr.GetZoneAndAreaId(out zoneId, out areaId, data.mapid, data.posX, data.posY, data.posZ);
PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_GAMEOBJECT_ZONE_AREA_DATA);
stmt.AddValue(0, zoneId);
stmt.AddValue(1, areaId);
stmt.AddValue(2, guid);
DB.World.Execute(stmt);
}
if (gameEvent == 0 && PoolId == 0) // if not this is to be managed by GameEvent System or Pool system
AddGameObjectToGrid(guid, data);
gameObjectDataStorage[guid] = data;
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobjects in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
public void LoadGameObjectAddons()
{
uint oldMSTime = Time.GetMSTime();
_gameObjectAddonStorage.Clear();
// 0 1 2 3 4 5 6
SQLResult result = DB.World.Query("SELECT guid, parent_rotation0, parent_rotation1, parent_rotation2, parent_rotation3, invisibilityType, invisibilityValue FROM gameobject_addon");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 gameobject addon definitions. DB table `gameobject_addon` is empty.");
return;
}
uint count = 0;
do
{
ulong guid = result.Read<ulong>(0);
GameObjectData goData = GetGOData(guid);
if (goData == null)
{
Log.outError(LogFilter.Sql, "GameObject (GUID: {0}) does not exist but has a record in `gameobject_addon`", guid);
continue;
}
GameObjectAddon gameObjectAddon = new GameObjectAddon();
gameObjectAddon.ParentRotation = new Quaternion(result.Read<float>(1), result.Read<float>(2), result.Read<float>(3), result.Read<float>(4));
gameObjectAddon.invisibilityType = (InvisibilityType)result.Read<byte>(5);
gameObjectAddon.invisibilityValue = result.Read<uint>(6);
if (gameObjectAddon.invisibilityType >= InvisibilityType.Max)
{
Log.outError(LogFilter.Sql, "GameObject (GUID: {0}) has invalid InvisibilityType in `gameobject_addon`, disabled invisibility", guid);
gameObjectAddon.invisibilityType = InvisibilityType.General;
gameObjectAddon.invisibilityValue = 0;
}
if (gameObjectAddon.invisibilityType != 0 && gameObjectAddon.invisibilityValue == 0)
{
Log.outError(LogFilter.Sql, "GameObject (GUID: {0}) has InvisibilityType set but has no InvisibilityValue in `gameobject_addon`, set to 1", guid);
gameObjectAddon.invisibilityValue = 1;
}
if (!gameObjectAddon.ParentRotation.isUnit())
{
Log.outError(LogFilter.Sql, "GameObject (GUID: {0}) has invalid parent rotation in `gameobject_addon`, set to default", guid);
gameObjectAddon.ParentRotation = Quaternion.WAxis;
}
_gameObjectAddonStorage[guid] = gameObjectAddon;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobject addons in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadGameObjectQuestItems()
{
uint oldMSTime = Time.GetMSTime();
// 0 1
SQLResult result = DB.World.Query("SELECT GameObjectEntry, ItemId, Idx FROM gameobject_questitem ORDER BY Idx ASC");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 gameobject quest items. DB table `gameobject_questitem` is empty.");
return;
}
uint count = 0;
do
{
uint entry = result.Read<uint>(0);
uint item = result.Read<uint>(1);
uint idx = result.Read<uint>(2);
if (!gameObjectTemplateStorage.ContainsKey(entry))
{
Log.outError(LogFilter.Sql, "Table `gameobject_questitem` has data for nonexistent gameobject (entry: {0}, idx: {1}), skipped", entry, idx);
continue;
}
if (!CliDB.ItemStorage.ContainsKey(item))
{
Log.outError(LogFilter.Sql, "Table `gameobject_questitem` has nonexistent item (ID: {0}) in gameobject (entry: {1}, idx: {2}), skipped", item, entry, idx);
continue;
}
_gameObjectQuestItemStorage.Add(entry, item);
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobject quest items in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadGameObjectForQuests()
{
uint oldMSTime = Time.GetMSTime();
_gameObjectForQuestStorage.Clear(); // need for reload case
if (GetGameObjectTemplates().Empty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 GameObjects for quests");
return;
}
uint count = 0;
// collect GO entries for GO that must activated
foreach (var go in GetGameObjectTemplates())
{
switch (go.Value.type)
{
case GameObjectTypes.QuestGiver:
_gameObjectForQuestStorage.Add(go.Value.entry);
++count;
break;
case GameObjectTypes.Chest:
{
// scan GO chest with loot including quest items
uint loot_id = (go.Value.GetLootId());
// find quest loot for GO
if (go.Value.Chest.questID != 0 || Loots.LootStorage.Gameobject.HaveQuestLootFor(loot_id))
{
_gameObjectForQuestStorage.Add(go.Value.entry);
++count;
}
break;
}
case GameObjectTypes.Generic:
{
if (go.Value.Generic.questID > 0) //quests objects
{
_gameObjectForQuestStorage.Add(go.Value.entry);
++count;
}
break;
}
case GameObjectTypes.Goober:
{
if (go.Value.Goober.questID > 0) //quests objects
{
_gameObjectForQuestStorage.Add(go.Value.entry);
++count;
}
break;
}
default:
break;
}
}
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} GameObjects for quests in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void AddGameObjectToGrid(ulong guid, GameObjectData data)
{
uint mask = data.spawnMask;
for (byte i = 0; mask != 0; i++, mask >>= 1)
{
if (Convert.ToBoolean(mask & 1))
{
CellCoord cellCoord = GridDefines.ComputeCellCoord(data.posX, data.posY);
var cellguids = CreateCellObjectGuids(data.mapid, i, cellCoord);
cellguids.gameobjects.Add(guid);
}
}
}
public void RemoveGameObjectFromGrid(ulong guid, GameObjectData data)
{
uint mask = data.spawnMask;
for (byte i = 0; mask != 0; i++, mask >>= 1)
{
if (Convert.ToBoolean(mask & 1))
{
CellCoord cellCoord = GridDefines.ComputeCellCoord(data.posX, data.posY);
CellObjectGuids cellguids = GetCellObjectGuids(data.mapid, i, cellCoord.GetId());
if (cellguids == null)
return;
cellguids.gameobjects.Remove(guid);
}
}
}
public ulong AddGOData(uint entry, uint mapId, float x, float y, float z, float o, uint spawntimedelay, float rotation0, float rotation1, float rotation2, float rotation3)
{
GameObjectTemplate goinfo = GetGameObjectTemplate(entry);
if (goinfo == null)
return 0;
Map map = Global.MapMgr.CreateBaseMap(mapId);
if (map == null)
return 0;
ulong guid = GenerateGameObjectSpawnId();
GameObjectData data = new GameObjectData();
data.id = entry;
data.mapid = (ushort)mapId;
data.posX = x;
data.posY = y;
data.posZ = z;
data.orientation = o;
data.rotation.X = rotation0;
data.rotation.Y = rotation1;
data.rotation.Z = rotation2;
data.rotation.W = rotation3;
data.spawntimesecs = (int)spawntimedelay;
data.animprogress = 100;
data.spawnMask = 1;
data.go_state = GameObjectState.Ready;
data.phaseMask = (ushort)PhaseMasks.Normal;
data.artKit = (byte)(goinfo.type == GameObjectTypes.ControlZone ? 21 : 0);
data.dbData = false;
NewGOData(guid, data);
AddGameObjectToGrid(guid, data);
// Spawn if necessary (loaded grids only)
// We use spawn coords to spawn
if (!map.Instanceable() && map.IsGridLoaded(x, y))
{
GameObject go = new GameObject();
if (!go.LoadGameObjectFromDB(guid, map))
{
Log.outError(LogFilter.Server, "AddGOData: cannot add gameobject entry {0} to map", entry);
return 0;
}
}
Log.outDebug(LogFilter.Maps, "AddGOData: dbguid:{0} entry:{1} map:{2} x:{3} y:{4} z:{5} o:{6}", guid, entry, mapId, x, y, z, o);
return guid;
}
public GameObjectAddon GetGameObjectAddon(ulong lowguid)
{
return _gameObjectAddonStorage.LookupByKey(lowguid);
}
public List<uint> GetGameObjectQuestItemList(uint id)
{
return _gameObjectQuestItemStorage.LookupByKey(id);
}
MultiMap<uint, uint> GetGameObjectQuestItemMap() { return _gameObjectQuestItemStorage; }
public GameObjectData GetGOData(ulong guid)
{
return gameObjectDataStorage.LookupByKey(guid);
}
public void DeleteGOData(ulong guid)
{
GameObjectData data = GetGOData(guid);
if (data != null)
RemoveGameObjectFromGrid(guid, data);
gameObjectDataStorage.Remove(guid);
}
public void NewGOData(ulong guid, GameObjectData data)
{
gameObjectDataStorage.Add(guid, data);
}
public GameObjectTemplate GetGameObjectTemplate(uint entry)
{
return gameObjectTemplateStorage.LookupByKey(entry);
}
public GameObjectTemplateAddon GetGameObjectTemplateAddon(uint entry)
{
return _gameObjectTemplateAddonStore.LookupByKey(entry);
}
public Dictionary<uint, GameObjectTemplate> GetGameObjectTemplates()
{
return gameObjectTemplateStorage;
}
public bool IsGameObjectForQuests(uint entry)
{
return _gameObjectForQuestStorage.Contains(entry);
}
void CheckGOLockId(GameObjectTemplate goInfo, uint dataN, uint N)
{
if (CliDB.LockStorage.ContainsKey(dataN))
return;
Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but lock (Id: {4}) not found.", goInfo.entry, goInfo.type, N, goInfo.Door.open, goInfo.Door.open);
}
void CheckGOLinkedTrapId(GameObjectTemplate goInfo, uint dataN, uint N)
{
GameObjectTemplate trapInfo = Global.ObjectMgr.GetGameObjectTemplate(dataN);
if (trapInfo != null)
{
if (trapInfo.type != GameObjectTypes.Trap)
Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but GO (Entry {4}) have not GAMEOBJECT_TYPE_TRAP type.", goInfo.entry, goInfo.type, N, dataN, dataN);
}
}
void CheckGOSpellId(GameObjectTemplate goInfo, uint dataN, uint N)
{
if (Global.SpellMgr.HasSpellInfo(dataN))
return;
Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but Spell (Entry {4}) not exist.", goInfo.entry, goInfo.type, N, dataN, dataN);
}
void CheckAndFixGOChairHeightId(GameObjectTemplate goInfo, ref uint dataN, uint N)
{
if (dataN <= (UnitStandStateType.SitHighChair - UnitStandStateType.SitLowChair))
return;
Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but correct chair height in range 0..{4}.", goInfo.entry, goInfo.type, N, dataN, UnitStandStateType.SitHighChair - UnitStandStateType.SitLowChair);
// prevent client and server unexpected work
dataN = 0;
}
void CheckGONoDamageImmuneId(GameObjectTemplate goTemplate, uint dataN, uint N)
{
// 0/1 correct values
if (dataN <= 1)
return;
Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but expected boolean (0/1) noDamageImmune field value.", goTemplate.entry, goTemplate.type, N, dataN);
}
void CheckGOConsumable(GameObjectTemplate goInfo, uint dataN, uint N)
{
// 0/1 correct values
if (dataN <= 1)
return;
Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but expected boolean (0/1) consumable field value.",
goInfo.entry, goInfo.type, N, dataN);
}
//Items
public void LoadItemTemplates()
{
var oldMSTime = Time.GetMSTime();
uint sparseCount = 0;
foreach (var sparse in CliDB.ItemSparseStorage.Values)
{
ItemRecord db2Data = CliDB.ItemStorage.LookupByKey(sparse.Id);
if (db2Data == null)
continue;
var itemTemplate = new ItemTemplate(db2Data, sparse);
itemTemplate.MaxDurability = FillMaxDurability(db2Data.Class, db2Data.SubClass, sparse.inventoryType, (ItemQuality)sparse.Quality, sparse.ItemLevel);
FillDisenchantFields(out itemTemplate.DisenchantID, out itemTemplate.RequiredDisenchantSkill, itemTemplate);
var itemSpecOverrides = Global.DB2Mgr.GetItemSpecOverrides(sparse.Id);
if (itemSpecOverrides != null)
{
foreach (ItemSpecOverrideRecord itemSpecOverride in itemSpecOverrides)
{
ChrSpecializationRecord specialization = CliDB.ChrSpecializationStorage.LookupByKey(itemSpecOverride.SpecID);
if (specialization != null)
{
itemTemplate.ItemSpecClassMask |= 1u << (specialization.ClassID - 1);
itemTemplate.Specializations[0].Set(ItemTemplate.CalculateItemSpecBit(specialization), true);
itemTemplate.Specializations[1] = itemTemplate.Specializations[1].Or(itemTemplate.Specializations[0]);
itemTemplate.Specializations[2] = itemTemplate.Specializations[2].Or(itemTemplate.Specializations[0]);
}
}
}
else
{
ItemSpecStats itemSpecStats = new ItemSpecStats(db2Data, sparse);
foreach (ItemSpecRecord itemSpec in CliDB.ItemSpecStorage.Values)
{
if (itemSpecStats.ItemType != itemSpec.ItemType)
continue;
bool hasPrimary = itemSpec.PrimaryStat == ItemSpecStat.None;
bool hasSecondary = itemSpec.SecondaryStat == ItemSpecStat.None;
for (uint i = 0; i < itemSpecStats.ItemSpecStatCount; ++i)
{
if (itemSpecStats.ItemSpecStatTypes[i] == itemSpec.PrimaryStat)
hasPrimary = true;
if (itemSpecStats.ItemSpecStatTypes[i] == itemSpec.SecondaryStat)
hasSecondary = true;
}
if (!hasPrimary || !hasSecondary)
continue;
ChrSpecializationRecord specialization = CliDB.ChrSpecializationStorage.LookupByKey(itemSpec.SpecID);
if (specialization != null)
{
if (Convert.ToBoolean((1 << (specialization.ClassID - 1)) & sparse.AllowableClass))
{
itemTemplate.ItemSpecClassMask |= 1u << (specialization.ClassID - 1);
int specBit = ItemTemplate.CalculateItemSpecBit(specialization);
itemTemplate.Specializations[0].Set(specBit, true);
if (itemSpec.MaxLevel > 40)
itemTemplate.Specializations[1].Set(specBit, true);
if (itemSpec.MaxLevel >= 110)
itemTemplate.Specializations[2].Set(specBit, true);
}
}
}
}
// Items that have no specializations set can be used by everyone
foreach (var specs in itemTemplate.Specializations)
if (specs.Count == 0)
specs.SetAll(true);
++sparseCount;
ItemTemplateStorage.Add(sparse.Id, itemTemplate);
}
foreach (var effectEntry in CliDB.ItemEffectStorage.Values)
{
var itemTemplate = ItemTemplateStorage.LookupByKey(effectEntry.ItemID);
if (itemTemplate == null)
continue;
itemTemplate.Effects.Add(effectEntry);
}
// Check if item templates for DBC referenced character start outfit are present
List<uint> notFoundOutfit = new List<uint>();
foreach (var entry in CliDB.CharStartOutfitStorage.Values)
{
for (int j = 0; j < ItemConst.MaxOutfitItems; ++j)
{
if (entry.ItemID[j] <= 0)
continue;
uint item_id = (uint)entry.ItemID[j];
if (GetItemTemplate(item_id) == null)
notFoundOutfit.Add(item_id);
}
}
foreach (var id in notFoundOutfit)
Log.outError(LogFilter.Sql, "Item (Entry: {0}) does not exist in `item_template` but is referenced in `CharStartOutfit.dbc`", id);
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} item templates in {1} ms", sparseCount, Time.GetMSTimeDiffToNow(oldMSTime));
}
static float[] qualityMultipliers = new float[]
{
0.92f, 0.92f, 0.92f, 1.11f, 1.32f, 1.61f, 0.0f, 0.0f
};
static float[] armorMultipliers = new float[]
{
0.00f, // INVTYPE_NON_EQUIP
0.60f, // INVTYPE_HEAD
0.00f, // INVTYPE_NECK
0.60f, // INVTYPE_SHOULDERS
0.00f, // INVTYPE_BODY
1.00f, // INVTYPE_CHEST
0.33f, // INVTYPE_WAIST
0.72f, // INVTYPE_LEGS
0.48f, // INVTYPE_FEET
0.33f, // INVTYPE_WRISTS
0.33f, // INVTYPE_HANDS
0.00f, // INVTYPE_FINGER
0.00f, // INVTYPE_TRINKET
0.00f, // INVTYPE_WEAPON
0.72f, // INVTYPE_SHIELD
0.00f, // INVTYPE_RANGED
0.00f, // INVTYPE_CLOAK
0.00f, // INVTYPE_2HWEAPON
0.00f, // INVTYPE_BAG
0.00f, // INVTYPE_TABARD
1.00f, // INVTYPE_ROBE
0.00f, // INVTYPE_WEAPONMAINHAND
0.00f, // INVTYPE_WEAPONOFFHAND
0.00f, // INVTYPE_HOLDABLE
0.00f, // INVTYPE_AMMO
0.00f, // INVTYPE_THROWN
0.00f, // INVTYPE_RANGEDRIGHT
0.00f, // INVTYPE_QUIVER
0.00f, // INVTYPE_RELIC
};
static float[] weaponMultipliers = new float[]
{
0.91f, // ITEM_SUBCLASS_WEAPON_AXE
1.00f, // ITEM_SUBCLASS_WEAPON_AXE2
1.00f, // ITEM_SUBCLASS_WEAPON_BOW
1.00f, // ITEM_SUBCLASS_WEAPON_GUN
0.91f, // ITEM_SUBCLASS_WEAPON_MACE
1.00f, // ITEM_SUBCLASS_WEAPON_MACE2
1.00f, // ITEM_SUBCLASS_WEAPON_POLEARM
0.91f, // ITEM_SUBCLASS_WEAPON_SWORD
1.00f, // ITEM_SUBCLASS_WEAPON_SWORD2
1.00f, // ITEM_SUBCLASS_WEAPON_WARGLAIVES
1.00f, // ITEM_SUBCLASS_WEAPON_STAFF
0.00f, // ITEM_SUBCLASS_WEAPON_EXOTIC
0.00f, // ITEM_SUBCLASS_WEAPON_EXOTIC2
0.66f, // ITEM_SUBCLASS_WEAPON_FIST_WEAPON
0.00f, // ITEM_SUBCLASS_WEAPON_MISCELLANEOUS
0.66f, // ITEM_SUBCLASS_WEAPON_DAGGER
0.00f, // ITEM_SUBCLASS_WEAPON_THROWN
0.00f, // ITEM_SUBCLASS_WEAPON_SPEAR
1.00f, // ITEM_SUBCLASS_WEAPON_CROSSBOW
0.66f, // ITEM_SUBCLASS_WEAPON_WAND
0.66f, // ITEM_SUBCLASS_WEAPON_FISHING_POLE
};
uint FillMaxDurability(ItemClass itemClass, uint itemSubClass, InventoryType inventoryType, ItemQuality quality, uint itemLevel)
{
if (itemClass != ItemClass.Armor && itemClass != ItemClass.Weapon)
return 0;
float levelPenalty = 1.0f;
if (itemLevel <= 28)
levelPenalty = 0.966f - (28u - itemLevel) / 54.0f;
if (itemClass == ItemClass.Armor)
{
if (inventoryType > InventoryType.Robe)
return 0;
return 5 * (uint)(Math.Round(25.0f * qualityMultipliers[(int)quality] * armorMultipliers[(int)inventoryType] * levelPenalty));
}
return 5 * (uint)(Math.Round(18.0f * qualityMultipliers[(int)quality] * weaponMultipliers[itemSubClass] * levelPenalty));
}
void FillDisenchantFields(out uint disenchantID, out uint requiredDisenchantSkill, ItemTemplate itemTemplate)
{
disenchantID = 0;
requiredDisenchantSkill = 0xFFFFFFFF;
if (itemTemplate.GetFlags().HasAnyFlag(ItemFlags.Conjured | ItemFlags.NoDisenchant) ||
itemTemplate.GetBonding() == ItemBondingType.Quest || itemTemplate.GetArea() != 0 || itemTemplate.GetMap() != 0 ||
itemTemplate.GetMaxStackSize() > 1 ||
itemTemplate.GetQuality() < ItemQuality.Uncommon || itemTemplate.GetQuality() > ItemQuality.Epic ||
!(itemTemplate.GetClass() == ItemClass.Armor || itemTemplate.GetClass() == ItemClass.Weapon) ||
!(Item.GetSpecialPrice(itemTemplate) != 0 || Global.DB2Mgr.HasItemCurrencyCost(itemTemplate.GetId())))
return;
foreach (var disenchant in CliDB.ItemDisenchantLootStorage.Values)
{
if (disenchant.ItemClass == (uint)itemTemplate.GetClass() && disenchant.ItemQuality == (uint)itemTemplate.GetQuality() &&
disenchant.MinItemLevel <= itemTemplate.GetBaseItemLevel() && disenchant.MaxItemLevel >= itemTemplate.GetBaseItemLevel())
{
if (disenchant.Id == 60 || disenchant.Id == 61) // epic item disenchant ilvl range 66-99 (classic)
{
if (itemTemplate.GetBaseRequiredLevel() > 60 || itemTemplate.GetRequiredSkillRank() > 300)
continue; // skip to epic item disenchant ilvl range 90-199 (TBC)
}
else if (disenchant.Id == 66 || disenchant.Id == 67) // epic item disenchant ilvl range 90-199 (TBC)
{
if (itemTemplate.GetBaseRequiredLevel() <= 60 || (itemTemplate.GetRequiredSkill() != 0 && itemTemplate.GetRequiredSkillRank() <= 300))
continue;
}
disenchantID = disenchant.Id;
requiredDisenchantSkill = disenchant.RequiredDisenchantSkill;
return;
}
}
}
public void LoadItemTemplateAddon()
{
var time = Time.GetMSTime();
uint count = 0;
SQLResult result = DB.World.Query("SELECT Id, FlagsCu, FoodType, MinMoneyLoot, MaxMoneyLoot, SpellPPMChance FROM item_template_addon");
if (!result.IsEmpty())
{
do
{
uint itemId = result.Read<uint>(0);
ItemTemplate itemTemplate = GetItemTemplate(itemId);
if (itemTemplate == null)
{
Log.outError(LogFilter.Sql, "Item {0} specified in `itemtemplateaddon` does not exist, skipped.", itemId);
continue;
}
uint minMoneyLoot = result.Read<uint>(3);
uint maxMoneyLoot = result.Read<uint>(4);
if (minMoneyLoot > maxMoneyLoot)
{
Log.outError(LogFilter.Sql, "Minimum money loot specified in `itemtemplateaddon` for item {0} was greater than maximum amount, swapping.", itemId);
uint temp = minMoneyLoot;
minMoneyLoot = maxMoneyLoot;
maxMoneyLoot = temp;
}
itemTemplate.FlagsCu = (ItemFlagsCustom)result.Read<uint>(1);
itemTemplate.FoodType = result.Read<uint>(2);
itemTemplate.MinMoneyLoot = minMoneyLoot;
itemTemplate.MaxMoneyLoot = maxMoneyLoot;
itemTemplate.SpellPPMRate = result.Read<float>(5);
++count;
} while (result.NextRow());
}
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} item addon templates in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
public void LoadItemScriptNames()
{
uint oldMSTime = Time.GetMSTime();
uint count = 0;
SQLResult result = DB.World.Query("SELECT Id, ScriptName FROM item_script_names");
if (!result.IsEmpty())
{
do
{
uint itemId = result.Read<uint>(0);
if (GetItemTemplate(itemId) == null)
{
Log.outError(LogFilter.Sql, "Item {0} specified in `item_script_names` does not exist, skipped.", itemId);
continue;
}
ItemTemplateStorage[itemId].ScriptId = GetScriptId(result.Read<string>(1));
++count;
} while (result.NextRow());
}
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} item script names in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public ItemTemplate GetItemTemplate(uint ItemId)
{
return ItemTemplateStorage.LookupByKey(ItemId);
}
public Dictionary<uint, ItemTemplate> GetItemTemplates()
{
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];
vList.AddItem(item, maxcount, incrtime, extendedCost, type);
if (persist)
{
PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.INS_NPC_VENDOR);
stmt.AddValue(0, entry);
stmt.AddValue(1, item);
stmt.AddValue(2, maxcount);
stmt.AddValue(3, incrtime);
stmt.AddValue(4, extendedCost);
stmt.AddValue(5, (byte)type);
DB.World.Execute(stmt);
}
}
public bool RemoveVendorItem(uint entry, uint item, ItemVendorType type, bool persist = true)
{
var iter = cacheVendorItemStorage.LookupByKey(entry);
if (iter == null)
return false;
if (!iter.RemoveItem(item, type))
return false;
if (persist)
{
PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_NPC_VENDOR);
stmt.AddValue(0, entry);
stmt.AddValue(1, item);
stmt.AddValue(2, (byte)type);
DB.World.Execute(stmt);
}
return true;
}
public bool IsVendorItemValid(uint vendorentry, uint id, int maxcount, uint incrtime, uint ExtendedCost, ItemVendorType type, Player player = null, List<uint> skipvendors = null, ulong ORnpcflag = 0)
{
CreatureTemplate cInfo = GetCreatureTemplate(vendorentry);
if (cInfo == null)
{
if (player != null)
player.SendSysMessage(CypherStrings.CommandVendorselection);
else
Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` have data for not existed creature template (Entry: {0}), ignore", vendorentry);
return false;
}
if (!Convert.ToBoolean(((ulong)cInfo.Npcflag | ORnpcflag) & (ulong)NPCFlags.Vendor))
{
if (skipvendors == null || skipvendors.Count() == 0)
{
if (player != null)
player.SendSysMessage(CypherStrings.CommandVendorselection);
else
Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` have data for not creature template (Entry: {0}) without vendor flag, ignore", vendorentry);
if (skipvendors != null)
skipvendors.Add(vendorentry);
}
return false;
}
if ((type == ItemVendorType.Item && GetItemTemplate(id) == null) ||
(type == ItemVendorType.Currency && CliDB.CurrencyTypesStorage.LookupByKey(id) == null))
{
if (player != null)
player.SendSysMessage(CypherStrings.ItemNotFound, id, type);
else
Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` for Vendor (Entry: {0}) have in item list non-existed item ({1}, type {2}), ignore", vendorentry, id, type);
return false;
}
if (ExtendedCost != 0 && !CliDB.ItemExtendedCostStorage.ContainsKey(ExtendedCost))
{
if (player != null)
player.SendSysMessage(CypherStrings.ExtendedCostNotExist, ExtendedCost);
else
Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` have Item (Entry: {0}) with wrong ExtendedCost ({1}) for vendor ({2}), ignore", id, ExtendedCost, vendorentry);
return false;
}
if (type == ItemVendorType.Item) // not applicable to currencies
{
if (maxcount > 0 && incrtime == 0)
{
if (player != null)
player.SendSysMessage("MaxCount != 0 ({0}) but IncrTime == 0", maxcount);
else
Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` has `maxcount` ({0}) for item {1} of vendor (Entry: {2}) but `incrtime`=0, ignore", maxcount, id, vendorentry);
return false;
}
else if (maxcount == 0 && incrtime > 0)
{
if (player != null)
player.SendSysMessage("MaxCount == 0 but IncrTime<>= 0");
else
Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` has `maxcount`=0 for item {0} of vendor (Entry: {0}) but `incrtime`<>0, ignore", id, vendorentry);
return false;
}
}
VendorItemData vItems = GetNpcVendorItemList(vendorentry);
if (vItems == null)
return true; // later checks for non-empty lists
if (vItems.FindItemCostPair(id, ExtendedCost, type) != null)
{
if (player != null)
player.SendSysMessage(CypherStrings.ItemAlreadyInList, id, ExtendedCost, type);
else
Log.outError(LogFilter.Sql, "Table `npcvendor` has duplicate items {0} (with extended cost {1}, type {2}) for vendor (Entry: {3}), ignoring", id, ExtendedCost, type, vendorentry);
return false;
}
return true;
}
public VendorItemData GetNpcVendorItemList(uint entry)
{
return cacheVendorItemStorage.LookupByKey(entry);
}
public EquipmentInfo GetEquipmentInfo(uint entry, int id)
{
var equip = equipmentInfoStorage.LookupByKey(entry);
if (equip.Empty())
return null;
if (id == -1)
return equip[RandomHelper.IRand(0, equip.Count - 1)].Item2;
else
return equip.Find(p => p.Item1 == id).Item2;
}
//Maps
public void LoadInstanceTemplate()
{
var time = Time.GetMSTime();
// 0 1 2 4
SQLResult result = DB.World.Query("SELECT map, parent, script, allowMount FROM instance_template");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 instance templates. DB table `instance_template` is empty!");
return;
}
uint count = 0;
do
{
uint mapID = result.Read<uint>(0);
if (!Global.MapMgr.IsValidMAP(mapID, true))
{
Log.outError(LogFilter.Sql, "ObjectMgr.LoadInstanceTemplate: bad mapid {0} for template!", mapID);
continue;
}
var instanceTemplate = new InstanceTemplate();
instanceTemplate.AllowMount = result.Read<bool>(3);
instanceTemplate.Parent = result.Read<uint>(1);
instanceTemplate.ScriptId = Global.ObjectMgr.GetScriptId(result.Read<string>(2));
instanceTemplateStorage.Add(mapID, instanceTemplate);
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} instance templates in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
public void LoadGameTele()
{
uint oldMSTime = Time.GetMSTime();
gameTeleStorage.Clear();
// 0 1 2 3 4 5 6
SQLResult result = DB.World.Query("SELECT id, position_x, position_y, position_z, orientation, map, name FROM game_tele");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 GameTeleports. DB table `game_tele` is empty!");
return;
}
uint count = 0;
do
{
uint id = result.Read<uint>(0);
GameTele gt = new GameTele();
gt.posX = result.Read<float>(1);
gt.posY = result.Read<float>(2);
gt.posZ = result.Read<float>(3);
gt.orientation = result.Read<float>(4);
gt.mapId = result.Read<uint>(5);
gt.name = result.Read<string>(6);
if (!GridDefines.IsValidMapCoord(gt.mapId, gt.posX, gt.posY, gt.posZ, gt.orientation))
{
Log.outError(LogFilter.Sql, "Wrong position for id {0} (name: {1}) in `game_tele` table, ignoring.", id, gt.name);
continue;
}
gameTeleStorage.Add(id, gt);
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} GameTeleports in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadAreaTriggerTeleports()
{
uint oldMSTime = Time.GetMSTime();
_areaTriggerStorage.Clear(); // need for reload case
// 0 1
SQLResult result = DB.World.Query("SELECT ID, PortLocID FROM areatrigger_teleport");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 area trigger teleport definitions. DB table `areatrigger_teleport` is empty.");
return;
}
uint count = 0;
do
{
++count;
uint Trigger_ID = result.Read<uint>(0);
uint PortLocID = result.Read<uint>(1);
WorldSafeLocsRecord portLoc = CliDB.WorldSafeLocsStorage.LookupByKey(PortLocID);
if (portLoc == null)
{
Log.outError(LogFilter.Sql, "Area Trigger (ID: {0}) has a non-existing Port Loc (ID: {1}) in WorldSafeLocs.dbc, skipped", Trigger_ID, PortLocID);
continue;
}
AreaTriggerStruct at = new AreaTriggerStruct();
at.target_mapId = portLoc.MapID;
at.target_X = portLoc.Loc.X;
at.target_Y = portLoc.Loc.Y;
at.target_Z = portLoc.Loc.Z;
at.target_Orientation = (portLoc.Facing * MathFunctions.PI) / 180; // Orientation is initially in degrees
at.PortLocId = portLoc.Id;
AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(Trigger_ID);
if (atEntry == null)
{
Log.outError(LogFilter.Sql, "Area trigger (ID: {0}) does not exist in `AreaTrigger.dbc`.", Trigger_ID);
continue;
}
_areaTriggerStorage[Trigger_ID] = at;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} area trigger teleport definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadAccessRequirements()
{
uint oldMSTime = Time.GetMSTime();
_accessRequirementStorage.Clear();
// 0 1 2 3 4 5 6 7 8 9
SQLResult result = DB.World.Query("SELECT mapid, difficulty, level_min, level_max, item, item2, quest_done_A, quest_done_H, completed_achievement, quest_failed_text FROM access_requirement");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 access requirement definitions. DB table `access_requirement` is empty.");
return;
}
uint count = 0;
do
{
uint mapid = result.Read<uint>(0);
if (!CliDB.MapStorage.ContainsKey(mapid))
{
Log.outError(LogFilter.Sql, "Map {0} referenced in `access_requirement` does not exist, skipped.", mapid);
continue;
}
uint difficulty = result.Read<uint>(1);
if (Global.DB2Mgr.GetMapDifficultyData(mapid, (Difficulty)difficulty) == null)
{
Log.outError(LogFilter.Sql, "Map {0} referenced in `access_requirement` does not have difficulty {1}, skipped", mapid, difficulty);
continue;
}
ulong requirementId = MathFunctions.MakePair64(mapid, difficulty);
AccessRequirement ar = new AccessRequirement();
ar.levelMin = result.Read<byte>(2);
ar.levelMax = result.Read<byte>(3);
ar.item = result.Read<uint>(4);
ar.item2 = result.Read<uint>(5);
ar.quest_A = result.Read<uint>(6);
ar.quest_H = result.Read<uint>(7);
ar.achievement = result.Read<uint>(8);
ar.questFailedText = result.Read<string>(9);
if (ar.item != 0)
{
ItemTemplate pProto = GetItemTemplate(ar.item);
if (pProto == null)
{
Log.outError(LogFilter.Sql, "Key item {0} does not exist for map {1} difficulty {2}, removing key requirement.", ar.item, mapid, difficulty);
ar.item = 0;
}
}
if (ar.item2 != 0)
{
ItemTemplate pProto = GetItemTemplate(ar.item2);
if (pProto == null)
{
Log.outError(LogFilter.Sql, "Second item {0} does not exist for map {1} difficulty {2}, removing key requirement.", ar.item2, mapid, difficulty);
ar.item2 = 0;
}
}
if (ar.quest_A != 0)
{
if (GetQuestTemplate(ar.quest_A) == null)
{
Log.outError(LogFilter.Sql, "Required Alliance Quest {0} not exist for map {1} difficulty {2}, remove quest done requirement.", ar.quest_A, mapid, difficulty);
ar.quest_A = 0;
}
}
if (ar.quest_H != 0)
{
if (GetQuestTemplate(ar.quest_H) == null)
{
Log.outError(LogFilter.Sql, "Required Horde Quest {0} not exist for map {1} difficulty {2}, remove quest done requirement.", ar.quest_H, mapid, difficulty);
ar.quest_H = 0;
}
}
if (ar.achievement != 0)
{
if (!CliDB.AchievementStorage.ContainsKey(ar.achievement))
{
Log.outError(LogFilter.Sql, "Required Achievement {0} not exist for map {1} difficulty {2}, remove quest done requirement.", ar.achievement, mapid, difficulty);
ar.achievement = 0;
}
}
_accessRequirementStorage[requirementId] = ar;
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} access requirement definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadInstanceEncounters()
{
uint oldMSTime = Time.GetMSTime();
// 0 1 2 3
SQLResult result = DB.World.Query("SELECT entry, creditType, creditEntry, lastEncounterDungeon FROM instance_encounters");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 instance encounters, table is empty!");
return;
}
uint count = 0;
Dictionary<uint, Tuple<uint, DungeonEncounterRecord>> dungeonLastBosses = new Dictionary<uint, Tuple<uint, DungeonEncounterRecord>>();
do
{
uint entry = result.Read<uint>(0);
EncounterCreditType creditType = (EncounterCreditType)result.Read<byte>(1);
uint creditEntry = result.Read<uint>(2);
uint lastEncounterDungeon = result.Read<uint>(3);
DungeonEncounterRecord dungeonEncounter = CliDB.DungeonEncounterStorage.LookupByKey(entry);
if (dungeonEncounter == null)
{
Log.outError(LogFilter.Sql, "Table `instance_encounters` has an invalid encounter id {0}, skipped!", entry);
continue;
}
if (lastEncounterDungeon != 0 && Global.LFGMgr.GetLFGDungeonEntry(lastEncounterDungeon) == 0)
{
Log.outError(LogFilter.Sql, "Table `instance_encounters` has an encounter {0} ({1}) marked as final for invalid dungeon id {2}, skipped!",
entry, dungeonEncounter.Name[Global.WorldMgr.GetDefaultDbcLocale()], lastEncounterDungeon);
continue;
}
var pair = dungeonLastBosses.LookupByKey(lastEncounterDungeon);
if (lastEncounterDungeon != 0)
{
if (pair != null)
{
Log.outError(LogFilter.Sql, "Table `instance_encounters` specified encounter {0} ({1}) as last encounter but {2} ({3}) is already marked as one, skipped!",
entry, dungeonEncounter.Name[Global.WorldMgr.GetDefaultDbcLocale()], pair.Item1, pair.Item2.Name[Global.WorldMgr.GetDefaultDbcLocale()]);
continue;
}
dungeonLastBosses[lastEncounterDungeon] = Tuple.Create(entry, dungeonEncounter);
}
switch (creditType)
{
case EncounterCreditType.KillCreature:
{
CreatureTemplate creatureInfo = GetCreatureTemplate(creditEntry);
if (creatureInfo == null)
{
Log.outError(LogFilter.Sql, "Table `instance_encounters` has an invalid creature (entry {0}) linked to the encounter {1} ({2}), skipped!",
creditEntry, entry, dungeonEncounter.Name[Global.WorldMgr.GetDefaultDbcLocale()]);
continue;
}
creatureInfo.FlagsExtra |= CreatureFlagsExtra.DungeonBoss;
break;
}
case EncounterCreditType.CastSpell:
if (!Global.SpellMgr.HasSpellInfo(creditEntry))
{
Log.outError(LogFilter.Sql, "Table `instance_encounters` has an invalid spell (entry {0}) linked to the encounter {1} ({2}), skipped!",
creditEntry, entry, dungeonEncounter.Name[Global.WorldMgr.GetDefaultDbcLocale()]);
continue;
}
break;
default:
Log.outError(LogFilter.Sql, "Table `instance_encounters` has an invalid credit type ({0}) for encounter {1} ({2}), skipped!",
creditType, entry, dungeonEncounter.Name[Global.WorldMgr.GetDefaultDbcLocale()]);
continue;
}
if (dungeonEncounter.DifficultyID == 0)
{
for (uint i = 0; i < (int)Difficulty.Max; ++i)
{
if (Global.DB2Mgr.GetMapDifficultyData(dungeonEncounter.MapID, (Difficulty)i) != null)
_dungeonEncounterStorage.Add(MathFunctions.MakePair64(dungeonEncounter.MapID, i), new DungeonEncounter(dungeonEncounter, creditType, creditEntry, lastEncounterDungeon));
}
}
else
_dungeonEncounterStorage.Add(MathFunctions.MakePair64(dungeonEncounter.MapID, (uint)dungeonEncounter.DifficultyID), new DungeonEncounter(dungeonEncounter, creditType, creditEntry, lastEncounterDungeon));
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} instance encounters in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public InstanceTemplate GetInstanceTemplate(uint mapID)
{
return instanceTemplateStorage.LookupByKey(mapID);
}
public GameTele GetGameTele(uint id)
{
return gameTeleStorage.LookupByKey(id);
}
public GameTele GetGameTele(string name)
{
return gameTeleStorage.Values.FirstOrDefault(p => p.name.ToLower() == name.ToLower());
}
public List<DungeonEncounter> GetDungeonEncounterList(uint mapId, Difficulty difficulty)
{
return _dungeonEncounterStorage.LookupByKey(MathFunctions.MakePair64(mapId, (uint)difficulty));
}
public bool IsTransportMap(uint mapId) { return _transportMaps.Contains((ushort)mapId); }
//Player
public void LoadPlayerInfo()
{
for (uint race = 0; race < (int)Race.Max; ++race)
_playerInfo[race] = new PlayerInfo[(int)Class.Max];
var time = Time.GetMSTime();
// Load playercreate
{
// 0 1 2 3 4 5 6 7
SQLResult result = DB.World.Query("SELECT race, class, map, zone, position_x, position_y, position_z, orientation FROM playercreateinfo");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 player create definitions. DB table `playercreateinfo` is empty.");
return;
}
uint count = 0;
do
{
uint currentrace = result.Read<uint>(0);
uint currentclass = result.Read<uint>(1);
uint mapId = result.Read<uint>(2);
uint zoneId = result.Read<uint>(3);
float positionX = result.Read<float>(4);
float positionY = result.Read<float>(5);
float positionZ = result.Read<float>(6);
float orientation = result.Read<float>(7);
if (currentrace >= (int)Race.Max)
{
Log.outError(LogFilter.Sql, "Wrong race {0} in `playercreateinfo` table, ignoring.", currentrace);
continue;
}
var rEntry = CliDB.ChrRacesStorage.LookupByKey(currentrace);
if (rEntry == null)
{
Log.outError(LogFilter.Sql, "Wrong race {0} in `playercreateinfo` table, ignoring.", currentrace);
continue;
}
if (currentclass >= (int)Class.Max)
{
Log.outError(LogFilter.Sql, "Wrong class {0} in `playercreateinfo` table, ignoring.", currentclass);
continue;
}
if (CliDB.ChrClassesStorage.LookupByKey(currentclass) == null)
{
Log.outError(LogFilter.Sql, "Wrong class {0} in `playercreateinfo` table, ignoring.", currentclass);
continue;
}
PlayerInfo pInfo = new PlayerInfo();
pInfo.MapId = mapId;
pInfo.ZoneId = zoneId;
pInfo.PositionX = positionX;
pInfo.PositionY = positionY;
pInfo.PositionZ = positionZ;
pInfo.Orientation = orientation;
pInfo.DisplayId_m = rEntry.MaleDisplayID;
pInfo.DisplayId_f = rEntry.FemaleDisplayID;
_playerInfo[currentrace][currentclass] = pInfo;
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} player create definitions in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
time = Time.GetMSTime();
// Load playercreate items
Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Items Data...");
{
// 0 1 2 3
SQLResult result = DB.World.Query("SELECT race, class, itemid, amount FROM playercreateinfo_item");
if (result.IsEmpty())
Log.outError(LogFilter.ServerLoading, "Loaded 0 custom player create items. DB table `playercreateinfo_item` is empty.");
else
{
uint count = 0;
do
{
uint currentrace = result.Read<uint>(0);
if (currentrace >= (int)Race.Max)
{
Log.outError(LogFilter.Sql, "Wrong race {0} in `playercreateinfo_item` table, ignoring.", currentrace);
continue;
}
uint currentclass = result.Read<uint>(1);
if (currentclass >= (int)Class.Max)
{
Log.outError(LogFilter.Sql, "Wrong class {0} in `playercreateinfo_item` table, ignoring.", currentclass);
continue;
}
uint itemid = result.Read<uint>(2);
if (GetItemTemplate(itemid).GetId() == 0)
{
Log.outError(LogFilter.Sql, "Item id {0} (race {1} class {2}) in `playercreateinfo_item` table but not listed in `itemtemplate`, ignoring.", itemid, currentrace, currentclass);
continue;
}
int amount = result.Read<int>(3);
if (amount == 0)
{
Log.outError(LogFilter.Sql, "Item id {0} (class {1} race {2}) have amount == 0 in `playercreateinfo_item` table, ignoring.", itemid, currentrace, currentclass);
continue;
}
if (currentrace == 0 || currentclass == 0)
{
uint minrace = currentrace != 0 ? currentrace : 1;
uint maxrace = currentrace != 0 ? currentrace + 1 : (int)Race.Max;
uint minclass = currentclass != 0 ? currentclass : 1;
uint maxclass = currentclass != 0 ? currentclass + 1 : (int)Class.Max;
for (var r = minrace; r < maxrace; ++r)
for (var c = minclass; c < maxclass; ++c)
PlayerCreateInfoAddItemHelper(r, c, itemid, amount);
}
else
PlayerCreateInfoAddItemHelper(currentrace, currentclass, itemid, amount);
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} custom player create items in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
}
// Load playercreate skills
Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Skill Data...");
{
uint oldMSTime = Time.GetMSTime();
foreach (SkillRaceClassInfoRecord rcInfo in CliDB.SkillRaceClassInfoStorage.Values)
{
if (rcInfo.Availability == 1)
{
for (int raceIndex = (int)Race.Human; raceIndex < (int)Race.Max; ++raceIndex)
{
if (rcInfo.RaceMask == -1 || Convert.ToBoolean((1 << (raceIndex - 1)) & rcInfo.RaceMask))
{
for (int classIndex = (int)Class.Warrior; classIndex < (int)Class.Max; ++classIndex)
{
if (rcInfo.ClassMask == -1 || Convert.ToBoolean((1 << (classIndex - 1)) & rcInfo.ClassMask))
{
PlayerInfo info = _playerInfo[raceIndex][classIndex];
if (info != null)
info.skills.Add(rcInfo);
}
}
}
}
}
}
Log.outInfo(LogFilter.ServerLoading, "Loaded player create skills in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime));
}
// Load playercreate custom spells
Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Custom Spell Data...");
{
uint oldMSTime = Time.GetMSTime();
SQLResult result = DB.World.Query("SELECT racemask, classmask, Spell FROM playercreateinfo_spell_custom");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 player create custom spells. DB table `playercreateinfo_spell_custom` is empty.");
}
else
{
uint count = 0;
do
{
uint raceMask = result.Read<uint>(0);
uint classMask = result.Read<uint>(1);
uint spellId = result.Read<uint>(2);
if (raceMask != 0 && !Convert.ToBoolean(raceMask & (int)Race.RaceMaskAllPlayable))
{
Log.outError(LogFilter.Sql, "Wrong race mask {0} in `playercreateinfo_spell_custom` table, ignoring.", raceMask);
continue;
}
if (classMask != 0 && !Convert.ToBoolean(classMask & (int)Class.ClassMaskAllPlayable))
{
Log.outError(LogFilter.Sql, "Wrong class mask {0} in `playercreateinfo_spell_custom` table, ignoring.", classMask);
continue;
}
for (int raceIndex = (int)Race.Human; raceIndex < (int)Race.Max; ++raceIndex)
{
if (raceMask == 0 || Convert.ToBoolean((1 << (raceIndex - 1)) & raceMask))
{
for (int classIndex = (int)Class.Warrior; classIndex < (int)Class.Max; ++classIndex)
{
if (classMask == 0 || Convert.ToBoolean((1 << (classIndex - 1)) & classMask))
{
PlayerInfo info = _playerInfo[raceIndex][classIndex];
if (info != null)
{
info.customSpells.Add(spellId);
++count;
}
// We need something better here, the check is not accounting for spells used by multiple races/classes but not all of them.
// Either split the masks per class, or per race, which kind of kills the point yet.
}
}
}
}
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} custom player create spells in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
}
// Load playercreate cast spell
Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Cast Spell Data...");
{
uint oldMSTime = Time.GetMSTime();
SQLResult result = DB.World.Query("SELECT raceMask, classMask, spell FROM playercreateinfo_cast_spell");
if (result.IsEmpty())
Log.outError(LogFilter.ServerLoading, "Loaded 0 player create cast spells. DB table `playercreateinfo_cast_spell` is empty.");
else
{
uint count = 0;
do
{
uint raceMask = result.Read<uint>(0);
uint classMask = result.Read<uint>(1);
uint spellId = result.Read<uint>(2);
if (raceMask != 0 && !raceMask.HasAnyFlag<uint>((uint)Race.RaceMaskAllPlayable))
{
Log.outError(LogFilter.Sql, "Wrong race mask {0} in `playercreateinfo_cast_spell` table, ignoring.", raceMask);
continue;
}
if (classMask != 0 && !classMask.HasAnyFlag<uint>((uint)Class.ClassMaskAllPlayable))
{
Log.outError(LogFilter.Sql, "Wrong class mask {0} in `playercreateinfo_cast_spell` table, ignoring.", classMask);
continue;
}
for (int raceIndex = (int)Race.Human; raceIndex < (int)Race.Max; ++raceIndex)
{
if (raceMask == 0 || Convert.ToBoolean((1 << (raceIndex - 1)) & raceMask))
{
for (int classIndex = (int)Class.Warrior; classIndex < (int)Class.Max; ++classIndex)
{
if (classMask == 0 || Convert.ToBoolean((1 << (classIndex - 1)) & classMask))
{
PlayerInfo info = _playerInfo[raceIndex][classIndex];
if (info != null)
{
info.castSpells.Add(spellId);
++count;
}
}
}
}
}
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} player create cast spells in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
}
// Load playercreate actions
time = Time.GetMSTime();
Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Action Data...");
{
// 0 1 2 3 4
SQLResult result = DB.World.Query("SELECT race, class, button, action, type FROM playercreateinfo_action");
if (result.IsEmpty())
Log.outError(LogFilter.ServerLoading, "Loaded 0 player create actions. DB table `playercreateinfo_action` is empty.");
else
{
uint count = 0;
do
{
uint currentrace = result.Read<uint>(0);
if (currentrace >= (int)Race.Max)
{
Log.outError(LogFilter.Sql, "Wrong race {0} in `playercreateinfo_action` table, ignoring.", currentrace);
continue;
}
uint currentclass = result.Read<uint>(1);
if (currentclass >= (int)Class.Max)
{
Log.outError(LogFilter.Sql, "Wrong class {0} in `playercreateinfo_action` table, ignoring.", currentclass);
continue;
}
PlayerInfo info = _playerInfo[currentrace][currentclass];
if (info != null)
info.action.Add(new PlayerCreateInfoAction(result.Read<byte>(2), result.Read<uint>(3), result.Read<byte>(4)));
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} player create actions in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
}
time = Time.GetMSTime();
// Loading levels data (class/race dependent)
Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Level Stats Data...");
{
// 0 1 2 3 4 5 6
SQLResult result = DB.World.Query("SELECT race, class, level, str, agi, sta, inte FROM player_levelstats");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 level stats definitions. DB table `player_levelstats` is empty.");
Global.WorldMgr.StopNow();
return;
}
uint count = 0;
do
{
uint currentrace = result.Read<uint>(0);
if (currentrace >= (int)Race.Max)
{
Log.outError(LogFilter.Sql, "Wrong race {0} in `player_levelstats` table, ignoring.", currentrace);
continue;
}
uint currentclass = result.Read<uint>(1);
if (currentclass >= (int)Class.Max)
{
Log.outError(LogFilter.Sql, "Wrong class {0} in `player_levelstats` table, ignoring.", currentclass);
continue;
}
uint currentlevel = result.Read<uint>(2);
if (currentlevel > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
{
if (currentlevel > 255) // hardcoded level maximum
Log.outError(LogFilter.Sql, "Wrong (> {0}) level {1} in `player_levelstats` table, ignoring.", 255, currentlevel);
else
{
Log.outError(LogFilter.Sql, "Unused (> MaxPlayerLevel in worldserver.conf) level {0} in `player_levelstats` table, ignoring.", currentlevel);
++count; // make result loading percent "expected" correct in case disabled detail mode for example.
}
continue;
}
var pInfo = _playerInfo[currentrace][currentclass];
if (pInfo == null)
continue;
var levelinfo = new PlayerLevelInfo();
for (var i = 0; i < (int)Stats.Max; i++)
levelinfo.stats[i] = result.Read<ushort>(i + 3);
pInfo.levelInfo[currentlevel - 1] = levelinfo;
++count;
} while (result.NextRow());
// Fill gaps and check integrity
for (uint race = 0; race < (int)Race.Max; ++race)
{
// skip non existed races
if (!CliDB.ChrRacesStorage.ContainsKey(race) || _playerInfo[race][0] == null)
continue;
for (uint _class = 0; _class < (int)Class.Max; ++_class)
{
// skip non existed classes
if (CliDB.ChrClassesStorage.LookupByKey(_class) == null || _playerInfo[race][_class] == null)
continue;
PlayerInfo pInfo = _playerInfo[race][_class];
if (pInfo == null)
continue;
// skip non loaded combinations
if (pInfo.DisplayId_m == 0 || pInfo.DisplayId_f == 0)
continue;
// skip expansion races if not playing with expansion
if (WorldConfig.GetIntValue(WorldCfg.Expansion) < (int)Expansion.BurningCrusade && (race == (int)Race.BloodElf || race == (int)Race.Draenei))
continue;
// skip expansion classes if not playing with expansion
if (WorldConfig.GetIntValue(WorldCfg.Expansion) < (int)Expansion.WrathOfTheLichKing && _class == (int)Class.Deathknight)
continue;
if (WorldConfig.GetIntValue(WorldCfg.Expansion) < (int)Expansion.MistsOfPandaria && (race == (int)Race.PandarenNeutral || race == (int)Race.PandarenHorde || race == (int)Race.PandarenAlliance))
continue;
if (WorldConfig.GetIntValue(WorldCfg.Expansion) < (int)Expansion.Legion && _class == (int)Class.DemonHunter)
continue;
// fatal error if no level 1 data
if (pInfo.levelInfo == null || pInfo.levelInfo[0] == null)
{
Log.outError(LogFilter.Sql, "Race {0} Class {1} Level 1 does not have stats data!", race, _class);
Global.WorldMgr.StopNow();
return;
}
// fill level gaps
for (var level = 1; level < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel); ++level)
{
if (pInfo.levelInfo[level] == null)
{
Log.outError(LogFilter.Sql, "Race {0} Class {1} Level {2} does not have stats data. Using stats data of level {3}.", race, _class, level + 1, level);
pInfo.levelInfo[level] = pInfo.levelInfo[level - 1];
}
}
}
}
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} level stats definitions in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
time = Time.GetMSTime();
// Loading xp per level data
Log.outInfo(LogFilter.ServerLoading, "Loading Player Create XP Data...");
{
_playerXPperLevel = new uint[CliDB.XpGameTable.GetTableRowCount() + 1];
// 0 1
SQLResult result = DB.World.Query("SELECT Level, Experience FROM player_xp_for_level");
// load the DBC's levels at first...
for (uint level = 1; level < CliDB.XpGameTable.GetTableRowCount(); ++level)
_playerXPperLevel[level] = (uint)CliDB.XpGameTable.GetRow(level).Total;
uint count = 0;
// ...overwrite if needed (custom values)
if (!result.IsEmpty())
{
do
{
uint currentlevel = result.Read<byte>(0);
uint currentxp = result.Read<uint>(1);
if (currentlevel >= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
{
if (currentlevel > SharedConst.StrongMaxLevel) // hardcoded level maximum
Log.outError(LogFilter.Sql, "Wrong (> {0}) level {1} in `player_xp_for_level` table, ignoring.", 255, currentlevel);
else
{
Log.outError(LogFilter.Sql, "Unused (> MaxPlayerLevel in worldserver.conf) level {0} in `player_xp_for_levels` table, ignoring.", currentlevel);
++count; // make result loading percent "expected" correct in case disabled detail mode for example.
}
continue;
}
//PlayerXPperLevel
_playerXPperLevel[currentlevel] = currentxp;
++count;
} while (result.NextRow());
// fill level gaps
for (var level = 1; level < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel); ++level)
{
if (_playerXPperLevel[level] == 0)
{
Log.outError(LogFilter.Sql, "Level {0} does not have XP for level data. Using data of level [{1}] + 12000.", level + 1, level);
_playerXPperLevel[level] = _playerXPperLevel[level - 1] + 12000;
}
}
}
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} xp for level definition(s) from database in {1} ms", count, Time.GetMSTimeDiffToNow(time));
}
}
void PlayerCreateInfoAddItemHelper(uint race, uint _class, uint itemId, int count)
{
if (_playerInfo[race][_class] == null)
return;
if (count > 0)
_playerInfo[race][_class].item.Add(new PlayerCreateInfoItem(itemId, (uint)count));
else
{
if (count < -1)
Log.outError(LogFilter.Sql, "Invalid count {0} specified on item {1} be removed from original player create info (use -1)!", count, itemId);
for (byte gender = 0; gender < (int)Gender.None; ++gender)
{
CharStartOutfitRecord entry = Global.DB2Mgr.GetCharStartOutfitEntry(race, _class, gender);
if (entry != null)
{
bool found = false;
for (var x = 0; x < ItemConst.MaxOutfitItems; ++x)
{
if (entry.ItemID[x] > 0 && entry.ItemID[x] == itemId)
{
found = true;
entry.ItemID[x] = 0;
break;
}
}
if (!found)
Log.outError(LogFilter.Sql, "Item {0} specified to be removed from original create info not found in dbc!", itemId);
}
}
}
}
public PlayerInfo GetPlayerInfo(Race raceId, Class classId)
{
if (raceId >= Race.Max)
return null;
if (classId >= Class.Max)
return null;
var info = _playerInfo[(int)raceId][(int)classId];
if (info == null)
return null;
return info;
}
public void GetPlayerClassLevelInfo(Class _class, uint level, out uint baseMana)
{
baseMana = 0;
if (level < 1 || _class >= Class.Max)
return;
if (level > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
level = (byte)WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel);
GtBaseMPRecord mp = CliDB.BaseMPGameTable.GetRow(level);
if (mp == null)
{
Log.outError(LogFilter.Sql, "Tried to get non-existant Class-Level combination data for base mp. Class {0} Level {1}", _class, level);
return;
}
baseMana = (uint)CliDB.GetGameTableColumnForClass(mp, _class);
}
public PlayerLevelInfo GetPlayerLevelInfo(Race race, Class _class, uint level)
{
if (level < 1 || race >= Race.Max || _class >= Class.Max)
return null;
PlayerInfo pInfo = _playerInfo[(int)race][(int)_class];
if (pInfo.DisplayId_m == 0 || pInfo.DisplayId_f == 0)
return null;
if (level <= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
return pInfo.levelInfo[level - 1];
//else
//return BuildPlayerLevelInfo(race, _class, level, info);
return null;
}
/*
void BuildPlayerLevelInfo(byte race, Class _class, byte level, PlayerLevelInfo info)
{
// base data (last known level)
info = _playerInfo[race][_class].levelInfo[WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) - 1];
// if conversion from uint32 to uint8 causes unexpected behaviour, change lvl to uint32
for (int lvl = WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) - 1; lvl < level; ++lvl)
{
switch (_class)
{
case Class.Warrior:
info.stats[STAT_STRENGTH] += (lvl > 23 ? 2 : (lvl > 1 ? 1 : 0));
info.stats[STAT_STAMINA] += (lvl > 23 ? 2 : (lvl > 1 ? 1 : 0));
info.stats[STAT_AGILITY] += (lvl > 36 ? 1 : (lvl > 6 && (lvl % 2) ? 1 : 0));
info.stats[STAT_INTELLECT] += (lvl > 9 && !(lvl % 2) ? 1 : 0);
break;
case CLASS_PALADIN:
info.stats[STAT_STRENGTH] += (lvl > 3 ? 1 : 0);
info.stats[STAT_STAMINA] += (lvl > 33 ? 2 : (lvl > 1 ? 1 : 0));
info.stats[STAT_AGILITY] += (lvl > 38 ? 1 : (lvl > 7 && !(lvl % 2) ? 1 : 0));
info.stats[STAT_INTELLECT] += (lvl > 6 && (lvl % 2) ? 1 : 0);
break;
case CLASS_HUNTER:
info.stats[STAT_STRENGTH] += (lvl > 4 ? 1 : 0);
info.stats[STAT_STAMINA] += (lvl > 4 ? 1 : 0);
info.stats[STAT_AGILITY] += (lvl > 33 ? 2 : (lvl > 1 ? 1 : 0));
info.stats[STAT_INTELLECT] += (lvl > 8 && (lvl % 2) ? 1 : 0);
break;
case CLASS_ROGUE:
info.stats[STAT_STRENGTH] += (lvl > 5 ? 1 : 0);
info.stats[STAT_STAMINA] += (lvl > 4 ? 1 : 0);
info.stats[STAT_AGILITY] += (lvl > 16 ? 2 : (lvl > 1 ? 1 : 0));
info.stats[STAT_INTELLECT] += (lvl > 8 && !(lvl % 2) ? 1 : 0);
break;
case CLASS_PRIEST:
info.stats[STAT_STRENGTH] += (lvl > 9 && !(lvl % 2) ? 1 : 0);
info.stats[STAT_STAMINA] += (lvl > 5 ? 1 : 0);
info.stats[STAT_AGILITY] += (lvl > 38 ? 1 : (lvl > 8 && (lvl % 2) ? 1 : 0));
info.stats[STAT_INTELLECT] += (lvl > 22 ? 2 : (lvl > 1 ? 1 : 0));
break;
case CLASS_SHAMAN:
info.stats[STAT_STRENGTH] += (lvl > 34 ? 1 : (lvl > 6 && (lvl % 2) ? 1 : 0));
info.stats[STAT_STAMINA] += (lvl > 4 ? 1 : 0);
info.stats[STAT_AGILITY] += (lvl > 7 && !(lvl % 2) ? 1 : 0);
info.stats[STAT_INTELLECT] += (lvl > 5 ? 1 : 0);
break;
case CLASS_MAGE:
info.stats[STAT_STRENGTH] += (lvl > 9 && !(lvl % 2) ? 1 : 0);
info.stats[STAT_STAMINA] += (lvl > 5 ? 1 : 0);
info.stats[STAT_AGILITY] += (lvl > 9 && !(lvl % 2) ? 1 : 0);
info.stats[STAT_INTELLECT] += (lvl > 24 ? 2 : (lvl > 1 ? 1 : 0));
break;
case CLASS_WARLOCK:
info.stats[STAT_STRENGTH] += (lvl > 9 && !(lvl % 2) ? 1 : 0);
info.stats[STAT_STAMINA] += (lvl > 38 ? 2 : (lvl > 3 ? 1 : 0));
info.stats[STAT_AGILITY] += (lvl > 9 && !(lvl % 2) ? 1 : 0);
info.stats[STAT_INTELLECT] += (lvl > 33 ? 2 : (lvl > 2 ? 1 : 0));
break;
case CLASS_DRUID:
info.stats[STAT_STRENGTH] += (lvl > 38 ? 2 : (lvl > 6 && (lvl % 2) ? 1 : 0));
info.stats[STAT_STAMINA] += (lvl > 32 ? 2 : (lvl > 4 ? 1 : 0));
info.stats[STAT_AGILITY] += (lvl > 38 ? 2 : (lvl > 8 && (lvl % 2) ? 1 : 0));
info.stats[STAT_INTELLECT] += (lvl > 38 ? 3 : (lvl > 4 ? 1 : 0));
}
}
}
*/
//Pets
public void LoadPetLevelInfo()
{
uint oldMSTime = Time.GetMSTime();
// 0 1 2 3 4 5 6 7 8 9
SQLResult result = DB.World.Query("SELECT creature_entry, level, hp, mana, str, agi, sta, inte, spi, armor FROM pet_levelstats");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 level pet stats definitions. DB table `pet_levelstats` is empty.");
return;
}
uint count = 0;
do
{
uint creatureid = result.Read<uint>(0);
if (GetCreatureTemplate(creatureid) == null)
{
Log.outError(LogFilter.Sql, "Wrong creature id {0} in `pet_levelstats` table, ignoring.", creatureid);
continue;
}
uint currentlevel = result.Read<uint>(1);
if (currentlevel > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
{
if (currentlevel > SharedConst.StrongMaxLevel) // hardcoded level maximum
Log.outError(LogFilter.Sql, "Wrong (> {0}) level {1} in `pet_levelstats` table, ignoring.", SharedConst.StrongMaxLevel, currentlevel);
else
{
Log.outInfo(LogFilter.Server, "Unused (> MaxPlayerLevel in worldserver.conf) level {0} in `pet_levelstats` table, ignoring.", currentlevel);
++count; // make result loading percent "expected" correct in case disabled detail mode for example.
}
continue;
}
else if (currentlevel < 1)
{
Log.outError(LogFilter.Sql, "Wrong (<1) level {0} in `pet_levelstats` table, ignoring.", currentlevel);
continue;
}
var pInfoMapEntry = petInfoStore.LookupByKey(creatureid);
if (pInfoMapEntry == null)
pInfoMapEntry = new PetLevelInfo[WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)];
PetLevelInfo pLevelInfo = new PetLevelInfo();
pLevelInfo.health = result.Read<uint>(2);
pLevelInfo.mana = result.Read<uint>(3);
pLevelInfo.armor = result.Read<uint>(9);
for (int i = 0; i < (int)Stats.Max; i++)
{
pLevelInfo.stats[i] = result.Read<uint>(i + 4);
}
pInfoMapEntry[currentlevel - 1] = pLevelInfo;
++count;
}
while (result.NextRow());
// Fill gaps and check integrity
foreach (var map in petInfoStore)
{
var pInfo = map.Value;
// fatal error if no level 1 data
if (pInfo == null || pInfo[0].health == 0)
{
Log.outError(LogFilter.Sql, "Creature {0} does not have pet stats data for Level 1!", map.Key);
Global.WorldMgr.StopNow();
}
// fill level gaps
for (byte level = 1; level < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel); ++level)
{
if (pInfo[level].health == 0)
{
Log.outError(LogFilter.Sql, "Creature {0} has no data for Level {1} pet stats data, using data of Level {2}.", map.Key, level + 1, level);
pInfo[level] = pInfo[level - 1];
}
}
}
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} level pet stats definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadPetNames()
{
uint oldMSTime = Time.GetMSTime();
// 0 1 2
SQLResult result = DB.World.Query("SELECT word, entry, half FROM pet_name_generation");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 pet name parts. DB table `pet_name_generation` is empty!");
return;
}
uint count = 0;
do
{
string word = result.Read<string>(0);
uint entry = result.Read<uint>(1);
bool half = result.Read<bool>(2);
if (half)
_petHalfName1.Add(entry, word);
else
_petHalfName0.Add(entry, word);
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} pet name parts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadPetNumber()
{
uint oldMSTime = Time.GetMSTime();
SQLResult result = DB.Characters.Query("SELECT MAX(id) FROM character_pet");
if (!result.IsEmpty())
_hiPetNumber = result.Read<uint>(0) + 1;
Log.outInfo(LogFilter.ServerLoading, "Loaded the max pet number: {0} in {1} ms", _hiPetNumber - 1, Time.GetMSTimeDiffToNow(oldMSTime));
}
public PetLevelInfo GetPetLevelInfo(uint creatureid, uint level)
{
if (level > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
level = WorldConfig.GetUIntValue(WorldCfg.MaxPlayerLevel);
var petinfo = petInfoStore.LookupByKey(creatureid);
if (petinfo == null)
return null;
return petinfo[level - 1]; // data for level 1 stored in [0] array element, ...
}
public string GeneratePetName(uint entry)
{
var list0 = _petHalfName0[entry];
var list1 = _petHalfName1[entry];
if (list0.Empty() || list1.Empty())
{
CreatureTemplate cinfo = GetCreatureTemplate(entry);
if (cinfo == null)
return "";
string petname = Global.DB2Mgr.GetCreatureFamilyPetName(cinfo.Family, Global.WorldMgr.GetDefaultDbcLocale());
if (!string.IsNullOrEmpty(petname))
return petname;
else
return cinfo.Name;
}
return list0[RandomHelper.IRand(0, list0.Count - 1)] + list1[RandomHelper.IRand(0, list1.Count - 1)];
}
public uint GeneratePetNumber()
{
if (_hiPetNumber >= 0xFFFFFFFE)
{
Log.outError(LogFilter.Misc, "_hiPetNumber Id overflow!! Can't continue, shutting down server. ");
Global.WorldMgr.StopNow(ShutdownExitCode.Error);
}
return _hiPetNumber++;
}
//Faction Change
public void LoadFactionChangeAchievements()
{
uint oldMSTime = Time.GetMSTime();
SQLResult result = DB.World.Query("SELECT alliance_id, horde_id FROM player_factionchange_achievement");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 faction change achievement pairs. DB table `player_factionchange_achievement` is empty.");
return;
}
uint count = 0;
do
{
uint alliance = result.Read<uint>(0);
uint horde = result.Read<uint>(1);
if (!CliDB.AchievementStorage.ContainsKey(alliance))
Log.outError(LogFilter.Sql, "Achievement {0} (alliance_id) referenced in `player_factionchange_achievement` does not exist, pair skipped!", alliance);
else if (!CliDB.AchievementStorage.ContainsKey(horde))
Log.outError(LogFilter.Sql, "Achievement {0} (horde_id) referenced in `player_factionchange_achievement` does not exist, pair skipped!", horde);
else
FactionChangeAchievements[alliance] = horde;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} faction change achievement pairs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadFactionChangeItems()
{
uint oldMSTime = Time.GetMSTime();
SQLResult result = DB.World.Query("SELECT alliance_id, horde_id FROM player_factionchange_items");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 faction change item pairs. DB table `player_factionchange_items` is empty.");
return;
}
uint count = 0;
do
{
uint alliance = result.Read<uint>(0);
uint horde = result.Read<uint>(1);
if (GetItemTemplate(alliance) == null)
Log.outError(LogFilter.Sql, "Item {0} (alliance_id) referenced in `player_factionchange_items` does not exist, pair skipped!", alliance);
else if (GetItemTemplate(horde) == null)
Log.outError(LogFilter.Sql, "Item {0} (horde_id) referenced in `player_factionchange_items` does not exist, pair skipped!", horde);
else
FactionChangeItems[alliance] = horde;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} faction change item pairs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadFactionChangeQuests()
{
uint oldMSTime = Time.GetMSTime();
SQLResult result = DB.World.Query("SELECT alliance_id, horde_id FROM player_factionchange_quests");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 faction change quest pairs. DB table `player_factionchange_quests` is empty.");
return;
}
uint count = 0;
do
{
uint alliance = result.Read<uint>(0);
uint horde = result.Read<uint>(1);
if (Global.ObjectMgr.GetQuestTemplate(alliance) == null)
Log.outError(LogFilter.Sql, "Quest {0} (alliance_id) referenced in `player_factionchange_quests` does not exist, pair skipped!", alliance);
else if (Global.ObjectMgr.GetQuestTemplate(horde) == null)
Log.outError(LogFilter.Sql, "Quest {0} (horde_id) referenced in `player_factionchange_quests` does not exist, pair skipped!", horde);
else
FactionChangeQuests[alliance] = horde;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} faction change quest pairs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadFactionChangeReputations()
{
uint oldMSTime = Time.GetMSTime();
SQLResult result = DB.World.Query("SELECT alliance_id, horde_id FROM player_factionchange_reputations");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 faction change reputation pairs. DB table `player_factionchange_reputations` is empty.");
return;
}
uint count = 0;
do
{
uint alliance = result.Read<uint>(0);
uint horde = result.Read<uint>(1);
if (!CliDB.FactionStorage.ContainsKey(alliance))
Log.outError(LogFilter.Sql, "Reputation {0} (alliance_id) referenced in `player_factionchange_reputations` does not exist, pair skipped!", alliance);
else if (!CliDB.FactionStorage.ContainsKey(horde))
Log.outError(LogFilter.Sql, "Reputation {0} (horde_id) referenced in `player_factionchange_reputations` does not exist, pair skipped!", horde);
else
FactionChangeReputation[alliance] = horde;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} faction change reputation pairs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadFactionChangeSpells()
{
uint oldMSTime = Time.GetMSTime();
SQLResult result = DB.World.Query("SELECT alliance_id, horde_id FROM player_factionchange_spells");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 faction change spell pairs. DB table `player_factionchange_spells` is empty.");
return;
}
uint count = 0;
do
{
uint alliance = result.Read<uint>(0);
uint horde = result.Read<uint>(1);
if (!Global.SpellMgr.HasSpellInfo(alliance))
Log.outError(LogFilter.Sql, "Spell {0} (alliance_id) referenced in `player_factionchange_spells` does not exist, pair skipped!", alliance);
else if (!Global.SpellMgr.HasSpellInfo(horde))
Log.outError(LogFilter.Sql, "Spell {0} (horde_id) referenced in `player_factionchange_spells` does not exist, pair skipped!", horde);
else
FactionChangeSpells[alliance] = horde;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} faction change spell pairs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadFactionChangeTitles()
{
uint oldMSTime = Time.GetMSTime();
SQLResult result = DB.World.Query("SELECT alliance_id, horde_id FROM player_factionchange_titles");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 faction change title pairs. DB table `player_factionchange_title` is empty.");
return;
}
uint count = 0;
do
{
uint alliance = result.Read<uint>(0);
uint horde = result.Read<uint>(1);
if (!CliDB.CharTitlesStorage.ContainsKey(alliance))
Log.outError(LogFilter.Sql, "Title {0} (alliance_id) referenced in `player_factionchange_title` does not exist, pair skipped!", alliance);
else if (!CliDB.CharTitlesStorage.ContainsKey(horde))
Log.outError(LogFilter.Sql, "Title {0} (horde_id) referenced in `player_factionchange_title` does not exist, pair skipped!", horde);
else
FactionChangeTitles[alliance] = horde;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} faction change title pairs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
//Quests
public void LoadQuests()
{
uint oldMSTime = Time.GetMSTime();
// For reload case
_questTemplates.Clear();
_questObjectives.Clear();
_exclusiveQuestGroups.Clear();
SQLResult result = DB.World.Query("SELECT " +
//0 1 2 3 4 5 6 7 8 9 10
"ID, QuestType, QuestLevel, QuestPackageID, MinLevel, QuestSortID, QuestInfoID, SuggestedGroupNum, RewardNextQuest, RewardXPDifficulty, RewardXPMultiplier, " +
//11 12 13 14 15 16 17 18 19 20 21
"RewardMoney, RewardMoneyDifficulty, RewardMoneyMultiplier, RewardBonusMoney, RewardDisplaySpell1, RewardDisplaySpell2, RewardDisplaySpell3, RewardSpell, RewardHonor, RewardKillHonor, StartItem, " +
//22 23 24 25 26
"RewardArtifactXPDifficulty, RewardArtifactXPMultiplier, RewardArtifactCategoryID, Flags, FlagsEx, " +
//27 28 29 30 31 32 33 34
"RewardItem1, RewardAmount1, ItemDrop1, ItemDropQuantity1, RewardItem2, RewardAmount2, ItemDrop2, ItemDropQuantity2, " +
//35 36 37 38 39 40 41 42
"RewardItem3, RewardAmount3, ItemDrop3, ItemDropQuantity3, RewardItem4, RewardAmount4, ItemDrop4, ItemDropQuantity4, " +
//43 44 45 46 47 48
"RewardChoiceItemID1, RewardChoiceItemQuantity1, RewardChoiceItemDisplayID1, RewardChoiceItemID2, RewardChoiceItemQuantity2, RewardChoiceItemDisplayID2, " +
//49 50 51 52 53 54
"RewardChoiceItemID3, RewardChoiceItemQuantity3, RewardChoiceItemDisplayID3, RewardChoiceItemID4, RewardChoiceItemQuantity4, RewardChoiceItemDisplayID4, " +
//55 56 57 58 59 60
"RewardChoiceItemID5, RewardChoiceItemQuantity5, RewardChoiceItemDisplayID5, RewardChoiceItemID6, RewardChoiceItemQuantity6, RewardChoiceItemDisplayID6, " +
//61 62 63 64 65 66 67 68 69 70
"POIContinent, POIx, POIy, POIPriority, RewardTitle, RewardArenaPoints, RewardSkillLineID, RewardNumSkillUps, PortraitGiver, PortraitTurnIn, " +
//71 72 73 74 75 76 77 78
"RewardFactionID1, RewardFactionValue1, RewardFactionOverride1, RewardFactionCapIn1, RewardFactionID2, RewardFactionValue2, RewardFactionOverride2, RewardFactionCapIn2, " +
//79 80 81 82 83 84 85 86
"RewardFactionID3, RewardFactionValue3, RewardFactionOverride3, RewardFactionCapIn3, RewardFactionID4, RewardFactionValue4, RewardFactionOverride4, RewardFactionCapIn4, " +
//87 88 89 90 91
"RewardFactionID5, RewardFactionValue5, RewardFactionOverride5, RewardFactionCapIn5, RewardFactionFlags, " +
//92 93 94 95 96 97 98 99
"RewardCurrencyID1, RewardCurrencyQty1, RewardCurrencyID2, RewardCurrencyQty2, RewardCurrencyID3, RewardCurrencyQty3, RewardCurrencyID4, RewardCurrencyQty4, " +
//100 101 102 103 104 105 106
"AcceptedSoundKitID, CompleteSoundKitID, AreaGroupID, TimeAllowed, AllowableRaces, QuestRewardID, Expansion, " +
//107 108 109 110 111 112 113 114 115
"LogTitle, LogDescription, QuestDescription, AreaDescription, PortraitGiverText, PortraitGiverName, PortraitTurnInText, PortraitTurnInName, QuestCompletionLog" +
" FROM quest_template");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 quests definitions. DB table `quest_template` is empty.");
return;
}
// create multimap previous quest for each existed quest
// some quests can have many previous maps set by NextQuestId in previous quest
// for example set of race quests can lead to single not race specific quest
do
{
Quest newQuest = new Quest(result.GetFields());
_questTemplates[newQuest.Id] = newQuest;
}
while (result.NextRow());
// Load `quest_details`
// 0 1 2 3 4 5 6 7 8
result = DB.World.Query("SELECT ID, Emote1, Emote2, Emote3, Emote4, EmoteDelay1, EmoteDelay2, EmoteDelay3, EmoteDelay4 FROM quest_details");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 quest details. DB table `quest_details` is empty.");
}
else
{
do
{
uint questId = result.Read<uint>(0);
var quest = _questTemplates.LookupByKey(questId);
if (quest != null)
quest.LoadQuestDetails(result.GetFields());
else
Log.outError(LogFilter.Sql, "Table `quest_details` has data for quest {0} but such quest does not exist", questId);
} while (result.NextRow());
}
// Load `quest_request_items`
// 0 1 2 3 4 5
result = DB.World.Query("SELECT ID, EmoteOnComplete, EmoteOnIncomplete, EmoteOnCompleteDelay, EmoteOnIncompleteDelay, CompletionText FROM quest_request_items");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 quest request items. DB table `quest_request_items` is empty.");
}
else
{
do
{
uint questId = result.Read<uint>(0);
var quest = _questTemplates.LookupByKey(questId);
if (quest != null)
quest.LoadQuestRequestItems(result.GetFields());
else
Log.outError(LogFilter.Sql, "Table `quest_request_items` has data for quest {0} but such quest does not exist", questId);
} while (result.NextRow());
}
// Load `quest_offer_reward`
// 0 1 2 3 4 5 6 7 8 9
result = DB.World.Query("SELECT ID, Emote1, Emote2, Emote3, Emote4, EmoteDelay1, EmoteDelay2, EmoteDelay3, EmoteDelay4, RewardText FROM quest_offer_reward");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 quest reward emotes. DB table `quest_offer_reward` is empty.");
}
else
{
do
{
uint questId = result.Read<uint>(0);
var quest = _questTemplates.LookupByKey(questId);
if (quest != null)
quest.LoadQuestOfferReward(result.GetFields());
else
Log.outError(LogFilter.Sql, "Table `quest_offer_reward` has data for quest {0} but such quest does not exist", questId);
} while (result.NextRow());
}
// Load `quest_template_addon`
// 0 1 2 3 4 5 6 7 8
result = DB.World.Query("SELECT ID, MaxLevel, AllowableClasses, SourceSpellID, PrevQuestID, NextQuestID, ExclusiveGroup, RewardMailTemplateID, RewardMailDelay, " +
//9 10 11 12 13 14 15 16 17
"RequiredSkillID, RequiredSkillPoints, RequiredMinRepFaction, RequiredMaxRepFaction, RequiredMinRepValue, RequiredMaxRepValue, ProvidedItemCount, RewardMailSenderEntry, SpecialFlags FROM quest_template_addon LEFT JOIN quest_mail_sender ON Id=QuestId");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 quest template addons. DB table `quest_template_addon` is empty.");
}
else
{
do
{
uint questId = result.Read<uint>(0);
var quest = _questTemplates.LookupByKey(questId);
if (quest != null)
quest.LoadQuestTemplateAddon(result.GetFields());
else
Log.outError(LogFilter.Sql, "Table `quest_template_addon` has data for quest {0} but such quest does not exist", questId);
} while (result.NextRow());
}
// Load `quest_objectives`
// 0 1 2 3 4 5 6 7 8 9
result = DB.World.Query("SELECT ID, QuestID, Type, StorageIndex, ObjectID, Amount, Flags, Flags2, ProgressBarWeight, Description FROM quest_objectives ORDER BY `Order` ASC, StorageIndex ASC");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 quest objectives. DB table `quest_objectives` is empty.");
}
else
{
do
{
uint questId = result.Read<uint>(1);
var quest = _questTemplates.LookupByKey(questId);
if (quest != null)
quest.LoadQuestObjective(result.GetFields());
else
Log.outError(LogFilter.Sql, "Table `quest_objectives` has objective for quest {0} but such quest does not exist", questId);
} while (result.NextRow());
}
// Load `quest_visual_effect` join table with quest_objectives because visual effects are based on objective ID (core stores objectives by their index in quest)
// 0 1 2 3 4
result = DB.World.Query("SELECT v.ID, o.ID, o.QuestID, v.Index, v.VisualEffect FROM quest_visual_effect AS v LEFT JOIN quest_objectives AS o ON v.ID = o.ID ORDER BY v.Index DESC");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 quest visual effects. DB table `quest_visual_effect` is empty.");
}
else
{
do
{
uint vID = result.Read<uint>(0);
uint oID = result.Read<uint>(1);
if (vID == 0)
{
Log.outError(LogFilter.Sql, "Table `quest_visual_effect` has visual effect for null objective id");
continue;
}
// objID will be null if match for table join is not found
if (vID != oID)
{
Log.outError(LogFilter.Sql, "Table `quest_visual_effect` has visual effect for objective {0} but such objective does not exist.", vID);
continue;
}
uint questId = result.Read<uint>(2);
// Do not throw error here because error for non existing quest is thrown while loading quest objectives. we do not need duplication
var quest = _questTemplates.LookupByKey(questId);
if (quest != null)
quest.LoadQuestObjectiveVisualEffect(result.GetFields());
} while (result.NextRow());
}
Dictionary<uint, uint> usedMailTemplates = new Dictionary<uint, uint>();
// Post processing
foreach (var qinfo in _questTemplates.Values)
{
// skip post-loading checks for disabled quests
if (Global.DisableMgr.IsDisabledFor(DisableType.Quest, qinfo.Id, null))
continue;
// additional quest integrity checks (GO, creaturetemplate and itemtemplate must be loaded already)
if (qinfo.Type >= QuestType.Max)
Log.outError(LogFilter.Sql, "Quest {0} has `Method` = {1}, expected values are 0, 1 or 2.", qinfo.Id, qinfo.Type);
if (Convert.ToBoolean(qinfo.SpecialFlags & ~QuestSpecialFlags.DbAllowed))
{
Log.outError(LogFilter.Sql, "Quest {0} has `SpecialFlags` = {1} > max allowed value. Correct `SpecialFlags` to value <= {2}",
qinfo.Id, qinfo.SpecialFlags, QuestSpecialFlags.DbAllowed);
qinfo.SpecialFlags &= QuestSpecialFlags.DbAllowed;
}
if (qinfo.Flags.HasAnyFlag(QuestFlags.Daily) && qinfo.Flags.HasAnyFlag(QuestFlags.Weekly))
{
Log.outError(LogFilter.Sql, "Weekly Quest {0} is marked as daily quest in `Flags`, removed daily flag.", qinfo.Id);
qinfo.Flags &= ~QuestFlags.Daily;
}
if (qinfo.Flags.HasAnyFlag(QuestFlags.Daily))
{
if (!qinfo.SpecialFlags.HasAnyFlag(QuestSpecialFlags.Repeatable))
{
Log.outError(LogFilter.Sql, "Daily Quest {0} not marked as repeatable in `SpecialFlags`, added.", qinfo.Id);
qinfo.SpecialFlags |= QuestSpecialFlags.Repeatable;
}
}
if (qinfo.Flags.HasAnyFlag(QuestFlags.Weekly))
{
if (!qinfo.SpecialFlags.HasAnyFlag(QuestSpecialFlags.Repeatable))
{
Log.outError(LogFilter.Sql, "Weekly Quest {0} not marked as repeatable in `SpecialFlags`, added.", qinfo.Id);
qinfo.SpecialFlags |= QuestSpecialFlags.Repeatable;
}
}
if (qinfo.SpecialFlags.HasAnyFlag(QuestSpecialFlags.Monthly))
{
if (!qinfo.SpecialFlags.HasAnyFlag(QuestSpecialFlags.Repeatable))
{
Log.outError(LogFilter.Sql, "Monthly quest {0} not marked as repeatable in `SpecialFlags`, added.", qinfo.Id);
qinfo.SpecialFlags |= QuestSpecialFlags.Repeatable;
}
}
if (Convert.ToBoolean(qinfo.Flags & QuestFlags.Tracking))
{
// at auto-reward can be rewarded only RewardChoiceItemId[0]
for (int j = 1; j < qinfo.RewardChoiceItemId.Length; ++j)
{
var id = qinfo.RewardChoiceItemId[j];
if (id != 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardChoiceItemId{1}` = {2} but item from `RewardChoiceItemId{3}` can't be rewarded with quest flag QUESTFLAGSTRACKING.",
qinfo.Id, j + 1, id, j + 1);
// no changes, quest ignore this data
}
}
}
if (qinfo.MinLevel == -1 || qinfo.MinLevel > SharedConst.DefaultMaxLevel)
{
Log.outError(LogFilter.Sql, "Quest {0} should be disabled because `MinLevel` = {1}", qinfo.Id, qinfo.MinLevel);
// no changes needed, sending -1 in SMSGQUESTQUERYRESPONSE is valid
}
// client quest log visual (area case)
if (qinfo.QuestSortID > 0)
{
if (!CliDB.AreaTableStorage.ContainsKey(qinfo.QuestSortID))
{
Log.outError(LogFilter.Sql, "Quest {0} has `ZoneOrSort` = {1} (zone case) but zone with this id does not exist.",
qinfo.Id, qinfo.QuestSortID);
// no changes, quest not dependent from this value but can have problems at client
}
}
// client quest log visual (sort case)
if (qinfo.QuestSortID < 0)
{
var qSort = CliDB.QuestSortStorage.LookupByKey((uint)-qinfo.QuestSortID);
if (qSort == null)
{
Log.outError(LogFilter.Sql, "Quest {0} has `ZoneOrSort` = {1} (sort case) but quest sort with this id does not exist.",
qinfo.Id, qinfo.QuestSortID);
// no changes, quest not dependent from this value but can have problems at client (note some may be 0, we must allow this so no check)
}
//check for proper RequiredSkillId value (skill case)
var skillid = SharedConst.SkillByQuestSort(-qinfo.QuestSortID);
if (skillid != SkillType.None)
{
if (qinfo.RequiredSkillId != (uint)skillid)
{
Log.outError(LogFilter.Sql, "Quest {0} has `ZoneOrSort` = {1} but `RequiredSkillId` does not have a corresponding value ({2}).",
qinfo.Id, qinfo.QuestSortID, skillid);
//override, and force proper value here?
}
}
}
// AllowableClasses, can be 0/CLASSMASK_ALL_PLAYABLE to allow any class
if (qinfo.AllowableClasses != 0)
{
if (!Convert.ToBoolean(qinfo.AllowableClasses & (uint)Class.ClassMaskAllPlayable))
{
Log.outError(LogFilter.Sql, "Quest {0} does not contain any playable classes in `RequiredClasses` ({1}), value set to 0 (all classes).", qinfo.Id, qinfo.AllowableClasses);
qinfo.AllowableClasses = 0;
}
}
// AllowableRaces, can be -1/RACEMASK_ALL_PLAYABLE to allow any race
if (qinfo.AllowableRaces != -1)
{
if (!Convert.ToBoolean(qinfo.AllowableRaces & (uint)Race.RaceMaskAllPlayable))
{
Log.outError(LogFilter.Sql, "Quest {0} does not contain any playable races in `RequiredRaces` ({1}), value set to 0 (all races).", qinfo.Id, qinfo.AllowableRaces);
qinfo.AllowableRaces = -1;
}
}
// RequiredSkillId, can be 0
if (qinfo.RequiredSkillId != 0)
{
if (!CliDB.SkillLineStorage.ContainsKey(qinfo.RequiredSkillId))
{
Log.outError(LogFilter.Sql, "Quest {0} has `RequiredSkillId` = {1} but this skill does not exist",
qinfo.Id, qinfo.RequiredSkillId);
}
}
if (qinfo.RequiredSkillPoints != 0)
{
if (qinfo.RequiredSkillPoints > Global.WorldMgr.GetConfigMaxSkillValue())
{
Log.outError(LogFilter.Sql, "Quest {0} has `RequiredSkillPoints` = {1} but max possible skill is {2}, quest can't be done.",
qinfo.Id, qinfo.RequiredSkillPoints, Global.WorldMgr.GetConfigMaxSkillValue());
// no changes, quest can't be done for this requirement
}
}
// else Skill quests can have 0 skill level, this is ok
if (qinfo.RequiredMinRepFaction != 0 && !CliDB.FactionStorage.ContainsKey(qinfo.RequiredMinRepFaction))
{
Log.outError(LogFilter.Sql, "Quest {0} has `RequiredMinRepFaction` = {1} but faction template {2} does not exist, quest can't be done.",
qinfo.Id, qinfo.RequiredMinRepFaction, qinfo.RequiredMinRepFaction);
// no changes, quest can't be done for this requirement
}
if (qinfo.RequiredMaxRepFaction != 0 && !CliDB.FactionStorage.ContainsKey(qinfo.RequiredMaxRepFaction))
{
Log.outError(LogFilter.Sql, "Quest {0} has `RequiredMaxRepFaction` = {1} but faction template {2} does not exist, quest can't be done.",
qinfo.Id, qinfo.RequiredMaxRepFaction, qinfo.RequiredMaxRepFaction);
// no changes, quest can't be done for this requirement
}
if (qinfo.RequiredMinRepValue != 0 && qinfo.RequiredMinRepValue > SharedConst.ReputationCap)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RequiredMinRepValue` = {1} but max reputation is {2}, quest can't be done.",
qinfo.Id, qinfo.RequiredMinRepValue, SharedConst.ReputationCap);
// no changes, quest can't be done for this requirement
}
if (qinfo.RequiredMinRepValue != 0 && qinfo.RequiredMaxRepValue != 0 && qinfo.RequiredMaxRepValue <= qinfo.RequiredMinRepValue)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RequiredMaxRepValue` = {1} and `RequiredMinRepValue` = {2}, quest can't be done.",
qinfo.Id, qinfo.RequiredMaxRepValue, qinfo.RequiredMinRepValue);
// no changes, quest can't be done for this requirement
}
if (qinfo.RequiredMinRepFaction == 0 && qinfo.RequiredMinRepValue != 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RequiredMinRepValue` = {1} but `RequiredMinRepFaction` is 0, value has no effect",
qinfo.Id, qinfo.RequiredMinRepValue);
// warning
}
if (qinfo.RequiredMaxRepFaction == 0 && qinfo.RequiredMaxRepValue != 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RequiredMaxRepValue` = {1} but `RequiredMaxRepFaction` is 0, value has no effect",
qinfo.Id, qinfo.RequiredMaxRepValue);
// warning
}
if (qinfo.RewardTitleId != 0 && !CliDB.CharTitlesStorage.ContainsKey(qinfo.RewardTitleId))
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardTitleId` = {1} but CharTitle Id {1} does not exist, quest can't be rewarded with title.",
qinfo.Id, qinfo.RewardTitleId);
qinfo.RewardTitleId = 0;
// quest can't reward this title
}
if (qinfo.SourceItemId != 0)
{
if (Global.ObjectMgr.GetItemTemplate(qinfo.SourceItemId) == null)
{
Log.outError(LogFilter.Sql, "Quest {0} has `SourceItemId` = {1} but item with entry {2} does not exist, quest can't be done.",
qinfo.Id, qinfo.SourceItemId, qinfo.SourceItemId);
qinfo.SourceItemId = 0; // quest can't be done for this requirement
}
else if (qinfo.SourceItemIdCount == 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `StartItem` = {1} but `ProvidedItemCount` = 0, set to 1 but need fix in DB.",
qinfo.Id, qinfo.SourceItemId);
qinfo.SourceItemIdCount = 1; // update to 1 for allow quest work for backward compatibility with DB
}
}
else if (qinfo.SourceItemIdCount > 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `SourceItemId` = 0 but `SourceItemIdCount` = {1}, useless value.",
qinfo.Id, qinfo.SourceItemIdCount);
qinfo.SourceItemIdCount = 0; // no quest work changes in fact
}
if (qinfo.SourceSpellID != 0)
{
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(qinfo.SourceSpellID);
if (spellInfo == null)
{
Log.outError(LogFilter.Sql, "Quest {0} has `SourceSpellid` = {1} but spell {1} doesn't exist, quest can't be done.",
qinfo.Id, qinfo.SourceSpellID);
qinfo.SourceSpellID = 0; // quest can't be done for this requirement
}
else if (!Global.SpellMgr.IsSpellValid(spellInfo))
{
Log.outError(LogFilter.Sql, "Quest {0} has `SourceSpellid` = {1} but spell {1} is broken, quest can't be done.",
qinfo.Id, qinfo.SourceSpellID);
qinfo.SourceSpellID = 0; // quest can't be done for this requirement
}
}
foreach (QuestObjective obj in qinfo.Objectives)
{
// Store objective for lookup by id
_questObjectives[obj.ID] = obj;
// Check storage index for objectives which store data
if (obj.StorageIndex < 0)
{
switch (obj.Type)
{
case QuestObjectiveType.Monster:
case QuestObjectiveType.Item:
case QuestObjectiveType.GameObject:
case QuestObjectiveType.TalkTo:
case QuestObjectiveType.PlayerKills:
case QuestObjectiveType.AreaTrigger:
case QuestObjectiveType.WinPetBattleAgainstNpc:
case QuestObjectiveType.ObtainCurrency:
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has invalid StorageIndex = {2} for objective type {3}", qinfo.Id, obj.ID, obj.StorageIndex, obj.Type);
break;
default:
break;
}
}
switch (obj.Type)
{
case QuestObjectiveType.Item:
qinfo.SetSpecialFlag(QuestSpecialFlags.Deliver);
if (GetItemTemplate((uint)obj.ObjectID) == null)
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing item entry {2}, quest can't be done.", qinfo.Id, obj.ID, obj.ObjectID);
break;
case QuestObjectiveType.Monster:
qinfo.SetSpecialFlag(QuestSpecialFlags.Kill | QuestSpecialFlags.Cast);
if (GetCreatureTemplate((uint)obj.ObjectID) == null)
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing creature entry {2}, quest can't be done.", qinfo.Id, obj.ID, obj.ObjectID);
break;
case QuestObjectiveType.GameObject:
qinfo.SetSpecialFlag(QuestSpecialFlags.Kill | QuestSpecialFlags.Cast);
if (GetGameObjectTemplate((uint)obj.ObjectID) == null)
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing gameobject entry {2}, quest can't be done.", qinfo.Id, obj.ID, obj.ObjectID);
break;
case QuestObjectiveType.TalkTo:
// Need checks (is it creature only?)
qinfo.SetSpecialFlag(QuestSpecialFlags.Cast | QuestSpecialFlags.Speakto);
break;
case QuestObjectiveType.MinReputation:
case QuestObjectiveType.MaxReputation:
if (!CliDB.FactionStorage.ContainsKey((uint)obj.ObjectID))
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing faction id {2}", qinfo.Id, obj.ID, obj.ObjectID);
break;
case QuestObjectiveType.PlayerKills:
qinfo.SetSpecialFlag(QuestSpecialFlags.Kill);
if (obj.Amount <= 0)
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has invalid player kills count {2}", qinfo.Id, obj.ID, obj.Amount);
break;
case QuestObjectiveType.Currency:
case QuestObjectiveType.HaveCurrency:
case QuestObjectiveType.ObtainCurrency:
if (!CliDB.CurrencyTypesStorage.ContainsKey((uint)obj.ObjectID))
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing currency {2}", qinfo.Id, obj.ID, obj.ObjectID);
if (obj.Amount <= 0)
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has invalid currency amount {2}", qinfo.Id, obj.ID, obj.Amount);
break;
case QuestObjectiveType.LearnSpell:
if (!Global.SpellMgr.HasSpellInfo((uint)obj.ObjectID))
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing spell id {2}", qinfo.Id, obj.ID, obj.ObjectID);
break;
case QuestObjectiveType.WinPetBattleAgainstNpc:
if (obj.ObjectID != 0 && Global.ObjectMgr.GetCreatureTemplate((uint)obj.ObjectID) == null)
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing creature entry {2}, quest can't be done.", qinfo.Id, obj.ID, obj.ObjectID);
break;
case QuestObjectiveType.DefeatBattlePet:
if (!CliDB.BattlePetSpeciesStorage.ContainsKey((uint)obj.ObjectID))
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing battlepet species id {2}", qinfo.Id, obj.ID, obj.ObjectID);
break;
case QuestObjectiveType.CriteriaTree:
if (!CliDB.CriteriaTreeStorage.ContainsKey((uint)obj.ObjectID))
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing criteria tree id {2}", qinfo.Id, obj.ID, obj.ObjectID);
break;
case QuestObjectiveType.AreaTrigger:
if (!CliDB.AreaTriggerStorage.ContainsKey((uint)obj.ObjectID) && obj.ObjectID != -1)
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing areatrigger id {2}", qinfo.Id, obj.ID, obj.ObjectID);
break;
case QuestObjectiveType.Money:
case QuestObjectiveType.WinPvpPetBattles:
break;
default:
Log.outError(LogFilter.Sql, "Quest {0} objective {1} has unhandled type {2}", qinfo.Id, obj.ID, obj.Type);
break;
}
}
for (var j = 0; j < SharedConst.QuestItemDropCount; j++)
{
var id = qinfo.ItemDrop[j];
if (id != 0)
{
if (GetItemTemplate(id) == null)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RequiredSourceItemId{1}` = {2} but item with entry {2} does not exist, quest can't be done.",
qinfo.Id, j + 1, id);
// no changes, quest can't be done for this requirement
}
}
else
{
if (qinfo.ItemDropQuantity[j] > 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RequiredSourceItemId{1}` = 0 but `RequiredSourceItemCount{1}` = {2}.",
qinfo.Id, j + 1, qinfo.ItemDropQuantity[j]);
// no changes, quest ignore this data
}
}
}
for (var j = 0; j < SharedConst.QuestRewardChoicesCount; ++j)
{
var id = qinfo.RewardChoiceItemId[j];
if (id != 0)
{
if (Global.ObjectMgr.GetItemTemplate(id) == null)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardChoiceItemId{1}` = {2} but item with entry {2} does not exist, quest will not reward this item.",
qinfo.Id, j + 1, id);
qinfo.RewardChoiceItemId[j] = 0; // no changes, quest will not reward this
}
if (qinfo.RewardChoiceItemCount[j] == 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardChoiceItemId{1}` = {2} but `RewardChoiceItemCount{1}` = 0, quest can't be done.",
qinfo.Id, j + 1, id);
// no changes, quest can't be done
}
}
else if (qinfo.RewardChoiceItemCount[j] > 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardChoiceItemId{1}` = 0 but `RewardChoiceItemCount{1}` = {3}.",
qinfo.Id, j + 1, qinfo.RewardChoiceItemCount[j]);
// no changes, quest ignore this data
}
}
for (var j = 0; j < SharedConst.QuestRewardItemCount; ++j)
{
var id = qinfo.RewardItemId[j];
if (id != 0)
{
if (Global.ObjectMgr.GetItemTemplate(id) == null)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardItemId{1}` = {2} but item with entry {3} does not exist, quest will not reward this item.",
qinfo.Id, j + 1, id, id);
qinfo.RewardItemId[j] = 0; // no changes, quest will not reward this item
}
if (qinfo.RewardItemCount[j] == 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardItemId{1}` = {2} but `RewardItemIdCount{3}` = 0, quest will not reward this item.",
qinfo.Id, j + 1, id, j + 1);
// no changes
}
}
else if (qinfo.RewardItemCount[j] > 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardItemId{1}` = 0 but `RewardItemIdCount{2}` = {3}.",
qinfo.Id, j + 1, j + 1, qinfo.RewardItemCount[j]);
// no changes, quest ignore this data
}
}
for (var j = 0; j < SharedConst.QuestRewardReputationsCount; ++j)
{
if (qinfo.RewardFactionId[j] != 0)
{
if (Math.Abs(qinfo.RewardFactionValue[j]) > 9)
{
Log.outError(LogFilter.Sql, "Quest {0} has RewardFactionValueId{1} = {2}. That is outside the range of valid values (-9 to 9).", qinfo.Id, j + 1, qinfo.RewardFactionValue[j]);
}
if (!CliDB.FactionStorage.ContainsKey(qinfo.RewardFactionId[j]))
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardFactionId{1}` = {2} but raw faction (faction.dbc) {3} does not exist, quest will not reward reputation for this faction.",
qinfo.Id, j + 1, qinfo.RewardFactionId[j], qinfo.RewardFactionId[j]);
qinfo.RewardFactionId[j] = 0; // quest will not reward this
}
}
else if (qinfo.RewardFactionOverride[j] != 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardFactionId{1}` = 0 but `RewardFactionValueIdOverride{2}` = {3}.",
qinfo.Id, j + 1, j + 1, qinfo.RewardFactionOverride[j]);
// no changes, quest ignore this data
}
}
for (uint i = 0; i < SharedConst.QuestRewardDisplaySpellCount; ++i)
{
if (qinfo.RewardDisplaySpell[i] != 0)
{
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(qinfo.RewardSpell);
if (spellInfo == null)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardSpell` = {1} but spell {2} does not exist, spell removed as display reward.",
qinfo.Id, qinfo.RewardSpell, qinfo.RewardSpell);
qinfo.RewardDisplaySpell[i] = 0; // no spell reward will display for this quest
}
else if (!Global.SpellMgr.IsSpellValid(spellInfo))
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardSpell` = {1} but spell {2} is broken, quest will not have a spell reward.",
qinfo.Id, qinfo.RewardSpell, qinfo.RewardSpell);
qinfo.RewardDisplaySpell[i] = 0; // no spell reward will display for this quest
}
}
}
if (qinfo.RewardSpell > 0)
{
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(qinfo.RewardSpell);
if (spellInfo == null)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardSpellCast` = {1} but spell {2} does not exist, quest will not have a spell reward.",
qinfo.Id, qinfo.RewardSpell, qinfo.RewardSpell);
qinfo.RewardSpell = 0; // no spell will be casted on player
}
else if (!Global.SpellMgr.IsSpellValid(spellInfo))
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardSpellCast` = {1} but spell {2} is broken, quest will not have a spell reward.",
qinfo.Id, qinfo.RewardSpell, qinfo.RewardSpell);
qinfo.RewardSpell = 0; // no spell will be casted on player
}
}
if (qinfo.RewardMailTemplateId != 0)
{
if (!CliDB.MailTemplateStorage.ContainsKey(qinfo.RewardMailTemplateId))
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardMailTemplateId` = {1} but mail template {2} does not exist, quest will not have a mail reward.",
qinfo.Id, qinfo.RewardMailTemplateId, qinfo.RewardMailTemplateId);
qinfo.RewardMailTemplateId = 0; // no mail will send to player
qinfo.RewardMailDelay = 0; // no mail will send to player
qinfo.RewardMailSenderEntry = 0;
}
else if (usedMailTemplates.ContainsKey(qinfo.RewardMailTemplateId))
{
var usedId = usedMailTemplates.LookupByKey(qinfo.RewardMailTemplateId);
Log.outError(LogFilter.Sql, "Quest {0} has `RewardMailTemplateId` = {1} but mail template {2} already used for quest {3}, quest will not have a mail reward.",
qinfo.Id, qinfo.RewardMailTemplateId, qinfo.RewardMailTemplateId, usedId);
qinfo.RewardMailTemplateId = 0; // no mail will send to player
qinfo.RewardMailDelay = 0; // no mail will send to player
qinfo.RewardMailSenderEntry = 0;
}
else
usedMailTemplates[qinfo.RewardMailTemplateId] = qinfo.Id;
}
if (qinfo.NextQuestInChain != 0)
{
var qNext = _questTemplates.LookupByKey(qinfo.NextQuestInChain);
if (qNext == null)
{
Log.outError(LogFilter.Sql, "Quest {0} has `NextQuestIdChain` = {1} but quest {2} does not exist, quest chain will not work.",
qinfo.Id, qinfo.NextQuestInChain, qinfo.NextQuestInChain);
qinfo.NextQuestInChain = 0;
}
else
qNext.prevChainQuests.Add(qinfo.Id);
}
for (var j = 0; j < SharedConst.QuestRewardCurrencyCount; ++j)
{
if (qinfo.RewardCurrencyId[j] != 0)
{
if (qinfo.RewardCurrencyCount[j] == 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardCurrencyId{1}` = {2} but `RewardCurrencyCount{3}` = 0, quest can't be done.",
qinfo.Id, j + 1, qinfo.RewardCurrencyId[j], j + 1);
// no changes, quest can't be done for this requirement
}
if (!CliDB.CurrencyTypesStorage.ContainsKey(qinfo.RewardCurrencyId[j]))
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardCurrencyId{1}` = {2} but currency with entry {3} does not exist, quest can't be done.",
qinfo.Id, j + 1, qinfo.RewardCurrencyId[j], qinfo.RewardCurrencyId[j]);
qinfo.RewardCurrencyCount[j] = 0; // prevent incorrect work of quest
}
}
else if (qinfo.RewardCurrencyCount[j] > 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardCurrencyId{1}` = 0 but `RewardCurrencyCount{2}` = {3}, quest can't be done.",
qinfo.Id, j + 1, j + 1, qinfo.RewardCurrencyCount[j]);
qinfo.RewardCurrencyCount[j] = 0; // prevent incorrect work of quest
}
}
if (qinfo.SoundAccept != 0)
{
if (!CliDB.SoundKitStorage.ContainsKey(qinfo.SoundAccept))
{
Log.outError(LogFilter.Sql, "Quest {0} has `SoundAccept` = {1} but sound {2} does not exist, set to 0.",
qinfo.Id, qinfo.SoundAccept, qinfo.SoundAccept);
qinfo.SoundAccept = 0; // no sound will be played
}
}
if (qinfo.SoundTurnIn != 0)
{
if (!CliDB.SoundKitStorage.ContainsKey(qinfo.SoundTurnIn))
{
Log.outError(LogFilter.Sql, "Quest {0} has `SoundTurnIn` = {1} but sound {2} does not exist, set to 0.",
qinfo.Id, qinfo.SoundTurnIn, qinfo.SoundTurnIn);
qinfo.SoundTurnIn = 0; // no sound will be played
}
}
if (qinfo.RewardSkillId > 0)
{
if (!CliDB.SkillLineStorage.ContainsKey(qinfo.RewardSkillId))
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardSkillId` = {1} but this skill does not exist",
qinfo.Id, qinfo.RewardSkillId);
}
if (qinfo.RewardSkillPoints == 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardSkillId` = {1} but `RewardSkillPoints` is 0",
qinfo.Id, qinfo.RewardSkillId);
}
}
if (qinfo.RewardSkillPoints != 0)
{
if (qinfo.RewardSkillPoints > Global.WorldMgr.GetConfigMaxSkillValue())
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardSkillPoints` = {1} but max possible skill is {2}, quest can't be done.",
qinfo.Id, qinfo.RewardSkillPoints, Global.WorldMgr.GetConfigMaxSkillValue());
// no changes, quest can't be done for this requirement
}
if (qinfo.RewardSkillId == 0)
{
Log.outError(LogFilter.Sql, "Quest {0} has `RewardSkillPoints` = {1} but `RewardSkillId` is 0",
qinfo.Id, qinfo.RewardSkillPoints);
}
}
// fill additional data stores
if (qinfo.PrevQuestId != 0)
{
if (!_questTemplates.ContainsKey((uint)Math.Abs(qinfo.PrevQuestId)))
Log.outError(LogFilter.Sql, "Quest {0} has PrevQuestId {1}, but no such quest", qinfo.Id, qinfo.PrevQuestId);
else
qinfo.prevQuests.Add(qinfo.PrevQuestId);
}
if (qinfo.NextQuestId != 0)
{
var nextquest = _questTemplates.LookupByKey((uint)Math.Abs(qinfo.NextQuestId));
if (nextquest == null)
Log.outError(LogFilter.Sql, "Quest {0} has NextQuestId {1}, but no such quest", qinfo.Id, qinfo.NextQuestId);
else
{
int signedQuestId = qinfo.NextQuestId < 0 ? -(int)qinfo.Id : (int)qinfo.Id;
nextquest.prevQuests.Add(signedQuestId);
}
}
if (qinfo.ExclusiveGroup != 0)
_exclusiveQuestGroups.Add(qinfo.ExclusiveGroup, qinfo.Id);
if (qinfo.LimitTime != 0)
qinfo.SetSpecialFlag(QuestSpecialFlags.Timed);
}
// check QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT for spell with SPELL_EFFECT_QUEST_COMPLETE
foreach (var spellInfo in Global.SpellMgr.GetSpellInfoStorage().Values)
{
foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
{
if (effect == null || effect.Effect != SpellEffectName.QuestComplete)
continue;
uint questId = (uint)effect.MiscValue;
Quest quest = GetQuestTemplate(questId);
// some quest referenced in spells not exist (outdated spells)
if (quest == null)
continue;
if (!quest.HasSpecialFlag(QuestSpecialFlags.ExplorationOrEvent))
{
Log.outError(LogFilter.Sql, "Spell (id: {0}) have SPELL_EFFECT_QUEST_COMPLETE for quest {1}, but quest not have flag QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT. " +
"Quest flags must be fixed, quest modified to enable objective.", spellInfo.Id, questId);
// this will prevent quest completing without objective
quest.SetSpecialFlag(QuestSpecialFlags.ExplorationOrEvent);
}
}
}
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quests definitions in {1} ms", _questTemplates.Count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadQuestStartersAndEnders()
{
Log.outInfo(LogFilter.ServerLoading, "Loading GO Start Quest Data...");
LoadGameobjectQuestStarters();
Log.outInfo(LogFilter.ServerLoading, "Loading GO End Quest Data...");
LoadGameobjectQuestEnders();
Log.outInfo(LogFilter.ServerLoading, "Loading Creature Start Quest Data...");
LoadCreatureQuestStarters();
Log.outInfo(LogFilter.ServerLoading, "Loading Creature End Quest Data...");
LoadCreatureQuestEnders();
}
public void LoadGameobjectQuestStarters()
{
LoadQuestRelationsHelper(_goQuestRelations, null, "gameobject_queststarter", true, true);
foreach (var pair in _goQuestRelations)
{
GameObjectTemplate goInfo = GetGameObjectTemplate(pair.Key);
if (goInfo == null)
Log.outError(LogFilter.Sql, "Table `gameobject_queststarter` have data for not existed gameobject entry ({0}) and existed quest {1}", pair.Key, pair.Value);
else if (goInfo.type != GameObjectTypes.QuestGiver)
Log.outError(LogFilter.Sql, "Table `gameobject_queststarter` have data gameobject entry ({0}) for quest {1}, but GO is not GAMEOBJECT_TYPE_QUESTGIVER", pair.Key, pair.Value);
}
}
public void LoadGameobjectQuestEnders()
{
LoadQuestRelationsHelper(_goQuestInvolvedRelations, _goQuestInvolvedRelationsReverse, "gameobject_questender", false, true);
foreach (var pair in _goQuestInvolvedRelations)
{
GameObjectTemplate goInfo = GetGameObjectTemplate(pair.Key);
if (goInfo == null)
Log.outError(LogFilter.Sql, "Table `gameobject_questender` have data for not existed gameobject entry ({0}) and existed quest {1}", pair.Key, pair.Value);
else if (goInfo.type != GameObjectTypes.QuestGiver)
Log.outError(LogFilter.Sql, "Table `gameobject_questender` have data gameobject entry ({0}) for quest {1}, but GO is not GAMEOBJECT_TYPE_QUESTGIVER", pair.Key, pair.Value);
}
}
public void LoadCreatureQuestStarters()
{
LoadQuestRelationsHelper(_creatureQuestRelations, null, "creature_queststarter", true, false);
foreach (var pair in _creatureQuestRelations)
{
CreatureTemplate cInfo = GetCreatureTemplate(pair.Key);
if (cInfo == null)
Log.outError(LogFilter.Sql, "Table `creature_queststarter` have data for not existed creature entry ({0}) and existed quest {1}", pair.Key, pair.Value);
else if (!Convert.ToBoolean(cInfo.Npcflag & NPCFlags.QuestGiver))
Log.outError(LogFilter.Sql, "Table `creature_queststarter` has creature entry ({0}) for quest {1}, but npcflag does not include UNIT_NPC_FLAG_QUESTGIVER", pair.Key, pair.Value);
}
}
public void LoadCreatureQuestEnders()
{
LoadQuestRelationsHelper(_creatureQuestInvolvedRelations, _creatureQuestInvolvedRelationsReverse, "creature_questender", false, false);
foreach (var pair in _creatureQuestInvolvedRelations)
{
CreatureTemplate cInfo = GetCreatureTemplate(pair.Key);
if (cInfo == null)
Log.outError(LogFilter.Sql, "Table `creature_questender` have data for not existed creature entry ({0}) and existed quest {1}", pair.Key, pair.Value);
else if (!Convert.ToBoolean(cInfo.Npcflag & NPCFlags.QuestGiver))
Log.outError(LogFilter.Sql, "Table `creature_questender` has creature entry ({0}) for quest {1}, but npcflag does not include UNIT_NPC_FLAG_QUESTGIVER", pair.Key, pair.Value);
}
}
void LoadQuestRelationsHelper(MultiMap<uint, uint> map, MultiMap<uint, uint> reverseMap, string table, bool starter, bool go)
{
uint oldMSTime = Time.GetMSTime();
map.Clear(); // need for reload case
uint count = 0;
SQLResult result = DB.World.Query("SELECT id, quest, pool_entry FROM {0} qr LEFT JOIN pool_quest pq ON qr.quest = pq.entry", table);
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 quest relations from `{0}`, table is empty.", table);
return;
}
var poolRelationMap = go ? Global.PoolMgr.mQuestGORelation : Global.PoolMgr.mQuestCreatureRelation;
if (starter)
poolRelationMap.Clear();
do
{
uint id = result.Read<uint>(0);
uint quest = result.Read<uint>(1);
uint poolId = result.Read<uint>(2);
if (!_questTemplates.ContainsKey(quest))
{
Log.outError(LogFilter.Sql, "Table `{0}`: Quest {1} listed for entry {2} does not exist.", table, quest, id);
continue;
}
if (poolId == 0 || !starter)
{
map.Add(id, quest);
if (reverseMap != null)
reverseMap.Add(quest, id);
}
else if (starter)
poolRelationMap.Add(quest, id);
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quest relations from {1} in {2} ms", count, table, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadQuestPOI()
{
uint oldMSTime = Time.GetMSTime();
_questPOIStorage.Clear(); // need for reload case
uint count = 0;
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13
SQLResult result = DB.World.Query("SELECT QuestID, BlobIndex, Idx1, ObjectiveIndex, QuestObjectiveID, QuestObjectID, MapID, WorldMapAreaId, Floor, Priority, Flags, WorldEffectID, PlayerConditionID, WoDUnk1 FROM quest_poi order by QuestID, Idx1");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 quest POI definitions. DB table `quest_poi` is empty.");
return;
}
// 0 1 2 3
SQLResult points = DB.World.Query("SELECT QuestID, Idx1, X, Y FROM quest_poi_points ORDER BY QuestID DESC, Idx1, Idx2");
Dictionary<uint, MultiMap<int, QuestPOIPoint>> POIs = new Dictionary<uint, MultiMap<int, QuestPOIPoint>>();
if (!points.IsEmpty())
{
do
{
uint questId = points.Read<uint>(0);
int Idx1 = points.Read<int>(1);
int x = points.Read<int>(2);
int y = points.Read<int>(3);
if (!POIs.ContainsKey(questId))
POIs[questId] = new MultiMap<int, QuestPOIPoint>();
QuestPOIPoint point = new QuestPOIPoint(x, y);
POIs[questId].Add(Idx1, point);
} while (points.NextRow());
}
do
{
uint QuestID = Convert.ToUInt32(result.Read<int>(0));
int BlobIndex = result.Read<int>(1);
int Idx1 = result.Read<int>(2);
int ObjectiveIndex = result.Read<int>(3);
int QuestObjectiveID = result.Read<int>(4);
int QuestObjectID = result.Read<int>(5);
int MapID = result.Read<int>(6);
int WorldMapAreaId = result.Read<int>(7);
int Floor = result.Read<int>(8);
int Priority = result.Read<int>(9);
int Flags = result.Read<int>(10);
int WorldEffectID = result.Read<int>(11);
int PlayerConditionID = result.Read<int>(12);
int WoDUnk1 = result.Read<int>(13);
if (Global.ObjectMgr.GetQuestTemplate(QuestID) == null)
Log.outError(LogFilter.Sql, "`quest_poi` quest id ({0}) Idx1 ({1}) does not exist in `quest_template`", QuestID, Idx1);
QuestPOI POI = new QuestPOI(BlobIndex, ObjectiveIndex, QuestObjectiveID, QuestObjectID, MapID, WorldMapAreaId, Floor, Priority, Flags, WorldEffectID, PlayerConditionID, WoDUnk1);
if (!POIs.ContainsKey(QuestID) || !POIs[QuestID].ContainsKey(Idx1))
{
Log.outError(LogFilter.Sql, "Table quest_poi references unknown quest points for quest {0} POI id {1}", QuestID, BlobIndex);
continue;
}
POI.points = POIs[QuestID][Idx1];
_questPOIStorage.Add(QuestID, POI);
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quest POI definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadQuestAreaTriggers()
{
uint oldMSTime = Time.GetMSTime();
_questAreaTriggerStorage.Clear(); // need for reload case
SQLResult result = DB.World.Query("SELECT id, quest FROM areatrigger_involvedrelation");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 quest trigger points. DB table `areatrigger_involvedrelation` is empty.");
return;
}
uint count = 0;
do
{
++count;
uint trigger_ID = result.Read<uint>(0);
uint quest_ID = result.Read<uint>(1);
AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(trigger_ID);
if (atEntry == null)
{
Log.outError(LogFilter.Sql, "Area trigger (ID:{0}) does not exist in `AreaTrigger.dbc`.", trigger_ID);
continue;
}
Quest quest = GetQuestTemplate(quest_ID);
if (quest == null)
{
Log.outError(LogFilter.Sql, "Table `areatrigger_involvedrelation` has record (id: {0}) for not existing quest {1}", trigger_ID, quest_ID);
continue;
}
if (!quest.HasSpecialFlag(QuestSpecialFlags.ExplorationOrEvent))
{
Log.outError(LogFilter.Sql, "Table `areatrigger_involvedrelation` has record (id: {0}) for not quest {1}, but quest not have flag QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT. Trigger or quest flags must be fixed, quest modified to require objective.", trigger_ID, quest_ID);
// this will prevent quest completing without objective
quest.SetSpecialFlag(QuestSpecialFlags.ExplorationOrEvent);
// continue; - quest modified to required objective and trigger can be allowed.
}
_questAreaTriggerStorage.Add(trigger_ID, quest_ID);
} while (result.NextRow());
foreach (var pair in _questObjectives)
{
QuestObjective objective = pair.Value;
if (objective.Type == QuestObjectiveType.AreaTrigger)
_questAreaTriggerStorage.Add((uint)objective.ObjectID, objective.QuestID);
}
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quest trigger points in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadQuestGreetings()
{
uint oldMSTime = Time.GetMSTime();
_questGreetingStorage.Clear(); // need for reload case
// 0 1 2 3
SQLResult result = DB.World.Query("SELECT ID, type, GreetEmoteType, GreetEmoteDelay, Greeting FROM quest_greeting");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 npc texts, table is empty!");
return;
}
do
{
uint id = result.Read<uint>(0);
byte type = result.Read<byte>(1);
// overwrite
switch (type)
{
case 0: // Creature
type = (byte)TypeId.Unit;
if (Global.ObjectMgr.GetCreatureTemplate(id) == null)
{
Log.outError(LogFilter.Sql, "Table `quest_greeting`: creature template entry {0} does not exist.", id);
continue;
}
break;
case 1: // GameObject
type = (byte)TypeId.GameObject;
if (Global.ObjectMgr.GetGameObjectTemplate(id) == null)
{
Log.outError(LogFilter.Sql, "Table `quest_greeting`: gameobject template entry {0} does not exist.", id);
continue;
}
break;
default:
continue;
}
ushort greetEmoteType = result.Read<ushort>(2);
uint greetEmoteDelay = result.Read<uint>(3);
string greeting = result.Read<string>(4);
if (!_questGreetingStorage.ContainsKey(type))
_questGreetingStorage[type] = new Dictionary<uint, QuestGreeting>();
_questGreetingStorage[type][id] = new QuestGreeting(greetEmoteType, greetEmoteDelay, greeting);
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quest_greeting in {1} ms", _questGreetingStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public Quest GetQuestTemplate(uint questId)
{
return _questTemplates.LookupByKey(questId);
}
public Dictionary<uint, Quest> GetQuestTemplates()
{
return _questTemplates;
}
public Dictionary<uint, Quest> GetQuestStorage()
{
return _questTemplates;
}
public MultiMap<uint, uint> GetGOQuestRelationMap()
{
return _goQuestRelations;
}
public List<uint> GetGOQuestRelationBounds(uint go_entry)
{
return _goQuestRelations.LookupByKey(go_entry);
}
public List<uint> GetGOQuestInvolvedRelationBounds(uint go_entry)
{
return _goQuestInvolvedRelations.LookupByKey(go_entry);
}
public List<uint> GetGOQuestInvolvedRelationReverseBounds(uint questId)
{
return _goQuestInvolvedRelationsReverse.LookupByKey(questId);
}
public MultiMap<uint, uint> GetCreatureQuestRelationMap()
{
return _creatureQuestRelations;
}
public List<uint> GetCreatureQuestRelationBounds(uint creature_entry)
{
return _creatureQuestRelations.LookupByKey(creature_entry);
}
public List<uint> GetCreatureQuestInvolvedRelationBounds(uint creature_entry)
{
return _creatureQuestInvolvedRelations.LookupByKey(creature_entry);
}
public List<uint> GetCreatureQuestInvolvedRelationReverseBounds(uint questId)
{
return _creatureQuestInvolvedRelationsReverse.LookupByKey(questId);
}
public List<QuestPOI> GetQuestPOIList(uint questId)
{
return _questPOIStorage.LookupByKey(questId);
}
public QuestObjective GetQuestObjective(uint questObjectiveId)
{
return _questObjectives.LookupByKey(questObjectiveId);
}
public List<uint> GetQuestsForAreaTrigger(uint triggerId)
{
return _questAreaTriggerStorage.LookupByKey(triggerId);
}
public QuestGreeting GetQuestGreeting(ObjectGuid guid)
{
var dic = _questGreetingStorage.LookupByKey(guid.GetTypeId());
if (dic.Empty())
return null;
var greeting = dic.LookupByKey(guid.GetEntry());
if (greeting == null)
return null;
return greeting;
}
//Spells /Skills / Phases
public void LoadTerrainSwapDefaults()
{
_terrainMapDefaultStore.Clear();
uint oldMSTime = Time.GetMSTime();
SQLResult result = DB.World.Query("SELECT MapId, TerrainSwapMap FROM `terrain_swap_defaults`");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 terrain swap defaults. DB table `terrain_swap_defaults` is empty.");
return;
}
uint count = 0;
do
{
uint mapId = result.Read<uint>(0);
if (!CliDB.MapStorage.ContainsKey(mapId))
{
Log.outError(LogFilter.Sql, "Map {0} defined in `terrain_swap_defaults` does not exist, skipped.", mapId);
continue;
}
uint terrainSwap = result.Read<uint>(1);
if (!CliDB.MapStorage.ContainsKey(terrainSwap))
{
Log.outError(LogFilter.Sql, "TerrainSwapMap {0} defined in `terrain_swap_defaults` does not exist, skipped.", terrainSwap);
continue;
}
_terrainMapDefaultStore.Add(mapId, terrainSwap);
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} terrain swap defaults in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadTerrainPhaseInfo()
{
_terrainPhaseInfoStore.Clear();
uint oldMSTime = Time.GetMSTime();
SQLResult result = DB.World.Query("SELECT Id, TerrainSwapMap FROM `terrain_phase_info`");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 terrain phase infos. DB table `terrain_phase_info` is empty.");
return;
}
uint count = 0;
do
{
uint phaseId = result.Read<uint>(0);
if (!CliDB.PhaseStorage.ContainsKey(phaseId))
{
Log.outError(LogFilter.Sql, "Phase {0} defined in `terrain_phase_info` does not exist, skipped.", phaseId);
continue;
}
uint terrainSwap = result.Read<uint>(1);
_terrainPhaseInfoStore.Add(phaseId, terrainSwap);
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} phase infos in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadTerrainWorldMaps()
{
_terrainWorldMapStore.Clear();
uint oldMSTime = Time.GetMSTime();
// 0 1
SQLResult result = DB.World.Query("SELECT TerrainSwapMap, WorldMapArea FROM `terrain_worldmap`");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 terrain world maps. DB table `terrain_worldmap` is empty.");
return;
}
uint count = 0;
do
{
uint mapId = result.Read<uint>(0);
if (!CliDB.MapStorage.ContainsKey(mapId))
{
Log.outError(LogFilter.Sql, "TerrainSwapMap {0} defined in `terrain_worldmap` does not exist, skipped.", mapId);
continue;
}
uint worldMapArea = result.Read<uint>(1);
_terrainWorldMapStore.Add(mapId, worldMapArea);
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} terrain world maps in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadAreaPhases()
{
_phases.Clear();
uint oldMSTime = Time.GetMSTime();
// 0 1
SQLResult result = DB.World.Query("SELECT AreaId, PhaseId FROM `phase_area`");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 phase areas. DB table `phase_area` is empty.");
return;
}
uint count = 0;
do
{
PhaseInfoStruct phase = new PhaseInfoStruct();
uint area = result.Read<uint>(0);
phase.Id = result.Read<uint>(1);
_phases.Add(area, phase);
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} phase areas in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadNPCSpellClickSpells()
{
uint oldMSTime = Time.GetMSTime();
_spellClickInfoStorage.Clear();
// 0 1 2 3
SQLResult result = DB.World.Query("SELECT npc_entry, spell_id, cast_flags, user_type FROM npc_spellclick_spells");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 spellclick spells. DB table `npc_spellclick_spells` is empty.");
return;
}
uint count = 0;
do
{
uint npc_entry = result.Read<uint>(0);
CreatureTemplate cInfo = GetCreatureTemplate(npc_entry);
if (cInfo == null)
{
Log.outError(LogFilter.Sql, "Table npc_spellclick_spells references unknown creature_template {0}. Skipping entry.", npc_entry);
continue;
}
uint spellid = result.Read<uint>(1);
SpellInfo spellinfo = Global.SpellMgr.GetSpellInfo(spellid);
if (spellinfo == null)
{
Log.outError(LogFilter.Sql, "Table npc_spellclick_spells creature: {0} references unknown spellid {1}. Skipping entry.", npc_entry, spellid);
continue;
}
SpellClickUserTypes userType = (SpellClickUserTypes)result.Read<byte>(3);
if (userType >= SpellClickUserTypes.Max)
Log.outError(LogFilter.Sql, "Table npc_spellclick_spells creature: {0} references unknown user type {1}. Skipping entry.", npc_entry, userType);
byte castFlags = result.Read<byte>(2);
SpellClickInfo info = new SpellClickInfo();
info.spellId = spellid;
info.castFlags = castFlags;
info.userType = userType;
_spellClickInfoStorage.Add(npc_entry, info);
++count;
}
while (result.NextRow());
// all spellclick data loaded, now we check if there are creatures with NPC_FLAG_SPELLCLICK but with no data
// NOTE: It *CAN* be the other way around: no spellclick flag but with spellclick data, in case of creature-only vehicle accessories
var ctc = GetCreatureTemplates();
foreach (var creature in ctc.Values)
{
if (creature.Npcflag.HasAnyFlag(NPCFlags.SpellClick) && !_spellClickInfoStorage.ContainsKey(creature.Entry))
{
Log.outError(LogFilter.Sql, "npc_spellclick_spells: Creature template {0} has UNIT_NPC_FLAG_SPELLCLICK but no data in spellclick table! Removing flag", creature.Entry);
creature.Npcflag &= ~NPCFlags.SpellClick;
}
}
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spellclick definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadFishingBaseSkillLevel()
{
uint oldMSTime = Time.GetMSTime();
_fishingBaseForAreaStorage.Clear(); // for reload case
SQLResult result = DB.World.Query("SELECT entry, skill FROM skill_fishing_base_level");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 areas for fishing base skill level. DB table `skill_fishing_base_level` is empty.");
return;
}
uint count = 0;
do
{
uint entry = result.Read<uint>(0);
int skill = result.Read<int>(1);
AreaTableRecord fArea = CliDB.AreaTableStorage.LookupByKey(entry);
if (fArea == null)
{
Log.outError(LogFilter.Sql, "AreaId {0} defined in `skill_fishing_base_level` does not exist", entry);
continue;
}
_fishingBaseForAreaStorage[entry] = skill;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} areas for fishing base skill level in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadSkillTiers()
{
uint oldMSTime = Time.GetMSTime();
_skillTiers.Clear();
SQLResult result = DB.World.Query("SELECT ID, Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8, Value9, Value10, " +
" Value11, Value12, Value13, Value14, Value15, Value16 FROM skill_tiers");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 skill max values. DB table `skill_tiers` is empty.");
return;
}
do
{
uint id = result.Read<uint>(0);
SkillTiersEntry tier = new SkillTiersEntry();
for (int i = 0; i < SkillConst.MaxSkillStep; ++i)
tier.Value[i] = result.Read<uint>(1 + i);
_skillTiers[id] = tier;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} skill max values in {1} ms", _skillTiers.Count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public List<uint> GetPhaseTerrainSwaps(uint phaseid) { return _terrainPhaseInfoStore[phaseid]; }
public List<uint> GetDefaultTerrainSwaps(uint mapid) { return _terrainMapDefaultStore[mapid]; }
public List<uint> GetTerrainWorldMaps(uint terrainId) { return _terrainWorldMapStore[terrainId]; }
public MultiMap<uint, uint> GetDefaultTerrainSwapStore() { return _terrainMapDefaultStore; }
public List<PhaseInfoStruct> GetPhasesForArea(uint area) { return _phases[area]; }
public MultiMap<uint, PhaseInfoStruct> GetAreaAndZonePhases() { return _phases; }
public List<PhaseInfoStruct> GetPhasesForAreaOrZoneForLoading(uint areaOrZone)
{
return _phases.LookupByKey(areaOrZone);
}
public List<SpellClickInfo> GetSpellClickInfoMapBounds(uint creature_id)
{
return _spellClickInfoStorage.LookupByKey(creature_id);
}
public int GetFishingBaseSkillLevel(uint entry)
{
return _fishingBaseForAreaStorage.LookupByKey(entry);
}
public SkillTiersEntry GetSkillTier(uint skillTierId)
{
return _skillTiers.LookupByKey(skillTierId);
}
//Locales
public void LoadCreatureLocales()
{
uint oldMSTime = Time.GetMSTime();
_creatureLocaleStorage.Clear(); // need for reload case
// 0 1 2 3 4 5
SQLResult result = DB.World.Query("SELECT entry, locale, Name, NameAlt, Title, TitleAlt FROM creature_template_locale");
if (result.IsEmpty())
return;
do
{
uint id = result.Read<uint>(0);
string localeName = result.Read<string>(1);
string name = result.Read<string>(2);
string nameAlt = result.Read<string>(3);
string title = result.Read<string>(4);
string titleAlt = result.Read<string>(5);
if (!_creatureLocaleStorage.ContainsKey(id))
_creatureLocaleStorage[id] = new CreatureLocale();
LocaleConstant locale = localeName.ToEnum<LocaleConstant>();
if (locale == LocaleConstant.enUS)
continue;
CreatureLocale data = _creatureLocaleStorage[id];
AddLocaleString(name, locale, data.Name);
AddLocaleString(nameAlt, locale, data.NameAlt);
AddLocaleString(title, locale, data.Title);
AddLocaleString(titleAlt, locale, data.TitleAlt);
} while (result.NextRow());
}
public void LoadGameObjectLocales()
{
uint oldMSTime = Time.GetMSTime();
_gameObjectLocaleStorage.Clear(); // need for reload case
// 0 1 2 3 4
SQLResult result = DB.World.Query("SELECT entry, locale, name, castBarCaption, unk1 FROM gameobject_template_locale");
if (result.IsEmpty())
return;
do
{
uint id = result.Read<uint>(0);
string localeName = result.Read<string>(1);
string name = result.Read<string>(2);
string castBarCaption = result.Read<string>(3);
string unk1 = result.Read<string>(4);
if (!_gameObjectLocaleStorage.ContainsKey(id))
_gameObjectLocaleStorage[id] = new GameObjectLocale();
GameObjectLocale data = _gameObjectLocaleStorage[id];
LocaleConstant locale = localeName.ToEnum<LocaleConstant>();
if (locale == LocaleConstant.enUS)
continue;
AddLocaleString(name, locale, data.Name);
AddLocaleString(castBarCaption, locale, data.CastBarCaption);
AddLocaleString(unk1, locale, data.Unk1);
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobject_template_locale locale strings in {1} ms", _gameObjectLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadQuestTemplateLocale()
{
uint oldMSTime = Time.GetMSTime();
_questObjectivesLocaleStorage.Clear(); // need for reload case
// 0 1 2 3 4 5 6 7 8 9 10
SQLResult result = DB.World.Query("SELECT Id, locale, LogTitle, LogDescription, QuestDescription, AreaDescription, PortraitGiverText, PortraitGiverName, PortraitTurnInText, PortraitTurnInName, QuestCompletionLog" +
" FROM quest_template_locale");
if (result.IsEmpty())
return;
do
{
uint id = result.Read<uint>(0);
string localeName = result.Read<string>(1);
string logTitle = result.Read<string>(2);
string logDescription = result.Read<string>(3);
string questDescription = result.Read<string>(4);
string areaDescription = result.Read<string>(5);
string portraitGiverText = result.Read<string>(6);
string portraitGiverName = result.Read<string>(7);
string portraitTurnInText = result.Read<string>(8);
string portraitTurnInName = result.Read<string>(9);
string questCompletionLog = result.Read<string>(10);
if (!_questTemplateLocaleStorage.ContainsKey(id))
_questTemplateLocaleStorage[id] = new QuestTemplateLocale();
QuestTemplateLocale data = _questTemplateLocaleStorage[id];
LocaleConstant locale = localeName.ToEnum<LocaleConstant>();
if (locale == LocaleConstant.enUS)
continue;
AddLocaleString(logTitle, locale, data.LogTitle);
AddLocaleString(logDescription, locale, data.LogDescription);
AddLocaleString(questDescription, locale, data.QuestDescription);
AddLocaleString(areaDescription, locale, data.AreaDescription);
AddLocaleString(portraitGiverText, locale, data.PortraitGiverText);
AddLocaleString(portraitGiverName, locale, data.PortraitGiverName);
AddLocaleString(portraitTurnInText, locale, data.PortraitTurnInText);
AddLocaleString(portraitTurnInName, locale, data.PortraitTurnInName);
AddLocaleString(questCompletionLog, locale, data.QuestCompletionLog);
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Quest Tempalate locale strings in {1} ms", _questTemplateLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadQuestObjectivesLocale()
{
uint oldMSTime = Time.GetMSTime();
_questObjectivesLocaleStorage.Clear(); // need for reload case
// 0 1 2
SQLResult result = DB.World.Query("SELECT Id, locale, Description FROM quest_objectives_locale");
if (result.IsEmpty())
return;
do
{
uint id = result.Read<uint>(0);
string localeName = result.Read<string>(1);
string Description = result.Read<string>(2);
if (!_questObjectivesLocaleStorage.ContainsKey(id))
_questObjectivesLocaleStorage[id] = new QuestObjectivesLocale();
QuestObjectivesLocale data = _questObjectivesLocaleStorage[id];
LocaleConstant locale = localeName.ToEnum<LocaleConstant>();
if (locale == LocaleConstant.enUS)
continue;
AddLocaleString(Description, locale, data.Description);
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Quest Objectives locale strings in {1} ms", _questObjectivesLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadQuestOfferRewardLocale()
{
uint oldMSTime = Time.GetMSTime();
_questOfferRewardLocaleStorage.Clear(); // need for reload case
// 0 1 2
SQLResult result = DB.World.Query("SELECT Id, locale, RewardText FROM quest_offer_reward_locale");
if (result.IsEmpty())
return;
do
{
uint id = result.Read<uint>(0);
string localeName = result.Read<string>(1);
string RewardText = result.Read<string>(2);
if (!_questOfferRewardLocaleStorage.ContainsKey(id))
_questOfferRewardLocaleStorage[id] = new QuestOfferRewardLocale();
QuestOfferRewardLocale data = _questOfferRewardLocaleStorage[id];
LocaleConstant locale = localeName.ToEnum<LocaleConstant>();
if (locale == LocaleConstant.enUS)
continue;
AddLocaleString(RewardText, locale, data.RewardText);
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Quest Offer Reward locale strings in {1} ms", _questOfferRewardLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadQuestRequestItemsLocale()
{
uint oldMSTime = Time.GetMSTime();
_questRequestItemsLocaleStorage.Clear(); // need for reload case
// 0 1 2
SQLResult result = DB.World.Query("SELECT Id, locale, CompletionText FROM quest_request_items_locale");
if (result.IsEmpty())
return;
do
{
uint id = result.Read<uint>(0);
string localeName = result.Read<string>(1);
string completionText = result.Read<string>(2);
if (!_questRequestItemsLocaleStorage.ContainsKey(id))
_questRequestItemsLocaleStorage[id] = new QuestRequestItemsLocale();
QuestRequestItemsLocale data = _questRequestItemsLocaleStorage[id];
LocaleConstant locale = localeName.ToEnum<LocaleConstant>();
if (locale == LocaleConstant.enUS)
continue;
AddLocaleString(completionText, locale, data.CompletionText);
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Quest Request Items locale strings in {1} ms", _questRequestItemsLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadGossipMenuItemsLocales()
{
uint oldMSTime = Time.GetMSTime();
_gossipMenuItemsLocaleStorage.Clear(); // need for reload case
SQLResult result = DB.World.Query("SELECT menu_id, id, option_text_loc1, box_text_loc1, option_text_loc2, box_text_loc2, " +
"option_text_loc3, box_text_loc3, option_text_loc4, box_text_loc4, option_text_loc5, box_text_loc5, option_text_loc6, box_text_loc6, " +
"option_text_loc7, box_text_loc7, option_text_loc8, box_text_loc8 FROM locales_gossip_menu_option");
if (result.IsEmpty())
return;
do
{
uint menuId = result.Read<ushort>(0);
uint id = result.Read<ushort>(1);
GossipMenuItemsLocale data = new GossipMenuItemsLocale();
for (byte i = 1; i < (int)LocaleConstant.OldTotal; ++i)
{
LocaleConstant locale = (LocaleConstant)i;
AddLocaleString(result.Read<string>(2 + 2 * (i - 1)), locale, data.OptionText);
AddLocaleString(result.Read<string>(2 + 2 * (i - 1) + 1), locale, data.BoxText);
}
_gossipMenuItemsLocaleStorage[MathFunctions.MakePair32(menuId, id)] = data;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gossip_menu_option locale strings in {1} ms", _gossipMenuItemsLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadPageTextLocales()
{
uint oldMSTime = Time.GetMSTime();
_pageTextLocaleStorage.Clear(); // needed for reload case
// 0 1 2
SQLResult result = DB.World.Query("SELECT ID, locale, Text FROM page_text_locale");
if (result.IsEmpty())
return;
do
{
uint id = result.Read<uint>(0);
string localeName = result.Read<string>(1);
string text = result.Read<string>(2);
if (!_pageTextLocaleStorage.ContainsKey(id))
_pageTextLocaleStorage[id] = new PageTextLocale();
PageTextLocale data = _pageTextLocaleStorage[id];
LocaleConstant locale = localeName.ToEnum<LocaleConstant>();
if (locale == LocaleConstant.enUS)
continue;
AddLocaleString(text, locale, data.Text);
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} PageText locale strings in {1} ms", _pageTextLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadPointOfInterestLocales()
{
uint oldMSTime = Time.GetMSTime();
_pointOfInterestLocaleStorage.Clear(); // need for reload case
// 0 1 2
SQLResult result = DB.World.Query("SELECT ID, locale, Name FROM points_of_interest_locale");
if (result.IsEmpty())
return;
do
{
uint id = result.Read<uint>(0);
string localeName = result.Read<string>(1);
string name = result.Read<string>(2);
if (!_pointOfInterestLocaleStorage.ContainsKey(id))
_pointOfInterestLocaleStorage[id] = new PointOfInterestLocale();
PointOfInterestLocale data = _pointOfInterestLocaleStorage[id];
LocaleConstant locale = localeName.ToEnum<LocaleConstant>();
if (locale == LocaleConstant.enUS)
continue;
AddLocaleString(name, locale, data.Name);
}
while (result.NextRow());
}
public CreatureLocale GetCreatureLocale(uint entry)
{
return _creatureLocaleStorage.LookupByKey(entry);
}
public GameObjectLocale GetGameObjectLocale(uint entry)
{
return _gameObjectLocaleStorage.LookupByKey(entry);
}
public QuestTemplateLocale GetQuestLocale(uint entry)
{
return _questTemplateLocaleStorage.LookupByKey(entry);
}
public QuestOfferRewardLocale GetQuestOfferRewardLocale(uint entry)
{
return _questOfferRewardLocaleStorage.LookupByKey(entry);
}
public QuestRequestItemsLocale GetQuestRequestItemsLocale(uint entry)
{
return _questRequestItemsLocaleStorage.LookupByKey(entry);
}
public QuestObjectivesLocale GetQuestObjectivesLocale(uint entry)
{
return _questObjectivesLocaleStorage.LookupByKey(entry);
}
public GossipMenuItemsLocale GetGossipMenuItemsLocale(uint menuId, uint menuItemId)
{
return _gossipMenuItemsLocaleStorage.LookupByKey(MathFunctions.MakePair32(menuId, menuItemId));
}
public PageTextLocale GetPageTextLocale(uint entry)
{
return _pageTextLocaleStorage.LookupByKey(entry);
}
public PointOfInterestLocale GetPointOfInterestLocale(uint id)
{
return _pointOfInterestLocaleStorage.LookupByKey(id);
}
//General
public void LoadReputationRewardRate()
{
uint oldMSTime = Time.GetMSTime();
_repRewardRateStorage.Clear(); // for reload case
// 0 1 2 3 4 5 6 7
SQLResult result = DB.World.Query("SELECT faction, quest_rate, quest_daily_rate, quest_weekly_rate, quest_monthly_rate, quest_repeatable_rate, creature_rate, spell_rate FROM reputation_reward_rate");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded `reputation_reward_rate`, table is empty!");
return;
}
uint count = 0;
do
{
uint factionId = result.Read<uint>(0);
RepRewardRate repRate = new RepRewardRate();
repRate.questRate = result.Read<float>(1);
repRate.questDailyRate = result.Read<float>(2);
repRate.questWeeklyRate = result.Read<float>(3);
repRate.questMonthlyRate = result.Read<float>(4);
repRate.questRepeatableRate = result.Read<float>(5);
repRate.creatureRate = result.Read<float>(6);
repRate.spellRate = result.Read<float>(7);
FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(factionId);
if (factionEntry == null)
{
Log.outError(LogFilter.Sql, "Faction (faction.dbc) {0} does not exist but is used in `reputation_reward_rate`", factionId);
continue;
}
if (repRate.questRate < 0.0f)
{
Log.outError(LogFilter.Sql, "Table reputation_reward_rate has quest_rate with invalid rate {0}, skipping data for faction {1}", repRate.questRate, factionId);
continue;
}
if (repRate.questDailyRate < 0.0f)
{
Log.outError(LogFilter.Sql, "Table reputation_reward_rate has quest_daily_rate with invalid rate {0}, skipping data for faction {1}", repRate.questDailyRate, factionId);
continue;
}
if (repRate.questWeeklyRate < 0.0f)
{
Log.outError(LogFilter.Sql, "Table reputation_reward_rate has quest_weekly_rate with invalid rate {0}, skipping data for faction {1}", repRate.questWeeklyRate, factionId);
continue;
}
if (repRate.questMonthlyRate < 0.0f)
{
Log.outError(LogFilter.Sql, "Table reputation_reward_rate has quest_monthly_rate with invalid rate {0}, skipping data for faction {1}", repRate.questMonthlyRate, factionId);
continue;
}
if (repRate.questRepeatableRate < 0.0f)
{
Log.outError(LogFilter.Sql, "Table reputation_reward_rate has quest_repeatable_rate with invalid rate {0}, skipping data for faction {1}", repRate.questRepeatableRate, factionId);
continue;
}
if (repRate.creatureRate < 0.0f)
{
Log.outError(LogFilter.Sql, "Table reputation_reward_rate has creature_rate with invalid rate {0}, skipping data for faction {1}", repRate.creatureRate, factionId);
continue;
}
if (repRate.spellRate < 0.0f)
{
Log.outError(LogFilter.Sql, "Table reputation_reward_rate has spell_rate with invalid rate {0}, skipping data for faction {1}", repRate.spellRate, factionId);
continue;
}
_repRewardRateStorage[factionId] = repRate;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} reputation_reward_rate in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadReputationOnKill()
{
uint oldMSTime = Time.GetMSTime();
// For reload case
_repOnKillStorage.Clear();
// 0 1 2
SQLResult result = DB.World.Query("SELECT creature_id, RewOnKillRepFaction1, RewOnKillRepFaction2, " +
// 3 4 5 6 7 8 9
"IsTeamAward1, MaxStanding1, RewOnKillRepValue1, IsTeamAward2, MaxStanding2, RewOnKillRepValue2, TeamDependent " +
"FROM creature_onkill_reputation");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "oaded 0 creature award reputation definitions. DB table `creature_onkill_reputation` is empty.");
return;
}
uint count = 0;
do
{
uint creature_id = result.Read<uint>(0);
ReputationOnKillEntry repOnKill = new ReputationOnKillEntry();
repOnKill.RepFaction1 = result.Read<ushort>(1);
repOnKill.RepFaction2 = result.Read<ushort>(2);
repOnKill.IsTeamAward1 = result.Read<bool>(3);
repOnKill.ReputationMaxCap1 = result.Read<byte>(4);
repOnKill.RepValue1 = result.Read<int>(5);
repOnKill.IsTeamAward2 = result.Read<bool>(6);
repOnKill.ReputationMaxCap2 = result.Read<byte>(7);
repOnKill.RepValue2 = result.Read<int>(8);
repOnKill.TeamDependent = result.Read<bool>(9);
if (GetCreatureTemplate(creature_id) == null)
{
Log.outError(LogFilter.Sql, "Table `creature_onkill_reputation` have data for not existed creature entry ({0}), skipped", creature_id);
continue;
}
if (repOnKill.RepFaction1 != 0)
{
FactionRecord factionEntry1 = CliDB.FactionStorage.LookupByKey(repOnKill.RepFaction1);
if (factionEntry1 == null)
{
Log.outError(LogFilter.Sql, "Faction (faction.dbc) {0} does not exist but is used in `creature_onkill_reputation`", repOnKill.RepFaction1);
continue;
}
}
if (repOnKill.RepFaction2 != 0)
{
FactionRecord factionEntry2 = CliDB.FactionStorage.LookupByKey(repOnKill.RepFaction2);
if (factionEntry2 == null)
{
Log.outError(LogFilter.Sql, "Faction (faction.dbc) {0} does not exist but is used in `creature_onkill_reputation`", repOnKill.RepFaction2);
continue;
}
}
_repOnKillStorage[creature_id] = repOnKill;
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature award reputation definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadReputationSpilloverTemplate()
{
var oldMSTime = Time.GetMSTime();
_repSpilloverTemplateStorage.Clear(); // for reload case
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
SQLResult result = DB.World.Query("SELECT faction, faction1, rate_1, rank_1, faction2, rate_2, rank_2, faction3, rate_3, rank_3, faction4, rate_4, rank_4, faction5, rate_5, rank_5 FROM " +
"reputation_spillover_template");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded `reputation_spillover_template`, table is empty.");
return;
}
uint count = 0;
do
{
uint factionId = result.Read<uint>(0);
RepSpilloverTemplate repTemplate = new RepSpilloverTemplate();
repTemplate.faction[0] = result.Read<uint>(1);
repTemplate.faction_rate[0] = result.Read<float>(2);
repTemplate.faction_rank[0] = result.Read<uint>(3);
repTemplate.faction[1] = result.Read<uint>(4);
repTemplate.faction_rate[1] = result.Read<float>(5);
repTemplate.faction_rank[1] = result.Read<uint>(6);
repTemplate.faction[2] = result.Read<uint>(7);
repTemplate.faction_rate[2] = result.Read<float>(8);
repTemplate.faction_rank[2] = result.Read<uint>(9);
repTemplate.faction[3] = result.Read<uint>(10);
repTemplate.faction_rate[3] = result.Read<float>(11);
repTemplate.faction_rank[3] = result.Read<uint>(12);
repTemplate.faction[4] = result.Read<uint>(13);
repTemplate.faction_rate[4] = result.Read<float>(14);
repTemplate.faction_rank[4] = result.Read<uint>(15);
var factionEntry = CliDB.FactionStorage.LookupByKey(factionId);
if (factionEntry == null)
{
Log.outError(LogFilter.Sql, "Faction (faction.dbc) {0} does not exist but is used in `reputation_spillover_template`", factionId);
continue;
}
if (factionEntry.ParentFactionID == 0)
{
Log.outError(LogFilter.Sql, "Faction (faction.dbc) {0} in `reputation_spillover_template` does not belong to any team, skipping", factionId);
continue;
}
bool invalidSpilloverFaction = false;
for (var i = 0; i < 5; ++i)
{
if (repTemplate.faction[i] != 0)
{
var factionSpillover = CliDB.FactionStorage.LookupByKey(repTemplate.faction[i]);
if (factionSpillover.Id == 0)
{
Log.outError(LogFilter.Sql, "Spillover faction (faction.dbc) {0} does not exist but is used in `reputation_spillover_template` for faction {1}, skipping", repTemplate.faction[i], factionId);
invalidSpilloverFaction = true;
break;
}
if (!factionSpillover.CanHaveReputation())
{
Log.outError(LogFilter.Sql, "Spillover faction (faction.dbc) {0} for faction {1} in `reputation_spillover_template` can not be listed for client, and then useless, skipping",
repTemplate.faction[i], factionId);
invalidSpilloverFaction = true;
break;
}
if (repTemplate.faction_rank[i] >= (uint)ReputationRank.Max)
{
Log.outError(LogFilter.Sql, "Rank {0} used in `reputation_spillover_template` for spillover faction {1} is not valid, skipping", repTemplate.faction_rank[i], repTemplate.faction[i]);
invalidSpilloverFaction = true;
break;
}
}
}
if (invalidSpilloverFaction)
continue;
_repSpilloverTemplateStorage[factionId] = repTemplate;
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} reputation_spillover_template in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadTavernAreaTriggers()
{
uint oldMSTime = Time.GetMSTime();
_tavernAreaTriggerStorage.Clear(); // need for reload case
SQLResult result = DB.World.Query("SELECT id FROM areatrigger_tavern");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 tavern triggers. DB table `areatrigger_tavern` is empty.");
return;
}
uint count = 0;
do
{
++count;
uint Trigger_ID = result.Read<uint>(0);
AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(Trigger_ID);
if (atEntry == null)
{
Log.outError(LogFilter.Sql, "Area trigger (ID:{0}) does not exist in `AreaTrigger.dbc`.", Trigger_ID);
continue;
}
_tavernAreaTriggerStorage.Add(Trigger_ID);
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} tavern triggers in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadMailLevelRewards()
{
uint oldMSTime = Time.GetMSTime();
_mailLevelRewardStorage.Clear(); // for reload case
// 0 1 2 3
SQLResult result = DB.World.Query("SELECT level, raceMask, mailTemplateId, senderEntry FROM mail_level_reward");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 level dependent mail rewards. DB table `mail_level_reward` is empty.");
return;
}
uint count = 0;
do
{
byte level = result.Read<byte>(0);
uint raceMask = result.Read<uint>(1);
uint mailTemplateId = result.Read<uint>(2);
uint senderEntry = result.Read<uint>(3);
if (level > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
{
Log.outError(LogFilter.Sql, "Table `mail_level_reward` have data for level {0} that more supported by client ({1}), ignoring.", level, WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel));
continue;
}
if (!Convert.ToBoolean(raceMask & (uint)Race.RaceMaskAllPlayable))
{
Log.outError(LogFilter.Sql, "Table `mail_level_reward` have raceMask ({0}) for level {1} that not include any player races, ignoring.", raceMask, level);
continue;
}
if (!CliDB.MailTemplateStorage.ContainsKey(mailTemplateId))
{
Log.outError(LogFilter.Sql, "Table `mail_level_reward` have invalid mailTemplateId ({0}) for level {1} that invalid not include any player races, ignoring.", mailTemplateId, level);
continue;
}
if (GetCreatureTemplate(senderEntry) == null)
{
Log.outError(LogFilter.Sql, "Table `mail_level_reward` have not existed sender creature entry ({0}) for level {1} that invalid not include any player races, ignoring.", senderEntry, level);
continue;
}
_mailLevelRewardStorage.Add(level, new MailLevelReward(raceMask, mailTemplateId, senderEntry));
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} level dependent mail rewards in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadExplorationBaseXP()
{
uint oldMSTime = Time.GetMSTime();
SQLResult result = DB.World.Query("SELECT level, basexp FROM exploration_basexp");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 BaseXP definitions. DB table `exploration_basexp` is empty.");
return;
}
uint count = 0;
do
{
byte level = result.Read<byte>(0);
uint basexp = result.Read<uint>(1);
_baseXPTable[level] = basexp;
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} BaseXP definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadTempSummons()
{
uint oldMSTime = Time.GetMSTime();
_tempSummonDataStorage.Clear(); // needed for reload case
// 0 1 2 3 4 5 6 7 8 9
SQLResult result = DB.World.Query("SELECT summonerId, summonerType, groupId, entry, position_x, position_y, position_z, orientation, summonType, summonTime FROM creature_summon_groups");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 temp summons. DB table `creature_summon_groups` is empty.");
return;
}
uint count = 0;
do
{
uint summonerId = result.Read<uint>(0);
SummonerType summonerType = (SummonerType)result.Read<byte>(1);
byte group = result.Read<byte>(2);
switch (summonerType)
{
case SummonerType.Creature:
if (GetCreatureTemplate(summonerId) == null)
{
Log.outError(LogFilter.Sql, "Table `creature_summon_groups` has summoner with non existing entry {0} for creature summoner type, skipped.", summonerId);
continue;
}
break;
case SummonerType.GameObject:
if (GetGameObjectTemplate(summonerId) == null)
{
Log.outError(LogFilter.Sql, "Table `creature_summon_groups` has summoner with non existing entry {0} for gameobject summoner type, skipped.", summonerId);
continue;
}
break;
case SummonerType.Map:
if (!CliDB.MapStorage.ContainsKey(summonerId))
{
Log.outError(LogFilter.Sql, "Table `creature_summon_groups` has summoner with non existing entry {0} for map summoner type, skipped.", summonerId);
continue;
}
break;
default:
Log.outError(LogFilter.Sql, "Table `creature_summon_groups` has unhandled summoner type {0} for summoner {1}, skipped.", summonerType, summonerId);
continue;
}
TempSummonData data = new TempSummonData();
data.entry = result.Read<uint>(3);
if (GetCreatureTemplate(data.entry) == null)
{
Log.outError(LogFilter.Sql, "Table `creature_summon_groups` has creature in group [Summoner ID: {0}, Summoner Type: {1}, Group ID: {2}] with non existing creature entry {3}, skipped.",
summonerId, summonerType, group, data.entry);
continue;
}
float posX = result.Read<float>(4);
float posY = result.Read<float>(5);
float posZ = result.Read<float>(6);
float orientation = result.Read<float>(7);
data.pos = new Position(posX, posY, posZ, orientation);
data.type = (TempSummonType)result.Read<byte>(8);
if (data.type > TempSummonType.ManualDespawn)
{
Log.outError(LogFilter.Sql, "Table `creature_summon_groups` has unhandled temp summon type {0} in group [Summoner ID: {1}, Summoner Type: {2}, Group ID: {3}] for creature entry {4}, skipped.",
data.type, summonerId, summonerType, group, data.entry);
continue;
}
data.time = result.Read<uint>(9);
Tuple<uint, SummonerType, byte> key = Tuple.Create(summonerId, summonerType, group);
_tempSummonDataStorage.Add(key, data);
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} temp summons in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadPageTexts()
{
uint oldMSTime = Time.GetMSTime();
// 0 1 2 3 4
SQLResult result = DB.World.Query("SELECT ID, text, NextPageID, PlayerConditionID, Flags FROM page_text");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 page texts. DB table `page_text` is empty!");
return;
}
uint count = 0;
do
{
uint id = result.Read<uint>(0);
PageText pageText = new PageText();
pageText.Text = result.Read<string>(1);
pageText.NextPageID = result.Read<uint>(2);
pageText.PlayerConditionID = result.Read<int>(3);
pageText.Flags = result.Read<byte>(4);
_pageTextStorage[id] = pageText;
++count;
}
while (result.NextRow());
foreach (var pair in _pageTextStorage)
{
if (pair.Value.NextPageID != 0)
{
if (!_pageTextStorage.ContainsKey(pair.Value.NextPageID))
Log.outError(LogFilter.Sql, "Page text (ID: {0}) has non-existing `NextPageID` ({1})", pair.Key, pair.Value.NextPageID);
}
}
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} page texts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadReservedPlayersNames()
{
uint oldMSTime = Time.GetMSTime();
_reservedNamesStorage.Clear(); // need for reload case
SQLResult result = DB.Characters.Query("SELECT name FROM reserved_name");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 reserved player names. DB table `reserved_name` is empty!");
return;
}
uint count = 0;
do
{
string name = result.Read<string>(0);
_reservedNamesStorage.Add(name.ToLower());
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} reserved player names in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
//not very fast function but it is called only once a day, or on starting-up
public void ReturnOrDeleteOldMails(bool serverUp)
{
uint oldMSTime = Time.GetMSTime();
long curTime = Time.UnixTime;
DateTime lt = Time.UnixTimeToDateTime(curTime).ToLocalTime();
long basetime = curTime;
Log.outInfo(LogFilter.Server, "Returning mails current time: hour: {0}, minute: {1}, second: {2} ", lt.Hour, lt.Minute, lt.Second);
PreparedStatement stmt;
// Delete all old mails without item and without body immediately, if starting server
if (!serverUp)
{
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EMPTY_EXPIRED_MAIL);
stmt.AddValue(0, basetime);
DB.Characters.Execute(stmt);
}
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_EXPIRED_MAIL);
stmt.AddValue(0, basetime);
SQLResult result = DB.Characters.Query(stmt);
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "No expired mails found.");
return; // any mails need to be returned or deleted
}
MultiMap<uint, MailItemInfo> itemsCache = new MultiMap<uint, MailItemInfo>();
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_EXPIRED_MAIL_ITEMS);
stmt.AddValue(0, (uint)basetime);
SQLResult items = DB.Characters.Query(stmt);
if (!items.IsEmpty())
{
MailItemInfo item = new MailItemInfo();
do
{
item.item_guid = result.Read<uint>(0);
item.item_template = result.Read<uint>(1);
uint mailId = result.Read<uint>(2);
itemsCache.Add(mailId, item);
} while (items.NextRow());
}
uint deletedCount = 0;
uint returnedCount = 0;
do
{
Mail m = new Mail();
m.messageID = result.Read<uint>(0);
m.messageType = (MailMessageType)result.Read<byte>(1);
m.sender = result.Read<uint>(2);
m.receiver = result.Read<ulong>(3);
bool has_items = result.Read<bool>(4);
m.expire_time = result.Read<uint>(5);
m.deliver_time = 0;
m.COD = result.Read<ulong>(6);
m.checkMask = (MailCheckMask)result.Read<byte>(7);
m.mailTemplateId = result.Read<ushort>(8);
Player player = null;
if (serverUp)
player = Global.ObjAccessor.FindPlayer(ObjectGuid.Create(HighGuid.Player, m.receiver));
if (player && player.m_mailsLoaded)
{ // this code will run very improbably (the time is between 4 and 5 am, in game is online a player, who has old mail
// his in mailbox and he has already listed his mails)
continue;
}
// Delete or return mail
if (has_items)
{
// read items from cache
List<MailItemInfo> temp = itemsCache[m.messageID];
Extensions.Swap(ref m.items, ref temp);
// if it is mail from non-player, or if it's already return mail, it shouldn't be returned, but deleted
if (m.messageType != MailMessageType.Normal || (m.checkMask.HasAnyFlag(MailCheckMask.CodPayment | MailCheckMask.Returned)))
{
// mail open and then not returned
foreach (var itemInfo in m.items)
{
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE);
stmt.AddValue(0, itemInfo.item_guid);
DB.Characters.Execute(stmt);
}
}
else
{
// Mail will be returned
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_MAIL_RETURNED);
stmt.AddValue(0, m.receiver);
stmt.AddValue(1, m.sender);
stmt.AddValue(2, basetime + 30 * Time.Day);
stmt.AddValue(3, basetime);
stmt.AddValue(4, MailCheckMask.Returned);
stmt.AddValue(5, m.messageID);
DB.Characters.Execute(stmt);
foreach (var itemInfo in m.items)
{
// Update receiver in mail items for its proper delivery, and in instance_item for avoid lost item at sender delete
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_MAIL_ITEM_RECEIVER);
stmt.AddValue(0, m.sender);
stmt.AddValue(1, itemInfo.item_guid);
DB.Characters.Execute(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ITEM_OWNER);
stmt.AddValue(0, m.sender);
stmt.AddValue(1, itemInfo.item_guid);
DB.Characters.Execute(stmt);
}
++returnedCount;
continue;
}
}
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_BY_ID);
stmt.AddValue(0, m.messageID);
DB.Characters.Execute(stmt);
++deletedCount;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Processed {0} expired mails: {1} deleted and {2} returned in {3} ms", deletedCount + returnedCount, deletedCount, returnedCount, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadSceneTemplates()
{
uint oldMSTime = Time.GetMSTime();
_sceneTemplateStorage.Clear();
SQLResult result = DB.World.Query("SELECT SceneId, Flags, ScriptPackageID, ScriptName FROM scene_template");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 scene templates. DB table `scene_template` is empty.");
return;
}
uint count = 0;
do
{
uint sceneId = result.Read<uint>(0);
SceneTemplate sceneTemplate = new SceneTemplate();
sceneTemplate.SceneId = sceneId;
sceneTemplate.PlaybackFlags = (SceneFlags)result.Read<uint>(1);
sceneTemplate.ScenePackageId = result.Read<uint>(2);
sceneTemplate.ScriptId = GetScriptId(result.Read<string>(3));
_sceneTemplateStorage[sceneId] = sceneTemplate;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} scene templates in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public MailLevelReward GetMailLevelReward(uint level, uint raceMask)
{
var mailList = _mailLevelRewardStorage.LookupByKey((byte)level);
if (mailList.Empty())
return null;
foreach (var mailReward in mailList)
if (Convert.ToBoolean(mailReward.raceMask & raceMask))
return mailReward;
return null;
}
public RepRewardRate GetRepRewardRate(uint factionId)
{
return _repRewardRateStorage.LookupByKey(factionId);
}
public RepSpilloverTemplate GetRepSpillover(uint factionId)
{
return _repSpilloverTemplateStorage.LookupByKey(factionId);
}
public ReputationOnKillEntry GetReputationOnKilEntry(uint id)
{
return _repOnKillStorage.LookupByKey(id);
}
public void SetHighestGuids()
{
SQLResult result = DB.Characters.Query("SELECT MAX(guid) FROM characters");
if (!result.IsEmpty())
GetGuidSequenceGenerator(HighGuid.Player).Set(result.Read<ulong>(0) + 1);
result = DB.Characters.Query("SELECT MAX(guid) FROM item_instance");
if (!result.IsEmpty())
GetGuidSequenceGenerator(HighGuid.Item).Set(result.Read<ulong>(0) + 1);
// Cleanup other tables from not existed guids ( >= hiItemGuid)
DB.Characters.Execute("DELETE FROM character_inventory WHERE item >= {0}", GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query
DB.Characters.Execute("DELETE FROM mail_items WHERE item_guid >= {0}", GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query
DB.Characters.Execute("DELETE FROM auctionhouse WHERE itemguid >= {0}", GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query
DB.Characters.Execute("DELETE FROM guild_bank_item WHERE item_guid >= {0}", GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query
result = DB.World.Query("SELECT MAX(guid) FROM transports");
if (!result.IsEmpty())
GetGuidSequenceGenerator(HighGuid.Transport).Set(result.Read<ulong>(0) + 1);
result = DB.Characters.Query("SELECT MAX(id) FROM auctionhouse");
if (!result.IsEmpty())
_auctionId = result.Read<uint>(0) + 1;
result = DB.Characters.Query("SELECT MAX(id) FROM mail");
if (!result.IsEmpty())
_mailId = result.Read<uint>(0) + 1;
result = DB.Characters.Query("SELECT MAX(arenateamid) FROM arena_team");
if (!result.IsEmpty())
Global.ArenaTeamMgr.SetNextArenaTeamId(result.Read<uint>(0) + 1);
result = DB.Characters.Query("SELECT MAX(setguid) FROM character_equipmentsets");
if (!result.IsEmpty())
_equipmentSetGuid = result.Read<ulong>(0) + 1;
result = DB.Characters.Query("SELECT MAX(guildId) FROM guild");
if (result.GetRowCount() == 1)
Global.GuildMgr.SetNextGuildId(result.Read<uint>(0) + 1);
result = DB.Characters.Query("SELECT MAX(itemId) from character_void_storage");
if (!result.IsEmpty())
_voidItemId = result.Read<ulong>(0) + 1;
result = DB.World.Query("SELECT MAX(guid) FROM creature");
if (!result.IsEmpty())
_creatureSpawnId = result.Read<ulong>(0) + 1;
result = DB.World.Query("SELECT MAX(guid) FROM gameobject");
if (!result.IsEmpty())
_gameObjectSpawnId = result.Read<ulong>(0) + 1;
}
public uint GenerateAuctionID()
{
if (_auctionId >= 0xFFFFFFFE)
{
Log.outError(LogFilter.Server, "Auctions ids overflow!! Can't continue, shutting down server. ");
Global.WorldMgr.StopNow();
}
return _auctionId++;
}
public ulong GenerateEquipmentSetGuid()
{
if (_equipmentSetGuid >= 0xFFFFFFFFFFFFFFFE)
{
Log.outError(LogFilter.Server, "EquipmentSet guid overflow!! Can't continue, shutting down server. ");
Global.WorldMgr.StopNow();
}
return _equipmentSetGuid++;
}
public uint GenerateMailID()
{
if (_mailId >= 0xFFFFFFFE)
{
Log.outError(LogFilter.Server, "Mail ids overflow!! Can't continue, shutting down server. ");
Global.WorldMgr.StopNow();
}
return _mailId++;
}
public ulong GenerateVoidStorageItemId()
{
if (_voidItemId >= 0xFFFFFFFFFFFFFFFE)
{
Log.outError(LogFilter.Misc, "_voidItemId overflow!! Can't continue, shutting down server. ");
Global.WorldMgr.StopNow(ShutdownExitCode.Error);
}
return _voidItemId++;
}
public ulong GenerateCreatureSpawnId()
{
if (_creatureSpawnId >= 0xFFFFFFFFFFFFFFFE)
{
Log.outFatal(LogFilter.Server, "Creature spawn id overflow!! Can't continue, shutting down server. ");
Global.WorldMgr.StopNow();
}
return _creatureSpawnId++;
}
public ulong GenerateGameObjectSpawnId()
{
if (_gameObjectSpawnId >= 0xFFFFFFFFFFFFFFFE)
{
Log.outFatal(LogFilter.Server, "Gameobject spawn id overflow!! Can't continue, shutting down server. ");
Global.WorldMgr.StopNow();
}
return _gameObjectSpawnId++;
}
public ObjectGuidGenerator GetGenerator(HighGuid high)
{
Contract.Assert(ObjectGuid.IsGlobal(high) || ObjectGuid.IsRealmSpecific(high), "Only global guid can be generated in ObjectMgr context");
return GetGuidSequenceGenerator(high);
}
ObjectGuidGenerator GetGuidSequenceGenerator(HighGuid high)
{
if (!_guidGenerators.ContainsKey(high))
_guidGenerators[high] = new ObjectGuidGenerator(high);
return _guidGenerators[high];
}
public uint GetBaseXP(uint level)
{
return _baseXPTable.ContainsKey(level) ? _baseXPTable[level] : 0;
}
public uint GetXPForLevel(uint level)
{
if (level < _playerXPperLevel.Length)
return _playerXPperLevel[level];
return 0;
}
public uint GetMaxLevelForExpansion(Expansion expansion)
{
switch (expansion)
{
case Expansion.Classic:
return 60;
case Expansion.BurningCrusade:
return 70;
case Expansion.WrathOfTheLichKing:
return 80;
case Expansion.Cataclysm:
return 85;
case Expansion.MistsOfPandaria:
return 90;
case Expansion.WarlordsOfDraenor:
return 100;
case Expansion.Legion:
return 110;
default:
break;
}
return 0;
}
public CellObjectGuids CreateCellObjectGuids(uint mapid, byte spawnMask, CellCoord cellCoord)
{
return CreateCellObjectGuids(mapid, spawnMask, cellCoord.GetId());
}
CellObjectGuids CreateCellObjectGuids(uint mapid, byte spawnMask, uint cellid)
{
uint newid = MathFunctions.MakePair32(mapid, spawnMask);
if (!mapObjectGuidsStore.ContainsKey(newid))
mapObjectGuidsStore.Add(newid, new Dictionary<uint, CellObjectGuids>());
if (!mapObjectGuidsStore[newid].ContainsKey(cellid))
mapObjectGuidsStore[newid].Add(cellid, new CellObjectGuids());
return mapObjectGuidsStore[newid][cellid];
}
public CellObjectGuids GetCellObjectGuids(uint mapid, byte spawnMask, uint cellid)
{
uint newid = MathFunctions.MakePair32(mapid, spawnMask);
if (mapObjectGuidsStore.ContainsKey(newid) && mapObjectGuidsStore[newid].ContainsKey(cellid))
return mapObjectGuidsStore[newid][cellid];
return null;
}
public Dictionary<uint, CellObjectGuids> GetMapObjectGuids(uint mapid, byte spawnMode)
{
var pair = MathFunctions.MakePair32(mapid, spawnMode);
return mapObjectGuidsStore.LookupByKey(pair);
}
public PageText GetPageText(uint pageEntry)
{
return _pageTextStorage.LookupByKey(pageEntry);
}
public uint GetNearestTaxiNode(float x, float y, float z, uint mapid, Team team)
{
bool found = false;
float dist = 10000;
uint id = 0;
TaxiNodeFlags requireFlag = (team == Team.Alliance) ? TaxiNodeFlags.Alliance : TaxiNodeFlags.Horde;
foreach (var node in CliDB.TaxiNodesStorage.Values)
{
var i = node.Id;
if (node.MapID != mapid || !node.Flags.HasAnyFlag(requireFlag))
continue;
byte field = (byte)((i - 1) / 8);
byte submask = (byte)(1 << (int)((i - 1) % 8));
// skip not taxi network nodes
if ((CliDB.TaxiNodesMask[field] & submask) == 0)
continue;
float dist2 = (node.Pos.X - x) * (node.Pos.X - x) + (node.Pos.Y - y) * (node.Pos.Y - y) + (node.Pos.Z - z) * (node.Pos.Z - z);
if (found)
{
if (dist2 < dist)
{
dist = dist2;
id = i;
}
}
else
{
found = true;
dist = dist2;
id = i;
}
}
return id;
}
public void GetTaxiPath(uint source, uint destination, out uint path, out uint cost)
{
var pathSet = CliDB.TaxiPathSetBySource.LookupByKey(source);
if (pathSet == null)
{
path = 0;
cost = 0;
return;
}
var dest_i = pathSet.LookupByKey(destination);
if (dest_i == null)
{
path = 0;
cost = 0;
return;
}
cost = dest_i.price;
path = dest_i.ID;
}
public uint GetTaxiMountDisplayId(uint id, Team team, bool allowed_alt_team = false)
{
uint mount_id = 0;
// select mount creature id
TaxiNodesRecord node = CliDB.TaxiNodesStorage.LookupByKey(id);
if (node != null)
{
uint mount_entry = 0;
if (team == Team.Alliance)
mount_entry = node.MountCreatureID[1];
else
mount_entry = node.MountCreatureID[0];
// Fix for Alliance not being able to use Acherus taxi
// only one mount type for both sides
if (mount_entry == 0 && allowed_alt_team)
{
// Simply reverse the selection. At least one team in theory should have a valid mount ID to choose.
mount_entry = team == Team.Alliance ? node.MountCreatureID[0] : node.MountCreatureID[1];
}
CreatureTemplate mount_info = GetCreatureTemplate(mount_entry);
if (mount_info != null)
{
mount_id = mount_info.GetRandomValidModelId();
if (mount_id == 0)
{
Log.outError(LogFilter.Sql, "No displayid found for the taxi mount with the entry {0}! Can't load it!", mount_entry);
return 0;
}
}
}
// minfo is not actually used but the mount_id was updated
GetCreatureModelRandomGender(ref mount_id);
return mount_id;
}
public AreaTriggerStruct GetAreaTrigger(uint trigger)
{
return _areaTriggerStorage.LookupByKey(trigger);
}
public AccessRequirement GetAccessRequirement(uint mapid, Difficulty difficulty)
{
return _accessRequirementStorage.LookupByKey(MathFunctions.MakePair64(mapid, (uint)difficulty));
}
public bool IsTavernAreaTrigger(uint Trigger_ID)
{
return _tavernAreaTriggerStorage.Contains(Trigger_ID);
}
public AreaTriggerStruct GetGoBackTrigger(uint Map)
{
bool useParentDbValue = false;
uint parentId = 0;
MapRecord mapEntry = CliDB.MapStorage.LookupByKey(Map);
if (mapEntry == null || mapEntry.CorpseMapID < 0)
return null;
if (mapEntry.IsDungeon())
{
InstanceTemplate iTemplate = GetInstanceTemplate(Map);
if (iTemplate == null)
return null;
parentId = iTemplate.Parent;
useParentDbValue = true;
}
uint entrance_map = (uint)mapEntry.CorpseMapID;
foreach (var pair in _areaTriggerStorage)
{
if ((!useParentDbValue && pair.Value.target_mapId == entrance_map) || (useParentDbValue && pair.Value.target_mapId == parentId))
{
AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(pair.Key);
if (atEntry != null && atEntry.MapID == Map)
return pair.Value;
}
}
return null;
}
public AreaTriggerStruct GetMapEntranceTrigger(uint Map)
{
foreach (var pair in _areaTriggerStorage)
{
if (pair.Value.target_mapId == Map)
{
AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(pair.Key);
if (atEntry != null)
return pair.Value;
}
}
return null;
}
public SceneTemplate GetSceneTemplate(uint sceneId)
{
return _sceneTemplateStorage.LookupByKey(sceneId);
}
public List<TempSummonData> GetSummonGroup(uint summonerId, SummonerType summonerType, byte group)
{
Tuple<uint, SummonerType,byte> key = Tuple.Create(summonerId, summonerType, group);
return _tempSummonDataStorage.LookupByKey(key);
}
public bool IsReservedName(string name)
{
return _reservedNamesStorage.Contains(name.ToLower());
}
//Vehicles
public void LoadVehicleTemplateAccessories()
{
uint oldMSTime = Time.GetMSTime();
_vehicleTemplateAccessoryStore.Clear(); // needed for reload case
uint count = 0;
// 0 1 2 3 4 5
SQLResult result = DB.World.Query("SELECT `entry`, `accessory_entry`, `seat_id`, `minion`, `summontype`, `summontimer` FROM `vehicle_template_accessory`");
if (result.IsEmpty())
{
Log.outError(LogFilter.ServerLoading, "Loaded 0 vehicle template accessories. DB table `vehicle_template_accessory` is empty.");
return;
}
do
{
uint entry = result.Read<uint>(0);
uint accessory = result.Read<uint>(1);
sbyte seatId = result.Read<sbyte>(2);
bool isMinion = result.Read<bool>(3);
byte summonType = result.Read<byte>(4);
uint summonTimer = result.Read<uint>(5);
if (GetCreatureTemplate(entry) == null)
{
Log.outError(LogFilter.Sql, "Table `vehicle_template_accessory`: creature template entry {0} does not exist.", entry);
continue;
}
if (GetCreatureTemplate(accessory) == null)
{
Log.outError(LogFilter.Sql, "Table `vehicle_template_accessory`: Accessory {0} does not exist.", accessory);
continue;
}
if (!_spellClickInfoStorage.ContainsKey(entry))
{
Log.outError(LogFilter.Sql, "Table `vehicle_template_accessory`: creature template entry {0} has no data in npc_spellclick_spells", entry);
continue;
}
_vehicleTemplateAccessoryStore.Add(entry, new VehicleAccessory(accessory, seatId, isMinion, summonType, summonTimer));
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Vehicle Template Accessories in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadVehicleAccessories()
{
uint oldMSTime = Time.GetMSTime();
_vehicleAccessoryStore.Clear(); // needed for reload case
uint count = 0;
// 0 1 2 3 4 5
SQLResult result = DB.World.Query("SELECT `guid`, `accessory_entry`, `seat_id`, `minion`, `summontype`, `summontimer` FROM `vehicle_accessory`");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Vehicle Accessories in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime));
return;
}
do
{
uint uiGUID = result.Read<uint>(0);
uint uiAccessory = result.Read<uint>(1);
sbyte uiSeat = result.Read<sbyte>(2);
bool bMinion = result.Read<bool>(3);
byte uiSummonType = result.Read<byte>(4);
uint uiSummonTimer = result.Read<uint>(5);
if (GetCreatureTemplate(uiAccessory) == null)
{
Log.outError(LogFilter.Sql, "Table `vehicle_accessory`: Accessory {0} does not exist.", uiAccessory);
continue;
}
_vehicleAccessoryStore.Add(uiGUID, new VehicleAccessory(uiAccessory, uiSeat, bMinion, uiSummonType, uiSummonTimer));
++count;
}
while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Vehicle Accessories in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public List<VehicleAccessory> GetVehicleAccessoryList(Vehicle veh)
{
Creature cre = veh.GetBase().ToCreature();
if (cre != null)
{
// Give preference to GUID-based accessories
var list = _vehicleAccessoryStore.LookupByKey(cre.GetSpawnId());
if (!list.Empty())
return list;
}
// Otherwise return entry-based
return _vehicleTemplateAccessoryStore.LookupByKey(veh.GetCreatureEntry());
}
#region Fields
public static LanguageDesc[] lang_description;
//General
Dictionary<uint, StringArray> CypherStringStorage = new Dictionary<uint, StringArray>();
Dictionary<uint, RepRewardRate> _repRewardRateStorage = new Dictionary<uint, RepRewardRate>();
Dictionary<uint, ReputationOnKillEntry> _repOnKillStorage = new Dictionary<uint, ReputationOnKillEntry>();
Dictionary<uint, RepSpilloverTemplate> _repSpilloverTemplateStorage = new Dictionary<uint, RepSpilloverTemplate>();
MultiMap<byte, MailLevelReward> _mailLevelRewardStorage = new MultiMap<byte, MailLevelReward>();
MultiMap<Tuple<uint, SummonerType, byte>, TempSummonData> _tempSummonDataStorage = new MultiMap<Tuple<uint, SummonerType, byte>, TempSummonData>();
Dictionary<uint, PageText> _pageTextStorage = new Dictionary<uint, PageText>();
List<string> _reservedNamesStorage = new List<string>();
Dictionary<uint, SceneTemplate> _sceneTemplateStorage = new Dictionary<uint, SceneTemplate>();
Dictionary<byte, byte> _raceExpansionRequirementStorage = new Dictionary<byte, byte>();
Dictionary<byte, byte> _classExpansionRequirementStorage = new Dictionary<byte, byte>();
Dictionary<uint, string> _realmNameStorage = new Dictionary<uint, string>();
//Quest
Dictionary<uint, Quest> _questTemplates = new Dictionary<uint, Quest>();
MultiMap<uint, uint> _goQuestRelations = new MultiMap<uint, uint>();
MultiMap<uint, uint> _goQuestInvolvedRelations = new MultiMap<uint, uint>();
MultiMap<uint, uint> _goQuestInvolvedRelationsReverse = new MultiMap<uint, uint>();
MultiMap<uint, uint> _creatureQuestRelations = new MultiMap<uint, uint>();
MultiMap<uint, uint> _creatureQuestInvolvedRelations = new MultiMap<uint, uint>();
MultiMap<uint, uint> _creatureQuestInvolvedRelationsReverse = new MultiMap<uint, uint>();
public MultiMap<int, uint> _exclusiveQuestGroups = new MultiMap<int, uint>();
MultiMap<uint, QuestPOI> _questPOIStorage = new MultiMap<uint, QuestPOI>();
MultiMap<uint, uint> _questAreaTriggerStorage = new MultiMap<uint, uint>();
Dictionary<uint, QuestObjective> _questObjectives = new Dictionary<uint, QuestObjective>();
Dictionary<byte, Dictionary<uint, QuestGreeting>> _questGreetingStorage = new Dictionary<byte, Dictionary<uint, QuestGreeting>>();
//Scripts
List<string> scriptNamesStorage = new List<string>();
MultiMap<uint, uint> spellScriptsStorage = new MultiMap<uint, uint>();
public Dictionary<uint, MultiMap<uint, ScriptInfo>> sSpellScripts = new Dictionary<uint, MultiMap<uint, ScriptInfo>>();
public Dictionary<uint, MultiMap<uint, ScriptInfo>> sEventScripts = new Dictionary<uint, MultiMap<uint, ScriptInfo>>();
public Dictionary<uint, MultiMap<uint, ScriptInfo>> sWaypointScripts = new Dictionary<uint, MultiMap<uint, ScriptInfo>>();
Dictionary<uint, uint> areaTriggerScriptStorage = new Dictionary<uint, uint>();
//Maps
public Dictionary<uint, GameTele> gameTeleStorage = new Dictionary<uint, GameTele>();
Dictionary<uint, Dictionary<uint, CellObjectGuids>> mapObjectGuidsStore = new Dictionary<uint, Dictionary<uint, CellObjectGuids>>();
Dictionary<uint, InstanceTemplate> instanceTemplateStorage = new Dictionary<uint, InstanceTemplate>();
public MultiMap<uint, GraveYardData> GraveYardStorage = new MultiMap<uint, GraveYardData>();
List<ushort> _transportMaps = new List<ushort>();
//Spells /Skills / Phases
MultiMap<uint, uint> _terrainPhaseInfoStore = new MultiMap<uint, uint>();
MultiMap<uint, uint> _terrainMapDefaultStore = new MultiMap<uint, uint>();
MultiMap<uint, uint> _terrainWorldMapStore = new MultiMap<uint, uint>();
MultiMap<uint, PhaseInfoStruct> _phases = new MultiMap<uint, PhaseInfoStruct>();
MultiMap<uint, SpellClickInfo> _spellClickInfoStorage = new MultiMap<uint, SpellClickInfo>();
Dictionary<uint, int> _fishingBaseForAreaStorage = new Dictionary<uint, int>();
Dictionary<uint, SkillTiersEntry> _skillTiers = new Dictionary<uint, SkillTiersEntry>();
//Gossip
MultiMap<uint, GossipMenuItems> gossipMenuItemsStorage = new MultiMap<uint, GossipMenuItems>();
MultiMap<uint, GossipMenus> gossipMenusStorage = new MultiMap<uint, GossipMenus>();
Dictionary<uint, PointOfInterest> pointsOfInterestStorage = new Dictionary<uint, PointOfInterest>();
//Creature
Dictionary<uint, CreatureTemplate> creatureTemplateStorage = new Dictionary<uint, CreatureTemplate>();
Dictionary<uint, CreatureModelInfo> creatureModelStorage = new Dictionary<uint, CreatureModelInfo>();
Dictionary<ulong, CreatureData> creatureDataStorage = new Dictionary<ulong, CreatureData>();
Dictionary<ulong, CreatureAddon> creatureAddonStorage = new Dictionary<ulong, CreatureAddon>();
MultiMap<uint, uint> _creatureQuestItemStorage = new MultiMap<uint, uint>();
Dictionary<uint, CreatureAddon> creatureTemplateAddonStorage = new Dictionary<uint, CreatureAddon>();
MultiMap<uint, Tuple<uint, EquipmentInfo>> equipmentInfoStorage = new MultiMap<uint, Tuple<uint, EquipmentInfo>>();
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, 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>();
//GameObject
Dictionary<uint, GameObjectTemplate> gameObjectTemplateStorage = new Dictionary<uint, GameObjectTemplate>();
Dictionary<ulong, GameObjectData> gameObjectDataStorage = new Dictionary<ulong, GameObjectData>();
Dictionary<ulong, GameObjectTemplateAddon> _gameObjectTemplateAddonStore = new Dictionary<ulong, GameObjectTemplateAddon>();
Dictionary<ulong, GameObjectAddon> _gameObjectAddonStorage = new Dictionary<ulong, GameObjectAddon>();
MultiMap<uint, uint> _gameObjectQuestItemStorage = new MultiMap<uint, uint>();
List<uint> _gameObjectForQuestStorage = new List<uint>();
//Item
Dictionary<uint, ItemTemplate> ItemTemplateStorage = new Dictionary<uint, ItemTemplate>();
//Player
PlayerInfo[][] _playerInfo = new PlayerInfo[(int)Race.Max][];
//Faction Change
public Dictionary<uint, uint> FactionChangeAchievements = new Dictionary<uint, uint>();
public Dictionary<uint, uint> FactionChangeItems = new Dictionary<uint, uint>();
public Dictionary<uint, uint> FactionChangeQuests = new Dictionary<uint, uint>();
public Dictionary<uint, uint> FactionChangeReputation = new Dictionary<uint, uint>();
public Dictionary<uint, uint> FactionChangeSpells = new Dictionary<uint, uint>();
public Dictionary<uint, uint> FactionChangeTitles = new Dictionary<uint, uint>();
//Pets
Dictionary<uint, PetLevelInfo[]> petInfoStore = new Dictionary<uint, PetLevelInfo[]>();
MultiMap<uint, string> _petHalfName0 = new MultiMap<uint, string>();
MultiMap<uint, string> _petHalfName1 = new MultiMap<uint, string>();
//Vehicles
MultiMap<uint, VehicleAccessory> _vehicleTemplateAccessoryStore = new MultiMap<uint, VehicleAccessory>();
MultiMap<ulong, VehicleAccessory> _vehicleAccessoryStore = new MultiMap<ulong, VehicleAccessory>();
//Locales
Dictionary<uint, CreatureLocale> _creatureLocaleStorage = new Dictionary<uint, CreatureLocale>();
Dictionary<uint, GameObjectLocale> _gameObjectLocaleStorage = new Dictionary<uint, GameObjectLocale>();
Dictionary<uint, QuestTemplateLocale> _questTemplateLocaleStorage = new Dictionary<uint, QuestTemplateLocale>();
Dictionary<uint, QuestObjectivesLocale> _questObjectivesLocaleStorage = new Dictionary<uint, QuestObjectivesLocale>();
Dictionary<uint, QuestOfferRewardLocale> _questOfferRewardLocaleStorage = new Dictionary<uint, QuestOfferRewardLocale>();
Dictionary<uint, QuestRequestItemsLocale> _questRequestItemsLocaleStorage = new Dictionary<uint, QuestRequestItemsLocale>();
Dictionary<uint, GossipMenuItemsLocale> _gossipMenuItemsLocaleStorage = new Dictionary<uint, GossipMenuItemsLocale>();
Dictionary<uint, PageTextLocale> _pageTextLocaleStorage = new Dictionary<uint, PageTextLocale>();
Dictionary<uint, PointOfInterestLocale> _pointOfInterestLocaleStorage = new Dictionary<uint, PointOfInterestLocale>();
List<uint> _tavernAreaTriggerStorage = new List<uint>();
Dictionary<uint, AreaTriggerStruct> _areaTriggerStorage = new Dictionary<uint, AreaTriggerStruct>();
Dictionary<ulong, AccessRequirement> _accessRequirementStorage = new Dictionary<ulong, AccessRequirement>();
MultiMap<ulong, DungeonEncounter> _dungeonEncounterStorage = new MultiMap<ulong, DungeonEncounter>();
Dictionary<HighGuid, ObjectGuidGenerator> _guidGenerators = new Dictionary<HighGuid, ObjectGuidGenerator>();
// first free id for selected id type
uint _auctionId;
ulong _equipmentSetGuid;
uint _mailId;
uint _hiPetNumber;
ulong _voidItemId;
ulong _creatureSpawnId;
ulong _gameObjectSpawnId;
uint[] _playerXPperLevel;
Dictionary<uint, uint> _baseXPTable = new Dictionary<uint, uint>();
#endregion
}
[StructLayout(LayoutKind.Explicit)]
public struct ScriptInfo
{
[FieldOffset(0)]
public ScriptsType type;
[FieldOffset(4)]
public uint id;
[FieldOffset(8)]
public uint delay;
[FieldOffset(12)]
public ScriptCommands command;
[FieldOffset(16)]
public raw Raw;
[FieldOffset(16)]
public talk Talk;
[FieldOffset(16)]
public emote Emote;
[FieldOffset(16)]
public fieldset FieldSet;
[FieldOffset(16)]
public moveto MoveTo;
[FieldOffset(16)]
public flagtoggle FlagToggle;
[FieldOffset(16)]
public teleportto TeleportTo;
[FieldOffset(16)]
public questexplored QuestExplored;
[FieldOffset(16)]
public killcredit KillCredit;
[FieldOffset(16)]
public respawngameobject RespawnGameObject;
[FieldOffset(16)]
public tempsummoncreature TempSummonCreature;
[FieldOffset(16)]
public toggledoor ToggleDoor;
[FieldOffset(16)]
public removeaura RemoveAura;
[FieldOffset(16)]
public castspell CastSpell;
[FieldOffset(16)]
public playsound PlaySound;
[FieldOffset(16)]
public createitem CreateItem;
[FieldOffset(16)]
public despawnself DespawnSelf;
[FieldOffset(16)]
public loadpath LoadPath;
[FieldOffset(16)]
public callscript CallScript;
[FieldOffset(16)]
public kill Kill;
[FieldOffset(16)]
public orientation Orientation;
[FieldOffset(16)]
public equip Equip;
[FieldOffset(16)]
public model Model;
[FieldOffset(16)]
public playmovie PlayMovie;
[FieldOffset(16)]
public movement Movement;
[FieldOffset(16)]
public playanimkit PlayAnimKit;
public string GetDebugInfo()
{
return string.Format("{0} ('{1}' script id: {2})", command, Global.ObjectMgr.GetScriptsTableNameByType(type), id);
}
#region Structs
public unsafe struct raw
{
public fixed uint nData[3];
public fixed float fData[4];
}
public struct talk // TALK (0)
{
public ChatMsg ChatType; // datalong
public eScriptFlags Flags; // datalong2
public int TextID; // dataint
}
public struct emote // EMOTE (1)
{
public uint EmoteID; // datalong
public eScriptFlags Flags; // datalong2
}
public struct fieldset // FIELDSET (2)
{
public uint FieldID; // datalong
public uint FieldValue; // datalong2
}
public struct moveto // MOVETO (3)
{
public uint Unused1; // datalong
public uint TravelTime; // datalong2
public int Unused2; // dataint
public float DestX;
public float DestY;
public float DestZ;
}
public struct flagtoggle // FLAGSET (4)
// FLAGREMOVE (5)
{
public uint FieldID; // datalong
public uint FieldValue; // datalong2
}
public struct teleportto // TELEPORTTO (6)
{
public uint MapID; // datalong
public eScriptFlags Flags; // datalong2
public int Unused1; // dataint
public float DestX;
public float DestY;
public float DestZ;
public float Orientation;
}
public struct questexplored // QUESTEXPLORED (7)
{
public uint QuestID; // datalong
public uint Distance; // datalong2
}
public struct killcredit // KILLCREDIT (8)
{
public uint CreatureEntry; // datalong
public eScriptFlags Flags; // datalong2
}
public struct respawngameobject // RESPAWNGAMEOBJECT (9)
{
public uint GOGuid; // datalong
public uint DespawnDelay; // datalong2
}
public struct tempsummoncreature // TEMPSUMMONCREATURE (10)
{
public uint CreatureEntry; // datalong
public uint DespawnDelay; // datalong2
public int Unused1; // dataint
public float PosX;
public float PosY;
public float PosZ;
public float Orientation;
}
public struct toggledoor // CLOSEDOOR (12)
// OPENDOOR (11)
{
public uint GOGuid; // datalong
public uint ResetDelay; // datalong2
}
// ACTIVATEOBJECT (13)
public struct removeaura // REMOVEAURA (14)
{
public uint SpellID; // datalong
public eScriptFlags Flags; // datalong2
}
public struct castspell // CASTSPELL (15)
{
public uint SpellID; // datalong
public eScriptFlags Flags; // datalong2
public int CreatureEntry; // dataint
public float SearchRadius;
}
public struct playsound // PLAYSOUND (16)
{
public uint SoundID; // datalong
public eScriptFlags Flags; // datalong2
}
public struct createitem // CREATEITEM (17)
{
public uint ItemEntry; // datalong
public uint Amount; // datalong2
}
public struct despawnself // DESPAWNSELF (18)
{
public uint DespawnDelay; // datalong
}
public struct loadpath // LOADPATH (20)
{
public uint PathID; // datalong
public uint IsRepeatable; // datalong2
}
public struct callscript // CALLSCRIPTTOUNIT (21)
{
public uint CreatureEntry; // datalong
public uint ScriptID; // datalong2
public uint ScriptType; // dataint
}
public struct kill // KILL (22)
{
public uint Unused1; // datalong
public uint Unused2; // datalong2
public int RemoveCorpse; // dataint
}
public struct orientation // ORIENTATION (30)
{
public eScriptFlags Flags; // datalong
public uint Unused1; // datalong2
public int Unused2; // dataint
public float Unused3;
public float Unused4;
public float Unused5;
public float _Orientation;
}
public struct equip // EQUIP (31)
{
public uint EquipmentID; // datalong
}
public struct model // MODEL (32)
{
public uint ModelID; // datalong
}
// CLOSEGOSSIP (33)
public struct playmovie // PLAYMOVIE (34)
{
public uint MovieID; // datalong
}
public struct movement // SCRIPT_COMMAND_MOVEMENT (35)
{
public uint MovementType; // datalong
public uint MovementDistance; // datalong2
public int Path; // dataint
}
public struct playanimkit // SCRIPT_COMMAND_PLAY_ANIMKIT (36)
{
public uint AnimKitID; // datalong
}
#endregion
}
public class CellObjectGuids
{
public SortedSet<ulong> creatures = new SortedSet<ulong>();
public SortedSet<ulong> gameobjects = new SortedSet<ulong>();
}
public class GameTele
{
public float posX;
public float posY;
public float posZ;
public float orientation;
public uint mapId;
public string name;
public string nameLow;
}
public class PetLevelInfo
{
public PetLevelInfo()
{
health = 0;
mana = 0;
}
public uint[] stats = new uint[(int)Stats.Max];
public uint health;
public uint mana;
public uint armor;
}
public class SpellClickInfo
{
public uint spellId;
public byte castFlags;
public SpellClickUserTypes userType;
// helpers
public bool IsFitToRequirements(Unit clicker, Unit clickee)
{
Player playerClicker = clicker.ToPlayer();
if (playerClicker == null)
return true;
Unit summoner = null;
// Check summoners for party
if (clickee.IsSummon())
summoner = clickee.ToTempSummon().GetSummoner();
if (summoner == null)
summoner = clickee;
// This only applies to players
switch (userType)
{
case SpellClickUserTypes.Friend:
if (!playerClicker.IsFriendlyTo(summoner))
return false;
break;
case SpellClickUserTypes.Raid:
if (!playerClicker.IsInRaidWith(summoner))
return false;
break;
case SpellClickUserTypes.Party:
if (!playerClicker.IsInPartyWith(summoner))
return false;
break;
default:
break;
}
return true;
}
}
public class GraveYardData
{
public uint safeLocId;
public uint team;
}
public class QuestPOI
{
public QuestPOI(int _BlobIndex, int _ObjectiveIndex, int _QuestObjectiveID, int _QuestObjectID, int _MapID, int _WorldMapAreaID, int _Foor, int _Priority, int _Flags,
int _WorldEffectID, int _PlayerConditionID, int _UnkWoD1)
{
BlobIndex = _BlobIndex;
ObjectiveIndex = _ObjectiveIndex;
QuestObjectiveID = _QuestObjectiveID;
QuestObjectID = _QuestObjectID;
MapID = _MapID;
WorldMapAreaID = _WorldMapAreaID;
Floor = _Foor;
Priority = _Priority;
Flags = _Flags;
WorldEffectID = _WorldEffectID;
PlayerConditionID = _PlayerConditionID;
UnkWoD1 = _UnkWoD1;
}
public int BlobIndex;
public int ObjectiveIndex;
public int QuestObjectiveID;
public int QuestObjectID;
public int MapID;
public int WorldMapAreaID;
public int Floor;
public int Priority;
public int Flags;
public int WorldEffectID;
public int PlayerConditionID;
public int UnkWoD1;
public List<QuestPOIPoint> points = new List<QuestPOIPoint>();
}
public class QuestPOIPoint
{
public QuestPOIPoint(int _x, int _y)
{
X = _x;
Y = _y;
}
public int X;
public int Y;
}
public class QuestGreeting
{
public QuestGreeting()
{
greeting = "";
}
public QuestGreeting(ushort _greetEmoteType, uint _greetEmoteDelay, string _greeting)
{
greetEmoteType = _greetEmoteType;
greetEmoteDelay = _greetEmoteDelay;
greeting = _greeting;
}
public ushort greetEmoteType;
public uint greetEmoteDelay;
public string greeting;
}
public class AreaTriggerStruct
{
public uint target_mapId;
public float target_X;
public float target_Y;
public float target_Z;
public float target_Orientation;
public uint PortLocId;
}
public class DungeonEncounter
{
public DungeonEncounter(DungeonEncounterRecord _dbcEntry, EncounterCreditType _creditType, uint _creditEntry, uint _lastEncounterDungeon)
{
dbcEntry = _dbcEntry;
creditType = _creditType;
creditEntry = _creditEntry;
lastEncounterDungeon = _lastEncounterDungeon;
}
public DungeonEncounterRecord dbcEntry;
public EncounterCreditType creditType;
public uint creditEntry;
public uint lastEncounterDungeon;
}
public class MailLevelReward
{
public MailLevelReward(uint _raceMask = 0, uint _mailTemplateId = 0, uint _senderEntry = 0)
{
raceMask = _raceMask;
mailTemplateId = _mailTemplateId;
senderEntry = _senderEntry;
}
public uint raceMask;
public uint mailTemplateId;
public uint senderEntry;
}
public class PageText
{
public string Text;
public uint NextPageID;
public int PlayerConditionID;
public byte Flags;
}
public struct ExtendedPlayerName
{
public ExtendedPlayerName(string name, string realmName)
{
Name = name;
Realm = realmName;
}
public string Name;
public string Realm;
}
public class LanguageDesc
{
public LanguageDesc(Language langid, uint spellid, object skillid)
{
lang_id = langid;
spell_id = spellid;
skill_id = Convert.ToUInt32(skillid);
}
public Language lang_id;
public uint spell_id;
public uint skill_id;
}
class ItemSpecStats
{
public ItemSpecStats(ItemRecord item, ItemSparseRecord sparse)
{
if (item.Class == ItemClass.Weapon)
{
ItemType = 5;
switch ((ItemSubClassWeapon)item.SubClass)
{
case ItemSubClassWeapon.Axe:
AddStat(ItemSpecStat.OneHandedAxe);
break;
case ItemSubClassWeapon.Axe2:
AddStat(ItemSpecStat.TwoHandedAxe);
break;
case ItemSubClassWeapon.Bow:
AddStat(ItemSpecStat.Bow);
break;
case ItemSubClassWeapon.Gun:
AddStat(ItemSpecStat.Gun);
break;
case ItemSubClassWeapon.Mace:
AddStat(ItemSpecStat.OneHandedMace);
break;
case ItemSubClassWeapon.Mace2:
AddStat(ItemSpecStat.TwoHandedMace);
break;
case ItemSubClassWeapon.Polearm:
AddStat(ItemSpecStat.Polearm);
break;
case ItemSubClassWeapon.Sword:
AddStat(ItemSpecStat.OneHandedSword);
break;
case ItemSubClassWeapon.Sword2:
AddStat(ItemSpecStat.TwoHandedSword);
break;
case ItemSubClassWeapon.Warglaives:
AddStat(ItemSpecStat.Warglaives);
break;
case ItemSubClassWeapon.Staff:
AddStat(ItemSpecStat.Staff);
break;
case ItemSubClassWeapon.Fist:
AddStat(ItemSpecStat.FistWeapon);
break;
case ItemSubClassWeapon.Dagger:
AddStat(ItemSpecStat.Dagger);
break;
case ItemSubClassWeapon.Thrown:
AddStat(ItemSpecStat.Thrown);
break;
case ItemSubClassWeapon.Crossbow:
AddStat(ItemSpecStat.Crossbow);
break;
case ItemSubClassWeapon.Wand:
AddStat(ItemSpecStat.Wand);
break;
default:
break;
}
}
else if (item.Class == ItemClass.Armor)
{
switch ((ItemSubClassArmor)item.SubClass)
{
case ItemSubClassArmor.Cloth:
if (sparse.inventoryType != InventoryType.Cloak)
{
ItemType = 1;
break;
}
ItemType = 0;
AddStat(ItemSpecStat.Cloak);
break;
case ItemSubClassArmor.Leather:
ItemType = 2;
break;
case ItemSubClassArmor.Mail:
ItemType = 3;
break;
case ItemSubClassArmor.Plate:
ItemType = 4;
break;
default:
if (item.SubClass == (int)ItemSubClassArmor.Shield)
{
ItemType = 6;
AddStat(ItemSpecStat.Shield);
}
else if (item.SubClass > (int)ItemSubClassArmor.Shield && item.SubClass <= (int)ItemSubClassArmor.Relic)
{
ItemType = 6;
AddStat(ItemSpecStat.Relic);
}
else
ItemType = 0;
break;
}
}
else if (item.Class == ItemClass.Gem)
{
ItemType = 7;
GemPropertiesRecord gem = CliDB.GemPropertiesStorage.LookupByKey(sparse.GemProperties);
if (gem != null)
{
if (gem.Type.HasAnyFlag(SocketColor.RelicIron))
AddStat(ItemSpecStat.RelicIron);
if (gem.Type.HasAnyFlag(SocketColor.RelicBlood))
AddStat(ItemSpecStat.RelicBlood);
if (gem.Type.HasAnyFlag(SocketColor.RelicShadow))
AddStat(ItemSpecStat.RelicShadow);
if (gem.Type.HasAnyFlag(SocketColor.RelicFel))
AddStat(ItemSpecStat.RelicFel);
if (gem.Type.HasAnyFlag(SocketColor.RelicArcane))
AddStat(ItemSpecStat.RelicArcane);
if (gem.Type.HasAnyFlag(SocketColor.RelicFrost))
AddStat(ItemSpecStat.RelicFrost);
if (gem.Type.HasAnyFlag(SocketColor.RelicFire))
AddStat(ItemSpecStat.RelicFire);
if (gem.Type.HasAnyFlag(SocketColor.RelicWater))
AddStat(ItemSpecStat.RelicWater);
if (gem.Type.HasAnyFlag(SocketColor.RelicLife))
AddStat(ItemSpecStat.RelicLife);
if (gem.Type.HasAnyFlag(SocketColor.RelicWind))
AddStat(ItemSpecStat.RelicWind);
if (gem.Type.HasAnyFlag(SocketColor.RelicHoly))
AddStat(ItemSpecStat.RelicHoly);
}
}
else
ItemType = 0;
for (uint i = 0; i < ItemConst.MaxStats; ++i)
if (sparse.ItemStatType[i] != -1)
AddModStat(sparse.ItemStatType[i]);
}
void AddStat(ItemSpecStat statType)
{
if (ItemSpecStatCount >= ItemConst.MaxStats)
return;
for (uint i = 0; i < ItemConst.MaxStats; ++i)
if (ItemSpecStatTypes[i] == statType)
return;
ItemSpecStatTypes[ItemSpecStatCount++] = statType;
}
void AddModStat(int itemStatType)
{
switch ((ItemModType)itemStatType)
{
case ItemModType.Agility:
AddStat(ItemSpecStat.Agility);
break;
case ItemModType.Strength:
AddStat(ItemSpecStat.Strength);
break;
case ItemModType.Intellect:
AddStat(ItemSpecStat.Intellect);
break;
case ItemModType.DodgeRating:
AddStat(ItemSpecStat.Dodge);
break;
case ItemModType.ParryRating:
AddStat(ItemSpecStat.Parry);
break;
case ItemModType.CritMeleeRating:
case ItemModType.CritRangedRating:
case ItemModType.CritSpellRating:
case ItemModType.CritRating:
AddStat(ItemSpecStat.Crit);
break;
case ItemModType.HasteRating:
AddStat(ItemSpecStat.Haste);
break;
case ItemModType.HitRating:
AddStat(ItemSpecStat.Hit);
break;
case ItemModType.ExtraArmor:
AddStat(ItemSpecStat.BonusArmor);
break;
case ItemModType.AgiStrInt:
AddStat(ItemSpecStat.Agility);
AddStat(ItemSpecStat.Strength);
AddStat(ItemSpecStat.Intellect);
break;
case ItemModType.AgiStr:
AddStat(ItemSpecStat.Agility);
AddStat(ItemSpecStat.Strength);
break;
case ItemModType.AgiInt:
AddStat(ItemSpecStat.Agility);
AddStat(ItemSpecStat.Intellect);
break;
case ItemModType.StrInt:
AddStat(ItemSpecStat.Strength);
AddStat(ItemSpecStat.Intellect);
break;
}
}
public uint ItemType;
public ItemSpecStat[] ItemSpecStatTypes = new ItemSpecStat[ItemConst.MaxStats];
public uint ItemSpecStatCount;
}
public class SkillTiersEntry
{
public uint Id;
public uint[] Value = new uint[SkillConst.MaxSkillStep];
}
public class PhaseInfoStruct
{
public uint Id;
public List<Condition> Conditions = new List<Condition>();
}
public class SceneTemplate
{
public uint SceneId;
public SceneFlags PlaybackFlags;
public uint ScenePackageId;
public uint ScriptId;
}
public class DefaultCreatureBaseStats : CreatureBaseStats
{
public DefaultCreatureBaseStats()
{
BaseArmor = 1;
for (byte j = 0; j < 4; ++j)
{
BaseHealth[j] = 1;
BaseDamage[j] = 0.0f;
}
BaseMana = 0;
AttackPower = 0;
RangedAttackPower = 0;
}
}
public class GossipMenuItemsLocale
{
public StringArray OptionText = new StringArray((int)LocaleConstant.Total);
public StringArray BoxText = new StringArray((int)LocaleConstant.Total);
}
}