/* * Copyright (C) 2012-2018 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 . */ 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; using Framework.Dynamic; namespace Game { public sealed class ObjectManager : Singleton { ObjectManager() { for (var i = 0; i < SharedConst.MaxCreatureDifficulties; ++i) { _difficultyEntries[i] = new List(); _hasDifficultyEntries[i] = new List(); } 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(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(0); CypherStringStorage[entry] = new StringArray((int)SharedConst.DefaultLocale + 1); count++; for (var i = SharedConst.DefaultLocale; i >= 0; --i) AddLocaleString(result.Read((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(); _raceUnlockRequirementStorage.Clear(); // 0 1 2 SQLResult result = DB.World.Query("SELECT raceID, expansion, achievementId FROM `race_unlock_requirement`"); if (!result.IsEmpty()) { uint count = 0; do { byte raceID = result.Read(0); byte expansion = result.Read(1); uint achievementId = result.Read(2); ChrRacesRecord raceEntry = CliDB.ChrRacesStorage.LookupByKey(raceID); if (raceEntry == null) { Log.outError(LogFilter.Sql, "Race {0} defined in `race_unlock_requirement` does not exists, skipped.", raceID); continue; } if (expansion >= (int)Expansion.MaxAccountExpansions) { Log.outError(LogFilter.Sql, "Race {0} defined in `race_unlock_requirement` has incorrect expansion {1}, skipped.", raceID, expansion); continue; } if (achievementId != 0 && !CliDB.AchievementStorage.ContainsKey(achievementId)) { Log.outError(LogFilter.Sql, $"Race {raceID} defined in `race_unlock_requirement` has incorrect achievement {achievementId}, skipped."); continue; } RaceUnlockRequirement raceUnlockRequirement = new RaceUnlockRequirement(); raceUnlockRequirement.Expansion = expansion; raceUnlockRequirement.AchievementId = achievementId; _raceUnlockRequirementStorage[raceID] = raceUnlockRequirement; ++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(0); byte expansion = result.Read(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(0); string realmName = result.Read(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 ""; } 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 GetRaceUnlockRequirements() { return _raceUnlockRequirementStorage; } public RaceUnlockRequirement GetRaceUnlockRequirement(Race race) { return _raceUnlockRequirementStorage.LookupByKey((byte)race); } public Dictionary GetClassExpansionRequirements() { return _classExpansionRequirementStorage; } public Expansion GetClassExpansionRequirement(Class class_) { if (_classExpansionRequirementStorage.ContainsKey((byte)class_)) return (Expansion)_classExpansionRequirementStorage[(byte)class_]; return Expansion.Classic; } public PlayerChoice GetPlayerChoice(int choiceId) { return _playerChoices.LookupByKey(choiceId); } public PlayerChoiceLocale GetPlayerChoiceLocale(int ChoiceID) { return _playerChoiceLocales.LookupByKey(ChoiceID); } //Gossip public void LoadGossipMenu() { uint oldMSTime = Time.GetMSTime(); gossipMenusStorage.Clear(); SQLResult result = DB.World.Query("SELECT MenuId, TextId FROM gossip_menu"); if (result.IsEmpty()) { Log.outError(LogFilter.ServerLoading, "Loaded 0 gossip_menu entries. DB table `gossip_menu` is empty!"); return; } do { GossipMenus gMenu = new GossipMenus(); gMenu.MenuId = result.Read(0); gMenu.TextId = result.Read(1); if (GetNpcText(gMenu.TextId) == null) { Log.outError(LogFilter.Sql, "Table gossip_menu: Id {0} is using non-existing TextId {1}", gMenu.MenuId, gMenu.TextId); continue; } gossipMenusStorage.Add(gMenu.MenuId, gMenu); } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gossip_menu Ids in {1} ms", gossipMenusStorage.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 Ids. DB table `gossip_menu_option` is empty!"); return; } do { GossipMenuItems gMenuItem = new GossipMenuItems(); gMenuItem.MenuId = result.Read(0); gMenuItem.OptionIndex = result.Read(1); gMenuItem.OptionIcon = (GossipOptionIcon)result.Read(2); gMenuItem.OptionText = result.Read(3); gMenuItem.OptionBroadcastTextId = result.Read(4); gMenuItem.OptionType = (GossipOption)result.Read(5); gMenuItem.OptionNpcFlag = (NPCFlags)result.Read(6); gMenuItem.ActionMenuId = result.Read(7); gMenuItem.ActionPoiId = result.Read(8); gMenuItem.BoxCoded = result.Read(9); gMenuItem.BoxMoney = result.Read(10); gMenuItem.BoxText = result.Read(11); gMenuItem.BoxBroadcastTextId = result.Read(12); gMenuItem.TrainerId = result.Read(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 GossipOptionIcon.Chat"); 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); } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, $"Loaded {gossipMenuItemsStorage.Count} gossip_menu_option entries in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } 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(0); PointOfInterest POI = new PointOfInterest(); POI.ID = id; POI.Pos = new Vector2(result.Read(1), result.Read(2)); POI.Icon = result.Read(3); POI.Flags = result.Read(4); POI.Importance = result.Read(5); POI.Name = result.Read(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 GetGossipMenusMapBounds(uint uiMenuId) { return gossipMenusStorage.LookupByKey(uiMenuId); } public List 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(0); uint zoneId = result.Read(1); Team team = (Team)result.Read(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(WorldLocation location, Team team, WorldObject conditionObject) { float x, y, z; location.GetPosition(out x, out y, out z); uint MapId = location.GetMapId(); // search for zone associated closest graveyard uint zoneId = Global.MapMgr.GetZoneId(conditionObject ? conditionObject.GetPhaseShift() : PhasingHandler.EmptyPhaseShift, 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); ConditionSourceInfo conditionSource = new ConditionSourceInfo(conditionObject); // 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; if (conditionObject) { if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.Graveyard, data.safeLocId, conditionSource)) continue; if (entry.MapID == mapEntry.ParentMapID && !conditionObject.GetPhaseShift().HasVisibleMapId(entry.MapID)) continue; } // find now nearest graveyard at other map if (MapId != entry.MapID && entry.MapID != mapEntry.ParentMapID) { // 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.Corpse.X == 0 && mapEntry.Corpse.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.Corpse.X) * (entry.Loc.X - mapEntry.Corpse.X) + (entry.Loc.Y - mapEntry.Corpse.Y) * (entry.Loc.Y - mapEntry.Corpse.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 conversation_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(ScriptName) FROM quest_template_addon 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(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(0); string scriptName = result.Read(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(0); if (isSpellScriptTable) tmp.id |= result.Read(10) << 24; tmp.delay = result.Read(1); tmp.command = (ScriptCommands)result.Read(2); unsafe { tmp.Raw.nData[0] = result.Read(3); tmp.Raw.nData[1] = result.Read(4); tmp.Raw.nData[2] = (uint)result.Read(5); tmp.Raw.fData[0] = result.Read(6); tmp.Raw.fData[1] = result.Read(7); tmp.Raw.fData[2] = result.Read(8); tmp.Raw.fData[3] = result.Read(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(); 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 evt_scripts = new List(); // 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 actionSet = new List(); 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(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(0); string scriptName = result.Read(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.KeyValueList) { SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(script.Key); Dictionary 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 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 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> 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 20 "modelid4, name, femaleName, subname, TitleAlt, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, " + //21 22 23 24 25 26 27 28 29 30 31 "faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, " + //32 33 34 35 36 37 38 39 "unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_class, type, " + // 40 41 42 43 44 45 46 47 48 49 50 "type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, " + //51 52 53 54 55 56 57 58 59 60 61 62 63 "spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, " + //64 65 66 67 68 69 70 71 72 "InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, " + //73 74 75 76 77 78 "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; } do { LoadCreatureTemplate(result.GetFields()); } 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", creatureTemplateStorage.Count, Time.GetMSTimeDiffToNow(time)); } public void LoadCreatureTemplate(SQLFields fields) { uint entry = fields.Read(0); CreatureTemplate creature = new CreatureTemplate(); creature.Entry = entry; for (var i = 0; i < SharedConst.MaxCreatureDifficulties; ++i) creature.DifficultyEntry[i] = fields.Read(1 + i); for (var i = 0; i < 2; ++i) creature.KillCredit[i] = fields.Read(4 + i); creature.ModelId1 = fields.Read(6); creature.ModelId2 = fields.Read(7); creature.ModelId3 = fields.Read(8); creature.ModelId4 = fields.Read(9); creature.Name = fields.Read(10); creature.FemaleName = fields.Read(11); creature.SubName = fields.Read(12); creature.TitleAlt = fields.Read(13); creature.IconName = fields.Read(14); creature.GossipMenuId = fields.Read(15); creature.Minlevel = fields.Read(16); creature.Maxlevel = fields.Read(17); creature.HealthScalingExpansion = fields.Read(18); creature.RequiredExpansion = fields.Read(19); creature.VignetteID = fields.Read(20); creature.Faction = fields.Read(21); creature.Npcflag = (NPCFlags)fields.Read(22); creature.SpeedWalk = fields.Read(23); creature.SpeedRun = fields.Read(24); creature.Scale = fields.Read(25); creature.Rank = (CreatureEliteType)fields.Read(26); creature.DmgSchool = fields.Read(27); creature.BaseAttackTime = fields.Read(28); creature.RangeAttackTime = fields.Read(29); creature.BaseVariance = fields.Read(30); creature.RangeVariance = fields.Read(31); creature.UnitClass = fields.Read(32); creature.UnitFlags = (UnitFlags)fields.Read(33); creature.UnitFlags2 = fields.Read(34); creature.UnitFlags3 = fields.Read(35); creature.DynamicFlags = fields.Read(36); creature.Family = (CreatureFamily)fields.Read(37); creature.TrainerClass = (Class)fields.Read(38); creature.CreatureType = (CreatureType)fields.Read(39); creature.TypeFlags = (CreatureTypeFlags)fields.Read(40); creature.TypeFlags2 = fields.Read(41); creature.LootId = fields.Read(42); creature.PickPocketId = fields.Read(43); creature.SkinLootId = fields.Read(44); for (var i = (int)SpellSchools.Holy; i < (int)SpellSchools.Max; ++i) creature.Resistance[i] = fields.Read(45 + i - 1); for (var i = 0; i < SharedConst.MaxCreatureSpells; ++i) creature.Spells[i] = fields.Read(51 + i); creature.VehicleId = fields.Read(59); creature.MinGold = fields.Read(60); creature.MaxGold = fields.Read(61); creature.AIName = fields.Read(62); creature.MovementType = fields.Read(63); creature.InhabitType = (InhabitType)fields.Read(64); creature.HoverHeight = fields.Read(65); creature.ModHealth = fields.Read(66); creature.ModHealthExtra = fields.Read(67); creature.ModMana = fields.Read(68); creature.ModManaExtra = fields.Read(69); creature.ModArmor = fields.Read(70); creature.ModDamage = fields.Read(71); creature.ModExperience = fields.Read(72); creature.RacialLeader = fields.Read(73); creature.MovementId = fields.Read(74); creature.RegenHealth = fields.Read(75); creature.MechanicImmuneMask = fields.Read(76); creature.FlagsExtra = (CreatureFlagsExtra)fields.Read(77); creature.ScriptID = GetScriptId(fields.Read(78)); 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(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(1); creatureAddon.mount = result.Read(2); creatureAddon.bytes1 = result.Read(3); creatureAddon.bytes2 = result.Read(4); creatureAddon.emote = result.Read(5); creatureAddon.aiAnimKit = result.Read(6); creatureAddon.movementAnimKit = result.Read(7); creatureAddon.meleeAnimKit = result.Read(8); var tokens = new StringArray(result.Read(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(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(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(2); creatureAddon.bytes1 = result.Read(3); creatureAddon.bytes2 = result.Read(4); creatureAddon.emote = result.Read(5); creatureAddon.aiAnimKit = result.Read(6); creatureAddon.movementAnimKit = result.Read(7); creatureAddon.meleeAnimKit = result.Read(8); var tokens = new StringArray(result.Read(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(0); uint item = result.Read(1); uint idx = result.Read(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(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(1); EquipmentInfo equipmentInfo = new EquipmentInfo(); for (var i = 0; i < SharedConst.MaxEquipmentItems; ++i) { equipmentInfo.Items[i].ItemId = result.Read(2 + i * 3); equipmentInfo.Items[i].AppearanceModId = result.Read(3 + i * 3); equipmentInfo.Items[i].ItemVisual = result.Read(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.ItemAppearanceModifierID; 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(0); byte _class = result.Read(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(2); stats.BaseArmor = result.Read(3); stats.AttackPower = result.Read(4); stats.RangedAttackPower = result.Read(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(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(1); modelInfo.CombatReach = result.Read(2); modelInfo.DisplayIdOtherGender = result.Read(3); modelInfo.gender = 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 LoadCreatureScalingData() { uint oldMSTime = Time.GetMSTime(); // 0 1 2 3 4 SQLResult result = DB.World.Query("SELECT Entry, LevelScalingMin, LevelScalingMax, LevelScalingDeltaMin, LevelScalingDeltaMax FROM creature_template_scaling"); if (result.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creature template scaling definitions. DB table `creature_template_scaling` is empty."); return; } uint count = 0; do { uint entry = result.Read(0); var template = creatureTemplateStorage.LookupByKey(entry); if (template == null) { Log.outError(LogFilter.Sql, $"Creature template (Entry: {entry}) does not exist but has a record in `creature_template_scaling`"); continue; } CreatureLevelScaling creatureLevelScaling; creatureLevelScaling.MinLevel = result.Read(1); creatureLevelScaling.MaxLevel = result.Read(2); creatureLevelScaling.DeltaLevelMin = result.Read(3); creatureLevelScaling.DeltaLevelMax = result.Read(3); template.levelScaling.Set(creatureLevelScaling); ++count; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} creature template scaling data in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } 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}). This can lead to crashes, set to faction 35", cInfo.Entry, cInfo.Faction); cInfo.Faction = 35; } // 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(0); ulong linkedGuidLow = result.Read(1); byte linkType = result.Read(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(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(1 + i); npcText.Data[i].BroadcastTextID = result.Read(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 spellsByTrainer = new MultiMap(); 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(0); spell.SpellId = trainerSpellsResult.Read(1); spell.MoneyCost = trainerSpellsResult.Read(2); spell.ReqSkillLine = trainerSpellsResult.Read(3); spell.ReqSkillRank = trainerSpellsResult.Read(4); spell.ReqAbility[0] = trainerSpellsResult.Read(5); spell.ReqAbility[1] = trainerSpellsResult.Read(6); spell.ReqAbility[2] = trainerSpellsResult.Read(7); spell.ReqLevel = trainerSpellsResult.Read(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; spell.LearnedSpellId = spell.SpellId; foreach (SpellEffectInfo spellEffect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) { if (spellEffect != null && spellEffect.IsEffect(SpellEffectName.LearnSpell)) { Contract.Assert(spell.LearnedSpellId == spell.SpellId, $"Only one learned spell is currently supported - spell {spell.SpellId} already teaches {spell.LearnedSpellId} but it tried to overwrite it with {spellEffect.TriggerSpell}"); spell.LearnedSpellId = 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(0); TrainerType trainerType = (TrainerType)trainersResult.Read(1); string greeting = trainersResult.Read(2); List spells = new List(); 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(0); string localeName = trainerLocalesResult.Read(1); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; Trainer trainer = _trainers.LookupByKey(trainerId); if (trainer != null) trainer.AddGreetingLocale(locale, trainerLocalesResult.Read(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 LoadCreatureDefaultTrainers() { uint oldMSTime = Time.GetMSTime(); _creatureDefaultTrainers.Clear(); SQLResult result = DB.World.Query("SELECT CreatureId, TrainerId FROM creature_default_trainer"); if (!result.IsEmpty()) { do { uint creatureId = result.Read(0); uint trainerId = result.Read(1); if (GetCreatureTemplate(creatureId) == null) { Log.outError(LogFilter.Sql, $"Table `creature_default_trainer` references non-existing creature template (CreatureId: {creatureId}), ignoring"); continue; } if (GetTrainer(trainerId) == null) { Log.outError(LogFilter.Sql, $"Table `creature_default_trainer` references non-existing trainer (TrainerId: {trainerId}) for CreatureId {creatureId}, ignoring"); continue; } _creatureDefaultTrainers[creatureId] = trainerId; } while (result.NextRow()); } Log.outInfo(LogFilter.ServerLoading, $"Loaded {_creatureDefaultTrainers.Count} default trainers in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } public void LoadVendors() { var time = Time.GetMSTime(); // For reload case cacheVendorItemStorage.Clear(); List skipvendors = new List(); SQLResult result = DB.World.Query("SELECT entry, item, maxcount, incrtime, ExtendedCost, type, BonusListIDs, PlayerConditionID, IgnoreFiltering 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(0); int itemid = result.Read(1); // if item is a negative, its a reference if (itemid < 0) count += LoadReferenceVendor((int)entry, -itemid, skipvendors); else { VendorItem vItem = new VendorItem(); vItem.item = (uint)itemid; vItem.maxcount = result.Read(2); vItem.incrtime = result.Read(3); vItem.ExtendedCost = result.Read(4); vItem.Type = (ItemVendorType)result.Read(5); vItem.PlayerConditionId = result.Read(7); vItem.IgnoreFiltering = result.Read(8); var bonusListIDsTok = new StringArray(result.Read(6), ' '); if (!bonusListIDsTok.IsEmpty()) { foreach (string token in bonusListIDsTok) { if (uint.TryParse(token, out uint id)) vItem.BonusListIDs.Add(id); } } if (!IsVendorItemValid(entry, vItem, null, skipvendors)) continue; if (cacheVendorItemStorage.LookupByKey(entry) == null) cacheVendorItemStorage.Add(entry, new VendorItemData()); cacheVendorItemStorage[entry].AddItem(vItem); ++count; } } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Vendors in {1} ms", count, Time.GetMSTimeDiffToNow(time)); } uint LoadReferenceVendor(int vendor, int item, List skip_vendors) { // find all items from the reference vendor PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_NPC_VENDOR_REF); stmt.AddValue(0, item); SQLResult result = DB.World.Query(stmt); if (result.IsEmpty()) return 0; uint count = 0; do { int item_id = result.Read(0); // if item is a negative, its a reference if (item_id < 0) count += LoadReferenceVendor(vendor, -item_id, skip_vendors); else { VendorItem vItem = new VendorItem(); vItem.item = (uint)item_id; vItem.maxcount = result.Read(1); vItem.incrtime = result.Read(2); vItem.ExtendedCost = result.Read(3); vItem.Type = (ItemVendorType)result.Read(4); vItem.PlayerConditionId = result.Read(6); vItem.IgnoreFiltering = result.Read(7); var bonusListIDsTok = new StringArray(result.Read(5), ' '); if (!bonusListIDsTok.IsEmpty()) { foreach (string token in bonusListIDsTok) { if (uint.TryParse(token, out uint id)) vItem.BonusListIDs.Add(id); } } if (!IsVendorItemValid((uint)vendor, vItem, null, skip_vendors)) continue; VendorItemData vList = cacheVendorItemStorage.LookupByKey((uint)vendor); if (vList == null) continue; vList.AddItem(vItem); ++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 26 27 "creature.dynamicflags, creature.phaseUseFlags, creature.phaseid, creature.phasegroup, creature.terrainSwapMap, 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 spawnMasks = new Dictionary(); 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] |= (1ul << (int)difficultyPair.Key); } } PhaseShift phaseShift = new PhaseShift(); uint count = 0; do { ulong guid = result.Read(0); uint entry = result.Read(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(2); data.displayid = result.Read(3); data.equipmentId = result.Read(4); data.posX = result.Read(5); data.posY = result.Read(6); data.posZ = result.Read(7); data.orientation = result.Read(8); data.spawntimesecs = result.Read(9); data.spawndist = result.Read(10); data.currentwaypoint = result.Read(11); data.curhealth = result.Read(12); data.curmana = result.Read(13); data.movementType = result.Read(14); data.spawnMask = result.Read(15); short gameEvent = result.Read(16); uint PoolId = result.Read(17); data.npcflag = result.Read(18); data.unit_flags = result.Read(19); data.unit_flags2 = result.Read(20); data.unit_flags3 = result.Read(21); data.dynamicflags = result.Read(22); data.phaseUseFlags = (PhaseUseFlagsValues)result.Read(23); data.phaseId = result.Read(24); data.phaseGroup = result.Read(25); data.terrainSwapMap = result.Read(26); data.ScriptId = GetScriptId(result.Read(27)); 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); } if (Convert.ToBoolean(data.phaseUseFlags & ~PhaseUseFlagsValues.All)) { Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) has unknown `phaseUseFlags` set, removed unknown value.", guid, data.id); data.phaseUseFlags &= PhaseUseFlagsValues.All; } if (data.phaseUseFlags.HasAnyFlag(PhaseUseFlagsValues.AlwaysVisible) && data.phaseUseFlags.HasAnyFlag(PhaseUseFlagsValues.Inverse)) { Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) has both `phaseUseFlags` PHASE_USE_FLAGS_ALWAYS_VISIBLE and PHASE_USE_FLAGS_INVERSE," + " removing PHASE_USE_FLAGS_INVERSE.", guid, data.id); data.phaseUseFlags &= ~PhaseUseFlagsValues.Inverse; } 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 (data.terrainSwapMap != -1) { MapRecord terrainSwapEntry = CliDB.MapStorage.LookupByKey(data.terrainSwapMap); if (terrainSwapEntry == null) { Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `terrainSwapMap` {2} does not exist, set to -1", guid, data.id, data.terrainSwapMap); data.terrainSwapMap = -1; } else if (terrainSwapEntry.ParentMapID != data.mapid) { Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `terrainSwapMap` {2} which cannot be used on spawn map, set to -1", guid, data.id, data.terrainSwapMap); data.terrainSwapMap = -1; } } if (WorldConfig.GetBoolValue(WorldCfg.CalculateCreatureZoneAreaData)) { uint zoneId = 0; uint areaId = 0; PhasingHandler.InitDbVisibleMapId(phaseShift, data.terrainSwapMap); Global.MapMgr.GetZoneAndAreaId(phaseShift, 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) { ulong 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) { ulong 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 = (int)SpawnMask.Continent; 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 = Creature.CreateCreatureFromDB(guid, map); if (!creature) { Log.outError(LogFilter.Server, "AddCreature: Cannot add creature entry {0} to map", entry); return 0; } } return guid; } public List 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 uint GetCreatureDefaultTrainer(uint creatureId) { return _creatureDefaultTrainers.LookupByKey(creatureId); } public Dictionary 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.URand(0, 1) == 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.TypeID; go.displayId = db2go.DisplayID; go.name = db2go.Name[Global.WorldMgr.GetDefaultDbcLocale()]; go.size = db2go.Scale; unsafe { fixed (int* b = go.Raw.data) { for (byte x = 0; x < db2go.PropValue.Length; ++x) b[x] = db2go.PropValue[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(0); GameObjectTemplate got = new GameObjectTemplate(); got.entry = entry; got.type = (GameObjectTypes)result.Read(1); got.displayId = result.Read(2); got.name = result.Read(3); got.IconName = result.Read(4); got.castBarCaption = result.Read(5); got.unk1 = result.Read(6); got.size = result.Read(7); unsafe { fixed (int* b = got.Raw.data) { for (byte x = 0; x < SharedConst.MaxGOData; ++x) b[x] = result.Read(8 + x); } } got.RequiredLevel = result.Read(41); got.AIName = result.Read(42); got.ScriptId = Global.ObjectMgr.GetScriptId(result.Read(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 5 SQLResult result = DB.World.Query("SELECT entry, faction, flags, mingold, maxgold, WorldEffectID 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(0); GameObjectTemplate got = Global.ObjectMgr.GetGameObjectTemplate(entry); if (got == null) { Log.outError(LogFilter.Sql, $"GameObject template (Entry: {entry}) does not exist but has a record in `gameobject_template_addon`"); continue; } GameObjectTemplateAddon gameObjectAddon = new GameObjectTemplateAddon(); gameObjectAddon.faction = result.Read(1); gameObjectAddon.flags = result.Read(2); gameObjectAddon.mingold = result.Read(3); gameObjectAddon.maxgold = result.Read(4); gameObjectAddon.WorldEffectID = result.Read(5); // checks if (gameObjectAddon.faction != 0 && !CliDB.FactionTemplateStorage.ContainsKey(gameObjectAddon.faction)) Log.outError(LogFilter.Sql, $"GameObject (Entry: {entry}) has invalid faction ({gameObjectAddon.faction}) defined in `gameobject_template_addon`."); if (gameObjectAddon.maxgold > 0) { switch (got.type) { case GameObjectTypes.Chest: case GameObjectTypes.FishingHole: break; default: Log.outError(LogFilter.Sql, $"GameObject (Entry {entry} GoType: {got.type}) cannot be looted but has maxgold set in `gameobject_template_addon`."); break; } } if (gameObjectAddon.WorldEffectID != 0 && !CliDB.WorldEffectStorage.ContainsKey(gameObjectAddon.WorldEffectID)) { Log.outError(LogFilter.Sql, $"GameObject (Entry: {entry}) has invalid WorldEffectID ({gameObjectAddon.WorldEffectID}) defined in `gameobject_template_addon`, set to 0."); gameObjectAddon.WorldEffectID = 0; } _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 20 21 "phaseUseFlags, phaseid, phasegroup, terrainSwapMap, 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 spawnMasks = new Dictionary(); 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] |= (1ul << (int)difficultyPair.Key); } } PhaseShift phaseShift = new PhaseShift(); do { ulong guid = result.Read(0); uint entry = result.Read(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(2); data.posX = result.Read(3); data.posY = result.Read(4); data.posZ = result.Read(5); data.orientation = result.Read(6); data.rotation.X = result.Read(7); data.rotation.Y = result.Read(8); data.rotation.Z = result.Read(9); data.rotation.W = result.Read(10); data.spawntimesecs = result.Read(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(12); data.artKit = 0; uint gostate = result.Read(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(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(15); uint PoolId = result.Read(16); data.phaseUseFlags = (PhaseUseFlagsValues)result.Read(17); data.phaseId = result.Read(18); data.phaseGroup = result.Read(19); if (Convert.ToBoolean(data.phaseUseFlags & ~PhaseUseFlagsValues.All)) { Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) has unknown `phaseUseFlags` set, removed unknown value.", guid, data.id); data.phaseUseFlags &= PhaseUseFlagsValues.All; } if (data.phaseUseFlags.HasAnyFlag(PhaseUseFlagsValues.AlwaysVisible) && data.phaseUseFlags.HasAnyFlag(PhaseUseFlagsValues.Inverse)) { Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) has both `phaseUseFlags` PHASE_USE_FLAGS_ALWAYS_VISIBLE and PHASE_USE_FLAGS_INVERSE," + " removing PHASE_USE_FLAGS_INVERSE.", guid, data.id); data.phaseUseFlags &= ~PhaseUseFlagsValues.Inverse; } 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.terrainSwapMap = result.Read(20); if (data.terrainSwapMap != -1) { MapRecord terrainSwapEntry = CliDB.MapStorage.LookupByKey(data.terrainSwapMap); if (terrainSwapEntry == null) { Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `terrainSwapMap` {2} does not exist, set to -1", guid, data.id, data.terrainSwapMap); data.terrainSwapMap = -1; } else if (terrainSwapEntry.ParentMapID != data.mapid) { Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `terrainSwapMap` {2} which cannot be used on spawn map, set to -1", guid, data.id, data.terrainSwapMap); data.terrainSwapMap = -1; } } data.ScriptId = GetScriptId(result.Read(21)); 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 ({2}) 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 ({2}) 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 ({2}) 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; } if (WorldConfig.GetBoolValue(WorldCfg.CalculateGameobjectZoneAreaData)) { uint zoneId = 0; uint areaId = 0; PhasingHandler.InitDbVisibleMapId(phaseShift, data.terrainSwapMap); Global.MapMgr.GetZoneAndAreaId(phaseShift, 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 7 SQLResult result = DB.World.Query("SELECT guid, parent_rotation0, parent_rotation1, parent_rotation2, parent_rotation3, invisibilityType, invisibilityValue, WorldEffectID 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(0); GameObjectData goData = GetGOData(guid); if (goData == null) { Log.outError(LogFilter.Sql, $"GameObject (GUID: {guid}) does not exist but has a record in `gameobject_addon`"); continue; } GameObjectAddon gameObjectAddon = new GameObjectAddon(); gameObjectAddon.ParentRotation = new Quaternion(result.Read(1), result.Read(2), result.Read(3), result.Read(4)); gameObjectAddon.invisibilityType = (InvisibilityType)result.Read(5); gameObjectAddon.invisibilityValue = result.Read(6); gameObjectAddon.WorldEffectID = result.Read(7); if (gameObjectAddon.invisibilityType >= InvisibilityType.Max) { Log.outError(LogFilter.Sql, $"GameObject (GUID: {guid}) has invalid InvisibilityType in `gameobject_addon`, disabled invisibility"); gameObjectAddon.invisibilityType = InvisibilityType.General; gameObjectAddon.invisibilityValue = 0; } if (gameObjectAddon.invisibilityType != 0 && gameObjectAddon.invisibilityValue == 0) { Log.outError(LogFilter.Sql, $"GameObject (GUID: {guid}) has InvisibilityType set but has no InvisibilityValue in `gameobject_addon`, set to 1"); gameObjectAddon.invisibilityValue = 1; } if (!gameObjectAddon.ParentRotation.isUnit()) { Log.outError(LogFilter.Sql, $"GameObject (GUID: {guid}) has invalid parent rotation in `gameobject_addon`, set to default"); gameObjectAddon.ParentRotation = Quaternion.WAxis; } if (gameObjectAddon.WorldEffectID != 0 && !CliDB.WorldEffectStorage.ContainsKey(gameObjectAddon.WorldEffectID)) { Log.outError(LogFilter.Sql, $"GameObject (GUID: {guid}) has invalid WorldEffectID ({gameObjectAddon.WorldEffectID}) in `gameobject_addon`, set to 0."); gameObjectAddon.WorldEffectID = 0; } _gameObjectAddonStorage[guid] = gameObjectAddon; ++count; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} gameobject addons in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } 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(0); uint item = result.Read(1); uint idx = result.Read(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) { ulong 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) { ulong 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 = (int)SpawnMask.Continent; data.go_state = GameObjectState.Ready; 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 = GameObject.CreateGameObjectFromDB(guid, map); if (!go) { 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 GetGameObjectQuestItemList(uint id) { return _gameObjectQuestItemStorage.LookupByKey(id); } MultiMap 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 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.ClassID, db2Data.SubclassID, sparse.inventoryType, (ItemQuality)sparse.OverallQualityID, sparse.ItemLevel); 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.SpecializationID); 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); } // Load item effects (spells) foreach (var effectEntry in CliDB.ItemEffectStorage.Values) { var itemTemplate = ItemTemplateStorage.LookupByKey(effectEntry.ParentItemID); if (itemTemplate == null) continue; itemTemplate.Effects.Add(effectEntry); } // Check if item templates for DBC referenced character start outfit are present List notFoundOutfit = new List(); 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)); } 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(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(3); uint maxMoneyLoot = result.Read(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(1); itemTemplate.FoodType = result.Read(2); itemTemplate.MinMoneyLoot = minMoneyLoot; itemTemplate.MaxMoneyLoot = maxMoneyLoot; itemTemplate.SpellPPMRate = result.Read(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(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(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 GetItemTemplates() { return ItemTemplateStorage; } public Trainer GetTrainer(uint trainerId) { return _trainers.LookupByKey(trainerId); } public void AddVendorItem(uint entry, VendorItem vItem, bool persist = true) { VendorItemData vList = cacheVendorItemStorage[entry]; vList.AddItem(vItem); if (persist) { PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.INS_NPC_VENDOR); stmt.AddValue(0, entry); stmt.AddValue(1, vItem.item); stmt.AddValue(2, vItem.maxcount); stmt.AddValue(3, vItem.incrtime); stmt.AddValue(4, vItem.ExtendedCost); stmt.AddValue(5, vItem.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, VendorItem vItem, Player player = null, List 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 ((vItem.Type == ItemVendorType.Item && GetItemTemplate(vItem.item) == null) || (vItem.Type == ItemVendorType.Currency && CliDB.CurrencyTypesStorage.LookupByKey(vItem.item) == null)) { if (player != null) player.SendSysMessage(CypherStrings.ItemNotFound, vItem.item, vItem.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, vItem.item, vItem.Type); return false; } if (vItem.PlayerConditionId != 0 && !CliDB.PlayerConditionStorage.ContainsKey(vItem.PlayerConditionId)) { Log.outError(LogFilter.Sql, "Table `(game_event_)npc_vendor` has Item (Entry: {0}) with invalid PlayerConditionId ({1}) for vendor ({2}), ignore", vItem.item, vItem.PlayerConditionId, vendorentry); return false; } if (vItem.ExtendedCost != 0 && !CliDB.ItemExtendedCostStorage.ContainsKey(vItem.ExtendedCost)) { if (player != null) player.SendSysMessage(CypherStrings.ExtendedCostNotExist, vItem.ExtendedCost); else Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` have Item (Entry: {0}) with wrong ExtendedCost ({1}) for vendor ({2}), ignore", vItem.item, vItem.ExtendedCost, vendorentry); return false; } if (vItem.Type == ItemVendorType.Item) // not applicable to currencies { if (vItem.maxcount > 0 && vItem.incrtime == 0) { if (player != null) player.SendSysMessage("MaxCount != 0 ({0}) but IncrTime == 0", vItem.maxcount); else Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` has `maxcount` ({0}) for item {1} of vendor (Entry: {2}) but `incrtime`=0, ignore", vItem.maxcount, vItem.item, vendorentry); return false; } else if (vItem.maxcount == 0 && vItem.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", vItem.item, vendorentry); return false; } foreach (uint bonusList in vItem.BonusListIDs) { if (Global.DB2Mgr.GetItemBonusList(bonusList) == null) { Log.outError(LogFilter.Sql, "Table `(game_event_)npc_vendor` have Item (Entry: {0}) with invalid bonus {1} for vendor ({2}), ignore", vItem.item, bonusList, vendorentry); return false; } } } VendorItemData vItems = GetNpcVendorItemList(vendorentry); if (vItems == null) return true; // later checks for non-empty lists if (vItems.FindItemCostPair(vItem.item, vItem.ExtendedCost, vItem.Type) != null) { if (player != null) player.SendSysMessage(CypherStrings.ItemAlreadyInList, vItem.item, vItem.ExtendedCost, vItem.Type); else Log.outError(LogFilter.Sql, "Table `npcvendor` has duplicate items {0} (with extended cost {1}, type {2}) for vendor (Entry: {3}), ignoring", vItem.item, vItem.ExtendedCost, vItem.Type, vendorentry); return false; } if (vItem.Type == ItemVendorType.Currency && vItem.maxcount == 0) { Log.outError(LogFilter.Sql, "Table `(game_event_)npc_vendor` have Item (Entry: {0}, type: {1}) with missing maxcount for vendor ({2}), ignore", vItem.item, vItem.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(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(3); instanceTemplate.Parent = result.Read(1); instanceTemplate.ScriptId = Global.ObjectMgr.GetScriptId(result.Read(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(0); GameTele gt = new GameTele(); gt.posX = result.Read(1); gt.posY = result.Read(2); gt.posZ = result.Read(3); gt.orientation = result.Read(4); gt.mapId = result.Read(5); gt.name = result.Read(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(0); uint PortLocID = result.Read(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(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(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(2); ar.levelMax = result.Read(3); ar.item = result.Read(4); ar.item2 = result.Read(5); ar.quest_A = result.Read(6); ar.quest_H = result.Read(7); ar.achievement = result.Read(8); ar.questFailedText = result.Read(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> dungeonLastBosses = new Dictionary>(); do { uint entry = result.Read(0); EncounterCreditType creditType = (EncounterCreditType)result.Read(1); uint creditEntry = result.Read(2); uint lastEncounterDungeon = result.Read(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 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(0); uint currentclass = result.Read(1); uint mapId = result.Read(2); uint zoneId = result.Read(3); float positionX = result.Read(4); float positionY = result.Read(5); float positionZ = result.Read(6); float orientation = result.Read(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(0); if (currentrace >= (int)Race.Max) { Log.outError(LogFilter.Sql, "Wrong race {0} in `playercreateinfo_item` table, ignoring.", currentrace); continue; } uint currentclass = result.Read(1); if (currentclass >= (int)Class.Max) { Log.outError(LogFilter.Sql, "Wrong class {0} in `playercreateinfo_item` table, ignoring.", currentclass); continue; } uint itemid = result.Read(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(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((1L << (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(0); uint classMask = result.Read(1); uint spellId = result.Read(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(0); uint classMask = result.Read(1); uint spellId = result.Read(2); if (raceMask != 0 && !raceMask.HasAnyFlag((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)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(0); if (currentrace >= (int)Race.Max) { Log.outError(LogFilter.Sql, "Wrong race {0} in `playercreateinfo_action` table, ignoring.", currentrace); continue; } uint currentclass = result.Read(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(2), result.Read(3), result.Read(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(0); if (currentrace >= (int)Race.Max) { Log.outError(LogFilter.Sql, "Wrong race {0} in `player_levelstats` table, ignoring.", currentrace); continue; } uint currentclass = result.Read(1); if (currentclass >= (int)Class.Max) { Log.outError(LogFilter.Sql, "Wrong class {0} in `player_levelstats` table, ignoring.", currentclass); continue; } uint currentlevel = result.Read(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(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(0); uint currentxp = result.Read(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(0); if (GetCreatureTemplate(creatureid) == null) { Log.outError(LogFilter.Sql, "Wrong creature id {0} in `pet_levelstats` table, ignoring.", creatureid); continue; } uint currentlevel = result.Read(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(2); pLevelInfo.mana = result.Read(3); pLevelInfo.armor = result.Read(9); for (int i = 0; i < (int)Stats.Max; i++) { pLevelInfo.stats[i] = result.Read(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(0); uint entry = result.Read(1); bool half = result.Read(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(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(0); uint horde = result.Read(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(0); uint horde = result.Read(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(0); uint horde = result.Read(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(0); uint horde = result.Read(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(0); uint horde = result.Read(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(0); uint horde = result.Read(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 11 "ID, QuestType, QuestLevel, MaxScalingLevel, QuestPackageID, MinLevel, QuestSortID, QuestInfoID, SuggestedGroupNum, RewardNextQuest, RewardXPDifficulty, RewardXPMultiplier, " + //12 13 14 15 16 17 18 19 20 21 22 "RewardMoney, RewardMoneyDifficulty, RewardMoneyMultiplier, RewardBonusMoney, RewardDisplaySpell1, RewardDisplaySpell2, RewardDisplaySpell3, RewardSpell, RewardHonor, RewardKillHonor, StartItem, " + //23 24 25 26 27 "RewardArtifactXPDifficulty, RewardArtifactXPMultiplier, RewardArtifactCategoryID, Flags, FlagsEx, " + //28 29 30 31 32 33 34 35 "RewardItem1, RewardAmount1, ItemDrop1, ItemDropQuantity1, RewardItem2, RewardAmount2, ItemDrop2, ItemDropQuantity2, " + //36 37 38 39 40 41 42 43 "RewardItem3, RewardAmount3, ItemDrop3, ItemDropQuantity3, RewardItem4, RewardAmount4, ItemDrop4, ItemDropQuantity4, " + //44 45 46 47 48 49 "RewardChoiceItemID1, RewardChoiceItemQuantity1, RewardChoiceItemDisplayID1, RewardChoiceItemID2, RewardChoiceItemQuantity2, RewardChoiceItemDisplayID2, " + //50 51 52 53 54 55 "RewardChoiceItemID3, RewardChoiceItemQuantity3, RewardChoiceItemDisplayID3, RewardChoiceItemID4, RewardChoiceItemQuantity4, RewardChoiceItemDisplayID4, " + //56 57 58 59 60 61 "RewardChoiceItemID5, RewardChoiceItemQuantity5, RewardChoiceItemDisplayID5, RewardChoiceItemID6, RewardChoiceItemQuantity6, RewardChoiceItemDisplayID6, " + //62 63 64 65 66 67 68 69 70 71 "POIContinent, POIx, POIy, POIPriority, RewardTitle, RewardArenaPoints, RewardSkillLineID, RewardNumSkillUps, PortraitGiver, PortraitTurnIn, " + //72 73 74 75 76 77 78 79 "RewardFactionID1, RewardFactionValue1, RewardFactionOverride1, RewardFactionCapIn1, RewardFactionID2, RewardFactionValue2, RewardFactionOverride2, RewardFactionCapIn2, " + //80 81 82 83 84 85 86 87 "RewardFactionID3, RewardFactionValue3, RewardFactionOverride3, RewardFactionCapIn3, RewardFactionID4, RewardFactionValue4, RewardFactionOverride4, RewardFactionCapIn4, " + //88 89 90 91 92 "RewardFactionID5, RewardFactionValue5, RewardFactionOverride5, RewardFactionCapIn5, RewardFactionFlags, " + //93 94 95 96 97 98 99 100 "RewardCurrencyID1, RewardCurrencyQty1, RewardCurrencyID2, RewardCurrencyQty2, RewardCurrencyID3, RewardCurrencyQty3, RewardCurrencyID4, RewardCurrencyQty4, " + //101 102 103 104 105 106 107 "AcceptedSoundKitID, CompleteSoundKitID, AreaGroupID, TimeAllowed, AllowableRaces, QuestRewardID, Expansion, " + //108 109 110 111 112 113 114 115 116 "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(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(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(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 "RequiredSkillID, RequiredSkillPoints, RequiredMinRepFaction, RequiredMaxRepFaction, RequiredMinRepValue, RequiredMaxRepValue, ProvidedItemCount, RewardMailSenderEntry, " + //17 18 "SpecialFlags, ScriptName 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(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(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(0); uint oID = result.Read(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(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 usedMailTemplates = new Dictionary(); // 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 (qinfo.AllowableRaces > 0 && !Convert.ToBoolean(qinfo.AllowableRaces & (long)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 map, MultiMap 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(0); uint quest = result.Read(1); uint poolId = result.Read(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 14 SQLResult result = DB.World.Query("SELECT QuestID, BlobIndex, Idx1, ObjectiveIndex, QuestObjectiveID, QuestObjectID, MapID, WorldMapAreaId, Floor, Priority, Flags, WorldEffectID, PlayerConditionID, WoDUnk1, AlwaysAllowMergingBlobs 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> POIs = new Dictionary>(); if (!points.IsEmpty()) { do { uint questId = points.Read(0); int Idx1 = points.Read(1); int x = points.Read(2); int y = points.Read(3); if (!POIs.ContainsKey(questId)) POIs[questId] = new MultiMap(); QuestPOIPoint point = new QuestPOIPoint(x, y); POIs[questId].Add(Idx1, point); } while (points.NextRow()); } do { uint QuestID = Convert.ToUInt32(result.Read(0)); int BlobIndex = result.Read(1); int Idx1 = result.Read(2); int ObjectiveIndex = result.Read(3); int QuestObjectiveID = result.Read(4); int QuestObjectID = result.Read(5); int MapID = result.Read(6); int WorldMapAreaId = result.Read(7); int Floor = result.Read(8); int Priority = result.Read(9); int Flags = result.Read(10); int WorldEffectID = result.Read(11); int PlayerConditionID = result.Read(12); int WoDUnk1 = result.Read(13); bool AlwaysAllowMergingBlobs = result.Read(14); 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, AlwaysAllowMergingBlobs); 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(0); uint quest_ID = result.Read(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(); for (var i = 0; i < 2; ++i) _questGreetingStorage[i] = new Dictionary(); // 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; } uint count = 0; do { uint id = result.Read(0); byte type = result.Read(1); switch (type) { case 0: // Creature 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 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(2); uint greetEmoteDelay = result.Read(3); string greeting = result.Read(4); _questGreetingStorage[type][id] = new QuestGreeting(greetEmoteType, greetEmoteDelay, greeting); count++; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} quest_greeting in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } public Quest GetQuestTemplate(uint questId) { return _questTemplates.LookupByKey(questId); } public Dictionary GetQuestTemplates() { return _questTemplates; } public Dictionary GetQuestStorage() { return _questTemplates; } public MultiMap GetGOQuestRelationMap() { return _goQuestRelations; } public List GetGOQuestRelationBounds(uint go_entry) { return _goQuestRelations.LookupByKey(go_entry); } public List GetGOQuestInvolvedRelationBounds(uint go_entry) { return _goQuestInvolvedRelations.LookupByKey(go_entry); } public List GetGOQuestInvolvedRelationReverseBounds(uint questId) { return _goQuestInvolvedRelationsReverse.LookupByKey(questId); } public MultiMap GetCreatureQuestRelationMap() { return _creatureQuestRelations; } public List GetCreatureQuestRelationBounds(uint creature_entry) { return _creatureQuestRelations.LookupByKey(creature_entry); } public List GetCreatureQuestInvolvedRelationBounds(uint creature_entry) { return _creatureQuestInvolvedRelations.LookupByKey(creature_entry); } public List GetCreatureQuestInvolvedRelationReverseBounds(uint questId) { return _creatureQuestInvolvedRelationsReverse.LookupByKey(questId); } public List GetQuestPOIList(uint questId) { return _questPOIStorage.LookupByKey(questId); } public QuestObjective GetQuestObjective(uint questObjectiveId) { return _questObjectives.LookupByKey(questObjectiveId); } public List GetQuestsForAreaTrigger(uint triggerId) { return _questAreaTriggerStorage.LookupByKey(triggerId); } public QuestGreeting GetQuestGreeting(TypeId type, uint id) { byte typeIndex; if (type == TypeId.Unit) typeIndex = 0; else if (type == TypeId.GameObject) typeIndex = 1; else return null; return _questGreetingStorage[typeIndex].LookupByKey(id); } public QuestGreetingLocale GetQuestGreetingLocale(TypeId type, uint id) { byte typeIndex; if (type == TypeId.Unit) typeIndex = 0; else if (type == TypeId.GameObject) typeIndex = 1; else return null; return _questGreetingLocaleStorage[typeIndex].LookupByKey(id); } //Spells /Skills / Phases public void LoadPhases() { foreach (PhaseRecord phase in CliDB.PhaseStorage.Values) _phaseInfoById.Add(phase.Id, new PhaseInfoStruct(phase.Id)); foreach (MapRecord map in CliDB.MapStorage.Values) if (map.ParentMapID != -1) _terrainSwapInfoById.Add(map.Id, new TerrainSwapInfo(map.Id)); Log.outInfo(LogFilter.ServerLoading, "Loading Terrain World Map definitions..."); LoadTerrainWorldMaps(); Log.outInfo(LogFilter.ServerLoading, "Loading Terrain Swap Default definitions..."); LoadTerrainSwapDefaults(); Log.outInfo(LogFilter.ServerLoading, "Loading Phase Area definitions..."); LoadAreaPhases(); } public void UnloadPhaseConditions() { foreach (var pair in _phaseInfoByArea) pair.Value.Conditions.Clear(); } void LoadTerrainWorldMaps() { 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(0); uint worldMapArea = result.Read(1); if (!CliDB.MapStorage.ContainsKey(mapId)) { Log.outError(LogFilter.Sql, "TerrainSwapMap {0} defined in `terrain_worldmap` does not exist, skipped.", mapId); continue; } if (!CliDB.WorldMapAreaStorage.ContainsKey(worldMapArea)) { Log.outError(LogFilter.Sql, "WorldMapArea {0} defined in `terrain_worldmap` does not exist, skipped.", worldMapArea); continue; } if (!_terrainSwapInfoById.ContainsKey(mapId)) _terrainSwapInfoById.Add(mapId, new TerrainSwapInfo()); TerrainSwapInfo terrainSwapInfo = _terrainSwapInfoById[mapId]; terrainSwapInfo.Id = mapId; terrainSwapInfo.UiWorldMapAreaIDSwaps.Add(worldMapArea); ++count; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} terrain world maps in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); } void LoadTerrainSwapDefaults() { 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(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(1); if (!CliDB.MapStorage.ContainsKey(terrainSwap)) { Log.outError(LogFilter.Sql, "TerrainSwapMap {0} defined in `terrain_swap_defaults` does not exist, skipped.", terrainSwap); continue; } TerrainSwapInfo terrainSwapInfo = _terrainSwapInfoById[terrainSwap]; terrainSwapInfo.Id = terrainSwap; _terrainSwapInfoByMap[mapId].Add(terrainSwapInfo); ++count; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} terrain swap defaults in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); } void LoadAreaPhases() { 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; } PhaseInfoStruct getOrCreatePhaseIfMissing(uint phaseId) { PhaseInfoStruct phaseInfo = _phaseInfoById[phaseId]; phaseInfo.Id = phaseId; return phaseInfo; } uint count = 0; do { uint area = result.Read(0); uint phaseId = result.Read(1); if (!CliDB.AreaTableStorage.ContainsKey(area)) { Log.outError(LogFilter.Sql, $"Area {area} defined in `phase_area` does not exist, skipped."); continue; } if (!CliDB.PhaseStorage.ContainsKey(phaseId)) { Log.outError(LogFilter.Sql, $"Phase {phaseId} defined in `phase_area` does not exist, skipped."); continue; } PhaseInfoStruct phase = getOrCreatePhaseIfMissing(phaseId); phase.Areas.Add(area); _phaseInfoByArea[area].Add(new PhaseAreaInfo(phase)); ++count; } while (result.NextRow()); foreach (var pair in _phaseInfoByArea) { uint parentAreaId = pair.Key; do { AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(parentAreaId); if (area == null) break; parentAreaId = area.ParentAreaID; if (parentAreaId == 0) break; var parentAreaPhases = _phaseInfoByArea.LookupByKey(parentAreaId); foreach (PhaseAreaInfo parentAreaPhase in parentAreaPhases) if (parentAreaPhase.PhaseInfo.Id == pair.Value.PhaseInfo.Id) parentAreaPhase.SubAreaExclusions.Add(pair.Key); } while (true); } Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} phase areas in {Time.GetMSTimeDiffToNow(oldMSTime)} ms."); } 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(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(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(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(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(0); int skill = result.Read(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(0); SkillTiersEntry tier = new SkillTiersEntry(); for (int i = 0; i < SkillConst.MaxSkillStep; ++i) tier.Value[i] = result.Read(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 PhaseInfoStruct GetPhaseInfo(uint phaseId) { return _phaseInfoById.LookupByKey(phaseId); } public List GetPhasesForArea(uint areaId) { return _phaseInfoByArea.LookupByKey(areaId); } public TerrainSwapInfo GetTerrainSwapInfo(uint terrainSwapId) { return _terrainSwapInfoById.LookupByKey(terrainSwapId); } public List GetTerrainSwapsForMap(uint mapId) { return _terrainSwapInfoByMap.LookupByKey(mapId); } public List 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(0); string localeName = result.Read(1); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; if (!_creatureLocaleStorage.ContainsKey(id)) _creatureLocaleStorage[id] = new CreatureLocale(); CreatureLocale data = _creatureLocaleStorage[id]; AddLocaleString(result.Read(2), locale, data.Name); AddLocaleString(result.Read(3), locale, data.NameAlt); AddLocaleString(result.Read(4), locale, data.Title); AddLocaleString(result.Read(5), locale, data.TitleAlt); } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature locale strings in {1} ms", _creatureLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); } 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(0); string localeName = result.Read(1); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; if (!_gameObjectLocaleStorage.ContainsKey(id)) _gameObjectLocaleStorage[id] = new GameObjectLocale(); GameObjectLocale data = _gameObjectLocaleStorage[id]; AddLocaleString(result.Read(2), locale, data.Name); AddLocaleString(result.Read(3), locale, data.CastBarCaption); AddLocaleString(result.Read(4), 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(0); string localeName = result.Read(1); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; if (!_questTemplateLocaleStorage.ContainsKey(id)) _questTemplateLocaleStorage[id] = new QuestTemplateLocale(); QuestTemplateLocale data = _questTemplateLocaleStorage[id]; AddLocaleString(result.Read(2), locale, data.LogTitle); AddLocaleString(result.Read(3), locale, data.LogDescription); AddLocaleString(result.Read(4), locale, data.QuestDescription); AddLocaleString(result.Read(5), locale, data.AreaDescription); AddLocaleString(result.Read(6), locale, data.PortraitGiverText); AddLocaleString(result.Read(7), locale, data.PortraitGiverName); AddLocaleString(result.Read(8), locale, data.PortraitTurnInText); AddLocaleString(result.Read(9), locale, data.PortraitTurnInName); AddLocaleString(result.Read(10), 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(0); string localeName = result.Read(1); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; if (!_questObjectivesLocaleStorage.ContainsKey(id)) _questObjectivesLocaleStorage[id] = new QuestObjectivesLocale(); QuestObjectivesLocale data = _questObjectivesLocaleStorage[id]; AddLocaleString(result.Read(2), 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 LoadQuestGreetingLocales() { uint oldMSTime = Time.GetMSTime(); for (var i = 0; i < 2; ++i) _questGreetingLocaleStorage[i] = new Dictionary(); // 0 1 2 3 SQLResult result = DB.World.Query("SELECT Id, type, locale, Greeting FROM quest_greeting_locale"); if (result.IsEmpty()) return; uint count = 0; do { uint id = result.Read(0); byte type = result.Read(1); switch (type) { case 0: // Creature if (GetCreatureTemplate(id) == null) { Log.outError(LogFilter.Sql, $"Table `quest_greeting_locale`: creature template entry {id} does not exist."); continue; } break; case 1: // GameObject if (GetGameObjectTemplate(id) == null) { Log.outError(LogFilter.Sql, $"Table `quest_greeting_locale`: gameobject template entry {id} does not exist."); continue; } break; default: continue; } string localeName = result.Read(2); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; if (!_questGreetingLocaleStorage[type].ContainsKey(id)) _questGreetingLocaleStorage[type][id] = new QuestGreetingLocale(); QuestGreetingLocale data = _questGreetingLocaleStorage[type][id]; AddLocaleString(result.Read(3), locale, data.Greeting); ++count; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} Quest Greeting locale strings in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } 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(0); string localeName = result.Read(1); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; if (!_questOfferRewardLocaleStorage.ContainsKey(id)) _questOfferRewardLocaleStorage[id] = new QuestOfferRewardLocale(); QuestOfferRewardLocale data = _questOfferRewardLocaleStorage[id]; AddLocaleString(result.Read(2), 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(0); string localeName = result.Read(1); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; if (!_questRequestItemsLocaleStorage.ContainsKey(id)) _questRequestItemsLocaleStorage[id] = new QuestRequestItemsLocale(); QuestRequestItemsLocale data = _questRequestItemsLocaleStorage[id]; AddLocaleString(result.Read(2), 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 // 0 1 2 3 4 SQLResult result = DB.World.Query("SELECT MenuId, OptionIndex, Locale, OptionText, BoxText FROM gossip_menu_option_locale"); if (result.IsEmpty()) return; do { uint menuId = result.Read(0); uint optionIndex = result.Read(1); string localeName = result.Read(2); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; GossipMenuItemsLocale data = new GossipMenuItemsLocale(); AddLocaleString(result.Read(3), locale, data.OptionText); AddLocaleString(result.Read(4), locale, data.BoxText); _gossipMenuItemsLocaleStorage[Tuple.Create(menuId, optionIndex)] = 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(0); string localeName = result.Read(1); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; if (!_pageTextLocaleStorage.ContainsKey(id)) _pageTextLocaleStorage[id] = new PageTextLocale(); PageTextLocale data = _pageTextLocaleStorage[id]; AddLocaleString(result.Read(2), 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(0); string localeName = result.Read(1); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; if (!_pointOfInterestLocaleStorage.ContainsKey(id)) _pointOfInterestLocaleStorage[id] = new PointOfInterestLocale(); PointOfInterestLocale data = _pointOfInterestLocaleStorage[id]; AddLocaleString(result.Read(2), locale, data.Name); } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} points_of_interest locale strings in {1} ms", _pointOfInterestLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); } 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 optionIndex) { return _gossipMenuItemsLocaleStorage.LookupByKey(Tuple.Create(menuId, optionIndex)); } 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(0); RepRewardRate repRate = new RepRewardRate(); repRate.questRate = result.Read(1); repRate.questDailyRate = result.Read(2); repRate.questWeeklyRate = result.Read(3); repRate.questMonthlyRate = result.Read(4); repRate.questRepeatableRate = result.Read(5); repRate.creatureRate = result.Read(6); repRate.spellRate = result.Read(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(0); ReputationOnKillEntry repOnKill = new ReputationOnKillEntry(); repOnKill.RepFaction1 = result.Read(1); repOnKill.RepFaction2 = result.Read(2); repOnKill.IsTeamAward1 = result.Read(3); repOnKill.ReputationMaxCap1 = result.Read(4); repOnKill.RepValue1 = result.Read(5); repOnKill.IsTeamAward2 = result.Read(6); repOnKill.ReputationMaxCap2 = result.Read(7); repOnKill.RepValue2 = result.Read(8); repOnKill.TeamDependent = result.Read(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(0); RepSpilloverTemplate repTemplate = new RepSpilloverTemplate(); repTemplate.faction[0] = result.Read(1); repTemplate.faction_rate[0] = result.Read(2); repTemplate.faction_rank[0] = result.Read(3); repTemplate.faction[1] = result.Read(4); repTemplate.faction_rate[1] = result.Read(5); repTemplate.faction_rank[1] = result.Read(6); repTemplate.faction[2] = result.Read(7); repTemplate.faction_rate[2] = result.Read(8); repTemplate.faction_rank[2] = result.Read(9); repTemplate.faction[3] = result.Read(10); repTemplate.faction_rate[3] = result.Read(11); repTemplate.faction_rank[3] = result.Read(12); repTemplate.faction[4] = result.Read(13); repTemplate.faction_rate[4] = result.Read(14); repTemplate.faction_rank[4] = result.Read(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(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(0); uint raceMask = result.Read(1); uint mailTemplateId = result.Read(2); uint senderEntry = result.Read(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(0); uint basexp = result.Read(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(0); SummonerType summonerType = (SummonerType)result.Read(1); byte group = result.Read(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(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(4); float posY = result.Read(5); float posZ = result.Read(6); float orientation = result.Read(7); data.pos = new Position(posX, posY, posZ, orientation); data.type = (TempSummonType)result.Read(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(9); Tuple 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(0); PageText pageText = new PageText(); pageText.Text = result.Read(1); pageText.NextPageID = result.Read(2); pageText.PlayerConditionID = result.Read(3); pageText.Flags = result.Read(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(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 itemsCache = new MultiMap(); 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(0); item.item_template = result.Read(1); uint mailId = result.Read(2); itemsCache.Add(mailId, item); } while (items.NextRow()); } uint deletedCount = 0; uint returnedCount = 0; do { Mail m = new Mail(); m.messageID = result.Read(0); m.messageType = (MailMessageType)result.Read(1); m.sender = result.Read(2); m.receiver = result.Read(3); bool has_items = result.Read(4); m.expire_time = result.Read(5); m.deliver_time = 0; m.COD = result.Read(6); m.checkMask = (MailCheckMask)result.Read(7); m.mailTemplateId = result.Read(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 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); } stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_ITEM_BY_ID); stmt.AddValue(0, m.messageID); 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(0); SceneTemplate sceneTemplate = new SceneTemplate(); sceneTemplate.SceneId = sceneId; sceneTemplate.PlaybackFlags = (SceneFlags)result.Read(1); sceneTemplate.ScenePackageId = result.Read(2); sceneTemplate.ScriptId = GetScriptId(result.Read(3)); _sceneTemplateStorage[sceneId] = sceneTemplate; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} scene templates in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); } public void LoadPlayerChoices() { uint oldMSTime = Time.GetMSTime(); _playerChoices.Clear(); SQLResult choiceResult = DB.World.Query("SELECT ChoiceId, UiTextureKitId, Question, HideWarboardHeader FROM playerchoice"); if (choiceResult.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 player choices. DB table `playerchoice` is empty."); return; } uint responseCount = 0; uint rewardCount = 0; uint itemRewardCount = 0; uint currencyRewardCount = 0; uint factionRewardCount = 0; do { PlayerChoice choice = new PlayerChoice(); choice.ChoiceId = choiceResult.Read(0); choice.UiTextureKitId = choiceResult.Read(1); choice.Question = choiceResult.Read(2); choice.HideWarboardHeader = choiceResult.Read(3); _playerChoices[choice.ChoiceId] = choice; } while (choiceResult.NextRow()); SQLResult responses = DB.World.Query("SELECT ChoiceId, ResponseId, ChoiceArtFileId, Header, Answer, Description, Confirmation FROM playerchoice_response ORDER BY `Index` ASC"); if (!responses.IsEmpty()) { do { int choiceId = responses.Read(0); int responseId = responses.Read(1); if (!_playerChoices.ContainsKey(choiceId)) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response` references non-existing ChoiceId: {choiceId} (ResponseId: {responseId}), skipped"); continue; } PlayerChoice choice = _playerChoices[choiceId]; PlayerChoiceResponse response = new PlayerChoiceResponse(); response.ResponseId = responseId; response.ChoiceArtFileId = responses.Read(2); response.Header = responses.Read(3); response.Answer = responses.Read(4); response.Description = responses.Read(5); response.Confirmation = responses.Read(6); ++responseCount; choice.Responses[responseId] = response; } while (responses.NextRow()); } SQLResult rewards = DB.World.Query("SELECT ChoiceId, ResponseId, TitleId, PackageId, SkillLineId, SkillPointCount, ArenaPointCount, HonorPointCount, Money, Xp FROM playerchoice_response_reward"); if (!rewards.IsEmpty()) { do { int choiceId = rewards.Read(0); int responseId = rewards.Read(1); if (!_playerChoices.ContainsKey(choiceId)) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward` references non-existing ChoiceId: {choiceId} (ResponseId: {responseId}), skipped"); continue; } PlayerChoice choice = _playerChoices[choiceId]; if (!choice.Responses.Any(playerChoiceResponse => playerChoiceResponse.ResponseId == responseId)) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward` references non-existing ResponseId: {responseId} for ChoiceId {choiceId}, skipped"); continue; } PlayerChoiceResponseReward reward = new PlayerChoiceResponseReward(); reward.TitleId = rewards.Read(2); reward.PackageId = rewards.Read(3); reward.SkillLineId = rewards.Read(4); reward.SkillPointCount = rewards.Read(5); reward.ArenaPointCount = rewards.Read(6); reward.HonorPointCount = rewards.Read(7); reward.Money = rewards.Read(8); reward.Xp = rewards.Read(9); ++rewardCount; if (reward.TitleId != 0 && !CliDB.CharTitlesStorage.ContainsKey(reward.TitleId)) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward` references non-existing Title {reward.TitleId} for ChoiceId {choiceId}, ResponseId: {responseId}, set to 0"); reward.TitleId = 0; } if (reward.PackageId != 0 && Global.DB2Mgr.GetQuestPackageItems((uint)reward.PackageId) == null) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward` references non-existing QuestPackage {reward.TitleId} for ChoiceId {choiceId}, ResponseId: {responseId}, set to 0"); reward.PackageId = 0; } if (reward.SkillLineId != 0 && !CliDB.SkillLineStorage.ContainsKey(reward.SkillLineId)) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward` references non-existing SkillLine {reward.TitleId} for ChoiceId {choiceId}, ResponseId: {responseId}, set to 0"); reward.SkillLineId = 0; reward.SkillPointCount = 0; } choice.Responses[responseId].Reward.Set(reward); } while (rewards.NextRow()); } SQLResult rewardItem = DB.World.Query("SELECT ChoiceId, ResponseId, ItemId, BonusListIDs, Quantity FROM playerchoice_response_reward_item ORDER BY `Index` ASC"); if (!rewardItem.IsEmpty()) { do { int choiceId = rewardItem.Read(0); int responseId = rewardItem.Read(1); uint itemId = rewardItem.Read(2); StringArray bonusListIDsTok = new StringArray(rewardItem.Read(3), ' '); List bonusListIds = new List(); if (!bonusListIDsTok.IsEmpty()) { foreach (uint token in bonusListIDsTok) bonusListIds.Add(token); } int quantity = rewardItem.Read(4); PlayerChoice choice = _playerChoices.LookupByKey(choiceId); if (choice == null) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward_item` references non-existing ChoiceId: {choiceId} (ResponseId: {responseId}), skipped"); continue; } var response = choice.Responses.FirstOrDefault(playerChoiceResponse => playerChoiceResponse.ResponseId == responseId); if (response == null) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward_item` references non-existing ResponseId: {responseId} for ChoiceId {choiceId}, skipped"); continue; } if (!response.Reward.HasValue) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward_item` references non-existing player choice reward for ChoiceId {choiceId}, ResponseId: {responseId}, skipped"); continue; } if (GetItemTemplate(itemId) == null) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward_item` references non-existing item {itemId} for ChoiceId {choiceId}, ResponseId: {responseId}, skipped"); continue; } itemRewardCount++; response.Reward.Value.Items.Add(new PlayerChoiceResponseRewardItem(itemId, bonusListIds, quantity)); } while (rewardItem.NextRow()); } SQLResult rewardCurrency = DB.World.Query("SELECT ChoiceId, ResponseId, CurrencyId, Quantity FROM playerchoice_response_reward_currency ORDER BY `Index` ASC"); if (!rewardCurrency.IsEmpty()) { do { int choiceId = rewardCurrency.Read(0); int responseId = rewardCurrency.Read(1); uint currencyId = rewardCurrency.Read(2); int quantity = rewardCurrency.Read(3); PlayerChoice choice = _playerChoices.LookupByKey(choiceId); if (choice == null) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward_currency` references non-existing ChoiceId: {choiceId} (ResponseId: {responseId}), skipped"); continue; } var response = choice.Responses.FirstOrDefault(playerChoiceResponse => playerChoiceResponse.ResponseId == responseId); if (response == null) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward_currency` references non-existing ResponseId: {responseId} for ChoiceId {choiceId}, skipped"); continue; } if (!response.Reward.HasValue) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward_currency` references non-existing player choice reward for ChoiceId {choiceId}, ResponseId: {responseId}, skipped"); continue; } if (!CliDB.CurrencyTypesStorage.ContainsKey(currencyId)) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward_currency` references non-existing currency {currencyId} for ChoiceId {choiceId}, ResponseId: {responseId}, skipped"); continue; } currencyRewardCount++; response.Reward.Value.Currency.Add(new PlayerChoiceResponseRewardEntry(currencyId, quantity)); } while (rewards.NextRow()); } SQLResult rewardFaction = DB.World.Query("SELECT ChoiceId, ResponseId, FactionId, Quantity FROM playerchoice_response_reward_faction ORDER BY `Index` ASC"); if (!rewardFaction.IsEmpty()) { do { int choiceId = rewardFaction.Read(0); int responseId = rewardFaction.Read(1); uint factionId = rewardFaction.Read(2); int quantity = rewardFaction.Read(3); PlayerChoice choice = _playerChoices.LookupByKey(choiceId); if (choice == null) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward_faction` references non-existing ChoiceId: {choiceId} (ResponseId: {responseId}), skipped"); continue; } var response = choice.Responses.FirstOrDefault(playerChoiceResponse => playerChoiceResponse.ResponseId == responseId); if (response == null) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward_faction` references non-existing ResponseId: {responseId} for ChoiceId {choiceId}, skipped"); continue; } if (!response.Reward.HasValue) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward_faction` references non-existing player choice reward for ChoiceId {choiceId}, ResponseId: {responseId}, skipped"); continue; } if (!CliDB.FactionStorage.ContainsKey(factionId)) { Log.outError(LogFilter.Sql, $"Table `playerchoice_response_reward_faction` references non-existing faction {factionId} for ChoiceId {choiceId}, ResponseId: {responseId}, skipped"); continue; } factionRewardCount++; response.Reward.Value.Faction.Add(new PlayerChoiceResponseRewardEntry(factionId, quantity)); } while (rewardFaction.NextRow()); } Log.outInfo(LogFilter.ServerLoading, $"Loaded {_playerChoices.Count} player choices, {responseCount} responses, {rewardCount} rewards, {itemRewardCount} item rewards, {currencyRewardCount} " + $"currency rewards and {factionRewardCount} faction rewards in {Time.GetMSTimeDiffToNow(oldMSTime)} ms."); } public void LoadPlayerChoicesLocale() { uint oldMSTime = Time.GetMSTime(); // need for reload case _playerChoiceLocales.Clear(); // 0 1 2 SQLResult result = DB.World.Query("SELECT ChoiceId, locale, Question FROM playerchoice_locale"); if (!result.IsEmpty()) { do { int choiceId = result.Read(0); string localeName = result.Read(1); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; if (GetPlayerChoice(choiceId) == null) { Log.outError(LogFilter.Sql, $"Table `playerchoice_locale` references non-existing ChoiceId: {choiceId} for locale {localeName}, skipped"); continue; } if (!_playerChoiceLocales.ContainsKey(choiceId)) _playerChoiceLocales[choiceId] = new PlayerChoiceLocale(); PlayerChoiceLocale data = _playerChoiceLocales[choiceId]; AddLocaleString(result.Read(2), locale, data.Question); } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, $"Loaded {_playerChoiceLocales.Count} Player Choice locale strings in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } oldMSTime = Time.GetMSTime(); // 0 1 2 3 4 5 6 result = DB.World.Query("SELECT ChoiceID, ResponseID, locale, Header, Answer, Description, Confirmation FROM playerchoice_response_locale"); if (!result.IsEmpty()) { uint count = 0; do { int choiceId = result.Read(0); int responseId = result.Read(1); string localeName = result.Read(2); LocaleConstant locale = localeName.ToEnum(); if (locale == LocaleConstant.enUS) continue; var playerChoiceLocale = _playerChoiceLocales.LookupByKey(choiceId); if (playerChoiceLocale == null) { Log.outError(LogFilter.Sql, $"Table `playerchoice_locale` references non-existing ChoiceId: {choiceId} for ResponseId {responseId} locale {localeName}, skipped"); continue; } PlayerChoice playerChoice = GetPlayerChoice(choiceId); if (playerChoice.GetResponse(responseId) == null) { Log.outError(LogFilter.Sql, $"Table `playerchoice_locale` references non-existing ResponseId: {responseId} for ChoiceId {choiceId} locale {localeName}, skipped"); continue; } PlayerChoiceResponseLocale data = playerChoiceLocale.Responses[responseId]; AddLocaleString(result.Read(3), locale, data.Header); AddLocaleString(result.Read(4), locale, data.Answer); AddLocaleString(result.Read(5), locale, data.Description); AddLocaleString(result.Read(6), locale, data.Confirmation); count++; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} Player Choice Response locale strings in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } } 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(0) + 1); result = DB.Characters.Query("SELECT MAX(guid) FROM item_instance"); if (!result.IsEmpty()) GetGuidSequenceGenerator(HighGuid.Item).Set(result.Read(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(0) + 1); result = DB.Characters.Query("SELECT MAX(id) FROM auctionhouse"); if (!result.IsEmpty()) _auctionId = result.Read(0) + 1; result = DB.Characters.Query("SELECT MAX(id) FROM mail"); if (!result.IsEmpty()) _mailId = result.Read(0) + 1; result = DB.Characters.Query("SELECT MAX(arenateamid) FROM arena_team"); if (!result.IsEmpty()) Global.ArenaTeamMgr.SetNextArenaTeamId(result.Read(0) + 1); result = DB.Characters.Query("SELECT MAX(maxguid) FROM ((SELECT MAX(setguid) AS maxguid FROM character_equipmentsets) UNION (SELECT MAX(setguid) AS maxguid FROM character_transmog_outfits)) allsets"); if (!result.IsEmpty()) _equipmentSetGuid = result.Read(0) + 1; result = DB.Characters.Query("SELECT MAX(guildId) FROM guild"); if (!result.IsEmpty()) Global.GuildMgr.SetNextGuildId(result.Read(0) + 1); result = DB.Characters.Query("SELECT MAX(itemId) from character_void_storage"); if (!result.IsEmpty()) _voidItemId = result.Read(0) + 1; result = DB.World.Query("SELECT MAX(guid) FROM creature"); if (!result.IsEmpty()) _creatureSpawnId = result.Read(0) + 1; result = DB.World.Query("SELECT MAX(guid) FROM gameobject"); if (!result.IsEmpty()) _gameObjectSpawnId = result.Read(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()); 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 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.ContinentID != 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.ContinentID == 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 GetSummonGroup(uint summonerId, SummonerType summonerType, byte group) { Tuple 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(0); uint accessory = result.Read(1); sbyte seatId = result.Read(2); bool isMinion = result.Read(3); byte summonType = result.Read(4); uint summonTimer = result.Read(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(0); uint uiAccessory = result.Read(1); sbyte uiSeat = result.Read(2); bool bMinion = result.Read(3); byte uiSummonType = result.Read(4); uint uiSummonTimer = result.Read(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 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 CypherStringStorage = new Dictionary(); Dictionary _repRewardRateStorage = new Dictionary(); Dictionary _repOnKillStorage = new Dictionary(); Dictionary _repSpilloverTemplateStorage = new Dictionary(); MultiMap _mailLevelRewardStorage = new MultiMap(); MultiMap, TempSummonData> _tempSummonDataStorage = new MultiMap, TempSummonData>(); Dictionary _playerChoices = new Dictionary(); Dictionary _pageTextStorage = new Dictionary(); List _reservedNamesStorage = new List(); Dictionary _sceneTemplateStorage = new Dictionary(); Dictionary _raceUnlockRequirementStorage = new Dictionary(); Dictionary _classExpansionRequirementStorage = new Dictionary(); Dictionary _realmNameStorage = new Dictionary(); //Quest Dictionary _questTemplates = new Dictionary(); MultiMap _goQuestRelations = new MultiMap(); MultiMap _goQuestInvolvedRelations = new MultiMap(); MultiMap _goQuestInvolvedRelationsReverse = new MultiMap(); MultiMap _creatureQuestRelations = new MultiMap(); MultiMap _creatureQuestInvolvedRelations = new MultiMap(); MultiMap _creatureQuestInvolvedRelationsReverse = new MultiMap(); public MultiMap _exclusiveQuestGroups = new MultiMap(); MultiMap _questPOIStorage = new MultiMap(); MultiMap _questAreaTriggerStorage = new MultiMap(); Dictionary _questObjectives = new Dictionary(); Dictionary[] _questGreetingStorage = new Dictionary[2]; Dictionary[] _questGreetingLocaleStorage = new Dictionary[2]; //Scripts List scriptNamesStorage = new List(); MultiMap spellScriptsStorage = new MultiMap(); public Dictionary> sSpellScripts = new Dictionary>(); public Dictionary> sEventScripts = new Dictionary>(); public Dictionary> sWaypointScripts = new Dictionary>(); Dictionary areaTriggerScriptStorage = new Dictionary(); //Maps public Dictionary gameTeleStorage = new Dictionary(); Dictionary> mapObjectGuidsStore = new Dictionary>(); Dictionary instanceTemplateStorage = new Dictionary(); public MultiMap GraveYardStorage = new MultiMap(); List _transportMaps = new List(); //Spells /Skills / Phases Dictionary _phaseInfoById = new Dictionary(); Dictionary _terrainSwapInfoById = new Dictionary(); MultiMap _phaseInfoByArea = new MultiMap(); MultiMap _terrainSwapInfoByMap = new MultiMap(); MultiMap _spellClickInfoStorage = new MultiMap(); Dictionary _fishingBaseForAreaStorage = new Dictionary(); Dictionary _skillTiers = new Dictionary(); //Gossip MultiMap gossipMenuItemsStorage = new MultiMap(); MultiMap gossipMenusStorage = new MultiMap(); Dictionary pointsOfInterestStorage = new Dictionary(); //Creature Dictionary creatureTemplateStorage = new Dictionary(); Dictionary creatureModelStorage = new Dictionary(); Dictionary creatureDataStorage = new Dictionary(); Dictionary creatureAddonStorage = new Dictionary(); MultiMap _creatureQuestItemStorage = new MultiMap(); Dictionary creatureTemplateAddonStorage = new Dictionary(); MultiMap> equipmentInfoStorage = new MultiMap>(); Dictionary linkedRespawnStorage = new Dictionary(); Dictionary creatureBaseStatsStorage = new Dictionary(); Dictionary cacheVendorItemStorage = new Dictionary(); Dictionary _trainers = new Dictionary(); Dictionary _creatureDefaultTrainers = new Dictionary(); List[] _difficultyEntries = new List[SharedConst.MaxCreatureDifficulties]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate List[] _hasDifficultyEntries = new List[SharedConst.MaxCreatureDifficulties]; // already loaded creatures with difficulty 1 values, used in CheckCreatureTemplate Dictionary _npcTextStorage = new Dictionary(); //GameObject Dictionary gameObjectTemplateStorage = new Dictionary(); Dictionary gameObjectDataStorage = new Dictionary(); Dictionary _gameObjectTemplateAddonStore = new Dictionary(); Dictionary _gameObjectAddonStorage = new Dictionary(); MultiMap _gameObjectQuestItemStorage = new MultiMap(); List _gameObjectForQuestStorage = new List(); //Item Dictionary ItemTemplateStorage = new Dictionary(); //Player PlayerInfo[][] _playerInfo = new PlayerInfo[(int)Race.Max][]; //Faction Change public Dictionary FactionChangeAchievements = new Dictionary(); public Dictionary FactionChangeItems = new Dictionary(); public Dictionary FactionChangeQuests = new Dictionary(); public Dictionary FactionChangeReputation = new Dictionary(); public Dictionary FactionChangeSpells = new Dictionary(); public Dictionary FactionChangeTitles = new Dictionary(); //Pets Dictionary petInfoStore = new Dictionary(); MultiMap _petHalfName0 = new MultiMap(); MultiMap _petHalfName1 = new MultiMap(); //Vehicles MultiMap _vehicleTemplateAccessoryStore = new MultiMap(); MultiMap _vehicleAccessoryStore = new MultiMap(); //Locales Dictionary _creatureLocaleStorage = new Dictionary(); Dictionary _gameObjectLocaleStorage = new Dictionary(); Dictionary _questTemplateLocaleStorage = new Dictionary(); Dictionary _questObjectivesLocaleStorage = new Dictionary(); Dictionary _questOfferRewardLocaleStorage = new Dictionary(); Dictionary _questRequestItemsLocaleStorage = new Dictionary(); Dictionary, GossipMenuItemsLocale> _gossipMenuItemsLocaleStorage = new Dictionary, GossipMenuItemsLocale>(); Dictionary _pageTextLocaleStorage = new Dictionary(); Dictionary _pointOfInterestLocaleStorage = new Dictionary(); Dictionary _playerChoiceLocales = new Dictionary(); List _tavernAreaTriggerStorage = new List(); Dictionary _areaTriggerStorage = new Dictionary(); Dictionary _accessRequirementStorage = new Dictionary(); MultiMap _dungeonEncounterStorage = new MultiMap(); Dictionary _guidGenerators = new Dictionary(); // first free id for selected id type uint _auctionId; ulong _equipmentSetGuid; uint _mailId; uint _hiPetNumber; ulong _voidItemId; ulong _creatureSpawnId; ulong _gameObjectSpawnId; uint[] _playerXPperLevel; Dictionary _baseXPTable = new Dictionary(); #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 $"{command} ('{Global.ObjectMgr.GetScriptsTableNameByType(type)}' script id: {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 creatures = new SortedSet(); public SortedSet gameobjects = new SortedSet(); } 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, bool _AlwaysAllowMergingBlobs) { BlobIndex = _BlobIndex; ObjectiveIndex = _ObjectiveIndex; QuestObjectiveID = _QuestObjectiveID; QuestObjectID = _QuestObjectID; MapID = _MapID; WorldMapAreaID = _WorldMapAreaID; Floor = _Foor; Priority = _Priority; Flags = _Flags; WorldEffectID = _WorldEffectID; PlayerConditionID = _PlayerConditionID; UnkWoD1 = _UnkWoD1; AlwaysAllowMergingBlobs = _AlwaysAllowMergingBlobs; } 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 points = new List(); public bool AlwaysAllowMergingBlobs; } public class QuestPOIPoint { public QuestPOIPoint(int _x, int _y) { X = _x; Y = _y; } public int X; public int Y; } 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.ClassID == ItemClass.Weapon) { ItemType = 5; switch ((ItemSubClassWeapon)item.SubclassID) { 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.ClassID == ItemClass.Armor) { switch ((ItemSubClassArmor)item.SubclassID) { 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.SubclassID == (int)ItemSubClassArmor.Shield) { ItemType = 6; AddStat(ItemSpecStat.Shield); } else if (item.SubclassID > (int)ItemSubClassArmor.Shield && item.SubclassID <= (int)ItemSubClassArmor.Relic) { ItemType = 6; AddStat(ItemSpecStat.Relic); } else ItemType = 0; break; } } else if (item.ClassID == 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.StatModifierBonusStat[i] != -1) AddModStat(sparse.StatModifierBonusStat[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 TerrainSwapInfo { public TerrainSwapInfo() { } public TerrainSwapInfo(uint id) { Id = id; } public uint Id; public List UiWorldMapAreaIDSwaps = new List(); } public class PhaseInfoStruct { public PhaseInfoStruct(uint id) { Id = id; } public bool IsAllowedInArea(uint areaId) { return Areas.Any(areaToCheck => Global.DB2Mgr.IsInArea(areaId, areaToCheck)); } public uint Id; public List Areas = new List(); } public class PhaseAreaInfo { public PhaseAreaInfo(PhaseInfoStruct phaseInfo) { PhaseInfo = phaseInfo; } public PhaseInfoStruct PhaseInfo; public List SubAreaExclusions = new List(); public List Conditions = new List(); } 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); } public class PlayerChoiceLocale { public StringArray Question = new StringArray((int)LocaleConstant.Total); public Dictionary Responses = new Dictionary(); } public class PlayerChoiceResponseLocale { public StringArray Answer = new StringArray((int)LocaleConstant.Total); public StringArray Header = new StringArray((int)LocaleConstant.Total); public StringArray Description = new StringArray((int)LocaleConstant.Total); public StringArray Confirmation = new StringArray((int)LocaleConstant.Total); } public class PlayerChoiceResponseRewardItem { public PlayerChoiceResponseRewardItem() { } public PlayerChoiceResponseRewardItem(uint id, List bonusListIDs, int quantity) { Id = id; BonusListIDs = bonusListIDs; Quantity = quantity; } public uint Id; public List BonusListIDs = new List(); public int Quantity; } public class PlayerChoiceResponseRewardEntry { public PlayerChoiceResponseRewardEntry(uint id, int quantity) { Id = id; Quantity = quantity; } public uint Id; public int Quantity; } public class PlayerChoiceResponseReward { public int TitleId; public int PackageId; public int SkillLineId; public uint SkillPointCount; public uint ArenaPointCount; public uint HonorPointCount; public ulong Money; public uint Xp; public List Items = new List(); public List Currency = new List(); public List Faction = new List(); } public class PlayerChoiceResponse { public int ResponseId; public int ChoiceArtFileId; public string Header; public string Answer; public string Description; public string Confirmation; public Optional Reward; } public class PlayerChoice { public PlayerChoiceResponse GetResponse(int responseId) { return Responses.FirstOrDefault(playerChoiceResponse => playerChoiceResponse.ResponseId == responseId); } public int ChoiceId; public int UiTextureKitId; public string Question; public List Responses = new List(); public bool HideWarboardHeader; } public class RaceUnlockRequirement { public byte Expansion; public uint AchievementId; } }