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