mirror of
https://github.com/araxiaonline/AscEmu.git
synced 2026-06-13 03:02:22 -04:00
2850 lines
96 KiB
C++
2850 lines
96 KiB
C++
/*
|
|
* AscEmu Framework based on ArcEmu MMORPG Server
|
|
* Copyright (c) 2014-2023 AscEmu Team <http://www.ascemu.org>
|
|
* Copyright (C) 2008-2012 ArcEmu Team <http://www.ArcEmu.org/>
|
|
* Copyright (C) 2005-2007 Ascent Team
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include <utility>
|
|
|
|
#include "Storage/DBC/DBCStores.h"
|
|
#include "Management/QuestLogEntry.hpp"
|
|
#include "Objects/Container.hpp"
|
|
#include "Objects/Units/Stats.h"
|
|
#include "Management/ArenaTeam.hpp"
|
|
#include "Storage/MySQLDataStore.hpp"
|
|
#include "Storage/MySQLStructures.h"
|
|
#include "Objects/Units/Players/PlayerClasses.hpp"
|
|
#include "Server/MainServerDefines.h"
|
|
#include "Map/Maps/InstanceDefines.hpp"
|
|
#include "Map/Management/MapMgr.hpp"
|
|
#include "Map/Maps/MapScriptInterface.h"
|
|
#include "Spell/SpellMgr.hpp"
|
|
#include "Objects/Units/Creatures/Pet.h"
|
|
#include "Spell/Definitions/SpellEffects.hpp"
|
|
#include "Management/TaxiMgr.h"
|
|
#include "Management/LFG/LFGMgr.hpp"
|
|
#include "Movement/MovementManager.h"
|
|
#include "Objects/Units/Creatures/Summons/Summon.hpp"
|
|
#include "Utilities/Strings.hpp"
|
|
|
|
#if VERSION_STRING < Cata
|
|
#include "Management/Guild/Guild.hpp"
|
|
#endif
|
|
|
|
const char* NormalTalkMessage = "DMSG";
|
|
|
|
ObjectMgr& ObjectMgr::getInstance()
|
|
{
|
|
static ObjectMgr mInstance;
|
|
return mInstance;
|
|
}
|
|
|
|
void ObjectMgr::initialize()
|
|
{
|
|
m_hiItemGuid = 0;
|
|
m_hiGroupId = 0;
|
|
m_mailid = 0;
|
|
m_reportID = 0;
|
|
m_setGUID = 0;
|
|
m_hiCorpseGuid = 0;
|
|
m_hiGuildId = 0;
|
|
m_hiPetGuid = 0;
|
|
m_hiArenaTeamId = 0;
|
|
m_hiPlayerGuid = 1;
|
|
#if VERSION_STRING > WotLK
|
|
m_voidItemId = 1;
|
|
#endif
|
|
|
|
loadCreatureDisplayInfo();
|
|
}
|
|
|
|
void ObjectMgr::finalize()
|
|
{
|
|
sLogger.info("ObjectMgr : Deleting Corpses...");
|
|
unloadCorpseCollector();
|
|
|
|
sLogger.info("ObjectMgr : Deleting Vendors...");
|
|
for (VendorMap::iterator i = mVendors.begin(); i != mVendors.end(); ++i)
|
|
{
|
|
delete i->second;
|
|
}
|
|
|
|
sLogger.info("ObjectMgr : Deleting TrainserSpellSets...");
|
|
for (auto i = m_trainerSpellSet.begin(); i != m_trainerSpellSet.end(); ++i)
|
|
{
|
|
delete i->second;
|
|
}
|
|
|
|
sLogger.info("ObjectMgr : Deleting Trainers UIMessages...");
|
|
for (auto trainerPair : m_trainers)
|
|
{
|
|
auto trainer = trainerPair.second;
|
|
if (trainer->UIMessage && trainer->UIMessage != (char*)NormalTalkMessage)
|
|
delete[] trainer->UIMessage;
|
|
}
|
|
m_trainers.clear();
|
|
|
|
sLogger.info("ObjectMgr : Deleting Level Information...");
|
|
for (LevelInfoMap::iterator i = mLevelInfo.begin(); i != mLevelInfo.end(); ++i)
|
|
{
|
|
LevelMap* l = i->second;
|
|
for (LevelMap::iterator i2 = l->begin(); i2 != l->end(); ++i2)
|
|
{
|
|
delete i2->second;
|
|
}
|
|
|
|
l->clear();
|
|
delete l;
|
|
}
|
|
|
|
sLogger.info("ObjectMgr : Deleting timed emote Cache...");
|
|
for (std::unordered_map<uint32, TimedEmoteList*>::iterator i = m_timedemotes.begin(); i != m_timedemotes.end(); ++i)
|
|
{
|
|
for (TimedEmoteList::iterator i2 = i->second->begin(); i2 != i->second->end(); ++i2)
|
|
if ((*i2))
|
|
{
|
|
delete[](*i2)->msg;
|
|
delete(*i2);
|
|
}
|
|
|
|
delete i->second;
|
|
}
|
|
|
|
sLogger.info("ObjectMgr : Clearing Charters...");
|
|
for (auto& charter : m_charters)
|
|
charter.clear();
|
|
|
|
sLogger.info("ObjectMgr : Deleting Reputation Tables...");
|
|
for (ReputationModMap::iterator itr = m_reputation_creature.begin(); itr != m_reputation_creature.end(); ++itr)
|
|
{
|
|
ReputationModifier* mod = itr->second;
|
|
mod->mods.clear();
|
|
delete mod;
|
|
}
|
|
for (ReputationModMap::iterator itr = m_reputation_faction.begin(); itr != m_reputation_faction.end(); ++itr)
|
|
{
|
|
ReputationModifier* mod = itr->second;
|
|
mod->mods.clear();
|
|
delete mod;
|
|
}
|
|
|
|
for (std::unordered_map<uint32, InstanceReputationModifier*>::iterator itr = this->m_reputation_instance.begin(); itr != this->m_reputation_instance.end(); ++itr)
|
|
{
|
|
InstanceReputationModifier* mod = itr->second;
|
|
mod->mods.clear();
|
|
delete mod;
|
|
}
|
|
|
|
sLogger.info("ObjectMgr : Deleting Groups...");
|
|
for (GroupMap::iterator itr = m_groups.begin(); itr != m_groups.end();)
|
|
{
|
|
Group* pGroup = itr->second;
|
|
++itr;
|
|
if (pGroup != nullptr)
|
|
{
|
|
for (uint32 i = 0; i < pGroup->GetSubGroupCount(); ++i)
|
|
{
|
|
SubGroup* pSubGroup = pGroup->GetSubGroup(i);
|
|
if (pSubGroup != nullptr)
|
|
{
|
|
pSubGroup->Disband();
|
|
}
|
|
}
|
|
delete pGroup;
|
|
}
|
|
}
|
|
|
|
sLogger.info("ObjectMgr : Clearing Player Information...");
|
|
for (auto& itr : m_cachedCharacterInfo)
|
|
itr.second->m_Group = nullptr;
|
|
|
|
m_cachedCharacterInfo.clear();
|
|
|
|
sLogger.info("ObjectMgr : Clearing Boss Information...");
|
|
m_dungeonEncounterStore.clear();
|
|
|
|
sLogger.info("ObjectMgr : Clearing Arena Teams...");
|
|
m_arenaTeams.clear();
|
|
|
|
#ifdef FT_VEHICLES
|
|
sLogger.info("ObjectMgr : Cleaning up vehicle accessories...");
|
|
_vehicleAccessoryStore.clear();
|
|
_vehicleSeatAddonStore.clear();
|
|
#endif
|
|
|
|
sLogger.info("ObjectMgr : Cleaning up worldstate templates...");
|
|
for (std::map< uint32, std::multimap< uint32, WorldState >* >::iterator itr = m_worldstateTemplates.begin(); itr != m_worldstateTemplates.end(); ++itr)
|
|
{
|
|
itr->second->clear();
|
|
delete itr->second;
|
|
}
|
|
|
|
m_worldstateTemplates.clear();
|
|
|
|
m_creatureDisplayInfoData.clear();
|
|
|
|
sLogger.info("ObjectMgr : Clearing up event scripts...");
|
|
mEventScriptMaps.clear();
|
|
mSpellEffectMaps.clear();
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Arena Team
|
|
void ObjectMgr::loadArenaTeams()
|
|
{
|
|
QueryResult* result = CharacterDatabase.Query("SELECT * FROM arenateams");
|
|
if (result != nullptr)
|
|
{
|
|
if (result->GetFieldCount() != 22)
|
|
{
|
|
sLogger.failure("arenateams table format is invalid. Please update your database.");
|
|
return;
|
|
}
|
|
do
|
|
{
|
|
const std::shared_ptr<ArenaTeam> team = std::make_shared<ArenaTeam>(result->Fetch());
|
|
addArenaTeam(team);
|
|
if (team->m_id > static_cast<uint32_t>(m_hiArenaTeamId.load()))
|
|
m_hiArenaTeamId = static_cast<uint32_t>(team->m_id);
|
|
|
|
} while (result->NextRow());
|
|
delete result;
|
|
}
|
|
|
|
updateArenaTeamRankings();
|
|
}
|
|
|
|
void ObjectMgr::addArenaTeam(std::shared_ptr<ArenaTeam> _arenaTeam)
|
|
{
|
|
std::lock_guard guard(m_arenaTeamLock);
|
|
m_arenaTeams[_arenaTeam->m_id] = _arenaTeam;
|
|
m_arenaTeamMap[_arenaTeam->m_type].insert(std::make_pair(_arenaTeam->m_id, _arenaTeam));
|
|
}
|
|
|
|
void ObjectMgr::removeArenaTeam(std::shared_ptr<ArenaTeam> _arenaTeam)
|
|
{
|
|
std::lock_guard guard(m_arenaTeamLock);
|
|
m_arenaTeams.erase(_arenaTeam->m_id);
|
|
m_arenaTeamMap[_arenaTeam->m_type].erase(_arenaTeam->m_id);
|
|
}
|
|
|
|
std::shared_ptr<ArenaTeam> ObjectMgr::getArenaTeamByName(std::string& _name, uint32_t /*type*/)
|
|
{
|
|
std::lock_guard guard(m_arenaTeamLock);
|
|
for (auto& arenaTeam : m_arenaTeams)
|
|
if (arenaTeam.second->m_name == _name)
|
|
return arenaTeam.second;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<ArenaTeam> ObjectMgr::getArenaTeamById(uint32_t _id)
|
|
{
|
|
std::lock_guard guard(m_arenaTeamLock);
|
|
const auto arenaTeam = m_arenaTeams.find(_id);
|
|
return arenaTeam == m_arenaTeams.end() ? nullptr : arenaTeam->second;
|
|
}
|
|
|
|
std::shared_ptr<ArenaTeam> ObjectMgr::getArenaTeamByGuid(uint32_t _guid, uint32_t _type)
|
|
{
|
|
std::lock_guard guard(m_arenaTeamLock);
|
|
for (auto& arenaTeam : m_arenaTeamMap[_type])
|
|
{
|
|
if (arenaTeam.second->isMember(_guid))
|
|
return arenaTeam.second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
class ArenaSorter
|
|
{
|
|
public:
|
|
|
|
bool operator()(std::shared_ptr<ArenaTeam> const& _arenaTeamA, std::shared_ptr<ArenaTeam> const& _arenaTeamB) const
|
|
{
|
|
return (_arenaTeamA->m_stats.rating > _arenaTeamB->m_stats.rating);
|
|
}
|
|
|
|
bool operator()(std::shared_ptr<ArenaTeam>& _arenaTeamA, std::shared_ptr<ArenaTeam>& _arenaTeamB) const
|
|
{
|
|
return (_arenaTeamA->m_stats.rating > _arenaTeamB->m_stats.rating);
|
|
}
|
|
};
|
|
|
|
void ObjectMgr::updateArenaTeamRankings()
|
|
{
|
|
std::lock_guard guard(m_arenaTeamLock);
|
|
for (auto& arenaTeams : m_arenaTeamMap)
|
|
{
|
|
std::vector<std::shared_ptr<ArenaTeam>> ranking;
|
|
ranking.reserve(arenaTeams.size());
|
|
|
|
for (auto& arenaTeamPair : arenaTeams)
|
|
ranking.push_back(arenaTeamPair.second);
|
|
|
|
std::ranges::sort(ranking, ArenaSorter());
|
|
uint32_t rank = 1;
|
|
|
|
for (const auto& arenaTeam : ranking)
|
|
{
|
|
if (arenaTeam->m_stats.ranking != rank)
|
|
{
|
|
arenaTeam->m_stats.ranking = rank;
|
|
arenaTeam->saveToDB();
|
|
}
|
|
|
|
++rank;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ObjectMgr::updateArenaTeamWeekly()
|
|
{
|
|
std::lock_guard guard(m_arenaTeamLock);
|
|
for (auto& arenaTeams : m_arenaTeamMap)
|
|
{
|
|
for (const auto& arenaTeamPair : arenaTeams)
|
|
{
|
|
if (const std::shared_ptr<ArenaTeam> arenaTeam = arenaTeamPair.second)
|
|
{
|
|
arenaTeam->m_stats.played_week = 0;
|
|
arenaTeam->m_stats.won_week = 0;
|
|
|
|
for (uint32_t j = 0; j < arenaTeam->m_memberCount; ++j)
|
|
{
|
|
arenaTeam->m_members[j].Played_ThisWeek = 0;
|
|
arenaTeam->m_members[j].Won_ThisWeek = 0;
|
|
}
|
|
|
|
arenaTeam->saveToDB();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ObjectMgr::resetArenaTeamRatings()
|
|
{
|
|
std::lock_guard guard(m_arenaTeamLock);
|
|
for (auto& arenaTeams : m_arenaTeamMap)
|
|
{
|
|
for (auto& arenaTeamPair : arenaTeams)
|
|
{
|
|
if (const std::shared_ptr<ArenaTeam> arenaTeam = arenaTeamPair.second)
|
|
{
|
|
arenaTeam->m_stats.played_season = 0;
|
|
arenaTeam->m_stats.played_week = 0;
|
|
arenaTeam->m_stats.won_season = 0;
|
|
arenaTeam->m_stats.won_week = 0;
|
|
arenaTeam->m_stats.rating = 1500;
|
|
|
|
for (uint32_t j = 0; j < arenaTeam->m_memberCount; ++j)
|
|
{
|
|
arenaTeam->m_members[j].Played_ThisSeason = 0;
|
|
arenaTeam->m_members[j].Played_ThisWeek = 0;
|
|
arenaTeam->m_members[j].Won_ThisSeason = 0;
|
|
arenaTeam->m_members[j].Won_ThisWeek = 0;
|
|
arenaTeam->m_members[j].PersonalRating = 1500;
|
|
}
|
|
arenaTeam->saveToDB();
|
|
}
|
|
}
|
|
}
|
|
|
|
updateArenaTeamRankings();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Charter
|
|
void ObjectMgr::loadCharters()
|
|
{
|
|
m_hiCharterId = 0;
|
|
|
|
if (QueryResult* result = CharacterDatabase.Query("SELECT * FROM charters"))
|
|
{
|
|
do
|
|
{
|
|
auto charter = std::make_shared<Charter>(result->Fetch());
|
|
m_charters[charter->getCharterType()].insert(std::make_pair(charter->getId(), charter));
|
|
if (charter->getId() > static_cast<int64_t>(m_hiCharterId.load()))
|
|
m_hiCharterId = charter->getId();
|
|
|
|
} while (result->NextRow());
|
|
|
|
delete result;
|
|
}
|
|
sLogger.info("ObjectMgr : %u charters loaded.", static_cast<uint32_t>(m_charters[0].size()));
|
|
}
|
|
|
|
void ObjectMgr::removeCharter(const std::shared_ptr<Charter>& _charter)
|
|
{
|
|
if (_charter)
|
|
{
|
|
if (_charter->getCharterType() >= NUM_CHARTER_TYPES)
|
|
{
|
|
sLogger.debug("ObjectMgr : Charter %u cannot be destroyed as type %u is not a valid type.", _charter->getId(), static_cast<uint32_t>(_charter->getCharterType()));
|
|
return;
|
|
}
|
|
|
|
std::lock_guard guard(m_charterLock);
|
|
m_charters[_charter->getCharterType()].erase(_charter->getId());
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<Charter> ObjectMgr::createCharter(uint32_t _leaderGuid, CharterTypes _type)
|
|
{
|
|
uint32_t charterId = ++m_hiCharterId;
|
|
auto charter = std::make_shared<Charter>(charterId, _leaderGuid, _type);
|
|
|
|
std::lock_guard guard(m_charterLock);
|
|
m_charters[charter->getCharterType()].insert(std::make_pair(charter->getId(), charter));
|
|
|
|
return charter;
|
|
}
|
|
|
|
std::shared_ptr<Charter> ObjectMgr::getCharterByName(const std::string& _charterName, const CharterTypes _type)
|
|
{
|
|
std::lock_guard guard(m_charterLock);
|
|
|
|
for (auto& charterPair : m_charters[_type])
|
|
if (charterPair.second->getGuildName() == _charterName)
|
|
return charterPair.second;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<Charter> ObjectMgr::getCharter(const uint32_t _charterId, const CharterTypes _type)
|
|
{
|
|
std::lock_guard guard(m_charterLock);
|
|
const auto charterPair = m_charters[_type].find(_charterId);
|
|
return charterPair == m_charters[_type].end() ? nullptr : charterPair->second;
|
|
}
|
|
|
|
std::shared_ptr<Charter> ObjectMgr::getCharterByGuid(const uint64_t _playerGuid, const CharterTypes _type)
|
|
{
|
|
std::lock_guard guard(m_charterLock);
|
|
for (auto& charterPair : m_charters[_type])
|
|
{
|
|
if (_playerGuid == charterPair.second->getLeaderGuid())
|
|
return charterPair.second;
|
|
|
|
for (const uint32_t playerGuid : charterPair.second->getSignatures())
|
|
if (playerGuid == _playerGuid)
|
|
return charterPair.second;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<Charter> ObjectMgr::getCharterByItemGuid(const uint64_t _itemGuid)
|
|
{
|
|
std::lock_guard guard(m_charterLock);
|
|
for (auto& charterType : m_charters)
|
|
{
|
|
for (auto& charterPair : charterType)
|
|
if (charterPair.second->getItemGuid() == _itemGuid)
|
|
return charterPair.second;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// CachedCharacterInfo
|
|
void ObjectMgr::loadCharacters()
|
|
{
|
|
QueryResult* result = CharacterDatabase.Query("SELECT guid, name, race, class, level, gender, zoneid, timestamp, acct FROM characters");
|
|
if (result)
|
|
{
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
const auto cachedCharacterInfo = std::make_shared<CachedCharacterInfo>();
|
|
cachedCharacterInfo->guid = fields[0].GetUInt32();
|
|
|
|
std::string characterNameDB = fields[1].GetString();
|
|
AscEmu::Util::Strings::capitalize(characterNameDB);
|
|
|
|
cachedCharacterInfo->name = characterNameDB;
|
|
cachedCharacterInfo->race = fields[2].GetUInt8();
|
|
cachedCharacterInfo->cl = fields[3].GetUInt8();
|
|
cachedCharacterInfo->lastLevel = fields[4].GetUInt32();
|
|
cachedCharacterInfo->gender = fields[5].GetUInt8();
|
|
cachedCharacterInfo->lastZone = fields[6].GetUInt32();
|
|
cachedCharacterInfo->lastOnline = fields[7].GetUInt32();
|
|
cachedCharacterInfo->acct = fields[8].GetUInt32();
|
|
cachedCharacterInfo->m_Group = nullptr;
|
|
cachedCharacterInfo->subGroup = 0;
|
|
cachedCharacterInfo->m_guild = 0;
|
|
cachedCharacterInfo->guildRank = GUILD_RANK_NONE;
|
|
cachedCharacterInfo->team = getSideByRace(cachedCharacterInfo->race);
|
|
|
|
m_cachedCharacterInfo[cachedCharacterInfo->guid] = cachedCharacterInfo;
|
|
|
|
} while (result->NextRow());
|
|
delete result;
|
|
}
|
|
sLogger.info("ObjectMgr : %u players loaded.", static_cast<uint32_t>(m_cachedCharacterInfo.size()));
|
|
}
|
|
|
|
void ObjectMgr::addCachedCharacterInfo(const std::shared_ptr<CachedCharacterInfo>& _characterInfo)
|
|
{
|
|
std::lock_guard guard(m_cachedCharacterLock);
|
|
m_cachedCharacterInfo[_characterInfo->guid] = _characterInfo;
|
|
}
|
|
|
|
std::shared_ptr<CachedCharacterInfo> ObjectMgr::getCachedCharacterInfo(uint32_t _playerGuid)
|
|
{
|
|
std::lock_guard guard(m_cachedCharacterLock);
|
|
|
|
const auto characterPair = m_cachedCharacterInfo.find(_playerGuid);
|
|
if (characterPair != m_cachedCharacterInfo.end())
|
|
return characterPair->second;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<CachedCharacterInfo> ObjectMgr::getCachedCharacterInfoByName(std::string _playerName)
|
|
{
|
|
std::string searchName = std::string(std::move(_playerName));
|
|
AscEmu::Util::Strings::toLowerCase(searchName);
|
|
|
|
std::lock_guard guard(m_cachedCharacterLock);
|
|
|
|
for (const auto characterPair : m_cachedCharacterInfo)
|
|
{
|
|
std::string characterName = characterPair.second->name;
|
|
AscEmu::Util::Strings::toLowerCase(characterName);
|
|
if (characterName == searchName)
|
|
return characterPair.second;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ObjectMgr::updateCachedCharacterInfoName(const std::shared_ptr<CachedCharacterInfo>& _characterInfo, const std::string& _newName)
|
|
{
|
|
std::lock_guard guard(m_cachedCharacterLock);
|
|
|
|
for (const auto& characterPair : m_cachedCharacterInfo)
|
|
if (_characterInfo == characterPair.second)
|
|
characterPair.second->name = _newName;
|
|
}
|
|
|
|
void ObjectMgr::deleteCachedCharacterInfo(const uint32_t _playerGuid)
|
|
{
|
|
std::lock_guard guard(m_cachedCharacterLock);
|
|
|
|
const auto characterPair = m_cachedCharacterInfo.find(_playerGuid);
|
|
if (characterPair == m_cachedCharacterInfo.end())
|
|
return;
|
|
|
|
const auto characterInfo = characterPair->second;
|
|
if (characterInfo->m_Group)
|
|
characterInfo->m_Group->RemovePlayer(characterInfo);
|
|
|
|
m_cachedCharacterInfo.erase(characterPair);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Corpse
|
|
void ObjectMgr::loadCorpsesForInstance(WorldMap* _worldMap) const
|
|
{
|
|
if (QueryResult* result = CharacterDatabase.Query("SELECT * FROM corpses WHERE instanceid = %u", _worldMap->getInstanceId()))
|
|
{
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
const auto corpse = std::make_shared<Corpse>(HIGHGUID_TYPE_CORPSE, fields[0].GetUInt32());
|
|
corpse->SetPosition(fields[1].GetFloat(), fields[2].GetFloat(), fields[3].GetFloat(), fields[4].GetFloat());
|
|
corpse->setZoneId(fields[5].GetUInt32());
|
|
corpse->SetMapId(fields[6].GetUInt32());
|
|
corpse->SetInstanceID(fields[7].GetUInt32());
|
|
corpse->setCorpseDataFromDbString(fields[8].GetString());
|
|
|
|
if (corpse->getDisplayId() == 0)
|
|
continue;
|
|
|
|
corpse->PushToWorld(_worldMap);
|
|
} while (result->NextRow());
|
|
|
|
delete result;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<Corpse> ObjectMgr::loadCorpseByGuid(const uint32_t _corpseGuid) const
|
|
{
|
|
if (QueryResult* result = CharacterDatabase.Query("SELECT * FROM corpses WHERE guid =%u ", _corpseGuid))
|
|
{
|
|
Field* field = result->Fetch();
|
|
const auto corpse = std::make_shared<Corpse>(HIGHGUID_TYPE_CORPSE, field[0].GetUInt32());
|
|
corpse->SetPosition(field[1].GetFloat(), field[2].GetFloat(), field[3].GetFloat(), field[4].GetFloat());
|
|
corpse->setZoneId(field[5].GetUInt32());
|
|
corpse->SetMapId(field[6].GetUInt32());
|
|
corpse->setCorpseDataFromDbString(field[7].GetString());
|
|
|
|
if (corpse->getDisplayId() == 0)
|
|
return nullptr;
|
|
|
|
corpse->setLoadedFromDB(true);
|
|
corpse->SetInstanceID(field[8].GetUInt32());
|
|
corpse->AddToWorld();
|
|
|
|
delete result;
|
|
return corpse;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<Corpse> ObjectMgr::createCorpse()
|
|
{
|
|
uint32_t corpseGuid = ++m_hiCorpseGuid;
|
|
return std::make_shared<Corpse>(HIGHGUID_TYPE_CORPSE, corpseGuid);
|
|
}
|
|
|
|
void ObjectMgr::addCorpse(const std::shared_ptr<Corpse>& _corpse)
|
|
{
|
|
std::lock_guard guard(m_corpseLock);
|
|
m_corpses[_corpse->getGuidLow()] = _corpse;
|
|
}
|
|
|
|
void ObjectMgr::removeCorpse(const std::shared_ptr<Corpse>& _corpse)
|
|
{
|
|
std::lock_guard guard(m_corpseLock);
|
|
m_corpses.erase(_corpse->getGuidLow());
|
|
}
|
|
|
|
std::shared_ptr<Corpse> ObjectMgr::getCorpseByGuid(uint32_t _corpseGuid)
|
|
{
|
|
std::lock_guard guard(m_corpseLock);
|
|
const auto corpsePair = m_corpses.find(_corpseGuid);
|
|
return corpsePair != m_corpses.end() ? corpsePair->second : nullptr;
|
|
}
|
|
|
|
std::shared_ptr<Corpse> ObjectMgr::getCorpseByOwner(const uint32_t _playerGuid)
|
|
{
|
|
std::lock_guard guard(m_corpseLock);
|
|
for (const auto& corpsePair : m_corpses)
|
|
{
|
|
WoWGuid wowGuid;
|
|
wowGuid.Init(corpsePair.second->getOwnerGuid());
|
|
|
|
if (wowGuid.getGuidLowPart() == _playerGuid)
|
|
return corpsePair.second;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ObjectMgr::unloadCorpseCollector()
|
|
{
|
|
std::lock_guard guard(m_corpseLock);
|
|
for (const auto& corpsePair : m_corpses)
|
|
{
|
|
const auto corpse = corpsePair.second;
|
|
if (corpse->IsInWorld())
|
|
corpse->RemoveFromWorld(false);
|
|
}
|
|
m_corpses.clear();
|
|
}
|
|
|
|
void ObjectMgr::addCorpseDespawnTime(const std::shared_ptr<Corpse>& _corpse)
|
|
{
|
|
if (_corpse->IsInWorld())
|
|
_corpse->getWorldMap()->addCorpseDespawn(_corpse->getGuid(), 600000);
|
|
}
|
|
|
|
void ObjectMgr::delinkCorpseForPlayer(const Player* _player)
|
|
{
|
|
if (const auto corpse = getCorpseByOwner(_player->getGuidLow()))
|
|
{
|
|
corpse->delink();
|
|
addCorpseDespawnTime(corpse);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Achievement
|
|
#if VERSION_STRING > TBC
|
|
void ObjectMgr::loadAchievementCriteriaList()
|
|
{
|
|
for (uint32 rowId = 0; rowId < sAchievementCriteriaStore.GetNumRows(); ++rowId)
|
|
{
|
|
auto criteria = sAchievementCriteriaStore.LookupEntry(rowId);
|
|
if (!criteria)
|
|
continue;
|
|
|
|
#if VERSION_STRING > WotLK
|
|
auto achievement = sAchievementStore.LookupEntry(criteria->referredAchievement);
|
|
if (achievement && achievement->flags & ACHIEVEMENT_FLAG_GUILD)
|
|
m_GuildAchievementCriteriasByType[criteria->requiredType].push_back(criteria);
|
|
else
|
|
#endif
|
|
m_AchievementCriteriasByType[criteria->requiredType].push_back(criteria);
|
|
}
|
|
}
|
|
|
|
void ObjectMgr::loadAchievementRewards()
|
|
{
|
|
m_achievementRewards.clear();
|
|
|
|
QueryResult* result = WorldDatabase.Query("SELECT entry, gender, title_A, title_H, item, sender, subject, text FROM achievement_reward");
|
|
|
|
if (!result)
|
|
{
|
|
sLogger.info("Loaded 0 achievement rewards. DB table `achievement_reward` is empty.");
|
|
return;
|
|
}
|
|
|
|
uint32 count = 0;
|
|
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
uint32 entry = fields[0].GetUInt32();
|
|
|
|
if (!sAchievementStore.LookupEntry(entry))
|
|
{
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "ObjectMgr : Achievement reward entry %u has wrong achievement, ignore", entry);
|
|
continue;
|
|
}
|
|
|
|
AchievementReward reward;
|
|
reward.gender = fields[1].GetUInt8();
|
|
reward.titel_A = fields[2].GetUInt32();
|
|
reward.titel_H = fields[3].GetUInt32();
|
|
reward.itemId = fields[4].GetUInt32();
|
|
reward.sender = fields[5].GetUInt32();
|
|
reward.subject = fields[6].GetString() ? fields[6].GetString() : "";
|
|
reward.text = fields[7].GetString() ? fields[7].GetString() : "";
|
|
|
|
if (reward.gender > 2)
|
|
sLogger.debug("ObjectMgr : achievement reward %u has wrong gender %u.", entry, static_cast<uint32_t>(reward.gender));
|
|
|
|
bool dup = false;
|
|
AchievementRewardsMapBounds bounds = m_achievementRewards.equal_range(entry);
|
|
for (AchievementRewardsMap::const_iterator iter = bounds.first; iter != bounds.second; ++iter)
|
|
{
|
|
if (iter->second.gender == 2 || reward.gender == 2)
|
|
{
|
|
dup = true;
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "ObjectMgr : Achievement reward %u must have single GENDER_NONE (%u), ignore duplicate case", 2, entry);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dup)
|
|
continue;
|
|
|
|
// must be title or mail at least
|
|
if (!reward.titel_A && !reward.titel_H && !reward.sender)
|
|
{
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "ObjectMgr : achievement_reward %u not have title or item reward data, ignore.", entry);
|
|
continue;
|
|
}
|
|
|
|
if (reward.titel_A)
|
|
{
|
|
auto const* char_title_entry = sCharTitlesStore.LookupEntry(reward.titel_A);
|
|
if (!char_title_entry)
|
|
{
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "ObjectMgr : achievement_reward %u has invalid title id (%u) in `title_A`, set to 0", entry, reward.titel_A);
|
|
reward.titel_A = 0;
|
|
}
|
|
}
|
|
|
|
if (reward.titel_H)
|
|
{
|
|
auto const* char_title_entry = sCharTitlesStore.LookupEntry(reward.titel_H);
|
|
if (!char_title_entry)
|
|
{
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "ObjectMgr : achievement_reward %u has invalid title id (%u) in `title_A`, set to 0", entry, reward.titel_H);
|
|
reward.titel_H = 0;
|
|
}
|
|
}
|
|
|
|
//check mail data before item for report including wrong item case
|
|
if (reward.sender)
|
|
{
|
|
if (!sMySQLStore.getCreatureProperties(reward.sender))
|
|
{
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "ObjectMgr : achievement_reward %u has invalid creature entry %u as sender, mail reward skipped.", entry, reward.sender);
|
|
reward.sender = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (reward.itemId)
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "ObjectMgr : achievement_reward %u not have sender data but have item reward, item will not rewarded", entry);
|
|
|
|
if (!reward.subject.empty())
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "ObjectMgr : achievement_reward %u not have sender data but have mail subject.", entry);
|
|
|
|
if (!reward.text.empty())
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "ObjectMgr : achievement_reward %u not have sender data but have mail text.", entry);
|
|
}
|
|
|
|
if (reward.itemId == 0)
|
|
{
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "ObjectMgr : achievement_reward %u has invalid item id %u, reward mail will be without item.", entry, reward.itemId);
|
|
}
|
|
|
|
m_achievementRewards.insert(AchievementRewardsMap::value_type(entry, reward));
|
|
++count;
|
|
|
|
} while (result->NextRow());
|
|
|
|
delete result;
|
|
|
|
sLogger.info("ObjectMgr : Loaded %u achievement rewards", count);
|
|
}
|
|
|
|
void ObjectMgr::loadCompletedAchievements()
|
|
{
|
|
QueryResult* result = CharacterDatabase.Query("SELECT achievement FROM character_achievement GROUP BY achievement");
|
|
|
|
if (!result)
|
|
{
|
|
sLogger.failure("Query failed: SELECT achievement FROM character_achievement");
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
m_allCompletedAchievements.insert(fields[0].GetUInt32());
|
|
} while (result->NextRow());
|
|
delete result;
|
|
}
|
|
|
|
AchievementReward const* ObjectMgr::getAchievementReward(uint32_t _entry, uint8_t _gender)
|
|
{
|
|
AchievementRewardsMapBounds bounds = m_achievementRewards.equal_range(_entry);
|
|
for (AchievementRewardsMap::const_iterator iter = bounds.first; iter != bounds.second; ++iter)
|
|
{
|
|
if (iter->second.gender == 2 || iter->second.gender == _gender)
|
|
return &iter->second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
AchievementCriteriaEntryList const& ObjectMgr::getAchievementCriteriaByType(AchievementCriteriaTypes _type)
|
|
{
|
|
return m_AchievementCriteriasByType[_type];
|
|
}
|
|
|
|
void ObjectMgr::addCompletedAchievement(uint32_t _achievementId)
|
|
{
|
|
m_allCompletedAchievements.insert(_achievementId);
|
|
}
|
|
|
|
std::set<uint32_t> ObjectMgr::getAllCompleteAchievements()
|
|
{
|
|
return m_allCompletedAchievements;
|
|
}
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Misc
|
|
void ObjectMgr::generateDatabaseGossipMenu(Object* _object, uint32_t _gossipMenuId, Player* _player, uint32_t _forcedTextId /*= 0*/)
|
|
{
|
|
uint32_t textId = 2;
|
|
|
|
if (_forcedTextId == 0)
|
|
{
|
|
auto gossipMenuTextStore = sMySQLStore.getGossipMenuInitTextId();
|
|
for (auto& initItr : *gossipMenuTextStore)
|
|
{
|
|
if (initItr.first == _gossipMenuId)
|
|
{
|
|
textId = initItr.second.textId;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
textId = _forcedTextId;
|
|
}
|
|
|
|
GossipMenu menu(_object->getGuid(), textId, _player->getSession()->language, _gossipMenuId);
|
|
|
|
sQuestMgr.FillQuestMenu(dynamic_cast<Creature*>(_object), _player, menu);
|
|
|
|
typedef MySQLDataStore::GossipMenuItemsContainer::iterator GossipMenuItemsIterator;
|
|
std::pair<GossipMenuItemsIterator, GossipMenuItemsIterator> gossipEqualRange = sMySQLStore._gossipMenuItemsStores.equal_range(_gossipMenuId);
|
|
for (GossipMenuItemsIterator itr = gossipEqualRange.first; itr != gossipEqualRange.second; ++itr)
|
|
{
|
|
// check requirements
|
|
// 0 = none
|
|
// 1 = has(active)Quest
|
|
// 2 = has(finished)Quest
|
|
// 3 = canGainXP
|
|
// 4 = canNotGainXP
|
|
|
|
if (itr->first == _gossipMenuId)
|
|
{
|
|
auto& gossipMenuItem = itr->second;
|
|
if (gossipMenuItem.requirementType == 1 && !_player->hasQuestInQuestLog(gossipMenuItem.requirementData))
|
|
continue;
|
|
|
|
if (gossipMenuItem.requirementType == 3)
|
|
{
|
|
if (_player->canGainXp())
|
|
menu.addItem(gossipMenuItem.icon, gossipMenuItem.menuOptionText, gossipMenuItem.itemOrder, "", gossipMenuItem.onChooseData, _player->getSession()->LocalizedGossipOption(gossipMenuItem.onChooseData2));
|
|
|
|
continue;
|
|
}
|
|
|
|
if (gossipMenuItem.requirementType == 4)
|
|
{
|
|
if (!_player->canGainXp())
|
|
menu.addItem(gossipMenuItem.icon, gossipMenuItem.menuOptionText, gossipMenuItem.itemOrder, "", gossipMenuItem.onChooseData, _player->getSession()->LocalizedGossipOption(gossipMenuItem.onChooseData2));
|
|
|
|
continue;
|
|
}
|
|
|
|
menu.addItem(gossipMenuItem.icon, gossipMenuItem.menuOptionText, gossipMenuItem.itemOrder);
|
|
}
|
|
}
|
|
|
|
menu.sendGossipPacket(_player);
|
|
}
|
|
|
|
void ObjectMgr::generateDatabaseGossipOptionAndSubMenu(Object* _object, Player* _player, uint32_t _gossipItemId, uint32_t _gossipMenuId)
|
|
{
|
|
sLogger.debug("GossipId: %u gossipItemId: %u", _gossipMenuId, _gossipItemId);
|
|
|
|
// bool openSubMenu = true;
|
|
|
|
typedef MySQLDataStore::GossipMenuItemsContainer::iterator GossipMenuItemsIterator;
|
|
std::pair<GossipMenuItemsIterator, GossipMenuItemsIterator> gossipEqualRange = sMySQLStore._gossipMenuItemsStores.equal_range(_gossipMenuId);
|
|
for (GossipMenuItemsIterator itr = gossipEqualRange.first; itr != gossipEqualRange.second; ++itr)
|
|
{
|
|
if (itr->second.itemOrder == _gossipItemId)
|
|
{
|
|
// onChooseAction
|
|
// 0 = None
|
|
// 1 = sendPoiById (on_choose_data = poiId)
|
|
// 2 = castSpell (on_choose_data = spellId)
|
|
// 3 = sendTaxi (on_choose_data = taxiId, on_choose_data2 = modelId)
|
|
// 4 = required standing (on_choose_data = factionId, on_choose_data2 = standing, on_choose_data3 = broadcastTextId)
|
|
// 5 = close window
|
|
// 6 = toggleXPGain
|
|
|
|
// onChooseData
|
|
// depending on Action...
|
|
switch (itr->second.onChooseAction)
|
|
{
|
|
case 1:
|
|
{
|
|
generateDatabaseGossipMenu(_object, itr->second.nextGossipMenu, _player, itr->second.nextGossipMenuText);
|
|
|
|
if (itr->second.onChooseData != 0)
|
|
_player->sendPoiById(itr->second.onChooseData);
|
|
|
|
} break;
|
|
case 2:
|
|
{
|
|
if (itr->second.onChooseData != 0)
|
|
{
|
|
_player->castSpell(_player, sSpellMgr.getSpellInfo(itr->second.onChooseData), true);
|
|
GossipMenu::senGossipComplete(_player);
|
|
}
|
|
|
|
} break;
|
|
case 3:
|
|
{
|
|
if (itr->second.onChooseData != 0)
|
|
{
|
|
if (_object->isCreature())
|
|
_player->getSession()->sendTaxiMenu(_object->ToCreature());
|
|
|
|
GossipMenu::senGossipComplete(_player);
|
|
}
|
|
|
|
} break;
|
|
case 4:
|
|
{
|
|
if (itr->second.onChooseData != 0)
|
|
{
|
|
if (_player->getFactionStanding(itr->second.onChooseData) >= static_cast<int32_t>(itr->second.onChooseData2))
|
|
_player->castSpell(_player, sSpellMgr.getSpellInfo(itr->second.onChooseData3), true);
|
|
else
|
|
_player->broadcastMessage(_player->getSession()->LocalizedWorldSrv(itr->second.onChooseData4));
|
|
|
|
GossipMenu::senGossipComplete(_player);
|
|
}
|
|
|
|
} break;
|
|
case 5:
|
|
{
|
|
GossipMenu::senGossipComplete(_player);
|
|
|
|
} break;
|
|
case 6:
|
|
{
|
|
if (_player->hasEnoughCoinage(itr->second.onChooseData))
|
|
{
|
|
_player->modCoinage(-static_cast<int32_t>(itr->second.onChooseData));
|
|
_player->toggleXpGain();
|
|
GossipMenu::senGossipComplete(_player);
|
|
}
|
|
} break;
|
|
default: // action 0
|
|
{
|
|
generateDatabaseGossipMenu(_object, itr->second.nextGossipMenu, _player, itr->second.nextGossipMenuText);
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ObjectMgr::loadTrainerSpellSets()
|
|
{
|
|
auto* const spellSetResult = sMySQLStore.getWorldDBQuery("SELECT * FROM trainer_properties_spellset WHERE min_build <= %u AND max_build >= %u;", VERSION_STRING, VERSION_STRING);
|
|
if (spellSetResult != nullptr)
|
|
{
|
|
std::unordered_map<uint32_t, std::vector<TrainerSpell>*>::const_iterator itr;
|
|
std::vector<TrainerSpell>* trainerSpells;
|
|
|
|
do
|
|
{
|
|
Field* fields = spellSetResult->Fetch();
|
|
|
|
itr = m_trainerSpellSet.find(fields[0].GetUInt32());
|
|
|
|
if (itr == m_trainerSpellSet.end())
|
|
{
|
|
trainerSpells = new std::vector<TrainerSpell>;
|
|
m_trainerSpellSet[fields[0].GetUInt32()] = trainerSpells;
|
|
}
|
|
else
|
|
{
|
|
trainerSpells = itr->second;
|
|
}
|
|
|
|
auto* const fields2 = spellSetResult->Fetch();
|
|
|
|
auto castSpellID = fields2[3].GetUInt32();
|
|
auto learnSpellID = fields2[4].GetUInt32();
|
|
|
|
TrainerSpell ts;
|
|
auto abrt = false;
|
|
if (castSpellID != 0)
|
|
{
|
|
ts.castSpell = sSpellMgr.getSpellInfo(castSpellID);
|
|
if (ts.castSpell != nullptr)
|
|
{
|
|
// Check that the castable spell has learn spell effect
|
|
for (uint8_t i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (ts.castSpell->getEffect(i) == SPELL_EFFECT_LEARN_SPELL)
|
|
{
|
|
ts.castRealSpell = sSpellMgr.getSpellInfo(ts.castSpell->getEffectTriggerSpell(i));
|
|
if (ts.castRealSpell == nullptr)
|
|
{
|
|
sLogger.failure("LoadTrainers : TrainerSpellSet %u contains cast spell %u that is non-teaching", fields[0].GetUInt32(), castSpellID);
|
|
abrt = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (abrt)
|
|
continue;
|
|
}
|
|
|
|
if (learnSpellID != 0)
|
|
ts.learnSpell = sSpellMgr.getSpellInfo(learnSpellID);
|
|
|
|
if (ts.castSpell == nullptr && ts.learnSpell == nullptr)
|
|
continue;
|
|
|
|
if (ts.castSpell != nullptr && ts.castRealSpell == nullptr)
|
|
continue;
|
|
|
|
ts.cost = fields2[5].GetUInt32();
|
|
ts.requiredSpell[0] = fields2[6].GetUInt32();
|
|
ts.requiredSkillLine = fields2[7].GetUInt16();
|
|
ts.requiredSkillLineValue = fields2[8].GetUInt32();
|
|
ts.requiredLevel = fields2[9].GetUInt32();
|
|
ts.deleteSpell = fields2[10].GetUInt32();
|
|
ts.isStatic = fields2[11].GetUInt32();
|
|
|
|
// Check if spell teaches a primary profession skill
|
|
if (ts.requiredSkillLine == 0 && ts.castRealSpell != nullptr)
|
|
ts.isPrimaryProfession = ts.castRealSpell->isPrimaryProfession();
|
|
|
|
// Add all required spells
|
|
const auto spellInfo = ts.castRealSpell != nullptr ? ts.castSpell : ts.learnSpell;
|
|
const auto requiredSpells = sSpellMgr.getSpellsRequiredForSpellBounds(spellInfo->getId());
|
|
for (auto itr = requiredSpells.first; itr != requiredSpells.second; ++itr)
|
|
{
|
|
for (uint8_t i = 0; i < 3; ++i)
|
|
{
|
|
if (ts.requiredSpell[i] == itr->second)
|
|
break;
|
|
|
|
if (ts.requiredSpell[i] != 0)
|
|
continue;
|
|
|
|
ts.requiredSpell[i] = itr->second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
trainerSpells->push_back(ts);
|
|
} while (spellSetResult->NextRow());
|
|
|
|
sLogger.info("LoadTrainers : %u TrainerSpellSet loaded", static_cast<uint32_t>(m_trainerSpellSet.size()));
|
|
}
|
|
}
|
|
|
|
std::vector<TrainerSpell> ObjectMgr::getTrainerSpellSetById(uint32_t _id)
|
|
{
|
|
auto itr = m_trainerSpellSet.find(_id);
|
|
if (itr == m_trainerSpellSet.end())
|
|
return {};
|
|
|
|
return *itr->second;
|
|
}
|
|
|
|
void ObjectMgr::loadTrainers()
|
|
{
|
|
if (auto* const trainerResult = sMySQLStore.getWorldDBQuery("SELECT * FROM trainer_properties WHERE build <= %u;", VERSION_STRING))
|
|
{
|
|
do
|
|
{
|
|
auto* const fields = trainerResult->Fetch();
|
|
const auto entry = fields[0].GetUInt32();
|
|
|
|
std::shared_ptr<Trainer> trainer = std::make_shared<Trainer>();
|
|
trainer->RequiredSkill = fields[2].GetUInt16();
|
|
trainer->RequiredSkillLine = fields[3].GetUInt32();
|
|
trainer->RequiredClass = fields[4].GetUInt32();
|
|
trainer->RequiredRace = fields[5].GetUInt32();
|
|
trainer->RequiredRepFaction = fields[6].GetUInt32();
|
|
trainer->RequiredRepValue = fields[7].GetUInt32();
|
|
trainer->TrainerType = fields[8].GetUInt32();
|
|
trainer->Can_Train_Gossip_TextId = fields[10].GetUInt32();
|
|
trainer->Cannot_Train_GossipTextId = fields[11].GetUInt32();
|
|
trainer->spellset_id = fields[12].GetUInt32();
|
|
trainer->can_train_max_level = fields[13].GetUInt32();
|
|
trainer->can_train_min_skill_value = fields[14].GetUInt32();
|
|
trainer->can_train_max_skill_value = fields[15].GetUInt32();
|
|
|
|
if (!trainer->Can_Train_Gossip_TextId)
|
|
trainer->Can_Train_Gossip_TextId = 1;
|
|
if (!trainer->Cannot_Train_GossipTextId)
|
|
trainer->Cannot_Train_GossipTextId = 1;
|
|
|
|
const char* temp = fields[9].GetString();
|
|
size_t len = strlen(temp);
|
|
if (len)
|
|
{
|
|
trainer->UIMessage = new char[len + 1];
|
|
strcpy(trainer->UIMessage, temp);
|
|
trainer->UIMessage[len] = 0;
|
|
}
|
|
else
|
|
{
|
|
trainer->UIMessage = new char[strlen(NormalTalkMessage) + 1];
|
|
strcpy(trainer->UIMessage, NormalTalkMessage);
|
|
trainer->UIMessage[strlen(NormalTalkMessage)] = 0;
|
|
}
|
|
|
|
trainer->SpellCount = static_cast<uint32_t>(getTrainerSpellSetById(trainer->spellset_id).size());
|
|
|
|
// and now we insert it to our lookup table
|
|
if (trainer->SpellCount == 0)
|
|
{
|
|
if (trainer->UIMessage != NormalTalkMessage)
|
|
delete[] trainer->UIMessage;
|
|
continue;
|
|
}
|
|
|
|
m_trainers.insert(std::pair(entry, trainer));
|
|
} while (trainerResult->NextRow());
|
|
|
|
delete trainerResult;
|
|
sLogger.info("ObjectMgr : %u trainers loaded.", static_cast<uint32_t>(m_trainers.size()));
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<Trainer> ObjectMgr::getTrainer(uint32_t _entry)
|
|
{
|
|
const auto iter = m_trainers.find(_entry);
|
|
if (iter == m_trainers.end())
|
|
return nullptr;
|
|
|
|
return iter->second;
|
|
}
|
|
|
|
void ObjectMgr::loadCreatureDisplayInfo()
|
|
{
|
|
for (uint32_t i = 0; i < sCreatureDisplayInfoStore.GetNumRows(); ++i)
|
|
{
|
|
const auto* const displayInfoEntry = sCreatureDisplayInfoStore.LookupEntry(i);
|
|
if (displayInfoEntry == nullptr)
|
|
continue;
|
|
|
|
CreatureDisplayInfoData data;
|
|
data.id = displayInfoEntry->ID;
|
|
data.modelId = displayInfoEntry->ModelID;
|
|
data.extendedDisplayInfoId = displayInfoEntry->ExtendedDisplayInfoID;
|
|
data.creatureModelScale = displayInfoEntry->CreatureModelScale;
|
|
data.modelInfo = sCreatureModelDataStore.LookupEntry(data.modelId);
|
|
if (data.modelInfo != nullptr)
|
|
{
|
|
if (strstr(data.modelInfo->ModelName, "InvisibleStalker"))
|
|
data.isModelInvisibleStalker = true;
|
|
}
|
|
|
|
m_creatureDisplayInfoData.insert(std::make_pair(displayInfoEntry->ID, data));
|
|
}
|
|
}
|
|
|
|
CreatureDisplayInfoData const* ObjectMgr::getCreatureDisplayInfoData(uint32_t displayId) const
|
|
{
|
|
const auto itr = m_creatureDisplayInfoData.find(displayId);
|
|
if (itr == m_creatureDisplayInfoData.cend())
|
|
return nullptr;
|
|
|
|
return &itr->second;
|
|
}
|
|
|
|
Player* ObjectMgr::createPlayerByGuid(uint8_t _class, uint32_t guid)
|
|
{
|
|
Player* player;
|
|
|
|
switch (_class)
|
|
{
|
|
case WARRIOR:
|
|
player = new Warrior(guid);
|
|
break;
|
|
case PALADIN:
|
|
player = new Paladin(guid);
|
|
break;
|
|
case HUNTER:
|
|
player = new Hunter(guid);
|
|
break;
|
|
case ROGUE:
|
|
player = new Rogue(guid);
|
|
break;
|
|
case PRIEST:
|
|
player = new Priest(guid);
|
|
break;
|
|
#if VERSION_STRING > TBC
|
|
case DEATHKNIGHT:
|
|
player = new DeathKnight(guid);
|
|
break;
|
|
#endif
|
|
case SHAMAN:
|
|
player = new Shaman(guid);
|
|
break;
|
|
case MAGE:
|
|
player = new Mage(guid);
|
|
break;
|
|
case WARLOCK:
|
|
player = new Warlock(guid);
|
|
break;
|
|
#if VERSION_STRING > Cata
|
|
case MONK:
|
|
player = new Monk(guid);
|
|
break;
|
|
#endif
|
|
case DRUID:
|
|
player = new Druid(guid);
|
|
break;
|
|
default:
|
|
player = nullptr;
|
|
break;
|
|
}
|
|
|
|
return player;
|
|
}
|
|
|
|
GameObject* ObjectMgr::createGameObjectByGuid(uint32_t id, uint32_t guid)
|
|
{
|
|
GameObjectProperties const* gameobjectProperties = sMySQLStore.getGameObjectProperties(id);
|
|
if (gameobjectProperties == nullptr)
|
|
return nullptr;
|
|
|
|
GameObject* gameObject;
|
|
|
|
const uint64_t createdGuid = uint64_t((uint64_t(HIGHGUID_TYPE_GAMEOBJECT) << 32) | guid);
|
|
|
|
switch (gameobjectProperties->type)
|
|
{
|
|
case GAMEOBJECT_TYPE_DOOR:
|
|
gameObject = new GameObject_Door(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_BUTTON:
|
|
gameObject = new GameObject_Button(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_QUESTGIVER:
|
|
gameObject = new GameObject_QuestGiver(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_CHEST:
|
|
gameObject = new GameObject_Chest(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_TRAP:
|
|
gameObject = new GameObject_Trap(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_CHAIR:
|
|
gameObject = new GameObject_Chair(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_SPELL_FOCUS:
|
|
gameObject = new GameObject_SpellFocus(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_GOOBER:
|
|
gameObject = new GameObject_Goober(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_TRANSPORT:
|
|
gameObject = new GameObject_Transport(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_CAMERA:
|
|
gameObject = new GameObject_Camera(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_FISHINGNODE:
|
|
gameObject = new GameObject_FishingNode(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_RITUAL:
|
|
gameObject = new GameObject_Ritual(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_SPELLCASTER:
|
|
gameObject = new GameObject_SpellCaster(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_MEETINGSTONE:
|
|
gameObject = new GameObject_Meetingstone(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_FLAGSTAND:
|
|
gameObject = new GameObject_FlagStand(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_FISHINGHOLE:
|
|
gameObject = new GameObject_FishingHole(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_FLAGDROP:
|
|
gameObject = new GameObject_FlagDrop(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_BARBER_CHAIR:
|
|
gameObject = new GameObject_BarberChair(createdGuid);
|
|
break;
|
|
case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING:
|
|
gameObject = new GameObject_Destructible(createdGuid);
|
|
break;
|
|
default:
|
|
gameObject = new GameObject(createdGuid);
|
|
break;
|
|
}
|
|
|
|
gameObject->SetGameObjectProperties(gameobjectProperties);
|
|
|
|
return gameObject;
|
|
}
|
|
|
|
DungeonEncounterList const* ObjectMgr::getDungeonEncounterList(uint32_t _mapId, uint8_t _difficulty)
|
|
{
|
|
#if VERSION_STRING >= WotLK
|
|
std::unordered_map<uint32_t, DungeonEncounterList>::const_iterator itr = m_dungeonEncounterStore.find(uint32_t(uint16_t(_mapId) | (uint32_t(_difficulty) << 16)));
|
|
#else
|
|
std::unordered_map<uint32_t, DungeonEncounterList>::const_iterator itr = _dungeonEncounterStore.find(_mapId);
|
|
#endif
|
|
if (itr != m_dungeonEncounterStore.end())
|
|
return &itr->second;
|
|
return nullptr;
|
|
}
|
|
|
|
void ObjectMgr::loadCreatureMovementOverrides()
|
|
{
|
|
const auto startTime = Util::TimeNow();
|
|
uint32_t count = 0;
|
|
|
|
m_creatureMovementOverrides.clear();
|
|
|
|
QueryResult* result = WorldDatabase.Query("SELECT SpawnId, Ground, Swim, Flight, Rooted, Chase, Random from creature_movement_override");
|
|
if (!result)
|
|
{
|
|
sLogger.info("loadCreatureMovementOverrides : Loaded 0 creature movement overrides. DB table `creature_movement_override` is empty!");
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
uint32_t spawnId = fields[0].GetUInt32();
|
|
|
|
QueryResult* spawnResult = WorldDatabase.Query("SELECT * FROM creature_spawns WHERE id = %u", spawnId);
|
|
if (spawnResult == nullptr)
|
|
{
|
|
sLogger.failure("Creature (SpawnId: %u) does not exist but has a record in `creature_movement_override`", spawnId);
|
|
delete spawnResult;
|
|
continue;
|
|
}
|
|
|
|
CreatureMovementData& movement = m_creatureMovementOverrides[spawnId];
|
|
movement.Ground = static_cast<CreatureGroundMovementType>(fields[1].GetUInt8());
|
|
movement.Swim = fields[2].GetBool();
|
|
movement.Flight = static_cast<CreatureFlightMovementType>(fields[3].GetUInt8());
|
|
movement.Rooted = fields[4].GetBool();
|
|
movement.Chase = static_cast<CreatureChaseMovementType>(fields[5].GetUInt8());
|
|
movement.Random = static_cast<CreatureRandomMovementType>(fields[6].GetUInt8());
|
|
|
|
checkCreatureMovement(spawnId, movement);
|
|
++count;
|
|
|
|
} while (result->NextRow());
|
|
|
|
delete result;
|
|
|
|
sLogger.info("ObjectMgr : Loaded %u movement overrides in %u ms", count, static_cast<uint32_t>(Util::GetTimeDifferenceToNow(startTime)));
|
|
}
|
|
|
|
void ObjectMgr::checkCreatureMovement(uint32_t /*id*/, CreatureMovementData& _creatureMovement)
|
|
{
|
|
if (_creatureMovement.Ground >= CreatureGroundMovementType::Max)
|
|
_creatureMovement.Ground = CreatureGroundMovementType::Run;
|
|
|
|
if (_creatureMovement.Flight >= CreatureFlightMovementType::Max)
|
|
_creatureMovement.Flight = CreatureFlightMovementType::None;
|
|
|
|
if (_creatureMovement.Chase >= CreatureChaseMovementType::Max)
|
|
_creatureMovement.Chase = CreatureChaseMovementType::Run;
|
|
|
|
if (_creatureMovement.Random >= CreatureRandomMovementType::Max)
|
|
_creatureMovement.Random = CreatureRandomMovementType::Walk;
|
|
}
|
|
|
|
CreatureMovementData const* ObjectMgr::getCreatureMovementOverride(uint32_t _spawnId) const
|
|
{
|
|
const auto itr = m_creatureMovementOverrides.find(_spawnId);
|
|
if (itr != m_creatureMovementOverrides.end())
|
|
return &itr->second;
|
|
return nullptr;
|
|
}
|
|
|
|
void ObjectMgr::loadWorldStateTemplates()
|
|
{
|
|
QueryResult* result = WorldDatabase.QueryNA("SELECT DISTINCT map FROM worldstate_templates ORDER BY map;");
|
|
if (result == nullptr)
|
|
return;
|
|
|
|
do
|
|
{
|
|
Field* field = result->Fetch();
|
|
uint32_t mapId = field[0].GetUInt32();
|
|
|
|
m_worldstateTemplates.insert(std::make_pair(mapId, new std::multimap<uint32_t, WorldState>()));
|
|
|
|
} while (result->NextRow());
|
|
|
|
delete result;
|
|
|
|
result = WorldDatabase.QueryNA("SELECT map, zone, field, value FROM worldstate_templates;");
|
|
if (result == nullptr)
|
|
return;
|
|
|
|
do
|
|
{
|
|
Field* field = result->Fetch();
|
|
WorldState worldState;
|
|
|
|
uint32_t mapId = field[0].GetUInt32();
|
|
uint32_t zone = field[1].GetUInt32();
|
|
worldState.field = field[2].GetUInt32();
|
|
worldState.value = field[3].GetUInt32();
|
|
|
|
auto itr = m_worldstateTemplates.find(mapId);
|
|
if (itr == m_worldstateTemplates.end())
|
|
continue;
|
|
|
|
itr->second->insert(std::make_pair(zone, worldState));
|
|
|
|
} while (result->NextRow());
|
|
|
|
delete result;
|
|
}
|
|
|
|
std::multimap<uint32, WorldState>* ObjectMgr::getWorldStatesForMap(uint32 map) const
|
|
{
|
|
const auto itr = m_worldstateTemplates.find(map);
|
|
if (itr == m_worldstateTemplates.end())
|
|
return nullptr;
|
|
return itr->second;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Groups
|
|
Group* ObjectMgr::GetGroupByLeader(Player* pPlayer)
|
|
{
|
|
std::lock_guard<std::mutex> guard(m_groupLock);
|
|
|
|
for (GroupMap::iterator itr = m_groups.begin(); itr != m_groups.end(); ++itr)
|
|
{
|
|
if (itr->second->GetLeader() == pPlayer->getPlayerInfo())
|
|
{
|
|
return itr->second;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Group* ObjectMgr::GetGroupById(uint32 id)
|
|
{
|
|
std::lock_guard<std::mutex> guard(m_groupLock);
|
|
|
|
GroupMap::iterator itr = m_groups.find(id);
|
|
if (itr != m_groups.end())
|
|
return itr->second;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ObjectMgr::SetHighestGuids()
|
|
{
|
|
QueryResult* result = CharacterDatabase.Query("SELECT MAX(guid) FROM characters");
|
|
if (result)
|
|
{
|
|
m_hiPlayerGuid = result->Fetch()[0].GetUInt32();
|
|
delete result;
|
|
}
|
|
|
|
result = CharacterDatabase.Query("SELECT MAX(guid) FROM playeritems");
|
|
if (result)
|
|
{
|
|
m_hiItemGuid = result->Fetch()[0].GetUInt32();
|
|
delete result;
|
|
}
|
|
|
|
result = CharacterDatabase.Query("SELECT MAX(guid) FROM corpses");
|
|
if (result)
|
|
{
|
|
m_hiCorpseGuid = result->Fetch()[0].GetUInt32();
|
|
delete result;
|
|
}
|
|
|
|
result = sMySQLStore.getWorldDBQuery("SELECT MAX(id) FROM creature_spawns WHERE min_build <= %u AND max_build >= %u AND event_entry = 0", VERSION_STRING, VERSION_STRING);
|
|
if (result)
|
|
{
|
|
do
|
|
{
|
|
m_hiCreatureSpawnId = result->Fetch()[0].GetUInt32();
|
|
} while (result->NextRow());
|
|
|
|
delete result;
|
|
}
|
|
|
|
result = sMySQLStore.getWorldDBQuery("SELECT MAX(id) FROM gameobject_spawns WHERE min_build <= %u AND max_build >= %u AND event_entry = 0", VERSION_STRING, VERSION_STRING);
|
|
if (result)
|
|
{
|
|
do
|
|
{
|
|
m_hiGameObjectSpawnId = result->Fetch()[0].GetUInt32();
|
|
} while (result->NextRow());
|
|
delete result;
|
|
}
|
|
|
|
result = CharacterDatabase.Query("SELECT MAX(group_id) FROM `groups`");
|
|
if (result)
|
|
{
|
|
m_hiGroupId = result->Fetch()[0].GetUInt32();
|
|
delete result;
|
|
}
|
|
|
|
result = CharacterDatabase.Query("SELECT MAX(charterid) FROM charters");
|
|
if (result)
|
|
{
|
|
m_hiCharterId = result->Fetch()[0].GetUInt32();
|
|
delete result;
|
|
}
|
|
|
|
result = CharacterDatabase.Query("SELECT MAX(guildid) FROM guilds");
|
|
if (result)
|
|
{
|
|
m_hiGuildId = result->Fetch()[0].GetUInt32();
|
|
delete result;
|
|
}
|
|
|
|
result = CharacterDatabase.Query("SELECT MAX(UID) FROM playerbugreports");
|
|
if (result != nullptr)
|
|
{
|
|
m_reportID = uint32(result->Fetch()[0].GetUInt64() + 1);
|
|
delete result;
|
|
}
|
|
|
|
result = CharacterDatabase.Query("SELECT MAX(message_id) FROM mailbox");
|
|
if (result)
|
|
{
|
|
m_mailid = uint32(result->Fetch()[0].GetUInt64() + 1);
|
|
delete result;
|
|
}
|
|
|
|
result = CharacterDatabase.Query("SELECT MAX(setGUID) FROM equipmentsets");
|
|
if (result != nullptr)
|
|
{
|
|
m_setGUID = uint32(result->Fetch()[0].GetUInt32() + 1);
|
|
delete result;
|
|
}
|
|
|
|
#if VERSION_STRING > WotLK
|
|
result = CharacterDatabase.Query("SELECT MAX(itemId) FROM character_void_storage");
|
|
if (result != nullptr)
|
|
{
|
|
m_voidItemId = uint64_t(result->Fetch()[0].GetUInt32() + 1);
|
|
}
|
|
#endif
|
|
|
|
sLogger.info("ObjectMgr : HighGuid(CORPSE) = %lu", m_hiCorpseGuid.load());
|
|
sLogger.info("ObjectMgr : HighGuid(PLAYER) = %lu", m_hiPlayerGuid.load());
|
|
sLogger.info("ObjectMgr : HighGuid(GAMEOBJ) = %lu", m_hiGameObjectSpawnId.load());
|
|
sLogger.info("ObjectMgr : HighGuid(UNIT) = %lu", m_hiCreatureSpawnId.load());
|
|
sLogger.info("ObjectMgr : HighGuid(ITEM) = %lu", m_hiItemGuid.load());
|
|
sLogger.info("ObjectMgr : HighGuid(CONTAINER) = %lu", m_hiItemGuid.load());
|
|
sLogger.info("ObjectMgr : HighGuid(GROUP) = %lu", m_hiGroupId.load());
|
|
sLogger.info("ObjectMgr : HighGuid(CHARTER) = %lu", m_hiCharterId.load());
|
|
sLogger.info("ObjectMgr : HighGuid(GUILD) = %lu", m_hiGuildId.load());
|
|
sLogger.info("ObjectMgr : HighGuid(BUGREPORT) = %u", uint32(m_reportID.load() - 1));
|
|
sLogger.info("ObjectMgr : HighGuid(MAIL) = %u", uint32(m_mailid.load()));
|
|
sLogger.info("ObjectMgr : HighGuid(EQUIPMENTSET) = %u", uint32(m_setGUID.load() - 1));
|
|
}
|
|
|
|
uint32 ObjectMgr::GenerateReportID()
|
|
{
|
|
return ++m_reportID;
|
|
}
|
|
|
|
uint32 ObjectMgr::GenerateEquipmentSetID()
|
|
{
|
|
return ++m_setGUID;
|
|
}
|
|
|
|
uint32 ObjectMgr::GenerateMailID()
|
|
{
|
|
return ++m_mailid;
|
|
}
|
|
|
|
uint32 ObjectMgr::GenerateLowGuid(uint32 guidhigh)
|
|
{
|
|
uint32 ret;
|
|
|
|
switch (guidhigh)
|
|
{
|
|
case HIGHGUID_TYPE_PLAYER:
|
|
ret = ++m_hiPlayerGuid;
|
|
break;
|
|
case HIGHGUID_TYPE_ITEM:
|
|
case HIGHGUID_TYPE_CONTAINER:
|
|
ret = ++m_hiItemGuid;
|
|
break;
|
|
default:
|
|
sLogger.failure("ObjectMgr::GenerateLowGuid tried to generate low guid gor non player/item, return 0!");
|
|
ret = 0;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
Player* ObjectMgr::GetPlayer(const char* name, bool caseSensitive)
|
|
{
|
|
std::lock_guard<std::mutex> guard(_playerslock);
|
|
|
|
if (!caseSensitive)
|
|
{
|
|
std::string strName = name;
|
|
AscEmu::Util::Strings::toLowerCase(strName);
|
|
for (PlayerStorageMap::const_iterator itr = _players.begin(); itr != _players.end(); ++itr)
|
|
{
|
|
if (!stricmp(itr->second->getName().c_str(), strName.c_str()))
|
|
{
|
|
return itr->second;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (PlayerStorageMap::const_iterator itr = _players.begin(); itr != _players.end(); ++itr)
|
|
{
|
|
if (!strcmp(itr->second->getName().c_str(), name))
|
|
{
|
|
return itr->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Player* ObjectMgr::GetPlayer(uint32 guid)
|
|
{
|
|
std::lock_guard<std::mutex> guard(_playerslock);
|
|
|
|
PlayerStorageMap::const_iterator itr = _players.find(guid);
|
|
return (itr != _players.end()) ? itr->second : nullptr;
|
|
}
|
|
|
|
void ObjectMgr::LoadVendors()
|
|
{
|
|
QueryResult* result = sMySQLStore.getWorldDBQuery("SELECT * FROM vendors");
|
|
if (result != nullptr)
|
|
{
|
|
std::unordered_map<uint32, std::vector<CreatureItem>*>::const_iterator itr;
|
|
std::vector<CreatureItem> *items;
|
|
|
|
if (result->GetFieldCount() < 6 + 1)
|
|
{
|
|
sLogger.failure("Invalid format in vendors (%u/6) columns, not enough data to proceed.", result->GetFieldCount());
|
|
delete result;
|
|
return;
|
|
}
|
|
else if (result->GetFieldCount() > 6 + 1)
|
|
{
|
|
sLogger.failure("Invalid format in vendors (%u/6) columns, loading anyway because we have enough data", result->GetFieldCount());
|
|
}
|
|
|
|
#if VERSION_STRING < Cata
|
|
DBC::Structures::ItemExtendedCostEntry const* item_extended_cost = nullptr;
|
|
#else
|
|
DB2::Structures::ItemExtendedCostEntry const* item_extended_cost = nullptr;
|
|
#endif
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
|
|
itr = mVendors.find(fields[0].GetUInt32());
|
|
|
|
if (itr == mVendors.end())
|
|
{
|
|
items = new std::vector < CreatureItem > ;
|
|
mVendors[fields[0].GetUInt32()] = items;
|
|
}
|
|
else
|
|
{
|
|
items = itr->second;
|
|
}
|
|
|
|
CreatureItem itm;
|
|
itm.itemid = fields[1].GetUInt32();
|
|
itm.amount = fields[2].GetUInt32();
|
|
itm.available_amount = fields[3].GetUInt32();
|
|
itm.max_amount = fields[3].GetUInt32();
|
|
itm.incrtime = fields[4].GetUInt32();
|
|
if (fields[5].GetUInt32() > 0)
|
|
{
|
|
item_extended_cost = sItemExtendedCostStore.LookupEntry(fields[5].GetUInt32());
|
|
if (item_extended_cost == nullptr)
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "LoadVendors : Extendedcost for item %u references nonexistent EC %u", fields[1].GetUInt32(), fields[5].GetUInt32());
|
|
}
|
|
else
|
|
item_extended_cost = nullptr;
|
|
|
|
itm.extended_cost = item_extended_cost;
|
|
items->push_back(itm);
|
|
}
|
|
while (result->NextRow());
|
|
|
|
delete result;
|
|
}
|
|
sLogger.info("ObjectMgr : %u vendors loaded.", static_cast<uint32_t>(mVendors.size()));
|
|
}
|
|
|
|
void ObjectMgr::ReloadVendors()
|
|
{
|
|
mVendors.clear();
|
|
LoadVendors();
|
|
}
|
|
|
|
std::vector<CreatureItem>* ObjectMgr::GetVendorList(uint32 entry)
|
|
{
|
|
return mVendors[entry];
|
|
}
|
|
|
|
Item* ObjectMgr::CreateItem(uint32 entry, Player* owner)
|
|
{
|
|
ItemProperties const* proto = sMySQLStore.getItemProperties(entry);
|
|
if (proto ==nullptr)
|
|
return nullptr;
|
|
|
|
if (proto->InventoryType == INVTYPE_BAG)
|
|
{
|
|
Container* pContainer = new Container(HIGHGUID_TYPE_CONTAINER, GenerateLowGuid(HIGHGUID_TYPE_CONTAINER));
|
|
pContainer->create(entry, owner);
|
|
pContainer->setStackCount(1);
|
|
return pContainer;
|
|
}
|
|
else
|
|
{
|
|
Item* pItem = new Item;
|
|
pItem->init(HIGHGUID_TYPE_ITEM, GenerateLowGuid(HIGHGUID_TYPE_ITEM));
|
|
pItem->create(entry, owner);
|
|
pItem->setStackCount(1);
|
|
|
|
#if VERSION_STRING > TBC
|
|
if (owner != nullptr)
|
|
{
|
|
uint32* played = owner->getPlayedTime();
|
|
pItem->setCreatePlayedTime(played[1]);
|
|
}
|
|
#endif
|
|
|
|
return pItem;
|
|
}
|
|
}
|
|
|
|
Item* ObjectMgr::LoadItem(uint32 lowguid)
|
|
{
|
|
QueryResult* result = CharacterDatabase.Query("SELECT * FROM playeritems WHERE guid = %u", lowguid);
|
|
Item* pReturn = nullptr;
|
|
|
|
if (result)
|
|
{
|
|
ItemProperties const* pProto = sMySQLStore.getItemProperties(result->Fetch()[2].GetUInt32());
|
|
if (!pProto)
|
|
return nullptr;
|
|
|
|
if (pProto->InventoryType == INVTYPE_BAG)
|
|
{
|
|
Container* pContainer = new Container(HIGHGUID_TYPE_CONTAINER, lowguid);
|
|
pContainer->loadFromDB(result->Fetch());
|
|
pReturn = pContainer;
|
|
}
|
|
else
|
|
{
|
|
Item* pItem = new Item;
|
|
pItem->init(HIGHGUID_TYPE_ITEM, lowguid);
|
|
pItem->loadFromDB(result->Fetch(), nullptr, false);
|
|
pReturn = pItem;
|
|
}
|
|
delete result;
|
|
}
|
|
|
|
return pReturn;
|
|
}
|
|
|
|
void ObjectMgr::GenerateLevelUpInfo()
|
|
{
|
|
struct MissingLevelData
|
|
{
|
|
uint32_t _level;
|
|
uint8_t _race;
|
|
uint8_t _class;
|
|
};
|
|
|
|
std::vector<MissingLevelData> _missingHealthLevelData;
|
|
std::vector<MissingLevelData> _missingStatLevelData;
|
|
|
|
// Copy existing level stats
|
|
|
|
uint32_t levelstat_counter = 0;
|
|
uint32_t class_levelstat_counter = 0;
|
|
for (uint8 Class = WARRIOR; Class < MAX_PLAYER_CLASSES; ++Class)
|
|
{
|
|
for (uint8 Race = RACE_HUMAN; Race < DBC_NUM_RACES; ++Race)
|
|
{
|
|
if (!isClassRaceCombinationPossible(Class, Race))
|
|
{
|
|
if (auto* playerLevelstats = sMySQLStore.getPlayerLevelstats(1, Race, Class))
|
|
{
|
|
sLogger.info("ObjectMgr : Invalid class/race combination! %u class and %u race.", uint32_t(Class), uint32_t(Race));
|
|
sLogger.info("ObjectMgr : But class/race values for level 1 in db!");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
LevelMap* levelMap = new LevelMap;
|
|
|
|
for (uint32_t level = 1; level <= worldConfig.player.playerLevelCap; ++level)
|
|
{
|
|
LevelInfo* levelInfo = new LevelInfo;
|
|
|
|
if (auto* playerClassLevelstats = sMySQLStore.getPlayerClassLevelStats(level, Class))
|
|
{
|
|
levelInfo->HP = playerClassLevelstats->health;
|
|
levelInfo->Mana = playerClassLevelstats->mana;
|
|
++class_levelstat_counter;
|
|
}
|
|
else //calculate missing stats based on last level
|
|
{
|
|
levelInfo->HP = 0;
|
|
levelInfo->Mana = 0;
|
|
|
|
_missingHealthLevelData.push_back({ level, Race, Class });
|
|
}
|
|
|
|
if (auto* playerLevelstats = sMySQLStore.getPlayerLevelstats(level, Race, Class))
|
|
{
|
|
levelInfo->Stat[0] = playerLevelstats->strength;
|
|
levelInfo->Stat[1] = playerLevelstats->agility;
|
|
levelInfo->Stat[2] = playerLevelstats->stamina;
|
|
levelInfo->Stat[3] = playerLevelstats->intellect;
|
|
levelInfo->Stat[4] = playerLevelstats->spirit;
|
|
++levelstat_counter;
|
|
}
|
|
else //calculate missing stats based on last level
|
|
{
|
|
for (uint8_t id = 0; id < 5; ++id)
|
|
levelInfo->Stat[id] = 0;
|
|
|
|
_missingStatLevelData.push_back({ level, Race, Class });
|
|
}
|
|
|
|
// Insert into map
|
|
levelMap->insert(LevelMap::value_type(level, levelInfo));
|
|
}
|
|
|
|
// Insert back into the main map.
|
|
mLevelInfo.insert(LevelInfoMap::value_type(std::make_pair(Race, Class), levelMap));
|
|
}
|
|
}
|
|
|
|
sLogger.info("ObjectMgr : %u levelstats and %u classlevelstats applied from db.", levelstat_counter, class_levelstat_counter);
|
|
|
|
// generate missing data
|
|
uint32_t hp_counter = 0;
|
|
for (auto missingHP : _missingHealthLevelData)
|
|
{
|
|
uint32 TotalHealthGain = 0;
|
|
uint32 TotalManaGain = 0;
|
|
|
|
// use legacy gaining
|
|
switch (missingHP._class)
|
|
{
|
|
case WARRIOR:
|
|
if (missingHP._level < 13) TotalHealthGain += 19;
|
|
else if (missingHP._level < 36) TotalHealthGain += missingHP._level + 6;
|
|
else if (missingHP._level > 60) TotalHealthGain += missingHP._level + 206;
|
|
else TotalHealthGain += 2 * missingHP._level - 30;
|
|
break;
|
|
case HUNTER:
|
|
if (missingHP._level < 13) TotalHealthGain += 17;
|
|
else if (missingHP._level > 60) TotalHealthGain += missingHP._level + 161;
|
|
else TotalHealthGain += missingHP._level + 4;
|
|
|
|
if (missingHP._level < 11) TotalManaGain += 29;
|
|
else if (missingHP._level < 27) TotalManaGain += missingHP._level + 18;
|
|
else if (missingHP._level > 60) TotalManaGain += missingHP._level + 150;
|
|
else TotalManaGain += 45;
|
|
break;
|
|
case ROGUE:
|
|
if (missingHP._level < 15) TotalHealthGain += 17;
|
|
else if (missingHP._level > 60) TotalHealthGain += missingHP._level + 191;
|
|
else TotalHealthGain += missingHP._level + 2;
|
|
break;
|
|
case DRUID:
|
|
if (missingHP._level < 17) TotalHealthGain += 17;
|
|
else if (missingHP._level > 60) TotalHealthGain += missingHP._level + 176;
|
|
else TotalHealthGain += missingHP._level;
|
|
|
|
if (missingHP._level < 26) TotalManaGain += missingHP._level + 20;
|
|
else if (missingHP._level > 60) TotalManaGain += missingHP._level + 150;
|
|
else TotalManaGain += 45;
|
|
break;
|
|
case MAGE:
|
|
if (missingHP._level < 23) TotalHealthGain += 15;
|
|
else if (missingHP._level > 60) TotalHealthGain += missingHP._level + 190;
|
|
else TotalHealthGain += missingHP._level - 8;
|
|
|
|
if (missingHP._level < 28) TotalManaGain += missingHP._level + 23;
|
|
else if (missingHP._level > 60) TotalManaGain += missingHP._level + 115;
|
|
else TotalManaGain += 51;
|
|
break;
|
|
case SHAMAN:
|
|
if (missingHP._level < 16) TotalHealthGain += 17;
|
|
else if (missingHP._level > 60) TotalHealthGain += missingHP._level + 157;
|
|
else TotalHealthGain += missingHP._level + 1;
|
|
|
|
if (missingHP._level < 22) TotalManaGain += missingHP._level + 19;
|
|
else if (missingHP._level > 60) TotalManaGain += missingHP._level + 175;
|
|
else TotalManaGain += 49;
|
|
break;
|
|
case WARLOCK:
|
|
if (missingHP._level < 17) TotalHealthGain += 17;
|
|
else if (missingHP._level > 60) TotalHealthGain += missingHP._level + 192;
|
|
else TotalHealthGain += missingHP._level - 2;
|
|
|
|
if (missingHP._level < 30) TotalManaGain += missingHP._level + 21;
|
|
else if (missingHP._level > 60) TotalManaGain += missingHP._level + 121;
|
|
else TotalManaGain += 51;
|
|
break;
|
|
case PALADIN:
|
|
if (missingHP._level < 14) TotalHealthGain += 18;
|
|
else if (missingHP._level > 60) TotalHealthGain += missingHP._level + 167;
|
|
else TotalHealthGain += missingHP._level + 4;
|
|
|
|
if (missingHP._level < 30) TotalManaGain += missingHP._level + 17;
|
|
else if (missingHP._level > 60) TotalManaGain += missingHP._level + 131;
|
|
else TotalManaGain += 42;
|
|
break;
|
|
case PRIEST:
|
|
if (missingHP._level < 21) TotalHealthGain += 15;
|
|
else if (missingHP._level > 60) TotalHealthGain += missingHP._level + 157;
|
|
else TotalHealthGain += missingHP._level - 6;
|
|
|
|
if (missingHP._level < 22) TotalManaGain += missingHP._level + 22;
|
|
else if (missingHP._level < 32) TotalManaGain += missingHP._level + 37;
|
|
else if (missingHP._level > 60) TotalManaGain += missingHP._level + 207;
|
|
else TotalManaGain += 54;
|
|
break;
|
|
case DEATHKNIGHT:
|
|
TotalHealthGain += 92;
|
|
break;
|
|
default:
|
|
TotalHealthGain += 15;
|
|
TotalManaGain += 45;
|
|
break;
|
|
}
|
|
|
|
if (auto level_info = sObjectMgr.GetLevelInfo(missingHP._race, missingHP._class, missingHP._level))
|
|
{
|
|
level_info->HP = level_info->HP + TotalHealthGain;
|
|
level_info->Mana = level_info->Mana + TotalManaGain;
|
|
++hp_counter;
|
|
}
|
|
}
|
|
|
|
uint32_t stat_counter = 0;
|
|
for (auto missingStat : _missingStatLevelData)
|
|
{
|
|
if (auto level_info = sObjectMgr.GetLevelInfo(missingStat._race, missingStat._class, missingStat._level))
|
|
{
|
|
uint32 val;
|
|
for (uint8_t id = 0; id < 5; ++id)
|
|
{
|
|
val = GainStat(static_cast<uint16>(missingStat._level), missingStat._class, id);
|
|
level_info->Stat[id] = level_info->Stat[id] + val;
|
|
}
|
|
|
|
++stat_counter;
|
|
}
|
|
}
|
|
|
|
sLogger.info("ObjectMgr : %u level up information generated.", (stat_counter + hp_counter));
|
|
|
|
}
|
|
|
|
|
|
LevelInfo* ObjectMgr::GetLevelInfo(uint32 Race, uint32 Class, uint32 Level)
|
|
{
|
|
// Iterate levelinfo map until we find the right class+race.
|
|
LevelInfoMap::iterator itr = mLevelInfo.begin();
|
|
for (; itr != mLevelInfo.end(); ++itr)
|
|
{
|
|
if (itr->first.first == Race && itr->first.second == Class)
|
|
{
|
|
// We got a match.
|
|
// Let's check that our level is valid first.
|
|
if (Level > worldConfig.player.playerLevelCap)
|
|
Level = worldConfig.player.playerLevelCap;
|
|
|
|
// Pull the level information from the second map.
|
|
LevelMap::iterator it2 = itr->second->find(Level);
|
|
if (it2 == itr->second->end())
|
|
{
|
|
sLogger.info("GetLevelInfo : No level information found for level %u!", Level);
|
|
return nullptr;
|
|
}
|
|
|
|
return it2->second;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ObjectMgr::LoadPetSpellCooldowns()
|
|
{
|
|
for (uint32 i = 0; i < sCreatureSpellDataStore.GetNumRows(); ++i)
|
|
{
|
|
auto creture_spell_data = sCreatureSpellDataStore.LookupEntry(i);
|
|
|
|
for (uint8 j = 0; j < 3; ++j)
|
|
{
|
|
if (creture_spell_data == nullptr)
|
|
continue;
|
|
|
|
uint32 SpellId = creture_spell_data->Spells[j];
|
|
uint32 Cooldown = creture_spell_data->Cooldowns[j] * 10;
|
|
|
|
if (SpellId != 0)
|
|
{
|
|
PetSpellCooldownMap::iterator itr = mPetSpellCooldowns.find(SpellId);
|
|
if (itr == mPetSpellCooldowns.end())
|
|
{
|
|
if (Cooldown)
|
|
mPetSpellCooldowns.insert(std::make_pair(SpellId, Cooldown));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 ObjectMgr::GetPetSpellCooldown(uint32 SpellId)
|
|
{
|
|
PetSpellCooldownMap::iterator itr = mPetSpellCooldowns.find(SpellId);
|
|
if (itr != mPetSpellCooldowns.end())
|
|
return itr->second;
|
|
|
|
SpellInfo const* sp = sSpellMgr.getSpellInfo(SpellId);
|
|
if (sp->getRecoveryTime() > sp->getCategoryRecoveryTime())
|
|
return sp->getRecoveryTime();
|
|
else
|
|
return sp->getCategoryRecoveryTime();
|
|
}
|
|
|
|
void ObjectMgr::SetVendorList(uint32 Entry, std::vector<CreatureItem>* list_)
|
|
{
|
|
mVendors[Entry] = list_;
|
|
}
|
|
|
|
void ObjectMgr::LoadCreatureTimedEmotes()
|
|
{
|
|
QueryResult* result = WorldDatabase.Query("SELECT * FROM creature_timed_emotes order by rowid asc");
|
|
if (!result)return;
|
|
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
spawn_timed_emotes* te = new spawn_timed_emotes;
|
|
te->type = fields[2].GetUInt8();
|
|
te->value = fields[3].GetUInt32();
|
|
char* str = (char*)fields[4].GetString();
|
|
if (str)
|
|
{
|
|
uint32 len = (int)strlen(str);
|
|
te->msg = new char[len + 1];
|
|
memcpy(te->msg, str, len + 1);
|
|
}
|
|
else te->msg = nullptr;
|
|
te->msg_type = static_cast<uint8>(fields[5].GetUInt32());
|
|
te->msg_lang = static_cast<uint8>(fields[6].GetUInt32());
|
|
te->expire_after = fields[7].GetUInt32();
|
|
|
|
std::unordered_map<uint32, TimedEmoteList*>::const_iterator i;
|
|
uint32 spawnid = fields[0].GetUInt32();
|
|
i = m_timedemotes.find(spawnid);
|
|
if (i == m_timedemotes.end())
|
|
{
|
|
TimedEmoteList* m = new TimedEmoteList;
|
|
m->push_back(te);
|
|
m_timedemotes[spawnid] = m;
|
|
}
|
|
else
|
|
{
|
|
i->second->push_back(te);
|
|
}
|
|
}
|
|
while (result->NextRow());
|
|
|
|
sLogger.info("ObjectMgr : %u timed emotes cached.", result->GetRowCount());
|
|
delete result;
|
|
}
|
|
|
|
TimedEmoteList* ObjectMgr::GetTimedEmoteList(uint32 spawnid)
|
|
{
|
|
std::unordered_map<uint32, TimedEmoteList*>::const_iterator i;
|
|
i = m_timedemotes.find(spawnid);
|
|
if (i != m_timedemotes.end())
|
|
{
|
|
TimedEmoteList* m = i->second;
|
|
return m;
|
|
}
|
|
else return nullptr;
|
|
}
|
|
|
|
Pet* ObjectMgr::CreatePet(uint32 entry)
|
|
{
|
|
uint32 guid;
|
|
guid = ++m_hiPetGuid;
|
|
return new Pet(WoWGuid(guid, entry, HIGHGUID_TYPE_PET));
|
|
}
|
|
|
|
Player* ObjectMgr::CreatePlayer(uint8 _class)
|
|
{
|
|
uint32_t guid= ++m_hiPlayerGuid;
|
|
|
|
return createPlayerByGuid(_class, guid);
|
|
}
|
|
|
|
void ObjectMgr::AddPlayer(Player* p)
|
|
{
|
|
std::lock_guard<std::mutex> guard(_playerslock);
|
|
|
|
_players[p->getGuidLow()] = p;
|
|
}
|
|
|
|
void ObjectMgr::RemovePlayer(Player* p)
|
|
{
|
|
std::lock_guard<std::mutex> guard(_playerslock);
|
|
|
|
_players.erase(p->getGuidLow());
|
|
}
|
|
|
|
void ObjectMgr::LoadReputationModifierTable(const char* tablename, ReputationModMap* dmap)
|
|
{
|
|
QueryResult* result = WorldDatabase.Query("SELECT * FROM %s", tablename);
|
|
|
|
if (result)
|
|
{
|
|
do
|
|
{
|
|
ReputationMod mod;
|
|
mod.faction[TEAM_ALLIANCE] = result->Fetch()[1].GetUInt32();
|
|
mod.faction[TEAM_HORDE] = result->Fetch()[2].GetUInt32();
|
|
mod.value = result->Fetch()[3].GetInt32();
|
|
mod.replimit = result->Fetch()[4].GetUInt32();
|
|
|
|
ReputationModMap::iterator itr = dmap->find(result->Fetch()[0].GetUInt32());
|
|
if (itr == dmap->end())
|
|
{
|
|
ReputationModifier* modifier = new ReputationModifier;
|
|
modifier->entry = result->Fetch()[0].GetUInt32();
|
|
modifier->mods.push_back(mod);
|
|
dmap->insert(ReputationModMap::value_type(result->Fetch()[0].GetUInt32(), modifier));
|
|
}
|
|
else
|
|
{
|
|
itr->second->mods.push_back(mod);
|
|
}
|
|
}
|
|
while (result->NextRow());
|
|
delete result;
|
|
}
|
|
sLogger.info("ObjectMgr : %u reputation modifiers on %s.", static_cast<uint32_t>(dmap->size()), tablename);
|
|
}
|
|
|
|
void ObjectMgr::LoadReputationModifiers()
|
|
{
|
|
LoadReputationModifierTable("reputation_creature_onkill", &m_reputation_creature);
|
|
LoadReputationModifierTable("reputation_faction_onkill", &m_reputation_faction);
|
|
LoadInstanceReputationModifiers();
|
|
}
|
|
|
|
ReputationModifier* ObjectMgr::GetReputationModifier(uint32 entry_id, uint32 faction_id)
|
|
{
|
|
// first, try fetching from the creature table (by faction is a fallback)
|
|
ReputationModMap::iterator itr = m_reputation_creature.find(entry_id);
|
|
if (itr != m_reputation_creature.end())
|
|
return itr->second;
|
|
|
|
// fetch from the faction table
|
|
itr = m_reputation_faction.find(faction_id);
|
|
if (itr != m_reputation_faction.end())
|
|
return itr->second;
|
|
|
|
// no data. fallback to default -5 value.
|
|
return nullptr;
|
|
}
|
|
|
|
void ObjectMgr::LoadInstanceReputationModifiers()
|
|
{
|
|
QueryResult* result = WorldDatabase.Query("SELECT * FROM reputation_instance_onkill");
|
|
if (!result)
|
|
return;
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
InstanceReputationMod mod;
|
|
mod.mapid = fields[0].GetUInt32();
|
|
mod.mob_rep_reward = fields[1].GetInt32();
|
|
mod.mob_rep_limit = fields[2].GetUInt32();
|
|
mod.boss_rep_reward = fields[3].GetInt32();
|
|
mod.boss_rep_limit = fields[4].GetUInt32();
|
|
mod.faction[TEAM_ALLIANCE] = fields[5].GetUInt32();
|
|
mod.faction[TEAM_HORDE] = fields[6].GetUInt32();
|
|
|
|
std::unordered_map<uint32, InstanceReputationModifier*>::iterator itr = m_reputation_instance.find(mod.mapid);
|
|
if (itr == m_reputation_instance.end())
|
|
{
|
|
InstanceReputationModifier* m = new InstanceReputationModifier;
|
|
m->mapid = mod.mapid;
|
|
m->mods.push_back(mod);
|
|
m_reputation_instance.insert(std::make_pair(m->mapid, m));
|
|
}
|
|
else
|
|
itr->second->mods.push_back(mod);
|
|
|
|
}
|
|
while (result->NextRow());
|
|
delete result;
|
|
|
|
sLogger.info("ObjectMgr : %u instance reputation modifiers loaded.", static_cast<uint32_t>(m_reputation_instance.size()));
|
|
}
|
|
|
|
bool ObjectMgr::HandleInstanceReputationModifiers(Player* pPlayer, Unit* pVictim)
|
|
{
|
|
uint32 team = pPlayer->getTeam();
|
|
|
|
if (!pVictim->isCreature())
|
|
return false;
|
|
|
|
std::unordered_map<uint32, InstanceReputationModifier*>::iterator itr = m_reputation_instance.find(pVictim->GetMapId());
|
|
if (itr == m_reputation_instance.end())
|
|
return false;
|
|
|
|
bool is_boss = false;
|
|
if (static_cast< Creature* >(pVictim)->GetCreatureProperties()->isBoss)
|
|
is_boss = true;
|
|
|
|
// Apply the bonuses as normal.
|
|
int32 replimit;
|
|
int32 value;
|
|
|
|
for (std::vector<InstanceReputationMod>::iterator i = itr->second->mods.begin(); i != itr->second->mods.end(); ++i)
|
|
{
|
|
if (!(*i).faction[team])
|
|
continue;
|
|
|
|
if (is_boss)
|
|
{
|
|
value = i->boss_rep_reward;
|
|
replimit = i->boss_rep_limit;
|
|
}
|
|
else
|
|
{
|
|
value = i->mob_rep_reward;
|
|
replimit = i->mob_rep_limit;
|
|
}
|
|
|
|
if (!value || (replimit && pPlayer->getFactionStanding(i->faction[team]) >= replimit))
|
|
continue;
|
|
|
|
//value *= sWorld.getRate(RATE_KILLREPUTATION);
|
|
value = float2int32(value * worldConfig.getFloatRate(RATE_KILLREPUTATION));
|
|
pPlayer->modFactionStanding(i->faction[team], value);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ObjectMgr::LoadGroups()
|
|
{
|
|
QueryResult* result = CharacterDatabase.Query("SELECT * FROM `groups`");
|
|
if (result)
|
|
{
|
|
if (result->GetFieldCount() != 52)
|
|
{
|
|
sLogger.failure("groups table format is invalid. Please update your database.");
|
|
return;
|
|
}
|
|
do
|
|
{
|
|
Group* g = new Group(false);
|
|
g->LoadFromDB(result->Fetch());
|
|
}
|
|
while (result->NextRow());
|
|
delete result;
|
|
}
|
|
|
|
sLogger.info("ObjectMgr : %u groups loaded.", static_cast<uint32_t>(this->m_groups.size()));
|
|
}
|
|
|
|
void ObjectMgr::loadGroupInstances()
|
|
{
|
|
// Delete Invalid Instances
|
|
CharacterDatabase.Execute("DELETE FROM group_instance WHERE guid NOT IN (SELECT guid FROM `groups`)");
|
|
|
|
QueryResult* result = CharacterDatabase.Query("SELECT gi.guid, i.map, gi.instance, gi.permanent, i.difficulty, i.resettime, (SELECT COUNT(1) FROM character_instance ci LEFT JOIN `groups` g ON ci.guid = g.group1member1 WHERE ci.instance = gi.instance AND ci.permanent = 1 LIMIT 1) FROM group_instance gi LEFT JOIN instance i ON gi.instance = i.id ORDER BY guid");
|
|
if (!result)
|
|
{
|
|
sLogger.info("Loaded 0 group-instance saves. DB table `group_instance` is empty!");
|
|
return;
|
|
}
|
|
|
|
uint32_t count = 0;
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
Group* group = sObjectMgr.GetGroupById(fields[0].GetUInt32());
|
|
|
|
DBC::Structures::MapEntry const* mapEntry = sMapStore.LookupEntry(fields[1].GetUInt16());
|
|
if (!mapEntry || !mapEntry->isDungeon())
|
|
{
|
|
sLogger.failure("Incorrect entry in group_instance table : no dungeon map %d", fields[1].GetUInt16());
|
|
continue;
|
|
}
|
|
|
|
uint32_t diff = fields[4].GetUInt8();
|
|
if (diff >= static_cast<uint32_t>(mapEntry->isRaid() ? InstanceDifficulty::Difficulties::MAX_RAID_DIFFICULTY : InstanceDifficulty::Difficulties::MAX_DUNGEON_DIFFICULTY))
|
|
{
|
|
sLogger.failure("Wrong dungeon difficulty use in group_instance table: %d", diff + 1);
|
|
diff = 0; // default for both difficaly types
|
|
}
|
|
|
|
InstanceSaved* save = sInstanceMgr.addInstanceSave(mapEntry->id, fields[2].GetUInt32(), InstanceDifficulty::Difficulties(diff), time_t(fields[5].GetUInt64()), fields[6].GetUInt64() == 0, true);
|
|
group->bindToInstance(save, fields[3].GetBool(), true);
|
|
++count;
|
|
}
|
|
while (result->NextRow());
|
|
delete result;
|
|
|
|
sLogger.info("Loaded %u group-instance saves", count);
|
|
}
|
|
|
|
void ObjectMgr::ResetDailies()
|
|
{
|
|
std::lock_guard<std::mutex> guard(_playerslock);
|
|
|
|
for (auto itr : _players)
|
|
{
|
|
if (Player* pPlayer = itr.second)
|
|
pPlayer->resetFinishedDailies();
|
|
}
|
|
}
|
|
|
|
uint32 ObjectMgr::GenerateArenaTeamId()
|
|
{
|
|
uint32 ret;
|
|
ret = ++m_hiArenaTeamId;
|
|
|
|
return ret;
|
|
}
|
|
|
|
uint32 ObjectMgr::GenerateGroupId()
|
|
{
|
|
uint32 r;
|
|
r = ++m_hiGroupId;
|
|
|
|
return r;
|
|
}
|
|
|
|
uint32 ObjectMgr::GenerateGuildId()
|
|
{
|
|
uint32 r;
|
|
r = ++m_hiGuildId;
|
|
|
|
return r;
|
|
}
|
|
|
|
void ObjectMgr::AddGroup(Group* group)
|
|
{
|
|
std::lock_guard<std::mutex> guard(m_groupLock);
|
|
m_groups.insert(std::make_pair(group->GetID(), group));
|
|
}
|
|
|
|
void ObjectMgr::RemoveGroup(Group* group)
|
|
{
|
|
std::lock_guard<std::mutex> guard(m_groupLock);
|
|
m_groups.erase(group->GetID());
|
|
}
|
|
|
|
uint32 ObjectMgr::GenerateCreatureSpawnID()
|
|
{
|
|
uint32 r;
|
|
r = ++m_hiCreatureSpawnId;
|
|
|
|
return r;
|
|
}
|
|
|
|
uint32 ObjectMgr::GenerateGameObjectSpawnID()
|
|
{
|
|
uint32 r;
|
|
r = ++m_hiGameObjectSpawnId;
|
|
|
|
return r;
|
|
}
|
|
#ifdef FT_VEHICLES
|
|
void ObjectMgr::LoadVehicleAccessories()
|
|
{
|
|
_vehicleAccessoryStore.clear();
|
|
|
|
QueryResult* result = WorldDatabase.Query("SELECT entry, accessory_entry, seat_id , minion, summontype, summontimer FROM vehicle_accessories;");
|
|
if (result != nullptr)
|
|
{
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
|
|
uint32_t entry = fields[0].GetUInt32();
|
|
uint32_t accessory = fields[1].GetUInt32();
|
|
int8_t seatId = fields[2].GetInt8();
|
|
bool isMinion = fields[3].GetBool();
|
|
uint8_t summonType = fields[4].GetUInt8();
|
|
uint32_t summonTimer = fields[5].GetUInt32();
|
|
|
|
if (!sMySQLStore.getCreatureProperties(entry))
|
|
{
|
|
sLogger.failure("Table `vehicle_accessories`: creature template entry %u does not exist.", entry);
|
|
continue;
|
|
}
|
|
|
|
if (!sMySQLStore.getCreatureProperties(accessory))
|
|
{
|
|
sLogger.failure("Table `vehicle_accessories`: Accessory %u does not exist.", accessory);
|
|
continue;
|
|
}
|
|
|
|
auto _spellClickInfoStore = sMySQLStore.getSpellClickSpellsStore();
|
|
if (_spellClickInfoStore->find(entry) == _spellClickInfoStore->end())
|
|
{
|
|
sLogger.failure("Table `vehicle_accessories`: creature template entry %u has no data in npc_spellclick_spells", entry);
|
|
continue;
|
|
}
|
|
|
|
_vehicleAccessoryStore[entry].push_back(VehicleAccessory(accessory, seatId, isMinion, summonType, summonTimer));
|
|
|
|
} while (result->NextRow());
|
|
|
|
delete result;
|
|
}
|
|
}
|
|
|
|
void ObjectMgr::loadVehicleSeatAddon()
|
|
{
|
|
_vehicleSeatAddonStore.clear();
|
|
|
|
QueryResult* result = WorldDatabase.Query("SELECT SeatEntry, SeatOrientation, ExitParamX , ExitParamY, ExitParamZ, ExitParamO, ExitParamValue FROM vehicle_seat_addon;");
|
|
if (result != nullptr)
|
|
{
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
|
|
uint32_t seatID = fields[0].GetUInt32();
|
|
float orientation = fields[1].GetFloat();
|
|
float exitX = fields[2].GetFloat();
|
|
float exitY = fields[3].GetFloat();
|
|
float exitZ = fields[4].GetFloat();
|
|
float exitO = fields[5].GetFloat();
|
|
uint8_t exitParam = fields[6].GetUInt8();
|
|
|
|
_vehicleSeatAddonStore[seatID] = VehicleSeatAddon(orientation, { exitX, exitY, exitZ, exitO }, exitParam);
|
|
|
|
} while (result->NextRow());
|
|
|
|
delete result;
|
|
}
|
|
}
|
|
|
|
VehicleAccessoryList const* ObjectMgr::getVehicleAccessories(Vehicle* vehicle)
|
|
{
|
|
VehicleAccessoryContainer::const_iterator itr = _vehicleAccessoryStore.find(vehicle->getEntry());
|
|
if (itr != _vehicleAccessoryStore.end())
|
|
return &itr->second;
|
|
return nullptr;
|
|
}
|
|
#endif
|
|
|
|
void ObjectMgr::LoadEventScripts()
|
|
{
|
|
sLogger.info("ObjectMgr : Loading Event Scripts...");
|
|
|
|
bool success = false;
|
|
const char* eventScriptsQuery = "SELECT `event_id`, `function`, `script_type`, `data_1`, `data_2`, `data_3`, `data_4`, `data_5`, `x`, `y`, `z`, `o`, `delay`, `next_event` FROM `event_scripts` WHERE `event_id` > 0 ORDER BY `event_id`";
|
|
auto result = WorldDatabase.Query(&success, eventScriptsQuery);
|
|
|
|
if (!success)
|
|
{
|
|
sLogger.debug("LoadEventScripts : Failed on Loading Queries from event_scripts.");
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (!result)
|
|
{
|
|
sLogger.debug("LoadEventScripts : Loaded 0 event_scripts. DB table `event_scripts` is empty.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint32 count = 0;
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
|
|
uint32 event_id = fields[0].GetUInt32();
|
|
SimpleEventScript eventscript;
|
|
|
|
eventscript.eventId = event_id;
|
|
eventscript.function = static_cast<uint8>(ScriptCommands(fields[1].GetUInt8()));
|
|
eventscript.scripttype = static_cast<uint8>(EasyScriptTypes(fields[2].GetUInt8()));
|
|
eventscript.data_1 = fields[3].GetUInt32();
|
|
eventscript.data_2 = fields[4].GetUInt32();
|
|
eventscript.data_3 = fields[5].GetUInt32();
|
|
eventscript.data_4 = fields[6].GetUInt32();
|
|
eventscript.data_5 = fields[7].GetUInt32();
|
|
eventscript.x = fields[8].GetUInt32();
|
|
eventscript.y = fields[9].GetUInt32();
|
|
eventscript.z = fields[10].GetUInt32();
|
|
eventscript.o = fields[11].GetUInt32();
|
|
eventscript.delay = fields[12].GetUInt32();
|
|
eventscript.nextevent = fields[13].GetUInt32();
|
|
|
|
SimpleEventScript* SimpleEventScript = &mEventScriptMaps.insert(EventScriptMaps::value_type(event_id, eventscript))->second;
|
|
|
|
// for search by spellid ( data_1 is spell id )
|
|
if (eventscript.data_1 && eventscript.scripttype == static_cast<uint8>(EasyScriptTypes::SCRIPT_TYPE_SPELL_EFFECT))
|
|
mSpellEffectMaps.insert(SpellEffectMaps::value_type(eventscript.data_1, SimpleEventScript));
|
|
|
|
|
|
++count;
|
|
|
|
} while (result->NextRow());
|
|
|
|
delete result;
|
|
|
|
sLogger.info("ObjectMgr : Loaded event_scripts for %u events...", count);
|
|
}
|
|
|
|
EventScriptBounds ObjectMgr::GetEventScripts(uint32 event_id) const
|
|
{
|
|
return EventScriptBounds(mEventScriptMaps.lower_bound(event_id), mEventScriptMaps.upper_bound(event_id));
|
|
}
|
|
|
|
SpellEffectMapBounds ObjectMgr::GetSpellEffectBounds(uint32 data_1) const
|
|
{
|
|
return SpellEffectMapBounds(mSpellEffectMaps.lower_bound(data_1), mSpellEffectMaps.upper_bound(data_1));
|
|
}
|
|
|
|
bool ObjectMgr::CheckforScripts(Player* plr, uint32 event_id)
|
|
{
|
|
EventScriptBounds EventScript = sObjectMgr.GetEventScripts(event_id);
|
|
if (EventScript.first == EventScript.second)
|
|
return false;
|
|
|
|
for (EventScriptMaps::const_iterator itr = EventScript.first; itr != EventScript.second; ++itr)
|
|
{
|
|
sEventMgr.AddEvent(this, &ObjectMgr::EventScriptsUpdate, plr, itr->second.eventId, EVENT_EVENT_SCRIPTS, itr->second.delay, 1, 0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ObjectMgr::CheckforDummySpellScripts(Player* plr, uint32 data_1)
|
|
{
|
|
SpellEffectMapBounds EventScript = sObjectMgr.GetSpellEffectBounds(data_1);
|
|
if (EventScript.first == EventScript.second)
|
|
return false;
|
|
|
|
for (SpellEffectMaps::const_iterator itr = EventScript.first; itr != EventScript.second; ++itr)
|
|
{
|
|
sEventMgr.AddEvent(this, &ObjectMgr::EventScriptsUpdate, plr, itr->second->eventId, EVENT_EVENT_SCRIPTS, itr->second->delay, 1, 0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ObjectMgr::EventScriptsUpdate(Player* plr, uint32 next_event)
|
|
{
|
|
EventScriptBounds EventScript = sObjectMgr.GetEventScripts(next_event);
|
|
|
|
for (EventScriptMaps::const_iterator itr = EventScript.first; itr != EventScript.second; ++itr)
|
|
{
|
|
if (itr->second.scripttype == static_cast<uint8>(EasyScriptTypes::SCRIPT_TYPE_SPELL_EFFECT) || itr->second.scripttype == static_cast<uint8>(EasyScriptTypes::SCRIPT_TYPE_DUMMY))
|
|
{
|
|
switch (itr->second.function)
|
|
{
|
|
case static_cast<uint8>(ScriptCommands::SCRIPT_COMMAND_RESPAWN_GAMEOBJECT):
|
|
{
|
|
Object* target = plr->getWorldMap()->getInterface()->getGameObjectNearestCoords(plr->GetPositionX(), plr->GetPositionY(), plr->GetPositionZ(), itr->second.data_1);
|
|
if (target == nullptr)
|
|
return;
|
|
|
|
static_cast<GameObject*>(target)->despawn(1000, itr->second.data_2);
|
|
|
|
break;
|
|
}
|
|
|
|
case static_cast<uint8>(ScriptCommands::SCRIPT_COMMAND_KILL_CREDIT):
|
|
{
|
|
if (auto* questLog = plr->getQuestLogByQuestId(itr->second.data_2))
|
|
{
|
|
if (questLog->getQuestProperties()->required_mob_or_go[itr->second.data_5] >= 0)
|
|
{
|
|
uint32 required_mob = static_cast<uint32>(questLog->getQuestProperties()->required_mob_or_go[itr->second.data_5]);
|
|
const auto index = static_cast<uint8_t>(itr->second.data_5);
|
|
if (questLog->getMobCountByIndex(index) < required_mob)
|
|
{
|
|
questLog->setMobCountForIndex(index, questLog->getMobCountByIndex(index) + 1);
|
|
questLog->sendUpdateAddKill(index);
|
|
questLog->updatePlayerFields();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (itr->second.scripttype == static_cast<uint8>(EasyScriptTypes::SCRIPT_TYPE_GAMEOBJECT) || itr->second.scripttype == static_cast<uint8>(EasyScriptTypes::SCRIPT_TYPE_DUMMY))
|
|
{
|
|
switch (itr->second.function)
|
|
{
|
|
case static_cast<uint8>(ScriptCommands::SCRIPT_COMMAND_ACTIVATE_OBJECT):
|
|
{
|
|
if ((itr->second.x || itr->second.y || itr->second.z) == 0)
|
|
{
|
|
Object* target = plr->getWorldMap()->getInterface()->getGameObjectNearestCoords(plr->GetPositionX(), plr->GetPositionY(), plr->GetPositionZ(), itr->second.data_1);
|
|
if (target == nullptr)
|
|
return;
|
|
|
|
if (static_cast<GameObject*>(target)->getState() != GO_STATE_OPEN)
|
|
{
|
|
static_cast<GameObject*>(target)->setState(GO_STATE_OPEN);
|
|
}
|
|
else
|
|
{
|
|
static_cast<GameObject*>(target)->setState(GO_STATE_CLOSED);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Object* target = plr->getWorldMap()->getInterface()->getGameObjectNearestCoords(float(itr->second.x), float(itr->second.y), float(itr->second.z), itr->second.data_1);
|
|
if (target == nullptr)
|
|
return;
|
|
|
|
if (static_cast<GameObject*>(target)->getState() != GO_STATE_OPEN)
|
|
{
|
|
static_cast<GameObject*>(target)->setState(GO_STATE_OPEN);
|
|
}
|
|
else
|
|
{
|
|
static_cast<GameObject*>(target)->setState(GO_STATE_CLOSED);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (itr->second.nextevent != 0)
|
|
{
|
|
sObjectMgr.CheckforScripts(plr, itr->second.nextevent);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ObjectMgr::LoadInstanceEncounters()
|
|
{
|
|
const auto startTime = Util::TimeNow();
|
|
|
|
// 0 1 2 3 4 5
|
|
QueryResult* result = WorldDatabase.Query("SELECT entry, creditType, creditEntry, lastEncounterDungeon, comment, mapid FROM instance_encounters");
|
|
if (result == nullptr)
|
|
{
|
|
sLogger.debug(">> Loaded 0 instance encounters, table is empty!");
|
|
return;
|
|
}
|
|
|
|
#if VERSION_STRING >= WotLK
|
|
std::map<uint32_t, DBC::Structures::DungeonEncounterEntry const*> dungeonLastBosses;
|
|
#endif
|
|
|
|
uint32_t count = 0;
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
auto entry = fields[0].GetUInt32();
|
|
auto creditType = fields[1].GetUInt8();
|
|
auto creditEntry = fields[2].GetUInt32();
|
|
auto lastEncounterDungeon = fields[3].GetUInt16();
|
|
auto dungeonEncounterName = fields[4].GetString();
|
|
|
|
#if VERSION_STRING <= TBC
|
|
auto mapId = fields[5].GetUInt32();
|
|
#else
|
|
const auto dungeonEncounter = sDungeonEncounterStore.LookupEntry(entry);
|
|
if (dungeonEncounter == nullptr)
|
|
{
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "Table `instance_encounters` has an invalid encounter id %u, skipped!", entry);
|
|
continue;
|
|
}
|
|
|
|
#if VERSION_STRING == WotLK
|
|
dungeonEncounterName = dungeonEncounter->encounterName[sWorld.getDbcLocaleLanguageId()];
|
|
#else
|
|
dungeonEncounterName = dungeonEncounter->encounterName;
|
|
#endif
|
|
#endif
|
|
|
|
if (lastEncounterDungeon && sLfgMgr.GetLFGDungeon(lastEncounterDungeon) == 0)
|
|
{
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "Table `instance_encounters` has an encounter %u (%s) marked as final for invalid dungeon id %u, skipped!", entry, dungeonEncounterName, lastEncounterDungeon);
|
|
continue;
|
|
}
|
|
|
|
#if VERSION_STRING >= WotLK
|
|
if (lastEncounterDungeon)
|
|
{
|
|
const auto itr = dungeonLastBosses.find(lastEncounterDungeon);
|
|
if (itr != dungeonLastBosses.end())
|
|
{
|
|
#if VERSION_STRING == WotLK
|
|
const auto itrEncounterName = itr->second->encounterName[sWorld.getDbcLocaleLanguageId()];
|
|
#else
|
|
const auto itrEncounterName = itr->second->encounterName;
|
|
#endif
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "Table `instance_encounters` specified encounter %u (%s) as last encounter but %u (%s) is already marked as one, skipped!", entry, dungeonEncounterName, itr->second->id, itrEncounterName);
|
|
continue;
|
|
}
|
|
|
|
dungeonLastBosses[lastEncounterDungeon] = dungeonEncounter;
|
|
}
|
|
#endif
|
|
|
|
switch (creditType)
|
|
{
|
|
case ENCOUNTER_CREDIT_KILL_CREATURE:
|
|
{
|
|
auto creatureprop = sMySQLStore.getCreatureProperties(creditEntry);
|
|
if (creatureprop == nullptr)
|
|
{
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "Table `instance_encounters` has an invalid creature (entry %u) linked to the encounter %u (%s), skipped!", creditEntry, entry, dungeonEncounterName);
|
|
continue;
|
|
}
|
|
const_cast<CreatureProperties*>(creatureprop)->extra_a9_flags |= 0x10000000; // Flagged Dungeon Boss
|
|
break;
|
|
}
|
|
case ENCOUNTER_CREDIT_CAST_SPELL:
|
|
{
|
|
if (sSpellMgr.getSpellInfo(creditEntry) == nullptr)
|
|
{
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "Table `instance_encounters` has an invalid spell (entry %u) linked to the encounter %u (%s), skipped!", creditEntry, entry, dungeonEncounterName);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
sLogger.debugFlag(AscEmu::Logging::LF_DB_TABLES, "Table `instance_encounters` has an invalid credit type (%u) for encounter %u (%s), skipped!", creditType, entry, dungeonEncounterName);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
#if VERSION_STRING <= TBC
|
|
DungeonEncounterList& encounters = _dungeonEncounterStore[mapId];
|
|
encounters.push_back(std::make_shared<DungeonEncounter>(EncounterCreditType(creditType), creditEntry));
|
|
#else
|
|
DungeonEncounterList& encounters = m_dungeonEncounterStore[static_cast<int32_t>(static_cast<uint16_t>(dungeonEncounter->mapId) | (static_cast<uint32_t>(dungeonEncounter->difficulty) << 16))];
|
|
encounters.push_back(std::make_shared<DungeonEncounter>(dungeonEncounter, EncounterCreditType(creditType), creditEntry, lastEncounterDungeon));
|
|
#endif
|
|
++count;
|
|
} while (result->NextRow());
|
|
|
|
sLogger.info("ObjectMgr : Loaded %u instance encounters in %u ms", count, static_cast<uint32_t>(Util::GetTimeDifferenceToNow(startTime)));
|
|
}
|
|
|
|
#if VERSION_STRING > WotLK
|
|
uint64_t ObjectMgr::generateVoidStorageItemId()
|
|
{
|
|
return ++m_voidItemId;
|
|
}
|
|
#endif
|