diff options
Diffstat (limited to 'src/server/game/Handlers/CharacterHandler.cpp')
-rw-r--r-- | src/server/game/Handlers/CharacterHandler.cpp | 1915 |
1 files changed, 1915 insertions, 0 deletions
diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp new file mode 100644 index 00000000000..bd9668ce5b8 --- /dev/null +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -0,0 +1,1915 @@ +/* + * Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include "Common.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "ArenaTeamMgr.h" +#include "GuildMgr.h" +#include "SystemConfig.h" +#include "World.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "DatabaseEnv.h" + +#include "ArenaTeam.h" +#include "Chat.h" +#include "Group.h" +#include "Guild.h" +#include "Language.h" +#include "Log.h" +#include "Opcodes.h" +#include "Player.h" +#include "PlayerDump.h" +#include "SharedDefines.h" +#include "SocialMgr.h" +#include "UpdateMask.h" +#include "Util.h" +#include "ScriptMgr.h" +#include "Battleground.h" +#include "AccountMgr.h" +#include "LFGMgr.h" + +class LoginQueryHolder : public SQLQueryHolder +{ + private: + uint32 m_accountId; + uint64 m_guid; + public: + LoginQueryHolder(uint32 accountId, uint64 guid) + : m_accountId(accountId), m_guid(guid) { } + uint64 GetGuid() const { return m_guid; } + uint32 GetAccountId() const { return m_accountId; } + bool Initialize(); +}; + +bool LoginQueryHolder::Initialize() +{ + SetSize(MAX_PLAYER_LOGIN_QUERY); + + bool res = true; + uint32 lowGuid = GUID_LOPART(m_guid); + PreparedStatement* stmt = NULL; + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADFROM, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_GROUP_MEMBER); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADGROUP, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_INSTANCE); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADBOUNDINSTANCES, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_AURAS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADAURAS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SPELL); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADSPELLS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_QUESTSTATUS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADQUESTSTATUS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_DAILYQUESTSTATUS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADDAILYQUESTSTATUS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_WEEKLYQUESTSTATUS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADWEEKLYQUESTSTATUS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SEASONALQUESTSTATUS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADSEASONALQUESTSTATUS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_REPUTATION); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADREPUTATION, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_INVENTORY); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADINVENTORY, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACTIONS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADACTIONS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_MAILCOUNT); + stmt->setUInt32(0, lowGuid); + stmt->setUInt64(1, uint64(time(NULL))); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADMAILCOUNT, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_MAILDATE); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADMAILDATE, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SOCIALLIST); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADSOCIALLIST, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_HOMEBIND); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADHOMEBIND, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SPELLCOOLDOWNS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADSPELLCOOLDOWNS, stmt); + + if (sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED)) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_DECLINEDNAMES); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADDECLINEDNAMES, stmt); + } + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_GUILD_MEMBER); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADGUILD, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ARENAINFO); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADARENAINFO, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACHIEVEMENTS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADACHIEVEMENTS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_CRITERIAPROGRESS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADCRITERIAPROGRESS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_EQUIPMENTSETS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADEQUIPMENTSETS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_BGDATA); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADBGDATA, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_GLYPHS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADGLYPHS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_TALENTS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADTALENTS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PLAYER_ACCOUNT_DATA); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADACCOUNTDATA, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SKILLS); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADSKILLS, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_RANDOMBG); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADRANDOMBG, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_BANNED); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADBANNED, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_QUESTSTATUSREW); + stmt->setUInt32(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADQUESTSTATUSREW, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ACCOUNT_INSTANCELOCKTIMES); + stmt->setUInt32(0, m_accountId); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOADINSTANCELOCKTIMES, stmt); + + return res; +} + +void WorldSession::HandleCharEnum(PreparedQueryResult result) +{ + WorldPacket data(SMSG_CHAR_ENUM, 100); // we guess size + + uint8 num = 0; + + data << num; + + _allowedCharsToLogin.clear(); + if (result) + { + do + { + uint32 guidlow = (*result)[0].GetUInt32(); + sLog->outDetail("Loading char guid %u from account %u.", guidlow, GetAccountId()); + if (Player::BuildEnumData(result, &data)) + { + _allowedCharsToLogin.insert(guidlow); + ++num; + } + } + while (result->NextRow()); + } + + data.put<uint8>(0, num); + + SendPacket(&data); +} + +void WorldSession::HandleCharEnumOpcode(WorldPacket & /*recv_data*/) +{ + // remove expired bans + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_EXPIRED_BANS); + CharacterDatabase.Execute(stmt); + + /// get all the data necessary for loading all characters (along with their pets) on the account + + if (sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED)) + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ENUM_DECLINED_NAME); + else + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ENUM); + + stmt->setUInt8(0, PET_SAVE_AS_CURRENT); + stmt->setUInt32(1, GetAccountId()); + + _charEnumCallback = CharacterDatabase.AsyncQuery(stmt); +} + +void WorldSession::HandleCharCreateOpcode(WorldPacket & recv_data) +{ + std::string name; + uint8 race_, class_; + + recv_data >> name; + + recv_data >> race_; + recv_data >> class_; + + // extract other data required for player creating + uint8 gender, skin, face, hairStyle, hairColor, facialHair, outfitId; + recv_data >> gender >> skin >> face; + recv_data >> hairStyle >> hairColor >> facialHair >> outfitId; + + WorldPacket data(SMSG_CHAR_CREATE, 1); // returned with diff.values in all cases + + if (AccountMgr::IsPlayerAccount(GetSecurity())) + { + if (uint32 mask = sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_DISABLED)) + { + bool disabled = false; + + uint32 team = Player::TeamForRace(race_); + switch (team) + { + case ALLIANCE: disabled = mask & (1 << 0); break; + case HORDE: disabled = mask & (1 << 1); break; + } + + if (disabled) + { + data << (uint8)CHAR_CREATE_DISABLED; + SendPacket(&data); + return; + } + } + } + + ChrClassesEntry const* classEntry = sChrClassesStore.LookupEntry(class_); + if (!classEntry) + { + data << (uint8)CHAR_CREATE_FAILED; + SendPacket(&data); + sLog->outError("Class (%u) not found in DBC while creating new char for account (ID: %u): wrong DBC files or cheater?", class_, GetAccountId()); + return; + } + + ChrRacesEntry const* raceEntry = sChrRacesStore.LookupEntry(race_); + if (!raceEntry) + { + data << (uint8)CHAR_CREATE_FAILED; + SendPacket(&data); + sLog->outError("Race (%u) not found in DBC while creating new char for account (ID: %u): wrong DBC files or cheater?", race_, GetAccountId()); + return; + } + + // prevent character creating Expansion race without Expansion account + if (raceEntry->expansion > Expansion()) + { + data << (uint8)CHAR_CREATE_EXPANSION; + sLog->outError("Expansion %u account:[%d] tried to Create character with expansion %u race (%u)", Expansion(), GetAccountId(), raceEntry->expansion, race_); + SendPacket(&data); + return; + } + + // prevent character creating Expansion class without Expansion account + if (classEntry->expansion > Expansion()) + { + data << (uint8)CHAR_CREATE_EXPANSION_CLASS; + sLog->outError("Expansion %u account:[%d] tried to Create character with expansion %u class (%u)", Expansion(), GetAccountId(), classEntry->expansion, class_); + SendPacket(&data); + return; + } + + if (AccountMgr::IsPlayerAccount(GetSecurity())) + { + uint32 raceMaskDisabled = sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_DISABLED_RACEMASK); + if ((1 << (race_ - 1)) & raceMaskDisabled) + { + data << uint8(CHAR_CREATE_DISABLED); + SendPacket(&data); + return; + } + + uint32 classMaskDisabled = sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_DISABLED_CLASSMASK); + if ((1 << (class_ - 1)) & classMaskDisabled) + { + data << uint8(CHAR_CREATE_DISABLED); + SendPacket(&data); + return; + } + } + + // prevent character creating with invalid name + if (!normalizePlayerName(name)) + { + data << (uint8)CHAR_NAME_NO_NAME; + SendPacket(&data); + sLog->outError("Account:[%d] but tried to Create character with empty [name] ", GetAccountId()); + return; + } + + // check name limitations + uint8 res = ObjectMgr::CheckPlayerName(name, true); + if (res != CHAR_NAME_SUCCESS) + { + data << uint8(res); + SendPacket(&data); + return; + } + + if (AccountMgr::IsPlayerAccount(GetSecurity()) && sObjectMgr->IsReservedName(name)) + { + data << (uint8)CHAR_NAME_RESERVED; + SendPacket(&data); + return; + } + + // speedup check for heroic class disabled case + uint32 heroic_free_slots = sWorld->getIntConfig(CONFIG_HEROIC_CHARACTERS_PER_REALM); + if (heroic_free_slots == 0 && AccountMgr::IsPlayerAccount(GetSecurity()) && class_ == CLASS_DEATH_KNIGHT) + { + data << (uint8)CHAR_CREATE_UNIQUE_CLASS_LIMIT; + SendPacket(&data); + return; + } + + // speedup check for heroic class disabled case + uint32 req_level_for_heroic = sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_MIN_LEVEL_FOR_HEROIC_CHARACTER); + if (AccountMgr::IsPlayerAccount(GetSecurity()) && class_ == CLASS_DEATH_KNIGHT && req_level_for_heroic > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) + { + data << (uint8)CHAR_CREATE_LEVEL_REQUIREMENT; + SendPacket(&data); + return; + } + + delete _charCreateCallback.GetParam(); // Delete existing if any, to make the callback chain reset to stage 0 + _charCreateCallback.SetParam(new CharacterCreateInfo(name, race_, class_, gender, skin, face, hairStyle, hairColor, facialHair, outfitId, recv_data)); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_NAME); + stmt->setString(0, name); + _charCreateCallback.SetFutureResult(CharacterDatabase.AsyncQuery(stmt)); +} + +void WorldSession::HandleCharCreateCallback(PreparedQueryResult result, CharacterCreateInfo* createInfo) +{ + /** This is a series of callbacks executed consecutively as a result from the database becomes available. + This is much more efficient than synchronous requests on packet handler, and much less DoS prone. + It also prevents data syncrhonisation errors. + */ + switch (_charCreateCallback.GetStage()) + { + case 0: + { + if (result) + { + WorldPacket data(SMSG_CHAR_CREATE, 1); + data << uint8(CHAR_CREATE_NAME_IN_USE); + SendPacket(&data); + delete createInfo; + _charCreateCallback.Reset(); + return; + } + + ASSERT(_charCreateCallback.GetParam() == createInfo); + + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_SUM_REALM_CHARACTERS); + stmt->setUInt32(0, GetAccountId()); + + _charCreateCallback.FreeResult(); + _charCreateCallback.SetFutureResult(LoginDatabase.AsyncQuery(stmt)); + _charCreateCallback.NextStage(); + } + break; + case 1: + { + uint16 acctCharCount = 0; + if (result) + { + Field* fields = result->Fetch(); + // SELECT SUM(x) is MYSQL_TYPE_NEWDECIMAL - needs to be read as string + const char* ch = fields[0].GetCString(); + if (ch) + acctCharCount = atoi(ch); + } + + if (acctCharCount >= sWorld->getIntConfig(CONFIG_CHARACTERS_PER_ACCOUNT)) + { + WorldPacket data(SMSG_CHAR_CREATE, 1); + data << uint8(CHAR_CREATE_ACCOUNT_LIMIT); + SendPacket(&data); + delete createInfo; + _charCreateCallback.Reset(); + return; + } + + + ASSERT(_charCreateCallback.GetParam() == createInfo); + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_SUM_CHARS); + stmt->setUInt32(0, GetAccountId()); + + _charCreateCallback.FreeResult(); + _charCreateCallback.SetFutureResult(CharacterDatabase.AsyncQuery(stmt)); + _charCreateCallback.NextStage(); + } + break; + case 2: + { + if (result) + { + Field* fields = result->Fetch(); + createInfo->CharCount = fields[0].GetUInt8(); + + if (createInfo->CharCount >= sWorld->getIntConfig(CONFIG_CHARACTERS_PER_REALM)) + { + WorldPacket data(SMSG_CHAR_CREATE, 1); + data << uint8(CHAR_CREATE_SERVER_LIMIT); + SendPacket(&data); + delete createInfo; + _charCreateCallback.Reset(); + return; + } + } + + bool allowTwoSideAccounts = !sWorld->IsPvPRealm() || sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_ACCOUNTS) || !AccountMgr::IsPlayerAccount(GetSecurity()); + uint32 skipCinematics = sWorld->getIntConfig(CONFIG_SKIP_CINEMATICS); + + _charCreateCallback.FreeResult(); + + if (!allowTwoSideAccounts || skipCinematics == 1 || createInfo->Class == CLASS_DEATH_KNIGHT) + { + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_CREATE_INFO); + stmt->setUInt32(0, GetAccountId()); + stmt->setUInt32(1, (skipCinematics == 1 || createInfo->Class == CLASS_DEATH_KNIGHT) ? 10 : 1); + _charCreateCallback.SetFutureResult(CharacterDatabase.AsyncQuery(stmt)); + _charCreateCallback.NextStage(); + return; + } + + _charCreateCallback.NextStage(); + HandleCharCreateCallback(PreparedQueryResult(NULL), createInfo); // Will jump to case 3 + } + break; + case 3: + { + bool haveSameRace = false; + uint32 heroicReqLevel = sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_MIN_LEVEL_FOR_HEROIC_CHARACTER); + bool hasHeroicReqLevel = (heroicReqLevel == 0); + bool allowTwoSideAccounts = !sWorld->IsPvPRealm() || sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_ACCOUNTS) || !AccountMgr::IsPlayerAccount(GetSecurity()); + uint32 skipCinematics = sWorld->getIntConfig(CONFIG_SKIP_CINEMATICS); + + if (result) + { + uint32 team = Player::TeamForRace(createInfo->Race); + uint32 freeHeroicSlots = sWorld->getIntConfig(CONFIG_HEROIC_CHARACTERS_PER_REALM); + + Field* field = result->Fetch(); + uint8 accRace = field[1].GetUInt8(); + + if (AccountMgr::IsPlayerAccount(GetSecurity()) && createInfo->Class == CLASS_DEATH_KNIGHT) + { + uint8 accClass = field[2].GetUInt8(); + if (accClass == CLASS_DEATH_KNIGHT) + { + if (freeHeroicSlots > 0) + --freeHeroicSlots; + + if (freeHeroicSlots == 0) + { + WorldPacket data(SMSG_CHAR_CREATE, 1); + data << uint8(CHAR_CREATE_UNIQUE_CLASS_LIMIT); + SendPacket(&data); + delete createInfo; + _charCreateCallback.Reset(); + return; + } + } + + if (!hasHeroicReqLevel) + { + uint8 accLevel = field[0].GetUInt8(); + if (accLevel >= heroicReqLevel) + hasHeroicReqLevel = true; + } + } + + // need to check team only for first character + // TODO: what to if account already has characters of both races? + if (!allowTwoSideAccounts) + { + uint32 accTeam = 0; + if (accRace > 0) + accTeam = Player::TeamForRace(accRace); + + if (accTeam != team) + { + WorldPacket data(SMSG_CHAR_CREATE, 1); + data << uint8(CHAR_CREATE_PVP_TEAMS_VIOLATION); + SendPacket(&data); + delete createInfo; + _charCreateCallback.Reset(); + return; + } + } + + // search same race for cinematic or same class if need + // TODO: check if cinematic already shown? (already logged in?; cinematic field) + while ((skipCinematics == 1 && !haveSameRace) || createInfo->Class == CLASS_DEATH_KNIGHT) + { + if (!result->NextRow()) + break; + + field = result->Fetch(); + accRace = field[1].GetUInt8(); + + if (!haveSameRace) + haveSameRace = createInfo->Race == accRace; + + if (AccountMgr::IsPlayerAccount(GetSecurity()) && createInfo->Class == CLASS_DEATH_KNIGHT) + { + uint8 acc_class = field[2].GetUInt8(); + if (acc_class == CLASS_DEATH_KNIGHT) + { + if (freeHeroicSlots > 0) + --freeHeroicSlots; + + if (freeHeroicSlots == 0) + { + WorldPacket data(SMSG_CHAR_CREATE, 1); + data << uint8(CHAR_CREATE_UNIQUE_CLASS_LIMIT); + SendPacket(&data); + delete createInfo; + _charCreateCallback.Reset(); + return; + } + } + + if (!hasHeroicReqLevel) + { + uint8 acc_level = field[0].GetUInt8(); + if (acc_level >= heroicReqLevel) + hasHeroicReqLevel = true; + } + } + } + } + + if (AccountMgr::IsPlayerAccount(GetSecurity()) && createInfo->Class == CLASS_DEATH_KNIGHT && !hasHeroicReqLevel) + { + WorldPacket data(SMSG_CHAR_CREATE, 1); + data << uint8(CHAR_CREATE_LEVEL_REQUIREMENT); + SendPacket(&data); + delete createInfo; + _charCreateCallback.Reset(); + return; + } + + if (createInfo->Data.rpos() < createInfo->Data.wpos()) + { + uint8 unk; + createInfo->Data >> unk; + sLog->outDebug(LOG_FILTER_NETWORKIO, "Character creation %s (account %u) has unhandled tail data: [%u]", createInfo->Name.c_str(), GetAccountId(), unk); + } + + Player newChar(this); + if (!newChar.Create(sObjectMgr->GenerateLowGuid(HIGHGUID_PLAYER), createInfo)) + { + // Player not create (race/class/etc problem?) + newChar.CleanupsBeforeDelete(); + + WorldPacket data(SMSG_CHAR_CREATE, 1); + data << uint8(CHAR_CREATE_ERROR); + SendPacket(&data); + delete createInfo; + _charCreateCallback.Reset(); + return; + } + + if ((haveSameRace && skipCinematics == 1) || skipCinematics == 2) + newChar.setCinematic(1); // not show intro + + newChar.SetAtLoginFlag(AT_LOGIN_FIRST); // First login + + // Player created, save it now + newChar.SaveToDB(true); + createInfo->CharCount += 1; + + SQLTransaction trans = LoginDatabase.BeginTransaction(); + + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_REALM_CHARACTERS); + stmt->setUInt32(0, GetAccountId()); + stmt->setUInt32(1, realmID); + trans->Append(stmt); + + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_REALM_CHARACTERS); + stmt->setUInt32(0, createInfo->CharCount); + stmt->setUInt32(1, GetAccountId()); + stmt->setUInt32(2, realmID); + trans->Append(stmt); + + LoginDatabase.CommitTransaction(trans); + + newChar.CleanupsBeforeDelete(); + + WorldPacket data(SMSG_CHAR_CREATE, 1); + data << uint8(CHAR_CREATE_SUCCESS); + SendPacket(&data); + + std::string IP_str = GetRemoteAddress(); + sLog->outDetail("Account: %d (IP: %s) Create Character:[%s] (GUID: %u)", GetAccountId(), IP_str.c_str(), createInfo->Name.c_str(), newChar.GetGUIDLow()); + sLog->outChar("Account: %d (IP: %s) Create Character:[%s] (GUID: %u)", GetAccountId(), IP_str.c_str(), createInfo->Name.c_str(), newChar.GetGUIDLow()); + sScriptMgr->OnPlayerCreate(&newChar); + sWorld->AddCharacterNameData(newChar.GetGUIDLow(), std::string(newChar.GetName()), newChar.getGender(), newChar.getRace(), newChar.getClass()); + + delete createInfo; + _charCreateCallback.Reset(); + } + break; + } +} + +void WorldSession::HandleCharDeleteOpcode(WorldPacket & recv_data) +{ + uint64 guid; + recv_data >> guid; + + // can't delete loaded character + if (ObjectAccessor::FindPlayer(guid)) + return; + + uint32 accountId = 0; + std::string name; + + // is guild leader + if (sGuildMgr->GetGuildByLeader(guid)) + { + WorldPacket data(SMSG_CHAR_DELETE, 1); + data << (uint8)CHAR_DELETE_FAILED_GUILD_LEADER; + SendPacket(&data); + return; + } + + // is arena team captain + if (sArenaTeamMgr->GetArenaTeamByCaptain(guid)) + { + WorldPacket data(SMSG_CHAR_DELETE, 1); + data << (uint8)CHAR_DELETE_FAILED_ARENA_CAPTAIN; + SendPacket(&data); + return; + } + + QueryResult result = CharacterDatabase.PQuery("SELECT account, name FROM characters WHERE guid='%u'", GUID_LOPART(guid)); + if (result) + { + Field* fields = result->Fetch(); + accountId = fields[0].GetUInt32(); + name = fields[1].GetString(); + } + + // prevent deleting other players' characters using cheating tools + if (accountId != GetAccountId()) + return; + + std::string IP_str = GetRemoteAddress(); + sLog->outDetail("Account: %d (IP: %s) Delete Character:[%s] (GUID: %u)", GetAccountId(), IP_str.c_str(), name.c_str(), GUID_LOPART(guid)); + sLog->outChar("Account: %d (IP: %s) Delete Character:[%s] (GUID: %u)", GetAccountId(), IP_str.c_str(), name.c_str(), GUID_LOPART(guid)); + sScriptMgr->OnPlayerDelete(guid); + sWorld->DeleteCharaceterNameData(GUID_LOPART(guid)); + + if (sLog->IsOutCharDump()) // optimize GetPlayerDump call + { + std::string dump; + if (PlayerDumpWriter().GetDump(GUID_LOPART(guid), dump)) + sLog->outCharDump(dump.c_str(), GetAccountId(), GUID_LOPART(guid), name.c_str()); + } + + Player::DeleteFromDB(guid, GetAccountId()); + + WorldPacket data(SMSG_CHAR_DELETE, 1); + data << (uint8)CHAR_DELETE_SUCCESS; + SendPacket(&data); +} + +void WorldSession::HandlePlayerLoginOpcode(WorldPacket & recv_data) +{ + if (PlayerLoading() || GetPlayer() != NULL) + { + sLog->outError("Player tryes to login again, AccountId = %d", GetAccountId()); + return; + } + + m_playerLoading = true; + uint64 playerGuid = 0; + + sLog->outStaticDebug("WORLD: Recvd Player Logon Message"); + + recv_data >> playerGuid; + + if (!CharCanLogin(GUID_LOPART(playerGuid))) + { + sLog->outError("Account (%u) can't login with that character (%u).", GetAccountId(), GUID_LOPART(playerGuid)); + KickPlayer(); + return; + } + + LoginQueryHolder *holder = new LoginQueryHolder(GetAccountId(), playerGuid); + if (!holder->Initialize()) + { + delete holder; // delete all unprocessed queries + m_playerLoading = false; + return; + } + + _charLoginCallback = CharacterDatabase.DelayQueryHolder((SQLQueryHolder*)holder); +} + +void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder) +{ + uint64 playerGuid = holder->GetGuid(); + + Player* pCurrChar = new Player(this); + // for send server info and strings (config) + ChatHandler chH = ChatHandler(pCurrChar); + + // "GetAccountId() == db stored account id" checked in LoadFromDB (prevent login not own character using cheating tools) + if (!pCurrChar->LoadFromDB(GUID_LOPART(playerGuid), holder)) + { + SetPlayer(NULL); + KickPlayer(); // disconnect client, player no set to session and it will not deleted or saved at kick + delete pCurrChar; // delete it manually + delete holder; // delete all unprocessed queries + m_playerLoading = false; + return; + } + + pCurrChar->GetMotionMaster()->Initialize(); + pCurrChar->SendDungeonDifficulty(false); + + WorldPacket data(SMSG_LOGIN_VERIFY_WORLD, 20); + data << pCurrChar->GetMapId(); + data << pCurrChar->GetPositionX(); + data << pCurrChar->GetPositionY(); + data << pCurrChar->GetPositionZ(); + data << pCurrChar->GetOrientation(); + SendPacket(&data); + + // load player specific part before send times + LoadAccountData(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOADACCOUNTDATA), PER_CHARACTER_CACHE_MASK); + SendAccountDataTimes(PER_CHARACTER_CACHE_MASK); + + data.Initialize(SMSG_FEATURE_SYSTEM_STATUS, 2); // added in 2.2.0 + data << uint8(2); // unknown value + data << uint8(0); // enable(1)/disable(0) voice chat interface in client + SendPacket(&data); + + // Send MOTD + { + data.Initialize(SMSG_MOTD, 50); // new in 2.0.1 + data << (uint32)0; + + uint32 linecount=0; + std::string str_motd = sWorld->GetMotd(); + std::string::size_type pos, nextpos; + + pos = 0; + while ((nextpos= str_motd.find('@', pos)) != std::string::npos) + { + if (nextpos != pos) + { + data << str_motd.substr(pos, nextpos-pos); + ++linecount; + } + pos = nextpos+1; + } + + if (pos<str_motd.length()) + { + data << str_motd.substr(pos); + ++linecount; + } + + data.put(0, linecount); + + SendPacket(&data); + sLog->outStaticDebug("WORLD: Sent motd (SMSG_MOTD)"); + + // send server info + if (sWorld->getIntConfig(CONFIG_ENABLE_SINFO_LOGIN) == 1) + chH.PSendSysMessage(_FULLVERSION); + + sLog->outStaticDebug("WORLD: Sent server info"); + } + + //QueryResult* result = CharacterDatabase.PQuery("SELECT guildid, rank FROM guild_member WHERE guid = '%u'", pCurrChar->GetGUIDLow()); + if (PreparedQueryResult resultGuild = holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOADGUILD)) + { + Field* fields = resultGuild->Fetch(); + pCurrChar->SetInGuild(fields[0].GetUInt32()); + pCurrChar->SetRank(fields[1].GetUInt8()); + } + else if (pCurrChar->GetGuildId()) // clear guild related fields in case wrong data about non existed membership + { + pCurrChar->SetInGuild(0); + pCurrChar->SetRank(0); + } + + if (pCurrChar->GetGuildId() != 0) + { + if (Guild* guild = sGuildMgr->GetGuildById(pCurrChar->GetGuildId())) + guild->SendLoginInfo(this); + else + { + // remove wrong guild data + sLog->outError("Player %s (GUID: %u) marked as member of not existing guild (id: %u), removing guild membership for player.", pCurrChar->GetName(), pCurrChar->GetGUIDLow(), pCurrChar->GetGuildId()); + pCurrChar->SetInGuild(0); + } + } + + data.Initialize(SMSG_LEARNED_DANCE_MOVES, 4+4); + data << uint32(0); + data << uint32(0); + SendPacket(&data); + + pCurrChar->SendInitialPacketsBeforeAddToMap(); + + //Show cinematic at the first time that player login + if (!pCurrChar->getCinematic()) + { + pCurrChar->setCinematic(1); + + if (ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(pCurrChar->getClass())) + { + if (cEntry->CinematicSequence) + pCurrChar->SendCinematicStart(cEntry->CinematicSequence); + else if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(pCurrChar->getRace())) + pCurrChar->SendCinematicStart(rEntry->CinematicSequence); + + // send new char string if not empty + if (!sWorld->GetNewCharString().empty()) + chH.PSendSysMessage("%s", sWorld->GetNewCharString().c_str()); + } + } + + if (Group* group = pCurrChar->GetGroup()) + { + if (group->isLFGGroup()) + { + LfgDungeonSet Dungeons; + Dungeons.insert(sLFGMgr->GetDungeon(group->GetGUID())); + sLFGMgr->SetSelectedDungeons(pCurrChar->GetGUID(), Dungeons); + sLFGMgr->SetState(pCurrChar->GetGUID(), sLFGMgr->GetState(group->GetGUID())); + } + } + + if (!pCurrChar->GetMap()->AddPlayerToMap(pCurrChar) || !pCurrChar->CheckInstanceLoginValid()) + { + AreaTrigger const* at = sObjectMgr->GetGoBackTrigger(pCurrChar->GetMapId()); + if (at) + pCurrChar->TeleportTo(at->target_mapId, at->target_X, at->target_Y, at->target_Z, pCurrChar->GetOrientation()); + else + pCurrChar->TeleportTo(pCurrChar->m_homebindMapId, pCurrChar->m_homebindX, pCurrChar->m_homebindY, pCurrChar->m_homebindZ, pCurrChar->GetOrientation()); + } + + sObjectAccessor->AddObject(pCurrChar); + //sLog->outDebug("Player %s added to Map.", pCurrChar->GetName()); + + pCurrChar->SendInitialPacketsAfterAddToMap(); + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_ONLINE); + + stmt->setUInt32(0, pCurrChar->GetGUIDLow()); + + CharacterDatabase.Execute(stmt); + + stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_ONLINE); + + stmt->setUInt32(0, GetAccountId()); + + LoginDatabase.Execute(stmt); + + pCurrChar->SetInGameTime(getMSTime()); + + // announce group about member online (must be after add to player list to receive announce to self) + if (Group* group = pCurrChar->GetGroup()) + { + //pCurrChar->groupInfo.group->SendInit(this); // useless + group->SendUpdate(); + group->ResetMaxEnchantingLevel(); + } + + // friend status + sSocialMgr->SendFriendStatus(pCurrChar, FRIEND_ONLINE, pCurrChar->GetGUIDLow(), true); + + // Place character in world (and load zone) before some object loading + pCurrChar->LoadCorpse(); + + // setting Ghost+speed if dead + if (pCurrChar->m_deathState != ALIVE) + { + // not blizz like, we must correctly save and load player instead... + if (pCurrChar->getRace() == RACE_NIGHTELF) + pCurrChar->CastSpell(pCurrChar, 20584, true, 0);// auras SPELL_AURA_INCREASE_SPEED(+speed in wisp form), SPELL_AURA_INCREASE_SWIM_SPEED(+swim speed in wisp form), SPELL_AURA_TRANSFORM (to wisp form) + pCurrChar->CastSpell(pCurrChar, 8326, true, 0); // auras SPELL_AURA_GHOST, SPELL_AURA_INCREASE_SPEED(why?), SPELL_AURA_INCREASE_SWIM_SPEED(why?) + + pCurrChar->SetMovement(MOVE_WATER_WALK); + } + + pCurrChar->ContinueTaxiFlight(); + + // reset for all pets before pet loading + if (pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS)) + Pet::resetTalentsForAllPetsOf(pCurrChar); + + // Load pet if any (if player not alive and in taxi flight or another then pet will remember as temporary unsummoned) + pCurrChar->LoadPet(); + + // Set FFA PvP for non GM in non-rest mode + if (sWorld->IsFFAPvPRealm() && !pCurrChar->isGameMaster() && !pCurrChar->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING)) + pCurrChar->SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP); + + if (pCurrChar->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_CONTESTED_PVP)) + pCurrChar->SetContestedPvP(); + + // Apply at_login requests + if (pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_SPELLS)) + { + pCurrChar->resetSpells(); + SendNotification(LANG_RESET_SPELLS); + } + + if (pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_TALENTS)) + { + pCurrChar->resetTalents(true); + pCurrChar->SendTalentsInfoData(false); // original talents send already in to SendInitialPacketsBeforeAddToMap, resend reset state + SendNotification(LANG_RESET_TALENTS); + } + + if (pCurrChar->HasAtLoginFlag(AT_LOGIN_FIRST)) + pCurrChar->RemoveAtLoginFlag(AT_LOGIN_FIRST); + + // show time before shutdown if shutdown planned. + if (sWorld->IsShuttingDown()) + sWorld->ShutdownMsg(true, pCurrChar); + + if (sWorld->getBoolConfig(CONFIG_ALL_TAXI_PATHS)) + pCurrChar->SetTaxiCheater(true); + + if (pCurrChar->isGameMaster()) + SendNotification(LANG_GM_ON); + + std::string IP_str = GetRemoteAddress(); + sLog->outChar("Account: %d (IP: %s) Login Character:[%s] (GUID: %u)", + GetAccountId(), IP_str.c_str(), pCurrChar->GetName(), pCurrChar->GetGUIDLow()); + + if (!pCurrChar->IsStandState() && !pCurrChar->HasUnitState(UNIT_STAT_STUNNED)) + pCurrChar->SetStandState(UNIT_STAND_STATE_STAND); + + m_playerLoading = false; + + sScriptMgr->OnPlayerLogin(pCurrChar); + delete holder; +} + +void WorldSession::HandleSetFactionAtWar(WorldPacket & recv_data) +{ + sLog->outStaticDebug("WORLD: Received CMSG_SET_FACTION_ATWAR"); + + uint32 repListID; + uint8 flag; + + recv_data >> repListID; + recv_data >> flag; + + GetPlayer()->GetReputationMgr().SetAtWar(repListID, flag); +} + +//I think this function is never used :/ I dunno, but i guess this opcode not exists +void WorldSession::HandleSetFactionCheat(WorldPacket & /*recv_data*/) +{ + sLog->outError("WORLD SESSION: HandleSetFactionCheat, not expected call, please report."); + GetPlayer()->GetReputationMgr().SendStates(); +} + +void WorldSession::HandleTutorialFlag(WorldPacket & recv_data) +{ + uint32 data; + recv_data >> data; + + uint8 index = uint8(data / 32); + if (index >= MAX_ACCOUNT_TUTORIAL_VALUES) + return; + + uint32 value = (data % 32); + + uint32 flag = GetTutorialInt(index); + flag |= (1 << value); + SetTutorialInt(index, flag); +} + +void WorldSession::HandleTutorialClear(WorldPacket & /*recv_data*/) +{ + for (uint8 i = 0; i < MAX_ACCOUNT_TUTORIAL_VALUES; ++i) + SetTutorialInt(i, 0xFFFFFFFF); +} + +void WorldSession::HandleTutorialReset(WorldPacket & /*recv_data*/) +{ + for (uint8 i = 0; i < MAX_ACCOUNT_TUTORIAL_VALUES; ++i) + SetTutorialInt(i, 0x00000000); +} + +void WorldSession::HandleSetWatchedFactionOpcode(WorldPacket & recv_data) +{ + sLog->outStaticDebug("WORLD: Received CMSG_SET_WATCHED_FACTION"); + uint32 fact; + recv_data >> fact; + GetPlayer()->SetUInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, fact); +} + +void WorldSession::HandleSetFactionInactiveOpcode(WorldPacket & recv_data) +{ + sLog->outStaticDebug("WORLD: Received CMSG_SET_FACTION_INACTIVE"); + uint32 replistid; + uint8 inactive; + recv_data >> replistid >> inactive; + + _player->GetReputationMgr().SetInactive(replistid, inactive); +} + +void WorldSession::HandleShowingHelmOpcode(WorldPacket& recv_data) +{ + sLog->outStaticDebug("CMSG_SHOWING_HELM for %s", _player->GetName()); + recv_data.read_skip<uint8>(); // unknown, bool? + _player->ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_HELM); +} + +void WorldSession::HandleShowingCloakOpcode(WorldPacket& recv_data) +{ + sLog->outStaticDebug("CMSG_SHOWING_CLOAK for %s", _player->GetName()); + recv_data.read_skip<uint8>(); // unknown, bool? + _player->ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_CLOAK); +} + +void WorldSession::HandleCharRenameOpcode(WorldPacket& recv_data) +{ + uint64 guid; + std::string newName; + + recv_data >> guid; + recv_data >> newName; + + // prevent character rename to invalid name + if (!normalizePlayerName(newName)) + { + WorldPacket data(SMSG_CHAR_RENAME, 1); + data << uint8(CHAR_NAME_NO_NAME); + SendPacket(&data); + return; + } + + uint8 res = ObjectMgr::CheckPlayerName(newName, true); + if (res != CHAR_NAME_SUCCESS) + { + WorldPacket data(SMSG_CHAR_RENAME, 1); + data << uint8(res); + SendPacket(&data); + return; + } + + // check name limitations + if (AccountMgr::IsPlayerAccount(GetSecurity()) && sObjectMgr->IsReservedName(newName)) + { + WorldPacket data(SMSG_CHAR_RENAME, 1); + data << uint8(CHAR_NAME_RESERVED); + SendPacket(&data); + return; + } + + // Ensure that the character belongs to the current account, that rename at login is enabled + // and that there is no character with the desired new name + _charRenameCallback.SetParam(newName); + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_FREE_NAME); + + stmt->setUInt32(0, GUID_LOPART(guid)); + stmt->setUInt32(1, GetAccountId()); + stmt->setUInt16(2, AT_LOGIN_RENAME); + stmt->setUInt16(3, AT_LOGIN_RENAME); + stmt->setString(4, newName); + + _charRenameCallback.SetFutureResult(CharacterDatabase.AsyncQuery(stmt)); +} + +void WorldSession::HandleChangePlayerNameOpcodeCallBack(PreparedQueryResult result, std::string newName) +{ + if (!result) + { + WorldPacket data(SMSG_CHAR_RENAME, 1); + data << uint8(CHAR_CREATE_ERROR); + SendPacket(&data); + return; + } + + Field* fields = result->Fetch(); + + uint32 guidLow = fields[0].GetUInt32(); + std::string oldName = fields[1].GetString(); + + uint64 guid = MAKE_NEW_GUID(guidLow, 0, HIGHGUID_PLAYER); + + // Update name and at_login flag in the db + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NAME); + + stmt->setString(0, newName); + stmt->setUInt16(1, AT_LOGIN_RENAME); + stmt->setUInt32(2, guidLow); + + CharacterDatabase.Execute(stmt); + + // Removed declined name from db + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_DECLINED_NAME); + + stmt->setUInt32(0, guidLow); + + CharacterDatabase.Execute(stmt); + + sLog->outChar("Account: %d (IP: %s) Character:[%s] (guid:%u) Changed name to: %s", GetAccountId(), GetRemoteAddress().c_str(), oldName.c_str(), guidLow, newName.c_str()); + + WorldPacket data(SMSG_CHAR_RENAME, 1+8+(newName.size()+1)); + data << uint8(RESPONSE_SUCCESS); + data << uint64(guid); + data << newName; + SendPacket(&data); + + sWorld->UpdateCharacterNameData(guidLow, newName); +} + +void WorldSession::HandleSetPlayerDeclinedNames(WorldPacket& recv_data) +{ + uint64 guid; + + recv_data >> guid; + + // not accept declined names for unsupported languages + std::string name; + if (!sObjectMgr->GetPlayerNameByGUID(guid, name)) + { + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT, 4+8); + data << uint32(1); + data << uint64(guid); + SendPacket(&data); + return; + } + + std::wstring wname; + if (!Utf8toWStr(name, wname)) + { + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT, 4+8); + data << uint32(1); + data << uint64(guid); + SendPacket(&data); + return; + } + + if (!isCyrillicCharacter(wname[0])) // name already stored as only single alphabet using + { + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT, 4+8); + data << uint32(1); + data << uint64(guid); + SendPacket(&data); + return; + } + + std::string name2; + DeclinedName declinedname; + + recv_data >> name2; + + if (name2 != name) // character have different name + { + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT, 4+8); + data << uint32(1); + data << uint64(guid); + SendPacket(&data); + return; + } + + for (int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) + { + recv_data >> declinedname.name[i]; + if (!normalizePlayerName(declinedname.name[i])) + { + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT, 4+8); + data << uint32(1); + data << uint64(guid); + SendPacket(&data); + return; + } + } + + if (!ObjectMgr::CheckDeclinedNames(wname, declinedname)) + { + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT, 4+8); + data << uint32(1); + data << uint64(guid); + SendPacket(&data); + return; + } + + for (int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) + CharacterDatabase.EscapeString(declinedname.name[i]); + + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + trans->PAppend("DELETE FROM character_declinedname WHERE guid = '%u'", GUID_LOPART(guid)); + trans->PAppend("INSERT INTO character_declinedname (guid, genitive, dative, accusative, instrumental, prepositional) VALUES ('%u', '%s', '%s', '%s', '%s', '%s')", + GUID_LOPART(guid), declinedname.name[0].c_str(), declinedname.name[1].c_str(), declinedname.name[2].c_str(), declinedname.name[3].c_str(), declinedname.name[4].c_str()); + CharacterDatabase.CommitTransaction(trans); + + WorldPacket data(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT, 4+8); + data << uint32(0); // OK + data << uint64(guid); + SendPacket(&data); +} + +void WorldSession::HandleAlterAppearance(WorldPacket & recv_data) +{ + sLog->outDebug(LOG_FILTER_NETWORKIO, "CMSG_ALTER_APPEARANCE"); + + uint32 Hair, Color, FacialHair, SkinColor; + recv_data >> Hair >> Color >> FacialHair >> SkinColor; + + BarberShopStyleEntry const* bs_hair = sBarberShopStyleStore.LookupEntry(Hair); + + if (!bs_hair || bs_hair->type != 0 || bs_hair->race != _player->getRace() || bs_hair->gender != _player->getGender()) + return; + + BarberShopStyleEntry const* bs_facialHair = sBarberShopStyleStore.LookupEntry(FacialHair); + + if (!bs_facialHair || bs_facialHair->type != 2 || bs_facialHair->race != _player->getRace() || bs_facialHair->gender != _player->getGender()) + return; + + BarberShopStyleEntry const* bs_skinColor = sBarberShopStyleStore.LookupEntry(SkinColor); + + if (bs_skinColor && (bs_skinColor->type != 3 || bs_skinColor->race != _player->getRace() || bs_skinColor->gender != _player->getGender())) + return; + + uint32 Cost = _player->GetBarberShopCost(bs_hair->hair_id, Color, bs_facialHair->hair_id, bs_skinColor); + + // 0 - ok + // 1, 3 - not enough money + // 2 - you have to seat on barber chair + if (!_player->HasEnoughMoney(Cost)) + { + WorldPacket data(SMSG_BARBER_SHOP_RESULT, 4); + data << uint32(1); // no money + SendPacket(&data); + return; + } + else + { + WorldPacket data(SMSG_BARBER_SHOP_RESULT, 4); + data << uint32(0); // ok + SendPacket(&data); + } + + _player->ModifyMoney(-int32(Cost)); // it isn't free + _player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_AT_BARBER, Cost); + + _player->SetByteValue(PLAYER_BYTES, 2, uint8(bs_hair->hair_id)); + _player->SetByteValue(PLAYER_BYTES, 3, uint8(Color)); + _player->SetByteValue(PLAYER_BYTES_2, 0, uint8(bs_facialHair->hair_id)); + if (bs_skinColor) + _player->SetByteValue(PLAYER_BYTES, 0, uint8(bs_skinColor->hair_id)); + + _player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_VISIT_BARBER_SHOP, 1); + + _player->SetStandState(0); // stand up +} + +void WorldSession::HandleRemoveGlyph(WorldPacket & recv_data) +{ + uint32 slot; + recv_data >> slot; + + if (slot >= MAX_GLYPH_SLOT_INDEX) + { + sLog->outDebug(LOG_FILTER_NETWORKIO, "Client sent wrong glyph slot number in opcode CMSG_REMOVE_GLYPH %u", slot); + return; + } + + if (uint32 glyph = _player->GetGlyph(slot)) + { + if (GlyphPropertiesEntry const* gp = sGlyphPropertiesStore.LookupEntry(glyph)) + { + _player->RemoveAurasDueToSpell(gp->SpellId); + _player->SetGlyph(slot, 0); + _player->SendTalentsInfoData(false); + } + } +} + +void WorldSession::HandleCharCustomize(WorldPacket& recv_data) +{ + uint64 guid; + std::string newName; + + recv_data >> guid; + recv_data >> newName; + + uint8 gender, skin, face, hairStyle, hairColor, facialHair; + recv_data >> gender >> skin >> hairColor >> hairStyle >> facialHair >> face; + + QueryResult result = CharacterDatabase.PQuery("SELECT at_login FROM characters WHERE guid ='%u'", GUID_LOPART(guid)); + if (!result) + { + WorldPacket data(SMSG_CHAR_CUSTOMIZE, 1); + data << uint8(CHAR_CREATE_ERROR); + SendPacket(&data); + return; + } + + Field* fields = result->Fetch(); + uint32 at_loginFlags = fields[0].GetUInt16(); + + if (!(at_loginFlags & AT_LOGIN_CUSTOMIZE)) + { + WorldPacket data(SMSG_CHAR_CUSTOMIZE, 1); + data << uint8(CHAR_CREATE_ERROR); + SendPacket(&data); + return; + } + + // prevent character rename to invalid name + if (!normalizePlayerName(newName)) + { + WorldPacket data(SMSG_CHAR_CUSTOMIZE, 1); + data << uint8(CHAR_NAME_NO_NAME); + SendPacket(&data); + return; + } + + uint8 res = ObjectMgr::CheckPlayerName(newName, true); + if (res != CHAR_NAME_SUCCESS) + { + WorldPacket data(SMSG_CHAR_CUSTOMIZE, 1); + data << uint8(res); + SendPacket(&data); + return; + } + + // check name limitations + if (AccountMgr::IsPlayerAccount(GetSecurity()) && sObjectMgr->IsReservedName(newName)) + { + WorldPacket data(SMSG_CHAR_CUSTOMIZE, 1); + data << uint8(CHAR_NAME_RESERVED); + SendPacket(&data); + return; + } + + // character with this name already exist + if (uint64 newguid = sObjectMgr->GetPlayerGUIDByName(newName)) + { + if (newguid != guid) + { + WorldPacket data(SMSG_CHAR_CUSTOMIZE, 1); + data << uint8(CHAR_CREATE_NAME_IN_USE); + SendPacket(&data); + return; + } + } + + if (QueryResult oldNameResult = CharacterDatabase.PQuery("SELECT name FROM characters WHERE guid ='%u'", GUID_LOPART(guid))) + { + std::string oldname = oldNameResult->Fetch()[0].GetString(); + std::string IP_str = GetRemoteAddress(); + sLog->outChar("Account: %d (IP: %s), Character[%s] (guid:%u) Customized to: %s", GetAccountId(), IP_str.c_str(), oldname.c_str(), GUID_LOPART(guid), newName.c_str()); + } + Player::Customize(guid, gender, skin, face, hairStyle, hairColor, facialHair); + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_NAME_AT_LOGIN); + + stmt->setString(0, newName); + stmt->setUInt16(1, uint16(AT_LOGIN_CUSTOMIZE)); + stmt->setUInt32(2, GUID_LOPART(guid)); + + CharacterDatabase.Execute(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_DECLINED_NAME); + + stmt->setUInt32(0, GUID_LOPART(guid)); + + CharacterDatabase.Execute(stmt); + + sWorld->UpdateCharacterNameData(GUID_LOPART(guid), newName, gender); + + WorldPacket data(SMSG_CHAR_CUSTOMIZE, 1+8+(newName.size()+1)+6); + data << uint8(RESPONSE_SUCCESS); + data << uint64(guid); + data << newName; + data << uint8(gender); + data << uint8(skin); + data << uint8(face); + data << uint8(hairStyle); + data << uint8(hairColor); + data << uint8(facialHair); + SendPacket(&data); +} + +void WorldSession::HandleEquipmentSetSave(WorldPacket &recv_data) +{ + sLog->outDebug(LOG_FILTER_NETWORKIO, "CMSG_EQUIPMENT_SET_SAVE"); + + uint64 setGuid; + recv_data.readPackGUID(setGuid); + + uint32 index; + recv_data >> index; + if (index >= MAX_EQUIPMENT_SET_INDEX) // client set slots amount + return; + + std::string name; + recv_data >> name; + + std::string iconName; + recv_data >> iconName; + + EquipmentSet eqSet; + + eqSet.Guid = setGuid; + eqSet.Name = name; + eqSet.IconName = iconName; + eqSet.state = EQUIPMENT_SET_NEW; + + for (uint32 i = 0; i < EQUIPMENT_SLOT_END; ++i) + { + uint64 itemGuid; + recv_data.readPackGUID(itemGuid); + + Item* item = _player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + + if (!item && itemGuid) // cheating check 1 + return; + + if (item && item->GetGUID() != itemGuid) // cheating check 2 + return; + + eqSet.Items[i] = GUID_LOPART(itemGuid); + } + + _player->SetEquipmentSet(index, eqSet); +} + +void WorldSession::HandleEquipmentSetDelete(WorldPacket &recv_data) +{ + sLog->outDebug(LOG_FILTER_NETWORKIO, "CMSG_EQUIPMENT_SET_DELETE"); + + uint64 setGuid; + recv_data.readPackGUID(setGuid); + + _player->DeleteEquipmentSet(setGuid); +} + +void WorldSession::HandleEquipmentSetUse(WorldPacket &recv_data) +{ + if (_player->isInCombat()) + return; + + sLog->outDebug(LOG_FILTER_NETWORKIO, "CMSG_EQUIPMENT_SET_USE"); + + for (uint32 i = 0; i < EQUIPMENT_SLOT_END; ++i) + { + uint64 itemGuid; + recv_data.readPackGUID(itemGuid); + + uint8 srcbag, srcslot; + recv_data >> srcbag >> srcslot; + + sLog->outDebug(LOG_FILTER_PLAYER_ITEMS, "Item " UI64FMTD ": srcbag %u, srcslot %u", itemGuid, srcbag, srcslot); + + Item* item = _player->GetItemByGuid(itemGuid); + + uint16 dstpos = i | (INVENTORY_SLOT_BAG_0 << 8); + + if (!item) + { + Item* uItem = _player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (!uItem) + continue; + + ItemPosCountVec sDest; + InventoryResult msg = _player->CanStoreItem(NULL_BAG, NULL_SLOT, sDest, uItem, false); + if (msg == EQUIP_ERR_OK) + { + _player->RemoveItem(INVENTORY_SLOT_BAG_0, i, true); + _player->StoreItem(sDest, uItem, true); + } + else + _player->SendEquipError(msg, uItem, NULL); + + continue; + } + + if (item->GetPos() == dstpos) + continue; + + _player->SwapItem(item->GetPos(), dstpos); + } + + WorldPacket data(SMSG_EQUIPMENT_SET_USE_RESULT, 1); + data << uint8(0); // 4 - equipment swap failed - inventory is full + SendPacket(&data); +} + +void WorldSession::HandleCharFactionOrRaceChange(WorldPacket& recv_data) +{ + // TODO: Move queries to prepared statements + uint64 guid; + std::string newname; + uint8 gender, skin, face, hairStyle, hairColor, facialHair, race; + recv_data >> guid; + recv_data >> newname; + recv_data >> gender >> skin >> hairColor >> hairStyle >> facialHair >> face >> race; + + uint32 lowGuid = GUID_LOPART(guid); + QueryResult result = CharacterDatabase.PQuery("SELECT class, level, at_login FROM characters WHERE guid ='%u'", lowGuid); + if (!result) + { + WorldPacket data(SMSG_CHAR_FACTION_CHANGE, 1); + data << uint8(CHAR_CREATE_ERROR); + SendPacket(&data); + return; + } + + Field* fields = result->Fetch(); + uint32 playerClass = fields[0].GetUInt32(); + uint32 level = fields[1].GetUInt32(); + uint32 at_loginFlags = fields[2].GetUInt16(); + uint32 used_loginFlag = ((recv_data.GetOpcode() == CMSG_CHAR_RACE_CHANGE) ? AT_LOGIN_CHANGE_RACE : AT_LOGIN_CHANGE_FACTION); + + if (!sObjectMgr->GetPlayerInfo(race, playerClass)) + { + WorldPacket data(SMSG_CHAR_FACTION_CHANGE, 1); + data << uint8(CHAR_CREATE_ERROR); + SendPacket(&data); + return; + } + + if (!(at_loginFlags & used_loginFlag)) + { + WorldPacket data(SMSG_CHAR_FACTION_CHANGE, 1); + data << uint8(CHAR_CREATE_ERROR); + SendPacket(&data); + return; + } + + if (AccountMgr::IsPlayerAccount(GetSecurity())) + { + uint32 raceMaskDisabled = sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_DISABLED_RACEMASK); + if ((1 << (race - 1)) & raceMaskDisabled) + { + WorldPacket data(SMSG_CHAR_FACTION_CHANGE, 1); + data << uint8(CHAR_CREATE_ERROR); + SendPacket(&data); + return; + } + } + + // prevent character rename to invalid name + if (!normalizePlayerName(newname)) + { + WorldPacket data(SMSG_CHAR_FACTION_CHANGE, 1); + data << uint8(CHAR_NAME_NO_NAME); + SendPacket(&data); + return; + } + + uint8 res = ObjectMgr::CheckPlayerName(newname, true); + if (res != CHAR_NAME_SUCCESS) + { + WorldPacket data(SMSG_CHAR_FACTION_CHANGE, 1); + data << uint8(res); + SendPacket(&data); + return; + } + + // check name limitations + if (AccountMgr::IsPlayerAccount(GetSecurity()) && sObjectMgr->IsReservedName(newname)) + { + WorldPacket data(SMSG_CHAR_FACTION_CHANGE, 1); + data << uint8(CHAR_NAME_RESERVED); + SendPacket (&data); + return; + } + + // character with this name already exist + if (uint64 newguid = sObjectMgr->GetPlayerGUIDByName(newname)) + { + if (newguid != guid) + { + WorldPacket data(SMSG_CHAR_FACTION_CHANGE, 1); + data << uint8(CHAR_CREATE_NAME_IN_USE); + SendPacket(&data); + return; + } + } + + CharacterDatabase.EscapeString(newname); + Player::Customize(guid, gender, skin, face, hairStyle, hairColor, facialHair); + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + trans->PAppend("UPDATE `characters` SET name='%s', race='%u', at_login=at_login & ~ %u WHERE guid='%u'", newname.c_str(), race, used_loginFlag, lowGuid); + trans->PAppend("DELETE FROM character_declinedname WHERE guid ='%u'", lowGuid); + sWorld->UpdateCharacterNameData(GUID_LOPART(guid), newname, gender, race); + + BattlegroundTeamId team = BG_TEAM_ALLIANCE; + + // Search each faction is targeted + switch (race) + { + case RACE_ORC: + case RACE_TAUREN: + case RACE_UNDEAD_PLAYER: + case RACE_TROLL: + case RACE_BLOODELF: + team = BG_TEAM_HORDE; + break; + default: + break; + } + + // Switch Languages + // delete all languages first + trans->PAppend("DELETE FROM `character_skills` WHERE `skill` IN (98, 113, 759, 111, 313, 109, 115, 315, 673, 137) AND `guid`='%u'", lowGuid); + + // now add them back + if (team == BG_TEAM_ALLIANCE) + { + trans->PAppend("INSERT INTO `character_skills` (guid, skill, value, max) VALUES (%u, 98, 300, 300)", lowGuid); + switch (race) + { + case RACE_DWARF: + trans->PAppend("INSERT INTO `character_skills` (guid, skill, value, max) VALUES (%u, 111, 300, 300)", lowGuid); + break; + case RACE_DRAENEI: + trans->PAppend("INSERT INTO `character_skills` (guid, skill, value, max) VALUES (%u, 759, 300, 300)", lowGuid); + break; + case RACE_GNOME: + trans->PAppend("INSERT INTO `character_skills` (guid, skill, value, max) VALUES (%u, 313, 300, 300)", lowGuid); + break; + case RACE_NIGHTELF: + trans->PAppend("INSERT INTO `character_skills` (guid, skill, value, max) VALUES (%u, 113, 300, 300)", lowGuid); + break; + } + } + else if (team == BG_TEAM_HORDE) + { + trans->PAppend("INSERT INTO `character_skills` (guid, skill, value, max) VALUES (%u, 109, 300, 300)", lowGuid); + switch (race) + { + case RACE_UNDEAD_PLAYER: + trans->PAppend("INSERT INTO `character_skills` (guid, skill, value, max) VALUES (%u, 673, 300, 300)", lowGuid); + break; + case RACE_TAUREN: + trans->PAppend("INSERT INTO `character_skills` (guid, skill, value, max) VALUES (%u, 115, 300, 300)", lowGuid); + break; + case RACE_TROLL: + trans->PAppend("INSERT INTO `character_skills` (guid, skill, value, max) VALUES (%u, 315, 300, 300)", lowGuid); + break; + case RACE_BLOODELF: + trans->PAppend("INSERT INTO `character_skills` (guid, skill, value, max) VALUES (%u, 137, 300, 300)", lowGuid); + break; + } + } + + if (recv_data.GetOpcode() == CMSG_CHAR_FACTION_CHANGE) + { + // Delete all Flypaths + trans->PAppend("UPDATE `characters` SET taxi_path = '' WHERE guid ='%u'", lowGuid); + + if (level > 7) + { + // Update Taxi path + // this doesn't seem to be 100% blizzlike... but it can't really be helped. + std::ostringstream taximaskstream; + uint32 numFullTaximasks = level / 7; + if (numFullTaximasks > 11) + numFullTaximasks = 11; + if (team == BG_TEAM_ALLIANCE) + { + if (playerClass != CLASS_DEATH_KNIGHT) + { + for (uint8 i = 0; i < numFullTaximasks; ++i) + taximaskstream << uint32(sAllianceTaxiNodesMask[i]) << ' '; + } + else + { + for (uint8 i = 0; i < numFullTaximasks; ++i) + taximaskstream << uint32(sAllianceTaxiNodesMask[i] | sDeathKnightTaxiNodesMask[i]) << ' '; + } + } + else + { + if (playerClass != CLASS_DEATH_KNIGHT) + { + for (uint8 i = 0; i < numFullTaximasks; ++i) + taximaskstream << uint32(sHordeTaxiNodesMask[i]) << ' '; + } + else + { + for (uint8 i = 0; i < numFullTaximasks; ++i) + taximaskstream << uint32(sHordeTaxiNodesMask[i] | sDeathKnightTaxiNodesMask[i]) << ' '; + } + } + + uint32 numEmptyTaximasks = 11 - numFullTaximasks; + for (uint8 i = 0; i < numEmptyTaximasks; ++i) + taximaskstream << "0 "; + taximaskstream << '0'; + std::string taximask = taximaskstream.str(); + trans->PAppend("UPDATE `characters` SET `taximask`= '%s' WHERE `guid` = '%u'", taximask.c_str(), lowGuid); + } + + // Delete all current quests + trans->PAppend("DELETE FROM `character_queststatus` WHERE guid ='%u'", GUID_LOPART(guid)); + + // Delete record of the faction old completed quests + { + std::ostringstream quests; + ObjectMgr::QuestMap const& qTemplates = sObjectMgr->GetQuestTemplates(); + for (ObjectMgr::QuestMap::const_iterator iter = qTemplates.begin(); iter != qTemplates.end(); ++iter) + { + Quest *qinfo = iter->second; + uint32 requiredRaces = qinfo->GetRequiredRaces(); + if (team == BG_TEAM_ALLIANCE) + { + if (requiredRaces & RACEMASK_ALLIANCE) + { + quests << uint32(qinfo->GetQuestId()); + quests << ','; + } + } + else // if (team == BG_TEAM_HORDE) + { + if (requiredRaces & RACEMASK_HORDE) + { + quests << uint32(qinfo->GetQuestId()); + quests << ','; + } + } + } + + std::string questsStr = quests.str(); + questsStr = questsStr.substr(0, questsStr.length() - 1); + + if (!questsStr.empty()) + trans->PAppend("DELETE FROM `character_queststatus_rewarded` WHERE guid='%u' AND quest IN (%s)", lowGuid, questsStr.c_str()); + } + + if (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD)) + { + // Reset guild + if (QueryResult result = CharacterDatabase.PQuery("SELECT guildid FROM `guild_member` WHERE guid ='%u'", lowGuid)) + if (Guild* guild = sGuildMgr->GetGuildById((result->Fetch()[0]).GetUInt32())) + guild->DeleteMember(MAKE_NEW_GUID(lowGuid, 0, HIGHGUID_PLAYER)); + } + + if (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_ADD_FRIEND)) + { + // Delete Friend List + trans->PAppend("DELETE FROM `character_social` WHERE `guid`= '%u'", lowGuid); + trans->PAppend("DELETE FROM `character_social` WHERE `friend`= '%u'", lowGuid); + } + + // Leave Arena Teams + Player::LeaveAllArenaTeams(guid); + + // Reset homebind and position + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_HOMEBIND); + stmt->setUInt32(0, lowGuid); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PLAYER_HOMEBIND); + stmt->setUInt32(0, lowGuid); + if (team == BG_TEAM_ALLIANCE) + { + stmt->setUInt16(1, 0); + stmt->setUInt16(2, 1519); + stmt->setFloat (3, -8867.68f); + stmt->setFloat (4, 673.373f); + stmt->setFloat (5, 97.9034f); + Player::SavePositionInDB(0, -8867.68f, 673.373f, 97.9034f, 0.0f, 1519, lowGuid); + } + else + { + stmt->setUInt16(1, 1); + stmt->setUInt16(2, 1637); + stmt->setFloat (3, 1633.33f); + stmt->setFloat (4, -4439.11f); + stmt->setFloat (5, 15.7588f); + Player::SavePositionInDB(1, 1633.33f, -4439.11f, 15.7588f, 0.0f, 1637, lowGuid); + } + trans->Append(stmt); + + // Achievement conversion + for (std::map<uint32, uint32>::const_iterator it = sObjectMgr->factionchange_achievements.begin(); it != sObjectMgr->factionchange_achievements.end(); ++it) + { + uint32 achiev_alliance = it->first; + uint32 achiev_horde = it->second; + trans->PAppend("DELETE FROM `character_achievement` WHERE `achievement`=%u AND `guid`=%u", + team == BG_TEAM_ALLIANCE ? achiev_alliance : achiev_horde, lowGuid); + trans->PAppend("UPDATE `character_achievement` SET achievement = '%u' where achievement = '%u' AND guid = '%u'", + team == BG_TEAM_ALLIANCE ? achiev_alliance : achiev_horde, team == BG_TEAM_ALLIANCE ? achiev_horde : achiev_alliance, lowGuid); + } + + // Item conversion + for (std::map<uint32, uint32>::const_iterator it = sObjectMgr->factionchange_items.begin(); it != sObjectMgr->factionchange_items.end(); ++it) + { + uint32 item_alliance = it->first; + uint32 item_horde = it->second; + trans->PAppend("UPDATE `item_instance` ii, `character_inventory` ci SET ii.itemEntry = '%u' WHERE ii.itemEntry = '%u' AND ci.guid = '%u' AND ci.item = ii.guid", + team == BG_TEAM_ALLIANCE ? item_alliance : item_horde, team == BG_TEAM_ALLIANCE ? item_horde : item_alliance, guid); + } + + // Spell conversion + for (std::map<uint32, uint32>::const_iterator it = sObjectMgr->factionchange_spells.begin(); it != sObjectMgr->factionchange_spells.end(); ++it) + { + uint32 spell_alliance = it->first; + uint32 spell_horde = it->second; + trans->PAppend("DELETE FROM `character_spell` WHERE `spell`=%u AND `guid`=%u", + team == BG_TEAM_ALLIANCE ? spell_alliance : spell_horde, lowGuid); + trans->PAppend("UPDATE `character_spell` SET spell = '%u' where spell = '%u' AND guid = '%u'", + team == BG_TEAM_ALLIANCE ? spell_alliance : spell_horde, team == BG_TEAM_ALLIANCE ? spell_horde : spell_alliance, lowGuid); + } + + // Reputation conversion + for (std::map<uint32, uint32>::const_iterator it = sObjectMgr->factionchange_reputations.begin(); it != sObjectMgr->factionchange_reputations.end(); ++it) + { + uint32 reputation_alliance = it->first; + uint32 reputation_horde = it->second; + trans->PAppend("DELETE FROM character_reputation WHERE faction = '%u' AND guid = '%u'", + team == BG_TEAM_ALLIANCE ? reputation_alliance : reputation_horde, lowGuid); + trans->PAppend("UPDATE `character_reputation` SET faction = '%u' where faction = '%u' AND guid = '%u'", + team == BG_TEAM_ALLIANCE ? reputation_alliance : reputation_horde, team == BG_TEAM_ALLIANCE ? reputation_horde : reputation_alliance, lowGuid); + } + } + + CharacterDatabase.CommitTransaction(trans); + + std::string IP_str = GetRemoteAddress(); + sLog->outDebug(LOG_FILTER_UNITS, "Account: %d (IP: %s), Character guid: %u Change Race/Faction to: %s", GetAccountId(), IP_str.c_str(), lowGuid, newname.c_str()); + + WorldPacket data(SMSG_CHAR_FACTION_CHANGE, 1 + 8 + (newname.size() + 1) + 1 + 1 + 1 + 1 + 1 + 1 + 1); + data << uint8(RESPONSE_SUCCESS); + data << uint64(guid); + data << newname; + data << uint8(gender); + data << uint8(skin); + data << uint8(face); + data << uint8(hairStyle); + data << uint8(hairColor); + data << uint8(facialHair); + data << uint8(race); + SendPacket(&data); +} |