· 8 years ago · Feb 02, 2018, 08:40 PM
1/*
2 * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
3 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "ObjectMgr.h"
20#include "AchievementMgr.h"
21#include "ArenaTeamMgr.h"
22#include "Bag.h"
23#include "Chat.h"
24#include "Containers.h"
25#include "CreatureAIFactory.h"
26#include "DatabaseEnv.h"
27#include "DisableMgr.h"
28#include "GameObject.h"
29#include "GameObjectAIFactory.h"
30#include "GameTime.h"
31#include "GossipDef.h"
32#include "GroupMgr.h"
33#include "GuildMgr.h"
34#include "InstanceSaveMgr.h"
35#include "InstanceScript.h"
36#include "Language.h"
37#include "LFGMgr.h"
38#include "Log.h"
39#include "LootMgr.h"
40#include "Mail.h"
41#include "MapManager.h"
42#include "MotionMaster.h"
43#include "ObjectAccessor.h"
44#include "Player.h"
45#include "PoolMgr.h"
46#include "QueryPackets.h"
47#include "Random.h"
48#include "ReputationMgr.h"
49#include "ScriptMgr.h"
50#include "SpellAuras.h"
51#include "SpellMgr.h"
52#include "SpellScript.h"
53#include "TemporarySummon.h"
54#include "UpdateMask.h"
55#include "Util.h"
56#include "Vehicle.h"
57#include "World.h"
58
59ScriptMapMap sSpellScripts;
60ScriptMapMap sEventScripts;
61ScriptMapMap sWaypointScripts;
62
63std::string GetScriptsTableNameByType(ScriptsType type)
64{
65 std::string res = "";
66 switch (type)
67 {
68 case SCRIPTS_SPELL: res = "spell_scripts"; break;
69 case SCRIPTS_EVENT: res = "event_scripts"; break;
70 case SCRIPTS_WAYPOINT: res = "waypoint_scripts"; break;
71 default: break;
72 }
73 return res;
74}
75
76ScriptMapMap* GetScriptsMapByType(ScriptsType type)
77{
78 ScriptMapMap* res = nullptr;
79 switch (type)
80 {
81 case SCRIPTS_SPELL: res = &sSpellScripts; break;
82 case SCRIPTS_EVENT: res = &sEventScripts; break;
83 case SCRIPTS_WAYPOINT: res = &sWaypointScripts; break;
84 default: break;
85 }
86 return res;
87}
88
89std::string GetScriptCommandName(ScriptCommands command)
90{
91 std::string res = "";
92 switch (command)
93 {
94 case SCRIPT_COMMAND_TALK: res = "SCRIPT_COMMAND_TALK"; break;
95 case SCRIPT_COMMAND_EMOTE: res = "SCRIPT_COMMAND_EMOTE"; break;
96 case SCRIPT_COMMAND_FIELD_SET: res = "SCRIPT_COMMAND_FIELD_SET"; break;
97 case SCRIPT_COMMAND_MOVE_TO: res = "SCRIPT_COMMAND_MOVE_TO"; break;
98 case SCRIPT_COMMAND_FLAG_SET: res = "SCRIPT_COMMAND_FLAG_SET"; break;
99 case SCRIPT_COMMAND_FLAG_REMOVE: res = "SCRIPT_COMMAND_FLAG_REMOVE"; break;
100 case SCRIPT_COMMAND_TELEPORT_TO: res = "SCRIPT_COMMAND_TELEPORT_TO"; break;
101 case SCRIPT_COMMAND_QUEST_EXPLORED: res = "SCRIPT_COMMAND_QUEST_EXPLORED"; break;
102 case SCRIPT_COMMAND_KILL_CREDIT: res = "SCRIPT_COMMAND_KILL_CREDIT"; break;
103 case SCRIPT_COMMAND_RESPAWN_GAMEOBJECT: res = "SCRIPT_COMMAND_RESPAWN_GAMEOBJECT"; break;
104 case SCRIPT_COMMAND_TEMP_SUMMON_CREATURE: res = "SCRIPT_COMMAND_TEMP_SUMMON_CREATURE"; break;
105 case SCRIPT_COMMAND_OPEN_DOOR: res = "SCRIPT_COMMAND_OPEN_DOOR"; break;
106 case SCRIPT_COMMAND_CLOSE_DOOR: res = "SCRIPT_COMMAND_CLOSE_DOOR"; break;
107 case SCRIPT_COMMAND_ACTIVATE_OBJECT: res = "SCRIPT_COMMAND_ACTIVATE_OBJECT"; break;
108 case SCRIPT_COMMAND_REMOVE_AURA: res = "SCRIPT_COMMAND_REMOVE_AURA"; break;
109 case SCRIPT_COMMAND_CAST_SPELL: res = "SCRIPT_COMMAND_CAST_SPELL"; break;
110 case SCRIPT_COMMAND_PLAY_SOUND: res = "SCRIPT_COMMAND_PLAY_SOUND"; break;
111 case SCRIPT_COMMAND_CREATE_ITEM: res = "SCRIPT_COMMAND_CREATE_ITEM"; break;
112 case SCRIPT_COMMAND_DESPAWN_SELF: res = "SCRIPT_COMMAND_DESPAWN_SELF"; break;
113 case SCRIPT_COMMAND_LOAD_PATH: res = "SCRIPT_COMMAND_LOAD_PATH"; break;
114 case SCRIPT_COMMAND_CALLSCRIPT_TO_UNIT: res = "SCRIPT_COMMAND_CALLSCRIPT_TO_UNIT"; break;
115 case SCRIPT_COMMAND_KILL: res = "SCRIPT_COMMAND_KILL"; break;
116 // TrinityCore only
117 case SCRIPT_COMMAND_ORIENTATION: res = "SCRIPT_COMMAND_ORIENTATION"; break;
118 case SCRIPT_COMMAND_EQUIP: res = "SCRIPT_COMMAND_EQUIP"; break;
119 case SCRIPT_COMMAND_MODEL: res = "SCRIPT_COMMAND_MODEL"; break;
120 case SCRIPT_COMMAND_CLOSE_GOSSIP: res = "SCRIPT_COMMAND_CLOSE_GOSSIP"; break;
121 case SCRIPT_COMMAND_PLAYMOVIE: res = "SCRIPT_COMMAND_PLAYMOVIE"; break;
122 case SCRIPT_COMMAND_MOVEMENT: res = "SCRIPT_COMMAND_MOVEMENT"; break;
123 default:
124 {
125 char sz[32];
126 sprintf(sz, "Unknown command: %d", command);
127 res = sz;
128 break;
129 }
130 }
131 return res;
132}
133
134std::string ScriptInfo::GetDebugInfo() const
135{
136 char sz[256];
137 sprintf(sz, "%s ('%s' script id: %u)", GetScriptCommandName(command).c_str(), GetScriptsTableNameByType(type).c_str(), id);
138 return std::string(sz);
139}
140
141bool normalizePlayerName(std::string& name)
142{
143 if (name.empty())
144 return false;
145
146 wchar_t wstr_buf[MAX_INTERNAL_PLAYER_NAME+1];
147 size_t wstr_len = MAX_INTERNAL_PLAYER_NAME;
148
149 if (!Utf8toWStr(name, &wstr_buf[0], wstr_len))
150 return false;
151
152 wstr_buf[0] = wcharToUpper(wstr_buf[0]);
153 for (size_t i = 1; i < wstr_len; ++i)
154 wstr_buf[i] = wcharToLower(wstr_buf[i]);
155
156 if (!WStrToUtf8(wstr_buf, wstr_len, name))
157 return false;
158
159 return true;
160}
161
162LanguageDesc lang_description[LANGUAGES_COUNT] =
163{
164 { LANG_ADDON, 0, 0 },
165 { LANG_UNIVERSAL, 0, 0 },
166 { LANG_ORCISH, 669, SKILL_LANG_ORCISH },
167 { LANG_DARNASSIAN, 671, SKILL_LANG_DARNASSIAN },
168 { LANG_TAURAHE, 670, SKILL_LANG_TAURAHE },
169 { LANG_DWARVISH, 672, SKILL_LANG_DWARVEN },
170 { LANG_COMMON, 668, SKILL_LANG_COMMON },
171 { LANG_DEMONIC, 815, SKILL_LANG_DEMON_TONGUE },
172 { LANG_TITAN, 816, SKILL_LANG_TITAN },
173 { LANG_THALASSIAN, 813, SKILL_LANG_THALASSIAN },
174 { LANG_DRACONIC, 814, SKILL_LANG_DRACONIC },
175 { LANG_KALIMAG, 817, SKILL_LANG_OLD_TONGUE },
176 { LANG_GNOMISH, 7340, SKILL_LANG_GNOMISH },
177 { LANG_TROLL, 7341, SKILL_LANG_TROLL },
178 { LANG_GUTTERSPEAK, 17737, SKILL_LANG_GUTTERSPEAK },
179 { LANG_DRAENEI, 29932, SKILL_LANG_DRAENEI },
180 { LANG_ZOMBIE, 0, 0 },
181 { LANG_GNOMISH_BINARY, 0, 0 },
182 { LANG_GOBLIN_BINARY, 0, 0 }
183};
184
185LanguageDesc const* GetLanguageDescByID(uint32 lang)
186{
187 for (uint8 i = 0; i < LANGUAGES_COUNT; ++i)
188 {
189 if (uint32(lang_description[i].lang_id) == lang)
190 return &lang_description[i];
191 }
192
193 return nullptr;
194}
195
196bool SpellClickInfo::IsFitToRequirements(Unit const* clicker, Unit const* clickee) const
197{
198 Player const* playerClicker = clicker->ToPlayer();
199 if (!playerClicker)
200 return true;
201
202 Unit const* summoner = nullptr;
203 // Check summoners for party
204 if (clickee->IsSummon())
205 summoner = clickee->ToTempSummon()->GetSummoner();
206 if (!summoner)
207 summoner = clickee;
208
209 // This only applies to players
210 switch (userType)
211 {
212 case SPELL_CLICK_USER_FRIEND:
213 if (!playerClicker->IsFriendlyTo(summoner))
214 return false;
215 break;
216 case SPELL_CLICK_USER_RAID:
217 if (!playerClicker->IsInRaidWith(summoner))
218 return false;
219 break;
220 case SPELL_CLICK_USER_PARTY:
221 if (!playerClicker->IsInPartyWith(summoner))
222 return false;
223 break;
224 default:
225 break;
226 }
227
228 return true;
229}
230
231ObjectMgr::ObjectMgr():
232 _auctionId(1),
233 _equipmentSetGuid(1),
234 _mailId(1),
235 _hiPetNumber(1),
236 _creatureSpawnId(1),
237 _gameObjectSpawnId(1),
238 DBCLocaleIndex(LOCALE_enUS)
239{
240}
241
242ObjectMgr* ObjectMgr::instance()
243{
244 static ObjectMgr instance;
245 return &instance;
246}
247
248ObjectMgr::~ObjectMgr()
249{
250}
251
252void ObjectMgr::AddLocaleString(std::string const& value, LocaleConstant localeConstant, std::vector<std::string>& data)
253{
254 if (!value.empty())
255 {
256 if (data.size() <= size_t(localeConstant))
257 data.resize(localeConstant + 1);
258
259 data[localeConstant] = value;
260 }
261}
262
263void ObjectMgr::LoadCreatureLocales()
264{
265 uint32 oldMSTime = getMSTime();
266
267 _creatureLocaleStore.clear(); // need for reload case
268
269 // 0 1 2 3
270 QueryResult result = WorldDatabase.Query("SELECT entry, locale, Name, Title FROM creature_template_locale");
271 if (!result)
272 return;
273
274 do
275 {
276 Field* fields = result->Fetch();
277
278 uint32 id = fields[0].GetUInt32();
279 std::string localeName = fields[1].GetString();
280
281 std::string name = fields[2].GetString();
282 std::string title = fields[3].GetString();
283
284 CreatureLocale& data = _creatureLocaleStore[id];
285 LocaleConstant locale = GetLocaleByName(localeName);
286 if (locale == LOCALE_enUS)
287 continue;
288
289 AddLocaleString(name, locale, data.Name);
290 AddLocaleString(title, locale, data.Title);
291
292 } while (result->NextRow());
293
294 TC_LOG_INFO("server.loading", ">> Loaded %u creature locale strings in %u ms", uint32(_creatureLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime));
295}
296
297void ObjectMgr::LoadGossipMenuItemsLocales()
298{
299 uint32 oldMSTime = getMSTime();
300
301 _gossipMenuItemsLocaleStore.clear(); // need for reload case
302
303 // 0 1 2 3 4
304 QueryResult result = WorldDatabase.Query("SELECT MenuID, OptionID, Locale, OptionText, BoxText FROM gossip_menu_option_locale");
305
306 if (!result)
307 return;
308
309 do
310 {
311 Field* fields = result->Fetch();
312
313 uint16 menuId = fields[0].GetUInt16();
314 uint16 optionId = fields[1].GetUInt16();
315 std::string localeName = fields[2].GetString();
316 std::string optionText = fields[3].GetString();
317 std::string boxText = fields[4].GetString();
318
319 GossipMenuItemsLocale& data = _gossipMenuItemsLocaleStore[MAKE_PAIR32(menuId, optionId)];
320 LocaleConstant locale = GetLocaleByName(localeName);
321 if (locale == LOCALE_enUS)
322 continue;
323
324 AddLocaleString(optionText, locale, data.OptionText);
325 AddLocaleString(boxText, locale, data.BoxText);
326 } while (result->NextRow());
327
328 TC_LOG_INFO("server.loading", ">> Loaded %u gossip_menu_option locale strings in %u ms", uint32(_gossipMenuItemsLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime));
329}
330
331void ObjectMgr::LoadPointOfInterestLocales()
332{
333 uint32 oldMSTime = getMSTime();
334
335 _pointOfInterestLocaleStore.clear(); // need for reload case
336
337 // 0 1 2
338 QueryResult result = WorldDatabase.Query("SELECT ID, locale, Name FROM points_of_interest_locale");
339
340 if (!result)
341 return;
342
343 do
344 {
345 Field* fields = result->Fetch();
346
347 uint32 id = fields[0].GetUInt32();
348 std::string localeName = fields[1].GetString();
349 std::string name = fields[2].GetString();
350
351 PointOfInterestLocale& data = _pointOfInterestLocaleStore[id];
352 LocaleConstant locale = GetLocaleByName(localeName);
353
354 AddLocaleString(name, locale, data.Name);
355 } while (result->NextRow());
356
357 TC_LOG_INFO("server.loading", ">> Loaded %u points_of_interest locale strings in %u ms", uint32(_pointOfInterestLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime));
358}
359
360void ObjectMgr::LoadCreatureTemplates()
361{
362 uint32 oldMSTime = getMSTime();
363
364 // 0 1 2 3 4 5 6 7 8
365 QueryResult result = WorldDatabase.Query("SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, "
366 // 9 10 11 12 13 14 15 16 17 18 19 20
367 "modelid4, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, "
368 // 21 22 23 24 25 26 27 28 29 30
369 "scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, "
370 // 31 32 33 34 35 36 37
371 "dynamicflags, family, trainer_type, trainer_spell, trainer_class, trainer_race, type, "
372 // 38 39 40 41 42 43 44 45 46 47 48 49 50 51
373 "type_flags, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, resistance7, resistance8, resistance9, spell1, "
374 // 52 53 54 55 56 57 58 59 60 61 62 63 64
375 "spell2, spell3, spell4, spell5, spell6, spell7, spell8, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, "
376 // 65 66 67 68 69 70 71 72
377 "InhabitType, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, "
378 // 73 74 75 76 77 78
379 "movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName "
380 "FROM creature_template");
381
382 if (!result)
383 {
384 TC_LOG_INFO("server.loading", ">> Loaded 0 creature template definitions. DB table `creature_template` is empty.");
385 return;
386 }
387
388 _creatureTemplateStore.reserve(result->GetRowCount());
389 do
390 {
391 Field* fields = result->Fetch();
392 LoadCreatureTemplate(fields);
393 } while (result->NextRow());
394
395 // Checking needs to be done after loading because of the difficulty self referencing
396 for (auto const& ctPair : _creatureTemplateStore)
397 CheckCreatureTemplate(&ctPair.second);
398
399 TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " creature definitions in %u ms", _creatureTemplateStore.size(), GetMSTimeDiffToNow(oldMSTime));
400}
401
402void ObjectMgr::LoadCreatureTemplate(Field* fields)
403{
404 uint32 entry = fields[0].GetUInt32();
405 CreatureTemplate& creatureTemplate = _creatureTemplateStore[entry];
406
407 creatureTemplate.Entry = entry;
408
409 for (uint8 i = 0; i < MAX_DIFFICULTY - 1; ++i)
410 creatureTemplate.DifficultyEntry[i] = fields[1 + i].GetUInt32();
411
412 for (uint8 i = 0; i < MAX_KILL_CREDIT; ++i)
413 creatureTemplate.KillCredit[i] = fields[4 + i].GetUInt32();
414
415 creatureTemplate.Modelid1 = fields[6].GetUInt32();
416 creatureTemplate.Modelid2 = fields[7].GetUInt32();
417 creatureTemplate.Modelid3 = fields[8].GetUInt32();
418 creatureTemplate.Modelid4 = fields[9].GetUInt32();
419 creatureTemplate.Name = fields[10].GetString();
420 creatureTemplate.Title = fields[11].GetString();
421 creatureTemplate.IconName = fields[12].GetString();
422 creatureTemplate.GossipMenuId = fields[13].GetUInt32();
423 creatureTemplate.minlevel = fields[14].GetUInt8();
424 creatureTemplate.maxlevel = fields[15].GetUInt8();
425 creatureTemplate.expansion = uint32(fields[16].GetInt16());
426 creatureTemplate.faction = fields[17].GetUInt16();
427 creatureTemplate.npcflag = fields[18].GetUInt32();
428 creatureTemplate.speed_walk = fields[19].GetFloat();
429 creatureTemplate.speed_run = fields[20].GetFloat();
430 creatureTemplate.scale = fields[21].GetFloat();
431 creatureTemplate.rank = fields[22].GetUInt8();
432 creatureTemplate.dmgschool = uint32(fields[23].GetInt8());
433 creatureTemplate.BaseAttackTime = fields[24].GetUInt32();
434 creatureTemplate.RangeAttackTime = fields[25].GetUInt32();
435 creatureTemplate.BaseVariance = fields[26].GetFloat();
436 creatureTemplate.RangeVariance = fields[27].GetFloat();
437 creatureTemplate.unit_class = fields[28].GetUInt8();
438 creatureTemplate.unit_flags = fields[29].GetUInt32();
439 creatureTemplate.unit_flags2 = fields[30].GetUInt32();
440 creatureTemplate.dynamicflags = fields[31].GetUInt32();
441 creatureTemplate.family = CreatureFamily(fields[32].GetUInt8());
442 creatureTemplate.trainer_type = fields[33].GetUInt8();
443 creatureTemplate.trainer_spell = fields[34].GetUInt32();
444 creatureTemplate.trainer_class = fields[35].GetUInt8();
445 creatureTemplate.trainer_race = fields[36].GetUInt8();
446 creatureTemplate.type = fields[37].GetUInt8();
447 creatureTemplate.type_flags = fields[38].GetUInt32();
448 creatureTemplate.lootid = fields[39].GetUInt32();
449 creatureTemplate.pickpocketLootId = fields[40].GetUInt32();
450 creatureTemplate.SkinLootId = fields[41].GetUInt32();
451
452 for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
453 creatureTemplate.resistance[i] = fields[42 + i - 1].GetInt16();
454
455 for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i)
456 creatureTemplate.spells[i] = fields[51 + i].GetUInt32();
457
458 creatureTemplate.PetSpellDataId = fields[59].GetUInt32();
459 creatureTemplate.VehicleId = fields[60].GetUInt32();
460 creatureTemplate.mingold = fields[61].GetUInt32();
461 creatureTemplate.maxgold = fields[62].GetUInt32();
462 creatureTemplate.AIName = fields[63].GetString();
463 creatureTemplate.MovementType = fields[64].GetUInt8();
464 creatureTemplate.InhabitType = fields[65].GetUInt8();
465 creatureTemplate.HoverHeight = fields[66].GetFloat();
466 creatureTemplate.ModHealth = fields[67].GetFloat();
467 creatureTemplate.ModMana = fields[68].GetFloat();
468 creatureTemplate.ModArmor = fields[69].GetFloat();
469 creatureTemplate.ModDamage = fields[70].GetFloat();
470 creatureTemplate.ModExperience = fields[71].GetFloat();
471 creatureTemplate.RacialLeader = fields[72].GetBool();
472
473 creatureTemplate.movementId = fields[73].GetUInt32();
474 creatureTemplate.RegenHealth = fields[74].GetBool();
475 creatureTemplate.MechanicImmuneMask = fields[75].GetUInt32();
476 creatureTemplate.SpellSchoolImmuneMask = fields[76].GetUInt32();
477 creatureTemplate.flags_extra = fields[77].GetUInt32();
478 creatureTemplate.ScriptID = GetScriptId(fields[78].GetString());
479}
480
481void ObjectMgr::LoadCreatureTemplateAddons()
482{
483 uint32 oldMSTime = getMSTime();
484
485 // 0 1 2 3 4 5 6
486 QueryResult result = WorldDatabase.Query("SELECT entry, path_id, mount, bytes1, bytes2, emote, auras FROM creature_template_addon");
487
488 if (!result)
489 {
490 TC_LOG_INFO("server.loading", ">> Loaded 0 creature template addon definitions. DB table `creature_template_addon` is empty.");
491 return;
492 }
493
494 uint32 count = 0;
495 do
496 {
497 Field* fields = result->Fetch();
498
499 uint32 entry = fields[0].GetUInt32();
500
501 if (!sObjectMgr->GetCreatureTemplate(entry))
502 {
503 TC_LOG_ERROR("sql.sql", "Creature template (Entry: %u) does not exist but has a record in `creature_template_addon`", entry);
504 continue;
505 }
506
507 CreatureAddon& creatureAddon = _creatureTemplateAddonStore[entry];
508
509 creatureAddon.path_id = fields[1].GetUInt32();
510 creatureAddon.mount = fields[2].GetUInt32();
511 creatureAddon.bytes1 = fields[3].GetUInt32();
512 creatureAddon.bytes2 = fields[4].GetUInt32();
513 creatureAddon.emote = fields[5].GetUInt32();
514
515 Tokenizer tokens(fields[6].GetString(), ' ');
516 uint8 i = 0;
517 creatureAddon.auras.resize(tokens.size());
518 for (Tokenizer::const_iterator itr = tokens.begin(); itr != tokens.end(); ++itr)
519 {
520 SpellInfo const* AdditionalSpellInfo = sSpellMgr->GetSpellInfo(atoul(*itr));
521 if (!AdditionalSpellInfo)
522 {
523 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has wrong spell %lu defined in `auras` field in `creature_template_addon`.", entry, atoul(*itr));
524 continue;
525 }
526
527 if (AdditionalSpellInfo->HasAura(SPELL_AURA_CONTROL_VEHICLE))
528 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has SPELL_AURA_CONTROL_VEHICLE aura %lu defined in `auras` field in `creature_template_addon`.", entry, atoul(*itr));
529
530 if (std::find(creatureAddon.auras.begin(), creatureAddon.auras.end(), atoul(*itr)) != creatureAddon.auras.end())
531 {
532 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has duplicate aura (spell %lu) in `auras` field in `creature_template_addon`.", entry, atoul(*itr));
533 continue;
534 }
535
536 creatureAddon.auras[i++] = atoul(*itr);
537 }
538
539 if (creatureAddon.mount)
540 {
541 if (!sCreatureDisplayInfoStore.LookupEntry(creatureAddon.mount))
542 {
543 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has invalid displayInfoId (%u) for mount defined in `creature_template_addon`", entry, creatureAddon.mount);
544 creatureAddon.mount = 0;
545 }
546 }
547
548 if (!sEmotesStore.LookupEntry(creatureAddon.emote))
549 {
550 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has invalid emote (%u) defined in `creature_template_addon`.", entry, creatureAddon.emote);
551 creatureAddon.emote = 0;
552 }
553
554 ++count;
555 }
556 while (result->NextRow());
557
558 TC_LOG_INFO("server.loading", ">> Loaded %u creature template addons in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
559}
560
561void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo)
562{
563 if (!cInfo)
564 return;
565
566 bool ok = true; // bool to allow continue outside this loop
567 for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && ok; ++diff)
568 {
569 if (!cInfo->DifficultyEntry[diff])
570 continue;
571 ok = false; // will be set to true at the end of this loop again
572
573 CreatureTemplate const* difficultyInfo = GetCreatureTemplate(cInfo->DifficultyEntry[diff]);
574 if (!difficultyInfo)
575 {
576 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has `difficulty_entry_%u`=%u but creature entry %u does not exist.",
577 cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff], cInfo->DifficultyEntry[diff]);
578 continue;
579 }
580
581 bool ok2 = true;
582 for (uint32 diff2 = 0; diff2 < MAX_DIFFICULTY - 1 && ok2; ++diff2)
583 {
584 ok2 = false;
585 if (_difficultyEntries[diff2].find(cInfo->Entry) != _difficultyEntries[diff2].end())
586 {
587 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) is listed as `difficulty_entry_%u` of another creature, but itself lists %u in `difficulty_entry_%u`.",
588 cInfo->Entry, diff2 + 1, cInfo->DifficultyEntry[diff], diff + 1);
589 continue;
590 }
591
592 if (_difficultyEntries[diff2].find(cInfo->DifficultyEntry[diff]) != _difficultyEntries[diff2].end())
593 {
594 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) already listed as `difficulty_entry_%u` for another entry.", cInfo->DifficultyEntry[diff], diff2 + 1);
595 continue;
596 }
597
598 if (_hasDifficultyEntries[diff2].find(cInfo->DifficultyEntry[diff]) != _hasDifficultyEntries[diff2].end())
599 {
600 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has `difficulty_entry_%u`=%u but creature entry %u has itself a value in `difficulty_entry_%u`.",
601 cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff], cInfo->DifficultyEntry[diff], diff2 + 1);
602 continue;
603 }
604 ok2 = true;
605 }
606
607 if (!ok2)
608 continue;
609
610 if (cInfo->expansion > difficultyInfo->expansion)
611 {
612 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, exp: %u) has different `exp` in difficulty %u mode (Entry: %u, exp: %u).",
613 cInfo->Entry, cInfo->expansion, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->expansion);
614 }
615
616 if (cInfo->minlevel > difficultyInfo->minlevel)
617 {
618 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, minlevel: %u) has lower `minlevel` in difficulty %u mode (Entry: %u, minlevel: %u).",
619 cInfo->Entry, cInfo->minlevel, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->minlevel);
620 }
621
622 if (cInfo->maxlevel > difficultyInfo->maxlevel)
623 {
624 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, maxlevel: %u) has lower `maxlevel` in difficulty %u mode (Entry: %u, maxlevel: %u).",
625 cInfo->Entry, cInfo->maxlevel, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->maxlevel);
626 }
627
628 if (cInfo->faction != difficultyInfo->faction)
629 {
630 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, faction: %u) has different `faction` in difficulty %u mode (Entry: %u, faction: %u).",
631 cInfo->Entry, cInfo->faction, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->faction);
632 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `faction`=%u WHERE `entry`=%u;",
633 cInfo->faction, cInfo->DifficultyEntry[diff]);
634 }
635
636 if (cInfo->unit_class != difficultyInfo->unit_class)
637 {
638 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, class: %u) has different `unit_class` in difficulty %u mode (Entry: %u, class: %u).",
639 cInfo->Entry, cInfo->unit_class, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->unit_class);
640 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `unit_class`=%u WHERE `entry`=%u;",
641 cInfo->unit_class, cInfo->DifficultyEntry[diff]);
642 continue;
643 }
644
645 uint32 differenceMask = cInfo->npcflag ^ difficultyInfo->npcflag;
646 if (cInfo->npcflag != difficultyInfo->npcflag)
647 {
648 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, `npcflag`: %u) has different `npcflag` in difficulty %u mode (Entry: %u, `npcflag`: %u).",
649 cInfo->Entry, cInfo->npcflag, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->npcflag);
650 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `npcflag`=`npcflag`^%u WHERE `entry`=%u;",
651 differenceMask, cInfo->DifficultyEntry[diff]);
652 }
653
654 if (cInfo->dmgschool != difficultyInfo->dmgschool)
655 {
656 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, `dmgschool`: %u) has different `dmgschool` in difficulty %u mode (Entry: %u, `dmgschool`: %u).",
657 cInfo->Entry, cInfo->dmgschool, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->dmgschool);
658 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `dmgschool`=%u WHERE `entry`=%u;",
659 cInfo->dmgschool, cInfo->DifficultyEntry[diff]);
660 }
661
662 differenceMask = cInfo->unit_flags2 ^ difficultyInfo->unit_flags2;
663 if (cInfo->unit_flags2 != difficultyInfo->unit_flags2)
664 {
665 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, `unit_flags2`: %u) has different `unit_flags2` in difficulty %u mode (Entry: %u, `unit_flags2`: %u).",
666 cInfo->Entry, cInfo->unit_flags2, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->unit_flags2);
667 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `unit_flags2`=`unit_flags2`^%u WHERE `entry`=%u;",
668 differenceMask, cInfo->DifficultyEntry[diff]);
669 }
670
671 if (cInfo->family != difficultyInfo->family)
672 {
673 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, family: %u) has different `family` in difficulty %u mode (Entry: %u, family: %u).",
674 cInfo->Entry, cInfo->family, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->family);
675 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `family`=%u WHERE `entry`=%u;",
676 cInfo->family, cInfo->DifficultyEntry[diff]);
677 }
678
679 if (cInfo->trainer_class != difficultyInfo->trainer_class)
680 {
681 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, trainer_class: %u) has different `trainer_class` in difficulty %u mode (Entry: %u, trainer_class: %u).",
682 cInfo->Entry, cInfo->trainer_class, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->trainer_class);
683 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `trainer_class`=%u WHERE `entry`=%u;",
684 cInfo->trainer_class, cInfo->DifficultyEntry[diff]);
685 continue;
686 }
687
688 if (cInfo->trainer_race != difficultyInfo->trainer_race)
689 {
690 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, trainer_race: %u) has different `trainer_race` in difficulty %u mode (Entry: %u, trainer_race: %u).",
691 cInfo->Entry, cInfo->trainer_race, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->trainer_race);
692 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `trainer_race`=%u WHERE `entry`=%u;",
693 cInfo->trainer_race, cInfo->DifficultyEntry[diff]);
694 continue;
695 }
696
697 if (cInfo->trainer_type != difficultyInfo->trainer_type)
698 {
699 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, trainer_type: %u) has different `trainer_type` in difficulty %u mode (Entry: %u, trainer_type: %u).",
700 cInfo->Entry, cInfo->trainer_type, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->trainer_type);
701 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `trainer_type`=%u WHERE `entry`=%u;",
702 cInfo->trainer_type, cInfo->DifficultyEntry[diff]);
703 continue;
704 }
705
706 if (cInfo->trainer_spell != difficultyInfo->trainer_spell)
707 {
708 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has different `trainer_spell` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
709 continue;
710 }
711
712 if (cInfo->type != difficultyInfo->type)
713 {
714 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, type: %u) has different `type` in difficulty %u mode (Entry: %u, type: %u).",
715 cInfo->Entry, cInfo->type, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->type);
716 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `type`=%u WHERE `entry`=%u;",
717 cInfo->type, cInfo->DifficultyEntry[diff]);
718 }
719
720 if (!cInfo->VehicleId && difficultyInfo->VehicleId)
721 {
722 TC_LOG_ERROR("sql.sql", "Non-vehicle Creature (Entry: %u, VehicleId: %u) has `VehicleId` set in difficulty %u mode (Entry: %u, VehicleId: %u).",
723 cInfo->Entry, cInfo->VehicleId, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->VehicleId);
724 }
725
726 if (cInfo->RegenHealth != difficultyInfo->RegenHealth)
727 {
728 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, RegenHealth: %u) has different `RegenHealth` in difficulty %u mode (Entry: %u, RegenHealth: %u).",
729 cInfo->Entry, cInfo->RegenHealth, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->RegenHealth);
730 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `RegenHealth`=%u WHERE `entry`=%u;",
731 cInfo->RegenHealth, cInfo->DifficultyEntry[diff]);
732 }
733
734 differenceMask = cInfo->MechanicImmuneMask & (~difficultyInfo->MechanicImmuneMask);
735 if (differenceMask)
736 {
737 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, mechanic_immune_mask: %u) has weaker immunities in difficulty %u mode (Entry: %u, mechanic_immune_mask: %u).",
738 cInfo->Entry, cInfo->MechanicImmuneMask, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->MechanicImmuneMask);
739 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `mechanic_immune_mask`=`mechanic_immune_mask`|%u WHERE `entry`=%u;",
740 differenceMask, cInfo->DifficultyEntry[diff]);
741 }
742
743 differenceMask = (cInfo->flags_extra ^ difficultyInfo->flags_extra) & (~CREATURE_FLAG_EXTRA_INSTANCE_BIND);
744 if (differenceMask)
745 {
746 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, flags_extra: %u) has different `flags_extra` in difficulty %u mode (Entry: %u, flags_extra: %u).",
747 cInfo->Entry, cInfo->flags_extra, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->flags_extra);
748 TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `flags_extra`=`flags_extra`^%u WHERE `entry`=%u;",
749 differenceMask, cInfo->DifficultyEntry[diff]);
750 }
751
752 if (!difficultyInfo->AIName.empty())
753 {
754 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) lists difficulty %u mode entry %u with `AIName` filled in. `AIName` of difficulty 0 mode creature is always used instead.",
755 cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
756 continue;
757 }
758
759 if (difficultyInfo->ScriptID)
760 {
761 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) lists difficulty %u mode entry %u with `ScriptName` filled in. `ScriptName` of difficulty 0 mode creature is always used instead.",
762 cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
763 continue;
764 }
765
766 _hasDifficultyEntries[diff].insert(cInfo->Entry);
767 _difficultyEntries[diff].insert(cInfo->DifficultyEntry[diff]);
768 ok = true;
769 }
770
771 if (cInfo->mingold > cInfo->maxgold)
772 {
773 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has `mingold` %u which is greater than `maxgold` %u, setting `maxgold` to %u.",
774 cInfo->Entry, cInfo->mingold, cInfo->maxgold, cInfo->mingold);
775 const_cast<CreatureTemplate*>(cInfo)->maxgold = cInfo->mingold;
776 }
777
778 if (!cInfo->AIName.empty())
779 {
780 auto registryItem = sCreatureAIRegistry->GetRegistryItem(cInfo->AIName);
781 if (!registryItem)
782 {
783 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has non-registered `AIName` '%s' set, removing", cInfo->Entry, cInfo->AIName.c_str());
784 const_cast<CreatureTemplate*>(cInfo)->AIName.clear();
785 }
786 else
787 {
788 DBPermit const* permit = dynamic_cast<DBPermit const*>(registryItem);
789 if (!ASSERT_NOTNULL(permit)->IsScriptNameAllowedInDB())
790 {
791 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has not-allowed `AIName` '%s' set, removing", cInfo->Entry, cInfo->AIName.c_str());
792 const_cast<CreatureTemplate*>(cInfo)->AIName.clear();
793 }
794 }
795 }
796
797 FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction);
798 if (!factionTemplate)
799 {
800 TC_LOG_FATAL("sql.sql", "Creature (Entry: %u) has non-existing faction template (%u). This can lead to crashes, aborting.", cInfo->Entry, cInfo->faction);
801 ABORT();
802 }
803
804 // used later for scale
805 CreatureDisplayInfoEntry const* displayScaleEntry = nullptr;
806
807 if (cInfo->Modelid1)
808 {
809 CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid1);
810 if (!displayEntry)
811 {
812 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) lists non-existing Modelid1 id (%u), this can crash the client.", cInfo->Entry, cInfo->Modelid1);
813 const_cast<CreatureTemplate*>(cInfo)->Modelid1 = 0;
814 }
815 else
816 displayScaleEntry = displayEntry;
817
818 CreatureModelInfo const* modelInfo = GetCreatureModelInfo(cInfo->Modelid1);
819 if (!modelInfo)
820 TC_LOG_ERROR("sql.sql", "No model data exist for `Modelid1` = %u listed by creature (Entry: %u).", cInfo->Modelid1, cInfo->Entry);
821 }
822
823 if (cInfo->Modelid2)
824 {
825 CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid2);
826 if (!displayEntry)
827 {
828 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) lists non-existing Modelid2 id (%u), this can crash the client.", cInfo->Entry, cInfo->Modelid2);
829 const_cast<CreatureTemplate*>(cInfo)->Modelid2 = 0;
830 }
831 else if (!displayScaleEntry)
832 displayScaleEntry = displayEntry;
833
834 CreatureModelInfo const* modelInfo = GetCreatureModelInfo(cInfo->Modelid2);
835 if (!modelInfo)
836 TC_LOG_ERROR("sql.sql", "No model data exist for `Modelid2` = %u listed by creature (Entry: %u).", cInfo->Modelid2, cInfo->Entry);
837 }
838
839 if (cInfo->Modelid3)
840 {
841 CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid3);
842 if (!displayEntry)
843 {
844 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) lists non-existing Modelid3 id (%u), this can crash the client.", cInfo->Entry, cInfo->Modelid3);
845 const_cast<CreatureTemplate*>(cInfo)->Modelid3 = 0;
846 }
847 else if (!displayScaleEntry)
848 displayScaleEntry = displayEntry;
849
850 CreatureModelInfo const* modelInfo = GetCreatureModelInfo(cInfo->Modelid3);
851 if (!modelInfo)
852 TC_LOG_ERROR("sql.sql", "No model data exist for `Modelid3` = %u listed by creature (Entry: %u).", cInfo->Modelid3, cInfo->Entry);
853 }
854
855 if (cInfo->Modelid4)
856 {
857 CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid4);
858 if (!displayEntry)
859 {
860 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) lists non-existing Modelid4 id (%u), this can crash the client.", cInfo->Entry, cInfo->Modelid4);
861 const_cast<CreatureTemplate*>(cInfo)->Modelid4 = 0;
862 }
863 else if (!displayScaleEntry)
864 displayScaleEntry = displayEntry;
865
866 CreatureModelInfo const* modelInfo = GetCreatureModelInfo(cInfo->Modelid4);
867 if (!modelInfo)
868 TC_LOG_ERROR("sql.sql", "No model data exist for `Modelid4` = %u listed by creature (Entry: %u).", cInfo->Modelid4, cInfo->Entry);
869 }
870
871 if (!displayScaleEntry)
872 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) does not have any existing display id in Modelid1/Modelid2/Modelid3/Modelid4.", cInfo->Entry);
873
874 for (uint8 k = 0; k < MAX_KILL_CREDIT; ++k)
875 {
876 if (cInfo->KillCredit[k])
877 {
878 if (!GetCreatureTemplate(cInfo->KillCredit[k]))
879 {
880 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) lists non-existing creature entry %u in `KillCredit%d`.", cInfo->Entry, cInfo->KillCredit[k], k + 1);
881 const_cast<CreatureTemplate*>(cInfo)->KillCredit[k] = 0;
882 }
883 }
884 }
885
886 if (!cInfo->unit_class || ((1 << (cInfo->unit_class-1)) & CLASSMASK_ALL_CREATURES) == 0)
887 {
888 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has invalid unit_class (%u) in creature_template. Set to 1 (UNIT_CLASS_WARRIOR).", cInfo->Entry, cInfo->unit_class);
889 const_cast<CreatureTemplate*>(cInfo)->unit_class = UNIT_CLASS_WARRIOR;
890 }
891
892 if (cInfo->dmgschool >= MAX_SPELL_SCHOOL)
893 {
894 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has invalid spell school value (%u) in `dmgschool`.", cInfo->Entry, cInfo->dmgschool);
895 const_cast<CreatureTemplate*>(cInfo)->dmgschool = SPELL_SCHOOL_NORMAL;
896 }
897
898 if (cInfo->BaseAttackTime == 0)
899 const_cast<CreatureTemplate*>(cInfo)->BaseAttackTime = BASE_ATTACK_TIME;
900
901 if (cInfo->RangeAttackTime == 0)
902 const_cast<CreatureTemplate*>(cInfo)->RangeAttackTime = BASE_ATTACK_TIME;
903
904 if ((cInfo->npcflag & UNIT_NPC_FLAG_TRAINER) && cInfo->trainer_type >= MAX_TRAINER_TYPE)
905 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has wrong trainer type %u.", cInfo->Entry, cInfo->trainer_type);
906
907 if (cInfo->speed_walk == 0.0f)
908 {
909 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has wrong value (%f) in speed_walk, set to 1.", cInfo->Entry, cInfo->speed_walk);
910 const_cast<CreatureTemplate*>(cInfo)->speed_walk = 1.0f;
911 }
912
913 if (cInfo->speed_run == 0.0f)
914 {
915 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has wrong value (%f) in speed_run, set to 1.14286.", cInfo->Entry, cInfo->speed_run);
916 const_cast<CreatureTemplate*>(cInfo)->speed_run = 1.14286f;
917 }
918
919 if (cInfo->type && !sCreatureTypeStore.LookupEntry(cInfo->type))
920 {
921 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has invalid creature type (%u) in `type`.", cInfo->Entry, cInfo->type);
922 const_cast<CreatureTemplate*>(cInfo)->type = CREATURE_TYPE_HUMANOID;
923 }
924
925 // must exist or used hidden but used in data horse case
926 if (cInfo->family && !sCreatureFamilyStore.LookupEntry(cInfo->family) && cInfo->family != CREATURE_FAMILY_HORSE_CUSTOM)
927 {
928 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has invalid creature family (%u) in `family`.", cInfo->Entry, cInfo->family);
929 const_cast<CreatureTemplate*>(cInfo)->family = CREATURE_FAMILY_NONE;
930 }
931
932 if (cInfo->InhabitType <= 0 || cInfo->InhabitType > INHABIT_ANYWHERE)
933 {
934 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has wrong value (%u) in `InhabitType`, creature will not correctly walk/swim/fly.", cInfo->Entry, cInfo->InhabitType);
935 const_cast<CreatureTemplate*>(cInfo)->InhabitType = INHABIT_ANYWHERE;
936 }
937
938 if (cInfo->HoverHeight < 0.0f)
939 {
940 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has wrong value (%f) in `HoverHeight`", cInfo->Entry, cInfo->HoverHeight);
941 const_cast<CreatureTemplate*>(cInfo)->HoverHeight = 1.0f;
942 }
943
944 if (cInfo->VehicleId)
945 {
946 VehicleEntry const* vehId = sVehicleStore.LookupEntry(cInfo->VehicleId);
947 if (!vehId)
948 {
949 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has a non-existing VehicleId (%u). This *WILL* cause the client to freeze!", cInfo->Entry, cInfo->VehicleId);
950 const_cast<CreatureTemplate*>(cInfo)->VehicleId = 0;
951 }
952 }
953
954 if (cInfo->PetSpellDataId)
955 {
956 CreatureSpellDataEntry const* spellDataId = sCreatureSpellDataStore.LookupEntry(cInfo->PetSpellDataId);
957 if (!spellDataId)
958 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has non-existing PetSpellDataId (%u).", cInfo->Entry, cInfo->PetSpellDataId);
959 }
960
961 for (uint8 j = 0; j < MAX_CREATURE_SPELLS; ++j)
962 {
963 if (cInfo->spells[j] && !sSpellMgr->GetSpellInfo(cInfo->spells[j]))
964 {
965 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has non-existing Spell%d (%u), set to 0.", cInfo->Entry, j+1, cInfo->spells[j]);
966 const_cast<CreatureTemplate*>(cInfo)->spells[j] = 0;
967 }
968 }
969
970 if (cInfo->MovementType >= MAX_DB_MOTION_TYPE)
971 {
972 TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has wrong movement generator type (%u), ignored and set to IDLE.", cInfo->Entry, cInfo->MovementType);
973 const_cast<CreatureTemplate*>(cInfo)->MovementType = IDLE_MOTION_TYPE;
974 }
975
976 /// if not set custom creature scale then load scale from CreatureDisplayInfo.dbc
977 if (cInfo->scale <= 0.0f)
978 {
979 if (displayScaleEntry)
980 const_cast<CreatureTemplate*>(cInfo)->scale = displayScaleEntry->scale;
981 else
982 const_cast<CreatureTemplate*>(cInfo)->scale = 1.0f;
983 }
984
985 if (cInfo->expansion > (MAX_EXPANSIONS - 1))
986 {
987 TC_LOG_ERROR("sql.sql", "Table `creature_template` lists creature (Entry: %u) with expansion %u. Ignored and set to 0.", cInfo->Entry, cInfo->expansion);
988 const_cast<CreatureTemplate*>(cInfo)->expansion = 0;
989 }
990
991 if (uint32 badFlags = (cInfo->flags_extra & ~CREATURE_FLAG_EXTRA_DB_ALLOWED))
992 {
993 TC_LOG_ERROR("sql.sql", "Table `creature_template` lists creature (Entry: %u) with disallowed `flags_extra` %u, removing incorrect flag.", cInfo->Entry, badFlags);
994 const_cast<CreatureTemplate*>(cInfo)->flags_extra &= CREATURE_FLAG_EXTRA_DB_ALLOWED;
995 }
996
997 const_cast<CreatureTemplate*>(cInfo)->ModDamage *= Creature::_GetDamageMod(cInfo->rank);
998}
999
1000void ObjectMgr::LoadCreatureAddons()
1001{
1002 uint32 oldMSTime = getMSTime();
1003
1004 // 0 1 2 3 4 5 6
1005 QueryResult result = WorldDatabase.Query("SELECT guid, path_id, mount, bytes1, bytes2, emote, auras FROM creature_addon");
1006
1007 if (!result)
1008 {
1009 TC_LOG_INFO("server.loading", ">> Loaded 0 creature addon definitions. DB table `creature_addon` is empty.");
1010 return;
1011 }
1012
1013 uint32 count = 0;
1014 do
1015 {
1016 Field* fields = result->Fetch();
1017
1018 ObjectGuid::LowType guid = fields[0].GetUInt32();
1019
1020 CreatureData const* creData = GetCreatureData(guid);
1021 if (!creData)
1022 {
1023 TC_LOG_ERROR("sql.sql", "Creature (GUID: %u) does not exist but has a record in `creature_addon`", guid);
1024 continue;
1025 }
1026
1027 CreatureAddon& creatureAddon = _creatureAddonStore[guid];
1028
1029 creatureAddon.path_id = fields[1].GetUInt32();
1030 if (creData->movementType == WAYPOINT_MOTION_TYPE && !creatureAddon.path_id)
1031 {
1032 const_cast<CreatureData*>(creData)->movementType = IDLE_MOTION_TYPE;
1033 TC_LOG_ERROR("sql.sql", "Creature (GUID %u) has movement type set to WAYPOINT_MOTION_TYPE but no path assigned", guid);
1034 }
1035
1036 creatureAddon.mount = fields[2].GetUInt32();
1037 creatureAddon.bytes1 = fields[3].GetUInt32();
1038 creatureAddon.bytes2 = fields[4].GetUInt32();
1039 creatureAddon.emote = fields[5].GetUInt32();
1040
1041 Tokenizer tokens(fields[6].GetString(), ' ');
1042 uint8 i = 0;
1043 creatureAddon.auras.resize(tokens.size());
1044 for (Tokenizer::const_iterator itr = tokens.begin(); itr != tokens.end(); ++itr)
1045 {
1046 SpellInfo const* AdditionalSpellInfo = sSpellMgr->GetSpellInfo(atoul(*itr));
1047 if (!AdditionalSpellInfo)
1048 {
1049 TC_LOG_ERROR("sql.sql", "Creature (GUID: %u) has wrong spell %lu defined in `auras` field in `creature_addon`.", guid, atoul(*itr));
1050 continue;
1051 }
1052
1053 if (AdditionalSpellInfo->HasAura(SPELL_AURA_CONTROL_VEHICLE))
1054 TC_LOG_ERROR("sql.sql", "Creature (GUID: %u) has SPELL_AURA_CONTROL_VEHICLE aura %lu defined in `auras` field in `creature_addon`.", guid, atoul(*itr));
1055
1056 if (std::find(creatureAddon.auras.begin(), creatureAddon.auras.end(), atoul(*itr)) != creatureAddon.auras.end())
1057 {
1058 TC_LOG_ERROR("sql.sql", "Creature (GUID: %u) has duplicate aura (spell %lu) in `auras` field in `creature_addon`.", guid, atoul(*itr));
1059 continue;
1060 }
1061
1062 creatureAddon.auras[i++] = atoul(*itr);
1063 }
1064
1065 if (creatureAddon.mount)
1066 {
1067 if (!sCreatureDisplayInfoStore.LookupEntry(creatureAddon.mount))
1068 {
1069 TC_LOG_ERROR("sql.sql", "Creature (GUID: %u) has invalid displayInfoId (%u) for mount defined in `creature_addon`", guid, creatureAddon.mount);
1070 creatureAddon.mount = 0;
1071 }
1072 }
1073
1074 if (!sEmotesStore.LookupEntry(creatureAddon.emote))
1075 {
1076 TC_LOG_ERROR("sql.sql", "Creature (GUID: %u) has invalid emote (%u) defined in `creature_addon`.", guid, creatureAddon.emote);
1077 creatureAddon.emote = 0;
1078 }
1079
1080 ++count;
1081 }
1082 while (result->NextRow());
1083
1084 TC_LOG_INFO("server.loading", ">> Loaded %u creature addons in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
1085}
1086
1087void ObjectMgr::LoadGameObjectAddons()
1088{
1089 uint32 oldMSTime = getMSTime();
1090
1091 // 0 1 2 3 4 5 6
1092 QueryResult result = WorldDatabase.Query("SELECT guid, parent_rotation0, parent_rotation1, parent_rotation2, parent_rotation3, invisibilityType, invisibilityValue FROM gameobject_addon");
1093
1094 if (!result)
1095 {
1096 TC_LOG_INFO("server.loading", ">> Loaded 0 gameobject addon definitions. DB table `gameobject_addon` is empty.");
1097 return;
1098 }
1099
1100 uint32 count = 0;
1101 do
1102 {
1103 Field* fields = result->Fetch();
1104
1105 ObjectGuid::LowType guid = fields[0].GetUInt32();
1106
1107 GameObjectData const* goData = GetGameObjectData(guid);
1108 if (!goData)
1109 {
1110 TC_LOG_ERROR("sql.sql", "GameObject (GUID: %u) does not exist but has a record in `gameobject_addon`", guid);
1111 continue;
1112 }
1113
1114 GameObjectAddon& gameObjectAddon = _gameObjectAddonStore[guid];
1115 gameObjectAddon.ParentRotation = QuaternionData(fields[1].GetFloat(), fields[2].GetFloat(), fields[3].GetFloat(), fields[4].GetFloat());
1116 gameObjectAddon.invisibilityType = InvisibilityType(fields[5].GetUInt8());
1117 gameObjectAddon.InvisibilityValue = fields[6].GetUInt32();
1118
1119 if (gameObjectAddon.invisibilityType >= TOTAL_INVISIBILITY_TYPES)
1120 {
1121 TC_LOG_ERROR("sql.sql", "GameObject (GUID: %u) has invalid InvisibilityType in `gameobject_addon`, disabled invisibility", guid);
1122 gameObjectAddon.invisibilityType = INVISIBILITY_GENERAL;
1123 gameObjectAddon.InvisibilityValue = 0;
1124 }
1125
1126 if (gameObjectAddon.invisibilityType && !gameObjectAddon.InvisibilityValue)
1127 {
1128 TC_LOG_ERROR("sql.sql", "GameObject (GUID: %u) has InvisibilityType set but has no InvisibilityValue in `gameobject_addon`, set to 1", guid);
1129 gameObjectAddon.InvisibilityValue = 1;
1130 }
1131
1132 if (!gameObjectAddon.ParentRotation.isUnit())
1133 {
1134 TC_LOG_ERROR("sql.sql", "GameObject (GUID: %u) has invalid parent rotation in `gameobject_addon`, set to default", guid);
1135 gameObjectAddon.ParentRotation = QuaternionData();
1136 }
1137
1138 ++count;
1139 }
1140 while (result->NextRow());
1141
1142 TC_LOG_INFO("server.loading", ">> Loaded %u gameobject addons in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
1143}
1144
1145GameObjectAddon const* ObjectMgr::GetGameObjectAddon(ObjectGuid::LowType lowguid) const
1146{
1147 GameObjectAddonContainer::const_iterator itr = _gameObjectAddonStore.find(lowguid);
1148 if (itr != _gameObjectAddonStore.end())
1149 return &(itr->second);
1150
1151 return nullptr;
1152}
1153
1154CreatureAddon const* ObjectMgr::GetCreatureAddon(ObjectGuid::LowType lowguid) const
1155{
1156 CreatureAddonContainer::const_iterator itr = _creatureAddonStore.find(lowguid);
1157 if (itr != _creatureAddonStore.end())
1158 return &(itr->second);
1159
1160 return nullptr;
1161}
1162
1163CreatureAddon const* ObjectMgr::GetCreatureTemplateAddon(uint32 entry) const
1164{
1165 CreatureAddonContainer::const_iterator itr = _creatureTemplateAddonStore.find(entry);
1166 if (itr != _creatureTemplateAddonStore.end())
1167 return &(itr->second);
1168
1169 return nullptr;
1170}
1171
1172EquipmentInfo const* ObjectMgr::GetEquipmentInfo(uint32 entry, int8& id) const
1173{
1174 EquipmentInfoContainer::const_iterator itr = _equipmentInfoStore.find(entry);
1175 if (itr == _equipmentInfoStore.end())
1176 return nullptr;
1177
1178 if (itr->second.empty())
1179 return nullptr;
1180
1181 if (id == -1) // select a random element
1182 {
1183 EquipmentInfoContainerInternal::const_iterator ritr = itr->second.begin();
1184 std::advance(ritr, urand(0u, itr->second.size() - 1));
1185 id = std::distance(itr->second.begin(), ritr) + 1;
1186 return &ritr->second;
1187 }
1188 else
1189 {
1190 EquipmentInfoContainerInternal::const_iterator itr2 = itr->second.find(id);
1191 if (itr2 != itr->second.end())
1192 return &itr2->second;
1193 }
1194
1195 return nullptr;
1196}
1197
1198void ObjectMgr::LoadEquipmentTemplates()
1199{
1200 uint32 oldMSTime = getMSTime();
1201
1202 // 0 1 2 3 4
1203 QueryResult result = WorldDatabase.Query("SELECT CreatureID, ID, ItemID1, ItemID2, ItemID3 FROM creature_equip_template");
1204
1205 if (!result)
1206 {
1207 TC_LOG_INFO("server.loading", ">> Loaded 0 creature equipment templates. DB table `creature_equip_template` is empty!");
1208 return;
1209 }
1210
1211 uint32 count = 0;
1212 do
1213 {
1214 Field* fields = result->Fetch();
1215
1216 uint32 entry = fields[0].GetUInt32();
1217
1218 if (!sObjectMgr->GetCreatureTemplate(entry))
1219 {
1220 TC_LOG_ERROR("sql.sql", "Creature template (Entry: %u) does not exist but has a record in `creature_equip_template`", entry);
1221 continue;
1222 }
1223
1224 uint8 id = fields[1].GetUInt8();
1225 if (!id)
1226 {
1227 TC_LOG_ERROR("sql.sql", "Creature equipment template with id 0 found for creature %u, skipped.", entry);
1228 continue;
1229 }
1230
1231 EquipmentInfo& equipmentInfo = _equipmentInfoStore[entry][id];
1232
1233 equipmentInfo.ItemEntry[0] = fields[2].GetUInt32();
1234 equipmentInfo.ItemEntry[1] = fields[3].GetUInt32();
1235 equipmentInfo.ItemEntry[2] = fields[4].GetUInt32();
1236
1237 for (uint8 i = 0; i < MAX_EQUIPMENT_ITEMS; ++i)
1238 {
1239 if (!equipmentInfo.ItemEntry[i])
1240 continue;
1241
1242 ItemEntry const* dbcItem = sItemStore.LookupEntry(equipmentInfo.ItemEntry[i]);
1243
1244 if (!dbcItem)
1245 {
1246 TC_LOG_ERROR("sql.sql", "Unknown item (entry=%u) in creature_equip_template.itemEntry%u for entry = %u and id=%u, forced to 0.",
1247 equipmentInfo.ItemEntry[i], i+1, entry, id);
1248 equipmentInfo.ItemEntry[i] = 0;
1249 continue;
1250 }
1251
1252 if (dbcItem->InventoryType != INVTYPE_WEAPON &&
1253 dbcItem->InventoryType != INVTYPE_SHIELD &&
1254 dbcItem->InventoryType != INVTYPE_RANGED &&
1255 dbcItem->InventoryType != INVTYPE_2HWEAPON &&
1256 dbcItem->InventoryType != INVTYPE_WEAPONMAINHAND &&
1257 dbcItem->InventoryType != INVTYPE_WEAPONOFFHAND &&
1258 dbcItem->InventoryType != INVTYPE_HOLDABLE &&
1259 dbcItem->InventoryType != INVTYPE_THROWN &&
1260 dbcItem->InventoryType != INVTYPE_RANGEDRIGHT)
1261 {
1262 TC_LOG_ERROR("sql.sql", "Item (entry=%u) in creature_equip_template.itemEntry%u for entry = %u and id = %u is not equipable in a hand, forced to 0.",
1263 equipmentInfo.ItemEntry[i], i+1, entry, id);
1264 equipmentInfo.ItemEntry[i] = 0;
1265 }
1266 }
1267
1268 ++count;
1269 }
1270 while (result->NextRow());
1271
1272 TC_LOG_INFO("server.loading", ">> Loaded %u equipment templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
1273}
1274
1275CreatureModelInfo const* ObjectMgr::GetCreatureModelInfo(uint32 modelId) const
1276{
1277 CreatureModelContainer::const_iterator itr = _creatureModelStore.find(modelId);
1278 if (itr != _creatureModelStore.end())
1279 return &(itr->second);
1280
1281 return nullptr;
1282}
1283
1284uint32 ObjectMgr::ChooseDisplayId(CreatureTemplate const* cinfo, CreatureData const* data /*= nullptr*/)
1285{
1286 // Load creature model (display id)
1287 if (data && data->displayid)
1288 return data->displayid;
1289
1290 if (!(cinfo->flags_extra & CREATURE_FLAG_EXTRA_TRIGGER))
1291 return cinfo->GetRandomValidModelId();
1292
1293 // Triggers by default receive the invisible model
1294 return cinfo->GetFirstInvisibleModel();
1295}
1296
1297void ObjectMgr::ChooseCreatureFlags(CreatureTemplate const* cinfo, uint32& npcflag, uint32& unit_flags, uint32& dynamicflags, CreatureData const* data /*= nullptr*/)
1298{
1299 npcflag = cinfo->npcflag;
1300 unit_flags = cinfo->unit_flags;
1301 dynamicflags = cinfo->dynamicflags;
1302
1303 if (data)
1304 {
1305 if (data->npcflag)
1306 npcflag = data->npcflag;
1307
1308 if (data->unit_flags)
1309 unit_flags = data->unit_flags;
1310
1311 if (data->dynamicflags)
1312 dynamicflags = data->dynamicflags;
1313 }
1314}
1315
1316CreatureModelInfo const* ObjectMgr::GetCreatureModelRandomGender(uint32* displayID) const
1317{
1318 CreatureModelInfo const* modelInfo = GetCreatureModelInfo(*displayID);
1319 if (!modelInfo)
1320 return nullptr;
1321
1322 // If a model for another gender exists, 50% chance to use it
1323 if (modelInfo->modelid_other_gender != 0 && urand(0, 1) == 0)
1324 {
1325 CreatureModelInfo const* minfo_tmp = GetCreatureModelInfo(modelInfo->modelid_other_gender);
1326 if (!minfo_tmp)
1327 TC_LOG_ERROR("sql.sql", "Model (Entry: %u) has modelid_other_gender %u not found in table `creature_model_info`. ", *displayID, modelInfo->modelid_other_gender);
1328 else
1329 {
1330 // Model ID changed
1331 *displayID = modelInfo->modelid_other_gender;
1332 return minfo_tmp;
1333 }
1334 }
1335
1336 return modelInfo;
1337}
1338
1339void ObjectMgr::LoadCreatureModelInfo()
1340{
1341 uint32 oldMSTime = getMSTime();
1342 // 0 1 2 3 4
1343 QueryResult result = WorldDatabase.Query("SELECT DisplayID, BoundingRadius, CombatReach, Gender, DisplayID_Other_Gender FROM creature_model_info");
1344
1345 if (!result)
1346 {
1347 TC_LOG_INFO("server.loading", ">> Loaded 0 creature model definitions. DB table `creature_model_info` is empty.");
1348 return;
1349 }
1350
1351 _creatureModelStore.rehash(result->GetRowCount());
1352 uint32 count = 0;
1353
1354 do
1355 {
1356 Field* fields = result->Fetch();
1357
1358 uint32 modelId = fields[0].GetUInt32();
1359 CreatureDisplayInfoEntry const* creatureDisplay = sCreatureDisplayInfoStore.LookupEntry(modelId);
1360 if (!creatureDisplay)
1361 {
1362 TC_LOG_ERROR("sql.sql", "Table `creature_model_info` has model for nonexistent display id (%u).", modelId);
1363 continue;
1364 }
1365
1366 CreatureModelInfo& modelInfo = _creatureModelStore[modelId];
1367
1368 modelInfo.bounding_radius = fields[1].GetFloat();
1369 modelInfo.combat_reach = fields[2].GetFloat();
1370 modelInfo.gender = fields[3].GetUInt8();
1371 modelInfo.modelid_other_gender = fields[4].GetUInt32();
1372 modelInfo.is_trigger = false;
1373
1374 // Checks
1375
1376 if (modelInfo.gender > GENDER_NONE)
1377 {
1378 TC_LOG_ERROR("sql.sql", "Table `creature_model_info` has wrong gender (%u) for display id (%u).", uint32(modelInfo.gender), modelId);
1379 modelInfo.gender = GENDER_MALE;
1380 }
1381
1382 if (modelInfo.modelid_other_gender && !sCreatureDisplayInfoStore.LookupEntry(modelInfo.modelid_other_gender))
1383 {
1384 TC_LOG_ERROR("sql.sql", "Table `creature_model_info` has nonexistent alt.gender model (%u) for existed display id (%u).", modelInfo.modelid_other_gender, modelId);
1385 modelInfo.modelid_other_gender = 0;
1386 }
1387
1388 if (modelInfo.combat_reach < 0.1f)
1389 modelInfo.combat_reach = DEFAULT_PLAYER_COMBAT_REACH;
1390
1391 if (CreatureModelDataEntry const* modelData = sCreatureModelDataStore.LookupEntry(creatureDisplay->ModelId))
1392 modelInfo.is_trigger = strstr(modelData->ModelPath, "InvisibleStalker") != nullptr;
1393
1394 ++count;
1395 }
1396 while (result->NextRow());
1397
1398 TC_LOG_INFO("server.loading", ">> Loaded %u creature model based info in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
1399}
1400
1401void ObjectMgr::LoadLinkedRespawn()
1402{
1403 uint32 oldMSTime = getMSTime();
1404
1405 _linkedRespawnStore.clear();
1406 // 0 1 2
1407 QueryResult result = WorldDatabase.Query("SELECT guid, linkedGuid, linkType FROM linked_respawn ORDER BY guid ASC");
1408
1409 if (!result)
1410 {
1411 TC_LOG_INFO("server.loading", ">> Loaded 0 linked respawns. DB table `linked_respawn` is empty.");
1412 return;
1413 }
1414
1415 do
1416 {
1417 Field* fields = result->Fetch();
1418
1419 ObjectGuid::LowType guidLow = fields[0].GetUInt32();
1420 ObjectGuid::LowType linkedGuidLow = fields[1].GetUInt32();
1421 uint8 linkType = fields[2].GetUInt8();
1422
1423 ObjectGuid guid, linkedGuid;
1424 bool error = false;
1425 switch (linkType)
1426 {
1427 case LINKED_RESPAWN_CREATURE_TO_CREATURE:
1428 {
1429 CreatureData const* slave = GetCreatureData(guidLow);
1430 if (!slave)
1431 {
1432 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature (guid) '%u' not found in creature table", guidLow);
1433 error = true;
1434 break;
1435 }
1436
1437 CreatureData const* master = GetCreatureData(linkedGuidLow);
1438 if (!master)
1439 {
1440 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature (linkedGuid) '%u' not found in creature table", linkedGuidLow);
1441 error = true;
1442 break;
1443 }
1444
1445 MapEntry const* const map = sMapStore.LookupEntry(master->spawnPoint.GetMapId());
1446 if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId()))
1447 {
1448 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '%u' linking to Creature '%u' on an unpermitted map.", guidLow, linkedGuidLow);
1449 error = true;
1450 break;
1451 }
1452
1453 if (!(master->spawnMask & slave->spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
1454 {
1455 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '%u' linking to Creature '%u' with not corresponding spawnMask", guidLow, linkedGuidLow);
1456 error = true;
1457 break;
1458 }
1459
1460 guid = ObjectGuid(HighGuid::Unit, slave->id, guidLow);
1461 linkedGuid = ObjectGuid(HighGuid::Unit, master->id, linkedGuidLow);
1462 break;
1463 }
1464 case LINKED_RESPAWN_CREATURE_TO_GO:
1465 {
1466 CreatureData const* slave = GetCreatureData(guidLow);
1467 if (!slave)
1468 {
1469 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature (guid) '%u' not found in creature table", guidLow);
1470 error = true;
1471 break;
1472 }
1473
1474 GameObjectData const* master = GetGameObjectData(linkedGuidLow);
1475 if (!master)
1476 {
1477 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (linkedGuid) '%u' not found in gameobject table", linkedGuidLow);
1478 error = true;
1479 break;
1480 }
1481
1482 MapEntry const* const map = sMapStore.LookupEntry(master->spawnPoint.GetMapId());
1483 if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId()))
1484 {
1485 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '%u' linking to Gameobject '%u' on an unpermitted map.", guidLow, linkedGuidLow);
1486 error = true;
1487 break;
1488 }
1489
1490 if (!(master->spawnMask & slave->spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
1491 {
1492 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '%u' linking to Gameobject '%u' with not corresponding spawnMask", guidLow, linkedGuidLow);
1493 error = true;
1494 break;
1495 }
1496
1497 guid = ObjectGuid(HighGuid::Unit, slave->id, guidLow);
1498 linkedGuid = ObjectGuid(HighGuid::GameObject, master->id, linkedGuidLow);
1499 break;
1500 }
1501 case LINKED_RESPAWN_GO_TO_GO:
1502 {
1503 GameObjectData const* slave = GetGameObjectData(guidLow);
1504 if (!slave)
1505 {
1506 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (guid) '%u' not found in gameobject table", guidLow);
1507 error = true;
1508 break;
1509 }
1510
1511 GameObjectData const* master = GetGameObjectData(linkedGuidLow);
1512 if (!master)
1513 {
1514 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (linkedGuid) '%u' not found in gameobject table", linkedGuidLow);
1515 error = true;
1516 break;
1517 }
1518
1519 MapEntry const* const map = sMapStore.LookupEntry(master->spawnPoint.GetMapId());
1520 if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId()))
1521 {
1522 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '%u' linking to Gameobject '%u' on an unpermitted map.", guidLow, linkedGuidLow);
1523 error = true;
1524 break;
1525 }
1526
1527 if (!(master->spawnMask & slave->spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
1528 {
1529 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '%u' linking to Gameobject '%u' with not corresponding spawnMask", guidLow, linkedGuidLow);
1530 error = true;
1531 break;
1532 }
1533
1534 guid = ObjectGuid(HighGuid::GameObject, slave->id, guidLow);
1535 linkedGuid = ObjectGuid(HighGuid::GameObject, master->id, linkedGuidLow);
1536 break;
1537 }
1538 case LINKED_RESPAWN_GO_TO_CREATURE:
1539 {
1540 GameObjectData const* slave = GetGameObjectData(guidLow);
1541 if (!slave)
1542 {
1543 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (guid) '%u' not found in gameobject table", guidLow);
1544 error = true;
1545 break;
1546 }
1547
1548 CreatureData const* master = GetCreatureData(linkedGuidLow);
1549 if (!master)
1550 {
1551 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature (linkedGuid) '%u' not found in creature table", linkedGuidLow);
1552 error = true;
1553 break;
1554 }
1555
1556 MapEntry const* const map = sMapStore.LookupEntry(master->spawnPoint.GetMapId());
1557 if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId()))
1558 {
1559 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '%u' linking to Creature '%u' on an unpermitted map.", guidLow, linkedGuidLow);
1560 error = true;
1561 break;
1562 }
1563
1564 if (!(master->spawnMask & slave->spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
1565 {
1566 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '%u' linking to Creature '%u' with not corresponding spawnMask", guidLow, linkedGuidLow);
1567 error = true;
1568 break;
1569 }
1570
1571 guid = ObjectGuid(HighGuid::GameObject, slave->id, guidLow);
1572 linkedGuid = ObjectGuid(HighGuid::Unit, master->id, linkedGuidLow);
1573 break;
1574 }
1575 }
1576
1577 if (!error)
1578 _linkedRespawnStore[guid] = linkedGuid;
1579 }
1580 while (result->NextRow());
1581
1582 TC_LOG_INFO("server.loading", ">> Loaded " UI64FMTD " linked respawns in %u ms", uint64(_linkedRespawnStore.size()), GetMSTimeDiffToNow(oldMSTime));
1583}
1584
1585bool ObjectMgr::SetCreatureLinkedRespawn(ObjectGuid::LowType guidLow, ObjectGuid::LowType linkedGuidLow)
1586{
1587 if (!guidLow)
1588 return false;
1589
1590 CreatureData const* master = GetCreatureData(guidLow);
1591 ASSERT(master);
1592 ObjectGuid guid(HighGuid::Unit, master->id, guidLow);
1593
1594 if (!linkedGuidLow) // we're removing the linking
1595 {
1596 _linkedRespawnStore.erase(guid);
1597 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN);
1598 stmt->setUInt32(0, guidLow);
1599 stmt->setUInt32(1, LINKED_RESPAWN_CREATURE_TO_CREATURE);
1600 WorldDatabase.Execute(stmt);
1601 return true;
1602 }
1603
1604 CreatureData const* slave = GetCreatureData(linkedGuidLow);
1605 if (!slave)
1606 {
1607 TC_LOG_ERROR("sql.sql", "Creature '%u' linking to non-existent creature '%u'.", guidLow, linkedGuidLow);
1608 return false;
1609 }
1610
1611 MapEntry const* map = sMapStore.LookupEntry(master->spawnPoint.GetMapId());
1612 if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId()))
1613 {
1614 TC_LOG_ERROR("sql.sql", "Creature '%u' linking to '%u' on an unpermitted map.", guidLow, linkedGuidLow);
1615 return false;
1616 }
1617
1618 if (!(master->spawnMask & slave->spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
1619 {
1620 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '%u' linking to '%u' with not corresponding spawnMask", guidLow, linkedGuidLow);
1621 return false;
1622 }
1623
1624 ObjectGuid linkedGuid(HighGuid::Unit, slave->id, linkedGuidLow);
1625
1626 _linkedRespawnStore[guid] = linkedGuid;
1627 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_REP_LINKED_RESPAWN);
1628 stmt->setUInt32(0, guidLow);
1629 stmt->setUInt32(1, linkedGuidLow);
1630 stmt->setUInt32(2, LINKED_RESPAWN_CREATURE_TO_CREATURE);
1631 WorldDatabase.Execute(stmt);
1632 return true;
1633}
1634
1635void ObjectMgr::LoadTempSummons()
1636{
1637 uint32 oldMSTime = getMSTime();
1638
1639 _tempSummonDataStore.clear(); // needed for reload case
1640
1641 // 0 1 2 3 4 5 6 7 8 9
1642 QueryResult result = WorldDatabase.Query("SELECT summonerId, summonerType, groupId, entry, position_x, position_y, position_z, orientation, summonType, summonTime FROM creature_summon_groups");
1643
1644 if (!result)
1645 {
1646 TC_LOG_INFO("server.loading", ">> Loaded 0 temp summons. DB table `creature_summon_groups` is empty.");
1647 return;
1648 }
1649
1650 uint32 count = 0;
1651 do
1652 {
1653 Field* fields = result->Fetch();
1654
1655 uint32 summonerId = fields[0].GetUInt32();
1656 SummonerType summonerType = SummonerType(fields[1].GetUInt8());
1657 uint8 group = fields[2].GetUInt8();
1658
1659 switch (summonerType)
1660 {
1661 case SUMMONER_TYPE_CREATURE:
1662 if (!GetCreatureTemplate(summonerId))
1663 {
1664 TC_LOG_ERROR("sql.sql", "Table `creature_summon_groups` has summoner with non existing entry %u for creature summoner type, skipped.", summonerId);
1665 continue;
1666 }
1667 break;
1668 case SUMMONER_TYPE_GAMEOBJECT:
1669 if (!GetGameObjectTemplate(summonerId))
1670 {
1671 TC_LOG_ERROR("sql.sql", "Table `creature_summon_groups` has summoner with non existing entry %u for gameobject summoner type, skipped.", summonerId);
1672 continue;
1673 }
1674 break;
1675 case SUMMONER_TYPE_MAP:
1676 if (!sMapStore.LookupEntry(summonerId))
1677 {
1678 TC_LOG_ERROR("sql.sql", "Table `creature_summon_groups` has summoner with non existing entry %u for map summoner type, skipped.", summonerId);
1679 continue;
1680 }
1681 break;
1682 default:
1683 TC_LOG_ERROR("sql.sql", "Table `creature_summon_groups` has unhandled summoner type %u for summoner %u, skipped.", summonerType, summonerId);
1684 continue;
1685 }
1686
1687 TempSummonData data;
1688 data.entry = fields[3].GetUInt32();
1689
1690 if (!GetCreatureTemplate(data.entry))
1691 {
1692 TC_LOG_ERROR("sql.sql", "Table `creature_summon_groups` has creature in group [Summoner ID: %u, Summoner Type: %u, Group ID: %u] with non existing creature entry %u, skipped.", summonerId, summonerType, group, data.entry);
1693 continue;
1694 }
1695
1696 float posX = fields[4].GetFloat();
1697 float posY = fields[5].GetFloat();
1698 float posZ = fields[6].GetFloat();
1699 float orientation = fields[7].GetFloat();
1700
1701 data.pos.Relocate(posX, posY, posZ, orientation);
1702
1703 data.type = TempSummonType(fields[8].GetUInt8());
1704
1705 if (data.type > TEMPSUMMON_MANUAL_DESPAWN)
1706 {
1707 TC_LOG_ERROR("sql.sql", "Table `creature_summon_groups` has unhandled temp summon type %u in group [Summoner ID: %u, Summoner Type: %u, Group ID: %u] for creature entry %u, skipped.", data.type, summonerId, summonerType, group, data.entry);
1708 continue;
1709 }
1710
1711 data.time = fields[9].GetUInt32();
1712
1713 TempSummonGroupKey key(summonerId, summonerType, group);
1714 _tempSummonDataStore[key].push_back(data);
1715
1716 ++count;
1717
1718 } while (result->NextRow());
1719
1720 TC_LOG_INFO("server.loading", ">> Loaded %u temp summons in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
1721}
1722
1723void ObjectMgr::LoadCreatures()
1724{
1725 uint32 oldMSTime = getMSTime();
1726
1727 // 0 1 2 3 4 5 6 7 8 9 10
1728 QueryResult result = WorldDatabase.Query("SELECT creature.guid, id, map, position_x, position_y, position_z, orientation, modelid, equipment_id, spawntimesecs, spawndist, "
1729 // 11 12 13 14 15 16 17 18 19 20 21
1730 "currentwaypoint, curhealth, curmana, MovementType, spawnMask, phaseMask, eventEntry, pool_entry, creature.npcflag, creature.unit_flags, creature.dynamicflags, "
1731 // 22
1732 "creature.ScriptName "
1733 "FROM creature "
1734 "LEFT OUTER JOIN game_event_creature ON creature.guid = game_event_creature.guid "
1735 "LEFT OUTER JOIN pool_creature ON creature.guid = pool_creature.guid");
1736
1737 if (!result)
1738 {
1739 TC_LOG_INFO("server.loading", ">> Loaded 0 creatures. DB table `creature` is empty.");
1740 return;
1741 }
1742
1743 // Build single time for check spawnmask
1744 std::map<uint32, uint32> spawnMasks;
1745 for (uint32 i = 0; i < sMapStore.GetNumRows(); ++i)
1746 if (sMapStore.LookupEntry(i))
1747 for (uint8 k = 0; k < MAX_DIFFICULTY; ++k)
1748 if (GetMapDifficultyData(i, Difficulty(k)))
1749 spawnMasks[i] |= (1 << k);
1750
1751 _creatureDataStore.rehash(result->GetRowCount());
1752
1753 do
1754 {
1755 Field* fields = result->Fetch();
1756
1757 ObjectGuid::LowType guid = fields[0].GetUInt32();
1758 uint32 entry = fields[1].GetUInt32();
1759
1760 CreatureTemplate const* cInfo = GetCreatureTemplate(entry);
1761 if (!cInfo)
1762 {
1763 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u) with non existing creature entry %u, skipped.", guid, entry);
1764 continue;
1765 }
1766
1767 CreatureData& data = _creatureDataStore[guid];
1768 data.spawnId = guid;
1769 data.id = entry;
1770 data.spawnPoint.WorldRelocate(fields[2].GetUInt16(), fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat(), fields[6].GetFloat());
1771 data.displayid = fields[7].GetUInt32();
1772 data.equipmentId = fields[8].GetInt8();
1773 data.spawntimesecs = fields[9].GetUInt32();
1774 data.spawndist = fields[10].GetFloat();
1775 data.currentwaypoint= fields[11].GetUInt32();
1776 data.curhealth = fields[12].GetUInt32();
1777 data.curmana = fields[13].GetUInt32();
1778 data.movementType = fields[14].GetUInt8();
1779 data.spawnMask = fields[15].GetUInt8();
1780 data.phaseMask = fields[16].GetUInt32();
1781 int16 gameEvent = fields[17].GetInt8();
1782 uint32 PoolId = fields[18].GetUInt32();
1783 data.npcflag = fields[19].GetUInt32();
1784 data.unit_flags = fields[20].GetUInt32();
1785 data.dynamicflags = fields[21].GetUInt32();
1786 data.scriptId = GetScriptId(fields[22].GetString());
1787 data.spawnGroupData = &_spawnGroupDataStore[0];
1788
1789 MapEntry const* mapEntry = sMapStore.LookupEntry(data.spawnPoint.GetMapId());
1790 if (!mapEntry)
1791 {
1792 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u) that spawned at nonexistent map (Id: %u), skipped.", guid, data.spawnPoint.GetMapId());
1793 continue;
1794 }
1795
1796 // Skip spawnMask check for transport maps
1797 if (!IsTransportMap(data.spawnPoint.GetMapId()) && data.spawnMask & ~spawnMasks[data.spawnPoint.GetMapId()])
1798 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u) that have wrong spawn mask %u including unsupported difficulty modes for map (Id: %u).", guid, data.spawnMask, data.spawnPoint.GetMapId());
1799
1800 bool ok = true;
1801 for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && ok; ++diff)
1802 {
1803 if (_difficultyEntries[diff].find(data.id) != _difficultyEntries[diff].end())
1804 {
1805 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u) that is listed as difficulty %u template (entry: %u) in `creature_template`, skipped.",
1806 guid, diff + 1, data.id);
1807 ok = false;
1808 }
1809 }
1810 if (!ok)
1811 continue;
1812
1813 // -1 random, 0 no equipment
1814 if (data.equipmentId != 0)
1815 {
1816 if (!GetEquipmentInfo(data.id, data.equipmentId))
1817 {
1818 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (Entry: %u) with equipment_id %u not found in table `creature_equip_template`, set to no equipment.", data.id, data.equipmentId);
1819 data.equipmentId = 0;
1820 }
1821 }
1822
1823 if (cInfo->flags_extra & CREATURE_FLAG_EXTRA_INSTANCE_BIND)
1824 {
1825 if (!mapEntry->IsDungeon())
1826 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u Entry: %u) with `creature_template`.`flags_extra` including CREATURE_FLAG_EXTRA_INSTANCE_BIND but creature is not in instance.", guid, data.id);
1827 }
1828
1829 if (data.movementType >= MAX_DB_MOTION_TYPE)
1830 {
1831 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u Entry: %u) with wrong movement generator type (%u), ignored and set to IDLE.", guid, data.id, data.movementType);
1832 data.movementType = IDLE_MOTION_TYPE;
1833 }
1834
1835 if (data.spawndist < 0.0f)
1836 {
1837 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u Entry: %u) with `spawndist`< 0, set to 0.", guid, data.id);
1838 data.spawndist = 0.0f;
1839 }
1840 else if (data.movementType == RANDOM_MOTION_TYPE)
1841 {
1842 if (data.spawndist == 0.0f)
1843 {
1844 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u Entry: %u) with `MovementType`=1 (random movement) but with `spawndist`=0, replace by idle movement type (0).", guid, data.id);
1845 data.movementType = IDLE_MOTION_TYPE;
1846 }
1847 }
1848 else if (data.movementType == IDLE_MOTION_TYPE)
1849 {
1850 if (data.spawndist != 0.0f)
1851 {
1852 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u Entry: %u) with `MovementType`=0 (idle) have `spawndist`<>0, set to 0.", guid, data.id);
1853 data.spawndist = 0.0f;
1854 }
1855 }
1856
1857 if (data.phaseMask == 0)
1858 {
1859 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u Entry: %u) with `phaseMask`=0 (not visible for anyone), set to 1.", guid, data.id);
1860 data.phaseMask = 1;
1861 }
1862
1863 if (sWorld->getBoolConfig(CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA))
1864 {
1865 uint32 zoneId = 0;
1866 uint32 areaId = 0;
1867 sMapMgr->GetZoneAndAreaId(zoneId, areaId, data.spawnPoint);
1868
1869 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_CREATURE_ZONE_AREA_DATA);
1870
1871 stmt->setUInt32(0, zoneId);
1872 stmt->setUInt32(1, areaId);
1873 stmt->setUInt64(2, guid);
1874
1875 WorldDatabase.Execute(stmt);
1876 }
1877
1878 // Add to grid if not managed by the game event or pool system
1879 if (gameEvent == 0 && PoolId == 0)
1880 AddCreatureToGrid(guid, &data);
1881 }
1882 while (result->NextRow());
1883
1884 TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " creatures in %u ms", _creatureDataStore.size(), GetMSTimeDiffToNow(oldMSTime));
1885}
1886
1887void ObjectMgr::AddCreatureToGrid(ObjectGuid::LowType guid, CreatureData const* data)
1888{
1889 uint8 mask = data->spawnMask;
1890 for (uint8 i = 0; mask != 0; i++, mask >>= 1)
1891 {
1892 if (mask & 1)
1893 {
1894 CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY());
1895 CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), i)][cellCoord.GetId()];
1896 cell_guids.creatures.insert(guid);
1897 }
1898 }
1899}
1900
1901void ObjectMgr::RemoveCreatureFromGrid(ObjectGuid::LowType guid, CreatureData const* data)
1902{
1903 uint8 mask = data->spawnMask;
1904 for (uint8 i = 0; mask != 0; i++, mask >>= 1)
1905 {
1906 if (mask & 1)
1907 {
1908 CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY());
1909 CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), i)][cellCoord.GetId()];
1910 cell_guids.creatures.erase(guid);
1911 }
1912 }
1913}
1914
1915ObjectGuid::LowType ObjectMgr::AddGameObjectData(uint32 entry, uint32 mapId, Position const& pos, QuaternionData const& rot, uint32 spawntimedelay /*= 0*/)
1916{
1917 GameObjectTemplate const* goinfo = GetGameObjectTemplate(entry);
1918 if (!goinfo)
1919 return 0;
1920
1921 Map* map = sMapMgr->CreateBaseMap(mapId);
1922 if (!map)
1923 return 0;
1924
1925 ObjectGuid::LowType spawnId = GenerateGameObjectSpawnId();
1926
1927 GameObjectData& data = NewOrExistGameObjectData(spawnId);
1928 data.spawnId = spawnId;
1929 data.id = entry;
1930 data.spawnPoint.WorldRelocate(mapId,pos);
1931 data.rotation = rot;
1932 data.spawntimesecs = spawntimedelay;
1933 data.animprogress = 100;
1934 data.spawnMask = 1;
1935 data.goState = GO_STATE_READY;
1936 data.phaseMask = PHASEMASK_NORMAL;
1937 data.artKit = goinfo->type == GAMEOBJECT_TYPE_CAPTURE_POINT ? 21 : 0;
1938 data.dbData = false;
1939 data.spawnGroupData = GetLegacySpawnGroup();
1940
1941 AddGameobjectToGrid(spawnId, &data);
1942
1943 // Spawn if necessary (loaded grids only)
1944 // We use spawn coords to spawn
1945 if (!map->Instanceable() && map->IsGridLoaded(data.spawnPoint))
1946 {
1947 GameObject* go = new GameObject;
1948 if (!go->LoadFromDB(spawnId, map, true))
1949 {
1950 TC_LOG_ERROR("misc", "AddGameObjectData: cannot add gameobject entry %u to map", entry);
1951 delete go;
1952 return 0;
1953 }
1954 }
1955
1956 TC_LOG_DEBUG("maps", "AddGameObjectData: dbguid %u entry %u map %u pos %s", spawnId, entry, mapId, data.spawnPoint.ToString().c_str());
1957
1958 return spawnId;
1959}
1960
1961
1962ObjectGuid::LowType ObjectMgr::AddCreatureData(uint32 entry, uint32 mapId, Position const& pos, uint32 spawntimedelay /*= 0*/)
1963{
1964 CreatureTemplate const* cInfo = GetCreatureTemplate(entry);
1965 if (!cInfo)
1966 return 0;
1967
1968 uint32 level = cInfo->minlevel == cInfo->maxlevel ? cInfo->minlevel : urand(cInfo->minlevel, cInfo->maxlevel); // Only used for extracting creature base stats
1969 CreatureBaseStats const* stats = GetCreatureBaseStats(level, cInfo->unit_class);
1970 Map* map = sMapMgr->CreateBaseMap(mapId);
1971 if (!map)
1972 return 0;
1973
1974 ObjectGuid::LowType spawnId = GenerateCreatureSpawnId();
1975 CreatureData& data = NewOrExistCreatureData(spawnId);
1976 data.spawnId = spawnId;
1977 data.id = entry;
1978 data.spawnPoint.WorldRelocate(mapId, pos);
1979 data.displayid = 0;
1980 data.equipmentId = 0;
1981 data.spawntimesecs = spawntimedelay;
1982 data.spawndist = 0;
1983 data.currentwaypoint = 0;
1984 data.curhealth = stats->GenerateHealth(cInfo);
1985 data.curmana = stats->GenerateMana(cInfo);
1986 data.movementType = cInfo->MovementType;
1987 data.spawnMask = 1;
1988 data.phaseMask = PHASEMASK_NORMAL;
1989 data.dbData = false;
1990 data.npcflag = cInfo->npcflag;
1991 data.unit_flags = cInfo->unit_flags;
1992 data.dynamicflags = cInfo->dynamicflags;
1993 data.spawnGroupData = GetLegacySpawnGroup();
1994
1995 AddCreatureToGrid(spawnId, &data);
1996
1997 // We use spawn coords to spawn
1998 if (!map->Instanceable() && !map->IsRemovalGrid(data.spawnPoint))
1999 {
2000 Creature* creature = new Creature();
2001 if (!creature->LoadFromDB(spawnId, map, true, true))
2002 {
2003 TC_LOG_ERROR("misc", "AddCreature: Cannot add creature entry %u to map", entry);
2004 delete creature;
2005 return 0;
2006 }
2007 }
2008
2009 return spawnId;
2010}
2011
2012void ObjectMgr::LoadGameObjects()
2013{
2014 uint32 oldMSTime = getMSTime();
2015
2016 // 0 1 2 3 4 5 6
2017 QueryResult result = WorldDatabase.Query("SELECT gameobject.guid, id, map, position_x, position_y, position_z, orientation, "
2018 // 7 8 9 10 11 12 13 14 15 16 17
2019 "rotation0, rotation1, rotation2, rotation3, spawntimesecs, animprogress, state, spawnMask, phaseMask, eventEntry, pool_entry, "
2020 // 18
2021 "ScriptName "
2022 "FROM gameobject LEFT OUTER JOIN game_event_gameobject ON gameobject.guid = game_event_gameobject.guid "
2023 "LEFT OUTER JOIN pool_gameobject ON gameobject.guid = pool_gameobject.guid");
2024
2025 if (!result)
2026 {
2027 TC_LOG_INFO("server.loading", ">> Loaded 0 gameobjects. DB table `gameobject` is empty.");
2028 return;
2029 }
2030
2031 // build single time for check spawnmask
2032 std::map<uint32, uint32> spawnMasks;
2033 for (uint32 i = 0; i < sMapStore.GetNumRows(); ++i)
2034 if (sMapStore.LookupEntry(i))
2035 for (uint8 k = 0; k < MAX_DIFFICULTY; ++k)
2036 if (GetMapDifficultyData(i, Difficulty(k)))
2037 spawnMasks[i] |= (1 << k);
2038
2039 _gameObjectDataStore.rehash(result->GetRowCount());
2040
2041 do
2042 {
2043 Field* fields = result->Fetch();
2044
2045 ObjectGuid::LowType guid = fields[0].GetUInt32();
2046 uint32 entry = fields[1].GetUInt32();
2047
2048 GameObjectTemplate const* gInfo = GetGameObjectTemplate(entry);
2049 if (!gInfo)
2050 {
2051 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u) with non existing gameobject entry %u, skipped.", guid, entry);
2052 continue;
2053 }
2054
2055 if (!gInfo->displayId)
2056 {
2057 switch (gInfo->type)
2058 {
2059 case GAMEOBJECT_TYPE_TRAP:
2060 case GAMEOBJECT_TYPE_SPELL_FOCUS:
2061 break;
2062 default:
2063 TC_LOG_ERROR("sql.sql", "Gameobject (GUID: %u Entry %u GoType: %u) doesn't have a displayId (%u), not loaded.", guid, entry, gInfo->type, gInfo->displayId);
2064 break;
2065 }
2066 }
2067
2068 if (gInfo->displayId && !sGameObjectDisplayInfoStore.LookupEntry(gInfo->displayId))
2069 {
2070 TC_LOG_ERROR("sql.sql", "Gameobject (GUID: %u Entry %u GoType: %u) has an invalid displayId (%u), not loaded.", guid, entry, gInfo->type, gInfo->displayId);
2071 continue;
2072 }
2073
2074 GameObjectData& data = _gameObjectDataStore[guid];
2075
2076 data.spawnId = guid;
2077 data.id = entry;
2078 data.spawnPoint.WorldRelocate(fields[2].GetUInt16(), fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat(), fields[6].GetFloat());
2079 data.rotation.x = fields[7].GetFloat();
2080 data.rotation.y = fields[8].GetFloat();
2081 data.rotation.z = fields[9].GetFloat();
2082 data.rotation.w = fields[10].GetFloat();
2083 data.spawntimesecs = fields[11].GetInt32();
2084 data.spawnGroupData = &_spawnGroupDataStore[0];
2085
2086 MapEntry const* mapEntry = sMapStore.LookupEntry(data.spawnPoint.GetMapId());
2087 if (!mapEntry)
2088 {
2089 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) spawned on a non-existed map (Id: %u), skip", guid, data.id, data.spawnPoint.GetMapId());
2090 continue;
2091 }
2092
2093 if (data.spawntimesecs == 0 && gInfo->IsDespawnAtAction())
2094 {
2095 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) with `spawntimesecs` (0) value, but the gameobejct is marked as despawnable at action.", guid, data.id);
2096 }
2097
2098 data.animprogress = fields[12].GetUInt8();
2099 data.artKit = 0;
2100
2101 uint32 go_state = fields[13].GetUInt8();
2102 if (go_state >= MAX_GO_STATE)
2103 {
2104 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) with invalid `state` (%u) value, skip", guid, data.id, go_state);
2105 continue;
2106 }
2107 data.goState = GOState(go_state);
2108
2109 data.spawnMask = fields[14].GetUInt8();
2110
2111 if (!IsTransportMap(data.spawnPoint.GetMapId()) && data.spawnMask & ~spawnMasks[data.spawnPoint.GetMapId()])
2112 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) that has wrong spawn mask %u including unsupported difficulty modes for map (Id: %u), skip", guid, data.id, data.spawnMask, data.spawnPoint.GetMapId());
2113
2114 data.phaseMask = fields[15].GetUInt32();
2115 int16 gameEvent = fields[16].GetInt8();
2116 uint32 PoolId = fields[17].GetUInt32();
2117
2118 data.scriptId = GetScriptId(fields[18].GetString());
2119
2120 if (data.rotation.x < -1.0f || data.rotation.x > 1.0f)
2121 {
2122 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) with invalid rotationX (%f) value, skip", guid, data.id, data.rotation.x);
2123 continue;
2124 }
2125
2126 if (data.rotation.y < -1.0f || data.rotation.y > 1.0f)
2127 {
2128 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) with invalid rotationY (%f) value, skip", guid, data.id, data.rotation.y);
2129 continue;
2130 }
2131
2132 if (data.rotation.z < -1.0f || data.rotation.z > 1.0f)
2133 {
2134 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) with invalid rotationZ (%f) value, skip", guid, data.id, data.rotation.z);
2135 continue;
2136 }
2137
2138 if (data.rotation.w < -1.0f || data.rotation.w > 1.0f)
2139 {
2140 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) with invalid rotationW (%f) value, skip", guid, data.id, data.rotation.w);
2141 continue;
2142 }
2143
2144 if (!MapManager::IsValidMapCoord(data.spawnPoint))
2145 {
2146 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) with invalid coordinates, skip", guid, data.id);
2147 continue;
2148 }
2149
2150 if (data.phaseMask == 0)
2151 {
2152 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) with `phaseMask`=0 (not visible for anyone), set to 1.", guid, data.id);
2153 data.phaseMask = 1;
2154 }
2155
2156 if (sWorld->getBoolConfig(CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA))
2157 {
2158 uint32 zoneId = 0;
2159 uint32 areaId = 0;
2160 sMapMgr->GetZoneAndAreaId(zoneId, areaId, data.spawnPoint);
2161
2162 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_GAMEOBJECT_ZONE_AREA_DATA);
2163
2164 stmt->setUInt32(0, zoneId);
2165 stmt->setUInt32(1, areaId);
2166 stmt->setUInt64(2, guid);
2167
2168 WorldDatabase.Execute(stmt);
2169 }
2170
2171 if (gameEvent == 0 && PoolId == 0) // if not this is to be managed by GameEvent System or Pool system
2172 AddGameobjectToGrid(guid, &data);
2173 }
2174 while (result->NextRow());
2175
2176 TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " gameobjects in %u ms", _gameObjectDataStore.size(), GetMSTimeDiffToNow(oldMSTime));
2177}
2178
2179void ObjectMgr::LoadSpawnGroupTemplates()
2180{
2181 uint32 oldMSTime = getMSTime();
2182
2183 // 0 1 2
2184 QueryResult result = WorldDatabase.Query("SELECT groupId, groupName, groupFlags FROM spawn_group_template");
2185
2186 if (result)
2187 {
2188 do
2189 {
2190 Field* fields = result->Fetch();
2191 uint32 groupId = fields[0].GetUInt32();
2192 SpawnGroupTemplateData& group = _spawnGroupDataStore[groupId];
2193 group.groupId = groupId;
2194 group.name = fields[1].GetString();
2195 group.mapId = SPAWNGROUP_MAP_UNSET;
2196 uint32 flags = fields[2].GetUInt32();
2197 if (flags & ~SPAWNGROUP_FLAGS_ALL)
2198 {
2199 flags &= SPAWNGROUP_FLAGS_ALL;
2200 TC_LOG_ERROR("sql.sql", "Invalid spawn group flag %u on group ID %u (%s), reduced to valid flag %u.", flags, groupId, group.name.c_str(), uint32(group.flags));
2201 }
2202 if (flags & SPAWNGROUP_FLAG_SYSTEM && flags & SPAWNGROUP_FLAG_MANUAL_SPAWN)
2203 {
2204 flags &= ~SPAWNGROUP_FLAG_MANUAL_SPAWN;
2205 TC_LOG_ERROR("sql.sql", "System spawn group %u (%s) has invalid manual spawn flag. Ignored.", groupId, group.name.c_str());
2206 }
2207 group.flags = SpawnGroupFlags(flags);
2208 } while (result->NextRow());
2209 }
2210
2211 if (_spawnGroupDataStore.find(0) == _spawnGroupDataStore.end())
2212 {
2213 TC_LOG_ERROR("sql.sql", "Default spawn group (index 0) is missing from DB! Manually inserted.");
2214 SpawnGroupTemplateData& data = _spawnGroupDataStore[0];
2215 data.groupId = 0;
2216 data.name = "Default Group";
2217 data.mapId = 0;
2218 data.flags = SPAWNGROUP_FLAG_SYSTEM;
2219 }
2220 if (_spawnGroupDataStore.find(1) == _spawnGroupDataStore.end())
2221 {
2222 TC_LOG_ERROR("sql.sql", "Default legacy spawn group (index 1) is missing from DB! Manually inserted.");
2223 SpawnGroupTemplateData&data = _spawnGroupDataStore[1];
2224 data.groupId = 1;
2225 data.name = "Legacy Group";
2226 data.mapId = 0;
2227 data.flags = SpawnGroupFlags(SPAWNGROUP_FLAG_SYSTEM | SPAWNGROUP_FLAG_COMPATIBILITY_MODE);
2228 }
2229
2230 if (result)
2231 TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " spawn group templates in %u ms", _spawnGroupDataStore.size(), GetMSTimeDiffToNow(oldMSTime));
2232 else
2233 TC_LOG_INFO("server.loading", ">> Loaded 0 spawn group templates. DB table `spawn_group_template` is empty.");
2234
2235 return;
2236}
2237
2238void ObjectMgr::LoadSpawnGroups()
2239{
2240 uint32 oldMSTime = getMSTime();
2241
2242 // 0 1 2
2243 QueryResult result = WorldDatabase.Query("SELECT groupId, spawnType, spawnId FROM spawn_group");
2244
2245 if (!result)
2246 {
2247 TC_LOG_INFO("server.loading", ">> Loaded 0 spawn group members. DB table `spawn_group` is empty.");
2248 return;
2249 }
2250
2251 uint32 numMembers = 0;
2252 do
2253 {
2254 Field* fields = result->Fetch();
2255 uint32 groupId = fields[0].GetUInt32();
2256 SpawnObjectType spawnType;
2257 {
2258 uint32 type = fields[1].GetUInt8();
2259 if (type >= SPAWN_TYPE_MAX)
2260 {
2261 TC_LOG_ERROR("sql.sql", "Spawn data with invalid type %u listed for spawn group %u. Skipped.", type, groupId);
2262 continue;
2263 }
2264 spawnType = SpawnObjectType(type);
2265 }
2266 ObjectGuid::LowType spawnId = fields[2].GetUInt32();
2267
2268 SpawnData const* data = GetSpawnData(spawnType, spawnId);
2269 if (!data)
2270 {
2271 TC_LOG_ERROR("sql.sql", "Spawn data with ID (%u,%u) not found, but is listed as a member of spawn group %u!", uint32(spawnType), spawnId, groupId);
2272 continue;
2273 }
2274 else if (data->spawnGroupData->groupId)
2275 {
2276 TC_LOG_ERROR("sql.sql", "Spawn with ID (%u,%u) is listed as a member of spawn group %u, but is already a member of spawn group %u. Skipping.", uint32(spawnType), spawnId, groupId, data->spawnGroupData->groupId);
2277 continue;
2278 }
2279 auto it = _spawnGroupDataStore.find(groupId);
2280 if (it == _spawnGroupDataStore.end())
2281 {
2282 TC_LOG_ERROR("sql.sql", "Spawn group %u assigned to spawn ID (%u,%u), but group is found!", groupId, uint32(spawnType), spawnId);
2283 continue;
2284 }
2285 else
2286 {
2287 SpawnGroupTemplateData& groupTemplate = it->second;
2288 if (groupTemplate.mapId == SPAWNGROUP_MAP_UNSET)
2289 groupTemplate.mapId = data->spawnPoint.GetMapId();
2290 else if (groupTemplate.mapId != data->spawnPoint.GetMapId() && !(groupTemplate.flags & SPAWNGROUP_FLAG_SYSTEM))
2291 {
2292 TC_LOG_ERROR("sql.sql", "Spawn group %u has map ID %u, but spawn (%u,%u) has map id %u - spawn NOT added to group!", groupId, groupTemplate.mapId, uint32(spawnType), spawnId, data->spawnPoint.GetMapId());
2293 continue;
2294 }
2295 const_cast<SpawnData*>(data)->spawnGroupData = &groupTemplate;
2296 if (!(groupTemplate.flags & SPAWNGROUP_FLAG_SYSTEM))
2297 _spawnGroupMapStore.emplace(groupId, data);
2298 ++numMembers;
2299 }
2300 } while (result->NextRow());
2301
2302 TC_LOG_INFO("server.loading", ">> Loaded %u spawn group members in %u ms", numMembers, GetMSTimeDiffToNow(oldMSTime));
2303}
2304
2305void ObjectMgr::LoadInstanceSpawnGroups()
2306{
2307 uint32 oldMSTime = getMSTime();
2308
2309 // 0 1 2 3 4
2310 QueryResult result = WorldDatabase.Query("SELECT instanceMapId, bossStateId, bossStates, spawnGroupId, flags FROM instance_spawn_groups");
2311
2312 if (!result)
2313 {
2314 TC_LOG_INFO("server.loading", ">> Loaded 0 instance spawn groups. DB table `instance_spawn_groups` is empty.");
2315 return;
2316 }
2317
2318 uint32 n = 0;
2319 do
2320 {
2321 Field* fields = result->Fetch();
2322 uint32 const spawnGroupId = fields[3].GetUInt32();
2323 auto it = _spawnGroupDataStore.find(spawnGroupId);
2324 if (it == _spawnGroupDataStore.end() || (it->second.flags & SPAWNGROUP_FLAG_SYSTEM))
2325 {
2326 TC_LOG_ERROR("sql.sql", "Invalid spawn group %u specified for instance %u. Skipped.", spawnGroupId, fields[0].GetUInt16());
2327 continue;
2328 }
2329
2330 uint16 const instanceMapId = fields[0].GetUInt16();
2331 auto& vector = _instanceSpawnGroupStore[instanceMapId];
2332 vector.emplace_back();
2333 InstanceSpawnGroupInfo& info = vector.back();
2334 info.SpawnGroupId = spawnGroupId;
2335 info.BossStateId = fields[1].GetUInt8();
2336
2337 uint8 const ALL_STATES = (1 << TO_BE_DECIDED) - 1;
2338 uint8 const states = fields[2].GetUInt8();
2339 if (states & ~ALL_STATES)
2340 {
2341 info.BossStates = states & ALL_STATES;
2342 TC_LOG_ERROR("sql.sql", "Instance spawn group (%u,%u) had invalid boss state mask %u - truncated to %u.", instanceMapId, spawnGroupId, states, info.BossStates);
2343 }
2344 else
2345 info.BossStates = states;
2346
2347 uint8 const flags = fields[4].GetUInt8();
2348 if (flags & ~InstanceSpawnGroupInfo::FLAG_ALL)
2349 {
2350 info.Flags = flags & InstanceSpawnGroupInfo::FLAG_ALL;
2351 TC_LOG_ERROR("sql.sql", "Instance spawn group (%u,%u) had invalid flags %u - truncated to %u.", instanceMapId, spawnGroupId, flags, info.Flags);
2352 }
2353 else
2354 info.Flags = flags;
2355
2356 ++n;
2357 } while (result->NextRow());
2358
2359 TC_LOG_INFO("server.loading", ">> Loaded %u instance spawn groups in %u ms", n, GetMSTimeDiffToNow(oldMSTime));
2360}
2361
2362void ObjectMgr::OnDeleteSpawnData(SpawnData const* data)
2363{
2364 auto templateIt = _spawnGroupDataStore.find(data->spawnGroupData->groupId);
2365 ASSERT(templateIt != _spawnGroupDataStore.end(), "Creature data for (%u,%u) is being deleted and has invalid spawn group index %u!", uint32(data->type), data->spawnId, data->spawnGroupData->groupId);
2366 if (templateIt->second.flags & SPAWNGROUP_FLAG_SYSTEM) // system groups don't store their members in the map
2367 return;
2368
2369 auto pair = _spawnGroupMapStore.equal_range(data->spawnGroupData->groupId);
2370 for (auto it = pair.first; it != pair.second; ++it)
2371 {
2372 if (it->second != data)
2373 continue;
2374 _spawnGroupMapStore.erase(it);
2375 return;
2376 }
2377 ASSERT(false, "Spawn data (%u,%u) being removed is member of spawn group %u, but not actually listed in the lookup table for that group!", uint32(data->type), data->spawnId, data->spawnGroupData->groupId);
2378}
2379
2380void ObjectMgr::AddGameobjectToGrid(ObjectGuid::LowType guid, GameObjectData const* data)
2381{
2382 uint8 mask = data->spawnMask;
2383 for (uint8 i = 0; mask != 0; i++, mask >>= 1)
2384 {
2385 if (mask & 1)
2386 {
2387 CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY());
2388 CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), i)][cellCoord.GetId()];
2389 cell_guids.gameobjects.insert(guid);
2390 }
2391 }
2392}
2393
2394void ObjectMgr::RemoveGameobjectFromGrid(ObjectGuid::LowType guid, GameObjectData const* data)
2395{
2396 uint8 mask = data->spawnMask;
2397 for (uint8 i = 0; mask != 0; i++, mask >>= 1)
2398 {
2399 if (mask & 1)
2400 {
2401 CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY());
2402 CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), i)][cellCoord.GetId()];
2403 cell_guids.gameobjects.erase(guid);
2404 }
2405 }
2406}
2407
2408void ObjectMgr::LoadItemLocales()
2409{
2410 uint32 oldMSTime = getMSTime();
2411
2412 _itemLocaleStore.clear(); // need for reload case
2413
2414 // 0 1 2 3
2415 QueryResult result = WorldDatabase.Query("SELECT ID, locale, Name, Description FROM item_template_locale");
2416 if (!result)
2417 return;
2418
2419 do
2420 {
2421 Field* fields = result->Fetch();
2422
2423 uint32 id = fields[0].GetUInt32();
2424 std::string localeName = fields[1].GetString();
2425 std::string Name = fields[2].GetString();
2426 std::string Description = fields[3].GetString();
2427
2428 ItemLocale& data = _itemLocaleStore[id];
2429 LocaleConstant locale = GetLocaleByName(localeName);
2430 if (locale == LOCALE_enUS)
2431 continue;
2432
2433 AddLocaleString(Name, locale, data.Name);
2434 AddLocaleString(Description, locale, data.Description);
2435 } while (result->NextRow());
2436
2437 TC_LOG_INFO("server.loading", ">> Loaded %u Item locale strings in %u ms", uint32(_itemLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime));
2438}
2439
2440void ObjectMgr::LoadItemTemplates()
2441{
2442 uint32 oldMSTime = getMSTime();
2443
2444 // 0 1 2 3 4 5 6 7 8 9 10 11 12
2445 QueryResult result = WorldDatabase.Query("SELECT entry, class, subclass, SoundOverrideSubclass, name, displayid, Quality, Flags, FlagsExtra, BuyCount, BuyPrice, SellPrice, InventoryType, "
2446 // 13 14 15 16 17 18 19 20
2447 "AllowableClass, AllowableRace, ItemLevel, RequiredLevel, RequiredSkill, RequiredSkillRank, requiredspell, requiredhonorrank, "
2448 // 21 22 23 24 25 26 27 28
2449 "RequiredCityRank, RequiredReputationFaction, RequiredReputationRank, maxcount, stackable, ContainerSlots, StatsCount, stat_type1, "
2450 // 29 30 31 32 33 34 35 36 37 38
2451 "stat_value1, stat_type2, stat_value2, stat_type3, stat_value3, stat_type4, stat_value4, stat_type5, stat_value5, stat_type6, "
2452 // 39 40 41 42 43 44 45 46 47
2453 "stat_value6, stat_type7, stat_value7, stat_type8, stat_value8, stat_type9, stat_value9, stat_type10, stat_value10, "
2454 // 48 49 50 51 52 53 54 55 56 57 58
2455 "ScalingStatDistribution, ScalingStatValue, dmg_min1, dmg_max1, dmg_type1, dmg_min2, dmg_max2, dmg_type2, armor, holy_res, fire_res, "
2456 // 59 60 61 62 63 64 65 66 67 68 69 70 71
2457 "nature_res, frost_res, shadow_res, arcane_res, customa_res, customb_res, customc_res, delay, ammo_type, RangedModRange, spellid_1, spelltrigger_1, spellcharges_1, "
2458 // 72 73 74 75 76 77 78
2459 "spellppmRate_1, spellcooldown_1, spellcategory_1, spellcategorycooldown_1, spellid_2, spelltrigger_2, spellcharges_2, "
2460 // 79 80 81 82 83 84 85
2461 "spellppmRate_2, spellcooldown_2, spellcategory_2, spellcategorycooldown_2, spellid_3, spelltrigger_3, spellcharges_3, "
2462 // 86 87 88 89 90 91 92
2463 "spellppmRate_3, spellcooldown_3, spellcategory_3, spellcategorycooldown_3, spellid_4, spelltrigger_4, spellcharges_4, "
2464 // 93 94 95 96 97 98 99
2465 "spellppmRate_4, spellcooldown_4, spellcategory_4, spellcategorycooldown_4, spellid_5, spelltrigger_5, spellcharges_5, "
2466 // 100 101 102 103 104 105 106 107 108
2467 "spellppmRate_5, spellcooldown_5, spellcategory_5, spellcategorycooldown_5, bonding, description, PageText, LanguageID, PageMaterial, "
2468 // 109 110 111 112 113 114 115 116 117 118 119 120
2469 "startquest, lockid, Material, sheath, RandomProperty, RandomSuffix, block, itemset, MaxDurability, area, Map, BagFamily, "
2470 // 121 122 123 124 125 126 127 128
2471 "TotemCategory, socketColor_1, socketContent_1, socketColor_2, socketContent_2, socketColor_3, socketContent_3, socketBonus, "
2472 // 129 130 131 132 133 134 135 136
2473 "GemProperties, RequiredDisenchantSkill, ArmorDamageModifier, duration, ItemLimitCategory, HolidayId, ScriptName, DisenchantID, "
2474 // 137 138 139 140
2475 "FoodType, minMoneyLoot, maxMoneyLoot, flagsCustom FROM item_template");
2476
2477 if (!result)
2478 {
2479 TC_LOG_INFO("server.loading", ">> Loaded 0 item templates. DB table `item_template` is empty.");
2480 return;
2481 }
2482
2483 _itemTemplateStore.reserve(result->GetRowCount());
2484 bool enforceDBCAttributes = sWorld->getBoolConfig(CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES);
2485
2486 do
2487 {
2488 Field* fields = result->Fetch();
2489
2490 uint32 entry = fields[0].GetUInt32();
2491 ItemTemplate& itemTemplate = _itemTemplateStore[entry];
2492
2493 itemTemplate.ItemId = entry;
2494 itemTemplate.Class = uint32(fields[1].GetUInt8());
2495 itemTemplate.SubClass = uint32(fields[2].GetUInt8());
2496 itemTemplate.SoundOverrideSubclass = int32(fields[3].GetInt8());
2497 itemTemplate.Name1 = fields[4].GetString();
2498 itemTemplate.DisplayInfoID = fields[5].GetUInt32();
2499 itemTemplate.Quality = uint32(fields[6].GetUInt8());
2500 itemTemplate.Flags = fields[7].GetUInt32();
2501 itemTemplate.Flags2 = fields[8].GetUInt32();
2502 itemTemplate.BuyCount = uint32(fields[9].GetUInt8());
2503 itemTemplate.BuyPrice = int32(fields[10].GetInt64());
2504 itemTemplate.SellPrice = fields[11].GetUInt32();
2505 itemTemplate.InventoryType = uint32(fields[12].GetUInt8());
2506 itemTemplate.AllowableClass = fields[13].GetInt32();
2507 itemTemplate.AllowableRace = fields[14].GetInt32();
2508 itemTemplate.ItemLevel = uint32(fields[15].GetUInt16());
2509 itemTemplate.RequiredLevel = uint32(fields[16].GetUInt8());
2510 itemTemplate.RequiredSkill = uint32(fields[17].GetUInt16());
2511 itemTemplate.RequiredSkillRank = uint32(fields[18].GetUInt16());
2512 itemTemplate.RequiredSpell = fields[19].GetUInt32();
2513 itemTemplate.RequiredHonorRank = fields[20].GetUInt32();
2514 itemTemplate.RequiredCityRank = fields[21].GetUInt32();
2515 itemTemplate.RequiredReputationFaction = uint32(fields[22].GetUInt16());
2516 itemTemplate.RequiredReputationRank = uint32(fields[23].GetUInt16());
2517 itemTemplate.MaxCount = fields[24].GetInt32();
2518 itemTemplate.Stackable = fields[25].GetInt32();
2519 itemTemplate.ContainerSlots = uint32(fields[26].GetUInt8());
2520 itemTemplate.StatsCount = uint32(fields[27].GetUInt8());
2521
2522 if (itemTemplate.StatsCount > MAX_ITEM_PROTO_STATS)
2523 {
2524 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has too large value in statscount (%u), replace by hardcoded limit (%u).", entry, itemTemplate.StatsCount, MAX_ITEM_PROTO_STATS);
2525 itemTemplate.StatsCount = MAX_ITEM_PROTO_STATS;
2526 }
2527
2528 for (uint8 i = 0; i < itemTemplate.StatsCount; ++i)
2529 {
2530 itemTemplate.ItemStat[i].ItemStatType = uint32(fields[28 + i*2].GetUInt8());
2531 itemTemplate.ItemStat[i].ItemStatValue = int32(fields[29 + i*2].GetInt16());
2532 }
2533
2534 itemTemplate.ScalingStatDistribution = uint32(fields[48].GetUInt16());
2535 itemTemplate.ScalingStatValue = fields[49].GetInt32();
2536
2537 for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i)
2538 {
2539 itemTemplate.Damage[i].DamageMin = fields[50 + i*3].GetFloat();
2540 itemTemplate.Damage[i].DamageMax = fields[51 + i*3].GetFloat();
2541 itemTemplate.Damage[i].DamageType = uint32(fields[52 + i*3].GetUInt8());
2542 }
2543
2544 itemTemplate.Armor = uint32(fields[56].GetUInt16());
2545 itemTemplate.HolyRes = uint32(fields[57].GetUInt8());
2546 itemTemplate.FireRes = uint32(fields[58].GetUInt8());
2547 itemTemplate.NatureRes = uint32(fields[59].GetUInt8());
2548 itemTemplate.FrostRes = uint32(fields[60].GetUInt8());
2549 itemTemplate.ShadowRes = uint32(fields[61].GetUInt8());
2550 itemTemplate.ArcaneRes = uint32(fields[62].GetUInt8());
2551 itemTemplate.CustomARes = uint32(fields[63].GetUInt8());
2552 itemTemplate.CustomBRes = uint32(fields[64].GetUInt8());
2553 itemTemplate.CustomCRes = uint32(fields[65].GetUInt8());
2554 itemTemplate.Delay = uint32(fields[66].GetUInt16());
2555 itemTemplate.AmmoType = uint32(fields[67].GetUInt8());
2556 itemTemplate.RangedModRange = fields[68].GetFloat();
2557
2558 for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
2559 {
2560 itemTemplate.Spells[i].SpellId = fields[69 + i*7 ].GetInt32();
2561 itemTemplate.Spells[i].SpellTrigger = uint32(fields[70 + i*7].GetUInt8());
2562 itemTemplate.Spells[i].SpellCharges = int32(fields[71 + i*7].GetInt16());
2563 itemTemplate.Spells[i].SpellPPMRate = fields[72 + i*7].GetFloat();
2564 itemTemplate.Spells[i].SpellCooldown = fields[73 + i*7].GetInt32();
2565 itemTemplate.Spells[i].SpellCategory = uint32(fields[74 + i*7].GetUInt16());
2566 itemTemplate.Spells[i].SpellCategoryCooldown = fields[75 + i*7].GetInt32();
2567 }
2568
2569 itemTemplate.Bonding = uint32(fields[104].GetUInt8());
2570 itemTemplate.Description = fields[105].GetString();
2571 itemTemplate.PageText = fields[106].GetUInt32();
2572 itemTemplate.LanguageID = uint32(fields[107].GetUInt8());
2573 itemTemplate.PageMaterial = uint32(fields[108].GetUInt8());
2574 itemTemplate.StartQuest = fields[109].GetUInt32();
2575 itemTemplate.LockID = fields[110].GetUInt32();
2576 itemTemplate.Material = int32(fields[111].GetInt8());
2577 itemTemplate.Sheath = uint32(fields[112].GetUInt8());
2578 itemTemplate.RandomProperty = fields[113].GetUInt32();
2579 itemTemplate.RandomSuffix = fields[114].GetInt32();
2580 itemTemplate.Block = fields[115].GetUInt32();
2581 itemTemplate.ItemSet = fields[116].GetUInt32();
2582 itemTemplate.MaxDurability = uint32(fields[117].GetUInt16());
2583 itemTemplate.Area = fields[118].GetUInt32();
2584 itemTemplate.Map = uint32(fields[119].GetUInt16());
2585 itemTemplate.BagFamily = fields[120].GetUInt32();
2586 itemTemplate.TotemCategory = fields[121].GetUInt32();
2587
2588 for (uint8 i = 0; i < MAX_ITEM_PROTO_SOCKETS; ++i)
2589 {
2590 itemTemplate.Socket[i].Color = uint32(fields[122 + i*2].GetUInt8());
2591 itemTemplate.Socket[i].Content = fields[123 + i*2].GetUInt32();
2592 }
2593
2594 itemTemplate.socketBonus = fields[128].GetUInt32();
2595 itemTemplate.GemProperties = fields[129].GetUInt32();
2596 itemTemplate.RequiredDisenchantSkill = uint32(fields[130].GetInt16());
2597 itemTemplate.ArmorDamageModifier = fields[131].GetFloat();
2598 itemTemplate.Duration = fields[132].GetUInt32();
2599 itemTemplate.ItemLimitCategory = uint32(fields[133].GetInt16());
2600 itemTemplate.HolidayId = fields[134].GetUInt32();
2601 itemTemplate.ScriptId = sObjectMgr->GetScriptId(fields[135].GetString());
2602 itemTemplate.DisenchantID = fields[136].GetUInt32();
2603 itemTemplate.FoodType = uint32(fields[137].GetUInt8());
2604 itemTemplate.MinMoneyLoot = fields[138].GetUInt32();
2605 itemTemplate.MaxMoneyLoot = fields[139].GetUInt32();
2606 itemTemplate.FlagsCu = fields[140].GetUInt32();
2607
2608 // Checks
2609
2610 ItemEntry const* dbcitem = sItemStore.LookupEntry(entry);
2611
2612 if (dbcitem)
2613 {
2614 if (itemTemplate.Class != dbcitem->Class)
2615 {
2616 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) does not have a correct class %u, must be %u .", entry, itemTemplate.Class, dbcitem->Class);
2617 if (enforceDBCAttributes)
2618 itemTemplate.Class = dbcitem->Class;
2619 }
2620
2621 if (itemTemplate.SoundOverrideSubclass != dbcitem->SoundOverrideSubclass)
2622 {
2623 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) does not have a correct SoundOverrideSubclass (%i), must be %i .", entry, itemTemplate.SoundOverrideSubclass, dbcitem->SoundOverrideSubclass);
2624 if (enforceDBCAttributes)
2625 itemTemplate.SoundOverrideSubclass = dbcitem->SoundOverrideSubclass;
2626 }
2627 if (itemTemplate.Material != dbcitem->Material)
2628 {
2629 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) does not have a correct material (%i), must be %i .", entry, itemTemplate.Material, dbcitem->Material);
2630 if (enforceDBCAttributes)
2631 itemTemplate.Material = dbcitem->Material;
2632 }
2633 if (itemTemplate.InventoryType != dbcitem->InventoryType)
2634 {
2635 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) does not have a correct inventory type (%u), must be %u .", entry, itemTemplate.InventoryType, dbcitem->InventoryType);
2636 if (enforceDBCAttributes)
2637 itemTemplate.InventoryType = dbcitem->InventoryType;
2638 }
2639 if (itemTemplate.DisplayInfoID != dbcitem->DisplayId)
2640 {
2641 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) does not have a correct display id (%u), must be %u .", entry, itemTemplate.DisplayInfoID, dbcitem->DisplayId);
2642 if (enforceDBCAttributes)
2643 itemTemplate.DisplayInfoID = dbcitem->DisplayId;
2644 }
2645 if (itemTemplate.Sheath != dbcitem->Sheath)
2646 {
2647 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) does not have a correct sheathid (%u), must be %u .", entry, itemTemplate.Sheath, dbcitem->Sheath);
2648 if (enforceDBCAttributes)
2649 itemTemplate.Sheath = dbcitem->Sheath;
2650 }
2651
2652 }
2653 else
2654 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) does not exist in item.dbc! (not correct id?).", entry);
2655
2656 if (itemTemplate.Class >= MAX_ITEM_CLASS)
2657 {
2658 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong Class value (%u)", entry, itemTemplate.Class);
2659 itemTemplate.Class = ITEM_CLASS_MISC;
2660 }
2661
2662 if (itemTemplate.SubClass >= MaxItemSubclassValues[itemTemplate.Class])
2663 {
2664 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong Subclass value (%u) for class %u", entry, itemTemplate.SubClass, itemTemplate.Class);
2665 itemTemplate.SubClass = 0;// exist for all item classes
2666 }
2667
2668 if (itemTemplate.Quality >= MAX_ITEM_QUALITY)
2669 {
2670 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong Quality value (%u)", entry, itemTemplate.Quality);
2671 itemTemplate.Quality = ITEM_QUALITY_NORMAL;
2672 }
2673
2674 if (itemTemplate.Flags2 & ITEM_FLAG2_FACTION_HORDE)
2675 {
2676 if (FactionEntry const* faction = sFactionStore.LookupEntry(HORDE))
2677 if ((itemTemplate.AllowableRace & faction->BaseRepRaceMask[0]) == 0)
2678 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has value (%u) in `AllowableRace` races, not compatible with ITEM_FLAG2_FACTION_HORDE (%u) in Flags field, item cannot be equipped or used by these races.",
2679 entry, itemTemplate.AllowableRace, ITEM_FLAG2_FACTION_HORDE);
2680
2681 if (itemTemplate.Flags2 & ITEM_FLAG2_FACTION_ALLIANCE)
2682 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has value (%u) in `Flags2` flags (ITEM_FLAG2_FACTION_ALLIANCE) and ITEM_FLAG2_FACTION_HORDE (%u) in Flags field, this is a wrong combination.",
2683 entry, ITEM_FLAG2_FACTION_ALLIANCE, ITEM_FLAG2_FACTION_HORDE);
2684 }
2685 else if (itemTemplate.Flags2 & ITEM_FLAG2_FACTION_ALLIANCE)
2686 {
2687 if (FactionEntry const* faction = sFactionStore.LookupEntry(ALLIANCE))
2688 if ((itemTemplate.AllowableRace & faction->BaseRepRaceMask[0]) == 0)
2689 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has value (%u) in `AllowableRace` races, not compatible with ITEM_FLAG2_FACTION_ALLIANCE (%u) in Flags field, item cannot be equipped or used by these races.",
2690 entry, itemTemplate.AllowableRace, ITEM_FLAG2_FACTION_ALLIANCE);
2691 }
2692
2693 if (itemTemplate.BuyCount <= 0)
2694 {
2695 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong BuyCount value (%u), set to default(1).", entry, itemTemplate.BuyCount);
2696 itemTemplate.BuyCount = 1;
2697 }
2698
2699 if (itemTemplate.InventoryType >= MAX_INVTYPE)
2700 {
2701 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong InventoryType value (%u)", entry, itemTemplate.InventoryType);
2702 itemTemplate.InventoryType = INVTYPE_NON_EQUIP;
2703 }
2704
2705 if (itemTemplate.RequiredSkill >= MAX_SKILL_TYPE)
2706 {
2707 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong RequiredSkill value (%u)", entry, itemTemplate.RequiredSkill);
2708 itemTemplate.RequiredSkill = 0;
2709 }
2710
2711 {
2712 // can be used in equip slot, as page read use in inventory, or spell casting at use
2713 bool req = itemTemplate.InventoryType != INVTYPE_NON_EQUIP || itemTemplate.PageText;
2714 if (!req)
2715 for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; ++j)
2716 {
2717 if (itemTemplate.Spells[j].SpellId > 0)
2718 {
2719 req = true;
2720 break;
2721 }
2722 }
2723
2724 if (req)
2725 {
2726 if (!(itemTemplate.AllowableClass & CLASSMASK_ALL_PLAYABLE))
2727 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) does not have any playable classes (%u) in `AllowableClass` and can't be equipped or used.", entry, itemTemplate.AllowableClass);
2728
2729 if (!(itemTemplate.AllowableRace & RACEMASK_ALL_PLAYABLE))
2730 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) does not have any playable races (%u) in `AllowableRace` and can't be equipped or used.", entry, itemTemplate.AllowableRace);
2731 }
2732 }
2733
2734 if (itemTemplate.RequiredSpell && !sSpellMgr->GetSpellInfo(itemTemplate.RequiredSpell))
2735 {
2736 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has a wrong (non-existing) spell in RequiredSpell (%u)", entry, itemTemplate.RequiredSpell);
2737 itemTemplate.RequiredSpell = 0;
2738 }
2739
2740 if (itemTemplate.RequiredReputationRank >= MAX_REPUTATION_RANK)
2741 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong reputation rank in RequiredReputationRank (%u), item can't be used.", entry, itemTemplate.RequiredReputationRank);
2742
2743 if (itemTemplate.RequiredReputationFaction)
2744 {
2745 if (!sFactionStore.LookupEntry(itemTemplate.RequiredReputationFaction))
2746 {
2747 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong (not existing) faction in RequiredReputationFaction (%u)", entry, itemTemplate.RequiredReputationFaction);
2748 itemTemplate.RequiredReputationFaction = 0;
2749 }
2750
2751 if (itemTemplate.RequiredReputationRank == MIN_REPUTATION_RANK)
2752 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has min. reputation rank in RequiredReputationRank (0) but RequiredReputationFaction > 0, faction setting is useless.", entry);
2753 }
2754
2755 if (itemTemplate.MaxCount < -1)
2756 {
2757 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has too large negative in maxcount (%i), replace by value (-1) no storing limits.", entry, itemTemplate.MaxCount);
2758 itemTemplate.MaxCount = -1;
2759 }
2760
2761 if (itemTemplate.Stackable == 0)
2762 {
2763 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong value in stackable (%i), replace by default 1.", entry, itemTemplate.Stackable);
2764 itemTemplate.Stackable = 1;
2765 }
2766 else if (itemTemplate.Stackable < -1)
2767 {
2768 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has too large negative in stackable (%i), replace by value (-1) no stacking limits.", entry, itemTemplate.Stackable);
2769 itemTemplate.Stackable = -1;
2770 }
2771
2772 if (itemTemplate.ContainerSlots > MAX_BAG_SIZE)
2773 {
2774 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has too large value in ContainerSlots (%u), replace by hardcoded limit (%u).", entry, itemTemplate.ContainerSlots, MAX_BAG_SIZE);
2775 itemTemplate.ContainerSlots = MAX_BAG_SIZE;
2776 }
2777
2778 for (uint8 j = 0; j < itemTemplate.StatsCount; ++j)
2779 {
2780 // for ItemStatValue != 0
2781 if (itemTemplate.ItemStat[j].ItemStatValue && itemTemplate.ItemStat[j].ItemStatType >= MAX_ITEM_MOD)
2782 {
2783 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong (non-existing?) stat_type%d (%u)", entry, j+1, itemTemplate.ItemStat[j].ItemStatType);
2784 itemTemplate.ItemStat[j].ItemStatType = 0;
2785 }
2786
2787 switch (itemTemplate.ItemStat[j].ItemStatType)
2788 {
2789 case ITEM_MOD_SPELL_HEALING_DONE:
2790 case ITEM_MOD_SPELL_DAMAGE_DONE:
2791 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has deprecated stat_type%d (%u)", entry, j+1, itemTemplate.ItemStat[j].ItemStatType);
2792 break;
2793 default:
2794 break;
2795 }
2796 }
2797
2798 for (uint8 j = 0; j < MAX_ITEM_PROTO_DAMAGES; ++j)
2799 {
2800 if (itemTemplate.Damage[j].DamageType >= MAX_SPELL_SCHOOL)
2801 {
2802 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong dmg_type%d (%u)", entry, j+1, itemTemplate.Damage[j].DamageType);
2803 itemTemplate.Damage[j].DamageType = 0;
2804 }
2805 }
2806
2807 // special format
2808 if ((itemTemplate.Spells[0].SpellId == 483) || (itemTemplate.Spells[0].SpellId == 55884))
2809 {
2810 // spell_1
2811 if (itemTemplate.Spells[0].SpellTrigger != ITEM_SPELLTRIGGER_ON_USE)
2812 {
2813 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong item spell trigger value in spelltrigger_%d (%u) for special learning format", entry, 0+1, itemTemplate.Spells[0].SpellTrigger);
2814 itemTemplate.Spells[0].SpellId = 0;
2815 itemTemplate.Spells[0].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
2816 itemTemplate.Spells[1].SpellId = 0;
2817 itemTemplate.Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
2818 }
2819
2820 // spell_2 have learning spell
2821 if (itemTemplate.Spells[1].SpellTrigger != ITEM_SPELLTRIGGER_LEARN_SPELL_ID)
2822 {
2823 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong item spell trigger value in spelltrigger_%d (%u) for special learning format.", entry, 1+1, itemTemplate.Spells[1].SpellTrigger);
2824 itemTemplate.Spells[0].SpellId = 0;
2825 itemTemplate.Spells[1].SpellId = 0;
2826 itemTemplate.Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
2827 }
2828 else if (!itemTemplate.Spells[1].SpellId)
2829 {
2830 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) does not have an expected spell in spellid_%d in special learning format.", entry, 1+1);
2831 itemTemplate.Spells[0].SpellId = 0;
2832 itemTemplate.Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
2833 }
2834 else if (itemTemplate.Spells[1].SpellId != -1)
2835 {
2836 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itemTemplate.Spells[1].SpellId);
2837 if (!spellInfo && !DisableMgr::IsDisabledFor(DISABLE_TYPE_SPELL, itemTemplate.Spells[1].SpellId, nullptr))
2838 {
2839 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong (not existing) spell in spellid_%d (%d)", entry, 1+1, itemTemplate.Spells[1].SpellId);
2840 itemTemplate.Spells[0].SpellId = 0;
2841 itemTemplate.Spells[1].SpellId = 0;
2842 itemTemplate.Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
2843 }
2844 // allowed only in special format
2845 else if ((itemTemplate.Spells[1].SpellId == 483) || (itemTemplate.Spells[1].SpellId == 55884))
2846 {
2847 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has broken spell in spellid_%d (%d)", entry, 1+1, itemTemplate.Spells[1].SpellId);
2848 itemTemplate.Spells[0].SpellId = 0;
2849 itemTemplate.Spells[1].SpellId = 0;
2850 itemTemplate.Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
2851 }
2852 }
2853
2854 // spell_3*, spell_4*, spell_5* is empty
2855 for (uint8 j = 2; j < MAX_ITEM_PROTO_SPELLS; ++j)
2856 {
2857 if (itemTemplate.Spells[j].SpellTrigger != ITEM_SPELLTRIGGER_ON_USE)
2858 {
2859 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong item spell trigger value in spelltrigger_%d (%u)", entry, j+1, itemTemplate.Spells[j].SpellTrigger);
2860 itemTemplate.Spells[j].SpellId = 0;
2861 itemTemplate.Spells[j].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
2862 }
2863 else if (itemTemplate.Spells[j].SpellId != 0)
2864 {
2865 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong spell in spellid_%d (%d) for learning special format", entry, j+1, itemTemplate.Spells[j].SpellId);
2866 itemTemplate.Spells[j].SpellId = 0;
2867 }
2868 }
2869 }
2870 // normal spell list
2871 else
2872 {
2873 for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; ++j)
2874 {
2875 if (itemTemplate.Spells[j].SpellTrigger >= MAX_ITEM_SPELLTRIGGER || itemTemplate.Spells[j].SpellTrigger == ITEM_SPELLTRIGGER_LEARN_SPELL_ID)
2876 {
2877 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong item spell trigger value in spelltrigger_%d (%u)", entry, j+1, itemTemplate.Spells[j].SpellTrigger);
2878 itemTemplate.Spells[j].SpellId = 0;
2879 itemTemplate.Spells[j].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
2880 }
2881
2882 if (itemTemplate.Spells[j].SpellId && itemTemplate.Spells[j].SpellId != -1)
2883 {
2884 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itemTemplate.Spells[j].SpellId);
2885 if (!spellInfo && !DisableMgr::IsDisabledFor(DISABLE_TYPE_SPELL, itemTemplate.Spells[j].SpellId, nullptr))
2886 {
2887 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong (not existing) spell in spellid_%d (%d)", entry, j+1, itemTemplate.Spells[j].SpellId);
2888 itemTemplate.Spells[j].SpellId = 0;
2889 }
2890 // allowed only in special format
2891 else if ((itemTemplate.Spells[j].SpellId == 483) || (itemTemplate.Spells[j].SpellId == 55884))
2892 {
2893 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has broken spell in spellid_%d (%d)", entry, j+1, itemTemplate.Spells[j].SpellId);
2894 itemTemplate.Spells[j].SpellId = 0;
2895 }
2896 }
2897 }
2898 }
2899
2900 if (itemTemplate.Bonding >= MAX_BIND_TYPE)
2901 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong Bonding value (%u)", entry, itemTemplate.Bonding);
2902
2903 if (itemTemplate.PageText && !GetPageText(itemTemplate.PageText))
2904 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has non existing first page (Id:%u)", entry, itemTemplate.PageText);
2905
2906 if (itemTemplate.LockID && !sLockStore.LookupEntry(itemTemplate.LockID))
2907 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong LockID (%u)", entry, itemTemplate.LockID);
2908
2909 if (itemTemplate.Sheath >= MAX_SHEATHETYPE)
2910 {
2911 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong Sheath (%u)", entry, itemTemplate.Sheath);
2912 itemTemplate.Sheath = SHEATHETYPE_NONE;
2913 }
2914
2915 if (itemTemplate.RandomProperty)
2916 {
2917 // To be implemented later
2918 if (itemTemplate.RandomProperty == -1)
2919 itemTemplate.RandomProperty = 0;
2920
2921 else if (!sItemRandomPropertiesStore.LookupEntry(GetItemEnchantMod(itemTemplate.RandomProperty)))
2922 {
2923 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has unknown (wrong or not listed in `item_enchantment_template`) RandomProperty (%u)", entry, itemTemplate.RandomProperty);
2924 itemTemplate.RandomProperty = 0;
2925 }
2926 }
2927
2928 if (itemTemplate.RandomSuffix && !sItemRandomSuffixStore.LookupEntry(GetItemEnchantMod(itemTemplate.RandomSuffix)))
2929 {
2930 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong RandomSuffix (%u)", entry, itemTemplate.RandomSuffix);
2931 itemTemplate.RandomSuffix = 0;
2932 }
2933
2934 if (itemTemplate.ItemSet && !sItemSetStore.LookupEntry(itemTemplate.ItemSet))
2935 {
2936 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) have wrong ItemSet (%u)", entry, itemTemplate.ItemSet);
2937 itemTemplate.ItemSet = 0;
2938 }
2939
2940 if (itemTemplate.Area && !sAreaTableStore.LookupEntry(itemTemplate.Area))
2941 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong Area (%u)", entry, itemTemplate.Area);
2942
2943 if (itemTemplate.Map && !sMapStore.LookupEntry(itemTemplate.Map))
2944 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong Map (%u)", entry, itemTemplate.Map);
2945
2946 if (itemTemplate.BagFamily)
2947 {
2948 // check bits
2949 for (uint32 j = 0; j < sizeof(itemTemplate.BagFamily)*8; ++j)
2950 {
2951 uint32 mask = 1 << j;
2952 if ((itemTemplate.BagFamily & mask) == 0)
2953 continue;
2954
2955 ItemBagFamilyEntry const* bf = sItemBagFamilyStore.LookupEntry(j+1);
2956 if (!bf)
2957 {
2958 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has bag family bit set not listed in ItemBagFamily.dbc, remove bit", entry);
2959 itemTemplate.BagFamily &= ~mask;
2960 continue;
2961 }
2962
2963 if (BAG_FAMILY_MASK_CURRENCY_TOKENS & mask)
2964 {
2965 CurrencyTypesEntry const* ctEntry = sCurrencyTypesStore.LookupEntry(itemTemplate.ItemId);
2966 if (!ctEntry)
2967 {
2968 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has currency bag family bit set in BagFamily but not listed in CurrencyTypes.dbc, remove bit", entry);
2969 itemTemplate.BagFamily &= ~mask;
2970 }
2971 }
2972 }
2973 }
2974
2975 if (itemTemplate.TotemCategory && !sTotemCategoryStore.LookupEntry(itemTemplate.TotemCategory))
2976 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong TotemCategory (%u)", entry, itemTemplate.TotemCategory);
2977
2978 for (uint8 j = 0; j < MAX_ITEM_PROTO_SOCKETS; ++j)
2979 {
2980 if (itemTemplate.Socket[j].Color && (itemTemplate.Socket[j].Color & SOCKET_COLOR_ALL) != itemTemplate.Socket[j].Color)
2981 {
2982 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong socketColor_%d (%u)", entry, j+1, itemTemplate.Socket[j].Color);
2983 itemTemplate.Socket[j].Color = 0;
2984 }
2985 }
2986
2987 if (itemTemplate.GemProperties && !sGemPropertiesStore.LookupEntry(itemTemplate.GemProperties))
2988 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong GemProperties (%u)", entry, itemTemplate.GemProperties);
2989
2990 if (itemTemplate.FoodType >= MAX_PET_DIET)
2991 {
2992 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong FoodType value (%u)", entry, itemTemplate.FoodType);
2993 itemTemplate.FoodType = 0;
2994 }
2995
2996 if (itemTemplate.ItemLimitCategory && !sItemLimitCategoryStore.LookupEntry(itemTemplate.ItemLimitCategory))
2997 {
2998 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong LimitCategory value (%u)", entry, itemTemplate.ItemLimitCategory);
2999 itemTemplate.ItemLimitCategory = 0;
3000 }
3001
3002 if (itemTemplate.HolidayId && !sHolidaysStore.LookupEntry(itemTemplate.HolidayId))
3003 {
3004 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong HolidayId value (%u)", entry, itemTemplate.HolidayId);
3005 itemTemplate.HolidayId = 0;
3006 }
3007
3008 if (itemTemplate.FlagsCu & ITEM_FLAGS_CU_DURATION_REAL_TIME && !itemTemplate.Duration)
3009 {
3010 TC_LOG_ERROR("sql.sql", "Item (Entry %u) has flag ITEM_FLAGS_CU_DURATION_REAL_TIME but it does not have duration limit", entry);
3011 itemTemplate.FlagsCu &= ~ITEM_FLAGS_CU_DURATION_REAL_TIME;
3012 }
3013
3014 // Load cached data
3015 itemTemplate._LoadTotalAP();
3016 } while (result->NextRow());
3017
3018 // Check if item templates for DBC referenced character start outfit are present
3019 std::set<uint32> notFoundOutfit;
3020 for (uint32 i = 1; i < sCharStartOutfitStore.GetNumRows(); ++i)
3021 {
3022 CharStartOutfitEntry const* entry = sCharStartOutfitStore.LookupEntry(i);
3023 if (!entry)
3024 continue;
3025
3026 for (uint8 j = 0; j < MAX_OUTFIT_ITEMS; ++j)
3027 {
3028 if (entry->ItemId[j] <= 0)
3029 continue;
3030
3031 uint32 item_id = entry->ItemId[j];
3032
3033 if (!GetItemTemplate(item_id))
3034 notFoundOutfit.insert(item_id);
3035 }
3036 }
3037
3038 for (std::set<uint32>::const_iterator itr = notFoundOutfit.begin(); itr != notFoundOutfit.end(); ++itr)
3039 TC_LOG_ERROR("sql.sql", "Item (Entry: %u) does not exist in `item_template` but is referenced in `CharStartOutfit.dbc`", *itr);
3040
3041 TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " item templates in %u ms", _itemTemplateStore.size(), GetMSTimeDiffToNow(oldMSTime));
3042}
3043
3044ItemTemplate const* ObjectMgr::GetItemTemplate(uint32 entry) const
3045{
3046 return Trinity::Containers::MapGetValuePtr(_itemTemplateStore, entry);
3047}
3048
3049void ObjectMgr::LoadItemSetNameLocales()
3050{
3051 uint32 oldMSTime = getMSTime();
3052
3053 _itemSetNameLocaleStore.clear(); // need for reload case
3054
3055 // 0 1 2
3056 QueryResult result = WorldDatabase.Query("SELECT ID, locale, Name FROM item_set_names_locale");
3057 if (!result)
3058 return;
3059
3060 do
3061 {
3062 Field* fields = result->Fetch();
3063
3064 uint32 id = fields[0].GetUInt32();
3065 std::string localeName = fields[1].GetString();
3066 std::string Name = fields[2].GetString();
3067
3068 ItemSetNameLocale& data = _itemSetNameLocaleStore[id];
3069 LocaleConstant locale = GetLocaleByName(localeName);
3070 if (locale == LOCALE_enUS)
3071 continue;
3072
3073 AddLocaleString(Name, locale, data.Name);
3074 } while (result->NextRow());
3075
3076 TC_LOG_INFO("server.loading", ">> Loaded " UI64FMTD " Item set name locale strings in %u ms", uint64(_itemSetNameLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime));
3077}
3078
3079void ObjectMgr::LoadItemSetNames()
3080{
3081 uint32 oldMSTime = getMSTime();
3082
3083 _itemSetNameStore.clear(); // needed for reload case
3084
3085 std::set<uint32> itemSetItems;
3086
3087 // fill item set member ids
3088 for (uint32 entryId = 0; entryId < sItemSetStore.GetNumRows(); ++entryId)
3089 {
3090 ItemSetEntry const* setEntry = sItemSetStore.LookupEntry(entryId);
3091 if (!setEntry)
3092 continue;
3093
3094 for (uint32 i = 0; i < MAX_ITEM_SET_ITEMS; ++i)
3095 if (setEntry->itemId[i])
3096 itemSetItems.insert(setEntry->itemId[i]);
3097 }
3098
3099 // 0 1 2
3100 QueryResult result = WorldDatabase.Query("SELECT `entry`, `name`, `InventoryType` FROM `item_set_names`");
3101
3102 if (!result)
3103 {
3104 TC_LOG_INFO("server.loading", ">> Loaded 0 item set names. DB table `item_set_names` is empty.");
3105 return;
3106 }
3107
3108 _itemSetNameStore.rehash(result->GetRowCount());
3109 uint32 count = 0;
3110
3111 do
3112 {
3113 Field* fields = result->Fetch();
3114
3115 uint32 entry = fields[0].GetUInt32();
3116 if (itemSetItems.find(entry) == itemSetItems.end())
3117 {
3118 TC_LOG_ERROR("sql.sql", "Item set name (Entry: %u) not found in ItemSet.dbc, data useless.", entry);
3119 continue;
3120 }
3121
3122 ItemSetNameEntry &data = _itemSetNameStore[entry];
3123 data.name = fields[1].GetString();
3124
3125 uint32 invType = fields[2].GetUInt8();
3126 if (invType >= MAX_INVTYPE)
3127 {
3128 TC_LOG_ERROR("sql.sql", "Item set name (Entry: %u) has wrong InventoryType value (%u)", entry, invType);
3129 invType = INVTYPE_NON_EQUIP;
3130 }
3131
3132 data.InventoryType = invType;
3133 itemSetItems.erase(entry);
3134 ++count;
3135 } while (result->NextRow());
3136
3137 if (!itemSetItems.empty())
3138 {
3139 ItemTemplate const* pProto;
3140 for (std::set<uint32>::iterator itr = itemSetItems.begin(); itr != itemSetItems.end(); ++itr)
3141 {
3142 uint32 entry = *itr;
3143 // add data from item_template if available
3144 pProto = sObjectMgr->GetItemTemplate(entry);
3145 if (pProto)
3146 {
3147 TC_LOG_ERROR("sql.sql", "Item set part (Entry: %u) does not have entry in `item_set_names`, adding data from `item_template`.", entry);
3148 ItemSetNameEntry &data = _itemSetNameStore[entry];
3149 data.name = pProto->Name1;
3150 data.InventoryType = pProto->InventoryType;
3151 ++count;
3152 }
3153 else
3154 TC_LOG_ERROR("sql.sql", "Item set part (Entry: %u) does not have entry in `item_set_names`, set will not display properly.", entry);
3155 }
3156 }
3157
3158 TC_LOG_INFO("server.loading", ">> Loaded %u item set names in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
3159}
3160
3161void ObjectMgr::LoadVehicleTemplateAccessories()
3162{
3163 uint32 oldMSTime = getMSTime();
3164
3165 _vehicleTemplateAccessoryStore.clear(); // needed for reload case
3166
3167 uint32 count = 0;
3168
3169 // 0 1 2 3 4 5
3170 QueryResult result = WorldDatabase.Query("SELECT `entry`, `accessory_entry`, `seat_id`, `minion`, `summontype`, `summontimer` FROM `vehicle_template_accessory`");
3171
3172 if (!result)
3173 {
3174 TC_LOG_INFO("server.loading", ">> Loaded 0 vehicle template accessories. DB table `vehicle_template_accessory` is empty.");
3175 return;
3176 }
3177
3178 do
3179 {
3180 Field* fields = result->Fetch();
3181
3182 uint32 entry = fields[0].GetUInt32();
3183 uint32 accessory = fields[1].GetUInt32();
3184 int8 seatId = fields[2].GetInt8();
3185 bool isMinion = fields[3].GetBool();
3186 uint8 summonType = fields[4].GetUInt8();
3187 uint32 summonTimer = fields[5].GetUInt32();
3188
3189 if (!sObjectMgr->GetCreatureTemplate(entry))
3190 {
3191 TC_LOG_ERROR("sql.sql", "Table `vehicle_template_accessory`: creature template entry %u does not exist.", entry);
3192 continue;
3193 }
3194
3195 if (!sObjectMgr->GetCreatureTemplate(accessory))
3196 {
3197 TC_LOG_ERROR("sql.sql", "Table `vehicle_template_accessory`: Accessory %u does not exist.", accessory);
3198 continue;
3199 }
3200
3201 if (_spellClickInfoStore.find(entry) == _spellClickInfoStore.end())
3202 {
3203 TC_LOG_ERROR("sql.sql", "Table `vehicle_template_accessory`: creature template entry %u has no data in npc_spellclick_spells", entry);
3204 continue;
3205 }
3206
3207 _vehicleTemplateAccessoryStore[entry].push_back(VehicleAccessory(accessory, seatId, isMinion, summonType, summonTimer));
3208
3209 ++count;
3210 }
3211 while (result->NextRow());
3212
3213 TC_LOG_INFO("server.loading", ">> Loaded %u Vehicle Template Accessories in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
3214}
3215
3216void ObjectMgr::LoadVehicleAccessories()
3217{
3218 uint32 oldMSTime = getMSTime();
3219
3220 _vehicleAccessoryStore.clear(); // needed for reload case
3221
3222 uint32 count = 0;
3223
3224 // 0 1 2 3 4 5
3225 QueryResult result = WorldDatabase.Query("SELECT `guid`, `accessory_entry`, `seat_id`, `minion`, `summontype`, `summontimer` FROM `vehicle_accessory`");
3226
3227 if (!result)
3228 {
3229 TC_LOG_INFO("server.loading", ">> Loaded 0 Vehicle Accessories in %u ms", GetMSTimeDiffToNow(oldMSTime));
3230 return;
3231 }
3232
3233 do
3234 {
3235 Field* fields = result->Fetch();
3236
3237 uint32 uiGUID = fields[0].GetUInt32();
3238 uint32 uiAccessory = fields[1].GetUInt32();
3239 int8 uiSeat = int8(fields[2].GetInt16());
3240 bool bMinion = fields[3].GetBool();
3241 uint8 uiSummonType = fields[4].GetUInt8();
3242 uint32 uiSummonTimer= fields[5].GetUInt32();
3243
3244 if (!sObjectMgr->GetCreatureTemplate(uiAccessory))
3245 {
3246 TC_LOG_ERROR("sql.sql", "Table `vehicle_accessory`: Accessory %u does not exist.", uiAccessory);
3247 continue;
3248 }
3249
3250 _vehicleAccessoryStore[uiGUID].push_back(VehicleAccessory(uiAccessory, uiSeat, bMinion, uiSummonType, uiSummonTimer));
3251
3252 ++count;
3253 }
3254 while (result->NextRow());
3255
3256 TC_LOG_INFO("server.loading", ">> Loaded %u Vehicle Accessories in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
3257}
3258
3259void ObjectMgr::LoadPetLevelInfo()
3260{
3261 uint32 oldMSTime = getMSTime();
3262
3263 // 0 1 2 3 4 5 6 7 8 9
3264 QueryResult result = WorldDatabase.Query("SELECT creature_entry, level, hp, mana, str, agi, sta, inte, spi, armor FROM pet_levelstats");
3265
3266 if (!result)
3267 {
3268 TC_LOG_INFO("server.loading", ">> Loaded 0 level pet stats definitions. DB table `pet_levelstats` is empty.");
3269 return;
3270 }
3271
3272 uint32 count = 0;
3273
3274 do
3275 {
3276 Field* fields = result->Fetch();
3277
3278 uint32 creature_id = fields[0].GetUInt32();
3279 if (!sObjectMgr->GetCreatureTemplate(creature_id))
3280 {
3281 TC_LOG_ERROR("sql.sql", "Wrong creature id %u in `pet_levelstats` table, ignoring.", creature_id);
3282 continue;
3283 }
3284
3285 uint32 current_level = fields[1].GetUInt8();
3286 if (current_level > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
3287 {
3288 if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum
3289 TC_LOG_ERROR("sql.sql", "Wrong (> %u) level %u in `pet_levelstats` table, ignoring.", STRONG_MAX_LEVEL, current_level);
3290 else
3291 {
3292 TC_LOG_INFO("misc", "Unused (> MaxPlayerLevel in worldserver.conf) level %u in `pet_levelstats` table, ignoring.", current_level);
3293 ++count; // make result loading percent "expected" correct in case disabled detail mode for example.
3294 }
3295 continue;
3296 }
3297 else if (current_level < 1)
3298 {
3299 TC_LOG_ERROR("sql.sql", "Wrong (<1) level %u in `pet_levelstats` table, ignoring.", current_level);
3300 continue;
3301 }
3302
3303 auto& pInfoMapEntry = _petInfoStore[creature_id];
3304 if (!pInfoMapEntry)
3305 pInfoMapEntry = Trinity::make_unique<PetLevelInfo[]>(sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL));
3306
3307 // data for level 1 stored in [0] array element, ...
3308 PetLevelInfo* pLevelInfo = &pInfoMapEntry[current_level - 1];
3309
3310 pLevelInfo->health = fields[2].GetUInt16();
3311 pLevelInfo->mana = fields[3].GetUInt16();
3312 pLevelInfo->armor = fields[9].GetUInt32();
3313
3314 for (uint8 i = 0; i < MAX_STATS; i++)
3315 pLevelInfo->stats[i] = fields[i + 4].GetUInt16();
3316
3317 ++count;
3318 }
3319 while (result->NextRow());
3320
3321 // Fill gaps and check integrity
3322 for (PetLevelInfoContainer::iterator itr = _petInfoStore.begin(); itr != _petInfoStore.end(); ++itr)
3323 {
3324 auto& pInfo = itr->second;
3325
3326 // fatal error if no level 1 data
3327 if (!pInfo || pInfo[0].health == 0)
3328 {
3329 TC_LOG_ERROR("sql.sql", "Creature %u does not have pet stats data for Level 1!", itr->first);
3330 ABORT();
3331 }
3332
3333 // fill level gaps
3334 for (uint8 level = 1; level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
3335 {
3336 if (pInfo[level].health == 0)
3337 {
3338 TC_LOG_ERROR("sql.sql", "Creature %u has no data for Level %i pet stats data, using data of Level %i.", itr->first, level + 1, level);
3339 pInfo[level] = pInfo[level - 1];
3340 }
3341 }
3342 }
3343
3344 TC_LOG_INFO("server.loading", ">> Loaded %u level pet stats definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
3345}
3346
3347PetLevelInfo const* ObjectMgr::GetPetLevelInfo(uint32 creature_id, uint8 level) const
3348{
3349 if (level > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
3350 level = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
3351
3352 auto itr = _petInfoStore.find(creature_id);
3353 if (itr == _petInfoStore.end())
3354 return nullptr;
3355
3356 return &itr->second[level - 1]; // data for level 1 stored in [0] array element, ...
3357}
3358
3359void ObjectMgr::PlayerCreateInfoAddItemHelper(uint32 race_, uint32 class_, uint32 itemId, int32 count)
3360{
3361 if (!_playerInfo[race_][class_])
3362 return;
3363
3364 if (count > 0)
3365 _playerInfo[race_][class_]->item.push_back(PlayerCreateInfoItem(itemId, count));
3366 else
3367 {
3368 if (count < -1)
3369 TC_LOG_ERROR("sql.sql", "Invalid count %i specified on item %u be removed from original player create info (use -1)!", count, itemId);
3370
3371 for (uint32 gender = 0; gender < GENDER_NONE; ++gender)
3372 {
3373 if (CharStartOutfitEntry const* entry = GetCharStartOutfitEntry(race_, class_, gender))
3374 {
3375 bool found = false;
3376 for (uint8 x = 0; x < MAX_OUTFIT_ITEMS; ++x)
3377 {
3378 if (entry->ItemId[x] > 0 && uint32(entry->ItemId[x]) == itemId)
3379 {
3380 found = true;
3381 const_cast<CharStartOutfitEntry*>(entry)->ItemId[x] = 0;
3382 break;
3383 }
3384 }
3385
3386 if (!found)
3387 TC_LOG_ERROR("sql.sql", "Item %u specified to be removed from original create info not found in dbc!", itemId);
3388 }
3389 }
3390 }
3391}
3392
3393void ObjectMgr::LoadPlayerInfo()
3394{
3395 // Load playercreate
3396 {
3397 uint32 oldMSTime = getMSTime();
3398 // 0 1 2 3 4 5 6
3399 QueryResult result = WorldDatabase.Query("SELECT race, class, map, zone, position_x, position_y, position_z, orientation FROM playercreateinfo");
3400
3401 if (!result)
3402 {
3403 TC_LOG_ERROR("server.loading", ">> Loaded 0 player create definitions. DB table `playercreateinfo` is empty.");
3404 ABORT();
3405 }
3406 else
3407 {
3408 uint32 count = 0;
3409
3410 do
3411 {
3412 Field* fields = result->Fetch();
3413
3414 uint32 current_race = fields[0].GetUInt8();
3415 uint32 current_class = fields[1].GetUInt8();
3416 uint32 mapId = fields[2].GetUInt16();
3417 uint32 areaId = fields[3].GetUInt32(); // zone
3418 float positionX = fields[4].GetFloat();
3419 float positionY = fields[5].GetFloat();
3420 float positionZ = fields[6].GetFloat();
3421 float orientation = fields[7].GetFloat();
3422
3423 if (current_race >= MAX_RACES)
3424 {
3425 TC_LOG_ERROR("sql.sql", "Wrong race %u in `playercreateinfo` table, ignoring.", current_race);
3426 continue;
3427 }
3428
3429 ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(current_race);
3430 if (!rEntry)
3431 {
3432 TC_LOG_ERROR("sql.sql", "Wrong race %u in `playercreateinfo` table, ignoring.", current_race);
3433 continue;
3434 }
3435
3436 if (current_class >= MAX_CLASSES)
3437 {
3438 TC_LOG_ERROR("sql.sql", "Wrong class %u in `playercreateinfo` table, ignoring.", current_class);
3439 continue;
3440 }
3441
3442 if (!sChrClassesStore.LookupEntry(current_class))
3443 {
3444 TC_LOG_ERROR("sql.sql", "Wrong class %u in `playercreateinfo` table, ignoring.", current_class);
3445 continue;
3446 }
3447
3448 // accept DB data only for valid position (and non instanceable)
3449 if (!MapManager::IsValidMapCoord(mapId, positionX, positionY, positionZ, orientation))
3450 {
3451 TC_LOG_ERROR("sql.sql", "Wrong home position for class %u race %u pair in `playercreateinfo` table, ignoring.", current_class, current_race);
3452 continue;
3453 }
3454
3455 if (sMapStore.LookupEntry(mapId)->Instanceable())
3456 {
3457 TC_LOG_ERROR("sql.sql", "Home position in instanceable map for class %u race %u pair in `playercreateinfo` table, ignoring.", current_class, current_race);
3458 continue;
3459 }
3460
3461 std::unique_ptr<PlayerInfo> info = Trinity::make_unique<PlayerInfo>();
3462 info->mapId = mapId;
3463 info->areaId = areaId;
3464 info->positionX = positionX;
3465 info->positionY = positionY;
3466 info->positionZ = positionZ;
3467 info->orientation = orientation;
3468 info->displayId_m = rEntry->model_m;
3469 info->displayId_f = rEntry->model_f;
3470 _playerInfo[current_race][current_class] = std::move(info);
3471
3472 ++count;
3473 }
3474 while (result->NextRow());
3475
3476 TC_LOG_INFO("server.loading", ">> Loaded %u player create definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
3477 }
3478 }
3479
3480 // Load playercreate items
3481 TC_LOG_INFO("server.loading", "Loading Player Create Items Data...");
3482 {
3483 uint32 oldMSTime = getMSTime();
3484 // 0 1 2 3
3485 QueryResult result = WorldDatabase.Query("SELECT race, class, itemid, amount FROM playercreateinfo_item");
3486
3487 if (!result)
3488 {
3489 TC_LOG_INFO("server.loading", ">> Loaded 0 custom player create items. DB table `playercreateinfo_item` is empty.");
3490 }
3491 else
3492 {
3493 uint32 count = 0;
3494
3495 do
3496 {
3497 Field* fields = result->Fetch();
3498
3499 uint32 current_race = fields[0].GetUInt8();
3500 if (current_race >= MAX_RACES)
3501 {
3502 TC_LOG_ERROR("sql.sql", "Wrong race %u in `playercreateinfo_item` table, ignoring.", current_race);
3503 continue;
3504 }
3505
3506 uint32 current_class = fields[1].GetUInt8();
3507 if (current_class >= MAX_CLASSES)
3508 {
3509 TC_LOG_ERROR("sql.sql", "Wrong class %u in `playercreateinfo_item` table, ignoring.", current_class);
3510 continue;
3511 }
3512
3513 uint32 item_id = fields[2].GetUInt32();
3514
3515 if (!GetItemTemplate(item_id))
3516 {
3517 TC_LOG_ERROR("sql.sql", "Item id %u (race %u class %u) in `playercreateinfo_item` table but not listed in `item_template`, ignoring.", item_id, current_race, current_class);
3518 continue;
3519 }
3520
3521 int32 amount = fields[3].GetInt8();
3522
3523 if (!amount)
3524 {
3525 TC_LOG_ERROR("sql.sql", "Item id %u (class %u race %u) have amount == 0 in `playercreateinfo_item` table, ignoring.", item_id, current_race, current_class);
3526 continue;
3527 }
3528
3529 if (!current_race || !current_class)
3530 {
3531 uint32 min_race = current_race ? current_race : 1;
3532 uint32 max_race = current_race ? current_race + 1 : MAX_RACES;
3533 uint32 min_class = current_class ? current_class : 1;
3534 uint32 max_class = current_class ? current_class + 1 : MAX_CLASSES;
3535 for (uint32 r = min_race; r < max_race; ++r)
3536 for (uint32 c = min_class; c < max_class; ++c)
3537 PlayerCreateInfoAddItemHelper(r, c, item_id, amount);
3538 }
3539 else
3540 PlayerCreateInfoAddItemHelper(current_race, current_class, item_id, amount);
3541
3542 ++count;
3543 }
3544 while (result->NextRow());
3545
3546 TC_LOG_INFO("server.loading", ">> Loaded %u custom player create items in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
3547 }
3548 }
3549
3550
3551 // Load playercreate skills
3552 TC_LOG_INFO("server.loading", "Loading Player Create Skill Data...");
3553 {
3554 uint32 oldMSTime = getMSTime();
3555
3556 QueryResult result = WorldDatabase.PQuery("SELECT raceMask, classMask, skill, rank FROM playercreateinfo_skills");
3557
3558 if (!result)
3559 {
3560 TC_LOG_INFO("server.loading", ">> Loaded 0 player create skills. DB table `playercreateinfo_skills` is empty.");
3561 }
3562 else
3563 {
3564 uint32 count = 0;
3565
3566 do
3567 {
3568 Field* fields = result->Fetch();
3569 uint32 raceMask = fields[0].GetUInt32();
3570 uint32 classMask = fields[1].GetUInt32();
3571 PlayerCreateInfoSkill skill;
3572 skill.SkillId = fields[2].GetUInt16();
3573 skill.Rank = fields[3].GetUInt16();
3574
3575 if (skill.Rank >= MAX_SKILL_STEP)
3576 {
3577 TC_LOG_ERROR("sql.sql", "Skill rank value %hu set for skill %hu raceMask %u classMask %u is too high, max allowed value is %d", skill.Rank, skill.SkillId, raceMask, classMask, MAX_SKILL_STEP);
3578 continue;
3579 }
3580
3581 if (raceMask != 0 && !(raceMask & RACEMASK_ALL_PLAYABLE))
3582 {
3583 TC_LOG_ERROR("sql.sql", "Wrong race mask %u in `playercreateinfo_skills` table, ignoring.", raceMask);
3584 continue;
3585 }
3586
3587 if (classMask != 0 && !(classMask & CLASSMASK_ALL_PLAYABLE))
3588 {
3589 TC_LOG_ERROR("sql.sql", "Wrong class mask %u in `playercreateinfo_skills` table, ignoring.", classMask);
3590 continue;
3591 }
3592
3593 if (!sSkillLineStore.LookupEntry(skill.SkillId))
3594 {
3595 TC_LOG_ERROR("sql.sql", "Wrong skill id %u in `playercreateinfo_skills` table, ignoring.", skill.SkillId);
3596 continue;
3597 }
3598
3599 for (uint32 raceIndex = RACE_HUMAN; raceIndex < MAX_RACES; ++raceIndex)
3600 {
3601 if (raceMask == 0 || ((1 << (raceIndex - 1)) & raceMask))
3602 {
3603 for (uint32 classIndex = CLASS_WARRIOR; classIndex < MAX_CLASSES; ++classIndex)
3604 {
3605 if (classMask == 0 || ((1 << (classIndex - 1)) & classMask))
3606 {
3607 if (!GetSkillRaceClassInfo(skill.SkillId, raceIndex, classIndex))
3608 continue;
3609
3610 if (auto& info = _playerInfo[raceIndex][classIndex])
3611 {
3612 info->skills.push_back(skill);
3613 ++count;
3614 }
3615 }
3616 }
3617 }
3618 }
3619 } while (result->NextRow());
3620
3621 TC_LOG_INFO("server.loading", ">> Loaded %u player create skills in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
3622 }
3623 }
3624
3625 // Load playercreate spells
3626 TC_LOG_INFO("server.loading", "Loading Player Create Spell Data...");
3627 {
3628 uint32 oldMSTime = getMSTime();
3629
3630 QueryResult result = WorldDatabase.PQuery("SELECT racemask, classmask, Spell FROM playercreateinfo_spell_custom");
3631
3632 if (!result)
3633 {
3634 TC_LOG_INFO("server.loading", ">> Loaded 0 player create spells. DB table `playercreateinfo_spell_custom` is empty.");
3635 }
3636 else
3637 {
3638 uint32 count = 0;
3639
3640 do
3641 {
3642 Field* fields = result->Fetch();
3643 uint32 raceMask = fields[0].GetUInt32();
3644 uint32 classMask = fields[1].GetUInt32();
3645 uint32 spellId = fields[2].GetUInt32();
3646
3647 if (raceMask != 0 && !(raceMask & RACEMASK_ALL_PLAYABLE))
3648 {
3649 TC_LOG_ERROR("sql.sql", "Wrong race mask %u in `playercreateinfo_spell_custom` table, ignoring.", raceMask);
3650 continue;
3651 }
3652
3653 if (classMask != 0 && !(classMask & CLASSMASK_ALL_PLAYABLE))
3654 {
3655 TC_LOG_ERROR("sql.sql", "Wrong class mask %u in `playercreateinfo_spell_custom` table, ignoring.", classMask);
3656 continue;
3657 }
3658
3659 for (uint32 raceIndex = RACE_HUMAN; raceIndex < MAX_RACES; ++raceIndex)
3660 {
3661 if (raceMask == 0 || ((1 << (raceIndex - 1)) & raceMask))
3662 {
3663 for (uint32 classIndex = CLASS_WARRIOR; classIndex < MAX_CLASSES; ++classIndex)
3664 {
3665 if (classMask == 0 || ((1 << (classIndex - 1)) & classMask))
3666 {
3667 if (auto& info = _playerInfo[raceIndex][classIndex])
3668 {
3669 info->customSpells.push_back(spellId);
3670 ++count;
3671 }
3672 // We need something better here, the check is not accounting for spells used by multiple races/classes but not all of them.
3673 // Either split the masks per class, or per race, which kind of kills the point yet.
3674 // else if (raceMask != 0 && classMask != 0)
3675 // TC_LOG_ERROR("sql.sql", "Racemask/classmask (%u/%u) combination was found containing an invalid race/class combination (%u/%u) in `%s` (Spell %u), ignoring.", raceMask, classMask, raceIndex, classIndex, tableName.c_str(), spellId);
3676 }
3677 }
3678 }
3679 }
3680 }
3681 while (result->NextRow());
3682
3683 TC_LOG_INFO("server.loading", ">> Loaded %u custom player create spells in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
3684 }
3685 }
3686
3687 // Load playercreate cast spell
3688 TC_LOG_INFO("server.loading", "Loading Player Create Cast Spell Data...");
3689 {
3690 uint32 oldMSTime = getMSTime();
3691
3692 QueryResult result = WorldDatabase.PQuery("SELECT raceMask, classMask, spell FROM playercreateinfo_cast_spell");
3693
3694 if (!result)
3695 TC_LOG_INFO("server.loading", ">> Loaded 0 player create cast spells. DB table `playercreateinfo_cast_spell` is empty.");
3696 else
3697 {
3698 uint32 count = 0;
3699
3700 do
3701 {
3702 Field* fields = result->Fetch();
3703 uint32 raceMask = fields[0].GetUInt32();
3704 uint32 classMask = fields[1].GetUInt32();
3705 uint32 spellId = fields[2].GetUInt32();
3706
3707 if (raceMask != 0 && !(raceMask & RACEMASK_ALL_PLAYABLE))
3708 {
3709 TC_LOG_ERROR("sql.sql", "Wrong race mask %u in `playercreateinfo_cast_spell` table, ignoring.", raceMask);
3710 continue;
3711 }
3712
3713 if (classMask != 0 && !(classMask & CLASSMASK_ALL_PLAYABLE))
3714 {
3715 TC_LOG_ERROR("sql.sql", "Wrong class mask %u in `playercreateinfo_cast_spell` table, ignoring.", classMask);
3716 continue;
3717 }
3718
3719 for (uint32 raceIndex = RACE_HUMAN; raceIndex < MAX_RACES; ++raceIndex)
3720 {
3721 if (raceMask == 0 || ((1 << (raceIndex - 1)) & raceMask))
3722 {
3723 for (uint32 classIndex = CLASS_WARRIOR; classIndex < MAX_CLASSES; ++classIndex)
3724 {
3725 if (classMask == 0 || ((1 << (classIndex - 1)) & classMask))
3726 {
3727 if (auto& info = _playerInfo[raceIndex][classIndex])
3728 {
3729 info->castSpells.push_back(spellId);
3730 ++count;
3731 }
3732 }
3733 }
3734 }
3735 }
3736 } while (result->NextRow());
3737
3738 TC_LOG_INFO("server.loading", ">> Loaded %u player create cast spells in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
3739 }
3740 }
3741
3742 // Load playercreate actions
3743 TC_LOG_INFO("server.loading", "Loading Player Create Action Data...");
3744 {
3745 uint32 oldMSTime = getMSTime();
3746
3747 // 0 1 2 3 4
3748 QueryResult result = WorldDatabase.Query("SELECT race, class, button, action, type FROM playercreateinfo_action");
3749
3750 if (!result)
3751 {
3752 TC_LOG_INFO("server.loading", ">> Loaded 0 player create actions. DB table `playercreateinfo_action` is empty.");
3753 }
3754 else
3755 {
3756 uint32 count = 0;
3757
3758 do
3759 {
3760 Field* fields = result->Fetch();
3761
3762 uint32 current_race = fields[0].GetUInt8();
3763 if (current_race >= MAX_RACES)
3764 {
3765 TC_LOG_ERROR("sql.sql", "Wrong race %u in `playercreateinfo_action` table, ignoring.", current_race);
3766 continue;
3767 }
3768
3769 uint32 current_class = fields[1].GetUInt8();
3770 if (current_class >= MAX_CLASSES)
3771 {
3772 TC_LOG_ERROR("sql.sql", "Wrong class %u in `playercreateinfo_action` table, ignoring.", current_class);
3773 continue;
3774 }
3775
3776 if (auto& info = _playerInfo[current_race][current_class])
3777 info->action.push_back(PlayerCreateInfoAction(fields[2].GetUInt16(), fields[3].GetUInt32(), fields[4].GetUInt16()));
3778
3779 ++count;
3780 }
3781 while (result->NextRow());
3782
3783 TC_LOG_INFO("server.loading", ">> Loaded %u player create actions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
3784 }
3785 }
3786
3787 // Loading levels data (class only dependent)
3788 TC_LOG_INFO("server.loading", "Loading Player Create Level HP/Mana Data...");
3789 {
3790 uint32 oldMSTime = getMSTime();
3791
3792 // 0 1 2 3
3793 QueryResult result = WorldDatabase.Query("SELECT class, level, basehp, basemana FROM player_classlevelstats");
3794
3795 if (!result)
3796 {
3797 TC_LOG_ERROR("server.loading", ">> Loaded 0 level health/mana definitions. DB table `player_classlevelstats` is empty.");
3798 ABORT();
3799 }
3800
3801 uint32 count = 0;
3802
3803 do
3804 {
3805 Field* fields = result->Fetch();
3806
3807 uint32 current_class = fields[0].GetUInt8();
3808 if (current_class >= MAX_CLASSES)
3809 {
3810 TC_LOG_ERROR("sql.sql", "Wrong class %u in `player_classlevelstats` table, ignoring.", current_class);
3811 continue;
3812 }
3813
3814 uint8 current_level = fields[1].GetUInt8(); // Can't be > than STRONG_MAX_LEVEL (hardcoded level maximum) due to var type
3815 if (current_level > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
3816 {
3817 TC_LOG_INFO("misc", "Unused (> MaxPlayerLevel in worldserver.conf) level %u in `player_classlevelstats` table, ignoring.", current_level);
3818 ++count; // make result loading percent "expected" correct in case disabled detail mode for example.
3819 continue;
3820 }
3821
3822 auto& info = _playerClassInfo[current_class];
3823 if (!info)
3824 {
3825 info = Trinity::make_unique<PlayerClassInfo>();
3826 info->levelInfo = Trinity::make_unique<PlayerClassLevelInfo[]>(sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL));
3827 }
3828
3829 PlayerClassLevelInfo& levelInfo = info->levelInfo[current_level - 1];
3830 levelInfo.basehealth = fields[2].GetUInt16();
3831 levelInfo.basemana = fields[3].GetUInt16();
3832
3833 ++count;
3834 }
3835 while (result->NextRow());
3836
3837 // Fill gaps and check integrity
3838 for (uint8 class_ = 0; class_ < MAX_CLASSES; ++class_)
3839 {
3840 // skip non existed classes
3841 if (!sChrClassesStore.LookupEntry(class_))
3842 continue;
3843
3844 auto& pClassInfo = _playerClassInfo[class_];
3845
3846 // fatal error if no level 1 data
3847 if (!pClassInfo->levelInfo || pClassInfo->levelInfo[0].basehealth == 0)
3848 {
3849 TC_LOG_ERROR("sql.sql", "Class %i Level 1 does not have health/mana data!", class_);
3850 ABORT();
3851 }
3852
3853 // fill level gaps
3854 for (uint8 level = 1; level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
3855 {
3856 if (pClassInfo->levelInfo[level].basehealth == 0)
3857 {
3858 TC_LOG_ERROR("sql.sql", "Class %i Level %i does not have health/mana data. Using stats data of level %i.", class_, level + 1, level);
3859 pClassInfo->levelInfo[level] = pClassInfo->levelInfo[level - 1];
3860 }
3861 }
3862 }
3863
3864 TC_LOG_INFO("server.loading", ">> Loaded %u level health/mana definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
3865 }
3866
3867 // Loading levels data (class/race dependent)
3868 TC_LOG_INFO("server.loading", "Loading Player Create Level Stats Data...");
3869 {
3870 uint32 oldMSTime = getMSTime();
3871
3872 // 0 1 2 3 4 5 6 7
3873 QueryResult result = WorldDatabase.Query("SELECT race, class, level, str, agi, sta, inte, spi FROM player_levelstats");
3874
3875 if (!result)
3876 {
3877 TC_LOG_ERROR("server.loading", ">> Loaded 0 level stats definitions. DB table `player_levelstats` is empty.");
3878 ABORT();
3879 }
3880
3881 uint32 count = 0;
3882
3883 do
3884 {
3885 Field* fields = result->Fetch();
3886
3887 uint32 current_race = fields[0].GetUInt8();
3888 if (current_race >= MAX_RACES)
3889 {
3890 TC_LOG_ERROR("sql.sql", "Wrong race %u in `player_levelstats` table, ignoring.", current_race);
3891 continue;
3892 }
3893
3894 uint32 current_class = fields[1].GetUInt8();
3895 if (current_class >= MAX_CLASSES)
3896 {
3897 TC_LOG_ERROR("sql.sql", "Wrong class %u in `player_levelstats` table, ignoring.", current_class);
3898 continue;
3899 }
3900
3901 uint32 current_level = fields[2].GetUInt8();
3902 if (current_level > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
3903 {
3904 if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum
3905 TC_LOG_ERROR("sql.sql", "Wrong (> %u) level %u in `player_levelstats` table, ignoring.", STRONG_MAX_LEVEL, current_level);
3906 else
3907 {
3908 TC_LOG_INFO("misc", "Unused (> MaxPlayerLevel in worldserver.conf) level %u in `player_levelstats` table, ignoring.", current_level);
3909 ++count; // make result loading percent "expected" correct in case disabled detail mode for example.
3910 }
3911 continue;
3912 }
3913
3914 if (auto& info = _playerInfo[current_race][current_class])
3915 {
3916 if (!info->levelInfo)
3917 info->levelInfo = Trinity::make_unique<PlayerLevelInfo[]>(sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL));
3918
3919 PlayerLevelInfo& levelInfo = info->levelInfo[current_level - 1];
3920 for (uint8 i = 0; i < MAX_STATS; ++i)
3921 levelInfo.stats[i] = fields[i + 3].GetUInt8();
3922 }
3923
3924 ++count;
3925 }
3926 while (result->NextRow());
3927
3928 // Fill gaps and check integrity
3929 for (uint8 race = 0; race < MAX_RACES; ++race)
3930 {
3931 // skip non existed races
3932 if (!sChrRacesStore.LookupEntry(race))
3933 continue;
3934
3935 for (uint8 class_ = 0; class_ < MAX_CLASSES; ++class_)
3936 {
3937 // skip non existed classes
3938 if (!sChrClassesStore.LookupEntry(class_))
3939 continue;
3940
3941 auto& info = _playerInfo[race][class_];
3942 if (!info)
3943 continue;
3944
3945 // skip expansion races if not playing with expansion
3946 if (sWorld->getIntConfig(CONFIG_EXPANSION) < EXPANSION_THE_BURNING_CRUSADE && (race == RACE_BLOODELF || race == RACE_DRAENEI))
3947 continue;
3948
3949 // skip expansion classes if not playing with expansion
3950 if (sWorld->getIntConfig(CONFIG_EXPANSION) < EXPANSION_WRATH_OF_THE_LICH_KING && class_ == CLASS_DEATH_KNIGHT)
3951 continue;
3952
3953 // fatal error if no level 1 data
3954 if (!info->levelInfo || info->levelInfo[0].stats[0] == 0)
3955 {
3956 TC_LOG_ERROR("sql.sql", "Race %i Class %i Level 1 does not have stats data!", race, class_);
3957 ABORT();
3958 }
3959
3960 // fill level gaps
3961 for (uint8 level = 1; level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
3962 {
3963 if (info->levelInfo[level].stats[0] == 0)
3964 {
3965 TC_LOG_ERROR("sql.sql", "Race %i Class %i Level %i does not have stats data. Using stats data of level %i.", race, class_, level + 1, level);
3966 info->levelInfo[level] = info->levelInfo[level - 1];
3967 }
3968 }
3969 }
3970 }
3971
3972 TC_LOG_INFO("server.loading", ">> Loaded %u level stats definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
3973 }
3974
3975 // Loading xp per level data
3976 TC_LOG_INFO("server.loading", "Loading Player Create XP Data...");
3977 {
3978 uint32 oldMSTime = getMSTime();
3979
3980 _playerXPperLevel.resize(sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL));
3981 for (uint8 level = 0; level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
3982 _playerXPperLevel[level] = 0;
3983
3984 // 0 1
3985 QueryResult result = WorldDatabase.Query("SELECT Level, Experience FROM player_xp_for_level");
3986
3987 if (!result)
3988 {
3989 TC_LOG_ERROR("server.loading", ">> Loaded 0 xp for level definitions. DB table `player_xp_for_level` is empty.");
3990 ABORT();
3991 }
3992
3993 uint32 count = 0;
3994
3995 do
3996 {
3997 Field* fields = result->Fetch();
3998
3999 uint32 current_level = fields[0].GetUInt8();
4000 uint32 current_xp = fields[1].GetUInt32();
4001
4002 if (current_level >= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
4003 {
4004 if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum
4005 TC_LOG_ERROR("sql.sql", "Wrong (> %u) level %u in `player_xp_for_level` table, ignoring.", STRONG_MAX_LEVEL, current_level);
4006 else
4007 {
4008 TC_LOG_INFO("misc", "Unused (> MaxPlayerLevel in worldserver.conf) level %u in `player_xp_for_levels` table, ignoring.", current_level);
4009 ++count; // make result loading percent "expected" correct in case disabled detail mode for example.
4010 }
4011 continue;
4012 }
4013 //PlayerXPperLevel
4014 _playerXPperLevel[current_level] = current_xp;
4015 ++count;
4016 }
4017 while (result->NextRow());
4018
4019 // fill level gaps
4020 for (uint8 level = 1; level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
4021 {
4022 if (_playerXPperLevel[level] == 0)
4023 {
4024 TC_LOG_ERROR("sql.sql", "Level %i does not have XP for level data. Using data of level [%i] + 100.", level + 1, level);
4025 _playerXPperLevel[level] = _playerXPperLevel[level - 1] + 100;
4026 }
4027 }
4028
4029 TC_LOG_INFO("server.loading", ">> Loaded %u xp for level definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
4030 }
4031}
4032
4033void ObjectMgr::GetPlayerClassLevelInfo(uint32 class_, uint8 level, PlayerClassLevelInfo* info) const
4034{
4035 if (level < 1 || class_ >= MAX_CLASSES)
4036 return;
4037
4038 auto const& pInfo = _playerClassInfo[class_];
4039
4040 if (level > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
4041 level = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
4042
4043 *info = pInfo->levelInfo[level - 1];
4044}
4045
4046void ObjectMgr::GetPlayerLevelInfo(uint32 race, uint32 class_, uint8 level, PlayerLevelInfo* info) const
4047{
4048 if (level < 1 || race >= MAX_RACES || class_ >= MAX_CLASSES)
4049 return;
4050
4051 auto const& pInfo = _playerInfo[race][class_];
4052 if (!pInfo)
4053 return;
4054
4055 if (level <= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
4056 *info = pInfo->levelInfo[level - 1];
4057 else
4058 BuildPlayerLevelInfo(race, class_, level, info);
4059}
4060
4061void ObjectMgr::BuildPlayerLevelInfo(uint8 race, uint8 _class, uint8 level, PlayerLevelInfo* info) const
4062{
4063 // base data (last known level)
4064 *info = _playerInfo[race][_class]->levelInfo[sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) - 1];
4065
4066 // if conversion from uint32 to uint8 causes unexpected behaviour, change lvl to uint32
4067 for (uint8 lvl = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) - 1; lvl < level; ++lvl)
4068 {
4069 switch (_class)
4070 {
4071 case CLASS_WARRIOR:
4072 info->stats[STAT_STRENGTH] += (lvl > 23 ? 2: (lvl > 1 ? 1: 0));
4073 info->stats[STAT_STAMINA] += (lvl > 23 ? 2: (lvl > 1 ? 1: 0));
4074 info->stats[STAT_AGILITY] += (lvl > 36 ? 1: (lvl > 6 && (lvl%2) ? 1: 0));
4075 info->stats[STAT_INTELLECT] += (lvl > 9 && !(lvl%2) ? 1: 0);
4076 info->stats[STAT_SPIRIT] += (lvl > 9 && !(lvl%2) ? 1: 0);
4077 break;
4078 case CLASS_PALADIN:
4079 info->stats[STAT_STRENGTH] += (lvl > 3 ? 1: 0);
4080 info->stats[STAT_STAMINA] += (lvl > 33 ? 2: (lvl > 1 ? 1: 0));
4081 info->stats[STAT_AGILITY] += (lvl > 38 ? 1: (lvl > 7 && !(lvl%2) ? 1: 0));
4082 info->stats[STAT_INTELLECT] += (lvl > 6 && (lvl%2) ? 1: 0);
4083 info->stats[STAT_SPIRIT] += (lvl > 7 ? 1: 0);
4084 break;
4085 case CLASS_HUNTER:
4086 info->stats[STAT_STRENGTH] += (lvl > 4 ? 1: 0);
4087 info->stats[STAT_STAMINA] += (lvl > 4 ? 1: 0);
4088 info->stats[STAT_AGILITY] += (lvl > 33 ? 2: (lvl > 1 ? 1: 0));
4089 info->stats[STAT_INTELLECT] += (lvl > 8 && (lvl%2) ? 1: 0);
4090 info->stats[STAT_SPIRIT] += (lvl > 38 ? 1: (lvl > 9 && !(lvl%2) ? 1: 0));
4091 break;
4092 case CLASS_ROGUE:
4093 info->stats[STAT_STRENGTH] += (lvl > 5 ? 1: 0);
4094 info->stats[STAT_STAMINA] += (lvl > 4 ? 1: 0);
4095 info->stats[STAT_AGILITY] += (lvl > 16 ? 2: (lvl > 1 ? 1: 0));
4096 info->stats[STAT_INTELLECT] += (lvl > 8 && !(lvl%2) ? 1: 0);
4097 info->stats[STAT_SPIRIT] += (lvl > 38 ? 1: (lvl > 9 && !(lvl%2) ? 1: 0));
4098 break;
4099 case CLASS_PRIEST:
4100 info->stats[STAT_STRENGTH] += (lvl > 9 && !(lvl%2) ? 1: 0);
4101 info->stats[STAT_STAMINA] += (lvl > 5 ? 1: 0);
4102 info->stats[STAT_AGILITY] += (lvl > 38 ? 1: (lvl > 8 && (lvl%2) ? 1: 0));
4103 info->stats[STAT_INTELLECT] += (lvl > 22 ? 2: (lvl > 1 ? 1: 0));
4104 info->stats[STAT_SPIRIT] += (lvl > 3 ? 1: 0);
4105 break;
4106 case CLASS_SHAMAN:
4107 info->stats[STAT_STRENGTH] += (lvl > 34 ? 1: (lvl > 6 && (lvl%2) ? 1: 0));
4108 info->stats[STAT_STAMINA] += (lvl > 4 ? 1: 0);
4109 info->stats[STAT_AGILITY] += (lvl > 7 && !(lvl%2) ? 1: 0);
4110 info->stats[STAT_INTELLECT] += (lvl > 5 ? 1: 0);
4111 info->stats[STAT_SPIRIT] += (lvl > 4 ? 1: 0);
4112 break;
4113 case CLASS_MAGE:
4114 info->stats[STAT_STRENGTH] += (lvl > 9 && !(lvl%2) ? 1: 0);
4115 info->stats[STAT_STAMINA] += (lvl > 5 ? 1: 0);
4116 info->stats[STAT_AGILITY] += (lvl > 9 && !(lvl%2) ? 1: 0);
4117 info->stats[STAT_INTELLECT] += (lvl > 24 ? 2: (lvl > 1 ? 1: 0));
4118 info->stats[STAT_SPIRIT] += (lvl > 33 ? 2: (lvl > 2 ? 1: 0));
4119 break;
4120 case CLASS_WARLOCK:
4121 info->stats[STAT_STRENGTH] += (lvl > 9 && !(lvl%2) ? 1: 0);
4122 info->stats[STAT_STAMINA] += (lvl > 38 ? 2: (lvl > 3 ? 1: 0));
4123 info->stats[STAT_AGILITY] += (lvl > 9 && !(lvl%2) ? 1: 0);
4124 info->stats[STAT_INTELLECT] += (lvl > 33 ? 2: (lvl > 2 ? 1: 0));
4125 info->stats[STAT_SPIRIT] += (lvl > 38 ? 2: (lvl > 3 ? 1: 0));
4126 break;
4127 case CLASS_DRUID:
4128 info->stats[STAT_STRENGTH] += (lvl > 38 ? 2: (lvl > 6 && (lvl%2) ? 1: 0));
4129 info->stats[STAT_STAMINA] += (lvl > 32 ? 2: (lvl > 4 ? 1: 0));
4130 info->stats[STAT_AGILITY] += (lvl > 38 ? 2: (lvl > 8 && (lvl%2) ? 1: 0));
4131 info->stats[STAT_INTELLECT] += (lvl > 38 ? 3: (lvl > 4 ? 1: 0));
4132 info->stats[STAT_SPIRIT] += (lvl > 38 ? 3: (lvl > 5 ? 1: 0));
4133 }
4134 }
4135}
4136
4137void ObjectMgr::LoadQuests()
4138{
4139 uint32 oldMSTime = getMSTime();
4140
4141 _questTemplates.clear();
4142
4143 _exclusiveQuestGroups.clear();
4144
4145 QueryResult result = WorldDatabase.Query("SELECT "
4146 //0 1 2 3 4 5 6 7 8
4147 "ID, QuestType, QuestLevel, MinLevel, QuestSortID, QuestInfoID, SuggestedGroupNum, TimeAllowed, AllowableRaces,"
4148 // 9 10 11 12
4149 "RequiredFactionId1, RequiredFactionId2, RequiredFactionValue1, RequiredFactionValue2, "
4150 // 13 14 15 16 17 18 19 20
4151 "RewardNextQuest, RewardXPDifficulty, RewardMoney, RewardBonusMoney, RewardDisplaySpell, RewardSpell, RewardHonor, RewardKillHonor, "
4152 // 21 22 23 24 25 26
4153 "StartItem, Flags, RewardTitle, RequiredPlayerKills, RewardTalents, RewardArenaPoints, "
4154 // 27 28 29 30 31 32 33 34
4155 "RewardItem1, RewardAmount1, RewardItem2, RewardAmount2, RewardItem3, RewardAmount3, RewardItem4, RewardAmount4, "
4156 // 35 36 37 38 39 40 41 42 43 44 45 46
4157 "RewardChoiceItemID1, RewardChoiceItemQuantity1, RewardChoiceItemID2, RewardChoiceItemQuantity2, RewardChoiceItemID3, RewardChoiceItemQuantity3, RewardChoiceItemID4, RewardChoiceItemQuantity4, RewardChoiceItemID5, RewardChoiceItemQuantity5, RewardChoiceItemID6, RewardChoiceItemQuantity6, "
4158 // 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
4159 "RewardFactionID1, RewardFactionValue1, RewardFactionOverride1, RewardFactionID2, RewardFactionValue2, RewardFactionOverride2, RewardFactionID3, RewardFactionValue3, RewardFactionOverride3, RewardFactionID4, RewardFactionValue4, RewardFactionOverride4, RewardFactionID5, RewardFactionValue5, RewardFactionOverride5,"
4160 // 62 63 64 65
4161 "POIContinent, POIx, POIy, POIPriority, "
4162 // 66 67 68 69 70
4163 "LogTitle, LogDescription, QuestDescription, AreaDescription, QuestCompletionLog, "
4164 // 71 72 73 74 75 76 77 78
4165 "RequiredNpcOrGo1, RequiredNpcOrGo2, RequiredNpcOrGo3, RequiredNpcOrGo4, RequiredNpcOrGoCount1, RequiredNpcOrGoCount2, RequiredNpcOrGoCount3, RequiredNpcOrGoCount4, "
4166 // 79 80 81 82 83 84 85 86
4167 "ItemDrop1, ItemDrop2, ItemDrop3, ItemDrop4, ItemDropQuantity1, ItemDropQuantity2, ItemDropQuantity3, ItemDropQuantity4, "
4168 // 87 88 89 90 91 92 93 94 95 96 97 98
4169 "RequiredItemId1, RequiredItemId2, RequiredItemId3, RequiredItemId4, RequiredItemId5, RequiredItemId6, RequiredItemCount1, RequiredItemCount2, RequiredItemCount3, RequiredItemCount4, RequiredItemCount5, RequiredItemCount6, "
4170 // 99 100 101 102 103
4171 "Unknown0, ObjectiveText1, ObjectiveText2, ObjectiveText3, ObjectiveText4"
4172 " FROM quest_template");
4173 if (!result)
4174 {
4175 TC_LOG_INFO("server.loading", ">> Loaded 0 quests definitions. DB table `quest_template` is empty.");
4176 return;
4177 }
4178
4179 _questTemplates.reserve(result->GetRowCount());
4180
4181 // create multimap previous quest for each existed quest
4182 // some quests can have many previous maps set by NextQuestId in previous quest
4183 // for example set of race quests can lead to single not race specific quest
4184 do
4185 {
4186 Field* fields = result->Fetch();
4187
4188 uint32 questId = fields[0].GetUInt32();
4189 _questTemplates.emplace(std::piecewise_construct, std::forward_as_tuple(questId), std::forward_as_tuple(fields));
4190 } while (result->NextRow());
4191
4192 std::unordered_map<uint32, uint32> usedMailTemplates;
4193
4194 struct QuestLoaderHelper
4195 {
4196 typedef void(Quest::*QuestLoaderFunction)(Field* fields);
4197
4198 char const* QueryFields;
4199 char const* TableName;
4200 char const* TableDesc;
4201 QuestLoaderFunction LoaderFunction;
4202 };
4203
4204 static std::vector<QuestLoaderHelper> const QuestLoaderHelpers =
4205 {
4206 // 0 1 2 3 4 5 6 7 8
4207 { "ID, Emote1, Emote2, Emote3, Emote4, EmoteDelay1, EmoteDelay2, EmoteDelay3, EmoteDelay4", "quest_details", "details", &Quest::LoadQuestDetails },
4208
4209 // 0 1 2 3
4210 { "ID, EmoteOnComplete, EmoteOnIncomplete, CompletionText", "quest_request_items", "request items", &Quest::LoadQuestRequestItems },
4211
4212 // 0 1 2 3 4 5 6 7 8 9
4213 { "ID, Emote1, Emote2, Emote3, Emote4, EmoteDelay1, EmoteDelay2, EmoteDelay3, EmoteDelay4, RewardText", "quest_offer_reward", "reward emotes", &Quest::LoadQuestOfferReward },
4214
4215 // 0 1 2 3 4 5 6 7 8
4216 { "ID, MaxLevel, AllowableClasses, SourceSpellID, PrevQuestID, NextQuestID, ExclusiveGroup, RewardMailTemplateID, RewardMailDelay,"
4217 // 9 10 11 12 13 14 15 16
4218 " RequiredSkillID, RequiredSkillPoints, RequiredMinRepFaction, RequiredMaxRepFaction, RequiredMinRepValue, RequiredMaxRepValue, ProvidedItemCount, SpecialFlags", "quest_template_addon", "template addons", &Quest::LoadQuestTemplateAddon },
4219
4220 // 0 1
4221 { "QuestId, RewardMailSenderEntry", "quest_mail_sender", "mail sender entries", &Quest::LoadQuestMailSender }
4222 };
4223
4224 for (QuestLoaderHelper const& loader : QuestLoaderHelpers)
4225 {
4226 result = WorldDatabase.PQuery("SELECT %s FROM %s", loader.QueryFields, loader.TableName);
4227 if (!result)
4228 TC_LOG_INFO("server.loading", ">> Loaded 0 quest %s. DB table `%s` is empty.", loader.TableDesc, loader.TableName);
4229 else
4230 {
4231 do
4232 {
4233 Field* fields = result->Fetch();
4234 uint32 questId = fields[0].GetUInt32();
4235
4236 auto itr = _questTemplates.find(questId);
4237 if (itr != _questTemplates.end())
4238 (itr->second.*loader.LoaderFunction)(fields);
4239 else
4240 TC_LOG_ERROR("server.loading", "Table `%s` has data for quest %u but such quest does not exist", loader.TableName, questId);
4241 } while (result->NextRow());
4242 }
4243 }
4244
4245 // Post processing
4246 for (auto& questPair : _questTemplates)
4247 {
4248 // skip post-loading checks for disabled quests
4249 if (DisableMgr::IsDisabledFor(DISABLE_TYPE_QUEST, questPair.first, nullptr))
4250 continue;
4251
4252 Quest* qinfo = &questPair.second;
4253
4254 // additional quest integrity checks (GO, creature_template and item_template must be loaded already)
4255
4256 if (qinfo->GetQuestMethod() >= 3)
4257 TC_LOG_ERROR("sql.sql", "Quest %u has `Method` = %u, expected values are 0, 1 or 2.", qinfo->GetQuestId(), qinfo->GetQuestMethod());
4258
4259 if (qinfo->_specialFlags & ~QUEST_SPECIAL_FLAGS_DB_ALLOWED)
4260 {
4261 TC_LOG_ERROR("sql.sql", "Quest %u has `SpecialFlags` = %u > max allowed value. Correct `SpecialFlags` to value <= %u",
4262 qinfo->GetQuestId(), qinfo->_specialFlags, QUEST_SPECIAL_FLAGS_DB_ALLOWED);
4263 qinfo->_specialFlags &= QUEST_SPECIAL_FLAGS_DB_ALLOWED;
4264 }
4265
4266 if (qinfo->_flags & QUEST_FLAGS_DAILY && qinfo->_flags & QUEST_FLAGS_WEEKLY)
4267 {
4268 TC_LOG_ERROR("sql.sql", "Weekly Quest %u is marked as daily quest in `Flags`, removed daily flag.", qinfo->GetQuestId());
4269 qinfo->_flags &= ~QUEST_FLAGS_DAILY;
4270 }
4271
4272 if (qinfo->_flags & QUEST_FLAGS_DAILY)
4273 {
4274 if (!(qinfo->_specialFlags & QUEST_SPECIAL_FLAGS_REPEATABLE))
4275 {
4276 TC_LOG_ERROR("sql.sql", "Daily Quest %u not marked as repeatable in `SpecialFlags`, added.", qinfo->GetQuestId());
4277 qinfo->_specialFlags |= QUEST_SPECIAL_FLAGS_REPEATABLE;
4278 }
4279 }
4280
4281 if (qinfo->_flags & QUEST_FLAGS_WEEKLY)
4282 {
4283 if (!(qinfo->_specialFlags & QUEST_SPECIAL_FLAGS_REPEATABLE))
4284 {
4285 TC_LOG_ERROR("sql.sql", "Weekly Quest %u not marked as repeatable in `SpecialFlags`, added.", qinfo->GetQuestId());
4286 qinfo->_specialFlags |= QUEST_SPECIAL_FLAGS_REPEATABLE;
4287 }
4288 }
4289
4290 if (qinfo->_specialFlags & QUEST_SPECIAL_FLAGS_MONTHLY)
4291 {
4292 if (!(qinfo->_specialFlags & QUEST_SPECIAL_FLAGS_REPEATABLE))
4293 {
4294 TC_LOG_ERROR("sql.sql", "Monthly quest %u not marked as repeatable in `SpecialFlags`, added.", qinfo->GetQuestId());
4295 qinfo->_specialFlags |= QUEST_SPECIAL_FLAGS_REPEATABLE;
4296 }
4297 }
4298
4299 if (qinfo->_flags & QUEST_FLAGS_TRACKING)
4300 {
4301 // at auto-reward can be rewarded only RewardChoiceItemId[0]
4302 for (uint32 j = 1; j < QUEST_REWARD_CHOICES_COUNT; ++j)
4303 {
4304 if (uint32 id = qinfo->RewardChoiceItemId[j])
4305 {
4306 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardChoiceItemId%d` = %u but item from `RewardChoiceItemId%d` can't be rewarded with quest flag QUEST_FLAGS_TRACKING.",
4307 qinfo->GetQuestId(), j + 1, id, j + 1);
4308 // no changes, quest ignore this data
4309 }
4310 }
4311 }
4312
4313 // client quest log visual (area case)
4314 if (qinfo->_zoneOrSort > 0)
4315 {
4316 if (!sAreaTableStore.LookupEntry(qinfo->_zoneOrSort))
4317 {
4318 TC_LOG_ERROR("sql.sql", "Quest %u has `ZoneOrSort` = %u (zone case) but zone with this id does not exist.",
4319 qinfo->GetQuestId(), qinfo->_zoneOrSort);
4320 // no changes, quest not dependent from this value but can have problems at client
4321 }
4322 }
4323 // client quest log visual (sort case)
4324 if (qinfo->_zoneOrSort < 0)
4325 {
4326 QuestSortEntry const* qSort = sQuestSortStore.LookupEntry(-int32(qinfo->_zoneOrSort));
4327 if (!qSort)
4328 {
4329 TC_LOG_ERROR("sql.sql", "Quest %u has `ZoneOrSort` = %i (sort case) but quest sort with this id does not exist.",
4330 qinfo->GetQuestId(), qinfo->_zoneOrSort);
4331 // 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)
4332 }
4333 //check for proper RequiredSkillId value (skill case)
4334 if (uint32 skill_id = SkillByQuestSort(-qinfo->_zoneOrSort))
4335 {
4336 if (qinfo->_requiredSkillId != skill_id)
4337 {
4338 TC_LOG_ERROR("sql.sql", "Quest %u has `ZoneOrSort` = %i but `RequiredSkillId` does not have a corresponding value (%d).",
4339 qinfo->GetQuestId(), qinfo->_zoneOrSort, skill_id);
4340 //override, and force proper value here?
4341 }
4342 }
4343 }
4344
4345 // RequiredClasses, can be 0/CLASSMASK_ALL_PLAYABLE to allow any class
4346 if (qinfo->_requiredClasses)
4347 {
4348 if (!(qinfo->_requiredClasses & CLASSMASK_ALL_PLAYABLE))
4349 {
4350 TC_LOG_ERROR("sql.sql", "Quest %u does not contain any playable classes in `RequiredClasses` (%u), value set to 0 (all classes).", qinfo->GetQuestId(), qinfo->_requiredClasses);
4351 qinfo->_requiredClasses = 0;
4352 }
4353 }
4354
4355 // AllowableRaces, can be 0/RACEMASK_ALL_PLAYABLE to allow any race
4356 if (qinfo->_allowableRaces)
4357 {
4358 if (!(qinfo->_allowableRaces & RACEMASK_ALL_PLAYABLE))
4359 {
4360 TC_LOG_ERROR("sql.sql", "Quest %u does not contain any playable races in `AllowableRaces` (%u), value set to 0 (all races).", qinfo->GetQuestId(), qinfo->_allowableRaces);
4361 qinfo->_allowableRaces = 0;
4362 }
4363 }
4364
4365 // RequiredSkillId, can be 0
4366 if (qinfo->_requiredSkillId)
4367 {
4368 if (!sSkillLineStore.LookupEntry(qinfo->_requiredSkillId))
4369 {
4370 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredSkillId` = %u but this skill does not exist",
4371 qinfo->GetQuestId(), qinfo->_requiredSkillId);
4372 }
4373 }
4374
4375 if (qinfo->_requiredSkillPoints)
4376 {
4377 if (qinfo->_requiredSkillPoints > sWorld->GetConfigMaxSkillValue())
4378 {
4379 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredSkillPoints` = %u but max possible skill is %u, quest can't be done.",
4380 qinfo->GetQuestId(), qinfo->_requiredSkillPoints, sWorld->GetConfigMaxSkillValue());
4381 // no changes, quest can't be done for this requirement
4382 }
4383 }
4384 // else Skill quests can have 0 skill level, this is ok
4385
4386 if (qinfo->_requiredFactionId2 && !sFactionStore.LookupEntry(qinfo->_requiredFactionId2))
4387 {
4388 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredFactionId2` = %u but faction template %u does not exist, quest can't be done.",
4389 qinfo->GetQuestId(), qinfo->_requiredFactionId2, qinfo->_requiredFactionId2);
4390 // no changes, quest can't be done for this requirement
4391 }
4392
4393 if (qinfo->_requiredFactionId1 && !sFactionStore.LookupEntry(qinfo->_requiredFactionId1))
4394 {
4395 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredFactionId1` = %u but faction template %u does not exist, quest can't be done.",
4396 qinfo->GetQuestId(), qinfo->_requiredFactionId1, qinfo->_requiredFactionId1);
4397 // no changes, quest can't be done for this requirement
4398 }
4399
4400 if (qinfo->_requiredMinRepFaction && !sFactionStore.LookupEntry(qinfo->_requiredMinRepFaction))
4401 {
4402 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredMinRepFaction` = %u but faction template %u does not exist, quest can't be done.",
4403 qinfo->GetQuestId(), qinfo->_requiredMinRepFaction, qinfo->_requiredMinRepFaction);
4404 // no changes, quest can't be done for this requirement
4405 }
4406
4407 if (qinfo->_requiredMaxRepFaction && !sFactionStore.LookupEntry(qinfo->_requiredMaxRepFaction))
4408 {
4409 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredMaxRepFaction` = %u but faction template %u does not exist, quest can't be done.",
4410 qinfo->GetQuestId(), qinfo->_requiredMaxRepFaction, qinfo->_requiredMaxRepFaction);
4411 // no changes, quest can't be done for this requirement
4412 }
4413
4414 if (qinfo->_requiredMinRepValue && qinfo->_requiredMinRepValue > ReputationMgr::Reputation_Cap)
4415 {
4416 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredMinRepValue` = %d but max reputation is %u, quest can't be done.",
4417 qinfo->GetQuestId(), qinfo->_requiredMinRepValue, ReputationMgr::Reputation_Cap);
4418 // no changes, quest can't be done for this requirement
4419 }
4420
4421 if (qinfo->_requiredMinRepValue && qinfo->_requiredMaxRepValue && qinfo->_requiredMaxRepValue <= qinfo->_requiredMinRepValue)
4422 {
4423 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredMaxRepValue` = %d and `RequiredMinRepValue` = %d, quest can't be done.",
4424 qinfo->GetQuestId(), qinfo->_requiredMaxRepValue, qinfo->_requiredMinRepValue);
4425 // no changes, quest can't be done for this requirement
4426 }
4427
4428 if (!qinfo->_requiredFactionId1 && qinfo->_requiredFactionValue1 != 0)
4429 {
4430 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredFactionValue1` = %d but `RequiredFactionId1` is 0, value has no effect",
4431 qinfo->GetQuestId(), qinfo->_requiredFactionValue1);
4432 // warning
4433 }
4434
4435 if (!qinfo->_requiredFactionId2 && qinfo->_requiredFactionValue2 != 0)
4436 {
4437 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredFactionValue2` = %d but `RequiredFactionId2` is 0, value has no effect",
4438 qinfo->GetQuestId(), qinfo->_requiredFactionValue2);
4439 // warning
4440 }
4441
4442 if (!qinfo->_requiredMinRepFaction && qinfo->_requiredMinRepValue != 0)
4443 {
4444 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredMinRepValue` = %d but `RequiredMinRepFaction` is 0, value has no effect",
4445 qinfo->GetQuestId(), qinfo->_requiredMinRepValue);
4446 // warning
4447 }
4448
4449 if (!qinfo->_requiredMaxRepFaction && qinfo->_requiredMaxRepValue != 0)
4450 {
4451 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredMaxRepValue` = %d but `RequiredMaxRepFaction` is 0, value has no effect",
4452 qinfo->GetQuestId(), qinfo->_requiredMaxRepValue);
4453 // warning
4454 }
4455
4456 if (qinfo->_rewardTitleId && !sCharTitlesStore.LookupEntry(qinfo->_rewardTitleId))
4457 {
4458 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardTitleId` = %u but CharTitle Id %u does not exist, quest can't be rewarded with title.",
4459 qinfo->GetQuestId(), qinfo->GetCharTitleId(), qinfo->GetCharTitleId());
4460 qinfo->_rewardTitleId = 0;
4461 // quest can't reward this title
4462 }
4463
4464 if (qinfo->_startItem)
4465 {
4466 if (!sObjectMgr->GetItemTemplate(qinfo->_startItem))
4467 {
4468 TC_LOG_ERROR("sql.sql", "Quest %u has `StartItem` = %u but item with entry %u does not exist, quest can't be done.",
4469 qinfo->GetQuestId(), qinfo->_startItem, qinfo->_startItem);
4470 qinfo->_startItem = 0; // quest can't be done for this requirement
4471 }
4472 else if (qinfo->_startItemCount == 0)
4473 {
4474 TC_LOG_ERROR("sql.sql", "Quest %u has `StartItem` = %u but `StartItemCount` = 0, set to 1 but need fix in DB.",
4475 qinfo->GetQuestId(), qinfo->_startItem);
4476 qinfo->_startItemCount = 1; // update to 1 for allow quest work for backward compatibility with DB
4477 }
4478 }
4479 else if (qinfo->_startItemCount > 0)
4480 {
4481 TC_LOG_ERROR("sql.sql", "Quest %u has `StartItem` = 0 but `StartItemCount` = %u, useless value.",
4482 qinfo->GetQuestId(), qinfo->_startItemCount);
4483 qinfo->_startItemCount = 0; // no quest work changes in fact
4484 }
4485
4486 if (qinfo->_sourceSpellid)
4487 {
4488 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(qinfo->_sourceSpellid);
4489 if (!spellInfo)
4490 {
4491 TC_LOG_ERROR("sql.sql", "Quest %u has `SourceSpellid` = %u but spell %u doesn't exist, quest can't be done.",
4492 qinfo->GetQuestId(), qinfo->_sourceSpellid, qinfo->_sourceSpellid);
4493 qinfo->_sourceSpellid = 0; // quest can't be done for this requirement
4494 }
4495 else if (!SpellMgr::IsSpellValid(spellInfo))
4496 {
4497 TC_LOG_ERROR("sql.sql", "Quest %u has `SourceSpellid` = %u but spell %u is broken, quest can't be done.",
4498 qinfo->GetQuestId(), qinfo->_sourceSpellid, qinfo->_sourceSpellid);
4499 qinfo->_sourceSpellid = 0; // quest can't be done for this requirement
4500 }
4501 }
4502
4503 for (uint8 j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j)
4504 {
4505 uint32 id = qinfo->RequiredItemId[j];
4506 if (id)
4507 {
4508 if (qinfo->RequiredItemCount[j] == 0)
4509 {
4510 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredItemId%d` = %u but `RequiredItemCount%d` = 0, quest can't be done.",
4511 qinfo->GetQuestId(), j+1, id, j+1);
4512 // no changes, quest can't be done for this requirement
4513 }
4514
4515 qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_DELIVER);
4516
4517 if (!sObjectMgr->GetItemTemplate(id))
4518 {
4519 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredItemId%d` = %u but item with entry %u does not exist, quest can't be done.",
4520 qinfo->GetQuestId(), j+1, id, id);
4521 qinfo->RequiredItemCount[j] = 0; // prevent incorrect work of quest
4522 }
4523 }
4524 else if (qinfo->RequiredItemCount[j]>0)
4525 {
4526 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredItemId%d` = 0 but `RequiredItemCount%d` = %u, quest can't be done.",
4527 qinfo->GetQuestId(), j+1, j+1, qinfo->RequiredItemCount[j]);
4528 qinfo->RequiredItemCount[j] = 0; // prevent incorrect work of quest
4529 }
4530 }
4531
4532 for (uint8 j = 0; j < QUEST_SOURCE_ITEM_IDS_COUNT; ++j)
4533 {
4534 uint32 id = qinfo->ItemDrop[j];
4535 if (id)
4536 {
4537 if (!sObjectMgr->GetItemTemplate(id))
4538 {
4539 TC_LOG_ERROR("sql.sql", "Quest %u has `ItemDrop%d` = %u but item with entry %u does not exist, quest can't be done.",
4540 qinfo->GetQuestId(), j+1, id, id);
4541 // no changes, quest can't be done for this requirement
4542 }
4543 }
4544 else
4545 {
4546 if (qinfo->ItemDropQuantity[j]>0)
4547 {
4548 TC_LOG_ERROR("sql.sql", "Quest %u has `ItemDrop%d` = 0 but `ItemDropQuantity%d` = %u.",
4549 qinfo->GetQuestId(), j+1, j+1, qinfo->ItemDropQuantity[j]);
4550 // no changes, quest ignore this data
4551 }
4552 }
4553 }
4554
4555 for (uint8 j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
4556 {
4557 int32 id = qinfo->RequiredNpcOrGo[j];
4558 if (id < 0 && !sObjectMgr->GetGameObjectTemplate(-id))
4559 {
4560 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredNpcOrGo%d` = %i but gameobject %u does not exist, quest can't be done.",
4561 qinfo->GetQuestId(), j+1, id, uint32(-id));
4562 qinfo->RequiredNpcOrGo[j] = 0; // quest can't be done for this requirement
4563 }
4564
4565 if (id > 0 && !sObjectMgr->GetCreatureTemplate(id))
4566 {
4567 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredNpcOrGo%d` = %i but creature with entry %u does not exist, quest can't be done.",
4568 qinfo->GetQuestId(), j+1, id, uint32(id));
4569 qinfo->RequiredNpcOrGo[j] = 0; // quest can't be done for this requirement
4570 }
4571
4572 if (id)
4573 {
4574 // In fact SpeakTo and Kill are quite same: either you can speak to mob:SpeakTo or you can't:Kill/Cast
4575
4576 qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO);
4577
4578 if (!qinfo->RequiredNpcOrGoCount[j])
4579 {
4580 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredNpcOrGo%d` = %u but `RequiredNpcOrGoCount%d` = 0, quest can't be done.",
4581 qinfo->GetQuestId(), j+1, id, j+1);
4582 // no changes, quest can be incorrectly done, but we already report this
4583 }
4584 }
4585 else if (qinfo->RequiredNpcOrGoCount[j]>0)
4586 {
4587 TC_LOG_ERROR("sql.sql", "Quest %u has `RequiredNpcOrGo%d` = 0 but `RequiredNpcOrGoCount%d` = %u.",
4588 qinfo->GetQuestId(), j+1, j+1, qinfo->RequiredNpcOrGoCount[j]);
4589 // no changes, quest ignore this data
4590 }
4591 }
4592
4593 for (uint8 j = 0; j < QUEST_REWARD_CHOICES_COUNT; ++j)
4594 {
4595 uint32 id = qinfo->RewardChoiceItemId[j];
4596 if (id)
4597 {
4598 if (!sObjectMgr->GetItemTemplate(id))
4599 {
4600 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardChoiceItemId%d` = %u but item with entry %u does not exist, quest will not reward this item.",
4601 qinfo->GetQuestId(), j+1, id, id);
4602 qinfo->RewardChoiceItemId[j] = 0; // no changes, quest will not reward this
4603 }
4604
4605 if (!qinfo->RewardChoiceItemCount[j])
4606 {
4607 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardChoiceItemId%d` = %u but `RewardChoiceItemCount%d` = 0, quest can't be done.",
4608 qinfo->GetQuestId(), j+1, id, j+1);
4609 // no changes, quest can't be done
4610 }
4611 }
4612 else if (qinfo->RewardChoiceItemCount[j]>0)
4613 {
4614 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardChoiceItemId%d` = 0 but `RewardChoiceItemCount%d` = %u.",
4615 qinfo->GetQuestId(), j+1, j+1, qinfo->RewardChoiceItemCount[j]);
4616 // no changes, quest ignore this data
4617 }
4618 }
4619
4620 for (uint8 j = 0; j < QUEST_REWARDS_COUNT; ++j)
4621 {
4622 uint32 id = qinfo->RewardItemId[j];
4623 if (id)
4624 {
4625 if (!sObjectMgr->GetItemTemplate(id))
4626 {
4627 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardItemId%d` = %u but item with entry %u does not exist, quest will not reward this item.",
4628 qinfo->GetQuestId(), j+1, id, id);
4629 qinfo->RewardItemId[j] = 0; // no changes, quest will not reward this item
4630 }
4631
4632 if (!qinfo->RewardItemIdCount[j])
4633 {
4634 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardItemId%d` = %u but `RewardItemIdCount%d` = 0, quest will not reward this item.",
4635 qinfo->GetQuestId(), j+1, id, j+1);
4636 // no changes
4637 }
4638 }
4639 else if (qinfo->RewardItemIdCount[j]>0)
4640 {
4641 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardItemId%d` = 0 but `RewardItemIdCount%d` = %u.",
4642 qinfo->GetQuestId(), j+1, j+1, qinfo->RewardItemIdCount[j]);
4643 // no changes, quest ignore this data
4644 }
4645 }
4646
4647 for (uint8 j = 0; j < QUEST_REPUTATIONS_COUNT; ++j)
4648 {
4649 if (qinfo->RewardFactionId[j])
4650 {
4651 if (std::abs(qinfo->RewardFactionValueId[j]) > 9)
4652 {
4653 TC_LOG_ERROR("sql.sql", "Quest %u has RewardFactionValueId%d = %i. That is outside the range of valid values (-9 to 9).", qinfo->GetQuestId(), j+1, qinfo->RewardFactionValueId[j]);
4654 }
4655 if (!sFactionStore.LookupEntry(qinfo->RewardFactionId[j]))
4656 {
4657 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardFactionId%d` = %u but raw faction (faction.dbc) %u does not exist, quest will not reward reputation for this faction.", qinfo->GetQuestId(), j+1, qinfo->RewardFactionId[j], qinfo->RewardFactionId[j]);
4658 qinfo->RewardFactionId[j] = 0; // quest will not reward this
4659 }
4660 }
4661
4662 else if (qinfo->RewardFactionValueIdOverride[j] != 0)
4663 {
4664 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardFactionId%d` = 0 but `RewardFactionValueIdOverride%d` = %i.",
4665 qinfo->GetQuestId(), j+1, j+1, qinfo->RewardFactionValueIdOverride[j]);
4666 // no changes, quest ignore this data
4667 }
4668 }
4669
4670 if (qinfo->_rewardDisplaySpell)
4671 {
4672 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(qinfo->_rewardDisplaySpell);
4673 if (!spellInfo)
4674 {
4675 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardDisplaySpell` = %u but spell %u does not exist, spell removed as display reward.",
4676 qinfo->GetQuestId(), qinfo->_rewardDisplaySpell, qinfo->_rewardDisplaySpell);
4677 qinfo->_rewardDisplaySpell = 0; // no spell reward will display for this quest
4678 }
4679
4680 else if (!SpellMgr::IsSpellValid(spellInfo))
4681 {
4682 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardDisplaySpell` = %u but spell %u is broken, quest will not have a spell reward.",
4683 qinfo->GetQuestId(), qinfo->_rewardDisplaySpell, qinfo->_rewardDisplaySpell);
4684 qinfo->_rewardDisplaySpell = 0; // no spell reward will display for this quest
4685 }
4686
4687 else if (GetTalentSpellCost(qinfo->_rewardDisplaySpell))
4688 {
4689 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardDisplaySpell` = %u but spell %u is talent, quest will not have a spell reward.",
4690 qinfo->GetQuestId(), qinfo->_rewardDisplaySpell, qinfo->_rewardDisplaySpell);
4691 qinfo->_rewardDisplaySpell = 0; // no spell reward will display for this quest
4692 }
4693 }
4694
4695 if (qinfo->_rewardSpell > 0)
4696 {
4697 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(qinfo->_rewardSpell);
4698 if (!spellInfo)
4699 {
4700 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardSpell` = %u but spell %u does not exist, quest will not have a spell reward.",
4701 qinfo->GetQuestId(), qinfo->_rewardSpell, qinfo->_rewardSpell);
4702 qinfo->_rewardSpell = 0; // no spell will be cast on player
4703 }
4704
4705 else if (!SpellMgr::IsSpellValid(spellInfo))
4706 {
4707 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardSpell` = %u but spell %u is broken, quest will not have a spell reward.",
4708 qinfo->GetQuestId(), qinfo->_rewardSpell, qinfo->_rewardSpell);
4709 qinfo->_rewardSpell = 0; // no spell will be cast on player
4710 }
4711
4712 else if (GetTalentSpellCost(qinfo->_rewardSpell))
4713 {
4714 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardDisplaySpell` = %u but spell %u is talent, quest will not have a spell reward.",
4715 qinfo->GetQuestId(), qinfo->_rewardSpell, qinfo->_rewardSpell);
4716 qinfo->_rewardSpell = 0; // no spell will be cast on player
4717 }
4718 }
4719
4720 if (qinfo->_rewardMailTemplateId)
4721 {
4722 if (!sMailTemplateStore.LookupEntry(qinfo->_rewardMailTemplateId))
4723 {
4724 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardMailTemplateId` = %u but mail template %u does not exist, quest will not have a mail reward.",
4725 qinfo->GetQuestId(), qinfo->_rewardMailTemplateId, qinfo->_rewardMailTemplateId);
4726 qinfo->_rewardMailTemplateId = 0; // no mail will send to player
4727 qinfo->_rewardMailDelay = 0; // no mail will send to player
4728 qinfo->_rewardMailSenderEntry = 0;
4729 }
4730 else if (usedMailTemplates.find(qinfo->_rewardMailTemplateId) != usedMailTemplates.end())
4731 {
4732 auto used_mt_itr = usedMailTemplates.find(qinfo->_rewardMailTemplateId);
4733 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardMailTemplateId` = %u but mail template %u already used for quest %u, quest will not have a mail reward.",
4734 qinfo->GetQuestId(), qinfo->_rewardMailTemplateId, qinfo->_rewardMailTemplateId, used_mt_itr->second);
4735 qinfo->_rewardMailTemplateId = 0; // no mail will send to player
4736 qinfo->_rewardMailDelay = 0; // no mail will send to player
4737 qinfo->_rewardMailSenderEntry = 0;
4738 }
4739 else
4740 usedMailTemplates.emplace(qinfo->_rewardMailTemplateId, qinfo->GetQuestId());
4741 }
4742
4743 if (uint32 rewardNextQuest = qinfo->_rewardNextQuest)
4744 {
4745 if (!_questTemplates.count(rewardNextQuest))
4746 {
4747 TC_LOG_ERROR("sql.sql", "Quest %u has `RewardNextQuest` = %u but quest %u does not exist, quest chain will not work.",
4748 qinfo->GetQuestId(), qinfo->_rewardNextQuest, qinfo->_rewardNextQuest);
4749 qinfo->_rewardNextQuest = 0;
4750 }
4751 }
4752
4753 // fill additional data stores
4754 if (uint32 prevQuestId = std::abs(qinfo->_prevQuestId))
4755 {
4756 if (!_questTemplates.count(prevQuestId))
4757 TC_LOG_ERROR("sql.sql", "Quest %u has PrevQuestId %i, but no such quest", qinfo->GetQuestId(), qinfo->_prevQuestId);
4758 }
4759
4760 if (uint32 nextQuestId = qinfo->_nextQuestId)
4761 {
4762 auto nextQuestItr = _questTemplates.find(nextQuestId);
4763 if (nextQuestItr == _questTemplates.end())
4764 TC_LOG_ERROR("sql.sql", "Quest %u has NextQuestId %u, but no such quest", qinfo->GetQuestId(), qinfo->_nextQuestId);
4765 else
4766 nextQuestItr->second.DependentPreviousQuests.push_back(qinfo->GetQuestId());
4767 }
4768
4769 if (qinfo->_exclusiveGroup)
4770 _exclusiveQuestGroups.emplace(qinfo->_exclusiveGroup, qinfo->GetQuestId());
4771 if (qinfo->_timeAllowed)
4772 qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_TIMED);
4773 if (qinfo->_requiredPlayerKills)
4774 qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_PLAYER_KILL);
4775
4776 // Special flag to determine if quest is completed from the start, used to determine if we can fail timed quest if it is completed
4777 if (!qinfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_KILL | QUEST_SPECIAL_FLAGS_CAST | QUEST_SPECIAL_FLAGS_SPEAKTO | QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT))
4778 {
4779 bool addFlag = true;
4780 if (qinfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_DELIVER))
4781 {
4782 for (uint8 j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j)
4783 {
4784 if (qinfo->RequiredItemId[j] != 0 && (qinfo->RequiredItemId[j] != qinfo->GetSrcItemId() || qinfo->RequiredItemCount[j] > qinfo->GetSrcItemCount()))
4785 {
4786 addFlag = false;
4787 break;
4788 }
4789 }
4790 }
4791
4792 if (addFlag)
4793 qinfo->SetSpecialFlag(QUEST_SPECIAL_FLAGS_COMPLETED_AT_START);
4794 }
4795 }
4796
4797 // check QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT for spell with SPELL_EFFECT_QUEST_COMPLETE
4798 for (uint32 i = 0; i < sSpellMgr->GetSpellInfoStoreSize(); ++i)
4799 {
4800 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(i);
4801 if (!spellInfo)
4802 continue;
4803
4804 for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
4805 {
4806 if (spellInfo->Effects[j].Effect != SPELL_EFFECT_QUEST_COMPLETE)
4807 continue;
4808
4809 uint32 quest_id = spellInfo->Effects[j].MiscValue;
4810
4811 Quest const* quest = GetQuestTemplate(quest_id);
4812
4813 // some quest referenced in spells not exist (outdated spells)
4814 if (!quest)
4815 continue;
4816
4817 if (!quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT))
4818 {
4819 TC_LOG_ERROR("sql.sql", "Spell (id: %u) have SPELL_EFFECT_QUEST_COMPLETE for quest %u, but quest not have flag QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT. Quest flags must be fixed, quest modified to enable objective.", spellInfo->Id, quest_id);
4820
4821 // this will prevent quest completing without objective
4822 const_cast<Quest*>(quest)->SetSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT);
4823 }
4824 }
4825 }
4826
4827 TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " quests definitions in %u ms", _questTemplates.size(), GetMSTimeDiffToNow(oldMSTime));
4828}
4829
4830void ObjectMgr::LoadQuestStartersAndEnders()
4831{
4832 TC_LOG_INFO("server.loading", "Loading GO Start Quest Data...");
4833 LoadGameobjectQuestStarters();
4834 TC_LOG_INFO("server.loading", "Loading GO End Quest Data...");
4835 LoadGameobjectQuestEnders();
4836 TC_LOG_INFO("server.loading", "Loading Creature Start Quest Data...");
4837 LoadCreatureQuestStarters();
4838 TC_LOG_INFO("server.loading", "Loading Creature End Quest Data...");
4839 LoadCreatureQuestEnders();
4840}
4841
4842void ObjectMgr::LoadQuestLocales()
4843{
4844 uint32 oldMSTime = getMSTime();
4845
4846 _questLocaleStore.clear(); // need for reload case
4847
4848 // 0 1 2 3 4 5 6 7 8 9 10 11 12
4849 QueryResult result = WorldDatabase.Query("SELECT ID, locale, Title, Details, Objectives, OfferRewardText, RequestItemsText, EndText, CompletedText, ObjectiveText1, ObjectiveText2, ObjectiveText3, ObjectiveText4 FROM quest_template_locale");
4850 if (!result)
4851 return;
4852
4853 do
4854 {
4855 Field* fields = result->Fetch();
4856
4857 uint32 id = fields[0].GetUInt32();
4858 std::string localeName = fields[1].GetString();
4859
4860 QuestLocale& data = _questLocaleStore[id];
4861
4862 LocaleConstant locale = GetLocaleByName(localeName);
4863 if (locale == LOCALE_enUS)
4864 continue;
4865
4866 AddLocaleString(fields[2].GetString(), locale, data.Title);
4867 AddLocaleString(fields[3].GetString(), locale, data.Details);
4868 AddLocaleString(fields[4].GetString(), locale, data.Objectives);
4869 AddLocaleString(fields[5].GetString(), locale, data.OfferRewardText);
4870 AddLocaleString(fields[6].GetString(), locale, data.RequestItemsText);
4871 AddLocaleString(fields[7].GetString(), locale, data.AreaDescription);
4872 AddLocaleString(fields[8].GetString(), locale, data.CompletedText);
4873
4874 for (uint8 i = 0; i < 4; ++i)
4875 AddLocaleString(fields[i + 9].GetString(), locale, data.ObjectiveText[i]);
4876 } while (result->NextRow());
4877
4878 TC_LOG_INFO("server.loading", ">> Loaded %u Quest locale strings in %u ms", uint32(_questLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime));
4879}
4880
4881void ObjectMgr::LoadScripts(ScriptsType type)
4882{
4883 uint32 oldMSTime = getMSTime();
4884
4885 ScriptMapMap* scripts = GetScriptsMapByType(type);
4886 if (!scripts)
4887 return;
4888
4889 std::string tableName = GetScriptsTableNameByType(type);
4890 if (tableName.empty())
4891 return;
4892
4893 if (sMapMgr->IsScriptScheduled()) // function cannot be called when scripts are in use.
4894 return;
4895
4896 TC_LOG_INFO("server.loading", "Loading %s...", tableName.c_str());
4897
4898 scripts->clear(); // need for reload support
4899
4900 bool isSpellScriptTable = (type == SCRIPTS_SPELL);
4901 // 0 1 2 3 4 5 6 7 8 9
4902 QueryResult result = WorldDatabase.PQuery("SELECT id, delay, command, datalong, datalong2, dataint, x, y, z, o%s FROM %s", isSpellScriptTable ? ", effIndex" : "", tableName.c_str());
4903
4904 if (!result)
4905 {
4906 TC_LOG_INFO("server.loading", ">> Loaded 0 script definitions. DB table `%s` is empty!", tableName.c_str());
4907 return;
4908 }
4909
4910 uint32 count = 0;
4911
4912 do
4913 {
4914 Field* fields = result->Fetch();
4915 ScriptInfo tmp;
4916 tmp.type = type;
4917 tmp.id = fields[0].GetUInt32();
4918 if (isSpellScriptTable)
4919 tmp.id |= fields[10].GetUInt8() << 24;
4920 tmp.delay = fields[1].GetUInt32();
4921 tmp.command = ScriptCommands(fields[2].GetUInt32());
4922 tmp.Raw.nData[0] = fields[3].GetUInt32();
4923 tmp.Raw.nData[1] = fields[4].GetUInt32();
4924 tmp.Raw.nData[2] = fields[5].GetInt32();
4925 tmp.Raw.fData[0] = fields[6].GetFloat();
4926 tmp.Raw.fData[1] = fields[7].GetFloat();
4927 tmp.Raw.fData[2] = fields[8].GetFloat();
4928 tmp.Raw.fData[3] = fields[9].GetFloat();
4929
4930 // generic command args check
4931 switch (tmp.command)
4932 {
4933 case SCRIPT_COMMAND_TALK:
4934 {
4935 if (tmp.Talk.ChatType > CHAT_TYPE_WHISPER && tmp.Talk.ChatType != CHAT_MSG_RAID_BOSS_WHISPER)
4936 {
4937 TC_LOG_ERROR("sql.sql", "Table `%s` has invalid talk type (datalong = %u) in SCRIPT_COMMAND_TALK for script id %u",
4938 tableName.c_str(), tmp.Talk.ChatType, tmp.id);
4939 continue;
4940 }
4941 if (!sObjectMgr->GetBroadcastText(uint32(tmp.Talk.TextID)))
4942 {
4943 TC_LOG_ERROR("sql.sql", "Table `%s` has invalid talk text id (dataint = %i) in SCRIPT_COMMAND_TALK for script id %u",
4944 tableName.c_str(), tmp.Talk.TextID, tmp.id);
4945 continue;
4946 }
4947
4948 break;
4949 }
4950
4951 case SCRIPT_COMMAND_EMOTE:
4952 {
4953 if (!sEmotesStore.LookupEntry(tmp.Emote.EmoteID))
4954 {
4955 TC_LOG_ERROR("sql.sql", "Table `%s` has invalid emote id (datalong = %u) in SCRIPT_COMMAND_EMOTE for script id %u",
4956 tableName.c_str(), tmp.Emote.EmoteID, tmp.id);
4957 continue;
4958 }
4959 break;
4960 }
4961
4962 case SCRIPT_COMMAND_TELEPORT_TO:
4963 {
4964 if (!sMapStore.LookupEntry(tmp.TeleportTo.MapID))
4965 {
4966 TC_LOG_ERROR("sql.sql", "Table `%s` has invalid map (Id: %u) in SCRIPT_COMMAND_TELEPORT_TO for script id %u",
4967 tableName.c_str(), tmp.TeleportTo.MapID, tmp.id);
4968 continue;
4969 }
4970
4971 if (!Trinity::IsValidMapCoord(tmp.TeleportTo.DestX, tmp.TeleportTo.DestY, tmp.TeleportTo.DestZ, tmp.TeleportTo.Orientation))
4972 {
4973 TC_LOG_ERROR("sql.sql", "Table `%s` has invalid coordinates (X: %f Y: %f Z: %f O: %f) in SCRIPT_COMMAND_TELEPORT_TO for script id %u",
4974 tableName.c_str(), tmp.TeleportTo.DestX, tmp.TeleportTo.DestY, tmp.TeleportTo.DestZ, tmp.TeleportTo.Orientation, tmp.id);
4975 continue;
4976 }
4977 break;
4978 }
4979
4980 case SCRIPT_COMMAND_QUEST_EXPLORED:
4981 {
4982 Quest const* quest = GetQuestTemplate(tmp.QuestExplored.QuestID);
4983 if (!quest)
4984 {
4985 TC_LOG_ERROR("sql.sql", "Table `%s` has invalid quest (ID: %u) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u",
4986 tableName.c_str(), tmp.QuestExplored.QuestID, tmp.id);
4987 continue;
4988 }
4989
4990 if (!quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT))
4991 {
4992 TC_LOG_ERROR("sql.sql", "Table `%s` has quest (ID: %u) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u, 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.",
4993 tableName.c_str(), tmp.QuestExplored.QuestID, tmp.id);
4994
4995 // this will prevent quest completing without objective
4996 const_cast<Quest*>(quest)->SetSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT);
4997
4998 // continue; - quest objective requirement set and command can be allowed
4999 }
5000
5001 if (float(tmp.QuestExplored.Distance) > DEFAULT_VISIBILITY_DISTANCE)
5002 {
5003 TC_LOG_ERROR("sql.sql", "Table `%s` has too large distance (%u) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u",
5004 tableName.c_str(), tmp.QuestExplored.Distance, tmp.id);
5005 continue;
5006 }
5007
5008 if (tmp.QuestExplored.Distance && float(tmp.QuestExplored.Distance) > DEFAULT_VISIBILITY_DISTANCE)
5009 {
5010 TC_LOG_ERROR("sql.sql", "Table `%s` has too large distance (%u) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u, max distance is %f or 0 for disable distance check",
5011 tableName.c_str(), tmp.QuestExplored.Distance, tmp.id, DEFAULT_VISIBILITY_DISTANCE);
5012 continue;
5013 }
5014
5015 if (tmp.QuestExplored.Distance && float(tmp.QuestExplored.Distance) < INTERACTION_DISTANCE)
5016 {
5017 TC_LOG_ERROR("sql.sql", "Table `%s` has too small distance (%u) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u, min distance is %f or 0 for disable distance check",
5018 tableName.c_str(), tmp.QuestExplored.Distance, tmp.id, INTERACTION_DISTANCE);
5019 continue;
5020 }
5021
5022 break;
5023 }
5024
5025 case SCRIPT_COMMAND_KILL_CREDIT:
5026 {
5027 if (!GetCreatureTemplate(tmp.KillCredit.CreatureEntry))
5028 {
5029 TC_LOG_ERROR("sql.sql", "Table `%s` has invalid creature (Entry: %u) in SCRIPT_COMMAND_KILL_CREDIT for script id %u",
5030 tableName.c_str(), tmp.KillCredit.CreatureEntry, tmp.id);
5031 continue;
5032 }
5033 break;
5034 }
5035
5036 case SCRIPT_COMMAND_RESPAWN_GAMEOBJECT:
5037 {
5038 GameObjectData const* data = GetGameObjectData(tmp.RespawnGameobject.GOGuid);
5039 if (!data)
5040 {
5041 TC_LOG_ERROR("sql.sql", "Table `%s` has invalid gameobject (GUID: %u) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id %u",
5042 tableName.c_str(), tmp.RespawnGameobject.GOGuid, tmp.id);
5043 continue;
5044 }
5045
5046 GameObjectTemplate const* info = GetGameObjectTemplate(data->id);
5047 if (!info)
5048 {
5049 TC_LOG_ERROR("sql.sql", "Table `%s` has gameobject with invalid entry (GUID: %u Entry: %u) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id %u",
5050 tableName.c_str(), tmp.RespawnGameobject.GOGuid, data->id, tmp.id);
5051 continue;
5052 }
5053
5054 if (info->type == GAMEOBJECT_TYPE_FISHINGNODE ||
5055 info->type == GAMEOBJECT_TYPE_FISHINGHOLE ||
5056 info->type == GAMEOBJECT_TYPE_DOOR ||
5057 info->type == GAMEOBJECT_TYPE_BUTTON ||
5058 info->type == GAMEOBJECT_TYPE_TRAP)
5059 {
5060 TC_LOG_ERROR("sql.sql", "Table `%s` has gameobject type (%u) unsupported by command SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id %u",
5061 tableName.c_str(), info->entry, tmp.id);
5062 continue;
5063 }
5064 break;
5065 }
5066
5067 case SCRIPT_COMMAND_TEMP_SUMMON_CREATURE:
5068 {
5069 if (!Trinity::IsValidMapCoord(tmp.TempSummonCreature.PosX, tmp.TempSummonCreature.PosY, tmp.TempSummonCreature.PosZ, tmp.TempSummonCreature.Orientation))
5070 {
5071 TC_LOG_ERROR("sql.sql", "Table `%s` has invalid coordinates (X: %f Y: %f Z: %f O: %f) in SCRIPT_COMMAND_TEMP_SUMMON_CREATURE for script id %u",
5072 tableName.c_str(), tmp.TempSummonCreature.PosX, tmp.TempSummonCreature.PosY, tmp.TempSummonCreature.PosZ, tmp.TempSummonCreature.Orientation, tmp.id);
5073 continue;
5074 }
5075
5076 if (!GetCreatureTemplate(tmp.TempSummonCreature.CreatureEntry))
5077 {
5078 TC_LOG_ERROR("sql.sql", "Table `%s` has invalid creature (Entry: %u) in SCRIPT_COMMAND_TEMP_SUMMON_CREATURE for script id %u",
5079 tableName.c_str(), tmp.TempSummonCreature.CreatureEntry, tmp.id);
5080 continue;
5081 }
5082 break;
5083 }
5084
5085 case SCRIPT_COMMAND_OPEN_DOOR:
5086 case SCRIPT_COMMAND_CLOSE_DOOR:
5087 {
5088 GameObjectData const* data = GetGameObjectData(tmp.ToggleDoor.GOGuid);
5089 if (!data)
5090 {
5091 TC_LOG_ERROR("sql.sql", "Table `%s` has invalid gameobject (GUID: %u) in %s for script id %u",
5092 tableName.c_str(), tmp.ToggleDoor.GOGuid, GetScriptCommandName(tmp.command).c_str(), tmp.id);
5093 continue;
5094 }
5095
5096 GameObjectTemplate const* info = GetGameObjectTemplate(data->id);
5097 if (!info)
5098 {
5099 TC_LOG_ERROR("sql.sql", "Table `%s` has gameobject with invalid entry (GUID: %u Entry: %u) in %s for script id %u",
5100 tableName.c_str(), tmp.ToggleDoor.GOGuid, data->id, GetScriptCommandName(tmp.command).c_str(), tmp.id);
5101 continue;
5102 }
5103
5104 if (info->type != GAMEOBJECT_TYPE_DOOR)
5105 {
5106 TC_LOG_ERROR("sql.sql", "Table `%s` has gameobject type (%u) unsupported by command %s for script id %u",
5107 tableName.c_str(), info->entry, GetScriptCommandName(tmp.command).c_str(), tmp.id);
5108 continue;
5109 }
5110
5111 break;
5112 }
5113
5114 case SCRIPT_COMMAND_REMOVE_AURA:
5115 {
5116 if (!sSpellMgr->GetSpellInfo(tmp.RemoveAura.SpellID))
5117 {
5118 TC_LOG_ERROR("sql.sql", "Table `%s` using non-existent spell (id: %u) in SCRIPT_COMMAND_REMOVE_AURA for script id %u",
5119 tableName.c_str(), tmp.RemoveAura.SpellID, tmp.id);
5120 continue;
5121 }
5122 if (tmp.RemoveAura.Flags & ~0x1) // 1 bits (0, 1)
5123 {
5124 TC_LOG_ERROR("sql.sql", "Table `%s` using unknown flags in datalong2 (%u) in SCRIPT_COMMAND_REMOVE_AURA for script id %u",
5125 tableName.c_str(), tmp.RemoveAura.Flags, tmp.id);
5126 continue;
5127 }
5128 break;
5129 }
5130
5131 case SCRIPT_COMMAND_CAST_SPELL:
5132 {
5133 if (!sSpellMgr->GetSpellInfo(tmp.CastSpell.SpellID))
5134 {
5135 TC_LOG_ERROR("sql.sql", "Table `%s` using non-existent spell (id: %u) in SCRIPT_COMMAND_CAST_SPELL for script id %u",
5136 tableName.c_str(), tmp.CastSpell.SpellID, tmp.id);
5137 continue;
5138 }
5139 if (tmp.CastSpell.Flags > 4) // targeting type
5140 {
5141 TC_LOG_ERROR("sql.sql", "Table `%s` using unknown target in datalong2 (%u) in SCRIPT_COMMAND_CAST_SPELL for script id %u",
5142 tableName.c_str(), tmp.CastSpell.Flags, tmp.id);
5143 continue;
5144 }
5145 if (tmp.CastSpell.Flags != 4 && tmp.CastSpell.CreatureEntry & ~0x1) // 1 bit (0, 1)
5146 {
5147 TC_LOG_ERROR("sql.sql", "Table `%s` using unknown flags in dataint (%u) in SCRIPT_COMMAND_CAST_SPELL for script id %u",
5148 tableName.c_str(), tmp.CastSpell.CreatureEntry, tmp.id);
5149 continue;
5150 }
5151 else if (tmp.CastSpell.Flags == 4 && !GetCreatureTemplate(tmp.CastSpell.CreatureEntry))
5152 {
5153 TC_LOG_ERROR("sql.sql", "Table `%s` using invalid creature entry in dataint (%u) in SCRIPT_COMMAND_CAST_SPELL for script id %u",
5154 tableName.c_str(), tmp.CastSpell.CreatureEntry, tmp.id);
5155 continue;
5156 }
5157 break;
5158 }
5159
5160 case SCRIPT_COMMAND_CREATE_ITEM:
5161 {
5162 if (!GetItemTemplate(tmp.CreateItem.ItemEntry))
5163 {
5164 TC_LOG_ERROR("sql.sql", "Table `%s` has nonexistent item (entry: %u) in SCRIPT_COMMAND_CREATE_ITEM for script id %u",
5165 tableName.c_str(), tmp.CreateItem.ItemEntry, tmp.id);
5166 continue;
5167 }
5168 if (!tmp.CreateItem.Amount)
5169 {
5170 TC_LOG_ERROR("sql.sql", "Table `%s` SCRIPT_COMMAND_CREATE_ITEM but amount is %u for script id %u",
5171 tableName.c_str(), tmp.CreateItem.Amount, tmp.id);
5172 continue;
5173 }
5174 break;
5175 }
5176 default:
5177 break;
5178 }
5179
5180 if (scripts->find(tmp.id) == scripts->end())
5181 {
5182 ScriptMap emptyMap;
5183 (*scripts)[tmp.id] = emptyMap;
5184 }
5185 (*scripts)[tmp.id].insert(std::pair<uint32, ScriptInfo>(tmp.delay, tmp));
5186
5187 ++count;
5188 }
5189 while (result->NextRow());
5190
5191 TC_LOG_INFO("server.loading", ">> Loaded %u script definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
5192}
5193
5194void ObjectMgr::LoadSpellScripts()
5195{
5196 LoadScripts(SCRIPTS_SPELL);
5197
5198 // check ids
5199 for (ScriptMapMap::const_iterator itr = sSpellScripts.begin(); itr != sSpellScripts.end(); ++itr)
5200 {
5201 uint32 spellId = uint32(itr->first) & 0x00FFFFFF;
5202 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
5203
5204 if (!spellInfo)
5205 {
5206 TC_LOG_ERROR("sql.sql", "Table `spell_scripts` has not existing spell (Id: %u) as script id", spellId);
5207 continue;
5208 }
5209
5210 uint8 i = (uint8)((uint32(itr->first) >> 24) & 0x000000FF);
5211 //check for correct spellEffect
5212 if (!spellInfo->Effects[i].Effect || (spellInfo->Effects[i].Effect != SPELL_EFFECT_SCRIPT_EFFECT && spellInfo->Effects[i].Effect != SPELL_EFFECT_DUMMY))
5213 TC_LOG_ERROR("sql.sql", "Table `spell_scripts` - spell %u effect %u is not SPELL_EFFECT_SCRIPT_EFFECT or SPELL_EFFECT_DUMMY", spellId, i);
5214 }
5215}
5216
5217void ObjectMgr::LoadEventScripts()
5218{
5219 LoadScripts(SCRIPTS_EVENT);
5220
5221 std::set<uint32> evt_scripts;
5222 // Load all possible script entries from gameobjects
5223 for (auto const& gameObjectTemplatePair : _gameObjectTemplateStore)
5224 if (uint32 eventId = gameObjectTemplatePair.second.GetEventScriptId())
5225 evt_scripts.insert(eventId);
5226
5227 // Load all possible script entries from spells
5228 for (uint32 i = 1; i < sSpellMgr->GetSpellInfoStoreSize(); ++i)
5229 if (SpellInfo const* spell = sSpellMgr->GetSpellInfo(i))
5230 for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
5231 if (spell->Effects[j].Effect == SPELL_EFFECT_SEND_EVENT)
5232 if (spell->Effects[j].MiscValue)
5233 evt_scripts.insert(spell->Effects[j].MiscValue);
5234
5235 for (size_t path_idx = 0; path_idx < sTaxiPathNodesByPath.size(); ++path_idx)
5236 {
5237 for (size_t node_idx = 0; node_idx < sTaxiPathNodesByPath[path_idx].size(); ++node_idx)
5238 {
5239 TaxiPathNodeEntry const* node = sTaxiPathNodesByPath[path_idx][node_idx];
5240
5241 if (node->ArrivalEventID)
5242 evt_scripts.insert(node->ArrivalEventID);
5243
5244 if (node->DepartureEventID)
5245 evt_scripts.insert(node->DepartureEventID);
5246 }
5247 }
5248
5249 // Then check if all scripts are in above list of possible script entries
5250 for (ScriptMapMap::const_iterator itr = sEventScripts.begin(); itr != sEventScripts.end(); ++itr)
5251 {
5252 std::set<uint32>::const_iterator itr2 = evt_scripts.find(itr->first);
5253 if (itr2 == evt_scripts.end())
5254 TC_LOG_ERROR("sql.sql", "Table `event_scripts` has script (Id: %u) not referring to any gameobject_template type 10 data2 field, type 3 data6 field, type 13 data 2 field or any spell effect %u",
5255 itr->first, SPELL_EFFECT_SEND_EVENT);
5256 }
5257}
5258
5259//Load WP Scripts
5260void ObjectMgr::LoadWaypointScripts()
5261{
5262 LoadScripts(SCRIPTS_WAYPOINT);
5263
5264 std::set<uint32> actionSet;
5265
5266 for (ScriptMapMap::const_iterator itr = sWaypointScripts.begin(); itr != sWaypointScripts.end(); ++itr)
5267 actionSet.insert(itr->first);
5268
5269 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_WAYPOINT_DATA_ACTION);
5270 PreparedQueryResult result = WorldDatabase.Query(stmt);
5271
5272 if (result)
5273 {
5274 do
5275 {
5276 Field* fields = result->Fetch();
5277 uint32 action = fields[0].GetUInt32();
5278
5279 actionSet.erase(action);
5280 }
5281 while (result->NextRow());
5282 }
5283
5284 for (std::set<uint32>::iterator itr = actionSet.begin(); itr != actionSet.end(); ++itr)
5285 TC_LOG_ERROR("sql.sql", "There is no waypoint which links to the waypoint script %u", *itr);
5286}
5287
5288void ObjectMgr::LoadSpellScriptNames()
5289{
5290 uint32 oldMSTime = getMSTime();
5291
5292 _spellScriptsStore.clear(); // need for reload case
5293
5294 QueryResult result = WorldDatabase.Query("SELECT spell_id, ScriptName FROM spell_script_names");
5295
5296 if (!result)
5297 {
5298 TC_LOG_INFO("server.loading", ">> Loaded 0 spell script names. DB table `spell_script_names` is empty!");
5299 return;
5300 }
5301
5302 uint32 count = 0;
5303
5304 do
5305 {
5306
5307 Field* fields = result->Fetch();
5308
5309 int32 spellId = fields[0].GetInt32();
5310 std::string const scriptName = fields[1].GetString();
5311
5312 bool allRanks = false;
5313 if (spellId < 0)
5314 {
5315 allRanks = true;
5316 spellId = -spellId;
5317 }
5318
5319 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
5320 if (!spellInfo)
5321 {
5322 TC_LOG_ERROR("sql.sql", "Scriptname: `%s` spell (Id: %d) does not exist.", scriptName.c_str(), spellId);
5323 continue;
5324 }
5325
5326 if (allRanks)
5327 {
5328 if (!spellInfo->IsRanked())
5329 TC_LOG_ERROR("sql.sql", "Scriptname: `%s` spell (Id: %d) has no ranks of spell.", scriptName.c_str(), fields[0].GetInt32());
5330
5331 if (spellInfo->GetFirstRankSpell()->Id != uint32(spellId))
5332 {
5333 TC_LOG_ERROR("sql.sql", "Scriptname: `%s` spell (Id: %d) is not first rank of spell.", scriptName.c_str(), fields[0].GetInt32());
5334 continue;
5335 }
5336
5337 while (spellInfo)
5338 {
5339 _spellScriptsStore.insert(SpellScriptsContainer::value_type(spellInfo->Id, std::make_pair(GetScriptId(scriptName), true)));
5340 spellInfo = spellInfo->GetNextRankSpell();
5341 }
5342 }
5343 else
5344 {
5345 if (spellInfo->IsRanked())
5346 TC_LOG_ERROR("sql.sql", "Scriptname: `%s` spell (Id: %d) is ranked spell. Perhaps not all ranks are assigned to this script.", scriptName.c_str(), spellId);
5347
5348 _spellScriptsStore.insert(SpellScriptsContainer::value_type(spellInfo->Id, std::make_pair(GetScriptId(scriptName), true)));
5349 }
5350
5351 ++count;
5352 }
5353 while (result->NextRow());
5354
5355 TC_LOG_INFO("server.loading", ">> Loaded %u spell script names in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
5356}
5357
5358void ObjectMgr::ValidateSpellScripts()
5359{
5360 uint32 oldMSTime = getMSTime();
5361
5362 if (_spellScriptsStore.empty())
5363 {
5364 TC_LOG_INFO("server.loading", ">> Validated 0 scripts.");
5365 return;
5366 }
5367
5368 uint32 count = 0;
5369
5370 for (auto spell : _spellScriptsStore)
5371 {
5372 SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(spell.first);
5373
5374 auto const bounds = sObjectMgr->GetSpellScriptsBounds(spell.first);
5375
5376 for (auto itr = bounds.first; itr != bounds.second; ++itr)
5377 {
5378 if (SpellScriptLoader* spellScriptLoader = sScriptMgr->GetSpellScriptLoader(itr->second.first))
5379 {
5380 ++count;
5381
5382 std::unique_ptr<SpellScript> spellScript(spellScriptLoader->GetSpellScript());
5383 std::unique_ptr<AuraScript> auraScript(spellScriptLoader->GetAuraScript());
5384
5385 if (!spellScript && !auraScript)
5386 {
5387 TC_LOG_ERROR("scripts", "Functions GetSpellScript() and GetAuraScript() of script `%s` do not return objects - script skipped", GetScriptName(itr->second.first).c_str());
5388
5389 itr->second.second = false;
5390 continue;
5391 }
5392
5393 if (spellScript)
5394 {
5395 spellScript->_Init(&spellScriptLoader->GetName(), spellEntry->Id);
5396 spellScript->_Register();
5397
5398 if (!spellScript->_Validate(spellEntry))
5399 {
5400 itr->second.second = false;
5401 continue;
5402 }
5403 }
5404
5405 if (auraScript)
5406 {
5407 auraScript->_Init(&spellScriptLoader->GetName(), spellEntry->Id);
5408 auraScript->_Register();
5409
5410 if (!auraScript->_Validate(spellEntry))
5411 {
5412 itr->second.second = false;
5413 continue;
5414 }
5415 }
5416
5417 // Enable the script when all checks passed
5418 itr->second.second = true;
5419 }
5420 else
5421 itr->second.second = false;
5422 }
5423 }
5424
5425 TC_LOG_INFO("server.loading", ">> Validated %u scripts in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
5426}
5427
5428void ObjectMgr::LoadPageTexts()
5429{
5430 uint32 oldMSTime = getMSTime();
5431
5432 // 0 1 2
5433 QueryResult result = WorldDatabase.Query("SELECT ID, Text, NextPageID FROM page_text");
5434
5435 if (!result)
5436 {
5437 TC_LOG_INFO("server.loading", ">> Loaded 0 page texts. DB table `page_text` is empty!");
5438 return;
5439 }
5440
5441 uint32 count = 0;
5442 do
5443 {
5444 Field* fields = result->Fetch();
5445
5446 PageText& pageText = _pageTextStore[fields[0].GetUInt32()];
5447
5448 pageText.Text = fields[1].GetString();
5449 pageText.NextPageID = fields[2].GetUInt32();
5450
5451 ++count;
5452 }
5453 while (result->NextRow());
5454
5455 for (PageTextContainer::const_iterator itr = _pageTextStore.begin(); itr != _pageTextStore.end(); ++itr)
5456 {
5457 if (itr->second.NextPageID)
5458 {
5459 PageTextContainer::const_iterator itr2 = _pageTextStore.find(itr->second.NextPageID);
5460 if (itr2 == _pageTextStore.end())
5461 TC_LOG_ERROR("sql.sql", "Page text (ID: %u) has non-existing `NextPageID` (%u)", itr->first, itr->second.NextPageID);
5462
5463 }
5464 }
5465
5466 TC_LOG_INFO("server.loading", ">> Loaded %u page texts in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
5467}
5468
5469PageText const* ObjectMgr::GetPageText(uint32 pageEntry)
5470{
5471 PageTextContainer::const_iterator itr = _pageTextStore.find(pageEntry);
5472 if (itr != _pageTextStore.end())
5473 return &(itr->second);
5474
5475 return nullptr;
5476}
5477
5478void ObjectMgr::LoadPageTextLocales()
5479{
5480 uint32 oldMSTime = getMSTime();
5481
5482 _pageTextLocaleStore.clear(); // need for reload case
5483
5484 // 0 1 2
5485 QueryResult result = WorldDatabase.Query("SELECT ID, locale, Text FROM page_text_locale");
5486
5487 if (!result)
5488 return;
5489
5490 do
5491 {
5492 Field* fields = result->Fetch();
5493
5494 uint32 id = fields[0].GetUInt32();
5495 std::string localeName = fields[1].GetString();
5496 std::string text = fields[2].GetString();
5497
5498 PageTextLocale& data = _pageTextLocaleStore[id];
5499 LocaleConstant locale = GetLocaleByName(localeName);
5500
5501 AddLocaleString(text, locale, data.Text);
5502 } while (result->NextRow());
5503
5504 TC_LOG_INFO("server.loading", ">> Loaded %u PageText locale strings in %u ms", uint32(_pageTextLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime));
5505}
5506
5507void ObjectMgr::LoadInstanceTemplate()
5508{
5509 uint32 oldMSTime = getMSTime();
5510
5511 // 0 1 2 4
5512 QueryResult result = WorldDatabase.Query("SELECT map, parent, script, allowMount FROM instance_template");
5513
5514 if (!result)
5515 {
5516 TC_LOG_INFO("server.loading", ">> Loaded 0 instance templates. DB table `page_text` is empty!");
5517 return;
5518 }
5519
5520 uint32 count = 0;
5521 do
5522 {
5523 Field* fields = result->Fetch();
5524
5525 uint16 mapID = fields[0].GetUInt16();
5526
5527 if (!MapManager::IsValidMAP(mapID, true))
5528 {
5529 TC_LOG_ERROR("sql.sql", "ObjectMgr::LoadInstanceTemplate: bad mapid %d for template!", mapID);
5530 continue;
5531 }
5532
5533 InstanceTemplate instanceTemplate;
5534
5535 instanceTemplate.AllowMount = fields[3].GetBool();
5536 instanceTemplate.Parent = uint32(fields[1].GetUInt16());
5537 instanceTemplate.ScriptId = sObjectMgr->GetScriptId(fields[2].GetString());
5538
5539 _instanceTemplateStore[mapID] = instanceTemplate;
5540
5541 ++count;
5542 }
5543 while (result->NextRow());
5544
5545 TC_LOG_INFO("server.loading", ">> Loaded %u instance templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
5546}
5547
5548InstanceTemplate const* ObjectMgr::GetInstanceTemplate(uint32 mapID) const
5549{
5550 InstanceTemplateContainer::const_iterator itr = _instanceTemplateStore.find(uint16(mapID));
5551 if (itr != _instanceTemplateStore.end())
5552 return &(itr->second);
5553
5554 return nullptr;
5555}
5556
5557void ObjectMgr::LoadInstanceEncounters()
5558{
5559 uint32 oldMSTime = getMSTime();
5560
5561 // 0 1 2 3
5562 QueryResult result = WorldDatabase.Query("SELECT entry, creditType, creditEntry, lastEncounterDungeon FROM instance_encounters");
5563 if (!result)
5564 {
5565 TC_LOG_INFO("server.loading", ">> Loaded 0 instance encounters, table is empty!");
5566 return;
5567 }
5568
5569 uint32 count = 0;
5570 std::map<uint32, DungeonEncounterEntry const*> dungeonLastBosses;
5571 do
5572 {
5573 Field* fields = result->Fetch();
5574 uint32 entry = fields[0].GetUInt32();
5575 uint8 creditType = fields[1].GetUInt8();
5576 uint32 creditEntry = fields[2].GetUInt32();
5577 uint32 lastEncounterDungeon = fields[3].GetUInt16();
5578 DungeonEncounterEntry const* dungeonEncounter = sDungeonEncounterStore.LookupEntry(entry);
5579 if (!dungeonEncounter)
5580 {
5581 TC_LOG_ERROR("sql.sql", "Table `instance_encounters` has an invalid encounter id %u, skipped!", entry);
5582 continue;
5583 }
5584
5585 if (lastEncounterDungeon && !sLFGMgr->GetLFGDungeonEntry(lastEncounterDungeon))
5586 {
5587 TC_LOG_ERROR("sql.sql", "Table `instance_encounters` has an encounter %u (%s) marked as final for invalid dungeon id %u, skipped!", entry, dungeonEncounter->encounterName[0], lastEncounterDungeon);
5588 continue;
5589 }
5590
5591 auto itr = dungeonLastBosses.find(lastEncounterDungeon);
5592 if (lastEncounterDungeon)
5593 {
5594 if (itr != dungeonLastBosses.end())
5595 {
5596 TC_LOG_ERROR("sql.sql", "Table `instance_encounters` specified encounter %u (%s) as last encounter but %u (%s) is already marked as one, skipped!", entry, dungeonEncounter->encounterName[0], itr->second->id, itr->second->encounterName[0]);
5597 continue;
5598 }
5599
5600 dungeonLastBosses[lastEncounterDungeon] = dungeonEncounter;
5601 }
5602
5603 switch (creditType)
5604 {
5605 case ENCOUNTER_CREDIT_KILL_CREATURE:
5606 {
5607 CreatureTemplate const* creatureInfo = GetCreatureTemplate(creditEntry);
5608 if (!creatureInfo)
5609 {
5610 TC_LOG_ERROR("sql.sql", "Table `instance_encounters` has an invalid creature (entry %u) linked to the encounter %u (%s), skipped!", creditEntry, entry, dungeonEncounter->encounterName[0]);
5611 continue;
5612 }
5613 const_cast<CreatureTemplate*>(creatureInfo)->flags_extra |= CREATURE_FLAG_EXTRA_DUNGEON_BOSS;
5614 for (uint8 diff = 1; diff < MAX_DIFFICULTY; ++diff)
5615 {
5616 if (uint32 diffEntry = creatureInfo->DifficultyEntry[diff - 1])
5617 {
5618 if (CreatureTemplate const* diffInfo = GetCreatureTemplate(diffEntry))
5619 const_cast<CreatureTemplate*>(diffInfo)->flags_extra |= CREATURE_FLAG_EXTRA_DUNGEON_BOSS;
5620 }
5621 }
5622 break;
5623 }
5624 case ENCOUNTER_CREDIT_CAST_SPELL:
5625 if (!sSpellMgr->GetSpellInfo(creditEntry))
5626 {
5627 TC_LOG_ERROR("sql.sql", "Table `instance_encounters` has an invalid spell (entry %u) linked to the encounter %u (%s), skipped!", creditEntry, entry, dungeonEncounter->encounterName[0]);
5628 continue;
5629 }
5630 break;
5631 default:
5632 TC_LOG_ERROR("sql.sql", "Table `instance_encounters` has an invalid credit type (%u) for encounter %u (%s), skipped!", creditType, entry, dungeonEncounter->encounterName[0]);
5633 continue;
5634 }
5635
5636 DungeonEncounterList& encounters = _dungeonEncounterStore[MAKE_PAIR32(dungeonEncounter->mapId, dungeonEncounter->difficulty)];
5637 encounters.emplace_back(Trinity::make_unique<DungeonEncounter>(dungeonEncounter, EncounterCreditType(creditType), creditEntry, lastEncounterDungeon));
5638 ++count;
5639 } while (result->NextRow());
5640
5641 TC_LOG_INFO("server.loading", ">> Loaded %u instance encounters in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
5642}
5643
5644GossipText const* ObjectMgr::GetGossipText(uint32 Text_ID) const
5645{
5646 GossipTextContainer::const_iterator itr = _gossipTextStore.find(Text_ID);
5647 if (itr != _gossipTextStore.end())
5648 return &itr->second;
5649 return nullptr;
5650}
5651
5652void ObjectMgr::LoadGossipText()
5653{
5654 uint32 oldMSTime = getMSTime();
5655
5656 QueryResult result = WorldDatabase.Query("SELECT ID, "
5657 "text0_0, text0_1, BroadcastTextID0, lang0, Probability0, em0_0, em0_1, em0_2, em0_3, em0_4, em0_5, "
5658 "text1_0, text1_1, BroadcastTextID1, lang1, Probability1, em1_0, em1_1, em1_2, em1_3, em1_4, em1_5, "
5659 "text2_0, text2_1, BroadcastTextID2, lang2, Probability2, em2_0, em2_1, em2_2, em2_3, em2_4, em2_5, "
5660 "text3_0, text3_1, BroadcastTextID3, lang3, Probability3, em3_0, em3_1, em3_2, em3_3, em3_4, em3_5, "
5661 "text4_0, text4_1, BroadcastTextID4, lang4, Probability4, em4_0, em4_1, em4_2, em4_3, em4_4, em4_5, "
5662 "text5_0, text5_1, BroadcastTextID5, lang5, Probability5, em5_0, em5_1, em5_2, em5_3, em5_4, em5_5, "
5663 "text6_0, text6_1, BroadcastTextID6, lang6, Probability6, em6_0, em6_1, em6_2, em6_3, em6_4, em6_5, "
5664 "text7_0, text7_1, BroadcastTextID7, lang7, Probability7, em7_0, em7_1, em7_2, em7_3, em7_4, em7_5 "
5665 "FROM npc_text");
5666
5667
5668 if (!result)
5669 {
5670 TC_LOG_INFO("server.loading", ">> Loaded 0 npc texts, table is empty!");
5671 return;
5672 }
5673
5674 _gossipTextStore.rehash(result->GetRowCount());
5675
5676 uint32 count = 0;
5677 uint8 cic;
5678
5679 do
5680 {
5681 ++count;
5682 cic = 0;
5683
5684 Field* fields = result->Fetch();
5685
5686 uint32 id = fields[cic++].GetUInt32();
5687 if (!id)
5688 {
5689 TC_LOG_ERROR("sql.sql", "Table `npc_text` has record with reserved id 0, ignore.");
5690 continue;
5691 }
5692
5693 GossipText& gText = _gossipTextStore[id];
5694
5695 for (uint8 i = 0; i < MAX_GOSSIP_TEXT_OPTIONS; ++i)
5696 {
5697 gText.Options[i].Text_0 = fields[cic++].GetString();
5698 gText.Options[i].Text_1 = fields[cic++].GetString();
5699 gText.Options[i].BroadcastTextID = fields[cic++].GetUInt32();
5700 gText.Options[i].Language = fields[cic++].GetUInt8();
5701 gText.Options[i].Probability = fields[cic++].GetFloat();
5702
5703 for (uint8 j = 0; j < MAX_GOSSIP_TEXT_EMOTES; ++j)
5704 {
5705 gText.Options[i].Emotes[j]._Delay = fields[cic++].GetUInt16();
5706 gText.Options[i].Emotes[j]._Emote = fields[cic++].GetUInt16();
5707 }
5708 }
5709
5710 for (uint8 i = 0; i < MAX_GOSSIP_TEXT_OPTIONS; i++)
5711 {
5712 if (gText.Options[i].BroadcastTextID)
5713 {
5714 if (!sObjectMgr->GetBroadcastText(gText.Options[i].BroadcastTextID))
5715 {
5716 TC_LOG_ERROR("sql.sql", "GossipText (Id: %u) in table `npc_text` has non-existing or incompatible BroadcastTextID%u %u.", id, i, gText.Options[i].BroadcastTextID);
5717 gText.Options[i].BroadcastTextID = 0;
5718 }
5719 }
5720 }
5721
5722 }
5723 while (result->NextRow());
5724
5725 TC_LOG_INFO("server.loading", ">> Loaded %u npc texts in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
5726}
5727
5728void ObjectMgr::LoadNpcTextLocales()
5729{
5730 uint32 oldMSTime = getMSTime();
5731
5732 _npcTextLocaleStore.clear(); // need for reload case
5733
5734 QueryResult result = WorldDatabase.Query("SELECT ID, Locale, "
5735 // 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
5736 "Text0_0, Text0_1, Text1_0, Text1_1, Text2_0, Text2_1, Text3_0, Text3_1, Text4_0, Text4_1, Text5_0, Text5_1, Text6_0, Text6_1, Text7_0, Text7_1 "
5737 "FROM npc_text_locale");
5738
5739 if (!result)
5740 return;
5741
5742 do
5743 {
5744 Field* fields = result->Fetch();
5745
5746 uint32 id = fields[0].GetUInt32();
5747 std::string localeName = fields[1].GetString();
5748
5749 NpcTextLocale& data = _npcTextLocaleStore[id];
5750 LocaleConstant locale = GetLocaleByName(localeName);
5751 if (locale == LOCALE_enUS)
5752 continue;
5753
5754 for (uint8 i = 0; i < MAX_GOSSIP_TEXT_OPTIONS; ++i)
5755 {
5756 AddLocaleString(fields[2 + i * 2].GetString(), locale, data.Text_0[i]);
5757 AddLocaleString(fields[3 + i * 2].GetString(), locale, data.Text_1[i]);
5758 }
5759 } while (result->NextRow());
5760
5761 TC_LOG_INFO("server.loading", ">> Loaded %u NpcText locale strings in %u ms", uint32(_npcTextLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime));
5762}
5763
5764//not very fast function but it is called only once a day, or on starting-up
5765void ObjectMgr::ReturnOrDeleteOldMails(bool serverUp)
5766{
5767 uint32 oldMSTime = getMSTime();
5768
5769 time_t curTime = GameTime::GetGameTime();
5770 tm lt;
5771 localtime_r(&curTime, <);
5772 uint64 basetime(curTime);
5773 TC_LOG_INFO("misc", "Returning mails current time: hour: %d, minute: %d, second: %d ", lt.tm_hour, lt.tm_min, lt.tm_sec);
5774
5775 // Delete all old mails without item and without body immediately, if starting server
5776 if (!serverUp)
5777 {
5778 PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_EMPTY_EXPIRED_MAIL);
5779 stmt->setUInt64(0, basetime);
5780 CharacterDatabase.Execute(stmt);
5781 }
5782 PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_EXPIRED_MAIL);
5783 stmt->setUInt64(0, basetime);
5784 PreparedQueryResult result = CharacterDatabase.Query(stmt);
5785 if (!result)
5786 {
5787 TC_LOG_INFO("server.loading", ">> No expired mails found.");
5788 return; // any mails need to be returned or deleted
5789 }
5790
5791 std::map<uint32 /*messageId*/, MailItemInfoVec> itemsCache;
5792 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_EXPIRED_MAIL_ITEMS);
5793 stmt->setUInt32(0, (uint32)basetime);
5794 if (PreparedQueryResult items = CharacterDatabase.Query(stmt))
5795 {
5796 MailItemInfo item;
5797 do
5798 {
5799 Field* fields = items->Fetch();
5800 item.item_guid = fields[0].GetUInt32();
5801 item.item_template = fields[1].GetUInt32();
5802 uint32 mailId = fields[2].GetUInt32();
5803 itemsCache[mailId].push_back(item);
5804 } while (items->NextRow());
5805 }
5806
5807 uint32 deletedCount = 0;
5808 uint32 returnedCount = 0;
5809 do
5810 {
5811 Field* fields = result->Fetch();
5812 Mail* m = new Mail;
5813 m->messageID = fields[0].GetUInt32();
5814 m->messageType = fields[1].GetUInt8();
5815 m->sender = fields[2].GetUInt32();
5816 m->receiver = fields[3].GetUInt32();
5817 bool has_items = fields[4].GetBool();
5818 m->expire_time = time_t(fields[5].GetUInt32());
5819 m->deliver_time = 0;
5820 m->COD = fields[6].GetUInt32();
5821 m->checked = fields[7].GetUInt8();
5822 m->mailTemplateId = fields[8].GetInt16();
5823
5824 Player* player = nullptr;
5825 if (serverUp)
5826 player = ObjectAccessor::FindConnectedPlayer(ObjectGuid(HighGuid::Player, m->receiver));
5827
5828 if (player && player->m_mailsLoaded)
5829 { // this code will run very improbably (the time is between 4 and 5 am, in game is online a player, who has old mail
5830 // his in mailbox and he has already listed his mails)
5831 delete m;
5832 continue;
5833 }
5834
5835 // Delete or return mail
5836 if (has_items)
5837 {
5838 // read items from cache
5839 m->items.swap(itemsCache[m->messageID]);
5840
5841 // if it is mail from non-player, or if it's already return mail, it shouldn't be returned, but deleted
5842 if (m->messageType != MAIL_NORMAL || (m->checked & (MAIL_CHECK_MASK_COD_PAYMENT | MAIL_CHECK_MASK_RETURNED)))
5843 {
5844 // mail open and then not returned
5845 for (MailItemInfoVec::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2)
5846 {
5847 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE);
5848 stmt->setUInt32(0, itr2->item_guid);
5849 CharacterDatabase.Execute(stmt);
5850 }
5851
5852 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
5853 stmt->setUInt32(0, m->messageID);
5854 CharacterDatabase.Execute(stmt);
5855 }
5856 else
5857 {
5858 // Mail will be returned
5859 stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_MAIL_RETURNED);
5860 stmt->setUInt32(0, m->receiver);
5861 stmt->setUInt32(1, m->sender);
5862 stmt->setUInt32(2, basetime + 30 * DAY);
5863 stmt->setUInt32(3, basetime);
5864 stmt->setUInt8 (4, uint8(MAIL_CHECK_MASK_RETURNED));
5865 stmt->setUInt32(5, m->messageID);
5866 CharacterDatabase.Execute(stmt);
5867 for (MailItemInfoVec::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2)
5868 {
5869 // Update receiver in mail items for its proper delivery, and in instance_item for avoid lost item at sender delete
5870 stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_MAIL_ITEM_RECEIVER);
5871 stmt->setUInt32(0, m->sender);
5872 stmt->setUInt32(1, itr2->item_guid);
5873 CharacterDatabase.Execute(stmt);
5874
5875 stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ITEM_OWNER);
5876 stmt->setUInt32(0, m->sender);
5877 stmt->setUInt32(1, itr2->item_guid);
5878 CharacterDatabase.Execute(stmt);
5879 }
5880 delete m;
5881 ++returnedCount;
5882 continue;
5883 }
5884 }
5885
5886 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
5887 stmt->setUInt32(0, m->messageID);
5888 CharacterDatabase.Execute(stmt);
5889 delete m;
5890 ++deletedCount;
5891 }
5892 while (result->NextRow());
5893
5894 TC_LOG_INFO("server.loading", ">> Processed %u expired mails: %u deleted and %u returned in %u ms", deletedCount + returnedCount, deletedCount, returnedCount, GetMSTimeDiffToNow(oldMSTime));
5895}
5896
5897void ObjectMgr::LoadQuestAreaTriggers()
5898{
5899 uint32 oldMSTime = getMSTime();
5900
5901 _questAreaTriggerStore.clear(); // need for reload case
5902
5903 QueryResult result = WorldDatabase.Query("SELECT id, quest FROM areatrigger_involvedrelation");
5904
5905 if (!result)
5906 {
5907 TC_LOG_INFO("server.loading", ">> Loaded 0 quest trigger points. DB table `areatrigger_involvedrelation` is empty.");
5908 return;
5909 }
5910
5911 uint32 count = 0;
5912
5913 do
5914 {
5915 ++count;
5916
5917 Field* fields = result->Fetch();
5918
5919 uint32 trigger_ID = fields[0].GetUInt32();
5920 uint32 quest_ID = fields[1].GetUInt32();
5921
5922 AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(trigger_ID);
5923 if (!atEntry)
5924 {
5925 TC_LOG_ERROR("sql.sql", "Area trigger (ID:%u) does not exist in `AreaTrigger.dbc`.", trigger_ID);
5926 continue;
5927 }
5928
5929 Quest const* quest = GetQuestTemplate(quest_ID);
5930
5931 if (!quest)
5932 {
5933 TC_LOG_ERROR("sql.sql", "Table `areatrigger_involvedrelation` has record (id: %u) for not existing quest %u", trigger_ID, quest_ID);
5934 continue;
5935 }
5936
5937 if (!quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT))
5938 {
5939 TC_LOG_ERROR("sql.sql", "Table `areatrigger_involvedrelation` has record (id: %u) for not quest %u, 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);
5940
5941 // this will prevent quest completing without objective
5942 const_cast<Quest*>(quest)->SetSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT);
5943
5944 // continue; - quest modified to required objective and trigger can be allowed.
5945 }
5946
5947 _questAreaTriggerStore[trigger_ID] = quest_ID;
5948
5949 } while (result->NextRow());
5950
5951 TC_LOG_INFO("server.loading", ">> Loaded %u quest trigger points in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
5952}
5953
5954QuestGreeting const* ObjectMgr::GetQuestGreeting(ObjectGuid guid) const
5955{
5956 auto itr = _questGreetingStore.find(guid.GetTypeId());
5957 if (itr == _questGreetingStore.end())
5958 return nullptr;
5959
5960 auto questItr = itr->second.find(guid.GetEntry());
5961 if (questItr == itr->second.end())
5962 return nullptr;
5963
5964 return &questItr->second;
5965}
5966
5967void ObjectMgr::LoadQuestGreetings()
5968{
5969 uint32 oldMSTime = getMSTime();
5970
5971 _questGreetingStore.clear(); // need for reload case
5972
5973 // 0 1 2 3 4
5974 QueryResult result = WorldDatabase.Query("SELECT ID, Type, GreetEmoteType, GreetEmoteDelay, Greeting FROM quest_greeting");
5975 if (!result)
5976 {
5977 TC_LOG_INFO("server.loading", ">> Loaded 0 quest greetings. DB table `quest_greeting` is empty.");
5978 return;
5979 }
5980
5981 _questGreetingStore.rehash(result->GetRowCount());
5982
5983 uint32 count = 0;
5984
5985 do
5986 {
5987 Field* fields = result->Fetch();
5988
5989 uint32 id = fields[0].GetUInt32();
5990 uint8 type = fields[1].GetUInt8();
5991 // overwrite
5992 switch (type)
5993 {
5994 case 0: // Creature
5995 type = TYPEID_UNIT;
5996 if (!sObjectMgr->GetCreatureTemplate(id))
5997 {
5998 TC_LOG_ERROR("sql.sql", "Table `quest_greeting`: creature template (entry: %u) does not exist.", id);
5999 continue;
6000 }
6001 break;
6002 case 1: // GameObject
6003 type = TYPEID_GAMEOBJECT;
6004 if (!sObjectMgr->GetGameObjectTemplate(id))
6005 {
6006 TC_LOG_ERROR("sql.sql", "Table `quest_greeting`: gameobject template (entry: %u) does not exist.", id);
6007 continue;
6008 }
6009 break;
6010 default:
6011 TC_LOG_ERROR("sql.sql", "Table `quest_greeting`: unknown type = %u for entry = %u. Skipped.", type, id);
6012 continue;
6013 }
6014
6015 uint16 greetEmoteType = fields[2].GetUInt16();
6016
6017 if (greetEmoteType > 0 && !sEmotesStore.LookupEntry(greetEmoteType))
6018 {
6019 TC_LOG_DEBUG("sql.sql", "Table `quest_greeting`: entry %u has greetEmoteType = %u but emote does not exist. Set to 0.", id, greetEmoteType);
6020 greetEmoteType = 0;
6021 }
6022
6023 uint32 greetEmoteDelay = fields[3].GetUInt32();
6024 std::string greeting = fields[4].GetString();
6025
6026 _questGreetingStore[type][id] = QuestGreeting(greetEmoteType, greetEmoteDelay, greeting);
6027
6028 ++count;
6029 }
6030 while (result->NextRow());
6031
6032 TC_LOG_INFO("server.loading", ">> Loaded %u quest_greeting in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
6033}
6034
6035void ObjectMgr::LoadQuestGreetingsLocales()
6036{
6037 uint32 oldMSTime = getMSTime();
6038
6039 _questGreetingLocaleStore.clear(); // need for reload case
6040
6041 // 0 1 2 3
6042 QueryResult result = WorldDatabase.Query("SELECT ID, Type, Locale, Greeting FROM quest_greeting_locale");
6043 if (!result)
6044 {
6045 TC_LOG_INFO("server.loading", ">> Loaded 0 quest_greeting locales. DB table `quest_greeting_locale` is empty.");
6046 return;
6047 }
6048
6049 do
6050 {
6051 Field* fields = result->Fetch();
6052
6053 uint32 id = fields[0].GetUInt32();
6054 uint8 type = fields[1].GetUInt8();
6055 // overwrite
6056 switch (type)
6057 {
6058 case 0: // Creature
6059 type = TYPEID_UNIT;
6060 break;
6061 case 1: // GameObject
6062 type = TYPEID_GAMEOBJECT;
6063 break;
6064 default:
6065 break;
6066 }
6067
6068 std::string localeName = fields[2].GetString();
6069 std::string greeting = fields[3].GetString();
6070
6071 QuestGreetingLocale& data = _questGreetingLocaleStore[MAKE_PAIR32(id, type)];
6072 LocaleConstant locale = GetLocaleByName(localeName);
6073 if (locale == LOCALE_enUS)
6074 continue;
6075
6076 AddLocaleString(greeting, locale, data.greeting);
6077 } while (result->NextRow());
6078
6079 TC_LOG_INFO("server.loading", ">> Loaded %u quest greeting locale strings in %u ms", uint32(_questGreetingLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime));
6080}
6081
6082void ObjectMgr::LoadTavernAreaTriggers()
6083{
6084 uint32 oldMSTime = getMSTime();
6085
6086 _tavernAreaTriggerStore.clear(); // need for reload case
6087
6088 QueryResult result = WorldDatabase.Query("SELECT id FROM areatrigger_tavern");
6089
6090 if (!result)
6091 {
6092 TC_LOG_INFO("server.loading", ">> Loaded 0 tavern triggers. DB table `areatrigger_tavern` is empty.");
6093 return;
6094 }
6095
6096 uint32 count = 0;
6097
6098 do
6099 {
6100 ++count;
6101
6102 Field* fields = result->Fetch();
6103
6104 uint32 Trigger_ID = fields[0].GetUInt32();
6105
6106 AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(Trigger_ID);
6107 if (!atEntry)
6108 {
6109 TC_LOG_ERROR("sql.sql", "Area trigger (ID:%u) does not exist in `AreaTrigger.dbc`.", Trigger_ID);
6110 continue;
6111 }
6112
6113 _tavernAreaTriggerStore.insert(Trigger_ID);
6114 } while (result->NextRow());
6115
6116 TC_LOG_INFO("server.loading", ">> Loaded %u tavern triggers in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
6117}
6118
6119void ObjectMgr::LoadAreaTriggerScripts()
6120{
6121 uint32 oldMSTime = getMSTime();
6122
6123 _areaTriggerScriptStore.clear(); // need for reload case
6124
6125 QueryResult result = WorldDatabase.Query("SELECT entry, ScriptName FROM areatrigger_scripts");
6126 if (!result)
6127 {
6128 TC_LOG_INFO("server.loading", ">> Loaded 0 areatrigger scripts. DB table `areatrigger_scripts` is empty.");
6129 return;
6130 }
6131
6132 do
6133 {
6134 Field* fields = result->Fetch();
6135
6136 uint32 triggerId = fields[0].GetUInt32();
6137 std::string const scriptName = fields[1].GetString();
6138
6139 AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(triggerId);
6140 if (!atEntry)
6141 {
6142 TC_LOG_ERROR("sql.sql", "AreaTrigger (ID: %u) does not exist in `AreaTrigger.dbc`.", triggerId);
6143 continue;
6144 }
6145 _areaTriggerScriptStore[triggerId] = GetScriptId(scriptName);
6146 }
6147 while (result->NextRow());
6148
6149 TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " areatrigger scripts in %u ms", _areaTriggerScriptStore.size(), GetMSTimeDiffToNow(oldMSTime));
6150}
6151
6152uint32 ObjectMgr::GetNearestTaxiNode(float x, float y, float z, uint32 mapid, uint32 team)
6153{
6154 bool found = false;
6155 float dist = 10000;
6156 uint32 id = 0;
6157
6158 for (uint32 i = 1; i < sTaxiNodesStore.GetNumRows(); ++i)
6159 {
6160 TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(i);
6161
6162 if (!node || node->map_id != mapid || (!node->MountCreatureID[team == ALLIANCE ? 1 : 0] && node->MountCreatureID[0] != 32981)) // dk flight
6163 continue;
6164
6165 uint8 field = (uint8)((i - 1) / 32);
6166 uint32 submask = 1<<((i-1)%32);
6167
6168 // skip not taxi network nodes
6169 if ((sTaxiNodesMask[field] & submask) == 0)
6170 continue;
6171
6172 float dist2 = (node->x - x)*(node->x - x)+(node->y - y)*(node->y - y)+(node->z - z)*(node->z - z);
6173 if (found)
6174 {
6175 if (dist2 < dist)
6176 {
6177 dist = dist2;
6178 id = i;
6179 }
6180 }
6181 else
6182 {
6183 found = true;
6184 dist = dist2;
6185 id = i;
6186 }
6187 }
6188
6189 return id;
6190}
6191
6192void ObjectMgr::GetTaxiPath(uint32 source, uint32 destination, uint32 &path, uint32 &cost)
6193{
6194 TaxiPathSetBySource::iterator src_i = sTaxiPathSetBySource.find(source);
6195 if (src_i == sTaxiPathSetBySource.end())
6196 {
6197 path = 0;
6198 cost = 0;
6199 return;
6200 }
6201
6202 TaxiPathSetForSource& pathSet = src_i->second;
6203
6204 TaxiPathSetForSource::iterator dest_i = pathSet.find(destination);
6205 if (dest_i == pathSet.end())
6206 {
6207 path = 0;
6208 cost = 0;
6209 return;
6210 }
6211
6212 cost = dest_i->second.price;
6213 path = dest_i->second.ID;
6214}
6215
6216uint32 ObjectMgr::GetTaxiMountDisplayId(uint32 id, uint32 team, bool allowed_alt_team /* = false */)
6217{
6218 uint32 mount_id = 0;
6219
6220 // select mount creature id
6221 TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(id);
6222 if (node)
6223 {
6224 uint32 mount_entry = 0;
6225 if (team == ALLIANCE)
6226 mount_entry = node->MountCreatureID[1];
6227 else
6228 mount_entry = node->MountCreatureID[0];
6229
6230 // Fix for Alliance not being able to use Acherus taxi
6231 // only one mount type for both sides
6232 if (mount_entry == 0 && allowed_alt_team)
6233 {
6234 // Simply reverse the selection. At least one team in theory should have a valid mount ID to choose.
6235 mount_entry = team == ALLIANCE ? node->MountCreatureID[0] : node->MountCreatureID[1];
6236 }
6237
6238 CreatureTemplate const* mount_info = GetCreatureTemplate(mount_entry);
6239 if (mount_info)
6240 {
6241 mount_id = mount_info->GetRandomValidModelId();
6242 if (!mount_id)
6243 {
6244 TC_LOG_ERROR("sql.sql", "No displayid found for the taxi mount with the entry %u! Can't load it!", mount_entry);
6245 return 0;
6246 }
6247 }
6248 }
6249
6250 // minfo is not actually used but the mount_id was updated
6251 GetCreatureModelRandomGender(&mount_id);
6252
6253 return mount_id;
6254}
6255
6256Quest const* ObjectMgr::GetQuestTemplate(uint32 quest_id) const
6257{
6258 return Trinity::Containers::MapGetValuePtr(_questTemplates, quest_id);
6259}
6260
6261void ObjectMgr::LoadGraveyardZones()
6262{
6263 uint32 oldMSTime = getMSTime();
6264
6265 GraveyardStore.clear(); // need for reload case
6266
6267 // 0 1 2
6268 QueryResult result = WorldDatabase.Query("SELECT ID, GhostZone, Faction FROM graveyard_zone");
6269
6270 if (!result)
6271 {
6272 TC_LOG_INFO("server.loading", ">> Loaded 0 graveyard-zone links. DB table `graveyard_zone` is empty.");
6273 return;
6274 }
6275
6276 uint32 count = 0;
6277
6278 do
6279 {
6280 ++count;
6281
6282 Field* fields = result->Fetch();
6283
6284 uint32 safeLocId = fields[0].GetUInt32();
6285 uint32 zoneId = fields[1].GetUInt32();
6286 uint32 team = fields[2].GetUInt16();
6287
6288 WorldSafeLocsEntry const* entry = sWorldSafeLocsStore.LookupEntry(safeLocId);
6289 if (!entry)
6290 {
6291 TC_LOG_ERROR("sql.sql", "Table `graveyard_zone` has a record for non-existing graveyard (WorldSafeLocsID: %u), skipped.", safeLocId);
6292 continue;
6293 }
6294
6295 AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(zoneId);
6296 if (!areaEntry)
6297 {
6298 TC_LOG_ERROR("sql.sql", "Table `graveyard_zone` has a record for non-existing Zone (ID: %u), skipped.", zoneId);
6299 continue;
6300 }
6301
6302 if (areaEntry->zone != 0)
6303 {
6304 TC_LOG_ERROR("sql.sql", "Table `graveyard_zone` has a record for SubZone (ID: %u) instead of zone, skipped.", zoneId);
6305 continue;
6306 }
6307
6308 if (team != 0 && team != HORDE && team != ALLIANCE)
6309 {
6310 TC_LOG_ERROR("sql.sql", "Table `graveyard_zone` has a record for non player faction (%u), skipped.", team);
6311 continue;
6312 }
6313
6314 if (!AddGraveyardLink(safeLocId, zoneId, team, false))
6315 TC_LOG_ERROR("sql.sql", "Table `graveyard_zone` has a duplicate record for Graveyard (ID: %u) and Zone (ID: %u), skipped.", safeLocId, zoneId);
6316 } while (result->NextRow());
6317
6318 TC_LOG_INFO("server.loading", ">> Loaded %u graveyard-zone links in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
6319}
6320
6321WorldSafeLocsEntry const* ObjectMgr::GetDefaultGraveyard(uint32 team) const
6322{
6323 enum DefaultGraveyard
6324 {
6325 HORDE_GRAVEYARD = 10, // Crossroads
6326 ALLIANCE_GRAVEYARD = 4 // Westfall
6327 };
6328
6329 if (team == HORDE)
6330 return sWorldSafeLocsStore.LookupEntry(HORDE_GRAVEYARD);
6331 else if (team == ALLIANCE)
6332 return sWorldSafeLocsStore.LookupEntry(ALLIANCE_GRAVEYARD);
6333 else return nullptr;
6334}
6335
6336WorldSafeLocsEntry const* ObjectMgr::GetClosestGraveyard(float x, float y, float z, uint32 MapId, uint32 team) const
6337{
6338 // search for zone associated closest graveyard
6339 uint32 zoneId = sMapMgr->GetZoneId(MapId, x, y, z);
6340
6341 if (!zoneId)
6342 {
6343 if (z > -500)
6344 {
6345 TC_LOG_ERROR("misc", "ZoneId not found for map %u coords (%f, %f, %f)", MapId, x, y, z);
6346 return GetDefaultGraveyard(team);
6347 }
6348 }
6349
6350 // Simulate std. algorithm:
6351 // found some graveyard associated to (ghost_zone, ghost_map)
6352 //
6353 // if mapId == graveyard.mapId (ghost in plain zone or city or battleground) and search graveyard at same map
6354 // then check faction
6355 // if mapId != graveyard.mapId (ghost in instance) and search any graveyard associated
6356 // then check faction
6357 GraveyardMapBounds range = GraveyardStore.equal_range(zoneId);
6358 MapEntry const* map = sMapStore.LookupEntry(MapId);
6359
6360 // not need to check validity of map object; MapId _MUST_ be valid here
6361 if (range.first == range.second && !map->IsBattlegroundOrArena())
6362 {
6363 if (zoneId != 0) // zone == 0 can't be fixed, used by bliz for bugged zones
6364 TC_LOG_ERROR("sql.sql", "Table `graveyard_zone` incomplete: Zone %u Team %u does not have a linked graveyard.", zoneId, team);
6365 return GetDefaultGraveyard(team);
6366 }
6367
6368 // at corpse map
6369 bool foundNear = false;
6370 float distNear = 10000;
6371 WorldSafeLocsEntry const* entryNear = nullptr;
6372
6373 // at entrance map for corpse map
6374 bool foundEntr = false;
6375 float distEntr = 10000;
6376 WorldSafeLocsEntry const* entryEntr = nullptr;
6377
6378 // some where other
6379 WorldSafeLocsEntry const* entryFar = nullptr;
6380
6381 MapEntry const* mapEntry = sMapStore.LookupEntry(MapId);
6382
6383 for (; range.first != range.second; ++range.first)
6384 {
6385 GraveyardData const& data = range.first->second;
6386
6387 WorldSafeLocsEntry const* entry = sWorldSafeLocsStore.LookupEntry(data.safeLocId);
6388 if (!entry)
6389 {
6390 TC_LOG_ERROR("sql.sql", "Table `graveyard_zone` has record for not existing graveyard (WorldSafeLocsID %u), skipped.", data.safeLocId);
6391 continue;
6392 }
6393
6394 // skip enemy faction graveyard
6395 // team == 0 case can be at call from .neargrave
6396 if (data.team != 0 && team != 0 && data.team != team)
6397 continue;
6398
6399 // find now nearest graveyard at other map
6400 if (MapId != entry->map_id)
6401 {
6402 // if find graveyard at different map from where entrance placed (or no entrance data), use any first
6403 if (!mapEntry
6404 || mapEntry->entrance_map < 0
6405 || uint32(mapEntry->entrance_map) != entry->map_id
6406 || (mapEntry->entrance_x == 0 && mapEntry->entrance_y == 0))
6407 {
6408 // not have any corrdinates for check distance anyway
6409 entryFar = entry;
6410 continue;
6411 }
6412
6413 // at entrance map calculate distance (2D);
6414 float dist2 = (entry->x - mapEntry->entrance_x)*(entry->x - mapEntry->entrance_x)
6415 +(entry->y - mapEntry->entrance_y)*(entry->y - mapEntry->entrance_y);
6416 if (foundEntr)
6417 {
6418 if (dist2 < distEntr)
6419 {
6420 distEntr = dist2;
6421 entryEntr = entry;
6422 }
6423 }
6424 else
6425 {
6426 foundEntr = true;
6427 distEntr = dist2;
6428 entryEntr = entry;
6429 }
6430 }
6431 // find now nearest graveyard at same map
6432 else
6433 {
6434 float dist2 = (entry->x - x)*(entry->x - x)+(entry->y - y)*(entry->y - y)+(entry->z - z)*(entry->z - z);
6435 if (foundNear)
6436 {
6437 if (dist2 < distNear)
6438 {
6439 distNear = dist2;
6440 entryNear = entry;
6441 }
6442 }
6443 else
6444 {
6445 foundNear = true;
6446 distNear = dist2;
6447 entryNear = entry;
6448 }
6449 }
6450 }
6451
6452 if (entryNear)
6453 return entryNear;
6454
6455 if (entryEntr)
6456 return entryEntr;
6457
6458 return entryFar;
6459}
6460
6461GraveyardData const* ObjectMgr::FindGraveyardData(uint32 id, uint32 zoneId) const
6462{
6463 GraveyardMapBounds range = GraveyardStore.equal_range(zoneId);
6464 for (; range.first != range.second; ++range.first)
6465 {
6466 GraveyardData const& data = range.first->second;
6467 if (data.safeLocId == id)
6468 return &data;
6469 }
6470 return nullptr;
6471}
6472
6473AreaTrigger const* ObjectMgr::GetAreaTrigger(uint32 trigger) const
6474{
6475 AreaTriggerContainer::const_iterator itr = _areaTriggerStore.find(trigger);
6476 if (itr != _areaTriggerStore.end())
6477 return &itr->second;
6478 return nullptr;
6479}
6480
6481AccessRequirement const* ObjectMgr::GetAccessRequirement(uint32 mapid, Difficulty difficulty) const
6482{
6483 AccessRequirementContainer::const_iterator itr = _accessRequirementStore.find(MAKE_PAIR32(mapid, difficulty));
6484 if (itr != _accessRequirementStore.end())
6485 return itr->second.get();
6486 return nullptr;
6487}
6488
6489bool ObjectMgr::AddGraveyardLink(uint32 id, uint32 zoneId, uint32 team, bool persist /*= true*/)
6490{
6491 if (FindGraveyardData(id, zoneId))
6492 return false;
6493
6494 // add link to loaded data
6495 GraveyardData data;
6496 data.safeLocId = id;
6497 data.team = team;
6498
6499 GraveyardStore.insert(GraveyardContainer::value_type(zoneId, data));
6500
6501 // add link to DB
6502 if (persist)
6503 {
6504 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_GRAVEYARD_ZONE);
6505
6506 stmt->setUInt32(0, id);
6507 stmt->setUInt32(1, zoneId);
6508 stmt->setUInt16(2, uint16(team));
6509
6510 WorldDatabase.Execute(stmt);
6511 }
6512
6513 return true;
6514}
6515
6516void ObjectMgr::RemoveGraveyardLink(uint32 id, uint32 zoneId, uint32 team, bool persist /*= false*/)
6517{
6518 GraveyardMapBoundsNonConst range = GraveyardStore.equal_range(zoneId);
6519 if (range.first == range.second)
6520 {
6521 //TC_LOG_ERROR("sql.sql", "Table `graveyard_zone` incomplete: Zone %u Team %u does not have a linked graveyard.", zoneId, team);
6522 return;
6523 }
6524
6525 bool found = false;
6526
6527
6528 for (; range.first != range.second; ++range.first)
6529 {
6530 GraveyardData & data = range.first->second;
6531
6532 // skip not matching safezone id
6533 if (data.safeLocId != id)
6534 continue;
6535
6536 // skip enemy faction graveyard at same map (normal area, city, or battleground)
6537 // team == 0 case can be at call from .neargrave
6538 if (data.team != 0 && team != 0 && data.team != team)
6539 continue;
6540
6541 found = true;
6542 break;
6543 }
6544
6545 // no match, return
6546 if (!found)
6547 return;
6548
6549 // remove from links
6550 GraveyardStore.erase(range.first);
6551
6552 // remove link from DB
6553 if (persist)
6554 {
6555 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GRAVEYARD_ZONE);
6556
6557 stmt->setUInt32(0, id);
6558 stmt->setUInt32(1, zoneId);
6559 stmt->setUInt16(2, uint16(team));
6560
6561 WorldDatabase.Execute(stmt);
6562 }
6563}
6564
6565void ObjectMgr::LoadAreaTriggerTeleports()
6566{
6567 uint32 oldMSTime = getMSTime();
6568
6569 _areaTriggerStore.clear(); // need for reload case
6570
6571 // 0 1 2 3 4 5
6572 QueryResult result = WorldDatabase.Query("SELECT ID, target_map, target_position_x, target_position_y, target_position_z, target_orientation FROM areatrigger_teleport");
6573 if (!result)
6574 {
6575 TC_LOG_INFO("server.loading", ">> Loaded 0 area trigger teleport definitions. DB table `areatrigger_teleport` is empty.");
6576 return;
6577 }
6578
6579 uint32 count = 0;
6580
6581 do
6582 {
6583 Field* fields = result->Fetch();
6584
6585 ++count;
6586
6587 uint32 Trigger_ID = fields[0].GetUInt32();
6588
6589 AreaTrigger at;
6590
6591 at.target_mapId = fields[1].GetUInt16();
6592 at.target_X = fields[2].GetFloat();
6593 at.target_Y = fields[3].GetFloat();
6594 at.target_Z = fields[4].GetFloat();
6595 at.target_Orientation = fields[5].GetFloat();
6596
6597 AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(Trigger_ID);
6598 if (!atEntry)
6599 {
6600 TC_LOG_ERROR("sql.sql", "Area trigger (ID:%u) does not exist in `AreaTrigger.dbc`.", Trigger_ID);
6601 continue;
6602 }
6603
6604 MapEntry const* mapEntry = sMapStore.LookupEntry(at.target_mapId);
6605 if (!mapEntry)
6606 {
6607 TC_LOG_ERROR("sql.sql", "Area trigger (ID:%u) target map (ID: %u) does not exist in `Map.dbc`.", Trigger_ID, at.target_mapId);
6608 continue;
6609 }
6610
6611 if (at.target_X == 0 && at.target_Y == 0 && at.target_Z == 0)
6612 {
6613 TC_LOG_ERROR("sql.sql", "Area trigger (ID:%u) target coordinates not provided.", Trigger_ID);
6614 continue;
6615 }
6616
6617 _areaTriggerStore[Trigger_ID] = at;
6618
6619 } while (result->NextRow());
6620
6621 TC_LOG_INFO("server.loading", ">> Loaded %u area trigger teleport definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
6622}
6623
6624void ObjectMgr::LoadAccessRequirements()
6625{
6626 uint32 oldMSTime = getMSTime();
6627
6628 _accessRequirementStore.clear(); // need for reload case
6629
6630 // 0 1 2 3 4 5 6 7 8 9 10
6631 QueryResult result = WorldDatabase.Query("SELECT mapid, difficulty, level_min, level_max, item_level, item, item2, quest_done_A, quest_done_H, completed_achievement, quest_failed_text FROM access_requirement");
6632
6633 if (!result)
6634 {
6635 TC_LOG_INFO("server.loading", ">> Loaded 0 access requirement definitions. DB table `access_requirement` is empty.");
6636 return;
6637 }
6638
6639 uint32 count = 0;
6640
6641 do
6642 {
6643 Field* fields = result->Fetch();
6644
6645 ++count;
6646
6647 uint32 mapid = fields[0].GetUInt32();
6648 uint8 difficulty = fields[1].GetUInt8();
6649 uint32 requirement_ID = MAKE_PAIR32(mapid, difficulty);
6650
6651 auto& ar = _accessRequirementStore[requirement_ID];
6652 ar = Trinity::make_unique<AccessRequirement>();
6653
6654 ar->levelMin = fields[2].GetUInt8();
6655 ar->levelMax = fields[3].GetUInt8();
6656 ar->item_level = fields[4].GetUInt16();
6657 ar->item = fields[5].GetUInt32();
6658 ar->item2 = fields[6].GetUInt32();
6659 ar->quest_A = fields[7].GetUInt32();
6660 ar->quest_H = fields[8].GetUInt32();
6661 ar->achievement = fields[9].GetUInt32();
6662 ar->questFailedText = fields[10].GetString();
6663
6664 if (ar->item)
6665 {
6666 ItemTemplate const* pProto = GetItemTemplate(ar->item);
6667 if (!pProto)
6668 {
6669 TC_LOG_ERROR("misc", "Key item %u does not exist for map %u difficulty %u, removing key requirement.", ar->item, mapid, difficulty);
6670 ar->item = 0;
6671 }
6672 }
6673
6674 if (ar->item2)
6675 {
6676 ItemTemplate const* pProto = GetItemTemplate(ar->item2);
6677 if (!pProto)
6678 {
6679 TC_LOG_ERROR("misc", "Second item %u does not exist for map %u difficulty %u, removing key requirement.", ar->item2, mapid, difficulty);
6680 ar->item2 = 0;
6681 }
6682 }
6683
6684 if (ar->quest_A)
6685 {
6686 if (!GetQuestTemplate(ar->quest_A))
6687 {
6688 TC_LOG_ERROR("sql.sql", "Required Alliance Quest %u not exist for map %u difficulty %u, remove quest done requirement.", ar->quest_A, mapid, difficulty);
6689 ar->quest_A = 0;
6690 }
6691 }
6692
6693 if (ar->quest_H)
6694 {
6695 if (!GetQuestTemplate(ar->quest_H))
6696 {
6697 TC_LOG_ERROR("sql.sql", "Required Horde Quest %u not exist for map %u difficulty %u, remove quest done requirement.", ar->quest_H, mapid, difficulty);
6698 ar->quest_H = 0;
6699 }
6700 }
6701
6702 if (ar->achievement)
6703 {
6704 if (!sAchievementMgr->GetAchievement(ar->achievement))
6705 {
6706 TC_LOG_ERROR("sql.sql", "Required Achievement %u not exist for map %u difficulty %u, remove quest done requirement.", ar->achievement, mapid, difficulty);
6707 ar->achievement = 0;
6708 }
6709 }
6710 } while (result->NextRow());
6711
6712 TC_LOG_INFO("server.loading", ">> Loaded %u access requirement definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
6713}
6714
6715/*
6716 * Searches for the areatrigger which teleports players out of the given map with instance_template.parent field support
6717 */
6718AreaTrigger const* ObjectMgr::GetGoBackTrigger(uint32 Map) const
6719{
6720 bool useParentDbValue = false;
6721 uint32 parentId = 0;
6722 MapEntry const* mapEntry = sMapStore.LookupEntry(Map);
6723 if (!mapEntry || mapEntry->entrance_map < 0)
6724 return nullptr;
6725
6726 if (mapEntry->IsDungeon())
6727 {
6728 InstanceTemplate const* iTemplate = sObjectMgr->GetInstanceTemplate(Map);
6729
6730 if (!iTemplate)
6731 return nullptr;
6732
6733 parentId = iTemplate->Parent;
6734 useParentDbValue = true;
6735 }
6736
6737 uint32 entrance_map = uint32(mapEntry->entrance_map);
6738 for (AreaTriggerContainer::const_iterator itr = _areaTriggerStore.begin(); itr != _areaTriggerStore.end(); ++itr)
6739 if ((!useParentDbValue && itr->second.target_mapId == entrance_map) || (useParentDbValue && itr->second.target_mapId == parentId))
6740 {
6741 AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(itr->first);
6742 if (atEntry && atEntry->mapid == Map)
6743 return &itr->second;
6744 }
6745 return nullptr;
6746}
6747
6748/**
6749 * Searches for the areatrigger which teleports players to the given map
6750 */
6751AreaTrigger const* ObjectMgr::GetMapEntranceTrigger(uint32 Map) const
6752{
6753 for (AreaTriggerContainer::const_iterator itr = _areaTriggerStore.begin(); itr != _areaTriggerStore.end(); ++itr)
6754 {
6755 if (itr->second.target_mapId == Map)
6756 {
6757 AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(itr->first);
6758 if (atEntry)
6759 return &itr->second;
6760 }
6761 }
6762 return nullptr;
6763}
6764
6765void ObjectMgr::SetHighestGuids()
6766{
6767 QueryResult result = CharacterDatabase.Query("SELECT MAX(guid) FROM characters");
6768 if (result)
6769 GetGuidSequenceGenerator<HighGuid::Player>().Set((*result)[0].GetUInt32()+1);
6770
6771 result = CharacterDatabase.Query("SELECT MAX(guid) FROM item_instance");
6772 if (result)
6773 GetGuidSequenceGenerator<HighGuid::Item>().Set((*result)[0].GetUInt32()+1);
6774
6775 // Cleanup other tables from nonexistent guids ( >= _hiItemGuid)
6776 CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item >= '%u'", GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
6777 CharacterDatabase.PExecute("DELETE FROM mail_items WHERE item_guid >= '%u'", GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
6778 CharacterDatabase.PExecute("DELETE FROM auctionhouse WHERE itemguid >= '%u'", GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
6779 CharacterDatabase.PExecute("DELETE FROM guild_bank_item WHERE item_guid >= '%u'", GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
6780
6781 result = WorldDatabase.Query("SELECT MAX(guid) FROM transports");
6782 if (result)
6783 GetGuidSequenceGenerator<HighGuid::Mo_Transport>().Set((*result)[0].GetUInt32()+1);
6784
6785 result = CharacterDatabase.Query("SELECT MAX(id) FROM auctionhouse");
6786 if (result)
6787 _auctionId = (*result)[0].GetUInt32()+1;
6788
6789 result = CharacterDatabase.Query("SELECT MAX(id) FROM mail");
6790 if (result)
6791 _mailId = (*result)[0].GetUInt32()+1;
6792
6793 result = CharacterDatabase.Query("SELECT MAX(arenateamid) FROM arena_team");
6794 if (result)
6795 sArenaTeamMgr->SetNextArenaTeamId((*result)[0].GetUInt32()+1);
6796
6797 result = CharacterDatabase.Query("SELECT MAX(setguid) FROM character_equipmentsets");
6798 if (result)
6799 _equipmentSetGuid = (*result)[0].GetUInt64()+1;
6800
6801 result = CharacterDatabase.Query("SELECT MAX(guildId) FROM guild");
6802 if (result)
6803 sGuildMgr->SetNextGuildId((*result)[0].GetUInt32()+1);
6804
6805 result = CharacterDatabase.Query("SELECT MAX(guid) FROM groups");
6806 if (result)
6807 sGroupMgr->SetGroupDbStoreSize((*result)[0].GetUInt32()+1);
6808
6809 result = WorldDatabase.Query("SELECT MAX(guid) FROM creature");
6810 if (result)
6811 _creatureSpawnId = (*result)[0].GetUInt32() + 1;
6812
6813 result = WorldDatabase.Query("SELECT MAX(guid) FROM gameobject");
6814 if (result)
6815 _gameObjectSpawnId = (*result)[0].GetUInt32() + 1;
6816}
6817
6818uint32 ObjectMgr::GenerateAuctionID()
6819{
6820 if (_auctionId >= 0xFFFFFFFE)
6821 {
6822 TC_LOG_ERROR("misc", "Auctions ids overflow!! Can't continue, shutting down server. Search on forum for TCE00007 for more info. ");
6823 World::StopNow(ERROR_EXIT_CODE);
6824 }
6825 return _auctionId++;
6826}
6827
6828uint64 ObjectMgr::GenerateEquipmentSetGuid()
6829{
6830 if (_equipmentSetGuid >= uint64(0xFFFFFFFFFFFFFFFELL))
6831 {
6832 TC_LOG_ERROR("misc", "EquipmentSet guid overflow!! Can't continue, shutting down server. Search on forum for TCE00007 for more info. ");
6833 World::StopNow(ERROR_EXIT_CODE);
6834 }
6835 return _equipmentSetGuid++;
6836}
6837
6838uint32 ObjectMgr::GenerateMailID()
6839{
6840 if (_mailId >= 0xFFFFFFFE)
6841 {
6842 TC_LOG_ERROR("misc", "Mail ids overflow!! Can't continue, shutting down server. Search on forum for TCE00007 for more info. ");
6843 World::StopNow(ERROR_EXIT_CODE);
6844 }
6845 return _mailId++;
6846}
6847
6848uint32 ObjectMgr::GeneratePetNumber()
6849{
6850 if (_hiPetNumber >= 0xFFFFFFFE)
6851 {
6852 TC_LOG_ERROR("misc", "_hiPetNumber Id overflow!! Can't continue, shutting down server. Search on forum for TCE00007 for more info.");
6853 World::StopNow(ERROR_EXIT_CODE);
6854 }
6855 return _hiPetNumber++;
6856}
6857
6858uint32 ObjectMgr::GenerateCreatureSpawnId()
6859{
6860 if (_creatureSpawnId >= uint32(0xFFFFFF))
6861 {
6862 TC_LOG_ERROR("misc", "Creature spawn id overflow!! Can't continue, shutting down server. Search on forum for TCE00007 for more info.");
6863 World::StopNow(ERROR_EXIT_CODE);
6864 }
6865 return _creatureSpawnId++;
6866}
6867
6868uint32 ObjectMgr::GenerateGameObjectSpawnId()
6869{
6870 if (_gameObjectSpawnId >= uint32(0xFFFFFF))
6871 {
6872 TC_LOG_ERROR("misc", "GameObject spawn id overflow!! Can't continue, shutting down server. Search on forum for TCE00007 for more info. ");
6873 World::StopNow(ERROR_EXIT_CODE);
6874 }
6875 return _gameObjectSpawnId++;
6876}
6877
6878void ObjectMgr::LoadGameObjectLocales()
6879{
6880 uint32 oldMSTime = getMSTime();
6881
6882 _gameObjectLocaleStore.clear(); // need for reload case
6883
6884 // 0 1 2 3
6885 QueryResult result = WorldDatabase.Query("SELECT entry, locale, name, castBarCaption FROM gameobject_template_locale");
6886 if (!result)
6887 return;
6888
6889 do
6890 {
6891 Field* fields = result->Fetch();
6892
6893 uint32 id = fields[0].GetUInt32();
6894 std::string localeName = fields[1].GetString();
6895
6896 std::string name = fields[2].GetString();
6897 std::string castBarCaption = fields[3].GetString();
6898
6899 GameObjectLocale& data = _gameObjectLocaleStore[id];
6900 LocaleConstant locale = GetLocaleByName(localeName);
6901 if (locale == LOCALE_enUS)
6902 continue;
6903
6904 AddLocaleString(name, locale, data.Name);
6905 AddLocaleString(castBarCaption, locale, data.CastBarCaption);
6906
6907 } while (result->NextRow());
6908
6909 TC_LOG_INFO("server.loading", ">> Loaded %u gameobject_template_locale strings in %u ms", uint32(_gameObjectLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime));
6910}
6911
6912inline void CheckGOLockId(GameObjectTemplate const* goInfo, uint32 dataN, uint32 N)
6913{
6914 if (sLockStore.LookupEntry(dataN))
6915 return;
6916
6917 TC_LOG_ERROR("sql.sql", "Gameobject (Entry: %u GoType: %u) have data%d=%u but lock (Id: %u) not found.",
6918 goInfo->entry, goInfo->type, N, goInfo->door.lockId, goInfo->door.lockId);
6919}
6920
6921inline void CheckGOLinkedTrapId(GameObjectTemplate const* goInfo, uint32 dataN, uint32 N)
6922{
6923 if (GameObjectTemplate const* trapInfo = sObjectMgr->GetGameObjectTemplate(dataN))
6924 {
6925 if (trapInfo->type != GAMEOBJECT_TYPE_TRAP)
6926 TC_LOG_ERROR("sql.sql", "Gameobject (Entry: %u GoType: %u) have data%d=%u but GO (Entry %u) have not GAMEOBJECT_TYPE_TRAP (%u) type.",
6927 goInfo->entry, goInfo->type, N, dataN, dataN, GAMEOBJECT_TYPE_TRAP);
6928 }
6929}
6930
6931inline void CheckGOSpellId(GameObjectTemplate const* goInfo, uint32 dataN, uint32 N)
6932{
6933 if (sSpellMgr->GetSpellInfo(dataN))
6934 return;
6935
6936 TC_LOG_ERROR("sql.sql", "Gameobject (Entry: %u GoType: %u) have data%d=%u but Spell (Entry %u) not exist.",
6937 goInfo->entry, goInfo->type, N, dataN, dataN);
6938}
6939
6940inline void CheckAndFixGOChairHeightId(GameObjectTemplate const* goInfo, uint32 const& dataN, uint32 N)
6941{
6942 if (dataN <= (UNIT_STAND_STATE_SIT_HIGH_CHAIR-UNIT_STAND_STATE_SIT_LOW_CHAIR))
6943 return;
6944
6945 TC_LOG_ERROR("sql.sql", "Gameobject (Entry: %u GoType: %u) have data%d=%u but correct chair height in range 0..%i.",
6946 goInfo->entry, goInfo->type, N, dataN, UNIT_STAND_STATE_SIT_HIGH_CHAIR-UNIT_STAND_STATE_SIT_LOW_CHAIR);
6947
6948 // prevent client and server unexpected work
6949 const_cast<uint32&>(dataN) = 0;
6950}
6951
6952inline void CheckGONoDamageImmuneId(GameObjectTemplate* goTemplate, uint32 dataN, uint32 N)
6953{
6954 // 0/1 correct values
6955 if (dataN <= 1)
6956 return;
6957
6958 TC_LOG_ERROR("sql.sql", "Gameobject (Entry: %u GoType: %u) have data%d=%u but expected boolean (0/1) noDamageImmune field value.", goTemplate->entry, goTemplate->type, N, dataN);
6959}
6960
6961inline void CheckGOConsumable(GameObjectTemplate const* goInfo, uint32 dataN, uint32 N)
6962{
6963 // 0/1 correct values
6964 if (dataN <= 1)
6965 return;
6966
6967 TC_LOG_ERROR("sql.sql", "Gameobject (Entry: %u GoType: %u) have data%d=%u but expected boolean (0/1) consumable field value.",
6968 goInfo->entry, goInfo->type, N, dataN);
6969}
6970
6971void ObjectMgr::LoadGameObjectTemplate()
6972{
6973 uint32 oldMSTime = getMSTime();
6974
6975 // 0 1 2 3 4 5 6 7
6976 QueryResult result = WorldDatabase.Query("SELECT entry, type, displayId, name, IconName, castBarCaption, unk1, size, "
6977 // 8 9 10 11 12 13 14 15 16 17 18 19 20
6978 "Data0, Data1, Data2, Data3, Data4, Data5, Data6, Data7, Data8, Data9, Data10, Data11, Data12, "
6979 // 21 22 23 24 25 26 27 28 29 30 31 32 33
6980 "Data13, Data14, Data15, Data16, Data17, Data18, Data19, Data20, Data21, Data22, Data23, AIName, ScriptName "
6981 "FROM gameobject_template");
6982
6983 if (!result)
6984 {
6985 TC_LOG_INFO("server.loading", ">> Loaded 0 gameobject definitions. DB table `gameobject_template` is empty.");
6986 return;
6987 }
6988
6989 _gameObjectTemplateStore.reserve(result->GetRowCount());
6990 do
6991 {
6992 Field* fields = result->Fetch();
6993
6994 uint32 entry = fields[0].GetUInt32();
6995
6996 GameObjectTemplate& got = _gameObjectTemplateStore[entry];
6997 got.entry = entry;
6998 got.type = uint32(fields[1].GetUInt8());
6999 got.displayId = fields[2].GetUInt32();
7000 got.name = fields[3].GetString();
7001 got.IconName = fields[4].GetString();
7002 got.castBarCaption = fields[5].GetString();
7003 got.unk1 = fields[6].GetString();
7004 got.size = fields[7].GetFloat();
7005
7006 for (uint8 i = 0; i < MAX_GAMEOBJECT_DATA; ++i)
7007 got.raw.data[i] = fields[8 + i].GetInt32(); // data1 and data6 can be -1
7008
7009 got.AIName = fields[32].GetString();
7010 got.ScriptId = GetScriptId(fields[33].GetString());
7011
7012 // Checks
7013 if (!got.AIName.empty() && !sGameObjectAIRegistry->HasItem(got.AIName))
7014 {
7015 TC_LOG_ERROR("sql.sql", "GameObject (Entry: %u) has non-registered `AIName` '%s' set, removing", got.entry, got.AIName.c_str());
7016 got.AIName.clear();
7017 }
7018
7019 switch (got.type)
7020 {
7021 case GAMEOBJECT_TYPE_DOOR: //0
7022 {
7023 if (got.door.lockId)
7024 CheckGOLockId(&got, got.door.lockId, 1);
7025 CheckGONoDamageImmuneId(&got, got.door.noDamageImmune, 3);
7026 break;
7027 }
7028 case GAMEOBJECT_TYPE_BUTTON: //1
7029 {
7030 if (got.button.lockId)
7031 CheckGOLockId(&got, got.button.lockId, 1);
7032 CheckGONoDamageImmuneId(&got, got.button.noDamageImmune, 4);
7033 break;
7034 }
7035 case GAMEOBJECT_TYPE_QUESTGIVER: //2
7036 {
7037 if (got.questgiver.lockId)
7038 CheckGOLockId(&got, got.questgiver.lockId, 0);
7039 CheckGONoDamageImmuneId(&got, got.questgiver.noDamageImmune, 5);
7040 break;
7041 }
7042 case GAMEOBJECT_TYPE_CHEST: //3
7043 {
7044 if (got.chest.lockId)
7045 CheckGOLockId(&got, got.chest.lockId, 0);
7046
7047 CheckGOConsumable(&got, got.chest.consumable, 3);
7048
7049 if (got.chest.linkedTrapId) // linked trap
7050 CheckGOLinkedTrapId(&got, got.chest.linkedTrapId, 7);
7051 break;
7052 }
7053 case GAMEOBJECT_TYPE_TRAP: //6
7054 {
7055 if (got.trap.lockId)
7056 CheckGOLockId(&got, got.trap.lockId, 0);
7057 break;
7058 }
7059 case GAMEOBJECT_TYPE_CHAIR: //7
7060 CheckAndFixGOChairHeightId(&got, got.chair.height, 1);
7061 break;
7062 case GAMEOBJECT_TYPE_SPELL_FOCUS: //8
7063 {
7064 if (got.spellFocus.focusId)
7065 {
7066 if (!sSpellFocusObjectStore.LookupEntry(got.spellFocus.focusId))
7067 TC_LOG_ERROR("sql.sql", "GameObject (Entry: %u GoType: %u) have data0=%u but SpellFocus (Id: %u) not exist.",
7068 entry, got.type, got.spellFocus.focusId, got.spellFocus.focusId);
7069 }
7070
7071 if (got.spellFocus.linkedTrapId) // linked trap
7072 CheckGOLinkedTrapId(&got, got.spellFocus.linkedTrapId, 2);
7073 break;
7074 }
7075 case GAMEOBJECT_TYPE_GOOBER: //10
7076 {
7077 if (got.goober.lockId)
7078 CheckGOLockId(&got, got.goober.lockId, 0);
7079
7080 CheckGOConsumable(&got, got.goober.consumable, 3);
7081
7082 if (got.goober.pageId) // pageId
7083 {
7084 if (!GetPageText(got.goober.pageId))
7085 TC_LOG_ERROR("sql.sql", "GameObject (Entry: %u GoType: %u) have data7=%u but PageText (Entry %u) not exist.",
7086 entry, got.type, got.goober.pageId, got.goober.pageId);
7087 }
7088 CheckGONoDamageImmuneId(&got, got.goober.noDamageImmune, 11);
7089 if (got.goober.linkedTrapId) // linked trap
7090 CheckGOLinkedTrapId(&got, got.goober.linkedTrapId, 12);
7091 break;
7092 }
7093 case GAMEOBJECT_TYPE_AREADAMAGE: //12
7094 {
7095 if (got.areadamage.lockId)
7096 CheckGOLockId(&got, got.areadamage.lockId, 0);
7097 break;
7098 }
7099 case GAMEOBJECT_TYPE_CAMERA: //13
7100 {
7101 if (got.camera.lockId)
7102 CheckGOLockId(&got, got.camera.lockId, 0);
7103 break;
7104 }
7105 case GAMEOBJECT_TYPE_MO_TRANSPORT: //15
7106 {
7107 if (got.moTransport.taxiPathId)
7108 {
7109 if (got.moTransport.taxiPathId >= sTaxiPathNodesByPath.size() || sTaxiPathNodesByPath[got.moTransport.taxiPathId].empty())
7110 TC_LOG_ERROR("sql.sql", "GameObject (Entry: %u GoType: %u) have data0=%u but TaxiPath (Id: %u) not exist.",
7111 entry, got.type, got.moTransport.taxiPathId, got.moTransport.taxiPathId);
7112 }
7113 if (uint32 transportMap = got.moTransport.mapID)
7114 _transportMaps.insert(transportMap);
7115 break;
7116 }
7117 case GAMEOBJECT_TYPE_SUMMONING_RITUAL: //18
7118 break;
7119 case GAMEOBJECT_TYPE_SPELLCASTER: //22
7120 {
7121 // always must have spell
7122 CheckGOSpellId(&got, got.spellcaster.spellId, 0);
7123 break;
7124 }
7125 case GAMEOBJECT_TYPE_FLAGSTAND: //24
7126 {
7127 if (got.flagstand.lockId)
7128 CheckGOLockId(&got, got.flagstand.lockId, 0);
7129 CheckGONoDamageImmuneId(&got, got.flagstand.noDamageImmune, 5);
7130 break;
7131 }
7132 case GAMEOBJECT_TYPE_FISHINGHOLE: //25
7133 {
7134 if (got.fishinghole.lockId)
7135 CheckGOLockId(&got, got.fishinghole.lockId, 4);
7136 break;
7137 }
7138 case GAMEOBJECT_TYPE_FLAGDROP: //26
7139 {
7140 if (got.flagdrop.lockId)
7141 CheckGOLockId(&got, got.flagdrop.lockId, 0);
7142 CheckGONoDamageImmuneId(&got, got.flagdrop.noDamageImmune, 3);
7143 break;
7144 }
7145 case GAMEOBJECT_TYPE_BARBER_CHAIR: //32
7146 CheckAndFixGOChairHeightId(&got, got.barberChair.chairheight, 0);
7147 break;
7148 }
7149 } while (result->NextRow());
7150
7151 TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " game object templates in %u ms", _gameObjectTemplateStore.size(), GetMSTimeDiffToNow(oldMSTime));
7152}
7153
7154void ObjectMgr::LoadGameObjectTemplateAddons()
7155{
7156 uint32 oldMSTime = getMSTime();
7157
7158 // 0 1 2 3 4
7159 QueryResult result = WorldDatabase.Query("SELECT entry, faction, flags, mingold, maxgold FROM gameobject_template_addon");
7160
7161 if (!result)
7162 {
7163 TC_LOG_INFO("server.loading", ">> Loaded 0 gameobject template addon definitions. DB table `gameobject_template_addon` is empty.");
7164 return;
7165 }
7166
7167 uint32 count = 0;
7168 do
7169 {
7170 Field* fields = result->Fetch();
7171
7172 uint32 entry = fields[0].GetUInt32();
7173
7174 GameObjectTemplate const* got = sObjectMgr->GetGameObjectTemplate(entry);
7175 if (!got)
7176 {
7177 TC_LOG_ERROR("sql.sql", "GameObject template (Entry: %u) does not exist but has a record in `gameobject_template_addon`", entry);
7178 continue;
7179 }
7180
7181 GameObjectTemplateAddon& gameObjectAddon = _gameObjectTemplateAddonStore[entry];
7182 gameObjectAddon.faction = uint32(fields[1].GetUInt16());
7183 gameObjectAddon.flags = fields[2].GetUInt32();
7184 gameObjectAddon.mingold = fields[3].GetUInt32();
7185 gameObjectAddon.maxgold = fields[4].GetUInt32();
7186
7187 // checks
7188 if (gameObjectAddon.faction && !sFactionTemplateStore.LookupEntry(gameObjectAddon.faction))
7189 TC_LOG_ERROR("sql.sql", "GameObject (Entry: %u) has invalid faction (%u) defined in `gameobject_template_addon`.", entry, gameObjectAddon.faction);
7190
7191 if (gameObjectAddon.maxgold > 0)
7192 {
7193 switch (got->type)
7194 {
7195 case GAMEOBJECT_TYPE_CHEST:
7196 case GAMEOBJECT_TYPE_FISHINGHOLE:
7197 break;
7198 default:
7199 TC_LOG_ERROR("sql.sql", "GameObject (Entry %u GoType: %u) cannot be looted but has maxgold set in `gameobject_template_addon`.", entry, got->type);
7200 break;
7201 }
7202 }
7203
7204 ++count;
7205 }
7206 while (result->NextRow());
7207
7208 TC_LOG_INFO("server.loading", ">> Loaded %u game object template addons in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
7209}
7210
7211void ObjectMgr::LoadExplorationBaseXP()
7212{
7213 uint32 oldMSTime = getMSTime();
7214
7215 QueryResult result = WorldDatabase.Query("SELECT level, basexp FROM exploration_basexp");
7216
7217 if (!result)
7218 {
7219 TC_LOG_INFO("server.loading", ">> Loaded 0 BaseXP definitions. DB table `exploration_basexp` is empty.");
7220 return;
7221 }
7222
7223 uint32 count = 0;
7224
7225 do
7226 {
7227 Field* fields = result->Fetch();
7228 uint8 level = fields[0].GetUInt8();
7229 uint32 basexp = fields[1].GetInt32();
7230 _baseXPTable[level] = basexp;
7231 ++count;
7232 }
7233 while (result->NextRow());
7234
7235 TC_LOG_INFO("server.loading", ">> Loaded %u BaseXP definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
7236}
7237
7238uint32 ObjectMgr::GetBaseXP(uint8 level)
7239{
7240 return _baseXPTable[level] ? _baseXPTable[level] : 0;
7241}
7242
7243uint32 ObjectMgr::GetXPForLevel(uint8 level) const
7244{
7245 if (level < _playerXPperLevel.size())
7246 return _playerXPperLevel[level];
7247 return 0;
7248}
7249
7250void ObjectMgr::LoadPetNames()
7251{
7252 uint32 oldMSTime = getMSTime();
7253 // 0 1 2
7254 QueryResult result = WorldDatabase.Query("SELECT word, entry, half FROM pet_name_generation");
7255
7256 if (!result)
7257 {
7258 TC_LOG_INFO("server.loading", ">> Loaded 0 pet name parts. DB table `pet_name_generation` is empty!");
7259 return;
7260 }
7261
7262 uint32 count = 0;
7263
7264 do
7265 {
7266 Field* fields = result->Fetch();
7267 std::string word = fields[0].GetString();
7268 uint32 entry = fields[1].GetUInt32();
7269 bool half = fields[2].GetBool();
7270 if (half)
7271 _petHalfName1[entry].push_back(word);
7272 else
7273 _petHalfName0[entry].push_back(word);
7274 ++count;
7275 }
7276 while (result->NextRow());
7277
7278 TC_LOG_INFO("server.loading", ">> Loaded %u pet name parts in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
7279}
7280
7281void ObjectMgr::LoadPetNumber()
7282{
7283 uint32 oldMSTime = getMSTime();
7284
7285 QueryResult result = CharacterDatabase.Query("SELECT MAX(id) FROM character_pet");
7286 if (result)
7287 {
7288 Field* fields = result->Fetch();
7289 _hiPetNumber = fields[0].GetUInt32()+1;
7290 }
7291
7292 TC_LOG_INFO("server.loading", ">> Loaded the max pet number: %d in %u ms", _hiPetNumber-1, GetMSTimeDiffToNow(oldMSTime));
7293}
7294
7295std::string ObjectMgr::GeneratePetName(uint32 entry)
7296{
7297 std::vector<std::string>& list0 = _petHalfName0[entry];
7298 std::vector<std::string>& list1 = _petHalfName1[entry];
7299
7300 if (list0.empty() || list1.empty())
7301 {
7302 CreatureTemplate const* cinfo = GetCreatureTemplate(entry);
7303 if (!cinfo)
7304 return std::string();
7305
7306 char* petname = GetPetName(cinfo->family, sWorld->GetDefaultDbcLocale());
7307 if (petname)
7308 return std::string(petname);
7309 else
7310 return cinfo->Name;
7311 }
7312
7313 return *(list0.begin()+urand(0, list0.size()-1)) + *(list1.begin()+urand(0, list1.size()-1));
7314}
7315
7316void ObjectMgr::LoadReputationRewardRate()
7317{
7318 uint32 oldMSTime = getMSTime();
7319
7320 _repRewardRateStore.clear(); // for reload case
7321
7322 uint32 count = 0; // 0 1 2 3 4 5 6 7
7323 QueryResult result = WorldDatabase.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");
7324 if (!result)
7325 {
7326 TC_LOG_INFO("server.loading", ">> Loaded `reputation_reward_rate`, table is empty!");
7327 return;
7328 }
7329
7330 do
7331 {
7332 Field* fields = result->Fetch();
7333
7334 uint32 factionId = fields[0].GetUInt32();
7335
7336 RepRewardRate repRate;
7337
7338 repRate.questRate = fields[1].GetFloat();
7339 repRate.questDailyRate = fields[2].GetFloat();
7340 repRate.questWeeklyRate = fields[3].GetFloat();
7341 repRate.questMonthlyRate = fields[4].GetFloat();
7342 repRate.questRepeatableRate = fields[5].GetFloat();
7343 repRate.creatureRate = fields[6].GetFloat();
7344 repRate.spellRate = fields[7].GetFloat();
7345
7346 FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionId);
7347 if (!factionEntry)
7348 {
7349 TC_LOG_ERROR("sql.sql", "Faction (faction.dbc) %u does not exist but is used in `reputation_reward_rate`", factionId);
7350 continue;
7351 }
7352
7353 if (repRate.questRate < 0.0f)
7354 {
7355 TC_LOG_ERROR("sql.sql", "Table reputation_reward_rate has quest_rate with invalid rate %f, skipping data for faction %u", repRate.questRate, factionId);
7356 continue;
7357 }
7358
7359 if (repRate.questDailyRate < 0.0f)
7360 {
7361 TC_LOG_ERROR("sql.sql", "Table reputation_reward_rate has quest_daily_rate with invalid rate %f, skipping data for faction %u", repRate.questDailyRate, factionId);
7362 continue;
7363 }
7364
7365 if (repRate.questWeeklyRate < 0.0f)
7366 {
7367 TC_LOG_ERROR("sql.sql", "Table reputation_reward_rate has quest_weekly_rate with invalid rate %f, skipping data for faction %u", repRate.questWeeklyRate, factionId);
7368 continue;
7369 }
7370
7371 if (repRate.questMonthlyRate < 0.0f)
7372 {
7373 TC_LOG_ERROR("sql.sql", "Table reputation_reward_rate has quest_monthly_rate with invalid rate %f, skipping data for faction %u", repRate.questMonthlyRate, factionId);
7374 continue;
7375 }
7376
7377 if (repRate.questRepeatableRate < 0.0f)
7378 {
7379 TC_LOG_ERROR("sql.sql", "Table reputation_reward_rate has quest_repeatable_rate with invalid rate %f, skipping data for faction %u", repRate.questRepeatableRate, factionId);
7380 continue;
7381 }
7382
7383 if (repRate.creatureRate < 0.0f)
7384 {
7385 TC_LOG_ERROR("sql.sql", "Table reputation_reward_rate has creature_rate with invalid rate %f, skipping data for faction %u", repRate.creatureRate, factionId);
7386 continue;
7387 }
7388
7389 if (repRate.spellRate < 0.0f)
7390 {
7391 TC_LOG_ERROR("sql.sql", "Table reputation_reward_rate has spell_rate with invalid rate %f, skipping data for faction %u", repRate.spellRate, factionId);
7392 continue;
7393 }
7394
7395 _repRewardRateStore[factionId] = repRate;
7396
7397 ++count;
7398 }
7399 while (result->NextRow());
7400
7401 TC_LOG_INFO("server.loading", ">> Loaded %u reputation_reward_rate in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
7402}
7403
7404void ObjectMgr::LoadReputationOnKill()
7405{
7406 uint32 oldMSTime = getMSTime();
7407
7408 // For reload case
7409 _repOnKillStore.clear();
7410
7411 uint32 count = 0;
7412
7413 // 0 1 2
7414 QueryResult result = WorldDatabase.Query("SELECT creature_id, RewOnKillRepFaction1, RewOnKillRepFaction2, "
7415 // 3 4 5 6 7 8 9
7416 "IsTeamAward1, MaxStanding1, RewOnKillRepValue1, IsTeamAward2, MaxStanding2, RewOnKillRepValue2, TeamDependent "
7417 "FROM creature_onkill_reputation");
7418
7419 if (!result)
7420 {
7421 TC_LOG_INFO("server.loading", ">> Loaded 0 creature award reputation definitions. DB table `creature_onkill_reputation` is empty.");
7422 return;
7423 }
7424
7425 do
7426 {
7427 Field* fields = result->Fetch();
7428
7429 uint32 creature_id = fields[0].GetUInt32();
7430
7431 ReputationOnKillEntry repOnKill;
7432 repOnKill.RepFaction1 = fields[1].GetInt16();
7433 repOnKill.RepFaction2 = fields[2].GetInt16();
7434 repOnKill.IsTeamAward1 = fields[3].GetBool();
7435 repOnKill.ReputationMaxCap1 = fields[4].GetUInt8();
7436 repOnKill.RepValue1 = fields[5].GetInt32();
7437 repOnKill.IsTeamAward2 = fields[6].GetBool();
7438 repOnKill.ReputationMaxCap2 = fields[7].GetUInt8();
7439 repOnKill.RepValue2 = fields[8].GetInt32();
7440 repOnKill.TeamDependent = fields[9].GetBool();
7441
7442 if (!GetCreatureTemplate(creature_id))
7443 {
7444 TC_LOG_ERROR("sql.sql", "Table `creature_onkill_reputation` has data for nonexistent creature entry (%u), skipped", creature_id);
7445 continue;
7446 }
7447
7448 if (repOnKill.RepFaction1)
7449 {
7450 FactionEntry const* factionEntry1 = sFactionStore.LookupEntry(repOnKill.RepFaction1);
7451 if (!factionEntry1)
7452 {
7453 TC_LOG_ERROR("sql.sql", "Faction (faction.dbc) %u does not exist but is used in `creature_onkill_reputation`", repOnKill.RepFaction1);
7454 continue;
7455 }
7456 }
7457
7458 if (repOnKill.RepFaction2)
7459 {
7460 FactionEntry const* factionEntry2 = sFactionStore.LookupEntry(repOnKill.RepFaction2);
7461 if (!factionEntry2)
7462 {
7463 TC_LOG_ERROR("sql.sql", "Faction (faction.dbc) %u does not exist but is used in `creature_onkill_reputation`", repOnKill.RepFaction2);
7464 continue;
7465 }
7466 }
7467
7468 _repOnKillStore[creature_id] = repOnKill;
7469
7470 ++count;
7471 } while (result->NextRow());
7472
7473 TC_LOG_INFO("server.loading", ">> Loaded %u creature award reputation definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
7474}
7475
7476void ObjectMgr::LoadReputationSpilloverTemplate()
7477{
7478 uint32 oldMSTime = getMSTime();
7479
7480 _repSpilloverTemplateStore.clear(); // for reload case
7481
7482 uint32 count = 0; // 0 1 2 3 4 5 6 7 8 9 10 11 12
7483 QueryResult result = WorldDatabase.Query("SELECT faction, faction1, rate_1, rank_1, faction2, rate_2, rank_2, faction3, rate_3, rank_3, faction4, rate_4, rank_4 FROM reputation_spillover_template");
7484
7485 if (!result)
7486 {
7487 TC_LOG_INFO("server.loading", ">> Loaded `reputation_spillover_template`, table is empty.");
7488 return;
7489 }
7490
7491 do
7492 {
7493 Field* fields = result->Fetch();
7494
7495 uint32 factionId = fields[0].GetUInt16();
7496
7497 RepSpilloverTemplate repTemplate;
7498
7499 repTemplate.faction[0] = fields[1].GetUInt16();
7500 repTemplate.faction_rate[0] = fields[2].GetFloat();
7501 repTemplate.faction_rank[0] = fields[3].GetUInt8();
7502 repTemplate.faction[1] = fields[4].GetUInt16();
7503 repTemplate.faction_rate[1] = fields[5].GetFloat();
7504 repTemplate.faction_rank[1] = fields[6].GetUInt8();
7505 repTemplate.faction[2] = fields[7].GetUInt16();
7506 repTemplate.faction_rate[2] = fields[8].GetFloat();
7507 repTemplate.faction_rank[2] = fields[9].GetUInt8();
7508 repTemplate.faction[3] = fields[10].GetUInt16();
7509 repTemplate.faction_rate[3] = fields[11].GetFloat();
7510 repTemplate.faction_rank[3] = fields[12].GetUInt8();
7511
7512 FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionId);
7513
7514 if (!factionEntry)
7515 {
7516 TC_LOG_ERROR("sql.sql", "Faction (faction.dbc) %u does not exist but is used in `reputation_spillover_template`", factionId);
7517 continue;
7518 }
7519
7520 if (factionEntry->team == 0)
7521 {
7522 TC_LOG_ERROR("sql.sql", "Faction (faction.dbc) %u in `reputation_spillover_template` does not belong to any team, skipping", factionId);
7523 continue;
7524 }
7525
7526 bool invalidSpilloverFaction = false;
7527 for (uint32 i = 0; i < MAX_SPILLOVER_FACTIONS; ++i)
7528 {
7529 if (repTemplate.faction[i])
7530 {
7531 FactionEntry const* factionSpillover = sFactionStore.LookupEntry(repTemplate.faction[i]);
7532
7533 if (!factionSpillover)
7534 {
7535 TC_LOG_ERROR("sql.sql", "Spillover faction (faction.dbc) %u does not exist but is used in `reputation_spillover_template` for faction %u, skipping", repTemplate.faction[i], factionId);
7536 invalidSpilloverFaction = true;
7537 break;
7538 }
7539
7540 if (factionSpillover->reputationListID < 0)
7541 {
7542 TC_LOG_ERROR("sql.sql", "Spillover faction (faction.dbc) %u for faction %u in `reputation_spillover_template` can not be listed for client, and then useless, skipping", repTemplate.faction[i], factionId);
7543 invalidSpilloverFaction = true;
7544 break;
7545 }
7546
7547 if (repTemplate.faction_rank[i] >= MAX_REPUTATION_RANK)
7548 {
7549 TC_LOG_ERROR("sql.sql", "Rank %u used in `reputation_spillover_template` for spillover faction %u is not valid, skipping", repTemplate.faction_rank[i], repTemplate.faction[i]);
7550 invalidSpilloverFaction = true;
7551 break;
7552 }
7553 }
7554 }
7555
7556 if (invalidSpilloverFaction)
7557 continue;
7558
7559 _repSpilloverTemplateStore[factionId] = repTemplate;
7560
7561 ++count;
7562 }
7563 while (result->NextRow());
7564
7565 TC_LOG_INFO("server.loading", ">> Loaded %u reputation_spillover_template in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
7566}
7567
7568void ObjectMgr::LoadPointsOfInterest()
7569{
7570 uint32 oldMSTime = getMSTime();
7571
7572 _pointsOfInterestStore.clear(); // need for reload case
7573
7574 uint32 count = 0;
7575
7576 // 0 1 2 3 4 5 6
7577 QueryResult result = WorldDatabase.Query("SELECT ID, PositionX, PositionY, Icon, Flags, Importance, Name FROM points_of_interest");
7578
7579 if (!result)
7580 {
7581 TC_LOG_INFO("server.loading", ">> Loaded 0 Points of Interest definitions. DB table `points_of_interest` is empty.");
7582 return;
7583 }
7584
7585 do
7586 {
7587 Field* fields = result->Fetch();
7588
7589 uint32 id = fields[0].GetUInt32();
7590
7591 PointOfInterest pointOfInterest;
7592 pointOfInterest.ID = id;
7593 pointOfInterest.PositionX = fields[1].GetFloat();
7594 pointOfInterest.PositionY = fields[2].GetFloat();
7595 pointOfInterest.Icon = fields[3].GetUInt32();
7596 pointOfInterest.Flags = fields[4].GetUInt32();
7597 pointOfInterest.Importance = fields[5].GetUInt32();
7598 pointOfInterest.Name = fields[6].GetString();
7599
7600 if (!Trinity::IsValidMapCoord(pointOfInterest.PositionX, pointOfInterest.PositionY))
7601 {
7602 TC_LOG_ERROR("sql.sql", "Table `points_of_interest` (ID: %u) have invalid coordinates (X: %f Y: %f), ignored.", id, pointOfInterest.PositionX, pointOfInterest.PositionY);
7603 continue;
7604 }
7605
7606 _pointsOfInterestStore[id] = pointOfInterest;
7607
7608 ++count;
7609 } while (result->NextRow());
7610
7611 TC_LOG_INFO("server.loading", ">> Loaded %u Points of Interest definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
7612}
7613
7614void ObjectMgr::LoadQuestPOI()
7615{
7616 uint32 oldMSTime = getMSTime();
7617
7618 _questPOIStore.clear(); // need for reload case
7619
7620 // 0 1 2 3 4 5 6 7
7621 QueryResult result = WorldDatabase.Query("SELECT QuestID, id, ObjectiveIndex, MapID, WorldMapAreaId, Floor, Priority, Flags FROM quest_poi");
7622 if (!result)
7623 {
7624 TC_LOG_INFO("server.loading", ">> Loaded 0 quest POI definitions. DB table `quest_poi` is empty.");
7625 return;
7626 }
7627
7628 _questPOIStore.reserve(result->GetRowCount());
7629
7630 // 0 1 2 3
7631 QueryResult points = WorldDatabase.Query("SELECT QuestID, Idx1, X, Y FROM quest_poi_points ORDER BY QuestID DESC, Idx2");
7632
7633 std::vector<std::vector<std::vector<QuestPOIBlobPoint>>> POIs;
7634 if (points)
7635 {
7636 // The first result should have the highest questId
7637 Field* fields = points->Fetch();
7638 uint32 const maxQuestPOIId = fields[0].GetUInt32();
7639 POIs.resize(maxQuestPOIId + 1);
7640
7641 do
7642 {
7643 fields = points->Fetch();
7644
7645 uint32 questId = fields[0].GetUInt32();
7646 uint32 id = fields[1].GetUInt32();
7647 int32 x = fields[2].GetInt32();
7648 int32 y = fields[3].GetInt32();
7649
7650 if (POIs[questId].size() <= id + 1)
7651 POIs[questId].resize(id + 10);
7652
7653 QuestPOIBlobPoint point;
7654 point.X = x;
7655 point.Y = y;
7656
7657 POIs[questId][id].push_back(point);
7658 } while (points->NextRow());
7659 }
7660
7661 do
7662 {
7663 Field* fields = result->Fetch();
7664
7665 uint32 questId = fields[0].GetUInt32();
7666 uint32 id = fields[1].GetUInt32();
7667 int32 objIndex = fields[2].GetInt32();
7668 uint32 mapId = fields[3].GetUInt32();
7669 uint32 WorldMapAreaId = fields[4].GetUInt32();
7670 uint32 FloorId = fields[5].GetUInt32();
7671 uint32 unk3 = fields[6].GetUInt32();
7672 uint32 unk4 = fields[7].GetUInt32();
7673
7674 QuestPOIBlobData POI;
7675 POI.BlobIndex = id;
7676 POI.ObjectiveIndex = objIndex;
7677 POI.MapID = mapId;
7678 POI.WorldMapAreaID = WorldMapAreaId;
7679 POI.Floor = FloorId;
7680 POI.Unk3 = unk3;
7681 POI.Unk4 = unk4;
7682
7683 if (questId < POIs.size() && id < POIs[questId].size())
7684 {
7685 POI.QuestPOIBlobPointStats = POIs[questId][id];
7686
7687 auto itr = _questPOIStore.find(questId);
7688 if (itr == _questPOIStore.end())
7689 {
7690 QuestPOIWrapper wrapper;
7691 QuestPOIData data;
7692 data.QuestID = questId;
7693 wrapper.POIData = data;
7694
7695 std::tie(itr, std::ignore) = _questPOIStore.emplace(questId, std::move(wrapper));
7696 }
7697
7698 itr->second.POIData.QuestPOIBlobDataStats.push_back(POI);
7699 }
7700 else
7701 TC_LOG_ERROR("sql.sql", "Table quest_poi references unknown quest points for quest %u POI id %u", questId, id);
7702 } while (result->NextRow());
7703
7704 TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " quest POI definitions in %u ms", _questPOIStore.size(), GetMSTimeDiffToNow(oldMSTime));
7705}
7706
7707void ObjectMgr::LoadNPCSpellClickSpells()
7708{
7709 uint32 oldMSTime = getMSTime();
7710
7711 _spellClickInfoStore.clear();
7712 // 0 1 2 3
7713 QueryResult result = WorldDatabase.Query("SELECT npc_entry, spell_id, cast_flags, user_type FROM npc_spellclick_spells");
7714
7715 if (!result)
7716 {
7717 TC_LOG_INFO("server.loading", ">> Loaded 0 spellclick spells. DB table `npc_spellclick_spells` is empty.");
7718 return;
7719 }
7720
7721 uint32 count = 0;
7722
7723 do
7724 {
7725 Field* fields = result->Fetch();
7726
7727 uint32 npc_entry = fields[0].GetUInt32();
7728 CreatureTemplate const* cInfo = GetCreatureTemplate(npc_entry);
7729 if (!cInfo)
7730 {
7731 TC_LOG_ERROR("sql.sql", "Table npc_spellclick_spells references unknown creature_template %u. Skipping entry.", npc_entry);
7732 continue;
7733 }
7734
7735 uint32 spellid = fields[1].GetUInt32();
7736 SpellInfo const* spellinfo = sSpellMgr->GetSpellInfo(spellid);
7737 if (!spellinfo)
7738 {
7739 TC_LOG_ERROR("sql.sql", "Table npc_spellclick_spells creature: %u references unknown spellid %u. Skipping entry.", npc_entry, spellid);
7740 continue;
7741 }
7742
7743 uint8 userType = fields[3].GetUInt16();
7744 if (userType >= SPELL_CLICK_USER_MAX)
7745 TC_LOG_ERROR("sql.sql", "Table npc_spellclick_spells creature: %u references unknown user type %u. Skipping entry.", npc_entry, uint32(userType));
7746
7747 uint8 castFlags = fields[2].GetUInt8();
7748 SpellClickInfo info;
7749 info.spellId = spellid;
7750 info.castFlags = castFlags;
7751 info.userType = SpellClickUserTypes(userType);
7752 _spellClickInfoStore.insert(SpellClickInfoContainer::value_type(npc_entry, info));
7753
7754 ++count;
7755 }
7756 while (result->NextRow());
7757
7758 // all spellclick data loaded, now we check if there are creatures with NPC_FLAG_SPELLCLICK but with no data
7759 // NOTE: It *CAN* be the other way around: no spellclick flag but with spellclick data, in case of creature-only vehicle accessories
7760 for (auto& creatureTemplatePair : _creatureTemplateStore)
7761 {
7762 if ((creatureTemplatePair.second.npcflag & UNIT_NPC_FLAG_SPELLCLICK) && !_spellClickInfoStore.count(creatureTemplatePair.first))
7763 {
7764 TC_LOG_ERROR("sql.sql", "npc_spellclick_spells: Creature template %u has UNIT_NPC_FLAG_SPELLCLICK but no data in spellclick table! Removing flag", creatureTemplatePair.first);
7765 creatureTemplatePair.second.npcflag &= ~UNIT_NPC_FLAG_SPELLCLICK;
7766 }
7767 }
7768
7769 TC_LOG_INFO("server.loading", ">> Loaded %u spellclick definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
7770}
7771
7772void ObjectMgr::DeleteCreatureData(ObjectGuid::LowType guid)
7773{
7774 // remove mapid*cellid -> guid_set map
7775 CreatureData const* data = GetCreatureData(guid);
7776 if (data)
7777 {
7778 RemoveCreatureFromGrid(guid, data);
7779 OnDeleteSpawnData(data);
7780 }
7781
7782 _creatureDataStore.erase(guid);
7783}
7784
7785void ObjectMgr::DeleteGameObjectData(ObjectGuid::LowType guid)
7786{
7787 // remove mapid*cellid -> guid_set map
7788 GameObjectData const* data = GetGameObjectData(guid);
7789 if (data)
7790 {
7791 RemoveGameobjectFromGrid(guid, data);
7792 OnDeleteSpawnData(data);
7793 }
7794
7795 _gameObjectDataStore.erase(guid);
7796}
7797
7798void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map, QuestRelationsReverse* reverseMap, std::string const& table, bool starter, bool go)
7799{
7800 uint32 oldMSTime = getMSTime();
7801
7802 map.clear(); // need for reload case
7803
7804 uint32 count = 0;
7805
7806 QueryResult result = WorldDatabase.PQuery("SELECT id, quest, pool_entry FROM %s qr LEFT JOIN pool_quest pq ON qr.quest = pq.entry", table.c_str());
7807
7808 if (!result)
7809 {
7810 TC_LOG_INFO("server.loading", ">> Loaded 0 quest relations from `%s`, table is empty.", table.c_str());
7811 return;
7812 }
7813
7814 PooledQuestRelation* poolRelationMap = go ? &sPoolMgr->mQuestGORelation : &sPoolMgr->mQuestCreatureRelation;
7815 if (starter)
7816 poolRelationMap->clear();
7817
7818 do
7819 {
7820 uint32 id = result->Fetch()[0].GetUInt32();
7821 uint32 quest = result->Fetch()[1].GetUInt32();
7822 uint32 poolId = result->Fetch()[2].GetUInt32();
7823
7824 if (!_questTemplates.count(quest))
7825 {
7826 TC_LOG_ERROR("sql.sql", "Table `%s`: Quest %u listed for entry %u does not exist.", table.c_str(), quest, id);
7827 continue;
7828 }
7829
7830 if (!poolId || !starter)
7831 {
7832 map.insert(QuestRelations::value_type(id, quest));
7833 if (reverseMap)
7834 reverseMap->insert(QuestRelationsReverse::value_type(quest, id));
7835 }
7836 else
7837 poolRelationMap->insert(PooledQuestRelation::value_type(quest, id));
7838
7839 ++count;
7840 } while (result->NextRow());
7841
7842 TC_LOG_INFO("server.loading", ">> Loaded %u quest relations from %s in %u ms", count, table.c_str(), GetMSTimeDiffToNow(oldMSTime));
7843}
7844
7845void ObjectMgr::LoadGameobjectQuestStarters()
7846{
7847 LoadQuestRelationsHelper(_goQuestRelations, nullptr, "gameobject_queststarter", true, true);
7848
7849 for (QuestRelations::iterator itr = _goQuestRelations.begin(); itr != _goQuestRelations.end(); ++itr)
7850 {
7851 GameObjectTemplate const* goInfo = GetGameObjectTemplate(itr->first);
7852 if (!goInfo)
7853 TC_LOG_ERROR("sql.sql", "Table `gameobject_queststarter` has data for nonexistent gameobject entry (%u) and existed quest %u", itr->first, itr->second);
7854 else if (goInfo->type != GAMEOBJECT_TYPE_QUESTGIVER)
7855 TC_LOG_ERROR("sql.sql", "Table `gameobject_queststarter` has data gameobject entry (%u) for quest %u, but GO is not GAMEOBJECT_TYPE_QUESTGIVER", itr->first, itr->second);
7856 }
7857}
7858
7859void ObjectMgr::LoadGameobjectQuestEnders()
7860{
7861 LoadQuestRelationsHelper(_goQuestInvolvedRelations, &_goQuestInvolvedRelationsReverse, "gameobject_questender", false, true);
7862
7863 for (QuestRelations::iterator itr = _goQuestInvolvedRelations.begin(); itr != _goQuestInvolvedRelations.end(); ++itr)
7864 {
7865 GameObjectTemplate const* goInfo = GetGameObjectTemplate(itr->first);
7866 if (!goInfo)
7867 TC_LOG_ERROR("sql.sql", "Table `gameobject_questender` has data for nonexistent gameobject entry (%u) and existed quest %u", itr->first, itr->second);
7868 else if (goInfo->type != GAMEOBJECT_TYPE_QUESTGIVER)
7869 TC_LOG_ERROR("sql.sql", "Table `gameobject_questender` has data gameobject entry (%u) for quest %u, but GO is not GAMEOBJECT_TYPE_QUESTGIVER", itr->first, itr->second);
7870 }
7871}
7872
7873void ObjectMgr::LoadCreatureQuestStarters()
7874{
7875 LoadQuestRelationsHelper(_creatureQuestRelations, nullptr, "creature_queststarter", true, false);
7876
7877 for (QuestRelations::iterator itr = _creatureQuestRelations.begin(); itr != _creatureQuestRelations.end(); ++itr)
7878 {
7879 CreatureTemplate const* cInfo = GetCreatureTemplate(itr->first);
7880 if (!cInfo)
7881 TC_LOG_ERROR("sql.sql", "Table `creature_queststarter` has data for nonexistent creature entry (%u) and existed quest %u", itr->first, itr->second);
7882 else if (!(cInfo->npcflag & UNIT_NPC_FLAG_QUESTGIVER))
7883 TC_LOG_ERROR("sql.sql", "Table `creature_queststarter` has creature entry (%u) for quest %u, but npcflag does not include UNIT_NPC_FLAG_QUESTGIVER", itr->first, itr->second);
7884 }
7885}
7886
7887void ObjectMgr::LoadCreatureQuestEnders()
7888{
7889 LoadQuestRelationsHelper(_creatureQuestInvolvedRelations, &_creatureQuestInvolvedRelationsReverse, "creature_questender", false, false);
7890
7891 for (QuestRelations::iterator itr = _creatureQuestInvolvedRelations.begin(); itr != _creatureQuestInvolvedRelations.end(); ++itr)
7892 {
7893 CreatureTemplate const* cInfo = GetCreatureTemplate(itr->first);
7894 if (!cInfo)
7895 TC_LOG_ERROR("sql.sql", "Table `creature_questender` has data for nonexistent creature entry (%u) and existed quest %u", itr->first, itr->second);
7896 else if (!(cInfo->npcflag & UNIT_NPC_FLAG_QUESTGIVER))
7897 TC_LOG_ERROR("sql.sql", "Table `creature_questender` has creature entry (%u) for quest %u, but npcflag does not include UNIT_NPC_FLAG_QUESTGIVER", itr->first, itr->second);
7898 }
7899}
7900
7901void ObjectMgr::LoadReservedPlayersNames()
7902{
7903 uint32 oldMSTime = getMSTime();
7904
7905 _reservedNamesStore.clear(); // need for reload case
7906
7907 QueryResult result = CharacterDatabase.Query("SELECT name FROM reserved_name");
7908
7909 if (!result)
7910 {
7911 TC_LOG_INFO("server.loading", ">> Loaded 0 reserved player names. DB table `reserved_name` is empty!");
7912 return;
7913 }
7914
7915 uint32 count = 0;
7916
7917 Field* fields;
7918 do
7919 {
7920 fields = result->Fetch();
7921 std::string name= fields[0].GetString();
7922
7923 std::wstring wstr;
7924 if (!Utf8toWStr (name, wstr))
7925 {
7926 TC_LOG_ERROR("misc", "Table `reserved_name` has invalid name: %s", name.c_str());
7927 continue;
7928 }
7929
7930 wstrToLower(wstr);
7931
7932 _reservedNamesStore.insert(wstr);
7933 ++count;
7934 }
7935 while (result->NextRow());
7936
7937 TC_LOG_INFO("server.loading", ">> Loaded %u reserved player names in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
7938}
7939
7940bool ObjectMgr::IsReservedName(const std::string& name) const
7941{
7942 std::wstring wstr;
7943 if (!Utf8toWStr (name, wstr))
7944 return false;
7945
7946 wstrToLower(wstr);
7947
7948 return _reservedNamesStore.find(wstr) != _reservedNamesStore.end();
7949}
7950
7951enum LanguageType
7952{
7953 LT_BASIC_LATIN = 0x0000,
7954 LT_EXTENDEN_LATIN = 0x0001,
7955 LT_CYRILLIC = 0x0002,
7956 LT_EAST_ASIA = 0x0004,
7957 LT_ANY = 0xFFFF
7958};
7959
7960static LanguageType GetRealmLanguageType(bool create)
7961{
7962 switch (sWorld->getIntConfig(CONFIG_REALM_ZONE))
7963 {
7964 case REALM_ZONE_UNKNOWN: // any language
7965 case REALM_ZONE_DEVELOPMENT:
7966 case REALM_ZONE_TEST_SERVER:
7967 case REALM_ZONE_QA_SERVER:
7968 return LT_ANY;
7969 case REALM_ZONE_UNITED_STATES: // extended-Latin
7970 case REALM_ZONE_OCEANIC:
7971 case REALM_ZONE_LATIN_AMERICA:
7972 case REALM_ZONE_ENGLISH:
7973 case REALM_ZONE_GERMAN:
7974 case REALM_ZONE_FRENCH:
7975 case REALM_ZONE_SPANISH:
7976 return LT_EXTENDEN_LATIN;
7977 case REALM_ZONE_KOREA: // East-Asian
7978 case REALM_ZONE_TAIWAN:
7979 case REALM_ZONE_CHINA:
7980 return LT_EAST_ASIA;
7981 case REALM_ZONE_RUSSIAN: // Cyrillic
7982 return LT_CYRILLIC;
7983 default:
7984 return create ? LT_BASIC_LATIN : LT_ANY; // basic-Latin at create, any at login
7985 }
7986}
7987
7988bool isValidString(const std::wstring& wstr, uint32 strictMask, bool numericOrSpace, bool create = false)
7989{
7990 if (strictMask == 0) // any language, ignore realm
7991 {
7992 if (isExtendedLatinString(wstr, numericOrSpace))
7993 return true;
7994 if (isCyrillicString(wstr, numericOrSpace))
7995 return true;
7996 if (isEastAsianString(wstr, numericOrSpace))
7997 return true;
7998 return false;
7999 }
8000
8001 if (strictMask & 0x2) // realm zone specific
8002 {
8003 LanguageType lt = GetRealmLanguageType(create);
8004 if (lt & LT_EXTENDEN_LATIN)
8005 if (isExtendedLatinString(wstr, numericOrSpace))
8006 return true;
8007 if (lt & LT_CYRILLIC)
8008 if (isCyrillicString(wstr, numericOrSpace))
8009 return true;
8010 if (lt & LT_EAST_ASIA)
8011 if (isEastAsianString(wstr, numericOrSpace))
8012 return true;
8013 }
8014
8015 if (strictMask & 0x1) // basic Latin
8016 {
8017 if (isBasicLatinString(wstr, numericOrSpace))
8018 return true;
8019 }
8020
8021 return false;
8022}
8023
8024ResponseCodes ObjectMgr::CheckPlayerName(std::string const& name, LocaleConstant locale, bool create /*= false*/)
8025{
8026 std::wstring wname;
8027 if (!Utf8toWStr(name, wname))
8028 return CHAR_NAME_INVALID_CHARACTER;
8029
8030 if (wname.size() > MAX_PLAYER_NAME)
8031 return CHAR_NAME_TOO_LONG;
8032
8033 uint32 minName = sWorld->getIntConfig(CONFIG_MIN_PLAYER_NAME);
8034 if (wname.size() < minName)
8035 return CHAR_NAME_TOO_SHORT;
8036
8037 uint32 strictMask = sWorld->getIntConfig(CONFIG_STRICT_PLAYER_NAMES);
8038 if (!isValidString(wname, strictMask, false, create))
8039 return CHAR_NAME_MIXED_LANGUAGES;
8040
8041 wstrToLower(wname);
8042 for (size_t i = 2; i < wname.size(); ++i)
8043 if (wname[i] == wname[i-1] && wname[i] == wname[i-2])
8044 return CHAR_NAME_THREE_CONSECUTIVE;
8045
8046 return ValidateName(wname, locale);
8047}
8048
8049bool ObjectMgr::IsValidCharterName(const std::string& name)
8050{
8051 std::wstring wname;
8052 if (!Utf8toWStr(name, wname))
8053 return false;
8054
8055 if (wname.size() > MAX_CHARTER_NAME)
8056 return false;
8057
8058 uint32 minName = sWorld->getIntConfig(CONFIG_MIN_CHARTER_NAME);
8059 if (wname.size() < minName)
8060 return false;
8061
8062 uint32 strictMask = sWorld->getIntConfig(CONFIG_STRICT_CHARTER_NAMES);
8063
8064 return isValidString(wname, strictMask, true);
8065}
8066
8067PetNameInvalidReason ObjectMgr::CheckPetName(const std::string& name, LocaleConstant locale)
8068{
8069 std::wstring wname;
8070 if (!Utf8toWStr(name, wname))
8071 return PET_NAME_INVALID;
8072
8073 if (wname.size() > MAX_PET_NAME)
8074 return PET_NAME_TOO_LONG;
8075
8076 uint32 minName = sWorld->getIntConfig(CONFIG_MIN_PET_NAME);
8077 if (wname.size() < minName)
8078 return PET_NAME_TOO_SHORT;
8079
8080 uint32 strictMask = sWorld->getIntConfig(CONFIG_STRICT_PET_NAMES);
8081 if (!isValidString(wname, strictMask, false))
8082 return PET_NAME_MIXED_LANGUAGES;
8083
8084 switch (ValidateName(wname, locale))
8085 {
8086 case CHAR_NAME_PROFANE:
8087 return PET_NAME_PROFANE;
8088 case CHAR_NAME_RESERVED:
8089 return PET_NAME_RESERVED;
8090 case RESPONSE_FAILURE:
8091 return PET_NAME_INVALID;
8092 default:
8093 break;
8094 }
8095 return PET_NAME_SUCCESS;
8096}
8097
8098void ObjectMgr::LoadGameObjectForQuests()
8099{
8100 uint32 oldMSTime = getMSTime();
8101
8102 _gameObjectForQuestStore.clear(); // need for reload case
8103
8104 if (_gameObjectTemplateStore.empty())
8105 {
8106 TC_LOG_INFO("server.loading", ">> Loaded 0 GameObjects for quests");
8107 return;
8108 }
8109
8110 uint32 count = 0;
8111
8112 // collect GO entries for GO that must activated
8113 for (auto const& gameObjectTemplatePair : _gameObjectTemplateStore)
8114 {
8115 switch (gameObjectTemplatePair.second.type)
8116 {
8117 case GAMEOBJECT_TYPE_QUESTGIVER:
8118 break;
8119 case GAMEOBJECT_TYPE_CHEST:
8120 {
8121 // scan GO chest with loot including quest items
8122 uint32 lootId = gameObjectTemplatePair.second.GetLootId();
8123 // find quest loot for GO
8124 if (gameObjectTemplatePair.second.chest.questId || LootTemplates_Gameobject.HaveQuestLootFor(lootId))
8125 break;
8126 continue;
8127 }
8128 case GAMEOBJECT_TYPE_GENERIC:
8129 {
8130 if (gameObjectTemplatePair.second._generic.questID > 0) //quests objects
8131 break;
8132 continue;
8133 }
8134 case GAMEOBJECT_TYPE_GOOBER:
8135 {
8136 if (gameObjectTemplatePair.second.goober.questId > 0) //quests objects
8137 break;
8138 continue;
8139 }
8140 default:
8141 continue;
8142 }
8143
8144 _gameObjectForQuestStore.insert(gameObjectTemplatePair.first);
8145 ++count;
8146 }
8147
8148 TC_LOG_INFO("server.loading", ">> Loaded %u GameObjects for quests in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
8149}
8150
8151bool ObjectMgr::LoadTrinityStrings()
8152{
8153 uint32 oldMSTime = getMSTime();
8154
8155 _trinityStringStore.clear(); // for reload case
8156
8157 QueryResult result = WorldDatabase.Query("SELECT entry, content_default, content_loc1, content_loc2, content_loc3, content_loc4, content_loc5, content_loc6, content_loc7, content_loc8 FROM trinity_string");
8158 if (!result)
8159 {
8160 TC_LOG_INFO("server.loading", ">> Loaded 0 trinity strings. DB table `trinity_string` is empty. You have imported an incorrect database for more info search for TCE00003 on forum.");
8161 return false;
8162 }
8163
8164 do
8165 {
8166 Field* fields = result->Fetch();
8167
8168 uint32 entry = fields[0].GetUInt32();
8169
8170 TrinityString& data = _trinityStringStore[entry];
8171
8172 data.Content.resize(DEFAULT_LOCALE + 1);
8173
8174 for (int8 i = TOTAL_LOCALES - 1; i >= 0; --i)
8175 AddLocaleString(fields[i + 1].GetString(), LocaleConstant(i), data.Content);
8176 }
8177 while (result->NextRow());
8178
8179 TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " trinity strings in %u ms", _trinityStringStore.size(), GetMSTimeDiffToNow(oldMSTime));
8180 return true;
8181}
8182
8183char const* ObjectMgr::GetTrinityString(uint32 entry, LocaleConstant locale) const
8184{
8185 if (TrinityString const* ts = GetTrinityString(entry))
8186 {
8187 if (ts->Content.size() > size_t(locale) && !ts->Content[locale].empty())
8188 return ts->Content[locale].c_str();
8189 return ts->Content[DEFAULT_LOCALE].c_str();
8190 }
8191
8192 TC_LOG_ERROR("sql.sql", "Trinity string entry %u not found in DB.", entry);
8193 return "<error>";
8194}
8195
8196void ObjectMgr::LoadFishingBaseSkillLevel()
8197{
8198 uint32 oldMSTime = getMSTime();
8199
8200 _fishingBaseForAreaStore.clear(); // for reload case
8201
8202 QueryResult result = WorldDatabase.Query("SELECT entry, skill FROM skill_fishing_base_level");
8203
8204 if (!result)
8205 {
8206 TC_LOG_INFO("server.loading", ">> Loaded 0 areas for fishing base skill level. DB table `skill_fishing_base_level` is empty.");
8207 return;
8208 }
8209
8210 uint32 count = 0;
8211
8212 do
8213 {
8214 Field* fields = result->Fetch();
8215 uint32 entry = fields[0].GetUInt32();
8216 int32 skill = fields[1].GetInt16();
8217
8218 AreaTableEntry const* fArea = sAreaTableStore.LookupEntry(entry);
8219 if (!fArea)
8220 {
8221 TC_LOG_ERROR("sql.sql", "AreaId %u defined in `skill_fishing_base_level` does not exist", entry);
8222 continue;
8223 }
8224
8225 _fishingBaseForAreaStore[entry] = skill;
8226 ++count;
8227 }
8228 while (result->NextRow());
8229
8230 TC_LOG_INFO("server.loading", ">> Loaded %u areas for fishing base skill level in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
8231}
8232
8233bool ObjectMgr::CheckDeclinedNames(const std::wstring& w_ownname, DeclinedName const& names)
8234{
8235 // get main part of the name
8236 std::wstring mainpart = GetMainPartOfName(w_ownname, 0);
8237 // prepare flags
8238 bool x = true;
8239 bool y = true;
8240
8241 // check declined names
8242 for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
8243 {
8244 std::wstring wname;
8245 if (!Utf8toWStr(names.name[i], wname))
8246 return false;
8247
8248 if (mainpart != GetMainPartOfName(wname, i+1))
8249 x = false;
8250
8251 if (w_ownname != wname)
8252 y = false;
8253 }
8254 return (x || y);
8255}
8256
8257uint32 ObjectMgr::GetAreaTriggerScriptId(uint32 trigger_id) const
8258{
8259 AreaTriggerScriptContainer::const_iterator i = _areaTriggerScriptStore.find(trigger_id);
8260 if (i!= _areaTriggerScriptStore.end())
8261 return i->second;
8262 return 0;
8263}
8264
8265SpellScriptsBounds ObjectMgr::GetSpellScriptsBounds(uint32 spellId)
8266{
8267 return _spellScriptsStore.equal_range(spellId);
8268}
8269
8270// this allows calculating base reputations to offline players, just by race and class
8271int32 ObjectMgr::GetBaseReputationOf(FactionEntry const* factionEntry, uint8 race, uint8 playerClass) const
8272{
8273 if (!factionEntry)
8274 return 0;
8275
8276 uint32 raceMask = (1 << (race - 1));
8277 uint32 classMask = (1 << (playerClass-1));
8278
8279 for (uint8 i = 0; i < 4; ++i)
8280 {
8281 if ((!factionEntry->BaseRepClassMask[i] ||
8282 factionEntry->BaseRepClassMask[i] & classMask) &&
8283 (!factionEntry->BaseRepRaceMask[i] ||
8284 factionEntry->BaseRepRaceMask[i] & raceMask))
8285 return factionEntry->BaseRepValue[i];
8286 }
8287
8288 return 0;
8289}
8290
8291SkillRangeType GetSkillRangeType(SkillRaceClassInfoEntry const* rcEntry)
8292{
8293 SkillLineEntry const* skill = sSkillLineStore.LookupEntry(rcEntry->SkillId);
8294 if (!skill)
8295 return SKILL_RANGE_NONE;
8296
8297 if (sSkillTiersStore.LookupEntry(rcEntry->SkillTier))
8298 return SKILL_RANGE_RANK;
8299
8300 if (rcEntry->SkillId == SKILL_RUNEFORGING)
8301 return SKILL_RANGE_MONO;
8302
8303 switch (skill->categoryId)
8304 {
8305 case SKILL_CATEGORY_ARMOR:
8306 return SKILL_RANGE_MONO;
8307 case SKILL_CATEGORY_LANGUAGES:
8308 return SKILL_RANGE_LANGUAGE;
8309 }
8310
8311 return SKILL_RANGE_LEVEL;
8312}
8313
8314void ObjectMgr::LoadGameTele()
8315{
8316 uint32 oldMSTime = getMSTime();
8317
8318 _gameTeleStore.clear(); // for reload case
8319
8320 // 0 1 2 3 4 5 6
8321 QueryResult result = WorldDatabase.Query("SELECT id, position_x, position_y, position_z, orientation, map, name FROM game_tele");
8322
8323 if (!result)
8324 {
8325 TC_LOG_INFO("server.loading", ">> Loaded 0 GameTeleports. DB table `game_tele` is empty!");
8326 return;
8327 }
8328
8329 uint32 count = 0;
8330
8331 do
8332 {
8333 Field* fields = result->Fetch();
8334
8335 uint32 id = fields[0].GetUInt32();
8336
8337 GameTele gt;
8338
8339 gt.position_x = fields[1].GetFloat();
8340 gt.position_y = fields[2].GetFloat();
8341 gt.position_z = fields[3].GetFloat();
8342 gt.orientation = fields[4].GetFloat();
8343 gt.mapId = fields[5].GetUInt16();
8344 gt.name = fields[6].GetString();
8345
8346 if (!MapManager::IsValidMapCoord(gt.mapId, gt.position_x, gt.position_y, gt.position_z, gt.orientation))
8347 {
8348 TC_LOG_ERROR("sql.sql", "Wrong position for id %u (name: %s) in `game_tele` table, ignoring.", id, gt.name.c_str());
8349 continue;
8350 }
8351
8352 if (!Utf8toWStr(gt.name, gt.wnameLow))
8353 {
8354 TC_LOG_ERROR("sql.sql", "Wrong UTF8 name for id %u in `game_tele` table, ignoring.", id);
8355 continue;
8356 }
8357
8358 wstrToLower(gt.wnameLow);
8359
8360 _gameTeleStore[id] = gt;
8361
8362 ++count;
8363 }
8364 while (result->NextRow());
8365
8366 TC_LOG_INFO("server.loading", ">> Loaded %u GameTeleports in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
8367}
8368
8369GameTele const* ObjectMgr::GetGameTele(const std::string& name) const
8370{
8371 // explicit name case
8372 std::wstring wname;
8373 if (!Utf8toWStr(name, wname))
8374 return nullptr;
8375
8376 // converting string that we try to find to lower case
8377 wstrToLower(wname);
8378
8379 // Alternative first GameTele what contains wnameLow as substring in case no GameTele location found
8380 GameTele const* alt = nullptr;
8381 for (GameTeleContainer::const_iterator itr = _gameTeleStore.begin(); itr != _gameTeleStore.end(); ++itr)
8382 {
8383 if (itr->second.wnameLow == wname)
8384 return &itr->second;
8385 else if (!alt && itr->second.wnameLow.find(wname) != std::wstring::npos)
8386 alt = &itr->second;
8387 }
8388
8389 return alt;
8390}
8391
8392GameTele const* ObjectMgr::GetGameTeleExactName(const std::string& name) const
8393{
8394 // explicit name case
8395 std::wstring wname;
8396 if (!Utf8toWStr(name, wname))
8397 return nullptr;
8398
8399 // converting string that we try to find to lower case
8400 wstrToLower(wname);
8401
8402 for (GameTeleContainer::const_iterator itr = _gameTeleStore.begin(); itr != _gameTeleStore.end(); ++itr)
8403 {
8404 if (itr->second.wnameLow == wname)
8405 return &itr->second;
8406 }
8407
8408 return nullptr;
8409}
8410
8411bool ObjectMgr::AddGameTele(GameTele& tele)
8412{
8413 // find max id
8414 uint32 new_id = 0;
8415 for (GameTeleContainer::const_iterator itr = _gameTeleStore.begin(); itr != _gameTeleStore.end(); ++itr)
8416 if (itr->first > new_id)
8417 new_id = itr->first;
8418
8419 // use next
8420 ++new_id;
8421
8422 if (!Utf8toWStr(tele.name, tele.wnameLow))
8423 return false;
8424
8425 wstrToLower(tele.wnameLow);
8426
8427 _gameTeleStore[new_id] = tele;
8428
8429 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_GAME_TELE);
8430
8431 stmt->setUInt32(0, new_id);
8432 stmt->setFloat(1, tele.position_x);
8433 stmt->setFloat(2, tele.position_y);
8434 stmt->setFloat(3, tele.position_z);
8435 stmt->setFloat(4, tele.orientation);
8436 stmt->setUInt16(5, uint16(tele.mapId));
8437 stmt->setString(6, tele.name);
8438
8439 WorldDatabase.Execute(stmt);
8440
8441 return true;
8442}
8443
8444bool ObjectMgr::DeleteGameTele(const std::string& name)
8445{
8446 // explicit name case
8447 std::wstring wname;
8448 if (!Utf8toWStr(name, wname))
8449 return false;
8450
8451 // converting string that we try to find to lower case
8452 wstrToLower(wname);
8453
8454 for (GameTeleContainer::iterator itr = _gameTeleStore.begin(); itr != _gameTeleStore.end(); ++itr)
8455 {
8456 if (itr->second.wnameLow == wname)
8457 {
8458 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAME_TELE);
8459
8460 stmt->setString(0, itr->second.name);
8461
8462 WorldDatabase.Execute(stmt);
8463
8464 _gameTeleStore.erase(itr);
8465 return true;
8466 }
8467 }
8468
8469 return false;
8470}
8471
8472void ObjectMgr::LoadMailLevelRewards()
8473{
8474 uint32 oldMSTime = getMSTime();
8475
8476 _mailLevelRewardStore.clear(); // for reload case
8477
8478 // 0 1 2 3
8479 QueryResult result = WorldDatabase.Query("SELECT level, raceMask, mailTemplateId, senderEntry FROM mail_level_reward");
8480
8481 if (!result)
8482 {
8483 TC_LOG_INFO("server.loading", ">> Loaded 0 level dependent mail rewards. DB table `mail_level_reward` is empty.");
8484 return;
8485 }
8486
8487 uint32 count = 0;
8488
8489 do
8490 {
8491 Field* fields = result->Fetch();
8492
8493 uint8 level = fields[0].GetUInt8();
8494 uint32 raceMask = fields[1].GetUInt32();
8495 uint32 mailTemplateId = fields[2].GetUInt32();
8496 uint32 senderEntry = fields[3].GetUInt32();
8497
8498 if (level > MAX_LEVEL)
8499 {
8500 TC_LOG_ERROR("sql.sql", "Table `mail_level_reward` has data for level %u that more supported by client (%u), ignoring.", level, MAX_LEVEL);
8501 continue;
8502 }
8503
8504 if (!(raceMask & RACEMASK_ALL_PLAYABLE))
8505 {
8506 TC_LOG_ERROR("sql.sql", "Table `mail_level_reward` has raceMask (%u) for level %u that not include any player races, ignoring.", raceMask, level);
8507 continue;
8508 }
8509
8510 if (!sMailTemplateStore.LookupEntry(mailTemplateId))
8511 {
8512 TC_LOG_ERROR("sql.sql", "Table `mail_level_reward` has invalid mailTemplateId (%u) for level %u that invalid not include any player races, ignoring.", mailTemplateId, level);
8513 continue;
8514 }
8515
8516 if (!GetCreatureTemplate(senderEntry))
8517 {
8518 TC_LOG_ERROR("sql.sql", "Table `mail_level_reward` has nonexistent sender creature entry (%u) for level %u that invalid not include any player races, ignoring.", senderEntry, level);
8519 continue;
8520 }
8521
8522 _mailLevelRewardStore[level].push_back(MailLevelReward(raceMask, mailTemplateId, senderEntry));
8523
8524 ++count;
8525 }
8526 while (result->NextRow());
8527
8528 TC_LOG_INFO("server.loading", ">> Loaded %u level dependent mail rewards in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
8529}
8530
8531void ObjectMgr::AddSpellToTrainer(uint32 ID, uint32 SpellID, uint32 MoneyCost, uint32 ReqSkillLine, uint32 ReqSkillRank, uint32 ReqLevel)
8532{
8533 if (ID >= TRINITY_TRAINER_START_REF)
8534 return;
8535
8536 CreatureTemplate const* cInfo = GetCreatureTemplate(ID);
8537 if (!cInfo)
8538 {
8539 TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains entries for a non-existing creature template (ID: %u), ignoring", ID);
8540 return;
8541 }
8542
8543 if (!(cInfo->npcflag & UNIT_NPC_FLAG_TRAINER))
8544 {
8545 TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains entries for a creature template (ID: %u) without trainer flag, ignoring", ID);
8546 return;
8547 }
8548
8549 SpellInfo const* spellinfo = sSpellMgr->GetSpellInfo(SpellID);
8550 if (!spellinfo)
8551 {
8552 TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains an ID (%u) for a non-existing SpellID (%u), ignoring", ID, SpellID);
8553 return;
8554 }
8555
8556 if (!SpellMgr::IsSpellValid(spellinfo))
8557 {
8558 TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains an ID (%u) for a broken SpellID (%u), ignoring", ID, SpellID);
8559 return;
8560 }
8561
8562 if (GetTalentSpellCost(SpellID))
8563 {
8564 TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains an ID (%u) for a non-existing SpellID (%u) which is a talent, ignoring", ID, SpellID);
8565 return;
8566 }
8567
8568 TrainerSpellData& data = _cacheTrainerSpellStore[ID];
8569
8570 TrainerSpell& trainerSpell = data.spellList[SpellID];
8571 trainerSpell.SpellID = SpellID;
8572 trainerSpell.MoneyCost = MoneyCost;
8573 trainerSpell.ReqSkillLine = ReqSkillLine;
8574 trainerSpell.ReqSkillRank = ReqSkillRank;
8575 trainerSpell.ReqLevel = ReqLevel;
8576
8577 if (!trainerSpell.ReqLevel)
8578 trainerSpell.ReqLevel = spellinfo->SpellLevel;
8579
8580 // calculate learned spell for profession case when stored cast-spell
8581 trainerSpell.ReqAbility[0] = SpellID;
8582 for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
8583 {
8584 if (spellinfo->Effects[i].Effect != SPELL_EFFECT_LEARN_SPELL)
8585 continue;
8586 if (trainerSpell.ReqAbility[0] == SpellID)
8587 trainerSpell.ReqAbility[0] = 0;
8588 // player must be able to cast spell on himself
8589 if (spellinfo->Effects[i].TargetA.GetTarget() != 0 && spellinfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ALLY
8590 && spellinfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ANY && spellinfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_CASTER)
8591 {
8592 TC_LOG_ERROR("sql.sql", "Table `npc_trainer` has spell %u for trainer entry %u with learn effect which has incorrect target type, ignoring learn effect!", SpellID, ID);
8593 continue;
8594 }
8595
8596 trainerSpell.ReqAbility[i] = spellinfo->Effects[i].TriggerSpell;
8597
8598 if (trainerSpell.ReqAbility[i])
8599 {
8600 SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainerSpell.ReqAbility[i]);
8601 if (learnedSpellInfo && learnedSpellInfo->IsProfession())
8602 data.trainerType = 2;
8603 }
8604 }
8605
8606 return;
8607}
8608
8609void ObjectMgr::LoadTrainerSpell()
8610{
8611 uint32 oldMSTime = getMSTime();
8612
8613 // For reload case
8614 _cacheTrainerSpellStore.clear();
8615
8616 QueryResult result = WorldDatabase.Query("SELECT b.ID, a.SpellID, a.MoneyCost, a.ReqSkillLine, a.ReqSkillRank, a.ReqLevel FROM npc_trainer AS a "
8617 "INNER JOIN npc_trainer AS b ON a.ID = -(b.SpellID) "
8618 "UNION SELECT * FROM npc_trainer WHERE SpellID > 0");
8619
8620 if (!result)
8621 {
8622 TC_LOG_ERROR("sql.sql", ">> Loaded 0 Trainers. DB table `npc_trainer` is empty!");
8623
8624 return;
8625 }
8626
8627 uint32 count = 0;
8628
8629 do
8630 {
8631 Field* fields = result->Fetch();
8632
8633 uint32 ID = fields[0].GetUInt32();
8634 uint32 SpellID = fields[1].GetUInt32();
8635 uint32 MoneyCost = fields[2].GetUInt32();
8636 uint32 ReqSkillLine = fields[3].GetUInt16();
8637 uint32 ReqSkillRank = fields[4].GetUInt16();
8638 uint32 ReqLevel = fields[5].GetUInt8();
8639
8640 AddSpellToTrainer(ID, SpellID, MoneyCost, ReqSkillLine, ReqSkillRank, ReqLevel);
8641
8642 ++count;
8643 }
8644 while (result->NextRow());
8645
8646 TC_LOG_INFO("server.loading", ">> Loaded %d Trainers in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
8647}
8648
8649uint32 ObjectMgr::LoadReferenceVendor(int32 vendor, int32 item, std::set<uint32>* skip_vendors)
8650{
8651 // find all items from the reference vendor
8652 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_NPC_VENDOR_REF);
8653 stmt->setUInt32(0, uint32(item));
8654 PreparedQueryResult result = WorldDatabase.Query(stmt);
8655
8656 if (!result)
8657 return 0;
8658
8659 uint32 count = 0;
8660 do
8661 {
8662 Field* fields = result->Fetch();
8663
8664 int32 item_id = fields[0].GetInt32();
8665
8666 // if item is a negative, its a reference
8667 if (item_id < 0)
8668 count += LoadReferenceVendor(vendor, -item_id, skip_vendors);
8669 else
8670 {
8671 int32 maxcount = fields[1].GetUInt8();
8672 uint32 incrtime = fields[2].GetUInt32();
8673 uint32 ExtendedCost = fields[3].GetUInt32();
8674
8675 if (!IsVendorItemValid(vendor, item_id, maxcount, incrtime, ExtendedCost, nullptr, skip_vendors))
8676 continue;
8677
8678 VendorItemData& vList = _cacheVendorItemStore[vendor];
8679
8680 vList.AddItem(item_id, maxcount, incrtime, ExtendedCost);
8681 ++count;
8682 }
8683 } while (result->NextRow());
8684
8685 return count;
8686}
8687
8688void ObjectMgr::LoadVendors()
8689{
8690 uint32 oldMSTime = getMSTime();
8691
8692 // For reload case
8693 for (CacheVendorItemContainer::iterator itr = _cacheVendorItemStore.begin(); itr != _cacheVendorItemStore.end(); ++itr)
8694 itr->second.Clear();
8695 _cacheVendorItemStore.clear();
8696
8697 std::set<uint32> skip_vendors;
8698
8699 QueryResult result = WorldDatabase.Query("SELECT entry, item, maxcount, incrtime, ExtendedCost FROM npc_vendor ORDER BY entry, slot ASC");
8700 if (!result)
8701 {
8702
8703 TC_LOG_ERROR("sql.sql", ">> Loaded 0 Vendors. DB table `npc_vendor` is empty!");
8704 return;
8705 }
8706
8707 uint32 count = 0;
8708
8709 do
8710 {
8711 Field* fields = result->Fetch();
8712
8713 uint32 entry = fields[0].GetUInt32();
8714 int32 item_id = fields[1].GetInt32();
8715
8716 // if item is a negative, its a reference
8717 if (item_id < 0)
8718 count += LoadReferenceVendor(entry, -item_id, &skip_vendors);
8719 else
8720 {
8721 uint32 maxcount = fields[2].GetUInt8();
8722 uint32 incrtime = fields[3].GetUInt32();
8723 uint32 ExtendedCost = fields[4].GetUInt32();
8724
8725 if (!IsVendorItemValid(entry, item_id, maxcount, incrtime, ExtendedCost, nullptr, &skip_vendors))
8726 continue;
8727
8728 VendorItemData& vList = _cacheVendorItemStore[entry];
8729
8730 vList.AddItem(item_id, maxcount, incrtime, ExtendedCost);
8731 ++count;
8732 }
8733 }
8734 while (result->NextRow());
8735
8736 TC_LOG_INFO("server.loading", ">> Loaded %d Vendors in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
8737}
8738
8739void ObjectMgr::LoadGossipMenu()
8740{
8741 uint32 oldMSTime = getMSTime();
8742
8743 _gossipMenusStore.clear();
8744
8745 // 0 1
8746 QueryResult result = WorldDatabase.Query("SELECT MenuID, TextID FROM gossip_menu");
8747
8748 if (!result)
8749 {
8750 TC_LOG_INFO("server.loading", ">> Loaded 0 gossip_menu IDs. DB table `gossip_menu` is empty!");
8751 return;
8752 }
8753
8754 do
8755 {
8756 Field* fields = result->Fetch();
8757
8758 GossipMenus gMenu;
8759
8760 gMenu.MenuID = fields[0].GetUInt16();
8761 gMenu.TextID = fields[1].GetUInt32();
8762
8763 if (!GetGossipText(gMenu.TextID))
8764 {
8765 TC_LOG_ERROR("sql.sql", "Table gossip_menu: ID %u is using non-existing TextID %u", gMenu.MenuID, gMenu.TextID);
8766 continue;
8767 }
8768
8769 _gossipMenusStore.insert(GossipMenusContainer::value_type(gMenu.MenuID, gMenu));
8770 } while (result->NextRow());
8771
8772 TC_LOG_INFO("server.loading", ">> Loaded %u gossip_menu IDs in %u ms", uint32(_gossipMenusStore.size()), GetMSTimeDiffToNow(oldMSTime));
8773}
8774
8775void ObjectMgr::LoadGossipMenuItems()
8776{
8777 uint32 oldMSTime = getMSTime();
8778
8779 _gossipMenuItemsStore.clear();
8780
8781 QueryResult result = WorldDatabase.Query(
8782 // 0 1 2 3 4 5 6 7 8 9 10 11 12
8783 "SELECT MenuID, OptionID, OptionIcon, OptionText, OptionBroadcastTextID, OptionType, OptionNpcFlag, ActionMenuID, ActionPoiID, BoxCoded, BoxMoney, BoxText, BoxBroadcastTextID "
8784 "FROM gossip_menu_option ORDER BY MenuID, OptionID");
8785
8786 if (!result)
8787 {
8788 TC_LOG_INFO("server.loading", ">> Loaded 0 gossip_menu_option IDs. DB table `gossip_menu_option` is empty!");
8789 return;
8790 }
8791
8792 do
8793 {
8794 Field* fields = result->Fetch();
8795
8796 GossipMenuItems gMenuItem;
8797
8798 gMenuItem.MenuID = fields[0].GetUInt16();
8799 gMenuItem.OptionID = fields[1].GetUInt16();
8800 gMenuItem.OptionIcon = fields[2].GetUInt32();
8801 gMenuItem.OptionText = fields[3].GetString();
8802 gMenuItem.OptionBroadcastTextID = fields[4].GetUInt32();
8803 gMenuItem.OptionType = fields[5].GetUInt8();
8804 gMenuItem.OptionNpcFlag = fields[6].GetUInt32();
8805 gMenuItem.ActionMenuID = fields[7].GetUInt32();
8806 gMenuItem.ActionPoiID = fields[8].GetUInt32();
8807 gMenuItem.BoxCoded = fields[9].GetBool();
8808 gMenuItem.BoxMoney = fields[10].GetUInt32();
8809 gMenuItem.BoxText = fields[11].GetString();
8810 gMenuItem.BoxBroadcastTextID = fields[12].GetUInt32();
8811
8812 if (gMenuItem.OptionIcon >= GOSSIP_ICON_MAX)
8813 {
8814 TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u has unknown icon id %u. Replacing with GOSSIP_ICON_CHAT", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.OptionIcon);
8815 gMenuItem.OptionIcon = GOSSIP_ICON_CHAT;
8816 }
8817
8818 if (gMenuItem.OptionBroadcastTextID)
8819 {
8820 if (!GetBroadcastText(gMenuItem.OptionBroadcastTextID))
8821 {
8822 TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u has non-existing or incompatible OptionBroadcastTextID %u, ignoring.", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.OptionBroadcastTextID);
8823 gMenuItem.OptionBroadcastTextID = 0;
8824 }
8825 }
8826
8827 if (gMenuItem.OptionType >= GOSSIP_OPTION_MAX)
8828 TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u has unknown option id %u. Option will not be used", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.OptionType);
8829
8830 if (gMenuItem.ActionPoiID && !GetPointOfInterest(gMenuItem.ActionPoiID))
8831 {
8832 TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u use non-existing ActionPoiID %u, ignoring", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.ActionPoiID);
8833 gMenuItem.ActionPoiID = 0;
8834 }
8835
8836 if (gMenuItem.BoxBroadcastTextID)
8837 {
8838 if (!GetBroadcastText(gMenuItem.BoxBroadcastTextID))
8839 {
8840 TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u has non-existing or incompatible BoxBroadcastTextID %u, ignoring.", gMenuItem.MenuID, gMenuItem.OptionID, gMenuItem.BoxBroadcastTextID);
8841 gMenuItem.BoxBroadcastTextID = 0;
8842 }
8843 }
8844
8845 _gossipMenuItemsStore.insert(GossipMenuItemsContainer::value_type(gMenuItem.MenuID, gMenuItem));
8846 } while (result->NextRow());
8847
8848 TC_LOG_INFO("server.loading", ">> Loaded %u gossip_menu_option entries in %u ms", uint32(_gossipMenuItemsStore.size()), GetMSTimeDiffToNow(oldMSTime));
8849}
8850
8851void ObjectMgr::AddVendorItem(uint32 entry, uint32 item, int32 maxcount, uint32 incrtime, uint32 extendedCost, bool persist /*= true*/)
8852{
8853 VendorItemData& vList = _cacheVendorItemStore[entry];
8854 vList.AddItem(item, maxcount, incrtime, extendedCost);
8855
8856 if (persist)
8857 {
8858 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_NPC_VENDOR);
8859
8860 stmt->setUInt32(0, entry);
8861 stmt->setUInt32(1, item);
8862 stmt->setUInt8(2, maxcount);
8863 stmt->setUInt32(3, incrtime);
8864 stmt->setUInt32(4, extendedCost);
8865
8866 WorldDatabase.Execute(stmt);
8867 }
8868}
8869
8870bool ObjectMgr::RemoveVendorItem(uint32 entry, uint32 item, bool persist /*= true*/)
8871{
8872 CacheVendorItemContainer::iterator iter = _cacheVendorItemStore.find(entry);
8873 if (iter == _cacheVendorItemStore.end())
8874 return false;
8875
8876 if (!iter->second.RemoveItem(item))
8877 return false;
8878
8879 if (persist)
8880 {
8881 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_NPC_VENDOR);
8882
8883 stmt->setUInt32(0, entry);
8884 stmt->setUInt32(1, item);
8885
8886 WorldDatabase.Execute(stmt);
8887 }
8888
8889 return true;
8890}
8891
8892bool ObjectMgr::IsVendorItemValid(uint32 vendor_entry, uint32 item_id, int32 maxcount, uint32 incrtime, uint32 ExtendedCost, Player* player, std::set<uint32>* skip_vendors, uint32 ORnpcflag) const
8893{
8894 CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(vendor_entry);
8895 if (!cInfo)
8896 {
8897 if (player)
8898 ChatHandler(player->GetSession()).SendSysMessage(LANG_COMMAND_VENDORSELECTION);
8899 else
8900 TC_LOG_ERROR("sql.sql", "Table `(game_event_)npc_vendor` has data for nonexistent creature template (Entry: %u), ignore", vendor_entry);
8901 return false;
8902 }
8903
8904 if (!((cInfo->npcflag | ORnpcflag) & UNIT_NPC_FLAG_VENDOR))
8905 {
8906 if (!skip_vendors || skip_vendors->count(vendor_entry) == 0)
8907 {
8908 if (player)
8909 ChatHandler(player->GetSession()).SendSysMessage(LANG_COMMAND_VENDORSELECTION);
8910 else
8911 TC_LOG_ERROR("sql.sql", "Table `(game_event_)npc_vendor` has data for creature template (Entry: %u) without vendor flag, ignore", vendor_entry);
8912
8913 if (skip_vendors)
8914 skip_vendors->insert(vendor_entry);
8915 }
8916 return false;
8917 }
8918
8919 if (!sObjectMgr->GetItemTemplate(item_id))
8920 {
8921 if (player)
8922 ChatHandler(player->GetSession()).PSendSysMessage(LANG_ITEM_NOT_FOUND, item_id);
8923 else
8924 TC_LOG_ERROR("sql.sql", "Table `(game_event_)npc_vendor` for Vendor (Entry: %u) have in item list non-existed item (%u), ignore", vendor_entry, item_id);
8925 return false;
8926 }
8927
8928 if (ExtendedCost && !sItemExtendedCostStore.LookupEntry(ExtendedCost))
8929 {
8930 if (player)
8931 ChatHandler(player->GetSession()).PSendSysMessage(LANG_EXTENDED_COST_NOT_EXIST, ExtendedCost);
8932 else
8933 TC_LOG_ERROR("sql.sql", "Table `(game_event_)npc_vendor` has Item (Entry: %u) with wrong ExtendedCost (%u) for vendor (%u), ignore", item_id, ExtendedCost, vendor_entry);
8934 return false;
8935 }
8936
8937 if (maxcount > 0 && incrtime == 0)
8938 {
8939 if (player)
8940 ChatHandler(player->GetSession()).PSendSysMessage("MaxCount != 0 (%u) but IncrTime == 0", maxcount);
8941 else
8942 TC_LOG_ERROR("sql.sql", "Table `(game_event_)npc_vendor` has `maxcount` (%u) for item %u of vendor (Entry: %u) but `incrtime`=0, ignore", maxcount, item_id, vendor_entry);
8943 return false;
8944 }
8945 else if (maxcount == 0 && incrtime > 0)
8946 {
8947 if (player)
8948 ChatHandler(player->GetSession()).PSendSysMessage("MaxCount == 0 but IncrTime<>= 0");
8949 else
8950 TC_LOG_ERROR("sql.sql", "Table `(game_event_)npc_vendor` has `maxcount`=0 for item %u of vendor (Entry: %u) but `incrtime`<>0, ignore", item_id, vendor_entry);
8951 return false;
8952 }
8953
8954 VendorItemData const* vItems = GetNpcVendorItemList(vendor_entry);
8955 if (!vItems)
8956 return true; // later checks for non-empty lists
8957
8958 if (vItems->FindItemCostPair(item_id, ExtendedCost))
8959 {
8960 if (player)
8961 ChatHandler(player->GetSession()).PSendSysMessage(LANG_ITEM_ALREADY_IN_LIST, item_id, ExtendedCost);
8962 else
8963 TC_LOG_ERROR("sql.sql", "Table `npc_vendor` has duplicate items %u (with extended cost %u) for vendor (Entry: %u), ignoring", item_id, ExtendedCost, vendor_entry);
8964 return false;
8965 }
8966
8967 return true;
8968}
8969
8970void ObjectMgr::LoadScriptNames()
8971{
8972 uint32 oldMSTime = getMSTime();
8973
8974 // We insert an empty placeholder here so we can use the
8975 // script id 0 as dummy for "no script found".
8976 _scriptNamesStore.emplace_back("");
8977
8978 QueryResult result = WorldDatabase.Query(
8979 "SELECT DISTINCT(ScriptName) FROM achievement_criteria_data WHERE ScriptName <> '' AND type = 11 "
8980 "UNION "
8981 "SELECT DISTINCT(ScriptName) FROM battleground_template WHERE ScriptName <> '' "
8982 "UNION "
8983 "SELECT DISTINCT(ScriptName) FROM creature WHERE ScriptName <> '' "
8984 "UNION "
8985 "SELECT DISTINCT(ScriptName) FROM creature_template WHERE ScriptName <> '' "
8986 "UNION "
8987 "SELECT DISTINCT(ScriptName) FROM gameobject WHERE ScriptName <> '' "
8988 "UNION "
8989 "SELECT DISTINCT(ScriptName) FROM gameobject_template WHERE ScriptName <> '' "
8990 "UNION "
8991 "SELECT DISTINCT(ScriptName) FROM item_template WHERE ScriptName <> '' "
8992 "UNION "
8993 "SELECT DISTINCT(ScriptName) FROM areatrigger_scripts WHERE ScriptName <> '' "
8994 "UNION "
8995 "SELECT DISTINCT(ScriptName) FROM spell_script_names WHERE ScriptName <> '' "
8996 "UNION "
8997 "SELECT DISTINCT(ScriptName) FROM transports WHERE ScriptName <> '' "
8998 "UNION "
8999 "SELECT DISTINCT(ScriptName) FROM game_weather WHERE ScriptName <> '' "
9000 "UNION "
9001 "SELECT DISTINCT(ScriptName) FROM conditions WHERE ScriptName <> '' "
9002 "UNION "
9003 "SELECT DISTINCT(ScriptName) FROM outdoorpvp_template WHERE ScriptName <> '' "
9004 "UNION "
9005 "SELECT DISTINCT(script) FROM instance_template WHERE script <> ''");
9006
9007 if (!result)
9008 {
9009 TC_LOG_INFO("server.loading", ">> Loaded empty set of Script Names!");
9010 return;
9011 }
9012
9013 _scriptNamesStore.reserve(result->GetRowCount() + 1);
9014
9015 do
9016 {
9017 _scriptNamesStore.push_back((*result)[0].GetString());
9018 }
9019 while (result->NextRow());
9020
9021 std::sort(_scriptNamesStore.begin(), _scriptNamesStore.end());
9022
9023 TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " ScriptNames in %u ms", _scriptNamesStore.size(), GetMSTimeDiffToNow(oldMSTime));
9024}
9025
9026ObjectMgr::ScriptNameContainer const& ObjectMgr::GetAllScriptNames() const
9027{
9028 return _scriptNamesStore;
9029}
9030
9031std::string const& ObjectMgr::GetScriptName(uint32 id) const
9032{
9033 static std::string const empty = "";
9034 return (id < _scriptNamesStore.size()) ? _scriptNamesStore[id] : empty;
9035}
9036
9037
9038uint32 ObjectMgr::GetScriptId(std::string const& name)
9039{
9040 // use binary search to find the script name in the sorted vector
9041 // assume "" is the first element
9042 if (name.empty())
9043 return 0;
9044
9045 ScriptNameContainer::const_iterator itr = std::lower_bound(_scriptNamesStore.begin(), _scriptNamesStore.end(), name);
9046 if (itr == _scriptNamesStore.end() || *itr != name)
9047 return 0;
9048
9049 return uint32(itr - _scriptNamesStore.begin());
9050}
9051
9052void ObjectMgr::LoadBroadcastTexts()
9053{
9054 uint32 oldMSTime = getMSTime();
9055
9056 _broadcastTextStore.clear(); // for reload case
9057
9058 // 0 1 2 3 4 5 6 7 8 9 10 11 12
9059 QueryResult result = WorldDatabase.Query("SELECT ID, Language, MaleText, FemaleText, EmoteID0, EmoteID1, EmoteID2, EmoteDelay0, EmoteDelay1, EmoteDelay2, SoundId, Unk1, Unk2 FROM broadcast_text");
9060 if (!result)
9061 {
9062 TC_LOG_INFO("server.loading", ">> Loaded 0 broadcast texts. DB table `broadcast_text` is empty.");
9063 return;
9064 }
9065
9066 _broadcastTextStore.rehash(result->GetRowCount());
9067
9068 do
9069 {
9070 Field* fields = result->Fetch();
9071
9072 BroadcastText bct;
9073
9074 bct.Id = fields[0].GetUInt32();
9075 bct.Language = fields[1].GetUInt32();
9076 bct.MaleText[DEFAULT_LOCALE] = fields[2].GetString();
9077 bct.FemaleText[DEFAULT_LOCALE] = fields[3].GetString();
9078 bct.EmoteId0 = fields[4].GetUInt32();
9079 bct.EmoteId1 = fields[5].GetUInt32();
9080 bct.EmoteId2 = fields[6].GetUInt32();
9081 bct.EmoteDelay0 = fields[7].GetUInt32();
9082 bct.EmoteDelay1 = fields[8].GetUInt32();
9083 bct.EmoteDelay2 = fields[9].GetUInt32();
9084 bct.SoundId = fields[10].GetUInt32();
9085 bct.Unk1 = fields[11].GetUInt32();
9086 bct.Unk2 = fields[12].GetUInt32();
9087
9088 if (bct.SoundId)
9089 {
9090 if (!sSoundEntriesStore.LookupEntry(bct.SoundId))
9091 {
9092 TC_LOG_DEBUG("broadcasttext", "BroadcastText (Id: %u) in table `broadcast_text` has SoundId %u but sound does not exist.", bct.Id, bct.SoundId);
9093 bct.SoundId = 0;
9094 }
9095 }
9096
9097 if (!GetLanguageDescByID(bct.Language))
9098 {
9099 TC_LOG_DEBUG("broadcasttext", "BroadcastText (Id: %u) in table `broadcast_text` using Language %u but Language does not exist.", bct.Id, bct.Language);
9100 bct.Language = LANG_UNIVERSAL;
9101 }
9102
9103 if (bct.EmoteId0)
9104 {
9105 if (!sEmotesStore.LookupEntry(bct.EmoteId0))
9106 {
9107 TC_LOG_DEBUG("broadcasttext", "BroadcastText (Id: %u) in table `broadcast_text` has EmoteId0 %u but emote does not exist.", bct.Id, bct.EmoteId0);
9108 bct.EmoteId0 = 0;
9109 }
9110 }
9111
9112 if (bct.EmoteId1)
9113 {
9114 if (!sEmotesStore.LookupEntry(bct.EmoteId1))
9115 {
9116 TC_LOG_DEBUG("broadcasttext", "BroadcastText (Id: %u) in table `broadcast_text` has EmoteId1 %u but emote does not exist.", bct.Id, bct.EmoteId1);
9117 bct.EmoteId1 = 0;
9118 }
9119 }
9120
9121 if (bct.EmoteId2)
9122 {
9123 if (!sEmotesStore.LookupEntry(bct.EmoteId2))
9124 {
9125 TC_LOG_DEBUG("broadcasttext", "BroadcastText (Id: %u) in table `broadcast_text` has EmoteId2 %u but emote does not exist.", bct.Id, bct.EmoteId2);
9126 bct.EmoteId2 = 0;
9127 }
9128 }
9129
9130 _broadcastTextStore[bct.Id] = bct;
9131 }
9132 while (result->NextRow());
9133
9134 TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " broadcast texts in %u ms", _broadcastTextStore.size(), GetMSTimeDiffToNow(oldMSTime));
9135}
9136
9137void ObjectMgr::LoadBroadcastTextLocales()
9138{
9139 uint32 oldMSTime = getMSTime();
9140
9141 // 0 1 2 3
9142 QueryResult result = WorldDatabase.Query("SELECT ID, locale, MaleText, FemaleText FROM broadcast_text_locale");
9143 if (!result)
9144 {
9145 TC_LOG_INFO("server.loading", ">> Loaded 0 broadcast text locales. DB table `broadcast_text_locale` is empty.");
9146 return;
9147 }
9148
9149 do
9150 {
9151 Field* fields = result->Fetch();
9152
9153 uint32 id = fields[0].GetUInt32();
9154 std::string localeName = fields[1].GetString();
9155 std::string MaleText = fields[2].GetString();
9156 std::string FemaleText = fields[3].GetString();
9157
9158 BroadcastTextContainer::iterator bct = _broadcastTextStore.find(id);
9159 if (bct == _broadcastTextStore.end())
9160 {
9161 TC_LOG_ERROR("sql.sql", "BroadcastText (Id: %u) in table `broadcast_text_locale` does not exist. Skipped!", id);
9162 continue;
9163 }
9164
9165 LocaleConstant locale = GetLocaleByName(localeName);
9166 if (locale == LOCALE_enUS)
9167 continue;
9168
9169 AddLocaleString(MaleText, locale, bct->second.MaleText);
9170 AddLocaleString(FemaleText, locale, bct->second.FemaleText);
9171 } while (result->NextRow());
9172
9173 TC_LOG_INFO("server.loading", ">> Loaded %u broadcast text locales in %u ms", uint32(_broadcastTextStore.size()), GetMSTimeDiffToNow(oldMSTime));
9174}
9175
9176CreatureBaseStats const* ObjectMgr::GetCreatureBaseStats(uint8 level, uint8 unitClass)
9177{
9178 CreatureBaseStatsContainer::const_iterator it = _creatureBaseStatsStore.find(MAKE_PAIR16(level, unitClass));
9179
9180 if (it != _creatureBaseStatsStore.end())
9181 return &(it->second);
9182
9183 struct DefaultCreatureBaseStats : public CreatureBaseStats
9184 {
9185 DefaultCreatureBaseStats()
9186 {
9187 BaseArmor = 1;
9188 for (uint8 j = 0; j < MAX_EXPANSIONS; ++j)
9189 {
9190 BaseHealth[j] = 1;
9191 BaseDamage[j] = 0.0f;
9192 }
9193 BaseMana = 0;
9194 AttackPower = 0;
9195 RangedAttackPower = 0;
9196 }
9197 };
9198 static const DefaultCreatureBaseStats defStats;
9199 return &defStats;
9200}
9201
9202void ObjectMgr::LoadCreatureClassLevelStats()
9203{
9204 uint32 oldMSTime = getMSTime();
9205
9206 QueryResult result = WorldDatabase.Query("SELECT level, class, basehp0, basehp1, basehp2, basemana, basearmor, attackpower, rangedattackpower, damage_base, damage_exp1, damage_exp2 FROM creature_classlevelstats");
9207
9208 if (!result)
9209 {
9210 TC_LOG_INFO("server.loading", ">> Loaded 0 creature base stats. DB table `creature_classlevelstats` is empty.");
9211 return;
9212 }
9213
9214 uint32 count = 0;
9215 do
9216 {
9217 Field* fields = result->Fetch();
9218
9219 uint8 Level = fields[0].GetUInt8();
9220 uint8 Class = fields[1].GetUInt8();
9221
9222 if (!Class || ((1 << (Class - 1)) & CLASSMASK_ALL_CREATURES) == 0)
9223 TC_LOG_ERROR("sql.sql", "Creature base stats for level %u has invalid class %u", Level, Class);
9224
9225 CreatureBaseStats stats;
9226
9227 for (uint8 i = 0; i < MAX_EXPANSIONS; ++i)
9228 {
9229 stats.BaseHealth[i] = fields[2 + i].GetUInt16();
9230
9231 if (stats.BaseHealth[i] == 0)
9232 {
9233 TC_LOG_ERROR("sql.sql", "Creature base stats for class %u, level %u has invalid zero base HP[%u] - set to 1", Class, Level, i);
9234 stats.BaseHealth[i] = 1;
9235 }
9236
9237 stats.BaseDamage[i] = fields[9 + i].GetFloat();
9238 if (stats.BaseDamage[i] < 0.0f)
9239 {
9240 TC_LOG_ERROR("sql.sql", "Creature base stats for class %u, level %u has invalid negative base damage[%u] - set to 0.0", Class, Level, i);
9241 stats.BaseDamage[i] = 0.0f;
9242 }
9243 }
9244
9245 stats.BaseMana = fields[5].GetUInt16();
9246 stats.BaseArmor = fields[6].GetUInt16();
9247
9248 stats.AttackPower = fields[7].GetUInt16();
9249 stats.RangedAttackPower = fields[8].GetUInt16();
9250
9251 _creatureBaseStatsStore[MAKE_PAIR16(Level, Class)] = stats;
9252
9253 ++count;
9254 }
9255 while (result->NextRow());
9256
9257 for (auto const& creatureTemplatePair : _creatureTemplateStore)
9258 {
9259 for (uint16 lvl = creatureTemplatePair.second.minlevel; lvl <= creatureTemplatePair.second.maxlevel; ++lvl)
9260 {
9261 if (!_creatureBaseStatsStore.count(MAKE_PAIR16(lvl, creatureTemplatePair.second.unit_class)))
9262 TC_LOG_ERROR("sql.sql", "Missing base stats for creature class %u level %u", creatureTemplatePair.second.unit_class, lvl);
9263 }
9264 }
9265
9266 TC_LOG_INFO("server.loading", ">> Loaded %u creature base stats in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
9267}
9268
9269void ObjectMgr::LoadFactionChangeAchievements()
9270{
9271 uint32 oldMSTime = getMSTime();
9272
9273 QueryResult result = WorldDatabase.Query("SELECT alliance_id, horde_id FROM player_factionchange_achievement");
9274
9275 if (!result)
9276 {
9277 TC_LOG_INFO("server.loading", ">> Loaded 0 faction change achievement pairs. DB table `player_factionchange_achievement` is empty.");
9278 return;
9279 }
9280
9281 uint32 count = 0;
9282
9283 do
9284 {
9285 Field* fields = result->Fetch();
9286
9287 uint32 alliance = fields[0].GetUInt32();
9288 uint32 horde = fields[1].GetUInt32();
9289
9290 if (!sAchievementMgr->GetAchievement(alliance))
9291 TC_LOG_ERROR("sql.sql", "Achievement %u (alliance_id) referenced in `player_factionchange_achievement` does not exist, pair skipped!", alliance);
9292 else if (!sAchievementMgr->GetAchievement(horde))
9293 TC_LOG_ERROR("sql.sql", "Achievement %u (horde_id) referenced in `player_factionchange_achievement` does not exist, pair skipped!", horde);
9294 else
9295 FactionChangeAchievements[alliance] = horde;
9296
9297 ++count;
9298 }
9299 while (result->NextRow());
9300
9301 TC_LOG_INFO("server.loading", ">> Loaded %u faction change achievement pairs in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
9302}
9303
9304void ObjectMgr::LoadFactionChangeItems()
9305{
9306 uint32 oldMSTime = getMSTime();
9307
9308 QueryResult result = WorldDatabase.Query("SELECT alliance_id, horde_id FROM player_factionchange_items");
9309
9310 if (!result)
9311 {
9312 TC_LOG_INFO("server.loading", ">> Loaded 0 faction change item pairs. DB table `player_factionchange_items` is empty.");
9313 return;
9314 }
9315
9316 uint32 count = 0;
9317
9318 do
9319 {
9320 Field* fields = result->Fetch();
9321
9322 uint32 alliance = fields[0].GetUInt32();
9323 uint32 horde = fields[1].GetUInt32();
9324
9325 if (!GetItemTemplate(alliance))
9326 TC_LOG_ERROR("sql.sql", "Item %u (alliance_id) referenced in `player_factionchange_items` does not exist, pair skipped!", alliance);
9327 else if (!GetItemTemplate(horde))
9328 TC_LOG_ERROR("sql.sql", "Item %u (horde_id) referenced in `player_factionchange_items` does not exist, pair skipped!", horde);
9329 else
9330 FactionChangeItems[alliance] = horde;
9331
9332 ++count;
9333 }
9334 while (result->NextRow());
9335
9336 TC_LOG_INFO("server.loading", ">> Loaded %u faction change item pairs in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
9337}
9338
9339void ObjectMgr::LoadFactionChangeQuests()
9340{
9341 uint32 oldMSTime = getMSTime();
9342
9343 QueryResult result = WorldDatabase.Query("SELECT alliance_id, horde_id FROM player_factionchange_quests");
9344
9345 if (!result)
9346 {
9347 TC_LOG_INFO("server.loading", ">> Loaded 0 faction change quest pairs. DB table `player_factionchange_quests` is empty.");
9348 return;
9349 }
9350
9351 uint32 count = 0;
9352
9353 do
9354 {
9355 Field* fields = result->Fetch();
9356
9357 uint32 alliance = fields[0].GetUInt32();
9358 uint32 horde = fields[1].GetUInt32();
9359
9360 if (!sObjectMgr->GetQuestTemplate(alliance))
9361 TC_LOG_ERROR("sql.sql", "Quest %u (alliance_id) referenced in `player_factionchange_quests` does not exist, pair skipped!", alliance);
9362 else if (!sObjectMgr->GetQuestTemplate(horde))
9363 TC_LOG_ERROR("sql.sql", "Quest %u (horde_id) referenced in `player_factionchange_quests` does not exist, pair skipped!", horde);
9364 else
9365 FactionChangeQuests[alliance] = horde;
9366
9367 ++count;
9368 }
9369 while (result->NextRow());
9370
9371 TC_LOG_INFO("server.loading", ">> Loaded %u faction change quest pairs in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
9372}
9373
9374void ObjectMgr::LoadFactionChangeReputations()
9375{
9376 uint32 oldMSTime = getMSTime();
9377
9378 QueryResult result = WorldDatabase.Query("SELECT alliance_id, horde_id FROM player_factionchange_reputations");
9379
9380 if (!result)
9381 {
9382 TC_LOG_INFO("server.loading", ">> Loaded 0 faction change reputation pairs. DB table `player_factionchange_reputations` is empty.");
9383 return;
9384 }
9385
9386 uint32 count = 0;
9387
9388 do
9389 {
9390 Field* fields = result->Fetch();
9391
9392 uint32 alliance = fields[0].GetUInt32();
9393 uint32 horde = fields[1].GetUInt32();
9394
9395 if (!sFactionStore.LookupEntry(alliance))
9396 TC_LOG_ERROR("sql.sql", "Reputation %u (alliance_id) referenced in `player_factionchange_reputations` does not exist, pair skipped!", alliance);
9397 else if (!sFactionStore.LookupEntry(horde))
9398 TC_LOG_ERROR("sql.sql", "Reputation %u (horde_id) referenced in `player_factionchange_reputations` does not exist, pair skipped!", horde);
9399 else
9400 FactionChangeReputation[alliance] = horde;
9401
9402 ++count;
9403 }
9404 while (result->NextRow());
9405
9406 TC_LOG_INFO("server.loading", ">> Loaded %u faction change reputation pairs in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
9407}
9408
9409void ObjectMgr::LoadFactionChangeSpells()
9410{
9411 uint32 oldMSTime = getMSTime();
9412
9413 QueryResult result = WorldDatabase.Query("SELECT alliance_id, horde_id FROM player_factionchange_spells");
9414
9415 if (!result)
9416 {
9417 TC_LOG_INFO("server.loading", ">> Loaded 0 faction change spell pairs. DB table `player_factionchange_spells` is empty.");
9418 return;
9419 }
9420
9421 uint32 count = 0;
9422
9423 do
9424 {
9425 Field* fields = result->Fetch();
9426
9427 uint32 alliance = fields[0].GetUInt32();
9428 uint32 horde = fields[1].GetUInt32();
9429
9430 if (!sSpellMgr->GetSpellInfo(alliance))
9431 TC_LOG_ERROR("sql.sql", "Spell %u (alliance_id) referenced in `player_factionchange_spells` does not exist, pair skipped!", alliance);
9432 else if (!sSpellMgr->GetSpellInfo(horde))
9433 TC_LOG_ERROR("sql.sql", "Spell %u (horde_id) referenced in `player_factionchange_spells` does not exist, pair skipped!", horde);
9434 else
9435 FactionChangeSpells[alliance] = horde;
9436
9437 ++count;
9438 }
9439 while (result->NextRow());
9440
9441 TC_LOG_INFO("server.loading", ">> Loaded %u faction change spell pairs in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
9442}
9443
9444void ObjectMgr::LoadFactionChangeTitles()
9445{
9446 uint32 oldMSTime = getMSTime();
9447
9448 QueryResult result = WorldDatabase.Query("SELECT alliance_id, horde_id FROM player_factionchange_titles");
9449
9450 if (!result)
9451 {
9452 TC_LOG_INFO("server.loading", ">> Loaded 0 faction change title pairs. DB table `player_factionchange_title` is empty.");
9453 return;
9454 }
9455
9456 uint32 count = 0;
9457
9458 do
9459 {
9460 Field* fields = result->Fetch();
9461
9462 uint32 alliance = fields[0].GetUInt32();
9463 uint32 horde = fields[1].GetUInt32();
9464
9465 if (!sCharTitlesStore.LookupEntry(alliance))
9466 TC_LOG_ERROR("sql.sql", "Title %u (alliance_id) referenced in `player_factionchange_title` does not exist, pair skipped!", alliance);
9467 else if (!sCharTitlesStore.LookupEntry(horde))
9468 TC_LOG_ERROR("sql.sql", "Title %u (horde_id) referenced in `player_factionchange_title` does not exist, pair skipped!", horde);
9469 else
9470 FactionChangeTitles[alliance] = horde;
9471
9472 ++count;
9473 }
9474 while (result->NextRow());
9475
9476 TC_LOG_INFO("server.loading", ">> Loaded %u faction change title pairs in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
9477}
9478
9479GameObjectTemplate const* ObjectMgr::GetGameObjectTemplate(uint32 entry) const
9480{
9481 return Trinity::Containers::MapGetValuePtr(_gameObjectTemplateStore, entry);
9482}
9483
9484GameObjectTemplateAddon const* ObjectMgr::GetGameObjectTemplateAddon(uint32 entry) const
9485{
9486 auto itr = _gameObjectTemplateAddonStore.find(entry);
9487 if (itr != _gameObjectTemplateAddonStore.end())
9488 return &itr->second;
9489
9490 return nullptr;
9491}
9492
9493CreatureTemplate const* ObjectMgr::GetCreatureTemplate(uint32 entry) const
9494{
9495 return Trinity::Containers::MapGetValuePtr(_creatureTemplateStore, entry);
9496}
9497
9498QuestPOIWrapper const* ObjectMgr::GetQuestPOIWrapper(uint32 questId) const
9499{
9500 return Trinity::Containers::MapGetValuePtr(_questPOIStore, questId);
9501}
9502
9503VehicleAccessoryList const* ObjectMgr::GetVehicleAccessoryList(Vehicle* veh) const
9504{
9505 if (Creature* cre = veh->GetBase()->ToCreature())
9506 {
9507 // Give preference to GUID-based accessories
9508 VehicleAccessoryContainer::const_iterator itr = _vehicleAccessoryStore.find(cre->GetSpawnId());
9509 if (itr != _vehicleAccessoryStore.end())
9510 return &itr->second;
9511 }
9512
9513 // Otherwise return entry-based
9514 VehicleAccessoryContainer::const_iterator itr = _vehicleTemplateAccessoryStore.find(veh->GetCreatureEntry());
9515 if (itr != _vehicleTemplateAccessoryStore.end())
9516 return &itr->second;
9517 return nullptr;
9518}
9519
9520DungeonEncounterList const* ObjectMgr::GetDungeonEncounterList(uint32 mapId, Difficulty difficulty) const
9521{
9522 auto itr = _dungeonEncounterStore.find(MAKE_PAIR32(mapId, difficulty));
9523 if (itr != _dungeonEncounterStore.end())
9524 return &itr->second;
9525 return nullptr;
9526}
9527
9528PlayerInfo const* ObjectMgr::GetPlayerInfo(uint32 race, uint32 class_) const
9529{
9530 if (race >= MAX_RACES)
9531 return nullptr;
9532 if (class_ >= MAX_CLASSES)
9533 return nullptr;
9534 auto const& info = _playerInfo[race][class_];
9535 if (!info)
9536 return nullptr;
9537 return info.get();
9538}
9539
9540void ObjectMgr::LoadGameObjectQuestItems()
9541{
9542 uint32 oldMSTime = getMSTime();
9543
9544 // 0 1 2
9545 QueryResult result = WorldDatabase.Query("SELECT GameObjectEntry, ItemId, Idx FROM gameobject_questitem ORDER BY Idx ASC");
9546
9547 if (!result)
9548 {
9549 TC_LOG_INFO("server.loading", ">> Loaded 0 gameobject quest items. DB table `gameobject_questitem` is empty.");
9550 return;
9551 }
9552
9553 uint32 count = 0;
9554 do
9555 {
9556 Field* fields = result->Fetch();
9557
9558 uint32 entry = fields[0].GetUInt32();
9559 uint32 item = fields[1].GetUInt32();
9560 uint32 idx = fields[2].GetUInt32();
9561
9562 GameObjectTemplate const* goInfo = GetGameObjectTemplate(entry);
9563 if (!goInfo)
9564 {
9565 TC_LOG_ERROR("sql.sql", "Table `gameobject_questitem` has data for nonexistent gameobject (entry: %u, idx: %u), skipped", entry, idx);
9566 continue;
9567 };
9568
9569 ItemEntry const* db2Data = sItemStore.LookupEntry(item);
9570 if (!db2Data)
9571 {
9572 TC_LOG_ERROR("sql.sql", "Table `gameobject_questitem` has nonexistent item (ID: %u) in gameobject (entry: %u, idx: %u), skipped", item, entry, idx);
9573 continue;
9574 };
9575
9576 _gameObjectQuestItemStore[entry].push_back(item);
9577
9578 ++count;
9579 }
9580 while (result->NextRow());
9581
9582 TC_LOG_INFO("server.loading", ">> Loaded %u gameobject quest items in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
9583}
9584
9585void ObjectMgr::LoadCreatureQuestItems()
9586{
9587 uint32 oldMSTime = getMSTime();
9588
9589 // 0 1 2
9590 QueryResult result = WorldDatabase.Query("SELECT CreatureEntry, ItemId, Idx FROM creature_questitem ORDER BY Idx ASC");
9591
9592 if (!result)
9593 {
9594 TC_LOG_INFO("server.loading", ">> Loaded 0 creature quest items. DB table `creature_questitem` is empty.");
9595 return;
9596 }
9597
9598 uint32 count = 0;
9599 do
9600 {
9601 Field* fields = result->Fetch();
9602
9603 uint32 entry = fields[0].GetUInt32();
9604 uint32 item = fields[1].GetUInt32();
9605 uint32 idx = fields[2].GetUInt32();
9606
9607 CreatureTemplate const* creatureInfo = GetCreatureTemplate(entry);
9608 if (!creatureInfo)
9609 {
9610 TC_LOG_ERROR("sql.sql", "Table `creature_questitem` has data for nonexistent creature (entry: %u, idx: %u), skipped", entry, idx);
9611 continue;
9612 };
9613
9614 ItemEntry const* db2Data = sItemStore.LookupEntry(item);
9615 if (!db2Data)
9616 {
9617 TC_LOG_ERROR("sql.sql", "Table `creature_questitem` has nonexistent item (ID: %u) in creature (entry: %u, idx: %u), skipped", item, entry, idx);
9618 continue;
9619 };
9620
9621 _creatureQuestItemStore[entry].push_back(item);
9622
9623 ++count;
9624 }
9625 while (result->NextRow());
9626
9627 TC_LOG_INFO("server.loading", ">> Loaded %u creature quest items in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
9628}
9629
9630void ObjectMgr::InitializeQueriesData(QueryDataGroup mask)
9631{
9632 // cache disabled
9633 if (!sWorld->getBoolConfig(CONFIG_CACHE_DATA_QUERIES))
9634 return;
9635
9636 // Initialize Query data for creatures
9637 if (mask & QUERY_DATA_CREATURES)
9638 for (auto& creatureTemplatePair : _creatureTemplateStore)
9639 creatureTemplatePair.second.InitializeQueryData();
9640
9641 // Initialize Query Data for gameobjects
9642 if (mask & QUERY_DATA_GAMEOBJECTS)
9643 for (auto& gameObjectTemplatePair : _gameObjectTemplateStore)
9644 gameObjectTemplatePair.second.InitializeQueryData();
9645
9646 // Initialize Query Data for items
9647 if (mask & QUERY_DATA_ITEMS)
9648 for (auto& itemTemplatePair : _itemTemplateStore)
9649 itemTemplatePair.second.InitializeQueryData();
9650
9651 // Initialize Query Data for quests
9652 if (mask & QUERY_DATA_QUESTS)
9653 for (auto& questTemplatePair : _questTemplates)
9654 questTemplatePair.second.InitializeQueryData();
9655
9656 // Initialize Quest POI data
9657 if (mask & QUERY_DATA_POIS)
9658 for (auto& poiWrapperPair : _questPOIStore)
9659 poiWrapperPair.second.InitializeQueryData();
9660}
9661
9662void QuestPOIWrapper::InitializeQueryData()
9663{
9664 QueryDataBuffer = BuildQueryData();
9665}
9666
9667ByteBuffer QuestPOIWrapper::BuildQueryData() const
9668{
9669 ByteBuffer tempBuffer;
9670 tempBuffer << uint32(POIData.QuestID); // quest ID
9671 tempBuffer << uint32(POIData.QuestPOIBlobDataStats.size()); // POI count
9672
9673 for (QuestPOIBlobData const& questPOIBlobData : POIData.QuestPOIBlobDataStats)
9674 {
9675 tempBuffer << uint32(questPOIBlobData.BlobIndex); // POI index
9676 tempBuffer << int32(questPOIBlobData.ObjectiveIndex); // objective index
9677 tempBuffer << uint32(questPOIBlobData.MapID); // mapid
9678 tempBuffer << uint32(questPOIBlobData.WorldMapAreaID); // areaid
9679 tempBuffer << uint32(questPOIBlobData.Floor); // floorid
9680 tempBuffer << uint32(questPOIBlobData.Unk3); // unknown
9681 tempBuffer << uint32(questPOIBlobData.Unk4); // unknown
9682 tempBuffer << uint32(questPOIBlobData.QuestPOIBlobPointStats.size()); // POI points count
9683
9684 for (QuestPOIBlobPoint const& questPOIBlobPoint : questPOIBlobData.QuestPOIBlobPointStats)
9685 {
9686 tempBuffer << int32(questPOIBlobPoint.X); // POI point x
9687 tempBuffer << int32(questPOIBlobPoint.Y); // POI point y
9688 }
9689 }
9690
9691 return tempBuffer;
9692}