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