TrinityCore
ObjectMgr.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "ObjectMgr.h"
20#include "AreaTriggerTemplate.h"
21#include "ArenaTeamMgr.h"
23#include "AzeriteItem.h"
24#include "Chat.h"
25#include "Containers.h"
26#include "CreatureAIFactory.h"
27#include "CriteriaHandler.h"
28#include "DB2Stores.h"
29#include "DatabaseEnv.h"
30#include "DisableMgr.h"
31#include "GameObject.h"
32#include "GameObjectAIFactory.h"
33#include "GameTables.h"
34#include "GameTime.h"
35#include "GossipDef.h"
36#include "GridDefines.h"
37#include "GroupMgr.h"
38#include "GuildMgr.h"
39#include "InstanceScript.h"
40#include "Item.h"
41#include "ItemBonusMgr.h"
42#include "LFGMgr.h"
43#include "Language.h"
44#include "Log.h"
45#include "LootMgr.h"
46#include "Mail.h"
47#include "MapManager.h"
48#include "MapUtils.h"
49#include "MotionMaster.h"
50#include "MovementTypedefs.h"
51#include "ObjectAccessor.h"
52#include "ObjectDefines.h"
53#include "PhasingHandler.h"
54#include "Player.h"
55#include "QueryPackets.h"
56#include "QuestDef.h"
57#include "Random.h"
58#include "RealmList.h"
59#include "ReputationMgr.h"
60#include "ScriptMgr.h"
61#include "ScriptReloadMgr.h"
62#include "SpellInfo.h"
63#include "SpellMgr.h"
64#include "SpellScript.h"
65#include "StringConvert.h"
66#include "TemporarySummon.h"
67#include "TerrainMgr.h"
68#include "ThreadPool.h"
69#include "Timer.h"
70#include "TransportMgr.h"
71#include "VMapFactory.h"
72#include "VMapManager2.h"
73#include "Vehicle.h"
74#include "World.h"
75#include <G3D/g3dmath.h>
76#include <limits>
77#include <numeric>
78
81
83{
84 std::string res = "";
85 switch (type)
86 {
87 case SCRIPTS_SPELL: res = "spell_scripts"; break;
88 case SCRIPTS_EVENT: res = "event_scripts"; break;
89 default: break;
90 }
91 return res;
92}
93
95{
96 ScriptMapMap* res = nullptr;
97 switch (type)
98 {
99 case SCRIPTS_SPELL: res = &sSpellScripts; break;
100 case SCRIPTS_EVENT: res = &sEventScripts; break;
101 default: break;
102 }
103 return res;
104}
105
107{
108 std::string res = "";
109 switch (command)
110 {
111 case SCRIPT_COMMAND_TALK: res = "SCRIPT_COMMAND_TALK"; break;
112 case SCRIPT_COMMAND_EMOTE: res = "SCRIPT_COMMAND_EMOTE"; break;
113 case SCRIPT_COMMAND_FIELD_SET_DEPRECATED: res = "SCRIPT_COMMAND_FIELD_SET_DEPRECATED"; break;
114 case SCRIPT_COMMAND_MOVE_TO: res = "SCRIPT_COMMAND_MOVE_TO"; break;
115 case SCRIPT_COMMAND_FLAG_SET_DEPRECATED: res = "SCRIPT_COMMAND_FLAG_SET_DEPRECATED"; break;
116 case SCRIPT_COMMAND_FLAG_REMOVE_DEPRECATED: res = "SCRIPT_COMMAND_FLAG_REMOVE_DEPRECATED"; break;
117 case SCRIPT_COMMAND_TELEPORT_TO: res = "SCRIPT_COMMAND_TELEPORT_TO"; break;
118 case SCRIPT_COMMAND_QUEST_EXPLORED: res = "SCRIPT_COMMAND_QUEST_EXPLORED"; break;
119 case SCRIPT_COMMAND_KILL_CREDIT: res = "SCRIPT_COMMAND_KILL_CREDIT"; break;
120 case SCRIPT_COMMAND_RESPAWN_GAMEOBJECT: res = "SCRIPT_COMMAND_RESPAWN_GAMEOBJECT"; break;
121 case SCRIPT_COMMAND_TEMP_SUMMON_CREATURE: res = "SCRIPT_COMMAND_TEMP_SUMMON_CREATURE"; break;
122 case SCRIPT_COMMAND_OPEN_DOOR: res = "SCRIPT_COMMAND_OPEN_DOOR"; break;
123 case SCRIPT_COMMAND_CLOSE_DOOR: res = "SCRIPT_COMMAND_CLOSE_DOOR"; break;
124 case SCRIPT_COMMAND_ACTIVATE_OBJECT: res = "SCRIPT_COMMAND_ACTIVATE_OBJECT"; break;
125 case SCRIPT_COMMAND_REMOVE_AURA: res = "SCRIPT_COMMAND_REMOVE_AURA"; break;
126 case SCRIPT_COMMAND_CAST_SPELL: res = "SCRIPT_COMMAND_CAST_SPELL"; break;
127 case SCRIPT_COMMAND_PLAY_SOUND: res = "SCRIPT_COMMAND_PLAY_SOUND"; break;
128 case SCRIPT_COMMAND_CREATE_ITEM: res = "SCRIPT_COMMAND_CREATE_ITEM"; break;
129 case SCRIPT_COMMAND_DESPAWN_SELF: res = "SCRIPT_COMMAND_DESPAWN_SELF"; break;
130 case SCRIPT_COMMAND_LOAD_PATH: res = "SCRIPT_COMMAND_LOAD_PATH"; break;
131 case SCRIPT_COMMAND_CALLSCRIPT_TO_UNIT: res = "SCRIPT_COMMAND_CALLSCRIPT_TO_UNIT"; break;
132 case SCRIPT_COMMAND_KILL: res = "SCRIPT_COMMAND_KILL"; break;
133 // TrinityCore only
134 case SCRIPT_COMMAND_ORIENTATION: res = "SCRIPT_COMMAND_ORIENTATION"; break;
135 case SCRIPT_COMMAND_EQUIP: res = "SCRIPT_COMMAND_EQUIP"; break;
136 case SCRIPT_COMMAND_MODEL: res = "SCRIPT_COMMAND_MODEL"; break;
137 case SCRIPT_COMMAND_CLOSE_GOSSIP: res = "SCRIPT_COMMAND_CLOSE_GOSSIP"; break;
138 case SCRIPT_COMMAND_PLAYMOVIE: res = "SCRIPT_COMMAND_PLAYMOVIE"; break;
139 case SCRIPT_COMMAND_MOVEMENT: res = "SCRIPT_COMMAND_MOVEMENT"; break;
140 case SCRIPT_COMMAND_PLAY_ANIMKIT: res = "SCRIPT_COMMAND_PLAY_ANIMKIT"; break;
141 default:
142 {
143 res = Trinity::StringFormat("Unknown command: {}", command);
144 break;
145 }
146 }
147 return res;
148}
149
150std::string ScriptInfo::GetDebugInfo() const
151{
152 return Trinity::StringFormat("{} ('{}' script id: {})", GetScriptCommandName(command), GetScriptsTableNameByType(type), id);
153}
154
155bool normalizePlayerName(std::string& name)
156{
157 if (name.empty())
158 return false;
159
160 std::wstring tmp;
161 if (!Utf8toWStr(name, tmp))
162 return false;
163
164 wstrToLower(tmp);
165 if (!tmp.empty())
166 tmp[0] = wcharToUpper(tmp[0]);
167
168 if (!WStrToUtf8(tmp, name))
169 return false;
170
171 return true;
172}
173
174// Extracts player and realm names delimited by -
176{
177 size_t pos = name.find('-');
178 if (pos != std::string::npos)
179 return ExtendedPlayerName(name.substr(0, pos), name.substr(pos + 1));
180 else
181 return ExtendedPlayerName(name, "");
182}
183
184bool SpellClickInfo::IsFitToRequirements(Unit const* clicker, Unit const* clickee) const
185{
186 Player const* playerClicker = clicker->ToPlayer();
187 if (!playerClicker)
188 return true;
189
190 Unit const* summoner = nullptr;
191 // Check summoners for party
192 if (clickee->IsSummon())
193 summoner = clickee->ToTempSummon()->GetSummonerUnit();
194 if (!summoner)
195 summoner = clickee;
196
197 // This only applies to players
198 switch (userType)
199 {
201 if (!playerClicker->IsFriendlyTo(summoner))
202 return false;
203 break;
205 if (!playerClicker->IsInRaidWith(summoner))
206 return false;
207 break;
209 if (!playerClicker->IsInPartyWith(summoner))
210 return false;
211 break;
212 default:
213 break;
214 }
215
216 return true;
217}
218
220 _auctionId(1),
221 _equipmentSetGuid(1),
222 _mailId(1),
223 _hiPetNumber(1),
224 _creatureSpawnId(1),
225 _gameObjectSpawnId(1),
226 _voidItemId(1),
227 DBCLocaleIndex(LOCALE_enUS)
228{
229}
230
232{
233 static ObjectMgr instance;
234 return &instance;
235}
236
238{
239}
240
241void ObjectMgr::AddLocaleString(std::string_view value, LocaleConstant localeConstant, std::vector<std::string>& data)
242{
243 if (data.size() <= size_t(localeConstant))
244 {
245 data.reserve(TOTAL_LOCALES);
246 data.resize(localeConstant + 1);
247 }
248
249 data[localeConstant] = value.empty() ? "" : value;
250}
251
253{
254 uint32 oldMSTime = getMSTime();
255
256 _creatureLocaleStore.clear(); // need for reload case
257
258 // 0 1 2 3 4 5
259 QueryResult result = WorldDatabase.Query("SELECT entry, locale, Name, NameAlt, Title, TitleAlt FROM creature_template_locale");
260 if (!result)
261 return;
262
263 do
264 {
265 Field* fields = result->Fetch();
266
267 uint32 id = fields[0].GetUInt32();
268 std::string_view localeName = fields[1].GetStringView();
269
270 LocaleConstant locale = GetLocaleByName(localeName);
271 if (!IsValidLocale(locale) || locale == LOCALE_enUS)
272 continue;
273
275 AddLocaleString(fields[2].GetStringView(), locale, data.Name);
276 AddLocaleString(fields[3].GetStringView(), locale, data.NameAlt);
277 AddLocaleString(fields[4].GetStringView(), locale, data.Title);
278 AddLocaleString(fields[5].GetStringView(), locale, data.TitleAlt);
279
280 } while (result->NextRow());
281
282 TC_LOG_INFO("server.loading", ">> Loaded {} creature locale strings in {} ms", uint32(_creatureLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime));
283}
284
286{
287 uint32 oldMSTime = getMSTime();
288
289 _gossipMenuItemsLocaleStore.clear(); // need for reload case
290
291 // 0 1 2 3 4
292 QueryResult result = WorldDatabase.Query("SELECT MenuID, OptionID, Locale, OptionText, BoxText FROM gossip_menu_option_locale");
293
294 if (!result)
295 return;
296
297 do
298 {
299 Field* fields = result->Fetch();
300
301 uint32 menuId = fields[0].GetUInt32();
302 uint32 optionId = fields[1].GetUInt32();
303 std::string_view localeName = fields[2].GetStringView();
304
305 LocaleConstant locale = GetLocaleByName(localeName);
306 if (!IsValidLocale(locale) || locale == LOCALE_enUS)
307 continue;
308
309 GossipMenuItemsLocale& data = _gossipMenuItemsLocaleStore[std::make_pair(menuId, optionId)];
310 AddLocaleString(fields[3].GetStringView(), locale, data.OptionText);
311 AddLocaleString(fields[4].GetStringView(), locale, data.BoxText);
312 } while (result->NextRow());
313
314 TC_LOG_INFO("server.loading", ">> Loaded {} gossip_menu_option locale strings in {} ms", _gossipMenuItemsLocaleStore.size(), GetMSTimeDiffToNow(oldMSTime));
315}
316
318{
319 uint32 oldMSTime = getMSTime();
320
321 _pointOfInterestLocaleStore.clear(); // need for reload case
322
323 // 0 1 2
324 QueryResult result = WorldDatabase.Query("SELECT ID, locale, Name FROM points_of_interest_locale");
325 if (!result)
326 return;
327
328 do
329 {
330 Field* fields = result->Fetch();
331
332 uint32 id = fields[0].GetUInt32();
333 std::string_view localeName = fields[1].GetStringView();
334
335 LocaleConstant locale = GetLocaleByName(localeName);
336 if (!IsValidLocale(locale) || locale == LOCALE_enUS)
337 continue;
338
340 AddLocaleString(fields[2].GetStringView(), locale, data.Name);
341 } while (result->NextRow());
342
343 TC_LOG_INFO("server.loading", ">> Loaded {} points_of_interest locale strings in {} ms", uint32(_pointOfInterestLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime));
344}
345
347{
348 uint32 oldMSTime = getMSTime();
349
350 // 0 1 2 3 4 5
351 // "SELECT entry, KillCredit1, KillCredit2, name, femaleName, subname, "
352 // 6 7 8 9
353 // "TitleAlt, IconName, RequiredExpansion, VignetteID, "
354 // 10 11 12 13 14 15 16 17 18 19 20
355 // "faction, npcflag, speed_walk, speed_run, scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, "
356 // 21 22 23 24
357 // "unit_class, unit_flags, unit_flags2, unit_flags3, "
358 // 25 26 27 28 29 30
359 // "family, trainer_class, type, VehicleId, AIName, MovementType, "
360 // 31 32 33 34 35
361 // "ctm.HoverInitiallyEnabled, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, ExperienceModifier, "
362 // 36 37 38 39 40
363 // "RacialLeader, movementId, WidgetSetID, WidgetSetUnitConditionID, RegenHealth, "
364 // 41 42
365 // "CreatureImmunitiesId, flags_extra, "
366 // 43 44
367 // "ScriptName, StringId FROM creature_template WHERE entry = ? OR 1 = ?");
368
370 stmt->setUInt32(0, 0);
371 stmt->setUInt32(1, 1);
372
373 PreparedQueryResult result = WorldDatabase.Query(stmt);
374 if (!result)
375 {
376 TC_LOG_INFO("server.loading", ">> Loaded 0 creature template definitions. DB table `creature_template` is empty.");
377 return;
378 }
379
380 _creatureTemplateStore.reserve(result->GetRowCount());
381 do
382 {
383 Field* fields = result->Fetch();
384 LoadCreatureTemplate(fields);
385 } while (result->NextRow());
386
389
390 // We load the creature models after loading but before checking
392
393 // Checking needs to be done after loading because of the difficulty self referencing
394 for (auto const& ctPair : _creatureTemplateStore)
395 CheckCreatureTemplate(&ctPair.second);
396
397 TC_LOG_INFO("server.loading", ">> Loaded {} creature definitions in {} ms", _creatureTemplateStore.size(), GetMSTimeDiffToNow(oldMSTime));
398}
399
401{
402 uint32 entry = fields[0].GetUInt32();
403 CreatureTemplate& creatureTemplate = _creatureTemplateStore[entry];
404
405 creatureTemplate.Entry = entry;
406
407 for (uint8 i = 0; i < MAX_KILL_CREDIT; ++i)
408 creatureTemplate.KillCredit[i] = fields[1 + i].GetUInt32();
409
410 creatureTemplate.Name = fields[3].GetString();
411 creatureTemplate.FemaleName = fields[4].GetString();
412 creatureTemplate.SubName = fields[5].GetString();
413 creatureTemplate.TitleAlt = fields[6].GetString();
414 creatureTemplate.IconName = fields[7].GetString();
415 creatureTemplate.RequiredExpansion = fields[8].GetUInt32();
416 creatureTemplate.VignetteID = fields[9].GetUInt32();
417 creatureTemplate.faction = fields[10].GetUInt16();
418 creatureTemplate.npcflag = fields[11].GetUInt64();
419 creatureTemplate.speed_walk = fields[12].GetFloat();
420 creatureTemplate.speed_run = fields[13].GetFloat();
421 creatureTemplate.scale = fields[14].GetFloat();
422 creatureTemplate.Classification = CreatureClassifications(fields[15].GetUInt8());
423 creatureTemplate.dmgschool = uint32(fields[16].GetInt8());
424 creatureTemplate.BaseAttackTime = fields[17].GetUInt32();
425 creatureTemplate.RangeAttackTime = fields[18].GetUInt32();
426 creatureTemplate.BaseVariance = fields[19].GetFloat();
427 creatureTemplate.RangeVariance = fields[20].GetFloat();
428 creatureTemplate.unit_class = uint32(fields[21].GetUInt8());
429 creatureTemplate.unit_flags = fields[22].GetUInt32();
430 creatureTemplate.unit_flags2 = fields[23].GetUInt32();
431 creatureTemplate.unit_flags3 = fields[24].GetUInt32();
432 creatureTemplate.family = CreatureFamily(fields[25].GetInt32());
433 creatureTemplate.trainer_class = uint32(fields[26].GetUInt8());
434 creatureTemplate.type = uint32(fields[27].GetUInt8());
435
436 for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
437 creatureTemplate.resistance[i] = 0;
438
439 for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i)
440 creatureTemplate.spells[i] = 0;
441
442 creatureTemplate.VehicleId = fields[28].GetUInt32();
443 creatureTemplate.AIName = fields[29].GetString();
444 creatureTemplate.MovementType = uint32(fields[30].GetUInt8());
445
446 if (!fields[31].IsNull())
447 creatureTemplate.Movement.HoverInitiallyEnabled = fields[31].GetBool();
448
449 if (!fields[32].IsNull())
450 creatureTemplate.Movement.Chase = static_cast<CreatureChaseMovementType>(fields[32].GetUInt8());
451
452 if (!fields[33].IsNull())
453 creatureTemplate.Movement.Random = static_cast<CreatureRandomMovementType>(fields[33].GetUInt8());
454
455 if (!fields[34].IsNull())
456 creatureTemplate.Movement.InteractionPauseTimer = fields[34].GetUInt32();
457
458 creatureTemplate.ModExperience = fields[35].GetFloat();
459 creatureTemplate.RacialLeader = fields[36].GetBool();
460 creatureTemplate.movementId = fields[37].GetUInt32();
461 creatureTemplate.WidgetSetID = fields[38].GetInt32();
462 creatureTemplate.WidgetSetUnitConditionID = fields[39].GetInt32();
463 creatureTemplate.RegenHealth = fields[40].GetBool();
464 creatureTemplate.CreatureImmunitiesId = fields[41].GetInt32();
465 creatureTemplate.flags_extra = fields[42].GetUInt32();
466 creatureTemplate.ScriptID = GetScriptId(fields[43].GetString());
467 creatureTemplate.StringId = fields[44].GetString();
468}
469
471{
472 uint32 oldMSTime = getMSTime();
473
474 // 0 1
475 QueryResult result = WorldDatabase.Query("SELECT CreatureID, MenuID FROM creature_template_gossip");
476
477 if (!result)
478 {
479 TC_LOG_INFO("server.loading", ">> Loaded 0 creature template gossip definitions. DB table `creature_template_gossip` is empty.");
480 return;
481 }
482
483 uint32 count = 0;
484
485 do
486 {
487 Field* fields = result->Fetch();
488
489 uint32 creatureID = fields[0].GetUInt32();
490 uint32 menuID = fields[1].GetUInt32();
491
492 CreatureTemplateContainer::iterator itr = _creatureTemplateStore.find(creatureID);
493 if (itr == _creatureTemplateStore.end())
494 {
495 TC_LOG_ERROR("sql.sql", "creature_template_gossip has gossip definitions for creature {} but this creature doesn't exist", creatureID);
496 continue;
497 }
498
499 GossipMenusMapBounds menuBounds = sObjectMgr->GetGossipMenusMapBounds(menuID);
500 if (menuBounds.first == menuBounds.second)
501 {
502 TC_LOG_ERROR("sql.sql", "creature_template_gossip has gossip definitions for menu id {} but this menu doesn't exist", menuID);
503 continue;
504 }
505
506 CreatureTemplate& creatureTemplate = itr->second;
507 creatureTemplate.GossipMenuIds.push_back(menuID);
508
509 ++count;
510
511 } while (result->NextRow());
512
513 TC_LOG_INFO("server.loading", ">> Loaded {} creature template gossip menus in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
514}
515
517{
518 uint32 oldMSTime = getMSTime();
519
520 // 0 1 2
521 QueryResult result = WorldDatabase.Query("SELECT CreatureID, School, Resistance FROM creature_template_resistance");
522
523 if (!result)
524 {
525 TC_LOG_INFO("server.loading", ">> Loaded 0 creature template resistance definitions. DB table `creature_template_resistance` is empty.");
526 return;
527 }
528
529 uint32 count = 0;
530
531 do
532 {
533 Field* fields = result->Fetch();
534
535 uint32 creatureID = fields[0].GetUInt32();
536 uint8 school = fields[1].GetUInt8();
537
538 if (school == SPELL_SCHOOL_NORMAL || school >= MAX_SPELL_SCHOOL)
539 {
540 TC_LOG_ERROR("sql.sql", "creature_template_resistance has resistance definitions for creature {} but this school {} doesn't exist", creatureID, school);
541 continue;
542 }
543
544 CreatureTemplateContainer::iterator itr = _creatureTemplateStore.find(creatureID);
545 if (itr == _creatureTemplateStore.end())
546 {
547 TC_LOG_ERROR("sql.sql", "creature_template_resistance has resistance definitions for creature {} but this creature doesn't exist", creatureID);
548 continue;
549 }
550
551 CreatureTemplate& creatureTemplate = itr->second;
552 creatureTemplate.resistance[school] = fields[2].GetInt16();
553
554 ++count;
555
556 } while (result->NextRow());
557
558 TC_LOG_INFO("server.loading", ">> Loaded {} creature template resistances in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
559}
560
562{
563 uint32 oldMSTime = getMSTime();
564
565 // 0 1 2
566 QueryResult result = WorldDatabase.Query("SELECT CreatureID, `Index`, Spell FROM creature_template_spell");
567
568 if (!result)
569 {
570 TC_LOG_INFO("server.loading", ">> Loaded 0 creature template spell definitions. DB table `creature_template_spell` is empty.");
571 return;
572 }
573
574 uint32 count = 0;
575
576 do
577 {
578 Field* fields = result->Fetch();
579
580 uint32 creatureID = fields[0].GetUInt32();
581 uint8 index = fields[1].GetUInt8();
582
583 if (index >= MAX_CREATURE_SPELLS)
584 {
585 TC_LOG_ERROR("sql.sql", "creature_template_spell has spell definitions for creature {} with a incorrect index {}", creatureID, index);
586 continue;
587 }
588
589 CreatureTemplateContainer::iterator itr = _creatureTemplateStore.find(creatureID);
590 if (itr == _creatureTemplateStore.end())
591 {
592 TC_LOG_ERROR("sql.sql", "creature_template_spell has spell definitions for creature {} but this creature doesn't exist", creatureID);
593 continue;
594 }
595
596 CreatureTemplate& creatureTemplate = itr->second;
597 creatureTemplate.spells[index] = fields[2].GetUInt32();;
598
599 ++count;
600
601 } while (result->NextRow());
602
603 TC_LOG_INFO("server.loading", ">> Loaded {} creature template spells in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
604}
605
607{
608 uint32 oldMSTime = getMSTime();
609
610 // 0 1 2 3
611 QueryResult result = WorldDatabase.Query("SELECT CreatureID, CreatureDisplayID, DisplayScale, Probability FROM creature_template_model ORDER BY Idx ASC");
612
613 if (!result)
614 {
615 TC_LOG_INFO("server.loading", ">> Loaded 0 creature template model definitions. DB table `creature_template_model` is empty.");
616 return;
617 }
618
619 uint32 count = 0;
620 do
621 {
622 Field* fields = result->Fetch();
623
624 uint32 creatureId = fields[0].GetUInt32();
625 uint32 creatureDisplayId = fields[1].GetUInt32();
626 float displayScale = fields[2].GetFloat();
627 float probability = fields[3].GetFloat();
628
629 CreatureTemplate const* cInfo = GetCreatureTemplate(creatureId);
630 if (!cInfo)
631 {
632 TC_LOG_ERROR("sql.sql", "Creature template (Entry: {}) does not exist but has a record in `creature_template_model`", creatureId);
633 continue;
634 }
635
636 CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(creatureDisplayId);
637 if (!displayEntry)
638 {
639 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) lists non-existing CreatureDisplayID id ({}), this can crash the client.", creatureId, creatureDisplayId);
640 continue;
641 }
642
643 CreatureModelInfo const* modelInfo = GetCreatureModelInfo(creatureDisplayId);
644 if (!modelInfo)
645 TC_LOG_ERROR("sql.sql", "No model data exist for `CreatureDisplayID` = {} listed by creature (Entry: {}).", creatureDisplayId, creatureId);
646
647 if (displayScale <= 0.0f)
648 displayScale = 1.0f;
649
650 const_cast<CreatureTemplate*>(cInfo)->Models.emplace_back(creatureDisplayId, displayScale, probability);
651
652 ++count;
653 }
654 while (result->NextRow());
655
656 TC_LOG_INFO("server.loading", ">> Loaded {} creature template models in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
657}
658
660{
661 uint32 oldMSTime = getMSTime();
662
663 // 0 1 2 3 4
664 QueryResult result = WorldDatabase.Query("SELECT CreatureID, CreatureIDVisibleToSummoner, GroundMountDisplayID, FlyingMountDisplayID, DespawnOnQuestsRemoved FROM creature_summoned_data");
665
666 if (!result)
667 {
668 TC_LOG_INFO("server.loading", ">> Loaded 0 creature summoned data definitions. DB table `creature_summoned_data` is empty.");
669 return;
670 }
671
672 do
673 {
674 Field* fields = result->Fetch();
675
676 uint32 creatureId = fields[0].GetUInt32();
677 if (!GetCreatureTemplate(creatureId))
678 {
679 TC_LOG_ERROR("sql.sql", "Table `creature_summoned_data` references non-existing creature {}, skipped", creatureId);
680 continue;
681 }
682
683 CreatureSummonedData& summonedData = _creatureSummonedDataStore[creatureId];
684
685 if (!fields[1].IsNull())
686 {
687 summonedData.CreatureIDVisibleToSummoner = fields[1].GetUInt32();
689 {
690 TC_LOG_ERROR("sql.sql", "Table `creature_summoned_data` references non-existing creature {} in CreatureIDVisibleToSummoner for creature {}, set to 0",
691 *summonedData.CreatureIDVisibleToSummoner, creatureId);
692 summonedData.CreatureIDVisibleToSummoner.reset();
693 }
694 }
695
696 if (!fields[2].IsNull())
697 {
698 summonedData.GroundMountDisplayID = fields[2].GetUInt32();
699 if (!sCreatureDisplayInfoStore.LookupEntry(*summonedData.GroundMountDisplayID))
700 {
701 TC_LOG_ERROR("sql.sql", "Table `creature_summoned_data` references non-existing display id {} in GroundMountDisplayID for creature {}, set to 0",
702 *summonedData.GroundMountDisplayID, creatureId);
703 summonedData.CreatureIDVisibleToSummoner.reset();
704 }
705 }
706
707 if (!fields[3].IsNull())
708 {
709 summonedData.FlyingMountDisplayID = fields[3].GetUInt32();
710 if (!sCreatureDisplayInfoStore.LookupEntry(*summonedData.FlyingMountDisplayID))
711 {
712 TC_LOG_ERROR("sql.sql", "Table `creature_summoned_data` references non-existing display id {} in FlyingMountDisplayID for creature {}, set to 0",
713 *summonedData.FlyingMountDisplayID, creatureId);
714 summonedData.GroundMountDisplayID.reset();
715 }
716 }
717
718 if (!fields[4].IsNull())
719 {
720 std::vector<uint32> questList;
721 for (std::string_view questStr : Trinity::Tokenize(fields[4].GetStringView(), ',', false))
722 {
723 Optional<uint32> questId = Trinity::StringTo<uint32>(questStr);
724 if (!questId)
725 continue;
726
727 Quest const* quest = GetQuestTemplate(*questId);
728 if (!quest)
729 {
730 TC_LOG_ERROR("sql.sql", "Table `creature_summoned_data` references non-existing quest {} in DespawnOnQuestsRemoved for creature {}, skipping",
731 *questId, creatureId);
732 continue;
733 }
734
735 questList.push_back(*questId);
736 }
737
738 if (!questList.empty())
739 summonedData.DespawnOnQuestsRemoved = std::move(questList);
740 }
741
742 } while (result->NextRow());
743
744 TC_LOG_INFO("server.loading", ">> Loaded {} creature summoned data definitions in {} ms", _creatureSummonedDataStore.size(), GetMSTimeDiffToNow(oldMSTime));
745}
746
748{
749 uint32 oldMSTime = getMSTime();
750
751 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13
752 QueryResult result = WorldDatabase.Query("SELECT entry, PathId, mount, StandState, AnimTier, VisFlags, SheathState, PvPFlags, emote, aiAnimKit, movementAnimKit, meleeAnimKit, visibilityDistanceType, auras FROM creature_template_addon");
753
754 if (!result)
755 {
756 TC_LOG_INFO("server.loading", ">> Loaded 0 creature template addon definitions. DB table `creature_template_addon` is empty.");
757 return;
758 }
759
760 uint32 count = 0;
761 do
762 {
763 Field* fields = result->Fetch();
764
765 uint32 entry = fields[0].GetUInt32();
766
767 if (!GetCreatureTemplate(entry))
768 {
769 TC_LOG_ERROR("sql.sql", "Creature template (Entry: {}) does not exist but has a record in `creature_template_addon`", entry);
770 continue;
771 }
772
773 CreatureAddon& creatureAddon = _creatureTemplateAddonStore[entry];
774
775 creatureAddon.PathId = fields[1].GetUInt32();
776 creatureAddon.mount = fields[2].GetUInt32();
777 creatureAddon.standState = fields[3].GetUInt8();
778 creatureAddon.animTier = fields[4].GetUInt8();
779 creatureAddon.visFlags = fields[5].GetUInt8();
780 creatureAddon.sheathState = fields[6].GetUInt8();
781 creatureAddon.pvpFlags = fields[7].GetUInt8();
782 creatureAddon.emote = fields[8].GetUInt32();
783 creatureAddon.aiAnimKit = fields[9].GetUInt16();
784 creatureAddon.movementAnimKit = fields[10].GetUInt16();
785 creatureAddon.meleeAnimKit = fields[11].GetUInt16();
786 creatureAddon.visibilityDistanceType = VisibilityDistanceType(fields[12].GetUInt8());
787
788 for (std::string_view aura : Trinity::Tokenize(fields[13].GetStringView(), ' ', false))
789 {
790 SpellInfo const* spellInfo = nullptr;
791 if (Optional<uint32> spellId = Trinity::StringTo<uint32>(aura))
792 spellInfo = sSpellMgr->GetSpellInfo(*spellId, DIFFICULTY_NONE);
793
794 if (!spellInfo)
795 {
796 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong spell '{}' defined in `auras` field in `creature_template_addon`.", entry, std::string(aura));
797 continue;
798 }
799
800 if (spellInfo->HasAura(SPELL_AURA_CONTROL_VEHICLE))
801 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has SPELL_AURA_CONTROL_VEHICLE aura {} defined in `auras` field in `creature_template_addon`.", entry, spellInfo->Id);
802
803 if (std::find(creatureAddon.auras.begin(), creatureAddon.auras.end(), spellInfo->Id) != creatureAddon.auras.end())
804 {
805 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has duplicate aura (spell {}) in `auras` field in `creature_template_addon`.", entry, spellInfo->Id);
806 continue;
807 }
808
809 if (spellInfo->GetDuration() > 0)
810 {
811 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has temporary aura (spell {}) in `auras` field in `creature_template_addon`.", entry, spellInfo->Id);
812 continue;
813 }
814
815 creatureAddon.auras.push_back(spellInfo->Id);
816 }
817
818 if (creatureAddon.mount)
819 {
820 if (!sCreatureDisplayInfoStore.LookupEntry(creatureAddon.mount))
821 {
822 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid displayInfoId ({}) for mount defined in `creature_template_addon`", entry, creatureAddon.mount);
823 creatureAddon.mount = 0;
824 }
825 }
826
827 if (creatureAddon.standState >= MAX_UNIT_STAND_STATE)
828 {
829 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid unit stand state ({}) defined in `creature_template_addon`. Truncated to 0.", entry, creatureAddon.standState);
830 creatureAddon.standState = 0;
831 }
832
833 if (AnimTier(creatureAddon.animTier) >= AnimTier::Max)
834 {
835 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid animation tier ({}) defined in `creature_template_addon`. Truncated to 0.", entry, creatureAddon.animTier);
836 creatureAddon.animTier = 0;
837 }
838
839 if (creatureAddon.sheathState >= MAX_SHEATH_STATE)
840 {
841 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid sheath state ({}) defined in `creature_template_addon`. Truncated to 0.", entry, creatureAddon.sheathState);
842 creatureAddon.sheathState = 0;
843 }
844
845 // PvPFlags don't need any checking for the time being since they cover the entire range of a byte
846
847 if (!sEmotesStore.LookupEntry(creatureAddon.emote))
848 {
849 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid emote ({}) defined in `creature_template_addon`.", entry, creatureAddon.emote);
850 creatureAddon.emote = 0;
851 }
852
853 if (creatureAddon.aiAnimKit && !sAnimKitStore.LookupEntry(creatureAddon.aiAnimKit))
854 {
855 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid aiAnimKit ({}) defined in `creature_template_addon`.", entry, creatureAddon.aiAnimKit);
856 creatureAddon.aiAnimKit = 0;
857 }
858
859 if (creatureAddon.movementAnimKit && !sAnimKitStore.LookupEntry(creatureAddon.movementAnimKit))
860 {
861 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid movementAnimKit ({}) defined in `creature_template_addon`.", entry, creatureAddon.movementAnimKit);
862 creatureAddon.movementAnimKit = 0;
863 }
864
865 if (creatureAddon.meleeAnimKit && !sAnimKitStore.LookupEntry(creatureAddon.meleeAnimKit))
866 {
867 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid meleeAnimKit ({}) defined in `creature_template_addon`.", entry, creatureAddon.meleeAnimKit);
868 creatureAddon.meleeAnimKit = 0;
869 }
870
872 {
873 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid visibilityDistanceType ({}) defined in `creature_template_addon`.",
874 entry, AsUnderlyingType(creatureAddon.visibilityDistanceType));
876 }
877
878 ++count;
879 }
880 while (result->NextRow());
881
882 TC_LOG_INFO("server.loading", ">> Loaded {} creature template addons in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
883}
884
886{
887 uint32 oldMSTime = getMSTime();
888
889 // 0 1
890 QueryResult result = WorldDatabase.Query("SELECT Entry, NoNPCDamageBelowHealthPct FROM creature_template_sparring");
891
892 if (!result)
893 {
894 TC_LOG_INFO("server.loading", ">> Loaded 0 creature template sparring definitions. DB table `creature_template_sparring` is empty.");
895 return;
896 }
897
898 uint32 count = 0;
899 do
900 {
901 Field* fields = result->Fetch();
902
903 uint32 entry = fields[0].GetUInt32();
904 float noNPCDamageBelowHealthPct = fields[1].GetFloat();
905
906 if (!sObjectMgr->GetCreatureTemplate(entry))
907 {
908 TC_LOG_ERROR("sql.sql", "Creature template (Entry: {}) does not exist but has a record in `creature_template_sparring`", entry);
909 continue;
910 }
911
912 if (noNPCDamageBelowHealthPct <= 0 || noNPCDamageBelowHealthPct > 100)
913 {
914 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid NoNPCDamageBelowHealthPct ({}) defined in `creature_template_sparring`. Skipping",
915 entry, noNPCDamageBelowHealthPct);
916 continue;
917 }
918 _creatureTemplateSparringStore[entry].push_back(noNPCDamageBelowHealthPct);
919
920 ++count;
921 } while (result->NextRow());
922
923 TC_LOG_INFO("server.loading", ">> Loaded {} creature template sparring rows in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
924}
925
927{
928 uint32 oldMSTime = getMSTime();
929
930 // 0 1 2 3 4 5
931 QueryResult result = WorldDatabase.Query("SELECT Entry, DifficultyID, LevelScalingDeltaMin, LevelScalingDeltaMax, ContentTuningID, HealthScalingExpansion, "
932 // 6 7 8 9 10 11 12
933 "HealthModifier, ManaModifier, ArmorModifier, DamageModifier, CreatureDifficultyID, TypeFlags, TypeFlags2, "
934 // 13 14 15 16 17
935 "LootID, PickPocketLootID, SkinLootID, GoldMin, GoldMax,"
936 // 18 19 20 21 22 23 24 25
937 "StaticFlags1, StaticFlags2, StaticFlags3, StaticFlags4, StaticFlags5, StaticFlags6, StaticFlags7, StaticFlags8 "
938 "FROM creature_template_difficulty ORDER BY Entry");
939
940 if (!result)
941 {
942 TC_LOG_INFO("server.loading", ">> Loaded 0 creature template difficulty definitions. DB table `creature_template_difficulty` is empty.");
943 return;
944 }
945
946 uint32 count = 0;
947 do
948 {
949 Field* fields = result->Fetch();
950
951 uint32 entry = fields[0].GetUInt32();
952 Difficulty difficulty = Difficulty(fields[1].GetUInt8());
953
954 auto itr = _creatureTemplateStore.find(entry);
955 if (itr == _creatureTemplateStore.end())
956 {
957 TC_LOG_ERROR("sql.sql", "Creature template (Entry: {}) does not exist but has a record in `creature_template_difficulty`", entry);
958 continue;
959 }
960
961 CreatureDifficulty creatureDifficulty;
962 creatureDifficulty.DeltaLevelMin = fields[2].GetInt16();
963 creatureDifficulty.DeltaLevelMax = fields[3].GetInt16();
964 creatureDifficulty.ContentTuningID = fields[4].GetInt32();
965 creatureDifficulty.HealthScalingExpansion = fields[5].GetInt32();
966 creatureDifficulty.HealthModifier = fields[6].GetFloat();
967 creatureDifficulty.ManaModifier = fields[7].GetFloat();
968 creatureDifficulty.ArmorModifier = fields[8].GetFloat();
969 creatureDifficulty.DamageModifier = fields[9].GetFloat();
970 creatureDifficulty.CreatureDifficultyID = fields[10].GetInt32();
971 creatureDifficulty.TypeFlags = fields[11].GetUInt32();
972 creatureDifficulty.TypeFlags2 = fields[12].GetUInt32();
973 creatureDifficulty.LootID = fields[13].GetUInt32();
974 creatureDifficulty.PickPocketLootID = fields[14].GetUInt32();
975 creatureDifficulty.SkinLootID = fields[15].GetUInt32();
976 creatureDifficulty.GoldMin = fields[16].GetUInt32();
977 creatureDifficulty.GoldMax = fields[17].GetUInt32();
978 creatureDifficulty.StaticFlags = CreatureStaticFlagsHolder(CreatureStaticFlags(fields[18].GetUInt32()), CreatureStaticFlags2(fields[19].GetUInt32()),
979 CreatureStaticFlags3(fields[20].GetUInt32()), CreatureStaticFlags4(fields[21].GetUInt32()), CreatureStaticFlags5(fields[22].GetUInt32()),
980 CreatureStaticFlags6(fields[23].GetUInt32()), CreatureStaticFlags7(fields[24].GetUInt32()), CreatureStaticFlags8(fields[25].GetUInt32()));
981
982 // TODO: Check if this still applies
983 creatureDifficulty.DamageModifier *= Creature::GetDamageMod(itr->second.Classification);
984
985 if (creatureDifficulty.HealthScalingExpansion < EXPANSION_LEVEL_CURRENT || creatureDifficulty.HealthScalingExpansion >= MAX_EXPANSIONS)
986 {
987 TC_LOG_ERROR("sql.sql", "Table `creature_template_difficulty` lists creature (ID: {}) with invalid `HealthScalingExpansion` {}. Ignored and set to 0.",
988 entry, creatureDifficulty.HealthScalingExpansion);
989 creatureDifficulty.HealthScalingExpansion = 0;
990 }
991
992 if (creatureDifficulty.GoldMin > creatureDifficulty.GoldMax)
993 {
994 TC_LOG_ERROR("sql.sql", "Table `creature_template_difficulty` lists creature (ID: {}) with `GoldMin` {} greater than `GoldMax` {}, setting `GoldMax` to {}.",
995 entry, creatureDifficulty.GoldMin, creatureDifficulty.GoldMax, creatureDifficulty.GoldMin);
996 creatureDifficulty.GoldMax = creatureDifficulty.GoldMin;
997 }
998
999 itr->second.difficultyStore[difficulty] = creatureDifficulty;
1000
1001 ++count;
1002 } while (result->NextRow());
1003
1004 TC_LOG_INFO("server.loading", ">> Loaded {} creature template difficulty data in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
1005}
1006
1008{
1009 if (!cInfo)
1010 return;
1011
1012 if (!cInfo->AIName.empty())
1013 {
1014 auto registryItem = sCreatureAIRegistry->GetRegistryItem(cInfo->AIName);
1015 if (!registryItem)
1016 {
1017 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has non-registered `AIName` '{}' set, removing", cInfo->Entry, cInfo->AIName);
1018 const_cast<CreatureTemplate*>(cInfo)->AIName.clear();
1019 }
1020 else
1021 {
1022 DBPermit const* permit = dynamic_cast<DBPermit const*>(registryItem);
1023 if (!ASSERT_NOTNULL(permit)->IsScriptNameAllowedInDB())
1024 {
1025 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has not-allowed `AIName` '{}' set, removing", cInfo->Entry, cInfo->AIName);
1026 const_cast<CreatureTemplate*>(cInfo)->AIName.clear();
1027 }
1028 }
1029 }
1030
1031 FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction);
1032 if (!factionTemplate)
1033 {
1034 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has non-existing faction template ({}). This can lead to crashes, set to faction 35.", cInfo->Entry, cInfo->faction);
1035 const_cast<CreatureTemplate*>(cInfo)->faction = sFactionTemplateStore.AssertEntry(35)->ID; // this might seem stupid but all shit will would break if faction 35 did not exist
1036 }
1037
1038 for (uint8 k = 0; k < MAX_KILL_CREDIT; ++k)
1039 {
1040 if (cInfo->KillCredit[k])
1041 {
1042 if (!GetCreatureTemplate(cInfo->KillCredit[k]))
1043 {
1044 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) lists non-existing creature entry {} in `KillCredit{}`.", cInfo->Entry, cInfo->KillCredit[k], k + 1);
1045 const_cast<CreatureTemplate*>(cInfo)->KillCredit[k] = 0;
1046 }
1047 }
1048 }
1049
1050 if (cInfo->Models.empty())
1051 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) does not have any existing display id in creature_template_model.", cInfo->Entry);
1052
1053 if (!cInfo->unit_class || ((1 << (cInfo->unit_class-1)) & CLASSMASK_ALL_CREATURES) == 0)
1054 {
1055 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid unit_class ({}) in creature_template. Set to 1 (UNIT_CLASS_WARRIOR).", cInfo->Entry, cInfo->unit_class);
1056 const_cast<CreatureTemplate*>(cInfo)->unit_class = UNIT_CLASS_WARRIOR;
1057 }
1058
1059 if (cInfo->dmgschool >= MAX_SPELL_SCHOOL)
1060 {
1061 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid spell school value ({}) in `dmgschool`.", cInfo->Entry, cInfo->dmgschool);
1062 const_cast<CreatureTemplate*>(cInfo)->dmgschool = SPELL_SCHOOL_NORMAL;
1063 }
1064
1065 if (cInfo->BaseAttackTime == 0)
1066 const_cast<CreatureTemplate*>(cInfo)->BaseAttackTime = BASE_ATTACK_TIME;
1067
1068 if (cInfo->RangeAttackTime == 0)
1069 const_cast<CreatureTemplate*>(cInfo)->RangeAttackTime = BASE_ATTACK_TIME;
1070
1071 if (cInfo->speed_walk == 0.0f)
1072 {
1073 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong value ({}) in speed_walk, set to 1.", cInfo->Entry, cInfo->speed_walk);
1074 const_cast<CreatureTemplate*>(cInfo)->speed_walk = 1.0f;
1075 }
1076
1077 if (cInfo->speed_run == 0.0f)
1078 {
1079 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong value ({}) in speed_run, set to 1.14286.", cInfo->Entry, cInfo->speed_run);
1080 const_cast<CreatureTemplate*>(cInfo)->speed_run = 1.14286f;
1081 }
1082
1083 if (cInfo->type && !sCreatureTypeStore.LookupEntry(cInfo->type))
1084 {
1085 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid creature type ({}) in `type`.", cInfo->Entry, cInfo->type);
1086 const_cast<CreatureTemplate*>(cInfo)->type = CREATURE_TYPE_HUMANOID;
1087 }
1088
1089 if (cInfo->family && !sCreatureFamilyStore.LookupEntry(cInfo->family))
1090 {
1091 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid creature family ({}) in `family`.", cInfo->Entry, cInfo->family);
1092 const_cast<CreatureTemplate*>(cInfo)->family = CREATURE_FAMILY_NONE;
1093 }
1094
1095 CheckCreatureMovement("creature_template_movement", cInfo->Entry, const_cast<CreatureTemplate*>(cInfo)->Movement);
1096
1097 if (cInfo->VehicleId)
1098 {
1099 VehicleEntry const* vehId = sVehicleStore.LookupEntry(cInfo->VehicleId);
1100 if (!vehId)
1101 {
1102 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has a non-existing VehicleId ({}). This *WILL* cause the client to freeze!", cInfo->Entry, cInfo->VehicleId);
1103 const_cast<CreatureTemplate*>(cInfo)->VehicleId = 0;
1104 }
1105 }
1106
1107 for (uint8 j = 0; j < MAX_CREATURE_SPELLS; ++j)
1108 {
1109 if (cInfo->spells[j] && !sSpellMgr->GetSpellInfo(cInfo->spells[j], DIFFICULTY_NONE))
1110 {
1111 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has non-existing Spell{} ({}), set to 0.", cInfo->Entry, j+1, cInfo->spells[j]);
1112 const_cast<CreatureTemplate*>(cInfo)->spells[j] = 0;
1113 }
1114 }
1115
1116 if (cInfo->MovementType >= MAX_DB_MOTION_TYPE)
1117 {
1118 TC_LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong movement generator type ({}), ignored and set to IDLE.", cInfo->Entry, cInfo->MovementType);
1119 const_cast<CreatureTemplate*>(cInfo)->MovementType = IDLE_MOTION_TYPE;
1120 }
1121
1122 if (cInfo->RequiredExpansion >= MAX_EXPANSIONS)
1123 {
1124 TC_LOG_ERROR("sql.sql", "Table `creature_template` lists creature (Entry: {}) with `RequiredExpansion` {}. Ignored and set to 0.", cInfo->Entry, cInfo->RequiredExpansion);
1125 const_cast<CreatureTemplate*>(cInfo)->RequiredExpansion = 0;
1126 }
1127
1128 if (uint32 badFlags = (cInfo->flags_extra & ~CREATURE_FLAG_EXTRA_DB_ALLOWED))
1129 {
1130 TC_LOG_ERROR("sql.sql", "Table `creature_template` lists creature (Entry: {}) with disallowed `flags_extra` {}, removing incorrect flag.", cInfo->Entry, badFlags);
1131 const_cast<CreatureTemplate*>(cInfo)->flags_extra &= CREATURE_FLAG_EXTRA_DB_ALLOWED;
1132 }
1133
1134 if (uint32 disallowedUnitFlags = (cInfo->unit_flags & ~UNIT_FLAG_ALLOWED))
1135 {
1136 TC_LOG_ERROR("sql.sql", "Table `creature_template` lists creature (Entry: {}) with disallowed `unit_flags` {}, removing incorrect flag.", cInfo->Entry, disallowedUnitFlags);
1137 const_cast<CreatureTemplate*>(cInfo)->unit_flags &= UNIT_FLAG_ALLOWED;
1138 }
1139
1140 if (uint32 disallowedUnitFlags2 = (cInfo->unit_flags2 & ~UNIT_FLAG2_ALLOWED))
1141 {
1142 TC_LOG_ERROR("sql.sql", "Table `creature_template` lists creature (Entry: {}) with disallowed `unit_flags2` {}, removing incorrect flag.", cInfo->Entry, disallowedUnitFlags2);
1143 const_cast<CreatureTemplate*>(cInfo)->unit_flags2 &= UNIT_FLAG2_ALLOWED;
1144 }
1145
1146 if (uint32 disallowedUnitFlags3 = (cInfo->unit_flags3 & ~UNIT_FLAG3_ALLOWED))
1147 {
1148 TC_LOG_ERROR("sql.sql", "Table `creature_template` lists creature (Entry: {}) with disallowed `unit_flags3` {}, removing incorrect flag.", cInfo->Entry, disallowedUnitFlags3);
1149 const_cast<CreatureTemplate*>(cInfo)->unit_flags3 &= UNIT_FLAG3_ALLOWED;
1150 }
1151
1152 if (!cInfo->GossipMenuIds.empty() && !(cInfo->npcflag & UNIT_NPC_FLAG_GOSSIP))
1153 TC_LOG_INFO("sql.sql", "Creature (Entry: {}) has assigned gossip menu, but npcflag does not include UNIT_NPC_FLAG_GOSSIP.", cInfo->Entry);
1154 else if (cInfo->GossipMenuIds.empty() && cInfo->npcflag & UNIT_NPC_FLAG_GOSSIP)
1155 TC_LOG_INFO("sql.sql", "Creature (Entry: {}) has npcflag UNIT_NPC_FLAG_GOSSIP, but gossip menu is unassigned.", cInfo->Entry);
1156
1157 if (cInfo->VignetteID && !sVignetteStore.HasRecord(cInfo->VignetteID))
1158 {
1159 TC_LOG_INFO("sql.sql", "Creature (Entry: {}) has non-existing Vignette {}, set to 0.", cInfo->Entry, cInfo->VignetteID);
1160 const_cast<CreatureTemplate*>(cInfo)->VignetteID = 0;
1161 }
1162}
1163
1164void ObjectMgr::CheckCreatureMovement(char const* table, uint64 id, CreatureMovementData& creatureMovement)
1165{
1166 if (creatureMovement.Chase >= CreatureChaseMovementType::Max)
1167 {
1168 TC_LOG_ERROR("sql.sql", "`{}`.`Chase` wrong value ({}) for Id {}, setting to Run.",
1169 table, uint32(creatureMovement.Chase), id);
1170 creatureMovement.Chase = CreatureChaseMovementType::Run;
1171 }
1172
1173 if (creatureMovement.Random >= CreatureRandomMovementType::Max)
1174 {
1175 TC_LOG_ERROR("sql.sql", "`{}`.`Random` wrong value ({}) for Id {}, setting to Walk.",
1176 table, uint32(creatureMovement.Random), id);
1177 creatureMovement.Random = CreatureRandomMovementType::Walk;
1178 }
1179}
1180
1182{
1183 uint32 oldMSTime = getMSTime();
1184
1185 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13
1186 QueryResult result = WorldDatabase.Query("SELECT guid, PathId, mount, StandState, AnimTier, VisFlags, SheathState, PvPFlags, emote, aiAnimKit, movementAnimKit, meleeAnimKit, visibilityDistanceType, auras FROM creature_addon");
1187
1188 if (!result)
1189 {
1190 TC_LOG_INFO("server.loading", ">> Loaded 0 creature addon definitions. DB table `creature_addon` is empty.");
1191 return;
1192 }
1193
1194 uint32 count = 0;
1195 do
1196 {
1197 Field* fields = result->Fetch();
1198
1199 ObjectGuid::LowType guid = fields[0].GetUInt64();
1200
1201 CreatureData const* creData = GetCreatureData(guid);
1202 if (!creData)
1203 {
1204 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) does not exist but has a record in `creature_addon`", guid);
1205 continue;
1206 }
1207
1208 CreatureAddon& creatureAddon = _creatureAddonStore[guid];
1209
1210 creatureAddon.PathId = fields[1].GetUInt32();
1211 if (creData->movementType == WAYPOINT_MOTION_TYPE && !creatureAddon.PathId)
1212 {
1213 const_cast<CreatureData*>(creData)->movementType = IDLE_MOTION_TYPE;
1214 TC_LOG_ERROR("sql.sql", "Creature (GUID {}) has movement type set to WAYPOINT_MOTION_TYPE but no path assigned", guid);
1215 }
1216
1217 creatureAddon.mount = fields[2].GetUInt32();
1218 creatureAddon.standState = fields[3].GetUInt8();
1219 creatureAddon.animTier = fields[4].GetUInt8();
1220 creatureAddon.visFlags = fields[5].GetUInt8();
1221 creatureAddon.sheathState = fields[6].GetUInt8();
1222 creatureAddon.pvpFlags = fields[7].GetUInt8();
1223 creatureAddon.emote = fields[8].GetUInt32();
1224 creatureAddon.aiAnimKit = fields[9].GetUInt16();
1225 creatureAddon.movementAnimKit = fields[10].GetUInt16();
1226 creatureAddon.meleeAnimKit = fields[11].GetUInt16();
1227 creatureAddon.visibilityDistanceType = VisibilityDistanceType(fields[12].GetUInt8());
1228
1229 for (std::string_view aura : Trinity::Tokenize(fields[13].GetStringView(), ' ', false))
1230 {
1231 SpellInfo const* spellInfo = nullptr;
1232 if (Optional<uint32> spellId = Trinity::StringTo<uint32>(aura))
1233 spellInfo = sSpellMgr->GetSpellInfo(*spellId, DIFFICULTY_NONE);
1234
1235 if (!spellInfo)
1236 {
1237 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has wrong spell '{}' defined in `auras` field in `creature_addon`.", guid, std::string(aura));
1238 continue;
1239 }
1240
1241 if (spellInfo->HasAura(SPELL_AURA_CONTROL_VEHICLE))
1242 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has SPELL_AURA_CONTROL_VEHICLE aura {} defined in `auras` field in `creature_addon`.", guid, spellInfo->Id);
1243
1244 if (std::find(creatureAddon.auras.begin(), creatureAddon.auras.end(), spellInfo->Id) != creatureAddon.auras.end())
1245 {
1246 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has duplicate aura (spell {}) in `auras` field in `creature_addon`.", guid, spellInfo->Id);
1247 continue;
1248 }
1249
1250 if (spellInfo->GetDuration() > 0)
1251 {
1252 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has temporary aura (spell {}) in `auras` field in `creature_addon`.", guid, spellInfo->Id);
1253 continue;
1254 }
1255
1256 creatureAddon.auras.push_back(spellInfo->Id);
1257 }
1258
1259 if (creatureAddon.mount)
1260 {
1261 if (!sCreatureDisplayInfoStore.LookupEntry(creatureAddon.mount))
1262 {
1263 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has invalid displayInfoId ({}) for mount defined in `creature_addon`", guid, creatureAddon.mount);
1264 creatureAddon.mount = 0;
1265 }
1266 }
1267
1268 if (creatureAddon.standState >= MAX_UNIT_STAND_STATE)
1269 {
1270 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has invalid unit stand state ({}) defined in `creature_addon`. Truncated to 0.", guid, creatureAddon.standState);
1271 creatureAddon.standState = 0;
1272 }
1273
1274 if (AnimTier(creatureAddon.animTier) >= AnimTier::Max)
1275 {
1276 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has invalid animation tier ({}) defined in `creature_addon`. Truncated to 0.", guid, creatureAddon.animTier);
1277 creatureAddon.animTier = 0;
1278 }
1279
1280 if (creatureAddon.sheathState >= MAX_SHEATH_STATE)
1281 {
1282 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has invalid sheath state ({}) defined in `creature_addon`. Truncated to 0.", guid, creatureAddon.sheathState);
1283 creatureAddon.sheathState = 0;
1284 }
1285
1286 // PvPFlags don't need any checking for the time being since they cover the entire range of a byte
1287
1288 if (!sEmotesStore.LookupEntry(creatureAddon.emote))
1289 {
1290 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has invalid emote ({}) defined in `creature_addon`.", guid, creatureAddon.emote);
1291 creatureAddon.emote = 0;
1292 }
1293
1294 if (creatureAddon.aiAnimKit && !sAnimKitStore.LookupEntry(creatureAddon.aiAnimKit))
1295 {
1296 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has invalid aiAnimKit ({}) defined in `creature_addon`.", guid, creatureAddon.aiAnimKit);
1297 creatureAddon.aiAnimKit = 0;
1298 }
1299
1300 if (creatureAddon.movementAnimKit && !sAnimKitStore.LookupEntry(creatureAddon.movementAnimKit))
1301 {
1302 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has invalid movementAnimKit ({}) defined in `creature_addon`.", guid, creatureAddon.movementAnimKit);
1303 creatureAddon.movementAnimKit = 0;
1304 }
1305
1306 if (creatureAddon.meleeAnimKit && !sAnimKitStore.LookupEntry(creatureAddon.meleeAnimKit))
1307 {
1308 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has invalid meleeAnimKit ({}) defined in `creature_addon`.", guid, creatureAddon.meleeAnimKit);
1309 creatureAddon.meleeAnimKit = 0;
1310 }
1311
1313 {
1314 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) has invalid visibilityDistanceType ({}) defined in `creature_addon`.",
1315 guid, AsUnderlyingType(creatureAddon.visibilityDistanceType));
1317 }
1318
1319 ++count;
1320 }
1321 while (result->NextRow());
1322
1323 TC_LOG_INFO("server.loading", ">> Loaded {} creature addons in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
1324}
1325
1327{
1328 uint32 oldMSTime = getMSTime();
1329
1330 // 0 1 2 3 4 5 6 7 8
1331 QueryResult result = WorldDatabase.Query("SELECT guid, parent_rotation0, parent_rotation1, parent_rotation2, parent_rotation3, invisibilityType, invisibilityValue, WorldEffectID, AIAnimKitID FROM gameobject_addon");
1332
1333 if (!result)
1334 {
1335 TC_LOG_INFO("server.loading", ">> Loaded 0 gameobject addon definitions. DB table `gameobject_addon` is empty.");
1336 return;
1337 }
1338
1339 uint32 count = 0;
1340 do
1341 {
1342 Field* fields = result->Fetch();
1343
1344 ObjectGuid::LowType guid = fields[0].GetUInt64();
1345
1346 GameObjectData const* goData = GetGameObjectData(guid);
1347 if (!goData)
1348 {
1349 TC_LOG_ERROR("sql.sql", "GameObject (GUID: {}) does not exist but has a record in `gameobject_addon`", guid);
1350 continue;
1351 }
1352
1353 GameObjectAddon& gameObjectAddon = _gameObjectAddonStore[guid];
1354 gameObjectAddon.ParentRotation = QuaternionData(fields[1].GetFloat(), fields[2].GetFloat(), fields[3].GetFloat(), fields[4].GetFloat());
1355 gameObjectAddon.invisibilityType = InvisibilityType(fields[5].GetUInt8());
1356 gameObjectAddon.InvisibilityValue = fields[6].GetUInt32();
1357 gameObjectAddon.WorldEffectID = fields[7].GetUInt32();
1358 gameObjectAddon.AIAnimKitID = fields[8].GetUInt32();
1359
1360 if (gameObjectAddon.invisibilityType >= TOTAL_INVISIBILITY_TYPES)
1361 {
1362 TC_LOG_ERROR("sql.sql", "GameObject (GUID: {}) has invalid InvisibilityType in `gameobject_addon`, disabled invisibility", guid);
1363 gameObjectAddon.invisibilityType = INVISIBILITY_GENERAL;
1364 gameObjectAddon.InvisibilityValue = 0;
1365 }
1366
1367 if (gameObjectAddon.invisibilityType && !gameObjectAddon.InvisibilityValue)
1368 {
1369 TC_LOG_ERROR("sql.sql", "GameObject (GUID: {}) has InvisibilityType set but has no InvisibilityValue in `gameobject_addon`, set to 1", guid);
1370 gameObjectAddon.InvisibilityValue = 1;
1371 }
1372
1373 if (!gameObjectAddon.ParentRotation.isUnit())
1374 {
1375 TC_LOG_ERROR("sql.sql", "GameObject (GUID: {}) has invalid parent rotation in `gameobject_addon`, set to default", guid);
1376 gameObjectAddon.ParentRotation = QuaternionData();
1377 }
1378
1379 if (gameObjectAddon.WorldEffectID && !sWorldEffectStore.LookupEntry(gameObjectAddon.WorldEffectID))
1380 {
1381 TC_LOG_ERROR("sql.sql", "GameObject (GUID: {}) has invalid WorldEffectID ({}) in `gameobject_addon`, set to 0.", guid, gameObjectAddon.WorldEffectID);
1382 gameObjectAddon.WorldEffectID = 0;
1383 }
1384
1385 if (gameObjectAddon.AIAnimKitID && !sAnimKitStore.LookupEntry(gameObjectAddon.AIAnimKitID))
1386 {
1387 TC_LOG_ERROR("sql.sql", "GameObject (GUID: {}) has invalid AIAnimKitID ({}) in `gameobject_addon`, set to 0.", guid, gameObjectAddon.AIAnimKitID);
1388 gameObjectAddon.AIAnimKitID = 0;
1389 }
1390
1391 ++count;
1392 }
1393 while (result->NextRow());
1394
1395 TC_LOG_INFO("server.loading", ">> Loaded {} gameobject addons in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
1396}
1397
1399{
1400 GameObjectAddonContainer::const_iterator itr = _gameObjectAddonStore.find(lowguid);
1401 if (itr != _gameObjectAddonStore.end())
1402 return &(itr->second);
1403
1404 return nullptr;
1405}
1406
1408{
1409 CreatureAddonContainer::const_iterator itr = _creatureAddonStore.find(lowguid);
1410 if (itr != _creatureAddonStore.end())
1411 return &(itr->second);
1412
1413 return nullptr;
1414}
1415
1417{
1418 CreatureTemplateAddonContainer::const_iterator itr = _creatureTemplateAddonStore.find(entry);
1419 if (itr != _creatureTemplateAddonStore.end())
1420 return &(itr->second);
1421
1422 return nullptr;
1423}
1424
1425std::vector<float> const* ObjectMgr::GetCreatureTemplateSparringValues(uint32 entry) const
1426{
1428}
1429
1431{
1433}
1434
1436{
1437 EquipmentInfoContainer::const_iterator itr = _equipmentInfoStore.find(entry);
1438 if (itr == _equipmentInfoStore.end())
1439 return nullptr;
1440
1441 if (itr->second.empty())
1442 return nullptr;
1443
1444 if (id == -1) // select a random element
1445 {
1446 EquipmentInfoContainerInternal::const_iterator ritr = itr->second.begin();
1447 std::advance(ritr, urand(0u, itr->second.size() - 1));
1448 id = std::distance(itr->second.begin(), ritr) + 1;
1449 return &ritr->second;
1450 }
1451 else
1452 {
1453 EquipmentInfoContainerInternal::const_iterator itr2 = itr->second.find(id);
1454 if (itr2 != itr->second.end())
1455 return &itr2->second;
1456 }
1457
1458 return nullptr;
1459}
1460
1462{
1463 uint32 oldMSTime = getMSTime();
1464
1465 // 0 1 2 3 4
1466 QueryResult result = WorldDatabase.Query("SELECT CreatureID, ID, ItemID1, AppearanceModID1, ItemVisual1, "
1467 // 5 6 7
1468 "ItemID2, AppearanceModID2, ItemVisual2, "
1469 // 8 9 10
1470 "ItemID3, AppearanceModID3, ItemVisual3 "
1471 "FROM creature_equip_template");
1472
1473 if (!result)
1474 {
1475 TC_LOG_INFO("server.loading", ">> Loaded 0 creature equipment templates. DB table `creature_equip_template` is empty!");
1476 return;
1477 }
1478
1479 uint32 count = 0;
1480 do
1481 {
1482 Field* fields = result->Fetch();
1483
1484 uint32 entry = fields[0].GetUInt32();
1485
1486 if (!GetCreatureTemplate(entry))
1487 {
1488 TC_LOG_ERROR("sql.sql", "Creature template (CreatureID: {}) does not exist but has a record in `creature_equip_template`", entry);
1489 continue;
1490 }
1491
1492 uint8 id = fields[1].GetUInt8();
1493 if (!id)
1494 {
1495 TC_LOG_ERROR("sql.sql", "Creature equipment template with id 0 found for creature {}, skipped.", entry);
1496 continue;
1497 }
1498
1499 EquipmentInfo& equipmentInfo = _equipmentInfoStore[entry][id];
1500 for (uint8 i = 0; i < MAX_EQUIPMENT_ITEMS; ++i)
1501 {
1502 equipmentInfo.Items[i].ItemId = fields[2 + i * 3].GetUInt32();
1503 equipmentInfo.Items[i].AppearanceModId = fields[3 + i * 3].GetUInt16();
1504 equipmentInfo.Items[i].ItemVisual = fields[4 + i * 3].GetUInt16();
1505
1506 if (!equipmentInfo.Items[i].ItemId)
1507 continue;
1508
1509 ItemEntry const* dbcItem = sItemStore.LookupEntry(equipmentInfo.Items[i].ItemId);
1510 if (!dbcItem)
1511 {
1512 TC_LOG_ERROR("sql.sql", "Unknown item (ID={}) in creature_equip_template.ItemID{} for CreatureID = {} and ID={}, forced to 0.",
1513 equipmentInfo.Items[i].ItemId, i + 1, entry, id);
1514 equipmentInfo.Items[i].ItemId = 0;
1515 continue;
1516 }
1517
1518 // AppearanceModId 0 is always valid
1519 if (equipmentInfo.Items[i].AppearanceModId && !sDB2Manager.GetItemModifiedAppearance(equipmentInfo.Items[i].ItemId, equipmentInfo.Items[i].AppearanceModId))
1520 {
1521 TC_LOG_ERROR("sql.sql", "Unknown item appearance for (ID={}, AppearanceModID={}) pair in creature_equip_template.ItemID{} creature_equip_template.AppearanceModID{} "
1522 "for CreatureID = {} and ID={}, forced to default.",
1523 equipmentInfo.Items[i].ItemId, equipmentInfo.Items[i].AppearanceModId, i + 1, i + 1, entry, id);
1524 if (ItemModifiedAppearanceEntry const* defaultAppearance = sDB2Manager.GetDefaultItemModifiedAppearance(equipmentInfo.Items[i].ItemId))
1525 equipmentInfo.Items[i].AppearanceModId = defaultAppearance->ItemAppearanceModifierID;
1526 else
1527 equipmentInfo.Items[i].AppearanceModId = 0;
1528 continue;
1529 }
1530
1531 if (std::ranges::none_of(InventoryTypesEquipable, [dbcItem](InventoryType inventoryType) { return inventoryType == dbcItem->InventoryType; }))
1532 {
1533 TC_LOG_ERROR("sql.sql", "Item (ID={}) in creature_equip_template.ItemID{} for CreatureID = {} and ID = {} is not equipable in a hand, forced to 0.",
1534 equipmentInfo.Items[i].ItemId, i + 1, entry, id);
1535 equipmentInfo.Items[i].ItemId = 0;
1536 }
1537 }
1538
1539 ++count;
1540 }
1541 while (result->NextRow());
1542
1543 TC_LOG_INFO("server.loading", ">> Loaded {} equipment templates in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
1544}
1545
1547{
1548 uint32 oldMSTime = getMSTime();
1549
1551
1552 // Load the data from creature_movement_override and if NULL fallback to creature_template_movement
1553 QueryResult result = WorldDatabase.Query(
1554 "SELECT cmo.SpawnId,"
1555 "COALESCE(cmo.HoverInitiallyEnabled, ctm.HoverInitiallyEnabled),"
1556 "COALESCE(cmo.Chase, ctm.Chase),"
1557 "COALESCE(cmo.Random, ctm.Random),"
1558 "COALESCE(cmo.InteractionPauseTimer, ctm.InteractionPauseTimer) "
1559 "FROM creature_movement_override AS cmo "
1560 "LEFT JOIN creature AS c ON c.guid = cmo.SpawnId "
1561 "LEFT JOIN creature_template_movement AS ctm ON ctm.CreatureId = c.id");
1562
1563 if (!result)
1564 {
1565 TC_LOG_INFO("server.loading", ">> Loaded 0 creature movement overrides. DB table `creature_movement_override` is empty!");
1566 return;
1567 }
1568
1569 do
1570 {
1571 Field* fields = result->Fetch();
1572 ObjectGuid::LowType spawnId = fields[0].GetUInt64();
1573 if (!GetCreatureData(spawnId))
1574 {
1575 TC_LOG_ERROR("sql.sql", "Creature (GUID: {}) does not exist but has a record in `creature_movement_override`", spawnId);
1576 continue;
1577 }
1578
1580 if (!fields[1].IsNull())
1581 movement.HoverInitiallyEnabled = fields[1].GetBool();
1582 if (!fields[2].IsNull())
1583 movement.Chase = static_cast<CreatureChaseMovementType>(fields[2].GetUInt8());
1584 if (!fields[3].IsNull())
1585 movement.Random = static_cast<CreatureRandomMovementType>(fields[3].GetUInt8());
1586 if (!fields[4].IsNull())
1587 movement.InteractionPauseTimer = fields[4].GetUInt32();
1588
1589 CheckCreatureMovement("creature_movement_override", spawnId, movement);
1590 }
1591 while (result->NextRow());
1592
1593 TC_LOG_INFO("server.loading", ">> Loaded {} movement overrides in {} ms", _creatureMovementOverrides.size(), GetMSTimeDiffToNow(oldMSTime));
1594}
1595
1597{
1598 CreatureModelContainer::const_iterator itr = _creatureModelStore.find(modelId);
1599 if (itr != _creatureModelStore.end())
1600 return &(itr->second);
1601
1602 return nullptr;
1603}
1604
1606{
1608}
1609
1610CreatureModel const* ObjectMgr::ChooseDisplayId(CreatureTemplate const* cinfo, CreatureData const* data /*= nullptr*/)
1611{
1612 // Load creature model (display id)
1613 if (data && data->display)
1614 return &*data->display;
1615
1617 if (CreatureModel const* model = cinfo->GetRandomValidModel())
1618 return model;
1619
1620 // Triggers by default receive the invisible model
1621 return cinfo->GetFirstInvisibleModel();
1622}
1623
1624void ObjectMgr::ChooseCreatureFlags(CreatureTemplate const* cInfo, uint64* npcFlags, uint32* unitFlags, uint32* unitFlags2, uint32* unitFlags3, CreatureStaticFlagsHolder const& staticFlags, CreatureData const* data /*= nullptr*/)
1625{
1626#define ChooseCreatureFlagSource(field) ((data && data->field.has_value()) ? *data->field : cInfo->field)
1627
1628 if (npcFlags)
1629 *npcFlags = ChooseCreatureFlagSource(npcflag);
1630
1631 if (unitFlags)
1632 {
1633 *unitFlags = ChooseCreatureFlagSource(unit_flags);
1634 if (staticFlags.HasFlag(CREATURE_STATIC_FLAG_CAN_SWIM))
1635 *unitFlags |= UNIT_FLAG_CAN_SWIM;
1636
1638 *unitFlags |= UNIT_FLAG_CANT_SWIM;
1639 }
1640
1641 if (unitFlags2)
1642 {
1643 *unitFlags2 = ChooseCreatureFlagSource(unit_flags2);
1645 *unitFlags2 |= UNIT_FLAG2_CANNOT_TURN;
1646
1648 *unitFlags2 |= UNIT_FLAG2_INTERACT_WHILE_HOSTILE;
1649 }
1650
1651 if (unitFlags3)
1652 {
1653 *unitFlags3 = ChooseCreatureFlagSource(unit_flags3);
1656 }
1657
1658#undef ChooseCreatureFlagSource
1659}
1660
1662{
1663 CreatureModelInfo const* modelInfo = GetCreatureModelInfo(model->CreatureDisplayID);
1664 if (!modelInfo)
1665 return nullptr;
1666
1667 // If a model for another gender exists, 50% chance to use it
1668 if (modelInfo->displayId_other_gender != 0 && urand(0, 1) == 0)
1669 {
1670 CreatureModelInfo const* minfo_tmp = GetCreatureModelInfo(modelInfo->displayId_other_gender);
1671 if (!minfo_tmp)
1672 TC_LOG_ERROR("sql.sql", "Model (Entry: {}) has modelid_other_gender {} not found in table `creature_model_info`. ", model->CreatureDisplayID, modelInfo->displayId_other_gender);
1673 else
1674 {
1675 // DisplayID changed
1676 model->CreatureDisplayID = modelInfo->displayId_other_gender;
1677 if (creatureTemplate)
1678 {
1679 auto itr = std::find_if(creatureTemplate->Models.begin(), creatureTemplate->Models.end(), [&](CreatureModel const& templateModel)
1680 {
1681 return templateModel.CreatureDisplayID == modelInfo->displayId_other_gender;
1682 });
1683 if (itr != creatureTemplate->Models.end())
1684 *model = *itr;
1685 }
1686 return minfo_tmp;
1687 }
1688 }
1689
1690 return modelInfo;
1691}
1692
1694{
1695 uint32 oldMSTime = getMSTime();
1696
1697 QueryResult result = WorldDatabase.Query("SELECT DisplayID, BoundingRadius, CombatReach, DisplayID_Other_Gender FROM creature_model_info");
1698
1699 if (!result)
1700 {
1701 TC_LOG_INFO("server.loading", ">> Loaded 0 creature model definitions. DB table `creature_model_info` is empty.");
1702 return;
1703 }
1704
1705 _creatureModelStore.reserve(result->GetRowCount());
1706 uint32 count = 0;
1707
1708 // List of model FileDataIDs that the client treats as invisible stalker
1709 uint32 trigggerCreatureModelFileID[5] = { 124640, 124641, 124642, 343863, 439302 };
1710
1711 do
1712 {
1713 Field* fields = result->Fetch();
1714
1715 uint32 displayId = fields[0].GetUInt32();
1716
1717 CreatureDisplayInfoEntry const* creatureDisplay = sCreatureDisplayInfoStore.LookupEntry(displayId);
1718 if (!creatureDisplay)
1719 {
1720 TC_LOG_ERROR("sql.sql", "Table `creature_model_info` has a non-existent DisplayID (ID: {}). Skipped.", displayId);
1721 continue;
1722 }
1723
1724 CreatureModelInfo& modelInfo = _creatureModelStore[displayId];
1725
1726 modelInfo.bounding_radius = fields[1].GetFloat();
1727 modelInfo.combat_reach = fields[2].GetFloat();
1728 modelInfo.displayId_other_gender = fields[3].GetUInt32();
1729 modelInfo.gender = creatureDisplay->Gender;
1730 modelInfo.is_trigger = false;
1731
1732 // Checks
1733
1734 // to remove when the purpose of GENDER_UNKNOWN is known
1735 if (modelInfo.gender == GENDER_UNKNOWN)
1736 {
1737 // We don't need more errors
1738 //TC_LOG_ERROR("sql.sql", "Table `creature_model_info` has an unimplemented Gender (ID: {}) being used by DisplayID (ID: {}). Gender set to GENDER_MALE.", modelInfo.gender, modelId);
1739 modelInfo.gender = GENDER_MALE;
1740 }
1741
1742 if (modelInfo.displayId_other_gender && !sCreatureDisplayInfoStore.LookupEntry(modelInfo.displayId_other_gender))
1743 {
1744 TC_LOG_ERROR("sql.sql", "Table `creature_model_info` has a non-existent DisplayID_Other_Gender (ID: {}) being used by DisplayID (ID: {}).", modelInfo.displayId_other_gender, displayId);
1745 modelInfo.displayId_other_gender = 0;
1746 }
1747
1748 if (modelInfo.combat_reach < 0.1f)
1750
1751 if (CreatureModelDataEntry const* modelData = sCreatureModelDataStore.LookupEntry(creatureDisplay->ModelID))
1752 {
1753 for (uint32 i = 0; i < 5; ++i)
1754 {
1755 if (modelData->FileDataID == trigggerCreatureModelFileID[i])
1756 {
1757 modelInfo.is_trigger = true;
1758 break;
1759 }
1760 }
1761 }
1762
1763 ++count;
1764 }
1765 while (result->NextRow());
1766
1767 TC_LOG_INFO("server.loading", ">> Loaded {} creature model based info in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
1768}
1769
1771{
1772 uint32 oldMSTime = getMSTime();
1773
1774 _linkedRespawnStore.clear();
1775 // 0 1 2
1776 QueryResult result = WorldDatabase.Query("SELECT guid, linkedGuid, linkType FROM linked_respawn ORDER BY guid ASC");
1777
1778 if (!result)
1779 {
1780 TC_LOG_INFO("server.loading", ">> Loaded 0 linked respawns. DB table `linked_respawn` is empty.");
1781 return;
1782 }
1783
1784 do
1785 {
1786 Field* fields = result->Fetch();
1787
1788 ObjectGuid::LowType guidLow = fields[0].GetUInt64();
1789 ObjectGuid::LowType linkedGuidLow = fields[1].GetUInt64();
1790 uint8 linkType = fields[2].GetUInt8();
1791
1792 ObjectGuid guid, linkedGuid;
1793 bool error = false;
1794 switch (linkType)
1795 {
1797 {
1798 CreatureData const* slave = GetCreatureData(guidLow);
1799 if (!slave)
1800 {
1801 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature (guid) '{}' not found in creature table", guidLow);
1802 error = true;
1803 break;
1804 }
1805
1806 CreatureData const* master = GetCreatureData(linkedGuidLow);
1807 if (!master)
1808 {
1809 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature (linkedGuid) '{}' not found in creature table", linkedGuidLow);
1810 error = true;
1811 break;
1812 }
1813
1814 MapEntry const* const map = sMapStore.LookupEntry(master->mapId);
1815 if (!map || !map->Instanceable() || (master->mapId != slave->mapId))
1816 {
1817 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '{}' linking to Creature '{}' on an unpermitted map.", guidLow, linkedGuidLow);
1818 error = true;
1819 break;
1820 }
1821
1822 // they must have a possibility to meet (normal/heroic difficulty)
1823 if (!Trinity::Containers::Intersects(master->spawnDifficulties.begin(), master->spawnDifficulties.end(), slave->spawnDifficulties.begin(), slave->spawnDifficulties.end()))
1824 {
1825 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '{}' linking to Creature '{}' with not corresponding spawnMask", guidLow, linkedGuidLow);
1826 error = true;
1827 break;
1828 }
1829
1830 guid = ObjectGuid::Create<HighGuid::Creature>(slave->mapId, slave->id, guidLow);
1831 linkedGuid = ObjectGuid::Create<HighGuid::Creature>(master->mapId, master->id, linkedGuidLow);
1832 break;
1833 }
1835 {
1836 CreatureData const* slave = GetCreatureData(guidLow);
1837 if (!slave)
1838 {
1839 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature (guid) '{}' not found in creature table", guidLow);
1840 error = true;
1841 break;
1842 }
1843
1844 GameObjectData const* master = GetGameObjectData(linkedGuidLow);
1845 if (!master)
1846 {
1847 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (linkedGuid) '{}' not found in gameobject table", linkedGuidLow);
1848 error = true;
1849 break;
1850 }
1851
1852 MapEntry const* const map = sMapStore.LookupEntry(master->mapId);
1853 if (!map || !map->Instanceable() || (master->mapId != slave->mapId))
1854 {
1855 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '{}' linking to Gameobject '{}' on an unpermitted map.", guidLow, linkedGuidLow);
1856 error = true;
1857 break;
1858 }
1859
1860 // they must have a possibility to meet (normal/heroic difficulty)
1861 if (!Trinity::Containers::Intersects(master->spawnDifficulties.begin(), master->spawnDifficulties.end(), slave->spawnDifficulties.begin(), slave->spawnDifficulties.end()))
1862 {
1863 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '{}' linking to Gameobject '{}' with not corresponding spawnMask", guidLow, linkedGuidLow);
1864 error = true;
1865 break;
1866 }
1867
1868 guid = ObjectGuid::Create<HighGuid::Creature>(slave->mapId, slave->id, guidLow);
1869 linkedGuid = ObjectGuid::Create<HighGuid::GameObject>(master->mapId, master->id, linkedGuidLow);
1870 break;
1871 }
1873 {
1874 GameObjectData const* slave = GetGameObjectData(guidLow);
1875 if (!slave)
1876 {
1877 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (guid) '{}' not found in gameobject table", guidLow);
1878 error = true;
1879 break;
1880 }
1881
1882 GameObjectData const* master = GetGameObjectData(linkedGuidLow);
1883 if (!master)
1884 {
1885 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (linkedGuid) '{}' not found in gameobject table", linkedGuidLow);
1886 error = true;
1887 break;
1888 }
1889
1890 MapEntry const* const map = sMapStore.LookupEntry(master->mapId);
1891 if (!map || !map->Instanceable() || (master->mapId != slave->mapId))
1892 {
1893 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '{}' linking to Gameobject '{}' on an unpermitted map.", guidLow, linkedGuidLow);
1894 error = true;
1895 break;
1896 }
1897
1898 // they must have a possibility to meet (normal/heroic difficulty)
1899 if (!Trinity::Containers::Intersects(master->spawnDifficulties.begin(), master->spawnDifficulties.end(), slave->spawnDifficulties.begin(), slave->spawnDifficulties.end()))
1900 {
1901 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '{}' linking to Gameobject '{}' with not corresponding spawnMask", guidLow, linkedGuidLow);
1902 error = true;
1903 break;
1904 }
1905
1906 guid = ObjectGuid::Create<HighGuid::GameObject>(slave->mapId, slave->id, guidLow);
1907 linkedGuid = ObjectGuid::Create<HighGuid::GameObject>(master->mapId, master->id, linkedGuidLow);
1908 break;
1909 }
1911 {
1912 GameObjectData const* slave = GetGameObjectData(guidLow);
1913 if (!slave)
1914 {
1915 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (guid) '{}' not found in gameobject table", guidLow);
1916 error = true;
1917 break;
1918 }
1919
1920 CreatureData const* master = GetCreatureData(linkedGuidLow);
1921 if (!master)
1922 {
1923 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature (linkedGuid) '{}' not found in creature table", linkedGuidLow);
1924 error = true;
1925 break;
1926 }
1927
1928 MapEntry const* const map = sMapStore.LookupEntry(master->mapId);
1929 if (!map || !map->Instanceable() || (master->mapId != slave->mapId))
1930 {
1931 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '{}' linking to Creature '{}' on an unpermitted map.", guidLow, linkedGuidLow);
1932 error = true;
1933 break;
1934 }
1935
1936 // they must have a possibility to meet (normal/heroic difficulty)
1937 if (!Trinity::Containers::Intersects(master->spawnDifficulties.begin(), master->spawnDifficulties.end(), slave->spawnDifficulties.begin(), slave->spawnDifficulties.end()))
1938 {
1939 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '{}' linking to Creature '{}' with not corresponding spawnMask", guidLow, linkedGuidLow);
1940 error = true;
1941 break;
1942 }
1943
1944 guid = ObjectGuid::Create<HighGuid::GameObject>(slave->mapId, slave->id, guidLow);
1945 linkedGuid = ObjectGuid::Create<HighGuid::Creature>(master->mapId, master->id, linkedGuidLow);
1946 break;
1947 }
1948 }
1949
1950 if (!error)
1951 _linkedRespawnStore[guid] = linkedGuid;
1952 }
1953 while (result->NextRow());
1954
1955 TC_LOG_INFO("server.loading", ">> Loaded {} linked respawns in {} ms", uint64(_linkedRespawnStore.size()), GetMSTimeDiffToNow(oldMSTime));
1956}
1957
1959{
1960 if (!guidLow)
1961 return false;
1962
1963 CreatureData const* master = GetCreatureData(guidLow);
1964 ASSERT(master);
1965 ObjectGuid guid = ObjectGuid::Create<HighGuid::Creature>(master->mapId, master->id, guidLow);
1966
1967 if (!linkedGuidLow) // we're removing the linking
1968 {
1969 _linkedRespawnStore.erase(guid);
1971 stmt->setUInt64(0, guidLow);
1973 WorldDatabase.Execute(stmt);
1974 return true;
1975 }
1976
1977 CreatureData const* slave = GetCreatureData(linkedGuidLow);
1978 if (!slave)
1979 {
1980 TC_LOG_ERROR("sql.sql", "Creature '{}' linking to non-existent creature '{}'.", guidLow, linkedGuidLow);
1981 return false;
1982 }
1983
1984 MapEntry const* map = sMapStore.LookupEntry(master->mapId);
1985 if (!map || !map->Instanceable() || (master->mapId != slave->mapId))
1986 {
1987 TC_LOG_ERROR("sql.sql", "Creature '{}' linking to '{}' on an unpermitted map.", guidLow, linkedGuidLow);
1988 return false;
1989 }
1990
1991 // they must have a possibility to meet (normal/heroic difficulty)
1992 if (!Trinity::Containers::Intersects(master->spawnDifficulties.begin(), master->spawnDifficulties.end(), slave->spawnDifficulties.begin(), slave->spawnDifficulties.end()))
1993 {
1994 TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '{}' linking to '{}' with not corresponding spawnMask", guidLow, linkedGuidLow);
1995 return false;
1996 }
1997
1998 ObjectGuid linkedGuid = ObjectGuid::Create<HighGuid::Creature>(slave->mapId, slave->id, linkedGuidLow);
1999
2000 _linkedRespawnStore[guid] = linkedGuid;
2002 stmt->setUInt64(0, guidLow);
2003 stmt->setUInt64(1, linkedGuidLow);
2005 WorldDatabase.Execute(stmt);
2006 return true;
2007}
2008
2010{
2011 uint32 oldMSTime = getMSTime();
2012
2013 _tempSummonDataStore.clear(); // needed for reload case
2014
2015 // 0 1 2 3 4 5 6 7 8 9
2016 QueryResult result = WorldDatabase.Query("SELECT summonerId, summonerType, groupId, entry, position_x, position_y, position_z, orientation, summonType, summonTime FROM creature_summon_groups");
2017
2018 if (!result)
2019 {
2020 TC_LOG_INFO("server.loading", ">> Loaded 0 temp summons. DB table `creature_summon_groups` is empty.");
2021 return;
2022 }
2023
2024 uint32 count = 0;
2025 do
2026 {
2027 Field* fields = result->Fetch();
2028
2029 uint32 summonerId = fields[0].GetUInt32();
2030 SummonerType summonerType = SummonerType(fields[1].GetUInt8());
2031 uint8 group = fields[2].GetUInt8();
2032
2033 switch (summonerType)
2034 {
2036 if (!GetCreatureTemplate(summonerId))
2037 {
2038 TC_LOG_ERROR("sql.sql", "Table `creature_summon_groups` has summoner with non existing entry {} for creature summoner type, skipped.", summonerId);
2039 continue;
2040 }
2041 break;
2043 if (!GetGameObjectTemplate(summonerId))
2044 {
2045 TC_LOG_ERROR("sql.sql", "Table `creature_summon_groups` has summoner with non existing entry {} for gameobject summoner type, skipped.", summonerId);
2046 continue;
2047 }
2048 break;
2049 case SUMMONER_TYPE_MAP:
2050 if (!sMapStore.LookupEntry(summonerId))
2051 {
2052 TC_LOG_ERROR("sql.sql", "Table `creature_summon_groups` has summoner with non existing entry {} for map summoner type, skipped.", summonerId);
2053 continue;
2054 }
2055 break;
2056 default:
2057 TC_LOG_ERROR("sql.sql", "Table `creature_summon_groups` has unhandled summoner type {} for summoner {}, skipped.", summonerType, summonerId);
2058 continue;
2059 }
2060
2061 TempSummonData data;
2062 data.entry = fields[3].GetUInt32();
2063
2064 if (!GetCreatureTemplate(data.entry))
2065 {
2066 TC_LOG_ERROR("sql.sql", "Table `creature_summon_groups` has creature in group [Summoner ID: {}, Summoner Type: {}, Group ID: {}] with non existing creature entry {}, skipped.", summonerId, summonerType, group, data.entry);
2067 continue;
2068 }
2069
2070 float posX = fields[4].GetFloat();
2071 float posY = fields[5].GetFloat();
2072 float posZ = fields[6].GetFloat();
2073 float orientation = fields[7].GetFloat();
2074
2075 data.pos.Relocate(posX, posY, posZ, orientation);
2076
2077 data.type = TempSummonType(fields[8].GetUInt8());
2078
2080 {
2081 TC_LOG_ERROR("sql.sql", "Table `creature_summon_groups` has unhandled temp summon type {} in group [Summoner ID: {}, Summoner Type: {}, Group ID: {}] for creature entry {}, skipped.", data.type, summonerId, summonerType, group, data.entry);
2082 continue;
2083 }
2084
2085 data.time = Milliseconds(fields[9].GetUInt32());
2086
2087 TempSummonGroupKey key(summonerId, summonerType, group);
2088 _tempSummonDataStore[key].push_back(data);
2089
2090 ++count;
2091
2092 } while (result->NextRow());
2093
2094 TC_LOG_INFO("server.loading", ">> Loaded {} temp summons in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
2095}
2096
2097std::vector<Difficulty> ObjectMgr::ParseSpawnDifficulties(std::string_view difficultyString, std::string_view table, ObjectGuid::LowType spawnId, uint32 mapId,
2098 std::set<Difficulty> const& mapDifficulties)
2099{
2100 std::vector<Difficulty> difficulties;
2101 bool isTransportMap = sObjectMgr->IsTransportMap(mapId);
2102 for (std::string_view token : Trinity::Tokenize(difficultyString, ',', false))
2103 {
2104 Difficulty difficultyId = Difficulty(Trinity::StringTo<std::underlying_type_t<Difficulty>>(token).value_or(DIFFICULTY_NONE));
2105 if (difficultyId && !sDifficultyStore.LookupEntry(difficultyId))
2106 {
2107 TC_LOG_ERROR("sql.sql", "Table `{}` has {} (GUID: {}) with non invalid difficulty id {}, skipped.",
2108 table, table, spawnId, uint32(difficultyId));
2109 continue;
2110 }
2111
2112 if (!isTransportMap && mapDifficulties.find(difficultyId) == mapDifficulties.end())
2113 {
2114 TC_LOG_ERROR("sql.sql", "Table `{}` has {} (GUID: {}) has unsupported difficulty {} for map (Id: {}).",
2115 table, table, spawnId, uint32(difficultyId), mapId);
2116 continue;
2117 }
2118
2119 difficulties.push_back(difficultyId);
2120 }
2121
2122 std::sort(difficulties.begin(), difficulties.end());
2123 return difficulties;
2124}
2125
2127{
2128 uint32 oldMSTime = getMSTime();
2129
2130 // 0 1 2 3 4 5 6 7 8 9 10
2131 QueryResult result = WorldDatabase.Query("SELECT creature.guid, id, map, position_x, position_y, position_z, orientation, modelid, equipment_id, spawntimesecs, wander_distance, "
2132 // 11 12 13 14 15 16 17 18 19 20
2133 "currentwaypoint, curHealthPct, MovementType, spawnDifficulties, eventEntry, poolSpawnId, creature.npcflag, creature.unit_flags, creature.unit_flags2, creature.unit_flags3, "
2134 // 21 22 23 24 25 26
2135 "creature.phaseUseFlags, creature.phaseid, creature.phasegroup, creature.terrainSwapMap, creature.ScriptName, creature.StringId "
2136 "FROM creature "
2137 "LEFT OUTER JOIN game_event_creature ON creature.guid = game_event_creature.guid "
2138 "LEFT OUTER JOIN pool_members ON pool_members.type = 0 AND creature.guid = pool_members.spawnId");
2139
2140 if (!result)
2141 {
2142 TC_LOG_INFO("server.loading", ">> Loaded 0 creatures. DB table `creature` is empty.");
2143 return;
2144 }
2145
2146 // Build single time for check spawnmask
2147 std::unordered_map<uint32, std::set<Difficulty>> spawnMasks;
2148 for (MapDifficultyEntry const* mapDifficulty : sMapDifficultyStore)
2149 spawnMasks[mapDifficulty->MapID].insert(Difficulty(mapDifficulty->DifficultyID));
2150
2151 PhaseShift phaseShift;
2152
2153 _creatureDataStore.reserve(result->GetRowCount());
2154
2155 do
2156 {
2157 Field* fields = result->Fetch();
2158
2159 ObjectGuid::LowType guid = fields[0].GetUInt64();
2160 uint32 entry = fields[1].GetUInt32();
2161
2162 CreatureTemplate const* cInfo = GetCreatureTemplate(entry);
2163 if (!cInfo)
2164 {
2165 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {}) with non existing creature entry {}, skipped.", guid, entry);
2166 continue;
2167 }
2168
2169 CreatureData& data = _creatureDataStore[guid];
2170 data.spawnId = guid;
2171 data.id = entry;
2172 data.mapId = fields[2].GetUInt16();
2173 data.spawnPoint.Relocate(fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat(), fields[6].GetFloat());
2174 if (uint32 displayId = fields[7].GetUInt32())
2175 data.display.emplace(displayId, DEFAULT_PLAYER_DISPLAY_SCALE, 1.0f);
2176 data.equipmentId = fields[8].GetInt8();
2177 data.spawntimesecs = fields[9].GetUInt32();
2178 data.wander_distance = fields[10].GetFloat();
2179 data.currentwaypoint = fields[11].GetUInt32();
2180 data.curHealthPct = fields[12].GetUInt32();
2181 data.movementType = fields[13].GetUInt8();
2182 data.spawnDifficulties = ParseSpawnDifficulties(fields[14].GetStringView(), "creature", guid, data.mapId, spawnMasks[data.mapId]);
2183 int16 gameEvent = fields[15].GetInt8();
2184 data.poolId = fields[16].GetUInt32();
2185 if (!fields[17].IsNull())
2186 data.npcflag = fields[17].GetUInt64();
2187 if (!fields[18].IsNull())
2188 data.unit_flags = fields[18].GetUInt32();
2189 if (!fields[19].IsNull())
2190 data.unit_flags2 = fields[19].GetUInt32();
2191 if (!fields[20].IsNull())
2192 data.unit_flags3 = fields[20].GetUInt32();
2193 data.phaseUseFlags = fields[21].GetUInt8();
2194 data.phaseId = fields[22].GetUInt32();
2195 data.phaseGroup = fields[23].GetUInt32();
2196 data.terrainSwapMap = fields[24].GetInt32();
2197 data.scriptId = GetScriptId(fields[25].GetString());
2198 data.StringId = fields[26].GetString();
2199 data.spawnGroupData = IsTransportMap(data.mapId) ? GetLegacySpawnGroup() : GetDefaultSpawnGroup(); // transport spawns default to compatibility group
2200
2201 MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapId);
2202 if (!mapEntry)
2203 {
2204 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {}) that spawned at nonexistent map (Id: {}), skipped.", guid, data.mapId);
2205 continue;
2206 }
2207
2209 {
2211 {
2212 if (vmgr->isMapLoadingEnabled() && !IsTransportMap(data.mapId))
2213 {
2215 int gx = (MAX_NUMBER_OF_GRIDS - 1) - gridCoord.x_coord;
2216 int gy = (MAX_NUMBER_OF_GRIDS - 1) - gridCoord.y_coord;
2217
2218 VMAP::LoadResult result = vmgr->existsMap((sWorld->GetDataPath() + "vmaps").c_str(), data.mapId, gx, gy);
2219 if (result != VMAP::LoadResult::Success)
2220 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {} MapID: {}) spawned on a possible invalid position ({})",
2221 guid, data.id, data.mapId, data.spawnPoint.ToString());
2222 }
2223 }
2224 }
2225
2226 if (data.spawnDifficulties.empty())
2227 {
2228 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {}) that is not spawned in any difficulty, skipped.", guid);
2229 continue;
2230 }
2231
2232 if (data.display.has_value())
2233 {
2234 if (!GetCreatureModelInfo(data.display->CreatureDisplayID))
2235 {
2236 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) with invalid `modelid` {}, ignoring.", guid, data.id, data.display->CreatureDisplayID);
2237 data.display.reset();
2238 }
2239 }
2240
2241 // -1 random, 0 no equipment
2242 if (data.equipmentId != 0)
2243 {
2244 if (!GetEquipmentInfo(data.id, data.equipmentId))
2245 {
2246 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (Entry: {}) with equipment_id {} not found in table `creature_equip_template`, set to no equipment.", data.id, data.equipmentId);
2247 data.equipmentId = 0;
2248 }
2249 }
2250
2252 {
2253 if (!mapEntry->IsDungeon())
2254 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) with `creature_template`.`flags_extra` including CREATURE_FLAG_EXTRA_INSTANCE_BIND but creature is not in instance.", guid, data.id);
2255 }
2256
2257 if (data.movementType >= MAX_DB_MOTION_TYPE)
2258 {
2259 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) with wrong movement generator type ({}), ignored and set to IDLE.", guid, data.id, data.movementType);
2261 }
2262
2263 if (data.wander_distance < 0.0f)
2264 {
2265 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) with `wander_distance`< 0, set to 0.", guid, data.id);
2266 data.wander_distance = 0.0f;
2267 }
2268 else if (data.wander_distance > 0.0f && data.wander_distance < 0.1f)
2269 {
2270 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) with `wander_distance` below the allowed minimum distance of 0.1, set to 0.", guid, data.id);
2271 data.wander_distance = 0.0f;
2272 }
2273 else if (data.movementType == RANDOM_MOTION_TYPE)
2274 {
2275 if (G3D::fuzzyEq(data.wander_distance, 0.0f))
2276 {
2277 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) with `MovementType`=1 (random movement) but with `wander_distance`=0, replace by idle movement type (0).", guid, data.id);
2279 }
2280 }
2281 else if (data.movementType == IDLE_MOTION_TYPE)
2282 {
2283 if (data.wander_distance != 0.0f)
2284 {
2285 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) with `MovementType`=0 (idle) have `wander_distance`<>0, set to 0.", guid, data.id);
2286 data.wander_distance = 0.0f;
2287 }
2288 }
2289
2291 {
2292 TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: {} Entry: {}) has unknown `phaseUseFlags` set, removed unknown value.", guid, data.id);
2294 }
2295
2297 {
2298 TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: {} Entry: {}) has both `phaseUseFlags` PHASE_USE_FLAGS_ALWAYS_VISIBLE and PHASE_USE_FLAGS_INVERSE,"
2299 " removing PHASE_USE_FLAGS_INVERSE.", guid, data.id);
2300 data.phaseUseFlags &= ~PHASE_USE_FLAGS_INVERSE;
2301 }
2302
2303 if (data.phaseGroup && data.phaseId)
2304 {
2305 TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: {} Entry: {}) with both `phaseid` and `phasegroup` set, `phasegroup` set to 0", guid, data.id);
2306 data.phaseGroup = 0;
2307 }
2308
2309 if (data.phaseId)
2310 {
2311 if (!sPhaseStore.LookupEntry(data.phaseId))
2312 {
2313 TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: {} Entry: {}) with `phaseid` {} does not exist, set to 0", guid, data.id, data.phaseId);
2314 data.phaseId = 0;
2315 }
2316 }
2317
2318 if (data.phaseGroup)
2319 {
2320 if (!sDB2Manager.GetPhasesForGroup(data.phaseGroup))
2321 {
2322 TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: {} Entry: {}) with `phasegroup` {} does not exist, set to 0", guid, data.id, data.phaseGroup);
2323 data.phaseGroup = 0;
2324 }
2325 }
2326
2327 if (data.terrainSwapMap != -1)
2328 {
2329 MapEntry const* terrainSwapEntry = sMapStore.LookupEntry(data.terrainSwapMap);
2330 if (!terrainSwapEntry)
2331 {
2332 TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: {} Entry: {}) with `terrainSwapMap` {} does not exist, set to -1", guid, data.id, data.terrainSwapMap);
2333 data.terrainSwapMap = -1;
2334 }
2335 else if (terrainSwapEntry->ParentMapID != int16(data.mapId))
2336 {
2337 TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: {} Entry: {}) with `terrainSwapMap` {} which cannot be used on spawn map, set to -1", guid, data.id, data.terrainSwapMap);
2338 data.terrainSwapMap = -1;
2339 }
2340 }
2341
2342 if (data.unit_flags.has_value())
2343 {
2344 if (uint32 disallowedUnitFlags = (*data.unit_flags & ~UNIT_FLAG_ALLOWED))
2345 {
2346 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) with disallowed `unit_flags` {}, removing incorrect flag.", guid, data.id, disallowedUnitFlags);
2348 }
2349 }
2350
2351 if (data.unit_flags2.has_value())
2352 {
2353 if (uint32 disallowedUnitFlags2 = (*data.unit_flags2 & ~UNIT_FLAG2_ALLOWED))
2354 {
2355 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) with disallowed `unit_flags2` {}, removing incorrect flag.", guid, data.id, disallowedUnitFlags2);
2357 }
2358
2360 {
2361 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) has UNIT_FLAG2_FEIGN_DEATH set without IMMUNE_TO_PC | IMMUNE_TO_NPC, removing incorrect flag.", guid, data.id);
2362 *data.unit_flags2 &= ~UNIT_FLAG2_FEIGN_DEATH;
2363 }
2364 }
2365
2366 if (data.unit_flags3.has_value())
2367 {
2368 if (uint32 disallowedUnitFlags3 = (*data.unit_flags3 & ~UNIT_FLAG3_ALLOWED))
2369 {
2370 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) with disallowed `unit_flags3` {}, removing incorrect flag.", guid, data.id, disallowedUnitFlags3);
2372 }
2373
2374 if (*data.unit_flags3 & UNIT_FLAG3_FAKE_DEAD && (!data.unit_flags.has_value() || !(*data.unit_flags & (UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC))))
2375 {
2376 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) has UNIT_FLAG3_FAKE_DEAD set without IMMUNE_TO_PC | IMMUNE_TO_NPC, removing incorrect flag.", guid, data.id);
2377 *data.unit_flags3 &= ~UNIT_FLAG3_FAKE_DEAD;
2378 }
2379 }
2380
2381 uint32 healthPct = std::clamp<uint32>(data.curHealthPct, 1, 100);
2382 if (data.curHealthPct != healthPct)
2383 {
2384 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {} Entry: {}) with invalid `curHealthPct` {}, set to {}.", guid, data.id, data.curHealthPct, healthPct);
2385 data.curHealthPct = healthPct;
2386 }
2387
2389 {
2390 uint32 zoneId = 0;
2391 uint32 areaId = 0;
2393 sTerrainMgr.GetZoneAndAreaId(phaseShift, zoneId, areaId, data.mapId, data.spawnPoint);
2394
2396
2397 stmt->setUInt32(0, zoneId);
2398 stmt->setUInt32(1, areaId);
2399 stmt->setUInt64(2, guid);
2400
2401 WorldDatabase.Execute(stmt);
2402 }
2403
2404 // Add to grid if not managed by the game event
2405 if (gameEvent == 0)
2406 AddCreatureToGrid(&data);
2407 }
2408 while (result->NextRow());
2409
2410 TC_LOG_INFO("server.loading", ">> Loaded {} creatures in {} ms", _creatureDataStore.size(), GetMSTimeDiffToNow(oldMSTime));
2411}
2412
2414{
2415 if (CellObjectGuidsMap const* mapGuids = Trinity::Containers::MapGetValuePtr(_mapObjectGuidsStore, { mapid, spawnMode }))
2416 return Trinity::Containers::MapGetValuePtr(*mapGuids, cell_id);
2417
2418 return nullptr;
2419}
2420
2422{
2423 return Trinity::Containers::MapGetValuePtr(_mapObjectGuidsStore, { mapid, spawnMode });
2424}
2425
2426bool ObjectMgr::HasPersonalSpawns(uint32 mapid, Difficulty spawnMode, uint32 phaseId) const
2427{
2428 return Trinity::Containers::MapGetValuePtr(_mapPersonalObjectGuidsStore, { mapid, spawnMode, phaseId }) != nullptr;
2429}
2430
2432{
2433 if (CellObjectGuidsMap const* guids = Trinity::Containers::MapGetValuePtr(_mapPersonalObjectGuidsStore, { mapid, spawnMode, phaseId }))
2434 return Trinity::Containers::MapGetValuePtr(*guids, cell_id);
2435
2436 return nullptr;
2437}
2438
2439template<CellGuidSet CellObjectGuids::*guids>
2441{
2443 bool isPersonalPhase = PhasingHandler::IsPersonalPhase(data->phaseId);
2444 if (!isPersonalPhase)
2445 {
2446 for (Difficulty difficulty : data->spawnDifficulties)
2447 (_mapObjectGuidsStore[{ data->mapId, difficulty }][cellId].*guids).insert(data->spawnId);
2448 }
2449 else
2450 {
2451 for (Difficulty difficulty : data->spawnDifficulties)
2452 (_mapPersonalObjectGuidsStore[{ data->mapId, difficulty, data->phaseId }][cellId].*guids).insert(data->spawnId);
2453 }
2454}
2455
2456template<CellGuidSet CellObjectGuids::*guids>
2458{
2460 bool isPersonalPhase = PhasingHandler::IsPersonalPhase(data->phaseId);
2461 if (!isPersonalPhase)
2462 {
2463 for (Difficulty difficulty : data->spawnDifficulties)
2464 (_mapObjectGuidsStore[{ data->mapId, difficulty }][cellId].*guids).erase(data->spawnId);
2465 }
2466 else
2467 {
2468 for (Difficulty difficulty : data->spawnDifficulties)
2469 (_mapPersonalObjectGuidsStore[{ data->mapId, difficulty, data->phaseId }][cellId].*guids).erase(data->spawnId);
2470 }
2471}
2472
2474{
2475 AddSpawnDataToGrid<&CellObjectGuids::creatures>(data);
2476}
2477
2479{
2480 RemoveSpawnDataFromGrid<&CellObjectGuids::creatures>(data);
2481}
2482
2484{
2485 uint32 oldMSTime = getMSTime();
2486
2487 // 0 1 2 3 4 5 6
2488 QueryResult result = WorldDatabase.Query("SELECT gameobject.guid, id, map, position_x, position_y, position_z, orientation, "
2489 // 7 8 9 10 11 12 13 14 15 16
2490 "rotation0, rotation1, rotation2, rotation3, spawntimesecs, animprogress, state, spawnDifficulties, eventEntry, poolSpawnId, "
2491 // 17 18 19 20 21 22
2492 "phaseUseFlags, phaseid, phasegroup, terrainSwapMap, ScriptName, StringId "
2493 "FROM gameobject LEFT OUTER JOIN game_event_gameobject ON gameobject.guid = game_event_gameobject.guid "
2494 "LEFT OUTER JOIN pool_members ON pool_members.type = 1 AND gameobject.guid = pool_members.spawnId");
2495
2496 if (!result)
2497 {
2498 TC_LOG_INFO("server.loading", ">> Loaded 0 gameobjects. DB table `gameobject` is empty.");
2499 return;
2500 }
2501
2502 // build single time for check spawnmask
2503 std::unordered_map<uint32, std::set<Difficulty>> spawnMasks;
2504 for (MapDifficultyEntry const* mapDifficulty : sMapDifficultyStore)
2505 spawnMasks[mapDifficulty->MapID].insert(Difficulty(mapDifficulty->DifficultyID));
2506
2507 PhaseShift phaseShift;
2508
2509 _gameObjectDataStore.reserve(result->GetRowCount());
2510
2511 do
2512 {
2513 Field* fields = result->Fetch();
2514
2515 ObjectGuid::LowType guid = fields[0].GetUInt64();
2516 uint32 entry = fields[1].GetUInt32();
2517
2518 GameObjectTemplate const* gInfo = GetGameObjectTemplate(entry);
2519 if (!gInfo)
2520 {
2521 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {}) with non existing gameobject entry {}, skipped.", guid, entry);
2522 continue;
2523 }
2524
2525 if (!gInfo->displayId)
2526 {
2527 switch (gInfo->type)
2528 {
2531 break;
2532 default:
2533 TC_LOG_ERROR("sql.sql", "Gameobject (GUID: {} Entry {} GoType: {}) doesn't have a displayId ({}), not loaded.", guid, entry, gInfo->type, gInfo->displayId);
2534 break;
2535 }
2536 }
2537
2538 if (gInfo->displayId && !sGameObjectDisplayInfoStore.LookupEntry(gInfo->displayId))
2539 {
2540 TC_LOG_ERROR("sql.sql", "Gameobject (GUID: {} Entry {} GoType: {}) has an invalid displayId ({}), not loaded.", guid, entry, gInfo->type, gInfo->displayId);
2541 continue;
2542 }
2543
2545
2546 data.spawnId = guid;
2547 data.id = entry;
2548 data.mapId = fields[2].GetUInt16();
2549 data.spawnPoint.Relocate(fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat(), fields[6].GetFloat());
2550 data.rotation.x = fields[7].GetFloat();
2551 data.rotation.y = fields[8].GetFloat();
2552 data.rotation.z = fields[9].GetFloat();
2553 data.rotation.w = fields[10].GetFloat();
2554 data.spawntimesecs = fields[11].GetInt32();
2555 data.spawnGroupData = IsTransportMap(data.mapId) ? GetLegacySpawnGroup() : GetDefaultSpawnGroup(); // transport spawns default to compatibility group
2556
2557 MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapId);
2558 if (!mapEntry)
2559 {
2560 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) spawned on a non-existed map (Id: {}), skip", guid, data.id, data.mapId);
2561 continue;
2562 }
2563
2565 {
2567 {
2568 if (vmgr->isMapLoadingEnabled() && !IsTransportMap(data.mapId))
2569 {
2571 int gx = (MAX_NUMBER_OF_GRIDS - 1) - gridCoord.x_coord;
2572 int gy = (MAX_NUMBER_OF_GRIDS - 1) - gridCoord.y_coord;
2573
2574 VMAP::LoadResult result = vmgr->existsMap((sWorld->GetDataPath() + "vmaps").c_str(), data.mapId, gx, gy);
2575 if (result != VMAP::LoadResult::Success)
2576 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {} MapID: {}) spawned on a possible invalid position ({})",
2577 guid, data.id, data.mapId, data.spawnPoint.ToString());
2578 }
2579 }
2580 }
2581
2582 if (data.spawntimesecs == 0 && gInfo->IsDespawnAtAction())
2583 {
2584 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with `spawntimesecs` (0) value, but the gameobejct is marked as despawnable at action.", guid, data.id);
2585 }
2586
2587 data.animprogress = fields[12].GetUInt8();
2588 data.artKit = 0;
2589
2590 uint32 go_state = fields[13].GetUInt8();
2591 if (go_state >= MAX_GO_STATE)
2592 {
2594 {
2595 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid `state` ({}) value, skip", guid, data.id, go_state);
2596 continue;
2597 }
2598 }
2599 data.goState = GOState(go_state);
2600
2601 data.spawnDifficulties = ParseSpawnDifficulties(fields[14].GetStringView(), "gameobject", guid, data.mapId, spawnMasks[data.mapId]);
2602 if (data.spawnDifficulties.empty())
2603 {
2604 TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: {}) that is not spawned in any difficulty, skipped.", guid);
2605 continue;
2606 }
2607
2608 int16 gameEvent = fields[15].GetInt8();
2609 data.poolId = fields[16].GetUInt32();
2610 data.phaseUseFlags = fields[17].GetUInt8();
2611 data.phaseId = fields[18].GetUInt32();
2612 data.phaseGroup = fields[19].GetUInt32();
2613
2615 {
2616 TC_LOG_ERROR("sql.sql", "Table `gameobject` have gameobject (GUID: {} Entry: {}) has unknown `phaseUseFlags` set, removed unknown value.", guid, data.id);
2618 }
2619
2621 {
2622 TC_LOG_ERROR("sql.sql", "Table `gameobject` have gameobject (GUID: {} Entry: {}) has both `phaseUseFlags` PHASE_USE_FLAGS_ALWAYS_VISIBLE and PHASE_USE_FLAGS_INVERSE,"
2623 " removing PHASE_USE_FLAGS_INVERSE.", guid, data.id);
2624 data.phaseUseFlags &= ~PHASE_USE_FLAGS_INVERSE;
2625 }
2626
2627 if (data.phaseGroup && data.phaseId)
2628 {
2629 TC_LOG_ERROR("sql.sql", "Table `gameobject` have gameobject (GUID: {} Entry: {}) with both `phaseid` and `phasegroup` set, `phasegroup` set to 0", guid, data.id);
2630 data.phaseGroup = 0;
2631 }
2632
2633 if (data.phaseId)
2634 {
2635 if (!sPhaseStore.LookupEntry(data.phaseId))
2636 {
2637 TC_LOG_ERROR("sql.sql", "Table `gameobject` have gameobject (GUID: {} Entry: {}) with `phaseid` {} does not exist, set to 0", guid, data.id, data.phaseId);
2638 data.phaseId = 0;
2639 }
2640 }
2641
2642 if (data.phaseGroup)
2643 {
2644 if (!sDB2Manager.GetPhasesForGroup(data.phaseGroup))
2645 {
2646 TC_LOG_ERROR("sql.sql", "Table `gameobject` have gameobject (GUID: {} Entry: {}) with `phaseGroup` {} does not exist, set to 0", guid, data.id, data.phaseGroup);
2647 data.phaseGroup = 0;
2648 }
2649 }
2650
2651 data.terrainSwapMap = fields[20].GetInt32();
2652 if (data.terrainSwapMap != -1)
2653 {
2654 MapEntry const* terrainSwapEntry = sMapStore.LookupEntry(data.terrainSwapMap);
2655 if (!terrainSwapEntry)
2656 {
2657 TC_LOG_ERROR("sql.sql", "Table `gameobject` have gameobject (GUID: {} Entry: {}) with `terrainSwapMap` {} does not exist, set to -1", guid, data.id, data.terrainSwapMap);
2658 data.terrainSwapMap = -1;
2659 }
2660 else if (terrainSwapEntry->ParentMapID != int16(data.mapId))
2661 {
2662 TC_LOG_ERROR("sql.sql", "Table `gameobject` have gameobject (GUID: {} Entry: {}) with `terrainSwapMap` {} which cannot be used on spawn map, set to -1", guid, data.id, data.terrainSwapMap);
2663 data.terrainSwapMap = -1;
2664 }
2665 }
2666
2667 data.scriptId = GetScriptId(fields[21].GetString());
2668 data.StringId = fields[22].GetString();
2669
2670 if (data.rotation.x < -1.0f || data.rotation.x > 1.0f)
2671 {
2672 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid rotationX ({}) value, skip", guid, data.id, data.rotation.x);
2673 continue;
2674 }
2675
2676 if (data.rotation.y < -1.0f || data.rotation.y > 1.0f)
2677 {
2678 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid rotationY ({}) value, skip", guid, data.id, data.rotation.y);
2679 continue;
2680 }
2681
2682 if (data.rotation.z < -1.0f || data.rotation.z > 1.0f)
2683 {
2684 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid rotationZ ({}) value, skip", guid, data.id, data.rotation.z);
2685 continue;
2686 }
2687
2688 if (data.rotation.w < -1.0f || data.rotation.w > 1.0f)
2689 {
2690 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid rotationW ({}) value, skip", guid, data.id, data.rotation.w);
2691 continue;
2692 }
2693
2695 {
2696 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid coordinates, skip", guid, data.id);
2697 continue;
2698 }
2699
2700 if (!data.rotation.isUnit())
2701 {
2702 TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid rotation quaternion (non-unit), defaulting to orientation on Z axis only", guid, data.id);
2704 }
2705
2707 {
2708 uint32 zoneId = 0;
2709 uint32 areaId = 0;
2711 sTerrainMgr.GetZoneAndAreaId(phaseShift, zoneId, areaId, data.mapId, data.spawnPoint);
2712
2714
2715 stmt->setUInt32(0, zoneId);
2716 stmt->setUInt32(1, areaId);
2717 stmt->setUInt64(2, guid);
2718
2719 WorldDatabase.Execute(stmt);
2720 }
2721
2722 if (gameEvent == 0) // if not this is to be managed by GameEvent System
2723 AddGameobjectToGrid(&data);
2724 }
2725 while (result->NextRow());
2726
2727 TC_LOG_INFO("server.loading", ">> Loaded {} gameobjects in {} ms", _gameObjectDataStore.size(), GetMSTimeDiffToNow(oldMSTime));
2728}
2729
2731{
2732 uint32 oldMSTime = getMSTime();
2733
2734 // 0 1 2
2735 QueryResult result = WorldDatabase.Query("SELECT groupId, groupName, groupFlags FROM spawn_group_template");
2736
2737 if (result)
2738 {
2739 do
2740 {
2741 Field* fields = result->Fetch();
2742 uint32 groupId = fields[0].GetUInt32();
2744 group.groupId = groupId;
2745 group.name = fields[1].GetString();
2747 uint32 flags = fields[2].GetUInt32();
2749 {
2751 TC_LOG_ERROR("sql.sql", "Invalid spawn group flag {} on group ID {} ({}), reduced to valid flag {}.", flags, groupId, group.name, uint32(group.flags));
2752 }
2754 {
2755 flags &= ~SPAWNGROUP_FLAG_MANUAL_SPAWN;
2756 TC_LOG_ERROR("sql.sql", "System spawn group {} ({}) has invalid manual spawn flag. Ignored.", groupId, group.name);
2757 }
2758 group.flags = SpawnGroupFlags(flags);
2759 } while (result->NextRow());
2760 }
2761
2762 if (_spawnGroupDataStore.find(0) == _spawnGroupDataStore.end())
2763 {
2764 TC_LOG_ERROR("sql.sql", "Default spawn group (index 0) is missing from DB! Manually inserted.");
2766 data.groupId = 0;
2767 data.name = "Default Group";
2768 data.mapId = 0;
2770 }
2771 if (_spawnGroupDataStore.find(1) == _spawnGroupDataStore.end())
2772 {
2773 TC_LOG_ERROR("sql.sql", "Default legacy spawn group (index 1) is missing from DB! Manually inserted.");
2775 data.groupId = 1;
2776 data.name = "Legacy Group";
2777 data.mapId = 0;
2779 }
2780
2781 if (result)
2782 TC_LOG_INFO("server.loading", ">> Loaded {} spawn group templates in {} ms", _spawnGroupDataStore.size(), GetMSTimeDiffToNow(oldMSTime));
2783 else
2784 TC_LOG_INFO("server.loading", ">> Loaded 0 spawn group templates. DB table `spawn_group_template` is empty.");
2785
2786 return;
2787}
2788
2790{
2791 uint32 oldMSTime = getMSTime();
2792
2793 // 0 1 2
2794 QueryResult result = WorldDatabase.Query("SELECT groupId, spawnType, spawnId FROM spawn_group");
2795
2796 if (!result)
2797 {
2798 TC_LOG_INFO("server.loading", ">> Loaded 0 spawn group members. DB table `spawn_group` is empty.");
2799 return;
2800 }
2801
2802 uint32 numMembers = 0;
2803 do
2804 {
2805 Field* fields = result->Fetch();
2806 uint32 groupId = fields[0].GetUInt32();
2807 SpawnObjectType spawnType = SpawnObjectType(fields[1].GetUInt8());
2808 if (!SpawnData::TypeIsValid(spawnType))
2809 {
2810 TC_LOG_ERROR("sql.sql", "Spawn data with invalid type {} listed for spawn group {}. Skipped.", uint32(spawnType), groupId);
2811 continue;
2812 }
2813 ObjectGuid::LowType spawnId = fields[2].GetUInt64();
2814
2815 SpawnMetadata const* data = GetSpawnMetadata(spawnType, spawnId);
2816 if (!data)
2817 {
2818 TC_LOG_ERROR("sql.sql", "Spawn data with ID ({},{}) not found, but is listed as a member of spawn group {}!", uint32(spawnType), spawnId, groupId);
2819 continue;
2820 }
2821 else if (data->spawnGroupData->groupId)
2822 {
2823 TC_LOG_ERROR("sql.sql", "Spawn with ID ({},{}) is listed as a member of spawn group {}, but is already a member of spawn group {}. Skipping.", uint32(spawnType), spawnId, groupId, data->spawnGroupData->groupId);
2824 continue;
2825 }
2826 auto it = _spawnGroupDataStore.find(groupId);
2827 if (it == _spawnGroupDataStore.end())
2828 {
2829 TC_LOG_ERROR("sql.sql", "Spawn group {} assigned to spawn ID ({},{}), but group is found!", groupId, uint32(spawnType), spawnId);
2830 continue;
2831 }
2832 else
2833 {
2834 SpawnGroupTemplateData& groupTemplate = it->second;
2835 if (groupTemplate.mapId == SPAWNGROUP_MAP_UNSET)
2836 {
2837 groupTemplate.mapId = data->mapId;
2838 _spawnGroupsByMap[data->mapId].push_back(groupId);
2839 }
2840 else if (groupTemplate.mapId != data->mapId && !(groupTemplate.flags & SPAWNGROUP_FLAG_SYSTEM))
2841 {
2842 TC_LOG_ERROR("sql.sql", "Spawn group {} has map ID {}, but spawn ({},{}) has map id {} - spawn NOT added to group!", groupId, groupTemplate.mapId, uint32(spawnType), spawnId, data->mapId);
2843 continue;
2844 }
2845 const_cast<SpawnMetadata*>(data)->spawnGroupData = &groupTemplate;
2846 if (!(groupTemplate.flags & SPAWNGROUP_FLAG_SYSTEM))
2847 _spawnGroupMapStore.emplace(groupId, data);
2848 ++numMembers;
2849 }
2850 } while (result->NextRow());
2851
2852 TC_LOG_INFO("server.loading", ">> Loaded {} spawn group members in {} ms", numMembers, GetMSTimeDiffToNow(oldMSTime));
2853}
2854
2856{
2857 uint32 oldMSTime = getMSTime();
2858
2859 // 0 1 2 3 4
2860 QueryResult result = WorldDatabase.Query("SELECT instanceMapId, bossStateId, bossStates, spawnGroupId, flags FROM instance_spawn_groups");
2861
2862 if (!result)
2863 {
2864 TC_LOG_INFO("server.loading", ">> Loaded 0 instance spawn groups. DB table `instance_spawn_groups` is empty.");
2865 return;
2866 }
2867
2868 uint32 n = 0;
2869 do
2870 {
2871 Field* fields = result->Fetch();
2872 uint32 const spawnGroupId = fields[3].GetUInt32();
2873 auto it = _spawnGroupDataStore.find(spawnGroupId);
2874 if (it == _spawnGroupDataStore.end() || (it->second.flags & SPAWNGROUP_FLAG_SYSTEM))
2875 {
2876 TC_LOG_ERROR("sql.sql", "Invalid spawn group {} specified for instance {}. Skipped.", spawnGroupId, fields[0].GetUInt16());
2877 continue;
2878 }
2879
2880 uint16 const instanceMapId = fields[0].GetUInt16();
2881 if (it->second.mapId != instanceMapId)
2882 {
2883 TC_LOG_ERROR("sql.sql", "Instance spawn group {} specified for instance {} has spawns on a different map {}. Skipped.",
2884 spawnGroupId, instanceMapId, it->second.mapId);
2885 continue;
2886 }
2887
2888 InstanceSpawnGroupInfo& info = _instanceSpawnGroupStore[instanceMapId].emplace_back();
2889 info.SpawnGroupId = spawnGroupId;
2890 info.BossStateId = fields[1].GetUInt8();
2891
2892 uint8 const ALL_STATES = (1 << TO_BE_DECIDED) - 1;
2893 uint8 const states = fields[2].GetUInt8();
2894 if (states & ~ALL_STATES)
2895 {
2896 info.BossStates = states & ALL_STATES;
2897 TC_LOG_ERROR("sql.sql", "Instance spawn group ({},{}) had invalid boss state mask {} - truncated to {}.", instanceMapId, spawnGroupId, states, info.BossStates);
2898 }
2899 else
2900 info.BossStates = states;
2901
2902 uint8 const flags = fields[4].GetUInt8();
2904 {
2906 TC_LOG_ERROR("sql.sql", "Instance spawn group ({},{}) had invalid flags {} - truncated to {}.", instanceMapId, spawnGroupId, flags, info.Flags);
2907 }
2908 else
2909 info.Flags = flags;
2910
2912 {
2914 TC_LOG_ERROR("sql.sql", "Instance spawn group ({},{}) FLAG_ALLIANCE_ONLY and FLAG_HORDE_ONLY may not be used together in a single entry - truncated to {}.", instanceMapId, spawnGroupId, info.Flags);
2915 }
2916
2917 ++n;
2918 } while (result->NextRow());
2919
2920 TC_LOG_INFO("server.loading", ">> Loaded {} instance spawn groups in {} ms", n, GetMSTimeDiffToNow(oldMSTime));
2921}
2922
2924{
2925 if (!SpawnData::TypeHasData(type))
2926 return nullptr;
2927 switch (type)
2928 {
2930 return GetCreatureData(spawnId);
2932 return GetGameObjectData(spawnId);
2934 return sAreaTriggerDataStore->GetAreaTriggerSpawn(spawnId);
2935 default:
2936 ABORT_MSG("Invalid spawn object type %u", uint32(type));
2937 return nullptr;
2938 }
2939}
2940
2942{
2943 if (data->spawnTrackingData)
2944 {
2946 ASSERT(spawnTrackingData, "Creature data for (%u," UI64FMTD ") is being deleted and has invalid spawn tracking id %u!", uint32(data->type), data->spawnId, data->spawnTrackingData->SpawnTrackingId);
2947
2948 auto pair = _spawnTrackingMapStore.equal_range(data->spawnTrackingData->SpawnTrackingId);
2949 bool erased = false;
2950 for (auto it = pair.first; it != pair.second; ++it)
2951 {
2952 if (it->second != data)
2953 continue;
2954 _spawnTrackingMapStore.erase(it);
2955 erased = true;
2956 }
2957
2958 if (!erased)
2959 ABORT_MSG("Spawn data (%u," UI64FMTD ") being removed is member of spawn tracking %u, but not actually listed in the lookup table for that spawn tracking!", uint32(data->type), data->spawnId, data->spawnTrackingData->SpawnTrackingId);
2960 }
2961
2962 auto templateIt = _spawnGroupDataStore.find(data->spawnGroupData->groupId);
2963 ASSERT(templateIt != _spawnGroupDataStore.end(), "Creature data for (%u," UI64FMTD ") is being deleted and has invalid spawn group index %u!", uint32(data->type), data->spawnId, data->spawnGroupData->groupId);
2964 if (templateIt->second.flags & SPAWNGROUP_FLAG_SYSTEM) // system groups don't store their members in the map
2965 return;
2966
2967 auto pair = _spawnGroupMapStore.equal_range(data->spawnGroupData->groupId);
2968 for (auto it = pair.first; it != pair.second; ++it)
2969 {
2970 if (it->second != data)
2971 continue;
2972 _spawnGroupMapStore.erase(it);
2973 return;
2974 }
2975 ABORT_MSG("Spawn data (%u," UI64FMTD ") 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);
2976}
2977
2979{
2980 AddSpawnDataToGrid<&CellObjectGuids::gameobjects>(data);
2981}
2982
2984{
2985 RemoveSpawnDataFromGrid<&CellObjectGuids::gameobjects>(data);
2986}
2987
2988uint32 FillMaxDurability(uint32 itemClass, uint32 itemSubClass, uint32 inventoryType, uint32 quality, uint32 itemLevel)
2989{
2990 if (itemClass != ITEM_CLASS_ARMOR && itemClass != ITEM_CLASS_WEAPON)
2991 return 0;
2992
2993 static float const qualityMultipliers[MAX_ITEM_QUALITY] =
2994 {
2995 0.92f, 0.92f, 0.92f, 1.11f, 1.32f, 1.61f, 0.0f, 0.0f
2996 };
2997
2998 static float const armorMultipliers[MAX_INVTYPE] =
2999 {
3000 0.00f, // INVTYPE_NON_EQUIP
3001 0.60f, // INVTYPE_HEAD
3002 0.00f, // INVTYPE_NECK
3003 0.60f, // INVTYPE_SHOULDERS
3004 0.00f, // INVTYPE_BODY
3005 1.00f, // INVTYPE_CHEST
3006 0.33f, // INVTYPE_WAIST
3007 0.72f, // INVTYPE_LEGS
3008 0.48f, // INVTYPE_FEET
3009 0.33f, // INVTYPE_WRISTS
3010 0.33f, // INVTYPE_HANDS
3011 0.00f, // INVTYPE_FINGER
3012 0.00f, // INVTYPE_TRINKET
3013 0.00f, // INVTYPE_WEAPON
3014 0.72f, // INVTYPE_SHIELD
3015 0.00f, // INVTYPE_RANGED
3016 0.00f, // INVTYPE_CLOAK
3017 0.00f, // INVTYPE_2HWEAPON
3018 0.00f, // INVTYPE_BAG
3019 0.00f, // INVTYPE_TABARD
3020 1.00f, // INVTYPE_ROBE
3021 0.00f, // INVTYPE_WEAPONMAINHAND
3022 0.00f, // INVTYPE_WEAPONOFFHAND
3023 0.00f, // INVTYPE_HOLDABLE
3024 0.00f, // INVTYPE_AMMO
3025 0.00f, // INVTYPE_THROWN
3026 0.00f, // INVTYPE_RANGEDRIGHT
3027 0.00f, // INVTYPE_QUIVER
3028 0.00f, // INVTYPE_RELIC
3029 0.00f, // INVTYPE_PROFESSION_TOOL
3030 0.00f, // INVTYPE_PROFESSION_GEAR
3031 0.00f, // INVTYPE_EQUIPABLE_SPELL_OFFENSIVE
3032 0.00f, // INVTYPE_EQUIPABLE_SPELL_UTILITY
3033 0.00f, // INVTYPE_EQUIPABLE_SPELL_DEFENSIVE
3034 0.00f, // INVTYPE_EQUIPABLE_SPELL_MOBILITY
3035 };
3036
3037 static float const weaponMultipliers[MAX_ITEM_SUBCLASS_WEAPON] =
3038 {
3039 0.91f, // ITEM_SUBCLASS_WEAPON_AXE
3040 1.00f, // ITEM_SUBCLASS_WEAPON_AXE2
3041 1.00f, // ITEM_SUBCLASS_WEAPON_BOW
3042 1.00f, // ITEM_SUBCLASS_WEAPON_GUN
3043 0.91f, // ITEM_SUBCLASS_WEAPON_MACE
3044 1.00f, // ITEM_SUBCLASS_WEAPON_MACE2
3045 1.00f, // ITEM_SUBCLASS_WEAPON_POLEARM
3046 0.91f, // ITEM_SUBCLASS_WEAPON_SWORD
3047 1.00f, // ITEM_SUBCLASS_WEAPON_SWORD2
3048 1.00f, // ITEM_SUBCLASS_WEAPON_WARGLAIVES
3049 1.00f, // ITEM_SUBCLASS_WEAPON_STAFF
3050 0.00f, // ITEM_SUBCLASS_WEAPON_EXOTIC
3051 0.00f, // ITEM_SUBCLASS_WEAPON_EXOTIC2
3052 0.66f, // ITEM_SUBCLASS_WEAPON_FIST_WEAPON
3053 0.00f, // ITEM_SUBCLASS_WEAPON_MISCELLANEOUS
3054 0.66f, // ITEM_SUBCLASS_WEAPON_DAGGER
3055 0.00f, // ITEM_SUBCLASS_WEAPON_THROWN
3056 0.00f, // ITEM_SUBCLASS_WEAPON_SPEAR
3057 1.00f, // ITEM_SUBCLASS_WEAPON_CROSSBOW
3058 0.66f, // ITEM_SUBCLASS_WEAPON_WAND
3059 0.66f, // ITEM_SUBCLASS_WEAPON_FISHING_POLE
3060 };
3061
3062 float levelPenalty = 1.0f;
3063 if (itemLevel <= 28)
3064 levelPenalty = 0.966f - float(28u - itemLevel) / 54.0f;
3065
3066 if (itemClass == ITEM_CLASS_ARMOR)
3067 {
3068 if (inventoryType > INVTYPE_ROBE)
3069 return 0;
3070
3071 return 5 * uint32(round(25.0f * qualityMultipliers[quality] * armorMultipliers[inventoryType] * levelPenalty));
3072 }
3073
3074 return 5 * uint32(round(18.0f * qualityMultipliers[quality] * weaponMultipliers[itemSubClass] * levelPenalty));
3075};
3076
3078{
3082
3084 {
3085 memset(ItemSpecStatTypes, -1, sizeof(ItemSpecStatTypes));
3086
3087 if (item->ClassID == ITEM_CLASS_WEAPON)
3088 {
3089 ItemType = 5;
3090 switch (item->SubclassID)
3091 {
3094 break;
3097 break;
3100 break;
3103 break;
3106 break;
3109 break;
3112 break;
3115 break;
3118 break;
3121 break;
3124 break;
3127 break;
3130 break;
3133 break;
3136 break;
3139 break;
3140 default:
3141 break;
3142 }
3143 }
3144 else if (item->ClassID == ITEM_CLASS_ARMOR)
3145 {
3146 switch (item->SubclassID)
3147 {
3149 if (sparse->InventoryType != INVTYPE_CLOAK)
3150 {
3151 ItemType = 1;
3152 break;
3153 }
3154
3155 ItemType = 0;
3157 break;
3159 ItemType = 2;
3160 break;
3162 ItemType = 3;
3163 break;
3165 ItemType = 4;
3166 break;
3167 default:
3169 {
3170 ItemType = 6;
3172 }
3174 {
3175 ItemType = 6;
3177 }
3178 else
3179 ItemType = 0;
3180 break;
3181 }
3182 }
3183 else if (item->ClassID == ITEM_CLASS_GEM)
3184 {
3185 ItemType = 7;
3186 if (GemPropertiesEntry const* gem = sGemPropertiesStore.LookupEntry(sparse->GemProperties))
3187 {
3188 if (gem->Type & SOCKET_COLOR_RELIC_IRON)
3190 if (gem->Type & SOCKET_COLOR_RELIC_BLOOD)
3192 if (gem->Type & SOCKET_COLOR_RELIC_SHADOW)
3194 if (gem->Type & SOCKET_COLOR_RELIC_FEL)
3196 if (gem->Type & SOCKET_COLOR_RELIC_ARCANE)
3198 if (gem->Type & SOCKET_COLOR_RELIC_FROST)
3200 if (gem->Type & SOCKET_COLOR_RELIC_FIRE)
3202 if (gem->Type & SOCKET_COLOR_RELIC_WATER)
3204 if (gem->Type & SOCKET_COLOR_RELIC_LIFE)
3206 if (gem->Type & SOCKET_COLOR_RELIC_WIND)
3208 if (gem->Type & SOCKET_COLOR_RELIC_HOLY)
3210 }
3211 }
3212 else
3213 ItemType = 0;
3214
3215 for (uint32 i = 0; i < MAX_ITEM_PROTO_STATS; ++i)
3216 if (sparse->StatModifierBonusStat[i] != -1)
3218 }
3219
3220 void AddStat(ItemSpecStat statType)
3221 {
3223 return;
3224
3225 for (uint32 i = 0; i < MAX_ITEM_PROTO_STATS; ++i)
3226 if (ItemSpecStatTypes[i] == uint32(statType))
3227 return;
3228
3230 }
3231
3232 void AddModStat(int32 itemStatType)
3233 {
3234 switch (itemStatType)
3235 {
3236 case ITEM_MOD_AGILITY:
3238 break;
3239 case ITEM_MOD_STRENGTH:
3241 break;
3242 case ITEM_MOD_INTELLECT:
3244 break;
3247 break;
3250 break;
3256 break;
3259 break;
3262 break;
3265 break;
3270 break;
3271 case ITEM_MOD_AGI_STR:
3274 break;
3275 case ITEM_MOD_AGI_INT:
3278 break;
3279 case ITEM_MOD_STR_INT:
3282 break;
3283 }
3284 }
3285};
3286
3288{
3289 uint32 oldMSTime = getMSTime();
3290
3291 for (ItemSparseEntry const* sparse : sItemSparseStore)
3292 {
3293 ItemEntry const* db2Data = sItemStore.LookupEntry(sparse->ID);
3294 if (!db2Data)
3295 continue;
3296
3297 ItemTemplate& itemTemplate = _itemTemplateStore[sparse->ID];
3298
3299 itemTemplate.BasicData = db2Data;
3300 itemTemplate.ExtendedData = sparse;
3301
3302 itemTemplate.MaxDurability = FillMaxDurability(db2Data->ClassID, db2Data->SubclassID, sparse->InventoryType, sparse->OverallQualityID, sparse->ItemLevel);
3303 itemTemplate.ScriptId = 0;
3304 itemTemplate.FoodType = 0;
3305 itemTemplate.MinMoneyLoot = 0;
3306 itemTemplate.MaxMoneyLoot = 0;
3307 itemTemplate.FlagsCu = 0;
3308 itemTemplate.SpellPPMRate = 0.0f;
3309 itemTemplate.RandomBonusListTemplateId = 0;
3310 itemTemplate.ItemSpecClassMask = 0;
3311 itemTemplate.QuestLogItemId = 0;
3312
3313 if (std::vector<ItemSpecOverrideEntry const*> const* itemSpecOverrides = sDB2Manager.GetItemSpecOverrides(sparse->ID))
3314 {
3315 for (ItemSpecOverrideEntry const* itemSpecOverride : *itemSpecOverrides)
3316 {
3317 if (ChrSpecializationEntry const* specialization = sChrSpecializationStore.LookupEntry(itemSpecOverride->SpecID))
3318 {
3319 itemTemplate.ItemSpecClassMask |= 1 << (specialization->ClassID - 1);
3320 itemTemplate.Specializations[0].set(ItemTemplate::CalculateItemSpecBit(specialization));
3321 itemTemplate.Specializations[1] |= itemTemplate.Specializations[0];
3322 itemTemplate.Specializations[2] |= itemTemplate.Specializations[0];
3323 }
3324 }
3325 }
3326 else
3327 {
3328 ItemSpecStats itemSpecStats(db2Data, sparse);
3329
3330 for (ItemSpecEntry const* itemSpec : sItemSpecStore)
3331 {
3332 if (itemSpecStats.ItemType != itemSpec->ItemType)
3333 continue;
3334
3335 bool hasPrimary = itemSpec->PrimaryStat == ITEM_SPEC_STAT_NONE;
3336 bool hasSecondary = itemSpec->SecondaryStat == ITEM_SPEC_STAT_NONE;
3337 for (uint32 i = 0; i < itemSpecStats.ItemSpecStatCount; ++i)
3338 {
3339 if (itemSpecStats.ItemSpecStatTypes[i] == itemSpec->PrimaryStat)
3340 hasPrimary = true;
3341 if (itemSpecStats.ItemSpecStatTypes[i] == itemSpec->SecondaryStat)
3342 hasSecondary = true;
3343 }
3344
3345 if (!hasPrimary || !hasSecondary)
3346 continue;
3347
3348 if (ChrSpecializationEntry const* specialization = sChrSpecializationStore.LookupEntry(itemSpec->SpecializationID))
3349 {
3350 if ((1 << (specialization->ClassID - 1)) & sparse->AllowableClass)
3351 {
3352 itemTemplate.ItemSpecClassMask |= 1 << (specialization->ClassID - 1);
3353 std::size_t specBit = ItemTemplate::CalculateItemSpecBit(specialization);
3354 itemTemplate.Specializations[0].set(specBit);
3355 if (itemSpec->MaxLevel > 40)
3356 itemTemplate.Specializations[1].set(specBit);
3357 if (itemSpec->MaxLevel >= 110)
3358 itemTemplate.Specializations[2].set(specBit);
3359 }
3360 }
3361 }
3362 }
3363
3364 // Items that have no specializations set can be used by everyone
3365 for (auto& specs : itemTemplate.Specializations)
3366 if (specs.count() == 0)
3367 specs.set();
3368 }
3369
3370 // Load item effects (spells)
3371 for (ItemXItemEffectEntry const* effectEntry : sItemXItemEffectStore)
3373 if (ItemEffectEntry const* effect = sItemEffectStore.LookupEntry(effectEntry->ItemEffectID))
3374 item->Effects.push_back(effect);
3375
3376 TC_LOG_INFO("server.loading", ">> Loaded {} item templates in {} ms", _itemTemplateStore.size(), GetMSTimeDiffToNow(oldMSTime));
3377}
3378
3380{
3381 uint32 oldMSTime = getMSTime();
3382 uint32 count = 0;
3383
3384 QueryResult result = WorldDatabase.Query("SELECT Id, FlagsCu, FoodType, MinMoneyLoot, MaxMoneyLoot, SpellPPMChance, RandomBonusListTemplateId, QuestLogItemId FROM item_template_addon");
3385 if (result)
3386 {
3387 do
3388 {
3389 Field* fields = result->Fetch();
3390 uint32 itemId = fields[0].GetUInt32();
3391 ItemTemplate* itemTemplate = const_cast<ItemTemplate*>(GetItemTemplate(itemId));
3392 if (!itemTemplate)
3393 {
3394 TC_LOG_ERROR("sql.sql", "Item {} specified in `item_template_addon` does not exist, skipped.", itemId);
3395 continue;
3396 }
3397
3398 uint32 minMoneyLoot = fields[3].GetUInt32();
3399 uint32 maxMoneyLoot = fields[4].GetUInt32();
3400 if (minMoneyLoot > maxMoneyLoot)
3401 {
3402 TC_LOG_ERROR("sql.sql", "Minimum money loot specified in `item_template_addon` for item {} was greater than maximum amount, swapping.", itemId);
3403 std::swap(minMoneyLoot, maxMoneyLoot);
3404 }
3405 itemTemplate->FlagsCu = fields[1].GetUInt32();
3406 itemTemplate->FoodType = fields[2].GetUInt8();
3407 itemTemplate->MinMoneyLoot = minMoneyLoot;
3408 itemTemplate->MaxMoneyLoot = maxMoneyLoot;
3409 itemTemplate->SpellPPMRate = fields[5].GetFloat();
3410 itemTemplate->RandomBonusListTemplateId = fields[6].GetUInt32();
3411 itemTemplate->QuestLogItemId = fields[7].GetInt32();
3412 ++count;
3413 } while (result->NextRow());
3414 }
3415 TC_LOG_INFO("server.loading", ">> Loaded {} item addon templates in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
3416}
3417
3419{
3420 uint32 oldMSTime = getMSTime();
3421 uint32 count = 0;
3422
3423 QueryResult result = WorldDatabase.Query("SELECT Id, ScriptName FROM item_script_names");
3424 if (result)
3425 {
3426 do
3427 {
3428 Field* fields = result->Fetch();
3429 uint32 itemId = fields[0].GetUInt32();
3430 ItemTemplate* itemTemplate = const_cast<ItemTemplate*>(GetItemTemplate(itemId));
3431 if (!itemTemplate)
3432 {
3433 TC_LOG_ERROR("sql.sql", "Item {} specified in `item_script_names` does not exist, skipped.", itemId);
3434 continue;
3435 }
3436
3437 itemTemplate->ScriptId = GetScriptId(fields[1].GetString());
3438 ++count;
3439 } while (result->NextRow());
3440 }
3441
3442 TC_LOG_INFO("server.loading", ">> Loaded {} item script names in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
3443}
3444
3446{
3448}
3449
3451{
3452 uint32 oldMSTime = getMSTime();
3453
3454 _vehicleTemplateAccessoryStore.clear(); // needed for reload case
3455
3456 uint32 count = 0;
3457
3458 // 0 1 2 3 4 5
3459 QueryResult result = WorldDatabase.Query("SELECT `entry`, `accessory_entry`, `seat_id`, `minion`, `summontype`, `summontimer` FROM `vehicle_template_accessory`");
3460
3461 if (!result)
3462 {
3463 TC_LOG_INFO("server.loading", ">> Loaded 0 vehicle template accessories. DB table `vehicle_template_accessory` is empty.");
3464 return;
3465 }
3466
3467 do
3468 {
3469 Field* fields = result->Fetch();
3470
3471 uint32 entry = fields[0].GetUInt32();
3472 uint32 accessory = fields[1].GetUInt32();
3473 int8 seatId = fields[2].GetInt8();
3474 bool isMinion = fields[3].GetBool();
3475 uint8 summonType = fields[4].GetUInt8();
3476 uint32 summonTimer = fields[5].GetUInt32();
3477
3478 if (!GetCreatureTemplate(entry))
3479 {
3480 TC_LOG_ERROR("sql.sql", "Table `vehicle_template_accessory`: creature template entry {} does not exist.", entry);
3481 continue;
3482 }
3483
3484 if (!GetCreatureTemplate(accessory))
3485 {
3486 TC_LOG_ERROR("sql.sql", "Table `vehicle_template_accessory`: Accessory {} does not exist.", accessory);
3487 continue;
3488 }
3489
3490 if (_spellClickInfoStore.find(entry) == _spellClickInfoStore.end())
3491 {
3492 TC_LOG_ERROR("sql.sql", "Table `vehicle_template_accessory`: creature template entry {} has no data in npc_spellclick_spells", entry);
3493 continue;
3494 }
3495
3496 _vehicleTemplateAccessoryStore[entry].push_back(VehicleAccessory(accessory, seatId, isMinion, summonType, summonTimer));
3497
3498 ++count;
3499 }
3500 while (result->NextRow());
3501
3502 TC_LOG_INFO("server.loading", ">> Loaded {} Vehicle Template Accessories in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
3503}
3504
3506{
3507 uint32 oldMSTime = getMSTime();
3508
3509 _vehicleTemplateStore.clear();
3510
3511 // 0 1
3512 QueryResult result = WorldDatabase.Query("SELECT creatureId, despawnDelayMs FROM vehicle_template");
3513
3514 if (!result)
3515 {
3516 TC_LOG_INFO("server.loading", ">> Loaded 0 vehicle template. DB table `vehicle_template` is empty.");
3517 return;
3518 }
3519
3520 do
3521 {
3522 Field* fields = result->Fetch();
3523
3524 uint32 creatureId = fields[0].GetUInt32();
3525
3526 if (!GetCreatureTemplate(creatureId))
3527 {
3528 TC_LOG_ERROR("sql.sql", "Table `vehicle_template`: Vehicle {} does not exist.", creatureId);
3529 continue;
3530 }
3531
3532 VehicleTemplate& vehicleTemplate = _vehicleTemplateStore[creatureId];
3533 vehicleTemplate.DespawnDelay = Milliseconds(fields[1].GetInt32());
3534
3535 } while (result->NextRow());
3536
3537 TC_LOG_INFO("server.loading", ">> Loaded {} Vehicle Template entries in {} ms", _vehicleTemplateStore.size(), GetMSTimeDiffToNow(oldMSTime));
3538}
3539
3541{
3542 uint32 oldMSTime = getMSTime();
3543
3544 _vehicleAccessoryStore.clear(); // needed for reload case
3545
3546 uint32 count = 0;
3547
3548 // 0 1 2 3 4 5
3549 QueryResult result = WorldDatabase.Query("SELECT `guid`, `accessory_entry`, `seat_id`, `minion`, `summontype`, `summontimer` FROM `vehicle_accessory`");
3550
3551 if (!result)
3552 {
3553 TC_LOG_INFO("server.loading", ">> Loaded 0 vehicle accessories. DB table `vehicle_accessory` is empty.");
3554 return;
3555 }
3556
3557 do
3558 {
3559 Field* fields = result->Fetch();
3560
3561 ObjectGuid::LowType uiGUID = fields[0].GetUInt64();
3562 uint32 uiAccessory = fields[1].GetUInt32();
3563 int8 uiSeat = int8(fields[2].GetInt16());
3564 bool bMinion = fields[3].GetBool();
3565 uint8 uiSummonType = fields[4].GetUInt8();
3566 uint32 uiSummonTimer= fields[5].GetUInt32();
3567
3568 if (!GetCreatureTemplate(uiAccessory))
3569 {
3570 TC_LOG_ERROR("sql.sql", "Table `vehicle_accessory`: Accessory {} does not exist.", uiAccessory);
3571 continue;
3572 }
3573
3574 _vehicleAccessoryStore[uiGUID].push_back(VehicleAccessory(uiAccessory, uiSeat, bMinion, uiSummonType, uiSummonTimer));
3575
3576 ++count;
3577 }
3578 while (result->NextRow());
3579
3580 TC_LOG_INFO("server.loading", ">> Loaded {} Vehicle Accessories in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
3581}
3582
3584{
3585 uint32 oldMSTime = getMSTime();
3586
3587 _vehicleSeatAddonStore.clear(); // needed for reload case
3588
3589 uint32 count = 0;
3590
3591 // 0 1 2 3 4 5 6
3592 QueryResult result = WorldDatabase.Query("SELECT `SeatEntry`, `SeatOrientation`, `ExitParamX`, `ExitParamY`, `ExitParamZ`, `ExitParamO`, `ExitParamValue` FROM `vehicle_seat_addon`");
3593
3594 if (!result)
3595 {
3596 TC_LOG_ERROR("server.loading", ">> Loaded 0 vehicle seat addons. DB table `vehicle_seat_addon` is empty.");
3597 return;
3598 }
3599
3600 do
3601 {
3602 Field* fields = result->Fetch();
3603
3604 uint32 seatID = fields[0].GetUInt32();
3605 float orientation = fields[1].GetFloat();
3606 float exitX = fields[2].GetFloat();
3607 float exitY = fields[3].GetFloat();
3608 float exitZ = fields[4].GetFloat();
3609 float exitO = fields[5].GetFloat();
3610 uint8 exitParam = fields[6].GetUInt8();
3611
3612 if (!sVehicleSeatStore.LookupEntry(seatID))
3613 {
3614 TC_LOG_ERROR("sql.sql", "Table `vehicle_seat_addon`: SeatID: {} does not exist in VehicleSeat.dbc. Skipping entry.", seatID);
3615 continue;
3616 }
3617
3618 // Sanitizing values
3619 if (orientation > float(M_PI * 2))
3620 {
3621 TC_LOG_ERROR("sql.sql", "Table `vehicle_seat_addon`: SeatID: {} is using invalid angle offset value ({}). Set Value to 0.", seatID, orientation);
3622 orientation = 0.0f;
3623 }
3624
3626 {
3627 TC_LOG_ERROR("sql.sql", "Table `vehicle_seat_addon`: SeatID: {} is using invalid exit parameter value ({}). Setting to 0 (none).", seatID, exitParam);
3628 continue;
3629 }
3630
3631 _vehicleSeatAddonStore[seatID] = VehicleSeatAddon(orientation, exitX, exitY, exitZ, exitO, exitParam);
3632
3633 ++count;
3634 } while (result->NextRow());
3635
3636 TC_LOG_INFO("server.loading", ">> Loaded {} Vehicle Seat Addon entries in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
3637}
3638
3640{
3641 uint32 oldMSTime = getMSTime();
3642
3643 // 0 1 2 3 4 5 6 7 8 9
3644 QueryResult result = WorldDatabase.Query("SELECT creature_entry, level, hp, mana, str, agi, sta, inte, spi, armor FROM pet_levelstats");
3645
3646 if (!result)
3647 {
3648 TC_LOG_INFO("server.loading", ">> Loaded 0 level pet stats definitions. DB table `pet_levelstats` is empty.");
3649 return;
3650 }
3651
3652 uint32 count = 0;
3653
3654 do
3655 {
3656 Field* fields = result->Fetch();
3657
3658 uint32 creature_id = fields[0].GetUInt32();
3659 if (!GetCreatureTemplate(creature_id))
3660 {
3661 TC_LOG_ERROR("sql.sql", "Wrong creature id {} in `pet_levelstats` table, ignoring.", creature_id);
3662 continue;
3663 }
3664
3665 uint32 current_level = fields[1].GetUInt8();
3666 if (current_level > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
3667 {
3668 if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum
3669 TC_LOG_ERROR("sql.sql", "Wrong (> {}) level {} in `pet_levelstats` table, ignoring.", STRONG_MAX_LEVEL, current_level);
3670 else
3671 {
3672 TC_LOG_INFO("misc", "Unused (> MaxPlayerLevel in worldserver.conf) level {} in `pet_levelstats` table, ignoring.", current_level);
3673 ++count; // make result loading percent "expected" correct in case disabled detail mode for example.
3674 }
3675 continue;
3676 }
3677 else if (current_level < 1)
3678 {
3679 TC_LOG_ERROR("sql.sql", "Wrong (<1) level {} in `pet_levelstats` table, ignoring.", current_level);
3680 continue;
3681 }
3682
3683 auto& pInfoMapEntry = _petInfoStore[creature_id];
3684 if (!pInfoMapEntry)
3685 pInfoMapEntry = std::make_unique<PetLevelInfo[]>(sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL));
3686
3687 // data for level 1 stored in [0] array element, ...
3688 PetLevelInfo* pLevelInfo = &pInfoMapEntry[current_level - 1];
3689
3690 pLevelInfo->health = fields[2].GetUInt16();
3691 pLevelInfo->mana = fields[3].GetUInt16();
3692 pLevelInfo->armor = fields[9].GetUInt32();
3693
3694 for (uint8 i = 0; i < MAX_STATS; i++)
3695 pLevelInfo->stats[i] = fields[i + 4].GetUInt16();
3696
3697 ++count;
3698 }
3699 while (result->NextRow());
3700
3701 // Fill gaps and check integrity
3702 for (PetLevelInfoContainer::iterator itr = _petInfoStore.begin(); itr != _petInfoStore.end(); ++itr)
3703 {
3704 auto& pInfo = itr->second;
3705
3706 // fatal error if no level 1 data
3707 if (!pInfo || pInfo[0].health == 0)
3708 {
3709 TC_LOG_ERROR("sql.sql", "Creature {} does not have pet stats data for Level 1!", itr->first);
3710 ABORT();
3711 }
3712
3713 // fill level gaps
3714 for (uint8 level = 1; level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
3715 {
3716 if (pInfo[level].health == 0)
3717 {
3718 TC_LOG_ERROR("sql.sql", "Creature {} has no data for Level {} pet stats data, using data of Level {}.", itr->first, level + 1, level);
3719 pInfo[level] = pInfo[level - 1];
3720 }
3721 }
3722 }
3723
3724 TC_LOG_INFO("server.loading", ">> Loaded {} level pet stats definitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
3725}
3726
3727PetLevelInfo const* ObjectMgr::GetPetLevelInfo(uint32 creature_id, uint8 level) const
3728{
3729 if (level > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
3730 level = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
3731
3732 auto itr = _petInfoStore.find(creature_id);
3733 if (itr == _petInfoStore.end())
3734 return nullptr;
3735
3736 return &itr->second[level - 1]; // data for level 1 stored in [0] array element, ...
3737}
3738
3740{
3741 PlayerInfo* playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(race_), Classes(class_) });
3742 if (!playerInfo)
3743 return;
3744
3745 if (count > 0)
3746 playerInfo->item.emplace_back(itemId, count);
3747 else
3748 {
3749 if (count < -1)
3750 TC_LOG_ERROR("sql.sql", "Invalid count {} specified on item {} be removed from original player create info (use -1)!", count, itemId);
3751
3752 PlayerCreateInfoItems& items = playerInfo->item;
3753
3754 auto erased = std::remove_if(items.begin(), items.end(), [itemId](PlayerCreateInfoItem const& item) { return item.item_id == itemId; });
3755 if (erased == items.end())
3756 {
3757 TC_LOG_ERROR("sql.sql", "Item {} specified to be removed from original create info not found in db2!", itemId);
3758 return;
3759 }
3760
3761 items.erase(erased, items.end());
3762 }
3763}
3764
3766{
3767 // Load playercreate
3768 {
3769 uint32 oldMSTime = getMSTime();
3770 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
3771 QueryResult result = WorldDatabase.Query("SELECT race, class, map, position_x, position_y, position_z, orientation, npe_map, npe_position_x, npe_position_y, npe_position_z, npe_orientation, npe_transport_guid, intro_movie_id, intro_scene_id, npe_intro_scene_id FROM playercreateinfo");
3772
3773 if (!result)
3774 {
3775 TC_LOG_ERROR("server.loading", ">> Loaded 0 player create definitions. DB table `playercreateinfo` is empty.");
3776 ABORT();
3777 }
3778 else
3779 {
3780 uint32 count = 0;
3781
3782 do
3783 {
3784 Field* fields = result->Fetch();
3785
3786 uint32 current_race = fields[0].GetUInt8();
3787 uint32 current_class = fields[1].GetUInt8();
3788 uint32 mapId = fields[2].GetUInt16();
3789 float positionX = fields[3].GetFloat();
3790 float positionY = fields[4].GetFloat();
3791 float positionZ = fields[5].GetFloat();
3792 float orientation = fields[6].GetFloat();
3793
3794 if (!sChrRacesStore.LookupEntry(current_race))
3795 {
3796 TC_LOG_ERROR("sql.sql", "Wrong race {} in `playercreateinfo` table, ignoring.", current_race);
3797 continue;
3798 }
3799
3800 if (!sChrClassesStore.LookupEntry(current_class))
3801 {
3802 TC_LOG_ERROR("sql.sql", "Wrong class {} in `playercreateinfo` table, ignoring.", current_class);
3803 continue;
3804 }
3805
3806 // accept DB data only for valid position (and non instanceable)
3807 if (!MapManager::IsValidMapCoord(mapId, positionX, positionY, positionZ, orientation))
3808 {
3809 TC_LOG_ERROR("sql.sql", "Wrong home position for class {} race {} pair in `playercreateinfo` table, ignoring.", current_class, current_race);
3810 continue;
3811 }
3812
3813 if (sMapStore.LookupEntry(mapId)->Instanceable())
3814 {
3815 TC_LOG_ERROR("sql.sql", "Home position in instanceable map for class {} race {} pair in `playercreateinfo` table, ignoring.", current_class, current_race);
3816 continue;
3817 }
3818
3819 if (!sDB2Manager.GetChrModel(current_race, GENDER_MALE))
3820 {
3821 TC_LOG_ERROR("sql.sql", "Missing male model for race {}, ignoring.", current_race);
3822 continue;
3823 }
3824
3825 if (!sDB2Manager.GetChrModel(current_race, GENDER_FEMALE))
3826 {
3827 TC_LOG_ERROR("sql.sql", "Missing female model for race {}, ignoring.", current_race);
3828 continue;
3829 }
3830
3831 std::unique_ptr<PlayerInfo> info = std::make_unique<PlayerInfo>();
3832 info->createPosition.Loc.WorldRelocate(mapId, positionX, positionY, positionZ, orientation);
3833
3834 if (std::none_of(fields + 7, fields + 12, [](Field const& field) { return field.IsNull(); }))
3835 {
3836 info->createPositionNPE.emplace();
3837
3838 info->createPositionNPE->Loc.WorldRelocate(fields[7].GetUInt32(), fields[8].GetFloat(), fields[9].GetFloat(), fields[10].GetFloat(), fields[11].GetFloat());
3839 if (!fields[12].IsNull())
3840 info->createPositionNPE->TransportGuid = fields[12].GetUInt64();
3841
3842 if (!sMapStore.LookupEntry(info->createPositionNPE->Loc.GetMapId()))
3843 {
3844 TC_LOG_ERROR("sql.sql", "Invalid NPE map id {} for class {} race {} pair in `playercreateinfo` table, ignoring.",
3845 info->createPositionNPE->Loc.GetMapId(), current_class, current_race);
3846 info->createPositionNPE.reset();
3847 }
3848
3849 if (info->createPositionNPE && info->createPositionNPE->TransportGuid && !sTransportMgr->GetTransportSpawn(*info->createPositionNPE->TransportGuid))
3850 {
3851 TC_LOG_ERROR("sql.sql", "Invalid NPE transport spawn id {} for class {} race {} pair in `playercreateinfo` table, ignoring.",
3852 *info->createPositionNPE->TransportGuid, current_class, current_race);
3853 info->createPositionNPE.reset(); // remove entire NPE data - assume user put transport offsets into npe_position fields
3854 }
3855 }
3856
3857 if (!fields[13].IsNull())
3858 {
3859 uint32 introMovieId = fields[13].GetUInt32();
3860 if (sMovieStore.LookupEntry(introMovieId))
3861 info->introMovieId = introMovieId;
3862 else
3863 TC_LOG_ERROR("sql.sql", "Invalid intro movie id {} for class {} race {} pair in `playercreateinfo` table, ignoring.",
3864 introMovieId, current_class, current_race);
3865 }
3866
3867 if (!fields[14].IsNull())
3868 {
3869 uint32 introSceneId = fields[14].GetUInt32();
3870 if (GetSceneTemplate(introSceneId))
3871 info->introSceneId = introSceneId;
3872 else
3873 TC_LOG_ERROR("sql.sql", "Invalid intro scene id {} for class {} race {} pair in `playercreateinfo` table, ignoring.",
3874 introSceneId, current_class, current_race);
3875 }
3876
3877 if (!fields[15].IsNull())
3878 {
3879 uint32 introSceneId = fields[15].GetUInt32();
3880 if (GetSceneTemplate(introSceneId))
3881 info->introSceneIdNPE = introSceneId;
3882 else
3883 TC_LOG_ERROR("sql.sql", "Invalid NPE intro scene id {} for class {} race {} pair in `playercreateinfo` table, ignoring.",
3884 introSceneId, current_class, current_race);
3885 }
3886
3887 _playerInfo[{ Races(current_race), Classes(current_class) }] = std::move(info);
3888
3889 ++count;
3890 }
3891 while (result->NextRow());
3892
3893 TC_LOG_INFO("server.loading", ">> Loaded {} player create definitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
3894 }
3895 }
3896
3897 // Load playercreate items
3898 TC_LOG_INFO("server.loading", "Loading Player Create Items Data...");
3899 {
3900 std::unordered_map<uint32, std::vector<ItemTemplate const*>> itemsByCharacterLoadout;
3901 for (CharacterLoadoutItemEntry const* characterLoadoutItem : sCharacterLoadoutItemStore)
3902 if (ItemTemplate const* itemTemplate = GetItemTemplate(characterLoadoutItem->ItemID))
3903 itemsByCharacterLoadout[characterLoadoutItem->CharacterLoadoutID].push_back(itemTemplate);
3904
3905 for (CharacterLoadoutEntry const* characterLoadout : sCharacterLoadoutStore)
3906 {
3907 if (!characterLoadout->IsForNewCharacter())
3908 continue;
3909
3910 std::vector<ItemTemplate const*> const* items = Trinity::Containers::MapGetValuePtr(itemsByCharacterLoadout, characterLoadout->ID);
3911 if (!items)
3912 continue;
3913
3914 for (uint32 raceIndex = RACE_HUMAN; raceIndex < MAX_RACES; ++raceIndex)
3915 {
3916 if (!characterLoadout->RaceMask.HasRace(raceIndex))
3917 continue;
3918
3919 if (auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(raceIndex), Classes(characterLoadout->ChrClassID) }))
3920 {
3921 playerInfo->itemContext = ItemContext(characterLoadout->ItemContext);
3922
3923 for (ItemTemplate const* itemTemplate : *items)
3924 {
3925 // BuyCount by default
3926 uint32 count = itemTemplate->GetBuyCount();
3927
3928 // special amount for food/drink
3929 if (itemTemplate->GetClass() == ITEM_CLASS_CONSUMABLE && itemTemplate->GetSubClass() == ITEM_SUBCLASS_FOOD_DRINK)
3930 {
3931 if (!itemTemplate->Effects.empty())
3932 {
3933 switch (itemTemplate->Effects[0]->SpellCategoryID)
3934 {
3935 case SPELL_CATEGORY_FOOD: // food
3936 count = characterLoadout->ChrClassID == CLASS_DEATH_KNIGHT ? 10 : 4;
3937 break;
3938 case SPELL_CATEGORY_DRINK: // drink
3939 count = 2;
3940 break;
3941 }
3942 }
3943 if (itemTemplate->GetMaxStackSize() < count)
3944 count = itemTemplate->GetMaxStackSize();
3945 }
3946
3947 playerInfo->item.emplace_back(itemTemplate->GetId(), count);
3948 }
3949 }
3950 }
3951 }
3952 }
3953
3954 TC_LOG_INFO("server.loading", "Loading Player Create Items Override Data...");
3955 {
3956 uint32 oldMSTime = getMSTime();
3957 // 0 1 2 3
3958 QueryResult result = WorldDatabase.Query("SELECT race, class, itemid, amount FROM playercreateinfo_item");
3959
3960 if (!result)
3961 {
3962 TC_LOG_INFO("server.loading", ">> Loaded 0 custom player create items. DB table `playercreateinfo_item` is empty.");
3963 }
3964 else
3965 {
3966 uint32 count = 0;
3967
3968 do
3969 {
3970 Field* fields = result->Fetch();
3971
3972 uint32 current_race = fields[0].GetUInt8();
3973 if (current_race >= MAX_RACES)
3974 {
3975 TC_LOG_ERROR("sql.sql", "Wrong race {} in `playercreateinfo_item` table, ignoring.", current_race);
3976 continue;
3977 }
3978
3979 uint32 current_class = fields[1].GetUInt8();
3980 if (current_class >= MAX_CLASSES)
3981 {
3982 TC_LOG_ERROR("sql.sql", "Wrong class {} in `playercreateinfo_item` table, ignoring.", current_class);
3983 continue;
3984 }
3985
3986 uint32 item_id = fields[2].GetUInt32();
3987
3988 if (!GetItemTemplate(item_id))
3989 {
3990 TC_LOG_ERROR("sql.sql", "Item id {} (race {} class {}) in `playercreateinfo_item` table but it does not exist, ignoring.", item_id, current_race, current_class);
3991 continue;
3992 }
3993
3994 int32 amount = fields[3].GetInt8();
3995
3996 if (!amount)
3997 {
3998 TC_LOG_ERROR("sql.sql", "Item id {} (class {} race {}) have amount == 0 in `playercreateinfo_item` table, ignoring.", item_id, current_race, current_class);
3999 continue;
4000 }
4001
4002 if (!current_race || !current_class)
4003 {
4004 uint32 min_race = current_race ? current_race : 1;
4005 uint32 max_race = current_race ? current_race + 1 : MAX_RACES;
4006 uint32 min_class = current_class ? current_class : 1;
4007 uint32 max_class = current_class ? current_class + 1 : MAX_CLASSES;
4008 for (uint32 r = min_race; r < max_race; ++r)
4009 for (uint32 c = min_class; c < max_class; ++c)
4010 PlayerCreateInfoAddItemHelper(r, c, item_id, amount);
4011 }
4012 else
4013 PlayerCreateInfoAddItemHelper(current_race, current_class, item_id, amount);
4014
4015 ++count;
4016 }
4017 while (result->NextRow());
4018
4019 TC_LOG_INFO("server.loading", ">> Loaded {} custom player create items in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
4020 }
4021 }
4022
4023 // Load playercreate skills
4024 TC_LOG_INFO("server.loading", "Loading Player Create Skill Data...");
4025 {
4026 uint32 oldMSTime = getMSTime();
4027
4029 if (rcInfo->Availability == 1)
4030 for (uint32 raceIndex = RACE_HUMAN; raceIndex < MAX_RACES; ++raceIndex)
4031 if (rcInfo->RaceMask.IsEmpty() || rcInfo->RaceMask.HasRace(raceIndex))
4032 for (uint32 classIndex = CLASS_WARRIOR; classIndex < MAX_CLASSES; ++classIndex)
4033 if (rcInfo->ClassMask == -1 || rcInfo->ClassMask == 0 || ((1 << (classIndex - 1)) & rcInfo->ClassMask))
4034 if (auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(raceIndex), Classes(classIndex) }))
4035 playerInfo->skills.push_back(rcInfo);
4036
4037 TC_LOG_INFO("server.loading", ">> Loaded player create skills in {} ms", GetMSTimeDiffToNow(oldMSTime));
4038 }
4039
4040 // Load playercreate custom spells
4041 TC_LOG_INFO("server.loading", "Loading Player Create Custom Spell Data...");
4042 {
4043 uint32 oldMSTime = getMSTime();
4044
4045 QueryResult result = WorldDatabase.PQuery("SELECT racemask, classmask, Spell FROM playercreateinfo_spell_custom");
4046
4047 if (!result)
4048 {
4049 TC_LOG_INFO("server.loading", ">> Loaded 0 player create custom spells. DB table `playercreateinfo_spell_custom` is empty.");
4050 }
4051 else
4052 {
4053 uint32 count = 0;
4054
4055 do
4056 {
4057 Field* fields = result->Fetch();
4058 Trinity::RaceMask<uint64> raceMask = { fields[0].GetUInt64() };
4059 uint32 classMask = fields[1].GetUInt32();
4060 uint32 spellId = fields[2].GetUInt32();
4061
4062 if (!raceMask.IsEmpty() && (raceMask & RACEMASK_ALL_PLAYABLE).IsEmpty())
4063 {
4064 TC_LOG_ERROR("sql.sql", "Wrong race mask {} in `playercreateinfo_spell_custom` table, ignoring.", raceMask.RawValue);
4065 continue;
4066 }
4067
4068 if (classMask != 0 && !(classMask & CLASSMASK_ALL_PLAYABLE))
4069 {
4070 TC_LOG_ERROR("sql.sql", "Wrong class mask {} in `playercreateinfo_spell_custom` table, ignoring.", classMask);
4071 continue;
4072 }
4073
4074 for (uint32 raceIndex = RACE_HUMAN; raceIndex < MAX_RACES; ++raceIndex)
4075 {
4076 if (raceMask.IsEmpty() || raceMask.HasRace(raceIndex))
4077 {
4078 for (uint32 classIndex = CLASS_WARRIOR; classIndex < MAX_CLASSES; ++classIndex)
4079 {
4080 if (classMask == 0 || ((1 << (classIndex - 1)) & classMask))
4081 {
4082 if (auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(raceIndex), Classes(classIndex) }))
4083 {
4084 playerInfo->customSpells.push_back(spellId);
4085 ++count;
4086 }
4087 // We need something better here, the check is not accounting for spells used by multiple races/classes but not all of them.
4088 // Either split the masks per class, or per race, which kind of kills the point yet.
4089 // else if (raceMask != 0 && classMask != 0)
4090 // TC_LOG_ERROR("sql.sql", "Racemask/classmask ({}/{}) combination was found containing an invalid race/class combination ({}/{}) in `{}` (Spell {}), ignoring.", raceMask, classMask, raceIndex, classIndex, tableName, spellId);
4091 }
4092 }
4093 }
4094 }
4095 }
4096 while (result->NextRow());
4097
4098 TC_LOG_INFO("server.loading", ">> Loaded {} custom player create spells in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
4099 }
4100 }
4101
4102 // Load playercreate cast spell
4103 TC_LOG_INFO("server.loading", "Loading Player Create Cast Spell Data...");
4104 {
4105 uint32 oldMSTime = getMSTime();
4106
4107 QueryResult result = WorldDatabase.PQuery("SELECT raceMask, classMask, spell, createMode FROM playercreateinfo_cast_spell");
4108
4109 if (!result)
4110 TC_LOG_INFO("server.loading", ">> Loaded 0 player create cast spells. DB table `playercreateinfo_cast_spell` is empty.");
4111 else
4112 {
4113 uint32 count = 0;
4114
4115 do
4116 {
4117 Field* fields = result->Fetch();
4118 Trinity::RaceMask<uint64> raceMask = { fields[0].GetUInt64() };
4119 uint32 classMask = fields[1].GetUInt32();
4120 uint32 spellId = fields[2].GetUInt32();
4121 int8 playerCreateMode = fields[3].GetInt8();
4122
4123 if (!raceMask.IsEmpty() && (raceMask & RACEMASK_ALL_PLAYABLE).IsEmpty())
4124 {
4125 TC_LOG_ERROR("sql.sql", "Wrong race mask {} in `playercreateinfo_cast_spell` table, ignoring.", raceMask.RawValue);
4126 continue;
4127 }
4128
4129 if (classMask != 0 && !(classMask & CLASSMASK_ALL_PLAYABLE))
4130 {
4131 TC_LOG_ERROR("sql.sql", "Wrong class mask {} in `playercreateinfo_cast_spell` table, ignoring.", classMask);
4132 continue;
4133 }
4134
4135 if (playerCreateMode < 0 || playerCreateMode >= AsUnderlyingType(PlayerCreateMode::Max))
4136 {
4137 TC_LOG_ERROR("sql.sql", "Uses invalid createMode {} in `playercreateinfo_cast_spell` table, ignoring.", playerCreateMode);
4138 continue;
4139 }
4140
4141 for (uint32 raceIndex = RACE_HUMAN; raceIndex < MAX_RACES; ++raceIndex)
4142 {
4143 if (raceMask.IsEmpty() || raceMask.HasRace(raceIndex))
4144 {
4145 for (uint32 classIndex = CLASS_WARRIOR; classIndex < MAX_CLASSES; ++classIndex)
4146 {
4147 if (classMask == 0 || ((1 << (classIndex - 1)) & classMask))
4148 {
4149 if (auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(raceIndex), Classes(classIndex) }))
4150 {
4151 playerInfo->castSpells[playerCreateMode].push_back(spellId);
4152 ++count;
4153 }
4154 }
4155 }
4156 }
4157 }
4158 } while (result->NextRow());
4159
4160 TC_LOG_INFO("server.loading", ">> Loaded {} player create cast spells in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
4161 }
4162 }
4163
4164 // Load playercreate actions
4165 TC_LOG_INFO("server.loading", "Loading Player Create Action Data...");
4166 {
4167 uint32 oldMSTime = getMSTime();
4168
4169 // 0 1 2 3 4
4170 QueryResult result = WorldDatabase.Query("SELECT race, class, button, action, type FROM playercreateinfo_action");
4171
4172 if (!result)
4173 {
4174 TC_LOG_INFO("server.loading", ">> Loaded 0 player create actions. DB table `playercreateinfo_action` is empty.");
4175 }
4176 else
4177 {
4178 uint32 count = 0;
4179
4180 do
4181 {
4182 Field* fields = result->Fetch();
4183
4184 uint32 current_race = fields[0].GetUInt8();
4185 if (current_race >= MAX_RACES)
4186 {
4187 TC_LOG_ERROR("sql.sql", "Wrong race {} in `playercreateinfo_action` table, ignoring.", current_race);
4188 continue;
4189 }
4190
4191 uint32 current_class = fields[1].GetUInt8();
4192 if (current_class >= MAX_CLASSES)
4193 {
4194 TC_LOG_ERROR("sql.sql", "Wrong class {} in `playercreateinfo_action` table, ignoring.", current_class);
4195 continue;
4196 }
4197
4198 if (auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(current_race), Classes(current_class) }))
4199 playerInfo->action.push_back(PlayerCreateInfoAction(fields[2].GetUInt16(), fields[3].GetUInt32(), fields[4].GetUInt16()));
4200
4201 ++count;
4202 }
4203 while (result->NextRow());
4204
4205 TC_LOG_INFO("server.loading", ">> Loaded {} player create actions in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
4206 }
4207 }
4208
4209 // Loading levels data (class/race dependent)
4210 TC_LOG_INFO("server.loading", "Loading Player Create Level Stats Data...");
4211 {
4212 struct RaceStats
4213 {
4214 std::array<int16, MAX_STATS> StatModifier = { };
4215 };
4216
4217 std::array<RaceStats, MAX_RACES> raceStatModifiers = { };
4218
4219 uint32 oldMSTime = getMSTime();
4220
4221 QueryResult raceStatsResult = WorldDatabase.Query("SELECT race, str, agi, sta, inte FROM player_racestats");
4222
4223 if (!raceStatsResult)
4224 {
4225 TC_LOG_ERROR("server.loading", ">> Loaded 0 race stats definitions. DB table `player_racestats` is empty.");
4226 ABORT();
4227 }
4228
4229 do
4230 {
4231 Field* fields = raceStatsResult->Fetch();
4232
4233 uint32 current_race = fields[0].GetUInt8();
4234 if (current_race >= MAX_RACES)
4235 {
4236 TC_LOG_ERROR("sql.sql", "Wrong race {} in `player_racestats` table, ignoring.", current_race);
4237 continue;
4238 }
4239
4240 for (uint32 i = 0; i < MAX_STATS; ++i)
4241 raceStatModifiers[current_race].StatModifier[i] = fields[i + 1].GetInt16();
4242
4243 } while (raceStatsResult->NextRow());
4244
4245 // 0 1 2 3 4 5
4246 QueryResult result = WorldDatabase.Query("SELECT class, level, str, agi, sta, inte FROM player_classlevelstats");
4247
4248 if (!result)
4249 {
4250 TC_LOG_ERROR("server.loading", ">> Loaded 0 level stats definitions. DB table `player_classlevelstats` is empty.");
4251 ABORT();
4252 }
4253
4254 uint32 count = 0;
4255
4256 do
4257 {
4258 Field* fields = result->Fetch();
4259
4260 uint32 current_class = fields[0].GetUInt8();
4261 if (current_class >= MAX_CLASSES)
4262 {
4263 TC_LOG_ERROR("sql.sql", "Wrong class {} in `player_classlevelstats` table, ignoring.", current_class);
4264 continue;
4265 }
4266
4267 uint32 current_level = fields[1].GetUInt8();
4268 if (current_level > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
4269 {
4270 if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum
4271 TC_LOG_ERROR("sql.sql", "Wrong (> {}) level {} in `player_classlevelstats` table, ignoring.", STRONG_MAX_LEVEL, current_level);
4272 else
4273 TC_LOG_INFO("misc", "Unused (> MaxPlayerLevel in worldserver.conf) level {} in `player_classlevelstats` table, ignoring.", current_level);
4274
4275 continue;
4276 }
4277
4278 for (std::size_t race = 0; race < raceStatModifiers.size(); ++race)
4279 {
4280 if (auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(race), Classes(current_class) }))
4281 {
4282 if (!playerInfo->levelInfo)
4283 playerInfo->levelInfo = std::make_unique<PlayerLevelInfo[]>(sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL));
4284
4285 PlayerLevelInfo& levelInfo = playerInfo->levelInfo[current_level - 1];
4286 for (uint8 i = 0; i < MAX_STATS; ++i)
4287 levelInfo.stats[i] = fields[i + 2].GetInt32() + raceStatModifiers[race].StatModifier[i];
4288 }
4289 }
4290
4291 ++count;
4292 }
4293 while (result->NextRow());
4294
4295 // Fill gaps and check integrity
4296 for (uint8 race = 0; race < MAX_RACES; ++race)
4297 {
4298 // skip non existed races
4299 if (!sChrRacesStore.LookupEntry(race))
4300 continue;
4301
4302 for (uint8 class_ = 0; class_ < MAX_CLASSES; ++class_)
4303 {
4304 // skip non existed classes
4305 if (!sChrClassesStore.LookupEntry(class_))
4306 continue;
4307
4308 auto const& playerInfo = Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(race), Classes(class_) });
4309 if (!playerInfo)
4310 continue;
4311
4312 // skip expansion races if not playing with expansion
4313 if (sWorld->getIntConfig(CONFIG_EXPANSION) < EXPANSION_THE_BURNING_CRUSADE && (race == RACE_BLOODELF || race == RACE_DRAENEI))
4314 continue;
4315
4316 // skip expansion classes if not playing with expansion
4318 continue;
4319
4320 // skip expansion races if not playing with expansion
4321 if (sWorld->getIntConfig(CONFIG_EXPANSION) < EXPANSION_CATACLYSM && (race == RACE_GOBLIN || race == RACE_WORGEN))
4322 continue;
4323
4325 continue;
4326
4327 if (sWorld->getIntConfig(CONFIG_EXPANSION) < EXPANSION_LEGION && class_ == CLASS_DEMON_HUNTER)
4328 continue;
4329
4330 if (sWorld->getIntConfig(CONFIG_EXPANSION) < EXPANSION_DRAGONFLIGHT && (class_ == CLASS_EVOKER || race == RACE_DRACTHYR_ALLIANCE || race == RACE_DRACTHYR_HORDE))
4331 continue;
4332
4334 continue;
4335
4336 // fatal error if no level 1 data
4337 if (!playerInfo->levelInfo || playerInfo->levelInfo[0].stats[0] == 0)
4338 {
4339 TC_LOG_ERROR("sql.sql", "Race {} Class {} Level 1 does not have stats data!", race, class_);
4340 ABORT();
4341 }
4342
4343 // fill level gaps
4344 for (uint8 level = 1; level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
4345 {
4346 if (playerInfo->levelInfo[level].stats[0] == 0)
4347 {
4348 TC_LOG_ERROR("sql.sql", "Race {} Class {} Level {} does not have stats data. Using stats data of level {}.", race, class_, level + 1, level);
4349 playerInfo->levelInfo[level] = playerInfo->levelInfo[level - 1];
4350 }
4351 }
4352 }
4353 }
4354
4355 TC_LOG_INFO("server.loading", ">> Loaded {} level stats definitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
4356 }
4357
4358 // Loading xp per level data
4359 TC_LOG_INFO("server.loading", "Loading Player Create XP Data...");
4360 {
4361 uint32 oldMSTime = getMSTime();
4362
4363 _playerXPperLevel.resize(sXpGameTable.GetTableRowCount(), 0);
4364
4365 // 0 1
4366 QueryResult result = WorldDatabase.Query("SELECT Level, Experience FROM player_xp_for_level");
4367
4368 // load the DBC's levels at first...
4369 for (uint32 level = 1; level < sXpGameTable.GetTableRowCount(); ++level)
4370 _playerXPperLevel[level] = sXpGameTable.GetRow(level)->Total;
4371
4372 uint32 count = 0;
4373
4374 // ...overwrite if needed (custom values)
4375 if (result)
4376 {
4377 do
4378 {
4379 Field* fields = result->Fetch();
4380
4381 uint32 current_level = fields[0].GetUInt8();
4382 uint32 current_xp = fields[1].GetUInt32();
4383
4384 if (current_level >= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
4385 {
4386 if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum
4387 TC_LOG_ERROR("sql.sql", "Wrong (> {}) level {} in `player_xp_for_level` table, ignoring.", STRONG_MAX_LEVEL, current_level);
4388 else
4389 {
4390 TC_LOG_INFO("misc", "Unused (> MaxPlayerLevel in worldserver.conf) level {} in `player_xp_for_level` table, ignoring.", current_level);
4391 ++count; // make result loading percent "expected" correct in case disabled detail mode for example.
4392 }
4393 continue;
4394 }
4395 //PlayerXPperLevel
4396 _playerXPperLevel[current_level] = current_xp;
4397 ++count;
4398 } while (result->NextRow());
4399 }
4400
4401 // fill level gaps - only accounting levels > MAX_LEVEL
4402 for (uint8 level = 1; level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
4403 {
4404 if (_playerXPperLevel[level] == 0)
4405 {
4406 TC_LOG_ERROR("sql.sql", "Level {} does not have XP for level data. Using data of level [{}] + 12000.", level + 1, level);
4407 _playerXPperLevel[level] = _playerXPperLevel[level - 1] + 12000;
4408 }
4409 }
4410
4411 TC_LOG_INFO("server.loading", ">> Loaded {} xp for level definition(s) from database in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
4412 }
4413}
4414
4415void ObjectMgr::GetPlayerClassLevelInfo(uint32 class_, uint8 level, uint32& baseMana) const
4416{
4417 if (level < 1 || class_ >= MAX_CLASSES)
4418 return;
4419
4420 if (level > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
4421 level = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
4422
4423 GtBaseMPEntry const* mp = sBaseMPGameTable.GetRow(level);
4424 if (!mp)
4425 {
4426 TC_LOG_ERROR("misc", "Tried to get non-existant Class-Level combination data for base hp/mp. Class {} Level {}", class_, level);
4427 return;
4428 }
4429
4430 baseMana = uint32(GetGameTableColumnForClass(mp, class_));
4431}
4432
4434{
4435 if (level < 1 || race >= MAX_RACES || class_ >= MAX_CLASSES)
4436 return;
4437
4439 if (!pInfo)
4440 return;
4441
4442 if (level <= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
4443 *info = pInfo->levelInfo[level - 1];
4444 else
4445 BuildPlayerLevelInfo(race, class_, level, info);
4446}
4447
4449{
4450 // base data (last known level)
4451 *info = ASSERT_NOTNULL(Trinity::Containers::MapGetValuePtr(_playerInfo, { Races(race), Classes(_class) }))->levelInfo[sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) - 1];
4452
4453 // if conversion from uint32 to uint8 causes unexpected behaviour, change lvl to uint32
4454 for (uint8 lvl = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) - 1; lvl < level; ++lvl)
4455 {
4456 switch (_class)
4457 {
4458 case CLASS_WARRIOR:
4459 info->stats[STAT_STRENGTH] += (lvl > 23 ? 2: (lvl > 1 ? 1: 0));
4460 info->stats[STAT_STAMINA] += (lvl > 23 ? 2: (lvl > 1 ? 1: 0));
4461 info->stats[STAT_AGILITY] += (lvl > 36 ? 1: (lvl > 6 && (lvl%2) ? 1: 0));
4462 info->stats[STAT_INTELLECT] += (lvl > 9 && !(lvl%2) ? 1: 0);
4463 break;
4464 case CLASS_PALADIN:
4465 info->stats[STAT_STRENGTH] += (lvl > 3 ? 1: 0);
4466 info->stats[STAT_STAMINA] += (lvl > 33 ? 2: (lvl > 1 ? 1: 0));
4467 info->stats[STAT_AGILITY] += (lvl > 38 ? 1: (lvl > 7 && !(lvl%2) ? 1: 0));
4468 info->stats[STAT_INTELLECT] += (lvl > 6 && (lvl%2) ? 1: 0);
4469 break;
4470 case CLASS_HUNTER:
4471 info->stats[STAT_STRENGTH] += (lvl > 4 ? 1: 0);
4472 info->stats[STAT_STAMINA] += (lvl > 4 ? 1: 0);
4473 info->stats[STAT_AGILITY] += (lvl > 33 ? 2: (lvl > 1 ? 1: 0));
4474 info->stats[STAT_INTELLECT] += (lvl > 8 && (lvl%2) ? 1: 0);
4475 break;
4476 case CLASS_ROGUE:
4477 info->stats[STAT_STRENGTH] += (lvl > 5 ? 1: 0);
4478 info->stats[STAT_STAMINA] += (lvl > 4 ? 1: 0);
4479 info->stats[STAT_AGILITY] += (lvl > 16 ? 2: (lvl > 1 ? 1: 0));
4480 info->stats[STAT_INTELLECT] += (lvl > 8 && !(lvl%2) ? 1: 0);
4481 break;
4482 case CLASS_PRIEST:
4483 info->stats[STAT_STRENGTH] += (lvl > 9 && !(lvl%2) ? 1: 0);
4484 info->stats[STAT_STAMINA] += (lvl > 5 ? 1: 0);
4485 info->stats[STAT_AGILITY] += (lvl > 38 ? 1: (lvl > 8 && (lvl%2) ? 1: 0));
4486 info->stats[STAT_INTELLECT] += (lvl > 22 ? 2: (lvl > 1 ? 1: 0));
4487 break;
4488 case CLASS_SHAMAN:
4489 info->stats[STAT_STRENGTH] += (lvl > 34 ? 1: (lvl > 6 && (lvl%2) ? 1: 0));
4490 info->stats[STAT_STAMINA] += (lvl > 4 ? 1: 0);
4491 info->stats[STAT_AGILITY] += (lvl > 7 && !(lvl%2) ? 1: 0);
4492 info->stats[STAT_INTELLECT] += (lvl > 5 ? 1: 0);
4493 break;
4494 case CLASS_MAGE:
4495 info->stats[STAT_STRENGTH] += (lvl > 9 && !(lvl%2) ? 1: 0);
4496 info->stats[STAT_STAMINA] += (lvl > 5 ? 1: 0);
4497 info->stats[STAT_AGILITY] += (lvl > 9 && !(lvl%2) ? 1: 0);
4498 info->stats[STAT_INTELLECT] += (lvl > 24 ? 2: (lvl > 1 ? 1: 0));
4499 break;
4500 case CLASS_WARLOCK:
4501 info->stats[STAT_STRENGTH] += (lvl > 9 && !(lvl%2) ? 1: 0);
4502 info->stats[STAT_STAMINA] += (lvl > 38 ? 2: (lvl > 3 ? 1: 0));
4503 info->stats[STAT_AGILITY] += (lvl > 9 && !(lvl%2) ? 1: 0);
4504 info->stats[STAT_INTELLECT] += (lvl > 33 ? 2: (lvl > 2 ? 1: 0));
4505 break;
4506 case CLASS_DRUID:
4507 info->stats[STAT_STRENGTH] += (lvl > 38 ? 2: (lvl > 6 && (lvl%2) ? 1: 0));
4508 info->stats[STAT_STAMINA] += (lvl > 32 ? 2: (lvl > 4 ? 1: 0));
4509 info->stats[STAT_AGILITY] += (lvl > 38 ? 2: (lvl > 8 && (lvl%2) ? 1: 0));
4510 info->stats[STAT_INTELLECT] += (lvl > 38 ? 3: (lvl > 4 ? 1: 0));
4511 break;
4512 }
4513 }
4514}
4515
4516std::vector<uint32> const* ObjectMgr::GetCreatureQuestItemList(uint32 creatureEntry, Difficulty difficulty) const
4517{
4518 if (std::vector<uint32> const* items = Trinity::Containers::MapGetValuePtr(_creatureQuestItemStore, { creatureEntry, difficulty }))
4519 return items;
4520
4521 // If there is no data for the difficulty, try to get data for the fallback difficulty
4522 if (DifficultyEntry const* difficultyEntry = sDifficultyStore.LookupEntry(difficulty))
4523 return GetCreatureQuestItemList(creatureEntry, Difficulty(difficultyEntry->FallbackDifficultyID));
4524
4525 return nullptr;
4526}
4527
4528std::vector<int32> const* ObjectMgr::GetCreatureQuestCurrencyList(uint32 creatureId) const
4529{
4531}
4532
4534{
4535 uint32 oldMSTime = getMSTime();
4536
4537 _questTemplates.clear();
4539 _questObjectives.clear();
4540
4541 _exclusiveQuestGroups.clear();
4542
4543 QueryResult result = WorldDatabase.Query("SELECT "
4544 //0 1 2 3 4 5 6 7 8 9
4545 "ID, QuestType, QuestPackageID, ContentTuningID, QuestSortID, QuestInfoID, SuggestedGroupNum, RewardNextQuest, RewardXPDifficulty, RewardXPMultiplier, "
4546 //10 11 12 13 14 15 16
4547 "RewardMoneyDifficulty, RewardMoneyMultiplier, RewardBonusMoney, RewardSpell, RewardHonor, RewardKillHonor, StartItem, "
4548 //17 18 19 20 21 22
4549 "RewardArtifactXPDifficulty, RewardArtifactXPMultiplier, RewardArtifactCategoryID, Flags, FlagsEx, FlagsEx2, "
4550 //23 24 25 26 27 28 29 30
4551 "RewardItem1, RewardAmount1, ItemDrop1, ItemDropQuantity1, RewardItem2, RewardAmount2, ItemDrop2, ItemDropQuantity2, "
4552 //31 32 33 34 35 36 37 38
4553 "RewardItem3, RewardAmount3, ItemDrop3, ItemDropQuantity3, RewardItem4, RewardAmount4, ItemDrop4, ItemDropQuantity4, "
4554 //39 40 41 42 43 44
4555 "RewardChoiceItemID1, RewardChoiceItemQuantity1, RewardChoiceItemDisplayID1, RewardChoiceItemID2, RewardChoiceItemQuantity2, RewardChoiceItemDisplayID2, "
4556 //45 46 47 48 49 50
4557 "RewardChoiceItemID3, RewardChoiceItemQuantity3, RewardChoiceItemDisplayID3, RewardChoiceItemID4, RewardChoiceItemQuantity4, RewardChoiceItemDisplayID4, "
4558 //51 52 53 54 55 56
4559 "RewardChoiceItemID5, RewardChoiceItemQuantity5, RewardChoiceItemDisplayID5, RewardChoiceItemID6, RewardChoiceItemQuantity6, RewardChoiceItemDisplayID6, "
4560 //57 58 59 60 61 62 63 64
4561 "POIContinent, POIx, POIy, POIPriority, RewardTitle, RewardArenaPoints, RewardSkillLineID, RewardNumSkillUps, "
4562 //65 66 67 68
4563 "PortraitGiver, PortraitGiverMount, PortraitGiverModelSceneID, PortraitTurnIn, "
4564 //69 70 71 72 73 74 75 76
4565 "RewardFactionID1, RewardFactionValue1, RewardFactionOverride1, RewardFactionCapIn1, RewardFactionID2, RewardFactionValue2, RewardFactionOverride2, RewardFactionCapIn2, "
4566 //77 78 79 80 81 82 83 84
4567 "RewardFactionID3, RewardFactionValue3, RewardFactionOverride3, RewardFactionCapIn3, RewardFactionID4, RewardFactionValue4, RewardFactionOverride4, RewardFactionCapIn4, "
4568 //85 86 87 88 89
4569 "RewardFactionID5, RewardFactionValue5, RewardFactionOverride5, RewardFactionCapIn5, RewardFactionFlags, "
4570 //90 91 92 93 94 95 96 97
4571 "RewardCurrencyID1, RewardCurrencyQty1, RewardCurrencyID2, RewardCurrencyQty2, RewardCurrencyID3, RewardCurrencyQty3, RewardCurrencyID4, RewardCurrencyQty4, "
4572 //98 99 100 101 102 103 104 105 106
4573 "AcceptedSoundKitID, CompleteSoundKitID, AreaGroupID, TimeAllowed, AllowableRaces, ResetByScheduler, Expansion, ManagedWorldStateID, QuestSessionBonus, "