/*
 * Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
 * 
 * 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU 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/>.
 */

using Framework.Collections;
using Framework.Constants;
using Framework.Database;
using Game.DataStorage;
using Game.Entities;
using Game.Groups;
using Game.Guilds;
using Game.Maps;
using Game.Network;
using Game.Network.Packets;
using System;
using System.Collections.Generic;

namespace Game
{
    public partial class WorldSession
    {
        [WorldPacketHandler(ClientOpcodes.EnumCharacters, Status = SessionStatus.Authed)]
        void HandleCharEnum(EnumCharacters charEnum)
        {
            // remove expired bans
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EXPIRED_BANS);
            DB.Characters.Execute(stmt);

            // get all the data necessary for loading all characters (along with their pets) on the account
            if (WorldConfig.GetBoolValue(WorldCfg.DeclinedNamesUsed))
                stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ENUM_DECLINED_NAME);
            else
                stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ENUM);

            stmt.AddValue(0, PetSaveMode.AsCurrent);
            stmt.AddValue(1, GetAccountId());

            _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(HandleCharEnumCallback));
        }

        void HandleCharEnumCallback(SQLResult result)
        {
            byte demonHunterCount = 0; // We use this counter to allow multiple demon hunter creations when allowed in config
            bool canAlwaysCreateDemonHunter = HasPermission(RBACPermissions.SkipCheckCharacterCreationDemonHunter);
            if (WorldConfig.GetIntValue(WorldCfg.CharacterCreatingMinLevelForDemonHunter) == 0) // char level = 0 means this check is disabled, so always true
                canAlwaysCreateDemonHunter = true;

            EnumCharactersResult charResult = new EnumCharactersResult();
            charResult.Success = true;
            charResult.IsDeletedCharacters = false;
            charResult.DisabledClassesMask.Set(WorldConfig.GetUIntValue(WorldCfg.CharacterCreatingDisabledClassmask));

            _legitCharacters.Clear();
            if (!result.IsEmpty())
            {
                do
                {
                    EnumCharactersResult.CharacterInfo charInfo = new EnumCharactersResult.CharacterInfo(result.GetFields());

                    Log.outInfo(LogFilter.Network, "Loading Character {0} from account {1}.", charInfo.Guid.ToString(), GetAccountId());

                    if (!Player.ValidateAppearance((Race)charInfo.RaceId, charInfo.ClassId, (Gender)charInfo.Sex, charInfo.HairStyle, charInfo.HairColor, charInfo.Face, charInfo.FacialHair, charInfo.Skin, charInfo.CustomDisplay))
                    {
                        Log.outError(LogFilter.Player, "Player {0} has wrong Appearance values (Hair/Skin/Color), forcing recustomize", charInfo.Guid.ToString());

                        // Make sure customization always works properly - send all zeroes instead
                        charInfo.Skin = 0;
                        charInfo.Face = 0;
                        charInfo.HairStyle = 0;
                        charInfo.HairColor = 0;
                        charInfo.FacialHair = 0;

                        if (!(charInfo.CustomizationFlag == CharacterCustomizeFlags.Customize))
                        {
                            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ADD_AT_LOGIN_FLAG);
                            stmt.AddValue(0, (ushort)AtLoginFlags.Customize);
                            stmt.AddValue(1, charInfo.Guid.GetCounter());
                            DB.Characters.Execute(stmt);
                            charInfo.CustomizationFlag = CharacterCustomizeFlags.Customize;
                        }
                    }

                    // Do not allow locked characters to login
                    if (!charInfo.Flags.HasAnyFlag(CharacterFlags.CharacterLockedForTransfer | CharacterFlags.LockedByBilling))
                        _legitCharacters.Add(charInfo.Guid);

                    if (!Global.WorldMgr.HasCharacterInfo(charInfo.Guid)) // This can happen if characters are inserted into the database manually. Core hasn't loaded name data yet.
                        Global.WorldMgr.AddCharacterInfo(charInfo.Guid, GetAccountId(), charInfo.Name, charInfo.Sex, charInfo.RaceId, (byte)charInfo.ClassId, charInfo.Level, false);

                    if (charInfo.ClassId == Class.DemonHunter)
                        demonHunterCount++;

                    if (demonHunterCount >= WorldConfig.GetIntValue(WorldCfg.DemonHuntersPerRealm) && !canAlwaysCreateDemonHunter)
                        charResult.HasDemonHunterOnRealm = true;
                    else
                        charResult.HasDemonHunterOnRealm = false;

                    if (charInfo.Level >= WorldConfig.GetIntValue(WorldCfg.CharacterCreatingMinLevelForDemonHunter) || canAlwaysCreateDemonHunter)
                        charResult.HasLevel70OnRealm = true;

                    charResult.Characters.Add(charInfo);
                }
                while (result.NextRow());
            }

            charResult.IsDemonHunterCreationAllowed = (!charResult.HasDemonHunterOnRealm && charResult.HasLevel70OnRealm) || canAlwaysCreateDemonHunter;

            SendPacket(charResult);
        }

        [WorldPacketHandler(ClientOpcodes.EnumCharactersDeletedByClient, Status = SessionStatus.Authed)]
        void HandleCharUndeleteEnum(EnumCharacters enumCharacters)
        {
            /// get all the data necessary for loading all undeleted characters (along with their pets) on the account
            PreparedStatement stmt;
            if (WorldConfig.GetBoolValue(WorldCfg.DeclinedNamesUsed))
                stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_UNDELETE_ENUM_DECLINED_NAME);
            else
                stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_UNDELETE_ENUM);

            stmt.AddValue(0, (uint)PetSaveMode.AsCurrent);
            stmt.AddValue(1, GetAccountId());

            _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(HandleCharUndeleteEnumCallback));
        }

        void HandleCharUndeleteEnumCallback(SQLResult result)
        {
            EnumCharactersResult charEnum = new EnumCharactersResult();
            charEnum.Success = true;
            charEnum.IsDeletedCharacters = true;
            charEnum.DisabledClassesMask.Set(WorldConfig.GetUIntValue(WorldCfg.CharacterCreatingDisabledClassmask));

            if (!result.IsEmpty())
            {
                do
                {
                    EnumCharactersResult.CharacterInfo charInfo = new EnumCharactersResult.CharacterInfo(result.GetFields());

                    Log.outInfo(LogFilter.Network, "Loading undeleted char guid {0} from account {1}.", charInfo.Guid.ToString(), GetAccountId());

                    if (!Global.WorldMgr.HasCharacterInfo(charInfo.Guid)) // This can happen if characters are inserted into the database manually. Core hasn't loaded name data yet.
                        Global.WorldMgr.AddCharacterInfo(charInfo.Guid, GetAccountId(), charInfo.Name, charInfo.Sex, charInfo.RaceId, (byte)charInfo.ClassId, charInfo.Level, true);

                    charEnum.Characters.Add(charInfo);
                }
                while (result.NextRow());
            }

            SendPacket(charEnum);
        }

        [WorldPacketHandler(ClientOpcodes.CreateCharacter, Status = SessionStatus.Authed)]
        void HandleCharCreate(CreateCharacter charCreate)
        {
            if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationTeammask))
            {
                int mask = WorldConfig.GetIntValue(WorldCfg.CharacterCreatingDisabled);
                if (mask != 0)
                {
                    bool disabled = false;

                    uint team = Player.TeamIdForRace(charCreate.CreateInfo.RaceId);
                    switch (team)
                    {
                        case TeamId.Alliance:
                            disabled = Convert.ToBoolean(mask & (1 << 0));
                            break;
                        case TeamId.Horde:
                            disabled = Convert.ToBoolean(mask & (1 << 1));
                            break;
                        case TeamId.Neutral:
                            disabled = Convert.ToBoolean(mask & (1 << 2));
                            break;
                    }

                    if (disabled)
                    {
                        SendCharCreate(ResponseCodes.CharCreateDisabled);
                        return;
                    }
                }
            }

            ChrClassesRecord classEntry = CliDB.ChrClassesStorage.LookupByKey(charCreate.CreateInfo.ClassId);
            if (classEntry == null)
            {
                Log.outError(LogFilter.Network, "Class ({0}) not found in DBC while creating new char for account (ID: {1}): wrong DBC files or cheater?", charCreate.CreateInfo.ClassId, GetAccountId());
                SendCharCreate(ResponseCodes.CharCreateFailed);
                return;
            }

            ChrRacesRecord raceEntry = CliDB.ChrRacesStorage.LookupByKey(charCreate.CreateInfo.RaceId);
            if (raceEntry == null)
            {
                Log.outError(LogFilter.Network, "Race ({0}) not found in DBC while creating new char for account (ID: {1}): wrong DBC files or cheater?", charCreate.CreateInfo.RaceId, GetAccountId());
                SendCharCreate(ResponseCodes.CharCreateFailed);
                return;
            }

            // prevent character creating Expansion race without Expansion account
            var raceExpansionRequirement = Global.ObjectMgr.GetRaceExpansionRequirement(charCreate.CreateInfo.RaceId);
            if (raceExpansionRequirement > GetExpansion())
            {
                Log.outError(LogFilter.Network, "Expansion {0} account:[{1}] tried to Create character with expansion {2} race ({3})", GetExpansion(), GetAccountId(), raceExpansionRequirement, charCreate.CreateInfo.RaceId);
                SendCharCreate(ResponseCodes.CharCreateExpansion);
                return;
            }

            // prevent character creating Expansion class without Expansion account
            var classExpansionRequirement = Global.ObjectMgr.GetClassExpansionRequirement(charCreate.CreateInfo.ClassId);
            if (classExpansionRequirement > GetExpansion())
            {
                Log.outError(LogFilter.Network, "Expansion {0} account:[{1}] tried to Create character with expansion {2} class ({3})", GetExpansion(), GetAccountId(), classExpansionRequirement, charCreate.CreateInfo.ClassId);
                SendCharCreate(ResponseCodes.CharCreateExpansionClass);
                return;
            }

            if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationRacemask))
            {
                int raceMaskDisabled = WorldConfig.GetIntValue(WorldCfg.CharacterCreatingDisabledRacemask);
                if (Convert.ToBoolean((1 << ((int)charCreate.CreateInfo.RaceId - 1)) & raceMaskDisabled))
                {
                    SendCharCreate(ResponseCodes.CharCreateDisabled);
                    return;
                }
            }

            if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationClassmask))
            {
                int classMaskDisabled = WorldConfig.GetIntValue(WorldCfg.CharacterCreatingDisabledClassmask);
                if (Convert.ToBoolean((1 << ((int)charCreate.CreateInfo.ClassId - 1)) & classMaskDisabled))
                {
                    SendCharCreate(ResponseCodes.CharCreateDisabled);
                    return;
                }
            }

            // prevent character creating with invalid name
            if (!ObjectManager.NormalizePlayerName(ref charCreate.CreateInfo.Name))
            {
                Log.outError(LogFilter.Network, "Account:[{0}] but tried to Create character with empty [name] ", GetAccountId());
                SendCharCreate(ResponseCodes.CharNameNoName);
                return;
            }

            // check name limitations
            ResponseCodes res = ObjectManager.CheckPlayerName(charCreate.CreateInfo.Name, GetSessionDbcLocale(), true);
            if (res != ResponseCodes.CharNameSuccess)
            {
                SendCharCreate(res);
                return;
            }

            if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationReservedname) && Global.ObjectMgr.IsReservedName(charCreate.CreateInfo.Name))
            {
                SendCharCreate(ResponseCodes.CharNameReserved);
                return;
            }

            if (charCreate.CreateInfo.ClassId == Class.Deathknight && !HasPermission(RBACPermissions.SkipCheckCharacterCreationDeathKnight))
            {
                // speedup check for death knight class disabled case
                if (WorldConfig.GetIntValue(WorldCfg.DeathKnightsPerRealm) == 0)
                {
                    SendCharCreate(ResponseCodes.CharCreateUniqueClassLimit);
                    return;
                }

                // speedup check for death knight class disabled case
                if (WorldConfig.GetIntValue(WorldCfg.CharacterCreatingMinLevelForDeathKnight) > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
                {
                    SendCharCreate(ResponseCodes.CharCreateLevelRequirement);
                    return;
                }
            }

            CharacterCreateInfo createInfo = charCreate.CreateInfo;
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHECK_NAME);
            stmt.AddValue(0, charCreate.CreateInfo.Name);

            _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithChainingCallback((queryCallback, result) =>
            {
                if (!result.IsEmpty())
                {
                    SendCharCreate(ResponseCodes.CharCreateNameInUse);
                    return;
                }

                stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_SUM_REALM_CHARACTERS);
                stmt.AddValue(0, GetAccountId());
                queryCallback.SetNextQuery(DB.Login.AsyncQuery(stmt));

            }).WithChainingCallback((queryCallback, result) =>
            {
                ulong acctCharCount = 0;
                if (!result.IsEmpty())
                    acctCharCount = result.Read<ulong>(0);

                if (acctCharCount >= WorldConfig.GetUIntValue(WorldCfg.CharactersPerAccount))
                {
                    SendCharCreate(ResponseCodes.CharCreateAccountLimit);
                    return;
                }

                stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_SUM_CHARS);
                stmt.AddValue(0, GetAccountId());
                queryCallback.SetNextQuery(DB.Characters.AsyncQuery(stmt));
            }).WithChainingCallback((queryCallback, result) =>
            {
                if (!result.IsEmpty())
                {
                    createInfo.CharCount = (byte)result.Read<ulong>(0); // SQL's COUNT() returns uint64 but it will always be less than uint8.Max

                    if (createInfo.CharCount >= WorldConfig.GetIntValue(WorldCfg.CharactersPerRealm))
                    {
                        SendCharCreate(ResponseCodes.CharCreateServerLimit);
                        return;
                    }
                }

                bool allowTwoSideAccounts = !Global.WorldMgr.IsPvPRealm() || HasPermission(RBACPermissions.TwoSideCharacterCreation);
                int skipCinematics = WorldConfig.GetIntValue(WorldCfg.SkipCinematics);

                Action<SQLResult> finalizeCharacterCreation = result1 =>
                {
                    bool haveSameRace = false;
                    int deathKnightReqLevel = WorldConfig.GetIntValue(WorldCfg.CharacterCreatingMinLevelForDeathKnight);
                    int demonHunterReqLevel = WorldConfig.GetIntValue(WorldCfg.CharacterCreatingMinLevelForDemonHunter);
                    bool hasDeathKnightReqLevel = (deathKnightReqLevel == 0);
                    bool hasDemonHunterReqLevel = (demonHunterReqLevel == 0);
                    bool checkDeathKnightReqs = createInfo.ClassId == Class.Deathknight && !HasPermission(RBACPermissions.SkipCheckCharacterCreationDeathKnight);
                    bool checkDemonHunterReqs = createInfo.ClassId == Class.DemonHunter && !HasPermission(RBACPermissions.SkipCheckCharacterCreationDemonHunter);

                    if (result1 != null && !result1.IsEmpty())
                    {
                        Team team = Player.TeamForRace(createInfo.RaceId);
                        int freeDeathKnightSlots = WorldConfig.GetIntValue(WorldCfg.DeathKnightsPerRealm);
                        int freeDemonHunterSlots = WorldConfig.GetIntValue(WorldCfg.DemonHuntersPerRealm);

                        byte accRace = result1.Read<byte>(1);

                        if (checkDeathKnightReqs)
                        {
                            byte accClass = result1.Read<byte>(2);
                            if (accClass == (byte)Class.Deathknight)
                            {
                                if (freeDeathKnightSlots > 0)
                                    --freeDeathKnightSlots;

                                if (freeDeathKnightSlots == 0)
                                {
                                    SendCharCreate(ResponseCodes.CharCreateUniqueClassLimit);
                                    return;
                                }
                            }

                            if (!hasDeathKnightReqLevel)
                            {
                                byte accLevel = result1.Read<byte>(0);
                                if (accLevel >= deathKnightReqLevel)
                                    hasDeathKnightReqLevel = true;
                            }
                        }

                        if (checkDemonHunterReqs)
                        {
                            byte accClass = result1.Read<byte>(2);
                            if (accClass == (byte)Class.DemonHunter)
                            {
                                if (freeDemonHunterSlots > 0)
                                    --freeDemonHunterSlots;

                                if (freeDemonHunterSlots == 0)
                                {
                                    SendCharCreate(ResponseCodes.CharCreateFailed);
                                    return;
                                }
                            }

                            if (!hasDemonHunterReqLevel)
                            {
                                byte accLevel = result1.Read<byte>(0);
                                if (accLevel >= demonHunterReqLevel)
                                    hasDemonHunterReqLevel = true;
                            }
                        }

                        // need to check team only for first character
                        /// @todo what to if account already has characters of both races?
                        if (!allowTwoSideAccounts)
                        {
                            Team accTeam = 0;
                            if (accRace > 0)
                                accTeam = Player.TeamForRace((Race)accRace);

                            if (accTeam != team)
                            {
                                SendCharCreate(ResponseCodes.CharCreatePvpTeamsViolation);
                                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.ClassId == Class.Deathknight || createInfo.ClassId == Class.DemonHunter)
                        {
                            if (!result1.NextRow())
                                break;

                            accRace = result1.Read<byte>(1);

                            if (!haveSameRace)
                                haveSameRace = createInfo.RaceId == (Race)accRace;

                            if (checkDeathKnightReqs)
                            {
                                byte acc_class = result1.Read<byte>(2);
                                if (acc_class == (byte)Class.Deathknight)
                                {
                                    if (freeDeathKnightSlots > 0)
                                        --freeDeathKnightSlots;

                                    if (freeDeathKnightSlots == 0)
                                    {
                                        SendCharCreate(ResponseCodes.CharCreateUniqueClassLimit);
                                        return;
                                    }
                                }

                                if (!hasDeathKnightReqLevel)
                                {
                                    byte acc_level = result1.Read<byte>(0);
                                    if (acc_level >= deathKnightReqLevel)
                                        hasDeathKnightReqLevel = true;
                                }
                            }

                            if (checkDemonHunterReqs)
                            {
                                byte acc_class = result1.Read<byte>(2);
                                if (acc_class == (byte)Class.DemonHunter)
                                {
                                    if (freeDemonHunterSlots > 0)
                                        --freeDemonHunterSlots;

                                    if (freeDemonHunterSlots == 0)
                                    {
                                        SendCharCreate(ResponseCodes.CharCreateFailed);
                                        return;
                                    }
                                }

                                if (!hasDemonHunterReqLevel)
                                {
                                    byte acc_level = result1.Read<byte>(0);
                                    if (acc_level >= demonHunterReqLevel)
                                        hasDemonHunterReqLevel = true;
                                }
                            }
                        }
                    }

                    if (checkDeathKnightReqs && !hasDeathKnightReqLevel)
                    {
                        SendCharCreate(ResponseCodes.CharCreateLevelRequirement);
                        return;
                    }

                    if (checkDemonHunterReqs && !hasDemonHunterReqLevel)
                    {
                        SendCharCreate(ResponseCodes.CharCreateLevelRequirement);
                        return;
                    }

                    Player newChar = new Player(this);
                    newChar.GetMotionMaster().Initialize();
                    if (!newChar.Create(Global.ObjectMgr.GetGenerator(HighGuid.Player).Generate(), createInfo))
                    {
                        // Player not create (race/class/etc problem?)
                        newChar.CleanupsBeforeDelete();

                        SendCharCreate(ResponseCodes.CharCreateError);
                        return;
                    }

                    if ((haveSameRace && skipCinematics == 1) || skipCinematics == 2)
                        newChar.setCinematic(1);                          // not show intro

                    newChar.atLoginFlags = AtLoginFlags.FirstLogin;               // First login

                    // Player created, save it now
                    newChar.SaveToDB(true);
                    createInfo.CharCount += 1;

                    SQLTransaction trans = new SQLTransaction();

                    stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_REALM_CHARACTERS_BY_REALM);
                    stmt.AddValue(0, GetAccountId());
                    stmt.AddValue(1, Global.WorldMgr.GetRealm().Id.Realm);
                    trans.Append(stmt);

                    stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_REALM_CHARACTERS);
                    stmt.AddValue(0, createInfo.CharCount);
                    stmt.AddValue(1, GetAccountId());
                    stmt.AddValue(2, Global.WorldMgr.GetRealm().Id.Realm);
                    trans.Append(stmt);

                    DB.Login.CommitTransaction(trans);

                    // Success
                    SendCharCreate(ResponseCodes.CharCreateSuccess);

                    Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Create Character: {2} {3}", GetAccountId(), GetRemoteAddress(), createInfo.Name, newChar.GetGUID().ToString());
                    Global.ScriptMgr.OnPlayerCreate(newChar);
                    Global.WorldMgr.AddCharacterInfo(newChar.GetGUID(), GetAccountId(), newChar.GetName(), newChar.GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender), (byte)newChar.GetRace(), (byte)newChar.GetClass(), (byte)newChar.getLevel(), false);

                    newChar.CleanupsBeforeDelete();
                };

                if (!allowTwoSideAccounts || skipCinematics == 1 || createInfo.ClassId == Class.Deathknight || createInfo.ClassId == Class.DemonHunter)
                {
                    finalizeCharacterCreation(new SQLResult());
                    return;
                }

                stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_CREATE_INFO);
                stmt.AddValue(0, GetAccountId());
                stmt.AddValue(1, (skipCinematics == 1 || createInfo.ClassId == Class.Deathknight || createInfo.ClassId == Class.DemonHunter) ? 12 : 1);
                queryCallback.WithCallback(finalizeCharacterCreation).SetNextQuery(DB.Characters.AsyncQuery(stmt));
            }));
        }

        [WorldPacketHandler(ClientOpcodes.CharDelete, Status = SessionStatus.Authed)]
        void HandleCharDelete(CharDelete charDelete)
        {
            // can't delete loaded character
            if (Global.ObjAccessor.FindPlayer(charDelete.Guid))
                return;

            // is guild leader
            if (Global.GuildMgr.GetGuildByLeader(charDelete.Guid))
            {
                SendCharDelete(ResponseCodes.CharDeleteFailedGuildLeader);
                return;
            }

            // is arena team captain
            if (Global.ArenaTeamMgr.GetArenaTeamByCaptain(charDelete.Guid) != null)
            {
                SendCharDelete(ResponseCodes.CharDeleteFailedArenaCaptain);
                return;
            }

            CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(charDelete.Guid);
            if (characterInfo == null)
            {
                //Global.ScriptMgr.OnPlayerFailedDelete(charDelete.Guid, initAccountId);
                return;
            }

            uint accountId = characterInfo.AccountId;
            string name = characterInfo.Name;
            byte level = characterInfo.Level;

            // prevent deleting other players' characters using cheating tools
            if (accountId != GetAccountId())
                return;

            string IP_str = GetRemoteAddress();
            Log.outInfo(LogFilter.Player, "Account: {0}, IP: {1} deleted character: {2}, {3}, Level: {4}", accountId, IP_str, name, charDelete.Guid.ToString(), level);
            Global.ScriptMgr.OnPlayerDelete(charDelete.Guid);

            Global.GuildFinderMgr.RemoveAllMembershipRequestsFromPlayer(charDelete.Guid);
            Global.CalendarMgr.RemoveAllPlayerEventsAndInvites(charDelete.Guid);
            Player.DeleteFromDB(charDelete.Guid, accountId);

            SendCharDelete(ResponseCodes.CharDeleteSuccess);
        }

        [WorldPacketHandler(ClientOpcodes.GenerateRandomCharacterName, Status = SessionStatus.Authed)]
        void HandleRandomizeCharName(GenerateRandomCharacterName packet)
        {
            if (!Player.IsValidRace((Race)packet.Race))
            {
                Log.outError(LogFilter.Network, "Invalid race ({0}) sent by accountId: {1}", packet.Race, GetAccountId());
                return;
            }

            if (!Player.IsValidGender((Gender)packet.Sex))
            {
                Log.outError(LogFilter.Network, "Invalid gender ({0}) sent by accountId: {1}", packet.Sex, GetAccountId());
                return;
            }

            GenerateRandomCharacterNameResult result = new GenerateRandomCharacterNameResult();
            result.Success = true;
            result.Name = Global.DB2Mgr.GetNameGenEntry(packet.Race, packet.Sex, GetSessionDbcLocale(), Global.WorldMgr.GetDefaultDbcLocale());

            SendPacket(result);
        }

        [WorldPacketHandler(ClientOpcodes.ReorderCharacters, Status = SessionStatus.Authed)]
        void HandleReorderCharacters(ReorderCharacters reorderChars)
        {
            SQLTransaction trans = new SQLTransaction();

            foreach (var reorderInfo in reorderChars.Entries)
            {
                PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_LIST_SLOT);
                stmt.AddValue(0, reorderInfo.NewPosition);
                stmt.AddValue(1, reorderInfo.PlayerGUID.GetCounter());
                stmt.AddValue(2, GetAccountId());
                trans.Append(stmt);
            }

            DB.Characters.CommitTransaction(trans);
        }

        [WorldPacketHandler(ClientOpcodes.PlayerLogin, Status = SessionStatus.Authed)]
        void HandlePlayerLogin(PlayerLogin playerLogin)
        {
            if (PlayerLoading() || GetPlayer() != null)
            {
                Log.outError(LogFilter.Network, "Player tries to login again, AccountId = {0}", GetAccountId());
                return;
            }

            m_playerLoading = playerLogin.Guid;
            Log.outDebug(LogFilter.Network, "Character {0} logging in", playerLogin.Guid.ToString());

            if (!_legitCharacters.Contains(playerLogin.Guid))
            {
                Log.outError(LogFilter.Network, "Account ({0}) can't login with that character ({1}).", GetAccountId(), playerLogin.Guid.ToString());
                KickPlayer();
                return;
            }

            SendConnectToInstance(ConnectToSerial.WorldAttempt1);
        }

        public void HandleContinuePlayerLogin()
        {
            if (!PlayerLoading() || GetPlayer())
            {
                KickPlayer();
                return;
            }

            LoginQueryHolder holder = new LoginQueryHolder(GetAccountId(), m_playerLoading);
            holder.Initialize();

            SendPacket(new ResumeComms(ConnectionType.Instance));

            _charLoginCallback = DB.Characters.DelayQueryHolder(holder);
        }

        public void HandlePlayerLogin(LoginQueryHolder holder)
        {
            ObjectGuid playerGuid = holder.GetGuid();

            Player pCurrChar = new Player(this);
            if (!pCurrChar.LoadFromDB(playerGuid, holder))
            {
                SetPlayer(null);
                KickPlayer();
                m_playerLoading.Clear();
                return;
            }

            pCurrChar.SetUInt32Value(PlayerFields.VirtualRealm, Global.WorldMgr.GetVirtualRealmAddress());

            SendTutorialsData();

            pCurrChar.GetMotionMaster().Initialize();
            pCurrChar.SendDungeonDifficulty();

            LoginVerifyWorld loginVerifyWorld = new LoginVerifyWorld();
            loginVerifyWorld.MapID = (int)pCurrChar.GetMapId();
            loginVerifyWorld.Pos = pCurrChar.GetPosition();
            SendPacket(loginVerifyWorld);

            LoadAccountData(holder.GetResult(PlayerLoginQueryLoad.AccountData), AccountDataTypes.PerCharacterCacheMask);

            AccountDataTimes accountDataTimes = new AccountDataTimes();
            accountDataTimes.PlayerGuid = playerGuid;
            accountDataTimes.ServerTime = (uint)Global.WorldMgr.GetGameTime();
            for (AccountDataTypes i = 0; i < AccountDataTypes.Max; ++i)
                accountDataTimes.AccountTimes[(int)i] = (uint)GetAccountData(i).Time;

            SendPacket(accountDataTimes);

            SendFeatureSystemStatus();

            MOTD motd = new MOTD();
            motd.Text = Global.WorldMgr.GetMotd();
            SendPacket(motd);

            SendSetTimeZoneInformation();

            // Send PVPSeason
            {
                PVPSeason season = new PVPSeason();
                season.PreviousSeason = (WorldConfig.GetUIntValue(WorldCfg.ArenaSeasonId) - (WorldConfig.GetBoolValue(WorldCfg.ArenaSeasonInProgress) ? 1u : 0u));

                if (WorldConfig.GetBoolValue(WorldCfg.ArenaSeasonInProgress))
                    season.CurrentSeason = WorldConfig.GetUIntValue(WorldCfg.ArenaSeasonId);

                SendPacket(season);
            }

            SQLResult resultGuild = holder.GetResult(PlayerLoginQueryLoad.Guild);
            if (!resultGuild.IsEmpty())
            {
                pCurrChar.SetInGuild(resultGuild.Read<uint>(0));
                pCurrChar.SetRank(resultGuild.Read<byte>(1));
                Guild guild = Global.GuildMgr.GetGuildById(pCurrChar.GetGuildId());
                if (guild)
                    pCurrChar.SetGuildLevel(guild.GetLevel());
            }
            else if (pCurrChar.GetGuildId() != 0)
            {
                pCurrChar.SetInGuild(0);
                pCurrChar.SetRank(0);
                pCurrChar.SetGuildLevel(0);
            }

            //WorldPacket data = new WorldPacket(ServerOpcodes.LearnedDanceMoves);
            //data.WriteUInt64(0);
            //SendPacket(data);

            // TODO: Move this to BattlePetMgr::SendJournalLock() just to have all packets in one file
            SendPacket(new BattlePetJournalLockAcquired());

            pCurrChar.SendInitialPacketsBeforeAddToMap();

            //Show cinematic at the first time that player login
            if (pCurrChar.getCinematic() == 0)
            {
                pCurrChar.setCinematic(1);
                ChrClassesRecord cEntry = CliDB.ChrClassesStorage.LookupByKey(pCurrChar.GetClass());
                if (cEntry != null)
                {
                    ChrRacesRecord rEntry = CliDB.ChrRacesStorage.LookupByKey(pCurrChar.GetRace());
                    if (pCurrChar.GetClass() == Class.DemonHunter) // @todo: find a more generic solution
                        pCurrChar.SendMovieStart(469);
                    else if (cEntry.CinematicSequenceID != 0)
                        pCurrChar.SendCinematicStart(cEntry.CinematicSequenceID);
                    else if (rEntry != null)
                        pCurrChar.SendCinematicStart(rEntry.CinematicSequenceID);
                }
            }

            if (!pCurrChar.GetMap().AddPlayerToMap(pCurrChar))
            {
                var at = Global.ObjectMgr.GetGoBackTrigger(pCurrChar.GetMapId());
                if (at != null)
                    pCurrChar.TeleportTo(at.target_mapId, at.target_X, at.target_Y, at.target_Z, pCurrChar.Orientation);
                else
                    pCurrChar.TeleportTo(pCurrChar.GetHomebind());
            }
            Global.ObjAccessor.AddObject(pCurrChar);

            if (pCurrChar.GetGuildId() != 0)
            {
                Guild guild = Global.GuildMgr.GetGuildById(pCurrChar.GetGuildId());
                if (guild)
                    guild.SendLoginInfo(this);
                else
                {
                    // remove wrong guild data
                    Log.outError(LogFilter.Server, "Player {0} ({1}) marked as member of not existing guild (id: {2}), removing guild membership for player.", pCurrChar.GetName(), pCurrChar.GetGUID().ToString(),
                        pCurrChar.GetGuildId());
                    pCurrChar.SetInGuild(0);
                }
            }
            pCurrChar.SendInitialPacketsAfterAddToMap();

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_ONLINE);
            stmt.AddValue(0, pCurrChar.GetGUID().GetCounter());
            DB.Characters.Execute(stmt);

            stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_ACCOUNT_ONLINE);
            stmt.AddValue(0, GetAccountId());
            DB.Login.Execute(stmt);

            pCurrChar.SetInGameTime(Time.GetMSTime());

            // announce group about member online (must be after add to player list to receive announce to self)
            Group group = pCurrChar.GetGroup();
            if (group)
            {
                group.SendUpdate();
                group.ResetMaxEnchantingLevel();
            }

            // friend status
            Global.SocialMgr.SendFriendStatus(pCurrChar, FriendsResult.Online, pCurrChar.GetGUID(), true);

            // Place character in world (and load zone) before some object loading
            pCurrChar.LoadCorpse(holder.GetResult(PlayerLoginQueryLoad.CorpseLocation));

            // setting Ghost+speed if dead
            if (pCurrChar.getDeathState() != DeathState.Alive)
            {
                // not blizz like, we must correctly save and load player instead...
                if (pCurrChar.GetRace() == Race.NightElf && !pCurrChar.HasAura(20584))
                    pCurrChar.CastSpell(pCurrChar, 20584, true);// 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)

                if (!pCurrChar.HasAura(8326))
                    pCurrChar.CastSpell(pCurrChar, 8326, true, null);     // auras SPELL_AURA_GHOST, SPELL_AURA_INCREASE_SPEED(why?), SPELL_AURA_INCREASE_SWIM_SPEED(why?)

                pCurrChar.SetWaterWalking(true);
            }

            pCurrChar.ContinueTaxiFlight();

            // reset for all pets before pet loading
            if (pCurrChar.HasAtLoginFlag(AtLoginFlags.ResetPetTalents))
            {
                // Delete all of the player's pet spells
                PreparedStatement stmtSpells = DB.Characters.GetPreparedStatement(CharStatements.DEL_ALL_PET_SPELLS_BY_OWNER);
                stmtSpells.AddValue(0, pCurrChar.GetGUID().GetCounter());
                DB.Characters.Execute(stmtSpells);

                // Then reset all of the player's pet specualizations
                PreparedStatement stmtSpec = DB.Characters.GetPreparedStatement(CharStatements.UPD_PET_SPECS_BY_OWNER);
                stmtSpec.AddValue(0, pCurrChar.GetGUID().GetCounter());
                DB.Characters.Execute(stmtSpec);
            }

            // 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 (Global.WorldMgr.IsFFAPvPRealm() && !pCurrChar.IsGameMaster() && !pCurrChar.HasFlag(PlayerFields.Flags, PlayerFlags.Resting))
                pCurrChar.SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp);

            if (pCurrChar.HasFlag(PlayerFields.Flags, PlayerFlags.ContestedPVP))
                pCurrChar.SetContestedPvP();

            // Apply at_login requests
            if (pCurrChar.HasAtLoginFlag(AtLoginFlags.ResetSpells))
            {
                pCurrChar.ResetSpells();
                SendNotification(CypherStrings.ResetSpells);
            }

            if (pCurrChar.HasAtLoginFlag(AtLoginFlags.ResetTalents))
            {
                pCurrChar.ResetTalents(true);
                pCurrChar.ResetTalentSpecialization();
                pCurrChar.SendTalentsInfoData();              // original talents send already in to SendInitialPacketsBeforeAddToMap, resend reset state
                SendNotification(CypherStrings.ResetTalents);
            }

            if (pCurrChar.HasAtLoginFlag(AtLoginFlags.FirstLogin))
            {
                pCurrChar.RemoveAtLoginFlag(AtLoginFlags.FirstLogin);

                PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(pCurrChar.GetRace(), pCurrChar.GetClass());
                foreach (var spellId in info.castSpells)
                    pCurrChar.CastSpell(pCurrChar, spellId, true);
            }

            // show time before shutdown if shutdown planned.
            if (Global.WorldMgr.IsShuttingDown())
                Global.WorldMgr.ShutdownMsg(true, pCurrChar);

            if (WorldConfig.GetBoolValue(WorldCfg.AllTaxiPaths))
                pCurrChar.SetTaxiCheater(true);

            if (pCurrChar.IsGameMaster())
                SendNotification(CypherStrings.GmOn);

            string IP_str = GetRemoteAddress();
            Log.outDebug(LogFilter.Network, "Account: {0} (IP: {1}) Login Character:[{2}] ({3}) Level: {4}",
                GetAccountId(), IP_str, pCurrChar.GetName(), pCurrChar.GetGUID().ToString(), pCurrChar.getLevel());

            if (!pCurrChar.IsStandState() && !pCurrChar.HasUnitState(UnitState.Stunned))
                pCurrChar.SetStandState(UnitStandStateType.Stand);

            m_playerLoading.Clear();

            Global.ScriptMgr.OnPlayerLogin(pCurrChar);
        }

        public void AbortLogin(LoginFailureReason reason)
        {
            if (!PlayerLoading() || GetPlayer())
            {
                KickPlayer();
                return;
            }

            m_playerLoading.Clear();
            SendPacket(new CharacterLoginFailed(reason));
        }

        [WorldPacketHandler(ClientOpcodes.LoadingScreenNotify, Status = SessionStatus.Authed)]
        void HandleLoadScreen(LoadingScreenNotify loadingScreenNotify)
        {
            // TODO: Do something with this packet
        }

        public void SendFeatureSystemStatus()
        {
            FeatureSystemStatus features = new FeatureSystemStatus();

            /// START OF DUMMY VALUES
            features.ComplaintStatus = 2;
            features.ScrollOfResurrectionRequestsRemaining = 1;
            features.ScrollOfResurrectionMaxRequestsPerDay = 1;
            features.TwitterPostThrottleLimit = 60;
            features.TwitterPostThrottleCooldown = 20;
            features.CfgRealmID = 2;
            features.CfgRealmRecID = 0;
            features.TokenPollTimeSeconds = 300;
            features.TokenRedeemIndex = 0;
            features.VoiceEnabled = false;
            features.BrowserEnabled = false; // Has to be false, otherwise client will crash if "Customer Support" is opened

            features.EuropaTicketSystemStatus.HasValue = true;
            features.EuropaTicketSystemStatus.Value.ThrottleState.MaxTries = 10;
            features.EuropaTicketSystemStatus.Value.ThrottleState.PerMilliseconds = 60000;
            features.EuropaTicketSystemStatus.Value.ThrottleState.TryCount = 1;
            features.EuropaTicketSystemStatus.Value.ThrottleState.LastResetTimeBeforeNow = 111111;
            features.ComplaintStatus = 0;
            features.TutorialsEnabled = true;
            features.NPETutorialsEnabled = true;
            /// END OF DUMMY VALUES

            features.EuropaTicketSystemStatus.Value.TicketsEnabled = WorldConfig.GetBoolValue(WorldCfg.SupportTicketsEnabled);
            features.EuropaTicketSystemStatus.Value.BugsEnabled = WorldConfig.GetBoolValue(WorldCfg.SupportBugsEnabled);
            features.EuropaTicketSystemStatus.Value.ComplaintsEnabled = WorldConfig.GetBoolValue(WorldCfg.SupportComplaintsEnabled);
            features.EuropaTicketSystemStatus.Value.SuggestionsEnabled = WorldConfig.GetBoolValue(WorldCfg.SupportSuggestionsEnabled);

            SendPacket(features);
        }

        [WorldPacketHandler(ClientOpcodes.SetFactionAtWar)]
        void HandleSetFactionAtWar(SetFactionAtWar packet)
        {
            GetPlayer().GetReputationMgr().SetAtWar(packet.FactionIndex, true);
        }

        [WorldPacketHandler(ClientOpcodes.SetFactionNotAtWar)]
        void HandleSetFactionNotAtWar(SetFactionNotAtWar packet)
        {
            GetPlayer().GetReputationMgr().SetAtWar(packet.FactionIndex, false);
        }

        [WorldPacketHandler(ClientOpcodes.Tutorial)]
        void HandleTutorialFlag(TutorialSetFlag packet)
        {
            switch (packet.Action)
            {
                case TutorialAction.Update:
                    {
                        byte index = (byte)(packet.TutorialBit >> 5);
                        if (index >= SharedConst.MaxAccountTutorialValues)
                        {
                            Log.outError(LogFilter.Network, "CMSG_TUTORIAL_FLAG received bad TutorialBit {0}.", packet.TutorialBit);
                            return;
                        }
                        uint flag = GetTutorialInt(index);
                        flag |= (uint)(1 << (int)(packet.TutorialBit & 0x1F));
                        SetTutorialInt(index, flag);
                        break;
                    }
                case TutorialAction.Clear:
                    for (byte i = 0; i < SharedConst.MaxAccountTutorialValues; ++i)
                        SetTutorialInt(i, 0xFFFFFFFF);
                    break;
                case TutorialAction.Reset:
                    for (byte i = 0; i < SharedConst.MaxAccountTutorialValues; ++i)
                        SetTutorialInt(i, 0x00000000);
                    break;
                default:
                    Log.outError(LogFilter.Network, "CMSG_TUTORIAL_FLAG received unknown TutorialAction {0}.", packet.Action);
                    return;
            }
        }

        [WorldPacketHandler(ClientOpcodes.SetWatchedFaction)]
        void HandleSetWatchedFaction(SetWatchedFaction packet)
        {
            GetPlayer().SetInt32Value(PlayerFields.WatchedFactionIndex, (int)packet.FactionIndex);
        }

        [WorldPacketHandler(ClientOpcodes.SetFactionInactive)]
        void HandleSetFactionInactive(SetFactionInactive packet)
        {
            GetPlayer().GetReputationMgr().SetInactive(packet.Index, packet.State);
        }

        [WorldPacketHandler(ClientOpcodes.RequestForcedReactions)]
        void HandleRequestForcedReactions(RequestForcedReactions requestForcedReactions)
        {
            GetPlayer().GetReputationMgr().SendForceReactions();
        }

        [WorldPacketHandler(ClientOpcodes.CharacterRenameRequest, Status = SessionStatus.Authed)]
        void HandleCharRename(CharacterRenameRequest request)
        {
            if (!_legitCharacters.Contains(request.RenameInfo.Guid))
            {
                Log.outError(LogFilter.Network, "Account {0}, IP: {1} tried to rename character {2}, but it does not belong to their account!",
                    GetAccountId(), GetRemoteAddress(), request.RenameInfo.Guid.ToString());
                KickPlayer();
                return;
            }

            // prevent character rename to invalid name
            if (!ObjectManager.NormalizePlayerName(ref request.RenameInfo.NewName))
            {
                SendCharRename(ResponseCodes.CharNameNoName, request.RenameInfo);
                return;
            }

            ResponseCodes res = ObjectManager.CheckPlayerName(request.RenameInfo.NewName, GetSessionDbcLocale(), true);
            if (res != ResponseCodes.CharNameSuccess)
            {
                SendCharRename(res, request.RenameInfo);
                return;
            }

            if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationReservedname) && Global.ObjectMgr.IsReservedName(request.RenameInfo.NewName))
            {
                SendCharRename(ResponseCodes.CharNameReserved, request.RenameInfo);
                return;
            }

            // Ensure that there is no character with the desired new name
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_FREE_NAME);
            stmt.AddValue(0, request.RenameInfo.Guid.GetCounter());
            stmt.AddValue(1, request.RenameInfo.NewName);

            _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(HandleCharRenameCallBack, request.RenameInfo));
        }

        void HandleCharRenameCallBack(CharacterRenameInfo renameInfo, SQLResult result)
        {
            if (result.IsEmpty())
            {
                SendCharRename(ResponseCodes.CharNameFailure, renameInfo);
                return;
            }

            string oldName = result.Read<string>(0);
            // check name limitations
            AtLoginFlags atLoginFlags = (AtLoginFlags)result.Read<uint>(1);
            if (!atLoginFlags.HasAnyFlag(AtLoginFlags.Rename))
            {
                SendCharRename(ResponseCodes.CharCreateError, renameInfo);
                return;
            }
            atLoginFlags &= ~AtLoginFlags.Rename;

            SQLTransaction trans = new SQLTransaction();
            ulong lowGuid = renameInfo.Guid.GetCounter();

            // Update name and at_login flag in the db
            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_NAME_AT_LOGIN);
            stmt.AddValue(0, renameInfo.NewName);
            stmt.AddValue(1, atLoginFlags);
            stmt.AddValue(2, lowGuid);
            trans.Append(stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_DECLINED_NAME);
            stmt.AddValue(0, lowGuid);
            trans.Append(stmt);

            DB.Characters.CommitTransaction(trans);

            Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] ({3}) Changed name to: {4}",
                GetAccountId(), GetRemoteAddress(), oldName, renameInfo.Guid.ToString(), renameInfo.NewName);

            SendCharRename(ResponseCodes.Success, renameInfo);

            Global.WorldMgr.UpdateCharacterInfo(renameInfo.Guid, renameInfo.NewName);
        }

        [WorldPacketHandler(ClientOpcodes.SetPlayerDeclinedNames, Status = SessionStatus.Authed)]
        void HandleSetPlayerDeclinedNames(SetPlayerDeclinedNames packet)
        {
            // not accept declined names for unsupported languages
            string name;
            if (!ObjectManager.GetPlayerNameByGUID(packet.Player, out name))
            {
                SendSetPlayerDeclinedNamesResult(DeclinedNameResult.Error, packet.Player);
                return;
            }

            if (!char.IsLetter(name[0]))                      // name already stored as only single alphabet using
            {
                SendSetPlayerDeclinedNamesResult(DeclinedNameResult.Error, packet.Player);
                return;
            }

            for (int i = 0; i < SharedConst.MaxDeclinedNameCases; ++i)
            {
                string declinedName = packet.DeclinedNames.name[i];
                if (!ObjectManager.NormalizePlayerName(ref declinedName))
                {
                    SendSetPlayerDeclinedNamesResult(DeclinedNameResult.Error, packet.Player);
                    return;
                }
                packet.DeclinedNames.name[i] = declinedName;
            }

            for (int i = 0; i < SharedConst.MaxDeclinedNameCases; ++i)
            {
                string declinedName = packet.DeclinedNames.name[i];
                DB.Characters.EscapeString(ref declinedName);
                packet.DeclinedNames.name[i] = declinedName;
            }

            SQLTransaction trans = new SQLTransaction();

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_DECLINED_NAME);
            stmt.AddValue(0, packet.Player.GetCounter());
            trans.Append(stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_DECLINED_NAME);
            stmt.AddValue(0, packet.Player.GetCounter());

            for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; i++)
                stmt.AddValue(i + 1, packet.DeclinedNames.name[i]);

            trans.Append(stmt);

            DB.Characters.CommitTransaction(trans);

            SendSetPlayerDeclinedNamesResult(DeclinedNameResult.Success, packet.Player);
        }

        [WorldPacketHandler(ClientOpcodes.AlterAppearance)]
        void HandleAlterAppearance(AlterApperance packet)
        {
            BarberShopStyleRecord bs_hair = CliDB.BarberShopStyleStorage.LookupByKey(packet.NewHairStyle);
            if (bs_hair == null || bs_hair.Type != 0 || bs_hair.Race != (byte)GetPlayer().GetRace() || bs_hair.Sex != GetPlayer().GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender))
                return;

            BarberShopStyleRecord bs_facialHair = CliDB.BarberShopStyleStorage.LookupByKey(packet.NewFacialHair);
            if (bs_facialHair == null || bs_facialHair.Type != 2 || bs_facialHair.Race != (byte)GetPlayer().GetRace() || bs_facialHair.Sex != GetPlayer().GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender))
                return;

            BarberShopStyleRecord bs_skinColor = CliDB.BarberShopStyleStorage.LookupByKey(packet.NewSkinColor);
            if (bs_skinColor != null && (bs_skinColor.Type != 3 || bs_skinColor.Race != (byte)GetPlayer().GetRace() || bs_skinColor.Sex != GetPlayer().GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender)))
                return;

            BarberShopStyleRecord bs_face = CliDB.BarberShopStyleStorage.LookupByKey(packet.NewFace);
            if (bs_face != null && (bs_face.Type != 4 || bs_face.Race != (byte)GetPlayer().GetRace() || bs_face.Sex != GetPlayer().GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender)))
                return;

            Array<BarberShopStyleRecord> customDisplayEntries = new Array<BarberShopStyleRecord>(PlayerConst.CustomDisplaySize);
            Array<byte> customDisplay = new Array<byte>(PlayerConst.CustomDisplaySize);
            for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i)
            {
                BarberShopStyleRecord bs_customDisplay = CliDB.BarberShopStyleStorage.LookupByKey(packet.NewCustomDisplay[i]);
                if (bs_customDisplay != null && (bs_customDisplay.Type != 5 + i || bs_customDisplay.Race != (byte)_player.GetRace() || bs_customDisplay.Sex != _player.GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender)))
                    return;

                customDisplayEntries[i] = bs_customDisplay;
                customDisplay[i] = (byte)(bs_customDisplay != null ? bs_customDisplay.Data : 0);
            }

            if (!Player.ValidateAppearance(GetPlayer().GetRace(), GetPlayer().GetClass(), (Gender)GetPlayer().GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender),
                bs_hair.Data, (byte)packet.NewHairColor, bs_face != null ? bs_face.Data : GetPlayer().GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId),
                bs_facialHair.Data, (bs_skinColor != null ? bs_skinColor.Data : GetPlayer().GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId)), customDisplay))
                return;

            GameObject go = GetPlayer().FindNearestGameObjectOfType(GameObjectTypes.BarberChair, 5.0f);
            if (!go)
            {
                SendPacket(new BarberShopResult(BarberShopResult.ResultEnum.NotOnChair));
                return;
            }

            if (GetPlayer().GetStandState() != (UnitStandStateType)((int)UnitStandStateType.SitLowChair + go.GetGoInfo().BarberChair.chairheight))
            {
                SendPacket(new BarberShopResult(BarberShopResult.ResultEnum.NotOnChair));
                return;
            }

            uint cost = GetPlayer().GetBarberShopCost(bs_hair, packet.NewHairColor, bs_facialHair, bs_skinColor, bs_face, customDisplayEntries);
            if (!GetPlayer().HasEnoughMoney((ulong)cost))
            {
                SendPacket(new BarberShopResult(BarberShopResult.ResultEnum.NoMoney));
                return;
            }

            SendPacket(new BarberShopResult(BarberShopResult.ResultEnum.Success));

            _player.ModifyMoney(-cost);
            _player.UpdateCriteria(CriteriaTypes.GoldSpentAtBarber, cost);

            _player.SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId, bs_hair.Data);
            _player.SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId, (byte)packet.NewHairColor);
            _player.SetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle, bs_facialHair.Data);
            if (bs_skinColor != null)
                GetPlayer().SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId, bs_skinColor.Data);
            if (bs_face != null)
                _player.SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId, bs_face.Data);

            for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i)
                _player.SetByteValue(PlayerFields.Bytes2, (byte)(PlayerFieldOffsets.Bytes2OffsetCustomDisplayOption + i), customDisplay[i]);

            _player.UpdateCriteria(CriteriaTypes.VisitBarberShop, 1);

            _player.SetStandState(UnitStandStateType.Stand);
        }

        [WorldPacketHandler(ClientOpcodes.CharCustomize, Status = SessionStatus.Authed)]
        void HandleCharCustomize(CharCustomize packet)
        {
            if (!_legitCharacters.Contains(packet.CustomizeInfo.CharGUID))
            {
                Log.outError(LogFilter.Network, "Account {0}, IP: {1} tried to customise {2}, but it does not belong to their account!",
                    GetAccountId(), GetRemoteAddress(), packet.CustomizeInfo.CharGUID.ToString());
                KickPlayer();
                return;
            }

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_CUSTOMIZE_INFO);
            stmt.AddValue(0, packet.CustomizeInfo.CharGUID.GetCounter());

            _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(HandleCharCustomizeCallback, packet.CustomizeInfo));
        }

        void HandleCharCustomizeCallback(CharCustomizeInfo customizeInfo, SQLResult result)
        {
            if (result.IsEmpty())
            {
                SendCharCustomize(ResponseCodes.CharCreateError, customizeInfo);
                return;
            }

            string oldName = result.Read<string>(0);
            byte plrRace = result.Read<byte>(1);
            byte plrClass = result.Read<byte>(2);
            byte plrGender = result.Read<byte>(3);
            AtLoginFlags atLoginFlags = (AtLoginFlags)result.Read<ushort>(4);

            if (!Player.ValidateAppearance((Race)plrRace, (Class)plrClass, (Gender)plrGender, customizeInfo.HairStyleID, customizeInfo.HairColorID, customizeInfo.FaceID, 
                customizeInfo.FacialHairStyleID, customizeInfo.SkinID, customizeInfo.CustomDisplay))
            {
                SendCharCustomize(ResponseCodes.CharCreateError, customizeInfo);
                return;
            }

            if (!atLoginFlags.HasAnyFlag(AtLoginFlags.Customize))
            {
                SendCharCustomize(ResponseCodes.CharCreateError, customizeInfo);
                return;
            }

            // prevent character rename
            if (WorldConfig.GetBoolValue(WorldCfg.PreventRenameCustomization) && (customizeInfo.CharName != oldName))
            {
                SendCharCustomize(ResponseCodes.CharNameFailure, customizeInfo);
                return;
            }

            atLoginFlags &= ~AtLoginFlags.Customize;

            // prevent character rename to invalid name
            if (!ObjectManager.NormalizePlayerName(ref customizeInfo.CharName))
            {
                SendCharCustomize(ResponseCodes.CharNameNoName, customizeInfo);
                return;
            }

            ResponseCodes res = ObjectManager.CheckPlayerName(customizeInfo.CharName, GetSessionDbcLocale(), true);
            if (res != ResponseCodes.CharNameSuccess)
            {
                SendCharCustomize(res, customizeInfo);
                return;
            }

            // check name limitations
            if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationReservedname) && Global.ObjectMgr.IsReservedName(customizeInfo.CharName))
            {
                SendCharCustomize(ResponseCodes.CharNameReserved, customizeInfo);
                return;
            }

            // character with this name already exist
            /// @todo: make async
            ObjectGuid newGuid = ObjectManager.GetPlayerGUIDByName(customizeInfo.CharName);
            if (!newGuid.IsEmpty())
            {
                if (newGuid != customizeInfo.CharGUID)
                {
                    SendCharCustomize(ResponseCodes.CharCreateNameInUse, customizeInfo);
                    return;
                }
            }

            PreparedStatement stmt;
            SQLTransaction trans = new SQLTransaction();
            ulong lowGuid = customizeInfo.CharGUID.GetCounter();

            /// Customize
            {
                stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GENDER_AND_APPEARANCE);

                stmt.AddValue(0, customizeInfo.SexID);
                stmt.AddValue(1, customizeInfo.SkinID);
                stmt.AddValue(2, customizeInfo.FaceID);
                stmt.AddValue(3, customizeInfo.HairStyleID);
                stmt.AddValue(4, customizeInfo.HairColorID);
                stmt.AddValue(5, customizeInfo.FacialHairStyleID);
                stmt.AddValue(6, customizeInfo.CustomDisplay[0]);
                stmt.AddValue(7, customizeInfo.CustomDisplay[1]);
                stmt.AddValue(8, customizeInfo.CustomDisplay[2]);
                stmt.AddValue(9, lowGuid);

                trans.Append(stmt);
            }

            /// Name Change and update atLogin flags
            {
                stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_NAME_AT_LOGIN);
                stmt.AddValue(0, customizeInfo.CharName);
                stmt.AddValue(1, atLoginFlags);
                stmt.AddValue(2, lowGuid);
                trans.Append(stmt);

                stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_DECLINED_NAME);
                stmt.AddValue(0, lowGuid);

                trans.Append(stmt);
            }

            DB.Characters.CommitTransaction(trans);

            Global.WorldMgr.UpdateCharacterInfo(customizeInfo.CharGUID, customizeInfo.CharName, customizeInfo.SexID);

            SendCharCustomize(ResponseCodes.Success, customizeInfo);

            Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}), Character[{2}] ({3}) Customized to: {4}",
                GetAccountId(), GetRemoteAddress(), oldName, customizeInfo.CharGUID.ToString(), customizeInfo.CharName);
        }

        [WorldPacketHandler(ClientOpcodes.SaveEquipmentSet)]
        void HandleEquipmentSetSave(SaveEquipmentSet saveEquipmentSet)
        {
            if (saveEquipmentSet.Set.SetID >= ItemConst.MaxEquipmentSetIndex) // client set slots amount
                return;

            if (saveEquipmentSet.Set.Type > EquipmentSetInfo.EquipmentSetType.Transmog)
                return;

            for (byte i = 0; i < EquipmentSlot.End; ++i)
            {
                if (!Convert.ToBoolean(saveEquipmentSet.Set.IgnoreMask & (1 << i)))
                {
                    if (saveEquipmentSet.Set.Type == EquipmentSetInfo.EquipmentSetType.Equipment)
                    {
                        saveEquipmentSet.Set.Appearances[i] = 0;

                        ObjectGuid itemGuid = saveEquipmentSet.Set.Pieces[i];

                        Item item = _player.GetItemByPos(InventorySlots.Bag0, i);

                        /// cheating check 1 (item equipped but sent empty guid)
                        if (!item && !itemGuid.IsEmpty())
                            return;

                        /// cheating check 2 (sent guid does not match equipped item)
                        if (item && item.GetGUID() != itemGuid)
                            return;
                    }
                    else
                    {
                        saveEquipmentSet.Set.Pieces[i].Clear();
                        if (saveEquipmentSet.Set.Appearances[i] != 0)
                        {
                            if (!CliDB.ItemModifiedAppearanceStorage.ContainsKey(saveEquipmentSet.Set.Appearances[i]))
                                return;

                            var pairValue = GetCollectionMgr().HasItemAppearance((uint)saveEquipmentSet.Set.Appearances[i]);
                            if (!pairValue.Item1)
                                return;
                        }
                    }
                }
                else
                {
                    saveEquipmentSet.Set.Pieces[i].Clear();
                    saveEquipmentSet.Set.Appearances[i] = 0;
                }
            }
            saveEquipmentSet.Set.IgnoreMask &= 0x7FFFF; /// clear invalid bits (i > EQUIPMENT_SLOT_END)
            if (saveEquipmentSet.Set.Type == EquipmentSetInfo.EquipmentSetType.Equipment)
            {
                saveEquipmentSet.Set.Enchants[0] = 0;
                saveEquipmentSet.Set.Enchants[1] = 0;
            }
            else
            {
                var validateIllusion = new Func<uint, bool>(enchantId =>
                {
                    SpellItemEnchantmentRecord illusion = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchantId);
                    if (illusion == null)
                        return false;

                    if (illusion.ItemVisual == 0 || !illusion.Flags.HasAnyFlag(EnchantmentSlotMask.Collectable))
                        return false;

                    PlayerConditionRecord condition = CliDB.PlayerConditionStorage.LookupByKey(illusion.PlayerConditionID);
                    if (condition != null)
                        if (!ConditionManager.IsPlayerMeetingCondition(_player, condition))
                            return false;

                    if (illusion.ScalingClassRestricted > 0 && illusion.ScalingClassRestricted != (byte)_player.GetClass())
                        return false;

                    return true;
                });

                if (saveEquipmentSet.Set.Enchants[0] != 0 && !validateIllusion((uint)saveEquipmentSet.Set.Enchants[0]))
                    return;

                if (saveEquipmentSet.Set.Enchants[1] != 0 && !validateIllusion((uint)saveEquipmentSet.Set.Enchants[1]))
                    return;
            }

            GetPlayer().SetEquipmentSet(saveEquipmentSet.Set);
        }

        [WorldPacketHandler(ClientOpcodes.DeleteEquipmentSet)]
        void HandleDeleteEquipmentSet(DeleteEquipmentSet packet)
        {
            GetPlayer().DeleteEquipmentSet(packet.ID);
        }

        [WorldPacketHandler(ClientOpcodes.UseEquipmentSet)]
        void HandleUseEquipmentSet(UseEquipmentSet useEquipmentSet)
        {
            ObjectGuid ignoredItemGuid = new ObjectGuid(0, 1);
            for (byte i = 0; i < EquipmentSlot.End; ++i)
            {
                Log.outDebug(LogFilter.Player, "{0}: ContainerSlot: {1}, Slot: {2}", useEquipmentSet.Items[i].Item.ToString(), useEquipmentSet.Items[i].ContainerSlot, useEquipmentSet.Items[i].Slot);

                // check if item slot is set to "ignored" (raw value == 1), must not be unequipped then
                if (useEquipmentSet.Items[i].Item == ignoredItemGuid)
                    continue;

                // Only equip weapons in combat
                if (GetPlayer().IsInCombat() && i != EquipmentSlot.MainHand && i != EquipmentSlot.OffHand)
                    continue;

                Item item = GetPlayer().GetItemByGuid(useEquipmentSet.Items[i].Item);

                ushort dstPos = (ushort)(i | (InventorySlots.Bag0 << 8));
                if (!item)
                {
                    Item uItem = GetPlayer().GetItemByPos(InventorySlots.Bag0, i);
                    if (!uItem)
                        continue;

                    List<ItemPosCount> itemPosCount = new List<ItemPosCount>();
                    InventoryResult inventoryResult = GetPlayer().CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, itemPosCount, uItem, false);
                    if (inventoryResult == InventoryResult.Ok)
                    {
                        GetPlayer().RemoveItem(InventorySlots.Bag0, i, true);
                        GetPlayer().StoreItem(itemPosCount, uItem, true);
                    }
                    else
                        GetPlayer().SendEquipError(inventoryResult, uItem);

                    continue;
                }

                if (item.GetPos() == dstPos)
                    continue;

                GetPlayer().SwapItem(item.GetPos(), dstPos);
            }

            UseEquipmentSetResult result = new UseEquipmentSetResult();
            result.GUID = useEquipmentSet.GUID;
            result.Reason = 0; // 4 - equipment swap failed - inventory is full
            SendPacket(result);
        }

        [WorldPacketHandler(ClientOpcodes.CharRaceOrFactionChange, Status = SessionStatus.Authed)]
        void HandleCharRaceOrFactionChange(CharRaceOrFactionChange packet)
        {
            if (!_legitCharacters.Contains(packet.RaceOrFactionChangeInfo.Guid))
            {
                Log.outError(LogFilter.Network, "Account {0}, IP: {1} tried to factionchange character {2}, but it does not belong to their account!",
                    GetAccountId(), GetRemoteAddress(), packet.RaceOrFactionChangeInfo.Guid.ToString());
                KickPlayer();
                return;
            }

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_RACE_OR_FACTION_CHANGE_INFOS);
            stmt.AddValue(0, packet.RaceOrFactionChangeInfo.Guid.GetCounter());

            _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(HandleCharRaceOrFactionChangeCallback, packet.RaceOrFactionChangeInfo));
        }

        void HandleCharRaceOrFactionChangeCallback(CharRaceOrFactionChangeInfo factionChangeInfo, SQLResult result)
        {
            if (result.IsEmpty())
            {
                SendCharFactionChange(ResponseCodes.CharCreateError, factionChangeInfo);
                return;
            }

            // get the players old (at this moment current) race
            CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(factionChangeInfo.Guid);
            if (characterInfo == null)
            {
                SendCharFactionChange(ResponseCodes.CharCreateError, factionChangeInfo);
                return;
            }

            string oldName = characterInfo.Name;
            Race oldRace = characterInfo.RaceID;
            Class playerClass = characterInfo.ClassID;
            byte level = characterInfo.Level;

            if (Global.ObjectMgr.GetPlayerInfo(factionChangeInfo.RaceID, playerClass) == null)
            {
                SendCharFactionChange(ResponseCodes.CharCreateError, factionChangeInfo);
                return;
            }

            AtLoginFlags atLoginFlags = (AtLoginFlags)result.Read<ushort>(0);
            string knownTitlesStr = result.Read<string>(1);

            AtLoginFlags usedLoginFlag = (factionChangeInfo.FactionChange ? AtLoginFlags.ChangeFaction : AtLoginFlags.ChangeRace);
            if (!atLoginFlags.HasAnyFlag(usedLoginFlag))
            {
                SendCharFactionChange(ResponseCodes.CharCreateError, factionChangeInfo);
                return;
            }

            uint newTeamId = Player.TeamIdForRace(factionChangeInfo.RaceID);
            if (newTeamId == TeamId.Neutral)
            {
                SendCharFactionChange(ResponseCodes.CharCreateRestrictedRaceclass, factionChangeInfo);
                return;
            }

            if (factionChangeInfo.FactionChange == (Player.TeamIdForRace(oldRace) == newTeamId))
            {
                SendCharFactionChange(factionChangeInfo.FactionChange ? ResponseCodes.CharCreateCharacterSwapFaction : ResponseCodes.CharCreateCharacterRaceOnly, factionChangeInfo);
                return;
            }

            if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationRacemask))
            {
                uint raceMaskDisabled = WorldConfig.GetUIntValue(WorldCfg.CharacterCreatingDisabledRacemask);
                if (Convert.ToBoolean(1 << ((int)factionChangeInfo.RaceID - 1) & raceMaskDisabled))
                {
                    SendCharFactionChange(ResponseCodes.CharCreateError, factionChangeInfo);
                    return;
                }
            }

            // prevent character rename
            if (WorldConfig.GetBoolValue(WorldCfg.PreventRenameCustomization) && (factionChangeInfo.Name != oldName))
            {
                SendCharFactionChange(ResponseCodes.CharNameFailure, factionChangeInfo);
                return;
            }

            // prevent character rename to invalid name
            if (!ObjectManager.NormalizePlayerName(ref factionChangeInfo.Name))
            {
                SendCharFactionChange(ResponseCodes.CharNameNoName, factionChangeInfo);
                return;
            }

            ResponseCodes res = ObjectManager.CheckPlayerName(factionChangeInfo.Name, GetSessionDbcLocale(), true);
            if (res != ResponseCodes.CharNameSuccess)
            {
                SendCharFactionChange(res, factionChangeInfo);
                return;
            }

            // check name limitations
            if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationReservedname) && Global.ObjectMgr.IsReservedName(factionChangeInfo.Name))
            {
                SendCharFactionChange(ResponseCodes.CharNameReserved, factionChangeInfo);
                return;
            }

            // character with this name already exist
            ObjectGuid newGuid = ObjectManager.GetPlayerGUIDByName(factionChangeInfo.Name);
            if (!newGuid.IsEmpty())
            {
                if (newGuid != factionChangeInfo.Guid)
                {
                    SendCharFactionChange(ResponseCodes.CharCreateNameInUse, factionChangeInfo);
                    return;
                }
            }

            if (Global.ArenaTeamMgr.GetArenaTeamByCaptain(factionChangeInfo.Guid) != null)
            {
                SendCharFactionChange(ResponseCodes.CharCreateCharacterArenaLeader, factionChangeInfo);
                return;
            }

            // All checks are fine, deal with race change now
            ulong lowGuid = factionChangeInfo.Guid.GetCounter();

            PreparedStatement stmt;
            SQLTransaction trans = new SQLTransaction();

            // resurrect the character in case he's dead
            Player.OfflineResurrect(factionChangeInfo.Guid, trans);

            /// Name Change and update atLogin flags
            {
                stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_NAME_AT_LOGIN);
                stmt.AddValue(0, factionChangeInfo.Name);
                stmt.AddValue(1, (ushort)((atLoginFlags | AtLoginFlags.Resurrect) & ~usedLoginFlag));
                stmt.AddValue(2, lowGuid);

                trans.Append(stmt);

                stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_DECLINED_NAME);
                stmt.AddValue(0, lowGuid);

                trans.Append(stmt);
            }

            // Customize
            {
                stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GENDER_AND_APPEARANCE);
                stmt.AddValue(0, factionChangeInfo.SexID);
                stmt.AddValue(1, factionChangeInfo.SkinID);
                stmt.AddValue(2, factionChangeInfo.FaceID);
                stmt.AddValue(3, factionChangeInfo.HairStyleID);
                stmt.AddValue(4, factionChangeInfo.HairColorID);
                stmt.AddValue(5, factionChangeInfo.FacialHairStyleID);
                stmt.AddValue(6, factionChangeInfo.CustomDisplay[0]);
                stmt.AddValue(7, factionChangeInfo.CustomDisplay[1]);
                stmt.AddValue(8, factionChangeInfo.CustomDisplay[2]);
                stmt.AddValue(9, lowGuid);

                trans.Append(stmt);
            }

            // Race Change
            {
                stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_RACE);
                stmt.AddValue(0, factionChangeInfo.RaceID);
                stmt.AddValue(1, lowGuid);

                trans.Append(stmt);
            }

            Global.WorldMgr.UpdateCharacterInfo(factionChangeInfo.Guid, factionChangeInfo.Name, factionChangeInfo.SexID, factionChangeInfo.RaceID);

            if (oldRace != factionChangeInfo.RaceID)
            {
                // Switch Languages
                // delete all languages first
                stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SKILL_LANGUAGES);
                stmt.AddValue(0, lowGuid);
                trans.Append(stmt);

                // Now add them back
                stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_SKILL_LANGUAGE);
                stmt.AddValue(0, lowGuid);

                // Faction specific languages
                if (newTeamId == TeamId.Horde)
                    stmt.AddValue(1, 109);
                else
                    stmt.AddValue(1, 98);

                trans.Append(stmt);

                // Race specific languages
                if (factionChangeInfo.RaceID != Race.Orc && factionChangeInfo.RaceID != Race.Human)
                {
                    stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_SKILL_LANGUAGE);
                    stmt.AddValue(0, lowGuid);

                    switch (factionChangeInfo.RaceID)
                    {
                        case Race.Dwarf:
                            stmt.AddValue(1, 111);
                            break;
                        case Race.Draenei:
                            stmt.AddValue(1, 759);
                            break;
                        case Race.Gnome:
                            stmt.AddValue(1, 313);
                            break;
                        case Race.NightElf:
                            stmt.AddValue(1, 113);
                            break;
                        case Race.Worgen:
                            stmt.AddValue(1, 791);
                            break;
                        case Race.Undead:
                            stmt.AddValue(1, 673);
                            break;
                        case Race.Tauren:
                            stmt.AddValue(1, 115);
                            break;
                        case Race.Troll:
                            stmt.AddValue(1, 315);
                            break;
                        case Race.BloodElf:
                            stmt.AddValue(1, 137);
                            break;
                        case Race.Goblin:
                            stmt.AddValue(1, 792);
                            break;
                    }

                    trans.Append(stmt);
                }

                // Team Conversation
                if (factionChangeInfo.FactionChange)
                {
                    // Delete all Flypaths
                    stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_TAXI_PATH);
                    stmt.AddValue(0, lowGuid);
                    trans.Append(stmt);

                    if (level > 7)
                    {
                        // Update Taxi path
                        // this doesn't seem to be 100% blizzlike... but it can't really be helped.
                        string taximaskstream = "";


                        var factionMask = newTeamId == TeamId.Horde ? CliDB.HordeTaxiNodesMask : CliDB.AllianceTaxiNodesMask;
                        for (byte i = 0; i < PlayerConst.TaxiMaskSize; ++i)
                        {
                            // i = (315 - 1) / 8 = 39
                            // m = 1 << ((315 - 1) % 8) = 4
                            int deathKnightExtraNode = playerClass != Class.Deathknight || i != 39 ? 0 : 4;
                            taximaskstream += (uint)(factionMask[i] | deathKnightExtraNode) + ' ';
                        }

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_TAXIMASK);
                        stmt.AddValue(0, taximaskstream);
                        stmt.AddValue(1, lowGuid);
                        trans.Append(stmt);
                    }

                    /// @todo: make this part asynch
                    if (!WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGuild))
                    {
                        // Reset guild
                        stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_MEMBER);
                        stmt.AddValue(0, lowGuid);

                        result = DB.Characters.Query(stmt);
                        if (!result.IsEmpty())
                        {
                            Guild guild = Global.GuildMgr.GetGuildById(result.Read<ulong>(0));
                            if (guild)
                                guild.DeleteMember(factionChangeInfo.Guid, false, false, true);
                        }

                        Player.LeaveAllArenaTeams(factionChangeInfo.Guid);
                    }

                    if (!HasPermission(RBACPermissions.TwoSideAddFriend))
                    {
                        // Delete Friend List
                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SOCIAL_BY_GUID);
                        stmt.AddValue(0, lowGuid);
                        trans.Append(stmt);

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SOCIAL_BY_FRIEND);
                        stmt.AddValue(0, lowGuid);
                        trans.Append(stmt);
                    }

                    // Reset homebind and position
                    stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PLAYER_HOMEBIND);
                    stmt.AddValue(0, lowGuid);
                    trans.Append(stmt);

                    stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PLAYER_HOMEBIND);
                    stmt.AddValue(0, lowGuid);

                    WorldLocation loc;
                    ushort zoneId = 0;
                    if (newTeamId == TeamId.Alliance)
                    {
                        loc = new WorldLocation(0, -8867.68f, 673.373f, 97.9034f, 0.0f);
                        zoneId = 1519;
                    }
                    else
                    {
                        loc = new WorldLocation(1, 1633.33f, -4439.11f, 15.7588f, 0.0f);
                        zoneId = 1637;
                    }

                    stmt.AddValue(1, loc.GetMapId());
                    stmt.AddValue(2, zoneId);
                    stmt.AddValue(3, loc.GetPositionX());
                    stmt.AddValue(4, loc.GetPositionY());
                    stmt.AddValue(5, loc.GetPositionZ());
                    trans.Append(stmt);

                    Player.SavePositionInDB(loc, zoneId, factionChangeInfo.Guid, trans);

                    // Achievement conversion
                    foreach (var it in Global.ObjectMgr.FactionChangeAchievements)
                    {
                        uint achiev_alliance = it.Key;
                        uint achiev_horde = it.Value;

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_ACHIEVEMENT_BY_ACHIEVEMENT);
                        stmt.AddValue(0, (ushort)(newTeamId == TeamId.Alliance ? achiev_alliance : achiev_horde));
                        stmt.AddValue(1, lowGuid);
                        trans.Append(stmt);

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_ACHIEVEMENT);
                        stmt.AddValue(0, (ushort)(newTeamId == TeamId.Alliance ? achiev_alliance : achiev_horde));
                        stmt.AddValue(1, (ushort)(newTeamId == TeamId.Alliance ? achiev_horde : achiev_alliance));
                        stmt.AddValue(2, lowGuid);
                        trans.Append(stmt);
                    }

                    // Item conversion
                    foreach (var it in Global.ObjectMgr.FactionChangeItems)
                    {
                        uint item_alliance = it.Key;
                        uint item_horde = it.Value;

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_INVENTORY_FACTION_CHANGE);
                        stmt.AddValue(0, (newTeamId == TeamId.Alliance ? item_alliance : item_horde));
                        stmt.AddValue(1, (newTeamId == TeamId.Alliance ? item_horde : item_alliance));
                        stmt.AddValue(2, lowGuid);
                        trans.Append(stmt);
                    }

                    // Delete all current quests
                    stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_QUESTSTATUS);
                    stmt.AddValue(0, lowGuid);
                    trans.Append(stmt);

                    // Quest conversion
                    foreach (var it in Global.ObjectMgr.FactionChangeQuests)
                    {
                        uint quest_alliance = it.Key;
                        uint quest_horde = it.Value;

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST);
                        stmt.AddValue(0, lowGuid);
                        stmt.AddValue(1, (newTeamId == TeamId.Alliance ? quest_alliance : quest_horde));
                        trans.Append(stmt);

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_QUESTSTATUS_REWARDED_FACTION_CHANGE);
                        stmt.AddValue(0, (newTeamId == TeamId.Alliance ? quest_alliance : quest_horde));
                        stmt.AddValue(1, (newTeamId == TeamId.Alliance ? quest_horde : quest_alliance));
                        stmt.AddValue(2, lowGuid);
                        trans.Append(stmt);
                    }

                    // Mark all rewarded quests as "active" (will count for completed quests achievements)
                    stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_QUESTSTATUS_REWARDED_ACTIVE);
                    stmt.AddValue(0, lowGuid);
                    trans.Append(stmt);

                    // Disable all old-faction specific quests
                    {
                        var questTemplates = Global.ObjectMgr.GetQuestTemplates();
                        foreach (Quest quest in questTemplates.Values)
                        {
                            uint newRaceMask = (uint)(newTeamId == TeamId.Alliance ? Race.RaceMaskAlliance : Race.RaceMaskHorde);
                            if (quest.AllowableRaces != -1 && !Convert.ToBoolean(quest.AllowableRaces & newRaceMask))
                            {
                                stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_QUESTSTATUS_REWARDED_ACTIVE_BY_QUEST);
                                stmt.AddValue(0, lowGuid);
                                stmt.AddValue(1, quest.Id);
                                trans.Append(stmt);
                            }
                        }
                    }

                    // Spell conversion
                    foreach (var it in Global.ObjectMgr.FactionChangeSpells)
                    {
                        uint spell_alliance = it.Key;
                        uint spell_horde = it.Value;

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SPELL_BY_SPELL);
                        stmt.AddValue(0, (newTeamId == TeamId.Alliance ? spell_alliance : spell_horde));
                        stmt.AddValue(1, lowGuid);
                        trans.Append(stmt);

                        stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_SPELL_FACTION_CHANGE);
                        stmt.AddValue(0, (newTeamId == TeamId.Alliance ? spell_alliance : spell_horde));
                        stmt.AddValue(1, (newTeamId == TeamId.Alliance ? spell_horde : spell_alliance));
                        stmt.AddValue(2, lowGuid);
                        trans.Append(stmt);
                    }

                    // Reputation conversion
                    foreach (var it in Global.ObjectMgr.FactionChangeReputation)
                    {
                        uint reputation_alliance = it.Key;
                        uint reputation_horde = it.Value;
                        uint newReputation = (newTeamId == TeamId.Alliance) ? reputation_alliance : reputation_horde;
                        uint oldReputation = (newTeamId == TeamId.Alliance) ? reputation_horde : reputation_alliance;

                        // select old standing set in db
                        stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_REP_BY_FACTION);
                        stmt.AddValue(0, oldReputation);
                        stmt.AddValue(1, lowGuid);

                        result = DB.Characters.Query(stmt);
                        if (!result.IsEmpty())
                        {
                            int oldDBRep = result.Read<int>(0);
                            FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(oldReputation);

                            // old base reputation
                            int oldBaseRep = ReputationMgr.GetBaseReputationOf(factionEntry, oldRace, playerClass);

                            // new base reputation
                            int newBaseRep = ReputationMgr.GetBaseReputationOf(CliDB.FactionStorage.LookupByKey(newReputation), factionChangeInfo.RaceID, playerClass);

                            // final reputation shouldnt change
                            int FinalRep = oldDBRep + oldBaseRep;
                            int newDBRep = FinalRep - newBaseRep;

                            stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_REP_BY_FACTION);
                            stmt.AddValue(0, newReputation);
                            stmt.AddValue(1, lowGuid);
                            trans.Append(stmt);

                            stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_REP_FACTION_CHANGE);
                            stmt.AddValue(0, (ushort)newReputation);
                            stmt.AddValue(1, newDBRep);
                            stmt.AddValue(2, (ushort)oldReputation);
                            stmt.AddValue(3, lowGuid);
                            trans.Append(stmt);
                        }
                    }

                    // Title conversion
                    if (!string.IsNullOrEmpty(knownTitlesStr))
                    {
                        uint ktcount = PlayerConst.KnowTitlesSize * 2;
                        uint[] knownTitles = new uint[ktcount];

                        var tokens = new StringArray(knownTitlesStr, ' ');
                        if (tokens.Length != ktcount)
                        {
                            SendCharFactionChange(ResponseCodes.CharCreateError, factionChangeInfo);
                            return;
                        }

                        for (int index = 0; index < ktcount; ++index)
                            knownTitles[index] = uint.Parse(tokens[index]);

                        foreach (var it in Global.ObjectMgr.FactionChangeTitles)
                        {
                            uint title_alliance = it.Key;
                            uint title_horde = it.Value;

                            CharTitlesRecord atitleInfo = CliDB.CharTitlesStorage.LookupByKey(title_alliance);
                            CharTitlesRecord htitleInfo = CliDB.CharTitlesStorage.LookupByKey(title_horde);
                            // new team
                            if (newTeamId == TeamId.Alliance)
                            {
                                uint maskID = htitleInfo.MaskID;
                                uint index = maskID / 32;
                                uint old_flag = (uint)(1 << (int)(maskID % 32));
                                uint new_flag = (uint)(1 << (int)(atitleInfo.MaskID % 32));
                                if (Convert.ToBoolean(knownTitles[index] & old_flag))
                                {
                                    knownTitles[index] &= ~old_flag;
                                    // use index of the new title
                                    knownTitles[atitleInfo.MaskID / 32] |= new_flag;
                                }
                            }
                            else
                            {
                                uint maskID = atitleInfo.MaskID;
                                uint index = maskID / 32;
                                uint old_flag = (uint)(1 << (int)(maskID % 32));
                                uint new_flag = (uint)(1 << (int)(htitleInfo.MaskID % 32));
                                if (Convert.ToBoolean(knownTitles[index] & old_flag))
                                {
                                    knownTitles[index] &= ~old_flag;
                                    // use index of the new title
                                    knownTitles[htitleInfo.MaskID / 32] |= new_flag;
                                }
                            }

                            string ss = "";
                            for (uint index = 0; index < ktcount; ++index)
                                ss += knownTitles[index] + ' ';

                            stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_TITLES_FACTION_CHANGE);
                            stmt.AddValue(0, ss);
                            stmt.AddValue(1, lowGuid);
                            trans.Append(stmt);

                            // unset any currently chosen title
                            stmt = DB.Characters.GetPreparedStatement(CharStatements.RES_CHAR_TITLES_FACTION_CHANGE);
                            stmt.AddValue(0, lowGuid);
                            trans.Append(stmt);
                        }
                    }
                }
            }

            DB.Characters.CommitTransaction(trans);

            Log.outDebug(LogFilter.Player, "{0} (IP: {1}) changed race from {2} to {3}", GetPlayerInfo(), GetRemoteAddress(), oldRace, factionChangeInfo.RaceID);

            SendCharFactionChange(ResponseCodes.Success, factionChangeInfo);
        }

        [WorldPacketHandler(ClientOpcodes.OpeningCinematic)]
        void HandleOpeningCinematic(OpeningCinematic packet)
        {
            // Only players that has not yet gained any experience can use this
            if (GetPlayer().GetUInt32Value(PlayerFields.Xp) != 0)
                return;

            ChrClassesRecord classEntry = CliDB.ChrClassesStorage.LookupByKey(GetPlayer().GetClass());
            if (classEntry != null)
            {
                ChrRacesRecord raceEntry = CliDB.ChrRacesStorage.LookupByKey(GetPlayer().GetRace());
                if (classEntry.CinematicSequenceID != 0)
                    GetPlayer().SendCinematicStart(classEntry.CinematicSequenceID);
                else if (raceEntry != null)
                    GetPlayer().SendCinematicStart(raceEntry.CinematicSequenceID);
            }
        }

        [WorldPacketHandler(ClientOpcodes.GetUndeleteCharacterCooldownStatus, Status = SessionStatus.Authed)]
        void HandleGetUndeleteCooldownStatus(GetUndeleteCharacterCooldownStatus getCooldown)
        {
            PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_LAST_CHAR_UNDELETE);
            stmt.AddValue(0, GetBattlenetAccountId());

            _queryProcessor.AddQuery(DB.Login.AsyncQuery(stmt).WithCallback(HandleUndeleteCooldownStatusCallback));
        }

        void HandleUndeleteCooldownStatusCallback(SQLResult result)
        {
            uint cooldown = 0;
            uint maxCooldown = WorldConfig.GetUIntValue(WorldCfg.FeatureSystemCharacterUndeleteCooldown);
            if (!result.IsEmpty())
            {
                uint lastUndelete = result.Read<uint>(0);
                uint now = (uint)Time.UnixTime;
                if (lastUndelete + maxCooldown > now)
                    cooldown = Math.Max(0, lastUndelete + maxCooldown - now);
            }

            SendUndeleteCooldownStatusResponse(cooldown, maxCooldown);
        }

        [WorldPacketHandler(ClientOpcodes.UndeleteCharacter, Status = SessionStatus.Authed)]
        void HandleCharUndelete(UndeleteCharacter undeleteCharacter)
        {
            if (!WorldConfig.GetBoolValue(WorldCfg.FeatureSystemCharacterUndeleteEnabled))
            {
                SendUndeleteCharacterResponse(CharacterUndeleteResult.Disabled, undeleteCharacter.UndeleteInfo);
                return;
            }

            PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_LAST_CHAR_UNDELETE);
            stmt.AddValue(0, GetBattlenetAccountId());

            CharacterUndeleteInfo undeleteInfo = undeleteCharacter.UndeleteInfo;
            _queryProcessor.AddQuery(DB.Login.AsyncQuery(stmt).WithChainingCallback((queryCallback, result) =>
            {
                if (!result.IsEmpty())
                {
                    uint lastUndelete = result.Read<uint>(0);
                    uint maxCooldown = WorldConfig.GetUIntValue(WorldCfg.FeatureSystemCharacterUndeleteCooldown);
                    if (lastUndelete != 0 && (lastUndelete + maxCooldown > Time.UnixTime))
                    {
                        SendUndeleteCharacterResponse(CharacterUndeleteResult.Cooldown, undeleteInfo);
                        return;
                    }
                }

                stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_DEL_INFO_BY_GUID);
                stmt.AddValue(0, undeleteInfo.CharacterGuid.GetCounter());
                queryCallback.SetNextQuery(DB.Characters.AsyncQuery(stmt));
            }).WithChainingCallback((queryCallback, result) =>
            {
                if (result.IsEmpty())
                {
                    SendUndeleteCharacterResponse(CharacterUndeleteResult.CharCreate, undeleteInfo);
                    return;
                }

                undeleteInfo.Name = result.Read<string>(1);
                uint account = result.Read<uint>(2);
                if (account != GetAccountId())
                {
                    SendUndeleteCharacterResponse(CharacterUndeleteResult.Unknown, undeleteInfo);
                    return;
                }

                stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHECK_NAME);
                stmt.AddValue(0, undeleteInfo.Name);
                queryCallback.SetNextQuery(DB.Characters.AsyncQuery(stmt));
            }).WithChainingCallback((queryCallback, result) =>
            {
                if (!result.IsEmpty())
                {
                    SendUndeleteCharacterResponse(CharacterUndeleteResult.NameTakenByThisAccount, undeleteInfo);
                    return;
                }

                /// @todo: add more safety checks
                /// * max char count per account
                /// * max death knight count
                /// * max demon hunter count
                /// * team violation

                stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_SUM_CHARS);
                stmt.AddValue(0, GetAccountId());
                queryCallback.SetNextQuery(DB.Characters.AsyncQuery(stmt));
            }).WithCallback(result =>
            {
                if (!result.IsEmpty())
                {
                    if (result.Read<ulong>(0) >= WorldConfig.GetUIntValue(WorldCfg.CharactersPerRealm)) // SQL's COUNT() returns uint64 but it will always be less than uint8.Max
                    {
                        SendUndeleteCharacterResponse(CharacterUndeleteResult.CharCreate, undeleteInfo);
                        return;
                    }
                }

                stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_RESTORE_DELETE_INFO);
                stmt.AddValue(0, undeleteInfo.Name);
                stmt.AddValue(1, GetAccountId());
                stmt.AddValue(2, undeleteInfo.CharacterGuid.GetCounter());
                DB.Characters.Execute(stmt);

                stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_LAST_CHAR_UNDELETE);
                stmt.AddValue(0, GetBattlenetAccountId());
                DB.Login.Execute(stmt);

                Global.WorldMgr.UpdateCharacterInfoDeleted(undeleteInfo.CharacterGuid, false, undeleteInfo.Name);

                SendUndeleteCharacterResponse(CharacterUndeleteResult.Ok, undeleteInfo);
            }));
        }

        [WorldPacketHandler(ClientOpcodes.RepopRequest)]
        void HandleRepopRequest(RepopRequest packet)
        {
            if (GetPlayer().IsAlive() || GetPlayer().HasFlag(PlayerFields.Flags, PlayerFlags.Ghost))
                return;

            if (GetPlayer().HasAuraType(AuraType.PreventResurrection))
                return; // silently return, client should display the error by itself

            // the world update order is sessions, players, creatures
            // the netcode runs in parallel with all of these
            // creatures can kill players
            // so if the server is lagging enough the player can
            // release spirit after he's killed but before he is updated
            if (GetPlayer().getDeathState() == DeathState.JustDied)
            {
                Log.outDebug(LogFilter.Network, "HandleRepopRequestOpcode: got request after player {0} ({1}) was killed and before he was updated",
                    GetPlayer().GetName(), GetPlayer().GetGUID().ToString());
                GetPlayer().KillPlayer();
            }

            //this is spirit release confirm?
            GetPlayer().RemovePet(null, PetSaveMode.NotInSlot, true);
            GetPlayer().BuildPlayerRepop();
            GetPlayer().RepopAtGraveyard();
        }

        [WorldPacketHandler(ClientOpcodes.ClientPortGraveyard)]
        void HandlePortGraveyard(PortGraveyard packet)
        {
            if (GetPlayer().IsAlive() || !GetPlayer().HasFlag(PlayerFields.Flags, PlayerFlags.Ghost))
                return;
            GetPlayer().RepopAtGraveyard();
        }

        [WorldPacketHandler(ClientOpcodes.RequestCemeteryList, Processing = PacketProcessing.Inplace)]
        void HandleRequestCemeteryList(RequestCemeteryList requestCemeteryList)
        {
            uint zoneId = GetPlayer().GetZoneId();
            uint team = (uint)GetPlayer().GetTeam();

            List<int> graveyardIds = new List<int>();
            var range = Global.ObjectMgr.GraveYardStorage.LookupByKey(zoneId);

            for (int i = 0; i < range.Count && graveyardIds.Count < 16; ++i) // client max
            {
                var gYard = range[i];
                if (gYard.team == 0 || gYard.team == team)
                    graveyardIds.Add(i);
            }

            if (graveyardIds.Empty())
            {
                Log.outDebug(LogFilter.Network, "No graveyards found for zone {0} for player {1} (team {2}) in CMSG_REQUEST_CEMETERY_LIST",
                    zoneId, m_GUIDLow, team);
                return;
            }

            RequestCemeteryListResponse packet = new RequestCemeteryListResponse();
            packet.IsGossipTriggered = false;

            foreach (uint id in graveyardIds)
                packet.CemeteryID.Add(id);

            SendPacket(packet);
        }

        [WorldPacketHandler(ClientOpcodes.ReclaimCorpse)]
        void HandleReclaimCorpse(ReclaimCorpse packet)
        {
            if (GetPlayer().IsAlive())
                return;

            // do not allow corpse reclaim in arena
            if (GetPlayer().InArena())
                return;

            // body not released yet
            if (!GetPlayer().HasFlag(PlayerFields.Flags, PlayerFlags.Ghost))
                return;

            Corpse corpse = GetPlayer().GetCorpse();
            if (!corpse)
                return;

            // prevent resurrect before 30-sec delay after body release not finished
            if ((corpse.GetGhostTime() + GetPlayer().GetCorpseReclaimDelay(corpse.GetCorpseType() == CorpseType.ResurrectablePVP)) > Time.UnixTime)
                return;

            if (!corpse.IsWithinDistInMap(GetPlayer(), 39, true))
                return;

            // resurrect
            GetPlayer().ResurrectPlayer(GetPlayer().InBattleground() ? 1.0f : 0.5f);

            // spawn bones
            GetPlayer().SpawnCorpseBones();
        }

        [WorldPacketHandler(ClientOpcodes.ResurrectResponse)]
        void HandleResurrectResponse(ResurrectResponse packet)
        {
            if (GetPlayer().IsAlive())
                return;

            if (packet.Response != 0) // Accept = 0 Decline = 1 Timeout = 2
            {
                GetPlayer().ClearResurrectRequestData(); // reject
                return;
            }

            if (!GetPlayer().IsRessurectRequestedBy(packet.Resurrecter))
                return;

            Player ressPlayer = Global.ObjAccessor.GetPlayer(GetPlayer(), packet.Resurrecter);
            if (ressPlayer)
            {
                InstanceScript instance = ressPlayer.GetInstanceScript();
                if (instance != null)
                {
                    if (instance.IsEncounterInProgress())
                    {
                        if (instance.GetCombatResurrectionCharges() == 0)
                            return;
                        else
                            instance.UseCombatResurrection();
                    }
                }
            }

            GetPlayer().ResurrectUsingRequestData();
        }

        [WorldPacketHandler(ClientOpcodes.StandStateChange)]
        void HandleStandStateChange(StandStateChange packet)
        {
            GetPlayer().SetStandState(packet.StandState);
        }

        void SendCharCreate(ResponseCodes result)
        {
            CreateChar response = new CreateChar();
            response.Code = result;

            SendPacket(response);
        }

        void SendCharDelete(ResponseCodes result)
        {
            DeleteChar response = new DeleteChar();
            response.Code = result;

            SendPacket(response);
        }

        void SendCharRename(ResponseCodes result, CharacterRenameInfo renameInfo)
        {
            CharacterRenameResult packet = new CharacterRenameResult();
            packet.Result = result;
            packet.Name = renameInfo.NewName;
            if (result == ResponseCodes.Success)
                packet.Guid.Set(renameInfo.Guid);

            SendPacket(packet);
        }

        void SendCharCustomize(ResponseCodes result, CharCustomizeInfo customizeInfo)
        {
            if (result == ResponseCodes.Success)
            {
                CharCustomizeResponse response = new CharCustomizeResponse(customizeInfo);
                SendPacket(response);
            }
            else
            {
                CharCustomizeFailed failed = new CharCustomizeFailed();
                failed.Result = (byte)result;
                failed.CharGUID = customizeInfo.CharGUID;
                SendPacket(failed);
            }
        }

        void SendCharFactionChange(ResponseCodes result, CharRaceOrFactionChangeInfo factionChangeInfo)
        {
            CharFactionChangeResult packet = new CharFactionChangeResult();
            packet.Result = result;
            packet.Guid = factionChangeInfo.Guid;

            if (result == ResponseCodes.Success)
            {
                packet.Display.HasValue = true;
                packet.Display.Value.Name = factionChangeInfo.Name;
                packet.Display.Value.SexID = (byte)factionChangeInfo.SexID;
                packet.Display.Value.SkinID = factionChangeInfo.SkinID;
                packet.Display.Value.HairColorID = factionChangeInfo.HairColorID;
                packet.Display.Value.HairStyleID = factionChangeInfo.HairStyleID;
                packet.Display.Value.FacialHairStyleID = factionChangeInfo.FacialHairStyleID;
                packet.Display.Value.FaceID = factionChangeInfo.FaceID;
                packet.Display.Value.RaceID = (byte)factionChangeInfo.RaceID;
                packet.Display.Value.CustomDisplay = factionChangeInfo.CustomDisplay;
            }

            SendPacket(packet);
        }

        void SendSetPlayerDeclinedNamesResult(DeclinedNameResult result, ObjectGuid guid)
        {
            SetPlayerDeclinedNamesResult packet = new SetPlayerDeclinedNamesResult();
            packet.ResultCode = result;
            packet.Player = guid;

            SendPacket(packet);
        }

        void SendUndeleteCooldownStatusResponse(uint currentCooldown, uint maxCooldown)
        {
            UndeleteCooldownStatusResponse response = new UndeleteCooldownStatusResponse();
            response.OnCooldown = (currentCooldown > 0);
            response.MaxCooldown = maxCooldown;
            response.CurrentCooldown = currentCooldown;

            SendPacket(response);
        }

        void SendUndeleteCharacterResponse(CharacterUndeleteResult result, CharacterUndeleteInfo undeleteInfo)
        {
            UndeleteCharacterResponse response = new UndeleteCharacterResponse();
            response.UndeleteInfo = undeleteInfo;
            response.Result = result;

            SendPacket(response);
        }
    }

    public class LoginQueryHolder : SQLQueryHolder<PlayerLoginQueryLoad>
    {
        public LoginQueryHolder(uint accountId, ObjectGuid guid)
        {
            m_accountId = accountId;
            m_guid = guid;
        }

        public void Initialize()
        {
            ulong lowGuid = m_guid.GetCounter();

            PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.From, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GROUP_MEMBER);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Group, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_INSTANCE);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.BoundInstances, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_AURAS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Auras, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_AURA_EFFECTS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.AuraEffects, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_SPELL);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Spells, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.QuestStatus, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_OBJECTIVES);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.QuestStatusObjectives, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_DAILY);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.DailyQuestStatus, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_WEEKLY);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.WeeklyQuestStatus, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_MONTHLY);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.MonthlyQuestStatus, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_SEASONAL);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.SeasonalQuestStatus, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_REPUTATION);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Reputation, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_INVENTORY);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Inventory, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ITEM_INSTANCE_ARTIFACT);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Artifacts, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_VOID_STORAGE);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.VoidStorage, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ACTIONS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Actions, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_MAILCOUNT);
            stmt.AddValue(0, lowGuid);
            stmt.AddValue(1, Time.UnixTime);
            SetQuery(PlayerLoginQueryLoad.MailCount, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_MAILDATE);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.MailDate, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_SOCIALLIST);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.SocialList, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_HOMEBIND);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.HomeBind, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_SPELLCOOLDOWNS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.SpellCooldowns, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_SPELL_CHARGES);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.SpellCharges, stmt);

            if (WorldConfig.GetBoolValue(WorldCfg.DeclinedNamesUsed))
            {
                stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_DECLINEDNAMES);
                stmt.AddValue(0, lowGuid);
                SetQuery(PlayerLoginQueryLoad.DeclinedNames, stmt);
            }

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_MEMBER);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Guild, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ARENAINFO);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.ArenaInfo, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ACHIEVEMENTS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Achievements, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_CRITERIAPROGRESS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.CriteriaProgress, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_EQUIPMENTSETS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.EquipmentSets, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_TRANSMOG_OUTFITS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.TransmogOutfits, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_CUF_PROFILES);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.CufProfiles, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_BGDATA);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.BgData, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GLYPHS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Glyphs, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_TALENTS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Talents, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PLAYER_ACCOUNT_DATA);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.AccountData, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_SKILLS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Skills, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_RANDOMBG);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.RandomBg, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_BANNED);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Banned, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUSREW);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.QuestStatusRew, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ACCOUNT_INSTANCELOCKTIMES);
            stmt.AddValue(0, m_accountId);
            SetQuery(PlayerLoginQueryLoad.InstanceLockTimes, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PLAYER_CURRENCY);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Currency, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CORPSE_LOCATION);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.CorpseLocation, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GARRISON);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.Garrison, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GARRISON_BLUEPRINTS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.GarrisonBlueprints, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GARRISON_BUILDINGS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.GarrisonBuildings, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GARRISON_FOLLOWERS);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.GarrisonFollowers, stmt);

            stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GARRISON_FOLLOWER_ABILITIES);
            stmt.AddValue(0, lowGuid);
            SetQuery(PlayerLoginQueryLoad.GarrisonFollowerAbilities, stmt);
        }

        public ObjectGuid GetGuid() { return m_guid; }

        uint GetAccountId() { return m_accountId; }

        uint m_accountId;
        ObjectGuid m_guid;
    }

    // used at player loading query list preparing, and later result selection
    public enum PlayerLoginQueryLoad
    {
        From,
        Group,
        BoundInstances,
        Auras,
        AuraEffects,
        Spells,
        QuestStatus,
        QuestStatusObjectives,
        DailyQuestStatus,
        Reputation,
        Inventory,
        Artifacts,
        Actions,
        MailCount,
        MailDate,
        SocialList,
        HomeBind,
        SpellCooldowns,
        SpellCharges,
        DeclinedNames,
        Guild,
        ArenaInfo,
        Achievements,
        CriteriaProgress,
        EquipmentSets,
        TransmogOutfits,
        BgData,
        Glyphs,
        Talents,
        AccountData,
        Skills,
        WeeklyQuestStatus,
        RandomBg,
        Banned,
        QuestStatusRew,
        InstanceLockTimes,
        SeasonalQuestStatus,
        MonthlyQuestStatus,
        VoidStorage,
        Currency,
        CufProfiles,
        CorpseLocation,
        Garrison,
        GarrisonBlueprints,
        GarrisonBuildings,
        GarrisonFollowers,
        GarrisonFollowerAbilities,
        Max
    }
}
