aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Globals/ObjectMgr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/game/Globals/ObjectMgr.cpp')
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp8724
1 files changed, 8724 insertions, 0 deletions
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
new file mode 100644
index 00000000000..ce86c2a4e77
--- /dev/null
+++ b/src/server/game/Globals/ObjectMgr.cpp
@@ -0,0 +1,8724 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "Common.h"
+#include "Database/DatabaseEnv.h"
+#include "Database/SQLStorage.h"
+#include "Database/SQLStorageImpl.h"
+#include "Policies/SingletonImp.h"
+
+#include "Log.h"
+#include "MapManager.h"
+#include "ObjectMgr.h"
+#include "SpellMgr.h"
+#include "UpdateMask.h"
+#include "World.h"
+#include "Group.h"
+#include "Guild.h"
+#include "ArenaTeam.h"
+#include "Transports.h"
+#include "ProgressBar.h"
+#include "Language.h"
+#include "GameEventMgr.h"
+#include "Spell.h"
+#include "Chat.h"
+#include "AccountMgr.h"
+#include "InstanceSaveMgr.h"
+#include "SpellAuras.h"
+#include "Util.h"
+#include "WaypointManager.h"
+#include "GossipDef.h"
+#include "Vehicle.h"
+#include "AchievementMgr.h"
+
+INSTANTIATE_SINGLETON_1(ObjectMgr);
+
+ScriptMapMap sQuestEndScripts;
+ScriptMapMap sQuestStartScripts;
+ScriptMapMap sSpellScripts;
+ScriptMapMap sGameObjectScripts;
+ScriptMapMap sEventScripts;
+ScriptMapMap sGossipScripts;
+ScriptMapMap sWaypointScripts;
+
+bool normalizePlayerName(std::string& name)
+{
+ if (name.empty())
+ return false;
+
+ wchar_t wstr_buf[MAX_INTERNAL_PLAYER_NAME+1];
+ size_t wstr_len = MAX_INTERNAL_PLAYER_NAME;
+
+ if (!Utf8toWStr(name,&wstr_buf[0],wstr_len))
+ return false;
+
+ wstr_buf[0] = wcharToUpper(wstr_buf[0]);
+ for (size_t i = 1; i < wstr_len; ++i)
+ wstr_buf[i] = wcharToLower(wstr_buf[i]);
+
+ if (!WStrToUtf8(wstr_buf,wstr_len,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 NULL;
+}
+
+bool SpellClickInfo::IsFitToRequirements(Player const* player, Creature const * clickNpc) const
+{
+ if (questStart)
+ {
+ // not in expected required quest state
+ if (!player || ((!questStartCanActive || !player->IsActiveQuest(questStart)) && !player->GetQuestRewardStatus(questStart)))
+ return false;
+ }
+
+ if (questEnd)
+ {
+ // not in expected forbidden quest state
+ if (!player || player->GetQuestRewardStatus(questEnd))
+ return false;
+ }
+
+ if (auraRequired)
+ if (!player->HasAura(auraRequired))
+ return false;
+
+ if (auraForbidden)
+ if (player->HasAura(auraForbidden))
+ return false;
+
+ Unit const * summoner = NULL;
+ // Check summoners for party
+ if (clickNpc->isSummon())
+ summoner = clickNpc->ToTempSummon()->GetSummoner();
+ if (!summoner)
+ summoner = clickNpc;
+
+ switch (userType)
+ {
+ case SPELL_CLICK_USER_FRIEND:
+ if (!player->IsFriendlyTo(summoner))
+ return false;
+ break;
+ case SPELL_CLICK_USER_RAID:
+ if (!player->IsInRaidWith(summoner))
+ return false;
+ break;
+ case SPELL_CLICK_USER_PARTY:
+ if (!player->IsInPartyWith(summoner))
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+ObjectMgr::ObjectMgr()
+{
+ m_hiCharGuid = 1;
+ m_hiCreatureGuid = 1;
+ m_hiPetGuid = 1;
+ m_hiVehicleGuid = 1;
+ m_hiItemGuid = 1;
+ m_hiGoGuid = 1;
+ m_hiDoGuid = 1;
+ m_hiCorpseGuid = 1;
+ m_hiPetNumber = 1;
+ m_hiGroupGuid = 1;
+ m_ItemTextId = 1;
+ m_mailid = 1;
+ m_equipmentSetGuid = 1;
+ m_guildId = 1;
+ m_arenaTeamId = 1;
+ m_auctionid = 1;
+}
+
+ObjectMgr::~ObjectMgr()
+{
+ for (QuestMap::iterator i = mQuestTemplates.begin(); i != mQuestTemplates.end(); ++i)
+ delete i->second;
+
+ for (PetLevelInfoMap::iterator i = petInfo.begin(); i != petInfo.end(); ++i)
+ delete[] i->second;
+
+ // free only if loaded
+ for (int class_ = 0; class_ < MAX_CLASSES; ++class_)
+ delete[] playerClassInfo[class_].levelInfo;
+
+ for (int race = 0; race < MAX_RACES; ++race)
+ for (int class_ = 0; class_ < MAX_CLASSES; ++class_)
+ delete[] playerInfo[race][class_].levelInfo;
+
+ // free group and guild objects
+ for (GroupSet::iterator itr = mGroupSet.begin(); itr != mGroupSet.end(); ++itr)
+ delete (*itr);
+
+ for (GuildMap::iterator itr = mGuildMap.begin(); itr != mGuildMap.end(); ++itr)
+ delete itr->second;
+
+ for (ArenaTeamMap::iterator itr = mArenaTeamMap.begin(); itr != mArenaTeamMap.end(); ++itr)
+ delete itr->second;
+
+ for (CacheVendorItemMap::iterator itr = m_mCacheVendorItemMap.begin(); itr != m_mCacheVendorItemMap.end(); ++itr)
+ itr->second.Clear();
+
+ for (CacheTrainerSpellMap::iterator itr = m_mCacheTrainerSpellMap.begin(); itr != m_mCacheTrainerSpellMap.end(); ++itr)
+ itr->second.Clear();
+}
+
+Group * ObjectMgr::GetGroupByGUID(const uint64 &guid) const
+{
+ for (GroupSet::const_iterator itr = mGroupSet.begin(); itr != mGroupSet.end(); ++itr)
+ if ((*itr)->GetGUID() == guid)
+ return *itr;
+
+ return NULL;
+}
+
+Guild * ObjectMgr::GetGuildById(uint32 GuildId) const
+{
+ GuildMap::const_iterator itr = mGuildMap.find(GuildId);
+ if (itr != mGuildMap.end())
+ return itr->second;
+
+ return NULL;
+}
+
+Guild * ObjectMgr::GetGuildByName(const std::string& guildname) const
+{
+ std::string search = guildname;
+ std::transform(search.begin(), search.end(), search.begin(), ::toupper);
+ for (GuildMap::const_iterator itr = mGuildMap.begin(); itr != mGuildMap.end(); ++itr)
+ {
+ std::string gname = itr->second->GetName();
+ std::transform(gname.begin(), gname.end(), gname.begin(), ::toupper);
+ if (search == gname)
+ return itr->second;
+ }
+ return NULL;
+}
+
+std::string ObjectMgr::GetGuildNameById(uint32 GuildId) const
+{
+ GuildMap::const_iterator itr = mGuildMap.find(GuildId);
+ if (itr != mGuildMap.end())
+ return itr->second->GetName();
+
+ return "";
+}
+
+Guild* ObjectMgr::GetGuildByLeader(const uint64 &guid) const
+{
+ for (GuildMap::const_iterator itr = mGuildMap.begin(); itr != mGuildMap.end(); ++itr)
+ if (itr->second->GetLeader() == guid)
+ return itr->second;
+
+ return NULL;
+}
+
+void ObjectMgr::AddGuild(Guild* guild)
+{
+ mGuildMap[guild->GetId()] = guild;
+}
+
+void ObjectMgr::RemoveGuild(uint32 Id)
+{
+ mGuildMap.erase(Id);
+}
+
+ArenaTeam* ObjectMgr::GetArenaTeamById(uint32 arenateamid) const
+{
+ ArenaTeamMap::const_iterator itr = mArenaTeamMap.find(arenateamid);
+ if (itr != mArenaTeamMap.end())
+ return itr->second;
+
+ return NULL;
+}
+
+ArenaTeam* ObjectMgr::GetArenaTeamByName(const std::string& arenateamname) const
+{
+ std::string search = arenateamname;
+ std::transform(search.begin(), search.end(), search.begin(), ::toupper);
+ for (ArenaTeamMap::const_iterator itr = mArenaTeamMap.begin(); itr != mArenaTeamMap.end(); ++itr)
+ {
+ std::string teamname = itr->second->GetName();
+ std::transform(teamname.begin(), teamname.end(), teamname.begin(), ::toupper);
+ if (search == teamname)
+ return itr->second;
+ }
+ return NULL;
+}
+
+ArenaTeam* ObjectMgr::GetArenaTeamByCaptain(uint64 const& guid) const
+{
+ for (ArenaTeamMap::const_iterator itr = mArenaTeamMap.begin(); itr != mArenaTeamMap.end(); ++itr)
+ if (itr->second->GetCaptain() == guid)
+ return itr->second;
+
+ return NULL;
+}
+
+void ObjectMgr::AddArenaTeam(ArenaTeam* arenaTeam)
+{
+ mArenaTeamMap[arenaTeam->GetId()] = arenaTeam;
+}
+
+void ObjectMgr::RemoveArenaTeam(uint32 Id)
+{
+ mArenaTeamMap.erase(Id);
+}
+
+CreatureInfo const* ObjectMgr::GetCreatureTemplate(uint32 id)
+{
+ return sCreatureStorage.LookupEntry<CreatureInfo>(id);
+}
+
+void ObjectMgr::LoadCreatureLocales()
+{
+ mCreatureLocaleMap.clear(); // need for reload case
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,name_loc1,subname_loc1,name_loc2,subname_loc2,name_loc3,subname_loc3,name_loc4,subname_loc4,name_loc5,subname_loc5,name_loc6,subname_loc6,name_loc7,subname_loc7,name_loc8,subname_loc8 FROM locales_creature");
+
+ if (!result)
+ return;
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 entry = fields[0].GetUInt32();
+
+ CreatureLocale& data = mCreatureLocaleMap[entry];
+
+ for (uint8 i = 1; i < MAX_LOCALE; ++i)
+ {
+ std::string str = fields[1+2*(i-1)].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.Name.size() <= idx)
+ data.Name.resize(idx+1);
+
+ data.Name[idx] = str;
+ }
+ }
+ str = fields[1+2*(i-1)+1].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.SubName.size() <= idx)
+ data.SubName.resize(idx+1);
+
+ data.SubName[idx] = str;
+ }
+ }
+ }
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %lu creature locale strings", (unsigned long)mCreatureLocaleMap.size());
+}
+
+void ObjectMgr::LoadGossipMenuItemsLocales()
+{
+ mGossipMenuItemsLocaleMap.clear(); // need for reload case
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT menu_id,id,"
+ "option_text_loc1,box_text_loc1,option_text_loc2,box_text_loc2,"
+ "option_text_loc3,box_text_loc3,option_text_loc4,box_text_loc4,"
+ "option_text_loc5,box_text_loc5,option_text_loc6,box_text_loc6,"
+ "option_text_loc7,box_text_loc7,option_text_loc8,box_text_loc8 "
+ "FROM locales_gossip_menu_option");
+
+ if (!result)
+ return;
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint16 menuId = fields[0].GetUInt16();
+ uint16 id = fields[1].GetUInt16();
+
+ GossipMenuItemsLocale& data = mGossipMenuItemsLocaleMap[MAKE_PAIR32(menuId,id)];
+
+ for (uint8 i = 1; i < MAX_LOCALE; ++i)
+ {
+ std::string str = fields[2+2*(i-1)].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.OptionText.size() <= idx)
+ data.OptionText.resize(idx+1);
+
+ data.OptionText[idx] = str;
+ }
+ }
+ str = fields[2+2*(i-1)+1].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.BoxText.size() <= idx)
+ data.BoxText.resize(idx+1);
+
+ data.BoxText[idx] = str;
+ }
+ }
+ }
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %lu gossip_menu_option locale strings", (unsigned long)mGossipMenuItemsLocaleMap.size());
+}
+
+void ObjectMgr::LoadPointOfInterestLocales()
+{
+ mPointOfInterestLocaleMap.clear(); // need for reload case
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,icon_name_loc1,icon_name_loc2,icon_name_loc3,icon_name_loc4,icon_name_loc5,icon_name_loc6,icon_name_loc7,icon_name_loc8 FROM locales_points_of_interest");
+
+ if (!result)
+ return;
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 entry = fields[0].GetUInt32();
+
+ PointOfInterestLocale& data = mPointOfInterestLocaleMap[entry];
+
+ for (uint8 i = 1; i < MAX_LOCALE; ++i)
+ {
+ std::string str = fields[i].GetCppString();
+ if (str.empty())
+ continue;
+
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.IconName.size() <= idx)
+ data.IconName.resize(idx+1);
+
+ data.IconName[idx] = str;
+ }
+ }
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %lu points_of_interest locale strings", (unsigned long)mPointOfInterestLocaleMap.size());
+}
+
+struct SQLCreatureLoader : public SQLStorageLoaderBase<SQLCreatureLoader>
+{
+ template<class D>
+ void convert_from_str(uint32 /*field_pos*/, char *src, D &dst)
+ {
+ dst = D(objmgr.GetScriptId(src));
+ }
+};
+
+void ObjectMgr::LoadCreatureTemplates()
+{
+ SQLCreatureLoader loader;
+ loader.Load(sCreatureStorage);
+
+ sLog.outString(">> Loaded %u creature definitions", sCreatureStorage.RecordCount);
+ sLog.outString();
+
+ // check data correctness
+ for (uint32 i = 1; i < sCreatureStorage.MaxEntry; ++i)
+ {
+ CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(i);
+ CheckCreatureTemplate(cInfo);
+ }
+}
+
+void ObjectMgr::CheckCreatureTemplate(CreatureInfo 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
+
+ CreatureInfo const* difficultyInfo = GetCreatureTemplate(cInfo->DifficultyEntry[diff]);
+ if (!difficultyInfo)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) have `difficulty_entry_%u`=%u but creature entry %u not exist.",
+ cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff], cInfo->DifficultyEntry[diff]);
+ continue;
+ }
+
+ if (difficultyEntries[diff].find(cInfo->Entry) != difficultyEntries[diff].end())
+ {
+ sLog.outErrorDb("Creature (Entry: %u) listed as difficulty %u but have value in `difficulty_entry_1`.", cInfo->Entry, diff + 1);
+ continue;
+ }
+
+ bool ok2 = true;
+ for (uint32 diff2 = 0; diff2 < MAX_DIFFICULTY - 1 && ok2; ++diff2)
+ {
+ ok2 = false;
+ if (difficultyEntries[diff2].find(cInfo->DifficultyEntry[diff]) != difficultyEntries[diff2].end())
+ {
+ sLog.outErrorDb("Creature (Entry: %u) already listed as difficulty %u for another entry.", cInfo->DifficultyEntry[diff], diff2 + 1);
+ continue;
+ }
+
+ if (hasDifficultyEntries[diff2].find(cInfo->DifficultyEntry[diff]) != hasDifficultyEntries[diff2].end())
+ {
+ sLog.outErrorDb("Creature (Entry: %u) have `difficulty_entry_%u`=%u but creature entry %u have difficulty %u entry also.",
+ cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff], cInfo->DifficultyEntry[diff], diff2 + 1);
+ continue;
+ }
+ ok2 = true;
+ }
+ if (!ok2)
+ continue;
+
+ if (cInfo->unit_class != difficultyInfo->unit_class)
+ {
+ sLog.outErrorDb("Creature (Entry: %u, class %u) has different `unit_class` in difficulty %u mode (Entry: %u, class %u).",
+ cInfo->Entry, cInfo->unit_class, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->unit_class);
+ continue;
+ }
+
+ if (cInfo->npcflag != difficultyInfo->npcflag)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has different `npcflag` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
+ continue;
+ }
+
+ if (cInfo->trainer_class != difficultyInfo->trainer_class)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has different `trainer_class` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
+ continue;
+ }
+
+ if (cInfo->trainer_race != difficultyInfo->trainer_race)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has different `trainer_race` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
+ continue;
+ }
+
+ if (cInfo->trainer_type != difficultyInfo->trainer_type)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has different `trainer_type` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
+ continue;
+ }
+
+ if (cInfo->trainer_spell != difficultyInfo->trainer_spell)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has different `trainer_spell` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
+ continue;
+ }
+
+ if (difficultyInfo->AIName && *difficultyInfo->AIName)
+ {
+ sLog.outErrorDb("Difficulty %u mode creature (Entry: %u) has `AIName`, but in any case will used difficulty 0 mode creature (Entry: %u) AIName.",
+ diff, cInfo->DifficultyEntry[diff], cInfo->Entry);
+ continue;
+ }
+
+ if (difficultyInfo->ScriptID)
+ {
+ sLog.outErrorDb("Difficulty %u mode creature (Entry: %u) has `ScriptName`, but in any case will used difficulty 0 mode creature (Entry: %u) ScriptName.",
+ diff, cInfo->DifficultyEntry[diff], cInfo->Entry);
+ continue;
+ }
+
+ hasDifficultyEntries[diff].insert(cInfo->Entry);
+ difficultyEntries[diff].insert(cInfo->DifficultyEntry[diff]);
+ ok = true;
+ }
+
+ FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction_A);
+ if (!factionTemplate)
+ sLog.outErrorDb("Creature (Entry: %u) has non-existing faction_A template (%u)", cInfo->Entry, cInfo->faction_A);
+
+ factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction_H);
+ if (!factionTemplate)
+ sLog.outErrorDb("Creature (Entry: %u) has non-existing faction_H template (%u)", cInfo->Entry, cInfo->faction_H);
+
+ // used later for scale
+ CreatureDisplayInfoEntry const* displayScaleEntry = NULL;
+
+ if (cInfo->Modelid1)
+ {
+ CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid1);
+ if (!displayEntry)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has non-existing Modelid1 id (%u), can crash client", cInfo->Entry, cInfo->Modelid1);
+ const_cast<CreatureInfo*>(cInfo)->Modelid1 = 0;
+ }
+ else if (!displayScaleEntry)
+ displayScaleEntry = displayEntry;
+
+ CreatureModelInfo const* minfo = sCreatureModelStorage.LookupEntry<CreatureModelInfo>(cInfo->Modelid1);
+ if (!minfo)
+ sLog.outErrorDb("Creature (Entry: %u) not has model data for Modelid1 (%u)", cInfo->Entry, cInfo->Modelid1);
+ }
+
+ if (cInfo->Modelid2)
+ {
+ CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid2);
+ if (!displayEntry)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has non-existing Modelid2 id (%u), can crash client", cInfo->Entry, cInfo->Modelid2);
+ const_cast<CreatureInfo*>(cInfo)->Modelid2 = 0;
+ }
+ else if (!displayScaleEntry)
+ displayScaleEntry = displayEntry;
+
+ CreatureModelInfo const* minfo = sCreatureModelStorage.LookupEntry<CreatureModelInfo>(cInfo->Modelid2);
+ if (!minfo)
+ sLog.outErrorDb("Creature (Entry: %u) not has model data for Modelid2 (%u)", cInfo->Entry, cInfo->Modelid2);
+ }
+
+ if (cInfo->Modelid3)
+ {
+ CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid3);
+ if (!displayEntry)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has non-existing Modelid3 id (%u), can crash client", cInfo->Entry, cInfo->Modelid3);
+ const_cast<CreatureInfo*>(cInfo)->Modelid3 = 0;
+ }
+ else if (!displayScaleEntry)
+ displayScaleEntry = displayEntry;
+
+ CreatureModelInfo const* minfo = sCreatureModelStorage.LookupEntry<CreatureModelInfo>(cInfo->Modelid3);
+ if (!minfo)
+ sLog.outErrorDb("Creature (Entry: %u) not has model data for Modelid3 (%u)", cInfo->Entry, cInfo->Modelid3);
+ }
+
+ if (cInfo->Modelid4)
+ {
+ CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid4);
+ if (!displayEntry)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has non-existing Modelid4 id (%u), can crash client", cInfo->Entry, cInfo->Modelid4);
+ const_cast<CreatureInfo*>(cInfo)->Modelid4 = 0;
+ }
+ else if (!displayScaleEntry)
+ displayScaleEntry = displayEntry;
+
+ CreatureModelInfo const* minfo = sCreatureModelStorage.LookupEntry<CreatureModelInfo>(cInfo->Modelid4);
+ if (!minfo)
+ sLog.outErrorDb("Creature (Entry: %u) not has model data for Modelid4 (%u)", cInfo->Entry, cInfo->Modelid4);
+ }
+
+ if (!displayScaleEntry)
+ sLog.outErrorDb("Creature (Entry: %u) not has any existed display id in Modelid1/Modelid2/Modelid3/Modelid4", cInfo->Entry);
+
+ for (int k = 0; k < MAX_KILL_CREDIT; ++k)
+ {
+ if (cInfo->KillCredit[k])
+ {
+ if (!GetCreatureTemplate(cInfo->KillCredit[k]))
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has not existed creature entry in `KillCredit%d` (%u)",cInfo->Entry,k+1,cInfo->KillCredit[k]);
+ const_cast<CreatureInfo*>(cInfo)->KillCredit[k] = 0;
+ }
+ }
+ }
+
+ if (!cInfo->unit_class || ((1 << (cInfo->unit_class-1)) & CLASSMASK_ALL_CREATURES) == 0)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has invalid unit_class(%u) for creature_template. Set to 1 (UNIT_CLASS_WARRIOR).", cInfo->Entry, cInfo->unit_class);
+ const_cast<CreatureInfo*>(cInfo)->unit_class = UNIT_CLASS_WARRIOR;
+ }
+
+ if (cInfo->dmgschool >= MAX_SPELL_SCHOOL)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has invalid spell school value (%u) in `dmgschool`",cInfo->Entry,cInfo->dmgschool);
+ const_cast<CreatureInfo*>(cInfo)->dmgschool = SPELL_SCHOOL_NORMAL;
+ }
+
+ if (cInfo->baseattacktime == 0)
+ const_cast<CreatureInfo*>(cInfo)->baseattacktime = BASE_ATTACK_TIME;
+
+ if (cInfo->rangeattacktime == 0)
+ const_cast<CreatureInfo*>(cInfo)->rangeattacktime = BASE_ATTACK_TIME;
+
+ if (cInfo->npcflag & UNIT_NPC_FLAG_SPELLCLICK)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has dynamic flag UNIT_NPC_FLAG_SPELLCLICK (%u) set, it expect to be set by code base at `npc_spellclick_spells` content.",cInfo->Entry,UNIT_NPC_FLAG_SPELLCLICK);
+ const_cast<CreatureInfo*>(cInfo)->npcflag &= ~UNIT_NPC_FLAG_SPELLCLICK;
+ }
+
+ if ((cInfo->npcflag & UNIT_NPC_FLAG_TRAINER) && cInfo->trainer_type >= MAX_TRAINER_TYPE)
+ sLog.outErrorDb("Creature (Entry: %u) has wrong trainer type %u",cInfo->Entry,cInfo->trainer_type);
+
+ if (cInfo->type && !sCreatureTypeStore.LookupEntry(cInfo->type))
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has invalid creature type (%u) in `type`",cInfo->Entry,cInfo->type);
+ const_cast<CreatureInfo*>(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)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has invalid creature family (%u) in `family`",cInfo->Entry,cInfo->family);
+ const_cast<CreatureInfo*>(cInfo)->family = 0;
+ }
+
+ if (cInfo->InhabitType <= 0 || cInfo->InhabitType > INHABIT_ANYWHERE)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has wrong value (%u) in `InhabitType`, creature will not correctly walk/swim/fly",cInfo->Entry,cInfo->InhabitType);
+ const_cast<CreatureInfo*>(cInfo)->InhabitType = INHABIT_ANYWHERE;
+ }
+
+ if (cInfo->VehicleId)
+ {
+ VehicleEntry const* vehId = sVehicleStore.LookupEntry(cInfo->VehicleId);
+ if (!vehId)
+ sLog.outErrorDb("Creature (Entry: %u) has a non-existing VehicleId (%u). This *WILL* cause the client to freeze!", cInfo->Entry, cInfo->VehicleId);
+ }
+
+ if (cInfo->PetSpellDataId)
+ {
+ CreatureSpellDataEntry const* spellDataId = sCreatureSpellDataStore.LookupEntry(cInfo->PetSpellDataId);
+ if (!spellDataId)
+ sLog.outErrorDb("Creature (Entry: %u) has non-existing PetSpellDataId (%u)", cInfo->Entry, cInfo->PetSpellDataId);
+ }
+
+ for (uint8 j = 0; j < CREATURE_MAX_SPELLS; ++j)
+ {
+ if (cInfo->spells[j] && !sSpellStore.LookupEntry(cInfo->spells[j]))
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has non-existing Spell%d (%u), set to 0", cInfo->Entry, j+1,cInfo->spells[j]);
+ const_cast<CreatureInfo*>(cInfo)->spells[j] = 0;
+ }
+ }
+
+ if (cInfo->MovementType >= MAX_DB_MOTION_TYPE)
+ {
+ sLog.outErrorDb("Creature (Entry: %u) has wrong movement generator type (%u), ignore and set to IDLE.",cInfo->Entry,cInfo->MovementType);
+ const_cast<CreatureInfo*>(cInfo)->MovementType = IDLE_MOTION_TYPE;
+ }
+
+ if (cInfo->equipmentId > 0) // 0 no equipment
+ {
+ if (!GetEquipmentInfo(cInfo->equipmentId))
+ {
+ sLog.outErrorDb("Table `creature_template` have creature (Entry: %u) with equipment_id %u not found in table `creature_equip_template`, set to no equipment.", cInfo->Entry, cInfo->equipmentId);
+ const_cast<CreatureInfo*>(cInfo)->equipmentId = 0;
+ }
+ }
+
+ /// if not set custom creature scale then load scale from CreatureDisplayInfo.dbc
+ if (cInfo->scale <= 0.0f)
+ {
+ if (displayScaleEntry)
+ const_cast<CreatureInfo*>(cInfo)->scale = displayScaleEntry->scale;
+ else
+ const_cast<CreatureInfo*>(cInfo)->scale = 1.0f;
+ }
+
+ if (cInfo->expansion > (MAX_CREATURE_BASE_HP - 1))
+ {
+ sLog.outErrorDb("Table `creature_template` have creature (Entry: %u) with expansion %u ignore and set to NULL.", cInfo->expansion);
+ const_cast<CreatureInfo*>(cInfo)->expansion = 0;
+ }
+
+ const_cast<CreatureInfo*>(cInfo)->dmg_multiplier *= Creature::_GetDamageMod(cInfo->rank);
+}
+
+void ObjectMgr::ConvertCreatureAddonAuras(CreatureDataAddon* addon, char const* table, char const* guidEntryStr)
+{
+ // Now add the auras, format "spellid effectindex spellid effectindex..."
+ char *p,*s;
+ std::map<uint32, uint32> val;
+ s=p=(char*)reinterpret_cast<char const*>(addon->auras);
+ if (p)
+ {
+ uint32 currSpellId = 0;
+ bool spell = true;
+ while (p[0] != 0)
+ {
+ ++p;
+ if (p[0] == ' ' || p[0] == 0)
+ {
+ if (spell)
+ currSpellId = atoi(s);
+ else
+ {
+ uint8 eff = atoi(s);
+ if (eff >=3)
+ {
+ sLog.outErrorDb("Creature (%s: %u) has wrong `auras` data in `%s`(too high aura effect: %d for spell: %d)",guidEntryStr,addon->guidOrEntry,table,eff,currSpellId);
+ }
+ val[currSpellId] |= 1<<eff;
+ }
+ spell = !spell;
+ if (p[0] == 0)
+ break;
+ s=++p;
+ }
+ }
+
+ // free char* loaded memory
+ delete[] (char*)reinterpret_cast<char const*>(addon->auras);
+
+ // wrong list
+ if (!spell)
+ {
+ addon->auras = NULL;
+ sLog.outErrorDb("Creature (%s: %u) has wrong `auras` data in `%s`.",guidEntryStr,addon->guidOrEntry,table);
+ return;
+ }
+ }
+
+ // empty list
+ if (val.empty())
+ {
+ addon->auras = NULL;
+ return;
+ }
+
+ // replace by new structures array
+ const_cast<CreatureDataAddonAura*&>(addon->auras) = new CreatureDataAddonAura[val.size()+1];
+
+ uint32 i=0;
+ for (std::map<uint32, uint32>::iterator itr = val.begin(); itr != val.end();++itr)
+ {
+ CreatureDataAddonAura& cAura = const_cast<CreatureDataAddonAura&>(addon->auras[i]);
+ cAura.spell_id = itr->first;
+ cAura.effectMask = itr->second;
+ if (cAura.effectMask > 7 || !cAura.effectMask)
+ {
+ sLog.outErrorDb("Creature (%s: %u) has wrong effect for spell %u in `auras` field in `%s`.",guidEntryStr,addon->guidOrEntry,cAura.spell_id,table);
+ continue;
+ }
+ SpellEntry const *AdditionalSpellInfo = sSpellStore.LookupEntry(cAura.spell_id);
+ if (!AdditionalSpellInfo)
+ {
+ sLog.outErrorDb("Creature (%s: %u) has wrong spell %u defined in `auras` field in `%s`.",guidEntryStr,addon->guidOrEntry,cAura.spell_id,table);
+ continue;
+ }
+ for (uint8 eff = 0; eff < MAX_SPELL_EFFECTS; ++eff)
+ {
+ if ((1<<eff) & cAura.effectMask)
+ {
+ if (!AdditionalSpellInfo->Effect[eff] || !AdditionalSpellInfo->EffectApplyAuraName[eff])
+ {
+ sLog.outErrorDb("Creature (%s: %u) has not aura effect %u of spell %u defined in `auras` field in `%s`.",guidEntryStr,addon->guidOrEntry,eff,cAura.spell_id,table);
+ continue;
+ }
+ else if (AdditionalSpellInfo->Effect[eff] == SPELL_EFFECT_PERSISTENT_AREA_AURA)
+ {
+ sLog.outErrorDb("Creature (%s: %u) has persistent area aura effect %u of spell %u defined in `auras` field in `%s`.",guidEntryStr,addon->guidOrEntry,eff,cAura.spell_id,table);
+ continue;
+ }
+ }
+ }
+
+ ++i;
+ }
+
+ // fill terminator element (after last added)
+ CreatureDataAddonAura& endAura = const_cast<CreatureDataAddonAura&>(addon->auras[i]);
+ endAura.spell_id = 0;
+ endAura.effectMask = 0;
+}
+
+void ObjectMgr::LoadCreatureAddons(SQLStorage& creatureaddons, char const* entryName, char const* comment)
+{
+ creatureaddons.Load();
+
+ sLog.outString(">> Loaded %u %s", creatureaddons.RecordCount, comment);
+ sLog.outString();
+
+ // check data correctness and convert 'auras'
+ for (uint32 i = 1; i < creatureaddons.MaxEntry; ++i)
+ {
+ CreatureDataAddon const* addon = creatureaddons.LookupEntry<CreatureDataAddon>(i);
+ if (!addon)
+ continue;
+
+ if (addon->mount)
+ {
+ if (!sCreatureDisplayInfoStore.LookupEntry(addon->mount))
+ {
+ sLog.outErrorDb("Creature (%s %u) have invalid displayInfoId for mount (%u) defined in `%s`.", entryName, addon->guidOrEntry, addon->mount, creatureaddons.GetTableName());
+ const_cast<CreatureDataAddon*>(addon)->mount = 0;
+ }
+ }
+
+ if (!sEmotesStore.LookupEntry(addon->emote))
+ sLog.outErrorDb("Creature (%s %u) have invalid emote (%u) defined in `%s`.", entryName, addon->guidOrEntry, addon->emote, creatureaddons.GetTableName());
+
+ /*if (addon->move_flags & (MONSTER_MOVE_UNK1|MONSTER_MOVE_UNK4))
+ {
+ sLog.outErrorDb("Creature (%s %u) movement flags mask defined in `%s` include forbidden flags (" I32FMT ") that can crash client, cleanup at load.", entryName, addon->guidOrEntry, creatureaddons.GetTableName(), (MONSTER_MOVE_UNK1|MONSTER_MOVE_UNK4));
+ const_cast<CreatureDataAddon*>(addon)->move_flags &= ~(MONSTER_MOVE_UNK1|MONSTER_MOVE_UNK4);
+ }*/
+
+ ConvertCreatureAddonAuras(const_cast<CreatureDataAddon*>(addon), creatureaddons.GetTableName(), entryName);
+ }
+}
+
+void ObjectMgr::LoadCreatureAddons()
+{
+ LoadCreatureAddons(sCreatureInfoAddonStorage,"Entry","creature template addons");
+
+ // check entry ids
+ for (uint32 i = 1; i < sCreatureInfoAddonStorage.MaxEntry; ++i)
+ if (CreatureDataAddon const* addon = sCreatureInfoAddonStorage.LookupEntry<CreatureDataAddon>(i))
+ if (!sCreatureStorage.LookupEntry<CreatureInfo>(addon->guidOrEntry))
+ sLog.outErrorDb("Creature (Entry: %u) does not exist but has a record in `%s`",addon->guidOrEntry, sCreatureInfoAddonStorage.GetTableName());
+
+ sLog.outString("Loading Creature Addon Data...");
+ LoadCreatureAddons(sCreatureDataAddonStorage,"GUID","creature addons");
+
+ // check entry ids
+ for (uint32 i = 1; i < sCreatureDataAddonStorage.MaxEntry; ++i)
+ if (CreatureDataAddon const* addon = sCreatureDataAddonStorage.LookupEntry<CreatureDataAddon>(i))
+ if (mCreatureDataMap.find(addon->guidOrEntry) == mCreatureDataMap.end())
+ sLog.outErrorDb("Creature (GUID: %u) does not exist but has a record in `creature_addon`",addon->guidOrEntry);
+}
+
+EquipmentInfo const* ObjectMgr::GetEquipmentInfo(uint32 entry)
+{
+ return sEquipmentStorage.LookupEntry<EquipmentInfo>(entry);
+}
+
+void ObjectMgr::LoadEquipmentTemplates()
+{
+ sEquipmentStorage.Load();
+
+ for (uint32 i=0; i< sEquipmentStorage.MaxEntry; ++i)
+ {
+ EquipmentInfo const* eqInfo = sEquipmentStorage.LookupEntry<EquipmentInfo>(i);
+
+ if (!eqInfo)
+ continue;
+
+ for (uint8 j=0; j<3; j++)
+ {
+ if (!eqInfo->equipentry[j])
+ continue;
+
+ ItemEntry const *dbcitem = sItemStore.LookupEntry(eqInfo->equipentry[j]);
+
+ if (!dbcitem)
+ {
+ sLog.outErrorDb("Unknown item (entry=%u) in creature_equip_template.equipentry%u for entry = %u, forced to 0.", eqInfo->equipentry[j], j+1, i);
+ const_cast<EquipmentInfo*>(eqInfo)->equipentry[j] = 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)
+ {
+ sLog.outErrorDb("Item (entry=%u) in creature_equip_template.equipentry%u for entry = %u is not equipable in a hand, forced to 0.", eqInfo->equipentry[j], j+1, i);
+ const_cast<EquipmentInfo*>(eqInfo)->equipentry[j] = 0;
+ }
+ }
+ }
+ sLog.outString(">> Loaded %u equipment template", sEquipmentStorage.RecordCount);
+ sLog.outString();
+}
+
+CreatureModelInfo const* ObjectMgr::GetCreatureModelInfo(uint32 modelid)
+{
+ return sCreatureModelStorage.LookupEntry<CreatureModelInfo>(modelid);
+}
+
+uint32 ObjectMgr::ChooseDisplayId(uint32 /*team*/, const CreatureInfo *cinfo, const CreatureData *data /*= NULL*/)
+{
+ // Load creature model (display id)
+ uint32 display_id = 0;
+
+ if (!data || data->displayid == 0)
+ {
+ display_id = cinfo->GetRandomValidModelId();
+ }
+ else
+ return data->displayid;
+
+ /*if (!team)
+ {
+ switch(cinfo->Entry)
+ {
+ case 28511: // Eye of Acherus
+ case 33114: // Flame Leviathan Seat (model 24914 chair)
+ case 33167: // Salvaged Demolisher Mechanic Seat
+ case 33189: // Liquid Pryite
+ return cinfo->Modelid1;
+ case 33218: // Pyrite Safety Container
+ return cinfo->Modelid2;
+ case 33143: // Overload Control Device
+ return cinfo->Modelid3;
+ default:
+ return cinfo->GetRandomValidModelId();
+ }
+ }*/
+
+ return display_id;
+}
+
+CreatureModelInfo const* ObjectMgr::GetCreatureModelRandomGender(uint32 display_id)
+{
+ CreatureModelInfo const *minfo = GetCreatureModelInfo(display_id);
+ if (!minfo)
+ return NULL;
+
+ // If a model for another gender exists, 50% chance to use it
+ if (minfo->modelid_other_gender != 0 && urand(0,1) == 0)
+ {
+ CreatureModelInfo const *minfo_tmp = GetCreatureModelInfo(minfo->modelid_other_gender);
+ if (!minfo_tmp)
+ {
+ sLog.outErrorDb("Model (Entry: %u) has modelid_other_gender %u not found in table `creature_model_info`. ", minfo->modelid, minfo->modelid_other_gender);
+ return minfo; // not fatal, just use the previous one
+ }
+ else
+ return minfo_tmp;
+ }
+ else
+ return minfo;
+}
+
+void ObjectMgr::LoadCreatureModelInfo()
+{
+ sCreatureModelStorage.Load();
+
+ // post processing
+ for (uint32 i = 1; i < sCreatureModelStorage.MaxEntry; ++i)
+ {
+ CreatureModelInfo const *minfo = sCreatureModelStorage.LookupEntry<CreatureModelInfo>(i);
+ if (!minfo)
+ continue;
+
+ if (!sCreatureDisplayInfoStore.LookupEntry(minfo->modelid))
+ sLog.outErrorDb("Table `creature_model_info` has model for not existed display id (%u).", minfo->modelid);
+
+ if (minfo->gender > GENDER_NONE)
+ {
+ sLog.outErrorDb("Table `creature_model_info` has wrong gender (%u) for display id (%u).", uint32(minfo->gender), minfo->modelid);
+ const_cast<CreatureModelInfo*>(minfo)->gender = GENDER_MALE;
+ }
+
+ if (minfo->modelid_other_gender && !sCreatureDisplayInfoStore.LookupEntry(minfo->modelid_other_gender))
+ {
+ sLog.outErrorDb("Table `creature_model_info` has not existed alt.gender model (%u) for existed display id (%u).", minfo->modelid_other_gender, minfo->modelid);
+ const_cast<CreatureModelInfo*>(minfo)->modelid_other_gender = 0;
+ }
+ }
+
+ sLog.outString(">> Loaded %u creature model based info", sCreatureModelStorage.RecordCount);
+ sLog.outString();
+
+ // check if combat_reach is valid
+ for (uint32 i = 1; i < sCreatureModelStorage.MaxEntry; ++i)
+ {
+ CreatureModelInfo const* mInfo = sCreatureModelStorage.LookupEntry<CreatureModelInfo>(i);
+ if (!mInfo)
+ continue;
+
+ if (mInfo->combat_reach < 0.1f)
+ {
+ //sLog.outErrorDb("Creature model (Entry: %u) has invalid combat reach (%f), setting it to 0.5", mInfo->modelid, mInfo->combat_reach);
+ const_cast<CreatureModelInfo*>(mInfo)->combat_reach = DEFAULT_COMBAT_REACH;
+ }
+ }
+}
+
+bool ObjectMgr::CheckCreatureLinkedRespawn(uint32 guid, uint32 linkedGuid) const
+{
+ const CreatureData* const slave = GetCreatureData(guid);
+ const CreatureData* const master = GetCreatureData(linkedGuid);
+
+ if (!slave || !master) // they must have a corresponding entry in db
+ {
+ sLog.outError("LinkedRespawn: Creature '%u' linking to '%u' which doesn't exist",guid,linkedGuid);
+ return false;
+ }
+
+ const MapEntry* const map = sMapStore.LookupEntry(master->mapid);
+
+ if (master->mapid != slave->mapid // link only to same map
+ && (!map || map->Instanceable())) // or to unistanced world
+ {
+ sLog.outError("LinkedRespawn: Creature '%u' linking to '%u' on an unpermitted map",guid,linkedGuid);
+ return false;
+ }
+
+ if (!(master->spawnMask & slave->spawnMask) // they must have a possibility to meet (normal/heroic difficulty)
+ && (!map || map->Instanceable()))
+ {
+ sLog.outError("LinkedRespawn: Creature '%u' linking to '%u' with not corresponding spawnMask",guid,linkedGuid);
+ return false;
+ }
+
+ return true;
+}
+
+void ObjectMgr::LoadCreatureLinkedRespawn()
+{
+ mCreatureLinkedRespawnMap.clear();
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT guid, linkedGuid FROM creature_linked_respawn ORDER BY guid ASC");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString("");
+ sLog.outErrorDb(">> Loaded 0 linked respawns. DB table `creature_linked_respawn` is empty.");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 guid = fields[0].GetUInt32();
+ uint32 linkedGuid = fields[1].GetUInt32();
+
+ if (CheckCreatureLinkedRespawn(guid,linkedGuid))
+ mCreatureLinkedRespawnMap[guid] = linkedGuid;
+
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u linked respawns", mCreatureLinkedRespawnMap.size());
+}
+
+bool ObjectMgr::SetCreatureLinkedRespawn(uint32 guid, uint32 linkedGuid)
+{
+ if (!guid)
+ return false;
+
+ if (!linkedGuid) // we're removing the linking
+ {
+ mCreatureLinkedRespawnMap.erase(guid);
+ WorldDatabase.DirectPExecute("DELETE FROM creature_linked_respawn WHERE guid = '%u'",guid);
+ return true;
+ }
+
+ if (CheckCreatureLinkedRespawn(guid,linkedGuid)) // we add/change linking
+ {
+ mCreatureLinkedRespawnMap[guid] = linkedGuid;
+ WorldDatabase.DirectPExecute("REPLACE INTO creature_linked_respawn (guid,linkedGuid) VALUES ('%u','%u')",guid,linkedGuid);
+ return true;
+ }
+ return false;
+}
+
+void ObjectMgr::LoadCreatures()
+{
+ uint32 count = 0;
+ // 0 1 2 3
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT creature.guid, id, map, modelid,"
+ // 4 5 6 7 8 9 10 11
+ "equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, spawndist, currentwaypoint,"
+ // 12 13 14 15 16 17 18 19
+ "curhealth, curmana, DeathState, MovementType, spawnMask, phaseMask, event, pool_entry "
+ "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)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded 0 creature. DB table `creature` is empty.");
+ return;
+ }
+
+ // build single time for check creature data
+ std::set<uint32> difficultyCreatures[MAX_DIFFICULTY - 1];
+ for (uint32 i = 0; i < sCreatureStorage.MaxEntry; ++i)
+ if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(i))
+ for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1; ++diff)
+ if (cInfo->DifficultyEntry[diff])
+ difficultyCreatures[diff].insert(cInfo->DifficultyEntry[diff]);
+
+ // build single time for check spawnmask
+ std::map<uint32,uint32> 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);
+
+ //TODO: remove this
+ //gameeventmgr.mGameEventCreatureGuids.resize(52*2-1);
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 guid = fields[ 0].GetUInt32();
+ uint32 entry = fields[ 1].GetUInt32();
+
+ CreatureInfo const* cInfo = GetCreatureTemplate(entry);
+ if (!cInfo)
+ {
+ sLog.outErrorDb("Table `creature` has creature (GUID: %u) with non existing creature entry %u, skipped.", guid, entry);
+ continue;
+ }
+
+ CreatureData& data = mCreatureDataMap[guid];
+
+ data.id = entry;
+ data.mapid = fields[ 2].GetUInt32();
+ data.displayid = fields[ 3].GetUInt32();
+ data.equipmentId = fields[ 4].GetUInt32();
+ data.posX = fields[ 5].GetFloat();
+ data.posY = fields[ 6].GetFloat();
+ data.posZ = fields[ 7].GetFloat();
+ data.orientation = fields[ 8].GetFloat();
+ data.spawntimesecs = fields[ 9].GetUInt32();
+ data.spawndist = fields[10].GetFloat();
+ data.currentwaypoint= fields[11].GetUInt32();
+ data.curhealth = fields[12].GetUInt32();
+ data.curmana = fields[13].GetUInt32();
+ data.is_dead = fields[14].GetBool();
+ data.movementType = fields[15].GetUInt8();
+ data.spawnMask = fields[16].GetUInt8();
+ data.phaseMask = fields[17].GetUInt16();
+ int16 gameEvent = fields[18].GetInt16();
+ int16 PoolId = fields[19].GetInt16();
+
+ MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapid);
+ if (!mapEntry)
+ {
+ sLog.outErrorDb("Table `creature` have creature (GUID: %u) that spawned at not existed map (Id: %u), skipped.",guid, data.mapid);
+ continue;
+ }
+
+ if (data.spawnMask & ~spawnMasks[data.mapid])
+ sLog.outErrorDb("Table `creature` have creature (GUID: %u) that have wrong spawn mask %u including not supported difficulty modes for map (Id: %u).",guid, data.spawnMask, data.mapid);
+
+ bool ok = true;
+ for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && ok; ++diff)
+ {
+ if (difficultyCreatures[diff].find(data.id) != difficultyCreatures[diff].end())
+ {
+ sLog.outErrorDb("Table `creature` have creature (GUID: %u) that listed as difficulty %u template (entry: %u) in `creature_template`, skipped.",
+ guid, diff + 1, data.id);
+ ok = false;
+ }
+ }
+ if (!ok)
+ continue;
+
+ // I do not know why but in db most display id are not zero
+ /*if (data.displayid == 11686 || data.displayid == 24719)
+ {
+ (const_cast<CreatureInfo*>(cInfo))->flags_extra |= CREATURE_FLAG_EXTRA_TRIGGER;
+ }
+ else if (data.displayid == cInfo->DisplayID_A || data.displayid == cInfo->DisplayID_A2
+ || data.displayid == cInfo->DisplayID_H || data.displayid == cInfo->DisplayID_H2)
+ data.displayid = 0;
+ */
+
+ if (data.equipmentId > 0) // -1 no equipment, 0 use default
+ {
+ if (!GetEquipmentInfo(data.equipmentId))
+ {
+ sLog.outErrorDb("Table `creature` have creature (Entry: %u) with equipment_id %u not found in table `creature_equip_template`, set to no equipment.", data.id, data.equipmentId);
+ data.equipmentId = -1;
+ }
+ }
+
+ if (cInfo->flags_extra & CREATURE_FLAG_EXTRA_INSTANCE_BIND)
+ {
+ if (!mapEntry || !mapEntry->IsDungeon())
+ sLog.outErrorDb("Table `creature` have creature (GUID: %u Entry: %u) with `creature_template`.`flags_extra` including CREATURE_FLAG_EXTRA_INSTANCE_BIND but creature are not in instance.",guid,data.id);
+ }
+
+ if (data.spawndist < 0.0f)
+ {
+ sLog.outErrorDb("Table `creature` have creature (GUID: %u Entry: %u) with `spawndist`< 0, set to 0.",guid,data.id);
+ data.spawndist = 0.0f;
+ }
+ else if (data.movementType == RANDOM_MOTION_TYPE)
+ {
+ if (data.spawndist == 0.0f)
+ {
+ sLog.outErrorDb("Table `creature` have creature (GUID: %u Entry: %u) with `MovementType`=1 (random movement) but with `spawndist`=0, replace by idle movement type (0).",guid,data.id);
+ data.movementType = IDLE_MOTION_TYPE;
+ }
+ else if (cInfo->flags_extra & CREATURE_FLAG_EXTRA_TRIGGER)
+ data.movementType = IDLE_MOTION_TYPE;
+ }
+ else if (data.movementType == IDLE_MOTION_TYPE)
+ {
+ if (data.spawndist != 0.0f)
+ {
+ sLog.outErrorDb("Table `creature` have creature (GUID: %u Entry: %u) with `MovementType`=0 (idle) have `spawndist`<>0, set to 0.",guid,data.id);
+ data.spawndist = 0.0f;
+ }
+ }
+
+ if (data.phaseMask == 0)
+ {
+ sLog.outErrorDb("Table `creature` have creature (GUID: %u Entry: %u) with `phaseMask`=0 (not visible for anyone), set to 1.",guid,data.id);
+ data.phaseMask = 1;
+ }
+
+ //if (entry == 32307 || entry == 32308)
+ /*if (entry == 30739 || entry == 30740)
+ {
+ gameEvent = 51;
+ uint32 guid2 = objmgr.GenerateLowGuid(HIGHGUID_UNIT);
+ CreatureData& data2 = mCreatureDataMap[guid2];
+ data2 = data;
+// data2.id = (entry == 32307 ? 32308 : 32307);
+ data2.id = (entry == 30739 ? 30740 : 30739);
+ data2.displayid = 0;
+ gameeventmgr.mGameEventCreatureGuids[51+51].push_back(guid);
+ gameeventmgr.mGameEventCreatureGuids[51+50].push_back(guid2);
+ }*/
+
+ if (gameEvent == 0 && PoolId == 0) // if not this is to be managed by GameEvent System or Pool system
+ AddCreatureToGrid(guid, &data);
+
+ ++count;
+
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %lu creatures", (unsigned long)mCreatureDataMap.size());
+}
+
+void ObjectMgr::AddCreatureToGrid(uint32 guid, CreatureData const* data)
+{
+ uint8 mask = data->spawnMask;
+ for (uint8 i = 0; mask != 0; i++, mask >>= 1)
+ {
+ if (mask & 1)
+ {
+ CellPair cell_pair = Trinity::ComputeCellPair(data->posX, data->posY);
+ uint32 cell_id = (cell_pair.y_coord*TOTAL_NUMBER_OF_CELLS_PER_MAP) + cell_pair.x_coord;
+
+ CellObjectGuids& cell_guids = mMapObjectGuids[MAKE_PAIR32(data->mapid,i)][cell_id];
+ cell_guids.creatures.insert(guid);
+ }
+ }
+}
+
+void ObjectMgr::RemoveCreatureFromGrid(uint32 guid, CreatureData const* data)
+{
+ uint8 mask = data->spawnMask;
+ for (uint8 i = 0; mask != 0; i++, mask >>= 1)
+ {
+ if (mask & 1)
+ {
+ CellPair cell_pair = Trinity::ComputeCellPair(data->posX, data->posY);
+ uint32 cell_id = (cell_pair.y_coord*TOTAL_NUMBER_OF_CELLS_PER_MAP) + cell_pair.x_coord;
+
+ CellObjectGuids& cell_guids = mMapObjectGuids[MAKE_PAIR32(data->mapid,i)][cell_id];
+ cell_guids.creatures.erase(guid);
+ }
+ }
+}
+
+uint32 ObjectMgr::AddGOData(uint32 entry, uint32 mapId, float x, float y, float z, float o, uint32 spawntimedelay, float rotation0, float rotation1, float rotation2, float rotation3)
+{
+ GameObjectInfo const* goinfo = GetGameObjectInfo(entry);
+ if (!goinfo)
+ return 0;
+
+ Map* map = const_cast<Map*>(MapManager::Instance().CreateBaseMap(mapId));
+ if (!map)
+ return 0;
+
+ uint32 guid = GenerateLowGuid(HIGHGUID_GAMEOBJECT);
+ GameObjectData& data = NewGOData(guid);
+ data.id = entry;
+ data.mapid = mapId;
+ data.posX = x;
+ data.posY = y;
+ data.posZ = z;
+ data.orientation = o;
+ data.rotation0 = rotation0;
+ data.rotation1 = rotation1;
+ data.rotation2 = rotation2;
+ data.rotation3 = 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(guid, &data);
+
+ // Spawn if necessary (loaded grids only)
+ // We use spawn coords to spawn
+ if (!map->Instanceable() && map->IsLoaded(x, y))
+ {
+ GameObject *go = new GameObject;
+ if (!go->LoadFromDB(guid, map))
+ {
+ sLog.outError("AddGOData: cannot add gameobject entry %u to map", entry);
+ delete go;
+ return 0;
+ }
+ map->Add(go);
+ }
+
+ sLog.outDebug("AddGOData: dbguid %u entry %u map %u x %f y %f z %f o %f", guid, entry, mapId, x, y, z, o);
+
+ return guid;
+}
+
+bool ObjectMgr::MoveCreData(uint32 guid, uint32 mapId, Position pos)
+{
+ CreatureData& data = NewOrExistCreatureData(guid);
+ if (!data.id)
+ return false;
+
+ RemoveCreatureFromGrid(guid, &data);
+ if (data.posX == pos.GetPositionX() && data.posY == pos.GetPositionY() && data.posZ == pos.GetPositionZ())
+ return true;
+ data.posX = pos.GetPositionX();
+ data.posY = pos.GetPositionY();
+ data.posZ = pos.GetPositionZ();
+ data.orientation = pos.GetOrientation();
+ AddCreatureToGrid(guid, &data);
+
+ // Spawn if necessary (loaded grids only)
+ if (Map* map = const_cast<Map*>(MapManager::Instance().CreateBaseMap(mapId)))
+ {
+ // We use spawn coords to spawn
+ if (!map->Instanceable() && map->IsLoaded(data.posX, data.posY))
+ {
+ Creature *creature = new Creature;
+ if (!creature->LoadFromDB(guid, map))
+ {
+ sLog.outError("AddCreature: cannot add creature entry %u to map", guid);
+ delete creature;
+ return false;
+ }
+ map->Add(creature);
+ }
+ }
+ return true;
+}
+
+uint32 ObjectMgr::AddCreData(uint32 entry, uint32 /*team*/, uint32 mapId, float x, float y, float z, float o, uint32 spawntimedelay)
+{
+ CreatureInfo 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 = objmgr.GetCreatureBaseStats(level, cInfo->unit_class);
+
+ uint32 guid = GenerateLowGuid(HIGHGUID_UNIT);
+ CreatureData& data = NewOrExistCreatureData(guid);
+ data.id = entry;
+ data.mapid = mapId;
+ data.displayid = 0;
+ data.equipmentId = cInfo->equipmentId;
+ data.posX = x;
+ data.posY = y;
+ data.posZ = z;
+ data.orientation = o;
+ data.spawntimesecs = spawntimedelay;
+ data.spawndist = 0;
+ data.currentwaypoint = 0;
+ data.curhealth = stats->GenerateHealth(cInfo);
+ data.curmana = stats->GenerateMana(cInfo);
+ data.is_dead = false;
+ data.movementType = cInfo->MovementType;
+ data.spawnMask = 1;
+ data.phaseMask = PHASEMASK_NORMAL;
+ data.dbData = false;
+
+ AddCreatureToGrid(guid, &data);
+
+ // Spawn if necessary (loaded grids only)
+ if (Map* map = const_cast<Map*>(MapManager::Instance().CreateBaseMap(mapId)))
+ {
+ // We use spawn coords to spawn
+ if (!map->Instanceable() && !map->IsRemovalGrid(x, y))
+ {
+ Creature* creature = new Creature;
+ if (!creature->LoadFromDB(guid, map))
+ {
+ sLog.outError("AddCreature: cannot add creature entry %u to map", entry);
+ delete creature;
+ return 0;
+ }
+ map->Add(creature);
+ }
+ }
+
+ return guid;
+}
+
+void ObjectMgr::LoadGameobjects()
+{
+ uint32 count = 0;
+
+ // 0 1 2 3 4 5 6
+ QueryResult_AutoPtr 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, event, pool_entry "
+ "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)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded 0 gameobjects. DB table `gameobject` is empty.");
+ return;
+ }
+
+ // build single time for check spawnmask
+ std::map<uint32,uint32> 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);
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 guid = fields[ 0].GetUInt32();
+ uint32 entry = fields[ 1].GetUInt32();
+
+ GameObjectInfo const* gInfo = GetGameObjectInfo(entry);
+ if (!gInfo)
+ {
+ sLog.outErrorDb("Table `gameobject` has gameobject (GUID: %u) with non existing gameobject entry %u, skipped.", guid, entry);
+ continue;
+ }
+
+ if (!gInfo->displayId)
+ {
+ switch (gInfo->type)
+ {
+ case GAMEOBJECT_TYPE_TRAP:
+ case GAMEOBJECT_TYPE_SPELL_FOCUS:
+ break;
+ default:
+ sLog.outErrorDb("Gameobject (GUID: %u Entry %u GoType: %u) doesn't have displayId (%u), not loaded.", guid, entry, gInfo->type, gInfo->displayId);
+ break;
+ }
+ }
+
+ if (gInfo->displayId && !sGameObjectDisplayInfoStore.LookupEntry(gInfo->displayId))
+ {
+ sLog.outErrorDb("Gameobject (GUID: %u Entry %u GoType: %u) have invalid displayId (%u), not loaded.",guid, entry, gInfo->type, gInfo->displayId);
+ continue;
+ }
+
+ GameObjectData& data = mGameObjectDataMap[guid];
+
+ data.id = entry;
+ data.mapid = fields[ 2].GetUInt32();
+ data.posX = fields[ 3].GetFloat();
+ data.posY = fields[ 4].GetFloat();
+ data.posZ = fields[ 5].GetFloat();
+ data.orientation = fields[ 6].GetFloat();
+ data.rotation0 = fields[ 7].GetFloat();
+ data.rotation1 = fields[ 8].GetFloat();
+ data.rotation2 = fields[ 9].GetFloat();
+ data.rotation3 = fields[10].GetFloat();
+ data.spawntimesecs = fields[11].GetInt32();
+
+ MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapid);
+ if (!mapEntry)
+ {
+ sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) that spawned at not existed map (Id: %u), skip", guid, data.id, data.mapid);
+ continue;
+ }
+
+ if (data.spawntimesecs == 0 && gInfo->IsDespawnAtAction())
+ {
+ sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) with `spawntimesecs` (0) value, but gameobejct marked as despawnable at action.",guid,data.id);
+ }
+
+ data.animprogress = fields[12].GetUInt32();
+ data.artKit = 0;
+
+ uint32 go_state = fields[13].GetUInt32();
+ if (go_state >= MAX_GO_STATE)
+ {
+ sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) with invalid `state` (%u) value, skip",guid,data.id,go_state);
+ continue;
+ }
+ data.go_state = GOState(go_state);
+
+ data.spawnMask = fields[14].GetUInt8();
+
+ if (data.spawnMask & ~spawnMasks[data.mapid])
+ sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) that have wrong spawn mask %u including not supported difficulty modes for map (Id: %u), skip", guid, data.id, data.spawnMask, data.mapid);
+
+ data.phaseMask = fields[15].GetUInt16();
+ int16 gameEvent = fields[16].GetInt16();
+ int16 PoolId = fields[17].GetInt16();
+
+ if (data.rotation2 < -1.0f || data.rotation2 > 1.0f)
+ {
+ sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) with invalid rotation2 (%f) value, skip",guid,data.id,data.rotation2);
+ continue;
+ }
+
+ if (data.rotation3 < -1.0f || data.rotation3 > 1.0f)
+ {
+ sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) with invalid rotation3 (%f) value, skip",guid,data.id,data.rotation3);
+ continue;
+ }
+
+ if (!MapManager::IsValidMapCoord(data.mapid,data.posX,data.posY,data.posZ,data.orientation))
+ {
+ sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) with invalid coordinates, skip",guid,data.id);
+ continue;
+ }
+
+ if (data.phaseMask == 0)
+ {
+ sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) with `phaseMask`=0 (not visible for anyone), set to 1.",guid,data.id);
+ data.phaseMask = 1;
+ }
+
+ if (gameEvent == 0 && PoolId == 0) // if not this is to be managed by GameEvent System or Pool system
+ AddGameobjectToGrid(guid, &data);
+ ++count;
+
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %lu gameobjects", (unsigned long)mGameObjectDataMap.size());
+}
+
+void ObjectMgr::AddGameobjectToGrid(uint32 guid, GameObjectData const* data)
+{
+ uint8 mask = data->spawnMask;
+ for (uint8 i = 0; mask != 0; i++, mask >>= 1)
+ {
+ if (mask & 1)
+ {
+ CellPair cell_pair = Trinity::ComputeCellPair(data->posX, data->posY);
+ uint32 cell_id = (cell_pair.y_coord*TOTAL_NUMBER_OF_CELLS_PER_MAP) + cell_pair.x_coord;
+
+ CellObjectGuids& cell_guids = mMapObjectGuids[MAKE_PAIR32(data->mapid,i)][cell_id];
+ cell_guids.gameobjects.insert(guid);
+ }
+ }
+}
+
+void ObjectMgr::RemoveGameobjectFromGrid(uint32 guid, GameObjectData const* data)
+{
+ uint8 mask = data->spawnMask;
+ for (uint8 i = 0; mask != 0; i++, mask >>= 1)
+ {
+ if (mask & 1)
+ {
+ CellPair cell_pair = Trinity::ComputeCellPair(data->posX, data->posY);
+ uint32 cell_id = (cell_pair.y_coord*TOTAL_NUMBER_OF_CELLS_PER_MAP) + cell_pair.x_coord;
+
+ CellObjectGuids& cell_guids = mMapObjectGuids[MAKE_PAIR32(data->mapid,i)][cell_id];
+ cell_guids.gameobjects.erase(guid);
+ }
+ }
+}
+
+void ObjectMgr::LoadCreatureRespawnTimes()
+{
+ uint32 count = 0;
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT guid,respawntime,instance FROM creature_respawn");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded 0 creature respawn time.");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 loguid = fields[0].GetUInt32();
+ uint64 respawn_time = fields[1].GetUInt64();
+ uint32 instance = fields[2].GetUInt32();
+
+ mCreatureRespawnTimes[MAKE_PAIR64(loguid,instance)] = time_t(respawn_time);
+
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %lu creature respawn times", (unsigned long)mCreatureRespawnTimes.size());
+}
+
+void ObjectMgr::LoadGameobjectRespawnTimes()
+{
+ // remove outdated data
+ WorldDatabase.DirectExecute("DELETE FROM gameobject_respawn WHERE respawntime <= UNIX_TIMESTAMP(NOW())");
+
+ uint32 count = 0;
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT guid,respawntime,instance FROM gameobject_respawn");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded 0 gameobject respawn time.");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 loguid = fields[0].GetUInt32();
+ uint64 respawn_time = fields[1].GetUInt64();
+ uint32 instance = fields[2].GetUInt32();
+
+ mGORespawnTimes[MAKE_PAIR64(loguid,instance)] = time_t(respawn_time);
+
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString(">> Loaded %lu gameobject respawn times", (unsigned long)mGORespawnTimes.size());
+ sLog.outString();
+}
+
+// name must be checked to correctness (if received) before call this function
+uint64 ObjectMgr::GetPlayerGUIDByName(std::string name) const
+{
+ uint64 guid = 0;
+
+ CharacterDatabase.escape_string(name);
+
+ // Player name safe to sending to DB (checked at login) and this function using
+ QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT guid FROM characters WHERE name = '%s'", name.c_str());
+ if (result)
+ guid = MAKE_NEW_GUID((*result)[0].GetUInt32(), 0, HIGHGUID_PLAYER);
+
+ return guid;
+}
+
+bool ObjectMgr::GetPlayerNameByGUID(const uint64 &guid, std::string &name) const
+{
+ // prevent DB access for online player
+ if (Player* player = GetPlayer(guid))
+ {
+ name = player->GetName();
+ return true;
+ }
+
+ QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT name FROM characters WHERE guid = '%u'", GUID_LOPART(guid));
+
+ if (result)
+ {
+ name = (*result)[0].GetCppString();
+ return true;
+ }
+
+ return false;
+}
+
+uint32 ObjectMgr::GetPlayerTeamByGUID(const uint64 &guid) const
+{
+ // prevent DB access for online player
+ if (Player* player = GetPlayer(guid))
+ {
+ return Player::TeamForRace(player->getRace());
+ }
+
+ QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT race FROM characters WHERE guid = '%u'", GUID_LOPART(guid));
+
+ if (result)
+ {
+ uint8 race = (*result)[0].GetUInt8();
+ return Player::TeamForRace(race);
+ }
+
+ return 0;
+}
+
+uint32 ObjectMgr::GetPlayerAccountIdByGUID(const uint64 &guid) const
+{
+ // prevent DB access for online player
+ if (Player* player = GetPlayer(guid))
+ {
+ return player->GetSession()->GetAccountId();
+ }
+
+ QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT account FROM characters WHERE guid = '%u'", GUID_LOPART(guid));
+ if (result)
+ {
+ uint32 acc = (*result)[0].GetUInt32();
+ return acc;
+ }
+
+ return 0;
+}
+
+uint32 ObjectMgr::GetPlayerAccountIdByPlayerName(const std::string& name) const
+{
+ QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT account FROM characters WHERE name = '%s'", name.c_str());
+ if (result)
+ {
+ uint32 acc = (*result)[0].GetUInt32();
+ return acc;
+ }
+
+ return 0;
+}
+
+void ObjectMgr::LoadItemLocales()
+{
+ mItemLocaleMap.clear(); // need for reload case
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,name_loc1,description_loc1,name_loc2,description_loc2,name_loc3,description_loc3,name_loc4,description_loc4,name_loc5,description_loc5,name_loc6,description_loc6,name_loc7,description_loc7,name_loc8,description_loc8 FROM locales_item");
+
+ if (!result)
+ return;
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 entry = fields[0].GetUInt32();
+
+ ItemLocale& data = mItemLocaleMap[entry];
+
+ for (uint8 i = 1; i < MAX_LOCALE; ++i)
+ {
+ std::string str = fields[1+2*(i-1)].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.Name.size() <= idx)
+ data.Name.resize(idx+1);
+
+ data.Name[idx] = str;
+ }
+ }
+
+ str = fields[1+2*(i-1)+1].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.Description.size() <= idx)
+ data.Description.resize(idx+1);
+
+ data.Description[idx] = str;
+ }
+ }
+ }
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %lu Item locale strings", (unsigned long)mItemLocaleMap.size());
+}
+
+struct SQLItemLoader : public SQLStorageLoaderBase<SQLItemLoader>
+{
+ template<class D>
+ void convert_from_str(uint32 /*field_pos*/, char *src, D &dst)
+ {
+ dst = D(objmgr.GetScriptId(src));
+ }
+};
+
+void ObjectMgr::LoadItemPrototypes()
+{
+ SQLItemLoader loader;
+ loader.Load(sItemStorage);
+ sLog.outString(">> Loaded %u item prototypes", sItemStorage.RecordCount);
+ sLog.outString();
+
+ // check data correctness
+ for (uint32 i = 1; i < sItemStorage.MaxEntry; ++i)
+ {
+ ItemPrototype const* proto = sItemStorage.LookupEntry<ItemPrototype >(i);
+ ItemEntry const *dbcitem = sItemStore.LookupEntry(i);
+ if (!proto)
+ {
+ /* to many errors, and possible not all items really used in game
+ if (dbcitem)
+ sLog.outErrorDb("Item (Entry: %u) doesn't exists in DB, but must exist.",i);
+ */
+ continue;
+ }
+
+ if (dbcitem)
+ {
+ if (proto->Class != dbcitem->Class)
+ {
+ sLog.outErrorDb("Item (Entry: %u) not correct class %u, must be %u (still using DB value).",i,proto->Class,dbcitem->Class);
+ // It safe let use Class from DB
+ }
+ /* disabled: have some strange wrong cases for Subclass values.
+ for enable also uncomment Subclass field in ItemEntry structure and in Itemfmt[]
+ if (proto->SubClass != dbcitem->SubClass)
+ {
+ sLog.outErrorDb("Item (Entry: %u) not correct (Class: %u, Sub: %u) pair, must be (Class: %u, Sub: %u) (still using DB value).",i,proto->Class,proto->SubClass,dbcitem->Class,dbcitem->SubClass);
+ // It safe let use Subclass from DB
+ }
+ */
+
+ if (proto->Unk0 != dbcitem->Unk0)
+ {
+ sLog.outErrorDb("Item (Entry: %u) not correct %i Unk0, must be %i (still using DB value).",i,proto->Unk0,dbcitem->Unk0);
+ // It safe let use Unk0 from DB
+ }
+
+ if (proto->Material != dbcitem->Material)
+ {
+ sLog.outErrorDb("Item (Entry: %u) not correct %i material, must be %i (still using DB value).",i,proto->Material,dbcitem->Material);
+ // It safe let use Material from DB
+ }
+
+ if (proto->InventoryType != dbcitem->InventoryType)
+ {
+ sLog.outErrorDb("Item (Entry: %u) not correct %u inventory type, must be %u (still using DB value).",i,proto->InventoryType,dbcitem->InventoryType);
+ // It safe let use InventoryType from DB
+ }
+
+ if (proto->DisplayInfoID != dbcitem->DisplayId)
+ {
+ sLog.outErrorDb("Item (Entry: %u) not correct %u display id, must be %u (using it).",i,proto->DisplayInfoID,dbcitem->DisplayId);
+ const_cast<ItemPrototype*>(proto)->DisplayInfoID = dbcitem->DisplayId;
+ }
+ if (proto->Sheath != dbcitem->Sheath)
+ {
+ sLog.outErrorDb("Item (Entry: %u) not correct %u sheath, must be %u (using it).",i,proto->Sheath,dbcitem->Sheath);
+ const_cast<ItemPrototype*>(proto)->Sheath = dbcitem->Sheath;
+ }
+ }
+ else
+ sLog.outErrorDb("Item (Entry: %u) not correct (not listed in list of existed items).",i);
+
+ if (proto->Class >= MAX_ITEM_CLASS)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong Class value (%u)",i,proto->Class);
+ const_cast<ItemPrototype*>(proto)->Class = ITEM_CLASS_MISC;
+ }
+
+ if (proto->SubClass >= MaxItemSubclassValues[proto->Class])
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong Subclass value (%u) for class %u",i,proto->SubClass,proto->Class);
+ const_cast<ItemPrototype*>(proto)->SubClass = 0;// exist for all item classes
+ }
+
+ if (proto->Quality >= MAX_ITEM_QUALITY)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong Quality value (%u)",i,proto->Quality);
+ const_cast<ItemPrototype*>(proto)->Quality = ITEM_QUALITY_NORMAL;
+ }
+
+ if (proto->BuyCount <= 0)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong BuyCount value (%u), set to default(1).",i,proto->BuyCount);
+ const_cast<ItemPrototype*>(proto)->BuyCount = 1;
+ }
+
+ if (proto->InventoryType >= MAX_INVTYPE)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong InventoryType value (%u)",i,proto->InventoryType);
+ const_cast<ItemPrototype*>(proto)->InventoryType = INVTYPE_NON_EQUIP;
+ }
+
+ if (proto->RequiredSkill >= MAX_SKILL_TYPE)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong RequiredSkill value (%u)",i,proto->RequiredSkill);
+ const_cast<ItemPrototype*>(proto)->RequiredSkill = 0;
+ }
+
+ {
+ // can be used in equip slot, as page read use in inventory, or spell casting at use
+ bool req = proto->InventoryType != INVTYPE_NON_EQUIP || proto->PageText;
+ if (!req)
+ for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; ++j)
+ {
+ if (proto->Spells[j].SpellId)
+ {
+ req = true;
+ break;
+ }
+ }
+
+ if (req)
+ {
+ if (!(proto->AllowableClass & CLASSMASK_ALL_PLAYABLE))
+ sLog.outErrorDb("Item (Entry: %u) not have in `AllowableClass` any playable classes (%u) and can't be equipped or use.",i,proto->AllowableClass);
+
+ if (!(proto->AllowableRace & RACEMASK_ALL_PLAYABLE))
+ sLog.outErrorDb("Item (Entry: %u) not have in `AllowableRace` any playable races (%u) and can't be equipped or use.",i,proto->AllowableRace);
+ }
+ }
+
+ if (proto->RequiredSpell && !sSpellStore.LookupEntry(proto->RequiredSpell))
+ {
+ sLog.outErrorDb("Item (Entry: %u) have wrong (non-existed) spell in RequiredSpell (%u)",i,proto->RequiredSpell);
+ const_cast<ItemPrototype*>(proto)->RequiredSpell = 0;
+ }
+
+ if (proto->RequiredReputationRank >= MAX_REPUTATION_RANK)
+ sLog.outErrorDb("Item (Entry: %u) has wrong reputation rank in RequiredReputationRank (%u), item can't be used.",i,proto->RequiredReputationRank);
+
+ if (proto->RequiredReputationFaction)
+ {
+ if (!sFactionStore.LookupEntry(proto->RequiredReputationFaction))
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong (not existing) faction in RequiredReputationFaction (%u)",i,proto->RequiredReputationFaction);
+ const_cast<ItemPrototype*>(proto)->RequiredReputationFaction = 0;
+ }
+
+ if (proto->RequiredReputationRank == MIN_REPUTATION_RANK)
+ sLog.outErrorDb("Item (Entry: %u) has min. reputation rank in RequiredReputationRank (0) but RequiredReputationFaction > 0, faction setting is useless.",i);
+ }
+
+ if (proto->MaxCount < -1)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has too large negative in maxcount (%i), replace by value (-1) no storing limits.",i,proto->MaxCount);
+ const_cast<ItemPrototype*>(proto)->MaxCount = -1;
+ }
+
+ if (proto->Stackable == 0)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong value in stackable (%i), replace by default 1.",i,proto->Stackable);
+ const_cast<ItemPrototype*>(proto)->Stackable = 1;
+ }
+ else if (proto->Stackable < -1)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has too large negative in stackable (%i), replace by value (-1) no stacking limits.",i,proto->Stackable);
+ const_cast<ItemPrototype*>(proto)->Stackable = -1;
+ }
+
+ if (proto->ContainerSlots > MAX_BAG_SIZE)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has too large value in ContainerSlots (%u), replace by hardcoded limit (%u).",i,proto->ContainerSlots,MAX_BAG_SIZE);
+ const_cast<ItemPrototype*>(proto)->ContainerSlots = MAX_BAG_SIZE;
+ }
+
+ if (proto->StatsCount > MAX_ITEM_PROTO_STATS)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has too large value in statscount (%u), replace by hardcoded limit (%u).",i,proto->StatsCount,MAX_ITEM_PROTO_STATS);
+ const_cast<ItemPrototype*>(proto)->StatsCount = MAX_ITEM_PROTO_STATS;
+ }
+
+ for (uint8 j = 0; j < MAX_ITEM_PROTO_STATS; ++j)
+ {
+ // for ItemStatValue != 0
+ if (proto->ItemStat[j].ItemStatValue && proto->ItemStat[j].ItemStatType >= MAX_ITEM_MOD)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong stat_type%d (%u)",i,j+1,proto->ItemStat[j].ItemStatType);
+ const_cast<ItemPrototype*>(proto)->ItemStat[j].ItemStatType = 0;
+ }
+
+ switch (proto->ItemStat[j].ItemStatType)
+ {
+ case ITEM_MOD_SPELL_HEALING_DONE:
+ case ITEM_MOD_SPELL_DAMAGE_DONE:
+ sLog.outErrorDb("Item (Entry: %u) has deprecated stat_type%d (%u)",i,j+1,proto->ItemStat[j].ItemStatType);
+ break;
+ default:
+ break;
+ }
+ }
+
+ for (uint8 j = 0; j < MAX_ITEM_PROTO_DAMAGES; ++j)
+ {
+ if (proto->Damage[j].DamageType >= MAX_SPELL_SCHOOL)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong dmg_type%d (%u)",i,j+1,proto->Damage[j].DamageType);
+ const_cast<ItemPrototype*>(proto)->Damage[j].DamageType = 0;
+ }
+ }
+
+ // special format
+ if ((proto->Spells[0].SpellId == 483) || (proto->Spells[0].SpellId == 55884))
+ {
+ // spell_1
+ if (proto->Spells[0].SpellTrigger != ITEM_SPELLTRIGGER_ON_USE)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong item spell trigger value in spelltrigger_%d (%u) for special learning format",i,0+1,proto->Spells[0].SpellTrigger);
+ const_cast<ItemPrototype*>(proto)->Spells[0].SpellId = 0;
+ const_cast<ItemPrototype*>(proto)->Spells[0].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
+ const_cast<ItemPrototype*>(proto)->Spells[1].SpellId = 0;
+ const_cast<ItemPrototype*>(proto)->Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
+ }
+
+ // spell_2 have learning spell
+ if (proto->Spells[1].SpellTrigger != ITEM_SPELLTRIGGER_LEARN_SPELL_ID)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong item spell trigger value in spelltrigger_%d (%u) for special learning format.",i,1+1,proto->Spells[1].SpellTrigger);
+ const_cast<ItemPrototype*>(proto)->Spells[0].SpellId = 0;
+ const_cast<ItemPrototype*>(proto)->Spells[1].SpellId = 0;
+ const_cast<ItemPrototype*>(proto)->Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
+ }
+ else if (!proto->Spells[1].SpellId)
+ {
+ sLog.outErrorDb("Item (Entry: %u) not has expected spell in spellid_%d in special learning format.",i,1+1);
+ const_cast<ItemPrototype*>(proto)->Spells[0].SpellId = 0;
+ const_cast<ItemPrototype*>(proto)->Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
+ }
+ else if (proto->Spells[1].SpellId != -1)
+ {
+ SpellEntry const* spellInfo = sSpellStore.LookupEntry(proto->Spells[1].SpellId);
+ if (!spellInfo)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong (not existing) spell in spellid_%d (%d)",i,1+1,proto->Spells[1].SpellId);
+ const_cast<ItemPrototype*>(proto)->Spells[0].SpellId = 0;
+ const_cast<ItemPrototype*>(proto)->Spells[1].SpellId = 0;
+ const_cast<ItemPrototype*>(proto)->Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
+ }
+ // allowed only in special format
+ else if ((proto->Spells[1].SpellId == 483) || (proto->Spells[1].SpellId == 55884))
+ {
+ sLog.outErrorDb("Item (Entry: %u) has broken spell in spellid_%d (%d)",i,1+1,proto->Spells[1].SpellId);
+ const_cast<ItemPrototype*>(proto)->Spells[0].SpellId = 0;
+ const_cast<ItemPrototype*>(proto)->Spells[1].SpellId = 0;
+ const_cast<ItemPrototype*>(proto)->Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
+ }
+ }
+
+ // spell_3*,spell_4*,spell_5* is empty
+ for (uint8 j = 2; j < MAX_ITEM_PROTO_SPELLS; ++j)
+ {
+ if (proto->Spells[j].SpellTrigger != ITEM_SPELLTRIGGER_ON_USE)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong item spell trigger value in spelltrigger_%d (%u)",i,j+1,proto->Spells[j].SpellTrigger);
+ const_cast<ItemPrototype*>(proto)->Spells[j].SpellId = 0;
+ const_cast<ItemPrototype*>(proto)->Spells[j].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
+ }
+ else if (proto->Spells[j].SpellId != 0)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong spell in spellid_%d (%d) for learning special format",i,j+1,proto->Spells[j].SpellId);
+ const_cast<ItemPrototype*>(proto)->Spells[j].SpellId = 0;
+ }
+ }
+ }
+ // normal spell list
+ else
+ {
+ for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; ++j)
+ {
+ if (proto->Spells[j].SpellTrigger >= MAX_ITEM_SPELLTRIGGER || proto->Spells[j].SpellTrigger == ITEM_SPELLTRIGGER_LEARN_SPELL_ID)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong item spell trigger value in spelltrigger_%d (%u)",i,j+1,proto->Spells[j].SpellTrigger);
+ const_cast<ItemPrototype*>(proto)->Spells[j].SpellId = 0;
+ const_cast<ItemPrototype*>(proto)->Spells[j].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE;
+ }
+
+ if (proto->Spells[j].SpellId && proto->Spells[j].SpellId != -1)
+ {
+ SpellEntry const* spellInfo = sSpellStore.LookupEntry(proto->Spells[j].SpellId);
+ if (!spellInfo)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong (not existing) spell in spellid_%d (%d)",i,j+1,proto->Spells[j].SpellId);
+ const_cast<ItemPrototype*>(proto)->Spells[j].SpellId = 0;
+ }
+ // allowed only in special format
+ else if ((proto->Spells[j].SpellId == 483) || (proto->Spells[j].SpellId == 55884))
+ {
+ sLog.outErrorDb("Item (Entry: %u) has broken spell in spellid_%d (%d)",i,j+1,proto->Spells[j].SpellId);
+ const_cast<ItemPrototype*>(proto)->Spells[j].SpellId = 0;
+ }
+ }
+ }
+ }
+
+ if (proto->Bonding >= MAX_BIND_TYPE)
+ sLog.outErrorDb("Item (Entry: %u) has wrong Bonding value (%u)",i,proto->Bonding);
+
+ if (proto->PageText && !sPageTextStore.LookupEntry<PageText>(proto->PageText))
+ sLog.outErrorDb("Item (Entry: %u) has non existing first page (Id:%u)", i,proto->PageText);
+
+ if (proto->LockID && !sLockStore.LookupEntry(proto->LockID))
+ sLog.outErrorDb("Item (Entry: %u) has wrong LockID (%u)",i,proto->LockID);
+
+ if (proto->Sheath >= MAX_SHEATHETYPE)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong Sheath (%u)",i,proto->Sheath);
+ const_cast<ItemPrototype*>(proto)->Sheath = SHEATHETYPE_NONE;
+ }
+
+ if (proto->RandomProperty && !sItemRandomPropertiesStore.LookupEntry(GetItemEnchantMod(proto->RandomProperty)))
+ {
+ sLog.outErrorDb("Item (Entry: %u) has unknown (wrong or not listed in `item_enchantment_template`) RandomProperty (%u)",i,proto->RandomProperty);
+ const_cast<ItemPrototype*>(proto)->RandomProperty = 0;
+ }
+
+ if (proto->RandomSuffix && !sItemRandomSuffixStore.LookupEntry(GetItemEnchantMod(proto->RandomSuffix)))
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong RandomSuffix (%u)",i,proto->RandomSuffix);
+ const_cast<ItemPrototype*>(proto)->RandomSuffix = 0;
+ }
+
+ if (proto->ItemSet && !sItemSetStore.LookupEntry(proto->ItemSet))
+ {
+ sLog.outErrorDb("Item (Entry: %u) have wrong ItemSet (%u)",i,proto->ItemSet);
+ const_cast<ItemPrototype*>(proto)->ItemSet = 0;
+ }
+
+ if (proto->Area && !GetAreaEntryByAreaID(proto->Area))
+ sLog.outErrorDb("Item (Entry: %u) has wrong Area (%u)",i,proto->Area);
+
+ if (proto->Map && !sMapStore.LookupEntry(proto->Map))
+ sLog.outErrorDb("Item (Entry: %u) has wrong Map (%u)",i,proto->Map);
+
+ if (proto->BagFamily)
+ {
+ // check bits
+ for (uint32 j = 0; j < sizeof(proto->BagFamily)*8; ++j)
+ {
+ uint32 mask = 1 << j;
+ if ((proto->BagFamily & mask) == 0)
+ continue;
+
+ ItemBagFamilyEntry const* bf = sItemBagFamilyStore.LookupEntry(j+1);
+ if (!bf)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has bag family bit set not listed in ItemBagFamily.dbc, remove bit",i);
+ const_cast<ItemPrototype*>(proto)->BagFamily &= ~mask;
+ continue;
+ }
+
+ if (BAG_FAMILY_MASK_CURRENCY_TOKENS & mask)
+ {
+ CurrencyTypesEntry const* ctEntry = sCurrencyTypesStore.LookupEntry(proto->ItemId);
+ if (!ctEntry)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has currency bag family bit set in BagFamily but not listed in CurrencyTypes.dbc, remove bit",i);
+ const_cast<ItemPrototype*>(proto)->BagFamily &= ~mask;
+ }
+ }
+ }
+ }
+
+ if (proto->TotemCategory && !sTotemCategoryStore.LookupEntry(proto->TotemCategory))
+ sLog.outErrorDb("Item (Entry: %u) has wrong TotemCategory (%u)",i,proto->TotemCategory);
+
+ for (uint8 j = 0; j < MAX_ITEM_PROTO_SOCKETS; ++j)
+ {
+ if (proto->Socket[j].Color && (proto->Socket[j].Color & SOCKET_COLOR_ALL) != proto->Socket[j].Color)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong socketColor_%d (%u)",i,j+1,proto->Socket[j].Color);
+ const_cast<ItemPrototype*>(proto)->Socket[j].Color = 0;
+ }
+ }
+
+ if (proto->GemProperties && !sGemPropertiesStore.LookupEntry(proto->GemProperties))
+ sLog.outErrorDb("Item (Entry: %u) has wrong GemProperties (%u)",i,proto->GemProperties);
+
+ if (proto->FoodType >= MAX_PET_DIET)
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong FoodType value (%u)",i,proto->FoodType);
+ const_cast<ItemPrototype*>(proto)->FoodType = 0;
+ }
+
+ if (proto->ItemLimitCategory && !sItemLimitCategoryStore.LookupEntry(proto->ItemLimitCategory))
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong LimitCategory value (%u)",i,proto->ItemLimitCategory);
+ const_cast<ItemPrototype*>(proto)->ItemLimitCategory = 0;
+ }
+
+ if (proto->HolidayId && !sHolidaysStore.LookupEntry(proto->HolidayId))
+ {
+ sLog.outErrorDb("Item (Entry: %u) has wrong HolidayId value (%u)", i, proto->HolidayId);
+ const_cast<ItemPrototype*>(proto)->HolidayId = 0;
+ }
+ }
+
+ // check some dbc referecned items (avoid duplicate reports)
+ std::set<uint32> notFoundOutfit;
+ for (uint32 i = 1; i < sCharStartOutfitStore.GetNumRows(); ++i)
+ {
+ CharStartOutfitEntry const* entry = sCharStartOutfitStore.LookupEntry(i);
+ if (!entry)
+ continue;
+
+ for (int j = 0; j < MAX_OUTFIT_ITEMS; ++j)
+ {
+ if (entry->ItemId[j] <= 0)
+ continue;
+
+ uint32 item_id = entry->ItemId[j];
+
+ if (!GetItemPrototype(item_id))
+ notFoundOutfit.insert(item_id);
+ }
+ }
+
+ for (std::set<uint32>::const_iterator itr = notFoundOutfit.begin(); itr != notFoundOutfit.end(); ++itr)
+ sLog.outErrorDb("Item (Entry: %u) not exist in `item_template` but referenced in `CharStartOutfit.dnc`", *itr);
+}
+
+void ObjectMgr::LoadVehicleAccessories()
+{
+ m_VehicleAccessoryMap.clear(); // needed for reload case
+
+ uint32 count = 0;
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT `entry`,`accessory_entry`,`seat_id`,`minion` FROM `vehicle_accessory`");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded 0 LoadVehicleAccessor. DB table `vehicle_accessory` is empty.");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 uiEntry = fields[0].GetUInt32();
+ uint32 uiAccessory = fields[1].GetUInt32();
+ int8 uiSeat = int8(fields[2].GetInt16());
+ bool bMinion = fields[3].GetBool();
+
+ if (!sCreatureStorage.LookupEntry<CreatureInfo>(uiEntry))
+ {
+ sLog.outErrorDb("Table `vehicle_accessory`: creature template entry %u does not exist.", uiEntry);
+ continue;
+ }
+
+ if (!sCreatureStorage.LookupEntry<CreatureInfo>(uiAccessory))
+ {
+ sLog.outErrorDb("Table `vehicle_accessory`: Accessory %u does not exist.", uiAccessory);
+ continue;
+ }
+
+ m_VehicleAccessoryMap[uiEntry].push_back(VehicleAccessory(uiAccessory, uiSeat, bMinion));
+
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u Vehicle Accessories", count);
+}
+
+void ObjectMgr::LoadPetLevelInfo()
+{
+ // Loading levels data
+ {
+ // 0 1 2 3 4 5 6 7 8 9
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT creature_entry, level, hp, mana, str, agi, sta, inte, spi, armor FROM pet_levelstats");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u level pet stats definitions", count);
+ sLog.outErrorDb("Error loading `pet_levelstats` table or empty table.");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 creature_id = fields[0].GetUInt32();
+ if (!sCreatureStorage.LookupEntry<CreatureInfo>(creature_id))
+ {
+ sLog.outErrorDb("Wrong creature id %u in `pet_levelstats` table, ignoring.",creature_id);
+ continue;
+ }
+
+ uint32 current_level = fields[1].GetUInt32();
+ if (current_level > sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL))
+ {
+ if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum
+ sLog.outErrorDb("Wrong (> %u) level %u in `pet_levelstats` table, ignoring.",STRONG_MAX_LEVEL,current_level);
+ else
+ {
+ sLog.outDetail("Unused (> MaxPlayerLevel in Trinityd.conf) level %u in `pet_levelstats` table, ignoring.",current_level);
+ ++count; // make result loading percent "expected" correct in case disabled detail mode for example.
+ }
+ continue;
+ }
+ else if (current_level < 1)
+ {
+ sLog.outErrorDb("Wrong (<1) level %u in `pet_levelstats` table, ignoring.",current_level);
+ continue;
+ }
+
+ PetLevelInfo*& pInfoMapEntry = petInfo[creature_id];
+
+ if (pInfoMapEntry == NULL)
+ pInfoMapEntry = new PetLevelInfo[sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)];
+
+ // data for level 1 stored in [0] array element, ...
+ PetLevelInfo* pLevelInfo = &pInfoMapEntry[current_level-1];
+
+ pLevelInfo->health = fields[2].GetUInt16();
+ pLevelInfo->mana = fields[3].GetUInt16();
+ pLevelInfo->armor = fields[9].GetUInt16();
+
+ for (int i = 0; i < MAX_STATS; i++)
+ {
+ pLevelInfo->stats[i] = fields[i+4].GetUInt16();
+ }
+
+ bar.step();
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u level pet stats definitions", count);
+ }
+
+ // Fill gaps and check integrity
+ for (PetLevelInfoMap::iterator itr = petInfo.begin(); itr != petInfo.end(); ++itr)
+ {
+ PetLevelInfo* pInfo = itr->second;
+
+ // fatal error if no level 1 data
+ if (!pInfo || pInfo[0].health == 0)
+ {
+ sLog.outErrorDb("Creature %u does not have pet stats data for Level 1!",itr->first);
+ exit(1);
+ }
+
+ // fill level gaps
+ for (uint8 level = 1; level < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
+ {
+ if (pInfo[level].health == 0)
+ {
+ sLog.outErrorDb("Creature %u has no data for Level %i pet stats data, using data of Level %i.",itr->first,level+1, level);
+ pInfo[level] = pInfo[level-1];
+ }
+ }
+ }
+}
+
+PetLevelInfo const* ObjectMgr::GetPetLevelInfo(uint32 creature_id, uint8 level) const
+{
+ if (level > sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL))
+ level = sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL);
+
+ PetLevelInfoMap::const_iterator itr = petInfo.find(creature_id);
+ if (itr == petInfo.end())
+ return NULL;
+
+ return &itr->second[level-1]; // data for level 1 stored in [0] array element, ...
+}
+
+void ObjectMgr::LoadPlayerInfo()
+{
+ // Load playercreate
+ {
+ // 0 1 2 3 4 5 6
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT race, class, map, zone, position_x, position_y, position_z FROM playercreateinfo");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u player create definitions", count);
+ sLog.outErrorDb("Error loading `playercreateinfo` table or empty table.");
+ exit(1);
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 current_race = fields[0].GetUInt32();
+ uint32 current_class = fields[1].GetUInt32();
+ uint32 mapId = fields[2].GetUInt32();
+ uint32 areaId = fields[3].GetUInt32();
+ float positionX = fields[4].GetFloat();
+ float positionY = fields[5].GetFloat();
+ float positionZ = fields[6].GetFloat();
+
+ if (current_race >= MAX_RACES)
+ {
+ sLog.outErrorDb("Wrong race %u in `playercreateinfo` table, ignoring.",current_race);
+ continue;
+ }
+
+ ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(current_race);
+ if (!rEntry)
+ {
+ sLog.outErrorDb("Wrong race %u in `playercreateinfo` table, ignoring.",current_race);
+ continue;
+ }
+
+ if (current_class >= MAX_CLASSES)
+ {
+ sLog.outErrorDb("Wrong class %u in `playercreateinfo` table, ignoring.",current_class);
+ continue;
+ }
+
+ if (!sChrClassesStore.LookupEntry(current_class))
+ {
+ sLog.outErrorDb("Wrong class %u in `playercreateinfo` table, ignoring.",current_class);
+ continue;
+ }
+
+ // accept DB data only for valid position (and non instanceable)
+ if (!MapManager::IsValidMapCoord(mapId,positionX,positionY,positionZ))
+ {
+ sLog.outErrorDb("Wrong home position for class %u race %u pair in `playercreateinfo` table, ignoring.",current_class,current_race);
+ continue;
+ }
+
+ if (sMapStore.LookupEntry(mapId)->Instanceable())
+ {
+ sLog.outErrorDb("Home position in instanceable map for class %u race %u pair in `playercreateinfo` table, ignoring.",current_class,current_race);
+ continue;
+ }
+
+ PlayerInfo* pInfo = &playerInfo[current_race][current_class];
+
+ pInfo->mapId = mapId;
+ pInfo->areaId = areaId;
+ pInfo->positionX = positionX;
+ pInfo->positionY = positionY;
+ pInfo->positionZ = positionZ;
+
+ pInfo->displayId_m = rEntry->model_m;
+ pInfo->displayId_f = rEntry->model_f;
+
+ bar.step();
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u player create definitions", count);
+ }
+
+ // Load playercreate items
+ sLog.outString("Loading Player Create Items Data...");
+ {
+ // 0 1 2 3
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT race, class, itemid, amount FROM playercreateinfo_item");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u custom player create items", count);
+ }
+ else
+ {
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 current_race = fields[0].GetUInt32();
+ if (current_race >= MAX_RACES)
+ {
+ sLog.outErrorDb("Wrong race %u in `playercreateinfo_item` table, ignoring.",current_race);
+ continue;
+ }
+
+ uint32 current_class = fields[1].GetUInt32();
+ if (current_class >= MAX_CLASSES)
+ {
+ sLog.outErrorDb("Wrong class %u in `playercreateinfo_item` table, ignoring.",current_class);
+ continue;
+ }
+
+ PlayerInfo* pInfo = &playerInfo[current_race][current_class];
+
+ uint32 item_id = fields[2].GetUInt32();
+
+ if (!GetItemPrototype(item_id))
+ {
+ sLog.outErrorDb("Item id %u (race %u class %u) in `playercreateinfo_item` table but not listed in `item_template`, ignoring.",item_id,current_race,current_class);
+ continue;
+ }
+
+ uint32 amount = fields[3].GetUInt32();
+
+ if (!amount)
+ {
+ sLog.outErrorDb("Item id %u (class %u race %u) have amount == 0 in `playercreateinfo_item` table, ignoring.",item_id,current_race,current_class);
+ continue;
+ }
+
+ pInfo->item.push_back(PlayerCreateInfoItem(item_id, amount));
+
+ bar.step();
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u custom player create items", count);
+ }
+ }
+
+ // Load playercreate spells
+ sLog.outString("Loading Player Create Spell Data...");
+ {
+
+ QueryResult_AutoPtr result = QueryResult_AutoPtr(NULL);
+ if (sWorld.getConfig(CONFIG_START_ALL_SPELLS))
+ result = WorldDatabase.Query("SELECT race, class, Spell, Active FROM playercreateinfo_spell_custom");
+ else
+ result = WorldDatabase.Query("SELECT race, class, Spell FROM playercreateinfo_spell");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u player create spells", count);
+ sLog.outErrorDb("Error loading player starting spells or empty table.");
+ }
+ else
+ {
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 current_race = fields[0].GetUInt32();
+ if (current_race >= MAX_RACES)
+ {
+ sLog.outErrorDb("Wrong race %u in `playercreateinfo_spell` table, ignoring.",current_race);
+ continue;
+ }
+
+ uint32 current_class = fields[1].GetUInt32();
+ if (current_class >= MAX_CLASSES)
+ {
+ sLog.outErrorDb("Wrong class %u in `playercreateinfo_spell` table, ignoring.",current_class);
+ continue;
+ }
+
+ if (!current_race || !current_class)
+ {
+ uint32 min_race = current_race ? current_race : 1;
+ uint32 max_race = current_race ? current_race + 1 : MAX_RACES;
+ uint32 min_class = current_class ? current_class : 1;
+ uint32 max_class = current_class ? current_class + 1 : MAX_CLASSES;
+ for (uint32 r = min_race; r < max_race; ++r)
+ for (uint32 c = min_class; c < max_class; ++c)
+ playerInfo[r][c].spell.push_back(fields[2].GetUInt32());
+ }
+ else
+ playerInfo[current_race][current_class].spell.push_back(fields[2].GetUInt32());
+
+ bar.step();
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u player create spells", count);
+ }
+ }
+
+ // Load playercreate actions
+ sLog.outString("Loading Player Create Action Data...");
+ {
+ // 0 1 2 3 4
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT race, class, button, action, type FROM playercreateinfo_action");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u player create actions", count);
+ sLog.outErrorDb("Error loading `playercreateinfo_action` table or empty table.");
+ }
+ else
+ {
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 current_race = fields[0].GetUInt32();
+ if (current_race >= MAX_RACES)
+ {
+ sLog.outErrorDb("Wrong race %u in `playercreateinfo_action` table, ignoring.",current_race);
+ continue;
+ }
+
+ uint32 current_class = fields[1].GetUInt32();
+ if (current_class >= MAX_CLASSES)
+ {
+ sLog.outErrorDb("Wrong class %u in `playercreateinfo_action` table, ignoring.",current_class);
+ continue;
+ }
+
+ PlayerInfo* pInfo = &playerInfo[current_race][current_class];
+ pInfo->action.push_back(PlayerCreateInfoAction(fields[2].GetUInt8(),fields[3].GetUInt32(),fields[4].GetUInt8()));
+
+ bar.step();
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u player create actions", count);
+ }
+ }
+
+ // Loading levels data (class only dependent)
+ sLog.outString("Loading Player Create Level HP/Mana Data...");
+ {
+ // 0 1 2 3
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT class, level, basehp, basemana FROM player_classlevelstats");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u level health/mana definitions", count);
+ sLog.outErrorDb("Error loading `player_classlevelstats` table or empty table.");
+ exit(1);
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 current_class = fields[0].GetUInt32();
+ if (current_class >= MAX_CLASSES)
+ {
+ sLog.outErrorDb("Wrong class %u in `player_classlevelstats` table, ignoring.",current_class);
+ continue;
+ }
+
+ uint8 current_level = fields[1].GetUInt8();
+ if (current_level > sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL))
+ {
+ if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum
+ sLog.outErrorDb("Wrong (> %u) level %u in `player_classlevelstats` table, ignoring.",STRONG_MAX_LEVEL,current_level);
+ else
+ {
+ sLog.outDetail("Unused (> MaxPlayerLevel in Trinityd.conf) level %u in `player_classlevelstats` table, ignoring.",current_level);
+ ++count; // make result loading percent "expected" correct in case disabled detail mode for example.
+ }
+ continue;
+ }
+
+ PlayerClassInfo* pClassInfo = &playerClassInfo[current_class];
+
+ if (!pClassInfo->levelInfo)
+ pClassInfo->levelInfo = new PlayerClassLevelInfo[sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)];
+
+ PlayerClassLevelInfo* pClassLevelInfo = &pClassInfo->levelInfo[current_level-1];
+
+ pClassLevelInfo->basehealth = fields[2].GetUInt16();
+ pClassLevelInfo->basemana = fields[3].GetUInt16();
+
+ bar.step();
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u level health/mana definitions", count);
+ }
+
+ // Fill gaps and check integrity
+ for (int class_ = 0; class_ < MAX_CLASSES; ++class_)
+ {
+ // skip non existed classes
+ if (!sChrClassesStore.LookupEntry(class_))
+ continue;
+
+ PlayerClassInfo* pClassInfo = &playerClassInfo[class_];
+
+ // fatal error if no level 1 data
+ if (!pClassInfo->levelInfo || pClassInfo->levelInfo[0].basehealth == 0)
+ {
+ sLog.outErrorDb("Class %i Level 1 does not have health/mana data!",class_);
+ exit(1);
+ }
+
+ // fill level gaps
+ for (uint8 level = 1; level < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
+ {
+ if (pClassInfo->levelInfo[level].basehealth == 0)
+ {
+ sLog.outErrorDb("Class %i Level %i does not have health/mana data. Using stats data of level %i.",class_,level+1, level);
+ pClassInfo->levelInfo[level] = pClassInfo->levelInfo[level-1];
+ }
+ }
+ }
+
+ // Loading levels data (class/race dependent)
+ sLog.outString("Loading Player Create Level Stats Data...");
+ {
+ // 0 1 2 3 4 5 6 7
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT race, class, level, str, agi, sta, inte, spi FROM player_levelstats");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u level stats definitions", count);
+ sLog.outErrorDb("Error loading `player_levelstats` table or empty table.");
+ exit(1);
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 current_race = fields[0].GetUInt32();
+ if (current_race >= MAX_RACES)
+ {
+ sLog.outErrorDb("Wrong race %u in `player_levelstats` table, ignoring.",current_race);
+ continue;
+ }
+
+ uint32 current_class = fields[1].GetUInt32();
+ if (current_class >= MAX_CLASSES)
+ {
+ sLog.outErrorDb("Wrong class %u in `player_levelstats` table, ignoring.",current_class);
+ continue;
+ }
+
+ uint32 current_level = fields[2].GetUInt32();
+ if (current_level > sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL))
+ {
+ if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum
+ sLog.outErrorDb("Wrong (> %u) level %u in `player_levelstats` table, ignoring.",STRONG_MAX_LEVEL,current_level);
+ else
+ {
+ sLog.outDetail("Unused (> MaxPlayerLevel in Trinityd.conf) level %u in `player_levelstats` table, ignoring.",current_level);
+ ++count; // make result loading percent "expected" correct in case disabled detail mode for example.
+ }
+ continue;
+ }
+
+ PlayerInfo* pInfo = &playerInfo[current_race][current_class];
+
+ if (!pInfo->levelInfo)
+ pInfo->levelInfo = new PlayerLevelInfo[sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)];
+
+ PlayerLevelInfo* pLevelInfo = &pInfo->levelInfo[current_level-1];
+
+ for (int i = 0; i < MAX_STATS; i++)
+ {
+ pLevelInfo->stats[i] = fields[i+3].GetUInt8();
+ }
+
+ bar.step();
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u level stats definitions", count);
+ }
+
+ // Fill gaps and check integrity
+ for (int race = 0; race < MAX_RACES; ++race)
+ {
+ // skip non existed races
+ if (!sChrRacesStore.LookupEntry(race))
+ continue;
+
+ for (int class_ = 0; class_ < MAX_CLASSES; ++class_)
+ {
+ // skip non existed classes
+ if (!sChrClassesStore.LookupEntry(class_))
+ continue;
+
+ PlayerInfo* pInfo = &playerInfo[race][class_];
+
+ // skip non loaded combinations
+ if (!pInfo->displayId_m || !pInfo->displayId_f)
+ continue;
+
+ // skip expansion races if not playing with expansion
+ if (sWorld.getConfig(CONFIG_EXPANSION) < 1 && (race == RACE_BLOODELF || race == RACE_DRAENEI))
+ continue;
+
+ // skip expansion classes if not playing with expansion
+ if (sWorld.getConfig(CONFIG_EXPANSION) < 2 && class_ == CLASS_DEATH_KNIGHT)
+ continue;
+
+ // fatal error if no level 1 data
+ if (!pInfo->levelInfo || pInfo->levelInfo[0].stats[0] == 0)
+ {
+ sLog.outErrorDb("Race %i Class %i Level 1 does not have stats data!",race,class_);
+ exit(1);
+ }
+
+ // fill level gaps
+ for (uint8 level = 1; level < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
+ {
+ if (pInfo->levelInfo[level].stats[0] == 0)
+ {
+ sLog.outErrorDb("Race %i Class %i Level %i does not have stats data. Using stats data of level %i.",race,class_,level+1, level);
+ pInfo->levelInfo[level] = pInfo->levelInfo[level-1];
+ }
+ }
+ }
+ }
+
+ // Loading xp per level data
+ sLog.outString("Loading Player Create XP Data...");
+ {
+ mPlayerXPperLevel.resize(sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL));
+ for (uint8 level = 0; level < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
+ mPlayerXPperLevel[level] = 0;
+
+ // 0 1
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT lvl, xp_for_next_level FROM player_xp_for_level");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u xp for level definitions", count);
+ sLog.outErrorDb("Error loading `player_xp_for_level` table or empty table.");
+ exit(1);
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 current_level = fields[0].GetUInt32();
+ uint32 current_xp = fields[1].GetUInt32();
+
+ if (current_level >= sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL))
+ {
+ if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum
+ sLog.outErrorDb("Wrong (> %u) level %u in `player_xp_for_level` table, ignoring.", STRONG_MAX_LEVEL,current_level);
+ else
+ {
+ sLog.outDetail("Unused (> MaxPlayerLevel in TrinityCore.conf) level %u in `player_xp_for_levels` table, ignoring.",current_level);
+ ++count; // make result loading percent "expected" correct in case disabled detail mode for example.
+ }
+ continue;
+ }
+ //PlayerXPperLevel
+ mPlayerXPperLevel[current_level] = current_xp;
+ bar.step();
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u xp for level definitions", count);
+ }
+
+ // fill level gaps
+ for (uint8 level = 1; level < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL); ++level)
+ {
+ if (mPlayerXPperLevel[level] == 0)
+ {
+ sLog.outErrorDb("Level %i does not have XP for level data. Using data of level [%i] + 100.",level+1, level);
+ mPlayerXPperLevel[level] = mPlayerXPperLevel[level-1]+100;
+ }
+ }
+}
+
+void ObjectMgr::GetPlayerClassLevelInfo(uint32 class_, uint8 level, PlayerClassLevelInfo* info) const
+{
+ if (level < 1 || class_ >= MAX_CLASSES)
+ return;
+
+ PlayerClassInfo const* pInfo = &playerClassInfo[class_];
+
+ if (level > sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL))
+ level = sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL);
+
+ *info = pInfo->levelInfo[level-1];
+}
+
+void ObjectMgr::GetPlayerLevelInfo(uint32 race, uint32 class_, uint8 level, PlayerLevelInfo* info) const
+{
+ if (level < 1 || race >= MAX_RACES || class_ >= MAX_CLASSES)
+ return;
+
+ PlayerInfo const* pInfo = &playerInfo[race][class_];
+ if (pInfo->displayId_m == 0 || pInfo->displayId_f == 0)
+ return;
+
+ if (level <= sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL))
+ *info = pInfo->levelInfo[level-1];
+ else
+ BuildPlayerLevelInfo(race,class_,level,info);
+}
+
+void ObjectMgr::BuildPlayerLevelInfo(uint8 race, uint8 _class, uint8 level, PlayerLevelInfo* info) const
+{
+ // base data (last known level)
+ *info = playerInfo[race][_class].levelInfo[sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)-1];
+
+ // if conversion from uint32 to uint8 causes unexpected behaviour, change lvl to uint32
+ for (uint8 lvl = sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)-1; lvl < level; ++lvl)
+ {
+ switch(_class)
+ {
+ case CLASS_WARRIOR:
+ info->stats[STAT_STRENGTH] += (lvl > 23 ? 2: (lvl > 1 ? 1: 0));
+ info->stats[STAT_STAMINA] += (lvl > 23 ? 2: (lvl > 1 ? 1: 0));
+ info->stats[STAT_AGILITY] += (lvl > 36 ? 1: (lvl > 6 && (lvl%2) ? 1: 0));
+ info->stats[STAT_INTELLECT] += (lvl > 9 && !(lvl%2) ? 1: 0);
+ info->stats[STAT_SPIRIT] += (lvl > 9 && !(lvl%2) ? 1: 0);
+ break;
+ case CLASS_PALADIN:
+ info->stats[STAT_STRENGTH] += (lvl > 3 ? 1: 0);
+ info->stats[STAT_STAMINA] += (lvl > 33 ? 2: (lvl > 1 ? 1: 0));
+ info->stats[STAT_AGILITY] += (lvl > 38 ? 1: (lvl > 7 && !(lvl%2) ? 1: 0));
+ info->stats[STAT_INTELLECT] += (lvl > 6 && (lvl%2) ? 1: 0);
+ info->stats[STAT_SPIRIT] += (lvl > 7 ? 1: 0);
+ break;
+ case CLASS_HUNTER:
+ info->stats[STAT_STRENGTH] += (lvl > 4 ? 1: 0);
+ info->stats[STAT_STAMINA] += (lvl > 4 ? 1: 0);
+ info->stats[STAT_AGILITY] += (lvl > 33 ? 2: (lvl > 1 ? 1: 0));
+ info->stats[STAT_INTELLECT] += (lvl > 8 && (lvl%2) ? 1: 0);
+ info->stats[STAT_SPIRIT] += (lvl > 38 ? 1: (lvl > 9 && !(lvl%2) ? 1: 0));
+ break;
+ case CLASS_ROGUE:
+ info->stats[STAT_STRENGTH] += (lvl > 5 ? 1: 0);
+ info->stats[STAT_STAMINA] += (lvl > 4 ? 1: 0);
+ info->stats[STAT_AGILITY] += (lvl > 16 ? 2: (lvl > 1 ? 1: 0));
+ info->stats[STAT_INTELLECT] += (lvl > 8 && !(lvl%2) ? 1: 0);
+ info->stats[STAT_SPIRIT] += (lvl > 38 ? 1: (lvl > 9 && !(lvl%2) ? 1: 0));
+ break;
+ case CLASS_PRIEST:
+ info->stats[STAT_STRENGTH] += (lvl > 9 && !(lvl%2) ? 1: 0);
+ info->stats[STAT_STAMINA] += (lvl > 5 ? 1: 0);
+ info->stats[STAT_AGILITY] += (lvl > 38 ? 1: (lvl > 8 && (lvl%2) ? 1: 0));
+ info->stats[STAT_INTELLECT] += (lvl > 22 ? 2: (lvl > 1 ? 1: 0));
+ info->stats[STAT_SPIRIT] += (lvl > 3 ? 1: 0);
+ break;
+ case CLASS_SHAMAN:
+ info->stats[STAT_STRENGTH] += (lvl > 34 ? 1: (lvl > 6 && (lvl%2) ? 1: 0));
+ info->stats[STAT_STAMINA] += (lvl > 4 ? 1: 0);
+ info->stats[STAT_AGILITY] += (lvl > 7 && !(lvl%2) ? 1: 0);
+ info->stats[STAT_INTELLECT] += (lvl > 5 ? 1: 0);
+ info->stats[STAT_SPIRIT] += (lvl > 4 ? 1: 0);
+ break;
+ case CLASS_MAGE:
+ info->stats[STAT_STRENGTH] += (lvl > 9 && !(lvl%2) ? 1: 0);
+ info->stats[STAT_STAMINA] += (lvl > 5 ? 1: 0);
+ info->stats[STAT_AGILITY] += (lvl > 9 && !(lvl%2) ? 1: 0);
+ info->stats[STAT_INTELLECT] += (lvl > 24 ? 2: (lvl > 1 ? 1: 0));
+ info->stats[STAT_SPIRIT] += (lvl > 33 ? 2: (lvl > 2 ? 1: 0));
+ break;
+ case CLASS_WARLOCK:
+ info->stats[STAT_STRENGTH] += (lvl > 9 && !(lvl%2) ? 1: 0);
+ info->stats[STAT_STAMINA] += (lvl > 38 ? 2: (lvl > 3 ? 1: 0));
+ info->stats[STAT_AGILITY] += (lvl > 9 && !(lvl%2) ? 1: 0);
+ info->stats[STAT_INTELLECT] += (lvl > 33 ? 2: (lvl > 2 ? 1: 0));
+ info->stats[STAT_SPIRIT] += (lvl > 38 ? 2: (lvl > 3 ? 1: 0));
+ break;
+ case CLASS_DRUID:
+ info->stats[STAT_STRENGTH] += (lvl > 38 ? 2: (lvl > 6 && (lvl%2) ? 1: 0));
+ info->stats[STAT_STAMINA] += (lvl > 32 ? 2: (lvl > 4 ? 1: 0));
+ info->stats[STAT_AGILITY] += (lvl > 38 ? 2: (lvl > 8 && (lvl%2) ? 1: 0));
+ info->stats[STAT_INTELLECT] += (lvl > 38 ? 3: (lvl > 4 ? 1: 0));
+ info->stats[STAT_SPIRIT] += (lvl > 38 ? 3: (lvl > 5 ? 1: 0));
+ }
+ }
+}
+
+void ObjectMgr::LoadGuilds()
+{
+ Guild *newGuild;
+ uint32 count = 0;
+
+ // 0 1 2 3 4 5 6
+ QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT guild.guildid,guild.name,leaderguid,EmblemStyle,EmblemColor,BorderStyle,BorderColor,"
+ // 7 8 9 10 11 12
+ "BackgroundColor,info,motd,createdate,BankMoney,COUNT(guild_bank_tab.guildid) "
+ "FROM guild LEFT JOIN guild_bank_tab ON guild.guildid = guild_bank_tab.guildid GROUP BY guild.guildid ORDER BY guildid ASC");
+
+ if (!result)
+ {
+
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u guild definitions", count);
+ return;
+ }
+
+ // load guild ranks
+ // 0 1 2 3 4
+ QueryResult_AutoPtr guildRanksResult = CharacterDatabase.Query("SELECT guildid,rid,rname,rights,BankMoneyPerDay FROM guild_rank ORDER BY guildid ASC, rid ASC");
+
+ // load guild members
+ // 0 1 2 3 4 5 6
+ QueryResult_AutoPtr guildMembersResult = CharacterDatabase.Query("SELECT guildid,guild_member.guid,rank,pnote,offnote,BankResetTimeMoney,BankRemMoney,"
+ // 7 8 9 10 11 12
+ "BankResetTimeTab0,BankRemSlotsTab0,BankResetTimeTab1,BankRemSlotsTab1,BankResetTimeTab2,BankRemSlotsTab2,"
+ // 13 14 15 16 17 18
+ "BankResetTimeTab3,BankRemSlotsTab3,BankResetTimeTab4,BankRemSlotsTab4,BankResetTimeTab5,BankRemSlotsTab5,"
+ // 19 20 21 22 23
+ "characters.name, characters.level, characters.class, characters.zone, characters.logout_time "
+ "FROM guild_member LEFT JOIN characters ON characters.guid = guild_member.guid ORDER BY guildid ASC");
+
+ // load guild bank tab rights
+ // 0 1 2 3 4
+ QueryResult_AutoPtr guildBankTabRightsResult = CharacterDatabase.Query("SELECT guildid,TabId,rid,gbright,SlotPerDay FROM guild_bank_right ORDER BY guildid ASC, TabId ASC");
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ //Field *fields = result->Fetch();
+
+ bar.step();
+ ++count;
+
+ newGuild = new Guild;
+ if (!newGuild->LoadGuildFromDB(result) ||
+ !newGuild->LoadRanksFromDB(guildRanksResult) ||
+ !newGuild->LoadMembersFromDB(guildMembersResult) ||
+ !newGuild->LoadBankRightsFromDB(guildBankTabRightsResult) ||
+ !newGuild->CheckGuildStructure()
+)
+ {
+ newGuild->Disband();
+ delete newGuild;
+ continue;
+ }
+ newGuild->LoadGuildEventLogFromDB();
+ newGuild->LoadGuildBankEventLogFromDB();
+ newGuild->LoadGuildBankFromDB();
+ AddGuild(newGuild);
+
+ } while (result->NextRow());
+
+ //delete unused LogGuid records in guild_eventlog and guild_bank_eventlog table
+ //you can comment these lines if you don't plan to change CONFIG_GUILD_EVENT_LOG_COUNT and CONFIG_GUILD_BANK_EVENT_LOG_COUNT
+ CharacterDatabase.PQuery("DELETE FROM guild_eventlog WHERE LogGuid > '%u'", sWorld.getConfig(CONFIG_GUILD_EVENT_LOG_COUNT));
+ CharacterDatabase.PQuery("DELETE FROM guild_bank_eventlog WHERE LogGuid > '%u'", sWorld.getConfig(CONFIG_GUILD_BANK_EVENT_LOG_COUNT));
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u guild definitions", count);
+}
+
+void ObjectMgr::LoadArenaTeams()
+{
+ uint32 count = 0;
+
+ // 0 1 2 3 4 5
+ QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT arena_team.arenateamid,name,captainguid,type,BackgroundColor,EmblemStyle,"
+ // 6 7 8 9 10 11 12 13 14
+ "EmblemColor,BorderStyle,BorderColor, rating,games,wins,played,wins2,rank "
+ "FROM arena_team LEFT JOIN arena_team_stats ON arena_team.arenateamid = arena_team_stats.arenateamid ORDER BY arena_team.arenateamid ASC");
+
+ if (!result)
+ {
+
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u arenateam definitions", count);
+ return;
+ }
+
+ // load arena_team members
+ QueryResult_AutoPtr arenaTeamMembersResult = CharacterDatabase.Query(
+ // 0 1 2 3 4 5 6 7 8
+ "SELECT arenateamid,member.guid,played_week,wons_week,played_season,wons_season,personal_rating,name,class "
+ "FROM arena_team_member member LEFT JOIN characters chars on member.guid = chars.guid ORDER BY member.arenateamid ASC");
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ //Field *fields = result->Fetch();
+
+ bar.step();
+ ++count;
+
+ ArenaTeam *newArenaTeam = new ArenaTeam;
+ if (!newArenaTeam->LoadArenaTeamFromDB(result) ||
+ !newArenaTeam->LoadMembersFromDB(arenaTeamMembersResult))
+ {
+ newArenaTeam->Disband(NULL);
+ delete newArenaTeam;
+ continue;
+ }
+ AddArenaTeam(newArenaTeam);
+ }while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u arenateam definitions", count);
+}
+
+void ObjectMgr::LoadGroups()
+{
+ Group *group = NULL;
+ Field *fields = NULL;
+ uint64 groupGuid = 0;
+ uint32 count = 0;
+
+ // Consistency cleaning before load to avoid having to do some checks later
+ // Delete all members that does not exist
+ CharacterDatabase.PExecute("DELETE FROM group_member WHERE NOT EXISTS (SELECT guid FROM characters WHERE guid=memberGuid)");
+ // Delete all groups whose leader does not exist
+ CharacterDatabase.PExecute("DELETE FROM groups WHERE NOT EXISTS (SELECT guid FROM characters WHERE guid=leaderGuid)");
+ // Delete all groups with less than 2 members
+ CharacterDatabase.PExecute("DELETE FROM groups WHERE guid NOT IN (SELECT guid FROM group_member GROUP BY guid HAVING COUNT(guid) > 1)");
+ // Delete all rows from group_member or group_instance with no group
+ CharacterDatabase.PExecute("DELETE FROM group_member WHERE guid NOT IN (SELECT guid FROM groups)");
+ CharacterDatabase.PExecute("DELETE FROM group_instance WHERE guid NOT IN (SELECT guid FROM groups)");
+
+ // ----------------------- Load Group definitions
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT leaderGuid, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raiddifficulty, guid FROM groups");
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded 0 group definitions");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+ do
+ {
+ bar.step();
+ fields = result->Fetch();
+ ++count;
+ group = new Group;
+ groupGuid = MAKE_NEW_GUID(fields[15].GetUInt32(),0,HIGHGUID_GROUP);
+ group->LoadGroupFromDB(groupGuid, result, false);
+ // group load will never be false (we have run consistency sql's before loading)
+ AddGroup(group);
+ }while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u group definitions", count);
+
+ // ----------------------- Load member
+ // 0 1 2 3
+ result = CharacterDatabase.Query("SELECT guid, memberGuid, memberFlags, subgroup FROM group_member ORDER BY guid");
+ if (!result)
+ {
+ barGoLink bar2(1);
+ bar2.step();
+ sLog.outString();
+ sLog.outString(">> Loaded 0 group members");
+ return;
+ }
+
+ barGoLink bar2(result->GetRowCount());
+ uint32 groupLowGuid = 0;
+ count = 0;
+ do
+ {
+ bar2.step();
+ fields = result->Fetch();
+
+ if (groupLowGuid != fields[0].GetUInt32())
+ {
+ groupLowGuid = fields[0].GetUInt32();
+ groupGuid = MAKE_NEW_GUID(groupLowGuid, 0, HIGHGUID_GROUP);
+ group = GetGroupByGUID(groupGuid);
+ // group will never be NULL (we have run consistency sql's before loading)
+ }
+ group->LoadMemberFromDB(fields[1].GetUInt32(), fields[2].GetUInt8(), fields[3].GetUInt8());
+ ++count;
+ }while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u group members", count);
+
+
+ // ----------------------- Load instance save
+ // 0 1 2 3 4 5
+ result = CharacterDatabase.Query("SELECT guid, map, instance, permanent, difficulty, resettime, "
+ // 6
+ "(SELECT COUNT(1) FROM groups JOIN character_instance ON leaderGuid = groups.guid WHERE instance = group_instance.instance AND permanent = 1 LIMIT 1) "
+ "FROM group_instance LEFT JOIN instance ON instance = id ORDER BY guid");
+
+ if (!result)
+ {
+ barGoLink bar2(1);
+ bar2.step();
+ sLog.outString();
+ sLog.outString(">> Loaded 0 group-instance saves");
+ return;
+ }
+
+ barGoLink bar3(result->GetRowCount());
+ count = 0;
+ do
+ {
+ bar3.step();
+ fields = result->Fetch();
+ groupGuid = MAKE_NEW_GUID(fields[0].GetUInt32(), 0, HIGHGUID_GROUP);
+ group = GetGroupByGUID(groupGuid);
+ // group will never be NULL (we have run consistency sql's before loading)
+
+ MapEntry const* mapEntry = sMapStore.LookupEntry(fields[1].GetUInt32());
+ if (!mapEntry || !mapEntry->IsDungeon())
+ {
+ sLog.outErrorDb("Incorrect entry in group_instance table : no dungeon map %d", fields[1].GetUInt32());
+ continue;
+ }
+
+ uint32 diff = fields[4].GetUInt8();
+ if (diff >= (mapEntry->IsRaid() ? MAX_RAID_DIFFICULTY : MAX_DUNGEON_DIFFICULTY))
+ {
+ sLog.outErrorDb("Wrong dungeon difficulty use in group_instance table: %d", diff + 1);
+ diff = 0; // default for both difficaly types
+ }
+
+ InstanceSave *save = sInstanceSaveManager.AddInstanceSave(mapEntry->MapID, fields[2].GetUInt32(), Difficulty(diff), time_t(fields[5].GetUInt64()), fields[6].GetBool(), true);
+ group->BindToInstance(save, fields[3].GetBool(), true);
+ ++count;
+ }while (result->NextRow());
+ sLog.outString();
+ sLog.outString(">> Loaded %u group-instance saves", count);
+}
+
+void ObjectMgr::LoadQuests()
+{
+ // For reload case
+ for (QuestMap::const_iterator itr=mQuestTemplates.begin(); itr != mQuestTemplates.end(); ++itr)
+ delete itr->second;
+ mQuestTemplates.clear();
+
+ mExclusiveQuestGroups.clear();
+
+ // 0 1 2 3 4 5 6 7 8 9
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, Method, ZoneOrSort, SkillOrClassMask, MinLevel, MaxLevel, QuestLevel, Type, RequiredRaces, RequiredSkillValue,"
+ // 10 11 12 13 14 15 16 17 18 19
+ "RepObjectiveFaction, RepObjectiveValue, RepObjectiveFaction2, RepObjectiveValue2, RequiredMinRepFaction, RequiredMinRepValue, RequiredMaxRepFaction, RequiredMaxRepValue, SuggestedPlayers, LimitTime,"
+ // 20 21 22 23 24 25 26 27 28 29 30 31 32 33
+ "QuestFlags, SpecialFlags, CharTitleId, PlayersSlain, BonusTalents, RewardArenaPoints, PrevQuestId, NextQuestId, ExclusiveGroup, NextQuestInChain, RewXPId, SrcItemId, SrcItemCount, SrcSpell,"
+ // 34 35 36 37 38 39 40 41 42 43 44
+ "Title, Details, Objectives, OfferRewardText, RequestItemsText, EndText, CompletedText, ObjectiveText1, ObjectiveText2, ObjectiveText3, ObjectiveText4,"
+ // 45 46 47 48 49 50 51 52 53 54 55 56
+ "ReqItemId1, ReqItemId2, ReqItemId3, ReqItemId4, ReqItemId5, ReqItemId6, ReqItemCount1, ReqItemCount2, ReqItemCount3, ReqItemCount4, ReqItemCount5, ReqItemCount6,"
+ // 57 58 59 60 61 62 63 64
+ "ReqSourceId1, ReqSourceId2, ReqSourceId3, ReqSourceId4, ReqSourceCount1, ReqSourceCount2, ReqSourceCount3, ReqSourceCount4,"
+ // 65 66 67 68 69 70 71 72
+ "ReqCreatureOrGOId1, ReqCreatureOrGOId2, ReqCreatureOrGOId3, ReqCreatureOrGOId4, ReqCreatureOrGOCount1, ReqCreatureOrGOCount2, ReqCreatureOrGOCount3, ReqCreatureOrGOCount4,"
+ // 73 74 75 76
+ "ReqSpellCast1, ReqSpellCast2, ReqSpellCast3, ReqSpellCast4,"
+ // 77 78 79 80 81 82
+ "RewChoiceItemId1, RewChoiceItemId2, RewChoiceItemId3, RewChoiceItemId4, RewChoiceItemId5, RewChoiceItemId6,"
+ // 83 84 85 86 87 88
+ "RewChoiceItemCount1, RewChoiceItemCount2, RewChoiceItemCount3, RewChoiceItemCount4, RewChoiceItemCount5, RewChoiceItemCount6,"
+ // 89 90 91 92 93 94 95 96
+ "RewItemId1, RewItemId2, RewItemId3, RewItemId4, RewItemCount1, RewItemCount2, RewItemCount3, RewItemCount4,"
+ // 97 98 99 100 101 102 103 104 105 106
+ "RewRepFaction1, RewRepFaction2, RewRepFaction3, RewRepFaction4, RewRepFaction5, RewRepValueId1, RewRepValueId2, RewRepValueId3, RewRepValueId4, RewRepValueId5,"
+ // 107 108 109 110 111
+ "RewRepValue1, RewRepValue2, RewRepValue3, RewRepValue4, RewRepValue5,"
+ // 112 113 114 115 116 117 118 119 120 121 122 123
+ "RewHonorAddition, RewHonorMultiplier, RewOrReqMoney, RewMoneyMaxLevel, RewSpell, RewSpellCast, RewMailTemplateId, RewMailDelaySecs, PointMapId, PointX, PointY, PointOpt,"
+ // 124 125 126 127 128 129 130 131
+ "DetailsEmote1, DetailsEmote2, DetailsEmote3, DetailsEmote4, DetailsEmoteDelay1, DetailsEmoteDelay2, DetailsEmoteDelay3, DetailsEmoteDelay4,"
+ // 132 133 134 135 136 137
+ "IncompleteEmote, CompleteEmote, OfferRewardEmote1, OfferRewardEmote2, OfferRewardEmote3, OfferRewardEmote4,"
+ // 138 139 140 141
+ "OfferRewardEmoteDelay1, OfferRewardEmoteDelay2, OfferRewardEmoteDelay3, OfferRewardEmoteDelay4,"
+ // 142 143
+ "StartScript, CompleteScript"
+ " FROM quest_template");
+ if (result == NULL)
+ {
+ barGoLink bar(1);
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded 0 quests definitions");
+ sLog.outErrorDb("`quest_template` table is empty!");
+ return;
+ }
+
+ // create multimap previous quest for each existed quest
+ // some quests can have many previous maps set by NextQuestId in previous quest
+ // for example set of race quests can lead to single not race specific quest
+ barGoLink bar(result->GetRowCount());
+ do
+ {
+ bar.step();
+ Field *fields = result->Fetch();
+
+ Quest * newQuest = new Quest(fields);
+ mQuestTemplates[newQuest->GetQuestId()] = newQuest;
+ } while (result->NextRow());
+
+ std::map<uint32,uint32> usedMailTemplates;
+
+ // Post processing
+ for (QuestMap::iterator iter = mQuestTemplates.begin(); iter != mQuestTemplates.end(); ++iter)
+ {
+ Quest * qinfo = iter->second;
+
+ // additional quest integrity checks (GO, creature_template and item_template must be loaded already)
+
+ if (qinfo->GetQuestMethod() >= 3)
+ {
+ sLog.outErrorDb("Quest %u has `Method` = %u, expected values are 0, 1 or 2.",qinfo->GetQuestId(),qinfo->GetQuestMethod());
+ }
+
+ if (qinfo->QuestFlags & ~QUEST_TRINITY_FLAGS_DB_ALLOWED)
+ {
+ sLog.outErrorDb("Quest %u has `SpecialFlags` = %u > max allowed value. Correct `SpecialFlags` to value <= %u",
+ qinfo->GetQuestId(),qinfo->QuestFlags >> 20, QUEST_TRINITY_FLAGS_DB_ALLOWED >> 20);
+ qinfo->QuestFlags &= QUEST_TRINITY_FLAGS_DB_ALLOWED;
+ }
+
+ if (qinfo->QuestFlags & QUEST_FLAGS_DAILY && qinfo->QuestFlags & QUEST_FLAGS_WEEKLY)
+ {
+ sLog.outErrorDb("Weekly Quest %u is marked as daily quest in `QuestFlags`, removed daily flag.",qinfo->GetQuestId());
+ qinfo->QuestFlags &= ~QUEST_FLAGS_DAILY;
+ }
+
+ if (qinfo->QuestFlags & QUEST_FLAGS_DAILY)
+ {
+ if (!(qinfo->QuestFlags & QUEST_TRINITY_FLAGS_REPEATABLE))
+ {
+ sLog.outErrorDb("Daily Quest %u not marked as repeatable in `SpecialFlags`, added.",qinfo->GetQuestId());
+ qinfo->QuestFlags |= QUEST_TRINITY_FLAGS_REPEATABLE;
+ }
+ }
+
+ if (qinfo->QuestFlags & QUEST_FLAGS_WEEKLY)
+ {
+ if (!(qinfo->QuestFlags & QUEST_TRINITY_FLAGS_REPEATABLE))
+ {
+ sLog.outErrorDb("Weekly Quest %u not marked as repeatable in `SpecialFlags`, added.",qinfo->GetQuestId());
+ qinfo->QuestFlags |= QUEST_TRINITY_FLAGS_REPEATABLE;
+ }
+ }
+
+ if (qinfo->QuestFlags & QUEST_FLAGS_AUTO_REWARDED)
+ {
+ // at auto-reward can be rewarded only RewChoiceItemId[0]
+ for (int j = 1; j < QUEST_REWARD_CHOICES_COUNT; ++j )
+ {
+ if (uint32 id = qinfo->RewChoiceItemId[j])
+ {
+ sLog.outErrorDb("Quest %u has `RewChoiceItemId%d` = %u but item from `RewChoiceItemId%d` can't be rewarded with quest flag QUEST_FLAGS_AUTO_REWARDED.",
+ qinfo->GetQuestId(),j+1,id,j+1);
+ // no changes, quest ignore this data
+ }
+ }
+ }
+
+ // client quest log visual (area case)
+ if (qinfo->ZoneOrSort > 0)
+ {
+ if (!GetAreaEntryByAreaID(qinfo->ZoneOrSort))
+ {
+ sLog.outErrorDb("Quest %u has `ZoneOrSort` = %u (zone case) but zone with this id does not exist.",
+ qinfo->GetQuestId(),qinfo->ZoneOrSort);
+ // no changes, quest not dependent from this value but can have problems at client
+ }
+ }
+ // client quest log visual (sort case)
+ if (qinfo->ZoneOrSort < 0)
+ {
+ QuestSortEntry const* qSort = sQuestSortStore.LookupEntry(-int32(qinfo->ZoneOrSort));
+ if (!qSort)
+ {
+ sLog.outErrorDb("Quest %u has `ZoneOrSort` = %i (sort case) but quest sort with this id does not exist.",
+ qinfo->GetQuestId(),qinfo->ZoneOrSort);
+ // no changes, quest not dependent from this value but can have problems at client (note some may be 0, we must allow this so no check)
+ }
+ //check SkillOrClass value (class case).
+ if (ClassByQuestSort(-int32(qinfo->ZoneOrSort)))
+ {
+ // SkillOrClass should not have class case when class case already set in ZoneOrSort.
+ if (qinfo->SkillOrClassMask < 0)
+ {
+ sLog.outErrorDb("Quest %u has `ZoneOrSort` = %i (class sort case) and `SkillOrClassMask` = %i (class case), redundant.",
+ qinfo->GetQuestId(),qinfo->ZoneOrSort,qinfo->SkillOrClassMask);
+ }
+ }
+ //check for proper SkillOrClass value (skill case)
+ if (int32 skill_id = SkillByQuestSort(-int32(qinfo->ZoneOrSort)))
+ {
+ // skill is positive value in SkillOrClass
+ if (qinfo->SkillOrClassMask != skill_id)
+ {
+ sLog.outErrorDb("Quest %u has `ZoneOrSort` = %i (skill sort case) but `SkillOrClassMask` does not have a corresponding value (%i).",
+ qinfo->GetQuestId(),qinfo->ZoneOrSort,skill_id);
+ //override, and force proper value here?
+ }
+ }
+ }
+
+ // SkillOrClassMask (class case)
+ if (qinfo->SkillOrClassMask < 0)
+ {
+ if (!(-int32(qinfo->SkillOrClassMask) & CLASSMASK_ALL_PLAYABLE))
+ {
+ sLog.outErrorDb("Quest %u has `SkillOrClassMask` = %i (class case) but classmask does not have valid class",
+ qinfo->GetQuestId(),qinfo->SkillOrClassMask);
+ }
+ }
+ // SkillOrClassMask (skill case)
+ if (qinfo->SkillOrClassMask > 0)
+ {
+ if (!sSkillLineStore.LookupEntry(qinfo->SkillOrClassMask))
+ {
+ sLog.outErrorDb("Quest %u has `SkillOrClass` = %u (skill case) but skill (%i) does not exist",
+ qinfo->GetQuestId(),qinfo->SkillOrClassMask,qinfo->SkillOrClassMask);
+ }
+ }
+
+ if (qinfo->RequiredSkillValue)
+ {
+ if (qinfo->RequiredSkillValue > sWorld.GetConfigMaxSkillValue())
+ {
+ sLog.outErrorDb("Quest %u has `RequiredSkillValue` = %u but max possible skill is %u, quest can't be done.",
+ qinfo->GetQuestId(),qinfo->RequiredSkillValue,sWorld.GetConfigMaxSkillValue());
+ // no changes, quest can't be done for this requirement
+ }
+
+ if (qinfo->SkillOrClassMask <= 0)
+ {
+ sLog.outErrorDb("Quest %u has `RequiredSkillValue` = %u but `SkillOrClass` = %i (class case), value ignored.",
+ qinfo->GetQuestId(),qinfo->RequiredSkillValue,qinfo->SkillOrClassMask);
+ // no changes, quest can't be done for this requirement (fail at wrong skill id)
+ }
+ }
+ // else Skill quests can have 0 skill level, this is ok
+
+ if (qinfo->RepObjectiveFaction2 && !sFactionStore.LookupEntry(qinfo->RepObjectiveFaction2))
+ {
+ sLog.outErrorDb("Quest %u has `RepObjectiveFaction2` = %u but faction template %u does not exist, quest can't be done.",
+ qinfo->GetQuestId(),qinfo->RepObjectiveFaction2,qinfo->RepObjectiveFaction2);
+ // no changes, quest can't be done for this requirement
+ }
+
+ if (qinfo->RepObjectiveFaction && !sFactionStore.LookupEntry(qinfo->RepObjectiveFaction))
+ {
+ sLog.outErrorDb("Quest %u has `RepObjectiveFaction` = %u but faction template %u does not exist, quest can't be done.",
+ qinfo->GetQuestId(),qinfo->RepObjectiveFaction,qinfo->RepObjectiveFaction);
+ // no changes, quest can't be done for this requirement
+ }
+
+ if (qinfo->RequiredMinRepFaction && !sFactionStore.LookupEntry(qinfo->RequiredMinRepFaction))
+ {
+ sLog.outErrorDb("Quest %u has `RequiredMinRepFaction` = %u but faction template %u does not exist, quest can't be done.",
+ qinfo->GetQuestId(),qinfo->RequiredMinRepFaction,qinfo->RequiredMinRepFaction);
+ // no changes, quest can't be done for this requirement
+ }
+
+ if (qinfo->RequiredMaxRepFaction && !sFactionStore.LookupEntry(qinfo->RequiredMaxRepFaction))
+ {
+ sLog.outErrorDb("Quest %u has `RequiredMaxRepFaction` = %u but faction template %u does not exist, quest can't be done.",
+ qinfo->GetQuestId(),qinfo->RequiredMaxRepFaction,qinfo->RequiredMaxRepFaction);
+ // no changes, quest can't be done for this requirement
+ }
+
+ if (qinfo->RequiredMinRepValue && qinfo->RequiredMinRepValue > ReputationMgr::Reputation_Cap)
+ {
+ sLog.outErrorDb("Quest %u has `RequiredMinRepValue` = %d but max reputation is %u, quest can't be done.",
+ qinfo->GetQuestId(),qinfo->RequiredMinRepValue,ReputationMgr::Reputation_Cap);
+ // no changes, quest can't be done for this requirement
+ }
+
+ if (qinfo->RequiredMinRepValue && qinfo->RequiredMaxRepValue && qinfo->RequiredMaxRepValue <= qinfo->RequiredMinRepValue)
+ {
+ sLog.outErrorDb("Quest %u has `RequiredMaxRepValue` = %d and `RequiredMinRepValue` = %d, quest can't be done.",
+ qinfo->GetQuestId(),qinfo->RequiredMaxRepValue,qinfo->RequiredMinRepValue);
+ // no changes, quest can't be done for this requirement
+ }
+
+ if (!qinfo->RepObjectiveFaction && qinfo->RepObjectiveValue > 0)
+ {
+ sLog.outErrorDb("Quest %u has `RepObjectiveValue` = %d but `RepObjectiveFaction` is 0, value has no effect",
+ qinfo->GetQuestId(),qinfo->RepObjectiveValue);
+ // warning
+ }
+
+ if (!qinfo->RepObjectiveFaction2 && qinfo->RepObjectiveValue2 > 0)
+ {
+ sLog.outErrorDb("Quest %u has `RepObjectiveValue2` = %d but `RepObjectiveFaction2` is 0, value has no effect",
+ qinfo->GetQuestId(),qinfo->RepObjectiveValue2);
+ // warning
+ }
+
+ if (!qinfo->RequiredMinRepFaction && qinfo->RequiredMinRepValue > 0)
+ {
+ sLog.outErrorDb("Quest %u has `RequiredMinRepValue` = %d but `RequiredMinRepFaction` is 0, value has no effect",
+ qinfo->GetQuestId(),qinfo->RequiredMinRepValue);
+ // warning
+ }
+
+ if (!qinfo->RequiredMaxRepFaction && qinfo->RequiredMaxRepValue > 0)
+ {
+ sLog.outErrorDb("Quest %u has `RequiredMaxRepValue` = %d but `RequiredMaxRepFaction` is 0, value has no effect",
+ qinfo->GetQuestId(),qinfo->RequiredMaxRepValue);
+ // warning
+ }
+
+ if (qinfo->CharTitleId && !sCharTitlesStore.LookupEntry(qinfo->CharTitleId))
+ {
+ sLog.outErrorDb("Quest %u has `CharTitleId` = %u but CharTitle Id %u does not exist, quest can't be rewarded with title.",
+ qinfo->GetQuestId(),qinfo->GetCharTitleId(),qinfo->GetCharTitleId());
+ qinfo->CharTitleId = 0;
+ // quest can't reward this title
+ }
+
+ if (qinfo->SrcItemId)
+ {
+ if (!sItemStorage.LookupEntry<ItemPrototype>(qinfo->SrcItemId))
+ {
+ sLog.outErrorDb("Quest %u has `SrcItemId` = %u but item with entry %u does not exist, quest can't be done.",
+ qinfo->GetQuestId(),qinfo->SrcItemId,qinfo->SrcItemId);
+ qinfo->SrcItemId = 0; // quest can't be done for this requirement
+ }
+ else if (qinfo->SrcItemCount == 0)
+ {
+ sLog.outErrorDb("Quest %u has `SrcItemId` = %u but `SrcItemCount` = 0, set to 1 but need fix in DB.",
+ qinfo->GetQuestId(),qinfo->SrcItemId);
+ qinfo->SrcItemCount = 1; // update to 1 for allow quest work for backward compatibility with DB
+ }
+ }
+ else if (qinfo->SrcItemCount>0)
+ {
+ sLog.outErrorDb("Quest %u has `SrcItemId` = 0 but `SrcItemCount` = %u, useless value.",
+ qinfo->GetQuestId(),qinfo->SrcItemCount);
+ qinfo->SrcItemCount=0; // no quest work changes in fact
+ }
+
+ if (qinfo->SrcSpell)
+ {
+ SpellEntry const* spellInfo = sSpellStore.LookupEntry(qinfo->SrcSpell);
+ if (!spellInfo)
+ {
+ sLog.outErrorDb("Quest %u has `SrcSpell` = %u but spell %u doesn't exist, quest can't be done.",
+ qinfo->GetQuestId(),qinfo->SrcSpell,qinfo->SrcSpell);
+ qinfo->SrcSpell = 0; // quest can't be done for this requirement
+ }
+ else if (!SpellMgr::IsSpellValid(spellInfo))
+ {
+ sLog.outErrorDb("Quest %u has `SrcSpell` = %u but spell %u is broken, quest can't be done.",
+ qinfo->GetQuestId(),qinfo->SrcSpell,qinfo->SrcSpell);
+ qinfo->SrcSpell = 0; // quest can't be done for this requirement
+ }
+ }
+
+ for (uint8 j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j)
+ {
+ uint32 id = qinfo->ReqItemId[j];
+ if (id)
+ {
+ if (qinfo->ReqItemCount[j] == 0)
+ {
+ sLog.outErrorDb("Quest %u has `ReqItemId%d` = %u but `ReqItemCount%d` = 0, quest can't be done.",
+ qinfo->GetQuestId(),j+1,id,j+1);
+ // no changes, quest can't be done for this requirement
+ }
+
+ qinfo->SetFlag(QUEST_TRINITY_FLAGS_DELIVER);
+
+ if (!sItemStorage.LookupEntry<ItemPrototype>(id))
+ {
+ sLog.outErrorDb("Quest %u has `ReqItemId%d` = %u but item with entry %u does not exist, quest can't be done.",
+ qinfo->GetQuestId(),j+1,id,id);
+ qinfo->ReqItemCount[j] = 0; // prevent incorrect work of quest
+ }
+ }
+ else if (qinfo->ReqItemCount[j]>0)
+ {
+ sLog.outErrorDb("Quest %u has `ReqItemId%d` = 0 but `ReqItemCount%d` = %u, quest can't be done.",
+ qinfo->GetQuestId(),j+1,j+1,qinfo->ReqItemCount[j]);
+ qinfo->ReqItemCount[j] = 0; // prevent incorrect work of quest
+ }
+ }
+
+ for (uint8 j = 0; j < QUEST_SOURCE_ITEM_IDS_COUNT; ++j)
+ {
+ uint32 id = qinfo->ReqSourceId[j];
+ if (id)
+ {
+ if (!sItemStorage.LookupEntry<ItemPrototype>(id))
+ {
+ sLog.outErrorDb("Quest %u has `ReqSourceId%d` = %u but item with entry %u does not exist, quest can't be done.",
+ qinfo->GetQuestId(),j+1,id,id);
+ // no changes, quest can't be done for this requirement
+ }
+ }
+ else
+ {
+ if (qinfo->ReqSourceCount[j]>0)
+ {
+ sLog.outErrorDb("Quest %u has `ReqSourceId%d` = 0 but `ReqSourceCount%d` = %u.",
+ qinfo->GetQuestId(),j+1,j+1,qinfo->ReqSourceCount[j]);
+ // no changes, quest ignore this data
+ }
+ }
+ }
+
+ for (uint8 j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
+ {
+ uint32 id = qinfo->ReqSpell[j];
+ if (id)
+ {
+ SpellEntry const* spellInfo = sSpellStore.LookupEntry(id);
+ if (!spellInfo)
+ {
+ sLog.outErrorDb("Quest %u has `ReqSpellCast%d` = %u but spell %u does not exist, quest can't be done.",
+ qinfo->GetQuestId(),j+1,id,id);
+ continue;
+ }
+
+ if (!qinfo->ReqCreatureOrGOId[j])
+ {
+ bool found = false;
+ for (uint8 k = 0; k < 3; ++k)
+ {
+ if ((spellInfo->Effect[k] == SPELL_EFFECT_QUEST_COMPLETE && uint32(spellInfo->EffectMiscValue[k]) == qinfo->QuestId) ||
+ spellInfo->Effect[k] == SPELL_EFFECT_SEND_EVENT)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (found)
+ {
+ if (!qinfo->HasFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT))
+ {
+ sLog.outErrorDb("Spell (id: %u) have SPELL_EFFECT_QUEST_COMPLETE or SPELL_EFFECT_SEND_EVENT for quest %u and ReqCreatureOrGOId%d = 0, but quest not have flag QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT. Quest flags or ReqCreatureOrGOId%d must be fixed, quest modified to enable objective.",spellInfo->Id,qinfo->QuestId,j+1,j+1);
+
+ // this will prevent quest completing without objective
+ const_cast<Quest*>(qinfo)->SetFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT);
+ }
+ }
+ else
+ {
+ sLog.outErrorDb("Quest %u has `ReqSpellCast%d` = %u and ReqCreatureOrGOId%d = 0 but spell %u does not have SPELL_EFFECT_QUEST_COMPLETE or SPELL_EFFECT_SEND_EVENT effect for this quest, quest can't be done.",
+ qinfo->GetQuestId(),j+1,id,j+1,id);
+ // no changes, quest can't be done for this requirement
+ }
+ }
+ }
+ }
+
+ for (uint8 j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
+ {
+ int32 id = qinfo->ReqCreatureOrGOId[j];
+ if (id < 0 && !sGOStorage.LookupEntry<GameObjectInfo>(-id))
+ {
+ sLog.outErrorDb("Quest %u has `ReqCreatureOrGOId%d` = %i but gameobject %u does not exist, quest can't be done.",
+ qinfo->GetQuestId(),j+1,id,uint32(-id));
+ qinfo->ReqCreatureOrGOId[j] = 0; // quest can't be done for this requirement
+ }
+
+ if (id > 0 && !sCreatureStorage.LookupEntry<CreatureInfo>(id))
+ {
+ sLog.outErrorDb("Quest %u has `ReqCreatureOrGOId%d` = %i but creature with entry %u does not exist, quest can't be done.",
+ qinfo->GetQuestId(),j+1,id,uint32(id));
+ qinfo->ReqCreatureOrGOId[j] = 0; // quest can't be done for this requirement
+ }
+
+ if (id)
+ {
+ // In fact SpeakTo and Kill are quite same: either you can speak to mob:SpeakTo or you can't:Kill/Cast
+
+ qinfo->SetFlag(QUEST_TRINITY_FLAGS_KILL_OR_CAST | QUEST_TRINITY_FLAGS_SPEAKTO);
+
+ if (!qinfo->ReqCreatureOrGOCount[j])
+ {
+ sLog.outErrorDb("Quest %u has `ReqCreatureOrGOId%d` = %u but `ReqCreatureOrGOCount%d` = 0, quest can't be done.",
+ qinfo->GetQuestId(),j+1,id,j+1);
+ // no changes, quest can be incorrectly done, but we already report this
+ }
+ }
+ else if (qinfo->ReqCreatureOrGOCount[j]>0)
+ {
+ sLog.outErrorDb("Quest %u has `ReqCreatureOrGOId%d` = 0 but `ReqCreatureOrGOCount%d` = %u.",
+ qinfo->GetQuestId(),j+1,j+1,qinfo->ReqCreatureOrGOCount[j]);
+ // no changes, quest ignore this data
+ }
+ }
+
+ for (uint8 j = 0; j < QUEST_REWARD_CHOICES_COUNT; ++j)
+ {
+ uint32 id = qinfo->RewChoiceItemId[j];
+ if (id)
+ {
+ if (!sItemStorage.LookupEntry<ItemPrototype>(id))
+ {
+ sLog.outErrorDb("Quest %u has `RewChoiceItemId%d` = %u but item with entry %u does not exist, quest will not reward this item.",
+ qinfo->GetQuestId(),j+1,id,id);
+ qinfo->RewChoiceItemId[j] = 0; // no changes, quest will not reward this
+ }
+
+ if (!qinfo->RewChoiceItemCount[j])
+ {
+ sLog.outErrorDb("Quest %u has `RewChoiceItemId%d` = %u but `RewChoiceItemCount%d` = 0, quest can't be done.",
+ qinfo->GetQuestId(),j+1,id,j+1);
+ // no changes, quest can't be done
+ }
+ }
+ else if (qinfo->RewChoiceItemCount[j]>0)
+ {
+ sLog.outErrorDb("Quest %u has `RewChoiceItemId%d` = 0 but `RewChoiceItemCount%d` = %u.",
+ qinfo->GetQuestId(),j+1,j+1,qinfo->RewChoiceItemCount[j]);
+ // no changes, quest ignore this data
+ }
+ }
+
+ for (uint8 j = 0; j < QUEST_REWARDS_COUNT; ++j)
+ {
+ uint32 id = qinfo->RewItemId[j];
+ if (id)
+ {
+ if (!sItemStorage.LookupEntry<ItemPrototype>(id))
+ {
+ sLog.outErrorDb("Quest %u has `RewItemId%d` = %u but item with entry %u does not exist, quest will not reward this item.",
+ qinfo->GetQuestId(),j+1,id,id);
+ qinfo->RewItemId[j] = 0; // no changes, quest will not reward this item
+ }
+
+ if (!qinfo->RewItemCount[j])
+ {
+ sLog.outErrorDb("Quest %u has `RewItemId%d` = %u but `RewItemCount%d` = 0, quest will not reward this item.",
+ qinfo->GetQuestId(),j+1,id,j+1);
+ // no changes
+ }
+ }
+ else if (qinfo->RewItemCount[j]>0)
+ {
+ sLog.outErrorDb("Quest %u has `RewItemId%d` = 0 but `RewItemCount%d` = %u.",
+ qinfo->GetQuestId(),j+1,j+1,qinfo->RewItemCount[j]);
+ // no changes, quest ignore this data
+ }
+ }
+
+ for (uint8 j = 0; j < QUEST_REPUTATIONS_COUNT; ++j)
+ {
+ if (qinfo->RewRepFaction[j])
+ {
+ if (abs(qinfo->RewRepValueId[j]) > 9)
+ {
+ sLog.outErrorDb("Quest %u has RewRepValueId%d = %i. That is outside the range of valid values (-9 to 9).", qinfo->GetQuestId(), j+1, qinfo->RewRepValueId[j]);
+ }
+ if (!sFactionStore.LookupEntry(qinfo->RewRepFaction[j]))
+ {
+ sLog.outErrorDb("Quest %u has `RewRepFaction%d` = %u but raw faction (faction.dbc) %u does not exist, quest will not reward reputation for this faction.", qinfo->GetQuestId(),j+1,qinfo->RewRepFaction[j] ,qinfo->RewRepFaction[j]);
+ qinfo->RewRepFaction[j] = 0; // quest will not reward this
+ }
+ }
+
+
+ else if (qinfo->RewRepValue[j] != 0)
+ {
+ sLog.outErrorDb("Quest %u has `RewRepFaction%d` = 0 but `RewRepValue%d` = %i.",
+ qinfo->GetQuestId(),j+1,j+1,qinfo->RewRepValue[j]);
+ // no changes, quest ignore this data
+ }
+ }
+
+
+ if (qinfo->RewSpell)
+ {
+ SpellEntry const* spellInfo = sSpellStore.LookupEntry(qinfo->RewSpell);
+
+ if (!spellInfo)
+ {
+ sLog.outErrorDb("Quest %u has `RewSpell` = %u but spell %u does not exist, spell removed as display reward.",
+ qinfo->GetQuestId(),qinfo->RewSpell,qinfo->RewSpell);
+ qinfo->RewSpell = 0; // no spell reward will display for this quest
+ }
+
+ else if (!SpellMgr::IsSpellValid(spellInfo))
+ {
+ sLog.outErrorDb("Quest %u has `RewSpell` = %u but spell %u is broken, quest will not have a spell reward.",
+ qinfo->GetQuestId(),qinfo->RewSpell,qinfo->RewSpell);
+ qinfo->RewSpell = 0; // no spell reward will display for this quest
+ }
+
+ else if (GetTalentSpellCost(qinfo->RewSpell))
+ {
+ sLog.outErrorDb("Quest %u has `RewSpell` = %u but spell %u is talent, quest will not have a spell reward.",
+ qinfo->GetQuestId(),qinfo->RewSpell,qinfo->RewSpell);
+ qinfo->RewSpell = 0; // no spell reward will display for this quest
+ }
+ }
+
+ if (qinfo->RewSpellCast > 0)
+ {
+ SpellEntry const* spellInfo = sSpellStore.LookupEntry(qinfo->RewSpellCast);
+
+ if (!spellInfo)
+ {
+ sLog.outErrorDb("Quest %u has `RewSpellCast` = %u but spell %u does not exist, quest will not have a spell reward.",
+ qinfo->GetQuestId(),qinfo->RewSpellCast,qinfo->RewSpellCast);
+ qinfo->RewSpellCast = 0; // no spell will be casted on player
+ }
+
+ else if (!SpellMgr::IsSpellValid(spellInfo))
+ {
+ sLog.outErrorDb("Quest %u has `RewSpellCast` = %u but spell %u is broken, quest will not have a spell reward.",
+ qinfo->GetQuestId(),qinfo->RewSpellCast,qinfo->RewSpellCast);
+ qinfo->RewSpellCast = 0; // no spell will be casted on player
+ }
+
+ else if (GetTalentSpellCost(qinfo->RewSpellCast))
+ {
+ sLog.outErrorDb("Quest %u has `RewSpell` = %u but spell %u is talent, quest will not have a spell reward.",
+ qinfo->GetQuestId(),qinfo->RewSpellCast,qinfo->RewSpellCast);
+ qinfo->RewSpellCast = 0; // no spell will be casted on player
+ }
+ }
+
+ if (qinfo->RewMailTemplateId)
+ {
+ if (!sMailTemplateStore.LookupEntry(qinfo->RewMailTemplateId))
+ {
+ sLog.outErrorDb("Quest %u has `RewMailTemplateId` = %u but mail template %u does not exist, quest will not have a mail reward.",
+ qinfo->GetQuestId(),qinfo->RewMailTemplateId,qinfo->RewMailTemplateId);
+ qinfo->RewMailTemplateId = 0; // no mail will send to player
+ qinfo->RewMailDelaySecs = 0; // no mail will send to player
+ }
+ else if (usedMailTemplates.find(qinfo->RewMailTemplateId) != usedMailTemplates.end())
+ {
+ std::map<uint32,uint32>::const_iterator used_mt_itr = usedMailTemplates.find(qinfo->RewMailTemplateId);
+ sLog.outErrorDb("Quest %u has `RewMailTemplateId` = %u but mail template %u already used for quest %u, quest will not have a mail reward.",
+ qinfo->GetQuestId(),qinfo->RewMailTemplateId,qinfo->RewMailTemplateId,used_mt_itr->second);
+ qinfo->RewMailTemplateId = 0; // no mail will send to player
+ qinfo->RewMailDelaySecs = 0; // no mail will send to player
+ }
+ else
+ usedMailTemplates[qinfo->RewMailTemplateId] = qinfo->GetQuestId();
+ }
+
+ if (qinfo->NextQuestInChain)
+ {
+ QuestMap::iterator qNextItr = mQuestTemplates.find(qinfo->NextQuestInChain);
+ if (qNextItr == mQuestTemplates.end())
+ {
+ sLog.outErrorDb("Quest %u has `NextQuestInChain` = %u but quest %u does not exist, quest chain will not work.",
+ qinfo->GetQuestId(),qinfo->NextQuestInChain ,qinfo->NextQuestInChain);
+ qinfo->NextQuestInChain = 0;
+ }
+ else
+ qNextItr->second->prevChainQuests.push_back(qinfo->GetQuestId());
+ }
+
+ // fill additional data stores
+ if (qinfo->PrevQuestId)
+ {
+ if (mQuestTemplates.find(abs(qinfo->GetPrevQuestId())) == mQuestTemplates.end())
+ {
+ sLog.outErrorDb("Quest %d has PrevQuestId %i, but no such quest", qinfo->GetQuestId(), qinfo->GetPrevQuestId());
+ }
+ else
+ {
+ qinfo->prevQuests.push_back(qinfo->PrevQuestId);
+ }
+ }
+
+ if (qinfo->NextQuestId)
+ {
+ QuestMap::iterator qNextItr = mQuestTemplates.find(abs(qinfo->GetNextQuestId()));
+ if (qNextItr == mQuestTemplates.end())
+ {
+ sLog.outErrorDb("Quest %d has NextQuestId %i, but no such quest", qinfo->GetQuestId(), qinfo->GetNextQuestId());
+ }
+ else
+ {
+ int32 signedQuestId = qinfo->NextQuestId < 0 ? -int32(qinfo->GetQuestId()) : int32(qinfo->GetQuestId());
+ qNextItr->second->prevQuests.push_back(signedQuestId);
+ }
+ }
+
+ if (qinfo->ExclusiveGroup)
+ mExclusiveQuestGroups.insert(std::pair<int32, uint32>(qinfo->ExclusiveGroup, qinfo->GetQuestId()));
+ if (qinfo->LimitTime)
+ qinfo->SetFlag(QUEST_TRINITY_FLAGS_TIMED);
+ }
+
+ // check QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT for spell with SPELL_EFFECT_QUEST_COMPLETE
+ for (uint32 i = 0; i < sSpellStore.GetNumRows(); ++i)
+ {
+ SpellEntry const *spellInfo = sSpellStore.LookupEntry(i);
+ if (!spellInfo)
+ continue;
+
+ for (uint8 j = 0; j < 3; ++j)
+ {
+ if (spellInfo->Effect[j] != SPELL_EFFECT_QUEST_COMPLETE)
+ continue;
+
+ uint32 quest_id = spellInfo->EffectMiscValue[j];
+
+ Quest const* quest = GetQuestTemplate(quest_id);
+
+ // some quest referenced in spells not exist (outdated spells)
+ if (!quest)
+ continue;
+
+ if (!quest->HasFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT))
+ {
+ sLog.outErrorDb("Spell (id: %u) have SPELL_EFFECT_QUEST_COMPLETE for quest %u , but quest not have flag QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT. Quest flags must be fixed, quest modified to enable objective.",spellInfo->Id,quest_id);
+
+ // this will prevent quest completing without objective
+ const_cast<Quest*>(quest)->SetFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT);
+ }
+ }
+ }
+
+ sLog.outString();
+ sLog.outString(">> Loaded %lu quests definitions", (unsigned long)mQuestTemplates.size());
+}
+
+void ObjectMgr::LoadQuestLocales()
+{
+ mQuestLocaleMap.clear(); // need for reload case
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,"
+ "Title_loc1,Details_loc1,Objectives_loc1,OfferRewardText_loc1,RequestItemsText_loc1,EndText_loc1,CompletedText_loc1,ObjectiveText1_loc1,ObjectiveText2_loc1,ObjectiveText3_loc1,ObjectiveText4_loc1,"
+ "Title_loc2,Details_loc2,Objectives_loc2,OfferRewardText_loc2,RequestItemsText_loc2,EndText_loc2,CompletedText_loc2,ObjectiveText1_loc2,ObjectiveText2_loc2,ObjectiveText3_loc2,ObjectiveText4_loc2,"
+ "Title_loc3,Details_loc3,Objectives_loc3,OfferRewardText_loc3,RequestItemsText_loc3,EndText_loc3,CompletedText_loc3,ObjectiveText1_loc3,ObjectiveText2_loc3,ObjectiveText3_loc3,ObjectiveText4_loc3,"
+ "Title_loc4,Details_loc4,Objectives_loc4,OfferRewardText_loc4,RequestItemsText_loc4,EndText_loc4,CompletedText_loc4,ObjectiveText1_loc4,ObjectiveText2_loc4,ObjectiveText3_loc4,ObjectiveText4_loc4,"
+ "Title_loc5,Details_loc5,Objectives_loc5,OfferRewardText_loc5,RequestItemsText_loc5,EndText_loc5,CompletedText_loc5,ObjectiveText1_loc5,ObjectiveText2_loc5,ObjectiveText3_loc5,ObjectiveText4_loc5,"
+ "Title_loc6,Details_loc6,Objectives_loc6,OfferRewardText_loc6,RequestItemsText_loc6,EndText_loc6,CompletedText_loc6,ObjectiveText1_loc6,ObjectiveText2_loc6,ObjectiveText3_loc6,ObjectiveText4_loc6,"
+ "Title_loc7,Details_loc7,Objectives_loc7,OfferRewardText_loc7,RequestItemsText_loc7,EndText_loc7,CompletedText_loc7,ObjectiveText1_loc7,ObjectiveText2_loc7,ObjectiveText3_loc7,ObjectiveText4_loc7,"
+ "Title_loc8,Details_loc8,Objectives_loc8,OfferRewardText_loc8,RequestItemsText_loc8,EndText_loc8,CompletedText_loc8,ObjectiveText1_loc8,ObjectiveText2_loc8,ObjectiveText3_loc8,ObjectiveText4_loc8"
+ " FROM locales_quest"
+);
+
+ if (!result)
+ return;
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 entry = fields[0].GetUInt32();
+
+ QuestLocale& data = mQuestLocaleMap[entry];
+
+ for (uint8 i = 1; i < MAX_LOCALE; ++i)
+ {
+ std::string str = fields[1+11*(i-1)].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.Title.size() <= idx)
+ data.Title.resize(idx+1);
+
+ data.Title[idx] = str;
+ }
+ }
+ str = fields[1+11*(i-1)+1].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.Details.size() <= idx)
+ data.Details.resize(idx+1);
+
+ data.Details[idx] = str;
+ }
+ }
+ str = fields[1+11*(i-1)+2].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.Objectives.size() <= idx)
+ data.Objectives.resize(idx+1);
+
+ data.Objectives[idx] = str;
+ }
+ }
+ str = fields[1+11*(i-1)+3].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.OfferRewardText.size() <= idx)
+ data.OfferRewardText.resize(idx+1);
+
+ data.OfferRewardText[idx] = str;
+ }
+ }
+ str = fields[1+11*(i-1)+4].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.RequestItemsText.size() <= idx)
+ data.RequestItemsText.resize(idx+1);
+
+ data.RequestItemsText[idx] = str;
+ }
+ }
+ str = fields[1+11*(i-1)+5].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.EndText.size() <= idx)
+ data.EndText.resize(idx+1);
+
+ data.EndText[idx] = str;
+ }
+ }
+ str = fields[1+11*(i-1)+6].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.CompletedText.size() <= idx)
+ data.CompletedText.resize(idx+1);
+
+ data.CompletedText[idx] = str;
+ }
+ }
+
+ for (uint8 k = 0; k < 4; ++k)
+ {
+ str = fields[1+11*(i-1)+7+k].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.ObjectiveText[k].size() <= idx)
+ data.ObjectiveText[k].resize(idx+1);
+
+ data.ObjectiveText[k][idx] = str;
+ }
+ }
+ }
+ }
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %lu Quest locale strings", (unsigned long)mQuestLocaleMap.size());
+}
+
+void ObjectMgr::LoadScripts(ScriptMapMap& scripts, char const* tablename)
+{
+ if (sWorld.IsScriptScheduled()) // function don't must be called in time scripts use.
+ return;
+
+ sLog.outString("%s :", tablename);
+
+ scripts.clear(); // need for reload support
+
+ QueryResult_AutoPtr result = WorldDatabase.PQuery("SELECT id,delay,command,datalong,datalong2,dataint, x, y, z, o FROM %s", tablename);
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u script definitions", count);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ bar.step();
+
+ Field *fields = result->Fetch();
+ ScriptInfo tmp;
+ tmp.id = fields[0].GetUInt32();
+ tmp.delay = fields[1].GetUInt32();
+ tmp.command = fields[2].GetUInt32();
+ tmp.datalong = fields[3].GetUInt32();
+ tmp.datalong2 = fields[4].GetUInt32();
+ tmp.dataint = fields[5].GetInt32();
+ tmp.x = fields[6].GetFloat();
+ tmp.y = fields[7].GetFloat();
+ tmp.z = fields[8].GetFloat();
+ tmp.o = fields[9].GetFloat();
+
+ // generic command args check
+ switch (tmp.command)
+ {
+ case SCRIPT_COMMAND_TALK:
+ {
+ if (tmp.datalong > CHAT_TYPE_WHISPER)
+ {
+ sLog.outErrorDb("Table `%s` has invalid talk type (datalong = %u) in SCRIPT_COMMAND_TALK for script id %u",tablename,tmp.datalong,tmp.id);
+ continue;
+ }
+ if (tmp.dataint == 0)
+ {
+ sLog.outErrorDb("Table `%s` has invalid talk text id (dataint = %i) in SCRIPT_COMMAND_TALK for script id %u",tablename,tmp.dataint,tmp.id);
+ continue;
+ }
+ if (tmp.dataint < MIN_DB_SCRIPT_STRING_ID || tmp.dataint >= MAX_DB_SCRIPT_STRING_ID)
+ {
+ sLog.outErrorDb("Table `%s` has out of range text id (dataint = %i expected %u-%u) in SCRIPT_COMMAND_TALK for script id %u",tablename,tmp.dataint,MIN_DB_SCRIPT_STRING_ID,MAX_DB_SCRIPT_STRING_ID,tmp.id);
+ continue;
+ }
+
+ break;
+ }
+
+ case SCRIPT_COMMAND_EMOTE:
+ {
+ if (!sEmotesStore.LookupEntry(tmp.datalong))
+ {
+ sLog.outErrorDb("Table `%s` has invalid emote id (datalong = %u) in SCRIPT_COMMAND_EMOTE for script id %u",tablename,tmp.datalong,tmp.id);
+ continue;
+ }
+ break;
+ }
+
+ case SCRIPT_COMMAND_TELEPORT_TO:
+ {
+ if (!sMapStore.LookupEntry(tmp.datalong))
+ {
+ sLog.outErrorDb("Table `%s` has invalid map (Id: %u) in SCRIPT_COMMAND_TELEPORT_TO for script id %u",tablename,tmp.datalong,tmp.id);
+ continue;
+ }
+
+ if (!Trinity::IsValidMapCoord(tmp.x,tmp.y,tmp.z,tmp.o))
+ {
+ sLog.outErrorDb("Table `%s` has invalid coordinates (X: %f Y: %f) in SCRIPT_COMMAND_TELEPORT_TO for script id %u",tablename,tmp.x,tmp.y,tmp.id);
+ continue;
+ }
+ break;
+ }
+
+ case SCRIPT_COMMAND_KILL_CREDIT:
+ {
+ if (!GetCreatureTemplate(tmp.datalong))
+ {
+ sLog.outErrorDb("Table `%s` has invalid creature (Entry: %u) in SCRIPT_COMMAND_KILL_CREDIT for script id %u",tablename,tmp.datalong,tmp.id);
+ continue;
+ }
+ break;
+ }
+
+ case SCRIPT_COMMAND_TEMP_SUMMON_CREATURE:
+ {
+ if (!Trinity::IsValidMapCoord(tmp.x,tmp.y,tmp.z,tmp.o))
+ {
+ sLog.outErrorDb("Table `%s` has invalid coordinates (X: %f Y: %f) in SCRIPT_COMMAND_TEMP_SUMMON_CREATURE for script id %u",tablename,tmp.x,tmp.y,tmp.id);
+ continue;
+ }
+
+ if (!GetCreatureTemplate(tmp.datalong))
+ {
+ sLog.outErrorDb("Table `%s` has invalid creature (Entry: %u) in SCRIPT_COMMAND_TEMP_SUMMON_CREATURE for script id %u",tablename,tmp.datalong,tmp.id);
+ continue;
+ }
+ break;
+ }
+
+ case SCRIPT_COMMAND_RESPAWN_GAMEOBJECT:
+ {
+ GameObjectData const* data = GetGOData(tmp.datalong);
+ if (!data)
+ {
+ sLog.outErrorDb("Table `%s` has invalid gameobject (GUID: %u) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id %u",tablename,tmp.datalong,tmp.id);
+ continue;
+ }
+
+ GameObjectInfo const* info = GetGameObjectInfo(data->id);
+ if (!info)
+ {
+ sLog.outErrorDb("Table `%s` has gameobject with invalid entry (GUID: %u Entry: %u) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id %u",tablename,tmp.datalong,data->id,tmp.id);
+ continue;
+ }
+
+ if (info->type == GAMEOBJECT_TYPE_FISHINGNODE ||
+ info->type == GAMEOBJECT_TYPE_FISHINGHOLE ||
+ info->type == GAMEOBJECT_TYPE_DOOR ||
+ info->type == GAMEOBJECT_TYPE_BUTTON ||
+ info->type == GAMEOBJECT_TYPE_TRAP)
+ {
+ sLog.outErrorDb("Table `%s` have gameobject type (%u) unsupported by command SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id %u",tablename,info->id,tmp.id);
+ continue;
+ }
+ break;
+ }
+ case SCRIPT_COMMAND_OPEN_DOOR:
+ case SCRIPT_COMMAND_CLOSE_DOOR:
+ {
+ GameObjectData const* data = GetGOData(tmp.datalong);
+ if (!data)
+ {
+ sLog.outErrorDb("Table `%s` has invalid gameobject (GUID: %u) in %s for script id %u",tablename,tmp.datalong,(tmp.command == SCRIPT_COMMAND_OPEN_DOOR ? "SCRIPT_COMMAND_OPEN_DOOR" : "SCRIPT_COMMAND_CLOSE_DOOR"),tmp.id);
+ continue;
+ }
+
+ GameObjectInfo const* info = GetGameObjectInfo(data->id);
+ if (!info)
+ {
+ sLog.outErrorDb("Table `%s` has gameobject with invalid entry (GUID: %u Entry: %u) in %s for script id %u",tablename,tmp.datalong,data->id,(tmp.command == SCRIPT_COMMAND_OPEN_DOOR ? "SCRIPT_COMMAND_OPEN_DOOR" : "SCRIPT_COMMAND_CLOSE_DOOR"),tmp.id);
+ continue;
+ }
+
+ if (info->type != GAMEOBJECT_TYPE_DOOR)
+ {
+ sLog.outErrorDb("Table `%s` has gameobject type (%u) non supported by command %s for script id %u",tablename,info->id,(tmp.command == SCRIPT_COMMAND_OPEN_DOOR ? "SCRIPT_COMMAND_OPEN_DOOR" : "SCRIPT_COMMAND_CLOSE_DOOR"),tmp.id);
+ continue;
+ }
+
+ break;
+ }
+ case SCRIPT_COMMAND_QUEST_EXPLORED:
+ {
+ Quest const* quest = GetQuestTemplate(tmp.datalong);
+ if (!quest)
+ {
+ sLog.outErrorDb("Table `%s` has invalid quest (ID: %u) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u",tablename,tmp.datalong,tmp.id);
+ continue;
+ }
+
+ if (!quest->HasFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT))
+ {
+ sLog.outErrorDb("Table `%s` has quest (ID: %u) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u, but quest not have flag QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT in quest flags. Script command or quest flags wrong. Quest modified to require objective.",tablename,tmp.datalong,tmp.id);
+
+ // this will prevent quest completing without objective
+ const_cast<Quest*>(quest)->SetFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT);
+
+ // continue; - quest objective requirement set and command can be allowed
+ }
+
+ if (float(tmp.datalong2) > DEFAULT_VISIBILITY_DISTANCE)
+ {
+ sLog.outErrorDb("Table `%s` has too large distance (%u) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u",
+ tablename,tmp.datalong2,tmp.id);
+ continue;
+ }
+
+ if (tmp.datalong2 && float(tmp.datalong2) > DEFAULT_VISIBILITY_DISTANCE)
+ {
+ sLog.outErrorDb("Table `%s` has too large distance (%u) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u, max distance is %f or 0 for disable distance check",
+ tablename,tmp.datalong2,tmp.id,DEFAULT_VISIBILITY_DISTANCE);
+ continue;
+ }
+
+ if (tmp.datalong2 && float(tmp.datalong2) < INTERACTION_DISTANCE)
+ {
+ sLog.outErrorDb("Table `%s` has too small distance (%u) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u, min distance is %f or 0 for disable distance check",
+ tablename,tmp.datalong2,tmp.id,INTERACTION_DISTANCE);
+ continue;
+ }
+
+ break;
+ }
+
+ case SCRIPT_COMMAND_REMOVE_AURA:
+ {
+ if (!sSpellStore.LookupEntry(tmp.datalong))
+ {
+ sLog.outErrorDb("Table `%s` using non-existent spell (id: %u) in SCRIPT_COMMAND_REMOVE_AURA or SCRIPT_COMMAND_CAST_SPELL for script id %u",
+ tablename,tmp.datalong,tmp.id);
+ continue;
+ }
+ if (tmp.datalong2 & ~0x1) // 1 bits (0,1)
+ {
+ sLog.outErrorDb("Table `%s` using unknown flags in datalong2 (%u)i n SCRIPT_COMMAND_CAST_SPELL for script id %u",
+ tablename,tmp.datalong2,tmp.id);
+ continue;
+ }
+ break;
+ }
+ case SCRIPT_COMMAND_CAST_SPELL:
+ {
+ if (!sSpellStore.LookupEntry(tmp.datalong))
+ {
+ sLog.outErrorDb("Table `%s` using non-existent spell (id: %u) in SCRIPT_COMMAND_REMOVE_AURA or SCRIPT_COMMAND_CAST_SPELL for script id %u",
+ tablename,tmp.datalong,tmp.id);
+ continue;
+ }
+ if (tmp.datalong2 & ~0x3) // 2 bits
+ {
+ sLog.outErrorDb("Table `%s` using unknown flags in datalong2 (%u)i n SCRIPT_COMMAND_CAST_SPELL for script id %u",
+ tablename,tmp.datalong2,tmp.id);
+ continue;
+ }
+ break;
+ }
+
+ case SCRIPT_COMMAND_CREATE_ITEM:
+ {
+ if (!GetItemPrototype(tmp.datalong))
+ {
+ sLog.outErrorDb("Table `%s` has nonexistent item (entry: %u) in SCRIPT_COMMAND_CREATE_ITEM for script id %u",
+ tablename, tmp.datalong, tmp.id);
+ continue;
+ }
+ if (!tmp.datalong2)
+ {
+ sLog.outErrorDb("Table `%s` SCRIPT_COMMAND_CREATE_ITEM but amount is %u for script id %u",
+ tablename, tmp.datalong2, tmp.id);
+ continue;
+ }
+ break;
+ }
+ }
+
+ if (scripts.find(tmp.id) == scripts.end())
+ {
+ ScriptMap emptyMap;
+ scripts[tmp.id] = emptyMap;
+ }
+ scripts[tmp.id].insert(std::pair<uint32, ScriptInfo>(tmp.delay, tmp));
+
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u script definitions", count);
+}
+
+void ObjectMgr::LoadGameObjectScripts()
+{
+ LoadScripts(sGameObjectScripts, "gameobject_scripts");
+
+ // check ids
+ for (ScriptMapMap::const_iterator itr = sGameObjectScripts.begin(); itr != sGameObjectScripts.end(); ++itr)
+ {
+ if (!GetGOData(itr->first))
+ sLog.outErrorDb("Table `gameobject_scripts` has not existing gameobject (GUID: %u) as script id",itr->first);
+ }
+}
+
+void ObjectMgr::LoadQuestEndScripts()
+{
+ LoadScripts(sQuestEndScripts, "quest_end_scripts");
+
+ // check ids
+ for (ScriptMapMap::const_iterator itr = sQuestEndScripts.begin(); itr != sQuestEndScripts.end(); ++itr)
+ {
+ if (!GetQuestTemplate(itr->first))
+ sLog.outErrorDb("Table `quest_end_scripts` has not existing quest (Id: %u) as script id",itr->first);
+ }
+}
+
+void ObjectMgr::LoadQuestStartScripts()
+{
+ LoadScripts(sQuestStartScripts,"quest_start_scripts");
+
+ // check ids
+ for (ScriptMapMap::const_iterator itr = sQuestStartScripts.begin(); itr != sQuestStartScripts.end(); ++itr)
+ {
+ if (!GetQuestTemplate(itr->first))
+ sLog.outErrorDb("Table `quest_start_scripts` has not existing quest (Id: %u) as script id",itr->first);
+ }
+}
+
+void ObjectMgr::LoadSpellScripts()
+{
+ LoadScripts(sSpellScripts, "spell_scripts");
+
+ // check ids
+ for (ScriptMapMap::const_iterator itr = sSpellScripts.begin(); itr != sSpellScripts.end(); ++itr)
+ {
+ SpellEntry const* spellInfo = sSpellStore.LookupEntry(itr->first);
+
+ if (!spellInfo)
+ {
+ sLog.outErrorDb("Table `spell_scripts` has not existing spell (Id: %u) as script id",itr->first);
+ continue;
+ }
+
+ //check for correct spellEffect
+ bool found = false;
+ for (uint8 i=0; i<3; ++i)
+ {
+ // skip empty effects
+ if (!spellInfo->Effect[i])
+ continue;
+
+ if (spellInfo->Effect[i] == SPELL_EFFECT_SCRIPT_EFFECT)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ sLog.outErrorDb("Table `spell_scripts` has unsupported spell (Id: %u) without SPELL_EFFECT_SCRIPT_EFFECT (%u) spell effect",itr->first,SPELL_EFFECT_SCRIPT_EFFECT);
+ }
+}
+
+void ObjectMgr::LoadEventScripts()
+{
+ LoadScripts(sEventScripts, "event_scripts");
+
+ std::set<uint32> evt_scripts;
+ // Load all possible script entries from gameobjects
+ for (uint32 i = 1; i < sGOStorage.MaxEntry; ++i)
+ {
+ GameObjectInfo const * goInfo = sGOStorage.LookupEntry<GameObjectInfo>(i);
+ if (goInfo)
+ {
+ switch(goInfo->type)
+ {
+ case GAMEOBJECT_TYPE_GOOBER:
+ if (goInfo->goober.eventId)
+ evt_scripts.insert(goInfo->goober.eventId);
+ break;
+ case GAMEOBJECT_TYPE_CHEST:
+ if (goInfo->chest.eventId)
+ evt_scripts.insert(goInfo->chest.eventId);
+ break;
+ case GAMEOBJECT_TYPE_CAMERA:
+ if (goInfo->camera.eventID)
+ evt_scripts.insert(goInfo->camera.eventID);
+ default:
+ break;
+ }
+ }
+ }
+ // Load all possible script entries from spells
+ for (uint32 i = 1; i < sSpellStore.GetNumRows(); ++i)
+ {
+ SpellEntry const * spell = sSpellStore.LookupEntry(i);
+ if (spell)
+ {
+ for (uint8 j=0; j<3; ++j)
+ {
+ if (spell->Effect[j] == SPELL_EFFECT_SEND_EVENT)
+ {
+ if (spell->EffectMiscValue[j])
+ evt_scripts.insert(spell->EffectMiscValue[j]);
+ }
+ }
+ }
+ }
+
+ // Then check if all scripts are in above list of possible script entries
+ for (ScriptMapMap::const_iterator itr = sEventScripts.begin(); itr != sEventScripts.end(); ++itr)
+ {
+ std::set<uint32>::const_iterator itr2 = evt_scripts.find(itr->first);
+ if (itr2 == evt_scripts.end())
+ sLog.outErrorDb("Table `event_scripts` has script (Id: %u) not referring to any gameobject_template type 10 data2 field, type 3 data6 field, type 13 data 2 field or any spell effect %u",
+ itr->first, SPELL_EFFECT_SEND_EVENT);
+ }
+}
+
+//Load WP Scripts
+void ObjectMgr::LoadWaypointScripts()
+{
+ LoadScripts(sWaypointScripts, "waypoint_scripts");
+
+ for (ScriptMapMap::const_iterator itr = sWaypointScripts.begin(); itr != sWaypointScripts.end(); ++itr)
+ {
+ QueryResult_AutoPtr query = WorldDatabase.PQuery("SELECT * FROM waypoint_scripts WHERE id = %u", itr->first);
+ if (!query || !query->GetRowCount())
+ sLog.outErrorDb("There is no waypoint which links to the waypoint script %u", itr->first);
+ }
+}
+
+void ObjectMgr::LoadGossipScripts()
+{
+ LoadScripts(sGossipScripts, "gossip_scripts");
+
+ // checks are done in LoadGossipMenuItems
+}
+
+void ObjectMgr::LoadPageTexts()
+{
+ sPageTextStore.Free(); // for reload case
+
+ sPageTextStore.Load();
+ sLog.outString(">> Loaded %u page texts", sPageTextStore.RecordCount);
+ sLog.outString();
+
+ for (uint32 i = 1; i < sPageTextStore.MaxEntry; ++i)
+ {
+ // check data correctness
+ PageText const* page = sPageTextStore.LookupEntry<PageText>(i);
+ if (!page)
+ continue;
+
+ if (page->Next_Page && !sPageTextStore.LookupEntry<PageText>(page->Next_Page))
+ {
+ sLog.outErrorDb("Page text (Id: %u) has not existing next page (Id:%u)", i,page->Next_Page);
+ continue;
+ }
+
+ // detect circular reference
+ std::set<uint32> checkedPages;
+ for (PageText const* pageItr = page; pageItr; pageItr = sPageTextStore.LookupEntry<PageText>(pageItr->Next_Page))
+ {
+ if (!pageItr->Next_Page)
+ break;
+ checkedPages.insert(pageItr->Page_ID);
+ if (checkedPages.find(pageItr->Next_Page)!= checkedPages.end())
+ {
+ std::ostringstream ss;
+ ss << "The text page(s) ";
+ for (std::set<uint32>::iterator itr= checkedPages.begin(); itr != checkedPages.end(); ++itr)
+ ss << *itr << " ";
+ ss << "create(s) a circular reference, which can cause the server to freeze. Changing Next_Page of page "
+ << pageItr->Page_ID <<" to 0";
+ sLog.outErrorDb(ss.str().c_str());
+ const_cast<PageText*>(pageItr)->Next_Page = 0;
+ break;
+ }
+ }
+ }
+}
+
+void ObjectMgr::LoadPageTextLocales()
+{
+ mPageTextLocaleMap.clear(); // need for reload case
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,text_loc1,text_loc2,text_loc3,text_loc4,text_loc5,text_loc6,text_loc7,text_loc8 FROM locales_page_text");
+
+ if (!result)
+ return;
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 entry = fields[0].GetUInt32();
+
+ PageTextLocale& data = mPageTextLocaleMap[entry];
+
+ for (uint8 i = 1; i < MAX_LOCALE; ++i)
+ {
+ std::string str = fields[i].GetCppString();
+ if (str.empty())
+ continue;
+
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.Text.size() <= idx)
+ data.Text.resize(idx+1);
+
+ data.Text[idx] = str;
+ }
+ }
+
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %lu PageText locale strings", (unsigned long)mPageTextLocaleMap.size());
+}
+
+struct SQLInstanceLoader : public SQLStorageLoaderBase<SQLInstanceLoader>
+{
+ template<class D>
+ void convert_from_str(uint32 /*field_pos*/, char *src, D &dst)
+ {
+ dst = D(objmgr.GetScriptId(src));
+ }
+};
+
+void ObjectMgr::LoadInstanceTemplate()
+{
+ SQLInstanceLoader loader;
+ loader.Load(sInstanceTemplate);
+
+ for (uint32 i = 0; i < sInstanceTemplate.MaxEntry; i++)
+ {
+ InstanceTemplate* temp = (InstanceTemplate*)GetInstanceTemplate(i);
+ if (!temp)
+ continue;
+
+ if (!MapManager::IsValidMAP(temp->map))
+ sLog.outErrorDb("ObjectMgr::LoadInstanceTemplate: bad mapid %d for template!", temp->map);
+
+ if (!MapManager::IsValidMapCoord(temp->parent,temp->startLocX,temp->startLocY,temp->startLocZ,temp->startLocO))
+ {
+ sLog.outErrorDb("ObjectMgr::LoadInstanceTemplate: bad parent entrance coordinates for map id %d template!", temp->map);
+ temp->parent = 0; // will have wrong continent 0 parent, at least existed
+ }
+ }
+
+ sLog.outString(">> Loaded %u Instance Template definitions", sInstanceTemplate.RecordCount);
+ sLog.outString();
+}
+
+GossipText const *ObjectMgr::GetGossipText(uint32 Text_ID) const
+{
+ GossipTextMap::const_iterator itr = mGossipText.find(Text_ID);
+ if (itr != mGossipText.end())
+ return &itr->second;
+ return NULL;
+}
+
+void ObjectMgr::LoadGossipText()
+{
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT * FROM npc_text");
+
+ int count = 0;
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u npc texts", count);
+ return;
+ }
+
+ int cic;
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ ++count;
+ cic = 0;
+
+ Field *fields = result->Fetch();
+
+ bar.step();
+
+ uint32 Text_ID = fields[cic++].GetUInt32();
+ if (!Text_ID)
+ {
+ sLog.outErrorDb("Table `npc_text` has record wit reserved id 0, ignore.");
+ continue;
+ }
+
+ GossipText& gText = mGossipText[Text_ID];
+
+ for (int i=0; i< 8; i++)
+ {
+ gText.Options[i].Text_0 = fields[cic++].GetCppString();
+ gText.Options[i].Text_1 = fields[cic++].GetCppString();
+
+ gText.Options[i].Language = fields[cic++].GetUInt32();
+ gText.Options[i].Probability = fields[cic++].GetFloat();
+
+ for (uint8 j=0; j < 3; ++j)
+ {
+ gText.Options[i].Emotes[j]._Delay = fields[cic++].GetUInt32();
+ gText.Options[i].Emotes[j]._Emote = fields[cic++].GetUInt32();
+ }
+ }
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u npc texts", count);
+}
+
+void ObjectMgr::LoadNpcTextLocales()
+{
+ mNpcTextLocaleMap.clear(); // need for reload case
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,"
+ "Text0_0_loc1,Text0_1_loc1,Text1_0_loc1,Text1_1_loc1,Text2_0_loc1,Text2_1_loc1,Text3_0_loc1,Text3_1_loc1,Text4_0_loc1,Text4_1_loc1,Text5_0_loc1,Text5_1_loc1,Text6_0_loc1,Text6_1_loc1,Text7_0_loc1,Text7_1_loc1,"
+ "Text0_0_loc2,Text0_1_loc2,Text1_0_loc2,Text1_1_loc2,Text2_0_loc2,Text2_1_loc2,Text3_0_loc2,Text3_1_loc1,Text4_0_loc2,Text4_1_loc2,Text5_0_loc2,Text5_1_loc2,Text6_0_loc2,Text6_1_loc2,Text7_0_loc2,Text7_1_loc2,"
+ "Text0_0_loc3,Text0_1_loc3,Text1_0_loc3,Text1_1_loc3,Text2_0_loc3,Text2_1_loc3,Text3_0_loc3,Text3_1_loc1,Text4_0_loc3,Text4_1_loc3,Text5_0_loc3,Text5_1_loc3,Text6_0_loc3,Text6_1_loc3,Text7_0_loc3,Text7_1_loc3,"
+ "Text0_0_loc4,Text0_1_loc4,Text1_0_loc4,Text1_1_loc4,Text2_0_loc4,Text2_1_loc4,Text3_0_loc4,Text3_1_loc1,Text4_0_loc4,Text4_1_loc4,Text5_0_loc4,Text5_1_loc4,Text6_0_loc4,Text6_1_loc4,Text7_0_loc4,Text7_1_loc4,"
+ "Text0_0_loc5,Text0_1_loc5,Text1_0_loc5,Text1_1_loc5,Text2_0_loc5,Text2_1_loc5,Text3_0_loc5,Text3_1_loc1,Text4_0_loc5,Text4_1_loc5,Text5_0_loc5,Text5_1_loc5,Text6_0_loc5,Text6_1_loc5,Text7_0_loc5,Text7_1_loc5,"
+ "Text0_0_loc6,Text0_1_loc6,Text1_0_loc6,Text1_1_loc6,Text2_0_loc6,Text2_1_loc6,Text3_0_loc6,Text3_1_loc1,Text4_0_loc6,Text4_1_loc6,Text5_0_loc6,Text5_1_loc6,Text6_0_loc6,Text6_1_loc6,Text7_0_loc6,Text7_1_loc6,"
+ "Text0_0_loc7,Text0_1_loc7,Text1_0_loc7,Text1_1_loc7,Text2_0_loc7,Text2_1_loc7,Text3_0_loc7,Text3_1_loc1,Text4_0_loc7,Text4_1_loc7,Text5_0_loc7,Text5_1_loc7,Text6_0_loc7,Text6_1_loc7,Text7_0_loc7,Text7_1_loc7, "
+ "Text0_0_loc8,Text0_1_loc8,Text1_0_loc8,Text1_1_loc8,Text2_0_loc8,Text2_1_loc8,Text3_0_loc8,Text3_1_loc1,Text4_0_loc8,Text4_1_loc8,Text5_0_loc8,Text5_1_loc8,Text6_0_loc8,Text6_1_loc8,Text7_0_loc8,Text7_1_loc8 "
+ " FROM locales_npc_text");
+
+ if (!result)
+ return;
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 entry = fields[0].GetUInt32();
+
+ NpcTextLocale& data = mNpcTextLocaleMap[entry];
+
+ for (uint8 i=1; i<MAX_LOCALE; ++i)
+ {
+ for (uint8 j=0; j<8; ++j)
+ {
+ std::string str0 = fields[1+8*2*(i-1)+2*j].GetCppString();
+ if (!str0.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.Text_0[j].size() <= idx)
+ data.Text_0[j].resize(idx+1);
+
+ data.Text_0[j][idx] = str0;
+ }
+ }
+ std::string str1 = fields[1+8*2*(i-1)+2*j+1].GetCppString();
+ if (!str1.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.Text_1[j].size() <= idx)
+ data.Text_1[j].resize(idx+1);
+
+ data.Text_1[j][idx] = str1;
+ }
+ }
+ }
+ }
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %lu NpcText locale strings", (unsigned long)mNpcTextLocaleMap.size());
+}
+
+//not very fast function but it is called only once a day, or on starting-up
+void ObjectMgr::ReturnOrDeleteOldMails(bool serverUp)
+{
+ time_t basetime = time(NULL);
+ sLog.outDebug("Returning mails current time: hour: %d, minute: %d, second: %d ", localtime(&basetime)->tm_hour, localtime(&basetime)->tm_min, localtime(&basetime)->tm_sec);
+ //delete all old mails without item and without body immediately, if starting server
+ if (!serverUp)
+ CharacterDatabase.PExecute("DELETE FROM mail WHERE expire_time < '" UI64FMTD "' AND has_items = '0' AND body = ''", (uint64)basetime);
+ // 0 1 2 3 4 5 6 7 8 9
+ QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT id,messageType,sender,receiver,has_items,expire_time,cod,checked,mailTemplateId FROM mail WHERE expire_time < '" UI64FMTD "'", (uint64)basetime);
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+ sLog.outString();
+ sLog.outString(">> Only expired mails (need to be return or delete) or DB table `mail` is empty.");
+ return; // any mails need to be returned or deleted
+ }
+
+ //std::ostringstream delitems, delmails; //will be here for optimization
+ //bool deletemail = false, deleteitem = false;
+ //delitems << "DELETE FROM item_instance WHERE guid IN (";
+ //delmails << "DELETE FROM mail WHERE id IN ("
+
+ barGoLink bar(result->GetRowCount());
+ uint32 count = 0;
+ Field *fields;
+
+ do
+ {
+ bar.step();
+
+ fields = result->Fetch();
+ Mail *m = new Mail;
+ m->messageID = fields[0].GetUInt32();
+ m->messageType = fields[1].GetUInt8();
+ m->sender = fields[2].GetUInt32();
+ m->receiver = fields[3].GetUInt32();
+ bool has_items = fields[4].GetBool();
+ m->expire_time = (time_t)fields[5].GetUInt64();
+ m->deliver_time = 0;
+ m->COD = fields[6].GetUInt32();
+ m->checked = fields[7].GetUInt32();
+ m->mailTemplateId = fields[8].GetInt16();
+
+ Player *pl = 0;
+ if (serverUp)
+ pl = GetPlayer((uint64)m->receiver);
+ if (pl && pl->m_mailsLoaded)
+ { //this code will run very improbably (the time is between 4 and 5 am, in game is online a player, who has old mail
+ //his in mailbox and he has already listed his mails)
+ delete m;
+ continue;
+ }
+ //delete or return mail:
+ if (has_items)
+ {
+ QueryResult_AutoPtr resultItems = CharacterDatabase.PQuery("SELECT item_guid,item_template FROM mail_items WHERE mail_id='%u'", m->messageID);
+ if (resultItems)
+ {
+ do
+ {
+ Field *fields2 = resultItems->Fetch();
+
+ uint32 item_guid_low = fields2[0].GetUInt32();
+ uint32 item_template = fields2[1].GetUInt32();
+
+ m->AddItem(item_guid_low, item_template);
+ }
+ while (resultItems->NextRow());
+ }
+ //if it is mail from AH, it shouldn't be returned, but deleted
+ if (m->messageType != MAIL_NORMAL || m->messageType == MAIL_AUCTION || (m->checked & (MAIL_CHECK_MASK_COD_PAYMENT | MAIL_CHECK_MASK_RETURNED)))
+ {
+ // mail open and then not returned
+ for (std::vector<MailItemInfo>::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2)
+ CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid = '%u'", itr2->item_guid);
+ }
+ else
+ {
+ //mail will be returned:
+ CharacterDatabase.PExecute("UPDATE mail SET sender = '%u', receiver = '%u', expire_time = '" UI64FMTD "', deliver_time = '" UI64FMTD "',cod = '0', checked = '%u' WHERE id = '%u'", m->receiver, m->sender, (uint64)(basetime + 30*DAY), (uint64)basetime, MAIL_CHECK_MASK_RETURNED, m->messageID);
+ delete m;
+ continue;
+ }
+ }
+
+ //deletemail = true;
+ //delmails << m->messageID << ", ";
+ CharacterDatabase.PExecute("DELETE FROM mail WHERE id = '%u'", m->messageID);
+ delete m;
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u mails", count);
+}
+
+void ObjectMgr::LoadQuestAreaTriggers()
+{
+ mQuestAreaTriggerMap.clear(); // need for reload case
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id,quest FROM areatrigger_involvedrelation");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u quest trigger points", count);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ ++count;
+ bar.step();
+
+ Field *fields = result->Fetch();
+
+ uint32 trigger_ID = fields[0].GetUInt32();
+ uint32 quest_ID = fields[1].GetUInt32();
+
+ AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(trigger_ID);
+ if (!atEntry)
+ {
+ sLog.outErrorDb("Area trigger (ID:%u) does not exist in `AreaTrigger.dbc`.",trigger_ID);
+ continue;
+ }
+
+ Quest const* quest = GetQuestTemplate(quest_ID);
+
+ if (!quest)
+ {
+ sLog.outErrorDb("Table `areatrigger_involvedrelation` has record (id: %u) for not existing quest %u",trigger_ID,quest_ID);
+ continue;
+ }
+
+ if (!quest->HasFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT))
+ {
+ sLog.outErrorDb("Table `areatrigger_involvedrelation` has record (id: %u) for not quest %u, but quest not have flag QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT. Trigger or quest flags must be fixed, quest modified to require objective.",trigger_ID,quest_ID);
+
+ // this will prevent quest completing without objective
+ const_cast<Quest*>(quest)->SetFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT);
+
+ // continue; - quest modified to required objective and trigger can be allowed.
+ }
+
+ mQuestAreaTriggerMap[trigger_ID] = quest_ID;
+
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u quest trigger points", count);
+}
+
+void ObjectMgr::LoadTavernAreaTriggers()
+{
+ mTavernAreaTriggerSet.clear(); // need for reload case
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id FROM areatrigger_tavern");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u tavern triggers", count);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ ++count;
+ bar.step();
+
+ Field *fields = result->Fetch();
+
+ uint32 Trigger_ID = fields[0].GetUInt32();
+
+ AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(Trigger_ID);
+ if (!atEntry)
+ {
+ sLog.outErrorDb("Area trigger (ID:%u) does not exist in `AreaTrigger.dbc`.",Trigger_ID);
+ continue;
+ }
+
+ mTavernAreaTriggerSet.insert(Trigger_ID);
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u tavern triggers", count);
+}
+
+void ObjectMgr::LoadAreaTriggerScripts()
+{
+ mAreaTriggerScripts.clear(); // need for reload case
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, ScriptName FROM areatrigger_scripts");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u areatrigger scripts", count);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ ++count;
+ bar.step();
+
+ Field *fields = result->Fetch();
+
+ uint32 Trigger_ID = fields[0].GetUInt32();
+ const char *scriptName = fields[1].GetString();
+
+ AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(Trigger_ID);
+ if (!atEntry)
+ {
+ sLog.outErrorDb("Area trigger (ID:%u) does not exist in `AreaTrigger.dbc`.",Trigger_ID);
+ continue;
+ }
+ mAreaTriggerScripts[Trigger_ID] = GetScriptId(scriptName);
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u areatrigger scripts", count);
+}
+
+uint32 ObjectMgr::GetNearestTaxiNode(float x, float y, float z, uint32 mapid, uint32 team)
+{
+ bool found = false;
+ float dist = 10000;
+ uint32 id = 0;
+
+ for (uint32 i = 1; i < sTaxiNodesStore.GetNumRows(); ++i)
+ {
+ TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(i);
+
+ if (!node || node->map_id != mapid || !node->MountCreatureID[team == ALLIANCE ? 1 : 0] && node->MountCreatureID[0] != 32981) // dk flight
+ continue;
+
+ uint8 field = (uint8)((i - 1) / 32);
+ uint32 submask = 1<<((i-1)%32);
+
+ // skip not taxi network nodes
+ if ((sTaxiNodesMask[field] & submask) == 0)
+ continue;
+
+ float dist2 = (node->x - x)*(node->x - x)+(node->y - y)*(node->y - y)+(node->z - z)*(node->z - z);
+ if (found)
+ {
+ if (dist2 < dist)
+ {
+ dist = dist2;
+ id = i;
+ }
+ }
+ else
+ {
+ found = true;
+ dist = dist2;
+ id = i;
+ }
+ }
+
+ return id;
+}
+
+void ObjectMgr::GetTaxiPath(uint32 source, uint32 destination, uint32 &path, uint32 &cost)
+{
+ TaxiPathSetBySource::iterator src_i = sTaxiPathSetBySource.find(source);
+ if (src_i == sTaxiPathSetBySource.end())
+ {
+ path = 0;
+ cost = 0;
+ return;
+ }
+
+ TaxiPathSetForSource& pathSet = src_i->second;
+
+ TaxiPathSetForSource::iterator dest_i = pathSet.find(destination);
+ if (dest_i == pathSet.end())
+ {
+ path = 0;
+ cost = 0;
+ return;
+ }
+
+ cost = dest_i->second.price;
+ path = dest_i->second.ID;
+}
+
+uint32 ObjectMgr::GetTaxiMountDisplayId(uint32 id, uint32 team, bool allowed_alt_team /* = false */)
+{
+ uint32 mount_entry = 0;
+ uint32 mount_id = 0;
+
+ // select mount creature id
+ TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(id);
+ if (node)
+ {
+ if (team == ALLIANCE)
+ mount_entry = node->MountCreatureID[1];
+ else
+ mount_entry = node->MountCreatureID[0];
+
+ // Fix for Alliance not being able to use Acherus taxi
+ // only one mount type for both sides
+ if (mount_entry == 0 && allowed_alt_team)
+ {
+ // Simply reverse the selection. At least one team in theory should have a valid mount ID to choose.
+ mount_entry = team == ALLIANCE ? node->MountCreatureID[0] : node->MountCreatureID[1];
+ }
+
+ CreatureInfo const *mount_info = GetCreatureTemplate(mount_entry);
+ if (mount_info)
+ {
+ mount_id = mount_info->GetRandomValidModelId();
+ if (!mount_id)
+ {
+ sLog.outErrorDb("No displayid found for the taxi mount with the entry %u! Can't load it!", mount_entry);
+ return false;
+ }
+ }
+ }
+
+ CreatureModelInfo const *minfo = objmgr.GetCreatureModelRandomGender(mount_id);
+ if (minfo)
+ mount_id = minfo->modelid;
+
+ return mount_id;
+}
+
+void ObjectMgr::GetTaxiPathNodes(uint32 path, Path &pathnodes, std::vector<uint32>& mapIds)
+{
+ if (path >= sTaxiPathNodesByPath.size())
+ return;
+
+ TaxiPathNodeList& nodeList = sTaxiPathNodesByPath[path];
+
+ pathnodes.Resize(nodeList.size());
+ mapIds.resize(nodeList.size());
+
+ for (size_t i = 0; i < nodeList.size(); ++i)
+ {
+ pathnodes[ i ].x = nodeList[i].x;
+ pathnodes[ i ].y = nodeList[i].y;
+ pathnodes[ i ].z = nodeList[i].z;
+
+ mapIds[i] = nodeList[i].mapid;
+ }
+}
+
+void ObjectMgr::GetTransportPathNodes(uint32 path, TransportPath &pathnodes)
+{
+ if (path >= sTaxiPathNodesByPath.size())
+ return;
+
+ TaxiPathNodeList& nodeList = sTaxiPathNodesByPath[path];
+
+ pathnodes.Resize(nodeList.size());
+
+ for (size_t i = 0; i < nodeList.size(); ++i)
+ {
+ pathnodes[ i ].mapid = nodeList[i].mapid;
+ pathnodes[ i ].x = nodeList[i].x;
+ pathnodes[ i ].y = nodeList[i].y;
+ pathnodes[ i ].z = nodeList[i].z;
+ pathnodes[ i ].actionFlag = nodeList[i].actionFlag;
+ pathnodes[ i ].delay = nodeList[i].delay;
+ }
+}
+
+void ObjectMgr::LoadGraveyardZones()
+{
+ mGraveYardMap.clear(); // need for reload case
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id,ghost_zone,faction FROM game_graveyard_zone");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u graveyard-zone links", count);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ ++count;
+ bar.step();
+
+ Field *fields = result->Fetch();
+
+ uint32 safeLocId = fields[0].GetUInt32();
+ uint32 zoneId = fields[1].GetUInt32();
+ uint32 team = fields[2].GetUInt32();
+
+ WorldSafeLocsEntry const* entry = sWorldSafeLocsStore.LookupEntry(safeLocId);
+ if (!entry)
+ {
+ sLog.outErrorDb("Table `game_graveyard_zone` has record for not existing graveyard (WorldSafeLocs.dbc id) %u, skipped.",safeLocId);
+ continue;
+ }
+
+ AreaTableEntry const *areaEntry = GetAreaEntryByAreaID(zoneId);
+ if (!areaEntry)
+ {
+ sLog.outErrorDb("Table `game_graveyard_zone` has record for not existing zone id (%u), skipped.",zoneId);
+ continue;
+ }
+
+ if (areaEntry->zone != 0)
+ {
+ sLog.outErrorDb("Table `game_graveyard_zone` has record subzone id (%u) instead of zone, skipped.",zoneId);
+ continue;
+ }
+
+ if (team != 0 && team != HORDE && team != ALLIANCE)
+ {
+ sLog.outErrorDb("Table `game_graveyard_zone` has record for non player faction (%u), skipped.",team);
+ continue;
+ }
+
+ if (!AddGraveYardLink(safeLocId,zoneId,team,false))
+ sLog.outErrorDb("Table `game_graveyard_zone` has a duplicate record for Graveyard (ID: %u) and Zone (ID: %u), skipped.",safeLocId,zoneId);
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u graveyard-zone links", count);
+}
+
+WorldSafeLocsEntry const *ObjectMgr::GetClosestGraveYard(float x, float y, float z, uint32 MapId, uint32 team)
+{
+ // search for zone associated closest graveyard
+ uint32 zoneId = MapManager::Instance().GetZoneId(MapId,x,y,z);
+
+ // Simulate std. algorithm:
+ // found some graveyard associated to (ghost_zone,ghost_map)
+ //
+ // if mapId == graveyard.mapId (ghost in plain zone or city or battleground) and search graveyard at same map
+ // then check faction
+ // if mapId != graveyard.mapId (ghost in instance) and search any graveyard associated
+ // then check faction
+ GraveYardMap::const_iterator graveLow = mGraveYardMap.lower_bound(zoneId);
+ GraveYardMap::const_iterator graveUp = mGraveYardMap.upper_bound(zoneId);
+ MapEntry const* map = sMapStore.LookupEntry(MapId);
+ // not need to check validity of map object; MapId _MUST_ be valid here
+
+ if (graveLow == graveUp && !map->IsBattleArena())
+ {
+ //sLog.outErrorDb("Table `game_graveyard_zone` incomplete: Zone %u Team %u does not have a linked graveyard.",zoneId,team);
+ return NULL;
+ }
+
+ // at corpse map
+ bool foundNear = false;
+ float distNear = 10000;
+ WorldSafeLocsEntry const* entryNear = NULL;
+
+ // at entrance map for corpse map
+ bool foundEntr = false;
+ float distEntr = 10000;
+ WorldSafeLocsEntry const* entryEntr = NULL;
+
+ // some where other
+ WorldSafeLocsEntry const* entryFar = NULL;
+
+ MapEntry const* mapEntry = sMapStore.LookupEntry(MapId);
+
+ for (GraveYardMap::const_iterator itr = graveLow; itr != graveUp; ++itr)
+ {
+ GraveYardData const& data = itr->second;
+
+ WorldSafeLocsEntry const* entry = sWorldSafeLocsStore.LookupEntry(data.safeLocId);
+ if (!entry)
+ {
+ sLog.outErrorDb("Table `game_graveyard_zone` has record for not existing graveyard (WorldSafeLocs.dbc id) %u, skipped.",data.safeLocId);
+ continue;
+ }
+
+ // skip enemy faction graveyard
+ // team == 0 case can be at call from .neargrave
+ if (data.team != 0 && team != 0 && data.team != team)
+ continue;
+
+ // find now nearest graveyard at other map
+ if (MapId != entry->map_id)
+ {
+ // if find graveyard at different map from where entrance placed (or no entrance data), use any first
+ if (!mapEntry ||
+ mapEntry->entrance_map < 0 ||
+ uint32(mapEntry->entrance_map) != entry->map_id ||
+ (mapEntry->entrance_x == 0 && mapEntry->entrance_y == 0))
+ {
+ // not have any corrdinates for check distance anyway
+ entryFar = entry;
+ continue;
+ }
+
+ // at entrance map calculate distance (2D);
+ float dist2 = (entry->x - mapEntry->entrance_x)*(entry->x - mapEntry->entrance_x)
+ +(entry->y - mapEntry->entrance_y)*(entry->y - mapEntry->entrance_y);
+ if (foundEntr)
+ {
+ if (dist2 < distEntr)
+ {
+ distEntr = dist2;
+ entryEntr = entry;
+ }
+ }
+ else
+ {
+ foundEntr = true;
+ distEntr = dist2;
+ entryEntr = entry;
+ }
+ }
+ // find now nearest graveyard at same map
+ else
+ {
+ float dist2 = (entry->x - x)*(entry->x - x)+(entry->y - y)*(entry->y - y)+(entry->z - z)*(entry->z - z);
+ if (foundNear)
+ {
+ if (dist2 < distNear)
+ {
+ distNear = dist2;
+ entryNear = entry;
+ }
+ }
+ else
+ {
+ foundNear = true;
+ distNear = dist2;
+ entryNear = entry;
+ }
+ }
+ }
+
+ if (entryNear)
+ return entryNear;
+
+ if (entryEntr)
+ return entryEntr;
+
+ return entryFar;
+}
+
+GraveYardData const* ObjectMgr::FindGraveYardData(uint32 id, uint32 zoneId)
+{
+ GraveYardMap::const_iterator graveLow = mGraveYardMap.lower_bound(zoneId);
+ GraveYardMap::const_iterator graveUp = mGraveYardMap.upper_bound(zoneId);
+
+ for (GraveYardMap::const_iterator itr = graveLow; itr != graveUp; ++itr)
+ {
+ if (itr->second.safeLocId == id)
+ return &itr->second;
+ }
+
+ return NULL;
+}
+
+bool ObjectMgr::AddGraveYardLink(uint32 id, uint32 zoneId, uint32 team, bool inDB)
+{
+ if (FindGraveYardData(id,zoneId))
+ return false;
+
+ // add link to loaded data
+ GraveYardData data;
+ data.safeLocId = id;
+ data.team = team;
+
+ mGraveYardMap.insert(GraveYardMap::value_type(zoneId,data));
+
+ // add link to DB
+ if (inDB)
+ {
+ WorldDatabase.PExecuteLog("INSERT INTO game_graveyard_zone (id,ghost_zone,faction) "
+ "VALUES ('%u', '%u','%u')",id,zoneId,team);
+ }
+
+ return true;
+}
+
+void ObjectMgr::RemoveGraveYardLink(uint32 id, uint32 zoneId, uint32 team, bool inDB)
+{
+ GraveYardMap::iterator graveLow = mGraveYardMap.lower_bound(zoneId);
+ GraveYardMap::iterator graveUp = mGraveYardMap.upper_bound(zoneId);
+ if (graveLow == graveUp)
+ {
+ //sLog.outErrorDb("Table `game_graveyard_zone` incomplete: Zone %u Team %u does not have a linked graveyard.",zoneId,team);
+ return;
+ }
+
+ bool found = false;
+
+ GraveYardMap::iterator itr;
+
+ for (itr = graveLow; itr != graveUp; ++itr)
+ {
+ GraveYardData & data = itr->second;
+
+ // skip not matching safezone id
+ if (data.safeLocId != id)
+ continue;
+
+ // skip enemy faction graveyard at same map (normal area, city, or battleground)
+ // team == 0 case can be at call from .neargrave
+ if (data.team != 0 && team != 0 && data.team != team)
+ continue;
+
+ found = true;
+ break;
+ }
+
+ // no match, return
+ if (!found)
+ return;
+
+ // remove from links
+ mGraveYardMap.erase(itr);
+
+ // remove link from DB
+ if (inDB)
+ {
+ WorldDatabase.PExecute("DELETE FROM game_graveyard_zone WHERE id = '%u' AND ghost_zone = '%u' AND faction = '%u'",id,zoneId,team);
+ }
+
+ return;
+}
+
+void ObjectMgr::LoadAreaTriggerTeleports()
+{
+ mAreaTriggers.clear(); // need for reload case
+
+ uint32 count = 0;
+
+ // 0 1 2 3 4 5 6
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, access_id, target_map, target_position_x, target_position_y, target_position_z, target_orientation FROM areatrigger_teleport");
+ if (!result)
+ {
+
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u area trigger teleport definitions", count);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+
+ bar.step();
+
+ ++count;
+
+ uint32 Trigger_ID = fields[0].GetUInt32();
+
+ AreaTrigger at;
+
+ at.access_id = fields[1].GetUInt32();
+ at.target_mapId = fields[2].GetUInt32();
+ at.target_X = fields[3].GetFloat();
+ at.target_Y = fields[4].GetFloat();
+ at.target_Z = fields[5].GetFloat();
+ at.target_Orientation = fields[6].GetFloat();
+
+ AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(Trigger_ID);
+ if (!atEntry)
+ {
+ sLog.outErrorDb("Area trigger (ID:%u) does not exist in `AreaTrigger.dbc`.",Trigger_ID);
+ continue;
+ }
+
+ MapEntry const* mapEntry = sMapStore.LookupEntry(at.target_mapId);
+ if (!mapEntry)
+ {
+ sLog.outErrorDb("Area trigger (ID:%u) target map (ID: %u) does not exist in `Map.dbc`.",Trigger_ID,at.target_mapId);
+ continue;
+ }
+
+ if (at.target_X == 0 && at.target_Y == 0 && at.target_Z == 0)
+ {
+ sLog.outErrorDb("Area trigger (ID:%u) target coordinates not provided.",Trigger_ID);
+ continue;
+ }
+
+ mAreaTriggers[Trigger_ID] = at;
+
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u area trigger teleport definitions", count);
+}
+
+void ObjectMgr::LoadAccessRequirements()
+{
+ mAccessRequirements.clear(); // need for reload case
+
+ uint32 count = 0;
+
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, level_min, level_max, item, item2, heroic_key, heroic_key2, quest_done, quest_failed_text, heroic_quest_done, heroic_quest_failed_text, heroic_level_min, status FROM access_requirement");
+ if (!result)
+ {
+
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u access requirement definitions", count);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+
+ bar.step();
+
+ ++count;
+
+ uint32 requiremt_ID = fields[0].GetUInt32();
+
+ AccessRequirement ar;
+
+ ar.levelMin = fields[1].GetUInt8();
+ ar.levelMax = fields[2].GetUInt8();
+ ar.heroicLevelMin = fields[11].GetUInt8();
+ ar.item = fields[3].GetUInt32();
+ ar.item2 = fields[4].GetUInt32();
+ ar.heroicKey = fields[5].GetUInt32();
+ ar.heroicKey2 = fields[6].GetUInt32();
+ ar.quest = fields[7].GetUInt32();
+ ar.questFailedText = fields[8].GetCppString();
+ ar.heroicQuest = fields[9].GetUInt32();
+ ar.heroicQuestFailedText = fields[10].GetCppString();
+ ar.status = fields[12].GetUInt8();
+
+ if (ar.item)
+ {
+ ItemPrototype const *pProto = GetItemPrototype(ar.item);
+ if (!pProto)
+ {
+ sLog.outError("Key item %u does not exist for requirement %u, removing key requirement.", ar.item, requiremt_ID);
+ ar.item = 0;
+ }
+ }
+
+ if (ar.item2)
+ {
+ ItemPrototype const *pProto = GetItemPrototype(ar.item2);
+ if (!pProto)
+ {
+ sLog.outError("Second item %u does not exist for requirement %u, removing key requirement.", ar.item2, requiremt_ID);
+ ar.item2 = 0;
+ }
+ }
+
+ if (ar.heroicKey)
+ {
+ ItemPrototype const *pProto = GetItemPrototype(ar.heroicKey);
+ if (!pProto)
+ {
+ sLog.outError("Heroic key %u not exist for trigger %u, remove key requirement.", ar.heroicKey, requiremt_ID);
+ ar.heroicKey = 0;
+ }
+ }
+
+ if (ar.heroicKey2)
+ {
+ ItemPrototype const *pProto = GetItemPrototype(ar.heroicKey2);
+ if (!pProto)
+ {
+ sLog.outError("Second heroic key %u not exist for trigger %u, remove key requirement.", ar.heroicKey2, requiremt_ID);
+ ar.heroicKey2 = 0;
+ }
+ }
+
+ if (ar.heroicQuest)
+ {
+ QuestMap::iterator qReqItr = mQuestTemplates.find(ar.heroicQuest);
+ if (qReqItr == mQuestTemplates.end())
+ {
+ sLog.outErrorDb("Required Heroic Quest %u not exist for trigger %u, remove heroic quest done requirement.",ar.heroicQuest,requiremt_ID);
+ ar.heroicQuest = 0;
+ }
+ }
+
+ if (ar.quest)
+ {
+ QuestMap::iterator qReqItr = mQuestTemplates.find(ar.quest);
+ if (qReqItr == mQuestTemplates.end())
+ {
+ sLog.outErrorDb("Required Quest %u not exist for trigger %u, remove quest done requirement.",ar.quest,requiremt_ID);
+ ar.quest = 0;
+ }
+ }
+
+ mAccessRequirements[requiremt_ID] = ar;
+
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u access requirement definitions", count);
+}
+
+/*
+ * Searches for the areatrigger which teleports players out of the given map with instance_template.parent field support
+ */
+AreaTrigger const* ObjectMgr::GetGoBackTrigger(uint32 Map) const
+{
+ bool useParentDbValue = false;
+ uint32 parentId = 0;
+ const MapEntry *mapEntry = sMapStore.LookupEntry(Map);
+ if (!mapEntry || mapEntry->entrance_map < 0)
+ return NULL;
+
+ if (mapEntry->IsDungeon())
+ {
+ const InstanceTemplate *iTemplate = objmgr.GetInstanceTemplate(Map);
+
+ if (!iTemplate)
+ return NULL;
+
+ parentId = iTemplate->parent;
+ useParentDbValue = true;
+ }
+
+ uint32 entrance_map = uint32(mapEntry->entrance_map);
+ for (AreaTriggerMap::const_iterator itr = mAreaTriggers.begin(); itr != mAreaTriggers.end(); ++itr)
+ if ((!useParentDbValue && itr->second.target_mapId == entrance_map) || (useParentDbValue && itr->second.target_mapId == parentId))
+ {
+ AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(itr->first);
+ if (atEntry && atEntry->mapid == Map)
+ return &itr->second;
+ }
+ return NULL;
+}
+
+/**
+ * Searches for the areatrigger which teleports players to the given map
+ */
+AreaTrigger const* ObjectMgr::GetMapEntranceTrigger(uint32 Map) const
+{
+ for (AreaTriggerMap::const_iterator itr = mAreaTriggers.begin(); itr != mAreaTriggers.end(); ++itr)
+ {
+ if (itr->second.target_mapId == Map)
+ {
+ AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(itr->first);
+ if (atEntry)
+ return &itr->second;
+ }
+ }
+ return NULL;
+}
+
+void ObjectMgr::SetHighestGuids()
+{
+ QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT MAX(guid) FROM characters");
+ if (result)
+ m_hiCharGuid = (*result)[0].GetUInt32()+1;
+
+ result = WorldDatabase.Query("SELECT MAX(guid) FROM creature");
+ if (result)
+ m_hiCreatureGuid = (*result)[0].GetUInt32()+1;
+
+ result = CharacterDatabase.Query("SELECT MAX(guid) FROM item_instance");
+ if (result)
+ m_hiItemGuid = (*result)[0].GetUInt32()+1;
+
+ // Cleanup other tables from not existed guids ( >= m_hiItemGuid)
+ CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item >= '%u'", m_hiItemGuid);
+ CharacterDatabase.PExecute("DELETE FROM mail_items WHERE item_guid >= '%u'", m_hiItemGuid);
+ CharacterDatabase.PExecute("DELETE FROM auctionhouse WHERE itemguid >= '%u'", m_hiItemGuid);
+ CharacterDatabase.PExecute("DELETE FROM guild_bank_item WHERE item_guid >= '%u'", m_hiItemGuid);
+
+ result = WorldDatabase.Query("SELECT MAX(guid) FROM gameobject");
+ if (result)
+ m_hiGoGuid = (*result)[0].GetUInt32()+1;
+
+ result = CharacterDatabase.Query("SELECT MAX(id) FROM auctionhouse");
+ if (result)
+ m_auctionid = (*result)[0].GetUInt32()+1;
+
+ result = CharacterDatabase.Query("SELECT MAX(id) FROM mail");
+ if (result)
+ m_mailid = (*result)[0].GetUInt32()+1;
+
+ result = CharacterDatabase.Query("SELECT MAX(guid) FROM corpse");
+ if (result)
+ m_hiCorpseGuid = (*result)[0].GetUInt32()+1;
+
+ result = CharacterDatabase.Query("SELECT MAX(arenateamid) FROM arena_team");
+ if (result)
+ m_arenaTeamId = (*result)[0].GetUInt32()+1;
+
+ result = CharacterDatabase.Query("SELECT MAX(setguid) FROM character_equipmentsets");
+ if (result)
+ m_equipmentSetGuid = (*result)[0].GetUInt64()+1;
+
+ result = CharacterDatabase.Query("SELECT MAX(guildid) FROM guild");
+ if (result)
+ m_guildId = (*result)[0].GetUInt32()+1;
+
+ result = CharacterDatabase.Query("SELECT MAX(guid) FROM groups");
+ if (result)
+ m_hiGroupGuid = (*result)[0].GetUInt32()+1;
+}
+
+uint32 ObjectMgr::GenerateArenaTeamId()
+{
+ if (m_arenaTeamId >= 0xFFFFFFFE)
+ {
+ sLog.outError("Arena team ids overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_arenaTeamId++;
+}
+
+uint32 ObjectMgr::GenerateAuctionID()
+{
+ if (m_auctionid >= 0xFFFFFFFE)
+ {
+ sLog.outError("Auctions ids overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_auctionid++;
+}
+
+uint64 ObjectMgr::GenerateEquipmentSetGuid()
+{
+ if (m_equipmentSetGuid >= 0xFFFFFFFFFFFFFFFEll)
+ {
+ sLog.outError("EquipmentSet guid overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_equipmentSetGuid++;
+}
+
+uint32 ObjectMgr::GenerateGuildId()
+{
+ if (m_guildId >= 0xFFFFFFFE)
+ {
+ sLog.outError("Guild ids overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_guildId++;
+}
+
+uint32 ObjectMgr::GenerateMailID()
+{
+ if (m_mailid >= 0xFFFFFFFE)
+ {
+ sLog.outError("Mail ids overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_mailid++;
+}
+
+uint32 ObjectMgr::GenerateLowGuid(HighGuid guidhigh)
+{
+ switch(guidhigh)
+ {
+ case HIGHGUID_ITEM:
+ if (m_hiItemGuid >= 0xFFFFFFFE)
+ {
+ sLog.outError("Item guid overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_hiItemGuid++;
+ case HIGHGUID_UNIT:
+ if (m_hiCreatureGuid >= 0x00FFFFFE)
+ {
+ sLog.outError("Creature guid overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_hiCreatureGuid++;
+ case HIGHGUID_PET:
+ if (m_hiPetGuid >= 0x00FFFFFE)
+ {
+ sLog.outError("Pet guid overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_hiPetGuid++;
+ case HIGHGUID_VEHICLE:
+ if (m_hiVehicleGuid >= 0x00FFFFFF)
+ {
+ sLog.outError("Vehicle guid overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_hiVehicleGuid++;
+ case HIGHGUID_PLAYER:
+ if (m_hiCharGuid >= 0xFFFFFFFE)
+ {
+ sLog.outError("Players guid overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_hiCharGuid++;
+ case HIGHGUID_GAMEOBJECT:
+ if (m_hiGoGuid >= 0x00FFFFFE)
+ {
+ sLog.outError("Gameobject guid overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_hiGoGuid++;
+ case HIGHGUID_CORPSE:
+ if (m_hiCorpseGuid >= 0xFFFFFFFE)
+ {
+ sLog.outError("Corpse guid overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_hiCorpseGuid++;
+ case HIGHGUID_DYNAMICOBJECT:
+ if (m_hiDoGuid >= 0xFFFFFFFE)
+ {
+ sLog.outError("DynamicObject guid overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_hiDoGuid++;
+ case HIGHGUID_GROUP:
+ if (m_hiGroupGuid >= 0xFFFFFFFE)
+ {
+ sLog.outError("Group guid overflow!! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+ return m_hiGroupGuid++;
+ default:
+ ASSERT(0);
+ }
+
+ ASSERT(0);
+ return 0;
+}
+
+void ObjectMgr::LoadGameObjectLocales()
+{
+ mGameObjectLocaleMap.clear(); // need for reload case
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,"
+ "name_loc1,name_loc2,name_loc3,name_loc4,name_loc5,name_loc6,name_loc7,name_loc8,"
+ "castbarcaption_loc1,castbarcaption_loc2,castbarcaption_loc3,castbarcaption_loc4,"
+ "castbarcaption_loc5,castbarcaption_loc6,castbarcaption_loc7,castbarcaption_loc8 FROM locales_gameobject");
+
+ if (!result)
+ return;
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 entry = fields[0].GetUInt32();
+
+ GameObjectLocale& data = mGameObjectLocaleMap[entry];
+
+ for (uint8 i = 1; i < MAX_LOCALE; ++i)
+ {
+ std::string str = fields[i].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.Name.size() <= idx)
+ data.Name.resize(idx+1);
+
+ data.Name[idx] = str;
+ }
+ }
+ }
+
+ for (uint8 i = 1; i < MAX_LOCALE; ++i)
+ {
+ std::string str = fields[i+(MAX_LOCALE-1)].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ if (data.CastBarCaption.size() <= idx)
+ data.CastBarCaption.resize(idx+1);
+
+ data.CastBarCaption[idx] = str;
+ }
+ }
+ }
+
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %lu gameobject locale strings", (unsigned long)mGameObjectLocaleMap.size());
+}
+
+struct SQLGameObjectLoader : public SQLStorageLoaderBase<SQLGameObjectLoader>
+{
+ template<class D>
+ void convert_from_str(uint32 /*field_pos*/, char *src, D &dst)
+ {
+ dst = D(objmgr.GetScriptId(src));
+ }
+};
+
+inline void CheckGOLockId(GameObjectInfo const* goInfo,uint32 dataN,uint32 N)
+{
+ if (sLockStore.LookupEntry(dataN))
+ return;
+
+ sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but lock (Id: %u) not found.",
+ goInfo->id,goInfo->type,N,goInfo->door.lockId,goInfo->door.lockId);
+}
+
+inline void CheckGOLinkedTrapId(GameObjectInfo const* goInfo,uint32 dataN,uint32 N)
+{
+ if (GameObjectInfo const* trapInfo = sGOStorage.LookupEntry<GameObjectInfo>(dataN))
+ {
+ if (trapInfo->type != GAMEOBJECT_TYPE_TRAP)
+ sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but GO (Entry %u) have not GAMEOBJECT_TYPE_TRAP (%u) type.",
+ goInfo->id,goInfo->type,N,dataN,dataN,GAMEOBJECT_TYPE_TRAP);
+ }
+ /* disable check for while (too many error reports baout not existed in trap templates
+ else
+ sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but trap GO (Entry %u) not exist in `gameobject_template`.",
+ goInfo->id,goInfo->type,N,dataN,dataN);
+ */
+}
+
+inline void CheckGOSpellId(GameObjectInfo const* goInfo,uint32 dataN,uint32 N)
+{
+ if (sSpellStore.LookupEntry(dataN))
+ return;
+
+ sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but Spell (Entry %u) not exist.",
+ goInfo->id,goInfo->type,N,dataN,dataN);
+}
+
+inline void CheckAndFixGOChairHeightId(GameObjectInfo const* goInfo,uint32 const& dataN,uint32 N)
+{
+ if (dataN <= (UNIT_STAND_STATE_SIT_HIGH_CHAIR-UNIT_STAND_STATE_SIT_LOW_CHAIR))
+ return;
+
+ sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but correct chair height in range 0..%i.",
+ goInfo->id,goInfo->type,N,dataN,UNIT_STAND_STATE_SIT_HIGH_CHAIR-UNIT_STAND_STATE_SIT_LOW_CHAIR);
+
+ // prevent client and server unexpected work
+ const_cast<uint32&>(dataN) = 0;
+}
+
+inline void CheckGONoDamageImmuneId(GameObjectInfo const* goInfo,uint32 dataN,uint32 N)
+{
+ // 0/1 correct values
+ if (dataN <= 1)
+ return;
+
+ sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but expected boolean (0/1) noDamageImmune field value.",
+ goInfo->id,goInfo->type,N,dataN);
+}
+
+inline void CheckGOConsumable(GameObjectInfo const* goInfo,uint32 dataN,uint32 N)
+{
+ // 0/1 correct values
+ if (dataN <= 1)
+ return;
+
+ sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but expected boolean (0/1) consumable field value.",
+ goInfo->id,goInfo->type,N,dataN);
+}
+
+void ObjectMgr::LoadGameobjectInfo()
+{
+ SQLGameObjectLoader loader;
+ loader.Load(sGOStorage);
+
+ // some checks
+ for (uint32 id = 1; id < sGOStorage.MaxEntry; id++)
+ {
+ GameObjectInfo const* goInfo = sGOStorage.LookupEntry<GameObjectInfo>(id);
+ if (!goInfo)
+ continue;
+
+ // some GO types have unused go template, check goInfo->displayId at GO spawn data loading or ignore
+
+ switch(goInfo->type)
+ {
+ case GAMEOBJECT_TYPE_DOOR: //0
+ {
+ if (goInfo->door.lockId)
+ CheckGOLockId(goInfo,goInfo->door.lockId,1);
+ CheckGONoDamageImmuneId(goInfo,goInfo->door.noDamageImmune,3);
+ break;
+ }
+ case GAMEOBJECT_TYPE_BUTTON: //1
+ {
+ if (goInfo->button.lockId)
+ CheckGOLockId(goInfo,goInfo->button.lockId,1);
+ CheckGONoDamageImmuneId(goInfo,goInfo->button.noDamageImmune,4);
+ break;
+ }
+ case GAMEOBJECT_TYPE_QUESTGIVER: //2
+ {
+ if (goInfo->questgiver.lockId)
+ CheckGOLockId(goInfo,goInfo->questgiver.lockId,0);
+ CheckGONoDamageImmuneId(goInfo,goInfo->questgiver.noDamageImmune,5);
+ break;
+ }
+ case GAMEOBJECT_TYPE_CHEST: //3
+ {
+ if (goInfo->chest.lockId)
+ CheckGOLockId(goInfo,goInfo->chest.lockId,0);
+
+ CheckGOConsumable(goInfo,goInfo->chest.consumable,3);
+
+ if (goInfo->chest.linkedTrapId) // linked trap
+ CheckGOLinkedTrapId(goInfo,goInfo->chest.linkedTrapId,7);
+ break;
+ }
+ case GAMEOBJECT_TYPE_TRAP: //6
+ {
+ if (goInfo->trap.lockId)
+ CheckGOLockId(goInfo,goInfo->trap.lockId,0);
+ /* disable check for while, too many not existed spells
+ if (goInfo->trap.spellId) // spell
+ CheckGOSpellId(goInfo,goInfo->trap.spellId,3);
+ */
+ break;
+ }
+ case GAMEOBJECT_TYPE_CHAIR: //7
+ CheckAndFixGOChairHeightId(goInfo,goInfo->chair.height,1);
+ break;
+ case GAMEOBJECT_TYPE_SPELL_FOCUS: //8
+ {
+ if (goInfo->spellFocus.focusId)
+ {
+ if (!sSpellFocusObjectStore.LookupEntry(goInfo->spellFocus.focusId))
+ sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data0=%u but SpellFocus (Id: %u) not exist.",
+ id,goInfo->type,goInfo->spellFocus.focusId,goInfo->spellFocus.focusId);
+ }
+
+ if (goInfo->spellFocus.linkedTrapId) // linked trap
+ CheckGOLinkedTrapId(goInfo,goInfo->spellFocus.linkedTrapId,2);
+ break;
+ }
+ case GAMEOBJECT_TYPE_GOOBER: //10
+ {
+ if (goInfo->goober.lockId)
+ CheckGOLockId(goInfo,goInfo->goober.lockId,0);
+
+ CheckGOConsumable(goInfo,goInfo->goober.consumable,3);
+
+ if (goInfo->goober.pageId) // pageId
+ {
+ if (!sPageTextStore.LookupEntry<PageText>(goInfo->goober.pageId))
+ sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data7=%u but PageText (Entry %u) not exist.",
+ id,goInfo->type,goInfo->goober.pageId,goInfo->goober.pageId);
+ }
+ /* disable check for while, too many not existed spells
+ if (goInfo->goober.spellId) // spell
+ CheckGOSpellId(goInfo,goInfo->goober.spellId,10);
+ */
+ CheckGONoDamageImmuneId(goInfo,goInfo->goober.noDamageImmune,11);
+ if (goInfo->goober.linkedTrapId) // linked trap
+ CheckGOLinkedTrapId(goInfo,goInfo->goober.linkedTrapId,12);
+ break;
+ }
+ case GAMEOBJECT_TYPE_AREADAMAGE: //12
+ {
+ if (goInfo->areadamage.lockId)
+ CheckGOLockId(goInfo,goInfo->areadamage.lockId,0);
+ break;
+ }
+ case GAMEOBJECT_TYPE_CAMERA: //13
+ {
+ if (goInfo->camera.lockId)
+ CheckGOLockId(goInfo,goInfo->camera.lockId,0);
+ break;
+ }
+ case GAMEOBJECT_TYPE_MO_TRANSPORT: //15
+ {
+ if (goInfo->moTransport.taxiPathId)
+ {
+ if (goInfo->moTransport.taxiPathId >= sTaxiPathNodesByPath.size() || sTaxiPathNodesByPath[goInfo->moTransport.taxiPathId].empty())
+ sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data0=%u but TaxiPath (Id: %u) not exist.",
+ id,goInfo->type,goInfo->moTransport.taxiPathId,goInfo->moTransport.taxiPathId);
+ }
+ break;
+ }
+ case GAMEOBJECT_TYPE_SUMMONING_RITUAL: //18
+ {
+ /* disable check for while, too many not existed spells
+ // always must have spell
+ CheckGOSpellId(goInfo,goInfo->summoningRitual.spellId,1);
+ */
+ break;
+ }
+ case GAMEOBJECT_TYPE_SPELLCASTER: //22
+ {
+ // always must have spell
+ CheckGOSpellId(goInfo,goInfo->spellcaster.spellId,0);
+ break;
+ }
+ case GAMEOBJECT_TYPE_FLAGSTAND: //24
+ {
+ if (goInfo->flagstand.lockId)
+ CheckGOLockId(goInfo,goInfo->flagstand.lockId,0);
+ CheckGONoDamageImmuneId(goInfo,goInfo->flagstand.noDamageImmune,5);
+ break;
+ }
+ case GAMEOBJECT_TYPE_FISHINGHOLE: //25
+ {
+ if (goInfo->fishinghole.lockId)
+ CheckGOLockId(goInfo,goInfo->fishinghole.lockId,4);
+ break;
+ }
+ case GAMEOBJECT_TYPE_FLAGDROP: //26
+ {
+ if (goInfo->flagdrop.lockId)
+ CheckGOLockId(goInfo,goInfo->flagdrop.lockId,0);
+ CheckGONoDamageImmuneId(goInfo,goInfo->flagdrop.noDamageImmune,3);
+ break;
+ }
+ case GAMEOBJECT_TYPE_BARBER_CHAIR: //32
+ CheckAndFixGOChairHeightId(goInfo,goInfo->barberChair.chairheight,0);
+ break;
+ }
+ }
+
+ sLog.outString(">> Loaded %u game object templates", sGOStorage.RecordCount);
+ sLog.outString();
+}
+
+void ObjectMgr::LoadExplorationBaseXP()
+{
+ uint32 count = 0;
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT level,basexp FROM exploration_basexp");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u BaseXP definitions", count);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ bar.step();
+
+ Field *fields = result->Fetch();
+ uint8 level = fields[0].GetUInt8();
+ uint32 basexp = fields[1].GetUInt32();
+ mBaseXPTable[level] = basexp;
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u BaseXP definitions", count);
+}
+
+uint32 ObjectMgr::GetBaseXP(uint8 level)
+{
+ return mBaseXPTable[level] ? mBaseXPTable[level] : 0;
+}
+
+uint32 ObjectMgr::GetXPForLevel(uint8 level)
+{
+ if (level < mPlayerXPperLevel.size())
+ return mPlayerXPperLevel[level];
+ return 0;
+}
+
+void ObjectMgr::LoadPetNames()
+{
+ uint32 count = 0;
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT word,entry,half FROM pet_name_generation");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u pet name parts", count);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ bar.step();
+
+ Field *fields = result->Fetch();
+ std::string word = fields[0].GetString();
+ uint32 entry = fields[1].GetUInt32();
+ bool half = fields[2].GetBool();
+ if (half)
+ PetHalfName1[entry].push_back(word);
+ else
+ PetHalfName0[entry].push_back(word);
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u pet name parts", count);
+}
+
+void ObjectMgr::LoadPetNumber()
+{
+ QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT MAX(id) FROM character_pet");
+ if (result)
+ {
+ Field *fields = result->Fetch();
+ m_hiPetNumber = fields[0].GetUInt32()+1;
+ }
+
+ barGoLink bar(1);
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded the max pet number: %d", m_hiPetNumber-1);
+}
+
+std::string ObjectMgr::GeneratePetName(uint32 entry)
+{
+ std::vector<std::string> & list0 = PetHalfName0[entry];
+ std::vector<std::string> & list1 = PetHalfName1[entry];
+
+ if (list0.empty() || list1.empty())
+ {
+ CreatureInfo const *cinfo = GetCreatureTemplate(entry);
+ char* petname = GetPetName(cinfo->family, sWorld.GetDefaultDbcLocale());
+ if (!petname)
+ petname = cinfo->Name;
+ return std::string(petname);
+ }
+
+ return *(list0.begin()+urand(0, list0.size()-1)) + *(list1.begin()+urand(0, list1.size()-1));
+}
+
+uint32 ObjectMgr::GeneratePetNumber()
+{
+ return ++m_hiPetNumber;
+}
+
+void ObjectMgr::LoadCorpses()
+{
+ uint32 count = 0;
+ // 0 1 2 3 4 5 6 7 8 9 10
+ QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT position_x, position_y, position_z, orientation, map, data, time, corpse_type, instance, phaseMask, guid FROM corpse WHERE corpse_type <> 0");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u corpses", count);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ bar.step();
+
+ Field *fields = result->Fetch();
+
+ uint32 guid = fields[result->GetFieldCount()-1].GetUInt32();
+
+ Corpse *corpse = new Corpse;
+ if (!corpse->LoadFromDB(guid,fields))
+ {
+ delete corpse;
+ continue;
+ }
+
+ ObjectAccessor::Instance().AddCorpse(corpse);
+
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u corpses", count);
+}
+
+void ObjectMgr::LoadReputationOnKill()
+{
+ uint32 count = 0;
+
+ // 0 1 2
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT creature_id, RewOnKillRepFaction1, RewOnKillRepFaction2,"
+ // 3 4 5 6 7 8 9
+ "IsTeamAward1, MaxStanding1, RewOnKillRepValue1, IsTeamAward2, MaxStanding2, RewOnKillRepValue2, TeamDependent "
+ "FROM creature_onkill_reputation");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded 0 creature award reputation definitions. DB table `creature_onkill_reputation` is empty.");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 creature_id = fields[0].GetUInt32();
+
+ ReputationOnKillEntry repOnKill;
+ repOnKill.repfaction1 = fields[1].GetUInt32();
+ repOnKill.repfaction2 = fields[2].GetUInt32();
+ repOnKill.is_teamaward1 = fields[3].GetBool();
+ repOnKill.reputation_max_cap1 = fields[4].GetUInt32();
+ repOnKill.repvalue1 = fields[5].GetInt32();
+ repOnKill.is_teamaward2 = fields[6].GetBool();
+ repOnKill.reputation_max_cap2 = fields[7].GetUInt32();
+ repOnKill.repvalue2 = fields[8].GetInt32();
+ repOnKill.team_dependent = fields[9].GetUInt8();
+
+ if (!GetCreatureTemplate(creature_id))
+ {
+ sLog.outErrorDb("Table `creature_onkill_reputation` have data for not existed creature entry (%u), skipped",creature_id);
+ continue;
+ }
+
+ if (repOnKill.repfaction1)
+ {
+ FactionEntry const *factionEntry1 = sFactionStore.LookupEntry(repOnKill.repfaction1);
+ if (!factionEntry1)
+ {
+ sLog.outErrorDb("Faction (faction.dbc) %u does not exist but is used in `creature_onkill_reputation`",repOnKill.repfaction1);
+ continue;
+ }
+ }
+
+ if (repOnKill.repfaction2)
+ {
+ FactionEntry const *factionEntry2 = sFactionStore.LookupEntry(repOnKill.repfaction2);
+ if (!factionEntry2)
+ {
+ sLog.outErrorDb("Faction (faction.dbc) %u does not exist but is used in `creature_onkill_reputation`",repOnKill.repfaction2);
+ continue;
+ }
+ }
+
+ mRepOnKill[creature_id] = repOnKill;
+
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u creature award reputation definitions", count);
+}
+
+void ObjectMgr::LoadPointsOfInterest()
+{
+ uint32 count = 0;
+
+ // 0 1 2 3 4 5 6
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, x, y, icon, flags, data, icon_name FROM points_of_interest");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded 0 Points of Interest definitions. DB table `points_of_interest` is empty.");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 point_id = fields[0].GetUInt32();
+
+ PointOfInterest POI;
+ POI.x = fields[1].GetFloat();
+ POI.y = fields[2].GetFloat();
+ POI.icon = fields[3].GetUInt32();
+ POI.flags = fields[4].GetUInt32();
+ POI.data = fields[5].GetUInt32();
+ POI.icon_name = fields[6].GetCppString();
+
+ if (!Trinity::IsValidMapCoord(POI.x,POI.y))
+ {
+ sLog.outErrorDb("Table `points_of_interest` (Entry: %u) have invalid coordinates (X: %f Y: %f), ignored.",point_id,POI.x,POI.y);
+ continue;
+ }
+
+ mPointsOfInterest[point_id] = POI;
+
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u Points of Interest definitions", count);
+}
+
+void ObjectMgr::LoadQuestPOI()
+{
+ uint32 count = 0;
+
+ // 0 1 2 3
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT questId, id, objIndex, mapid, WorldMapAreaId, FloorId, unk3, unk4 FROM quest_poi order by questId");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded 0 quest POI definitions. DB table `quest_poi` is empty.");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 questId = fields[0].GetUInt32();
+ uint32 id = fields[1].GetUInt32();
+ int32 objIndex = fields[2].GetInt32();
+ uint32 mapId = fields[3].GetUInt32();
+ uint32 WorldMapAreaId = fields[4].GetUInt32();
+ uint32 FloorId = fields[5].GetUInt32();
+ uint32 unk3 = fields[6].GetUInt32();
+ uint32 unk4 = fields[7].GetUInt32();
+
+ QuestPOI POI(id, objIndex, mapId, WorldMapAreaId, FloorId, unk3, unk4);
+
+ QueryResult_AutoPtr points = WorldDatabase.PQuery("SELECT x, y FROM quest_poi_points WHERE questId='%u' AND id='%i'", questId, id);
+
+ if (points)
+ {
+ do
+ {
+ Field *pointFields = points->Fetch();
+ int32 x = pointFields[0].GetInt32();
+ int32 y = pointFields[1].GetInt32();
+ QuestPOIPoint point(x, y);
+ POI.points.push_back(point);
+ } while (points->NextRow());
+ }
+
+ mQuestPOIMap[questId].push_back(POI);
+
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u quest POI definitions", count);
+}
+
+void ObjectMgr::LoadNPCSpellClickSpells()
+{
+ uint32 count = 0;
+
+ mSpellClickInfoMap.clear();
+ // 0 1 2 3 4 5 6 7 8
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT npc_entry, spell_id, quest_start, quest_start_active, quest_end, cast_flags, aura_required, aura_forbidden, user_type FROM npc_spellclick_spells");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded 0 spellclick spells. DB table `npc_spellclick_spells` is empty.");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 npc_entry = fields[0].GetUInt32();
+ CreatureInfo const* cInfo = GetCreatureTemplate(npc_entry);
+ if (!cInfo)
+ {
+ sLog.outErrorDb("Table npc_spellclick_spells references unknown creature_template %u. Skipping entry.", npc_entry);
+ continue;
+ }
+
+ if (!(cInfo->npcflag & UNIT_NPC_FLAG_SPELLCLICK))
+ const_cast<CreatureInfo*>(cInfo)->npcflag |= UNIT_NPC_FLAG_SPELLCLICK;
+
+ uint32 spellid = fields[1].GetUInt32();
+ SpellEntry const *spellinfo = sSpellStore.LookupEntry(spellid);
+ if (!spellinfo)
+ {
+ sLog.outErrorDb("Table npc_spellclick_spells references unknown spellid %u. Skipping entry.", spellid);
+ continue;
+ }
+
+ uint32 auraRequired = fields[6].GetUInt32();
+ if (auraRequired)
+ {
+ SpellEntry const *aurReqInfo = sSpellStore.LookupEntry(auraRequired);
+ if (!aurReqInfo)
+ {
+ sLog.outErrorDb("Table npc_spellclick_spells references unknown aura required %u. Skipping entry.", auraRequired);
+ continue;
+ }
+ }
+
+ uint32 auraForbidden = fields[7].GetUInt32();
+ if (auraForbidden)
+ {
+ SpellEntry const *aurForInfo = sSpellStore.LookupEntry(auraForbidden);
+ if (!aurForInfo)
+ {
+ sLog.outErrorDb("Table npc_spellclick_spells references unknown aura forbidden %u. Skipping entry.", auraForbidden);
+ continue;
+ }
+ }
+
+ uint32 quest_start = fields[2].GetUInt32();
+
+ // quest might be 0 to enable spellclick independent of any quest
+ if (quest_start)
+ {
+ if (mQuestTemplates.find(quest_start) == mQuestTemplates.end())
+ {
+ sLog.outErrorDb("Table npc_spellclick_spells references unknown start quest %u. Skipping entry.", quest_start);
+ continue;
+ }
+ }
+
+ bool quest_start_active = fields[3].GetBool();
+
+ uint32 quest_end = fields[4].GetUInt32();
+ // quest might be 0 to enable spellclick active infinity after start quest
+ if (quest_end)
+ {
+ if (mQuestTemplates.find(quest_end) == mQuestTemplates.end())
+ {
+ sLog.outErrorDb("Table npc_spellclick_spells references unknown end quest %u. Skipping entry.", quest_end);
+ continue;
+ }
+ }
+
+ uint8 userType = fields[8].GetUInt8();
+ if (userType >= SPELL_CLICK_USER_MAX)
+ sLog.outErrorDb("Table npc_spellclick_spells references unknown user type %u. Skipping entry.", uint32(userType));
+
+ uint8 castFlags = fields[5].GetUInt8();
+ SpellClickInfo info;
+ info.spellId = spellid;
+ info.questStart = quest_start;
+ info.questStartCanActive = quest_start_active;
+ info.questEnd = quest_end;
+ info.castFlags = castFlags;
+ info.auraRequired = auraRequired;
+ info.auraForbidden = auraForbidden;
+ info.userType = SpellClickUserTypes(userType);
+ mSpellClickInfoMap.insert(SpellClickInfoMap::value_type(npc_entry, info));
+
+ // mark creature template as spell clickable
+ const_cast<CreatureInfo*>(cInfo)->npcflag |= UNIT_NPC_FLAG_SPELLCLICK;
+
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u spellclick definitions", count);
+}
+
+void ObjectMgr::LoadWeatherZoneChances()
+{
+ uint32 count = 0;
+
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT zone, spring_rain_chance, spring_snow_chance, spring_storm_chance, summer_rain_chance, summer_snow_chance, summer_storm_chance, fall_rain_chance, fall_snow_chance, fall_storm_chance, winter_rain_chance, winter_snow_chance, winter_storm_chance FROM game_weather");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded 0 weather definitions. DB table `game_weather` is empty.");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 zone_id = fields[0].GetUInt32();
+
+ WeatherZoneChances& wzc = mWeatherZoneMap[zone_id];
+
+ for (uint8 season = 0; season < WEATHER_SEASONS; ++season)
+ {
+ wzc.data[season].rainChance = fields[season * (MAX_WEATHER_TYPE-1) + 1].GetUInt32();
+ wzc.data[season].snowChance = fields[season * (MAX_WEATHER_TYPE-1) + 2].GetUInt32();
+ wzc.data[season].stormChance = fields[season * (MAX_WEATHER_TYPE-1) + 3].GetUInt32();
+
+ if (wzc.data[season].rainChance > 100)
+ {
+ wzc.data[season].rainChance = 25;
+ sLog.outErrorDb("Weather for zone %u season %u has wrong rain chance > 100%%",zone_id,season);
+ }
+
+ if (wzc.data[season].snowChance > 100)
+ {
+ wzc.data[season].snowChance = 25;
+ sLog.outErrorDb("Weather for zone %u season %u has wrong snow chance > 100%%",zone_id,season);
+ }
+
+ if (wzc.data[season].stormChance > 100)
+ {
+ wzc.data[season].stormChance = 25;
+ sLog.outErrorDb("Weather for zone %u season %u has wrong storm chance > 100%%",zone_id,season);
+ }
+ }
+
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u weather definitions", count);
+}
+
+void ObjectMgr::SaveCreatureRespawnTime(uint32 loguid, uint32 instance, time_t t)
+{
+ mCreatureRespawnTimes[MAKE_PAIR64(loguid,instance)] = t;
+ WorldDatabase.PExecute("DELETE FROM creature_respawn WHERE guid = '%u' AND instance = '%u'", loguid, instance);
+ if (t)
+ WorldDatabase.PExecute("INSERT INTO creature_respawn VALUES ('%u', '" UI64FMTD "', '%u')", loguid, uint64(t), instance);
+}
+
+void ObjectMgr::DeleteCreatureData(uint32 guid)
+{
+ // remove mapid*cellid -> guid_set map
+ CreatureData const* data = GetCreatureData(guid);
+ if (data)
+ RemoveCreatureFromGrid(guid, data);
+
+ mCreatureDataMap.erase(guid);
+}
+
+void ObjectMgr::SaveGORespawnTime(uint32 loguid, uint32 instance, time_t t)
+{
+ mGORespawnTimes[MAKE_PAIR64(loguid,instance)] = t;
+ WorldDatabase.PExecute("DELETE FROM gameobject_respawn WHERE guid = '%u' AND instance = '%u'", loguid, instance);
+ if (t)
+ WorldDatabase.PExecute("INSERT INTO gameobject_respawn VALUES ('%u', '" UI64FMTD "', '%u')", loguid, uint64(t), instance);
+}
+
+void ObjectMgr::DeleteRespawnTimeForInstance(uint32 instance)
+{
+ RespawnTimes::iterator next;
+
+ for (RespawnTimes::iterator itr = mGORespawnTimes.begin(); itr != mGORespawnTimes.end(); itr = next)
+ {
+ next = itr;
+ ++next;
+
+ if (GUID_HIPART(itr->first) == instance)
+ mGORespawnTimes.erase(itr);
+ }
+
+ for (RespawnTimes::iterator itr = mCreatureRespawnTimes.begin(); itr != mCreatureRespawnTimes.end(); itr = next)
+ {
+ next = itr;
+ ++next;
+
+ if (GUID_HIPART(itr->first) == instance)
+ mCreatureRespawnTimes.erase(itr);
+ }
+
+ WorldDatabase.PExecute("DELETE FROM creature_respawn WHERE instance = '%u'", instance);
+ WorldDatabase.PExecute("DELETE FROM gameobject_respawn WHERE instance = '%u'", instance);
+}
+
+void ObjectMgr::DeleteGOData(uint32 guid)
+{
+ // remove mapid*cellid -> guid_set map
+ GameObjectData const* data = GetGOData(guid);
+ if (data)
+ RemoveGameobjectFromGrid(guid, data);
+
+ mGameObjectDataMap.erase(guid);
+}
+
+void ObjectMgr::AddCorpseCellData(uint32 mapid, uint32 cellid, uint32 player_guid, uint32 instance)
+{
+ // corpses are always added to spawn mode 0 and they are spawned by their instance id
+ CellObjectGuids& cell_guids = mMapObjectGuids[MAKE_PAIR32(mapid,0)][cellid];
+ cell_guids.corpses[player_guid] = instance;
+}
+
+void ObjectMgr::DeleteCorpseCellData(uint32 mapid, uint32 cellid, uint32 player_guid)
+{
+ // corpses are always added to spawn mode 0 and they are spawned by their instance id
+ CellObjectGuids& cell_guids = mMapObjectGuids[MAKE_PAIR32(mapid,0)][cellid];
+ cell_guids.corpses.erase(player_guid);
+}
+
+void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map,char const* table)
+{
+ map.clear(); // need for reload case
+
+ uint32 count = 0;
+
+ QueryResult_AutoPtr result = WorldDatabase.PQuery("SELECT id,quest FROM %s",table);
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded 0 quest relations from %s. DB table `%s` is empty.",table,table);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ uint32 id = fields[0].GetUInt32();
+ uint32 quest = fields[1].GetUInt32();
+
+ if (mQuestTemplates.find(quest) == mQuestTemplates.end())
+ {
+ sLog.outErrorDb("Table `%s: Quest %u listed for entry %u does not exist.",table,quest,id);
+ continue;
+ }
+
+ map.insert(QuestRelations::value_type(id,quest));
+
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u quest relations from %s", count,table);
+}
+
+void ObjectMgr::LoadGameobjectQuestRelations()
+{
+ LoadQuestRelationsHelper(mGOQuestRelations,"gameobject_questrelation");
+
+ for (QuestRelations::iterator itr = mGOQuestRelations.begin(); itr != mGOQuestRelations.end(); ++itr)
+ {
+ GameObjectInfo const* goInfo = GetGameObjectInfo(itr->first);
+ if (!goInfo)
+ sLog.outErrorDb("Table `gameobject_questrelation` have data for not existed gameobject entry (%u) and existed quest %u",itr->first,itr->second);
+ else if (goInfo->type != GAMEOBJECT_TYPE_QUESTGIVER)
+ sLog.outErrorDb("Table `gameobject_questrelation` have data gameobject entry (%u) for quest %u, but GO is not GAMEOBJECT_TYPE_QUESTGIVER",itr->first,itr->second);
+ }
+}
+
+void ObjectMgr::LoadGameobjectInvolvedRelations()
+{
+ LoadQuestRelationsHelper(mGOQuestInvolvedRelations,"gameobject_involvedrelation");
+
+ for (QuestRelations::iterator itr = mGOQuestInvolvedRelations.begin(); itr != mGOQuestInvolvedRelations.end(); ++itr)
+ {
+ GameObjectInfo const* goInfo = GetGameObjectInfo(itr->first);
+ if (!goInfo)
+ sLog.outErrorDb("Table `gameobject_involvedrelation` have data for not existed gameobject entry (%u) and existed quest %u",itr->first,itr->second);
+ else if (goInfo->type != GAMEOBJECT_TYPE_QUESTGIVER)
+ sLog.outErrorDb("Table `gameobject_involvedrelation` have data gameobject entry (%u) for quest %u, but GO is not GAMEOBJECT_TYPE_QUESTGIVER",itr->first,itr->second);
+ }
+}
+
+void ObjectMgr::LoadCreatureQuestRelations()
+{
+ LoadQuestRelationsHelper(mCreatureQuestRelations,"creature_questrelation");
+
+ for (QuestRelations::iterator itr = mCreatureQuestRelations.begin(); itr != mCreatureQuestRelations.end(); ++itr)
+ {
+ CreatureInfo const* cInfo = GetCreatureTemplate(itr->first);
+ if (!cInfo)
+ sLog.outErrorDb("Table `creature_questrelation` have data for not existed creature entry (%u) and existed quest %u",itr->first,itr->second);
+ else if (!(cInfo->npcflag & UNIT_NPC_FLAG_QUESTGIVER))
+ sLog.outErrorDb("Table `creature_questrelation` has creature entry (%u) for quest %u, but npcflag does not include UNIT_NPC_FLAG_QUESTGIVER",itr->first,itr->second);
+ }
+}
+
+void ObjectMgr::LoadCreatureInvolvedRelations()
+{
+ LoadQuestRelationsHelper(mCreatureQuestInvolvedRelations,"creature_involvedrelation");
+
+ for (QuestRelations::iterator itr = mCreatureQuestInvolvedRelations.begin(); itr != mCreatureQuestInvolvedRelations.end(); ++itr)
+ {
+ CreatureInfo const* cInfo = GetCreatureTemplate(itr->first);
+ if (!cInfo)
+ sLog.outErrorDb("Table `creature_involvedrelation` have data for not existed creature entry (%u) and existed quest %u",itr->first,itr->second);
+ else if (!(cInfo->npcflag & UNIT_NPC_FLAG_QUESTGIVER))
+ sLog.outErrorDb("Table `creature_involvedrelation` has creature entry (%u) for quest %u, but npcflag does not include UNIT_NPC_FLAG_QUESTGIVER",itr->first,itr->second);
+ }
+}
+
+void ObjectMgr::LoadReservedPlayersNames()
+{
+ m_ReservedNames.clear(); // need for reload case
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT name FROM reserved_name");
+
+ uint32 count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u reserved player names", count);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ Field* fields;
+ do
+ {
+ bar.step();
+ fields = result->Fetch();
+ std::string name= fields[0].GetCppString();
+
+ std::wstring wstr;
+ if (!Utf8toWStr (name,wstr))
+ {
+ sLog.outError("Table `reserved_name` have invalid name: %s", name.c_str());
+ continue;
+ }
+
+ wstrToLower(wstr);
+
+ m_ReservedNames.insert(wstr);
+ ++count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u reserved player names", count);
+}
+
+bool ObjectMgr::IsReservedName(const std::string& name) const
+{
+ std::wstring wstr;
+ if (!Utf8toWStr (name,wstr))
+ return false;
+
+ wstrToLower(wstr);
+
+ return m_ReservedNames.find(wstr) != m_ReservedNames.end();
+}
+
+enum LanguageType
+{
+ LT_BASIC_LATIN = 0x0000,
+ LT_EXTENDEN_LATIN = 0x0001,
+ LT_CYRILLIC = 0x0002,
+ LT_EAST_ASIA = 0x0004,
+ LT_ANY = 0xFFFF
+};
+
+static LanguageType GetRealmLanguageType(bool create)
+{
+ switch(sWorld.getConfig(CONFIG_REALM_ZONE))
+ {
+ case REALM_ZONE_UNKNOWN: // any language
+ case REALM_ZONE_DEVELOPMENT:
+ case REALM_ZONE_TEST_SERVER:
+ case REALM_ZONE_QA_SERVER:
+ return LT_ANY;
+ case REALM_ZONE_UNITED_STATES: // extended-Latin
+ case REALM_ZONE_OCEANIC:
+ case REALM_ZONE_LATIN_AMERICA:
+ case REALM_ZONE_ENGLISH:
+ case REALM_ZONE_GERMAN:
+ case REALM_ZONE_FRENCH:
+ case REALM_ZONE_SPANISH:
+ return LT_EXTENDEN_LATIN;
+ case REALM_ZONE_KOREA: // East-Asian
+ case REALM_ZONE_TAIWAN:
+ case REALM_ZONE_CHINA:
+ return LT_EAST_ASIA;
+ case REALM_ZONE_RUSSIAN: // Cyrillic
+ return LT_CYRILLIC;
+ default:
+ return create ? LT_BASIC_LATIN : LT_ANY; // basic-Latin at create, any at login
+ }
+}
+
+bool isValidString(std::wstring wstr, uint32 strictMask, bool numericOrSpace, bool create = false)
+{
+ if (strictMask == 0) // any language, ignore realm
+ {
+ if (isExtendedLatinString(wstr,numericOrSpace))
+ return true;
+ if (isCyrillicString(wstr,numericOrSpace))
+ return true;
+ if (isEastAsianString(wstr,numericOrSpace))
+ return true;
+ return false;
+ }
+
+ if (strictMask & 0x2) // realm zone specific
+ {
+ LanguageType lt = GetRealmLanguageType(create);
+ if (lt & LT_EXTENDEN_LATIN)
+ if (isExtendedLatinString(wstr,numericOrSpace))
+ return true;
+ if (lt & LT_CYRILLIC)
+ if (isCyrillicString(wstr,numericOrSpace))
+ return true;
+ if (lt & LT_EAST_ASIA)
+ if (isEastAsianString(wstr,numericOrSpace))
+ return true;
+ }
+
+ if (strictMask & 0x1) // basic Latin
+ {
+ if (isBasicLatinString(wstr,numericOrSpace))
+ return true;
+ }
+
+ return false;
+}
+
+uint8 ObjectMgr::CheckPlayerName(const std::string& name, bool create)
+{
+ std::wstring wname;
+ if (!Utf8toWStr(name,wname))
+ return CHAR_NAME_INVALID_CHARACTER;
+
+ if (wname.size() > MAX_PLAYER_NAME)
+ return CHAR_NAME_TOO_LONG;
+
+ uint32 minName = sWorld.getConfig(CONFIG_MIN_PLAYER_NAME);
+ if (wname.size() < minName)
+ return CHAR_NAME_TOO_SHORT;
+
+ uint32 strictMask = sWorld.getConfig(CONFIG_STRICT_PLAYER_NAMES);
+ if (!isValidString(wname,strictMask,false,create))
+ return CHAR_NAME_MIXED_LANGUAGES;
+
+ return CHAR_NAME_SUCCESS;
+}
+
+bool ObjectMgr::IsValidCharterName(const std::string& name)
+{
+ std::wstring wname;
+ if (!Utf8toWStr(name,wname))
+ return false;
+
+ if (wname.size() > MAX_CHARTER_NAME)
+ return false;
+
+ uint32 minName = sWorld.getConfig(CONFIG_MIN_CHARTER_NAME);
+ if (wname.size() < minName)
+ return false;
+
+ uint32 strictMask = sWorld.getConfig(CONFIG_STRICT_CHARTER_NAMES);
+
+ return isValidString(wname,strictMask,true);
+}
+
+PetNameInvalidReason ObjectMgr::CheckPetName(const std::string& name)
+{
+ std::wstring wname;
+ if (!Utf8toWStr(name,wname))
+ return PET_NAME_INVALID;
+
+ if (wname.size() > MAX_PET_NAME)
+ return PET_NAME_TOO_LONG;
+
+ uint32 minName = sWorld.getConfig(CONFIG_MIN_PET_NAME);
+ if (wname.size() < minName)
+ return PET_NAME_TOO_SHORT;
+
+ uint32 strictMask = sWorld.getConfig(CONFIG_STRICT_PET_NAMES);
+ if (!isValidString(wname,strictMask,false))
+ return PET_NAME_MIXED_LANGUAGES;
+
+ return PET_NAME_SUCCESS;
+}
+
+int ObjectMgr::GetIndexForLocale(LocaleConstant loc)
+{
+ if (loc == LOCALE_enUS)
+ return -1;
+
+ for (size_t i=0; i < m_LocalForIndex.size(); ++i)
+ if (m_LocalForIndex[i] == loc)
+ return i;
+
+ return -1;
+}
+
+LocaleConstant ObjectMgr::GetLocaleForIndex(int i)
+{
+ if (i<0 || i >= m_LocalForIndex.size())
+ return LOCALE_enUS;
+
+ return m_LocalForIndex[i];
+}
+
+int ObjectMgr::GetOrNewIndexForLocale(LocaleConstant loc)
+{
+ if (loc == LOCALE_enUS)
+ return -1;
+
+ for (size_t i=0; i < m_LocalForIndex.size(); ++i)
+ if (m_LocalForIndex[i] == loc)
+ return i;
+
+ m_LocalForIndex.push_back(loc);
+ return m_LocalForIndex.size()-1;
+}
+
+void ObjectMgr::LoadGameObjectForQuests()
+{
+ mGameObjectForQuestSet.clear(); // need for reload case
+
+ if (!sGOStorage.MaxEntry)
+ {
+ barGoLink bar(1);
+ bar.step();
+ sLog.outString();
+ sLog.outString(">> Loaded 0 GameObjects for quests");
+ return;
+ }
+
+ barGoLink bar(sGOStorage.MaxEntry - 1);
+ uint32 count = 0;
+
+ // collect GO entries for GO that must activated
+ for (uint32 go_entry = 1; go_entry < sGOStorage.MaxEntry; ++go_entry)
+ {
+ bar.step();
+ GameObjectInfo const* goInfo = sGOStorage.LookupEntry<GameObjectInfo>(go_entry);
+ if (!goInfo)
+ continue;
+
+ switch(goInfo->type)
+ {
+ // scan GO chest with loot including quest items
+ case GAMEOBJECT_TYPE_CHEST:
+ {
+ uint32 loot_id = goInfo->GetLootId();
+
+ // find quest loot for GO
+ if (LootTemplates_Gameobject.HaveQuestLootFor(loot_id))
+ {
+ mGameObjectForQuestSet.insert(go_entry);
+ ++count;
+ }
+ break;
+ }
+ case GAMEOBJECT_TYPE_GOOBER:
+ {
+ if (goInfo->goober.questId) //quests objects
+ {
+ mGameObjectForQuestSet.insert(go_entry);
+ count++;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u GameObjects for quests", count);
+}
+
+bool ObjectMgr::LoadTrinityStrings(DatabaseType& db, char const* table, int32 min_value, int32 max_value)
+{
+ int32 start_value = min_value;
+ int32 end_value = max_value;
+ // some string can have negative indexes range
+ if (start_value < 0)
+ {
+ if (end_value >= start_value)
+ {
+ sLog.outErrorDb("Table '%s' attempt loaded with invalid range (%d - %d), strings not loaded.",table,min_value,max_value);
+ return false;
+ }
+
+ // real range (max+1,min+1) exaple: (-10,-1000) -> -999...-10+1
+ std::swap(start_value,end_value);
+ ++start_value;
+ ++end_value;
+ }
+ else
+ {
+ if (start_value >= end_value)
+ {
+ sLog.outErrorDb("Table '%s' attempt loaded with invalid range (%d - %d), strings not loaded.",table,min_value,max_value);
+ return false;
+ }
+ }
+
+ // cleanup affected map part for reloading case
+ for (TrinityStringLocaleMap::iterator itr = mTrinityStringLocaleMap.begin(); itr != mTrinityStringLocaleMap.end();)
+ {
+ if (itr->first >= start_value && itr->first < end_value)
+ mTrinityStringLocaleMap.erase(itr++);
+ else
+ ++itr;
+ }
+
+ QueryResult_AutoPtr result = db.PQuery("SELECT entry,content_default,content_loc1,content_loc2,content_loc3,content_loc4,content_loc5,content_loc6,content_loc7,content_loc8 FROM %s",table);
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ if (min_value == MIN_TRINITY_STRING_ID) // error only in case internal strings
+ sLog.outErrorDb(">> Loaded 0 trinity strings. DB table `%s` is empty. Cannot continue.",table);
+ else
+ sLog.outString(">> Loaded 0 string templates. DB table `%s` is empty.",table);
+ return false;
+ }
+
+ uint32 count = 0;
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ Field *fields = result->Fetch();
+ bar.step();
+
+ int32 entry = fields[0].GetInt32();
+
+ if (entry == 0)
+ {
+ sLog.outErrorDb("Table `%s` contain reserved entry 0, ignored.",table);
+ continue;
+ }
+ else if (entry < start_value || entry >= end_value)
+ {
+ sLog.outErrorDb("Table `%s` contain entry %i out of allowed range (%d - %d), ignored.",table,entry,min_value,max_value);
+ continue;
+ }
+
+ TrinityStringLocale& data = mTrinityStringLocaleMap[entry];
+
+ if (data.Content.size() > 0)
+ {
+ sLog.outErrorDb("Table `%s` contain data for already loaded entry %i (from another table?), ignored.",table,entry);
+ continue;
+ }
+
+ data.Content.resize(1);
+ ++count;
+
+ // 0 -> default, idx in to idx+1
+ data.Content[0] = fields[1].GetCppString();
+
+ for (uint8 i = 1; i < MAX_LOCALE; ++i)
+ {
+ std::string str = fields[i+1].GetCppString();
+ if (!str.empty())
+ {
+ int idx = GetOrNewIndexForLocale(LocaleConstant(i));
+ if (idx >= 0)
+ {
+ // 0 -> default, idx in to idx+1
+ if (data.Content.size() <= idx+1)
+ data.Content.resize(idx+2);
+
+ data.Content[idx+1] = str;
+ }
+ }
+ }
+ } while (result->NextRow());
+
+ sLog.outString();
+ if (min_value == MIN_TRINITY_STRING_ID)
+ sLog.outString(">> Loaded %u Trinity strings from table %s", count,table);
+ else
+ sLog.outString(">> Loaded %u string templates from %s", count,table);
+
+ return true;
+}
+
+const char *ObjectMgr::GetTrinityString(int32 entry, int locale_idx) const
+{
+ // locale_idx == -1 -> default, locale_idx >= 0 in to idx+1
+ // Content[0] always exist if exist TrinityStringLocale
+ if (TrinityStringLocale const *msl = GetTrinityStringLocale(entry))
+ {
+ if (msl->Content.size() > locale_idx+1 && !msl->Content[locale_idx+1].empty())
+ return msl->Content[locale_idx+1].c_str();
+ else
+ return msl->Content[0].c_str();
+ }
+
+ if (entry > 0)
+ sLog.outErrorDb("Entry %i not found in `trinity_string` table.",entry);
+ else
+ sLog.outErrorDb("Trinity string entry %i not found in DB.",entry);
+ return "<error>";
+}
+
+void ObjectMgr::LoadSpellDisabledEntrys()
+{
+ m_DisabledPlayerSpells.clear(); // need for reload case
+ m_DisabledCreatureSpells.clear();
+ m_DisabledPetSpells.clear();
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, disable_mask FROM spell_disabled");
+
+ uint32 total_count = 0;
+
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u disabled spells", total_count);
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ Field* fields;
+ do
+ {
+ bar.step();
+ fields = result->Fetch();
+ uint32 spellid = fields[0].GetUInt32();
+ if (!sSpellStore.LookupEntry(spellid))
+ {
+ sLog.outErrorDb("Spell entry %u from `spell_disabled` doesn't exist in dbc, ignoring.",spellid);
+ continue;
+ }
+ uint32 disable_mask = fields[1].GetUInt32();
+ if (disable_mask & SPELL_DISABLE_PLAYER)
+ m_DisabledPlayerSpells.insert(spellid);
+ if (disable_mask & SPELL_DISABLE_CREATURE)
+ m_DisabledCreatureSpells.insert(spellid);
+ if (disable_mask & SPELL_DISABLE_PET)
+ m_DisabledPetSpells.insert(spellid);
+ ++total_count;
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u disabled spells from `spell_disabled`", total_count);
+}
+
+void ObjectMgr::LoadFishingBaseSkillLevel()
+{
+ mFishingBaseForArea.clear(); // for reload case
+
+ uint32 count = 0;
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,skill FROM skill_fishing_base_level");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded `skill_fishing_base_level`, table is empty!");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ bar.step();
+
+ Field *fields = result->Fetch();
+ uint32 entry = fields[0].GetUInt32();
+ int32 skill = fields[1].GetInt32();
+
+ AreaTableEntry const* fArea = GetAreaEntryByAreaID(entry);
+ if (!fArea)
+ {
+ sLog.outErrorDb("AreaId %u defined in `skill_fishing_base_level` does not exist",entry);
+ continue;
+ }
+
+ mFishingBaseForArea[entry] = skill;
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u areas for fishing base skill level", count);
+}
+
+bool ObjectMgr::CheckDeclinedNames(std::wstring mainpart, DeclinedName const& names)
+{
+ for (uint8 i =0; i < MAX_DECLINED_NAME_CASES; ++i)
+ {
+ std::wstring wname;
+ if (!Utf8toWStr(names.name[i],wname))
+ return false;
+
+ if (mainpart != GetMainPartOfName(wname,i+1))
+ return false;
+ }
+ return true;
+}
+
+uint32 ObjectMgr::GetAreaTriggerScriptId(uint32 trigger_id)
+{
+ AreaTriggerScriptMap::const_iterator i = mAreaTriggerScripts.find(trigger_id);
+ if (i!= mAreaTriggerScripts.end())
+ return i->second;
+ return 0;
+}
+
+SkillRangeType GetSkillRangeType(SkillLineEntry const *pSkill, bool racial)
+{
+ switch(pSkill->categoryId)
+ {
+ case SKILL_CATEGORY_LANGUAGES: return SKILL_RANGE_LANGUAGE;
+ case SKILL_CATEGORY_WEAPON:
+ if (pSkill->id != SKILL_FIST_WEAPONS)
+ return SKILL_RANGE_LEVEL;
+ else
+ return SKILL_RANGE_MONO;
+ case SKILL_CATEGORY_ARMOR:
+ case SKILL_CATEGORY_CLASS:
+ if (pSkill->id != SKILL_LOCKPICKING)
+ return SKILL_RANGE_MONO;
+ else
+ return SKILL_RANGE_LEVEL;
+ case SKILL_CATEGORY_SECONDARY:
+ case SKILL_CATEGORY_PROFESSION:
+ // not set skills for professions and racial abilities
+ if (IsProfessionSkill(pSkill->id))
+ return SKILL_RANGE_RANK;
+ else if (racial)
+ return SKILL_RANGE_NONE;
+ else
+ return SKILL_RANGE_MONO;
+ default:
+ case SKILL_CATEGORY_ATTRIBUTES: //not found in dbc
+ case SKILL_CATEGORY_GENERIC: //only GENERIC(DND)
+ return SKILL_RANGE_NONE;
+ }
+}
+
+void ObjectMgr::LoadGameTele()
+{
+ m_GameTeleMap.clear(); // for reload case
+
+ uint32 count = 0;
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, position_x, position_y, position_z, orientation, map, name FROM game_tele");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded `game_tele`, table is empty!");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ bar.step();
+
+ Field *fields = result->Fetch();
+
+ uint32 id = fields[0].GetUInt32();
+
+ GameTele gt;
+
+ gt.position_x = fields[1].GetFloat();
+ gt.position_y = fields[2].GetFloat();
+ gt.position_z = fields[3].GetFloat();
+ gt.orientation = fields[4].GetFloat();
+ gt.mapId = fields[5].GetUInt32();
+ gt.name = fields[6].GetCppString();
+
+ if (!MapManager::IsValidMapCoord(gt.mapId,gt.position_x,gt.position_y,gt.position_z,gt.orientation))
+ {
+ sLog.outErrorDb("Wrong position for id %u (name: %s) in `game_tele` table, ignoring.",id,gt.name.c_str());
+ continue;
+ }
+
+ if (!Utf8toWStr(gt.name,gt.wnameLow))
+ {
+ sLog.outErrorDb("Wrong UTF8 name for id %u in `game_tele` table, ignoring.",id);
+ continue;
+ }
+
+ wstrToLower(gt.wnameLow);
+
+ m_GameTeleMap[id] = gt;
+
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u GameTeleports", count);
+}
+
+GameTele const* ObjectMgr::GetGameTele(const std::string& name) const
+{
+ // explicit name case
+ std::wstring wname;
+ if (!Utf8toWStr(name,wname))
+ return false;
+
+ // converting string that we try to find to lower case
+ wstrToLower(wname);
+
+ // Alternative first GameTele what contains wnameLow as substring in case no GameTele location found
+ const GameTele* alt = NULL;
+ for (GameTeleMap::const_iterator itr = m_GameTeleMap.begin(); itr != m_GameTeleMap.end(); ++itr)
+ {
+ if (itr->second.wnameLow == wname)
+ return &itr->second;
+ else if (alt == NULL && itr->second.wnameLow.find(wname) != std::wstring::npos)
+ alt = &itr->second;
+ }
+
+ return alt;
+}
+
+bool ObjectMgr::AddGameTele(GameTele& tele)
+{
+ // find max id
+ uint32 new_id = 0;
+ for (GameTeleMap::const_iterator itr = m_GameTeleMap.begin(); itr != m_GameTeleMap.end(); ++itr)
+ if (itr->first > new_id)
+ new_id = itr->first;
+
+ // use next
+ ++new_id;
+
+ if (!Utf8toWStr(tele.name,tele.wnameLow))
+ return false;
+
+ wstrToLower(tele.wnameLow);
+
+ m_GameTeleMap[new_id] = tele;
+
+ return WorldDatabase.PExecuteLog("INSERT INTO game_tele (id,position_x,position_y,position_z,orientation,map,name) VALUES (%u,%f,%f,%f,%f,%d,'%s')",
+ new_id,tele.position_x,tele.position_y,tele.position_z,tele.orientation,tele.mapId,tele.name.c_str());
+}
+
+bool ObjectMgr::DeleteGameTele(const std::string& name)
+{
+ // explicit name case
+ std::wstring wname;
+ if (!Utf8toWStr(name,wname))
+ return false;
+
+ // converting string that we try to find to lower case
+ wstrToLower(wname);
+
+ for (GameTeleMap::iterator itr = m_GameTeleMap.begin(); itr != m_GameTeleMap.end(); ++itr)
+ {
+ if (itr->second.wnameLow == wname)
+ {
+ WorldDatabase.PExecuteLog("DELETE FROM game_tele WHERE name = '%s'",itr->second.name.c_str());
+ m_GameTeleMap.erase(itr);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ObjectMgr::LoadMailLevelRewards()
+{
+ m_mailLevelRewardMap.clear(); // for reload case
+
+ uint32 count = 0;
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT level, raceMask, mailTemplateId, senderEntry FROM mail_level_reward");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded `mail_level_reward`, table is empty!");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ do
+ {
+ bar.step();
+
+ Field *fields = result->Fetch();
+
+ uint8 level = fields[0].GetUInt8();
+ uint32 raceMask = fields[1].GetUInt32();
+ uint32 mailTemplateId = fields[2].GetUInt32();
+ uint32 senderEntry = fields[3].GetUInt32();
+
+ if (level > MAX_LEVEL)
+ {
+ sLog.outErrorDb("Table `mail_level_reward` have data for level %u that more supported by client (%u), ignoring.",level,MAX_LEVEL);
+ continue;
+ }
+
+ if (!(raceMask & RACEMASK_ALL_PLAYABLE))
+ {
+ sLog.outErrorDb("Table `mail_level_reward` have raceMask (%u) for level %u that not include any player races, ignoring.",raceMask,level);
+ continue;
+ }
+
+ if (!sMailTemplateStore.LookupEntry(mailTemplateId))
+ {
+ sLog.outErrorDb("Table `mail_level_reward` have invalid mailTemplateId (%u) for level %u that invalid not include any player races, ignoring.",mailTemplateId,level);
+ continue;
+ }
+
+ if (!GetCreatureTemplateStore(senderEntry))
+ {
+ sLog.outErrorDb("Table `mail_level_reward` have not existed sender creature entry (%u) for level %u that invalid not include any player races, ignoring.",senderEntry,level);
+ continue;
+ }
+
+ m_mailLevelRewardMap[level].push_back(MailLevelReward(raceMask,mailTemplateId,senderEntry));
+
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u level dependent mail rewards,", count);
+}
+
+bool ObjectMgr::AddSpellToTrainer(uint32 entry, uint32 spell, Field *fields, std::set<uint32> *skip_trainers, std::set<uint32> *talentIds)
+{
+ if (entry >= TRINITY_TRAINER_START_REF)
+ return false;
+
+ CreatureInfo const* cInfo = GetCreatureTemplate(entry);
+ if (!cInfo)
+ {
+ sLog.outErrorDb("Table `npc_trainer` have entry for not existed creature template (Entry: %u), ignore", entry);
+ return false;
+ }
+
+ if (!(cInfo->npcflag & UNIT_NPC_FLAG_TRAINER))
+ {
+ if (skip_trainers->find(entry) == skip_trainers->end())
+ {
+ sLog.outErrorDb("Table `npc_trainer` have data for not creature template (Entry: %u) without trainer flag, ignore", entry);
+ skip_trainers->insert(entry);
+ }
+ return false;
+ }
+
+ SpellEntry const *spellinfo = sSpellStore.LookupEntry(spell);
+ if (!spellinfo)
+ {
+ sLog.outErrorDb("Table `npc_trainer` for Trainer (Entry: %u) has non existing spell %u, ignore", entry,spell);
+ return false;
+ }
+
+ if (!SpellMgr::IsSpellValid(spellinfo))
+ {
+ sLog.outErrorDb("Table `npc_trainer` for Trainer (Entry: %u) has broken learning spell %u, ignore", entry, spell);
+ return false;
+ }
+
+ if (GetTalentSpellCost(spell))
+ {
+ if (talentIds->count(spell) == 0)
+ {
+ sLog.outErrorDb("Table `npc_trainer` has talent as learning spell %u, ignore", spell);
+ talentIds->insert(spell);
+ }
+ return false;
+ }
+
+ TrainerSpellData& data = m_mCacheTrainerSpellMap[entry];
+
+ TrainerSpell& trainerSpell = data.spellList[spell];
+ trainerSpell.spell = spell;
+ trainerSpell.spellCost = fields[2].GetUInt32();
+ trainerSpell.reqSkill = fields[3].GetUInt32();
+ trainerSpell.reqSkillValue = fields[4].GetUInt32();
+ trainerSpell.reqLevel = fields[5].GetUInt32();
+
+ if (!trainerSpell.reqLevel)
+ trainerSpell.reqLevel = spellinfo->spellLevel;
+
+ // calculate learned spell for profession case when stored cast-spell
+ trainerSpell.learnedSpell[0] = spell;
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ {
+ if (spellinfo->Effect[i] != SPELL_EFFECT_LEARN_SPELL)
+ continue;
+ if (trainerSpell.learnedSpell[0] == spell)
+ trainerSpell.learnedSpell[0] = 0;
+ // player must be able to cast spell on himself
+ if (spellinfo->EffectImplicitTargetA[i] != 0 && spellinfo->EffectImplicitTargetA[i] != TARGET_UNIT_TARGET_ALLY
+ && spellinfo->EffectImplicitTargetA[i] != TARGET_UNIT_TARGET_ANY && spellinfo->EffectImplicitTargetA[i] != TARGET_UNIT_CASTER)
+ {
+ sLog.outErrorDb("Table `npc_trainer` has spell %u for trainer entry %u with learn effect which has incorrect target type, ignoring learn effect!", spell, entry);
+ continue;
+ }
+
+ trainerSpell.learnedSpell[i] = spellinfo->EffectTriggerSpell[i];
+ }
+
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ {
+ if (!trainerSpell.learnedSpell[i])
+ continue;
+ if (SpellMgr::IsProfessionSpell(trainerSpell.learnedSpell[i]))
+ {
+ data.trainerType = 2;
+ break;
+ }
+ }
+ return true;
+}
+int ObjectMgr::LoadReferenceTrainer(uint32 trainer, int32 spell, std::set<uint32> *skip_trainers, std::set<uint32> *talentIds)
+{
+ QueryResult_AutoPtr result = WorldDatabase.PQuery("SELECT entry, spell,spellcost,reqskill,reqskillvalue,reqlevel FROM npc_trainer WHERE entry='%d'", spell);
+ if (!result)
+ return 0;
+
+ uint32 count = 0;
+ do
+ {
+
+ Field* fields = result->Fetch();
+
+ int32 spell = fields[1].GetInt32();
+ if (spell < 0)
+ count += this->LoadReferenceTrainer(trainer, -spell, skip_trainers, talentIds);
+ else if (this->AddSpellToTrainer(trainer, uint32(spell), fields, skip_trainers, talentIds))
+ ++count;
+ } while (result->NextRow());
+
+ return count;
+}
+
+void ObjectMgr::LoadTrainerSpell()
+{
+ // For reload case
+ for (CacheTrainerSpellMap::iterator itr = m_mCacheTrainerSpellMap.begin(); itr != m_mCacheTrainerSpellMap.end(); ++itr)
+ itr->second.Clear();
+ m_mCacheTrainerSpellMap.clear();
+
+ std::set<uint32> skip_trainers;
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, spell,spellcost,reqskill,reqskillvalue,reqlevel FROM npc_trainer");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded `npc_trainer`, table is empty!");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ std::set<uint32> talentIds;
+
+ uint32 count = 0;
+ do
+ {
+ bar.step();
+
+ Field* fields = result->Fetch();
+
+ uint32 entry = fields[0].GetUInt32();
+ int32 spell = fields[1].GetInt32();
+ if (spell < 0)
+ count += this->LoadReferenceTrainer(entry, -spell, &skip_trainers, &talentIds);
+ else if (this->AddSpellToTrainer(entry, uint32(spell), fields, &skip_trainers, &talentIds))
+ ++count;
+
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %d Trainers", count);
+}
+
+int ObjectMgr::LoadReferenceVendor(int32 vendor, int32 item, std::set<uint32> *skip_vendors)
+{
+ // find all items from the reference vendor
+ QueryResult_AutoPtr result = WorldDatabase.PQuery("SELECT item, maxcount, incrtime, ExtendedCost FROM npc_vendor WHERE entry='%d' ORDER BY slot ASC", item);
+ if (!result)
+ return 0;
+
+ uint32 count = 0;
+ do
+ {
+ Field* fields = result->Fetch();
+
+ int32 item_id = fields[0].GetInt32();
+
+ // if item is a negative, its a reference
+ if (item_id < 0)
+ count += LoadReferenceVendor(vendor, -item_id, skip_vendors);
+ else
+ {
+ int32 maxcount = fields[1].GetInt32();
+ uint32 incrtime = fields[2].GetUInt32();
+ uint32 ExtendedCost = fields[3].GetUInt32();
+
+ if (!IsVendorItemValid(vendor,item_id,maxcount,incrtime,ExtendedCost,NULL,skip_vendors))
+ continue;
+
+ VendorItemData& vList = m_mCacheVendorItemMap[vendor];
+
+ vList.AddItem(item_id,maxcount,incrtime,ExtendedCost);
+ ++count;
+ }
+
+ } while (result->NextRow());
+
+ return count;
+}
+
+void ObjectMgr::LoadVendors()
+{
+ // For reload case
+ for (CacheVendorItemMap::iterator itr = m_mCacheVendorItemMap.begin(); itr != m_mCacheVendorItemMap.end(); ++itr)
+ itr->second.Clear();
+ m_mCacheVendorItemMap.clear();
+
+ std::set<uint32> skip_vendors;
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, item, maxcount, incrtime, ExtendedCost FROM npc_vendor ORDER BY entry, slot ASC");
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded `npc_vendor`, table is empty!");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ uint32 count = 0;
+ do
+ {
+ bar.step();
+ Field* fields = result->Fetch();
+
+ uint32 entry = fields[0].GetUInt32();
+ int32 item_id = fields[1].GetInt32();
+
+ // if item is a negative, its a reference
+ if (item_id < 0)
+ count += LoadReferenceVendor(entry, -item_id, &skip_vendors);
+ else
+ {
+ int32 maxcount = fields[2].GetInt32();
+ uint32 incrtime = fields[3].GetUInt32();
+ uint32 ExtendedCost = fields[4].GetUInt32();
+
+ if (!IsVendorItemValid(entry,item_id,maxcount,incrtime,ExtendedCost,NULL,&skip_vendors))
+ continue;
+
+ VendorItemData& vList = m_mCacheVendorItemMap[entry];
+
+ vList.AddItem(item_id,maxcount,incrtime,ExtendedCost);
+ ++count;
+ }
+
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %d Vendors ", count);
+}
+
+void ObjectMgr::LoadNpcTextId()
+{
+
+ m_mCacheNpcTextIdMap.clear();
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT npc_guid, textid FROM npc_gossip");
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded `npc_gossip`, table is empty!");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ uint32 count = 0;
+ uint32 guid,textid;
+ do
+ {
+ bar.step();
+
+ Field* fields = result->Fetch();
+
+ guid = fields[0].GetUInt32();
+ textid = fields[1].GetUInt32();
+
+ if (!GetCreatureData(guid))
+ {
+ sLog.outErrorDb("Table `npc_gossip` have not existed creature (GUID: %u) entry, ignore. ",guid);
+ continue;
+ }
+ if (!GetGossipText(textid))
+ {
+ sLog.outErrorDb("Table `npc_gossip` for creature (GUID: %u) have wrong Textid (%u), ignore. ", guid, textid);
+ continue;
+ }
+
+ m_mCacheNpcTextIdMap[guid] = textid ;
+ ++count;
+
+ } while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %d NpcTextId ", count);
+}
+
+void ObjectMgr::LoadGossipMenu()
+{
+ m_mGossipMenusMap.clear();
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, text_id FROM gossip_menu");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded `gossip_menu`, table is empty!");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ uint32 count = 0;
+
+ do
+ {
+ bar.step();
+
+ Field* fields = result->Fetch();
+
+ GossipMenus gMenu;
+
+ gMenu.entry = fields[0].GetUInt32();
+ gMenu.text_id = fields[1].GetUInt32();
+
+ if (!GetGossipText(gMenu.text_id))
+ {
+ sLog.outErrorDb("Table gossip_menu entry %u are using non-existing text_id %u", gMenu.entry, gMenu.text_id);
+ continue;
+ }
+
+ m_mGossipMenusMap.insert(GossipMenusMap::value_type(gMenu.entry, gMenu));
+
+ ++count;
+ }
+ while (result->NextRow());
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u gossip_menu entries", count);
+}
+
+void ObjectMgr::LoadGossipMenuItems()
+{
+ m_mGossipMenuItemsMap.clear();
+
+ QueryResult_AutoPtr result = WorldDatabase.Query(
+ "SELECT menu_id, id, option_icon, option_text, option_id, npc_option_npcflag, "
+ "action_menu_id, action_poi_id, action_script_id, box_coded, box_money, box_text "
+ "FROM gossip_menu_option");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+
+ bar.step();
+
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded gossip_menu_option, table is empty!");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ uint32 count = 0;
+
+ std::set<uint32> gossipScriptSet;
+
+ for (ScriptMapMap::const_iterator itr = sGossipScripts.begin(); itr != sGossipScripts.end(); ++itr)
+ gossipScriptSet.insert(itr->first);
+
+ do
+ {
+ bar.step();
+
+ Field* fields = result->Fetch();
+
+ GossipMenuItems gMenuItem;
+
+ gMenuItem.menu_id = fields[0].GetUInt32();
+ gMenuItem.id = fields[1].GetUInt32();
+ gMenuItem.option_icon = fields[2].GetUInt8();
+ gMenuItem.option_text = fields[3].GetCppString();
+ gMenuItem.option_id = fields[4].GetUInt32();
+ gMenuItem.npc_option_npcflag = fields[5].GetUInt32();
+ gMenuItem.action_menu_id = fields[6].GetUInt32();
+ gMenuItem.action_poi_id = fields[7].GetUInt32();
+ gMenuItem.action_script_id = fields[8].GetUInt32();
+ gMenuItem.box_coded = fields[9].GetUInt8() != 0;
+ gMenuItem.box_money = fields[10].GetUInt32();
+ gMenuItem.box_text = fields[11].GetCppString();
+
+ if (gMenuItem.option_icon >= GOSSIP_ICON_MAX)
+ {
+ sLog.outErrorDb("Table gossip_menu_option for menu %u, id %u has unknown icon id %u. Replacing with GOSSIP_ICON_CHAT", gMenuItem.menu_id, gMenuItem.id, gMenuItem.option_icon);
+ gMenuItem.option_icon = GOSSIP_ICON_CHAT;
+ }
+
+ if (gMenuItem.option_id >= GOSSIP_OPTION_MAX)
+ sLog.outErrorDb("Table gossip_menu_option for menu %u, id %u has unknown option id %u. Option will not be used", gMenuItem.menu_id, gMenuItem.id, gMenuItem.option_id);
+
+ if (gMenuItem.action_poi_id && !GetPointOfInterest(gMenuItem.action_poi_id))
+ {
+ sLog.outErrorDb("Table gossip_menu_option for menu %u, id %u use non-existing action_poi_id %u, ignoring", gMenuItem.menu_id, gMenuItem.id, gMenuItem.action_poi_id);
+ gMenuItem.action_poi_id = 0;
+ }
+
+ if (gMenuItem.action_script_id)
+ {
+ if (gMenuItem.option_id != GOSSIP_OPTION_GOSSIP)
+ {
+ sLog.outErrorDb("Table gossip_menu_option for menu %u, id %u have action_script_id %u but option_id is not GOSSIP_OPTION_GOSSIP, ignoring", gMenuItem.menu_id, gMenuItem.id, gMenuItem.action_script_id);
+ continue;
+ }
+
+ if (sGossipScripts.find(gMenuItem.action_script_id) == sGossipScripts.end())
+ {
+ sLog.outErrorDb("Table gossip_menu_option for menu %u, id %u have action_script_id %u that does not exist in `gossip_scripts`, ignoring", gMenuItem.menu_id, gMenuItem.id, gMenuItem.action_script_id);
+ continue;
+ }
+
+ gossipScriptSet.erase(gMenuItem.action_script_id);
+ }
+
+ m_mGossipMenuItemsMap.insert(GossipMenuItemsMap::value_type(gMenuItem.menu_id, gMenuItem));
+
+ ++count;
+
+ }
+ while (result->NextRow());
+
+ if (!gossipScriptSet.empty())
+ {
+ for (std::set<uint32>::const_iterator itr = gossipScriptSet.begin(); itr != gossipScriptSet.end(); ++itr)
+ sLog.outErrorDb("Table `gossip_scripts` contain unused script, id %u.", *itr);
+ }
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u gossip_menu_option entries", count);
+}
+
+void ObjectMgr::AddVendorItem(uint32 entry,uint32 item, int32 maxcount, uint32 incrtime, uint32 extendedcost, bool savetodb)
+{
+ VendorItemData& vList = m_mCacheVendorItemMap[entry];
+ vList.AddItem(item,maxcount,incrtime,extendedcost);
+
+ if (savetodb) WorldDatabase.PExecuteLog("INSERT INTO npc_vendor (entry,item,maxcount,incrtime,extendedcost) VALUES('%u','%u','%u','%u','%u')",entry, item, maxcount,incrtime,extendedcost);
+}
+
+bool ObjectMgr::RemoveVendorItem(uint32 entry,uint32 item, bool savetodb)
+{
+ CacheVendorItemMap::iterator iter = m_mCacheVendorItemMap.find(entry);
+ if (iter == m_mCacheVendorItemMap.end())
+ return false;
+
+ if(!iter->second.RemoveItem(item))
+ return false;
+
+ if (savetodb) WorldDatabase.PExecuteLog("DELETE FROM npc_vendor WHERE entry='%u' AND item='%u'",entry, item);
+ return true;
+}
+
+bool ObjectMgr::IsVendorItemValid(uint32 vendor_entry, uint32 item_id, int32 maxcount, uint32 incrtime, uint32 ExtendedCost, Player* pl, std::set<uint32>* skip_vendors, uint32 ORnpcflag) const
+{
+ CreatureInfo const* cInfo = GetCreatureTemplate(vendor_entry);
+ if (!cInfo)
+ {
+ if (pl)
+ ChatHandler(pl).SendSysMessage(LANG_COMMAND_VENDORSELECTION);
+ else
+ sLog.outErrorDb("Table `(game_event_)npc_vendor` have data for not existed creature template (Entry: %u), ignore", vendor_entry);
+ return false;
+ }
+
+ if (!((cInfo->npcflag | ORnpcflag) & UNIT_NPC_FLAG_VENDOR))
+ {
+ if (!skip_vendors || skip_vendors->count(vendor_entry) == 0)
+ {
+ if (pl)
+ ChatHandler(pl).SendSysMessage(LANG_COMMAND_VENDORSELECTION);
+ else
+ sLog.outErrorDb("Table `(game_event_)npc_vendor` have data for not creature template (Entry: %u) without vendor flag, ignore", vendor_entry);
+
+ if (skip_vendors)
+ skip_vendors->insert(vendor_entry);
+ }
+ return false;
+ }
+
+ if (!GetItemPrototype(item_id))
+ {
+ if (pl)
+ ChatHandler(pl).PSendSysMessage(LANG_ITEM_NOT_FOUND, item_id);
+ else
+ sLog.outErrorDb("Table `(game_event_)npc_vendor` for Vendor (Entry: %u) have in item list non-existed item (%u), ignore",vendor_entry,item_id);
+ return false;
+ }
+
+ if (ExtendedCost && !sItemExtendedCostStore.LookupEntry(ExtendedCost))
+ {
+ if (pl)
+ ChatHandler(pl).PSendSysMessage(LANG_EXTENDED_COST_NOT_EXIST,ExtendedCost);
+ else
+ sLog.outErrorDb("Table `(game_event_)npc_vendor` have Item (Entry: %u) with wrong ExtendedCost (%u) for vendor (%u), ignore",item_id,ExtendedCost,vendor_entry);
+ return false;
+ }
+
+ if (maxcount > 0 && incrtime == 0)
+ {
+ if (pl)
+ ChatHandler(pl).PSendSysMessage("MaxCount != 0 (%u) but IncrTime == 0", maxcount);
+ else
+ sLog.outErrorDb("Table `(game_event_)npc_vendor` has `maxcount` (%u) for item %u of vendor (Entry: %u) but `incrtime`=0, ignore", maxcount, item_id, vendor_entry);
+ return false;
+ }
+ else if (maxcount == 0 && incrtime > 0)
+ {
+ if (pl)
+ ChatHandler(pl).PSendSysMessage("MaxCount == 0 but IncrTime<>= 0");
+ else
+ sLog.outErrorDb("Table `(game_event_)npc_vendor` has `maxcount`=0 for item %u of vendor (Entry: %u) but `incrtime`<>0, ignore", item_id, vendor_entry);
+ return false;
+ }
+
+ VendorItemData const* vItems = GetNpcVendorItemList(vendor_entry);
+ if (!vItems)
+ return true; // later checks for non-empty lists
+
+ if(vItems->FindItemCostPair(item_id,ExtendedCost))
+ {
+ if (pl)
+ ChatHandler(pl).PSendSysMessage(LANG_ITEM_ALREADY_IN_LIST, item_id, ExtendedCost);
+ else
+ sLog.outErrorDb( "Table `npc_vendor` has duplicate items %u (with extended cost %u) for vendor (Entry: %u), ignoring", item_id, ExtendedCost, vendor_entry);
+ return false;
+ }
+
+ if (vItems->GetItemCount() >= MAX_VENDOR_ITEMS)
+ {
+ if (pl)
+ ChatHandler(pl).SendSysMessage(LANG_COMMAND_ADDVENDORITEMITEMS);
+ else
+ sLog.outErrorDb("Table `npc_vendor` has too many items (%u >= %i) for vendor (Entry: %u), ignore", vItems->GetItemCount(), MAX_VENDOR_ITEMS, vendor_entry);
+ return false;
+ }
+
+ return true;
+}
+
+void ObjectMgr::LoadScriptNames()
+{
+ m_scriptNames.push_back("");
+ QueryResult_AutoPtr result = WorldDatabase.Query(
+ "SELECT DISTINCT(ScriptName) FROM creature_template WHERE ScriptName <> '' "
+ "UNION "
+ "SELECT DISTINCT(ScriptName) FROM gameobject_template WHERE ScriptName <> '' "
+ "UNION "
+ "SELECT DISTINCT(ScriptName) FROM item_template WHERE ScriptName <> '' "
+ "UNION "
+ "SELECT DISTINCT(ScriptName) FROM areatrigger_scripts WHERE ScriptName <> '' "
+ "UNION "
+ "SELECT DISTINCT(script) FROM instance_template WHERE script <> ''");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+ sLog.outString();
+ sLog.outErrorDb(">> Loaded empty set of Script Names!");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+
+ //OnEvent Changes
+ m_scriptNames.push_back("scripted_on_events");
+ uint32 count = 1;
+
+ do
+ {
+ bar.step();
+ m_scriptNames.push_back((*result)[0].GetString());
+ ++count;
+ } while (result->NextRow());
+
+ std::sort(m_scriptNames.begin(), m_scriptNames.end());
+ sLog.outString();
+ sLog.outString(">> Loaded %d Script Names", count);
+}
+
+uint32 ObjectMgr::GetScriptId(const char *name)
+{
+ // use binary search to find the script name in the sorted vector
+ // assume "" is the first element
+ if (!name) return 0;
+ ScriptNameMap::const_iterator itr =
+ std::lower_bound(m_scriptNames.begin(), m_scriptNames.end(), name);
+ if (itr == m_scriptNames.end() || *itr != name) return 0;
+ return itr - m_scriptNames.begin();
+}
+
+void ObjectMgr::CheckScripts(ScriptMapMap const& scripts,std::set<int32>& ids)
+{
+ for (ScriptMapMap::const_iterator itrMM = scripts.begin(); itrMM != scripts.end(); ++itrMM)
+ {
+ for (ScriptMap::const_iterator itrM = itrMM->second.begin(); itrM != itrMM->second.end(); ++itrM)
+ {
+ switch(itrM->second.command)
+ {
+ case SCRIPT_COMMAND_TALK:
+ {
+ if (!GetTrinityStringLocale (itrM->second.dataint))
+ sLog.outErrorDb("Table `db_script_string` not has string id %u used db script (ID: %u)", itrM->second.dataint, itrMM->first);
+
+ if (ids.find(itrM->second.dataint) != ids.end())
+ ids.erase(itrM->second.dataint);
+ }
+ }
+ }
+ }
+}
+
+void ObjectMgr::LoadDbScriptStrings()
+{
+ LoadTrinityStrings(WorldDatabase,"db_script_string",MIN_DB_SCRIPT_STRING_ID,MAX_DB_SCRIPT_STRING_ID);
+
+ std::set<int32> ids;
+
+ for (int32 i = MIN_DB_SCRIPT_STRING_ID; i < MAX_DB_SCRIPT_STRING_ID; ++i)
+ if (GetTrinityStringLocale(i))
+ ids.insert(i);
+
+ CheckScripts(sQuestEndScripts,ids);
+ CheckScripts(sQuestStartScripts,ids);
+ CheckScripts(sSpellScripts,ids);
+ CheckScripts(sGameObjectScripts,ids);
+ CheckScripts(sEventScripts,ids);
+
+ CheckScripts(sWaypointScripts,ids);
+
+ for (std::set<int32>::const_iterator itr = ids.begin(); itr != ids.end(); ++itr)
+ sLog.outErrorDb("Table `db_script_string` has unused string id %u", *itr);
+}
+
+// Functions for scripting access
+uint32 GetAreaTriggerScriptId(uint32 trigger_id)
+{
+ return objmgr.GetAreaTriggerScriptId(trigger_id);
+}
+
+bool LoadTrinityStrings(DatabaseType& db, char const* table,int32 start_value, int32 end_value)
+{
+ // MAX_DB_SCRIPT_STRING_ID is max allowed negative value for scripts (scrpts can use only more deep negative values
+ // start/end reversed for negative values
+ if (start_value > MAX_DB_SCRIPT_STRING_ID || end_value >= start_value)
+ {
+ sLog.outErrorDb("Table '%s' load attempted with range (%d - %d) reserved by Trinity, strings not loaded.",table,start_value,end_value+1);
+ return false;
+ }
+
+ return objmgr.LoadTrinityStrings(db,table,start_value,end_value);
+}
+
+uint32 GetScriptId(const char *name)
+{
+ return objmgr.GetScriptId(name);
+}
+
+ObjectMgr::ScriptNameMap & GetScriptNames()
+{
+ return objmgr.GetScriptNames();
+}
+
+GameObjectInfo const *GetGameObjectInfo(uint32 id)
+{
+ return objmgr.GetGameObjectInfo(id);
+}
+
+CreatureInfo const *GetCreatureInfo(uint32 id)
+{
+ return objmgr.GetCreatureTemplate(id);
+}
+
+void ObjectMgr::LoadTransportEvents()
+{
+
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, waypoint_id, event_id FROM transport_events");
+
+ if (!result)
+ {
+ barGoLink bar1(1);
+ bar1.step();
+ sLog.outString("\n>> Transport events table is empty \n");
+ return;
+ }
+
+ barGoLink bar1(result->GetRowCount());
+
+ do
+ {
+ bar1.step();
+
+ Field *fields = result->Fetch();
+
+ //Load event values
+ uint32 entry = fields[0].GetUInt32();
+ uint32 waypoint_id = fields[1].GetUInt32();
+ uint32 event_id = fields[2].GetUInt32();
+
+ uint32 event_count = (entry*100)+waypoint_id;
+ TransportEventMap[event_count] = event_id;
+ }
+ while (result->NextRow());
+
+ sLog.outString("\n>> Loaded %u transport events \n", result->GetRowCount());
+}
+
+CreatureInfo const* GetCreatureTemplateStore(uint32 entry)
+{
+ return sCreatureStorage.LookupEntry<CreatureInfo>(entry);
+}
+
+Quest const* GetQuestTemplateStore(uint32 entry)
+{
+ return objmgr.GetQuestTemplate(entry);
+}
+
+uint64 ObjectMgr::GenerateGMTicketId()
+{
+ return ++m_GMticketid;
+}
+
+void ObjectMgr::LoadGMTickets()
+{
+ if (!m_GMTicketList.empty())
+ {
+ for (GmTicketList::const_iterator itr = m_GMTicketList.begin(); itr != m_GMTicketList.end(); ++itr)
+ delete *itr;
+ }
+ m_GMTicketList.clear();
+ m_GMticketid = 0;
+
+ QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT guid, playerGuid, name, message, createtime, map, posX, posY, posZ, timestamp, closed, assignedto, comment FROM gm_tickets");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+ sLog.outString();
+ sLog.outString(">> GM Tickets table is empty, no tickets were loaded.");
+ return;
+ }
+
+ uint16 count = 0;
+ barGoLink bar ((*result).GetRowCount());
+ GM_Ticket *ticket;
+ do
+ {
+ Field *fields = result->Fetch();
+ ticket = new GM_Ticket;
+ ticket->guid = fields[0].GetUInt64();
+ ticket->playerGuid = fields[1].GetUInt64();
+ ticket->name = fields[2].GetCppString();
+ ticket->message = fields[3].GetCppString();
+ ticket->createtime = fields[4].GetUInt64();
+ ticket->map = fields[5].GetUInt32();
+ ticket->pos_x = fields[6].GetFloat();
+ ticket->pos_y = fields[7].GetFloat();
+ ticket->pos_z = fields[8].GetFloat();
+ ticket->timestamp = fields[9].GetUInt64();
+ ticket->closed = fields[10].GetUInt64();
+ ticket->assignedToGM = fields[11].GetUInt64();
+ ticket->comment = fields[12].GetCppString();
+ ++count;
+ bar.step();
+
+ m_GMTicketList.push_back(ticket);
+
+ } while (result->NextRow());
+
+ result = CharacterDatabase.Query("SELECT MAX(guid) from gm_tickets");
+
+ if (result)
+ {
+ Field *fields = result->Fetch();
+ m_GMticketid = fields[0].GetUInt64();
+ }
+
+ sLog.outString(">> Loaded %u GM Tickets from the database.", count);
+}
+
+void ObjectMgr::AddOrUpdateGMTicket(GM_Ticket &ticket, bool create)
+{
+ if (create)
+ m_GMTicketList.push_back(&ticket);
+
+ _AddOrUpdateGMTicket(ticket);
+}
+
+void ObjectMgr::_AddOrUpdateGMTicket(GM_Ticket &ticket)
+{
+ std::string msg(ticket.message), name(ticket.name), comment(ticket.comment);
+ CharacterDatabase.escape_string(msg);
+ CharacterDatabase.escape_string(name);
+ CharacterDatabase.escape_string(comment);
+ std::ostringstream ss;
+ ss << "REPLACE INTO gm_tickets (guid, playerGuid, name, message, createtime, map, posX, posY, posZ, timestamp, closed, assignedto, comment) VALUES('";
+ ss << ticket.guid << "', '";
+ ss << ticket.playerGuid << "', '";
+ ss << name << "', '";
+ ss << msg << "', '" ;
+ ss << ticket.createtime << "', '";
+ ss << ticket.map << "', '";
+ ss << ticket.pos_x << "', '";
+ ss << ticket.pos_y << "', '";
+ ss << ticket.pos_z << "', '";
+ ss << ticket.timestamp << "', '";
+ ss << ticket.closed << "', '";
+ ss << ticket.assignedToGM << "', '";
+ ss << comment << "');";
+ CharacterDatabase.BeginTransaction();
+ CharacterDatabase.Execute(ss.str().c_str());
+ CharacterDatabase.CommitTransaction();
+}
+
+void ObjectMgr::RemoveGMTicket(GM_Ticket *ticket, int64 source, bool permanently)
+{
+ for (GmTicketList::iterator i = m_GMTicketList.begin(); i != m_GMTicketList.end(); ++i)
+ if ((*i)->guid == ticket->guid)
+ {
+ if (permanently)
+ {
+ CharacterDatabase.PExecute("DELETE FROM gm_tickets WHERE guid = '%u'", ticket->guid);
+ i = m_GMTicketList.erase(i);
+ ticket = NULL;
+ return;
+ }
+ (*i)->closed = source;
+ _AddOrUpdateGMTicket(*(*i));
+ }
+}
+
+void ObjectMgr::RemoveGMTicket(uint64 ticketGuid, int64 source, bool permanently)
+{
+ GM_Ticket *ticket = GetGMTicket(ticketGuid);
+ assert(ticket);
+ RemoveGMTicket(ticket, source, permanently);
+}
+
+CreatureBaseStats const* ObjectMgr::GetCreatureBaseStats(uint8 level, uint8 unitClass)
+{
+ CreatureBaseStatsMap::const_iterator it = m_creatureBaseStatsMap.find(MAKE_PAIR16(level,unitClass));
+
+ if (it != m_creatureBaseStatsMap.end())
+ return &(it->second);
+
+ struct DefaultCreatureBaseStats : public CreatureBaseStats
+ {
+ DefaultCreatureBaseStats()
+ {
+ BaseArmor = 1;
+ for (uint8 j = 0; j < MAX_CREATURE_BASE_HP; ++j)
+ BaseHealth[j] = 1;
+ BaseMana = 0;
+ }
+ };
+ static const DefaultCreatureBaseStats def_stats;
+ return &def_stats;
+}
+
+void ObjectMgr::LoadCreatureClassLevelStats()
+{
+ QueryResult_AutoPtr result = WorldDatabase.Query("SELECT level, class, basehp0, basehp1, basehp2, basemana, basearmor FROM creature_classlevelstats");
+
+ if (!result)
+ {
+ barGoLink bar(1);
+ bar.step();
+ sLog.outString();
+ sLog.outString(">> Loaded 0 creature base stats. DB table `creature_classlevelstats` is empty.");
+ return;
+ }
+
+ barGoLink bar(result->GetRowCount());
+ uint32 counter = 0;
+
+ do
+ {
+ Field *fields = result->Fetch();
+
+ uint8 Level = fields[0].GetUInt32();
+ uint8 Class = fields[1].GetUInt8();
+
+ CreatureBaseStats stats;
+ for (uint8 i = 0; i < MAX_CREATURE_BASE_HP; ++i)
+ stats.BaseHealth[i] = fields[i + 2].GetUInt32();
+ stats.BaseMana = fields[5].GetUInt32();
+ stats.BaseArmor = fields[6].GetUInt32();
+
+ if (Level > STRONG_MAX_LEVEL)
+ {
+ sLog.outErrorDb("Creature base stats for class %u has invalid level %u (max is %u) - set to %u",
+ Class, Level, STRONG_MAX_LEVEL, STRONG_MAX_LEVEL);
+ Level = STRONG_MAX_LEVEL;
+ }
+
+ if (!Class || ((1 << (Class - 1)) & CLASSMASK_ALL_CREATURES) == 0)
+ sLog.outErrorDb("Creature base stats for level %u has invalid class %u",
+ Level, Class);
+
+ for (uint8 i = 0; i < MAX_CREATURE_BASE_HP; ++i)
+ {
+ if (stats.BaseHealth[i] < 1)
+ {
+ sLog.outErrorDb("Creature base stats for class %u, level %u has invalid zero base HP[%u] - set to 1",
+ Class, Level, i);
+ stats.BaseHealth[i] = 1;
+ }
+ }
+
+ m_creatureBaseStatsMap[MAKE_PAIR16(Level, Class)] = stats;
+
+ bar.step();
+ ++counter;
+ }
+ while (result->NextRow());
+
+ for (uint32 i = 0; i < sCreatureStorage.MaxEntry; ++i)
+ {
+ CreatureInfo const* info = sCreatureStorage.LookupEntry<CreatureInfo>(i);
+ if (!info)
+ continue;
+
+ for (uint16 lvl = info->minlevel; lvl <= info->maxlevel; ++lvl)
+ {
+ if (m_creatureBaseStatsMap.find(MAKE_PAIR16(lvl, info->unit_class)) == m_creatureBaseStatsMap.end())
+ sLog.outErrorDb("Missing base stats for creature class %u level %u", info->unit_class, lvl);
+ }
+ }
+
+ sLog.outString();
+ sLog.outString(">> Loaded %u creature base stats.", counter);
+}