/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
#include "ObjectMgr.h"
#include "AchievementMgr.h"
#include "ArenaTeamMgr.h"
#include "CharacterCache.h"
#include "Chat.h"
#include "Common.h"
#include "Config.h"
#include "Containers.h"
#include "CreatureAIFactory.h"
#include "DBCStructure.h"
#include "DatabaseEnv.h"
#include "DisableMgr.h"
#include "GameEventMgr.h"
#include "GameObjectAIFactory.h"
#include "GameTime.h"
#include "GossipDef.h"
#include "GroupMgr.h"
#include "GuildMgr.h"
#include "LFGMgr.h"
#include "Log.h"
#include "MapMgr.h"
#include "Pet.h"
#include "PoolMgr.h"
#include "ReputationMgr.h"
#include "ScriptMgr.h"
#include "Spell.h"
#include "SpellMgr.h"
#include "SpellScript.h"
#include "SpellScriptLoader.h"
#include "StringConvert.h"
#include "Tokenize.h"
#include "Transport.h"
#include "Unit.h"
#include "Util.h"
#include "Vehicle.h"
#include "World.h"
#include
#include
#include "ItemEnchantmentMgr.h"
ScriptMapMap sSpellScripts;
ScriptMapMap sEventScripts;
ScriptMapMap sWaypointScripts;
std::string GetScriptsTableNameByType(ScriptsType type)
{
std::string res = "";
switch (type)
{
case SCRIPTS_SPELL:
res = "spell_scripts";
break;
case SCRIPTS_EVENT:
res = "event_scripts";
break;
case SCRIPTS_WAYPOINT:
res = "waypoint_scripts";
break;
default:
break;
}
return res;
}
ScriptMapMap* GetScriptsMapByType(ScriptsType type)
{
ScriptMapMap* res = nullptr;
switch (type)
{
case SCRIPTS_SPELL:
res = &sSpellScripts;
break;
case SCRIPTS_EVENT:
res = &sEventScripts;
break;
case SCRIPTS_WAYPOINT:
res = &sWaypointScripts;
break;
default:
break;
}
return res;
}
std::string GetScriptCommandName(ScriptCommands command)
{
std::string res = "";
switch (command)
{
case SCRIPT_COMMAND_TALK:
res = "SCRIPT_COMMAND_TALK";
break;
case SCRIPT_COMMAND_EMOTE:
res = "SCRIPT_COMMAND_EMOTE";
break;
case SCRIPT_COMMAND_FIELD_SET:
res = "SCRIPT_COMMAND_FIELD_SET";
break;
case SCRIPT_COMMAND_MOVE_TO:
res = "SCRIPT_COMMAND_MOVE_TO";
break;
case SCRIPT_COMMAND_FLAG_SET:
res = "SCRIPT_COMMAND_FLAG_SET";
break;
case SCRIPT_COMMAND_FLAG_REMOVE:
res = "SCRIPT_COMMAND_FLAG_REMOVE";
break;
case SCRIPT_COMMAND_TELEPORT_TO:
res = "SCRIPT_COMMAND_TELEPORT_TO";
break;
case SCRIPT_COMMAND_QUEST_EXPLORED:
res = "SCRIPT_COMMAND_QUEST_EXPLORED";
break;
case SCRIPT_COMMAND_KILL_CREDIT:
res = "SCRIPT_COMMAND_KILL_CREDIT";
break;
case SCRIPT_COMMAND_RESPAWN_GAMEOBJECT:
res = "SCRIPT_COMMAND_RESPAWN_GAMEOBJECT";
break;
case SCRIPT_COMMAND_TEMP_SUMMON_CREATURE:
res = "SCRIPT_COMMAND_TEMP_SUMMON_CREATURE";
break;
case SCRIPT_COMMAND_OPEN_DOOR:
res = "SCRIPT_COMMAND_OPEN_DOOR";
break;
case SCRIPT_COMMAND_CLOSE_DOOR:
res = "SCRIPT_COMMAND_CLOSE_DOOR";
break;
case SCRIPT_COMMAND_ACTIVATE_OBJECT:
res = "SCRIPT_COMMAND_ACTIVATE_OBJECT";
break;
case SCRIPT_COMMAND_REMOVE_AURA:
res = "SCRIPT_COMMAND_REMOVE_AURA";
break;
case SCRIPT_COMMAND_CAST_SPELL:
res = "SCRIPT_COMMAND_CAST_SPELL";
break;
case SCRIPT_COMMAND_PLAY_SOUND:
res = "SCRIPT_COMMAND_PLAY_SOUND";
break;
case SCRIPT_COMMAND_CREATE_ITEM:
res = "SCRIPT_COMMAND_CREATE_ITEM";
break;
case SCRIPT_COMMAND_DESPAWN_SELF:
res = "SCRIPT_COMMAND_DESPAWN_SELF";
break;
case SCRIPT_COMMAND_LOAD_PATH:
res = "SCRIPT_COMMAND_LOAD_PATH";
break;
case SCRIPT_COMMAND_CALLSCRIPT_TO_UNIT:
res = "SCRIPT_COMMAND_CALLSCRIPT_TO_UNIT";
break;
case SCRIPT_COMMAND_KILL:
res = "SCRIPT_COMMAND_KILL";
break;
// AzerothCore only
case SCRIPT_COMMAND_ORIENTATION:
res = "SCRIPT_COMMAND_ORIENTATION";
break;
case SCRIPT_COMMAND_EQUIP:
res = "SCRIPT_COMMAND_EQUIP";
break;
case SCRIPT_COMMAND_MODEL:
res = "SCRIPT_COMMAND_MODEL";
break;
case SCRIPT_COMMAND_CLOSE_GOSSIP:
res = "SCRIPT_COMMAND_CLOSE_GOSSIP";
break;
case SCRIPT_COMMAND_PLAYMOVIE:
res = "SCRIPT_COMMAND_PLAYMOVIE";
break;
default:
{
char sz[32];
snprintf(sz, sizeof(sz), "Unknown command: %d", command);
res = sz;
break;
}
}
return res;
}
std::string ScriptInfo::GetDebugInfo() const
{
char sz[256];
snprintf(sz, sizeof(sz), "%s ('%s' script id: %u)", GetScriptCommandName(command).c_str(), GetScriptsTableNameByType(type).c_str(), id);
return std::string(sz);
}
bool normalizePlayerName(std::string& name)
{
if (name.empty())
return false;
if (name.find(" ") != std::string::npos)
return false;
std::wstring tmp;
if (!Utf8toWStr(name, tmp))
return false;
wstrToLower(tmp);
if (!tmp.empty())
tmp[0] = wcharToUpper(tmp[0]);
if (!WStrToUtf8(tmp, name))
return false;
return true;
}
LanguageDesc lang_description[LANGUAGES_COUNT] =
{
{ LANG_ADDON, 0, 0 },
{ LANG_UNIVERSAL, 0, 0 },
{ LANG_ORCISH, 669, SKILL_LANG_ORCISH },
{ LANG_DARNASSIAN, 671, SKILL_LANG_DARNASSIAN },
{ LANG_TAURAHE, 670, SKILL_LANG_TAURAHE },
{ LANG_DWARVISH, 672, SKILL_LANG_DWARVEN },
{ LANG_COMMON, 668, SKILL_LANG_COMMON },
{ LANG_DEMONIC, 815, SKILL_LANG_DEMON_TONGUE },
{ LANG_TITAN, 816, SKILL_LANG_TITAN },
{ LANG_THALASSIAN, 813, SKILL_LANG_THALASSIAN },
{ LANG_DRACONIC, 814, SKILL_LANG_DRACONIC },
{ LANG_KALIMAG, 817, SKILL_LANG_OLD_TONGUE },
{ LANG_GNOMISH, 7340, SKILL_LANG_GNOMISH },
{ LANG_TROLL, 7341, SKILL_LANG_TROLL },
{ LANG_GUTTERSPEAK, 17737, SKILL_LANG_GUTTERSPEAK },
{ LANG_DRAENEI, 29932, SKILL_LANG_DRAENEI },
{ LANG_ZOMBIE, 0, 0 },
{ LANG_GNOMISH_BINARY, 0, 0 },
{ LANG_GOBLIN_BINARY, 0, 0 }
};
LanguageDesc const* GetLanguageDescByID(uint32 lang)
{
for (uint8 i = 0; i < LANGUAGES_COUNT; ++i)
{
if (uint32(lang_description[i].lang_id) == lang)
return &lang_description[i];
}
return nullptr;
}
bool SpellClickInfo::IsFitToRequirements(Unit const* clicker, Unit const* clickee) const
{
Player const* playerClicker = clicker->ToPlayer();
if (!playerClicker)
return true;
Unit const* summoner = nullptr;
// Check summoners for party
if (clickee->IsSummon())
summoner = clickee->ToTempSummon()->GetSummonerUnit();
if (!summoner)
summoner = clickee;
// This only applies to players
switch (userType)
{
case SPELL_CLICK_USER_FRIEND:
if (!playerClicker->IsFriendlyTo(summoner))
return false;
break;
case SPELL_CLICK_USER_RAID:
if (!playerClicker->IsInRaidWith(summoner))
return false;
break;
case SPELL_CLICK_USER_PARTY:
if (!playerClicker->IsInPartyWith(summoner))
return false;
break;
default:
break;
}
return true;
}
ObjectMgr::ObjectMgr():
_auctionId(1),
_equipmentSetGuid(1),
_mailId(1),
_hiPetNumber(1),
_creatureSpawnId(1),
_gameObjectSpawnId(1),
DBCLocaleIndex(LOCALE_enUS)
{
for (uint8 i = 0; i < MAX_CLASSES; ++i)
{
_playerClassInfo[i] = nullptr;
for (uint8 j = 0; j < MAX_RACES; ++j)
_playerInfo[j][i] = nullptr;
}
}
ObjectMgr::~ObjectMgr()
{
for (QuestMap::iterator i = _questTemplates.begin(); i != _questTemplates.end(); ++i)
delete i->second;
for (PetLevelInfoContainer::iterator i = _petInfoStore.begin(); i != _petInfoStore.end(); ++i)
delete[] i->second;
// free only if loaded
for (int class_ = 0; class_ < MAX_CLASSES; ++class_)
{
if (_playerClassInfo[class_])
delete[] _playerClassInfo[class_]->levelInfo;
delete _playerClassInfo[class_];
}
for (int race = 0; race < MAX_RACES; ++race)
{
for (int class_ = 0; class_ < MAX_CLASSES; ++class_)
{
if (_playerInfo[race][class_])
delete[] _playerInfo[race][class_]->levelInfo;
delete _playerInfo[race][class_];
}
}
for (CacheVendorItemContainer::iterator itr = _cacheVendorItemStore.begin(); itr != _cacheVendorItemStore.end(); ++itr)
itr->second.Clear();
_cacheTrainerSpellStore.clear();
for (DungeonEncounterContainer::iterator itr = _dungeonEncounterStore.begin(); itr != _dungeonEncounterStore.end(); ++itr)
for (DungeonEncounterList::iterator encounterItr = itr->second.begin(); encounterItr != itr->second.end(); ++encounterItr)
delete *encounterItr;
for (DungeonProgressionRequirementsContainer::iterator itr = _accessRequirementStore.begin(); itr != _accessRequirementStore.end(); ++itr)
{
std::unordered_map difficulties = itr->second;
for (auto difficultiesItr = difficulties.begin(); difficultiesItr != difficulties.end(); ++difficultiesItr)
{
for (auto questItr = difficultiesItr->second->quests.begin(); questItr != difficultiesItr->second->quests.end(); ++questItr)
{
delete* questItr;
}
for (auto achievementItr = difficultiesItr->second->achievements.begin(); achievementItr != difficultiesItr->second->achievements.end(); ++achievementItr)
{
delete* achievementItr;
}
for (auto itemsItr = difficultiesItr->second->items.begin(); itemsItr != difficultiesItr->second->items.end(); ++itemsItr)
{
delete* itemsItr;
}
delete difficultiesItr->second;
}
}
}
ObjectMgr* ObjectMgr::instance()
{
static ObjectMgr instance;
return &instance;
}
void ObjectMgr::AddLocaleString(std::string&& s, LocaleConstant locale, std::vector& data)
{
if (!s.empty())
{
if (data.size() <= std::size_t(locale))
data.resize(locale + 1);
data[locale] = std::move(s);
}
}
void ObjectMgr::LoadCreatureLocales()
{
uint32 oldMSTime = getMSTime();
_creatureLocaleStore.clear(); // need for reload case
// 0 1 2 3
QueryResult result = WorldDatabase.Query("SELECT entry, locale, Name, Title FROM creature_template_locale");
if (!result)
return;
do
{
Field* fields = result->Fetch();
uint32 ID = fields[0].Get();
LocaleConstant locale = GetLocaleByName(fields[1].Get());
if (locale == LOCALE_enUS)
continue;
CreatureLocale& data = _creatureLocaleStore[ID];
AddLocaleString(fields[2].Get(), locale, data.Name);
AddLocaleString(fields[3].Get(), locale, data.Title);
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Creature Locale Strings in {} ms", (unsigned long)_creatureLocaleStore.size(), GetMSTimeDiffToNow(oldMSTime));
}
void ObjectMgr::LoadGossipMenuItemsLocales()
{
uint32 oldMSTime = getMSTime();
_gossipMenuItemsLocaleStore.clear(); // need for reload case
// 0 1 2 3 4
QueryResult result = WorldDatabase.Query("SELECT MenuID, OptionID, Locale, OptionText, BoxText FROM gossip_menu_option_locale");
if (!result)
return;
do
{
Field* fields = result->Fetch();
uint32 MenuID = fields[0].Get();
uint16 OptionID = fields[1].Get();
LocaleConstant locale = GetLocaleByName(fields[2].Get());
if (locale == LOCALE_enUS)
continue;
GossipMenuItemsLocale& data = _gossipMenuItemsLocaleStore[MAKE_PAIR32(MenuID, OptionID)];
AddLocaleString(fields[3].Get(), locale, data.OptionText);
AddLocaleString(fields[4].Get(), locale, data.BoxText);
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Gossip Menu Option Locale Strings in {} ms", (uint32)_gossipMenuItemsLocaleStore.size(), GetMSTimeDiffToNow(oldMSTime));
}
void ObjectMgr::LoadPetNamesLocales()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3
QueryResult result = WorldDatabase.Query("SELECT Locale, Word, Entry, Half FROM pet_name_generation_locale");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 pet name locales parts. DB table `pet_name_generation_locale` is empty!");
LOG_INFO("server.loading", " ");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
LocaleConstant locale = GetLocaleByName(fields[0].Get());
std::string word = fields[1].Get();
uint32 entry = fields[2].Get();
bool half = fields[3].Get();
std::pair pairkey = std::make_pair(entry, locale);
if (half)
{
_petHalfLocaleName1[pairkey].push_back(word);
}
else
{
_petHalfLocaleName0[pairkey].push_back(word);
}
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Pet Name Locales Parts in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ObjectMgr::LoadPointOfInterestLocales()
{
uint32 oldMSTime = getMSTime();
_pointOfInterestLocaleStore.clear(); // need for reload case
// 0 1 2
QueryResult result = WorldDatabase.Query("SELECT ID, locale, Name FROM points_of_interest_locale");
if (!result)
return;
do
{
Field* fields = result->Fetch();
uint32 ID = fields[0].Get();
LocaleConstant locale = GetLocaleByName(fields[1].Get());
if (locale == LOCALE_enUS)
continue;
PointOfInterestLocale& data = _pointOfInterestLocaleStore[ID];
AddLocaleString(fields[2].Get(), locale, data.Name);
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Points Of Interest Locale Strings in {} ms", (uint32)_pointOfInterestLocaleStore.size(), GetMSTimeDiffToNow(oldMSTime));
}
void ObjectMgr::LoadCreatureTemplates()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4 5 6 7 8
QueryResult result = WorldDatabase.Query("SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, "
// 9 10 11 12 13 14 15 16 17 18 19 20 21 22
"gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, "
// 23 24 25 26 27 28 29 30 31 32 33 34
"DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, trainer_type, trainer_spell, "
// 35 36 37 38 39 40 41
"trainer_class, trainer_race, type, type_flags, lootid, pickpocketloot, skinloot, "
// 42 43 44 45 46 47 48 49 50 51
"PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, "
// 52 53 54 55 56 57 58 59 60 61 62 63
"ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, "
// 64 65 66
"spell_school_immune_mask, flags_extra, ScriptName "
"FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId ORDER BY entry DESC;");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 creature template definitions. DB table `creature_template` is empty.");
return;
}
_creatureTemplateStore.rehash(result->GetRowCount());
_creatureTemplateStoreFast.clear();
uint32 count = 0;
do
{
Field* fields = result->Fetch();
LoadCreatureTemplate(fields);
++count;
} while (result->NextRow());
// We load the creature models after loading but before checking
LoadCreatureTemplateModels();
sScriptMgr->OnAfterDatabaseLoadCreatureTemplates(_creatureTemplateStoreFast);
LoadCreatureTemplateResistances();
LoadCreatureTemplateSpells();
// Checking needs to be done after loading because of the difficulty self referencing
for (CreatureTemplateContainer::iterator itr = _creatureTemplateStore.begin(); itr != _creatureTemplateStore.end(); ++itr)
{
CheckCreatureTemplate(&itr->second);
itr->second.InitializeQueryData();
}
LOG_INFO("server.loading", ">> Loaded {} Creature Definitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
/**
* @brief Loads a creature template from a database result
*
* @param fields Database result
* @param triggerHook If true, will trigger the OnAfterDatabaseLoadCreatureTemplates hook. Useful if you are not calling the hook yourself.
*/
void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook)
{
uint32 entry = fields[0].Get();
CreatureTemplate& creatureTemplate = _creatureTemplateStore[entry];
// enlarge the fast cache as necessary
if (_creatureTemplateStoreFast.size() < entry + 1)
{
_creatureTemplateStoreFast.resize(entry + 1, nullptr);
}
// load a pointer to this creatureTemplate into the fast cache
_creatureTemplateStoreFast[entry] = &creatureTemplate;
// build the creatureTemplate
creatureTemplate.Entry = entry;
for (uint8 i = 0; i < MAX_DIFFICULTY - 1; ++i)
{
creatureTemplate.DifficultyEntry[i] = fields[1 + i].Get();
}
for (uint8 i = 0; i < MAX_KILL_CREDIT; ++i)
{
creatureTemplate.KillCredit[i] = fields[4 + i].Get();
}
creatureTemplate.Name = fields[6].Get();
creatureTemplate.SubName = fields[7].Get();
creatureTemplate.IconName = fields[8].Get();
creatureTemplate.GossipMenuId = fields[9].Get();
creatureTemplate.minlevel = fields[10].Get();
creatureTemplate.maxlevel = fields[11].Get();
creatureTemplate.expansion = uint32(fields[12].Get());
creatureTemplate.faction = uint32(fields[13].Get());
creatureTemplate.npcflag = fields[14].Get();
creatureTemplate.speed_walk = fields[15].Get();
creatureTemplate.speed_run = fields[16].Get();
creatureTemplate.speed_swim = fields[17].Get();
creatureTemplate.speed_flight = fields[18].Get();
creatureTemplate.detection_range = fields[19].Get();
creatureTemplate.scale = fields[20].Get();
creatureTemplate.rank = uint32(fields[21].Get());
creatureTemplate.dmgschool = uint32(fields[22].Get());
creatureTemplate.DamageModifier = fields[23].Get();
creatureTemplate.BaseAttackTime = fields[24].Get();
creatureTemplate.RangeAttackTime = fields[25].Get();
creatureTemplate.BaseVariance = fields[26].Get();
creatureTemplate.RangeVariance = fields[27].Get();
creatureTemplate.unit_class = uint32(fields[28].Get());
creatureTemplate.unit_flags = fields[29].Get();
creatureTemplate.unit_flags2 = fields[30].Get();
creatureTemplate.dynamicflags = fields[31].Get();
creatureTemplate.family = uint32(fields[32].Get());
creatureTemplate.trainer_type = uint32(fields[33].Get());
creatureTemplate.trainer_spell = fields[34].Get();
creatureTemplate.trainer_class = uint32(fields[35].Get());
creatureTemplate.trainer_race = uint32(fields[36].Get());
creatureTemplate.type = uint32(fields[37].Get());
creatureTemplate.type_flags = fields[38].Get();
creatureTemplate.lootid = fields[39].Get();
creatureTemplate.pickpocketLootId = fields[40].Get();
creatureTemplate.SkinLootId = fields[41].Get();
for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
{
creatureTemplate.resistance[i] = 0;
}
for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i)
{
creatureTemplate.spells[i] = 0;
}
creatureTemplate.PetSpellDataId = fields[42].Get();
creatureTemplate.VehicleId = fields[43].Get();
creatureTemplate.mingold = fields[44].Get();
creatureTemplate.maxgold = fields[45].Get();
creatureTemplate.AIName = fields[46].Get(); // stopped here, fix it
creatureTemplate.MovementType = uint32(fields[47].Get());
if (!fields[48].IsNull())
{
creatureTemplate.Movement.Ground = static_cast(fields[48].Get());
}
creatureTemplate.Movement.Swim = fields[49].Get();
if (!fields[50].IsNull())
{
creatureTemplate.Movement.Flight = static_cast(fields[50].Get());
}
creatureTemplate.Movement.Rooted = fields[51].Get();
if (!fields[52].IsNull())
{
creatureTemplate.Movement.Chase = static_cast(fields[52].Get());
}
if (!fields[53].IsNull())
{
creatureTemplate.Movement.Random = static_cast(fields[53].Get());
}
if (!fields[54].IsNull())
{
creatureTemplate.Movement.InteractionPauseTimer = fields[54].Get();
}
creatureTemplate.HoverHeight = fields[55].Get();
creatureTemplate.ModHealth = fields[56].Get();
creatureTemplate.ModMana = fields[57].Get();
creatureTemplate.ModArmor = fields[58].Get();
creatureTemplate.ModExperience = fields[59].Get();
creatureTemplate.RacialLeader = fields[60].Get();
creatureTemplate.movementId = fields[61].Get();
creatureTemplate.RegenHealth = fields[62].Get();
creatureTemplate.MechanicImmuneMask = fields[63].Get();
creatureTemplate.SpellSchoolImmuneMask = fields[64].Get();
creatureTemplate.flags_extra = fields[65].Get();
creatureTemplate.ScriptID = GetScriptId(fields[66].Get());
// useful if the creature template load is being triggered from outside this class
if (triggerHook)
{
sScriptMgr->OnAfterDatabaseLoadCreatureTemplates(_creatureTemplateStoreFast);
}
}
void ObjectMgr::LoadCreatureTemplateModels()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3
QueryResult result = WorldDatabase.Query("SELECT CreatureID, CreatureDisplayID, DisplayScale, Probability FROM creature_template_model ORDER BY Idx ASC");
if (!result)
{
LOG_INFO("server.loading", ">> Loaded 0 creature template model definitions. DB table `creature_template_model` is empty.");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 creatureId = fields[0].Get();
uint32 creatureDisplayId = fields[1].Get();
float displayScale = fields[2].Get();
float probability = fields[3].Get();
CreatureTemplate const* cInfo = GetCreatureTemplate(creatureId);
if (!cInfo)
{
LOG_ERROR("sql.sql", "Creature template (Entry: {}) does not exist but has a record in `creature_template_model`", creatureId);
continue;
}
CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(creatureDisplayId);
if (!displayEntry)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) lists non-existing CreatureDisplayID id ({}), this can crash the client.", creatureId, creatureDisplayId);
continue;
}
CreatureModelInfo const* modelInfo = GetCreatureModelInfo(creatureDisplayId);
if (!modelInfo)
LOG_ERROR("sql.sql", "No model data exist for `CreatureDisplayID` = {} listed by creature (Entry: {}).", creatureDisplayId, creatureId);
if (displayScale <= 0.0f)
displayScale = 1.0f;
const_cast(cInfo)->Models.emplace_back(creatureDisplayId, displayScale, probability);
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} creature template models in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
}
void ObjectMgr::LoadCreatureTemplateResistances()
{
uint32 oldMSTime = getMSTime();
// 0 1 2
QueryResult result = WorldDatabase.Query("SELECT CreatureID, School, Resistance FROM creature_template_resistance");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 creature template resistance definitions. DB table `creature_template_resistance` is empty.");
LOG_INFO("server.loading", " ");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 creatureID = fields[0].Get();
uint8 school = fields[1].Get();
if (school == SPELL_SCHOOL_NORMAL || school >= MAX_SPELL_SCHOOL)
{
LOG_ERROR("sql.sql", "creature_template_resistance has resistance definitions for creature {} but this school {} doesn't exist", creatureID, school);
continue;
}
CreatureTemplateContainer::iterator itr = _creatureTemplateStore.find(creatureID);
if (itr == _creatureTemplateStore.end())
{
LOG_ERROR("sql.sql", "creature_template_resistance has resistance definitions for creature {} but this creature doesn't exist", creatureID);
continue;
}
CreatureTemplate& creatureTemplate = itr->second;
creatureTemplate.resistance[school] = fields[2].Get();
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Creature Template Resistances in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ObjectMgr::LoadCreatureTemplateSpells()
{
uint32 oldMSTime = getMSTime();
// 0 1 2
QueryResult result = WorldDatabase.Query("SELECT CreatureID, `Index`, Spell FROM creature_template_spell");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 creature template spell definitions. DB table `creature_template_spell` is empty.");
LOG_INFO("server.loading", " ");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 creatureID = fields[0].Get();
uint8 index = fields[1].Get();
if (index >= MAX_CREATURE_SPELLS)
{
LOG_ERROR("sql.sql", "creature_template_spell has spell definitions for creature {} with a incorrect index {}", creatureID, index);
continue;
}
CreatureTemplateContainer::iterator itr = _creatureTemplateStore.find(creatureID);
if (itr == _creatureTemplateStore.end())
{
LOG_ERROR("sql.sql", "creature_template_spell has spell definitions for creature {} but this creature doesn't exist", creatureID);
continue;
}
CreatureTemplate& creatureTemplate = itr->second;
creatureTemplate.spells[index] = fields[2].Get();
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Creature Template Spells in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ObjectMgr::LoadCreatureTemplateAddons()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4 5 6 7
QueryResult result = WorldDatabase.Query("SELECT entry, path_id, mount, bytes1, bytes2, emote, visibilityDistanceType, auras FROM creature_template_addon");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 creature template addon definitions. DB table `creature_template_addon` is empty.");
LOG_INFO("server.loading", " ");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 entry = fields[0].Get();
if (!GetCreatureTemplate(entry))
{
LOG_ERROR("sql.sql", "Creature template (Entry: {}) does not exist but has a record in `creature_template_addon`", entry);
continue;
}
CreatureAddon& creatureAddon = _creatureTemplateAddonStore[entry];
creatureAddon.path_id = fields[1].Get();
creatureAddon.mount = fields[2].Get();
creatureAddon.bytes1 = fields[3].Get();
creatureAddon.bytes2 = fields[4].Get();
creatureAddon.emote = fields[5].Get();
creatureAddon.visibilityDistanceType = VisibilityDistanceType(fields[6].Get());
for (std::string_view aura : Acore::Tokenize(fields[7].Get(), ' ', false))
{
SpellInfo const* spellInfo = nullptr;
if (Optional spellId = Acore::StringTo(aura))
{
spellInfo = sSpellMgr->GetSpellInfo(*spellId);
}
if (!spellInfo)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong spell '{}' defined in `auras` field in `creature_template_addon`.", entry, aura);
continue;
}
if (std::find(creatureAddon.auras.begin(), creatureAddon.auras.end(), spellInfo->Id) != creatureAddon.auras.end())
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has duplicate aura (spell {}) in `auras` field in `creature_template_addon`.", entry, spellInfo->Id);
continue;
}
if (spellInfo->GetDuration() > 0)
{
LOG_DEBUG/*ERROR*/("sql.sql", "Creature (Entry: {}) has temporary aura (spell {}) in `auras` field in `creature_template_addon`.", entry, spellInfo->Id);
// continue;
}
creatureAddon.auras.push_back(spellInfo->Id);
}
if (creatureAddon.mount)
{
if (!sCreatureDisplayInfoStore.LookupEntry(creatureAddon.mount))
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid displayInfoId ({}) for mount defined in `creature_template_addon`", entry, creatureAddon.mount);
creatureAddon.mount = 0;
}
}
if (!sEmotesStore.LookupEntry(creatureAddon.emote))
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid emote ({}) defined in `creature_addon`.", entry, creatureAddon.emote);
creatureAddon.emote = 0;
}
if (creatureAddon.visibilityDistanceType >= VisibilityDistanceType::Max)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid visibilityDistanceType ({}) defined in `creature_template_addon`.", entry, AsUnderlyingType(creatureAddon.visibilityDistanceType));
creatureAddon.visibilityDistanceType = VisibilityDistanceType::Normal;
}
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Creature Template Addons in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
/**
* @brief Load config option Creatures.CustomIDs into Store
*/
void ObjectMgr::LoadCreatureCustomIDs()
{
// Hack for modules
std::string stringCreatureIds = sConfigMgr->GetOption("Creatures.CustomIDs", "190010,55005,999991,25462,98888,601014,34567,34568");
std::vector CustomCreatures = Acore::Tokenize(stringCreatureIds, ',', false);
for (auto& itr : CustomCreatures)
{
_creatureCustomIDsStore.push_back(Acore::StringTo(itr).value());
}
}
void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo)
{
if (!cInfo)
return;
bool ok = true; // bool to allow continue outside this loop
for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && ok; ++diff)
{
if (!cInfo->DifficultyEntry[diff])
continue;
ok = false; // will be set to true at the end of this loop again
CreatureTemplate const* difficultyInfo = GetCreatureTemplate(cInfo->DifficultyEntry[diff]);
if (!difficultyInfo)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has `difficulty_entry_{}`={} but creature entry {} does not exist.",
cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff], cInfo->DifficultyEntry[diff]);
continue;
}
bool ok2 = true;
for (uint32 diff2 = 0; diff2 < MAX_DIFFICULTY - 1 && ok2; ++diff2)
{
ok2 = false;
if (_difficultyEntries[diff2].find(cInfo->Entry) != _difficultyEntries[diff2].end())
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) is listed as `difficulty_entry_{}` of another creature, but itself lists {} in `difficulty_entry_{}`.",
cInfo->Entry, diff2 + 1, cInfo->DifficultyEntry[diff], diff + 1);
continue;
}
if (_difficultyEntries[diff2].find(cInfo->DifficultyEntry[diff]) != _difficultyEntries[diff2].end())
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) already listed as `difficulty_entry_{}` for another entry.", cInfo->DifficultyEntry[diff], diff2 + 1);
continue;
}
if (_hasDifficultyEntries[diff2].find(cInfo->DifficultyEntry[diff]) != _hasDifficultyEntries[diff2].end())
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has `difficulty_entry_{}`={} but creature entry {} has itself a value in `difficulty_entry_{}`.",
cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff], cInfo->DifficultyEntry[diff], diff2 + 1);
continue;
}
ok2 = true;
}
if (!ok2)
continue;
if (cInfo->expansion > difficultyInfo->expansion)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}, expansion {}) has different `expansion` in difficulty {} mode (Entry: {}, expansion {}).",
cInfo->Entry, cInfo->expansion, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->expansion);
}
if (cInfo->faction != difficultyInfo->faction)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}, faction {}) has different `faction` in difficulty {} mode (Entry: {}, faction {}).",
cInfo->Entry, cInfo->faction, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->faction);
}
if (cInfo->unit_class != difficultyInfo->unit_class)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}, class {}) has different `unit_class` in difficulty {} mode (Entry: {}, class {}).",
cInfo->Entry, cInfo->unit_class, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->unit_class);
continue;
}
if (cInfo->npcflag != difficultyInfo->npcflag)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `npcflag` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
continue;
}
if (cInfo->family != difficultyInfo->family)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}, family {}) has different `family` in difficulty {} mode (Entry: {}, family {}).",
cInfo->Entry, cInfo->family, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->family);
}
if (cInfo->trainer_class != difficultyInfo->trainer_class)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `trainer_class` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
continue;
}
if (cInfo->trainer_race != difficultyInfo->trainer_race)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `trainer_race` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
continue;
}
if (cInfo->trainer_type != difficultyInfo->trainer_type)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `trainer_type` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
continue;
}
if (cInfo->trainer_spell != difficultyInfo->trainer_spell)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `trainer_spell` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
continue;
}
if (cInfo->type != difficultyInfo->type)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}, type {}) has different `type` in difficulty {} mode (Entry: {}, type {}).",
cInfo->Entry, cInfo->type, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->type);
}
if (!cInfo->VehicleId && difficultyInfo->VehicleId)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}, VehicleId {}) has different `VehicleId` in difficulty {} mode (Entry: {}, VehicleId {}).",
cInfo->Entry, cInfo->VehicleId, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->VehicleId);
}
// Xinef: check dmg school
if (cInfo->dmgschool != difficultyInfo->dmgschool)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `dmgschool` in difficulty {} mode (Entry: {})", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
}
if (!difficultyInfo->AIName.empty())
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) lists difficulty {} mode entry {} with `AIName` filled in. `AIName` of difficulty 0 mode creature is always used instead.",
cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
continue;
}
if (difficultyInfo->ScriptID)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) lists difficulty {} mode entry {} with `ScriptName` filled in. `ScriptName` of difficulty 0 mode creature is always used instead.",
cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
continue;
}
_hasDifficultyEntries[diff].insert(cInfo->Entry);
_difficultyEntries[diff].insert(cInfo->DifficultyEntry[diff]);
ok = true;
}
if (!cInfo->AIName.empty() && !sCreatureAIRegistry->HasItem(cInfo->AIName))
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has non-registered `AIName` '{}' set, removing", cInfo->Entry, cInfo->AIName);
const_cast(cInfo)->AIName.clear();
}
FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction);
if (!factionTemplate)
LOG_ERROR("sql.sql", "Creature (Entry: {}) has non-existing faction template ({}).", cInfo->Entry, cInfo->faction);
for (int k = 0; k < MAX_KILL_CREDIT; ++k)
{
if (cInfo->KillCredit[k])
{
if (!GetCreatureTemplate(cInfo->KillCredit[k]))
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) lists non-existing creature entry {} in `KillCredit{}`.", cInfo->Entry, cInfo->KillCredit[k], k + 1);
const_cast(cInfo)->KillCredit[k] = 0;
}
}
}
if (!cInfo->Models.size())
LOG_ERROR("sql.sql", "Creature (Entry: {}) does not have any existing display id in creature_template_model.", cInfo->Entry);
else if (std::accumulate(cInfo->Models.begin(), cInfo->Models.end(), 0.0f, [](float sum, CreatureModel const& model) { return sum + model.Probability; }) <= 0.0f)
LOG_ERROR("sql.sql", "Creature (Entry: {}) has zero total chance for all models in creature_template_model.", cInfo->Entry);
if (!cInfo->unit_class || ((1 << (cInfo->unit_class - 1)) & CLASSMASK_ALL_CREATURES) == 0)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid unit_class ({}) in creature_template. Set to 1 (UNIT_CLASS_WARRIOR).", cInfo->Entry, cInfo->unit_class);
const_cast(cInfo)->unit_class = UNIT_CLASS_WARRIOR;
}
if (cInfo->dmgschool >= MAX_SPELL_SCHOOL)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid spell school value ({}) in `dmgschool`.", cInfo->Entry, cInfo->dmgschool);
const_cast(cInfo)->dmgschool = SPELL_SCHOOL_NORMAL;
}
if (cInfo->BaseAttackTime == 0)
const_cast(cInfo)->BaseAttackTime = BASE_ATTACK_TIME;
if (cInfo->RangeAttackTime == 0)
const_cast(cInfo)->RangeAttackTime = BASE_ATTACK_TIME;
if ((cInfo->npcflag & UNIT_NPC_FLAG_TRAINER) && cInfo->trainer_type >= MAX_TRAINER_TYPE)
LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong trainer type {}.", cInfo->Entry, cInfo->trainer_type);
if (cInfo->speed_walk == 0.0f)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong value ({}) in speed_walk, set to 1.", cInfo->Entry, cInfo->speed_walk);
const_cast(cInfo)->speed_walk = 1.0f;
}
if (cInfo->speed_run == 0.0f)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong value ({}) in speed_run, set to 1.14286.", cInfo->Entry, cInfo->speed_run);
const_cast(cInfo)->speed_run = 1.14286f;
}
if (cInfo->type && !sCreatureTypeStore.LookupEntry(cInfo->type))
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid creature type ({}) in `type`.", cInfo->Entry, cInfo->type);
const_cast(cInfo)->type = CREATURE_TYPE_HUMANOID;
}
// must exist or used hidden but used in data horse case
if (cInfo->family && !sCreatureFamilyStore.LookupEntry(cInfo->family) && cInfo->family != CREATURE_FAMILY_HORSE_CUSTOM)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has invalid creature family ({}) in `family`.", cInfo->Entry, cInfo->family);
const_cast(cInfo)->family = 0;
}
CheckCreatureMovement("creature_template_movement", cInfo->Entry, const_cast(cInfo)->Movement);
if (cInfo->HoverHeight < 0.0f)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong value ({}) in `HoverHeight`", cInfo->Entry, cInfo->HoverHeight);
const_cast(cInfo)->HoverHeight = 1.0f;
}
if (cInfo->VehicleId)
{
VehicleEntry const* vehId = sVehicleStore.LookupEntry(cInfo->VehicleId);
if (!vehId)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has a non-existing VehicleId ({}). This *WILL* cause the client to freeze!", cInfo->Entry, cInfo->VehicleId);
const_cast(cInfo)->VehicleId = 0;
}
}
if (cInfo->PetSpellDataId)
{
CreatureSpellDataEntry const* spellDataId = sCreatureSpellDataStore.LookupEntry(cInfo->PetSpellDataId);
if (!spellDataId)
LOG_ERROR("sql.sql", "Creature (Entry: {}) has non-existing PetSpellDataId ({}).", cInfo->Entry, cInfo->PetSpellDataId);
}
for (uint8 j = 0; j < MAX_CREATURE_SPELLS; ++j)
{
if (cInfo->spells[j] && !sSpellMgr->GetSpellInfo(cInfo->spells[j]))
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has non-existing Spell{} ({}), set to 0.", cInfo->Entry, j + 1, cInfo->spells[j]);
const_cast(cInfo)->spells[j] = 0;
}
}
if (cInfo->MovementType >= MAX_DB_MOTION_TYPE)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong movement generator type ({}), ignored and set to IDLE.", cInfo->Entry, cInfo->MovementType);
const_cast(cInfo)->MovementType = IDLE_MOTION_TYPE;
}
if (cInfo->expansion > (MAX_EXPANSIONS - 1))
{
LOG_ERROR("sql.sql", "Table `creature_template` lists creature (Entry: {}) with expansion {}. Ignored and set to 0.", cInfo->Entry, cInfo->expansion);
const_cast(cInfo)->expansion = 0;
}
if (uint32 badFlags = (cInfo->flags_extra & ~CREATURE_FLAG_EXTRA_DB_ALLOWED))
{
LOG_ERROR("sql.sql", "Table `creature_template` lists creature (Entry: {}) with disallowed `flags_extra` {}, removing incorrect flag.", cInfo->Entry, badFlags);
const_cast(cInfo)->flags_extra &= CREATURE_FLAG_EXTRA_DB_ALLOWED;
}
const_cast(cInfo)->DamageModifier *= Creature::_GetDamageMod(cInfo->rank);
// Hack for modules
for (auto& itr : _creatureCustomIDsStore)
{
if (cInfo->Entry == itr)
return;
}
if ((cInfo->GossipMenuId && !(cInfo->npcflag & UNIT_NPC_FLAG_GOSSIP)) && !(cInfo->flags_extra & CREATURE_FLAG_EXTRA_MODULE))
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has assigned gossip menu {}, but npcflag does not include UNIT_NPC_FLAG_GOSSIP (1).", cInfo->Entry, cInfo->GossipMenuId);
}
else if ((!cInfo->GossipMenuId && (cInfo->npcflag & UNIT_NPC_FLAG_GOSSIP)) && !(cInfo->flags_extra & CREATURE_FLAG_EXTRA_MODULE))
{
LOG_INFO("sql.sql", "Creature (Entry: {}) has npcflag UNIT_NPC_FLAG_GOSSIP (1), but gossip menu is unassigned.", cInfo->Entry);
}
}
void ObjectMgr::CheckCreatureMovement(char const* table, uint64 id, CreatureMovementData& creatureMovement)
{
if (creatureMovement.Ground >= CreatureGroundMovementType::Max)
{
LOG_ERROR("sql.sql", "`{}`.`Ground` wrong value ({}) for Id {}, setting to Run.", table, uint32(creatureMovement.Ground), id);
creatureMovement.Ground = CreatureGroundMovementType::Run;
}
if (creatureMovement.Flight >= CreatureFlightMovementType::Max)
{
LOG_ERROR("sql.sql", "`{}`.`Flight` wrong value ({}) for Id {}, setting to None.", table, uint32(creatureMovement.Flight), id);
creatureMovement.Flight = CreatureFlightMovementType::None;
}
if (creatureMovement.Chase >= CreatureChaseMovementType::Max)
{
LOG_ERROR("sql.sql", "`{}`.`Chase` wrong value ({}) for Id {}, setting to Run.", table, uint32(creatureMovement.Chase), id);
creatureMovement.Chase = CreatureChaseMovementType::Run;
}
if (creatureMovement.Random >= CreatureRandomMovementType::Max)
{
LOG_ERROR("sql.sql", "`{}`.`Random` wrong value ({}) for Id {}, setting to Walk.", table, uint32(creatureMovement.Random), id);
creatureMovement.Random = CreatureRandomMovementType::Walk;
}
}
void ObjectMgr::LoadCreatureAddons()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4 5 6 7
QueryResult result = WorldDatabase.Query("SELECT guid, path_id, mount, bytes1, bytes2, emote, visibilityDistanceType, auras FROM creature_addon");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 creature addon definitions. DB table `creature_addon` is empty.");
LOG_INFO("server.loading", " ");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
ObjectGuid::LowType guid = fields[0].Get();
CreatureData const* creData = GetCreatureData(guid);
if (!creData)
{
LOG_ERROR("sql.sql", "Creature (GUID: {}) does not exist but has a record in `creature_addon`", guid);
continue;
}
CreatureAddon& creatureAddon = _creatureAddonStore[guid];
creatureAddon.path_id = fields[1].Get();
if (creData->movementType == WAYPOINT_MOTION_TYPE && !creatureAddon.path_id)
{
const_cast(creData)->movementType = IDLE_MOTION_TYPE;
LOG_ERROR("sql.sql", "Creature (GUID {}) has movement type set to WAYPOINT_MOTION_TYPE but no path assigned", guid);
}
creatureAddon.mount = fields[2].Get();
creatureAddon.bytes1 = fields[3].Get();
creatureAddon.bytes2 = fields[4].Get();
creatureAddon.emote = fields[5].Get();
creatureAddon.visibilityDistanceType = VisibilityDistanceType(fields[6].Get());
for (std::string_view aura : Acore::Tokenize(fields[7].Get(), ' ', false))
{
SpellInfo const* spellInfo = nullptr;
if (Optional spellId = Acore::StringTo(aura))
{
spellInfo = sSpellMgr->GetSpellInfo(*spellId);
}
if (!spellInfo)
{
LOG_ERROR("sql.sql", "Creature (GUID: {}) has wrong spell '{}' defined in `auras` field in `creature_addon`.", guid, aura);
continue;
}
if (std::find(creatureAddon.auras.begin(), creatureAddon.auras.end(), spellInfo->Id) != creatureAddon.auras.end())
{
LOG_ERROR("sql.sql", "Creature (GUID: {}) has duplicate aura (spell {}) in `auras` field in `creature_addon`.", guid, spellInfo->Id);
continue;
}
if (spellInfo->GetDuration() > 0)
{
LOG_DEBUG/*ERROR*/("sql.sql", "Creature (Entry: {}) has temporary aura (spell {}) in `auras` field in `creature_template_addon`.", guid, spellInfo->Id);
// continue;
}
creatureAddon.auras.push_back(spellInfo->Id);
}
if (creatureAddon.mount)
{
if (!sCreatureDisplayInfoStore.LookupEntry(creatureAddon.mount))
{
LOG_ERROR("sql.sql", "Creature (GUID: {}) has invalid displayInfoId ({}) for mount defined in `creature_addon`", guid, creatureAddon.mount);
creatureAddon.mount = 0;
}
}
if (!sEmotesStore.LookupEntry(creatureAddon.emote))
{
LOG_ERROR("sql.sql", "Creature (GUID: {}) has invalid emote ({}) defined in `creature_addon`.", guid, creatureAddon.emote);
creatureAddon.emote = 0;
}
if (creatureAddon.visibilityDistanceType >= VisibilityDistanceType::Max)
{
LOG_ERROR("sql.sql", "Creature (GUID: {}) has invalid visibilityDistanceType ({}) defined in `creature_addon`.", guid, AsUnderlyingType(creatureAddon.visibilityDistanceType));
creatureAddon.visibilityDistanceType = VisibilityDistanceType::Normal;
}
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Creature Addons in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ObjectMgr::LoadGameObjectAddons()
{
uint32 oldMSTime = getMSTime();
// 0 1 2
QueryResult result = WorldDatabase.Query("SELECT guid, invisibilityType, invisibilityValue FROM gameobject_addon");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 gameobject addon definitions. DB table `gameobject_addon` is empty.");
LOG_INFO("server.loading", " ");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
ObjectGuid::LowType guid = fields[0].Get();
const GameObjectData* goData = GetGameObjectData(guid);
if (!goData)
{
LOG_ERROR("sql.sql", "GameObject (GUID: {}) does not exist but has a record in `gameobject_addon`", guid);
continue;
}
GameObjectAddon& gameObjectAddon = _gameObjectAddonStore[guid];
gameObjectAddon.invisibilityType = InvisibilityType(fields[1].Get());
gameObjectAddon.InvisibilityValue = fields[2].Get();
if (gameObjectAddon.invisibilityType >= TOTAL_INVISIBILITY_TYPES)
{
LOG_ERROR("sql.sql", "GameObject (GUID: {}) has invalid InvisibilityType in `gameobject_addon`", guid);
gameObjectAddon.invisibilityType = INVISIBILITY_GENERAL;
gameObjectAddon.InvisibilityValue = 0;
}
if (gameObjectAddon.invisibilityType && !gameObjectAddon.InvisibilityValue)
{
LOG_ERROR("sql.sql", "GameObject (GUID: {}) has InvisibilityType set but has no InvisibilityValue in `gameobject_addon`, set to 1", guid);
gameObjectAddon.InvisibilityValue = 1;
}
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Gameobject Addons in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
GameObjectAddon const* ObjectMgr::GetGameObjectAddon(ObjectGuid::LowType lowguid)
{
GameObjectAddonContainer::const_iterator itr = _gameObjectAddonStore.find(lowguid);
if (itr != _gameObjectAddonStore.end())
return &(itr->second);
return nullptr;
}
CreatureAddon const* ObjectMgr::GetCreatureAddon(ObjectGuid::LowType lowguid)
{
CreatureAddonContainer::const_iterator itr = _creatureAddonStore.find(lowguid);
if (itr != _creatureAddonStore.end())
return &(itr->second);
return nullptr;
}
CreatureAddon const* ObjectMgr::GetCreatureTemplateAddon(uint32 entry)
{
CreatureAddonContainer::const_iterator itr = _creatureTemplateAddonStore.find(entry);
if (itr != _creatureTemplateAddonStore.end())
return &(itr->second);
return nullptr;
}
CreatureMovementData const* ObjectMgr::GetCreatureMovementOverride(ObjectGuid::LowType spawnId) const
{
return Acore::Containers::MapGetValuePtr(_creatureMovementOverrides, spawnId);
}
EquipmentInfo const* ObjectMgr::GetEquipmentInfo(uint32 entry, int8& id)
{
EquipmentInfoContainer::const_iterator itr = _equipmentInfoStore.find(entry);
if (itr == _equipmentInfoStore.end())
return nullptr;
if (itr->second.empty())
return nullptr;
if (id == -1) // select a random element
{
EquipmentInfoContainerInternal::const_iterator ritr = itr->second.begin();
std::advance(ritr, urand(0u, itr->second.size() - 1));
id = std::distance(itr->second.begin(), ritr) + 1;
return &ritr->second;
}
else
{
EquipmentInfoContainerInternal::const_iterator itr2 = itr->second.find(id);
if (itr2 != itr->second.end())
return &itr2->second;
}
return nullptr;
}
void ObjectMgr::LoadEquipmentTemplates()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4
QueryResult result = WorldDatabase.Query("SELECT CreatureID, ID, ItemID1, ItemID2, ItemID3 FROM creature_equip_template");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 creature equipment templates. DB table `creature_equip_template` is empty!");
LOG_INFO("server.loading", " ");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 entry = fields[0].Get();
if (!GetCreatureTemplate(entry))
{
LOG_ERROR("sql.sql", "Creature template (CreatureID: {}) does not exist but has a record in `creature_equip_template`", entry);
continue;
}
uint8 id = fields[1].Get();
if (!id)
{
LOG_ERROR("sql.sql", "Creature equipment template with id 0 found for creature {}, skipped.", entry);
continue;
}
EquipmentInfo& equipmentInfo = _equipmentInfoStore[entry][id];
equipmentInfo.ItemEntry[0] = fields[2].Get();
equipmentInfo.ItemEntry[1] = fields[3].Get();
equipmentInfo.ItemEntry[2] = fields[4].Get();
for (uint8 i = 0; i < MAX_EQUIPMENT_ITEMS; ++i)
{
if (!equipmentInfo.ItemEntry[i])
continue;
ItemEntry const* dbcItem = sItemStore.LookupEntry(equipmentInfo.ItemEntry[i]);
if (!dbcItem)
{
LOG_ERROR("sql.sql", "Unknown item (ID={}) in creature_equip_template.ItemID{} for CreatureID = {} and ID = {}, forced to 0.",
equipmentInfo.ItemEntry[i], i + 1, entry, id);
equipmentInfo.ItemEntry[i] = 0;
continue;
}
if (dbcItem->InventoryType != INVTYPE_WEAPON &&
dbcItem->InventoryType != INVTYPE_SHIELD &&
dbcItem->InventoryType != INVTYPE_RANGED &&
dbcItem->InventoryType != INVTYPE_2HWEAPON &&
dbcItem->InventoryType != INVTYPE_WEAPONMAINHAND &&
dbcItem->InventoryType != INVTYPE_WEAPONOFFHAND &&
dbcItem->InventoryType != INVTYPE_HOLDABLE &&
dbcItem->InventoryType != INVTYPE_THROWN &&
dbcItem->InventoryType != INVTYPE_RANGEDRIGHT)
{
LOG_ERROR("sql.sql", "Item (ID={}) in creature_equip_template.ItemID{} for CreatureID = {} and ID = {} is not equipable in a hand, forced to 0.",
equipmentInfo.ItemEntry[i], i + 1, entry, id);
equipmentInfo.ItemEntry[i] = 0;
}
}
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Equipment Templates in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ObjectMgr::LoadCreatureMovementOverrides()
{
uint32 oldMSTime = getMSTime();
_creatureMovementOverrides.clear();
// Load the data from creature_movement_override and if NULL fallback to creature_template_movement
QueryResult result = WorldDatabase.Query("SELECT cmo.SpawnId,"
"COALESCE(cmo.Ground, ctm.Ground),"
"COALESCE(cmo.Swim, ctm.Swim),"
"COALESCE(cmo.Flight, ctm.Flight),"
"COALESCE(cmo.Rooted, ctm.Rooted),"
"COALESCE(cmo.Chase, ctm.Chase),"
"COALESCE(cmo.Random, ctm.Random),"
"COALESCE(cmo.InteractionPauseTimer, ctm.InteractionPauseTimer) "
"FROM creature_movement_override AS cmo "
"LEFT JOIN creature AS c ON c.guid = cmo.SpawnId "
"LEFT JOIN creature_template_movement AS ctm ON ctm.CreatureId = c.id1");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 creature movement overrides. DB table `creature_movement_override` is empty!");
return;
}
do
{
Field* fields = result->Fetch();
ObjectGuid::LowType spawnId = fields[0].Get();
if (!GetCreatureData(spawnId))
{
LOG_ERROR("sql.sql", "Creature (GUID: {}) does not exist but has a record in `creature_movement_override`", spawnId);
continue;
}
CreatureMovementData& movement = _creatureMovementOverrides[spawnId];
if (!fields[1].IsNull())
{
movement.Ground = static_cast(fields[1].Get());
}
if (!fields[2].IsNull())
{
movement.Swim = fields[2].Get();
}
if (!fields[3].IsNull())
{
movement.Flight = static_cast(fields[3].Get());
}
if (!fields[4].IsNull())
{
movement.Rooted = fields[4].Get();
}
if (!fields[5].IsNull())
{
movement.Chase = static_cast(fields[5].Get());
}
if (!fields[6].IsNull())
{
movement.Random = static_cast(fields[6].Get());
}
if (!fields[7].IsNull())
{
movement.InteractionPauseTimer = fields[7].Get();
}
CheckCreatureMovement("creature_movement_override", spawnId, movement);
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Movement Overrides in {} ms", _creatureMovementOverrides.size(), GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
CreatureModelInfo const* ObjectMgr::GetCreatureModelInfo(uint32 modelId) const
{
CreatureModelContainer::const_iterator itr = _creatureModelStore.find(modelId);
if (itr != _creatureModelStore.end())
return &(itr->second);
return nullptr;
}
CreatureModel const* ObjectMgr::ChooseDisplayId(CreatureTemplate const* cinfo, CreatureData const* data /*= nullptr*/)
{
// Load creature model (display id)
if (data && data->displayid)
if (CreatureModel const* model = cinfo->GetModelWithDisplayId(data->displayid))
return model;
if (!cinfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_TRIGGER))
if (CreatureModel const* model = cinfo->GetRandomValidModel())
return model;
// Triggers by default receive the invisible model
return cinfo->GetFirstInvisibleModel();
}
void ObjectMgr::ChooseCreatureFlags(const CreatureTemplate* cinfo, uint32& npcflag, uint32& unit_flags, uint32& dynamicflags, const CreatureData* data /*= nullptr*/)
{
npcflag = cinfo->npcflag;
unit_flags = cinfo->unit_flags;
dynamicflags = cinfo->dynamicflags;
if (data)
{
if (data->npcflag)
npcflag = data->npcflag;
if (data->unit_flags)
unit_flags = data->unit_flags;
if (data->dynamicflags)
dynamicflags = data->dynamicflags;
}
}
CreatureModelInfo const* ObjectMgr::GetCreatureModelRandomGender(CreatureModel* model, CreatureTemplate const* creatureTemplate) const
{
CreatureModelInfo const* modelInfo = GetCreatureModelInfo(model->CreatureDisplayID);
if (!modelInfo)
return nullptr;
// If a model for another gender exists, 50% chance to use it
if (modelInfo->modelid_other_gender != 0 && urand(0, 1) == 0)
{
CreatureModelInfo const* minfo_tmp = GetCreatureModelInfo(modelInfo->modelid_other_gender);
if (!minfo_tmp)
LOG_ERROR("sql.sql", "Model (Entry: {}) has modelid_other_gender {} not found in table `creature_model_info`. ", model->CreatureDisplayID, modelInfo->modelid_other_gender);
else
{
// Model ID changed
model->CreatureDisplayID = modelInfo->modelid_other_gender;
if (creatureTemplate)
{
auto itr = std::find_if(creatureTemplate->Models.begin(), creatureTemplate->Models.end(), [&](CreatureModel const& templateModel)
{
return templateModel.CreatureDisplayID == modelInfo->modelid_other_gender;
});
if (itr != creatureTemplate->Models.end())
*model = *itr;
}
return minfo_tmp;
}
}
return modelInfo;
}
void ObjectMgr::LoadCreatureModelInfo()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4
QueryResult result = WorldDatabase.Query("SELECT DisplayID, BoundingRadius, CombatReach, Gender, DisplayID_Other_Gender FROM creature_model_info");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 creature model definitions. DB table `creature_model_info` is empty.");
LOG_INFO("server.loading", " ");
return;
}
_creatureModelStore.rehash(result->GetRowCount());
uint32 count = 0;
// List of ModelDataIDs that use Invisible models
uint32 triggerCreatureModelDataID[14] = { 1731, 1752, 2206, 2296, 2372, 2382, 2481, 2512, 2513, 2611, 2636, 2790, 3230, 3274 };
do
{
Field* fields = result->Fetch();
uint32 displayId = fields[0].Get();
CreatureDisplayInfoEntry const* creatureDisplay = sCreatureDisplayInfoStore.LookupEntry(displayId);
uint32 modelId = fields[0].Get();
CreatureModelInfo& modelInfo = _creatureModelStore[modelId];
modelInfo.bounding_radius = fields[1].Get();
modelInfo.combat_reach = fields[2].Get();
modelInfo.gender = fields[3].Get();
modelInfo.modelid_other_gender = fields[4].Get();
modelInfo.is_trigger = false;
// Checks
if (!sCreatureDisplayInfoStore.LookupEntry(modelId))
LOG_ERROR("sql.sql", "Table `creature_model_info` has model for not existed display id ({}).", modelId);
if (modelInfo.gender > GENDER_NONE)
{
LOG_ERROR("sql.sql", "Table `creature_model_info` has wrong gender ({}) for display id ({}).", uint32(modelInfo.gender), modelId);
modelInfo.gender = GENDER_MALE;
}
if (modelInfo.modelid_other_gender && !sCreatureDisplayInfoStore.LookupEntry(modelInfo.modelid_other_gender))
{
LOG_ERROR("sql.sql", "Table `creature_model_info` has not existed alt.gender model ({}) for existed display id ({}).", modelInfo.modelid_other_gender, modelId);
modelInfo.modelid_other_gender = 0;
}
if (modelInfo.combat_reach < 0.1f)
modelInfo.combat_reach = DEFAULT_COMBAT_REACH;
if (CreatureModelDataEntry const* modelData = sCreatureModelDataStore.LookupEntry(creatureDisplay->ModelId))
{
for (uint32 i = 0; i < 14; i++)
{
if (modelData->Id == triggerCreatureModelDataID[i])
{
modelInfo.is_trigger = true;
break;
}
}
}
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Creature Model Based Info in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ObjectMgr::LoadPlayerTotemModels()
{
uint32 oldMSTime = getMSTime();
QueryResult result = WorldDatabase.Query("SELECT TotemID, RaceID, ModelID from player_totem_model");
if (!result)
{
LOG_INFO("server.loading", ">> Loaded 0 player totem model records. DB table `player_totem_model` is empty.");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
SummonSlot totemSlot = SummonSlot(fields[0].Get());
uint8 race = fields[1].Get();
uint32 displayId = fields[2].Get();
if (totemSlot < SUMMON_SLOT_TOTEM_FIRE || totemSlot >= MAX_TOTEM_SLOT)
{
LOG_ERROR("sql.sql", "Wrong TotemSlot {} in `player_totem_model` table, skipped.", totemSlot);
continue;
}
ChrRacesEntry const* raceEntry = sChrRacesStore.LookupEntry(race);
if (!raceEntry)
{
LOG_ERROR("sql.sql", "Race {} defined in `player_totem_model` does not exists, skipped.", uint32(race));
continue;
}
CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(displayId);
if (!displayEntry)
{
LOG_ERROR("sql.sql", "TotemSlot: {} defined in `player_totem_model` has non-existing model ({}), skipped.", totemSlot, displayId);
continue;
}
_playerTotemModel[std::make_pair(totemSlot, Races(race))] = displayId;
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} player totem model records in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
uint32 ObjectMgr::GetModelForTotem(SummonSlot totemSlot, Races race) const
{
auto itr = _playerTotemModel.find(std::make_pair(totemSlot, race));
if (itr != _playerTotemModel.end())
return itr->second;
LOG_ERROR("misc", "TotemSlot {} with RaceID ({}) have no totem model data defined, set to default model.", totemSlot, race);
return 0;
}
void ObjectMgr::LoadPlayerShapeshiftModels()
{
uint32 oldMSTime = getMSTime();
QueryResult result = WorldDatabase.Query("SELECT ShapeshiftID, RaceID, CustomizationID, GenderID, ModelID from player_shapeshift_model");
if (!result)
{
LOG_INFO("server.loading", ">> Loaded 0 player shapeshift model records. DB table `player_shapeshift_model` is empty.");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
ShapeshiftForm shapeshiftForm = ShapeshiftForm(fields[0].Get());
uint8 race = fields[1].Get();
uint8 customizationID = fields[2].Get();
uint8 genderID = Gender(fields[3].Get());
uint32 modelId = fields[4].Get();
ChrRacesEntry const* raceEntry = sChrRacesStore.LookupEntry(race);
if (!raceEntry)
{
LOG_ERROR("sql.sql", "Race {} defined in `player_shapeshift_model` does not exists, skipped.", uint32(race));
continue;
}
CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(modelId);
if (!displayEntry)
{
LOG_ERROR("sql.sql", "ShapeshiftForm: {}, Race: {} defined in `player_shapeshift_model` has non-existing model ({}), skipped.", shapeshiftForm, race, modelId);
continue;
}
_playerShapeshiftModel[std::make_tuple(shapeshiftForm, race, customizationID, genderID)] = modelId;
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} player totem model records in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
uint32 ObjectMgr::GetModelForShapeshift(ShapeshiftForm form, Player* player) const
{
uint8 customizationID;
if (player->GetTeamId() == TEAM_ALLIANCE)
customizationID = player->GetByteValue(PLAYER_BYTES, 3); // Use Hair Color
else
customizationID = player->GetByteValue(PLAYER_BYTES, 0); // Use Skin Color
auto itr = _playerShapeshiftModel.find(std::make_tuple(form, player->getRace(), customizationID, player->getGender()));
if (itr != _playerShapeshiftModel.end())
return itr->second; // Explicit combination
itr = _playerShapeshiftModel.find(std::make_tuple(form, player->getRace(), customizationID, GENDER_NONE));
if (itr != _playerShapeshiftModel.end())
return itr->second; // Combination applied to both genders
itr = _playerShapeshiftModel.find(std::make_tuple(form, player->getRace(), 255, player->getGender()));
if (itr != _playerShapeshiftModel.end())
return itr->second; // Default gender-dependent model
itr = _playerShapeshiftModel.find(std::make_tuple(form, player->getRace(), 255, GENDER_NONE));
if (itr != _playerShapeshiftModel.end())
return itr->second; // Last resort
LOG_DEBUG("entities.player", "ShapeshiftForm {} with RaceID ({}) have no shapeshift model data defined, using fallback data.", form, player->getRace());
return 0;
}
void ObjectMgr::LoadLinkedRespawn()
{
uint32 oldMSTime = getMSTime();
_linkedRespawnStore.clear();
// 0 1 2
QueryResult result = WorldDatabase.Query("SELECT guid, linkedGuid, linkType FROM linked_respawn ORDER BY guid ASC");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 linked respawns. DB table `linked_respawn` is empty.");
LOG_INFO("server.loading", " ");
return;
}
do
{
Field* fields = result->Fetch();
ObjectGuid::LowType guidLow = fields[0].Get();
ObjectGuid::LowType linkedGuidLow = fields[1].Get();
uint8 linkType = fields[2].Get();
ObjectGuid guid, linkedGuid;
bool error = false;
switch (linkType)
{
case CREATURE_TO_CREATURE:
{
const CreatureData* slave = GetCreatureData(guidLow);
if (!slave)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Creature (guid) {} not found in creature table", guidLow);
error = true;
break;
}
const CreatureData* master = GetCreatureData(linkedGuidLow);
if (!master)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Creature (linkedGuid) {} not found in creature table", linkedGuidLow);
error = true;
break;
}
MapEntry const* const map = sMapStore.LookupEntry(master->mapid);
if (!map || !map->Instanceable() || (master->mapid != slave->mapid))
{
LOG_ERROR("sql.sql", "LinkedRespawn: Creature '{}' linking to Creature '{}' on an unpermitted map.", guidLow, linkedGuidLow);
error = true;
break;
}
if (!(master->spawnMask & slave->spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Creature '{}' linking to Creature '{}' with not corresponding spawnMask", guidLow, linkedGuidLow);
error = true;
break;
}
guid = ObjectGuid::Create(slave->id1, guidLow);
linkedGuid = ObjectGuid::Create(master->id1, linkedGuidLow);
break;
}
case CREATURE_TO_GO:
{
const CreatureData* slave = GetCreatureData(guidLow);
if (!slave)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Creature (guid) {} not found in creature table", guidLow);
error = true;
break;
}
const GameObjectData* master = GetGameObjectData(linkedGuidLow);
if (!master)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (linkedGuid) {} not found in gameobject table", linkedGuidLow);
error = true;
break;
}
MapEntry const* const map = sMapStore.LookupEntry(master->mapid);
if (!map || !map->Instanceable() || (master->mapid != slave->mapid))
{
LOG_ERROR("sql.sql", "LinkedRespawn: Creature '{}' linking to Gameobject '{}' on an unpermitted map.", guidLow, linkedGuidLow);
error = true;
break;
}
if (!(master->spawnMask & slave->spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Creature '{}' linking to Gameobject '{}' with not corresponding spawnMask", guidLow, linkedGuidLow);
error = true;
break;
}
guid = ObjectGuid::Create(slave->id1, guidLow);
linkedGuid = ObjectGuid::Create(master->id, linkedGuidLow);
break;
}
case GO_TO_GO:
{
const GameObjectData* slave = GetGameObjectData(guidLow);
if (!slave)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (guid) {} not found in gameobject table", guidLow);
error = true;
break;
}
const GameObjectData* master = GetGameObjectData(linkedGuidLow);
if (!master)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (linkedGuid) {} not found in gameobject table", linkedGuidLow);
error = true;
break;
}
MapEntry const* const map = sMapStore.LookupEntry(master->mapid);
if (!map || !map->Instanceable() || (master->mapid != slave->mapid))
{
LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '{}' linking to Gameobject '{}' on an unpermitted map.", guidLow, linkedGuidLow);
error = true;
break;
}
if (!(master->spawnMask & slave->spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '{}' linking to Gameobject '{}' with not corresponding spawnMask", guidLow, linkedGuidLow);
error = true;
break;
}
guid = ObjectGuid::Create(slave->id, guidLow);
linkedGuid = ObjectGuid::Create(master->id, linkedGuidLow);
break;
}
case GO_TO_CREATURE:
{
const GameObjectData* slave = GetGameObjectData(guidLow);
if (!slave)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (guid) {} not found in gameobject table", guidLow);
error = true;
break;
}
const CreatureData* master = GetCreatureData(linkedGuidLow);
if (!master)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Creature (linkedGuid) {} not found in creature table", linkedGuidLow);
error = true;
break;
}
MapEntry const* const map = sMapStore.LookupEntry(master->mapid);
if (!map || !map->Instanceable() || (master->mapid != slave->mapid))
{
LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '{}' linking to Creature '{}' on an unpermitted map.", guidLow, linkedGuidLow);
error = true;
break;
}
if (!(master->spawnMask & slave->spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '{}' linking to Creature '{}' with not corresponding spawnMask", guidLow, linkedGuidLow);
error = true;
break;
}
guid = ObjectGuid::Create(slave->id, guidLow);
linkedGuid = ObjectGuid::Create(master->id1, linkedGuidLow);
break;
}
}
if (!error)
_linkedRespawnStore[guid] = linkedGuid;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Linked Respawns In {} ms", uint64(_linkedRespawnStore.size()), GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
bool ObjectMgr::SetCreatureLinkedRespawn(ObjectGuid::LowType guidLow, ObjectGuid::LowType linkedGuidLow)
{
if (!guidLow)
return false;
CreatureData const* master = GetCreatureData(guidLow);
ObjectGuid guid = ObjectGuid::Create(master->id1, guidLow);
if (!linkedGuidLow) // we're removing the linking
{
_linkedRespawnStore.erase(guid);
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CRELINKED_RESPAWN);
stmt->SetData(0, guidLow);
WorldDatabase.Execute(stmt);
return true;
}
CreatureData const* slave = GetCreatureData(linkedGuidLow);
if (!slave)
{
LOG_ERROR("sql.sql", "Creature '{}' linking to non-existent creature '{}'.", guidLow, linkedGuidLow);
return false;
}
MapEntry const* map = sMapStore.LookupEntry(master->mapid);
if (!map || !map->Instanceable() || (master->mapid != slave->mapid))
{
LOG_ERROR("sql.sql", "Creature '{}' linking to '{}' on an unpermitted map.", guidLow, linkedGuidLow);
return false;
}
if (!(master->spawnMask & slave->spawnMask)) // they must have a possibility to meet (normal/heroic difficulty)
{
LOG_ERROR("sql.sql", "LinkedRespawn: Creature '{}' linking to '{}' with not corresponding spawnMask", guidLow, linkedGuidLow);
return false;
}
ObjectGuid linkedGuid = ObjectGuid::Create(slave->id1, linkedGuidLow);
_linkedRespawnStore[guid] = linkedGuid;
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_REP_CREATURE_LINKED_RESPAWN);
stmt->SetData(0, guidLow);
stmt->SetData(1, linkedGuidLow);
WorldDatabase.Execute(stmt);
return true;
}
void ObjectMgr::LoadTempSummons()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4 5 6 7 8 9
QueryResult result = WorldDatabase.Query("SELECT summonerId, summonerType, groupId, entry, position_x, position_y, position_z, orientation, summonType, summonTime FROM creature_summon_groups");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 temp summons. DB table `creature_summon_groups` is empty.");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 summonerId = fields[0].Get();
SummonerType summonerType = SummonerType(fields[1].Get());
uint8 group = fields[2].Get();
switch (summonerType)
{
case SUMMONER_TYPE_CREATURE:
if (!GetCreatureTemplate(summonerId))
{
LOG_ERROR("sql.sql", "Table `creature_summon_groups` has summoner with non existing entry {} for creature summoner type, skipped.", summonerId);
continue;
}
break;
case SUMMONER_TYPE_GAMEOBJECT:
if (!GetGameObjectTemplate(summonerId))
{
LOG_ERROR("sql.sql", "Table `creature_summon_groups` has summoner with non existing entry {} for gameobject summoner type, skipped.", summonerId);
continue;
}
break;
case SUMMONER_TYPE_MAP:
if (!sMapStore.LookupEntry(summonerId))
{
LOG_ERROR("sql.sql", "Table `creature_summon_groups` has summoner with non existing entry {} for map summoner type, skipped.", summonerId);
continue;
}
break;
default:
LOG_ERROR("sql.sql", "Table `creature_summon_groups` has unhandled summoner type {} for summoner {}, skipped.", summonerType, summonerId);
continue;
}
TempSummonData data;
data.entry = fields[3].Get();
if (!GetCreatureTemplate(data.entry))
{
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);
continue;
}
float posX = fields[4].Get();
float posY = fields[5].Get();
float posZ = fields[6].Get();
float orientation = fields[7].Get();
data.pos.Relocate(posX, posY, posZ, orientation);
data.type = TempSummonType(fields[8].Get());
if (data.type > TEMPSUMMON_MANUAL_DESPAWN)
{
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);
continue;
}
data.time = fields[9].Get();
TempSummonGroupKey key(summonerId, summonerType, group);
_tempSummonDataStore[key].push_back(data);
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Temporary Summons in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ObjectMgr::LoadCreatures()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4 5 6 7 8 9 10 11
QueryResult result = WorldDatabase.Query("SELECT creature.guid, id1, id2, id3, map, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, wander_distance, "
// 12 13 14 15 16 17 18 19 20 21 22
"currentwaypoint, curhealth, curmana, MovementType, spawnMask, phaseMask, eventEntry, pool_entry, creature.npcflag, creature.unit_flags, creature.dynamicflags, "
// 23
"creature.ScriptName "
"FROM creature "
"LEFT OUTER JOIN game_event_creature ON creature.guid = game_event_creature.guid "
"LEFT OUTER JOIN pool_creature ON creature.guid = pool_creature.guid");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 creatures. DB table `creature` is empty.");
LOG_INFO("server.loading", " ");
return;
}
if (sWorld->getBoolConfig(CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA))
LOG_INFO("server.loading", "Calculating zone and area fields. This may take a moment...");
// Build single time for check spawnmask
std::map spawnMasks;
for (uint32 i = 0; i < sMapStore.GetNumRows(); ++i)
if (sMapStore.LookupEntry(i))
for (int k = 0; k < MAX_DIFFICULTY; ++k)
if (GetMapDifficultyData(i, Difficulty(k)))
spawnMasks[i] |= (1 << k);
_creatureDataStore.rehash(result->GetRowCount());
uint32 count = 0;
do
{
Field* fields = result->Fetch();
ObjectGuid::LowType spawnId = fields[0].Get();
uint32 id1 = fields[1].Get();
uint32 id2 = fields[2].Get();
uint32 id3 = fields[3].Get();
CreatureTemplate const* cInfo = GetCreatureTemplate(id1);
if (!cInfo)
{
LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with non existing creature entry {} in id1 field, skipped.", spawnId, id1);
continue;
}
CreatureTemplate const* cInfo2 = GetCreatureTemplate(id2);
if (!cInfo2 && id2)
{
LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with non existing creature entry {} in id2 field, skipped.", spawnId, id2);
continue;
}
CreatureTemplate const* cInfo3 = GetCreatureTemplate(id3);
if (!cInfo3 && id3)
{
LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with non existing creature entry {} in id3 field, skipped.", spawnId, id3);
continue;
}
if (!id2 && id3)
{
LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with creature entry {} in id3 field but no entry in id2 field, skipped.", spawnId, id3);
continue;
}
CreatureData& data = _creatureDataStore[spawnId];
data.id1 = id1;
data.id2 = id2;
data.id3 = id3;
data.mapid = fields[4].Get();
data.equipmentId = fields[5].Get();
data.posX = fields[6].Get();
data.posY = fields[7].Get();
data.posZ = fields[8].Get();
data.orientation = fields[9].Get();
data.spawntimesecs = fields[10].Get();
data.wander_distance = fields[11].Get();
data.currentwaypoint = fields[12].Get();
data.curhealth = fields[13].Get();
data.curmana = fields[14].Get();
data.movementType = fields[15].Get();
data.spawnMask = fields[16].Get();
data.phaseMask = fields[17].Get();
int16 gameEvent = fields[18].Get();
uint32 PoolId = fields[19].Get();
data.npcflag = fields[20].Get();
data.unit_flags = fields[21].Get();
data.dynamicflags = fields[22].Get();
data.ScriptId = GetScriptId(fields[23].Get());
if (!data.ScriptId)
data.ScriptId = cInfo->ScriptID;
MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapid);
if (!mapEntry)
{
LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {}) that spawned at not existed map (Id: {}), skipped.", spawnId, data.mapid);
continue;
}
// pussywizard: 7 days means no reaspawn, so set it to 14 days, because manual id reset may be late
if (mapEntry->IsRaid() && data.spawntimesecs >= 7 * DAY && data.spawntimesecs < 14 * DAY)
data.spawntimesecs = 14 * DAY;
// Skip spawnMask check for transport maps
if (!_transportMaps.count(data.mapid) && data.spawnMask & ~spawnMasks[data.mapid])
LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {}) that have wrong spawn mask {} including not supported difficulty modes for map (Id: {}).",
spawnId, data.spawnMask, data.mapid);
bool ok = true;
for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && ok; ++diff)
{
if ((_difficultyEntries[diff].find(data.id1) != _difficultyEntries[diff].end()) || (_difficultyEntries[diff].find(data.id2) != _difficultyEntries[diff].end()) || (_difficultyEntries[diff].find(data.id3) != _difficultyEntries[diff].end()))
{
LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {}) that listed as difficulty {} template (Entries: {}, {}, {}) in `creature_template`, skipped.",
spawnId, diff + 1, data.id1, data.id2, data.id3);
ok = false;
}
}
if (!ok)
continue;
// -1 random, 0 no equipment,
if (data.equipmentId != 0)
{
if ((!GetEquipmentInfo(data.id1, data.equipmentId)) || (data.id2 && !GetEquipmentInfo(data.id2, data.equipmentId)) || (data.id3 && !GetEquipmentInfo(data.id3, data.equipmentId)))
{
LOG_ERROR("sql.sql", "Table `creature` have creature (Entries: {}, {}, {}) one or more with equipment_id {} not found in table `creature_equip_template`, set to no equipment.",
data.id1, data.id2, data.id3, data.equipmentId);
data.equipmentId = 0;
}
}
if (cInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_INSTANCE_BIND) || (data.id2 && cInfo2->HasFlagsExtra(CREATURE_FLAG_EXTRA_INSTANCE_BIND)) || (data.id3 && cInfo3->HasFlagsExtra(CREATURE_FLAG_EXTRA_INSTANCE_BIND)))
{
if (!mapEntry->IsDungeon())
LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entries: {}, {}, {}) with a `creature_template`.`flags_extra` in one or more entries including CREATURE_FLAG_EXTRA_INSTANCE_BIND but creature are not in instance.",
spawnId, data.id1, data.id2, data.id3);
}
if (data.movementType >= MAX_DB_MOTION_TYPE)
{
LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entries: {}, {}, {}) with wrong movement generator type ({}), ignored and set to IDLE.", spawnId, data.id1, data.id2, data.id3, data.movementType);
data.movementType = IDLE_MOTION_TYPE;
}
if (data.wander_distance < 0.0f)
{
LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entries: {}, {}, {}) with `wander_distance`< 0, set to 0.", spawnId, data.id1, data.id2, data.id3);
data.wander_distance = 0.0f;
}
else if (data.movementType == RANDOM_MOTION_TYPE)
{
if (data.wander_distance == 0.0f)
{
LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entries: {}, {}, {}) with `MovementType`=1 (random movement) but with `wander_distance`=0, replace by idle movement type (0).",
spawnId, data.id1, data.id2, data.id3);
data.movementType = IDLE_MOTION_TYPE;
}
}
else if (data.movementType == IDLE_MOTION_TYPE)
{
if (data.wander_distance != 0.0f)
{
LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entries: {}, {}, {}) with `MovementType`=0 (idle) have `wander_distance`<>0, set to 0.", spawnId, data.id1, data.id2, data.id3);
data.wander_distance = 0.0f;
}
}
if (data.phaseMask == 0)
{
LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entries: {}, {}, {}) with `phaseMask`=0 (not visible for anyone), set to 1.", spawnId, data.id1, data.id2, data.id3);
data.phaseMask = 1;
}
if (sWorld->getBoolConfig(CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA))
{
uint32 zoneId = sMapMgr->GetZoneId(data.phaseMask, data.mapid, data.posX, data.posY, data.posZ);
uint32 areaId = sMapMgr->GetAreaId(data.phaseMask, data.mapid, data.posX, data.posY, data.posZ);
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_CREATURE_ZONE_AREA_DATA);
stmt->SetData(0, zoneId);
stmt->SetData(1, areaId);
stmt->SetData(2, spawnId);
WorldDatabase.Execute(stmt);
}
// Add to grid if not managed by the game event or pool system
if (gameEvent == 0 && PoolId == 0)
AddCreatureToGrid(spawnId, &data);
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Creatures in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ObjectMgr::LoadCreatureSparring()
{
uint32 oldMSTime = getMSTime();
QueryResult result = WorldDatabase.Query("SELECT GUID, SparringPCT FROM creature_sparring");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 sparring data. DB table `creature_sparring` is empty.");
LOG_INFO("server.loading", " ");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
ObjectGuid::LowType spawnId = fields[0].Get();
float sparringHealthPct = fields[1].Get();
if (!GetCreatureData(spawnId))
{
LOG_ERROR("sql.sql", "Entry {} has a record in `creature_sparring` but doesn't exist in `creatures` table");
continue;
}
_creatureSparringStore[spawnId].push_back(sparringHealthPct);
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} sparring data in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ObjectMgr::AddCreatureToGrid(ObjectGuid::LowType guid, CreatureData const* data)
{
uint8 mask = data->spawnMask;
for (uint8 i = 0; mask != 0; i++, mask >>= 1)
{
if (mask & 1)
{
GridCoord gridCoord = Acore::ComputeGridCoord(data->posX, data->posY);
CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, i)][gridCoord.GetId()];
cell_guids.creatures.insert(guid);
}
}
}
void ObjectMgr::RemoveCreatureFromGrid(ObjectGuid::LowType guid, CreatureData const* data)
{
uint8 mask = data->spawnMask;
for (uint8 i = 0; mask != 0; i++, mask >>= 1)
{
if (mask & 1)
{
GridCoord gridCoord = Acore::ComputeGridCoord(data->posX, data->posY);
CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, i)][gridCoord.GetId()];
cell_guids.creatures.erase(guid);
}
}
}
ObjectGuid::LowType ObjectMgr::AddGOData(uint32 entry, uint32 mapId, float x, float y, float z, float o, uint32 spawntimedelay, float rotation0, float rotation1, float rotation2, float rotation3)
{
GameObjectTemplate const* goinfo = GetGameObjectTemplate(entry);
if (!goinfo)
return 0;
Map* map = sMapMgr->CreateBaseMap(mapId);
if (!map)
return 0;
ObjectGuid::LowType spawnId = GenerateGameObjectSpawnId();
GameObjectData& data = NewGOData(spawnId);
data.id = entry;
data.mapid = mapId;
data.posX = x;
data.posY = y;
data.posZ = z;
data.orientation = o;
data.rotation.x = rotation0;
data.rotation.y = rotation1;
data.rotation.z = rotation2;
data.rotation.w = rotation3;
data.spawntimesecs = spawntimedelay;
data.animprogress = 100;
data.spawnMask = 1;
data.go_state = GO_STATE_READY;
data.phaseMask = PHASEMASK_NORMAL;
data.artKit = goinfo->type == GAMEOBJECT_TYPE_CAPTURE_POINT ? 21 : 0;
data.dbData = false;
AddGameobjectToGrid(spawnId, &data);
// Spawn if necessary (loaded grids only)
// We use spawn coords to spawn
if (!map->Instanceable() && map->IsGridLoaded(x, y))
{
GameObject* go = IsGameObjectStaticTransport(data.id) ? new StaticTransport() : new GameObject();
if (!go->LoadGameObjectFromDB(spawnId, map))
{
LOG_ERROR("sql.sql", "AddGOData: cannot add gameobject entry {} to map", entry);
delete go;
return 0;
}
}
LOG_DEBUG("maps", "AddGOData: spawnId {} entry {} map {} x {} y {} z {} o {}", spawnId, entry, mapId, x, y, z, o);
return spawnId;
}
ObjectGuid::LowType ObjectMgr::AddCreData(uint32 entry, uint32 mapId, float x, float y, float z, float o, uint32 spawntimedelay)
{
CreatureTemplate const* cInfo = GetCreatureTemplate(entry);
if (!cInfo)
return 0;
uint32 level = cInfo->minlevel == cInfo->maxlevel ? cInfo->minlevel : urand(cInfo->minlevel, cInfo->maxlevel); // Only used for extracting creature base stats
CreatureBaseStats const* stats = GetCreatureBaseStats(level, cInfo->unit_class);
Map* map = sMapMgr->CreateBaseMap(mapId);
if (!map)
return 0;
ObjectGuid::LowType spawnId = GenerateCreatureSpawnId();
CreatureData& data = NewOrExistCreatureData(spawnId);
data.spawnMask = spawnId;
data.id1 = entry;
data.id2 = 0;
data.id3 = 0;
data.mapid = mapId;
data.displayid = 0;
data.equipmentId = 0;
data.posX = x;
data.posY = y;
data.posZ = z;
data.orientation = o;
data.spawntimesecs = spawntimedelay;
data.wander_distance = 0;
data.currentwaypoint = 0;
data.curhealth = stats->GenerateHealth(cInfo);
data.curmana = stats->GenerateMana(cInfo);
data.movementType = cInfo->MovementType;
data.spawnMask = 1;
data.phaseMask = PHASEMASK_NORMAL;
data.dbData = false;
data.npcflag = cInfo->npcflag;
data.unit_flags = cInfo->unit_flags;
data.dynamicflags = cInfo->dynamicflags;
AddCreatureToGrid(spawnId, &data);
// Spawn if necessary (loaded grids only)
if (!map->Instanceable() && map->IsGridLoaded(x, y))
{
Creature* creature = new Creature();
if (!creature->LoadCreatureFromDB(spawnId, map, true, true))
{
LOG_ERROR("sql.sql", "AddCreature: Cannot add creature entry {} to map", entry);
delete creature;
return 0;
}
}
return spawnId;
}
void ObjectMgr::LoadGameobjects()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4 5 6
QueryResult result = WorldDatabase.Query("SELECT gameobject.guid, id, map, position_x, position_y, position_z, orientation, "
// 7 8 9 10 11 12 13 14 15 16 17
"rotation0, rotation1, rotation2, rotation3, spawntimesecs, animprogress, state, spawnMask, phaseMask, eventEntry, pool_entry, "
// 18
"ScriptName "
"FROM gameobject LEFT OUTER JOIN game_event_gameobject ON gameobject.guid = game_event_gameobject.guid "
"LEFT OUTER JOIN pool_gameobject ON gameobject.guid = pool_gameobject.guid");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 gameobjects. DB table `gameobject` is empty.");
LOG_INFO("server.loading", " ");
return;
}
if (sWorld->getBoolConfig(CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA))
LOG_INFO("server.loading", "Calculating zone and area fields. This may take a moment...");
// build single time for check spawnmask
std::map spawnMasks;
for (uint32 i = 0; i < sMapStore.GetNumRows(); ++i)
if (sMapStore.LookupEntry(i))
for (int k = 0; k < MAX_DIFFICULTY; ++k)
if (GetMapDifficultyData(i, Difficulty(k)))
spawnMasks[i] |= (1 << k);
_gameObjectDataStore.rehash(result->GetRowCount());
do
{
Field* fields = result->Fetch();
ObjectGuid::LowType guid = fields[0].Get();
uint32 entry = fields[1].Get();
GameObjectTemplate const* gInfo = GetGameObjectTemplate(entry);
if (!gInfo)
{
LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {}) with non existing gameobject entry {}, skipped.", guid, entry);
continue;
}
if (!gInfo->displayId)
{
switch (gInfo->type)
{
case GAMEOBJECT_TYPE_TRAP:
case GAMEOBJECT_TYPE_SPELL_FOCUS:
break;
default:
LOG_ERROR("sql.sql", "Gameobject (GUID: {} Entry {} GoType: {}) doesn't have a displayId ({}), not loaded.", guid, entry, gInfo->type, gInfo->displayId);
break;
}
}
if (gInfo->displayId && !sGameObjectDisplayInfoStore.LookupEntry(gInfo->displayId))
{
LOG_ERROR("sql.sql", "Gameobject (GUID: {} Entry {} GoType: {}) has an invalid displayId ({}), not loaded.", guid, entry, gInfo->type, gInfo->displayId);
continue;
}
GameObjectData& data = _gameObjectDataStore[guid];
data.id = entry;
data.mapid = fields[2].Get();
data.posX = fields[3].Get();
data.posY = fields[4].Get();
data.posZ = fields[5].Get();
data.orientation = fields[6].Get();
data.rotation.x = fields[7].Get();
data.rotation.y = fields[8].Get();
data.rotation.z = fields[9].Get();
data.rotation.w = fields[10].Get();
data.spawntimesecs = fields[11].Get();
data.ScriptId = GetScriptId(fields[18].Get());
if (!data.ScriptId)
data.ScriptId = gInfo->ScriptId;
MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapid);
if (!mapEntry)
{
LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) spawned on a non-existed map (Id: {}), skip", guid, data.id, data.mapid);
continue;
}
if (data.spawntimesecs == 0 && gInfo->IsDespawnAtAction())
{
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);
}
data.animprogress = fields[12].Get();
data.artKit = 0;
uint32 go_state = fields[13].Get();
if (go_state >= MAX_GO_STATE)
{
LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid `state` ({}) value, skip", guid, data.id, go_state);
continue;
}
data.go_state = GOState(go_state);
data.spawnMask = fields[14].Get();
if (!_transportMaps.count(data.mapid) && data.spawnMask & ~spawnMasks[data.mapid])
LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) that has wrong spawn mask {} including not supported difficulty modes for map (Id: {}), skip", guid, data.id, data.spawnMask, data.mapid);
data.phaseMask = fields[15].Get();
int16 gameEvent = fields[16].Get();
uint32 PoolId = fields[17].Get();
if (data.rotation.x < -1.0f || data.rotation.x > 1.0f)
{
LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid rotationX ({}) value, skip", guid, data.id, data.rotation.x);
continue;
}
if (data.rotation.y < -1.0f || data.rotation.y > 1.0f)
{
LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid rotationY ({}) value, skip", guid, data.id, data.rotation.y);
continue;
}
if (data.rotation.z < -1.0f || data.rotation.z > 1.0f)
{
LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid rotationZ ({}) value, skip", guid, data.id, data.rotation.z);
continue;
}
if (data.rotation.w < -1.0f || data.rotation.w > 1.0f)
{
LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid rotationW ({}) value, skip", guid, data.id, data.rotation.w);
continue;
}
if (!MapMgr::IsValidMapCoord(data.mapid, data.posX, data.posY, data.posZ, data.orientation))
{
LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with invalid coordinates, skip", guid, data.id);
continue;
}
if (data.phaseMask == 0)
{
LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) with `phaseMask`=0 (not visible for anyone), set to 1.", guid, data.id);
data.phaseMask = 1;
}
if (sWorld->getBoolConfig(CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA))
{
uint32 zoneId = sMapMgr->GetZoneId(data.phaseMask, data.mapid, data.posX, data.posY, data.posZ);
uint32 areaId = sMapMgr->GetAreaId(data.phaseMask, data.mapid, data.posX, data.posY, data.posZ);
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_GAMEOBJECT_ZONE_AREA_DATA);
stmt->SetData(0, zoneId);
stmt->SetData(1, areaId);
stmt->SetData(2, guid);
WorldDatabase.Execute(stmt);
}
if (gameEvent == 0 && PoolId == 0) // if not this is to be managed by GameEvent System or Pool system
AddGameobjectToGrid(guid, &data);
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Gameobjects in {} ms", (unsigned long)_gameObjectDataStore.size(), GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ObjectMgr::AddGameobjectToGrid(ObjectGuid::LowType guid, GameObjectData const* data)
{
uint8 mask = data->spawnMask;
for (uint8 i = 0; mask != 0; i++, mask >>= 1)
{
if (mask & 1)
{
GridCoord gridCoord = Acore::ComputeGridCoord(data->posX, data->posY);
CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, i)][gridCoord.GetId()];
cell_guids.gameobjects.insert(guid);
}
}
}
void ObjectMgr::RemoveGameobjectFromGrid(ObjectGuid::LowType guid, GameObjectData const* data)
{
uint8 mask = data->spawnMask;
for (uint8 i = 0; mask != 0; i++, mask >>= 1)
{
if (mask & 1)
{
GridCoord gridCoord = Acore::ComputeGridCoord(data->posX, data->posY);
CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, i)][gridCoord.GetId()];
cell_guids.gameobjects.erase(guid);
}
}
}
void ObjectMgr::LoadItemLocales()
{
uint32 oldMSTime = getMSTime();
_itemLocaleStore.clear(); // need for reload case
QueryResult result = WorldDatabase.Query("SELECT ID, locale, Name, Description FROM item_template_locale");
if (!result)
return;
do
{
Field* fields = result->Fetch();
uint32 ID = fields[0].Get();
LocaleConstant locale = GetLocaleByName(fields[1].Get());
if (locale == LOCALE_enUS)
continue;
ItemLocale& data = _itemLocaleStore[ID];
AddLocaleString(fields[2].Get(), locale, data.Name);
AddLocaleString(fields[3].Get(), locale, data.Description);
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Item Locale Strings in {} ms", (uint32)_itemLocaleStore.size(), GetMSTimeDiffToNow(oldMSTime));
}
ServerConfigs const qualityToBuyValueConfig[MAX_ITEM_QUALITY] =
{
RATE_BUYVALUE_ITEM_POOR, // ITEM_QUALITY_POOR
RATE_BUYVALUE_ITEM_NORMAL, // ITEM_QUALITY_NORMAL
RATE_BUYVALUE_ITEM_UNCOMMON, // ITEM_QUALITY_UNCOMMON
RATE_BUYVALUE_ITEM_RARE, // ITEM_QUALITY_RARE
RATE_BUYVALUE_ITEM_EPIC, // ITEM_QUALITY_EPIC
RATE_BUYVALUE_ITEM_LEGENDARY, // ITEM_QUALITY_LEGENDARY
RATE_BUYVALUE_ITEM_ARTIFACT, // ITEM_QUALITY_ARTIFACT
RATE_BUYVALUE_ITEM_HEIRLOOM, // ITEM_QUALITY_HEIRLOOM
};
ServerConfigs const qualityToSellValueConfig[MAX_ITEM_QUALITY] =
{
RATE_SELLVALUE_ITEM_POOR, // ITEM_QUALITY_POOR
RATE_SELLVALUE_ITEM_NORMAL, // ITEM_QUALITY_NORMAL
RATE_SELLVALUE_ITEM_UNCOMMON, // ITEM_QUALITY_UNCOMMON
RATE_SELLVALUE_ITEM_RARE, // ITEM_QUALITY_RARE
RATE_SELLVALUE_ITEM_EPIC, // ITEM_QUALITY_EPIC
RATE_SELLVALUE_ITEM_LEGENDARY, // ITEM_QUALITY_LEGENDARY
RATE_SELLVALUE_ITEM_ARTIFACT, // ITEM_QUALITY_ARTIFACT
RATE_SELLVALUE_ITEM_HEIRLOOM, // ITEM_QUALITY_HEIRLOOM
};
void ObjectMgr::LoadItemTemplates()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4 5 6 7 8 9 10 11 12
QueryResult result = WorldDatabase.Query("SELECT entry, class, subclass, SoundOverrideSubclass, name, displayid, Quality, Flags, FlagsExtra, BuyCount, BuyPrice, SellPrice, InventoryType, "
// 13 14 15 16 17 18 19 20
"AllowableClass, AllowableRace, ItemLevel, RequiredLevel, RequiredSkill, RequiredSkillRank, requiredspell, requiredhonorrank, "
// 21 22 23 24 25 26 27
"RequiredCityRank, RequiredReputationFaction, RequiredReputationRank, maxcount, stackable, ContainerSlots, stat_type1, "
// 28 29 30 31 32 33 34 35 36 37
"stat_value1, stat_type2, stat_value2, stat_type3, stat_value3, stat_type4, stat_value4, stat_type5, stat_value5, stat_type6, "
// 38 39 40 41 42 43 44 45 46
"stat_value6, stat_type7, stat_value7, stat_type8, stat_value8, stat_type9, stat_value9, stat_type10, stat_value10, "
// 47 48 49 50 51 52 53 54 55 56 57
"ScalingStatDistribution, ScalingStatValue, dmg_min1, dmg_max1, dmg_type1, dmg_min2, dmg_max2, dmg_type2, armor, holy_res, fire_res, "
// 58 59 60 61 62 63 64 65 66 67
"nature_res, frost_res, shadow_res, arcane_res, delay, ammo_type, RangedModRange, spellid_1, spelltrigger_1, spellcharges_1, "
// 68 69 70 71 72 73 74
"spellppmRate_1, spellcooldown_1, spellcategory_1, spellcategorycooldown_1, spellid_2, spelltrigger_2, spellcharges_2, "
// 75 76 77 78 79 80 81
"spellppmRate_2, spellcooldown_2, spellcategory_2, spellcategorycooldown_2, spellid_3, spelltrigger_3, spellcharges_3, "
// 82 83 84 85 86 87 88
"spellppmRate_3, spellcooldown_3, spellcategory_3, spellcategorycooldown_3, spellid_4, spelltrigger_4, spellcharges_4, "
// 89 90 91 92 93 94 95
"spellppmRate_4, spellcooldown_4, spellcategory_4, spellcategorycooldown_4, spellid_5, spelltrigger_5, spellcharges_5, "
// 96 97 98 99 100 101 102 103 104
"spellppmRate_5, spellcooldown_5, spellcategory_5, spellcategorycooldown_5, bonding, description, PageText, LanguageID, PageMaterial, "
// 105 106 107 108 109 110 111 112 113 114 115 116
"startquest, lockid, Material, sheath, RandomProperty, RandomSuffix, block, itemset, MaxDurability, area, Map, BagFamily, "
// 117 118 119 120 121 122 123 124
"TotemCategory, socketColor_1, socketContent_1, socketColor_2, socketContent_2, socketColor_3, socketContent_3, socketBonus, "
// 125 126 127 128 129 130 131 132
"GemProperties, RequiredDisenchantSkill, ArmorDamageModifier, duration, ItemLimitCategory, HolidayId, ScriptName, DisenchantID, "
// 133 134 135 136
"FoodType, minMoneyLoot, maxMoneyLoot, flagsCustom FROM item_template");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 item templates. DB table `item_template` is empty.");
LOG_INFO("server.loading", " ");
return;
}
_itemTemplateStore.reserve(result->GetRowCount());
uint32 count = 0;
// original inspiration https://github.com/TrinityCore/TrinityCore/commit/0c44bd33ee7b42c924859139a9f4b04cf2b91261
bool enforceDBCAttributes = sWorld->getBoolConfig(CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES);
do
{
Field* fields = result->Fetch();
uint32 entry = fields[0].Get();
ItemTemplate& itemTemplate = _itemTemplateStore[entry];
itemTemplate.ItemId = entry;
itemTemplate.Class = uint32(fields[1].Get());
itemTemplate.SubClass = uint32(fields[2].Get());
itemTemplate.SoundOverrideSubclass = int32(fields[3].Get());
itemTemplate.Name1 = fields[4].Get