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