diff --git a/src/game/Opcodes.cpp b/src/game/Opcodes.cpp new file mode 100644 index 000000000..7302895f4 --- /dev/null +++ b/src/game/Opcodes.cpp @@ -0,0 +1,1416 @@ +/** + * This code is part of MaNGOS. Contributor & Copyright details are in AUTHORS/THANKS. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** \file + \ingroup u2w +*/ + +#include "Opcodes.h" +#include "WorldSession.h" + +static void DefineOpcode(uint16 opcode, const char* name, SessionStatus status, PacketProcessing packetProcessing, void (WorldSession::*handler)(WorldPacket& recvPacket)) +{ + opcodeTable[opcode].name = name; + opcodeTable[opcode].status = status; + opcodeTable[opcode].packetProcessing = packetProcessing; + opcodeTable[opcode].handler = handler; +} + +#define OPCODE( name, status, packetProcessing, handler ) DefineOpcode( name, #name, status, packetProcessing, handler ) + +/// Correspondence between opcodes and their names +OpcodeHandler opcodeTable[MAX_OPCODE_TABLE_SIZE]; + +void InitializeOpcodes() +{ + for(uint16 i = 0; i < MAX_OPCODE_TABLE_SIZE; ++i) + DefineOpcode(i, "UNKNOWN", STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); + + OPCODE(MSG_WOW_CONNECTION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_EarlyProccess ); + OPCODE(SMSG_AUTH_CHALLENGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_AUTH_SESSION, STATUS_NEVER, PROCESS_THREADUNSAFE, &WorldSession::Handle_EarlyProccess ); + OPCODE(SMSG_AUTH_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(MSG_NULL_ACTION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_BOOTME, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_DBLOOKUP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_DBLOOKUP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_QUERY_OBJECT_POSITION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_QUERY_OBJECT_POSITION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_QUERY_OBJECT_ROTATION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_QUERY_OBJECT_ROTATION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_WORLD_TELEPORT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleWorldTeleportOpcode ); + OPCODE(CMSG_TELEPORT_TO_UNIT, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_ZONE_MAP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_ZONE_MAP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_DEBUG_CHANGECELLZONE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_MOVE_CHARACTER_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_MOVE_CHARACTER_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_RECHARGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_LEARN_SPELL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_CREATEMONSTER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_DESTROYMONSTER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_CREATEITEM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_CREATEGAMEOBJECT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_CHECK_FOR_BOTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_MAKEMONSTERATTACKGUID, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_BOT_DETECTED2, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_FORCEACTION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_FORCEACTIONONOTHER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_FORCEACTIONSHOW, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_FORCEACTIONSHOW, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_PETGODMODE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_PETGODMODE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_REFER_A_FRIEND_EXPIRED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_WEATHER_SPEED_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_UNDRESSPLAYER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_BEASTMASTER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GODMODE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_GODMODE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CHEAT_SETMONEY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_LEVEL_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_PET_LEVEL_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_WORLDSTATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_COOLDOWN_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_USE_SKILL_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_FLAG_QUEST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_FLAG_QUEST_FINISH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_CLEAR_QUEST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SEND_EVENT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_DEBUG_AISTATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_DEBUG_AISTATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_DISABLE_PVP_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_ADVANCE_SPAWN_TIME, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_DESTRUCTIBLE_BUILDING_DAMAGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_AUTH_SRP6_BEGIN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_AUTH_SRP6_PROOF, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_AUTH_SRP6_RECODE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_CHAR_CREATE, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleCharCreateOpcode ); + OPCODE(CMSG_CHAR_ENUM, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleCharEnumOpcode ); + OPCODE(CMSG_CHAR_DELETE, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleCharDeleteOpcode ); + //OPCODE(SMSG_AUTH_SRP6_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CHAR_CREATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CHAR_ENUM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CHAR_DELETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PLAYER_LOGIN, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandlePlayerLoginOpcode ); + OPCODE(SMSG_NEW_WORLD, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_TRANSFER_PENDING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_TRANSFER_ABORTED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CHARACTER_LOGIN_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOGIN_SETTIMESPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_GAMETIME_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GAMETIME_SET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_GAMETIME_SET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GAMESPEED_SET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_GAMESPEED_SET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SERVERTIME, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_SERVERTIME, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_PLAYER_LOGOUT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePlayerLogoutOpcode ); + OPCODE(CMSG_LOGOUT_REQUEST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLogoutRequestOpcode ); + OPCODE(SMSG_LOGOUT_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOGOUT_COMPLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_LOGOUT_CANCEL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLogoutCancelOpcode ); + OPCODE(SMSG_LOGOUT_CANCEL_ACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_NAME_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNameQueryOpcode ); + OPCODE(SMSG_NAME_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PET_NAME_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetNameQueryOpcode ); + OPCODE(SMSG_PET_NAME_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GUILD_QUERY, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildQueryOpcode ); + OPCODE(SMSG_GUILD_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PAGE_TEXT_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePageTextQueryOpcode ); + OPCODE(SMSG_PAGE_TEXT_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_QUEST_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestQueryOpcode ); + OPCODE(SMSG_QUEST_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GAMEOBJECT_QUERY, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleGameObjectQueryOpcode ); + OPCODE(SMSG_GAMEOBJECT_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CREATURE_QUERY, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleCreatureQueryOpcode ); + OPCODE(SMSG_CREATURE_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_WHO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleWhoOpcode ); + OPCODE(SMSG_WHO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_WHOIS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleWhoisOpcode ); + OPCODE(SMSG_WHOIS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CONTACT_LIST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleContactListOpcode ); + OPCODE(SMSG_CONTACT_LIST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_FRIEND_STATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_ADD_FRIEND, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAddFriendOpcode ); + OPCODE(CMSG_DEL_FRIEND, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleDelFriendOpcode ); + OPCODE(CMSG_SET_CONTACT_NOTES, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetContactNotesOpcode ); + OPCODE(CMSG_ADD_IGNORE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAddIgnoreOpcode ); + OPCODE(CMSG_DEL_IGNORE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleDelIgnoreOpcode ); + OPCODE(CMSG_GROUP_INVITE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGroupInviteOpcode ); + OPCODE(SMSG_GROUP_INVITE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GROUP_CANCEL, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_GROUP_CANCEL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GROUP_INVITE_RESPONSE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGroupInviteResponseOpcode ); + OPCODE(SMSG_GROUP_DECLINE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GROUP_UNINVITE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGroupUninviteOpcode ); + OPCODE(CMSG_GROUP_UNINVITE_GUID, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGroupUninviteGuidOpcode ); + OPCODE(SMSG_GROUP_UNINVITE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GROUP_SET_LEADER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGroupSetLeaderOpcode ); + OPCODE(SMSG_GROUP_SET_LEADER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_LOOT_METHOD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLootMethodOpcode ); + OPCODE(CMSG_GROUP_DISBAND, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGroupDisbandOpcode ); + OPCODE(SMSG_GROUP_DESTROYED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_GROUP_LIST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PARTY_MEMBER_STATS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PARTY_COMMAND_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(UMSG_UPDATE_GROUP_MEMBERS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GUILD_CREATE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildCreateOpcode ); + OPCODE(CMSG_GUILD_INVITE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildInviteOpcode ); + OPCODE(SMSG_GUILD_INVITE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GUILD_ACCEPT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildAcceptOpcode ); + OPCODE(CMSG_GUILD_DECLINE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildDeclineOpcode ); + OPCODE(SMSG_GUILD_DECLINE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GUILD_INFO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildInfoOpcode ); + //OPCODE(SMSG_GUILD_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GUILD_ROSTER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildRosterOpcode ); + OPCODE(SMSG_GUILD_ROSTER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GUILD_PROMOTE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildPromoteOpcode ); + OPCODE(CMSG_GUILD_DEMOTE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildDemoteOpcode ); + OPCODE(CMSG_GUILD_SET_RANK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildSetRankOpcode ); + OPCODE(CMSG_GUILD_SWITCH_RANK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildSwitchRankOpcode ); + OPCODE(CMSG_GUILD_LEAVE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildLeaveOpcode ); + OPCODE(CMSG_GUILD_REMOVE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildRemoveOpcode ); + OPCODE(CMSG_GUILD_DISBAND, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildDisbandOpcode ); + OPCODE(CMSG_GUILD_LEADER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildLeaderOpcode ); + OPCODE(CMSG_GUILD_MOTD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildMOTDOpcode ); + OPCODE(SMSG_GUILD_EVENT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_GUILD_COMMAND_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GUILD_AUTO_DECLINE_TOGGLE, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleGuildAutoDeclineToggleOpcode ); + OPCODE(CMSG_GUILD_AUTO_DECLINE, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleGuildDeclineOpcode ); + OPCODE(CMSG_GUILD_QUERY_RANKS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildQueryRanksOpcode ); + OPCODE(SMSG_GUILD_QUERY_RANKS_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(UMSG_UPDATE_GUILD, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_MESSAGECHAT_ADDON_BATTLEGROUND, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAddonMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_ADDON_GUILD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAddonMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_ADDON_OFFICER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAddonMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_ADDON_PARTY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAddonMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_ADDON_RAID, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAddonMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_ADDON_WHISPER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAddonMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_AFK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_BATTLEGROUND, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_CHANNEL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_DND, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_EMOTE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_GUILD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_OFFICER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_PARTY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_RAID, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_RAID_WARNING, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_SAY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_WHISPER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(CMSG_MESSAGECHAT_YELL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMessagechatOpcode ); + OPCODE(SMSG_MESSAGECHAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_JOIN_CHANNEL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleJoinChannelOpcode ); + OPCODE(CMSG_LEAVE_CHANNEL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLeaveChannelOpcode ); + OPCODE(SMSG_CHANNEL_NOTIFY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CHANNEL_LIST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelListOpcode ); + OPCODE(SMSG_CHANNEL_LIST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CHANNEL_PASSWORD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelPasswordOpcode ); + OPCODE(CMSG_CHANNEL_SET_OWNER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelSetOwnerOpcode ); + OPCODE(CMSG_CHANNEL_OWNER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelOwnerOpcode ); + OPCODE(CMSG_CHANNEL_MODERATOR, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelModeratorOpcode ); + OPCODE(CMSG_CHANNEL_UNMODERATOR, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelUnmoderatorOpcode ); + OPCODE(CMSG_CHANNEL_MUTE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelMuteOpcode ); + OPCODE(CMSG_CHANNEL_UNMUTE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelUnmuteOpcode ); + OPCODE(CMSG_CHANNEL_INVITE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelInviteOpcode ); + OPCODE(CMSG_CHANNEL_KICK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelKickOpcode ); + OPCODE(CMSG_CHANNEL_BAN, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelBanOpcode ); + OPCODE(CMSG_CHANNEL_UNBAN, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelUnbanOpcode ); + OPCODE(CMSG_CHANNEL_ANNOUNCEMENTS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelAnnouncementsOpcode); + //OPCODE(CMSG_CHANNEL_MODERATE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelModerateOpcode ); + OPCODE(SMSG_UPDATE_OBJECT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_DESTROY_OBJECT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_USE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleUseItemOpcode ); + OPCODE(CMSG_OPEN_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleOpenItemOpcode ); + OPCODE(CMSG_READ_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleReadItemOpcode ); + OPCODE(SMSG_READ_ITEM_OK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_READ_ITEM_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ITEM_COOLDOWN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GAMEOBJ_USE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGameObjectUseOpcode ); + //OPCODE(CMSG_DESTROY_ITEMS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_GAMEOBJECT_CUSTOM_ANIM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_AREATRIGGER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAreaTriggerOpcode ); + OPCODE(CMSG_MOVE_START_FORWARD, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_START_BACKWARD, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_STOP, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_START_STRAFE_LEFT, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_START_STRAFE_RIGHT, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_STOP_STRAFE, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_JUMP, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_START_TURN_LEFT, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_START_TURN_RIGHT, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_STOP_TURN, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_START_PITCH_UP, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_START_PITCH_DOWN, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_STOP_PITCH, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_SET_RUN_MODE, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_SET_WALK_MODE, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + //OPCODE(MSG_MOVE_TOGGLE_LOGGING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MOVE_TELEPORT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_TELEPORT_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_MOVE_TELEPORT_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMoveTeleportAckOpcode ); + //OPCODE(MSG_MOVE_TOGGLE_FALL_LOGGING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_MOVE_FALL_LAND, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_START_SWIM, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_STOP_SWIM, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + //OPCODE(MSG_MOVE_SET_RUN_SPEED_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MOVE_SET_RUN_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_SET_RUN_BACK_SPEED_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MOVE_SET_RUN_BACK_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_SET_WALK_SPEED_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MOVE_SET_WALK_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_SET_SWIM_SPEED_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MOVE_SET_SWIM_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_SET_SWIM_BACK_SPEED_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MOVE_SET_SWIM_BACK_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_SET_ALL_SPEED_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_SET_TURN_RATE_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MOVE_SET_TURN_RATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_TOGGLE_COLLISION_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_MOVE_SET_FACING, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_SET_PITCH, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(MSG_MOVE_WORLDPORT_ACK, STATUS_TRANSFER, PROCESS_THREADUNSAFE, &WorldSession::HandleMoveWorldportAckOpcode ); + OPCODE(SMSG_MONSTER_MOVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_MOVE_WATER_WALK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_MOVE_LAND_WALK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_MOVE_CHARM_PORT_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_MOVE_SET_RAW_POSITION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_FORCE_RUN_SPEED_CHANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_FORCE_RUN_SPEED_CHANGE_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleForceSpeedChangeAckOpcodes); + //OPCODE(SMSG_FORCE_RUN_BACK_SPEED_CHANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_FORCE_RUN_BACK_SPEED_CHANGE_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleForceSpeedChangeAckOpcodes); + //OPCODE(SMSG_FORCE_SWIM_SPEED_CHANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_FORCE_SWIM_SPEED_CHANGE_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleForceSpeedChangeAckOpcodes); + OPCODE(SMSG_FORCE_MOVE_ROOT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_FORCE_MOVE_ROOT_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMoveRootAck ); + OPCODE(SMSG_FORCE_MOVE_UNROOT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_FORCE_MOVE_UNROOT_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMoveUnRootAck ); + //OPCODE(MSG_MOVE_ROOT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_UNROOT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(MSG_MOVE_HEARTBEAT, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(SMSG_MOVE_KNOCK_BACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_MOVE_KNOCK_BACK_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMoveKnockBackAck ); + OPCODE(SMSG_MOVE_UPDATE_KNOCK_BACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_MOVE_FEATHER_FALL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_MOVE_NORMAL_FALL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_MOVE_SET_HOVER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_MOVE_UNSET_HOVER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_MOVE_HOVER_ACK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMoveHoverAck ); + //OPCODE(MSG_MOVE_HOVER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_SPLINE_MOVE_SET_WALK_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_SET_RUN_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_SET_RUN_BACK_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_SET_SWIM_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_SET_SWIM_BACK_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_SET_TURN_RATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_SET_FLIGHT_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_SET_FLIGHT_BACK_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_SET_PITCH_RATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_TRIGGER_CINEMATIC_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_OPENING_CINEMATIC, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_TRIGGER_CINEMATIC, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_NEXT_CINEMATIC_CAMERA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNextCinematicCamera ); + OPCODE(CMSG_COMPLETE_CINEMATIC, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCompleteCinematic ); + OPCODE(SMSG_TUTORIAL_FLAGS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_TUTORIAL_FLAG, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTutorialFlagOpcode ); + OPCODE(CMSG_TUTORIAL_CLEAR, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTutorialClearOpcode ); + OPCODE(CMSG_TUTORIAL_RESET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTutorialResetOpcode ); + OPCODE(CMSG_STANDSTATECHANGE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleStandStateChangeOpcode ); + OPCODE(CMSG_EMOTE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleEmoteOpcode ); + OPCODE(SMSG_EMOTE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_TEXT_EMOTE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTextEmoteOpcode ); + OPCODE(SMSG_TEXT_EMOTE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_AUTOEQUIP_GROUND_ITEM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_AUTOSTORE_GROUND_ITEM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_AUTOSTORE_LOOT_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAutostoreLootItemOpcode ); + OPCODE(CMSG_LOOT_CURRENCY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAutostoreLootItemOpcode ); + //OPCODE(CMSG_STORE_LOOT_IN_SLOT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_AUTOEQUIP_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAutoEquipItemOpcode ); + OPCODE(CMSG_AUTOSTORE_BAG_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAutoStoreBagItemOpcode ); + OPCODE(CMSG_SWAP_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSwapItem ); + OPCODE(CMSG_SWAP_INV_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSwapInvItemOpcode ); + OPCODE(CMSG_SPLIT_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSplitItemOpcode ); + OPCODE(CMSG_AUTOEQUIP_ITEM_SLOT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAutoEquipItemSlotOpcode ); + //OPCODE(CMSG_UNCLAIM_LICENSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_DESTROYITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleDestroyItemOpcode ); + OPCODE(SMSG_INVENTORY_CHANGE_FAILURE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_OPEN_CONTAINER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_INSPECT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleInspectOpcode ); + OPCODE(SMSG_INSPECT_RESULTS_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_INITIATE_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleInitiateTradeOpcode ); + OPCODE(CMSG_BEGIN_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBeginTradeOpcode ); + OPCODE(CMSG_BUSY_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBusyTradeOpcode ); + OPCODE(CMSG_IGNORE_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleIgnoreTradeOpcode ); + OPCODE(CMSG_ACCEPT_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAcceptTradeOpcode ); + OPCODE(CMSG_UNACCEPT_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleUnacceptTradeOpcode ); + OPCODE(CMSG_CANCEL_TRADE, STATUS_LOGGEDIN_OR_RECENTLY_LOGGEDOUT, PROCESS_THREADUNSAFE, &WorldSession::HandleCancelTradeOpcode); + OPCODE(CMSG_SET_TRADE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetTradeItemOpcode ); + OPCODE(CMSG_CLEAR_TRADE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleClearTradeItemOpcode ); + OPCODE(CMSG_SET_TRADE_GOLD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetTradeGoldOpcode ); + OPCODE(SMSG_TRADE_STATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_TRADE_STATUS_EXTENDED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_INITIALIZE_FACTIONS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SET_FACTION_VISIBLE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SET_FACTION_STANDING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SET_FACTION_ATWAR, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetFactionAtWarOpcode ); + //OPCODE(CMSG_SET_FACTION_CHEAT, STATUS_NEVER, PROCESS_THREADUNSAFE, &WorldSession::Handle_Deprecated ); + OPCODE(SMSG_SET_PROFICIENCY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SET_ACTION_BUTTON, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetActionButtonOpcode ); + OPCODE(SMSG_ACTION_BUTTONS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_INITIAL_SPELLS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LEARNED_SPELL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SUPERCEDED_SPELL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_NEW_SPELL_SLOT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_CAST_SPELL, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleCastSpellOpcode ); + OPCODE(CMSG_CANCEL_CAST, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleCancelCastOpcode ); + OPCODE(SMSG_CAST_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELL_START, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELL_GO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELL_FAILURE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELL_COOLDOWN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_COOLDOWN_EVENT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CANCEL_AURA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCancelAuraOpcode ); + OPCODE(SMSG_EQUIPMENT_SET_ID, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PET_CAST_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CHANNEL_START, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_CHANNEL_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_CANCEL_CHANNELLING, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCancelChanneling ); + OPCODE(SMSG_AI_REACTION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SET_SELECTION, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleSetSelectionOpcode ); + OPCODE(CMSG_EQUIPMENT_SET_DELETE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleEquipmentSetDeleteOpcode ); + //OPCODE(CMSG_INSTANCE_LOCK_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_DEBUG_PASSIVE_AURA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_ATTACKSWING, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleAttackSwingOpcode ); + OPCODE(CMSG_ATTACKSTOP, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleAttackStopOpcode ); + OPCODE(SMSG_ATTACKSTART, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ATTACKSTOP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ATTACKSWING_NOTINRANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ATTACKSWING_BADFACING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PENDING_RAID_LOCK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ATTACKSWING_DEADTARGET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ATTACKSWING_CANT_ATTACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ATTACKERSTATEUPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_BATTLEFIELD_PORT_DENIED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_PERFORM_ACTION_SET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_RESUME_CAST_BAR, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CANCEL_COMBAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELLBREAKLOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELLHEALLOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELLENERGIZELOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_BREAK_TARGET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SAVE_PLAYER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SETDEATHBINDPOINT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_BINDPOINTUPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GETDEATHBINDZONE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_BINDZONEREPLY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PLAYERBOUND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CLIENT_CONTROL_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_REPOP_REQUEST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRepopRequestOpcode ); + OPCODE(SMSG_RESURRECT_REQUEST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_RESURRECT_RESPONSE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleResurrectResponseOpcode ); + OPCODE(CMSG_RETURN_TO_GRAVEYARD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleReturnToGraveyard ); + OPCODE(CMSG_LOOT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLootOpcode ); + OPCODE(CMSG_LOOT_MONEY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLootMoneyOpcode ); + OPCODE(CMSG_LOOT_RELEASE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLootReleaseOpcode ); + OPCODE(SMSG_LOOT_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOOT_RELEASE_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOOT_REMOVED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOOT_CURRENCY_REMOVED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOOT_MONEY_NOTIFY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOOT_ITEM_NOTIFY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOOT_CLEAR_MONEY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ITEM_PUSH_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_DUEL_REQUESTED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_DUEL_OUTOFBOUNDS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_DUEL_INBOUNDS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_DUEL_COMPLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_DUEL_WINNER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_DUEL_ACCEPTED, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleDuelAcceptedOpcode ); + OPCODE(CMSG_DUEL_CANCELLED, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleDuelCancelledOpcode ); + OPCODE(SMSG_MOUNTRESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_DISMOUNTRESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_REMOVED_FROM_PVP_QUEUE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_MOUNTSPECIAL_ANIM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMountSpecialAnimOpcode ); + OPCODE(SMSG_MOUNTSPECIAL_ANIM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PET_TAME_FAILURE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PET_SET_ACTION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetSetAction ); + OPCODE(CMSG_PET_ACTION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetAction ); + OPCODE(CMSG_PET_ABANDON, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetAbandon ); + OPCODE(CMSG_PET_RENAME, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetRename ); + OPCODE(SMSG_PET_NAME_INVALID, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PET_SPELLS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PET_MODE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GOSSIP_HELLO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGossipHelloOpcode ); + OPCODE(CMSG_GOSSIP_SELECT_OPTION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGossipSelectOptionOpcode ); + OPCODE(SMSG_GOSSIP_MESSAGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_GOSSIP_COMPLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_NPC_TEXT_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNpcTextQueryOpcode ); + OPCODE(SMSG_NPC_TEXT_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_NPC_WONT_TALK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_QUESTGIVER_STATUS_QUERY, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleQuestgiverStatusQueryOpcode); + OPCODE(SMSG_QUESTGIVER_STATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_QUESTGIVER_HELLO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestgiverHelloOpcode ); + OPCODE(SMSG_QUESTGIVER_QUEST_LIST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_QUESTGIVER_QUERY_QUEST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestgiverQueryQuestOpcode); + //OPCODE(CMSG_QUESTGIVER_QUEST_AUTOLAUNCH, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestgiverQuestAutoLaunch ); + OPCODE(SMSG_QUESTGIVER_QUEST_DETAILS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); // + + OPCODE(CMSG_QUESTGIVER_ACCEPT_QUEST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestgiverAcceptQuestOpcode); + OPCODE(CMSG_QUESTGIVER_COMPLETE_QUEST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestgiverCompleteQuest ); + OPCODE(SMSG_QUESTGIVER_REQUEST_ITEMS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_QUESTGIVER_REQUEST_REWARD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestgiverRequestRewardOpcode); + OPCODE(SMSG_QUESTGIVER_OFFER_REWARD, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_QUESTGIVER_CHOOSE_REWARD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestgiverChooseRewardOpcode); + OPCODE(SMSG_QUESTGIVER_QUEST_INVALID, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_QUESTGIVER_CANCEL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestgiverCancel ); + OPCODE(SMSG_QUESTGIVER_QUEST_COMPLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_QUESTGIVER_QUEST_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_QUESTLOG_SWAP_QUEST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestLogSwapQuest ); + OPCODE(CMSG_QUESTLOG_REMOVE_QUEST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestLogRemoveQuest ); + OPCODE(SMSG_QUESTLOG_FULL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_QUESTUPDATE_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_QUESTUPDATE_FAILEDTIMER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_QUESTUPDATE_COMPLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_QUESTUPDATE_ADD_KILL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_QUESTUPDATE_ADD_ITEM_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_QUEST_CONFIRM_ACCEPT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestConfirmAccept ); + OPCODE(SMSG_QUEST_CONFIRM_ACCEPT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PUSHQUESTTOPARTY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePushQuestToParty ); + OPCODE(CMSG_LIST_INVENTORY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleListInventoryOpcode ); + OPCODE(SMSG_LIST_INVENTORY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_LOAD_SCREEN, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleLoadScreenOpcode ); + OPCODE(CMSG_SELL_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSellItemOpcode ); + OPCODE(SMSG_SELL_ITEM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_BUY_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBuyItemOpcode ); + OPCODE(SMSG_BUY_ITEM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_BUY_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_TAXICLEARALLNODES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_TAXIENABLEALLNODES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_TAXISHOWNODES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_SHOWTAXINODES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_TAXINODE_STATUS_QUERY, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleTaxiNodeStatusQueryOpcode ); + OPCODE(SMSG_TAXINODE_STATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_TAXIQUERYAVAILABLENODES, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleTaxiQueryAvailableNodes ); + OPCODE(CMSG_ACTIVATETAXI, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleActivateTaxiOpcode ); + OPCODE(SMSG_ACTIVATETAXIREPLY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_NEW_TAXI_PATH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_TRAINER_LIST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTrainerListOpcode ); + OPCODE(SMSG_TRAINER_LIST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_TRAINER_BUY_SPELL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTrainerBuySpellOpcode ); + OPCODE(SMSG_TRAINER_SERVICE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_TRAINER_BUY_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_BINDER_ACTIVATE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBinderActivateOpcode ); + OPCODE(SMSG_PLAYERBINDERROR, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_BANKER_ACTIVATE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBankerActivateOpcode ); + OPCODE(SMSG_SHOW_BANK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_BUY_BANK_SLOT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBuyBankSlotOpcode ); + OPCODE(CMSG_PETITION_SHOWLIST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetitionShowListOpcode ); + OPCODE(SMSG_PETITION_SHOWLIST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PETITION_BUY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetitionBuyOpcode ); + OPCODE(CMSG_PETITION_SHOW_SIGNATURES, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetitionShowSignOpcode ); + OPCODE(SMSG_PETITION_SHOW_SIGNATURES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PETITION_SIGN, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetitionSignOpcode ); + OPCODE(SMSG_PETITION_SIGN_RESULTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(MSG_PETITION_DECLINE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetitionDeclineOpcode ); + OPCODE(CMSG_OFFER_PETITION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleOfferPetitionOpcode ); + OPCODE(CMSG_TURN_IN_PETITION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTurnInPetitionOpcode ); + OPCODE(SMSG_TURN_IN_PETITION_RESULTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PETITION_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetitionQueryOpcode ); + OPCODE(SMSG_PETITION_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_FISH_NOT_HOOKED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_FISH_ESCAPED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_BUG, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBugOpcode ); + OPCODE(SMSG_NOTIFICATION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PLAYED_TIME, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePlayedTime ); + OPCODE(SMSG_PLAYED_TIME, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_QUERY_TIME, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQueryTimeOpcode ); + OPCODE(SMSG_QUERY_TIME_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOG_XPGAIN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_AURACASTLOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_RECLAIM_CORPSE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleReclaimCorpseOpcode ); + OPCODE(CMSG_WRAP_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleWrapItemOpcode ); + OPCODE(SMSG_LEVELUP_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(MSG_MINIMAP_PING, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMinimapPingOpcode ); + //OPCODE(SMSG_RESISTLOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ENCHANTMENTLOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SET_SKILL_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_START_MIRROR_TIMER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PAUSE_MIRROR_TIMER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_STOP_MIRROR_TIMER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_EarlyProccess ); + OPCODE(SMSG_PONG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CLEAR_COOLDOWNS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_GAMEOBJECT_PAGETEXT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SETSHEATHED, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleSetSheathedOpcode ); + OPCODE(SMSG_COOLDOWN_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELL_DELAYED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_QUEST_POI_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestPOIQueryOpcode ); + OPCODE(SMSG_QUEST_POI_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GHOST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_INVIS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_INVALID_PROMOTION_CODE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(MSG_GM_BIND_OTHER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_GM_SUMMON, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_ITEM_TIME_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ITEM_ENCHANT_TIME_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(MSG_GM_SHOWLABEL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_PET_CAST_SPELL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetCastSpellOpcode ); + OPCODE(MSG_SAVE_GUILD_EMBLEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSaveGuildEmblemOpcode ); + OPCODE(MSG_TABARDVENDOR_ACTIVATE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTabardVendorActivateOpcode); + OPCODE(SMSG_PLAY_SPELL_VISUAL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_ZONEUPDATE, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleZoneUpdateOpcode ); + OPCODE(SMSG_PARTYKILLLOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_COMPRESSED_UPDATE_OBJECT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_EXPLORATION_EXPERIENCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GM_SET_SECURITY_GROUP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_NUKE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(MSG_RANDOM_ROLL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRandomRollOpcode ); + OPCODE(SMSG_ENVIRONMENTALDAMAGELOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CHANGEPLAYER_DIFFICULTY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_RWHOIS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_LFG_PLAYER_REWARD, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_LFG_TELEPORT_DENIED, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_UNLEARN_SPELL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_UNLEARN_SKILL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleUnlearnSkillOpcode ); + OPCODE(SMSG_REMOVED_SPELL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_DECHARGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_GMTICKET_CREATE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGMTicketCreateOpcode ); + OPCODE(SMSG_GMTICKET_CREATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GMTICKET_UPDATETEXT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGMTicketUpdateTextOpcode ); + OPCODE(SMSG_GMTICKET_UPDATETEXT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ACCOUNT_DATA_TIMES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_REQUEST_ACCOUNT_DATA, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleRequestAccountData ); + OPCODE(CMSG_UPDATE_ACCOUNT_DATA, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleUpdateAccountData ); + OPCODE(SMSG_UPDATE_ACCOUNT_DATA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CLEAR_FAR_SIGHT_IMMEDIATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CHANGEPLAYER_DIFFICULTY_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GM_TEACH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_CREATE_ITEM_TARGET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_GMTICKET_GETTICKET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGMTicketGetTicketOpcode ); + OPCODE(SMSG_GMTICKET_GETTICKET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_UNLEARN_TALENTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_INSTANCE_ENCOUNTER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_GAMEOBJECT_DESPAWN_ANIM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(MSG_CORPSE_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCorpseQueryOpcode ); + OPCODE(CMSG_GMTICKET_DELETETICKET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGMTicketDeleteTicketOpcode); + OPCODE(SMSG_GMTICKET_DELETETICKET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CHAT_WRONG_FACTION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GMTICKET_SYSTEMSTATUS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGMTicketSystemStatusOpcode); + OPCODE(SMSG_GMTICKET_SYSTEMSTATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SPIRIT_HEALER_ACTIVATE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSpiritHealerActivateOpcode); + //OPCODE(CMSG_SET_STAT_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_QUEST_FORCE_REMOVED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SKILL_BUY_STEP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SKILL_BUY_RANK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_XP_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_SPIRIT_HEALER_CONFIRM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CHARACTER_POINT_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_GOSSIP_POI, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CHAT_IGNORED, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChatIgnoredOpcode ); + //OPCODE(CMSG_GM_VISION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SERVER_COMMAND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_SILENCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_REVEALTO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_RESURRECT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_SUMMONMOB, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_MOVECORPSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_FREEZE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_UBERINVIS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_REQUEST_PLAYER_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_GM_PLAYER_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_GUILD_RANK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildRankOpcode ); + OPCODE(CMSG_GUILD_ADD_RANK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildAddRankOpcode ); + OPCODE(CMSG_GUILD_DEL_RANK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildDelRankOpcode ); + OPCODE(CMSG_GUILD_SET_NOTE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildSetNoteOpcode ); + OPCODE(SMSG_LOGIN_VERIFY_WORLD, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CLEAR_EXPLORATION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_SEND_MAIL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSendMail ); + OPCODE(SMSG_SEND_MAIL_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GET_MAIL_LIST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGetMailList ); + OPCODE(SMSG_MAIL_LIST_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_BATTLEFIELD_LIST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlefieldListOpcode ); + OPCODE(SMSG_BATTLEFIELD_LIST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_BATTLEFIELD_JOIN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_FORCE_SET_VEHICLE_REC_ID, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SET_VEHICLE_REC_ID_ACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_TAXICLEARNODE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_TAXIENABLENODE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_ITEM_TEXT_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleItemTextQuery ); + OPCODE(SMSG_ITEM_TEXT_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_MAIL_TAKE_MONEY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMailTakeMoney ); + OPCODE(CMSG_MAIL_TAKE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMailTakeItem ); + OPCODE(CMSG_MAIL_MARK_AS_READ, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMailMarkAsRead ); + OPCODE(CMSG_MAIL_RETURN_TO_SENDER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMailReturnToSender ); + OPCODE(CMSG_MAIL_DELETE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMailDelete ); + OPCODE(CMSG_MAIL_CREATE_TEXT_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleMailCreateTextItem ); + OPCODE(SMSG_SPELLLOGMISS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELLLOGEXECUTE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_DEBUGAURAPROC, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PERIODICAURALOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELLDAMAGESHIELD, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELLNONMELEEDAMAGELOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_LEARN_TALENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnTalentOpcode ); + //OPCODE(SMSG_RESURRECT_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_TOGGLE_PVP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTogglePvP ); + OPCODE(SMSG_ZONE_UNDER_ATTACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(MSG_AUCTION_HELLO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionHelloOpcode ); + OPCODE(CMSG_AUCTION_SELL_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionSellItem ); + OPCODE(CMSG_AUCTION_REMOVE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionRemoveItem ); + OPCODE(CMSG_AUCTION_LIST_ITEMS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionListItems ); + OPCODE(CMSG_AUCTION_LIST_OWNER_ITEMS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionListOwnerItems ); + OPCODE(CMSG_AUCTION_PLACE_BID, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionPlaceBid ); + OPCODE(SMSG_AUCTION_COMMAND_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_AUCTION_LIST_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_AUCTION_OWNER_LIST_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_AUCTION_BIDDER_NOTIFICATION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_AUCTION_OWNER_NOTIFICATION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_PROCRESIST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_COMBAT_EVENT_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_DISPEL_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELLOGDAMAGE_IMMUNE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_AUCTION_LIST_BIDDER_ITEMS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionListBidderItems ); + OPCODE(SMSG_AUCTION_BIDDER_LIST_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SET_FLAT_SPELL_MODIFIER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SET_PCT_SPELL_MODIFIER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SET_AMMO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetAmmoOpcode ); + OPCODE(SMSG_CORPSE_RECLAIM_DELAY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SET_ACTIVE_MOVER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetActiveMoverOpcode ); + OPCODE(CMSG_PET_CANCEL_AURA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetCancelAuraOpcode ); + //OPCODE(CMSG_PLAYER_AI_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_CANCEL_AUTO_REPEAT_SPELL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCancelAutoRepeatSpellOpcode); + //OPCODE(MSG_GM_ACCOUNT_ONLINE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(MSG_LIST_STABLED_PETS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleListStabledPetsOpcode ); + //OPCODE(CMSG_STABLE_PET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleStablePet ); + //OPCODE(CMSG_UNSTABLE_PET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleUnstablePet ); + //OPCODE(CMSG_BUY_STABLE_SLOT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBuyStableSlot ); + OPCODE(SMSG_STABLE_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_STABLE_REVIVE_PET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleStableRevivePet ); + //OPCODE(CMSG_STABLE_SWAP_PET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleStableSwapPet ); + OPCODE(MSG_QUEST_PUSH_RESULT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestPushResult ); + OPCODE(SMSG_PLAY_MUSIC, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PLAY_OBJECT_SOUND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PLAY_ONE_SHOT_ANIM_KIT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_REQUEST_PET_INFO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRequestPetInfoOpcode ); + OPCODE(CMSG_FAR_SIGHT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleFarSightOpcode ); + OPCODE(SMSG_SPELLDISPELLOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_DAMAGE_CALC_LOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_ENABLE_DAMAGE_LOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_GROUP_CHANGE_SUB_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGroupChangeSubGroupOpcode ); + OPCODE(CMSG_REQUEST_PARTY_MEMBER_STATS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRequestPartyMemberStatsOpcode); + //OPCODE(CMSG_GROUP_SWAP_SUB_GROUP, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_RESET_FACTION_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_AUTOSTORE_BANK_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAutoStoreBankItemOpcode ); + OPCODE(CMSG_AUTOBANK_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAutoBankItemOpcode ); + OPCODE(MSG_QUERY_NEXT_MAIL_TIME, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQueryNextMailTime ); + OPCODE(SMSG_RECEIVED_MAIL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_RAID_GROUP_ONLY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SET_DURABILITY_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_PVP_RANK_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_ADD_PVP_MEDAL_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_DEL_PVP_MEDAL_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_PVP_TITLE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_PVP_CREDIT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_AUCTION_REMOVED_NOTIFICATION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GROUP_RAID_CONVERT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGroupRaidConvertOpcode ); + OPCODE(CMSG_GROUP_ASSISTANT_LEADER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGroupAssistantLeaderOpcode); + OPCODE(CMSG_BUYBACK_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBuybackItem ); + OPCODE(SMSG_SERVER_MESSAGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SET_SAVED_INSTANCE_EXTEND, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_LFG_OFFER_CONTINUE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_TEST_DROP_RATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_TEST_DROP_RATE_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_LFG_GET_STATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_SHOW_MAILBOX, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_RESET_RANGED_COMBAT_TIMER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CHAT_NOT_IN_PARTY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GMTICKETSYSTEM_TOGGLE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_CANCEL_GROWTH_AURA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCancelGrowthAuraOpcode ); + OPCODE(SMSG_CANCEL_AUTO_REPEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_STANDSTATE_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOOT_ALL_PASSED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOOT_ROLL_WON, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_LOOT_ROLL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLootRoll ); + OPCODE(SMSG_LOOT_START_ROLL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOOT_ROLL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_LOOT_MASTER_GIVE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLootMasterGiveOpcode ); + OPCODE(SMSG_LOOT_MASTER_LIST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SET_FORCED_REACTIONS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELL_FAILED_OTHER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_GAMEOBJECT_RESET_STATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_REPAIR_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRepairItemOpcode ); + OPCODE(SMSG_CHAT_PLAYER_NOT_FOUND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(MSG_TALENT_WIPE_CONFIRM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTalentWipeConfirmOpcode ); + OPCODE(SMSG_SUMMON_REQUEST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SUMMON_RESPONSE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSummonResponseOpcode ); + //OPCODE(MSG_DEV_SHOWLABEL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MONSTER_MOVE_TRANSPORT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_PET_BROKEN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(MSG_MOVE_FEATHER_FALL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_WATER_WALK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SERVER_BROADCAST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_SELF_RES, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSelfResOpcode ); + //OPCODE(SMSG_FEIGN_DEATH_RESISTED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_RUN_SCRIPT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_SCRIPT_MESSAGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_DUEL_COUNTDOWN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_AREA_TRIGGER_MESSAGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SHOWING_HELM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleShowingHelmOpcode ); + OPCODE(CMSG_SHOWING_CLOAK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleShowingCloakOpcode ); + //OPCODE(SMSG_ROLE_CHOSEN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_PLAYER_SKINNED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_DURABILITY_DAMAGE_DEATH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SET_EXPLORATION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_SET_ACTIONBAR_TOGGLES, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleSetActionBarTogglesOpcode ); + //OPCODE(UMSG_DELETE_GUILD_CHARTER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(MSG_PETITION_RENAME, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetitionRenameOpcode ); + OPCODE(SMSG_INIT_WORLD_STATES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_UPDATE_WORLD_STATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PET_ACTION_FEEDBACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CHAR_RENAME, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleCharRenameOpcode ); + OPCODE(SMSG_CHAR_RENAME, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_MOVE_SPLINE_DONE, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMoveSplineDoneOpcode ); + OPCODE(CMSG_MOVE_FALL_RESET, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(SMSG_INSTANCE_SAVE_CREATED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_RAID_INSTANCE_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_REQUEST_RAID_INFO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRequestRaidInfoOpcode ); + OPCODE(CMSG_MOVE_TIME_SKIPPED, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleMoveTimeSkippedOpcode ); + OPCODE(CMSG_MOVE_FEATHER_FALL_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleFeatherFallAck ); + OPCODE(CMSG_MOVE_WATER_WALK_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMoveWaterWalkAck ); + OPCODE(CMSG_MOVE_NOT_ACTIVE_MOVER, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMoveNotActiveMoverOpcode ); + OPCODE(SMSG_PLAY_SOUND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_BATTLEFIELD_STATUS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlefieldStatusOpcode ); + OPCODE(SMSG_BATTLEFIELD_STATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_BATTLEFIELD_STATUS_ACTIVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_BATTLEFIELD_STATUS_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_BATTLEFIELD_STATUS_NEEDCONFIRMATION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_BATTLEFIELD_STATUS_QUEUED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_BATTLEFIELD_STATUS_WAITFORGROUPS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_BATTLEFIELD_PORT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattleFieldPortOpcode ); + OPCODE(CMSG_INSPECT_HONOR_STATS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleInspectHonorStatsOpcode ); + OPCODE(SMSG_INSPECT_HONOR_STATS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_BATTLEMASTER_HELLO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlemasterHelloOpcode ); + //OPCODE(CMSG_MOVE_START_SWIM_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_MOVE_STOP_SWIM_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_FORCE_WALK_SPEED_CHANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_FORCE_WALK_SPEED_CHANGE_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleForceSpeedChangeAckOpcodes); + //OPCODE(SMSG_FORCE_SWIM_BACK_SPEED_CHANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_FORCE_SWIM_BACK_SPEED_CHANGE_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleForceSpeedChangeAckOpcodes); + //OPCODE(SMSG_FORCE_TURN_RATE_CHANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_FORCE_TURN_RATE_CHANGE_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleForceSpeedChangeAckOpcodes); + OPCODE(CMSG_PVP_LOG_DATA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePVPLogDataOpcode ); + OPCODE(SMSG_PVP_LOG_DATA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_LEAVE_BATTLEFIELD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLeaveBattlefieldOpcode ); + OPCODE(CMSG_AREA_SPIRIT_HEALER_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAreaSpiritHealerQueryOpcode); + OPCODE(CMSG_AREA_SPIRIT_HEALER_QUEUE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAreaSpiritHealerQueueOpcode); + OPCODE(SMSG_AREA_SPIRIT_HEALER_TIME, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GM_UNTEACH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_WARDEN_DATA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_WARDEN_DATA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleWardenDataOpcode ); + OPCODE(CMSG_BATTLEGROUND_PLAYER_POSITIONS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattleGroundPlayerPositionsOpcode); + OPCODE(SMSG_BATTLEGROUND_PLAYER_POSITIONS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PET_STOP_ATTACK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetStopAttack ); + OPCODE(SMSG_BINDER_CONFIRM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_BATTLEGROUND_PLAYER_JOINED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_BATTLEGROUND_PLAYER_LEFT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_BATTLEMASTER_JOIN, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlemasterJoinOpcode ); + OPCODE(SMSG_ADDON_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_PET_UNLEARN, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetUnlearnOpcode ); + //OPCODE(SMSG_PET_UNLEARN_CONFIRM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PARTY_MEMBER_STATS_FULL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PET_SPELL_AUTOCAST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetSpellAutocastOpcode ); + OPCODE(SMSG_WEATHER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_PLAY_TIME_WARNING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_MINIGAME_SETUP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_MINIGAME_STATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_MINIGAME_MOVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_MINIGAME_MOVE_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_RAID_INSTANCE_MESSAGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_COMPRESSED_MOVES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GUILD_INFO_TEXT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildChangeInfoTextOpcode ); + OPCODE(SMSG_CHAT_RESTRICTED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SPLINE_SET_RUN_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SPLINE_SET_RUN_BACK_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SPLINE_SET_SWIM_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SPLINE_SET_WALK_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SPLINE_SET_SWIM_BACK_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SPLINE_SET_TURN_RATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_UNROOT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SPLINE_MOVE_FEATHER_FALL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SPLINE_MOVE_NORMAL_FALL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_SET_HOVER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_UNSET_HOVER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_WATER_WALK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_LAND_WALK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_START_SWIM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SPLINE_MOVE_STOP_SWIM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_SET_RUN_MODE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_SET_WALK_MODE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GM_NUKE_ACCOUNT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_GM_DESTROY_CORPSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_DESTROY_ONLINE_CORPSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_ACTIVATETAXIEXPRESS, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleActivateTaxiExpressOpcode ); + //OPCODE(SMSG_SET_FACTION_ATWAR, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_GAMETIMEBIAS_SET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_DEBUG_ACTIONS_START, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_DEBUG_ACTIONS_STOP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_SET_FACTION_INACTIVE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetFactionInactiveOpcode ); + OPCODE(CMSG_SET_WATCHED_FACTION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetWatchedFactionOpcode ); + OPCODE(MSG_MOVE_TIME_SKIPPED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_SPLINE_MOVE_ROOT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SET_EXPLORATION_ALL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_INVALIDATE_PLAYER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_RESET_INSTANCES, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleResetInstancesOpcode ); + OPCODE(SMSG_INSTANCE_RESET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_INSTANCE_RESET_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_UPDATE_LAST_INSTANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(MSG_RAID_TARGET_UPDATE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRaidTargetUpdateOpcode ); + OPCODE(MSG_RAID_READY_CHECK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRaidReadyCheckOpcode ); + //OPCODE(CMSG_LUA_USAGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_PET_ACTION_SOUND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PET_DISMISS_SOUND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_GHOSTEE_GONE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GM_UPDATE_TICKET_STATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_GM_TICKET_STATUS_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(MSG_SET_DUNGEON_DIFFICULTY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetDungeonDifficultyOpcode); + OPCODE(CMSG_GMSURVEY_SUBMIT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGMSurveySubmitOpcode ); + OPCODE(SMSG_UPDATE_INSTANCE_OWNERSHIP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_IGNORE_KNOCKBACK_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_CHAT_PLAYER_AMBIGUOUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(MSG_DELAY_GHOST_TELEPORT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_SPELLINSTAKILLLOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELL_UPDATE_CHAIN_TARGETS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CHAT_FILTERED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_EXPECTED_SPAM_RECORDS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPELLSTEALLOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_LOTTERY_QUERY_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_LOTTERY_QUERY_RESULT_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_BUY_LOTTERY_TICKET_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_LOTTERY_RESULT_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CHARACTER_PROFILE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CHARACTER_PROFILE_REALM_CONNECTED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_DEFENSE_MESSAGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_WORLD_SERVER_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(MSG_GM_RESETINSTANCELIMIT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MOTD, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_MOVE_SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_MOVE_UNSET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_MOVE_SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY_ACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_START_SWIM_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_STOP_SWIM_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MOVE_SET_CAN_FLY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_MOVE_UNSET_CAN_FLY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_MOVE_SET_CAN_FLY_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMoveSetCanFlyAckOpcode ); + //OPCODE(CMSG_MOVE_SET_FLY, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_SOCKET_GEMS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSocketOpcode ); + OPCODE(CMSG_ARENA_TEAM_CREATE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleArenaTeamCreateOpcode ); + OPCODE(SMSG_ARENA_TEAM_COMMAND_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(MSG_MOVE_UPDATE_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_ARENA_TEAM_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleArenaTeamQueryOpcode ); + OPCODE(SMSG_ARENA_TEAM_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_ARENA_TEAM_ROSTER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleArenaTeamRosterOpcode ); + OPCODE(SMSG_ARENA_TEAM_ROSTER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_ARENA_TEAM_INVITE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleArenaTeamInviteOpcode ); + OPCODE(SMSG_ARENA_TEAM_INVITE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_ARENA_TEAM_ACCEPT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleArenaTeamAcceptOpcode ); + OPCODE(CMSG_ARENA_TEAM_DECLINE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleArenaTeamDeclineOpcode ); + OPCODE(CMSG_ARENA_TEAM_LEAVE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleArenaTeamLeaveOpcode ); + OPCODE(CMSG_ARENA_TEAM_REMOVE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleArenaTeamRemoveOpcode ); + OPCODE(CMSG_ARENA_TEAM_DISBAND, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleArenaTeamDisbandOpcode ); + OPCODE(CMSG_ARENA_TEAM_LEADER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleArenaTeamLeaderOpcode ); + OPCODE(SMSG_ARENA_TEAM_EVENT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_BATTLEMASTER_JOIN_ARENA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleBattlemasterJoinArena ); + OPCODE(CMSG_MOVE_START_ASCEND, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(CMSG_MOVE_STOP_ASCEND, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(SMSG_ARENA_TEAM_STATS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_LFG_JOIN, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLfgJoinOpcode ); + //OPCODE(CMSG_LFG_LEAVE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLfgLeaveOpcode ); + //OPCODE(CMSG_LFG_SEARCH_JOIN, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSearchLfgJoinOpcode ); + //OPCODE(CMSG_LFG_SEARCH_LEAVE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSearchLfgLeaveOpcode ); + //OPCODE(SMSG_LFG_SEARCH_RESULTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_LFG_PROPOSAL_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_LFG_PROPOSAL_RESPONSE, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_LFG_ROLE_CHECK_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_LFG_JOIN_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_LFG_QUEUE_STATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SET_LFG_COMMENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetLfgCommentOpcode ); + //OPCODE(SMSG_LFG_UPDATE_PLAYER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_LFG_UPDATE_PARTY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_LFG_UPDATE_SEARCH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_LFG_SET_ROLES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_LFG_SET_NEEDS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_LFG_BOOT_PLAYER_VOTE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_LFG_BOOT_PLAYER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_LFG_GET_PLAYER_INFO, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_LFG_PLAYER_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_LFG_TELEPORT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_LFG_GET_PARTY_INFO, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_LFG_PARTY_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_TITLE_EARNED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SET_TITLE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetTitleOpcode ); + OPCODE(CMSG_CANCEL_MOUNT_AURA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCancelMountAuraOpcode ); + OPCODE(SMSG_ARENA_ERROR, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(MSG_INSPECT_ARENA_TEAMS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleInspectArenaTeamsOpcode ); + OPCODE(SMSG_DEATH_RELEASE_LOC, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CANCEL_TEMP_ENCHANTMENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCancelTempEnchantmentOpcode); + //OPCODE(SMSG_FORCED_DEATH_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CHEAT_SET_HONOR_CURRENCY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_CHEAT_SET_ARENA_CURRENCY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_SET_FLIGHT_SPEED_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MOVE_SET_FLIGHT_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_SET_FLIGHT_BACK_SPEED_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MOVE_SET_FLIGHT_BACK_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_FORCE_FLIGHT_SPEED_CHANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_FORCE_FLIGHT_SPEED_CHANGE_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleForceSpeedChangeAckOpcodes); + //OPCODE(SMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE_ACK, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleForceSpeedChangeAckOpcodes); + //OPCODE(SMSG_SPLINE_SET_FLIGHT_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SPLINE_SET_FLIGHT_BACK_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_MAELSTROM_INVALIDATE_CACHE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_FLIGHT_SPLINE_SYNC, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SET_TAXI_BENCHMARK_MODE, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleSetTaxiBenchmarkOpcode ); + //OPCODE(SMSG_JOINED_BATTLEGROUND_QUEUE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_REALM_SPLIT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_REALM_SPLIT, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleRealmSplitOpcode ); + OPCODE(CMSG_MOVE_CHNG_TRANSPORT, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + OPCODE(MSG_PARTY_ASSIGNMENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePartyAssignmentOpcode ); + //OPCODE(SMSG_OFFER_PETITION_ERROR, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_TIME_SYNC_REQ, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_TIME_SYNC_RESP, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleTimeSyncResp ); + //OPCODE(CMSG_SEND_LOCAL_EVENT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SEND_GENERAL_TRIGGER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SEND_COMBAT_TRIGGER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_MAELSTROM_GM_SENT_MAIL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_RESET_FAILED_NOTIFY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_REAL_GROUP_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_LFG_DISABLED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_ACTIVE_PVP_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_CHEAT_DUMP_ITEMS_DEBUG_ONLY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_CHEAT_DUMP_ITEMS_DEBUG_ONLY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CHEAT_DUMP_ITEMS_DEBUG_ONLY_RESPONSE_WRITE_FILE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_UPDATE_COMBO_POINTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_VOICE_SESSION_ROSTER_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_VOICE_SESSION_LEAVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_VOICE_SESSION_ADJUST_PRIORITY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_VOICE_SET_TALKER_MUTED_REQUEST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_VOICE_SET_TALKER_MUTED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_INIT_EXTRA_AURA_INFO_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SET_EXTRA_AURA_INFO_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SET_EXTRA_AURA_INFO_NEED_UPDATE_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CLEAR_EXTRA_AURA_INFO_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_MOVE_START_DESCEND, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes ); + //OPCODE(CMSG_IGNORE_REQUIREMENTS_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_IGNORE_REQUIREMENTS_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SPELL_CHANCE_PROC_LOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_MOVE_SET_RUN_SPEED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_DISMOUNT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(MSG_MOVE_UPDATE_CAN_FLY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(MSG_RAID_READY_CHECK_CONFIRM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_VOICE_SESSION_ENABLE, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleVoiceSessionEnableOpcode ); + //OPCODE(SMSG_VOICE_SESSION_ENABLE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_VOICE_PARENTAL_CONTROLS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GM_WHISPER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_GM_MESSAGECHAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(MSG_GM_GEARRATING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_COMMENTATOR_ENABLE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_COMMENTATOR_STATE_CHANGED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_COMMENTATOR_GET_MAP_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_COMMENTATOR_MAP_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_COMMENTATOR_GET_PLAYER_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_COMMENTATOR_GET_PLAYER_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_COMMENTATOR_PLAYER_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_COMMENTATOR_ENTER_INSTANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_COMMENTATOR_EXIT_INSTANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_COMMENTATOR_INSTANCE_COMMAND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_CLEAR_TARGET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_BOT_DETECTED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_CROSSED_INEBRIATION_THRESHOLD, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CHEAT_PLAYER_LOGIN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_CHEAT_PLAYER_LOOKUP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_CHEAT_PLAYER_LOOKUP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_KICK_REASON, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(MSG_RAID_READY_CHECK_FINISHED, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRaidReadyCheckFinishedOpcode); + OPCODE(CMSG_COMPLAIN, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleComplainOpcode ); + OPCODE(SMSG_COMPLAIN_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_FEATURE_SYSTEM_STATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GM_SHOW_COMPLAINTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_UNSQUELCH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_CHANNEL_SILENCE_VOICE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_CHANNEL_SILENCE_ALL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_CHANNEL_UNSILENCE_VOICE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_CHANNEL_UNSILENCE_ALL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_TARGET_CAST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_TARGET_SCRIPT_CAST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_CHANNEL_DISPLAY_LIST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelDisplayListQueryOpcode); + OPCODE(CMSG_SET_ACTIVE_VOICE_CHANNEL, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleSetActiveVoiceChannel ); + //OPCODE(CMSG_GET_CHANNEL_MEMBER_COUNT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGetChannelMemberCountOpcode); + OPCODE(SMSG_CHANNEL_MEMBER_COUNT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CHANNEL_VOICE_ON, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleChannelVoiceOnOpcode ); + //OPCODE(CMSG_CHANNEL_VOICE_OFF, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_DEBUG_LIST_TARGETS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_DEBUG_LIST_TARGETS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_AVAILABLE_VOICE_CHANNEL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_ADD_VOICE_IGNORE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_DEL_VOICE_IGNORE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_PARTY_SILENCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_PARTY_UNSILENCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(MSG_NOTIFY_PARTY_SQUELCH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_COMSAT_RECONNECT_TRY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_COMSAT_DISCONNECT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_COMSAT_CONNECT_FAIL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_VOICE_CHAT_STATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_REPORT_PVP_AFK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleReportPvPAFK ); + OPCODE(SMSG_REPORT_PVP_AFK_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_GUILD_BANKER_ACTIVATE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildBankerActivate ); + OPCODE(CMSG_GUILD_BANK_QUERY_TAB, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildBankQueryTab ); + OPCODE(SMSG_GUILD_BANK_LIST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GUILD_BANK_SWAP_ITEMS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildBankSwapItems ); + OPCODE(CMSG_GUILD_BANK_BUY_TAB, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildBankBuyTab ); + OPCODE(CMSG_GUILD_BANK_UPDATE_TAB, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildBankUpdateTab ); + OPCODE(CMSG_GUILD_BANK_DEPOSIT_MONEY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildBankDepositMoney ); + OPCODE(CMSG_GUILD_BANK_WITHDRAW_MONEY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildBankWithdrawMoney ); + OPCODE(CMSG_GUILD_BANK_LOG_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildBankLogQuery ); + OPCODE(SMSG_GUILD_BANK_LOG_QUERY_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SET_CHANNEL_WATCH, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetChannelWatchOpcode ); + OPCODE(SMSG_USERLIST_ADD, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_USERLIST_REMOVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_USERLIST_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CLEAR_CHANNEL_WATCH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_INSPECT_RESULTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_GOGOGO_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_ECHO_PARTY_SQUELCH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SET_TITLE_SUFFIX, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_SPELLCLICK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSpellClick ); + OPCODE(SMSG_LOOT_LIST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GM_CHARACTER_RESTORE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_CHARACTER_SAVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_VOICESESSION_FULL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GUILD_PERMISSIONS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildPermissions ); + OPCODE(SMSG_GUILD_PERMISSIONS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GUILD_BANK_MONEY_WITHDRAWN, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildBankMoneyWithdrawn ); + OPCODE(SMSG_GUILD_BANK_MONEY_WITHDRAWN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GUILD_EVENT_LOG_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGuildEventLogQueryOpcode ); + OPCODE(SMSG_GUILD_EVENT_LOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_MAELSTROM_RENAME_GUILD, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_GET_MIRRORIMAGE_DATA, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleGetMirrorimageData ); + OPCODE(SMSG_MIRRORIMAGE_DATA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_FORCE_DISPLAY_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SPELL_CHANCE_RESIST_PUSHBACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_IGNORE_DIMINISHING_RETURNS_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_IGNORE_DIMINISHING_RETURNS_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_KEEP_ALIVE, STATUS_NEVER, PROCESS_THREADUNSAFE, &WorldSession::Handle_EarlyProccess ); + //OPCODE(SMSG_RAID_READY_CHECK_ERROR, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_OPT_OUT_OF_LOOT, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleOptOutOfLootOpcode ); + OPCODE(CMSG_QUERY_GUILD_BANK_TEXT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQueryGuildBankTabText ); + OPCODE(SMSG_GUILD_BANK_TEXT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SET_GUILD_BANK_TEXT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetGuildBankTabText ); + //OPCODE(CMSG_SET_GRANTABLE_LEVELS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_GRANT_LEVEL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_REFER_A_FRIEND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_GM_CHANGE_ARENA_RATING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_DECLINE_CHANNEL_INVITE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_GROUPACTION_THROTTLED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_OVERRIDE_LIGHT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_TOTEM_CREATED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_TOTEM_DESTROYED, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTotemDestroyed ); + //OPCODE(CMSG_EXPIRE_RAID_INSTANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_NO_SPELL_VARIANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_QUESTGIVER_STATUS_MULTIPLE_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQuestgiverStatusMultipleQuery); + OPCODE(SMSG_QUESTGIVER_STATUS_MULTIPLE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SET_PLAYER_DECLINED_NAMES, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleSetPlayerDeclinedNamesOpcode); + OPCODE(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_QUERY_SERVER_BUCK_DATA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_CLEAR_SERVER_BUCK_DATA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_SERVER_BUCK_DATA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SEND_UNLEARN_SPELLS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PROPOSE_LEVEL_GRANT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_ACCEPT_LEVEL_GRANT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_REFER_A_FRIEND_FAILURE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_SET_FLYING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_UNSET_FLYING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SUMMON_CANCEL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CHANGE_PERSONAL_ARENA_RATING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_ALTER_APPEARANCE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAlterAppearanceOpcode ); + OPCODE(SMSG_ENABLE_BARBER_SHOP, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_BARBER_SHOP_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CALENDAR_GET_CALENDAR, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarGetCalendar ); + //OPCODE(CMSG_CALENDAR_GET_EVENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarGetEvent ); + //OPCODE(CMSG_CALENDAR_GUILD_FILTER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarGuildFilter ); + //OPCODE(CMSG_CALENDAR_ARENA_TEAM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarArenaTeam ); + OPCODE(CMSG_CALENDAR_ADD_EVENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarAddEvent ); + //OPCODE(CMSG_CALENDAR_UPDATE_EVENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarUpdateEvent ); + //OPCODE(CMSG_CALENDAR_REMOVE_EVENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarRemoveEvent ); + //OPCODE(CMSG_CALENDAR_COPY_EVENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarCopyEvent ); + OPCODE(CMSG_CALENDAR_EVENT_INVITE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarEventInvite ); + //OPCODE(CMSG_CALENDAR_EVENT_RSVP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarEventRsvp ); + //OPCODE(CMSG_CALENDAR_EVENT_REMOVE_INVITE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarEventRemoveInvite ); + //OPCODE(CMSG_CALENDAR_EVENT_STATUS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarEventStatus ); + //OPCODE(CMSG_CALENDAR_EVENT_MODERATOR_STATUS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarEventModeratorStatus); + OPCODE(SMSG_CALENDAR_SEND_CALENDAR, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CALENDAR_SEND_EVENT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_FILTER_GUILD, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_ARENA_TEAM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_EVENT_INVITE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_EVENT_INVITE_REMOVED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_EVENT_STATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_COMMAND_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_RAID_LOCKOUT_ADDED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_RAID_LOCKOUT_REMOVED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CALENDAR_EVENT_INVITE_ALERT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_EVENT_INVITE_REMOVED_ALERT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_EVENT_INVITE_STATUS_ALERT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_EVENT_REMOVED_ALERT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_EVENT_UPDATED_ALERT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_EVENT_MODERATOR_STATUS_ALERT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CALENDAR_COMPLAIN, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarComplain ); + OPCODE(CMSG_CALENDAR_GET_NUM_PENDING, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarGetNumPending ); + OPCODE(SMSG_CALENDAR_SEND_NUM_PENDING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SAVE_DANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_NOTIFY_DANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_PLAY_DANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_PLAY_DANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_LOAD_DANCES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_STOP_DANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_STOP_DANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SYNC_DANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_DANCE_QUERY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_DANCE_QUERY_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_INVALIDATE_DANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_DELETE_DANCE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_LEARNED_DANCE_MOVES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_LEARN_DANCE_MOVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_UNLEARN_DANCE_MOVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_RUNE_COUNT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_RUNE_COOLDOWN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_SET_PITCH_RATE_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_MOVE_SET_PITCH_RATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_FORCE_PITCH_RATE_CHANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_FORCE_PITCH_RATE_CHANGE_ACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_SPLINE_SET_PITCH_RATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CALENDAR_EVENT_INVITE_NOTES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_CALENDAR_EVENT_INVITE_NOTES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_CALENDAR_EVENT_INVITE_NOTES_ALERT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_UPDATE_MISSILE_TRAJECTORY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_UPDATE_ACCOUNT_DATA_COMPLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_TRIGGER_MOVIE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_COMPLETE_MOVIE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_GLYPH_SLOT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_GLYPH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_ACHIEVEMENT_EARNED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_DYNAMIC_DROP_ROLL_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CRITERIA_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_QUERY_INSPECT_ACHIEVEMENTS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQueryInspectAchievementsOpcode); + OPCODE(SMSG_RESPOND_INSPECT_ACHIEVEMENTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_DISMISS_CONTROLLED_VEHICLE, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleDismissControlledVehicle ); + //OPCODE(CMSG_COMPLETE_ACHIEVEMENT_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_QUESTUPDATE_ADD_PVP_KILL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SET_CRITERIA_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_CALENDAR_RAID_LOCKOUT_UPDATED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_UNITANIMTIER_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_CHAR_CUSTOMIZE, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleCharCustomizeOpcode ); + OPCODE(SMSG_CHAR_CUSTOMIZE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PET_RENAMEABLE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_REQUEST_VEHICLE_EXIT, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_REQUEST_VEHICLE_PREV_SEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_REQUEST_VEHICLE_NEXT_SEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_REQUEST_VEHICLE_SWITCH_SEAT, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_PET_LEARN_TALENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetLearnTalent ); + //OPCODE(CMSG_PET_UNLEARN_TALENTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_SET_PHASE_SHIFT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ALL_ACHIEVEMENT_DATA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_FORCE_SAY_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_HEALTH_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_POWER_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GAMEOBJ_REPORT_USE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGameobjectReportUse ); + OPCODE(SMSG_HIGHEST_THREAT_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_THREAT_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_THREAT_REMOVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_THREAT_CLEAR, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CONVERT_RUNE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_RESYNC_RUNES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ADD_RUNE_POWER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_START_QUEST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_REMOVE_GLYPH, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRemoveGlyphOpcode ); + //OPCODE(CMSG_DUMP_OBJECTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_DUMP_OBJECTS_DATA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_DISMISS_CRITTER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_NOTIFY_DEST_LOC_SPELL_CAST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_AUCTION_LIST_PENDING_SALES, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAuctionListPendingSales ); + OPCODE(SMSG_AUCTION_LIST_PENDING_SALES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_MODIFY_COOLDOWN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PET_UPDATE_COMBO_POINTS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_ENABLETAXI, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleTaxiQueryAvailableNodes ); + OPCODE(SMSG_PRE_RESURRECT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_AURA_UPDATE_ALL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_AURA_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_FLOOD_GRACE_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_SERVER_FIRST_ACHIEVEMENT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PET_LEARNED_SPELL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PET_REMOVED_SPELL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CHANGE_SEATS_ON_CONTROLLED_VEHICLE, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_HEARTH_AND_RESURRECT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleHearthandResurrect ); + OPCODE(SMSG_ON_CANCEL_EXPECTED_RIDE_VEHICLE_AURA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CRITERIA_DELETED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_ACHIEVEMENT_DELETED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SERVER_INFO_QUERY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_SERVER_INFO_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CHECK_LOGIN_CRITERIA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_SERVER_BUCK_DATA_START, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SET_BREATH, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_QUERY_VEHICLE_STATUS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_BATTLEGROUND_INFO_THROTTLED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_SET_VEHICLE_REC_ID, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_RIDE_VEHICLE_INTERACT, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleRideVehicleInteract ); + //OPCODE(CMSG_CONTROLLER_EJECT_PASSENGER, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleEjectPassenger ); + OPCODE(SMSG_PET_GUIDS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_CLIENTCACHE_VERSION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CHANGE_GDF_ARENA_RATING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_ARENA_TEAM_RATING_BY_INDEX, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_ARENA_TEAM_WEEKLY_GAMES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_ARENA_TEAM_SEASON_GAMES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_ARENA_MEMBER_WEEKLY_GAMES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_ARENA_MEMBER_SEASON_GAMES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_SET_ITEM_PURCHASE_DATA, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GET_ITEM_PURCHASE_DATA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleItemRefundInfoRequest ); + OPCODE(CMSG_ITEM_PURCHASE_REFUND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_ITEM_PURCHASE_REFUND_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CORPSE_TRANSPORT_QUERY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCorpseMapPositionQueryOpcode); + OPCODE(SMSG_CORPSE_TRANSPORT_QUERY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_UNUSED5, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_UNUSED6, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_CALENDAR_EVENT_SIGNUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCalendarEventSignup ); + //OPCODE(SMSG_CALENDAR_CLEAR_PENDING_ACTION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_LOAD_EQUIPMENT_SET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SAVE_EQUIPMENT_SET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleEquipmentSetSaveOpcode ); + //OPCODE(CMSG_ON_MISSILE_TRAJECTORY_COLLISION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_NOTIFY_MISSILE_TRAJECTORY_COLLISION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_TALENT_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_LEARN_TALENT_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnPreviewTalents ); + OPCODE(CMSG_PET_LEARN_TALENT_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnPreviewTalentsPet ); + //OPCODE(CMSG_SET_ACTIVE_TALENT_GROUP_OBSOLETE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_GRANT_ACHIEVEMENT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_REMOVE_ACHIEVEMENT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_SET_CRITERIA_FOR_PLAYER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_DESTROY_ARENA_UNIT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_ARENA_TEAM_CHANGE_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_PROFILEDATA_REQUEST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_PROFILEDATA_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_START_BATTLEFIELD_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_END_BATTLEFIELD_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_COMPOUND_MOVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_MOVE_GRAVITY_DISABLE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_MOVE_GRAVITY_DISABLE_ACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_MOVE_GRAVITY_ENABLE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_MOVE_GRAVITY_ENABLE_ACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_GRAVITY_CHNG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_GRAVITY_DISABLE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SPLINE_MOVE_GRAVITY_ENABLE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_USE_EQUIPMENT_SET, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleEquipmentSetUseOpcode ); + OPCODE(SMSG_USE_EQUIPMENT_SET_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_FORCE_ANIM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_FORCE_ANIM, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CHAR_FACTION_CHANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_CHAR_FACTION_CHANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_PVP_QUEUE_STATS_REQUEST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_PVP_QUEUE_STATS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SET_PAID_SERVICE_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_BATTLEFIELD_MANAGER_ENTRY_INVITE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_BATTLEFIELD_MANAGER_ENTRY_INVITE_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_BATTLEFIELD_MANAGER_ENTERING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_BATTLEFIELD_MANAGER_QUEUE_INVITE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_BATTLEFIELD_MANAGER_QUEUE_INVITE_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_BATTLEFIELD_MANAGER_QUEUE_REQUEST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_BATTLEFIELD_MANAGER_QUEUE_REQUEST_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_BATTLEFIELD_MANAGER_EJECT_PENDING, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_BATTLEFIELD_MANAGER_EJECTED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_BATTLEFIELD_MANAGER_EXIT_REQUEST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_BATTLEFIELD_MANAGER_STATE_CHANGED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_BATTLEFIELD_MANAGER_ADVANCE_STATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_BATTLEFIELD_MANAGER_SET_NEXT_TRANSITION_TIME, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(MSG_SET_RAID_DIFFICULTY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetRaidDifficultyOpcode ); + //OPCODE(CMSG_XPGAIN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_XPGAIN, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_GMTICKET_RESPONSE_ERROR, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_GMTICKET_GET_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_GMTICKET_RESOLVE_RESPONSE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGMResponseResolveOpcode ); + OPCODE(SMSG_GMTICKET_RESOLVE_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_GMTICKET_CREATE_RESPONSE_TICKET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_GM_CREATE_TICKET_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_SERVERINFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_SERVERINFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_UI_TIME_REQUEST, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleUITimeRequestOpcode ); + OPCODE(SMSG_UI_TIME, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_CHAR_RACE_CHANGE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_VIEW_PHASE_SHIFT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_TALENTS_INVOLUNTARILY_RESET, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_DEBUG_SERVER_GEO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_DEBUG_SERVER_GEO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_LOOT_UPDATE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(UMSG_UPDATE_GROUP_INFO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_READY_FOR_ACCOUNT_DATA_TIMES, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleReadyForAccountDataTimesOpcode); + OPCODE(CMSG_QUERY_GET_ALL_QUESTS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleQueryQuestsCompletedOpcode); + OPCODE(SMSG_ALL_QUESTS_COMPLETED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GMLAGREPORT_SUBMIT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_AFK_MONITOR_INFO_REQUEST, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_AFK_MONITOR_INFO_RESPONSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_AFK_MONITOR_INFO_CLEAR, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_AREA_TRIGGER_NO_CORPSE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_GM_NUKE_CHARACTER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_LOW_LEVEL_RAID, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleSetAllowLowLevelRaidOpcode); + OPCODE(CMSG_LOW_LEVEL_RAID_USER, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleSetAllowLowLevelRaidOpcode); + OPCODE(SMSG_CAMERA_SHAKE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SOCKET_GEMS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SET_CHARACTER_MODEL, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + OPCODE(SMSG_CONNECT_TO, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CONNECT_TO_FAILED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_SUSPEND_COMMS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_SUSPEND_COMMS_ACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_RESUME_COMMS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_AUTH_CONTINUED_SESSION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_DROP_NEW_CONNECTION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_SEND_ALL_COMBAT_LOG, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_OPEN_LFG_DUNGEON_FINDER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_MOVE_SET_COLLISION_HGT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_MOVE_SET_COLLISION_HGT_ACK, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(MSG_MOVE_SET_COLLISION_HGT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_CLEAR_RANDOM_BG_WIN_TIME, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_CLEAR_HOLIDAY_BG_WIN_TIME, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_COMMENTATOR_SKIRMISH_QUEUE_COMMAND, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL ); + //OPCODE(SMSG_COMMENTATOR_SKIRMISH_QUEUE_RESULT1, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_COMMENTATOR_SKIRMISH_QUEUE_RESULT2, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(SMSG_COMPRESSED_UNKNOWN_1310, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_PLAYER_MOVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_REORDER_CHARACTERS, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleReorderCharactersOpcode ); + OPCODE(SMSG_SET_CURRENCY_WEEK_LIMIT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SET_CURRENCY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_SEND_CURRENCIES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_SET_CURRENCY_FLAGS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetCurrencyFlagsOpcode ); + OPCODE(SMSG_WEEKLY_RESET_CURRENCIES, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_INSPECT_RATED_BG_STATS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL ); + //OPCODE(CMSG_REQUEST_RATED_BG_INFO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL ); + OPCODE(CMSG_REQUEST_RATED_BG_STATS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRequestRatedBGStatsOpcode ); + OPCODE(SMSG_RATED_BG_STATS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_REQUEST_PVP_REWARDS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRequestPvPRewardsOpcode ); + OPCODE(SMSG_PVP_REWARDS, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_REQUEST_PVP_OPTIONS_ENABLED, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleRequestPvPOptionsEnabledOpcode ); + OPCODE(SMSG_PVP_OPTIONS_ENABLED, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(CMSG_REQUEST_HOTFIX, STATUS_AUTHED, PROCESS_INPLACE, &WorldSession::HandleRequestHotfix ); + OPCODE(SMSG_DB_REPLY, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + //OPCODE(CMSG_OBJECT_UPDATE_FAILED, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleObjectUpdateFailedOpcode ); + OPCODE(CMSG_REFORGE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleReforgeItemOpcode ); + OPCODE(SMSG_REFORGE_RESULT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); + OPCODE(SMSG_START_TIMER, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide ); +}; diff --git a/src/game/Player.cpp b/src/game/Player.cpp new file mode 100644 index 000000000..dbe6a725e --- /dev/null +++ b/src/game/Player.cpp @@ -0,0 +1,24506 @@ +/** + * This code is part of MaNGOS. Contributor & Copyright details are in AUTHORS/THANKS. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Player.h" +#include "Language.h" +#include "Database/DatabaseEnv.h" +#include "Log.h" +#include "Opcodes.h" +#include "SpellMgr.h" +#include "World.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "UpdateMask.h" +#include "SkillDiscovery.h" +#include "QuestDef.h" +#include "GossipDef.h" +#include "UpdateData.h" +#include "Channel.h" +#include "ChannelMgr.h" +#include "MapManager.h" +#include "MapPersistentStateMgr.h" +#include "InstanceData.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" +#include "ObjectMgr.h" +#include "ObjectAccessor.h" +#include "CreatureAI.h" +#include "Formulas.h" +#include "Group.h" +#include "Guild.h" +#include "GuildMgr.h" +#include "Pet.h" +#include "Util.h" +#include "Transports.h" +#include "Weather.h" +#include "BattleGround/BattleGround.h" +#include "BattleGround/BattleGroundMgr.h" +#include "BattleGround/BattleGroundAV.h" +#include "OutdoorPvP/OutdoorPvP.h" +#include "ArenaTeam.h" +#include "Chat.h" +#include "Database/DatabaseImpl.h" +#include "Spell.h" +#include "ScriptMgr.h" +#include "SocialMgr.h" +#include "AchievementMgr.h" +#include "Mail.h" +#include "SpellAuras.h" +#include "DBCStores.h" +#include "DB2Stores.h" +#include "SQLStorages.h" +#include "Vehicle.h" +#include "Calendar.h" +#include "PhaseMgr.h" + +#include + +#define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) + +enum CharacterFlags +{ + CHARACTER_FLAG_NONE = 0x00000000, + CHARACTER_FLAG_UNK1 = 0x00000001, + CHARACTER_FLAG_UNK2 = 0x00000002, + CHARACTER_LOCKED_FOR_TRANSFER = 0x00000004, + CHARACTER_FLAG_UNK4 = 0x00000008, + CHARACTER_FLAG_UNK5 = 0x00000010, + CHARACTER_FLAG_UNK6 = 0x00000020, + CHARACTER_FLAG_UNK7 = 0x00000040, + CHARACTER_FLAG_UNK8 = 0x00000080, + CHARACTER_FLAG_UNK9 = 0x00000100, + CHARACTER_FLAG_UNK10 = 0x00000200, + CHARACTER_FLAG_HIDE_HELM = 0x00000400, + CHARACTER_FLAG_HIDE_CLOAK = 0x00000800, + CHARACTER_FLAG_UNK13 = 0x00001000, + CHARACTER_FLAG_GHOST = 0x00002000, + CHARACTER_FLAG_RENAME = 0x00004000, + CHARACTER_FLAG_UNK16 = 0x00008000, + CHARACTER_FLAG_UNK17 = 0x00010000, + CHARACTER_FLAG_UNK18 = 0x00020000, + CHARACTER_FLAG_UNK19 = 0x00040000, + CHARACTER_FLAG_UNK20 = 0x00080000, + CHARACTER_FLAG_UNK21 = 0x00100000, + CHARACTER_FLAG_UNK22 = 0x00200000, + CHARACTER_FLAG_UNK23 = 0x00400000, + CHARACTER_FLAG_UNK24 = 0x00800000, + CHARACTER_FLAG_LOCKED_BY_BILLING = 0x01000000, + CHARACTER_FLAG_DECLINED = 0x02000000, + CHARACTER_FLAG_UNK27 = 0x04000000, + CHARACTER_FLAG_UNK28 = 0x08000000, + CHARACTER_FLAG_UNK29 = 0x10000000, + CHARACTER_FLAG_UNK30 = 0x20000000, + CHARACTER_FLAG_UNK31 = 0x40000000, + CHARACTER_FLAG_UNK32 = 0x80000000 +}; + +enum CharacterCustomizeFlags +{ + CHAR_CUSTOMIZE_FLAG_NONE = 0x00000000, + CHAR_CUSTOMIZE_FLAG_CUSTOMIZE = 0x00000001, // name, gender, etc... + CHAR_CUSTOMIZE_FLAG_FACTION = 0x00010000, // name, gender, faction, etc... + CHAR_CUSTOMIZE_FLAG_RACE = 0x00100000 // name, gender, race, etc... +}; + +// corpse reclaim times +#define DEATH_EXPIRE_STEP (5*MINUTE) +#define MAX_DEATH_COUNT 3 + +static const uint32 corpseReclaimDelay[MAX_DEATH_COUNT] = {30, 60, 120}; + +//== PlayerTaxi ================================================ + +PlayerTaxi::PlayerTaxi() +{ + // Taxi nodes + memset(m_taximask, 0, sizeof(m_taximask)); +} + +void PlayerTaxi::InitTaxiNodesForLevel(uint32 race, uint32 chrClass, uint32 level) +{ + // class specific initial known nodes + switch (chrClass) + { + case CLASS_DEATH_KNIGHT: + { + for (int i = 0; i < TaxiMaskSize; ++i) + m_taximask[i] |= sOldContinentsNodesMask[i]; + break; + } + } + + // race specific initial known nodes: capital and taxi hub masks + switch (race) + { + case RACE_HUMAN: SetTaximaskNode(2); break; // Human + case RACE_ORC: SetTaximaskNode(23); break; // Orc + case RACE_DWARF: SetTaximaskNode(6); break; // Dwarf + case RACE_NIGHTELF: SetTaximaskNode(26); + SetTaximaskNode(27); break; // Night Elf + case RACE_UNDEAD: SetTaximaskNode(11); break; // Undead + case RACE_TAUREN: SetTaximaskNode(22); break; // Tauren + case RACE_GNOME: SetTaximaskNode(6); break; // Gnome + case RACE_TROLL: SetTaximaskNode(23); break; // Troll + case RACE_BLOODELF: SetTaximaskNode(82); break; // Blood Elf + case RACE_DRAENEI: SetTaximaskNode(94); break; // Draenei + } + + // new continent starting masks (It will be accessible only at new map) + switch (Player::TeamForRace(race)) + { + case ALLIANCE: SetTaximaskNode(100); break; + case HORDE: SetTaximaskNode(99); break; + default: break; + } + // level dependent taxi hubs + if (level >= 68) + SetTaximaskNode(213); // Shattered Sun Staging Area +} + +void PlayerTaxi::LoadTaxiMask(const char* data) +{ + Tokens tokens = StrSplit(data, " "); + + int index; + Tokens::iterator iter; + for (iter = tokens.begin(), index = 0; + (index < TaxiMaskSize) && (iter != tokens.end()); ++iter, ++index) + { + // load and set bits only for existing taxi nodes + m_taximask[index] = sTaxiNodesMask[index] & uint8(atol((*iter).c_str())); + } +} + +void PlayerTaxi::AppendTaximaskTo(ByteBuffer& data, bool all) +{ + data << uint32(TaxiMaskSize); + if (all) + { + for (uint8 i = 0; i < TaxiMaskSize; ++i) + data << uint8(sTaxiNodesMask[i]); // all existing nodes + } + else + { + for (uint8 i = 0; i < TaxiMaskSize; ++i) + data << uint8(m_taximask[i]); // known nodes + } +} + +bool PlayerTaxi::LoadTaxiDestinationsFromString(const std::string& values, Team team) +{ + ClearTaxiDestinations(); + + Tokens tokens = StrSplit(values, " "); + + for (Tokens::iterator iter = tokens.begin(); iter != tokens.end(); ++iter) + { + uint32 node = uint32(atol(iter->c_str())); + AddTaxiDestination(node); + } + + if (m_TaxiDestinations.empty()) + return true; + + // Check integrity + if (m_TaxiDestinations.size() < 2) + return false; + + for (size_t i = 1; i < m_TaxiDestinations.size(); ++i) + { + uint32 cost; + uint32 path; + sObjectMgr.GetTaxiPath(m_TaxiDestinations[i - 1], m_TaxiDestinations[i], path, cost); + if (!path) + return false; + } + + // can't load taxi path without mount set (quest taxi path?) + if (!sObjectMgr.GetTaxiMountDisplayId(GetTaxiSource(), team, true)) + return false; + + return true; +} + +std::string PlayerTaxi::SaveTaxiDestinationsToString() +{ + if (m_TaxiDestinations.empty()) + return ""; + + std::ostringstream ss; + + for (size_t i = 0; i < m_TaxiDestinations.size(); ++i) + ss << m_TaxiDestinations[i] << " "; + + return ss.str(); +} + +uint32 PlayerTaxi::GetCurrentTaxiPath() const +{ + if (m_TaxiDestinations.size() < 2) + return 0; + + uint32 path; + uint32 cost; + + sObjectMgr.GetTaxiPath(m_TaxiDestinations[0], m_TaxiDestinations[1], path, cost); + + return path; +} + +std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi) +{ + for (int i = 0; i < TaxiMaskSize; ++i) + ss << uint32(taxi.m_taximask[i]) << " "; // cast to prevent conversion to char + return ss; +} +//== TradeData ================================================= + +TradeData* TradeData::GetTraderData() const +{ + return m_trader->GetTradeData(); +} + +Item* TradeData::GetItem(TradeSlots slot) const +{ + return m_items[slot] ? m_player->GetItemByGuid(m_items[slot]) : NULL; +} + +bool TradeData::HasItem(ObjectGuid item_guid) const +{ + for (int i = 0; i < TRADE_SLOT_COUNT; ++i) + if (m_items[i] == item_guid) + return true; + return false; +} + +Item* TradeData::GetSpellCastItem() const +{ + return m_spellCastItem ? m_player->GetItemByGuid(m_spellCastItem) : NULL; +} + +void TradeData::SetItem(TradeSlots slot, Item* item) +{ + ObjectGuid itemGuid = item ? item->GetObjectGuid() : ObjectGuid(); + + if (m_items[slot] == itemGuid) + return; + + m_items[slot] = itemGuid; + + SetAccepted(false); + GetTraderData()->SetAccepted(false); + + Update(); + + // need remove possible trader spell applied to changed item + if (slot == TRADE_SLOT_NONTRADED) + GetTraderData()->SetSpell(0); + + // need remove possible player spell applied (possible move reagent) + SetSpell(0); +} + +void TradeData::SetSpell(uint32 spell_id, Item* castItem /*= NULL*/) +{ + ObjectGuid itemGuid = castItem ? castItem->GetObjectGuid() : ObjectGuid(); + + if (m_spell == spell_id && m_spellCastItem == itemGuid) + return; + + m_spell = spell_id; + m_spellCastItem = itemGuid; + + SetAccepted(false); + GetTraderData()->SetAccepted(false); + + Update(true); // send spell info to item owner + Update(false); // send spell info to caster self +} + +void TradeData::SetMoney(uint64 money) +{ + if (m_money == money) + return; + + m_money = money; + + SetAccepted(false); + GetTraderData()->SetAccepted(false); + + Update(); +} + +void TradeData::Update(bool for_trader /*= true*/) +{ + if (for_trader) + m_trader->GetSession()->SendUpdateTrade(true); // player state for trader + else + m_player->GetSession()->SendUpdateTrade(false); // player state for player +} + +void TradeData::SetAccepted(bool state, bool crosssend /*= false*/) +{ + m_accepted = state; + + if (!state) + { + if (crosssend) + m_trader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + else + m_player->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); + } +} + +//== Player ==================================================== + +UpdateMask Player::updateVisualBits; + +Player::Player(WorldSession* session): Unit(), m_mover(this), m_camera(this), m_achievementMgr(this), m_reputationMgr(this) +{ + m_transport = 0; + + m_speakTime = 0; + m_speakCount = 0; + + m_objectType |= TYPEMASK_PLAYER; + m_objectTypeId = TYPEID_PLAYER; + + m_valuesCount = PLAYER_END; + + SetActiveObjectState(true); // player is always active object + + m_session = session; + + m_ExtraFlags = 0; + if (GetSession()->GetSecurity() >= SEC_GAMEMASTER) + SetAcceptTicket(true); + + // players always accept + if (GetSession()->GetSecurity() == SEC_PLAYER) + SetAcceptWhispers(true); + + m_comboPoints = 0; + + m_usedTalentCount = 0; + m_questRewardTalentCount = 0; + m_freeTalentPoints = 0; + + m_regenTimer = 0; + m_holyPowerRegenTimer = REGEN_TIME_HOLY_POWER; + m_weaponChangeTimer = 0; + + m_zoneUpdateId = 0; + m_zoneUpdateTimer = 0; + m_positionStatusUpdateTimer = 0; + + m_areaUpdateId = 0; + + m_nextSave = sWorld.getConfig(CONFIG_UINT32_INTERVAL_SAVE); + + // randomize first save time in range [CONFIG_UINT32_INTERVAL_SAVE] around [CONFIG_UINT32_INTERVAL_SAVE] + // this must help in case next save after mass player load after server startup + m_nextSave = urand(m_nextSave / 2, m_nextSave * 3 / 2); + + clearResurrectRequestData(); + + memset(m_items, 0, sizeof(Item*)*PLAYER_SLOTS_COUNT); + + m_social = NULL; + + // group is initialized in the reference constructor + SetGroupInvite(NULL); + m_groupUpdateMask = 0; + m_auraUpdateMask = 0; + + duel = NULL; + + m_GuildIdInvited = 0; + m_ArenaTeamIdInvited = 0; + + m_atLoginFlags = AT_LOGIN_NONE; + + mSemaphoreTeleport_Near = false; + mSemaphoreTeleport_Far = false; + + m_DelayedOperations = 0; + m_bCanDelayTeleport = false; + m_bHasDelayedTeleport = false; + m_bHasBeenAliveAtDelayedTeleport = true; // overwrite always at setup teleport data, so not used infact + m_teleport_options = 0; + + m_trade = NULL; + + m_cinematic = 0; + + PlayerTalkClass = new PlayerMenu(GetSession()); + m_currentBuybackSlot = BUYBACK_SLOT_START; + + m_DailyQuestChanged = false; + m_WeeklyQuestChanged = false; + + m_lastLiquid = NULL; + + for (int i = 0; i < MAX_TIMERS; ++i) + m_MirrorTimer[i] = DISABLED_MIRROR_TIMER; + + m_MirrorTimerFlags = UNDERWATER_NONE; + m_MirrorTimerFlagsLast = UNDERWATER_NONE; + + m_isInWater = false; + m_drunkTimer = 0; + m_restTime = 0; + m_deathTimer = 0; + m_deathExpireTime = 0; + + m_swingErrorMsg = 0; + + m_DetectInvTimer = 1 * IN_MILLISECONDS; + + for (int j = 0; j < PLAYER_MAX_BATTLEGROUND_QUEUES; ++j) + { + m_bgBattleGroundQueueID[j].bgQueueTypeId = BATTLEGROUND_QUEUE_NONE; + m_bgBattleGroundQueueID[j].invitedToInstance = 0; + } + + m_logintime = time(NULL); + m_Last_tick = m_logintime; + m_WeaponProficiency = 0; + m_ArmorProficiency = 0; + m_canParry = false; + m_canBlock = false; + m_canDualWield = false; + m_canTitanGrip = false; + m_ammoDPS = 0.0f; + + m_temporaryUnsummonedPetNumber = 0; + + //////////////////// Rest System///////////////////// + time_inn_enter = 0; + inn_trigger_id = 0; + m_rest_bonus = 0; + rest_type = REST_TYPE_NO; + //////////////////// Rest System///////////////////// + + m_mailsUpdated = false; + unReadMails = 0; + m_nextMailDelivereTime = 0; + + m_resetTalentsCost = 0; + m_resetTalentsTime = 0; + m_itemUpdateQueueBlocked = false; + + for (int i = 0; i < MAX_MOVE_TYPE; ++i) + m_forced_speed_changes[i] = 0; + + m_stableSlots = 0; + + /////////////////// Instance System ///////////////////// + + m_HomebindTimer = 0; + m_InstanceValid = true; + m_dungeonDifficulty = DUNGEON_DIFFICULTY_NORMAL; + m_raidDifficulty = RAID_DIFFICULTY_10MAN_NORMAL; + + m_lastPotionId = 0; + + m_activeSpec = 0; + m_specsCount = 1; + for (int i = 0; i < MAX_TALENT_SPEC_COUNT; ++i) + m_talentsPrimaryTree[i] = 0; + + for (int i = 0; i < BASEMOD_END; ++i) + { + m_auraBaseMod[i][FLAT_MOD] = 0.0f; + m_auraBaseMod[i][PCT_MOD] = 1.0f; + } + + for (int i = 0; i < MAX_COMBAT_RATING; ++i) + m_baseRatingValue[i] = 0; + + m_baseSpellPower = 0; + m_baseHealthRegen = 0; + m_baseManaRegen = 0; + m_armorPenetrationPct = 0.0f; + m_spellPenetrationItemMod = 0; + + m_lastHonorKillsUpdateTime = time(NULL); + + // Player summoning + m_summon_expire = 0; + m_summon_mapid = 0; + m_summon_x = 0.0f; + m_summon_y = 0.0f; + m_summon_z = 0.0f; + + m_contestedPvPTimer = 0; + + m_declinedname = NULL; + m_runes = NULL; + + m_lastFallTime = 0; + m_lastFallZ = 0; + + m_cachedGS = 0; + + m_slot = 255; + + phaseMgr = new PhaseMgr(this); +} + +Player::~Player() +{ + CleanupsBeforeDelete(); + + // it must be unloaded already in PlayerLogout and accessed only for loggined player + // m_social = NULL; + + // Note: buy back item already deleted from DB when player was saved + for (int i = 0; i < PLAYER_SLOTS_COUNT; ++i) + { + delete m_items[i]; + } + CleanupChannels(); + + // all mailed items should be deleted, also all mail should be deallocated + for (PlayerMails::const_iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr) + delete *itr; + + for (ItemMap::const_iterator iter = mMitems.begin(); iter != mMitems.end(); ++iter) + delete iter->second; // if item is duplicated... then server may crash ... but that item should be deallocated + + delete PlayerTalkClass; + + if (m_transport) + { + m_transport->RemovePassenger(this); + } + + for (size_t x = 0; x < ItemSetEff.size(); ++x) + delete ItemSetEff[x]; + + // clean up player-instance binds, may unload some instance saves + for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) + for (BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr) + itr->second.state->RemovePlayer(this); + + delete m_declinedname; + delete m_runes; + + delete phaseMgr; +} + +void Player::CleanupsBeforeDelete() +{ + if (m_uint32Values) // only for fully created Object + { + TradeCancel(false); + DuelComplete(DUEL_INTERRUPTED); + } + + // notify zone scripts for player logout + sOutdoorPvPMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId); + + Unit::CleanupsBeforeDelete(); +} + +bool Player::Create(uint32 guidlow, const std::string& name, uint8 race, uint8 class_, uint8 gender, uint8 skin, uint8 face, uint8 hairStyle, uint8 hairColor, uint8 facialHair, uint8 /*outfitId */) +{ + // FIXME: outfitId not used in player creating + + Object::_Create(guidlow, 0, HIGHGUID_PLAYER); + + m_name = name; + + PlayerInfo const* info = sObjectMgr.GetPlayerInfo(race, class_); + if (!info) + { + sLog.outError("Player have incorrect race/class pair. Can't be loaded."); + return false; + } + + ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(class_); + if (!cEntry) + { + sLog.outError("Class %u not found in DBC (Wrong DBC files?)", class_); + return false; + } + + // player store gender in single bit + if (gender != uint8(GENDER_MALE) && gender != uint8(GENDER_FEMALE)) + { + sLog.outError("Invalid gender %u at player creating", uint32(gender)); + return false; + } + + for (int i = 0; i < PLAYER_SLOTS_COUNT; ++i) + m_items[i] = NULL; + + SetLocationMapId(info->mapId); + Relocate(info->positionX, info->positionY, info->positionZ, info->orientation); + + SetMap(sMapMgr.CreateMap(info->mapId, this)); + + uint8 powertype = cEntry->powerType; + + setFactionForRace(race); + + SetByteValue(UNIT_FIELD_BYTES_0, 0, race); + SetByteValue(UNIT_FIELD_BYTES_0, 1, class_); + SetByteValue(UNIT_FIELD_BYTES_0, 2, gender); + SetByteValue(UNIT_FIELD_BYTES_0, 3, powertype); + + InitDisplayIds(); // model, scale and model data + + SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_PVP); + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE); + SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_REGENERATE_POWER); + SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f); // fix cast time showed in spell tooltip on client + SetFloatValue(UNIT_FIELD_HOVERHEIGHT, 1.0f); // default for players in 3.0.3 + + SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, -1); // -1 is default value + + SetByteValue(PLAYER_BYTES, 0, skin); + SetByteValue(PLAYER_BYTES, 1, face); + SetByteValue(PLAYER_BYTES, 2, hairStyle); + SetByteValue(PLAYER_BYTES, 3, hairColor); + + SetByteValue(PLAYER_BYTES_2, 0, facialHair); + SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_NORMAL); + + SetByteValue(PLAYER_BYTES_3, 0, gender); + SetByteValue(PLAYER_BYTES_3, 3, 0); // BattlefieldArenaFaction (0 or 1) + + SetInGuild(0); + SetGuildLevel(0); + SetUInt32Value(PLAYER_GUILDRANK, 0); + SetUInt32Value(PLAYER_GUILD_TIMESTAMP, 0); + + for (int i = 0; i < KNOWN_TITLES_SIZE; ++i) + SetUInt64Value(PLAYER__FIELD_KNOWN_TITLES + i, 0); // 0=disabled + SetUInt32Value(PLAYER_CHOSEN_TITLE, 0); + + SetUInt32Value(PLAYER_FIELD_KILLS, 0); + SetUInt32Value(PLAYER_FIELD_LIFETIME_HONORBALE_KILLS, 0); + + // set starting level + uint32 start_level = getClass() != CLASS_DEATH_KNIGHT + ? sWorld.getConfig(CONFIG_UINT32_START_PLAYER_LEVEL) + : sWorld.getConfig(CONFIG_UINT32_START_HEROIC_PLAYER_LEVEL); + + if (GetSession()->GetSecurity() >= SEC_MODERATOR) + { + uint32 gm_level = sWorld.getConfig(CONFIG_UINT32_START_GM_LEVEL); + if (gm_level > start_level) + start_level = gm_level; + } + + SetUInt32Value(UNIT_FIELD_LEVEL, start_level); + + InitRunes(); + + SetUInt64Value(PLAYER_FIELD_COINAGE, sWorld.getConfig(CONFIG_UINT64_START_PLAYER_MONEY)); + SetCurrencyCount(CURRENCY_HONOR_POINTS,sWorld.getConfig(CONFIG_UINT32_CURRENCY_START_HONOR_POINTS)); + SetCurrencyCount(CURRENCY_CONQUEST_POINTS, sWorld.getConfig(CONFIG_UINT32_CURRENCY_START_CONQUEST_POINTS)); + + // Played time + m_Last_tick = time(NULL); + m_Played_time[PLAYED_TIME_TOTAL] = 0; + m_Played_time[PLAYED_TIME_LEVEL] = 0; + + // base stats and related field values + InitStatsForLevel(); + InitTaxiNodesForLevel(); + InitGlyphsForLevel(); + InitTalentForLevel(); + InitPrimaryProfessions(); // to max set before any spell added + + // apply original stats mods before spell loading or item equipment that call before equip _RemoveStatsMods() + UpdateMaxHealth(); // Update max Health (for add bonus from stamina) + SetHealth(GetMaxHealth()); + + if (getPowerType() == POWER_MANA) + { + UpdateMaxPower(POWER_MANA); // Update max Mana (for add bonus from intellect) + SetPower(POWER_MANA, GetMaxPower(POWER_MANA)); + } + + if (getPowerType() != POWER_MANA) // hide additional mana bar if we have no mana + { + SetPower(POWER_MANA, 0); + SetMaxPower(POWER_MANA, 0); + } + + // original spells + learnDefaultSpells(); + + // original action bar + for (PlayerCreateInfoActions::const_iterator action_itr = info->action.begin(); action_itr != info->action.end(); ++action_itr) + addActionButton(0, action_itr->button, action_itr->action, action_itr->type); + + // original items + uint32 raceClassGender = GetUInt32Value(UNIT_FIELD_BYTES_0) & 0x00FFFFFF; + + CharStartOutfitEntry const* oEntry = NULL; + for (uint32 i = 1; i < sCharStartOutfitStore.GetNumRows(); ++i) + { + if (CharStartOutfitEntry const* entry = sCharStartOutfitStore.LookupEntry(i)) + { + if (entry->RaceClassGender == raceClassGender) + { + oEntry = entry; + break; + } + } + } + + if (oEntry) + { + for (int j = 0; j < MAX_OUTFIT_ITEMS; ++j) + { + if (oEntry->ItemId[j] <= 0) + continue; + + uint32 item_id = oEntry->ItemId[j]; + + // just skip, reported in ObjectMgr::LoadItemPrototypes + ItemPrototype const* iProto = ObjectMgr::GetItemPrototype(item_id); + if (!iProto) + continue; + + // BuyCount by default + int32 count = iProto->BuyCount; + + // special amount for foor/drink + if (iProto->Class == ITEM_CLASS_CONSUMABLE && iProto->SubClass == ITEM_SUBCLASS_FOOD) + { + switch (iProto->Spells[0].SpellCategory) + { + case 11: // food + count = getClass() == CLASS_DEATH_KNIGHT ? 10 : 4; + break; + case 59: // drink + count = 2; + break; + } + if (iProto->Stackable < count) + count = iProto->Stackable; + } + + StoreNewItemInBestSlots(item_id, count); + } + } + + for (PlayerCreateInfoItems::const_iterator item_id_itr = info->item.begin(); item_id_itr != info->item.end(); ++item_id_itr) + StoreNewItemInBestSlots(item_id_itr->item_id, item_id_itr->item_amount); + + // bags and main-hand weapon must equipped at this moment + // now second pass for not equipped (offhand weapon/shield if it attempt equipped before main-hand weapon) + // or ammo not equipped in special bag + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + uint16 eDest; + // equip offhand weapon/shield if it attempt equipped before main-hand weapon + InventoryResult msg = CanEquipItem(NULL_SLOT, eDest, pItem, false); + if (msg == EQUIP_ERR_OK) + { + RemoveItem(INVENTORY_SLOT_BAG_0, i, true); + EquipItem(eDest, pItem, true); + } + // move other items to more appropriate slots (ammo not equipped in special bag) + else + { + ItemPosCountVec sDest; + msg = CanStoreItem(NULL_BAG, NULL_SLOT, sDest, pItem, false); + if (msg == EQUIP_ERR_OK) + { + RemoveItem(INVENTORY_SLOT_BAG_0, i, true); + pItem = StoreItem(sDest, pItem, true); + } + + // if this is ammo then use it + msg = CanUseAmmo(pItem->GetEntry()); + if (msg == EQUIP_ERR_OK) + SetAmmo(pItem->GetEntry()); + } + } + } + // all item positions resolved + + return true; +} + +bool Player::StoreNewItemInBestSlots(uint32 titem_id, uint32 titem_amount) +{ + DEBUG_LOG("STORAGE: Creating initial item, itemId = %u, count = %u", titem_id, titem_amount); + + // attempt equip by one + while (titem_amount > 0) + { + uint16 eDest; + uint8 msg = CanEquipNewItem(NULL_SLOT, eDest, titem_id, false); + if (msg != EQUIP_ERR_OK) + break; + + EquipNewItem(eDest, titem_id, true); + AutoUnequipOffhandIfNeed(); + --titem_amount; + } + + if (titem_amount == 0) + return true; // equipped + + // attempt store + ItemPosCountVec sDest; + // store in main bag to simplify second pass (special bags can be not equipped yet at this moment) + uint8 msg = CanStoreNewItem(INVENTORY_SLOT_BAG_0, NULL_SLOT, sDest, titem_id, titem_amount); + if (msg == EQUIP_ERR_OK) + { + StoreNewItem(sDest, titem_id, true, Item::GenerateItemRandomPropertyId(titem_id)); + return true; // stored + } + + // item can't be added + sLog.outError("STORAGE: Can't equip or store initial item %u for race %u class %u , error msg = %u", titem_id, getRace(), getClass(), msg); + return false; +} + +// helper function, mainly for script side, but can be used for simple task in mangos also. +Item* Player::StoreNewItemInInventorySlot(uint32 itemEntry, uint32 amount) +{ + ItemPosCountVec vDest; + + uint8 msg = CanStoreNewItem(INVENTORY_SLOT_BAG_0, NULL_SLOT, vDest, itemEntry, amount); + + if (msg == EQUIP_ERR_OK) + { + if (Item* pItem = StoreNewItem(vDest, itemEntry, true, Item::GenerateItemRandomPropertyId(itemEntry))) + return pItem; + } + + return NULL; +} + +void Player::SendMirrorTimer(MirrorTimerType Type, uint32 MaxValue, uint32 CurrentValue, int32 Regen) +{ + if (int(MaxValue) == DISABLED_MIRROR_TIMER) + { + if (int(CurrentValue) != DISABLED_MIRROR_TIMER) + StopMirrorTimer(Type); + return; + } + WorldPacket data(SMSG_START_MIRROR_TIMER, (21)); + data << (uint32)Type; + data << CurrentValue; + data << MaxValue; + data << Regen; + data << (uint8)0; + data << (uint32)0; // spell id + GetSession()->SendPacket(&data); +} + +void Player::StopMirrorTimer(MirrorTimerType Type) +{ + m_MirrorTimer[Type] = DISABLED_MIRROR_TIMER; + WorldPacket data(SMSG_STOP_MIRROR_TIMER, 4); + data << (uint32)Type; + GetSession()->SendPacket(&data); +} + +uint32 Player::EnvironmentalDamage(EnviromentalDamage type, uint32 damage) +{ + if (!isAlive() || isGameMaster()) + return 0; + + // Absorb, resist some environmental damage type + uint32 absorb = 0; + uint32 resist = 0; + if (type == DAMAGE_LAVA) + CalculateDamageAbsorbAndResist(this, SPELL_SCHOOL_MASK_FIRE, DIRECT_DAMAGE, damage, &absorb, &resist); + else if (type == DAMAGE_SLIME) + CalculateDamageAbsorbAndResist(this, SPELL_SCHOOL_MASK_NATURE, DIRECT_DAMAGE, damage, &absorb, &resist); + + damage -= absorb + resist; + + DealDamageMods(this, damage, &absorb); + + WorldPacket data(SMSG_ENVIRONMENTALDAMAGELOG, (21)); + data << GetObjectGuid(); + data << uint8(type != DAMAGE_FALL_TO_VOID ? type : DAMAGE_FALL); + data << uint32(damage); + data << uint32(absorb); + data << uint32(resist); + SendMessageToSet(&data, true); + + uint32 final_damage = DealDamage(this, damage, NULL, SELF_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); + + if (!isAlive()) + { + if (type == DAMAGE_FALL) // DealDamage not apply item durability loss at self damage + { + DEBUG_LOG("We are fall to death, loosing 10 percents durability"); + DurabilityLossAll(0.10f, false); + // durability lost message + WorldPacket data2(SMSG_DURABILITY_DAMAGE_DEATH, 0); + GetSession()->SendPacket(&data2); + } + + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATHS_FROM, 1, type); + } + + return final_damage; +} + +int32 Player::getMaxTimer(MirrorTimerType timer) +{ + switch (timer) + { + case FATIGUE_TIMER: + if (GetSession()->GetSecurity() >= (AccountTypes)sWorld.getConfig(CONFIG_UINT32_TIMERBAR_FATIGUE_GMLEVEL)) + return DISABLED_MIRROR_TIMER; + return sWorld.getConfig(CONFIG_UINT32_TIMERBAR_FATIGUE_MAX) * IN_MILLISECONDS; + case BREATH_TIMER: + { + if (!isAlive() || HasAuraType(SPELL_AURA_WATER_BREATHING) || + GetSession()->GetSecurity() >= (AccountTypes)sWorld.getConfig(CONFIG_UINT32_TIMERBAR_BREATH_GMLEVEL)) + return DISABLED_MIRROR_TIMER; + int32 UnderWaterTime = sWorld.getConfig(CONFIG_UINT32_TIMERBAR_BREATH_MAX) * IN_MILLISECONDS; + AuraList const& mModWaterBreathing = GetAurasByType(SPELL_AURA_MOD_WATER_BREATHING); + for (AuraList::const_iterator i = mModWaterBreathing.begin(); i != mModWaterBreathing.end(); ++i) + UnderWaterTime = uint32(UnderWaterTime * (100.0f + (*i)->GetModifier()->m_amount) / 100.0f); + return UnderWaterTime; + } + case FIRE_TIMER: + { + if (!isAlive() || GetSession()->GetSecurity() >= (AccountTypes)sWorld.getConfig(CONFIG_UINT32_TIMERBAR_FIRE_GMLEVEL)) + return DISABLED_MIRROR_TIMER; + return sWorld.getConfig(CONFIG_UINT32_TIMERBAR_FIRE_MAX) * IN_MILLISECONDS; + } + default: + return 0; + } + return 0; +} + +void Player::UpdateMirrorTimers() +{ + // Desync flags for update on next HandleDrowning + if (m_MirrorTimerFlags) + m_MirrorTimerFlagsLast = ~m_MirrorTimerFlags; +} + +void Player::HandleDrowning(uint32 time_diff) +{ + if (!m_MirrorTimerFlags) + return; + + // In water + if (m_MirrorTimerFlags & UNDERWATER_INWATER) + { + // Breath timer not activated - activate it + if (m_MirrorTimer[BREATH_TIMER] == DISABLED_MIRROR_TIMER) + { + m_MirrorTimer[BREATH_TIMER] = getMaxTimer(BREATH_TIMER); + SendMirrorTimer(BREATH_TIMER, m_MirrorTimer[BREATH_TIMER], m_MirrorTimer[BREATH_TIMER], -1); + } + else + { + m_MirrorTimer[BREATH_TIMER] -= time_diff; + // Timer limit - need deal damage + if (m_MirrorTimer[BREATH_TIMER] < 0) + { + m_MirrorTimer[BREATH_TIMER] += 2 * IN_MILLISECONDS; + // Calculate and deal damage + // TODO: Check this formula + uint32 damage = GetMaxHealth() / 5 + urand(0, getLevel() - 1); + EnvironmentalDamage(DAMAGE_DROWNING, damage); + } + else if (!(m_MirrorTimerFlagsLast & UNDERWATER_INWATER)) // Update time in client if need + SendMirrorTimer(BREATH_TIMER, getMaxTimer(BREATH_TIMER), m_MirrorTimer[BREATH_TIMER], -1); + } + } + else if (m_MirrorTimer[BREATH_TIMER] != DISABLED_MIRROR_TIMER) // Regen timer + { + int32 UnderWaterTime = getMaxTimer(BREATH_TIMER); + // Need breath regen + m_MirrorTimer[BREATH_TIMER] += 10 * time_diff; + if (m_MirrorTimer[BREATH_TIMER] >= UnderWaterTime || !isAlive()) + StopMirrorTimer(BREATH_TIMER); + else if (m_MirrorTimerFlagsLast & UNDERWATER_INWATER) + SendMirrorTimer(BREATH_TIMER, UnderWaterTime, m_MirrorTimer[BREATH_TIMER], 10); + } + + // In dark water + if (m_MirrorTimerFlags & UNDERWATER_INDARKWATER) + { + // Fatigue timer not activated - activate it + if (m_MirrorTimer[FATIGUE_TIMER] == DISABLED_MIRROR_TIMER) + { + m_MirrorTimer[FATIGUE_TIMER] = getMaxTimer(FATIGUE_TIMER); + SendMirrorTimer(FATIGUE_TIMER, m_MirrorTimer[FATIGUE_TIMER], m_MirrorTimer[FATIGUE_TIMER], -1); + } + else + { + m_MirrorTimer[FATIGUE_TIMER] -= time_diff; + // Timer limit - need deal damage or teleport ghost to graveyard + if (m_MirrorTimer[FATIGUE_TIMER] < 0) + { + m_MirrorTimer[FATIGUE_TIMER] += 2 * IN_MILLISECONDS; + if (isAlive()) // Calculate and deal damage + { + uint32 damage = GetMaxHealth() / 5 + urand(0, getLevel() - 1); + EnvironmentalDamage(DAMAGE_EXHAUSTED, damage); + } + else if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) // Teleport ghost to graveyard + RepopAtGraveyard(); + } + else if (!(m_MirrorTimerFlagsLast & UNDERWATER_INDARKWATER)) + SendMirrorTimer(FATIGUE_TIMER, getMaxTimer(FATIGUE_TIMER), m_MirrorTimer[FATIGUE_TIMER], -1); + } + } + else if (m_MirrorTimer[FATIGUE_TIMER] != DISABLED_MIRROR_TIMER) // Regen timer + { + int32 DarkWaterTime = getMaxTimer(FATIGUE_TIMER); + m_MirrorTimer[FATIGUE_TIMER] += 10 * time_diff; + if (m_MirrorTimer[FATIGUE_TIMER] >= DarkWaterTime || !isAlive()) + StopMirrorTimer(FATIGUE_TIMER); + else if (m_MirrorTimerFlagsLast & UNDERWATER_INDARKWATER) + SendMirrorTimer(FATIGUE_TIMER, DarkWaterTime, m_MirrorTimer[FATIGUE_TIMER], 10); + } + + if (m_MirrorTimerFlags & (UNDERWATER_INLAVA /*| UNDERWATER_INSLIME*/) && !(m_lastLiquid && m_lastLiquid->SpellId)) + { + // Breath timer not activated - activate it + if (m_MirrorTimer[FIRE_TIMER] == DISABLED_MIRROR_TIMER) + m_MirrorTimer[FIRE_TIMER] = getMaxTimer(FIRE_TIMER); + else + { + m_MirrorTimer[FIRE_TIMER] -= time_diff; + if (m_MirrorTimer[FIRE_TIMER] < 0) + { + m_MirrorTimer[FIRE_TIMER] += 2 * IN_MILLISECONDS; + // Calculate and deal damage + // TODO: Check this formula + uint32 damage = urand(600, 700); + if (m_MirrorTimerFlags & UNDERWATER_INLAVA) + EnvironmentalDamage(DAMAGE_LAVA, damage); + // need to skip Slime damage in Undercity, + // maybe someone can find better way to handle environmental damage + //else if (m_zoneUpdateId != 1497) + // EnvironmentalDamage(DAMAGE_SLIME, damage); + } + } + } + else + m_MirrorTimer[FIRE_TIMER] = DISABLED_MIRROR_TIMER; + + // Recheck timers flag + m_MirrorTimerFlags &= ~UNDERWATER_EXIST_TIMERS; + for (int i = 0; i < MAX_TIMERS; ++i) + if (m_MirrorTimer[i] != DISABLED_MIRROR_TIMER) + { + m_MirrorTimerFlags |= UNDERWATER_EXIST_TIMERS; + break; + } + m_MirrorTimerFlagsLast = m_MirrorTimerFlags; +} + +// The player sobers by 1% every 9 seconds +void Player::HandleSobering() +{ + uint8 currentDrunkValue = GetDrunkValue(); + if (currentDrunkValue) + { + --currentDrunkValue; + SetDrunkValue(currentDrunkValue); + } +} + +DrunkenState Player::GetDrunkenstateByValue(uint8 value) +{ + if (value >= 90) + return DRUNKEN_SMASHED; + if (value >= 50) + return DRUNKEN_DRUNK; + if (value) + return DRUNKEN_TIPSY; + return DRUNKEN_SOBER; +} + +void Player::SetDrunkValue(uint8 newDrunkValue, uint32 itemId /*= 0*/) +{ + if (newDrunkValue > 100) + newDrunkValue = 100; + + if (newDrunkValue < GetDrunkValue()) + m_drunkTimer = 0; // reset sobering timer + + uint32 oldDrunkenState = Player::GetDrunkenstateByValue(GetDrunkValue()); + + SetByteValue(PLAYER_BYTES_3, 1, newDrunkValue); + + uint32 newDrunkenState = Player::GetDrunkenstateByValue(newDrunkValue); + + // special drunk invisibility detection + if (newDrunkenState >= DRUNKEN_DRUNK) + m_detectInvisibilityMask |= (1 << 6); + else + m_detectInvisibilityMask &= ~(1 << 6); + + if (newDrunkenState == oldDrunkenState) + return; + + WorldPacket data(SMSG_CROSSED_INEBRIATION_THRESHOLD, (8 + 4 + 4)); + data << GetObjectGuid(); + data << uint32(newDrunkenState); + data << uint32(itemId); + + SendMessageToSet(&data, true); +} + +void Player::Update(uint32 update_diff, uint32 p_time) +{ + if (!IsInWorld()) + return; + + // Remove failed timed Achievements + GetAchievementMgr().DoFailedTimedAchievementCriterias(); + + // Undelivered mail + if (m_nextMailDelivereTime && m_nextMailDelivereTime <= time(NULL)) + { + SendNewMail(); + ++unReadMails; + + // It will be recalculate at mailbox open (for unReadMails important non-0 until mailbox open, it also will be recalculated) + m_nextMailDelivereTime = 0; + } + + // Used to implement delayed far teleports + SetCanDelayTeleport(true); + Unit::Update(update_diff, p_time); + SetCanDelayTeleport(false); + + // Update player only attacks + if (uint32 ranged_att = getAttackTimer(RANGED_ATTACK)) + setAttackTimer(RANGED_ATTACK, (update_diff >= ranged_att ? 0 : ranged_att - update_diff)); + + time_t now = time(NULL); + + UpdatePvPFlag(now); + + UpdateContestedPvP(update_diff); + + UpdateDuelFlag(now); + + CheckDuelDistance(now); + + UpdateAfkReport(now); + + // Update items that have just a limited lifetime + if (now > m_Last_tick) + UpdateItemDuration(uint32(now - m_Last_tick)); + + if (!m_timedquests.empty()) + { + QuestSet::iterator iter = m_timedquests.begin(); + while (iter != m_timedquests.end()) + { + QuestStatusData& q_status = mQuestStatus[*iter]; + if (q_status.m_timer <= update_diff) + { + uint32 quest_id = *iter; + ++iter; // Current iter will be removed in FailQuest + FailQuest(quest_id); + } + else + { + q_status.m_timer -= update_diff; + if (q_status.uState != QUEST_NEW) q_status.uState = QUEST_CHANGED; + ++iter; + } + } + } + + if (hasUnitState(UNIT_STAT_MELEE_ATTACKING)) + { + UpdateMeleeAttackingState(); + + Unit* pVictim = getVictim(); + if (pVictim && !IsNonMeleeSpellCasted(false)) + { + Player* vOwner = pVictim->GetCharmerOrOwnerPlayerOrPlayerItself(); + if (vOwner && vOwner->IsPvP() && !IsInDuelWith(vOwner)) + { + UpdatePvP(true); + RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); + } + } + } + + if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING)) + { + if (roll_chance_i(3) && GetTimeInnEnter() > 0) // Freeze update + { + time_t time_inn = time(NULL) - GetTimeInnEnter(); + if (time_inn >= 10) // Freeze update + { + float bubble = 0.125f * sWorld.getConfig(CONFIG_FLOAT_RATE_REST_INGAME); + // Speed collect rest bonus (section/in hour) + SetRestBonus(float(GetRestBonus() + time_inn * (GetUInt32Value(PLAYER_NEXT_LEVEL_XP) / 72000) * bubble)); + UpdateInnerTime(time(NULL)); + } + } + } + + if (m_regenTimer) + { + if (update_diff >= m_regenTimer) + m_regenTimer = 0; + else + m_regenTimer -= update_diff; + } + + if (m_positionStatusUpdateTimer) + { + if (update_diff >= m_positionStatusUpdateTimer) + m_positionStatusUpdateTimer = 0; + else + m_positionStatusUpdateTimer -= update_diff; + } + + if (m_weaponChangeTimer > 0) + { + if (update_diff >= m_weaponChangeTimer) + m_weaponChangeTimer = 0; + else + m_weaponChangeTimer -= update_diff; + } + + if (m_zoneUpdateTimer > 0) + { + if (update_diff >= m_zoneUpdateTimer) + { + uint32 newzone, newarea; + GetZoneAndAreaId(newzone, newarea); + + if (m_zoneUpdateId != newzone) + UpdateZone(newzone, newarea); // Also update area + else + { + // Use area updates as well + // Needed for free for all arenas for example + if (m_areaUpdateId != newarea) + UpdateArea(newarea); + + m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL; + } + } + else + m_zoneUpdateTimer -= update_diff; + } + + if (m_timeSyncTimer > 0) + { + if (update_diff >= m_timeSyncTimer) + SendTimeSync(); + else + m_timeSyncTimer -= update_diff; + } + + if (isAlive()) + { + if (!HasAuraType(SPELL_AURA_STOP_NATURAL_MANA_REGEN)) + SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_REGENERATE_POWER); + + if (!m_regenTimer) + RegenerateAll(); + } + + if (m_deathState == JUST_DIED) + KillPlayer(); + + if (m_nextSave > 0) + { + if (update_diff >= m_nextSave) + { + // m_nextSave reseted in SaveToDB call + SaveToDB(); + DETAIL_LOG("Player '%s' (GUID: %u) saved", GetName(), GetGUIDLow()); + } + else + m_nextSave -= update_diff; + } + + // Handle Water/drowning + HandleDrowning(update_diff); + + // Handle detect stealth players + if (m_DetectInvTimer > 0) + { + if (update_diff >= m_DetectInvTimer) + { + HandleStealthedUnitsDetection(); + m_DetectInvTimer = 3000; + } + else + m_DetectInvTimer -= update_diff; + } + + // Played time + if (now > m_Last_tick) + { + uint32 elapsed = uint32(now - m_Last_tick); + m_Played_time[PLAYED_TIME_TOTAL] += elapsed; // Total played time + m_Played_time[PLAYED_TIME_LEVEL] += elapsed; // Level played time + m_Last_tick = now; + } + + if (GetDrunkValue()) + { + m_drunkTimer += update_diff; + + if (m_drunkTimer > 9 * IN_MILLISECONDS) + HandleSobering(); + } + + // Not auto-free ghost from body in instances + if (m_deathTimer > 0 && !GetMap()->Instanceable()) + { + if (p_time >= m_deathTimer) + { + m_deathTimer = 0; + BuildPlayerRepop(); + RepopAtGraveyard(); + } + else + m_deathTimer -= p_time; + } + + UpdateEnchantTime(update_diff); + UpdateHomebindTime(update_diff); + + // Group update + SendUpdateToOutOfRangeGroupMembers(); + + Pet* pet = GetPet(); + if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityDistance()) && (GetCharmGuid() && (pet->GetObjectGuid() != GetCharmGuid()))) + pet->Unsummon(PET_SAVE_REAGENTS, this); + + if (IsHasDelayedTeleport()) + TeleportTo(m_teleport_dest, m_teleport_options); +} + +void Player::SetDeathState(DeathState s) +{ + uint32 ressSpellId = 0; + + bool cur = isAlive(); + + if (s == JUST_DIED && cur) + { + // drunken state is cleared on death + SetDrunkValue(0); + // lost combo points at any target (targeted combo points clear in Unit::SetDeathState) + ClearComboPoints(); + + clearResurrectRequestData(); + + // remove form before other mods to prevent incorrect stats calculation + RemoveSpellsCausingAura(SPELL_AURA_MOD_SHAPESHIFT); + + // FIXME: is pet dismissed at dying or releasing spirit? if second, add SetDeathState(DEAD) to HandleRepopRequestOpcode and define pet unsummon here with (s == DEAD) + RemovePet(PET_SAVE_REAGENTS); + + // save value before aura remove in Unit::SetDeathState + ressSpellId = GetUInt32Value(PLAYER_SELF_RES_SPELL); + + // passive spell + if (!ressSpellId) + ressSpellId = GetResurrectionSpellId(); + + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP, 1); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATH, 1); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATH_IN_DUNGEON, 1); + + if (InstanceData* mapInstance = GetInstanceData()) + mapInstance->OnPlayerDeath(this); + } + + Unit::SetDeathState(s); + + // restore resurrection spell id for player after aura remove + if (s == JUST_DIED && cur && ressSpellId) + SetUInt32Value(PLAYER_SELF_RES_SPELL, ressSpellId); + + if (isAlive() && !cur) + { + // clear aura case after resurrection by another way (spells will be applied before next death) + SetUInt32Value(PLAYER_SELF_RES_SPELL, 0); + + // restore default warrior stance + if (getClass() == CLASS_WARRIOR) + CastSpell(this, SPELL_ID_PASSIVE_BATTLE_STANCE, true); + } +} + +bool Player::BuildEnumData(QueryResult* result, ByteBuffer* data, ByteBuffer* buffer) +{ + // 0 1 2 3 4 5 6 7 + // "SELECT characters.guid, characters.name, characters.race, characters.class, characters.gender, characters.playerBytes, characters.playerBytes2, characters.level, " + // 8 9 10 11 12 13 14 + // "characters.zone, characters.map, characters.position_x, characters.position_y, characters.position_z, guild_member.guildid, characters.playerFlags, " + // 15 16 17 18 19 20 21 + // "characters.at_login, character_pet.entry, character_pet.modelid, character_pet.level, characters.equipmentCache, characters.slot, character_declinedname.genitive " + + Field* fields = result->Fetch(); + ObjectGuid guid = ObjectGuid(HIGHGUID_PLAYER, fields[0].GetUInt32()); + uint8 pRace = fields[2].GetUInt8(); + uint8 pClass = fields[3].GetUInt8(); + + PlayerInfo const* info = sObjectMgr.GetPlayerInfo(pRace, pClass); + if (!info) + { + sLog.outError("%s has incorrect race/class pair. Don't build enum.", guid.GetString().c_str()); + return false; + } + + ObjectGuid guildGuid = ObjectGuid(HIGHGUID_GUILD, fields[13].GetUInt32()); + std::string name = fields[1].GetCppString(); + uint8 gender = fields[4].GetUInt8(); + uint32 playerBytes = fields[5].GetUInt32(); + uint8 level = fields[7].GetUInt8(); + uint32 playerFlags = fields[14].GetUInt32(); + uint32 atLoginFlags = fields[15].GetUInt32(); + uint32 zone = fields[8].GetUInt32(); + uint32 petDisplayId = 0; + uint32 petLevel = 0; + uint32 petFamily = 0; + uint32 char_flags = 0; + + data->WriteGuidMask<3>(guid); + data->WriteGuidMask<1, 7, 2>(guildGuid); + data->WriteBits(name.length(), 7); + data->WriteGuidMask<4, 7>(guid); + data->WriteGuidMask<3>(guildGuid); + data->WriteGuidMask<5>(guid); + data->WriteGuidMask<6>(guildGuid); + data->WriteGuidMask<1>(guid); + data->WriteGuidMask<5, 4>(guildGuid); + data->WriteBit(atLoginFlags & AT_LOGIN_FIRST); + data->WriteGuidMask<0, 2, 6>(guid); + data->WriteGuidMask<0>(guildGuid); + + // show pet at selection character in character list only for non-ghost character + if (result && !(playerFlags & PLAYER_FLAGS_GHOST) && (pClass == CLASS_WARLOCK || pClass == CLASS_HUNTER || pClass == CLASS_DEATH_KNIGHT)) + { + uint32 entry = fields[16].GetUInt32(); + CreatureInfo const* cInfo = sCreatureStorage.LookupEntry(entry); + if(cInfo) + { + petDisplayId = fields[17].GetUInt32(); + petLevel = fields[18].GetUInt32(); + petFamily = cInfo->family; + } + } + + if(playerFlags & PLAYER_FLAGS_HIDE_HELM) + char_flags |= CHARACTER_FLAG_HIDE_HELM; + if(playerFlags & PLAYER_FLAGS_HIDE_CLOAK) + char_flags |= CHARACTER_FLAG_HIDE_CLOAK; + if(playerFlags & PLAYER_FLAGS_GHOST) + char_flags |= CHARACTER_FLAG_GHOST; + if(atLoginFlags & AT_LOGIN_RENAME) + char_flags |= CHARACTER_FLAG_RENAME; + if(sWorld.getConfig(CONFIG_BOOL_DECLINED_NAMES_USED)) + { + if(!fields[21].GetCppString().empty()) + char_flags |= CHARACTER_FLAG_DECLINED; + } + else + char_flags |= CHARACTER_FLAG_DECLINED; + + *buffer << uint8(pClass); // class + + Tokens tdata = StrSplit(fields[19].GetCppString(), " "); + for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot) + { + uint32 visualbase = slot * 2; + uint32 item_id = GetUInt32ValueFromArray(tdata, visualbase); + const ItemPrototype * proto = ObjectMgr::GetItemPrototype(item_id); + if(!proto) + { + *buffer << uint8(0); + *buffer << uint32(0); + *buffer << uint32(0); + continue; + } + + SpellItemEnchantmentEntry const *enchant = NULL; + + uint32 enchants = GetUInt32ValueFromArray(tdata, visualbase + 1); + for(uint8 enchantSlot = PERM_ENCHANTMENT_SLOT; enchantSlot <= TEMP_ENCHANTMENT_SLOT; ++enchantSlot) + { + // values stored in 2 uint16 + uint32 enchantId = 0x0000FFFF & (enchants >> enchantSlot*16); + if(!enchantId) + continue; + + if ((enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId))) + break; + } + + *buffer << uint8(proto->InventoryType); + *buffer << uint32(proto->DisplayInfoID); + *buffer << uint32(enchant ? enchant->aura_id : 0); + } + + for (int32 i = 0; i < 4; i++) + { + *buffer << uint8(0); + *buffer << uint32(0); + *buffer << uint32(0); + } + + *buffer << uint32(petFamily); // Pet DisplayID + buffer->WriteGuidBytes<2>(guildGuid); + *buffer << uint8(fields[20].GetUInt8()); // char order id + *buffer << uint8((playerBytes >> 16) & 0xFF); // Hair style + buffer->WriteGuidBytes<3>(guildGuid); + *buffer << uint32(petDisplayId); // Pet DisplayID + *buffer << uint32(char_flags); // character flags + *buffer << uint8((playerBytes >> 24) & 0xFF); // Hair color + buffer->WriteGuidBytes<4>(guid); + *buffer << uint32(fields[9].GetUInt32()); // map + buffer->WriteGuidBytes<5>(guildGuid); + *buffer << fields[12].GetFloat(); // z + buffer->WriteGuidBytes<6>(guildGuid); + *buffer << uint32(petLevel); // pet level + buffer->WriteGuidBytes<3>(guid); + *buffer << fields[11].GetFloat(); // y + // character customize flags + *buffer << uint32(atLoginFlags & AT_LOGIN_CUSTOMIZE ? CHAR_CUSTOMIZE_FLAG_CUSTOMIZE : CHAR_CUSTOMIZE_FLAG_NONE); + + uint32 playerBytes2 = fields[6].GetUInt32(); + *buffer << uint8(playerBytes2 & 0xFF); // facial hair + buffer->WriteGuidBytes<7>(guid); + *buffer << uint8(gender); // Gender + buffer->append(name.c_str(), name.length()); + *buffer << uint8((playerBytes >> 8) & 0xFF); // face + + buffer->WriteGuidBytes<0, 2>(guid); + buffer->WriteGuidBytes<1, 7>(guildGuid); + + *buffer << fields[10].GetFloat(); // x + *buffer << uint8(playerBytes & 0xFF); // skin + *buffer << uint8(pRace); // Race + *buffer << uint8(level); // Level + buffer->WriteGuidBytes<6>(guid); + buffer->WriteGuidBytes<4, 0>(guildGuid); + buffer->WriteGuidBytes<5, 1>(guid); + + *buffer << uint32(zone); // Zone id + + return true; +} + +void Player::ToggleAFK() +{ + ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_AFK); + + // afk player not allowed in battleground + if (isAFK() && InBattleGround() && !InArena()) + LeaveBattleground(); +} + +void Player::ToggleDND() +{ + ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_DND); +} + +uint8 Player::GetChatTag() const +{ + uint8 tag = CHAT_TAG_NONE; + + if (isAFK()) + tag |= CHAT_TAG_AFK; + if (isDND()) + tag |= CHAT_TAG_DND; + if (isGMChat()) + tag |= CHAT_TAG_GM; + if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_COMMENTATOR)) + tag |= CHAT_TAG_COM; + if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_DEVELOPER)) + tag |= CHAT_TAG_DEV; + + return tag; +} + +void Player::SendTeleportPacket(float oldX, float oldY, float oldZ, float oldO) +{ + ObjectGuid guid = GetObjectGuid(); + ObjectGuid transportGuid = m_movementInfo.GetTransportGuid(); + + WorldPacket data(SMSG_MOVE_TELEPORT, 38); + data.WriteGuidMask<6, 0, 3, 2>(guid); + data.WriteBit(0); // unknown + data.WriteBit(!transportGuid.IsEmpty()); + data.WriteGuidMask<1>(guid); + if (transportGuid) + data.WriteGuidMask<1, 3, 2, 5, 0, 7, 6, 4>(transportGuid); + + data.WriteGuidMask<4, 7, 5>(guid); + + if (transportGuid) + data.WriteGuidBytes<5, 6, 1, 7, 0, 2, 4, 3>(transportGuid); + + data << uint32(0); // counter + data.WriteGuidBytes<1, 2, 3, 5>(guid); + data << float(GetPositionX()); + data.WriteGuidBytes<4>(guid); + data << float(GetOrientation()); + data.WriteGuidBytes<7>(guid); + data << float(GetPositionZ()); + data.WriteGuidBytes<0, 6>(guid); + data << float(GetPositionY()); + + Relocate(oldX, oldY, oldZ, oldO); + SendDirectMessage(&data); +} + +bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientation, uint32 options /*=0*/, AreaTrigger const* at /*=NULL*/) +{ + orientation = NormalizeOrientation(orientation); + + if (!MapManager::IsValidMapCoord(mapid, x, y, z, orientation)) + { + sLog.outError("TeleportTo: invalid map %d or absent instance template.", mapid); + return false; + } + + MapEntry const* mEntry = sMapStore.LookupEntry(mapid); // Validity checked in IsValidMapCoord + + // preparing unsummon pet if lost (we must get pet before teleportation or will not find it later) + Pet* pet = GetPet(); + + // don't let enter battlegrounds without assigned battleground id (for example through areatrigger)... + // don't let gm level > 1 either + if (!InBattleGround() && mEntry->IsBattleGroundOrArena()) + return false; + + // Get MapEntrance trigger if teleport to other -nonBG- map + bool assignedAreaTrigger = false; + if (GetMapId() != mapid && !mEntry->IsBattleGroundOrArena() && !at) + { + at = sObjectMgr.GetMapEntranceTrigger(mapid); + assignedAreaTrigger = true; + } + + // Check requirements for teleport + if (at) + { + uint32 miscRequirement = 0; + AreaLockStatus lockStatus = GetAreaTriggerLockStatus(at, GetDifficulty(mEntry->IsRaid()), miscRequirement); + if (lockStatus != AREA_LOCKSTATUS_OK) + { + // Teleport not requested by area-trigger + // TODO - Assume a player with expansion 0 travels from BootyBay to Ratched, and he is attempted to be teleported to outlands + // then he will repop near BootyBay instead of normally continuing his journey + // This code is probably added to catch passengers on ships to northrend who shouldn't go there + if (lockStatus == AREA_LOCKSTATUS_INSUFFICIENT_EXPANSION && !assignedAreaTrigger && GetTransport()) + RepopAtGraveyard(); // Teleport to near graveyard if on transport, looks blizz like :) + + SendTransferAbortedByLockStatus(mEntry, lockStatus, miscRequirement); + return false; + } + } + + if (Group* grp = GetGroup()) // TODO: Verify that this is correct place + grp->SetPlayerMap(GetObjectGuid(), mapid); + + // if we were on a transport, leave + if (!(options & TELE_TO_NOT_LEAVE_TRANSPORT) && m_transport) + { + m_transport->RemovePassenger(this); + m_transport = NULL; + m_movementInfo.ClearTransportData(); + } + + // The player was ported to another map and looses the duel immediately. + // We have to perform this check before the teleport, otherwise the + // ObjectAccessor won't find the flag. + if (duel && GetMapId() != mapid) + if (GetMap()->GetGameObject(GetGuidValue(PLAYER_DUEL_ARBITER))) + DuelComplete(DUEL_FLED); + + // reset movement flags at teleport, because player will continue move with these flags after teleport + m_movementInfo.SetMovementFlags(MOVEFLAG_NONE); + DisableSpline(); + + if ((GetMapId() == mapid) && (!m_transport)) // TODO the !m_transport might have unexpected effects when teleporting from transport to other place on same map + { + // lets reset far teleport flag if it wasn't reset during chained teleports + SetSemaphoreTeleportFar(false); + // setup delayed teleport flag + // if teleport spell is casted in Unit::Update() func + // then we need to delay it until update process will be finished + if (SetDelayedTeleportFlagIfCan()) + { + SetSemaphoreTeleportNear(true); + // lets save teleport destination for player + m_teleport_dest = WorldLocation(mapid, x, y, z, orientation); + m_teleport_options = options; + return true; + } + + if (!(options & TELE_TO_NOT_UNSUMMON_PET)) + { + // same map, only remove pet if out of range for new position + if (pet && !pet->IsWithinDist3d(x, y, z, GetMap()->GetVisibilityDistance())) + UnsummonPetTemporaryIfAny(); + } + + if (!(options & TELE_TO_NOT_LEAVE_COMBAT)) + CombatStop(); + + // this will be used instead of the current location in SaveToDB + m_teleport_dest = WorldLocation(mapid, x, y, z, orientation); + SetFallInformation(0, z); + + // code for finish transfer called in WorldSession::HandleMovementOpcodes() + // at client packet CMSG_MOVE_TELEPORT_ACK + SetSemaphoreTeleportNear(true); + // near teleport, triggering send CMSG_MOVE_TELEPORT_ACK from client at landing + if (!GetSession()->PlayerLogout()) + { + float oldX, oldY, oldZ; + float oldO = GetOrientation(); + GetPosition(oldX, oldY, oldZ);; + Relocate(x, y, z, orientation); + SendTeleportPacket(oldX, oldY, oldZ, oldO); + } + } + else + { + // far teleport to another map + Map* oldmap = IsInWorld() ? GetMap() : NULL; + // check if we can enter before stopping combat / removing pet / totems / interrupting spells + + // If the map is not created, assume it is possible to enter it. + // It will be created in the WorldPortAck. + DungeonPersistentState* state = GetBoundInstanceSaveForSelfOrGroup(mapid); + Map* map = sMapMgr.FindMap(mapid, state ? state->GetInstanceId() : 0); + if (!map || map->CanEnter(this)) + { + // lets reset near teleport flag if it wasn't reset during chained teleports + SetSemaphoreTeleportNear(false); + // setup delayed teleport flag + // if teleport spell is casted in Unit::Update() func + // then we need to delay it until update process will be finished + if (SetDelayedTeleportFlagIfCan()) + { + SetSemaphoreTeleportFar(true); + // lets save teleport destination for player + m_teleport_dest = WorldLocation(mapid, x, y, z, orientation); + m_teleport_options = options; + return true; + } + + SetSelectionGuid(ObjectGuid()); + + CombatStop(); + + ResetContestedPvP(); + + // remove player from battleground on far teleport (when changing maps) + if (BattleGround const* bg = GetBattleGround()) + { + // Note: at battleground join battleground id set before teleport + // and we already will found "current" battleground + // just need check that this is targeted map or leave + if (bg->GetMapId() != mapid) + LeaveBattleground(false); // don't teleport to entry point + } + + // remove pet on map change + if (pet) + UnsummonPetTemporaryIfAny(); + + // remove vehicle accessories on map change + if (IsVehicle()) + GetVehicleInfo()->RemoveAccessoriesFromMap(); + + // remove all dyn objects + RemoveAllDynObjects(); + + // stop spellcasting + // not attempt interrupt teleportation spell at caster teleport + if (!(options & TELE_TO_SPELL)) + if (IsNonMeleeSpellCasted(true)) + InterruptNonMeleeSpells(true); + + // remove auras before removing from map... + RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_CHANGE_MAP | AURA_INTERRUPT_FLAG_MOVE | AURA_INTERRUPT_FLAG_TURNING); + + if (!GetSession()->PlayerLogout()) + { + // send transfer packet to display load screen + WorldPacket data(SMSG_TRANSFER_PENDING, (4 + 4 + 4)); + data.WriteBit(0); // unknown + if (m_transport) + { + data.WriteBit(1); // has transport + data << uint32(GetMapId()); + data << uint32(m_transport->GetEntry()); + } + else + data.WriteBit(0); // has transport + + data << uint32(mapid); + GetSession()->SendPacket(&data); + } + + // remove from old map now + if (oldmap) + oldmap->Remove(this, false); + + // new final coordinates + float final_x = x; + float final_y = y; + float final_z = z; + float final_o = orientation; + + if (m_transport) + { + final_x += m_movementInfo.GetTransportPos()->x; + final_y += m_movementInfo.GetTransportPos()->y; + final_z += m_movementInfo.GetTransportPos()->z; + final_o += m_movementInfo.GetTransportPos()->o; + } + + m_teleport_dest = WorldLocation(mapid, final_x, final_y, final_z, final_o); + SetFallInformation(0, final_z); + // if the player is saved before worldport ack (at logout for example) + // this will be used instead of the current location in SaveToDB + + // move packet sent by client always after far teleport + // code for finish transfer to new map called in WorldSession::HandleMoveWorldportAckOpcode at client packet + SetSemaphoreTeleportFar(true); + + if (!GetSession()->PlayerLogout()) + { + // transfer finished, inform client to start load + WorldPacket data(SMSG_NEW_WORLD, 20); + if (m_transport) + { + data << float(m_movementInfo.GetTransportPos()->x); + data << float(NormalizeOrientation(m_movementInfo.GetTransportPos()->o)); + data << float(m_movementInfo.GetTransportPos()->y); + } + else + { + data << float(final_x); + data << float(NormalizeOrientation(final_o)); + data << float(final_y); + } + + data << uint32(mapid); + + if (m_transport) + data << float(m_movementInfo.GetTransportPos()->z); + else + data << float(final_z); + + GetSession()->SendPacket(&data); + SendSavedInstances(); + } + } + else // !map->CanEnter(this) + return false; + } + return true; +} + +bool Player::TeleportToBGEntryPoint() +{ + ScheduleDelayedOperation(DELAYED_BG_MOUNT_RESTORE); + ScheduleDelayedOperation(DELAYED_BG_TAXI_RESTORE); + return TeleportTo(m_bgData.joinPos); +} + +void Player::ProcessDelayedOperations() +{ + if (m_DelayedOperations == 0) + return; + + if (m_DelayedOperations & DELAYED_RESURRECT_PLAYER) + { + ResurrectPlayer(0.0f, false); + + if (GetMaxHealth() > m_resurrectHealth) + SetHealth(m_resurrectHealth); + else + SetHealth(GetMaxHealth()); + + if (GetMaxPower(POWER_MANA) > m_resurrectMana) + SetPower(POWER_MANA, m_resurrectMana); + else + SetPower(POWER_MANA, GetMaxPower(POWER_MANA)); + + SetPower(POWER_RAGE, 0); + SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY)); + + SpawnCorpseBones(); + } + + if (m_DelayedOperations & DELAYED_SAVE_PLAYER) + { + SaveToDB(); + } + + if (m_DelayedOperations & DELAYED_SPELL_CAST_DESERTER) + { + CastSpell(this, 26013, true); // Deserter + } + + if (m_DelayedOperations & DELAYED_BG_MOUNT_RESTORE) + { + if (m_bgData.mountSpell) + { + CastSpell(this, m_bgData.mountSpell, true); + m_bgData.mountSpell = 0; + } + } + + if (m_DelayedOperations & DELAYED_BG_TAXI_RESTORE) + { + if (m_bgData.HasTaxiPath()) + { + m_taxi.AddTaxiDestination(m_bgData.taxiPath[0]); + m_taxi.AddTaxiDestination(m_bgData.taxiPath[1]); + m_bgData.ClearTaxiPath(); + + ContinueTaxiFlight(); + } + } + + // we have executed ALL delayed ops, so clear the flag + m_DelayedOperations = 0; +} + +void Player::AddToWorld() +{ + ///- Do not add/remove the player from the object storage + ///- It will crash when updating the ObjectAccessor + ///- The player should only be added when logging in + Unit::AddToWorld(); + + for (int i = PLAYER_SLOT_START; i < PLAYER_SLOT_END; ++i) + { + if (m_items[i]) + m_items[i]->AddToWorld(); + } +} + +void Player::RemoveFromWorld() +{ + for (int i = PLAYER_SLOT_START; i < PLAYER_SLOT_END; ++i) + { + if (m_items[i]) + m_items[i]->RemoveFromWorld(); + } + + ///- Do not add/remove the player from the object storage + ///- It will crash when updating the ObjectAccessor + ///- The player should only be removed when logging out + if (IsInWorld()) + GetCamera().ResetView(); + + Unit::RemoveFromWorld(); +} + +void Player::RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker) +{ + float addRage; + + float rageconversion = float((0.0091107836 * getLevel() * getLevel()) + 3.225598133 * getLevel()) + 4.2652911f; + + if (attacker) + { + addRage = ((damage / rageconversion * 7.5f + weaponSpeedHitFactor) / 2.0f); + + // talent who gave more rage on attack + addRage *= 1.0f + GetTotalAuraModifier(SPELL_AURA_MOD_RAGE_FROM_DAMAGE_DEALT) / 100.0f; + } + else + { + addRage = damage / rageconversion * 2.5f; + + // Berserker Rage effect + if (HasAura(18499, EFFECT_INDEX_0)) + addRage *= 1.3f; + } + + addRage *= sWorld.getConfig(CONFIG_FLOAT_RATE_POWER_RAGE_INCOME); + + ModifyPower(POWER_RAGE, uint32(addRage * 10)); +} + +void Player::RegenerateAll(uint32 diff) +{ + // Not in combat or they have regeneration + if (!isInCombat() || HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT) || + HasAuraType(SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT) || IsPolymorphed() || m_baseHealthRegen) + { + RegenerateHealth(diff); + if (!isInCombat() && !HasAuraType(SPELL_AURA_INTERRUPT_REGEN)) + { + Regenerate(POWER_RAGE, diff); + if (getClass() == CLASS_DEATH_KNIGHT) + Regenerate(POWER_RUNIC_POWER, diff); + } + } + + Regenerate(POWER_ENERGY, diff); + + Regenerate(POWER_MANA, diff); + + if (getClass() == CLASS_DEATH_KNIGHT) + Regenerate(POWER_RUNE, diff); + + if (getClass() == CLASS_HUNTER) + Regenerate(POWER_FOCUS, diff); + + if (getClass() == CLASS_PALADIN) + { + if (isInCombat()) + ResetHolyPowerRegenTimer(); + else if (m_holyPowerRegenTimer <= diff) + m_holyPowerRegenTimer = 0; + else + m_holyPowerRegenTimer -= diff; + + if (!m_holyPowerRegenTimer) + { + Regenerate(POWER_HOLY_POWER, diff); + ResetHolyPowerRegenTimer(); + } + } + + m_regenTimer = REGEN_TIME_FULL; +} + +// diff contains the time in milliseconds since last regen. +void Player::Regenerate(Powers power, uint32 diff) +{ + uint32 powerIndex = GetPowerIndex(power); + uint32 maxValue, curValue; + if (powerIndex == INVALID_POWER_INDEX) + return; + + if (power != POWER_RUNE) + { + maxValue = GetMaxPowerByIndex(powerIndex); + if (!maxValue) + return; + + curValue = GetPowerByIndex(powerIndex); + } + + float addvalue = 0.0f; + + switch (power) + { + case POWER_MANA: + { + if (HasAuraType(SPELL_AURA_STOP_NATURAL_MANA_REGEN)) + break; + float ManaIncreaseRate = sWorld.getConfig(CONFIG_FLOAT_RATE_POWER_MANA); + + if (isInCombat()) + { + // Mangos Updates Mana in intervals of 2s, which is correct + addvalue = GetFloatValue(UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER) * ManaIncreaseRate * 2.00f; + } + else + addvalue = GetFloatValue(UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER) * ManaIncreaseRate * 2.00f; + + break; + } + case POWER_RAGE: // Regenerate rage + { + float RageDecreaseRate = sWorld.getConfig(CONFIG_FLOAT_RATE_POWER_RAGE_LOSS); + addvalue = 20 * RageDecreaseRate; // 2 rage by tick (= 2 seconds => 1 rage/sec) + break; + } + case POWER_FOCUS: + addvalue = 12; + break; + case POWER_HOLY_POWER: + if (!m_holyPowerRegenTimer) + addvalue = 1; + else + return; + break; + case POWER_ENERGY: // Regenerate energy + { + float EnergyRate = sWorld.getConfig(CONFIG_FLOAT_RATE_POWER_ENERGY); + addvalue = 20 * EnergyRate; + break; + } + case POWER_RUNIC_POWER: + { + float RunicPowerDecreaseRate = sWorld.getConfig(CONFIG_FLOAT_RATE_POWER_RUNICPOWER_LOSS); + addvalue = 30 * RunicPowerDecreaseRate; // 3 RunicPower by tick + break; + } + case POWER_RUNE: + { + if (getClass() != CLASS_DEATH_KNIGHT) + return; + + for (uint8 rune = 0; rune < MAX_RUNES; rune += 2) + { + uint8 runeToRegen = rune; + uint16 cd = GetRuneCooldown(rune); + uint16 secondRuneCd = GetRuneCooldown(rune + 1); + // Regenerate second rune of the same type only after first rune is off the cooldown + if (secondRuneCd && (cd > secondRuneCd || !cd)) + { + runeToRegen = rune + 1; + cd = secondRuneCd; + } + + if (cd) + { + if (cd == GetBaseRuneCooldown(runeToRegen)) + UpdateRuneRegen(runeSlotTypes[runeToRegen]); + + uint16 mod = uint32(diff * GetFloatValue(PLAYER_RUNE_REGEN_1 + uint8(GetCurrentRune(runeToRegen))) / 0.1f); + uint16 newCd = (cd > mod) ? cd - mod : 0; + SetRuneCooldown(runeToRegen, newCd); + } + } + break; + } + case POWER_HEALTH: + break; + } + + // Mana regen calculated in Player::UpdateManaRegen() + // Exist only for POWER_MANA, POWER_ENERGY, POWER_FOCUS auras + if (power != POWER_MANA) + { + AuraList const& ModPowerRegenPCTAuras = GetAurasByType(SPELL_AURA_MOD_POWER_REGEN_PERCENT); + for (AuraList::const_iterator i = ModPowerRegenPCTAuras.begin(); i != ModPowerRegenPCTAuras.end(); ++i) + if ((*i)->GetModifier()->m_miscvalue == int32(power)) + addvalue *= ((*i)->GetModifier()->m_amount + 100) / 100.0f; + } + + // addvalue computed on a 2sec basis. => update to diff time + addvalue *= float(diff) / REGEN_TIME_FULL; + + if (power != POWER_RAGE && power != POWER_RUNIC_POWER && power != POWER_HOLY_POWER) + { + curValue += uint32(addvalue); + if (curValue > maxValue) + curValue = maxValue; + } + else + { + if (curValue <= uint32(addvalue)) + curValue = 0; + else + curValue -= uint32(addvalue); + } + SetPower(power, curValue); +} + +void Player::RegenerateHealth(uint32 diff) +{ + uint32 curValue = GetHealth(); + uint32 maxValue = GetMaxHealth(); + + if (curValue >= maxValue) return; + + float HealthIncreaseRate = sWorld.getConfig(CONFIG_FLOAT_RATE_HEALTH); + + float addvalue = 0.0f; + + // polymorphed case + if (IsPolymorphed()) + addvalue = (float)GetMaxHealth() / 3; + // normal regen case (maybe partly in combat case) + else if (!isInCombat() || HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT)) + { + addvalue = HealthIncreaseRate; + if (!isInCombat()) + { + if (getLevel() < 15) + addvalue = 0.20f * GetMaxHealth() * addvalue / getLevel(); + else + addvalue = 0.015f * GetMaxHealth() * addvalue; + + AuraList const& mModHealthRegenPct = GetAurasByType(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT); + for (AuraList::const_iterator i = mModHealthRegenPct.begin(); i != mModHealthRegenPct.end(); ++i) + addvalue *= (100.0f + (*i)->GetModifier()->m_amount) / 100.0f; + + addvalue += GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * 2.0f / 5.0f; + } + else if (HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT)) + addvalue *= GetTotalAuraModifier(SPELL_AURA_MOD_REGEN_DURING_COMBAT) / 100.0f; + + if (!IsStandState()) + addvalue *= 1.33f; + } + + // always regeneration bonus (including combat) + addvalue += GetTotalAuraModifier(SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT); + + addvalue += m_baseHealthRegen / 2.5f; + + if (addvalue < 0) + addvalue = 0; + + addvalue *= (float)diff / REGEN_TIME_FULL; + + ModifyHealth(int32(addvalue)); +} + +Creature* Player::GetNPCIfCanInteractWith(ObjectGuid guid, uint32 npcflagmask) +{ + // some basic checks + if (!guid || !IsInWorld() || IsTaxiFlying()) + return NULL; + + // not in interactive state + if (hasUnitState(UNIT_STAT_CAN_NOT_REACT_OR_LOST_CONTROL)) + return NULL; + + // exist (we need look pets also for some interaction (quest/etc) + Creature* unit = GetMap()->GetAnyTypeCreature(guid); + if (!unit) + return NULL; + + // appropriate npc type + if (npcflagmask && !unit->HasFlag(UNIT_NPC_FLAGS, npcflagmask)) + return NULL; + + if (npcflagmask == UNIT_NPC_FLAG_STABLEMASTER) + { + if (getClass() != CLASS_HUNTER) + return NULL; + } + + // if a dead unit should be able to talk - the creature must be alive and have special flags + if (!unit->isAlive()) + return NULL; + + if (isAlive() && unit->isInvisibleForAlive()) + return NULL; + + // not allow interaction under control, but allow with own pets + if (unit->GetCharmerGuid()) + return NULL; + + // not enemy + if (unit->IsHostileTo(this)) + return NULL; + + // not too far + if (!unit->IsWithinDistInMap(this, INTERACTION_DISTANCE)) + return NULL; + + return unit; +} + +GameObject* Player::GetGameObjectIfCanInteractWith(ObjectGuid guid, uint32 gameobject_type) const +{ + // some basic checks + if (!guid || !IsInWorld() || IsTaxiFlying()) + return NULL; + + // not in interactive state + if (hasUnitState(UNIT_STAT_CAN_NOT_REACT_OR_LOST_CONTROL)) + return NULL; + + if (GameObject* go = GetMap()->GetGameObject(guid)) + { + if (uint32(go->GetGoType()) == gameobject_type || gameobject_type == MAX_GAMEOBJECT_TYPE) + { + float maxdist; + switch (go->GetGoType()) + { + // TODO: find out how the client calculates the maximal usage distance to spellless working + // gameobjects like guildbanks and mailboxes - 10.0 is a just an abitrary choosen number + case GAMEOBJECT_TYPE_GUILD_BANK: + case GAMEOBJECT_TYPE_MAILBOX: + maxdist = 10.0f; + break; + case GAMEOBJECT_TYPE_FISHINGHOLE: + maxdist = 20.0f + CONTACT_DISTANCE; // max spell range + break; + default: + maxdist = INTERACTION_DISTANCE; + break; + } + + if (go->IsWithinDistInMap(this, maxdist) && go->isSpawned()) + return go; + + sLog.outError("GetGameObjectIfCanInteractWith: GameObject '%s' [GUID: %u] is too far away from player %s [GUID: %u] to be used by him (distance=%f, maximal %f is allowed)", + go->GetGOInfo()->name, go->GetGUIDLow(), GetName(), GetGUIDLow(), go->GetDistance(this), maxdist); + } + } + return NULL; +} + +bool Player::IsUnderWater() const +{ + return GetTerrain()->IsUnderWater(GetPositionX(), GetPositionY(), GetPositionZ() + 2); +} + +void Player::SetInWater(bool apply) +{ + if (m_isInWater == apply) + return; + + // define player in water by opcodes + // move player's guid into HateOfflineList of those mobs + // which can't swim and move guid back into ThreatList when + // on surface. + // TODO: exist also swimming mobs, and function must be symmetric to enter/leave water + m_isInWater = apply; + + // remove auras that need water/land + RemoveAurasWithInterruptFlags(apply ? AURA_INTERRUPT_FLAG_NOT_ABOVEWATER : AURA_INTERRUPT_FLAG_NOT_UNDERWATER); + + getHostileRefManager().updateThreatTables(); +} + +struct SetGameMasterOnHelper +{ + explicit SetGameMasterOnHelper() {} + void operator()(Unit* unit) const + { + unit->setFaction(35); + unit->getHostileRefManager().setOnlineOfflineState(false); + } +}; + +struct SetGameMasterOffHelper +{ + explicit SetGameMasterOffHelper(uint32 _faction) : faction(_faction) {} + void operator()(Unit* unit) const + { + unit->setFaction(faction); + unit->getHostileRefManager().setOnlineOfflineState(true); + } + uint32 faction; +}; + +void Player::SetGameMaster(bool on) +{ + if (on) + { + m_ExtraFlags |= PLAYER_EXTRA_GM_ON; + setFaction(35); + SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_GM); + + CallForAllControlledUnits(SetGameMasterOnHelper(), CONTROLLED_PET | CONTROLLED_TOTEMS | CONTROLLED_GUARDIANS | CONTROLLED_CHARM); + + SetFFAPvP(false); + ResetContestedPvP(); + + getHostileRefManager().setOnlineOfflineState(false); + CombatStopWithPets(); + + SetPhaseMask(PHASEMASK_ANYWHERE, false); // see and visible in all phases + } + else + { + m_ExtraFlags &= ~PLAYER_EXTRA_GM_ON; + setFactionForRace(getRace()); + RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_GM); + + // restore phase + SetPhaseMask(phaseMgr->GetCurrentPhasemask(), false); + + CallForAllControlledUnits(SetGameMasterOffHelper(getFaction()), CONTROLLED_PET | CONTROLLED_TOTEMS | CONTROLLED_GUARDIANS | CONTROLLED_CHARM); + + // restore FFA PvP Server state + if (sWorld.IsFFAPvPRealm()) + SetFFAPvP(true); + + // restore FFA PvP area state, remove not allowed for GM mounts + UpdateArea(m_areaUpdateId); + + getHostileRefManager().setOnlineOfflineState(true); + + phaseMgr->AddUpdateFlag(PHASE_UPDATE_FLAG_SERVERSIDE_CHANGED); + phaseMgr->Update(); + } + + m_camera.UpdateVisibilityForOwner(); + UpdateObjectVisibility(); + UpdateForQuestWorldObjects(); +} + +void Player::SetGMVisible(bool on) +{ + if (on) + { + m_ExtraFlags &= ~PLAYER_EXTRA_GM_INVISIBLE; // remove flag + + // Reapply stealth/invisibility if active or show if not any + if (HasAuraType(SPELL_AURA_MOD_STEALTH)) + SetVisibility(VISIBILITY_GROUP_STEALTH); + else if (HasAuraType(SPELL_AURA_MOD_INVISIBILITY)) + SetVisibility(VISIBILITY_GROUP_INVISIBILITY); + else + SetVisibility(VISIBILITY_ON); + } + else + { + m_ExtraFlags |= PLAYER_EXTRA_GM_INVISIBLE; // add flag + + SetAcceptWhispers(false); + SetGameMaster(true); + + SetVisibility(VISIBILITY_OFF); + } +} + +bool Player::IsGroupVisibleFor(Player* p) const +{ + switch (sWorld.getConfig(CONFIG_UINT32_GROUP_VISIBILITY)) + { + default: return IsInSameGroupWith(p); + case 1: return IsInSameRaidWith(p); + case 2: return GetTeam() == p->GetTeam(); + } +} + +bool Player::IsInSameGroupWith(Player const* p) const +{ + return (p == this || (GetGroup() != NULL && + GetGroup()->SameSubGroup(this, p))); +} + +///- If the player is invited, remove him. If the group if then only 1 person, disband the group. +/// \todo Shouldn't we also check if there is no other invitees before disbanding the group? +void Player::UninviteFromGroup() +{ + Group* group = GetGroupInvite(); + if (!group) + return; + + group->RemoveInvite(this); + + if (group->GetMembersCount() <= 1) // group has just 1 member => disband + { + if (group->IsCreated()) + { + group->Disband(true); + sObjectMgr.RemoveGroup(group); + } + else + group->RemoveAllInvites(); + + delete group; + } +} + +void Player::RemoveFromGroup(Group* group, ObjectGuid guid) +{ + if (group) + { + if (group->RemoveMember(guid, 0) <= 1) + { + // group->Disband(); already disbanded in RemoveMember + sObjectMgr.RemoveGroup(group); + delete group; + // removemember sets the player's group pointer to NULL + } + } +} + +void Player::SendLogXPGain(uint32 GivenXP, Unit* victim, uint32 RestXP) +{ + WorldPacket data(SMSG_LOG_XPGAIN, 21); + data << (victim ? victim->GetObjectGuid() : ObjectGuid());// guid + data << uint32(GivenXP + RestXP); // given experience + data << uint8(victim ? 0 : 1); // 00-kill_xp type, 01-non_kill_xp type + if (victim) + { + data << uint32(GivenXP); // experience without rested bonus + data << float(1); // 1 - none 0 - 100% group bonus output + } + data << uint8(0); // new 2.4.0 + GetSession()->SendPacket(&data); +} + +void Player::GiveXP(uint32 xp, Unit* victim) +{ + if (xp < 1) + return; + + if (!isAlive()) + return; + + if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_XP_USER_DISABLED)) + return; + + uint32 level = getLevel(); + + // XP to money conversion processed in Player::RewardQuest + if (level >= sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)) + return; + + if (victim) + { + // handle SPELL_AURA_MOD_KILL_XP_PCT auras + Unit::AuraList const& ModXPPctAuras = GetAurasByType(SPELL_AURA_MOD_KILL_XP_PCT); + for (Unit::AuraList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i) + xp = uint32(xp * (1.0f + (*i)->GetModifier()->m_amount / 100.0f)); + } + else + { + // handle SPELL_AURA_MOD_QUEST_XP_PCT auras + Unit::AuraList const& ModXPPctAuras = GetAurasByType(SPELL_AURA_MOD_QUEST_XP_PCT); + for (Unit::AuraList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i) + xp = uint32(xp * (1.0f + (*i)->GetModifier()->m_amount / 100.0f)); + } + + // XP resting bonus for kill + uint32 rested_bonus_xp = victim ? GetXPRestBonus(xp) : 0; + + SendLogXPGain(xp, victim, rested_bonus_xp); + + uint32 curXP = GetUInt32Value(PLAYER_XP); + uint32 nextLvlXP = GetUInt32Value(PLAYER_NEXT_LEVEL_XP); + uint32 newXP = curXP + xp + rested_bonus_xp; + + while (newXP >= nextLvlXP && level < sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)) + { + newXP -= nextLvlXP; + + if (level < sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)) + GiveLevel(level + 1); + + level = getLevel(); + nextLvlXP = GetUInt32Value(PLAYER_NEXT_LEVEL_XP); + } + + SetUInt32Value(PLAYER_XP, newXP); +} + +// Update player to next level +// Current player experience not update (must be update by caller) +void Player::GiveLevel(uint32 level) +{ + if (level == getLevel()) + return; + + PlayerLevelInfo info; + sObjectMgr.GetPlayerLevelInfo(getRace(), getClass(), level, &info); + + uint32 basehp = 0, basemana = 0; + sObjectMgr.GetPlayerClassLevelInfo(getClass(), level, basehp, basemana); + + // send levelup info to client + WorldPacket data(SMSG_LEVELUP_INFO, (4 + 4 + MAX_STORED_POWERS * 4 + MAX_STATS * 4)); + data << uint32(level); + data << uint32(int32(basehp) - int32(GetCreateHealth())); + // for(int i = 0; i < MAX_POWERS; ++i) // Powers loop (0-4) + data << uint32(int32(basemana) - int32(GetCreateMana())); + data << uint32(0); + data << uint32(0); + data << uint32(0); + data << uint32(0); + // end for + for (int i = STAT_STRENGTH; i < MAX_STATS; ++i) // Stats loop (0-4) + data << uint32(int32(info.stats[i]) - GetCreateStat(Stats(i))); + + GetSession()->SendPacket(&data); + + SetUInt32Value(PLAYER_NEXT_LEVEL_XP, sObjectMgr.GetXPForLevel(level)); + + // update level, max level of skills + m_Played_time[PLAYED_TIME_LEVEL] = 0; // Level Played Time reset + + _ApplyAllLevelScaleItemMods(false); + + SetLevel(level); + + UpdateSkillsForLevel(); + + // save base values (bonuses already included in stored stats + for (int i = STAT_STRENGTH; i < MAX_STATS; ++i) + SetCreateStat(Stats(i), info.stats[i]); + + SetCreateHealth(basehp); + SetCreateMana(basemana); + + InitTalentForLevel(); + InitTaxiNodesForLevel(); + InitGlyphsForLevel(); + + UpdateAllStats(); + + // set current level health and mana/energy to maximum after applying all mods. + if (isAlive()) + SetHealth(GetMaxHealth()); + SetPower(POWER_MANA, GetMaxPower(POWER_MANA)); + SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY)); + if (GetPower(POWER_RAGE) > GetMaxPower(POWER_RAGE)) + SetPower(POWER_RAGE, GetMaxPower(POWER_RAGE)); + SetPower(POWER_FOCUS, 0); + + _ApplyAllLevelScaleItemMods(true); + + // update level to hunter/summon pet + if (Pet* pet = GetPet()) + pet->SynchronizeLevelWithOwner(); + + if (MailLevelReward const* mailReward = sObjectMgr.GetMailLevelReward(level, getRaceMask())) + MailDraft(mailReward->mailTemplateId).SendMailTo(this, MailSender(MAIL_CREATURE, mailReward->senderEntry)); + + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL); + + PhaseUpdateData phaseUdateData; + phaseUdateData.AddConditionType(CONDITION_LEVEL); + + phaseMgr->NotifyConditionChanged(phaseUdateData); +} + +void Player::UpdateFreeTalentPoints(bool resetIfNeed) +{ + uint32 level = getLevel(); + // talents base at level diff ( talents = level - 9 but some can be used already) + if (level < 10) + { + // Remove all talent points + if (m_usedTalentCount > 0) // Free any used talents + { + if (resetIfNeed) + resetTalents(true); + SetFreeTalentPoints(0); + } + } + else + { + if (m_specsCount == 0) + { + m_specsCount = 1; + m_activeSpec = 0; + } + + uint32 talentPointsForLevel = CalculateTalentsPoints(); + + // if used more that have then reset + if (m_usedTalentCount > talentPointsForLevel) + { + if (resetIfNeed && GetSession()->GetSecurity() < SEC_ADMINISTRATOR) + resetTalents(true); + else + SetFreeTalentPoints(0); + } + // else update amount of free points + else + SetFreeTalentPoints(talentPointsForLevel - m_usedTalentCount); + } +} + +void Player::InitTalentForLevel() +{ + UpdateFreeTalentPoints(); + + if (!GetSession()->PlayerLoading()) + SendTalentsInfoData(false); // update at client +} + +void Player::InitStatsForLevel(bool reapplyMods) +{ + if (reapplyMods) // reapply stats values only on .reset stats (level) command + _RemoveAllStatBonuses(); + + uint32 basehp = 0, basemana = 0; + sObjectMgr.GetPlayerClassLevelInfo(getClass(), getLevel(), basehp, basemana); + + PlayerLevelInfo info; + sObjectMgr.GetPlayerLevelInfo(getRace(), getClass(), getLevel(), &info); + + SetUInt32Value(PLAYER_FIELD_MAX_LEVEL, sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)); + SetUInt32Value(PLAYER_NEXT_LEVEL_XP, sObjectMgr.GetXPForLevel(getLevel())); + + // reset before any aura state sources (health set/aura apply) + SetUInt32Value(UNIT_FIELD_AURASTATE, 0); + + UpdateSkillsForLevel(); + + // set default cast time multiplier + SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f); + + // save base values (bonuses already included in stored stats + for (int i = STAT_STRENGTH; i < MAX_STATS; ++i) + SetCreateStat(Stats(i), info.stats[i]); + + for (int i = STAT_STRENGTH; i < MAX_STATS; ++i) + SetStat(Stats(i), info.stats[i]); + + SetCreateHealth(basehp); + + // set create powers + SetCreateMana(basemana); + + SetArmor(int32(m_createStats[STAT_AGILITY] * 2)); + + InitStatBuffMods(); + + // reset rating fields values + for (uint16 index = PLAYER_FIELD_COMBAT_RATING_1; index < PLAYER_FIELD_COMBAT_RATING_1 + MAX_COMBAT_RATING; ++index) + SetUInt32Value(index, 0); + + SetUInt32Value(PLAYER_FIELD_MOD_HEALING_DONE_POS, 0); + SetFloatValue(PLAYER_FIELD_MOD_HEALING_PCT, 1.0f); + SetFloatValue(PLAYER_FIELD_MOD_HEALING_DONE_PCT, 1.0f); + for (int i = 0; i < MAX_SPELL_SCHOOL; ++i) + { + SetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG + i, 0); + SetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + i, 0); + SetFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT + i, 1.00f); + } + + SetFloatValue(PLAYER_FIELD_MOD_SPELL_POWER_PCT, 1.0f); + + // reset attack power, damage and attack speed fields + SetFloatValue(UNIT_FIELD_BASEATTACKTIME, 2000.0f); + SetFloatValue(UNIT_FIELD_BASEATTACKTIME + 1, 2000.0f); // offhand attack time + SetFloatValue(UNIT_FIELD_RANGEDATTACKTIME, 2000.0f); + + SetFloatValue(UNIT_FIELD_MINDAMAGE, 0.0f); + SetFloatValue(UNIT_FIELD_MAXDAMAGE, 0.0f); + SetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE, 0.0f); + SetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE, 0.0f); + SetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE, 0.0f); + SetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE, 0.0f); + SetFloatValue(PLAYER_FIELD_WEAPON_DMG_MULTIPLIERS, 1.0f); + + SetInt32Value(UNIT_FIELD_ATTACK_POWER, 0 ); + SetInt32Value(UNIT_FIELD_ATTACK_POWER_MOD_POS, 0 ); + SetInt32Value(UNIT_FIELD_ATTACK_POWER_MOD_NEG, 0 ); + SetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER,0.0f); + SetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER, 0 ); + SetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MOD_POS,0 ); + SetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MOD_NEG,0 ); + SetFloatValue(UNIT_FIELD_RANGED_ATTACK_POWER_MULTIPLIER,0.0f); + + // Base crit values (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset + SetFloatValue(PLAYER_CRIT_PERCENTAGE, 0.0f); + SetFloatValue(PLAYER_OFFHAND_CRIT_PERCENTAGE, 0.0f); + SetFloatValue(PLAYER_RANGED_CRIT_PERCENTAGE, 0.0f); + + // Init spell schools (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset + for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i) + SetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1 + i, 0.0f); + + SetFloatValue(PLAYER_PARRY_PERCENTAGE, 0.0f); + SetFloatValue(PLAYER_BLOCK_PERCENTAGE, 0.0f); + SetUInt32Value(PLAYER_SHIELD_BLOCK, uint32(BASE_BLOCK_DAMAGE_PERCENT)); + + // Dodge percentage + SetFloatValue(PLAYER_DODGE_PERCENTAGE, 0.0f); + + // set armor (resistance 0) to original value (create_agility*2) + SetArmor(int32(m_createStats[STAT_AGILITY] * 2)); + SetResistanceBuffMods(SpellSchools(0), true, 0.0f); + SetResistanceBuffMods(SpellSchools(0), false, 0.0f); + // set other resistance to original value (0) + for (int i = 1; i < MAX_SPELL_SCHOOL; ++i) + { + SetResistance(SpellSchools(i), 0); + SetResistanceBuffMods(SpellSchools(i), true, 0.0f); + SetResistanceBuffMods(SpellSchools(i), false, 0.0f); + } + + SetUInt32Value(PLAYER_FIELD_MOD_TARGET_RESISTANCE, 0); + SetUInt32Value(PLAYER_FIELD_MOD_TARGET_PHYSICAL_RESISTANCE, 0); + for (int i = 0; i < MAX_SPELL_SCHOOL; ++i) + { + SetUInt32Value(UNIT_FIELD_POWER_COST_MODIFIER + i, 0); + SetFloatValue(UNIT_FIELD_POWER_COST_MULTIPLIER + i, 0.0f); + } + // Reset no reagent cost field + for (int i = 0; i < 3; ++i) + SetUInt32Value(PLAYER_NO_REAGENT_COST_1 + i, 0); + // Init data for form but skip reapply item mods for form + InitDataForForm(reapplyMods); + + // save new stats + for (int i = POWER_MANA; i < MAX_POWERS; ++i) + SetMaxPower(Powers(i), GetCreateMaxPowers(Powers(i))); + + SetMaxHealth(basehp); // stamina bonus will applied later + + // cleanup mounted state (it will set correctly at aura loading if player saved at mount. + SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, 0); + + // cleanup unit flags (will be re-applied if need at aura load). + RemoveFlag(UNIT_FIELD_FLAGS, + UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_NOT_ATTACKABLE_1 | + UNIT_FLAG_OOC_NOT_ATTACKABLE | UNIT_FLAG_PASSIVE | UNIT_FLAG_LOOTING | + UNIT_FLAG_PET_IN_COMBAT | UNIT_FLAG_SILENCED | UNIT_FLAG_PACIFIED | + UNIT_FLAG_STUNNED | UNIT_FLAG_IN_COMBAT | UNIT_FLAG_DISARMED | + UNIT_FLAG_CONFUSED | UNIT_FLAG_FLEEING | UNIT_FLAG_NOT_SELECTABLE | + UNIT_FLAG_SKINNABLE | UNIT_FLAG_MOUNT | UNIT_FLAG_TAXI_FLIGHT); + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE); // must be set + + SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_REGENERATE_POWER); // must be set + + // cleanup player flags (will be re-applied if need at aura load), to avoid have ghost flag without ghost aura, for example. + RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_AFK | PLAYER_FLAGS_DND | PLAYER_FLAGS_GM | PLAYER_FLAGS_GHOST); + + RemoveStandFlags(UNIT_STAND_FLAGS_ALL); // one form stealth modified bytes + RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP | UNIT_BYTE2_FLAG_SANCTUARY); + + // restore if need some important flags + SetUInt32Value(PLAYER_FIELD_BYTES2, 0); // flags empty by default + + if (reapplyMods) // reapply stats values only on .reset stats (level) command + _ApplyAllStatBonuses(); + + // set current level health and mana/energy to maximum after applying all mods. + SetHealth(GetMaxHealth()); + SetPower(POWER_MANA, GetMaxPower(POWER_MANA)); + SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY)); + if (GetPower(POWER_RAGE) > GetMaxPower(POWER_RAGE)) + SetPower(POWER_RAGE, GetMaxPower(POWER_RAGE)); + SetPower(POWER_FOCUS, 0); + SetPower(POWER_RUNIC_POWER, 0); + + // update level to hunter/summon pet + if (Pet* pet = GetPet()) + pet->SynchronizeLevelWithOwner(); +} + +void Player::SendInitialSpells() +{ + time_t curTime = time(NULL); + time_t infTime = curTime + infinityCooldownDelayCheck; + + uint16 spellCount = 0; + + WorldPacket data(SMSG_INITIAL_SPELLS, (1 + 2 + 4 * m_spells.size() + 2 + m_spellCooldowns.size() * (2 + 2 + 2 + 4 + 4))); + data << uint8(0); + + size_t countPos = data.wpos(); + data << uint16(spellCount); // spell count placeholder + + for (PlayerSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr) + { + if (itr->second.state == PLAYERSPELL_REMOVED) + continue; + + if (!itr->second.active || itr->second.disabled) + continue; + + data << uint32(itr->first); + data << uint16(0); // it's not slot id + + spellCount += 1; + } + + data.put(countPos, spellCount); // write real count value + + uint16 spellCooldowns = m_spellCooldowns.size(); + data << uint16(spellCooldowns); + for (SpellCooldowns::const_iterator itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); ++itr) + { + SpellEntry const* sEntry = sSpellStore.LookupEntry(itr->first); + if (!sEntry) + continue; + + data << uint32(itr->first); + + data << uint32(itr->second.itemid); // cast item id + data << uint16(sEntry->GetCategory()); // spell category + + // send infinity cooldown in special format + if (itr->second.end >= infTime) + { + data << uint32(1); // cooldown + data << uint32(0x80000000); // category cooldown + continue; + } + + time_t cooldown = itr->second.end > curTime ? (itr->second.end - curTime) * IN_MILLISECONDS : 0; + + if(sEntry->GetCategory()) // may be wrong, but anyway better than nothing... + { + data << uint32(0); // cooldown + data << uint32(cooldown); // category cooldown + } + else + { + data << uint32(cooldown); // cooldown + data << uint32(0); // category cooldown + } + } + + GetSession()->SendPacket(&data); + + DETAIL_LOG("CHARACTER: Sent Initial Spells"); +} + +void Player::RemoveMail(uint32 id) +{ + for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr) + { + if ((*itr)->messageID == id) + { + // do not delete item, because Player::removeMail() is called when returning mail to sender. + m_mail.erase(itr); + return; + } + } +} + +void Player::SendMailResult(uint32 mailId, MailResponseType mailAction, MailResponseResult mailError, uint32 equipError, uint32 item_guid, uint32 item_count) +{ + WorldPacket data(SMSG_SEND_MAIL_RESULT, (4 + 4 + 4 + (mailError == MAIL_ERR_EQUIP_ERROR ? 4 : (mailAction == MAIL_ITEM_TAKEN ? 4 + 4 : 0)))); + data << (uint32) mailId; + data << (uint32) mailAction; + data << (uint32) mailError; + if (mailError == MAIL_ERR_EQUIP_ERROR) + data << (uint32) equipError; + else if (mailAction == MAIL_ITEM_TAKEN) + { + data << (uint32) item_guid; // item guid low? + data << (uint32) item_count; // item count? + } + GetSession()->SendPacket(&data); +} + +void Player::SendNewMail() +{ + // deliver undelivered mail + WorldPacket data(SMSG_RECEIVED_MAIL, 4); + data << float(0.0f); + GetSession()->SendPacket(&data); +} + +void Player::UpdateNextMailTimeAndUnreads() +{ + // calculate next delivery time (min. from non-delivered mails + // and recalculate unReadMail + time_t cTime = time(NULL); + m_nextMailDelivereTime = 0; + unReadMails = 0; + for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr) + { + if ((*itr)->deliver_time > cTime) + { + if (!m_nextMailDelivereTime || m_nextMailDelivereTime > (*itr)->deliver_time) + m_nextMailDelivereTime = (*itr)->deliver_time; + } + else if (((*itr)->checked & MAIL_CHECK_MASK_READ) == 0) + ++unReadMails; + } +} + +void Player::AddNewMailDeliverTime(time_t deliver_time) +{ + if (deliver_time <= time(NULL)) // ready now + { + ++unReadMails; + SendNewMail(); + } + else // not ready and no have ready mails + { + if (!m_nextMailDelivereTime || m_nextMailDelivereTime > deliver_time) + m_nextMailDelivereTime = deliver_time; + } +} + +bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependent, bool disabled) +{ + SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id); + if (!spellInfo) + { + // do character spell book cleanup (all characters) + if (!IsInWorld() && !learning) // spell load case + { + sLog.outError("Player::addSpell: nonexistent in SpellStore spell #%u request, deleting for all characters in `character_spell`.", spell_id); + CharacterDatabase.PExecute("DELETE FROM character_spell WHERE spell = '%u'", spell_id); + } + else + sLog.outError("Player::addSpell: nonexistent in SpellStore spell #%u request.", spell_id); + + return false; + } + + if (!SpellMgr::IsSpellValid(spellInfo, this, false)) + { + // do character spell book cleanup (all characters) + if (!IsInWorld() && !learning) // spell load case + { + sLog.outError("Player::addSpell: Broken spell #%u learning not allowed, deleting for all characters in `character_spell`.", spell_id); + CharacterDatabase.PExecute("DELETE FROM character_spell WHERE spell = '%u'", spell_id); + } + else + sLog.outError("Player::addSpell: Broken spell #%u learning not allowed.", spell_id); + + return false; + } + + PlayerSpellState state = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED; + + bool dependent_set = false; + bool disabled_case = false; + bool superceded_old = false; + + PlayerSpellMap::iterator itr = m_spells.find(spell_id); + if (itr != m_spells.end()) + { + uint32 next_active_spell_id = 0; + // fix activate state for non-stackable low rank (and find next spell for !active case) + if (sSpellMgr.IsRankedSpellNonStackableInSpellBook(spellInfo)) + { + SpellChainMapNext const& nextMap = sSpellMgr.GetSpellChainNext(); + for (SpellChainMapNext::const_iterator next_itr = nextMap.lower_bound(spell_id); next_itr != nextMap.upper_bound(spell_id); ++next_itr) + { + if (HasSpell(next_itr->second)) + { + // high rank already known so this must !active + active = false; + next_active_spell_id = next_itr->second; + break; + } + } + } + + // not do anything if already known in expected state + if (itr->second.state != PLAYERSPELL_REMOVED && itr->second.active == active && + itr->second.dependent == dependent && itr->second.disabled == disabled) + { + if (!IsInWorld() && !learning) // explicitly load from DB and then exist in it already and set correctly + itr->second.state = PLAYERSPELL_UNCHANGED; + + return false; + } + + // dependent spell known as not dependent, overwrite state + if (itr->second.state != PLAYERSPELL_REMOVED && !itr->second.dependent && dependent) + { + itr->second.dependent = dependent; + if (itr->second.state != PLAYERSPELL_NEW) + itr->second.state = PLAYERSPELL_CHANGED; + dependent_set = true; + } + + // update active state for known spell + if (itr->second.active != active && itr->second.state != PLAYERSPELL_REMOVED && !itr->second.disabled) + { + itr->second.active = active; + + if (!IsInWorld() && !learning && !dependent_set)// explicitly load from DB and then exist in it already and set correctly + itr->second.state = PLAYERSPELL_UNCHANGED; + else if (itr->second.state != PLAYERSPELL_NEW) + itr->second.state = PLAYERSPELL_CHANGED; + + if (active) + { + if (IsNeedCastPassiveLikeSpellAtLearn(spellInfo)) + CastSpell(this, spell_id, true); + } + else if (IsInWorld()) + { + if (next_active_spell_id) + { + // update spell ranks in spellbook and action bar + WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4); + data << uint32(spell_id); + data << uint32(next_active_spell_id); + GetSession()->SendPacket(&data); + } + else + { + WorldPacket data(SMSG_REMOVED_SPELL, 4); + data << uint32(spell_id); + GetSession()->SendPacket(&data); + } + } + + return active; // learn (show in spell book if active now) + } + + if (itr->second.disabled != disabled && itr->second.state != PLAYERSPELL_REMOVED) + { + if (itr->second.state != PLAYERSPELL_NEW) + itr->second.state = PLAYERSPELL_CHANGED; + itr->second.disabled = disabled; + + if (disabled) + return false; + + disabled_case = true; + } + else switch (itr->second.state) + { + case PLAYERSPELL_UNCHANGED: // known saved spell + return false; + case PLAYERSPELL_REMOVED: // re-learning removed not saved spell + { + m_spells.erase(itr); + state = PLAYERSPELL_CHANGED; + break; // need re-add + } + default: // known not saved yet spell (new or modified) + { + // can be in case spell loading but learned at some previous spell loading + if (!IsInWorld() && !learning && !dependent_set) + itr->second.state = PLAYERSPELL_UNCHANGED; + + return false; + } + } + } + + TalentSpellPos const* talentPos = GetTalentSpellPos(spell_id); + + if (!disabled_case) // skip new spell adding if spell already known (disabled spells case) + { + // talent: unlearn all other talent ranks (high and low) + if (talentPos) + { + if (TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentPos->talent_id)) + { + for (int i = 0; i < MAX_TALENT_RANK; ++i) + { + // skip learning spell and no rank spell case + uint32 rankSpellId = talentInfo->RankID[i]; + if (!rankSpellId || rankSpellId == spell_id) + continue; + + removeSpell(rankSpellId, false, false); + } + } + } + // non talent spell: learn low ranks (recursive call) + else if (uint32 prev_spell = sSpellMgr.GetPrevSpellInChain(spell_id)) + { + if (!IsInWorld() || disabled) // at spells loading, no output, but allow save + addSpell(prev_spell, active, true, true, disabled); + else // at normal learning + learnSpell(prev_spell, true); + } + + PlayerSpell newspell; + newspell.state = state; + newspell.active = active; + newspell.dependent = dependent; + newspell.disabled = disabled; + + // replace spells in action bars and spellbook to bigger rank if only one spell rank must be accessible + if (newspell.active && !newspell.disabled && sSpellMgr.IsRankedSpellNonStackableInSpellBook(spellInfo)) + { + for (PlayerSpellMap::iterator itr2 = m_spells.begin(); itr2 != m_spells.end(); ++itr2) + { + if (itr2->second.state == PLAYERSPELL_REMOVED) continue; + SpellEntry const* i_spellInfo = sSpellStore.LookupEntry(itr2->first); + if (!i_spellInfo) continue; + + if (sSpellMgr.IsRankSpellDueToSpell(spellInfo, itr2->first)) + { + if (itr2->second.active) + { + if (sSpellMgr.IsHighRankOfSpell(spell_id, itr2->first)) + { + if (IsInWorld()) // not send spell (re-/over-)learn packets at loading + { + WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4); + data << uint32(itr2->first); + data << uint32(spell_id); + GetSession()->SendPacket(&data); + } + + // mark old spell as disable (SMSG_SUPERCEDED_SPELL replace it in client by new) + itr2->second.active = false; + if (itr2->second.state != PLAYERSPELL_NEW) + itr2->second.state = PLAYERSPELL_CHANGED; + superceded_old = true; // new spell replace old in action bars and spell book. + } + else if (sSpellMgr.IsHighRankOfSpell(itr2->first, spell_id)) + { + if (IsInWorld()) // not send spell (re-/over-)learn packets at loading + { + WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4); + data << uint32(spell_id); + data << uint32(itr2->first); + GetSession()->SendPacket(&data); + } + + // mark new spell as disable (not learned yet for client and will not learned) + newspell.active = false; + if (newspell.state != PLAYERSPELL_NEW) + newspell.state = PLAYERSPELL_CHANGED; + } + } + } + } + } + + m_spells[spell_id] = newspell; + + // return false if spell disabled + if (newspell.disabled) + return false; + } + + if (talentPos) + { + // update talent map + PlayerTalentMap::iterator iter = m_talents[m_activeSpec].find(talentPos->talent_id); + if (iter != m_talents[m_activeSpec].end()) + { + // check if ranks different or removed + if ((*iter).second.state == PLAYERSPELL_REMOVED || talentPos->rank != (*iter).second.currentRank) + { + (*iter).second.currentRank = talentPos->rank; + + if ((*iter).second.state != PLAYERSPELL_NEW) + (*iter).second.state = PLAYERSPELL_CHANGED; + } + } + else + { + PlayerTalent talent; + talent.currentRank = talentPos->rank; + talent.talentEntry = sTalentStore.LookupEntry(talentPos->talent_id); + talent.state = IsInWorld() ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED; + m_talents[m_activeSpec][talentPos->talent_id] = talent; + } + + // update used talent points count + m_usedTalentCount += GetTalentSpellCost(talentPos); + UpdateFreeTalentPoints(false); + } + + // update free primary prof.points (if any, can be none in case GM .learn prof. learning) + if (uint32 freeProfs = GetFreePrimaryProfessionPoints()) + { + if (sSpellMgr.IsPrimaryProfessionFirstRankSpell(spell_id)) + SetFreePrimaryProfessions(freeProfs - 1); + } + + // cast talents with SPELL_EFFECT_LEARN_SPELL (other dependent spells will learned later as not auto-learned) + // note: all spells with SPELL_EFFECT_LEARN_SPELL isn't passive + if (talentPos && IsSpellHaveEffect(spellInfo, SPELL_EFFECT_LEARN_SPELL)) + { + // ignore stance requirement for talent learn spell (stance set for spell only for client spell description show) + CastSpell(this, spell_id, true); + } + // also cast passive (and passive like) spells (including all talents without SPELL_EFFECT_LEARN_SPELL) with additional checks + else if (IsNeedCastPassiveLikeSpellAtLearn(spellInfo)) + { + CastSpell(this, spell_id, true); + } + else if (IsSpellHaveEffect(spellInfo, SPELL_EFFECT_SKILL_STEP)) + { + CastSpell(this, spell_id, true); + return false; + } + + // add dependent skills + uint16 maxskill = GetMaxSkillValueForLevel(); + + SpellLearnSkillNode const* spellLearnSkill = sSpellMgr.GetSpellLearnSkill(spell_id); + + SkillLineAbilityMapBounds skill_bounds = sSpellMgr.GetSkillLineAbilityMapBounds(spell_id); + + if (spellLearnSkill) + { + uint32 skill_value = GetPureSkillValue(spellLearnSkill->skill); + uint32 skill_max_value = GetPureMaxSkillValue(spellLearnSkill->skill); + + if (skill_value < spellLearnSkill->value) + skill_value = spellLearnSkill->value; + + uint32 new_skill_max_value = spellLearnSkill->maxvalue == 0 ? maxskill : spellLearnSkill->maxvalue; + + if (skill_max_value < new_skill_max_value) + skill_max_value = new_skill_max_value; + + SetSkill(spellLearnSkill->skill, skill_value, skill_max_value, spellLearnSkill->step); + } + else + { + // not ranked skills + for (SkillLineAbilityMap::const_iterator _spell_idx = skill_bounds.first; _spell_idx != skill_bounds.second; ++_spell_idx) + { + SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(_spell_idx->second->skillId); + if (!pSkill) + continue; + + if (HasSkill(pSkill->id)) + continue; + + if (_spell_idx->second->learnOnGetSkill == ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL || + // lockpicking/runeforging special case, not have ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL + (pSkill->id == SKILL_RUNEFORGING && _spell_idx->second->max_value == 0)) + { + switch (GetSkillRangeType(pSkill, _spell_idx->second->racemask != 0)) + { + case SKILL_RANGE_LANGUAGE: + SetSkill(pSkill->id, 300, 300, GetSkillStep(pSkill->id)); + break; + case SKILL_RANGE_LEVEL: + SetSkill(pSkill->id, 1, GetMaxSkillValueForLevel(), GetSkillStep(pSkill->id)); + break; + case SKILL_RANGE_MONO: + SetSkill(pSkill->id, 1, 1, GetSkillStep(pSkill->id)); + break; + default: + break; + } + } + } + } + + // learn dependent spells + SpellLearnSpellMapBounds spell_bounds = sSpellMgr.GetSpellLearnSpellMapBounds(spell_id); + + for (SpellLearnSpellMap::const_iterator itr2 = spell_bounds.first; itr2 != spell_bounds.second; ++itr2) + { + if (!itr2->second.autoLearned) + { + if (!IsInWorld() || !itr2->second.active) // at spells loading, no output, but allow save + addSpell(itr2->second.spell, itr2->second.active, true, true, false); + else // at normal learning + learnSpell(itr2->second.spell, true); + } + } + + if (!GetSession()->PlayerLoading()) + { + // not ranked skills + for (SkillLineAbilityMap::const_iterator _spell_idx = skill_bounds.first; _spell_idx != skill_bounds.second; ++_spell_idx) + { + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LINE, _spell_idx->second->skillId); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILLLINE_SPELLS, _spell_idx->second->skillId); + } + + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL, spell_id); + } + + // return true (for send learn packet) only if spell active (in case ranked spells) and not replace old spell + return active && !disabled && !superceded_old; +} + +bool Player::IsNeedCastPassiveLikeSpellAtLearn(SpellEntry const* spellInfo) const +{ + ShapeshiftForm form = GetShapeshiftForm(); + + if (IsNeedCastSpellAtFormApply(spellInfo, form)) // SPELL_ATTR_PASSIVE | SPELL_ATTR_UNK7 spells + return true; // all stance req. cases, not have auarastate cases + + if (!spellInfo->HasAttribute(SPELL_ATTR_PASSIVE)) + return false; + + // note: form passives activated with shapeshift spells be implemented by HandleShapeshiftBoosts instead of spell_learn_spell + // talent dependent passives activated at form apply have proper stance data + SpellShapeshiftEntry const* shapeShift = spellInfo->GetSpellShapeshift(); + bool need_cast = (!shapeShift || !shapeShift->Stances || (!form && spellInfo->HasAttribute(SPELL_ATTR_EX2_NOT_NEED_SHAPESHIFT))); + + // Check CasterAuraStates + SpellAuraRestrictionsEntry const* auraRestrictions = spellInfo->GetSpellAuraRestrictions(); + return need_cast && (!auraRestrictions || !auraRestrictions->CasterAuraState || HasAuraState(AuraState(auraRestrictions->CasterAuraState))); +} + +void Player::learnSpell(uint32 spell_id, bool dependent) +{ + PlayerSpellMap::iterator itr = m_spells.find(spell_id); + + bool disabled = (itr != m_spells.end()) ? itr->second.disabled : false; + bool active = disabled ? itr->second.active : true; + + bool learning = addSpell(spell_id, active, true, dependent, false); + + // prevent duplicated entires in spell book, also not send if not in world (loading) + if (learning && IsInWorld()) + { + WorldPacket data(SMSG_LEARNED_SPELL, 6); + data << uint32(spell_id); + data << uint32(0); // 3.3.3 unk + GetSession()->SendPacket(&data); + } + + // learn all disabled higher ranks (recursive) + if (disabled) + { + SpellChainMapNext const& nextMap = sSpellMgr.GetSpellChainNext(); + for (SpellChainMapNext::const_iterator i = nextMap.lower_bound(spell_id); i != nextMap.upper_bound(spell_id); ++i) + { + PlayerSpellMap::iterator iter = m_spells.find(i->second); + if (iter != m_spells.end() && iter->second.disabled) + learnSpell(i->second, false); + } + } + + if (IsInWorld()) + SpellAddedQuestCheck(spell_id); +} + +void Player::removeSpell(uint32 spell_id, bool disabled, bool learn_low_rank, bool sendUpdate) +{ + PlayerSpellMap::iterator itr = m_spells.find(spell_id); + if (itr == m_spells.end()) + return; + + if (itr->second.state == PLAYERSPELL_REMOVED || (disabled && itr->second.disabled)) + return; + + // unlearn non talent higher ranks (recursive) + SpellChainMapNext const& nextMap = sSpellMgr.GetSpellChainNext(); + for (SpellChainMapNext::const_iterator itr2 = nextMap.lower_bound(spell_id); itr2 != nextMap.upper_bound(spell_id); ++itr2) + if (HasSpell(itr2->second) && !GetTalentSpellPos(itr2->second)) + removeSpell(itr2->second, disabled, false); + + // re-search, it can be corrupted in prev loop + itr = m_spells.find(spell_id); + if (itr == m_spells.end() || itr->second.state == PLAYERSPELL_REMOVED) + return; // already unleared + + bool cur_active = itr->second.active; + bool cur_dependent = itr->second.dependent; + + if (disabled) + { + itr->second.disabled = disabled; + if (itr->second.state != PLAYERSPELL_NEW) + itr->second.state = PLAYERSPELL_CHANGED; + } + else + { + if (itr->second.state == PLAYERSPELL_NEW) + m_spells.erase(itr); + else + itr->second.state = PLAYERSPELL_REMOVED; + } + + RemoveAurasDueToSpell(spell_id); + + // remove pet auras + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + if (PetAura const* petSpell = sSpellMgr.GetPetAura(spell_id, SpellEffectIndex(i))) + RemovePetAura(petSpell); + + TalentSpellPos const* talentPos = GetTalentSpellPos(spell_id); + if (talentPos) + { + // update talent map + PlayerTalentMap::iterator iter = m_talents[m_activeSpec].find(talentPos->talent_id); + if (iter != m_talents[m_activeSpec].end()) + { + if ((*iter).second.state != PLAYERSPELL_NEW) + (*iter).second.state = PLAYERSPELL_REMOVED; + else + m_talents[m_activeSpec].erase(iter); + } + else + sLog.outError("removeSpell: Player (GUID: %u) has talent spell (id: %u) but doesn't have talent", GetGUIDLow(), spell_id); + + // free talent points + uint32 talentCosts = GetTalentSpellCost(talentPos); + + if (talentCosts < m_usedTalentCount) + m_usedTalentCount -= talentCosts; + else + m_usedTalentCount = 0; + + UpdateFreeTalentPoints(false); + } + + // update free primary prof.points (if not overflow setting, can be in case GM use before .learn prof. learning) + if (sSpellMgr.IsPrimaryProfessionFirstRankSpell(spell_id)) + { + uint32 freeProfs = GetFreePrimaryProfessionPoints() + 1; + uint32 maxProfs = GetSession()->GetSecurity() < AccountTypes(sWorld.getConfig(CONFIG_UINT32_TRADE_SKILL_GMIGNORE_MAX_PRIMARY_COUNT)) ? sWorld.getConfig(CONFIG_UINT32_MAX_PRIMARY_TRADE_SKILL) : 10; + if (freeProfs <= maxProfs) + SetFreePrimaryProfessions(freeProfs); + } + + // remove dependent skill + SpellLearnSkillNode const* spellLearnSkill = sSpellMgr.GetSpellLearnSkill(spell_id); + if (spellLearnSkill) + { + uint32 prev_spell = sSpellMgr.GetPrevSpellInChain(spell_id); + if (!prev_spell) // first rank, remove skill + SetSkill(spellLearnSkill->skill, 0, 0); + else + { + // search prev. skill setting by spell ranks chain + SpellLearnSkillNode const* prevSkill = sSpellMgr.GetSpellLearnSkill(prev_spell); + while (!prevSkill && prev_spell) + { + prev_spell = sSpellMgr.GetPrevSpellInChain(prev_spell); + prevSkill = sSpellMgr.GetSpellLearnSkill(sSpellMgr.GetFirstSpellInChain(prev_spell)); + } + + if (!prevSkill) // not found prev skill setting, remove skill + SetSkill(spellLearnSkill->skill, 0, 0); + else // set to prev. skill setting values + { + uint32 skill_value = GetPureSkillValue(prevSkill->skill); + uint32 skill_max_value = GetPureMaxSkillValue(prevSkill->skill); + + if (skill_value > prevSkill->value) + skill_value = prevSkill->value; + + uint32 new_skill_max_value = prevSkill->maxvalue == 0 ? GetMaxSkillValueForLevel() : prevSkill->maxvalue; + + if (skill_max_value > new_skill_max_value) + skill_max_value = new_skill_max_value; + + SetSkill(prevSkill->skill, skill_value, skill_max_value, prevSkill->step); + } + } + } + else + { + // not ranked skills + SkillLineAbilityMapBounds bounds = sSpellMgr.GetSkillLineAbilityMapBounds(spell_id); + + for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx) + { + SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(_spell_idx->second->skillId); + if (!pSkill) + continue; + + if ((_spell_idx->second->learnOnGetSkill == ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL && + pSkill->categoryId != SKILL_CATEGORY_CLASS) ||// not unlearn class skills (spellbook/talent pages) + // lockpicking/runeforging special case, not have ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL + (pSkill->id == SKILL_RUNEFORGING && _spell_idx->second->max_value == 0)) + { + // not reset skills for professions and racial abilities + if ((pSkill->categoryId == SKILL_CATEGORY_SECONDARY || pSkill->categoryId == SKILL_CATEGORY_PROFESSION) && + (IsProfessionSkill(pSkill->id) || _spell_idx->second->racemask != 0)) + continue; + + SetSkill(pSkill->id, 0, 0, GetSkillStep(pSkill->id)); + } + } + } + + // remove dependent spells + SpellLearnSpellMapBounds spell_bounds = sSpellMgr.GetSpellLearnSpellMapBounds(spell_id); + + for (SpellLearnSpellMap::const_iterator itr2 = spell_bounds.first; itr2 != spell_bounds.second; ++itr2) + removeSpell(itr2->second.spell, disabled); + + // activate lesser rank in spellbook/action bar, and cast it if need + bool prev_activate = false; + + if (uint32 prev_id = sSpellMgr.GetPrevSpellInChain(spell_id)) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id); + + // if talent then lesser rank also talent and need learn + if (talentPos) + { + if (learn_low_rank) + learnSpell(prev_id, false); + } + // if ranked non-stackable spell: need activate lesser rank and update dependence state + else if (cur_active && sSpellMgr.IsRankedSpellNonStackableInSpellBook(spellInfo)) + { + // need manually update dependence state (learn spell ignore like attempts) + PlayerSpellMap::iterator prev_itr = m_spells.find(prev_id); + if (prev_itr != m_spells.end()) + { + if (prev_itr->second.dependent != cur_dependent) + { + prev_itr->second.dependent = cur_dependent; + if (prev_itr->second.state != PLAYERSPELL_NEW) + prev_itr->second.state = PLAYERSPELL_CHANGED; + } + + // now re-learn if need re-activate + if (cur_active && !prev_itr->second.active && learn_low_rank) + { + if (addSpell(prev_id, true, false, prev_itr->second.dependent, prev_itr->second.disabled)) + { + // downgrade spell ranks in spellbook and action bar + WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4); + data << uint32(spell_id); + data << uint32(prev_id); + GetSession()->SendPacket(&data); + prev_activate = true; + } + } + } + } + } + + // for Titan's Grip and shaman Dual-wield + if (CanDualWield() || CanTitanGrip()) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id); + + if (CanDualWield() && IsSpellHaveEffect(spellInfo, SPELL_EFFECT_DUAL_WIELD)) + SetCanDualWield(false); + + if (CanTitanGrip() && IsSpellHaveEffect(spellInfo, SPELL_EFFECT_TITAN_GRIP)) + { + SetCanTitanGrip(false); + // Remove Titan's Grip damage penalty now + RemoveAurasDueToSpell(49152); + } + } + + // for talents and normal spell unlearn that allow offhand use for some weapons + if (sWorld.getConfig(CONFIG_BOOL_OFFHAND_CHECK_AT_TALENTS_RESET)) + AutoUnequipOffhandIfNeed(); + + // remove from spell book if not replaced by lesser rank + if (!prev_activate && sendUpdate) + { + WorldPacket data(SMSG_REMOVED_SPELL, 4); + data << uint32(spell_id); + GetSession()->SendPacket(&data); + } + + if (IsInWorld()) + SpellRemovedQuestCheck(spell_id); +} + +void Player::RemoveSpellCooldown(uint32 spell_id, bool update /* = false */) +{ + m_spellCooldowns.erase(spell_id); + + if (update) + SendClearCooldown(spell_id, this); +} + +void Player::RemoveSpellCategoryCooldown(uint32 cat, bool update /* = false */) +{ + SpellCategoryStore::const_iterator ct = sSpellCategoryStore.find(cat); + if (ct == sSpellCategoryStore.end()) + return; + + const SpellCategorySet& ct_set = ct->second; + for (SpellCooldowns::const_iterator i = m_spellCooldowns.begin(); i != m_spellCooldowns.end();) + { + if (ct_set.find(i->first) != ct_set.end()) + RemoveSpellCooldown((i++)->first, update); + else + ++i; + } +} + +void Player::RemoveArenaSpellCooldowns() +{ + // remove cooldowns on spells that has < 15 min CD + SpellCooldowns::iterator itr, next; + // iterate spell cooldowns + for (itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); itr = next) + { + next = itr; + ++next; + SpellEntry const* entry = sSpellStore.LookupEntry(itr->first); + // check if spellentry is present and if the cooldown is less than 15 mins + if( entry && + entry->GetRecoveryTime() <= 15 * MINUTE * IN_MILLISECONDS && + entry->GetCategoryRecoveryTime() <= 15 * MINUTE * IN_MILLISECONDS ) + { + // remove & notify + RemoveSpellCooldown(itr->first, true); + } + } +} + +void Player::RemoveAllSpellCooldown() +{ + if (!m_spellCooldowns.empty()) + { + ObjectGuid guid = GetObjectGuid(); + + WorldPacket data(SMSG_CLEAR_COOLDOWNS, 1 + 8 + m_spellCooldowns.size() * 4); + data.WriteGuidMask<1, 3, 6>(guid); + data.WriteBits(m_spellCooldowns.size(), 24); // cooldown count + data.WriteGuidMask<7, 5, 2, 4, 0>(guid); + + data.WriteGuidBytes<7, 2, 4, 5, 1, 3>(guid); + + for (SpellCooldowns::const_iterator itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); ++itr) + data << uint32(itr->first); + + data.WriteGuidBytes<0, 6>(guid); + + SendDirectMessage(&data); + + m_spellCooldowns.clear(); + } +} + +void Player::_LoadSpellCooldowns(QueryResult* result) +{ + // some cooldowns can be already set at aura loading... + + // QueryResult *result = CharacterDatabase.PQuery("SELECT spell,item,time FROM character_spell_cooldown WHERE guid = '%u'",GetGUIDLow()); + + if (result) + { + time_t curTime = time(NULL); + + do + { + Field* fields = result->Fetch(); + + uint32 spell_id = fields[0].GetUInt32(); + uint32 item_id = fields[1].GetUInt32(); + time_t db_time = (time_t)fields[2].GetUInt64(); + + if (!sSpellStore.LookupEntry(spell_id)) + { + sLog.outError("Player %u has unknown spell %u in `character_spell_cooldown`, skipping.", GetGUIDLow(), spell_id); + continue; + } + + // skip outdated cooldown + if (db_time <= curTime) + continue; + + AddSpellCooldown(spell_id, item_id, db_time); + + DEBUG_LOG("Player (GUID: %u) spell %u, item %u cooldown loaded (%u secs).", GetGUIDLow(), spell_id, item_id, uint32(db_time - curTime)); + } + while (result->NextRow()); + + delete result; + } +} + +void Player::_SaveSpellCooldowns() +{ + static SqlStatementID deleteSpellCooldown ; + static SqlStatementID insertSpellCooldown ; + + SqlStatement stmt = CharacterDatabase.CreateStatement(deleteSpellCooldown, "DELETE FROM character_spell_cooldown WHERE guid = ?"); + stmt.PExecute(GetGUIDLow()); + + time_t curTime = time(NULL); + time_t infTime = curTime + infinityCooldownDelayCheck; + + // remove outdated and save active + for (SpellCooldowns::iterator itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end();) + { + if (itr->second.end <= curTime) + m_spellCooldowns.erase(itr++); + else if (itr->second.end <= infTime) // not save locked cooldowns, it will be reset or set at reload + { + stmt = CharacterDatabase.CreateStatement(insertSpellCooldown, "INSERT INTO character_spell_cooldown (guid,spell,item,time) VALUES( ?, ?, ?, ?)"); + stmt.PExecute(GetGUIDLow(), itr->first, itr->second.itemid, uint64(itr->second.end)); + ++itr; + } + else + ++itr; + } +} + +uint32 Player::resetTalentsCost() const +{ + // The first time reset costs 1 gold + if (m_resetTalentsCost < 1 * GOLD) + return 1 * GOLD; + // then 5 gold + else if (m_resetTalentsCost < 5 * GOLD) + return 5 * GOLD; + // After that it increases in increments of 5 gold + else if (m_resetTalentsCost < 10 * GOLD) + return 10 * GOLD; + else + { + time_t months = (sWorld.GetGameTime() - m_resetTalentsTime) / MONTH; + if (months > 0) + { + // This cost will be reduced by a rate of 5 gold per month + int32 new_cost = int32((m_resetTalentsCost) - 5 * GOLD * months); + // to a minimum of 10 gold. + return uint32(new_cost < 10 * GOLD ? 10 * GOLD : new_cost); + } + else + { + // After that it increases in increments of 5 gold + int32 new_cost = m_resetTalentsCost + 5 * GOLD; + // until it hits a cap of 50 gold. + if (new_cost > 50 * GOLD) + new_cost = 50 * GOLD; + return new_cost; + } + } +} + +bool Player::resetTalents(bool no_cost, bool all_specs) +{ + // not need after this call + if (HasAtLoginFlag(AT_LOGIN_RESET_TALENTS) && all_specs) + RemoveAtLoginFlag(AT_LOGIN_RESET_TALENTS, true); + + if (m_usedTalentCount == 0 && !all_specs) + { + UpdateFreeTalentPoints(false); // for fix if need counter + return false; + } + + uint32 cost = 0; + + if (!no_cost) + { + cost = resetTalentsCost(); + + if (GetMoney() < cost) + { + SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0); + return false; + } + } + + for (PlayerTalentMap::iterator iter = m_talents[m_activeSpec].begin(); iter != m_talents[m_activeSpec].end();) + { + if (iter->second.state == PLAYERSPELL_REMOVED) + { + ++iter; + continue; + } + + TalentEntry const* talentInfo = iter->second.talentEntry; + if (!talentInfo) + { + m_talents[m_activeSpec].erase(iter++); + continue; + } + + TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab); + + if (!talentTabInfo) + { + m_talents[m_activeSpec].erase(iter++); + continue; + } + + // unlearn only talents for character class + // some spell learned by one class as normal spells or know at creation but another class learn it as talent, + // to prevent unexpected lost normal learned spell skip another class talents + if ((getClassMask() & talentTabInfo->ClassMask) == 0) + { + ++iter; + continue; + } + + for (int j = 0; j < MAX_TALENT_RANK; ++j) + if (talentInfo->RankID[j]) + removeSpell(talentInfo->RankID[j], !IsPassiveSpell(talentInfo->RankID[j]), false); + + iter = m_talents[m_activeSpec].begin(); + } + + // Remove spec specific spells + for (uint32 i = 0; i < MAX_TALENT_TABS; ++i) + { + if (std::vector const* specSpells = GetTalentTreeMasterySpells(GetTalentTabPages(getClass())[i])) + for (size_t i = 0; i < specSpells->size(); ++i) + removeSpell(specSpells->at(i), true); + + if (std::vector const* specSpells = GetTalentTreePrimarySpells(GetTalentTabPages(getClass())[i])) + for (size_t i = 0; i < specSpells->size(); ++i) + removeSpell(specSpells->at(i), true); + } + + for (uint8 spec = 0; spec < MAX_TALENT_SPEC_COUNT; ++spec) + { + if (!all_specs && spec != m_activeSpec) + continue; + + m_talentsPrimaryTree[spec] = 0; + } + + // for not current spec just mark removed all saved to DB case and drop not saved + if (all_specs) + { + for (uint8 spec = 0; spec < MAX_TALENT_SPEC_COUNT; ++spec) + { + if (spec == m_activeSpec) + continue; + + for (PlayerTalentMap::iterator iter = m_talents[spec].begin(); iter != m_talents[spec].end();) + { + switch (iter->second.state) + { + case PLAYERSPELL_REMOVED: + ++iter; + break; + case PLAYERSPELL_NEW: + m_talents[spec].erase(iter++); + break; + default: + iter->second.state = PLAYERSPELL_REMOVED; + ++iter; + break; + } + } + } + } + + UpdateFreeTalentPoints(false); + + if (!no_cost) + { + ModifyMoney(-(int64)cost); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TALENTS, cost); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_NUMBER_OF_TALENT_RESETS, 1); + + m_resetTalentsCost = cost; + m_resetTalentsTime = time(NULL); + } + + // Update talent tree role-dependent mana regen + UpdateManaRegen(); + + UpdateArmorSpecializations(); + + // FIXME: remove pet before or after unlearn spells? for now after unlearn to allow removing of talent related, pet affecting auras + RemovePet(PET_SAVE_REAGENTS); + /* when prev line will dropped use next line + if(Pet* pet = GetPet()) + { + if(pet->getPetType()==HUNTER_PET && !pet->GetCreatureInfo()->isTameable(CanTameExoticPets())) + pet->Unsummon(PET_SAVE_REAGENTS, this); + } + */ + return true; +} + +Mail* Player::GetMail(uint32 id) +{ + for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr) + { + if ((*itr)->messageID == id) + { + return (*itr); + } + } + return NULL; +} + +void Player::_SetCreateBits(UpdateMask* updateMask, Player* target) const +{ + if (target == this) + { + Object::_SetCreateBits(updateMask, target); + } + else + { + for (uint16 index = 0; index < m_valuesCount; ++index) + { + if (GetUInt32Value(index) != 0 && updateVisualBits.GetBit(index)) + updateMask->SetBit(index); + } + } +} + +void Player::_SetUpdateBits(UpdateMask* updateMask, Player* target) const +{ + if (target == this) + { + Object::_SetUpdateBits(updateMask, target); + } + else + { + Object::_SetUpdateBits(updateMask, target); + *updateMask &= updateVisualBits; + } +} + +void Player::InitVisibleBits() +{ + updateVisualBits.SetCount(PLAYER_END); + + updateVisualBits.SetBit(OBJECT_FIELD_GUID + 0); + updateVisualBits.SetBit(OBJECT_FIELD_GUID + 1); + updateVisualBits.SetBit(OBJECT_FIELD_TYPE); + updateVisualBits.SetBit(OBJECT_FIELD_ENTRY); + updateVisualBits.SetBit(OBJECT_FIELD_DATA + 0); + updateVisualBits.SetBit(OBJECT_FIELD_DATA + 1); + updateVisualBits.SetBit(OBJECT_FIELD_SCALE_X); + updateVisualBits.SetBit(UNIT_FIELD_CHARM + 0); + updateVisualBits.SetBit(UNIT_FIELD_CHARM + 1); + updateVisualBits.SetBit(UNIT_FIELD_SUMMON + 0); + updateVisualBits.SetBit(UNIT_FIELD_SUMMON + 1); + updateVisualBits.SetBit(UNIT_FIELD_CHARMEDBY + 0); + updateVisualBits.SetBit(UNIT_FIELD_CHARMEDBY + 1); + updateVisualBits.SetBit(UNIT_FIELD_TARGET + 0); + updateVisualBits.SetBit(UNIT_FIELD_TARGET + 1); + updateVisualBits.SetBit(UNIT_FIELD_CHANNEL_OBJECT + 0); + updateVisualBits.SetBit(UNIT_FIELD_CHANNEL_OBJECT + 1); + updateVisualBits.SetBit(UNIT_FIELD_BYTES_0); + updateVisualBits.SetBit(UNIT_FIELD_HEALTH); + updateVisualBits.SetBit(UNIT_FIELD_POWER1); + updateVisualBits.SetBit(UNIT_FIELD_POWER2); + updateVisualBits.SetBit(UNIT_FIELD_POWER3); + updateVisualBits.SetBit(UNIT_FIELD_POWER4); + updateVisualBits.SetBit(UNIT_FIELD_POWER5); + updateVisualBits.SetBit(UNIT_FIELD_MAXHEALTH); + updateVisualBits.SetBit(UNIT_FIELD_MAXPOWER1); + updateVisualBits.SetBit(UNIT_FIELD_MAXPOWER2); + updateVisualBits.SetBit(UNIT_FIELD_MAXPOWER3); + updateVisualBits.SetBit(UNIT_FIELD_MAXPOWER4); + updateVisualBits.SetBit(UNIT_FIELD_MAXPOWER5); + updateVisualBits.SetBit(UNIT_FIELD_LEVEL); + updateVisualBits.SetBit(UNIT_FIELD_FACTIONTEMPLATE); + updateVisualBits.SetBit(UNIT_VIRTUAL_ITEM_SLOT_ID + 0); + updateVisualBits.SetBit(UNIT_VIRTUAL_ITEM_SLOT_ID + 1); + updateVisualBits.SetBit(UNIT_VIRTUAL_ITEM_SLOT_ID + 2); + updateVisualBits.SetBit(UNIT_FIELD_FLAGS); + updateVisualBits.SetBit(UNIT_FIELD_FLAGS_2); + updateVisualBits.SetBit(UNIT_FIELD_AURASTATE); + updateVisualBits.SetBit(UNIT_FIELD_BASEATTACKTIME + 0); + updateVisualBits.SetBit(UNIT_FIELD_BASEATTACKTIME + 1); + updateVisualBits.SetBit(UNIT_FIELD_BOUNDINGRADIUS); + updateVisualBits.SetBit(UNIT_FIELD_COMBATREACH); + updateVisualBits.SetBit(UNIT_FIELD_DISPLAYID); + updateVisualBits.SetBit(UNIT_FIELD_NATIVEDISPLAYID); + updateVisualBits.SetBit(UNIT_FIELD_MOUNTDISPLAYID); + updateVisualBits.SetBit(UNIT_FIELD_BYTES_1); + updateVisualBits.SetBit(UNIT_FIELD_PETNUMBER); + updateVisualBits.SetBit(UNIT_FIELD_PET_NAME_TIMESTAMP); + updateVisualBits.SetBit(UNIT_DYNAMIC_FLAGS); + updateVisualBits.SetBit(UNIT_CHANNEL_SPELL); + updateVisualBits.SetBit(UNIT_MOD_CAST_SPEED); + updateVisualBits.SetBit(UNIT_NPC_FLAGS); + updateVisualBits.SetBit(UNIT_FIELD_BASE_MANA); + updateVisualBits.SetBit(UNIT_FIELD_BYTES_2); + updateVisualBits.SetBit(UNIT_FIELD_HOVERHEIGHT); + + updateVisualBits.SetBit(PLAYER_DUEL_ARBITER + 0); + updateVisualBits.SetBit(PLAYER_DUEL_ARBITER + 1); + updateVisualBits.SetBit(PLAYER_FLAGS); + //updateVisualBits.SetBit(PLAYER_GUILDID); + updateVisualBits.SetBit(PLAYER_GUILDRANK); + updateVisualBits.SetBit(PLAYER_GUILDLEVEL); + updateVisualBits.SetBit(PLAYER_BYTES); + updateVisualBits.SetBit(PLAYER_BYTES_2); + updateVisualBits.SetBit(PLAYER_BYTES_3); + updateVisualBits.SetBit(PLAYER_DUEL_TEAM); + updateVisualBits.SetBit(PLAYER_GUILD_TIMESTAMP); + updateVisualBits.SetBit(UNIT_NPC_FLAGS); + + // PLAYER_QUEST_LOG_x also visible bit on official (but only on party/raid)... + for (uint16 i = PLAYER_QUEST_LOG_1_1; i < PLAYER_QUEST_LOG_25_2; i += MAX_QUEST_OFFSET) + updateVisualBits.SetBit(i); + + // Players visible items are not inventory stuff + for (uint16 i = 0; i < EQUIPMENT_SLOT_END; ++i) + { + uint32 offset = i * 2; + + // item entry + updateVisualBits.SetBit(PLAYER_VISIBLE_ITEM_1_ENTRYID + offset); + // enchant + updateVisualBits.SetBit(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + offset); + } + + updateVisualBits.SetBit(PLAYER_CHOSEN_TITLE); +} + +void Player::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const +{ + if (target == this) + { + for (int i = 0; i < EQUIPMENT_SLOT_END; ++i) + { + if (m_items[i] == NULL) + continue; + + m_items[i]->BuildCreateUpdateBlockForPlayer(data, target); + } + for (int i = INVENTORY_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + { + if (m_items[i] == NULL) + continue; + + m_items[i]->BuildCreateUpdateBlockForPlayer(data, target); + } + } + + Unit::BuildCreateUpdateBlockForPlayer(data, target); +} + +void Player::DestroyForPlayer(Player* target, bool anim) const +{ + Unit::DestroyForPlayer(target, anim); + + for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (m_items[i] == NULL) + continue; + + m_items[i]->DestroyForPlayer(target); + } + + if (target == this) + { + for (int i = INVENTORY_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + { + if (m_items[i] == NULL) + continue; + + m_items[i]->DestroyForPlayer(target); + } + } +} + +bool Player::HasSpell(uint32 spell) const +{ + PlayerSpellMap::const_iterator itr = m_spells.find(spell); + return (itr != m_spells.end() && itr->second.state != PLAYERSPELL_REMOVED && + !itr->second.disabled); +} + +bool Player::HasActiveSpell(uint32 spell) const +{ + PlayerSpellMap::const_iterator itr = m_spells.find(spell); + return (itr != m_spells.end() && itr->second.state != PLAYERSPELL_REMOVED && + itr->second.active && !itr->second.disabled); +} + +TrainerSpellState Player::GetTrainerSpellState(TrainerSpell const* trainer_spell, uint32 reqLevel) const +{ + if (!trainer_spell) + return TRAINER_SPELL_RED; + + if (!trainer_spell->learnedSpell) + return TRAINER_SPELL_RED; + + // known spell + if (HasSpell(trainer_spell->learnedSpell)) + return TRAINER_SPELL_GRAY; + + // check race/class requirement + if (!IsSpellFitByClassAndRace(trainer_spell->learnedSpell)) + return TRAINER_SPELL_RED; + + bool prof = SpellMgr::IsProfessionSpell(trainer_spell->learnedSpell); + + // check level requirement + if (!prof || GetSession()->GetSecurity() < AccountTypes(sWorld.getConfig(CONFIG_UINT32_TRADE_SKILL_GMIGNORE_LEVEL))) + if (getLevel() < reqLevel) + return TRAINER_SPELL_RED; + + if (SpellChainNode const* spell_chain = sSpellMgr.GetSpellChainNode(trainer_spell->learnedSpell)) + { + // check prev.rank requirement + if (spell_chain->prev && !HasSpell(spell_chain->prev)) + return TRAINER_SPELL_RED; + + // check additional spell requirement + if (spell_chain->req && !HasSpell(spell_chain->req)) + return TRAINER_SPELL_RED; + } + + // check skill requirement + if (!prof || GetSession()->GetSecurity() < AccountTypes(sWorld.getConfig(CONFIG_UINT32_TRADE_SKILL_GMIGNORE_SKILL))) + if (trainer_spell->reqSkill && GetBaseSkillValue(trainer_spell->reqSkill) < trainer_spell->reqSkillValue) + return TRAINER_SPELL_RED; + + // exist, already checked at loading + SpellEntry const* spell = sSpellStore.LookupEntry(trainer_spell->learnedSpell); + + // secondary prof. or not prof. spell + SpellEffectEntry const* spellEffect = spell->GetSpellEffect(EFFECT_INDEX_1); + uint32 skill = spellEffect ? spellEffect->EffectMiscValue : 0; + + if(spellEffect && (spellEffect->Effect != SPELL_EFFECT_SKILL || !IsPrimaryProfessionSkill(skill))) + return TRAINER_SPELL_GREEN; + + // check primary prof. limit + if (sSpellMgr.IsPrimaryProfessionFirstRankSpell(spell->Id) && GetFreePrimaryProfessionPoints() == 0) + return TRAINER_SPELL_GREEN_DISABLED; + + return TRAINER_SPELL_GREEN; +} + +/* + * Deletes a character from the database + * + * The way, how the characters will be deleted is decided based on the config option. + * + * @see Player::DeleteOldCharacters + * + * @param playerguid the low-GUID from the player which should be deleted + * @param accountId the account id from the player + * @param updateRealmChars when this flag is set, the amount of characters on that realm will be updated in the realmlist + * @param deleteFinally if this flag is set, the config option will be ignored and the character will be permanently removed from the database + */ +void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRealmChars, bool deleteFinally) +{ + // for nonexistent account avoid update realm + if (accountId == 0) + updateRealmChars = false; + + uint32 charDelete_method = sWorld.getConfig(CONFIG_UINT32_CHARDELETE_METHOD); + uint32 charDelete_minLvl = sWorld.getConfig(CONFIG_UINT32_CHARDELETE_MIN_LEVEL); + + // if we want to finally delete the character or the character does not meet the level requirement, we set it to mode 0 + if (deleteFinally || Player::GetLevelFromDB(playerguid) < charDelete_minLvl) + charDelete_method = 0; + + uint32 lowguid = playerguid.GetCounter(); + + // convert corpse to bones if exist (to prevent exiting Corpse in World without DB entry) + // bones will be deleted by corpse/bones deleting thread shortly + sObjectAccessor.ConvertCorpseForPlayer(playerguid); + + // remove from guild + if (uint32 guildId = GetGuildIdFromDB(playerguid)) + { + if (Guild* guild = sGuildMgr.GetGuildById(guildId)) + { + if (guild->DelMember(playerguid)) + { + guild->Disband(); + delete guild; + } + } + } + + // remove from arena teams + LeaveAllArenaTeams(playerguid); + + // the player was uninvited already on logout so just remove from group + QueryResult* resultGroup = CharacterDatabase.PQuery("SELECT groupId FROM group_member WHERE memberGuid='%u'", lowguid); + if (resultGroup) + { + uint32 groupId = (*resultGroup)[0].GetUInt32(); + delete resultGroup; + if (Group* group = sObjectMgr.GetGroupById(groupId)) + RemoveFromGroup(group, playerguid); + } + + // remove signs from petitions (also remove petitions if owner); + RemovePetitionsAndSigns(playerguid); + + switch (charDelete_method) + { + // completely remove from the database + case 0: + { + // return back all mails with COD and Item 0 1 2 3 4 5 6 7 + QueryResult* resultMail = CharacterDatabase.PQuery("SELECT id,messageType,mailTemplateId,sender,subject,body,money,has_items FROM mail WHERE receiver='%u' AND has_items<>0 AND cod<>0", lowguid); + if (resultMail) + { + do + { + Field* fields = resultMail->Fetch(); + + uint32 mail_id = fields[0].GetUInt32(); + uint16 mailType = fields[1].GetUInt16(); + uint16 mailTemplateId = fields[2].GetUInt16(); + uint32 sender = fields[3].GetUInt32(); + std::string subject = fields[4].GetCppString(); + std::string body = fields[5].GetCppString(); + uint64 money = fields[6].GetUInt32(); + bool has_items = fields[7].GetBool(); + + // we can return mail now + // so firstly delete the old one + CharacterDatabase.PExecute("DELETE FROM mail WHERE id = '%u'", mail_id); + + // mail not from player + if (mailType != MAIL_NORMAL) + { + if (has_items) + CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mail_id); + continue; + } + + MailDraft draft; + if (mailTemplateId) + draft.SetMailTemplate(mailTemplateId, false);// items already included + else + draft.SetSubjectAndBody(subject, body); + + if (has_items) + { + // data needs to be at first place for Item::LoadFromDB + // 0 1 2 3 + QueryResult* resultItems = CharacterDatabase.PQuery("SELECT data,text,item_guid,item_template FROM mail_items JOIN item_instance ON item_guid = guid WHERE mail_id='%u'", mail_id); + if (resultItems) + { + do + { + Field* fields2 = resultItems->Fetch(); + + uint32 item_guidlow = fields2[2].GetUInt32(); + uint32 item_template = fields2[3].GetUInt32(); + + ItemPrototype const* itemProto = ObjectMgr::GetItemPrototype(item_template); + if (!itemProto) + { + CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid = '%u'", item_guidlow); + continue; + } + + Item* pItem = NewItemOrBag(itemProto); + if (!pItem->LoadFromDB(item_guidlow, fields2, playerguid)) + { + pItem->FSetState(ITEM_REMOVED); + pItem->SaveToDB(); // it also deletes item object ! + continue; + } + + draft.AddItem(pItem); + } + while (resultItems->NextRow()); + + delete resultItems; + } + } + + CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mail_id); + + uint32 pl_account = sObjectMgr.GetPlayerAccountIdByGUID(playerguid); + + draft.SetMoney(money).SendReturnToSender(pl_account, playerguid, ObjectGuid(HIGHGUID_PLAYER, sender)); + } + while (resultMail->NextRow()); + + delete resultMail; + } + + // unsummon and delete for pets in world is not required: player deleted from CLI or character list with not loaded pet. + // Get guids of character's pets, will deleted in transaction + QueryResult* resultPets = CharacterDatabase.PQuery("SELECT id FROM character_pet WHERE owner = '%u'", lowguid); + + // delete char from friends list when selected chars is online (non existing - error) + QueryResult* resultFriend = CharacterDatabase.PQuery("SELECT DISTINCT guid FROM character_social WHERE friend = '%u'", lowguid); + + // NOW we can finally clear other DB data related to character + CharacterDatabase.BeginTransaction(); + if (resultPets) + { + do + { + Field* fields3 = resultPets->Fetch(); + uint32 petguidlow = fields3[0].GetUInt32(); + // do not create separate transaction for pet delete otherwise we will get fatal error! + Pet::DeleteFromDB(petguidlow, false); + } + while (resultPets->NextRow()); + delete resultPets; + } + + // cleanup friends for online players, offline case will cleanup later in code + if (resultFriend) + { + do + { + Field* fieldsFriend = resultFriend->Fetch(); + if (Player* sFriend = sObjectAccessor.FindPlayer(ObjectGuid(HIGHGUID_PLAYER, fieldsFriend[0].GetUInt32()))) + { + if (sFriend->IsInWorld()) + { + sFriend->GetSocial()->RemoveFromSocialList(playerguid, false); + sSocialMgr.SendFriendStatus(sFriend, FRIEND_REMOVED, playerguid, false); + } + } + } + while (resultFriend->NextRow()); + delete resultFriend; + } + + CharacterDatabase.PExecute("DELETE FROM characters WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_account_data WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_declinedname WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_action WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_aura WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_battleground_data WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_gifts WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_glyphs WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_homebind WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM group_instance WHERE leaderGuid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_queststatus WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_queststatus_daily WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_queststatus_weekly WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_reputation WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_skills WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_spell WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_spell_cooldown WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_ticket WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM item_instance WHERE owner_guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_social WHERE guid = '%u' OR friend='%u'", lowguid, lowguid); + CharacterDatabase.PExecute("DELETE FROM mail WHERE receiver = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM mail_items WHERE receiver = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_pet WHERE owner = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_pet_declinedname WHERE owner = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_achievement WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_achievement_progress WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_equipmentsets WHERE guid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM guild_eventlog WHERE PlayerGuid1 = '%u' OR PlayerGuid2 = '%u'", lowguid, lowguid); + CharacterDatabase.PExecute("DELETE FROM guild_bank_eventlog WHERE PlayerGuid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_currencies WHERE guid = '%u'", lowguid); + CharacterDatabase.CommitTransaction(); + break; + } + // The character gets unlinked from the account, the name gets freed up and appears as deleted ingame + case 1: + CharacterDatabase.PExecute("UPDATE characters SET deleteInfos_Name=name, deleteInfos_Account=account, deleteDate='" UI64FMTD "', name='', account=0 WHERE guid=%u", uint64(time(NULL)), lowguid); + break; + default: + sLog.outError("Player::DeleteFromDB: Unsupported delete method: %u.", charDelete_method); + } + + if (updateRealmChars) + sWorld.UpdateRealmCharCount(accountId); +} + +/* + * Characters which were kept back in the database after being deleted and are now too old (see config option "CharDelete.KeepDays"), will be completely deleted. + * + * @see Player::DeleteFromDB + */ +void Player::DeleteOldCharacters() +{ + uint32 keepDays = sWorld.getConfig(CONFIG_UINT32_CHARDELETE_KEEP_DAYS); + if (!keepDays) + return; + + Player::DeleteOldCharacters(keepDays); +} + +/* + * Characters which were kept back in the database after being deleted and are older than the specified amount of days, will be completely deleted. + * + * @see Player::DeleteFromDB + * + * @param keepDays overrite the config option by another amount of days + */ +void Player::DeleteOldCharacters(uint32 keepDays) +{ + sLog.outString("Player::DeleteOldChars: Deleting all characters which have been deleted %u days before...", keepDays); + + QueryResult* resultChars = CharacterDatabase.PQuery("SELECT guid, deleteInfos_Account FROM characters WHERE deleteDate IS NOT NULL AND deleteDate < '" UI64FMTD "'", uint64(time(NULL) - time_t(keepDays * DAY))); + if (resultChars) + { + sLog.outString("Player::DeleteOldChars: Found %u character(s) to delete", uint32(resultChars->GetRowCount())); + do + { + Field* charFields = resultChars->Fetch(); + ObjectGuid guid = ObjectGuid(HIGHGUID_PLAYER, charFields[0].GetUInt32()); + Player::DeleteFromDB(guid, charFields[1].GetUInt32(), true, true); + } + while (resultChars->NextRow()); + delete resultChars; + } +} + +void Player::SetRoot(bool enable) +{ + WorldPacket data; + BuildForceMoveRootPacket(&data, enable, 0); + GetSession()->SendPacket(&data); +} + +void Player::SetWaterWalk(bool enable) +{ + WorldPacket data; + BuildMoveWaterWalkPacket(&data, enable, 0); + GetSession()->SendPacket(&data); +} + +/* Preconditions: + - a resurrectable corpse must not be loaded for the player (only bones) + - the player must be in world +*/ +void Player::BuildPlayerRepop() +{ + WorldPacket data(SMSG_PRE_RESURRECT, GetPackGUID().size()); + data << GetPackGUID(); + GetSession()->SendPacket(&data); + + if (getRace() == RACE_NIGHTELF) + CastSpell(this, 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) + CastSpell(this, 8326, true); // auras SPELL_AURA_GHOST, SPELL_AURA_INCREASE_SPEED(why?), SPELL_AURA_INCREASE_SWIM_SPEED(why?) + + // there must be SMSG.FORCE_RUN_SPEED_CHANGE, SMSG.FORCE_SWIM_SPEED_CHANGE, SMSG.MOVE_WATER_WALK + // there must be SMSG.STOP_MIRROR_TIMER + // there we must send 888 opcode + + // the player cannot have a corpse already, only bones which are not returned by GetCorpse + if (GetCorpse()) + { + sLog.outError("BuildPlayerRepop: player %s(%d) already has a corpse", GetName(), GetGUIDLow()); + MANGOS_ASSERT(false); + } + + // create a corpse and place it at the player's location + Corpse* corpse = CreateCorpse(); + if (!corpse) + { + sLog.outError("Error creating corpse for Player %s [%u]", GetName(), GetGUIDLow()); + return; + } + GetMap()->Add(corpse); + + // convert player body to ghost + SetHealth(1); + + SetWaterWalk(true); + if (!GetSession()->isLogingOut()) + SetRoot(false); + + // BG - remove insignia related + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); + + SendCorpseReclaimDelay(); + + // to prevent cheating + corpse->ResetGhostTime(); + + StopMirrorTimers(); // disable timers(bars) + + // set and clear other + SetByteValue(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_ALWAYS_STAND); +} + +void Player::ResurrectPlayer(float restore_percent, bool applySickness) +{ + WorldPacket data(SMSG_DEATH_RELEASE_LOC, 4 * 4); // remove spirit healer position + data << uint32(-1); + data << float(0); + data << float(0); + data << float(0); + GetSession()->SendPacket(&data); + + // speed change, land walk + + // remove death flag + set aura + SetByteValue(UNIT_FIELD_BYTES_1, 3, 0x00); + if (getRace() == RACE_NIGHTELF) + RemoveAurasDueToSpell(20584); // speed bonuses + RemoveAurasDueToSpell(8326); // SPELL_AURA_GHOST + + SetDeathState(ALIVE); + + SetWaterWalk(false); + SetRoot(false); + + m_deathTimer = 0; + + // set health/powers (0- will be set in caller) + if (restore_percent > 0.0f) + { + SetHealth(uint32(GetMaxHealth()*restore_percent)); + SetPower(POWER_MANA, uint32(GetMaxPower(POWER_MANA)*restore_percent)); + SetPower(POWER_RAGE, 0); + SetPower(POWER_ENERGY, uint32(GetMaxPower(POWER_ENERGY)*restore_percent)); + } + + // trigger update zone for alive state zone updates + uint32 newzone, newarea; + GetZoneAndAreaId(newzone, newarea); + UpdateZone(newzone, newarea); + + // update visibility of world around viewpoint + m_camera.UpdateVisibilityForOwner(); + // update visibility of player for nearby cameras + UpdateObjectVisibility(); + + if (!applySickness) + return; + + // Characters from level 1-10 are not affected by resurrection sickness. + // Characters from level 11-19 will suffer from one minute of sickness + // for each level they are above 10. + // Characters level 20 and up suffer from ten minutes of sickness. + int32 startLevel = sWorld.getConfig(CONFIG_INT32_DEATH_SICKNESS_LEVEL); + + if (int32(getLevel()) >= startLevel) + { + // set resurrection sickness + CastSpell(this, SPELL_ID_PASSIVE_RESURRECTION_SICKNESS, true); + + // not full duration + if (int32(getLevel()) < startLevel + 9) + { + int32 delta = (int32(getLevel()) - startLevel + 1) * MINUTE; + + if (SpellAuraHolder* holder = GetSpellAuraHolder(SPELL_ID_PASSIVE_RESURRECTION_SICKNESS)) + { + holder->SetAuraDuration(delta * IN_MILLISECONDS); + holder->SendAuraUpdate(false); + } + } + } +} + +void Player::KillPlayer() +{ + SetRoot(true); + + StopMirrorTimers(); // disable timers(bars) + + SetDeathState(CORPSE); + // SetFlag( UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_IN_PVP ); + + SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE); + ApplyModByteFlag(PLAYER_FIELD_BYTES, 0, PLAYER_FIELD_BYTE_RELEASE_TIMER, !sMapStore.LookupEntry(GetMapId())->Instanceable()); + + // 6 minutes until repop at graveyard + m_deathTimer = 6 * MINUTE * IN_MILLISECONDS; + + UpdateCorpseReclaimDelay(); // dependent at use SetDeathPvP() call before kill + + // don't create corpse at this moment, player might be falling + + // update visibility + UpdateObjectVisibility(); +} + +Corpse* Player::CreateCorpse() +{ + // prevent existence 2 corpse for player + SpawnCorpseBones(); + + Corpse* corpse = new Corpse((m_ExtraFlags & PLAYER_EXTRA_PVP_DEATH) ? CORPSE_RESURRECTABLE_PVP : CORPSE_RESURRECTABLE_PVE); + SetPvPDeath(false); + + if (!corpse->Create(sObjectMgr.GenerateCorpseLowGuid(), this)) + { + delete corpse; + return NULL; + } + + uint8 skin = GetByteValue(PLAYER_BYTES, 0); + uint8 face = GetByteValue(PLAYER_BYTES, 1); + uint8 hairstyle = GetByteValue(PLAYER_BYTES, 2); + uint8 haircolor = GetByteValue(PLAYER_BYTES, 3); + uint8 facialhair = GetByteValue(PLAYER_BYTES_2, 0); + + corpse->SetByteValue(CORPSE_FIELD_BYTES_1, 1, getRace()); + corpse->SetByteValue(CORPSE_FIELD_BYTES_1, 2, getGender()); + corpse->SetByteValue(CORPSE_FIELD_BYTES_1, 3, skin); + + corpse->SetByteValue(CORPSE_FIELD_BYTES_2, 0, face); + corpse->SetByteValue(CORPSE_FIELD_BYTES_2, 1, hairstyle); + corpse->SetByteValue(CORPSE_FIELD_BYTES_2, 2, haircolor); + corpse->SetByteValue(CORPSE_FIELD_BYTES_2, 3, facialhair); + + uint32 flags = CORPSE_FLAG_UNK2; + if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_HELM)) + flags |= CORPSE_FLAG_HIDE_HELM; + if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_CLOAK)) + flags |= CORPSE_FLAG_HIDE_CLOAK; + if (InBattleGround() && !InArena()) + flags |= CORPSE_FLAG_LOOTABLE; // to be able to remove insignia + corpse->SetUInt32Value(CORPSE_FIELD_FLAGS, flags); + + corpse->SetUInt32Value(CORPSE_FIELD_DISPLAY_ID, GetNativeDisplayId()); + + uint32 iDisplayID; + uint32 iIventoryType; + uint32 _cfi; + for (int i = 0; i < EQUIPMENT_SLOT_END; ++i) + { + if (m_items[i]) + { + iDisplayID = m_items[i]->GetProto()->DisplayInfoID; + iIventoryType = m_items[i]->GetProto()->InventoryType; + + _cfi = iDisplayID | (iIventoryType << 24); + corpse->SetUInt32Value(CORPSE_FIELD_ITEM + i, _cfi); + } + } + + // we not need saved corpses for BG/arenas + if (!GetMap()->IsBattleGroundOrArena()) + corpse->SaveToDB(); + + // register for player, but not show + sObjectAccessor.AddCorpse(corpse); + return corpse; +} + +void Player::SpawnCorpseBones() +{ + if (sObjectAccessor.ConvertCorpseForPlayer(GetObjectGuid())) + if (!GetSession()->PlayerLogoutWithSave()) // at logout we will already store the player + SaveToDB(); // prevent loading as ghost without corpse +} + +Corpse* Player::GetCorpse() const +{ + return sObjectAccessor.GetCorpseForPlayerGUID(GetObjectGuid()); +} + +void Player::DurabilityLossAll(double percent, bool inventory) +{ + for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + DurabilityLoss(pItem, percent); + + if (inventory) + { + // bags not have durability + // for(int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + DurabilityLoss(pItem, percent); + + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + if (Item* pItem = GetItemByPos(i, j)) + DurabilityLoss(pItem, percent); + } +} + +void Player::DurabilityLoss(Item* item, double percent) +{ + if (!item) + return; + + uint32 pMaxDurability = item ->GetUInt32Value(ITEM_FIELD_MAXDURABILITY); + + if (!pMaxDurability) + return; + + uint32 pDurabilityLoss = uint32(pMaxDurability * percent); + + if (pDurabilityLoss < 1) + pDurabilityLoss = 1; + + DurabilityPointsLoss(item, pDurabilityLoss); +} + +void Player::DurabilityPointsLossAll(int32 points, bool inventory) +{ + for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + DurabilityPointsLoss(pItem, points); + + if (inventory) + { + // bags not have durability + // for(int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + DurabilityPointsLoss(pItem, points); + + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + if (Item* pItem = GetItemByPos(i, j)) + DurabilityPointsLoss(pItem, points); + } +} + +void Player::DurabilityPointsLoss(Item* item, int32 points) +{ + int32 pMaxDurability = item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY); + int32 pOldDurability = item->GetUInt32Value(ITEM_FIELD_DURABILITY); + int32 pNewDurability = pOldDurability - points; + + if (pNewDurability < 0) + pNewDurability = 0; + else if (pNewDurability > pMaxDurability) + pNewDurability = pMaxDurability; + + if (pOldDurability != pNewDurability) + { + // modify item stats _before_ Durability set to 0 to pass _ApplyItemMods internal check + if (pNewDurability == 0 && pOldDurability > 0 && item->IsEquipped()) + _ApplyItemMods(item, item->GetSlot(), false); + + item->SetUInt32Value(ITEM_FIELD_DURABILITY, pNewDurability); + + // modify item stats _after_ restore durability to pass _ApplyItemMods internal check + if (pNewDurability > 0 && pOldDurability == 0 && item->IsEquipped()) + _ApplyItemMods(item, item->GetSlot(), true); + + item->SetState(ITEM_CHANGED, this); + } +} + +void Player::DurabilityPointLossForEquipSlot(EquipmentSlots slot) +{ + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + DurabilityPointsLoss(pItem, 1); +} + +uint32 Player::DurabilityRepairAll(bool cost, float discountMod, bool guildBank) +{ + uint32 TotalCost = 0; + // equipped, backpack, bags itself + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + TotalCost += DurabilityRepair(((INVENTORY_SLOT_BAG_0 << 8) | i), cost, discountMod, guildBank); + + // bank, buyback and keys not repaired + + // items in inventory bags + for (int j = INVENTORY_SLOT_BAG_START; j < INVENTORY_SLOT_BAG_END; ++j) + for (int i = 0; i < MAX_BAG_SIZE; ++i) + TotalCost += DurabilityRepair(((j << 8) | i), cost, discountMod, guildBank); + return TotalCost; +} + +uint32 Player::DurabilityRepair(uint16 pos, bool cost, float discountMod, bool guildBank) +{ + Item* item = GetItemByPos(pos); + + uint32 TotalCost = 0; + if (!item) + return TotalCost; + + uint32 maxDurability = item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY); + if (!maxDurability) + return TotalCost; + + uint32 curDurability = item->GetUInt32Value(ITEM_FIELD_DURABILITY); + + if (cost) + { + uint32 LostDurability = maxDurability - curDurability; + if (LostDurability > 0) + { + ItemPrototype const* ditemProto = item->GetProto(); + + DurabilityCostsEntry const* dcost = sDurabilityCostsStore.LookupEntry(ditemProto->ItemLevel); + if (!dcost) + { + sLog.outError("RepairDurability: Wrong item lvl %u", ditemProto->ItemLevel); + return TotalCost; + } + + uint32 dQualitymodEntryId = (ditemProto->Quality + 1) * 2; + DurabilityQualityEntry const* dQualitymodEntry = sDurabilityQualityStore.LookupEntry(dQualitymodEntryId); + if (!dQualitymodEntry) + { + sLog.outError("RepairDurability: Wrong dQualityModEntry %u", dQualitymodEntryId); + return TotalCost; + } + + uint32 dmultiplier = dcost->multiplier[ItemSubClassToDurabilityMultiplierId(ditemProto->Class, ditemProto->SubClass)]; + uint32 costs = uint32(LostDurability * dmultiplier * double(dQualitymodEntry->quality_mod)); + + costs = uint32(costs * discountMod); + + if (costs == 0) // fix for ITEM_QUALITY_ARTIFACT + costs = 1; + + if (guildBank) + { + if (GetGuildId() == 0) + { + DEBUG_LOG("You are not member of a guild"); + return TotalCost; + } + + Guild* pGuild = sGuildMgr.GetGuildById(GetGuildId()); + if (!pGuild) + return TotalCost; + + if (!pGuild->HasRankRight(GetRank(), GR_RIGHT_WITHDRAW_REPAIR)) + { + DEBUG_LOG("You do not have rights to withdraw for repairs"); + return TotalCost; + } + + if (pGuild->GetMemberMoneyWithdrawRem(GetGUIDLow()) < costs) + { + DEBUG_LOG("You do not have enough money withdraw amount remaining"); + return TotalCost; + } + + if (pGuild->GetGuildBankMoney() < costs) + { + DEBUG_LOG("There is not enough money in bank"); + return TotalCost; + } + + pGuild->MemberMoneyWithdraw(costs, GetGUIDLow()); + TotalCost = costs; + } + else if (GetMoney() < costs) + { + DEBUG_LOG("You do not have enough money"); + return TotalCost; + } + else + ModifyMoney(-int64(costs)); + } + } + + item->SetUInt32Value(ITEM_FIELD_DURABILITY, maxDurability); + item->SetState(ITEM_CHANGED, this); + + // reapply mods for total broken and repaired item if equipped + if (IsEquipmentPos(pos) && !curDurability) + _ApplyItemMods(item, pos & 255, true); + return TotalCost; +} + +void Player::RepopAtGraveyard() +{ + // note: this can be called also when the player is alive + // for example from WorldSession::HandleMovementOpcodes + + AreaTableEntry const* zone = GetAreaEntryByAreaID(GetAreaId()); + + // Such zones are considered unreachable as a ghost and the player must be automatically revived + if ((!isAlive() && zone && zone->flags & AREA_FLAG_NEED_FLY) || GetTransport()) + { + ResurrectPlayer(0.5f); + SpawnCorpseBones(); + } + + WorldSafeLocsEntry const* ClosestGrave = NULL; + + // Special handle for battleground maps + if (BattleGround* bg = GetBattleGround()) + ClosestGrave = bg->GetClosestGraveYard(this); + else + ClosestGrave = sObjectMgr.GetClosestGraveYard(GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId(), GetTeam()); + + // stop countdown until repop + m_deathTimer = 0; + + // if no grave found, stay at the current location + // and don't show spirit healer location + if (ClosestGrave) + { + bool updateVisibility = IsInWorld() && GetMapId() == ClosestGrave->map_id; + TeleportTo(ClosestGrave->map_id, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, GetOrientation()); + if (isDead()) // not send if alive, because it used in TeleportTo() + { + WorldPacket data(SMSG_DEATH_RELEASE_LOC, 4 * 4);// show spirit healer position on minimap + data << ClosestGrave->map_id; + data << ClosestGrave->x; + data << ClosestGrave->y; + data << ClosestGrave->z; + GetSession()->SendPacket(&data); + } + if (updateVisibility && IsInWorld()) + UpdateVisibilityAndView(); + } +} + +void Player::JoinedChannel(Channel* c) +{ + m_channels.push_back(c); +} + +void Player::LeftChannel(Channel* c) +{ + m_channels.remove(c); +} + +void Player::CleanupChannels() +{ + while (!m_channels.empty()) + { + Channel* ch = *m_channels.begin(); + m_channels.erase(m_channels.begin()); // remove from player's channel list + ch->Leave(GetObjectGuid(), false); // not send to client, not remove from player's channel list + if (ChannelMgr* cMgr = channelMgr(GetTeam())) + cMgr->LeftChannel(ch->GetName()); // deleted channel if empty + } + DEBUG_LOG("Player: channels cleaned up!"); +} + +void Player::UpdateLocalChannels(uint32 newZone) +{ + if (m_channels.empty()) + return; + + AreaTableEntry const* current_zone = GetAreaEntryByAreaID(newZone); + if (!current_zone) + return; + + ChannelMgr* cMgr = channelMgr(GetTeam()); + if (!cMgr) + return; + + std::string current_zone_name = current_zone->area_name[GetSession()->GetSessionDbcLocale()]; + + for (JoinedChannelsList::iterator i = m_channels.begin(), next; i != m_channels.end(); i = next) + { + next = i; ++next; + + // skip non built-in channels + if (!(*i)->IsConstant()) + continue; + + ChatChannelsEntry const* ch = GetChannelEntryFor((*i)->GetChannelId()); + if (!ch) + continue; + + if ((ch->flags & 4) == 4) // global channel without zone name in pattern + continue; + + // new channel + char new_channel_name_buf[100]; + snprintf(new_channel_name_buf, 100, ch->pattern[m_session->GetSessionDbcLocale()], current_zone_name.c_str()); + Channel* new_channel = cMgr->GetJoinChannel(new_channel_name_buf, ch->ChannelID); + + if ((*i) != new_channel) + { + new_channel->Join(GetObjectGuid(), ""); // will output Changed Channel: N. Name + + // leave old channel + (*i)->Leave(GetObjectGuid(), false); // not send leave channel, it already replaced at client + std::string name = (*i)->GetName(); // store name, (*i)erase in LeftChannel + LeftChannel(*i); // remove from player's channel list + cMgr->LeftChannel(name); // delete if empty + } + } + DEBUG_LOG("Player: channels cleaned up!"); +} + +void Player::LeaveLFGChannel() +{ + for (JoinedChannelsList::iterator i = m_channels.begin(); i != m_channels.end(); ++i) + { + if ((*i)->IsLFG()) + { + (*i)->Leave(GetObjectGuid()); + break; + } + } +} + +void Player::HandleBaseModValue(BaseModGroup modGroup, BaseModType modType, float amount, bool apply) +{ + if (modGroup >= BASEMOD_END || modType >= MOD_END) + { + sLog.outError("ERROR in HandleBaseModValue(): nonexistent BaseModGroup of wrong BaseModType!"); + return; + } + + float val = 1.0f; + + switch (modType) + { + case FLAT_MOD: + m_auraBaseMod[modGroup][modType] += apply ? amount : -amount; + break; + case PCT_MOD: + if (amount <= -100.0f) + amount = -200.0f; + + val = (100.0f + amount) / 100.0f; + m_auraBaseMod[modGroup][modType] *= apply ? val : (1.0f / val); + break; + } + + if (!CanModifyStats()) + return; + + switch (modGroup) + { + case CRIT_PERCENTAGE: UpdateCritPercentage(BASE_ATTACK); break; + case RANGED_CRIT_PERCENTAGE: UpdateCritPercentage(RANGED_ATTACK); break; + case OFFHAND_CRIT_PERCENTAGE: UpdateCritPercentage(OFF_ATTACK); break; + case SHIELD_BLOCK_DAMAGE_VALUE: UpdateShieldBlockDamageValue(); break; + default: break; + } +} + +float Player::GetBaseModValue(BaseModGroup modGroup, BaseModType modType) const +{ + if (modGroup >= BASEMOD_END || modType > MOD_END) + { + sLog.outError("trial to access nonexistent BaseModGroup or wrong BaseModType!"); + return 0.0f; + } + + if (modType == PCT_MOD && m_auraBaseMod[modGroup][PCT_MOD] <= 0.0f) + return 0.0f; + + return m_auraBaseMod[modGroup][modType]; +} + +float Player::GetTotalBaseModValue(BaseModGroup modGroup) const +{ + if (modGroup >= BASEMOD_END) + { + sLog.outError("wrong BaseModGroup in GetTotalBaseModValue()!"); + return 0.0f; + } + + if (m_auraBaseMod[modGroup][PCT_MOD] <= 0.0f) + return 0.0f; + + return m_auraBaseMod[modGroup][FLAT_MOD] * m_auraBaseMod[modGroup][PCT_MOD]; +} + +uint32 Player::GetShieldBlockDamageValue() const +{ + float value = m_canBlock ? BASE_BLOCK_DAMAGE_PERCENT : 0; + value = (value + m_auraBaseMod[SHIELD_BLOCK_DAMAGE_VALUE][FLAT_MOD]) * m_auraBaseMod[SHIELD_BLOCK_DAMAGE_VALUE][PCT_MOD]; + + value = (value < 0) ? 0 : value; + + return uint32(value); +} + +float Player::GetMeleeCritFromAgility() +{ + uint32 level = getLevel(); + uint32 pclass = getClass(); + + if (level > GT_MAX_LEVEL) level = GT_MAX_LEVEL; + + GtChanceToMeleeCritBaseEntry const* critBase = sGtChanceToMeleeCritBaseStore.LookupEntry(pclass - 1); + GtChanceToMeleeCritEntry const* critRatio = sGtChanceToMeleeCritStore.LookupEntry((pclass - 1) * GT_MAX_LEVEL + level - 1); + if (critBase == NULL || critRatio == NULL) + return 0.0f; + + float crit = critBase->base + GetStat(STAT_AGILITY) * critRatio->ratio; + return crit * 100.0f; +} + +void Player::GetDodgeFromAgility(float& diminishing, float& nondiminishing) +{ + // 4.2.0: these classes no longer receive dodge from agility and have 5% base + if (getClass() == CLASS_WARRIOR || getClass() == CLASS_PALADIN || getClass() == CLASS_DEATH_KNIGHT) + { + nondiminishing += 5.0f; + return; + } + + // Table for base dodge values + const float dodge_base[MAX_CLASSES] = + { + 0.036640f, // Warrior + 0.034943f, // Paladin + -0.040873f, // Hunter + 0.020957f, // Rogue + 0.034178f, // Priest + 0.036640f, // DK + 0.021080f, // Shaman + 0.036587f, // Mage + 0.024211f, // Warlock + 0.0f, // ?? + 0.056097f // Druid + }; + // Crit/agility to dodge/agility coefficient multipliers; 3.2.0 increased required agility by 15% + const float crit_to_dodge[MAX_CLASSES] = + { + 0.85f / 1.15f, // Warrior + 1.00f / 1.15f, // Paladin + 1.11f / 1.15f, // Hunter + 2.00f / 1.15f, // Rogue + 1.00f / 1.15f, // Priest + 0.85f / 1.15f, // DK + 1.60f / 1.15f, // Shaman + 1.00f / 1.15f, // Mage + 0.97f / 1.15f, // Warlock (?) + 0.0f, // ?? + 2.00f / 1.15f // Druid + }; + + uint32 level = getLevel(); + uint32 pclass = getClass(); + + if (level > GT_MAX_LEVEL) level = GT_MAX_LEVEL; + + // Dodge per agility is proportional to crit per agility, which is available from DBC files + GtChanceToMeleeCritEntry const* dodgeRatio = sGtChanceToMeleeCritStore.LookupEntry((pclass - 1) * GT_MAX_LEVEL + level - 1); + if (dodgeRatio == NULL || pclass > MAX_CLASSES) + return; + + // TODO: research if talents/effects that increase total agility by x% should increase non-diminishing part + float base_agility = GetCreateStat(STAT_AGILITY) * m_auraModifiersGroup[UNIT_MOD_STAT_START + STAT_AGILITY][BASE_PCT]; + float bonus_agility = GetStat(STAT_AGILITY) - base_agility; + // calculate diminishing (green in char screen) and non-diminishing (white) contribution + diminishing += 100.0f * bonus_agility * dodgeRatio->ratio * crit_to_dodge[pclass - 1]; + nondiminishing += 100.0f * (dodge_base[pclass - 1] + base_agility * dodgeRatio->ratio * crit_to_dodge[pclass - 1]); +} + +void Player::GetParryFromStrength(float& diminishing, float& nondiminishing) +{ + // other classes than these do not receive parry bonus from strength + if (getClass() != CLASS_WARRIOR && getClass() != CLASS_PALADIN && getClass() != CLASS_DEATH_KNIGHT) + return; + + float base_strength = GetCreateStat(STAT_STRENGTH) * m_auraModifiersGroup[UNIT_MOD_STAT_START + STAT_STRENGTH][BASE_PCT]; + float bonus_strength = GetStat(STAT_STRENGTH) - base_strength; + // calculate diminishing (green in char screen) and non-diminishing (white) contribution + diminishing += bonus_strength * GetRatingMultiplier(CR_PARRY) * 0.27f; + nondiminishing += base_strength * GetRatingMultiplier(CR_PARRY) * 0.27f; +} + +float Player::GetSpellCritFromIntellect() +{ + uint32 level = getLevel(); + uint32 pclass = getClass(); + + if (level > GT_MAX_LEVEL) level = GT_MAX_LEVEL; + + GtChanceToSpellCritBaseEntry const* critBase = sGtChanceToSpellCritBaseStore.LookupEntry(pclass - 1); + GtChanceToSpellCritEntry const* critRatio = sGtChanceToSpellCritStore.LookupEntry((pclass - 1) * GT_MAX_LEVEL + level - 1); + if (critBase == NULL || critRatio == NULL) + return 0.0f; + + float crit = critBase->base + GetStat(STAT_INTELLECT) * critRatio->ratio; + return crit * 100.0f; +} + +float Player::GetRatingMultiplier(CombatRating cr) const +{ + uint32 level = getLevel(); + + if (level > GT_MAX_LEVEL) level = GT_MAX_LEVEL; + + GtCombatRatingsEntry const* Rating = sGtCombatRatingsStore.LookupEntry(cr * GT_MAX_LEVEL + level - 1); + // gtOCTClassCombatRatingScalarStore.dbc starts with 1, CombatRating with zero, so cr+1 + GtOCTClassCombatRatingScalarEntry const* classRating = sGtOCTClassCombatRatingScalarStore.LookupEntry((getClass() - 1) * GT_MAX_RATING + cr + 1); + if (!Rating || !classRating) + return 1.0f; // By default use minimum coefficient (not must be called) + + return classRating->ratio / Rating->ratio; +} + +float Player::GetRatingBonusValue(CombatRating cr) const +{ + return float(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + cr)) * GetRatingMultiplier(cr); +} + +float Player::GetExpertiseDodgeOrParryReduction(WeaponAttackType attType) const +{ + switch (attType) + { + case BASE_ATTACK: + return GetUInt32Value(PLAYER_EXPERTISE) / 4.0f; + case OFF_ATTACK: + return GetUInt32Value(PLAYER_OFFHAND_EXPERTISE) / 4.0f; + default: + break; + } + return 0.0f; +} + +float Player::OCTRegenMPPerSpirit() +{ + // Only healers have regen bonus from spirit. Others regenerate by combat regen. + uint32 rolesMask = GetTalentTreeRolesMask(m_talentsPrimaryTree[m_activeSpec]); + if ((rolesMask & TALENT_ROLE_HEALER) == 0) + return 0.0f; + + uint32 level = getLevel(); + uint32 pclass = getClass(); + + if (level > GT_MAX_LEVEL) level = GT_MAX_LEVEL; + +// GtOCTRegenMPEntry const *baseRatio = sGtOCTRegenMPStore.LookupEntry((pclass-1)*GT_MAX_LEVEL + level-1); + GtRegenMPPerSptEntry const* moreRatio = sGtRegenMPPerSptStore.LookupEntry((pclass - 1) * GT_MAX_LEVEL + level - 1); + if (moreRatio == NULL) + return 0.0f; + + // Formula get from PaperDollFrame script + float spirit = GetStat(STAT_SPIRIT); + float regen = spirit * moreRatio->ratio; + return regen; +} + +void Player::ApplyRatingMod(CombatRating cr, int32 value, bool apply) +{ + m_baseRatingValue[cr] += (apply ? value : -value); + + // explicit affected values + switch (cr) + { + case CR_HASTE_MELEE: + { + float RatingChange = value * GetRatingMultiplier(cr); + ApplyAttackTimePercentMod(BASE_ATTACK, RatingChange, apply); + ApplyAttackTimePercentMod(OFF_ATTACK, RatingChange, apply); + break; + } + case CR_HASTE_RANGED: + { + float RatingChange = value * GetRatingMultiplier(cr); + ApplyAttackTimePercentMod(RANGED_ATTACK, RatingChange, apply); + break; + } + case CR_HASTE_SPELL: + { + float RatingChange = value * GetRatingMultiplier(cr); + ApplyCastTimePercentMod(RatingChange, apply); + break; + } + default: + break; + } + + UpdateRating(cr); +} + +void Player::UpdateRating(CombatRating cr) +{ + int32 amount = m_baseRatingValue[cr]; + // Apply bonus from SPELL_AURA_MOD_RATING_FROM_STAT + // stat used stored in miscValueB for this aura + AuraList const& modRatingFromStat = GetAurasByType(SPELL_AURA_MOD_RATING_FROM_STAT); + for (AuraList::const_iterator i = modRatingFromStat.begin(); i != modRatingFromStat.end(); ++i) + if ((*i)->GetMiscValue() & (1 << cr)) + amount += int32(GetStat(Stats((*i)->GetMiscBValue())) * (*i)->GetModifier()->m_amount / 100.0f); + if (amount < 0) + amount = 0; + SetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + cr, uint32(amount)); + + bool affectStats = CanModifyStats(); + + switch (cr) + { + case CR_WEAPON_SKILL: + case CR_DEFENSE_SKILL: + break; + case CR_DODGE: + UpdateDodgePercentage(); + break; + case CR_PARRY: + UpdateParryPercentage(); + break; + case CR_BLOCK: + UpdateBlockPercentage(); + break; + case CR_HIT_MELEE: + UpdateMeleeHitChances(); + break; + case CR_HIT_RANGED: + UpdateRangedHitChances(); + break; + case CR_HIT_SPELL: + UpdateSpellHitChances(); + break; + case CR_CRIT_MELEE: + if (affectStats) + { + UpdateCritPercentage(BASE_ATTACK); + UpdateCritPercentage(OFF_ATTACK); + } + break; + case CR_CRIT_RANGED: + if (affectStats) + UpdateCritPercentage(RANGED_ATTACK); + break; + case CR_CRIT_SPELL: + if (affectStats) + UpdateAllSpellCritChances(); + break; + case CR_RESILIENCE_DAMAGE_TAKEN: + break; + case CR_HASTE_MELEE: // Implemented in Player::ApplyRatingMod + case CR_HASTE_RANGED: + case CR_HASTE_SPELL: + break; + case CR_EXPERTISE: + if (affectStats) + { + UpdateExpertise(BASE_ATTACK); + UpdateExpertise(OFF_ATTACK); + } + break; + case CR_ARMOR_PENETRATION: + if (affectStats) + UpdateArmorPenetration(); + break; + case CR_MASTERY: + UpdateMasteryAuras(); + break; + // deprecated + case CR_HIT_TAKEN_MELEE: + case CR_HIT_TAKEN_RANGED: + case CR_HIT_TAKEN_SPELL: + case CR_CRIT_TAKEN_SPELL: + case CR_CRIT_TAKEN_MELEE: + case CR_WEAPON_SKILL_MAINHAND: + case CR_WEAPON_SKILL_OFFHAND: + case CR_WEAPON_SKILL_RANGED: + break; + } +} + +void Player::UpdateAllRatings() +{ + for (int cr = 0; cr < MAX_COMBAT_RATING; ++cr) + UpdateRating(CombatRating(cr)); +} + +void Player::SetRegularAttackTime() +{ + for (int i = 0; i < MAX_ATTACK; ++i) + { + Item* tmpitem = GetWeaponForAttack(WeaponAttackType(i), true, false); + if (tmpitem) + { + ItemPrototype const* proto = tmpitem->GetProto(); + if (proto->Delay) + SetAttackTime(WeaponAttackType(i), proto->Delay); + else + SetAttackTime(WeaponAttackType(i), BASE_ATTACK_TIME); + } + } +} + +// skill+step, checking for max value +bool Player::UpdateSkill(uint32 skill_id, uint32 step) +{ + if (!skill_id) + return false; + + if (skill_id == SKILL_FIST_WEAPONS) + skill_id = SKILL_UNARMED; + + SkillStatusMap::iterator itr = mSkillStatus.find(skill_id); + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + return false; + + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; // itr->second.pos % 2 + + uint16 value = GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset); + uint16 max = GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset); + + if (!max || !value || value >= max) + return false; + + uint32 new_value = value + step; + if (new_value > max) + new_value = max; + + SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, new_value); + if (itr->second.uState != SKILL_NEW) + itr->second.uState = SKILL_CHANGED; + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, skill_id); + + return true; +} + +inline int SkillGainChance(uint32 SkillValue, uint32 GrayLevel, uint32 GreenLevel, uint32 YellowLevel) +{ + if (SkillValue >= GrayLevel) + return sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_GREY) * 10; + if (SkillValue >= GreenLevel) + return sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_GREEN) * 10; + if (SkillValue >= YellowLevel) + return sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_YELLOW) * 10; + return sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_ORANGE) * 10; +} + +bool Player::UpdateCraftSkill(uint32 spellid) +{ + DEBUG_LOG("UpdateCraftSkill spellid %d", spellid); + + SkillLineAbilityMapBounds bounds = sSpellMgr.GetSkillLineAbilityMapBounds(spellid); + + for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx) + { + if (_spell_idx->second->skillId) + { + uint32 SkillValue = GetPureSkillValue(_spell_idx->second->skillId); + + // Alchemy Discoveries here + SpellEntry const* spellEntry = sSpellStore.LookupEntry(spellid); + if (spellEntry && spellEntry->GetMechanic() == MECHANIC_DISCOVERY) + { + if (uint32 discoveredSpell = GetSkillDiscoverySpell(_spell_idx->second->skillId, spellid, this)) + learnSpell(discoveredSpell, false); + } + + uint32 craft_skill_gain = sWorld.getConfig(CONFIG_UINT32_SKILL_GAIN_CRAFTING); + + return UpdateSkillPro(_spell_idx->second->skillId, SkillGainChance(SkillValue, + _spell_idx->second->max_value, + (_spell_idx->second->max_value + _spell_idx->second->min_value) / 2, + _spell_idx->second->min_value), + craft_skill_gain); + } + } + return false; +} + +bool Player::UpdateGatherSkill(uint32 SkillId, uint32 SkillValue, uint32 RedLevel, uint32 Multiplicator) +{ + DEBUG_LOG("UpdateGatherSkill(SkillId %d SkillLevel %d RedLevel %d)", SkillId, SkillValue, RedLevel); + + uint32 gathering_skill_gain = sWorld.getConfig(CONFIG_UINT32_SKILL_GAIN_GATHERING); + + // For skinning and Mining chance decrease with level. 1-74 - no decrease, 75-149 - 2 times, 225-299 - 8 times + switch (SkillId) + { + case SKILL_HERBALISM: + case SKILL_JEWELCRAFTING: + case SKILL_INSCRIPTION: + return UpdateSkillPro(SkillId, SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * Multiplicator, gathering_skill_gain); + case SKILL_SKINNING: + if (sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_SKINNING_STEPS) == 0) + return UpdateSkillPro(SkillId, SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * Multiplicator, gathering_skill_gain); + else + return UpdateSkillPro(SkillId, (SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * Multiplicator) >> (SkillValue / sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_SKINNING_STEPS)), gathering_skill_gain); + case SKILL_MINING: + if (sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_MINING_STEPS) == 0) + return UpdateSkillPro(SkillId, SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * Multiplicator, gathering_skill_gain); + else + return UpdateSkillPro(SkillId, (SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * Multiplicator) >> (SkillValue / sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_MINING_STEPS)), gathering_skill_gain); + } + return false; +} + +bool Player::UpdateFishingSkill() +{ + DEBUG_LOG("UpdateFishingSkill"); + + uint32 SkillValue = GetPureSkillValue(SKILL_FISHING); + + int32 chance = SkillValue < 75 ? 100 : 2500 / (SkillValue - 50); + + uint32 gathering_skill_gain = sWorld.getConfig(CONFIG_UINT32_SKILL_GAIN_GATHERING); + + return UpdateSkillPro(SKILL_FISHING, chance * 10, gathering_skill_gain); +} + +// levels sync. with spell requirement for skill levels to learn +// bonus abilities in sSkillLineAbilityStore +// Used only to avoid scan DBC at each skill grow +static uint32 bonusSkillLevels[] = {75, 150, 225, 300, 375, 450, 525}; + +bool Player::UpdateSkillPro(uint16 SkillId, int32 Chance, uint32 step) +{ + DEBUG_LOG("UpdateSkillPro(SkillId %d, Chance %3.1f%%)", SkillId, Chance / 10.0); + if (!SkillId) + return false; + + if (Chance <= 0) // speedup in 0 chance case + { + DEBUG_LOG("Player::UpdateSkillPro Chance=%3.1f%% missed", Chance / 10.0); + return false; + } + + SkillStatusMap::iterator itr = mSkillStatus.find(SkillId); + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + return false; + + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; // itr->second.pos % 2 + + uint16 SkillValue = GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset); + uint16 MaxValue = GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset); + + if (!MaxValue || !SkillValue || SkillValue >= MaxValue) + return false; + + int32 Roll = irand(1, 1000); + + if (Roll <= Chance) + { + uint16 new_value = SkillValue + step; + if (new_value > MaxValue) + new_value = MaxValue; + + SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, new_value); + if (itr->second.uState != SKILL_NEW) + itr->second.uState = SKILL_CHANGED; + for (uint32* bsl = &bonusSkillLevels[0]; *bsl; ++bsl) + { + if (SkillValue < *bsl && new_value >= *bsl) + { + learnSkillRewardedSpells(SkillId, new_value); + break; + } + } + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, SkillId); + DEBUG_LOG("Player::UpdateSkillPro Chance=%3.1f%% taken", Chance / 10.0); + return true; + } + + DEBUG_LOG("Player::UpdateSkillPro Chance=%3.1f%% missed", Chance / 10.0); + return false; +} + +void Player::ModifySkillBonus(uint32 skillid, int32 val, bool talent) +{ + SkillStatusMap::const_iterator itr = mSkillStatus.find(skillid); + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + return; + + uint16 field = itr->second.pos / 2 + (talent ? PLAYER_SKILL_TALENT_0 : PLAYER_SKILL_MODIFIER_0); + uint8 offset = itr->second.pos & 1; // itr->second.pos % 2 + + uint16 bonus = GetUInt16Value(field, offset); + + SetUInt16Value(field, offset, bonus + val); +} + +void Player::UpdateSkillsForLevel() +{ + uint32 maxSkill = GetMaxSkillValueForLevel(); + + for (SkillStatusMap::iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end(); ++itr) + { + if (itr->second.uState == SKILL_DELETED) + continue; + + uint32 pskill = itr->first; + + SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(pskill); + if (!pSkill) + continue; + + if (GetSkillRangeType(pSkill, false) != SKILL_RANGE_LEVEL) + continue; + // only weapons skills curently have SKILL_RANGE_LEVEL + + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; // itr->second.pos % 2 + + uint16 max = GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset); + + /// update only level dependent max skill values + if (max != 1) + { + SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, maxSkill); + if (itr->second.uState != SKILL_NEW) + itr->second.uState = SKILL_CHANGED; + } + } +} + +void Player::UpdateSkillsToMaxSkillsForLevel() +{ + for (SkillStatusMap::iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end(); ++itr) + { + if (itr->second.uState == SKILL_DELETED) + continue; + + uint32 pskill = itr->first; + if (IsProfessionOrRidingSkill(pskill)) + continue; + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; // itr->second.pos % 2 + + uint16 max = GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset); + + if (max > 1) + { + SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, max); + if (itr->second.uState != SKILL_NEW) + itr->second.uState = SKILL_CHANGED; + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, pskill); + } + } +} + +// This functions sets a skill line value (and adds if doesn't exist yet) +// To "remove" a skill line, set it's values to zero +void Player::SetSkill(uint16 id, uint16 currVal, uint16 maxVal, uint16 step /*=0*/) +{ + if (!id) + return; + + uint16 oldVal; + SkillStatusMap::iterator itr = mSkillStatus.find(id); + + // has skill + if (itr != mSkillStatus.end() && itr->second.uState != SKILL_DELETED) + { + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; // itr->second.pos % 2 + oldVal = GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset); + if (currVal) + { + SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, step); + // update value + SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, currVal); + SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, maxVal); + if (itr->second.uState != SKILL_NEW) + itr->second.uState = SKILL_CHANGED; + learnSkillRewardedSpells(id, oldVal); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, id); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL, id); + } + else // remove + { + // clear skill fields + SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, 0); + SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, 0); + SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, 0); + SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, 0); + SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, 0); + SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, 0); + + // mark as deleted or simply remove from map if not saved yet + if (itr->second.uState != SKILL_NEW) + itr->second.uState = SKILL_DELETED; + else + mSkillStatus.erase(itr); + + // remove all spells that related to this skill + for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j) + if (SkillLineAbilityEntry const* pAbility = sSkillLineAbilityStore.LookupEntry(j)) + if (pAbility->skillId == id) + removeSpell(sSpellMgr.GetFirstSpellInChain(pAbility->spellId)); + } + } + else if (currVal) // add + { + for (int i = 0; i < PLAYER_MAX_SKILLS; ++i) + { + uint16 field = i / 2; + uint8 offset = i & 1; // i % 2 + + if (!GetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset)) + { + SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(id); + if (!pSkill) + { + sLog.outError("Skill not found in SkillLineStore: skill #%u", id); + return; + } + + SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, id); + SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, step); + SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, currVal); + SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, maxVal); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, id); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL, id); + + // insert new entry or update if not deleted old entry yet + if (itr != mSkillStatus.end()) + { + itr->second.pos = i; + itr->second.uState = SKILL_CHANGED; + } + else + mSkillStatus.insert(SkillStatusMap::value_type(id, SkillStatusData(i, SKILL_NEW))); + + // apply skill bonuses + SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, 0); + SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, 0); + + // temporary bonuses + AuraList const& mModSkill = GetAurasByType(SPELL_AURA_MOD_SKILL); + for (AuraList::const_iterator j = mModSkill.begin(); j != mModSkill.end(); ++j) + if ((*j)->GetModifier()->m_miscvalue == int32(id)) + (*j)->ApplyModifier(true); + + // permanent bonuses + AuraList const& mModSkillTalent = GetAurasByType(SPELL_AURA_MOD_SKILL_TALENT); + for (AuraList::const_iterator j = mModSkillTalent.begin(); j != mModSkillTalent.end(); ++j) + if ((*j)->GetModifier()->m_miscvalue == int32(id)) + (*j)->ApplyModifier(true); + + // Learn all spells for skill + learnSkillRewardedSpells(id, currVal); + return; + } + } + } +} + +bool Player::HasSkill(uint32 skill) const +{ + if (!skill) + return false; + + SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); + return (itr != mSkillStatus.end() && itr->second.uState != SKILL_DELETED); +} + +uint16 Player::GetSkillStep(uint16 skill) const +{ + if (!skill) + return 0; + + SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + return 0; + + return GetUInt16Value(PLAYER_SKILL_STEP_0 + itr->second.pos / 2, itr->second.pos & 1); +} + +uint16 Player::GetSkillValue(uint32 skill) const +{ + if (!skill) + return 0; + + SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + return 0; + + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; + + int32 result = int32(GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset)); + result += int32(GetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset)); + result += int32(GetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset)); + return result < 0 ? 0 : result; +} + +uint16 Player::GetMaxSkillValue(uint32 skill) const +{ + if (!skill) + return 0; + + SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + return 0; + + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; + + int32 result = int32(GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset)); + result += int32(GetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset)); + result += int32(GetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset)); + return result < 0 ? 0 : result; +} + +uint16 Player::GetPureMaxSkillValue(uint32 skill) const +{ + if (!skill) + return 0; + + SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + return 0; + + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; + + return GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset); +} + +uint16 Player::GetBaseSkillValue(uint32 skill) const +{ + if (!skill) + return 0; + + SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + return 0; + + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; + + int32 result = int32(GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset)); + result += int32(GetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset)); + return result < 0 ? 0 : result; +} + +uint16 Player::GetPureSkillValue(uint32 skill) const +{ + if (!skill) + return 0; + + SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + return 0; + + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; + + return GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset); +} + +int16 Player::GetSkillPermBonusValue(uint32 skill) const +{ + if (!skill) + return 0; + + SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + return 0; + + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; + + return GetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset); +} + +int16 Player::GetSkillTempBonusValue(uint32 skill) const +{ + if (!skill) + return 0; + + SkillStatusMap::const_iterator itr = mSkillStatus.find(skill); + if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED) + return 0; + + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; + + return GetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset); +} + +void Player::SendInitialActionButtons() const +{ + DETAIL_LOG("Initializing Action Buttons for '%u' spec '%u'", GetGUIDLow(), m_activeSpec); + + WorldPacket data(SMSG_ACTION_BUTTONS, 1 + (MAX_ACTION_BUTTONS * 4)); + ActionButtonList const& currentActionButtonList = m_actionButtons[m_activeSpec]; + for (uint8 button = 0; button < MAX_ACTION_BUTTONS; ++button) + { + ActionButtonList::const_iterator itr = currentActionButtonList.find(button); + if (itr != currentActionButtonList.end() && itr->second.uState != ACTIONBUTTON_DELETED) + data << uint32(itr->second.packedData); + else + data << uint32(0); + } + data << uint8(1); // talent spec amount (in packet) + GetSession()->SendPacket(&data); + DETAIL_LOG("Action Buttons for '%u' spec '%u' Initialized", GetGUIDLow(), m_activeSpec); +} + +void Player::SendLockActionButtons() const +{ + DETAIL_LOG("Locking Action Buttons for '%u' spec '%u'", GetGUIDLow(), m_activeSpec); + WorldPacket data(SMSG_ACTION_BUTTONS, 1); + // sending 2 locks actions bars, neither user can remove buttons, nor client removes buttons at spell unlearn + // they remain locked until server sends new action buttons + data << uint8(2); + GetSession()->SendPacket(&data); +} + +bool Player::IsActionButtonDataValid(uint8 button, uint32 action, uint8 type, Player* player, bool msg) +{ + if (button >= MAX_ACTION_BUTTONS) + { + if (msg) + { + if (player) + sLog.outError("Action %u not added into button %u for player %s: button must be < %u", action, button, player->GetName(), MAX_ACTION_BUTTONS); + else + sLog.outError("Table `playercreateinfo_action` have action %u into button %u : button must be < %u", action, button, MAX_ACTION_BUTTONS); + } + return false; + } + + if (action >= MAX_ACTION_BUTTON_ACTION_VALUE) + { + if (msg) + { + if (player) + sLog.outError("Action %u not added into button %u for player %s: action must be < %u", action, button, player->GetName(), MAX_ACTION_BUTTON_ACTION_VALUE); + else + sLog.outError("Table `playercreateinfo_action` have action %u into button %u : action must be < %u", action, button, MAX_ACTION_BUTTON_ACTION_VALUE); + } + return false; + } + + switch (type) + { + case ACTION_BUTTON_SPELL: + { + SpellEntry const* spellProto = sSpellStore.LookupEntry(action); + if (!spellProto) + { + if (msg) + { + if (player) + sLog.outError("Spell action %u not added into button %u for player %s: spell not exist", action, button, player->GetName()); + else + sLog.outError("Table `playercreateinfo_action` have spell action %u into button %u: spell not exist", action, button); + } + return false; + } + + if (player) + { + if (!player->HasSpell(spellProto->Id)) + { + if (msg) + sLog.outError("Spell action %u not added into button %u for player %s: player don't known this spell", action, button, player->GetName()); + return false; + } + else if (IsPassiveSpell(spellProto)) + { + if (msg) + sLog.outError("Spell action %u not added into button %u for player %s: spell is passive", action, button, player->GetName()); + return false; + } + // current range for button of totem bar is from ACTION_BUTTON_SHAMAN_TOTEMS_BAR to (but not including) ACTION_BUTTON_SHAMAN_TOTEMS_BAR + 12 + else if (button >= ACTION_BUTTON_SHAMAN_TOTEMS_BAR && button < (ACTION_BUTTON_SHAMAN_TOTEMS_BAR + 12) + && !spellProto->HasAttribute(SPELL_ATTR_EX7_TOTEM_SPELL)) + { + if (msg) + sLog.outError("Spell action %u not added into button %u for player %s: attempt to add non totem spell to totem bar", action, button, player->GetName()); + return false; + } + } + break; + } + case ACTION_BUTTON_ITEM: + { + if (!ObjectMgr::GetItemPrototype(action)) + { + if (msg) + { + if (player) + sLog.outError("Item action %u not added into button %u for player %s: item not exist", action, button, player->GetName()); + else + sLog.outError("Table `playercreateinfo_action` have item action %u into button %u: item not exist", action, button); + } + return false; + } + break; + } + default: + break; // other cases not checked at this moment + } + + return true; +} + +ActionButton* Player::addActionButton(uint8 spec, uint8 button, uint32 action, uint8 type) +{ + // check action only for active spec (so not check at copy/load passive spec) + if (spec == GetActiveSpec() && !IsActionButtonDataValid(button, action, type, this)) + return NULL; + + // it create new button (NEW state) if need or return existing + ActionButton& ab = m_actionButtons[spec][button]; + + // set data and update to CHANGED if not NEW + ab.SetActionAndType(action, ActionButtonType(type)); + + DETAIL_LOG("Player '%u' Added Action '%u' (type %u) to Button '%u' for spec %u", GetGUIDLow(), action, uint32(type), button, spec); + return &ab; +} + +void Player::removeActionButton(uint8 spec, uint8 button) +{ + ActionButtonList& currentActionButtonList = m_actionButtons[spec]; + ActionButtonList::iterator buttonItr = currentActionButtonList.find(button); + if (buttonItr == currentActionButtonList.end() || buttonItr->second.uState == ACTIONBUTTON_DELETED) + return; + + if (buttonItr->second.uState == ACTIONBUTTON_NEW) + currentActionButtonList.erase(buttonItr); // new and not saved + else + buttonItr->second.uState = ACTIONBUTTON_DELETED; // saved, will deleted at next save + + DETAIL_LOG("Action Button '%u' Removed from Player '%u' for spec %u", button, GetGUIDLow(), spec); +} + +ActionButton const* Player::GetActionButton(uint8 button) +{ + ActionButtonList& currentActionButtonList = m_actionButtons[m_activeSpec]; + ActionButtonList::iterator buttonItr = currentActionButtonList.find(button); + if (buttonItr == currentActionButtonList.end() || buttonItr->second.uState == ACTIONBUTTON_DELETED) + return NULL; + + return &buttonItr->second; +} + +bool Player::SetPosition(float x, float y, float z, float orientation, bool teleport) +{ + // prevent crash when a bad coord is sent by the client + if (!MaNGOS::IsValidMapCoord(x, y, z, orientation)) + { + DEBUG_LOG("Player::SetPosition(%f, %f, %f, %f, %d) .. bad coordinates for player %d!", x, y, z, orientation, teleport, GetGUIDLow()); + return false; + } + + Map* m = GetMap(); + + const float old_x = GetPositionX(); + const float old_y = GetPositionY(); + const float old_z = GetPositionZ(); + const float old_r = GetOrientation(); + + if (teleport || old_x != x || old_y != y || old_z != z || old_r != orientation) + { + if (teleport || old_x != x || old_y != y || old_z != z) + RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_MOVE | AURA_INTERRUPT_FLAG_TURNING); + else + RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TURNING); + + RemoveSpellsCausingAura(SPELL_AURA_FEIGN_DEATH); + + // move and update visible state if need + m->PlayerRelocation(this, x, y, z, orientation); + + // reread after Map::Relocation + m = GetMap(); + x = GetPositionX(); + y = GetPositionY(); + z = GetPositionZ(); + + // group update + if (GetGroup() && (old_x != x || old_y != y)) + SetGroupUpdateFlag(GROUP_UPDATE_FLAG_POSITION); + + if (GetTrader() && !IsWithinDistInMap(GetTrader(), INTERACTION_DISTANCE)) + GetSession()->SendCancelTrade(); // will close both side trade windows + } + + if (m_positionStatusUpdateTimer) // Update position's state only on interval + return true; + m_positionStatusUpdateTimer = 100; + + // code block for underwater state update + UpdateUnderwaterState(m, x, y, z); + + // code block for outdoor state and area-explore check + CheckAreaExploreAndOutdoor(); + + return true; +} + +void Player::SaveRecallPosition() +{ + m_recallMap = GetMapId(); + m_recallX = GetPositionX(); + m_recallY = GetPositionY(); + m_recallZ = GetPositionZ(); + m_recallO = GetOrientation(); +} + +void Player::SendMessageToSet(WorldPacket* data, bool self) const +{ + if (IsInWorld()) + GetMap()->MessageBroadcast(this, data, false); + + // if player is not in world and map in not created/already destroyed + // no need to create one, just send packet for itself! + if (self) + GetSession()->SendPacket(data); +} + +void Player::SendMessageToSetInRange(WorldPacket* data, float dist, bool self) const +{ + if (IsInWorld()) + GetMap()->MessageDistBroadcast(this, data, dist, false); + + if (self) + GetSession()->SendPacket(data); +} + +void Player::SendMessageToSetInRange(WorldPacket* data, float dist, bool self, bool own_team_only) const +{ + if (IsInWorld()) + GetMap()->MessageDistBroadcast(this, data, dist, false, own_team_only); + + if (self) + GetSession()->SendPacket(data); +} + +void Player::SendDirectMessage(WorldPacket* data) const +{ + GetSession()->SendPacket(data); +} + +void Player::SendCinematicStart(uint32 CinematicSequenceId) +{ + WorldPacket data(SMSG_TRIGGER_CINEMATIC, 4); + data << uint32(CinematicSequenceId); + SendDirectMessage(&data); +} + +void Player::SendMovieStart(uint32 MovieId) +{ + WorldPacket data(SMSG_TRIGGER_MOVIE, 4); + data << uint32(MovieId); + SendDirectMessage(&data); +} + +void Player::CheckAreaExploreAndOutdoor() +{ + if (!isAlive()) + return; + + if (IsTaxiFlying()) + return; + + bool isOutdoor; + uint16 areaFlag = GetTerrain()->GetAreaFlag(GetPositionX(), GetPositionY(), GetPositionZ(), &isOutdoor); + + if (isOutdoor) + { + if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) && GetRestType() == REST_TYPE_IN_TAVERN) + { + AreaTriggerEntry const* at = sAreaTriggerStore.LookupEntry(inn_trigger_id); + if (!at || !IsPointInAreaTriggerZone(at, GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ())) + { + // Player left inn (REST_TYPE_IN_CITY overrides REST_TYPE_IN_TAVERN, so just clear rest) + SetRestType(REST_TYPE_NO); + } + } + // Check if we need to reaply outdoor only passive spells + const PlayerSpellMap& sp_list = GetSpellMap(); + for (PlayerSpellMap::const_iterator itr = sp_list.begin(); itr != sp_list.end(); ++itr) + { + if (itr->second.state == PLAYERSPELL_REMOVED) + continue; + SpellEntry const* spellInfo = sSpellStore.LookupEntry(itr->first); + if (!spellInfo || !IsNeedCastSpellAtOutdoor(spellInfo) || HasAura(itr->first)) + continue; + CastSpell(this, itr->first, true, NULL); + } + } + else if (sWorld.getConfig(CONFIG_BOOL_VMAP_INDOOR_CHECK) && !isGameMaster()) + RemoveAurasWithAttribute(SPELL_ATTR_OUTDOORS_ONLY); + + if (areaFlag == 0xffff) + return; + int offset = areaFlag / 32; + + if (offset >= PLAYER_EXPLORED_ZONES_SIZE) + { + sLog.outError("Wrong area flag %u in map data for (X: %f Y: %f) point to field PLAYER_EXPLORED_ZONES_1 + %u ( %u must be < %u ).", areaFlag, GetPositionX(), GetPositionY(), offset, offset, PLAYER_EXPLORED_ZONES_SIZE); + return; + } + + uint32 val = (uint32)(1 << (areaFlag % 32)); + uint32 currFields = GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + offset); + + if (!(currFields & val)) + { + SetUInt32Value(PLAYER_EXPLORED_ZONES_1 + offset, (uint32)(currFields | val)); + + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA); + + AreaTableEntry const* p = GetAreaEntryByAreaFlagAndMap(areaFlag, GetMapId()); + if (!p) + { + sLog.outError("PLAYER: Player %u discovered unknown area (x: %f y: %f map: %u", GetGUIDLow(), GetPositionX(), GetPositionY(), GetMapId()); + } + else if (p->area_level > 0) + { + uint32 area = p->ID; + if (getLevel() >= sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)) + { + SendExplorationExperience(area, 0); + } + else + { + int32 diff = int32(getLevel()) - p->area_level; + uint32 XP = 0; + if (diff < -5) + { + XP = uint32(sObjectMgr.GetBaseXP(getLevel() + 5) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE)); + } + else if (diff > 5) + { + int32 exploration_percent = (100 - ((diff - 5) * 5)); + if (exploration_percent > 100) + exploration_percent = 100; + else if (exploration_percent < 0) + exploration_percent = 0; + + XP = uint32(sObjectMgr.GetBaseXP(p->area_level) * exploration_percent / 100 * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE)); + } + else + { + XP = uint32(sObjectMgr.GetBaseXP(p->area_level) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE)); + } + + GiveXP(XP, NULL); + SendExplorationExperience(area, XP); + } + DETAIL_LOG("PLAYER: Player %u discovered a new area: %u", GetGUIDLow(), area); + } + } +} + +Team Player::TeamForRace(uint8 race) +{ + ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race); + if (!rEntry) + { + sLog.outError("Race %u not found in DBC: wrong DBC files?", uint32(race)); + return ALLIANCE; + } + + switch (rEntry->TeamID) + { + case 7: return ALLIANCE; + case 1: return HORDE; + } + + sLog.outError("Race %u have wrong teamid %u in DBC: wrong DBC files?", uint32(race), rEntry->TeamID); + return TEAM_NONE; +} + +uint32 Player::getFactionForRace(uint8 race) +{ + ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race); + if (!rEntry) + { + sLog.outError("Race %u not found in DBC: wrong DBC files?", uint32(race)); + return 0; + } + + return rEntry->FactionID; +} + +void Player::setFactionForRace(uint8 race) +{ + m_team = TeamForRace(race); + setFaction(getFactionForRace(race)); +} + +ReputationRank Player::GetReputationRank(uint32 faction) const +{ + FactionEntry const* factionEntry = sFactionStore.LookupEntry(faction); + return GetReputationMgr().GetRank(factionEntry); +} + +// Calculate total reputation percent player gain with quest/creature level +int32 Player::CalculateReputationGain(ReputationSource source, int32 rep, int32 faction, uint32 creatureOrQuestLevel, bool noAuraBonus) +{ + float percent = 100.0f; + + float repMod = noAuraBonus ? 0.0f : (float)GetTotalAuraModifier(SPELL_AURA_MOD_REPUTATION_GAIN); + + // faction specific auras only seem to apply to kills + if (source == REPUTATION_SOURCE_KILL) + repMod += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_FACTION_REPUTATION_GAIN, faction); + + percent += rep > 0 ? repMod : -repMod; + + float rate; + switch (source) + { + case REPUTATION_SOURCE_KILL: + rate = sWorld.getConfig(CONFIG_FLOAT_RATE_REPUTATION_LOWLEVEL_KILL); + break; + case REPUTATION_SOURCE_QUEST: + rate = sWorld.getConfig(CONFIG_FLOAT_RATE_REPUTATION_LOWLEVEL_QUEST); + break; + case REPUTATION_SOURCE_SPELL: + default: + rate = 1.0f; + break; + } + + if (rate != 1.0f && creatureOrQuestLevel <= MaNGOS::XP::GetGrayLevel(getLevel())) + percent *= rate; + + if (percent <= 0.0f) + return 0; + + // Multiply result with the faction specific rate + if (const RepRewardRate* repData = sObjectMgr.GetRepRewardRate(faction)) + { + float repRate = 0.0f; + switch (source) + { + case REPUTATION_SOURCE_KILL: + repRate = repData->creature_rate; + break; + case REPUTATION_SOURCE_QUEST: + repRate = repData->quest_rate; + break; + case REPUTATION_SOURCE_SPELL: + repRate = repData->spell_rate; + break; + } + + // for custom, a rate of 0.0 will totally disable reputation gain for this faction/type + if (repRate <= 0.0f) + return 0; + + percent *= repRate; + } + + return int32(sWorld.getConfig(CONFIG_FLOAT_RATE_REPUTATION_GAIN) * rep * percent / 100.0f); +} + +// Calculates how many reputation points player gains in victim's enemy factions +void Player::RewardReputation(Unit* pVictim, float rate) +{ + if (!pVictim || pVictim->GetTypeId() == TYPEID_PLAYER) + return; + + // used current difficulty creature entry instead normal version (GetEntry()) + ReputationOnKillEntry const* Rep = sObjectMgr.GetReputationOnKillEntry(((Creature*)pVictim)->GetCreatureInfo()->Entry); + + if (!Rep) + return; + + uint32 repFaction1 = Rep->repfaction1; + uint32 repFaction2 = Rep->repfaction2; + + // Championning tabard reputation system + // Aura 57818 is a hidden aura common to tabards allowing championning. + if (pVictim->GetMap()->IsNonRaidDungeon() && HasAura(57818)) + { + MapEntry const* storedMap = sMapStore.LookupEntry(GetMapId()); + InstanceTemplate const* instance = ObjectMgr::GetInstanceTemplate(GetMapId()); + Item const* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_TABARD); + if (storedMap && instance && pItem) + { + ItemPrototype const* pProto = pItem->GetProto();// Checked on load + // The required MinLevel for the tabard to work is related to the item level of the tabard + if ((instance->levelMin + 1 >= pProto->ItemLevel || !GetMap()->IsRegularDifficulty()) + // For ItemLevel == 75 (or 85) need to check expansion + && (pProto->ItemLevel == 75 && storedMap->Expansion() == EXPANSION_WOTLK)) + { + if (uint32 tabardFactionID = pItem->GetProto()->RequiredReputationFaction) + { + repFaction1 = tabardFactionID; + repFaction2 = tabardFactionID; + } + } + } + } + + if (repFaction1 && (!Rep->team_dependent || GetTeam() == ALLIANCE)) + { + int32 donerep1 = CalculateReputationGain(REPUTATION_SOURCE_KILL, Rep->repvalue1, repFaction1, pVictim->getLevel()); + donerep1 = int32(donerep1 * rate); + FactionEntry const* factionEntry1 = sFactionStore.LookupEntry(repFaction1); + uint32 current_reputation_rank1 = GetReputationMgr().GetRank(factionEntry1); + if (factionEntry1 && current_reputation_rank1 <= Rep->reputation_max_cap1) + GetReputationMgr().ModifyReputation(factionEntry1, donerep1); + + // Wiki: Team factions value divided by 2 + if (factionEntry1 && Rep->is_teamaward1) + { + FactionEntry const* team1_factionEntry = sFactionStore.LookupEntry(factionEntry1->team); + if (team1_factionEntry) + GetReputationMgr().ModifyReputation(team1_factionEntry, donerep1 / 2); + } + } + + if (repFaction2 && (!Rep->team_dependent || GetTeam() == HORDE)) + { + int32 donerep2 = CalculateReputationGain(REPUTATION_SOURCE_KILL, Rep->repvalue2, repFaction2, pVictim->getLevel()); + donerep2 = int32(donerep2 * rate); + FactionEntry const* factionEntry2 = sFactionStore.LookupEntry(repFaction2); + uint32 current_reputation_rank2 = GetReputationMgr().GetRank(factionEntry2); + if (factionEntry2 && current_reputation_rank2 <= Rep->reputation_max_cap2) + GetReputationMgr().ModifyReputation(factionEntry2, donerep2); + + // Wiki: Team factions value divided by 2 + if (factionEntry2 && Rep->is_teamaward2) + { + FactionEntry const* team2_factionEntry = sFactionStore.LookupEntry(factionEntry2->team); + if (team2_factionEntry) + GetReputationMgr().ModifyReputation(team2_factionEntry, donerep2 / 2); + } + } +} + +// Calculate how many reputation points player gain with the quest +void Player::RewardReputation(Quest const* pQuest) +{ + // quest reputation reward/loss + for (int i = 0; i < QUEST_REPUTATIONS_COUNT; ++i) + { + if (!pQuest->RewRepFaction[i]) + continue; + + // No diplomacy mod are applied to the final value (flat). Note the formula (finalValue = DBvalue/100) + if (pQuest->RewRepValue[i]) + { + int32 rep = CalculateReputationGain(REPUTATION_SOURCE_QUEST, pQuest->RewRepValue[i] / 100, pQuest->RewRepFaction[i], GetQuestLevelForPlayer(pQuest), true); + + if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(pQuest->RewRepFaction[i])) + GetReputationMgr().ModifyReputation(factionEntry, rep); + } + else + { + uint32 row = ((pQuest->RewRepValueId[i] < 0) ? 1 : 0) + 1; + uint32 field = abs(pQuest->RewRepValueId[i]); + + if (const QuestFactionRewardEntry* pRow = sQuestFactionRewardStore.LookupEntry(row)) + { + int32 repPoints = pRow->rewardValue[field]; + + if (!repPoints) + continue; + + repPoints = CalculateReputationGain(REPUTATION_SOURCE_QUEST, repPoints, pQuest->RewRepFaction[i], GetQuestLevelForPlayer(pQuest)); + + if (const FactionEntry* factionEntry = sFactionStore.LookupEntry(pQuest->RewRepFaction[i])) + GetReputationMgr().ModifyReputation(factionEntry, repPoints); + } + } + } + + // TODO: implement reputation spillover +} + +void Player::UpdateHonorKills() +{ + /// called when rewarding honor and at each save + time_t now = time(NULL); + time_t today = (time(NULL) / DAY) * DAY; + + if (m_lastHonorKillsUpdateTime < today) + { + time_t yesterday = today - DAY; + + uint16 kills_today = GetUInt16Value(PLAYER_FIELD_KILLS, 0); + + // update yesterday's contribution + if (m_lastHonorKillsUpdateTime >= yesterday) + { + // this is the first update today, reset today's contribution + SetUInt16Value(PLAYER_FIELD_KILLS, 0, 0); + SetUInt16Value(PLAYER_FIELD_KILLS, 1, kills_today); + } + else + { + // no honor/kills yesterday or today, reset + SetUInt32Value(PLAYER_FIELD_KILLS, 0); + } + } + + m_lastHonorKillsUpdateTime = now; +} + +/// Calculate the amount of honor gained based on the victim +/// and the size of the group for which the honor is divided +/// An exact honor value can also be given (overriding the calcs) +bool Player::RewardHonor(Unit* uVictim, uint32 groupsize, float honor) +{ + // do not reward honor in arenas, but enable onkill spellproc + if (InArena()) + { + if (!uVictim || uVictim == this || uVictim->GetTypeId() != TYPEID_PLAYER) + return false; + + if (GetBGTeam() == ((Player*)uVictim)->GetBGTeam()) + return false; + + return true; + } + + // 'Inactive' this aura prevents the player from gaining honor points and battleground tokens + if (GetDummyAura(SPELL_AURA_PLAYER_INACTIVE)) + return false; + + ObjectGuid victim_guid; + uint32 victim_rank = 0; + + // need call before fields update to have chance move yesterday data to appropriate fields before today data change. + UpdateHonorKills(); + + if (honor <= 0) + { + if (!uVictim || uVictim == this || uVictim->HasAuraType(SPELL_AURA_NO_PVP_CREDIT)) + return false; + + victim_guid = uVictim->GetObjectGuid(); + + if (uVictim->GetTypeId() == TYPEID_PLAYER) + { + Player* pVictim = (Player*)uVictim; + + if (GetTeam() == pVictim->GetTeam() && !sWorld.IsFFAPvPRealm()) + return false; + + float f = 1; // need for total kills (?? need more info) + uint32 k_grey = 0; + uint32 k_level = getLevel(); + uint32 v_level = pVictim->getLevel(); + + { + // PLAYER_CHOSEN_TITLE VALUES DESCRIPTION + // [0] Just name + // [1..14] Alliance honor titles and player name + // [15..28] Horde honor titles and player name + // [29..38] Other title and player name + // [39+] Nothing + uint32 victim_title = pVictim->GetUInt32Value(PLAYER_CHOSEN_TITLE); + // Get Killer titles, CharTitlesEntry::bit_index + // Ranks: + // title[1..14] -> rank[5..18] + // title[15..28] -> rank[5..18] + // title[other] -> 0 + if (victim_title == 0) + victim_guid.Clear(); // Don't show HK: message, only log. + else if (victim_title < 15) + victim_rank = victim_title + 4; + else if (victim_title < 29) + victim_rank = victim_title - 14 + 4; + else + victim_guid.Clear(); // Don't show HK: message, only log. + } + + k_grey = MaNGOS::XP::GetGrayLevel(k_level); + + if (v_level <= k_grey) + return false; + + float diff_level = (k_level == k_grey) ? 1 : ((float(v_level) - float(k_grey)) / (float(k_level) - float(k_grey))); + + int32 v_rank = 1; // need more info + + honor = ((f * diff_level * (190 + v_rank * 10)) / 6); + honor *= float(k_level) / 70.0f; // factor of dependence on levels of the killer + + // count the number of playerkills in one day + ApplyModUInt32Value(PLAYER_FIELD_KILLS, 1, true); + // and those in a lifetime + ApplyModUInt32Value(PLAYER_FIELD_LIFETIME_HONORBALE_KILLS, 1, true); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EARN_HONORABLE_KILL); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_CLASS, pVictim->getClass()); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_RACE, pVictim->getRace()); + } + else + { + Creature* cVictim = (Creature*)uVictim; + + if (!cVictim->IsRacialLeader()) + return false; + + honor = 100; // ??? need more info + victim_rank = 19; // HK: Leader + } + } + + if (uVictim != NULL) + { + honor *= sWorld.getConfig(CONFIG_FLOAT_RATE_HONOR); + honor *= (GetMaxPositiveAuraModifier(SPELL_AURA_MOD_HONOR_GAIN) + 100.0f) / 100.0f; + + if (groupsize > 1) + honor /= groupsize; + + honor *= (((float)urand(8, 12)) / 10); // approx honor: 80% - 120% of real honor + } + + // honor - for show honor points in log + // victim_guid - for show victim name in log + // victim_rank [1..4] HK: + // victim_rank [5..19] HK: + // victim_rank [0,20+] HK: <> + WorldPacket data(SMSG_PVP_CREDIT, 4 + 8 + 4); + data << uint32(honor); + data << ObjectGuid(victim_guid); + data << uint32(victim_rank); + GetSession()->SendPacket(&data); + + // add honor points + ModifyCurrencyCount(CURRENCY_HONOR_POINTS, int32(honor)); + + return true; +} + +void Player::SetInGuild(uint32 GuildId) +{ + if (GuildId) + SetGuidValue(OBJECT_FIELD_DATA, ObjectGuid(HIGHGUID_GUILD, 0, GuildId)); + else + SetGuidValue(OBJECT_FIELD_DATA, ObjectGuid()); + + ApplyModFlag(PLAYER_FLAGS, PLAYER_FLAGS_GUILD_LEVELING_ENABLED, GuildId != 0 && sWorld.getConfig(CONFIG_BOOL_GUILD_LEVELING_ENABLED)); + SetUInt16Value(OBJECT_FIELD_TYPE, 1, GuildId != 0); +} + +std::string Player::GetGuildName() const +{ + if (Guild* guild = sGuildMgr.GetGuildById(GetGuildId())) + return guild->GetName(); + + return ""; +} + +uint32 Player::GetGuildIdFromDB(ObjectGuid guid) +{ + uint32 lowguid = guid.GetCounter(); + + QueryResult* result = CharacterDatabase.PQuery("SELECT guildid FROM guild_member WHERE guid='%u'", lowguid); + if (!result) + return 0; + + uint32 id = result->Fetch()[0].GetUInt32(); + delete result; + return id; +} + +ObjectGuid Player::GetGuildGuidFromDB(ObjectGuid guid) +{ + if (uint32 guildId = GetGuildIdFromDB(guid)) + return ObjectGuid(HIGHGUID_GUILD, GetGuildIdFromDB(guid)); + else + return ObjectGuid(); +} + +uint32 Player::GetRankFromDB(ObjectGuid guid) +{ + QueryResult* result = CharacterDatabase.PQuery("SELECT rank FROM guild_member WHERE guid='%u'", guid.GetCounter()); + if (result) + { + uint32 v = result->Fetch()[0].GetUInt32(); + delete result; + return v; + } + else + return 0; +} + +void Player::SendGuildDeclined(std::string name, bool autodecline) +{ + WorldPacket data(SMSG_GUILD_DECLINE, 10); + data << name; + data << uint8(autodecline); + GetSession()->SendPacket(&data); +} + + +uint32 Player::GetArenaTeamIdFromDB(ObjectGuid guid, ArenaType type) +{ + QueryResult* result = CharacterDatabase.PQuery("SELECT arena_team_member.arenateamid FROM arena_team_member JOIN arena_team ON arena_team_member.arenateamid = arena_team.arenateamid WHERE guid='%u' AND type='%u' LIMIT 1", guid.GetCounter(), type); + if (!result) + return 0; + + uint32 id = (*result)[0].GetUInt32(); + delete result; + return id; +} + +uint32 Player::GetZoneIdFromDB(ObjectGuid guid) +{ + uint32 lowguid = guid.GetCounter(); + QueryResult* result = CharacterDatabase.PQuery("SELECT zone FROM characters WHERE guid='%u'", lowguid); + if (!result) + return 0; + Field* fields = result->Fetch(); + uint32 zone = fields[0].GetUInt32(); + delete result; + + if (!zone) + { + // stored zone is zero, use generic and slow zone detection + result = CharacterDatabase.PQuery("SELECT map,position_x,position_y,position_z FROM characters WHERE guid='%u'", lowguid); + if (!result) + return 0; + fields = result->Fetch(); + uint32 map = fields[0].GetUInt32(); + float posx = fields[1].GetFloat(); + float posy = fields[2].GetFloat(); + float posz = fields[3].GetFloat(); + delete result; + + zone = sTerrainMgr.GetZoneId(map, posx, posy, posz); + + if (zone > 0) + CharacterDatabase.PExecute("UPDATE characters SET zone='%u' WHERE guid='%u'", zone, lowguid); + } + + return zone; +} + +uint32 Player::GetLevelFromDB(ObjectGuid guid) +{ + uint32 lowguid = guid.GetCounter(); + + QueryResult* result = CharacterDatabase.PQuery("SELECT level FROM characters WHERE guid='%u'", lowguid); + if (!result) + return 0; + + Field* fields = result->Fetch(); + uint32 level = fields[0].GetUInt32(); + delete result; + + return level; +} + +void Player::UpdateArea(uint32 newArea) +{ + m_areaUpdateId = newArea; + + AreaTableEntry const* area = GetAreaEntryByAreaID(newArea); + + // FFA_PVP flags are area and not zone id dependent + // so apply them accordingly + if (area && (area->flags & AREA_FLAG_ARENA)) + { + if (!isGameMaster()) + SetFFAPvP(true); + } + else + { + // remove ffa flag only if not ffapvp realm + // removal in sanctuaries and capitals is handled in zone update + if (IsFFAPvP() && !sWorld.IsFFAPvPRealm()) + SetFFAPvP(false); + } + + phaseMgr->AddUpdateFlag(PHASE_UPDATE_FLAG_AREA_UPDATE); + + UpdateAreaDependentAuras(); + + phaseMgr->RemoveUpdateFlag(PHASE_UPDATE_FLAG_AREA_UPDATE); +} + +bool Player::CanUseCapturePoint() +{ + return isAlive() && // living + !HasStealthAura() && // not stealthed + !HasInvisibilityAura() && // visible + (IsPvP() || sWorld.IsPvPRealm()) && + !HasMovementFlag(MOVEFLAG_FLYING) && + !IsTaxiFlying() && + !isGameMaster(); +} + +void Player::UpdateZone(uint32 newZone, uint32 newArea) +{ + AreaTableEntry const* zone = GetAreaEntryByAreaID(newZone); + if (!zone) + return; + + phaseMgr->AddUpdateFlag(PHASE_UPDATE_FLAG_ZONE_UPDATE); + + if (m_zoneUpdateId != newZone) + { + // handle outdoor pvp zones + sOutdoorPvPMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId); + sOutdoorPvPMgr.HandlePlayerEnterZone(this, newZone); + + SendInitWorldStates(newZone, newArea); // only if really enters to new zone, not just area change, works strange... + + if (sWorld.getConfig(CONFIG_BOOL_WEATHER)) + { + if (Weather* wth = sWorld.FindWeather(zone->ID)) + wth->SendWeatherUpdateToPlayer(this); + else if (!sWorld.AddWeather(zone->ID)) + { + // send fine weather packet to remove old zone's weather + Weather::SendFineWeatherUpdateToPlayer(this); + } + } + } + + m_zoneUpdateId = newZone; + m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL; + + // zone changed, so area changed as well, update it + UpdateArea(newArea); + + // in PvP, any not controlled zone (except zone->team == 6, default case) + // in PvE, only opposition team capital + switch (zone->team) + { + case AREATEAM_ALLY: + pvpInfo.inHostileArea = GetTeam() != ALLIANCE && (sWorld.IsPvPRealm() || zone->flags & AREA_FLAG_CAPITAL); + break; + case AREATEAM_HORDE: + pvpInfo.inHostileArea = GetTeam() != HORDE && (sWorld.IsPvPRealm() || zone->flags & AREA_FLAG_CAPITAL); + break; + case AREATEAM_NONE: + // overwrite for battlegrounds, maybe batter some zone flags but current known not 100% fit to this + pvpInfo.inHostileArea = sWorld.IsPvPRealm() || InBattleGround(); + break; + default: // 6 in fact + pvpInfo.inHostileArea = false; + break; + } + + if (pvpInfo.inHostileArea) // in hostile area + { + if (!IsPvP() || pvpInfo.endTimer != 0) + UpdatePvP(true, true); + } + else // in friendly area + { + if (IsPvP() && !HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_IN_PVP) && pvpInfo.endTimer == 0) + pvpInfo.endTimer = time(0); // start toggle-off + } + + if (zone->flags & AREA_FLAG_SANCTUARY) // in sanctuary + { + SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY); + if (sWorld.IsFFAPvPRealm()) + SetFFAPvP(false); + } + else + { + RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY); + } + + if (zone->flags & AREA_FLAG_CAPITAL) // in capital city + SetRestType(REST_TYPE_IN_CITY); + else if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) && GetRestType() != REST_TYPE_IN_TAVERN) + // resting and not in tavern (leave city then); tavern leave handled in CheckAreaExploreAndOutdoor + SetRestType(REST_TYPE_NO); + + // remove items with area/map limitations (delete only for alive player to allow back in ghost mode) + // if player resurrected at teleport this will be applied in resurrect code + if (isAlive()) + DestroyZoneLimitedItem(true, newZone); + + // check some item equip limitations (in result lost CanTitanGrip at talent reset, for example) + AutoUnequipOffhandIfNeed(); + + // recent client version not send leave/join channel packets for built-in local channels + UpdateLocalChannels(newZone); + + // group update + if (GetGroup()) + SetGroupUpdateFlag(GROUP_UPDATE_FLAG_ZONE); + + UpdateZoneDependentAuras(); + UpdateZoneDependentPets(); + + phaseMgr->RemoveUpdateFlag(PHASE_UPDATE_FLAG_ZONE_UPDATE); +} + +// If players are too far way of duel flag... then player loose the duel +void Player::CheckDuelDistance(time_t currTime) +{ + if (!duel) + return; + + GameObject* obj = GetMap()->GetGameObject(GetGuidValue(PLAYER_DUEL_ARBITER)); + if (!obj) + { + // player not at duel start map + DuelComplete(DUEL_FLED); + return; + } + + if (duel->outOfBound == 0) + { + if (!IsWithinDistInMap(obj, 50)) + { + duel->outOfBound = currTime; + + WorldPacket data(SMSG_DUEL_OUTOFBOUNDS, 0); + GetSession()->SendPacket(&data); + } + } + else + { + if (IsWithinDistInMap(obj, 40)) + { + duel->outOfBound = 0; + + WorldPacket data(SMSG_DUEL_INBOUNDS, 0); + GetSession()->SendPacket(&data); + } + else if (currTime >= (duel->outOfBound + 10)) + { + DuelComplete(DUEL_FLED); + } + } +} + +void Player::DuelComplete(DuelCompleteType type) +{ + // duel not requested + if (!duel) + return; + + WorldPacket data(SMSG_DUEL_COMPLETE, (1)); + data << (uint8)((type != DUEL_INTERRUPTED) ? 1 : 0); + GetSession()->SendPacket(&data); + duel->opponent->GetSession()->SendPacket(&data); + + if (type != DUEL_INTERRUPTED) + { + data.Initialize(SMSG_DUEL_WINNER, (1 + 20)); // we guess size + data << (uint8)((type == DUEL_WON) ? 0 : 1); // 0 = just won; 1 = fled + data << duel->opponent->GetName(); + data << GetName(); + SendMessageToSet(&data, true); + } + + if (type == DUEL_WON) + { + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOSE_DUEL, 1); + if (duel->opponent) + duel->opponent->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_DUEL, 1); + } + + // Remove Duel Flag object + if (GameObject* obj = GetMap()->GetGameObject(GetGuidValue(PLAYER_DUEL_ARBITER))) + duel->initiator->RemoveGameObject(obj, true); + + /* remove auras */ + std::vector auras2remove; + SpellAuraHolderMap const& vAuras = duel->opponent->GetSpellAuraHolderMap(); + for (SpellAuraHolderMap::const_iterator i = vAuras.begin(); i != vAuras.end(); ++i) + { + if (!i->second->IsPositive() && i->second->GetCasterGuid() == GetObjectGuid() && i->second->GetAuraApplyTime() >= duel->startTime) + auras2remove.push_back(i->second->GetId()); + } + + for (size_t i = 0; i < auras2remove.size(); ++i) + duel->opponent->RemoveAurasDueToSpell(auras2remove[i]); + + auras2remove.clear(); + SpellAuraHolderMap const& auras = GetSpellAuraHolderMap(); + for (SpellAuraHolderMap::const_iterator i = auras.begin(); i != auras.end(); ++i) + { + if (!i->second->IsPositive() && i->second->GetCasterGuid() == duel->opponent->GetObjectGuid() && i->second->GetAuraApplyTime() >= duel->startTime) + auras2remove.push_back(i->second->GetId()); + } + for (size_t i = 0; i < auras2remove.size(); ++i) + RemoveAurasDueToSpell(auras2remove[i]); + + // cleanup combo points + if (GetComboTargetGuid() == duel->opponent->GetObjectGuid()) + ClearComboPoints(); + else if (GetComboTargetGuid() == duel->opponent->GetPetGuid()) + ClearComboPoints(); + + if (duel->opponent->GetComboTargetGuid() == GetObjectGuid()) + duel->opponent->ClearComboPoints(); + else if (duel->opponent->GetComboTargetGuid() == GetPetGuid()) + duel->opponent->ClearComboPoints(); + + // cleanups + SetGuidValue(PLAYER_DUEL_ARBITER, ObjectGuid()); + SetUInt32Value(PLAYER_DUEL_TEAM, 0); + duel->opponent->SetGuidValue(PLAYER_DUEL_ARBITER, ObjectGuid()); + duel->opponent->SetUInt32Value(PLAYER_DUEL_TEAM, 0); + + delete duel->opponent->duel; + duel->opponent->duel = NULL; + delete duel; + duel = NULL; +} + +//---------------------------------------------------------// + +void Player::_ApplyItemMods(Item* item, uint8 slot, bool apply) +{ + if (slot >= INVENTORY_SLOT_BAG_END || !item) + return; + + // not apply/remove mods for broken item + if (item->IsBroken()) + return; + + ItemPrototype const* proto = item->GetProto(); + + if (!proto) + return; + + DETAIL_LOG("applying mods for item %u ", item->GetGUIDLow()); + + uint32 attacktype = Player::GetAttackBySlot(slot); + if (attacktype < MAX_ATTACK) + _ApplyWeaponDependentAuraMods(item, WeaponAttackType(attacktype), apply); + + _ApplyItemBonuses(proto, slot, apply); + + if (slot == EQUIPMENT_SLOT_RANGED) + _ApplyAmmoBonuses(); + + ApplyItemEquipSpell(item, apply); + ApplyEnchantment(item, apply); + + if (proto->Socket[0].Color) // only (un)equipping of items with sockets can influence metagems, so no need to waste time with normal items + CorrectMetaGemEnchants(slot, apply); + + DEBUG_LOG("_ApplyItemMods complete."); +} + +void Player::_ApplyItemBonuses(ItemPrototype const* proto, uint8 slot, bool apply, bool only_level_scale /*= false*/) +{ + if (slot >= INVENTORY_SLOT_BAG_END || !proto) + return; + + ScalingStatDistributionEntry const* ssd = proto->ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(proto->ScalingStatDistribution) : NULL; + if (only_level_scale && !ssd) + return; + + // req. check at equip, but allow use for extended range if range limit max level, set proper level + uint32 ssd_level = getLevel(); + if (ssd && ssd_level > ssd->MaxLevel) + ssd_level = ssd->MaxLevel; + + ScalingStatValuesEntry const* ssv = proto->StatScalingFactor ? sScalingStatValuesStore.LookupEntry(ssd_level) : NULL; + if (only_level_scale && !ssv) + return; + + for (uint32 i = 0; i < MAX_ITEM_PROTO_STATS; ++i) + { + uint32 statType = 0; + int32 val = 0; + // If set ScalingStatDistribution need get stats and values from it + if (ssd && ssv) + { + if (ssd->StatMod[i] < 0) + continue; + statType = ssd->StatMod[i]; + val = (ssv->getssdMultiplier(proto->StatScalingFactor) * ssd->Modifier[i]) / 10000; + } + else + { + statType = proto->ItemStat[i].ItemStatType; + val = proto->ItemStat[i].ItemStatValue; + } + + if (val == 0) + continue; + + switch (statType) + { + /*case ITEM_MOD_MANA: + HandleStatModifier(UNIT_MOD_MANA, BASE_VALUE, float(val), apply); + break;*/ + case ITEM_MOD_AGILITY: // modify agility + HandleStatModifier(UNIT_MOD_STAT_AGILITY, BASE_VALUE, float(val), apply); + ApplyStatBuffMod(STAT_AGILITY, float(val), apply); + break; + case ITEM_MOD_STRENGTH: // modify strength + HandleStatModifier(UNIT_MOD_STAT_STRENGTH, BASE_VALUE, float(val), apply); + ApplyStatBuffMod(STAT_STRENGTH, float(val), apply); + break; + case ITEM_MOD_INTELLECT: // modify intellect + HandleStatModifier(UNIT_MOD_STAT_INTELLECT, BASE_VALUE, float(val), apply); + ApplyStatBuffMod(STAT_INTELLECT, float(val), apply); + break; + case ITEM_MOD_SPIRIT: // modify spirit + HandleStatModifier(UNIT_MOD_STAT_SPIRIT, BASE_VALUE, float(val), apply); + ApplyStatBuffMod(STAT_SPIRIT, float(val), apply); + break; + case ITEM_MOD_STAMINA: // modify stamina + HandleStatModifier(UNIT_MOD_STAT_STAMINA, BASE_VALUE, float(val), apply); + ApplyStatBuffMod(STAT_STAMINA, float(val), apply); + break; + case ITEM_MOD_DODGE_RATING: + ApplyRatingMod(CR_DODGE, int32(val), apply); + break; + case ITEM_MOD_PARRY_RATING: + ApplyRatingMod(CR_PARRY, int32(val), apply); + break; + case ITEM_MOD_CRIT_RANGED_RATING: + ApplyRatingMod(CR_CRIT_RANGED, int32(val), apply); + break; + case ITEM_MOD_HIT_RATING: + ApplyRatingMod(CR_HIT_MELEE, int32(val), apply); + ApplyRatingMod(CR_HIT_RANGED, int32(val), apply); + ApplyRatingMod(CR_HIT_SPELL, int32(val), apply); + break; + case ITEM_MOD_CRIT_RATING: + ApplyRatingMod(CR_CRIT_MELEE, int32(val), apply); + ApplyRatingMod(CR_CRIT_RANGED, int32(val), apply); + ApplyRatingMod(CR_CRIT_SPELL, int32(val), apply); + break; + case ITEM_MOD_RESILIENCE_RATING: + ApplyRatingMod(CR_RESILIENCE_DAMAGE_TAKEN, int32(val), apply); + break; + case ITEM_MOD_HASTE_RATING: + ApplyRatingMod(CR_HASTE_MELEE, int32(val), apply); + ApplyRatingMod(CR_HASTE_RANGED, int32(val), apply); + ApplyRatingMod(CR_HASTE_SPELL, int32(val), apply); + break; + case ITEM_MOD_EXPERTISE_RATING: + ApplyRatingMod(CR_EXPERTISE, int32(val), apply); + break; + case ITEM_MOD_ATTACK_POWER: + HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(val), apply); + HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(val), apply); + break; + case ITEM_MOD_RANGED_ATTACK_POWER: + HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(val), apply); + break; + case ITEM_MOD_SPELL_POWER: + ApplySpellPowerBonus(int32(val), apply); + break; + case ITEM_MOD_HEALTH_REGEN: + ApplyHealthRegenBonus(int32(val), apply); + break; + case ITEM_MOD_SPELL_PENETRATION: + ApplyModInt32Value(PLAYER_FIELD_MOD_TARGET_RESISTANCE, -int32(val), apply); + m_spellPenetrationItemMod += apply ? val : -val; + break; + case ITEM_MOD_MASTERY_RATING: + ApplyRatingMod(CR_MASTERY, int32(val), apply); + break; + // deprecated item mods + case ITEM_MOD_HEALTH: + case ITEM_MOD_DEFENSE_SKILL_RATING: + case ITEM_MOD_BLOCK_RATING: + case ITEM_MOD_HIT_MELEE_RATING: + case ITEM_MOD_HIT_RANGED_RATING: + case ITEM_MOD_HIT_SPELL_RATING: + case ITEM_MOD_CRIT_MELEE_RATING: + case ITEM_MOD_CRIT_SPELL_RATING: + case ITEM_MOD_HIT_TAKEN_MELEE_RATING: + case ITEM_MOD_HIT_TAKEN_RANGED_RATING: + case ITEM_MOD_HIT_TAKEN_SPELL_RATING: + case ITEM_MOD_CRIT_TAKEN_MELEE_RATING: + case ITEM_MOD_CRIT_TAKEN_RANGED_RATING: + case ITEM_MOD_CRIT_TAKEN_SPELL_RATING: + case ITEM_MOD_HASTE_MELEE_RATING: + case ITEM_MOD_HASTE_RANGED_RATING: + case ITEM_MOD_HASTE_SPELL_RATING: + case ITEM_MOD_HIT_TAKEN_RATING: + case ITEM_MOD_CRIT_TAKEN_RATING: + case ITEM_MOD_FERAL_ATTACK_POWER: + case ITEM_MOD_SPELL_HEALING_DONE: + case ITEM_MOD_SPELL_DAMAGE_DONE: + case ITEM_MOD_MANA_REGENERATION: + case ITEM_MOD_ARMOR_PENETRATION_RATING: + case ITEM_MOD_BLOCK_VALUE: + break; + } + } + + // Apply Spell Power from ScalingStatValue if set + if (ssv) + { + if (int32 spellbonus = ssv->getSpellBonus(proto->StatScalingFactor)) + ApplySpellPowerBonus(spellbonus, apply); + } + + // If set ScalingStatValue armor get it or use item armor + uint32 armor = proto->GetArmor(); + if (ssv) + { + if (uint32 ssvarmor = ssv->getArmorMod(proto->StatScalingFactor)) + armor = ssvarmor; + } + // Add armor bonus from ArmorDamageModifier if > 0 + if (proto->ArmorDamageModifier > 0) + armor += uint32(proto->ArmorDamageModifier); + + if (armor) + { + switch (proto->InventoryType) + { + case INVTYPE_TRINKET: + case INVTYPE_NECK: + case INVTYPE_CLOAK: + case INVTYPE_FINGER: + HandleStatModifier(UNIT_MOD_ARMOR, TOTAL_VALUE, float(armor), apply); + break; + default: + HandleStatModifier(UNIT_MOD_ARMOR, BASE_VALUE, float(armor), apply); + break; + } + } + + WeaponAttackType attType = BASE_ATTACK; + float damage = 0.0f; + + if (slot == EQUIPMENT_SLOT_RANGED && ( + proto->InventoryType == INVTYPE_RANGED || proto->InventoryType == INVTYPE_THROWN || + proto->InventoryType == INVTYPE_RANGEDRIGHT)) + { + attType = RANGED_ATTACK; + } + else if (slot == EQUIPMENT_SLOT_OFFHAND) + { + attType = OFF_ATTACK; + } + + float minDamage = proto->GetMinDamage(); + float maxDamage = proto->GetMaxDamage(); + int32 extraDPS = 0; + // If set dpsMod in ScalingStatValue use it for min (70% from average), max (130% from average) damage + if (ssv) + { + if ((extraDPS = ssv->getDPSMod(proto->StatScalingFactor))) + { + float average = extraDPS * proto->Delay / 1000.0f; + minDamage = 0.7f * average; + maxDamage = 1.3f * average; + } + } + if (minDamage > 0) + { + damage = apply ? minDamage : BASE_MINDAMAGE; + SetBaseWeaponDamage(attType, MINDAMAGE, damage); + // sLog.outError("applying mindam: assigning %f to weapon mindamage, now is: %f", damage, GetWeaponDamageRange(attType, MINDAMAGE)); + } + + if (maxDamage > 0) + { + damage = apply ? maxDamage : BASE_MAXDAMAGE; + SetBaseWeaponDamage(attType, MAXDAMAGE, damage); + } + + if (!CanUseEquippedWeapon(attType)) + return; + + if (proto->Delay) + { + if (slot == EQUIPMENT_SLOT_RANGED) + SetAttackTime(RANGED_ATTACK, apply ? proto->Delay : BASE_ATTACK_TIME); + else if (slot == EQUIPMENT_SLOT_MAINHAND) + SetAttackTime(BASE_ATTACK, apply ? proto->Delay : BASE_ATTACK_TIME); + else if (slot == EQUIPMENT_SLOT_OFFHAND) + SetAttackTime(OFF_ATTACK, apply ? proto->Delay : BASE_ATTACK_TIME); + } + + if (CanModifyStats() && (damage || proto->Delay)) + UpdateDamagePhysical(attType); +} + +void Player::_ApplyWeaponDependentAuraMods(Item* item, WeaponAttackType attackType, bool apply) +{ + AuraList const& auraCritList = GetAurasByType(SPELL_AURA_MOD_CRIT_PERCENT); + for (AuraList::const_iterator itr = auraCritList.begin(); itr != auraCritList.end(); ++itr) + _ApplyWeaponDependentAuraCritMod(item, attackType, *itr, apply); + + AuraList const& auraDamageFlatList = GetAurasByType(SPELL_AURA_MOD_DAMAGE_DONE); + for (AuraList::const_iterator itr = auraDamageFlatList.begin(); itr != auraDamageFlatList.end(); ++itr) + _ApplyWeaponDependentAuraDamageMod(item, attackType, *itr, apply); + + AuraList const& auraDamagePCTList = GetAurasByType(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE); + for (AuraList::const_iterator itr = auraDamagePCTList.begin(); itr != auraDamagePCTList.end(); ++itr) + _ApplyWeaponDependentAuraDamageMod(item, attackType, *itr, apply); +} + +void Player::_ApplyWeaponDependentAuraCritMod(Item* item, WeaponAttackType attackType, Aura* aura, bool apply) +{ + // generic not weapon specific case processes in aura code + if(aura->GetSpellProto()->GetEquippedItemClass() == -1) + return; + + BaseModGroup mod = BASEMOD_END; + switch (attackType) + { + case BASE_ATTACK: mod = CRIT_PERCENTAGE; break; + case OFF_ATTACK: mod = OFFHAND_CRIT_PERCENTAGE; break; + case RANGED_ATTACK: mod = RANGED_CRIT_PERCENTAGE; break; + default: return; + } + + if (item->IsFitToSpellRequirements(aura->GetSpellProto())) + { + HandleBaseModValue(mod, FLAT_MOD, float(aura->GetModifier()->m_amount), apply); + } +} + +void Player::_ApplyWeaponDependentAuraDamageMod(Item* item, WeaponAttackType attackType, Aura* aura, bool apply) +{ + // ignore spell mods for not wands + Modifier const* modifier = aura->GetModifier(); + if ((modifier->m_miscvalue & SPELL_SCHOOL_MASK_NORMAL) == 0 && (getClassMask() & CLASSMASK_WAND_USERS) == 0) + return; + + // generic not weapon specific case processes in aura code + if(aura->GetSpellProto()->GetEquippedItemClass() == -1) + return; + + UnitMods unitMod = UNIT_MOD_END; + switch (attackType) + { + case BASE_ATTACK: unitMod = UNIT_MOD_DAMAGE_MAINHAND; break; + case OFF_ATTACK: unitMod = UNIT_MOD_DAMAGE_OFFHAND; break; + case RANGED_ATTACK: unitMod = UNIT_MOD_DAMAGE_RANGED; break; + default: return; + } + + UnitModifierType unitModType = TOTAL_VALUE; + switch (modifier->m_auraname) + { + case SPELL_AURA_MOD_DAMAGE_DONE: unitModType = TOTAL_VALUE; break; + case SPELL_AURA_MOD_DAMAGE_PERCENT_DONE: unitModType = TOTAL_PCT; break; + default: return; + } + + if (item->IsFitToSpellRequirements(aura->GetSpellProto())) + { + HandleStatModifier(unitMod, unitModType, float(modifier->m_amount), apply); + } +} + +void Player::ApplyItemEquipSpell(Item* item, bool apply, bool form_change) +{ + if (!item) + return; + + ItemPrototype const* proto = item->GetProto(); + if (!proto) + return; + + for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + _Spell const& spellData = proto->Spells[i]; + + // no spell + if (!spellData.SpellId) + continue; + + if (apply) + { + // apply only at-equip spells + if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_EQUIP) + continue; + } + else + { + // at un-apply remove all spells (not only at-apply, so any at-use active affects from item and etc) + // except with at-use with negative charges, so allow consuming item spells (including with extra flag that prevent consume really) + // applied to player after item remove from equip slot + if (spellData.SpellTrigger == ITEM_SPELLTRIGGER_ON_USE && spellData.SpellCharges < 0) + continue; + } + + // check if it is valid spell + SpellEntry const* spellproto = sSpellStore.LookupEntry(spellData.SpellId); + if (!spellproto) + continue; + + ApplyEquipSpell(spellproto, item, apply, form_change); + } +} + +void Player::ApplyEquipSpell(SpellEntry const* spellInfo, Item* item, bool apply, bool form_change) +{ + if (apply) + { + // Cannot be used in this stance/form + if (GetErrorAtShapeshiftedCast(spellInfo, GetShapeshiftForm()) != SPELL_CAST_OK) + return; + + if (form_change) // check aura active state from other form + { + SpellAuraHolderBounds spair = GetSpellAuraHolderBounds(spellInfo->Id); + for (SpellAuraHolderMap::const_iterator iter = spair.first; iter != spair.second; ++iter) + { + if (!item || iter->second->GetCastItemGuid() == item->GetObjectGuid()) + return; // and skip re-cast already active aura at form change + } + } + + DEBUG_LOG("WORLD: cast %s Equip spellId - %i", (item ? "item" : "itemset"), spellInfo->Id); + + CastSpell(this, spellInfo, true, item); + } + else + { + if (form_change) // check aura compatibility + { + // Cannot be used in this stance/form + if (GetErrorAtShapeshiftedCast(spellInfo, GetShapeshiftForm()) == SPELL_CAST_OK) + return; // and remove only not compatible at form change + } + + if (item) + RemoveAurasDueToItemSpell(item, spellInfo->Id); // un-apply all spells , not only at-equipped + else + RemoveAurasDueToSpell(spellInfo->Id); // un-apply spell (item set case) + } +} + +void Player::UpdateEquipSpellsAtFormChange() +{ + for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (m_items[i] && !m_items[i]->IsBroken()) + { + ApplyItemEquipSpell(m_items[i], false, true); // remove spells that not fit to form + ApplyItemEquipSpell(m_items[i], true, true); // add spells that fit form but not active + } + } + + // item set bonuses not dependent from item broken state + for (size_t setindex = 0; setindex < ItemSetEff.size(); ++setindex) + { + ItemSetEffect* eff = ItemSetEff[setindex]; + if (!eff) + continue; + + for (uint32 y = 0; y < 8; ++y) + { + SpellEntry const* spellInfo = eff->spells[y]; + if (!spellInfo) + continue; + + ApplyEquipSpell(spellInfo, NULL, false, true); // remove spells that not fit to form + ApplyEquipSpell(spellInfo, NULL, true, true); // add spells that fit form but not active + } + } +} + +/* + * (un-)Apply item spells triggered at adding item to inventory ITEM_SPELLTRIGGER_ON_STORE + * + * @param item added/removed item to/from inventory + * @param apply (un-)apply spell affects. + * + * Note: item moved from slot to slot in 2 steps RemoveItem and StoreItem/EquipItem + * In result function not called in RemoveItem for prevent unexpected re-apply auras from related spells + * with duration reset and etc. Instead unapply done in StoreItem/EquipItem and in specialized + * functions for item final remove/destroy from inventory. If new RemoveItem calls added need be sure that + * function will call after it in some way if need. + */ + +void Player::ApplyItemOnStoreSpell(Item* item, bool apply) +{ + if (!item) + return; + + ItemPrototype const* proto = item->GetProto(); + if (!proto) + return; + + for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + _Spell const& spellData = proto->Spells[i]; + + // no spell + if (!spellData.SpellId) + continue; + + // apply/unapply only at-store spells + if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_STORE) + continue; + + if (apply) + { + // can be attempt re-applied at move in inventory slots + if (!HasAura(spellData.SpellId)) + CastSpell(this, spellData.SpellId, true, item); + } + else + RemoveAurasDueToItemSpell(item, spellData.SpellId); + } +} + +void Player::DestroyItemWithOnStoreSpell(Item* item, uint32 spellId) +{ + if (!item) + return; + + ItemPrototype const* proto = item->GetProto(); + if (!proto) + return; + + for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + _Spell const& spellData = proto->Spells[i]; + + if (spellData.SpellId != spellId) + continue; + + // apply/unapply only at-store spells + if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_STORE) + continue; + + DestroyItem(item->GetBagSlot(), item->GetSlot(), true); + break; + } +} + +/// handles unique effect of Deadly Poison: apply poison of the other weapon when already at max. stack +void Player::_HandleDeadlyPoison(Unit* Target, WeaponAttackType attType, SpellEntry const* spellInfo) +{ + SpellAuraHolder const* dPoison = NULL; + SpellAuraHolderConstBounds holders = Target->GetSpellAuraHolderBounds(spellInfo->Id); + for (SpellAuraHolderMap::const_iterator iter = holders.first; iter != holders.second; ++iter) + { + if (iter->second->GetCaster() == this) + { + dPoison = iter->second; + break; + } + } + if (dPoison && dPoison->GetStackAmount() == spellInfo->GetStackAmount()) + { + Item* otherWeapon = GetWeaponForAttack(attType == BASE_ATTACK ? OFF_ATTACK : BASE_ATTACK); + if (!otherWeapon) + return; + + // all poison enchantments are temporary + uint32 enchant_id = otherWeapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT); + if (!enchant_id) + return; + + SpellItemEnchantmentEntry const* pSecondEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pSecondEnchant) + return; + + for (int s = 0; s < 3; ++s) + { + if (pSecondEnchant->type[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL) + continue; + + if (SpellEntry const* combatEntry = sSpellStore.LookupEntry(pSecondEnchant->spellid[s])) + if (combatEntry->GetDispel() == DISPEL_POISON) + CastSpell(Target, combatEntry, true, otherWeapon); + } + } +} + +void Player::CastItemCombatSpell(Unit* Target, WeaponAttackType attType) +{ + Item* item = GetWeaponForAttack(attType, true, false); + if (!item) + return; + + ItemPrototype const* proto = item->GetProto(); + if (!proto) + return; + + if (!Target || Target == this) + return; + + for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + _Spell const& spellData = proto->Spells[i]; + + // no spell + if (!spellData.SpellId) + continue; + + // wrong triggering type + if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_CHANCE_ON_HIT) + continue; + + SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellData.SpellId); + if (!spellInfo) + { + sLog.outError("WORLD: unknown Item spellid %i", spellData.SpellId); + continue; + } + + // not allow proc extra attack spell at extra attack + if (m_extraAttacks && IsSpellHaveEffect(spellInfo, SPELL_EFFECT_ADD_EXTRA_ATTACKS)) + return; + + float chance = (float)spellInfo->GetProcChance(); + + if (spellData.SpellPPMRate) + { + uint32 WeaponSpeed = proto->Delay; + chance = GetPPMProcChance(WeaponSpeed, spellData.SpellPPMRate); + } + else if (chance > 100.0f) + { + chance = GetWeaponProcChance(); + } + + if (roll_chance_f(chance)) + CastSpell(Target, spellInfo->Id, true, item); + } + + // item combat enchantments + for (int e_slot = 0; e_slot < MAX_ENCHANTMENT_SLOT; ++e_slot) + { + if (e_slot > PRISMATIC_ENCHANTMENT_SLOT && e_slot < PROP_ENCHANTMENT_SLOT_0) + continue; + + uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(e_slot)); + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) continue; + for (int s = 0; s < 3; ++s) + { + if (pEnchant->type[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL) + continue; + + uint32 proc_spell_id = pEnchant->spellid[s]; + SpellEntry const* spellInfo = sSpellStore.LookupEntry(proc_spell_id); + if (!spellInfo) + { + sLog.outError("Player::CastItemCombatSpell Enchant %i, cast unknown spell %i", pEnchant->ID, proc_spell_id); + continue; + } + + // Use first rank to access spell item enchant procs + float ppmRate = sSpellMgr.GetItemEnchantProcChance(spellInfo->Id); + + float chance = ppmRate + ? GetPPMProcChance(proto->Delay, ppmRate) + : pEnchant->amount[s] != 0 ? float(pEnchant->amount[s]) : GetWeaponProcChance(); + + ApplySpellMod(spellInfo->Id, SPELLMOD_CHANCE_OF_SUCCESS, chance); + ApplySpellMod(spellInfo->Id, SPELLMOD_FREQUENCY_OF_SUCCESS, chance); + + if (roll_chance_f(chance)) + { + if (IsPositiveSpell(spellInfo->Id)) + CastSpell(this, spellInfo->Id, true, item); + else + { + // Deadly Poison, unique effect needs to be handled before casting triggered spell + if (spellInfo->IsFitToFamily(SPELLFAMILY_ROGUE, UI64LIT(0x0000000000010000))) + _HandleDeadlyPoison(Target, attType, spellInfo); + + CastSpell(Target, spellInfo->Id, true, item); + } + } + } + } +} + +void Player::CastItemUseSpell(Item* item, SpellCastTargets const& targets, uint8 cast_count, uint32 glyphIndex) +{ + ItemPrototype const* proto = item->GetProto(); + // special learning case + if (proto->Spells[0].SpellId == SPELL_ID_GENERIC_LEARN || proto->Spells[0].SpellId == SPELL_ID_GENERIC_LEARN_PET) + { + uint32 learn_spell_id = proto->Spells[0].SpellId; + uint32 learning_spell_id = proto->Spells[1].SpellId; + + SpellEntry const* spellInfo = sSpellStore.LookupEntry(learn_spell_id); + if (!spellInfo) + { + sLog.outError("Player::CastItemUseSpell: Item (Entry: %u) in have wrong spell id %u, ignoring ", proto->ItemId, learn_spell_id); + SendEquipError(EQUIP_ERR_NONE, item); + return; + } + + Spell* spell = new Spell(this, spellInfo, false); + spell->m_CastItem = item; + spell->m_cast_count = cast_count; // set count of casts + spell->m_currentBasePoints[EFFECT_INDEX_0] = learning_spell_id; + spell->prepare(&targets); + return; + } + + // use triggered flag only for items with many spell casts and for not first cast + int count = 0; + + // item spells casted at use + for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + _Spell const& spellData = proto->Spells[i]; + + // no spell + if (!spellData.SpellId) + continue; + + // wrong triggering type + if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_USE) + continue; + + SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellData.SpellId); + if (!spellInfo) + { + sLog.outError("Player::CastItemUseSpell: Item (Entry: %u) in have wrong spell id %u, ignoring", proto->ItemId, spellData.SpellId); + continue; + } + + Spell* spell = new Spell(this, spellInfo, (count > 0)); + spell->m_CastItem = item; + spell->m_cast_count = cast_count; // set count of casts + spell->m_glyphIndex = glyphIndex; // glyph index + spell->prepare(&targets); + + ++count; + } + + // Item enchantments spells casted at use + for (int e_slot = 0; e_slot < MAX_ENCHANTMENT_SLOT; ++e_slot) + { + if (e_slot > PRISMATIC_ENCHANTMENT_SLOT && e_slot < PROP_ENCHANTMENT_SLOT_0) + continue; + + uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(e_slot)); + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + continue; + + for (int s = 0; s < 3; ++s) + { + if (pEnchant->type[s] != ITEM_ENCHANTMENT_TYPE_USE_SPELL) + continue; + + SpellEntry const* spellInfo = sSpellStore.LookupEntry(pEnchant->spellid[s]); + if (!spellInfo) + { + sLog.outError("Player::CastItemUseSpell Enchant %i, cast unknown spell %i", pEnchant->ID, pEnchant->spellid[s]); + continue; + } + + Spell* spell = new Spell(this, spellInfo, (count > 0)); + spell->m_CastItem = item; + spell->m_cast_count = cast_count; // set count of casts + spell->m_glyphIndex = glyphIndex; // glyph index + spell->prepare(&targets); + + ++count; + } + } +} + +void Player::_RemoveAllItemMods() +{ + DEBUG_LOG("_RemoveAllItemMods start."); + + for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (m_items[i]) + { + ItemPrototype const* proto = m_items[i]->GetProto(); + if (!proto) + continue; + + // item set bonuses not dependent from item broken state + if (proto->ItemSet) + RemoveItemsSetItem(this, proto); + + if (m_items[i]->IsBroken()) + continue; + + ApplyItemEquipSpell(m_items[i], false); + ApplyEnchantment(m_items[i], false); + } + } + + for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (m_items[i]) + { + if (m_items[i]->IsBroken()) + continue; + ItemPrototype const* proto = m_items[i]->GetProto(); + if (!proto) + continue; + + uint32 attacktype = Player::GetAttackBySlot(i); + if (attacktype < MAX_ATTACK) + _ApplyWeaponDependentAuraMods(m_items[i], WeaponAttackType(attacktype), false); + + _ApplyItemBonuses(proto, i, false); + + if (i == EQUIPMENT_SLOT_RANGED) + _ApplyAmmoBonuses(); + } + } + + DEBUG_LOG("_RemoveAllItemMods complete."); +} + +void Player::_ApplyAllItemMods() +{ + DEBUG_LOG("_ApplyAllItemMods start."); + + for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (m_items[i]) + { + if (m_items[i]->IsBroken()) + continue; + + ItemPrototype const* proto = m_items[i]->GetProto(); + if (!proto) + continue; + + uint32 attacktype = Player::GetAttackBySlot(i); + if (attacktype < MAX_ATTACK) + _ApplyWeaponDependentAuraMods(m_items[i], WeaponAttackType(attacktype), true); + + _ApplyItemBonuses(proto, i, true); + + if (i == EQUIPMENT_SLOT_RANGED) + _ApplyAmmoBonuses(); + } + } + + for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (m_items[i]) + { + ItemPrototype const* proto = m_items[i]->GetProto(); + if (!proto) + continue; + + // item set bonuses not dependent from item broken state + if (proto->ItemSet) + AddItemsSetItem(this, m_items[i]); + + if (m_items[i]->IsBroken()) + continue; + + ApplyItemEquipSpell(m_items[i], true); + ApplyEnchantment(m_items[i], true); + } + } + + DEBUG_LOG("_ApplyAllItemMods complete."); +} + +void Player::_ApplyAllLevelScaleItemMods(bool apply) +{ + for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (m_items[i]) + { + if (m_items[i]->IsBroken()) + continue; + + ItemPrototype const* proto = m_items[i]->GetProto(); + if (!proto) + continue; + + _ApplyItemBonuses(proto, i, apply, true); + } + } +} + +void Player::_ApplyAmmoBonuses() +{ + //// check ammo + //uint32 ammo_id = GetUInt32Value(PLAYER_AMMO_ID); + //if(!ammo_id) + // return; + + //float currentAmmoDPS; + + //ItemPrototype const *ammo_proto = ObjectMgr::GetItemPrototype( ammo_id ); + //if( !ammo_proto || ammo_proto->Class!=ITEM_CLASS_PROJECTILE || !CheckAmmoCompatibility(ammo_proto)) + // currentAmmoDPS = 0.0f; + //else + // currentAmmoDPS = ammo_proto->Damage[0].DamageMin; + + //if(currentAmmoDPS == GetAmmoDPS()) + // return; + + //m_ammoDPS = currentAmmoDPS; + + //if(CanModifyStats()) + // UpdateDamagePhysical(RANGED_ATTACK); +} + +bool Player::CheckAmmoCompatibility(const ItemPrototype* ammo_proto) const +{ + if (!ammo_proto) + return false; + + // check ranged weapon + Item* weapon = GetWeaponForAttack(RANGED_ATTACK, true, false); + if (!weapon) + return false; + + ItemPrototype const* weapon_proto = weapon->GetProto(); + if (!weapon_proto || weapon_proto->Class != ITEM_CLASS_WEAPON) + return false; + + // check ammo ws. weapon compatibility + switch (weapon_proto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_BOW: + case ITEM_SUBCLASS_WEAPON_CROSSBOW: + if (ammo_proto->SubClass != ITEM_SUBCLASS_ARROW) + return false; + break; + case ITEM_SUBCLASS_WEAPON_GUN: + if (ammo_proto->SubClass != ITEM_SUBCLASS_BULLET) + return false; + break; + default: + return false; + } + + return true; +} + +/* If in a battleground a player dies, and an enemy removes the insignia, the player's bones is lootable + Called by remove insignia spell effect */ +void Player::RemovedInsignia(Player* looterPlr) +{ + if (!GetBattleGroundId()) + return; + + // If not released spirit, do it ! + if (m_deathTimer > 0) + { + m_deathTimer = 0; + BuildPlayerRepop(); + RepopAtGraveyard(); + } + + Corpse* corpse = GetCorpse(); + if (!corpse) + return; + + // We have to convert player corpse to bones, not to be able to resurrect there + // SpawnCorpseBones isn't handy, 'cos it saves player while he in BG + Corpse* bones = sObjectAccessor.ConvertCorpseForPlayer(GetObjectGuid(), true); + if (!bones) + return; + + // Now we must make bones lootable, and send player loot + bones->SetFlag(CORPSE_FIELD_DYNAMIC_FLAGS, CORPSE_DYNFLAG_LOOTABLE); + + // We store the level of our player in the gold field + // We retrieve this information at Player::SendLoot() + bones->loot.gold = getLevel(); + bones->lootRecipient = looterPlr; + looterPlr->SendLoot(bones->GetObjectGuid(), LOOT_INSIGNIA); +} + +void Player::SendLootRelease(ObjectGuid guid) +{ + WorldPacket data(SMSG_LOOT_RELEASE_RESPONSE, (8 + 1)); + data << guid; + data << uint8(1); + SendDirectMessage(&data); +} + +void Player::SendLoot(ObjectGuid guid, LootType loot_type) +{ + if (ObjectGuid lootGuid = GetLootGuid()) + m_session->DoLootRelease(lootGuid); + + Loot* loot = NULL; + PermissionTypes permission = ALL_PERMISSION; + + DEBUG_LOG("Player::SendLoot"); + switch (guid.GetHigh()) + { + case HIGHGUID_GAMEOBJECT: + { + DEBUG_LOG(" IS_GAMEOBJECT_GUID(guid)"); + GameObject* go = GetMap()->GetGameObject(guid); + + // not check distance for GO in case owned GO (fishing bobber case, for example) + // And permit out of range GO with no owner in case fishing hole + if (!go || (loot_type != LOOT_FISHINGHOLE && (loot_type != LOOT_FISHING && loot_type != LOOT_FISHING_FAIL || go->GetOwnerGuid() != GetObjectGuid()) && !go->IsWithinDistInMap(this, INTERACTION_DISTANCE))) + { + SendLootRelease(guid); + return; + } + + loot = &go->loot; + + Player* recipient = go->GetLootRecipient(); + if (!recipient) + { + go->SetLootRecipient(this); + recipient = this; + } + + // generate loot only if ready for open and spawned in world + if (go->getLootState() == GO_READY && go->isSpawned()) + { + uint32 lootid = go->GetGOInfo()->GetLootId(); + if ((go->GetEntry() == BG_AV_OBJECTID_MINE_N || go->GetEntry() == BG_AV_OBJECTID_MINE_S)) + { + if (BattleGround* bg = GetBattleGround()) + if (bg->GetTypeID() == BATTLEGROUND_AV) + if (!(((BattleGroundAV*)bg)->PlayerCanDoMineQuest(go->GetEntry(), GetTeam()))) + { + SendLootRelease(guid); + return; + } + } + + loot->clear(); + switch (loot_type) + { + // Entry 0 in fishing loot template used for store junk fish loot at fishing fail it junk allowed by config option + // this is overwrite fishinghole loot for example + case LOOT_FISHING_FAIL: + loot->FillLoot(0, LootTemplates_Fishing, this, true); + break; + case LOOT_FISHING: + uint32 zone, subzone; + go->GetZoneAndAreaId(zone, subzone); + // if subzone loot exist use it + if (!loot->FillLoot(subzone, LootTemplates_Fishing, this, true, (subzone != zone)) && subzone != zone) + // else use zone loot (if zone diff. from subzone, must exist in like case) + loot->FillLoot(zone, LootTemplates_Fishing, this, true); + break; + default: + if (!lootid) + break; + DEBUG_LOG(" send normal GO loot"); + + loot->FillLoot(lootid, LootTemplates_Gameobject, this, false); + loot->generateMoneyLoot(go->GetGOInfo()->MinMoneyLoot, go->GetGOInfo()->MaxMoneyLoot); + + if (go->GetGoType() == GAMEOBJECT_TYPE_CHEST && go->GetGOInfo()->chest.groupLootRules) + { + if (Group* group = go->GetGroupLootRecipient()) + { + group->UpdateLooterGuid(go, true); + + switch (group->GetLootMethod()) + { + case GROUP_LOOT: + // GroupLoot delete items over threshold (threshold even not implemented), and roll them. Items with qualityGroupLoot(go, loot); + permission = GROUP_PERMISSION; + break; + case NEED_BEFORE_GREED: + group->NeedBeforeGreed(go, loot); + permission = GROUP_PERMISSION; + break; + case MASTER_LOOT: + group->MasterLoot(go, loot); + permission = MASTER_PERMISSION; + break; + default: + break; + } + } + } + break; + } + + go->SetLootState(GO_ACTIVATED); + } + if (go->getLootState() == GO_ACTIVATED && go->GetGoType() == GAMEOBJECT_TYPE_CHEST && go->GetGOInfo()->chest.groupLootRules) + { + if (Group* group = go->GetGroupLootRecipient()) + { + if (group == GetGroup()) + { + if (group->GetLootMethod() == FREE_FOR_ALL) + permission = ALL_PERMISSION; + else if (group->GetLooterGuid() == GetObjectGuid()) + { + if (group->GetLootMethod() == MASTER_LOOT) + permission = MASTER_PERMISSION; + else + permission = ALL_PERMISSION; + } + else + permission = GROUP_PERMISSION; + } + else + permission = NONE_PERMISSION; + } + else if (recipient == this) + permission = ALL_PERMISSION; + else + permission = NONE_PERMISSION; + } + break; + } + case HIGHGUID_ITEM: + { + Item* item = GetItemByGuid(guid); + + if (!item) + { + SendLootRelease(guid); + return; + } + + permission = OWNER_PERMISSION; + + loot = &item->loot; + + if (!item->HasGeneratedLoot()) + { + item->loot.clear(); + + switch (loot_type) + { + case LOOT_DISENCHANTING: + loot->FillLoot(item->GetProto()->DisenchantID, LootTemplates_Disenchant, this, true); + item->SetLootState(ITEM_LOOT_TEMPORARY); + break; + case LOOT_PROSPECTING: + loot->FillLoot(item->GetEntry(), LootTemplates_Prospecting, this, true); + item->SetLootState(ITEM_LOOT_TEMPORARY); + break; + case LOOT_MILLING: + loot->FillLoot(item->GetEntry(), LootTemplates_Milling, this, true); + item->SetLootState(ITEM_LOOT_TEMPORARY); + break; + default: + loot->FillLoot(item->GetEntry(), LootTemplates_Item, this, true, item->GetProto()->MaxMoneyLoot == 0); + loot->generateMoneyLoot(item->GetProto()->MinMoneyLoot, item->GetProto()->MaxMoneyLoot); + item->SetLootState(ITEM_LOOT_CHANGED); + break; + } + } + break; + } + case HIGHGUID_CORPSE: // remove insignia + { + Corpse* bones = GetMap()->GetCorpse(guid); + + if (!bones || !((loot_type == LOOT_CORPSE) || (loot_type == LOOT_INSIGNIA)) || (bones->GetType() != CORPSE_BONES)) + { + SendLootRelease(guid); + return; + } + + loot = &bones->loot; + + if (!bones->lootForBody) + { + bones->lootForBody = true; + uint32 pLevel = bones->loot.gold; + bones->loot.clear(); + if (GetBattleGround()->GetTypeID() == BATTLEGROUND_AV) + loot->FillLoot(0, LootTemplates_Creature, this, false); + // It may need a better formula + // Now it works like this: lvl10: ~6copper, lvl70: ~9silver + bones->loot.gold = (uint32)(urand(50, 150) * 0.016f * pow(((float)pLevel) / 5.76f, 2.5f) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)); + } + + if (bones->lootRecipient != this) + permission = NONE_PERMISSION; + else + permission = OWNER_PERMISSION; + break; + } + case HIGHGUID_UNIT: + case HIGHGUID_VEHICLE: + { + Creature* creature = GetMap()->GetCreature(guid); + + // must be in range and creature must be alive for pickpocket and must be dead for another loot + if (!creature || creature->isAlive() != (loot_type == LOOT_PICKPOCKETING) || !creature->IsWithinDistInMap(this, INTERACTION_DISTANCE)) + { + SendLootRelease(guid); + return; + } + + if (loot_type == LOOT_PICKPOCKETING && IsFriendlyTo(creature)) + { + SendLootRelease(guid); + return; + } + + loot = &creature->loot; + + if (loot_type == LOOT_PICKPOCKETING) + { + if (!creature->lootForPickPocketed) + { + creature->lootForPickPocketed = true; + loot->clear(); + + if (uint32 lootid = creature->GetCreatureInfo()->pickpocketLootId) + loot->FillLoot(lootid, LootTemplates_Pickpocketing, this, false); + + // Generate extra money for pick pocket loot + const uint32 a = urand(0, creature->getLevel() / 2); + const uint32 b = urand(0, getLevel() / 2); + loot->gold = uint32(10 * (a + b) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)); + permission = OWNER_PERMISSION; + } + } + else + { + // the player whose group may loot the corpse + Player* recipient = creature->GetLootRecipient(); + if (!recipient) + { + creature->SetLootRecipient(this); + recipient = this; + } + + if (creature->lootForPickPocketed) + { + creature->lootForPickPocketed = false; + loot->clear(); + } + + if (!creature->lootForBody) + { + creature->lootForBody = true; + loot->clear(); + + if (uint32 lootid = creature->GetCreatureInfo()->lootid) + loot->FillLoot(lootid, LootTemplates_Creature, recipient, false); + + loot->generateMoneyLoot(creature->GetCreatureInfo()->mingold, creature->GetCreatureInfo()->maxgold); + + if (Group* group = creature->GetGroupLootRecipient()) + { + group->UpdateLooterGuid(creature, true); + + switch (group->GetLootMethod()) + { + case GROUP_LOOT: + // GroupLoot delete items over threshold (threshold even not implemented), and roll them. Items with qualityGroupLoot(creature, loot); + break; + case NEED_BEFORE_GREED: + group->NeedBeforeGreed(creature, loot); + break; + case MASTER_LOOT: + group->MasterLoot(creature, loot); + break; + default: + break; + } + } + } + + // possible only if creature->lootForBody && loot->empty() at spell cast check + if (loot_type == LOOT_SKINNING) + { + if (!creature->lootForSkin) + { + creature->lootForSkin = true; + loot->clear(); + loot->FillLoot(creature->GetCreatureInfo()->SkinLootId, LootTemplates_Skinning, this, false); + + // let reopen skinning loot if will closed. + if (!loot->empty()) + creature->SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE); + + permission = OWNER_PERMISSION; + } + } + // set group rights only for loot_type != LOOT_SKINNING + else + { + if (Group* group = creature->GetGroupLootRecipient()) + { + if (group == GetGroup()) + { + if (group->GetLootMethod() == FREE_FOR_ALL) + permission = ALL_PERMISSION; + else if (group->GetLooterGuid() == GetObjectGuid()) + { + if (group->GetLootMethod() == MASTER_LOOT) + permission = MASTER_PERMISSION; + else + permission = ALL_PERMISSION; + } + else + permission = GROUP_PERMISSION; + } + else + permission = NONE_PERMISSION; + } + else if (recipient == this) + permission = OWNER_PERMISSION; + else + permission = NONE_PERMISSION; + } + } + break; + } + default: + { + sLog.outError("%s is unsupported for looting.", guid.GetString().c_str()); + return; + } + } + + SetLootGuid(guid); + + // LOOT_INSIGNIA and LOOT_FISHINGHOLE unsupported by client + switch (loot_type) + { + case LOOT_INSIGNIA: loot_type = LOOT_SKINNING; break; + case LOOT_FISHING_FAIL: loot_type = LOOT_FISHING; break; + case LOOT_FISHINGHOLE: loot_type = LOOT_FISHING; break; + default: break; + } + + // need know merged fishing/corpse loot type for achievements + loot->loot_type = loot_type; + + WorldPacket data(SMSG_LOOT_RESPONSE, (9 + 50)); // we guess size + data << ObjectGuid(guid); + data << uint8(loot_type); + data << LootView(*loot, this, permission); + SendDirectMessage(&data); + + // add 'this' player as one of the players that are looting 'loot' + if (permission != NONE_PERMISSION) + loot->AddLooter(GetObjectGuid()); + + if (loot_type == LOOT_CORPSE && !guid.IsItem()) + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_LOOTING); +} + +void Player::SendNotifyLootMoneyRemoved() +{ + WorldPacket data(SMSG_LOOT_CLEAR_MONEY, 0); + GetSession()->SendPacket(&data); +} + +void Player::SendNotifyLootItemRemoved(uint8 lootSlot, bool currency) +{ + WorldPacket data(currency ? SMSG_LOOT_CURRENCY_REMOVED : SMSG_LOOT_REMOVED, 1); + data << uint8(lootSlot); + GetSession()->SendPacket(&data); +} + +void Player::SendUpdateWorldState(uint32 Field, uint32 Value) +{ + WorldPacket data(SMSG_UPDATE_WORLD_STATE, 8 + 1); + data << Field; + data << Value; + data << uint8(0); + GetSession()->SendPacket(&data); +} + +static WorldStatePair AV_world_states[] = +{ + { 0x7ae, 0x1 }, // 1966 7 snowfall n + { 0x532, 0x1 }, // 1330 8 frostwolfhut hc + { 0x531, 0x0 }, // 1329 9 frostwolfhut ac + { 0x52e, 0x0 }, // 1326 10 stormpike firstaid a_a + { 0x571, 0x0 }, // 1393 11 east frostwolf tower horde assaulted -unused + { 0x570, 0x0 }, // 1392 12 west frostwolf tower horde assaulted - unused + { 0x567, 0x1 }, // 1383 13 frostwolfe c + { 0x566, 0x1 }, // 1382 14 frostwolfw c + { 0x550, 0x1 }, // 1360 15 irondeep (N) ally + { 0x544, 0x0 }, // 1348 16 ice grave a_a + { 0x536, 0x0 }, // 1334 17 stormpike grave h_c + { 0x535, 0x1 }, // 1333 18 stormpike grave a_c + { 0x518, 0x0 }, // 1304 19 stoneheart grave a_a + { 0x517, 0x0 }, // 1303 20 stoneheart grave h_a + { 0x574, 0x0 }, // 1396 21 unk + { 0x573, 0x0 }, // 1395 22 iceblood tower horde assaulted -unused + { 0x572, 0x0 }, // 1394 23 towerpoint horde assaulted - unused + { 0x56f, 0x0 }, // 1391 24 unk + { 0x56e, 0x0 }, // 1390 25 iceblood a + { 0x56d, 0x0 }, // 1389 26 towerp a + { 0x56c, 0x0 }, // 1388 27 frostwolfe a + { 0x56b, 0x0 }, // 1387 28 froswolfw a + { 0x56a, 0x1 }, // 1386 29 unk + { 0x569, 0x1 }, // 1385 30 iceblood c + { 0x568, 0x1 }, // 1384 31 towerp c + { 0x565, 0x0 }, // 1381 32 stoneh tower a + { 0x564, 0x0 }, // 1380 33 icewing tower a + { 0x563, 0x0 }, // 1379 34 dunn a + { 0x562, 0x0 }, // 1378 35 duns a + { 0x561, 0x0 }, // 1377 36 stoneheart bunker alliance assaulted - unused + { 0x560, 0x0 }, // 1376 37 icewing bunker alliance assaulted - unused + { 0x55f, 0x0 }, // 1375 38 dunbaldar south alliance assaulted - unused + { 0x55e, 0x0 }, // 1374 39 dunbaldar north alliance assaulted - unused + { 0x55d, 0x0 }, // 1373 40 stone tower d + { 0x3c6, 0x0 }, // 966 41 unk + { 0x3c4, 0x0 }, // 964 42 unk + { 0x3c2, 0x0 }, // 962 43 unk + { 0x516, 0x1 }, // 1302 44 stoneheart grave a_c + { 0x515, 0x0 }, // 1301 45 stonheart grave h_c + { 0x3b6, 0x0 }, // 950 46 unk + { 0x55c, 0x0 }, // 1372 47 icewing tower d + { 0x55b, 0x0 }, // 1371 48 dunn d + { 0x55a, 0x0 }, // 1370 49 duns d + { 0x559, 0x0 }, // 1369 50 unk + { 0x558, 0x0 }, // 1368 51 iceblood d + { 0x557, 0x0 }, // 1367 52 towerp d + { 0x556, 0x0 }, // 1366 53 frostwolfe d + { 0x555, 0x0 }, // 1365 54 frostwolfw d + { 0x554, 0x1 }, // 1364 55 stoneh tower c + { 0x553, 0x1 }, // 1363 56 icewing tower c + { 0x552, 0x1 }, // 1362 57 dunn c + { 0x551, 0x1 }, // 1361 58 duns c + { 0x54f, 0x0 }, // 1359 59 irondeep (N) horde + { 0x54e, 0x0 }, // 1358 60 irondeep (N) ally + { 0x54d, 0x1 }, // 1357 61 mine (S) neutral + { 0x54c, 0x0 }, // 1356 62 mine (S) horde + { 0x54b, 0x0 }, // 1355 63 mine (S) ally + { 0x545, 0x0 }, // 1349 64 iceblood h_a + { 0x543, 0x1 }, // 1347 65 iceblod h_c + { 0x542, 0x0 }, // 1346 66 iceblood a_c + { 0x540, 0x0 }, // 1344 67 snowfall h_a + { 0x53f, 0x0 }, // 1343 68 snowfall a_a + { 0x53e, 0x0 }, // 1342 69 snowfall h_c + { 0x53d, 0x0 }, // 1341 70 snowfall a_c + { 0x53c, 0x0 }, // 1340 71 frostwolf g h_a + { 0x53b, 0x0 }, // 1339 72 frostwolf g a_a + { 0x53a, 0x1 }, // 1338 73 frostwolf g h_c + { 0x539, 0x0 }, // l33t 74 frostwolf g a_c + { 0x538, 0x0 }, // 1336 75 stormpike grave h_a + { 0x537, 0x0 }, // 1335 76 stormpike grave a_a + { 0x534, 0x0 }, // 1332 77 frostwolf hut h_a + { 0x533, 0x0 }, // 1331 78 frostwolf hut a_a + { 0x530, 0x0 }, // 1328 79 stormpike first aid h_a + { 0x52f, 0x0 }, // 1327 80 stormpike first aid h_c + { 0x52d, 0x1 }, // 1325 81 stormpike first aid a_c + { 0x0, 0x0 } +}; + +static WorldStatePair WS_world_states[] = +{ + { 0x62d, 0x0 }, // 1581 7 alliance flag captures + { 0x62e, 0x0 }, // 1582 8 horde flag captures + { 0x609, 0x0 }, // 1545 9 unk, set to 1 on alliance flag pickup... + { 0x60a, 0x0 }, // 1546 10 unk, set to 1 on horde flag pickup, after drop it's -1 + { 0x60b, 0x2 }, // 1547 11 unk + { 0x641, 0x3 }, // 1601 12 unk (max flag captures?) + { 0x922, 0x1 }, // 2338 13 horde (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing) + { 0x923, 0x1 }, // 2339 14 alliance (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing) + { 0x1097, 0x1 }, // 4247 15 show time limit? + { 0x1098, 0x19 }, // 4248 16 time remaining in minutes + { 0x0, 0x0 } +}; + +static WorldStatePair AB_world_states[] = +{ + { 0x6e7, 0x0 }, // 1767 7 stables alliance + { 0x6e8, 0x0 }, // 1768 8 stables horde + { 0x6e9, 0x0 }, // 1769 9 unk, ST? + { 0x6ea, 0x0 }, // 1770 10 stables (show/hide) + { 0x6ec, 0x0 }, // 1772 11 farm (0 - horde controlled, 1 - alliance controlled) + { 0x6ed, 0x0 }, // 1773 12 farm (show/hide) + { 0x6ee, 0x0 }, // 1774 13 farm color + { 0x6ef, 0x0 }, // 1775 14 gold mine color, may be FM? + { 0x6f0, 0x0 }, // 1776 15 alliance resources + { 0x6f1, 0x0 }, // 1777 16 horde resources + { 0x6f2, 0x0 }, // 1778 17 horde bases + { 0x6f3, 0x0 }, // 1779 18 alliance bases + { 0x6f4, 0x7d0 }, // 1780 19 max resources (2000) + { 0x6f6, 0x0 }, // 1782 20 blacksmith color + { 0x6f7, 0x0 }, // 1783 21 blacksmith (show/hide) + { 0x6f8, 0x0 }, // 1784 22 unk, bs? + { 0x6f9, 0x0 }, // 1785 23 unk, bs? + { 0x6fb, 0x0 }, // 1787 24 gold mine (0 - horde contr, 1 - alliance contr) + { 0x6fc, 0x0 }, // 1788 25 gold mine (0 - conflict, 1 - horde) + { 0x6fd, 0x0 }, // 1789 26 gold mine (1 - show/0 - hide) + { 0x6fe, 0x0 }, // 1790 27 gold mine color + { 0x700, 0x0 }, // 1792 28 gold mine color, wtf?, may be LM? + { 0x701, 0x0 }, // 1793 29 lumber mill color (0 - conflict, 1 - horde contr) + { 0x702, 0x0 }, // 1794 30 lumber mill (show/hide) + { 0x703, 0x0 }, // 1795 31 lumber mill color color + { 0x732, 0x1 }, // 1842 32 stables (1 - uncontrolled) + { 0x733, 0x1 }, // 1843 33 gold mine (1 - uncontrolled) + { 0x734, 0x1 }, // 1844 34 lumber mill (1 - uncontrolled) + { 0x735, 0x1 }, // 1845 35 farm (1 - uncontrolled) + { 0x736, 0x1 }, // 1846 36 blacksmith (1 - uncontrolled) + { 0x745, 0x2 }, // 1861 37 unk + { 0x7a3, 0x708 }, // 1955 38 warning limit (1800) + { 0x0, 0x0 } +}; + +static WorldStatePair EY_world_states[] = +{ + { 2753, 0 }, // WORLD_STATE_EY_TOWER_COUNT_HORDE + { 2752, 0 }, // WORLD_STATE_EY_TOWER_COUNT_ALLIANCE + { 2733, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_DRAENEI_RUINS_HORDE + { 2732, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_DRAENEI_RUINS_ALLIANCE + { 2731, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_DRAENEI_RUINS_NEUTRAL + { 2730, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_MAGE_TOWER_ALLIANCE + { 2729, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_MAGE_TOWER_HORDE + { 2728, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_MAGE_TOWER_NEUTRAL + { 2727, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_FEL_REAVER_HORDE + { 2726, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_FEL_REAVER_ALLIANCE + { 2725, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_FEL_REAVER_NEUTRAL + { 2724, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_BLOOD_ELF_HORDE + { 2723, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_BLOOD_ELF_ALLIANCE + { 2722, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_BLOOD_ELF_NEUTRAL + { 2757, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_NETHERSTORM_FLAG_READY + { 2770, 1 }, // WORLD_STATE_EY_NETHERSTORM_FLAG_STATE_HORDE + { 2769, 1 }, // WORLD_STATE_EY_NETHERSTORM_FLAG_STATE_ALLIANCE + { 2750, 0 }, // WORLD_STATE_EY_RESOURCES_HORDE + { 2749, 0 }, // WORLD_STATE_EY_RESOURCES_ALLIANCE + { 2565, 0x8e }, // global unk -- TODO: move to global world state + { 3085, 0x17b } // global unk -- TODO: move to global world state +}; + +static WorldStatePair SI_world_states[] = // Silithus +{ + { 2313, 0 }, // WORLD_STATE_SI_GATHERED_A + { 2314, 0 }, // WORLD_STATE_SI_GATHERED_H + { 2317, 0 } // WORLD_STATE_SI_SILITHYST_MAX +}; + +static WorldStatePair EP_world_states[] = // Eastern Plaguelands +{ + { 2327, 0 }, // WORLD_STATE_EP_TOWER_COUNT_ALLIANCE + { 2328, 0 }, // WORLD_STATE_EP_TOWER_COUNT_HORDE + { 2355, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_NEUTRAL + { 2374, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_CONTEST_ALLIANCE + { 2375, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_CONTEST_HORDE + { 2376, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_PROGRESS_ALLIANCE + { 2377, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_PROGRESS_HORDE + { 2378, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_ALLIANCE + { 2379, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_HORDE + { 2354, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_ALLIANCE + { 2356, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_HORDE + { 2357, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_PROGRESS_ALLIANCE + { 2358, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_PROGRESS_HORDE + { 2359, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_CONTEST_ALLIANCE + { 2360, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_CONTEST_HORDE + { 2361, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_NEUTRAL + { 2352, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_NEUTRAL + { 2362, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_CONTEST_ALLIANCE + { 2363, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_CONTEST_HORDE + { 2364, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_PROGRESS_ALLIANCE + { 2365, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_PROGRESS_HORDE + { 2372, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_ALLIANCE + { 2373, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_HORDE + { 2353, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_PLAGUEWOOD_NEUTRAL + { 2366, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_PLAGUEWOOD_CONTEST_ALLIANCE + { 2367, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_PLAGUEWOOD_CONTEST_HORDE - not in dbc! sent for consistency's sake, and to match field count + { 2368, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_PLAGUEWOOD_PROGRESS_ALLIANCE + { 2369, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_PLAGUEWOOD_PROGRESS_HORDE + { 2370, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_PLAGUEWOOD_ALLIANCE + { 2371, WORLD_STATE_REMOVE } // WORLD_STATE_EP_PLAGUEWOOD_HORDE +}; + +static WorldStatePair HP_world_states[] = // Hellfire Peninsula +{ + { 2490, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_TOWER_DISPLAY_A + { 2489, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_TOWER_DISPLAY_H + { 2485, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_BROKEN_HILL_NEUTRAL + { 2484, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_BROKEN_HILL_HORDE + { 2483, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_BROKEN_HILL_ALLIANCE + { 2482, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_OVERLOOK_NEUTRAL + { 2481, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_OVERLOOK_HORDE + { 2480, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_OVERLOOK_ALLIANCE + { 2478, 0 }, // WORLD_STATE_HP_TOWER_COUNT_HORDE + { 2476, 0 }, // WORLD_STATE_HP_TOWER_COUNT_ALLIANCE + { 2472, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_STADIUM_NEUTRAL + { 2471, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_STADIUM_ALLIANCE + { 2470, WORLD_STATE_REMOVE } // WORLD_STATE_HP_STADIUM_HORDE +}; + +static WorldStatePair TF_world_states[] = // Terokkar Forest +{ + { 2622, 0 }, // WORLD_STATE_TF_TOWER_COUNT_H + { 2621, 0 }, // WORLD_STATE_TF_TOWER_COUNT_A + { 2620, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_TOWERS_CONTROLLED + { 2695, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_SOUTH_EAST_TOWER_HORDE + { 2694, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_SOUTH_EAST_TOWER_ALLIANCE + { 2693, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_SOUTH_TOWER_NEUTRAL + { 2692, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_SOUTH_TOWER_HORDE + { 2691, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_SOUTH_TOWER_ALLIANCE + { 2690, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_EAST_TOWER_NEUTRAL + { 2689, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_EAST_TOWER_HORDE + { 2688, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_EAST_TOWER_ALLIANCE + { 2686, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_NORTH_TOWER_NEUTRAL + { 2685, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_NORTH_TOWER_HORDE + { 2684, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_NORTH_TOWER_ALLIANCE + { 2683, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_WEST_TOWER_ALLIANCE + { 2682, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_WEST_TOWER_HORDE + { 2681, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_WEST_TOWER_NEUTRAL + { 2512, 0 }, // WORLD_STATE_TF_TIME_MIN_FIRST_DIGIT + { 2510, 0 }, // WORLD_STATE_TF_TIME_MIN_SECOND_DIGIT + { 2509, 0 }, // WORLD_STATE_TF_TIME_HOURS + { 2508, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_LOCKED_NEUTRAL + { 2696, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_SOUTH_EAST_TOWER_NEUTRAL + { 2768, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_LOCKED_HORDE + { 2767, WORLD_STATE_REMOVE } // WORLD_STATE_TF_LOCKED_ALLIANCE +}; + +static WorldStatePair ZM_world_states[] = // Zangarmarsh +{ + { 2653, 0x1 }, // WORLD_STATE_ZM_UNK + { 2652, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_EAST_NEUTRAL + { 2651, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_EAST_HORDE + { 2650, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_EAST_ALLIANCE + { 2649, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_GRAVEYARD_HORDE + { 2648, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_GRAVEYARD_ALLIANCE + { 2647, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_GRAVEYARD_NEUTRAL + { 2646, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_WEST_NEUTRAL + { 2645, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_WEST_HORDE + { 2644, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_WEST_ALLIANCE + { 2560, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_EAST_UI_NEUTRAL + { 2559, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_EAST_UI_HORDE + { 2558, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_EAST_UI_ALLIANCE + { 2557, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_WEST_UI_NEUTRAL + { 2556, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_WEST_UI_HORDE + { 2555, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_WEST_UI_ALLIANCE + { 2658, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_FLAG_READY_HORDE + { 2657, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_FLAG_NOT_READY_HORDE + { 2656, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_FLAG_NOT_READY_ALLIANCE + { 2655, WORLD_STATE_REMOVE } // WORLD_STATE_ZM_FLAG_READY_ALLIANCE +}; + +static WorldStatePair NA_world_states[] = +{ + { 2503, 0 }, // WORLD_STATE_NA_GUARDS_HORDE + { 2502, 0 }, // WORLD_STATE_NA_GUARDS_ALLIANCE + { 2493, 0 }, // WORLD_STATE_NA_GUARDS_MAX + { 2491, 0 }, // WORLD_STATE_NA_GUARDS_LEFT + { 2762, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_NORTH_NEUTRAL_H + { 2662, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_NORTH_NEUTRAL_A + { 2663, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_NORTH_H + { 2664, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_NORTH_A + { 2760, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_SOUTH_NEUTRAL_H + { 2670, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_SOUTH_NEUTRAL_A + { 2668, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_SOUTH_H + { 2669, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_SOUTH_A + { 2761, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_WEST_NEUTRAL_H + { 2667, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_WEST_NEUTRAL_A + { 2665, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_WEST_H + { 2666, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_WEST_A + { 2763, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_EAST_NEUTRAL_H + { 2659, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_EAST_NEUTRAL_A + { 2660, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_EAST_H + { 2661, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_EAST_A + { 2671, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_HALAA_NEUTRAL + { 2676, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_HALAA_NEUTRAL_A + { 2677, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_HALAA_NEUTRAL_H + { 2672, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_HALAA_HORDE + { 2673, WORLD_STATE_REMOVE } // WORLD_STATE_NA_HALAA_ALLIANCE +}; + +void Player::SendInitWorldStates(uint32 zoneid, uint32 areaid) +{ + // data depends on zoneid/mapid... + BattleGround* bg = GetBattleGround(); + uint32 mapid = GetMapId(); + + DEBUG_LOG("Sending SMSG_INIT_WORLD_STATES to Map:%u, Zone: %u", mapid, zoneid); + + uint32 count = 0; // count of world states in packet + + WorldPacket data(SMSG_INIT_WORLD_STATES, (4 + 4 + 4 + 2 + 8 * 8)); // guess + data << uint32(mapid); // mapid + data << uint32(zoneid); // zone id + data << uint32(areaid); // area id, new 2.1.0 + size_t count_pos = data.wpos(); + data << uint16(0); // count of uint64 blocks, placeholder + + // common fields + FillInitialWorldState(data, count, 0x8d8, 0x0); // 2264 1 + FillInitialWorldState(data, count, 0x8d7, 0x0); // 2263 2 + FillInitialWorldState(data, count, 0x8d6, 0x0); // 2262 3 + FillInitialWorldState(data, count, 0x8d5, 0x0); // 2261 4 + FillInitialWorldState(data, count, 0x8d4, 0x0); // 2260 5 + FillInitialWorldState(data, count, 0x8d3, 0x0); // 2259 6 + // 3191 7 Current arena season + FillInitialWorldState(data, count, 0xC77, sWorld.getConfig(CONFIG_UINT32_ARENA_SEASON_ID)); + // 3901 8 Previous arena season + FillInitialWorldState(data, count, 0xF3D, sWorld.getConfig(CONFIG_UINT32_ARENA_SEASON_PREVIOUS_ID)); + FillInitialWorldState(data, count, 0xED9, 1); // 3801 9 0 - Battle for Wintergrasp in progress, 1 - otherwise + // 4354 10 Time when next Battle for Wintergrasp starts + FillInitialWorldState(data, count, 0x1102, uint32(time(NULL) + 9000)); + + if (mapid == 530) // Outland + { + FillInitialWorldState(data, count, 0x9bf, 0x0); // 2495 + FillInitialWorldState(data, count, 0x9bd, 0xF); // 2493 + FillInitialWorldState(data, count, 0x9bb, 0xF); // 2491 + } + + switch (zoneid) + { + case 1: // Dun Morogh + case 11: // Wetlands + case 12: // Elwynn Forest + case 38: // Loch Modan + case 40: // Westfall + case 51: // Searing Gorge + case 1519: // Stormwind City + case 1537: // Ironforge + case 2257: // Deeprun Tram + case 3703: // Shattrath City + break; + case 139: // Eastern Plaguelands + if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(zoneid)) + outdoorPvP->FillInitialWorldStates(data, count); + else + FillInitialWorldState(data, count, EP_world_states); + break; + case 1377: // Silithus + if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(zoneid)) + outdoorPvP->FillInitialWorldStates(data, count); + else + FillInitialWorldState(data, count, SI_world_states); + break; + case 2597: // AV + if (bg && bg->GetTypeID() == BATTLEGROUND_AV) + bg->FillInitialWorldStates(data, count); + else + FillInitialWorldState(data, count, AV_world_states); + break; + case 3277: // WS + if (bg && bg->GetTypeID() == BATTLEGROUND_WS) + bg->FillInitialWorldStates(data, count); + else + FillInitialWorldState(data, count, WS_world_states); + break; + case 3358: // AB + if (bg && bg->GetTypeID() == BATTLEGROUND_AB) + bg->FillInitialWorldStates(data, count); + else + FillInitialWorldState(data, count, AB_world_states); + break; + case 3820: // EY + if (bg && bg->GetTypeID() == BATTLEGROUND_EY) + bg->FillInitialWorldStates(data, count); + else + FillInitialWorldState(data, count, EY_world_states); + break; + case 3483: // Hellfire Peninsula + if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(zoneid)) + outdoorPvP->FillInitialWorldStates(data, count); + else + FillInitialWorldState(data, count, HP_world_states); + break; + case 3518: // Nagrand + if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(zoneid)) + outdoorPvP->FillInitialWorldStates(data, count); + else + FillInitialWorldState(data, count, NA_world_states); + break; + case 3519: // Terokkar Forest + if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(zoneid)) + outdoorPvP->FillInitialWorldStates(data, count); + else + FillInitialWorldState(data, count, TF_world_states); + break; + case 3521: // Zangarmarsh + if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(zoneid)) + outdoorPvP->FillInitialWorldStates(data, count); + else + FillInitialWorldState(data, count, ZM_world_states); + break; + case 3698: // Nagrand Arena + if (bg && bg->GetTypeID() == BATTLEGROUND_NA) + bg->FillInitialWorldStates(data, count); + else + { + FillInitialWorldState(data, count, 0xa0f, 0x0); // 2575 7 + FillInitialWorldState(data, count, 0xa10, 0x0); // 2576 8 + FillInitialWorldState(data, count, 0xa11, 0x0); // 2577 9 show + } + break; + case 3702: // Blade's Edge Arena + if (bg && bg->GetTypeID() == BATTLEGROUND_BE) + bg->FillInitialWorldStates(data, count); + else + { + FillInitialWorldState(data, count, 0x9f0, 0x0); // 2544 7 gold + FillInitialWorldState(data, count, 0x9f1, 0x0); // 2545 8 green + FillInitialWorldState(data, count, 0x9f3, 0x0); // 2547 9 show + } + break; + case 3968: // Ruins of Lordaeron + if (bg && bg->GetTypeID() == BATTLEGROUND_RL) + bg->FillInitialWorldStates(data, count); + else + { + FillInitialWorldState(data, count, 0xbb8, 0x0); // 3000 7 gold + FillInitialWorldState(data, count, 0xbb9, 0x0); // 3001 8 green + FillInitialWorldState(data, count, 0xbba, 0x0); // 3002 9 show + } + break; + default: + FillInitialWorldState(data, count, 0x914, 0x0); // 2324 7 + FillInitialWorldState(data, count, 0x913, 0x0); // 2323 8 + FillInitialWorldState(data, count, 0x912, 0x0); // 2322 9 + FillInitialWorldState(data, count, 0x915, 0x0); // 2325 10 + break; + } + + FillBGWeekendWorldStates(data, count); + + data.put(count_pos, count); // set actual world state amount + + GetSession()->SendPacket(&data); +} + +void Player::FillBGWeekendWorldStates(WorldPacket& data, uint32& count) +{ + for (uint32 i = 1; i < sBattlemasterListStore.GetNumRows(); ++i) + { + BattlemasterListEntry const* bl = sBattlemasterListStore.LookupEntry(i); + if (bl && bl->HolidayWorldStateId) + { + if (BattleGroundMgr::IsBGWeekend(BattleGroundTypeId(bl->id))) + FillInitialWorldState(data, count, bl->HolidayWorldStateId, 1); + else + FillInitialWorldState(data, count, bl->HolidayWorldStateId, 0); + } + } +} + +uint32 Player::GetXPRestBonus(uint32 xp) +{ + uint32 rested_bonus = (uint32)GetRestBonus(); // xp for each rested bonus + + if (rested_bonus > xp) // max rested_bonus == xp or (r+x) = 200% xp + rested_bonus = xp; + + SetRestBonus(GetRestBonus() - rested_bonus); + + DETAIL_LOG("Player gain %u xp (+ %u Rested Bonus). Rested points=%f", xp + rested_bonus, rested_bonus, GetRestBonus()); + return rested_bonus; +} + +void Player::SetBindPoint(ObjectGuid guid) +{ + WorldPacket data(SMSG_BINDER_CONFIRM, 8); + data << ObjectGuid(guid); + GetSession()->SendPacket(&data); +} + +void Player::SendTalentWipeConfirm(ObjectGuid guid) +{ + WorldPacket data(MSG_TALENT_WIPE_CONFIRM, (8 + 4)); + data << ObjectGuid(guid); + data << uint32(resetTalentsCost()); + GetSession()->SendPacket(&data); +} + +void Player::SendPetSkillWipeConfirm() +{ + Pet* pet = GetPet(); + if (!pet) + return; + WorldPacket data(SMSG_PET_UNLEARN_CONFIRM, (8 + 4)); + data << ObjectGuid(pet->GetObjectGuid()); + data << uint32(pet->resetTalentsCost()); + GetSession()->SendPacket(&data); +} + +/*********************************************************/ +/*** STORAGE SYSTEM ***/ +/*********************************************************/ + +void Player::SetVirtualItemSlot(uint8 i, Item* item) +{ + MANGOS_ASSERT(i < 3); + if (i < 2 && item) + { + if (!item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) + return; + uint32 charges = item->GetEnchantmentCharges(TEMP_ENCHANTMENT_SLOT); + if (charges == 0) + return; + if (charges > 1) + item->SetEnchantmentCharges(TEMP_ENCHANTMENT_SLOT, charges - 1); + else if (charges <= 1) + { + ApplyEnchantment(item, TEMP_ENCHANTMENT_SLOT, false); + item->ClearEnchantment(TEMP_ENCHANTMENT_SLOT); + } + } +} + +void Player::SetSheath(SheathState sheathed) +{ + switch (sheathed) + { + case SHEATH_STATE_UNARMED: // no prepared weapon + SetVirtualItemSlot(0, NULL); + SetVirtualItemSlot(1, NULL); + SetVirtualItemSlot(2, NULL); + break; + case SHEATH_STATE_MELEE: // prepared melee weapon + { + SetVirtualItemSlot(0, GetWeaponForAttack(BASE_ATTACK, true, true)); + SetVirtualItemSlot(1, GetWeaponForAttack(OFF_ATTACK, true, true)); + SetVirtualItemSlot(2, NULL); + }; break; + case SHEATH_STATE_RANGED: // prepared ranged weapon + SetVirtualItemSlot(0, NULL); + SetVirtualItemSlot(1, NULL); + SetVirtualItemSlot(2, GetWeaponForAttack(RANGED_ATTACK, true, true)); + break; + default: + SetVirtualItemSlot(0, NULL); + SetVirtualItemSlot(1, NULL); + SetVirtualItemSlot(2, NULL); + break; + } + Unit::SetSheath(sheathed); // this must visualize Sheath changing for other players... +} + +bool Player::GetSlotsForInventoryType(uint8 invType, uint8* slots, uint32 subClass) const +{ + uint8 pClass = getClass(); + + slots[0] = NULL_SLOT; + slots[1] = NULL_SLOT; + slots[2] = NULL_SLOT; + slots[3] = NULL_SLOT; + switch (invType) + { + case INVTYPE_HEAD: + slots[0] = EQUIPMENT_SLOT_HEAD; + break; + case INVTYPE_NECK: + slots[0] = EQUIPMENT_SLOT_NECK; + break; + case INVTYPE_SHOULDERS: + slots[0] = EQUIPMENT_SLOT_SHOULDERS; + break; + case INVTYPE_BODY: + slots[0] = EQUIPMENT_SLOT_BODY; + break; + case INVTYPE_CHEST: + slots[0] = EQUIPMENT_SLOT_CHEST; + break; + case INVTYPE_ROBE: + slots[0] = EQUIPMENT_SLOT_CHEST; + break; + case INVTYPE_WAIST: + slots[0] = EQUIPMENT_SLOT_WAIST; + break; + case INVTYPE_LEGS: + slots[0] = EQUIPMENT_SLOT_LEGS; + break; + case INVTYPE_FEET: + slots[0] = EQUIPMENT_SLOT_FEET; + break; + case INVTYPE_WRISTS: + slots[0] = EQUIPMENT_SLOT_WRISTS; + break; + case INVTYPE_HANDS: + slots[0] = EQUIPMENT_SLOT_HANDS; + break; + case INVTYPE_FINGER: + slots[0] = EQUIPMENT_SLOT_FINGER1; + slots[1] = EQUIPMENT_SLOT_FINGER2; + break; + case INVTYPE_TRINKET: + slots[0] = EQUIPMENT_SLOT_TRINKET1; + slots[1] = EQUIPMENT_SLOT_TRINKET2; + break; + case INVTYPE_CLOAK: + slots[0] = EQUIPMENT_SLOT_BACK; + break; + case INVTYPE_WEAPON: + { + slots[0] = EQUIPMENT_SLOT_MAINHAND; + + // suggest offhand slot only if know dual wielding + // (this will be replace mainhand weapon at auto equip instead unwanted "you don't known dual wielding" ... + if (CanDualWield()) + slots[1] = EQUIPMENT_SLOT_OFFHAND; + break; + } + case INVTYPE_SHIELD: + slots[0] = EQUIPMENT_SLOT_OFFHAND; + break; + case INVTYPE_RANGED: + slots[0] = EQUIPMENT_SLOT_RANGED; + break; + case INVTYPE_2HWEAPON: + slots[0] = EQUIPMENT_SLOT_MAINHAND; + if (CanDualWield() && CanTitanGrip()) + slots[1] = EQUIPMENT_SLOT_OFFHAND; + break; + case INVTYPE_TABARD: + slots[0] = EQUIPMENT_SLOT_TABARD; + break; + case INVTYPE_WEAPONMAINHAND: + slots[0] = EQUIPMENT_SLOT_MAINHAND; + break; + case INVTYPE_WEAPONOFFHAND: + slots[0] = EQUIPMENT_SLOT_OFFHAND; + break; + case INVTYPE_HOLDABLE: + slots[0] = EQUIPMENT_SLOT_OFFHAND; + break; + case INVTYPE_THROWN: + slots[0] = EQUIPMENT_SLOT_RANGED; + break; + case INVTYPE_RANGEDRIGHT: + slots[0] = EQUIPMENT_SLOT_RANGED; + break; + case INVTYPE_BAG: + slots[0] = INVENTORY_SLOT_BAG_START + 0; + slots[1] = INVENTORY_SLOT_BAG_START + 1; + slots[2] = INVENTORY_SLOT_BAG_START + 2; + slots[3] = INVENTORY_SLOT_BAG_START + 3; + break; + case INVTYPE_RELIC: + { + if (subClass == ITEM_SUBCLASS_ARMOR_RELIC && + (pClass == CLASS_PALADIN || pClass == CLASS_DRUID || + pClass == CLASS_SHAMAN || pClass == CLASS_DEATH_KNIGHT)) + slots[0] = EQUIPMENT_SLOT_RANGED; + break; + } + default: + return false; + } + + return true; +} + +uint8 Player::FindEquipSlot(ItemPrototype const* proto, uint32 slot, bool swap) const +{ + uint8 slots[4]; + if (!GetSlotsForInventoryType(proto->InventoryType, slots, proto->SubClass)) + return NULL_SLOT; + + if (slot != NULL_SLOT) + { + if (swap || !GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + { + for (int i = 0; i < 4; ++i) + { + if (slots[i] == slot) + return slot; + } + } + } + else + { + // search free slot at first + for (int i = 0; i < 4; ++i) + { + if (slots[i] != NULL_SLOT && !GetItemByPos(INVENTORY_SLOT_BAG_0, slots[i])) + { + // in case 2hand equipped weapon (without titan grip) offhand slot empty but not free + if (slots[i] != EQUIPMENT_SLOT_OFFHAND || !IsTwoHandUsed()) + return slots[i]; + } + } + + // if not found free and can swap return first appropriate from used + for (int i = 0; i < 4; ++i) + { + if (slots[i] != NULL_SLOT && swap) + return slots[i]; + } + } + + // no free position + return NULL_SLOT; +} + +InventoryResult Player::CanUnequipItems(uint32 item, uint32 count) const +{ + Item* pItem; + uint32 tempcount = 0; + + InventoryResult res = EQUIP_ERR_OK; + + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem && pItem->GetEntry() == item) + { + InventoryResult ires = CanUnequipItem(INVENTORY_SLOT_BAG_0 << 8 | i, false); + if (ires == EQUIP_ERR_OK) + { + tempcount += pItem->GetCount(); + if (tempcount >= count) + return EQUIP_ERR_OK; + } + else + res = ires; + } + } + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem && pItem->GetEntry() == item) + { + tempcount += pItem->GetCount(); + if (tempcount >= count) + return EQUIP_ERR_OK; + } + } + Bag* pBag; + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pBag) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + pItem = GetItemByPos(i, j); + if (pItem && pItem->GetEntry() == item) + { + tempcount += pItem->GetCount(); + if (tempcount >= count) + return EQUIP_ERR_OK; + } + } + } + } + + // not found req. item count and have unequippable items + return res; +} + +uint32 Player::GetItemCount(uint32 item, bool inBankAlso, Item* skipItem) const +{ + uint32 count = 0; + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem && pItem != skipItem && pItem->GetEntry() == item) + count += pItem->GetCount(); + } + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pBag) + count += pBag->GetItemCount(item, skipItem); + } + + if (skipItem && skipItem->GetProto()->GemProperties) + { + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem && pItem != skipItem && pItem->GetProto()->Socket[0].Color) + count += pItem->GetGemCountWithID(item); + } + } + + if (inBankAlso) + { + for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i) + { + Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem && pItem != skipItem && pItem->GetEntry() == item) + count += pItem->GetCount(); + } + for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + { + Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pBag) + count += pBag->GetItemCount(item, skipItem); + } + + if (skipItem && skipItem->GetProto()->GemProperties) + { + for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i) + { + Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem && pItem != skipItem && pItem->GetProto()->Socket[0].Color) + count += pItem->GetGemCountWithID(item); + } + } + } + + return count; +} + +uint32 Player::GetItemCountWithLimitCategory(uint32 limitCategory, Item* skipItem) const +{ + uint32 count = 0; + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->GetProto()->ItemLimitCategory == limitCategory && pItem != skipItem) + count += pItem->GetCount(); + + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + count += pBag->GetItemCountWithLimitCategory(limitCategory, skipItem); + + for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->GetProto()->ItemLimitCategory == limitCategory && pItem != skipItem) + count += pItem->GetCount(); + + for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + count += pBag->GetItemCountWithLimitCategory(limitCategory, skipItem); + + return count; +} + +Item* Player::GetItemByEntry(uint32 item) const +{ + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->GetEntry() == item) + return pItem; + + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (Item* itemPtr = pBag->GetItemByEntry(item)) + return itemPtr; + + return NULL; +} + +Item* Player::GetItemByLimitedCategory(uint32 limitedCategory) const +{ + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->GetProto()->ItemLimitCategory == limitedCategory) + return pItem; + + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (Item* itemPtr = pBag->GetItemByLimitedCategory(limitedCategory)) + return itemPtr; + + return NULL; +} + +Item* Player::GetItemByGuid(ObjectGuid guid) const +{ + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->GetObjectGuid() == guid) + return pItem; + + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + if (Item* pItem = pBag->GetItemByPos(j)) + if (pItem->GetObjectGuid() == guid) + return pItem; + + for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->GetObjectGuid() == guid) + return pItem; + + for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + if (Item* pItem = pBag->GetItemByPos(j)) + if (pItem->GetObjectGuid() == guid) + return pItem; + + return NULL; +} + +Item* Player::GetItemByPos(uint16 pos) const +{ + uint8 bag = pos >> 8; + uint8 slot = pos & 255; + return GetItemByPos(bag, slot); +} + +Item* Player::GetItemByPos(uint8 bag, uint8 slot) const +{ + if (bag == INVENTORY_SLOT_BAG_0 && slot < BANK_SLOT_BAG_END) + return m_items[slot]; + else if ((bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END) + || (bag >= BANK_SLOT_BAG_START && bag < BANK_SLOT_BAG_END)) + { + Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + return pBag->GetItemByPos(slot); + } + return NULL; +} + +uint32 Player::GetItemDisplayIdInSlot(uint8 bag, uint8 slot) const +{ + const Item* pItem = GetItemByPos(bag, slot); + + if (!pItem) + return 0; + + return pItem->GetProto()->DisplayInfoID; +} + +Item* Player::GetWeaponForAttack(WeaponAttackType attackType, bool nonbroken, bool useable) const +{ + uint8 slot; + switch (attackType) + { + case BASE_ATTACK: slot = EQUIPMENT_SLOT_MAINHAND; break; + case OFF_ATTACK: slot = EQUIPMENT_SLOT_OFFHAND; break; + case RANGED_ATTACK: slot = EQUIPMENT_SLOT_RANGED; break; + default: return NULL; + } + + Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (!item || item->GetProto()->Class != ITEM_CLASS_WEAPON) + return NULL; + + if (useable && !CanUseEquippedWeapon(attackType)) + return NULL; + + if (nonbroken && item->IsBroken()) + return NULL; + + return item; +} + +Item* Player::GetShield(bool useable) const +{ + Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + if (!item || item->GetProto()->Class != ITEM_CLASS_ARMOR) + return NULL; + + if (!useable) + return item; + + if (item->IsBroken() || !CanUseEquippedWeapon(OFF_ATTACK)) + return NULL; + + return item; +} + +uint32 Player::GetAttackBySlot(uint8 slot) +{ + switch (slot) + { + case EQUIPMENT_SLOT_MAINHAND: return BASE_ATTACK; + case EQUIPMENT_SLOT_OFFHAND: return OFF_ATTACK; + case EQUIPMENT_SLOT_RANGED: return RANGED_ATTACK; + default: return MAX_ATTACK; + } +} + +bool Player::IsInventoryPos(uint8 bag, uint8 slot) +{ + if (bag == INVENTORY_SLOT_BAG_0 && slot == NULL_SLOT) + return true; + if (bag == INVENTORY_SLOT_BAG_0 && (slot >= INVENTORY_SLOT_ITEM_START && slot < INVENTORY_SLOT_ITEM_END)) + return true; + if (bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END) + return true; + return false; +} + +bool Player::IsEquipmentPos(uint8 bag, uint8 slot) +{ + if (bag == INVENTORY_SLOT_BAG_0 && (slot < EQUIPMENT_SLOT_END)) + return true; + if (bag == INVENTORY_SLOT_BAG_0 && (slot >= INVENTORY_SLOT_BAG_START && slot < INVENTORY_SLOT_BAG_END)) + return true; + return false; +} + +bool Player::IsBankPos(uint8 bag, uint8 slot) +{ + if (bag == INVENTORY_SLOT_BAG_0 && (slot >= BANK_SLOT_ITEM_START && slot < BANK_SLOT_ITEM_END)) + return true; + if (bag == INVENTORY_SLOT_BAG_0 && (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END)) + return true; + if (bag >= BANK_SLOT_BAG_START && bag < BANK_SLOT_BAG_END) + return true; + return false; +} + +bool Player::IsBagPos(uint16 pos) +{ + uint8 bag = pos >> 8; + uint8 slot = pos & 255; + if (bag == INVENTORY_SLOT_BAG_0 && (slot >= INVENTORY_SLOT_BAG_START && slot < INVENTORY_SLOT_BAG_END)) + return true; + if (bag == INVENTORY_SLOT_BAG_0 && (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END)) + return true; + return false; +} + +bool Player::IsValidPos(uint8 bag, uint8 slot, bool explicit_pos) const +{ + // post selected + if (bag == NULL_BAG && !explicit_pos) + return true; + + if (bag == INVENTORY_SLOT_BAG_0) + { + // any post selected + if (slot == NULL_SLOT && !explicit_pos) + return true; + + // equipment + if (slot < EQUIPMENT_SLOT_END) + return true; + + // bag equip slots + if (slot >= INVENTORY_SLOT_BAG_START && slot < INVENTORY_SLOT_BAG_END) + return true; + + // backpack slots + if (slot >= INVENTORY_SLOT_ITEM_START && slot < INVENTORY_SLOT_ITEM_END) + return true; + + // bank main slots + if (slot >= BANK_SLOT_ITEM_START && slot < BANK_SLOT_ITEM_END) + return true; + + // bank bag slots + if (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END) + return true; + + return false; + } + + // bag content slots + if (bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END) + { + Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (!pBag) + return false; + + // any post selected + if (slot == NULL_SLOT && !explicit_pos) + return true; + + return slot < pBag->GetBagSize(); + } + + // bank bag content slots + if (bag >= BANK_SLOT_BAG_START && bag < BANK_SLOT_BAG_END) + { + Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (!pBag) + return false; + + // any post selected + if (slot == NULL_SLOT && !explicit_pos) + return true; + + return slot < pBag->GetBagSize(); + } + + // where this? + return false; +} + +bool Player::HasItemCount(uint32 item, uint32 count, bool inBankAlso) const +{ + uint32 tempcount = 0; + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade()) + { + tempcount += pItem->GetCount(); + if (tempcount >= count) + return true; + } + } + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + Item* pItem = GetItemByPos(i, j); + if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade()) + { + tempcount += pItem->GetCount(); + if (tempcount >= count) + return true; + } + } + } + } + + if (inBankAlso) + { + for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i) + { + Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade()) + { + tempcount += pItem->GetCount(); + if (tempcount >= count) + return true; + } + } + for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + { + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + Item* pItem = GetItemByPos(i, j); + if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade()) + { + tempcount += pItem->GetCount(); + if (tempcount >= count) + return true; + } + } + } + } + } + + return false; +} + +bool Player::HasItemOrGemWithIdEquipped(uint32 item, uint32 count, uint8 except_slot) const +{ + uint32 tempcount = 0; + for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + { + if (i == int(except_slot)) + continue; + + Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem && pItem->GetEntry() == item) + { + tempcount += pItem->GetCount(); + if (tempcount >= count) + return true; + } + } + + ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(item); + if (pProto && pProto->GemProperties) + { + for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + { + if (i == int(except_slot)) + continue; + + Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem && pItem->GetProto()->Socket[0].Color) + { + tempcount += pItem->GetGemCountWithID(item); + if (tempcount >= count) + return true; + } + } + } + + return false; +} + +bool Player::HasItemOrGemWithLimitCategoryEquipped(uint32 limitCategory, uint32 count, uint8 except_slot) const +{ + uint32 tempcount = 0; + for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + { + if (i == int(except_slot)) + continue; + + Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (!pItem) + continue; + + ItemPrototype const* pProto = pItem->GetProto(); + if (!pProto) + continue; + + if (pProto->ItemLimitCategory == limitCategory) + { + tempcount += pItem->GetCount(); + if (tempcount >= count) + return true; + } + + if (pProto->Socket[0].Color) + { + tempcount += pItem->GetGemCountWithLimitCategory(limitCategory); + if (tempcount >= count) + return true; + } + } + + return false; +} + +InventoryResult Player::_CanTakeMoreSimilarItems(uint32 entry, uint32 count, Item* pItem, uint32* no_space_count) const +{ + ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(entry); + if (!pProto) + { + if (no_space_count) + *no_space_count = count; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + + // no maximum + if (pProto->MaxCount > 0) + { + uint32 curcount = GetItemCount(pProto->ItemId, true, pItem); + + if (curcount + count > uint32(pProto->MaxCount)) + { + if (no_space_count) + *no_space_count = count + curcount - pProto->MaxCount; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + } + + // check unique-equipped limit + if (pProto->ItemLimitCategory) + { + ItemLimitCategoryEntry const* limitEntry = sItemLimitCategoryStore.LookupEntry(pProto->ItemLimitCategory); + if (!limitEntry) + { + if (no_space_count) + *no_space_count = count; + return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; + } + + if (limitEntry->mode == ITEM_LIMIT_CATEGORY_MODE_HAVE) + { + uint32 curcount = GetItemCountWithLimitCategory(pProto->ItemLimitCategory, pItem); + + if (curcount + count > uint32(limitEntry->maxCount)) + { + if (no_space_count) + *no_space_count = count + curcount - limitEntry->maxCount; + return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED_IS; + } + } + } + + return EQUIP_ERR_OK; +} + +InventoryResult Player::_CanStoreItem_InSpecificSlot(uint8 bag, uint8 slot, ItemPosCountVec& dest, ItemPrototype const* pProto, uint32& count, bool swap, Item* pSrcItem) const +{ + Item* pItem2 = GetItemByPos(bag, slot); + + // ignore move item (this slot will be empty at move) + if (pItem2 == pSrcItem) + pItem2 = NULL; + + uint32 need_space; + + // empty specific slot - check item fit to slot + if (!pItem2 || swap) + { + if (bag == INVENTORY_SLOT_BAG_0) + { + // prevent cheating + if ((slot >= BUYBACK_SLOT_START && slot < BUYBACK_SLOT_END) || slot >= PLAYER_SLOT_END) + return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG; + } + else + { + Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (!pBag) + return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG; + + ItemPrototype const* pBagProto = pBag->GetProto(); + if (!pBagProto) + return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG; + + if (slot >= pBagProto->ContainerSlots) + return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG; + + if (!ItemCanGoIntoBag(pProto, pBagProto)) + return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG; + } + + // non empty stack with space + need_space = pProto->GetMaxStackSize(); + } + // non empty slot, check item type + else + { + // can be merged at least partly + InventoryResult res = pItem2->CanBeMergedPartlyWith(pProto); + if (res != EQUIP_ERR_OK) + return res; + + // free stack space or infinity + need_space = pProto->GetMaxStackSize() - pItem2->GetCount(); + } + + if (need_space > count) + need_space = count; + + ItemPosCount newPosition = ItemPosCount((bag << 8) | slot, need_space); + if (!newPosition.isContainedIn(dest)) + { + dest.push_back(newPosition); + count -= need_space; + } + return EQUIP_ERR_OK; +} + +InventoryResult Player::_CanStoreItem_InBag(uint8 bag, ItemPosCountVec& dest, ItemPrototype const* pProto, uint32& count, bool merge, bool non_specialized, Item* pSrcItem, uint8 skip_bag, uint8 skip_slot) const +{ + // skip specific bag already processed in first called _CanStoreItem_InBag + if (bag == skip_bag) + return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG; + + // skip nonexistent bag or self targeted bag + Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (!pBag || pBag == pSrcItem) + return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG; + + ItemPrototype const* pBagProto = pBag->GetProto(); + if (!pBagProto) + return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG; + + // specialized bag mode or non-specilized + if (non_specialized != (pBagProto->Class == ITEM_CLASS_CONTAINER && pBagProto->SubClass == ITEM_SUBCLASS_CONTAINER)) + return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG; + + if (!ItemCanGoIntoBag(pProto, pBagProto)) + return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG; + + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + // skip specific slot already processed in first called _CanStoreItem_InSpecificSlot + if (j == skip_slot) + continue; + + Item* pItem2 = GetItemByPos(bag, j); + + // ignore move item (this slot will be empty at move) + if (pItem2 == pSrcItem) + pItem2 = NULL; + + // if merge skip empty, if !merge skip non-empty + if ((pItem2 != NULL) != merge) + continue; + + uint32 need_space = pProto->GetMaxStackSize(); + + if (pItem2) + { + // can be merged at least partly + uint8 res = pItem2->CanBeMergedPartlyWith(pProto); + if (res != EQUIP_ERR_OK) + continue; + + // decrease at current stacksize + need_space -= pItem2->GetCount(); + } + + if (need_space > count) + need_space = count; + + ItemPosCount newPosition = ItemPosCount((bag << 8) | j, need_space); + if (!newPosition.isContainedIn(dest)) + { + dest.push_back(newPosition); + count -= need_space; + + if (count == 0) + return EQUIP_ERR_OK; + } + } + return EQUIP_ERR_OK; +} + +InventoryResult Player::_CanStoreItem_InInventorySlots(uint8 slot_begin, uint8 slot_end, ItemPosCountVec& dest, ItemPrototype const* pProto, uint32& count, bool merge, Item* pSrcItem, uint8 skip_bag, uint8 skip_slot) const +{ + for (uint32 j = slot_begin; j < slot_end; ++j) + { + // skip specific slot already processed in first called _CanStoreItem_InSpecificSlot + if (INVENTORY_SLOT_BAG_0 == skip_bag && j == skip_slot) + continue; + + Item* pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, j); + + // ignore move item (this slot will be empty at move) + if (pItem2 == pSrcItem) + pItem2 = NULL; + + // if merge skip empty, if !merge skip non-empty + if ((pItem2 != NULL) != merge) + continue; + + uint32 need_space = pProto->GetMaxStackSize(); + + if (pItem2) + { + // can be merged at least partly + uint8 res = pItem2->CanBeMergedPartlyWith(pProto); + if (res != EQUIP_ERR_OK) + continue; + + // descrease at current stacksize + need_space -= pItem2->GetCount(); + } + + if (need_space > count) + need_space = count; + + ItemPosCount newPosition = ItemPosCount((INVENTORY_SLOT_BAG_0 << 8) | j, need_space); + if (!newPosition.isContainedIn(dest)) + { + dest.push_back(newPosition); + count -= need_space; + + if (count == 0) + return EQUIP_ERR_OK; + } + } + return EQUIP_ERR_OK; +} + +InventoryResult Player::_CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, uint32 entry, uint32 count, Item* pItem, bool swap, uint32* no_space_count) const +{ + DEBUG_LOG("STORAGE: CanStoreItem bag = %u, slot = %u, item = %u, count = %u", bag, slot, entry, count); + + ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(entry); + if (!pProto) + { + if (no_space_count) + *no_space_count = count; + return swap ? EQUIP_ERR_ITEMS_CANT_BE_SWAPPED : EQUIP_ERR_ITEM_NOT_FOUND; + } + + if (pItem) + { + // item used + if (pItem->HasTemporaryLoot()) + { + if (no_space_count) + *no_space_count = count; + return EQUIP_ERR_ALREADY_LOOTED; + } + + if (pItem->IsBindedNotWith(this)) + { + if (no_space_count) + *no_space_count = count; + return EQUIP_ERR_DONT_OWN_THAT_ITEM; + } + } + + // check count of items (skip for auto move for same player from bank) + uint32 no_similar_count = 0; // can't store this amount similar items + InventoryResult res = _CanTakeMoreSimilarItems(entry, count, pItem, &no_similar_count); + if (res != EQUIP_ERR_OK) + { + if (count == no_similar_count) + { + if (no_space_count) + *no_space_count = no_similar_count; + return res; + } + count -= no_similar_count; + } + + // in specific slot + if (bag != NULL_BAG && slot != NULL_SLOT) + { + res = _CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, count, swap, pItem); + if (res != EQUIP_ERR_OK) + { + if (no_space_count) + *no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return EQUIP_ERR_OK; + + if (no_space_count) + *no_space_count = count + no_similar_count; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + } + + // not specific slot or have space for partly store only in specific slot + + // in specific bag + if (bag != NULL_BAG) + { + // search stack in bag for merge to + if (pProto->Stackable != 1) + { + if (bag == INVENTORY_SLOT_BAG_0) // inventory + { + res = _CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, INVENTORY_SLOT_ITEM_END, dest, pProto, count, true, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + { + if (no_space_count) + *no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return EQUIP_ERR_OK; + + if (no_space_count) + *no_space_count = count + no_similar_count; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + } + else // equipped bag + { + // we need check 2 time (specialized/non_specialized), use NULL_BAG to prevent skipping bag + res = _CanStoreItem_InBag(bag, dest, pProto, count, true, false, pItem, NULL_BAG, slot); + if (res != EQUIP_ERR_OK) + res = _CanStoreItem_InBag(bag, dest, pProto, count, true, true, pItem, NULL_BAG, slot); + + if (res != EQUIP_ERR_OK) + { + if (no_space_count) + *no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return EQUIP_ERR_OK; + + if (no_space_count) + *no_space_count = count + no_similar_count; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + } + } + + // search free slot in bag for place to + if (bag == INVENTORY_SLOT_BAG_0) // inventory + { + res = _CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, INVENTORY_SLOT_ITEM_END, dest, pProto, count, false, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + { + if (no_space_count) + *no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return EQUIP_ERR_OK; + + if (no_space_count) + *no_space_count = count + no_similar_count; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + } + else // equipped bag + { + res = _CanStoreItem_InBag(bag, dest, pProto, count, false, false, pItem, NULL_BAG, slot); + if (res != EQUIP_ERR_OK) + res = _CanStoreItem_InBag(bag, dest, pProto, count, false, true, pItem, NULL_BAG, slot); + + if (res != EQUIP_ERR_OK) + { + if (no_space_count) + *no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return EQUIP_ERR_OK; + + if (no_space_count) + *no_space_count = count + no_similar_count; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + } + } + + // not specific bag or have space for partly store only in specific bag + + // search stack for merge to + if (pProto->Stackable != 1) + { + res = _CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, INVENTORY_SLOT_ITEM_END, dest, pProto, count, true, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + { + if (no_space_count) + *no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return EQUIP_ERR_OK; + + if (no_space_count) + *no_space_count = count + no_similar_count; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + + if (pProto->BagFamily) + { + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + res = _CanStoreItem_InBag(i, dest, pProto, count, true, false, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + continue; + + if (count == 0) + { + if (no_similar_count == 0) + return EQUIP_ERR_OK; + + if (no_space_count) + *no_space_count = count + no_similar_count; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + } + } + + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + res = _CanStoreItem_InBag(i, dest, pProto, count, true, true, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + continue; + + if (count == 0) + { + if (no_similar_count == 0) + return EQUIP_ERR_OK; + + if (no_space_count) + *no_space_count = count + no_similar_count; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + } + } + + // search free slot - special bag case + if (pProto->BagFamily) + { + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + res = _CanStoreItem_InBag(i, dest, pProto, count, false, false, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + continue; + + if (count == 0) + { + if (no_similar_count == 0) + return EQUIP_ERR_OK; + + if (no_space_count) + *no_space_count = count + no_similar_count; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + } + } + + // Normally it would be impossible to autostore not empty bags + if (pItem && pItem->IsBag() && !((Bag*)pItem)->IsEmpty()) + return EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG; + + // search free slot + res = _CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, INVENTORY_SLOT_ITEM_END, dest, pProto, count, false, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + { + if (no_space_count) + *no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return EQUIP_ERR_OK; + + if (no_space_count) + *no_space_count = count + no_similar_count; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + res = _CanStoreItem_InBag(i, dest, pProto, count, false, true, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + continue; + + if (count == 0) + { + if (no_similar_count == 0) + return EQUIP_ERR_OK; + + if (no_space_count) + *no_space_count = count + no_similar_count; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + } + + if (no_space_count) + *no_space_count = count + no_similar_count; + + return EQUIP_ERR_INVENTORY_FULL; +} + +////////////////////////////////////////////////////////////////////////// +InventoryResult Player::CanStoreItems(Item** pItems, int count) const +{ + Item* pItem2; + + // fill space table + int inv_slot_items[INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START]; + int inv_bags[INVENTORY_SLOT_BAG_END - INVENTORY_SLOT_BAG_START][MAX_BAG_SIZE]; + + memset(inv_slot_items, 0, sizeof(int) * (INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START)); + memset(inv_bags, 0, sizeof(int) * (INVENTORY_SLOT_BAG_END - INVENTORY_SLOT_BAG_START)*MAX_BAG_SIZE); + + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + + if (pItem2 && !pItem2->IsInTrade()) + { + inv_slot_items[i - INVENTORY_SLOT_ITEM_START] = pItem2->GetCount(); + } + } + + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + pItem2 = GetItemByPos(i, j); + if (pItem2 && !pItem2->IsInTrade()) + { + inv_bags[i - INVENTORY_SLOT_BAG_START][j] = pItem2->GetCount(); + } + } + } + } + + // check free space for all items + for (int k = 0; k < count; ++k) + { + Item* pItem = pItems[k]; + + // no item + if (!pItem) continue; + + DEBUG_LOG("STORAGE: CanStoreItems %i. item = %u, count = %u", k + 1, pItem->GetEntry(), pItem->GetCount()); + ItemPrototype const* pProto = pItem->GetProto(); + + // strange item + if (!pProto) + return EQUIP_ERR_ITEM_NOT_FOUND; + + // item used + if (pItem->HasTemporaryLoot()) + return EQUIP_ERR_ALREADY_LOOTED; + + // item it 'bind' + if (pItem->IsBindedNotWith(this)) + return EQUIP_ERR_DONT_OWN_THAT_ITEM; + + Bag* pBag; + ItemPrototype const* pBagProto; + + // item is 'one item only' + InventoryResult res = CanTakeMoreSimilarItems(pItem); + if (res != EQUIP_ERR_OK) + return res; + + // search stack for merge to + if (pProto->Stackable != 1) + { + bool b_found = false; + + for (int t = INVENTORY_SLOT_ITEM_START; t < INVENTORY_SLOT_ITEM_END; ++t) + { + pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, t); + if (pItem2 && pItem2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && inv_slot_items[t - INVENTORY_SLOT_ITEM_START] + pItem->GetCount() <= pProto->GetMaxStackSize()) + { + inv_slot_items[t - INVENTORY_SLOT_ITEM_START] += pItem->GetCount(); + b_found = true; + break; + } + } + if (b_found) continue; + + for (int t = INVENTORY_SLOT_BAG_START; !b_found && t < INVENTORY_SLOT_BAG_END; ++t) + { + pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, t); + if (pBag) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + pItem2 = GetItemByPos(t, j); + if (pItem2 && pItem2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && inv_bags[t - INVENTORY_SLOT_BAG_START][j] + pItem->GetCount() <= pProto->GetMaxStackSize()) + { + inv_bags[t - INVENTORY_SLOT_BAG_START][j] += pItem->GetCount(); + b_found = true; + break; + } + } + } + } + if (b_found) continue; + } + + // special bag case + if (pProto->BagFamily) + { + bool b_found = false; + + for (int t = INVENTORY_SLOT_BAG_START; !b_found && t < INVENTORY_SLOT_BAG_END; ++t) + { + pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, t); + if (pBag) + { + pBagProto = pBag->GetProto(); + + // not plain container check + if (pBagProto && (pBagProto->Class != ITEM_CLASS_CONTAINER || pBagProto->SubClass != ITEM_SUBCLASS_CONTAINER) && + ItemCanGoIntoBag(pProto, pBagProto)) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + if (inv_bags[t - INVENTORY_SLOT_BAG_START][j] == 0) + { + inv_bags[t - INVENTORY_SLOT_BAG_START][j] = 1; + b_found = true; + break; + } + } + } + } + } + if (b_found) continue; + } + + // search free slot + bool b_found = false; + for (int t = INVENTORY_SLOT_ITEM_START; t < INVENTORY_SLOT_ITEM_END; ++t) + { + if (inv_slot_items[t - INVENTORY_SLOT_ITEM_START] == 0) + { + inv_slot_items[t - INVENTORY_SLOT_ITEM_START] = 1; + b_found = true; + break; + } + } + if (b_found) continue; + + // search free slot in bags + for (int t = INVENTORY_SLOT_BAG_START; !b_found && t < INVENTORY_SLOT_BAG_END; ++t) + { + pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, t); + if (pBag) + { + pBagProto = pBag->GetProto(); + + // special bag already checked + if (pBagProto && (pBagProto->Class != ITEM_CLASS_CONTAINER || pBagProto->SubClass != ITEM_SUBCLASS_CONTAINER)) + continue; + + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + if (inv_bags[t - INVENTORY_SLOT_BAG_START][j] == 0) + { + inv_bags[t - INVENTORY_SLOT_BAG_START][j] = 1; + b_found = true; + break; + } + } + } + } + + // no free slot found? + if (!b_found) + return EQUIP_ERR_INVENTORY_FULL; + } + + return EQUIP_ERR_OK; +} + +////////////////////////////////////////////////////////////////////////// +InventoryResult Player::CanEquipNewItem(uint8 slot, uint16& dest, uint32 item, bool swap) const +{ + dest = 0; + Item* pItem = Item::CreateItem(item, 1, this); + if (pItem) + { + InventoryResult result = CanEquipItem(slot, dest, pItem, swap); + delete pItem; + return result; + } + + return EQUIP_ERR_ITEM_NOT_FOUND; +} + +InventoryResult Player::CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool direct_action) const +{ + dest = 0; + if (pItem) + { + DEBUG_LOG("STORAGE: CanEquipItem slot = %u, item = %u, count = %u", slot, pItem->GetEntry(), pItem->GetCount()); + ItemPrototype const* pProto = pItem->GetProto(); + if (pProto) + { + // item used + if (pItem->HasTemporaryLoot()) + return EQUIP_ERR_ALREADY_LOOTED; + + if (pItem->IsBindedNotWith(this)) + return EQUIP_ERR_DONT_OWN_THAT_ITEM; + + // check count of items (skip for auto move for same player from bank) + InventoryResult res = CanTakeMoreSimilarItems(pItem); + if (res != EQUIP_ERR_OK) + return res; + + // check this only in game + if (direct_action) + { + // May be here should be more stronger checks; STUNNED checked + // ROOT, CONFUSED, DISTRACTED, FLEEING this needs to be checked. + if (hasUnitState(UNIT_STAT_STUNNED)) + return EQUIP_ERR_YOU_ARE_STUNNED; + + // do not allow equipping gear except weapons, offhands, projectiles, relics in + // - combat + // - in-progress arenas + if (!pProto->CanChangeEquipStateInCombat()) + { + if (isInCombat()) + return EQUIP_ERR_NOT_IN_COMBAT; + + if (BattleGround* bg = GetBattleGround()) + if (bg->isArena() && bg->GetStatus() == STATUS_IN_PROGRESS) + return EQUIP_ERR_NOT_DURING_ARENA_MATCH; + } + + // prevent equip item in process logout + if (GetSession()->isLogingOut()) + return EQUIP_ERR_YOU_ARE_STUNNED; + + if (isInCombat() && pProto->Class == ITEM_CLASS_WEAPON && m_weaponChangeTimer != 0) + return EQUIP_ERR_CANT_DO_RIGHT_NOW; // maybe exist better err + + if (IsNonMeleeSpellCasted(false)) + return EQUIP_ERR_CANT_DO_RIGHT_NOW; + } + + ScalingStatDistributionEntry const* ssd = pProto->ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(pProto->ScalingStatDistribution) : 0; + // check allowed level (extend range to upper values if MaxLevel more or equal max player level, this let GM set high level with 1...max range items) + if (ssd && ssd->MaxLevel < DEFAULT_MAX_LEVEL && ssd->MaxLevel < getLevel()) + return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; + + uint8 eslot = FindEquipSlot(pProto, slot, swap); + if (eslot == NULL_SLOT) + return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; + + InventoryResult msg = CanUseItem(pItem , direct_action); + if (msg != EQUIP_ERR_OK) + return msg; + if (!swap && GetItemByPos(INVENTORY_SLOT_BAG_0, eslot)) + return EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE; + + // if swap ignore item (equipped also) + if (InventoryResult res2 = CanEquipUniqueItem(pItem, swap ? eslot : uint8(NULL_SLOT))) + return res2; + + // check unique-equipped special item classes + if (pProto->Class == ITEM_CLASS_QUIVER) + { + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (Item* pBag = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (pBag != pItem) + { + if (ItemPrototype const* pBagProto = pBag->GetProto()) + { + if (pBagProto->Class == pProto->Class && (!swap || pBag->GetSlot() != eslot)) + return (pBagProto->SubClass == ITEM_SUBCLASS_AMMO_POUCH) + ? EQUIP_ERR_CAN_EQUIP_ONLY1_AMMOPOUCH + : EQUIP_ERR_CAN_EQUIP_ONLY1_QUIVER; + } + } + } + } + } + + uint32 type = pProto->InventoryType; + + if (eslot == EQUIPMENT_SLOT_OFFHAND) + { + if (type == INVTYPE_WEAPON || type == INVTYPE_WEAPONOFFHAND) + { + if (!CanDualWield()) + return EQUIP_ERR_CANT_DUAL_WIELD; + } + else if (type == INVTYPE_2HWEAPON) + { + if (!CanDualWield() || !CanTitanGrip()) + return EQUIP_ERR_CANT_DUAL_WIELD; + } + + if (IsTwoHandUsed()) + return EQUIP_ERR_CANT_EQUIP_WITH_TWOHANDED; + } + + // equip two-hand weapon case (with possible unequip 2 items) + if (type == INVTYPE_2HWEAPON) + { + if (eslot == EQUIPMENT_SLOT_OFFHAND) + { + if (!CanTitanGrip()) + return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; + } + else if (eslot != EQUIPMENT_SLOT_MAINHAND) + return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; + + if (!CanTitanGrip()) + { + // offhand item must can be stored in inventory for offhand item and it also must be unequipped + Item* offItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + ItemPosCountVec off_dest; + if (offItem && (!direct_action || + CanUnequipItem(uint16(INVENTORY_SLOT_BAG_0) << 8 | EQUIPMENT_SLOT_OFFHAND, false) != EQUIP_ERR_OK || + CanStoreItem(NULL_BAG, NULL_SLOT, off_dest, offItem, false) != EQUIP_ERR_OK)) + return swap ? EQUIP_ERR_ITEMS_CANT_BE_SWAPPED : EQUIP_ERR_INVENTORY_FULL; + } + } + dest = ((INVENTORY_SLOT_BAG_0 << 8) | eslot); + return EQUIP_ERR_OK; + } + } + + return !swap ? EQUIP_ERR_ITEM_NOT_FOUND : EQUIP_ERR_ITEMS_CANT_BE_SWAPPED; +} + +InventoryResult Player::CanUnequipItem(uint16 pos, bool swap) const +{ + // Applied only to equipped items and bank bags + if (!IsEquipmentPos(pos) && !IsBagPos(pos)) + return EQUIP_ERR_OK; + + Item* pItem = GetItemByPos(pos); + + // Applied only to existing equipped item + if (!pItem) + return EQUIP_ERR_OK; + + DEBUG_LOG("STORAGE: CanUnequipItem slot = %u, item = %u, count = %u", pos, pItem->GetEntry(), pItem->GetCount()); + + ItemPrototype const* pProto = pItem->GetProto(); + if (!pProto) + return EQUIP_ERR_ITEM_NOT_FOUND; + + // item used + if (pItem->HasTemporaryLoot()) + return EQUIP_ERR_ALREADY_LOOTED; + + // do not allow unequipping gear except weapons, offhands, projectiles, relics in + // - combat + // - in-progress arenas + if (!pProto->CanChangeEquipStateInCombat()) + { + if (isInCombat()) + return EQUIP_ERR_NOT_IN_COMBAT; + + if (BattleGround* bg = GetBattleGround()) + if (bg->isArena() && bg->GetStatus() == STATUS_IN_PROGRESS) + return EQUIP_ERR_NOT_DURING_ARENA_MATCH; + } + + // prevent unequip item in process logout + if (GetSession()->isLogingOut()) + return EQUIP_ERR_YOU_ARE_STUNNED; + + if (!swap && pItem->IsBag() && !((Bag*)pItem)->IsEmpty()) + return EQUIP_ERR_CAN_ONLY_DO_WITH_EMPTY_BAGS; + + return EQUIP_ERR_OK; +} + +InventoryResult Player::CanBankItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap, bool not_loading) const +{ + if (!pItem) + return swap ? EQUIP_ERR_ITEMS_CANT_BE_SWAPPED : EQUIP_ERR_ITEM_NOT_FOUND; + + uint32 count = pItem->GetCount(); + + DEBUG_LOG("STORAGE: CanBankItem bag = %u, slot = %u, item = %u, count = %u", bag, slot, pItem->GetEntry(), pItem->GetCount()); + ItemPrototype const* pProto = pItem->GetProto(); + if (!pProto) + return swap ? EQUIP_ERR_ITEMS_CANT_BE_SWAPPED : EQUIP_ERR_ITEM_NOT_FOUND; + + // item used + if (pItem->HasTemporaryLoot()) + return EQUIP_ERR_ALREADY_LOOTED; + + if (pItem->IsBindedNotWith(this)) + return EQUIP_ERR_DONT_OWN_THAT_ITEM; + + // check count of items (skip for auto move for same player from bank) + InventoryResult res = CanTakeMoreSimilarItems(pItem); + if (res != EQUIP_ERR_OK) + return res; + + // in specific slot + if (bag != NULL_BAG && slot != NULL_SLOT) + { + if (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END) + { + if (!pItem->IsBag()) + return EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT; + + if (slot - BANK_SLOT_BAG_START >= GetBankBagSlotCount()) + return EQUIP_ERR_MUST_PURCHASE_THAT_BAG_SLOT; + + res = CanUseItem(pItem, not_loading); + if (res != EQUIP_ERR_OK) + return res; + } + + res = _CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, count, swap, pItem); + if (res != EQUIP_ERR_OK) + return res; + + if (count == 0) + return EQUIP_ERR_OK; + } + + // not specific slot or have space for partly store only in specific slot + + // in specific bag + if (bag != NULL_BAG) + { + if (pProto->InventoryType == INVTYPE_BAG) + { + Bag* pBag = (Bag*)pItem; + if (pBag && !pBag->IsEmpty()) + return EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG; + } + + // search stack in bag for merge to + if (pProto->Stackable != 1) + { + if (bag == INVENTORY_SLOT_BAG_0) + { + res = _CanStoreItem_InInventorySlots(BANK_SLOT_ITEM_START, BANK_SLOT_ITEM_END, dest, pProto, count, true, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + return res; + + if (count == 0) + return EQUIP_ERR_OK; + } + else + { + res = _CanStoreItem_InBag(bag, dest, pProto, count, true, false, pItem, NULL_BAG, slot); + if (res != EQUIP_ERR_OK) + res = _CanStoreItem_InBag(bag, dest, pProto, count, true, true, pItem, NULL_BAG, slot); + + if (res != EQUIP_ERR_OK) + return res; + + if (count == 0) + return EQUIP_ERR_OK; + } + } + + // search free slot in bag + if (bag == INVENTORY_SLOT_BAG_0) + { + res = _CanStoreItem_InInventorySlots(BANK_SLOT_ITEM_START, BANK_SLOT_ITEM_END, dest, pProto, count, false, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + return res; + + if (count == 0) + return EQUIP_ERR_OK; + } + else + { + res = _CanStoreItem_InBag(bag, dest, pProto, count, false, false, pItem, NULL_BAG, slot); + if (res != EQUIP_ERR_OK) + res = _CanStoreItem_InBag(bag, dest, pProto, count, false, true, pItem, NULL_BAG, slot); + + if (res != EQUIP_ERR_OK) + return res; + + if (count == 0) + return EQUIP_ERR_OK; + } + } + + // not specific bag or have space for partly store only in specific bag + + // search stack for merge to + if (pProto->Stackable != 1) + { + // in slots + res = _CanStoreItem_InInventorySlots(BANK_SLOT_ITEM_START, BANK_SLOT_ITEM_END, dest, pProto, count, true, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + return res; + + if (count == 0) + return EQUIP_ERR_OK; + + // in special bags + if (pProto->BagFamily) + { + for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + { + res = _CanStoreItem_InBag(i, dest, pProto, count, true, false, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + continue; + + if (count == 0) + return EQUIP_ERR_OK; + } + } + + for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + { + res = _CanStoreItem_InBag(i, dest, pProto, count, true, true, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + continue; + + if (count == 0) + return EQUIP_ERR_OK; + } + } + + // search free place in special bag + if (pProto->BagFamily) + { + for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + { + res = _CanStoreItem_InBag(i, dest, pProto, count, false, false, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + continue; + + if (count == 0) + return EQUIP_ERR_OK; + } + } + + // search free space + res = _CanStoreItem_InInventorySlots(BANK_SLOT_ITEM_START, BANK_SLOT_ITEM_END, dest, pProto, count, false, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + return res; + + if (count == 0) + return EQUIP_ERR_OK; + + for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + { + res = _CanStoreItem_InBag(i, dest, pProto, count, false, true, pItem, bag, slot); + if (res != EQUIP_ERR_OK) + continue; + + if (count == 0) + return EQUIP_ERR_OK; + } + return EQUIP_ERR_BANK_FULL; +} + +InventoryResult Player::CanUseItem(Item* pItem, bool direct_action) const +{ + if (pItem) + { + DEBUG_LOG("STORAGE: CanUseItem item = %u", pItem->GetEntry()); + + if (!isAlive() && direct_action) + return EQUIP_ERR_YOU_ARE_DEAD; + + // if (isStunned()) + // return EQUIP_ERR_YOU_ARE_STUNNED; + + ItemPrototype const* pProto = pItem->GetProto(); + if (pProto) + { + if (pItem->IsBindedNotWith(this)) + return EQUIP_ERR_DONT_OWN_THAT_ITEM; + + InventoryResult msg = CanUseItem(pProto); + if (msg != EQUIP_ERR_OK) + return msg; + + if (uint32 item_use_skill = pItem->GetSkill()) + { + if (GetSkillValue(item_use_skill) == 0) + { + // armor items with scaling stats can downgrade armor skill reqs if related class can learn armor use at some level + if (pProto->Class != ITEM_CLASS_ARMOR) + return EQUIP_ERR_NO_REQUIRED_PROFICIENCY; + + ScalingStatDistributionEntry const* ssd = pProto->ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(pProto->ScalingStatDistribution) : NULL; + if (!ssd) + return EQUIP_ERR_NO_REQUIRED_PROFICIENCY; + + bool allowScaleSkill = false; + for (uint32 i = 0; i < sSkillLineAbilityStore.GetNumRows(); ++i) + { + SkillLineAbilityEntry const* skillInfo = sSkillLineAbilityStore.LookupEntry(i); + if (!skillInfo) + continue; + + if (skillInfo->skillId != item_use_skill) + continue; + + // can't learn + if (skillInfo->classmask && (skillInfo->classmask & getClassMask()) == 0) + continue; + + if (skillInfo->racemask && (skillInfo->racemask & getRaceMask()) == 0) + continue; + + allowScaleSkill = true; + break; + } + + if (!allowScaleSkill) + return EQUIP_ERR_NO_REQUIRED_PROFICIENCY; + } + } + + if (pProto->RequiredReputationFaction && uint32(GetReputationRank(pProto->RequiredReputationFaction)) < pProto->RequiredReputationRank) + return EQUIP_ERR_CANT_EQUIP_REPUTATION; + + return EQUIP_ERR_OK; + } + } + return EQUIP_ERR_ITEM_NOT_FOUND; +} + +InventoryResult Player::CanUseItem(ItemPrototype const* pProto) const +{ + // Used by group, function NeedBeforeGreed, to know if a prototype can be used by a player + + if (pProto) + { + if ((pProto->Flags2 & ITEM_FLAG2_HORDE_ONLY) && GetTeam() != HORDE) + return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; + + if ((pProto->Flags2 & ITEM_FLAG2_ALLIANCE_ONLY) && GetTeam() != ALLIANCE) + return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; + + if ((pProto->AllowableClass & getClassMask()) == 0 || (pProto->AllowableRace & getRaceMask()) == 0) + return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; + + if (pProto->RequiredSkill != 0) + { + if (GetSkillValue(pProto->RequiredSkill) == 0) + return EQUIP_ERR_NO_REQUIRED_PROFICIENCY; + else if (GetSkillValue(pProto->RequiredSkill) < pProto->RequiredSkillRank) + return EQUIP_ERR_CANT_EQUIP_SKILL; + } + + if (pProto->RequiredSpell != 0 && !HasSpell(pProto->RequiredSpell)) + return EQUIP_ERR_NO_REQUIRED_PROFICIENCY; + + if (getLevel() < pProto->RequiredLevel) + return EQUIP_ERR_CANT_EQUIP_LEVEL_I; + + return EQUIP_ERR_OK; + } + return EQUIP_ERR_ITEM_NOT_FOUND; +} + +InventoryResult Player::CanUseAmmo(uint32 item) const +{ + DEBUG_LOG("STORAGE: CanUseAmmo item = %u", item); + if (!isAlive()) + return EQUIP_ERR_YOU_ARE_DEAD; + // if( isStunned() ) + // return EQUIP_ERR_YOU_ARE_STUNNED; + ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(item); + if (pProto) + { + if (pProto->InventoryType != INVTYPE_AMMO) + return EQUIP_ERR_ONLY_AMMO_CAN_GO_HERE; + + InventoryResult msg = CanUseItem(pProto); + if (msg != EQUIP_ERR_OK) + return msg; + + /*if ( GetReputationMgr().GetReputation() < pProto->RequiredReputation ) + return EQUIP_ERR_CANT_EQUIP_REPUTATION; + */ + + // Requires No Ammo + if (GetDummyAura(46699)) + return EQUIP_ERR_BAG_FULL6; + + return EQUIP_ERR_OK; + } + return EQUIP_ERR_ITEM_NOT_FOUND; +} + +void Player::SetAmmo(uint32 item) +{ + //if(!item) + // return; + + //// already set + //if( GetUInt32Value(PLAYER_AMMO_ID) == item ) + // return; + + //// check ammo + //if (item) + //{ + // InventoryResult msg = CanUseAmmo( item ); + // if (msg != EQUIP_ERR_OK) + // { + // SendEquipError(msg, NULL, NULL, item); + // return; + // } + //} + + //SetUInt32Value(PLAYER_AMMO_ID, item); + + //_ApplyAmmoBonuses(); +} + +void Player::RemoveAmmo() +{ + //SetUInt32Value(PLAYER_AMMO_ID, 0); + + //m_ammoDPS = 0.0f; + + //if (CanModifyStats()) + // UpdateDamagePhysical(RANGED_ATTACK); +} + +// Return stored item (if stored to stack, it can diff. from pItem). And pItem ca be deleted in this case. +Item* Player::StoreNewItem(ItemPosCountVec const& dest, uint32 item, bool update, int32 randomPropertyId) +{ + uint32 count = 0; + for (ItemPosCountVec::const_iterator itr = dest.begin(); itr != dest.end(); ++itr) + count += itr->count; + + Item* pItem = Item::CreateItem(item, count, this, randomPropertyId); + if (pItem) + { + ItemAddedQuestCheck(item, count); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_RECEIVE_EPIC_ITEM, item, count); + pItem = StoreItem(dest, pItem, update); + } + return pItem; +} + +Item* Player::StoreItem(ItemPosCountVec const& dest, Item* pItem, bool update) +{ + if (!pItem) + return NULL; + + Item* lastItem = pItem; + uint32 entry = pItem->GetEntry(); + + for (ItemPosCountVec::const_iterator itr = dest.begin(); itr != dest.end();) + { + uint16 pos = itr->pos; + uint32 count = itr->count; + + ++itr; + + if (itr == dest.end()) + { + lastItem = _StoreItem(pos, pItem, count, false, update); + break; + } + + lastItem = _StoreItem(pos, pItem, count, true, update); + } + + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM, entry); + return lastItem; +} + +// Return stored item (if stored to stack, it can diff. from pItem). And pItem ca be deleted in this case. +Item* Player::_StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool update) +{ + if (!pItem) + return NULL; + + uint8 bag = pos >> 8; + uint8 slot = pos & 255; + + DEBUG_LOG("STORAGE: StoreItem bag = %u, slot = %u, item = %u, count = %u", bag, slot, pItem->GetEntry(), count); + + Item* pItem2 = GetItemByPos(bag, slot); + + if (!pItem2) + { + if (clone) + pItem = pItem->CloneItem(count, this); + else + pItem->SetCount(count); + + if (!pItem) + return NULL; + + if (pItem->GetProto()->Bonding == BIND_WHEN_PICKED_UP || + pItem->GetProto()->Bonding == BIND_QUEST_ITEM || + (pItem->GetProto()->Bonding == BIND_WHEN_EQUIPPED && IsBagPos(pos))) + pItem->SetBinding(true); + + if (bag == INVENTORY_SLOT_BAG_0) + { + m_items[slot] = pItem; + SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), pItem->GetObjectGuid()); + pItem->SetGuidValue(ITEM_FIELD_CONTAINED, GetObjectGuid()); + pItem->SetGuidValue(ITEM_FIELD_OWNER, GetObjectGuid()); + + pItem->SetSlot(slot); + pItem->SetContainer(NULL); + + if (IsInWorld() && update) + { + pItem->AddToWorld(); + pItem->SendCreateUpdateToPlayer(this); + } + + pItem->SetState(ITEM_CHANGED, this); + } + else if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag)) + { + pBag->StoreItem(slot, pItem, update); + if (IsInWorld() && update) + { + pItem->AddToWorld(); + pItem->SendCreateUpdateToPlayer(this); + } + pItem->SetState(ITEM_CHANGED, this); + pBag->SetState(ITEM_CHANGED, this); + } + + AddEnchantmentDurations(pItem); + AddItemDurations(pItem); + + // at place into not appropriate slot (bank, for example) remove aura + ApplyItemOnStoreSpell(pItem, IsEquipmentPos(pItem->GetBagSlot(), pItem->GetSlot()) || IsInventoryPos(pItem->GetBagSlot(), pItem->GetSlot())); + + return pItem; + } + else + { + if (pItem2->GetProto()->Bonding == BIND_WHEN_PICKED_UP || + pItem2->GetProto()->Bonding == BIND_QUEST_ITEM || + (pItem2->GetProto()->Bonding == BIND_WHEN_EQUIPPED && IsBagPos(pos))) + pItem2->SetBinding(true); + + pItem2->SetCount(pItem2->GetCount() + count); + if (IsInWorld() && update) + pItem2->SendCreateUpdateToPlayer(this); + + if (!clone) + { + // delete item (it not in any slot currently) + if (IsInWorld() && update) + { + pItem->RemoveFromWorld(); + pItem->DestroyForPlayer(this); + } + + RemoveEnchantmentDurations(pItem); + RemoveItemDurations(pItem); + + pItem->SetOwnerGuid(GetObjectGuid()); // prevent error at next SetState in case trade/mail/buy from vendor + pItem->SetState(ITEM_REMOVED, this); + } + + // AddItemDurations(pItem2); - pItem2 already have duration listed for player + AddEnchantmentDurations(pItem2); + + pItem2->SetState(ITEM_CHANGED, this); + + return pItem2; + } +} + +Item* Player::EquipNewItem(uint16 pos, uint32 item, bool update) +{ + if (Item* pItem = Item::CreateItem(item, 1, this)) + { + ItemAddedQuestCheck(item, 1); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_RECEIVE_EPIC_ITEM, item, 1); + return EquipItem(pos, pItem, update); + } + + return NULL; +} + +Item* Player::EquipItem(uint16 pos, Item* pItem, bool update) +{ + AddEnchantmentDurations(pItem); + AddItemDurations(pItem); + + uint8 bag = pos >> 8; + uint8 slot = pos & 255; + + Item* pItem2 = GetItemByPos(bag, slot); + if (!pItem2) + { + VisualizeItem(slot, pItem); + + if (isAlive()) + { + ItemPrototype const* pProto = pItem->GetProto(); + + // item set bonuses applied only at equip and removed at unequip, and still active for broken items + if (pProto && pProto->ItemSet) + AddItemsSetItem(this, pItem); + + _ApplyItemMods(pItem, slot, true); + + ApplyItemOnStoreSpell(pItem, true); + + // Weapons and also Totem/Relic/Sigil/etc + if (pProto && isInCombat() && (pProto->Class == ITEM_CLASS_WEAPON || pProto->InventoryType == INVTYPE_RELIC) && m_weaponChangeTimer == 0) + { + uint32 cooldownSpell = SPELL_ID_WEAPON_SWITCH_COOLDOWN_1_5s; + + if (getClass() == CLASS_ROGUE) + cooldownSpell = SPELL_ID_WEAPON_SWITCH_COOLDOWN_1_0s; + + SpellEntry const* spellProto = sSpellStore.LookupEntry(cooldownSpell); + + if (!spellProto) + sLog.outError("Weapon switch cooldown spell %u couldn't be found in Spell.dbc", cooldownSpell); + else + { + m_weaponChangeTimer = spellProto->GetStartRecoveryTime(); + + WorldPacket data(SMSG_SPELL_COOLDOWN, 8 + 1 + 4); + data << GetObjectGuid(); + data << uint8(1); + data << uint32(cooldownSpell); + data << uint32(0); + GetSession()->SendPacket(&data); + } + } + } + + if (IsInWorld() && update) + { + pItem->AddToWorld(); + pItem->SendCreateUpdateToPlayer(this); + } + + ApplyEquipCooldown(pItem); + + if (slot == EQUIPMENT_SLOT_MAINHAND) + { + UpdateExpertise(BASE_ATTACK); + UpdateArmorPenetration(); + } + else if (slot == EQUIPMENT_SLOT_OFFHAND) + { + UpdateExpertise(OFF_ATTACK); + UpdateArmorPenetration(); + } + + UpdateArmorSpecializations(); + } + else + { + pItem2->SetCount(pItem2->GetCount() + pItem->GetCount()); + if (IsInWorld() && update) + pItem2->SendCreateUpdateToPlayer(this); + + // delete item (it not in any slot currently) + // pItem->DeleteFromDB(); + if (IsInWorld() && update) + { + pItem->RemoveFromWorld(); + pItem->DestroyForPlayer(this); + } + + RemoveEnchantmentDurations(pItem); + RemoveItemDurations(pItem); + + pItem->SetOwnerGuid(GetObjectGuid()); // prevent error at next SetState in case trade/mail/buy from vendor + pItem->SetState(ITEM_REMOVED, this); + pItem2->SetState(ITEM_CHANGED, this); + + ApplyEquipCooldown(pItem2); + + return pItem2; + } + // Apply Titan's Grip damage penalty if necessary + if ((slot == EQUIPMENT_SLOT_MAINHAND || slot == EQUIPMENT_SLOT_OFFHAND) && CanTitanGrip() && HasTwoHandWeaponInOneHand() && !HasAura(49152)) + CastSpell(this, 49152, true); + + // only for full equip instead adding to stack + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EQUIP_ITEM, pItem->GetEntry()); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EQUIP_EPIC_ITEM, slot + 1); + + return pItem; +} + +void Player::QuickEquipItem(uint16 pos, Item* pItem) +{ + if (pItem) + { + AddEnchantmentDurations(pItem); + AddItemDurations(pItem); + ApplyItemOnStoreSpell(pItem, true); + + uint8 slot = pos & 255; + VisualizeItem(slot, pItem); + + if (IsInWorld()) + { + pItem->AddToWorld(); + pItem->SendCreateUpdateToPlayer(this); + } + // Apply Titan's Grip damage penalty if necessary + if ((slot == EQUIPMENT_SLOT_MAINHAND || slot == EQUIPMENT_SLOT_OFFHAND) && CanTitanGrip() && HasTwoHandWeaponInOneHand() && !HasAura(49152)) + CastSpell(this, 49152, true); + + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EQUIP_ITEM, pItem->GetEntry()); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EQUIP_EPIC_ITEM, slot + 1); + + UpdateArmorSpecializations(); + } +} + +void Player::SetVisibleItemSlot(uint8 slot, Item* pItem) +{ + if (pItem) + { + SetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + (slot * 2), pItem->GetEntry()); + SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 0, pItem->GetEnchantmentId(PERM_ENCHANTMENT_SLOT)); + SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 1, pItem->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)); + } + else + { + SetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + (slot * 2), 0); + SetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 0); + } +} + +void Player::VisualizeItem(uint8 slot, Item* pItem) +{ + if (!pItem) + return; + + // check also BIND_WHEN_PICKED_UP and BIND_QUEST_ITEM for .additem or .additemset case by GM (not binded at adding to inventory) + if (pItem->GetProto()->Bonding == BIND_WHEN_EQUIPPED || pItem->GetProto()->Bonding == BIND_WHEN_PICKED_UP || pItem->GetProto()->Bonding == BIND_QUEST_ITEM) + pItem->SetBinding(true); + + DEBUG_LOG("STORAGE: EquipItem slot = %u, item = %u", slot, pItem->GetEntry()); + + m_items[slot] = pItem; + SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), pItem->GetObjectGuid()); + pItem->SetGuidValue(ITEM_FIELD_CONTAINED, GetObjectGuid()); + pItem->SetGuidValue(ITEM_FIELD_OWNER, GetObjectGuid()); + pItem->SetSlot(slot); + pItem->SetContainer(NULL); + + if (slot < EQUIPMENT_SLOT_END) + SetVisibleItemSlot(slot, pItem); + + pItem->SetState(ITEM_CHANGED, this); +} + +void Player::RemoveItem(uint8 bag, uint8 slot, bool update) +{ + // note: removeitem does not actually change the item + // it only takes the item out of storage temporarily + // note2: if removeitem is to be used for delinking + // the item must be removed from the player's updatequeue + + if (Item* pItem = GetItemByPos(bag, slot)) + { + DEBUG_LOG("STORAGE: RemoveItem bag = %u, slot = %u, item = %u", bag, slot, pItem->GetEntry()); + + RemoveEnchantmentDurations(pItem); + RemoveItemDurations(pItem); + + if (bag == INVENTORY_SLOT_BAG_0) + { + if (slot < INVENTORY_SLOT_BAG_END) + { + ItemPrototype const* pProto = pItem->GetProto(); + // item set bonuses applied only at equip and removed at unequip, and still active for broken items + + if (pProto && pProto->ItemSet) + RemoveItemsSetItem(this, pProto); + + _ApplyItemMods(pItem, slot, false); + + // remove item dependent auras and casts (only weapon and armor slots) + if (slot < EQUIPMENT_SLOT_END) + { + RemoveItemDependentAurasAndCasts(pItem); + + // remove held enchantments, update expertise + if (slot == EQUIPMENT_SLOT_MAINHAND) + { + if (pItem->GetItemSuffixFactor()) + { + pItem->ClearEnchantment(PROP_ENCHANTMENT_SLOT_3); + pItem->ClearEnchantment(PROP_ENCHANTMENT_SLOT_4); + } + else + { + pItem->ClearEnchantment(PROP_ENCHANTMENT_SLOT_0); + pItem->ClearEnchantment(PROP_ENCHANTMENT_SLOT_1); + } + + UpdateExpertise(BASE_ATTACK); + UpdateArmorPenetration(); + } + else if (slot == EQUIPMENT_SLOT_OFFHAND) + { + UpdateExpertise(OFF_ATTACK); + UpdateArmorPenetration(); + } + } + } + + m_items[slot] = NULL; + SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), ObjectGuid()); + + if (slot < EQUIPMENT_SLOT_END) + { + SetVisibleItemSlot(slot, NULL); + // Remove Titan's Grip damage penalty if necessary + if ((slot == EQUIPMENT_SLOT_MAINHAND || slot == EQUIPMENT_SLOT_OFFHAND) && CanTitanGrip() && !HasTwoHandWeaponInOneHand()) + RemoveAurasDueToSpell(49152); + } + + UpdateArmorSpecializations(); + } + else + { + Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + pBag->RemoveItem(slot, update); + } + pItem->SetGuidValue(ITEM_FIELD_CONTAINED, ObjectGuid()); + // pItem->SetGuidValue(ITEM_FIELD_OWNER, ObjectGuid()); not clear owner at remove (it will be set at store). This used in mail and auction code + pItem->SetSlot(NULL_SLOT); + + // ApplyItemOnStoreSpell, for avoid re-apply will remove at _adding_ to not appropriate slot + + if (IsInWorld() && update) + pItem->SendCreateUpdateToPlayer(this); + } +} + +// Common operation need to remove item from inventory without delete in trade, auction, guild bank, mail.... +void Player::MoveItemFromInventory(uint8 bag, uint8 slot, bool update) +{ + if (Item* it = GetItemByPos(bag, slot)) + { + ItemRemovedQuestCheck(it->GetEntry(), it->GetCount()); + RemoveItem(bag, slot, update); + + // item atStore spell not removed in RemoveItem (for avoid reappaly in slots changes), so do it directly + if (IsEquipmentPos(bag, slot) || IsInventoryPos(bag, slot)) + ApplyItemOnStoreSpell(it, false); + + it->RemoveFromUpdateQueueOf(this); + if (it->IsInWorld()) + { + it->RemoveFromWorld(); + it->DestroyForPlayer(this); + } + } +} + +// Common operation need to add item from inventory without delete in trade, guild bank, mail.... +void Player::MoveItemToInventory(ItemPosCountVec const& dest, Item* pItem, bool update, bool in_characterInventoryDB) +{ + // update quest counters + ItemAddedQuestCheck(pItem->GetEntry(), pItem->GetCount()); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_RECEIVE_EPIC_ITEM, pItem->GetEntry(), pItem->GetCount()); + + // store item + Item* pLastItem = StoreItem(dest, pItem, update); + + // only set if not merged to existing stack (pItem can be deleted already but we can compare pointers any way) + if (pLastItem == pItem) + { + // update owner for last item (this can be original item with wrong owner + if (pLastItem->GetOwnerGuid() != GetObjectGuid()) + pLastItem->SetOwnerGuid(GetObjectGuid()); + + // if this original item then it need create record in inventory + // in case trade we already have item in other player inventory + pLastItem->SetState(in_characterInventoryDB ? ITEM_CHANGED : ITEM_NEW, this); + } +} + +void Player::DestroyItem(uint8 bag, uint8 slot, bool update) +{ + Item* pItem = GetItemByPos(bag, slot); + if (pItem) + { + DEBUG_LOG("STORAGE: DestroyItem bag = %u, slot = %u, item = %u", bag, slot, pItem->GetEntry()); + + // start from destroy contained items (only equipped bag can have its) + if (pItem->IsBag() && pItem->IsEquipped()) // this also prevent infinity loop if empty bag stored in bag==slot + { + for (int i = 0; i < MAX_BAG_SIZE; ++i) + DestroyItem(slot, i, update); + } + + if (pItem->HasFlag(ITEM_FIELD_FLAGS, ITEM_DYNFLAG_WRAPPED)) + { + static SqlStatementID delGifts ; + + SqlStatement stmt = CharacterDatabase.CreateStatement(delGifts, "DELETE FROM character_gifts WHERE item_guid = ?"); + stmt.PExecute(pItem->GetGUIDLow()); + } + + RemoveEnchantmentDurations(pItem); + RemoveItemDurations(pItem); + + if (IsEquipmentPos(bag, slot) || IsInventoryPos(bag, slot)) + ApplyItemOnStoreSpell(pItem, false); + + ItemRemovedQuestCheck(pItem->GetEntry(), pItem->GetCount()); + + if (bag == INVENTORY_SLOT_BAG_0) + { + SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), ObjectGuid()); + + // equipment and equipped bags can have applied bonuses + if (slot < INVENTORY_SLOT_BAG_END) + { + ItemPrototype const* pProto = pItem->GetProto(); + + // item set bonuses applied only at equip and removed at unequip, and still active for broken items + if (pProto && pProto->ItemSet) + RemoveItemsSetItem(this, pProto); + + _ApplyItemMods(pItem, slot, false); + } + + if (slot < EQUIPMENT_SLOT_END) + { + // remove item dependent auras and casts (only weapon and armor slots) + RemoveItemDependentAurasAndCasts(pItem); + + // update expertise + if (slot == EQUIPMENT_SLOT_MAINHAND) + { + UpdateExpertise(BASE_ATTACK); + UpdateArmorPenetration(); + } + else if (slot == EQUIPMENT_SLOT_OFFHAND) + { + UpdateExpertise(OFF_ATTACK); + UpdateArmorPenetration(); + } + + // equipment visual show + SetVisibleItemSlot(slot, NULL); + } + + m_items[slot] = NULL; + } + else if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag)) + pBag->RemoveItem(slot, update); + + if (IsInWorld() && update) + { + pItem->RemoveFromWorld(); + pItem->DestroyForPlayer(this); + } + + // pItem->SetOwnerGUID(0); + pItem->SetGuidValue(ITEM_FIELD_CONTAINED, ObjectGuid()); + pItem->SetSlot(NULL_SLOT); + pItem->SetState(ITEM_REMOVED, this); + } +} + +void Player::DestroyItemCount(uint32 item, uint32 count, bool update, bool unequip_check, bool inBankAlso) +{ + DEBUG_LOG("STORAGE: DestroyItemCount item = %u, count = %u", item, count); + uint32 remcount = 0; + + // in inventory + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (pItem->GetEntry() == item && !pItem->IsInTrade()) + { + if (pItem->GetCount() + remcount <= count) + { + // all items in inventory can unequipped + remcount += pItem->GetCount(); + DestroyItem(INVENTORY_SLOT_BAG_0, i, update); + + if (remcount >= count) + return; + } + else + { + ItemRemovedQuestCheck(pItem->GetEntry(), count - remcount); + pItem->SetCount(pItem->GetCount() - count + remcount); + if (IsInWorld() && update) + pItem->SendCreateUpdateToPlayer(this); + pItem->SetState(ITEM_CHANGED, this); + return; + } + } + } + } + + // in inventory bags + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + if (Item* pItem = pBag->GetItemByPos(j)) + { + if (pItem->GetEntry() == item && !pItem->IsInTrade()) + { + // all items in bags can be unequipped + if (pItem->GetCount() + remcount <= count) + { + remcount += pItem->GetCount(); + DestroyItem(i, j, update); + + if (remcount >= count) + return; + } + else + { + ItemRemovedQuestCheck(pItem->GetEntry(), count - remcount); + pItem->SetCount(pItem->GetCount() - count + remcount); + if (IsInWorld() && update) + pItem->SendCreateUpdateToPlayer(this); + pItem->SetState(ITEM_CHANGED, this); + return; + } + } + } + } + } + } + + // in equipment and bag list + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade()) + { + if (pItem->GetCount() + remcount <= count) + { + if (!unequip_check || CanUnequipItem(INVENTORY_SLOT_BAG_0 << 8 | i, false) == EQUIP_ERR_OK) + { + remcount += pItem->GetCount(); + DestroyItem(INVENTORY_SLOT_BAG_0, i, update); + + if (remcount >= count) + return; + } + } + else + { + ItemRemovedQuestCheck(pItem->GetEntry(), count - remcount); + pItem->SetCount(pItem->GetCount() - count + remcount); + if (IsInWorld() && update) + pItem->SendCreateUpdateToPlayer(this); + pItem->SetState(ITEM_CHANGED, this); + return; + } + } + } + } + + if (inBankAlso) // Remove items from bank as well + { + for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i) + { + Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade()) + { + if (pItem->GetCount() + remcount <= count) + { + remcount += pItem->GetCount(); + DestroyItem(INVENTORY_SLOT_BAG_0, i, update); + + if (remcount >= count) + return; + } + else + { + ItemRemovedQuestCheck(pItem->GetEntry(), count - remcount); + pItem->SetCount(pItem->GetCount() - count + remcount); + if (IsInWorld() && update) + pItem->SendCreateUpdateToPlayer(this); + pItem->SetState(ITEM_CHANGED, this); + return; + } + } + } + + for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + { + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + Item* pItem = pBag->GetItemByPos(j); + if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade()) + { + if (pItem->GetCount() + remcount <= count) + { + remcount += pItem->GetCount(); + DestroyItem(i, j, update); + + if (remcount >= count) + return; + } + else + { + ItemRemovedQuestCheck(pItem->GetEntry(), count - remcount); + pItem->SetCount(pItem->GetCount() - count + remcount); + if (IsInWorld() && update) + pItem->SendCreateUpdateToPlayer(this); + pItem->SetState(ITEM_CHANGED, this); + return; + } + } + } + } + } + } +} + +void Player::DestroyZoneLimitedItem(bool update, uint32 new_zone) +{ + DEBUG_LOG("STORAGE: DestroyZoneLimitedItem in map %u and area %u", GetMapId(), new_zone); + + // in inventory + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->IsLimitedToAnotherMapOrZone(GetMapId(), new_zone)) + DestroyItem(INVENTORY_SLOT_BAG_0, i, update); + + // in inventory bags + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + if (Item* pItem = pBag->GetItemByPos(j)) + if (pItem->IsLimitedToAnotherMapOrZone(GetMapId(), new_zone)) + DestroyItem(i, j, update); + + // in equipment and bag list + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->IsLimitedToAnotherMapOrZone(GetMapId(), new_zone)) + DestroyItem(INVENTORY_SLOT_BAG_0, i, update); +} + +void Player::DestroyConjuredItems(bool update) +{ + // used when entering arena + // destroys all conjured items + DEBUG_LOG("STORAGE: DestroyConjuredItems"); + + // in inventory + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->IsConjuredConsumable()) + DestroyItem(INVENTORY_SLOT_BAG_0, i, update); + + // in inventory bags + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + if (Item* pItem = pBag->GetItemByPos(j)) + if (pItem->IsConjuredConsumable()) + DestroyItem(i, j, update); + + // in equipment and bag list + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->IsConjuredConsumable()) + DestroyItem(INVENTORY_SLOT_BAG_0, i, update); +} + +void Player::DestroyItemCount(Item* pItem, uint32& count, bool update) +{ + if (!pItem) + return; + + DEBUG_LOG("STORAGE: DestroyItemCount item (GUID: %u, Entry: %u) count = %u", pItem->GetGUIDLow(), pItem->GetEntry(), count); + + if (pItem->GetCount() <= count) + { + count -= pItem->GetCount(); + + DestroyItem(pItem->GetBagSlot(), pItem->GetSlot(), update); + } + else + { + ItemRemovedQuestCheck(pItem->GetEntry(), count); + pItem->SetCount(pItem->GetCount() - count); + count = 0; + if (IsInWorld() && update) + pItem->SendCreateUpdateToPlayer(this); + pItem->SetState(ITEM_CHANGED, this); + } +} + +void Player::SplitItem(uint16 src, uint16 dst, uint32 count) +{ + uint8 srcbag = src >> 8; + uint8 srcslot = src & 255; + + uint8 dstbag = dst >> 8; + uint8 dstslot = dst & 255; + + Item* pSrcItem = GetItemByPos(srcbag, srcslot); + if (!pSrcItem) + { + SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pSrcItem, NULL); + return; + } + + if (pSrcItem->HasGeneratedLoot()) // prevent split looting item (stackable items can has only temporary loot and this meaning that loot window open) + { + // best error message found for attempting to split while looting + SendEquipError(EQUIP_ERR_COULDNT_SPLIT_ITEMS, pSrcItem, NULL); + return; + } + + // not let split all items (can be only at cheating) + if (pSrcItem->GetCount() == count) + { + SendEquipError(EQUIP_ERR_COULDNT_SPLIT_ITEMS, pSrcItem, NULL); + return; + } + + // not let split more existing items (can be only at cheating) + if (pSrcItem->GetCount() < count) + { + SendEquipError(EQUIP_ERR_TRIED_TO_SPLIT_MORE_THAN_COUNT, pSrcItem, NULL); + return; + } + + DEBUG_LOG("STORAGE: SplitItem bag = %u, slot = %u, item = %u, count = %u", dstbag, dstslot, pSrcItem->GetEntry(), count); + Item* pNewItem = pSrcItem->CloneItem(count, this); + if (!pNewItem) + { + SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pSrcItem, NULL); + return; + } + + if (IsInventoryPos(dst)) + { + // change item amount before check (for unique max count check) + pSrcItem->SetCount(pSrcItem->GetCount() - count); + + ItemPosCountVec dest; + InventoryResult msg = CanStoreItem(dstbag, dstslot, dest, pNewItem, false); + if (msg != EQUIP_ERR_OK) + { + delete pNewItem; + pSrcItem->SetCount(pSrcItem->GetCount() + count); + SendEquipError(msg, pSrcItem, NULL); + return; + } + + if (IsInWorld()) + pSrcItem->SendCreateUpdateToPlayer(this); + pSrcItem->SetState(ITEM_CHANGED, this); + StoreItem(dest, pNewItem, true); + } + else if (IsBankPos(dst)) + { + // change item amount before check (for unique max count check) + pSrcItem->SetCount(pSrcItem->GetCount() - count); + + ItemPosCountVec dest; + InventoryResult msg = CanBankItem(dstbag, dstslot, dest, pNewItem, false); + if (msg != EQUIP_ERR_OK) + { + delete pNewItem; + pSrcItem->SetCount(pSrcItem->GetCount() + count); + SendEquipError(msg, pSrcItem, NULL); + return; + } + + if (IsInWorld()) + pSrcItem->SendCreateUpdateToPlayer(this); + pSrcItem->SetState(ITEM_CHANGED, this); + BankItem(dest, pNewItem, true); + } + else if (IsEquipmentPos(dst)) + { + // change item amount before check (for unique max count check), provide space for splitted items + pSrcItem->SetCount(pSrcItem->GetCount() - count); + + uint16 dest; + InventoryResult msg = CanEquipItem(dstslot, dest, pNewItem, false); + if (msg != EQUIP_ERR_OK) + { + delete pNewItem; + pSrcItem->SetCount(pSrcItem->GetCount() + count); + SendEquipError(msg, pSrcItem, NULL); + return; + } + + if (IsInWorld()) + pSrcItem->SendCreateUpdateToPlayer(this); + pSrcItem->SetState(ITEM_CHANGED, this); + EquipItem(dest, pNewItem, true); + AutoUnequipOffhandIfNeed(); + } +} + +void Player::SwapItem(uint16 src, uint16 dst) +{ + uint8 srcbag = src >> 8; + uint8 srcslot = src & 255; + + uint8 dstbag = dst >> 8; + uint8 dstslot = dst & 255; + + Item* pSrcItem = GetItemByPos(srcbag, srcslot); + Item* pDstItem = GetItemByPos(dstbag, dstslot); + + if (!pSrcItem) + return; + + DEBUG_LOG("STORAGE: SwapItem bag = %u, slot = %u, item = %u", dstbag, dstslot, pSrcItem->GetEntry()); + + if (!isAlive()) + { + SendEquipError(EQUIP_ERR_YOU_ARE_DEAD, pSrcItem, pDstItem); + return; + } + + // SRC checks + + // check unequip potability for equipped items and bank bags + if (IsEquipmentPos(src) || IsBagPos(src)) + { + // bags can be swapped with empty bag slots, or with empty bag (items move possibility checked later) + InventoryResult msg = CanUnequipItem(src, !IsBagPos(src) || IsBagPos(dst) || (pDstItem && pDstItem->IsBag() && ((Bag*)pDstItem)->IsEmpty())); + if (msg != EQUIP_ERR_OK) + { + SendEquipError(msg, pSrcItem, pDstItem); + return; + } + } + + // prevent put equipped/bank bag in self + if (IsBagPos(src) && srcslot == dstbag) + { + SendEquipError(EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG, pSrcItem, pDstItem); + return; + } + + // prevent put equipped/bank bag in self + if (IsBagPos(dst) && dstslot == srcbag) + { + SendEquipError(EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG, pDstItem, pSrcItem); + return; + } + + // DST checks + + if (pDstItem) + { + // check unequip potability for equipped items and bank bags + if (IsEquipmentPos(dst) || IsBagPos(dst)) + { + // bags can be swapped with empty bag slots, or with empty bag (items move possibility checked later) + InventoryResult msg = CanUnequipItem(dst, !IsBagPos(dst) || IsBagPos(src) || (pSrcItem->IsBag() && ((Bag*)pSrcItem)->IsEmpty())); + if (msg != EQUIP_ERR_OK) + { + SendEquipError(msg, pSrcItem, pDstItem); + return; + } + } + } + + // NOW this is or item move (swap with empty), or swap with another item (including bags in bag possitions) + // or swap empty bag with another empty or not empty bag (with items exchange) + + // Move case + if (!pDstItem) + { + if (IsInventoryPos(dst)) + { + ItemPosCountVec dest; + InventoryResult msg = CanStoreItem(dstbag, dstslot, dest, pSrcItem, false); + if (msg != EQUIP_ERR_OK) + { + SendEquipError(msg, pSrcItem, NULL); + return; + } + + RemoveItem(srcbag, srcslot, true); + StoreItem(dest, pSrcItem, true); + } + else if (IsBankPos(dst)) + { + ItemPosCountVec dest; + InventoryResult msg = CanBankItem(dstbag, dstslot, dest, pSrcItem, false); + if (msg != EQUIP_ERR_OK) + { + SendEquipError(msg, pSrcItem, NULL); + return; + } + + RemoveItem(srcbag, srcslot, true); + BankItem(dest, pSrcItem, true); + } + else if (IsEquipmentPos(dst)) + { + uint16 dest; + InventoryResult msg = CanEquipItem(dstslot, dest, pSrcItem, false); + if (msg != EQUIP_ERR_OK) + { + SendEquipError(msg, pSrcItem, NULL); + return; + } + + RemoveItem(srcbag, srcslot, true); + EquipItem(dest, pSrcItem, true); + AutoUnequipOffhandIfNeed(); + } + + return; + } + + // attempt merge to / fill target item + if (!pSrcItem->IsBag() && !pDstItem->IsBag()) + { + InventoryResult msg; + ItemPosCountVec sDest; + uint16 eDest; + if (IsInventoryPos(dst)) + msg = CanStoreItem(dstbag, dstslot, sDest, pSrcItem, false); + else if (IsBankPos(dst)) + msg = CanBankItem(dstbag, dstslot, sDest, pSrcItem, false); + else if (IsEquipmentPos(dst)) + msg = CanEquipItem(dstslot, eDest, pSrcItem, false); + else + return; + + // can be merge/fill + if (msg == EQUIP_ERR_OK) + { + if (pSrcItem->GetCount() + pDstItem->GetCount() <= pSrcItem->GetProto()->GetMaxStackSize()) + { + RemoveItem(srcbag, srcslot, true); + + if (IsInventoryPos(dst)) + StoreItem(sDest, pSrcItem, true); + else if (IsBankPos(dst)) + BankItem(sDest, pSrcItem, true); + else if (IsEquipmentPos(dst)) + { + EquipItem(eDest, pSrcItem, true); + AutoUnequipOffhandIfNeed(); + } + } + else + { + pSrcItem->SetCount(pSrcItem->GetCount() + pDstItem->GetCount() - pSrcItem->GetProto()->GetMaxStackSize()); + pDstItem->SetCount(pSrcItem->GetProto()->GetMaxStackSize()); + pSrcItem->SetState(ITEM_CHANGED, this); + pDstItem->SetState(ITEM_CHANGED, this); + if (IsInWorld()) + { + pSrcItem->SendCreateUpdateToPlayer(this); + pDstItem->SendCreateUpdateToPlayer(this); + } + } + return; + } + } + + // impossible merge/fill, do real swap + InventoryResult msg; + + // check src->dest move possibility + ItemPosCountVec sDest; + uint16 eDest = 0; + if (IsInventoryPos(dst)) + msg = CanStoreItem(dstbag, dstslot, sDest, pSrcItem, true); + else if (IsBankPos(dst)) + msg = CanBankItem(dstbag, dstslot, sDest, pSrcItem, true); + else if (IsEquipmentPos(dst)) + { + msg = CanEquipItem(dstslot, eDest, pSrcItem, true); + if (msg == EQUIP_ERR_OK) + msg = CanUnequipItem(eDest, true); + } + + if (msg != EQUIP_ERR_OK) + { + SendEquipError(msg, pSrcItem, pDstItem); + return; + } + + // check dest->src move possibility + ItemPosCountVec sDest2; + uint16 eDest2 = 0; + if (IsInventoryPos(src)) + msg = CanStoreItem(srcbag, srcslot, sDest2, pDstItem, true); + else if (IsBankPos(src)) + msg = CanBankItem(srcbag, srcslot, sDest2, pDstItem, true); + else if (IsEquipmentPos(src)) + { + msg = CanEquipItem(srcslot, eDest2, pDstItem, true); + if (msg == EQUIP_ERR_OK) + msg = CanUnequipItem(eDest2, true); + } + + if (msg != EQUIP_ERR_OK) + { + SendEquipError(msg, pDstItem, pSrcItem); + return; + } + + // Check bag swap with item exchange (one from empty in not bag possition (equipped (not possible in fact) or store) + if (pSrcItem->IsBag() && pDstItem->IsBag()) + { + Bag* emptyBag = NULL; + Bag* fullBag = NULL; + if (((Bag*)pSrcItem)->IsEmpty() && !IsBagPos(src)) + { + emptyBag = (Bag*)pSrcItem; + fullBag = (Bag*)pDstItem; + } + else if (((Bag*)pDstItem)->IsEmpty() && !IsBagPos(dst)) + { + emptyBag = (Bag*)pDstItem; + fullBag = (Bag*)pSrcItem; + } + + // bag swap (with items exchange) case + if (emptyBag && fullBag) + { + ItemPrototype const* emotyProto = emptyBag->GetProto(); + + uint32 count = 0; + + for (uint32 i = 0; i < fullBag->GetBagSize(); ++i) + { + Item* bagItem = fullBag->GetItemByPos(i); + if (!bagItem) + continue; + + ItemPrototype const* bagItemProto = bagItem->GetProto(); + if (!bagItemProto || !ItemCanGoIntoBag(bagItemProto, emotyProto)) + { + // one from items not go to empty target bag + SendEquipError(EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG, pSrcItem, pDstItem); + return; + } + + ++count; + } + + if (count > emptyBag->GetBagSize()) + { + // too small targeted bag + SendEquipError(EQUIP_ERR_ITEMS_CANT_BE_SWAPPED, pSrcItem, pDstItem); + return; + } + + // Items swap + count = 0; // will pos in new bag + for (uint32 i = 0; i < fullBag->GetBagSize(); ++i) + { + Item* bagItem = fullBag->GetItemByPos(i); + if (!bagItem) + continue; + + fullBag->RemoveItem(i, true); + emptyBag->StoreItem(count, bagItem, true); + bagItem->SetState(ITEM_CHANGED, this); + + ++count; + } + } + } + + // now do moves, remove... + RemoveItem(dstbag, dstslot, false); + RemoveItem(srcbag, srcslot, false); + + // add to dest + if (IsInventoryPos(dst)) + StoreItem(sDest, pSrcItem, true); + else if (IsBankPos(dst)) + BankItem(sDest, pSrcItem, true); + else if (IsEquipmentPos(dst)) + EquipItem(eDest, pSrcItem, true); + + // add to src + if (IsInventoryPos(src)) + StoreItem(sDest2, pDstItem, true); + else if (IsBankPos(src)) + BankItem(sDest2, pDstItem, true); + else if (IsEquipmentPos(src)) + EquipItem(eDest2, pDstItem, true); + + AutoUnequipOffhandIfNeed(); +} + +void Player::AddItemToBuyBackSlot(Item* pItem) +{ + if (pItem) + { + uint32 slot = m_currentBuybackSlot; + // if current back slot non-empty search oldest or free + if (m_items[slot]) + { + uint32 oldest_time = GetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1); + uint32 oldest_slot = BUYBACK_SLOT_START; + + for (uint32 i = BUYBACK_SLOT_START + 1; i < BUYBACK_SLOT_END; ++i) + { + // found empty + if (!m_items[i]) + { + slot = i; + break; + } + + uint32 i_time = GetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1 + i - BUYBACK_SLOT_START); + + if (oldest_time > i_time) + { + oldest_time = i_time; + oldest_slot = i; + } + } + + // find oldest + slot = oldest_slot; + } + + RemoveItemFromBuyBackSlot(slot, true); + DEBUG_LOG("STORAGE: AddItemToBuyBackSlot item = %u, slot = %u", pItem->GetEntry(), slot); + + m_items[slot] = pItem; + time_t base = time(NULL); + uint32 etime = uint32(base - m_logintime + (30 * 3600)); + uint32 eslot = slot - BUYBACK_SLOT_START; + + SetGuidValue(PLAYER_FIELD_VENDORBUYBACK_SLOT_1 + (eslot * 2), pItem->GetObjectGuid()); + if (ItemPrototype const* pProto = pItem->GetProto()) + SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1 + eslot, pProto->SellPrice * pItem->GetCount()); + else + SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1 + eslot, 0); + SetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1 + eslot, (uint32)etime); + + // move to next (for non filled list is move most optimized choice) + if (m_currentBuybackSlot < BUYBACK_SLOT_END - 1) + ++m_currentBuybackSlot; + } +} + +Item* Player::GetItemFromBuyBackSlot(uint32 slot) +{ + DEBUG_LOG("STORAGE: GetItemFromBuyBackSlot slot = %u", slot); + if (slot >= BUYBACK_SLOT_START && slot < BUYBACK_SLOT_END) + return m_items[slot]; + return NULL; +} + +void Player::RemoveItemFromBuyBackSlot(uint32 slot, bool del) +{ + DEBUG_LOG("STORAGE: RemoveItemFromBuyBackSlot slot = %u", slot); + if (slot >= BUYBACK_SLOT_START && slot < BUYBACK_SLOT_END) + { + Item* pItem = m_items[slot]; + if (pItem) + { + pItem->RemoveFromWorld(); + if (del) pItem->SetState(ITEM_REMOVED, this); + } + + m_items[slot] = NULL; + + uint32 eslot = slot - BUYBACK_SLOT_START; + SetGuidValue(PLAYER_FIELD_VENDORBUYBACK_SLOT_1 + (eslot * 2), ObjectGuid()); + SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1 + eslot, 0); + SetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1 + eslot, 0); + + // if current backslot is filled set to now free slot + if (m_items[m_currentBuybackSlot]) + m_currentBuybackSlot = slot; + } +} + +void Player::SendEquipError(InventoryResult msg, Item* pItem, Item* pItem2, uint32 itemid /*= 0*/) const +{ + DEBUG_LOG("WORLD: Sent SMSG_INVENTORY_CHANGE_FAILURE (%u)", msg); + WorldPacket data(SMSG_INVENTORY_CHANGE_FAILURE, 1 + 8 + 8 + 1); + data << uint8(msg); + + if (msg != EQUIP_ERR_OK) + { + data << (pItem ? pItem->GetObjectGuid() : ObjectGuid()); + data << (pItem2 ? pItem2->GetObjectGuid() : ObjectGuid()); + data << uint8(0); // bag type subclass, used with EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM and EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG2 + + switch (msg) + { + case EQUIP_ERR_CANT_EQUIP_LEVEL_I: + case EQUIP_ERR_PURCHASE_LEVEL_TOO_LOW: + { + ItemPrototype const* proto = pItem ? pItem->GetProto() : ObjectMgr::GetItemPrototype(itemid); + data << uint32(proto ? proto->RequiredLevel : 0); + break; + } + case EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM: // no idea about this one... + { + data << uint64(0); + data << uint32(0); + data << uint64(0); + break; + } + case EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED_IS: + case EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_SOCKETED_EXCEEDED_IS: + case EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED_IS: + { + ItemPrototype const* proto = pItem ? pItem->GetProto() : ObjectMgr::GetItemPrototype(itemid); + data << uint32(proto ? proto->ItemLimitCategory : 0); + break; + } + default: + break; + } + } + GetSession()->SendPacket(&data); +} + +void Player::SendBuyError(BuyResult msg, Creature* pCreature, uint32 item, uint32 param) +{ + DEBUG_LOG("WORLD: Sent SMSG_BUY_FAILED"); + WorldPacket data(SMSG_BUY_FAILED, (8 + 4 + 4 + 1)); + data << (pCreature ? pCreature->GetObjectGuid() : ObjectGuid()); + data << uint32(item); + if (param > 0) + data << uint32(param); + data << uint8(msg); + GetSession()->SendPacket(&data); +} + +void Player::SendSellError(SellResult msg, Creature* pCreature, ObjectGuid itemGuid, uint32 param) +{ + DEBUG_LOG("WORLD: Sent SMSG_SELL_ITEM"); + WorldPacket data(SMSG_SELL_ITEM, (8 + 8 + (param ? 4 : 0) + 1)); // last check 2.0.10 + data << (pCreature ? pCreature->GetObjectGuid() : ObjectGuid()); + data << ObjectGuid(itemGuid); + if (param > 0) + data << uint32(param); + data << uint8(msg); + GetSession()->SendPacket(&data); +} + +void Player::TradeCancel(bool sendback) +{ + if (m_trade) + { + Player* trader = m_trade->GetTrader(); + + // send yellow "Trade canceled" message to both traders + if (sendback) + GetSession()->SendCancelTrade(); + + trader->GetSession()->SendCancelTrade(); + + // cleanup + delete m_trade; + m_trade = NULL; + delete trader->m_trade; + trader->m_trade = NULL; + } +} + +void Player::UpdateItemDuration(uint32 time, bool realtimeonly) +{ + if (m_itemDuration.empty()) + return; + + DEBUG_LOG("Player::UpdateItemDuration(%u,%u)", time, realtimeonly); + + for (ItemDurationList::const_iterator itr = m_itemDuration.begin(); itr != m_itemDuration.end();) + { + Item* item = *itr; + ++itr; // current element can be erased in UpdateDuration + + if ((realtimeonly && (item->GetProto()->ExtraFlags & ITEM_EXTRA_REAL_TIME_DURATION)) || !realtimeonly) + item->UpdateDuration(this, time); + } +} + +void Player::UpdateEnchantTime(uint32 time) +{ + for (EnchantDurationList::iterator itr = m_enchantDuration.begin(), next; itr != m_enchantDuration.end(); itr = next) + { + MANGOS_ASSERT(itr->item); + next = itr; + if (!itr->item->GetEnchantmentId(itr->slot)) + { + next = m_enchantDuration.erase(itr); + } + else if (itr->leftduration <= time) + { + ApplyEnchantment(itr->item, itr->slot, false, false); + itr->item->ClearEnchantment(itr->slot); + next = m_enchantDuration.erase(itr); + } + else if (itr->leftduration > time) + { + itr->leftduration -= time; + ++next; + } + } +} + +void Player::AddEnchantmentDurations(Item* item) +{ + for (int x = 0; x < MAX_ENCHANTMENT_SLOT; ++x) + { + if (x > PRISMATIC_ENCHANTMENT_SLOT && x < PROP_ENCHANTMENT_SLOT_0) + continue; + + if (!item->GetEnchantmentId(EnchantmentSlot(x))) + continue; + + uint32 duration = item->GetEnchantmentDuration(EnchantmentSlot(x)); + if (duration > 0) + AddEnchantmentDuration(item, EnchantmentSlot(x), duration); + } +} + +void Player::RemoveEnchantmentDurations(Item* item) +{ + for (EnchantDurationList::iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end();) + { + if (itr->item == item) + { + // save duration in item + item->SetEnchantmentDuration(EnchantmentSlot(itr->slot), itr->leftduration); + itr = m_enchantDuration.erase(itr); + } + else + ++itr; + } +} + +void Player::RemoveAllEnchantments(EnchantmentSlot slot) +{ + // remove enchantments from equipped items first to clean up the m_enchantDuration list + for (EnchantDurationList::iterator itr = m_enchantDuration.begin(), next; itr != m_enchantDuration.end(); itr = next) + { + next = itr; + if (itr->slot == slot) + { + if (itr->item && itr->item->GetEnchantmentId(slot)) + { + // remove from stats + ApplyEnchantment(itr->item, slot, false, false); + // remove visual + itr->item->ClearEnchantment(slot); + } + // remove from update list + next = m_enchantDuration.erase(itr); + } + else + ++next; + } + + // remove enchants from inventory items + // NOTE: no need to remove these from stats, since these aren't equipped + // in inventory + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->GetEnchantmentId(slot)) + pItem->ClearEnchantment(slot); + + // in inventory bags + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + if (Item* pItem = pBag->GetItemByPos(j)) + if (pItem->GetEnchantmentId(slot)) + pItem->ClearEnchantment(slot); +} + +// duration == 0 will remove item enchant +void Player::AddEnchantmentDuration(Item* item, EnchantmentSlot slot, uint32 duration) +{ + if (!item) + return; + + if (slot >= MAX_ENCHANTMENT_SLOT) + return; + + for (EnchantDurationList::iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr) + { + if (itr->item == item && itr->slot == slot) + { + itr->item->SetEnchantmentDuration(itr->slot, itr->leftduration); + m_enchantDuration.erase(itr); + break; + } + } + if (item && duration > 0) + { + GetSession()->SendItemEnchantTimeUpdate(GetObjectGuid(), item->GetObjectGuid(), slot, uint32(duration / 1000)); + m_enchantDuration.push_back(EnchantDuration(item, slot, duration)); + } +} + +void Player::ApplyEnchantment(Item* item, bool apply) +{ + for (uint32 slot = 0; slot < MAX_ENCHANTMENT_SLOT; ++slot) + ApplyEnchantment(item, EnchantmentSlot(slot), apply); +} + +void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool apply_dur, bool ignore_condition) +{ + if (slot > PRISMATIC_ENCHANTMENT_SLOT && slot < PROP_ENCHANTMENT_SLOT_0) + return; + + if (!item) + return; + + if (!item->IsEquipped()) + return; + + if (slot >= MAX_ENCHANTMENT_SLOT) + return; + + if (slot == REFORGE_ENCHANTMENT_SLOT) + { + ApplyReforgeEnchantment(item, apply); + return; + } + + uint32 enchant_id = item->GetEnchantmentId(slot); + if (!enchant_id) + return; + + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + return; + + if (!ignore_condition && pEnchant->EnchantmentCondition && !EnchantmentFitsRequirements(pEnchant->EnchantmentCondition, -1)) + return; + + if (!item->IsBroken()) + { + for (int s = 0; s < 3; ++s) + { + uint32 enchant_display_type = pEnchant->type[s]; + uint32 enchant_amount = pEnchant->amount[s]; + uint32 enchant_spell_id = pEnchant->spellid[s]; + + switch (enchant_display_type) + { + case ITEM_ENCHANTMENT_TYPE_NONE: + break; + case ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL: + // processed in Player::CastItemCombatSpell + break; + case ITEM_ENCHANTMENT_TYPE_DAMAGE: + if (item->GetSlot() == EQUIPMENT_SLOT_MAINHAND) + HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_VALUE, float(enchant_amount), apply); + else if (item->GetSlot() == EQUIPMENT_SLOT_OFFHAND) + HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_VALUE, float(enchant_amount), apply); + else if (item->GetSlot() == EQUIPMENT_SLOT_RANGED) + HandleStatModifier(UNIT_MOD_DAMAGE_RANGED, TOTAL_VALUE, float(enchant_amount), apply); + break; + case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL: + { + if (enchant_spell_id) + { + if (apply) + { + int32 basepoints = 0; + // Random Property Exist - try found basepoints for spell (basepoints depends from item suffix factor) + if (item->GetItemRandomPropertyId()) + { + ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); + if (item_rand) + { + // Search enchant_amount + for (int k = 0; k < 3; ++k) + { + if (item_rand->enchant_id[k] == enchant_id) + { + basepoints = int32((item_rand->prefix[k] * item->GetItemSuffixFactor()) / 10000); + break; + } + } + } + } + // Cast custom spell vs all equal basepoints got from enchant_amount + if (basepoints) + CastCustomSpell(this, enchant_spell_id, &basepoints, &basepoints, &basepoints, true, item); + else + CastSpell(this, enchant_spell_id, true, item); + } + else + RemoveAurasDueToItemSpell(item, enchant_spell_id); + } + break; + } + case ITEM_ENCHANTMENT_TYPE_RESISTANCE: + if (!enchant_amount) + { + ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); + if (item_rand) + { + for (int k = 0; k < 3; ++k) + { + if (item_rand->enchant_id[k] == enchant_id) + { + enchant_amount = uint32((item_rand->prefix[k] * item->GetItemSuffixFactor()) / 10000); + break; + } + } + } + } + + HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + enchant_spell_id), TOTAL_VALUE, float(enchant_amount), apply); + break; + case ITEM_ENCHANTMENT_TYPE_STAT: + { + if (!enchant_amount) + { + ItemRandomSuffixEntry const* item_rand_suffix = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); + if (item_rand_suffix) + { + for (int k = 0; k < 3; ++k) + { + if (item_rand_suffix->enchant_id[k] == enchant_id) + { + enchant_amount = uint32((item_rand_suffix->prefix[k] * item->GetItemSuffixFactor()) / 10000); + break; + } + } + } + } + + DEBUG_LOG("Adding %u to stat nb %u", enchant_amount, enchant_spell_id); + switch (enchant_spell_id) + { + /*case ITEM_MOD_MANA: + DEBUG_LOG("+ %u MANA", enchant_amount); + HandleStatModifier(UNIT_MOD_MANA, BASE_VALUE, float(enchant_amount), apply); + break;*/ + case ITEM_MOD_HEALTH: + DEBUG_LOG("+ %u HEALTH", enchant_amount); + HandleStatModifier(UNIT_MOD_HEALTH, BASE_VALUE, float(enchant_amount), apply); + break; + case ITEM_MOD_AGILITY: + DEBUG_LOG("+ %u AGILITY", enchant_amount); + HandleStatModifier(UNIT_MOD_STAT_AGILITY, TOTAL_VALUE, float(enchant_amount), apply); + ApplyStatBuffMod(STAT_AGILITY, float(enchant_amount), apply); + break; + case ITEM_MOD_STRENGTH: + DEBUG_LOG("+ %u STRENGTH", enchant_amount); + HandleStatModifier(UNIT_MOD_STAT_STRENGTH, TOTAL_VALUE, float(enchant_amount), apply); + ApplyStatBuffMod(STAT_STRENGTH, float(enchant_amount), apply); + break; + case ITEM_MOD_INTELLECT: + DEBUG_LOG("+ %u INTELLECT", enchant_amount); + HandleStatModifier(UNIT_MOD_STAT_INTELLECT, TOTAL_VALUE, float(enchant_amount), apply); + ApplyStatBuffMod(STAT_INTELLECT, float(enchant_amount), apply); + break; + case ITEM_MOD_SPIRIT: + DEBUG_LOG("+ %u SPIRIT", enchant_amount); + HandleStatModifier(UNIT_MOD_STAT_SPIRIT, TOTAL_VALUE, float(enchant_amount), apply); + ApplyStatBuffMod(STAT_SPIRIT, float(enchant_amount), apply); + break; + case ITEM_MOD_STAMINA: + DEBUG_LOG("+ %u STAMINA", enchant_amount); + HandleStatModifier(UNIT_MOD_STAT_STAMINA, TOTAL_VALUE, float(enchant_amount), apply); + ApplyStatBuffMod(STAT_STAMINA, float(enchant_amount), apply); + break; + case ITEM_MOD_DODGE_RATING: + ApplyRatingMod(CR_DODGE, enchant_amount, apply); + DEBUG_LOG("+ %u DODGE", enchant_amount); + break; + case ITEM_MOD_PARRY_RATING: + ApplyRatingMod(CR_PARRY, enchant_amount, apply); + DEBUG_LOG("+ %u PARRY", enchant_amount); + break; + case ITEM_MOD_CRIT_RANGED_RATING: + ApplyRatingMod(CR_CRIT_RANGED, enchant_amount, apply); + DEBUG_LOG("+ %u RANGED_CRIT", enchant_amount); + break; + case ITEM_MOD_HIT_RATING: + ApplyRatingMod(CR_HIT_MELEE, enchant_amount, apply); + ApplyRatingMod(CR_HIT_RANGED, enchant_amount, apply); + ApplyRatingMod(CR_HIT_SPELL, enchant_amount, apply); + DEBUG_LOG("+ %u HIT", enchant_amount); + break; + case ITEM_MOD_CRIT_RATING: + ApplyRatingMod(CR_CRIT_MELEE, enchant_amount, apply); + ApplyRatingMod(CR_CRIT_RANGED, enchant_amount, apply); + ApplyRatingMod(CR_CRIT_SPELL, enchant_amount, apply); + DEBUG_LOG("+ %u CRITICAL", enchant_amount); + break; + case ITEM_MOD_RESILIENCE_RATING: + ApplyRatingMod(CR_RESILIENCE_DAMAGE_TAKEN, enchant_amount, apply); + DEBUG_LOG("+ %u RESILIENCE", enchant_amount); + break; + case ITEM_MOD_HASTE_RATING: + ApplyRatingMod(CR_HASTE_MELEE, enchant_amount, apply); + ApplyRatingMod(CR_HASTE_RANGED, enchant_amount, apply); + ApplyRatingMod(CR_HASTE_SPELL, enchant_amount, apply); + DEBUG_LOG("+ %u HASTE", enchant_amount); + break; + case ITEM_MOD_EXPERTISE_RATING: + ApplyRatingMod(CR_EXPERTISE, enchant_amount, apply); + DEBUG_LOG("+ %u EXPERTISE", enchant_amount); + break; + case ITEM_MOD_ATTACK_POWER: + HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(enchant_amount), apply); + HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(enchant_amount), apply); + DEBUG_LOG("+ %u ATTACK_POWER", enchant_amount); + break; + case ITEM_MOD_RANGED_ATTACK_POWER: + HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(enchant_amount), apply); + DEBUG_LOG("+ %u RANGED_ATTACK_POWER", enchant_amount); + break; + case ITEM_MOD_SPELL_POWER: + ApplySpellPowerBonus(enchant_amount, apply); + DEBUG_LOG("+ %u SPELL_POWER", enchant_amount); + break; + case ITEM_MOD_HEALTH_REGEN: + ApplyHealthRegenBonus(enchant_amount, apply); + DEBUG_LOG("+ %u HEALTH_REGENERATION", enchant_amount); + break; + case ITEM_MOD_SPELL_PENETRATION: + ApplyModInt32Value(PLAYER_FIELD_MOD_TARGET_RESISTANCE, -int32(enchant_amount), apply); + m_spellPenetrationItemMod += apply ? enchant_amount : -int32(enchant_amount); + DEBUG_LOG("+ %u SPELL_PENETRATION", -int32(enchant_amount)); + break; + case ITEM_MOD_MASTERY_RATING: + ApplyRatingMod(CR_MASTERY, enchant_amount, apply); + DEBUG_LOG("+ %u MASTERY_RATING", enchant_amount); + break; + // deprecated + case ITEM_MOD_DEFENSE_SKILL_RATING: + case ITEM_MOD_BLOCK_RATING: + case ITEM_MOD_HIT_MELEE_RATING: + case ITEM_MOD_HIT_RANGED_RATING: + case ITEM_MOD_HIT_SPELL_RATING: + case ITEM_MOD_CRIT_MELEE_RATING: + case ITEM_MOD_CRIT_SPELL_RATING: + case ITEM_MOD_HIT_TAKEN_MELEE_RATING: + case ITEM_MOD_HIT_TAKEN_RANGED_RATING: + case ITEM_MOD_HIT_TAKEN_SPELL_RATING: + case ITEM_MOD_CRIT_TAKEN_MELEE_RATING: + case ITEM_MOD_CRIT_TAKEN_RANGED_RATING: + case ITEM_MOD_CRIT_TAKEN_SPELL_RATING: + case ITEM_MOD_HASTE_MELEE_RATING: + case ITEM_MOD_HASTE_RANGED_RATING: + case ITEM_MOD_HASTE_SPELL_RATING: + case ITEM_MOD_HIT_TAKEN_RATING: + case ITEM_MOD_CRIT_TAKEN_RATING: + case ITEM_MOD_FERAL_ATTACK_POWER: + case ITEM_MOD_SPELL_HEALING_DONE: + case ITEM_MOD_SPELL_DAMAGE_DONE: + case ITEM_MOD_MANA_REGENERATION: + case ITEM_MOD_ARMOR_PENETRATION_RATING: + case ITEM_MOD_BLOCK_VALUE: + default: + break; + } + break; + } + case ITEM_ENCHANTMENT_TYPE_TOTEM: // Shaman Rockbiter Weapon + { + if (getClass() == CLASS_SHAMAN) + { + float addValue = 0.0f; + if (item->GetSlot() == EQUIPMENT_SLOT_MAINHAND) + { + addValue = float(enchant_amount * item->GetProto()->Delay / 1000.0f); + HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_VALUE, addValue, apply); + } + else if (item->GetSlot() == EQUIPMENT_SLOT_OFFHAND) + { + addValue = float(enchant_amount * item->GetProto()->Delay / 1000.0f); + HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_VALUE, addValue, apply); + } + } + break; + } + case ITEM_ENCHANTMENT_TYPE_USE_SPELL: + // processed in Player::CastItemUseSpell + break; + case ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET: + // nothing do.. + break; + default: + sLog.outError("Unknown item enchantment (id = %d) display type: %d", enchant_id, enchant_display_type); + break; + } /*switch(enchant_display_type)*/ + } /*for*/ + } + + // visualize enchantment at player and equipped items + if (slot == PERM_ENCHANTMENT_SLOT) + SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (item->GetSlot() * 2), 0, apply ? item->GetEnchantmentId(slot) : 0); + + if (slot == TEMP_ENCHANTMENT_SLOT) + SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (item->GetSlot() * 2), 1, apply ? item->GetEnchantmentId(slot) : 0); + + if (apply_dur) + { + if (apply) + { + // set duration + uint32 duration = item->GetEnchantmentDuration(slot); + if (duration > 0) + AddEnchantmentDuration(item, slot, duration); + } + else + { + // duration == 0 will remove EnchantDuration + AddEnchantmentDuration(item, slot, 0); + } + } +} + +void Player::ApplyReforgeEnchantment(Item* item, bool apply) +{ + + if (!item) + return; + + ItemReforgeEntry const* reforge = sItemReforgeStore.LookupEntry(item->GetEnchantmentId(REFORGE_ENCHANTMENT_SLOT)); + if (!reforge) + return; + + float removeValue = item->GetReforgableStat(ItemModType(reforge->SourceStat)) * reforge->SourceMultiplier; + + float addValue = removeValue * reforge->FinalMultiplier; + + switch (reforge->SourceStat) + { + case ITEM_MOD_HEALTH: + HandleStatModifier(UNIT_MOD_HEALTH, BASE_VALUE, -removeValue, apply); + break; + case ITEM_MOD_AGILITY: + HandleStatModifier(UNIT_MOD_STAT_AGILITY, TOTAL_VALUE, -removeValue, apply); + ApplyStatBuffMod(STAT_AGILITY, -removeValue, apply); + break; + case ITEM_MOD_STRENGTH: + HandleStatModifier(UNIT_MOD_STAT_STRENGTH, TOTAL_VALUE, -removeValue, apply); + ApplyStatBuffMod(STAT_STRENGTH, -removeValue, apply); + break; + case ITEM_MOD_INTELLECT: + HandleStatModifier(UNIT_MOD_STAT_INTELLECT, TOTAL_VALUE, -removeValue, apply); + ApplyStatBuffMod(STAT_INTELLECT, -removeValue, apply); + break; + case ITEM_MOD_SPIRIT: + HandleStatModifier(UNIT_MOD_STAT_SPIRIT, TOTAL_VALUE, -removeValue, apply); + ApplyStatBuffMod(STAT_SPIRIT, -removeValue, apply); + break; + case ITEM_MOD_STAMINA: + HandleStatModifier(UNIT_MOD_STAT_STAMINA, TOTAL_VALUE, -removeValue, apply); + ApplyStatBuffMod(STAT_STAMINA, -removeValue, apply); + break; + case ITEM_MOD_DEFENSE_SKILL_RATING: + ApplyRatingMod(CR_DEFENSE_SKILL, -int32(removeValue), apply); + break; + case ITEM_MOD_DODGE_RATING: + ApplyRatingMod(CR_DODGE, -int32(removeValue), apply); + break; + case ITEM_MOD_PARRY_RATING: + ApplyRatingMod(CR_PARRY, -int32(removeValue), apply); + break; + case ITEM_MOD_BLOCK_RATING: + ApplyRatingMod(CR_BLOCK, -int32(removeValue), apply); + break; + case ITEM_MOD_HIT_MELEE_RATING: + ApplyRatingMod(CR_HIT_MELEE, -int32(removeValue), apply); + break; + case ITEM_MOD_HIT_RANGED_RATING: + ApplyRatingMod(CR_HIT_RANGED, -int32(removeValue), apply); + break; + case ITEM_MOD_HIT_SPELL_RATING: + ApplyRatingMod(CR_HIT_SPELL, -int32(removeValue), apply); + break; + case ITEM_MOD_CRIT_MELEE_RATING: + ApplyRatingMod(CR_CRIT_MELEE, -int32(removeValue), apply); + break; + case ITEM_MOD_CRIT_RANGED_RATING: + ApplyRatingMod(CR_CRIT_RANGED, -int32(removeValue), apply); + break; + case ITEM_MOD_CRIT_SPELL_RATING: + ApplyRatingMod(CR_CRIT_SPELL, -int32(removeValue), apply); + break; + case ITEM_MOD_HASTE_SPELL_RATING: + ApplyRatingMod(CR_HASTE_SPELL, -int32(removeValue), apply); + break; + case ITEM_MOD_HIT_RATING: + ApplyRatingMod(CR_HIT_MELEE, -int32(removeValue), apply); + ApplyRatingMod(CR_HIT_RANGED, -int32(removeValue), apply); + ApplyRatingMod(CR_HIT_SPELL, -int32(removeValue), apply); + break; + case ITEM_MOD_CRIT_RATING: + ApplyRatingMod(CR_CRIT_MELEE, -int32(removeValue), apply); + ApplyRatingMod(CR_CRIT_RANGED, -int32(removeValue), apply); + ApplyRatingMod(CR_CRIT_SPELL, -int32(removeValue), apply); + break; + case ITEM_MOD_RESILIENCE_RATING: + ApplyRatingMod(CR_RESILIENCE_DAMAGE_TAKEN, -int32(removeValue), apply); + break; + case ITEM_MOD_HASTE_RATING: + ApplyRatingMod(CR_HASTE_MELEE, -int32(removeValue), apply); + ApplyRatingMod(CR_HASTE_RANGED, -int32(removeValue), apply); + ApplyRatingMod(CR_HASTE_SPELL, -int32(removeValue), apply); + break; + case ITEM_MOD_EXPERTISE_RATING: + ApplyRatingMod(CR_EXPERTISE, -int32(removeValue), apply); + break; + case ITEM_MOD_ATTACK_POWER: + HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, -removeValue, apply); + HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, -removeValue, apply); + break; + case ITEM_MOD_RANGED_ATTACK_POWER: + HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, -removeValue, apply); + break; + case ITEM_MOD_MANA_REGENERATION: + ApplyManaRegenBonus(-int32(removeValue), apply); + break; + case ITEM_MOD_ARMOR_PENETRATION_RATING: + ApplyRatingMod(CR_ARMOR_PENETRATION, -int32(removeValue), apply); + break; + case ITEM_MOD_SPELL_POWER: + ApplySpellPowerBonus(-int32(removeValue), apply); + break; + case ITEM_MOD_HEALTH_REGEN: + ApplyHealthRegenBonus(-int32(removeValue), apply); + break; + case ITEM_MOD_SPELL_PENETRATION: + ApplyModInt32Value(PLAYER_FIELD_MOD_TARGET_RESISTANCE, -int32(removeValue), apply); + m_spellPenetrationItemMod += apply ? -int32(removeValue) : int32(removeValue); + break; + case ITEM_MOD_BLOCK_VALUE: + break; + case ITEM_MOD_MASTERY_RATING: + ApplyRatingMod(CR_MASTERY, -int32(removeValue), apply); + break; + + } + + switch (reforge->FinalStat) + { + case ITEM_MOD_HEALTH: + HandleStatModifier(UNIT_MOD_HEALTH, BASE_VALUE, addValue, apply); + break; + case ITEM_MOD_AGILITY: + HandleStatModifier(UNIT_MOD_STAT_AGILITY, TOTAL_VALUE, addValue, apply); + ApplyStatBuffMod(STAT_AGILITY, addValue, apply); + break; + case ITEM_MOD_STRENGTH: + HandleStatModifier(UNIT_MOD_STAT_STRENGTH, TOTAL_VALUE, addValue, apply); + ApplyStatBuffMod(STAT_STRENGTH, addValue, apply); + break; + case ITEM_MOD_INTELLECT: + HandleStatModifier(UNIT_MOD_STAT_INTELLECT, TOTAL_VALUE, addValue, apply); + ApplyStatBuffMod(STAT_INTELLECT, addValue, apply); + break; + case ITEM_MOD_SPIRIT: + HandleStatModifier(UNIT_MOD_STAT_SPIRIT, TOTAL_VALUE, addValue, apply); + ApplyStatBuffMod(STAT_SPIRIT, addValue, apply); + break; + case ITEM_MOD_STAMINA: + HandleStatModifier(UNIT_MOD_STAT_STAMINA, TOTAL_VALUE, addValue, apply); + ApplyStatBuffMod(STAT_STAMINA, addValue, apply); + break; + case ITEM_MOD_DEFENSE_SKILL_RATING: + ApplyRatingMod(CR_DEFENSE_SKILL, int32(addValue), apply); + break; + case ITEM_MOD_DODGE_RATING: + ApplyRatingMod(CR_DODGE, int32(addValue), apply); + break; + case ITEM_MOD_PARRY_RATING: + ApplyRatingMod(CR_PARRY, int32(addValue), apply); + break; + case ITEM_MOD_BLOCK_RATING: + ApplyRatingMod(CR_BLOCK, int32(addValue), apply); + break; + case ITEM_MOD_HIT_MELEE_RATING: + ApplyRatingMod(CR_HIT_MELEE, int32(addValue), apply); + break; + case ITEM_MOD_HIT_RANGED_RATING: + ApplyRatingMod(CR_HIT_RANGED, int32(addValue), apply); + break; + case ITEM_MOD_HIT_SPELL_RATING: + ApplyRatingMod(CR_HIT_SPELL, int32(addValue), apply); + break; + case ITEM_MOD_CRIT_MELEE_RATING: + ApplyRatingMod(CR_CRIT_MELEE, int32(addValue), apply); + break; + case ITEM_MOD_CRIT_RANGED_RATING: + ApplyRatingMod(CR_CRIT_RANGED, int32(addValue), apply); + break; + case ITEM_MOD_CRIT_SPELL_RATING: + ApplyRatingMod(CR_CRIT_SPELL, int32(addValue), apply); + break; + case ITEM_MOD_HASTE_SPELL_RATING: + ApplyRatingMod(CR_HASTE_SPELL, int32(addValue), apply); + break; + case ITEM_MOD_HIT_RATING: + ApplyRatingMod(CR_HIT_MELEE, int32(addValue), apply); + ApplyRatingMod(CR_HIT_RANGED, int32(addValue), apply); + ApplyRatingMod(CR_HIT_SPELL, int32(addValue), apply); + break; + case ITEM_MOD_CRIT_RATING: + ApplyRatingMod(CR_CRIT_MELEE, int32(addValue), apply); + ApplyRatingMod(CR_CRIT_RANGED, int32(addValue), apply); + ApplyRatingMod(CR_CRIT_SPELL, int32(addValue), apply); + break; + case ITEM_MOD_RESILIENCE_RATING: + ApplyRatingMod(CR_RESILIENCE_DAMAGE_TAKEN, int32(addValue), apply); + break; + case ITEM_MOD_HASTE_RATING: + ApplyRatingMod(CR_HASTE_MELEE, int32(addValue), apply); + ApplyRatingMod(CR_HASTE_RANGED, int32(addValue), apply); + ApplyRatingMod(CR_HASTE_SPELL, int32(addValue), apply); + break; + case ITEM_MOD_EXPERTISE_RATING: + ApplyRatingMod(CR_EXPERTISE, int32(addValue), apply); + break; + case ITEM_MOD_ATTACK_POWER: + HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, addValue, apply); + HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, addValue, apply); + break; + case ITEM_MOD_RANGED_ATTACK_POWER: + HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, addValue, apply); + break; + case ITEM_MOD_MANA_REGENERATION: + ApplyManaRegenBonus(int32(addValue), apply); + break; + case ITEM_MOD_ARMOR_PENETRATION_RATING: + ApplyRatingMod(CR_ARMOR_PENETRATION, int32(addValue), apply); + break; + case ITEM_MOD_SPELL_POWER: + ApplySpellPowerBonus(int32(addValue), apply); + break; + case ITEM_MOD_HEALTH_REGEN: + ApplyHealthRegenBonus(int32(addValue), apply); + break; + case ITEM_MOD_SPELL_PENETRATION: + ApplyModInt32Value(PLAYER_FIELD_MOD_TARGET_RESISTANCE, int32(addValue), apply); + m_spellPenetrationItemMod += apply ? int32(addValue) : -int32(addValue); + break; + case ITEM_MOD_BLOCK_VALUE: + break; + case ITEM_MOD_MASTERY_RATING: + ApplyRatingMod(CR_MASTERY, int32(addValue), apply); + break; + } +} + +void Player::SendEnchantmentDurations() +{ + for (EnchantDurationList::const_iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr) + { + GetSession()->SendItemEnchantTimeUpdate(GetObjectGuid(), itr->item->GetObjectGuid(), itr->slot, uint32(itr->leftduration) / 1000); + } +} + +void Player::SendItemDurations() +{ + for (ItemDurationList::const_iterator itr = m_itemDuration.begin(); itr != m_itemDuration.end(); ++itr) + { + (*itr)->SendTimeUpdate(this); + } +} + +void Player::SendNewItem(Item* item, uint32 count, bool received, bool created, bool broadcast) +{ + if (!item) // prevent crash + return; + + // last check 2.0.10 + WorldPacket data(SMSG_ITEM_PUSH_RESULT, (8 + 4 + 4 + 4 + 1 + 4 + 4 + 4 + 4 + 4)); + data << GetObjectGuid(); // player GUID + data << uint32(received); // 0=looted, 1=from npc + data << uint32(created); // 0=received, 1=created + data << uint32(1); // IsShowChatMessage + data << uint8(item->GetBagSlot()); // bagslot + // item slot, but when added to stack: 0xFFFFFFFF + data << uint32((item->GetCount() == count) ? item->GetSlot() : -1); + data << uint32(item->GetEntry()); // item id + data << uint32(item->GetItemSuffixFactor()); // SuffixFactor + data << uint32(item->GetItemRandomPropertyId()); // random item property id + data << uint32(count); // count of items + data << uint32(GetItemCount(item->GetEntry())); // count of items in inventory + + if (broadcast && GetGroup()) + GetGroup()->BroadcastPacket(&data, true); + else + GetSession()->SendPacket(&data); +} + +/*********************************************************/ +/*** GOSSIP SYSTEM ***/ +/*********************************************************/ + +void Player::PrepareGossipMenu(WorldObject* pSource, uint32 menuId) +{ + PlayerMenu* pMenu = PlayerTalkClass; + pMenu->ClearMenus(); + + pMenu->GetGossipMenu().SetMenuId(menuId); + + GossipMenuItemsMapBounds pMenuItemBounds = sObjectMgr.GetGossipMenuItemsMapBounds(menuId); + + // prepares quest menu when true + bool canSeeQuests = menuId == GetDefaultGossipMenuForSource(pSource); + + // if canSeeQuests (the default, top level menu) and no menu options exist for this, use options from default options + if (pMenuItemBounds.first == pMenuItemBounds.second && canSeeQuests) + pMenuItemBounds = sObjectMgr.GetGossipMenuItemsMapBounds(0); + + for (GossipMenuItemsMap::const_iterator itr = pMenuItemBounds.first; itr != pMenuItemBounds.second; ++itr) + { + bool hasMenuItem = true; + bool isGMSkipConditionCheck = false; + + if (itr->second.conditionId && !sObjectMgr.IsPlayerMeetToCondition(itr->second.conditionId, this, GetMap(), pSource, CONDITION_FROM_GOSSIP_OPTION)) + { + if (isGameMaster()) // Let GM always see menu items regardless of conditions + isGMSkipConditionCheck = true; + else + { + if (itr->second.option_id == GOSSIP_OPTION_QUESTGIVER) + canSeeQuests = false; + continue; // Skip this option + } + } + + if (pSource->GetTypeId() == TYPEID_UNIT) + { + Creature* pCreature = (Creature*)pSource; + + uint32 npcflags = pCreature->GetUInt32Value(UNIT_NPC_FLAGS); + + if (!(itr->second.npc_option_npcflag & npcflags)) + continue; + + switch (itr->second.option_id) + { + case GOSSIP_OPTION_GOSSIP: + break; + case GOSSIP_OPTION_QUESTGIVER: + hasMenuItem = false; + break; + case GOSSIP_OPTION_ARMORER: + hasMenuItem = false; // added in special mode + break; + case GOSSIP_OPTION_SPIRITHEALER: + if (!isDead()) + hasMenuItem = false; + break; + case GOSSIP_OPTION_VENDOR: + { + VendorItemData const* vItems = pCreature->GetVendorItems(); + VendorItemData const* tItems = pCreature->GetVendorTemplateItems(); + if ((!vItems || vItems->Empty()) && (!tItems || tItems->Empty())) + { + sLog.outErrorDb("Creature %u (Entry: %u) have UNIT_NPC_FLAG_VENDOR but have empty trading item list.", pCreature->GetGUIDLow(), pCreature->GetEntry()); + hasMenuItem = false; + } + break; + } + case GOSSIP_OPTION_TRAINER: + // pet trainers not have spells in fact now + /* FIXME: gossip menu with single unlearn pet talents option not show by some reason + if (pCreature->GetCreatureInfo()->trainer_type == TRAINER_TYPE_PETS) + hasMenuItem = false; + else */ + if (!pCreature->IsTrainerOf(this, false)) + hasMenuItem = false; + break; + case GOSSIP_OPTION_UNLEARNTALENTS: + if (!pCreature->CanTrainAndResetTalentsOf(this)) + hasMenuItem = false; + break; + case GOSSIP_OPTION_UNLEARNPETSKILLS: + if (pCreature->GetCreatureInfo()->trainer_type != TRAINER_TYPE_PETS || pCreature->GetCreatureInfo()->trainer_class != CLASS_HUNTER) + hasMenuItem = false; + else if (Pet* pet = GetPet()) + { + if (pet->getPetType() != HUNTER_PET || pet->m_spells.size() <= 1) + hasMenuItem = false; + } + else + hasMenuItem = false; + break; + case GOSSIP_OPTION_TAXIVENDOR: + if (GetSession()->SendLearnNewTaxiNode(pCreature)) + return; + break; + case GOSSIP_OPTION_BATTLEFIELD: + if (!pCreature->CanInteractWithBattleMaster(this, false)) + hasMenuItem = false; + break; + case GOSSIP_OPTION_STABLEPET: + if (getClass() != CLASS_HUNTER) + hasMenuItem = false; + break; + case GOSSIP_OPTION_SPIRITGUIDE: + case GOSSIP_OPTION_INNKEEPER: + case GOSSIP_OPTION_BANKER: + case GOSSIP_OPTION_PETITIONER: + case GOSSIP_OPTION_TABARDDESIGNER: + case GOSSIP_OPTION_AUCTIONEER: + case GOSSIP_OPTION_MAILBOX: + break; // no checks + default: + sLog.outErrorDb("Creature entry %u have unknown gossip option %u for menu %u", pCreature->GetEntry(), itr->second.option_id, itr->second.menu_id); + hasMenuItem = false; + break; + } + } + else if (pSource->GetTypeId() == TYPEID_GAMEOBJECT) + { + GameObject* pGo = (GameObject*)pSource; + + switch (itr->second.option_id) + { + case GOSSIP_OPTION_QUESTGIVER: + hasMenuItem = false; + break; + case GOSSIP_OPTION_GOSSIP: + if (pGo->GetGoType() != GAMEOBJECT_TYPE_QUESTGIVER && pGo->GetGoType() != GAMEOBJECT_TYPE_GOOBER) + hasMenuItem = false; + break; + default: + hasMenuItem = false; + break; + } + } + + if (hasMenuItem) + { + std::string strOptionText = itr->second.option_text; + std::string strBoxText = itr->second.box_text; + + int loc_idx = GetSession()->GetSessionDbLocaleIndex(); + + if (loc_idx >= 0) + { + uint32 idxEntry = MAKE_PAIR32(menuId, itr->second.id); + + if (GossipMenuItemsLocale const* no = sObjectMgr.GetGossipMenuItemsLocale(idxEntry)) + { + if (no->OptionText.size() > (size_t)loc_idx && !no->OptionText[loc_idx].empty()) + strOptionText = no->OptionText[loc_idx]; + + if (no->BoxText.size() > (size_t)loc_idx && !no->BoxText[loc_idx].empty()) + strBoxText = no->BoxText[loc_idx]; + } + } + + if (isGMSkipConditionCheck) + { + strOptionText.append(" ("); + strOptionText.append(GetSession()->GetMangosString(LANG_GM_ON)); + strOptionText.append(")"); + } + + pMenu->GetGossipMenu().AddMenuItem(itr->second.option_icon, strOptionText, 0, itr->second.option_id, strBoxText, itr->second.box_money, itr->second.box_coded); + pMenu->GetGossipMenu().AddGossipMenuItemData(itr->second.action_menu_id, itr->second.action_poi_id, itr->second.action_script_id); + } + } + + if (canSeeQuests) + PrepareQuestMenu(pSource->GetObjectGuid()); + + // some gossips aren't handled in normal way ... so we need to do it this way .. TODO: handle it in normal way ;-) + /*if (pMenu->Empty()) + { + if (pCreature->HasFlag(UNIT_NPC_FLAGS,UNIT_NPC_FLAG_TRAINER)) + { + // output error message if need + pCreature->IsTrainerOf(this, true); + } + + if (pCreature->HasFlag(UNIT_NPC_FLAGS,UNIT_NPC_FLAG_BATTLEMASTER)) + { + // output error message if need + pCreature->CanInteractWithBattleMaster(this, true); + } + }*/ +} + +void Player::SendPreparedGossip(WorldObject* pSource) +{ + if (!pSource) + return; + + if (pSource->GetTypeId() == TYPEID_UNIT) + { + // in case no gossip flag and quest menu not empty, open quest menu (client expect gossip menu with this flag) + if (!((Creature*)pSource)->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP) && !PlayerTalkClass->GetQuestMenu().Empty()) + { + SendPreparedQuest(pSource->GetObjectGuid()); + return; + } + } + else if (pSource->GetTypeId() == TYPEID_GAMEOBJECT) + { + // probably need to find a better way here + if (!PlayerTalkClass->GetGossipMenu().GetMenuId() && !PlayerTalkClass->GetQuestMenu().Empty()) + { + SendPreparedQuest(pSource->GetObjectGuid()); + return; + } + } + + // in case non empty gossip menu (that not included quests list size) show it + // (quest entries from quest menu will be included in list) + + uint32 textId = GetGossipTextId(pSource); + + if (uint32 menuId = PlayerTalkClass->GetGossipMenu().GetMenuId()) + textId = GetGossipTextId(menuId, pSource); + + PlayerTalkClass->SendGossipMenu(textId, pSource->GetObjectGuid()); +} + +void Player::OnGossipSelect(WorldObject* pSource, uint32 gossipListId, uint32 menuId) +{ + GossipMenu& gossipmenu = PlayerTalkClass->GetGossipMenu(); + + if (gossipListId >= gossipmenu.MenuItemCount()) + return; + + // if not same, then something funky is going on + if (menuId != gossipmenu.GetMenuId()) + return; + + GossipMenuItem const& menu_item = gossipmenu.GetItem(gossipListId); + + uint32 gossipOptionId = menu_item.m_gOptionId; + ObjectGuid guid = pSource->GetObjectGuid(); + uint32 moneyTake = menu_item.m_gBoxMoney; + + // if this function called and player have money for pay MoneyTake or cheating, proccess both cases + if (moneyTake > 0) + { + if (GetMoney() >= moneyTake) + ModifyMoney(-int64(moneyTake)); + else + return; // cheating + } + + if (pSource->GetTypeId() == TYPEID_GAMEOBJECT) + { + if (gossipOptionId > GOSSIP_OPTION_QUESTGIVER) + { + sLog.outError("Player guid %u request invalid gossip option for GameObject entry %u", GetGUIDLow(), pSource->GetEntry()); + return; + } + } + + GossipMenuItemData pMenuData = gossipmenu.GetItemData(gossipListId); + + switch (gossipOptionId) + { + case GOSSIP_OPTION_GOSSIP: + { + if (pMenuData.m_gAction_poi) + PlayerTalkClass->SendPointOfInterest(pMenuData.m_gAction_poi); + + // send new menu || close gossip || stay at current menu + if (pMenuData.m_gAction_menu > 0) + { + PrepareGossipMenu(pSource, uint32(pMenuData.m_gAction_menu)); + SendPreparedGossip(pSource); + } + else if (pMenuData.m_gAction_menu < 0) + { + PlayerTalkClass->CloseGossip(); + TalkedToCreature(pSource->GetEntry(), pSource->GetObjectGuid()); + } + + break; + } + case GOSSIP_OPTION_SPIRITHEALER: + if (isDead()) + ((Creature*)pSource)->CastSpell(((Creature*)pSource), 17251, true, NULL, NULL, GetObjectGuid()); + break; + case GOSSIP_OPTION_QUESTGIVER: + PrepareQuestMenu(guid); + SendPreparedQuest(guid); + break; + case GOSSIP_OPTION_VENDOR: + case GOSSIP_OPTION_ARMORER: + GetSession()->SendListInventory(guid); + break; + case GOSSIP_OPTION_STABLEPET: + GetSession()->SendStablePet(guid); + break; + case GOSSIP_OPTION_TRAINER: + GetSession()->SendTrainerList(guid); + break; + case GOSSIP_OPTION_UNLEARNTALENTS: + PlayerTalkClass->CloseGossip(); + SendTalentWipeConfirm(guid); + break; + case GOSSIP_OPTION_UNLEARNPETSKILLS: + PlayerTalkClass->CloseGossip(); + SendPetSkillWipeConfirm(); + break; + case GOSSIP_OPTION_TAXIVENDOR: + GetSession()->SendTaxiMenu(((Creature*)pSource)); + break; + case GOSSIP_OPTION_INNKEEPER: + PlayerTalkClass->CloseGossip(); + SetBindPoint(guid); + break; + case GOSSIP_OPTION_BANKER: + GetSession()->SendShowBank(guid); + break; + case GOSSIP_OPTION_PETITIONER: + PlayerTalkClass->CloseGossip(); + GetSession()->SendPetitionShowList(guid); + break; + case GOSSIP_OPTION_TABARDDESIGNER: + PlayerTalkClass->CloseGossip(); + GetSession()->SendTabardVendorActivate(guid); + break; + case GOSSIP_OPTION_AUCTIONEER: + GetSession()->SendAuctionHello(((Creature*)pSource)); + break; + case GOSSIP_OPTION_MAILBOX: + PlayerTalkClass->CloseGossip(); + GetSession()->SendShowMailBox(guid); + break; + case GOSSIP_OPTION_SPIRITGUIDE: + PrepareGossipMenu(pSource); + SendPreparedGossip(pSource); + break; + case GOSSIP_OPTION_BATTLEFIELD: + { + BattleGroundTypeId bgTypeId = sBattleGroundMgr.GetBattleMasterBG(pSource->GetEntry()); + + if (bgTypeId == BATTLEGROUND_TYPE_NONE) + { + sLog.outError("a user (guid %u) requested battlegroundlist from a npc who is no battlemaster", GetGUIDLow()); + return; + } + + GetSession()->SendBattlegGroundList(guid, bgTypeId); + break; + } + } + + if (pMenuData.m_gAction_script) + { + if (pSource->GetTypeId() == TYPEID_UNIT) + GetMap()->ScriptsStart(sGossipScripts, pMenuData.m_gAction_script, pSource, this, Map::SCRIPT_EXEC_PARAM_UNIQUE_BY_SOURCE); + else if (pSource->GetTypeId() == TYPEID_GAMEOBJECT) + GetMap()->ScriptsStart(sGossipScripts, pMenuData.m_gAction_script, this, pSource, Map::SCRIPT_EXEC_PARAM_UNIQUE_BY_TARGET); + } +} + +uint32 Player::GetGossipTextId(WorldObject* pSource) +{ + if (!pSource || pSource->GetTypeId() != TYPEID_UNIT) + return DEFAULT_GOSSIP_MESSAGE; + + if (uint32 pos = sObjectMgr.GetNpcGossip(((Creature*)pSource)->GetGUIDLow())) + return pos; + + return DEFAULT_GOSSIP_MESSAGE; +} + +uint32 Player::GetGossipTextId(uint32 menuId, WorldObject* pSource) +{ + uint32 textId = DEFAULT_GOSSIP_MESSAGE; + + if (!menuId) + return textId; + + uint32 scriptId = 0; + uint32 lastConditionId = 0; + + GossipMenusMapBounds pMenuBounds = sObjectMgr.GetGossipMenusMapBounds(menuId); + for (GossipMenusMap::const_iterator itr = pMenuBounds.first; itr != pMenuBounds.second; ++itr) + { + // Take the text that has the highest conditionId of all fitting + // No condition and no text with condition found OR higher and fitting condition found + if ((!itr->second.conditionId && !lastConditionId) || + (itr->second.conditionId > lastConditionId && sObjectMgr.IsPlayerMeetToCondition(itr->second.conditionId, this, GetMap(), pSource, CONDITION_FROM_GOSSIP_MENU))) + { + lastConditionId = itr->second.conditionId; + textId = itr->second.text_id; + scriptId = itr->second.script_id; + } + } + + // Start related script + if (scriptId) + GetMap()->ScriptsStart(sGossipScripts, scriptId, this, pSource, Map::SCRIPT_EXEC_PARAM_UNIQUE_BY_TARGET); + + return textId; +} + +uint32 Player::GetDefaultGossipMenuForSource(WorldObject* pSource) +{ + if (pSource->GetTypeId() == TYPEID_UNIT) + return ((Creature*)pSource)->GetCreatureInfo()->GossipMenuId; + else if (pSource->GetTypeId() == TYPEID_GAMEOBJECT) + return ((GameObject*)pSource)->GetGOInfo()->GetGossipMenuId(); + + return 0; +} + +/*********************************************************/ +/*** QUEST SYSTEM ***/ +/*********************************************************/ + +void Player::PrepareQuestMenu(ObjectGuid guid) +{ + QuestRelationsMapBounds rbounds; + QuestRelationsMapBounds irbounds; + + // pets also can have quests + if (Creature* pCreature = GetMap()->GetAnyTypeCreature(guid)) + { + rbounds = sObjectMgr.GetCreatureQuestRelationsMapBounds(pCreature->GetEntry()); + irbounds = sObjectMgr.GetCreatureQuestInvolvedRelationsMapBounds(pCreature->GetEntry()); + } + else + { + // we should obtain map pointer from GetMap() in 99% of cases. Special case + // only for quests which cast teleport spells on player + Map* _map = IsInWorld() ? GetMap() : sMapMgr.FindMap(GetMapId(), GetInstanceId()); + MANGOS_ASSERT(_map); + + if (GameObject* pGameObject = _map->GetGameObject(guid)) + { + rbounds = sObjectMgr.GetGOQuestRelationsMapBounds(pGameObject->GetEntry()); + irbounds = sObjectMgr.GetGOQuestInvolvedRelationsMapBounds(pGameObject->GetEntry()); + } + else + return; + } + + QuestMenu& qm = PlayerTalkClass->GetQuestMenu(); + qm.ClearMenu(); + + for (QuestRelationsMap::const_iterator itr = irbounds.first; itr != irbounds.second; ++itr) + { + uint32 quest_id = itr->second; + + Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id); + + if (!pQuest || !pQuest->IsActive()) + continue; + + QuestStatus status = GetQuestStatus(quest_id); + + if (status == QUEST_STATUS_COMPLETE && !GetQuestRewardStatus(quest_id)) + qm.AddMenuItem(quest_id, 4); + else if (status == QUEST_STATUS_INCOMPLETE) + qm.AddMenuItem(quest_id, 4); + else if (status == QUEST_STATUS_AVAILABLE) + qm.AddMenuItem(quest_id, 2); + } + + for (QuestRelationsMap::const_iterator itr = rbounds.first; itr != rbounds.second; ++itr) + { + uint32 quest_id = itr->second; + + Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id); + + if (!pQuest || !pQuest->IsActive()) + continue; + + QuestStatus status = GetQuestStatus(quest_id); + + if (pQuest->IsAutoComplete() && CanTakeQuest(pQuest, false)) + qm.AddMenuItem(quest_id, 4); + else if (status == QUEST_STATUS_NONE && CanTakeQuest(pQuest, false)) + qm.AddMenuItem(quest_id, 2); + } +} + +void Player::SendPreparedQuest(ObjectGuid guid) +{ + QuestMenu& questMenu = PlayerTalkClass->GetQuestMenu(); + + if (questMenu.Empty()) + return; + + QuestMenuItem const& qmi0 = questMenu.GetItem(0); + + uint32 icon = qmi0.m_qIcon; + + // single element case + if (questMenu.MenuItemCount() == 1) + { + // Auto open -- maybe also should verify there is no greeting + uint32 quest_id = qmi0.m_qId; + Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id); + + if (pQuest) + { + if (icon == 4 && !GetQuestRewardStatus(quest_id)) + PlayerTalkClass->SendQuestGiverRequestItems(pQuest, guid, CanRewardQuest(pQuest, false), true); + else if (icon == 4) + PlayerTalkClass->SendQuestGiverRequestItems(pQuest, guid, CanRewardQuest(pQuest, false), true); + // Send completable on repeatable and autoCompletable quest if player don't have quest + // TODO: verify if check for !pQuest->IsDaily() is really correct (possibly not) + else if (pQuest->IsAutoComplete() && pQuest->IsRepeatable() && !pQuest->IsDailyOrWeekly()) + PlayerTalkClass->SendQuestGiverRequestItems(pQuest, guid, CanCompleteRepeatableQuest(pQuest), true); + else + PlayerTalkClass->SendQuestGiverQuestDetails(pQuest, guid, true); + } + } + // multiply entries + else + { + QEmote qe; + qe._Delay = 0; + qe._Emote = 0; + std::string title = ""; + + // need pet case for some quests + if (Creature* pCreature = GetMap()->GetAnyTypeCreature(guid)) + { + uint32 textid = GetGossipTextId(pCreature); + + GossipText const* gossiptext = sObjectMgr.GetGossipText(textid); + if (!gossiptext) + { + qe._Delay = 0; // TEXTEMOTE_MESSAGE; // zyg: player emote + qe._Emote = 0; // TEXTEMOTE_HELLO; // zyg: NPC emote + title = ""; + } + else + { + qe = gossiptext->Options[0].Emotes[0]; + + int loc_idx = GetSession()->GetSessionDbLocaleIndex(); + + std::string title0 = gossiptext->Options[0].Text_0; + std::string title1 = gossiptext->Options[0].Text_1; + sObjectMgr.GetNpcTextLocaleStrings0(textid, loc_idx, &title0, &title1); + + title = !title0.empty() ? title0 : title1; + } + } + PlayerTalkClass->SendQuestGiverQuestList(qe, title, guid); + } +} + +bool Player::IsActiveQuest(uint32 quest_id) const +{ + QuestStatusMap::const_iterator itr = mQuestStatus.find(quest_id); + + return itr != mQuestStatus.end() && itr->second.m_status != QUEST_STATUS_NONE; +} + +bool Player::IsCurrentQuest(uint32 quest_id, uint8 completed_or_not) const +{ + QuestStatusMap::const_iterator itr = mQuestStatus.find(quest_id); + if (itr == mQuestStatus.end()) + return false; + + switch (completed_or_not) + { + case 1: + return itr->second.m_status == QUEST_STATUS_INCOMPLETE; + case 2: + return itr->second.m_status == QUEST_STATUS_COMPLETE && !itr->second.m_rewarded; + default: + return itr->second.m_status == QUEST_STATUS_INCOMPLETE || (itr->second.m_status == QUEST_STATUS_COMPLETE && !itr->second.m_rewarded); + } +} + +Quest const* Player::GetNextQuest(ObjectGuid guid, Quest const* pQuest) +{ + QuestRelationsMapBounds rbounds; + + if (Creature* pCreature = GetMap()->GetAnyTypeCreature(guid)) + { + rbounds = sObjectMgr.GetCreatureQuestRelationsMapBounds(pCreature->GetEntry()); + } + else + { + // we should obtain map pointer from GetMap() in 99% of cases. Special case + // only for quests which cast teleport spells on player + Map* _map = IsInWorld() ? GetMap() : sMapMgr.FindMap(GetMapId(), GetInstanceId()); + MANGOS_ASSERT(_map); + + if (GameObject* pGameObject = _map->GetGameObject(guid)) + { + rbounds = sObjectMgr.GetGOQuestRelationsMapBounds(pGameObject->GetEntry()); + } + else + return NULL; + } + + uint32 nextQuestID = pQuest->GetNextQuestInChain(); + for (QuestRelationsMap::const_iterator itr = rbounds.first; itr != rbounds.second; ++itr) + { + if (itr->second == nextQuestID) + return sObjectMgr.GetQuestTemplate(nextQuestID); + } + + return NULL; +} + +/** + * Check if a player could see a start quest + * Basic Quest-taking requirements: Class, Race, Skill, Quest-Line, ... + * Check if the quest-level is not too high (related config value CONFIG_INT32_QUEST_HIGH_LEVEL_HIDE_DIFF) + */ +bool Player::CanSeeStartQuest(Quest const* pQuest) const +{ + if (SatisfyQuestClass(pQuest, false) && SatisfyQuestRace(pQuest, false) && SatisfyQuestSkill(pQuest, false) && + SatisfyQuestExclusiveGroup(pQuest, false) && SatisfyQuestReputation(pQuest, false) && + SatisfyQuestPreviousQuest(pQuest, false) && SatisfyQuestNextChain(pQuest, false) && + SatisfyQuestPrevChain(pQuest, false) && SatisfyQuestDay(pQuest, false) && SatisfyQuestWeek(pQuest, false) && + SatisfyQuestMonth(pQuest, false) && + pQuest->IsActive()) + { + int32 highLevelDiff = sWorld.getConfig(CONFIG_INT32_QUEST_HIGH_LEVEL_HIDE_DIFF); + if (highLevelDiff < 0) + return true; + return getLevel() + uint32(highLevelDiff) >= pQuest->GetMinLevel(); + } + + return false; +} + +bool Player::CanTakeQuest(Quest const* pQuest, bool msg) const +{ + return SatisfyQuestStatus(pQuest, msg) && SatisfyQuestExclusiveGroup(pQuest, msg) && + SatisfyQuestClass(pQuest, msg) && SatisfyQuestRace(pQuest, msg) && SatisfyQuestLevel(pQuest, msg) && + SatisfyQuestSkill(pQuest, msg) && SatisfyQuestReputation(pQuest, msg) && + SatisfyQuestPreviousQuest(pQuest, msg) && SatisfyQuestTimed(pQuest, msg) && + SatisfyQuestNextChain(pQuest, msg) && SatisfyQuestPrevChain(pQuest, msg) && + SatisfyQuestDay(pQuest, msg) && SatisfyQuestWeek(pQuest, msg) && SatisfyQuestMonth(pQuest, msg) && + pQuest->IsActive(); +} + +bool Player::CanAddQuest(Quest const* pQuest, bool msg) const +{ + if (!SatisfyQuestLog(msg)) + return false; + + if (!CanGiveQuestSourceItemIfNeed(pQuest)) + return false; + + return true; +} + +bool Player::CanCompleteQuest(uint32 quest_id) const +{ + if (!quest_id) + return false; + + QuestStatusMap::const_iterator q_itr = mQuestStatus.find(quest_id); + + // some quests can be auto taken and auto completed in one step + QuestStatus status = q_itr != mQuestStatus.end() ? q_itr->second.m_status : QUEST_STATUS_NONE; + + if (status == QUEST_STATUS_COMPLETE) + return false; // not allow re-complete quest + + Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest_id); + + if (!qInfo) + return false; + + // only used for "flag" quests and not real in-game quests + if (qInfo->HasQuestFlag(QUEST_FLAGS_AUTO_REWARDED)) + { + // a few checks, not all "satisfy" is needed + if (SatisfyQuestPreviousQuest(qInfo, false) && SatisfyQuestLevel(qInfo, false) && SatisfyQuestSpell(qInfo, false) && + SatisfyQuestSkill(qInfo, false) && SatisfyQuestRace(qInfo, false) && SatisfyQuestClass(qInfo, false)) + return true; + + return false; + } + + // auto complete quest + if (qInfo->IsAutoComplete() && CanTakeQuest(qInfo, false)) + return true; + + if (status != QUEST_STATUS_INCOMPLETE) + return false; + + // incomplete quest have status data + QuestStatusData const& q_status = q_itr->second; + + if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER)) + { + for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i) + { + if (qInfo->ReqItemCount[i] != 0 && q_status.m_itemcount[i] < qInfo->ReqItemCount[i]) + return false; + } + + for (int i = 0; i < QUEST_REQUIRED_CURRENCY_COUNT; ++i) + { + if (qInfo->ReqCurrencyId[i] && !HasCurrencyCount(qInfo->ReqCurrencyId[i], int32(qInfo->ReqCurrencyCount[i] * GetCurrencyPrecision(qInfo->ReqCurrencyId[i])))) + return false; + } + } + + if (qInfo->HasSpecialFlag(QuestSpecialFlags(QUEST_SPECIAL_FLAG_KILL_OR_CAST | QUEST_SPECIAL_FLAG_SPEAKTO))) + { + for (int i = 0; i < QUEST_OBJECTIVES_COUNT; ++i) + { + if (qInfo->ReqCreatureOrGOId[i] == 0) + continue; + + if (qInfo->ReqCreatureOrGOCount[i] != 0 && q_status.m_creatureOrGOcount[i] < qInfo->ReqCreatureOrGOCount[i]) + return false; + } + } + + if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_EXPLORATION_OR_EVENT) && !q_status.m_explored) + return false; + + if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_TIMED) && q_status.m_timer == 0) + return false; + + if (qInfo->GetRewOrReqMoney() < 0) + { + if (GetMoney() < uint64(-qInfo->GetRewOrReqMoney())) + return false; + } + + uint32 repFacId = qInfo->GetRepObjectiveFaction(); + if (repFacId && GetReputationMgr().GetReputation(repFacId) < qInfo->GetRepObjectiveValue()) + return false; + + if (uint32 spell = qInfo->GetReqSpellLearned()) + if (!HasSpell(spell)) + return false; + + return true; +} + +bool Player::CanCompleteRepeatableQuest(Quest const* pQuest) const +{ + // Solve problem that player don't have the quest and try complete it. + // if repeatable she must be able to complete event if player don't have it. + // Seem that all repeatable quest are DELIVER Flag so, no need to add more. + if (!CanTakeQuest(pQuest, false)) + return false; + + if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER)) + { + for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i) + if (pQuest->ReqItemId[i] && pQuest->ReqItemCount[i] && !HasItemCount(pQuest->ReqItemId[i], pQuest->ReqItemCount[i])) + return false; + + for (int i = 0; i < QUEST_REQUIRED_CURRENCY_COUNT; ++i) + { + if (pQuest->ReqCurrencyId[i] && !HasCurrencyCount(pQuest->ReqCurrencyId[i], int32(pQuest->ReqCurrencyCount[i] * GetCurrencyPrecision(pQuest->ReqCurrencyId[i])))) + return false; + } + } + + if (!CanRewardQuest(pQuest, false)) + return false; + + return true; +} + +bool Player::CanRewardQuest(Quest const* pQuest, bool msg) const +{ + // not auto complete quest and not completed quest (only cheating case, then ignore without message) + if (!pQuest->IsAutoComplete() && GetQuestStatus(pQuest->GetQuestId()) != QUEST_STATUS_COMPLETE) + return false; + + // daily quest can't be rewarded (25 daily quest already completed) + if (!SatisfyQuestDay(pQuest, true) || !SatisfyQuestWeek(pQuest, true) || !SatisfyQuestMonth(pQuest, true)) + return false; + + // rewarded and not repeatable quest (only cheating case, then ignore without message) + if (GetQuestRewardStatus(pQuest->GetQuestId())) + return false; + + if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER)) + { + // prevent receive reward with quest items in bank + for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i) + { + if (pQuest->ReqItemCount[i] != 0 && + GetItemCount(pQuest->ReqItemId[i]) < pQuest->ReqItemCount[i]) + { + if (msg) + SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL, pQuest->ReqItemId[i]); + + return false; + } + } + + // prevent receive reward with low currency + for (int i = 0; i < QUEST_REQUIRED_CURRENCY_COUNT; ++i) + { + if (pQuest->ReqCurrencyId[i] && + !HasCurrencyCount(pQuest->ReqCurrencyId[i], int32(pQuest->ReqCurrencyCount[i] * GetCurrencyPrecision(pQuest->ReqCurrencyId[i])))) + { + if (msg) + SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, NULL); + + return false; + } + } + } + + // prevent receive reward with low money and GetRewOrReqMoney() < 0 + if (pQuest->GetRewOrReqMoney() < 0 && GetMoney() < uint64(-pQuest->GetRewOrReqMoney())) + return false; + + if (uint32 spell = pQuest->GetReqSpellLearned()) + if (!HasSpell(spell)) + return false; + + return true; +} + +bool Player::CanRewardQuest(Quest const* pQuest, uint32 reward, bool msg) const +{ + // prevent receive reward with quest items in bank or for not completed quest + if (!CanRewardQuest(pQuest, msg)) + return false; + + if (pQuest->GetRewChoiceItemsCount() > 0) + { + if (pQuest->RewChoiceItemId[reward]) + { + ItemPosCountVec dest; + InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, pQuest->RewChoiceItemId[reward], pQuest->RewChoiceItemCount[reward]); + if (res != EQUIP_ERR_OK) + { + SendEquipError(res, NULL, NULL, pQuest->RewChoiceItemId[reward]); + return false; + } + } + } + + if (pQuest->GetRewItemsCount() > 0) + { + for (uint32 i = 0; i < pQuest->GetRewItemsCount(); ++i) + { + if (pQuest->RewItemId[i]) + { + ItemPosCountVec dest; + InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, pQuest->RewItemId[i], pQuest->RewItemCount[i]); + if (res != EQUIP_ERR_OK) + { + SendEquipError(res, NULL, NULL); + return false; + } + } + } + } + + return true; +} + +void Player::SendPetTameFailure(PetTameFailureReason reason) +{ + WorldPacket data(SMSG_PET_TAME_FAILURE, 1); + data << uint8(reason); + GetSession()->SendPacket(&data); +} + +void Player::AddQuest(Quest const* pQuest, Object* questGiver) +{ + uint16 log_slot = FindQuestSlot(0); + MANGOS_ASSERT(log_slot < MAX_QUEST_LOG_SIZE); + + uint32 quest_id = pQuest->GetQuestId(); + + // if not exist then created with set uState==NEW and rewarded=false + QuestStatusData& questStatusData = mQuestStatus[quest_id]; + + // check for repeatable quests status reset + questStatusData.m_status = QUEST_STATUS_INCOMPLETE; + questStatusData.m_explored = false; + + if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER)) + { + for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i) + questStatusData.m_itemcount[i] = 0; + } + + if (pQuest->HasSpecialFlag(QuestSpecialFlags(QUEST_SPECIAL_FLAG_KILL_OR_CAST | QUEST_SPECIAL_FLAG_SPEAKTO))) + { + for (int i = 0; i < QUEST_OBJECTIVES_COUNT; ++i) + questStatusData.m_creatureOrGOcount[i] = 0; + } + + if (pQuest->GetRepObjectiveFaction()) + if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(pQuest->GetRepObjectiveFaction())) + GetReputationMgr().SetVisible(factionEntry); + + uint32 qtime = 0; + if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_TIMED)) + { + uint32 limittime = pQuest->GetLimitTime(); + + // shared timed quest + if (questGiver && questGiver->GetTypeId() == TYPEID_PLAYER) + limittime = ((Player*)questGiver)->getQuestStatusMap()[quest_id].m_timer / IN_MILLISECONDS; + + AddTimedQuest(quest_id); + questStatusData.m_timer = limittime * IN_MILLISECONDS; + qtime = static_cast(time(NULL)) + limittime; + } + else + questStatusData.m_timer = 0; + + SetQuestSlot(log_slot, quest_id, qtime); + + if (questStatusData.uState != QUEST_NEW) + questStatusData.uState = QUEST_CHANGED; + + // quest accept scripts + if (questGiver) + { + switch (questGiver->GetTypeId()) + { + case TYPEID_UNIT: + sScriptMgr.OnQuestAccept(this, (Creature*)questGiver, pQuest); + break; + case TYPEID_ITEM: + case TYPEID_CONTAINER: + sScriptMgr.OnQuestAccept(this, (Item*)questGiver, pQuest); + break; + case TYPEID_GAMEOBJECT: + sScriptMgr.OnQuestAccept(this, (GameObject*)questGiver, pQuest); + break; + } + + // starting initial DB quest script + if (pQuest->GetQuestStartScript() != 0) + GetMap()->ScriptsStart(sQuestStartScripts, pQuest->GetQuestStartScript(), questGiver, this, Map::SCRIPT_EXEC_PARAM_UNIQUE_BY_SOURCE); + } + + // remove start item if not need + if (questGiver && questGiver->isType(TYPEMASK_ITEM)) + { + // destroy not required for quest finish quest starting item + bool notRequiredItem = true; + for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i) + { + if (pQuest->ReqItemId[i] == questGiver->GetEntry()) + { + notRequiredItem = false; + break; + } + } + + if (pQuest->GetSrcItemId() == questGiver->GetEntry()) + notRequiredItem = false; + + if (notRequiredItem) + DestroyItem(((Item*)questGiver)->GetBagSlot(), ((Item*)questGiver)->GetSlot(), true); + } + + GiveQuestSourceItemIfNeed(pQuest); + + AdjustQuestReqItemCount(pQuest, questStatusData); + + // Some spells applied at quest activation + uint32 zone, area; + GetZoneAndAreaId(zone, area); + SpellAreaForAreaMapBounds saBounds = sSpellMgr.GetSpellAreaForAreaMapBounds(zone); + for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + itr->second->ApplyOrRemoveSpellIfCan(this, zone, area, true); + if (area != zone) + { + saBounds = sSpellMgr.GetSpellAreaForAreaMapBounds(area); + for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + itr->second->ApplyOrRemoveSpellIfCan(this, zone, area, true); + } + saBounds = sSpellMgr.GetSpellAreaForAreaMapBounds(0); + for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + itr->second->ApplyOrRemoveSpellIfCan(this, zone, area, true); + + UpdateForQuestWorldObjects(); +} + +void Player::CompleteQuest(uint32 quest_id) +{ + if (quest_id) + { + SetQuestStatus(quest_id, QUEST_STATUS_COMPLETE); + + uint16 log_slot = FindQuestSlot(quest_id); + if (log_slot < MAX_QUEST_LOG_SIZE) + SetQuestSlotState(log_slot, QUEST_STATE_COMPLETE); + + if (Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest_id)) + { + if (qInfo->HasQuestFlag(QUEST_FLAGS_AUTO_REWARDED)) + RewardQuest(qInfo, 0, this, false); + } + } +} + +void Player::IncompleteQuest(uint32 quest_id) +{ + if (quest_id) + { + SetQuestStatus(quest_id, QUEST_STATUS_INCOMPLETE); + + uint16 log_slot = FindQuestSlot(quest_id); + if (log_slot < MAX_QUEST_LOG_SIZE) + RemoveQuestSlotState(log_slot, QUEST_STATE_COMPLETE); + } +} + +void Player::RewardQuest(Quest const* pQuest, uint32 reward, Object* questGiver, bool announce) +{ + uint32 quest_id = pQuest->GetQuestId(); + + for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i) + { + if (pQuest->ReqItemId[i]) + DestroyItemCount(pQuest->ReqItemId[i], pQuest->ReqItemCount[i], true); + } + + for (int i = 0; i < QUEST_SOURCE_ITEM_IDS_COUNT; ++i) + { + if (pQuest->ReqSourceId[i]) + { + ItemPrototype const* iProto = ObjectMgr::GetItemPrototype(pQuest->ReqSourceId[i]); + if (iProto && iProto->Bonding == BIND_QUEST_ITEM) + DestroyItemCount(pQuest->ReqSourceId[i], pQuest->ReqSourceCount[i], true, false, true); + } + } + + // take currency + for (uint32 i = 0; i < QUEST_REQUIRED_CURRENCY_COUNT; ++i) + { + if (pQuest->ReqCurrencyId[i]) + ModifyCurrencyCount(pQuest->ReqCurrencyId[i], -int32(pQuest->ReqCurrencyCount[i] * GetCurrencyPrecision(pQuest->ReqCurrencyId[i]))); + } + + RemoveTimedQuest(quest_id); + + if (BattleGround* bg = GetBattleGround()) + if (bg->GetTypeID() == BATTLEGROUND_AV) + ((BattleGroundAV*)bg)->HandleQuestComplete(pQuest->GetQuestId(), this); + + if (pQuest->GetRewChoiceItemsCount() > 0) + { + if (uint32 itemId = pQuest->RewChoiceItemId[reward]) + { + ItemPosCountVec dest; + if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, pQuest->RewChoiceItemCount[reward]) == EQUIP_ERR_OK) + { + Item* item = StoreNewItem(dest, itemId, true, Item::GenerateItemRandomPropertyId(itemId)); + SendNewItem(item, pQuest->RewChoiceItemCount[reward], true, false); + } + } + } + + if (pQuest->GetRewItemsCount() > 0) + { + for (uint32 i = 0; i < pQuest->GetRewItemsCount(); ++i) + { + if (uint32 itemId = pQuest->RewItemId[i]) + { + ItemPosCountVec dest; + if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, pQuest->RewItemCount[i]) == EQUIP_ERR_OK) + { + Item* item = StoreNewItem(dest, itemId, true, Item::GenerateItemRandomPropertyId(itemId)); + SendNewItem(item, pQuest->RewItemCount[i], true, false); + } + } + } + } + + RewardReputation(pQuest); + + uint16 log_slot = FindQuestSlot(quest_id); + if (log_slot < MAX_QUEST_LOG_SIZE) + SetQuestSlot(log_slot, 0); + + QuestStatusData& q_status = mQuestStatus[quest_id]; + + // Used for client inform but rewarded only in case not max level + uint32 xp = uint32(pQuest->XPValue(this) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_QUEST)); + + if (getLevel() < sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)) + { + GiveXP(xp , NULL); + + // Give player extra money (for max level already included in pQuest->GetRewMoneyMaxLevel()) + if (pQuest->GetRewOrReqMoney() > 0) + { + ModifyMoney(pQuest->GetRewOrReqMoney()); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_MONEY_FROM_QUEST_REWARD, pQuest->GetRewOrReqMoney()); + } + } + else + { + // reward money for max level already included in pQuest->GetRewMoneyMaxLevel() + uint64 money = uint32(pQuest->GetRewMoneyMaxLevel() * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)); + + // reward money used if > xp replacement money + if (pQuest->GetRewOrReqMoney() > int64(money)) + money = pQuest->GetRewOrReqMoney(); + + ModifyMoney(money); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_MONEY_FROM_QUEST_REWARD, money); + } + + // req money case + if (pQuest->GetRewOrReqMoney() < 0) + ModifyMoney(pQuest->GetRewOrReqMoney()); + + // reward currency + for (uint32 i = 0; i < QUEST_REWARD_CURRENCY_COUNT; ++i) + { + if (pQuest->RewCurrencyId[i]) + ModifyCurrencyCount(pQuest->RewCurrencyId[i], int32(pQuest->RewCurrencyCount[i] * GetCurrencyPrecision(pQuest->RewCurrencyId[i]))); + } + + // reward skill + if (uint32 skill = pQuest->GetRewSkill()) + UpdateSkill(skill, pQuest->GetRewSkillValue()); + + // title reward + if (pQuest->GetCharTitleId()) + { + if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(pQuest->GetCharTitleId())) + SetTitle(titleEntry); + } + + if (pQuest->GetBonusTalents()) + { + m_questRewardTalentCount += pQuest->GetBonusTalents(); + InitTalentForLevel(); + } + + // Send reward mail + if (uint32 mail_template_id = pQuest->GetRewMailTemplateId()) + MailDraft(mail_template_id).SendMailTo(this, questGiver, MAIL_CHECK_MASK_HAS_BODY, pQuest->GetRewMailDelaySecs()); + + if (pQuest->IsDaily()) + { + SetDailyQuestStatus(quest_id); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST, 1); + } + + if (pQuest->IsWeekly()) + SetWeeklyQuestStatus(quest_id); + + if (pQuest->IsMonthly()) + SetMonthlyQuestStatus(quest_id); + + if (!pQuest->IsRepeatable()) + SetQuestStatus(quest_id, QUEST_STATUS_COMPLETE); + else + SetQuestStatus(quest_id, QUEST_STATUS_NONE); + + q_status.m_rewarded = true; + if (q_status.uState != QUEST_NEW) + q_status.uState = QUEST_CHANGED; + + if (announce) + SendQuestReward(pQuest, xp, questGiver); + + bool handled = false; + + switch (questGiver->GetTypeId()) + { + case TYPEID_UNIT: + handled = sScriptMgr.OnQuestRewarded(this, (Creature*)questGiver, pQuest); + break; + case TYPEID_GAMEOBJECT: + handled = sScriptMgr.OnQuestRewarded(this, (GameObject*)questGiver, pQuest); + break; + } + + if (!handled && pQuest->GetQuestCompleteScript() != 0) + GetMap()->ScriptsStart(sQuestEndScripts, pQuest->GetQuestCompleteScript(), questGiver, this, Map::SCRIPT_EXEC_PARAM_UNIQUE_BY_SOURCE); + + // cast spells after mark quest complete (some spells have quest completed state reqyurements in spell_area data) + if (pQuest->GetRewSpellCast() > 0) + CastSpell(this, pQuest->GetRewSpellCast(), true); + else if (pQuest->GetRewSpell() > 0) + CastSpell(this, pQuest->GetRewSpell(), true); + + if (pQuest->GetZoneOrSort() > 0) + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE, pQuest->GetZoneOrSort()); + + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, pQuest->GetQuestId()); + + UpdateForQuestWorldObjects(); + + // remove auras from spells with quest reward state limitations + // Some spells applied at quest reward + uint32 zone, area; + GetZoneAndAreaId(zone, area); + SpellAreaForAreaMapBounds saBounds = sSpellMgr.GetSpellAreaForAreaMapBounds(zone); + for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + itr->second->ApplyOrRemoveSpellIfCan(this, zone, area, false); + if (area != zone) + { + saBounds = sSpellMgr.GetSpellAreaForAreaMapBounds(area); + for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + itr->second->ApplyOrRemoveSpellIfCan(this, zone, area, false); + } + saBounds = sSpellMgr.GetSpellAreaForAreaMapBounds(0); + for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + itr->second->ApplyOrRemoveSpellIfCan(this, zone, area, false); + + PhaseUpdateData phaseUdateData; + phaseUdateData.AddQuestUpdate(quest_id); + + phaseMgr->NotifyConditionChanged(phaseUdateData); +} + +void Player::FailQuest(uint32 questId) +{ + if (Quest const* pQuest = sObjectMgr.GetQuestTemplate(questId)) + { + SetQuestStatus(questId, QUEST_STATUS_FAILED); + + uint16 log_slot = FindQuestSlot(questId); + + if (log_slot < MAX_QUEST_LOG_SIZE) + { + SetQuestSlotTimer(log_slot, 1); + SetQuestSlotState(log_slot, QUEST_STATE_FAIL); + } + + if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_TIMED)) + { + QuestStatusData& q_status = mQuestStatus[questId]; + + RemoveTimedQuest(questId); + q_status.m_timer = 0; + + SendQuestTimerFailed(questId); + } + else + SendQuestFailed(questId); + } +} + +bool Player::SatisfyQuestSkill(Quest const* qInfo, bool msg) const +{ + uint32 skill = qInfo->GetRequiredSkill(); + + // skip 0 case RequiredSkill + if (skill == 0) + return true; + + // check skill value + if (GetSkillValue(skill) < qInfo->GetRequiredSkillValue()) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); + + return false; + } + + return true; +} + +bool Player::SatisfyQuestSpell(Quest const* qInfo, bool msg) const +{ + uint32 spell = qInfo->GetReqSpellLearned(); + + // skip 0 case ReqSpellLearned + if (spell == 0) + return true; + + // check spell + if (!HasSpell(spell)) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_QUEST_FAILED_SPELL); + + return false; + } + + return true; +} + +bool Player::SatisfyQuestLevel(Quest const* qInfo, bool msg) const +{ + if (getLevel() < qInfo->GetMinLevel()) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); + + return false; + } + + return true; +} + +bool Player::SatisfyQuestLog(bool msg) const +{ + // exist free slot + if (FindQuestSlot(0) < MAX_QUEST_LOG_SIZE) + return true; + + if (msg) + { + WorldPacket data(SMSG_QUESTLOG_FULL, 0); + GetSession()->SendPacket(&data); + DEBUG_LOG("WORLD: Sent SMSG_QUESTLOG_FULL"); + } + return false; +} + +bool Player::SatisfyQuestPreviousQuest(Quest const* qInfo, bool msg) const +{ + // No previous quest (might be first quest in a series) + if (qInfo->prevQuests.empty()) + return true; + + for (Quest::PrevQuests::const_iterator iter = qInfo->prevQuests.begin(); iter != qInfo->prevQuests.end(); ++iter) + { + uint32 prevId = abs(*iter); + + QuestStatusMap::const_iterator i_prevstatus = mQuestStatus.find(prevId); + Quest const* qPrevInfo = sObjectMgr.GetQuestTemplate(prevId); + + if (qPrevInfo && i_prevstatus != mQuestStatus.end()) + { + // If any of the positive previous quests completed, return true + if (*iter > 0 && i_prevstatus->second.m_rewarded) + { + // skip one-from-all exclusive group + if (qPrevInfo->GetExclusiveGroup() >= 0) + return true; + + // each-from-all exclusive group ( < 0) + // can be start if only all quests in prev quest exclusive group completed and rewarded + ExclusiveQuestGroupsMapBounds bounds = sObjectMgr.GetExclusiveQuestGroupsMapBounds(qPrevInfo->GetExclusiveGroup()); + + MANGOS_ASSERT(bounds.first != bounds.second); // always must be found if qPrevInfo->ExclusiveGroup != 0 + + for (ExclusiveQuestGroupsMap::const_iterator iter2 = bounds.first; iter2 != bounds.second; ++iter2) + { + uint32 exclude_Id = iter2->second; + + // skip checked quest id, only state of other quests in group is interesting + if (exclude_Id == prevId) + continue; + + QuestStatusMap::const_iterator i_exstatus = mQuestStatus.find(exclude_Id); + + // alternative quest from group also must be completed and rewarded(reported) + if (i_exstatus == mQuestStatus.end() || !i_exstatus->second.m_rewarded) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); + + return false; + } + } + return true; + } + // If any of the negative previous quests active, return true + if (*iter < 0 && IsCurrentQuest(prevId)) + { + // skip one-from-all exclusive group + if (qPrevInfo->GetExclusiveGroup() >= 0) + return true; + + // each-from-all exclusive group ( < 0) + // can be start if only all quests in prev quest exclusive group active + ExclusiveQuestGroupsMapBounds bounds = sObjectMgr.GetExclusiveQuestGroupsMapBounds(qPrevInfo->GetExclusiveGroup()); + + MANGOS_ASSERT(bounds.first != bounds.second); // always must be found if qPrevInfo->ExclusiveGroup != 0 + + for (ExclusiveQuestGroupsMap::const_iterator iter2 = bounds.first; iter2 != bounds.second; ++iter2) + { + uint32 exclude_Id = iter2->second; + + // skip checked quest id, only state of other quests in group is interesting + if (exclude_Id == prevId) + continue; + + // alternative quest from group also must be active + if (!IsCurrentQuest(exclude_Id)) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); + + return false; + } + } + return true; + } + } + } + + // Has only positive prev. quests in non-rewarded state + // and negative prev. quests in non-active state + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); + + return false; +} + +bool Player::SatisfyQuestClass(Quest const* qInfo, bool msg) const +{ + uint32 reqClass = qInfo->GetRequiredClasses(); + + if (reqClass == 0) + return true; + + if ((reqClass & getClassMask()) == 0) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); + + return false; + } + + return true; +} + +bool Player::SatisfyQuestRace(Quest const* qInfo, bool msg) const +{ + uint32 reqraces = qInfo->GetRequiredRaces(); + + if (reqraces == 0) + return true; + + if ((reqraces & getRaceMask()) == 0) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_QUEST_FAILED_WRONG_RACE); + + return false; + } + + return true; +} + +bool Player::SatisfyQuestReputation(Quest const* qInfo, bool msg) const +{ + uint32 fIdMin = qInfo->GetRequiredMinRepFaction(); // Min required rep + if (fIdMin && GetReputationMgr().GetReputation(fIdMin) < qInfo->GetRequiredMinRepValue()) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); + + return false; + } + + uint32 fIdMax = qInfo->GetRequiredMaxRepFaction(); // Max required rep + if (fIdMax && GetReputationMgr().GetReputation(fIdMax) >= qInfo->GetRequiredMaxRepValue()) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); + + return false; + } + + return true; +} + +bool Player::SatisfyQuestStatus(Quest const* qInfo, bool msg) const +{ + QuestStatusMap::const_iterator itr = mQuestStatus.find(qInfo->GetQuestId()); + + if (itr != mQuestStatus.end() && itr->second.m_status != QUEST_STATUS_NONE) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_QUEST_ALREADY_ON); + + return false; + } + + return true; +} + +bool Player::SatisfyQuestTimed(Quest const* qInfo, bool msg) const +{ + if (!m_timedquests.empty() && qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_TIMED)) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_QUEST_ONLY_ONE_TIMED); + + return false; + } + + return true; +} + +bool Player::SatisfyQuestExclusiveGroup(Quest const* qInfo, bool msg) const +{ + // non positive exclusive group, if > 0 then can be start if any other quest in exclusive group already started/completed + if (qInfo->GetExclusiveGroup() <= 0) + return true; + + ExclusiveQuestGroupsMapBounds bounds = sObjectMgr.GetExclusiveQuestGroupsMapBounds(qInfo->GetExclusiveGroup()); + + MANGOS_ASSERT(bounds.first != bounds.second); // must always be found if qInfo->ExclusiveGroup != 0 + + for (ExclusiveQuestGroupsMap::const_iterator iter = bounds.first; iter != bounds.second; ++iter) + { + uint32 exclude_Id = iter->second; + + // skip checked quest id, only state of other quests in group is interesting + if (exclude_Id == qInfo->GetQuestId()) + continue; + + // not allow have daily quest if daily quest from exclusive group already recently completed + Quest const* Nquest = sObjectMgr.GetQuestTemplate(exclude_Id); + if (!SatisfyQuestDay(Nquest, false) || !SatisfyQuestWeek(Nquest, false)) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); + + return false; + } + + QuestStatusMap::const_iterator i_exstatus = mQuestStatus.find(exclude_Id); + + // alternative quest already started or completed + if (i_exstatus != mQuestStatus.end() && + (i_exstatus->second.m_status == QUEST_STATUS_COMPLETE || i_exstatus->second.m_status == QUEST_STATUS_INCOMPLETE)) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); + + return false; + } + } + + return true; +} + +bool Player::SatisfyQuestNextChain(Quest const* qInfo, bool msg) const +{ + if (!qInfo->GetNextQuestInChain()) + return true; + + // next quest in chain already started or completed + QuestStatusMap::const_iterator itr = mQuestStatus.find(qInfo->GetNextQuestInChain()); + if (itr != mQuestStatus.end() && + (itr->second.m_status == QUEST_STATUS_COMPLETE || itr->second.m_status == QUEST_STATUS_INCOMPLETE)) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); + + return false; + } + + // check for all quests further up the chain + // only necessary if there are quest chains with more than one quest that can be skipped + // return SatisfyQuestNextChain( qInfo->GetNextQuestInChain(), msg ); + return true; +} + +bool Player::SatisfyQuestPrevChain(Quest const* qInfo, bool msg) const +{ + // No previous quest in chain + if (qInfo->prevChainQuests.empty()) + return true; + + for (Quest::PrevChainQuests::const_iterator iter = qInfo->prevChainQuests.begin(); iter != qInfo->prevChainQuests.end(); ++iter) + { + uint32 prevId = *iter; + + // If any of the previous quests in chain active, return false + if (IsCurrentQuest(prevId)) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ); + + return false; + } + + // check for all quests further down the chain + // only necessary if there are quest chains with more than one quest that can be skipped + // if( !SatisfyQuestPrevChain( prevId, msg ) ) + // return false; + } + + // No previous quest in chain active + return true; +} + +bool Player::SatisfyQuestDay(Quest const* qInfo, bool msg) const +{ + if (!qInfo->IsDaily()) + return true; + + bool have_slot = false; + for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx) + { + uint32 id = GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx); + if (qInfo->GetQuestId() == id) + return false; + + if (!id) + have_slot = true; + } + + if (!have_slot) + { + if (msg) + SendCanTakeQuestResponse(INVALIDREASON_QUEST_FAILED_TOO_MANY_DAILY_QUESTS); + + return false; + } + + return true; +} + +bool Player::SatisfyQuestWeek(Quest const* qInfo, bool /*msg*/) const +{ + if (!qInfo->IsWeekly() || m_weeklyquests.empty()) + return true; + + // if not found in cooldown list + return m_weeklyquests.find(qInfo->GetQuestId()) == m_weeklyquests.end(); +} + +bool Player::SatisfyQuestMonth(Quest const* qInfo, bool /*msg*/) const +{ + if (!qInfo->IsMonthly() || m_monthlyquests.empty()) + return true; + + // if not found in cooldown list + return m_monthlyquests.find(qInfo->GetQuestId()) == m_monthlyquests.end(); +} + +bool Player::CanGiveQuestSourceItemIfNeed(Quest const* pQuest, ItemPosCountVec* dest) const +{ + if (uint32 srcitem = pQuest->GetSrcItemId()) + { + uint32 count = pQuest->GetSrcItemCount(); + + // player already have max amount required item (including bank), just report success + uint32 has_count = GetItemCount(srcitem, true); + if (has_count >= count) + return true; + + count -= has_count; // real need amount + + InventoryResult msg; + if (!dest) + { + ItemPosCountVec destTemp; + msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, destTemp, srcitem, count); + } + else + msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, *dest, srcitem, count); + + if (msg == EQUIP_ERR_OK) + return true; + else + SendEquipError(msg, NULL, NULL, srcitem); + return false; + } + + return true; +} + +void Player::GiveQuestSourceItemIfNeed(Quest const* pQuest) +{ + ItemPosCountVec dest; + if (CanGiveQuestSourceItemIfNeed(pQuest, &dest) && !dest.empty()) + { + uint32 count = 0; + for (ItemPosCountVec::const_iterator c_itr = dest.begin(); c_itr != dest.end(); ++c_itr) + count += c_itr->count; + + Item* item = StoreNewItem(dest, pQuest->GetSrcItemId(), true); + SendNewItem(item, count, true, false); + } +} + +bool Player::TakeQuestSourceItem(uint32 quest_id, bool msg) +{ + Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest_id); + if (qInfo) + { + uint32 srcitem = qInfo->GetSrcItemId(); + if (srcitem > 0) + { + uint32 count = qInfo->GetSrcItemCount(); + if (count <= 0) + count = 1; + + // exist one case when destroy source quest item not possible: + // non un-equippable item (equipped non-empty bag, for example) + InventoryResult res = CanUnequipItems(srcitem, count); + if (res != EQUIP_ERR_OK) + { + if (msg) + SendEquipError(res, NULL, NULL, srcitem); + return false; + } + + DestroyItemCount(srcitem, count, true, true); + } + } + return true; +} + +bool Player::GetQuestRewardStatus(uint32 quest_id) const +{ + Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest_id); + if (qInfo) + { + // for repeatable quests: rewarded field is set after first reward only to prevent getting XP more than once + QuestStatusMap::const_iterator itr = mQuestStatus.find(quest_id); + if (itr != mQuestStatus.end() && itr->second.m_status != QUEST_STATUS_NONE + && !qInfo->IsRepeatable()) + return itr->second.m_rewarded; + + return false; + } + return false; +} + +QuestStatus Player::GetQuestStatus(uint32 quest_id) const +{ + if (quest_id) + { + QuestStatusMap::const_iterator itr = mQuestStatus.find(quest_id); + if (itr != mQuestStatus.end()) + return itr->second.m_status; + } + return QUEST_STATUS_NONE; +} + +bool Player::CanShareQuest(uint32 quest_id) const +{ + if (Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest_id)) + if (qInfo->HasQuestFlag(QUEST_FLAGS_SHARABLE)) + return IsCurrentQuest(quest_id); + + return false; +} + +void Player::SetQuestStatus(uint32 quest_id, QuestStatus status) +{ + if (sObjectMgr.GetQuestTemplate(quest_id)) + { + QuestStatusData& q_status = mQuestStatus[quest_id]; + + q_status.m_status = status; + + if (q_status.uState != QUEST_NEW) + q_status.uState = QUEST_CHANGED; + } + + PhaseUpdateData phaseUdateData; + phaseUdateData.AddQuestUpdate(quest_id); + + phaseMgr->NotifyConditionChanged(phaseUdateData); + + UpdateForQuestWorldObjects(); +} + +// not used in MaNGOS, but used in scripting code +uint32 Player::GetReqKillOrCastCurrentCount(uint32 quest_id, int32 entry) +{ + Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest_id); + if (!qInfo) + return 0; + + for (int j = 0; j < QUEST_OBJECTIVES_COUNT; ++j) + if (qInfo->ReqCreatureOrGOId[j] == entry) + return mQuestStatus[quest_id].m_creatureOrGOcount[j]; + + return 0; +} + +void Player::AdjustQuestReqItemCount(Quest const* pQuest, QuestStatusData& questStatusData) +{ + if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER)) + { + for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i) + { + uint32 reqitemcount = pQuest->ReqItemCount[i]; + if (reqitemcount != 0) + { + uint32 curitemcount = GetItemCount(pQuest->ReqItemId[i], true); + + questStatusData.m_itemcount[i] = std::min(curitemcount, reqitemcount); + if (questStatusData.uState != QUEST_NEW) questStatusData.uState = QUEST_CHANGED; + } + } + } +} + +uint16 Player::FindQuestSlot(uint32 quest_id) const +{ + for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + if (GetQuestSlotQuestId(i) == quest_id) + return i; + + return MAX_QUEST_LOG_SIZE; +} + +void Player::AreaExploredOrEventHappens(uint32 questId) +{ + if (questId) + { + uint16 log_slot = FindQuestSlot(questId); + if (log_slot < MAX_QUEST_LOG_SIZE) + { + QuestStatusData& q_status = mQuestStatus[questId]; + + if (!q_status.m_explored) + { + SetQuestSlotState(log_slot, QUEST_STATE_COMPLETE); + SendQuestCompleteEvent(questId); + q_status.m_explored = true; + + if (q_status.uState != QUEST_NEW) + q_status.uState = QUEST_CHANGED; + } + } + if (CanCompleteQuest(questId)) + CompleteQuest(questId); + } +} + +// not used in mangosd, function for external script library +void Player::GroupEventHappens(uint32 questId, WorldObject const* pEventObject) +{ + if (Group* pGroup = GetGroup()) + { + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* pGroupGuy = itr->getSource(); + + // for any leave or dead (with not released body) group member at appropriate distance + if (pGroupGuy && pGroupGuy->IsAtGroupRewardDistance(pEventObject) && !pGroupGuy->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + pGroupGuy->AreaExploredOrEventHappens(questId); + } + } + else + AreaExploredOrEventHappens(questId); +} + +void Player::CurrencyAddedQuestCheck(uint32 entry) +{ + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + uint32 questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + QuestStatusData& q_status = mQuestStatus[questid]; + + if (q_status.m_status != QUEST_STATUS_INCOMPLETE) + continue; + + Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid); + if (!qInfo || !qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER)) + continue; + + for (int j = 0; j < QUEST_REQUIRED_CURRENCY_COUNT; ++j) + { + uint32 reqcurrency = qInfo->ReqCurrencyId[j]; + if (reqcurrency == entry) + { + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + } + } + } + + UpdateForQuestWorldObjects(); +} + +void Player::CurrencyRemovedQuestCheck(uint32 entry) +{ + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + uint32 questid = GetQuestSlotQuestId(i); + if (!questid) + continue; + Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid); + if (!qInfo || !qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER)) + continue; + + for (int j = 0; j < QUEST_REQUIRED_CURRENCY_COUNT; ++j) + { + uint32 reqcurrency = qInfo->ReqCurrencyId[j]; + if (reqcurrency == entry) + { + if (!HasCurrencyCount(entry, int32(qInfo->ReqCurrencyCount[j] * GetCurrencyPrecision(entry)))) + IncompleteQuest(questid); + } + } + } + + UpdateForQuestWorldObjects(); +} + +void Player::ItemAddedQuestCheck(uint32 entry, uint32 count) +{ + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + uint32 questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + QuestStatusData& q_status = mQuestStatus[questid]; + + if (q_status.m_status != QUEST_STATUS_INCOMPLETE) + continue; + + Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid); + if (!qInfo || !qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER)) + continue; + + for (int j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j) + { + uint32 reqitem = qInfo->ReqItemId[j]; + if (reqitem == entry) + { + uint32 reqitemcount = qInfo->ReqItemCount[j]; + uint32 curitemcount = q_status.m_itemcount[j]; + if (curitemcount < reqitemcount) + { + uint32 additemcount = (curitemcount + count <= reqitemcount ? count : reqitemcount - curitemcount); + q_status.m_itemcount[j] += additemcount; + if (q_status.uState != QUEST_NEW) + q_status.uState = QUEST_CHANGED; + } + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + return; + } + } + } + UpdateForQuestWorldObjects(); +} + +void Player::ItemRemovedQuestCheck(uint32 entry, uint32 count) +{ + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + uint32 questid = GetQuestSlotQuestId(i); + if (!questid) + continue; + Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid); + if (!qInfo) + continue; + if (!qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER)) + continue; + + for (int j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j) + { + uint32 reqitem = qInfo->ReqItemId[j]; + if (reqitem == entry) + { + QuestStatusData& q_status = mQuestStatus[questid]; + + uint32 reqitemcount = qInfo->ReqItemCount[j]; + uint32 curitemcount; + if (q_status.m_status != QUEST_STATUS_COMPLETE) + curitemcount = q_status.m_itemcount[j]; + else + curitemcount = GetItemCount(entry, true); + if (curitemcount < reqitemcount + count) + { + uint32 remitemcount = (curitemcount <= reqitemcount ? count : count + reqitemcount - curitemcount); + q_status.m_itemcount[j] = curitemcount - remitemcount; + if (q_status.uState != QUEST_NEW) q_status.uState = QUEST_CHANGED; + + IncompleteQuest(questid); + } + return; + } + } + } + UpdateForQuestWorldObjects(); +} + +void Player::SpellAddedQuestCheck(uint32 entry) +{ + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + uint32 questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + QuestStatusData& q_status = mQuestStatus[questid]; + + if (q_status.m_status != QUEST_STATUS_INCOMPLETE) + continue; + + Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid); + if (!qInfo) + continue; + + uint32 reqspelllearned = qInfo->GetReqSpellLearned(); + if (!reqspelllearned) + continue; + + if (reqspelllearned == entry) + { + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + } + } + + UpdateForQuestWorldObjects(); +} + +void Player::SpellRemovedQuestCheck(uint32 entry) +{ + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + uint32 questid = GetQuestSlotQuestId(i); + if (!questid) + continue; + Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid); + if (!qInfo) + continue; + + uint32 reqspelllearned = qInfo->GetReqSpellLearned(); + if (!reqspelllearned) + continue; + + if (reqspelllearned == entry) + { + if (!HasSpell(entry)) + IncompleteQuest(questid); + } + } + + UpdateForQuestWorldObjects(); +} + +void Player::KilledMonster(CreatureInfo const* cInfo, ObjectGuid guid) +{ + if (cInfo->Entry) + KilledMonsterCredit(cInfo->Entry, guid); + + for (int i = 0; i < MAX_KILL_CREDIT; ++i) + if (cInfo->KillCredit[i]) + KilledMonsterCredit(cInfo->KillCredit[i], guid); +} + +void Player::KilledMonsterCredit(uint32 entry, ObjectGuid guid) +{ + uint32 addkillcount = 1; + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE, entry, addkillcount); + + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + uint32 questid = GetQuestSlotQuestId(i); + if (!questid) + continue; + + Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid); + if (!qInfo) + continue; + // just if !ingroup || !noraidgroup || raidgroup + QuestStatusData& q_status = mQuestStatus[questid]; + if (q_status.m_status == QUEST_STATUS_INCOMPLETE && (!GetGroup() || !GetGroup()->isRaidGroup() || qInfo->IsAllowedInRaid())) + { + if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_KILL_OR_CAST)) + { + for (int j = 0; j < QUEST_OBJECTIVES_COUNT; ++j) + { + // skip GO activate objective or none + if (qInfo->ReqCreatureOrGOId[j] <= 0) + continue; + + // skip Cast at creature objective + if (qInfo->ReqSpell[j] != 0) + continue; + + uint32 reqkill = qInfo->ReqCreatureOrGOId[j]; + + if (reqkill == entry) + { + uint32 reqkillcount = qInfo->ReqCreatureOrGOCount[j]; + uint32 curkillcount = q_status.m_creatureOrGOcount[j]; + if (curkillcount < reqkillcount) + { + q_status.m_creatureOrGOcount[j] = curkillcount + addkillcount; + if (q_status.uState != QUEST_NEW) + q_status.uState = QUEST_CHANGED; + + SendQuestUpdateAddCreatureOrGo(qInfo, guid, j, q_status.m_creatureOrGOcount[j]); + } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). + continue; + } + } + } + } + } +} + +void Player::CastedCreatureOrGO(uint32 entry, ObjectGuid guid, uint32 spell_id, bool original_caster) +{ + bool isCreature = guid.IsCreatureOrVehicle(); + + uint32 addCastCount = 1; + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + uint32 questid = GetQuestSlotQuestId(i); + if (!questid) + continue; + + Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid); + if (!qInfo) + continue; + + if (!original_caster && !qInfo->HasQuestFlag(QUEST_FLAGS_SHARABLE)) + continue; + + if (!qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_KILL_OR_CAST)) + continue; + + QuestStatusData& q_status = mQuestStatus[questid]; + + if (q_status.m_status != QUEST_STATUS_INCOMPLETE) + continue; + + for (int j = 0; j < QUEST_OBJECTIVES_COUNT; ++j) + { + // skip kill creature objective (0) or wrong spell casts + if (qInfo->ReqSpell[j] != spell_id) + continue; + + uint32 reqTarget = 0; + + if (isCreature) + { + // creature activate objectives + if (qInfo->ReqCreatureOrGOId[j] > 0) + // checked at quest_template loading + reqTarget = qInfo->ReqCreatureOrGOId[j]; + } + else + { + // GO activate objective + if (qInfo->ReqCreatureOrGOId[j] < 0) + // checked at quest_template loading + reqTarget = - qInfo->ReqCreatureOrGOId[j]; + } + + // other not this creature/GO related objectives + if (reqTarget != entry) + continue; + + uint32 reqCastCount = qInfo->ReqCreatureOrGOCount[j]; + uint32 curCastCount = q_status.m_creatureOrGOcount[j]; + if (curCastCount < reqCastCount) + { + q_status.m_creatureOrGOcount[j] = curCastCount + addCastCount; + if (q_status.uState != QUEST_NEW) + q_status.uState = QUEST_CHANGED; + + SendQuestUpdateAddCreatureOrGo(qInfo, guid, j, q_status.m_creatureOrGOcount[j]); + } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). + break; + } + } +} + +void Player::TalkedToCreature(uint32 entry, ObjectGuid guid) +{ + uint32 addTalkCount = 1; + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + uint32 questid = GetQuestSlotQuestId(i); + if (!questid) + continue; + + Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid); + if (!qInfo) + continue; + + QuestStatusData& q_status = mQuestStatus[questid]; + + if (q_status.m_status == QUEST_STATUS_INCOMPLETE) + { + if (qInfo->HasSpecialFlag(QuestSpecialFlags(QUEST_SPECIAL_FLAG_KILL_OR_CAST | QUEST_SPECIAL_FLAG_SPEAKTO))) + { + for (int j = 0; j < QUEST_OBJECTIVES_COUNT; ++j) + { + // skip spell casts and Gameobject objectives + if (qInfo->ReqSpell[j] > 0 || qInfo->ReqCreatureOrGOId[j] < 0) + continue; + + uint32 reqTarget = 0; + + if (qInfo->ReqCreatureOrGOId[j] > 0) // creature activate objectives + // checked at quest_template loading + reqTarget = qInfo->ReqCreatureOrGOId[j]; + else + continue; + + if (reqTarget == entry) + { + uint32 reqTalkCount = qInfo->ReqCreatureOrGOCount[j]; + uint32 curTalkCount = q_status.m_creatureOrGOcount[j]; + if (curTalkCount < reqTalkCount) + { + q_status.m_creatureOrGOcount[j] = curTalkCount + addTalkCount; + if (q_status.uState != QUEST_NEW) q_status.uState = QUEST_CHANGED; + + SendQuestUpdateAddCreatureOrGo(qInfo, guid, j, q_status.m_creatureOrGOcount[j]); + } + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). + continue; + } + } + } + } + } +} + +void Player::MoneyChanged(uint32 count) +{ + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + uint32 questid = GetQuestSlotQuestId(i); + if (!questid) + continue; + + Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid); + if (qInfo && qInfo->GetRewOrReqMoney() < 0) + { + QuestStatusData& q_status = mQuestStatus[questid]; + + if (q_status.m_status == QUEST_STATUS_INCOMPLETE) + { + if (int32(count) >= -qInfo->GetRewOrReqMoney()) + { + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + } + } + else if (q_status.m_status == QUEST_STATUS_COMPLETE) + { + if (int32(count) < -qInfo->GetRewOrReqMoney()) + IncompleteQuest(questid); + } + } + } +} + +void Player::ReputationChanged(FactionEntry const* factionEntry) +{ + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + if (uint32 questid = GetQuestSlotQuestId(i)) + { + if (Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid)) + { + if (qInfo->GetRepObjectiveFaction() == factionEntry->ID) + { + QuestStatusData& q_status = mQuestStatus[questid]; + if (q_status.m_status == QUEST_STATUS_INCOMPLETE) + { + if (GetReputationMgr().GetReputation(factionEntry) >= qInfo->GetRepObjectiveValue()) + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + } + else if (q_status.m_status == QUEST_STATUS_COMPLETE) + { + if (GetReputationMgr().GetReputation(factionEntry) < qInfo->GetRepObjectiveValue()) + IncompleteQuest(questid); + } + } + } + } + } +} + +bool Player::HasQuestForItem(uint32 itemid) const +{ + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + uint32 questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + QuestStatusMap::const_iterator qs_itr = mQuestStatus.find(questid); + if (qs_itr == mQuestStatus.end()) + continue; + + QuestStatusData const& q_status = qs_itr->second; + + if (q_status.m_status == QUEST_STATUS_INCOMPLETE) + { + Quest const* qinfo = sObjectMgr.GetQuestTemplate(questid); + if (!qinfo) + continue; + + // hide quest if player is in raid-group and quest is no raid quest + if (GetGroup() && GetGroup()->isRaidGroup() && !qinfo->IsAllowedInRaid() && !InBattleGround()) + continue; + + // There should be no mixed ReqItem/ReqSource drop + // This part for ReqItem drop + for (int j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j) + { + if (itemid == qinfo->ReqItemId[j] && q_status.m_itemcount[j] < qinfo->ReqItemCount[j]) + return true; + } + // This part - for ReqSource + for (int j = 0; j < QUEST_SOURCE_ITEM_IDS_COUNT; ++j) + { + // examined item is a source item + if (qinfo->ReqSourceId[j] == itemid) + { + ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(itemid); + + // 'unique' item + if (pProto->MaxCount && (int32)GetItemCount(itemid, true) < pProto->MaxCount) + return true; + + // allows custom amount drop when not 0 + if (qinfo->ReqSourceCount[j]) + { + if (GetItemCount(itemid, true) < qinfo->ReqSourceCount[j]) + return true; + } + else if ((int32)GetItemCount(itemid, true) < pProto->Stackable) + return true; + } + } + } + } + return false; +} + +// Used for quests having some event (explore, escort, "external event") as quest objective. +void Player::SendQuestCompleteEvent(uint32 quest_id) +{ + if (quest_id) + { + WorldPacket data(SMSG_QUESTUPDATE_COMPLETE, 4); + data << uint32(quest_id); + GetSession()->SendPacket(&data); + DEBUG_LOG("WORLD: Sent SMSG_QUESTUPDATE_COMPLETE quest = %u", quest_id); + } +} + +void Player::SendQuestReward(Quest const* pQuest, uint32 XP, Object* /*questGiver*/) +{ + uint32 questid = pQuest->GetQuestId(); + DEBUG_LOG("WORLD: Sent SMSG_QUESTGIVER_QUEST_COMPLETE quest = %u", questid); + WorldPacket data(SMSG_QUESTGIVER_QUEST_COMPLETE, (4 + 4 + 4 + 4 + 4)); + data << uint32(pQuest->GetBonusTalents()); + data << uint32(pQuest->GetRewSkillValue()); + + if (getLevel() < sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)) + { + data << uint32(pQuest->GetRewOrReqMoney()); + data << uint32(XP); + } + else + { + data << uint32(pQuest->GetRewOrReqMoney() + int32(pQuest->GetRewMoneyMaxLevel() * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY))); + data << uint32(0); + } + + data << uint32(questid); + data << uint32(pQuest->GetRewSkill()); + + data.WriteBit(0); // unk + data.WriteBit(1); // unk + + GetSession()->SendPacket(&data); +} + +void Player::SendQuestFailed(uint32 quest_id, InventoryResult reason) +{ + if (quest_id) + { + WorldPacket data(SMSG_QUESTGIVER_QUEST_FAILED, 4 + 4); + data << uint32(quest_id); + data << uint32(reason); // failed reason (valid reasons: 4, 16, 50, 17, 74, other values show default message) + GetSession()->SendPacket(&data); + DEBUG_LOG("WORLD: Sent SMSG_QUESTGIVER_QUEST_FAILED"); + } +} + +void Player::SendQuestTimerFailed(uint32 quest_id) +{ + if (quest_id) + { + WorldPacket data(SMSG_QUESTUPDATE_FAILEDTIMER, 4); + data << uint32(quest_id); + GetSession()->SendPacket(&data); + DEBUG_LOG("WORLD: Sent SMSG_QUESTUPDATE_FAILEDTIMER"); + } +} + +void Player::SendCanTakeQuestResponse(uint32 msg) const +{ + WorldPacket data(SMSG_QUESTGIVER_QUEST_INVALID, 4); + data << uint32(msg); + GetSession()->SendPacket(&data); + DEBUG_LOG("WORLD: Sent SMSG_QUESTGIVER_QUEST_INVALID"); +} + +void Player::SendQuestConfirmAccept(const Quest* pQuest, Player* pReceiver) +{ + if (pReceiver) + { + int loc_idx = pReceiver->GetSession()->GetSessionDbLocaleIndex(); + std::string title = pQuest->GetTitle(); + sObjectMgr.GetQuestLocaleStrings(pQuest->GetQuestId(), loc_idx, &title); + + WorldPacket data(SMSG_QUEST_CONFIRM_ACCEPT, (4 + title.size() + 8)); + data << uint32(pQuest->GetQuestId()); + data << title; + data << GetObjectGuid(); + pReceiver->GetSession()->SendPacket(&data); + + DEBUG_LOG("WORLD: Sent SMSG_QUEST_CONFIRM_ACCEPT"); + } +} + +void Player::SendPushToPartyResponse(Player* pPlayer, uint32 msg) +{ + if (pPlayer) + { + WorldPacket data(MSG_QUEST_PUSH_RESULT, (8 + 1)); + data << pPlayer->GetObjectGuid(); + data << uint8(msg); // valid values: 0-8 + GetSession()->SendPacket(&data); + DEBUG_LOG("WORLD: Sent MSG_QUEST_PUSH_RESULT"); + } +} + +void Player::SendQuestUpdateAddCreatureOrGo(Quest const* pQuest, ObjectGuid guid, uint32 creatureOrGO_idx, uint32 count) +{ + MANGOS_ASSERT(count < 65536 && "mob/GO count store in 16 bits 2^16 = 65536 (0..65536)"); + + int32 entry = pQuest->ReqCreatureOrGOId[ creatureOrGO_idx ]; + if (entry < 0) + // client expected gameobject template id in form (id|0x80000000) + entry = (-entry) | 0x80000000; + + WorldPacket data(SMSG_QUESTUPDATE_ADD_KILL, (4 * 4 + 8)); + DEBUG_LOG("WORLD: Sent SMSG_QUESTUPDATE_ADD_KILL"); + data << uint32(pQuest->GetQuestId()); + data << uint32(entry); + data << uint32(count); + data << uint32(pQuest->ReqCreatureOrGOCount[ creatureOrGO_idx ]); + data << guid; + GetSession()->SendPacket(&data); + + uint16 log_slot = FindQuestSlot(pQuest->GetQuestId()); + if (log_slot < MAX_QUEST_LOG_SIZE) + SetQuestSlotCounter(log_slot, creatureOrGO_idx, count); +} + +/*********************************************************/ +/*** LOAD SYSTEM ***/ +/*********************************************************/ + +void Player::_LoadDeclinedNames(QueryResult* result) +{ + if (!result) + return; + + delete m_declinedname; + m_declinedname = new DeclinedName; + + Field* fields = result->Fetch(); + for (int i = 0; i < MAX_DECLINED_NAME_CASES; ++i) + m_declinedname->name[i] = fields[i].GetCppString(); + + delete result; +} + +void Player::_LoadArenaTeamInfo(QueryResult* result) +{ + // arenateamid, played_week, played_season, personal_rating + memset((void*)&m_uint32Values[PLAYER_FIELD_ARENA_TEAM_INFO_1_1], 0, sizeof(uint32) * MAX_ARENA_SLOT * ARENA_TEAM_END); + if (!result) + return; + + do + { + Field* fields = result->Fetch(); + + uint32 arenateamid = fields[0].GetUInt32(); + uint32 played_week = fields[1].GetUInt32(); + uint32 played_season = fields[2].GetUInt32(); + uint32 wons_season = fields[3].GetUInt32(); + uint32 personal_rating = fields[4].GetUInt32(); + + ArenaTeam* aTeam = sObjectMgr.GetArenaTeamById(arenateamid); + if (!aTeam) + { + sLog.outError("Player::_LoadArenaTeamInfo: couldn't load arenateam %u", arenateamid); + continue; + } + uint8 arenaSlot = aTeam->GetSlot(); + + SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_ID, arenateamid); + SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_TYPE, aTeam->GetType()); + SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_MEMBER, (aTeam->GetCaptainGuid() == GetObjectGuid()) ? 0 : 1); + SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_GAMES_WEEK, played_week); + SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_GAMES_SEASON, played_season); + SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_WINS_SEASON, wons_season); + SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_PERSONAL_RATING, personal_rating); + } + while (result->NextRow()); + delete result; +} + +void Player::_LoadEquipmentSets(QueryResult* result) +{ + // SetPQuery(PLAYER_LOGIN_QUERY_LOADEQUIPMENTSETS, "SELECT setguid, setindex, name, iconname, ignore_mask, item0, item1, item2, item3, item4, item5, item6, item7, item8, item9, item10, item11, item12, item13, item14, item15, item16, item17, item18 FROM character_equipmentsets WHERE guid = '%u' ORDER BY setindex", GUID_LOPART(m_guid)); + if (!result) + return; + + uint32 count = 0; + do + { + Field* fields = result->Fetch(); + + EquipmentSet eqSet; + + eqSet.Guid = fields[0].GetUInt64(); + uint32 index = fields[1].GetUInt32(); + eqSet.Name = fields[2].GetCppString(); + eqSet.IconName = fields[3].GetCppString(); + eqSet.IgnoreMask = fields[4].GetUInt32(); + eqSet.state = EQUIPMENT_SET_UNCHANGED; + + for (uint32 i = 0; i < EQUIPMENT_SLOT_END; ++i) + eqSet.Items[i] = fields[5 + i].GetUInt32(); + + m_EquipmentSets[index] = eqSet; + + ++count; + + if (count >= MAX_EQUIPMENT_SET_INDEX) // client limit + break; + } + while (result->NextRow()); + delete result; +} + +void Player::_LoadBGData(QueryResult* result) +{ + if (!result) + return; + + // Expecting only one row + Field* fields = result->Fetch(); + /* bgInstanceID, bgTeam, x, y, z, o, map, taxi[0], taxi[1], mountSpell */ + m_bgData.bgInstanceID = fields[0].GetUInt32(); + m_bgData.bgTeam = Team(fields[1].GetUInt32()); + m_bgData.joinPos = WorldLocation(fields[6].GetUInt32(), // Map + fields[2].GetFloat(), // X + fields[3].GetFloat(), // Y + fields[4].GetFloat(), // Z + fields[5].GetFloat()); // Orientation + m_bgData.taxiPath[0] = fields[7].GetUInt32(); + m_bgData.taxiPath[1] = fields[8].GetUInt32(); + m_bgData.mountSpell = fields[9].GetUInt32(); + + delete result; +} + +bool Player::LoadPositionFromDB(ObjectGuid guid, uint32& mapid, float& x, float& y, float& z, float& o, bool& in_flight) +{ + QueryResult* result = CharacterDatabase.PQuery("SELECT position_x,position_y,position_z,orientation,map,taxi_path FROM characters WHERE guid = '%u'", guid.GetCounter()); + if (!result) + return false; + + Field* fields = result->Fetch(); + + x = fields[0].GetFloat(); + y = fields[1].GetFloat(); + z = fields[2].GetFloat(); + o = fields[3].GetFloat(); + mapid = fields[4].GetUInt32(); + in_flight = !fields[5].GetCppString().empty(); + + delete result; + return true; +} + +void Player::_LoadIntoDataField(const char* data, uint32 startOffset, uint32 count) +{ + if (!data) + return; + + Tokens tokens = StrSplit(data, " "); + + if (tokens.size() != count) + return; + + Tokens::iterator iter; + uint32 index; + for (iter = tokens.begin(), index = 0; index < count; ++iter, ++index) + { + m_uint32Values[startOffset + index] = atol((*iter).c_str()); + } +} + +bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder) +{ + // 0 1 2 3 4 5 6 7 8 9 10 11 + // SELECT guid, account, name, race, class, gender, level, xp, money, playerBytes, playerBytes2, playerFlags," + // 12 13 14 15 16 17 18 19 20 21 22 23 24 + //"position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost," + // 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 + //"resettalents_time, primary_trees, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeon_difficulty," + // 40 41 42 43 44 45 + //"totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk," + // 46 47 48 49 50 51 52 53 54 55 56 57 58 + //"health, power1, power2, power3, power4, power5, specCount, activeSpec, exploredZones, equipmentCache, knownTitles, actionBars, slot FROM characters WHERE guid = '%u'", GUID_LOPART(m_guid)); + QueryResult *result = holder->GetResult(PLAYER_LOGIN_QUERY_LOADFROM); + + if (!result) + { + sLog.outError("%s not found in table `characters`, can't load. ", guid.GetString().c_str()); + return false; + } + + Field* fields = result->Fetch(); + + uint32 dbAccountId = fields[1].GetUInt32(); + + // check if the character's account in the db and the logged in account match. + // player should be able to load/delete character only with correct account! + if (dbAccountId != GetSession()->GetAccountId()) + { + sLog.outError("%s loading from wrong account (is: %u, should be: %u)", + guid.GetString().c_str(), GetSession()->GetAccountId(), dbAccountId); + delete result; + return false; + } + + Object::_Create(guid.GetCounter(), 0, HIGHGUID_PLAYER); + + m_name = fields[2].GetCppString(); + + // check name limitations + if (ObjectMgr::CheckPlayerName(m_name) != CHAR_NAME_SUCCESS || + (GetSession()->GetSecurity() == SEC_PLAYER && sObjectMgr.IsReservedName(m_name))) + { + delete result; + CharacterDatabase.PExecute("UPDATE characters SET at_login = at_login | '%u' WHERE guid ='%u'", + uint32(AT_LOGIN_RENAME), guid.GetCounter()); + return false; + } + + // overwrite possible wrong/corrupted guid + SetGuidValue(OBJECT_FIELD_GUID, guid); + + // overwrite some data fields + SetByteValue(UNIT_FIELD_BYTES_0, 0, fields[3].GetUInt8()); // race + SetByteValue(UNIT_FIELD_BYTES_0, 1, fields[4].GetUInt8()); // class + + uint8 gender = fields[5].GetUInt8() & 0x01; + SetByteValue(UNIT_FIELD_BYTES_0, 2, gender); // gender + + SetUInt32Value(UNIT_FIELD_LEVEL, fields[6].GetUInt8()); + SetUInt32Value(PLAYER_XP, fields[7].GetUInt32()); + + _LoadIntoDataField(fields[54].GetString(), PLAYER_EXPLORED_ZONES_1, PLAYER_EXPLORED_ZONES_SIZE); + _LoadIntoDataField(fields[56].GetString(), PLAYER__FIELD_KNOWN_TITLES, KNOWN_TITLES_SIZE*2); + + InitDisplayIds(); // model, scale and model data + + SetFloatValue(UNIT_FIELD_HOVERHEIGHT, 1.0f); + + // just load criteria/achievement data, safe call before any load, and need, because some spell/item/quest loading + // can triggering achievement criteria update that will be lost if this call will later + m_achievementMgr.LoadFromDB(holder->GetResult(PLAYER_LOGIN_QUERY_LOADACHIEVEMENTS), holder->GetResult(PLAYER_LOGIN_QUERY_LOADCRITERIAPROGRESS)); + + uint64 money = fields[8].GetUInt64(); + if (money > MAX_MONEY_AMOUNT) + money = MAX_MONEY_AMOUNT; + SetMoney(money); + + SetUInt32Value(PLAYER_BYTES, fields[9].GetUInt32()); + SetUInt32Value(PLAYER_BYTES_2, fields[10].GetUInt32()); + + SetByteValue(PLAYER_BYTES_3, 0, gender); + SetByteValue(PLAYER_BYTES_3, 1, fields[45].GetUInt8()); + + SetUInt32Value(PLAYER_FLAGS, fields[11].GetUInt32()); + SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, fields[44].GetInt32()); + + // Action bars state + SetByteValue(PLAYER_FIELD_BYTES, 2, fields[57].GetUInt8()); + + m_slot = fields[58].GetUInt8(); + + // cleanup inventory related item value fields (its will be filled correctly in _LoadInventory) + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), ObjectGuid()); + SetVisibleItemSlot(slot, NULL); + + delete m_items[slot]; + m_items[slot] = NULL; + } + + DEBUG_FILTER_LOG(LOG_FILTER_PLAYER_STATS, "Load Basic value of player %s is: ", m_name.c_str()); + outDebugStatsValues(); + + // Need to call it to initialize m_team (m_team can be calculated from race) + // Other way is to saves m_team into characters table. + setFactionForRace(getRace()); + SetCharm(NULL); + + // load home bind and check in same time class/race pair, it used later for restore broken positions + if (!_LoadHomeBind(holder->GetResult(PLAYER_LOGIN_QUERY_LOADHOMEBIND))) + { + delete result; + return false; + } + + InitPrimaryProfessions(); // to max set before any spell loaded + + // init saved position, and fix it later if problematic + uint32 transGUID = fields[31].GetUInt32(); + Relocate(fields[12].GetFloat(), fields[13].GetFloat(), fields[14].GetFloat(), fields[16].GetFloat()); + SetLocationMapId(fields[15].GetUInt32()); + + uint32 difficulty = fields[39].GetUInt32(); + if (difficulty >= MAX_DUNGEON_DIFFICULTY || getLevel() < LEVELREQUIREMENT_HEROIC) + difficulty = DUNGEON_DIFFICULTY_NORMAL; + SetDungeonDifficulty(Difficulty(difficulty)); // may be changed in _LoadGroup + + _LoadGroup(holder->GetResult(PLAYER_LOGIN_QUERY_LOADGROUP)); + + _LoadArenaTeamInfo(holder->GetResult(PLAYER_LOGIN_QUERY_LOADARENAINFO)); + + // check arena teams integrity + for (uint32 arena_slot = 0; arena_slot < MAX_ARENA_SLOT; ++arena_slot) + { + uint32 arena_team_id = GetArenaTeamId(arena_slot); + if (!arena_team_id) + continue; + + if (ArenaTeam* at = sObjectMgr.GetArenaTeamById(arena_team_id)) + if (at->HaveMember(GetObjectGuid())) + continue; + + // arena team not exist or not member, cleanup fields + for (int j = 0; j < ARENA_TEAM_END; ++j) + SetArenaTeamInfoField(arena_slot, ArenaTeamInfoType(j), 0); + } + + SetUInt32Value(PLAYER_FIELD_LIFETIME_HONORBALE_KILLS, fields[40].GetUInt32()); + SetUInt16Value(PLAYER_FIELD_KILLS, 0, fields[41].GetUInt16()); // today + SetUInt16Value(PLAYER_FIELD_KILLS, 1, fields[42].GetUInt16()); // yesterday + + _LoadBoundInstances(holder->GetResult(PLAYER_LOGIN_QUERY_LOADBOUNDINSTANCES)); + + if (!IsPositionValid()) + { + sLog.outError("%s have invalid coordinates (X: %f Y: %f Z: %f O: %f). Teleport to default race/class locations.", + guid.GetString().c_str(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation()); + RelocateToHomebind(); + + transGUID = 0; + + m_movementInfo.ClearTransportData(); + } + + _LoadBGData(holder->GetResult(PLAYER_LOGIN_QUERY_LOADBGDATA)); + + if (m_bgData.bgInstanceID) // saved in BattleGround + { + BattleGround* currentBg = sBattleGroundMgr.GetBattleGround(m_bgData.bgInstanceID, BATTLEGROUND_TYPE_NONE); + + bool player_at_bg = currentBg && currentBg->IsPlayerInBattleGround(GetObjectGuid()); + + if (player_at_bg && currentBg->GetStatus() != STATUS_WAIT_LEAVE) + { + BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BGQueueTypeId(currentBg->GetTypeID(), currentBg->GetArenaType()); + AddBattleGroundQueueId(bgQueueTypeId); + + m_bgData.bgTypeID = currentBg->GetTypeID(); // bg data not marked as modified + + // join player to battleground group + currentBg->EventPlayerLoggedIn(this, GetObjectGuid()); + currentBg->AddOrSetPlayerToCorrectBgGroup(this, GetObjectGuid(), m_bgData.bgTeam); + + SetInviteForBattleGroundQueueType(bgQueueTypeId, currentBg->GetInstanceID()); + } + else + { + // leave bg + if (player_at_bg) + currentBg->RemovePlayerAtLeave(GetObjectGuid(), false, true); + + // move to bg enter point + const WorldLocation& _loc = GetBattleGroundEntryPoint(); + SetLocationMapId(_loc.mapid); + Relocate(_loc.coord_x, _loc.coord_y, _loc.coord_z, _loc.orientation); + + // We are not in BG anymore + SetBattleGroundId(0, BATTLEGROUND_TYPE_NONE); + // remove outdated DB data in DB + _SaveBGData(); + } + } + else + { + MapEntry const* mapEntry = sMapStore.LookupEntry(GetMapId()); + // if server restart after player save in BG or area + // player can have current coordinates in to BG/Arena map, fix this + if (!mapEntry || mapEntry->IsBattleGroundOrArena()) + { + const WorldLocation& _loc = GetBattleGroundEntryPoint(); + SetLocationMapId(_loc.mapid); + Relocate(_loc.coord_x, _loc.coord_y, _loc.coord_z, _loc.orientation); + + // We are not in BG anymore + SetBattleGroundId(0, BATTLEGROUND_TYPE_NONE); + // remove outdated DB data in DB + _SaveBGData(); + } + } + + if (transGUID != 0) + { + m_movementInfo.SetTransportData(ObjectGuid(HIGHGUID_MO_TRANSPORT, transGUID), fields[27].GetFloat(), fields[28].GetFloat(), fields[29].GetFloat(), fields[30].GetFloat(), 0, -1); + + if (!MaNGOS::IsValidMapCoord( + GetPositionX() + m_movementInfo.GetTransportPos()->x, GetPositionY() + m_movementInfo.GetTransportPos()->y, + GetPositionZ() + m_movementInfo.GetTransportPos()->z, GetOrientation() + m_movementInfo.GetTransportPos()->o) || + // transport size limited + m_movementInfo.GetTransportPos()->x > 50 || m_movementInfo.GetTransportPos()->y > 50 || m_movementInfo.GetTransportPos()->z > 50) + { + sLog.outError("%s have invalid transport coordinates (X: %f Y: %f Z: %f O: %f). Teleport to default race/class locations.", + guid.GetString().c_str(), GetPositionX() + m_movementInfo.GetTransportPos()->x, GetPositionY() + m_movementInfo.GetTransportPos()->y, + GetPositionZ() + m_movementInfo.GetTransportPos()->z, GetOrientation() + m_movementInfo.GetTransportPos()->o); + + RelocateToHomebind(); + + m_movementInfo.ClearTransportData(); + + transGUID = 0; + } + } + + if (transGUID != 0) + { + for (MapManager::TransportSet::const_iterator iter = sMapMgr.m_Transports.begin(); iter != sMapMgr.m_Transports.end(); ++iter) + { + if ((*iter)->GetGUIDLow() == transGUID) + { + MapEntry const* transMapEntry = sMapStore.LookupEntry((*iter)->GetMapId()); + // client without expansion support + if (GetSession()->Expansion() < transMapEntry->Expansion()) + { + DEBUG_LOG("Player %s using client without required expansion tried login at transport at non accessible map %u", GetName(), (*iter)->GetMapId()); + break; + } + + m_transport = *iter; + m_transport->AddPassenger(this); + SetLocationMapId(m_transport->GetMapId()); + break; + } + } + + if (!m_transport) + { + sLog.outError("%s have problems with transport guid (%u). Teleport to default race/class locations.", + guid.GetString().c_str(), transGUID); + + RelocateToHomebind(); + + m_movementInfo.ClearTransportData(); + + transGUID = 0; + } + } + else // not transport case + { + MapEntry const* mapEntry = sMapStore.LookupEntry(GetMapId()); + // client without expansion support + if (GetSession()->Expansion() < mapEntry->Expansion()) + { + DEBUG_LOG("Player %s using client without required expansion tried login at non accessible map %u", GetName(), GetMapId()); + RelocateToHomebind(); + } + } + + // player bounded instance saves loaded in _LoadBoundInstances, group versions at group loading + DungeonPersistentState* state = GetBoundInstanceSaveForSelfOrGroup(GetMapId()); + + // load the player's map here if it's not already loaded + SetMap(sMapMgr.CreateMap(GetMapId(), this)); + + // if the player is in an instance and it has been reset in the meantime teleport him to the entrance + if (GetInstanceId() && !state) + { + AreaTrigger const* at = sObjectMgr.GetMapEntranceTrigger(GetMapId()); + if (at) + Relocate(at->target_X, at->target_Y, at->target_Z, at->target_Orientation); + else + sLog.outError("Player %s(GUID: %u) logged in to a reset instance (map: %u) and there is no area-trigger leading to this map. Thus he can't be ported back to the entrance. This _might_ be an exploit attempt.", GetName(), GetGUIDLow(), GetMapId()); + } + + SaveRecallPosition(); + + time_t now = time(NULL); + time_t logoutTime = time_t(fields[22].GetUInt64()); + + // since last logout (in seconds) + uint32 time_diff = uint32(now - logoutTime); + + // set value, including drunk invisibility detection + // calculate sobering. after 15 minutes logged out, the player will be sober again + uint8 newDrunkValue = 0; + if (time_diff < uint32(GetDrunkValue()) * 9) + newDrunkValue = GetDrunkValue() - time_diff / 9; + + SetDrunkValue(newDrunkValue); + + m_cinematic = fields[18].GetUInt32(); + m_Played_time[PLAYED_TIME_TOTAL] = fields[19].GetUInt32(); + m_Played_time[PLAYED_TIME_LEVEL] = fields[20].GetUInt32(); + + m_resetTalentsCost = fields[24].GetUInt32(); + m_resetTalentsTime = time_t(fields[25].GetUInt64()); + + // reserve some flags + uint32 old_safe_flags = GetUInt32Value(PLAYER_FLAGS) & (PLAYER_FLAGS_HIDE_CLOAK | PLAYER_FLAGS_HIDE_HELM); + + if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GM)) + SetUInt32Value(PLAYER_FLAGS, 0 | old_safe_flags); + + m_taxi.LoadTaxiMask(fields[17].GetString()); // must be before InitTaxiNodesForLevel + + uint32 extraflags = fields[32].GetUInt32(); + + m_stableSlots = fields[33].GetUInt32(); + if (m_stableSlots > MAX_PET_STABLES) + { + sLog.outError("Player can have not more %u stable slots, but have in DB %u", MAX_PET_STABLES, uint32(m_stableSlots)); + m_stableSlots = MAX_PET_STABLES; + } + + m_atLoginFlags = fields[34].GetUInt32(); + + // Update Honor kills data + m_lastHonorKillsUpdateTime = logoutTime; + UpdateHonorKills(); + + m_deathExpireTime = (time_t)fields[37].GetUInt64(); + if (m_deathExpireTime > now + MAX_DEATH_COUNT * DEATH_EXPIRE_STEP) + m_deathExpireTime = now + MAX_DEATH_COUNT * DEATH_EXPIRE_STEP - 1; + + std::string taxi_nodes = fields[38].GetCppString(); + + // clear channel spell data (if saved at channel spell casting) + SetChannelObjectGuid(ObjectGuid()); + SetUInt32Value(UNIT_CHANNEL_SPELL, 0); + + // clear charm/summon related fields + SetCharm(NULL); + SetPet(NULL); + SetTargetGuid(ObjectGuid()); + SetCharmerGuid(ObjectGuid()); + SetOwnerGuid(ObjectGuid()); + SetCreatorGuid(ObjectGuid()); + + // reset some aura modifiers before aura apply + + SetGuidValue(PLAYER_FARSIGHT, ObjectGuid()); + SetUInt32Value(PLAYER_TRACK_CREATURES, 0); + SetUInt32Value(PLAYER_TRACK_RESOURCES, 0); + + // cleanup aura list explicitly before skill load where some spells can be applied + RemoveAllAuras(); + + // make sure the unit is considered out of combat for proper loading + ClearInCombat(); + + // make sure the unit is considered not in duel for proper loading + SetGuidValue(PLAYER_DUEL_ARBITER, ObjectGuid()); + SetUInt32Value(PLAYER_DUEL_TEAM, 0); + + m_specsCount = fields[52].GetUInt8(); + m_activeSpec = fields[53].GetUInt8(); + + Tokens talentTrees = StrSplit(fields[26].GetString(), " "); + for (uint8 i = 0; i < MAX_TALENT_SPEC_COUNT; ++i) + { + if (i >= talentTrees.size()) + break; + + uint32 talentTree = atol(talentTrees[i].c_str()); + if (!talentTree || sTalentTabStore.LookupEntry(talentTree)) + m_talentsPrimaryTree[i] = talentTree; + else if (i == m_activeSpec) + SetAtLoginFlag(AT_LOGIN_RESET_TALENTS); // invalid tree, reset talents + } + + // reset stats before loading any modifiers + InitStatsForLevel(); + InitGlyphsForLevel(); + InitTaxiNodesForLevel(); + InitRunes(); + + // rest bonus can only be calculated after InitStatsForLevel() + m_rest_bonus = fields[21].GetFloat(); + + if (time_diff > 0) + { + // speed collect rest bonus in offline, in logout, far from tavern, city (section/in hour) + float bubble0 = 0.031f; + // speed collect rest bonus in offline, in logout, in tavern, city (section/in hour) + float bubble1 = 0.125f; + float bubble = fields[23].GetUInt32() > 0 + ? bubble1 * sWorld.getConfig(CONFIG_FLOAT_RATE_REST_OFFLINE_IN_TAVERN_OR_CITY) + : bubble0 * sWorld.getConfig(CONFIG_FLOAT_RATE_REST_OFFLINE_IN_WILDERNESS); + + SetRestBonus(GetRestBonus() + time_diff * ((float)GetUInt32Value(PLAYER_NEXT_LEVEL_XP) / 72000)*bubble); + } + + // load skills after InitStatsForLevel because it triggering aura apply also + _LoadSkills(holder->GetResult(PLAYER_LOGIN_QUERY_LOADSKILLS)); + + // apply original stats mods before spell loading or item equipment that call before equip _RemoveStatsMods() + + // Mail + _LoadMails(holder->GetResult(PLAYER_LOGIN_QUERY_LOADMAILS)); + _LoadMailedItems(holder->GetResult(PLAYER_LOGIN_QUERY_LOADMAILEDITEMS)); + UpdateNextMailTimeAndUnreads(); + + _LoadGlyphs(holder->GetResult(PLAYER_LOGIN_QUERY_LOADGLYPHS)); + + _LoadAuras(holder->GetResult(PLAYER_LOGIN_QUERY_LOADAURAS), time_diff); + ApplyGlyphs(true); + + // add ghost flag (must be after aura load: PLAYER_FLAGS_GHOST set in aura) + if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + m_deathState = DEAD; + + _LoadCurrencies(holder->GetResult(PLAYER_LOGIN_QUERY_LOADCURRENCIES)); + _LoadSpells(holder->GetResult(PLAYER_LOGIN_QUERY_LOADSPELLS)); + + // after spell load, learn rewarded spell if need also + _LoadQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADQUESTSTATUS)); + _LoadDailyQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADDAILYQUESTSTATUS)); + _LoadWeeklyQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADWEEKLYQUESTSTATUS)); + _LoadMonthlyQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADMONTHLYQUESTSTATUS)); + + _LoadTalents(holder->GetResult(PLAYER_LOGIN_QUERY_LOADTALENTS)); + + // after spell and quest load + InitTalentForLevel(); + learnDefaultSpells(); + + // must be before inventory (some items required reputation check) + m_reputationMgr.LoadFromDB(holder->GetResult(PLAYER_LOGIN_QUERY_LOADREPUTATION)); + + _LoadInventory(holder->GetResult(PLAYER_LOGIN_QUERY_LOADINVENTORY), time_diff); + _LoadItemLoot(holder->GetResult(PLAYER_LOGIN_QUERY_LOADITEMLOOT)); + + // update items with duration and realtime + UpdateItemDuration(time_diff, true); + + _LoadActions(holder->GetResult(PLAYER_LOGIN_QUERY_LOADACTIONS)); + + m_social = sSocialMgr.LoadFromDB(holder->GetResult(PLAYER_LOGIN_QUERY_LOADSOCIALLIST), GetObjectGuid()); + + // check PLAYER_CHOSEN_TITLE compatibility with PLAYER__FIELD_KNOWN_TITLES + // note: PLAYER__FIELD_KNOWN_TITLES updated at quest status loaded + uint32 curTitle = fields[43].GetUInt32(); + if (curTitle && !HasTitle(curTitle)) + curTitle = 0; + + SetUInt32Value(PLAYER_CHOSEN_TITLE, curTitle); + + // Not finish taxi flight path + if (m_bgData.HasTaxiPath()) + { + m_taxi.ClearTaxiDestinations(); + for (int i = 0; i < 2; ++i) + m_taxi.AddTaxiDestination(m_bgData.taxiPath[i]); + } + else if (!m_taxi.LoadTaxiDestinationsFromString(taxi_nodes, GetTeam())) + { + // problems with taxi path loading + TaxiNodesEntry const* nodeEntry = NULL; + if (uint32 node_id = m_taxi.GetTaxiSource()) + nodeEntry = sTaxiNodesStore.LookupEntry(node_id); + + if (!nodeEntry) // don't know taxi start node, to homebind + { + sLog.outError("Character %u have wrong data in taxi destination list, teleport to homebind.", GetGUIDLow()); + RelocateToHomebind(); + } + else // have start node, to it + { + sLog.outError("Character %u have too short taxi destination list, teleport to original node.", GetGUIDLow()); + SetLocationMapId(nodeEntry->map_id); + Relocate(nodeEntry->x, nodeEntry->y, nodeEntry->z, 0.0f); + } + + // we can be relocated from taxi and still have an outdated Map pointer! + // so we need to get a new Map pointer! + SetMap(sMapMgr.CreateMap(GetMapId(), this)); + SaveRecallPosition(); // save as recall also to prevent recall and fall from sky + + m_taxi.ClearTaxiDestinations(); + } + + if (uint32 node_id = m_taxi.GetTaxiSource()) + { + // save source node as recall coord to prevent recall and fall from sky + TaxiNodesEntry const* nodeEntry = sTaxiNodesStore.LookupEntry(node_id); + MANGOS_ASSERT(nodeEntry); // checked in m_taxi.LoadTaxiDestinationsFromString + m_recallMap = nodeEntry->map_id; + m_recallX = nodeEntry->x; + m_recallY = nodeEntry->y; + m_recallZ = nodeEntry->z; + + // flight will started later + } + + // has to be called after last Relocate() in Player::LoadFromDB + SetFallInformation(0, GetPositionZ()); + + _LoadSpellCooldowns(holder->GetResult(PLAYER_LOGIN_QUERY_LOADSPELLCOOLDOWNS)); + + // Spell code allow apply any auras to dead character in load time in aura/spell/item loading + // Do now before stats re-calculation cleanup for ghost state unexpected auras + if (!isAlive()) + RemoveAllAurasOnDeath(); + + // apply all stat bonuses from items and auras + SetCanModifyStats(true); + UpdateAllStats(); + + // restore remembered power/health values (but not more max values) + uint32 savedhealth = fields[46].GetUInt32(); + SetHealth(savedhealth > GetMaxHealth() ? GetMaxHealth() : savedhealth); + + COMPILE_ASSERT(MAX_STORED_POWERS == 5, "Query not updated."); + for (uint32 i = 0; i < MAX_STORED_POWERS; ++i) + { + uint32 savedpower = fields[47 + i].GetUInt32(); + SetPowerByIndex(i, std::min(savedpower, GetMaxPowerByIndex(i))); + } + + DEBUG_FILTER_LOG(LOG_FILTER_PLAYER_STATS, "The value of player %s after load item and aura is: ", m_name.c_str()); + outDebugStatsValues(); + + // all fields read + delete result; + + // GM state + if (GetSession()->GetSecurity() > SEC_PLAYER) + { + switch (sWorld.getConfig(CONFIG_UINT32_GM_LOGIN_STATE)) + { + default: + case 0: break; // disable + case 1: SetGameMaster(true); break; // enable + case 2: // save state + if (extraflags & PLAYER_EXTRA_GM_ON) + SetGameMaster(true); + break; + } + + switch (sWorld.getConfig(CONFIG_UINT32_GM_VISIBLE_STATE)) + { + default: + case 0: SetGMVisible(false); break; // invisible + case 1: break; // visible + case 2: // save state + if (extraflags & PLAYER_EXTRA_GM_INVISIBLE) + SetGMVisible(false); + break; + } + + switch (sWorld.getConfig(CONFIG_UINT32_GM_ACCEPT_TICKETS)) + { + default: + case 0: break; // disable + case 1: SetAcceptTicket(true); break; // enable + case 2: // save state + if (extraflags & PLAYER_EXTRA_GM_ACCEPT_TICKETS) + SetAcceptTicket(true); + break; + } + + switch (sWorld.getConfig(CONFIG_UINT32_GM_CHAT)) + { + default: + case 0: break; // disable + case 1: SetGMChat(true); break; // enable + case 2: // save state + if (extraflags & PLAYER_EXTRA_GM_CHAT) + SetGMChat(true); + break; + } + + switch (sWorld.getConfig(CONFIG_UINT32_GM_WISPERING_TO)) + { + default: + case 0: break; // disable + case 1: SetAcceptWhispers(true); break; // enable + case 2: // save state + if (extraflags & PLAYER_EXTRA_ACCEPT_WHISPERS) + SetAcceptWhispers(true); + break; + } + } + + _LoadDeclinedNames(holder->GetResult(PLAYER_LOGIN_QUERY_LOADDECLINEDNAMES)); + + m_achievementMgr.CheckAllAchievementCriteria(); + + _LoadEquipmentSets(holder->GetResult(PLAYER_LOGIN_QUERY_LOADEQUIPMENTSETS)); + + return true; +} + +bool Player::isAllowedToLoot(Creature* creature) +{ + // never tapped by any (mob solo kill) + if (!creature->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TAPPED)) + return false; + + if (Player* recipient = creature->GetLootRecipient()) + { + if (recipient == this) + return true; + + if (Group* otherGroup = recipient->GetGroup()) + { + Group* thisGroup = GetGroup(); + if (!thisGroup) + return false; + + return thisGroup == otherGroup; + } + return false; + } + else + // prevent other players from looting if the recipient got disconnected + return !creature->HasLootRecipient(); +} + +void Player::_LoadActions(QueryResult* result) +{ + for (int i = 0; i < MAX_TALENT_SPEC_COUNT; ++i) + m_actionButtons[i].clear(); + + // QueryResult *result = CharacterDatabase.PQuery("SELECT spec, button,action,type FROM character_action WHERE guid = '%u' ORDER BY button",GetGUIDLow()); + + if (result) + { + do + { + Field* fields = result->Fetch(); + + uint8 spec = fields[0].GetUInt8(); + uint8 button = fields[1].GetUInt8(); + uint32 action = fields[2].GetUInt32(); + uint8 type = fields[3].GetUInt8(); + + if (ActionButton* ab = addActionButton(spec, button, action, type)) + ab->uState = ACTIONBUTTON_UNCHANGED; + else + { + sLog.outError(" ...at loading, and will deleted in DB also"); + + // Will deleted in DB at next save (it can create data until save but marked as deleted) + m_actionButtons[spec][button].uState = ACTIONBUTTON_DELETED; + } + } + while (result->NextRow()); + + delete result; + } +} + +void Player::_LoadAuras(QueryResult* result, uint32 timediff) +{ + // RemoveAllAuras(); -- some spells casted before aura load, for example in LoadSkills, aura list explicitly cleaned early + + // QueryResult *result = CharacterDatabase.PQuery("SELECT caster_guid,item_guid,spell,stackcount,remaincharges,basepoints0,basepoints1,basepoints2,periodictime0,periodictime1,periodictime2,maxduration,remaintime,effIndexMask FROM character_aura WHERE guid = '%u'",GetGUIDLow()); + + if (result) + { + do + { + Field* fields = result->Fetch(); + ObjectGuid caster_guid = ObjectGuid(fields[0].GetUInt64()); + uint32 item_lowguid = fields[1].GetUInt32(); + uint32 spellid = fields[2].GetUInt32(); + uint32 stackcount = fields[3].GetUInt32(); + uint32 remaincharges = fields[4].GetUInt32(); + int32 damage[MAX_EFFECT_INDEX]; + uint32 periodicTime[MAX_EFFECT_INDEX]; + + for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i) + { + damage[i] = fields[i + 5].GetInt32(); + periodicTime[i] = fields[i + 8].GetUInt32(); + } + + int32 maxduration = fields[11].GetInt32(); + int32 remaintime = fields[12].GetInt32(); + uint32 effIndexMask = fields[13].GetUInt32(); + + SpellEntry const* spellproto = sSpellStore.LookupEntry(spellid); + if (!spellproto) + { + sLog.outError("Unknown spell (spellid %u), ignore.", spellid); + continue; + } + + if (remaintime != -1 && !IsPositiveSpell(spellproto)) + { + if (remaintime / IN_MILLISECONDS <= int32(timediff)) + continue; + + remaintime -= timediff * IN_MILLISECONDS; + } + + // prevent wrong values of remaincharges + if (uint32 procCharges = spellproto->GetProcCharges()) + { + if (remaincharges <= 0 || remaincharges > procCharges) + remaincharges = procCharges; + } + else + remaincharges = 0; + + uint32 defstackamount = spellproto->GetStackAmount(); + if (!defstackamount) + stackcount = 1; + else if (defstackamount < stackcount) + stackcount = defstackamount; + else if (!stackcount) + stackcount = 1; + + SpellAuraHolder* holder = CreateSpellAuraHolder(spellproto, this, NULL); + holder->SetLoadedState(caster_guid, ObjectGuid(HIGHGUID_ITEM, item_lowguid), stackcount, remaincharges, maxduration, remaintime); + + for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i) + { + if ((effIndexMask & (1 << i)) == 0) + continue; + + Aura* aura = CreateAura(spellproto, SpellEffectIndex(i), NULL, holder, this); + if (!damage[i]) + damage[i] = aura->GetModifier()->m_amount; + + aura->SetLoadedState(damage[i], periodicTime[i]); + holder->AddAura(aura, SpellEffectIndex(i)); + } + + if (!holder->IsEmptyHolder()) + { + // reset stolen single target auras + if (caster_guid != GetObjectGuid() && holder->GetTrackedAuraType() == TRACK_AURA_TYPE_SINGLE_TARGET) + holder->SetTrackedAuraType(TRACK_AURA_TYPE_NOT_TRACKED); + + AddSpellAuraHolder(holder); + DETAIL_LOG("Added auras from spellid %u", spellproto->Id); + } + else + delete holder; + } + while (result->NextRow()); + delete result; + } + + if (getClass() == CLASS_WARRIOR && !HasAuraType(SPELL_AURA_MOD_SHAPESHIFT)) + CastSpell(this, SPELL_ID_PASSIVE_BATTLE_STANCE, true); +} + +void Player::_LoadGlyphs(QueryResult* result) +{ + if (!result) + return; + + // 0 1 2 + // "SELECT spec, slot, glyph FROM character_glyphs WHERE guid='%u'" + + do + { + Field* fields = result->Fetch(); + uint8 spec = fields[0].GetUInt8(); + uint8 slot = fields[1].GetUInt8(); + uint32 glyph = fields[2].GetUInt32(); + + GlyphPropertiesEntry const* gp = sGlyphPropertiesStore.LookupEntry(glyph); + if (!gp) + { + sLog.outError("Player %s has not existing glyph entry %u on index %u, spec %u", m_name.c_str(), glyph, slot, spec); + CharacterDatabase.PExecute("DELETE FROM character_glyphs WHERE glyph = %u", glyph); + continue; + } + + GlyphSlotEntry const* gs = sGlyphSlotStore.LookupEntry(GetGlyphSlot(slot)); + if (!gs) + { + sLog.outError("Player %s has not existing glyph slot entry %u on index %u, spec %u", m_name.c_str(), GetGlyphSlot(slot), slot, spec); + CharacterDatabase.PExecute("DELETE FROM character_glyphs WHERE slot = %u AND spec = %u AND guid = %u", slot, spec, GetGUIDLow()); + continue; + } + + if (gp->TypeFlags != gs->TypeFlags) + { + sLog.outError("Player %s has glyph with typeflags %u in slot with typeflags %u, removing.", m_name.c_str(), gp->TypeFlags, gs->TypeFlags); + CharacterDatabase.PExecute("DELETE FROM character_glyphs WHERE slot = %u AND spec = %u AND guid = %u", slot, spec, GetGUIDLow()); + continue; + } + + m_glyphs[spec][slot].id = glyph; + } + while (result->NextRow()); + + delete result; +} + +void Player::LoadCorpse() +{ + if (isAlive()) + { + sObjectAccessor.ConvertCorpseForPlayer(GetObjectGuid()); + } + else + { + if (Corpse* corpse = GetCorpse()) + { + ApplyModByteFlag(PLAYER_FIELD_BYTES, 0, PLAYER_FIELD_BYTE_RELEASE_TIMER, corpse && !sMapStore.LookupEntry(corpse->GetMapId())->Instanceable()); + } + else + { + // Prevent Dead Player login without corpse + ResurrectPlayer(0.5f); + } + } +} + +void Player::_LoadInventory(QueryResult* result, uint32 timediff) +{ + // QueryResult *result = CharacterDatabase.PQuery("SELECT data,text,bag,slot,item,item_template FROM character_inventory JOIN item_instance ON character_inventory.item = item_instance.guid WHERE character_inventory.guid = '%u' ORDER BY bag,slot", GetGUIDLow()); + std::map bagMap; // fast guid lookup for bags + // NOTE: the "order by `bag`" is important because it makes sure + // the bagMap is filled before items in the bags are loaded + // NOTE2: the "order by `slot`" is needed because mainhand weapons are (wrongly?) + // expected to be equipped before offhand items (TODO: fixme) + + uint32 zone = GetZoneId(); + + if (result) + { + std::list problematicItems; + + // prevent items from being added to the queue when stored + m_itemUpdateQueueBlocked = true; + do + { + Field* fields = result->Fetch(); + uint32 bag_guid = fields[2].GetUInt32(); + uint8 slot = fields[3].GetUInt8(); + uint32 item_lowguid = fields[4].GetUInt32(); + uint32 item_id = fields[5].GetUInt32(); + + ItemPrototype const* proto = ObjectMgr::GetItemPrototype(item_id); + + if (!proto) + { + CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_lowguid); + CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid = '%u'", item_lowguid); + sLog.outError("Player::_LoadInventory: Player %s has an unknown item (id: #%u) in inventory, deleted.", GetName(), item_id); + continue; + } + + Item* item = NewItemOrBag(proto); + + if (!item->LoadFromDB(item_lowguid, fields, GetObjectGuid())) + { + sLog.outError("Player::_LoadInventory: Player %s has broken item (id: #%u) in inventory, deleted.", GetName(), item_id); + CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_lowguid); + item->FSetState(ITEM_REMOVED); + item->SaveToDB(); // it also deletes item object ! + continue; + } + + // not allow have in alive state item limited to another map/zone + if (isAlive() && item->IsLimitedToAnotherMapOrZone(GetMapId(), zone)) + { + CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_lowguid); + item->FSetState(ITEM_REMOVED); + item->SaveToDB(); // it also deletes item object ! + continue; + } + + // "Conjured items disappear if you are logged out for more than 15 minutes" + if (timediff > 15 * MINUTE && (item->GetProto()->Flags & ITEM_FLAG_CONJURED)) + { + CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_lowguid); + item->FSetState(ITEM_REMOVED); + item->SaveToDB(); // it also deletes item object ! + continue; + } + + bool success = true; + + // the item/bag is not in a bag + if (!bag_guid) + { + item->SetContainer(NULL); + item->SetSlot(slot); + + if (IsInventoryPos(INVENTORY_SLOT_BAG_0, slot)) + { + ItemPosCountVec dest; + if (CanStoreItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false) == EQUIP_ERR_OK) + item = StoreItem(dest, item, true); + else + success = false; + } + else if (IsEquipmentPos(INVENTORY_SLOT_BAG_0, slot)) + { + uint16 dest; + if (CanEquipItem(slot, dest, item, false, false) == EQUIP_ERR_OK) + QuickEquipItem(dest, item); + else + success = false; + } + else if (IsBankPos(INVENTORY_SLOT_BAG_0, slot)) + { + ItemPosCountVec dest; + if (CanBankItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false, false) == EQUIP_ERR_OK) + item = BankItem(dest, item, true); + else + success = false; + } + + if (success) + { + // store bags that may contain items in them + if (item->IsBag() && IsBagPos(item->GetPos())) + bagMap[item_lowguid] = (Bag*)item; + } + } + // the item/bag in a bag + else + { + item->SetSlot(NULL_SLOT); + // the item is in a bag, find the bag + std::map::const_iterator itr = bagMap.find(bag_guid); + if (itr != bagMap.end() && slot < itr->second->GetBagSize()) + { + ItemPosCountVec dest; + if (CanStoreItem(itr->second->GetSlot(), slot, dest, item, false) == EQUIP_ERR_OK) + item = StoreItem(dest, item, true); + else + success = false; + } + else + success = false; + } + + // item's state may have changed after stored + if (success) + { + item->SetState(ITEM_UNCHANGED, this); + + // restore container unchanged state also + if (item->GetContainer()) + item->GetContainer()->SetState(ITEM_UNCHANGED, this); + + // recharged mana gem + if (timediff > 15 * MINUTE && proto->ItemLimitCategory == ITEM_LIMIT_CATEGORY_MANA_GEM) + item->RestoreCharges(); + } + else + { + sLog.outError("Player::_LoadInventory: Player %s has item (GUID: %u Entry: %u) can't be loaded to inventory (Bag GUID: %u Slot: %u) by some reason, will send by mail.", GetName(), item_lowguid, item_id, bag_guid, slot); + CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_lowguid); + problematicItems.push_back(item); + } + } + while (result->NextRow()); + + delete result; + m_itemUpdateQueueBlocked = false; + + // send by mail problematic items + while (!problematicItems.empty()) + { + std::string subject = GetSession()->GetMangosString(LANG_NOT_EQUIPPED_ITEM); + + // fill mail + MailDraft draft(subject, "There's were problems with equipping item(s)."); + + for (int i = 0; !problematicItems.empty() && i < MAX_MAIL_ITEMS; ++i) + { + Item* item = problematicItems.front(); + problematicItems.pop_front(); + + draft.AddItem(item); + } + + draft.SendMailTo(this, MailSender(this, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_COPIED); + } + } + + // if(isAlive()) + _ApplyAllItemMods(); +} + +void Player::_LoadItemLoot(QueryResult* result) +{ + // QueryResult *result = CharacterDatabase.PQuery("SELECT guid,itemid,amount,suffix,property FROM item_loot WHERE guid = '%u'", GetGUIDLow()); + + if (result) + { + do + { + Field* fields = result->Fetch(); + uint32 item_guid = fields[0].GetUInt32(); + + Item* item = GetItemByGuid(ObjectGuid(HIGHGUID_ITEM, item_guid)); + + if (!item) + { + CharacterDatabase.PExecute("DELETE FROM item_loot WHERE guid = '%u'", item_guid); + sLog.outError("Player::_LoadItemLoot: Player %s has loot for nonexistent item (GUID: %u) in `item_loot`, deleted.", GetName(), item_guid); + continue; + } + + item->LoadLootFromDB(fields); + } + while (result->NextRow()); + + delete result; + } +} + +// load mailed item which should receive current player +void Player::_LoadMailedItems(QueryResult* result) +{ + // data needs to be at first place for Item::LoadFromDB + // 0 1 2 3 4 + // "SELECT data, text, mail_id, item_guid, item_template FROM mail_items JOIN item_instance ON item_guid = guid WHERE receiver = '%u'", GUID_LOPART(m_guid) + if (!result) + return; + + do + { + Field* fields = result->Fetch(); + uint32 mail_id = fields[2].GetUInt32(); + uint32 item_guid_low = fields[3].GetUInt32(); + uint32 item_template = fields[4].GetUInt32(); + + Mail* mail = GetMail(mail_id); + if (!mail) + continue; + mail->AddItem(item_guid_low, item_template); + + ItemPrototype const* proto = ObjectMgr::GetItemPrototype(item_template); + + if (!proto) + { + sLog.outError("Player %u has unknown item_template (ProtoType) in mailed items(GUID: %u template: %u) in mail (%u), deleted.", GetGUIDLow(), item_guid_low, item_template, mail->messageID); + CharacterDatabase.PExecute("DELETE FROM mail_items WHERE item_guid = '%u'", item_guid_low); + CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid = '%u'", item_guid_low); + continue; + } + + Item* item = NewItemOrBag(proto); + + if (!item->LoadFromDB(item_guid_low, fields, GetObjectGuid())) + { + sLog.outError("Player::_LoadMailedItems - Item in mail (%u) doesn't exist !!!! - item guid: %u, deleted from mail", mail->messageID, item_guid_low); + CharacterDatabase.PExecute("DELETE FROM mail_items WHERE item_guid = '%u'", item_guid_low); + item->FSetState(ITEM_REMOVED); + item->SaveToDB(); // it also deletes item object ! + continue; + } + + AddMItem(item); + } + while (result->NextRow()); + + delete result; +} + +void Player::_LoadMails(QueryResult* result) +{ + m_mail.clear(); + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + //"SELECT id,messageType,sender,receiver,subject,body,expire_time,deliver_time,money,cod,checked,stationery,mailTemplateId,has_items FROM mail WHERE receiver = '%u' ORDER BY id DESC", GetGUIDLow() + if (!result) + return; + + do + { + Field* fields = result->Fetch(); + Mail* m = new Mail; + m->messageID = fields[0].GetUInt32(); + m->messageType = fields[1].GetUInt8(); + m->sender = fields[2].GetUInt32(); + m->receiverGuid = ObjectGuid(HIGHGUID_PLAYER, fields[3].GetUInt32()); + m->subject = fields[4].GetCppString(); + m->body = fields[5].GetCppString(); + m->expire_time = (time_t)fields[6].GetUInt64(); + m->deliver_time = (time_t)fields[7].GetUInt64(); + m->money = fields[8].GetUInt32(); + m->COD = fields[9].GetUInt32(); + m->checked = fields[10].GetUInt32(); + m->stationery = fields[11].GetUInt8(); + m->mailTemplateId = fields[12].GetInt16(); + m->has_items = fields[13].GetBool(); // true, if mail have items or mail have template and items generated (maybe none) + + if (m->mailTemplateId && !sMailTemplateStore.LookupEntry(m->mailTemplateId)) + { + sLog.outError("Player::_LoadMail - Mail (%u) have nonexistent MailTemplateId (%u), remove at load", m->messageID, m->mailTemplateId); + m->mailTemplateId = 0; + } + + m->state = MAIL_STATE_UNCHANGED; + + m_mail.push_back(m); + + if (m->mailTemplateId && !m->has_items) + m->prepareTemplateItems(this); + } + while (result->NextRow()); + delete result; +} + +void Player::LoadPet() +{ + // fixme: the pet should still be loaded if the player is not in world + // just not added to the map + if (IsInWorld()) + { + Pet* pet = new Pet; + if (!pet->LoadPetFromDB(this, 0, 0, true)) + delete pet; + } +} + +void Player::_LoadQuestStatus(QueryResult* result) +{ + mQuestStatus.clear(); + + uint32 slot = 0; + + //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + // QueryResult *result = CharacterDatabase.PQuery("SELECT quest, status, rewarded, explored, timer, mobcount1, mobcount2, mobcount3, mobcount4, itemcount1, itemcount2, itemcount3, itemcount4, itemcount5, itemcount6 FROM character_queststatus WHERE guid = '%u'", GetGUIDLow()); + + if (result) + { + do + { + Field* fields = result->Fetch(); + + uint32 quest_id = fields[0].GetUInt32(); + // used to be new, no delete? + Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id); + if (pQuest) + { + // find or create + QuestStatusData& questStatusData = mQuestStatus[quest_id]; + + uint32 qstatus = fields[1].GetUInt32(); + if (qstatus < MAX_QUEST_STATUS) + questStatusData.m_status = QuestStatus(qstatus); + else + { + questStatusData.m_status = QUEST_STATUS_NONE; + sLog.outError("Player %s have invalid quest %d status (%d), replaced by QUEST_STATUS_NONE(0).", GetName(), quest_id, qstatus); + } + + questStatusData.m_rewarded = (fields[2].GetUInt8() > 0); + questStatusData.m_explored = (fields[3].GetUInt8() > 0); + + time_t quest_time = time_t(fields[4].GetUInt64()); + + if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_TIMED) && !GetQuestRewardStatus(quest_id) && questStatusData.m_status != QUEST_STATUS_NONE) + { + AddTimedQuest(quest_id); + + if (quest_time <= sWorld.GetGameTime()) + questStatusData.m_timer = 1; + else + questStatusData.m_timer = uint32(quest_time - sWorld.GetGameTime()) * IN_MILLISECONDS; + } + else + quest_time = 0; + + questStatusData.m_creatureOrGOcount[0] = fields[5].GetUInt32(); + questStatusData.m_creatureOrGOcount[1] = fields[6].GetUInt32(); + questStatusData.m_creatureOrGOcount[2] = fields[7].GetUInt32(); + questStatusData.m_creatureOrGOcount[3] = fields[8].GetUInt32(); + questStatusData.m_itemcount[0] = fields[9].GetUInt32(); + questStatusData.m_itemcount[1] = fields[10].GetUInt32(); + questStatusData.m_itemcount[2] = fields[11].GetUInt32(); + questStatusData.m_itemcount[3] = fields[12].GetUInt32(); + questStatusData.m_itemcount[4] = fields[13].GetUInt32(); + questStatusData.m_itemcount[5] = fields[14].GetUInt32(); + + questStatusData.uState = QUEST_UNCHANGED; + + // add to quest log + if (slot < MAX_QUEST_LOG_SIZE && + ((questStatusData.m_status == QUEST_STATUS_INCOMPLETE || + questStatusData.m_status == QUEST_STATUS_COMPLETE || + questStatusData.m_status == QUEST_STATUS_FAILED) && + (!questStatusData.m_rewarded || pQuest->IsRepeatable()))) + { + SetQuestSlot(slot, quest_id, uint32(quest_time)); + + if (questStatusData.m_explored) + SetQuestSlotState(slot, QUEST_STATE_COMPLETE); + + if (questStatusData.m_status == QUEST_STATUS_COMPLETE) + SetQuestSlotState(slot, QUEST_STATE_COMPLETE); + + if (questStatusData.m_status == QUEST_STATUS_FAILED) + SetQuestSlotState(slot, QUEST_STATE_FAIL); + + for (uint8 idx = 0; idx < QUEST_OBJECTIVES_COUNT; ++idx) + if (questStatusData.m_creatureOrGOcount[idx]) + SetQuestSlotCounter(slot, idx, questStatusData.m_creatureOrGOcount[idx]); + + ++slot; + } + + if (questStatusData.m_rewarded) + { + // learn rewarded spell if unknown + learnQuestRewardedSpells(pQuest); + + // set rewarded title if any + if (pQuest->GetCharTitleId()) + { + if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(pQuest->GetCharTitleId())) + SetTitle(titleEntry); + } + + if (pQuest->GetBonusTalents()) + m_questRewardTalentCount += pQuest->GetBonusTalents(); + } + + DEBUG_LOG("Quest status is {%u} for quest {%u} for player (GUID: %u)", questStatusData.m_status, quest_id, GetGUIDLow()); + } + } + while (result->NextRow()); + + delete result; + } + + // clear quest log tail + for (uint16 i = slot; i < MAX_QUEST_LOG_SIZE; ++i) + SetQuestSlot(i, 0); +} + +void Player::_LoadDailyQuestStatus(QueryResult* result) +{ + for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx) + SetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx, 0); + + // QueryResult *result = CharacterDatabase.PQuery("SELECT quest FROM character_queststatus_daily WHERE guid = '%u'", GetGUIDLow()); + + if (result) + { + uint32 quest_daily_idx = 0; + + do + { + if (quest_daily_idx >= PLAYER_MAX_DAILY_QUESTS) // max amount with exist data in query + { + sLog.outError("Player (GUID: %u) have more 25 daily quest records in `charcter_queststatus_daily`", GetGUIDLow()); + break; + } + + Field* fields = result->Fetch(); + + uint32 quest_id = fields[0].GetUInt32(); + + Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id); + if (!pQuest) + continue; + + SetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx, quest_id); + ++quest_daily_idx; + + DEBUG_LOG("Daily quest {%u} cooldown for player (GUID: %u)", quest_id, GetGUIDLow()); + } + while (result->NextRow()); + + delete result; + } + + m_DailyQuestChanged = false; +} + +void Player::_LoadWeeklyQuestStatus(QueryResult* result) +{ + m_weeklyquests.clear(); + + // QueryResult *result = CharacterDatabase.PQuery("SELECT quest FROM character_queststatus_weekly WHERE guid = '%u'", GetGUIDLow()); + + if (result) + { + do + { + Field* fields = result->Fetch(); + + uint32 quest_id = fields[0].GetUInt32(); + + Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id); + if (!pQuest) + continue; + + m_weeklyquests.insert(quest_id); + + DEBUG_LOG("Weekly quest {%u} cooldown for player (GUID: %u)", quest_id, GetGUIDLow()); + } + while (result->NextRow()); + + delete result; + } + m_WeeklyQuestChanged = false; +} + +void Player::_LoadMonthlyQuestStatus(QueryResult* result) +{ + m_monthlyquests.clear(); + + // QueryResult *result = CharacterDatabase.PQuery("SELECT quest FROM character_queststatus_weekly WHERE guid = '%u'", GetGUIDLow()); + + if (result) + { + do + { + Field* fields = result->Fetch(); + + uint32 quest_id = fields[0].GetUInt32(); + + Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id); + if (!pQuest) + continue; + + m_monthlyquests.insert(quest_id); + + DEBUG_LOG("Monthly quest {%u} cooldown for player (GUID: %u)", quest_id, GetGUIDLow()); + } + while (result->NextRow()); + + delete result; + } + + m_MonthlyQuestChanged = false; +} + +void Player::_LoadSpells(QueryResult* result) +{ + // QueryResult *result = CharacterDatabase.PQuery("SELECT spell,active,disabled FROM character_spell WHERE guid = '%u'",GetGUIDLow()); + + if (result) + { + do + { + Field* fields = result->Fetch(); + + uint32 spell_id = fields[0].GetUInt32(); + + // skip talents & drop unneeded data + if (GetTalentSpellPos(spell_id)) + { + sLog.outError("Player::_LoadSpells: %s has talent spell %u in character_spell, removing it.", + GetGuidStr().c_str(), spell_id); + CharacterDatabase.PExecute("DELETE FROM character_spell WHERE spell = '%u'", spell_id); + continue; + } + + addSpell(spell_id, fields[1].GetBool(), false, false, fields[2].GetBool()); + } + while (result->NextRow()); + + delete result; + } +} + +void Player::_LoadTalents(QueryResult* result) +{ + // QueryResult *result = CharacterDatabase.PQuery("SELECT talent_id, current_rank, spec FROM character_talent WHERE guid = '%u'",GetGUIDLow()); + if (result) + { + do + { + Field* fields = result->Fetch(); + + uint32 talent_id = fields[0].GetUInt32(); + TalentEntry const* talentInfo = sTalentStore.LookupEntry(talent_id); + + if (!talentInfo) + { + sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talent_id: %u , this talent will be deleted from character_talent", GetGUIDLow(), talent_id); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE talent_id = '%u'", talent_id); + continue; + } + + TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab); + + if (!talentTabInfo) + { + sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talentTabInfo: %u for talentID: %u , this talent will be deleted from character_talent", GetGUIDLow(), talentInfo->TalentTab, talentInfo->TalentID); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE talent_id = '%u'", talent_id); + continue; + } + + // prevent load talent for different class (cheating) + if ((getClassMask() & talentTabInfo->ClassMask) == 0) + { + sLog.outError("Player::_LoadTalents:Player (GUID: %u) has talent with ClassMask: %u , but Player's ClassMask is: %u , talentID: %u , this talent will be deleted from character_talent", GetGUIDLow(), talentTabInfo->ClassMask, getClassMask() , talentInfo->TalentID); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u' AND talent_id = '%u'", GetGUIDLow(), talent_id); + continue; + } + + uint32 currentRank = fields[1].GetUInt32(); + + if (currentRank > MAX_TALENT_RANK || talentInfo->RankID[currentRank] == 0) + { + sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talent rank: %u , talentID: %u , this talent will be deleted from character_talent", GetGUIDLow(), currentRank, talentInfo->TalentID); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u' AND talent_id = '%u'", GetGUIDLow(), talent_id); + continue; + } + + uint32 spec = fields[2].GetUInt32(); + + if (spec > MAX_TALENT_SPEC_COUNT) + { + sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talent spec: %u, spec will be deleted from character_talent", GetGUIDLow(), spec); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE spec = '%u' ", spec); + continue; + } + + if (spec >= m_specsCount) + { + sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talent spec: %u , this spec will be deleted from character_talent.", GetGUIDLow(), spec); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u' AND spec = '%u' ", GetGUIDLow(), spec); + continue; + } + + if (m_activeSpec == spec) + addSpell(talentInfo->RankID[currentRank], true, false, false, false); + else + { + PlayerTalent talent; + talent.currentRank = currentRank; + talent.talentEntry = talentInfo; + talent.state = PLAYERSPELL_UNCHANGED; + m_talents[spec][talentInfo->TalentID] = talent; + } + } + while (result->NextRow()); + delete result; + } +} + +void Player::_LoadGroup(QueryResult* result) +{ + // QueryResult *result = CharacterDatabase.PQuery("SELECT groupId FROM group_member WHERE memberGuid='%u'", GetGUIDLow()); + if (result) + { + uint32 groupId = (*result)[0].GetUInt32(); + delete result; + + if (Group* group = sObjectMgr.GetGroupById(groupId)) + { + uint8 subgroup = group->GetMemberGroup(GetObjectGuid()); + SetGroup(group, subgroup); + if (getLevel() >= LEVELREQUIREMENT_HEROIC) + { + // the group leader may change the instance difficulty while the player is offline + SetDungeonDifficulty(group->GetDungeonDifficulty()); + SetRaidDifficulty(group->GetRaidDifficulty()); + } + } + } +} + +void Player::_LoadBoundInstances(QueryResult* result) +{ + for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) + m_boundInstances[i].clear(); + + Group* group = GetGroup(); + + // QueryResult *result = CharacterDatabase.PQuery("SELECT id, permanent, map, difficulty, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = '%u'", GUID_LOPART(m_guid)); + if (result) + { + do + { + Field* fields = result->Fetch(); + bool perm = fields[1].GetBool(); + uint32 mapId = fields[2].GetUInt32(); + uint32 instanceId = fields[0].GetUInt32(); + uint8 difficulty = fields[3].GetUInt8(); + + time_t resetTime = (time_t)fields[4].GetUInt64(); + // the resettime for normal instances is only saved when the InstanceSave is unloaded + // so the value read from the DB may be wrong here but only if the InstanceSave is loaded + // and in that case it is not used + + MapEntry const* mapEntry = sMapStore.LookupEntry(mapId); + if (!mapEntry || !mapEntry->IsDungeon()) + { + sLog.outError("_LoadBoundInstances: player %s(%d) has bind to nonexistent or not dungeon map %d", GetName(), GetGUIDLow(), mapId); + CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u' AND instance = '%u'", GetGUIDLow(), instanceId); + continue; + } + + if (difficulty >= MAX_DIFFICULTY) + { + sLog.outError("_LoadBoundInstances: player %s(%d) has bind to nonexistent difficulty %d instance for map %u", GetName(), GetGUIDLow(), difficulty, mapId); + CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u' AND instance = '%u'", GetGUIDLow(), instanceId); + continue; + } + + MapDifficultyEntry const* mapDiff = GetMapDifficultyData(mapId, Difficulty(difficulty)); + if (!mapDiff) + { + sLog.outError("_LoadBoundInstances: player %s(%d) has bind to nonexistent difficulty %d instance for map %u", GetName(), GetGUIDLow(), difficulty, mapId); + CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u' AND instance = '%u'", GetGUIDLow(), instanceId); + continue; + } + + if (!perm && group) + { + sLog.outError("_LoadBoundInstances: %s is in group (Id: %d) but has a non-permanent character bind to map %d,%d,%d", + GetGuidStr().c_str(), group->GetId(), mapId, instanceId, difficulty); + CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u' AND instance = '%u'", + GetGUIDLow(), instanceId); + continue; + } + + // since non permanent binds are always solo bind, they can always be reset + DungeonPersistentState* state = (DungeonPersistentState*)sMapPersistentStateMgr.AddPersistentState(mapEntry, instanceId, Difficulty(difficulty), resetTime, !perm, true); + if (state) BindToInstance(state, perm, true); + } + while (result->NextRow()); + delete result; + } +} + +InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty) +{ + // some instances only have one difficulty + MapDifficultyEntry const* mapDiff = GetMapDifficultyData(mapid, difficulty); + if (!mapDiff) + return NULL; + + BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid); + if (itr != m_boundInstances[difficulty].end()) + return &itr->second; + else + return NULL; +} + +void Player::UnbindInstance(uint32 mapid, Difficulty difficulty, bool unload) +{ + BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid); + UnbindInstance(itr, difficulty, unload); +} + +void Player::UnbindInstance(BoundInstancesMap::iterator& itr, Difficulty difficulty, bool unload) +{ + if (itr != m_boundInstances[difficulty].end()) + { + if (!unload) + CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u' AND instance = '%u'", + GetGUIDLow(), itr->second.state->GetInstanceId()); + + sCalendarMgr.SendCalendarRaidLockoutRemove(this, itr->second.state); + + itr->second.state->RemovePlayer(this); // state can become invalid + m_boundInstances[difficulty].erase(itr++); + } +} + +InstancePlayerBind* Player::BindToInstance(DungeonPersistentState* state, bool permanent, bool load) +{ + if (state) + { + InstancePlayerBind& bind = m_boundInstances[state->GetDifficulty()][state->GetMapId()]; + if (bind.state) + { + // update the state when the group kills a boss + if (permanent != bind.perm || state != bind.state) + if (!load) + CharacterDatabase.PExecute("UPDATE character_instance SET instance = '%u', permanent = '%u' WHERE guid = '%u' AND instance = '%u'", + state->GetInstanceId(), permanent, GetGUIDLow(), bind.state->GetInstanceId()); + } + else + { + if (!load) + CharacterDatabase.PExecute("INSERT INTO character_instance (guid, instance, permanent) VALUES ('%u', '%u', '%u')", + GetGUIDLow(), state->GetInstanceId(), permanent); + } + + if (bind.state != state) + { + if (bind.state) + bind.state->RemovePlayer(this); + state->AddPlayer(this); + } + + if (permanent) + state->SetCanReset(false); + + bind.state = state; + bind.perm = permanent; + if (!load) + DEBUG_LOG("Player::BindToInstance: %s(%d) is now bound to map %d, instance %d, difficulty %d", + GetName(), GetGUIDLow(), state->GetMapId(), state->GetInstanceId(), state->GetDifficulty()); + return &bind; + } + else + return NULL; +} + +DungeonPersistentState* Player::GetBoundInstanceSaveForSelfOrGroup(uint32 mapid) +{ + MapEntry const* mapEntry = sMapStore.LookupEntry(mapid); + if (!mapEntry) + return NULL; + + InstancePlayerBind* pBind = GetBoundInstance(mapid, GetDifficulty(mapEntry->IsRaid())); + DungeonPersistentState* state = pBind ? pBind->state : NULL; + + // the player's permanent player bind is taken into consideration first + // then the player's group bind and finally the solo bind. + if (!pBind || !pBind->perm) + { + InstanceGroupBind* groupBind = NULL; + // use the player's difficulty setting (it may not be the same as the group's) + if (Group* group = GetGroup()) + if (groupBind = group->GetBoundInstance(mapid, this)) + state = groupBind->state; + } + + return state; +} + +void Player::SendRaidInfo() +{ + uint32 counter = 0; + + WorldPacket data(SMSG_RAID_INSTANCE_INFO, 4); + + size_t p_counter = data.wpos(); + data << uint32(counter); // placeholder + + time_t now = time(NULL); + + for (int i = 0; i < MAX_DIFFICULTY; ++i) + { + for (BoundInstancesMap::const_iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr) + { + if (itr->second.perm) + { + DungeonPersistentState* state = itr->second.state; + data << uint32(state->GetMapId()); // map id + data << uint32(state->GetDifficulty()); // difficulty + data << ObjectGuid(state->GetInstanceGuid());// instance guid + data << uint8(1); // expired = 0 + data << uint8(0); // extended = 1 + data << uint32(state->GetResetTime() - now);// reset time + data << uint32(state->GetCompletedEncountersMask());// completed encounter mask + ++counter; + } + } + } + data.put(p_counter, counter); + GetSession()->SendPacket(&data); +} + +/* +- called on every successful teleportation to a map +*/ +void Player::SendSavedInstances() +{ + bool hasBeenSaved = false; + WorldPacket data; + + for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) + { + for (BoundInstancesMap::const_iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr) + { + if (itr->second.perm) // only permanent binds are sent + { + hasBeenSaved = true; + break; + } + } + } + + // Send opcode 811. true or false means, whether you have current raid/heroic instances + data.Initialize(SMSG_UPDATE_INSTANCE_OWNERSHIP); + data << uint32(hasBeenSaved); + GetSession()->SendPacket(&data); + + if (!hasBeenSaved) + return; + + for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) + { + for (BoundInstancesMap::const_iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr) + { + if (itr->second.perm) + { + data.Initialize(SMSG_UPDATE_LAST_INSTANCE); + data << uint32(itr->second.state->GetMapId()); + GetSession()->SendPacket(&data); + } + } + } +} + +/// convert the player's binds to the group +void Player::ConvertInstancesToGroup(Player* player, Group* group, ObjectGuid player_guid) +{ + bool has_binds = false; + bool has_solo = false; + + if (player) + { + player_guid = player->GetObjectGuid(); + if (!group) + group = player->GetGroup(); + } + + MANGOS_ASSERT(player_guid); + + // copy all binds to the group, when changing leader it's assumed the character + // will not have any solo binds + + if (player) + { + for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) + { + for (BoundInstancesMap::iterator itr = player->m_boundInstances[i].begin(); itr != player->m_boundInstances[i].end();) + { + has_binds = true; + + if (group) + group->BindToInstance(itr->second.state, itr->second.perm, true); + + // permanent binds are not removed + if (!itr->second.perm) + { + // increments itr in call + player->UnbindInstance(itr, Difficulty(i), true); + has_solo = true; + } + else + ++itr; + } + } + } + + uint32 player_lowguid = player_guid.GetCounter(); + + // if the player's not online we don't know what binds it has + if (!player || !group || has_binds) + CharacterDatabase.PExecute("INSERT INTO group_instance SELECT guid, instance, permanent FROM character_instance WHERE guid = '%u'", player_lowguid); + + // the following should not get executed when changing leaders + if (!player || has_solo) + CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u' AND permanent = 0", player_lowguid); +} + +bool Player::_LoadHomeBind(QueryResult* result) +{ + PlayerInfo const* info = sObjectMgr.GetPlayerInfo(getRace(), getClass()); + if (!info) + { + sLog.outError("Player have incorrect race/class pair. Can't be loaded."); + return false; + } + + bool ok = false; + // QueryResult *result = CharacterDatabase.PQuery("SELECT map,zone,position_x,position_y,position_z FROM character_homebind WHERE guid = '%u'", GUID_LOPART(playerGuid)); + if (result) + { + Field* fields = result->Fetch(); + m_homebindMapId = fields[0].GetUInt32(); + m_homebindAreaId = fields[1].GetUInt16(); + m_homebindX = fields[2].GetFloat(); + m_homebindY = fields[3].GetFloat(); + m_homebindZ = fields[4].GetFloat(); + delete result; + + MapEntry const* bindMapEntry = sMapStore.LookupEntry(m_homebindMapId); + + // accept saved data only for valid position (and non instanceable), and accessable + if (MapManager::IsValidMapCoord(m_homebindMapId, m_homebindX, m_homebindY, m_homebindZ) && + !bindMapEntry->Instanceable() && GetSession()->Expansion() >= bindMapEntry->Expansion()) + { + ok = true; + } + else + CharacterDatabase.PExecute("DELETE FROM character_homebind WHERE guid = '%u'", GetGUIDLow()); + } + + if (!ok) + { + m_homebindMapId = info->mapId; + m_homebindAreaId = info->areaId; + m_homebindX = info->positionX; + m_homebindY = info->positionY; + m_homebindZ = info->positionZ; + + CharacterDatabase.PExecute("INSERT INTO character_homebind (guid,map,zone,position_x,position_y,position_z) VALUES ('%u', '%u', '%u', '%f', '%f', '%f')", GetGUIDLow(), m_homebindMapId, (uint32)m_homebindAreaId, m_homebindX, m_homebindY, m_homebindZ); + } + + DEBUG_LOG("Setting player home position: mapid is: %u, zoneid is %u, X is %f, Y is %f, Z is %f", + m_homebindMapId, m_homebindAreaId, m_homebindX, m_homebindY, m_homebindZ); + + return true; +} + +/*********************************************************/ +/*** SAVE SYSTEM ***/ +/*********************************************************/ + +void Player::SaveToDB() +{ + // we should assure this: ASSERT((m_nextSave != sWorld.getConfig(CONFIG_UINT32_INTERVAL_SAVE))); + // delay auto save at any saves (manual, in code, or autosave) + m_nextSave = sWorld.getConfig(CONFIG_UINT32_INTERVAL_SAVE); + + // lets allow only players in world to be saved + if (IsBeingTeleportedFar()) + { + ScheduleDelayedOperation(DELAYED_SAVE_PLAYER); + return; + } + + // first save/honor gain after midnight will also update the player's honor fields + UpdateHonorKills(); + + DEBUG_FILTER_LOG(LOG_FILTER_PLAYER_STATS, "The value of player %s at save: ", m_name.c_str()); + outDebugStatsValues(); + + CharacterDatabase.BeginTransaction(); + + static SqlStatementID delChar ; + static SqlStatementID insChar ; + + SqlStatement stmt = CharacterDatabase.CreateStatement(delChar, "DELETE FROM characters WHERE guid = ?"); + stmt.PExecute(GetGUIDLow()); + + SqlStatement uberInsert = CharacterDatabase.CreateStatement(insChar, "INSERT INTO characters (guid,account,name,race,class,gender,level,xp,money,playerBytes,playerBytes2,playerFlags," + "map, dungeon_difficulty, position_x, position_y, position_z, orientation, " + "taximask, online, cinematic, " + "totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, primary_trees, " + "trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, " + "death_expire_time, taxi_path, totalKills, " + "todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, health, power1, power2, power3, " + "power4, power5, specCount, activeSpec, exploredZones, equipmentCache, knownTitles, actionBars, slot) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, ?, " + "?, ?, ?, " + "?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, " + "?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, ?, ?, ?, ?) "); + + uberInsert.addUInt32(GetGUIDLow()); + uberInsert.addUInt32(GetSession()->GetAccountId()); + uberInsert.addString(m_name); + uberInsert.addUInt8(getRace()); + uberInsert.addUInt8(getClass()); + uberInsert.addUInt8(getGender()); + uberInsert.addUInt32(getLevel()); + uberInsert.addUInt32(GetUInt32Value(PLAYER_XP)); + uberInsert.addUInt64(GetMoney()); + uberInsert.addUInt32(GetUInt32Value(PLAYER_BYTES)); + uberInsert.addUInt32(GetUInt32Value(PLAYER_BYTES_2)); + uberInsert.addUInt32(GetUInt32Value(PLAYER_FLAGS)); + + if (!IsBeingTeleported()) + { + uberInsert.addUInt32(GetMapId()); + uberInsert.addUInt32(uint32(GetDungeonDifficulty())); + uberInsert.addFloat(finiteAlways(GetPositionX())); + uberInsert.addFloat(finiteAlways(GetPositionY())); + uberInsert.addFloat(finiteAlways(GetPositionZ())); + uberInsert.addFloat(finiteAlways(GetOrientation())); + } + else + { + uberInsert.addUInt32(GetTeleportDest().mapid); + uberInsert.addUInt32(uint32(GetDungeonDifficulty())); + uberInsert.addFloat(finiteAlways(GetTeleportDest().coord_x)); + uberInsert.addFloat(finiteAlways(GetTeleportDest().coord_y)); + uberInsert.addFloat(finiteAlways(GetTeleportDest().coord_z)); + uberInsert.addFloat(finiteAlways(GetTeleportDest().orientation)); + } + + std::ostringstream ss; + ss << m_taxi; // string with TaxiMaskSize numbers + uberInsert.addString(ss); + + uberInsert.addUInt32(IsInWorld() ? 1 : 0); + + uberInsert.addUInt32(m_cinematic); + + uberInsert.addUInt32(m_Played_time[PLAYED_TIME_TOTAL]); + uberInsert.addUInt32(m_Played_time[PLAYED_TIME_LEVEL]); + + uberInsert.addFloat(finiteAlways(m_rest_bonus)); + uberInsert.addUInt64(uint64(time(NULL))); + uberInsert.addUInt32(HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) ? 1 : 0); + // save, far from tavern/city + // save, but in tavern/city + uberInsert.addUInt32(m_resetTalentsCost); + uberInsert.addUInt64(uint64(m_resetTalentsTime)); + ss.str(""); + for (int i = 0; i < MAX_TALENT_SPEC_COUNT; ++i) + ss << m_talentsPrimaryTree[i] << " "; + uberInsert.addString(ss); + + uberInsert.addFloat(finiteAlways(m_movementInfo.GetTransportPos()->x)); + uberInsert.addFloat(finiteAlways(m_movementInfo.GetTransportPos()->y)); + uberInsert.addFloat(finiteAlways(m_movementInfo.GetTransportPos()->z)); + uberInsert.addFloat(finiteAlways(m_movementInfo.GetTransportPos()->o)); + if (m_transport) + uberInsert.addUInt32(m_transport->GetGUIDLow()); + else + uberInsert.addUInt32(0); + + uberInsert.addUInt32(m_ExtraFlags); + + uberInsert.addUInt32(uint32(m_stableSlots)); // to prevent save uint8 as char + + uberInsert.addUInt32(uint32(m_atLoginFlags)); + + uberInsert.addUInt32(IsInWorld() ? GetZoneId() : GetCachedZoneId()); + + uberInsert.addUInt64(uint64(m_deathExpireTime)); + + ss << m_taxi.SaveTaxiDestinationsToString(); // string + uberInsert.addString(ss); + + uberInsert.addUInt32(GetUInt32Value(PLAYER_FIELD_LIFETIME_HONORBALE_KILLS)); + + uberInsert.addUInt16(GetUInt16Value(PLAYER_FIELD_KILLS, 0)); + + uberInsert.addUInt16(GetUInt16Value(PLAYER_FIELD_KILLS, 1)); + + uberInsert.addUInt32(GetUInt32Value(PLAYER_CHOSEN_TITLE)); + + // FIXME: at this moment send to DB as unsigned, including unit32(-1) + uberInsert.addUInt32(GetUInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX)); + + uberInsert.addUInt8(GetDrunkValue()); + + uberInsert.addUInt32(GetHealth()); + + COMPILE_ASSERT(MAX_STORED_POWERS == 5, "Query not updated."); + for (uint32 i = 0; i < MAX_STORED_POWERS; ++i) + uberInsert.addUInt32(GetPowerByIndex(i)); + + uberInsert.addUInt32(uint32(m_specsCount)); + uberInsert.addUInt32(uint32(m_activeSpec)); + + for (uint32 i = 0; i < PLAYER_EXPLORED_ZONES_SIZE; ++i) // string + { + ss << GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + i) << " "; + } + uberInsert.addString(ss); + + for (uint32 i = 0; i < EQUIPMENT_SLOT_END * 2; ++i) // string + { + ss << GetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + i) << " "; + } + uberInsert.addString(ss); + + for(uint32 i = 0; i < KNOWN_TITLES_SIZE*2; ++i ) //string + { + ss << GetUInt32Value(PLAYER__FIELD_KNOWN_TITLES + i) << " "; + } + uberInsert.addString(ss); + + uberInsert.addUInt32(uint32(GetByteValue(PLAYER_FIELD_BYTES, 2))); + + uberInsert.addUInt8(m_slot); + + uberInsert.Execute(); + + if (m_mailsUpdated) // save mails only when needed + _SaveMail(); + + _SaveBGData(); + _SaveInventory(); + _SaveQuestStatus(); + _SaveDailyQuestStatus(); + _SaveWeeklyQuestStatus(); + _SaveMonthlyQuestStatus(); + _SaveSpells(); + _SaveSpellCooldowns(); + _SaveActions(); + _SaveAuras(); + _SaveSkills(); + m_achievementMgr.SaveToDB(); + m_reputationMgr.SaveToDB(); + _SaveCurrencies(); + _SaveEquipmentSets(); + GetSession()->SaveTutorialsData(); // changed only while character in game + _SaveGlyphs(); + _SaveTalents(); + + CharacterDatabase.CommitTransaction(); + + // check if stats should only be saved on logout + // save stats can be out of transaction + if (m_session->isLogingOut() || !sWorld.getConfig(CONFIG_BOOL_STATS_SAVE_ONLY_ON_LOGOUT)) + _SaveStats(); + + // save pet (hunter pet level and experience and all type pets health/mana). + if (Pet* pet = GetPet()) + pet->SavePetToDB(PET_SAVE_AS_CURRENT); +} + +// fast save function for item/money cheating preventing - save only inventory and money state +void Player::SaveInventoryAndGoldToDB() +{ + _SaveInventory(); + SaveGoldToDB(); +} + +void Player::SaveGoldToDB() +{ + static SqlStatementID updateGold ; + + SqlStatement stmt = CharacterDatabase.CreateStatement(updateGold, "UPDATE characters SET money = ? WHERE guid = ?"); + stmt.PExecute(GetMoney(), GetGUIDLow()); +} + +void Player::_SaveActions() +{ + static SqlStatementID insertAction ; + static SqlStatementID updateAction ; + static SqlStatementID deleteAction ; + + for (int i = 0; i < MAX_TALENT_SPEC_COUNT; ++i) + { + for (ActionButtonList::iterator itr = m_actionButtons[i].begin(); itr != m_actionButtons[i].end();) + { + switch (itr->second.uState) + { + case ACTIONBUTTON_NEW: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(insertAction, "INSERT INTO character_action (guid,spec, button,action,type) VALUES (?, ?, ?, ?, ?)"); + stmt.addUInt32(GetGUIDLow()); + stmt.addUInt32(i); + stmt.addUInt32(uint32(itr->first)); + stmt.addUInt32(itr->second.GetAction()); + stmt.addUInt32(uint32(itr->second.GetType())); + stmt.Execute(); + itr->second.uState = ACTIONBUTTON_UNCHANGED; + ++itr; + } + break; + case ACTIONBUTTON_CHANGED: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(updateAction, "UPDATE character_action SET action = ?, type = ? WHERE guid = ? AND button = ? AND spec = ?"); + stmt.addUInt32(itr->second.GetAction()); + stmt.addUInt32(uint32(itr->second.GetType())); + stmt.addUInt32(GetGUIDLow()); + stmt.addUInt32(uint32(itr->first)); + stmt.addUInt32(i); + stmt.Execute(); + itr->second.uState = ACTIONBUTTON_UNCHANGED; + ++itr; + } + break; + case ACTIONBUTTON_DELETED: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(deleteAction, "DELETE FROM character_action WHERE guid = ? AND button = ? AND spec = ?"); + stmt.addUInt32(GetGUIDLow()); + stmt.addUInt32(uint32(itr->first)); + stmt.addUInt32(i); + stmt.Execute(); + m_actionButtons[i].erase(itr++); + } + break; + default: + ++itr; + break; + } + } + } +} + +void Player::_SaveAuras() +{ + static SqlStatementID deleteAuras ; + static SqlStatementID insertAuras ; + + SqlStatement stmt = CharacterDatabase.CreateStatement(deleteAuras, "DELETE FROM character_aura WHERE guid = ?"); + stmt.PExecute(GetGUIDLow()); + + SpellAuraHolderMap const& auraHolders = GetSpellAuraHolderMap(); + + if (auraHolders.empty()) + return; + + stmt = CharacterDatabase.CreateStatement(insertAuras, "INSERT INTO character_aura (guid, caster_guid, item_guid, spell, stackcount, remaincharges, " + "basepoints0, basepoints1, basepoints2, periodictime0, periodictime1, periodictime2, maxduration, remaintime, effIndexMask) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + for (SpellAuraHolderMap::const_iterator itr = auraHolders.begin(); itr != auraHolders.end(); ++itr) + { + SpellAuraHolder* holder = itr->second; + // skip all holders from spells that are passive or channeled + // save singleTarget auras if self cast. + bool selfCastHolder = holder->GetCasterGuid() == GetObjectGuid(); + TrackedAuraType trackedType = holder->GetTrackedAuraType(); + if (!holder->IsPassive() && !IsChanneledSpell(holder->GetSpellProto()) && + (trackedType == TRACK_AURA_TYPE_NOT_TRACKED || (trackedType == TRACK_AURA_TYPE_SINGLE_TARGET && selfCastHolder))) + { + int32 damage[MAX_EFFECT_INDEX]; + uint32 periodicTime[MAX_EFFECT_INDEX]; + uint32 effIndexMask = 0; + + for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i) + { + damage[i] = 0; + periodicTime[i] = 0; + + if (Aura* aur = holder->GetAuraByEffectIndex(SpellEffectIndex(i))) + { + // don't save not own area auras + if (aur->IsAreaAura() && holder->GetCasterGuid() != GetObjectGuid()) + continue; + + damage[i] = aur->GetModifier()->m_amount; + periodicTime[i] = aur->GetModifier()->periodictime; + effIndexMask |= (1 << i); + } + } + + if (!effIndexMask) + continue; + + stmt.addUInt32(GetGUIDLow()); + stmt.addUInt64(holder->GetCasterGuid().GetRawValue()); + stmt.addUInt32(holder->GetCastItemGuid().GetCounter()); + stmt.addUInt32(holder->GetId()); + stmt.addUInt32(holder->GetStackAmount()); + stmt.addUInt8(holder->GetAuraCharges()); + + for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i) + stmt.addInt32(damage[i]); + + for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i) + stmt.addUInt32(periodicTime[i]); + + stmt.addInt32(holder->GetAuraMaxDuration()); + stmt.addInt32(holder->GetAuraDuration()); + stmt.addUInt32(effIndexMask); + stmt.Execute(); + } + } +} + +void Player::_SaveGlyphs() +{ + static SqlStatementID insertGlyph ; + static SqlStatementID updateGlyph ; + static SqlStatementID deleteGlyph ; + + for (uint8 spec = 0; spec < m_specsCount; ++spec) + { + for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot) + { + switch (m_glyphs[spec][slot].uState) + { + case GLYPH_NEW: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(insertGlyph, "INSERT INTO character_glyphs (guid, spec, slot, glyph) VALUES (?, ?, ?, ?)"); + stmt.PExecute(GetGUIDLow(), spec, slot, m_glyphs[spec][slot].GetId()); + break; + } + case GLYPH_CHANGED: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(updateGlyph, "UPDATE character_glyphs SET glyph = ? WHERE guid = ? AND spec = ? AND slot = ?"); + stmt.PExecute(m_glyphs[spec][slot].GetId(), GetGUIDLow(), spec, slot); + break; + } + case GLYPH_DELETED: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(deleteGlyph, "DELETE FROM character_glyphs WHERE guid = ? AND spec = ? AND slot = ?"); + stmt.PExecute(GetGUIDLow(), spec, slot); + break; + } + case GLYPH_UNCHANGED: + break; + } + m_glyphs[spec][slot].uState = GLYPH_UNCHANGED; + } + } +} + +void Player::_SaveInventory() +{ + // force items in buyback slots to new state + // and remove those that aren't already + for (uint8 i = BUYBACK_SLOT_START; i < BUYBACK_SLOT_END; ++i) + { + Item* item = m_items[i]; + if (!item || item->GetState() == ITEM_NEW) continue; + + static SqlStatementID delInv ; + static SqlStatementID delItemInst ; + + SqlStatement stmt = CharacterDatabase.CreateStatement(delInv, "DELETE FROM character_inventory WHERE item = ?"); + stmt.PExecute(item->GetGUIDLow()); + + stmt = CharacterDatabase.CreateStatement(delItemInst, "DELETE FROM item_instance WHERE guid = ?"); + stmt.PExecute(item->GetGUIDLow()); + + m_items[i]->FSetState(ITEM_NEW); + } + + // update enchantment durations + for (EnchantDurationList::const_iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr) + { + itr->item->SetEnchantmentDuration(itr->slot, itr->leftduration); + } + + // if no changes + if (m_itemUpdateQueue.empty()) return; + + // do not save if the update queue is corrupt + bool error = false; + for (size_t i = 0; i < m_itemUpdateQueue.size(); ++i) + { + Item* item = m_itemUpdateQueue[i]; + if (!item || item->GetState() == ITEM_REMOVED) continue; + Item* test = GetItemByPos(item->GetBagSlot(), item->GetSlot()); + + if (test == NULL) + { + sLog.outError("Player(GUID: %u Name: %s)::_SaveInventory - the bag(%d) and slot(%d) values for the item with guid %d are incorrect, the player doesn't have an item at that position!", GetGUIDLow(), GetName(), item->GetBagSlot(), item->GetSlot(), item->GetGUIDLow()); + error = true; + } + else if (test != item) + { + sLog.outError("Player(GUID: %u Name: %s)::_SaveInventory - the bag(%d) and slot(%d) values for the item with guid %d are incorrect, the item with guid %d is there instead!", GetGUIDLow(), GetName(), item->GetBagSlot(), item->GetSlot(), item->GetGUIDLow(), test->GetGUIDLow()); + error = true; + } + } + + if (error) + { + sLog.outError("Player::_SaveInventory - one or more errors occurred save aborted!"); + ChatHandler(this).SendSysMessage(LANG_ITEM_SAVE_FAILED); + return; + } + + static SqlStatementID insertInventory ; + static SqlStatementID updateInventory ; + static SqlStatementID deleteInventory ; + + for (size_t i = 0; i < m_itemUpdateQueue.size(); ++i) + { + Item* item = m_itemUpdateQueue[i]; + if (!item) continue; + + Bag* container = item->GetContainer(); + uint32 bag_guid = container ? container->GetGUIDLow() : 0; + + switch (item->GetState()) + { + case ITEM_NEW: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(insertInventory, "INSERT INTO character_inventory (guid,bag,slot,item,item_template) VALUES (?, ?, ?, ?, ?)"); + stmt.addUInt32(GetGUIDLow()); + stmt.addUInt32(bag_guid); + stmt.addUInt8(item->GetSlot()); + stmt.addUInt32(item->GetGUIDLow()); + stmt.addUInt32(item->GetEntry()); + stmt.Execute(); + } + break; + case ITEM_CHANGED: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(updateInventory, "UPDATE character_inventory SET guid = ?, bag = ?, slot = ?, item_template = ? WHERE item = ?"); + stmt.addUInt32(GetGUIDLow()); + stmt.addUInt32(bag_guid); + stmt.addUInt8(item->GetSlot()); + stmt.addUInt32(item->GetEntry()); + stmt.addUInt32(item->GetGUIDLow()); + stmt.Execute(); + } + break; + case ITEM_REMOVED: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(deleteInventory, "DELETE FROM character_inventory WHERE item = ?"); + stmt.PExecute(item->GetGUIDLow()); + } + break; + case ITEM_UNCHANGED: + break; + } + + item->SaveToDB(); // item have unchanged inventory record and can be save standalone + } + m_itemUpdateQueue.clear(); +} + +void Player::_SaveMail() +{ + static SqlStatementID updateMail ; + static SqlStatementID deleteMailItems ; + + static SqlStatementID deleteItem ; + static SqlStatementID deleteMain ; + static SqlStatementID deleteItems ; + + for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr) + { + Mail* m = (*itr); + if (m->state == MAIL_STATE_CHANGED) + { + SqlStatement stmt = CharacterDatabase.CreateStatement(updateMail, "UPDATE mail SET has_items = ?, expire_time = ?, deliver_time = ?, money = ?, cod = ?, checked = ? WHERE id = ?"); + stmt.addUInt32(m->HasItems() ? 1 : 0); + stmt.addUInt64(uint64(m->expire_time)); + stmt.addUInt64(uint64(m->deliver_time)); + stmt.addUInt32(m->money); + stmt.addUInt32(m->COD); + stmt.addUInt32(m->checked); + stmt.addUInt32(m->messageID); + stmt.Execute(); + + if (!m->removedItems.empty()) + { + stmt = CharacterDatabase.CreateStatement(deleteMailItems, "DELETE FROM mail_items WHERE item_guid = ?"); + + for (std::vector::const_iterator itr2 = m->removedItems.begin(); itr2 != m->removedItems.end(); ++itr2) + stmt.PExecute(*itr2); + + m->removedItems.clear(); + } + m->state = MAIL_STATE_UNCHANGED; + } + else if (m->state == MAIL_STATE_DELETED) + { + if (m->HasItems()) + { + SqlStatement stmt = CharacterDatabase.CreateStatement(deleteItem, "DELETE FROM item_instance WHERE guid = ?"); + for (MailItemInfoVec::const_iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2) + stmt.PExecute(itr2->item_guid); + } + + SqlStatement stmt = CharacterDatabase.CreateStatement(deleteMain, "DELETE FROM mail WHERE id = ?"); + stmt.PExecute(m->messageID); + + stmt = CharacterDatabase.CreateStatement(deleteItems, "DELETE FROM mail_items WHERE mail_id = ?"); + stmt.PExecute(m->messageID); + } + } + + // deallocate deleted mails... + for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end();) + { + if ((*itr)->state == MAIL_STATE_DELETED) + { + Mail* m = *itr; + m_mail.erase(itr); + delete m; + itr = m_mail.begin(); + } + else + ++itr; + } + + m_mailsUpdated = false; +} + +void Player::_SaveQuestStatus() +{ + static SqlStatementID insertQuestStatus ; + + static SqlStatementID updateQuestStatus ; + + // we don't need transactions here. + for (QuestStatusMap::iterator i = mQuestStatus.begin(); i != mQuestStatus.end(); ++i) + { + switch (i->second.uState) + { + case QUEST_NEW : + { + SqlStatement stmt = CharacterDatabase.CreateStatement(insertQuestStatus, "INSERT INTO character_queststatus (guid,quest,status,rewarded,explored,timer,mobcount1,mobcount2,mobcount3,mobcount4,itemcount1,itemcount2,itemcount3,itemcount4,itemcount5,itemcount6) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + stmt.addUInt32(GetGUIDLow()); + stmt.addUInt32(i->first); + stmt.addUInt8(i->second.m_status); + stmt.addUInt8(i->second.m_rewarded); + stmt.addUInt8(i->second.m_explored); + stmt.addUInt64(uint64(i->second.m_timer / IN_MILLISECONDS + sWorld.GetGameTime())); + for (int k = 0; k < QUEST_OBJECTIVES_COUNT; ++k) + stmt.addUInt32(i->second.m_creatureOrGOcount[k]); + for (int k = 0; k < QUEST_ITEM_OBJECTIVES_COUNT; ++k) + stmt.addUInt32(i->second.m_itemcount[k]); + stmt.Execute(); + } + break; + case QUEST_CHANGED : + { + SqlStatement stmt = CharacterDatabase.CreateStatement(updateQuestStatus, "UPDATE character_queststatus SET status = ?,rewarded = ?,explored = ?,timer = ?," + "mobcount1 = ?,mobcount2 = ?,mobcount3 = ?,mobcount4 = ?,itemcount1 = ?,itemcount2 = ?,itemcount3 = ?,itemcount4 = ?,itemcount5 = ?,itemcount6 = ? WHERE guid = ? AND quest = ?"); + + stmt.addUInt8(i->second.m_status); + stmt.addUInt8(i->second.m_rewarded); + stmt.addUInt8(i->second.m_explored); + stmt.addUInt64(uint64(i->second.m_timer / IN_MILLISECONDS + sWorld.GetGameTime())); + for (int k = 0; k < QUEST_OBJECTIVES_COUNT; ++k) + stmt.addUInt32(i->second.m_creatureOrGOcount[k]); + for (int k = 0; k < QUEST_ITEM_OBJECTIVES_COUNT; ++k) + stmt.addUInt32(i->second.m_itemcount[k]); + stmt.addUInt32(GetGUIDLow()); + stmt.addUInt32(i->first); + stmt.Execute(); + } + break; + case QUEST_UNCHANGED: + break; + }; + i->second.uState = QUEST_UNCHANGED; + } +} + +void Player::_SaveDailyQuestStatus() +{ + if (!m_DailyQuestChanged) + return; + + // we don't need transactions here. + static SqlStatementID delQuestStatus ; + static SqlStatementID insQuestStatus ; + + SqlStatement stmtDel = CharacterDatabase.CreateStatement(delQuestStatus, "DELETE FROM character_queststatus_daily WHERE guid = ?"); + SqlStatement stmtIns = CharacterDatabase.CreateStatement(insQuestStatus, "INSERT INTO character_queststatus_daily (guid,quest) VALUES (?, ?)"); + + stmtDel.PExecute(GetGUIDLow()); + + for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx) + if (GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx)) + stmtIns.PExecute(GetGUIDLow(), GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx)); + + m_DailyQuestChanged = false; +} + +void Player::_SaveWeeklyQuestStatus() +{ + if (!m_WeeklyQuestChanged || m_weeklyquests.empty()) + return; + + // we don't need transactions here. + static SqlStatementID delQuestStatus ; + static SqlStatementID insQuestStatus ; + + SqlStatement stmtDel = CharacterDatabase.CreateStatement(delQuestStatus, "DELETE FROM character_queststatus_weekly WHERE guid = ?"); + SqlStatement stmtIns = CharacterDatabase.CreateStatement(insQuestStatus, "INSERT INTO character_queststatus_weekly (guid,quest) VALUES (?, ?)"); + + stmtDel.PExecute(GetGUIDLow()); + + for (QuestSet::const_iterator iter = m_weeklyquests.begin(); iter != m_weeklyquests.end(); ++iter) + { + uint32 quest_id = *iter; + stmtIns.PExecute(GetGUIDLow(), quest_id); + } + + m_WeeklyQuestChanged = false; +} + +void Player::_SaveMonthlyQuestStatus() +{ + if (!m_MonthlyQuestChanged || m_monthlyquests.empty()) + return; + + // we don't need transactions here. + static SqlStatementID deleteQuest ; + static SqlStatementID insertQuest ; + + SqlStatement stmtDel = CharacterDatabase.CreateStatement(deleteQuest, "DELETE FROM character_queststatus_monthly WHERE guid = ?"); + SqlStatement stmtIns = CharacterDatabase.CreateStatement(insertQuest, "INSERT INTO character_queststatus_monthly (guid, quest) VALUES (?, ?)"); + + stmtDel.PExecute(GetGUIDLow()); + + for (QuestSet::const_iterator iter = m_monthlyquests.begin(); iter != m_monthlyquests.end(); ++iter) + { + uint32 quest_id = *iter; + stmtIns.PExecute(GetGUIDLow(), quest_id); + } + + m_MonthlyQuestChanged = false; +} + +void Player::_SaveSkills() +{ + static SqlStatementID delSkills ; + static SqlStatementID insSkills ; + static SqlStatementID updSkills ; + + // we don't need transactions here. + for (SkillStatusMap::iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end();) + { + if (itr->second.uState == SKILL_UNCHANGED) + { + ++itr; + continue; + } + + if (itr->second.uState == SKILL_DELETED) + { + SqlStatement stmt = CharacterDatabase.CreateStatement(delSkills, "DELETE FROM character_skills WHERE guid = ? AND skill = ?"); + stmt.PExecute(GetGUIDLow(), itr->first); + mSkillStatus.erase(itr++); + continue; + } + + uint16 field = itr->second.pos / 2; + uint8 offset = itr->second.pos & 1; + + uint16 value = GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset); + uint16 max = GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset); + + switch (itr->second.uState) + { + case SKILL_NEW: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(insSkills, "INSERT INTO character_skills (guid, skill, value, max) VALUES (?, ?, ?, ?)"); + stmt.PExecute(GetGUIDLow(), itr->first, value, max); + } + break; + case SKILL_CHANGED: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(updSkills, "UPDATE character_skills SET value = ?, max = ? WHERE guid = ? AND skill = ?"); + stmt.PExecute(value, max, GetGUIDLow(), itr->first); + } + break; + case SKILL_UNCHANGED: + case SKILL_DELETED: + MANGOS_ASSERT(false); + break; + }; + itr->second.uState = SKILL_UNCHANGED; + + ++itr; + } +} + +void Player::_SaveSpells() +{ + static SqlStatementID delSpells ; + static SqlStatementID insSpells ; + + SqlStatement stmtDel = CharacterDatabase.CreateStatement(delSpells, "DELETE FROM character_spell WHERE guid = ? and spell = ?"); + SqlStatement stmtIns = CharacterDatabase.CreateStatement(insSpells, "INSERT INTO character_spell (guid,spell,active,disabled) VALUES (?, ?, ?, ?)"); + + for (PlayerSpellMap::iterator itr = m_spells.begin(), next = m_spells.begin(); itr != m_spells.end();) + { + uint32 talentCosts = GetTalentSpellCost(itr->first); + + if (!talentCosts) + { + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.state == PLAYERSPELL_CHANGED) + stmtDel.PExecute(GetGUIDLow(), itr->first); + + // add only changed/new not dependent spells + if (!itr->second.dependent && (itr->second.state == PLAYERSPELL_NEW || itr->second.state == PLAYERSPELL_CHANGED)) + stmtIns.PExecute(GetGUIDLow(), itr->first, uint8(itr->second.active ? 1 : 0), uint8(itr->second.disabled ? 1 : 0)); + } + + if (itr->second.state == PLAYERSPELL_REMOVED) + m_spells.erase(itr++); + else + { + itr->second.state = PLAYERSPELL_UNCHANGED; + ++itr; + } + } +} + +void Player::_SaveTalents() +{ + static SqlStatementID delTalents ; + static SqlStatementID insTalents ; + + SqlStatement stmtDel = CharacterDatabase.CreateStatement(delTalents, "DELETE FROM character_talent WHERE guid = ? and talent_id = ? and spec = ?"); + SqlStatement stmtIns = CharacterDatabase.CreateStatement(insTalents, "INSERT INTO character_talent (guid, talent_id, current_rank , spec) VALUES (?, ?, ?, ?)"); + + for (uint32 i = 0; i < MAX_TALENT_SPEC_COUNT; ++i) + { + for (PlayerTalentMap::iterator itr = m_talents[i].begin(); itr != m_talents[i].end();) + { + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.state == PLAYERSPELL_CHANGED) + stmtDel.PExecute(GetGUIDLow(), itr->first, i); + + // add only changed/new talents + if (itr->second.state == PLAYERSPELL_NEW || itr->second.state == PLAYERSPELL_CHANGED) + stmtIns.PExecute(GetGUIDLow(), itr->first, itr->second.currentRank, i); + + if (itr->second.state == PLAYERSPELL_REMOVED) + m_talents[i].erase(itr++); + else + { + itr->second.state = PLAYERSPELL_UNCHANGED; + ++itr; + } + } + } +} + +// save player stats -- only for external usage +// real stats will be recalculated on player login +void Player::_SaveStats() +{ + // check if stat saving is enabled and if char level is high enough + if (!sWorld.getConfig(CONFIG_UINT32_MIN_LEVEL_STAT_SAVE) || getLevel() < sWorld.getConfig(CONFIG_UINT32_MIN_LEVEL_STAT_SAVE)) + return; + + static SqlStatementID delStats ; + static SqlStatementID insertStats ; + + SqlStatement stmt = CharacterDatabase.CreateStatement(delStats, "DELETE FROM character_stats WHERE guid = ?"); + stmt.PExecute(GetGUIDLow()); + + stmt = CharacterDatabase.CreateStatement(insertStats, "INSERT INTO character_stats (guid, maxhealth, maxpower1, maxpower2, maxpower3, maxpower4, maxpower5," + "strength, agility, stamina, intellect, spirit, armor, resHoly, resFire, resNature, resFrost, resShadow, resArcane, " + "blockPct, dodgePct, parryPct, critPct, rangedCritPct, spellCritPct, attackPower, rangedAttackPower, spellPower) " + "VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + stmt.addUInt32(GetGUIDLow()); + stmt.addUInt32(GetMaxHealth()); + COMPILE_ASSERT(MAX_STORED_POWERS == 5, "Query not updated."); + for (uint32 i = 0; i < MAX_STORED_POWERS; ++i) + stmt.addUInt32(GetMaxPowerByIndex(i)); + for (int i = 0; i < MAX_STATS; ++i) + stmt.addFloat(GetStat(Stats(i))); + // armor + school resistances + for (int i = 0; i < MAX_SPELL_SCHOOL; ++i) + stmt.addUInt32(GetResistance(SpellSchools(i))); + stmt.addFloat(GetFloatValue(PLAYER_BLOCK_PERCENTAGE)); + stmt.addFloat(GetFloatValue(PLAYER_DODGE_PERCENTAGE)); + stmt.addFloat(GetFloatValue(PLAYER_PARRY_PERCENTAGE)); + stmt.addFloat(GetFloatValue(PLAYER_CRIT_PERCENTAGE)); + stmt.addFloat(GetFloatValue(PLAYER_RANGED_CRIT_PERCENTAGE)); + stmt.addFloat(GetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1)); + stmt.addUInt32(GetUInt32Value(UNIT_FIELD_ATTACK_POWER)); + stmt.addUInt32(GetUInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER)); + stmt.addUInt32(GetBaseSpellPowerBonus()); + + stmt.Execute(); +} + +void Player::outDebugStatsValues() const +{ + // optimize disabled debug output + if (!sLog.HasLogLevelOrHigher(LOG_LVL_DEBUG) || sLog.HasLogFilter(LOG_FILTER_PLAYER_STATS)) + return; + + sLog.outDebug("HP is: \t\t\t%u\t\tMP is: \t\t\t%u", GetMaxHealth(), GetMaxPower(POWER_MANA)); + sLog.outDebug("AGILITY is: \t\t%f\t\tSTRENGTH is: \t\t%f", GetStat(STAT_AGILITY), GetStat(STAT_STRENGTH)); + sLog.outDebug("INTELLECT is: \t\t%f\t\tSPIRIT is: \t\t%f", GetStat(STAT_INTELLECT), GetStat(STAT_SPIRIT)); + sLog.outDebug("STAMINA is: \t\t%f", GetStat(STAT_STAMINA)); + sLog.outDebug("Armor is: \t\t%u\t\tBlock is: \t\t%f", GetArmor(), GetFloatValue(PLAYER_BLOCK_PERCENTAGE)); + sLog.outDebug("HolyRes is: \t\t%u\t\tFireRes is: \t\t%u", GetResistance(SPELL_SCHOOL_HOLY), GetResistance(SPELL_SCHOOL_FIRE)); + sLog.outDebug("NatureRes is: \t\t%u\t\tFrostRes is: \t\t%u", GetResistance(SPELL_SCHOOL_NATURE), GetResistance(SPELL_SCHOOL_FROST)); + sLog.outDebug("ShadowRes is: \t\t%u\t\tArcaneRes is: \t\t%u", GetResistance(SPELL_SCHOOL_SHADOW), GetResistance(SPELL_SCHOOL_ARCANE)); + sLog.outDebug("MIN_DAMAGE is: \t\t%f\tMAX_DAMAGE is: \t\t%f", GetFloatValue(UNIT_FIELD_MINDAMAGE), GetFloatValue(UNIT_FIELD_MAXDAMAGE)); + sLog.outDebug("MIN_OFFHAND_DAMAGE is: \t%f\tMAX_OFFHAND_DAMAGE is: \t%f", GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE), GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE)); + sLog.outDebug("MIN_RANGED_DAMAGE is: \t%f\tMAX_RANGED_DAMAGE is: \t%f", GetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE), GetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE)); + sLog.outDebug("ATTACK_TIME is: \t%u\t\tRANGE_ATTACK_TIME is: \t%u", GetAttackTime(BASE_ATTACK), GetAttackTime(RANGED_ATTACK)); +} + +/*********************************************************/ +/*** FLOOD FILTER SYSTEM ***/ +/*********************************************************/ + +void Player::UpdateSpeakTime() +{ + // ignore chat spam protection for GMs in any mode + if (GetSession()->GetSecurity() > SEC_PLAYER) + return; + + time_t current = time(NULL); + if (m_speakTime > current) + { + uint32 max_count = sWorld.getConfig(CONFIG_UINT32_CHATFLOOD_MESSAGE_COUNT); + if (!max_count) + return; + + ++m_speakCount; + if (m_speakCount >= max_count) + { + // prevent overwrite mute time, if message send just before mutes set, for example. + time_t new_mute = current + sWorld.getConfig(CONFIG_UINT32_CHATFLOOD_MUTE_TIME); + if (GetSession()->m_muteTime < new_mute) + GetSession()->m_muteTime = new_mute; + + m_speakCount = 0; + } + } + else + m_speakCount = 0; + + m_speakTime = current + sWorld.getConfig(CONFIG_UINT32_CHATFLOOD_MESSAGE_DELAY); +} + +bool Player::CanSpeak() const +{ + return GetSession()->m_muteTime <= time(NULL); +} + +/*********************************************************/ +/*** LOW LEVEL FUNCTIONS:Notifiers ***/ +/*********************************************************/ + +void Player::SendAttackSwingNotInRange() +{ + WorldPacket data(SMSG_ATTACKSWING_NOTINRANGE, 0); + GetSession()->SendPacket(&data); +} + +void Player::SavePositionInDB(ObjectGuid guid, uint32 mapid, float x, float y, float z, float o, uint32 zone) +{ + std::ostringstream ss; + ss << "UPDATE characters SET position_x='" << x << "',position_y='" << y + << "',position_z='" << z << "',orientation='" << o << "',map='" << mapid + << "',zone='" << zone << "',trans_x='0',trans_y='0',trans_z='0'," + << "transguid='0',taxi_path='' WHERE guid='" << guid.GetCounter() << "'"; + DEBUG_LOG("%s", ss.str().c_str()); + CharacterDatabase.Execute(ss.str().c_str()); +} + +void Player::SetUInt32ValueInArray(Tokens& tokens, uint16 index, uint32 value) +{ + char buf[11]; + snprintf(buf, 11, "%u", value); + + if (index >= tokens.size()) + return; + + tokens[index] = buf; +} + +void Player::Customize(ObjectGuid guid, uint8 gender, uint8 skin, uint8 face, uint8 hairStyle, uint8 hairColor, uint8 facialHair) +{ + // 0 + QueryResult* result = CharacterDatabase.PQuery("SELECT playerBytes2 FROM characters WHERE guid = '%u'", guid.GetCounter()); + if (!result) + return; + + Field* fields = result->Fetch(); + + uint32 player_bytes2 = fields[0].GetUInt32(); + player_bytes2 &= ~0xFF; + player_bytes2 |= facialHair; + + CharacterDatabase.PExecute("UPDATE characters SET gender = '%u', playerBytes = '%u', playerBytes2 = '%u' WHERE guid = '%u'", gender, skin | (face << 8) | (hairStyle << 16) | (hairColor << 24), player_bytes2, guid.GetCounter()); + + delete result; +} + +void Player::SendAttackSwingDeadTarget() +{ + WorldPacket data(SMSG_ATTACKSWING_DEADTARGET, 0); + GetSession()->SendPacket(&data); +} + +void Player::SendAttackSwingCantAttack() +{ + WorldPacket data(SMSG_ATTACKSWING_CANT_ATTACK, 0); + GetSession()->SendPacket(&data); +} + +void Player::SendAttackSwingCancelAttack() +{ + WorldPacket data(SMSG_CANCEL_COMBAT, 0); + GetSession()->SendPacket(&data); +} + +void Player::SendAttackSwingBadFacingAttack() +{ + WorldPacket data(SMSG_ATTACKSWING_BADFACING, 0); + GetSession()->SendPacket(&data); +} + +void Player::SendAutoRepeatCancel(Unit* target) +{ + WorldPacket data(SMSG_CANCEL_AUTO_REPEAT, target->GetPackGUID().size()); + data << target->GetPackGUID(); + GetSession()->SendPacket(&data); +} + +void Player::SendExplorationExperience(uint32 Area, uint32 Experience) +{ + WorldPacket data(SMSG_EXPLORATION_EXPERIENCE, 8); + data << uint32(Area); + data << uint32(Experience); + GetSession()->SendPacket(&data); +} + +void Player::SendDungeonDifficulty(bool IsInGroup) +{ + uint8 val = 0x00000001; + WorldPacket data(MSG_SET_DUNGEON_DIFFICULTY, 12); + data << uint32(GetDungeonDifficulty()); + data << uint32(val); + data << uint32(IsInGroup); + GetSession()->SendPacket(&data); +} + +void Player::SendRaidDifficulty(bool IsInGroup) +{ + uint8 val = 0x00000001; + WorldPacket data(MSG_SET_RAID_DIFFICULTY, 12); + data << uint32(GetRaidDifficulty()); + data << uint32(val); + data << uint32(IsInGroup); + GetSession()->SendPacket(&data); +} + +void Player::SendResetFailedNotify(uint32 mapid) +{ + WorldPacket data(SMSG_RESET_FAILED_NOTIFY, 4); + data << uint32(mapid); + GetSession()->SendPacket(&data); +} + +/// Reset all solo instances and optionally send a message on success for each +void Player::ResetInstances(InstanceResetMethod method, bool isRaid) +{ + // method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_JOIN + + // we assume that when the difficulty changes, all instances that can be reset will be + Difficulty diff = GetDifficulty(isRaid); + + for (BoundInstancesMap::iterator itr = m_boundInstances[diff].begin(); itr != m_boundInstances[diff].end();) + { + DungeonPersistentState* state = itr->second.state; + const MapEntry* entry = sMapStore.LookupEntry(itr->first); + if (!entry || entry->IsRaid() != isRaid || !state->CanReset()) + { + ++itr; + continue; + } + + if (method == INSTANCE_RESET_ALL) + { + // the "reset all instances" method can only reset normal maps + if (entry->map_type == MAP_RAID || diff == DUNGEON_DIFFICULTY_HEROIC) + { + ++itr; + continue; + } + } + + // if the map is loaded, reset it + if (Map* map = sMapMgr.FindMap(state->GetMapId(), state->GetInstanceId())) + if (map->IsDungeon()) + ((DungeonMap*)map)->Reset(method); + + // since this is a solo instance there should not be any players inside + if (method == INSTANCE_RESET_ALL || method == INSTANCE_RESET_CHANGE_DIFFICULTY) + SendResetInstanceSuccess(state->GetMapId()); + + state->DeleteFromDB(); + m_boundInstances[diff].erase(itr++); + + // the following should remove the instance save from the manager and delete it as well + state->RemovePlayer(this); + } +} + +void Player::SendResetInstanceSuccess(uint32 MapId) +{ + WorldPacket data(SMSG_INSTANCE_RESET, 4); + data << uint32(MapId); + GetSession()->SendPacket(&data); +} + +void Player::SendResetInstanceFailed(uint32 reason, uint32 MapId) +{ + // TODO: find what other fail reasons there are besides players in the instance + WorldPacket data(SMSG_INSTANCE_RESET_FAILED, 4); + data << uint32(reason); + data << uint32(MapId); + GetSession()->SendPacket(&data); +} + +/*********************************************************/ +/*** Update timers ***/ +/*********************************************************/ + +/// checks the 15 afk reports per 5 minutes limit +void Player::UpdateAfkReport(time_t currTime) +{ + if (m_bgData.bgAfkReportedTimer <= currTime) + { + m_bgData.bgAfkReportedCount = 0; + m_bgData.bgAfkReportedTimer = currTime + 5 * MINUTE; + } +} + +void Player::UpdateContestedPvP(uint32 diff) +{ + if (!m_contestedPvPTimer || isInCombat()) + return; + if (m_contestedPvPTimer <= diff) + { + ResetContestedPvP(); + } + else + m_contestedPvPTimer -= diff; +} + +void Player::UpdatePvPFlag(time_t currTime) +{ + if (!IsPvP()) + return; + if (pvpInfo.endTimer == 0 || currTime < (pvpInfo.endTimer + 300)) + return; + + UpdatePvP(false); +} + +void Player::UpdateDuelFlag(time_t currTime) +{ + if (!duel || duel->startTimer == 0 || currTime < duel->startTimer + 3) + return; + + SetUInt32Value(PLAYER_DUEL_TEAM, 1); + duel->opponent->SetUInt32Value(PLAYER_DUEL_TEAM, 2); + + duel->startTimer = 0; + duel->startTime = currTime; + duel->opponent->duel->startTimer = 0; + duel->opponent->duel->startTime = currTime; +} + +void Player::RemovePet(PetSaveMode mode) +{ + if (Pet* pet = GetPet()) + pet->Unsummon(mode, this); +} + +void Player::BuildPlayerChat(WorldPacket* data, uint8 msgtype, const std::string& text, uint32 language, const char* addonPrefix) const +{ + *data << uint8(msgtype); + *data << uint32(language); + *data << GetObjectGuid(); + *data << uint32(0); // constant unknown time 4.3.4 + if (addonPrefix) + *data << addonPrefix; + else + *data << GetObjectGuid(); + *data << uint32(text.length() + 1); + *data << text; + *data << uint8(GetChatTag()); +} + +void Player::Say(const std::string& text, const uint32 language) +{ + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildPlayerChat(&data, CHAT_MSG_SAY, text, language); + SendMessageToSetInRange(&data, sWorld.getConfig(CONFIG_FLOAT_LISTEN_RANGE_SAY), true); +} + +void Player::Yell(const std::string& text, const uint32 language) +{ + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildPlayerChat(&data, CHAT_MSG_YELL, text, language); + SendMessageToSetInRange(&data, sWorld.getConfig(CONFIG_FLOAT_LISTEN_RANGE_YELL), true); +} + +void Player::TextEmote(const std::string& text) +{ + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildPlayerChat(&data, CHAT_MSG_EMOTE, text, LANG_UNIVERSAL); + SendMessageToSetInRange(&data, sWorld.getConfig(CONFIG_FLOAT_LISTEN_RANGE_TEXTEMOTE), true, !sWorld.getConfig(CONFIG_BOOL_ALLOW_TWO_SIDE_INTERACTION_CHAT)); +} + +void Player::Whisper(const std::string& text, uint32 language, ObjectGuid receiver) +{ + Player* rPlayer = sObjectMgr.GetPlayer(receiver); + + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildPlayerChat(&data, CHAT_MSG_WHISPER, text, language); + rPlayer->GetSession()->SendPacket(&data); + + data.Initialize(SMSG_MESSAGECHAT, 200); + rPlayer->BuildPlayerChat(&data, CHAT_MSG_WHISPER_INFORM, text, language); + GetSession()->SendPacket(&data); + + if (!isAcceptWhispers()) + { + SetAcceptWhispers(true); + ChatHandler(this).SendSysMessage(LANG_COMMAND_WHISPERON); + } + + // announce afk or dnd message + if (rPlayer->isAFK()) + ChatHandler(this).PSendSysMessage(LANG_PLAYER_AFK, rPlayer->GetName(), rPlayer->autoReplyMsg.c_str()); + else if (rPlayer->isDND()) + ChatHandler(this).PSendSysMessage(LANG_PLAYER_DND, rPlayer->GetName(), rPlayer->autoReplyMsg.c_str()); +} + +void Player::WhisperAddon(const std::string& text, const std::string& prefix, ObjectGuid receiver) +{ + Player* rPlayer = sObjectMgr.GetPlayer(receiver); + + std::string _text(text); + + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildPlayerChat(&data, CHAT_MSG_WHISPER, _text, LANG_UNIVERSAL, prefix.c_str()); + rPlayer->GetSession()->SendPacket(&data); +} + +void Player::PetSpellInitialize() +{ + Pet* pet = GetPet(); + + if (!pet) + return; + + DEBUG_LOG("Pet Spells Groups"); + + CharmInfo* charmInfo = pet->GetCharmInfo(); + + WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * MAX_UNIT_ACTION_BAR_INDEX + 1 + 1); + data << pet->GetObjectGuid(); + data << uint16(pet->GetCreatureInfo()->family); // creature family (required for pet talents) + data << uint32(0); + data << uint8(charmInfo->GetReactState()) << uint8(charmInfo->GetCommandState()) << uint16(0); + + // action bar loop + charmInfo->BuildActionBar(&data); + + size_t spellsCountPos = data.wpos(); + + // spells count + uint8 addlist = 0; + data << uint8(addlist); // placeholder + + if (pet->IsPermanentPetFor(this)) + { + // spells loop + for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) + { + if (itr->second.state == PETSPELL_REMOVED) + continue; + + data << uint32(MAKE_UNIT_ACTION_BUTTON(itr->first, itr->second.active)); + ++addlist; + } + } + + data.put(spellsCountPos, addlist); + + uint8 cooldownsCount = pet->m_CreatureSpellCooldowns.size() + pet->m_CreatureCategoryCooldowns.size(); + data << uint8(cooldownsCount); + + time_t curTime = time(NULL); + + for (CreatureSpellCooldowns::const_iterator itr = pet->m_CreatureSpellCooldowns.begin(); itr != pet->m_CreatureSpellCooldowns.end(); ++itr) + { + time_t cooldown = (itr->second > curTime) ? (itr->second - curTime) * IN_MILLISECONDS : 0; + + data << uint32(itr->first); // spellid + data << uint16(0); // spell category? + data << uint32(cooldown); // cooldown + data << uint32(0); // category cooldown + } + + for (CreatureSpellCooldowns::const_iterator itr = pet->m_CreatureCategoryCooldowns.begin(); itr != pet->m_CreatureCategoryCooldowns.end(); ++itr) + { + time_t cooldown = (itr->second > curTime) ? (itr->second - curTime) * IN_MILLISECONDS : 0; + + data << uint32(itr->first); // spellid + data << uint16(0); // spell category? + data << uint32(0); // cooldown + data << uint32(cooldown); // category cooldown + } + + GetSession()->SendPacket(&data); +} + +void Player::SendPetGUIDs() +{ + if (!GetPetGuid()) + return; + + // Later this function might get modified for multiple guids + WorldPacket data(SMSG_PET_GUIDS, 12); + data << uint32(1); // count + data << ObjectGuid(GetPetGuid()); + GetSession()->SendPacket(&data); +} + +void Player::PossessSpellInitialize() +{ + Unit* charm = GetCharm(); + + if (!charm) + return; + + CharmInfo* charmInfo = charm->GetCharmInfo(); + + if (!charmInfo) + { + sLog.outError("Player::PossessSpellInitialize(): charm (GUID: %u TypeId: %u) has no charminfo!", charm->GetGUIDLow(), charm->GetTypeId()); + return; + } + + WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * MAX_UNIT_ACTION_BAR_INDEX + 1 + 1); + data << charm->GetObjectGuid(); + data << uint16(0); + data << uint32(0); + data << uint32(0); + + charmInfo->BuildActionBar(&data); + + data << uint8(0); // spells count + data << uint8(0); // cooldowns count + + GetSession()->SendPacket(&data); +} + +void Player::CharmSpellInitialize() +{ + Unit* charm = GetCharm(); + + if (!charm) + return; + + CharmInfo* charmInfo = charm->GetCharmInfo(); + if (!charmInfo) + { + sLog.outError("Player::CharmSpellInitialize(): the player's charm (GUID: %u TypeId: %u) has no charminfo!", charm->GetGUIDLow(), charm->GetTypeId()); + return; + } + + uint8 addlist = 0; + + if (charm->GetTypeId() != TYPEID_PLAYER) + { + CreatureInfo const* cinfo = ((Creature*)charm)->GetCreatureInfo(); + + if (cinfo && cinfo->type == CREATURE_TYPE_DEMON && getClass() == CLASS_WARLOCK) + { + for (uint32 i = 0; i < CREATURE_MAX_SPELLS; ++i) + { + if (charmInfo->GetCharmSpell(i)->GetAction()) + ++addlist; + } + } + } + + WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * MAX_UNIT_ACTION_BAR_INDEX + 1 + 4 * addlist + 1); + data << charm->GetObjectGuid(); + data << uint16(0); + data << uint32(0); + + if (charm->GetTypeId() != TYPEID_PLAYER) + data << uint8(charmInfo->GetReactState()) << uint8(charmInfo->GetCommandState()) << uint16(0); + else + data << uint8(0) << uint8(0) << uint16(0); + + charmInfo->BuildActionBar(&data); + + data << uint8(addlist); + + if (addlist) + { + for (uint32 i = 0; i < CREATURE_MAX_SPELLS; ++i) + { + CharmSpellEntry* cspell = charmInfo->GetCharmSpell(i); + if (cspell->GetAction()) + data << uint32(cspell->packedData); + } + } + + data << uint8(0); // cooldowns count + + GetSession()->SendPacket(&data); +} + +void Player::RemovePetActionBar() +{ + WorldPacket data(SMSG_PET_SPELLS, 8); + data << ObjectGuid(); + SendDirectMessage(&data); +} + +void Player::AddSpellMod(Aura* aura, bool apply) +{ + Modifier const* mod = aura->GetModifier(); + Opcodes opcode = (mod->m_auraname == SPELL_AURA_ADD_FLAT_MODIFIER) ? SMSG_SET_FLAT_SPELL_MODIFIER : SMSG_SET_PCT_SPELL_MODIFIER; + + uint32 modTypeCount = 0; // count of mods per one mod->op + WorldPacket data(opcode, 4 + 4 + 1 + 1 + 4); + data << uint32(1); // count of different mod->op's in packet + size_t writePos = data.wpos(); + data << uint32(modTypeCount); + data << uint8(mod->m_miscvalue); + for (int eff = 0; eff < 96; ++eff) + { + uint64 _mask = 0; + uint32 _mask2 = 0; + + if (eff < 64) + _mask = uint64(1) << (eff - 0); + else + _mask2 = uint32(1) << (eff - 64); + + if (aura->GetAuraSpellClassMask().IsFitToFamilyMask(_mask, _mask2)) + { + int32 val = 0; + for (AuraList::const_iterator itr = m_spellMods[mod->m_miscvalue].begin(); itr != m_spellMods[mod->m_miscvalue].end(); ++itr) + { + if ((*itr)->GetModifier()->m_auraname == mod->m_auraname && ((*itr)->GetAuraSpellClassMask().IsFitToFamilyMask(_mask, _mask2))) + val += (*itr)->GetModifier()->m_amount; + } + val += apply ? mod->m_amount : -(mod->m_amount); + data << uint8(eff); + data << float(val); + ++modTypeCount; + } + } + data.put(writePos, modTypeCount); + SendDirectMessage(&data); + + if (apply) + m_spellMods[mod->m_miscvalue].push_back(aura); + else + m_spellMods[mod->m_miscvalue].remove(aura); +} + +template T Player::ApplySpellMod(uint32 spellId, SpellModOp op, T& basevalue, Spell const* /*spell*/) +{ + SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellId); + if (!spellInfo) + return 0; + + int32 totalpct = 0; + int32 totalflat = 0; + for (AuraList::iterator itr = m_spellMods[op].begin(); itr != m_spellMods[op].end(); ++itr) + { + Aura* aura = *itr; + + Modifier const* mod = aura->GetModifier(); + + if (!aura->isAffectedOnSpell(spellInfo)) + continue; + + if (mod->m_auraname == SPELL_AURA_ADD_FLAT_MODIFIER) + totalflat += mod->m_amount; + else + { + // skip percent mods for null basevalue (most important for spell mods with charges ) + if (basevalue == T(0)) + continue; + + // special case (skip >10sec spell casts for instant cast setting) + if (mod->m_miscvalue == SPELLMOD_CASTING_TIME + && basevalue >= T(10 * IN_MILLISECONDS) && mod->m_amount <= -100) + continue; + + totalpct += mod->m_amount; + } + } + + float diff = (float)basevalue * (float)totalpct / 100.0f + (float)totalflat; + basevalue = T((float)basevalue + diff); + return T(diff); +} + +template int32 Player::ApplySpellMod(uint32 spellId, SpellModOp op, int32& basevalue, Spell const* spell); +template uint32 Player::ApplySpellMod(uint32 spellId, SpellModOp op, uint32& basevalue, Spell const* spell); +template float Player::ApplySpellMod(uint32 spellId, SpellModOp op, float& basevalue, Spell const* spell); + +// send Proficiency +void Player::SendProficiency(ItemClass itemClass, uint32 itemSubclassMask) +{ + WorldPacket data(SMSG_SET_PROFICIENCY, 1 + 4); + data << uint8(itemClass) << uint32(itemSubclassMask); + GetSession()->SendPacket(&data); +} + +void Player::RemovePetitionsAndSigns(ObjectGuid guid) +{ + uint32 lowguid = guid.GetCounter(); + + QueryResult* result = NULL; + result = CharacterDatabase.PQuery("SELECT ownerguid,petitionguid FROM petition_sign WHERE playerguid = '%u'", lowguid); + if (result) + { + do // this part effectively does nothing, since the deletion / modification only takes place _after_ the PetitionQuery. Though I don't know if the result remains intact if I execute the delete query beforehand. + { + // and SendPetitionQueryOpcode reads data from the DB + Field* fields = result->Fetch(); + ObjectGuid ownerguid = ObjectGuid(HIGHGUID_PLAYER, fields[0].GetUInt32()); + ObjectGuid petitionguid = ObjectGuid(HIGHGUID_ITEM, fields[1].GetUInt32()); + + // send update if charter owner in game + Player* owner = sObjectMgr.GetPlayer(ownerguid); + if (owner) + owner->GetSession()->SendPetitionQueryOpcode(petitionguid); + } + while (result->NextRow()); + + delete result; + + CharacterDatabase.PExecute("DELETE FROM petition_sign WHERE playerguid = '%u'", lowguid); + } + + CharacterDatabase.BeginTransaction(); + CharacterDatabase.PExecute("DELETE FROM petition WHERE ownerguid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM petition_sign WHERE ownerguid = '%u'", lowguid); + CharacterDatabase.CommitTransaction(); +} + +void Player::LeaveAllArenaTeams(ObjectGuid guid) +{ + uint32 lowguid = guid.GetCounter(); + QueryResult* result = CharacterDatabase.PQuery("SELECT arena_team_member.arenateamid FROM arena_team_member JOIN arena_team ON arena_team_member.arenateamid = arena_team.arenateamid WHERE guid='%u'", lowguid); + if (!result) + return; + + do + { + Field* fields = result->Fetch(); + if (uint32 at_id = fields[0].GetUInt32()) + if (ArenaTeam* at = sObjectMgr.GetArenaTeamById(at_id)) + at->DelMember(guid); + } + while (result->NextRow()); + + delete result; +} + +void Player::SetRestBonus(float rest_bonus_new) +{ + // Prevent resting on max level + if (getLevel() >= sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)) + rest_bonus_new = 0; + + if (rest_bonus_new < 0) + rest_bonus_new = 0; + + float rest_bonus_max = (float)GetUInt32Value(PLAYER_NEXT_LEVEL_XP) * 1.5f / 2.0f; + + if (rest_bonus_new > rest_bonus_max) + m_rest_bonus = rest_bonus_max; + else + m_rest_bonus = rest_bonus_new; + + // update data for client + if (m_rest_bonus > 10) + SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_RESTED); + else if (m_rest_bonus <= 1) + SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_NORMAL); + + // RestTickUpdate + SetUInt32Value(PLAYER_REST_STATE_EXPERIENCE, uint32(m_rest_bonus)); +} + +void Player::HandleStealthedUnitsDetection() +{ + std::list stealthedUnits; + + MaNGOS::AnyStealthedCheck u_check(this); + MaNGOS::UnitListSearcher searcher(stealthedUnits, u_check); + Cell::VisitAllObjects(this, searcher, MAX_PLAYER_STEALTH_DETECT_RANGE); + + WorldObject const* viewPoint = GetCamera().GetBody(); + + for (std::list::const_iterator i = stealthedUnits.begin(); i != stealthedUnits.end(); ++i) + { + if ((*i) == this) + continue; + + bool hasAtClient = HaveAtClient((*i)); + bool hasDetected = (*i)->isVisibleForOrDetect(this, viewPoint, true); + + if (hasDetected) + { + if (!hasAtClient) + { + ObjectGuid i_guid = (*i)->GetObjectGuid(); + (*i)->SendCreateUpdateToPlayer(this); + m_clientGUIDs.insert(i_guid); + + DEBUG_FILTER_LOG(LOG_FILTER_VISIBILITY_CHANGES, "%s is detected in stealth by player %u. Distance = %f", i_guid.GetString().c_str(), GetGUIDLow(), GetDistance(*i)); + + // target aura duration for caster show only if target exist at caster client + // send data at target visibility change (adding to client) + if ((*i) != this && (*i)->isType(TYPEMASK_UNIT)) + SendAurasForTarget(*i); + } + } + else + { + if (hasAtClient) + { + (*i)->DestroyForPlayer(this); + m_clientGUIDs.erase((*i)->GetObjectGuid()); + } + } + } +} + +bool Player::ActivateTaxiPathTo(std::vector const& nodes, Creature* npc /*= NULL*/, uint32 spellid /*= 0*/) +{ + if (nodes.size() < 2) + return false; + + // not let cheating with start flight in time of logout process || if casting not finished || while in combat || if not use Spell's with EffectSendTaxi + if (GetSession()->isLogingOut() || isInCombat()) + { + GetSession()->SendActivateTaxiReply(ERR_TAXIPLAYERBUSY); + return false; + } + + if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_MOVE)) + return false; + + // taximaster case + if (npc) + { + // not let cheating with start flight mounted + if (IsMounted()) + { + GetSession()->SendActivateTaxiReply(ERR_TAXIPLAYERALREADYMOUNTED); + return false; + } + + if (IsInDisallowedMountForm()) + { + GetSession()->SendActivateTaxiReply(ERR_TAXIPLAYERSHAPESHIFTED); + return false; + } + + // not let cheating with start flight in time of logout process || if casting not finished || while in combat || if not use Spell's with EffectSendTaxi + if (IsNonMeleeSpellCasted(false)) + { + GetSession()->SendActivateTaxiReply(ERR_TAXIPLAYERBUSY); + return false; + } + } + // cast case or scripted call case + else + { + RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); + + if (IsInDisallowedMountForm()) + RemoveSpellsCausingAura(SPELL_AURA_MOD_SHAPESHIFT); + + if (Spell* spell = GetCurrentSpell(CURRENT_GENERIC_SPELL)) + if (spell->m_spellInfo->Id != spellid) + InterruptSpell(CURRENT_GENERIC_SPELL, false); + + InterruptSpell(CURRENT_AUTOREPEAT_SPELL, false); + + if (Spell* spell = GetCurrentSpell(CURRENT_CHANNELED_SPELL)) + if (spell->m_spellInfo->Id != spellid) + InterruptSpell(CURRENT_CHANNELED_SPELL, true); + } + + uint32 sourcenode = nodes[0]; + + // starting node too far away (cheat?) + TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(sourcenode); + if (!node) + { + GetSession()->SendActivateTaxiReply(ERR_TAXINOSUCHPATH); + return false; + } + + // check node starting pos data set case if provided + if (node->x != 0.0f || node->y != 0.0f || node->z != 0.0f) + { + if (node->map_id != GetMapId() || + (node->x - GetPositionX()) * (node->x - GetPositionX()) + + (node->y - GetPositionY()) * (node->y - GetPositionY()) + + (node->z - GetPositionZ()) * (node->z - GetPositionZ()) > + (2 * INTERACTION_DISTANCE) * (2 * INTERACTION_DISTANCE) * (2 * INTERACTION_DISTANCE)) + { + GetSession()->SendActivateTaxiReply(ERR_TAXITOOFARAWAY); + return false; + } + } + // node must have pos if taxi master case (npc != NULL) + else if (npc) + { + GetSession()->SendActivateTaxiReply(ERR_TAXIUNSPECIFIEDSERVERERROR); + return false; + } + + // Prepare to flight start now + + // stop combat at start taxi flight if any + CombatStop(); + + // stop trade (client cancel trade at taxi map open but cheating tools can be used for reopen it) + TradeCancel(true); + + // clean not finished taxi path if any + m_taxi.ClearTaxiDestinations(); + + // 0 element current node + m_taxi.AddTaxiDestination(sourcenode); + + // fill destinations path tail + uint32 sourcepath = 0; + uint32 totalcost = 0; + + uint32 prevnode = sourcenode; + uint32 lastnode = 0; + + for (uint32 i = 1; i < nodes.size(); ++i) + { + uint32 path, cost; + + lastnode = nodes[i]; + sObjectMgr.GetTaxiPath(prevnode, lastnode, path, cost); + + if (!path) + { + m_taxi.ClearTaxiDestinations(); + return false; + } + + totalcost += cost; + + if (prevnode == sourcenode) + sourcepath = path; + + m_taxi.AddTaxiDestination(lastnode); + + prevnode = lastnode; + } + + // get mount model (in case non taximaster (npc==NULL) allow more wide lookup) + uint32 mount_display_id = sObjectMgr.GetTaxiMountDisplayId(sourcenode, GetTeam(), npc == NULL); + + // in spell case allow 0 model + if ((mount_display_id == 0 && spellid == 0) || sourcepath == 0) + { + GetSession()->SendActivateTaxiReply(ERR_TAXIUNSPECIFIEDSERVERERROR); + + m_taxi.ClearTaxiDestinations(); + return false; + } + + uint64 money = GetMoney(); + + if (npc) + totalcost = (uint32)ceil(totalcost * GetReputationPriceDiscount(npc)); + + if (money < totalcost) + { + GetSession()->SendActivateTaxiReply(ERR_TAXINOTENOUGHMONEY); + + m_taxi.ClearTaxiDestinations(); + return false; + } + + // Checks and preparations done, DO FLIGHT + ModifyMoney(-(int64)totalcost); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TRAVELLING, totalcost); + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_FLIGHT_PATHS_TAKEN, 1); + + // prevent stealth flight + RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); + + GetSession()->SendActivateTaxiReply(ERR_TAXIOK); + GetSession()->SendDoFlight(mount_display_id, sourcepath); + + return true; +} + +bool Player::ActivateTaxiPathTo(uint32 taxi_path_id, uint32 spellid /*= 0*/) +{ + TaxiPathEntry const* entry = sTaxiPathStore.LookupEntry(taxi_path_id); + if (!entry) + return false; + + std::vector nodes; + + nodes.resize(2); + nodes[0] = entry->from; + nodes[1] = entry->to; + + return ActivateTaxiPathTo(nodes, NULL, spellid); +} + +void Player::ContinueTaxiFlight() +{ + uint32 sourceNode = m_taxi.GetTaxiSource(); + if (!sourceNode) + return; + + DEBUG_LOG("WORLD: Restart character %u taxi flight", GetGUIDLow()); + + uint32 mountDisplayId = sObjectMgr.GetTaxiMountDisplayId(sourceNode, GetTeam(), true); + uint32 path = m_taxi.GetCurrentTaxiPath(); + + // search appropriate start path node + uint32 startNode = 0; + + TaxiPathNodeList const& nodeList = sTaxiPathNodesByPath[path]; + + float distPrev = MAP_SIZE * MAP_SIZE; + float distNext = + (nodeList[0].x - GetPositionX()) * (nodeList[0].x - GetPositionX()) + + (nodeList[0].y - GetPositionY()) * (nodeList[0].y - GetPositionY()) + + (nodeList[0].z - GetPositionZ()) * (nodeList[0].z - GetPositionZ()); + + for (uint32 i = 1; i < nodeList.size(); ++i) + { + TaxiPathNodeEntry const& node = nodeList[i]; + TaxiPathNodeEntry const& prevNode = nodeList[i - 1]; + + // skip nodes at another map + if (node.mapid != GetMapId()) + continue; + + distPrev = distNext; + + distNext = + (node.x - GetPositionX()) * (node.x - GetPositionX()) + + (node.y - GetPositionY()) * (node.y - GetPositionY()) + + (node.z - GetPositionZ()) * (node.z - GetPositionZ()); + + float distNodes = + (node.x - prevNode.x) * (node.x - prevNode.x) + + (node.y - prevNode.y) * (node.y - prevNode.y) + + (node.z - prevNode.z) * (node.z - prevNode.z); + + if (distNext + distPrev < distNodes) + { + startNode = i; + break; + } + } + + GetSession()->SendDoFlight(mountDisplayId, path, startNode); +} + +void Player::ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs) +{ + // last check 4.3.4 + WorldPacket data(SMSG_SPELL_COOLDOWN, 8 + 1 + m_spells.size() * 8); + data << GetObjectGuid(); + data << uint8(0x0); // flags (0x1, 0x2) + time_t curTime = time(NULL); + for (PlayerSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr) + { + if (itr->second.state == PLAYERSPELL_REMOVED) + continue; + uint32 unSpellId = itr->first; + SpellEntry const* spellInfo = sSpellStore.LookupEntry(unSpellId); + if (!spellInfo) + { + MANGOS_ASSERT(spellInfo); + continue; + } + + // Not send cooldown for this spells + if (spellInfo->HasAttribute(SPELL_ATTR_DISABLED_WHILE_ACTIVE)) + continue; + + if ((idSchoolMask & GetSpellSchoolMask(spellInfo)) && GetSpellCooldownDelay(unSpellId) < unTimeMs) + { + data << uint32(unSpellId); + data << uint32(unTimeMs); // in m.secs + AddSpellCooldown(unSpellId, 0, curTime + unTimeMs / IN_MILLISECONDS); + } + } + GetSession()->SendPacket(&data); +} + +void Player::InitDataForForm(bool reapplyMods) +{ + ShapeshiftForm form = GetShapeshiftForm(); + + SpellShapeshiftFormEntry const* ssEntry = sSpellShapeshiftFormStore.LookupEntry(form); + if (ssEntry && ssEntry->attackSpeed) + { + SetAttackTime(BASE_ATTACK, ssEntry->attackSpeed); + SetAttackTime(OFF_ATTACK, ssEntry->attackSpeed); + SetAttackTime(RANGED_ATTACK, BASE_ATTACK_TIME); + } + else + SetRegularAttackTime(); + + switch (form) + { + case FORM_CAT: + { + if (getPowerType() != POWER_ENERGY) + setPowerType(POWER_ENERGY); + break; + } + case FORM_BEAR: + { + if (getPowerType() != POWER_RAGE) + setPowerType(POWER_RAGE); + break; + } + default: // 0, for example + { + ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(getClass()); + if (cEntry && cEntry->powerType < MAX_POWERS && uint32(getPowerType()) != cEntry->powerType) + setPowerType(Powers(cEntry->powerType)); + break; + } + } + + // update auras at form change, ignore this at mods reapply (.reset stats/etc) when form not change. + if (!reapplyMods) + UpdateEquipSpellsAtFormChange(); + + UpdateAttackPowerAndDamage(); + UpdateAttackPowerAndDamage(true); +} + +void Player::InitDisplayIds() +{ + PlayerInfo const* info = sObjectMgr.GetPlayerInfo(getRace(), getClass()); + if (!info) + { + sLog.outError("Player %u has incorrect race/class pair. Can't init display ids.", GetGUIDLow()); + return; + } + + // reset scale before reapply auras + SetObjectScale(DEFAULT_OBJECT_SCALE); + + uint8 gender = getGender(); + switch (gender) + { + case GENDER_FEMALE: + SetDisplayId(info->displayId_f); + SetNativeDisplayId(info->displayId_f); + break; + case GENDER_MALE: + SetDisplayId(info->displayId_m); + SetNativeDisplayId(info->displayId_m); + break; + default: + sLog.outError("Invalid gender %u for player", gender); + return; + } +} + +void Player::TakeExtendedCost(uint32 extendedCostId) +{ + ItemExtendedCostEntry const* extendedCost = sItemExtendedCostStore.LookupEntry(extendedCostId); + + for (uint8 i = 0; i < MAX_EXTENDED_COST_ITEMS; ++i) + { + if (extendedCost->reqitem[i]) + DestroyItemCount(extendedCost->reqitem[i], extendedCost->reqitemcount[i], true); + } + + for (int i = 0; i < MAX_EXTENDED_COST_CURRENCIES; ++i) + { + if (extendedCost->reqcur[i] == CURRENCY_NONE) + continue; + + if (extendedCost->IsSeasonCurrencyRequirement(i)) + continue; + + CurrencyTypesEntry const * entry = sCurrencyTypesStore.LookupEntry(extendedCost->reqcur[i]); + if (!entry) + continue; + + int32 cost = int32(extendedCost->reqcurrcount[i]); + ModifyCurrencyCount(entry->ID, -cost); + } +} + +// Return true is the bought item has a max count to force refresh of window by caller +bool Player::BuyItemFromVendorSlot(ObjectGuid vendorGuid, uint32 vendorslot, uint32 item, uint32 count, uint8 bag, uint8 slot) +{ + // cheating attempt + if (count < 1) count = 1; + + if (!isAlive()) + return false; + + ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(item); + if (!pProto) + { + SendBuyError(BUY_ERR_CANT_FIND_ITEM, NULL, item, 0); + return false; + } + + Creature* pCreature = GetNPCIfCanInteractWith(vendorGuid, UNIT_NPC_FLAG_VENDOR); + if (!pCreature) + { + DEBUG_LOG("WORLD: BuyItemFromVendor - %s not found or you can't interact with him.", vendorGuid.GetString().c_str()); + SendBuyError(BUY_ERR_DISTANCE_TOO_FAR, NULL, item, 0); + return false; + } + + VendorItemData const* vItems = pCreature->GetVendorItems(); + VendorItemData const* tItems = pCreature->GetVendorTemplateItems(); + if ((!vItems || vItems->Empty()) && (!tItems || tItems->Empty())) + { + SendBuyError(BUY_ERR_CANT_FIND_ITEM, pCreature, item, 0); + return false; + } + + uint32 vCount = vItems ? vItems->GetItemCount() : 0; + uint32 tCount = tItems ? tItems->GetItemCount() : 0; + + if (vendorslot >= vCount + tCount) + { + SendBuyError(BUY_ERR_CANT_FIND_ITEM, pCreature, item, 0); + return false; + } + + VendorItem const* crItem = vendorslot < vCount ? vItems->GetItem(vendorslot) : tItems->GetItem(vendorslot - vCount); + if (!crItem) // store diff item (cheating) + { + SendBuyError(BUY_ERR_CANT_FIND_ITEM, pCreature, item, 0); + return false; + } + + if (crItem->item != item) // store diff item (cheating or special convert) + { + bool converted = false; + + // possible item converted for BoA case + ItemPrototype const* crProto = ObjectMgr::GetItemPrototype(crItem->item); + if (crProto->Flags & ITEM_FLAG_BOA && crProto->RequiredReputationFaction && + uint32(GetReputationRank(crProto->RequiredReputationFaction)) >= crProto->RequiredReputationRank) + converted = (sObjectMgr.GetItemConvert(crItem->item, getRaceMask()) != 0); + + if (!converted) + { + SendBuyError(BUY_ERR_CANT_FIND_ITEM, pCreature, item, 0); + return false; + } + } + + uint32 totalCount = count; + + // check current item amount if it limited + if (crItem->maxcount != 0) + { + if (pCreature->GetVendorItemCurrentCount(crItem) < totalCount) + { + SendBuyError(BUY_ERR_ITEM_ALREADY_SOLD, pCreature, item, 0); + return false; + } + } + + if (uint32(GetReputationRank(pProto->RequiredReputationFaction)) < pProto->RequiredReputationRank) + { + SendBuyError(BUY_ERR_REPUTATION_REQUIRE, pCreature, item, 0); + return false; + } + + if (uint32 extendedCostId = crItem->ExtendedCost) + { + if (pProto->BuyCount != count) + { + SendEquipError(EQUIP_ERR_CANT_BUY_QUANTITY, NULL, NULL); + return false; + } + + ItemExtendedCostEntry const* iece = sItemExtendedCostStore.LookupEntry(extendedCostId); + if (!iece) + { + sLog.outError("Item %u have wrong ExtendedCost field value %u", pProto->ItemId, extendedCostId); + return false; + } + + // item base price + for (uint8 i = 0; i < MAX_EXTENDED_COST_ITEMS; ++i) + { + if (iece->reqitem[i] && !HasItemCount(iece->reqitem[i], iece->reqitemcount[i])) + { + SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, NULL, NULL); + return false; + } + } + + // currency price + for (uint8 i = 0; i < MAX_EXTENDED_COST_CURRENCIES; ++i) + { + if (iece->reqcur[i] == CURRENCY_NONE) + continue; + + CurrencyTypesEntry const * costCurrency = sCurrencyTypesStore.LookupEntry(iece->reqcur[i]); + if (!costCurrency) + { + sLog.outError("Item %u has ExtendedCost %u with unexistent currency id %u", pProto->ItemId, extendedCostId, iece->reqcur[i]); + continue; + } + + int32 cost = int32(iece->reqcurrcount[i]); + + bool hasCount = iece->IsSeasonCurrencyRequirement(i) ? HasCurrencySeasonCount(iece->reqcur[i], cost) : HasCurrencyCount(iece->reqcur[i], cost); + if (!hasCount) + { + SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, NULL); + return false; + } + } + + // check for personal arena rating requirement + if (GetMaxPersonalArenaRatingRequirement(iece->reqarenaslot) < iece->reqpersonalarenarating) + { + // probably not the proper equip err + SendEquipError(EQUIP_ERR_CANT_EQUIP_RANK, NULL, NULL); + return false; + } + } + + if (crItem->conditionId && !isGameMaster() && !sObjectMgr.IsPlayerMeetToCondition(crItem->conditionId, this, pCreature->GetMap(), pCreature, CONDITION_FROM_VENDOR)) + { + SendBuyError(BUY_ERR_CANT_FIND_ITEM, pCreature, item, 0); + return false; + } + + uint64 price = (crItem->ExtendedCost == 0 || pProto->Flags2 & ITEM_FLAG2_EXT_COST_REQUIRES_GOLD) ? pProto->BuyPrice * count : 0; + if (pProto->BuyCount > 1) + price = uint64(price / float(pProto->BuyCount) + 0.5f); + + // reputation discount + if (price) + price = uint64(floor(price * GetReputationPriceDiscount(pCreature))); + + if (GetMoney() < price) + { + SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, pCreature, item, 0); + return false; + } + + Item* pItem = NULL; + + if ((bag == NULL_BAG && slot == NULL_SLOT) || IsInventoryPos(bag, slot)) + { + ItemPosCountVec dest; + InventoryResult msg = CanStoreNewItem(bag, slot, dest, item, totalCount); + if (msg != EQUIP_ERR_OK) + { + SendEquipError(msg, NULL, NULL, item); + return false; + } + + ModifyMoney(-int64(price)); + + if (crItem->ExtendedCost) + TakeExtendedCost(crItem->ExtendedCost); + + pItem = StoreNewItem(dest, item, true); + } + else if (IsEquipmentPos(bag, slot)) + { + if (totalCount != 1) + { + SendEquipError(EQUIP_ERR_ITEM_CANT_BE_EQUIPPED, NULL, NULL); + return false; + } + + uint16 dest; + InventoryResult msg = CanEquipNewItem(slot, dest, item, false); + if (msg != EQUIP_ERR_OK) + { + SendEquipError(msg, NULL, NULL, item); + return false; + } + + ModifyMoney(-int64(price)); + + if (crItem->ExtendedCost) + TakeExtendedCost(crItem->ExtendedCost); + + pItem = EquipNewItem(dest, item, true); + + if (pItem) + AutoUnequipOffhandIfNeed(); + } + else + { + SendEquipError(EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT, NULL, NULL); + return false; + } + + if (!pItem) + return false; + + uint32 new_count = pCreature->UpdateVendorItemCurrentCount(crItem, totalCount); + + WorldPacket data(SMSG_BUY_ITEM, 8 + 4 + 4 + 4); + data << pCreature->GetObjectGuid(); + data << uint32(vendorslot + 1); // numbered from 1 at client + data << uint32(crItem->maxcount > 0 ? new_count : 0xFFFFFFFF); + data << uint32(count); + GetSession()->SendPacket(&data); + + SendNewItem(pItem, totalCount, true, false, false); + + return crItem->maxcount != 0; +} + +bool Player::BuyCurrencyFromVendorSlot(ObjectGuid vendorGuid, uint32 vendorslot, uint32 currencyId, uint32 count) +{ + // cheating attempt + if (count < 1) count = 1; + + if (!isAlive()) + return false; + + CurrencyTypesEntry const* pCurrency = sCurrencyTypesStore.LookupEntry(currencyId); + if (!pCurrency) + return false; + + if (pCurrency->Category == CURRENCY_CATEGORY_META) + return false; + + Creature* pCreature = GetNPCIfCanInteractWith(vendorGuid, UNIT_NPC_FLAG_VENDOR); + if (!pCreature) + { + DEBUG_LOG("WORLD: BuyCurrencyFromVendorSlot - %s not found or you can't interact with him.", vendorGuid.GetString().c_str()); + return false; + } + + VendorItemData const* vItems = pCreature->GetVendorItems(); + VendorItemData const* tItems = pCreature->GetVendorTemplateItems(); + if ((!vItems || vItems->Empty()) && (!tItems || tItems->Empty())) + return false; + + uint32 vCount = vItems ? vItems->GetItemCount() : 0; + uint32 tCount = tItems ? tItems->GetItemCount() : 0; + + if (vendorslot >= vCount + tCount) + return false; + + VendorItem const* crItem = vendorslot < vCount ? vItems->GetItem(vendorslot) : tItems->GetItem(vendorslot - vCount); + if (!crItem) // store diff item (cheating) + return false; + + if (crItem->item != currencyId) // store diff item (cheating) + return false; + + if (!crItem->maxcount) + { + DEBUG_LOG("WORLD: BuyCurrencyFromVendorSlot - %s: crItem->maxcount (%u) == 0 for currency %u and player %s.", + vendorGuid.GetString().c_str(), crItem->maxcount, currencyId, GetGuidStr().c_str()); + return false; + } + + if (uint32 extendedCostId = crItem->ExtendedCost) + { + if (crItem->maxcount != count) + { + SendEquipError(EQUIP_ERR_CANT_BUY_QUANTITY, NULL, NULL); + return false; + } + + ItemExtendedCostEntry const* iece = sItemExtendedCostStore.LookupEntry(extendedCostId); + if (!iece) + { + sLog.outError("WORLD: BuyCurrencyFromVendorSlot: Currency %u have wrong ExtendedCost field value %u for %s", currencyId, extendedCostId, vendorGuid.GetString().c_str()); + return false; + } + + // item base price + for (uint8 i = 0; i < MAX_EXTENDED_COST_ITEMS; ++i) + { + if (iece->reqitem[i] && !HasItemCount(iece->reqitem[i], iece->reqitemcount[i])) + { + SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, NULL, NULL); + return false; + } + } + + // currency price + for (uint8 i = 0; i < MAX_EXTENDED_COST_CURRENCIES; ++i) + { + if (iece->reqcur[i] == CURRENCY_NONE) + continue; + + CurrencyTypesEntry const * costCurrency = sCurrencyTypesStore.LookupEntry(iece->reqcur[i]); + if (!costCurrency) + { + sLog.outError("Currency %u has ExtendedCost %u with unexistent currency id %u", currencyId, extendedCostId, iece->reqcur[i]); + continue; + } + + int32 cost = int32(iece->reqcurrcount[i]); + bool hasCount = iece->IsSeasonCurrencyRequirement(i) ? HasCurrencySeasonCount(iece->reqcur[i], cost) : HasCurrencyCount(iece->reqcur[i], cost); + if (!hasCount) + { + SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, NULL); + return false; + } + } + + // check for personal arena rating requirement + if (GetMaxPersonalArenaRatingRequirement(iece->reqarenaslot) < iece->reqpersonalarenarating) + { + // probably not the proper equip err + SendEquipError(EQUIP_ERR_CANT_EQUIP_RANK, NULL, NULL); + return false; + } + } + else + { + SendBuyError(BUY_ERR_ITEM_SOLD_OUT, 0, 0, 0); + return false; + } + + if (uint32 totalCap = GetCurrencyTotalCap(pCurrency)) + { + if (GetCurrencyCount(currencyId) >= totalCap) + { + + SendBuyError(BUY_ERR_CANT_CARRY_MORE, 0, 0, 0); + return false; + } + } + + if (uint32 weekCap = GetCurrencyWeekCap(pCurrency)) + { + if (GetCurrencyWeekCount(currencyId) >= weekCap) + { + SendBuyError(BUY_ERR_CANT_CARRY_MORE, 0, 0, 0); + return false; + } + } + + if (crItem->ExtendedCost) + TakeExtendedCost(crItem->ExtendedCost); + + ModifyCurrencyCount(currencyId, crItem->maxcount, true, false, true); + + + DEBUG_LOG("WORLD: BuyCurrencyFromVendorSlot - %s: Player %s buys currency %u amount %u count %u.", + vendorGuid.GetString().c_str(), GetGuidStr().c_str(), currencyId, crItem->maxcount, count); + + return true; +} + +uint32 Player::GetMaxPersonalArenaRatingRequirement(uint32 minarenaslot) +{ + // returns the maximal personal arena rating that can be used to purchase items requiring this condition + // the personal rating of the arena team must match the required limit as well + // so return max[in arenateams](min(personalrating[teamtype], teamrating[teamtype])) + uint32 max_personal_rating = 0; + for (int i = minarenaslot; i < MAX_ARENA_SLOT; ++i) + { + if (ArenaTeam* at = sObjectMgr.GetArenaTeamById(GetArenaTeamId(i))) + { + uint32 p_rating = GetArenaPersonalRating(i); + uint32 t_rating = at->GetRating(); + p_rating = p_rating < t_rating ? p_rating : t_rating; + if (max_personal_rating < p_rating) + max_personal_rating = p_rating; + } + } + return max_personal_rating; +} + +void Player::UpdateHomebindTime(uint32 time) +{ + // GMs never get homebind timer online + if (m_InstanceValid || isGameMaster()) + { + if (m_HomebindTimer) // instance valid, but timer not reset + { + // hide reminder + WorldPacket data(SMSG_RAID_GROUP_ONLY, 4 + 4); + data << uint32(0); + data << uint32(ERR_RAID_GROUP_NONE); // error used only when timer = 0 + GetSession()->SendPacket(&data); + } + // instance is valid, reset homebind timer + m_HomebindTimer = 0; + } + else if (m_HomebindTimer > 0) + { + if (time >= m_HomebindTimer) + { + // teleport to nearest graveyard + RepopAtGraveyard(); + } + else + m_HomebindTimer -= time; + } + else + { + // instance is invalid, start homebind timer + m_HomebindTimer = 60000; + // send message to player + WorldPacket data(SMSG_RAID_GROUP_ONLY, 4 + 4); + data << uint32(m_HomebindTimer); + data << uint32(ERR_RAID_GROUP_NONE); // error used only when timer = 0 + GetSession()->SendPacket(&data); + DEBUG_LOG("PLAYER: Player '%s' (GUID: %u) will be teleported to homebind in 60 seconds", GetName(), GetGUIDLow()); + } +} + +void Player::UpdatePvP(bool state, bool ovrride) +{ + if (!state || ovrride) + { + SetPvP(state); + pvpInfo.endTimer = 0; + } + else + { + if (pvpInfo.endTimer != 0) + pvpInfo.endTimer = time(NULL); + else + SetPvP(state); + } +} + +void Player::AddSpellAndCategoryCooldowns(SpellEntry const* spellInfo, uint32 itemId, Spell* spell, bool infinityCooldown) +{ + // init cooldown values + uint32 cat = 0; + int32 rec = -1; + int32 catrec = -1; + + // some special item spells without correct cooldown in SpellInfo + // cooldown information stored in item prototype + + if (itemId) + { + if (ItemPrototype const* proto = ObjectMgr::GetItemPrototype(itemId)) + { + for (int idx = 0; idx < MAX_ITEM_PROTO_SPELLS; ++idx) + { + if (proto->Spells[idx].SpellId == spellInfo->Id) + { + cat = proto->Spells[idx].SpellCategory; + rec = proto->Spells[idx].SpellCooldown; + catrec = proto->Spells[idx].SpellCategoryCooldown; + break; + } + } + } + } + + // if no cooldown found above then base at DBC data + if (rec < 0 && catrec < 0) + { + cat = spellInfo->GetCategory(); + rec = spellInfo->GetRecoveryTime(); + catrec = spellInfo->GetCategoryRecoveryTime(); + } + + time_t curTime = time(NULL); + + time_t catrecTime; + time_t recTime; + + // overwrite time for selected category + if (infinityCooldown) + { + // use +MONTH as infinity mark for spell cooldown (will checked as MONTH/2 at save ans skipped) + // but not allow ignore until reset or re-login + catrecTime = catrec > 0 ? curTime + infinityCooldownDelay : 0; + recTime = rec > 0 ? curTime + infinityCooldownDelay : catrecTime; + } + else + { + // shoot spells used equipped item cooldown values already assigned in GetAttackTime(RANGED_ATTACK) + // prevent 0 cooldowns set by another way + if (rec <= 0 && catrec <= 0 && (cat == 76 || (IsAutoRepeatRangedSpell(spellInfo) && spellInfo->Id != SPELL_ID_AUTOSHOT))) + rec = GetAttackTime(RANGED_ATTACK); + + // Now we have cooldown data (if found any), time to apply mods + if (rec > 0) + ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, rec, spell); + + if (catrec > 0) + ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, catrec, spell); + + // replace negative cooldowns by 0 + if (rec < 0) rec = 0; + if (catrec < 0) catrec = 0; + + // no cooldown after applying spell mods + if (rec == 0 && catrec == 0) + return; + + catrecTime = catrec ? curTime + catrec / IN_MILLISECONDS : 0; + recTime = rec ? curTime + rec / IN_MILLISECONDS : catrecTime; + } + + // self spell cooldown + if (recTime > 0) + AddSpellCooldown(spellInfo->Id, itemId, recTime); + + // category spells + if (cat && catrec > 0) + { + SpellCategoryStore::const_iterator i_scstore = sSpellCategoryStore.find(cat); + if (i_scstore != sSpellCategoryStore.end()) + { + for (SpellCategorySet::const_iterator i_scset = i_scstore->second.begin(); i_scset != i_scstore->second.end(); ++i_scset) + { + if (*i_scset == spellInfo->Id) // skip main spell, already handled above + continue; + + AddSpellCooldown(*i_scset, itemId, catrecTime); + } + } + } +} + +void Player::AddSpellCooldown(uint32 spellid, uint32 itemid, time_t end_time) +{ + SpellCooldown sc; + sc.end = end_time; + sc.itemid = itemid; + m_spellCooldowns[spellid] = sc; +} + +void Player::SendCooldownEvent(SpellEntry const* spellInfo, uint32 itemId, Spell* spell) +{ + // start cooldowns at server side, if any + AddSpellAndCategoryCooldowns(spellInfo, itemId, spell); + + // Send activate cooldown timer (possible 0) at client side + WorldPacket data(SMSG_COOLDOWN_EVENT, (4 + 8)); + data << uint32(spellInfo->Id); + data << GetObjectGuid(); + SendDirectMessage(&data); +} + +void Player::UpdatePotionCooldown(Spell* spell) +{ + // no potion used in combat or still in combat + if (!m_lastPotionId || isInCombat()) + return; + + // Call not from spell cast, send cooldown event for item spells if no in combat + if (!spell) + { + // spell/item pair let set proper cooldown (except nonexistent charged spell cooldown spellmods for potions) + if (ItemPrototype const* proto = ObjectMgr::GetItemPrototype(m_lastPotionId)) + for (int idx = 0; idx < 5; ++idx) + if (proto->Spells[idx].SpellId && proto->Spells[idx].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE) + if (SpellEntry const* spellInfo = sSpellStore.LookupEntry(proto->Spells[idx].SpellId)) + SendCooldownEvent(spellInfo, m_lastPotionId); + } + // from spell cases (m_lastPotionId set in Spell::SendSpellCooldown) + else + SendCooldownEvent(spell->m_spellInfo, m_lastPotionId, spell); + + m_lastPotionId = 0; +} + +// slot to be excluded while counting +bool Player::EnchantmentFitsRequirements(uint32 enchantmentcondition, int8 slot) +{ + if (!enchantmentcondition) + return true; + + SpellItemEnchantmentConditionEntry const* Condition = sSpellItemEnchantmentConditionStore.LookupEntry(enchantmentcondition); + + if (!Condition) + return true; + + uint8 curcount[4] = {0, 0, 0, 0}; + + // counting current equipped gem colors + for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + { + if (i == slot) + continue; + Item* pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem2 && !pItem2->IsBroken() && pItem2->GetProto()->Socket[0].Color) + { + for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + 3; ++enchant_slot) + { + uint32 enchant_id = pItem2->GetEnchantmentId(EnchantmentSlot(enchant_slot)); + if (!enchant_id) + continue; + + SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!enchantEntry) + continue; + + uint32 gemid = enchantEntry->GemID; + if (!gemid) + continue; + + ItemPrototype const* gemProto = sItemStorage.LookupEntry(gemid); + if (!gemProto) + continue; + + GemPropertiesEntry const* gemProperty = sGemPropertiesStore.LookupEntry(gemProto->GemProperties); + if (!gemProperty) + continue; + + uint8 GemColor = gemProperty->color; + + for (uint8 b = 0, tmpcolormask = 1; b < 4; ++b, tmpcolormask <<= 1) + { + if (tmpcolormask & GemColor) + ++curcount[b]; + } + } + } + } + + bool activate = true; + + for (int i = 0; i < 5; ++i) + { + if (!Condition->Color[i]) + continue; + + uint32 _cur_gem = curcount[Condition->Color[i] - 1]; + + // if have use them as count, else use from Condition + uint32 _cmp_gem = Condition->CompareColor[i] ? curcount[Condition->CompareColor[i] - 1] : Condition->Value[i]; + + switch (Condition->Comparator[i]) + { + case 2: // requires less than ( || ) gems + activate &= (_cur_gem < _cmp_gem) ? true : false; + break; + case 3: // requires more than ( || ) gems + activate &= (_cur_gem > _cmp_gem) ? true : false; + break; + case 5: // requires at least than ( || ) gems + activate &= (_cur_gem >= _cmp_gem) ? true : false; + break; + } + } + + DEBUG_LOG("Checking Condition %u, there are %u Meta Gems, %u Red Gems, %u Yellow Gems and %u Blue Gems, Activate:%s", enchantmentcondition, curcount[0], curcount[1], curcount[2], curcount[3], activate ? "yes" : "no"); + + return activate; +} + +void Player::CorrectMetaGemEnchants(uint8 exceptslot, bool apply) +{ + // cycle all equipped items + for (uint32 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + // enchants for the slot being socketed are handled by Player::ApplyItemMods + if (slot == exceptslot) + continue; + + Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + + if (!pItem || !pItem->GetProto()->Socket[0].Color) + continue; + + for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + 3; ++enchant_slot) + { + uint32 enchant_id = pItem->GetEnchantmentId(EnchantmentSlot(enchant_slot)); + if (!enchant_id) + continue; + + SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!enchantEntry) + continue; + + uint32 condition = enchantEntry->EnchantmentCondition; + if (condition) + { + // was enchant active with/without item? + bool wasactive = EnchantmentFitsRequirements(condition, apply ? exceptslot : -1); + // should it now be? + if (wasactive != EnchantmentFitsRequirements(condition, apply ? -1 : exceptslot)) + { + // ignore item gem conditions + // if state changed, (dis)apply enchant + ApplyEnchantment(pItem, EnchantmentSlot(enchant_slot), !wasactive, true, true); + } + } + } + } +} + +// if false -> then toggled off if was on| if true -> toggled on if was off AND meets requirements +void Player::ToggleMetaGemsActive(uint8 exceptslot, bool apply) +{ + // cycle all equipped items + for (int slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + // enchants for the slot being socketed are handled by WorldSession::HandleSocketOpcode(WorldPacket& recv_data) + if (slot == exceptslot) + continue; + + Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + + if (!pItem || !pItem->GetProto()->Socket[0].Color) // if item has no sockets or no item is equipped go to next item + continue; + + // cycle all (gem)enchants + for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + 3; ++enchant_slot) + { + uint32 enchant_id = pItem->GetEnchantmentId(EnchantmentSlot(enchant_slot)); + if (!enchant_id) // if no enchant go to next enchant(slot) + continue; + + SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!enchantEntry) + continue; + + // only metagems to be (de)activated, so only enchants with condition + uint32 condition = enchantEntry->EnchantmentCondition; + if (condition) + ApplyEnchantment(pItem, EnchantmentSlot(enchant_slot), apply); + } + } +} + +void Player::SetBattleGroundEntryPoint() +{ + // Taxi path store + if (!m_taxi.empty()) + { + m_bgData.mountSpell = 0; + m_bgData.taxiPath[0] = m_taxi.GetTaxiSource(); + m_bgData.taxiPath[1] = m_taxi.GetTaxiDestination(); + + // On taxi we don't need check for dungeon + m_bgData.joinPos = WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation()); + m_bgData.m_needSave = true; + return; + } + else + { + m_bgData.ClearTaxiPath(); + + // Mount spell id storing + if (IsMounted()) + { + AuraList const& auras = GetAurasByType(SPELL_AURA_MOUNTED); + if (!auras.empty()) + m_bgData.mountSpell = (*auras.begin())->GetId(); + } + else + m_bgData.mountSpell = 0; + + // If map is dungeon find linked graveyard + if (GetMap()->IsDungeon()) + { + if (const WorldSafeLocsEntry* entry = sObjectMgr.GetClosestGraveYard(GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId(), GetTeam())) + { + m_bgData.joinPos = WorldLocation(entry->map_id, entry->x, entry->y, entry->z, 0.0f); + m_bgData.m_needSave = true; + return; + } + else + sLog.outError("SetBattleGroundEntryPoint: Dungeon map %u has no linked graveyard, setting home location as entry point.", GetMapId()); + } + // If new entry point is not BG or arena set it + else if (!GetMap()->IsBattleGroundOrArena()) + { + m_bgData.joinPos = WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation()); + m_bgData.m_needSave = true; + return; + } + } + + // In error cases use homebind position + m_bgData.joinPos = WorldLocation(m_homebindMapId, m_homebindX, m_homebindY, m_homebindZ, 0.0f); + m_bgData.m_needSave = true; +} + +void Player::LeaveBattleground(bool teleportToEntryPoint) +{ + if (BattleGround* bg = GetBattleGround()) + { + bg->RemovePlayerAtLeave(GetObjectGuid(), teleportToEntryPoint, true); + + // call after remove to be sure that player resurrected for correct cast + if (bg->isBattleGround() && !isGameMaster() && sWorld.getConfig(CONFIG_BOOL_BATTLEGROUND_CAST_DESERTER)) + { + if (bg->GetStatus() == STATUS_IN_PROGRESS || bg->GetStatus() == STATUS_WAIT_JOIN) + { + // lets check if player was teleported from BG and schedule delayed Deserter spell cast + if (IsBeingTeleportedFar()) + { + ScheduleDelayedOperation(DELAYED_SPELL_CAST_DESERTER); + return; + } + + CastSpell(this, 26013, true); // Deserter + } + } + } +} + +bool Player::CanJoinToBattleground() const +{ + // check Deserter debuff + if (GetDummyAura(26013)) + return false; + + return true; +} + +bool Player::CanReportAfkDueToLimit() +{ + // a player can complain about 15 people per 5 minutes + if (m_bgData.bgAfkReportedCount++ >= 15) + return false; + + return true; +} + +/// This player has been blamed to be inactive in a battleground +void Player::ReportedAfkBy(Player* reporter) +{ + BattleGround* bg = GetBattleGround(); + if (!bg || bg != reporter->GetBattleGround() || GetTeam() != reporter->GetTeam()) + return; + + // check if player has 'Idle' or 'Inactive' debuff + if (m_bgData.bgAfkReporter.find(reporter->GetGUIDLow()) == m_bgData.bgAfkReporter.end() && !HasAura(43680, EFFECT_INDEX_0) && !HasAura(43681, EFFECT_INDEX_0) && reporter->CanReportAfkDueToLimit()) + { + m_bgData.bgAfkReporter.insert(reporter->GetGUIDLow()); + // 3 players have to complain to apply debuff + if (m_bgData.bgAfkReporter.size() >= 3) + { + // cast 'Idle' spell + CastSpell(this, 43680, true); + m_bgData.bgAfkReporter.clear(); + } + } +} + +bool Player::IsVisibleInGridForPlayer(Player* pl) const +{ + // gamemaster in GM mode see all, including ghosts + if (pl->isGameMaster() && GetSession()->GetSecurity() <= pl->GetSession()->GetSecurity()) + return true; + + // player see dead player/ghost from own group/raid + if (IsInSameRaidWith(pl)) + return true; + + // Live player see live player or dead player with not realized corpse + if (pl->isAlive() || pl->m_deathTimer > 0) + return isAlive() || m_deathTimer > 0; + + // Ghost see other friendly ghosts, that's for sure + if (!(isAlive() || m_deathTimer > 0) && IsFriendlyTo(pl)) + return true; + + // Dead player see live players near own corpse + if (isAlive()) + { + if (Corpse* corpse = pl->GetCorpse()) + { + // 20 - aggro distance for same level, 25 - max additional distance if player level less that creature level + if (corpse->IsWithinDistInMap(this, (20 + 25) * sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_AGGRO))) + return true; + } + } + + // and not see any other + return false; +} + +bool Player::IsVisibleGloballyFor(Player* u) const +{ + if (!u) + return false; + + // Always can see self + if (u == this) + return true; + + // Visible units, always are visible for all players + if (GetVisibility() == VISIBILITY_ON) + return true; + + // GMs are visible for higher gms (or players are visible for gms) + if (u->GetSession()->GetSecurity() > SEC_PLAYER) + return GetSession()->GetSecurity() <= u->GetSession()->GetSecurity(); + + // non faction visibility non-breakable for non-GMs + if (GetVisibility() == VISIBILITY_OFF) + return false; + + // non-gm stealth/invisibility not hide from global player lists + return true; +} + +template +inline void BeforeVisibilityDestroy(T* /*t*/, Player* /*p*/) +{ +} + +template<> +inline void BeforeVisibilityDestroy(Creature* t, Player* p) +{ + if (p->GetPetGuid() == t->GetObjectGuid() && ((Creature*)t)->IsPet()) + ((Pet*)t)->Unsummon(PET_SAVE_REAGENTS); +} + +void Player::UpdateVisibilityOf(WorldObject const* viewPoint, WorldObject* target) +{ + if (HaveAtClient(target)) + { + if (!target->isVisibleForInState(this, viewPoint, true)) + { + ObjectGuid t_guid = target->GetObjectGuid(); + + if (target->GetTypeId() == TYPEID_UNIT) + { + BeforeVisibilityDestroy((Creature*)target, this); + + // at remove from map (destroy) show kill animation (in different out of range/stealth case) + target->DestroyForPlayer(this, !target->IsInWorld() && ((Creature*)target)->isDead()); + } + else + target->DestroyForPlayer(this); + + m_clientGUIDs.erase(t_guid); + + DEBUG_FILTER_LOG(LOG_FILTER_VISIBILITY_CHANGES, "UpdateVisibilityOf: %s out of range for player %u. Distance = %f", t_guid.GetString().c_str(), GetGUIDLow(), GetDistance(target)); + } + } + else + { + if (target->isVisibleForInState(this, viewPoint, false)) + { + target->SendCreateUpdateToPlayer(this); + if (target->GetTypeId() != TYPEID_GAMEOBJECT || !((GameObject*)target)->IsTransport()) + m_clientGUIDs.insert(target->GetObjectGuid()); + + DEBUG_FILTER_LOG(LOG_FILTER_VISIBILITY_CHANGES, "UpdateVisibilityOf: %s is visible now for player %u. Distance = %f", target->GetGuidStr().c_str(), GetGUIDLow(), GetDistance(target)); + + // target aura duration for caster show only if target exist at caster client + // send data at target visibility change (adding to client) + if (target != this && target->isType(TYPEMASK_UNIT)) + SendAurasForTarget((Unit*)target); + } + } +} + +template +inline void UpdateVisibilityOf_helper(GuidSet& s64, T* target) +{ + s64.insert(target->GetObjectGuid()); +} + +template<> +inline void UpdateVisibilityOf_helper(GuidSet& s64, GameObject* target) +{ + if (!target->IsTransport()) + s64.insert(target->GetObjectGuid()); +} + +template +void Player::UpdateVisibilityOf(WorldObject const* viewPoint, T* target, UpdateData& data, std::set& visibleNow) +{ + if (HaveAtClient(target)) + { + if (!target->isVisibleForInState(this, viewPoint, true)) + { + BeforeVisibilityDestroy(target, this); + + ObjectGuid t_guid = target->GetObjectGuid(); + + target->BuildOutOfRangeUpdateBlock(&data); + m_clientGUIDs.erase(t_guid); + + DEBUG_FILTER_LOG(LOG_FILTER_VISIBILITY_CHANGES, "UpdateVisibilityOf(TemplateV): %s is out of range for %s. Distance = %f", t_guid.GetString().c_str(), GetGuidStr().c_str(), GetDistance(target)); + } + } + else + { + if (target->isVisibleForInState(this, viewPoint, false)) + { + visibleNow.insert(target); + target->BuildCreateUpdateBlockForPlayer(&data, this); + UpdateVisibilityOf_helper(m_clientGUIDs, target); + + DEBUG_FILTER_LOG(LOG_FILTER_VISIBILITY_CHANGES, "UpdateVisibilityOf(TemplateV): %s is visible now for %s. Distance = %f", target->GetGuidStr().c_str(), GetGuidStr().c_str(), GetDistance(target)); + } + } +} + +template void Player::UpdateVisibilityOf(WorldObject const* viewPoint, Player* target, UpdateData& data, std::set& visibleNow); +template void Player::UpdateVisibilityOf(WorldObject const* viewPoint, Creature* target, UpdateData& data, std::set& visibleNow); +template void Player::UpdateVisibilityOf(WorldObject const* viewPoint, Corpse* target, UpdateData& data, std::set& visibleNow); +template void Player::UpdateVisibilityOf(WorldObject const* viewPoint, GameObject* target, UpdateData& data, std::set& visibleNow); +template void Player::UpdateVisibilityOf(WorldObject const* viewPoint, DynamicObject* target, UpdateData& data, std::set& visibleNow); + +void Player::InitPrimaryProfessions() +{ + uint32 maxProfs = GetSession()->GetSecurity() < AccountTypes(sWorld.getConfig(CONFIG_UINT32_TRADE_SKILL_GMIGNORE_MAX_PRIMARY_COUNT)) + ? sWorld.getConfig(CONFIG_UINT32_MAX_PRIMARY_TRADE_SKILL) : 10; + SetFreePrimaryProfessions(maxProfs); +} + +void Player::SendComboPoints() +{ + Unit* combotarget = ObjectAccessor::GetUnit(*this, m_comboTargetGuid); + if (combotarget) + { + WorldPacket data(SMSG_UPDATE_COMBO_POINTS, combotarget->GetPackGUID().size() + 1); + data << combotarget->GetPackGUID(); + data << uint8(m_comboPoints); + GetSession()->SendPacket(&data); + } + /*else + { + // can be NULL, and then points=0. Use unknown; to reset points of some sort? + data << PackedGuid(); + data << uint8(0); + GetSession()->SendPacket(&data); + }*/ +} + +void Player::AddComboPoints(Unit* target, int8 count) +{ + if (!count) + return; + + // without combo points lost (duration checked in aura) + RemoveSpellsCausingAura(SPELL_AURA_RETAIN_COMBO_POINTS); + + if (target->GetObjectGuid() == m_comboTargetGuid) + { + m_comboPoints += count; + } + else + { + if (m_comboTargetGuid) + if (Unit* target2 = ObjectAccessor::GetUnit(*this, m_comboTargetGuid)) + target2->RemoveComboPointHolder(GetGUIDLow()); + + m_comboTargetGuid = target->GetObjectGuid(); + m_comboPoints = count; + + target->AddComboPointHolder(GetGUIDLow()); + } + + if (m_comboPoints > 5) m_comboPoints = 5; + if (m_comboPoints < 0) m_comboPoints = 0; + + SendComboPoints(); +} + +void Player::ClearComboPoints() +{ + if (!m_comboTargetGuid) + return; + + // without combopoints lost (duration checked in aura) + RemoveSpellsCausingAura(SPELL_AURA_RETAIN_COMBO_POINTS); + + m_comboPoints = 0; + + SendComboPoints(); + + if (Unit* target = ObjectAccessor::GetUnit(*this, m_comboTargetGuid)) + target->RemoveComboPointHolder(GetGUIDLow()); + + m_comboTargetGuid.Clear(); +} + +void Player::SetGroup(Group* group, int8 subgroup) +{ + if (group == NULL) + m_group.unlink(); + else + { + // never use SetGroup without a subgroup unless you specify NULL for group + MANGOS_ASSERT(subgroup >= 0); + m_group.link(group, this); + m_group.setSubGroup((uint8)subgroup); + } +} + +void Player::SendInitialPacketsBeforeAddToMap() +{ + GetSocial()->SendSocialList(); + + // Homebind + WorldPacket data(SMSG_BINDPOINTUPDATE, 5 * 4); + data << m_homebindX << m_homebindY << m_homebindZ; + data << (uint32) m_homebindMapId; + data << (uint32) m_homebindAreaId; + GetSession()->SendPacket(&data); + + // SMSG_SET_PROFICIENCY + // SMSG_SET_PCT_SPELL_MODIFIER + // SMSG_SET_FLAT_SPELL_MODIFIER + + SendTalentsInfoData(false); + + data.Initialize(SMSG_WORLD_SERVER_INFO, 1 + 1 + 4 + 4); + data.WriteBit(0); // HasRestrictedLevel + data.WriteBit(0); // HasRestrictedMoney + data.WriteBit(0); // IneligibleForLoot + + //if (IneligibleForLoot) + // data << uint32(0); // EncounterMask + + data << uint8(0); // IsOnTournamentRealm + + //if (HasRestrictedMoney) + // data << uint32(100000); // RestrictedMoney (starter accounts) + //if (HasRestrictedLevel) + // data << uint32(20); // RestrictedLevel (starter accounts) + + data << uint32(sWorld.GetNextWeeklyQuestsResetTime() - WEEK); // LastWeeklyReset (not instance reset) + data << uint32(GetMap()->GetDifficulty()); + GetSession()->SendPacket(&data); + + SendInitialSpells(); + + data.Initialize(SMSG_SEND_UNLEARN_SPELLS, 4); + data << uint32(0); // count, for(count) uint32; + GetSession()->SendPacket(&data); + + SendInitialActionButtons(); + m_reputationMgr.SendInitialReputations(); + + if (!isAlive()) + SendCorpseReclaimDelay(true); + + SendInitWorldStates(GetZoneId(), GetAreaId()); + + SendEquipmentSetList(); + + m_achievementMgr.SendAllAchievementData(); + + data.Initialize(SMSG_LOGIN_SETTIMESPEED, 4 + 4 + 4); + data << uint32(secsToTimeBitFields(sWorld.GetGameTime())); + data << (float)0.01666667f; // game speed + data << uint32(0); // added in 3.1.2 + GetSession()->SendPacket(&data); + + // SMSG_TALENTS_INFO x 2 for pet (unspent points and talents in separate packets...) + // SMSG_PET_GUIDS + // SMSG_POWER_UPDATE + + // set fly flag if in fly form or taxi flight to prevent visually drop at ground in showup moment + if (IsFreeFlying() || IsTaxiFlying()) + m_movementInfo.AddMovementFlag(MOVEFLAG_FLYING); + + SendCurrencies(); + + SetMover(this); +} + +void Player::SendInitialPacketsAfterAddToMap() +{ + // update zone + uint32 newzone, newarea; + GetZoneAndAreaId(newzone, newarea); + UpdateZone(newzone, newarea); // also call SendInitWorldStates(); + + ResetTimeSync(); + SendTimeSync(); + + CastSpell(this, 836, true); // LOGINEFFECT + + // set some aura effects that send packet to player client after add player to map + // SendMessageToSet not send it to player not it map, only for aura that not changed anything at re-apply + // same auras state lost at far teleport, send it one more time in this case also + static const AuraType auratypes[] = + { + SPELL_AURA_MOD_FEAR, SPELL_AURA_TRANSFORM, SPELL_AURA_WATER_WALK, + SPELL_AURA_FEATHER_FALL, SPELL_AURA_HOVER, SPELL_AURA_SAFE_FALL, + SPELL_AURA_FLY, SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED, SPELL_AURA_NONE + }; + for (AuraType const* itr = &auratypes[0]; itr && itr[0] != SPELL_AURA_NONE; ++itr) + { + Unit::AuraList const& auraList = GetAurasByType(*itr); + if (!auraList.empty()) + auraList.front()->ApplyModifier(true, true); + } + + if (HasAuraType(SPELL_AURA_MOD_STUN) || HasAuraType(SPELL_AURA_MOD_ROOT)) + SetRoot(true); + + SendAurasForTarget(this); + SendEnchantmentDurations(); // must be after add to map + SendItemDurations(); // must be after add to map + + UpdateSpeed(MOVE_RUN, true, 1.0f, true); + UpdateSpeed(MOVE_SWIM, true, 1.0f, true); + UpdateSpeed(MOVE_FLIGHT, true, 1.0f, true); +} + +void Player::SendUpdateToOutOfRangeGroupMembers() +{ + if (m_groupUpdateMask == GROUP_UPDATE_FLAG_NONE) + return; + if (Group* group = GetGroup()) + group->UpdatePlayerOutOfRange(this); + + m_groupUpdateMask = GROUP_UPDATE_FLAG_NONE; + m_auraUpdateMask = 0; + if (Pet* pet = GetPet()) + pet->ResetAuraUpdateMask(); +} + +void Player::SendTransferAbortedByLockStatus(MapEntry const* mapEntry, AreaLockStatus lockStatus, uint32 miscRequirement) +{ + MANGOS_ASSERT(mapEntry); + + DEBUG_LOG("SendTransferAbortedByLockStatus: Called for %s on map %u, LockAreaStatus %u, miscRequirement %u)", GetGuidStr().c_str(), mapEntry->MapID, lockStatus, miscRequirement); + + switch (lockStatus) + { + case AREA_LOCKSTATUS_TOO_LOW_LEVEL: + GetSession()->SendAreaTriggerMessage(GetSession()->GetMangosString(LANG_LEVEL_MINREQUIRED), miscRequirement); + break; + case AREA_LOCKSTATUS_ZONE_IN_COMBAT: + GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_ZONE_IN_COMBAT); + break; + case AREA_LOCKSTATUS_INSTANCE_IS_FULL: + GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_MAX_PLAYERS); + break; + case AREA_LOCKSTATUS_QUEST_NOT_COMPLETED: + if (mapEntry->MapID == 269) // Exception for Black Morass + { + GetSession()->SendAreaTriggerMessage(GetSession()->GetMangosString(LANG_TELEREQ_QUEST_BLACK_MORASS)); + break; + } + else if (mapEntry->IsContinent()) // do not report anything for quest areatrigge + { + DEBUG_LOG("SendTransferAbortedByLockStatus: LockAreaStatus %u, do not teleport, no message sent (mapId %u)", lockStatus, mapEntry->MapID); + break; + } + // No break here! + case AREA_LOCKSTATUS_MISSING_ITEM: + GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_DIFFICULTY, GetDifficulty(mapEntry->IsRaid())); + break; + case AREA_LOCKSTATUS_MISSING_DIFFICULTY: + { + Difficulty difficulty = GetDifficulty(mapEntry->IsRaid()); + GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_DIFFICULTY, difficulty > RAID_DIFFICULTY_10MAN_HEROIC ? RAID_DIFFICULTY_10MAN_HEROIC : difficulty); + break; + } + case AREA_LOCKSTATUS_INSUFFICIENT_EXPANSION: + GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_INSUF_EXPAN_LVL, miscRequirement); + break; + case AREA_LOCKSTATUS_NOT_ALLOWED: + GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_MAP_NOT_ALLOWED); + break; + case AREA_LOCKSTATUS_RAID_LOCKED: + GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_NEED_GROUP); + break; + case AREA_LOCKSTATUS_UNKNOWN_ERROR: + GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_ERROR); + break; + case AREA_LOCKSTATUS_OK: + sLog.outError("SendTransferAbortedByLockStatus: LockAreaStatus AREA_LOCKSTATUS_OK received for %s (mapId %u)", GetGuidStr().c_str(), mapEntry->MapID); + MANGOS_ASSERT(false); + break; + default: + sLog.outError("SendTransfertAbortedByLockstatus: unhandled LockAreaStatus %u, when %s attempts to enter in map %u", lockStatus, GetGuidStr().c_str(), mapEntry->MapID); + break; + } +} + +void Player::SendInstanceResetWarning(uint32 mapid, Difficulty difficulty, uint32 time) +{ + // type of warning, based on the time remaining until reset + uint32 type; + if (time > 3600) + type = RAID_INSTANCE_WELCOME; + else if (time > 900 && time <= 3600) + type = RAID_INSTANCE_WARNING_HOURS; + else if (time > 300 && time <= 900) + type = RAID_INSTANCE_WARNING_MIN; + else + type = RAID_INSTANCE_WARNING_MIN_SOON; + + WorldPacket data(SMSG_RAID_INSTANCE_MESSAGE, 4 + 4 + 4 + 4); + data << uint32(type); + data << uint32(mapid); + data << uint32(difficulty); // difficulty + data << uint32(time); + if (type == RAID_INSTANCE_WELCOME) + { + data << uint8(0); // is your (1) + data << uint8(0); // is extended (1), ignored if prev field is 0 + } + GetSession()->SendPacket(&data); +} + +void Player::ApplyEquipCooldown(Item* pItem) +{ + if (pItem->GetProto()->Flags & ITEM_FLAG_NO_EQUIP_COOLDOWN) + return; + + for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + _Spell const& spellData = pItem->GetProto()->Spells[i]; + + // no spell + if (!spellData.SpellId) + continue; + + // wrong triggering type (note: ITEM_SPELLTRIGGER_ON_NO_DELAY_USE not have cooldown) + if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_USE) + continue; + + AddSpellCooldown(spellData.SpellId, pItem->GetEntry(), time(NULL) + 30); + + WorldPacket data(SMSG_ITEM_COOLDOWN, 12); + data << pItem->GetObjectGuid(); + data << uint32(spellData.SpellId); + GetSession()->SendPacket(&data); + } +} + +void Player::resetSpells() +{ + // not need after this call + if (HasAtLoginFlag(AT_LOGIN_RESET_SPELLS)) + RemoveAtLoginFlag(AT_LOGIN_RESET_SPELLS, true); + + // make full copy of map (spells removed and marked as deleted at another spell remove + // and we can't use original map for safe iterative with visit each spell at loop end + PlayerSpellMap smap = GetSpellMap(); + + for (PlayerSpellMap::const_iterator iter = smap.begin(); iter != smap.end(); ++iter) + removeSpell(iter->first, false, false); // only iter->first can be accessed, object by iter->second can be deleted already + + learnDefaultSpells(); + learnQuestRewardedSpells(); +} + +void Player::learnDefaultSpells() +{ + // learn default race/class spells + PlayerInfo const* info = sObjectMgr.GetPlayerInfo(getRace(), getClass()); + for (PlayerCreateInfoSpells::const_iterator itr = info->spell.begin(); itr != info->spell.end(); ++itr) + { + uint32 tspell = *itr; + DEBUG_LOG("PLAYER (Class: %u Race: %u): Adding initial spell, id = %u", uint32(getClass()), uint32(getRace()), tspell); + if (!IsInWorld()) // will send in INITIAL_SPELLS in list anyway at map add + addSpell(tspell, true, true, true, false); + else // but send in normal spell in game learn case + learnSpell(tspell, true); + } +} + +void Player::learnQuestRewardedSpells(Quest const* quest) +{ + uint32 spell_id = quest->GetRewSpellCast(); + + // skip quests without rewarded spell + if (!spell_id) + return; + + SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id); + if (!spellInfo) + return; + + // check learned spells state + bool found = false; + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + { + if(SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(i))) + { + if(spellEffect->Effect == SPELL_EFFECT_LEARN_SPELL && !HasSpell(spellEffect->EffectTriggerSpell)) + { + found = true; + break; + } + } + } + + // skip quests with not teaching spell or already known spell + if (!found) + return; + + // prevent learn non first rank unknown profession and second specialization for same profession) + SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(EFFECT_INDEX_0); + uint32 learned_0 = spellEffect ? spellEffect->EffectTriggerSpell : 0; + + if( sSpellMgr.GetSpellRank(learned_0) > 1 && !HasSpell(learned_0) ) + { + // not have first rank learned (unlearned prof?) + uint32 first_spell = sSpellMgr.GetFirstSpellInChain(learned_0); + if (!HasSpell(first_spell)) + return; + + SpellEntry const* learnedInfo = sSpellStore.LookupEntry(learned_0); + if (!learnedInfo) + return; + + // specialization + SpellEffectEntry const* learnedSpellEffect0 = learnedInfo->GetSpellEffect(EFFECT_INDEX_0); + SpellEffectEntry const* learnedSpellEffect1 = learnedInfo->GetSpellEffect(EFFECT_INDEX_1); + if (learnedSpellEffect0 && learnedSpellEffect0->Effect == SPELL_EFFECT_TRADE_SKILL && learnedSpellEffect1 && learnedSpellEffect1->Effect == 0) + { + // search other specialization for same prof + for (PlayerSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr) + { + if (itr->second.state == PLAYERSPELL_REMOVED || itr->first == learned_0) + continue; + + SpellEntry const* itrInfo = sSpellStore.LookupEntry(itr->first); + if (!itrInfo) + return; + + // compare only specializations + SpellEffectEntry const* itrSpellEffect0 = learnedInfo->GetSpellEffect(EFFECT_INDEX_0); + SpellEffectEntry const* itrSpellEffect1 = learnedInfo->GetSpellEffect(EFFECT_INDEX_1); + if ((itrSpellEffect0 && itrSpellEffect0->Effect != SPELL_EFFECT_TRADE_SKILL) || (itrSpellEffect1 && itrSpellEffect1->Effect != 0)) + continue; + + // compare same chain spells + if (sSpellMgr.GetFirstSpellInChain(itr->first) != first_spell) + continue; + + // now we have 2 specialization, learn possible only if found is lesser specialization rank + if (!sSpellMgr.IsHighRankOfSpell(learned_0, itr->first)) + return; + } + } + } + + CastSpell(this, spell_id, true); +} + +void Player::learnQuestRewardedSpells() +{ + // learn spells received from quest completing + for (QuestStatusMap::const_iterator itr = mQuestStatus.begin(); itr != mQuestStatus.end(); ++itr) + { + // skip no rewarded quests + if (!itr->second.m_rewarded) + continue; + + Quest const* quest = sObjectMgr.GetQuestTemplate(itr->first); + if (!quest) + continue; + + learnQuestRewardedSpells(quest); + } +} + +void Player::learnSkillRewardedSpells(uint32 skill_id, uint32 skill_value) +{ + uint32 raceMask = getRaceMask(); + uint32 classMask = getClassMask(); + for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j) + { + SkillLineAbilityEntry const* pAbility = sSkillLineAbilityStore.LookupEntry(j); + if (!pAbility || pAbility->skillId != skill_id || pAbility->learnOnGetSkill != ABILITY_LEARNED_ON_GET_PROFESSION_SKILL) + continue; + // Check race if set + if (pAbility->racemask && !(pAbility->racemask & raceMask)) + continue; + // Check class if set + if (pAbility->classmask && !(pAbility->classmask & classMask)) + continue; + + if (sSpellStore.LookupEntry(pAbility->spellId)) + { + // need unlearn spell + if (skill_value < pAbility->req_skill_value) + removeSpell(pAbility->spellId); + // need learn + else if (!IsInWorld()) + addSpell(pAbility->spellId, true, true, true, false); + else + learnSpell(pAbility->spellId, true); + } + } +} + +void Player::SendAurasForTarget(Unit* target) +{ + Unit::VisibleAuraMap const& visibleAuras = target->GetVisibleAuras(); + if (visibleAuras.empty()) + return; + + WorldPacket data(SMSG_AURA_UPDATE_ALL); + data << target->GetPackGUID(); + + for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras.begin(); itr != visibleAuras.end(); ++itr) + itr->second->BuildUpdatePacket(data); + + GetSession()->SendPacket(&data); +} + +void Player::SetDailyQuestStatus(uint32 quest_id) +{ + for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx) + { + if (!GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx)) + { + SetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx, quest_id); + m_DailyQuestChanged = true; + break; + } + } +} + +void Player::SetWeeklyQuestStatus(uint32 quest_id) +{ + m_weeklyquests.insert(quest_id); + m_WeeklyQuestChanged = true; +} + +void Player::SetMonthlyQuestStatus(uint32 quest_id) +{ + m_monthlyquests.insert(quest_id); + m_MonthlyQuestChanged = true; +} + +void Player::ResetDailyQuestStatus() +{ + for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx) + SetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx, 0); + + // DB data deleted in caller + m_DailyQuestChanged = false; +} + +void Player::ResetWeeklyQuestStatus() +{ + if (m_weeklyquests.empty()) + return; + + m_weeklyquests.clear(); + // DB data deleted in caller + m_WeeklyQuestChanged = false; +} + +void Player::ResetMonthlyQuestStatus() +{ + if (m_monthlyquests.empty()) + return; + + m_monthlyquests.clear(); + // DB data deleted in caller + m_MonthlyQuestChanged = false; +} + +BattleGround* Player::GetBattleGround() const +{ + if (GetBattleGroundId() == 0) + return NULL; + + return sBattleGroundMgr.GetBattleGround(GetBattleGroundId(), m_bgData.bgTypeID); +} + +bool Player::InArena() const +{ + BattleGround* bg = GetBattleGround(); + if (!bg || !bg->isArena()) + return false; + + return true; +} + +bool Player::GetBGAccessByLevel(BattleGroundTypeId bgTypeId) const +{ + // get a template bg instead of running one + BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); + if (!bg) + return false; + + // limit check leel to dbc compatible level range + uint32 level = getLevel(); + if (level > DEFAULT_MAX_LEVEL) + level = DEFAULT_MAX_LEVEL; + + if (level < bg->GetMinLevel() || level > bg->GetMaxLevel()) + return false; + + return true; +} + +float Player::GetReputationPriceDiscount(Creature const* pCreature) const +{ + FactionTemplateEntry const* vendor_faction = pCreature->getFactionTemplateEntry(); + if (!vendor_faction || !vendor_faction->faction) + return 1.0f; + + ReputationRank rank = GetReputationRank(vendor_faction->faction); + if (rank <= REP_NEUTRAL) + return 1.0f; + + return 1.0f - 0.05f * (rank - REP_NEUTRAL); +} + +/* + * Check spell availability for training base at SkillLineAbility/SkillRaceClassInfo data. + * Checked allowed race/class and dependent from race/class allowed min level + * + * @param spell_id checked spell id + * @param pReqlevel if arg provided then function work in view mode (level check not applied but detected minlevel returned to var by arg pointer. + if arg not provided then considered train action mode and level checked + * @return true if spell available for show in trainer list (with skip level check) or training. + */ +bool Player::IsSpellFitByClassAndRace(uint32 spell_id, uint32* pReqlevel /*= NULL*/) const +{ + uint32 racemask = getRaceMask(); + uint32 classmask = getClassMask(); + + SkillLineAbilityMapBounds bounds = sSpellMgr.GetSkillLineAbilityMapBounds(spell_id); + if (bounds.first == bounds.second) + return true; + + for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx) + { + SkillLineAbilityEntry const* abilityEntry = _spell_idx->second; + // skip wrong race skills + if (abilityEntry->racemask && (abilityEntry->racemask & racemask) == 0) + continue; + + // skip wrong class skills + if (abilityEntry->classmask && (abilityEntry->classmask & classmask) == 0) + continue; + + SkillRaceClassInfoMapBounds bounds = sSpellMgr.GetSkillRaceClassInfoMapBounds(abilityEntry->skillId); + for (SkillRaceClassInfoMap::const_iterator itr = bounds.first; itr != bounds.second; ++itr) + { + SkillRaceClassInfoEntry const* skillRCEntry = itr->second; + if ((skillRCEntry->raceMask & racemask) && (skillRCEntry->classMask & classmask)) + { + if (skillRCEntry->flags & ABILITY_SKILL_NONTRAINABLE) + return false; + + if (pReqlevel) // show trainers list case + { + if (skillRCEntry->reqLevel) + { + *pReqlevel = skillRCEntry->reqLevel; + return true; + } + } + else // check availble case at train + { + if (skillRCEntry->reqLevel && getLevel() < skillRCEntry->reqLevel) + return false; + } + } + } + + return true; + } + + return false; +} + +bool Player::HasQuestForGO(int32 GOId) const +{ + for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i) + { + uint32 questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + QuestStatusMap::const_iterator qs_itr = mQuestStatus.find(questid); + if (qs_itr == mQuestStatus.end()) + continue; + + QuestStatusData const& qs = qs_itr->second; + + if (qs.m_status == QUEST_STATUS_INCOMPLETE) + { + Quest const* qinfo = sObjectMgr.GetQuestTemplate(questid); + if (!qinfo) + continue; + + if (GetGroup() && GetGroup()->isRaidGroup() && !qinfo->IsAllowedInRaid()) + continue; + + for (int j = 0; j < QUEST_OBJECTIVES_COUNT; ++j) + { + if (qinfo->ReqCreatureOrGOId[j] >= 0) // skip non GO case + continue; + + if ((-1)*GOId == qinfo->ReqCreatureOrGOId[j] && qs.m_creatureOrGOcount[j] < qinfo->ReqCreatureOrGOCount[j]) + return true; + } + } + } + return false; +} + +void Player::UpdateForQuestWorldObjects() +{ + if (m_clientGUIDs.empty()) + return; + + UpdateData udata(GetMapId()); + WorldPacket packet; + for (GuidSet::const_iterator itr = m_clientGUIDs.begin(); itr != m_clientGUIDs.end(); ++itr) + { + if (itr->IsGameObject()) + { + if (GameObject* obj = GetMap()->GetGameObject(*itr)) + obj->BuildValuesUpdateBlockForPlayer(&udata, this); + } + else if (itr->IsCreatureOrVehicle()) + { + Creature* obj = GetMap()->GetAnyTypeCreature(*itr); + if (!obj) + continue; + + // check if this unit requires quest specific flags + if (!obj->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK)) + continue; + + SpellClickInfoMapBounds clickPair = sObjectMgr.GetSpellClickInfoMapBounds(obj->GetEntry()); + for (SpellClickInfoMap::const_iterator _itr = clickPair.first; _itr != clickPair.second; ++_itr) + { + if (_itr->second.questStart || _itr->second.questEnd) + { + obj->BuildCreateUpdateBlockForPlayer(&udata, this); + break; + } + } + } + } + udata.BuildPacket(&packet); + GetSession()->SendPacket(&packet); +} + +void Player::SummonIfPossible(bool agree) +{ + if (!agree) + { + m_summon_expire = 0; + return; + } + + // expire and auto declined + if (m_summon_expire < time(NULL)) + return; + + // stop taxi flight at summon + if (IsTaxiFlying()) + { + GetMotionMaster()->MovementExpired(); + m_taxi.ClearTaxiDestinations(); + } + + // drop flag at summon + // this code can be reached only when GM is summoning player who carries flag, because player should be immune to summoning spells when he carries flag + if (BattleGround* bg = GetBattleGround()) + bg->EventPlayerDroppedFlag(this); + + m_summon_expire = 0; + + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ACCEPTED_SUMMONINGS, 1); + + TeleportTo(m_summon_mapid, m_summon_x, m_summon_y, m_summon_z, GetOrientation()); +} + +void Player::RemoveItemDurations(Item* item) +{ + for (ItemDurationList::iterator itr = m_itemDuration.begin(); itr != m_itemDuration.end(); ++itr) + { + if (*itr == item) + { + m_itemDuration.erase(itr); + break; + } + } +} + +void Player::AddItemDurations(Item* item) +{ + if (item->GetUInt32Value(ITEM_FIELD_DURATION)) + { + m_itemDuration.push_back(item); + item->SendTimeUpdate(this); + } +} + +void Player::AutoUnequipOffhandIfNeed() +{ + Item* offItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + if (!offItem) + return; + + // need unequip offhand for 2h-weapon without TitanGrip (in any from hands) + if ((CanDualWield() || offItem->GetProto()->InventoryType == INVTYPE_SHIELD || offItem->GetProto()->InventoryType == INVTYPE_HOLDABLE) && + (CanTitanGrip() || (offItem->GetProto()->InventoryType != INVTYPE_2HWEAPON && !IsTwoHandUsed()))) + return; + + ItemPosCountVec off_dest; + uint8 off_msg = CanStoreItem(NULL_BAG, NULL_SLOT, off_dest, offItem, false); + if (off_msg == EQUIP_ERR_OK) + { + RemoveItem(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND, true); + StoreItem(off_dest, offItem, true); + } + else + { + MoveItemFromInventory(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND, true); + CharacterDatabase.BeginTransaction(); + offItem->DeleteFromInventoryDB(); // deletes item from character's inventory + offItem->SaveToDB(); // recursive and not have transaction guard into self, item not in inventory and can be save standalone + CharacterDatabase.CommitTransaction(); + + std::string subject = GetSession()->GetMangosString(LANG_NOT_EQUIPPED_ITEM); + MailDraft(subject, "There's were problems with equipping this item.").AddItem(offItem).SendMailTo(this, MailSender(this, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_COPIED); + } +} + +bool Player::HasItemFitToSpellReqirements(SpellEntry const* spellInfo, Item const* ignoreItem) +{ + int32 itemClass = spellInfo->GetEquippedItemClass(); + if(itemClass < 0) + return true; + + // scan other equipped items for same requirements (mostly 2 daggers/etc) + // for optimize check 2 used cases only + switch(itemClass) + { + case ITEM_CLASS_WEAPON: + { + for (int i = EQUIPMENT_SLOT_MAINHAND; i < EQUIPMENT_SLOT_TABARD; ++i) + if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo)) + return true; + break; + } + case ITEM_CLASS_ARMOR: + { + // tabard not have dependent spells + for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_MAINHAND; ++i) + if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo)) + return true; + + // shields can be equipped to offhand slot + if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND)) + if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo)) + return true; + + // ranged slot can have some armor subclasses + if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED)) + if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo)) + return true; + + break; + } + default: + sLog.outError("HasItemFitToSpellReqirements: Not handled spell requirement for item class %u", itemClass); + break; + } + + return false; +} + +bool Player::CanNoReagentCast(SpellEntry const* spellInfo) const +{ + // don't take reagents for spells with SPELL_ATTR_EX5_NO_REAGENT_WHILE_PREP + if (spellInfo->HasAttribute(SPELL_ATTR_EX5_NO_REAGENT_WHILE_PREP) && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PREPARATION)) + return true; + + // Check no reagent use mask + uint64 noReagentMask_0_1 = GetUInt64Value(PLAYER_NO_REAGENT_COST_1); + uint32 noReagentMask_2 = GetUInt32Value(PLAYER_NO_REAGENT_COST_1 + 2); + if (spellInfo->IsFitToFamilyMask(noReagentMask_0_1, noReagentMask_2)) + return true; + + return false; +} + +void Player::RemoveItemDependentAurasAndCasts(Item* pItem) +{ + SpellAuraHolderMap& auras = GetSpellAuraHolderMap(); + for (SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end();) + { + SpellAuraHolder* holder = itr->second; + + // skip passive (passive item dependent spells work in another way) and not self applied auras + SpellEntry const* spellInfo = holder->GetSpellProto(); + if (holder->IsPassive() || holder->GetCasterGuid() != GetObjectGuid()) + { + ++itr; + continue; + } + + // skip if not item dependent or have alternative item + if (HasItemFitToSpellReqirements(spellInfo, pItem)) + { + ++itr; + continue; + } + + // no alt item, remove aura, restart check + RemoveAurasDueToSpell(holder->GetId()); + itr = auras.begin(); + } + + // currently casted spells can be dependent from item + for (uint32 i = 0; i < CURRENT_MAX_SPELL; ++i) + if (Spell* spell = GetCurrentSpell(CurrentSpellTypes(i))) + if (spell->getState() != SPELL_STATE_DELAYED && !HasItemFitToSpellReqirements(spell->m_spellInfo, pItem)) + InterruptSpell(CurrentSpellTypes(i)); +} + +uint32 Player::GetResurrectionSpellId() +{ + // search priceless resurrection possibilities + uint32 prio = 0; + uint32 spell_id = 0; + AuraList const& dummyAuras = GetAurasByType(SPELL_AURA_DUMMY); + for (AuraList::const_iterator itr = dummyAuras.begin(); itr != dummyAuras.end(); ++itr) + { + // Soulstone Resurrection // prio: 3 (max, non death persistent) + if (prio < 2 && (*itr)->GetSpellProto()->SpellVisual[0] == 99 && (*itr)->GetSpellProto()->SpellIconID == 92) + { + switch ((*itr)->GetId()) + { + case 20707: spell_id = 3026; break; // rank 1 + case 20762: spell_id = 20758; break; // rank 2 + case 20763: spell_id = 20759; break; // rank 3 + case 20764: spell_id = 20760; break; // rank 4 + case 20765: spell_id = 20761; break; // rank 5 + case 27239: spell_id = 27240; break; // rank 6 + case 47883: spell_id = 47882; break; // rank 7 + default: + sLog.outError("Unhandled spell %u: S.Resurrection", (*itr)->GetId()); + continue; + } + + prio = 3; + } + // Twisting Nether // prio: 2 (max) + else if ((*itr)->GetId() == 23701 && roll_chance_i(10)) + { + prio = 2; + spell_id = 23700; + } + } + + // Reincarnation (passive spell) // prio: 1 + // Glyph of Renewed Life remove reagent requiremnnt + if (prio < 1 && HasSpell(20608) && !HasSpellCooldown(21169) && (HasItemCount(17030, 1) || HasAura(58059, EFFECT_INDEX_0))) + spell_id = 21169; + + return spell_id; +} + +// Used in triggers for check "Only to targets that grant experience or honor" req +bool Player::isHonorOrXPTarget(Unit* pVictim) const +{ + uint32 v_level = pVictim->getLevel(); + uint32 k_grey = MaNGOS::XP::GetGrayLevel(getLevel()); + + // Victim level less gray level + if (v_level <= k_grey) + return false; + + if (pVictim->GetTypeId() == TYPEID_UNIT) + { + if (((Creature*)pVictim)->IsTotem() || + ((Creature*)pVictim)->IsPet() || + ((Creature*)pVictim)->GetCreatureInfo()->flags_extra & CREATURE_FLAG_EXTRA_NO_XP_AT_KILL) + return false; + } + return true; +} + +void Player::RewardSinglePlayerAtKill(Unit* pVictim) +{ + bool PvP = pVictim->isCharmedOwnedByPlayerOrPlayer(); + uint32 xp = PvP ? 0 : MaNGOS::XP::Gain(this, pVictim); + + // honor can be in PvP and !PvP (racial leader) cases + RewardHonor(pVictim, 1); + + // xp and reputation only in !PvP case + if (!PvP) + { + RewardReputation(pVictim, 1); + GiveXP(xp, pVictim); + + if (Pet* pet = GetPet()) + pet->GivePetXP(xp); + + // normal creature (not pet/etc) can be only in !PvP case + if (pVictim->GetTypeId() == TYPEID_UNIT) + if (CreatureInfo const* normalInfo = ObjectMgr::GetCreatureTemplate(pVictim->GetEntry())) + KilledMonster(normalInfo, pVictim->GetObjectGuid()); + } +} + +void Player::RewardPlayerAndGroupAtEvent(uint32 creature_id, WorldObject* pRewardSource) +{ + MANGOS_ASSERT((!GetGroup() || pRewardSource) && "Player::RewardPlayerAndGroupAtEvent called for Group-Case but no source for range searching provided"); + + ObjectGuid creature_guid = pRewardSource && pRewardSource->GetTypeId() == TYPEID_UNIT ? pRewardSource->GetObjectGuid() : ObjectGuid(); + + // prepare data for near group iteration + if (Group* pGroup = GetGroup()) + { + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* pGroupGuy = itr->getSource(); + if (!pGroupGuy) + continue; + + if (!pGroupGuy->IsAtGroupRewardDistance(pRewardSource)) + continue; // member (alive or dead) or his corpse at req. distance + + // quest objectives updated only for alive group member or dead but with not released body + if (pGroupGuy->isAlive() || !pGroupGuy->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + pGroupGuy->KilledMonsterCredit(creature_id, creature_guid); + } + } + else // if (!pGroup) + KilledMonsterCredit(creature_id, creature_guid); +} + +void Player::RewardPlayerAndGroupAtCast(WorldObject* pRewardSource, uint32 spellid) +{ + // prepare data for near group iteration + if (Group* pGroup = GetGroup()) + { + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* pGroupGuy = itr->getSource(); + if (!pGroupGuy) + continue; + + if (!pGroupGuy->IsAtGroupRewardDistance(pRewardSource)) + continue; // member (alive or dead) or his corpse at req. distance + + // quest objectives updated only for alive group member or dead but with not released body + if (pGroupGuy->isAlive() || !pGroupGuy->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + pGroupGuy->CastedCreatureOrGO(pRewardSource->GetEntry(), pRewardSource->GetObjectGuid(), spellid, pGroupGuy == this); + } + } + else // if (!pGroup) + CastedCreatureOrGO(pRewardSource->GetEntry(), pRewardSource->GetObjectGuid(), spellid); +} + +bool Player::IsAtGroupRewardDistance(WorldObject const* pRewardSource) const +{ + if (pRewardSource->IsWithinDistInMap(this, sWorld.getConfig(CONFIG_FLOAT_GROUP_XP_DISTANCE))) + return true; + + if (isAlive()) + return false; + + Corpse* corpse = GetCorpse(); + if (!corpse) + return false; + + return pRewardSource->IsWithinDistInMap(corpse, sWorld.getConfig(CONFIG_FLOAT_GROUP_XP_DISTANCE)); +} + +void Player::ResurectUsingRequestData() +{ + /// Teleport before resurrecting by player, otherwise the player might get attacked from creatures near his corpse + if (m_resurrectGuid.IsPlayer()) + TeleportTo(m_resurrectMap, m_resurrectX, m_resurrectY, m_resurrectZ, GetOrientation()); + + // we cannot resurrect player when we triggered far teleport + // player will be resurrected upon teleportation + if (IsBeingTeleportedFar()) + { + ScheduleDelayedOperation(DELAYED_RESURRECT_PLAYER); + return; + } + + ResurrectPlayer(0.0f, false); + + if (GetMaxHealth() > m_resurrectHealth) + SetHealth(m_resurrectHealth); + else + SetHealth(GetMaxHealth()); + + if (GetMaxPower(POWER_MANA) > m_resurrectMana) + SetPower(POWER_MANA, m_resurrectMana); + else + SetPower(POWER_MANA, GetMaxPower(POWER_MANA)); + + SetPower(POWER_RAGE, 0); + + SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY)); + + SpawnCorpseBones(); +} + +void Player::SetClientControl(Unit* target, uint8 allowMove) +{ + WorldPacket data(SMSG_CLIENT_CONTROL_UPDATE, target->GetPackGUID().size() + 1); + data << target->GetPackGUID(); + data << uint8(allowMove); + GetSession()->SendPacket(&data); +} + +void Player::UpdateZoneDependentAuras() +{ + // Some spells applied at enter into zone (with subzones), aura removed in UpdateAreaDependentAuras that called always at zone->area update + SpellAreaForAreaMapBounds saBounds = sSpellMgr.GetSpellAreaForAreaMapBounds(m_zoneUpdateId); + for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + itr->second->ApplyOrRemoveSpellIfCan(this, m_zoneUpdateId, 0, true); +} + +void Player::UpdateAreaDependentAuras() +{ + // remove auras from spells with area limitations + for (SpellAuraHolderMap::iterator iter = m_spellAuraHolders.begin(); iter != m_spellAuraHolders.end();) + { + // use m_zoneUpdateId for speed: UpdateArea called from UpdateZone or instead UpdateZone in both cases m_zoneUpdateId up-to-date + if (sSpellMgr.GetSpellAllowedInLocationError(iter->second->GetSpellProto(), GetMapId(), m_zoneUpdateId, m_areaUpdateId, this) != SPELL_CAST_OK) + { + RemoveSpellAuraHolder(iter->second); + iter = m_spellAuraHolders.begin(); + } + else + ++iter; + } + + // some auras applied at subzone enter + SpellAreaForAreaMapBounds saBounds = sSpellMgr.GetSpellAreaForAreaMapBounds(m_areaUpdateId); + for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + itr->second->ApplyOrRemoveSpellIfCan(this, m_zoneUpdateId, m_areaUpdateId, true); +} + +struct UpdateZoneDependentPetsHelper +{ + explicit UpdateZoneDependentPetsHelper(Player* _owner, uint32 zone, uint32 area) : owner(_owner), zone_id(zone), area_id(area) {} + void operator()(Unit* unit) const + { + if (unit->GetTypeId() == TYPEID_UNIT && ((Creature*)unit)->IsPet() && !((Pet*)unit)->IsPermanentPetFor(owner)) + if (uint32 spell_id = unit->GetUInt32Value(UNIT_CREATED_BY_SPELL)) + if (SpellEntry const* spellEntry = sSpellStore.LookupEntry(spell_id)) + if (sSpellMgr.GetSpellAllowedInLocationError(spellEntry, owner->GetMapId(), zone_id, area_id, owner) != SPELL_CAST_OK) + ((Pet*)unit)->Unsummon(PET_SAVE_AS_DELETED, owner); + } + Player* owner; + uint32 zone_id; + uint32 area_id; +}; + +void Player::UpdateZoneDependentPets() +{ + // check pet (permanent pets ignored), minipet, guardians (including protector) + CallForAllControlledUnits(UpdateZoneDependentPetsHelper(this, m_zoneUpdateId, m_areaUpdateId), CONTROLLED_PET | CONTROLLED_GUARDIANS | CONTROLLED_MINIPET); +} + +uint32 Player::GetCorpseReclaimDelay(bool pvp) const +{ + if ((pvp && !sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVP)) || + (!pvp && !sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVE))) + { + return corpseReclaimDelay[0]; + } + + time_t now = time(NULL); + // 0..2 full period + uint32 count = (now < m_deathExpireTime) ? uint32((m_deathExpireTime - now) / DEATH_EXPIRE_STEP) : 0; + return corpseReclaimDelay[count]; +} + +void Player::UpdateCorpseReclaimDelay() +{ + bool pvp = m_ExtraFlags & PLAYER_EXTRA_PVP_DEATH; + + if ((pvp && !sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVP)) || + (!pvp && !sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVE))) + return; + + time_t now = time(NULL); + if (now < m_deathExpireTime) + { + // full and partly periods 1..3 + uint32 count = uint32((m_deathExpireTime - now) / DEATH_EXPIRE_STEP + 1); + if (count < MAX_DEATH_COUNT) + m_deathExpireTime = now + (count + 1) * DEATH_EXPIRE_STEP; + else + m_deathExpireTime = now + MAX_DEATH_COUNT * DEATH_EXPIRE_STEP; + } + else + m_deathExpireTime = now + DEATH_EXPIRE_STEP; +} + +void Player::SendCorpseReclaimDelay(bool load) +{ + Corpse* corpse = GetCorpse(); + if (!corpse) + return; + + uint32 delay; + if (load) + { + if (corpse->GetGhostTime() > m_deathExpireTime) + return; + + bool pvp = corpse->GetType() == CORPSE_RESURRECTABLE_PVP; + + uint32 count; + if ((pvp && sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVP)) || + (!pvp && sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVE))) + { + count = uint32(m_deathExpireTime - corpse->GetGhostTime()) / DEATH_EXPIRE_STEP; + if (count >= MAX_DEATH_COUNT) + count = MAX_DEATH_COUNT - 1; + } + else + count = 0; + + time_t expected_time = corpse->GetGhostTime() + corpseReclaimDelay[count]; + + time_t now = time(NULL); + if (now >= expected_time) + return; + + delay = uint32(expected_time - now); + } + else + delay = GetCorpseReclaimDelay(corpse->GetType() == CORPSE_RESURRECTABLE_PVP); + + //! corpse reclaim delay 30 * 1000ms or longer at often deaths + WorldPacket data(SMSG_CORPSE_RECLAIM_DELAY, 4); + data << uint32(delay * IN_MILLISECONDS); + GetSession()->SendPacket(&data); +} + +Player* Player::GetNextRandomRaidMember(float radius) +{ + Group* pGroup = GetGroup(); + if (!pGroup) + return NULL; + + std::vector nearMembers; + nearMembers.reserve(pGroup->GetMembersCount()); + + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* Target = itr->getSource(); + + // IsHostileTo check duel and controlled by enemy + if (Target && Target != this && IsWithinDistInMap(Target, radius) && + !Target->HasInvisibilityAura() && !IsHostileTo(Target)) + nearMembers.push_back(Target); + } + + if (nearMembers.empty()) + return NULL; + + uint32 randTarget = urand(0, nearMembers.size() - 1); + return nearMembers[randTarget]; +} + +PartyResult Player::CanUninviteFromGroup() const +{ + const Group* grp = GetGroup(); + if (!grp) + return ERR_NOT_IN_GROUP; + + if (!grp->IsLeader(GetObjectGuid()) && !grp->IsAssistant(GetObjectGuid())) + return ERR_NOT_LEADER; + + if (InBattleGround()) + return ERR_INVITE_RESTRICTED; + + return ERR_PARTY_RESULT_OK; +} + +void Player::SetBattleGroundRaid(Group* group, int8 subgroup) +{ + // we must move references from m_group to m_originalGroup + SetOriginalGroup(GetGroup(), GetSubGroup()); + + m_group.unlink(); + m_group.link(group, this); + m_group.setSubGroup((uint8)subgroup); +} + +void Player::RemoveFromBattleGroundRaid() +{ + // remove existing reference + m_group.unlink(); + if (Group* group = GetOriginalGroup()) + { + m_group.link(group, this); + m_group.setSubGroup(GetOriginalSubGroup()); + } + SetOriginalGroup(NULL); +} + +void Player::SetOriginalGroup(Group* group, int8 subgroup) +{ + if (group == NULL) + m_originalGroup.unlink(); + else + { + // never use SetOriginalGroup without a subgroup unless you specify NULL for group + MANGOS_ASSERT(subgroup >= 0); + m_originalGroup.link(group, this); + m_originalGroup.setSubGroup((uint8)subgroup); + } +} + +void Player::UpdateUnderwaterState(Map* m, float x, float y, float z) +{ + GridMapLiquidData liquid_status; + GridMapLiquidStatus res = m->GetTerrain()->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &liquid_status); + if (!res) + { + m_MirrorTimerFlags &= ~(UNDERWATER_INWATER | UNDERWATER_INLAVA | UNDERWATER_INSLIME | UNDERWATER_INDARKWATER); + if (m_lastLiquid && m_lastLiquid->SpellId) + RemoveAurasDueToSpell(m_lastLiquid->SpellId == 37025 ? 37284 : m_lastLiquid->SpellId); + m_lastLiquid = NULL; + return; + } + + if (uint32 liqEntry = liquid_status.entry) + { + LiquidTypeEntry const* liquid = sLiquidTypeStore.LookupEntry(liqEntry); + if (m_lastLiquid && m_lastLiquid->SpellId && m_lastLiquid->Id != liqEntry) + RemoveAurasDueToSpell(m_lastLiquid->SpellId); + + if (liquid && liquid->SpellId) + { + // Exception for SSC water + uint32 liquidSpellId = liquid->SpellId == 37025 ? 37284 : liquid->SpellId; + + if (res & (LIQUID_MAP_UNDER_WATER | LIQUID_MAP_IN_WATER)) + { + if (!HasAura(liquidSpellId)) + { + // Handle exception for SSC water + if (liquid->SpellId == 37025) + { + if (InstanceData* pInst = GetInstanceData()) + { + if (pInst->CheckConditionCriteriaMeet(this, INSTANCE_CONDITION_ID_LURKER, NULL, CONDITION_FROM_HARDCODED)) + { + if (pInst->CheckConditionCriteriaMeet(this, INSTANCE_CONDITION_ID_SCALDING_WATER, NULL, CONDITION_FROM_HARDCODED)) + CastSpell(this, liquidSpellId, true); + else + { + SummonCreature(21508, 0, 0, 0, 0, TEMPSUMMON_TIMED_OOC_DESPAWN, 2000); + // Special update timer for the SSC water + m_positionStatusUpdateTimer = 2000; + } + } + } + } + else + CastSpell(this, liquidSpellId, true); + } + } + else + RemoveAurasDueToSpell(liquidSpellId); + } + + m_lastLiquid = liquid; + } + else if (m_lastLiquid && m_lastLiquid->SpellId) + { + RemoveAurasDueToSpell(m_lastLiquid->SpellId == 37025 ? 37284 : m_lastLiquid->SpellId); + m_lastLiquid = NULL; + } + + // All liquids type - check under water position + if (liquid_status.type_flags & (MAP_LIQUID_TYPE_WATER | MAP_LIQUID_TYPE_OCEAN | MAP_LIQUID_TYPE_MAGMA | MAP_LIQUID_TYPE_SLIME)) + { + if (res & LIQUID_MAP_UNDER_WATER) + m_MirrorTimerFlags |= UNDERWATER_INWATER; + else + m_MirrorTimerFlags &= ~UNDERWATER_INWATER; + } + + // Allow travel in dark water on taxi or transport + if ((liquid_status.type_flags & MAP_LIQUID_TYPE_DARK_WATER) && !IsTaxiFlying() && !GetTransport()) + m_MirrorTimerFlags |= UNDERWATER_INDARKWATER; + else + m_MirrorTimerFlags &= ~UNDERWATER_INDARKWATER; + + // in lava check, anywhere in lava level + if (liquid_status.type_flags & MAP_LIQUID_TYPE_MAGMA) + { + if (res & (LIQUID_MAP_UNDER_WATER | LIQUID_MAP_IN_WATER | LIQUID_MAP_WATER_WALK)) + m_MirrorTimerFlags |= UNDERWATER_INLAVA; + else + m_MirrorTimerFlags &= ~UNDERWATER_INLAVA; + } + // in slime check, anywhere in slime level + if (liquid_status.type_flags & MAP_LIQUID_TYPE_SLIME) + { + if (res & (LIQUID_MAP_UNDER_WATER | LIQUID_MAP_IN_WATER | LIQUID_MAP_WATER_WALK)) + m_MirrorTimerFlags |= UNDERWATER_INSLIME; + else + m_MirrorTimerFlags &= ~UNDERWATER_INSLIME; + } +} + +void Player::SetCanParry(bool value) +{ + if (m_canParry == value) + return; + + m_canParry = value; + UpdateParryPercentage(); +} + +void Player::SetCanBlock(bool value) +{ + if (m_canBlock == value) + return; + + m_canBlock = value; + UpdateBlockPercentage(); + UpdateShieldBlockDamageValue(); +} + +bool ItemPosCount::isContainedIn(ItemPosCountVec const& vec) const +{ + for (ItemPosCountVec::const_iterator itr = vec.begin(); itr != vec.end(); ++itr) + if (itr->pos == pos) + return true; + + return false; +} + +bool Player::CanUseBattleGroundObject() +{ + // TODO : some spells gives player ForceReaction to one faction (ReputationMgr::ApplyForceReaction) + // maybe gameobject code should handle that ForceReaction usage + // BUG: sometimes when player clicks on flag in AB - client won't send gameobject_use, only gameobject_report_use packet + return (isAlive() && // living + // the following two are incorrect, because invisible/stealthed players should get visible when they click on flag + !HasStealthAura() && // not stealthed + !HasInvisibilityAura() && // visible + !isTotalImmune() && // vulnerable (not immune) + !HasAura(SPELL_RECENTLY_DROPPED_FLAG, EFFECT_INDEX_0)); +} + +uint32 Player::GetBarberShopCost(uint8 newhairstyle, uint8 newhaircolor, uint8 newfacialhair, uint32 newskintone) +{ + uint32 level = getLevel(); + + if (level > GT_MAX_LEVEL) + level = GT_MAX_LEVEL; // max level in this dbc + + uint8 hairstyle = GetByteValue(PLAYER_BYTES, 2); + uint8 haircolor = GetByteValue(PLAYER_BYTES, 3); + uint8 facialhair = GetByteValue(PLAYER_BYTES_2, 0); + uint8 skintone = GetByteValue(PLAYER_BYTES, 0); + + if (hairstyle == newhairstyle && haircolor == newhaircolor && facialhair == newfacialhair && + (skintone == newskintone || newskintone == -1)) + return 0; + + GtBarberShopCostBaseEntry const* bsc = sGtBarberShopCostBaseStore.LookupEntry(level - 1); + + if (!bsc) // shouldn't happen + return 0xFFFFFFFF; + + float cost = 0; + + if (hairstyle != newhairstyle) + cost += bsc->cost; // full price + + if (haircolor != newhaircolor && hairstyle == newhairstyle) + cost += bsc->cost * 0.5f; // +1/2 of price + + if (facialhair != newfacialhair) + cost += bsc->cost * 0.75f; // +3/4 of price + + if (skintone != newskintone && newskintone != -1) + cost += bsc->cost * 0.5f; // +1/2 of price + + return uint32(cost); +} + +void Player::InitGlyphsForLevel() +{ + uint32 slot = 0; + for (uint32 i = 0; i < sGlyphSlotStore.GetNumRows() && slot < MAX_GLYPH_SLOT_INDEX; ++i) + if (GlyphSlotEntry const* gs = sGlyphSlotStore.LookupEntry(i)) + SetGlyphSlot(slot++, gs->Id); + + uint32 level = getLevel(); + uint32 value = 0; + + // 0x3F = 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 for 80 level + if (level >= 25) + value |= 0x01 | 0x02 | 0x40; + if (level >= 50) + value |= 0x04 | 0x08 | 0x80; + if (level >= 75) + value |= 0x10 | 0x20 | 0x100; + + SetUInt32Value(PLAYER_GLYPHS_ENABLED, value); +} + +void Player::ApplyGlyph(uint8 slot, bool apply) +{ + if (uint32 glyph = GetGlyph(slot)) + { + if (GlyphPropertiesEntry const* gp = sGlyphPropertiesStore.LookupEntry(glyph)) + { + if (apply) + { + CastSpell(this, gp->SpellId, true); + SetUInt32Value(PLAYER_FIELD_GLYPHS_1 + slot, glyph); + } + else + { + RemoveAurasDueToSpell(gp->SpellId); + SetUInt32Value(PLAYER_FIELD_GLYPHS_1 + slot, 0); + } + } + } +} + +void Player::ApplyGlyphs(bool apply) +{ + for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i) + ApplyGlyph(i, apply); +} + +bool Player::isTotalImmune() +{ + AuraList const& immune = GetAurasByType(SPELL_AURA_SCHOOL_IMMUNITY); + + uint32 immuneMask = 0; + for (AuraList::const_iterator itr = immune.begin(); itr != immune.end(); ++itr) + { + immuneMask |= (*itr)->GetModifier()->m_miscvalue; + if (immuneMask & SPELL_SCHOOL_MASK_ALL) // total immunity + return true; + } + return false; +} + +bool Player::HasTitle(uint32 bitIndex) const +{ + if (bitIndex > MAX_TITLE_INDEX) + return false; + + uint32 fieldIndexOffset = bitIndex / 32; + uint32 flag = 1 << (bitIndex % 32); + return HasFlag(PLAYER__FIELD_KNOWN_TITLES + fieldIndexOffset, flag); +} + +void Player::SetTitle(CharTitlesEntry const* title, bool lost) +{ + uint32 fieldIndexOffset = title->bit_index / 32; + uint32 flag = 1 << (title->bit_index % 32); + + if (lost) + { + if (!HasFlag(PLAYER__FIELD_KNOWN_TITLES + fieldIndexOffset, flag)) + return; + + RemoveFlag(PLAYER__FIELD_KNOWN_TITLES + fieldIndexOffset, flag); + } + else + { + if (HasFlag(PLAYER__FIELD_KNOWN_TITLES + fieldIndexOffset, flag)) + return; + + SetFlag(PLAYER__FIELD_KNOWN_TITLES + fieldIndexOffset, flag); + } + + WorldPacket data(SMSG_TITLE_EARNED, 4 + 4); + data << uint32(title->bit_index); + data << uint32(lost ? 0 : 1); // 1 - earned, 0 - lost + GetSession()->SendPacket(&data); +} + +void Player::UpdateRuneRegen(RuneType rune) +{ + if (rune >= RUNE_DEATH) + return; + + RuneType actualRune = rune; + float cooldown = RUNE_BASE_COOLDOWN; + for (uint8 i = 0; i < MAX_RUNES; i += 2) + { + if (GetBaseRune(i) != rune) + continue; + + uint32 cd = GetRuneCooldown(i); + uint32 secondRuneCd = GetRuneCooldown(i + 1); + if (!cd && !secondRuneCd) + { + actualRune = GetCurrentRune(i); + } + else if (secondRuneCd && (cd > secondRuneCd || !cd)) + { + cooldown = GetBaseRuneCooldown(i + 1); + actualRune = GetCurrentRune(i + 1); + } + else + { + cooldown = GetBaseRuneCooldown(i); + actualRune = GetCurrentRune(i); + } + break; + } + + float auraMod = 1.0f; + Unit::AuraList const& regenAuras = GetAurasByType(SPELL_AURA_MOD_POWER_REGEN_PERCENT); + for (Unit::AuraList::const_iterator i = regenAuras.begin(); i != regenAuras.end(); ++i) + if ((*i)->GetMiscValue() == POWER_RUNE && (*i)->GetSpellEffect()->EffectMiscValueB == rune) + auraMod *= (100.0f + (*i)->GetModifier()->m_amount) / 100.0f; + + // Unholy Presence + if (Aura* aura = GetAura(48265, EFFECT_INDEX_0)) + auraMod *= (100.0f + aura->GetModifier()->m_amount) / 100.0f; + + // Runic Corruption + if (Aura* aura = GetAura(51460, EFFECT_INDEX_0)) + auraMod *= (100.0f + aura->GetModifier()->m_amount) / 100.0f; + + float hastePct = (100.0f - GetRatingBonusValue(CR_HASTE_MELEE)) / 100.0f; + if (hastePct < 0) + hastePct = 1.0f; + + cooldown *= hastePct / auraMod; + + float value = float(1 * IN_MILLISECONDS) / cooldown; + SetFloatValue(PLAYER_RUNE_REGEN_1 + uint8(actualRune), value); +} + +void Player::UpdateRuneRegen() +{ + for (uint8 i = 0; i < NUM_RUNE_TYPES; ++i) + UpdateRuneRegen(RuneType(i)); +} + +uint8 Player::GetRuneCooldownFraction(uint8 index) const +{ + uint16 baseCd = GetBaseRuneCooldown(index); + if (!baseCd || !GetRuneCooldown(index)) + return 255; + else if (baseCd == GetRuneCooldown(index)) + return 0; + + return uint8(float(baseCd - GetRuneCooldown(index)) / baseCd * 255); +} + +void Player::AddRuneByAuraEffect(uint8 index, RuneType newType, Aura const* aura) +{ + // Item - Death Knight T11 DPS 4P Bonus + if (newType == RUNE_DEATH && HasAura(90459)) + CastSpell(this, 90507, true); // Death Eater + + SetRuneConvertAura(index, aura); ConvertRune(index, newType); +} + +void Player::RemoveRunesByAuraEffect(Aura const* aura) +{ + for (uint8 i = 0; i < MAX_RUNES; ++i) + { + if (m_runes->runes[i].ConvertAura == aura) + { + ConvertRune(i, GetBaseRune(i)); + SetRuneConvertAura(i, NULL); + } + } +} + +void Player::RestoreBaseRune(uint8 index) +{ + Aura const* aura = m_runes->runes[index].ConvertAura; + // If rune was converted by a non-pasive aura that still active we should keep it converted + if (aura && !IsPassiveSpell(aura->GetSpellProto())) + return; + + // Blood of the North + if (aura->GetId() == 54637 && HasAura(54637)) + return; + + ConvertRune(index, GetBaseRune(index)); + SetRuneConvertAura(index, NULL); + // Don't drop passive talents providing rune convertion + if (!aura || aura->GetModifier()->m_auraname != SPELL_AURA_CONVERT_RUNE) + return; + + for (uint8 i = 0; i < MAX_RUNES; ++i) + if (aura == m_runes->runes[i].ConvertAura) + return; + + if (Unit* target = aura->GetTarget()) + target->RemoveSpellAuraHolder(const_cast(aura)->GetHolder()); +} + +void Player::ConvertRune(uint8 index, RuneType newType) +{ + SetCurrentRune(index, newType); + + WorldPacket data(SMSG_CONVERT_RUNE, 2); + data << uint8(index); + data << uint8(newType); + GetSession()->SendPacket(&data); +} + +void Player::ResyncRunes() +{ + WorldPacket data(SMSG_RESYNC_RUNES, 4 + MAX_RUNES * 2); + data << uint32(MAX_RUNES); + for (uint32 i = 0; i < MAX_RUNES; ++i) + { + data << uint8(GetCurrentRune(i)); // rune type + data << uint8(GetRuneCooldownFraction(i)); + } + GetSession()->SendPacket(&data); +} + +void Player::AddRunePower(uint8 index) +{ + WorldPacket data(SMSG_ADD_RUNE_POWER, 4); + data << uint32(1 << index); // mask (0x00-0x3F probably) + GetSession()->SendPacket(&data); +} + +void Player::InitRunes() +{ + if (getClass() != CLASS_DEATH_KNIGHT) + return; + + m_runes = new Runes; + + m_runes->runeState = 0; + + for (uint32 i = 0; i < MAX_RUNES; ++i) + { + SetBaseRune(i, runeSlotTypes[i]); // init base types + SetCurrentRune(i, runeSlotTypes[i]); // init current types + SetRuneCooldown(i, 0); // reset cooldowns + m_runes->SetRuneState(i); + } + + for (uint8 i = 0; i < NUM_RUNE_TYPES; ++i) + SetFloatValue(PLAYER_RUNE_REGEN_1 + i, 0.1f); +} + +bool Player::IsBaseRuneSlotsOnCooldown(RuneType runeType) const +{ + for (uint32 i = 0; i < MAX_RUNES; ++i) + if (GetBaseRune(i) == runeType && GetRuneCooldown(i) == 0) + return false; + + return true; +} + +void Player::AutoStoreLoot(WorldObject const* lootTarget, uint32 loot_id, LootStore const& store, bool broadcast, uint8 bag, uint8 slot) +{ + Loot loot(lootTarget); + loot.FillLoot(loot_id, store, this, true); + + AutoStoreLoot(loot, broadcast, bag, slot); +} + +void Player::AutoStoreLoot(Loot& loot, bool broadcast, uint8 bag, uint8 slot) +{ + uint32 max_slot = loot.GetMaxSlotInLootFor(this); + for (uint32 i = 0; i < max_slot; ++i) + { + LootItem* lootItem = loot.LootItemInSlot(i, this); + + ItemPosCountVec dest; + InventoryResult msg = CanStoreNewItem(bag, slot, dest, lootItem->itemid, lootItem->count); + if (msg != EQUIP_ERR_OK && slot != NULL_SLOT) + msg = CanStoreNewItem(bag, NULL_SLOT, dest, lootItem->itemid, lootItem->count); + if (msg != EQUIP_ERR_OK && bag != NULL_BAG) + msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, lootItem->itemid, lootItem->count); + if (msg != EQUIP_ERR_OK) + { + SendEquipError(msg, NULL, NULL, lootItem->itemid); + continue; + } + + Item* pItem = StoreNewItem(dest, lootItem->itemid, true, lootItem->randomPropertyId); + SendNewItem(pItem, lootItem->count, false, false, broadcast); + } +} + +Item* Player::ConvertItem(Item* item, uint32 newItemId) +{ + uint16 pos = item->GetPos(); + + Item* pNewItem = Item::CreateItem(newItemId, 1, this); + if (!pNewItem) + return NULL; + + // copy enchantments + for (uint8 j = PERM_ENCHANTMENT_SLOT; j <= TEMP_ENCHANTMENT_SLOT; ++j) + { + if (item->GetEnchantmentId(EnchantmentSlot(j))) + pNewItem->SetEnchantment(EnchantmentSlot(j), item->GetEnchantmentId(EnchantmentSlot(j)), + item->GetEnchantmentDuration(EnchantmentSlot(j)), item->GetEnchantmentCharges(EnchantmentSlot(j))); + } + + // copy durability + if (item->GetUInt32Value(ITEM_FIELD_DURABILITY) < item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY)) + { + double loosePercent = 1 - item->GetUInt32Value(ITEM_FIELD_DURABILITY) / double(item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY)); + DurabilityLoss(pNewItem, loosePercent); + } + + if (IsInventoryPos(pos)) + { + ItemPosCountVec dest; + InventoryResult msg = CanStoreItem(item->GetBagSlot(), item->GetSlot(), dest, pNewItem, true); + // ignore cast/combat time restriction + if (msg == EQUIP_ERR_OK) + { + DestroyItem(item->GetBagSlot(), item->GetSlot(), true); + return StoreItem(dest, pNewItem, true); + } + } + else if (IsBankPos(pos)) + { + ItemPosCountVec dest; + InventoryResult msg = CanBankItem(item->GetBagSlot(), item->GetSlot(), dest, pNewItem, true); + // ignore cast/combat time restriction + if (msg == EQUIP_ERR_OK) + { + DestroyItem(item->GetBagSlot(), item->GetSlot(), true); + return BankItem(dest, pNewItem, true); + } + } + else if (IsEquipmentPos(pos)) + { + uint16 dest; + InventoryResult msg = CanEquipItem(item->GetSlot(), dest, pNewItem, true, false); + // ignore cast/combat time restriction + if (msg == EQUIP_ERR_OK) + { + DestroyItem(item->GetBagSlot(), item->GetSlot(), true); + pNewItem = EquipItem(dest, pNewItem, true); + AutoUnequipOffhandIfNeed(); + return pNewItem; + } + } + + // fail + delete pNewItem; + return NULL; +} + +uint32 Player::CalculateTalentsPoints() const +{ + // this dbc file has entries only up to level 100 + NumTalentsAtLevelEntry const* count = sNumTalentsAtLevelStore.LookupEntry(std::min(getLevel(), 100)); + if (!count) + return 0; + + float baseForLevel = count->Talents; + + if (getClass() != CLASS_DEATH_KNIGHT) + return uint32(baseForLevel * sWorld.getConfig(CONFIG_FLOAT_RATE_TALENT)); + + // Death Knight starting level + // hardcoded here - number of quest awarded talents is equal to number of talents any other class would have at level 55 + if (getLevel() < 55) + return 0; + + NumTalentsAtLevelEntry const* dkBase = sNumTalentsAtLevelStore.LookupEntry(55); + if (!dkBase) + return 0; + + float talentPointsForLevel = count->Talents - dkBase->Talents; + talentPointsForLevel += float(m_questRewardTalentCount); + + if (talentPointsForLevel > baseForLevel) + talentPointsForLevel = baseForLevel; + + return uint32(talentPointsForLevel * sWorld.getConfig(CONFIG_FLOAT_RATE_TALENT)); +} + +bool Player::CanStartFlyInArea(uint32 mapid, uint32 zone, uint32 area) const +{ + if (isGameMaster()) + return true; + + // continent checked in SpellMgr::GetSpellAllowedInLocationError at cast and area update + uint32 v_map = GetVirtualMapForMapAndZone(mapid, zone); + + // switch all known flying maps + switch (v_map) + { + case 0: // Eastern Kingdoms + case 1: // Kalimdor + case 646: // Deepholm + return HasSpell(90267); + case 530: // Outland + return true; + case 571: // Northrend + // Check Cold Weather Flying + // Disallow mounting in wintergrasp when battle is in progress + return HasSpell(54197); /* && (!inWintergrasp || wg->GetState() != BF_IN_PROGRESS);*/ + } + + return false; +} + +struct DoPlayerLearnSpell +{ + DoPlayerLearnSpell(Player& _player) : player(_player) {} + void operator()(uint32 spell_id) { player.learnSpell(spell_id, false); } + Player& player; +}; + +void Player::learnSpellHighRank(uint32 spellid) +{ + learnSpell(spellid, false); + + DoPlayerLearnSpell worker(*this); + sSpellMgr.doForHighRanks(spellid, worker); +} + +void Player::_LoadSkills(QueryResult* result) +{ + // 0 1 2 + // SetPQuery(PLAYER_LOGIN_QUERY_LOADSKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = '%u'", GUID_LOPART(m_guid)); + + uint32 count = 0; + uint8 professionCount = 0; + if (result) + { + do + { + Field* fields = result->Fetch(); + + uint16 skill = fields[0].GetUInt16(); + uint16 value = fields[1].GetUInt16(); + uint16 max = fields[2].GetUInt16(); + + SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(skill); + if (!pSkill) + { + sLog.outError("Character %u has skill %u that does not exist.", GetGUIDLow(), skill); + continue; + } + + // set fixed skill ranges + switch (GetSkillRangeType(pSkill, false)) + { + case SKILL_RANGE_LANGUAGE: // 300..300 + value = max = 300; + break; + case SKILL_RANGE_MONO: // 1..1, grey monolite bar + value = max = 1; + break; + default: + break; + } + + if (value == 0) + { + sLog.outError("Character %u has skill %u with value 0. Will be deleted.", GetGUIDLow(), skill); + CharacterDatabase.PExecute("DELETE FROM character_skills WHERE guid = '%u' AND skill = '%u' ", GetGUIDLow(), skill); + continue; + } + + uint16 field = count / 2; + uint8 offset = count & 1; + + SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, skill); + uint16 step = 0; + + if (pSkill->categoryId == SKILL_CATEGORY_SECONDARY) + step = max / 75; + + if (pSkill->categoryId == SKILL_CATEGORY_PROFESSION) + { + step = max / 75; + + if (professionCount < 2) + SetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1 + professionCount++, skill); + } + + SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, step); + SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, value); + SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, max); + SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, 0); + SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, 0); + + mSkillStatus.insert(SkillStatusMap::value_type(skill, SkillStatusData(count, SKILL_UNCHANGED))); + + learnSkillRewardedSpells(skill, value); + + ++count; + + if (count >= PLAYER_MAX_SKILLS) // client limit + { + sLog.outError("Character %u has more than %u skills.", GetGUIDLow(), PLAYER_MAX_SKILLS); + break; + } + } + while (result->NextRow()); + delete result; + } + + for (; count < PLAYER_MAX_SKILLS; ++count) + { + uint16 field = count / 2; + uint8 offset = count & 1; + + SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, 0); + SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, 0); + SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, 0); + SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, 0); + SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, 0); + SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, 0); + } + + // special settings + if (getClass() == CLASS_DEATH_KNIGHT) + { + uint32 base_level = std::min(getLevel(), sWorld.getConfig(CONFIG_UINT32_START_HEROIC_PLAYER_LEVEL)); + if (base_level < 1) + base_level = 1; + uint32 base_skill = (base_level - 1) * 5; // 270 at starting level 55 + if (base_skill < 1) + base_skill = 1; // skill mast be known and then > 0 in any case + + if (GetPureSkillValue(SKILL_FIRST_AID) < base_skill) + SetSkill(SKILL_FIRST_AID, base_skill, base_skill); + if (GetPureSkillValue(SKILL_AXES) < base_skill) + SetSkill(SKILL_AXES, base_skill, base_skill); + if (GetPureSkillValue(SKILL_DEFENSE) < base_skill) + SetSkill(SKILL_DEFENSE, base_skill, base_skill); + if (GetPureSkillValue(SKILL_POLEARMS) < base_skill) + SetSkill(SKILL_POLEARMS, base_skill, base_skill); + if (GetPureSkillValue(SKILL_SWORDS) < base_skill) + SetSkill(SKILL_SWORDS, base_skill, base_skill); + if (GetPureSkillValue(SKILL_2H_AXES) < base_skill) + SetSkill(SKILL_2H_AXES, base_skill, base_skill); + if (GetPureSkillValue(SKILL_2H_SWORDS) < base_skill) + SetSkill(SKILL_2H_SWORDS, base_skill, base_skill); + if (GetPureSkillValue(SKILL_UNARMED) < base_skill) + SetSkill(SKILL_UNARMED, base_skill, base_skill); + } +} + +InventoryResult Player::CanEquipUniqueItem(Item* pItem, uint8 eslot, uint32 limit_count) const +{ + ItemPrototype const* pProto = pItem->GetProto(); + + // proto based limitations + if (InventoryResult res = CanEquipUniqueItem(pProto, eslot, limit_count)) + return res; + + // check unique-equipped on gems + for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + 3; ++enchant_slot) + { + uint32 enchant_id = pItem->GetEnchantmentId(EnchantmentSlot(enchant_slot)); + if (!enchant_id) + continue; + SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!enchantEntry) + continue; + + ItemPrototype const* pGem = ObjectMgr::GetItemPrototype(enchantEntry->GemID); + if (!pGem) + continue; + + // include for check equip another gems with same limit category for not equipped item (and then not counted) + uint32 gem_limit_count = !pItem->IsEquipped() && pGem->ItemLimitCategory + ? pItem->GetGemCountWithLimitCategory(pGem->ItemLimitCategory) : 1; + + if (InventoryResult res = CanEquipUniqueItem(pGem, eslot, gem_limit_count)) + return res; + } + + return EQUIP_ERR_OK; +} + +InventoryResult Player::CanEquipUniqueItem(ItemPrototype const* itemProto, uint8 except_slot, uint32 limit_count) const +{ + // check unique-equipped on item + if (itemProto->Flags & ITEM_FLAG_UNIQUE_EQUIPPED) + { + // there is an equip limit on this item + if (HasItemOrGemWithIdEquipped(itemProto->ItemId, 1, except_slot)) + return EQUIP_ERR_ITEM_UNIQUE_EQUIPABLE; + } + + // check unique-equipped limit + if (itemProto->ItemLimitCategory) + { + ItemLimitCategoryEntry const* limitEntry = sItemLimitCategoryStore.LookupEntry(itemProto->ItemLimitCategory); + if (!limitEntry) + return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; + + // NOTE: limitEntry->mode not checked because if item have have-limit then it applied and to equip case + + if (limit_count > limitEntry->maxCount) + return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED_IS; + + // there is an equip limit on this item + if (HasItemOrGemWithLimitCategoryEquipped(itemProto->ItemLimitCategory, limitEntry->maxCount - limit_count + 1, except_slot)) + return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED_IS; + } + + return EQUIP_ERR_OK; +} + +void Player::HandleFall(MovementInfo const& movementInfo) +{ + // calculate total z distance of the fall + float z_diff = m_lastFallZ - movementInfo.GetPos()->z; + DEBUG_LOG("zDiff = %f", z_diff); + + // Players with low fall distance, Feather Fall or physical immunity (charges used) are ignored + // 14.57 can be calculated by resolving damageperc formula below to 0 + if (z_diff >= 14.57f && !isDead() && !isGameMaster() && /*!HasMovementFlag(MOVEFLAG_ONTRANSPORT) &&*/ + !HasAuraType(SPELL_AURA_HOVER) && !HasAuraType(SPELL_AURA_FEATHER_FALL) && + !HasAuraType(SPELL_AURA_FLY) && !IsImmunedToDamage(SPELL_SCHOOL_MASK_NORMAL)) + { + // Safe fall, fall height reduction + int32 safe_fall = GetTotalAuraModifier(SPELL_AURA_SAFE_FALL); + + float damageperc = 0.018f * (z_diff - safe_fall) - 0.2426f; + + if (damageperc > 0) + { + uint32 damage = (uint32)(damageperc * GetMaxHealth() * sWorld.getConfig(CONFIG_FLOAT_RATE_DAMAGE_FALL)); + + float height = movementInfo.GetPos()->z; + UpdateAllowedPositionZ(movementInfo.GetPos()->x, movementInfo.GetPos()->y, height); + + if (damage > 0) + { + // Prevent fall damage from being more than the player maximum health + if (damage > GetMaxHealth()) + damage = GetMaxHealth(); + + // Gust of Wind + if (GetDummyAura(43621)) + damage = GetMaxHealth() / 2; + + uint32 original_health = GetHealth(); + uint32 final_damage = EnvironmentalDamage(DAMAGE_FALL, damage); + + // recheck alive, might have died of EnvironmentalDamage, avoid cases when player die in fact like Spirit of Redemption case + if (isAlive() && final_damage < original_health) + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING, uint32(z_diff * 100)); + } + + // Z given by moveinfo, LastZ, FallTime, WaterZ, MapZ, Damage, Safefall reduction + DEBUG_LOG("FALLDAMAGE z=%f sz=%f pZ=%f FallTime=%d mZ=%f damage=%d SF=%d" , movementInfo.GetPos()->z, height, GetPositionZ(), movementInfo.GetFallTime(), height, damage, safe_fall); + } + } +} + +void Player::UpdateAchievementCriteria(AchievementCriteriaTypes type, uint32 miscvalue1/*=0*/, uint32 miscvalue2/*=0*/, Unit* unit/*=NULL*/, uint32 time/*=0*/) +{ + GetAchievementMgr().UpdateAchievementCriteria(type, miscvalue1, miscvalue2, unit, time); +} + +void Player::StartTimedAchievementCriteria(AchievementCriteriaTypes type, uint32 timedRequirementId, time_t startTime /*= 0*/) +{ + GetAchievementMgr().StartTimedAchievementCriteria(type, timedRequirementId, startTime); +} + +PlayerTalent const* Player::GetKnownTalentById(int32 talentId) const +{ + PlayerTalentMap::const_iterator itr = m_talents[m_activeSpec].find(talentId); + if (itr != m_talents[m_activeSpec].end() && itr->second.state != PLAYERSPELL_REMOVED) + return &itr->second; + else + return NULL; +} + +SpellEntry const* Player::GetKnownTalentRankById(int32 talentId) const +{ + if (PlayerTalent const* talent = GetKnownTalentById(talentId)) + return sSpellStore.LookupEntry(talent->talentEntry->RankID[talent->currentRank]); + else + return NULL; +} + +bool Player::LearnTalent(uint32 talentId, uint32 talentRank) +{ + uint32 CurTalentPoints = GetFreeTalentPoints(); + + if (CurTalentPoints == 0) + return false; + + if (talentRank >= MAX_TALENT_RANK) + return false; + + TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); + + if (!talentInfo) + return false; + + TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab); + + if (!talentTabInfo) + return false; + + // prevent learn talent for different class (cheating) + if ((getClassMask() & talentTabInfo->ClassMask) == 0) + return false; + + // find current max talent rank + uint32 curtalent_maxrank = 0; + if (PlayerTalent const* talent = GetKnownTalentById(talentId)) + curtalent_maxrank = talent->currentRank + 1; + + // we already have same or higher talent rank learned + if (curtalent_maxrank >= (talentRank + 1)) + return false; + + // check if we have enough talent points + if (CurTalentPoints < (talentRank - curtalent_maxrank + 1)) + return false; + + // Check if it requires another talent + if (talentInfo->DependsOn > 0) + { + if (TalentEntry const* depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn)) + { + bool hasEnoughRank = false; + PlayerTalentMap::iterator dependsOnTalent = m_talents[m_activeSpec].find(depTalentInfo->TalentID); + if (dependsOnTalent != m_talents[m_activeSpec].end() && dependsOnTalent->second.state != PLAYERSPELL_REMOVED) + { + PlayerTalent depTalent = (*dependsOnTalent).second; + if (depTalent.currentRank >= talentInfo->DependsOnRank) + hasEnoughRank = true; + } + + if (!hasEnoughRank) + return false; + } + } + + // Find out how many points we have in this field + uint32 spentPoints = 0; + + uint32 primaryTreeTalents = 0; + uint32 tTab = talentInfo->TalentTab; + bool isMainTree = m_talentsPrimaryTree[m_activeSpec] == tTab || !m_talentsPrimaryTree[m_activeSpec]; + + if (talentInfo->Row > 0 || !isMainTree) + { + for (uint32 i = 0; i < sTalentStore.GetNumRows(); i++) // Loop through all talents. + { + if (TalentEntry const* tmpTalent = sTalentStore.LookupEntry(i)) // Someday, someone needs to revamp the way talents are tracked + { + for (uint8 rank = 0; rank < MAX_TALENT_RANK; rank++) + { + if (tmpTalent->RankID[rank] != 0) + { + if (HasSpell(tmpTalent->RankID[rank])) + { + if (tmpTalent->TalentTab == tTab) + spentPoints += (rank + 1); + if (tmpTalent->TalentTab == m_talentsPrimaryTree[m_activeSpec]) + primaryTreeTalents += (rank + 1); + } + } + } + } + } + } + + // not have required min points spent in talent tree + if (spentPoints < (talentInfo->Row * MAX_TALENT_RANK)) + return false; + + // player has not spent 31 talents in main tree before attempting to learn other tree's talents + if (!isMainTree && primaryTreeTalents < REQ_PRIMARY_TREE_TALENTS) + return false; + + // spell not set in talent.dbc + uint32 spellid = talentInfo->RankID[talentRank]; + if (spellid == 0) + { + sLog.outError("Talent.dbc have for talent: %u Rank: %u spell id = 0", talentId, talentRank); + return false; + } + + // already known + if (HasSpell(spellid)) + return false; + + // learn! (other talent ranks will unlearned at learning) + learnSpell(spellid, false); + DETAIL_LOG("TalentID: %u Rank: %u Spell: %u\n", talentId, talentRank, spellid); + + // set talent tree for player + if (!m_talentsPrimaryTree[m_activeSpec]) + { + m_talentsPrimaryTree[m_activeSpec] = talentInfo->TalentTab; + if (std::vector const* specSpells = GetTalentTreeMasterySpells(talentInfo->TalentTab)) + for (size_t i = 0; i < specSpells->size(); ++i) + learnSpell(specSpells->at(i), false); + + if (std::vector const* specSpells = GetTalentTreePrimarySpells(talentInfo->TalentTab)) + for (size_t i = 0; i < specSpells->size(); ++i) + learnSpell(specSpells->at(i), false); + + // Update talent tree role-dependent mana regen + UpdateManaRegen(); + UpdateArmorSpecializations(); + } + + return true; +} + +void Player::LearnPetTalent(ObjectGuid petGuid, uint32 talentId, uint32 talentRank) +{ + Pet* pet = GetPet(); + if (!pet) + return; + + if (petGuid != pet->GetObjectGuid()) + return; + + uint32 CurTalentPoints = pet->GetFreeTalentPoints(); + + if (CurTalentPoints == 0) + return; + + if (talentRank >= MAX_PET_TALENT_RANK) + return; + + TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); + + if (!talentInfo) + return; + + TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab); + + if (!talentTabInfo) + return; + + CreatureInfo const* ci = pet->GetCreatureInfo(); + + if (!ci) + return; + + CreatureFamilyEntry const* pet_family = sCreatureFamilyStore.LookupEntry(ci->family); + + if (!pet_family) + return; + + if (pet_family->petTalentType < 0) // not hunter pet + return; + + // prevent learn talent for different family (cheating) + if (!((1 << pet_family->petTalentType) & talentTabInfo->petTalentMask)) + return; + + // find current max talent rank + int32 curtalent_maxrank = 0; + for (int32 k = MAX_TALENT_RANK - 1; k > -1; --k) + { + if (talentInfo->RankID[k] && pet->HasSpell(talentInfo->RankID[k])) + { + curtalent_maxrank = k + 1; + break; + } + } + + // we already have same or higher talent rank learned + if (curtalent_maxrank >= int32(talentRank + 1)) + return; + + // check if we have enough talent points + if (CurTalentPoints < (talentRank - curtalent_maxrank + 1)) + return; + + // Check if it requires another talent + if (talentInfo->DependsOn > 0) + { + if (TalentEntry const* depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn)) + { + bool hasEnoughRank = false; + for (int i = talentInfo->DependsOnRank; i < MAX_TALENT_RANK; ++i) + { + if (depTalentInfo->RankID[i] != 0) + if (pet->HasSpell(depTalentInfo->RankID[i])) + hasEnoughRank = true; + } + if (!hasEnoughRank) + return; + } + } + + // Find out how many points we have in this field + uint32 spentPoints = 0; + + uint32 tTab = talentInfo->TalentTab; + if (talentInfo->Row > 0) + { + unsigned int numRows = sTalentStore.GetNumRows(); + for (unsigned int i = 0; i < numRows; ++i) // Loop through all talents. + { + // Someday, someone needs to revamp + const TalentEntry* tmpTalent = sTalentStore.LookupEntry(i); + if (tmpTalent) // the way talents are tracked + { + if (tmpTalent->TalentTab == tTab) + { + for (int j = 0; j < MAX_TALENT_RANK; ++j) + { + if (tmpTalent->RankID[j] != 0) + { + if (pet->HasSpell(tmpTalent->RankID[j])) + { + spentPoints += j + 1; + } + } + } + } + } + } + } + + // not have required min points spent in talent tree + if (spentPoints < (talentInfo->Row * MAX_PET_TALENT_RANK)) + return; + + // spell not set in talent.dbc + uint32 spellid = talentInfo->RankID[talentRank]; + if (spellid == 0) + { + sLog.outError("Talent.dbc have for talent: %u Rank: %u spell id = 0", talentId, talentRank); + return; + } + + // already known + if (pet->HasSpell(spellid)) + return; + + // learn! (other talent ranks will unlearned at learning) + pet->learnSpell(spellid); + DETAIL_LOG("PetTalentID: %u Rank: %u Spell: %u\n", talentId, talentRank, spellid); +} + +void Player::UpdateFallInformationIfNeed(MovementInfo const& minfo, uint16 opcode) +{ + if (m_lastFallTime >= minfo.GetFallTime() || m_lastFallZ <= minfo.GetPos()->z || opcode == CMSG_MOVE_FALL_LAND) + SetFallInformation(minfo.GetFallTime(), minfo.GetPos()->z); +} + +void Player::UnsummonPetTemporaryIfAny() +{ + Pet* pet = GetPet(); + if (!pet) + return; + + if (!m_temporaryUnsummonedPetNumber && pet->isControlled() && !pet->isTemporarySummoned()) + m_temporaryUnsummonedPetNumber = pet->GetCharmInfo()->GetPetNumber(); + + pet->Unsummon(PET_SAVE_AS_CURRENT, this); +} + +void Player::ResummonPetTemporaryUnSummonedIfAny() +{ + if (!m_temporaryUnsummonedPetNumber) + return; + + // not resummon in not appropriate state + if (IsPetNeedBeTemporaryUnsummoned()) + return; + + if (GetPetGuid()) + return; + + Pet* NewPet = new Pet; + if (!NewPet->LoadPetFromDB(this, 0, m_temporaryUnsummonedPetNumber, true)) + delete NewPet; + + m_temporaryUnsummonedPetNumber = 0; +} + +bool Player::canSeeSpellClickOn(Creature const* c) const +{ + if (!c->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK)) + return false; + + SpellClickInfoMapBounds clickPair = sObjectMgr.GetSpellClickInfoMapBounds(c->GetEntry()); + for (SpellClickInfoMap::const_iterator itr = clickPair.first; itr != clickPair.second; ++itr) + if (itr->second.IsFitToRequirements(this, c)) + return true; + + return false; +} + +void Player::BuildPlayerTalentsInfoData(WorldPacket* data) +{ + *data << uint32(GetFreeTalentPoints()); // unspentTalentPoints + *data << uint8(m_specsCount); // talent group count (0, 1 or 2) + *data << uint8(m_activeSpec); // talent group index (0 or 1) + + if (m_specsCount) + { + if (m_specsCount > MAX_TALENT_SPEC_COUNT) + m_specsCount = MAX_TALENT_SPEC_COUNT; + + // loop through all specs (only 1 for now) + for (uint32 specIdx = 0; specIdx < m_specsCount; ++specIdx) + { + *data << uint32(m_talentsPrimaryTree[specIdx]); + uint8 talentIdCount = 0; + size_t pos = data->wpos(); + *data << uint8(talentIdCount); // [PH], talentIdCount + + // find class talent tabs (all players have 3 talent tabs) + uint32 const* talentTabIds = GetTalentTabPages(getClass()); + + for (uint32 i = 0; i < MAX_TALENT_TABS; ++i) + { + uint32 talentTabId = talentTabIds[i]; + for (PlayerTalentMap::iterator iter = m_talents[specIdx].begin(); iter != m_talents[specIdx].end(); ++iter) + { + PlayerTalent talent = (*iter).second; + + if (talent.state == PLAYERSPELL_REMOVED) + continue; + + // skip another tab talents + if (talent.talentEntry->TalentTab != talentTabId) + continue; + + *data << uint32(talent.talentEntry->TalentID); // Talent.dbc + *data << uint8(talent.currentRank); // talentMaxRank (0-4) + + ++talentIdCount; + } + } + + data->put(pos, talentIdCount); // put real count + + *data << uint8(MAX_GLYPH_SLOT_INDEX); // glyphs count + + // GlyphProperties.dbc + for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i) + *data << uint16(m_glyphs[specIdx][i].GetId()); + } + } +} + +void Player::BuildPetTalentsInfoData(WorldPacket* data) +{ + uint32 unspentTalentPoints = 0; + size_t pointsPos = data->wpos(); + *data << uint32(unspentTalentPoints); // [PH], unspentTalentPoints + + uint8 talentIdCount = 0; + size_t countPos = data->wpos(); + *data << uint8(talentIdCount); // [PH], talentIdCount + + Pet* pet = GetPet(); + if (!pet) + return; + + unspentTalentPoints = pet->GetFreeTalentPoints(); + + data->put(pointsPos, unspentTalentPoints); // put real points + + CreatureInfo const* ci = pet->GetCreatureInfo(); + if (!ci) + return; + + CreatureFamilyEntry const* pet_family = sCreatureFamilyStore.LookupEntry(ci->family); + if (!pet_family || pet_family->petTalentType < 0) + return; + + for (uint32 talentTabId = 1; talentTabId < sTalentTabStore.GetNumRows(); ++talentTabId) + { + TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentTabId); + if (!talentTabInfo) + continue; + + if (!((1 << pet_family->petTalentType) & talentTabInfo->petTalentMask)) + continue; + + for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) + { + TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId); + if (!talentInfo) + continue; + + // skip another tab talents + if (talentInfo->TalentTab != talentTabId) + continue; + + // find max talent rank + int32 curtalent_maxrank = -1; + for (int32 k = 4; k > -1; --k) + { + if (talentInfo->RankID[k] && pet->HasSpell(talentInfo->RankID[k])) + { + curtalent_maxrank = k; + break; + } + } + + // not learned talent + if (curtalent_maxrank < 0) + continue; + + *data << uint32(talentInfo->TalentID); // Talent.dbc + *data << uint8(curtalent_maxrank); // talentMaxRank (0-4) + + ++talentIdCount; + } + + data->put(countPos, talentIdCount); // put real count + + break; + } +} + +void Player::SendTalentsInfoData(bool pet) +{ + WorldPacket data(SMSG_TALENT_UPDATE, 50); + data << uint8(pet ? 1 : 0); + if (pet) + BuildPetTalentsInfoData(&data); + else + BuildPlayerTalentsInfoData(&data); + GetSession()->SendPacket(&data); +} + +void Player::BuildEnchantmentsInfoData(WorldPacket* data) +{ + uint32 slotUsedMask = 0; + size_t slotUsedMaskPos = data->wpos(); + *data << uint32(slotUsedMask); // slotUsedMask < 0x80000 + + for (uint32 i = 0; i < EQUIPMENT_SLOT_END; ++i) + { + Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + + if (!item) + continue; + + slotUsedMask |= (1 << i); + + *data << uint32(item->GetEntry()); // item entry + + uint16 enchantmentMask = 0; + size_t enchantmentMaskPos = data->wpos(); + *data << uint16(enchantmentMask); // enchantmentMask < 0x1000 + + for (uint32 j = 0; j < MAX_ENCHANTMENT_SLOT; ++j) + { + uint32 enchId = item->GetEnchantmentId(EnchantmentSlot(j)); + + if (!enchId) + continue; + + enchantmentMask |= (1 << j); + + *data << uint16(enchId); // enchantmentId? + } + + data->put(enchantmentMaskPos, enchantmentMask); + + *data << uint16(item->GetItemRandomPropertyId()); + *data << item->GetGuidValue(ITEM_FIELD_CREATOR).WriteAsPacked(); + *data << uint32(item->GetItemSuffixFactor()); + } + + data->put(slotUsedMaskPos, slotUsedMask); +} + +void Player::SendEquipmentSetList() +{ + uint32 count = 0; + WorldPacket data(SMSG_LOAD_EQUIPMENT_SET, 4); + size_t count_pos = data.wpos(); + data << uint32(count); // count placeholder + for (EquipmentSets::iterator itr = m_EquipmentSets.begin(); itr != m_EquipmentSets.end(); ++itr) + { + if (itr->second.state == EQUIPMENT_SET_DELETED) + continue; + data.appendPackGUID(itr->second.Guid); + data << uint32(itr->first); + data << itr->second.Name; + data << itr->second.IconName; + for (uint32 i = 0; i < EQUIPMENT_SLOT_END; ++i) + { + // ignored slots stored in IgnoreMask, client wants "1" as raw GUID, so no HIGHGUID_ITEM + if (itr->second.IgnoreMask & (1 << i)) + data << ObjectGuid(uint64(1)).WriteAsPacked(); + else + data << ObjectGuid(HIGHGUID_ITEM, itr->second.Items[i]).WriteAsPacked(); + } + + ++count; // client have limit but it checked at loading and set + } + data.put(count_pos, count); + GetSession()->SendPacket(&data); +} + +void Player::SetEquipmentSet(uint32 index, EquipmentSet eqset) +{ + if (eqset.Guid != 0) + { + bool found = false; + + for (EquipmentSets::iterator itr = m_EquipmentSets.begin(); itr != m_EquipmentSets.end(); ++itr) + { + if ((itr->second.Guid == eqset.Guid) && (itr->first == index)) + { + found = true; + break; + } + } + + if (!found) // something wrong... + { + sLog.outError("Player %s tried to save equipment set " UI64FMTD " (index %u), but that equipment set not found!", GetName(), eqset.Guid, index); + return; + } + } + + EquipmentSet& eqslot = m_EquipmentSets[index]; + + EquipmentSetUpdateState old_state = eqslot.state; + + eqslot = eqset; + + if (eqset.Guid == 0) + { + eqslot.Guid = sObjectMgr.GenerateEquipmentSetGuid(); + + WorldPacket data(SMSG_EQUIPMENT_SET_ID, 4 + 1); + data << uint32(index); + data.appendPackGUID(eqslot.Guid); + GetSession()->SendPacket(&data); + } + + eqslot.state = old_state == EQUIPMENT_SET_NEW ? EQUIPMENT_SET_NEW : EQUIPMENT_SET_CHANGED; +} + +void Player::_SaveEquipmentSets() +{ + static SqlStatementID updSets ; + static SqlStatementID insSets ; + static SqlStatementID delSets ; + + for (EquipmentSets::iterator itr = m_EquipmentSets.begin(); itr != m_EquipmentSets.end();) + { + uint32 index = itr->first; + EquipmentSet& eqset = itr->second; + switch (eqset.state) + { + case EQUIPMENT_SET_UNCHANGED: + ++itr; + break; // nothing do + case EQUIPMENT_SET_CHANGED: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(updSets, "UPDATE character_equipmentsets SET name=?, iconname=?, ignore_mask=?, item0=?, item1=?, item2=?, item3=?, item4=?, " + "item5=?, item6=?, item7=?, item8=?, item9=?, item10=?, item11=?, item12=?, item13=?, item14=?, " + "item15=?, item16=?, item17=?, item18=? WHERE guid=? AND setguid=? AND setindex=?"); + + stmt.addString(eqset.Name); + stmt.addString(eqset.IconName); + stmt.addUInt32(eqset.IgnoreMask); + + for (int i = 0; i < EQUIPMENT_SLOT_END; ++i) + stmt.addUInt32(eqset.Items[i]); + + stmt.addUInt32(GetGUIDLow()); + stmt.addUInt64(eqset.Guid); + stmt.addUInt32(index); + + stmt.Execute(); + + eqset.state = EQUIPMENT_SET_UNCHANGED; + ++itr; + break; + } + case EQUIPMENT_SET_NEW: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(insSets, "INSERT INTO character_equipmentsets VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + stmt.addUInt32(GetGUIDLow()); + stmt.addUInt64(eqset.Guid); + stmt.addUInt32(index); + stmt.addString(eqset.Name); + stmt.addString(eqset.IconName); + stmt.addUInt32(eqset.IgnoreMask); + + for (int i = 0; i < EQUIPMENT_SLOT_END; ++i) + stmt.addUInt32(eqset.Items[i]); + + stmt.Execute(); + + eqset.state = EQUIPMENT_SET_UNCHANGED; + ++itr; + break; + } + case EQUIPMENT_SET_DELETED: + { + SqlStatement stmt = CharacterDatabase.CreateStatement(delSets, "DELETE FROM character_equipmentsets WHERE setguid = ?"); + stmt.PExecute(eqset.Guid); + m_EquipmentSets.erase(itr++); + break; + } + } + } +} + +void Player::_SaveBGData() +{ + // nothing save + if (!m_bgData.m_needSave) + return; + + static SqlStatementID delBGData ; + static SqlStatementID insBGData ; + + SqlStatement stmt = CharacterDatabase.CreateStatement(delBGData, "DELETE FROM character_battleground_data WHERE guid = ?"); + + stmt.PExecute(GetGUIDLow()); + + if (m_bgData.bgInstanceID) + { + stmt = CharacterDatabase.CreateStatement(insBGData, "INSERT INTO character_battleground_data VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + /* guid, bgInstanceID, bgTeam, x, y, z, o, map, taxi[0], taxi[1], mountSpell */ + stmt.addUInt32(GetGUIDLow()); + stmt.addUInt32(m_bgData.bgInstanceID); + stmt.addUInt32(uint32(m_bgData.bgTeam)); + stmt.addFloat(m_bgData.joinPos.coord_x); + stmt.addFloat(m_bgData.joinPos.coord_y); + stmt.addFloat(m_bgData.joinPos.coord_z); + stmt.addFloat(m_bgData.joinPos.orientation); + stmt.addUInt32(m_bgData.joinPos.mapid); + stmt.addUInt32(m_bgData.taxiPath[0]); + stmt.addUInt32(m_bgData.taxiPath[1]); + stmt.addUInt32(m_bgData.mountSpell); + + stmt.Execute(); + } + + m_bgData.m_needSave = false; +} + +void Player::DeleteEquipmentSet(uint64 setGuid) +{ + for (EquipmentSets::iterator itr = m_EquipmentSets.begin(); itr != m_EquipmentSets.end(); ++itr) + { + if (itr->second.Guid == setGuid) + { + if (itr->second.state == EQUIPMENT_SET_NEW) + m_EquipmentSets.erase(itr); + else + itr->second.state = EQUIPMENT_SET_DELETED; + break; + } + } +} + +void Player::ActivateSpec(uint8 specNum) +{ + if (GetActiveSpec() == specNum) + return; + + if (specNum >= GetSpecsCount()) + return; + + UnsummonPetTemporaryIfAny(); + + // prevent deletion of action buttons by client at spell unlearn or by player while spec change in progress + SendLockActionButtons(); + + // Remove spec specific spells + for (uint32 i = 0; i < MAX_TALENT_TABS; ++i) + { + if (std::vector const* specSpells = GetTalentTreeMasterySpells(GetTalentTabPages(getClass())[i])) + for (size_t i = 0; i < specSpells->size(); ++i) + removeSpell(specSpells->at(i), true); + + if (std::vector const* specSpells = GetTalentTreePrimarySpells(GetTalentTabPages(getClass())[i])) + for (size_t i = 0; i < specSpells->size(); ++i) + removeSpell(specSpells->at(i), true); + } + + ApplyGlyphs(false); + + // copy of new talent spec (we will use it as model for converting current tlanet state to new) + PlayerTalentMap tempSpec = m_talents[specNum]; + + // copy old spec talents to new one, must be before spec switch to have previous spec num(as m_activeSpec) + m_talents[specNum] = m_talents[m_activeSpec]; + + SetActiveSpec(specNum); + + // remove all talent spells that don't exist in next spec but exist in old + for (PlayerTalentMap::iterator specIter = m_talents[m_activeSpec].begin(); specIter != m_talents[m_activeSpec].end();) + { + PlayerTalent& talent = specIter->second; + + if (talent.state == PLAYERSPELL_REMOVED) + { + ++specIter; + continue; + } + + PlayerTalentMap::iterator iterTempSpec = tempSpec.find(specIter->first); + + // remove any talent rank if talent not listed in temp spec + if (iterTempSpec == tempSpec.end() || iterTempSpec->second.state == PLAYERSPELL_REMOVED) + { + TalentEntry const* talentInfo = talent.talentEntry; + + for (int r = 0; r < MAX_TALENT_RANK; ++r) + if (talentInfo->RankID[r]) + removeSpell(talentInfo->RankID[r], !IsPassiveSpell(talentInfo->RankID[r]), false); + + specIter = m_talents[m_activeSpec].begin(); + } + else + ++specIter; + } + + // now new spec data have only talents (maybe different rank) as in temp spec data, sync ranks then. + for (PlayerTalentMap::const_iterator tempIter = tempSpec.begin(); tempIter != tempSpec.end(); ++tempIter) + { + PlayerTalent const& talent = tempIter->second; + + // removed state talent already unlearned in prev. loop + // but we need restore it if it deleted for finish removed-marked data in DB + if (talent.state == PLAYERSPELL_REMOVED) + { + m_talents[m_activeSpec][tempIter->first] = talent; + continue; + } + + uint32 talentSpellId = talent.talentEntry->RankID[talent.currentRank]; + + // learn talent spells if they not in new spec (old spec copy) + // and if they have different rank + if (PlayerTalent const* cur_talent = GetKnownTalentById(tempIter->first)) + { + if (cur_talent->currentRank != talent.currentRank) + learnSpell(talentSpellId, false); + } + else + learnSpell(talentSpellId, false); + + // sync states - original state is changed in addSpell that learnSpell calls + PlayerTalentMap::iterator specIter = m_talents[m_activeSpec].find(tempIter->first); + if (specIter != m_talents[m_activeSpec].end()) + specIter->second.state = talent.state; + else + { + sLog.outError("ActivateSpec: Talent spell %u expected to learned at spec switch but not listed in talents at final check!", talentSpellId); + + // attempt resync DB state (deleted lost spell from DB) + if (talent.state != PLAYERSPELL_NEW) + { + PlayerTalent& talentNew = m_talents[m_activeSpec][tempIter->first]; + talentNew = talent; + talentNew.state = PLAYERSPELL_REMOVED; + } + } + } + + InitTalentForLevel(); + + // recheck action buttons (not checked at loading/spec copy) + ActionButtonList const& currentActionButtonList = m_actionButtons[m_activeSpec]; + for (ActionButtonList::const_iterator itr = currentActionButtonList.begin(); itr != currentActionButtonList.end();) + { + if (itr->second.uState != ACTIONBUTTON_DELETED) + { + // remove broken without any output (it can be not correct because talents not copied at spec creating) + if (!IsActionButtonDataValid(itr->first, itr->second.GetAction(), itr->second.GetType(), this, false)) + { + removeActionButton(m_activeSpec, itr->first); + itr = currentActionButtonList.begin(); + continue; + } + } + ++itr; + } + + ResummonPetTemporaryUnSummonedIfAny(); + + if (std::vector const* specSpells = GetTalentTreeMasterySpells(m_talentsPrimaryTree[m_activeSpec])) + for (size_t i = 0; i < specSpells->size(); ++i) + learnSpell(specSpells->at(i), false); + + if (std::vector const* specSpells = GetTalentTreePrimarySpells(m_talentsPrimaryTree[m_activeSpec])) + for (size_t i = 0; i < specSpells->size(); ++i) + learnSpell(specSpells->at(i), false); + + ApplyGlyphs(true); + + SendInitialActionButtons(); + + Powers pw = getPowerType(); + if (pw != POWER_MANA) + SetPower(POWER_MANA, 0); + + SetPower(pw, 0); + + if (m_talentsPrimaryTree[m_activeSpec] && !sTalentTabStore.LookupEntry(m_talentsPrimaryTree[m_activeSpec])) + resetTalents(true); + + // Update talent tree role-dependent mana regen + UpdateManaRegen(); + + UpdateArmorSpecializations(); +} + +void Player::UpdateSpecCount(uint8 count) +{ + uint8 curCount = GetSpecsCount(); + if (curCount == count) + return; + + // maybe current spec data must be copied to 0 spec? + if (m_activeSpec >= count) + ActivateSpec(0); + + // copy spec data from new specs + if (count > curCount) + { + // copy action buttons from active spec (more easy in this case iterate first by button) + ActionButtonList const& currentActionButtonList = m_actionButtons[m_activeSpec]; + + for (ActionButtonList::const_iterator itr = currentActionButtonList.begin(); itr != currentActionButtonList.end(); ++itr) + { + if (itr->second.uState != ACTIONBUTTON_DELETED) + { + for (uint8 spec = curCount; spec < count; ++spec) + addActionButton(spec, itr->first, itr->second.GetAction(), itr->second.GetType()); + } + } + } + // delete spec data for removed specs + else if (count < curCount) + { + // delete action buttons for removed spec + for (uint8 spec = count; spec < curCount; ++spec) + { + // delete action buttons for removed spec + for (uint8 button = 0; button < MAX_ACTION_BUTTONS; ++button) + removeActionButton(spec, button); + } + } + + SetSpecsCount(count); + + SendTalentsInfoData(false); +} + +void Player::RemoveAtLoginFlag(AtLoginFlags f, bool in_db_also /*= false*/) +{ + m_atLoginFlags &= ~f; + + if (in_db_also) + CharacterDatabase.PExecute("UPDATE characters set at_login = at_login & ~ %u WHERE guid ='%u'", uint32(f), GetGUIDLow()); +} + +void Player::SendClearCooldown(uint32 spell_id, Unit* target) +{ + ObjectGuid guid = target->GetObjectGuid(); + + WorldPacket data(SMSG_CLEAR_COOLDOWNS, 1 + 8 + 4); + data.WriteGuidMask<1, 3, 6>(guid); + data.WriteBits(1, 24); // cooldown count + data.WriteGuidMask<7, 5, 2, 4, 0>(guid); + + data.WriteGuidBytes<7, 2, 4, 5, 1, 3>(guid); + data << uint32(spell_id); + data.WriteGuidBytes<0, 6>(guid); + + SendDirectMessage(&data); +} + +bool Player::HasMovementFlag(MovementFlags f) const +{ + return m_movementInfo.HasMovementFlag(f); +} + +void Player::ResetTimeSync() +{ + m_timeSyncCounter = 0; + m_timeSyncTimer = 0; + m_timeSyncClient = 0; + m_timeSyncServer = WorldTimer::getMSTime(); +} + +void Player::SendTimeSync() +{ + WorldPacket data(SMSG_TIME_SYNC_REQ, 4); + data << uint32(m_timeSyncCounter++); + GetSession()->SendPacket(&data); + + // Schedule next sync in 10 sec + m_timeSyncTimer = 10000; + m_timeSyncServer = WorldTimer::getMSTime(); +} + +void Player::SendDuelCountdown(uint32 counter) +{ + WorldPacket data(SMSG_DUEL_COUNTDOWN, 4); + data << uint32(counter); // seconds + GetSession()->SendPacket(&data); +} + +bool Player::IsImmuneToSpellEffect(SpellEntry const* spellInfo, SpellEffectIndex index, bool castOnSelf) const +{ + SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(index); + if(spellEffect) + { + switch(spellEffect->Effect) + { + case SPELL_EFFECT_ATTACK_ME: + return true; + default: + break; + } + switch(spellEffect->EffectApplyAuraName) + { + case SPELL_AURA_MOD_TAUNT: + return true; + default: + break; + } + } + + return Unit::IsImmuneToSpellEffect(spellInfo, index, castOnSelf); +} + +void Player::SetHomebindToLocation(WorldLocation const& loc, uint32 area_id) +{ + m_homebindMapId = loc.mapid; + m_homebindAreaId = area_id; + m_homebindX = loc.coord_x; + m_homebindY = loc.coord_y; + m_homebindZ = loc.coord_z; + + // update sql homebind + CharacterDatabase.PExecute("UPDATE character_homebind SET map = '%u', zone = '%u', position_x = '%f', position_y = '%f', position_z = '%f' WHERE guid = '%u'", + m_homebindMapId, m_homebindAreaId, m_homebindX, m_homebindY, m_homebindZ, GetGUIDLow()); +} + +Object* Player::GetObjectByTypeMask(ObjectGuid guid, TypeMask typemask) +{ + switch (guid.GetHigh()) + { + case HIGHGUID_ITEM: + if (typemask & TYPEMASK_ITEM) + return GetItemByGuid(guid); + break; + case HIGHGUID_PLAYER: + if (GetObjectGuid() == guid) + return this; + if ((typemask & TYPEMASK_PLAYER) && IsInWorld()) + return ObjectAccessor::FindPlayer(guid); + break; + case HIGHGUID_GAMEOBJECT: + if ((typemask & TYPEMASK_GAMEOBJECT) && IsInWorld()) + return GetMap()->GetGameObject(guid); + break; + case HIGHGUID_UNIT: + case HIGHGUID_VEHICLE: + if ((typemask & TYPEMASK_UNIT) && IsInWorld()) + return GetMap()->GetCreature(guid); + break; + case HIGHGUID_PET: + if ((typemask & TYPEMASK_UNIT) && IsInWorld()) + return GetMap()->GetPet(guid); + break; + case HIGHGUID_DYNAMICOBJECT: + if ((typemask & TYPEMASK_DYNAMICOBJECT) && IsInWorld()) + return GetMap()->GetDynamicObject(guid); + break; + case HIGHGUID_TRANSPORT: + case HIGHGUID_CORPSE: + case HIGHGUID_MO_TRANSPORT: + case HIGHGUID_INSTANCE: + case HIGHGUID_GROUP: + default: + break; + } + + return NULL; +} + +void Player::SetRestType(RestType n_r_type, uint32 areaTriggerId /*= 0*/) +{ + rest_type = n_r_type; + + if (rest_type == REST_TYPE_NO) + { + RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING); + + // Set player to FFA PVP when not in rested environment. + if (sWorld.IsFFAPvPRealm()) + SetFFAPvP(true); + } + else + { + SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING); + + inn_trigger_id = areaTriggerId; + time_inn_enter = time(NULL); + + if (sWorld.IsFFAPvPRealm()) + SetFFAPvP(false); + } +} + +uint32 Player::GetEquipGearScore(bool withBags, bool withBank) +{ + if (withBags && withBank && m_cachedGS > 0) + return m_cachedGS; + + GearScoreVec gearScore(EQUIPMENT_SLOT_END); + uint32 twoHandScore = 0; + + for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + { + if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + _fillGearScoreData(item, &gearScore, twoHandScore); + } + + if (withBags) + { + // check inventory + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + _fillGearScoreData(item, &gearScore, twoHandScore); + } + + // check bags + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + if (Item* item2 = pBag->GetItemByPos(j)) + _fillGearScoreData(item2, &gearScore, twoHandScore); + } + } + } + } + + if (withBank) + { + for (uint8 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i) + { + if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + _fillGearScoreData(item, &gearScore, twoHandScore); + } + + for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + { + if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (item->IsBag()) + { + Bag* bag = (Bag*)item; + for (uint8 j = 0; j < bag->GetBagSize(); ++j) + { + if (Item* item2 = bag->GetItemByPos(j)) + _fillGearScoreData(item2, &gearScore, twoHandScore); + } + } + } + } + } + + uint8 count = EQUIPMENT_SLOT_END - 2; // ignore body and tabard slots + uint32 sum = 0; + + // check if 2h hand is higher level than main hand + off hand + if (gearScore[EQUIPMENT_SLOT_MAINHAND] + gearScore[EQUIPMENT_SLOT_OFFHAND] < twoHandScore * 2) + { + gearScore[EQUIPMENT_SLOT_OFFHAND] = 0; // off hand is ignored in calculations if 2h weapon has higher score + --count; + gearScore[EQUIPMENT_SLOT_MAINHAND] = twoHandScore; + } + + for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + { + sum += gearScore[i]; + } + + if (count) + { + uint32 res = uint32(sum / count); + DEBUG_LOG("Player: calculating gear score for %u. Result is %u", GetObjectGuid().GetCounter(), res); + + if (withBags && withBank) + m_cachedGS = res; + + return res; + } + else + return 0; +} + +void Player::_fillGearScoreData(Item* item, GearScoreVec* gearScore, uint32& twoHandScore) +{ + if (!item) + return; + + if (CanUseItem(item->GetProto()) != EQUIP_ERR_OK) + return; + + uint8 type = item->GetProto()->InventoryType; + uint32 level = item->GetProto()->ItemLevel; + + switch (type) + { + case INVTYPE_2HWEAPON: + twoHandScore = std::max(twoHandScore, level); + break; + case INVTYPE_WEAPON: + case INVTYPE_WEAPONMAINHAND: + (*gearScore)[EQUIPMENT_SLOT_MAINHAND] = std::max((*gearScore)[EQUIPMENT_SLOT_MAINHAND], level); + break; + case INVTYPE_SHIELD: + case INVTYPE_WEAPONOFFHAND: + (*gearScore)[EQUIPMENT_SLOT_OFFHAND] = std::max((*gearScore)[EQUIPMENT_SLOT_OFFHAND], level); + break; + case INVTYPE_THROWN: + case INVTYPE_RANGEDRIGHT: + case INVTYPE_RANGED: + case INVTYPE_QUIVER: + case INVTYPE_RELIC: + (*gearScore)[EQUIPMENT_SLOT_RANGED] = std::max((*gearScore)[EQUIPMENT_SLOT_RANGED], level); + break; + case INVTYPE_HEAD: + (*gearScore)[EQUIPMENT_SLOT_HEAD] = std::max((*gearScore)[EQUIPMENT_SLOT_HEAD], level); + break; + case INVTYPE_NECK: + (*gearScore)[EQUIPMENT_SLOT_NECK] = std::max((*gearScore)[EQUIPMENT_SLOT_NECK], level); + break; + case INVTYPE_SHOULDERS: + (*gearScore)[EQUIPMENT_SLOT_SHOULDERS] = std::max((*gearScore)[EQUIPMENT_SLOT_SHOULDERS], level); + break; + case INVTYPE_BODY: + (*gearScore)[EQUIPMENT_SLOT_BODY] = std::max((*gearScore)[EQUIPMENT_SLOT_BODY], level); + break; + case INVTYPE_CHEST: + (*gearScore)[EQUIPMENT_SLOT_CHEST] = std::max((*gearScore)[EQUIPMENT_SLOT_CHEST], level); + break; + case INVTYPE_WAIST: + (*gearScore)[EQUIPMENT_SLOT_WAIST] = std::max((*gearScore)[EQUIPMENT_SLOT_WAIST], level); + break; + case INVTYPE_LEGS: + (*gearScore)[EQUIPMENT_SLOT_LEGS] = std::max((*gearScore)[EQUIPMENT_SLOT_LEGS], level); + break; + case INVTYPE_FEET: + (*gearScore)[EQUIPMENT_SLOT_FEET] = std::max((*gearScore)[EQUIPMENT_SLOT_FEET], level); + break; + case INVTYPE_WRISTS: + (*gearScore)[EQUIPMENT_SLOT_WRISTS] = std::max((*gearScore)[EQUIPMENT_SLOT_WRISTS], level); + break; + case INVTYPE_HANDS: + (*gearScore)[EQUIPMENT_SLOT_HEAD] = std::max((*gearScore)[EQUIPMENT_SLOT_HEAD], level); + break; + // equipped gear score check uses both rings and trinkets for calculation, assume that for bags/banks it is the same + // with keeping second highest score at second slot + case INVTYPE_FINGER: + { + if ((*gearScore)[EQUIPMENT_SLOT_FINGER1] < level) + { + (*gearScore)[EQUIPMENT_SLOT_FINGER2] = (*gearScore)[EQUIPMENT_SLOT_FINGER1]; + (*gearScore)[EQUIPMENT_SLOT_FINGER1] = level; + } + else if ((*gearScore)[EQUIPMENT_SLOT_FINGER2] < level) + (*gearScore)[EQUIPMENT_SLOT_FINGER2] = level; + break; + } + case INVTYPE_TRINKET: + { + if ((*gearScore)[EQUIPMENT_SLOT_TRINKET1] < level) + { + (*gearScore)[EQUIPMENT_SLOT_TRINKET2] = (*gearScore)[EQUIPMENT_SLOT_TRINKET1]; + (*gearScore)[EQUIPMENT_SLOT_TRINKET1] = level; + } + else if ((*gearScore)[EQUIPMENT_SLOT_TRINKET2] < level) + (*gearScore)[EQUIPMENT_SLOT_TRINKET2] = level; + break; + } + case INVTYPE_CLOAK: + (*gearScore)[EQUIPMENT_SLOT_BACK] = std::max((*gearScore)[EQUIPMENT_SLOT_BACK], level); + break; + default: + break; + } +} + +void Player::SendCurrencies() const +{ + WorldPacket data(SMSG_SEND_CURRENCIES, m_currencies.size() * 4); + data.WriteBits(m_currencies.size(), 23); + + for (PlayerCurrenciesMap::const_iterator itr = m_currencies.begin(); itr != m_currencies.end(); ++itr) + { + uint32 weekCap = GetCurrencyWeekCap(itr->second.currencyEntry); + data.WriteBit(weekCap && itr->second.weekCount); + data.WriteBits(itr->second.flags, 4); + data.WriteBit(weekCap); + data.WriteBit(itr->second.currencyEntry->HasSeasonCount()); + } + + for (PlayerCurrenciesMap::const_iterator itr = m_currencies.begin(); itr != m_currencies.end(); ++itr) + { + data << uint32(floor(itr->second.totalCount / itr->second.currencyEntry->GetPrecision())); + + uint32 weekCap = GetCurrencyWeekCap(itr->second.currencyEntry); + if (weekCap) + data << uint32(floor(weekCap / itr->second.currencyEntry->GetPrecision())); + if (itr->second.currencyEntry->HasSeasonCount()) + data << uint32(floor(itr->second.seasonCount / itr->second.currencyEntry->GetPrecision())); + data << uint32(itr->first); + if (weekCap && itr->second.weekCount) + data << uint32(floor(itr->second.weekCount / itr->second.currencyEntry->GetPrecision())); + } + + GetSession()->SendPacket(&data); +} + +uint32 Player::GetCurrencyWeekCap(CurrencyTypesEntry const * currency) const +{ + uint32 cap = currency->WeekCap; + switch (currency->ID) + { + case CURRENCY_CONQUEST_POINTS: + cap = sWorld.getConfig(CONFIG_UINT32_CURRENCY_CONQUEST_POINTS_DEFAULT_WEEK_CAP); + break; + } + + return cap; +} + +void Player::SendCurrencyWeekCap(uint32 id) const +{ + SendCurrencyWeekCap(sCurrencyTypesStore.LookupEntry(id)); +} + +void Player::SendCurrencyWeekCap(CurrencyTypesEntry const * currency) const +{ + if (!currency || !IsInWorld() || GetSession()->PlayerLoading()) + return; + + uint32 cap = GetCurrencyWeekCap(currency); + if (!cap) + return; + + WorldPacket packet(SMSG_SET_CURRENCY_WEEK_LIMIT, 8); + packet << uint32(floor(cap / currency->GetPrecision())); + packet << uint32(currency->ID); + GetSession()->SendPacket(&packet); +} + +uint32 Player::GetCurrencyTotalCap(CurrencyTypesEntry const* currency) const +{ + uint32 cap = currency->TotalCap; + + return cap; +} + +uint32 Player::GetCurrencyCount(uint32 id) const +{ + PlayerCurrenciesMap::const_iterator itr = m_currencies.find(id); + return itr != m_currencies.end() ? itr->second.totalCount : 0; +} + +uint32 Player::GetCurrencySeasonCount(uint32 id) const +{ + PlayerCurrenciesMap::const_iterator itr = m_currencies.find(id); + return itr != m_currencies.end() ? itr->second.seasonCount : 0; +} + +uint32 Player::GetCurrencyWeekCount(uint32 id) const +{ + PlayerCurrenciesMap::const_iterator itr = m_currencies.find(id); + return itr != m_currencies.end() ? itr->second.weekCount : 0; +} + +void Player::ModifyCurrencyCount(uint32 id, int32 count, bool modifyWeek, bool modifySeason, bool ignoreMultipliers) +{ + if (!count) + return; + + if (!ignoreMultipliers && count > 0) + count *= GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_CURRENCY_GAIN, id); + + CurrencyTypesEntry const * currency = NULL; + + int32 oldTotalCount = 0; + int32 oldWeekCount = 0; + PlayerCurrenciesMap::iterator itr = m_currencies.find(id); + + bool initWeek = false; + if (itr == m_currencies.end()) + { + currency = sCurrencyTypesStore.LookupEntry(id); + MANGOS_ASSERT(currency); + + PlayerCurrency cur; + cur.state = PLAYERCURRENCY_NEW; + cur.totalCount = 0; + cur.weekCount = 0; + cur.seasonCount = 0; + cur.flags = 0; + cur.currencyEntry = currency; + m_currencies[id] = cur; + initWeek = true; + itr = m_currencies.find(id); + } + else + { + oldTotalCount = itr->second.totalCount; + oldWeekCount = itr->second.weekCount; + currency = itr->second.currencyEntry; + } + + int32 newTotalCount = oldTotalCount + count; + if (newTotalCount < 0) + newTotalCount = 0; + + int32 newWeekCount = oldWeekCount + (modifyWeek && count > 0 ? count : 0); + if (newWeekCount < 0) + newWeekCount = 0; + + int32 totalCap = GetCurrencyTotalCap(currency); + if (totalCap && totalCap < newTotalCount) + { + int32 delta = newTotalCount - totalCap; + newTotalCount = totalCap; + newWeekCount -= delta; + } + + int32 weekCap = GetCurrencyWeekCap(currency); + if (modifyWeek && weekCap && newWeekCount > weekCap) + { + int32 delta = newWeekCount - weekCap; + newWeekCount = weekCap; + newTotalCount -= delta; + } + initWeek &= weekCap != currency->WeekCap; + + if (newTotalCount != oldTotalCount) + { + if (itr->second.state != PLAYERCURRENCY_NEW) + itr->second.state = PLAYERCURRENCY_CHANGED; + + itr->second.totalCount = newTotalCount; + itr->second.weekCount = newWeekCount; + + int32 diff = newTotalCount - oldTotalCount; + if (diff > 0 && modifySeason) + itr->second.seasonCount += diff; + + // probably excessive checks + if (IsInWorld() && !GetSession()->PlayerLoading()) + { + if (diff > 0) + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_CURRENCY_EARNED, id, newTotalCount); + + WorldPacket packet(SMSG_SET_CURRENCY, 13); + bool bit0 = modifyWeek && weekCap && diff > 0; + bool bit1 = currency->HasSeasonCount(); + bool bit2 = currency->Category == CURRENCY_CATEGORY_META; // hides message in client when set + packet.WriteBit(bit0); + packet.WriteBit(bit1); + packet.WriteBit(bit2); + + if (bit1) + packet << uint32(floor(itr->second.seasonCount / currency->GetPrecision())); + packet << uint32(floor(newTotalCount / currency->GetPrecision())); + packet << uint32(id); + if (bit0) + packet << uint32(floor(newWeekCount / currency->GetPrecision())); + GetSession()->SendPacket(&packet); + + // init currency week limit for new currencies + if (initWeek) + SendCurrencyWeekCap(currency); + + if (diff > 0) + CurrencyAddedQuestCheck(id); + else + CurrencyRemovedQuestCheck(id); + } + + if (itr->first == CURRENCY_CONQUEST_ARENA_META || itr->first == CURRENCY_CONQUEST_BG_META) + ModifyCurrencyCount(CURRENCY_CONQUEST_POINTS, diff, modifyWeek, modifySeason, ignoreMultipliers); + } +} + +void Player::SetCurrencyCount(uint32 id, uint32 count) +{ + ModifyCurrencyCount(id, int32(count) - GetCurrencyCount(id), false, false, true); +} + +void Player::_LoadCurrencies(QueryResult* result) +{ + // 0 1 2 4 5 + // "SELECT id, totalCount, weekCount, seasonCount, flags FROM character_currencies WHERE guid = '%u'" + + if (result) + { + do + { + Field* fields = result->Fetch(); + + uint32 currency_id = fields[0].GetUInt16(); + uint32 totalCount = fields[1].GetUInt32(); + uint32 weekCount = fields[2].GetUInt32(); + uint32 seasonCount = fields[3].GetUInt32(); + uint8 flags = fields[4].GetUInt8(); + + CurrencyTypesEntry const * entry = sCurrencyTypesStore.LookupEntry(currency_id); + if (!entry) + { + sLog.outError("Player::_LoadCurrencies: %s has not existing currency id %u, removing.", GetGuidStr().c_str(), currency_id); + CharacterDatabase.PExecute("DELETE FROM character_currencies WHERE id = '%u'", currency_id); + continue; + } + + uint32 weekCap = GetCurrencyWeekCap(entry); + uint32 totalCap = GetCurrencyTotalCap(entry); + + PlayerCurrency cur; + + cur.state = PLAYERCURRENCY_UNCHANGED; + + if (totalCap && totalCount > totalCap) + cur.totalCount = totalCap; + else + cur.totalCount = totalCount; + + if (weekCap && weekCount > weekCap) + cur.weekCount = weekCap; + else + cur.weekCount = weekCount; + + cur.seasonCount = seasonCount; + + cur.flags = flags & PLAYERCURRENCY_MASK_USED_BY_CLIENT; + cur.currencyEntry = entry; + + m_currencies[currency_id] = cur; + } + while (result->NextRow()); + } +} + +void Player::_SaveCurrencies() +{ + for (PlayerCurrenciesMap::iterator itr = m_currencies.begin(); itr != m_currencies.end();) + { + if (itr->second.state == PLAYERCURRENCY_CHANGED) + CharacterDatabase.PExecute("UPDATE `character_currencies` SET `totalCount` = '%u', `weekCount` = '%u', `seasonCount` = '%u', `flags` = '%u' WHERE `guid` = '%u' AND `id` = '%u'", itr->second.totalCount, itr->second.weekCount, itr->second.seasonCount, itr->second.flags, GetGUIDLow(), itr->first); + else if (itr->second.state == PLAYERCURRENCY_NEW) + CharacterDatabase.PExecute("INSERT INTO `character_currencies` (`guid`, `id`, `totalCount`, `weekCount`, `seasonCount`, `flags`) VALUES ('%u', '%u', '%u', '%u', '%u', '%u')", GetGUIDLow(), itr->first, itr->second.totalCount, itr->second.weekCount, itr->second.seasonCount, itr->second.flags); + + if (itr->second.state == PLAYERCURRENCY_REMOVED) + m_currencies.erase(itr++); + else + { + itr->second.state = PLAYERCURRENCY_UNCHANGED; + ++itr; + } + } +} + +void Player::SetCurrencyFlags(uint32 currencyId, uint8 flags) +{ + PlayerCurrenciesMap::iterator itr = m_currencies.find(currencyId); + if (itr == m_currencies.end()) + return; + + itr->second.flags = flags; + itr->second.state = PLAYERCURRENCY_CHANGED; +} + +void Player::ResetCurrencyWeekCounts() +{ + for (PlayerCurrenciesMap::iterator itr = m_currencies.begin(); itr != m_currencies.end(); ++itr) + { + itr->second.weekCount = 0; + itr->second.state = PLAYERCURRENCY_CHANGED; + } + + WorldPacket data(SMSG_WEEKLY_RESET_CURRENCIES, 0); + SendDirectMessage(&data); +} + +void Player::SendPvPRewards() +{ + // Placeholder + + WorldPacket data(SMSG_PVP_REWARDS, 6 * 4); + data << uint32(1650); // rbg conquest cap + data << uint32(0); // total conquest earned + data << uint32(1350); // arena conquest cap + data << uint32(0); // rbg conquest earned + data << uint32(0); // arena conquest earned + data << uint32(1650); // total conquest cap + + SendDirectMessage(&data); +} + +void Player::SendRatedBGStats() +{ + // Placeholder + + WorldPacket data(SMSG_RATED_BG_STATS, 18 * 4); + for (int i = 0; i < 18; ++i) + data << uint32(0); + + SendDirectMessage(&data); +} + +AreaLockStatus Player::GetAreaTriggerLockStatus(AreaTrigger const* at, Difficulty difficulty, uint32& miscRequirement) +{ + miscRequirement = 0; + + if (!at) + return AREA_LOCKSTATUS_UNKNOWN_ERROR; + + MapEntry const* mapEntry = sMapStore.LookupEntry(at->target_mapId); + if (!mapEntry) + return AREA_LOCKSTATUS_UNKNOWN_ERROR; + + bool isRegularTargetMap = !mapEntry->IsDungeon() || GetDifficulty(mapEntry->IsRaid()) == REGULAR_DIFFICULTY; + + MapDifficultyEntry const* mapDiff = GetMapDifficultyData(at->target_mapId, difficulty); + if (mapEntry->IsDungeon() && !mapDiff) + return AREA_LOCKSTATUS_MISSING_DIFFICULTY; + + // Expansion requirement + if (GetSession()->Expansion() < mapEntry->Expansion()) + { + miscRequirement = mapEntry->Expansion(); + return AREA_LOCKSTATUS_INSUFFICIENT_EXPANSION; + } + + // Gamemaster can always enter + if (isGameMaster()) + return AREA_LOCKSTATUS_OK; + + // Level Requirements + if (getLevel() < at->requiredLevel && !sWorld.getConfig(CONFIG_BOOL_INSTANCE_IGNORE_LEVEL)) + { + miscRequirement = at->requiredLevel; + return AREA_LOCKSTATUS_TOO_LOW_LEVEL; + } + if (!isRegularTargetMap && !sWorld.getConfig(CONFIG_BOOL_INSTANCE_IGNORE_LEVEL) && getLevel() < uint32(maxLevelForExpansion[mapEntry->Expansion()])) + { + miscRequirement = maxLevelForExpansion[mapEntry->Expansion()]; + return AREA_LOCKSTATUS_TOO_LOW_LEVEL; + } + + // Raid Requirements + if (mapEntry->IsRaid() && !sWorld.getConfig(CONFIG_BOOL_INSTANCE_IGNORE_RAID)) + if (!GetGroup() || !GetGroup()->isRaidGroup()) + return AREA_LOCKSTATUS_RAID_LOCKED; + + // Item Requirements: must have requiredItem OR requiredItem2, report the first one that's missing + if (at->requiredItem) + { + if (!HasItemCount(at->requiredItem, 1) && + (!at->requiredItem2 || !HasItemCount(at->requiredItem2, 1))) + { + miscRequirement = at->requiredItem; + return AREA_LOCKSTATUS_MISSING_ITEM; + } + } + else if (at->requiredItem2 && !HasItemCount(at->requiredItem2, 1)) + { + miscRequirement = at->requiredItem2; + return AREA_LOCKSTATUS_MISSING_ITEM; + } + // Heroic item requirements + if (!isRegularTargetMap && at->heroicKey) + { + if (!HasItemCount(at->heroicKey, 1) && (!at->heroicKey2 || !HasItemCount(at->heroicKey2, 1))) + { + miscRequirement = at->heroicKey; + return AREA_LOCKSTATUS_MISSING_ITEM; + } + } + else if (!isRegularTargetMap && at->heroicKey2 && !HasItemCount(at->heroicKey2, 1)) + { + miscRequirement = at->heroicKey2; + return AREA_LOCKSTATUS_MISSING_ITEM; + } + + // Quest Requirements + if (isRegularTargetMap && at->requiredQuest && !GetQuestRewardStatus(at->requiredQuest)) + { + miscRequirement = at->requiredQuest; + return AREA_LOCKSTATUS_QUEST_NOT_COMPLETED; + } + if (!isRegularTargetMap && at->requiredQuestHeroic && !GetQuestRewardStatus(at->requiredQuestHeroic)) + { + miscRequirement = at->requiredQuestHeroic; + return AREA_LOCKSTATUS_QUEST_NOT_COMPLETED; + } + + // If the map is not created, assume it is possible to enter it. + DungeonPersistentState* state = GetBoundInstanceSaveForSelfOrGroup(at->target_mapId); + Map* map = sMapMgr.FindMap(at->target_mapId, state ? state->GetInstanceId() : 0); + + // ToDo add achievement check + + // Map's state check + if (map && map->IsDungeon()) + { + // cannot enter if the instance is full (player cap), GMs don't count + if (((DungeonMap*)map)->GetPlayersCountExceptGMs() >= ((DungeonMap*)map)->GetMaxPlayers()) + return AREA_LOCKSTATUS_INSTANCE_IS_FULL; + + // In Combat check + if (map && map->GetInstanceData() && map->GetInstanceData()->IsEncounterInProgress()) + return AREA_LOCKSTATUS_ZONE_IN_COMBAT; + + // Bind Checks + InstancePlayerBind* pBind = GetBoundInstance(at->target_mapId, GetDifficulty(mapEntry->IsRaid())); + if (pBind && pBind->perm && pBind->state != state) + return AREA_LOCKSTATUS_HAS_BIND; + if (pBind && pBind->perm && pBind->state != map->GetPersistentState()) + return AREA_LOCKSTATUS_HAS_BIND; + } + + return AREA_LOCKSTATUS_OK; +} + +const uint32 armorSpecToClass[MAX_CLASSES] = +{ + 0, + 86526, // CLASS_WARRIOR + 86525, // CLASS_PALADIN + 86528, // CLASS_HUNTER + 86531, // CLASS_ROGUE + 0, // CLASS_PRIEST + 86524, // CLASS_DEATH_KNIGHT + 86529, // CLASS_SHAMAN + 0, // CLASS_MAGE + 0, // CLASS_WARLOCK + 0, // CLASS_UNK2 + 86530, // CLASS_DRUID +}; + +#define MAX_ARMOR_SPECIALIZATION_SPELLS 18 +struct armorSpecToTabInfo +{ + uint32 spellId; + uint8 Class; + uint16 tab; +}; + +const armorSpecToTabInfo armorSpecToTab[MAX_ARMOR_SPECIALIZATION_SPELLS] = +{ + { 86537, CLASS_DEATH_KNIGHT, 398 }, // blood + { 86113, CLASS_DEATH_KNIGHT, 399 }, // frost + { 86536, CLASS_DEATH_KNIGHT, 400 }, // unholy + { 86093, CLASS_DRUID, 752 }, // balance + { 86096, CLASS_DRUID, 750 }, // feral + { 86097, CLASS_DRUID, 750 }, // feral + { 86104, CLASS_DRUID, 748 }, // resto + { 86538, CLASS_HUNTER, 0 }, // + { 86103, CLASS_PALADIN, 831 }, // holy + { 86102, CLASS_PALADIN, 839 }, // prot + { 86539, CLASS_PALADIN, 855 }, // retro + { 86092, CLASS_ROGUE, 0 }, // + { 86100, CLASS_SHAMAN, 261 }, // elem + { 86099, CLASS_SHAMAN, 263 }, // ench + { 86108, CLASS_SHAMAN, 262 }, // restor + { 86101, CLASS_WARRIOR, 746 }, // arms + { 86110, CLASS_WARRIOR, 815 }, // fury + { 86535, CLASS_WARRIOR, 845 }, // prot +}; + +void Player::UpdateArmorSpecializations() +{ + uint32 specPassive = armorSpecToClass[getClass()]; + // return class has no armor specialization + if (!specPassive) + return; + + for (int i = 0; i < MAX_ARMOR_SPECIALIZATION_SPELLS; ++i) + { + if (armorSpecToTab[i].Class != getClass()) + continue; + + SpellEntry const * spellProto = sSpellStore.LookupEntry(armorSpecToTab[i].spellId); + if (!spellProto || !spellProto->HasAttribute(SPELL_ATTR_EX8_ARMOR_SPECIALIZATION)) + { + sLog.outError("Player::UpdateArmorSpecializations: unexistent or wrong spell %u for class %u", + armorSpecToTab[i].spellId, armorSpecToTab[i].Class); + continue; + } + + // remove if base passive is unlearned + if (!HasSpell(specPassive)) + { + RemoveAurasDueToSpell(spellProto->Id); + continue; + } + + SpellAuraHolder* holder = GetSpellAuraHolder(spellProto->Id); + if (!holder) + { + // cast absent spells that may be missing due to shapeshift form dependency + CastSpell(this, spellProto->Id, true); + continue; + } + + Aura* aura = holder->GetAuraByEffectIndex(EFFECT_INDEX_0); + if (!aura) + continue; + + // recalculate modifier depending on current tree + aura->ApplyModifier(false, false); + aura->GetModifier()->m_amount = CalculateSpellDamage(this, spellProto, EFFECT_INDEX_0); + aura->ApplyModifier(true, false); + } +} + +bool Player::FitArmorSpecializationRules(SpellEntry const * spellProto) const +{ + if (!spellProto || !spellProto->HasAttribute(SPELL_ATTR_EX8_ARMOR_SPECIALIZATION)) + return true; + + int i = 0; + for (; i < MAX_ARMOR_SPECIALIZATION_SPELLS; ++i) + { + if (spellProto->Id == armorSpecToTab[i].spellId) + { + if (!armorSpecToTab[i].tab && m_talentsPrimaryTree[m_activeSpec] == 0 || + armorSpecToTab[i].tab && armorSpecToTab[i].tab != m_talentsPrimaryTree[m_activeSpec]) + return false; + + break; + } + } + + if (i == MAX_ARMOR_SPECIALIZATION_SPELLS) + return false; + + if (!HasSpell(armorSpecToClass[getClass()])) + return false; + + if (SpellEquippedItemsEntry const * itemsEntry = spellProto->GetSpellEquippedItems()) + { + // there spells check items with inventory types which are in EquippedItemInventoryTypeMask + uint32 inventoryTypeMask = itemsEntry->EquippedItemInventoryTypeMask; + // get slots that should be check for item presence and SpellEquippedItemsEntry match + uint32 slotMask = 0; + uint8 slots[4]; + for (int i = 0; i < MAX_INVTYPE; ++i) + { + if (inventoryTypeMask & (1 << i)) + { + if (!GetSlotsForInventoryType(i, slots)) + continue; + for (int j = 0; j < 4; ++j) + if (slots[j] != NULL_SLOT) + slotMask |= 1 << slots[j]; + } + } + + for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + { + if (slotMask & (1 << i)) + { + Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i); + // item must be present for specialization to work + if (!item) + return false; + + if (item->GetProto()->Class != itemsEntry->EquippedItemClass) + return false; + + if (((1 << item->GetProto()->SubClass) & itemsEntry->EquippedItemSubClassMask) == 0) + return false; + } + } + } + + return true; +} + +void Player::SendPetitionSignResult(ObjectGuid petitionGuid, Player* player, uint32 result) +{ + WorldPacket data(SMSG_PETITION_SIGN_RESULTS, 8 + 8 + 4); + data << petitionGuid; + data << player->GetObjectGuid(); + data << uint32(result); + GetSession()->SendPacket(&data); +} + +void Player::SendPetitionTurnInResult(uint32 result) +{ + WorldPacket data(SMSG_TURN_IN_PETITION_RESULTS, 4); + data << uint32(result); + GetSession()->SendPacket(&data); +} diff --git a/src/game/Player.h b/src/game/Player.h new file mode 100644 index 000000000..2e59b2100 --- /dev/null +++ b/src/game/Player.h @@ -0,0 +1,2724 @@ +/** + * This code is part of MaNGOS. Contributor & Copyright details are in AUTHORS/THANKS. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _PLAYER_H +#define _PLAYER_H + +#include "Common.h" +#include "ItemPrototype.h" +#include "Unit.h" +#include "Item.h" + +#include "Database/DatabaseEnv.h" +#include "NPCHandler.h" +#include "QuestDef.h" +#include "Group.h" +#include "Bag.h" +#include "WorldSession.h" +#include "Pet.h" +#include "MapReference.h" +#include "Util.h" // for Tokens typedef +#include "AchievementMgr.h" +#include "ReputationMgr.h" +#include "BattleGround/BattleGround.h" +#include "SharedDefines.h" + +#include +#include + +struct Mail; +class Channel; +class DynamicObject; +class Creature; +class PlayerMenu; +class Transport; +class UpdateMask; +class SpellCastTargets; +class PlayerSocial; +class DungeonPersistentState; +class Spell; +class Item; +class PhaseMgr; + +struct AreaTrigger; + +typedef std::deque PlayerMails; + +#define PLAYER_MAX_SKILLS 128 +#define PLAYER_MAX_DAILY_QUESTS 25 +#define PLAYER_EXPLORED_ZONES_SIZE 156 + +// 2^n internal values, they are never sent to the client +enum PlayerUnderwaterState +{ + UNDERWATER_NONE = 0x00, + UNDERWATER_INWATER = 0x01, // terrain type is water and player is afflicted by it + UNDERWATER_INLAVA = 0x02, // terrain type is lava and player is afflicted by it + UNDERWATER_INSLIME = 0x04, // terrain type is lava and player is afflicted by it + UNDERWATER_INDARKWATER = 0x08, // terrain type is dark water and player is afflicted by it + + UNDERWATER_EXIST_TIMERS = 0x10 +}; + +enum BuyBankSlotResult +{ + ERR_BANKSLOT_FAILED_TOO_MANY = 0, + ERR_BANKSLOT_INSUFFICIENT_FUNDS = 1, + ERR_BANKSLOT_NOTBANKER = 2, + ERR_BANKSLOT_OK = 3 +}; + +enum PlayerCurrencyFlag +{ + PLAYERCURRENCY_FLAG_NONE = 0x0, + PLAYERCURRENCY_FLAG_UNK1 = 0x1, // unused? + PLAYERCURRENCY_FLAG_UNK2 = 0x2, // unused? + PLAYERCURRENCY_FLAG_SHOW_IN_BACKPACK = 0x4, + PLAYERCURRENCY_FLAG_UNUSED = 0x8, + + PLAYERCURRENCY_MASK_USED_BY_CLIENT = + PLAYERCURRENCY_FLAG_SHOW_IN_BACKPACK | + PLAYERCURRENCY_FLAG_UNUSED, +}; + +enum PlayerCurrencyState +{ + PLAYERCURRENCY_UNCHANGED = 0, + PLAYERCURRENCY_CHANGED = 1, + PLAYERCURRENCY_NEW = 2, + PLAYERCURRENCY_REMOVED = 3 +}; + +struct PlayerCurrency +{ + PlayerCurrencyState state; + uint32 totalCount; + uint32 weekCount; + uint32 seasonCount; + uint8 flags; + CurrencyTypesEntry const * currencyEntry; +}; + +enum PlayerSpellState +{ + PLAYERSPELL_UNCHANGED = 0, + PLAYERSPELL_CHANGED = 1, + PLAYERSPELL_NEW = 2, + PLAYERSPELL_REMOVED = 3 +}; + +struct PlayerSpell +{ + PlayerSpellState state : 8; + bool active : 1; // show in spellbook + bool dependent : 1; // learned as result another spell learn, skill grow, quest reward, etc + bool disabled : 1; // first rank has been learned in result talent learn but currently talent unlearned, save max learned ranks +}; + +struct PlayerTalent +{ + TalentEntry const* talentEntry; + uint32 currentRank; + PlayerSpellState state; +}; + +typedef UNORDERED_MAP PlayerCurrenciesMap; +typedef UNORDERED_MAP PlayerSpellMap; +typedef UNORDERED_MAP PlayerTalentMap; + +struct SpellCooldown +{ + time_t end; + uint16 itemid; +}; + +typedef std::map SpellCooldowns; + +enum TrainerSpellState +{ + TRAINER_SPELL_GRAY = 0, + TRAINER_SPELL_GREEN = 1, + TRAINER_SPELL_RED = 2, + TRAINER_SPELL_GREEN_DISABLED = 10 // custom value, not send to client: formally green but learn not allowed +}; + +enum ActionButtonUpdateState +{ + ACTIONBUTTON_UNCHANGED = 0, + ACTIONBUTTON_CHANGED = 1, + ACTIONBUTTON_NEW = 2, + ACTIONBUTTON_DELETED = 3 +}; + +enum ActionButtonType +{ + ACTION_BUTTON_SPELL = 0x00, + ACTION_BUTTON_C = 0x01, // click? + ACTION_BUTTON_EQSET = 0x20, + ACTION_BUTTON_EXPANDABLE = 0x30, + ACTION_BUTTON_MACRO = 0x40, + ACTION_BUTTON_CMACRO = ACTION_BUTTON_C | ACTION_BUTTON_MACRO, + ACTION_BUTTON_ITEM = 0x80 +}; + +#define ACTION_BUTTON_ACTION(X) (uint32(X) & 0x00FFFFFF) +#define ACTION_BUTTON_TYPE(X) ((uint32(X) & 0xFF000000) >> 24) +#define MAX_ACTION_BUTTON_ACTION_VALUE (0x00FFFFFF+1) + +struct ActionButton +{ + ActionButton() : packedData(0), uState(ACTIONBUTTON_NEW) {} + + uint32 packedData; + ActionButtonUpdateState uState; + + // helpers + ActionButtonType GetType() const { return ActionButtonType(ACTION_BUTTON_TYPE(packedData)); } + uint32 GetAction() const { return ACTION_BUTTON_ACTION(packedData); } + void SetActionAndType(uint32 action, ActionButtonType type) + { + uint32 newData = action | (uint32(type) << 24); + if (newData != packedData || uState == ACTIONBUTTON_DELETED) + { + packedData = newData; + if (uState != ACTIONBUTTON_NEW) + uState = ACTIONBUTTON_CHANGED; + } + } +}; + +// some action button indexes used in code or clarify structure +enum ActionButtonIndex +{ + ACTION_BUTTON_SHAMAN_TOTEMS_BAR = 132, +}; + +#define MAX_ACTION_BUTTONS 144 // checked in 3.2.0 + +typedef std::map ActionButtonList; + +enum GlyphUpdateState +{ + GLYPH_UNCHANGED = 0, + GLYPH_CHANGED = 1, + GLYPH_NEW = 2, + GLYPH_DELETED = 3 +}; + +struct Glyph +{ + uint32 id; + GlyphUpdateState uState; + + Glyph() : id(0), uState(GLYPH_UNCHANGED) { } + + uint32 GetId() { return id; } + + void SetId(uint32 newId) + { + if (newId == id) + return; + + if (id == 0 && uState == GLYPH_UNCHANGED) // not exist yet in db and already saved + { + uState = GLYPH_NEW; + } + else if (newId == 0) + { + if (uState == GLYPH_NEW) // delete before add new -> no change + uState = GLYPH_UNCHANGED; + else // delete existing data + uState = GLYPH_DELETED; + } + else if (uState != GLYPH_NEW) // if not new data, change current data + { + uState = GLYPH_CHANGED; + } + + id = newId; + } +}; + +struct PlayerCreateInfoItem +{ + PlayerCreateInfoItem(uint32 id, uint32 amount) : item_id(id), item_amount(amount) {} + + uint32 item_id; + uint32 item_amount; +}; + +typedef std::list PlayerCreateInfoItems; + +struct PlayerLevelInfo +{ + PlayerLevelInfo() { for (int i = 0; i < MAX_STATS; ++i) stats[i] = 0; } + + uint8 stats[MAX_STATS]; +}; + +typedef std::list PlayerCreateInfoSpells; + +struct PlayerCreateInfoAction +{ + PlayerCreateInfoAction() : button(0), type(0), action(0) {} + PlayerCreateInfoAction(uint8 _button, uint32 _action, uint8 _type) : button(_button), type(_type), action(_action) {} + + uint8 button; + uint8 type; + uint32 action; +}; + +typedef std::list PlayerCreateInfoActions; + +struct PlayerInfo +{ + // existence checked by displayId != 0 // existence checked by displayId != 0 + PlayerInfo() : displayId_m(0), displayId_f(0), levelInfo(NULL) + { + } + + uint32 mapId; + uint32 areaId; + float positionX; + float positionY; + float positionZ; + float orientation; + uint16 phaseMap; + uint16 displayId_m; + uint16 displayId_f; + PlayerCreateInfoItems item; + PlayerCreateInfoSpells spell; + PlayerCreateInfoActions action; + + PlayerLevelInfo* levelInfo; //[level-1] 0..MaxPlayerLevel-1 +}; + +struct PvPInfo +{ + PvPInfo() : inHostileArea(false), endTimer(0) {} + + bool inHostileArea; + time_t endTimer; +}; + +struct DuelInfo +{ + DuelInfo() : initiator(NULL), opponent(NULL), startTimer(0), startTime(0), outOfBound(0) {} + + Player* initiator; + Player* opponent; + time_t startTimer; + time_t startTime; + time_t outOfBound; +}; + +struct Areas +{ + uint32 areaID; + uint32 areaFlag; + float x1; + float x2; + float y1; + float y2; +}; + +#define MAX_RUNES 6 +enum RuneCooldowns +{ + RUNE_BASE_COOLDOWN = 10000, + RUNE_MISS_COOLDOWN = 1500 // cooldown applied on runes when the spell misses +}; + +enum RuneType +{ + RUNE_BLOOD = 0, + RUNE_UNHOLY = 1, + RUNE_FROST = 2, + RUNE_DEATH = 3, + NUM_RUNE_TYPES = 4 +}; + +static RuneType runeSlotTypes[MAX_RUNES] = +{ + RUNE_BLOOD, + RUNE_BLOOD, + RUNE_UNHOLY, + RUNE_UNHOLY, + RUNE_FROST, + RUNE_FROST +}; + +struct RuneInfo +{ + uint8 BaseRune; + uint8 CurrentRune; + uint16 BaseCooldown; + uint16 Cooldown; // msec + Aura const* ConvertAura; +}; + +struct Runes +{ + RuneInfo runes[MAX_RUNES]; + uint8 runeState; // mask of available runes + uint32 lastUsedRuneMask; + + void SetRuneState(uint8 index, bool set = true) + { + if (set) + runeState |= (1 << index); // usable + else + runeState &= ~(1 << index); // on cooldown + } +}; + +struct EnchantDuration +{ + EnchantDuration() : item(NULL), slot(MAX_ENCHANTMENT_SLOT), leftduration(0) {}; + EnchantDuration(Item* _item, EnchantmentSlot _slot, uint32 _leftduration) : item(_item), slot(_slot), leftduration(_leftduration) { MANGOS_ASSERT(item); }; + + Item* item; + EnchantmentSlot slot; + uint32 leftduration; +}; + +typedef std::list EnchantDurationList; +typedef std::list ItemDurationList; + +enum LfgRoles +{ + LEADER = 0x01, + TANK = 0x02, + HEALER = 0x04, + DAMAGE = 0x08 +}; + +enum RaidGroupError +{ + ERR_RAID_GROUP_NONE = 0, + ERR_RAID_GROUP_LOWLEVEL = 1, + ERR_RAID_GROUP_ONLY = 2, + ERR_RAID_GROUP_FULL = 3, + ERR_RAID_GROUP_REQUIREMENTS_UNMATCH = 4 +}; + +enum DrunkenState +{ + DRUNKEN_SOBER = 0, + DRUNKEN_TIPSY = 1, + DRUNKEN_DRUNK = 2, + DRUNKEN_SMASHED = 3 +}; + +#define MAX_DRUNKEN 4 + +enum PlayerFlags +{ + PLAYER_FLAGS_NONE = 0x00000000, + PLAYER_FLAGS_GROUP_LEADER = 0x00000001, + PLAYER_FLAGS_AFK = 0x00000002, + PLAYER_FLAGS_DND = 0x00000004, + PLAYER_FLAGS_GM = 0x00000008, + PLAYER_FLAGS_GHOST = 0x00000010, + PLAYER_FLAGS_RESTING = 0x00000020, + PLAYER_FLAGS_UNK7 = 0x00000040, // admin? + PLAYER_FLAGS_UNK8 = 0x00000080, // pre-3.0.3 PLAYER_FLAGS_FFA_PVP flag for FFA PVP state + PLAYER_FLAGS_CONTESTED_PVP = 0x00000100, // Player has been involved in a PvP combat and will be attacked by contested guards + PLAYER_FLAGS_IN_PVP = 0x00000200, + PLAYER_FLAGS_HIDE_HELM = 0x00000400, + PLAYER_FLAGS_HIDE_CLOAK = 0x00000800, + PLAYER_FLAGS_PARTIAL_PLAY_TIME = 0x00001000, // played long time + PLAYER_FLAGS_NO_PLAY_TIME = 0x00002000, // played too long time + PLAYER_FLAGS_IS_OUT_OF_BOUNDS = 0x00004000, // Lua_IsOutOfBounds + PLAYER_FLAGS_DEVELOPER = 0x00008000, // chat tag, name prefix + PLAYER_FLAGS_ENABLE_LOW_LEVEL_RAID = 0x00010000, // triggers lua event EVENT_ENABLE_LOW_LEVEL_RAID + PLAYER_FLAGS_TAXI_BENCHMARK = 0x00020000, // taxi benchmark mode (on/off) (2.0.1) + PLAYER_FLAGS_PVP_TIMER = 0x00040000, // 3.0.2, pvp timer active (after you disable pvp manually) + PLAYER_FLAGS_COMMENTATOR = 0x00080000, + PLAYER_FLAGS_UNK21 = 0x00100000, + PLAYER_FLAGS_UNK22 = 0x00200000, + PLAYER_FLAGS_COMMENTATOR_UBER = 0x00400000, // something like COMMENTATOR_CAN_USE_INSTANCE_COMMAND + PLAYER_FLAGS_UNK24 = 0x00800000, // EVENT_SPELL_UPDATE_USABLE and EVENT_UPDATE_SHAPESHIFT_USABLE, disabled all abilitys on tab except autoattack + PLAYER_FLAGS_UNK25 = 0x01000000, // EVENT_SPELL_UPDATE_USABLE and EVENT_UPDATE_SHAPESHIFT_USABLE, disabled all melee ability on tab include autoattack + PLAYER_FLAGS_XP_USER_DISABLED = 0x02000000, + PLAYER_FLAGS_UNK27 = 0x04000000, + PLAYER_FLAGS_AUTO_DECLINE_GUILDS = 0x08000000, // Automatically declines guild invites + PLAYER_FLAGS_GUILD_LEVELING_ENABLED = 0x10000000, // Lua_GetGuildLevelEnabled() - enables guild leveling related UI + PLAYER_FLAGS_VOID_STORAGE_UNLOCKED = 0x20000000, // unlocks void storage + PLAYER_FLAGS_UNK30 = 0x40000000, + PLAYER_FLAGS_UNK31 = 0x80000000, +}; + +// used for PLAYER__FIELD_KNOWN_TITLES field (uint64), (1< QuestStatusMap; + +enum QuestSlotOffsets +{ + QUEST_ID_OFFSET = 0, + QUEST_STATE_OFFSET = 1, + QUEST_COUNTS_OFFSET = 2, // 2 and 3 + QUEST_TIME_OFFSET = 4 +}; + +#define MAX_QUEST_OFFSET 5 + +enum QuestSlotStateMask +{ + QUEST_STATE_NONE = 0x0000, + QUEST_STATE_COMPLETE = 0x0001, + QUEST_STATE_FAIL = 0x0002 +}; + +enum SkillUpdateState +{ + SKILL_UNCHANGED = 0, + SKILL_CHANGED = 1, + SKILL_NEW = 2, + SKILL_DELETED = 3 +}; + +struct SkillStatusData +{ + SkillStatusData(uint8 _pos, SkillUpdateState _uState) : pos(_pos), uState(_uState) + { + } + uint8 pos; + SkillUpdateState uState; +}; + +typedef UNORDERED_MAP SkillStatusMap; + +enum PlayerSlots +{ + // first slot for item stored (in any way in player m_items data) + PLAYER_SLOT_START = 0, + // last+1 slot for item stored (in any way in player m_items data) + PLAYER_SLOT_END = 86, + PLAYER_SLOTS_COUNT = (PLAYER_SLOT_END - PLAYER_SLOT_START) +}; + +#define INVENTORY_SLOT_BAG_0 255 + +enum EquipmentSlots // 19 slots +{ + EQUIPMENT_SLOT_START = 0, + EQUIPMENT_SLOT_HEAD = 0, + EQUIPMENT_SLOT_NECK = 1, + EQUIPMENT_SLOT_SHOULDERS = 2, + EQUIPMENT_SLOT_BODY = 3, + EQUIPMENT_SLOT_CHEST = 4, + EQUIPMENT_SLOT_WAIST = 5, + EQUIPMENT_SLOT_LEGS = 6, + EQUIPMENT_SLOT_FEET = 7, + EQUIPMENT_SLOT_WRISTS = 8, + EQUIPMENT_SLOT_HANDS = 9, + EQUIPMENT_SLOT_FINGER1 = 10, + EQUIPMENT_SLOT_FINGER2 = 11, + EQUIPMENT_SLOT_TRINKET1 = 12, + EQUIPMENT_SLOT_TRINKET2 = 13, + EQUIPMENT_SLOT_BACK = 14, + EQUIPMENT_SLOT_MAINHAND = 15, + EQUIPMENT_SLOT_OFFHAND = 16, + EQUIPMENT_SLOT_RANGED = 17, + EQUIPMENT_SLOT_TABARD = 18, + EQUIPMENT_SLOT_END = 19 +}; + +enum InventorySlots // 4 slots +{ + INVENTORY_SLOT_BAG_START = 19, + INVENTORY_SLOT_BAG_END = 23 +}; + +enum InventoryPackSlots // 16 slots +{ + INVENTORY_SLOT_ITEM_START = 23, + INVENTORY_SLOT_ITEM_END = 39 +}; + +enum BankItemSlots // 28 slots +{ + BANK_SLOT_ITEM_START = 39, + BANK_SLOT_ITEM_END = 67 +}; + +enum BankBagSlots // 7 slots +{ + BANK_SLOT_BAG_START = 67, + BANK_SLOT_BAG_END = 74 +}; + +enum BuyBackSlots // 12 slots +{ + // stored in m_buybackitems + BUYBACK_SLOT_START = 74, + BUYBACK_SLOT_END = 86 +}; + +enum EquipmentSetUpdateState +{ + EQUIPMENT_SET_UNCHANGED = 0, + EQUIPMENT_SET_CHANGED = 1, + EQUIPMENT_SET_NEW = 2, + EQUIPMENT_SET_DELETED = 3 +}; + +struct EquipmentSet +{ + EquipmentSet() : Guid(0), IgnoreMask(0), state(EQUIPMENT_SET_NEW) + { + for (int i = 0; i < EQUIPMENT_SLOT_END; ++i) + Items[i] = 0; + } + + uint64 Guid; + std::string Name; + std::string IconName; + uint32 IgnoreMask; + uint32 Items[EQUIPMENT_SLOT_END]; + EquipmentSetUpdateState state; +}; + +#define MAX_EQUIPMENT_SET_INDEX 10 // client limit + +typedef std::map EquipmentSets; + +struct ItemPosCount +{ + ItemPosCount(uint16 _pos, uint32 _count) : pos(_pos), count(_count) {} + bool isContainedIn(std::vector const& vec) const; + uint16 pos; + uint32 count; +}; +typedef std::vector ItemPosCountVec; + +enum TradeSlots +{ + TRADE_SLOT_COUNT = 7, + TRADE_SLOT_TRADED_COUNT = 6, + TRADE_SLOT_NONTRADED = 6 +}; + +enum TransferAbortReason +{ + TRANSFER_ABORT_NONE = 0x00, + TRANSFER_ABORT_ERROR = 0x01, + TRANSFER_ABORT_MAX_PLAYERS = 0x02, // Transfer Aborted: instance is full + TRANSFER_ABORT_NOT_FOUND = 0x03, // Transfer Aborted: instance not found + TRANSFER_ABORT_TOO_MANY_INSTANCES = 0x04, // You have entered too many instances recently. + TRANSFER_ABORT_ZONE_IN_COMBAT = 0x06, // Unable to zone in while an encounter is in progress. + TRANSFER_ABORT_INSUF_EXPAN_LVL = 0x07, // You must have expansion installed to access this area. + TRANSFER_ABORT_DIFFICULTY = 0x08, // difficulty mode is not available for %s. + TRANSFER_ABORT_UNIQUE_MESSAGE = 0x09, // Until you've escaped TLK's grasp, you cannot leave this place! + TRANSFER_ABORT_TOO_MANY_REALM_INSTANCES = 0x0A, // Additional instances cannot be launched, please try again later. + TRANSFER_ABORT_NEED_GROUP = 0x0B, // 3.1 + TRANSFER_ABORT_NOT_FOUND2 = 0x0C, // 3.1 + TRANSFER_ABORT_NOT_FOUND3 = 0x0D, // 3.1 + TRANSFER_ABORT_NOT_FOUND4 = 0x0E, // 3.2 + TRANSFER_ABORT_REALM_ONLY = 0x0F, // All players on party must be from the same realm. + TRANSFER_ABORT_MAP_NOT_ALLOWED = 0x10, // Map can't be entered at this time. + // 0x11 not found + TRANSFER_ABORT_LOCKED_TO_DIFFERENT_INSTANCE = 0x12, // 4.0.1 + TRANSFER_ABORT_ALREADY_COMPLETED_ENCOUNTER = 0x13, // 4.0.1 +}; + +enum InstanceResetWarningType +{ + RAID_INSTANCE_WARNING_HOURS = 1, // WARNING! %s is scheduled to reset in %d hour(s). + RAID_INSTANCE_WARNING_MIN = 2, // WARNING! %s is scheduled to reset in %d minute(s)! + RAID_INSTANCE_WARNING_MIN_SOON = 3, // WARNING! %s is scheduled to reset in %d minute(s). Please exit the zone or you will be returned to your bind location! + RAID_INSTANCE_WELCOME = 4, // Welcome to %s. This raid instance is scheduled to reset in %s. + RAID_INSTANCE_EXPIRED = 5 +}; + +// PLAYER_FIELD_ARENA_TEAM_INFO_1_1 offsets +enum ArenaTeamInfoType +{ + ARENA_TEAM_ID = 0, + ARENA_TEAM_TYPE = 1, // new in 3.2 - team type? + ARENA_TEAM_MEMBER = 2, // 0 - captain, 1 - member + ARENA_TEAM_GAMES_WEEK = 3, + ARENA_TEAM_GAMES_SEASON = 4, + ARENA_TEAM_WINS_SEASON = 5, + ARENA_TEAM_PERSONAL_RATING = 6, + ARENA_TEAM_END = 7 +}; + +enum RestType +{ + REST_TYPE_NO = 0, + REST_TYPE_IN_TAVERN = 1, + REST_TYPE_IN_CITY = 2 +}; + +enum DuelCompleteType +{ + DUEL_INTERRUPTED = 0, + DUEL_WON = 1, + DUEL_FLED = 2 +}; + +enum TeleportToOptions +{ + TELE_TO_GM_MODE = 0x01, + TELE_TO_NOT_LEAVE_TRANSPORT = 0x02, + TELE_TO_NOT_LEAVE_COMBAT = 0x04, + TELE_TO_NOT_UNSUMMON_PET = 0x08, + TELE_TO_SPELL = 0x10, +}; + +/// Type of environmental damages +enum EnviromentalDamage +{ + DAMAGE_EXHAUSTED = 0, + DAMAGE_DROWNING = 1, + DAMAGE_FALL = 2, + DAMAGE_LAVA = 3, + DAMAGE_SLIME = 4, + DAMAGE_FIRE = 5, + DAMAGE_FALL_TO_VOID = 6 // custom case for fall without durability loss +}; + +enum PlayerChatTag +{ + CHAT_TAG_NONE = 0x00, + CHAT_TAG_AFK = 0x01, + CHAT_TAG_DND = 0x02, + CHAT_TAG_GM = 0x04, + CHAT_TAG_COM = 0x08, // Commentator + CHAT_TAG_DEV = 0x10, // Developer +}; + +enum PlayedTimeIndex +{ + PLAYED_TIME_TOTAL = 0, + PLAYED_TIME_LEVEL = 1 +}; + +#define MAX_PLAYED_TIME_INDEX 2 + +// used at player loading query list preparing, and later result selection +enum PlayerLoginQueryIndex +{ + PLAYER_LOGIN_QUERY_LOADFROM, + PLAYER_LOGIN_QUERY_LOADGROUP, + PLAYER_LOGIN_QUERY_LOADBOUNDINSTANCES, + PLAYER_LOGIN_QUERY_LOADAURAS, + PLAYER_LOGIN_QUERY_LOADSPELLS, + PLAYER_LOGIN_QUERY_LOADQUESTSTATUS, + PLAYER_LOGIN_QUERY_LOADDAILYQUESTSTATUS, + PLAYER_LOGIN_QUERY_LOADREPUTATION, + PLAYER_LOGIN_QUERY_LOADINVENTORY, + PLAYER_LOGIN_QUERY_LOADITEMLOOT, + PLAYER_LOGIN_QUERY_LOADACTIONS, + PLAYER_LOGIN_QUERY_LOADSOCIALLIST, + PLAYER_LOGIN_QUERY_LOADHOMEBIND, + PLAYER_LOGIN_QUERY_LOADSPELLCOOLDOWNS, + PLAYER_LOGIN_QUERY_LOADDECLINEDNAMES, + PLAYER_LOGIN_QUERY_LOADGUILD, + PLAYER_LOGIN_QUERY_LOADARENAINFO, + PLAYER_LOGIN_QUERY_LOADACHIEVEMENTS, + PLAYER_LOGIN_QUERY_LOADCRITERIAPROGRESS, + PLAYER_LOGIN_QUERY_LOADEQUIPMENTSETS, + PLAYER_LOGIN_QUERY_LOADBGDATA, + PLAYER_LOGIN_QUERY_LOADACCOUNTDATA, + PLAYER_LOGIN_QUERY_LOADSKILLS, + PLAYER_LOGIN_QUERY_LOADGLYPHS, + PLAYER_LOGIN_QUERY_LOADMAILS, + PLAYER_LOGIN_QUERY_LOADMAILEDITEMS, + PLAYER_LOGIN_QUERY_LOADTALENTS, + PLAYER_LOGIN_QUERY_LOADWEEKLYQUESTSTATUS, + PLAYER_LOGIN_QUERY_LOADMONTHLYQUESTSTATUS, + PLAYER_LOGIN_QUERY_LOADCURRENCIES, + + MAX_PLAYER_LOGIN_QUERY +}; + +enum PlayerDelayedOperations +{ + DELAYED_SAVE_PLAYER = 0x01, + DELAYED_RESURRECT_PLAYER = 0x02, + DELAYED_SPELL_CAST_DESERTER = 0x04, + DELAYED_BG_MOUNT_RESTORE = 0x08, ///< Flag to restore mount state after teleport from BG + DELAYED_BG_TAXI_RESTORE = 0x10, ///< Flag to restore taxi state after teleport from BG + DELAYED_END +}; + +enum ReputationSource +{ + REPUTATION_SOURCE_KILL, + REPUTATION_SOURCE_QUEST, + REPUTATION_SOURCE_SPELL +}; + +// Player summoning auto-decline time (in secs) +#define MAX_PLAYER_SUMMON_DELAY (2*MINUTE) +#define MAX_MONEY_AMOUNT (UI64LIT(9999999999)) // from wowpedia + +struct InstancePlayerBind +{ + DungeonPersistentState* state; + bool perm; + /* permanent PlayerInstanceBinds are created in Raid/Heroic instances for players + that aren't already permanently bound when they are inside when a boss is killed + or when they enter an instance that the group leader is permanently bound to. */ + InstancePlayerBind() : state(NULL), perm(false) {} +}; + +enum PlayerRestState +{ + REST_STATE_RESTED = 0x01, + REST_STATE_NORMAL = 0x02, + REST_STATE_RAF_LINKED = 0x04 // Exact use unknown +}; + +class MANGOS_DLL_SPEC PlayerTaxi +{ + public: + PlayerTaxi(); + ~PlayerTaxi() {} + // Nodes + void InitTaxiNodesForLevel(uint32 race, uint32 chrClass, uint32 level); + void LoadTaxiMask(const char* data); + + bool IsTaximaskNodeKnown(uint32 nodeidx) const + { + uint8 field = uint8((nodeidx - 1) / 8); + uint8 submask = 1 << ((nodeidx - 1) % 8); + return (m_taximask[field] & submask) == submask; + } + bool SetTaximaskNode(uint32 nodeidx) + { + uint8 field = uint8((nodeidx - 1) / 8); + uint8 submask = 1 << ((nodeidx - 1) % 8); + if ((m_taximask[field] & submask) != submask) + { + m_taximask[field] |= submask; + return true; + } + else + return false; + } + void AppendTaximaskTo(ByteBuffer& data, bool all); + + // Destinations + bool LoadTaxiDestinationsFromString(const std::string& values, Team team); + std::string SaveTaxiDestinationsToString(); + + void ClearTaxiDestinations() { m_TaxiDestinations.clear(); } + void AddTaxiDestination(uint32 dest) { m_TaxiDestinations.push_back(dest); } + uint32 GetTaxiSource() const { return m_TaxiDestinations.empty() ? 0 : m_TaxiDestinations.front(); } + uint32 GetTaxiDestination() const { return m_TaxiDestinations.size() < 2 ? 0 : m_TaxiDestinations[1]; } + uint32 GetCurrentTaxiPath() const; + uint32 NextTaxiDestination() + { + m_TaxiDestinations.pop_front(); + return GetTaxiDestination(); + } + bool empty() const { return m_TaxiDestinations.empty(); } + + friend std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi); + private: + TaxiMask m_taximask; + std::deque m_TaxiDestinations; +}; + +std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi); + +/// Holder for BattleGround data +struct BGData +{ + BGData() : bgInstanceID(0), bgTypeID(BATTLEGROUND_TYPE_NONE), bgAfkReportedCount(0), bgAfkReportedTimer(0), + bgTeam(TEAM_NONE), mountSpell(0), m_needSave(false) { ClearTaxiPath(); } + + uint32 bgInstanceID; ///< This variable is set to bg->m_InstanceID, saved + /// when player is teleported to BG - (it is battleground's GUID) + BattleGroundTypeId bgTypeID; + + std::set bgAfkReporter; + uint8 bgAfkReportedCount; + time_t bgAfkReportedTimer; + + Team bgTeam; ///< What side the player will be added to, saved + + uint32 mountSpell; ///< Mount used before join to bg, saved + uint32 taxiPath[2]; ///< Current taxi active path start/end nodes, saved + + WorldLocation joinPos; ///< From where player entered BG, saved + + bool m_needSave; ///< true, if saved to DB fields modified after prev. save (marked as "saved" above) + + void ClearTaxiPath() { taxiPath[0] = taxiPath[1] = 0; } + bool HasTaxiPath() const { return taxiPath[0] && taxiPath[1]; } +}; + +class TradeData +{ + public: // constructors + TradeData(Player* player, Player* trader) : + m_player(player), m_trader(trader), m_accepted(false), m_acceptProccess(false), + m_money(0), m_spell(0) {} + + public: // access functions + + Player* GetTrader() const { return m_trader; } + TradeData* GetTraderData() const; + + Item* GetItem(TradeSlots slot) const; + bool HasItem(ObjectGuid item_guid) const; + + uint32 GetSpell() const { return m_spell; } + Item* GetSpellCastItem() const; + bool HasSpellCastItem() const { return !m_spellCastItem.IsEmpty(); } + + uint64 GetMoney() const { return m_money; } + + bool IsAccepted() const { return m_accepted; } + bool IsInAcceptProcess() const { return m_acceptProccess; } + public: // access functions + + void SetItem(TradeSlots slot, Item* item); + void SetSpell(uint32 spell_id, Item* castItem = NULL); + void SetMoney(uint64 money); + + void SetAccepted(bool state, bool crosssend = false); + + // must be called only from accept handler helper functions + void SetInAcceptProcess(bool state) { m_acceptProccess = state; } + + private: // internal functions + + void Update(bool for_trader = true); + + private: // fields + + Player* m_player; // Player who own of this TradeData + Player* m_trader; // Player who trade with m_player + + bool m_accepted; // m_player press accept for trade list + bool m_acceptProccess; // one from player/trader press accept and this processed + + uint64 m_money; // m_player place money to trade + + uint32 m_spell; // m_player apply spell to non-traded slot item + ObjectGuid m_spellCastItem; // applied spell casted by item use + + ObjectGuid m_items[TRADE_SLOT_COUNT]; // traded itmes from m_player side including non-traded slot +}; + +class MANGOS_DLL_SPEC Player : public Unit +{ + friend class WorldSession; + friend void Item::AddToUpdateQueueOf(Player* player); + friend void Item::RemoveFromUpdateQueueOf(Player* player); + public: + explicit Player(WorldSession* session); + ~Player(); + + void CleanupsBeforeDelete() override; + + static UpdateMask updateVisualBits; + static void InitVisibleBits(); + + void AddToWorld() override; + void RemoveFromWorld() override; + + void SendTeleportPacket(float oldX, float oldY, float oldZ, float oldO); + bool TeleportTo(uint32 mapid, float x, float y, float z, float orientation, uint32 options = 0, AreaTrigger const* at = NULL); + + bool TeleportTo(WorldLocation const& loc, uint32 options = 0) + { + return TeleportTo(loc.mapid, loc.coord_x, loc.coord_y, loc.coord_z, loc.orientation, options); + } + + bool TeleportToBGEntryPoint(); + + void SetSummonPoint(uint32 mapid, float x, float y, float z) + { + m_summon_expire = time(NULL) + MAX_PLAYER_SUMMON_DELAY; + m_summon_mapid = mapid; + m_summon_x = x; + m_summon_y = y; + m_summon_z = z; + } + void SummonIfPossible(bool agree); + + bool Create(uint32 guidlow, const std::string& name, uint8 race, uint8 class_, uint8 gender, uint8 skin, uint8 face, uint8 hairStyle, uint8 hairColor, uint8 facialHair, uint8 outfitId); + + void Update(uint32 update_diff, uint32 time) override; + + static bool BuildEnumData(QueryResult* result, ByteBuffer* data, ByteBuffer* buffer); + + void SetInWater(bool apply); + + bool IsInWater() const override { return m_isInWater; } + bool IsUnderWater() const override; + + void SendInitialPacketsBeforeAddToMap(); + void SendInitialPacketsAfterAddToMap(); + void SendInstanceResetWarning(uint32 mapid, Difficulty difficulty, uint32 time); + + Creature* GetNPCIfCanInteractWith(ObjectGuid guid, uint32 npcflagmask); + GameObject* GetGameObjectIfCanInteractWith(ObjectGuid guid, uint32 gameobject_type = MAX_GAMEOBJECT_TYPE) const; + + void ToggleAFK(); + void ToggleDND(); + bool isAFK() const { return HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_AFK); } + bool isDND() const { return HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_DND); } + uint8 GetChatTag() const; + std::string autoReplyMsg; + + uint32 GetBarberShopCost(uint8 newhairstyle, uint8 newhaircolor, uint8 newfacialhair, uint32 newskintone); + + PlayerSocial* GetSocial() { return m_social; } + + PlayerTaxi m_taxi; + void InitTaxiNodesForLevel() { m_taxi.InitTaxiNodesForLevel(getRace(), getClass(), getLevel()); } + bool ActivateTaxiPathTo(std::vector const& nodes, Creature* npc = NULL, uint32 spellid = 0); + bool ActivateTaxiPathTo(uint32 taxi_path_id, uint32 spellid = 0); + // mount_id can be used in scripting calls + void ContinueTaxiFlight(); + bool isAcceptTickets() const { return GetSession()->GetSecurity() >= SEC_GAMEMASTER && (m_ExtraFlags & PLAYER_EXTRA_GM_ACCEPT_TICKETS); } + void SetAcceptTicket(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_GM_ACCEPT_TICKETS; else m_ExtraFlags &= ~PLAYER_EXTRA_GM_ACCEPT_TICKETS; } + bool isAcceptWhispers() const { return m_ExtraFlags & PLAYER_EXTRA_ACCEPT_WHISPERS; } + void SetAcceptWhispers(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_ACCEPT_WHISPERS; else m_ExtraFlags &= ~PLAYER_EXTRA_ACCEPT_WHISPERS; } + bool isGameMaster() const { return m_ExtraFlags & PLAYER_EXTRA_GM_ON; } + void SetGameMaster(bool on); + bool isGMChat() const { return GetSession()->GetSecurity() >= SEC_MODERATOR && (m_ExtraFlags & PLAYER_EXTRA_GM_CHAT); } + void SetGMChat(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_GM_CHAT; else m_ExtraFlags &= ~PLAYER_EXTRA_GM_CHAT; } + bool isTaxiCheater() const { return m_ExtraFlags & PLAYER_EXTRA_TAXICHEAT; } + void SetTaxiCheater(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_TAXICHEAT; else m_ExtraFlags &= ~PLAYER_EXTRA_TAXICHEAT; } + bool isGMVisible() const { return !(m_ExtraFlags & PLAYER_EXTRA_GM_INVISIBLE); } + void SetGMVisible(bool on); + void SetPvPDeath(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_PVP_DEATH; else m_ExtraFlags &= ~PLAYER_EXTRA_PVP_DEATH; } + + // 0 = own auction, -1 = enemy auction, 1 = goblin auction + int GetAuctionAccessMode() const { return m_ExtraFlags & PLAYER_EXTRA_AUCTION_ENEMY ? -1 : (m_ExtraFlags & PLAYER_EXTRA_AUCTION_NEUTRAL ? 1 : 0); } + void SetAuctionAccessMode(int state) + { + m_ExtraFlags &= ~(PLAYER_EXTRA_AUCTION_ENEMY | PLAYER_EXTRA_AUCTION_NEUTRAL); + + if (state < 0) + m_ExtraFlags |= PLAYER_EXTRA_AUCTION_ENEMY; + else if (state > 0) + m_ExtraFlags |= PLAYER_EXTRA_AUCTION_NEUTRAL; + } + + void GiveXP(uint32 xp, Unit* victim); + void GiveLevel(uint32 level); + + void InitStatsForLevel(bool reapplyMods = false); + + // Played Time Stuff + time_t m_logintime; + time_t m_Last_tick; + + uint32 m_Played_time[MAX_PLAYED_TIME_INDEX]; + uint32 GetTotalPlayedTime() { return m_Played_time[PLAYED_TIME_TOTAL]; } + uint32 GetLevelPlayedTime() { return m_Played_time[PLAYED_TIME_LEVEL]; } + + void ResetTimeSync(); + void SendTimeSync(); + + void SetDeathState(DeathState s) override; // overwrite Unit::SetDeathState + + float GetRestBonus() const { return m_rest_bonus; } + void SetRestBonus(float rest_bonus_new); + + RestType GetRestType() const { return rest_type; } + void SetRestType(RestType n_r_type, uint32 areaTriggerId = 0); + + time_t GetTimeInnEnter() const { return time_inn_enter; } + void UpdateInnerTime(time_t time) { time_inn_enter = time; } + + void RemovePet(PetSaveMode mode); + + PhaseMgr* GetPhaseMgr() { return phaseMgr; } + + void Say(const std::string& text, const uint32 language); + void Yell(const std::string& text, const uint32 language); + void TextEmote(const std::string& text); + void Whisper(const std::string& text, const uint32 language, ObjectGuid receiver); + void WhisperAddon(const std::string& text, const std::string& prefix, ObjectGuid receiver); + void BuildPlayerChat(WorldPacket* data, uint8 msgtype, const std::string& text, uint32 language, const char* addonPrefix = NULL) const; + + /*********************************************************/ + /*** STORAGE SYSTEM ***/ + /*********************************************************/ + + void SetVirtualItemSlot(uint8 i, Item* item); + void SetSheath(SheathState sheathed) override; // overwrite Unit version + bool GetSlotsForInventoryType(uint8 invType, uint8* slots, uint32 subClass = 0) const; + uint8 FindEquipSlot(ItemPrototype const* proto, uint32 slot, bool swap) const; + uint32 GetItemCount(uint32 item, bool inBankAlso = false, Item* skipItem = NULL) const; + uint32 GetItemCountWithLimitCategory(uint32 limitCategory, Item* skipItem = NULL) const; + Item* GetItemByGuid(ObjectGuid guid) const; + Item* GetItemByEntry(uint32 item) const; // only for special cases + Item* GetItemByLimitedCategory(uint32 limitedCategory) const; + Item* GetItemByPos(uint16 pos) const; + Item* GetItemByPos(uint8 bag, uint8 slot) const; + uint32 GetItemDisplayIdInSlot(uint8 bag, uint8 slot) const; + Item* GetWeaponForAttack(WeaponAttackType attackType) const { return GetWeaponForAttack(attackType, false, false); } + Item* GetWeaponForAttack(WeaponAttackType attackType, bool nonbroken, bool useable) const; + Item* GetShield(bool useable = false) const; + static uint32 GetAttackBySlot(uint8 slot); // MAX_ATTACK if not weapon slot + std::vector& GetItemUpdateQueue() { return m_itemUpdateQueue; } + static bool IsInventoryPos(uint16 pos) { return IsInventoryPos(pos >> 8, pos & 255); } + static bool IsInventoryPos(uint8 bag, uint8 slot); + static bool IsEquipmentPos(uint16 pos) { return IsEquipmentPos(pos >> 8, pos & 255); } + static bool IsEquipmentPos(uint8 bag, uint8 slot); + static bool IsBagPos(uint16 pos); + static bool IsBankPos(uint16 pos) { return IsBankPos(pos >> 8, pos & 255); } + static bool IsBankPos(uint8 bag, uint8 slot); + bool IsValidPos(uint16 pos, bool explicit_pos) const { return IsValidPos(pos >> 8, pos & 255, explicit_pos); } + bool IsValidPos(uint8 bag, uint8 slot, bool explicit_pos) const; + uint8 GetBankBagSlotCount() const { return GetByteValue(PLAYER_BYTES_2, 2); } + void SetBankBagSlotCount(uint8 count) { SetByteValue(PLAYER_BYTES_2, 2, count); } + bool HasItemCount(uint32 item, uint32 count, bool inBankAlso = false) const; + bool HasItemFitToSpellReqirements(SpellEntry const* spellInfo, Item const* ignoreItem = NULL); + bool CanNoReagentCast(SpellEntry const* spellInfo) const; + bool HasItemOrGemWithIdEquipped(uint32 item, uint32 count, uint8 except_slot = NULL_SLOT) const; + bool HasItemOrGemWithLimitCategoryEquipped(uint32 limitCategory, uint32 count, uint8 except_slot = NULL_SLOT) const; + InventoryResult CanTakeMoreSimilarItems(Item* pItem) const { return _CanTakeMoreSimilarItems(pItem->GetEntry(), pItem->GetCount(), pItem); } + InventoryResult CanTakeMoreSimilarItems(uint32 entry, uint32 count) const { return _CanTakeMoreSimilarItems(entry, count, NULL); } + InventoryResult CanStoreNewItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, uint32 item, uint32 count, uint32* no_space_count = NULL) const + { + return _CanStoreItem(bag, slot, dest, item, count, NULL, false, no_space_count); + } + InventoryResult CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap = false) const + { + if (!pItem) + return EQUIP_ERR_ITEM_NOT_FOUND; + uint32 count = pItem->GetCount(); + return _CanStoreItem(bag, slot, dest, pItem->GetEntry(), count, pItem, swap, NULL); + } + InventoryResult CanStoreItems(Item** pItem, int count) const; + InventoryResult CanEquipNewItem(uint8 slot, uint16& dest, uint32 item, bool swap) const; + InventoryResult CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool direct_action = true) const; + + InventoryResult CanEquipUniqueItem(Item* pItem, uint8 except_slot = NULL_SLOT, uint32 limit_count = 1) const; + InventoryResult CanEquipUniqueItem(ItemPrototype const* itemProto, uint8 except_slot = NULL_SLOT, uint32 limit_count = 1) const; + InventoryResult CanUnequipItems(uint32 item, uint32 count) const; + InventoryResult CanUnequipItem(uint16 src, bool swap) const; + InventoryResult CanBankItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap, bool not_loading = true) const; + InventoryResult CanUseItem(Item* pItem, bool direct_action = true) const; + InventoryResult CanUseItem(ItemPrototype const* pItem) const; + InventoryResult CanUseAmmo(uint32 item) const; + Item* StoreNewItem(ItemPosCountVec const& pos, uint32 item, bool update, int32 randomPropertyId = 0); + Item* StoreItem(ItemPosCountVec const& pos, Item* pItem, bool update); + Item* EquipNewItem(uint16 pos, uint32 item, bool update); + Item* EquipItem(uint16 pos, Item* pItem, bool update); + void AutoUnequipOffhandIfNeed(); + bool StoreNewItemInBestSlots(uint32 item_id, uint32 item_count); + Item* StoreNewItemInInventorySlot(uint32 itemEntry, uint32 amount); + + void AutoStoreLoot(WorldObject const* lootTarget, uint32 loot_id, LootStore const& store, bool broadcast = false, uint8 bag = NULL_BAG, uint8 slot = NULL_SLOT); + void AutoStoreLoot(Loot& loot, bool broadcast = false, uint8 bag = NULL_BAG, uint8 slot = NULL_SLOT); + + Item* ConvertItem(Item* item, uint32 newItemId); + + InventoryResult _CanTakeMoreSimilarItems(uint32 entry, uint32 count, Item* pItem, uint32* no_space_count = NULL) const; + InventoryResult _CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, uint32 entry, uint32 count, Item* pItem = NULL, bool swap = false, uint32* no_space_count = NULL) const; + + void ApplyEquipCooldown(Item* pItem); + void SetAmmo(uint32 item); + void RemoveAmmo(); + float GetAmmoDPS() const { return m_ammoDPS; } + bool CheckAmmoCompatibility(const ItemPrototype* ammo_proto) const; + void QuickEquipItem(uint16 pos, Item* pItem); + void VisualizeItem(uint8 slot, Item* pItem); + void SetVisibleItemSlot(uint8 slot, Item* pItem); + Item* BankItem(ItemPosCountVec const& dest, Item* pItem, bool update) + { + return StoreItem(dest, pItem, update); + } + Item* BankItem(uint16 pos, Item* pItem, bool update); + void RemoveItem(uint8 bag, uint8 slot, bool update);// see ApplyItemOnStoreSpell notes + void MoveItemFromInventory(uint8 bag, uint8 slot, bool update); + // in trade, auction, guild bank, mail.... + void MoveItemToInventory(ItemPosCountVec const& dest, Item* pItem, bool update, bool in_characterInventoryDB = false); + // in trade, guild bank, mail.... + void RemoveItemDependentAurasAndCasts(Item* pItem); + void DestroyItem(uint8 bag, uint8 slot, bool update); + void DestroyItemCount(uint32 item, uint32 count, bool update, bool unequip_check = false, bool inBankAlso = false); + void DestroyItemCount(Item* item, uint32& count, bool update); + void DestroyConjuredItems(bool update); + void DestroyZoneLimitedItem(bool update, uint32 new_zone); + void SplitItem(uint16 src, uint16 dst, uint32 count); + void SwapItem(uint16 src, uint16 dst); + void AddItemToBuyBackSlot(Item* pItem); + Item* GetItemFromBuyBackSlot(uint32 slot); + void RemoveItemFromBuyBackSlot(uint32 slot, bool del); + + void TakeExtendedCost(uint32 extendedCostId); + + void SendEquipError(InventoryResult msg, Item* pItem, Item* pItem2 = NULL, uint32 itemid = 0) const; + void SendBuyError(BuyResult msg, Creature* pCreature, uint32 item, uint32 param); + void SendSellError(SellResult msg, Creature* pCreature, ObjectGuid itemGuid, uint32 param); + void AddWeaponProficiency(uint32 newflag) { m_WeaponProficiency |= newflag; } + void AddArmorProficiency(uint32 newflag) { m_ArmorProficiency |= newflag; } + uint32 GetWeaponProficiency() const { return m_WeaponProficiency; } + uint32 GetArmorProficiency() const { return m_ArmorProficiency; } + bool IsTwoHandUsed() const + { + Item* mainItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + return mainItem && mainItem->GetProto()->InventoryType == INVTYPE_2HWEAPON && !CanTitanGrip(); + } + bool HasTwoHandWeaponInOneHand() const + { + Item* offItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + Item* mainItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + return offItem && ((mainItem && mainItem->GetProto()->InventoryType == INVTYPE_2HWEAPON) || offItem->GetProto()->InventoryType == INVTYPE_2HWEAPON); + } + void SendNewItem(Item* item, uint32 count, bool received, bool created, bool broadcast = false); + bool BuyItemFromVendorSlot(ObjectGuid vendorGuid, uint32 vendorslot, uint32 item, uint32 count, uint8 bag, uint8 slot); + bool BuyCurrencyFromVendorSlot(ObjectGuid vendorGuid, uint32 vendorslot, uint32 currencyId, uint32 count); + + float GetReputationPriceDiscount(Creature const* pCreature) const; + + Player* GetTrader() const { return m_trade ? m_trade->GetTrader() : NULL; } + TradeData* GetTradeData() const { return m_trade; } + void TradeCancel(bool sendback); + + void UpdateEnchantTime(uint32 time); + void UpdateItemDuration(uint32 time, bool realtimeonly = false); + void AddEnchantmentDurations(Item* item); + void RemoveEnchantmentDurations(Item* item); + void RemoveAllEnchantments(EnchantmentSlot slot); + void AddEnchantmentDuration(Item* item, EnchantmentSlot slot, uint32 duration); + void ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool apply_dur = true, bool ignore_condition = false); + void ApplyEnchantment(Item* item, bool apply); + void ApplyReforgeEnchantment(Item* item, bool apply); + void SendEnchantmentDurations(); + void BuildEnchantmentsInfoData(WorldPacket* data); + void AddItemDurations(Item* item); + void RemoveItemDurations(Item* item); + void SendItemDurations(); + void LoadCorpse(); + void LoadPet(); + + uint32 m_stableSlots; + + uint32 GetEquipGearScore(bool withBags = true, bool withBank = false); + void ResetCachedGearScore() { m_cachedGS = 0; } + typedef std::vector < uint32/*item level*/ > GearScoreVec; + + /*********************************************************/ + /*** GOSSIP SYSTEM ***/ + /*********************************************************/ + + void PrepareGossipMenu(WorldObject* pSource, uint32 menuId = 0); + void SendPreparedGossip(WorldObject* pSource); + void OnGossipSelect(WorldObject* pSource, uint32 gossipListId, uint32 menuId); + + uint32 GetGossipTextId(uint32 menuId, WorldObject* pSource); + uint32 GetGossipTextId(WorldObject* pSource); + uint32 GetDefaultGossipMenuForSource(WorldObject* pSource); + + /*********************************************************/ + /*** QUEST SYSTEM ***/ + /*********************************************************/ + + // Return player level when QuestLevel is dynamic (-1) + uint32 GetQuestLevelForPlayer(Quest const* pQuest) const { return pQuest && (pQuest->GetQuestLevel() > 0) ? (uint32)pQuest->GetQuestLevel() : getLevel(); } + + void PrepareQuestMenu(ObjectGuid guid); + void SendPreparedQuest(ObjectGuid guid); + bool IsActiveQuest(uint32 quest_id) const; // can be taken or taken + + // Quest is taken and not yet rewarded + // if completed_or_not = 0 (or any other value except 1 or 2) - returns true, if quest is taken and doesn't depend if quest is completed or not + // if completed_or_not = 1 - returns true, if quest is taken but not completed + // if completed_or_not = 2 - returns true, if quest is taken and already completed + bool IsCurrentQuest(uint32 quest_id, uint8 completed_or_not = 0) const; // taken and not yet rewarded + + Quest const* GetNextQuest(ObjectGuid guid, Quest const* pQuest); + bool CanSeeStartQuest(Quest const* pQuest) const; + bool CanTakeQuest(Quest const* pQuest, bool msg) const; + bool CanAddQuest(Quest const* pQuest, bool msg) const; + bool CanCompleteQuest(uint32 quest_id) const; + bool CanCompleteRepeatableQuest(Quest const* pQuest) const; + bool CanRewardQuest(Quest const* pQuest, bool msg) const; + bool CanRewardQuest(Quest const* pQuest, uint32 reward, bool msg) const; + void AddQuest(Quest const* pQuest, Object* questGiver); + void CompleteQuest(uint32 quest_id); + void IncompleteQuest(uint32 quest_id); + void RewardQuest(Quest const* pQuest, uint32 reward, Object* questGiver, bool announce = true); + + void FailQuest(uint32 quest_id); + bool SatisfyQuestSkill(Quest const* qInfo, bool msg) const; + bool SatisfyQuestSpell(Quest const* qInfo, bool msg) const; + bool SatisfyQuestLevel(Quest const* qInfo, bool msg) const; + bool SatisfyQuestLog(bool msg) const; + bool SatisfyQuestPreviousQuest(Quest const* qInfo, bool msg) const; + bool SatisfyQuestClass(Quest const* qInfo, bool msg) const; + bool SatisfyQuestRace(Quest const* qInfo, bool msg) const; + bool SatisfyQuestReputation(Quest const* qInfo, bool msg) const; + bool SatisfyQuestStatus(Quest const* qInfo, bool msg) const; + bool SatisfyQuestTimed(Quest const* qInfo, bool msg) const; + bool SatisfyQuestExclusiveGroup(Quest const* qInfo, bool msg) const; + bool SatisfyQuestNextChain(Quest const* qInfo, bool msg) const; + bool SatisfyQuestPrevChain(Quest const* qInfo, bool msg) const; + bool SatisfyQuestDay(Quest const* qInfo, bool msg) const; + bool SatisfyQuestWeek(Quest const* qInfo, bool msg) const; + bool SatisfyQuestMonth(Quest const* qInfo, bool msg) const; + bool CanGiveQuestSourceItemIfNeed(Quest const* pQuest, ItemPosCountVec* dest = NULL) const; + void GiveQuestSourceItemIfNeed(Quest const* pQuest); + bool TakeQuestSourceItem(uint32 quest_id, bool msg); + bool GetQuestRewardStatus(uint32 quest_id) const; + QuestStatus GetQuestStatus(uint32 quest_id) const; + void SetQuestStatus(uint32 quest_id, QuestStatus status); + + void SetDailyQuestStatus(uint32 quest_id); + void SetWeeklyQuestStatus(uint32 quest_id); + void SetMonthlyQuestStatus(uint32 quest_id); + void ResetDailyQuestStatus(); + void ResetWeeklyQuestStatus(); + void ResetMonthlyQuestStatus(); + + uint16 FindQuestSlot(uint32 quest_id) const; + uint32 GetQuestSlotQuestId(uint16 slot) const { return GetUInt32Value(PLAYER_QUEST_LOG_1_1 + slot * MAX_QUEST_OFFSET + QUEST_ID_OFFSET); } + void SetQuestSlot(uint16 slot, uint32 quest_id, uint32 timer = 0) + { + SetUInt32Value(PLAYER_QUEST_LOG_1_1 + slot * MAX_QUEST_OFFSET + QUEST_ID_OFFSET, quest_id); + SetUInt32Value(PLAYER_QUEST_LOG_1_1 + slot * MAX_QUEST_OFFSET + QUEST_STATE_OFFSET, 0); + SetUInt32Value(PLAYER_QUEST_LOG_1_1 + slot * MAX_QUEST_OFFSET + QUEST_COUNTS_OFFSET, 0); + SetUInt32Value(PLAYER_QUEST_LOG_1_1 + slot * MAX_QUEST_OFFSET + QUEST_COUNTS_OFFSET + 1, 0); + SetUInt32Value(PLAYER_QUEST_LOG_1_1 + slot * MAX_QUEST_OFFSET + QUEST_TIME_OFFSET, timer); + } + void SetQuestSlotCounter(uint16 slot, uint8 counter, uint16 count) + { + uint64 val = GetUInt64Value(PLAYER_QUEST_LOG_1_1 + slot * MAX_QUEST_OFFSET + QUEST_COUNTS_OFFSET); + val &= ~((uint64)0xFFFF << (counter * 16)); + val |= ((uint64)count << (counter * 16)); + SetUInt64Value(PLAYER_QUEST_LOG_1_1 + slot * MAX_QUEST_OFFSET + QUEST_COUNTS_OFFSET, val); + } + void SetQuestSlotState(uint16 slot, uint32 state) { SetFlag(PLAYER_QUEST_LOG_1_1 + slot * MAX_QUEST_OFFSET + QUEST_STATE_OFFSET, state); } + void RemoveQuestSlotState(uint16 slot, uint32 state) { RemoveFlag(PLAYER_QUEST_LOG_1_1 + slot * MAX_QUEST_OFFSET + QUEST_STATE_OFFSET, state); } + void SetQuestSlotTimer(uint16 slot, uint32 timer) { SetUInt32Value(PLAYER_QUEST_LOG_1_1 + slot * MAX_QUEST_OFFSET + QUEST_TIME_OFFSET, timer); } + void SwapQuestSlot(uint16 slot1, uint16 slot2) + { + for (int i = 0; i < MAX_QUEST_OFFSET; ++i) + { + uint32 temp1 = GetUInt32Value(PLAYER_QUEST_LOG_1_1 + MAX_QUEST_OFFSET * slot1 + i); + uint32 temp2 = GetUInt32Value(PLAYER_QUEST_LOG_1_1 + MAX_QUEST_OFFSET * slot2 + i); + + SetUInt32Value(PLAYER_QUEST_LOG_1_1 + MAX_QUEST_OFFSET * slot1 + i, temp2); + SetUInt32Value(PLAYER_QUEST_LOG_1_1 + MAX_QUEST_OFFSET * slot2 + i, temp1); + } + } + uint32 GetReqKillOrCastCurrentCount(uint32 quest_id, int32 entry); + void AreaExploredOrEventHappens(uint32 questId); + void GroupEventHappens(uint32 questId, WorldObject const* pEventObject); + void CurrencyAddedQuestCheck(uint32 entry); + void CurrencyRemovedQuestCheck(uint32 entry); + void ItemAddedQuestCheck(uint32 entry, uint32 count); + void ItemRemovedQuestCheck(uint32 entry, uint32 count); + void SpellAddedQuestCheck(uint32 entry); + void SpellRemovedQuestCheck(uint32 entry); + void KilledMonster(CreatureInfo const* cInfo, ObjectGuid guid); + void KilledMonsterCredit(uint32 entry, ObjectGuid guid = ObjectGuid()); + void CastedCreatureOrGO(uint32 entry, ObjectGuid guid, uint32 spell_id, bool original_caster = true); + void TalkedToCreature(uint32 entry, ObjectGuid guid); + void MoneyChanged(uint32 value); + void ReputationChanged(FactionEntry const* factionEntry); + bool HasQuestForItem(uint32 itemid) const; + bool HasQuestForGO(int32 GOId) const; + void UpdateForQuestWorldObjects(); + bool CanShareQuest(uint32 quest_id) const; + + void SendQuestCompleteEvent(uint32 quest_id); + void SendQuestReward(Quest const* pQuest, uint32 XP, Object* questGiver); + void SendQuestFailed(uint32 quest_id, InventoryResult reason = EQUIP_ERR_OK); + void SendQuestTimerFailed(uint32 quest_id); + void SendCanTakeQuestResponse(uint32 msg) const; + void SendQuestConfirmAccept(Quest const* pQuest, Player* pReceiver); + void SendPushToPartyResponse(Player* pPlayer, uint32 msg); + void SendQuestUpdateAddCreatureOrGo(Quest const* pQuest, ObjectGuid guid, uint32 creatureOrGO_idx, uint32 count); + + ObjectGuid GetDividerGuid() const { return m_dividerGuid; } + void SetDividerGuid(ObjectGuid guid) { m_dividerGuid = guid; } + void ClearDividerGuid() { m_dividerGuid.Clear(); } + + uint32 GetInGameTime() { return m_ingametime; } + + void SetInGameTime(uint32 time) { m_ingametime = time; } + + void AddTimedQuest(uint32 quest_id) { m_timedquests.insert(quest_id); } + void RemoveTimedQuest(uint32 quest_id) { m_timedquests.erase(quest_id); } + + /*********************************************************/ + /*** LOAD SYSTEM ***/ + /*********************************************************/ + + bool LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder); + + static uint32 GetZoneIdFromDB(ObjectGuid guid); + static uint32 GetLevelFromDB(ObjectGuid guid); + static bool LoadPositionFromDB(ObjectGuid guid, uint32& mapid, float& x, float& y, float& z, float& o, bool& in_flight); + + /*********************************************************/ + /*** SAVE SYSTEM ***/ + /*********************************************************/ + + void SaveToDB(); + void SaveInventoryAndGoldToDB(); // fast save function for item/money cheating preventing + void SaveGoldToDB(); + static void SetUInt32ValueInArray(Tokens& data, uint16 index, uint32 value); + static void SetFloatValueInArray(Tokens& data, uint16 index, float value); + static void Customize(ObjectGuid guid, uint8 gender, uint8 skin, uint8 face, uint8 hairStyle, uint8 hairColor, uint8 facialHair); + static void SavePositionInDB(ObjectGuid guid, uint32 mapid, float x, float y, float z, float o, uint32 zone); + + static void DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRealmChars = true, bool deleteFinally = false); + static void DeleteOldCharacters(); + static void DeleteOldCharacters(uint32 keepDays); + + bool m_mailsUpdated; + + void SendPetTameFailure(PetTameFailureReason reason); + + void SetBindPoint(ObjectGuid guid); + void SendTalentWipeConfirm(ObjectGuid guid); + void RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker); + void SendPetSkillWipeConfirm(); + void CalcRage(uint32 damage, bool attacker); + void RegenerateAll(uint32 diff = REGEN_TIME_FULL); + void Regenerate(Powers power, uint32 diff); + void RegenerateHealth(uint32 diff); + void setRegenTimer(uint32 time) {m_regenTimer = time;} + void setWeaponChangeTimer(uint32 time) {m_weaponChangeTimer = time;} + + uint64 GetMoney() const { return GetUInt64Value(PLAYER_FIELD_COINAGE); } + void ModifyMoney(int64 d) + { + if (d < 0) + SetMoney(GetMoney() > uint64(-d) ? GetMoney() + d : 0); + else + SetMoney(GetMoney() < uint64(MAX_MONEY_AMOUNT - d) ? GetMoney() + d : MAX_MONEY_AMOUNT); + + // "At Gold Limit" + if (GetMoney() >= MAX_MONEY_AMOUNT) + SendEquipError(EQUIP_ERR_TOO_MUCH_GOLD, NULL, NULL); + } + void SetMoney(uint64 value) + { + SetUInt64Value(PLAYER_FIELD_COINAGE, value); + MoneyChanged(value); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_GOLD_VALUE_OWNED); + } + + QuestStatusMap& getQuestStatusMap() { return mQuestStatus; }; + + ObjectGuid const& GetSelectionGuid() const { return m_curSelectionGuid; } + void SetSelectionGuid(ObjectGuid guid) { m_curSelectionGuid = guid; SetTargetGuid(guid); } + + uint8 GetComboPoints() const { return m_comboPoints; } + ObjectGuid const& GetComboTargetGuid() const { return m_comboTargetGuid; } + + void AddComboPoints(Unit* target, int8 count); + void ClearComboPoints(); + void SendComboPoints(); + + void SendMailResult(uint32 mailId, MailResponseType mailAction, MailResponseResult mailError, uint32 equipError = 0, uint32 item_guid = 0, uint32 item_count = 0); + void SendNewMail(); + void UpdateNextMailTimeAndUnreads(); + void AddNewMailDeliverTime(time_t deliver_time); + + void RemoveMail(uint32 id); + + void AddMail(Mail* mail) { m_mail.push_front(mail);}// for call from WorldSession::SendMailTo + uint32 GetMailSize() { return m_mail.size(); } + Mail* GetMail(uint32 id); + + PlayerMails::iterator GetMailBegin() { return m_mail.begin();} + PlayerMails::iterator GetMailEnd() { return m_mail.end();} + + /*********************************************************/ + /*** MAILED ITEMS SYSTEM ***/ + /*********************************************************/ + + uint8 unReadMails; + time_t m_nextMailDelivereTime; + + typedef UNORDERED_MAP ItemMap; + + ItemMap mMitems; // template defined in objectmgr.cpp + + Item* GetMItem(uint32 id) + { + ItemMap::const_iterator itr = mMitems.find(id); + return itr != mMitems.end() ? itr->second : NULL; + } + + void AddMItem(Item* it) + { + MANGOS_ASSERT(it); + // ASSERT deleted, because items can be added before loading + mMitems[it->GetGUIDLow()] = it; + } + + bool RemoveMItem(uint32 id) + { + return mMitems.erase(id) ? true : false; + } + + void PetSpellInitialize(); + void SendPetGUIDs(); + void CharmSpellInitialize(); + void PossessSpellInitialize(); + void RemovePetActionBar(); + + bool HasSpell(uint32 spell) const override; + bool HasActiveSpell(uint32 spell) const; // show in spellbook + TrainerSpellState GetTrainerSpellState(TrainerSpell const* trainer_spell, uint32 reqLevel) const; + bool IsSpellFitByClassAndRace(uint32 spell_id, uint32* pReqlevel = NULL) const; + bool IsNeedCastPassiveLikeSpellAtLearn(SpellEntry const* spellInfo) const; + bool IsImmuneToSpellEffect(SpellEntry const* spellInfo, SpellEffectIndex index, bool castOnSelf) const override; + + void SendProficiency(ItemClass itemClass, uint32 itemSubclassMask); + void SendInitialSpells(); + bool addSpell(uint32 spell_id, bool active, bool learning, bool dependent, bool disabled); + void learnSpell(uint32 spell_id, bool dependent); + void removeSpell(uint32 spell_id, bool disabled = false, bool learn_low_rank = true, bool sendUpdate = true); + void resetSpells(); + void learnDefaultSpells(); + void learnQuestRewardedSpells(); + void learnQuestRewardedSpells(Quest const* quest); + void learnSpellHighRank(uint32 spellid); + + uint32 GetFreeTalentPoints() const { return m_freeTalentPoints; } + void SetFreeTalentPoints(uint32 points) { m_freeTalentPoints = points; } + void UpdateFreeTalentPoints(bool resetIfNeed = true); + uint32 GetPrimaryTalentTree(uint8 spec) const { return m_talentsPrimaryTree[spec]; } + void SetPrimaryTalentTree(uint8 spec, uint32 tree) { m_talentsPrimaryTree[spec] = tree; } + bool resetTalents(bool no_cost = false, bool all_specs = false); + uint32 resetTalentsCost() const; + void InitTalentForLevel(); + void BuildPlayerTalentsInfoData(WorldPacket* data); + void BuildPetTalentsInfoData(WorldPacket* data); + void SendTalentsInfoData(bool pet); + bool LearnTalent(uint32 talentId, uint32 talentRank); + void LearnPetTalent(ObjectGuid petGuid, uint32 talentId, uint32 talentRank); + + uint32 CalculateTalentsPoints() const; + + // Dual Spec + uint8 GetActiveSpec() { return m_activeSpec; } + void SetActiveSpec(uint8 spec) { m_activeSpec = spec; } + uint8 GetSpecsCount() { return m_specsCount; } + void SetSpecsCount(uint8 count) { m_specsCount = count; } + void ActivateSpec(uint8 specNum); + void UpdateSpecCount(uint8 count); + + void InitGlyphsForLevel(); + void SetGlyphSlot(uint8 slot, uint32 slottype) { SetUInt32Value(PLAYER_FIELD_GLYPH_SLOTS_1 + slot, slottype); } + uint32 GetGlyphSlot(uint8 slot) const { return GetUInt32Value(PLAYER_FIELD_GLYPH_SLOTS_1 + slot); } + void SetGlyph(uint8 slot, uint32 glyph) { m_glyphs[m_activeSpec][slot].SetId(glyph); } + uint32 GetGlyph(uint8 slot) { return m_glyphs[m_activeSpec][slot].GetId(); } + void ApplyGlyph(uint8 slot, bool apply); + void ApplyGlyphs(bool apply); + + uint32 GetFreePrimaryProfessionPoints() const { return GetUInt32Value(PLAYER_CHARACTER_POINTS); } + void SetFreePrimaryProfessions(uint16 profs) { SetUInt32Value(PLAYER_CHARACTER_POINTS, profs); } + void InitPrimaryProfessions(); + + PlayerSpellMap const& GetSpellMap() const { return m_spells; } + PlayerSpellMap& GetSpellMap() { return m_spells; } + + SpellCooldowns const& GetSpellCooldownMap() const { return m_spellCooldowns; } + + PlayerTalent const* GetKnownTalentById(int32 talentId) const; + SpellEntry const* GetKnownTalentRankById(int32 talentId) const; + + void AddSpellMod(Aura* aura, bool apply); + template T ApplySpellMod(uint32 spellId, SpellModOp op, T& basevalue, Spell const* spell = NULL); + + static uint32 const infinityCooldownDelay = MONTH; // used for set "infinity cooldowns" for spells and check + static uint32 const infinityCooldownDelayCheck = MONTH / 2; + bool HasSpellCooldown(uint32 spell_id) const + { + SpellCooldowns::const_iterator itr = m_spellCooldowns.find(spell_id); + return itr != m_spellCooldowns.end() && itr->second.end > time(NULL); + } + time_t GetSpellCooldownDelay(uint32 spell_id) const + { + SpellCooldowns::const_iterator itr = m_spellCooldowns.find(spell_id); + time_t t = time(NULL); + return itr != m_spellCooldowns.end() && itr->second.end > t ? itr->second.end - t : 0; + } + void AddSpellAndCategoryCooldowns(SpellEntry const* spellInfo, uint32 itemId, Spell* spell = NULL, bool infinityCooldown = false); + void AddSpellCooldown(uint32 spell_id, uint32 itemid, time_t end_time); + void SendCooldownEvent(SpellEntry const* spellInfo, uint32 itemId = 0, Spell* spell = NULL); + void ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs) override; + void RemoveSpellCooldown(uint32 spell_id, bool update = false); + void RemoveSpellCategoryCooldown(uint32 cat, bool update = false); + void SendClearCooldown(uint32 spell_id, Unit* target); + + GlobalCooldownMgr& GetGlobalCooldownMgr() { return m_GlobalCooldownMgr; } + + void RemoveArenaSpellCooldowns(); + void RemoveAllSpellCooldown(); + void _LoadSpellCooldowns(QueryResult* result); + void _SaveSpellCooldowns(); + void SetLastPotionId(uint32 item_id) { m_lastPotionId = item_id; } + uint32 GetLastPotionId() { return m_lastPotionId; } + void UpdatePotionCooldown(Spell* spell = NULL); + + void setResurrectRequestData(ObjectGuid guid, uint32 mapId, float X, float Y, float Z, uint32 health, uint32 mana) + { + m_resurrectGuid = guid; + m_resurrectMap = mapId; + m_resurrectX = X; + m_resurrectY = Y; + m_resurrectZ = Z; + m_resurrectHealth = health; + m_resurrectMana = mana; + } + void clearResurrectRequestData() { setResurrectRequestData(ObjectGuid(), 0, 0.0f, 0.0f, 0.0f, 0, 0); } + bool isRessurectRequestedBy(ObjectGuid guid) const { return m_resurrectGuid == guid; } + bool isRessurectRequested() const { return !m_resurrectGuid.IsEmpty(); } + void ResurectUsingRequestData(); + + uint32 getCinematic() { return m_cinematic; } + void setCinematic(uint32 cine) { m_cinematic = cine; } + + static bool IsActionButtonDataValid(uint8 button, uint32 action, uint8 type, Player* player, bool msg = true); + ActionButton* addActionButton(uint8 spec, uint8 button, uint32 action, uint8 type); + void removeActionButton(uint8 spec, uint8 button); + void SendInitialActionButtons() const; + void SendLockActionButtons() const; + ActionButton const* GetActionButton(uint8 button); + + PvPInfo pvpInfo; + void UpdatePvP(bool state, bool ovrride = false); + void UpdateZone(uint32 newZone, uint32 newArea); + void UpdateArea(uint32 newArea); + uint32 GetCachedZoneId() const { return m_zoneUpdateId; } + + void UpdateZoneDependentAuras(); + void UpdateAreaDependentAuras(); // subzones + void UpdateZoneDependentPets(); + + void UpdateAfkReport(time_t currTime); + void UpdatePvPFlag(time_t currTime); + void UpdateContestedPvP(uint32 currTime); + void SetContestedPvPTimer(uint32 newTime) {m_contestedPvPTimer = newTime;} + void ResetContestedPvP() + { + clearUnitState(UNIT_STAT_ATTACK_PLAYER); + RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_CONTESTED_PVP); + m_contestedPvPTimer = 0; + } + + /** todo: -maybe move UpdateDuelFlag+DuelComplete to independent DuelHandler.. **/ + DuelInfo* duel; + bool IsInDuelWith(Player const* player) const { return duel && duel->opponent == player && duel->startTime != 0; } + void UpdateDuelFlag(time_t currTime); + void CheckDuelDistance(time_t currTime); + void DuelComplete(DuelCompleteType type); + void SendDuelCountdown(uint32 counter); + + bool IsGroupVisibleFor(Player* p) const; + bool IsInSameGroupWith(Player const* p) const; + bool IsInSameRaidWith(Player const* p) const { return p == this || (GetGroup() != NULL && GetGroup() == p->GetGroup()); } + void UninviteFromGroup(); + static void RemoveFromGroup(Group* group, ObjectGuid guid); + void RemoveFromGroup() { RemoveFromGroup(GetGroup(), GetObjectGuid()); } + void SendUpdateToOutOfRangeGroupMembers(); + void SetAllowLowLevelRaid(bool allow) { ApplyModFlag(PLAYER_FLAGS, PLAYER_FLAGS_ENABLE_LOW_LEVEL_RAID, allow); } + bool GetAllowLowLevelRaid() const { return HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_ENABLE_LOW_LEVEL_RAID); } + + void SetInGuild(uint32 GuildId); + void SetGuildLevel(uint32 level) { SetUInt32Value(PLAYER_GUILDLEVEL, level); } + void SetRank(uint32 rankId){ SetUInt32Value(PLAYER_GUILDRANK, rankId); } + void SetGuildInvited(uint32 GuildId, ObjectGuid inviter = ObjectGuid()) { m_GuildIdInvited = GuildId; m_GuildInviterGuid = inviter; } + uint32 GetGuildId() const { return GetGuildGuid().GetCounter(); } + ObjectGuid GetGuildGuid() const { return GetGuidValue(OBJECT_FIELD_DATA); } + std::string GetGuildName() const; + static uint32 GetGuildIdFromDB(ObjectGuid guid); + static ObjectGuid GetGuildGuidFromDB(ObjectGuid guid); + void SendGuildDeclined(std::string name, bool autodecline); + uint32 GetRank() { return GetUInt32Value(PLAYER_GUILDRANK); } + static uint32 GetRankFromDB(ObjectGuid guid); + int GetGuildIdInvited() const { return m_GuildIdInvited; } + ObjectGuid GetGuildInviterGuid() const { return m_GuildInviterGuid; } + static void RemovePetitionsAndSigns(ObjectGuid guid); + void SendPetitionSignResult(ObjectGuid petitionGuid, Player* player, uint32 result); + void SendPetitionTurnInResult(uint32 result); + + // Arena Team + void SetInArenaTeam(uint32 ArenaTeamId, uint8 slot, ArenaType type) + { + SetArenaTeamInfoField(slot, ARENA_TEAM_ID, ArenaTeamId); + SetArenaTeamInfoField(slot, ARENA_TEAM_TYPE, type); + } + void SetArenaTeamInfoField(uint8 slot, ArenaTeamInfoType type, uint32 value) + { + SetUInt32Value(PLAYER_FIELD_ARENA_TEAM_INFO_1_1 + (slot * ARENA_TEAM_END) + type, value); + } + uint32 GetArenaTeamId(uint8 slot) { return GetUInt32Value(PLAYER_FIELD_ARENA_TEAM_INFO_1_1 + (slot * ARENA_TEAM_END) + ARENA_TEAM_ID); } + uint32 GetArenaPersonalRating(uint8 slot) { return GetUInt32Value(PLAYER_FIELD_ARENA_TEAM_INFO_1_1 + (slot * ARENA_TEAM_END) + ARENA_TEAM_PERSONAL_RATING); } + static uint32 GetArenaTeamIdFromDB(ObjectGuid guid, ArenaType type); + void SetArenaTeamIdInvited(uint32 ArenaTeamId) { m_ArenaTeamIdInvited = ArenaTeamId; } + uint32 GetArenaTeamIdInvited() { return m_ArenaTeamIdInvited; } + static void LeaveAllArenaTeams(ObjectGuid guid); + + Difficulty GetDifficulty(bool isRaid) const { return isRaid ? m_raidDifficulty : m_dungeonDifficulty; } + Difficulty GetDungeonDifficulty() const { return m_dungeonDifficulty; } + Difficulty GetRaidDifficulty() const { return m_raidDifficulty; } + void SetDungeonDifficulty(Difficulty dungeon_difficulty) { m_dungeonDifficulty = dungeon_difficulty; } + void SetRaidDifficulty(Difficulty raid_difficulty) { m_raidDifficulty = raid_difficulty; } + + bool UpdateSkill(uint32 skill_id, uint32 step); + bool UpdateSkillPro(uint16 SkillId, int32 Chance, uint32 step); + + bool UpdateCraftSkill(uint32 spellid); + bool UpdateGatherSkill(uint32 SkillId, uint32 SkillValue, uint32 RedLevel, uint32 Multiplicator = 1); + bool UpdateFishingSkill(); + + float GetHealthBonusFromStamina(); + float GetManaBonusFromIntellect(); + + bool UpdateStats(Stats stat) override; + bool UpdateAllStats() override; + void UpdateResistances(uint32 school) override; + void UpdateArmor() override; + void UpdateMaxHealth() override; + void UpdateMaxPower(Powers power) override; + void UpdateAttackPowerAndDamage(bool ranged = false) override; + void UpdateShieldBlockDamageValue(); + void UpdateDamagePhysical(WeaponAttackType attType) override; + void ApplySpellPowerBonus(int32 amount, bool apply); + void UpdateSpellDamageAndHealingBonus(); + void ApplyRatingMod(CombatRating cr, int32 value, bool apply); + void UpdateRating(CombatRating cr); + void UpdateAllRatings(); + + void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, float& min_damage, float& max_damage); + + float GetMeleeCritFromAgility(); + void GetDodgeFromAgility(float& diminishing, float& nondiminishing); + void GetParryFromStrength(float& diminishing, float& nondiminishing); + float GetSpellCritFromIntellect(); + float OCTRegenMPPerSpirit(); + float GetRatingMultiplier(CombatRating cr) const; + float GetRatingBonusValue(CombatRating cr) const; + // Returns base spellpower bonus from items without intellect bonus + uint32 GetBaseSpellPowerBonus() const { return m_baseSpellPower; } + + float GetExpertiseDodgeOrParryReduction(WeaponAttackType attType) const; + void UpdateBlockPercentage(); + void UpdateCritPercentage(WeaponAttackType attType); + void UpdateAllCritPercentages(); + void UpdateParryPercentage(); + void UpdateDodgePercentage(); + void UpdateMeleeHitChances(); + void UpdateRangedHitChances(); + void UpdateSpellHitChances(); + + void UpdateAllSpellCritChances(); + void UpdateSpellCritChance(uint32 school); + void UpdateExpertise(WeaponAttackType attType); + void UpdateArmorPenetration(); + void ApplyManaRegenBonus(int32 amount, bool apply); + void ApplyHealthRegenBonus(int32 amount, bool apply); + void UpdateManaRegen(); + void UpdateMasteryAuras(); + void UpdateArmorSpecializations(); + bool FitArmorSpecializationRules(SpellEntry const * spellProto) const; + + ObjectGuid const& GetLootGuid() const { return m_lootGuid; } + void SetLootGuid(ObjectGuid const& guid) { m_lootGuid = guid; } + + void RemovedInsignia(Player* looterPlr); + + WorldSession* GetSession() const { return m_session; } + void SetSession(WorldSession* s) { m_session = s; } + + void BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const override; + void DestroyForPlayer(Player* target, bool anim = false) const override; + void SendLogXPGain(uint32 GivenXP, Unit* victim, uint32 RestXP); + + uint8 LastSwingErrorMsg() const { return m_swingErrorMsg; } + void SwingErrorMsg(uint8 val) { m_swingErrorMsg = val; } + + // notifiers + void SendAttackSwingCantAttack(); + void SendAttackSwingCancelAttack(); + void SendAttackSwingDeadTarget(); + void SendAttackSwingNotInRange(); + void SendAttackSwingBadFacingAttack(); + void SendAutoRepeatCancel(Unit* target); + void SendExplorationExperience(uint32 Area, uint32 Experience); + + void SendDungeonDifficulty(bool IsInGroup); + void SendRaidDifficulty(bool IsInGroup); + void ResetInstances(InstanceResetMethod method, bool isRaid); + void SendResetInstanceSuccess(uint32 MapId); + void SendResetInstanceFailed(uint32 reason, uint32 MapId); + void SendResetFailedNotify(uint32 mapid); + + bool SetPosition(float x, float y, float z, float orientation, bool teleport = false); + void UpdateUnderwaterState(Map* m, float x, float y, float z); + + void SendMessageToSet(WorldPacket* data, bool self) const override;// overwrite Object::SendMessageToSet + void SendMessageToSetInRange(WorldPacket* data, float fist, bool self) const override; + // overwrite Object::SendMessageToSetInRange + void SendMessageToSetInRange(WorldPacket* data, float dist, bool self, bool own_team_only) const; + + Corpse* GetCorpse() const; + void SpawnCorpseBones(); + Corpse* CreateCorpse(); + void KillPlayer(); + uint32 GetResurrectionSpellId(); + void ResurrectPlayer(float restore_percent, bool applySickness = false); + void BuildPlayerRepop(); + void RepopAtGraveyard(); + + void DurabilityLossAll(double percent, bool inventory); + void DurabilityLoss(Item* item, double percent); + void DurabilityPointsLossAll(int32 points, bool inventory); + void DurabilityPointsLoss(Item* item, int32 points); + void DurabilityPointLossForEquipSlot(EquipmentSlots slot); + uint32 DurabilityRepairAll(bool cost, float discountMod, bool guildBank); + uint32 DurabilityRepair(uint16 pos, bool cost, float discountMod, bool guildBank); + + void UpdateMirrorTimers(); + void StopMirrorTimers() + { + StopMirrorTimer(FATIGUE_TIMER); + StopMirrorTimer(BREATH_TIMER); + StopMirrorTimer(FIRE_TIMER); + } + + void SetRoot(bool enable) override; + void SetWaterWalk(bool enable) override; + + void JoinedChannel(Channel* c); + void LeftChannel(Channel* c); + void CleanupChannels(); + void UpdateLocalChannels(uint32 newZone); + void LeaveLFGChannel(); + + void SetSkill(uint16 id, uint16 currVal, uint16 maxVal, uint16 step = 0); + uint16 GetMaxSkillValue(uint32 skill) const; // max + perm. bonus + temp bonus + uint16 GetPureMaxSkillValue(uint32 skill) const; // max + uint16 GetSkillValue(uint32 skill) const; // skill value + perm. bonus + temp bonus + uint16 GetBaseSkillValue(uint32 skill) const; // skill value + perm. bonus + uint16 GetPureSkillValue(uint32 skill) const; // skill value + int16 GetSkillPermBonusValue(uint32 skill) const; + int16 GetSkillTempBonusValue(uint32 skill) const; + bool HasSkill(uint32 skill) const; + uint16 GetSkillStep(uint16 skill) const; + void learnSkillRewardedSpells(uint32 id, uint32 value); + + WorldLocation& GetTeleportDest() { return m_teleport_dest; } + bool IsBeingTeleported() const { return mSemaphoreTeleport_Near || mSemaphoreTeleport_Far; } + bool IsBeingTeleportedNear() const { return mSemaphoreTeleport_Near; } + bool IsBeingTeleportedFar() const { return mSemaphoreTeleport_Far; } + void SetSemaphoreTeleportNear(bool semphsetting) { mSemaphoreTeleport_Near = semphsetting; } + void SetSemaphoreTeleportFar(bool semphsetting) { mSemaphoreTeleport_Far = semphsetting; } + void ProcessDelayedOperations(); + + void CheckAreaExploreAndOutdoor(); + + static Team TeamForRace(uint8 race); + Team GetTeam() const { return m_team; } + static uint32 getFactionForRace(uint8 race); + void setFactionForRace(uint8 race); + + void InitDisplayIds(); + + bool IsAtGroupRewardDistance(WorldObject const* pRewardSource) const; + void RewardSinglePlayerAtKill(Unit* pVictim); + void RewardPlayerAndGroupAtEvent(uint32 creature_id, WorldObject* pRewardSource); + void RewardPlayerAndGroupAtCast(WorldObject* pRewardSource, uint32 spellid = 0); + bool isHonorOrXPTarget(Unit* pVictim) const; + + ReputationMgr& GetReputationMgr() { return m_reputationMgr; } + ReputationMgr const& GetReputationMgr() const { return m_reputationMgr; } + ReputationRank GetReputationRank(uint32 faction_id) const; + void RewardReputation(Unit* pVictim, float rate); + void RewardReputation(Quest const* pQuest); + int32 CalculateReputationGain(ReputationSource source, int32 rep, int32 faction, uint32 creatureOrQuestLevel = 0, bool noAuraBonus = false); + + void UpdateSkillsForLevel(); + void UpdateSkillsToMaxSkillsForLevel(); // for .levelup + void ModifySkillBonus(uint32 skillid, int32 val, bool talent); + + /*********************************************************/ + /*** CURRENCY SYSTEM ***/ + /*********************************************************/ + uint32 GetCurrencyCount(uint32 id) const; + uint32 GetCurrencySeasonCount(uint32 id) const; + uint32 GetCurrencyWeekCount(uint32 id) const; + void SendCurrencies() const; + void ModifyCurrencyCount(uint32 id, int32 count, bool modifyWeek = true, bool modifySeason = true, bool ignoreMultipliers = false); + bool HasCurrencyCount(uint32 id, uint32 count) const { return GetCurrencyCount(id) >= count; } + bool HasCurrencySeasonCount(uint32 id, uint32 count) const { return GetCurrencySeasonCount(id) >= count; } + void SetCurrencyCount(uint32 id, uint32 count); + void SendCurrencyWeekCap(uint32 id) const; + void SendCurrencyWeekCap(CurrencyTypesEntry const * currency) const; + void SetCurrencyFlags(uint32 currencyId, uint8 flags); + void ResetCurrencyWeekCounts(); + + /*********************************************************/ + /*** PVP SYSTEM ***/ + /*********************************************************/ + void UpdateHonorKills(); + bool RewardHonor(Unit *pVictim, uint32 groupsize, float honor = -1); + void SendPvPRewards(); + void SendRatedBGStats(); + + uint32 GetMaxPersonalArenaRatingRequirement(uint32 minarenaslot); + + // End of PvP System + + void SetDrunkValue(uint8 newDrunkValue, uint32 itemid = 0); + uint16 GetDrunkValue() const { return GetByteValue(PLAYER_BYTES_3, 1); } + static DrunkenState GetDrunkenstateByValue(uint8 value); + + uint32 GetDeathTimer() const { return m_deathTimer; } + uint32 GetCorpseReclaimDelay(bool pvp) const; + void UpdateCorpseReclaimDelay(); + void SendCorpseReclaimDelay(bool load = false); + + uint32 GetShieldBlockDamageValue() const override; // overwrite Unit version (virtual) + bool CanParry() const { return m_canParry; } + void SetCanParry(bool value); + bool CanBlock() const { return m_canBlock; } + void SetCanBlock(bool value); + bool CanDualWield() const { return m_canDualWield; } + void SetCanDualWield(bool value) { m_canDualWield = value; } + bool CanTitanGrip() const { return m_canTitanGrip; } + void SetCanTitanGrip(bool value) { m_canTitanGrip = value; } + bool CanTameExoticPets() const { return isGameMaster() || HasAuraType(SPELL_AURA_ALLOW_TAME_PET_TYPE); } + + void SetRegularAttackTime(); + void SetBaseModValue(BaseModGroup modGroup, BaseModType modType, float value) { m_auraBaseMod[modGroup][modType] = value; } + void HandleBaseModValue(BaseModGroup modGroup, BaseModType modType, float amount, bool apply); + float GetBaseModValue(BaseModGroup modGroup, BaseModType modType) const; + float GetTotalBaseModValue(BaseModGroup modGroup) const; + float GetTotalPercentageModValue(BaseModGroup modGroup) const { return m_auraBaseMod[modGroup][FLAT_MOD] + m_auraBaseMod[modGroup][PCT_MOD]; } + void _ApplyAllStatBonuses(); + void _RemoveAllStatBonuses(); + float GetArmorPenetrationPct() const { return m_armorPenetrationPct; } + int32 GetSpellPenetrationItemMod() const { return m_spellPenetrationItemMod; } + + void _ApplyWeaponDependentAuraMods(Item* item, WeaponAttackType attackType, bool apply); + void _ApplyWeaponDependentAuraCritMod(Item* item, WeaponAttackType attackType, Aura* aura, bool apply); + void _ApplyWeaponDependentAuraDamageMod(Item* item, WeaponAttackType attackType, Aura* aura, bool apply); + + void _ApplyItemMods(Item* item, uint8 slot, bool apply); + void _RemoveAllItemMods(); + void _ApplyAllItemMods(); + void _ApplyAllLevelScaleItemMods(bool apply); + void _ApplyItemBonuses(ItemPrototype const* proto, uint8 slot, bool apply, bool only_level_scale = false); + void _ApplyAmmoBonuses(); + bool EnchantmentFitsRequirements(uint32 enchantmentcondition, int8 slot); + void ToggleMetaGemsActive(uint8 exceptslot, bool apply); + void CorrectMetaGemEnchants(uint8 slot, bool apply); + void InitDataForForm(bool reapplyMods = false); + + void ApplyItemEquipSpell(Item* item, bool apply, bool form_change = false); + void ApplyEquipSpell(SpellEntry const* spellInfo, Item* item, bool apply, bool form_change = false); + void UpdateEquipSpellsAtFormChange(); + void CastItemCombatSpell(Unit* Target, WeaponAttackType attType); + void CastItemUseSpell(Item* item, SpellCastTargets const& targets, uint8 cast_count, uint32 glyphIndex); + + void ApplyItemOnStoreSpell(Item* item, bool apply); + void DestroyItemWithOnStoreSpell(Item* item, uint32 spellId); + + void SendEquipmentSetList(); + void SetEquipmentSet(uint32 index, EquipmentSet eqset); + void DeleteEquipmentSet(uint64 setGuid); + + void SendInitWorldStates(uint32 zone, uint32 area); + void SendUpdateWorldState(uint32 Field, uint32 Value); + void SendDirectMessage(WorldPacket* data) const; + void FillBGWeekendWorldStates(WorldPacket& data, uint32& count); + + void SendAurasForTarget(Unit* target); + + PlayerMenu* PlayerTalkClass; + std::vector ItemSetEff; + + void SendLoot(ObjectGuid guid, LootType loot_type); + void SendLootRelease(ObjectGuid guid); + void SendNotifyLootItemRemoved(uint8 lootSlot, bool currency = false); + void SendNotifyLootMoneyRemoved(); + + /*********************************************************/ + /*** BATTLEGROUND SYSTEM ***/ + /*********************************************************/ + + bool InBattleGround() const { return m_bgData.bgInstanceID != 0; } + bool InArena() const; + uint32 GetBattleGroundId() const { return m_bgData.bgInstanceID; } + BattleGroundTypeId GetBattleGroundTypeId() const { return m_bgData.bgTypeID; } + BattleGround* GetBattleGround() const; + + bool InBattleGroundQueue() const + { + for (int i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i) + if (m_bgBattleGroundQueueID[i].bgQueueTypeId != BATTLEGROUND_QUEUE_NONE) + return true; + return false; + } + + BattleGroundQueueTypeId GetBattleGroundQueueTypeId(uint32 index) const { return m_bgBattleGroundQueueID[index].bgQueueTypeId; } + BattleGroundQueueTypeId GetBattleGroundQueueTypeIdByClientInstance(uint32 instanceId) const + { + for (int i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i) + if (m_bgBattleGroundQueueID[i].invitedToInstance == instanceId) + return m_bgBattleGroundQueueID[i].bgQueueTypeId; + return BATTLEGROUND_QUEUE_NONE; + } + uint32 GetBattleGroundQueueIndex(BattleGroundQueueTypeId bgQueueTypeId) const + { + for (int i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i) + if (m_bgBattleGroundQueueID[i].bgQueueTypeId == bgQueueTypeId) + return i; + return PLAYER_MAX_BATTLEGROUND_QUEUES; + } + bool IsInvitedForBattleGroundQueueType(BattleGroundQueueTypeId bgQueueTypeId) const + { + for (int i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i) + if (m_bgBattleGroundQueueID[i].bgQueueTypeId == bgQueueTypeId) + return m_bgBattleGroundQueueID[i].invitedToInstance != 0; + return false; + } + bool InBattleGroundQueueForBattleGroundQueueType(BattleGroundQueueTypeId bgQueueTypeId) const + { + return GetBattleGroundQueueIndex(bgQueueTypeId) < PLAYER_MAX_BATTLEGROUND_QUEUES; + } + + void SetBattleGroundId(uint32 val, BattleGroundTypeId bgTypeId) + { + m_bgData.bgInstanceID = val; + m_bgData.bgTypeID = bgTypeId; + m_bgData.m_needSave = true; + } + uint32 AddBattleGroundQueueId(BattleGroundQueueTypeId val) + { + for (int i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i) + { + if (m_bgBattleGroundQueueID[i].bgQueueTypeId == BATTLEGROUND_QUEUE_NONE || m_bgBattleGroundQueueID[i].bgQueueTypeId == val) + { + m_bgBattleGroundQueueID[i].bgQueueTypeId = val; + m_bgBattleGroundQueueID[i].invitedToInstance = 0; + return i; + } + } + return PLAYER_MAX_BATTLEGROUND_QUEUES; + } + bool HasFreeBattleGroundQueueId() + { + for (int i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i) + if (m_bgBattleGroundQueueID[i].bgQueueTypeId == BATTLEGROUND_QUEUE_NONE) + return true; + return false; + } + void RemoveBattleGroundQueueId(BattleGroundQueueTypeId val) + { + for (int i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i) + { + if (m_bgBattleGroundQueueID[i].bgQueueTypeId == val) + { + m_bgBattleGroundQueueID[i].bgQueueTypeId = BATTLEGROUND_QUEUE_NONE; + m_bgBattleGroundQueueID[i].invitedToInstance = 0; + return; + } + } + } + void SetInviteForBattleGroundQueueType(BattleGroundQueueTypeId bgQueueTypeId, uint32 instanceId) + { + for (int i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i) + if (m_bgBattleGroundQueueID[i].bgQueueTypeId == bgQueueTypeId) + m_bgBattleGroundQueueID[i].invitedToInstance = instanceId; + } + bool IsInvitedForBattleGroundInstance(uint32 instanceId) const + { + for (int i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i) + if (m_bgBattleGroundQueueID[i].invitedToInstance == instanceId) + return true; + return false; + } + WorldLocation const& GetBattleGroundEntryPoint() const { return m_bgData.joinPos; } + void SetBattleGroundEntryPoint(); + + void SetBGTeam(Team team) { m_bgData.bgTeam = team; m_bgData.m_needSave = true; } + Team GetBGTeam() const { return m_bgData.bgTeam ? m_bgData.bgTeam : GetTeam(); } + + void LeaveBattleground(bool teleportToEntryPoint = true); + bool CanJoinToBattleground() const; + bool CanReportAfkDueToLimit(); + void ReportedAfkBy(Player* reporter); + void ClearAfkReports() { m_bgData.bgAfkReporter.clear(); } + + bool GetBGAccessByLevel(BattleGroundTypeId bgTypeId) const; + bool CanUseBattleGroundObject(); + bool isTotalImmune(); + + // returns true if the player is in active state for capture point capturing + bool CanUseCapturePoint(); + + /*********************************************************/ + /*** REST SYSTEM ***/ + /*********************************************************/ + + bool isRested() const { return GetRestTime() >= 10 * IN_MILLISECONDS; } + uint32 GetXPRestBonus(uint32 xp); + uint32 GetRestTime() const { return m_restTime; } + void SetRestTime(uint32 v) { m_restTime = v; } + + /*********************************************************/ + /*** ENVIROMENTAL SYSTEM ***/ + /*********************************************************/ + + uint32 EnvironmentalDamage(EnviromentalDamage type, uint32 damage); + + /*********************************************************/ + /*** FLOOD FILTER SYSTEM ***/ + /*********************************************************/ + + void UpdateSpeakTime(); + bool CanSpeak() const; + void ChangeSpeakTime(int utime); + + /*********************************************************/ + /*** VARIOUS SYSTEMS ***/ + /*********************************************************/ + bool HasMovementFlag(MovementFlags f) const; // for script access to m_movementInfo.HasMovementFlag + void UpdateFallInformationIfNeed(MovementInfo const& minfo, uint16 opcode); + void SetFallInformation(uint32 time, float z) + { + m_lastFallTime = time; + m_lastFallZ = z; + } + void HandleFall(MovementInfo const& movementInfo); + + bool isMoving() const { return m_movementInfo.HasMovementFlag(movementFlagsMask); } + bool isMovingOrTurning() const { return m_movementInfo.HasMovementFlag(movementOrTurningFlagsMask); } + + bool CanFly() const { return m_movementInfo.HasMovementFlag(MOVEFLAG_CAN_FLY); } + bool IsFlying() const { return m_movementInfo.HasMovementFlag(MOVEFLAG_FLYING); } + bool IsFreeFlying() const { return HasAuraType(SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED) || HasAuraType(SPELL_AURA_FLY); } + bool CanStartFlyInArea(uint32 mapid, uint32 zone, uint32 area) const; + + void SetClientControl(Unit* target, uint8 allowMove); + void SetMover(Unit* target) { m_mover = target ? target : this; } + Unit* GetMover() const { return m_mover; } + bool IsSelfMover() const { return m_mover == this; }// normal case for player not controlling other unit + + ObjectGuid const& GetFarSightGuid() const { return GetGuidValue(PLAYER_FARSIGHT); } + + // Transports + Transport* GetTransport() const { return m_transport; } + void SetTransport(Transport* t) { m_transport = t; } + + float GetTransOffsetX() const { return m_movementInfo.GetTransportPos()->x; } + float GetTransOffsetY() const { return m_movementInfo.GetTransportPos()->y; } + float GetTransOffsetZ() const { return m_movementInfo.GetTransportPos()->z; } + float GetTransOffsetO() const { return m_movementInfo.GetTransportPos()->o; } + uint32 GetTransTime() const { return m_movementInfo.GetTransportTime(); } + int8 GetTransSeat() const { return m_movementInfo.GetTransportSeat(); } + + uint32 GetSaveTimer() const { return m_nextSave; } + void SetSaveTimer(uint32 timer) { m_nextSave = timer; } + + // Recall position + uint32 m_recallMap; + float m_recallX; + float m_recallY; + float m_recallZ; + float m_recallO; + void SaveRecallPosition(); + + void SetHomebindToLocation(WorldLocation const& loc, uint32 area_id); + void RelocateToHomebind() { SetLocationMapId(m_homebindMapId); Relocate(m_homebindX, m_homebindY, m_homebindZ); } + bool TeleportToHomebind(uint32 options = 0) { return TeleportTo(m_homebindMapId, m_homebindX, m_homebindY, m_homebindZ, GetOrientation(), options); } + + Object* GetObjectByTypeMask(ObjectGuid guid, TypeMask typemask); + + // currently visible objects at player client + GuidSet m_clientGUIDs; + + bool HaveAtClient(WorldObject const* u) { return u == this || m_clientGUIDs.find(u->GetObjectGuid()) != m_clientGUIDs.end(); } + + bool IsVisibleInGridForPlayer(Player* pl) const override; + bool IsVisibleGloballyFor(Player* pl) const; + + void UpdateVisibilityOf(WorldObject const* viewPoint, WorldObject* target); + + template + void UpdateVisibilityOf(WorldObject const* viewPoint, T* target, UpdateData& data, std::set& visibleNow); + + // Stealth detection system + void HandleStealthedUnitsDetection(); + + Camera& GetCamera() { return m_camera; } + + uint8 m_forced_speed_changes[MAX_MOVE_TYPE]; + + bool HasAtLoginFlag(AtLoginFlags f) const { return m_atLoginFlags & f; } + void SetAtLoginFlag(AtLoginFlags f) { m_atLoginFlags |= f; } + void RemoveAtLoginFlag(AtLoginFlags f, bool in_db_also = false); + + // Temporarily removed pet cache + uint32 GetTemporaryUnsummonedPetNumber() const { return m_temporaryUnsummonedPetNumber; } + void SetTemporaryUnsummonedPetNumber(uint32 petnumber) { m_temporaryUnsummonedPetNumber = petnumber; } + void UnsummonPetTemporaryIfAny(); + void ResummonPetTemporaryUnSummonedIfAny(); + bool IsPetNeedBeTemporaryUnsummoned() const { return !IsInWorld() || !isAlive() || IsMounted() /*+in flight*/; } + + void SendCinematicStart(uint32 CinematicSequenceId); + void SendMovieStart(uint32 MovieId); + + /*********************************************************/ + /*** INSTANCE SYSTEM ***/ + /*********************************************************/ + + typedef UNORDERED_MAP < uint32 /*mapId*/, InstancePlayerBind > BoundInstancesMap; + + void UpdateHomebindTime(uint32 time); + + uint32 m_HomebindTimer; + bool m_InstanceValid; + // permanent binds and solo binds by difficulty + BoundInstancesMap m_boundInstances[MAX_DIFFICULTY]; + InstancePlayerBind* GetBoundInstance(uint32 mapid, Difficulty difficulty); + BoundInstancesMap& GetBoundInstances(Difficulty difficulty) { return m_boundInstances[difficulty]; } + void UnbindInstance(uint32 mapid, Difficulty difficulty, bool unload = false); + void UnbindInstance(BoundInstancesMap::iterator& itr, Difficulty difficulty, bool unload = false); + InstancePlayerBind* BindToInstance(DungeonPersistentState* save, bool permanent, bool load = false); + void SendRaidInfo(); + void SendSavedInstances(); + static void ConvertInstancesToGroup(Player* player, Group* group = NULL, ObjectGuid player_guid = ObjectGuid()); + DungeonPersistentState* GetBoundInstanceSaveForSelfOrGroup(uint32 mapid); + + AreaLockStatus GetAreaTriggerLockStatus(AreaTrigger const* at, Difficulty difficulty, uint32& miscRequirement); + void SendTransferAbortedByLockStatus(MapEntry const* mapEntry, AreaLockStatus lockStatus, uint32 miscRequirement = 0); + + /*********************************************************/ + /*** GROUP SYSTEM ***/ + /*********************************************************/ + + Group* GetGroupInvite() { return m_groupInvite; } + void SetGroupInvite(Group* group) { m_groupInvite = group; } + Group* GetGroup() { return m_group.getTarget(); } + const Group* GetGroup() const { return (const Group*)m_group.getTarget(); } + GroupReference& GetGroupRef() { return m_group; } + void SetGroup(Group* group, int8 subgroup = -1); + uint8 GetSubGroup() const { return m_group.getSubGroup(); } + uint32 GetGroupUpdateFlag() const { return m_groupUpdateMask; } + void SetGroupUpdateFlag(uint32 flag) { m_groupUpdateMask |= flag; } + const uint64& GetAuraUpdateMask() const { return m_auraUpdateMask; } + void SetAuraUpdateMask(uint8 slot) { m_auraUpdateMask |= (uint64(1) << slot); } + Player* GetNextRandomRaidMember(float radius); + PartyResult CanUninviteFromGroup() const; + // BattleGround Group System + void SetBattleGroundRaid(Group* group, int8 subgroup = -1); + void RemoveFromBattleGroundRaid(); + Group* GetOriginalGroup() { return m_originalGroup.getTarget(); } + GroupReference& GetOriginalGroupRef() { return m_originalGroup; } + uint8 GetOriginalSubGroup() const { return m_originalGroup.getSubGroup(); } + void SetOriginalGroup(Group* group, int8 subgroup = -1); + + GridReference& GetGridRef() { return m_gridRef; } + MapReference& GetMapRef() { return m_mapRef; } + + bool isAllowedToLoot(Creature* creature); + + DeclinedName const* GetDeclinedNames() const { return m_declinedname; } + + // Rune functions, need check getClass() == CLASS_DEATH_KNIGHT before access + uint8 GetRunesState() const { return m_runes->runeState; } + RuneType GetBaseRune(uint8 index) const { return RuneType(m_runes->runes[index].BaseRune); } + RuneType GetCurrentRune(uint8 index) const { return RuneType(m_runes->runes[index].CurrentRune); } + uint16 GetRuneCooldown(uint8 index) const { return m_runes->runes[index].Cooldown; } + uint16 GetBaseRuneCooldown(uint8 index) const { return m_runes->runes[index].BaseCooldown; } + uint8 GetRuneCooldownFraction(uint8 index) const; + void UpdateRuneRegen(RuneType rune); + void UpdateRuneRegen(); + bool IsBaseRuneSlotsOnCooldown(RuneType runeType) const; + void ClearLastUsedRuneMask() { m_runes->lastUsedRuneMask = 0; } + uint32 GetLastUsedRuneMask() const { return m_runes->lastUsedRuneMask; } + bool IsLastUsedRune(uint8 index) const { return m_runes->lastUsedRuneMask & (1 << index); } + void SetLastUsedRune(RuneType type) { m_runes->lastUsedRuneMask |= 1 << uint32(type); } + void SetBaseRune(uint8 index, RuneType baseRune) { m_runes->runes[index].BaseRune = baseRune; } + void SetCurrentRune(uint8 index, RuneType currentRune) { m_runes->runes[index].CurrentRune = currentRune; } + void SetRuneCooldown(uint8 index, uint16 cooldown) { m_runes->runes[index].Cooldown = cooldown; m_runes->SetRuneState(index, (cooldown == 0) ? true : false); } + void SetBaseRuneCooldown(uint8 index, uint16 cooldown) { m_runes->runes[index].BaseCooldown = cooldown; } + void SetRuneConvertAura(uint8 index, Aura const* aura) { m_runes->runes[index].ConvertAura = aura; } + void AddRuneByAuraEffect(uint8 index, RuneType newType, Aura const* aura); + void RemoveRunesByAuraEffect(Aura const* aura); + void RestoreBaseRune(uint8 index); + void ConvertRune(uint8 index, RuneType newType); + void ResyncRunes(); + void AddRunePower(uint8 index); + void InitRunes(); + + AchievementMgr const& GetAchievementMgr() const { return m_achievementMgr; } + AchievementMgr& GetAchievementMgr() { return m_achievementMgr; } + void UpdateAchievementCriteria(AchievementCriteriaTypes type, uint32 miscvalue1 = 0, uint32 miscvalue2 = 0, Unit* unit = NULL, uint32 time = 0); + void StartTimedAchievementCriteria(AchievementCriteriaTypes type, uint32 timedRequirementId, time_t startTime = 0); + + bool HasTitle(uint32 bitIndex) const; + bool HasTitle(CharTitlesEntry const* title) const { return HasTitle(title->bit_index); } + void SetTitle(CharTitlesEntry const* title, bool lost = false); + + bool canSeeSpellClickOn(Creature const* creature) const; + protected: + + uint32 m_contestedPvPTimer; + + /*********************************************************/ + /*** BATTLEGROUND SYSTEM ***/ + /*********************************************************/ + + /* + this is an array of BG queues (BgTypeIDs) in which is player + */ + struct BgBattleGroundQueueID_Rec + { + BattleGroundQueueTypeId bgQueueTypeId; + uint32 invitedToInstance; + }; + + BgBattleGroundQueueID_Rec m_bgBattleGroundQueueID[PLAYER_MAX_BATTLEGROUND_QUEUES]; + BGData m_bgData; + + /*********************************************************/ + /*** QUEST SYSTEM ***/ + /*********************************************************/ + + // We allow only one timed quest active at the same time. Below can then be simple value instead of set. + typedef std::set QuestSet; + QuestSet m_timedquests; + QuestSet m_weeklyquests; + QuestSet m_monthlyquests; + + ObjectGuid m_dividerGuid; + uint32 m_ingametime; + + /*********************************************************/ + /*** LOAD SYSTEM ***/ + /*********************************************************/ + + void _LoadActions(QueryResult* result); + void _LoadAuras(QueryResult* result, uint32 timediff); + void _LoadBoundInstances(QueryResult* result); + void _LoadInventory(QueryResult* result, uint32 timediff); + void _LoadItemLoot(QueryResult* result); + void _LoadMails(QueryResult* result); + void _LoadMailedItems(QueryResult* result); + void _LoadQuestStatus(QueryResult* result); + void _LoadDailyQuestStatus(QueryResult* result); + void _LoadWeeklyQuestStatus(QueryResult* result); + void _LoadMonthlyQuestStatus(QueryResult* result); + void _LoadGroup(QueryResult* result); + void _LoadSkills(QueryResult* result); + void _LoadSpells(QueryResult* result); + void _LoadTalents(QueryResult* result); + void _LoadFriendList(QueryResult* result); + bool _LoadHomeBind(QueryResult* result); + void _LoadDeclinedNames(QueryResult* result); + void _LoadArenaTeamInfo(QueryResult* result); + void _LoadEquipmentSets(QueryResult* result); + void _LoadBGData(QueryResult* result); + void _LoadGlyphs(QueryResult* result); + void _LoadIntoDataField(const char* data, uint32 startOffset, uint32 count); + + /*********************************************************/ + /*** SAVE SYSTEM ***/ + /*********************************************************/ + + void _SaveActions(); + void _SaveAuras(); + void _SaveInventory(); + void _SaveMail(); + void _SaveQuestStatus(); + void _SaveDailyQuestStatus(); + void _SaveWeeklyQuestStatus(); + void _SaveMonthlyQuestStatus(); + void _SaveSkills(); + void _SaveSpells(); + void _SaveEquipmentSets(); + void _SaveBGData(); + void _SaveGlyphs(); + void _SaveTalents(); + void _SaveStats(); + + void _SetCreateBits(UpdateMask* updateMask, Player* target) const override; + void _SetUpdateBits(UpdateMask* updateMask, Player* target) const override; + + /*********************************************************/ + /*** ENVIRONMENTAL SYSTEM ***/ + /*********************************************************/ + void HandleSobering(); + void SendMirrorTimer(MirrorTimerType Type, uint32 MaxValue, uint32 CurrentValue, int32 Regen); + void StopMirrorTimer(MirrorTimerType Type); + void HandleDrowning(uint32 time_diff); + int32 getMaxTimer(MirrorTimerType timer); + + /*********************************************************/ + /*** CURRENCY SYSTEM ***/ + /*********************************************************/ + PlayerCurrenciesMap m_currencies; + uint32 GetCurrencyWeekCap(CurrencyTypesEntry const * currency) const; + uint32 GetCurrencyTotalCap(CurrencyTypesEntry const * currency) const; + void _LoadCurrencies(QueryResult* result); + void _SaveCurrencies(); + + void outDebugStatsValues() const; + ObjectGuid m_lootGuid; + + Team m_team; + uint32 m_nextSave; + time_t m_speakTime; + uint32 m_speakCount; + Difficulty m_dungeonDifficulty; + Difficulty m_raidDifficulty; + time_t m_lastHonorKillsUpdateTime; + + uint32 m_atLoginFlags; + + Item* m_items[PLAYER_SLOTS_COUNT]; + uint32 m_currentBuybackSlot; + + std::vector m_itemUpdateQueue; + bool m_itemUpdateQueueBlocked; + + uint32 m_ExtraFlags; + ObjectGuid m_curSelectionGuid; + + ObjectGuid m_comboTargetGuid; + int8 m_comboPoints; + + QuestStatusMap mQuestStatus; + + SkillStatusMap mSkillStatus; + + uint32 m_GuildIdInvited; + ObjectGuid m_GuildInviterGuid; + uint32 m_ArenaTeamIdInvited; + + PlayerMails m_mail; + PlayerSpellMap m_spells; + PlayerTalentMap m_talents[MAX_TALENT_SPEC_COUNT]; + uint32 m_talentsPrimaryTree[MAX_TALENT_SPEC_COUNT]; + SpellCooldowns m_spellCooldowns; + uint32 m_lastPotionId; // last used health/mana potion in combat, that block next potion use + + GlobalCooldownMgr m_GlobalCooldownMgr; + + uint8 m_activeSpec; + uint8 m_specsCount; + + ActionButtonList m_actionButtons[MAX_TALENT_SPEC_COUNT]; + + Glyph m_glyphs[MAX_TALENT_SPEC_COUNT][MAX_GLYPH_SLOT_INDEX]; + + float m_auraBaseMod[BASEMOD_END][MOD_END]; + int16 m_baseRatingValue[MAX_COMBAT_RATING]; + uint16 m_baseSpellPower; + uint16 m_baseManaRegen; + uint32 m_baseHealthRegen; + float m_armorPenetrationPct; + int32 m_spellPenetrationItemMod; + + AuraList m_spellMods[MAX_SPELLMOD]; + EnchantDurationList m_enchantDuration; + ItemDurationList m_itemDuration; + + ObjectGuid m_resurrectGuid; + uint32 m_resurrectMap; + float m_resurrectX, m_resurrectY, m_resurrectZ; + uint32 m_resurrectHealth, m_resurrectMana; + + WorldSession* m_session; + + typedef std::list JoinedChannelsList; + JoinedChannelsList m_channels; + + uint32 m_cinematic; + + TradeData* m_trade; + + bool m_DailyQuestChanged; + bool m_WeeklyQuestChanged; + bool m_MonthlyQuestChanged; + + uint32 m_drunkTimer; + uint32 m_weaponChangeTimer; + + uint32 m_zoneUpdateId; + uint32 m_zoneUpdateTimer; + uint32 m_areaUpdateId; + uint32 m_positionStatusUpdateTimer; + + uint32 m_deathTimer; + time_t m_deathExpireTime; + + uint32 m_restTime; + + uint32 m_WeaponProficiency; + uint32 m_ArmorProficiency; + bool m_canParry; + bool m_canBlock; + bool m_canDualWield; + bool m_canTitanGrip; + uint8 m_swingErrorMsg; + float m_ammoDPS; + + //////////////////// Rest SystemPlayerSpell + time_t time_inn_enter; + uint32 inn_trigger_id; + float m_rest_bonus; + RestType rest_type; + //////////////////// Rest System///////////////////// + + // Transports + Transport* m_transport; + + uint32 m_freeTalentPoints; + uint32 m_resetTalentsCost; + time_t m_resetTalentsTime; + uint32 m_usedTalentCount; + uint32 m_questRewardTalentCount; + + // Social + PlayerSocial *m_social; + + // Groups + GroupReference m_group; + GroupReference m_originalGroup; + Group* m_groupInvite; + uint32 m_groupUpdateMask; + uint64 m_auraUpdateMask; + + // Player summoning + time_t m_summon_expire; + uint32 m_summon_mapid; + float m_summon_x; + float m_summon_y; + float m_summon_z; + + DeclinedName* m_declinedname; + Runes* m_runes; + EquipmentSets m_EquipmentSets; + uint8 m_slot; + + /// class dependent melee diminishing constant for dodge/parry/missed chances + static const float m_diminishing_k[MAX_CLASSES]; + + private: + void _HandleDeadlyPoison(Unit* Target, WeaponAttackType attType, SpellEntry const* spellInfo); + // internal common parts for CanStore/StoreItem functions + InventoryResult _CanStoreItem_InSpecificSlot(uint8 bag, uint8 slot, ItemPosCountVec& dest, ItemPrototype const* pProto, uint32& count, bool swap, Item* pSrcItem) const; + InventoryResult _CanStoreItem_InBag(uint8 bag, ItemPosCountVec& dest, ItemPrototype const* pProto, uint32& count, bool merge, bool non_specialized, Item* pSrcItem, uint8 skip_bag, uint8 skip_slot) const; + InventoryResult _CanStoreItem_InInventorySlots(uint8 slot_begin, uint8 slot_end, ItemPosCountVec& dest, ItemPrototype const* pProto, uint32& count, bool merge, Item* pSrcItem, uint8 skip_bag, uint8 skip_slot) const; + Item* _StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool update); + + void AdjustQuestReqItemCount(Quest const* pQuest, QuestStatusData& questStatusData); + + void SetCanDelayTeleport(bool setting) { m_bCanDelayTeleport = setting; } + bool IsHasDelayedTeleport() const + { + // we should not execute delayed teleports for now dead players but has been alive at teleport + // because we don't want player's ghost teleported from graveyard + return m_bHasDelayedTeleport && (isAlive() || !m_bHasBeenAliveAtDelayedTeleport); + } + + bool SetDelayedTeleportFlagIfCan() + { + m_bHasDelayedTeleport = m_bCanDelayTeleport; + m_bHasBeenAliveAtDelayedTeleport = isAlive(); + return m_bHasDelayedTeleport; + } + + void ScheduleDelayedOperation(uint32 operation) + { + if (operation < DELAYED_END) + m_DelayedOperations |= operation; + } + + void _fillGearScoreData(Item* item, GearScoreVec* gearScore, uint32& twoHandScore); + + Unit* m_mover; + Camera m_camera; + + GridReference m_gridRef; + MapReference m_mapRef; + + // Homebind coordinates + uint32 m_homebindMapId; + uint16 m_homebindAreaId; + float m_homebindX; + float m_homebindY; + float m_homebindZ; + + uint32 m_lastFallTime; + float m_lastFallZ; + + LiquidTypeEntry const* m_lastLiquid; + + int32 m_MirrorTimer[MAX_TIMERS]; + uint8 m_MirrorTimerFlags; + uint8 m_MirrorTimerFlagsLast; + bool m_isInWater; + + // Current teleport data + WorldLocation m_teleport_dest; + uint32 m_teleport_options; + bool mSemaphoreTeleport_Near; + bool mSemaphoreTeleport_Far; + + uint32 m_DelayedOperations; + bool m_bCanDelayTeleport; + bool m_bHasDelayedTeleport; + bool m_bHasBeenAliveAtDelayedTeleport; + + uint32 m_DetectInvTimer; + + // Temporary removed pet cache + uint32 m_temporaryUnsummonedPetNumber; + + AchievementMgr m_achievementMgr; + ReputationMgr m_reputationMgr; + + uint32 m_timeSyncCounter; + uint32 m_timeSyncTimer; + uint32 m_timeSyncClient; + uint32 m_timeSyncServer; + + uint32 m_cachedGS; + + PhaseMgr* phaseMgr; +}; + +void AddItemsSetItem(Player* player, Item* item); +void RemoveItemsSetItem(Player* player, ItemPrototype const* proto); + +#endif diff --git a/src/game/Spell.cpp b/src/game/Spell.cpp new file mode 100644 index 000000000..7cd553f7c --- /dev/null +++ b/src/game/Spell.cpp @@ -0,0 +1,8284 @@ +/** + * This code is part of MaNGOS. Contributor & Copyright details are in AUTHORS/THANKS. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Spell.h" +#include "Database/DatabaseEnv.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "Opcodes.h" +#include "Log.h" +#include "UpdateMask.h" +#include "World.h" +#include "ObjectMgr.h" +#include "SpellMgr.h" +#include "Player.h" +#include "Pet.h" +#include "Unit.h" +#include "DynamicObject.h" +#include "Group.h" +#include "UpdateData.h" +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "CellImpl.h" +#include "Policies/Singleton.h" +#include "SharedDefines.h" +#include "LootMgr.h" +#include "VMapFactory.h" +#include "BattleGround/BattleGround.h" +#include "Util.h" +#include "Chat.h" +#include "DB2Stores.h" +#include "SQLStorages.h" +#include "Vehicle.h" +#include "TemporarySummon.h" +#include "SQLStorages.h" + +extern pEffect SpellEffects[TOTAL_SPELL_EFFECTS]; + +class PrioritizeManaUnitWraper +{ + public: + explicit PrioritizeManaUnitWraper(Unit* unit) : i_unit(unit) + { + uint32 maxmana = unit->GetMaxPower(POWER_MANA); + i_percent = maxmana ? unit->GetPower(POWER_MANA) * 100 / maxmana : 101; + } + Unit* getUnit() const { return i_unit; } + uint32 getPercent() const { return i_percent; } + private: + Unit* i_unit; + uint32 i_percent; +}; + +struct PrioritizeMana +{ + int operator()(PrioritizeManaUnitWraper const& x, PrioritizeManaUnitWraper const& y) const + { + return x.getPercent() > y.getPercent(); + } +}; + +typedef std::priority_queue, PrioritizeMana> PrioritizeManaUnitQueue; + +class PrioritizeHealthUnitWraper +{ + public: + explicit PrioritizeHealthUnitWraper(Unit* unit) : i_unit(unit) + { + i_percent = unit->GetHealth() * 100 / unit->GetMaxHealth(); + } + Unit* getUnit() const { return i_unit; } + uint32 getPercent() const { return i_percent; } + private: + Unit* i_unit; + uint32 i_percent; +}; + +struct PrioritizeHealth +{ + int operator()(PrioritizeHealthUnitWraper const& x, PrioritizeHealthUnitWraper const& y) const + { + return x.getPercent() > y.getPercent(); + } +}; + +typedef std::priority_queue, PrioritizeHealth> PrioritizeHealthUnitQueue; + +bool IsQuestTameSpell(uint32 spellId) +{ + SpellEntry const* spellproto = sSpellStore.LookupEntry(spellId); + if (!spellproto) + return false; + + SpellEffectEntry const* spellEffect0 = spellproto->GetSpellEffect(EFFECT_INDEX_0); + SpellEffectEntry const* spellEffect1 = spellproto->GetSpellEffect(EFFECT_INDEX_1); + return spellEffect0 && spellEffect0->Effect == SPELL_EFFECT_THREAT && + spellEffect1 && spellEffect1->Effect == SPELL_EFFECT_APPLY_AURA && spellEffect1->EffectApplyAuraName == SPELL_AURA_DUMMY; +} + +SpellCastTargets::SpellCastTargets() +{ + m_unitTarget = NULL; + m_itemTarget = NULL; + m_GOTarget = NULL; + + m_itemTargetEntry = 0; + + m_srcX = m_srcY = m_srcZ = m_destX = m_destY = m_destZ = 0.0f; + m_strTarget = ""; + m_targetMask = 0; + + m_elevation = 0.0f; + m_speed = 0.0f; +} + +SpellCastTargets::~SpellCastTargets() +{ +} + +void SpellCastTargets::setUnitTarget(Unit* target) +{ + if (!target) + return; + + m_destX = target->GetPositionX(); + m_destY = target->GetPositionY(); + m_destZ = target->GetPositionZ(); + m_unitTarget = target; + m_unitTargetGUID = target->GetObjectGuid(); + m_targetMask |= TARGET_FLAG_UNIT; +} + +void SpellCastTargets::setDestination(float x, float y, float z) +{ + m_destX = x; + m_destY = y; + m_destZ = z; + m_targetMask |= TARGET_FLAG_DEST_LOCATION; +} + +void SpellCastTargets::setSource(float x, float y, float z) +{ + m_srcX = x; + m_srcY = y; + m_srcZ = z; + m_targetMask |= TARGET_FLAG_SOURCE_LOCATION; +} + +void SpellCastTargets::setGOTarget(GameObject* target) +{ + m_GOTarget = target; + m_GOTargetGUID = target->GetObjectGuid(); + // m_targetMask |= TARGET_FLAG_OBJECT; +} + +void SpellCastTargets::setItemTarget(Item* item) +{ + if (!item) + return; + + m_itemTarget = item; + m_itemTargetGUID = item->GetObjectGuid(); + m_itemTargetEntry = item->GetEntry(); + m_targetMask |= TARGET_FLAG_ITEM; +} + +void SpellCastTargets::setTradeItemTarget(Player* caster) +{ + m_itemTargetGUID = ObjectGuid(uint64(TRADE_SLOT_NONTRADED)); + m_itemTargetEntry = 0; + m_targetMask |= TARGET_FLAG_TRADE_ITEM; + + Update(caster); +} + +void SpellCastTargets::setCorpseTarget(Corpse* corpse) +{ + m_CorpseTargetGUID = corpse->GetObjectGuid(); +} + +void SpellCastTargets::Update(Unit* caster) +{ + m_GOTarget = m_GOTargetGUID ? caster->GetMap()->GetGameObject(m_GOTargetGUID) : NULL; + m_unitTarget = m_unitTargetGUID ? + (m_unitTargetGUID == caster->GetObjectGuid() ? caster : ObjectAccessor::GetUnit(*caster, m_unitTargetGUID)) : + NULL; + + m_itemTarget = NULL; + if (caster->GetTypeId() == TYPEID_PLAYER) +{ + Player* player = ((Player*)caster); + + if (m_targetMask & TARGET_FLAG_ITEM) + m_itemTarget = player->GetItemByGuid(m_itemTargetGUID); + else if (m_targetMask & TARGET_FLAG_TRADE_ITEM) + { + if (TradeData* pTrade = player->GetTradeData()) + if (m_itemTargetGUID.GetRawValue() < TRADE_SLOT_COUNT) + m_itemTarget = pTrade->GetTraderData()->GetItem(TradeSlots(m_itemTargetGUID.GetRawValue())); + } + + if (m_itemTarget) + m_itemTargetEntry = m_itemTarget->GetEntry(); + } +} + +void SpellCastTargets::read(ByteBuffer& data, Unit* caster) +{ + data >> m_targetMask; + + if (m_targetMask == TARGET_FLAG_SELF) + { + m_destX = caster->GetPositionX(); + m_destY = caster->GetPositionY(); + m_destZ = caster->GetPositionZ(); + m_unitTarget = caster; + m_unitTargetGUID = caster->GetObjectGuid(); + return; + } + + // TARGET_FLAG_UNK2 is used for non-combat pets, maybe other? + if (m_targetMask & (TARGET_FLAG_UNIT | TARGET_FLAG_UNK2)) + data >> m_unitTargetGUID.ReadAsPacked(); + + if (m_targetMask & (TARGET_FLAG_OBJECT)) + data >> m_GOTargetGUID.ReadAsPacked(); + + if ((m_targetMask & (TARGET_FLAG_ITEM | TARGET_FLAG_TRADE_ITEM)) && caster->GetTypeId() == TYPEID_PLAYER) + data >> m_itemTargetGUID.ReadAsPacked(); + + if (m_targetMask & (TARGET_FLAG_CORPSE | TARGET_FLAG_PVP_CORPSE)) + data >> m_CorpseTargetGUID.ReadAsPacked(); + + if (m_targetMask & TARGET_FLAG_SOURCE_LOCATION) + { + data >> m_srcTransportGUID.ReadAsPacked(); + data >> m_srcX >> m_srcY >> m_srcZ; + if (!MaNGOS::IsValidMapCoord(m_srcX, m_srcY, m_srcZ)) + throw ByteBufferException(false, data.rpos(), 0, data.size()); + } + + if (m_targetMask & TARGET_FLAG_DEST_LOCATION) + { + data >> m_destTransportGUID.ReadAsPacked(); + data >> m_destX >> m_destY >> m_destZ; + if (!MaNGOS::IsValidMapCoord(m_destX, m_destY, m_destZ)) + throw ByteBufferException(false, data.rpos(), 0, data.size()); + } + + if (m_targetMask & TARGET_FLAG_STRING) + data >> m_strTarget; + + // find real units/GOs + Update(caster); +} + +void SpellCastTargets::write(ByteBuffer& data) const +{ + data << uint32(m_targetMask); + + if (m_targetMask & (TARGET_FLAG_UNIT | TARGET_FLAG_PVP_CORPSE | TARGET_FLAG_OBJECT | TARGET_FLAG_CORPSE | TARGET_FLAG_UNK2)) + { + if (m_targetMask & TARGET_FLAG_UNIT) + { + if (m_unitTarget) + data << m_unitTarget->GetPackGUID(); + else + data << uint8(0); + } + else if (m_targetMask & TARGET_FLAG_OBJECT) + { + if (m_GOTarget) + data << m_GOTarget->GetPackGUID(); + else + data << uint8(0); + } + else if (m_targetMask & (TARGET_FLAG_CORPSE | TARGET_FLAG_PVP_CORPSE)) + data << m_CorpseTargetGUID.WriteAsPacked(); + else + data << uint8(0); + } + + if (m_targetMask & (TARGET_FLAG_ITEM | TARGET_FLAG_TRADE_ITEM)) + { + if (m_itemTarget) + data << m_itemTarget->GetPackGUID(); + else + data << uint8(0); + } + + if (m_targetMask & TARGET_FLAG_SOURCE_LOCATION) + { + data << m_srcTransportGUID.WriteAsPacked(); + data << m_srcX << m_srcY << m_srcZ; + } + + if (m_targetMask & TARGET_FLAG_DEST_LOCATION) + { + data << m_destTransportGUID.WriteAsPacked(); + data << m_destX << m_destY << m_destZ; + } + + if (m_targetMask & TARGET_FLAG_STRING) + data << m_strTarget; +} + +void SpellCastTargets::ReadAdditionalData(WorldPacket& data, uint8& cast_flags) +{ + if (cast_flags & 0x02) // has trajectory data + { + data >> m_elevation >> m_speed; + + bool hasMovementData; + data >> hasMovementData; + if (hasMovementData) + { + MovementInfo mi; + data >> mi; + setSource(mi.GetPos()->x, mi.GetPos()->y, mi.GetPos()->z); + } + } + else if (cast_flags & 0x08) // has archaeology weight + { + uint32 count; + uint8 type; + data >> count; + for (uint32 i = 0; i < count; ++i) + { + data >> type; + switch (type) + { + case 1: // Fragments + data >> Unused(); // Currency entry + data >> Unused(); // Currency count + break; + case 2: // Keystones + data >> Unused(); // Item entry + data >> Unused(); // Item count + break; + } + } + } +} + +Spell::Spell(Unit* caster, SpellEntry const* info, bool triggered, ObjectGuid originalCasterGUID, SpellEntry const* triggeredBy) +{ + MANGOS_ASSERT(caster != NULL && info != NULL); + MANGOS_ASSERT(info == sSpellStore.LookupEntry(info->Id) && "`info` must be pointer to sSpellStore element"); + + if (info->SpellDifficultyId && caster->IsInWorld() && caster->GetMap()->IsDungeon()) + { + if (SpellEntry const* spellEntry = GetSpellEntryByDifficulty(info->SpellDifficultyId, caster->GetMap()->GetDifficulty(), caster->GetMap()->IsRaid())) + m_spellInfo = spellEntry; + else + m_spellInfo = info; + } + else + m_spellInfo = info; + + m_triggeredBySpellInfo = triggeredBy; + + m_spellInterrupts = m_spellInfo->GetSpellInterrupts(); + + m_caster = caster; + m_selfContainer = NULL; + m_referencedFromCurrentSpell = false; + m_executedCurrently = false; + m_delayStart = 0; + m_delayAtDamageCount = 0; + + m_applyMultiplierMask = 0; + + // Get data for type of attack + m_attackType = GetWeaponAttackType(m_spellInfo); + + m_spellSchoolMask = GetSpellSchoolMask(info); // Can be override for some spell (wand shoot for example) + + if (m_attackType == RANGED_ATTACK) + { + // wand case + if ((m_caster->getClassMask() & CLASSMASK_WAND_USERS) != 0 && m_caster->GetTypeId() == TYPEID_PLAYER) + { + if (Item* pItem = ((Player*)m_caster)->GetWeaponForAttack(RANGED_ATTACK)) + m_spellSchoolMask = SpellSchoolMask(1 << pItem->GetProto()->DamageType); + } + } + // Set health leech amount to zero + m_healthLeech = 0; + + m_originalCasterGUID = originalCasterGUID ? originalCasterGUID : m_caster->GetObjectGuid(); + + UpdateOriginalCasterPointer(); + + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + m_currentBasePoints[i] = m_spellInfo->CalculateSimpleValue(SpellEffectIndex(i)); + + m_spellState = SPELL_STATE_PREPARING; + + m_castPositionX = m_castPositionY = m_castPositionZ = 0; + m_TriggerSpells.clear(); + m_preCastSpells.clear(); + m_IsTriggeredSpell = triggered; + // m_AreaAura = false; + m_CastItem = NULL; + + unitTarget = NULL; + itemTarget = NULL; + gameObjTarget = NULL; + focusObject = NULL; + m_cast_count = 0; + m_glyphIndex = 0; + m_triggeredByAuraSpell = NULL; + + // Auto Shot & Shoot (wand) + m_autoRepeat = IsAutoRepeatRangedSpell(m_spellInfo); + + m_runesState = 0; + m_powerCost = 0; // setup to correct value in Spell::prepare, don't must be used before. + m_usedHolyPower = 0; + m_casttime = 0; // setup to correct value in Spell::prepare, don't must be used before. + m_timer = 0; // will set to cast time in prepare + m_duration = 0; + + m_needAliveTargetMask = 0; + + // determine reflection + m_canReflect = false; + + m_spellFlags = SPELL_FLAG_NORMAL; + + if (m_spellInfo->GetDmgClass() == SPELL_DAMAGE_CLASS_MAGIC && !m_spellInfo->HasAttribute(SPELL_ATTR_EX_CANT_REFLECTED)) + { + for (int j = 0; j < MAX_EFFECT_INDEX; ++j) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(j)); + if(!spellEffect) + continue; + if (spellEffect->Effect == 0) + continue; + + if(!IsPositiveTarget(spellEffect->EffectImplicitTargetA, spellEffect->EffectImplicitTargetB)) + m_canReflect = true; + else + m_canReflect = m_spellInfo->HasAttribute(SPELL_ATTR_EX_NEGATIVE); + + if (m_canReflect) + continue; + else + break; + } + } + + CleanupTargetList(); +} + +Spell::~Spell() +{ +} + +template +WorldObject* Spell::FindCorpseUsing() +{ + // non-standard target selection + SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex); + float max_range = GetSpellMaxRange(srange); + + WorldObject* result = NULL; + + T u_check(m_caster, max_range); + MaNGOS::WorldObjectSearcher searcher(result, u_check); + + Cell::VisitGridObjects(m_caster, searcher, max_range); + + if (!result) + Cell::VisitWorldObjects(m_caster, searcher, max_range); + + return result; +} + +void Spell::FillTargetMap() +{ + // TODO: ADD the correct target FILLS!!!!!! + + UnitList tmpUnitLists[MAX_EFFECT_INDEX]; // Stores the temporary Target Lists for each effect + uint8 effToIndex[MAX_EFFECT_INDEX] = {0, 1, 2}; // Helper array, to link to another tmpUnitList, if the targets for both effects match + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(i)); + if(!spellEffect) + continue; + + // not call for empty effect. + // Also some spells use not used effect targets for store targets for dummy effect in triggered spells + if(spellEffect->Effect == SPELL_EFFECT_NONE) + continue; + + // targets for TARGET_SCRIPT_COORDINATES (A) and TARGET_SCRIPT + // for TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT (A) all is checked in Spell::CheckCast and in Spell::CheckItem + // filled in Spell::CheckCast call + if(spellEffect->EffectImplicitTargetA == TARGET_SCRIPT_COORDINATES || + spellEffect->EffectImplicitTargetA == TARGET_SCRIPT || + spellEffect->EffectImplicitTargetA == TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT || + (spellEffect->EffectImplicitTargetB == TARGET_SCRIPT && spellEffect->EffectImplicitTargetA != TARGET_SELF)) + continue; + + // TODO: find a way so this is not needed? + // for area auras always add caster as target (needed for totems for example) + if (IsAreaAuraEffect(spellEffect->Effect)) + AddUnitTarget(m_caster, SpellEffectIndex(i)); + + // no double fill for same targets + for (int j = 0; j < i; ++j) + { + SpellEffectEntry const* spellEffect1 = m_spellInfo->GetSpellEffect(SpellEffectIndex(j)); + if (!spellEffect1) + continue; + + // Check if same target, but handle i.e. AreaAuras different + if (spellEffect->EffectImplicitTargetA == spellEffect1->EffectImplicitTargetA && spellEffect->EffectImplicitTargetB == spellEffect1->EffectImplicitTargetB + && spellEffect1->Effect != SPELL_EFFECT_NONE + && !IsAreaAuraEffect(spellEffect->Effect) && !IsAreaAuraEffect(spellEffect1->Effect)) + // Add further conditions here if required + { + effToIndex[i] = j; // effect i has same targeting list as effect j + break; + } + } + + if (effToIndex[i] == i) // New target combination + { + // TargetA/TargetB dependent from each other, we not switch to full support this dependences + // but need it support in some know cases + switch(spellEffect->EffectImplicitTargetA) + { + case TARGET_NONE: + switch(spellEffect->EffectImplicitTargetB) + { + case TARGET_NONE: + if (m_caster->GetObjectGuid().IsPet()) + SetTargetMap(SpellEffectIndex(i), TARGET_SELF, tmpUnitLists[i /*==effToIndex[i]*/]); + else + SetTargetMap(SpellEffectIndex(i), TARGET_EFFECT_SELECT, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + default: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + } + break; + case TARGET_SELF: + switch(spellEffect->EffectImplicitTargetB) + { + case TARGET_NONE: + case TARGET_EFFECT_SELECT: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + case TARGET_AREAEFFECT_INSTANT: // use B case that not dependent from from A in fact + if((m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) == 0) + m_targets.setDestination(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ()); + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + case TARGET_BEHIND_VICTIM: // use B case that not dependent from from A in fact + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + default: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + } + break; + case TARGET_EFFECT_SELECT: + switch(spellEffect->EffectImplicitTargetB) + { + case TARGET_NONE: + case TARGET_EFFECT_SELECT: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + // dest point setup required + case TARGET_AREAEFFECT_INSTANT: + case TARGET_AREAEFFECT_CUSTOM: + case TARGET_ALL_ENEMY_IN_AREA: + case TARGET_ALL_ENEMY_IN_AREA_INSTANT: + case TARGET_ALL_ENEMY_IN_AREA_CHANNELED: + case TARGET_ALL_FRIENDLY_UNITS_IN_AREA: + case TARGET_AREAEFFECT_GO_AROUND_DEST: + case TARGET_RANDOM_NEARBY_DEST: + // triggered spells get dest point from default target set, ignore it + if (!(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) || m_IsTriggeredSpell) + if (WorldObject* castObject = GetCastingObject()) + m_targets.setDestination(castObject->GetPositionX(), castObject->GetPositionY(), castObject->GetPositionZ()); + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + // target pre-selection required + case TARGET_INNKEEPER_COORDINATES: + case TARGET_TABLE_X_Y_Z_COORDINATES: + case TARGET_CASTER_COORDINATES: + case TARGET_SCRIPT_COORDINATES: + case TARGET_CURRENT_ENEMY_COORDINATES: + case TARGET_DUELVSPLAYER_COORDINATES: + case TARGET_DYNAMIC_OBJECT_COORDINATES: + case TARGET_POINT_AT_NORTH: + case TARGET_POINT_AT_SOUTH: + case TARGET_POINT_AT_EAST: + case TARGET_POINT_AT_WEST: + case TARGET_POINT_AT_NE: + case TARGET_POINT_AT_NW: + case TARGET_POINT_AT_SE: + case TARGET_POINT_AT_SW: + // need some target for processing + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + default: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + } + break; + case TARGET_CASTER_COORDINATES: + switch(spellEffect->EffectImplicitTargetB) + { + case TARGET_ALL_ENEMY_IN_AREA: + // Note: this hack with search required until GO casting not implemented + // environment damage spells already have around enemies targeting but this not help in case nonexistent GO casting support + // currently each enemy selected explicitly and self cast damage + if (spellEffect->Effect == SPELL_EFFECT_ENVIRONMENTAL_DAMAGE) + { + if(m_targets.getUnitTarget()) + tmpUnitLists[i /*==effToIndex[i]*/].push_back(m_targets.getUnitTarget()); + } + else + { + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + } + break; + case 0: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + tmpUnitLists[i /*==effToIndex[i]*/].push_back(m_caster); + break; + default: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + } + break; + case TARGET_TABLE_X_Y_Z_COORDINATES: + switch(spellEffect->EffectImplicitTargetB) + { + case 0: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + + + // need some target for processing + SetTargetMap(SpellEffectIndex(i), TARGET_EFFECT_SELECT, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + case TARGET_AREAEFFECT_INSTANT: // All 17/7 pairs used for dest teleportation, A processed in effect code + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + default: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + } + case TARGET_SELF2: + switch(spellEffect->EffectImplicitTargetB) + { + case TARGET_NONE: + case TARGET_EFFECT_SELECT: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + case TARGET_AREAEFFECT_CUSTOM: + // triggered spells get dest point from default target set, ignore it + if (!(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) || m_IsTriggeredSpell) + if (WorldObject* castObject = GetCastingObject()) + m_targets.setDestination(castObject->GetPositionX(), castObject->GetPositionY(), castObject->GetPositionZ()); + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + // most A/B target pairs is self->negative and not expect adding caster to target list + default: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + } + break; + case TARGET_DUELVSPLAYER_COORDINATES: + switch(spellEffect->EffectImplicitTargetB) + { + case TARGET_NONE: + case TARGET_EFFECT_SELECT: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + if (Unit* currentTarget = m_targets.getUnitTarget()) + tmpUnitLists[i /*==effToIndex[i]*/].push_back(currentTarget); + break; + default: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + } + break; + default: + switch(spellEffect->EffectImplicitTargetB) + { + case 0: + case TARGET_EFFECT_SELECT: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + case TARGET_SCRIPT_COORDINATES: // B case filled in CheckCast but we need fill unit list base at A case + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + default: + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetA, tmpUnitLists[i /*==effToIndex[i]*/]); + SetTargetMap(SpellEffectIndex(i), spellEffect->EffectImplicitTargetB, tmpUnitLists[i /*==effToIndex[i]*/]); + break; + } + break; + } + } + + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + Player* me = (Player*)m_caster; + for (UnitList::const_iterator itr = tmpUnitLists[effToIndex[i]].begin(); itr != tmpUnitLists[effToIndex[i]].end(); ++itr) + { + Player* targetOwner = (*itr)->GetCharmerOrOwnerPlayerOrPlayerItself(); + if (targetOwner && targetOwner != me && targetOwner->IsPvP() && !me->IsInDuelWith(targetOwner)) + { + me->UpdatePvP(true); + me->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); + break; + } + } + } + + for (UnitList::iterator itr = tmpUnitLists[effToIndex[i]].begin(); itr != tmpUnitLists[effToIndex[i]].end();) + { + if (!CheckTarget(*itr, SpellEffectIndex(i))) + { + itr = tmpUnitLists[effToIndex[i]].erase(itr); + continue; + } + else + ++itr; + } + + for (UnitList::const_iterator iunit = tmpUnitLists[effToIndex[i]].begin(); iunit != tmpUnitLists[effToIndex[i]].end(); ++iunit) + AddUnitTarget((*iunit), SpellEffectIndex(i)); + } +} + +void Spell::prepareDataForTriggerSystem() +{ + //========================================================================================== + // Now fill data for trigger system, need know: + // an spell trigger another or not ( m_canTrigger ) + // Create base triggers flags for Attacker and Victim ( m_procAttacker and m_procVictim) + //========================================================================================== + // Fill flag can spell trigger or not + // TODO: possible exist spell attribute for this + m_canTrigger = false; + + if (m_CastItem) + m_canTrigger = false; // Do not trigger from item cast spell + else if (!m_IsTriggeredSpell) + m_canTrigger = true; // Normal cast - can trigger + else if (!m_triggeredByAuraSpell) + m_canTrigger = true; // Triggered from SPELL_EFFECT_TRIGGER_SPELL - can trigger + + if (!m_canTrigger) // Exceptions (some periodic triggers) + { + switch (m_spellInfo->GetSpellFamilyName()) + { + case SPELLFAMILY_MAGE: + // Arcane Missles / Blizzard triggers need do it + if (m_spellInfo->IsFitToFamilyMask(UI64LIT(0x0000000000200080))) + m_canTrigger = true; + // Clearcasting trigger need do it + else if (m_spellInfo->IsFitToFamilyMask(UI64LIT(0x0000000200000000), 0x00000008)) + m_canTrigger = true; + // Replenish Mana, item spell with triggered cases (Mana Agate, etc mana gems) + else if (m_spellInfo->IsFitToFamilyMask(UI64LIT(0x0000010000000000))) + m_canTrigger = true; + break; + case SPELLFAMILY_WARLOCK: + // For Hellfire Effect / Rain of Fire / Seed of Corruption triggers need do it + if (m_spellInfo->IsFitToFamilyMask(UI64LIT(0x0000800000000060))) + m_canTrigger = true; + break; + case SPELLFAMILY_PRIEST: + // For Penance,Mind Sear,Mind Flay heal/damage triggers need do it + if (m_spellInfo->IsFitToFamilyMask(UI64LIT(0x0001800000800000), 0x00000040)) + m_canTrigger = true; + break; + case SPELLFAMILY_ROGUE: + // For poisons need do it + if (m_spellInfo->IsFitToFamilyMask(UI64LIT(0x000000101001E000))) + m_canTrigger = true; + break; + case SPELLFAMILY_HUNTER: + // Hunter Rapid Killing/Explosive Trap Effect/Immolation Trap Effect/Frost Trap Aura/Snake Trap Effect/Explosive Shot + if (m_spellInfo->IsFitToFamilyMask(UI64LIT(0x0100200000000214), 0x200)) + m_canTrigger = true; + break; + case SPELLFAMILY_PALADIN: + // For Judgements (all) / Holy Shock triggers need do it + if (m_spellInfo->IsFitToFamilyMask(UI64LIT(0x0001000900B80400))) + m_canTrigger = true; + break; + default: + break; + } + } + + // Get data for type of attack and fill base info for trigger + switch (m_spellInfo->GetDmgClass()) + { + case SPELL_DAMAGE_CLASS_MELEE: + m_procAttacker = PROC_FLAG_SUCCESSFUL_MELEE_SPELL_HIT; + if (m_attackType == OFF_ATTACK) + m_procAttacker |= PROC_FLAG_SUCCESSFUL_OFFHAND_HIT; + m_procVictim = PROC_FLAG_TAKEN_MELEE_SPELL_HIT; + break; + case SPELL_DAMAGE_CLASS_RANGED: + // Auto attack + if (m_spellInfo->HasAttribute(SPELL_ATTR_EX2_AUTOREPEAT_FLAG)) + { + m_procAttacker = PROC_FLAG_SUCCESSFUL_RANGED_HIT; + m_procVictim = PROC_FLAG_TAKEN_RANGED_HIT; + } + else // Ranged spell attack + { + m_procAttacker = PROC_FLAG_SUCCESSFUL_RANGED_SPELL_HIT; + m_procVictim = PROC_FLAG_TAKEN_RANGED_SPELL_HIT; + } + break; + default: + if (IsPositiveSpell(m_spellInfo->Id)) // Check for positive spell + { + m_procAttacker = PROC_FLAG_SUCCESSFUL_POSITIVE_SPELL; + m_procVictim = PROC_FLAG_TAKEN_POSITIVE_SPELL; + } + else if (m_spellInfo->HasAttribute(SPELL_ATTR_EX2_AUTOREPEAT_FLAG)) // Wands auto attack + { + m_procAttacker = PROC_FLAG_SUCCESSFUL_RANGED_HIT; + m_procVictim = PROC_FLAG_TAKEN_RANGED_HIT; + } + else // Negative spell + { + m_procAttacker = PROC_FLAG_SUCCESSFUL_NEGATIVE_SPELL_HIT; + m_procVictim = PROC_FLAG_TAKEN_NEGATIVE_SPELL_HIT; + } + break; + } + + // some negative spells have positive effects to another or same targets + // avoid triggering negative hit for only positive targets + m_negativeEffectMask = 0x0; + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + if (!IsPositiveEffect(m_spellInfo, SpellEffectIndex(i))) + m_negativeEffectMask |= (1 << i); + + // Hunter traps spells (for Entrapment trigger) + // Gives your Immolation Trap, Frost Trap, Explosive Trap, and Snake Trap .... + if (m_spellInfo->IsFitToFamily(SPELLFAMILY_HUNTER, UI64LIT(0x000020000000001C))) + m_procAttacker |= PROC_FLAG_ON_TRAP_ACTIVATION; +} + +void Spell::CleanupTargetList() +{ + m_UniqueTargetInfo.clear(); + m_UniqueGOTargetInfo.clear(); + m_UniqueItemInfo.clear(); + m_delayMoment = 0; +} + +void Spell::AddUnitTarget(Unit* pVictim, SpellEffectIndex effIndex) +{ + SpellEffectEntry const *spellEffect = m_spellInfo->GetSpellEffect(effIndex); + if (!spellEffect || spellEffect->Effect == 0) + return; + + // Check for effect immune skip if immuned + bool immuned = pVictim->IsImmuneToSpellEffect(m_spellInfo, effIndex, pVictim == m_caster); + + if (pVictim->GetTypeId() == TYPEID_UNIT && ((Creature*)pVictim)->IsTotem() && (m_spellFlags & SPELL_FLAG_REDIRECTED)) + immuned = false; + + ObjectGuid targetGUID = pVictim->GetObjectGuid(); + + // Lookup target in already in list + for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if (targetGUID == ihit->targetGUID) // Found in list + { + if (!immuned) + ihit->effectMask |= 1 << effIndex; // Add only effect mask if not immuned + return; + } + } + + // This is new target calculate data for him + + // Get spell hit result on target + TargetInfo target; + target.targetGUID = targetGUID; // Store target GUID + target.effectMask = immuned ? 0 : (1 << effIndex); // Store index of effect if not immuned + target.processed = false; // Effects not applied on target + + // Calculate hit result + target.missCondition = m_caster->SpellHitResult(pVictim, m_spellInfo, m_canReflect); + + // spell fly from visual cast object + WorldObject* affectiveObject = GetAffectiveCasterObject(); + + // Spell have speed (possible inherited from triggering spell) - need calculate incoming time + float speed = m_spellInfo->speed == 0.0f && m_triggeredBySpellInfo ? m_triggeredBySpellInfo->speed : m_spellInfo->speed; + if (speed > 0.0f && affectiveObject && (pVictim != affectiveObject || (m_targets.m_targetMask & (TARGET_FLAG_SOURCE_LOCATION | TARGET_FLAG_DEST_LOCATION)))) + { + // calculate spell incoming interval + float dist = 0.0f; // distance to impact + if (pVictim == affectiveObject) // Calculate dist to destination target also for self-cast spells + { + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) + dist = affectiveObject->GetDistance(m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ); + else // Must have Source Target + dist = affectiveObject->GetDistance(m_targets.m_srcX, m_targets.m_srcY, m_targets.m_srcZ); + } + else // normal unit target, take distance + dist = affectiveObject->GetDistance(pVictim->GetPositionX(), pVictim->GetPositionY(), pVictim->GetPositionZ()); + + if (dist < 5.0f) + dist = 5.0f; + target.timeDelay = (uint64) floor(dist / speed * 1000.0f); + + // Calculate minimum incoming time + if (m_delayMoment == 0 || m_delayMoment > target.timeDelay) + m_delayMoment = target.timeDelay; + } + // Spell casted on self - mostly TRIGGER_MISSILE code + else if (m_spellInfo->speed > 0.0f && affectiveObject && pVictim == affectiveObject) + { + float dist = 0.0f; + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) + dist = affectiveObject->GetDistance(m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ); + + target.timeDelay = (uint64) floor(dist / m_spellInfo->speed * 1000.0f); + } + else + target.timeDelay = UI64LIT(0); + + // If target reflect spell back to caster + if (target.missCondition == SPELL_MISS_REFLECT) + { + // Calculate reflected spell result on caster + target.reflectResult = m_caster->SpellHitResult(m_caster, m_spellInfo, m_canReflect); + + if (target.reflectResult == SPELL_MISS_REFLECT) // Impossible reflect again, so simply deflect spell + target.reflectResult = SPELL_MISS_PARRY; + + // Increase time interval for reflected spells by 1.5 + target.timeDelay += target.timeDelay >> 1; + + m_spellFlags |= SPELL_FLAG_REFLECTED; + } + else + target.reflectResult = SPELL_MISS_NONE; + + // Add target to list + m_UniqueTargetInfo.push_back(target); +} + +void Spell::AddUnitTarget(ObjectGuid unitGuid, SpellEffectIndex effIndex) +{ + if (Unit* unit = m_caster->GetObjectGuid() == unitGuid ? m_caster : ObjectAccessor::GetUnit(*m_caster, unitGuid)) + AddUnitTarget(unit, effIndex); +} + +void Spell::AddGOTarget(GameObject* pVictim, SpellEffectIndex effIndex) +{ + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(effIndex); + if (!spellEffect || spellEffect->Effect == 0) + return; + + ObjectGuid targetGUID = pVictim->GetObjectGuid(); + + // Lookup target in already in list + for (GOTargetList::iterator ihit = m_UniqueGOTargetInfo.begin(); ihit != m_UniqueGOTargetInfo.end(); ++ihit) + { + if (targetGUID == ihit->targetGUID) // Found in list + { + ihit->effectMask |= (1 << effIndex); // Add only effect mask + return; + } + } + + // This is new target calculate data for him + + GOTargetInfo target; + target.targetGUID = targetGUID; + target.effectMask = (1 << effIndex); + target.processed = false; // Effects not apply on target + + // spell fly from visual cast object + WorldObject* affectiveObject = GetAffectiveCasterObject(); + + // Spell can have speed - need calculate incoming time + float speed = m_spellInfo->speed == 0.0f && m_triggeredBySpellInfo ? m_triggeredBySpellInfo->speed : m_spellInfo->speed; + if (speed > 0.0f && affectiveObject && pVictim != affectiveObject) + { + // calculate spell incoming interval + float dist = affectiveObject->GetDistance(pVictim->GetPositionX(), pVictim->GetPositionY(), pVictim->GetPositionZ()); + if (dist < 5.0f) + dist = 5.0f; + target.timeDelay = (uint64) floor(dist / speed * 1000.0f); + if (m_delayMoment == 0 || m_delayMoment > target.timeDelay) + m_delayMoment = target.timeDelay; + } + else + target.timeDelay = UI64LIT(0); + + // Add target to list + m_UniqueGOTargetInfo.push_back(target); +} + +void Spell::AddGOTarget(ObjectGuid goGuid, SpellEffectIndex effIndex) +{ + if (GameObject* go = m_caster->GetMap()->GetGameObject(goGuid)) + AddGOTarget(go, effIndex); +} + +void Spell::AddItemTarget(Item* pitem, SpellEffectIndex effIndex) +{ + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(effIndex); + if (!spellEffect || spellEffect->Effect == 0) + return; + + // Lookup target in already in list + for (ItemTargetList::iterator ihit = m_UniqueItemInfo.begin(); ihit != m_UniqueItemInfo.end(); ++ihit) + { + if (pitem == ihit->item) // Found in list + { + ihit->effectMask |= (1 << effIndex); // Add only effect mask + return; + } + } + + // This is new target add data + + ItemTargetInfo target; + target.item = pitem; + target.effectMask = (1 << effIndex); + m_UniqueItemInfo.push_back(target); +} + +void Spell::DoAllEffectOnTarget(TargetInfo* target) +{ + if (target->processed) // Check target + return; + target->processed = true; // Target checked in apply effects procedure + + // Get mask of effects for target + uint32 mask = target->effectMask; + + Unit* unit = m_caster->GetObjectGuid() == target->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target->targetGUID); + if (!unit) + return; + + // Get original caster (if exist) and calculate damage/healing from him data + Unit* real_caster = GetAffectiveCaster(); + // FIXME: in case wild GO heal/damage spells will be used target bonuses + Unit* caster = real_caster ? real_caster : m_caster; + + SpellMissInfo missInfo = target->missCondition; + // Need init unitTarget by default unit (can changed in code on reflect) + // Or on missInfo!=SPELL_MISS_NONE unitTarget undefined (but need in trigger subsystem) + unitTarget = unit; + + // Reset damage/healing counter + ResetEffectDamageAndHeal(); + + // Fill base trigger info + uint32 procAttacker = m_procAttacker; + uint32 procVictim = m_procVictim; + uint32 procEx = PROC_EX_NONE; + + // drop proc flags in case target not affected negative effects in negative spell + // for example caster bonus or animation, + // except miss case where will assigned PROC_EX_* flags later + if (((procAttacker | procVictim) & NEGATIVE_TRIGGER_MASK) && + !(target->effectMask & m_negativeEffectMask) && missInfo == SPELL_MISS_NONE) + { + procAttacker = PROC_FLAG_NONE; + procVictim = PROC_FLAG_NONE; + } + + float speed = m_spellInfo->speed == 0.0f && m_triggeredBySpellInfo ? m_triggeredBySpellInfo->speed : m_spellInfo->speed; + if (speed > 0.0f) + { + // mark effects that were already handled in Spell::HandleDelayedSpellLaunch on spell launch as processed + for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i) + if (IsEffectHandledOnDelayedSpellLaunch(m_spellInfo, SpellEffectIndex(i))) + mask &= ~(1 << i); + + // maybe used in effects that are handled on hit + m_damage += target->damage; + } + + if (missInfo == SPELL_MISS_NONE) // In case spell hit target, do all effect on that target + DoSpellHitOnUnit(unit, mask); + else if (missInfo == SPELL_MISS_REFLECT) // In case spell reflect from target, do all effect on caster (if hit) + { + if (target->reflectResult == SPELL_MISS_NONE) // If reflected spell hit caster -> do all effect on him + { + DoSpellHitOnUnit(m_caster, mask); + unitTarget = m_caster; + } + } + else if (missInfo == SPELL_MISS_MISS || missInfo == SPELL_MISS_RESIST) + { + if (real_caster && real_caster != unit) + { + // can cause back attack (if detected) + if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX3_NO_INITIAL_AGGRO) && !IsPositiveSpell(m_spellInfo->Id) && + m_caster->isVisibleForOrDetect(unit, unit, false)) + { + if (!unit->isInCombat() && unit->GetTypeId() != TYPEID_PLAYER && ((Creature*)unit)->AI()) + ((Creature*)unit)->AI()->AttackedBy(real_caster); + + unit->AddThreat(real_caster); + unit->SetInCombatWith(real_caster); + real_caster->SetInCombatWith(unit); + } + } + } + + // All calculated do it! + // Do healing and triggers + if (m_healing) + { + bool crit = real_caster && real_caster->IsSpellCrit(unitTarget, m_spellInfo, m_spellSchoolMask); + uint32 addhealth = m_healing; + if (crit) + { + procEx |= PROC_EX_CRITICAL_HIT; + addhealth = caster->SpellCriticalHealingBonus(m_spellInfo, addhealth, NULL); + } + else + procEx |= PROC_EX_NORMAL_HIT; + + uint32 absorb = 0; + unitTarget->CalculateHealAbsorb(addhealth, &absorb); + addhealth -= absorb; + + // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) + if (m_canTrigger && missInfo != SPELL_MISS_REFLECT) + { + caster->ProcDamageAndSpell(unitTarget, real_caster ? procAttacker : uint32(PROC_FLAG_NONE), procVictim, procEx, addhealth, m_attackType, m_spellInfo); + } + + int32 gain = caster->DealHeal(unitTarget, addhealth, m_spellInfo, crit, absorb); + + if (real_caster) + unitTarget->getHostileRefManager().threatAssist(real_caster, float(gain) * 0.5f * sSpellMgr.GetSpellThreatMultiplier(m_spellInfo), m_spellInfo); + } + // Do damage and triggers + else if (m_damage) + { + // Fill base damage struct (unitTarget - is real spell target) + SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo->Id, m_spellSchoolMask); + + if (speed > 0.0f) + { + damageInfo.damage = m_damage; + damageInfo.HitInfo = target->HitInfo; + } + // Add bonuses and fill damageInfo struct + else + caster->CalculateSpellDamage(&damageInfo, m_damage, m_spellInfo, m_attackType); + + unitTarget->CalculateAbsorbResistBlock(caster, &damageInfo, m_spellInfo); + + caster->DealDamageMods(damageInfo.target, damageInfo.damage, &damageInfo.absorb); + + // Send log damage message to client + caster->SendSpellNonMeleeDamageLog(&damageInfo); + + procEx = createProcExtendMask(&damageInfo, missInfo); + procVictim |= PROC_FLAG_TAKEN_ANY_DAMAGE; + + // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) + if (m_canTrigger && missInfo != SPELL_MISS_REFLECT) + caster->ProcDamageAndSpell(unitTarget, real_caster ? procAttacker : uint32(PROC_FLAG_NONE), procVictim, procEx, damageInfo.damage, m_attackType, m_spellInfo); + + // trigger weapon enchants for weapon based spells; exclude spells that stop attack, because may break CC + if (m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->GetEquippedItemClass() == ITEM_CLASS_WEAPON && + !m_spellInfo->HasAttribute(SPELL_ATTR_STOP_ATTACK_TARGET)) + ((Player*)m_caster)->CastItemCombatSpell(unitTarget, m_attackType); + + // Haunt (NOTE: for avoid use additional field damage stored in dummy value (replace unused 100%) + // apply before deal damage because aura can be removed at target kill + SpellClassOptionsEntry const *classOpt = m_spellInfo->GetSpellClassOptions(); + if (classOpt && classOpt->SpellFamilyName == SPELLFAMILY_WARLOCK && m_spellInfo->SpellIconID == 3172 && + (classOpt->SpellFamilyFlags & UI64LIT(0x0004000000000000))) + if(Aura* dummy = unitTarget->GetDummyAura(m_spellInfo->Id)) + dummy->GetModifier()->m_amount = damageInfo.damage; + + caster->DealSpellDamage(&damageInfo, true); + + // Scourge Strike, here because needs to use final damage in second part of the spell + if (classOpt && classOpt->SpellFamilyName == SPELLFAMILY_DEATHKNIGHT && classOpt->SpellFamilyFlags & UI64LIT(0x0800000000000000)) + { + uint32 count = 0; + Unit::SpellAuraHolderMap const& auras = unitTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + if(itr->second->GetSpellProto()->GetDispel() == DISPEL_DISEASE && + itr->second->GetCasterGuid() == caster->GetObjectGuid()) + ++count; + } + + if (count) + { + int32 bp = count * CalculateDamage(EFFECT_INDEX_2, unitTarget) * damageInfo.damage / 100; + if (bp) + caster->CastCustomSpell(unitTarget, 70890, &bp, NULL, NULL, true); + } + } + } + // Passive spell hits/misses or active spells only misses (only triggers if proc flags set) + else if (procAttacker || procVictim) + { + // Fill base damage struct (unitTarget - is real spell target) + SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo->Id, m_spellSchoolMask); + procEx = createProcExtendMask(&damageInfo, missInfo); + // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) + if (m_canTrigger && missInfo != SPELL_MISS_REFLECT) + caster->ProcDamageAndSpell(unit, real_caster ? procAttacker : uint32(PROC_FLAG_NONE), procVictim, procEx, 0, m_attackType, m_spellInfo); + } + + // Call scripted function for AI if this spell is casted upon a creature + if (unit->GetTypeId() == TYPEID_UNIT) + { + // cast at creature (or GO) quest objectives update at successful cast finished (+channel finished) + // ignore pets or autorepeat/melee casts for speed (not exist quest for spells (hm... ) + if (real_caster && !((Creature*)unit)->IsPet() && !IsAutoRepeat() && !IsNextMeleeSwingSpell() && !IsChannelActive()) + if (Player* p = real_caster->GetCharmerOrOwnerPlayerOrPlayerItself()) + p->RewardPlayerAndGroupAtCast(unit, m_spellInfo->Id); + + if (((Creature*)unit)->AI()) + ((Creature*)unit)->AI()->SpellHit(m_caster, m_spellInfo); + } + + // Call scripted function for AI if this spell is casted by a creature + if (m_caster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_caster)->AI()) + ((Creature*)m_caster)->AI()->SpellHitTarget(unit, m_spellInfo); + if (real_caster && real_caster != m_caster && real_caster->GetTypeId() == TYPEID_UNIT && ((Creature*)real_caster)->AI()) + ((Creature*)real_caster)->AI()->SpellHitTarget(unit, m_spellInfo); +} + +void Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask) +{ + if (!unit || !effectMask) + return; + + Unit* realCaster = GetAffectiveCaster(); + + // Recheck immune (only for delayed spells) + float speed = m_spellInfo->speed == 0.0f && m_triggeredBySpellInfo ? m_triggeredBySpellInfo->speed : m_spellInfo->speed; + if (speed && ( + unit->IsImmunedToDamage(GetSpellSchoolMask(m_spellInfo)) || + unit->IsImmuneToSpell(m_spellInfo, unit == realCaster))) + { + if (realCaster) + realCaster->SendSpellMiss(unit, m_spellInfo->Id, SPELL_MISS_IMMUNE); + + ResetEffectDamageAndHeal(); + return; + } + + if (unit->GetTypeId() == TYPEID_PLAYER) + { + ((Player*)unit)->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET, m_spellInfo->Id); + ((Player*)unit)->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2, m_spellInfo->Id); + } + + if (realCaster && realCaster->GetTypeId() == TYPEID_PLAYER) + ((Player*)realCaster)->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2, m_spellInfo->Id, 0, unit); + + if (realCaster && realCaster != unit) + { + // Recheck UNIT_FLAG_NON_ATTACKABLE for delayed spells + if (speed > 0.0f && + unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE) && + unit->GetCharmerOrOwnerGuid() != m_caster->GetObjectGuid()) + { + realCaster->SendSpellMiss(unit, m_spellInfo->Id, SPELL_MISS_EVADE); + ResetEffectDamageAndHeal(); + return; + } + + if (!realCaster->IsFriendlyTo(unit)) + { + // for delayed spells ignore not visible explicit target + if (speed > 0.0f && unit == m_targets.getUnitTarget() && + !unit->isVisibleForOrDetect(m_caster, m_caster, false)) + { + realCaster->SendSpellMiss(unit, m_spellInfo->Id, SPELL_MISS_EVADE); + ResetEffectDamageAndHeal(); + return; + } + + // not break stealth by cast targeting + if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX_NOT_BREAK_STEALTH)) + unit->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); + + // can cause back attack (if detected), stealth removed at Spell::cast if spell break it + if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX3_NO_INITIAL_AGGRO) && !IsPositiveSpell(m_spellInfo->Id) && + m_caster->isVisibleForOrDetect(unit, unit, false)) + { + // use speedup check to avoid re-remove after above lines + if (m_spellInfo->HasAttribute(SPELL_ATTR_EX_NOT_BREAK_STEALTH)) + unit->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); + + // caster can be detected but have stealth aura + m_caster->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); + + if (!unit->IsStandState() && !unit->hasUnitState(UNIT_STAT_STUNNED)) + unit->SetStandState(UNIT_STAND_STATE_STAND); + + if (!unit->isInCombat() && unit->GetTypeId() != TYPEID_PLAYER && ((Creature*)unit)->AI()) + unit->AttackedBy(realCaster); + + unit->AddThreat(realCaster); + unit->SetInCombatWith(realCaster); + realCaster->SetInCombatWith(unit); + + if (Player* attackedPlayer = unit->GetCharmerOrOwnerPlayerOrPlayerItself()) + realCaster->SetContestedPvP(attackedPlayer); + } + } + else + { + // for delayed spells ignore negative spells (after duel end) for friendly targets + if (speed > 0.0f && !IsPositiveSpell(m_spellInfo->Id)) + { + realCaster->SendSpellMiss(unit, m_spellInfo->Id, SPELL_MISS_EVADE); + ResetEffectDamageAndHeal(); + return; + } + + // assisting case, healing and resurrection + if (unit->hasUnitState(UNIT_STAT_ATTACK_PLAYER)) + realCaster->SetContestedPvP(); + + if (unit->isInCombat() && !m_spellInfo->HasAttribute(SPELL_ATTR_EX3_NO_INITIAL_AGGRO)) + { + realCaster->SetInCombatState(unit->GetCombatTimer() > 0); + unit->getHostileRefManager().threatAssist(realCaster, 0.0f, m_spellInfo); + } + } + } + + // Get Data Needed for Diminishing Returns, some effects may have multiple auras, so this must be done on spell hit, not aura add + m_diminishGroup = GetDiminishingReturnsGroupForSpell(m_spellInfo, m_triggeredByAuraSpell); + m_diminishLevel = unit->GetDiminishing(m_diminishGroup); + // Increase Diminishing on unit, current informations for actually casts will use values above + if ((GetDiminishingReturnsGroupType(m_diminishGroup) == DRTYPE_PLAYER && unit->GetTypeId() == TYPEID_PLAYER) || + GetDiminishingReturnsGroupType(m_diminishGroup) == DRTYPE_ALL) + unit->IncrDiminishing(m_diminishGroup); + + // Apply additional spell effects to target + CastPreCastSpells(unit); + + if (IsSpellAppliesAura(m_spellInfo, effectMask)) + { + m_spellAuraHolder = CreateSpellAuraHolder(m_spellInfo, unit, realCaster, m_CastItem); + m_spellAuraHolder->setDiminishGroup(m_diminishGroup); + } + else + m_spellAuraHolder = NULL; + + for (int effectNumber = 0; effectNumber < MAX_EFFECT_INDEX; ++effectNumber) + { + if (effectMask & (1 << effectNumber)) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(effectNumber)); + HandleEffects(unit, NULL, NULL, SpellEffectIndex(effectNumber), m_damageMultipliers[effectNumber]); + if (m_applyMultiplierMask & (1 << effectNumber)) + { + // Get multiplier + float multiplier = spellEffect ? spellEffect->DmgMultiplier : 1.0f; + // Apply multiplier mods + if (realCaster) + if (Player* modOwner = realCaster->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_EFFECT_PAST_FIRST, multiplier, this); + m_damageMultipliers[effectNumber] *= multiplier; + } + } + } + + // now apply all created auras + if (m_spellAuraHolder) + { + // normally shouldn't happen + if (!m_spellAuraHolder->IsEmptyHolder()) + { + int32 duration = m_spellAuraHolder->GetAuraMaxDuration(); + int32 originalDuration = duration; + + if (duration > 0) + { + int32 limitduration = GetDiminishingReturnsLimitDuration(m_diminishGroup, m_spellInfo); + unit->ApplyDiminishingToDuration(m_diminishGroup, duration, m_caster, m_diminishLevel, limitduration, m_spellFlags & SPELL_FLAG_REFLECTED); + + // Fully diminished + if (duration == 0) + { + delete m_spellAuraHolder; + return; + } + } + + duration = unit->CalculateAuraDuration(m_spellInfo, effectMask, duration, m_caster, this); + + if (duration != originalDuration) + { + m_spellAuraHolder->SetAuraMaxDuration(duration); + m_spellAuraHolder->SetAuraDuration(duration); + } + + unit->AddSpellAuraHolder(m_spellAuraHolder); + } + else + delete m_spellAuraHolder; + } +} + +void Spell::DoAllEffectOnTarget(GOTargetInfo* target) +{ + if (target->processed) // Check target + return; + target->processed = true; // Target checked in apply effects procedure + + uint32 effectMask = target->effectMask; + if (!effectMask) + return; + + GameObject* go = m_caster->GetMap()->GetGameObject(target->targetGUID); + if (!go) + return; + + for (int effectNumber = 0; effectNumber < MAX_EFFECT_INDEX; ++effectNumber) + if (effectMask & (1 << effectNumber)) + HandleEffects(NULL, NULL, go, SpellEffectIndex(effectNumber)); + + // cast at creature (or GO) quest objectives update at successful cast finished (+channel finished) + // ignore autorepeat/melee casts for speed (not exist quest for spells (hm... ) + if (!IsAutoRepeat() && !IsNextMeleeSwingSpell() && !IsChannelActive()) + { + if (Player* p = m_caster->GetCharmerOrOwnerPlayerOrPlayerItself()) + p->RewardPlayerAndGroupAtCast(go, m_spellInfo->Id); + } +} + +void Spell::DoAllEffectOnTarget(ItemTargetInfo* target) +{ + uint32 effectMask = target->effectMask; + if (!target->item || !effectMask) + return; + + for (int effectNumber = 0; effectNumber < MAX_EFFECT_INDEX; ++effectNumber) + if (effectMask & (1 << effectNumber)) + HandleEffects(NULL, target->item, NULL, SpellEffectIndex(effectNumber)); +} + +void Spell::HandleDelayedSpellLaunch(TargetInfo* target) +{ + // Get mask of effects for target + uint32 mask = target->effectMask; + + Unit* unit = m_caster->GetObjectGuid() == target->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target->targetGUID); + if (!unit) + return; + + // Get original caster (if exist) and calculate damage/healing from him data + Unit* real_caster = GetAffectiveCaster(); + // FIXME: in case wild GO heal/damage spells will be used target bonuses + Unit* caster = real_caster ? real_caster : m_caster; + + SpellMissInfo missInfo = target->missCondition; + // Need init unitTarget by default unit (can changed in code on reflect) + // Or on missInfo!=SPELL_MISS_NONE unitTarget undefined (but need in trigger subsystem) + unitTarget = unit; + + // Reset damage/healing counter + m_damage = 0; + m_healing = 0; // healing maybe not needed at this point + + // Fill base damage struct (unitTarget - is real spell target) + SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo->Id, m_spellSchoolMask); + + // keep damage amount for reflected spells + if (missInfo == SPELL_MISS_NONE || (missInfo == SPELL_MISS_REFLECT && target->reflectResult == SPELL_MISS_NONE)) + { + for (int32 effectNumber = 0; effectNumber < MAX_EFFECT_INDEX; ++effectNumber) + { + if (mask & (1 << effectNumber) && IsEffectHandledOnDelayedSpellLaunch(m_spellInfo, SpellEffectIndex(effectNumber))) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(effectNumber)); + HandleEffects(unit, NULL, NULL, SpellEffectIndex(effectNumber), m_damageMultipliers[effectNumber]); + if (m_applyMultiplierMask & (1 << effectNumber)) + { + // Get multiplier + float multiplier = spellEffect ? spellEffect->DmgMultiplier : 1.0f; + // Apply multiplier mods + if (real_caster) + if (Player* modOwner = real_caster->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_EFFECT_PAST_FIRST, multiplier, this); + m_damageMultipliers[effectNumber] *= multiplier; + } + } + } + + if (m_damage > 0) + caster->CalculateSpellDamage(&damageInfo, m_damage, m_spellInfo, m_attackType); + } + + target->damage = damageInfo.damage; + target->HitInfo = damageInfo.HitInfo; +} + +void Spell::InitializeDamageMultipliers() +{ + for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(i)); + if(!spellEffect) + continue; + if (spellEffect->Effect == 0) + continue; + + uint32 EffectChainTarget = spellEffect->EffectChainTarget; + if (Unit* realCaster = GetAffectiveCaster()) + if (Player* modOwner = realCaster->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_JUMP_TARGETS, EffectChainTarget, this); + + m_damageMultipliers[i] = 1.0f; + if( (spellEffect->EffectImplicitTargetA == TARGET_CHAIN_DAMAGE || spellEffect->EffectImplicitTargetA == TARGET_CHAIN_HEAL) && + (EffectChainTarget > 1) ) + m_applyMultiplierMask |= (1 << i); + } +} + +bool Spell::IsAliveUnitPresentInTargetList() +{ + // Not need check return true + if (m_needAliveTargetMask == 0) + return true; + + uint8 needAliveTargetMask = m_needAliveTargetMask; + + for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if (ihit->missCondition == SPELL_MISS_NONE && (needAliveTargetMask & ihit->effectMask)) + { + Unit* unit = m_caster->GetObjectGuid() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID); + + // either unit is alive and normal spell, or unit dead and deathonly-spell + if (unit && (unit->isAlive() != IsDeathOnlySpell(m_spellInfo))) + needAliveTargetMask &= ~ihit->effectMask; // remove from need alive mask effect that have alive target + } + } + + // is all effects from m_needAliveTargetMask have alive targets + return needAliveTargetMask == 0; +} + +// Helper for Chain Healing +// Spell target first +// Raidmates then descending by injury suffered (MaxHealth - Health) +// Other players/mobs then descending by injury suffered (MaxHealth - Health) +struct ChainHealingOrder : public std::binary_function +{ + const Unit* MainTarget; + ChainHealingOrder(Unit const* Target) : MainTarget(Target) {}; + // functor for operator ">" + bool operator()(Unit const* _Left, Unit const* _Right) const + { + return (ChainHealingHash(_Left) < ChainHealingHash(_Right)); + } + int32 ChainHealingHash(Unit const* Target) const + { + if (Target == MainTarget) + return 0; + else if (Target->GetTypeId() == TYPEID_PLAYER && MainTarget->GetTypeId() == TYPEID_PLAYER && + ((Player const*)Target)->IsInSameRaidWith((Player const*)MainTarget)) + { + if (Target->GetHealth() == Target->GetMaxHealth()) + return 40000; + else + return 20000 - Target->GetMaxHealth() + Target->GetHealth(); + } + else + return 40000 - Target->GetMaxHealth() + Target->GetHealth(); + } +}; + +class ChainHealingFullHealth: std::unary_function +{ + public: + const Unit* MainTarget; + ChainHealingFullHealth(const Unit* Target) : MainTarget(Target) {}; + + bool operator()(const Unit* Target) + { + return (Target != MainTarget && Target->GetHealth() == Target->GetMaxHealth()); + } +}; + +// Helper for targets nearest to the spell target +// The spell target is always first unless there is a target at _completely_ the same position (unbelievable case) +struct TargetDistanceOrderNear : public std::binary_function +{ + const Unit* MainTarget; + TargetDistanceOrderNear(const Unit* Target) : MainTarget(Target) {}; + // functor for operator ">" + bool operator()(const Unit* _Left, const Unit* _Right) const + { + return MainTarget->GetDistanceOrder(_Left, _Right); + } +}; + +// Helper for targets furthest away to the spell target +// The spell target is always first unless there is a target at _completely_ the same position (unbelievable case) +struct TargetDistanceOrderFarAway : public std::binary_function +{ + const Unit* MainTarget; + TargetDistanceOrderFarAway(const Unit* Target) : MainTarget(Target) {}; + // functor for operator "<" + bool operator()(const Unit* _Left, const Unit* _Right) const + { + return !MainTarget->GetDistanceOrder(_Left, _Right); + } +}; + +void Spell::SetTargetMap(SpellEffectIndex effIndex, uint32 targetMode, UnitList& targetUnitMap) +{ + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(effIndex); + SpellClassOptionsEntry const* classOpt = m_spellInfo->GetSpellClassOptions(); + if (!spellEffect) + return; + + uint32 EffectChainTarget = spellEffect ? spellEffect->EffectChainTarget : 0; + uint32 unMaxTargets = m_spellInfo->GetMaxAffectedTargets(); + + float radius; + GetSpellRangeAndRadius(spellEffect, radius, EffectChainTarget, unMaxTargets); + + std::list tempTargetGOList; + + switch (targetMode) + { + case TARGET_RANDOM_NEARBY_LOC: + // special case for Fatal Attraction (BT, Mother Shahraz) + if (m_spellInfo->Id == 40869) + radius = 30.0f; + + // Get a random point in circle. Use sqrt(rand) to correct distribution when converting polar to Cartesian coordinates. + radius *= sqrtf(rand_norm_f()); + // no 'break' expected since we use code in case TARGET_RANDOM_CIRCUMFERENCE_POINT!!! + case TARGET_RANDOM_CIRCUMFERENCE_POINT: + { + // Get a random point AT the circumference + float angle = 2.0f * M_PI_F * rand_norm_f(); + float dest_x, dest_y, dest_z; + m_caster->GetClosePoint(dest_x, dest_y, dest_z, 0.0f, radius, angle); + m_targets.setDestination(dest_x, dest_y, dest_z); + + // This targetMode is often used as 'last' implicitTarget for positive spells, that just require coordinates + // and no unitTarget (e.g. summon effects). As MaNGOS always needs a unitTarget we add just the caster here. + // Logic: This is first target, and no second target => use m_caster -- This is second target: use m_caster if the spell is positive or a summon spell + if ((spellEffect->EffectImplicitTargetA == targetMode && spellEffect->EffectImplicitTargetB == TARGET_NONE) || + (spellEffect->EffectImplicitTargetB == targetMode && (IsPositiveSpell(m_spellInfo) || spellEffect->Effect == SPELL_EFFECT_SUMMON))) + targetUnitMap.push_back(m_caster); + break; + } + case TARGET_91: + case TARGET_RANDOM_NEARBY_DEST: + { + // Get a random point IN the CIRCEL around current M_TARGETS COORDINATES(!). + if (radius > 0.0f) + { + // Use sqrt(rand) to correct distribution when converting polar to Cartesian coordinates. + radius *= sqrtf(rand_norm_f()); + float angle = 2.0f * M_PI_F * rand_norm_f(); + float dest_x = m_targets.m_destX + cos(angle) * radius; + float dest_y = m_targets.m_destY + sin(angle) * radius; + float dest_z = m_caster->GetPositionZ(); + if (!MapManager::IsValidMapCoord(m_caster->GetMapId(), dest_x, dest_y, dest_z)) + { + sLog.outError("Spell::SetTargetMap: invalid map coordinates for spell %u eff_idx %u target mode %u: mapid %u x %f y %f z %f\n" + "spell radius: %f caster position: x %f y %f z %f\n" + "base dest position: x %f y %f z %f", + m_spellInfo->Id, effIndex, targetMode, m_caster->GetMapId(), dest_x, dest_y, dest_z, + radius, m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ(), + m_targets.m_destX, m_targets.m_destY, m_caster->GetPositionZ()); + m_targets.setDestination(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ()); + } + else + { + m_caster->UpdateGroundPositionZ(dest_x, dest_y, dest_z); + m_targets.setDestination(dest_x, dest_y, dest_z); + } + } + + // This targetMode is often used as 'last' implicitTarget for positive spells, that just require coordinates + // and no unitTarget (e.g. summon effects). As MaNGOS always needs a unitTarget we add just the caster here. + // Logic: This is first target, and no second target => use m_caster -- This is second target: use m_caster if the spell is positive or a summon spell + if ((spellEffect->EffectImplicitTargetA == targetMode && spellEffect->EffectImplicitTargetB == TARGET_NONE) || + (spellEffect->EffectImplicitTargetB == targetMode && (IsPositiveSpell(m_spellInfo) || spellEffect->Effect == SPELL_EFFECT_SUMMON))) + targetUnitMap.push_back(m_caster); + + break; + } + case TARGET_TOTEM_EARTH: + case TARGET_TOTEM_WATER: + case TARGET_TOTEM_AIR: + case TARGET_TOTEM_FIRE: + { + float angle = m_caster->GetOrientation(); + switch (targetMode) + { + case TARGET_TOTEM_FIRE: angle += M_PI_F * 0.25f; break; // front - left + case TARGET_TOTEM_AIR: angle += M_PI_F * 0.75f; break; // back - left + case TARGET_TOTEM_WATER: angle += M_PI_F * 1.25f; break; // back - right + case TARGET_TOTEM_EARTH: angle += M_PI_F * 1.75f; break; // front - right + } + + float x, y; + float z = m_caster->GetPositionZ(); + // Do not search for a free spot. TODO: Should there be searched for a free spot. There was once a discussion that in case this space was impossible (LOS) m_caster's position should be used. + // TODO Bring this back to memory and search for it! + m_caster->GetNearPoint2D(x, y, radius, angle); + m_caster->UpdateAllowedPositionZ(x, y, z); + m_targets.setDestination(x, y, z); + + // Add Summoner + targetUnitMap.push_back(m_caster); + break; + } + case TARGET_SELF: + case TARGET_SELF2: + targetUnitMap.push_back(m_caster); + break; + case TARGET_RANDOM_ENEMY_CHAIN_IN_AREA: + { + m_targets.m_targetMask = 0; + unMaxTargets = EffectChainTarget; + float max_range = radius + unMaxTargets * CHAIN_SPELL_JUMP_RADIUS; + + UnitList tempTargetUnitMap; + + { + MaNGOS::AnyAoETargetUnitInObjectRangeCheck u_check(m_caster, max_range); + MaNGOS::UnitListSearcher searcher(tempTargetUnitMap, u_check); + Cell::VisitAllObjects(m_caster, searcher, max_range); + } + + if (tempTargetUnitMap.empty()) + break; + + tempTargetUnitMap.sort(TargetDistanceOrderNear(m_caster)); + + // Now to get us a random target that's in the initial range of the spell + uint32 t = 0; + UnitList::iterator itr = tempTargetUnitMap.begin(); + while (itr != tempTargetUnitMap.end() && (*itr)->IsWithinDist(m_caster, radius)) + ++t, ++itr; + + if (!t) + break; + + itr = tempTargetUnitMap.begin(); + std::advance(itr, rand() % t); + Unit* pUnitTarget = *itr; + targetUnitMap.push_back(pUnitTarget); + + tempTargetUnitMap.erase(itr); + + tempTargetUnitMap.sort(TargetDistanceOrderNear(pUnitTarget)); + + t = unMaxTargets - 1; + Unit* prev = pUnitTarget; + UnitList::iterator next = tempTargetUnitMap.begin(); + + while (t && next != tempTargetUnitMap.end()) + { + if (!prev->IsWithinDist(*next, CHAIN_SPELL_JUMP_RADIUS)) + break; + + if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX2_IGNORE_LOS) && !prev->IsWithinLOSInMap(*next)) + { + ++next; + continue; + } + + prev = *next; + targetUnitMap.push_back(prev); + tempTargetUnitMap.erase(next); + tempTargetUnitMap.sort(TargetDistanceOrderNear(prev)); + next = tempTargetUnitMap.begin(); + + --t; + } + break; + } + case TARGET_RANDOM_FRIEND_CHAIN_IN_AREA: + { + m_targets.m_targetMask = 0; + unMaxTargets = EffectChainTarget; + float max_range = radius + unMaxTargets * CHAIN_SPELL_JUMP_RADIUS; + UnitList tempTargetUnitMap; + { + MaNGOS::AnyFriendlyUnitInObjectRangeCheck u_check(m_caster, max_range); + MaNGOS::UnitListSearcher searcher(tempTargetUnitMap, u_check); + Cell::VisitAllObjects(m_caster, searcher, max_range); + } + + if (tempTargetUnitMap.empty()) + break; + + tempTargetUnitMap.sort(TargetDistanceOrderNear(m_caster)); + + // Now to get us a random target that's in the initial range of the spell + uint32 t = 0; + UnitList::iterator itr = tempTargetUnitMap.begin(); + while (itr != tempTargetUnitMap.end() && (*itr)->IsWithinDist(m_caster, radius)) + ++t, ++itr; + + if (!t) + break; + + itr = tempTargetUnitMap.begin(); + std::advance(itr, rand() % t); + Unit* pUnitTarget = *itr; + targetUnitMap.push_back(pUnitTarget); + + tempTargetUnitMap.erase(itr); + + tempTargetUnitMap.sort(TargetDistanceOrderNear(pUnitTarget)); + + t = unMaxTargets - 1; + Unit* prev = pUnitTarget; + UnitList::iterator next = tempTargetUnitMap.begin(); + + while (t && next != tempTargetUnitMap.end()) + { + if (!prev->IsWithinDist(*next, CHAIN_SPELL_JUMP_RADIUS)) + break; + + if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX2_IGNORE_LOS) && !prev->IsWithinLOSInMap(*next)) + { + ++next; + continue; + } + prev = *next; + targetUnitMap.push_back(prev); + tempTargetUnitMap.erase(next); + tempTargetUnitMap.sort(TargetDistanceOrderNear(prev)); + next = tempTargetUnitMap.begin(); + --t; + } + break; + } + case TARGET_PET: + { + Pet* tmpUnit = m_caster->GetPet(); + if (!tmpUnit) break; + targetUnitMap.push_back(tmpUnit); + break; + } + case TARGET_CHAIN_DAMAGE: + { + if (EffectChainTarget <= 1) + { + if (Unit* pUnitTarget = m_caster->SelectMagnetTarget(m_targets.getUnitTarget(), this, effIndex)) + { + if (m_targets.getUnitTarget() && m_targets.getUnitTarget() != pUnitTarget) + m_spellFlags |= SPELL_FLAG_REDIRECTED; + + m_targets.setUnitTarget(pUnitTarget); + targetUnitMap.push_back(pUnitTarget); + } + } + else + { + Unit* pUnitTarget = m_targets.getUnitTarget(); + WorldObject* originalCaster = GetAffectiveCasterObject(); + if (!pUnitTarget || !originalCaster) + break; + + unMaxTargets = EffectChainTarget; + + float max_range; + if(m_spellInfo->GetDmgClass() == SPELL_DAMAGE_CLASS_MELEE) + max_range = radius; + else + // FIXME: This very like horrible hack and wrong for most spells + max_range = radius + unMaxTargets * CHAIN_SPELL_JUMP_RADIUS; + + UnitList tempTargetUnitMap; + { + MaNGOS::AnyAoEVisibleTargetUnitInObjectRangeCheck u_check(pUnitTarget, originalCaster, max_range); + MaNGOS::UnitListSearcher searcher(tempTargetUnitMap, u_check); + Cell::VisitAllObjects(m_caster, searcher, max_range); + } + + if (tempTargetUnitMap.empty()) + break; + + tempTargetUnitMap.sort(TargetDistanceOrderNear(pUnitTarget)); + + if (*tempTargetUnitMap.begin() == pUnitTarget) + tempTargetUnitMap.erase(tempTargetUnitMap.begin()); + + targetUnitMap.push_back(pUnitTarget); + uint32 t = unMaxTargets - 1; + Unit* prev = pUnitTarget; + UnitList::iterator next = tempTargetUnitMap.begin(); + + while (t && next != tempTargetUnitMap.end()) + { + if (!prev->IsWithinDist(*next, CHAIN_SPELL_JUMP_RADIUS)) + break; + + if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX2_IGNORE_LOS) && !prev->IsWithinLOSInMap(*next)) + { + ++next; + continue; + } + + prev = *next; + targetUnitMap.push_back(prev); + tempTargetUnitMap.erase(next); + tempTargetUnitMap.sort(TargetDistanceOrderNear(prev)); + next = tempTargetUnitMap.begin(); + + --t; + } + } + break; + } + case TARGET_ALL_ENEMY_IN_AREA: + FillAreaTargets(targetUnitMap, radius, PUSH_DEST_CENTER, SPELL_TARGETS_AOE_DAMAGE); + + switch (m_spellInfo->Id) + { + // Do not target current victim + case 30843: // Enfeeble + case 31347: // Doom + case 37676: // Insidious Whisper + case 38028: // Watery Grave + case 40618: // Insignificance + case 41376: // Spite + case 62166: // Stone Grip + case 63981: // Stone Grip (h) + { + if (Unit* pVictim = m_caster->getVictim()) + targetUnitMap.remove(pVictim); + break; + } + // Other special cases + case 42005: // Bloodboil (spell hits only the 5 furthest away targets) + { + if (targetUnitMap.size() > unMaxTargets) + { + targetUnitMap.sort(TargetDistanceOrderFarAway(m_caster)); + targetUnitMap.resize(unMaxTargets); + } + break; + } + default: + break; + } + break; + case TARGET_AREAEFFECT_INSTANT: + { + SpellTargets targetB = SPELL_TARGETS_AOE_DAMAGE; + switch (spellEffect->Effect) + { + case SPELL_EFFECT_QUEST_COMPLETE: + case SPELL_EFFECT_KILL_CREDIT_PERSONAL: + case SPELL_EFFECT_KILL_CREDIT_GROUP: + targetB = SPELL_TARGETS_ALL; + default: + // Select friendly targets for positive effect + if (IsPositiveEffect(m_spellInfo, effIndex)) + targetB = SPELL_TARGETS_FRIENDLY; + } + + UnitList tempTargetUnitMap; + SQLMultiStorage::SQLMSIteratorBounds bounds = sSpellScriptTargetStorage.getBounds(m_spellInfo->Id); + + // fill real target list if no spell script target defined + FillAreaTargets(bounds.first != bounds.second ? tempTargetUnitMap : targetUnitMap, + radius, PUSH_DEST_CENTER, bounds.first != bounds.second ? SPELL_TARGETS_ALL : targetB); + + if (!tempTargetUnitMap.empty()) + { + for (UnitList::const_iterator iter = tempTargetUnitMap.begin(); iter != tempTargetUnitMap.end(); ++iter) + { + if ((*iter)->GetTypeId() != TYPEID_UNIT) + continue; + + for (SQLMultiStorage::SQLMultiSIterator i_spellST = bounds.first; i_spellST != bounds.second; ++i_spellST) + { + if (i_spellST->CanNotHitWithSpellEffect(effIndex)) + continue; + + // only creature entries supported for this target type + if (i_spellST->type == SPELL_TARGET_TYPE_GAMEOBJECT) + continue; + + if ((*iter)->GetEntry() == i_spellST->targetEntry) + { + if (i_spellST->type == SPELL_TARGET_TYPE_DEAD && ((Creature*)(*iter))->IsCorpse()) + { + targetUnitMap.push_back((*iter)); + } + else if (i_spellST->type == SPELL_TARGET_TYPE_CREATURE && (*iter)->isAlive()) + { + targetUnitMap.push_back((*iter)); + } + + break; + } + } + } + } + + // exclude caster + targetUnitMap.remove(m_caster); + break; + } + case TARGET_AREAEFFECT_CUSTOM: + { + if (spellEffect && spellEffect->Effect == SPELL_EFFECT_PERSISTENT_AREA_AURA) + break; + else if (spellEffect && spellEffect->Effect == SPELL_EFFECT_SUMMON) + { + targetUnitMap.push_back(m_caster); + break; + } + + UnitList tempTargetUnitMap; + SQLMultiStorage::SQLMSIteratorBounds bounds = sSpellScriptTargetStorage.getBounds(m_spellInfo->Id); + // fill real target list if no spell script target defined + FillAreaTargets(bounds.first != bounds.second ? tempTargetUnitMap : targetUnitMap, radius, PUSH_DEST_CENTER, SPELL_TARGETS_ALL); + + if (!tempTargetUnitMap.empty()) + { + for (UnitList::const_iterator iter = tempTargetUnitMap.begin(); iter != tempTargetUnitMap.end(); ++iter) + { + if ((*iter)->GetTypeId() != TYPEID_UNIT) + continue; + + for (SQLMultiStorage::SQLMultiSIterator i_spellST = bounds.first; i_spellST != bounds.second; ++i_spellST) + { + if (i_spellST->CanNotHitWithSpellEffect(effIndex)) + continue; + + // only creature entries supported for this target type + if (i_spellST->type == SPELL_TARGET_TYPE_GAMEOBJECT) + continue; + + if ((*iter)->GetEntry() == i_spellST->targetEntry) + { + if (i_spellST->type == SPELL_TARGET_TYPE_DEAD && ((Creature*)(*iter))->IsCorpse()) + { + targetUnitMap.push_back((*iter)); + } + else if (i_spellST->type == SPELL_TARGET_TYPE_CREATURE && (*iter)->isAlive()) + { + targetUnitMap.push_back((*iter)); + } + + break; + } + } + } + } + else + { + // remove not targetable units if spell has no script targets + for (UnitList::iterator itr = targetUnitMap.begin(); itr != targetUnitMap.end();) + { + if (!(*itr)->isTargetableForAttack(m_spellInfo->HasAttribute(SPELL_ATTR_EX3_CAST_ON_DEAD))) + targetUnitMap.erase(itr++); + else + ++itr; + } + } + break; + } + case TARGET_AREAEFFECT_GO_AROUND_SOURCE: + case TARGET_AREAEFFECT_GO_AROUND_DEST: + case TARGET_GO_IN_FRONT_OF_CASTER_90: + { + float x, y, z; + + if (targetMode == TARGET_AREAEFFECT_GO_AROUND_SOURCE && (m_targets.m_targetMask & TARGET_FLAG_SOURCE_LOCATION)) + m_targets.getSource(x, y, z); + else if (targetMode == TARGET_AREAEFFECT_GO_AROUND_DEST) + m_targets.getDestination(x, y, z); + else // can also happen for GO_AROUND_SOURCE without SOURCE_LOCATION + m_caster->GetPosition(x, y, z); + + bool fixedTargetExist = false; + SQLMultiStorage::SQLMSIteratorBounds bounds = sSpellScriptTargetStorage.getBounds(m_spellInfo->Id); + for (SQLMultiStorage::SQLMultiSIterator i_spellST = bounds.first; i_spellST != bounds.second; ++i_spellST) + { + if (i_spellST->CanNotHitWithSpellEffect(effIndex)) + continue; + + if (i_spellST->type == SPELL_TARGET_TYPE_GAMEOBJECT) + { + fixedTargetExist = true; + // search all GO's with entry, within range of m_destN + MaNGOS::GameObjectEntryInPosRangeCheck go_check(*m_caster, i_spellST->targetEntry, x, y, z, radius); + MaNGOS::GameObjectListSearcher checker(tempTargetGOList, go_check); + Cell::VisitGridObjects(m_caster, checker, radius + GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex))); + } + } + + if (!fixedTargetExist) + { + // Generic handling for spells that require GO-type 33 + if (spellEffect->Effect == SPELL_EFFECT_WMO_DAMAGE || spellEffect->Effect == SPELL_EFFECT_WMO_REPAIR || spellEffect->Effect == SPELL_EFFECT_WMO_CHANGE) + { + MaNGOS::GameObjectTypeInPosRangeCheck go_check(*m_caster, GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING, x, y, z, radius, spellEffect->Effect == SPELL_EFFECT_WMO_DAMAGE, spellEffect->Effect == SPELL_EFFECT_WMO_REPAIR); + MaNGOS::GameObjectListSearcher checker(tempTargetGOList, go_check); + Cell::VisitGridObjects(m_caster, checker, radius + GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex))); + } + } + + // Filter some targets for special target-type + for (std::list::iterator itr = tempTargetGOList.begin(); itr != tempTargetGOList.end();) + { + switch (targetMode) + { + case TARGET_GO_IN_FRONT_OF_CASTER_90: + if (!m_caster->HasInArc(M_PI_F / 2, *itr)) + { + tempTargetGOList.erase(itr++); + continue; + } + // no break here + case TARGET_AREAEFFECT_GO_AROUND_SOURCE: + case TARGET_AREAEFFECT_GO_AROUND_DEST: + default: + ++itr; + } + } + break; + } + case TARGET_ALL_ENEMY_IN_AREA_INSTANT: + { + // targets the ground, not the units in the area + if(!spellEffect) + break; + + switch(spellEffect->Effect) + { + case SPELL_EFFECT_PERSISTENT_AREA_AURA: + break; + case SPELL_EFFECT_SUMMON: + targetUnitMap.push_back(m_caster); + break; + default: + FillAreaTargets(targetUnitMap, radius, PUSH_DEST_CENTER, SPELL_TARGETS_AOE_DAMAGE); + + // Mind Sear, triggered + if (m_spellInfo->IsFitToFamily(SPELLFAMILY_PRIEST, UI64LIT(0x0008000000000000))) + if (Unit* unitTarget = m_targets.getUnitTarget()) + targetUnitMap.remove(unitTarget); + + break; + } + break; + } + case TARGET_DUELVSPLAYER_COORDINATES: + { + if (Unit* currentTarget = m_targets.getUnitTarget()) + m_targets.setDestination(currentTarget->GetPositionX(), currentTarget->GetPositionY(), currentTarget->GetPositionZ()); + break; + } + case TARGET_ALL_PARTY_AROUND_CASTER: + { + if (m_caster->GetObjectGuid().IsPet()) + { + // only affect pet and owner + targetUnitMap.push_back(m_caster); + if (Unit* owner = m_caster->GetOwner()) + targetUnitMap.push_back(owner); + } + else + { + FillRaidOrPartyTargets(targetUnitMap, m_caster, m_caster, radius, false, true, true); + } + break; + } + case TARGET_ALL_PARTY_AROUND_CASTER_2: + case TARGET_ALL_PARTY: + { + FillRaidOrPartyTargets(targetUnitMap, m_caster, m_caster, radius, false, true, true); + break; + } + case TARGET_ALL_RAID_AROUND_CASTER: + { + if (m_spellInfo->Id == 57669) // Replenishment (special target selection) + { + // in arena, target should be only caster + if (m_caster->GetMap()->IsBattleArena()) + targetUnitMap.push_back(m_caster); + else + FillRaidOrPartyManaPriorityTargets(targetUnitMap, m_caster, m_caster, radius, 10, true, false, true); + } + else if (m_spellInfo->Id == 52759) // Ancestral Awakening (special target selection) + FillRaidOrPartyHealthPriorityTargets(targetUnitMap, m_caster, m_caster, radius, 1, true, false, true); + else + FillRaidOrPartyTargets(targetUnitMap, m_caster, m_caster, radius, true, true, IsPositiveSpell(m_spellInfo->Id)); + break; + } + case TARGET_SINGLE_FRIEND: + case TARGET_SINGLE_FRIEND_2: + if (m_targets.getUnitTarget()) + targetUnitMap.push_back(m_targets.getUnitTarget()); + break; + case TARGET_NONCOMBAT_PET: + if (Unit* target = m_targets.getUnitTarget()) + if (target->GetTypeId() == TYPEID_UNIT && ((Creature*)target)->IsPet() && ((Pet*)target)->getPetType() == MINI_PET) + targetUnitMap.push_back(target); + break; + case TARGET_SUMMONER: + { + WorldObject* caster = GetAffectiveCasterObject(); + if (!caster) + return; + + if (caster->GetTypeId() == TYPEID_UNIT && ((Creature*)caster)->IsTemporarySummon()) + targetUnitMap.push_back(((TemporarySummon*)(Creature*)caster)->GetSummoner()); + else if (caster->GetTypeId() == TYPEID_GAMEOBJECT && !((GameObject*)caster)->HasStaticDBSpawnData()) + targetUnitMap.push_back(((GameObject*)caster)->GetOwner()); + else + sLog.outError("SPELL: Spell ID %u with target ID %u was used by non temporary summon object %s.", m_spellInfo->Id, targetMode, caster->GetGuidStr().c_str()); + break; + } + case TARGET_CONTROLLED_VEHICLE: + if (m_caster->IsBoarded() && m_caster->GetTransportInfo()->IsOnVehicle()) + targetUnitMap.push_back((Unit*)m_caster->GetTransportInfo()->GetTransport()); + break; + case TARGET_VEHICLE_PASSENGER_0: + case TARGET_VEHICLE_PASSENGER_1: + case TARGET_VEHICLE_PASSENGER_2: + case TARGET_VEHICLE_PASSENGER_3: + case TARGET_VEHICLE_PASSENGER_4: + case TARGET_VEHICLE_PASSENGER_5: + case TARGET_VEHICLE_PASSENGER_6: + case TARGET_VEHICLE_PASSENGER_7: + if (m_caster->IsVehicle()) + if (Unit* passenger = m_caster->GetVehicleInfo()->GetPassenger(targetMode - TARGET_VEHICLE_PASSENGER_0)) + targetUnitMap.push_back(passenger); + break; + case TARGET_CASTER_COORDINATES: + { + // Check original caster is GO - set its coordinates as src cast + if (WorldObject* caster = GetCastingObject()) + m_targets.setSource(caster->GetPositionX(), caster->GetPositionY(), caster->GetPositionZ()); + break; + } + case TARGET_ALL_HOSTILE_UNITS_AROUND_CASTER: + FillAreaTargets(targetUnitMap, radius, PUSH_SELF_CENTER, SPELL_TARGETS_HOSTILE); + break; + case TARGET_ALL_FRIENDLY_UNITS_AROUND_CASTER: + switch (m_spellInfo->Id) + { + case 56153: // Guardian Aura - Ahn'Kahet + FillAreaTargets(targetUnitMap, radius, PUSH_SELF_CENTER, SPELL_TARGETS_FRIENDLY); + targetUnitMap.remove(m_caster); + break; + case 64844: // Divine Hymn + // target amount stored in parent spell dummy effect but hard to access + FillRaidOrPartyHealthPriorityTargets(targetUnitMap, m_caster, m_caster, radius, 3, true, false, true); + break; + case 64904: // Hymn of Hope + // target amount stored in parent spell dummy effect but hard to access + FillRaidOrPartyManaPriorityTargets(targetUnitMap, m_caster, m_caster, radius, 3, true, false, true); + break; + default: + // selected friendly units (for casting objects) around casting object + FillAreaTargets(targetUnitMap, radius, PUSH_SELF_CENTER, SPELL_TARGETS_FRIENDLY, GetCastingObject()); + break; + } + break; + case TARGET_ALL_FRIENDLY_UNITS_IN_AREA: + // Death Pact (in fact selection by player selection) + if (m_spellInfo->Id == 48743) + { + // checked in Spell::CheckCast + if (m_caster->GetTypeId() == TYPEID_PLAYER) + if (Unit* target = m_caster->GetMap()->GetPet(((Player*)m_caster)->GetSelectionGuid())) + targetUnitMap.push_back(target); + } + // Circle of Healing + else if (m_spellInfo->GetSpellFamilyName() == SPELLFAMILY_PRIEST && m_spellInfo->SpellVisual[0] == 8253) + { + Unit* target = m_targets.getUnitTarget(); + if (!target) + target = m_caster; + + uint32 count = 5; + // Glyph of Circle of Healing + if (Aura const* glyph = m_caster->GetDummyAura(55675)) + count += glyph->GetModifier()->m_amount; + + FillRaidOrPartyHealthPriorityTargets(targetUnitMap, m_caster, target, radius, count, true, false, true); + } + // Wild Growth + else if (m_spellInfo->GetSpellFamilyName() == SPELLFAMILY_DRUID && m_spellInfo->SpellIconID == 2864) + { + Unit* target = m_targets.getUnitTarget(); + if (!target) + target = m_caster; + uint32 count = CalculateDamage(EFFECT_INDEX_2, m_caster); // stored in dummy effect, affected by mods + + FillRaidOrPartyHealthPriorityTargets(targetUnitMap, m_caster, target, radius, count, true, false, true); + } + else + FillAreaTargets(targetUnitMap, radius, PUSH_DEST_CENTER, SPELL_TARGETS_FRIENDLY); + break; + // TARGET_SINGLE_PARTY means that the spells can only be casted on a party member and not on the caster (some seals, fire shield from imp, etc..) + case TARGET_SINGLE_PARTY: + { + Unit* target = m_targets.getUnitTarget(); + // Those spells apparently can't be casted on the caster. + if (target && target != m_caster) + { + // Can only be casted on group's members or its pets + Group* pGroup = NULL; + + Unit* owner = m_caster->GetCharmerOrOwner(); + Unit* targetOwner = target->GetCharmerOrOwner(); + if (owner) + { + if (owner->GetTypeId() == TYPEID_PLAYER) + { + if (target == owner) + { + targetUnitMap.push_back(target); + break; + } + pGroup = ((Player*)owner)->GetGroup(); + } + } + else if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + if (targetOwner == m_caster && target->GetTypeId() == TYPEID_UNIT && ((Creature*)target)->IsPet()) + { + targetUnitMap.push_back(target); + break; + } + pGroup = ((Player*)m_caster)->GetGroup(); + } + + if (pGroup) + { + // Our target can also be a player's pet who's grouped with us or our pet. But can't be controlled player + if (targetOwner) + { + if (targetOwner->GetTypeId() == TYPEID_PLAYER && + target->GetTypeId() == TYPEID_UNIT && (((Creature*)target)->IsPet()) && + target->GetOwnerGuid() == targetOwner->GetObjectGuid() && + pGroup->IsMember(((Player*)targetOwner)->GetObjectGuid())) + { + targetUnitMap.push_back(target); + } + } + // 1Our target can be a player who is on our group + else if (target->GetTypeId() == TYPEID_PLAYER && pGroup->IsMember(((Player*)target)->GetObjectGuid())) + { + targetUnitMap.push_back(target); + } + } + } + break; + } + case TARGET_GAMEOBJECT: + if (m_targets.getGOTarget()) + AddGOTarget(m_targets.getGOTarget(), effIndex); + break; + case TARGET_IN_FRONT_OF_CASTER: + { + SpellNotifyPushType pushType = PUSH_IN_FRONT; + switch (m_spellInfo->SpellVisual[0]) // Some spell require a different target fill + { + case 3879: pushType = PUSH_IN_BACK; break; + case 7441: pushType = PUSH_IN_FRONT_15; break; + case 8669: pushType = PUSH_IN_FRONT_15; break; + } + FillAreaTargets(targetUnitMap, radius, pushType, SPELL_TARGETS_AOE_DAMAGE); + break; + } + case TARGET_LARGE_FRONTAL_CONE: + FillAreaTargets(targetUnitMap, radius, PUSH_IN_FRONT_90, SPELL_TARGETS_AOE_DAMAGE); + break; + case TARGET_NARROW_FRONTAL_CONE: + FillAreaTargets(targetUnitMap, radius, PUSH_IN_FRONT_15, SPELL_TARGETS_AOE_DAMAGE); + break; + case TARGET_IN_FRONT_OF_CASTER_30: + FillAreaTargets(targetUnitMap, radius, PUSH_IN_FRONT_30, SPELL_TARGETS_AOE_DAMAGE); + break; + case TARGET_DUELVSPLAYER: + { + if (Unit* target = m_targets.getUnitTarget()) + { + if (m_caster->IsFriendlyTo(target)) + { + targetUnitMap.push_back(target); + } + else + { + if (Unit* pUnitTarget = m_caster->SelectMagnetTarget(target, this, effIndex)) + { + if (target != pUnitTarget) + { + m_targets.setUnitTarget(pUnitTarget); + m_spellFlags |= SPELL_FLAG_REDIRECTED; + } + targetUnitMap.push_back(pUnitTarget); + } + } + } + break; + } + case TARGET_GAMEOBJECT_ITEM: + if (m_targets.getGOTargetGuid()) + AddGOTarget(m_targets.getGOTarget(), effIndex); + else if (m_targets.getItemTarget()) + AddItemTarget(m_targets.getItemTarget(), effIndex); + break; + case TARGET_MASTER: + if (Unit* owner = m_caster->GetCharmerOrOwner()) + targetUnitMap.push_back(owner); + break; + case TARGET_ALL_ENEMY_IN_AREA_CHANNELED: + // targets the ground, not the units in the area + if (spellEffect && spellEffect->Effect!=SPELL_EFFECT_PERSISTENT_AREA_AURA) + FillAreaTargets(targetUnitMap, radius, PUSH_DEST_CENTER, SPELL_TARGETS_AOE_DAMAGE); + break; + case TARGET_MINION: + if(spellEffect && spellEffect->Effect != SPELL_EFFECT_DUEL) + targetUnitMap.push_back(m_caster); + break; + case TARGET_SINGLE_ENEMY: + { + if (Unit* pUnitTarget = m_caster->SelectMagnetTarget(m_targets.getUnitTarget(), this, effIndex)) + { + if (m_targets.getUnitTarget() && m_targets.getUnitTarget() != pUnitTarget) + m_spellFlags |= SPELL_FLAG_REDIRECTED; + + targetUnitMap.push_back(pUnitTarget); + } + break; + } + case TARGET_AREAEFFECT_PARTY: + { + Unit* owner = m_caster->GetCharmerOrOwner(); + Player* pTarget = NULL; + + if (owner) + { + targetUnitMap.push_back(m_caster); + if (owner->GetTypeId() == TYPEID_PLAYER) + pTarget = (Player*)owner; + } + else if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + if (Unit* target = m_targets.getUnitTarget()) + { + if (target->GetTypeId() != TYPEID_PLAYER) + { + if (((Creature*)target)->IsPet()) + { + Unit* targetOwner = target->GetOwner(); + if (targetOwner->GetTypeId() == TYPEID_PLAYER) + pTarget = (Player*)targetOwner; + } + } + else + pTarget = (Player*)target; + } + } + + Group* pGroup = pTarget ? pTarget->GetGroup() : NULL; + + if (pGroup) + { + uint8 subgroup = pTarget->GetSubGroup(); + + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* Target = itr->getSource(); + + // IsHostileTo check duel and controlled by enemy + if (Target && Target->GetSubGroup() == subgroup && !m_caster->IsHostileTo(Target)) + { + if (pTarget->IsWithinDistInMap(Target, radius)) + targetUnitMap.push_back(Target); + + if (Pet* pet = Target->GetPet()) + if (pTarget->IsWithinDistInMap(pet, radius)) + targetUnitMap.push_back(pet); + } + } + } + else if (owner) + { + if (m_caster->IsWithinDistInMap(owner, radius)) + targetUnitMap.push_back(owner); + } + else if (pTarget) + { + targetUnitMap.push_back(pTarget); + + if (Pet* pet = pTarget->GetPet()) + if (m_caster->IsWithinDistInMap(pet, radius)) + targetUnitMap.push_back(pet); + } + break; + } + case TARGET_SCRIPT: + { + if (m_targets.getUnitTarget()) + targetUnitMap.push_back(m_targets.getUnitTarget()); + if (m_targets.getItemTarget()) + AddItemTarget(m_targets.getItemTarget(), effIndex); + break; + } + case TARGET_SELF_FISHING: + targetUnitMap.push_back(m_caster); + break; + case TARGET_CHAIN_HEAL: + { + Unit* pUnitTarget = m_targets.getUnitTarget(); + if (!pUnitTarget) + break; + + if (EffectChainTarget <= 1) + targetUnitMap.push_back(pUnitTarget); + else + { + unMaxTargets = EffectChainTarget; + float max_range = radius + unMaxTargets * CHAIN_SPELL_JUMP_RADIUS; + + UnitList tempTargetUnitMap; + + FillAreaTargets(tempTargetUnitMap, max_range, PUSH_SELF_CENTER, SPELL_TARGETS_FRIENDLY); + + if (m_caster != pUnitTarget && std::find(tempTargetUnitMap.begin(), tempTargetUnitMap.end(), m_caster) == tempTargetUnitMap.end()) + tempTargetUnitMap.push_front(m_caster); + + tempTargetUnitMap.sort(TargetDistanceOrderNear(pUnitTarget)); + + if (tempTargetUnitMap.empty()) + break; + + if (*tempTargetUnitMap.begin() == pUnitTarget) + tempTargetUnitMap.erase(tempTargetUnitMap.begin()); + + targetUnitMap.push_back(pUnitTarget); + uint32 t = unMaxTargets - 1; + Unit* prev = pUnitTarget; + UnitList::iterator next = tempTargetUnitMap.begin(); + + while (t && next != tempTargetUnitMap.end()) + { + if (!prev->IsWithinDist(*next, CHAIN_SPELL_JUMP_RADIUS)) + break; + + if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX2_IGNORE_LOS) && !prev->IsWithinLOSInMap(*next)) + { + ++next; + continue; + } + + if ((*next)->GetHealth() == (*next)->GetMaxHealth()) + { + next = tempTargetUnitMap.erase(next); + continue; + } + + prev = *next; + targetUnitMap.push_back(prev); + tempTargetUnitMap.erase(next); + tempTargetUnitMap.sort(TargetDistanceOrderNear(prev)); + next = tempTargetUnitMap.begin(); + + --t; + } + } + break; + } + case TARGET_CURRENT_ENEMY_COORDINATES: + { + Unit* currentTarget = m_targets.getUnitTarget(); + if (currentTarget) + { + targetUnitMap.push_back(currentTarget); + m_targets.setDestination(currentTarget->GetPositionX(), currentTarget->GetPositionY(), currentTarget->GetPositionZ()); + } + break; + } + case TARGET_AREAEFFECT_PARTY_AND_CLASS: + { + Player* targetPlayer = m_targets.getUnitTarget() && m_targets.getUnitTarget()->GetTypeId() == TYPEID_PLAYER + ? (Player*)m_targets.getUnitTarget() : NULL; + + Group* pGroup = targetPlayer ? targetPlayer->GetGroup() : NULL; + if (pGroup) + { + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* Target = itr->getSource(); + + // IsHostileTo check duel and controlled by enemy + if (Target && targetPlayer->IsWithinDistInMap(Target, radius) && + targetPlayer->getClass() == Target->getClass() && + !m_caster->IsHostileTo(Target)) + { + targetUnitMap.push_back(Target); + } + } + } + else if (m_targets.getUnitTarget()) + targetUnitMap.push_back(m_targets.getUnitTarget()); + break; + } + case TARGET_TABLE_X_Y_Z_COORDINATES: + { + if (SpellTargetPosition const* st = sSpellMgr.GetSpellTargetPosition(m_spellInfo->Id)) + { + m_targets.setDestination(st->target_X, st->target_Y, st->target_Z); + // TODO - maybe use an (internal) value for the map for neat far teleport handling + + // far-teleport spells are handled in SpellEffect, elsewise report an error about an unexpected map (spells are always locally) + if (st->target_mapId != m_caster->GetMapId() && spellEffect && spellEffect->Effect != SPELL_EFFECT_TELEPORT_UNITS && spellEffect->Effect != SPELL_EFFECT_BIND) + sLog.outError("SPELL: wrong map (%u instead %u) target coordinates for spell ID %u", st->target_mapId, m_caster->GetMapId(), m_spellInfo->Id); + } + else + sLog.outError("SPELL: unknown target coordinates for spell ID %u", m_spellInfo->Id); + break; + } + case TARGET_INFRONT_OF_VICTIM: + case TARGET_BEHIND_VICTIM: + case TARGET_RIGHT_FROM_VICTIM: + case TARGET_LEFT_FROM_VICTIM: + { + Unit* pTarget = NULL; + + // explicit cast data from client or server-side cast + // some spell at client send caster + if (m_targets.getUnitTarget() && m_targets.getUnitTarget() != m_caster) + pTarget = m_targets.getUnitTarget(); + else if (m_caster->getVictim()) + pTarget = m_caster->getVictim(); + else if (m_caster->GetTypeId() == TYPEID_PLAYER) + pTarget = ObjectAccessor::GetUnit(*m_caster, ((Player*)m_caster)->GetSelectionGuid()); + else if (m_targets.getUnitTarget()) + pTarget = m_caster; + + if (pTarget) + { + float angle = 0.0f; + + switch (targetMode) + { + case TARGET_INFRONT_OF_VICTIM: break; + case TARGET_BEHIND_VICTIM: angle = M_PI_F; break; + case TARGET_RIGHT_FROM_VICTIM: angle = -M_PI_F / 2; break; + case TARGET_LEFT_FROM_VICTIM: angle = M_PI_F / 2; break; + } + + float _target_x, _target_y, _target_z; + pTarget->GetClosePoint(_target_x, _target_y, _target_z, pTarget->GetObjectBoundingRadius(), radius, angle); + if (pTarget->IsWithinLOS(_target_x, _target_y, _target_z)) + { + targetUnitMap.push_back(m_caster); + m_targets.setDestination(_target_x, _target_y, _target_z); + } + } + break; + } + case TARGET_DYNAMIC_OBJECT_COORDINATES: + // if parent spell create dynamic object extract area from it + if (DynamicObject* dynObj = m_caster->GetDynObject(m_triggeredByAuraSpell ? m_triggeredByAuraSpell->Id : m_spellInfo->Id)) + m_targets.setDestination(dynObj->GetPositionX(), dynObj->GetPositionY(), dynObj->GetPositionZ()); + // else use destination of target if no destination set (ie for Mind Sear - 53022) + else if (!(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) && m_targets.m_targetMask & TARGET_FLAG_UNIT) + m_targets.setDestination(m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ); + break; + case TARGET_DYNAMIC_OBJECT_FRONT: + case TARGET_DYNAMIC_OBJECT_BEHIND: + case TARGET_DYNAMIC_OBJECT_LEFT_SIDE: + case TARGET_DYNAMIC_OBJECT_RIGHT_SIDE: + { + if (!(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION)) + { + // General override, we don't want to use max spell range here. + // Note: 0.0 radius is also for index 36. It is possible that 36 must be defined as + // "at the base of", in difference to 0 which appear to be "directly in front of". + // TODO: some summoned will make caster be half inside summoned object. Need to fix + // that in the below code (nearpoint vs closepoint, etc). + if (!spellEffect || spellEffect->GetRadiusIndex() == 0) + radius = 0.0f; + + if (m_spellInfo->Id == 50019) // Hawk Hunting, problematic 50K radius + radius = 10.0f; + + float angle = m_caster->GetOrientation(); + switch (targetMode) + { + case TARGET_DYNAMIC_OBJECT_FRONT: break; + case TARGET_DYNAMIC_OBJECT_BEHIND: angle += M_PI_F; break; + case TARGET_DYNAMIC_OBJECT_LEFT_SIDE: angle += M_PI_F / 2; break; + case TARGET_DYNAMIC_OBJECT_RIGHT_SIDE: angle -= M_PI_F / 2; break; + } + + float x, y; + m_caster->GetNearPoint2D(x, y, radius + m_caster->GetObjectBoundingRadius(), angle); + m_targets.setDestination(x, y, m_caster->GetPositionZ()); + } + + targetUnitMap.push_back(m_caster); + break; + } + case TARGET_POINT_AT_NORTH: + case TARGET_POINT_AT_SOUTH: + case TARGET_POINT_AT_EAST: + case TARGET_POINT_AT_WEST: + case TARGET_POINT_AT_NE: + case TARGET_POINT_AT_NW: + case TARGET_POINT_AT_SE: + case TARGET_POINT_AT_SW: + { + + if (!(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION)) + { + Unit* currentTarget = m_targets.getUnitTarget() ? m_targets.getUnitTarget() : m_caster; + float angle = currentTarget != m_caster ? currentTarget->GetAngle(m_caster) : m_caster->GetOrientation(); + + switch (targetMode) + { + case TARGET_POINT_AT_NORTH: break; + case TARGET_POINT_AT_SOUTH: angle += M_PI_F; break; + case TARGET_POINT_AT_EAST: angle -= M_PI_F / 2; break; + case TARGET_POINT_AT_WEST: angle += M_PI_F / 2; break; + case TARGET_POINT_AT_NE: angle -= M_PI_F / 4; break; + case TARGET_POINT_AT_NW: angle += M_PI_F / 4; break; + case TARGET_POINT_AT_SE: angle -= 3 * M_PI_F / 4; break; + case TARGET_POINT_AT_SW: angle += 3 * M_PI_F / 4; break; + } + + float x, y; + currentTarget->GetNearPoint2D(x, y, radius + currentTarget->GetObjectBoundingRadius(), angle); + m_targets.setDestination(x, y, currentTarget->GetPositionZ()); + } + break; + } + case TARGET_DIRECTLY_FORWARD: + { + if (!(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION)) + { + SpellRangeEntry const* rEntry = sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex); + float minRange = GetSpellMinRange(rEntry); + float maxRange = GetSpellMaxRange(rEntry); + float dist = minRange + rand_norm_f() * (maxRange - minRange); + + float _target_x, _target_y, _target_z; + m_caster->GetClosePoint(_target_x, _target_y, _target_z, m_caster->GetObjectBoundingRadius(), dist); + m_targets.setDestination(_target_x, _target_y, _target_z); + } + + targetUnitMap.push_back(m_caster); + break; + } + case TARGET_EFFECT_SELECT: + { + // add here custom effects that need default target. + // FOR EVERY TARGET TYPE THERE IS A DIFFERENT FILL!! + if(!spellEffect) + break; + + switch(spellEffect->Effect) + { + case SPELL_EFFECT_DUMMY: + { + switch (m_spellInfo->Id) + { + case 20577: // Cannibalize + { + WorldObject* result = FindCorpseUsing (); + + if (result) + { + switch (result->GetTypeId()) + { + case TYPEID_UNIT: + case TYPEID_PLAYER: + targetUnitMap.push_back((Unit*)result); + break; + case TYPEID_CORPSE: + m_targets.setCorpseTarget((Corpse*)result); + if (Player* owner = ObjectAccessor::FindPlayer(((Corpse*)result)->GetOwnerGuid())) + targetUnitMap.push_back(owner); + break; + } + } + else + { + // clear cooldown at fail + if (m_caster->GetTypeId() == TYPEID_PLAYER) + ((Player*)m_caster)->RemoveSpellCooldown(m_spellInfo->Id, true); + SendCastResult(SPELL_FAILED_NO_EDIBLE_CORPSES); + finish(false); + } + break; + } + default: + if (m_targets.getUnitTarget()) + targetUnitMap.push_back(m_targets.getUnitTarget()); + break; + } + // Add AoE target-mask to self, if no target-dest provided already + if ((m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) == 0) + m_targets.setDestination(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ()); + break; + } + case SPELL_EFFECT_BIND: + case SPELL_EFFECT_RESURRECT: + case SPELL_EFFECT_PARRY: + case SPELL_EFFECT_BLOCK: + case SPELL_EFFECT_CREATE_ITEM: + case SPELL_EFFECT_WEAPON: + case SPELL_EFFECT_TRIGGER_SPELL: + case SPELL_EFFECT_TRIGGER_MISSILE: + case SPELL_EFFECT_LEARN_SPELL: + case SPELL_EFFECT_SKILL_STEP: + case SPELL_EFFECT_PROFICIENCY: + case SPELL_EFFECT_SUMMON_OBJECT_WILD: + case SPELL_EFFECT_SELF_RESURRECT: + case SPELL_EFFECT_REPUTATION: + case SPELL_EFFECT_SEND_TAXI: + if (m_targets.getUnitTarget()) + targetUnitMap.push_back(m_targets.getUnitTarget()); + // Triggered spells have additional spell targets - cast them even if no explicit unit target is given (required for spell 50516 for example) + else if (spellEffect->Effect == SPELL_EFFECT_TRIGGER_SPELL) + targetUnitMap.push_back(m_caster); + break; + case SPELL_EFFECT_SUMMON_PLAYER: + if (m_caster->GetTypeId() == TYPEID_PLAYER && ((Player*)m_caster)->GetSelectionGuid()) + if (Player* target = sObjectMgr.GetPlayer(((Player*)m_caster)->GetSelectionGuid())) + targetUnitMap.push_back(target); + break; + case SPELL_EFFECT_RESURRECT_NEW: + if (m_targets.getUnitTarget()) + targetUnitMap.push_back(m_targets.getUnitTarget()); + if (m_targets.getCorpseTargetGuid()) + { + if (Corpse* corpse = m_caster->GetMap()->GetCorpse(m_targets.getCorpseTargetGuid())) + if (Player* owner = ObjectAccessor::FindPlayer(corpse->GetOwnerGuid())) + targetUnitMap.push_back(owner); + } + break; + case SPELL_EFFECT_TELEPORT_UNITS: + case SPELL_EFFECT_SUMMON: + case SPELL_EFFECT_SUMMON_CHANGE_ITEM: + case SPELL_EFFECT_TRANS_DOOR: + case SPELL_EFFECT_ADD_FARSIGHT: + case SPELL_EFFECT_APPLY_GLYPH: + case SPELL_EFFECT_STUCK: + case SPELL_EFFECT_BREAK_PLAYER_TARGETING: + case SPELL_EFFECT_SUMMON_ALL_TOTEMS: + case SPELL_EFFECT_FEED_PET: + case SPELL_EFFECT_DESTROY_ALL_TOTEMS: + case SPELL_EFFECT_SKILL: + targetUnitMap.push_back(m_caster); + break; + case SPELL_EFFECT_PERSISTENT_AREA_AURA: + if (Unit* currentTarget = m_targets.getUnitTarget()) + m_targets.setDestination(currentTarget->GetPositionX(), currentTarget->GetPositionY(), currentTarget->GetPositionZ()); + break; + case SPELL_EFFECT_LEARN_PET_SPELL: + if (Pet* pet = m_caster->GetPet()) + targetUnitMap.push_back(pet); + break; + case SPELL_EFFECT_ENCHANT_ITEM: + case SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY: + case SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC: + case SPELL_EFFECT_DISENCHANT: + case SPELL_EFFECT_PROSPECTING: + case SPELL_EFFECT_MILLING: + if (m_targets.getItemTarget()) + AddItemTarget(m_targets.getItemTarget(), effIndex); + break; + case SPELL_EFFECT_APPLY_AURA: + switch(spellEffect->EffectApplyAuraName) + { + case SPELL_AURA_ADD_FLAT_MODIFIER: // some spell mods auras have 0 target modes instead expected TARGET_SELF(1) (and present for other ranks for same spell for example) + case SPELL_AURA_ADD_PCT_MODIFIER: + targetUnitMap.push_back(m_caster); + break; + default: // apply to target in other case + if (m_targets.getUnitTarget()) + targetUnitMap.push_back(m_targets.getUnitTarget()); + break; + } + break; + case SPELL_EFFECT_APPLY_AREA_AURA_PARTY: + // AreaAura + if ((m_spellInfo->Attributes == (SPELL_ATTR_NOT_SHAPESHIFT | SPELL_ATTR_UNK18 | SPELL_ATTR_CASTABLE_WHILE_MOUNTED | SPELL_ATTR_CASTABLE_WHILE_SITTING)) || (m_spellInfo->Attributes == SPELL_ATTR_NOT_SHAPESHIFT)) + SetTargetMap(effIndex, TARGET_AREAEFFECT_PARTY, targetUnitMap); + break; + case SPELL_EFFECT_SKIN_PLAYER_CORPSE: + if (m_targets.getUnitTarget()) + targetUnitMap.push_back(m_targets.getUnitTarget()); + else if (m_targets.getCorpseTargetGuid()) + { + if (Corpse* corpse = m_caster->GetMap()->GetCorpse(m_targets.getCorpseTargetGuid())) + if (Player* owner = ObjectAccessor::FindPlayer(corpse->GetOwnerGuid())) + targetUnitMap.push_back(owner); + } + break; + default: + break; + } + break; + } + default: + // sLog.outError( "SPELL: Unknown implicit target (%u) for spell ID %u", targetMode, m_spellInfo->Id ); + break; + } + + if (unMaxTargets && targetUnitMap.size() > unMaxTargets) + { + // make sure one unit is always removed per iteration + uint32 removed_utarget = 0; + for (UnitList::iterator itr = targetUnitMap.begin(), next; itr != targetUnitMap.end(); itr = next) + { + next = itr; + ++next; + if (!*itr) continue; + if ((*itr) == m_targets.getUnitTarget()) + { + targetUnitMap.erase(itr); + removed_utarget = 1; + // break; + } + } + // remove random units from the map + while (targetUnitMap.size() > unMaxTargets - removed_utarget) + { + uint32 poz = urand(0, targetUnitMap.size() - 1); + for (UnitList::iterator itr = targetUnitMap.begin(); itr != targetUnitMap.end(); ++itr, --poz) + { + if (!*itr) continue; + + if (!poz) + { + targetUnitMap.erase(itr); + break; + } + } + } + // the player's target will always be added to the map + if (removed_utarget && m_targets.getUnitTarget()) + targetUnitMap.push_back(m_targets.getUnitTarget()); + } + if (!tempTargetGOList.empty()) // GO CASE + { + if (unMaxTargets && tempTargetGOList.size() > unMaxTargets) + { + // make sure one go is always removed per iteration + uint32 removed_utarget = 0; + for (std::list::iterator itr = tempTargetGOList.begin(), next; itr != tempTargetGOList.end(); itr = next) + { + next = itr; + ++next; + if (!*itr) continue; + if ((*itr) == m_targets.getGOTarget()) + { + tempTargetGOList.erase(itr); + removed_utarget = 1; + // break; + } + } + // remove random units from the map + while (tempTargetGOList.size() > unMaxTargets - removed_utarget) + { + uint32 poz = urand(0, tempTargetGOList.size() - 1); + for (std::list::iterator itr = tempTargetGOList.begin(); itr != tempTargetGOList.end(); ++itr, --poz) + { + if (!*itr) continue; + + if (!poz) + { + tempTargetGOList.erase(itr); + break; + } + } + } + } + // Add resulting GOs as GOTargets + for (std::list::iterator iter = tempTargetGOList.begin(); iter != tempTargetGOList.end(); ++iter) + AddGOTarget(*iter, effIndex); + } +} + +void Spell::prepare(SpellCastTargets const* targets, Aura* triggeredByAura) +{ + m_targets = *targets; + + m_spellState = SPELL_STATE_PREPARING; + + m_castPositionX = m_caster->GetPositionX(); + m_castPositionY = m_caster->GetPositionY(); + m_castPositionZ = m_caster->GetPositionZ(); + m_castOrientation = m_caster->GetOrientation(); + + if (triggeredByAura) + m_triggeredByAuraSpell = triggeredByAura->GetSpellProto(); + + // create and add update event for this spell + SpellEvent* Event = new SpellEvent(this); + m_caster->m_Events.AddEvent(Event, m_caster->m_Events.CalculateTime(1)); + + // Prevent casting at cast another spell (ServerSide check) + if (m_caster->IsNonMeleeSpellCasted(false, true, true) && m_cast_count) + { + SendCastResult(SPELL_FAILED_SPELL_IN_PROGRESS); + finish(false); + return; + } + + // Fill cost data + m_powerCost = CalculatePowerCost(m_spellInfo, m_caster, this, m_CastItem); + + SpellCastResult result = CheckCast(true); + if (result != SPELL_CAST_OK && !IsAutoRepeat()) // always cast autorepeat dummy for triggering + { + if (triggeredByAura) + { + SendChannelUpdate(0); + triggeredByAura->GetHolder()->SetAuraDuration(0); + } + SendCastResult(result); + finish(false); + return; + } + + // Prepare data for triggers + prepareDataForTriggerSystem(); + + // calculate cast time (calculated after first CheckCast check to prevent charge counting for first CheckCast fail) + m_casttime = GetSpellCastTime(m_spellInfo, this); + m_duration = CalculateSpellDuration(m_spellInfo, m_caster); + + // set timer base at cast time + ReSetTimer(); + + // stealth must be removed at cast starting (at show channel bar) + // skip triggered spell (item equip spell casting and other not explicit character casts/item uses) + if (!m_IsTriggeredSpell && isSpellBreakStealth(m_spellInfo)) + { + m_caster->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); + m_caster->RemoveSpellsCausingAura(SPELL_AURA_FEIGN_DEATH); + } + + // add non-triggered (with cast time and without) + if (!m_IsTriggeredSpell) + { + // add to cast type slot + m_caster->SetCurrentCastedSpell(this); + + // will show cast bar + SendSpellStart(); + + TriggerGlobalCooldown(); + } + // execute triggered without cast time explicitly in call point + else if (m_timer == 0) + cast(true); + // else triggered with cast time will execute execute at next tick or later + // without adding to cast type slot + // will not show cast bar but will show effects at casting time etc +} + +void Spell::cancel() +{ + if (m_spellState == SPELL_STATE_FINISHED) + return; + + // channeled spells don't display interrupted message even if they are interrupted, possible other cases with no "Interrupted" message + bool sendInterrupt = IsChanneledSpell(m_spellInfo) ? false : true; + + m_autoRepeat = false; + switch (m_spellState) + { + case SPELL_STATE_PREPARING: + CancelGlobalCooldown(); + + //(no break) + case SPELL_STATE_DELAYED: + { + SendInterrupted(0); + + if (sendInterrupt) + SendCastResult(SPELL_FAILED_INTERRUPTED); + } break; + + case SPELL_STATE_CASTING: + { + for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if (ihit->missCondition == SPELL_MISS_NONE) + { + Unit* unit = m_caster->GetObjectGuid() == (*ihit).targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID); + if (unit && unit->isAlive()) + unit->RemoveAurasByCasterSpell(m_spellInfo->Id, m_caster->GetObjectGuid()); + + // prevent other effects applying if spell is already interrupted + // i.e. if effects have different targets and it was interrupted on one of them when + // haven't yet applied to another + ihit->processed = true; + } + } + + SendChannelUpdate(0); + SendInterrupted(0); + + if (sendInterrupt) + SendCastResult(SPELL_FAILED_INTERRUPTED); + } break; + + default: + { + } break; + } + + finish(false); + m_caster->RemoveDynObject(m_spellInfo->Id); + m_caster->RemoveGameObject(m_spellInfo->Id, true); +} + +void Spell::cast(bool skipCheck) +{ + SetExecutedCurrently(true); + + if (!m_caster->CheckAndIncreaseCastCounter()) + { + if (m_triggeredByAuraSpell) + sLog.outError("Spell %u triggered by aura spell %u too deep in cast chain for cast. Cast not allowed for prevent overflow stack crash.", m_spellInfo->Id, m_triggeredByAuraSpell->Id); + else + sLog.outError("Spell %u too deep in cast chain for cast. Cast not allowed for prevent overflow stack crash.", m_spellInfo->Id); + + SendCastResult(SPELL_FAILED_ERROR); + finish(false); + SetExecutedCurrently(false); + return; + } + + // update pointers base at GUIDs to prevent access to already nonexistent object + UpdatePointers(); + + // cancel at lost main target unit + if (!m_targets.getUnitTarget() && m_targets.getUnitTargetGuid() && m_targets.getUnitTargetGuid() != m_caster->GetObjectGuid()) + { + cancel(); + m_caster->DecreaseCastCounter(); + SetExecutedCurrently(false); + return; + } + + if (m_caster->GetTypeId() != TYPEID_PLAYER && m_targets.getUnitTarget() && m_targets.getUnitTarget() != m_caster) + m_caster->SetInFront(m_targets.getUnitTarget()); + + SpellCastResult castResult = CheckPower(); + if (castResult != SPELL_CAST_OK) + { + SendCastResult(castResult); + finish(false); + m_caster->DecreaseCastCounter(); + SetExecutedCurrently(false); + return; + } + + // triggered cast called from Spell::prepare where it was already checked + if (!skipCheck) + { + castResult = CheckCast(false); + if (castResult != SPELL_CAST_OK) + { + SendCastResult(castResult); + finish(false); + m_caster->DecreaseCastCounter(); + SetExecutedCurrently(false); + return; + } + } + + SpellClassOptionsEntry const* classOpt = m_spellInfo->GetSpellClassOptions(); + + // different triggered (for caster) and precast (casted before apply effect to target) cases + switch(m_spellInfo->GetSpellFamilyName()) + { + case SPELLFAMILY_GENERIC: + { + // Bandages + if (m_spellInfo->GetMechanic() == MECHANIC_BANDAGE) + AddPrecastSpell(11196); // Recently Bandaged + // Stoneskin + else if (m_spellInfo->Id == 20594) + AddTriggeredSpell(65116); // Stoneskin - armor 10% for 8 sec + else if (m_spellInfo->Id == 68992) // Darkflight + { + AddPrecastSpell(96223); // Run Speed Marker + if (m_caster->HasWorgenForm()) + AddPrecastSpell(97709); // Altered Form + } + else if (m_spellInfo->Id == 68996) // Two Forms + { + if (m_caster->IsInWorgenForm()) + m_caster->RemoveSpellsCausingAura(SPELL_AURA_WORGEN_TRANSFORM); + else if (m_caster->HasWorgenForm()) + AddPrecastSpell(97709); // Altered Form + } + // Chaos Bane strength buff + else if (m_spellInfo->Id == 71904) + AddTriggeredSpell(73422); + break; + } + case SPELLFAMILY_MAGE: + { + // Ice Block + if (classOpt && classOpt->SpellFamilyFlags & UI64LIT(0x0000008000000000)) + AddPrecastSpell(41425); // Hypothermia + // Icy Veins + else if (m_spellInfo->Id == 12472) + { + if (m_caster->HasAura(56374)) // Glyph of Icy Veins + { + // not exist spell do it so apply directly + m_caster->RemoveSpellsCausingAura(SPELL_AURA_MOD_DECREASE_SPEED); + m_caster->RemoveSpellsCausingAura(SPELL_AURA_HASTE_SPELLS); + } + } + // Fingers of Frost + else if (m_spellInfo->Id == 44544) + AddPrecastSpell(74396); // Fingers of Frost + break; + } + case SPELLFAMILY_WARRIOR: + { + // Shield Slam + if (classOpt && (classOpt->SpellFamilyFlags & UI64LIT(0x0000020000000000)) && m_spellInfo->GetCategory()==1209) + { + if (m_caster->HasAura(58375)) // Glyph of Blocking + AddTriggeredSpell(58374); // Glyph of Blocking + } + // Bloodrage + if (classOpt && (classOpt->SpellFamilyFlags & UI64LIT(0x0000000000000100))) + { + if (m_caster->HasAura(70844)) // Item - Warrior T10 Protection 4P Bonus + AddTriggeredSpell(70845); // Stoicism + } + // Bloodsurge (triggered), Sudden Death (triggered) + else if (m_spellInfo->Id == 46916 || m_spellInfo->Id == 52437) + // Item - Warrior T10 Melee 4P Bonus + if (Aura* aur = m_caster->GetAura(70847, EFFECT_INDEX_0)) + if (roll_chance_i(aur->GetModifier()->m_amount)) + AddTriggeredSpell(70849); // Extra Charge! + break; + } + case SPELLFAMILY_PRIEST: + { + // Power Word: Shield + if (m_spellInfo->GetMechanic() == MECHANIC_SHIELD && + (classOpt && classOpt->SpellFamilyFlags & UI64LIT(0x0000000000000001))) + AddPrecastSpell(6788); // Weakened Soul + // Prayer of Mending (jump animation), we need formal caster instead original for correct animation + else if (classOpt && classOpt->SpellFamilyFlags & UI64LIT(0x0000002000000000)) + AddTriggeredSpell(41637); + + switch (m_spellInfo->Id) + { + case 15237: AddTriggeredSpell(23455); break;// Holy Nova, rank 1 + case 15430: AddTriggeredSpell(23458); break;// Holy Nova, rank 2 + case 15431: AddTriggeredSpell(23459); break;// Holy Nova, rank 3 + case 27799: AddTriggeredSpell(27803); break;// Holy Nova, rank 4 + case 27800: AddTriggeredSpell(27804); break;// Holy Nova, rank 5 + case 27801: AddTriggeredSpell(27805); break;// Holy Nova, rank 6 + case 25331: AddTriggeredSpell(25329); break;// Holy Nova, rank 7 + case 48077: AddTriggeredSpell(48075); break;// Holy Nova, rank 8 + case 48078: AddTriggeredSpell(48076); break;// Holy Nova, rank 9 + default: break; + } + break; + } + case SPELLFAMILY_DRUID: + { + // Faerie Fire (Feral) + if (m_spellInfo->Id == 16857 && m_caster->GetShapeshiftForm() != FORM_CAT) + AddTriggeredSpell(60089); + // Clearcasting + else if (m_spellInfo->Id == 16870) + { + if (m_caster->HasAura(70718)) // Item - Druid T10 Balance 2P Bonus + AddPrecastSpell(70721); // Omen of Doom + } + // Berserk (Bear Mangle part) + else if (m_spellInfo->Id == 50334) + AddTriggeredSpell(58923); + break; + } + case SPELLFAMILY_ROGUE: + // Fan of Knives (main hand) + if (m_spellInfo->Id == 51723 && m_caster->GetTypeId() == TYPEID_PLAYER && + ((Player*)m_caster)->haveOffhandWeapon()) + { + AddTriggeredSpell(52874); // Fan of Knives (offhand) + } + break; + case SPELLFAMILY_HUNTER: + { + // Deterrence + if (m_spellInfo->Id == 19263) + AddPrecastSpell(67801); + // Kill Command + else if (m_spellInfo->Id == 34026) + { + if (m_caster->HasAura(37483)) // Improved Kill Command - Item set bonus + m_caster->CastSpell(m_caster, 37482, true);// Exploited Weakness + } + // Lock and Load + else if (m_spellInfo->Id == 56453) + AddPrecastSpell(67544); // Lock and Load Marker + break; + } + case SPELLFAMILY_PALADIN: + { + // Divine Illumination + if (m_spellInfo->Id == 31842) + { + if (m_caster->HasAura(70755)) // Item - Paladin T10 Holy 2P Bonus + AddPrecastSpell(71166); // Divine Illumination + } + // Hand of Reckoning + else if (m_spellInfo->Id == 62124) + { + if (m_targets.getUnitTarget() && m_targets.getUnitTarget()->getVictim() != m_caster) + AddPrecastSpell(67485); // Hand of Rekoning (no typos in name ;) ) + } + // Divine Shield, Divine Protection or Hand of Protection + else if (classOpt && classOpt->SpellFamilyFlags & UI64LIT(0x0000000000400080)) + { + AddPrecastSpell(25771); // Forbearance + AddPrecastSpell(61987); // Avenging Wrath Marker + } + // Lay on Hands + else if (classOpt && classOpt->SpellFamilyFlags & UI64LIT(0x0000000000008000)) + { + // only for self cast + if (m_caster == m_targets.getUnitTarget()) + AddPrecastSpell(25771); // Forbearance + } + // Avenging Wrath + else if (classOpt && classOpt->SpellFamilyFlags & UI64LIT(0x0000200000000000)) + AddPrecastSpell(61987); // Avenging Wrath Marker + break; + } + case SPELLFAMILY_SHAMAN: + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(EFFECT_INDEX_0); + // Bloodlust + if (m_spellInfo->Id == 2825) + AddPrecastSpell(57724); // Sated + // Heroism + else if (m_spellInfo->Id == 32182) + AddPrecastSpell(57723); // Exhaustion + // Spirit Walk + else if (m_spellInfo->Id == 58875) + AddPrecastSpell(58876); + // Totem of Wrath + else if (spellEffect && spellEffect->Effect==SPELL_EFFECT_APPLY_AREA_AURA_RAID && classOpt && classOpt->SpellFamilyFlags & UI64LIT(0x0000000004000000)) + // only for main totem spell cast + AddTriggeredSpell(30708); // Totem of Wrath + break; + } + case SPELLFAMILY_DEATHKNIGHT: + { + // Chains of Ice + if (m_spellInfo->Id == 45524) + AddTriggeredSpell(55095); // Frost Fever + break; + } + default: + break; + } + + // traded items have trade slot instead of guid in m_itemTargetGUID + // set to real guid to be sent later to the client + m_targets.updateTradeSlotItem(); + + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + if (!m_IsTriggeredSpell && m_CastItem) + ((Player*)m_caster)->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM, m_CastItem->GetEntry()); + + ((Player*)m_caster)->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL, m_spellInfo->Id); + } + + FillTargetMap(); + + if (m_spellState == SPELL_STATE_FINISHED) // stop cast if spell marked as finish somewhere in FillTargetMap + { + m_caster->DecreaseCastCounter(); + SetExecutedCurrently(false); + return; + } + + // CAST SPELL + SendSpellCooldown(); + + TakePower(); + TakeReagents(); // we must remove reagents before HandleEffects to allow place crafted item in same slot + TakeAmmo(); + + SendCastResult(castResult); + SendSpellGo(); // we must send smsg_spell_go packet before m_castItem delete in TakeCastItem()... + + InitializeDamageMultipliers(); + + Unit* procTarget = m_targets.getUnitTarget(); + if (!procTarget) + procTarget = m_caster; + + // Okay, everything is prepared. Now we need to distinguish between immediate and evented delayed spells + float speed = m_spellInfo->speed == 0.0f && m_triggeredBySpellInfo ? m_triggeredBySpellInfo->speed : m_spellInfo->speed; + if (speed > 0.0f) + { + + // Remove used for cast item if need (it can be already NULL after TakeReagents call + // in case delayed spell remove item at cast delay start + TakeCastItem(); + + // fill initial spell damage from caster for delayed casted spells + for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + HandleDelayedSpellLaunch(&(*ihit)); + + // Okay, maps created, now prepare flags + m_immediateHandled = false; + m_spellState = SPELL_STATE_DELAYED; + SetDelayStart(0); + + // on spell cast end proc, + // critical hit related part is currently done on hit so proc there, + // 0 damage since any damage based procs should be on hit + // 0 victim proc since there is no victim proc dependent on successfull cast for caster + m_caster->ProcDamageAndSpell(procTarget, m_procAttacker, 0, PROC_EX_CAST_END, 0, m_attackType, m_spellInfo); + } + else + { + m_caster->ProcDamageAndSpell(procTarget, m_procAttacker, 0, PROC_EX_CAST_END, 0, m_attackType, m_spellInfo); + + // Immediate spell, no big deal + handle_immediate(); + } + + m_caster->DecreaseCastCounter(); + SetExecutedCurrently(false); +} + +void Spell::TakeAmmo() +{ + // take ammo + if (m_attackType == RANGED_ATTACK && m_caster->GetTypeId() == TYPEID_PLAYER) + { + Item* pItem = ((Player*)m_caster)->GetWeaponForAttack(RANGED_ATTACK, true, false); + + // wands don't have ammo + if (!pItem || pItem->GetProto()->SubClass == ITEM_SUBCLASS_WEAPON_WAND) + return; + + if (pItem->GetProto()->InventoryType == INVTYPE_THROWN) + { + if (pItem->GetMaxStackCount() == 1) + { + // decrease durability for non-stackable throw weapon + ((Player*)m_caster)->DurabilityPointLossForEquipSlot(EQUIPMENT_SLOT_RANGED); + } + else + { + // decrease items amount for stackable throw weapon + uint32 count = 1; + ((Player*)m_caster)->DestroyItemCount(pItem, count, true); + } + } + } +} + +void Spell::handle_immediate() +{ + // process immediate effects (items, ground, etc.) also initialize some variables + _handle_immediate_phase(); + + // start channeling if applicable (after _handle_immediate_phase for get persistent effect dynamic object for channel target + if (IsChanneledSpell(m_spellInfo) && m_duration) + { + m_spellState = SPELL_STATE_CASTING; + SendChannelStart(m_duration); + } + + for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + DoAllEffectOnTarget(&(*ihit)); + + for (GOTargetList::iterator ihit = m_UniqueGOTargetInfo.begin(); ihit != m_UniqueGOTargetInfo.end(); ++ihit) + DoAllEffectOnTarget(&(*ihit)); + + // spell is finished, perform some last features of the spell here + _handle_finish_phase(); + + // Remove used for cast item if need (it can be already NULL after TakeReagents call + TakeCastItem(); + + if (m_spellState != SPELL_STATE_CASTING) + finish(true); // successfully finish spell cast (not last in case autorepeat or channel spell) +} + +uint64 Spell::handle_delayed(uint64 t_offset) +{ + uint64 next_time = 0; + + if (!m_immediateHandled) + { + _handle_immediate_phase(); + m_immediateHandled = true; + } + + // now recheck units targeting correctness (need before any effects apply to prevent adding immunity at first effect not allow apply second spell effect and similar cases) + for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if (!ihit->processed) + { + if (ihit->timeDelay <= t_offset) + DoAllEffectOnTarget(&(*ihit)); + else if (next_time == 0 || ihit->timeDelay < next_time) + next_time = ihit->timeDelay; + } + } + + // now recheck gameobject targeting correctness + for (GOTargetList::iterator ighit = m_UniqueGOTargetInfo.begin(); ighit != m_UniqueGOTargetInfo.end(); ++ighit) + { + if (!ighit->processed) + { + if (ighit->timeDelay <= t_offset) + DoAllEffectOnTarget(&(*ighit)); + else if (next_time == 0 || ighit->timeDelay < next_time) + next_time = ighit->timeDelay; + } + } + // All targets passed - need finish phase + if (next_time == 0) + { + // spell is finished, perform some last features of the spell here + _handle_finish_phase(); + + finish(true); // successfully finish spell cast + + // return zero, spell is finished now + return 0; + } + else + { + // spell is unfinished, return next execution time + return next_time; + } +} + +void Spell::_handle_immediate_phase() +{ + // handle some immediate features of the spell here + HandleThreatSpells(); + + m_needSpellLog = IsNeedSendToClient(); + for (int j = 0; j < MAX_EFFECT_INDEX; ++j) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(j)); + if(!spellEffect || spellEffect->Effect == 0) + continue; + + // apply Send Event effect to ground in case empty target lists + if( spellEffect->Effect == SPELL_EFFECT_SEND_EVENT && !HaveTargetsForEffect(SpellEffectIndex(j)) ) + { + HandleEffects(NULL, NULL, NULL, SpellEffectIndex(j)); + continue; + } + + // Don't do spell log, if is school damage spell + if(spellEffect->Effect == SPELL_EFFECT_SCHOOL_DAMAGE || spellEffect->Effect == 0) + m_needSpellLog = false; + } + + // initialize Diminishing Returns Data + m_diminishLevel = DIMINISHING_LEVEL_1; + m_diminishGroup = DIMINISHING_NONE; + + // process items + for (ItemTargetList::iterator ihit = m_UniqueItemInfo.begin(); ihit != m_UniqueItemInfo.end(); ++ihit) + DoAllEffectOnTarget(&(*ihit)); + + // process ground + for (int j = 0; j < MAX_EFFECT_INDEX; ++j) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(j)); + if(!spellEffect) + continue; + // persistent area auras target only the ground + if(spellEffect->Effect == SPELL_EFFECT_PERSISTENT_AREA_AURA) + HandleEffects(NULL, NULL, NULL, SpellEffectIndex(j)); + } +} + +void Spell::_handle_finish_phase() +{ + // spell log + if (m_needSpellLog) + SendLogExecute(); +} + +void Spell::SendSpellCooldown() +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Player* _player = (Player*)m_caster; + + // mana/health/etc potions, disabled by client (until combat out as declarate) + if (m_CastItem && m_CastItem->IsPotion()) + { + // need in some way provided data for Spell::finish SendCooldownEvent + _player->SetLastPotionId(m_CastItem->GetEntry()); + return; + } + + // (1) have infinity cooldown but set at aura apply, (2) passive cooldown at triggering + if (m_spellInfo->HasAttribute(SPELL_ATTR_DISABLED_WHILE_ACTIVE) || m_spellInfo->HasAttribute(SPELL_ATTR_PASSIVE)) + return; + + _player->AddSpellAndCategoryCooldowns(m_spellInfo, m_CastItem ? m_CastItem->GetEntry() : 0, this); +} + +void Spell::update(uint32 difftime) +{ + // update pointers based at it's GUIDs + UpdatePointers(); + + if (m_targets.getUnitTargetGuid() && !m_targets.getUnitTarget()) + { + cancel(); + return; + } + + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(EFFECT_INDEX_0); + SpellInterruptsEntry const* spellInterrupts = m_spellInfo->GetSpellInterrupts(); + + // check if the player caster has moved before the spell finished + if ((m_caster->GetTypeId() == TYPEID_PLAYER && m_timer != 0) && + (m_castPositionX != m_caster->GetPositionX() || m_castPositionY != m_caster->GetPositionY() || m_castPositionZ != m_caster->GetPositionZ()) && + ((spellEffect && spellEffect->Effect != SPELL_EFFECT_STUCK) || !((Player*)m_caster)->m_movementInfo.HasMovementFlag(MOVEFLAG_FALLINGFAR)) && + !m_caster->HasAffectedAura(SPELL_AURA_ALLOW_CAST_WHILE_MOVING, m_spellInfo)) + { + // always cancel for channeled spells + if (m_spellState == SPELL_STATE_CASTING) + cancel(); + // don't cancel for melee, autorepeat, triggered and instant spells + else if(!IsNextMeleeSwingSpell() && !IsAutoRepeat() && !m_IsTriggeredSpell && (spellInterrupts && spellInterrupts->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT)) + cancel(); + } + + switch (m_spellState) + { + case SPELL_STATE_PREPARING: + { + if (m_timer) + { + if (difftime >= m_timer) + m_timer = 0; + else + m_timer -= difftime; + } + + if (m_timer == 0 && !IsNextMeleeSwingSpell() && !IsAutoRepeat()) + cast(); + } break; + case SPELL_STATE_CASTING: + { + if (m_timer > 0) + { + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + // check if player has jumped before the channeling finished + if (((Player*)m_caster)->m_movementInfo.HasMovementFlag(MOVEFLAG_FALLING) && + !m_caster->HasAffectedAura(SPELL_AURA_ALLOW_CAST_WHILE_MOVING, m_spellInfo)) + cancel(); + + // check for incapacitating player states + if (m_caster->hasUnitState(UNIT_STAT_CAN_NOT_REACT)) + cancel(); + + // check if player has turned if flag is set + if( spellInterrupts && (spellInterrupts->ChannelInterruptFlags & CHANNEL_FLAG_TURNING) && m_castOrientation != m_caster->GetOrientation() ) + cancel(); + } + + // check if there are alive targets left + if (!IsAliveUnitPresentInTargetList()) + { + SendChannelUpdate(0); + finish(); + } + + if (difftime >= m_timer) + m_timer = 0; + else + m_timer -= difftime; + } + + if (m_timer == 0) + { + SendChannelUpdate(0); + + // channeled spell processed independently for quest targeting + // cast at creature (or GO) quest objectives update at successful cast channel finished + // ignore autorepeat/melee casts for speed (not exist quest for spells (hm... ) + if (!IsAutoRepeat() && !IsNextMeleeSwingSpell()) + { + if (Player* p = m_caster->GetCharmerOrOwnerPlayerOrPlayerItself()) + { + for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + TargetInfo const& target = *ihit; + if (!target.targetGUID.IsCreatureOrVehicle()) + continue; + + Unit* unit = m_caster->GetObjectGuid() == target.targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target.targetGUID); + if (unit == NULL) + continue; + + p->RewardPlayerAndGroupAtCast(unit, m_spellInfo->Id); + } + + for (GOTargetList::const_iterator ihit = m_UniqueGOTargetInfo.begin(); ihit != m_UniqueGOTargetInfo.end(); ++ihit) + { + GOTargetInfo const& target = *ihit; + + GameObject* go = m_caster->GetMap()->GetGameObject(target.targetGUID); + if (!go) + continue; + + p->RewardPlayerAndGroupAtCast(go, m_spellInfo->Id); + } + } + } + + finish(); + } + } break; + default: + { + } break; + } +} + +void Spell::finish(bool ok) +{ + if (!m_caster) + return; + + if (m_spellState == SPELL_STATE_FINISHED) + return; + + m_spellState = SPELL_STATE_FINISHED; + + // other code related only to successfully finished spells + if (!ok) + return; + + // handle SPELL_AURA_ADD_TARGET_TRIGGER auras + Unit::AuraList const& targetTriggers = m_caster->GetAurasByType(SPELL_AURA_ADD_TARGET_TRIGGER); + for (Unit::AuraList::const_iterator i = targetTriggers.begin(); i != targetTriggers.end(); ++i) + { + if (!(*i)->isAffectedOnSpell(m_spellInfo)) + continue; + for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if (ihit->missCondition == SPELL_MISS_NONE) + { + // check m_caster->GetGUID() let load auras at login and speedup most often case + Unit* unit = m_caster->GetObjectGuid() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID); + if (unit && unit->isAlive()) + { + SpellEntry const* auraSpellInfo = (*i)->GetSpellProto(); + SpellEffectIndex auraSpellIdx = (*i)->GetEffIndex(); + // Calculate chance at that moment (can be depend for example from combo points) + int32 auraBasePoints = (*i)->GetBasePoints(); + int32 chance = m_caster->CalculateSpellDamage(unit, auraSpellInfo, auraSpellIdx, &auraBasePoints); + if(roll_chance_i(chance)) + if(SpellEffectEntry const* spellEffect = auraSpellInfo->GetSpellEffect(auraSpellIdx)) + m_caster->CastSpell(unit, spellEffect->EffectTriggerSpell, true, NULL, (*i)); + } + } + } + } + + // Heal caster for all health leech from all targets + if (m_healthLeech) + { + uint32 absorb = 0; + m_caster->CalculateHealAbsorb(uint32(m_healthLeech), &absorb); + m_caster->DealHeal(m_caster, uint32(m_healthLeech) - absorb, m_spellInfo, false, absorb); + } + + if (IsMeleeAttackResetSpell()) + { + m_caster->resetAttackTimer(BASE_ATTACK); + if (m_caster->haveOffhandWeapon()) + m_caster->resetAttackTimer(OFF_ATTACK); + } + + /*if (IsRangedAttackResetSpell()) + m_caster->resetAttackTimer(RANGED_ATTACK);*/ + + // Clear combo at finish state + if (m_caster->GetTypeId() == TYPEID_PLAYER && NeedsComboPoints(m_spellInfo)) + { + // Not drop combopoints if negative spell and if any miss on enemy exist + bool needDrop = true; + if (!IsPositiveSpell(m_spellInfo->Id)) + { + for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if (ihit->missCondition != SPELL_MISS_NONE && ihit->targetGUID != m_caster->GetObjectGuid()) + { + needDrop = false; + break; + } + } + } + if (needDrop) + ((Player*)m_caster)->ClearComboPoints(); + } + + // potions disabled by client, send event "not in combat" if need + if (m_caster->GetTypeId() == TYPEID_PLAYER) + ((Player*)m_caster)->UpdatePotionCooldown(this); + + // call triggered spell only at successful cast (after clear combo points -> for add some if need) + if (!m_TriggerSpells.empty()) + CastTriggerSpells(); + + // Stop Attack for some spells + if (m_spellInfo->HasAttribute(SPELL_ATTR_STOP_ATTACK_TARGET)) + m_caster->AttackStop(); + + // update encounter state if needed + Map* map = m_caster->GetMap(); + if (map->IsDungeon()) + ((DungeonMap*)map)->GetPersistanceState()->UpdateEncounterState(ENCOUNTER_CREDIT_CAST_SPELL, m_spellInfo->Id); +} + +void Spell::SendCastResult(SpellCastResult result) +{ + if (result == SPELL_CAST_OK) + return; + + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + if (((Player*)m_caster)->GetSession()->PlayerLoading()) // don't send cast results at loading time + return; + + SendCastResult((Player*)m_caster, m_spellInfo, m_cast_count, result); +} + +void Spell::SendCastResult(Player* caster, SpellEntry const* spellInfo, uint8 cast_count, SpellCastResult result, bool isPetCastResult /*=false*/) +{ + if (result == SPELL_CAST_OK) + return; + + WorldPacket data(isPetCastResult ? SMSG_PET_CAST_FAILED : SMSG_CAST_FAILED, (4 + 1 + 2)); + data << uint8(cast_count); // single cast or multi 2.3 (0/1) + data << uint32(spellInfo->Id); + data << uint8(!IsPassiveSpell(spellInfo) ? result : SPELL_FAILED_DONT_REPORT); // do not report failed passive spells + switch (result) + { + case SPELL_FAILED_NOT_READY: + data << uint32(0); // unknown, value 1 seen for 14177 (update cooldowns on client flag) + break; + case SPELL_FAILED_REQUIRES_SPELL_FOCUS: + data << uint32(spellInfo->GetRequiresSpellFocus()); + break; + case SPELL_FAILED_REQUIRES_AREA: // AreaTable.dbc id + // hardcode areas limitation case + switch (spellInfo->Id) + { + case 41617: // Cenarion Mana Salve + case 41619: // Cenarion Healing Salve + data << uint32(3905); + break; + case 41618: // Bottled Nethergon Energy + case 41620: // Bottled Nethergon Vapor + data << uint32(3842); + break; + case 45373: // Bloodberry Elixir + data << uint32(4075); + break; + default: // default case (don't must be) + data << uint32(0); + break; + } + break; + case SPELL_FAILED_TOTEMS: + { + SpellTotemsEntry const* totems = spellInfo->GetSpellTotems(); + for(int i = 0; i < MAX_SPELL_TOTEMS; ++i) + if(totems && totems->Totem[i]) + data << uint32(totems->Totem[i]); + } + break; + case SPELL_FAILED_TOTEM_CATEGORY: + { + SpellTotemsEntry const* totems = spellInfo->GetSpellTotems(); + for(int i = 0; i < MAX_SPELL_TOTEM_CATEGORIES; ++i) + if(totems && totems->TotemCategory[i]) + data << uint32(totems->TotemCategory[i]); + } + break; + case SPELL_FAILED_EQUIPPED_ITEM_CLASS: + { + SpellEquippedItemsEntry const* eqItems = spellInfo->GetSpellEquippedItems(); + data << uint32(eqItems ? eqItems->EquippedItemClass : -1); + data << uint32(eqItems ? eqItems->EquippedItemSubClassMask : 0); + //data << uint32(eqItems ? eqItems->EquippedItemInventoryTypeMask : 0); + } + break; + case SPELL_FAILED_EQUIPPED_ITEM_CLASS_MAINHAND: + case SPELL_FAILED_EQUIPPED_ITEM_CLASS_OFFHAND: + { + SpellEquippedItemsEntry const* eqItems = spellInfo->GetSpellEquippedItems(); + data << uint32(eqItems ? eqItems->EquippedItemClass : -1); + data << uint32(eqItems ? eqItems->EquippedItemSubClassMask : 0); + break; + } + case SPELL_FAILED_PREVENTED_BY_MECHANIC: + data << uint32(0); // SpellMechanic.dbc id + break; + case SPELL_FAILED_CUSTOM_ERROR: + data << uint32(0); // custom error id (see enum SpellCastResultCustom) + break; + case SPELL_FAILED_NEED_EXOTIC_AMMO: + { + SpellEquippedItemsEntry const* eqItems = spellInfo->GetSpellEquippedItems(); + data << uint32(eqItems ? eqItems->EquippedItemSubClassMask : 0);// seems correct... + break; + } + case SPELL_FAILED_REAGENTS: + data << uint32(0); // item id + break; + case SPELL_FAILED_NEED_MORE_ITEMS: + data << uint32(0); // item id + data << uint32(0); // item count? + break; + case SPELL_FAILED_MIN_SKILL: + data << uint32(0); // SkillLine.dbc id + data << uint32(0); // required skill value + break; + case SPELL_FAILED_TOO_MANY_OF_ITEM: + data << uint32(0); // ItemLimitCategory.dbc id + break; + case SPELL_FAILED_FISHING_TOO_LOW: + data << uint32(0); // required fishing skill + break; + default: + break; + } + caster->GetSession()->SendPacket(&data); +} + +void Spell::SendSpellStart() +{ + if (!IsNeedSendToClient()) + return; + + DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Sending SMSG_SPELL_START id=%u", m_spellInfo->Id); + + uint32 castFlags = CAST_FLAG_UNKNOWN2; + if (m_spellInfo->runeCostID) + castFlags |= CAST_FLAG_UNKNOWN19; + + if ((m_caster->GetTypeId() == TYPEID_PLAYER || + m_caster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_caster)->IsPet()) && + m_spellInfo->powerType != POWER_HEALTH) + castFlags |= CAST_FLAG_PREDICTED_POWER; + + WorldPacket data(SMSG_SPELL_START, (8 + 8 + 4 + 4 + 2)); + if (m_CastItem) + data << m_CastItem->GetPackGUID(); + else + data << m_caster->GetPackGUID(); + + data << m_caster->GetPackGUID(); + data << uint8(m_cast_count); // pending spell cast + data << uint32(m_spellInfo->Id); // spellId + data << uint32(castFlags); // cast flags + data << uint32(m_timer); // delay? + data << uint32(m_casttime); // m_casttime + + data << m_targets; + + if (castFlags & CAST_FLAG_PREDICTED_POWER) // predicted power + data << uint32(m_caster->GetPower(Powers(m_spellInfo->powerType))); + + if (castFlags & CAST_FLAG_PREDICTED_RUNES) // predicted runes + { + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + Player* caster = (Player*)m_caster; + + data << uint8(m_runesState); + data << uint8(caster->GetRunesState()); + for (uint8 i = 0; i < MAX_RUNES; ++i) + data << uint8(255 - ((caster->GetRuneCooldown(i) / REGEN_TIME_FULL) * 51)); + } + else + { + data << uint8(0); + data << uint8(0); + for (uint8 i = 0; i < MAX_RUNES; ++i) + data << uint8(0); + } + } + + if (castFlags & CAST_FLAG_AMMO) // projectile info + WriteAmmoToPacket(&data); + + if (castFlags & CAST_FLAG_IMMUNITY) // cast immunity + { + data << uint32(0); // used for SetCastSchoolImmunities + data << uint32(0); // used for SetCastImmunities + } + + if (castFlags & CAST_FLAG_HEAL_PREDICTION) + { + uint8 unk = 0; + data << uint32(0); + data << uint8(unk); + if (unk == 2) + data << ObjectGuid().WriteAsPacked(); + } + + m_caster->SendMessageToSet(&data, true); +} + +void Spell::SendSpellGo() +{ + // not send invisible spell casting + if (!IsNeedSendToClient()) + return; + + DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Sending SMSG_SPELL_GO id=%u", m_spellInfo->Id); + + uint32 castFlags = CAST_FLAG_UNKNOWN9; + + if ((m_caster->GetTypeId() == TYPEID_PLAYER || + m_caster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_caster)->IsPet()) && + m_spellInfo->powerType != POWER_HEALTH) + castFlags |= CAST_FLAG_PREDICTED_POWER; + + if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->getClass() == CLASS_DEATH_KNIGHT && m_spellInfo->runeCostID) + { + castFlags |= CAST_FLAG_UNKNOWN19; // same as in SMSG_SPELL_START + castFlags |= CAST_FLAG_PREDICTED_RUNES; // rune cooldowns list + } + + WorldPacket data(SMSG_SPELL_GO, 50); // guess size + + if (m_CastItem) + data << m_CastItem->GetPackGUID(); + else + data << m_caster->GetPackGUID(); + + data << m_caster->GetPackGUID(); + data << uint8(m_cast_count); // pending spell cast? + data << uint32(m_spellInfo->Id); // spellId + data << uint32(castFlags); // cast flags + data << uint32(m_timer); + data << uint32(WorldTimer::getMSTime()); // timestamp + + WriteSpellGoTargets(&data); + + data << m_targets; + + if (castFlags & CAST_FLAG_PREDICTED_POWER) // predicted power + data << uint32(m_caster->GetPower(Powers(m_spellInfo->powerType))); + + if (castFlags & CAST_FLAG_PREDICTED_RUNES) // predicted runes + { + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + Player* caster = (Player*)m_caster; + + data << uint8(m_runesState); + data << uint8(caster->GetRunesState()); + for (uint8 i = 0; i < MAX_RUNES; ++i) + data << uint8(caster->GetRuneCooldownFraction(i)); + } + else + { + data << uint8(0); + data << uint8(0); + for (uint8 i = 0; i < MAX_RUNES; ++i) + data << uint8(0); + } + } + + if (castFlags & CAST_FLAG_ADJUST_MISSILE) // adjust missile trajectory duration + { + data << float(m_targets.GetElevation()); + data << uint32(m_delayMoment); + } + + if (castFlags & CAST_FLAG_AMMO) // projectile info + WriteAmmoToPacket(&data); + + if (castFlags & CAST_FLAG_VISUAL_CHAIN) // spell visual chain effect + { + data << uint32(0); // SpellVisual.dbc id? + data << uint32(0); // overrides previous field if > 0 and violencelevel client cvar < 2 + } + + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) + { + data << uint8(0); // The value increase for each time, can remind of a cast count for the spell + } + + if (m_targets.m_targetMask & TARGET_FLAG_VISUAL_CHAIN) // probably used (or can be used) with CAST_FLAG_VISUAL_CHAIN flag + { + data << uint32(0); // count + + // for(int = 0; i < count; ++i) + //{ + // // position and guid? + // data << float(0) << float(0) << float(0) << uint64(0); + //} + } + + m_caster->SendMessageToSet(&data, true); +} + +void Spell::WriteAmmoToPacket(WorldPacket* data) +{ + uint32 ammoInventoryType = 0; + uint32 ammoDisplayID = 0; + + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + Item* pItem = ((Player*)m_caster)->GetWeaponForAttack(RANGED_ATTACK); + if (pItem) + { + ammoInventoryType = pItem->GetProto()->InventoryType; + if (ammoInventoryType == INVTYPE_THROWN) + ammoDisplayID = pItem->GetProto()->DisplayInfoID; + else + { + if(m_caster->GetDummyAura(46699)) // Requires No Ammo + { + ammoDisplayID = 5996; // normal arrow + ammoInventoryType = INVTYPE_AMMO; + } + } + } + } + else + { + for (uint8 i = 0; i < MAX_VIRTUAL_ITEM_SLOT; ++i) + { + if (uint32 item_id = m_caster->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i)) + { + if(ItemPrototype const* itemEntry = sItemStorage.LookupEntry(item_id)) + //if(ItemEntry const * itemEntry = sItemStore.LookupEntry(item_id)) + { + if (itemEntry->Class == ITEM_CLASS_WEAPON) + { + switch (itemEntry->SubClass) + { + case ITEM_SUBCLASS_WEAPON_THROWN: + ammoDisplayID = itemEntry->DisplayInfoID; + ammoInventoryType = itemEntry->InventoryType; + break; + case ITEM_SUBCLASS_WEAPON_BOW: + case ITEM_SUBCLASS_WEAPON_CROSSBOW: + ammoDisplayID = 5996; // is this need fixing? + ammoInventoryType = INVTYPE_AMMO; + break; + case ITEM_SUBCLASS_WEAPON_GUN: + ammoDisplayID = 5998; // is this need fixing? + ammoInventoryType = INVTYPE_AMMO; + break; + } + + if (ammoDisplayID) + break; + } + } + } + } + } + + *data << uint32(ammoDisplayID); + *data << uint32(ammoInventoryType); +} + +void Spell::WriteSpellGoTargets(WorldPacket* data) +{ + size_t count_pos = data->wpos(); + *data << uint8(0); // placeholder + + // This function also fill data for channeled spells: + // m_needAliveTargetMask req for stop channeling if one target die + uint32 hit = m_UniqueGOTargetInfo.size(); // Always hits on GO + uint32 miss = 0; + + for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if (ihit->effectMask == 0) // No effect apply - all immuned add state + { + // possibly SPELL_MISS_IMMUNE2 for this?? + ihit->missCondition = SPELL_MISS_IMMUNE2; + ++miss; + } + else if (ihit->missCondition == SPELL_MISS_NONE) // Add only hits + { + ++hit; + *data << ihit->targetGUID; + m_needAliveTargetMask |= ihit->effectMask; + } + else + ++miss; + } + + for (GOTargetList::const_iterator ighit = m_UniqueGOTargetInfo.begin(); ighit != m_UniqueGOTargetInfo.end(); ++ighit) + *data << ighit->targetGUID; // Always hits + + data->put(count_pos, hit); + + *data << (uint8)miss; + for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if (ihit->missCondition != SPELL_MISS_NONE) // Add only miss + { + *data << ihit->targetGUID; + *data << uint8(ihit->missCondition); + if (ihit->missCondition == SPELL_MISS_REFLECT) + *data << uint8(ihit->reflectResult); + } + } + // Reset m_needAliveTargetMask for non channeled spell + if (!IsChanneledSpell(m_spellInfo)) + m_needAliveTargetMask = 0; +} + +void Spell::SendLogExecute() +{ + Unit* target = m_targets.getUnitTarget() ? m_targets.getUnitTarget() : m_caster; + + WorldPacket data(SMSG_SPELLLOGEXECUTE, (8 + 4 + 4 + 4 + 4 + 8)); + + if (m_caster->GetTypeId() == TYPEID_PLAYER) + data << m_caster->GetPackGUID(); + else + data << target->GetPackGUID(); + + data << uint32(m_spellInfo->Id); + uint32 count1 = 1; + data << uint32(count1); // count1 (effect count?) + for (uint32 i = 0; i < count1; ++i) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(EFFECT_INDEX_0); + data << uint32(spellEffect ? spellEffect->Effect : 0);// spell effect + uint32 count2 = 1; + data << uint32(count2); // count2 (target count?) + for (uint32 j = 0; j < count2; ++j) + { + if(!spellEffect) + continue; + + switch(spellEffect->Effect) + { + case SPELL_EFFECT_POWER_DRAIN: + case SPELL_EFFECT_POWER_BURN: + if (Unit* unit = m_targets.getUnitTarget()) + data << unit->GetPackGUID(); + else + data << uint8(0); + data << uint32(0); + data << uint32(0); + data << float(0); + break; + case SPELL_EFFECT_ADD_EXTRA_ATTACKS: + if (Unit* unit = m_targets.getUnitTarget()) + data << unit->GetPackGUID(); + else + data << uint8(0); + data << uint32(0); // count? + break; + case SPELL_EFFECT_INTERRUPT_CAST: + if (Unit* unit = m_targets.getUnitTarget()) + data << unit->GetPackGUID(); + else + data << uint8(0); + data << uint32(0); // spellid + break; + case SPELL_EFFECT_DURABILITY_DAMAGE: + if (Unit* unit = m_targets.getUnitTarget()) + data << unit->GetPackGUID(); + else + data << uint8(0); + data << uint32(0); + data << uint32(0); + break; + case SPELL_EFFECT_OPEN_LOCK: + if (Item* item = m_targets.getItemTarget()) + data << item->GetPackGUID(); + else + data << uint8(0); + break; + case SPELL_EFFECT_CREATE_ITEM: + case SPELL_EFFECT_CREATE_RANDOM_ITEM: + case SPELL_EFFECT_CREATE_ITEM_2: + data << uint32(spellEffect->EffectItemType); + break; + case SPELL_EFFECT_SUMMON: + case SPELL_EFFECT_TRANS_DOOR: + case SPELL_EFFECT_SUMMON_PET: + case SPELL_EFFECT_SUMMON_OBJECT_WILD: + case SPELL_EFFECT_CREATE_HOUSE: + case SPELL_EFFECT_DUEL: + case SPELL_EFFECT_SUMMON_OBJECT_SLOT: + case SPELL_EFFECT_171: + if (Unit* unit = m_targets.getUnitTarget()) + data << unit->GetPackGUID(); + else if (m_targets.getItemTargetGuid()) + data << m_targets.getItemTargetGuid().WriteAsPacked(); + else if (GameObject* go = m_targets.getGOTarget()) + data << go->GetPackGUID(); + else + data << uint8(0); // guid + break; + case SPELL_EFFECT_FEED_PET: + data << uint32(m_targets.getItemTargetEntry()); + break; + case SPELL_EFFECT_DISMISS_PET: + if (Unit* unit = m_targets.getUnitTarget()) + data << unit->GetPackGUID(); + else + data << uint8(0); + break; + case SPELL_EFFECT_RESURRECT: + case SPELL_EFFECT_RESURRECT_NEW: + case SPELL_EFFECT_MASS_RESSURECTION: + if (Unit* unit = m_targets.getUnitTarget()) + data << unit->GetPackGUID(); + else + data << uint8(0); + break; + default: + return; + } + } + } + + m_caster->SendMessageToSet(&data, true); +} + +void Spell::SendInterrupted(uint8 result) +{ + WorldPacket data(SMSG_SPELL_FAILURE, (8 + 4 + 1)); + data << m_caster->GetPackGUID(); + data << uint8(m_cast_count); + data << uint32(m_spellInfo->Id); + data << uint8(result); + m_caster->SendMessageToSet(&data, true); + + data.Initialize(SMSG_SPELL_FAILED_OTHER, (8 + 4)); + data << m_caster->GetPackGUID(); + data << uint8(m_cast_count); + data << uint32(m_spellInfo->Id); + data << uint8(result); + m_caster->SendMessageToSet(&data, true); +} + +void Spell::SendChannelUpdate(uint32 time) +{ + if (time == 0) + { + // Reset farsight for some possessing auras of possessed summoned (as they might work with different aura types) + if (m_spellInfo->HasAttribute(SPELL_ATTR_EX_FARSIGHT) && m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->GetCharmGuid() + && !IsSpellHaveAura(m_spellInfo, SPELL_AURA_MOD_POSSESS) && !IsSpellHaveAura(m_spellInfo, SPELL_AURA_MOD_POSSESS_PET)) + { + Player* player = (Player*)m_caster; + // These Auras are applied to self, so get the possessed first + Unit* possessed = player->GetCharm(); + + player->SetCharm(NULL); + if (possessed) + player->SetClientControl(possessed, 0); + player->SetMover(NULL); + player->GetCamera().ResetView(); + player->RemovePetActionBar(); + + if (possessed) + { + possessed->clearUnitState(UNIT_STAT_CONTROLLED); + possessed->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + possessed->SetCharmerGuid(ObjectGuid()); + // TODO - Requires more specials for target? + + // Some possessed might want to despawn? + if (possessed->GetUInt32Value(UNIT_CREATED_BY_SPELL) == m_spellInfo->Id && possessed->GetTypeId() == TYPEID_UNIT) + ((Creature*)possessed)->ForcedDespawn(); + } + } + + m_caster->RemoveAurasByCasterSpell(m_spellInfo->Id, m_caster->GetObjectGuid()); + + ObjectGuid target_guid = m_caster->GetChannelObjectGuid(); + if (target_guid != m_caster->GetObjectGuid() && target_guid.IsUnit()) + if (Unit* target = ObjectAccessor::GetUnit(*m_caster, target_guid)) + target->RemoveAurasByCasterSpell(m_spellInfo->Id, m_caster->GetObjectGuid()); + + // Only finish channeling when latest channeled spell finishes + if (m_caster->GetUInt32Value(UNIT_CHANNEL_SPELL) != m_spellInfo->Id) + return; + + m_caster->SetChannelObjectGuid(ObjectGuid()); + m_caster->SetUInt32Value(UNIT_CHANNEL_SPELL, 0); + } + + WorldPacket data(SMSG_CHANNEL_UPDATE, 8 + 4); + data << m_caster->GetPackGUID(); + data << uint32(time); + m_caster->SendMessageToSet(&data, true); +} + +void Spell::SendChannelStart(uint32 duration) +{ + WorldObject* target = NULL; + + // select dynobject created by first effect if any + if (m_spellInfo->GetSpellEffectIdByIndex(EFFECT_INDEX_0) == SPELL_EFFECT_PERSISTENT_AREA_AURA) + target = m_caster->GetDynObject(m_spellInfo->Id, EFFECT_INDEX_0); + // select first not resisted target from target list for _0_ effect + else if (!m_UniqueTargetInfo.empty()) + { + for (TargetList::const_iterator itr = m_UniqueTargetInfo.begin(); itr != m_UniqueTargetInfo.end(); ++itr) + { + if ((itr->effectMask & (1 << EFFECT_INDEX_0)) && itr->reflectResult == SPELL_MISS_NONE && + itr->targetGUID != m_caster->GetObjectGuid()) + { + target = ObjectAccessor::GetUnit(*m_caster, itr->targetGUID); + break; + } + } + } + else if (!m_UniqueGOTargetInfo.empty()) + { + for (GOTargetList::const_iterator itr = m_UniqueGOTargetInfo.begin(); itr != m_UniqueGOTargetInfo.end(); ++itr) + { + if (itr->effectMask & (1 << EFFECT_INDEX_0)) + { + target = m_caster->GetMap()->GetGameObject(itr->targetGUID); + break; + } + } + } + + WorldPacket data(SMSG_CHANNEL_START, (8 + 4 + 4)); + data << m_caster->GetPackGUID(); + data << uint32(m_spellInfo->Id); + data << uint32(duration); + data << uint8(0); // unk1 + //if (unk1) + //{ + // data << uint32(0); + // data << uint32(0); + //} + data << uint8(0); // unk2 + //if (unk2) + //{ + // data << ObjectGuid().WriteAsPacked(); + // data << uint32(0); + // data << uint8(0); // unk3 + // if (unk3 == 2) + // data << ObjectGuid().WriteAsPacked(); + //} + + m_caster->SendMessageToSet(&data, true); + + m_timer = duration; + + if (target) + m_caster->SetChannelObjectGuid(target->GetObjectGuid()); + + m_caster->SetUInt32Value(UNIT_CHANNEL_SPELL, m_spellInfo->Id); +} + +void Spell::SendResurrectRequest(Player* target) +{ + // Both players and NPCs can resurrect using spells - have a look at creature 28487 for example + // However, the packet structure differs slightly + + const char* sentName = m_caster->GetTypeId() == TYPEID_PLAYER ? "" : m_caster->GetNameForLocaleIdx(target->GetSession()->GetSessionDbLocaleIndex()); + + WorldPacket data(SMSG_RESURRECT_REQUEST, (8 + 4 + strlen(sentName) + 1 + 1 + 1)); + data << m_caster->GetObjectGuid(); + data << uint32(strlen(sentName) + 1); + + data << sentName; + data << uint8(0); + + data << uint8(m_caster->GetTypeId() == TYPEID_PLAYER ? 0 : 1); + data << uint32(m_spellInfo->Id); + + target->GetSession()->SendPacket(&data); +} + +void Spell::SendPlaySpellVisual(uint32 SpellID) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + WorldPacket data; + m_caster->BuildSendPlayVisualPacket(&data, SpellID, false); + + ((Player*)m_caster)->GetSession()->SendPacket(&data); +} + +void Spell::TakeCastItem() +{ + if (!m_CastItem || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // not remove cast item at triggered spell (equipping, weapon damage, etc) + if (m_IsTriggeredSpell && !(m_targets.m_targetMask & TARGET_FLAG_TRADE_ITEM)) + return; + + ItemPrototype const* proto = m_CastItem->GetProto(); + + if (!proto) + { + // This code is to avoid a crash + // I'm not sure, if this is really an error, but I guess every item needs a prototype + sLog.outError("Cast item (%s) has no item prototype", m_CastItem->GetGuidStr().c_str()); + return; + } + + bool expendable = false; + bool withoutCharges = false; + + for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + if (proto->Spells[i].SpellId) + { + // item has limited charges + if (proto->Spells[i].SpellCharges) + { + if (proto->Spells[i].SpellCharges < 0 && !(proto->ExtraFlags & ITEM_EXTRA_NON_CONSUMABLE)) + expendable = true; + + int32 charges = m_CastItem->GetSpellCharges(i); + + // item has charges left + if (charges) + { + (charges > 0) ? --charges : ++charges; // abs(charges) less at 1 after use + if (proto->Stackable == 1) + m_CastItem->SetSpellCharges(i, charges); + m_CastItem->SetState(ITEM_CHANGED, (Player*)m_caster); + } + + // all charges used + withoutCharges = (charges == 0); + } + } + } + + if (expendable && withoutCharges) + { + uint32 count = 1; + ((Player*)m_caster)->DestroyItemCount(m_CastItem, count, true); + + // prevent crash at access to deleted m_targets.getItemTarget + ClearCastItem(); + } +} + +void Spell::TakePower() +{ + if (m_CastItem || m_triggeredByAuraSpell) + return; + + bool hit = true; + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + if (m_spellInfo->powerType == POWER_RAGE || m_spellInfo->powerType == POWER_ENERGY || m_spellInfo->powerType == POWER_HOLY_POWER) + { + ObjectGuid targetGuid = m_targets.getUnitTargetGuid(); + if (!targetGuid.IsEmpty()) + for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + if (ihit->targetGUID == targetGuid) + { + if (ihit->missCondition != SPELL_MISS_NONE && ihit->missCondition != SPELL_MISS_MISS) + hit = false; + if (ihit->missCondition != SPELL_MISS_NONE) + { + // lower spell cost on fail (by talent aura) + if (Player* modOwner = ((Player*)m_caster)->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_SPELL_COST_REFUND_ON_FAIL, m_powerCost); + } + break; + } + } + } + + // health as power used + if (m_spellInfo->powerType == POWER_HEALTH) + { + m_caster->ModifyHealth(-(int32)m_powerCost); + return; + } + + if (m_spellInfo->powerType >= MAX_POWERS) + { + sLog.outError("Spell::TakePower: Unknown power type '%d'", m_spellInfo->powerType); + return; + } + + Powers powerType = Powers(m_spellInfo->powerType); + + if (powerType == POWER_HOLY_POWER) + { + m_usedHolyPower = m_powerCost; + + // spells consume all holy power when successfully hit + if (hit) + { + // Divine Purpose + if (m_caster->HasAura(90174)) + { + m_usedHolyPower = m_caster->GetMaxPower(POWER_HOLY_POWER); + return; + } + else + m_usedHolyPower = m_caster->GetPower(POWER_HOLY_POWER); + } + + // Zealotry - does not take power + if (m_spellInfo->Id == 85696) + return; + + m_caster->ModifyPower(powerType, -(int32)m_usedHolyPower); + return; + } + + if (powerType == POWER_RUNE) + { + TakeRunePower(hit); + return; + } + + m_caster->ModifyPower(powerType, -(int32)m_powerCost); +} + +SpellCastResult Spell::CheckRunePower() +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return SPELL_CAST_OK; + + Player* plr = (Player*)m_caster; + + if (plr->getClass() != CLASS_DEATH_KNIGHT) + return SPELL_CAST_OK; + + SpellRuneCostEntry const* src = sSpellRuneCostStore.LookupEntry(m_spellInfo->runeCostID); + if (!src || (src->NoRuneCost())) + return SPELL_CAST_OK; + + int32 runeCost[NUM_RUNE_TYPES]; // blood, frost, unholy, death + for (uint8 i = 0; i < RUNE_DEATH; ++i) + { + runeCost[i] = src->RuneCost[i]; + if (Player* modOwner = plr->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_COST, runeCost[i], this); + } + + + runeCost[RUNE_DEATH] = MAX_RUNES; // calculated later + + // scan non-death runes (death rune not used explicitly in rune costs) + for (uint8 i = 0; i < MAX_RUNES; ++i) + { + RuneType rune = plr->GetCurrentRune(i); + if (!plr->GetRuneCooldown(i) && runeCost[rune] > 0) + --runeCost[rune]; + } + + // collect all not counted rune costs to death runes cost + for (uint8 i = 0; i < RUNE_DEATH; ++i) + if (runeCost[i] > 0) + runeCost[RUNE_DEATH] += runeCost[i]; + + // scan death runes + if (runeCost[RUNE_DEATH] > MAX_RUNES) + return SPELL_FAILED_NO_POWER; + + return SPELL_CAST_OK; +} + + +void Spell::TakeRunePower(bool hit) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Player* plr = (Player*)m_caster; + + if (plr->getClass() != CLASS_DEATH_KNIGHT) + return; + + SpellRuneCostEntry const* src = sSpellRuneCostStore.LookupEntry(m_spellInfo->runeCostID); + + if (!src) + return; + + if (src->NoRuneCost() && src->NoRunicPowerGain()) + return; + + m_runesState = plr->GetRunesState(); // store previous state + + // at this moment for rune cost exist only no cost mods, and no percent mods + int32 runeCost[NUM_RUNE_TYPES]; // blood, frost, unholy, death + for (uint32 i = 0; i < RUNE_DEATH; ++i) + { + runeCost[i] = src->RuneCost[i]; + if (Player* modOwner = m_caster->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_COST, runeCost[i], this); + } + + runeCost[RUNE_DEATH] = 0; // calculated later + + plr->ClearLastUsedRuneMask(); + + for (uint32 i = 0; i < MAX_RUNES; ++i) + { + RuneType rune = plr->GetCurrentRune(i); + if (!plr->GetRuneCooldown(i) && runeCost[rune] > 0) + { + uint16 baseCd = hit ? uint16(RUNE_BASE_COOLDOWN) : uint16(RUNE_MISS_COOLDOWN); + plr->SetBaseRuneCooldown(i, baseCd); + plr->SetRuneCooldown(i, baseCd); + plr->SetLastUsedRune(rune); + --runeCost[rune]; + } + } + + runeCost[RUNE_DEATH] = runeCost[RUNE_BLOOD] + runeCost[RUNE_UNHOLY] + runeCost[RUNE_FROST]; + + if (runeCost[RUNE_DEATH] > 0) + { + for (uint32 i = 0; i < MAX_RUNES; ++i) + { + RuneType rune = plr->GetCurrentRune(i); + if (!plr->GetRuneCooldown(i) && rune == RUNE_DEATH) + { + uint16 baseCd = hit ? uint16(RUNE_BASE_COOLDOWN) : uint16(RUNE_MISS_COOLDOWN); + plr->SetBaseRuneCooldown(i, baseCd); + plr->SetRuneCooldown(i, baseCd); + plr->SetLastUsedRune(rune); + --runeCost[rune]; + + // keep Death Rune type if missed + if (hit) + plr->RestoreBaseRune(i); + + if (runeCost[RUNE_DEATH] == 0) + break; + } + } + } + + if (hit) + { + // you can gain some runic power when use runes + int32 rp = int32(src->runePowerGain); + if (rp) + { + if (Player* modOwner = m_caster->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_COST, rp, this); + + rp = int32(sWorld.getConfig(CONFIG_FLOAT_RATE_POWER_RUNICPOWER_INCOME) * rp); + rp += int32(m_caster->GetTotalAuraModifier(SPELL_AURA_MOD_RUNIC_POWER_REGEN) * rp / 100); + if (rp > 0) + plr->ModifyPower(POWER_RUNIC_POWER, rp); + } + } +} + +void Spell::TakeReagents() +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + if (IgnoreItemRequirements()) // reagents used in triggered spell removed by original spell or don't must be removed. + return; + + Player* p_caster = (Player*)m_caster; + if (p_caster->CanNoReagentCast(m_spellInfo)) + return; + + SpellReagentsEntry const* spellReagents = m_spellInfo->GetSpellReagents(); + + for(uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x) + { + if(!spellReagents) + continue; + if(spellReagents->Reagent[x] <= 0) + continue; + + uint32 itemid = spellReagents->Reagent[x]; + uint32 itemcount = spellReagents->ReagentCount[x]; + + // if CastItem is also spell reagent + if (m_CastItem) + { + ItemPrototype const* proto = m_CastItem->GetProto(); + if (proto && proto->ItemId == itemid) + { + for (int s = 0; s < MAX_ITEM_PROTO_SPELLS; ++s) + { + // CastItem will be used up and does not count as reagent + int32 charges = m_CastItem->GetSpellCharges(s); + if (proto->Spells[s].SpellCharges < 0 && abs(charges) < 2) + { + ++itemcount; + break; + } + } + + m_CastItem = NULL; + } + } + + // if getItemTarget is also spell reagent + if (m_targets.getItemTargetEntry() == itemid) + m_targets.setItemTarget(NULL); + + p_caster->DestroyItemCount(itemid, itemcount, true); + } +} + +void Spell::HandleThreatSpells() +{ + if (m_UniqueTargetInfo.empty()) + return; + + SpellThreatEntry const* threatEntry = sSpellMgr.GetSpellThreatEntry(m_spellInfo->Id); + + if (!threatEntry || (!threatEntry->threat && threatEntry->ap_bonus == 0.0f)) + return; + + float threat = threatEntry->threat; + if (threatEntry->ap_bonus != 0.0f) + threat += threatEntry->ap_bonus * m_caster->GetTotalAttackPowerValue(GetWeaponAttackType(m_spellInfo)); + + bool positive = true; + uint8 effectMask = 0; + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + if (SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(i))) + if (spellEffect->Effect) + effectMask |= (1<Id, sSpellMgr.GetSpellRank(m_spellInfo->Id)); + return; + } + positive = false; + } + + // since 2.0.1 threat from positive effects also is distributed among all targets, so the overall caused threat is at most the defined bonus + threat /= m_UniqueTargetInfo.size(); + + for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if (ihit->missCondition != SPELL_MISS_NONE) + continue; + + Unit* target = m_caster->GetObjectGuid() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID); + if (!target) + continue; + + // positive spells distribute threat among all units that are in combat with target, like healing + if (positive) + { + target->getHostileRefManager().threatAssist(m_caster /*real_caster ??*/, threat, m_spellInfo); + } + // for negative spells threat gets distributed among affected targets + else + { + if (!target->CanHaveThreatList()) + continue; + + target->AddThreat(m_caster, threat, false, GetSpellSchoolMask(m_spellInfo), m_spellInfo); + } + } + + DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell %u added an additional %f threat for %s " SIZEFMTD " target(s)", m_spellInfo->Id, threat, positive ? "assisting" : "harming", m_UniqueTargetInfo.size()); +} + +void Spell::HandleEffects(Unit* pUnitTarget, Item* pItemTarget, GameObject* pGOTarget, SpellEffectIndex i, float DamageMultiplier) +{ + unitTarget = pUnitTarget; + itemTarget = pItemTarget; + gameObjTarget = pGOTarget; + + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(i)); + + damage = int32(CalculateDamage(i, unitTarget) * DamageMultiplier); + + if(spellEffect) + { + if(spellEffect->Effect < TOTAL_SPELL_EFFECTS) + { + DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell %u Effect%d : %u Targets: %s, %s, %s", + m_spellInfo->Id, i, spellEffect->Effect, + unitTarget ? unitTarget->GetGuidStr().c_str() : "-", + itemTarget ? itemTarget->GetGuidStr().c_str() : "-", + gameObjTarget ? gameObjTarget->GetGuidStr().c_str() : "-"); + + (*this.*SpellEffects[spellEffect->Effect])(spellEffect); + } + else + { + sLog.outError("WORLD: Spell %u Effect%d : %u > TOTAL_SPELL_EFFECTS", m_spellInfo->Id, i, spellEffect->Effect); + } + } + else + { + sLog.outError("WORLD: Spell %u has no effect at index %u", m_spellInfo->Id, i); + } +} + +void Spell::AddTriggeredSpell(uint32 spellId) +{ + SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellId); + + if (!spellInfo) + { + sLog.outError("Spell::AddTriggeredSpell: unknown spell id %u used as triggred spell for spell %u)", spellId, m_spellInfo->Id); + return; + } + + m_TriggerSpells.push_back(spellInfo); +} + +void Spell::AddPrecastSpell(uint32 spellId) +{ + SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellId); + + if (!spellInfo) + { + sLog.outError("Spell::AddPrecastSpell: unknown spell id %u used as pre-cast spell for spell %u)", spellId, m_spellInfo->Id); + return; + } + + m_preCastSpells.push_back(spellInfo); +} + +void Spell::CastTriggerSpells() +{ + for (SpellInfoList::const_iterator si = m_TriggerSpells.begin(); si != m_TriggerSpells.end(); ++si) + { + Spell* spell = new Spell(m_caster, (*si), true, m_originalCasterGUID); + spell->prepare(&m_targets); // use original spell original targets + } +} + +void Spell::CastPreCastSpells(Unit* target) +{ + for (SpellInfoList::const_iterator si = m_preCastSpells.begin(); si != m_preCastSpells.end(); ++si) + m_caster->CastSpell(target, (*si), true, m_CastItem); +} + +Unit* Spell::GetPrefilledUnitTargetOrUnitTarget(SpellEffectIndex effIndex) const +{ + for (TargetList::const_iterator itr = m_UniqueTargetInfo.begin(); itr != m_UniqueTargetInfo.end(); ++itr) + if (itr->effectMask & (1 << effIndex)) + return m_caster->GetMap()->GetUnit(itr->targetGUID); + + return m_targets.getUnitTarget(); +} + +SpellCastResult Spell::CheckCast(bool strict) +{ + // check cooldowns to prevent cheating (ignore passive spells, that client side visual only) + if (m_caster->GetTypeId() == TYPEID_PLAYER && !m_spellInfo->HasAttribute(SPELL_ATTR_PASSIVE) && + ((Player*)m_caster)->HasSpellCooldown(m_spellInfo->Id)) + { + if (m_triggeredByAuraSpell) + return SPELL_FAILED_DONT_REPORT; + else + return SPELL_FAILED_NOT_READY; + } + + // check global cooldown + if (strict && !m_IsTriggeredSpell && HasGlobalCooldown()) + return SPELL_FAILED_NOT_READY; + + // only allow triggered spells if at an ended battleground + if (!m_IsTriggeredSpell && m_caster->GetTypeId() == TYPEID_PLAYER) + if (BattleGround* bg = ((Player*)m_caster)->GetBattleGround()) + if (bg->GetStatus() == STATUS_WAIT_LEAVE) + return SPELL_FAILED_DONT_REPORT; + + if (!m_IsTriggeredSpell && IsNonCombatSpell(m_spellInfo) && + m_caster->isInCombat() && !m_caster->IsIgnoreUnitState(m_spellInfo, IGNORE_UNIT_COMBAT_STATE)) + return SPELL_FAILED_AFFECTING_COMBAT; + + if (m_caster->GetTypeId() == TYPEID_PLAYER && !((Player*)m_caster)->isGameMaster() && + sWorld.getConfig(CONFIG_BOOL_VMAP_INDOOR_CHECK) && + VMAP::VMapFactory::createOrGetVMapManager()->isLineOfSightCalcEnabled()) + { + if (m_spellInfo->HasAttribute(SPELL_ATTR_OUTDOORS_ONLY) && + !m_caster->GetTerrain()->IsOutdoors(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ())) + return SPELL_FAILED_ONLY_OUTDOORS; + + if (m_spellInfo->HasAttribute(SPELL_ATTR_INDOORS_ONLY) && + m_caster->GetTerrain()->IsOutdoors(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ())) + return SPELL_FAILED_ONLY_INDOORS; + } + // only check at first call, Stealth auras are already removed at second call + // for now, ignore triggered spells + if (strict && !m_IsTriggeredSpell) + { + // Ignore form req aura + if (!m_caster->HasAffectedAura(SPELL_AURA_MOD_IGNORE_SHAPESHIFT, m_spellInfo)) + { + // Cannot be used in this stance/form + SpellCastResult shapeError = GetErrorAtShapeshiftedCast(m_spellInfo, m_caster->GetShapeshiftForm()); + if (shapeError != SPELL_CAST_OK) + return shapeError; + + if (m_spellInfo->HasAttribute(SPELL_ATTR_ONLY_STEALTHED) && !(m_caster->HasStealthAura())) + return SPELL_FAILED_ONLY_STEALTHED; + } + } + + SpellAuraRestrictionsEntry const* auraRestrictions = m_spellInfo->GetSpellAuraRestrictions(); + + // caster state requirements + if(auraRestrictions && auraRestrictions->CasterAuraState && !m_caster->HasAuraState(AuraState(auraRestrictions->CasterAuraState))) + return SPELL_FAILED_CASTER_AURASTATE; + if(auraRestrictions && auraRestrictions->CasterAuraStateNot && m_caster->HasAuraState(AuraState(auraRestrictions->CasterAuraStateNot))) + return SPELL_FAILED_CASTER_AURASTATE; + + // Caster aura req check if need + if(auraRestrictions && auraRestrictions->casterAuraSpell && !m_caster->HasAura(auraRestrictions->casterAuraSpell)) + return SPELL_FAILED_CASTER_AURASTATE; + if(auraRestrictions && auraRestrictions->excludeCasterAuraSpell) + { + // Special cases of non existing auras handling + if(auraRestrictions->excludeCasterAuraSpell == 61988) + { + // Avenging Wrath Marker + if (m_caster->HasAura(61987)) + return SPELL_FAILED_CASTER_AURASTATE; + } + else if(m_caster->HasAura(auraRestrictions->excludeCasterAuraSpell)) + return SPELL_FAILED_CASTER_AURASTATE; + } + + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + // cancel autorepeat spells if cast start when moving + // (not wand currently autorepeat cast delayed to moving stop anyway in spell update code) + if (((Player*)m_caster)->isMoving() && !m_caster->HasAffectedAura(SPELL_AURA_ALLOW_CAST_WHILE_MOVING, m_spellInfo)) + { + // skip stuck spell to allow use it in falling case and apply spell limitations at movement + if ((!((Player*)m_caster)->m_movementInfo.HasMovementFlag(MOVEFLAG_FALLINGFAR) || m_spellInfo->GetSpellEffectIdByIndex(EFFECT_INDEX_0) != SPELL_EFFECT_STUCK) && + (IsAutoRepeat() || (m_spellInfo->GetAuraInterruptFlags() & AURA_INTERRUPT_FLAG_NOT_SEATED) != 0)) + return SPELL_FAILED_MOVING; + } + + if (!m_IsTriggeredSpell && NeedsComboPoints(m_spellInfo) && !m_caster->IsIgnoreUnitState(m_spellInfo, IGNORE_UNIT_TARGET_STATE) && + (!m_targets.getUnitTarget() || m_targets.getUnitTarget()->GetObjectGuid() != ((Player*)m_caster)->GetComboTargetGuid()) && + !m_spellInfo->HasAttribute(SPELL_ATTR_EX8_IGNORE_TARGET_FOR_COMBO_POINTS)) + // warrior not have real combo-points at client side but use this way for mark allow Overpower use + return m_caster->getClass() == CLASS_WARRIOR ? SPELL_FAILED_CASTER_AURASTATE : SPELL_FAILED_NO_COMBO_POINTS; + } + + // Spells like Disengage are allowed only in combat + if (!m_caster->isInCombat() && m_spellInfo->HasAttribute(SPELL_ATTR_STOP_ATTACK_TARGET) && m_spellInfo->HasAttribute(SPELL_ATTR_EX2_UNK26)) + return SPELL_FAILED_CASTER_AURASTATE; + + SpellClassOptionsEntry const* classOptions = m_spellInfo->GetSpellClassOptions(); + + if(Unit *target = m_targets.getUnitTarget()) + { + // target state requirements (not allowed state), apply to self also + if(auraRestrictions && auraRestrictions->TargetAuraStateNot && target->HasAuraState(AuraState(auraRestrictions->TargetAuraStateNot))) + return SPELL_FAILED_TARGET_AURASTATE; + + if (!m_IsTriggeredSpell && IsDeathOnlySpell(m_spellInfo) && target->isAlive()) + return SPELL_FAILED_TARGET_NOT_DEAD; + + // Target aura req check if need + if(auraRestrictions && auraRestrictions->targetAuraSpell && !target->HasAura(auraRestrictions->targetAuraSpell)) + return SPELL_FAILED_CASTER_AURASTATE; + + if(auraRestrictions && auraRestrictions->excludeTargetAuraSpell) + { + // Special cases of non existing auras handling + if (auraRestrictions->excludeTargetAuraSpell == 61988) + { + // Avenging Wrath Marker + if (target->HasAura(61987)) + return SPELL_FAILED_CASTER_AURASTATE; + } + else if (target->HasAura(auraRestrictions->excludeTargetAuraSpell)) + return SPELL_FAILED_CASTER_AURASTATE; + } + + // totem immunity for channeled spells(needs to be before spell cast) + // spell attribs for player channeled spells + if (m_spellInfo->HasAttribute(SPELL_ATTR_EX_UNK14) + && m_spellInfo->HasAttribute(SPELL_ATTR_EX5_UNK13) + && target->GetTypeId() == TYPEID_UNIT + && ((Creature*)target)->IsTotem()) + return SPELL_FAILED_IMMUNE; + + bool non_caster_target = target != m_caster && !IsSpellWithCasterSourceTargetsOnly(m_spellInfo); + + if (non_caster_target) + { + // target state requirements (apply to non-self only), to allow cast affects to self like Dirty Deeds + if (auraRestrictions && auraRestrictions->TargetAuraState && !target->HasAuraStateForCaster(AuraState(auraRestrictions->TargetAuraState), m_caster->GetObjectGuid()) && + !m_caster->IsIgnoreUnitState(m_spellInfo, auraRestrictions->TargetAuraState == AURA_STATE_FROZEN ? IGNORE_UNIT_TARGET_NON_FROZEN : IGNORE_UNIT_TARGET_STATE)) + return SPELL_FAILED_TARGET_AURASTATE; + + // Not allow casting on flying player + if (target->IsTaxiFlying()) + { + switch (m_spellInfo->Id) + { + // Except some spells from Taxi Flying cast + case 36573: // Vision Guide + case 42316: // Alcaz Survey Credit + case 42385: // Alcaz Survey Aura + break; + default: + return SPELL_FAILED_BAD_TARGETS; + } + } + + if (!m_IsTriggeredSpell && !m_spellInfo->HasAttribute(SPELL_ATTR_EX2_IGNORE_LOS) && VMAP::VMapFactory::checkSpellForLoS(m_spellInfo->Id) && !m_caster->IsWithinLOSInMap(target)) + return SPELL_FAILED_LINE_OF_SIGHT; + + // auto selection spell rank implemented in WorldSession::HandleCastSpellOpcode + // this case can be triggered if rank not found (too low-level target for first rank) + if (m_caster->GetTypeId() == TYPEID_PLAYER && !m_CastItem && !m_IsTriggeredSpell) + { + // spell expected to be auto-downranking in cast handle, so must be same + if (m_spellInfo != sSpellMgr.SelectAuraRankForLevel(m_spellInfo, target->getLevel())) + return SPELL_FAILED_LOWLEVEL; + } + + if (strict && m_spellInfo->HasAttribute(SPELL_ATTR_EX3_TARGET_ONLY_PLAYER) && target->GetTypeId() != TYPEID_PLAYER && !IsAreaOfEffectSpell(m_spellInfo)) + return SPELL_FAILED_BAD_TARGETS; + } + else if (m_caster == target) + { + if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->IsInWorld()) + { + // Additional check for some spells + // If 0 spell effect empty - client not send target data (need use selection) + // TODO: check it on next client version + if (m_targets.m_targetMask == TARGET_FLAG_SELF && + m_spellInfo->GetEffectImplicitTargetAByIndex(EFFECT_INDEX_1) == TARGET_CHAIN_DAMAGE) + { + target = m_caster->GetMap()->GetUnit(((Player*)m_caster)->GetSelectionGuid()); + if (!target) + return SPELL_FAILED_BAD_TARGETS; + + m_targets.setUnitTarget(target); + } + } + + // Some special spells with non-caster only mode + + // Fire Shield + if (classOptions && classOptions->SpellFamilyName == SPELLFAMILY_WARLOCK && + m_spellInfo->SpellIconID == 16) + return SPELL_FAILED_BAD_TARGETS; + + // Focus Magic (main spell) + if (m_spellInfo->Id == 54646) + return SPELL_FAILED_BAD_TARGETS; + + // Lay on Hands (self cast) + if (classOptions && classOptions->SpellFamilyName == SPELLFAMILY_PALADIN && + classOptions->SpellFamilyFlags & UI64LIT(0x0000000000008000)) + { + if (target->HasAura(25771)) // Forbearance + return SPELL_FAILED_CASTER_AURASTATE; + if (target->HasAura(61987)) // Avenging Wrath Marker + return SPELL_FAILED_CASTER_AURASTATE; + } + } + + // check pet presents + for (int j = 0; j < MAX_EFFECT_INDEX; ++j) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(j)); + if(!spellEffect) + continue; + + if(spellEffect->EffectImplicitTargetA == TARGET_PET) + { + Pet* pet = m_caster->GetPet(); + if (!pet) + { + if (m_triggeredByAuraSpell) // not report pet not existence for triggered spells + return SPELL_FAILED_DONT_REPORT; + else + return SPELL_FAILED_NO_PET; + } + else if (!pet->isAlive()) + return SPELL_FAILED_TARGETS_DEAD; + break; + } + } + + // check creature type + // ignore self casts (including area casts when caster selected as target) + if (non_caster_target) + { + if (!CheckTargetCreatureType(target)) + { + if (target->GetTypeId() == TYPEID_PLAYER) + return SPELL_FAILED_TARGET_IS_PLAYER; + else + return SPELL_FAILED_BAD_TARGETS; + } + + // simple cases + bool explicit_target_mode = false; + bool target_hostile = false; + bool target_hostile_checked = false; + bool target_friendly = false; + bool target_friendly_checked = false; + for (int k = 0; k < MAX_EFFECT_INDEX; ++k) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(k)); + if(!spellEffect) + continue; + if (IsExplicitPositiveTarget(spellEffect->EffectImplicitTargetA)) + { + if (!target_hostile_checked) + { + target_hostile_checked = true; + target_hostile = m_caster->IsHostileTo(target); + } + + if (target_hostile) + return SPELL_FAILED_BAD_TARGETS; + + explicit_target_mode = true; + } + else if (IsExplicitNegativeTarget(spellEffect->EffectImplicitTargetA)) + { + if (!target_friendly_checked) + { + target_friendly_checked = true; + target_friendly = m_caster->IsFriendlyTo(target); + } + + if (target_friendly) + return SPELL_FAILED_BAD_TARGETS; + + explicit_target_mode = true; + } + } + // TODO: this check can be applied and for player to prevent cheating when IsPositiveSpell will return always correct result. + // check target for pet/charmed casts (not self targeted), self targeted cast used for area effects and etc + if (!explicit_target_mode && m_caster->GetTypeId() == TYPEID_UNIT && m_caster->GetCharmerOrOwnerGuid()) + { + // check correctness positive/negative cast target (pet cast real check and cheating check) + if (IsPositiveSpell(m_spellInfo->Id)) + { + if (!target_hostile_checked) + { + target_hostile_checked = true; + target_hostile = m_caster->IsHostileTo(target); + } + + if (target_hostile) + return SPELL_FAILED_BAD_TARGETS; + } + else + { + if (!target_friendly_checked) + { + target_friendly_checked = true; + target_friendly = m_caster->IsFriendlyTo(target); + } + + if (target_friendly) + return SPELL_FAILED_BAD_TARGETS; + } + } + } + + if (IsPositiveSpell(m_spellInfo->Id)) + if (target->IsImmuneToSpell(m_spellInfo, target == m_caster)) + return SPELL_FAILED_TARGET_AURASTATE; + + // Must be behind the target. + if (m_spellInfo->AttributesEx2 == SPELL_ATTR_EX2_UNK20 && m_spellInfo->HasAttribute(SPELL_ATTR_EX_UNK9) && target->HasInArc(M_PI_F, m_caster)) + { + // Exclusion for Pounce: Facing Limitation was removed in 2.0.1, but it still uses the same, old Ex-Flags + // Exclusion for Mutilate:Facing Limitation was removed in 2.0.1 and 3.0.3, but they still use the same, old Ex-Flags + // Exclusion for Throw: Facing limitation was added in 3.2.x, but that shouldn't be + if (!m_spellInfo->IsFitToFamily(SPELLFAMILY_DRUID, UI64LIT(0x0000000000020000)) && + !m_spellInfo->IsFitToFamily(SPELLFAMILY_ROGUE, UI64LIT(0x0020000000000000)) && + m_spellInfo->Id != 2764) + { + SendInterrupted(2); + return SPELL_FAILED_NOT_BEHIND; + } + } + + // Target must be facing you. + if ((m_spellInfo->Attributes == (SPELL_ATTR_UNK4 | SPELL_ATTR_NOT_SHAPESHIFT | SPELL_ATTR_UNK18 | SPELL_ATTR_STOP_ATTACK_TARGET)) && !target->HasInArc(M_PI_F, m_caster)) + { + SendInterrupted(2); + return SPELL_FAILED_NOT_INFRONT; + } + + // check if target is in combat + if (non_caster_target && m_spellInfo->HasAttribute(SPELL_ATTR_EX_NOT_IN_COMBAT_TARGET) && target->isInCombat()) + return SPELL_FAILED_TARGET_AFFECTING_COMBAT; + } + // zone check + uint32 zone, area; + m_caster->GetZoneAndAreaId(zone, area); + + SpellCastResult locRes = sSpellMgr.GetSpellAllowedInLocationError(m_spellInfo, m_caster->GetMapId(), zone, area, + m_caster->GetCharmerOrOwnerPlayerOrPlayerItself()); + if (locRes != SPELL_CAST_OK) + return locRes; + + // not let players cast spells at mount (and let do it to creatures) + if (m_caster->IsMounted() && m_caster->GetTypeId() == TYPEID_PLAYER && !m_IsTriggeredSpell && + !IsPassiveSpell(m_spellInfo) && !m_spellInfo->HasAttribute(SPELL_ATTR_CASTABLE_WHILE_MOUNTED)) + { + if (m_caster->IsTaxiFlying()) + return SPELL_FAILED_NOT_ON_TAXI; + else + return SPELL_FAILED_NOT_MOUNTED; + } + + // always (except passive spells) check items + if (!IsPassiveSpell(m_spellInfo)) + { + SpellCastResult castResult = CheckItems(); + if (castResult != SPELL_CAST_OK) + return castResult; + } + + // check spell focus object + if (m_spellInfo->GetRequiresSpellFocus()) + { + GameObject* ok = NULL; + MaNGOS::GameObjectFocusCheck go_check(m_caster, m_spellInfo->GetRequiresSpellFocus()); + MaNGOS::GameObjectSearcher checker(ok, go_check); + Cell::VisitGridObjects(m_caster, checker, m_caster->GetMap()->GetVisibilityDistance()); + + if (!ok) + return SPELL_FAILED_REQUIRES_SPELL_FOCUS; + + focusObject = ok; // game object found in range + } + + // Database based targets from spell_target_script + if (m_UniqueTargetInfo.empty()) // skip second CheckCast apply (for delayed spells for example) + { + for (int j = 0; j < MAX_EFFECT_INDEX; ++j) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(j)); + if(!spellEffect) + continue; + + if (spellEffect->EffectImplicitTargetA == TARGET_SCRIPT || + (spellEffect->EffectImplicitTargetB == TARGET_SCRIPT && spellEffect->EffectImplicitTargetA != TARGET_SELF) || + spellEffect->EffectImplicitTargetA == TARGET_SCRIPT_COORDINATES || + spellEffect->EffectImplicitTargetB == TARGET_SCRIPT_COORDINATES || + spellEffect->EffectImplicitTargetA == TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT) + { + + SQLMultiStorage::SQLMSIteratorBounds bounds = sSpellScriptTargetStorage.getBounds(m_spellInfo->Id); + + if (bounds.first == bounds.second) + { + if (spellEffect->EffectImplicitTargetA == TARGET_SCRIPT || spellEffect->EffectImplicitTargetB == TARGET_SCRIPT) + sLog.outErrorDb("Spell entry %u, effect %i has EffectImplicitTargetA/EffectImplicitTargetB = TARGET_SCRIPT, but creature are not defined in `spell_script_target`", m_spellInfo->Id, j); + + if (spellEffect->EffectImplicitTargetA == TARGET_SCRIPT_COORDINATES || spellEffect->EffectImplicitTargetB == TARGET_SCRIPT_COORDINATES) + sLog.outErrorDb("Spell entry %u, effect %i has EffectImplicitTargetA/EffectImplicitTargetB = TARGET_SCRIPT_COORDINATES, but gameobject or creature are not defined in `spell_script_target`", m_spellInfo->Id, j); + + if (spellEffect->EffectImplicitTargetA == TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT) + sLog.outErrorDb("Spell entry %u, effect %i has EffectImplicitTargetA/EffectImplicitTargetB = TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT, but gameobject are not defined in `spell_script_target`", m_spellInfo->Id, j); + } + + SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex); + float range = GetSpellMaxRange(srange); + + Creature* targetExplicit = NULL; // used for cases where a target is provided (by script for example) + Creature* creatureScriptTarget = NULL; + GameObject* goScriptTarget = NULL; + + for (SQLMultiStorage::SQLMultiSIterator i_spellST = bounds.first; i_spellST != bounds.second; ++i_spellST) + { + if (i_spellST->CanNotHitWithSpellEffect(SpellEffectIndex(j))) + continue; + + switch (i_spellST->type) + { + case SPELL_TARGET_TYPE_GAMEOBJECT: + { + GameObject* p_GameObject = NULL; + + if (i_spellST->targetEntry) + { + MaNGOS::NearestGameObjectEntryInObjectRangeCheck go_check(*m_caster, i_spellST->targetEntry, range); + MaNGOS::GameObjectLastSearcher checker(p_GameObject, go_check); + Cell::VisitGridObjects(m_caster, checker, range); + + if (p_GameObject) + { + // remember found target and range, next attempt will find more near target with another entry + creatureScriptTarget = NULL; + goScriptTarget = p_GameObject; + range = go_check.GetLastRange(); + } + } + else if (focusObject) // Focus Object + { + float frange = m_caster->GetDistance(focusObject); + if (range >= frange) + { + creatureScriptTarget = NULL; + goScriptTarget = focusObject; + range = frange; + } + } + break; + } + case SPELL_TARGET_TYPE_CREATURE: + case SPELL_TARGET_TYPE_DEAD: + default: + { + Creature* p_Creature = NULL; + + // check if explicit target is provided and check it up against database valid target entry/state + if (Unit* pTarget = m_targets.getUnitTarget()) + { + if (pTarget->GetTypeId() == TYPEID_UNIT && pTarget->GetEntry() == i_spellST->targetEntry) + { + if (i_spellST->type == SPELL_TARGET_TYPE_DEAD && ((Creature*)pTarget)->IsCorpse()) + { + // always use spellMaxRange, in case GetLastRange returned different in a previous pass + if (pTarget->IsWithinDistInMap(m_caster, GetSpellMaxRange(srange))) + targetExplicit = (Creature*)pTarget; + } + else if (i_spellST->type == SPELL_TARGET_TYPE_CREATURE && pTarget->isAlive()) + { + // always use spellMaxRange, in case GetLastRange returned different in a previous pass + if (pTarget->IsWithinDistInMap(m_caster, GetSpellMaxRange(srange))) + targetExplicit = (Creature*)pTarget; + } + } + } + + // no target provided or it was not valid, so use closest in range + if (!targetExplicit) + { + MaNGOS::NearestCreatureEntryWithLiveStateInObjectRangeCheck u_check(*m_caster, i_spellST->targetEntry, i_spellST->type != SPELL_TARGET_TYPE_DEAD, i_spellST->type == SPELL_TARGET_TYPE_DEAD, range); + MaNGOS::CreatureLastSearcher searcher(p_Creature, u_check); + + // Visit all, need to find also Pet* objects + Cell::VisitAllObjects(m_caster, searcher, range); + + range = u_check.GetLastRange(); + } + + // always prefer provided target if it's valid + if (targetExplicit) + creatureScriptTarget = targetExplicit; + else if (p_Creature) + creatureScriptTarget = p_Creature; + + if (creatureScriptTarget) + goScriptTarget = NULL; + + break; + } + } + } + + if (creatureScriptTarget) + { + // store coordinates for TARGET_SCRIPT_COORDINATES + if (spellEffect->EffectImplicitTargetA == TARGET_SCRIPT_COORDINATES || + spellEffect->EffectImplicitTargetB == TARGET_SCRIPT_COORDINATES) + { + m_targets.setDestination(creatureScriptTarget->GetPositionX(), creatureScriptTarget->GetPositionY(), creatureScriptTarget->GetPositionZ()); + + if (spellEffect->EffectImplicitTargetA == TARGET_SCRIPT_COORDINATES && spellEffect->Effect != SPELL_EFFECT_PERSISTENT_AREA_AURA) + AddUnitTarget(creatureScriptTarget, SpellEffectIndex(j)); + } + // store explicit target for TARGET_SCRIPT + else + { + if (spellEffect->EffectImplicitTargetA == TARGET_SCRIPT || + spellEffect->EffectImplicitTargetB == TARGET_SCRIPT) + AddUnitTarget(creatureScriptTarget, SpellEffectIndex(j)); + } + } + else if (goScriptTarget) + { + // store coordinates for TARGET_SCRIPT_COORDINATES + if (spellEffect->EffectImplicitTargetA == TARGET_SCRIPT_COORDINATES || + spellEffect->EffectImplicitTargetB == TARGET_SCRIPT_COORDINATES) + { + m_targets.setDestination(goScriptTarget->GetPositionX(), goScriptTarget->GetPositionY(), goScriptTarget->GetPositionZ()); + + if (spellEffect->EffectImplicitTargetA == TARGET_SCRIPT_COORDINATES && spellEffect->Effect != SPELL_EFFECT_PERSISTENT_AREA_AURA) + AddGOTarget(goScriptTarget, SpellEffectIndex(j)); + } + // store explicit target for TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT + else + { + if (spellEffect->EffectImplicitTargetA == TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT || + spellEffect->EffectImplicitTargetB == TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT) + AddGOTarget(goScriptTarget, SpellEffectIndex(j)); + } + } + // Missing DB Entry or targets for this spellEffect. + else + { + /* For TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT makes DB targets optional not required for now + * TODO: Makes more research for this target type + */ + if (spellEffect->EffectImplicitTargetA != TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT) + { + // not report target not existence for triggered spells + if (m_triggeredByAuraSpell || m_IsTriggeredSpell) + return SPELL_FAILED_DONT_REPORT; + else + return SPELL_FAILED_BAD_TARGETS; + } + } + } + } + } + + if (!m_IsTriggeredSpell) + { + SpellCastResult castResult = CheckRange(strict); + if (castResult != SPELL_CAST_OK) + return castResult; + } + + { + SpellCastResult castResult = CheckPower(); + if (castResult != SPELL_CAST_OK) + return castResult; + } + + if (!m_IsTriggeredSpell) // triggered spell not affected by stun/etc + { + SpellCastResult castResult = CheckCasterAuras(); + if (castResult != SPELL_CAST_OK) + return castResult; + } + + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(i)); + if(!spellEffect) + continue; + // for effects of spells that have only one target + switch(spellEffect->Effect) + { + case SPELL_EFFECT_INSTAKILL: + // Death Pact + if (m_spellInfo->Id == 48743) + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return SPELL_FAILED_ERROR; + + if (!((Player*)m_caster)->GetSelectionGuid()) + return SPELL_FAILED_BAD_IMPLICIT_TARGETS; + Pet* target = m_caster->GetMap()->GetPet(((Player*)m_caster)->GetSelectionGuid()); + + // alive + if (!target || target->isDead()) + return SPELL_FAILED_BAD_IMPLICIT_TARGETS; + // undead + if (target->GetCreatureType() != CREATURE_TYPE_UNDEAD) + return SPELL_FAILED_BAD_IMPLICIT_TARGETS; + // owned + if (target->GetOwnerGuid() != m_caster->GetObjectGuid()) + return SPELL_FAILED_BAD_IMPLICIT_TARGETS; + + float dist = GetSpellRadius(sSpellRadiusStore.LookupEntry(spellEffect->GetRadiusIndex())); + if (!target->IsWithinDistInMap(m_caster,dist)) + return SPELL_FAILED_OUT_OF_RANGE; + + // will set in target selection code + } + break; + case SPELL_EFFECT_DUMMY: + { + if (m_spellInfo->Id == 51582) // Rocket Boots Engaged + { + if (m_caster->IsInWater()) + return SPELL_FAILED_ONLY_ABOVEWATER; + } + else if (m_spellInfo->Id == 51690) // Killing Spree + { + UnitList targets; + + float radius = GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex)); + + MaNGOS::AnyUnfriendlyVisibleUnitInObjectRangeCheck unitCheck(m_caster, m_caster, radius); + MaNGOS::UnitListSearcher checker(targets, unitCheck); + Cell::VisitAllObjects(m_caster, checker, radius); + + if (targets.empty()) + return SPELL_FAILED_OUT_OF_RANGE; + } + else if (m_spellInfo->Id == 68996) // Two forms + { + if (m_caster->isInCombat()) + return SPELL_FAILED_AFFECTING_COMBAT; + } + else if (m_spellInfo->SpellIconID == 156) // Holy Shock + { + // spell different for friends and enemies + // hart version required facing + if (m_targets.getUnitTarget() && !m_caster->IsFriendlyTo(m_targets.getUnitTarget()) && !m_caster->HasInArc(M_PI_F, m_targets.getUnitTarget())) + return SPELL_FAILED_UNIT_NOT_INFRONT; + } + // Fire Nova + if (m_spellInfo->GetSpellFamilyName() == SPELLFAMILY_SHAMAN && m_spellInfo->SpellIconID == 33) + { + // fire totems slot + if (!m_caster->GetTotemGuid(TOTEM_SLOT_FIRE)) + return SPELL_FAILED_TOTEMS; + } + break; + } + case SPELL_EFFECT_DISTRACT: // All nearby enemies must not be in combat + { + if (m_targets.m_targetMask & (TARGET_FLAG_DEST_LOCATION | TARGET_FLAG_SOURCE_LOCATION)) + { + UnitList targetsCombat; + float radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(spellEffect->GetRadiusIndex())); + + FillAreaTargets(targetsCombat, radius, PUSH_DEST_CENTER, SPELL_TARGETS_AOE_DAMAGE); + + if (targetsCombat.empty()) + break; + + for (UnitList::iterator itr = targetsCombat.begin(); itr != targetsCombat.end(); ++itr) + if ((*itr)->isInCombat()) + return SPELL_FAILED_TARGET_IN_COMBAT; + } + break; + } + case SPELL_EFFECT_SCHOOL_DAMAGE: + { + // Hammer of Wrath + if (m_spellInfo->SpellVisual[0] == 7250) + { + if (!m_targets.getUnitTarget()) + return SPELL_FAILED_BAD_IMPLICIT_TARGETS; + + if (m_targets.getUnitTarget()->GetHealth() > m_targets.getUnitTarget()->GetMaxHealth() * 0.2) + return SPELL_FAILED_BAD_TARGETS; + } + break; + } + case SPELL_EFFECT_TAMECREATURE: + { + // Spell can be triggered, we need to check original caster prior to caster + Unit* caster = GetAffectiveCaster(); + if (!caster || caster->GetTypeId() != TYPEID_PLAYER || + !m_targets.getUnitTarget() || + m_targets.getUnitTarget()->GetTypeId() == TYPEID_PLAYER) + return SPELL_FAILED_BAD_TARGETS; + + Player* plrCaster = (Player*)caster; + + bool gmmode = m_triggeredBySpellInfo == NULL; + + if (gmmode && !ChatHandler(plrCaster).FindCommand("npc tame")) + { + plrCaster->SendPetTameFailure(PETTAME_UNKNOWNERROR); + return SPELL_FAILED_DONT_REPORT; + } + + if (plrCaster->getClass() != CLASS_HUNTER && !gmmode) + { + plrCaster->SendPetTameFailure(PETTAME_UNITSCANTTAME); + return SPELL_FAILED_DONT_REPORT; + } + + Creature* target = (Creature*)m_targets.getUnitTarget(); + + if (target->IsPet() || target->isCharmed()) + { + plrCaster->SendPetTameFailure(PETTAME_CREATUREALREADYOWNED); + return SPELL_FAILED_DONT_REPORT; + } + + if (target->getLevel() > plrCaster->getLevel() && !gmmode) + { + plrCaster->SendPetTameFailure(PETTAME_TOOHIGHLEVEL); + return SPELL_FAILED_DONT_REPORT; + } + + if (target->GetCreatureInfo()->IsExotic() && !plrCaster->CanTameExoticPets() && !gmmode) + { + plrCaster->SendPetTameFailure(PETTAME_CANTCONTROLEXOTIC); + return SPELL_FAILED_DONT_REPORT; + } + + if (!target->GetCreatureInfo()->isTameable(plrCaster->CanTameExoticPets())) + { + plrCaster->SendPetTameFailure(PETTAME_NOTTAMEABLE); + return SPELL_FAILED_DONT_REPORT; + } + + if (plrCaster->GetPetGuid() || plrCaster->GetCharmGuid()) + { + plrCaster->SendPetTameFailure(PETTAME_ANOTHERSUMMONACTIVE); + return SPELL_FAILED_DONT_REPORT; + } + + break; + } + case SPELL_EFFECT_LEARN_SPELL: + { + if(spellEffect->EffectImplicitTargetA != TARGET_PET) + break; + + Pet* pet = m_caster->GetPet(); + + if (!pet) + return SPELL_FAILED_NO_PET; + + SpellEntry const *learn_spellproto = sSpellStore.LookupEntry(spellEffect->EffectTriggerSpell); + + if (!learn_spellproto) + return SPELL_FAILED_NOT_KNOWN; + + if(m_spellInfo->GetSpellLevel() > pet->getLevel()) + return SPELL_FAILED_LOWLEVEL; + + break; + } + case SPELL_EFFECT_LEARN_PET_SPELL: + { + Pet* pet = m_caster->GetPet(); + + if (!pet) + return SPELL_FAILED_NO_PET; + + SpellEntry const *learn_spellproto = sSpellStore.LookupEntry(spellEffect->EffectTriggerSpell); + if (!learn_spellproto) + return SPELL_FAILED_NOT_KNOWN; + + if(m_spellInfo->GetSpellLevel() > pet->getLevel()) + return SPELL_FAILED_LOWLEVEL; + + break; + } + case SPELL_EFFECT_APPLY_GLYPH: + { + uint32 glyphId = spellEffect->EffectMiscValue; + if(GlyphPropertiesEntry const *gp = sGlyphPropertiesStore.LookupEntry(glyphId)) + if(m_caster->HasAura(gp->SpellId)) + return SPELL_FAILED_UNIQUE_GLYPH; + break; + } + case SPELL_EFFECT_FEED_PET: + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return SPELL_FAILED_BAD_TARGETS; + + Item* foodItem = m_targets.getItemTarget(); + if (!foodItem) + return SPELL_FAILED_BAD_TARGETS; + + Pet* pet = m_caster->GetPet(); + + if (!pet) + return SPELL_FAILED_NO_PET; + + if (!pet->HaveInDiet(foodItem->GetProto())) + return SPELL_FAILED_WRONG_PET_FOOD; + + if (!pet->GetCurrentFoodBenefitLevel(foodItem->GetProto()->ItemLevel)) + return SPELL_FAILED_FOOD_LOWLEVEL; + + if (pet->isInCombat()) + return SPELL_FAILED_AFFECTING_COMBAT; + + break; + } + case SPELL_EFFECT_POWER_BURN: + case SPELL_EFFECT_POWER_DRAIN: + { + // Can be area effect, Check only for players and not check if target - caster (spell can have multiply drain/burn effects) + if (m_caster->GetTypeId() == TYPEID_PLAYER) + if (Unit* target = m_targets.getUnitTarget()) + if (target != m_caster && int32(target->getPowerType()) != spellEffect->EffectMiscValue) + return SPELL_FAILED_BAD_TARGETS; + break; + } + case SPELL_EFFECT_CHARGE: + { + if (m_caster->hasUnitState(UNIT_STAT_ROOT)) + return SPELL_FAILED_ROOTED; + + break; + } + case SPELL_EFFECT_SKINNING: + { + if (m_caster->GetTypeId() != TYPEID_PLAYER || !m_targets.getUnitTarget() || m_targets.getUnitTarget()->GetTypeId() != TYPEID_UNIT) + return SPELL_FAILED_BAD_TARGETS; + + if (!m_targets.getUnitTarget()->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE)) + return SPELL_FAILED_TARGET_UNSKINNABLE; + + Creature* creature = (Creature*)m_targets.getUnitTarget(); + if (creature->GetCreatureType() != CREATURE_TYPE_CRITTER && (!creature->lootForBody || creature->lootForSkin || !creature->loot.empty())) + { + return SPELL_FAILED_TARGET_NOT_LOOTED; + } + + uint32 skill = creature->GetCreatureInfo()->GetRequiredLootSkill(); + + int32 skillValue = ((Player*)m_caster)->GetSkillValue(skill); + int32 TargetLevel = m_targets.getUnitTarget()->getLevel(); + int32 ReqValue = (skillValue < 100 ? (TargetLevel - 10) * 10 : TargetLevel * 5); + if (ReqValue > skillValue) + return SPELL_FAILED_LOW_CASTLEVEL; + + // chance for fail at orange skinning attempt + if ((m_selfContainer && (*m_selfContainer) == this) && + skillValue < sWorld.GetConfigMaxSkillValue() && + (ReqValue < 0 ? 0 : ReqValue) > irand(skillValue - 25, skillValue + 37)) + return SPELL_FAILED_TRY_AGAIN; + + break; + } + case SPELL_EFFECT_OPEN_LOCK: + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) // only players can open locks, gather etc. + return SPELL_FAILED_BAD_TARGETS; + + // we need a go target in case of TARGET_GAMEOBJECT (for other targets acceptable GO and items) + if (spellEffect->EffectImplicitTargetA == TARGET_GAMEOBJECT) + { + if (!m_targets.getGOTarget()) + return SPELL_FAILED_BAD_TARGETS; + } + + // get the lock entry + uint32 lockId = 0; + if (GameObject* go = m_targets.getGOTarget()) + { + // In BattleGround players can use only flags and banners + if (((Player*)m_caster)->InBattleGround() && + !((Player*)m_caster)->CanUseBattleGroundObject()) + return SPELL_FAILED_TRY_AGAIN; + + lockId = go->GetGOInfo()->GetLockId(); + if (!lockId) + return SPELL_FAILED_ALREADY_OPEN; + } + else if (Item* item = m_targets.getItemTarget()) + { + // not own (trade?) + if (item->GetOwner() != m_caster) + return SPELL_FAILED_ITEM_GONE; + + lockId = item->GetProto()->LockID; + + // if already unlocked + if (!lockId || item->HasFlag(ITEM_FIELD_FLAGS, ITEM_DYNFLAG_UNLOCKED)) + return SPELL_FAILED_ALREADY_OPEN; + } + else + return SPELL_FAILED_BAD_TARGETS; + + SkillType skillId = SKILL_NONE; + int32 reqSkillValue = 0; + int32 skillValue = 0; + + // check lock compatibility + SpellCastResult res = CanOpenLock(SpellEffectIndex(i), lockId, skillId, reqSkillValue, skillValue); + if (res != SPELL_CAST_OK) + return res; + + // chance for fail at orange mining/herb/LockPicking gathering attempt + // second check prevent fail at rechecks + if (skillId != SKILL_NONE && (!m_selfContainer || ((*m_selfContainer) != this))) + { + bool canFailAtMax = skillId != SKILL_HERBALISM && skillId != SKILL_MINING; + + // chance for failure in orange gather / lockpick (gathering skill can't fail at maxskill) + if ((canFailAtMax || skillValue < sWorld.GetConfigMaxSkillValue()) && reqSkillValue > irand(skillValue - 25, skillValue + 37)) + return SPELL_FAILED_TRY_AGAIN; + } + break; + } + case SPELL_EFFECT_SUMMON_DEAD_PET: + { + Creature* pet = m_caster->GetPet(); + if (!pet) + return SPELL_FAILED_NO_PET; + + if (pet->isAlive()) + return SPELL_FAILED_ALREADY_HAVE_SUMMON; + + break; + } + // This is generic summon effect + case SPELL_EFFECT_SUMMON: + { + if (SummonPropertiesEntry const *summon_prop = sSummonPropertiesStore.LookupEntry(spellEffect->EffectMiscValueB)) + { + if (summon_prop->Group == SUMMON_PROP_GROUP_PETS) + { + if (m_caster->GetPetGuid()) + return SPELL_FAILED_ALREADY_HAVE_SUMMON; + + if (m_caster->GetCharmGuid()) + return SPELL_FAILED_ALREADY_HAVE_CHARM; + } + } + + break; + } + case SPELL_EFFECT_SUMMON_PET: + { + if (m_caster->GetPetGuid()) // let warlock do a replacement summon + { + + Pet* pet = ((Player*)m_caster)->GetPet(); + + if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->getClass() == CLASS_WARLOCK) + { + if (strict) // Summoning Disorientation, trigger pet stun (cast by pet so it doesn't attack player) + pet->CastSpell(pet, 32752, true, NULL, NULL, pet->GetObjectGuid()); + } + else + return SPELL_FAILED_ALREADY_HAVE_SUMMON; + } + + if (m_caster->GetCharmGuid()) + return SPELL_FAILED_ALREADY_HAVE_CHARM; + + break; + } + case SPELL_EFFECT_SUMMON_PLAYER: + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return SPELL_FAILED_BAD_TARGETS; + if (!((Player*)m_caster)->GetSelectionGuid()) + return SPELL_FAILED_BAD_TARGETS; + + Player* target = sObjectMgr.GetPlayer(((Player*)m_caster)->GetSelectionGuid()); + if (!target || ((Player*)m_caster) == target || !target->IsInSameRaidWith((Player*)m_caster)) + return SPELL_FAILED_BAD_TARGETS; + + // check if our map is dungeon + if (sMapStore.LookupEntry(m_caster->GetMapId())->IsDungeon()) + { + InstanceTemplate const* instance = ObjectMgr::GetInstanceTemplate(m_caster->GetMapId()); + if (!instance) + return SPELL_FAILED_TARGET_NOT_IN_INSTANCE; + if (instance->levelMin > target->getLevel()) + return SPELL_FAILED_LOWLEVEL; + if (instance->levelMax && instance->levelMax < target->getLevel()) + return SPELL_FAILED_HIGHLEVEL; + } + break; + } + case SPELL_EFFECT_LEAP: + case SPELL_EFFECT_TELEPORT_UNITS_FACE_CASTER: + { + float dis = GetSpellRadius(sSpellRadiusStore.LookupEntry(spellEffect->GetRadiusIndex())); + float fx = m_caster->GetPositionX() + dis * cos(m_caster->GetOrientation()); + float fy = m_caster->GetPositionY() + dis * sin(m_caster->GetOrientation()); + // teleport a bit above terrain level to avoid falling below it + float fz = m_caster->GetMap()->GetHeight(m_caster->GetPhaseMask(), fx, fy, m_caster->GetPositionZ()); + if (fz <= INVALID_HEIGHT) // note: this also will prevent use effect in instances without vmaps height enabled + return SPELL_FAILED_TRY_AGAIN; + + float caster_pos_z = m_caster->GetPositionZ(); + // Control the caster to not climb or drop when +-fz > 8 + if (!(fz <= caster_pos_z + 8 && fz >= caster_pos_z - 8)) + return SPELL_FAILED_TRY_AGAIN; + + // not allow use this effect at battleground until battleground start + if (m_caster->GetTypeId() == TYPEID_PLAYER) + if (BattleGround const* bg = ((Player*)m_caster)->GetBattleGround()) + if (bg->GetStatus() != STATUS_IN_PROGRESS) + return SPELL_FAILED_TRY_AGAIN; + break; + } + case SPELL_EFFECT_STEAL_BENEFICIAL_BUFF: + { + if (m_targets.getUnitTarget() == m_caster) + return SPELL_FAILED_BAD_TARGETS; + break; + } + default: break; + } + } + + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(i)); + if(!spellEffect) + continue; + + // Do not check in case of junk in DBC + if (!IsAuraApplyEffect(m_spellInfo, SpellEffectIndex(i))) + continue; + + // Possible Unit-target for the spell + Unit* expectedTarget = GetPrefilledUnitTargetOrUnitTarget(SpellEffectIndex(i)); + + switch (spellEffect->EffectApplyAuraName) + { + case SPELL_AURA_DUMMY: + { + // custom check + switch (m_spellInfo->Id) + { + case 34026: // Kill Command + if (!m_caster->GetPet()) + return SPELL_FAILED_NO_PET; + break; + case 61336: // Survival Instincts + if (m_caster->GetTypeId() != TYPEID_PLAYER || !((Player*)m_caster)->IsInFeralForm()) + return SPELL_FAILED_ONLY_SHAPESHIFT; + break; + default: + break; + } + break; + } + case SPELL_AURA_MOD_POSSESS: + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return SPELL_FAILED_UNKNOWN; + + if (expectedTarget == m_caster) + return SPELL_FAILED_BAD_TARGETS; + + if (m_caster->GetPetGuid()) + return SPELL_FAILED_ALREADY_HAVE_SUMMON; + + if (m_caster->GetCharmGuid()) + return SPELL_FAILED_ALREADY_HAVE_CHARM; + + if (m_caster->GetCharmerGuid()) + return SPELL_FAILED_CHARMED; + + if (!expectedTarget) + return SPELL_FAILED_BAD_IMPLICIT_TARGETS; + + if (expectedTarget->GetCharmerGuid()) + return SPELL_FAILED_CHARMED; + + if (int32(expectedTarget->getLevel()) > CalculateDamage(SpellEffectIndex(i), expectedTarget)) + return SPELL_FAILED_HIGHLEVEL; + + break; + } + case SPELL_AURA_MOD_CHARM: + { + if (expectedTarget == m_caster) + return SPELL_FAILED_BAD_TARGETS; + + if (m_caster->GetPetGuid()) + return SPELL_FAILED_ALREADY_HAVE_SUMMON; + + if (m_caster->GetCharmGuid()) + return SPELL_FAILED_ALREADY_HAVE_CHARM; + + if (m_caster->GetCharmerGuid()) + return SPELL_FAILED_CHARMED; + + if (!expectedTarget) + return SPELL_FAILED_BAD_IMPLICIT_TARGETS; + + if (expectedTarget->GetCharmerGuid()) + return SPELL_FAILED_CHARMED; + + if (int32(expectedTarget->getLevel()) > CalculateDamage(SpellEffectIndex(i), expectedTarget)) + return SPELL_FAILED_HIGHLEVEL; + + break; + } + case SPELL_AURA_MOD_POSSESS_PET: + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return SPELL_FAILED_UNKNOWN; + + if (m_caster->GetCharmGuid()) + return SPELL_FAILED_ALREADY_HAVE_CHARM; + + if (m_caster->GetCharmerGuid()) + return SPELL_FAILED_CHARMED; + + Pet* pet = m_caster->GetPet(); + if (!pet) + return SPELL_FAILED_NO_PET; + + if (pet->GetCharmerGuid()) + return SPELL_FAILED_CHARMED; + + break; + } + case SPELL_AURA_MOUNTED: + { + + if (m_caster->GetTypeId() == TYPEID_PLAYER && ((Player*)m_caster)->GetTransport()) + return SPELL_FAILED_NO_MOUNTS_ALLOWED; + + if (spellEffect->EffectMiscValueB && !m_caster->GetMountCapability(spellEffect->EffectMiscValueB)) + return SPELL_FAILED_NOT_HERE; + + // Ignore map check if spell have AreaId. AreaId already checked and this prevent special mount spells + if (m_caster->GetTypeId() == TYPEID_PLAYER && !sMapStore.LookupEntry(m_caster->GetMapId())->IsMountAllowed() && !m_IsTriggeredSpell && !m_spellInfo->GetAreaGroupId()) + return SPELL_FAILED_NO_MOUNTS_ALLOWED; + + if (m_caster->IsInDisallowedMountForm()) + return SPELL_FAILED_NOT_SHAPESHIFT; + + break; + } + case SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS: + { + if (!expectedTarget) + return SPELL_FAILED_BAD_IMPLICIT_TARGETS; + + // can be casted at non-friendly unit or own pet/charm + if (m_caster->IsFriendlyTo(expectedTarget)) + return SPELL_FAILED_TARGET_FRIENDLY; + + break; + } + case SPELL_AURA_FLY: + case SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED: + { + // not allow cast fly spells if not have req. skills (all spells is self target) + // allow always ghost flight spells + if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->isAlive()) + { + if (!((Player*)m_caster)->CanStartFlyInArea(m_caster->GetMapId(), zone, area)) + return m_IsTriggeredSpell ? SPELL_FAILED_DONT_REPORT : SPELL_FAILED_NOT_HERE; + } + break; + } + case SPELL_AURA_PERIODIC_MANA_LEECH: + { + if (!expectedTarget) + return SPELL_FAILED_BAD_IMPLICIT_TARGETS; + + if (m_caster->GetTypeId() != TYPEID_PLAYER || m_CastItem) + break; + + if (expectedTarget->getPowerType() != POWER_MANA) + return SPELL_FAILED_BAD_TARGETS; + + break; + } + case SPELL_AURA_CONTROL_VEHICLE: + { + if (m_caster->HasAuraType(SPELL_AURA_MOUNTED)) + return SPELL_FAILED_NOT_MOUNTED; + + if (!expectedTarget || !expectedTarget->IsVehicle()) + return SPELL_FAILED_BAD_TARGETS; + + // It is possible to change between vehicles that are boarded on each other + if (m_caster->IsBoarded() && m_caster->GetTransportInfo()->IsOnVehicle()) + { + // Check if trying to board a vehicle that is boarded on current transport + bool boardedOnEachOther = m_caster->GetTransportInfo()->HasOnBoard(expectedTarget); + // Check if trying to board a vehicle that has the current transport on board + if (!boardedOnEachOther) + boardedOnEachOther = expectedTarget->GetVehicleInfo()->HasOnBoard(m_caster); + + if (!boardedOnEachOther) + return SPELL_FAILED_NOT_ON_TRANSPORT; + } + + if (!expectedTarget->GetVehicleInfo()->CanBoard(m_caster)) + return SPELL_FAILED_BAD_TARGETS; + + break; + } + case SPELL_AURA_MIRROR_IMAGE: + { + if (!expectedTarget) + return SPELL_FAILED_BAD_TARGETS; + + // Target must be creature. TODO: Check if target can also be player + if (expectedTarget->GetTypeId() != TYPEID_UNIT) + return SPELL_FAILED_BAD_TARGETS; + + if (expectedTarget == m_caster) // Clone self can't be accepted + return SPELL_FAILED_BAD_TARGETS; + + // It is assumed that target can not be cloned if already cloned by same or other clone auras + if (expectedTarget->HasAuraType(SPELL_AURA_MIRROR_IMAGE)) + return SPELL_FAILED_BAD_TARGETS; + + break; + } + case SPELL_AURA_WORGEN_TRANSFORM: + { + if (!m_caster->HasWorgenForm()) + return m_IsTriggeredSpell ? SPELL_FAILED_DONT_REPORT : SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW; + break; + } + default: + break; + } + } + + // check trade slot case (last, for allow catch any another cast problems) + if (m_targets.m_targetMask & TARGET_FLAG_TRADE_ITEM) + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return SPELL_FAILED_NOT_TRADING; + + Player* pCaster = ((Player*)m_caster); + TradeData* my_trade = pCaster->GetTradeData(); + + if (!my_trade) + return SPELL_FAILED_NOT_TRADING; + + TradeSlots slot = TradeSlots(m_targets.getItemTargetGuid().GetRawValue()); + if (slot != TRADE_SLOT_NONTRADED) + return SPELL_FAILED_ITEM_NOT_READY; + + // if trade not complete then remember it in trade data + if (!my_trade->IsInAcceptProcess()) + { + // Spell will be casted at completing the trade. Silently ignore at this place + my_trade->SetSpell(m_spellInfo->Id, m_CastItem); + return SPELL_FAILED_DONT_REPORT; + } + } + + // all ok + return SPELL_CAST_OK; +} + +SpellCastResult Spell::CheckPetCast(Unit* target) +{ + if (!m_caster->isAlive()) + return SPELL_FAILED_CASTER_DEAD; + + if (m_caster->IsNonMeleeSpellCasted(false)) // prevent spellcast interruption by another spellcast + return SPELL_FAILED_SPELL_IN_PROGRESS; + if (m_caster->isInCombat() && IsNonCombatSpell(m_spellInfo)) + return SPELL_FAILED_AFFECTING_COMBAT; + + if (m_caster->GetTypeId() == TYPEID_UNIT && (((Creature*)m_caster)->IsPet() || m_caster->isCharmed())) + { + // dead owner (pets still alive when owners ressed?) + if (m_caster->GetCharmerOrOwner() && !m_caster->GetCharmerOrOwner()->isAlive()) + return SPELL_FAILED_CASTER_DEAD; + + if (!target && m_targets.getUnitTarget()) + target = m_targets.getUnitTarget(); + + bool need = false; + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(i)); + if(!spellEffect) + continue; + + if (spellEffect->EffectImplicitTargetA == TARGET_CHAIN_DAMAGE || + spellEffect->EffectImplicitTargetA == TARGET_SINGLE_FRIEND || + spellEffect->EffectImplicitTargetA == TARGET_SINGLE_FRIEND_2 || + spellEffect->EffectImplicitTargetA == TARGET_DUELVSPLAYER || + spellEffect->EffectImplicitTargetA == TARGET_SINGLE_PARTY || + spellEffect->EffectImplicitTargetA == TARGET_CURRENT_ENEMY_COORDINATES) + { + need = true; + if (!target) + return SPELL_FAILED_BAD_IMPLICIT_TARGETS; + break; + } + } + if (need) + m_targets.setUnitTarget(target); + + Unit* _target = m_targets.getUnitTarget(); + + // for target dead/target not valid + if (_target && m_targets.m_targetMask & TARGET_FLAG_UNIT) + { + if (!_target->isTargetableForAttack()) + return SPELL_FAILED_BAD_TARGETS; // guessed error + + if (IsPositiveSpell(m_spellInfo->Id)) + { + if (m_caster->IsHostileTo(_target)) + return SPELL_FAILED_BAD_TARGETS; + } + else + { + bool duelvsplayertar = false; + for (int j = 0; j < MAX_EFFECT_INDEX; ++j) + { + //TARGET_DUELVSPLAYER is positive AND negative + duelvsplayertar |= (m_spellInfo->GetEffectImplicitTargetAByIndex(SpellEffectIndex(j)) == TARGET_DUELVSPLAYER); + } + if (m_caster->IsFriendlyTo(target) && !duelvsplayertar) + { + return SPELL_FAILED_BAD_TARGETS; + } + } + } + // cooldown + if (((Creature*)m_caster)->HasSpellCooldown(m_spellInfo->Id)) + return SPELL_FAILED_NOT_READY; + } + + return CheckCast(true); +} + +SpellCastResult Spell::CheckCasterAuras() const +{ + // Flag drop spells totally immuned to caster auras + // FIXME: find more nice check for all totally immuned spells + // HasAttribute(SPELL_ATTR_EX3_UNK28) ? + if (m_spellInfo->Id == 23336 || // Alliance Flag Drop + m_spellInfo->Id == 23334 || // Horde Flag Drop + m_spellInfo->Id == 34991) // Summon Netherstorm Flag + return SPELL_CAST_OK; + + uint8 school_immune = 0; + uint32 mechanic_immune = 0; + uint32 dispel_immune = 0; + + // Check if the spell grants school or mechanic immunity. + // We use bitmasks so the loop is done only once and not on every aura check below. + if (m_spellInfo->HasAttribute(SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY)) + { + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(i)); + if(!spellEffect) + continue; + if (spellEffect->EffectApplyAuraName == SPELL_AURA_SCHOOL_IMMUNITY) + school_immune |= uint32(spellEffect->EffectMiscValue); + else if (spellEffect->EffectApplyAuraName == SPELL_AURA_MECHANIC_IMMUNITY) + mechanic_immune |= 1 << uint32(spellEffect->EffectMiscValue-1); + else if (spellEffect->EffectApplyAuraName == SPELL_AURA_MECHANIC_IMMUNITY_MASK) + mechanic_immune |= uint32(spellEffect->EffectMiscValue); + else if (spellEffect->EffectApplyAuraName == SPELL_AURA_DISPEL_IMMUNITY) + dispel_immune |= GetDispellMask(DispelType(spellEffect->EffectMiscValue)); + } + + // immune movement impairment and loss of control (spell data have special structure for mark this case) + if (IsSpellRemoveAllMovementAndControlLossEffects(m_spellInfo)) + mechanic_immune = IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK; + } + + // Check whether the cast should be prevented by any state you might have. + SpellCastResult prevented_reason = SPELL_CAST_OK; + bool spellUsableWhileStunned = m_spellInfo->HasAttribute(SPELL_ATTR_EX5_USABLE_WHILE_STUNNED); + + // Have to check if there is a stun aura. Otherwise will have problems with ghost aura apply while logging out + uint32 unitflag = m_caster->GetUInt32Value(UNIT_FIELD_FLAGS); // Get unit state + if (unitflag & UNIT_FLAG_STUNNED) + { + // Pain Suppression (have SPELL_ATTR_EX5_USABLE_WHILE_STUNNED that must be used only with glyph) + if (m_spellInfo->GetSpellFamilyName() == SPELLFAMILY_PRIEST && m_spellInfo->SpellIconID == 2178) + { + if (!m_caster->HasAura(63248)) // Glyph of Pain Suppression + spellUsableWhileStunned = false; + } + + // spell is usable while stunned, check if caster has only mechanic stun auras, another stun types must prevent cast spell + if (spellUsableWhileStunned) + { + bool is_stun_mechanic = true; + Unit::AuraList const& stunAuras = m_caster->GetAurasByType(SPELL_AURA_MOD_STUN); + for (Unit::AuraList::const_iterator itr = stunAuras.begin(); itr != stunAuras.end(); ++itr) + if (!(*itr)->HasMechanic(MECHANIC_STUN)) + { + is_stun_mechanic = false; + break; + } + if (!is_stun_mechanic) + prevented_reason = SPELL_FAILED_STUNNED; + } + else + prevented_reason = SPELL_FAILED_STUNNED; + } + else if (unitflag & UNIT_FLAG_CONFUSED && !m_spellInfo->HasAttribute(SPELL_ATTR_EX5_USABLE_WHILE_CONFUSED)) + prevented_reason = SPELL_FAILED_CONFUSED; + else if (unitflag & UNIT_FLAG_FLEEING && !m_spellInfo->HasAttribute(SPELL_ATTR_EX5_USABLE_WHILE_FEARED)) + prevented_reason = SPELL_FAILED_FLEEING; + else if (unitflag & UNIT_FLAG_SILENCED && m_spellInfo->GetPreventionType() == SPELL_PREVENTION_TYPE_SILENCE) + prevented_reason = SPELL_FAILED_SILENCED; + else if (unitflag & UNIT_FLAG_PACIFIED && m_spellInfo->GetPreventionType() == SPELL_PREVENTION_TYPE_PACIFY) + prevented_reason = SPELL_FAILED_PACIFIED; + else if (m_caster->HasAuraType(SPELL_AURA_ALLOW_ONLY_ABILITY)) + { + Unit::AuraList const& casingLimit = m_caster->GetAurasByType(SPELL_AURA_ALLOW_ONLY_ABILITY); + for (Unit::AuraList::const_iterator itr = casingLimit.begin(); itr != casingLimit.end(); ++itr) + { + if (!(*itr)->isAffectedOnSpell(m_spellInfo)) + { + prevented_reason = SPELL_FAILED_CASTER_AURASTATE; + break; + } + } + } + + // Attr must make flag drop spell totally immune from all effects + if (prevented_reason != SPELL_CAST_OK) + { + if (school_immune || mechanic_immune || dispel_immune) + { + // Checking auras is needed now, because you are prevented by some state but the spell grants immunity. + Unit::SpellAuraHolderMap const& auras = m_caster->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + SpellAuraHolder* holder = itr->second; + SpellEntry const* pEntry = holder->GetSpellProto(); + + if ((GetSpellSchoolMask(pEntry) & school_immune) && !pEntry->HasAttribute(SPELL_ATTR_EX_UNAFFECTED_BY_SCHOOL_IMMUNE)) + continue; + if ((1<<(pEntry->GetDispel())) & dispel_immune) + continue; + + for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i) + { + Aura* aura = holder->GetAuraByEffectIndex(SpellEffectIndex(i)); + if (!aura) + continue; + + if (GetSpellMechanicMask(pEntry, 1 << i) & mechanic_immune) + continue; + // Make a second check for spell failed so the right SPELL_FAILED message is returned. + // That is needed when your casting is prevented by multiple states and you are only immune to some of them. + switch (aura->GetModifier()->m_auraname) + { + case SPELL_AURA_MOD_STUN: + if (!spellUsableWhileStunned || !aura->HasMechanic(MECHANIC_STUN)) + return SPELL_FAILED_STUNNED; + break; + case SPELL_AURA_MOD_CONFUSE: + if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX5_USABLE_WHILE_CONFUSED)) + return SPELL_FAILED_CONFUSED; + break; + case SPELL_AURA_MOD_FEAR: + if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX5_USABLE_WHILE_FEARED)) + return SPELL_FAILED_FLEEING; + break; + case SPELL_AURA_MOD_SILENCE: + case SPELL_AURA_MOD_PACIFY: + case SPELL_AURA_MOD_PACIFY_SILENCE: + if( m_spellInfo->GetPreventionType() == SPELL_PREVENTION_TYPE_PACIFY) + return SPELL_FAILED_PACIFIED; + else if ( m_spellInfo->GetPreventionType() == SPELL_PREVENTION_TYPE_SILENCE) + return SPELL_FAILED_SILENCED; + break; + default: break; + } + } + } + } + // You are prevented from casting and the spell casted does not grant immunity. Return a failed error. + else + return prevented_reason; + } + return SPELL_CAST_OK; +} + +bool Spell::CanAutoCast(Unit* target) +{ + ObjectGuid targetguid = target->GetObjectGuid(); + + for (int j = 0; j < MAX_EFFECT_INDEX; ++j) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(j)); + if(!spellEffect) + continue; + if(spellEffect->Effect == SPELL_EFFECT_APPLY_AURA) + { + if( m_spellInfo->GetStackAmount() <= 1) + { + if (target->HasAura(m_spellInfo->Id, SpellEffectIndex(j))) + return false; + } + else + { + if(Aura* aura = target->GetAura(m_spellInfo->Id, SpellEffectIndex(j))) + if(aura->GetStackAmount() >= m_spellInfo->GetStackAmount()) + return false; + } + } + else if ( IsAreaAuraEffect( spellEffect->Effect )) + { + if (target->HasAura(m_spellInfo->Id, SpellEffectIndex(j))) + return false; + } + } + + SpellCastResult result = CheckPetCast(target); + + if (result == SPELL_CAST_OK || result == SPELL_FAILED_UNIT_NOT_INFRONT) + { + FillTargetMap(); + // check if among target units, our WANTED target is as well (->only self cast spells return false) + for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + if (ihit->targetGUID == targetguid) + return true; + } + return false; // target invalid +} + +SpellCastResult Spell::CheckRange(bool strict) +{ + Unit* target = m_targets.getUnitTarget(); + + // special range cases + switch (m_spellInfo->rangeIndex) + { + // self cast doesn't need range checking -- also for Starshards fix + // spells that can be cast anywhere also need no check + case SPELL_RANGE_IDX_SELF_ONLY: + case SPELL_RANGE_IDX_ANYWHERE: + return SPELL_CAST_OK; + // combat range spells are treated differently + case SPELL_RANGE_IDX_COMBAT: + { + if (target) + { + if (target == m_caster) + return SPELL_CAST_OK; + + float range_mod = strict ? 0.0f : 5.0f; + float base = ATTACK_DISTANCE; + if (Player* modOwner = m_caster->GetSpellModOwner()) + range_mod += modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_RANGE, base, this); + + // with additional 5 dist for non stricted case (some melee spells have delay in apply + return m_caster->CanReachWithMeleeAttack(target, range_mod) ? SPELL_CAST_OK : SPELL_FAILED_OUT_OF_RANGE; + } + break; // let continue in generic way for no target + } + } + + // add radius of caster and ~5 yds "give" for non stricred (landing) check + float range_mod = strict ? 1.25f : 6.25; + + SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex); + bool friendly = target ? target->IsFriendlyTo(m_caster) : false; + float max_range = GetSpellMaxRange(srange, friendly) + range_mod; + float min_range = GetSpellMinRange(srange, friendly); + + if (Player* modOwner = m_caster->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_RANGE, max_range, this); + + if (target && target != m_caster) + { + // distance from target in checks + float dist = m_caster->GetCombatDistance(target, m_spellInfo->rangeIndex == SPELL_RANGE_IDX_COMBAT); + + if (dist > max_range) + return SPELL_FAILED_OUT_OF_RANGE; + if (min_range && dist < min_range) + return SPELL_FAILED_TOO_CLOSE; + if( m_caster->GetTypeId() == TYPEID_PLAYER && + (m_spellInfo->GetFacingCasterFlags() & SPELL_FACING_FLAG_INFRONT) && !m_caster->HasInArc( M_PI_F, target ) ) + return SPELL_FAILED_UNIT_NOT_INFRONT; + } + + // TODO verify that such spells really use bounding radius + if (m_targets.m_targetMask == TARGET_FLAG_DEST_LOCATION && m_targets.m_destX != 0 && m_targets.m_destY != 0 && m_targets.m_destZ != 0) + { + if (!m_caster->IsWithinDist3d(m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ, max_range)) + return SPELL_FAILED_OUT_OF_RANGE; + if (min_range && m_caster->IsWithinDist3d(m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ, min_range)) + return SPELL_FAILED_TOO_CLOSE; + } + + return SPELL_CAST_OK; +} + +uint32 Spell::CalculatePowerCost(SpellEntry const* spellInfo, Unit* caster, Spell const* spell, Item* castItem) +{ + // item cast not used power + if (castItem) + return 0; + + // Spell drain all exist power on cast (Only paladin lay of Hands) + if (spellInfo->HasAttribute(SPELL_ATTR_EX_DRAIN_ALL_POWER)) + { + // If power type - health drain all + if (spellInfo->powerType == POWER_HEALTH) + return caster->GetHealth(); + // Else drain all power + if (spellInfo->powerType < MAX_POWERS) + return caster->GetPower(Powers(spellInfo->powerType)); + sLog.outError("Spell::CalculateManaCost: Unknown power type '%d' in spell %d", spellInfo->powerType, spellInfo->Id); + return 0; + } + + // Base powerCost + int32 powerCost = spellInfo->GetManaCost(); + // PCT cost from total amount + if (uint32 manaCostPct = spellInfo->GetManaCostPercentage()) + { + switch (spellInfo->powerType) + { + // health as power used + case POWER_HEALTH: + powerCost += manaCostPct * caster->GetCreateHealth() / 100; + break; + case POWER_MANA: + powerCost += manaCostPct * caster->GetCreateMana() / 100; + break; + case POWER_RAGE: + case POWER_FOCUS: + case POWER_ENERGY: + powerCost += manaCostPct * caster->GetMaxPower(Powers(spellInfo->powerType)) / 100; + break; + case POWER_RUNE: + case POWER_RUNIC_POWER: + DEBUG_LOG("Spell::CalculateManaCost: Not implemented yet!"); + break; + default: + sLog.outError("Spell::CalculateManaCost: Unknown power type '%d' in spell %d", spellInfo->powerType, spellInfo->Id); + return 0; + } + } + + SpellSchoolMask schoolMask = spell ? spell->m_spellSchoolMask : GetSpellSchoolMask(spellInfo); + // Flat mod from caster auras by spell school + Unit::AuraList const& pwrCostAuras = caster->GetAurasByType(SPELL_AURA_MOD_POWER_COST_SCHOOL); + for (Unit::AuraList::const_iterator itr = pwrCostAuras.begin(); itr != pwrCostAuras.end(); ++itr) + { + if (((*itr)->GetModifier()->m_miscvalue & schoolMask) && + (!(*itr)->GetSpellEffect()->EffectMiscValueB || (*itr)->GetSpellEffect()->EffectMiscValueB & (1 << spellInfo->powerType))) + powerCost += (*itr)->GetModifier()->m_amount; + } + + // Shiv - costs 20 + weaponSpeed*10 energy (apply only to non-triggered spell with energy cost) + if (spellInfo->HasAttribute(SPELL_ATTR_EX4_SPELL_VS_EXTEND_COST)) + powerCost += caster->GetAttackTime(OFF_ATTACK) / 100; + + // Apply cost mod by spell + if (spell) + if (Player* modOwner = caster->GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_COST, powerCost, spell); + + if (spellInfo->HasAttribute(SPELL_ATTR_LEVEL_DAMAGE_CALCULATION)) + powerCost = int32(powerCost / (1.117f * spellInfo->GetSpellLevel() / caster->getLevel() - 0.1327f)); + + // PCT mod from user auras by school + float pctCostMultiplier = 1.0f; + Unit::AuraList const& pwrCostPctAuras = caster->GetAurasByType(SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT); + for (Unit::AuraList::const_iterator itr = pwrCostPctAuras.begin(); itr != pwrCostPctAuras.end(); ++itr) + { + if (((*itr)->GetModifier()->m_miscvalue & schoolMask) && + (!(*itr)->GetSpellEffect()->EffectMiscValueB || (*itr)->GetSpellEffect()->EffectMiscValueB & (1 << spellInfo->powerType))) + pctCostMultiplier += (*itr)->GetModifier()->m_amount / 100.0f; + } + + // PCT mod from user auras by school + powerCost = int32(powerCost * pctCostMultiplier); + if (powerCost < 0) + powerCost = 0; + return powerCost; +} + +SpellCastResult Spell::CheckPower() +{ + // item cast not used power + if (m_CastItem) + return SPELL_CAST_OK; + + // Do precise power regen on spell cast + if (m_powerCost > 0 && m_caster->GetTypeId() == TYPEID_PLAYER) + { + Player* playerCaster = (Player*)m_caster; + uint32 diff = REGEN_TIME_FULL - m_caster->GetRegenTimer(); + if (diff >= REGEN_TIME_PRECISE) + playerCaster->RegenerateAll(diff); + } + + // health as power used - need check health amount + if (m_spellInfo->powerType == POWER_HEALTH) + { + if (m_caster->GetHealth() <= m_powerCost) + return SPELL_FAILED_CASTER_AURASTATE; + return SPELL_CAST_OK; + } + + // Check valid power type + if (m_spellInfo->powerType >= MAX_POWERS) + { + sLog.outError("Spell::CheckMana: Unknown power type '%d'", m_spellInfo->powerType); + return SPELL_FAILED_UNKNOWN; + } + + // check rune cost only if a spell has PowerType == POWER_RUNE + if (m_spellInfo->powerType == POWER_RUNE) + { + SpellCastResult failReason = CheckRunePower(); + if (failReason != SPELL_CAST_OK) + return failReason; + } + + // Check power amount + Powers powerType = Powers(m_spellInfo->powerType); + if (m_caster->GetPower(powerType) < m_powerCost) + return SPELL_FAILED_NO_POWER; + + return SPELL_CAST_OK; +} + +bool Spell::IgnoreItemRequirements() const +{ + /// Check if it's an enchant scroll. These have no required reagents even though their spell does. + if (m_CastItem && (m_CastItem->GetProto()->Flags & ITEM_FLAG_ENCHANT_SCROLL)) + return true; + + if (m_IsTriggeredSpell) + { + /// Not own traded item (in trader trade slot) req. reagents including triggered spell case + if (Item* targetItem = m_targets.getItemTarget()) + if (targetItem->GetOwnerGuid() != m_caster->GetObjectGuid()) + return false; + + /// Some triggered spells have same reagents that have master spell + /// expected in test: master spell have reagents in first slot then triggered don't must use own + if (m_triggeredBySpellInfo) + { + SpellReagentsEntry const* spellReagents = m_triggeredBySpellInfo->GetSpellReagents(); + if (!spellReagents || !spellReagents->Reagent[0]) + return false; + } + + return true; + } + + return false; +} + +SpellCastResult Spell::CheckItems() +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return SPELL_CAST_OK; + + Player* p_caster = (Player*)m_caster; + bool isScrollItem = false; + bool isVellumTarget = false; + + // cast item checks + if (m_CastItem) + { + if (m_CastItem->IsInTrade()) + return SPELL_FAILED_ITEM_NOT_FOUND; + + uint32 itemid = m_CastItem->GetEntry(); + if (!p_caster->HasItemCount(itemid, 1)) + return SPELL_FAILED_ITEM_NOT_FOUND; + + ItemPrototype const* proto = m_CastItem->GetProto(); + if (!proto) + return SPELL_FAILED_ITEM_NOT_FOUND; + + if (proto->Flags & ITEM_FLAG_ENCHANT_SCROLL) + isScrollItem = true; + + for (int i = 0; i < 5; ++i) + if (proto->Spells[i].SpellCharges) + if (m_CastItem->GetSpellCharges(i) == 0) + return SPELL_FAILED_NO_CHARGES_REMAIN; + + // consumable cast item checks + if (proto->Class == ITEM_CLASS_CONSUMABLE && m_targets.getUnitTarget()) + { + // such items should only fail if there is no suitable effect at all - see Rejuvenation Potions for example + SpellCastResult failReason = SPELL_CAST_OK; + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(i)); + if(!spellEffect) + continue; + // skip check, pet not required like checks, and for TARGET_PET m_targets.getUnitTarget() is not the real target but the caster + if (spellEffect->EffectImplicitTargetA == TARGET_PET) + continue; + + if (spellEffect->Effect == SPELL_EFFECT_HEAL) + { + if (m_targets.getUnitTarget()->GetHealth() == m_targets.getUnitTarget()->GetMaxHealth()) + { + failReason = SPELL_FAILED_ALREADY_AT_FULL_HEALTH; + continue; + } + else + { + failReason = SPELL_CAST_OK; + break; + } + } + + // Mana Potion, Rage Potion, Thistle Tea(Rogue), ... + if (spellEffect->Effect == SPELL_EFFECT_ENERGIZE) + { + if(spellEffect->EffectMiscValue < 0 || spellEffect->EffectMiscValue >= MAX_POWERS) + { + failReason = SPELL_FAILED_ALREADY_AT_FULL_POWER; + continue; + } + + Powers power = Powers(spellEffect->EffectMiscValue); + if (m_targets.getUnitTarget()->GetPower(power) == m_targets.getUnitTarget()->GetMaxPower(power)) + { + failReason = SPELL_FAILED_ALREADY_AT_FULL_POWER; + continue; + } + else + { + failReason = SPELL_CAST_OK; + break; + } + } + } + if (failReason != SPELL_CAST_OK) + return failReason; + } + } + + // check target item (for triggered case not report error) + if (m_targets.getItemTargetGuid()) + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return m_IsTriggeredSpell && !(m_targets.m_targetMask & TARGET_FLAG_TRADE_ITEM) + ? SPELL_FAILED_DONT_REPORT : SPELL_FAILED_BAD_TARGETS; + + if (!m_targets.getItemTarget()) + return m_IsTriggeredSpell && !(m_targets.m_targetMask & TARGET_FLAG_TRADE_ITEM) + ? SPELL_FAILED_DONT_REPORT : SPELL_FAILED_ITEM_GONE; + + isVellumTarget = m_targets.getItemTarget()->GetProto()->IsVellum(); + if (!m_targets.getItemTarget()->IsFitToSpellRequirements(m_spellInfo)) + return m_IsTriggeredSpell && !(m_targets.m_targetMask & TARGET_FLAG_TRADE_ITEM) + ? SPELL_FAILED_DONT_REPORT : SPELL_FAILED_EQUIPPED_ITEM_CLASS; + + // Do not enchant vellum with scroll + if (isVellumTarget && isScrollItem) + return m_IsTriggeredSpell && !(m_targets.m_targetMask & TARGET_FLAG_TRADE_ITEM) + ? SPELL_FAILED_DONT_REPORT : SPELL_FAILED_BAD_TARGETS; + } + // if not item target then required item must be equipped (for triggered case not report error) + else + { + if (m_caster->GetTypeId() == TYPEID_PLAYER && !((Player*)m_caster)->HasItemFitToSpellReqirements(m_spellInfo)) + return m_IsTriggeredSpell ? SPELL_FAILED_DONT_REPORT : SPELL_FAILED_EQUIPPED_ITEM_CLASS; + } + + // check reagents (ignore triggered spells with reagents processed by original spell) and special reagent ignore case. + if (!IgnoreItemRequirements()) + { + if (!p_caster->CanNoReagentCast(m_spellInfo)) + { + SpellReagentsEntry const* spellReagents = m_spellInfo->GetSpellReagents(); + if(spellReagents) + { + for(uint32 i = 0; i < MAX_SPELL_REAGENTS; ++i) + { + if(spellReagents->Reagent[i] <= 0) + continue; + + uint32 itemid = spellReagents->Reagent[i]; + uint32 itemcount = spellReagents->ReagentCount[i]; + + // if CastItem is also spell reagent + if (m_CastItem && m_CastItem->GetEntry() == itemid) + { + ItemPrototype const *proto = m_CastItem->GetProto(); + if (!proto) + return SPELL_FAILED_REAGENTS; + for(int s = 0; s < MAX_ITEM_PROTO_SPELLS; ++s) + { + // CastItem will be used up and does not count as reagent + int32 charges = m_CastItem->GetSpellCharges(s); + if (proto->Spells[s].SpellCharges < 0 && !(proto->ExtraFlags & ITEM_EXTRA_NON_CONSUMABLE) && abs(charges) < 2) + { + ++itemcount; + break; + } + } + } + + if (!p_caster->HasItemCount(itemid, itemcount)) + return SPELL_FAILED_REAGENTS; + } + } + } + + // check totem-item requirements (items presence in inventory) + SpellTotemsEntry const* spellTotems = m_spellInfo->GetSpellTotems(); + if(spellTotems) + { + uint32 totems = MAX_SPELL_TOTEMS; + for(int i = 0; i < MAX_SPELL_TOTEMS ; ++i) + { + if (spellTotems->Totem[i] != 0) + { + if (p_caster->HasItemCount(spellTotems->Totem[i], 1)) + { + totems -= 1; + continue; + } + } + else + totems -= 1; + } + + if (totems != 0) + return SPELL_FAILED_TOTEMS; + } + } + + // special checks for spell effects + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(i)); + if(!spellEffect) + continue; + switch (spellEffect->Effect) + { + case SPELL_EFFECT_CREATE_ITEM: + { + if (!m_IsTriggeredSpell && spellEffect->EffectItemType) + { + // Conjure Mana Gem (skip same or low level ranks for later recharge) + if (i == EFFECT_INDEX_0 && m_spellInfo->GetSpellEffectIdByIndex(EFFECT_INDEX_1) == SPELL_EFFECT_DUMMY) + { + if (ItemPrototype const* itemProto = ObjectMgr::GetItemPrototype(spellEffect->EffectItemType)) + { + if (Item* item = p_caster->GetItemByLimitedCategory(itemProto->ItemLimitCategory)) + { + if (item->GetProto()->ItemLevel <= itemProto->ItemLevel) + { + if (item->HasMaxCharges()) + return SPELL_FAILED_ITEM_AT_MAX_CHARGES; + + // will recharge in next effect + continue; + } + } + } + } + + ItemPosCountVec dest; + InventoryResult msg = p_caster->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, spellEffect->EffectItemType, 1 ); + if (msg != EQUIP_ERR_OK ) + { + p_caster->SendEquipError( msg, NULL, NULL, spellEffect->EffectItemType ); + return SPELL_FAILED_DONT_REPORT; + } + } + break; + } + case SPELL_EFFECT_RESTORE_ITEM_CHARGES: + { + if (Item* item = p_caster->GetItemByEntry(spellEffect->EffectItemType)) + if (item->HasMaxCharges()) + return SPELL_FAILED_ITEM_AT_MAX_CHARGES; + + break; + } + case SPELL_EFFECT_ENCHANT_ITEM: + case SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC: + { + Item* targetItem = m_targets.getItemTarget(); + if (!targetItem) + return SPELL_FAILED_ITEM_NOT_FOUND; + + if( targetItem->GetProto()->ItemLevel < m_spellInfo->GetBaseLevel() ) + return SPELL_FAILED_LOWLEVEL; + // Check if we can store a new scroll, enchanting vellum has implicit SPELL_EFFECT_CREATE_ITEM + if (isVellumTarget && spellEffect->EffectItemType) + { + ItemPosCountVec dest; + InventoryResult msg = p_caster->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, spellEffect->EffectItemType, 1 ); + if (msg != EQUIP_ERR_OK) + { + p_caster->SendEquipError(msg, NULL, NULL); + return SPELL_FAILED_DONT_REPORT; + } + } + // Not allow enchant in trade slot for some enchant type + if (targetItem->GetOwner() != m_caster) + { + uint32 enchant_id = spellEffect->EffectMiscValue; + SpellItemEnchantmentEntry const *pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if(!pEnchant) + return SPELL_FAILED_ERROR; + if (pEnchant->slot & ENCHANTMENT_CAN_SOULBOUND) + return SPELL_FAILED_NOT_TRADEABLE; + // cannot replace vellum with scroll in trade slot + if (isVellumTarget) + return SPELL_FAILED_ITEM_ENCHANT_TRADE_WINDOW; + } + break; + } + case SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY: + { + Item* item = m_targets.getItemTarget(); + if (!item) + return SPELL_FAILED_ITEM_NOT_FOUND; + // Not allow enchant in trade slot for some enchant type + if (item->GetOwner() != m_caster) + { + uint32 enchant_id = spellEffect->EffectMiscValue; + SpellItemEnchantmentEntry const *pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if(!pEnchant) + return SPELL_FAILED_ERROR; + if (pEnchant->slot & ENCHANTMENT_CAN_SOULBOUND) + return SPELL_FAILED_NOT_TRADEABLE; + } + break; + } + case SPELL_EFFECT_ENCHANT_HELD_ITEM: + // check item existence in effect code (not output errors at offhand hold item effect to main hand for example + break; + case SPELL_EFFECT_DISENCHANT: + { + if (!m_targets.getItemTarget()) + return SPELL_FAILED_CANT_BE_DISENCHANTED; + + // prevent disenchanting in trade slot + if (m_targets.getItemTarget()->GetOwnerGuid() != m_caster->GetObjectGuid()) + return SPELL_FAILED_CANT_BE_DISENCHANTED; + + ItemPrototype const* itemProto = m_targets.getItemTarget()->GetProto(); + if (!itemProto) + return SPELL_FAILED_CANT_BE_DISENCHANTED; + + // must have disenchant loot (other static req. checked at item prototype loading) + if (!itemProto->DisenchantID) + return SPELL_FAILED_CANT_BE_DISENCHANTED; + + // 2.0.x addon: Check player enchanting level against the item disenchanting requirements + int32 item_disenchantskilllevel = itemProto->RequiredDisenchantSkill; + if (item_disenchantskilllevel > int32(p_caster->GetSkillValue(SKILL_ENCHANTING))) + return SPELL_FAILED_LOW_CASTLEVEL; + break; + } + case SPELL_EFFECT_PROSPECTING: + { + if (!m_targets.getItemTarget()) + return SPELL_FAILED_CANT_BE_PROSPECTED; + // ensure item is a prospectable ore + if (!(m_targets.getItemTarget()->GetProto()->Flags & ITEM_FLAG_PROSPECTABLE)) + return SPELL_FAILED_CANT_BE_PROSPECTED; + // prevent prospecting in trade slot + if (m_targets.getItemTarget()->GetOwnerGuid() != m_caster->GetObjectGuid()) + return SPELL_FAILED_CANT_BE_PROSPECTED; + // Check for enough skill in jewelcrafting + uint32 item_prospectingskilllevel = m_targets.getItemTarget()->GetProto()->RequiredSkillRank; + if (item_prospectingskilllevel > p_caster->GetSkillValue(SKILL_JEWELCRAFTING)) + return SPELL_FAILED_LOW_CASTLEVEL; + // make sure the player has the required ores in inventory + if (int32(m_targets.getItemTarget()->GetCount()) < CalculateDamage(SpellEffectIndex(i), m_caster)) + return SPELL_FAILED_NEED_MORE_ITEMS; + + if (!LootTemplates_Prospecting.HaveLootFor(m_targets.getItemTargetEntry())) + return SPELL_FAILED_CANT_BE_PROSPECTED; + + break; + } + case SPELL_EFFECT_MILLING: + { + if (!m_targets.getItemTarget()) + return SPELL_FAILED_CANT_BE_MILLED; + // ensure item is a millable herb + if (!(m_targets.getItemTarget()->GetProto()->Flags & ITEM_FLAG_MILLABLE)) + return SPELL_FAILED_CANT_BE_MILLED; + // prevent milling in trade slot + if (m_targets.getItemTarget()->GetOwnerGuid() != m_caster->GetObjectGuid()) + return SPELL_FAILED_CANT_BE_MILLED; + // Check for enough skill in inscription + uint32 item_millingskilllevel = m_targets.getItemTarget()->GetProto()->RequiredSkillRank; + if (item_millingskilllevel > p_caster->GetSkillValue(SKILL_INSCRIPTION)) + return SPELL_FAILED_LOW_CASTLEVEL; + // make sure the player has the required herbs in inventory + if (int32(m_targets.getItemTarget()->GetCount()) < CalculateDamage(SpellEffectIndex(i), m_caster)) + return SPELL_FAILED_NEED_MORE_ITEMS; + + if (!LootTemplates_Milling.HaveLootFor(m_targets.getItemTargetEntry())) + return SPELL_FAILED_CANT_BE_MILLED; + + break; + } + case SPELL_EFFECT_WEAPON_DAMAGE: + case SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL: + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) return SPELL_FAILED_TARGET_NOT_PLAYER; + if (m_attackType != RANGED_ATTACK) + break; + Item* pItem = ((Player*)m_caster)->GetWeaponForAttack(m_attackType, true, false); + if (!pItem) + return SPELL_FAILED_EQUIPPED_ITEM; + + switch (pItem->GetProto()->SubClass) + { + case ITEM_SUBCLASS_WEAPON_THROWN: + { + uint32 ammo = pItem->GetEntry(); + if (!((Player*)m_caster)->HasItemCount(ammo, 1)) + return SPELL_FAILED_NO_AMMO; + }; break; + case ITEM_SUBCLASS_WEAPON_WAND: + break; + default: + break; + } + break; + } + default: break; + } + } + + return SPELL_CAST_OK; +} + +void Spell::Delayed() +{ + if (!m_caster || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + if (m_spellState == SPELL_STATE_DELAYED) + return; // spell is active and can't be time-backed + + if (isDelayableNoMore()) // Spells may only be delayed twice + return; + + // spells not loosing casting time ( slam, dynamites, bombs.. ) + if(!(m_spellInfo->GetInterruptFlags() & SPELL_INTERRUPT_FLAG_DAMAGE)) + return; + + // check pushback reduce + int32 delaytime = 500; // spellcasting delay is normally 500ms + int32 delayReduce = 100; // must be initialized to 100 for percent modifiers + ((Player*)m_caster)->ApplySpellMod(m_spellInfo->Id, SPELLMOD_NOT_LOSE_CASTING_TIME, delayReduce, this); + delayReduce += m_caster->GetTotalAuraModifier(SPELL_AURA_REDUCE_PUSHBACK) - 100; + if (delayReduce >= 100) + return; + + delaytime = delaytime * (100 - delayReduce) / 100; + + if (int32(m_timer) + delaytime > m_casttime) + { + delaytime = m_casttime - m_timer; + m_timer = m_casttime; + } + else + m_timer += delaytime; + + DETAIL_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell %u partially interrupted for (%d) ms at damage", m_spellInfo->Id, delaytime); + + WorldPacket data(SMSG_SPELL_DELAYED, 8 + 4); + data << m_caster->GetPackGUID(); + data << uint32(delaytime); + + m_caster->SendMessageToSet(&data, true); +} + +void Spell::DelayedChannel() +{ + if (!m_caster || m_caster->GetTypeId() != TYPEID_PLAYER || getState() != SPELL_STATE_CASTING) + return; + + if (isDelayableNoMore()) // Spells may only be delayed twice + return; + + // check pushback reduce + int32 delaytime = GetSpellDuration(m_spellInfo) * 25 / 100;// channeling delay is normally 25% of its time per hit + int32 delayReduce = 100; // must be initialized to 100 for percent modifiers + ((Player*)m_caster)->ApplySpellMod(m_spellInfo->Id, SPELLMOD_NOT_LOSE_CASTING_TIME, delayReduce, this); + delayReduce += m_caster->GetTotalAuraModifier(SPELL_AURA_REDUCE_PUSHBACK) - 100; + if (delayReduce >= 100) + return; + + delaytime = delaytime * (100 - delayReduce) / 100; + + if (int32(m_timer) < delaytime) + { + delaytime = m_timer; + m_timer = 0; + } + else + m_timer -= delaytime; + + DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell %u partially interrupted for %i ms, new duration: %u ms", m_spellInfo->Id, delaytime, m_timer); + + for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if ((*ihit).missCondition == SPELL_MISS_NONE) + { + if (Unit* unit = m_caster->GetObjectGuid() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID)) + unit->DelaySpellAuraHolder(m_spellInfo->Id, delaytime, unit->GetObjectGuid()); + } + } + + for (int j = 0; j < MAX_EFFECT_INDEX; ++j) + { + // partially interrupt persistent area auras + if (DynamicObject* dynObj = m_caster->GetDynObject(m_spellInfo->Id, SpellEffectIndex(j))) + dynObj->Delay(delaytime); + } + + SendChannelUpdate(m_timer); +} + +void Spell::UpdateOriginalCasterPointer() +{ + if (m_originalCasterGUID == m_caster->GetObjectGuid()) + m_originalCaster = m_caster; + else if (m_originalCasterGUID.IsGameObject()) + { + GameObject* go = m_caster->IsInWorld() ? m_caster->GetMap()->GetGameObject(m_originalCasterGUID) : NULL; + m_originalCaster = go ? go->GetOwner() : NULL; + } + else + { + Unit* unit = ObjectAccessor::GetUnit(*m_caster, m_originalCasterGUID); + m_originalCaster = unit && unit->IsInWorld() ? unit : NULL; + } +} + +void Spell::UpdatePointers() +{ + UpdateOriginalCasterPointer(); + + m_targets.Update(m_caster); +} + +bool Spell::CheckTargetCreatureType(Unit* target) const +{ + uint32 spellCreatureTargetMask = m_spellInfo->GetTargetCreatureType(); + + // Curse of Doom: not find another way to fix spell target check :/ + if (m_spellInfo->GetSpellFamilyName() == SPELLFAMILY_WARLOCK && m_spellInfo->GetCategory() == 1179) + { + // not allow cast at player + if (target->GetTypeId() == TYPEID_PLAYER) + return false; + + spellCreatureTargetMask = 0x7FF; + } + + // Dismiss Pet, Taming Lesson and Control Robot skipped + if (m_spellInfo->Id == 2641 || m_spellInfo->Id == 23356 || m_spellInfo->Id == 30009) + spellCreatureTargetMask = 0; + + if (spellCreatureTargetMask) + { + uint32 TargetCreatureType = target->GetCreatureTypeMask(); + + return !TargetCreatureType || (spellCreatureTargetMask & TargetCreatureType); + } + return true; +} + +CurrentSpellTypes Spell::GetCurrentContainer() +{ + if (IsNextMeleeSwingSpell()) + return (CURRENT_MELEE_SPELL); + else if (IsAutoRepeat()) + return (CURRENT_AUTOREPEAT_SPELL); + else if (IsChanneledSpell(m_spellInfo)) + return (CURRENT_CHANNELED_SPELL); + else + return (CURRENT_GENERIC_SPELL); +} + +bool Spell::CheckTarget(Unit* target, SpellEffectIndex eff) +{ + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(eff); + if(!spellEffect) + return false; + + // Check targets for creature type mask and remove not appropriate (skip explicit self target case, maybe need other explicit targets) + if(spellEffect->EffectImplicitTargetA != TARGET_SELF ) + { + if (!CheckTargetCreatureType(target)) + return false; + } + + // Check Aura spell req (need for AoE spells) + SpellAuraRestrictionsEntry const* auraRestrictions = m_spellInfo->GetSpellAuraRestrictions(); + if(auraRestrictions) + { + if(auraRestrictions->targetAuraSpell && !target->HasAura(auraRestrictions->targetAuraSpell)) + return false; + if (auraRestrictions->excludeTargetAuraSpell && target->HasAura(auraRestrictions->excludeTargetAuraSpell)) + return false; + } + + // Check targets for not_selectable unit flag and remove + // A player can cast spells on his pet (or other controlled unit) though in any state + if (target != m_caster && target->GetCharmerOrOwnerGuid() != m_caster->GetObjectGuid()) + { + // any unattackable target skipped + if (target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE)) + return false; + + // unselectable targets skipped in all cases except TARGET_SCRIPT targeting + // in case TARGET_SCRIPT target selected by server always and can't be cheated + if ((!m_IsTriggeredSpell || target != m_targets.getUnitTarget()) && + target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE) && + spellEffect->EffectImplicitTargetA != TARGET_SCRIPT && + spellEffect->EffectImplicitTargetB != TARGET_SCRIPT && + spellEffect->EffectImplicitTargetA != TARGET_AREAEFFECT_INSTANT && + spellEffect->EffectImplicitTargetB != TARGET_AREAEFFECT_INSTANT && + spellEffect->EffectImplicitTargetA != TARGET_AREAEFFECT_CUSTOM && + spellEffect->EffectImplicitTargetB != TARGET_AREAEFFECT_CUSTOM ) + return false; + } + + // Check player targets and remove if in GM mode or GM invisibility (for not self casting case) + if (target != m_caster && target->GetTypeId() == TYPEID_PLAYER) + { + if (((Player*)target)->GetVisibility() == VISIBILITY_OFF) + return false; + + if (((Player*)target)->isGameMaster() && !IsPositiveSpell(m_spellInfo->Id)) + return false; + } + + // Check targets for LOS visibility (except spells without range limitations ) + switch(spellEffect->Effect) + { + case SPELL_EFFECT_SUMMON_PLAYER: // from anywhere + break; + case SPELL_EFFECT_DUMMY: + if (m_spellInfo->Id != 20577) // Cannibalize + break; + // fall through + case SPELL_EFFECT_RESURRECT_NEW: + // player far away, maybe his corpse near? + if (target != m_caster && !m_spellInfo->HasAttribute(SPELL_ATTR_EX2_IGNORE_LOS) && !target->IsWithinLOSInMap(m_caster)) + { + if (!m_targets.getCorpseTargetGuid()) + return false; + + Corpse* corpse = m_caster->GetMap()->GetCorpse(m_targets.getCorpseTargetGuid()); + if (!corpse) + return false; + + if (target->GetObjectGuid() != corpse->GetOwnerGuid()) + return false; + + if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX2_IGNORE_LOS) && !corpse->IsWithinLOSInMap(m_caster)) + return false; + } + + // all ok by some way or another, skip normal check + break; + default: // normal case + // Get GO cast coordinates if original caster -> GO + if (target != m_caster) + if (WorldObject* caster = GetCastingObject()) + if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX2_IGNORE_LOS) && !target->IsWithinLOSInMap(caster)) + return false; + break; + } + + if (target->GetTypeId() != TYPEID_PLAYER && m_spellInfo->HasAttribute(SPELL_ATTR_EX3_TARGET_ONLY_PLAYER) + && spellEffect->EffectImplicitTargetA != TARGET_SCRIPT && spellEffect->EffectImplicitTargetA != TARGET_SELF) + return false; + + switch (m_spellInfo->Id) + { + case 37433: // Spout (The Lurker Below), only players affected if its not in water + if (target->GetTypeId() != TYPEID_PLAYER || target->IsInWater()) + return false; + break; + case 68921: // Soulstorm (FoS), only targets farer than 10 away + case 69049: // Soulstorm - = - + if (m_caster->IsWithinDist(target, 10.0f, false)) + return false; + break; + default: + break; + } + + return true; +} + +bool Spell::IsNeedSendToClient() const +{ + return m_spellInfo->SpellVisual[0] || m_spellInfo->SpellVisual[1] || IsChanneledSpell(m_spellInfo) || + m_spellInfo->speed > 0.0f || (!m_triggeredByAuraSpell && !m_IsTriggeredSpell); +} + +bool Spell::IsTriggeredSpellWithRedundentCastTime() const +{ + return m_IsTriggeredSpell && (m_spellInfo->GetManaCost() || m_spellInfo->GetManaCostPercentage()); +} + +bool Spell::HaveTargetsForEffect(SpellEffectIndex effect) const +{ + for (TargetList::const_iterator itr = m_UniqueTargetInfo.begin(); itr != m_UniqueTargetInfo.end(); ++itr) + if (itr->effectMask & (1 << effect)) + return true; + + for (GOTargetList::const_iterator itr = m_UniqueGOTargetInfo.begin(); itr != m_UniqueGOTargetInfo.end(); ++itr) + if (itr->effectMask & (1 << effect)) + return true; + + for (ItemTargetList::const_iterator itr = m_UniqueItemInfo.begin(); itr != m_UniqueItemInfo.end(); ++itr) + if (itr->effectMask & (1 << effect)) + return true; + + return false; +} + +SpellEvent::SpellEvent(Spell* spell) : BasicEvent() +{ + m_Spell = spell; +} + +SpellEvent::~SpellEvent() +{ + if (m_Spell->getState() != SPELL_STATE_FINISHED) + m_Spell->cancel(); + + if (m_Spell->IsDeletable()) + { + delete m_Spell; + } + else + { + sLog.outError("~SpellEvent: %s %u tried to delete non-deletable spell %u. Was not deleted, causes memory leak.", + (m_Spell->GetCaster()->GetTypeId() == TYPEID_PLAYER ? "Player" : "Creature"), m_Spell->GetCaster()->GetGUIDLow(), m_Spell->m_spellInfo->Id); + } +} + +bool SpellEvent::Execute(uint64 e_time, uint32 p_time) +{ + // update spell if it is not finished + if (m_Spell->getState() != SPELL_STATE_FINISHED) + m_Spell->update(p_time); + + // check spell state to process + switch (m_Spell->getState()) + { + case SPELL_STATE_FINISHED: + { + // spell was finished, check deletable state + if (m_Spell->IsDeletable()) + { + // check, if we do have unfinished triggered spells + return true; // spell is deletable, finish event + } + // event will be re-added automatically at the end of routine) + } break; + + case SPELL_STATE_CASTING: + { + // this spell is in channeled state, process it on the next update + // event will be re-added automatically at the end of routine) + } break; + + case SPELL_STATE_DELAYED: + { + // first, check, if we have just started + if (m_Spell->GetDelayStart() != 0) + { + // no, we aren't, do the typical update + // check, if we have channeled spell on our hands + if (IsChanneledSpell(m_Spell->m_spellInfo)) + { + // evented channeled spell is processed separately, casted once after delay, and not destroyed till finish + // check, if we have casting anything else except this channeled spell and autorepeat + if (m_Spell->GetCaster()->IsNonMeleeSpellCasted(false, true, true)) + { + // another non-melee non-delayed spell is casted now, abort + m_Spell->cancel(); + } + else + { + // do the action (pass spell to channeling state) + m_Spell->handle_immediate(); + } + // event will be re-added automatically at the end of routine) + } + else + { + // run the spell handler and think about what we can do next + uint64 t_offset = e_time - m_Spell->GetDelayStart(); + uint64 n_offset = m_Spell->handle_delayed(t_offset); + if (n_offset) + { + // re-add us to the queue + m_Spell->GetCaster()->m_Events.AddEvent(this, m_Spell->GetDelayStart() + n_offset, false); + return false; // event not complete + } + // event complete + // finish update event will be re-added automatically at the end of routine) + } + } + else + { + // delaying had just started, record the moment + m_Spell->SetDelayStart(e_time); + // re-plan the event for the delay moment + m_Spell->GetCaster()->m_Events.AddEvent(this, e_time + m_Spell->GetDelayMoment(), false); + return false; // event not complete + } + } break; + + default: + { + // all other states + // event will be re-added automatically at the end of routine) + } break; + } + + // spell processing not complete, plan event on the next update interval + m_Spell->GetCaster()->m_Events.AddEvent(this, e_time + 1, false); + return false; // event not complete +} + +void SpellEvent::Abort(uint64 /*e_time*/) +{ + // oops, the spell we try to do is aborted + if (m_Spell->getState() != SPELL_STATE_FINISHED) + m_Spell->cancel(); +} + +bool SpellEvent::IsDeletable() const +{ + return m_Spell->IsDeletable(); +} + +SpellCastResult Spell::CanOpenLock(SpellEffectIndex effIndex, uint32 lockId, SkillType& skillId, int32& reqSkillValue, int32& skillValue) +{ + if (!lockId) // possible case for GO and maybe for items. + return SPELL_CAST_OK; + + // Get LockInfo + LockEntry const* lockInfo = sLockStore.LookupEntry(lockId); + + if (!lockInfo) + return SPELL_FAILED_BAD_TARGETS; + + bool reqKey = false; // some locks not have reqs + + for (int j = 0; j < 8; ++j) + { + switch (lockInfo->Type[j]) + { + // check key item (many fit cases can be) + case LOCK_KEY_ITEM: + if (lockInfo->Index[j] && m_CastItem && m_CastItem->GetEntry() == lockInfo->Index[j]) + return SPELL_CAST_OK; + reqKey = true; + break; + // check key skill (only single first fit case can be) + case LOCK_KEY_SKILL: + { + reqKey = true; + + // wrong locktype, skip + if(uint32(m_spellInfo->GetEffectMiscValue(effIndex)) != lockInfo->Index[j]) + continue; + + skillId = SkillByLockType(LockType(lockInfo->Index[j])); + + if (skillId != SKILL_NONE || skillId == MAX_SKILL_TYPE) + { + // skill bonus provided by casting spell (mostly item spells) + // add the damage modifier from the spell casted (cheat lock / skeleton key etc.) (use m_currentBasePoints, CalculateDamage returns wrong value) + uint32 spellSkillBonus = uint32(m_currentBasePoints[effIndex]); + reqSkillValue = lockInfo->Skill[j]; + + // castitem check: rogue using skeleton keys. the skill values should not be added in this case. + // MAX_SKILL_TYPE - skill value scales with caster level + if (skillId == MAX_SKILL_TYPE) + skillValue = m_CastItem || m_caster->GetTypeId() != TYPEID_PLAYER ? 0 : m_caster->getLevel() * 5; + else + skillValue = m_CastItem || m_caster->GetTypeId() != TYPEID_PLAYER ? 0 : ((Player*)m_caster)->GetSkillValue(skillId); + + skillValue += spellSkillBonus; + + if (skillValue < reqSkillValue) + return SPELL_FAILED_LOW_CASTLEVEL; + } + + return SPELL_CAST_OK; + } + } + } + + if (reqKey) + return SPELL_FAILED_BAD_TARGETS; + + return SPELL_CAST_OK; +} + +/* + * Fill target list by units around (x,y) points at radius distance + + * @param targetUnitMap Reference to target list that filled by function + * @param x X coordinates of center point for target search + * @param y Y coordinates of center point for target search + * @param radius Radius around (x,y) for target search + * @param pushType Additional rules for target area selection (in front, angle, etc) + * @param spellTargets Additional rules for target selection base at hostile/friendly state to original spell caster + * @param originalCaster If provided set alternative original caster, if =NULL then used Spell::GetAffectiveObject() return + */ +void Spell::FillAreaTargets(UnitList& targetUnitMap, float radius, SpellNotifyPushType pushType, SpellTargets spellTargets, WorldObject* originalCaster /*=NULL*/) +{ + MaNGOS::SpellNotifierCreatureAndPlayer notifier(*this, targetUnitMap, radius, pushType, spellTargets, originalCaster); + Cell::VisitAllObjects(notifier.GetCenterX(), notifier.GetCenterY(), m_caster->GetMap(), notifier, radius); +} + +void Spell::FillRaidOrPartyTargets(UnitList& targetUnitMap, Unit* member, Unit* center, float radius, bool raid, bool withPets, bool withcaster) +{ + Player* pMember = member->GetCharmerOrOwnerPlayerOrPlayerItself(); + Group* pGroup = pMember ? pMember->GetGroup() : NULL; + + if (pGroup) + { + uint8 subgroup = pMember->GetSubGroup(); + + for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* Target = itr->getSource(); + + // IsHostileTo check duel and controlled by enemy + if (Target && (raid || subgroup == Target->GetSubGroup()) + && !m_caster->IsHostileTo(Target)) + { + if ((Target == center || center->IsWithinDistInMap(Target, radius)) && + (withcaster || Target != m_caster)) + targetUnitMap.push_back(Target); + + if (withPets) + if (Pet* pet = Target->GetPet()) + if ((pet == center || center->IsWithinDistInMap(pet, radius)) && + (withcaster || pet != m_caster)) + targetUnitMap.push_back(pet); + } + } + } + else + { + Unit* ownerOrSelf = pMember ? pMember : member->GetCharmerOrOwnerOrSelf(); + if ((ownerOrSelf == center || center->IsWithinDistInMap(ownerOrSelf, radius)) && + (withcaster || ownerOrSelf != m_caster)) + targetUnitMap.push_back(ownerOrSelf); + + if (withPets) + if (Pet* pet = ownerOrSelf->GetPet()) + if ((pet == center || center->IsWithinDistInMap(pet, radius)) && + (withcaster || pet != m_caster)) + targetUnitMap.push_back(pet); + } +} + +void Spell::FillRaidOrPartyManaPriorityTargets(UnitList& targetUnitMap, Unit* member, Unit* center, float radius, uint32 count, bool raid, bool withPets, bool withCaster) +{ + FillRaidOrPartyTargets(targetUnitMap, member, center, radius, raid, withPets, withCaster); + + PrioritizeManaUnitQueue manaUsers; + for (UnitList::const_iterator itr = targetUnitMap.begin(); itr != targetUnitMap.end(); ++itr) + if ((*itr)->getPowerType() == POWER_MANA && !(*itr)->isDead()) + manaUsers.push(PrioritizeManaUnitWraper(*itr)); + + targetUnitMap.clear(); + while (!manaUsers.empty() && targetUnitMap.size() < count) + { + targetUnitMap.push_back(manaUsers.top().getUnit()); + manaUsers.pop(); + } +} + +void Spell::FillRaidOrPartyHealthPriorityTargets(UnitList& targetUnitMap, Unit* member, Unit* center, float radius, uint32 count, bool raid, bool withPets, bool withCaster) +{ + FillRaidOrPartyTargets(targetUnitMap, member, center, radius, raid, withPets, withCaster); + + PrioritizeHealthUnitQueue healthQueue; + for (UnitList::const_iterator itr = targetUnitMap.begin(); itr != targetUnitMap.end(); ++itr) + if (!(*itr)->isDead()) + healthQueue.push(PrioritizeHealthUnitWraper(*itr)); + + targetUnitMap.clear(); + while (!healthQueue.empty() && targetUnitMap.size() < count) + { + targetUnitMap.push_back(healthQueue.top().getUnit()); + healthQueue.pop(); + } +} + +WorldObject* Spell::GetAffectiveCasterObject() const +{ + if (!m_originalCasterGUID) + return m_caster; + + if (m_originalCasterGUID.IsGameObject() && m_caster->IsInWorld()) + return m_caster->GetMap()->GetGameObject(m_originalCasterGUID); + return m_originalCaster; +} + +WorldObject* Spell::GetCastingObject() const +{ + if (m_originalCasterGUID.IsGameObject()) + return m_caster->IsInWorld() ? m_caster->GetMap()->GetGameObject(m_originalCasterGUID) : NULL; + else + return m_caster; +} + +void Spell::ResetEffectDamageAndHeal() +{ + m_damage = 0; + m_healing = 0; +} + +void Spell::SelectMountByAreaAndSkill(Unit* target, SpellEntry const* parentSpell, uint32 spellId75, uint32 spellId150, uint32 spellId225, uint32 spellId300, uint32 spellIdSpecial) +{ + if (!target || target->GetTypeId() != TYPEID_PLAYER) + return; + + // Prevent stacking of mounts + target->RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); + uint16 skillval = ((Player*)target)->GetSkillValue(SKILL_RIDING); + if (!skillval) + return; + + if (skillval >= 225 && (spellId300 > 0 || spellId225 > 0)) + { + uint32 spellid = skillval >= 300 ? spellId300 : spellId225; + SpellEntry const* pSpell = sSpellStore.LookupEntry(spellid); + if (!pSpell) + { + sLog.outError("SelectMountByAreaAndSkill: unknown spell id %i by caster: %s", spellid, target->GetGuidStr().c_str()); + return; + } + + // zone check + uint32 zone, area; + target->GetZoneAndAreaId(zone, area); + + SpellCastResult locRes = sSpellMgr.GetSpellAllowedInLocationError(pSpell, target->GetMapId(), zone, area, target->GetCharmerOrOwnerPlayerOrPlayerItself()); + if (locRes != SPELL_CAST_OK || !((Player*)target)->CanStartFlyInArea(target->GetMapId(), zone, area)) + target->CastSpell(target, spellId150, true, NULL, NULL, ObjectGuid(), parentSpell); + else if (spellIdSpecial > 0) + { + for (PlayerSpellMap::const_iterator iter = ((Player*)target)->GetSpellMap().begin(); iter != ((Player*)target)->GetSpellMap().end(); ++iter) + { + if (iter->second.state != PLAYERSPELL_REMOVED) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(iter->first); + for (int i = 0; i < MAX_EFFECT_INDEX; ++i) + { + SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(i)); + if(!spellEffect) + continue; + + if(spellEffect->EffectApplyAuraName == SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED) + { + int32 mountSpeed = spellInfo->CalculateSimpleValue(SpellEffectIndex(i)); + + // speed higher than 280 replace it + if (mountSpeed > 280) + { + target->CastSpell(target, spellIdSpecial, true, NULL, NULL, ObjectGuid(), parentSpell); + return; + } + } + } + } + } + target->CastSpell(target, pSpell, true, NULL, NULL, ObjectGuid(), parentSpell); + } + else + target->CastSpell(target, pSpell, true, NULL, NULL, ObjectGuid(), parentSpell); + } + else if (skillval >= 150 && spellId150 > 0) + target->CastSpell(target, spellId150, true, NULL, NULL, ObjectGuid(), parentSpell); + else if (spellId75 > 0) + target->CastSpell(target, spellId75, true, NULL, NULL, ObjectGuid(), parentSpell); + + return; +} + +void Spell::ClearCastItem() +{ + if (m_CastItem == m_targets.getItemTarget()) + m_targets.setItemTarget(NULL); + + m_CastItem = NULL; +} + +bool Spell::HasGlobalCooldown() +{ + // global cooldown have only player or controlled units + if (m_caster->GetCharmInfo()) + return m_caster->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(m_spellInfo); + else if (m_caster->GetTypeId() == TYPEID_PLAYER) + return ((Player*)m_caster)->GetGlobalCooldownMgr().HasGlobalCooldown(m_spellInfo); + else + return false; +} + +void Spell::TriggerGlobalCooldown() +{ + int32 gcd = m_spellInfo->GetStartRecoveryTime(); + if (!gcd) + return; + + // global cooldown can't leave range 1..1.5 secs (if it it) + // exist some spells (mostly not player directly casted) that have < 1 sec and > 1.5 sec global cooldowns + // but its as test show not affected any spell mods. + if (gcd >= 1000 && gcd <= 1500) + { + // gcd modifier auras applied only to self spells and only player have mods for this + if (m_caster->GetTypeId() == TYPEID_PLAYER) + ((Player*)m_caster)->ApplySpellMod(m_spellInfo->Id, SPELLMOD_GLOBAL_COOLDOWN, gcd, this); + + // apply haste rating + gcd = int32(float(gcd) * m_caster->GetFloatValue(UNIT_MOD_CAST_SPEED)); + + if (gcd < 1000) + gcd = 1000; + else if (gcd > 1500) + gcd = 1500; + } + + // global cooldown have only player or controlled units + if (m_caster->GetCharmInfo()) + m_caster->GetCharmInfo()->GetGlobalCooldownMgr().AddGlobalCooldown(m_spellInfo, gcd); + else if (m_caster->GetTypeId() == TYPEID_PLAYER) + ((Player*)m_caster)->GetGlobalCooldownMgr().AddGlobalCooldown(m_spellInfo, gcd); +} + +void Spell::CancelGlobalCooldown() +{ + if (!m_spellInfo->GetStartRecoveryTime()) + return; + + // cancel global cooldown when interrupting current cast + if (m_caster->GetCurrentSpell(CURRENT_GENERIC_SPELL) != this) + return; + + // global cooldown have only player or controlled units + if (m_caster->GetCharmInfo()) + m_caster->GetCharmInfo()->GetGlobalCooldownMgr().CancelGlobalCooldown(m_spellInfo); + else if (m_caster->GetTypeId() == TYPEID_PLAYER) + ((Player*)m_caster)->GetGlobalCooldownMgr().CancelGlobalCooldown(m_spellInfo); +} + +void Spell::GetSpellRangeAndRadius(SpellEffectEntry const* spellEffect, float& radius, uint32& EffectChainTarget, uint32& unMaxTargets) const +{ + if (uint32 radiusIndex = spellEffect->GetRadiusIndex()) + radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(spellEffect->GetRadiusIndex())); + else + radius = GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex)); + if (Unit* realCaster = GetAffectiveCaster()) + { + if (Player* modOwner = realCaster->GetSpellModOwner()) + { + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_RADIUS, radius, this); + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_JUMP_TARGETS, EffectChainTarget, this); + } + } + + // custom target amount cases + switch(m_spellInfo->GetSpellFamilyName()) + { + case SPELLFAMILY_GENERIC: + { + switch (m_spellInfo->Id) + { + case 802: // Mutate Bug (AQ40, Emperor Vek'nilash) + case 804: // Explode Bug (AQ40, Emperor Vek'lor) + case 23138: // Gate of Shazzrah (MC, Shazzrah) + case 28560: // Summon Blizzard (Naxx, Sapphiron) + case 30541: // Blaze (Magtheridon) + case 30572: // Quake (Magtheridon) + case 30769: // Pick Red Riding Hood (Karazhan, Big Bad Wolf) + case 30835: // Infernal Relay (Karazhan, Prince Malchezaar) + case 31347: // Doom (Hyjal Summit, Azgalor) + case 32312: // Move 1 (Karazhan, Chess Event) + case 33711: // Murmur's Touch (Shadow Labyrinth, Murmur) + case 37388: // Move 2 (Karazhan, Chess Event) + case 38794: // Murmur's Touch (h) (Shadow Labyrinth, Murmur) + case 39338: // Karazhan - Chess, Medivh CHEAT: Hand of Medivh, Target Horde + case 39342: // Karazhan - Chess, Medivh CHEAT: Hand of Medivh, Target Alliance + case 40834: // Agonizing Flames (BT, Illidan Stormrage) + case 41537: // Summon Enslaved Soul (BT, Reliquary of Souls) + case 44869: // Spectral Blast (SWP, Kalecgos) + case 45391: // Summon Demonic Vapor (SWP, Felmyst) + case 45785: // Sinister Reflection Clone (SWP, Kil'jaeden) + case 45892: // Sinister Reflection (SWP, Kil'jaeden) + case 45976: // Open Portal (SWP, M'uru) + case 46372: // Ice Spear Target Picker (Slave Pens, Ahune) + case 47669: // Awaken Subboss (Utgarde Pinnacle) + case 48278: // Paralyze (Utgarde Pinnacle) + case 50742: // Ooze Combine (Halls of Stone) + case 50988: // Glare of the Tribunal (Halls of Stone) + case 51003: // Summon Dark Matter Target (Halls of Stone) + case 51146: // Summon Searing Gaze Target (Halls Of Stone) + case 52438: // Summon Skittering Swarmer (Azjol Nerub, Krik'thir the Gatewatcher) + case 52449: // Summon Skittering Infector (Azjol Nerub, Krik'thir the Gatewatcher) + case 53457: // Impale (Azjol Nerub, Anub'arak) + case 54148: // Ritual of the Sword (Utgarde Pinnacle, Svala) + case 55479: // Forced Obedience (Naxxramas, Razovius) + case 56140: // Summon Power Spark (Eye of Eternity, Malygos) + case 57578: // Lava Strike (Obsidian Sanctum, Sartharion) + case 59870: // Glare of the Tribunal (h) (Halls of Stone) + case 62016: // Charge Orb (Ulduar, Thorim) + case 62042: // Stormhammer (Ulduar, Thorim) + case 62166: // Stone Grip (Ulduar, Kologarn) + case 62301: // Cosmic Smash (Ulduar, Algalon) + case 62374: // Pursued (Ulduar, Flame Leviathan) + case 62488: // Activate Construct (Ulduar, Ignis) + case 62577: // Blizzard (Ulduar, Thorim) + case 62603: // Blizzard (h) (Ulduar, Thorim) + case 62797: // Storm Cloud (Ulduar, Hodir) + case 63018: // Searing Light (Ulduar, XT-002) + case 63024: // Gravity Bomb (Ulduar, XT-002) + case 63387: // Rapid Burst + case 63545: // Icicle (Ulduar, Hodir) + case 63795: // Psychosis (Ulduar, Yogg-Saron) + case 63820: // Summon Scrap Bot Trigger (Ulduar, Mimiron) use for Scrap Bots, hits npc 33856 + case 64218: // Overcharge (VoA, Emalon) + case 64234: // Gravity Bomb (h) (Ulduar, XT-002) + case 64425: // Summon Scrap Bot Trigger (Ulduar, Mimiron) use for Assault Bots, hits npc 33856 + case 64531: // Rapid Burst (h) + case 64543: // Melt Ice (Ulduar, Hodir) + case 65121: // Searing Light (h) (Ulduar, XT-002) + case 65301: // Psychosis (Ulduar, Yogg-Saron) + case 65872: // Pursuing Spikes (ToCrusader, Anub'arak) + case 65950: // Touch of Light (ToCrusader, Val'kyr Twins) + case 66001: // Touch of Darkness (ToCrusader, Val'kyr Twins) + case 66152: // Bullet Controller Summon Periodic Trigger Light (ToCrusader) + case 66153: // Bullet Controller Summon Periodic Trigger Dark (ToCrusader) + case 66332: // Nerubian Burrower (Mode 0) (ToCrusader, Anub'arak) + case 66336: // Mistress' Kiss (ToCrusader, Jaraxxus) + case 66339: // Summon Scarab (ToCrusader, Anub'arak) + case 67077: // Mistress' Kiss (Mode 2) (ToCrusader, Jaraxxus) + case 67281: // Touch of Darkness (Mode 1) + case 67282: // Touch of Darkness (Mode 2) + case 67283: // Touch of Darkness (Mode 3) + case 67296: // Touch of Light (Mode 1) + case 67297: // Touch of Light (Mode 2) + case 67298: // Touch of Light (Mode 3) + case 68912: // Wailing Souls (FoS) + case 68950: // Fear (FoS) + case 68987: // Pursuit (PoS) + case 69048: // Mirrored Soul (FoS) + case 69057: // Bone Spike Graveyard (Icecrown Citadel, Lord Marrowgar) 10 man + case 72088: + case 73142: + case 73144: + case 69140: // Coldflame (ICC, Marrowgar) + case 69674: // Mutated Infection (ICC, Rotface) + case 70450: // Blood Mirror + case 70837: // Blood Mirror + case 70882: // Slime Spray Summon Trigger (ICC, Rotface) + case 70920: // Unbound Plague Search Effect (ICC, Putricide) + case 71224: // Mutated Infection (Mode 1) + case 71445: // Twilight Bloodbolt + case 71471: // Twilight Bloodbolt + case 71837: // Vampiric Bite + case 71861: // Swarming Shadows + case 72091: // Frozen Orb (Vault of Archavon, Toravon) + case 72254: // Mark of Fallen Champion (target selection) (ICC, Deathbringer Saurfang) + case 73022: // Mutated Infection (Mode 2) + case 73023: // Mutated Infection (Mode 3) + unMaxTargets = 1; + break; + case 10258: // Awaken Vault Warder (Uldaman) + case 28542: // Life Drain (Naxx, Sapphiron) + case 62476: // Icicle (Ulduar, Hodir) + case 66013: // Penetrating Cold (10 man) (ToCrusader, Anub'arak) + case 67755: // Nerubian Burrower (Mode 1) (ToCrusader, Anub'arak) + case 67756: // Nerubian Burrower (Mode 2) (ToCrusader, Anub'arak) + case 68509: // Penetrating Cold (10 man heroic) + case 69055: // Bone Slice (ICC, Lord Marrowgar) + case 69278: // Gas spore (ICC, Festergut) + case 70341: // Slime Puddle (ICC, Putricide) + case 71336: // Pact of the Darkfallen + case 71390: // Pact of the Darkfallen + unMaxTargets = 2; + break; + case 28796: // Poison Bolt Volley (Naxx, Faerlina) + case 29213: // Curse of the Plaguebringer (Naxx, Noth the Plaguebringer) + case 30004: // Flame Wreath (Karazhan, Shade of Aran) + case 31298: // Sleep (Hyjal Summit, Anetheron) + case 39341: // Karazhan - Chess, Medivh CHEAT: Fury of Medivh, Target Horde + case 39344: // Karazhan - Chess, Medivh CHEAT: Fury of Medivh, Target Alliance + case 39992: // Needle Spine Targeting (BT, Warlord Najentus) + case 40869: // Fatal Attraction (BT, Mother Shahraz) + case 41303: // Soul Drain (BT, Reliquary of Souls) + case 41376: // Spite (BT, Reliquary of Souls) + case 51904: // Summon Ghouls On Scarlet Crusade + case 54522: // Summon Ghouls On Scarlet Crusade + case 60936: // Surge of Power (h) (Malygos) + case 61693: // Arcane Storm (Malygos) + case 62477: // Icicle (h) (Ulduar, Hodir) + case 63981: // StoneGrip (h) (Ulduar, Kologarn) + case 64598: // Cosmic Smash (h) (Ulduar, Algalon) + case 64620: // Summon Fire Bot Trigger (Ulduar, Mimiron) hits npc 33856 + case 70814: // Bone Slice (ICC, Lord Marrowgar, heroic) + case 72095: // Frozen Orb (h) (Vault of Archavon, Toravon) + case 72089: // Bone Spike Graveyard (Icecrown Citadel, Lord Marrowgar) 25 man + case 70826: + case 73143: + case 73145: + unMaxTargets = 3; + break; + case 37676: // Insidious Whisper (SSC, Leotheras the Blind) + case 38028: // Watery Grave (SSC, Morogrim Tidewalker) + case 46650: // Open Brutallus Back Door (SWP, Felmyst) + case 67757: // Nerubian Burrower (Mode 3) (ToCrusader, Anub'arak) + case 71221: // Gas spore (Mode 1) (ICC, Festergut) + unMaxTargets = 4; + break; + case 30843: // Enfeeble (Karazhan, Prince Malchezaar) + case 40243: // Crushing Shadows (BT, Teron Gorefiend) + case 42005: // Bloodboil (BT, Gurtogg Bloodboil) + case 45641: // Fire Bloom (SWP, Kil'jaeden) + case 55665: // Life Drain (h) (Naxx, Sapphiron) + case 58917: // Consume Minions + case 64604: // Nature Bomb (Ulduar, Freya) + case 67076: // Mistress' Kiss (Mode 1) (ToCrusader, Jaraxxus) + case 67078: // Mistress' Kiss (Mode 3) (ToCrusader, Jaraxxus) + case 67700: // Penetrating Cold (25 man) + case 68510: // Penetrating Cold (25 man, heroic) + unMaxTargets = 5; + break; + case 61694: // Arcane Storm (h) (Malygos) + unMaxTargets = 7; + break; + case 38054: // Random Rocket Missile + unMaxTargets = 8; + break; + case 54098: // Poison Bolt Volley (h) (Naxx, Faerlina) + case 54835: // Curse of the Plaguebringer (h) (Naxx, Noth the Plaguebringer) + unMaxTargets = 10; + break; + case 25991: // Poison Bolt Volley (AQ40, Pincess Huhuran) + unMaxTargets = 15; + break; + case 61916: // Lightning Whirl (Ulduar, Stormcaller Brundir) + unMaxTargets = urand(2, 3); + break; + case 46771: // Flame Sear (SWP, Grand Warlock Alythess) + unMaxTargets = urand(3, 5); + break; + case 63482: // Lightning Whirl (h) (Ulduar, Stormcaller Brundir) + unMaxTargets = urand(3, 6); + break; + case 74452: // Conflagration (Saviana, Ruby Sanctum) + { + if (m_caster) + { + switch (m_caster->GetMap()->GetDifficulty()) + { + case RAID_DIFFICULTY_10MAN_NORMAL: + case RAID_DIFFICULTY_10MAN_HEROIC: + unMaxTargets = 2; + break; + case RAID_DIFFICULTY_25MAN_NORMAL: + case RAID_DIFFICULTY_25MAN_HEROIC: + unMaxTargets = 5; + break; + } + } + break; + } + default: + break; + } + break; + } + case SPELLFAMILY_MAGE: + { + if (m_spellInfo->Id == 38194) // Blink + unMaxTargets = 1; + break; + } + case SPELLFAMILY_WARRIOR: + { + // Sunder Armor (main spell) + if (m_spellInfo->IsFitToFamilyMask(UI64LIT(0x0000000000004000), 0x00000000) && m_spellInfo->SpellVisual[0] == 406) + if (m_caster->HasAura(58387)) // Glyph of Sunder Armor + EffectChainTarget = 2; + break; + } + case SPELLFAMILY_DRUID: + { + // Starfall + if (m_spellInfo->IsFitToFamilyMask(UI64LIT(0x0000000000000000), 0x00000100)) + unMaxTargets = 2; + break; + } + case SPELLFAMILY_DEATHKNIGHT: + { + if (m_spellInfo->SpellIconID == 1737) // Corpse Explosion // TODO - spell 50445? + unMaxTargets = 1; + break; + } + case SPELLFAMILY_PALADIN: + if (m_spellInfo->Id == 20424) // Seal of Command (2 more target for single targeted spell) + { + // overwrite EffectChainTarget for non single target spell + if (Spell* currSpell = m_caster->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + for(int i = 0; i < MAX_EFFECT_INDEX; ++i) + if(SpellEffectEntry const* spellEffect = currSpell->m_spellInfo->GetSpellEffect(SpellEffectIndex(i))) + if(spellEffect->EffectChainTarget > 0) + EffectChainTarget = 0; // no chain targets + if (currSpell->m_spellInfo->GetMaxAffectedTargets() > 0) + EffectChainTarget = 0; // no chain targets + } + } + break; + default: + break; + } + + // custom radius cases + switch (m_spellInfo->GetSpellFamilyName()) + { + case SPELLFAMILY_GENERIC: + { + switch (m_spellInfo->Id) + { + case 24811: // Draw Spirit (Lethon) + { + if (spellEffect->EffectIndex == EFFECT_INDEX_0) // Copy range from EFF_1 to 0 + radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(spellEffect->GetRadiusIndex())); + break; + } + case 28241: // Poison (Naxxramas, Grobbulus Cloud) + case 54363: // Poison (Naxxramas, Grobbulus Cloud) (H) + { + uint32 auraId = (m_spellInfo->Id == 28241 ? 28158 : 54362); + if (SpellAuraHolder* auraHolder = m_caster->GetSpellAuraHolder(auraId)) + radius = 0.5f * (60000 - auraHolder->GetAuraDuration()) * 0.001f; + break; + } + case 66881: // Slime Pool (ToCrusader, Acidmaw & Dreadscale) + case 67638: // Slime Pool (ToCrusader, Acidmaw & Dreadscale) (Mode 1) + case 67639: // Slime Pool (ToCrusader, Acidmaw & Dreadscale) (Mode 2) + case 67640: // Slime Pool (ToCrusader, Acidmaw & Dreadscale) (Mode 3) + if (SpellAuraHolder* auraHolder = m_caster->GetSpellAuraHolder(66882)) + radius = 0.5f * (60000 - auraHolder->GetAuraDuration()) * 0.001f; + break; + case 56438: // Arcane Overload + if (Unit* realCaster = GetAffectiveCaster()) + radius = radius * realCaster->GetObjectScale(); + break; + case 69057: // Bone Spike Graveyard (Icecrown Citadel, Lord Marrowgar encounter, 10N) + case 70826: // Bone Spike Graveyard (Icecrown Citadel, Lord Marrowgar encounter, 25N) + case 72088: // Bone Spike Graveyard (Icecrown Citadel, Lord Marrowgar encounter, 10H) + case 72089: // Bone Spike Graveyard (Icecrown Citadel, Lord Marrowgar encounter, 25H) + case 73142: // Bone Spike Graveyard (during Bone Storm) (Icecrown Citadel, Lord Marrowgar encounter, 10N) + case 73143: // Bone Spike Graveyard (during Bone Storm) (Icecrown Citadel, Lord Marrowgar encounter, 25N) + case 73144: // Bone Spike Graveyard (during Bone Storm) (Icecrown Citadel, Lord Marrowgar encounter, 10H) + case 73145: // Bone Spike Graveyard (during Bone Storm) (Icecrown Citadel, Lord Marrowgar encounter, 25H) + radius = DEFAULT_VISIBILITY_INSTANCE; + break; + default: + break; + } + break; + } + case SPELLFAMILY_DRUID: + { + switch (m_spellInfo->Id) + { + case 49376: // Feral Charge - Cat + // No default radius for this spell, so we need to use the contact distance + radius = CONTACT_DISTANCE; + break; + } + } + default: + break; + } +} diff --git a/src/game/Spell.h b/src/game/Spell.h new file mode 100644 index 000000000..31532b7ad --- /dev/null +++ b/src/game/Spell.h @@ -0,0 +1,937 @@ +/** + * This code is part of MaNGOS. Contributor & Copyright details are in AUTHORS/THANKS. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SPELL_H +#define __SPELL_H + +#include "Common.h" +#include "GridDefines.h" +#include "SharedDefines.h" +#include "DBCEnums.h" +#include "ObjectGuid.h" +#include "LootMgr.h" +#include "Unit.h" +#include "Player.h" + +class WorldSession; +class WorldPacket; +class DynamicObj; +class Item; +class GameObject; +class Group; +class Aura; + +enum SpellCastFlags +{ + CAST_FLAG_NONE = 0x00000000, + CAST_FLAG_HIDDEN_COMBATLOG = 0x00000001, // hide in combat log? + CAST_FLAG_UNKNOWN2 = 0x00000002, + CAST_FLAG_UNKNOWN3 = 0x00000004, + CAST_FLAG_UNKNOWN4 = 0x00000008, + CAST_FLAG_UNKNOWN5 = 0x00000010, + CAST_FLAG_AMMO = 0x00000020, // Projectiles visual + CAST_FLAG_UNKNOWN7 = 0x00000040, // !0x41 mask used to call CGTradeSkillInfo::DoRecast + CAST_FLAG_UNKNOWN8 = 0x00000080, + CAST_FLAG_UNKNOWN9 = 0x00000100, + CAST_FLAG_UNKNOWN10 = 0x00000200, + CAST_FLAG_UNKNOWN11 = 0x00000400, + CAST_FLAG_PREDICTED_POWER = 0x00000800, // wotlk, trigger rune cooldown + CAST_FLAG_UNKNOWN13 = 0x00001000, + CAST_FLAG_UNKNOWN14 = 0x00002000, + CAST_FLAG_UNKNOWN15 = 0x00004000, + CAST_FLAG_UNKNOWN16 = 0x00008000, + CAST_FLAG_UNKNOWN17 = 0x00010000, + CAST_FLAG_ADJUST_MISSILE = 0x00020000, // wotlk + CAST_FLAG_UNKNOWN19 = 0x00040000, // spell cooldown related (may be category cooldown) + CAST_FLAG_VISUAL_CHAIN = 0x00080000, // wotlk + CAST_FLAG_UNKNOWN21 = 0x00100000, + CAST_FLAG_PREDICTED_RUNES = 0x00200000, // wotlk, rune cooldown list + CAST_FLAG_IMMUNITY = 0x04000000, // spell cast school imminity info + CAST_FLAG_UNKNOWN24 = 0x08000000, + CAST_FLAG_UNKNOWN25 = 0x10000000, + CAST_FLAG_UNKNOWN26 = 0x20000000, + CAST_FLAG_HEAL_PREDICTION = 0x40000000, // heal prediction + CAST_FLAG_UNKNOWN28 = 0x80000000, +}; + +enum SpellFlags +{ + SPELL_FLAG_NORMAL = 0x00, + SPELL_FLAG_REFLECTED = 0x01, // reflected spell + SPELL_FLAG_REDIRECTED = 0x02 // redirected spell +}; + +enum SpellNotifyPushType +{ + PUSH_IN_FRONT, + PUSH_IN_FRONT_90, + PUSH_IN_FRONT_30, + PUSH_IN_FRONT_15, + PUSH_IN_BACK, + PUSH_SELF_CENTER, + PUSH_DEST_CENTER, + PUSH_TARGET_CENTER +}; + +bool IsQuestTameSpell(uint32 spellId); + +namespace MaNGOS +{ + struct SpellNotifierPlayer; + struct SpellNotifierCreatureAndPlayer; +} + +class SpellCastTargets; + +struct SpellCastTargetsReader +{ + explicit SpellCastTargetsReader(SpellCastTargets& _targets, Unit* _caster) : targets(_targets), caster(_caster) {} + + SpellCastTargets& targets; + Unit* caster; +}; + +class SpellCastTargets +{ + public: + SpellCastTargets(); + ~SpellCastTargets(); + + void read(ByteBuffer& data, Unit* caster); + void write(ByteBuffer& data) const; + + SpellCastTargetsReader ReadForCaster(Unit* caster) { return SpellCastTargetsReader(*this, caster); } + void ReadAdditionalData(WorldPacket& data, uint8& cast_flags); + + SpellCastTargets& operator=(const SpellCastTargets& target) + { + m_unitTarget = target.m_unitTarget; + m_itemTarget = target.m_itemTarget; + m_GOTarget = target.m_GOTarget; + + m_unitTargetGUID = target.m_unitTargetGUID; + m_GOTargetGUID = target.m_GOTargetGUID; + m_CorpseTargetGUID = target.m_CorpseTargetGUID; + m_itemTargetGUID = target.m_itemTargetGUID; + m_srcTransportGUID = target.m_srcTransportGUID; + m_destTransportGUID = target.m_destTransportGUID; + + m_itemTargetEntry = target.m_itemTargetEntry; + + m_srcX = target.m_srcX; + m_srcY = target.m_srcY; + m_srcZ = target.m_srcZ; + + m_destX = target.m_destX; + m_destY = target.m_destY; + m_destZ = target.m_destZ; + + m_strTarget = target.m_strTarget; + + m_targetMask = target.m_targetMask; + + m_elevation = target.m_elevation; + m_speed = target.m_speed; + + return *this; + } + + void setUnitTarget(Unit* target); + ObjectGuid getUnitTargetGuid() const { return m_unitTargetGUID; } + Unit* getUnitTarget() const { return m_unitTarget; } + + void setDestination(float x, float y, float z); + void setSource(float x, float y, float z); + void getDestination(float& x, float& y, float& z) const { x = m_destX; y = m_destY; z = m_destZ; } + void getSource(float& x, float& y, float& z) const { x = m_srcX; y = m_srcY, z = m_srcZ; } + + void setGOTarget(GameObject* target); + ObjectGuid getGOTargetGuid() const { return m_GOTargetGUID; } + GameObject* getGOTarget() const { return m_GOTarget; } + + void setCorpseTarget(Corpse* corpse); + ObjectGuid getCorpseTargetGuid() const { return m_CorpseTargetGUID; } + + void setItemTarget(Item* item); + ObjectGuid getItemTargetGuid() const { return m_itemTargetGUID; } + Item* getItemTarget() const { return m_itemTarget; } + uint32 getItemTargetEntry() const { return m_itemTargetEntry; } + + void setTradeItemTarget(Player* caster); + + void updateTradeSlotItem() + { + if (m_itemTarget && (m_targetMask & TARGET_FLAG_TRADE_ITEM)) + { + m_itemTargetGUID = m_itemTarget->GetObjectGuid(); + m_itemTargetEntry = m_itemTarget->GetEntry(); + } + } + + bool IsEmpty() const { return !m_GOTargetGUID && !m_unitTargetGUID && !m_itemTarget && !m_CorpseTargetGUID; } + + void Update(Unit* caster); + + float m_srcX, m_srcY, m_srcZ; + float m_destX, m_destY, m_destZ; + std::string m_strTarget; + + float GetElevation() const { return m_elevation; } + float GetSpeed() const { return m_speed; } + + uint32 m_targetMask; + + private: + // objects (can be used at spell creating and after Update at casting + Unit* m_unitTarget; + GameObject* m_GOTarget; + Item* m_itemTarget; + + // object GUID/etc, can be used always + ObjectGuid m_unitTargetGUID; + ObjectGuid m_GOTargetGUID; + ObjectGuid m_CorpseTargetGUID; + ObjectGuid m_itemTargetGUID; + ObjectGuid m_srcTransportGUID; + ObjectGuid m_destTransportGUID; + uint32 m_itemTargetEntry; + + float m_elevation; + float m_speed; +}; + +inline ByteBuffer& operator<< (ByteBuffer& buf, SpellCastTargets const& targets) +{ + targets.write(buf); + return buf; +} + +inline ByteBuffer& operator>> (ByteBuffer& buf, SpellCastTargetsReader const& targets) +{ + targets.targets.read(buf, targets.caster); + return buf; +} + +enum SpellState +{ + SPELL_STATE_PREPARING = 0, // cast time delay period, non channeled spell + SPELL_STATE_CASTING = 1, // channeled time period spell casting state + SPELL_STATE_FINISHED = 2, // cast finished to success or fail + SPELL_STATE_DELAYED = 3 // spell casted but need time to hit target(s) +}; + +enum SpellTargets +{ + SPELL_TARGETS_HOSTILE, + SPELL_TARGETS_NOT_FRIENDLY, + SPELL_TARGETS_NOT_HOSTILE, + SPELL_TARGETS_FRIENDLY, + SPELL_TARGETS_AOE_DAMAGE, + SPELL_TARGETS_ALL +}; + +typedef std::multimap SpellTargetTimeMap; + +class Spell +{ + friend struct MaNGOS::SpellNotifierPlayer; + friend struct MaNGOS::SpellNotifierCreatureAndPlayer; + friend void Unit::SetCurrentCastedSpell(Spell* pSpell); + + public: + void EffectEmpty(SpellEffectEntry const* effect); + void EffectNULL(SpellEffectEntry const* effect); + void EffectUnused(SpellEffectEntry const* effect); + void EffectDistract(SpellEffectEntry const* effect); + void EffectPull(SpellEffectEntry const* effect); + void EffectSchoolDMG(SpellEffectEntry const* effect); + void EffectEnvironmentalDMG(SpellEffectEntry const* effect); + void EffectInstaKill(SpellEffectEntry const* effect); + void EffectDummy(SpellEffectEntry const* effect); + void EffectTeleportUnits(SpellEffectEntry const* effect); + void EffectApplyAura(SpellEffectEntry const* effect); + void EffectSendEvent(SpellEffectEntry const* effect); + void EffectPowerBurn(SpellEffectEntry const* effect); + void EffectPowerDrain(SpellEffectEntry const* effect); + void EffectHeal(SpellEffectEntry const* effect); + void EffectBind(SpellEffectEntry const* effect); + void EffectHealthLeech(SpellEffectEntry const* effect); + void EffectQuestComplete(SpellEffectEntry const* effect); + void EffectCreateItem(SpellEffectEntry const* effect); + void EffectCreateItem2(SpellEffectEntry const* effect); + void EffectCreateRandomItem(SpellEffectEntry const* effect); + void EffectPersistentAA(SpellEffectEntry const* effect); + void EffectEnergize(SpellEffectEntry const* effect); + void EffectOpenLock(SpellEffectEntry const* effect); + void EffectSummonChangeItem(SpellEffectEntry const* effect); + void EffectProficiency(SpellEffectEntry const* effect); + void EffectApplyAreaAura(SpellEffectEntry const* effect); + void EffectSummonType(SpellEffectEntry const* effect); + void EffectLearnSpell(SpellEffectEntry const* effect); + void EffectDispel(SpellEffectEntry const* effect); + void EffectDualWield(SpellEffectEntry const* effect); + void EffectPickPocket(SpellEffectEntry const* effect); + void EffectAddFarsight(SpellEffectEntry const* effect); + void EffectHealMechanical(SpellEffectEntry const* effect); + void EffectJump(SpellEffectEntry const* effect); + void EffectTeleUnitsFaceCaster(SpellEffectEntry const* effect); + void EffectLearnSkill(SpellEffectEntry const* effect); + void EffectTradeSkill(SpellEffectEntry const* effect); + void EffectEnchantItemPerm(SpellEffectEntry const* effect); + void EffectEnchantItemTmp(SpellEffectEntry const* effect); + void EffectTameCreature(SpellEffectEntry const* effect); + void EffectSummonPet(SpellEffectEntry const* effect); + void EffectLearnPetSpell(SpellEffectEntry const* effect); + void EffectWeaponDmg(SpellEffectEntry const* effect); + void EffectClearQuest(SpellEffectEntry const* effect); + void EffectForceCast(SpellEffectEntry const* effect); + void EffectTriggerSpell(SpellEffectEntry const* effect); + void EffectTriggerMissileSpell(SpellEffectEntry const* effect); + void EffectThreat(SpellEffectEntry const* effect); + void EffectRestoreItemCharges(SpellEffectEntry const* effect); + void EffectHealMaxHealth(SpellEffectEntry const* effect); + void EffectInterruptCast(SpellEffectEntry const* effect); + void EffectSummonObjectWild(SpellEffectEntry const* effect); + void EffectScriptEffect(SpellEffectEntry const* effect); + void EffectSanctuary(SpellEffectEntry const* effect); + void EffectAddComboPoints(SpellEffectEntry const* effect); + void EffectDuel(SpellEffectEntry const* effect); + void EffectStuck(SpellEffectEntry const* effect); + void EffectSummonPlayer(SpellEffectEntry const* effect); + void EffectActivateObject(SpellEffectEntry const* effect); + void EffectApplyGlyph(SpellEffectEntry const* effect); + void EffectEnchantHeldItem(SpellEffectEntry const* effect); + void EffectSummonObject(SpellEffectEntry const* effect); + void EffectResurrect(SpellEffectEntry const* effect); + void EffectParry(SpellEffectEntry const* effect); + void EffectBlock(SpellEffectEntry const* effect); + void EffectLeapForward(SpellEffectEntry const* effect); + void EffectLeapBack(SpellEffectEntry const* effect); + void EffectTransmitted(SpellEffectEntry const* effect); + void EffectDisEnchant(SpellEffectEntry const* effect); + void EffectInebriate(SpellEffectEntry const* effect); + void EffectFeedPet(SpellEffectEntry const* effect); + void EffectDismissPet(SpellEffectEntry const* effect); + void EffectReputation(SpellEffectEntry const* effect); + void EffectSelfResurrect(SpellEffectEntry const* effect); + void EffectSkinning(SpellEffectEntry const* effect); + void EffectCharge(SpellEffectEntry const* effect); + void EffectCharge2(SpellEffectEntry const* effect); + void EffectProspecting(SpellEffectEntry const* effect); + void EffectRedirectThreat(SpellEffectEntry const* effect); + void EffectMilling(SpellEffectEntry const* effect); + void EffectRenamePet(SpellEffectEntry const* effect); + void EffectSendTaxi(SpellEffectEntry const* effect); + void EffectKnockBack(SpellEffectEntry const* effect); + void EffectPlayerPull(SpellEffectEntry const* effect); + void EffectDispelMechanic(SpellEffectEntry const* effect); + void EffectSummonDeadPet(SpellEffectEntry const* effect); + void EffectSummonAllTotems(SpellEffectEntry const* effect); + void EffectBreakPlayerTargeting (SpellEffectEntry const* effect); + void EffectDestroyAllTotems(SpellEffectEntry const* effect); + void EffectDurabilityDamage(SpellEffectEntry const* effect); + void EffectSkill(SpellEffectEntry const* effect); + void EffectTaunt(SpellEffectEntry const* effect); + void EffectDurabilityDamagePCT(SpellEffectEntry const* effect); + void EffectModifyThreatPercent(SpellEffectEntry const* effect); + void EffectResurrectNew(SpellEffectEntry const* effect); + void EffectAddExtraAttacks(SpellEffectEntry const* effect); + void EffectSpiritHeal(SpellEffectEntry const* effect); + void EffectSkinPlayerCorpse(SpellEffectEntry const* effect); + void EffectStealBeneficialBuff(SpellEffectEntry const* effect); + void EffectUnlearnSpecialization(SpellEffectEntry const* effect); + void EffectHealPct(SpellEffectEntry const* effect); + void EffectEnergisePct(SpellEffectEntry const* effect); + void EffectTriggerSpellWithValue(SpellEffectEntry const* effect); + void EffectTriggerRitualOfSummoning(SpellEffectEntry const* effect); + void EffectKillCreditPersonal(SpellEffectEntry const* effect); + void EffectKillCreditGroup(SpellEffectEntry const* effect); + void EffectQuestFail(SpellEffectEntry const* effect); + void EffectQuestOffer(SpellEffectEntry const* effect); + void EffectActivateRune(SpellEffectEntry const* effect); + void EffectTeachTaxiNode(SpellEffectEntry const* effect); + void EffectWMODamage(SpellEffectEntry const* effect); + void EffectWMORepair(SpellEffectEntry const* effect); + void EffectWMOChange(SpellEffectEntry const* effect); + void EffectTitanGrip(SpellEffectEntry const* effect); + void EffectEnchantItemPrismatic(SpellEffectEntry const* effect); + void EffectPlaySound(SpellEffectEntry const* effect); + void EffectPlayMusic(SpellEffectEntry const* effect); + void EffectSpecCount(SpellEffectEntry const* effect); + void EffectActivateSpec(SpellEffectEntry const* effect); + void EffectCancelAura(SpellEffectEntry const* effect); + void EffectKnockBackFromPosition(SpellEffectEntry const* effect); + + Spell(Unit* caster, SpellEntry const* info, bool triggered, ObjectGuid originalCasterGUID = ObjectGuid(), SpellEntry const* triggeredBy = NULL); + ~Spell(); + + void prepare(SpellCastTargets const* targets, Aura* triggeredByAura = NULL); + + void cancel(); + + void update(uint32 difftime); + void cast(bool skipCheck = false); + void finish(bool ok = true); + void TakePower(); + void TakeRunePower(bool hit); + void TakeAmmo(); + void TakeReagents(); + void TakeCastItem(); + + SpellCastResult CheckCast(bool strict); + SpellCastResult CheckPetCast(Unit* target); + + // handlers + void handle_immediate(); + uint64 handle_delayed(uint64 t_offset); + // handler helpers + void _handle_immediate_phase(); + void _handle_finish_phase(); + + SpellCastResult CheckItems(); + SpellCastResult CheckRange(bool strict); + SpellCastResult CheckPower(); + SpellCastResult CheckRunePower(); + SpellCastResult CheckCasterAuras() const; + + int32 CalculateDamage(SpellEffectIndex i, Unit* target) { return m_caster->CalculateSpellDamage(target, m_spellInfo, i, &m_currentBasePoints[i]); } + static uint32 CalculatePowerCost(SpellEntry const* spellInfo, Unit* caster, Spell const* spell = NULL, Item* castItem = NULL); + + bool HaveTargetsForEffect(SpellEffectIndex effect) const; + void Delayed(); + void DelayedChannel(); + uint32 getState() const { return m_spellState; } + void setState(uint32 state) { m_spellState = state; } + + void DoCreateItem(SpellEffectEntry const* effect, uint32 itemtype); + + void WriteSpellGoTargets(WorldPacket* data); + void WriteAmmoToPacket(WorldPacket* data); + + template WorldObject* FindCorpseUsing(); + + bool CheckTarget(Unit* target, SpellEffectIndex eff); + bool CanAutoCast(Unit* target); + + static void MANGOS_DLL_SPEC SendCastResult(Player* caster, SpellEntry const* spellInfo, uint8 cast_count, SpellCastResult result, bool isPetCastResult = false); + void SendCastResult(SpellCastResult result); + void SendSpellStart(); + void SendSpellGo(); + void SendSpellCooldown(); + void SendLogExecute(); + void SendInterrupted(uint8 result); + void SendChannelUpdate(uint32 time); + void SendChannelStart(uint32 duration); + void SendResurrectRequest(Player* target); + void SendPlaySpellVisual(uint32 SpellID); + + void HandleEffects(Unit* pUnitTarget, Item* pItemTarget, GameObject* pGOTarget, SpellEffectIndex i, float DamageMultiplier = 1.0); + void HandleThreatSpells(); + // void HandleAddAura(Unit* Target); + + SpellEntry const* m_spellInfo; + SpellEntry const* m_triggeredBySpellInfo; + SpellInterruptsEntry const* m_spellInterrupts; + int32 m_currentBasePoints[MAX_EFFECT_INDEX]; // cache SpellEntry::CalculateSimpleValue and use for set custom base points + Item* m_CastItem; + uint8 m_cast_count; + uint32 m_glyphIndex; + SpellCastTargets m_targets; + + int32 GetCastTime() const { return m_casttime; } + uint32 GetCastedTime() { return m_timer; } + bool IsAutoRepeat() const { return m_autoRepeat; } + void SetAutoRepeat(bool rep) { m_autoRepeat = rep; } + void ReSetTimer() { m_timer = m_casttime > 0 ? m_casttime : 0; } + bool IsNextMeleeSwingSpell() const + { + return m_spellInfo->HasAttribute(SPELL_ATTR_ON_NEXT_SWING_1) || m_spellInfo->HasAttribute(SPELL_ATTR_ON_NEXT_SWING_2); + } + bool IsRangedSpell() const + { + return m_spellInfo->HasAttribute(SPELL_ATTR_RANGED); + } + bool IsChannelActive() const { return m_caster->GetUInt32Value(UNIT_CHANNEL_SPELL) != 0; } + bool IsMeleeAttackResetSpell() const { return !m_IsTriggeredSpell && m_spellInterrupts && (m_spellInterrupts->InterruptFlags & SPELL_INTERRUPT_FLAG_AUTOATTACK); } + bool IsRangedAttackResetSpell() const { return !m_IsTriggeredSpell && IsRangedSpell() && m_spellInterrupts && (m_spellInterrupts->InterruptFlags & SPELL_INTERRUPT_FLAG_AUTOATTACK); } + + bool IsDeletable() const { return !m_referencedFromCurrentSpell && !m_executedCurrently; } + void SetReferencedFromCurrent(bool yes) { m_referencedFromCurrentSpell = yes; } + void SetExecutedCurrently(bool yes) { m_executedCurrently = yes; } + uint64 GetDelayStart() const { return m_delayStart; } + void SetDelayStart(uint64 m_time) { m_delayStart = m_time; } + uint64 GetDelayMoment() const { return m_delayMoment; } + + bool IsNeedSendToClient() const; // use for hide spell cast for client in case when cast not have client side affect (animation or log entries) + bool IsTriggeredSpellWithRedundentCastTime() const; // use for ignore some spell data for triggered spells like cast time, some triggered spells have redundent copy data from main spell for client use purpose + + CurrentSpellTypes GetCurrentContainer(); + + // caster types: + // formal spell caster, in game source of spell affects cast + Unit* GetCaster() const { return m_caster; } + // real source of cast affects, explicit caster, or DoT/HoT applier, or GO owner, or wild GO itself. Can be NULL + WorldObject* GetAffectiveCasterObject() const; + // limited version returning NULL in cases wild gameobject caster object, need for Aura (auras currently not support non-Unit caster) + Unit* GetAffectiveCaster() const { return m_originalCasterGUID ? m_originalCaster : m_caster; } + // m_originalCasterGUID can store GO guid, and in this case this is visual caster + WorldObject* GetCastingObject() const; + + uint32 GetPowerCost() const { return m_powerCost; } + uint32 GetUsedHolyPower() const { return m_usedHolyPower; } + + void UpdatePointers(); // must be used at call Spell code after time delay (non triggered spell cast/update spell call/etc) + + bool CheckTargetCreatureType(Unit* target) const; + + void AddTriggeredSpell(SpellEntry const* spellInfo) { m_TriggerSpells.push_back(spellInfo); } + void AddPrecastSpell(SpellEntry const* spellInfo) { m_preCastSpells.push_back(spellInfo); } + void AddTriggeredSpell(uint32 spellId); + void AddPrecastSpell(uint32 spellId); + void CastPreCastSpells(Unit* target); + void CastTriggerSpells(); + + void CleanupTargetList(); + void ClearCastItem(); + + static void SelectMountByAreaAndSkill(Unit* target, SpellEntry const* parentSpell, uint32 spellId75, uint32 spellId150, uint32 spellId225, uint32 spellId300, uint32 spellIdSpecial); + + typedef std::list UnitList; + + protected: + bool HasGlobalCooldown(); + void TriggerGlobalCooldown(); + void CancelGlobalCooldown(); + + void SendLoot(ObjectGuid guid, LootType loottype, LockType lockType); + bool IgnoreItemRequirements() const; // some item use spells have unexpected reagent data + void UpdateOriginalCasterPointer(); + + Unit* m_caster; + + ObjectGuid m_originalCasterGUID; // real source of cast (aura caster/etc), used for spell targets selection + // e.g. damage around area spell trigered by victim aura and da,age emeies of aura caster + Unit* m_originalCaster; // cached pointer for m_originalCaster, updated at Spell::UpdatePointers() + + Spell** m_selfContainer; // pointer to our spell container (if applicable) + + // Spell data + SpellSchoolMask m_spellSchoolMask; // Spell school (can be overwrite for some spells (wand shoot for example) + WeaponAttackType m_attackType; // For weapon based attack + uint32 m_powerCost; // Calculated spell cost initialized only in Spell::prepare + uint32 m_usedHolyPower; + int32 m_casttime; // Calculated spell cast time initialized only in Spell::prepare + int32 m_duration; + bool m_canReflect; // can reflect this spell? + uint8 m_spellFlags; // for spells whose target was changed in cast i.e. due to reflect + bool m_autoRepeat; + uint8 m_runesState; + + uint8 m_delayAtDamageCount; + bool isDelayableNoMore() + { + if (m_delayAtDamageCount >= 2) + return true; + + ++m_delayAtDamageCount; + return false; + } + + // Delayed spells system + uint64 m_delayStart; // time of spell delay start, filled by event handler, zero = just started + uint64 m_delayMoment; // moment of next delay call, used internally + bool m_immediateHandled; // were immediate actions handled? (used by delayed spells only) + + // These vars are used in both delayed spell system and modified immediate spell system + bool m_referencedFromCurrentSpell; // mark as references to prevent deleted and access by dead pointers + bool m_executedCurrently; // mark as executed to prevent deleted and access by dead pointers + bool m_needSpellLog; // need to send spell log? + uint8 m_applyMultiplierMask; // by effect: damage multiplier needed? + float m_damageMultipliers[3]; // by effect: damage multiplier + + // Current targets, to be used in SpellEffects (MUST BE USED ONLY IN SPELL EFFECTS) + Unit* unitTarget; + Item* itemTarget; + GameObject* gameObjTarget; + SpellAuraHolder* m_spellAuraHolder; // spell aura holder for current target, created only if spell has aura applying effect + int32 damage; + + // this is set in Spell Hit, but used in Apply Aura handler + DiminishingLevels m_diminishLevel; + DiminishingGroup m_diminishGroup; + + // ------------------------------------------- + GameObject* focusObject; + + // Damage and healing in effects need just calculate + int32 m_damage; // Damage in effects count here + int32 m_healing; // Healing in effects count here + int32 m_healthLeech; // Health leech in effects for all targets count here + + //****************************************** + // Spell trigger system + //****************************************** + bool m_canTrigger; // Can start trigger (m_IsTriggeredSpell can`t use for this) + uint8 m_negativeEffectMask; // Use for avoid sent negative spell procs for additional positive effects only targets + uint32 m_procAttacker; // Attacker trigger flags + uint32 m_procVictim; // Victim trigger flags + void prepareDataForTriggerSystem(); + + //***************************************** + // Spell target filling + //***************************************** + + void FillTargetMap(); + void SetTargetMap(SpellEffectIndex effIndex, uint32 targetMode, UnitList& targetUnitMap); + + void FillAreaTargets(UnitList& targetUnitMap, float radius, SpellNotifyPushType pushType, SpellTargets spellTargets, WorldObject* originalCaster = NULL); + void FillRaidOrPartyTargets(UnitList& targetUnitMap, Unit* member, Unit* center, float radius, bool raid, bool withPets, bool withcaster); + void FillRaidOrPartyManaPriorityTargets(UnitList& targetUnitMap, Unit* member, Unit* center, float radius, uint32 count, bool raid, bool withPets, bool withcaster); + void FillRaidOrPartyHealthPriorityTargets(UnitList& targetUnitMap, Unit* member, Unit* center, float radius, uint32 count, bool raid, bool withPets, bool withcaster); + + // Returns a target that was filled by SPELL_SCRIPT_TARGET (or selected victim) Can return NULL + Unit* GetPrefilledUnitTargetOrUnitTarget(SpellEffectIndex effIndex) const; + void GetSpellRangeAndRadius(SpellEffectEntry const* spellEffect, float& radius, uint32& EffectChainTarget, uint32& unMaxTargets) const; + + //***************************************** + // Spell target subsystem + //***************************************** + // Targets store structures and data + struct TargetInfo + { + ObjectGuid targetGUID; + uint64 timeDelay; + uint32 HitInfo; + uint32 damage; + SpellMissInfo missCondition: 8; + SpellMissInfo reflectResult: 8; + uint8 effectMask: 8; + bool processed: 1; + }; + uint8 m_needAliveTargetMask; // Mask req. alive targets + + struct GOTargetInfo + { + ObjectGuid targetGUID; + uint64 timeDelay; + uint8 effectMask: 8; + bool processed: 1; + }; + + struct ItemTargetInfo + { + Item* item; + uint8 effectMask; + }; + + typedef std::list TargetList; + typedef std::list GOTargetList; + typedef std::list ItemTargetList; + + TargetList m_UniqueTargetInfo; + GOTargetList m_UniqueGOTargetInfo; + ItemTargetList m_UniqueItemInfo; + + void AddUnitTarget(Unit* target, SpellEffectIndex effIndex); + void AddUnitTarget(ObjectGuid unitGuid, SpellEffectIndex effIndex); + void AddGOTarget(GameObject* target, SpellEffectIndex effIndex); + void AddGOTarget(ObjectGuid goGuid, SpellEffectIndex effIndex); + void AddItemTarget(Item* target, SpellEffectIndex effIndex); + void DoAllEffectOnTarget(TargetInfo* target); + void HandleDelayedSpellLaunch(TargetInfo* target); + void InitializeDamageMultipliers(); + void ResetEffectDamageAndHeal(); + void DoSpellHitOnUnit(Unit* unit, uint32 effectMask); + void DoAllEffectOnTarget(GOTargetInfo* target); + void DoAllEffectOnTarget(ItemTargetInfo* target); + bool IsAliveUnitPresentInTargetList(); + SpellCastResult CanOpenLock(SpellEffectIndex effIndex, uint32 lockid, SkillType& skillid, int32& reqSkillValue, int32& skillValue); + // ------------------------------------------- + + // List For Triggered Spells + typedef std::list SpellInfoList; + SpellInfoList m_TriggerSpells; // casted by caster to same targets settings in m_targets at success finish of current spell + SpellInfoList m_preCastSpells; // casted by caster to each target at spell hit before spell effects apply + + uint32 m_spellState; + uint32 m_timer; + + float m_castPositionX; + float m_castPositionY; + float m_castPositionZ; + float m_castOrientation; + bool m_IsTriggeredSpell; + + // if need this can be replaced by Aura copy + // we can't store original aura link to prevent access to deleted auras + // and in same time need aura data and after aura deleting. + SpellEntry const* m_triggeredByAuraSpell; + + private: + // NPC Summonings + struct CreaturePosition + { + CreaturePosition() : + x(0.0f), y(0.0f), z(0.0f), + creature(NULL) + {} + + float x, y, z; + Creature* creature; + }; + typedef std::vector CreatureSummonPositions; + + // return true IFF further processing required + bool DoSummonPet(SpellEffectEntry const* effect); + bool DoSummonTotem(SpellEffectEntry const* effect, uint8 slot_dbc = 0); + bool DoSummonWild(CreatureSummonPositions& list, SummonPropertiesEntry const* prop, SpellEffectEntry const* effect, uint32 level); + bool DoSummonCritter(CreatureSummonPositions& list, SummonPropertiesEntry const* prop, SpellEffectEntry const* effect, uint32 level); + bool DoSummonGuardian(CreatureSummonPositions& list, SummonPropertiesEntry const* prop, SpellEffectEntry const* effect, uint32 level); + bool DoSummonPossessed(CreatureSummonPositions& list, SummonPropertiesEntry const* prop, SpellEffectEntry const* effect, uint32 level); + bool DoSummonVehicle(CreatureSummonPositions& list, SummonPropertiesEntry const* prop, SpellEffectEntry const* effect, uint32 level); +}; + +enum ReplenishType +{ + REPLENISH_UNDEFINED = 0, + REPLENISH_HEALTH = 20, + REPLENISH_MANA = 21, + REPLENISH_RAGE = 22 +}; + +namespace MaNGOS +{ + struct MANGOS_DLL_DECL SpellNotifierPlayer // Currently unused. When put to use this one requires handling for source-location (smilar to below) + { + Spell::UnitList& i_data; + Spell& i_spell; + const uint32& i_index; + float i_radius; + WorldObject* i_originalCaster; + + SpellNotifierPlayer(Spell& spell, Spell::UnitList& data, const uint32& i, float radius) + : i_data(data), i_spell(spell), i_index(i), i_radius(radius) + { + i_originalCaster = i_spell.GetAffectiveCasterObject(); + } + + void Visit(PlayerMapType& m) + { + if (!i_originalCaster) + return; + + for (PlayerMapType::iterator itr = m.begin(); itr != m.end(); ++itr) + { + Player* pPlayer = itr->getSource(); + if (!pPlayer->isAlive() || pPlayer->IsTaxiFlying()) + continue; + + if (i_originalCaster->IsFriendlyTo(pPlayer)) + continue; + + if (pPlayer->IsWithinDist3d(i_spell.m_targets.m_destX, i_spell.m_targets.m_destY, i_spell.m_targets.m_destZ, i_radius)) + i_data.push_back(pPlayer); + } + } + template void Visit(GridRefManager&) {} + }; + + struct MANGOS_DLL_DECL SpellNotifierCreatureAndPlayer + { + Spell::UnitList* i_data; + Spell& i_spell; + SpellNotifyPushType i_push_type; + float i_radius; + SpellTargets i_TargetType; + WorldObject* i_originalCaster; + WorldObject* i_castingObject; + bool i_playerControlled; + float i_centerX; + float i_centerY; + float i_centerZ; + + float GetCenterX() const { return i_centerX; } + float GetCenterY() const { return i_centerY; } + + SpellNotifierCreatureAndPlayer(Spell& spell, Spell::UnitList& data, float radius, SpellNotifyPushType type, + SpellTargets TargetType = SPELL_TARGETS_NOT_FRIENDLY, WorldObject* originalCaster = NULL) + : i_data(&data), i_spell(spell), i_push_type(type), i_radius(radius), i_TargetType(TargetType), + i_originalCaster(originalCaster), i_castingObject(i_spell.GetCastingObject()) + { + if (!i_originalCaster) + i_originalCaster = i_spell.GetAffectiveCasterObject(); + i_playerControlled = i_originalCaster ? i_originalCaster->IsControlledByPlayer() : false; + + switch (i_push_type) + { + case PUSH_IN_FRONT: + case PUSH_IN_FRONT_90: + case PUSH_IN_FRONT_30: + case PUSH_IN_FRONT_15: + case PUSH_IN_BACK: + case PUSH_SELF_CENTER: + if (i_castingObject) + { + i_centerX = i_castingObject->GetPositionX(); + i_centerY = i_castingObject->GetPositionY(); + } + break; + case PUSH_DEST_CENTER: + if (i_spell.m_targets.m_targetMask & TARGET_FLAG_SOURCE_LOCATION) + i_spell.m_targets.getSource(i_centerX, i_centerY, i_centerZ); + else + i_spell.m_targets.getDestination(i_centerX, i_centerY, i_centerZ); + break; + case PUSH_TARGET_CENTER: + if (Unit* target = i_spell.m_targets.getUnitTarget()) + { + i_centerX = target->GetPositionX(); + i_centerY = target->GetPositionY(); + } + break; + default: + sLog.outError("SpellNotifierCreatureAndPlayer: unsupported PUSH_* case %u.", i_push_type); + } + } + + template inline void Visit(GridRefManager& m) + { + MANGOS_ASSERT(i_data); + + if (!i_originalCaster || !i_castingObject) + return; + + for (typename GridRefManager::iterator itr = m.begin(); itr != m.end(); ++itr) + { + // there are still more spells which can be casted on dead, but + // they are no AOE and don't have such a nice SPELL_ATTR flag + if ((i_TargetType != SPELL_TARGETS_ALL && !itr->getSource()->isTargetableForAttack(i_spell.m_spellInfo->HasAttribute(SPELL_ATTR_EX3_CAST_ON_DEAD))) + // mostly phase check + || !itr->getSource()->IsInMap(i_originalCaster)) + continue; + + switch (i_TargetType) + { + case SPELL_TARGETS_HOSTILE: + if (!i_originalCaster->IsHostileTo(itr->getSource())) + continue; + break; + case SPELL_TARGETS_NOT_FRIENDLY: + if (i_originalCaster->IsFriendlyTo(itr->getSource())) + continue; + break; + case SPELL_TARGETS_NOT_HOSTILE: + if (i_originalCaster->IsHostileTo(itr->getSource())) + continue; + break; + case SPELL_TARGETS_FRIENDLY: + if (!i_originalCaster->IsFriendlyTo(itr->getSource())) + continue; + break; + case SPELL_TARGETS_AOE_DAMAGE: + { + if (itr->getSource()->GetTypeId() == TYPEID_UNIT && ((Creature*)itr->getSource())->IsTotem()) + continue; + + if (i_playerControlled) + { + if (i_originalCaster->IsFriendlyTo(itr->getSource())) + continue; + } + else + { + if (!i_originalCaster->IsHostileTo(itr->getSource())) + continue; + } + } + break; + case SPELL_TARGETS_ALL: + break; + default: continue; + } + + // we don't need to check InMap here, it's already done some lines above + switch (i_push_type) + { + case PUSH_IN_FRONT: + if (i_castingObject->isInFront((Unit*)(itr->getSource()), i_radius, 2 * M_PI_F / 3)) + i_data->push_back(itr->getSource()); + break; + case PUSH_IN_FRONT_90: + if (i_castingObject->isInFront((Unit*)(itr->getSource()), i_radius, M_PI_F / 2)) + i_data->push_back(itr->getSource()); + break; + case PUSH_IN_FRONT_30: + if (i_castingObject->isInFront((Unit*)(itr->getSource()), i_radius, M_PI_F / 6)) + i_data->push_back(itr->getSource()); + break; + case PUSH_IN_FRONT_15: + if (i_castingObject->isInFront((Unit*)(itr->getSource()), i_radius, M_PI_F / 12)) + i_data->push_back(itr->getSource()); + break; + case PUSH_IN_BACK: + if (i_castingObject->isInBack((Unit*)(itr->getSource()), i_radius, 2 * M_PI_F / 3)) + i_data->push_back(itr->getSource()); + break; + case PUSH_SELF_CENTER: + if (i_castingObject->IsWithinDist((Unit*)(itr->getSource()), i_radius)) + i_data->push_back(itr->getSource()); + break; + case PUSH_DEST_CENTER: + if (itr->getSource()->IsWithinDist3d(i_centerX, i_centerY, i_centerZ, i_radius)) + i_data->push_back(itr->getSource()); + break; + case PUSH_TARGET_CENTER: + if (i_spell.m_targets.getUnitTarget() && i_spell.m_targets.getUnitTarget()->IsWithinDist((Unit*)(itr->getSource()), i_radius)) + i_data->push_back(itr->getSource()); + break; + } + } + } + +#ifdef WIN32 + template<> inline void Visit(CorpseMapType&) {} + template<> inline void Visit(GameObjectMapType&) {} + template<> inline void Visit(DynamicObjectMapType&) {} + template<> inline void Visit(CameraMapType&) {} +#endif + }; + +#ifndef WIN32 + template<> inline void SpellNotifierCreatureAndPlayer::Visit(CorpseMapType&) {} + template<> inline void SpellNotifierCreatureAndPlayer::Visit(GameObjectMapType&) {} + template<> inline void SpellNotifierCreatureAndPlayer::Visit(DynamicObjectMapType&) {} + template<> inline void SpellNotifierCreatureAndPlayer::Visit(CameraMapType&) {} +#endif +} + +typedef void(Spell::*pEffect)(SpellEffectEntry const* spellEffect); + +class SpellEvent : public BasicEvent +{ + public: + SpellEvent(Spell* spell); + virtual ~SpellEvent(); + + virtual bool Execute(uint64 e_time, uint32 p_time) override; + virtual void Abort(uint64 e_time) override; + virtual bool IsDeletable() const override; + protected: + Spell* m_Spell; +}; +#endif diff --git a/src/game/SpellEffects.cpp b/src/game/SpellEffects.cpp new file mode 100644 index 000000000..e3c677ea0 --- /dev/null +++ b/src/game/SpellEffects.cpp @@ -0,0 +1,10902 @@ +/** + * This code is part of MaNGOS. Contributor & Copyright details are in AUTHORS/THANKS. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "Database/DatabaseEnv.h" +#include "WorldPacket.h" +#include "Opcodes.h" +#include "Log.h" +#include "UpdateMask.h" +#include "World.h" +#include "ObjectMgr.h" +#include "SpellMgr.h" +#include "Player.h" +#include "SkillExtraItems.h" +#include "Unit.h" +#include "Spell.h" +#include "DynamicObject.h" +#include "SpellAuras.h" +#include "Group.h" +#include "UpdateData.h" +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "SharedDefines.h" +#include "Pet.h" +#include "GameObject.h" +#include "GossipDef.h" +#include "Creature.h" +#include "Totem.h" +#include "CreatureAI.h" +#include "BattleGround/BattleGroundMgr.h" +#include "BattleGround/BattleGround.h" +#include "BattleGround/BattleGroundEY.h" +#include "BattleGround/BattleGroundWS.h" +#include "Language.h" +#include "SocialMgr.h" +#include "VMapFactory.h" +#include "Util.h" +#include "TemporarySummon.h" +#include "ScriptMgr.h" +#include "SkillDiscovery.h" +#include "Formulas.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" +#include "PhaseMgr.h" + +pEffect SpellEffects[TOTAL_SPELL_EFFECTS] = +{ + &Spell::EffectNULL, // 0 + &Spell::EffectInstaKill, // 1 SPELL_EFFECT_INSTAKILL + &Spell::EffectSchoolDMG, // 2 SPELL_EFFECT_SCHOOL_DAMAGE + &Spell::EffectDummy, // 3 SPELL_EFFECT_DUMMY + &Spell::EffectUnused, // 4 SPELL_EFFECT_PORTAL_TELEPORT unused from pre-1.2.1 + &Spell::EffectTeleportUnits, // 5 SPELL_EFFECT_TELEPORT_UNITS + &Spell::EffectApplyAura, // 6 SPELL_EFFECT_APPLY_AURA + &Spell::EffectEnvironmentalDMG, // 7 SPELL_EFFECT_ENVIRONMENTAL_DAMAGE + &Spell::EffectPowerDrain, // 8 SPELL_EFFECT_POWER_DRAIN + &Spell::EffectHealthLeech, // 9 SPELL_EFFECT_HEALTH_LEECH + &Spell::EffectHeal, // 10 SPELL_EFFECT_HEAL + &Spell::EffectBind, // 11 SPELL_EFFECT_BIND + &Spell::EffectUnused, // 12 SPELL_EFFECT_PORTAL unused from pre-1.2.1, exist 2 spell, but not exist any data about its real usage + &Spell::EffectUnused, // 13 SPELL_EFFECT_RITUAL_BASE unused from pre-1.2.1 + &Spell::EffectUnused, // 14 SPELL_EFFECT_RITUAL_SPECIALIZE unused from pre-1.2.1 + &Spell::EffectUnused, // 15 SPELL_EFFECT_RITUAL_ACTIVATE_PORTAL unused from pre-1.2.1 + &Spell::EffectQuestComplete, // 16 SPELL_EFFECT_QUEST_COMPLETE + &Spell::EffectWeaponDmg, // 17 SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL + &Spell::EffectResurrect, // 18 SPELL_EFFECT_RESURRECT + &Spell::EffectAddExtraAttacks, // 19 SPELL_EFFECT_ADD_EXTRA_ATTACKS + &Spell::EffectEmpty, // 20 SPELL_EFFECT_DODGE one spell: Dodge + &Spell::EffectEmpty, // 21 SPELL_EFFECT_EVADE one spell: Evade (DND) + &Spell::EffectParry, // 22 SPELL_EFFECT_PARRY + &Spell::EffectBlock, // 23 SPELL_EFFECT_BLOCK one spell: Block + &Spell::EffectCreateItem, // 24 SPELL_EFFECT_CREATE_ITEM + &Spell::EffectEmpty, // 25 SPELL_EFFECT_WEAPON spell per weapon type, in ItemSubclassmask store mask that can be used for usability check at equip, but current way using skill also work. + &Spell::EffectEmpty, // 26 SPELL_EFFECT_DEFENSE one spell: Defense + &Spell::EffectPersistentAA, // 27 SPELL_EFFECT_PERSISTENT_AREA_AURA + &Spell::EffectSummonType, // 28 SPELL_EFFECT_SUMMON + &Spell::EffectLeapForward, // 29 SPELL_EFFECT_LEAP + &Spell::EffectEnergize, // 30 SPELL_EFFECT_ENERGIZE + &Spell::EffectWeaponDmg, // 31 SPELL_EFFECT_WEAPON_PERCENT_DAMAGE + &Spell::EffectTriggerMissileSpell, // 32 SPELL_EFFECT_TRIGGER_MISSILE + &Spell::EffectOpenLock, // 33 SPELL_EFFECT_OPEN_LOCK + &Spell::EffectSummonChangeItem, // 34 SPELL_EFFECT_SUMMON_CHANGE_ITEM + &Spell::EffectApplyAreaAura, // 35 SPELL_EFFECT_APPLY_AREA_AURA_PARTY + &Spell::EffectLearnSpell, // 36 SPELL_EFFECT_LEARN_SPELL + &Spell::EffectEmpty, // 37 SPELL_EFFECT_SPELL_DEFENSE one spell: SPELLDEFENSE (DND) + &Spell::EffectDispel, // 38 SPELL_EFFECT_DISPEL + &Spell::EffectEmpty, // 39 SPELL_EFFECT_LANGUAGE misc store lang id + &Spell::EffectDualWield, // 40 SPELL_EFFECT_DUAL_WIELD + &Spell::EffectJump, // 41 SPELL_EFFECT_JUMP + &Spell::EffectJump, // 42 SPELL_EFFECT_JUMP2 + &Spell::EffectTeleUnitsFaceCaster, // 43 SPELL_EFFECT_TELEPORT_UNITS_FACE_CASTER + &Spell::EffectLearnSkill, // 44 SPELL_EFFECT_SKILL_STEP + &Spell::EffectNULL, // 45 SPELL_EFFECT_PLAY_MOVIE + &Spell::EffectNULL, // 46 SPELL_EFFECT_SPAWN spawn/login animation, expected by spawn unit cast, also base points store some dynflags + &Spell::EffectTradeSkill, // 47 SPELL_EFFECT_TRADE_SKILL + &Spell::EffectUnused, // 48 SPELL_EFFECT_STEALTH one spell: Base Stealth + &Spell::EffectUnused, // 49 SPELL_EFFECT_DETECT one spell: Detect + &Spell::EffectTransmitted, // 50 SPELL_EFFECT_TRANS_DOOR + &Spell::EffectUnused, // 51 SPELL_EFFECT_FORCE_CRITICAL_HIT unused from pre-1.2.1 + &Spell::EffectUnused, // 52 SPELL_EFFECT_GUARANTEE_HIT unused from pre-1.2.1 + &Spell::EffectEnchantItemPerm, // 53 SPELL_EFFECT_ENCHANT_ITEM + &Spell::EffectEnchantItemTmp, // 54 SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY + &Spell::EffectTameCreature, // 55 SPELL_EFFECT_TAMECREATURE + &Spell::EffectSummonPet, // 56 SPELL_EFFECT_SUMMON_PET + &Spell::EffectLearnPetSpell, // 57 SPELL_EFFECT_LEARN_PET_SPELL + &Spell::EffectWeaponDmg, // 58 SPELL_EFFECT_WEAPON_DAMAGE + &Spell::EffectCreateRandomItem, // 59 SPELL_EFFECT_CREATE_RANDOM_ITEM create item base at spell specific loot + &Spell::EffectProficiency, // 60 SPELL_EFFECT_PROFICIENCY + &Spell::EffectSendEvent, // 61 SPELL_EFFECT_SEND_EVENT + &Spell::EffectPowerBurn, // 62 SPELL_EFFECT_POWER_BURN + &Spell::EffectThreat, // 63 SPELL_EFFECT_THREAT + &Spell::EffectTriggerSpell, // 64 SPELL_EFFECT_TRIGGER_SPELL + &Spell::EffectApplyAreaAura, // 65 SPELL_EFFECT_APPLY_AREA_AURA_RAID + &Spell::EffectRestoreItemCharges, // 66 SPELL_EFFECT_RESTORE_ITEM_CHARGES itemtype - is affected item ID + &Spell::EffectHealMaxHealth, // 67 SPELL_EFFECT_HEAL_MAX_HEALTH + &Spell::EffectInterruptCast, // 68 SPELL_EFFECT_INTERRUPT_CAST + &Spell::EffectDistract, // 69 SPELL_EFFECT_DISTRACT + &Spell::EffectPull, // 70 SPELL_EFFECT_PULL one spell: Distract Move + &Spell::EffectPickPocket, // 71 SPELL_EFFECT_PICKPOCKET + &Spell::EffectAddFarsight, // 72 SPELL_EFFECT_ADD_FARSIGHT + &Spell::EffectNULL, // 73 SPELL_EFFECT_UNTRAIN_TALENTS one spell: Trainer: Untrain Talents + &Spell::EffectApplyGlyph, // 74 SPELL_EFFECT_APPLY_GLYPH + &Spell::EffectHealMechanical, // 75 SPELL_EFFECT_HEAL_MECHANICAL one spell: Mechanical Patch Kit + &Spell::EffectSummonObjectWild, // 76 SPELL_EFFECT_SUMMON_OBJECT_WILD + &Spell::EffectScriptEffect, // 77 SPELL_EFFECT_SCRIPT_EFFECT + &Spell::EffectUnused, // 78 SPELL_EFFECT_ATTACK + &Spell::EffectSanctuary, // 79 SPELL_EFFECT_SANCTUARY + &Spell::EffectAddComboPoints, // 80 SPELL_EFFECT_ADD_COMBO_POINTS + &Spell::EffectUnused, // 81 SPELL_EFFECT_CREATE_HOUSE one spell: Create House (TEST) + &Spell::EffectNULL, // 82 SPELL_EFFECT_BIND_SIGHT + &Spell::EffectDuel, // 83 SPELL_EFFECT_DUEL + &Spell::EffectStuck, // 84 SPELL_EFFECT_STUCK + &Spell::EffectSummonPlayer, // 85 SPELL_EFFECT_SUMMON_PLAYER + &Spell::EffectActivateObject, // 86 SPELL_EFFECT_ACTIVATE_OBJECT + &Spell::EffectWMODamage, // 87 SPELL_EFFECT_WMO_DAMAGE (57 spells in 3.3.2) + &Spell::EffectWMORepair, // 88 SPELL_EFFECT_WMO_REPAIR (2 spells in 3.3.2) + &Spell::EffectWMOChange, // 89 SPELL_EFFECT_WMO_CHANGE (7 spells in 3.3.2) + &Spell::EffectKillCreditPersonal, // 90 SPELL_EFFECT_KILL_CREDIT_PERSONAL Kill credit but only for single person + &Spell::EffectUnused, // 91 SPELL_EFFECT_THREAT_ALL one spell: zzOLDBrainwash + &Spell::EffectEnchantHeldItem, // 92 SPELL_EFFECT_ENCHANT_HELD_ITEM + &Spell::EffectBreakPlayerTargeting, // 93 SPELL_EFFECT_BREAK_PLAYER_TARGETING + &Spell::EffectSelfResurrect, // 94 SPELL_EFFECT_SELF_RESURRECT + &Spell::EffectSkinning, // 95 SPELL_EFFECT_SKINNING + &Spell::EffectCharge, // 96 SPELL_EFFECT_CHARGE + &Spell::EffectSummonAllTotems, // 97 SPELL_EFFECT_SUMMON_ALL_TOTEMS + &Spell::EffectKnockBack, // 98 SPELL_EFFECT_KNOCK_BACK + &Spell::EffectDisEnchant, // 99 SPELL_EFFECT_DISENCHANT + &Spell::EffectInebriate, //100 SPELL_EFFECT_INEBRIATE + &Spell::EffectFeedPet, //101 SPELL_EFFECT_FEED_PET + &Spell::EffectDismissPet, //102 SPELL_EFFECT_DISMISS_PET + &Spell::EffectReputation, //103 SPELL_EFFECT_REPUTATION + &Spell::EffectSummonObject, //104 SPELL_EFFECT_SUMMON_OBJECT_SLOT + &Spell::EffectNULL, //105 SPELL_EFFECT_SURVEY + &Spell::EffectNULL, //106 SPELL_EFFECT_SUMMON_RAID_MARKER + &Spell::EffectNULL, //107 SPELL_EFFECT_LOOT_CORPSE + &Spell::EffectDispelMechanic, //108 SPELL_EFFECT_DISPEL_MECHANIC + &Spell::EffectSummonDeadPet, //109 SPELL_EFFECT_SUMMON_DEAD_PET + &Spell::EffectDestroyAllTotems, //110 SPELL_EFFECT_DESTROY_ALL_TOTEMS + &Spell::EffectDurabilityDamage, //111 SPELL_EFFECT_DURABILITY_DAMAGE + &Spell::EffectUnused, //112 SPELL_EFFECT_112 (old SPELL_EFFECT_SUMMON_DEMON) + &Spell::EffectResurrectNew, //113 SPELL_EFFECT_RESURRECT_NEW + &Spell::EffectTaunt, //114 SPELL_EFFECT_ATTACK_ME + &Spell::EffectDurabilityDamagePCT, //115 SPELL_EFFECT_DURABILITY_DAMAGE_PCT + &Spell::EffectSkinPlayerCorpse, //116 SPELL_EFFECT_SKIN_PLAYER_CORPSE one spell: Remove Insignia, bg usage, required special corpse flags... + &Spell::EffectSpiritHeal, //117 SPELL_EFFECT_SPIRIT_HEAL one spell: Spirit Heal + &Spell::EffectSkill, //118 SPELL_EFFECT_SKILL professions and more + &Spell::EffectApplyAreaAura, //119 SPELL_EFFECT_APPLY_AREA_AURA_PET + &Spell::EffectUnused, //120 SPELL_EFFECT_TELEPORT_GRAVEYARD one spell: Graveyard Teleport Test + &Spell::EffectWeaponDmg, //121 SPELL_EFFECT_NORMALIZED_WEAPON_DMG + &Spell::EffectUnused, //122 SPELL_EFFECT_122 unused + &Spell::EffectSendTaxi, //123 SPELL_EFFECT_SEND_TAXI taxi/flight related (misc value is taxi path id) + &Spell::EffectPlayerPull, //124 SPELL_EFFECT_PLAYER_PULL opposite of knockback effect (pulls player twoard caster) + &Spell::EffectModifyThreatPercent, //125 SPELL_EFFECT_MODIFY_THREAT_PERCENT + &Spell::EffectStealBeneficialBuff, //126 SPELL_EFFECT_STEAL_BENEFICIAL_BUFF spell steal effect? + &Spell::EffectProspecting, //127 SPELL_EFFECT_PROSPECTING Prospecting spell + &Spell::EffectApplyAreaAura, //128 SPELL_EFFECT_APPLY_AREA_AURA_FRIEND + &Spell::EffectApplyAreaAura, //129 SPELL_EFFECT_APPLY_AREA_AURA_ENEMY + &Spell::EffectRedirectThreat, //130 SPELL_EFFECT_REDIRECT_THREAT + &Spell::EffectPlaySound, //131 SPELL_EFFECT_PLAY_SOUND sound id in misc value (SoundEntries.dbc) + &Spell::EffectPlayMusic, //132 SPELL_EFFECT_PLAY_MUSIC sound id in misc value (SoundEntries.dbc) + &Spell::EffectUnlearnSpecialization, //133 SPELL_EFFECT_UNLEARN_SPECIALIZATION unlearn profession specialization + &Spell::EffectKillCreditGroup, //134 SPELL_EFFECT_KILL_CREDIT_GROUP misc value is creature entry + &Spell::EffectNULL, //135 SPELL_EFFECT_CALL_PET + &Spell::EffectHealPct, //136 SPELL_EFFECT_HEAL_PCT + &Spell::EffectEnergisePct, //137 SPELL_EFFECT_ENERGIZE_PCT + &Spell::EffectLeapBack, //138 SPELL_EFFECT_LEAP_BACK Leap back + &Spell::EffectClearQuest, //139 SPELL_EFFECT_CLEAR_QUEST (misc - is quest ID) + &Spell::EffectForceCast, //140 SPELL_EFFECT_FORCE_CAST + &Spell::EffectNULL, //141 SPELL_EFFECT_141 damage and reduce speed? + &Spell::EffectTriggerSpellWithValue, //142 SPELL_EFFECT_TRIGGER_SPELL_WITH_VALUE + &Spell::EffectApplyAreaAura, //143 SPELL_EFFECT_APPLY_AREA_AURA_OWNER + &Spell::EffectKnockBackFromPosition, //144 SPELL_EFFECT_KNOCKBACK_FROM_POSITION + &Spell::EffectNULL, //145 SPELL_EFFECT_145 Black Hole Effect + &Spell::EffectActivateRune, //146 SPELL_EFFECT_ACTIVATE_RUNE + &Spell::EffectQuestFail, //147 SPELL_EFFECT_QUEST_FAIL quest fail + &Spell::EffectNULL, //148 SPELL_EFFECT_148 single spell: Inflicts Fire damage to an enemy. + &Spell::EffectCharge2, //149 SPELL_EFFECT_CHARGE2 swoop + &Spell::EffectQuestOffer, //150 SPELL_EFFECT_QUEST_OFFER + &Spell::EffectTriggerRitualOfSummoning, //151 SPELL_EFFECT_TRIGGER_SPELL_2 + &Spell::EffectNULL, //152 SPELL_EFFECT_152 summon Refer-a-Friend + &Spell::EffectNULL, //153 SPELL_EFFECT_CREATE_PET misc value is creature entry + &Spell::EffectTeachTaxiNode, //154 SPELL_EFFECT_TEACH_TAXI_NODE single spell: Teach River's Heart Taxi Path + &Spell::EffectTitanGrip, //155 SPELL_EFFECT_TITAN_GRIP Allows you to equip two-handed axes, maces and swords in one hand, but you attack $49152s1% slower than normal. + &Spell::EffectEnchantItemPrismatic, //156 SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC + &Spell::EffectCreateItem2, //157 SPELL_EFFECT_CREATE_ITEM_2 create item or create item template and replace by some randon spell loot item + &Spell::EffectMilling, //158 SPELL_EFFECT_MILLING milling + &Spell::EffectRenamePet, //159 SPELL_EFFECT_ALLOW_RENAME_PET allow rename pet once again + &Spell::EffectNULL, //160 SPELL_EFFECT_160 single spell: Nerub'ar Web Random Unit + &Spell::EffectSpecCount, //161 SPELL_EFFECT_TALENT_SPEC_COUNT second talent spec (learn/revert) + &Spell::EffectActivateSpec, //162 SPELL_EFFECT_TALENT_SPEC_SELECT activate primary/secondary spec + &Spell::EffectUnused, //163 unused in 3.3.5a + &Spell::EffectCancelAura, //164 SPELL_EFFECT_CANCEL_AURA + &Spell::EffectNULL, //165 SPELL_EFFECT_DAMAGE_FROM_MAX_HEALTH_PCT 82 spells in 4.3.4 + &Spell::EffectNULL, //166 SPELL_EFFECT_REWARD_CURRENCY 56 spells in 4.3.4 + &Spell::EffectNULL, //167 SPELL_EFFECT_167 42 spells in 4.3.4 + &Spell::EffectNULL, //168 SPELL_EFFECT_168 2 spells in 4.3.4 Allows give commands to controlled pet + &Spell::EffectNULL, //169 SPELL_EFFECT_DESTROY_ITEM 9 spells in 4.3.4 + &Spell::EffectNULL, //170 SPELL_EFFECT_170 70 spells in 4.3.4 + &Spell::EffectNULL, //171 SPELL_EFFECT_171 19 spells in 4.3.4 related to GO summon + &Spell::EffectNULL, //172 SPELL_EFFECT_MASS_RESSURECTION Mass Ressurection (Guild Perk) + &Spell::EffectNULL, //173 SPELL_EFFECT_BUY_GUILD_BANKSLOT 4 spells in 4.3.4 basepoints - slot + &Spell::EffectNULL, //174 SPELL_EFFECT_174 13 spells some sort of area aura apply effect + &Spell::EffectUnused, //175 SPELL_EFFECT_175 unused in 4.3.4 + &Spell::EffectNULL, //176 SPELL_EFFECT_SANCTUARY_2 4 spells in 4.3.4 + &Spell::EffectNULL, //177 SPELL_EFFECT_177 2 spells in 4.3.4 Deluge(100757) and test spell + &Spell::EffectUnused, //178 SPELL_EFFECT_178 unused in 4.3.4 + &Spell::EffectNULL, //179 SPELL_EFFECT_179 15 spells in 4.3.4 + &Spell::EffectUnused, //180 SPELL_EFFECT_180 unused in 4.3.4 + &Spell::EffectUnused, //181 SPELL_EFFECT_181 unused in 4.3.4 + &Spell::EffectNULL, //182 SPELL_EFFECT_182 3 spells 4.3.4 +}; + +void Spell::EffectEmpty(SpellEffectEntry const* /*effect*/) +{ + // NOT NEED ANY IMPLEMENTATION CODE, EFFECT POSISBLE USED AS MARKER OR CLIENT INFORM +} + +void Spell::EffectNULL(SpellEffectEntry const* /*effect*/) +{ + DEBUG_LOG("WORLD: Spell Effect DUMMY"); +} + +void Spell::EffectUnused(SpellEffectEntry const* /*effect*/) +{ + // NOT USED BY ANY SPELL OR USELESS OR IMPLEMENTED IN DIFFERENT WAY IN MANGOS +} + +void Spell::EffectResurrectNew(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->isAlive()) + return; + + if (unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + if (!unitTarget->IsInWorld()) + return; + + Player* pTarget = ((Player*)unitTarget); + + if (pTarget->isRessurectRequested()) // already have one active request + return; + + uint32 health = damage; + uint32 mana = effect->EffectMiscValue; + pTarget->setResurrectRequestData(m_caster->GetObjectGuid(), m_caster->GetMapId(), m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ(), health, mana); + SendResurrectRequest(pTarget); +} + +void Spell::EffectInstaKill(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget || !unitTarget->isAlive()) + return; + + if (m_caster == unitTarget) // prevent interrupt message + finish(); + + WorldObject* caster = GetCastingObject(); // we need the original casting object + + WorldPacket data(SMSG_SPELLINSTAKILLLOG, (8 + 8 + 4)); + data << (caster && caster->GetTypeId() != TYPEID_GAMEOBJECT ? m_caster->GetObjectGuid() : ObjectGuid()); // Caster GUID + data << unitTarget->GetObjectGuid(); // Victim GUID + data << uint32(m_spellInfo->Id); + m_caster->SendMessageToSet(&data, true); + + m_caster->DealDamage(unitTarget, unitTarget->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); +} + +void Spell::EffectEnvironmentalDMG(SpellEffectEntry const* effect) +{ + uint32 absorb = 0; + uint32 resist = 0; + + // Note: this hack with damage replace required until GO casting not implemented + // environment damage spells already have around enemies targeting but this not help in case nonexistent GO casting support + // currently each enemy selected explicitly and self cast damage, we prevent apply self casted spell bonuses/etc + damage = effect->CalculateSimpleValue(); + + m_caster->CalculateDamageAbsorbAndResist(m_caster, GetSpellSchoolMask(m_spellInfo), SPELL_DIRECT_DAMAGE, damage, &absorb, &resist); + + m_caster->SendSpellNonMeleeDamageLog(m_caster, m_spellInfo->Id, damage, GetSpellSchoolMask(m_spellInfo), absorb, resist, false, 0, false); + if (m_caster->GetTypeId() == TYPEID_PLAYER) + ((Player*)m_caster)->EnvironmentalDamage(DAMAGE_FIRE, damage); +} + +void Spell::EffectSchoolDMG(SpellEffectEntry const* effect) +{ + if (unitTarget && unitTarget->isAlive()) + { + SpellClassOptionsEntry const* classOptions = m_spellInfo->GetSpellClassOptions(); + + switch(m_spellInfo->GetSpellFamilyName()) + { + case SPELLFAMILY_GENERIC: + { + switch (m_spellInfo->Id) // better way to check unknown + { + // Meteor like spells (divided damage to targets) + case 24340: case 26558: case 28884: // Meteor + case 36837: case 38903: case 41276: // Meteor + case 57467: // Meteor + case 26789: // Shard of the Fallen Star + case 31436: // Malevolent Cleave + case 35181: // Dive Bomb + case 40810: case 43267: case 43268: // Saber Lash + case 42384: // Brutal Swipe + case 45150: // Meteor Slash + case 64422: case 64688: // Sonic Screech + case 70492: case 72505: // Ooze Eruption + case 71904: // Chaos Bane + case 72624: case 72625: // Ooze Eruption + { + uint32 count = 0; + for(TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + if(ihit->effectMask & (1<EffectIndex)) + ++count; + + damage /= count; // divide to all targets + break; + } + // percent from health with min + case 25599: // Thundercrash + { + damage = unitTarget->GetHealth() / 2; + if (damage < 200) + damage = 200; + break; + } + // Intercept (warrior spell trigger) + case 20253: + case 61491: + { + damage += uint32(m_caster->GetTotalAttackPowerValue(BASE_ATTACK) * 0.12f); + break; + } + // percent max target health + case 29142: // Eyesore Blaster + case 35139: // Throw Boom's Doom + case 49882: // Leviroth Self-Impale + case 55269: // Deathly Stare + { + damage = damage * unitTarget->GetMaxHealth() / 100; + break; + } + // Cataclysmic Bolt + case 38441: + { + damage = unitTarget->GetMaxHealth() / 2; + break; + } + // Touch the Nightmare + case 50341: + { + if (SpellEffectIndex(effect->EffectIndex) == EFFECT_INDEX_2) + damage = int32(unitTarget->GetMaxHealth() * 0.3f); + break; + } + // Tympanic Tantrum + case 62775: + { + damage = unitTarget->GetMaxHealth() / 10; + break; + } + // Hand of Rekoning (name not have typos ;) ) + case 67485: + damage += uint32(0.5f * m_caster->GetTotalAttackPowerValue(BASE_ATTACK)); + break; + // Magic Bane normal (Forge of Souls - Bronjahm) + case 68793: + { + damage += uint32(unitTarget->GetMaxPower(POWER_MANA) / 2); + damage = std::min(damage, 10000); + break; + } + // Magic Bane heroic (Forge of Souls - Bronjahm) + case 69050: + { + damage += uint32(unitTarget->GetMaxPower(POWER_MANA) / 2); + damage = std::min(damage, 15000); + break; + } + } + break; + } + case SPELLFAMILY_MAGE: + // remove Arcane Blast buffs at any non-Arcane Blast arcane damage spell. + // NOTE: it removed at hit instead cast because currently spell done-damage calculated at hit instead cast + if ((m_spellInfo->SchoolMask & SPELL_SCHOOL_MASK_ARCANE) && !(classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x20000000))) + m_caster->RemoveAurasDueToSpell(36032); // Arcane Blast buff + break; + case SPELLFAMILY_WARRIOR: + { + // Bloodthirst + if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x40000000000)) + { + damage = uint32(damage * (m_caster->GetTotalAttackPowerValue(BASE_ATTACK)) / 100); + } + // Victory Rush + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x10000000000)) + { + damage = uint32(damage * m_caster->GetTotalAttackPowerValue(BASE_ATTACK) / 100); + m_caster->ModifyAuraState(AURA_STATE_WARRIOR_VICTORY_RUSH, false); + } + // Revenge ${$m1+$AP*0.310} to ${$M1+$AP*0.310} + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0000000000000400)) + damage+= uint32(m_caster->GetTotalAttackPowerValue(BASE_ATTACK) * 0.310f); + // Heroic Throw ${$m1+$AP*.50} + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0000000100000000)) + damage+= uint32(m_caster->GetTotalAttackPowerValue(BASE_ATTACK) * 0.5f); + // Shattering Throw ${$m1+$AP*.50} + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0040000000000000)) + damage+= uint32(m_caster->GetTotalAttackPowerValue(BASE_ATTACK) * 0.5f); + // Shockwave ${$m3/100*$AP} + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0000800000000000)) + { + int32 pct = m_caster->CalculateSpellDamage(unitTarget, m_spellInfo, EFFECT_INDEX_2); + if (pct > 0) + damage += int32(m_caster->GetTotalAttackPowerValue(BASE_ATTACK) * pct / 100); + break; + } + // Thunder Clap + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0000000000000080)) + { + damage += int32(m_caster->GetTotalAttackPowerValue(BASE_ATTACK) * 12 / 100); + } + break; + } + case SPELLFAMILY_WARLOCK: + { + // Incinerate Rank 1 & 2 + if ((classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x00004000000000)) && m_spellInfo->SpellIconID==2128) + { + // Incinerate does more dmg (dmg*0.25) if the target have Immolate debuff. + // Check aura state for speed but aura state set not only for Immolate spell + if (unitTarget->HasAuraState(AURA_STATE_CONFLAGRATE)) + { + Unit::AuraList const& RejorRegr = unitTarget->GetAurasByType(SPELL_AURA_PERIODIC_DAMAGE); + for (Unit::AuraList::const_iterator i = RejorRegr.begin(); i != RejorRegr.end(); ++i) + { + // Immolate + SpellClassOptionsEntry const* immSpellClassOpt = (*i)->GetSpellProto()->GetSpellClassOptions(); + if(!immSpellClassOpt) + continue; + if(immSpellClassOpt->SpellFamilyName == SPELLFAMILY_WARLOCK && + (immSpellClassOpt->SpellFamilyFlags & UI64LIT(0x00000000000004))) + { + damage += damage / 4; + break; + } + } + } + } + // Shadowflame + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0001000000000000)) + { + // Apply DOT part + switch (m_spellInfo->Id) + { + case 47897: m_caster->CastSpell(unitTarget, 47960, true); break; + case 61290: m_caster->CastSpell(unitTarget, 61291, true); break; + default: + sLog.outError("Spell::EffectDummy: Unhandeled Shadowflame spell rank %u", m_spellInfo->Id); + break; + } + } + // Shadow Bite + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0040000000000000)) + { + Unit* owner = m_caster->GetOwner(); + if (!owner) + break; + + uint32 counter = 0; + Unit::AuraList const& dotAuras = unitTarget->GetAurasByType(SPELL_AURA_PERIODIC_DAMAGE); + for (Unit::AuraList::const_iterator itr = dotAuras.begin(); itr != dotAuras.end(); ++itr) + if ((*itr)->GetCasterGuid() == owner->GetObjectGuid()) + ++counter; + + if (counter) + damage += (counter * owner->CalculateSpellDamage(unitTarget, m_spellInfo, EFFECT_INDEX_2) * damage) / 100.0f; + } + // Conflagrate - consumes Immolate or Shadowflame + else if (m_spellInfo->GetTargetAuraState() == AURA_STATE_CONFLAGRATE) + { + Aura const* aura = NULL; // found req. aura for damage calculation + + Unit::AuraList const& mPeriodic = unitTarget->GetAurasByType(SPELL_AURA_PERIODIC_DAMAGE); + for (Unit::AuraList::const_iterator i = mPeriodic.begin(); i != mPeriodic.end(); ++i) + { + // for caster applied auras only + if ((*i)->GetSpellProto()->GetSpellFamilyName() != SPELLFAMILY_WARLOCK || + (*i)->GetCasterGuid() != m_caster->GetObjectGuid()) + continue; + + // Immolate + if ((*i)->GetSpellProto()->IsFitToFamilyMask(UI64LIT(0x0000000000000004))) + { + aura = *i; // it selected always if exist + break; + } + + // Shadowflame + if ((*i)->GetSpellProto()->IsFitToFamilyMask(UI64LIT(0x0000000000000000), 0x00000002)) + aura = *i; // remember but wait possible Immolate as primary priority + } + + // found Immolate or Shadowflame + if (aura) + { + int32 damagetick = aura->GetModifier()->m_amount; + damage += damagetick * 4; + + // Glyph of Conflagrate + if (!m_caster->HasAura(56235)) + unitTarget->RemoveAurasByCasterSpell(aura->GetId(), m_caster->GetObjectGuid()); + break; + } + } + break; + } + case SPELLFAMILY_PRIEST: + { + // Shadow Word: Death - deals damage equal to damage done to caster + if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0000000200000000)) + m_caster->CastCustomSpell(m_caster, 32409, &damage, 0, 0, true); + // Improved Mind Blast (Mind Blast in shadow form bonus) + else if (m_caster->GetShapeshiftForm() == FORM_SHADOW && (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x00002000))) + { + Unit::AuraList const& ImprMindBlast = m_caster->GetAurasByType(SPELL_AURA_ADD_FLAT_MODIFIER); + for (Unit::AuraList::const_iterator i = ImprMindBlast.begin(); i != ImprMindBlast.end(); ++i) + { + if ((*i)->GetSpellProto()->GetSpellFamilyName() == SPELLFAMILY_PRIEST && + ((*i)->GetSpellProto()->SpellIconID == 95)) + { + int chance = (*i)->GetSpellProto()->CalculateSimpleValue(EFFECT_INDEX_1); + if (roll_chance_i(chance)) + // Mind Trauma + m_caster->CastSpell(unitTarget, 48301, true); + break; + } + } + } + break; + } + case SPELLFAMILY_DRUID: + { + SpellEffectEntry const* rakeSpellEffect = m_spellInfo->GetSpellEffect(EFFECT_INDEX_2); + // Ferocious Bite + if (m_caster->GetTypeId()==TYPEID_PLAYER && (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x000800000)) && m_spellInfo->SpellVisual[0]==6587) + { + // converts up to 30 points of energy into ($f1+$AP/410) additional damage + float ap = m_caster->GetTotalAttackPowerValue(BASE_ATTACK); + float multiple = ap / 410 + effect->DmgMultiplier; + damage += int32(((Player*)m_caster)->GetComboPoints() * ap * 7 / 100); + uint32 energy = m_caster->GetPower(POWER_ENERGY); + uint32 used_energy = energy > 30 ? 30 : energy; + damage += int32(used_energy * multiple); + m_caster->SetPower(POWER_ENERGY, energy - used_energy); + } + // Rake + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0000000000001000) && rakeSpellEffect && rakeSpellEffect->Effect == SPELL_EFFECT_ADD_COMBO_POINTS) + { + // $AP*0.01 bonus + damage += int32(m_caster->GetTotalAttackPowerValue(BASE_ATTACK) / 100); + } + // Swipe + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0010000000000000)) + { + damage += int32(m_caster->GetTotalAttackPowerValue(BASE_ATTACK) * 0.08f); + } + break; + } + case SPELLFAMILY_ROGUE: + { + // Envenom + if (m_caster->GetTypeId()==TYPEID_PLAYER && (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x800000000))) + { + // consume from stack dozes not more that have combo-points + if (uint32 combo = ((Player*)m_caster)->GetComboPoints()) + { + Aura* poison = 0; + // Lookup for Deadly poison (only attacker applied) + Unit::AuraList const& auras = unitTarget->GetAurasByType(SPELL_AURA_PERIODIC_DAMAGE); + for(Unit::AuraList::const_iterator itr = auras.begin(); itr!=auras.end(); ++itr) + { + SpellClassOptionsEntry const* poisonClassOptions = (*itr)->GetSpellProto()->GetSpellClassOptions(); + if(!poisonClassOptions) + continue; + if( poisonClassOptions->SpellFamilyName==SPELLFAMILY_ROGUE && + (poisonClassOptions->SpellFamilyFlags & UI64LIT(0x10000)) && + (*itr)->GetCasterGuid() == m_caster->GetObjectGuid()) + { + poison = *itr; + break; + } + } + // count consumed deadly poison doses at target + if (poison) + { + bool needConsume = true; + uint32 spellId = poison->GetId(); + uint32 doses = poison->GetStackAmount(); + if (doses > combo) + doses = combo; + + // Master Poisoner + Unit::AuraList const& auraList = ((Player*)m_caster)->GetAurasByType(SPELL_AURA_MOD_DURATION_OF_EFFECTS_BY_DISPEL); + for (Unit::AuraList::const_iterator iter = auraList.begin(); iter != auraList.end(); ++iter) + { + if ((*iter)->GetSpellProto()->GetSpellFamilyName() == SPELLFAMILY_ROGUE && (*iter)->GetSpellProto()->SpellIconID == 1960) + { + if (int32 chance = (*iter)->GetSpellProto()->CalculateSimpleValue(EFFECT_INDEX_2)) + if (roll_chance_i(chance)) + needConsume = false; + + break; + } + } + + if (needConsume) + unitTarget->RemoveAuraHolderFromStack(spellId, doses, m_caster->GetObjectGuid()); + + damage *= doses; + damage += int32(((Player*)m_caster)->GetTotalAttackPowerValue(BASE_ATTACK) * 0.09f * doses); + } + // Eviscerate and Envenom Bonus Damage (item set effect) + if (m_caster->GetDummyAura(37169)) + damage += ((Player*)m_caster)->GetComboPoints() * 40; + } + } + // Eviscerate + else if ((classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x00020000)) && m_caster->GetTypeId()==TYPEID_PLAYER) + { + if (uint32 combo = ((Player*)m_caster)->GetComboPoints()) + { + float ap = m_caster->GetTotalAttackPowerValue(BASE_ATTACK); + damage += irand(int32(ap * combo * 0.03f), int32(ap * combo * 0.07f)); + + // Eviscerate and Envenom Bonus Damage (item set effect) + if (m_caster->GetDummyAura(37169)) + damage += combo * 40; + } + } + break; + } + case SPELLFAMILY_HUNTER: + { + // Gore + if (m_spellInfo->SpellIconID == 1578) + { + if (m_caster->HasAura(57627)) // Charge 6 sec post-affect + damage *= 2; + } + // Steady Shot + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x100000000)) + { + int32 base = irand((int32)m_caster->GetWeaponDamageRange(RANGED_ATTACK, MINDAMAGE), (int32)m_caster->GetWeaponDamageRange(RANGED_ATTACK, MAXDAMAGE)); + damage += int32(float(base) / m_caster->GetAttackTime(RANGED_ATTACK) * 2800 + m_caster->GetTotalAttackPowerValue(RANGED_ATTACK) * 0.1f); + } + break; + } + case SPELLFAMILY_PALADIN: + { + // Judgement of Righteousness - receive benefit from Spell Damage and Attack power + if (m_spellInfo->Id == 20187) + { + float ap = m_caster->GetTotalAttackPowerValue(BASE_ATTACK); + int32 holy = m_caster->SpellBaseDamageBonusDone(GetSpellSchoolMask(m_spellInfo)); + if (holy < 0) + holy = 0; + damage += int32(ap * 0.2f) + int32(holy * 32 / 100); + } + // Judgement of Vengeance/Corruption ${1+0.22*$SPH+0.14*$AP} + 10% for each application of Holy Vengeance/Blood Corruption on the target + else if ((classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x800000000)) && m_spellInfo->SpellIconID==2292) + { + uint32 debuf_id; + switch (m_spellInfo->Id) + { + case 53733: debuf_id = 53742; break;// Judgement of Corruption -> Blood Corruption + case 31804: debuf_id = 31803; break;// Judgement of Vengeance -> Holy Vengeance + default: return; + } + + float ap = m_caster->GetTotalAttackPowerValue(BASE_ATTACK); + int32 holy = m_caster->SpellBaseDamageBonusDone(GetSpellSchoolMask(m_spellInfo)); + if (holy < 0) + holy = 0; + damage += int32(ap * 0.14f) + int32(holy * 22 / 100); + // Get stack of Holy Vengeance on the target added by caster + uint32 stacks = 0; + Unit::AuraList const& auras = unitTarget->GetAurasByType(SPELL_AURA_PERIODIC_DAMAGE); + for (Unit::AuraList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + if (((*itr)->GetId() == debuf_id) && (*itr)->GetCasterGuid() == m_caster->GetObjectGuid()) + { + stacks = (*itr)->GetStackAmount(); + break; + } + } + // + 10% for each application of Holy Vengeance on the target + if (stacks) + damage += damage * stacks * 10 / 100; + } + // Avenger's Shield ($m1+0.07*$SPH+0.07*$AP) - ranged sdb for future + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0000000000004000)) + { + float ap = m_caster->GetTotalAttackPowerValue(BASE_ATTACK); + int32 holy = m_caster->SpellBaseDamageBonusDone(GetSpellSchoolMask(m_spellInfo)); + if (holy < 0) + holy = 0; + damage += int32(ap * 0.07f) + int32(holy * 7 / 100); + } + // Hammer of Wrath ($m1+0.15*$SPH+0.15*$AP) - ranged type sdb future fix + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0000008000000000)) + { + float ap = m_caster->GetTotalAttackPowerValue(BASE_ATTACK); + int32 holy = m_caster->SpellBaseDamageBonusDone(GetSpellSchoolMask(m_spellInfo)); + if (holy < 0) + holy = 0; + damage += int32(ap * 0.15f) + int32(holy * 15 / 100); + } + // Hammer of the Righteous + else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0004000000000000)) + { + // Add main hand dps * effect[2] amount + float average = (m_caster->GetFloatValue(UNIT_FIELD_MINDAMAGE) + m_caster->GetFloatValue(UNIT_FIELD_MAXDAMAGE)) / 2; + int32 count = m_caster->CalculateSpellDamage(unitTarget, m_spellInfo, EFFECT_INDEX_2); + damage += count * int32(average * IN_MILLISECONDS) / m_caster->GetAttackTime(BASE_ATTACK); + } + // Judgement + else if (m_spellInfo->Id == 54158) + { + // [1 + 0.25 * SPH + 0.16 * AP] + damage += int32(m_caster->GetTotalAttackPowerValue(BASE_ATTACK) * 0.16f); + } + break; + } + } + + if (damage >= 0) + m_damage += damage; + } +} + +void Spell::EffectDummy(SpellEffectEntry const* effect) +{ + if (!unitTarget && !gameObjTarget && !itemTarget) + return; + + // selection by spell family + switch(m_spellInfo->GetSpellFamilyName()) + { + case SPELLFAMILY_GENERIC: + { + switch (m_spellInfo->Id) + { + case 3360: // Curse of the Eye + { + if (!unitTarget) + return; + + uint32 spell_id = (unitTarget->getGender() == GENDER_MALE) ? 10651 : 10653; + + m_caster->CastSpell(unitTarget, spell_id, true); + return; + } + case 7671: // Transformation (human<->worgen) + { + if (!unitTarget) + return; + + // Transform Visual + unitTarget->CastSpell(unitTarget, 24085, true); + return; + } + case 8063: // Deviate Fish + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spell_id = 0; + switch (urand(1, 5)) + { + case 1: spell_id = 8064; break; // Sleepy + case 2: spell_id = 8065; break; // Invigorate + case 3: spell_id = 8066; break; // Shrink + case 4: spell_id = 8067; break; // Party Time! + case 5: spell_id = 8068; break; // Healthy Spirit + } + m_caster->CastSpell(m_caster, spell_id, true, NULL); + return; + } + case 8213: // Savory Deviate Delight + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spell_id = 0; + switch (urand(1, 2)) + { + // Flip Out - ninja + case 1: spell_id = (m_caster->getGender() == GENDER_MALE ? 8219 : 8220); break; + // Yaaarrrr - pirate + case 2: spell_id = (m_caster->getGender() == GENDER_MALE ? 8221 : 8222); break; + } + + m_caster->CastSpell(m_caster, spell_id, true, NULL); + return; + } + case 9976: // Polly Eats the E.C.A.C. + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // Summon Polly Jr. + unitTarget->CastSpell(unitTarget, 9998, true); + + ((Creature*)unitTarget)->ForcedDespawn(100); + return; + } + case 10254: // Stone Dwarf Awaken Visual + { + if (m_caster->GetTypeId() != TYPEID_UNIT) + return; + + // see spell 10255 (aura dummy) + m_caster->clearUnitState(UNIT_STAT_ROOT); + m_caster->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + return; + } + case 13120: // net-o-matic + { + if (!unitTarget) + return; + + uint32 spell_id = 0; + + uint32 roll = urand(0, 99); + + if (roll < 2) // 2% for 30 sec self root (off-like chance unknown) + spell_id = 16566; + else if (roll < 4) // 2% for 20 sec root, charge to target (off-like chance unknown) + spell_id = 13119; + else // normal root + spell_id = 13099; + + m_caster->CastSpell(unitTarget, spell_id, true, NULL); + return; + } + case 13567: // Dummy Trigger + { + // can be used for different aura triggering, so select by aura + if (!m_triggeredByAuraSpell || !unitTarget) + return; + + switch (m_triggeredByAuraSpell->Id) + { + case 26467: // Persistent Shield + m_caster->CastCustomSpell(unitTarget, 26470, &damage, NULL, NULL, true); + break; + default: + sLog.outError("EffectDummy: Non-handled case for spell 13567 for triggered aura %u", m_triggeredByAuraSpell->Id); + break; + } + return; + } + case 14537: // Six Demon Bag + { + if (!unitTarget) + return; + + Unit* newTarget = unitTarget; + uint32 spell_id = 0; + uint32 roll = urand(0, 99); + if (roll < 25) // Fireball (25% chance) + spell_id = 15662; + else if (roll < 50) // Frostbolt (25% chance) + spell_id = 11538; + else if (roll < 70) // Chain Lighting (20% chance) + spell_id = 21179; + else if (roll < 77) // Polymorph (10% chance, 7% to target) + spell_id = 14621; + else if (roll < 80) // Polymorph (10% chance, 3% to self, backfire) + { + spell_id = 14621; + newTarget = m_caster; + } + else if (roll < 95) // Enveloping Winds (15% chance) + spell_id = 25189; + else // Summon Felhund minion (5% chance) + { + spell_id = 14642; + newTarget = m_caster; + } + + m_caster->CastSpell(newTarget, spell_id, true, m_CastItem); + return; + } + case 15998: // Capture Worg Pup + case 29435: // Capture Female Kaliri Hatchling + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + Creature* creatureTarget = (Creature*)unitTarget; + + creatureTarget->ForcedDespawn(); + return; + } + case 16589: // Noggenfogger Elixir + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spell_id = 0; + switch (urand(1, 3)) + { + case 1: spell_id = 16595; break; + case 2: spell_id = 16593; break; + default: spell_id = 16591; break; + } + + m_caster->CastSpell(m_caster, spell_id, true, NULL); + return; + } + case 17009: // Voodoo + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spell_id = 0; + switch (urand(0, 6)) + { + case 0: spell_id = 16707; break; // Hex + case 1: spell_id = 16708; break; // Hex + case 2: spell_id = 16709; break; // Hex + case 3: spell_id = 16711; break; // Grow + case 4: spell_id = 16712; break; // Special Brew + case 5: spell_id = 16713; break; // Ghostly + case 6: spell_id = 16716; break; // Launch + } + + m_caster->CastSpell(unitTarget, spell_id, true, NULL, NULL, m_originalCasterGUID, m_spellInfo); + return; + } + case 17251: // Spirit Healer Res + { + if (!unitTarget) + return; + + Unit* caster = GetAffectiveCaster(); + + if (caster && caster->GetTypeId() == TYPEID_PLAYER) + { + WorldPacket data(SMSG_SPIRIT_HEALER_CONFIRM, 8); + data << unitTarget->GetObjectGuid(); + ((Player*)caster)->GetSession()->SendPacket(&data); + } + return; + } + case 17271: // Test Fetid Skull + { + if (!itemTarget && m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spell_id = urand(0, 1) + ? 17269 // Create Resonating Skull + : 17270; // Create Bone Dust + + m_caster->CastSpell(m_caster, spell_id, true, NULL); + return; + } + case 17770: // Wolfshead Helm Energy + { + m_caster->CastSpell(m_caster, 29940, true, NULL); + return; + } + case 17950: // Shadow Portal + { + if (!unitTarget) + return; + + // Shadow Portal + const uint32 spell_list[6] = {17863, 17939, 17943, 17944, 17946, 17948}; + + m_caster->CastSpell(unitTarget, spell_list[urand(0, 5)], true); + return; + } + case 19411: // Lava Bomb + case 20474: // Lava Bomb + { + if (!unitTarget) + return; + + // Hack alert! + // This dummy are expected to cast spell 20494 to summon GO entry 177704 + // Spell does not exist client side, so we have to make a hack, creating the GO (SPELL_EFFECT_SUMMON_OBJECT_WILD) + // Spell should appear in both SMSG_SPELL_START/GO and SMSG_SPELLLOGEXECUTE + + // For later, creating custom spell + // _START: packguid: target, cast flags: 0xB, TARGET_FLAG_SELF + // _GO: packGuid: target, cast flags: 0x4309, TARGET_FLAG_DEST_LOCATION + // LOG: spell: 20494, effect, pguid: goguid + + GameObject* pGameObj = new GameObject; + + Map* map = unitTarget->GetMap(); + + if (!pGameObj->Create(map->GenerateLocalLowGuid(HIGHGUID_GAMEOBJECT), 177704, + map, m_caster->GetPhaseMask(), + unitTarget->GetPositionX(), unitTarget->GetPositionY(), unitTarget->GetPositionZ(), + unitTarget->GetOrientation())) + { + delete pGameObj; + return; + } + + DEBUG_LOG("Gameobject, create custom in SpellEffects.cpp EffectDummy"); + + // Expect created without owner, but with level from _template + pGameObj->SetRespawnTime(MINUTE / 2); + pGameObj->SetUInt32Value(GAMEOBJECT_LEVEL, pGameObj->GetGOInfo()->trap.level); + pGameObj->SetSpellId(m_spellInfo->Id); + + map->Add(pGameObj); + + return; + } + case 19869: // Dragon Orb + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER || unitTarget->HasAura(23958)) + return; + + unitTarget->CastSpell(unitTarget, 19832, true); + return; + } + case 20037: // Explode Orb Effect + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 20038, true); + return; + } + case 20577: // Cannibalize + { + if (unitTarget) + m_caster->CastSpell(m_caster, 20578, false, NULL); + + return; + } + case 21147: // Arcane Vacuum + { + if (!unitTarget) + return; + + // Spell used by Azuregos to teleport all the players to him + // This also resets the target threat + if (m_caster->getThreatManager().getThreat(unitTarget)) + m_caster->getThreatManager().modifyThreatPercent(unitTarget, -100); + + // cast summon player + m_caster->CastSpell(unitTarget, 21150, true); + + return; + } + case 23019: // Crystal Prison Dummy DND + { + if (!unitTarget || !unitTarget->isAlive() || unitTarget->GetTypeId() != TYPEID_UNIT || ((Creature*)unitTarget)->IsPet()) + return; + + Creature* creatureTarget = (Creature*)unitTarget; + if (creatureTarget->IsPet()) + return; + + GameObject* pGameObj = new GameObject; + + Map* map = creatureTarget->GetMap(); + + // create before death for get proper coordinates + if (!pGameObj->Create(map->GenerateLocalLowGuid(HIGHGUID_GAMEOBJECT), 179644, map, m_caster->GetPhaseMask(), + creatureTarget->GetPositionX(), creatureTarget->GetPositionY(), creatureTarget->GetPositionZ(), + creatureTarget->GetOrientation())) + { + delete pGameObj; + return; + } + + pGameObj->SetRespawnTime(creatureTarget->GetRespawnTime() - time(NULL)); + pGameObj->SetOwnerGuid(m_caster->GetObjectGuid()); + pGameObj->SetUInt32Value(GAMEOBJECT_LEVEL, m_caster->getLevel()); + pGameObj->SetSpellId(m_spellInfo->Id); + + creatureTarget->ForcedDespawn(); + + DEBUG_LOG("AddObject at SpellEfects.cpp EffectDummy"); + map->Add(pGameObj); + + return; + } + case 23074: // Arcanite Dragonling + { + if (!m_CastItem) + return; + + m_caster->CastSpell(m_caster, 19804, true, m_CastItem); + return; + } + case 23075: // Mithril Mechanical Dragonling + { + if (!m_CastItem) + return; + + m_caster->CastSpell(m_caster, 12749, true, m_CastItem); + return; + } + case 23076: // Mechanical Dragonling + { + if (!m_CastItem) + return; + + m_caster->CastSpell(m_caster, 4073, true, m_CastItem); + return; + } + case 23133: // Gnomish Battle Chicken + { + if (!m_CastItem) + return; + + m_caster->CastSpell(m_caster, 13166, true, m_CastItem); + return; + } + case 23138: // Gate of Shazzrah + { + if (!unitTarget) + return; + + // Effect probably include a threat change, but it is unclear if fully + // reset or just forced upon target for teleport (SMSG_HIGHEST_THREAT_UPDATE) + + // Gate of Shazzrah + m_caster->CastSpell(unitTarget, 23139, true); + return; + } + case 23448: // Transporter Arrival - Ultrasafe Transporter: Gadgetzan - backfires + { + int32 r = irand(0, 119); + if (r < 20) // Transporter Malfunction - 1/6 polymorph + m_caster->CastSpell(m_caster, 23444, true); + else if (r < 100) // Evil Twin - 4/6 evil twin + m_caster->CastSpell(m_caster, 23445, true); + else // Transporter Malfunction - 1/6 miss the target + m_caster->CastSpell(m_caster, 36902, true); + + return; + } + case 23453: // Gnomish Transporter - Ultrasafe Transporter: Gadgetzan + { + if (roll_chance_i(50)) // Gadgetzan Transporter - success + m_caster->CastSpell(m_caster, 23441, true); + else // Gadgetzan Transporter Failure - failure + m_caster->CastSpell(m_caster, 23446, true); + + return; + } + case 23645: // Hourglass Sand + m_caster->RemoveAurasDueToSpell(23170); // Brood Affliction: Bronze + return; + case 23725: // Gift of Life (warrior bwl trinket) + m_caster->CastSpell(m_caster, 23782, true); + m_caster->CastSpell(m_caster, 23783, true); + return; + case 24930: // Hallow's End Treat + { + uint32 spell_id = 0; + + switch (urand(1, 4)) + { + case 1: spell_id = 24924; break; // Larger and Orange + case 2: spell_id = 24925; break; // Skeleton + case 3: spell_id = 24926; break; // Pirate + case 4: spell_id = 24927; break; // Ghost + } + + m_caster->CastSpell(m_caster, spell_id, true); + return; + } + case 25860: // Reindeer Transformation + { + if (!m_caster->HasAuraType(SPELL_AURA_MOUNTED)) + return; + + float flyspeed = m_caster->GetSpeedRate(MOVE_FLIGHT); + float speed = m_caster->GetSpeedRate(MOVE_RUN); + + m_caster->RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); + + // 5 different spells used depending on mounted speed and if mount can fly or not + if (flyspeed >= 4.1f) + // Flying Reindeer + m_caster->CastSpell(m_caster, 44827, true); // 310% flying Reindeer + else if (flyspeed >= 3.8f) + // Flying Reindeer + m_caster->CastSpell(m_caster, 44825, true); // 280% flying Reindeer + else if (flyspeed >= 1.6f) + // Flying Reindeer + m_caster->CastSpell(m_caster, 44824, true); // 60% flying Reindeer + else if (speed >= 2.0f) + // Reindeer + m_caster->CastSpell(m_caster, 25859, true); // 100% ground Reindeer + else + // Reindeer + m_caster->CastSpell(m_caster, 25858, true); // 60% ground Reindeer + + return; + } + case 26074: // Holiday Cheer + // implemented at client side + return; + case 28006: // Arcane Cloaking + { + if (unitTarget && unitTarget->GetTypeId() == TYPEID_PLAYER) + // Naxxramas Entry Flag Effect DND + m_caster->CastSpell(unitTarget, 29294, true); + + return; + } + case 29126: // Cleansing Flames (Darnassus) + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + unitTarget->CastSpell(unitTarget, 29099, true); + return; + } + case 29135: // Cleansing Flames (Ironforge) + case 29136: // Cleansing Flames (Orgrimmar) + case 29137: // Cleansing Flames (Stormwind) + case 29138: // Cleansing Flames (Thunder Bluff) + case 29139: // Cleansing Flames (Undercity) + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spellIDs[] = {29102, 29130, 29101, 29132, 29133}; + unitTarget->CastSpell(unitTarget, spellIDs[m_spellInfo->Id - 29135], true); + return; + } + case 29200: // Purify Helboar Meat + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spell_id = roll_chance_i(50) + ? 29277 // Summon Purified Helboar Meat + : 29278; // Summon Toxic Helboar Meat + + m_caster->CastSpell(m_caster, spell_id, true, NULL); + return; + } + case 29858: // Soulshatter + { + if (unitTarget && unitTarget->GetTypeId() == TYPEID_UNIT && unitTarget->IsHostileTo(m_caster)) + m_caster->CastSpell(unitTarget, 32835, true); + + return; + } + case 29969: // Summon Blizzard + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 29952, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + case 29979: // Massive Magnetic Pull + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, 30010, true); + return; + } + case 30004: // Flame Wreath + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, 29946, true); + return; + } + case 30458: // Nigh Invulnerability + { + if (!m_CastItem) + return; + + if (roll_chance_i(86)) // Nigh-Invulnerability - success + m_caster->CastSpell(m_caster, 30456, true, m_CastItem); + else // Complete Vulnerability - backfire in 14% casts + m_caster->CastSpell(m_caster, 30457, true, m_CastItem); + + return; + } + case 30507: // Poultryizer + { + if (!m_CastItem) + return; + + if (roll_chance_i(80)) // Poultryized! - success + m_caster->CastSpell(unitTarget, 30501, true, m_CastItem); + else // Poultryized! - backfire 20% + m_caster->CastSpell(unitTarget, 30504, true, m_CastItem); + + return; + } + case 32146: // Liquid Fire + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)m_caster)->KilledMonsterCredit(unitTarget->GetEntry(), unitTarget->GetObjectGuid()); + ((Creature*)unitTarget)->ForcedDespawn(); + return; + } + case 32300: // Focus Fire + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, unitTarget->GetMap()->IsRegularDifficulty() ? 32302 : 38382, true); + return; + } + case 32312: // Move 1 (Chess event AI short distance move) + case 37388: // Move 2 (Chess event AI long distance move) + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // cast generic move spell + m_caster->CastSpell(unitTarget, 30012, true); + return; + } + case 33060: // Make a Wish + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spell_id = 0; + + switch (urand(1, 5)) + { + case 1: spell_id = 33053; break; // Mr Pinchy's Blessing + case 2: spell_id = 33057; break; // Summon Mighty Mr. Pinchy + case 3: spell_id = 33059; break; // Summon Furious Mr. Pinchy + case 4: spell_id = 33062; break; // Tiny Magical Crawdad + case 5: spell_id = 33064; break; // Mr. Pinchy's Gift + } + + m_caster->CastSpell(m_caster, spell_id, true, NULL); + return; + } + case 34803: // Summon Reinforcements + { + m_caster->CastSpell(m_caster, 34810, true); // Summon 20083 behind of the caster + m_caster->CastSpell(m_caster, 34817, true); // Summon 20078 right of the caster + m_caster->CastSpell(m_caster, 34818, true); // Summon 20078 left of the caster + m_caster->CastSpell(m_caster, 34819, true); // Summon 20078 front of the caster + return; + } + case 36677: // Chaos Breath + { + if (!unitTarget) + return; + + uint32 possibleSpells[] = {36693, 36694, 36695, 36696, 36697, 36698, 36699, 36700} ; + std::vector spellPool(possibleSpells, possibleSpells + countof(possibleSpells)); + std::random_shuffle(spellPool.begin(), spellPool.end()); + + for (uint8 i = 0; i < (m_caster->GetMap()->IsRegularDifficulty() ? 2 : 4); ++i) + m_caster->CastSpell(m_caster, spellPool[i], true); + + return; + } + case 33923: // Sonic Boom + case 38796: // Sonic Boom (heroic) + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, m_spellInfo->Id == 33923 ? 33666 : 38795, true); + return; + } + case 35745: // Socrethar's Stone + { + uint32 spell_id; + switch (m_caster->GetAreaId()) + { + case 3900: spell_id = 35743; break; // Socrethar Portal + case 3742: spell_id = 35744; break; // Socrethar Portal + default: return; + } + + m_caster->CastSpell(m_caster, spell_id, true); + return; + } + case 37674: // Chaos Blast + { + if (!unitTarget) + return; + + int32 basepoints0 = 100; + m_caster->CastCustomSpell(unitTarget, 37675, &basepoints0, NULL, NULL, true); + return; + } + case 39189: // Sha'tari Torch + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // Flames + if (unitTarget->HasAura(39199)) + return; + + unitTarget->CastSpell(unitTarget, 39199, true); + ((Player*)m_caster)->KilledMonsterCredit(unitTarget->GetEntry(), unitTarget->GetObjectGuid()); + ((Creature*)unitTarget)->ForcedDespawn(10000); + return; + } + case 39635: // Throw Glaive (first) + case 39849: // Throw Glaive (second) + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 41466, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + case 40802: // Mingo's Fortune Generator (Mingo's Fortune Giblets) + { + // selecting one from Bloodstained Fortune item + uint32 newitemid; + switch (urand(1, 20)) + { + case 1: newitemid = 32688; break; + case 2: newitemid = 32689; break; + case 3: newitemid = 32690; break; + case 4: newitemid = 32691; break; + case 5: newitemid = 32692; break; + case 6: newitemid = 32693; break; + case 7: newitemid = 32700; break; + case 8: newitemid = 32701; break; + case 9: newitemid = 32702; break; + case 10: newitemid = 32703; break; + case 11: newitemid = 32704; break; + case 12: newitemid = 32705; break; + case 13: newitemid = 32706; break; + case 14: newitemid = 32707; break; + case 15: newitemid = 32708; break; + case 16: newitemid = 32709; break; + case 17: newitemid = 32710; break; + case 18: newitemid = 32711; break; + case 19: newitemid = 32712; break; + case 20: newitemid = 32713; break; + default: + return; + } + + DoCreateItem(effect, newitemid); + return; + } + case 40834: // Agonizing Flames + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, 40932, true); + return; + } + case 40869: // Fatal Attraction + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, 41001, true); + return; + } + case 40962: // Blade's Edge Terrace Demon Boss Summon Branch + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spell_id = 0; + switch (urand(1, 4)) + { + case 1: spell_id = 40957; break; // Blade's Edge Terrace Demon Boss Summon 1 + case 2: spell_id = 40959; break; // Blade's Edge Terrace Demon Boss Summon 2 + case 3: spell_id = 40960; break; // Blade's Edge Terrace Demon Boss Summon 3 + case 4: spell_id = 40961; break; // Blade's Edge Terrace Demon Boss Summon 4 + } + unitTarget->CastSpell(unitTarget, spell_id, true); + return; + } + case 41333: // Empyreal Equivalency + { + if (!unitTarget) + return; + + // Equilize the health of all targets based on the corresponding health percent + float health_diff = (float)unitTarget->GetMaxHealth() / (float)m_caster->GetMaxHealth(); + unitTarget->SetHealth(m_caster->GetHealth() * health_diff); + return; + } + case 42287: // Salvage Wreckage + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + if (roll_chance_i(66)) + m_caster->CastSpell(m_caster, 42289, true, m_CastItem); + else + m_caster->CastSpell(m_caster, 42288, true); + + return; + } + case 42628: // Fire Bomb (throw) + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 42629, true); + return; + } + case 42631: // Fire Bomb (explode) + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + unitTarget->RemoveAurasDueToSpell(42629); + unitTarget->CastSpell(unitTarget, 42630, true); + + // despawn the bomb after exploding + ((Creature*)unitTarget)->ForcedDespawn(3000); + return; + } + case 42793: // Burn Body + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Creature* pCreature = (Creature*)unitTarget; + + // Spell can be used in combat and may affect different target than the expected. + // If target is not the expected we need to prevent this effect. + if (pCreature->HasLootRecipient() || pCreature->isInCombat()) + return; + + // set loot recipient, prevent re-use same target + pCreature->SetLootRecipient(m_caster); + + pCreature->ForcedDespawn(m_duration); + + // EFFECT_INDEX_2 has 0 miscvalue for effect 134, doing the killcredit here instead (only one known case exist where 0) + ((Player*)m_caster)->KilledMonster(pCreature->GetCreatureInfo(), pCreature->GetObjectGuid()); + return; + } + case 43014: // Despawn Self + { + if (m_caster->GetTypeId() != TYPEID_UNIT) + return; + + ((Creature*)m_caster)->ForcedDespawn(); + return; + } + case 43036: // Dismembering Corpse + { + if (!unitTarget || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + if (unitTarget->HasAura(43059, EFFECT_INDEX_0)) + return; + + unitTarget->CastSpell(m_caster, 43037, true); + unitTarget->CastSpell(unitTarget, 43059, true); + return; + } + case 43069: // Towers of Certain Doom: Skorn Cannonfire + { + // Towers of Certain Doom: Tower Caster Instakill + m_caster->CastSpell(m_caster, 43072, true); + return; + } + case 43096: // Summon All Players + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, 43097, true); + return; + } + case 43144: // Hatch All Eggs + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 42493, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + case 43209: // Place Ram Meat + { + if (!unitTarget) + return; + + // Self Visual - Sleep Until Cancelled (DND) + unitTarget->RemoveAurasDueToSpell(6606); + return; + } + case 43498: // Siphon Soul + { + // This spell should cast the next spell only for one (player)target, however it should hit multiple targets, hence this kind of implementation + if (!unitTarget || m_UniqueTargetInfo.rbegin()->targetGUID != unitTarget->GetObjectGuid()) + return; + + std::vector possibleTargets; + possibleTargets.reserve(m_UniqueTargetInfo.size()); + for (TargetList::const_iterator itr = m_UniqueTargetInfo.begin(); itr != m_UniqueTargetInfo.end(); ++itr) + { + // Skip Non-Players + if (!itr->targetGUID.IsPlayer()) + continue; + + if (Unit* target = m_caster->GetMap()->GetPlayer(itr->targetGUID)) + possibleTargets.push_back(target); + } + + // Cast Siphon Soul channeling spell + if (!possibleTargets.empty()) + m_caster->CastSpell(possibleTargets[urand(0, possibleTargets.size() - 1)], 43501, false); + + return; + } + case 43572: // Send Them Packing: On /Raise Emote Dummy to Player + { + if (!unitTarget) + return; + + // m_caster (creature) should start walking back to it's "home" here, no clear way how to do that + + // Send Them Packing: On Successful Dummy Spell Kill Credit + m_caster->CastSpell(unitTarget, 42721, true); + return; + } + // Demon Broiled Surprise + case 43723: + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)m_caster)->CastSpell(unitTarget, 43753, true, m_CastItem, NULL, m_originalCasterGUID, m_spellInfo); + return; + } + case 43882: // Scourging Crystal Controller Dummy + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // see spell dummy 50133 + unitTarget->RemoveAurasDueToSpell(43874); + return; + } + case 44454: // Tasty Reef Fish + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + m_caster->CastSpell(unitTarget, 44455, true, m_CastItem); + return; + } + case 44869: // Spectral Blast + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // If target has spectral exhaustion or spectral realm aura return + if (unitTarget->HasAura(44867) || unitTarget->HasAura(46021)) + return; + + // Cast the spectral realm effect spell, visual spell and spectral blast rift summoning + unitTarget->CastSpell(unitTarget, 44866, true, NULL, NULL, m_caster->GetObjectGuid()); + unitTarget->CastSpell(unitTarget, 46648, true, NULL, NULL, m_caster->GetObjectGuid()); + unitTarget->CastSpell(unitTarget, 44811, true); + return; + } + case 44875: // Complete Raptor Capture + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + Creature* creatureTarget = (Creature*)unitTarget; + + creatureTarget->ForcedDespawn(); + + // cast spell Raptor Capture Credit + m_caster->CastSpell(m_caster, 42337, true, NULL); + return; + } + case 44997: // Converting Sentry + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + Creature* creatureTarget = (Creature*)unitTarget; + + creatureTarget->ForcedDespawn(); + + // Converted Sentry Credit + m_caster->CastSpell(m_caster, 45009, true); + return; + } + case 45030: // Impale Emissary + { + // Emissary of Hate Credit + m_caster->CastSpell(m_caster, 45088, true); + return; + } + case 45449: // Arcane Prisoner Rescue + { + uint32 spellId = 0; + switch (rand() % 2) + { + case 0: spellId = 45446; break; // Summon Arcane Prisoner - Male + case 1: spellId = 45448; break; // Summon Arcane Prisoner - Female + } + // Spawn + m_caster->CastSpell(m_caster, spellId, true); + + if (!unitTarget) return; + // Arcane Prisoner Kill Credit + unitTarget->CastSpell(m_caster, 45456, true); + + break; + } + case 45583: // Throw Gnomish Grenade + { + if (!unitTarget || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)m_caster)->KilledMonsterCredit(unitTarget->GetEntry(), unitTarget->GetObjectGuid()); + + // look for gameobject within max spell range of unitTarget, and respawn if found + + // big fire + GameObject* pGo = NULL; + + float fMaxDist = GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex)); + + MaNGOS::NearestGameObjectEntryInPosRangeCheck go_check_big(*unitTarget, 187675, unitTarget->GetPositionX(), unitTarget->GetPositionY(), unitTarget->GetPositionZ(), fMaxDist); + MaNGOS::GameObjectSearcher checker1(pGo, go_check_big); + + Cell::VisitGridObjects(unitTarget, checker1, fMaxDist); + + if (pGo && !pGo->isSpawned()) + { + pGo->SetRespawnTime(MINUTE / 2); + pGo->Refresh(); + } + + // small fire + std::list lList; + + MaNGOS::GameObjectEntryInPosRangeCheck go_check_small(*unitTarget, 187676, unitTarget->GetPositionX(), unitTarget->GetPositionY(), unitTarget->GetPositionZ(), fMaxDist); + MaNGOS::GameObjectListSearcher checker2(lList, go_check_small); + + Cell::VisitGridObjects(unitTarget, checker2, fMaxDist); + + for (std::list::iterator iter = lList.begin(); iter != lList.end(); ++iter) + { + if (!(*iter)->isSpawned()) + { + (*iter)->SetRespawnTime(MINUTE / 2); + (*iter)->Refresh(); + } + } + + return; + } + case 45685: // Magnataur On Death 2 + { + m_caster->RemoveAurasDueToSpell(45673); + m_caster->RemoveAurasDueToSpell(45672); + m_caster->RemoveAurasDueToSpell(45677); + m_caster->RemoveAurasDueToSpell(45681); + m_caster->RemoveAurasDueToSpell(45683); + return; + } + case 45958: // Signal Alliance + { + m_caster->CastSpell(m_caster, effect->CalculateSimpleValue(), true); + return; + } + case 45976: // Open Portal + case 46177: // Open All Portals + { + if (!unitTarget) + return; + + // portal visual + unitTarget->CastSpell(unitTarget, 45977, true); + + // break in case additional procressing in scripting library required + break; + } + case 45980: // Re-Cursive Transmatter Injection + { + if (m_caster->GetTypeId() == TYPEID_PLAYER && unitTarget) + { + if (const SpellEntry* pSpell = sSpellStore.LookupEntry(46022)) + { + m_caster->CastSpell(unitTarget, pSpell, true); + SpellEffectEntry const* killSpellEffect = pSpell->GetSpellEffect(EFFECT_INDEX_0); + ((Player*)m_caster)->KilledMonsterCredit(killSpellEffect ? killSpellEffect->EffectMiscValue : 0); + } + + if (unitTarget->GetTypeId() == TYPEID_UNIT) + ((Creature*)unitTarget)->ForcedDespawn(); + } + + return; + } + case 45989: // Summon Void Sentinel Summoner Visual + { + if (!unitTarget) + return; + + // summon void sentinel + unitTarget->CastSpell(unitTarget, 45988, true); + + return; + } + case 45990: // Collect Oil + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + if (const SpellEntry* pSpell = sSpellStore.LookupEntry(45991)) + { + unitTarget->CastSpell(unitTarget, pSpell, true); + ((Creature*)unitTarget)->ForcedDespawn(m_duration); + } + + return; + } + case 46167: // Planning for the Future: Create Snowfall Glade Pup Cover + case 50918: // Gluttonous Lurkers: Create Basilisk Crystals Cover + case 50926: // Gluttonous Lurkers: Create Zul'Drak Rat Cover + case 51026: // Create Drakkari Medallion Cover + case 51592: // Pickup Primordial Hatchling + case 51961: // Captured Chicken Cover + case 55364: // Create Ghoul Drool Cover + case 61832: // Rifle the Bodies: Create Magehunter Personal Effects Cover + case 74904: // Pickup Sen'jin Frog + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spellId = 0; + + switch (m_spellInfo->Id) + { + case 46167: spellId = 46773; break; + case 50918: spellId = 50919; break; + case 50926: spellId = 50927; break; + case 51026: spellId = 50737; break; + case 51592: spellId = 51593; break; + case 51961: spellId = 51037; break; + case 55364: spellId = 55363; break; + case 61832: spellId = 47096; break; + case 74904: spellId = 74905; break; + } + + if (const SpellEntry* pSpell = sSpellStore.LookupEntry(spellId)) + { + unitTarget->CastSpell(m_caster, spellId, true); + + Creature* creatureTarget = (Creature*)unitTarget; + + if (const SpellCastTimesEntry* pCastTime = sSpellCastTimesStore.LookupEntry(pSpell->CastingTimeIndex)) + creatureTarget->ForcedDespawn(pCastTime->CastTime + 1); + } + return; + } + case 46171: // Scuttle Wrecked Flying Machine + { + if (!unitTarget || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)m_caster)->KilledMonsterCredit(unitTarget->GetEntry(), unitTarget->GetObjectGuid()); + + // look for gameobject within max spell range of unitTarget, and respawn if found + GameObject* pGo = NULL; + + float fMaxDist = GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex)); + + MaNGOS::NearestGameObjectEntryInPosRangeCheck go_check(*unitTarget, 187675, unitTarget->GetPositionX(), unitTarget->GetPositionY(), unitTarget->GetPositionZ(), fMaxDist); + MaNGOS::GameObjectSearcher checker(pGo, go_check); + + Cell::VisitGridObjects(unitTarget, checker, fMaxDist); + + if (pGo && !pGo->isSpawned()) + { + pGo->SetRespawnTime(MINUTE / 2); + pGo->Refresh(); + } + + return; + } + case 46372: // Ice Spear Target Picker + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, 46359, true); + return; + } + case 46289: // Negative Energy + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, 46285, true); + return; + } + case 46430: // Synch Health + { + if (!unitTarget) + return; + + unitTarget->SetHealth(m_caster->GetHealth()); + return; + } + case 46485: // Greatmother's Soulcatcher + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + if (const SpellEntry* pSpell = sSpellStore.LookupEntry(46486)) + { + m_caster->CastSpell(unitTarget, pSpell, true); + + if (SpellEffectEntry const* pSpellEffect = pSpell->GetSpellEffect(EFFECT_INDEX_0)) + if (const SpellEntry *pSpellCredit = sSpellStore.LookupEntry(pSpellEffect->EffectTriggerSpell)) + if(SpellEffectEntry const* pSpellCreditEffect = pSpellCredit->GetSpellEffect(EFFECT_INDEX_0)) + ((Player*)m_caster)->KilledMonsterCredit(pSpellCreditEffect->EffectMiscValue); + + ((Creature*)unitTarget)->ForcedDespawn(); + } + + return; + } + case 46606: // Plague Canister Dummy + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + unitTarget->CastSpell(m_caster, 43160, true); + unitTarget->SetDeathState(JUST_DIED); + unitTarget->SetHealth(0); + return; + } + case 46671: // Cleansing Flames (Exodar) + case 46672: // Cleansing Flames (Silvermoon) + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + unitTarget->CastSpell(unitTarget, m_spellInfo->Id == 46671 ? 46690 : 46689, true); + return; + } + case 46797: // Quest - Borean Tundra - Set Explosives Cart + { + if (!unitTarget || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)m_caster)->KilledMonsterCredit(unitTarget->GetEntry(), unitTarget->GetObjectGuid()); + + // Quest - Borean Tundra - Summon Explosives Cart + unitTarget->CastSpell(unitTarget, 46798, true); + return; + } + case 47110: // Summon Drakuru's Image + { + uint32 spellId = 0; + + // Spell 47117,47149,47316,47405,50439 exist, are these used to check area/meet requirement + // and to cast correct spell in correct area? + + switch (m_caster->GetAreaId()) + { + case 4255: spellId = 47381; break; // Reagent Check (Frozen Mojo) + case 4209: spellId = 47386; break; // Reagent Check (Zim'Bo's Mojo) + case 4270: spellId = 47389; break; // Reagent Check (Desperate Mojo) + case 4216: spellId = 47408; break; // Reagent Check (Sacred Mojo) + case 4196: spellId = 50441; break; // Reagent Check (Survival Mojo) + } + + // The additional castspell arguments are needed here to remove reagents for triggered spells + if (spellId) + m_caster->CastSpell(m_caster, spellId, true, m_CastItem, NULL, m_caster->GetObjectGuid(), m_spellInfo); + + return; + } + case 47170: // Impale Leviroth + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + unitTarget->SetHealthPercent(8.0f); + + // Cosmetic - Underwater Blood (no sound) + unitTarget->CastSpell(unitTarget, 47172, true); + + ((Creature*)unitTarget)->AI()->AttackStart(m_caster); + return; + } + case 47176: // Infect Ice Troll + { + // Spell has wrong areaGroupid, so it can not be casted where expected. + // TODO: research if spells casted by NPC, having TARGET_SCRIPT, can have disabled area check + if (!unitTarget) + return; + + // Plague Effect Self + unitTarget->CastSpell(unitTarget, 47178, true); + return; + } + case 47305: // Potent Explosive Charge + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // only if below 80% hp + if (unitTarget->GetHealthPercent() > 80.0f) + return; + + // Issues with explosion animation (remove insta kill spell resolves the issue) + + // Quest - Jormungar Explosion Spell Spawner + unitTarget->CastSpell(unitTarget, 47311, true); + + // Potent Explosive Charge + unitTarget->CastSpell(unitTarget, 47306, true); + + return; + } + case 47381: // Reagent Check (Frozen Mojo) + case 47386: // Reagent Check (Zim'Bo's Mojo) + case 47389: // Reagent Check (Desperate Mojo) + case 47408: // Reagent Check (Sacred Mojo) + case 50441: // Reagent Check (Survival Mojo) + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + switch (m_spellInfo->Id) + { + case 47381: + // Envision Drakuru + m_caster->CastSpell(m_caster, 47118, true); + break; + case 47386: + m_caster->CastSpell(m_caster, 47150, true); + break; + case 47389: + m_caster->CastSpell(m_caster, 47317, true); + break; + case 47408: + m_caster->CastSpell(m_caster, 47406, true); + break; + case 50441: + m_caster->CastSpell(m_caster, 50440, true); + break; + } + + return; + } + case 48046: // Use Camera + { + if (!unitTarget) + return; + + // No despawn expected, nor any change in dynamic flags/other flags. + // Need internal way to track if credit has been given for this object. + + // Iron Dwarf Snapshot Credit + m_caster->CastSpell(m_caster, 48047, true, m_CastItem, NULL, unitTarget->GetObjectGuid()); + return; + } + case 48790: // Neltharion's Flame + { + if (!unitTarget) + return; + + // Neltharion's Flame Fire Bunny: Periodic Fire Aura + unitTarget->CastSpell(unitTarget, 48786, false); + return; + } + case 49357: // Brewfest Mount Transformation + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + if (!m_caster->HasAuraType(SPELL_AURA_MOUNTED)) + return; + + m_caster->RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); + + // Ram for Alliance, Kodo for Horde + if (((Player*)m_caster)->GetTeam() == ALLIANCE) + { + if (m_caster->GetSpeedRate(MOVE_RUN) >= 2.0f) + // 100% Ram + m_caster->CastSpell(m_caster, 43900, true); + else + // 60% Ram + m_caster->CastSpell(m_caster, 43899, true); + } + else + { + if (((Player*)m_caster)->GetSpeedRate(MOVE_RUN) >= 2.0f) + // 100% Kodo + m_caster->CastSpell(m_caster, 49379, true); + else + // 60% Kodo + m_caster->CastSpell(m_caster, 49378, true); + } + return; + } + case 49634: // Sergeant's Flare + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // Towers of Certain Doom: Tower Bunny Smoke Flare Effect + // TODO: MaNGOS::DynamicObjectUpdater::VisitHelper prevent aura to be applied to dummy creature (see HandleAuraDummy for effect of aura) + m_caster->CastSpell(unitTarget, 56511, true); + + static uint32 const spellCredit[4] = + { + 43077, // E Kill Credit + 43067, // NW Kill Credit + 43087, // SE Kill Credit + 43086, // SW Kill Credit + }; + + // for sizeof(spellCredit) + for (int i = 0; i < 4; ++i) + { + const SpellEntry* pSpell = sSpellStore.LookupEntry(spellCredit[i]); + + if (pSpell->GetEffectMiscValue(EFFECT_INDEX_0) == unitTarget->GetEntry()) + { + m_caster->CastSpell(m_caster, spellCredit[i], true); + break; + } + } + + return; + } + case 49859: // Rune of Command + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // Captive Stone Giant Kill Credit + unitTarget->CastSpell(m_caster, 43564, true); + // Is it supposed to despawn? + ((Creature*)unitTarget)->ForcedDespawn(); + return; + } + case 50133: // Scourging Crystal Controller + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // Scourge Mur'gul Camp: Force Shield Arcane Purple x3 + if (unitTarget->HasAura(43874)) + { + // someone else is already channeling target + if (unitTarget->HasAura(43878)) + return; + + // Scourging Crystal Controller + m_caster->CastSpell(unitTarget, 43878, true, m_CastItem); + } + + return; + } + case 50243: // Teach Language + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // spell has a 1/3 chance to trigger one of the below + if (roll_chance_i(66)) + return; + + if (((Player*)m_caster)->GetTeam() == ALLIANCE) + { + // 1000001 - gnomish binary + m_caster->CastSpell(m_caster, 50242, true); + } + else + { + // 01001000 - goblin binary + m_caster->CastSpell(m_caster, 50246, true); + } + + return; + } + case 50440: // Envision Drakuru + { + if (!unitTarget) + return; + + // Script Cast Summon Image of Drakuru 05 + unitTarget->CastSpell(unitTarget, 50439, true); + return; + } + case 50546: // Ley Line Focus Control Ring Effect + case 50547: // Ley Line Focus Control Amulet Effect + case 50548: // Ley Line Focus Control Talisman Effect + { + if (!m_originalCaster || !unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + switch (m_spellInfo->Id) + { + case 50546: unitTarget->CastSpell(m_originalCaster, 47390, true); break; + case 50547: unitTarget->CastSpell(m_originalCaster, 47472, true); break; + case 50548: unitTarget->CastSpell(m_originalCaster, 47635, true); break; + } + + return; + } + case 51276: // Incinerate Corpse + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + unitTarget->CastSpell(unitTarget, 51278, true); + unitTarget->CastSpell(m_caster, 51279, true); + + unitTarget->SetDeathState(JUST_DIED); + return; + } + case 51330: // Shoot RJR + { + if (!unitTarget) + return; + + // guessed chances + if (roll_chance_i(75)) + m_caster->CastSpell(unitTarget, roll_chance_i(50) ? 51332 : 51366, true, m_CastItem); + else + m_caster->CastSpell(unitTarget, 51331, true, m_CastItem); + + return; + } + case 51333: // Dig For Treasure + { + if (!unitTarget) + return; + + if (roll_chance_i(75)) + m_caster->CastSpell(unitTarget, 51370, true, m_CastItem); + else + m_caster->CastSpell(m_caster, 51345, true); + + return; + } + case 51336: // Magic Pull + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, 50770, true); + return; + } + case 51420: // Digging for Treasure Ping + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // only spell related protector pets exist currently + Pet* pPet = m_caster->GetProtectorPet(); + if (!pPet) + return; + + pPet->SetFacingToObject(unitTarget); + + // Digging for Treasure + pPet->CastSpell(unitTarget, 51405, true); + + ((Creature*)unitTarget)->ForcedDespawn(1); + return; + } + case 51582: // Rocket Boots Engaged (Rocket Boots Xtreme and Rocket Boots Xtreme Lite) + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + if (BattleGround* bg = ((Player*)m_caster)->GetBattleGround()) + bg->EventPlayerDroppedFlag((Player*)m_caster); + + m_caster->CastSpell(m_caster, 30452, true, NULL); + return; + } + case 51840: // Despawn Fruit Tosser + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + if (roll_chance_i(20)) + { + // summon NPC, or... + unitTarget->CastSpell(m_caster, 52070, true); + } + else + { + // ...drop banana, orange or papaya + switch (urand(0, 2)) + { + case 0: unitTarget->CastSpell(m_caster, 51836, true); break; + case 1: unitTarget->CastSpell(m_caster, 51837, true); break; + case 2: unitTarget->CastSpell(m_caster, 51839, true); break; + } + } + + ((Creature*)unitTarget)->ForcedDespawn(5000); + return; + } + case 51866: // Kick Nass + { + // It is possible that Nass Heartbeat (spell id 61438) is involved in this + // If so, unclear how it should work and using the below instead (even though it could be a bit hack-ish) + + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // Only own guardian pet + if (m_caster != unitTarget->GetOwner()) + return; + + // This means we already set state (see below) and need to wait. + if (unitTarget->hasUnitState(UNIT_STAT_ROOT)) + return; + + // Expecting pTargetDummy to be summoned by AI at death of target creatures. + + Creature* pTargetDummy = NULL; + float fRange = GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex)); + + MaNGOS::NearestCreatureEntryWithLiveStateInObjectRangeCheck u_check(*m_caster, 28523, true, false, fRange * 2); + MaNGOS::CreatureLastSearcher searcher(pTargetDummy, u_check); + + Cell::VisitGridObjects(m_caster, searcher, fRange * 2); + + if (pTargetDummy) + { + if (unitTarget->hasUnitState(UNIT_STAT_FOLLOW | UNIT_STAT_FOLLOW_MOVE)) + unitTarget->GetMotionMaster()->MovementExpired(); + + unitTarget->MonsterMoveWithSpeed(pTargetDummy->GetPositionX(), pTargetDummy->GetPositionY(), pTargetDummy->GetPositionZ(), 24.f); + + // Add state to temporarily prevent follow + unitTarget->addUnitState(UNIT_STAT_ROOT); + + // Collect Hair Sample + unitTarget->CastSpell(pTargetDummy, 51870, true); + } + + return; + } + case 51872: // Hair Sample Collected + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // clear state to allow follow again + m_caster->clearUnitState(UNIT_STAT_ROOT); + + // Nass Kill Credit + m_caster->CastSpell(m_caster, 51871, true); + + // Despawn dummy creature + ((Creature*)unitTarget)->ForcedDespawn(); + + return; + } + case 51964: // Tormentor's Incense + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // This might not be the best way, and effect may need some adjustment. Removal of any aura from surrounding dummy creatures? + if (((Creature*)unitTarget)->AI()) + ((Creature*)unitTarget)->AI()->AttackStart(m_caster); + + return; + } + case 51858: // Siphon of Acherus + { + if (!m_originalCaster || !unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + if (Player* pPlayer = m_originalCaster->GetCharmerOrOwnerPlayerOrPlayerItself()) + pPlayer->KilledMonsterCredit(unitTarget->GetEntry(), unitTarget->GetObjectGuid()); + + return; + } + case 52308: // Take Sputum Sample + { + switch(effect->EffectIndex) + { + case EFFECT_INDEX_0: + { + uint32 spellID = m_spellInfo->CalculateSimpleValue(EFFECT_INDEX_0); + uint32 reqAuraID = m_spellInfo->CalculateSimpleValue(EFFECT_INDEX_1); + + if (m_caster->HasAura(reqAuraID, EFFECT_INDEX_0)) + m_caster->CastSpell(m_caster, spellID, true, NULL); + return; + } + case EFFECT_INDEX_1: // additional data for dummy[0] + case EFFECT_INDEX_2: + return; + } + return; + } + case 52369: // Detonate Explosives + case 52371: // Detonate Explosives + { + if (!unitTarget) + return; + + // Cosmetic - Explosion + unitTarget->CastSpell(unitTarget, 46419, true); + + // look for gameobjects within max spell range of unitTarget, and respawn if found + std::list lList; + + float fMaxDist = GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex)); + + MaNGOS::GameObjectEntryInPosRangeCheck go_check(*unitTarget, 182071, unitTarget->GetPositionX(), unitTarget->GetPositionY(), unitTarget->GetPositionZ(), fMaxDist); + MaNGOS::GameObjectListSearcher checker(lList, go_check); + + Cell::VisitGridObjects(unitTarget, checker, fMaxDist); + + for (std::list::iterator iter = lList.begin(); iter != lList.end(); ++iter) + { + if (!(*iter)->isSpawned()) + { + (*iter)->SetRespawnTime(MINUTE / 2); + (*iter)->Refresh(); + } + } + + return; + } + case 52759: // Ancestral Awakening + { + if (!unitTarget) + return; + + m_caster->CastCustomSpell(unitTarget, 52752, &damage, NULL, NULL, true); + return; + } + case 52845: // Brewfest Mount Transformation (Faction Swap) + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + if (!m_caster->HasAuraType(SPELL_AURA_MOUNTED)) + return; + + m_caster->RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); + + // Ram for Horde, Kodo for Alliance + if (((Player*)m_caster)->GetTeam() == HORDE) + { + if (m_caster->GetSpeedRate(MOVE_RUN) >= 2.0f) + // Swift Brewfest Ram, 100% Ram + m_caster->CastSpell(m_caster, 43900, true); + else + // Brewfest Ram, 60% Ram + m_caster->CastSpell(m_caster, 43899, true); + } + else + { + if (((Player*)m_caster)->GetSpeedRate(MOVE_RUN) >= 2.0f) + // Great Brewfest Kodo, 100% Kodo + m_caster->CastSpell(m_caster, 49379, true); + else + // Brewfest Riding Kodo, 60% Kodo + m_caster->CastSpell(m_caster, 49378, true); + } + return; + } + case 53341: // Rune of Cinderglacier + case 53343: // Rune of Razorice + { + // Runeforging Credit + m_caster->CastSpell(m_caster, 54586, true); + return; + } + case 53475: // Set Oracle Faction Friendly + case 53487: // Set Wolvar Faction Honored + case 54015: // Set Oracle Faction Honored + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + if (effect->EffectIndex == EFFECT_INDEX_0) + { + Player* pPlayer = (Player*)m_caster; + + uint32 faction_id = m_currentBasePoints[effect->EffectIndex]; + int32 rep_change = m_currentBasePoints[EFFECT_INDEX_1]; + + FactionEntry const* factionEntry = sFactionStore.LookupEntry(faction_id); + + if (!factionEntry) + return; + + // Set rep to baserep + basepoints (expecting spillover for oposite faction -> become hated) + // Not when player already has equal or higher rep with this faction + if (pPlayer->GetReputationMgr().GetBaseReputation(factionEntry) < rep_change) + pPlayer->GetReputationMgr().SetReputation(factionEntry, rep_change); + + // EFFECT_INDEX_2 most likely update at war state, we already handle this in SetReputation + } + + return; + } + case 53808: // Pygmy Oil + { + const uint32 spellShrink = 53805; + const uint32 spellTransf = 53806; + + if (SpellAuraHolder* holder = m_caster->GetSpellAuraHolder(spellShrink)) + { + // chance to become pygmified (5, 10, 15 etc) + if (roll_chance_i(holder->GetStackAmount() * 5)) + { + m_caster->RemoveAurasDueToSpell(spellShrink); + m_caster->CastSpell(m_caster, spellTransf, true); + return; + } + } + + if (m_caster->HasAura(spellTransf)) + return; + + m_caster->CastSpell(m_caster, spellShrink, true); + return; + } + case 54577: // Throw U.D.E.D. + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // Sometimes issues with explosion animation. Unclear why + // but possibly caused by the order of spells. + + // Permanent Feign Death + unitTarget->CastSpell(unitTarget, 29266, true); + + // need to despawn later + ((Creature*)unitTarget)->ForcedDespawn(2000); + + // Mammoth Explosion Spell Spawner + unitTarget->CastSpell(unitTarget, 54581, true, m_CastItem); + return; + } + case 54850: // Emerge + { + // Cast Emerge summon + m_caster->CastSpell(m_caster, 54851, true); + return; + } + case 54092: // Monster Slayer's Kit + { + if (!unitTarget) + return; + + uint32 spellIds[] = {51853, 54063, 54071, 54086}; + m_caster->CastSpell(unitTarget, spellIds[urand(0, 3)], true); + return; + } + case 55004: // Nitro Boosts + { + if (!m_CastItem) + return; + + if (roll_chance_i(95)) // Nitro Boosts - success + m_caster->CastSpell(m_caster, 54861, true, m_CastItem); + else // Knocked Up - backfire 5% + m_caster->CastSpell(m_caster, 46014, true, m_CastItem); + + return; + } + case 55818: // Hurl Boulder + { + // unclear how many summon min/max random, best guess below + uint32 random = urand(3, 5); + + for (uint32 i = 0; i < random; ++i) + m_caster->CastSpell(m_caster, 55528, true); + + return; + } + case 56430: // Arcane Bomb + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 56431, true, NULL, NULL, m_caster->GetObjectGuid()); + unitTarget->CastSpell(unitTarget, 56432, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + case 57578: // Lava Strike + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + return; + } + case 57908: // Stain Cloth + { + // nothing do more + finish(); + + m_caster->CastSpell(m_caster, 57915, false, m_CastItem); + + // cast item deleted + ClearCastItem(); + break; + } + case 58418: // Portal to Orgrimmar + case 58420: // Portal to Stormwind + return; // implemented in EffectScript[0] + case 58601: // Remove Flight Auras + { + m_caster->RemoveSpellsCausingAura(SPELL_AURA_FLY); + m_caster->RemoveSpellsCausingAura(SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED); + return; + } + case 59640: // Underbelly Elixir + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spell_id = 0; + switch (urand(1, 3)) + { + case 1: spell_id = 59645; break; + case 2: spell_id = 59831; break; + case 3: spell_id = 59843; break; + } + + m_caster->CastSpell(m_caster, spell_id, true, NULL); + return; + } + case 60932: // Disengage (one from creature versions) + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, 60934, true, NULL); + return; + } + case 62105: // To'kini's Blowgun + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // Sleeping Sleep + unitTarget->CastSpell(unitTarget, 62248, true); + + // Although not really correct, it's needed to have access to m_caster later, + // to properly process spell 62110 (cast from gossip). + // Can possibly be replaced with a similar function that doesn't set any dynamic flags. + ((Creature*)unitTarget)->SetLootRecipient(m_caster); + + unitTarget->setFaction(190); // Ambient (neutral) + unitTarget->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_OOC_NOT_ATTACKABLE); + return; + } + case 62278: // Lightning Orb Charger + { + if (!unitTarget) + return; + + unitTarget->CastSpell(m_caster, 62466, true); + unitTarget->CastSpell(unitTarget, 62279, true); + return; + } + case 62797: // Storm Cloud + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, m_caster->GetMap()->IsRegularDifficulty() ? 65123 : 65133, true); + return; + } + case 62907: // Freya's Ward + { + if (!unitTarget) + return; + + for (uint8 i = 0; i < 5; ++i) + m_caster->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + return; + } + case 63499: // Dispel Magic + { + if (!unitTarget) + return; + + unitTarget->RemoveAurasDueToSpell(effect->CalculateSimpleValue()); + return; + } + case 63545: // Icicle + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + } + case 63820: // Summon Scrap Bot Trigger (Ulduar - Mimiron) for Scrap Bots + case 64425: // Summon Scrap Bot Trigger (Ulduar - Mimiron) for Assault Bots + case 64620: // Summon Fire Bot Trigger (Ulduar - Mimiron) for Fire Bots + { + if (!unitTarget) + return; + + uint32 triggerSpell = 0; + switch (m_spellInfo->Id) + { + case 63820: triggerSpell = 64398; break; + case 64425: triggerSpell = 64426; break; + case 64620: triggerSpell = 64621; break; + } + unitTarget->CastSpell(unitTarget, triggerSpell, false); + return; + } + case 64385: // Spinning (from Unusual Compass) + { + m_caster->SetFacingTo(frand(0, M_PI_F * 2)); + return; + } + case 64489: // Feral Rush + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, 64496, true); + return; + } + case 64543: // Melt Ice + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + m_caster->CastSpell(m_caster, 64540, true); + return; + } + case 64673: // Feral Rush (h) + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, 64674, true); + return; + } + case 64981: // Summon Random Vanquished Tentacle + { + uint32 spell_id = 0; + + switch (urand(0, 2)) + { + case 0: spell_id = 64982; break; // Summon Vanquished Crusher Tentacle + case 1: spell_id = 64983; break; // Summon Vanquished Constrictor Tentacle + case 2: spell_id = 64984; break; // Summon Vanquished Corruptor Tentacle + } + + m_caster->CastSpell(m_caster, spell_id, true); + return; + } + case 66390: // Read Last Rites + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // Summon Tualiq Proxy + // Not known what purpose this has + unitTarget->CastSpell(unitTarget, 66411, true); + + // Summon Tualiq Spirit + // Offtopic note: the summoned has aura from spell 37119 and 66419. One of them should + // most likely make summoned "rise", hover up/sideways in the air (MOVEFLAG_LEVITATING + MOVEFLAG_HOVER) + unitTarget->CastSpell(unitTarget, 66412, true); + + ((Player*)m_caster)->KilledMonsterCredit(unitTarget->GetEntry(), unitTarget->GetObjectGuid()); + + // Must have a delay for proper spell animation + ((Creature*)unitTarget)->ForcedDespawn(1000); + return; + } + case 67019: // Flask of the North + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spell_id = 0; + switch (m_caster->getClass()) + { + case CLASS_WARRIOR: + case CLASS_DEATH_KNIGHT: + spell_id = 67018; // STR for Warriors, Death Knights + break; + case CLASS_ROGUE: + case CLASS_HUNTER: + spell_id = 67017; // AP for Rogues, Hunters + break; + case CLASS_PRIEST: + case CLASS_MAGE: + case CLASS_WARLOCK: + spell_id = 67016; // SPD for Priests, Mages, Warlocks + break; + case CLASS_SHAMAN: + // random (SPD, AP) + spell_id = roll_chance_i(50) ? 67016 : 67017; + break; + case CLASS_PALADIN: + case CLASS_DRUID: + default: + // random (SPD, STR) + spell_id = roll_chance_i(50) ? 67016 : 67018; + break; + } + m_caster->CastSpell(m_caster, spell_id, true); + return; + } + case 69922: // Temper Quel'Delar + { + if (!unitTarget) + return; + + // Return Tempered Quel'Delar + unitTarget->CastSpell(m_caster, 69956, true); + return; + } + case 71445: // Twilight Bloodbolt + case 71471: // Twilight Bloodbolt + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, 71818, true); + return; + } + case 71718: // Conjure Flame + case 72040: // Conjure Empowered Flame + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + return; + } + case 71837: // Vampiric Bite + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, 71726, true); + return; + } + case 71861: // Swarming Shadows + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, 71264, true); + return; + } + case 72261: // Delirious Slash + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, m_caster->CanReachWithMeleeAttack(unitTarget) ? 71623 : 72264, true); + return; + } + case 74452: // Conflagration + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, 74453, true); + m_caster->CastSpell(unitTarget, 74454, true, NULL, NULL, m_caster->GetObjectGuid(), m_spellInfo); + return; + } + } + break; + } + case SPELLFAMILY_MAGE: + { + switch (m_spellInfo->Id) + { + case 11958: // Cold Snap + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // immediately finishes the cooldown on Frost spells + const SpellCooldowns& cm = ((Player*)m_caster)->GetSpellCooldownMap(); + for (SpellCooldowns::const_iterator itr = cm.begin(); itr != cm.end();) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(itr->first); + + if (spellInfo->GetSpellFamilyName() == SPELLFAMILY_MAGE && + (GetSpellSchoolMask(spellInfo) & SPELL_SCHOOL_MASK_FROST) && + spellInfo->Id != 11958 && GetSpellRecoveryTime(spellInfo) > 0) + { + ((Player*)m_caster)->RemoveSpellCooldown((itr++)->first, true); + } + else + ++itr; + } + return; + } + case 31687: // Summon Water Elemental + { + if (m_caster->HasAura(70937)) // Glyph of Eternal Water (permanent limited by known spells version) + m_caster->CastSpell(m_caster, 70908, true); + else // temporary version + m_caster->CastSpell(m_caster, 70907, true); + + return; + } + case 32826: // Polymorph Cast Visual + { + if (unitTarget && unitTarget->GetTypeId() == TYPEID_UNIT) + { + // Polymorph Cast Visual Rank 1 + const uint32 spell_list[6] = + { + 32813, // Squirrel Form + 32816, // Giraffe Form + 32817, // Serpent Form + 32818, // Dragonhawk Form + 32819, // Worgen Form + 32820 // Sheep Form + }; + unitTarget->CastSpell(unitTarget, spell_list[urand(0, 5)], true); + } + return; + } + case 38194: // Blink + { + // Blink + if (unitTarget) + m_caster->CastSpell(unitTarget, 38203, true); + + return; + } + } + + // Conjure Mana Gem + if (effect->EffectIndex == EFFECT_INDEX_1 && m_spellInfo->GetSpellEffectIdByIndex(EFFECT_INDEX_0) == SPELL_EFFECT_CREATE_ITEM) + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // checked in create item check, avoid unexpected + if (Item* item = ((Player*)m_caster)->GetItemByLimitedCategory(ITEM_LIMIT_CATEGORY_MANA_GEM)) + if (item->HasMaxCharges()) + return; + + unitTarget->CastSpell( unitTarget, effect->CalculateSimpleValue(), true, m_CastItem); + return; + } + break; + } + case SPELLFAMILY_WARRIOR: + { + SpellClassOptionsEntry const* warClassOptions = m_spellInfo->GetSpellClassOptions(); + // Charge + if (warClassOptions && (warClassOptions->SpellFamilyFlags & UI64LIT(0x1)) && m_spellInfo->SpellVisual[0] == 867) + { + int32 chargeBasePoints0 = damage; + m_caster->CastCustomSpell(m_caster, 34846, &chargeBasePoints0, NULL, NULL, true); + return; + } + // Execute + if (warClassOptions && warClassOptions->SpellFamilyFlags & UI64LIT(0x20000000)) + { + if (!unitTarget) + return; + + uint32 rage = m_caster->GetPower(POWER_RAGE); + + // up to max 30 rage cost + if (rage > 300) + rage = 300; + + // Glyph of Execution bonus + uint32 rage_modified = rage; + + if (Aura* aura = m_caster->GetDummyAura(58367)) + rage_modified += aura->GetModifier()->m_amount * 10; + + int32 basePoints0 = damage+int32(rage_modified * effect->DmgMultiplier + + m_caster->GetTotalAttackPowerValue(BASE_ATTACK)*0.2f); + + m_caster->CastCustomSpell(unitTarget, 20647, &basePoints0, NULL, NULL, true, 0); + + // Sudden Death + if (m_caster->HasAura(52437)) + { + Unit::AuraList const& auras = m_caster->GetAurasByType(SPELL_AURA_PROC_TRIGGER_SPELL); + for (Unit::AuraList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + // Only Sudden Death have this SpellIconID with SPELL_AURA_PROC_TRIGGER_SPELL + if ((*itr)->GetSpellProto()->SpellIconID == 1989) + { + // saved rage top stored in next affect + uint32 lastrage = (*itr)->GetSpellProto()->CalculateSimpleValue(EFFECT_INDEX_1) * 10; + if (lastrage < rage) + rage -= lastrage; + break; + } + } + } + + m_caster->SetPower(POWER_RAGE, m_caster->GetPower(POWER_RAGE) - rage); + return; + } + // Slam + if (warClassOptions && warClassOptions->SpellFamilyFlags & UI64LIT(0x0000000000200000)) + { + if (!unitTarget) + return; + + // dummy cast itself ignored by client in logs + m_caster->CastCustomSpell(unitTarget, 50782, &damage, NULL, NULL, true); + return; + } + // Concussion Blow + if (warClassOptions && warClassOptions->SpellFamilyFlags & UI64LIT(0x0000000004000000)) + { + m_damage += uint32(damage * m_caster->GetTotalAttackPowerValue(BASE_ATTACK) / 100); + return; + } + + switch (m_spellInfo->Id) + { + // Warrior's Wrath + case 21977: + { + if (!unitTarget) + return; + m_caster->CastSpell(unitTarget, 21887, true); // spell mod + return; + } + // Last Stand + case 12975: + { + int32 healthModSpellBasePoints0 = int32(m_caster->GetMaxHealth() * 0.3); + m_caster->CastCustomSpell(m_caster, 12976, &healthModSpellBasePoints0, NULL, NULL, true, NULL); + return; + } + // Bloodthirst + case 23881: + { + m_caster->CastCustomSpell(unitTarget, 23885, &damage, NULL, NULL, true, NULL); + return; + } + case 30012: // Move + { + if (!unitTarget || unitTarget->HasAura(39400)) + return; + + unitTarget->CastSpell(m_caster, 30253, true); + } + case 30284: // Change Facing + { + if (!unitTarget) + return; + + unitTarget->CastSpell(m_caster, 30270, true); + return; + } + case 37144: // Move (Chess event player knight move) + case 37146: // Move (Chess event player pawn move) + case 37148: // Move (Chess event player queen move) + case 37151: // Move (Chess event player rook move) + case 37152: // Move (Chess event player bishop move) + case 37153: // Move (Chess event player king move) + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + // cast generic move spell + m_caster->CastSpell(unitTarget, 30012, true); + return; + } + } + break; + } + case SPELLFAMILY_WARLOCK: + { + SpellClassOptionsEntry const* wrlClassOptions = m_spellInfo->GetSpellClassOptions(); + // Life Tap + if (wrlClassOptions && wrlClassOptions->SpellFamilyFlags & UI64LIT(0x0000000000040000)) + { + if (unitTarget && (int32(unitTarget->GetHealth()) > damage)) + { + // Shouldn't Appear in Combat Log + unitTarget->ModifyHealth(-damage); + + int32 spell_power = m_caster->SpellBaseDamageBonusDone(GetSpellSchoolMask(m_spellInfo)); + int32 mana = damage + spell_power / 2; + + // Improved Life Tap mod + Unit::AuraList const& auraDummy = m_caster->GetAurasByType(SPELL_AURA_DUMMY); + for(Unit::AuraList::const_iterator itr = auraDummy.begin(); itr != auraDummy.end(); ++itr) + if((*itr)->GetSpellProto()->GetSpellFamilyName()==SPELLFAMILY_WARLOCK && (*itr)->GetSpellProto()->SpellIconID == 208) + mana = ((*itr)->GetModifier()->m_amount + 100)* mana / 100; + + m_caster->CastCustomSpell(unitTarget, 31818, &mana, NULL, NULL, true); + + // Mana Feed + int32 manaFeedVal = 0; + Unit::AuraList const& mod = m_caster->GetAurasByType(SPELL_AURA_ADD_FLAT_MODIFIER); + for (Unit::AuraList::const_iterator itr = mod.begin(); itr != mod.end(); ++itr) + { + if((*itr)->GetSpellProto()->GetSpellFamilyName()==SPELLFAMILY_WARLOCK && (*itr)->GetSpellProto()->SpellIconID == 1982) + manaFeedVal+= (*itr)->GetModifier()->m_amount; + } + if (manaFeedVal > 0) + { + manaFeedVal = manaFeedVal * mana / 100; + m_caster->CastCustomSpell(m_caster, 32553, &manaFeedVal, NULL, NULL, true, NULL); + } + } + else + SendCastResult(SPELL_FAILED_FIZZLE); + + return; + } + break; + } + case SPELLFAMILY_PRIEST: + { + SpellClassOptionsEntry const* prtsClassOptions = m_spellInfo->GetSpellClassOptions(); + // Penance + if (prtsClassOptions && prtsClassOptions->SpellFamilyFlags & UI64LIT(0x0080000000000000)) + { + if (!unitTarget) + return; + + int hurt = 0; + int heal = 0; + switch (m_spellInfo->Id) + { + case 47540: hurt = 47758; heal = 47757; break; + case 53005: hurt = 53001; heal = 52986; break; + case 53006: hurt = 53002; heal = 52987; break; + case 53007: hurt = 53003; heal = 52988; break; + default: + sLog.outError("Spell::EffectDummy: Spell %u Penance need set correct heal/damage spell", m_spellInfo->Id); + return; + } + + // prevent interrupted message for main spell + finish(true); + + // replace cast by selected spell, this also make it interruptible including target death case + if (m_caster->IsFriendlyTo(unitTarget)) + m_caster->CastSpell(unitTarget, heal, false); + else + m_caster->CastSpell(unitTarget, hurt, false); + + return; + } + break; + + switch (m_spellInfo->Id) + { + case 21562: // Power Word: Fortitude + { + Unit* target = unitTarget; + if (!target) + target = m_caster; + + if (m_caster->GetTypeId() != TYPEID_PLAYER || target->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(target, ((Player*)m_caster)->GetGroup() != ((Player*)target)->GetGroup() ? 79104 : 79105, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + case 27683: // Shadow Protection + { + Unit* target = unitTarget; + if (!target) + target = m_caster; + + if (m_caster->GetTypeId() != TYPEID_PLAYER || target->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(target, ((Player*)m_caster)->GetGroup() != ((Player*)target)->GetGroup() ? 79106 : 79107, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + } + } + case SPELLFAMILY_DRUID: + { + // Starfall + if (m_spellInfo->IsFitToFamilyMask(UI64LIT(0x0000000000000000), 0x00000100)) + { + // Shapeshifting into an animal form or mounting cancels the effect. + if (m_caster->GetCreatureType() == CREATURE_TYPE_BEAST || m_caster->IsMounted()) + { + if (m_triggeredByAuraSpell) + m_caster->RemoveAurasDueToSpell(m_triggeredByAuraSpell->Id); + return; + } + + // Any effect which causes you to lose control of your character will supress the starfall effect. + if (m_caster->hasUnitState(UNIT_STAT_NO_FREE_MOVE)) + return; + + switch (m_spellInfo->Id) + { + case 50286: m_caster->CastSpell(unitTarget, 50288, true); return; + case 53196: m_caster->CastSpell(unitTarget, 53191, true); return; + case 53197: m_caster->CastSpell(unitTarget, 53194, true); return; + case 53198: m_caster->CastSpell(unitTarget, 53195, true); return; + default: + sLog.outError("Spell::EffectDummy: Unhandeled Starfall spell rank %u", m_spellInfo->Id); + return; + } + } + break; + } + case SPELLFAMILY_ROGUE: + { + switch (m_spellInfo->Id) + { + case 5938: // Shiv + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Player* pCaster = ((Player*)m_caster); + + Item* item = pCaster->GetWeaponForAttack(OFF_ATTACK); + if (!item) + return; + + // all poison enchantments is temporary + uint32 enchant_id = item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT); + if (!enchant_id) + return; + + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + return; + + for (int s = 0; s < 3; ++s) + { + if (pEnchant->type[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL) + continue; + + SpellEntry const* combatEntry = sSpellStore.LookupEntry(pEnchant->spellid[s]); + if (!combatEntry || combatEntry->GetDispel() != DISPEL_POISON) + continue; + + m_caster->CastSpell(unitTarget, combatEntry, true, item); + } + + m_caster->CastSpell(unitTarget, 5940, true); + return; + } + case 14185: // Preparation + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // immediately finishes the cooldown on certain Rogue abilities + const SpellCooldowns& cm = ((Player*)m_caster)->GetSpellCooldownMap(); + for (SpellCooldowns::const_iterator itr = cm.begin(); itr != cm.end();) + { + SpellEntry const *spellInfo = sSpellStore.LookupEntry(itr->first); + SpellClassOptionsEntry const* prepClassOptions = spellInfo->GetSpellClassOptions(); + if (prepClassOptions && prepClassOptions->SpellFamilyName == SPELLFAMILY_ROGUE && (prepClassOptions->SpellFamilyFlags & UI64LIT(0x0000024000000860))) + ((Player*)m_caster)->RemoveSpellCooldown((itr++)->first,true); + else + ++itr; + } + return; + } + case 31231: // Cheat Death + { + // Cheating Death + m_caster->CastSpell(m_caster, 45182, true); + return; + } + case 51662: // Hunger for Blood + { + m_caster->CastSpell(m_caster, 63848, true); + return; + } + } + break; + } + case SPELLFAMILY_HUNTER: + { + SpellClassOptionsEntry const* huntClassOptions = m_spellInfo->GetSpellClassOptions(); + // Steady Shot + if (huntClassOptions && huntClassOptions->SpellFamilyFlags & UI64LIT(0x100000000)) + { + if (!unitTarget || !unitTarget->isAlive()) + return; + + bool found = false; + + // check dazed affect + Unit::AuraList const& decSpeedList = unitTarget->GetAurasByType(SPELL_AURA_MOD_DECREASE_SPEED); + for (Unit::AuraList::const_iterator iter = decSpeedList.begin(); iter != decSpeedList.end(); ++iter) + { + if ((*iter)->GetSpellProto()->SpellIconID==15 && (*iter)->GetSpellProto()->GetDispel()==0) + { + found = true; + break; + } + } + + if (found) + m_damage += damage; + return; + } + + // Disengage + if (huntClassOptions && huntClassOptions->SpellFamilyFlags & UI64LIT(0x0000400000000000)) + { + Unit* target = unitTarget; + uint32 spellid; + switch (m_spellInfo->Id) + { + case 57635: spellid = 57636; break; // one from creature cases + case 61507: spellid = 61508; break; // one from creature cases + default: + sLog.outError("Spell %u not handled propertly in EffectDummy(Disengage)", m_spellInfo->Id); + return; + } + if (!target || !target->isAlive()) + return; + m_caster->CastSpell(target, spellid, true, NULL); + } + + switch (m_spellInfo->Id) + { + case 23989: // Readiness talent + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // immediately finishes the cooldown for hunter abilities + const SpellCooldowns& cm = ((Player*)m_caster)->GetSpellCooldownMap(); + for (SpellCooldowns::const_iterator itr = cm.begin(); itr != cm.end();) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(itr->first); + + if (spellInfo->GetSpellFamilyName() == SPELLFAMILY_HUNTER && spellInfo->Id != 23989 && GetSpellRecoveryTime(spellInfo) > 0 ) + ((Player*)m_caster)->RemoveSpellCooldown((itr++)->first,true); + else + ++itr; + } + return; + } + case 37506: // Scatter Shot + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // break Auto Shot and autohit + m_caster->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + m_caster->AttackStop(); + ((Player*)m_caster)->SendAttackSwingCancelAttack(); + return; + } + // Last Stand + case 53478: + { + if (!unitTarget) + return; + int32 healthModSpellBasePoints0 = int32(unitTarget->GetMaxHealth() * 0.3); + unitTarget->CastCustomSpell(unitTarget, 53479, &healthModSpellBasePoints0, NULL, NULL, true, NULL); + return; + } + // Master's Call + case 53271: + { + Pet* pet = m_caster->GetPet(); + if (!pet || !unitTarget) + return; + + pet->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + return; + } + } + break; + } + case SPELLFAMILY_PALADIN: + { + switch (m_spellInfo->SpellIconID) + { + case 156: // Holy Shock + { + if (!unitTarget) + return; + + int hurt = 0; + int heal = 0; + + switch (m_spellInfo->Id) + { + case 20473: hurt = 25912; heal = 25914; break; + case 20929: hurt = 25911; heal = 25913; break; + case 20930: hurt = 25902; heal = 25903; break; + case 27174: hurt = 27176; heal = 27175; break; + case 33072: hurt = 33073; heal = 33074; break; + case 48824: hurt = 48822; heal = 48820; break; + case 48825: hurt = 48823; heal = 48821; break; + default: + sLog.outError("Spell::EffectDummy: Spell %u not handled in HS", m_spellInfo->Id); + return; + } + + if (m_caster->IsFriendlyTo(unitTarget)) + m_caster->CastSpell(unitTarget, heal, true); + else + m_caster->CastSpell(unitTarget, hurt, true); + + return; + } + case 561: // Judgement of command + { + if (!unitTarget) + return; + + uint32 spell_id = m_currentBasePoints[effect->EffectIndex]; + SpellEntry const* spell_proto = sSpellStore.LookupEntry(spell_id); + if (!spell_proto) + return; + + m_caster->CastSpell(unitTarget, spell_proto, true, NULL); + return; + } + } + + switch (m_spellInfo->Id) + { + case 31789: // Righteous Defense (step 1) + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + { + SendCastResult(SPELL_FAILED_TARGET_AFFECTING_COMBAT); + return; + } + + // 31989 -> dummy effect (step 1) + dummy effect (step 2) -> 31709 (taunt like spell for each target) + Unit* friendTarget = !unitTarget || unitTarget->IsFriendlyTo(m_caster) ? unitTarget : unitTarget->getVictim(); + if (friendTarget) + { + Player* player = friendTarget->GetCharmerOrOwnerPlayerOrPlayerItself(); + if (!player || !player->IsInSameRaidWith((Player*)m_caster)) + friendTarget = NULL; + } + + // non-standard cast requirement check + if (!friendTarget || friendTarget->getAttackers().empty()) + { + ((Player*)m_caster)->RemoveSpellCooldown(m_spellInfo->Id, true); + SendCastResult(SPELL_FAILED_TARGET_AFFECTING_COMBAT); + return; + } + + // Righteous Defense (step 2) (in old version 31980 dummy effect) + // Clear targets for eff 1 + for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + ihit->effectMask &= ~(1 << 1); + + // not empty (checked), copy + Unit::AttackerSet attackers = friendTarget->getAttackers(); + + // selected from list 3 + for (uint32 i = 0; i < std::min(size_t(3), attackers.size()); ++i) + { + Unit::AttackerSet::iterator aItr = attackers.begin(); + std::advance(aItr, rand() % attackers.size()); + AddUnitTarget((*aItr), EFFECT_INDEX_1); + attackers.erase(aItr); + } + + // now let next effect cast spell at each target. + return; + } + case 37877: // Blessing of Faith + { + if (!unitTarget) + return; + + uint32 spell_id = 0; + switch (unitTarget->getClass()) + { + case CLASS_DRUID: spell_id = 37878; break; + case CLASS_PALADIN: spell_id = 37879; break; + case CLASS_PRIEST: spell_id = 37880; break; + case CLASS_SHAMAN: spell_id = 37881; break; + default: return; // ignore for not healing classes + } + + m_caster->CastSpell(m_caster, spell_id, true); + return; + } + } + break; + } + case SPELLFAMILY_SHAMAN: + { + SpellClassOptionsEntry const* shamClassOptions = m_spellInfo->GetSpellClassOptions(); + // Cleansing Totem + if (shamClassOptions && (shamClassOptions->SpellFamilyFlags & UI64LIT(0x0000000004000000)) && m_spellInfo->SpellIconID==1673) + { + if (unitTarget) + m_caster->CastSpell(unitTarget, 52025, true); + return; + } + // Healing Stream Totem + if (shamClassOptions && shamClassOptions->SpellFamilyFlags & UI64LIT(0x0000000000002000)) + { + if (unitTarget) + { + if (Unit* owner = m_caster->GetOwner()) + { + // spell have SPELL_DAMAGE_CLASS_NONE and not get bonuses from owner, use main spell for bonuses + if (m_triggeredBySpellInfo) + { + damage = int32(owner->SpellHealingBonusDone(unitTarget, m_triggeredBySpellInfo, damage, HEAL)); + damage = int32(unitTarget->SpellHealingBonusTaken(owner, m_triggeredBySpellInfo, damage, HEAL)); + } + + // Restorative Totems + Unit::AuraList const& mDummyAuras = owner->GetAurasByType(SPELL_AURA_DUMMY); + for (Unit::AuraList::const_iterator i = mDummyAuras.begin(); i != mDummyAuras.end(); ++i) + // only its have dummy with specific icon + if ((*i)->GetSpellProto()->GetSpellFamilyName() == SPELLFAMILY_SHAMAN && (*i)->GetSpellProto()->SpellIconID == 338) + damage += (*i)->GetModifier()->m_amount * damage / 100; + + // Glyph of Healing Stream Totem + if (Aura* dummy = owner->GetDummyAura(55456)) + damage += dummy->GetModifier()->m_amount * damage / 100; + } + m_caster->CastCustomSpell(unitTarget, 52042, &damage, NULL, NULL, true, 0, 0, m_originalCasterGUID); + } + return; + } + // Mana Spring Totem + if (shamClassOptions && shamClassOptions->SpellFamilyFlags & UI64LIT(0x0000000000004000)) + { + if (!unitTarget || unitTarget->getPowerType() != POWER_MANA) + return; + m_caster->CastCustomSpell(unitTarget, 52032, &damage, 0, 0, true, 0, 0, m_originalCasterGUID); + return; + } + // Flametongue Weapon Proc, Ranks + if (shamClassOptions && shamClassOptions->SpellFamilyFlags & UI64LIT(0x0000000000200000)) + { + if (!m_CastItem) + { + sLog.outError("Spell::EffectDummy: spell %i requires cast Item", m_spellInfo->Id); + return; + } + // found spelldamage coefficients of 0.381% per 0.1 speed and 15.244 per 4.0 speed + // but own calculation say 0.385 gives at most one point difference to published values + int32 spellDamage = m_caster->SpellBaseDamageBonusDone(GetSpellSchoolMask(m_spellInfo)); + float weaponSpeed = (1.0f / IN_MILLISECONDS) * m_CastItem->GetProto()->Delay; + int32 totalDamage = int32((damage + 3.85f * spellDamage) * 0.01 * weaponSpeed); + + m_caster->CastCustomSpell(unitTarget, 10444, &totalDamage, NULL, NULL, true, m_CastItem); + return; + } + if (m_spellInfo->Id == 39610) // Mana Tide Totem effect + { + if (!unitTarget || unitTarget->getPowerType() != POWER_MANA) + return; + + // Glyph of Mana Tide + if (Unit* owner = m_caster->GetOwner()) + if (Aura* dummy = owner->GetDummyAura(55441)) + damage += dummy->GetModifier()->m_amount; + // Regenerate 6% of Total Mana Every 3 secs + int32 EffectBasePoints0 = unitTarget->GetMaxPower(POWER_MANA) * damage / 100; + m_caster->CastCustomSpell(unitTarget, 39609, &EffectBasePoints0, NULL, NULL, true, NULL, NULL, m_originalCasterGUID); + return; + } + // Lava Lash + if (m_spellInfo->IsFitToFamilyMask(UI64LIT(0x0000000000000000), 0x00000004)) + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + Item* item = ((Player*)m_caster)->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + if (item) + { + // Damage is increased if your off-hand weapon is enchanted with Flametongue. + Unit::AuraList const& auraDummy = m_caster->GetAurasByType(SPELL_AURA_DUMMY); + for (Unit::AuraList::const_iterator itr = auraDummy.begin(); itr != auraDummy.end(); ++itr) + { + if ((*itr)->GetSpellProto()->IsFitToFamily(SPELLFAMILY_SHAMAN, UI64LIT(0x0000000000200000)) && + (*itr)->GetCastItemGuid() == item->GetObjectGuid()) + { + m_damage += m_damage * damage / 100; + return; + } + } + } + return; + } + // Fire Nova + if (m_spellInfo->SpellIconID == 33) + { + // fire totems slot + Totem* totem = m_caster->GetTotem(TOTEM_SLOT_FIRE); + if (!totem) + return; + + uint32 triggered_spell_id; + switch (m_spellInfo->Id) + { + case 1535: triggered_spell_id = 8349; break; + case 8498: triggered_spell_id = 8502; break; + case 8499: triggered_spell_id = 8503; break; + case 11314: triggered_spell_id = 11306; break; + case 11315: triggered_spell_id = 11307; break; + case 25546: triggered_spell_id = 25535; break; + case 25547: triggered_spell_id = 25537; break; + case 61649: triggered_spell_id = 61650; break; + case 61657: triggered_spell_id = 61654; break; + default: return; + } + + totem->CastSpell(totem, triggered_spell_id, true, NULL, NULL, m_caster->GetObjectGuid()); + + // Fire Nova Visual + totem->CastSpell(totem, 19823, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + break; + } + case SPELLFAMILY_DEATHKNIGHT: + { + SpellClassOptionsEntry const* dkClassOptions = m_spellInfo->GetSpellClassOptions(); + // Death Coil + if (dkClassOptions && dkClassOptions->SpellFamilyFlags & UI64LIT(0x002000)) + { + if (m_caster->IsFriendlyTo(unitTarget)) + { + if (!unitTarget || unitTarget->GetCreatureType() != CREATURE_TYPE_UNDEAD) + return; + + int32 bp = int32(damage * 1.5f); + m_caster->CastCustomSpell(unitTarget, 47633, &bp, NULL, NULL, true); + } + else + { + int32 bp = damage; + m_caster->CastCustomSpell(unitTarget, 47632, &bp, NULL, NULL, true); + } + return; + } + // Hungering Cold + else if (dkClassOptions && dkClassOptions->SpellFamilyFlags & UI64LIT(0x0000100000000000)) + { + m_caster->CastSpell(m_caster, 51209, true); + return; + } + // Death Strike + else if (dkClassOptions && dkClassOptions->SpellFamilyFlags & UI64LIT(0x0000000000000010)) + { + uint32 count = 0; + Unit::SpellAuraHolderMap const& auras = unitTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + if (itr->second->GetSpellProto()->GetDispel() == DISPEL_DISEASE && + itr->second->GetCasterGuid() == m_caster->GetObjectGuid()) + { + ++count; + // max. 15% + if (count == 3) + break; + } + } + + SpellEffectEntry const* dsSpellEffect = m_spellInfo->GetSpellEffect(EFFECT_INDEX_0); + int32 bp = int32(count * m_caster->GetMaxHealth() * (dsSpellEffect ? dsSpellEffect->DmgMultiplier : 0.0f) / 100); + + // Improved Death Strike (percent stored in nonexistent EFFECT_INDEX_2 effect base points) + Unit::AuraList const& auraMod = m_caster->GetAurasByType(SPELL_AURA_ADD_FLAT_MODIFIER); + for (Unit::AuraList::const_iterator iter = auraMod.begin(); iter != auraMod.end(); ++iter) + { + // only required spell have spellicon for SPELL_AURA_ADD_FLAT_MODIFIER + if ((*iter)->GetSpellProto()->SpellIconID == 2751 && (*iter)->GetSpellProto()->GetSpellFamilyName() == SPELLFAMILY_DEATHKNIGHT) + { + bp += (*iter)->GetSpellProto()->CalculateSimpleValue(EFFECT_INDEX_2) * bp / 100; + break; + } + } + + m_caster->CastCustomSpell(m_caster, 45470, &bp, NULL, NULL, true); + return; + } + // Obliterate + else if (dkClassOptions && dkClassOptions->SpellFamilyFlags & UI64LIT(0x0002000000000000)) + { + // search for Annihilation + Unit::AuraList const& dummyList = m_caster->GetAurasByType(SPELL_AURA_DUMMY); + for (Unit::AuraList::const_iterator itr = dummyList.begin(); itr != dummyList.end(); ++itr) + { + if ((*itr)->GetSpellProto()->GetSpellFamilyName() == SPELLFAMILY_DEATHKNIGHT && (*itr)->GetSpellProto()->SpellIconID == 2710) + { + if (roll_chance_i((*itr)->GetModifier()->m_amount)) // don't consume if found + return; + else + break; + } + } + + // consume diseases + unitTarget->RemoveAurasWithDispelType(DISPEL_DISEASE, m_caster->GetObjectGuid()); + } + break; + } + } + + // pet auras + if (PetAura const* petSpell = sSpellMgr.GetPetAura(m_spellInfo->Id, SpellEffectIndex(effect->EffectIndex))) + { + m_caster->AddPetAura(petSpell); + return; + } + + // Script based implementation. Must be used only for not good for implementation in core spell effects + // So called only for not processed cases + bool libraryResult = false; + if (gameObjTarget) + libraryResult = sScriptMgr.OnEffectDummy(m_caster, m_spellInfo->Id, SpellEffectIndex(effect->EffectIndex), gameObjTarget, m_originalCasterGUID); + else if (unitTarget && unitTarget->GetTypeId() == TYPEID_UNIT) + libraryResult = sScriptMgr.OnEffectDummy(m_caster, m_spellInfo->Id, SpellEffectIndex(effect->EffectIndex), (Creature*)unitTarget, m_originalCasterGUID); + else if (itemTarget) + libraryResult = sScriptMgr.OnEffectDummy(m_caster, m_spellInfo->Id, SpellEffectIndex(effect->EffectIndex), itemTarget, m_originalCasterGUID); + + if (libraryResult || !unitTarget) + return; + + // Previous effect might have started script + if (!ScriptMgr::CanSpellEffectStartDBScript(m_spellInfo, SpellEffectIndex(effect->EffectIndex))) + return; + + DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell ScriptStart spellid %u in EffectDummy", m_spellInfo->Id); + m_caster->GetMap()->ScriptsStart(sSpellScripts, m_spellInfo->Id, m_caster, unitTarget); +} + +void Spell::EffectTriggerSpellWithValue(SpellEffectEntry const* effect) +{ + uint32 triggered_spell_id = effect->EffectTriggerSpell; + + // normal case + SpellEntry const* spellInfo = sSpellStore.LookupEntry(triggered_spell_id); + + if (!spellInfo) + { + sLog.outError("EffectTriggerSpellWithValue of spell %u: triggering unknown spell id %i", m_spellInfo->Id, triggered_spell_id); + return; + } + + int32 bp = damage; + m_caster->CastCustomSpell(unitTarget, triggered_spell_id, &bp, &bp, &bp, true, m_CastItem , NULL, m_originalCasterGUID, m_spellInfo); +} + +void Spell::EffectTriggerRitualOfSummoning(SpellEffectEntry const* effect) +{ + uint32 triggered_spell_id = effect->EffectTriggerSpell; + SpellEntry const *spellInfo = sSpellStore.LookupEntry( triggered_spell_id ); + + if (!spellInfo) + { + sLog.outError("EffectTriggerRitualOfSummoning of spell %u: triggering unknown spell id %i", m_spellInfo->Id, triggered_spell_id); + return; + } + + finish(); + + m_caster->CastSpell(unitTarget, spellInfo, false); +} + +void Spell::EffectClearQuest(SpellEffectEntry const* effect) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Player* player = (Player*)m_caster; + + uint32 quest_id = effect->EffectMiscValue; + + if (!sObjectMgr.GetQuestTemplate(quest_id)) + { + sLog.outError("Spell::EffectClearQuest spell entry %u attempt clear quest entry %u but this quest does not exist.", m_spellInfo->Id, quest_id); + return; + } + + // remove quest possibly in quest log (is that expected?) + for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + uint32 quest = player->GetQuestSlotQuestId(slot); + + if (quest == quest_id) + { + player->SetQuestSlot(slot, 0); + // ignore unequippable quest items in this case, it will still be equipped + player->TakeQuestSourceItem(quest_id, false); + } + } + + player->SetQuestStatus(quest_id, QUEST_STATUS_NONE); + player->getQuestStatusMap()[quest_id].m_rewarded = false; + + PhaseUpdateData phaseUdateData; + phaseUdateData.AddQuestUpdate(quest_id); + + player->GetPhaseMgr()->NotifyConditionChanged(phaseUdateData); +} + +void Spell::EffectForceCast(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + + uint32 triggered_spell_id = effect->EffectTriggerSpell; + + // normal case + SpellEntry const* spellInfo = sSpellStore.LookupEntry(triggered_spell_id); + + if (!spellInfo) + { + sLog.outError("EffectForceCast of spell %u: triggering unknown spell id %i", m_spellInfo->Id, triggered_spell_id); + return; + } + + unitTarget->CastSpell(unitTarget, spellInfo, true, NULL, NULL, m_originalCasterGUID, m_spellInfo); +} + +void Spell::EffectTriggerSpell(SpellEffectEntry const* effect) +{ + // only unit case known + if (!unitTarget) + { + if (gameObjTarget || itemTarget) + sLog.outError("Spell::EffectTriggerSpell (Spell: %u): Unsupported non-unit case!", m_spellInfo->Id); + return; + } + + uint32 triggered_spell_id = effect->EffectTriggerSpell; + + // special cases + switch (triggered_spell_id) + { + case 18461: // Vanish (not exist) + { + unitTarget->RemoveSpellsCausingAura(SPELL_AURA_MOD_ROOT); + unitTarget->RemoveSpellsCausingAura(SPELL_AURA_MOD_DECREASE_SPEED); + unitTarget->RemoveSpellsCausingAura(SPELL_AURA_MOD_STALKED); + + // if this spell is given to NPC it must handle rest by it's own AI + if (unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spellId = 1784; + // reset cooldown on it if needed + if (((Player*)unitTarget)->HasSpellCooldown(spellId)) + ((Player*)unitTarget)->RemoveSpellCooldown(spellId); + + m_caster->CastSpell(unitTarget, spellId, true); + return; + } + case 29284: // Brittle Armor - (need add max stack of 24575 Brittle Armor) + m_caster->CastSpell(unitTarget, 24575, true, m_CastItem, NULL, m_originalCasterGUID); + return; + case 29286: // Mercurial Shield - (need add max stack of 26464 Mercurial Shield) + m_caster->CastSpell(unitTarget, 26464, true, m_CastItem, NULL, m_originalCasterGUID); + return; + case 31980: // Righteous Defense + { + m_caster->CastSpell(unitTarget, 31790, true, m_CastItem, NULL, m_originalCasterGUID); + return; + } + case 35729: // Cloak of Shadows + { + Unit::SpellAuraHolderMap& Auras = unitTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::iterator iter = Auras.begin(); iter != Auras.end(); ++iter) + { + // Remove all harmful spells on you except positive/passive/physical auras + if (!iter->second->IsPositive() && + !iter->second->IsPassive() && + !iter->second->IsDeathPersistent() && + (GetSpellSchoolMask(iter->second->GetSpellProto()) & SPELL_SCHOOL_MASK_NORMAL) == 0) + { + m_caster->RemoveAurasDueToSpell(iter->second->GetSpellProto()->Id); + iter = Auras.begin(); + } + } + return; + } + case 41967: // Priest Shadowfiend (34433) need apply mana gain trigger aura on pet + { + if (Unit* pet = unitTarget->GetPet()) + pet->CastSpell(pet, 28305, true); + return; + } + } + + // normal case + SpellEntry const* spellInfo = sSpellStore.LookupEntry(triggered_spell_id); + if (!spellInfo) + { + // No previous Effect might have started a script + bool startDBScript = unitTarget && ScriptMgr::CanSpellEffectStartDBScript(m_spellInfo, SpellEffectIndex(effect->EffectIndex)); + if (startDBScript) + { + DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell ScriptStart spellid %u in EffectTriggerSpell", m_spellInfo->Id); + startDBScript = m_caster->GetMap()->ScriptsStart(sSpellScripts, m_spellInfo->Id, m_caster, unitTarget); + } + + if (!startDBScript) + sLog.outError("EffectTriggerSpell of spell %u: triggering unknown spell id %i", m_spellInfo->Id, triggered_spell_id); + return; + } + + // select formal caster for triggered spell + Unit* caster = m_caster; + + // some triggered spells require specific equipment + if (spellInfo->GetEquippedItemClass() >=0 && m_caster->GetTypeId()==TYPEID_PLAYER) + { + // main hand weapon required + if (spellInfo->HasAttribute(SPELL_ATTR_EX3_MAIN_HAND)) + { + Item* item = ((Player*)m_caster)->GetWeaponForAttack(BASE_ATTACK, true, false); + + // skip spell if no weapon in slot or broken + if (!item) + return; + + // skip spell if weapon not fit to triggered spell + if (!item->IsFitToSpellRequirements(spellInfo)) + return; + } + + // offhand hand weapon required + if (spellInfo->HasAttribute(SPELL_ATTR_EX3_REQ_OFFHAND)) + { + Item* item = ((Player*)m_caster)->GetWeaponForAttack(OFF_ATTACK, true, false); + + // skip spell if no weapon in slot or broken + if (!item) + return; + + // skip spell if weapon not fit to triggered spell + if (!item->IsFitToSpellRequirements(spellInfo)) + return; + } + } + else + { + // Note: not exist spells with weapon req. and IsSpellHaveCasterSourceTargets == true + // so this just for speedup places in else + caster = IsSpellWithCasterSourceTargetsOnly(spellInfo) ? unitTarget : m_caster; + } + + caster->CastSpell(unitTarget, spellInfo, true, m_CastItem, NULL, m_originalCasterGUID, m_spellInfo); +} + +void Spell::EffectTriggerMissileSpell(SpellEffectEntry const* effect) +{ + uint32 triggered_spell_id = effect->EffectTriggerSpell; + + // normal case + SpellEntry const* spellInfo = sSpellStore.LookupEntry(triggered_spell_id); + + if (!spellInfo) + { + sLog.outError("EffectTriggerMissileSpell of spell %u (eff: %u): triggering unknown spell id %u", + m_spellInfo->Id,effect->EffectIndex,triggered_spell_id); + return; + } + + if (m_CastItem) + DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "WORLD: cast Item spellId - %i", spellInfo->Id); + + m_caster->CastSpell(m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ, spellInfo, true, m_CastItem, NULL, m_originalCasterGUID, m_spellInfo); +} + +void Spell::EffectJump(SpellEffectEntry const* effect) +{ + if (m_caster->IsTaxiFlying()) + return; + + // Init dest coordinates + float x, y, z, o; + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) + { + m_targets.getDestination(x, y, z); + + if(effect->EffectImplicitTargetA == TARGET_BEHIND_VICTIM) + { + // explicit cast data from client or server-side cast + // some spell at client send caster + Unit* pTarget = NULL; + if (m_targets.getUnitTarget() && m_targets.getUnitTarget() != m_caster) + pTarget = m_targets.getUnitTarget(); + else if (unitTarget->getVictim()) + pTarget = m_caster->getVictim(); + else if (m_caster->GetTypeId() == TYPEID_PLAYER) + pTarget = m_caster->GetMap()->GetUnit(((Player*)m_caster)->GetSelectionGuid()); + + o = pTarget ? pTarget->GetOrientation() : m_caster->GetOrientation(); + } + else + o = m_caster->GetOrientation(); + } + else if (unitTarget) + { + unitTarget->GetContactPoint(m_caster, x, y, z, CONTACT_DISTANCE); + o = m_caster->GetOrientation(); + } + else if (gameObjTarget) + { + gameObjTarget->GetContactPoint(m_caster, x, y, z, CONTACT_DISTANCE); + o = m_caster->GetOrientation(); + } + else + { + sLog.outError("Spell::EffectJump - unsupported target mode for spell ID %u", m_spellInfo->Id); + return; + } + + m_caster->NearTeleportTo(x, y, z, o, true); // TODO Implement this as jump movement? +} + +void Spell::EffectTeleportUnits(SpellEffectEntry const* effect) // TODO - Use target settings for this effect! +{ + if (!unitTarget || unitTarget->IsTaxiFlying()) + return; + + switch (m_spellInfo->Id) + { + case 48129: // Scroll of Recall + case 60320: // Scroll of Recall II + case 60321: // Scroll of Recall III + { + uint32 failAtLevel = 0; + switch (m_spellInfo->Id) + { + case 48129: failAtLevel = 40; break; + case 60320: failAtLevel = 70; break; + case 60321: failAtLevel = 80; break; + } + + if (unitTarget->getLevel() > failAtLevel && unitTarget->GetTypeId() == TYPEID_PLAYER) + { + unitTarget->CastSpell(unitTarget, 60444, true); + // TODO: Unclear use of probably related spell 60322 + uint32 spellId = (((Player*)unitTarget)->GetTeam() == ALLIANCE ? 60323 : 60328) + urand(0, 7); + unitTarget->CastSpell(unitTarget, spellId, true); + return; + } + break; + } + } + + // Target dependend on TargetB, if there is none provided, decide dependend on A + uint32 targetType = effect->EffectImplicitTargetB; + if (!targetType) + targetType = effect->EffectImplicitTargetA; + + switch (targetType) + { + case TARGET_INNKEEPER_COORDINATES: + { + // Only players can teleport to innkeeper + if (unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)unitTarget)->TeleportToHomebind(unitTarget == m_caster ? TELE_TO_SPELL : 0); + return; + } + case TARGET_AREAEFFECT_INSTANT: // in all cases first TARGET_TABLE_X_Y_Z_COORDINATES + case TARGET_TABLE_X_Y_Z_COORDINATES: + { + SpellTargetPosition const* st = sSpellMgr.GetSpellTargetPosition(m_spellInfo->Id); + if (!st) + { + sLog.outError("Spell::EffectTeleportUnits - unknown Teleport coordinates for spell ID %u", m_spellInfo->Id); + return; + } + + if (st->target_mapId == unitTarget->GetMapId()) + unitTarget->NearTeleportTo(st->target_X, st->target_Y, st->target_Z, st->target_Orientation, unitTarget == m_caster); + else if (unitTarget->GetTypeId() == TYPEID_PLAYER) + ((Player*)unitTarget)->TeleportTo(st->target_mapId, st->target_X, st->target_Y, st->target_Z, st->target_Orientation, unitTarget == m_caster ? TELE_TO_SPELL : 0); + break; + } + case TARGET_EFFECT_SELECT: + { + // m_destN filled, but sometimes for wrong dest and does not have TARGET_FLAG_DEST_LOCATION + + float x = unitTarget->GetPositionX(); + float y = unitTarget->GetPositionY(); + float z = unitTarget->GetPositionZ(); + float orientation = m_caster->GetOrientation(); + + m_caster->NearTeleportTo(x, y, z, orientation, unitTarget == m_caster); + return; + } + case TARGET_BEHIND_VICTIM: + { + Unit* pTarget = NULL; + + // explicit cast data from client or server-side cast + // some spell at client send caster + if (m_targets.getUnitTarget() && m_targets.getUnitTarget() != unitTarget) + pTarget = m_targets.getUnitTarget(); + else if (unitTarget->getVictim()) + pTarget = unitTarget->getVictim(); + else if (unitTarget->GetTypeId() == TYPEID_PLAYER) + pTarget = unitTarget->GetMap()->GetUnit(((Player*)unitTarget)->GetSelectionGuid()); + + // Init dest coordinates + float x = m_targets.m_destX; + float y = m_targets.m_destY; + float z = m_targets.m_destZ; + float orientation = pTarget ? pTarget->GetOrientation() : unitTarget->GetOrientation(); + unitTarget->NearTeleportTo(x, y, z, orientation, unitTarget == m_caster); + return; + } + default: + { + // If not exist data for dest location - return + if (!(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION)) + { + sLog.outError( "Spell::EffectTeleportUnits - unknown EffectImplicitTargetB[%u] = %u for spell ID %u", effect->EffectIndex, effect->EffectImplicitTargetB, m_spellInfo->Id ); + return; + } + // Init dest coordinates + float x = m_targets.m_destX; + float y = m_targets.m_destY; + float z = m_targets.m_destZ; + float orientation = unitTarget->GetOrientation(); + // Teleport + unitTarget->NearTeleportTo(x, y, z, orientation, unitTarget == m_caster); + return; + } + } + + // post effects for TARGET_TABLE_X_Y_Z_COORDINATES + switch (m_spellInfo->Id) + { + // Dimensional Ripper - Everlook + case 23442: + { + int32 r = irand(0, 119); + if (r >= 70) // 7/12 success + { + if (r < 100) // 4/12 evil twin + m_caster->CastSpell(m_caster, 23445, true); + else // 1/12 fire + m_caster->CastSpell(m_caster, 23449, true); + } + return; + } + // Ultrasafe Transporter: Toshley's Station + case 36941: + { + if (roll_chance_i(50)) // 50% success + { + int32 rand_eff = urand(1, 7); + switch (rand_eff) + { + case 1: + // soul split - evil + m_caster->CastSpell(m_caster, 36900, true); + break; + case 2: + // soul split - good + m_caster->CastSpell(m_caster, 36901, true); + break; + case 3: + // Increase the size + m_caster->CastSpell(m_caster, 36895, true); + break; + case 4: + // Decrease the size + m_caster->CastSpell(m_caster, 36893, true); + break; + case 5: + // Transform + { + if (((Player*)m_caster)->GetTeam() == ALLIANCE) + m_caster->CastSpell(m_caster, 36897, true); + else + m_caster->CastSpell(m_caster, 36899, true); + break; + } + case 6: + // chicken + m_caster->CastSpell(m_caster, 36940, true); + break; + case 7: + // evil twin + m_caster->CastSpell(m_caster, 23445, true); + break; + } + } + return; + } + // Dimensional Ripper - Area 52 + case 36890: + { + if (roll_chance_i(50)) // 50% success + { + int32 rand_eff = urand(1, 4); + switch (rand_eff) + { + case 1: + // soul split - evil + m_caster->CastSpell(m_caster, 36900, true); + break; + case 2: + // soul split - good + m_caster->CastSpell(m_caster, 36901, true); + break; + case 3: + // Increase the size + m_caster->CastSpell(m_caster, 36895, true); + break; + case 4: + // Transform + { + if (((Player*)m_caster)->GetTeam() == ALLIANCE) + m_caster->CastSpell(m_caster, 36897, true); + else + m_caster->CastSpell(m_caster, 36899, true); + break; + } + } + } + return; + } + } +} + +void Spell::EffectApplyAura(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + + // ghost spell check, allow apply any auras at player loading in ghost mode (will be cleanup after load) + if ((!unitTarget->isAlive() && !(IsDeathOnlySpell(m_spellInfo) || IsDeathPersistentSpell(m_spellInfo))) && + (unitTarget->GetTypeId() != TYPEID_PLAYER || !((Player*)unitTarget)->GetSession()->PlayerLoading())) + return; + + Unit* caster = GetAffectiveCaster(); + if (!caster) + { + // FIXME: currently we can't have auras applied explicitly by gameobjects + // so for auras from wild gameobjects (no owner) target used + if (m_originalCasterGUID.IsGameObject()) + caster = unitTarget; + else + return; + } + + DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell: Aura is: %u", effect->EffectApplyAuraName); + + Aura* aur = CreateAura(m_spellInfo, SpellEffectIndex(effect->EffectIndex), &m_currentBasePoints[effect->EffectIndex], m_spellAuraHolder, unitTarget, caster, m_CastItem); + m_spellAuraHolder->AddAura(aur, SpellEffectIndex(effect->EffectIndex)); +} + +void Spell::EffectUnlearnSpecialization(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + Player *_player = (Player*)unitTarget; + uint32 spellToUnlearn = effect->EffectTriggerSpell; + + _player->removeSpell(spellToUnlearn); + + if (WorldObject const* caster = GetCastingObject()) + DEBUG_LOG("Spell: %s has unlearned spell %u at %s", _player->GetGuidStr().c_str(), spellToUnlearn, caster->GetGuidStr().c_str()); +} + +void Spell::EffectPowerDrain(SpellEffectEntry const* effect) +{ + if(effect->EffectMiscValue < 0 || effect->EffectMiscValue >= MAX_POWERS) + return; + + Powers drain_power = Powers(effect->EffectMiscValue); + + if (!unitTarget) + return; + if (!unitTarget->isAlive()) + return; + if (unitTarget->getPowerType() != drain_power) + return; + if (damage < 0) + return; + + uint32 curPower = unitTarget->GetPower(drain_power); + + // add spell damage bonus + damage = m_caster->SpellDamageBonusDone(unitTarget, m_spellInfo, uint32(damage), SPELL_DIRECT_DAMAGE); + damage = unitTarget->SpellDamageBonusTaken(m_caster, m_spellInfo, uint32(damage), SPELL_DIRECT_DAMAGE); + + // resilience reduce mana draining effect at spell crit damage reduction (added in 2.4) + uint32 power = damage; + if (drain_power == POWER_MANA) + power -= unitTarget->GetCritDamageReduction(power); + + int32 new_damage; + if (curPower < power) + new_damage = curPower; + else + new_damage = power; + + unitTarget->ModifyPower(drain_power, -new_damage); + + // Don`t restore from self drain + if (drain_power == POWER_MANA && m_caster != unitTarget) + { + float manaMultiplier = effect->EffectMultipleValue; + if(manaMultiplier==0) + manaMultiplier = 1; + + if (Player* modOwner = m_caster->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_MULTIPLE_VALUE, manaMultiplier); + + int32 gain = int32(new_damage * manaMultiplier); + + m_caster->EnergizeBySpell(m_caster, m_spellInfo->Id, gain, POWER_MANA); + } +} + +void Spell::EffectSendEvent(SpellEffectEntry const* effect) +{ + /* + we do not handle a flag dropping or clicking on flag in battleground by sendevent system + TODO: Actually, why not... + */ + DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell ScriptStart %u for spellid %u in EffectSendEvent ", effect->EffectMiscValue, m_spellInfo->Id); + StartEvents_Event(m_caster->GetMap(), effect->EffectMiscValue, m_caster, focusObject, true, m_caster); +} + +void Spell::EffectPowerBurn(SpellEffectEntry const* effect) +{ + if (effect->EffectMiscValue < 0 || effect->EffectMiscValue >= MAX_POWERS) + return; + + Powers powertype = Powers(effect->EffectMiscValue); + + if (!unitTarget) + return; + if (!unitTarget->isAlive()) + return; + if (unitTarget->getPowerType() != powertype) + return; + if (damage < 0) + return; + + // burn x% of target's mana, up to maximum of 2x% of caster's mana (Mana Burn) + if (m_spellInfo->GetManaCostPercentage()) + { + int32 maxdamage = m_caster->GetMaxPower(powertype) * damage * 2 / 100; + damage = unitTarget->GetMaxPower(powertype) * damage / 100; + if (damage > maxdamage) + damage = maxdamage; + } + + int32 curPower = int32(unitTarget->GetPower(powertype)); + + // resilience reduce mana draining effect at spell crit damage reduction (added in 2.4) + int32 power = damage; + if (powertype == POWER_MANA) + power -= unitTarget->GetCritDamageReduction(power); + + int32 new_damage = (curPower < power) ? curPower : power; + + unitTarget->ModifyPower(powertype, -new_damage); + float multiplier = effect->EffectMultipleValue; + + if (Player* modOwner = m_caster->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_MULTIPLE_VALUE, multiplier); + + new_damage = int32(new_damage * multiplier); + m_damage += new_damage; +} + +void Spell::EffectHeal(SpellEffectEntry const* /*effect*/) +{ + if (unitTarget && unitTarget->isAlive() && damage >= 0) + { + // Try to get original caster + Unit* caster = GetAffectiveCaster(); + if (!caster) + return; + + int32 addhealth = damage; + + // Seal of Light proc + if (m_spellInfo->Id == 20167) + { + float ap = caster->GetTotalAttackPowerValue(BASE_ATTACK); + int32 holy = caster->SpellBaseHealingBonusDone(GetSpellSchoolMask(m_spellInfo)); + if (holy < 0) + holy = 0; + addhealth += int32(ap * 0.15) + int32(holy * 15 / 100); + } + // Vessel of the Naaru (Vial of the Sunwell trinket) + else if (m_spellInfo->Id == 45064) + { + // Amount of heal - depends from stacked Holy Energy + int damageAmount = 0; + Unit::AuraList const& mDummyAuras = m_caster->GetAurasByType(SPELL_AURA_DUMMY); + for (Unit::AuraList::const_iterator i = mDummyAuras.begin(); i != mDummyAuras.end(); ++i) + if ((*i)->GetId() == 45062) + damageAmount += (*i)->GetModifier()->m_amount; + if (damageAmount) + m_caster->RemoveAurasDueToSpell(45062); + + addhealth += damageAmount; + } + // Death Pact (percent heal) + else if (m_spellInfo->Id == 48743) + addhealth = addhealth * unitTarget->GetMaxHealth() / 100; + // Swiftmend - consumes Regrowth or Rejuvenation + else if (m_spellInfo->GetTargetAuraState() == AURA_STATE_SWIFTMEND && unitTarget->HasAuraState(AURA_STATE_SWIFTMEND)) + { + Unit::AuraList const& RejorRegr = unitTarget->GetAurasByType(SPELL_AURA_PERIODIC_HEAL); + // find most short by duration + Aura* targetAura = NULL; + for (Unit::AuraList::const_iterator i = RejorRegr.begin(); i != RejorRegr.end(); ++i) + { + SpellClassOptionsEntry const* smClassOptions = (*i)->GetSpellProto()->GetSpellClassOptions(); + if (smClassOptions && smClassOptions->SpellFamilyName == SPELLFAMILY_DRUID && + // Regrowth or Rejuvenation 0x40 | 0x10 + (smClassOptions->SpellFamilyFlags & UI64LIT(0x0000000000000050))) + { + if (!targetAura || (*i)->GetAuraDuration() < targetAura->GetAuraDuration()) + targetAura = *i; + } + } + + if (!targetAura) + { + sLog.outError("Target (GUID: %u TypeId: %u) has aurastate AURA_STATE_SWIFTMEND but no matching aura.", unitTarget->GetGUIDLow(), unitTarget->GetTypeId()); + return; + } + int idx = 0; + SpellEffectEntry const* targetSpellEffect = NULL; + while(idx < 3) + { + targetSpellEffect = targetAura->GetSpellProto()->GetSpellEffect(SpellEffectIndex(idx)); + if(targetSpellEffect && targetSpellEffect->EffectApplyAuraName == SPELL_AURA_PERIODIC_HEAL) + break; + ++idx; + } + + int32 tickheal = targetAura->GetModifier()->m_amount; + int32 tickcount = GetSpellDuration(targetAura->GetSpellProto()) / (targetSpellEffect ? targetSpellEffect->EffectAmplitude : 1) - 1; + + // Glyph of Swiftmend + if (!caster->HasAura(54824)) + unitTarget->RemoveAurasDueToSpell(targetAura->GetId()); + + addhealth += tickheal * tickcount; + } + // Runic Healing Injector & Healing Potion Injector effect increase for engineers + else if ((m_spellInfo->Id == 67486 || m_spellInfo->Id == 67489) && unitTarget->GetTypeId() == TYPEID_PLAYER) + { + Player* player = (Player*)unitTarget; + if (player->HasSkill(SKILL_ENGINEERING)) + addhealth += int32(addhealth * 0.25); + } + + // Chain Healing + SpellClassOptionsEntry const* chClassOptions = m_spellInfo->GetSpellClassOptions(); + if (chClassOptions && chClassOptions->SpellFamilyName == SPELLFAMILY_SHAMAN && chClassOptions->SpellFamilyFlags & UI64LIT(0x0000000000000100)) + { + if (unitTarget == m_targets.getUnitTarget()) + { + // check for Riptide + Aura* riptide = unitTarget->GetAura(SPELL_AURA_PERIODIC_HEAL, SPELLFAMILY_SHAMAN, UI64LIT(0x0), 0x00000010, caster->GetObjectGuid()); + if (riptide) + { + addhealth += addhealth / 4; + unitTarget->RemoveAurasDueToSpell(riptide->GetId()); + } + } + } + + addhealth = caster->SpellHealingBonusDone(unitTarget, m_spellInfo, addhealth, HEAL); + addhealth = unitTarget->SpellHealingBonusTaken(caster, m_spellInfo, addhealth, HEAL); + + m_healing += addhealth; + } +} + +void Spell::EffectHealPct(SpellEffectEntry const* /*effect*/) +{ + if (unitTarget && unitTarget->isAlive() && damage >= 0) + { + // Try to get original caster + Unit* caster = GetAffectiveCaster(); + if (!caster) + return; + + uint32 addhealth = unitTarget->GetMaxHealth() * damage / 100; + + addhealth = caster->SpellHealingBonusDone(unitTarget, m_spellInfo, addhealth, HEAL); + addhealth = unitTarget->SpellHealingBonusTaken(caster, m_spellInfo, addhealth, HEAL); + + uint32 absorb = 0; + unitTarget->CalculateHealAbsorb(addhealth, &absorb); + + int32 gain = caster->DealHeal(unitTarget, addhealth - absorb, m_spellInfo, false, absorb); + unitTarget->getHostileRefManager().threatAssist(caster, float(gain) * 0.5f * sSpellMgr.GetSpellThreatMultiplier(m_spellInfo), m_spellInfo); + } +} + +void Spell::EffectHealMechanical(SpellEffectEntry const* /*effect*/) +{ + // Mechanic creature type should be correctly checked by targetCreatureType field + if (unitTarget && unitTarget->isAlive() && damage >= 0) + { + // Try to get original caster + Unit* caster = GetAffectiveCaster(); + if (!caster) + return; + + uint32 addhealth = caster->SpellHealingBonusDone(unitTarget, m_spellInfo, damage, HEAL); + addhealth = unitTarget->SpellHealingBonusTaken(caster, m_spellInfo, addhealth, HEAL); + + uint32 absorb = 0; + unitTarget->CalculateHealAbsorb(addhealth, &absorb); + + caster->DealHeal(unitTarget, addhealth - absorb, m_spellInfo, false, absorb); + } +} + +void Spell::EffectHealthLeech(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + if (!unitTarget->isAlive()) + return; + + if (damage < 0) + return; + + DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "HealthLeech :%i", damage); + + uint32 curHealth = unitTarget->GetHealth(); + damage = m_caster->SpellNonMeleeDamageLog(unitTarget, m_spellInfo->Id, damage); + if ((int32)curHealth < damage) + damage = curHealth; + + float multiplier = effect->EffectMultipleValue; + + if (Player* modOwner = m_caster->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_MULTIPLE_VALUE, multiplier); + + int32 heal = int32(damage * multiplier); + if (m_caster->isAlive()) + { + heal = m_caster->SpellHealingBonusTaken(m_caster, m_spellInfo, heal, HEAL); + + uint32 absorb = 0; + m_caster->CalculateHealAbsorb(heal, &absorb); + + m_caster->DealHeal(m_caster, heal - absorb, m_spellInfo, false, absorb); + } +} + +void Spell::DoCreateItem(SpellEffectEntry const* effect, uint32 itemtype) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + Player* player = (Player*)unitTarget; + + uint32 newitemid = itemtype; + ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(newitemid); + if (!pProto) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL); + return; + } + + // bg reward have some special in code work + bool bg_mark = false; + switch (m_spellInfo->Id) + { + case SPELL_WG_MARK_VICTORY: + case SPELL_WG_MARK_DEFEAT: + bg_mark = true; + break; + default: + break; + } + + uint32 num_to_add = damage; + + if (num_to_add < 1) + num_to_add = 1; + if (num_to_add > pProto->GetMaxStackSize()) + num_to_add = pProto->GetMaxStackSize(); + + // init items_count to 1, since 1 item will be created regardless of specialization + int items_count = 1; + // the chance to create additional items + float additionalCreateChance = 0.0f; + // the maximum number of created additional items + uint8 additionalMaxNum = 0; + // get the chance and maximum number for creating extra items + if (canCreateExtraItems(player, m_spellInfo->Id, additionalCreateChance, additionalMaxNum)) + { + // roll with this chance till we roll not to create or we create the max num + while (roll_chance_f(additionalCreateChance) && items_count <= additionalMaxNum) + ++items_count; + } + + // really will be created more items + num_to_add *= items_count; + + // can the player store the new item? + ItemPosCountVec dest; + uint32 no_space = 0; + InventoryResult msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, newitemid, num_to_add, &no_space); + if (msg != EQUIP_ERR_OK) + { + // convert to possible store amount + if (msg == EQUIP_ERR_INVENTORY_FULL || msg == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) + num_to_add -= no_space; + else + { + // ignore mana gem case (next effect will recharge existing example) + if (effect->EffectIndex == EFFECT_INDEX_0 && m_spellInfo->GetSpellEffectIdByIndex(EFFECT_INDEX_1) == SPELL_EFFECT_DUMMY ) + return; + + // if not created by another reason from full inventory or unique items amount limitation + player->SendEquipError(msg, NULL, NULL, newitemid); + return; + } + } + + if (num_to_add) + { + // create the new item and store it + Item* pItem = player->StoreNewItem(dest, newitemid, true, Item::GenerateItemRandomPropertyId(newitemid)); + + // was it successful? return error if not + if (!pItem) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL); + return; + } + + // set the "Crafted by ..." property of the item + if (pItem->GetProto()->Class != ITEM_CLASS_CONSUMABLE && pItem->GetProto()->Class != ITEM_CLASS_QUEST) + pItem->SetGuidValue(ITEM_FIELD_CREATOR, player->GetObjectGuid()); + + // send info to the client + player->SendNewItem(pItem, num_to_add, true, !bg_mark); + + // we succeeded in creating at least one item, so a levelup is possible + if (!bg_mark) + player->UpdateCraftSkill(m_spellInfo->Id); + } +} + +void Spell::EffectCreateItem(SpellEffectEntry const* effect) +{ + DoCreateItem(effect, effect->EffectItemType); +} + +void Spell::EffectCreateItem2(SpellEffectEntry const* effect) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + Player* player = (Player*)m_caster; + + // explicit item (possible fake) + uint32 item_id = effect->EffectItemType; + + if (item_id) + DoCreateItem(effect, item_id); + + // not explicit loot (with fake item drop if need) + if (IsLootCraftingSpell(m_spellInfo)) + { + if (item_id) + { + if (!player->HasItemCount(item_id, 1)) + return; + + // remove reagent + uint32 count = 1; + player->DestroyItemCount(item_id, count, true); + } + + // create some random items + player->AutoStoreLoot(NULL, m_spellInfo->Id, LootTemplates_Spell); + } +} + +void Spell::EffectCreateRandomItem(SpellEffectEntry const* /*effect*/) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + Player* player = (Player*)m_caster; + + // create some random items + player->AutoStoreLoot(NULL, m_spellInfo->Id, LootTemplates_Spell); +} + +void Spell::EffectPersistentAA(SpellEffectEntry const* effect) +{ + Unit* pCaster = GetAffectiveCaster(); + // FIXME: in case wild GO will used wrong affective caster (target in fact) as dynobject owner + if (!pCaster) + pCaster = m_caster; + + float radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(effect->GetRadiusIndex())); + + if (Player* modOwner = pCaster->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_RADIUS, radius); + + DynamicObject* dynObj = new DynamicObject; + if (!dynObj->Create(pCaster->GetMap()->GenerateLocalLowGuid(HIGHGUID_DYNAMICOBJECT), pCaster, m_spellInfo->Id, SpellEffectIndex(effect->EffectIndex), m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ, m_duration, radius, DYNAMIC_OBJECT_AREA_SPELL)) + { + delete dynObj; + return; + } + + pCaster->AddDynObject(dynObj); + pCaster->GetMap()->Add(dynObj); +} + +void Spell::EffectEnergize(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + if (!unitTarget->isAlive()) + return; + + if(effect->EffectMiscValue < 0 || effect->EffectMiscValue >= MAX_POWERS) + return; + + Powers power = Powers(effect->EffectMiscValue); + + // Some level depends spells + int level_multiplier = 0; + int level_diff = 0; + switch (m_spellInfo->Id) + { + case 9512: // Restore Energy + level_diff = m_caster->getLevel() - 40; + level_multiplier = 2; + break; + case 24571: // Blood Fury + level_diff = m_caster->getLevel() - 60; + level_multiplier = 10; + break; + case 24532: // Burst of Energy + level_diff = m_caster->getLevel() - 60; + level_multiplier = 4; + break; + case 31930: // Judgements of the Wise + case 48542: // Revitalize (mana restore case) + case 63375: // Improved Stormstrike + case 68082: // Glyph of Seal of Command + damage = damage * unitTarget->GetCreateMana() / 100; + break; + case 67487: // Mana Potion Injector + case 67490: // Runic Mana Injector + { + if (unitTarget->GetTypeId() == TYPEID_PLAYER) + { + Player* player = (Player*)unitTarget; + if (player->HasSkill(SKILL_ENGINEERING)) + damage += int32(damage * 0.25); + } + break; + } + default: + break; + } + + if (level_diff > 0) + damage -= level_multiplier * level_diff; + + if (damage < 0) + return; + + if (unitTarget->GetMaxPower(power) == 0) + return; + + m_caster->EnergizeBySpell(unitTarget, m_spellInfo->Id, damage, power); + + // Mad Alchemist's Potion + if (m_spellInfo->Id == 45051) + { + // find elixirs on target + uint32 elixir_mask = 0; + Unit::SpellAuraHolderMap& Auras = unitTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::iterator itr = Auras.begin(); itr != Auras.end(); ++itr) + { + uint32 spell_id = itr->second->GetId(); + if (uint32 mask = sSpellMgr.GetSpellElixirMask(spell_id)) + elixir_mask |= mask; + } + + // get available elixir mask any not active type from battle/guardian (and flask if no any) + elixir_mask = (elixir_mask & ELIXIR_FLASK_MASK) ^ ELIXIR_FLASK_MASK; + + // get all available elixirs by mask and spell level + std::vector elixirs; + SpellElixirMap const& m_spellElixirs = sSpellMgr.GetSpellElixirMap(); + for (SpellElixirMap::const_iterator itr = m_spellElixirs.begin(); itr != m_spellElixirs.end(); ++itr) + { + if (itr->second & elixir_mask) + { + if (itr->second & (ELIXIR_UNSTABLE_MASK | ELIXIR_SHATTRATH_MASK)) + continue; + + SpellEntry const *spellInfo = sSpellStore.LookupEntry(itr->first); + if (spellInfo && (spellInfo->GetSpellLevel() < m_spellInfo->GetSpellLevel() || spellInfo->GetSpellLevel() > unitTarget->getLevel())) + continue; + + elixirs.push_back(itr->first); + } + } + + if (!elixirs.empty()) + { + // cast random elixir on target + uint32 rand_spell = urand(0, elixirs.size() - 1); + m_caster->CastSpell(unitTarget, elixirs[rand_spell], true, m_CastItem); + } + } +} + +void Spell::EffectEnergisePct(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + if (!unitTarget->isAlive()) + return; + + if (effect->EffectMiscValue < 0 || effect->EffectMiscValue >= MAX_POWERS) + return; + + Powers power = Powers(effect->EffectMiscValue); + + uint32 maxPower = unitTarget->GetMaxPower(power); + if (maxPower == 0) + return; + + uint32 gain = damage * maxPower / 100; + m_caster->EnergizeBySpell(unitTarget, m_spellInfo->Id, gain, power); +} + +void Spell::SendLoot(ObjectGuid guid, LootType loottype, LockType lockType) +{ + if (gameObjTarget) + { + switch (gameObjTarget->GetGoType()) + { + case GAMEOBJECT_TYPE_DOOR: + case GAMEOBJECT_TYPE_BUTTON: + case GAMEOBJECT_TYPE_QUESTGIVER: + case GAMEOBJECT_TYPE_SPELL_FOCUS: + case GAMEOBJECT_TYPE_GOOBER: + gameObjTarget->Use(m_caster); + return; + + case GAMEOBJECT_TYPE_CHEST: + gameObjTarget->Use(m_caster); + // Don't return, let loots been taken + break; + + case GAMEOBJECT_TYPE_TRAP: + if (lockType == LOCKTYPE_DISARM_TRAP) + { + gameObjTarget->SetLootState(GO_JUST_DEACTIVATED); + return; + } + sLog.outError("Spell::SendLoot unhandled locktype %u for GameObject trap (entry %u) for spell %u.", lockType, gameObjTarget->GetEntry(), m_spellInfo->Id); + return; + default: + sLog.outError("Spell::SendLoot unhandled GameObject type %u (entry %u) for spell %u.", gameObjTarget->GetGoType(), gameObjTarget->GetEntry(), m_spellInfo->Id); + return; + } + } + + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // Send loot + ((Player*)m_caster)->SendLoot(guid, loottype); +} + +void Spell::EffectOpenLock(SpellEffectEntry const* effect) +{ + if (!m_caster || m_caster->GetTypeId() != TYPEID_PLAYER) + { + DEBUG_LOG("WORLD: Open Lock - No Player Caster!"); + return; + } + + Player* player = (Player*)m_caster; + + uint32 lockId = 0; + ObjectGuid guid; + + // Get lockId + if (gameObjTarget) + { + GameObjectInfo const* goInfo = gameObjTarget->GetGOInfo(); + // Arathi Basin banner opening ! + if ((goInfo->type == GAMEOBJECT_TYPE_BUTTON && goInfo->button.noDamageImmune) || + (goInfo->type == GAMEOBJECT_TYPE_GOOBER && goInfo->goober.losOK)) + { + // CanUseBattleGroundObject() already called in CheckCast() + // in battleground check + if (BattleGround* bg = player->GetBattleGround()) + { + // check if it's correct bg + if (bg->GetTypeID() == BATTLEGROUND_AB || bg->GetTypeID() == BATTLEGROUND_AV) + bg->EventPlayerClickedOnFlag(player, gameObjTarget); + return; + } + } + else if (goInfo->type == GAMEOBJECT_TYPE_FLAGSTAND) + { + // CanUseBattleGroundObject() already called in CheckCast() + // in battleground check + if (BattleGround* bg = player->GetBattleGround()) + { + if (bg->GetTypeID() == BATTLEGROUND_EY) + bg->EventPlayerClickedOnFlag(player, gameObjTarget); + return; + } + } + lockId = goInfo->GetLockId(); + guid = gameObjTarget->GetObjectGuid(); + } + else if (itemTarget) + { + lockId = itemTarget->GetProto()->LockID; + guid = itemTarget->GetObjectGuid(); + } + else + { + DEBUG_LOG("WORLD: Open Lock - No GameObject/Item Target!"); + return; + } + + SkillType skillId = SKILL_NONE; + int32 reqSkillValue = 0; + int32 skillValue; + + SpellCastResult res = CanOpenLock(SpellEffectIndex(effect->EffectIndex), lockId, skillId, reqSkillValue, skillValue); + if (res != SPELL_CAST_OK) + { + SendCastResult(res); + return; + } + + // mark item as unlocked + if (itemTarget) + itemTarget->SetFlag(ITEM_FIELD_FLAGS, ITEM_DYNFLAG_UNLOCKED); + + SendLoot(guid, LOOT_SKINNING, LockType(effect->EffectMiscValue)); + + // not allow use skill grow at item base open + if (!m_CastItem && skillId != SKILL_NONE) + { + // update skill if really known + if (uint32 pureSkillValue = player->GetPureSkillValue(skillId)) + { + if (gameObjTarget) + { + // Allow one skill-up until respawned + if (!gameObjTarget->IsInSkillupList(player) && + player->UpdateGatherSkill(skillId, pureSkillValue, reqSkillValue)) + gameObjTarget->AddToSkillupList(player); + } + else if (itemTarget) + { + // Do one skill-up + player->UpdateGatherSkill(skillId, pureSkillValue, reqSkillValue); + } + } + } +} + +void Spell::EffectSummonChangeItem(SpellEffectEntry const* effect) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Player* player = (Player*)m_caster; + + // applied only to using item + if (!m_CastItem) + return; + + // ... only to item in own inventory/bank/equip_slot + if (m_CastItem->GetOwnerGuid() != player->GetObjectGuid()) + return; + + uint32 newitemid = effect->EffectItemType; + if (!newitemid) + return; + + Item* oldItem = m_CastItem; + + // prevent crash at access and unexpected charges counting with item update queue corrupt + ClearCastItem(); + + player->ConvertItem(oldItem, newitemid); +} + +void Spell::EffectProficiency(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + Player *p_target = (Player*)unitTarget; + + SpellEquippedItemsEntry const* eqItems = m_spellInfo->GetSpellEquippedItems(); + + if (!eqItems) + return; + + if (eqItems->EquippedItemClass == ITEM_CLASS_WEAPON && !(p_target->GetWeaponProficiency() & eqItems->EquippedItemSubClassMask)) + { + p_target->AddWeaponProficiency(eqItems->EquippedItemSubClassMask); + p_target->SendProficiency(ITEM_CLASS_WEAPON, p_target->GetWeaponProficiency()); + } + if (eqItems->EquippedItemClass == ITEM_CLASS_ARMOR && !(p_target->GetArmorProficiency() & eqItems->EquippedItemSubClassMask)) + { + p_target->AddArmorProficiency(eqItems->EquippedItemSubClassMask); + p_target->SendProficiency(ITEM_CLASS_ARMOR, p_target->GetArmorProficiency()); + } +} + +void Spell::EffectApplyAreaAura(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + if (!unitTarget->isAlive()) + return; + + AreaAura* Aur = new AreaAura(m_spellInfo, SpellEffectIndex(effect->EffectIndex), &m_currentBasePoints[effect->EffectIndex], m_spellAuraHolder, unitTarget, m_caster, m_CastItem); + m_spellAuraHolder->AddAura(Aur, SpellEffectIndex(effect->EffectIndex)); +} + +void Spell::EffectSummonType(SpellEffectEntry const* effect) +{ + uint32 prop_id = effect->EffectMiscValueB; + SummonPropertiesEntry const *summon_prop = sSummonPropertiesStore.LookupEntry(prop_id); + if(!summon_prop) + { + sLog.outError("EffectSummonType: Unhandled summon type %u", prop_id); + return; + } + + // Pet's are atm handled differently + if (summon_prop->Group == SUMMON_PROP_GROUP_PETS && prop_id != 1562) + { + DoSummonPet(effect); + return; + } + + // Expected Amount: TODO - there are quite some exceptions (like totems, engineering dragonlings..) + uint32 amount = damage > 0 ? damage : 1; + + // basepoints of SUMMON_PROP_GROUP_VEHICLE is often a spellId, set amount to 1 + if (summon_prop->Group == SUMMON_PROP_GROUP_VEHICLE) + amount = 1; + + // Get casting object + WorldObject* realCaster = GetCastingObject(); + if (!realCaster) + { + sLog.outError("EffectSummonType: No Casting Object found for spell %u, (caster = %s)", m_spellInfo->Id, m_caster->GetGuidStr().c_str()); + return; + } + + Unit* responsibleCaster = m_originalCaster; + if (realCaster->GetTypeId() == TYPEID_GAMEOBJECT) + responsibleCaster = ((GameObject*)realCaster)->GetOwner(); + + // Expected Level (Totem, Pet and Critter may not use this) + uint32 level = responsibleCaster ? responsibleCaster->getLevel() : m_caster->getLevel(); + // level of creature summoned using engineering item based at engineering skill level + if (m_caster->GetTypeId() == TYPEID_PLAYER && m_CastItem) + { + ItemPrototype const* proto = m_CastItem->GetProto(); + if (proto && proto->RequiredSkill == SKILL_ENGINEERING) + if (uint16 engineeringSkill = ((Player*)m_caster)->GetSkillValue(SKILL_ENGINEERING)) + { + level = engineeringSkill / 5; + amount = 1; // TODO HACK (needs a neat way of doing) + } + } + + CreatureSummonPositions summonPositions; + summonPositions.resize(amount, CreaturePosition()); + + // Set middle position + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) + m_targets.getDestination(summonPositions[0].x, summonPositions[0].y, summonPositions[0].z); + else + { + realCaster->GetPosition(summonPositions[0].x, summonPositions[0].y, summonPositions[0].z); + + // TODO - Is this really an error? + sLog.outDebug("Spell Effect EFFECT_SUMMON (%u) - summon without destination (spell id %u, effIndex %u)", effect->Effect, m_spellInfo->Id, SpellEffectIndex(effect->EffectIndex)); + } + + // Set summon positions + float radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(effect->GetRadiusIndex())); + CreatureSummonPositions::iterator itr = summonPositions.begin(); + for (++itr; itr != summonPositions.end(); ++itr) // In case of multiple summons around position for not-fist positions + { + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION || radius > 1.0f) + { + realCaster->GetRandomPoint(summonPositions[0].x, summonPositions[0].y, summonPositions[0].z, radius, itr->x, itr->y, itr->z); + if (realCaster->GetMap()->GetHitPosition(summonPositions[0].x, summonPositions[0].y, summonPositions[0].z, itr->x, itr->y, itr->z, m_caster->GetPhaseMask(), -0.5f)) + realCaster->UpdateAllowedPositionZ(itr->x, itr->y, itr->z); + } + else // Get a point near the caster + { + realCaster->GetRandomPoint(summonPositions[0].x, summonPositions[0].y, summonPositions[0].z, radius, itr->x, itr->y, itr->z); + if (realCaster->GetMap()->GetHitPosition(summonPositions[0].x, summonPositions[0].y, summonPositions[0].z, itr->x, itr->y, itr->z, m_caster->GetPhaseMask(), -0.5f)) + realCaster->UpdateAllowedPositionZ(itr->x, itr->y, itr->z); + } + } + + bool summonResult = false; + switch (summon_prop->Group) + { + // faction handled later on, or loaded from template + case SUMMON_PROP_GROUP_WILD: + case SUMMON_PROP_GROUP_FRIENDLY: + { + switch (summon_prop->Title) // better from known way sorting summons by AI types + { + case UNITNAME_SUMMON_TITLE_NONE: + { + // those are classical totems - effectbasepoints is their hp and not summon ammount! + // 121: 23035, battlestands + // 647: 52893, Anti-Magic Zone (npc used) + if (prop_id == 121 || prop_id == 647) + summonResult = DoSummonTotem(effect); + else + summonResult = DoSummonWild(summonPositions, summon_prop, effect, level); + break; + } + case UNITNAME_SUMMON_TITLE_PET: + case UNITNAME_SUMMON_TITLE_MINION: + case UNITNAME_SUMMON_TITLE_RUNEBLADE: + summonResult = DoSummonGuardian(summonPositions, summon_prop, effect, level); + break; + case UNITNAME_SUMMON_TITLE_GUARDIAN: + { + if (prop_id == 61) // mixed guardians, totems, statues + { + // * Stone Statue, etc -- fits much better totem AI + if (m_spellInfo->SpellIconID == 2056) + summonResult = DoSummonTotem(effect); + else + { + // possible sort totems/guardians only by summon creature type + CreatureInfo const* cInfo = ObjectMgr::GetCreatureTemplate(effect->EffectMiscValue); + + if (!cInfo) + return; + + // FIXME: not all totems and similar cases selected by this check... + if (cInfo->type == CREATURE_TYPE_TOTEM) + summonResult = DoSummonTotem(effect); + else + summonResult = DoSummonGuardian(summonPositions, summon_prop, effect, level); + } + } + else + summonResult = DoSummonGuardian(summonPositions, summon_prop, effect, level); + break; + } + case UNITNAME_SUMMON_TITLE_CONSTRUCT: + { + if (prop_id == 2913) // Scrapbot + summonResult = DoSummonWild(summonPositions, summon_prop, effect, level); + else + summonResult = DoSummonGuardian(summonPositions, summon_prop, effect, level); + break; + } + case UNITNAME_SUMMON_TITLE_TOTEM: + summonResult = DoSummonTotem(effect, summon_prop->Slot); + break; + case UNITNAME_SUMMON_TITLE_COMPANION: + // slot 6 set for critters that can help to player in fighting + if (summon_prop->Slot == 6) + summonResult = DoSummonGuardian(summonPositions, summon_prop, effect, level); + else + summonResult = DoSummonCritter(summonPositions, summon_prop, effect, level); + break; + case UNITNAME_SUMMON_TITLE_OPPONENT: + case UNITNAME_SUMMON_TITLE_VEHICLE: + case UNITNAME_SUMMON_TITLE_MOUNT: + case UNITNAME_SUMMON_TITLE_LIGHTWELL: + case UNITNAME_SUMMON_TITLE_BUTLER: + summonResult = DoSummonWild(summonPositions, summon_prop, effect, level); + break; + default: + sLog.outError("EffectSummonType: Unhandled summon title %u", summon_prop->Title); + break; + } + break; + } + case SUMMON_PROP_GROUP_PETS: + { + // FIXME : multiple summons - not yet supported as pet + // 1562 - force of nature - sid 33831 + // 1161 - feral spirit - sid 51533 + if (prop_id == 1562) // 3 uncontrolable instead of one controllable :/ + summonResult = DoSummonGuardian(summonPositions, summon_prop, effect, level); + break; + } + case SUMMON_PROP_GROUP_CONTROLLABLE: + { + // TODO: Fix spell 46619 + if (m_spellInfo->Id != 46619) + summonResult = DoSummonPossessed(summonPositions, summon_prop, effect, level); + break; + } + case SUMMON_PROP_GROUP_VEHICLE: + { + summonResult = DoSummonVehicle(summonPositions, summon_prop, effect, level); + break; + } + default: + sLog.outError("EffectSummonType: Unhandled summon group type %u", summon_prop->Group); + break; + } + + if (!summonResult) + return; // No further handling required + + for (CreatureSummonPositions::iterator itr = summonPositions.begin(); itr != summonPositions.end(); ++itr) + { + MANGOS_ASSERT(itr->creature || itr != summonPositions.begin()); + if (!itr->creature) + { + sLog.outError("EffectSummonType: Expected to have %u NPCs summoned, but some failed (Spell id %u)", amount, m_spellInfo->Id); + continue; + } + + if (summon_prop->FactionId) + itr->creature->setFaction(summon_prop->FactionId); + // Else set faction to summoner's faction for pet-like summoned + else if ((summon_prop->Flags & SUMMON_PROP_FLAG_INHERIT_FACTION) || !itr->creature->IsTemporarySummon()) + itr->creature->setFaction(responsibleCaster ? responsibleCaster->getFaction() : m_caster->getFaction()); + + if (!itr->creature->IsTemporarySummon()) + { + itr->creature->AIM_Initialize(); + + m_caster->GetMap()->Add(itr->creature); + + // Notify Summoner + if (m_caster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_caster)->AI()) + ((Creature*)m_caster)->AI()->JustSummoned(itr->creature); + if (m_originalCaster && m_originalCaster != m_caster && m_originalCaster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_originalCaster)->AI()) + ((Creature*)m_originalCaster)->AI()->JustSummoned(itr->creature); + } + } +} + +bool Spell::DoSummonWild(CreatureSummonPositions& list, SummonPropertiesEntry const* prop, SpellEffectEntry const* effect, uint32 level) +{ + MANGOS_ASSERT(!list.empty() && prop); + + uint32 creature_entry = effect->EffectMiscValue; + CreatureInfo const* cInfo = ObjectMgr::GetCreatureTemplate(creature_entry); + if (!cInfo) + { + sLog.outErrorDb("Spell::DoSummonWild: creature entry %u not found for spell %u.", creature_entry, m_spellInfo->Id); + return false; + } + + TempSummonType summonType = (m_duration == 0) ? TEMPSUMMON_DEAD_DESPAWN : TEMPSUMMON_TIMED_OOC_OR_DEAD_DESPAWN; + + for (CreatureSummonPositions::iterator itr = list.begin(); itr != list.end(); ++itr) + if (Creature* summon = m_caster->SummonCreature(creature_entry, itr->x, itr->y, itr->z, m_caster->GetOrientation(), summonType, m_duration)) + { + itr->creature = summon; + + summon->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->Id); + + // UNIT_FIELD_CREATEDBY are not set for these kind of spells. + // Does exceptions exist? If so, what are they? + // summon->SetCreatorGuid(m_caster->GetObjectGuid()); + + // Notify original caster if not done already + if (m_originalCaster && m_originalCaster != m_caster && m_originalCaster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_originalCaster)->AI()) + ((Creature*)m_originalCaster)->AI()->JustSummoned(summon); + } + else + return false; + + return true; +} + +bool Spell::DoSummonCritter(CreatureSummonPositions& list, SummonPropertiesEntry const* prop, SpellEffectEntry const* effect, uint32 /*level*/) +{ + MANGOS_ASSERT(!list.empty() && prop); + + // ATM only first position is supported for summoning + uint32 pet_entry = effect->EffectMiscValue; + CreatureInfo const* cInfo = ObjectMgr::GetCreatureTemplate(pet_entry); + if (!cInfo) + { + sLog.outErrorDb("Spell::DoSummonCritter: creature entry %u not found for spell %u.", pet_entry, m_spellInfo->Id); + return false; + } + + Pet* old_critter = m_caster->GetMiniPet(); + + // for same pet just despawn (player unsummon command) + if (m_caster->GetTypeId() == TYPEID_PLAYER && old_critter && old_critter->GetEntry() == pet_entry) + { + m_caster->RemoveMiniPet(); + return false; + } + + // despawn old pet before summon new + if (old_critter) + m_caster->RemoveMiniPet(); + + // for (CreatureSummonPositions::iterator itr = list.begin(); itr != list.end(); ++itr) + CreatureCreatePos pos(m_caster->GetMap(), list[0].x, list[0].y, list[0].z, m_caster->GetOrientation(), m_caster->GetPhaseMask()); + + // summon new pet + Pet* critter = new Pet(MINI_PET); + + uint32 pet_number = sObjectMgr.GeneratePetNumber(); + if (!critter->Create(m_caster->GetMap()->GenerateLocalLowGuid(HIGHGUID_PET), pos, cInfo, pet_number)) + { + sLog.outError("Spell::EffectSummonCritter, spellid %u: no such creature entry %u", m_spellInfo->Id, pet_entry); + delete critter; + return false; + } + + // itr! + list[0].creature = critter; + + critter->SetRespawnCoord(pos); + + // critter->SetName(""); // generated by client + critter->SetOwnerGuid(m_caster->GetObjectGuid()); + critter->SetCreatorGuid(m_caster->GetObjectGuid()); + + critter->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->Id); + + critter->InitPetCreateSpells(); // e.g. disgusting oozeling has a create spell as critter... + // critter->InitLevelupSpellsForLevel(); // none? + critter->SelectLevel(critter->GetCreatureInfo()); // some summoned creaters have different from 1 DB data for level/hp + critter->SetUInt32Value(UNIT_NPC_FLAGS, critter->GetCreatureInfo()->npcflag); + // some mini-pets have quests + // set timer for unsummon + if (m_duration > 0) + critter->SetDuration(m_duration); + + m_caster->SetMiniPet(critter); + + return true; +} + +bool Spell::DoSummonGuardian(CreatureSummonPositions& list, SummonPropertiesEntry const* prop, SpellEffectEntry const* effect, uint32 level) +{ + MANGOS_ASSERT(!list.empty() && prop); + + uint32 pet_entry = effect->EffectMiscValue; + CreatureInfo const* cInfo = ObjectMgr::GetCreatureTemplate(pet_entry); + if (!cInfo) + { + sLog.outErrorDb("Spell::DoSummonGuardian: creature entry %u not found for spell %u.", pet_entry, m_spellInfo->Id); + return false; + } + + PetType petType = prop->Title == UNITNAME_SUMMON_TITLE_COMPANION ? PROTECTOR_PET : GUARDIAN_PET; + + // second direct cast unsummon guardian(s) (guardians without like functionality have cooldown > spawn time) + if (!m_IsTriggeredSpell && m_caster->GetTypeId() == TYPEID_PLAYER) + { + bool found = false; + // including protector + while (Pet* old_summon = m_caster->FindGuardianWithEntry(pet_entry)) + { + old_summon->Unsummon(PET_SAVE_AS_DELETED, m_caster); + found = true; + } + + if (found) + return false; + } + + // protectors allowed only in single amount + if (petType == PROTECTOR_PET) + if (Pet* old_protector = m_caster->GetProtectorPet()) + old_protector->Unsummon(PET_SAVE_AS_DELETED, m_caster); + + // in another case summon new + for (CreatureSummonPositions::iterator itr = list.begin(); itr != list.end(); ++itr) + { + Pet* spawnCreature = new Pet(petType); + + CreatureCreatePos pos(m_caster->GetMap(), itr->x, itr->y, itr->z, -m_caster->GetOrientation(), m_caster->GetPhaseMask()); + + uint32 pet_number = sObjectMgr.GeneratePetNumber(); + if (!spawnCreature->Create(m_caster->GetMap()->GenerateLocalLowGuid(HIGHGUID_PET), pos, cInfo, pet_number)) + { + sLog.outError("Spell::DoSummonGuardian: can't create creature entry %u for spell %u.", pet_entry, m_spellInfo->Id); + delete spawnCreature; + return false; + } + + itr->creature = spawnCreature; + + spawnCreature->SetRespawnCoord(pos); + + if (m_duration > 0) + spawnCreature->SetDuration(m_duration); + + // spawnCreature->SetName(""); // generated by client + spawnCreature->SetOwnerGuid(m_caster->GetObjectGuid()); + spawnCreature->setPowerType(POWER_MANA); + spawnCreature->SetUInt32Value(UNIT_NPC_FLAGS, spawnCreature->GetCreatureInfo()->npcflag); + + spawnCreature->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, 0); + spawnCreature->SetCreatorGuid(m_caster->GetObjectGuid()); + spawnCreature->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->Id); + + spawnCreature->InitStatsForLevel(level, m_caster); + spawnCreature->GetCharmInfo()->SetPetNumber(pet_number, false); + + m_caster->AddGuardian(spawnCreature); + } + + return true; +} + +bool Spell::DoSummonTotem(SpellEffectEntry const* effect, uint8 slot_dbc) +{ + // DBC store slots starting from 1, with no slot 0 value) + int slot = slot_dbc ? slot_dbc - 1 : TOTEM_SLOT_NONE; + + // unsummon old totem + if (slot < MAX_TOTEM_SLOT) + if (Totem* OldTotem = m_caster->GetTotem(TotemSlot(slot))) + OldTotem->UnSummon(); + + // FIXME: Setup near to finish point because GetObjectBoundingRadius set in Create but some Create calls can be dependent from proper position + // if totem have creature_template_addon.auras with persistent point for example or script call + float angle = slot < MAX_TOTEM_SLOT ? M_PI_F / MAX_TOTEM_SLOT - (slot * 2 * M_PI_F / MAX_TOTEM_SLOT) : 0; + + CreatureCreatePos pos(m_caster, m_caster->GetOrientation(), 2.0f, angle); + + CreatureInfo const* cinfo = ObjectMgr::GetCreatureTemplate(effect->EffectMiscValue); + if (!cinfo) + { + sLog.outErrorDb("Creature entry %u does not exist but used in spell %u totem summon.", m_spellInfo->Id, effect->EffectMiscValue); + return false; + } + + Totem* pTotem = new Totem; + + if (!pTotem->Create(m_caster->GetMap()->GenerateLocalLowGuid(HIGHGUID_UNIT), pos, cinfo, m_caster)) + { + delete pTotem; + return false; + } + + pTotem->SetRespawnCoord(pos); + + if (slot < MAX_TOTEM_SLOT) + m_caster->_AddTotem(TotemSlot(slot), pTotem); + + // pTotem->SetName(""); // generated by client + pTotem->SetOwner(m_caster); + pTotem->SetTypeBySummonSpell(m_spellInfo); // must be after Create call where m_spells initialized + + pTotem->SetDuration(m_duration); + + if (damage) // if not spell info, DB values used + { + pTotem->SetMaxHealth(damage); + pTotem->SetHealth(damage); + } + + pTotem->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->Id); + + if (m_caster->GetTypeId() == TYPEID_PLAYER) + pTotem->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE); + + if (m_caster->IsPvP()) + pTotem->SetPvP(true); + + if (m_caster->IsFFAPvP()) + pTotem->SetFFAPvP(true); + + // sending SMSG_TOTEM_CREATED before add to map (done in Summon) + if (slot < MAX_TOTEM_SLOT && m_caster->GetTypeId() == TYPEID_PLAYER) + { + WorldPacket data(SMSG_TOTEM_CREATED, 1 + 8 + 4 + 4); + data << uint8(slot); + data << pTotem->GetObjectGuid(); + data << uint32(m_duration); + data << uint32(m_spellInfo->Id); + ((Player*)m_caster)->SendDirectMessage(&data); + } + + pTotem->Summon(m_caster); + + return false; +} + +bool Spell::DoSummonPossessed(CreatureSummonPositions& list, SummonPropertiesEntry const* prop, SpellEffectEntry const* effect, uint32 level) +{ + MANGOS_ASSERT(!list.empty() && prop); + + uint32 creatureEntry = effect->EffectMiscValue; + CreatureInfo const* cInfo = ObjectMgr::GetCreatureTemplate(creatureEntry); + if (!cInfo) + { + sLog.outErrorDb("Spell::DoSummonPossessed: creature entry %u not found for spell %u.", creatureEntry, m_spellInfo->Id); + return false; + } + + Creature* spawnCreature = m_caster->SummonCreature(creatureEntry, list[0].x, list[0].y, list[0].z, m_caster->GetOrientation(), TEMPSUMMON_CORPSE_DESPAWN, 0); + if (!spawnCreature) + { + sLog.outError("Spell::DoSummonPossessed: creature entry %u for spell %u could not be summoned.", creatureEntry, m_spellInfo->Id); + return false; + } + + list[0].creature = spawnCreature; + + // Changes to be sent + spawnCreature->SetCharmerGuid(m_caster->GetObjectGuid()); + spawnCreature->SetCreatorGuid(m_caster->GetObjectGuid()); + spawnCreature->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->Id); + spawnCreature->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + + spawnCreature->SetLevel(level); + + spawnCreature->SetWalk(m_caster->IsWalking(), true); + // TODO: Set Fly (ie glyph dependend) + + // Internal changes + spawnCreature->addUnitState(UNIT_STAT_CONTROLLED); + + // Changes to owner + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + Player* player = (Player*)m_caster; + + player->GetCamera().SetView(spawnCreature); + + player->SetCharm(spawnCreature); + player->SetClientControl(spawnCreature, 1); + player->SetMover(spawnCreature); + + if (CharmInfo* charmInfo = spawnCreature->InitCharmInfo(spawnCreature)) + charmInfo->InitPossessCreateSpells(); + player->PossessSpellInitialize(); + } + + // Notify Summoner + if (m_originalCaster && m_originalCaster != m_caster && m_originalCaster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_originalCaster)->AI()) + ((Creature*)m_originalCaster)->AI()->JustSummoned(spawnCreature); + + return true; +} + +bool Spell::DoSummonPet(SpellEffectEntry const* effect) +{ + if (m_caster->GetPetGuid()) + return false; + + if (!unitTarget) + return false; + + uint32 pet_entry = effect->EffectMiscValue; + CreatureInfo const* cInfo = ObjectMgr::GetCreatureTemplate(pet_entry); + if (!cInfo) + { + sLog.outErrorDb("Spell::DoSummonPet: creature entry %u not found for spell %u.", pet_entry, m_spellInfo->Id); + return false; + } + + uint32 level = m_caster->getLevel(); // TODO Engineering Pets have also caster-level? (if they exist) + Pet* spawnCreature = new Pet(SUMMON_PET); + + if (m_caster->GetTypeId() == TYPEID_PLAYER && spawnCreature->LoadPetFromDB((Player*)m_caster, pet_entry)) + { + // Summon in dest location + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) + spawnCreature->Relocate(m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ, -m_caster->GetOrientation()); + + // set timer for unsummon + if (m_duration > 0) + spawnCreature->SetDuration(m_duration); + + return false; + } + + // Summon in dest location + CreatureCreatePos pos(m_caster->GetMap(), m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ, -m_caster->GetOrientation(), m_caster->GetPhaseMask()); + + if (!(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION)) + pos = CreatureCreatePos(m_caster, -m_caster->GetOrientation()); + + Map* map = m_caster->GetMap(); + uint32 pet_number = sObjectMgr.GeneratePetNumber(); + if (!spawnCreature->Create(map->GenerateLocalLowGuid(HIGHGUID_PET), pos, cInfo, pet_number)) + { + sLog.outErrorDb("Spell::EffectSummon: can't create creature with entry %u for spell %u", cInfo->Entry, m_spellInfo->Id); + delete spawnCreature; + return false; + } + + spawnCreature->SetRespawnCoord(pos); + + // set timer for unsummon + if (m_duration > 0) + spawnCreature->SetDuration(m_duration); + + spawnCreature->SetOwnerGuid(m_caster->GetObjectGuid()); + spawnCreature->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); + spawnCreature->setPowerType(POWER_MANA); + spawnCreature->setFaction(m_caster->getFaction()); + spawnCreature->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, 0); + spawnCreature->SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0); + spawnCreature->SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, 1000); + spawnCreature->SetCreatorGuid(m_caster->GetObjectGuid()); + spawnCreature->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->Id); + + spawnCreature->InitStatsForLevel(level, m_caster); + + spawnCreature->GetCharmInfo()->SetPetNumber(pet_number, false); + + spawnCreature->AIM_Initialize(); + spawnCreature->InitPetCreateSpells(); + spawnCreature->InitLevelupSpellsForLevel(); + spawnCreature->SetHealth(spawnCreature->GetMaxHealth()); + spawnCreature->SetPower(POWER_MANA, spawnCreature->GetMaxPower(POWER_MANA)); + + // spawnCreature->SetName(""); // generated by client + + map->Add((Creature*)spawnCreature); + + m_caster->SetPet(spawnCreature); + + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + spawnCreature->GetCharmInfo()->SetReactState(REACT_DEFENSIVE); + spawnCreature->SavePetToDB(PET_SAVE_AS_CURRENT); + ((Player*)m_caster)->PetSpellInitialize(); + } + + if (m_caster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_caster)->AI()) + ((Creature*)m_caster)->AI()->JustSummoned((Creature*)spawnCreature); + if (m_originalCaster && m_originalCaster != m_caster && m_originalCaster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_originalCaster)->AI()) + ((Creature*)m_originalCaster)->AI()->JustSummoned((Creature*)spawnCreature); + + return false; +} + +bool Spell::DoSummonVehicle(CreatureSummonPositions& list, SummonPropertiesEntry const* prop, SpellEffectEntry const * effect, uint32 /*level*/) +{ + MANGOS_ASSERT(!list.empty() && prop); + + uint32 creatureEntry = effect->EffectMiscValue; + CreatureInfo const* cInfo = ObjectMgr::GetCreatureTemplate(creatureEntry); + if (!cInfo) + { + sLog.outErrorDb("Spell::DoSummonVehicle: creature entry %u not found for spell %u.", creatureEntry, m_spellInfo->Id); + return false; + } + + Creature* spawnCreature = m_caster->SummonCreature(creatureEntry, list[0].x, list[0].y, list[0].z, m_caster->GetOrientation(), + (m_duration == 0) ? TEMPSUMMON_CORPSE_DESPAWN : TEMPSUMMON_TIMED_OOC_OR_CORPSE_DESPAWN, m_duration); + + if (!spawnCreature) + { + sLog.outError("Spell::DoSummonVehicle: creature entry %u for spell %u could not be summoned.", creatureEntry, m_spellInfo->Id); + return false; + } + + list[0].creature = spawnCreature; + + // Changes to be sent + spawnCreature->SetCreatorGuid(m_caster->GetObjectGuid()); + spawnCreature->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->Id); + //spawnCreature->SetLevel(level); // Do we need to set level for vehicles? + + // Board the caster right after summoning + SpellEntry const* controlSpellEntry = sSpellStore.LookupEntry(effect->CalculateSimpleValue()); + if (controlSpellEntry && IsSpellHaveAura(controlSpellEntry, SPELL_AURA_CONTROL_VEHICLE)) + m_caster->CastSpell(spawnCreature, controlSpellEntry, true); + else + m_caster->CastSpell(spawnCreature, SPELL_RIDE_VEHICLE_HARDCODED, true); + + // If the boarding failed... + if (!spawnCreature->HasAuraType(SPELL_AURA_CONTROL_VEHICLE)) + { + spawnCreature->ForcedDespawn(); + return false; + } + + // Notify Summoner + if (m_originalCaster && m_originalCaster != m_caster && m_originalCaster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_originalCaster)->AI()) + ((Creature*)m_originalCaster)->AI()->JustSummoned(spawnCreature); + + return true; +} + +void Spell::EffectLearnSpell(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + + if (unitTarget->GetTypeId() != TYPEID_PLAYER) + { + if (m_caster->GetTypeId() == TYPEID_PLAYER) + EffectLearnPetSpell(effect); + + return; + } + + Player* player = (Player*)unitTarget; + + uint32 spellToLearn = ((m_spellInfo->Id==SPELL_ID_GENERIC_LEARN) || (m_spellInfo->Id==SPELL_ID_GENERIC_LEARN_PET)) ? damage : effect->EffectTriggerSpell; + player->learnSpell(spellToLearn, false); + + if (WorldObject const* caster = GetCastingObject()) + DEBUG_LOG("Spell: %s has learned spell %u from %s", player->GetGuidStr().c_str(), spellToLearn, caster->GetGuidStr().c_str()); +} + +void Spell::EffectDispel(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + + // Fill possible dispel list + std::list > dispel_list; + + // Create dispel mask by dispel type + uint32 dispel_type = effect->EffectMiscValue; + uint32 dispelMask = GetDispellMask( DispelType(dispel_type) ); + Unit::SpellAuraHolderMap const& auras = unitTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + SpellAuraHolder *holder = itr->second; + if ((1<GetSpellProto()->GetDispel()) & dispelMask) + { + if(holder->GetSpellProto()->GetDispel() == DISPEL_MAGIC) + { + bool positive = true; + if (!holder->IsPositive()) + positive = false; + else + positive = !holder->GetSpellProto()->HasAttribute(SPELL_ATTR_EX_NEGATIVE); + + // do not remove positive auras if friendly target + // negative auras if non-friendly target + if (positive == unitTarget->IsFriendlyTo(m_caster)) + continue; + } + // Unholy Blight prevents dispel of diseases from target + else if (holder->GetSpellProto()->GetDispel() == DISPEL_DISEASE) + if (unitTarget->HasAura(50536)) + continue; + + dispel_list.push_back(std::pair(holder, holder->GetStackAmount())); + } + } + // Ok if exist some buffs for dispel try dispel it + if (!dispel_list.empty()) + { + std::list > success_list; // (spell_id,casterGuid) + std::list < uint32 > fail_list; // spell_id + + // some spells have effect value = 0 and all from its by meaning expect 1 + if (!damage) + damage = 1; + + // Dispel N = damage buffs (or while exist buffs for dispel) + for (int32 count = 0; count < damage && !dispel_list.empty(); ++count) + { + // Random select buff for dispel + std::list >::iterator dispel_itr = dispel_list.begin(); + std::advance(dispel_itr, urand(0, dispel_list.size() - 1)); + + SpellAuraHolder* holder = dispel_itr->first; + + dispel_itr->second -= 1; + + // remove entry from dispel_list if nothing left in stack + if (dispel_itr->second == 0) + dispel_list.erase(dispel_itr); + + SpellEntry const* spellInfo = holder->GetSpellProto(); + // Base dispel chance + // TODO: possible chance depend from spell level?? + int32 miss_chance = 0; + // Apply dispel mod from aura caster + if (Unit* caster = holder->GetCaster()) + { + if (Player* modOwner = caster->GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_RESIST_DISPEL_CHANCE, miss_chance, this); + } + // Try dispel + if (roll_chance_i(miss_chance)) + fail_list.push_back(spellInfo->Id); + else + { + bool foundDispelled = false; + for (std::list >::iterator success_iter = success_list.begin(); success_iter != success_list.end(); ++success_iter) + { + if (success_iter->first->GetId() == holder->GetId() && success_iter->first->GetCasterGuid() == holder->GetCasterGuid()) + { + success_iter->second += 1; + foundDispelled = true; + break; + } + } + if (!foundDispelled) + success_list.push_back(std::pair(holder, 1)); + } + } + // Send success log and really remove auras + if (!success_list.empty()) + { + int32 count = success_list.size(); + WorldPacket data(SMSG_SPELLDISPELLOG, 8 + 8 + 4 + 1 + 4 + count * 5); + data << unitTarget->GetPackGUID(); // Victim GUID + data << m_caster->GetPackGUID(); // Caster GUID + data << uint32(m_spellInfo->Id); // Dispel spell id + data << uint8(0); // not used + data << uint32(count); // count + for (std::list >::iterator j = success_list.begin(); j != success_list.end(); ++j) + { + SpellAuraHolder* dispelledHolder = j->first; + data << uint32(dispelledHolder->GetId()); // Spell Id + data << uint8(0); // 0 - dispelled !=0 cleansed + unitTarget->RemoveAuraHolderDueToSpellByDispel(dispelledHolder->GetId(), j->second, dispelledHolder->GetCasterGuid(), m_caster); + } + m_caster->SendMessageToSet(&data, true); + + // On success dispel + // Devour Magic + if (m_spellInfo->GetSpellFamilyName() == SPELLFAMILY_WARLOCK && m_spellInfo->GetCategory() == SPELLCATEGORY_DEVOUR_MAGIC) + { + int32 heal_amount = m_spellInfo->CalculateSimpleValue(EFFECT_INDEX_1); + m_caster->CastCustomSpell(m_caster, 19658, &heal_amount, NULL, NULL, true); + } + } + // Send fail log to client + if (!fail_list.empty()) + { + // Failed to dispel + WorldPacket data(SMSG_DISPEL_FAILED, 8 + 8 + 4 + 4 * fail_list.size()); + data << m_caster->GetObjectGuid(); // Caster GUID + data << unitTarget->GetObjectGuid(); // Victim GUID + data << uint32(m_spellInfo->Id); // Dispel spell id + for (std::list< uint32 >::iterator j = fail_list.begin(); j != fail_list.end(); ++j) + data << uint32(*j); // Spell Id + m_caster->SendMessageToSet(&data, true); + } + } +} + +void Spell::EffectDualWield(SpellEffectEntry const* /*effect*/) +{ + if (unitTarget && unitTarget->GetTypeId() == TYPEID_PLAYER) + ((Player*)unitTarget)->SetCanDualWield(true); +} + +void Spell::EffectPull(SpellEffectEntry const* /*effect*/) +{ + // TODO: create a proper pull towards distract spell center for distract + DEBUG_LOG("WORLD: Spell Effect DUMMY"); +} + +void Spell::EffectDistract(SpellEffectEntry const* /*effect*/) +{ + // Check for possible target + if (!unitTarget || unitTarget->isInCombat()) + return; + + // target must be OK to do this + if (unitTarget->hasUnitState(UNIT_STAT_CAN_NOT_REACT)) + return; + + unitTarget->SetFacingTo(unitTarget->GetAngle(m_targets.m_destX, m_targets.m_destY)); + unitTarget->clearUnitState(UNIT_STAT_MOVING); + + if (unitTarget->GetTypeId() == TYPEID_UNIT) + unitTarget->GetMotionMaster()->MoveDistract(damage * IN_MILLISECONDS); +} + +void Spell::EffectPickPocket(SpellEffectEntry const* /*effect*/) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // victim must be creature and attackable + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT || m_caster->IsFriendlyTo(unitTarget)) + return; + + // victim have to be alive and humanoid or undead + if (unitTarget->isAlive() && (unitTarget->GetCreatureTypeMask() & CREATURE_TYPEMASK_HUMANOID_OR_UNDEAD) != 0) + { + int32 chance = 10 + int32(m_caster->getLevel()) - int32(unitTarget->getLevel()); + + if (chance > irand(0, 19)) + { + // Stealing successful + // DEBUG_LOG("Sending loot from pickpocket"); + ((Player*)m_caster)->SendLoot(unitTarget->GetObjectGuid(), LOOT_PICKPOCKETING); + } + else + { + // Reveal action + get attack + m_caster->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); + unitTarget->AttackedBy(m_caster); + } + } +} + +void Spell::EffectAddFarsight(SpellEffectEntry const* effect) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + int32 duration = GetSpellDuration(m_spellInfo); + DynamicObject* dynObj = new DynamicObject; + + // set radius to 0: spell not expected to work as persistent aura + if(!dynObj->Create(m_caster->GetMap()->GenerateLocalLowGuid(HIGHGUID_DYNAMICOBJECT), m_caster, m_spellInfo->Id, SpellEffectIndex(effect->EffectIndex), m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ, duration, 0, DYNAMIC_OBJECT_FARSIGHT_FOCUS)) + { + delete dynObj; + return; + } + + m_caster->AddDynObject(dynObj); + m_caster->GetMap()->Add(dynObj); + + ((Player*)m_caster)->GetCamera().SetView(dynObj); +} + +void Spell::EffectTeleUnitsFaceCaster(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + + if (unitTarget->IsTaxiFlying()) + return; + + float fx, fy, fz; + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) + m_targets.getDestination(fx, fy, fz); + else + { + float dis = GetSpellRadius(sSpellRadiusStore.LookupEntry(effect->GetRadiusIndex())); + m_caster->GetClosePoint(fx, fy, fz, unitTarget->GetObjectBoundingRadius(), dis); + } + + unitTarget->NearTeleportTo(fx, fy, fz, -m_caster->GetOrientation(), unitTarget == m_caster); +} + +void Spell::EffectLearnSkill(SpellEffectEntry const* effect) +{ + if (unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + if (damage < 0) + return; + + uint32 skillid = effect->EffectMiscValue; + uint16 skillval = ((Player*)unitTarget)->GetPureSkillValue(skillid); + ((Player*)unitTarget)->SetSkill(skillid, skillval ? skillval : 1, damage * 75, damage); + + if (WorldObject const* caster = GetCastingObject()) + DEBUG_LOG("Spell: %s has learned skill %u (to maxlevel %u) from %s", unitTarget->GetGuidStr().c_str(), skillid, damage * 75, caster->GetGuidStr().c_str()); +} + +void Spell::EffectTradeSkill(SpellEffectEntry const* /*effect*/) +{ + if (unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + // uint32 skillid = m_spellInfo->EffectMiscValue[i]; + // uint16 skillmax = ((Player*)unitTarget)->(skillid); + // ((Player*)unitTarget)->SetSkill(skillid,skillval?skillval:1,skillmax+75); +} + +void Spell::EffectEnchantItemPerm(SpellEffectEntry const* effect) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + if (!itemTarget) + return; + + uint32 enchant_id = effect->EffectMiscValue; + if (!enchant_id) + return; + + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + return; + + // item can be in trade slot and have owner diff. from caster + Player* item_owner = itemTarget->GetOwner(); + if (!item_owner) + return; + + Player* p_caster = (Player*)m_caster; + + // Enchanting a vellum requires special handling, as it creates a new item + // instead of modifying an existing one. + ItemPrototype const* targetProto = itemTarget->GetProto(); + if (targetProto->IsVellum() && effect->EffectItemType) + { + unitTarget = m_caster; + DoCreateItem(effect, effect->EffectItemType); + // Vellum target case: Target becomes additional reagent, new scroll item created instead in Spell::EffectEnchantItemPerm() + // cannot already delete in TakeReagents() unfortunately + p_caster->DestroyItemCount(targetProto->ItemId, 1, true); + return; + } + + // Using enchant stored on scroll does not increase enchanting skill! (Already granted on scroll creation) + if (!(m_CastItem && m_CastItem->GetProto()->Flags & ITEM_FLAG_ENCHANT_SCROLL)) + p_caster->UpdateCraftSkill(m_spellInfo->Id); + + if (item_owner != p_caster && p_caster->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_BOOL_GM_LOG_TRADE)) + { + sLog.outCommand(p_caster->GetSession()->GetAccountId(), "GM %s (Account: %u) enchanting(perm): %s (Entry: %d) for player: %s (Account: %u)", + p_caster->GetName(), p_caster->GetSession()->GetAccountId(), + itemTarget->GetProto()->Name1, itemTarget->GetEntry(), + item_owner->GetName(), item_owner->GetSession()->GetAccountId()); + } + + // remove old enchanting before applying new if equipped + item_owner->ApplyEnchantment(itemTarget, PERM_ENCHANTMENT_SLOT, false); + + itemTarget->SetEnchantment(PERM_ENCHANTMENT_SLOT, enchant_id, 0, 0); + + // add new enchanting if equipped + item_owner->ApplyEnchantment(itemTarget, PERM_ENCHANTMENT_SLOT, true); +} + +void Spell::EffectEnchantItemPrismatic(SpellEffectEntry const* effect) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + if (!itemTarget) + return; + + Player* p_caster = (Player*)m_caster; + + uint32 enchant_id = effect->EffectMiscValue; + if (!enchant_id) + return; + + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + return; + + // support only enchantings with add socket in this slot + { + bool add_socket = false; + for (int i = 0; i < 3; ++i) + { + if (pEnchant->type[i] == ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET) + { + add_socket = true; + break; + } + } + if (!add_socket) + { + sLog.outError("Spell::EffectEnchantItemPrismatic: attempt apply enchant spell %u with SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC (%u) but without ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET (%u), not suppoted yet.", + m_spellInfo->Id, SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC, ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET); + return; + } + } + + // item can be in trade slot and have owner diff. from caster + Player* item_owner = itemTarget->GetOwner(); + if (!item_owner) + return; + + if (item_owner != p_caster && p_caster->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_BOOL_GM_LOG_TRADE)) + { + sLog.outCommand(p_caster->GetSession()->GetAccountId(), "GM %s (Account: %u) enchanting(perm): %s (Entry: %d) for player: %s (Account: %u)", + p_caster->GetName(), p_caster->GetSession()->GetAccountId(), + itemTarget->GetProto()->Name1, itemTarget->GetEntry(), + item_owner->GetName(), item_owner->GetSession()->GetAccountId()); + } + + // remove old enchanting before applying new if equipped + item_owner->ApplyEnchantment(itemTarget, PRISMATIC_ENCHANTMENT_SLOT, false); + + itemTarget->SetEnchantment(PRISMATIC_ENCHANTMENT_SLOT, enchant_id, 0, 0); + + // add new enchanting if equipped + item_owner->ApplyEnchantment(itemTarget, PRISMATIC_ENCHANTMENT_SLOT, true); +} + +void Spell::EffectEnchantItemTmp(SpellEffectEntry const* effect) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Player* p_caster = (Player*)m_caster; + + // Rockbiter Weapon + SpellClassOptionsEntry const* classOptions = m_spellInfo->GetSpellClassOptions(); + if (classOptions && classOptions->SpellFamilyName == SPELLFAMILY_SHAMAN && classOptions->SpellFamilyFlags & UI64LIT(0x0000000000400000)) + { + uint32 spell_id = 0; + + // enchanting spell selected by calculated damage-per-sec stored in Effect[1] base value + // Note: damage calculated (correctly) with rounding int32(float(v)) but + // RW enchantments applied damage int32(float(v)+0.5), this create 0..1 difference sometime + switch (damage) + { + // Rank 1 + case 2: spell_id = 36744; break; // 0% [ 7% == 2, 14% == 2, 20% == 2] + // Rank 2 + case 4: spell_id = 36753; break; // 0% [ 7% == 4, 14% == 4] + case 5: spell_id = 36751; break; // 20% + // Rank 3 + case 6: spell_id = 36754; break; // 0% [ 7% == 6, 14% == 6] + case 7: spell_id = 36755; break; // 20% + // Rank 4 + case 9: spell_id = 36761; break; // 0% [ 7% == 6] + case 10: spell_id = 36758; break; // 14% + case 11: spell_id = 36760; break; // 20% + default: + sLog.outError("Spell::EffectEnchantItemTmp: Damage %u not handled in S'RW", damage); + return; + } + + SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id); + if (!spellInfo) + { + sLog.outError("Spell::EffectEnchantItemTmp: unknown spell id %i", spell_id); + return; + } + + Spell* spell = new Spell(m_caster, spellInfo, true); + SpellCastTargets targets; + targets.setItemTarget(itemTarget); + spell->prepare(&targets); + return; + } + + if (!itemTarget) + return; + + uint32 enchant_id = effect->EffectMiscValue; + + if (!enchant_id) + { + sLog.outError("Spell %u Effect %u (SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY) have 0 as enchanting id",m_spellInfo->Id,effect->EffectIndex); + return; + } + + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + { + sLog.outError("Spell %u Effect %u (SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY) have nonexistent enchanting id %u ",m_spellInfo->Id,effect->EffectIndex,enchant_id); + return; + } + + // select enchantment duration + uint32 duration; + + // rogue family enchantments exception by duration + if (m_spellInfo->Id == 38615) + duration = 1800; // 30 mins + // other rogue family enchantments always 1 hour (some have spell damage=0, but some have wrong data in EffBasePoints) + else if(classOptions && classOptions->SpellFamilyName == SPELLFAMILY_ROGUE) + duration = 3600; // 1 hour + // shaman family enchantments + else if(classOptions && classOptions->SpellFamilyName == SPELLFAMILY_SHAMAN) + duration = 1800; // 30 mins + // other cases with this SpellVisual already selected + else if (m_spellInfo->SpellVisual[0] == 215) + duration = 1800; // 30 mins + // some fishing pole bonuses + else if (m_spellInfo->SpellVisual[0] == 563) + duration = 600; // 10 mins + // shaman rockbiter enchantments + else if (m_spellInfo->SpellVisual[0] == 0) + duration = 1800; // 30 mins + else if (m_spellInfo->Id == 29702) + duration = 300; // 5 mins + else if (m_spellInfo->Id == 37360) + duration = 300; // 5 mins + // default case + else + duration = 3600; // 1 hour + + // item can be in trade slot and have owner diff. from caster + Player* item_owner = itemTarget->GetOwner(); + if (!item_owner) + return; + + if (item_owner != p_caster && p_caster->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_BOOL_GM_LOG_TRADE)) + { + sLog.outCommand(p_caster->GetSession()->GetAccountId(), "GM %s (Account: %u) enchanting(temp): %s (Entry: %d) for player: %s (Account: %u)", + p_caster->GetName(), p_caster->GetSession()->GetAccountId(), + itemTarget->GetProto()->Name1, itemTarget->GetEntry(), + item_owner->GetName(), item_owner->GetSession()->GetAccountId()); + } + + // remove old enchanting before applying new if equipped + item_owner->ApplyEnchantment(itemTarget, TEMP_ENCHANTMENT_SLOT, false); + + itemTarget->SetEnchantment(TEMP_ENCHANTMENT_SLOT, enchant_id, duration * 1000, 0); + + // add new enchanting if equipped + item_owner->ApplyEnchantment(itemTarget, TEMP_ENCHANTMENT_SLOT, true); +} + +void Spell::EffectTameCreature(SpellEffectEntry const* /*effect*/) +{ + // Caster must be player, checked in Spell::CheckCast + // Spell can be triggered, we need to check original caster prior to caster + Player* plr = (Player*)GetAffectiveCaster(); + + Creature* creatureTarget = (Creature*)unitTarget; + + // cast finish successfully + // SendChannelUpdate(0); + finish(); + + Pet* pet = new Pet(HUNTER_PET); + + if (!pet->CreateBaseAtCreature(creatureTarget)) + { + delete pet; + return; + } + + pet->SetOwnerGuid(plr->GetObjectGuid()); + pet->SetCreatorGuid(plr->GetObjectGuid()); + pet->setFaction(plr->getFaction()); + pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->Id); + + if (plr->IsPvP()) + pet->SetPvP(true); + + if (plr->IsFFAPvP()) + pet->SetFFAPvP(true); + + // level of hunter pet can't be less owner level at 5 levels + uint32 level = creatureTarget->getLevel() + 5 < plr->getLevel() ? (plr->getLevel() - 5) : creatureTarget->getLevel(); + + if (!pet->InitStatsForLevel(level)) + { + sLog.outError("Pet::InitStatsForLevel() failed for creature (Entry: %u)!", creatureTarget->GetEntry()); + delete pet; + return; + } + + pet->GetCharmInfo()->SetPetNumber(sObjectMgr.GeneratePetNumber(), true); + // this enables pet details window (Shift+P) + pet->AIM_Initialize(); + pet->InitPetCreateSpells(); + pet->InitLevelupSpellsForLevel(); + pet->InitTalentForLevel(); + pet->SetHealth(pet->GetMaxHealth()); + + // "kill" original creature + creatureTarget->ForcedDespawn(); + + // prepare visual effect for levelup + pet->SetUInt32Value(UNIT_FIELD_LEVEL, level - 1); + + // add to world + pet->GetMap()->Add((Creature*)pet); + + // visual effect for levelup + pet->SetUInt32Value(UNIT_FIELD_LEVEL, level); + + // caster have pet now + plr->SetPet(pet); + + pet->SavePetToDB(PET_SAVE_AS_CURRENT); + plr->PetSpellInitialize(); +} + +void Spell::EffectSummonPet(SpellEffectEntry const* effect) +{ + uint32 petentry = effect->EffectMiscValue; + + Pet* OldSummon = m_caster->GetPet(); + + // if pet requested type already exist + if (OldSummon) + { + if ((petentry == 0 || OldSummon->GetEntry() == petentry) && OldSummon->getPetType() != SUMMON_PET) + { + // pet in corpse state can't be summoned + if (OldSummon->isDead()) + return; + + OldSummon->GetMap()->Remove((Creature*)OldSummon, false); + + float px, py, pz; + m_caster->GetClosePoint(px, py, pz, OldSummon->GetObjectBoundingRadius()); + + OldSummon->Relocate(px, py, pz, OldSummon->GetOrientation()); + m_caster->GetMap()->Add((Creature*)OldSummon); + + if (m_caster->GetTypeId() == TYPEID_PLAYER && OldSummon->isControlled()) + { + ((Player*)m_caster)->PetSpellInitialize(); + } + return; + } + + if (m_caster->GetTypeId() == TYPEID_PLAYER) + OldSummon->Unsummon(OldSummon->getPetType() == HUNTER_PET ? PET_SAVE_AS_DELETED : PET_SAVE_NOT_IN_SLOT, m_caster); + else + return; + } + + CreatureInfo const* cInfo = ObjectMgr::GetCreatureTemplate(petentry); + + // == 0 in case call current pet, check only real summon case + if (petentry && !cInfo) + { + sLog.outErrorDb("EffectSummonPet: creature entry %u not found for spell %u.", petentry, m_spellInfo->Id); + return; + } + + Pet* NewSummon = new Pet; + + // petentry==0 for hunter "call pet" (current pet summoned if any) + if (m_caster->GetTypeId() == TYPEID_PLAYER && NewSummon->LoadPetFromDB((Player*)m_caster, petentry)) + return; + + // not error in case fail hunter call pet + if (!petentry) + { + delete NewSummon; + return; + } + + CreatureCreatePos pos(m_caster, m_caster->GetOrientation()); + + Map* map = m_caster->GetMap(); + uint32 pet_number = sObjectMgr.GeneratePetNumber(); + if (!NewSummon->Create(map->GenerateLocalLowGuid(HIGHGUID_PET), pos, cInfo, pet_number)) + { + delete NewSummon; + return; + } + + NewSummon->SetRespawnCoord(pos); + + uint32 petlevel = m_caster->getLevel(); + NewSummon->setPetType(SUMMON_PET); + + uint32 faction = m_caster->getFaction(); + if (m_caster->GetTypeId() == TYPEID_UNIT) + { + if (((Creature*)m_caster)->IsTotem()) + NewSummon->GetCharmInfo()->SetReactState(REACT_AGGRESSIVE); + else + NewSummon->GetCharmInfo()->SetReactState(REACT_DEFENSIVE); + } + + NewSummon->SetOwnerGuid(m_caster->GetObjectGuid()); + NewSummon->SetCreatorGuid(m_caster->GetObjectGuid()); + NewSummon->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); + NewSummon->setFaction(faction); + NewSummon->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(time(NULL))); + NewSummon->SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0); + NewSummon->SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, 1000); + NewSummon->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->Id); + + NewSummon->GetCharmInfo()->SetPetNumber(pet_number, true); + // this enables pet details window (Shift+P) + + if (m_caster->IsPvP()) + NewSummon->SetPvP(true); + + if (m_caster->IsFFAPvP()) + NewSummon->SetFFAPvP(true); + + NewSummon->InitStatsForLevel(petlevel, m_caster); + NewSummon->InitPetCreateSpells(); + NewSummon->InitLevelupSpellsForLevel(); + NewSummon->InitTalentForLevel(); + + if (m_caster->GetTypeId() == TYPEID_PLAYER && NewSummon->getPetType() == SUMMON_PET) + { + // generate new name for summon pet + std::string new_name = sObjectMgr.GeneratePetName(petentry); + if (!new_name.empty()) + NewSummon->SetName(new_name); + } + + if (NewSummon->getPetType() == HUNTER_PET) + { + NewSummon->RemoveByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED); + NewSummon->SetByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_ABANDONED); + } + + NewSummon->AIM_Initialize(); + NewSummon->SetHealth(NewSummon->GetMaxHealth()); + NewSummon->SetPower(POWER_MANA, NewSummon->GetMaxPower(POWER_MANA)); + + map->Add((Creature*)NewSummon); + + m_caster->SetPet(NewSummon); + DEBUG_LOG("New Pet has guid %u", NewSummon->GetGUIDLow()); + + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + NewSummon->SavePetToDB(PET_SAVE_AS_CURRENT); + ((Player*)m_caster)->PetSpellInitialize(); + } +} + +void Spell::EffectLearnPetSpell(SpellEffectEntry const* effect) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Player* _player = (Player*)m_caster; + + Pet* pet = _player->GetPet(); + if (!pet) + return; + if (!pet->isAlive()) + return; + + SpellEntry const *learn_spellproto = sSpellStore.LookupEntry(effect->EffectTriggerSpell); + if(!learn_spellproto) + return; + + pet->learnSpell(learn_spellproto->Id); + + pet->SavePetToDB(PET_SAVE_AS_CURRENT); + _player->PetSpellInitialize(); + + if (WorldObject const* caster = GetCastingObject()) + DEBUG_LOG("Spell: %s has learned spell %u from %s", pet->GetGuidStr().c_str(), learn_spellproto->Id, caster->GetGuidStr().c_str()); +} + +void Spell::EffectTaunt(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget) + return; + + // this effect use before aura Taunt apply for prevent taunt already attacking target + // for spell as marked "non effective at already attacking target" + if (unitTarget->GetTypeId() != TYPEID_PLAYER) + { + if (unitTarget->getVictim() == m_caster) + { + SendCastResult(SPELL_FAILED_DONT_REPORT); + return; + } + } + + // Also use this effect to set the taunter's threat to the taunted creature's highest value + if (unitTarget->CanHaveThreatList() && unitTarget->getThreatManager().getCurrentVictim()) + unitTarget->getThreatManager().addThreat(m_caster, unitTarget->getThreatManager().getCurrentVictim()->getThreat()); +} + +void Spell::EffectWeaponDmg(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + if (!unitTarget->isAlive()) + return; + + // multiple weapon dmg effect workaround + // execute only the last weapon damage + // and handle all effects at once + for (int j = 0; j < MAX_EFFECT_INDEX; ++j) + { + switch(m_spellInfo->GetSpellEffectIdByIndex(SpellEffectIndex(j))) + { + case SPELL_EFFECT_WEAPON_DAMAGE: + case SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL: + case SPELL_EFFECT_NORMALIZED_WEAPON_DMG: + case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE: + if (j < int(effect->EffectIndex)) // we must calculate only at last weapon effect + return; + break; + } + } + + // some spell specific modifiers + bool spellBonusNeedWeaponDamagePercentMod = false; // if set applied weapon damage percent mode to spell bonus + + float weaponDamagePercentMod = 1.0f; // applied to weapon damage and to fixed effect damage bonus + float totalDamagePercentMod = 1.0f; // applied to final bonus+weapon damage + bool normalized = false; + + int32 spell_bonus = 0; // bonus specific for spell + + SpellClassOptionsEntry const* classOptions = m_spellInfo->GetSpellClassOptions(); + + switch(m_spellInfo->GetSpellFamilyName()) + { + case SPELLFAMILY_GENERIC: + { + switch (m_spellInfo->Id) + { + // for spells with divided damage to targets + case 66765: case 66809: case 67331: // Meteor Fists + case 67333: // Meteor Fists + case 69055: // Bone Slice + case 71021: // Saber Lash + { + uint32 count = 0; + for(TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + if(ihit->effectMask & (1<EffectIndex)) + ++count; + + totalDamagePercentMod /= float(count); // divide to all targets + break; + } + } + break; + } + case SPELLFAMILY_WARRIOR: + { + // Devastate + if (m_spellInfo->SpellVisual[0] == 12295 && m_spellInfo->SpellIconID == 1508) + { + // Sunder Armor + Aura* sunder = unitTarget->GetAura(SPELL_AURA_MOD_RESISTANCE_PCT, SPELLFAMILY_WARRIOR, UI64LIT(0x0000000000004000), 0x00000000, m_caster->GetObjectGuid()); + + // Devastate bonus and sunder armor refresh + if (sunder) + { + sunder->GetHolder()->RefreshHolder(); + spell_bonus += sunder->GetStackAmount() * CalculateDamage(EFFECT_INDEX_2, unitTarget); + } + + // Devastate causing Sunder Armor Effect + // and no need to cast over max stack amount + if (!sunder || sunder->GetStackAmount() < sunder->GetSpellProto()->GetStackAmount()) + m_caster->CastSpell(unitTarget, 58567, true); + } + break; + } + case SPELLFAMILY_ROGUE: + { + // Mutilate (for each hand) + if(classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x600000000)) + { + bool found = false; + // fast check + if (unitTarget->HasAuraState(AURA_STATE_DEADLY_POISON)) + found = true; + // full aura scan + else + { + Unit::SpellAuraHolderMap const& auras = unitTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + if(itr->second->GetSpellProto()->GetDispel() == DISPEL_POISON) + { + found = true; + break; + } + } + } + + if (found) + totalDamagePercentMod *= 1.2f; // 120% if poisoned + } + // Fan of Knives + else if (m_caster->GetTypeId()==TYPEID_PLAYER && classOptions && (classOptions->SpellFamilyFlags & UI64LIT(0x0004000000000000))) + { + Item* weapon = ((Player*)m_caster)->GetWeaponForAttack(m_attackType, true, true); + if (weapon && weapon->GetProto()->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER) + totalDamagePercentMod *= 1.5f; // 150% to daggers + } + // Ghostly Strike + else if (m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->Id == 14278) + { + Item* weapon = ((Player*)m_caster)->GetWeaponForAttack(m_attackType, true, true); + if (weapon && weapon->GetProto()->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER) + totalDamagePercentMod *= 1.44f; // 144% to daggers + } + // Hemorrhage + else if (m_caster->GetTypeId() == TYPEID_PLAYER && classOptions && (classOptions->SpellFamilyFlags & UI64LIT(0x2000000))) + { + Item* weapon = ((Player*)m_caster)->GetWeaponForAttack(m_attackType, true, true); + if (weapon && weapon->GetProto()->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER) + totalDamagePercentMod *= 1.45f; // 145% to daggers + } + break; + } + case SPELLFAMILY_PALADIN: + { + // Judgement of Command - receive benefit from Spell Damage and Attack Power + if(classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x00020000000000)) + { + float ap = m_caster->GetTotalAttackPowerValue(BASE_ATTACK); + int32 holy = m_caster->SpellBaseDamageBonusDone(GetSpellSchoolMask(m_spellInfo)); + if (holy < 0) + holy = 0; + spell_bonus += int32(ap * 0.08f) + int32(holy * 13 / 100); + } + break; + } + case SPELLFAMILY_HUNTER: + { + // Kill Shot + if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x80000000000000)) + { + // 0.4*RAP added to damage (that is 0.2 if we apply PercentMod (200%) to spell_bonus, too) + spellBonusNeedWeaponDamagePercentMod = true; + spell_bonus += int32(0.2f * m_caster->GetTotalAttackPowerValue(RANGED_ATTACK)); + } + break; + } + case SPELLFAMILY_SHAMAN: + { + // Skyshatter Harness item set bonus + // Stormstrike + if(classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x001000000000)) + { + Unit::AuraList const& m_OverrideClassScript = m_caster->GetAurasByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for (Unit::AuraList::const_iterator citr = m_OverrideClassScript.begin(); citr != m_OverrideClassScript.end(); ++citr) + { + // Stormstrike AP Buff + if ((*citr)->GetModifier()->m_miscvalue == 5634) + { + m_caster->CastSpell(m_caster, 38430, true, NULL, *citr); + break; + } + } + } + break; + } + case SPELLFAMILY_DEATHKNIGHT: + { + // Blood Strike, Heart Strike, Obliterate + // Blood-Caked Strike + if (m_spellInfo->SpellIconID == 1736) + { + uint32 count = 0; + Unit::SpellAuraHolderMap const& auras = unitTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + if(itr->second->GetSpellProto()->GetDispel() == DISPEL_DISEASE && + itr->second->GetCasterGuid() == m_caster->GetObjectGuid()) + ++count; + } + + if (count) + { + // Effect 1(for Blood-Caked Strike)/3(other) damage is bonus + float bonus = count * CalculateDamage(m_spellInfo->SpellIconID == 1736 ? EFFECT_INDEX_0 : EFFECT_INDEX_2, unitTarget) / 100.0f; + // Blood Strike, Blood-Caked Strike and Obliterate store bonus*2 + if ((classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0002000000400000)) || + m_spellInfo->SpellIconID == 1736) + bonus /= 2.0f; + + totalDamagePercentMod *= 1.0f + bonus; + } + + // Heart Strike secondary target + if (m_spellInfo->SpellIconID == 3145) + if (m_targets.getUnitTarget() != unitTarget) + weaponDamagePercentMod /= 2.0f; + } + // Glyph of Blood Strike + if( classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0000000000400000) && + m_caster->HasAura(59332) && + unitTarget->HasAuraType(SPELL_AURA_MOD_DECREASE_SPEED)) + { + totalDamagePercentMod *= 1.2f; // 120% if snared + } + // Glyph of Death Strike + if( classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0000000000000010) && + m_caster->HasAura(59336)) + { + int32 rp = m_caster->GetPower(POWER_RUNIC_POWER) / 10; + if (rp > 25) + rp = 25; + totalDamagePercentMod *= 1.0f + rp / 100.0f; + } + // Glyph of Plague Strike + if( classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0000000000000001) && + m_caster->HasAura(58657) ) + { + totalDamagePercentMod *= 1.2f; + } + break; + } + } + + int32 fixed_bonus = 0; + for (int j = 0; j < MAX_EFFECT_INDEX; ++j) + { + SpellEffectEntry const* spellEffect = m_spellInfo->GetSpellEffect(SpellEffectIndex(j)); + if(!spellEffect) + continue; + + switch(spellEffect->Effect) + { + case SPELL_EFFECT_WEAPON_DAMAGE: + case SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL: + fixed_bonus += CalculateDamage(SpellEffectIndex(j), unitTarget); + break; + case SPELL_EFFECT_NORMALIZED_WEAPON_DMG: + fixed_bonus += CalculateDamage(SpellEffectIndex(j), unitTarget); + normalized = true; + break; + case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE: + weaponDamagePercentMod *= float(CalculateDamage(SpellEffectIndex(j), unitTarget)) / 100.0f; + + // applied only to prev.effects fixed damage + fixed_bonus = int32(fixed_bonus * weaponDamagePercentMod); + break; + default: + break; // not weapon damage effect, just skip + } + } + + // apply weaponDamagePercentMod to spell bonus also + if (spellBonusNeedWeaponDamagePercentMod) + spell_bonus = int32(spell_bonus * weaponDamagePercentMod); + + // non-weapon damage + int32 bonus = spell_bonus + fixed_bonus; + + // apply to non-weapon bonus weapon total pct effect, weapon total flat effect included in weapon damage + if (bonus) + { + UnitMods unitMod; + switch (m_attackType) + { + default: + case BASE_ATTACK: unitMod = UNIT_MOD_DAMAGE_MAINHAND; break; + case OFF_ATTACK: unitMod = UNIT_MOD_DAMAGE_OFFHAND; break; + case RANGED_ATTACK: unitMod = UNIT_MOD_DAMAGE_RANGED; break; + } + + float weapon_total_pct = m_caster->GetModifierValue(unitMod, TOTAL_PCT); + bonus = int32(bonus * weapon_total_pct); + } + + // + weapon damage with applied weapon% dmg to base weapon damage in call + bonus += int32(m_caster->CalculateDamage(m_attackType, normalized) * weaponDamagePercentMod); + + // total damage + bonus = int32(bonus * totalDamagePercentMod); + + // prevent negative damage + m_damage += uint32(bonus > 0 ? bonus : 0); + + // Hemorrhage + if (m_spellInfo->IsFitToFamily(SPELLFAMILY_ROGUE, UI64LIT(0x0000000002000000))) + { + if (m_caster->GetTypeId() == TYPEID_PLAYER) + ((Player*)m_caster)->AddComboPoints(unitTarget, 1); + } + // Mangle (Cat): CP + else if (m_spellInfo->IsFitToFamily(SPELLFAMILY_DRUID, UI64LIT(0x0000040000000000))) + { + if (m_caster->GetTypeId() == TYPEID_PLAYER) + ((Player*)m_caster)->AddComboPoints(unitTarget, 1); + } +} + +void Spell::EffectThreat(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget || !unitTarget->isAlive() || !m_caster->isAlive()) + return; + + if (!unitTarget->CanHaveThreatList()) + return; + + unitTarget->AddThreat(m_caster, float(damage), false, GetSpellSchoolMask(m_spellInfo), m_spellInfo); +} + +void Spell::EffectHealMaxHealth(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget) + return; + if (!unitTarget->isAlive()) + return; + + uint32 heal = m_caster->GetMaxHealth(); + + m_healing += heal; +} + +void Spell::EffectInterruptCast(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget) + return; + if (!unitTarget->isAlive()) + return; + + // TODO: not all spells that used this effect apply cooldown at school spells + // also exist case: apply cooldown to interrupted cast only and to all spells + for (uint32 i = CURRENT_FIRST_NON_MELEE_SPELL; i < CURRENT_MAX_SPELL; ++i) + { + if (Spell* spell = unitTarget->GetCurrentSpell(CurrentSpellTypes(i))) + { + SpellEntry const* curSpellInfo = spell->m_spellInfo; + // check if we can interrupt spell + if ((curSpellInfo->GetInterruptFlags() & SPELL_INTERRUPT_FLAG_INTERRUPT) && curSpellInfo->GetPreventionType() == SPELL_PREVENTION_TYPE_SILENCE ) + { + unitTarget->ProhibitSpellSchool(GetSpellSchoolMask(curSpellInfo), GetSpellDuration(m_spellInfo)); + unitTarget->InterruptSpell(CurrentSpellTypes(i), false); + } + } + } +} + +void Spell::EffectSummonObjectWild(SpellEffectEntry const* effect) +{ + uint32 gameobject_id = effect->EffectMiscValue; + + GameObject* pGameObj = new GameObject; + + WorldObject* target = focusObject; + if (!target) + target = m_caster; + + float x, y, z; + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) + m_targets.getDestination(x, y, z); + else + m_caster->GetClosePoint(x, y, z, DEFAULT_WORLD_OBJECT_SIZE); + + Map* map = target->GetMap(); + + if (!pGameObj->Create(map->GenerateLocalLowGuid(HIGHGUID_GAMEOBJECT), gameobject_id, map, + m_caster->GetPhaseMask(), x, y, z, target->GetOrientation())) + { + delete pGameObj; + return; + } + + pGameObj->SetRespawnTime(m_duration > 0 ? m_duration / IN_MILLISECONDS : 0); + pGameObj->SetSpellId(m_spellInfo->Id); + + // Wild object not have owner and check clickable by players + map->Add(pGameObj); + + // Store the GO to the caster + m_caster->AddWildGameObject(pGameObj); + + if (pGameObj->GetGoType() == GAMEOBJECT_TYPE_FLAGDROP && m_caster->GetTypeId() == TYPEID_PLAYER) + { + Player* pl = (Player*)m_caster; + BattleGround* bg = ((Player*)m_caster)->GetBattleGround(); + + switch (pGameObj->GetMapId()) + { + case 489: // WS + { + if (bg && bg->GetTypeID() == BATTLEGROUND_WS && bg->GetStatus() == STATUS_IN_PROGRESS) + { + Team team = pl->GetTeam() == ALLIANCE ? HORDE : ALLIANCE; + + ((BattleGroundWS*)bg)->SetDroppedFlagGuid(pGameObj->GetObjectGuid(), team); + } + break; + } + case 566: // EY + { + if (bg && bg->GetTypeID() == BATTLEGROUND_EY && bg->GetStatus() == STATUS_IN_PROGRESS) + { + ((BattleGroundEY*)bg)->SetDroppedFlagGuid(pGameObj->GetObjectGuid()); + } + break; + } + } + } + + pGameObj->SummonLinkedTrapIfAny(); + + if (m_caster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_caster)->AI()) + ((Creature*)m_caster)->AI()->JustSummoned(pGameObj); + if (m_originalCaster && m_originalCaster != m_caster && m_originalCaster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_originalCaster)->AI()) + ((Creature*)m_originalCaster)->AI()->JustSummoned(pGameObj); +} + +void Spell::EffectScriptEffect(SpellEffectEntry const* effect) +{ + // TODO: we must implement hunter pet summon at login there (spell 6962) + switch(m_spellInfo->GetSpellFamilyName()) + { + case SPELLFAMILY_GENERIC: + { + switch (m_spellInfo->Id) + { + case 8856: // Bending Shinbone + { + if (!itemTarget && m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spell_id = 0; + switch (urand(1, 5)) + { + case 1: spell_id = 8854; break; + default: spell_id = 8855; break; + } + + m_caster->CastSpell(m_caster, spell_id, true, NULL); + return; + } + case 17512: // Piccolo of the Flaming Fire + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + unitTarget->HandleEmoteCommand(EMOTE_STATE_DANCE); + return; + } + case 20589: // Escape artist + { + if (!unitTarget) + return; + + unitTarget->RemoveSpellsCausingAura(SPELL_AURA_MOD_ROOT); + unitTarget->RemoveSpellsCausingAura(SPELL_AURA_MOD_DECREASE_SPEED); + return; + } + case 22539: // Shadow Flame (All script effects, not just end ones to + case 22972: // prevent player from dodging the last triggered spell) + case 22975: + case 22976: + case 22977: + case 22978: + case 22979: + case 22980: + case 22981: + case 22982: + case 22983: + case 22984: + case 22985: + { + if (!unitTarget || !unitTarget->isAlive()) + return; + + // Onyxia Scale Cloak + if (unitTarget->GetDummyAura(22683)) + return; + + // Shadow Flame + m_caster->CastSpell(unitTarget, 22682, true); + return; + } + case 24194: // Uther's Tribute + case 24195: // Grom's Tribute + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + uint8 race = m_caster->getRace(); + uint32 spellId = 0; + + switch (m_spellInfo->Id) + { + case 24194: + switch (race) + { + case RACE_HUMAN: spellId = 24105; break; + case RACE_DWARF: spellId = 24107; break; + case RACE_NIGHTELF: spellId = 24108; break; + case RACE_GNOME: spellId = 24106; break; + case RACE_DRAENEI: spellId = 69533; break; + } + break; + case 24195: + switch (race) + { + case RACE_ORC: spellId = 24104; break; + case RACE_UNDEAD: spellId = 24103; break; + case RACE_TAUREN: spellId = 24102; break; + case RACE_TROLL: spellId = 24101; break; + case RACE_BLOODELF: spellId = 69530; break; + } + break; + } + + if (spellId) + m_caster->CastSpell(m_caster, spellId, true); + + return; + } + case 24320: // Poisonous Blood + { + unitTarget->CastSpell(unitTarget, 24321, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + case 24324: // Blood Siphon + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + unitTarget->CastSpell(m_caster, unitTarget->HasAura(24321) ? 24323 : 24322, true); + return; + } + case 24590: // Brittle Armor - need remove one 24575 Brittle Armor aura + unitTarget->RemoveAuraHolderFromStack(24575); + return; + case 24714: // Trick + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + if (roll_chance_i(14)) // Trick (can be different critter models). 14% since below can have 1 of 6 + m_caster->CastSpell(m_caster, 24753, true); + else // Random Costume, 6 different (plus add. for gender) + m_caster->CastSpell(m_caster, 24720, true); + + return; + } + case 24717: // Pirate Costume + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // Pirate Costume (male or female) + m_caster->CastSpell(unitTarget, unitTarget->getGender() == GENDER_MALE ? 24708 : 24709, true); + return; + } + case 24718: // Ninja Costume + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // Ninja Costume (male or female) + m_caster->CastSpell(unitTarget, unitTarget->getGender() == GENDER_MALE ? 24711 : 24710, true); + return; + } + case 24719: // Leper Gnome Costume + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // Leper Gnome Costume (male or female) + m_caster->CastSpell(unitTarget, unitTarget->getGender() == GENDER_MALE ? 24712 : 24713, true); + return; + } + case 24720: // Random Costume + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spellId = 0; + + switch (urand(0, 6)) + { + case 0: + spellId = unitTarget->getGender() == GENDER_MALE ? 24708 : 24709; + break; + case 1: + spellId = unitTarget->getGender() == GENDER_MALE ? 24711 : 24710; + break; + case 2: + spellId = unitTarget->getGender() == GENDER_MALE ? 24712 : 24713; + break; + case 3: + spellId = 24723; + break; + case 4: + spellId = 24732; + break; + case 5: + spellId = unitTarget->getGender() == GENDER_MALE ? 24735 : 24736; + break; + case 6: + spellId = 24740; + break; + } + + m_caster->CastSpell(unitTarget, spellId, true); + return; + } + case 24737: // Ghost Costume + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // Ghost Costume (male or female) + m_caster->CastSpell(unitTarget, unitTarget->getGender() == GENDER_MALE ? 24735 : 24736, true); + return; + } + case 24751: // Trick or Treat + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // Tricked or Treated + unitTarget->CastSpell(unitTarget, 24755, true); + + // Treat / Trick + unitTarget->CastSpell(unitTarget, roll_chance_i(50) ? 24714 : 24715, true); + return; + } + case 25140: // Orb teleport spells + case 25143: + case 25650: + case 25652: + case 29128: + case 29129: + case 35376: + case 35727: + { + if (!unitTarget) + return; + + uint32 spellid; + switch (m_spellInfo->Id) + { + case 25140: spellid = 32568; break; + case 25143: spellid = 32572; break; + case 25650: spellid = 30140; break; + case 25652: spellid = 30141; break; + case 29128: spellid = 32571; break; + case 29129: spellid = 32569; break; + case 35376: spellid = 25649; break; + case 35727: spellid = 35730; break; + default: + return; + } + + unitTarget->CastSpell(unitTarget, spellid, false); + return; + } + case 26004: // Mistletoe + { + if (!unitTarget) + return; + + unitTarget->HandleEmote(EMOTE_ONESHOT_CHEER); + return; + } + case 26137: // Rotate Trigger + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, urand(0, 1) ? 26009 : 26136, true); + return; + } + case 26218: // Mistletoe + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spells[3] = {26206, 26207, 45036}; + + m_caster->CastSpell(unitTarget, spells[urand(0, 2)], true); + return; + } + case 26275: // PX-238 Winter Wondervolt TRAP + { + uint32 spells[4] = {26272, 26157, 26273, 26274}; + + // check presence + for (int j = 0; j < 4; ++j) + if (unitTarget->HasAura(spells[j], EFFECT_INDEX_0)) + return; + + // cast + unitTarget->CastSpell(unitTarget, spells[urand(0, 3)], true); + return; + } + case 26465: // Mercurial Shield - need remove one 26464 Mercurial Shield aura + unitTarget->RemoveAuraHolderFromStack(26464); + return; + case 26656: // Summon Black Qiraji Battle Tank + { + if (!unitTarget) + return; + + // Prevent stacking of mounts + unitTarget->RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); + + // Two separate mounts depending on area id (allows use both in and out of specific instance) + if (unitTarget->GetAreaId() == 3428) + unitTarget->CastSpell(unitTarget, 25863, false); + else + unitTarget->CastSpell(unitTarget, 26655, false); + + return; + } + case 27687: // Summon Bone Minions + { + if (!unitTarget) + return; + + // Spells 27690, 27691, 27692, 27693 are missing from DBC + // So we need to summon creature 16119 manually + float x, y, z; + float angle = unitTarget->GetOrientation(); + for (uint8 i = 0; i < 4; ++i) + { + unitTarget->GetNearPoint(unitTarget, x, y, z, unitTarget->GetObjectBoundingRadius(), INTERACTION_DISTANCE, angle + i * M_PI_F / 2); + unitTarget->SummonCreature(16119, x, y, z, angle, TEMPSUMMON_TIMED_OOC_OR_DEAD_DESPAWN, 10 * MINUTE * IN_MILLISECONDS); + } + return; + } + case 27695: // Summon Bone Mages + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 27696, true); + unitTarget->CastSpell(unitTarget, 27697, true); + unitTarget->CastSpell(unitTarget, 27698, true); + unitTarget->CastSpell(unitTarget, 27699, true); + return; + } + case 28374: // Decimate (Naxxramas: Gluth) + case 54426: // Decimate (Naxxramas: Gluth (spells are identical)) + case 71123: // Decimate (ICC: Precious / Stinky) + { + if (!unitTarget) + return; + + float downToHealthPercent = (m_spellInfo->Id != 71123 ? 5 : effect->CalculateSimpleValue()) * 0.01f; + + int32 damage = unitTarget->GetHealth() - unitTarget->GetMaxHealth() * downToHealthPercent; + if (damage > 0) + m_caster->CastCustomSpell(unitTarget, 28375, &damage, NULL, NULL, true); + return; + } + case 28560: // Summon Blizzard + { + if (!unitTarget) + return; + + m_caster->SummonCreature(16474, unitTarget->GetPositionX(), unitTarget->GetPositionY(), unitTarget->GetPositionZ(), 0.0f, TEMPSUMMON_TIMED_DESPAWN, 30000); + return; + } + case 28859: // Cleansing Flames + case 29126: // Cleansing Flames (Darnassus) + case 29135: // Cleansing Flames (Ironforge) + case 29136: // Cleansing Flames (Orgrimmar) + case 29137: // Cleansing Flames (Stormwind) + case 29138: // Cleansing Flames (Thunder Bluff) + case 29139: // Cleansing Flames (Undercity) + case 46671: // Cleansing Flames (Exodar) + case 46672: // Cleansing Flames (Silvermoon) + { + // Cleanse all magic, curse, disease and poison + if (!unitTarget) + return; + + unitTarget->RemoveAurasWithDispelType(DISPEL_MAGIC); + unitTarget->RemoveAurasWithDispelType(DISPEL_CURSE); + unitTarget->RemoveAurasWithDispelType(DISPEL_DISEASE); + unitTarget->RemoveAurasWithDispelType(DISPEL_POISON); + + return; + } + case 29395: // Break Kaliri Egg + { + uint32 creature_id = 0; + uint32 rand = urand(0, 99); + + if (rand < 10) + creature_id = 17034; + else if (rand < 60) + creature_id = 17035; + else + creature_id = 17039; + + if (WorldObject* pSource = GetAffectiveCasterObject()) + pSource->SummonCreature(creature_id, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_TIMED_OOC_OR_DEAD_DESPAWN, 120 * IN_MILLISECONDS); + return; + } + case 29830: // Mirren's Drinking Hat + { + uint32 item = 0; + switch (urand(1, 6)) + { + case 1: + case 2: + case 3: + item = 23584; break; // Loch Modan Lager + case 4: + case 5: + item = 23585; break; // Stouthammer Lite + case 6: + item = 23586; break; // Aerie Peak Pale Ale + } + + if (item) + DoCreateItem(effect,item); + + break; + } + case 30541: // Blaze + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 30542, true); + break; + } + case 30769: // Pick Red Riding Hood + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // cast Little Red Riding Hood + m_caster->CastSpell(unitTarget, 30768, true); + break; + } + case 30835: // Infernal Relay + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 30836, true, NULL, NULL, m_caster->GetObjectGuid()); + break; + } + case 30918: // Improved Sprint + { + if (!unitTarget) + return; + + // Removes snares and roots. + unitTarget->RemoveAurasAtMechanicImmunity(IMMUNE_TO_ROOT_AND_SNARE_MASK, 30918, true); + break; + } + case 37142: // Karazhan - Chess NPC Action: Melee Attack: Conjured Water Elemental + case 37143: // Karazhan - Chess NPC Action: Melee Attack: Charger + case 37147: // Karazhan - Chess NPC Action: Melee Attack: Human Cleric + case 37149: // Karazhan - Chess NPC Action: Melee Attack: Human Conjurer + case 37150: // Karazhan - Chess NPC Action: Melee Attack: King Llane + case 37220: // Karazhan - Chess NPC Action: Melee Attack: Summoned Daemon + case 32227: // Karazhan - Chess NPC Action: Melee Attack: Footman + case 32228: // Karazhan - Chess NPC Action: Melee Attack: Grunt + case 37337: // Karazhan - Chess NPC Action: Melee Attack: Orc Necrolyte + case 37339: // Karazhan - Chess NPC Action: Melee Attack: Orc Wolf + case 37345: // Karazhan - Chess NPC Action: Melee Attack: Orc Warlock + case 37348: // Karazhan - Chess NPC Action: Melee Attack: Warchief Blackhand + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, 32247, true); + return; + } + case 32301: // Ping Shirrak + { + if (!unitTarget) + return; + + // Cast Focus fire on caster + unitTarget->CastSpell(m_caster, 32300, true); + return; + } + case 33676: // Incite Chaos + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + m_caster->CastSpell(unitTarget, 33684, true); + return; + } + case 35865: // Summon Nether Vapor + { + if (!unitTarget) + return; + + float x, y, z; + for (uint8 i = 0; i < 4; ++i) + { + m_caster->GetNearPoint(m_caster, x, y, z, 0.0f, INTERACTION_DISTANCE, M_PI_F * .5f * i + M_PI_F * .25f); + m_caster->SummonCreature(21002, x, y, z, 0, TEMPSUMMON_TIMED_OOC_OR_DEAD_DESPAWN, 30000); + } + return; + } + case 37431: // Spout + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, urand(0, 1) ? 37429 : 37430, true); + return; + } + case 37775: // Karazhan - Chess NPC Action - Poison Cloud + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, 37469, true); + return; + } + case 37824: // Karazhan - Chess NPC Action - Shadow Mend + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, 37456, true); + return; + } + case 38358: // Tidal Surge + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 38353, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + case 39338: // Karazhan - Chess, Medivh CHEAT: Hand of Medivh, Target Horde + case 39342: // Karazhan - Chess, Medivh CHEAT: Hand of Medivh, Target Alliance + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, 39339, true); + return; + } + case 39341: // Karazhan - Chess, Medivh CHEAT: Fury of Medivh, Target Horde + case 39344: // Karazhan - Chess, Medivh CHEAT: Fury of Medivh, Target Alliance + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + return; + } + case 41055: // Copy Weapon + { + if (m_caster->GetTypeId() != TYPEID_UNIT || !unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + if (Item* pItem = ((Player*)unitTarget)->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND)) + { + ((Creature*)m_caster)->SetVirtualItem(VIRTUAL_ITEM_SLOT_0, pItem->GetEntry()); + + // Unclear what this spell should do + unitTarget->CastSpell(m_caster, effect->CalculateSimpleValue(), true); + } + + return; + } + case 41126: // Flame Crash + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 41131, true); + break; + } + case 43365: // The Cleansing: Shrine Cast + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // Script Effect Player Cast Mirror Image + m_caster->CastSpell(m_caster, 50217, true); + return; + } + case 43375: // Mixing Vrykul Blood + { + if (!unitTarget) + return; + + uint32 triggeredSpell[] = {43376, 43378, 43970, 43377}; + + unitTarget->CastSpell(unitTarget, triggeredSpell[urand(0, 3)], true); + return; + } + case 44364: // Rock Falcon Primer + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // Are there anything special with this, a random chance or condition? + // Feeding Rock Falcon + unitTarget->CastSpell(unitTarget, effect->CalculateSimpleValue(), true, NULL, NULL, unitTarget->GetObjectGuid(), m_spellInfo); + return; + } + case 44455: // Character Script Effect Reverse Cast + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + Creature* pTarget = (Creature*)unitTarget; + + if (const SpellEntry *pSpell = sSpellStore.LookupEntry(effect->CalculateSimpleValue())) + { + // if we used item at least once... + if (pTarget->IsTemporarySummon() && int32(pTarget->GetEntry()) == pSpell->GetEffectMiscValue(SpellEffectIndex(effect->EffectIndex))) + { + TemporarySummon* pSummon = (TemporarySummon*)pTarget; + + // can only affect "own" summoned + if (pSummon->GetSummonerGuid() == m_caster->GetObjectGuid()) + { + if (pTarget->hasUnitState(UNIT_STAT_ROAMING | UNIT_STAT_ROAMING_MOVE)) + pTarget->GetMotionMaster()->MovementExpired(); + + // trigger cast of quest complete script (see code for this spell below) + pTarget->CastSpell(pTarget, 44462, true); + + pTarget->GetMotionMaster()->MovePoint(0, m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ()); + } + + return; + } + + // or if it is first time used item, cast summon and despawn the target + m_caster->CastSpell(pTarget, pSpell, true); + pTarget->ForcedDespawn(); + + // TODO: here we should get pointer to the just summoned and make it move. + // without, it will be one extra use of quest item + } + + return; + } + case 44462: // Cast Quest Complete on Master + { + if (m_caster->GetTypeId() != TYPEID_UNIT) + return; + + Creature* pQuestCow = NULL; + + float range = 20.0f; + + // search for a reef cow nearby + MaNGOS::NearestCreatureEntryWithLiveStateInObjectRangeCheck u_check(*m_caster, 24797, true, false, range); + MaNGOS::CreatureLastSearcher searcher(pQuestCow, u_check); + + Cell::VisitGridObjects(m_caster, searcher, range); + + // no cows found, so return + if (!pQuestCow) + return; + + if (!((Creature*)m_caster)->IsTemporarySummon()) + return; + + if (const SpellEntry *pSpell = sSpellStore.LookupEntry(effect->CalculateSimpleValue())) + { + TemporarySummon* pSummon = (TemporarySummon*)m_caster; + + // all ok, so make summoner cast the quest complete + if (Unit* pSummoner = pSummon->GetSummoner()) + pSummoner->CastSpell(pSummoner, pSpell, true); + } + + return; + } + case 44876: // Force Cast - Portal Effect: Sunwell Isle + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 44870, true); + break; + } + case 44811: // Spectral Realm + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // If the player can't be teleported, send him a notification + if (unitTarget->HasAura(44867)) + { + ((Player*)unitTarget)->GetSession()->SendNotification(LANG_FAIL_ENTER_SPECTRAL_REALM); + return; + } + + // Teleport target to the spectral realm, add debuff and force faction + unitTarget->CastSpell(unitTarget, 46019, true); + unitTarget->CastSpell(unitTarget, 46021, true); + unitTarget->CastSpell(unitTarget, 44845, true); + unitTarget->CastSpell(unitTarget, 44852, true); + return; + } + case 45141: // Burn + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 46394, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + case 45151: // Burn + { + if (!unitTarget || unitTarget->HasAura(46394)) + return; + + // Make the burn effect jump to another friendly target + unitTarget->CastSpell(unitTarget, 46394, true); + return; + } + case 45185: // Stomp + { + if (!unitTarget) + return; + + // Remove the burn effect + unitTarget->RemoveAurasDueToSpell(46394); + return; + } + case 45204: // Clone Me! + { + if (!unitTarget) + return; + + unitTarget->CastSpell(m_caster, effect->CalculateSimpleValue(), true); + return; + } + case 45206: // Copy Off-hand Weapon + { + if (m_caster->GetTypeId() != TYPEID_UNIT || !unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + if (Item* pItem = ((Player*)unitTarget)->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND)) + { + ((Creature*)m_caster)->SetVirtualItem(VIRTUAL_ITEM_SLOT_1, pItem->GetEntry()); + + // Unclear what this spell should do + unitTarget->CastSpell(m_caster, effect->CalculateSimpleValue(), true); + } + + return; + } + case 45235: // Blaze + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 45236, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + case 45260: // Karazhan - Chess - Force Player to Kill Bunny + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + unitTarget->CastSpell(unitTarget, 45259, true); + return; + } + case 45668: // Ultra-Advanced Proto-Typical Shortening Blaster + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + if (roll_chance_i(25)) // chance unknown, using 25 + return; + + static uint32 const spellPlayer[5] = + { + 45674, // Bigger! + 45675, // Shrunk + 45678, // Yellow + 45682, // Ghost + 45684 // Polymorph + }; + + static uint32 const spellTarget[5] = + { + 45673, // Bigger! + 45672, // Shrunk + 45677, // Yellow + 45681, // Ghost + 45683 // Polymorph + }; + + m_caster->CastSpell(m_caster, spellPlayer[urand(0, 4)], true); + unitTarget->CastSpell(unitTarget, spellTarget[urand(0, 4)], true); + + return; + } + case 45691: // Magnataur On Death 1 + { + // assuming caster is creature, if not, then return + if (m_caster->GetTypeId() != TYPEID_UNIT) + return; + + Player* pPlayer = ((Creature*)m_caster)->GetOriginalLootRecipient(); + + if (!pPlayer) + return; + + if (pPlayer->HasAura(45674) || pPlayer->HasAura(45675) || pPlayer->HasAura(45678) || pPlayer->HasAura(45682) || pPlayer->HasAura(45684)) + pPlayer->CastSpell(pPlayer, 45686, true); + + m_caster->CastSpell(m_caster, 45685, true); + + return; + } + case 45713: // Naked Caravan Guard - Master Transform + { + if (m_caster->GetTypeId() != TYPEID_UNIT) + return; + + const CreatureInfo* cTemplate = NULL; + + switch (m_caster->GetEntry()) + { + case 25342: cTemplate = ObjectMgr::GetCreatureTemplate(25340); break; + case 25343: cTemplate = ObjectMgr::GetCreatureTemplate(25341); break; + } + + if (!cTemplate) + return; + + uint32 display_id = 0; + + // Spell is designed to be used in creature addon. + // This makes it possible to set proper model before adding to map. + // For later, spell is used in gossip (with following despawn, + // so addon can reload the default model and data again). + + // It should be noted that additional spell id's have been seen in relation to this spell, but + // those does not exist in client (45701 (regular spell), 45705-45712 (auras), 45459-45460 (auras)). + // We can assume 45705-45712 are transform auras, used instead of hard coded models in the below code. + + // not in map yet OR no npc flags yet (restored after LoadCreatureAddon for respawn cases) + if (!m_caster->IsInMap(m_caster) || m_caster->GetUInt32Value(UNIT_NPC_FLAGS) == UNIT_NPC_FLAG_NONE) + { + display_id = Creature::ChooseDisplayId(cTemplate); + ((Creature*)m_caster)->LoadEquipment(((Creature*)m_caster)->GetEquipmentId()); + } + else + { + m_caster->SetUInt32Value(UNIT_NPC_FLAGS, cTemplate->npcflag); + ((Creature*)m_caster)->SetVirtualItem(VIRTUAL_ITEM_SLOT_0, 0); + ((Creature*)m_caster)->SetVirtualItem(VIRTUAL_ITEM_SLOT_1, 0); + + switch (m_caster->GetDisplayId()) + { + case 23246: display_id = 23245; break; + case 23247: display_id = 23250; break; + case 23248: display_id = 23251; break; + case 23249: display_id = 23252; break; + case 23124: display_id = 23253; break; + case 23125: display_id = 23254; break; + case 23126: display_id = 23255; break; + case 23127: display_id = 23256; break; + } + } + + m_caster->SetDisplayId(display_id); + return; + } + case 45714: // Fog of Corruption (caster inform) + { + if (!unitTarget || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + unitTarget->CastSpell(m_caster, effect->CalculateSimpleValue(), true); + return; + } + case 45717: // Fog of Corruption (player buff) + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + unitTarget->CastSpell(unitTarget, 45726, true); + return; + } + case 45785: // Sinister Reflection Clone + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + return; + } + case 45833: // Power of the Blue Flight + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 45836, true); + return; + } + case 45892: // Sinister Reflection + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // Summon 4 clones of the same player + for (uint8 i = 0; i < 4; ++i) + unitTarget->CastSpell(unitTarget, 45891, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + case 45918: // Soul Sever + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER || !unitTarget->HasAura(45717)) + return; + + // kill all charmed targets + unitTarget->CastSpell(unitTarget, 45917, true); + return; + } + case 45958: // Signal Alliance + { + // "escort" aura not present, so let nothing happen + if (!m_caster->HasAura(effect->CalculateSimpleValue())) + return; + // "escort" aura is present so break; and let DB table dbscripts_on_spell be used and process further. + else + break; + } + case 46203: // Goblin Weather Machine + { + if (!unitTarget) + return; + + uint32 spellId = 0; + switch (rand() % 4) + { + case 0: spellId = 46740; break; + case 1: spellId = 46739; break; + case 2: spellId = 46738; break; + case 3: spellId = 46736; break; + } + unitTarget->CastSpell(unitTarget, spellId, true); + break; + } + case 46642: // 5,000 Gold + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)unitTarget)->ModifyMoney(5000 * GOLD); + break; + } + case 47097: // Surge Needle Teleporter + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + if (unitTarget->GetAreaId() == 4157) + unitTarget->CastSpell(unitTarget, 47324, true); + else if (unitTarget->GetAreaId() == 4156) + unitTarget->CastSpell(unitTarget, 47325, true); + + break; + } + case 47311: // Quest - Jormungar Explosion Spell Spawner + { + // Summons npc's. They are expected to summon GO from 47315 + // but there is no way to get the summoned, to trigger a spell + // cast (workaround can be done with ai script). + + // Quest - Jormungar Explosion Summon Object + for (int i = 0; i < 2; ++i) + m_caster->CastSpell(m_caster, 47309, true); + + for (int i = 0; i < 2; ++i) + m_caster->CastSpell(m_caster, 47924, true); + + for (int i = 0; i < 2; ++i) + m_caster->CastSpell(m_caster, 47925, true); + + return; + } + case 47393: // The Focus on the Beach: Quest Completion Script + { + if (!unitTarget) + return; + + // Ley Line Information + unitTarget->RemoveAurasDueToSpell(47391); + return; + } + case 47615: // Atop the Woodlands: Quest Completion Script + { + if (!unitTarget) + return; + + // Ley Line Information + unitTarget->RemoveAurasDueToSpell(47473); + return; + } + case 47638: // The End of the Line: Quest Completion Script + { + if (!unitTarget) + return; + + // Ley Line Information + unitTarget->RemoveAurasDueToSpell(47636); + return; + } + case 47703: // Unholy Union + { + m_caster->CastSpell(m_caster, 50254, true); + return; + } + case 47724: // Frost Draw + { + m_caster->CastSpell(m_caster, 50239, true); + return; + } + case 47958: // Crystal Spikes + case 57083: // Crystal Spikes (h2) + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 47954, true); + unitTarget->CastSpell(unitTarget, 47955, true); + unitTarget->CastSpell(unitTarget, 47956, true); + unitTarget->CastSpell(unitTarget, 47957, true); + return; + } + case 48590: // Avenging Spirits + { + if (!unitTarget) + return; + + // Summon 4 spirits summoners + unitTarget->CastSpell(unitTarget, 48586, true); + unitTarget->CastSpell(unitTarget, 48587, true); + unitTarget->CastSpell(unitTarget, 48588, true); + unitTarget->CastSpell(unitTarget, 48589, true); + return; + } + case 48603: // High Executor's Branding Iron + // Torture the Torturer: High Executor's Branding Iron Impact + unitTarget->CastSpell(unitTarget, 48614, true); + return; + case 48724: // The Denouncement: Commander Jordan On Death + case 48726: // The Denouncement: Lead Cannoneer Zierhut On Death + case 48728: // The Denouncement: Blacksmith Goodman On Death + case 48730: // The Denouncement: Stable Master Mercer On Death + { + // Compelled + if (!unitTarget || !m_caster->HasAura(48714)) + return; + + unitTarget->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + return; + } + // Gender spells + case 48762: // A Fall from Grace: Scarlet Raven Priest Image - Master + case 45759: // Warsong Orc Disguise + case 69672: // Sunreaver Disguise + case 69673: // Silver Covenant Disguise + { + if (!unitTarget) + return; + + uint8 gender = unitTarget->getGender(); + uint32 spellId; + switch (m_spellInfo->Id) + { + case 48762: spellId = (gender == GENDER_MALE ? 48763 : 48761); break; + case 45759: spellId = (gender == GENDER_MALE ? 45760 : 45762); break; + case 69672: spellId = (gender == GENDER_MALE ? 70974 : 70973); break; + case 69673: spellId = (gender == GENDER_MALE ? 70972 : 70971); break; + default: return; + } + unitTarget->CastSpell(unitTarget, spellId, true); + return; + } + case 48810: // Death's Door + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // Spell effect order will summon creature first and then apply invisibility to caster. + // This result in that summoner/summoned can not see each other and that is not expected. + // Aura from 48814 can be used as a hack from creature_addon, but we can not get the + // summoned to cast this from this spell effect since we have no way to get pointer to creature. + // Most proper would be to summon to same visibility mask as summoner, and not use spell at all. + + // Binding Life + m_caster->CastSpell(m_caster, 48809, true); + + // After (after: meaning creature does not have auras at creation) + // creature is summoned and visible for player in map, it is expected to + // gain two auras. First from 29266(aura slot0) and then from 48808(aura slot1). + // We have no pointer to summoned, so only 48808 is possible from this spell effect. + + // Binding Death + m_caster->CastSpell(m_caster, 48808, true); + return; + } + case 48811: // Despawn Forgotten Soul + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + if (!((Creature*)unitTarget)->IsTemporarySummon()) + return; + + TemporarySummon* pSummon = (TemporarySummon*)unitTarget; + + Unit::AuraList const& images = unitTarget->GetAurasByType(SPELL_AURA_MIRROR_IMAGE); + + if (images.empty()) + return; + + Unit* pCaster = images.front()->GetCaster(); + Unit* pSummoner = unitTarget->GetMap()->GetUnit(pSummon->GetSummonerGuid()); + + if (pSummoner && pSummoner == pCaster) + pSummon->UnSummon(); + + return; + } + case 48917: // Who Are They: Cast from Questgiver + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // Male Shadowy Disguise / Female Shadowy Disguise + unitTarget->CastSpell(unitTarget, unitTarget->getGender() == GENDER_MALE ? 38080 : 38081, true); + // Shadowy Disguise + unitTarget->CastSpell(unitTarget, 32756, true); + return; + } + case 49380: // Consume + case 59803: // Consume (heroic) + { + if (!unitTarget) + return; + + // Each target hit buffs the caster + unitTarget->CastSpell(m_caster, m_spellInfo->Id == 49380 ? 49381 : 59805, true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + case 49405: // Invader Taunt Trigger + { + if (!unitTarget) + return; + + unitTarget->CastSpell(m_caster, effect->CalculateSimpleValue(), true); + return; + } + case 50217: // The Cleansing: Script Effect Player Cast Mirror Image + { + // Summon Your Inner Turmoil + m_caster->CastSpell(m_caster, 50167, true); + + // Spell 50218 has TARGET_SCRIPT, but other wild summons near may exist, and then target can become wrong + // Only way to make this safe is to get the actual summoned by m_caster + + // Your Inner Turmoil's Mirror Image Aura + m_caster->CastSpell(m_caster, 50218, true); + + return; + } + case 50218: // The Cleansing: Your Inner Turmoil's Mirror Image Aura + { + if (!m_originalCaster || m_originalCaster->GetTypeId() != TYPEID_PLAYER || !unitTarget) + return; + + // determine if and what weapons can be copied + switch(effect->EffectIndex) + { + case EFFECT_INDEX_1: + if (((Player*)m_originalCaster)->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND)) + unitTarget->CastSpell(m_originalCaster, effect->CalculateSimpleValue(), true); + + return; + case EFFECT_INDEX_2: + if (((Player*)m_originalCaster)->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND)) + unitTarget->CastSpell(m_originalCaster, effect->CalculateSimpleValue(), true); + + return; + default: + return; + } + return; + } + case 50238: // The Cleansing: Your Inner Turmoil's On Death Cast on Master + { + if (m_caster->GetTypeId() != TYPEID_UNIT) + return; + + if (((Creature*)m_caster)->IsTemporarySummon()) + { + TemporarySummon* pSummon = (TemporarySummon*)m_caster; + + if (pSummon->GetSummonerGuid().IsPlayer()) + { + if (Player* pSummoner = sObjectMgr.GetPlayer(pSummon->GetSummonerGuid())) + pSummoner->CastSpell(pSummoner, effect->CalculateSimpleValue(), true); + } + } + + return; + } + case 50252: // Blood Draw + { + m_caster->CastSpell(m_caster, 50250, true); + return; + } + case 50255: // Poisoned Spear + case 59331: // Poisoned Spear (heroic) + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, effect->CalculateSimpleValue(), true, NULL, NULL, m_originalCasterGUID); + return; + } + case 50439: // Script Cast Summon Image of Drakuru 05 + { + // TODO: check if summon already exist, if it does in this instance, return. + + // Summon Drakuru + m_caster->CastSpell(m_caster, 50446, true); + return; + } + case 50630: // Eject All Passengers + { + m_caster->RemoveSpellsCausingAura(SPELL_AURA_CONTROL_VEHICLE); + return; + } + case 50725: // Vigilance - remove cooldown on Taunt + { + Unit* caster = GetAffectiveCaster(); + if (!caster || caster->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)caster)->RemoveSpellCategoryCooldown(82, true); + return; + } + case 50742: // Ooze Combine + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + m_caster->CastSpell(unitTarget, 50747, true); + ((Creature*)m_caster)->ForcedDespawn(); + return; + } + case 50810: // Shatter + case 61546: // Shatter (h) + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + if (!unitTarget->HasAura(50812)) + return; + + unitTarget->RemoveAurasDueToSpell(50812); + unitTarget->CastSpell(unitTarget, m_spellInfo->Id == 50810 ? 50811 : 61547 , true, NULL, NULL, m_caster->GetObjectGuid()); + return; + } + case 50894: // Zul'Drak Rat + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + if (SpellAuraHolder* pHolder = unitTarget->GetSpellAuraHolder(m_spellInfo->Id)) + { + if (pHolder->GetStackAmount() + 1 >= m_spellInfo->GetStackAmount()) + { + // Gluttonous Lurkers: Summon Gorged Lurking Basilisk + unitTarget->CastSpell(m_caster, 50928, true); + ((Creature*)unitTarget)->ForcedDespawn(1); + } + } + + return; + } + case 51519: // Death Knight Initiate Visual + { + if (!unitTarget) + return; + + uint32 spellId = 0; + + bool isMale = unitTarget->getGender() == GENDER_MALE; + switch (unitTarget->getRace()) + { + case RACE_HUMAN: spellId = isMale ? 51520 : 51534; break; + case RACE_DWARF: spellId = isMale ? 51538 : 51537; break; + case RACE_NIGHTELF: spellId = isMale ? 51535 : 51536; break; + case RACE_GNOME: spellId = isMale ? 51539 : 51540; break; + case RACE_DRAENEI: spellId = isMale ? 51541 : 51542; break; + case RACE_ORC: spellId = isMale ? 51543 : 51544; break; + case RACE_UNDEAD: spellId = isMale ? 51549 : 51550; break; + case RACE_TAUREN: spellId = isMale ? 51547 : 51548; break; + case RACE_TROLL: spellId = isMale ? 51546 : 51545; break; + case RACE_BLOODELF: spellId = isMale ? 51551 : 51552; break; + default: + return; + } + + unitTarget->CastSpell(unitTarget, spellId, true); + return; + } + case 51770: // Emblazon Runeblade + { + Unit* caster = GetAffectiveCaster(); + if (!caster) + return; + + caster->CastSpell(caster, damage, false); + break; + } + case 51864: // Player Summon Nass + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // Summon Nass + if (const SpellEntry* pSpell = sSpellStore.LookupEntry(51865)) + { + // Only if he is not already there + if (!m_caster->FindGuardianWithEntry(pSpell->GetEffectMiscValue(EFFECT_INDEX_0))) + { + m_caster->CastSpell(m_caster, pSpell, true); + + if (Pet* pPet = m_caster->FindGuardianWithEntry(pSpell->GetEffectMiscValue(EFFECT_INDEX_0))) + { + // Nass Periodic Say aura + pPet->CastSpell(pPet, 51868, true); + } + } + } + return; + } + case 51889: // Quest Accept Summon Nass + { + // This is clearly for quest accept, is spell 51864 then for gossip and does pretty much the same thing? + // Just "jumping" to what may be the "gossip-spell" for now, doing the same thing + m_caster->CastSpell(m_caster, 51864, true); + return; + } + case 51904: // Summon Ghouls On Scarlet Crusade + { + if (!unitTarget) + return; + + // cast Summon Ghouls On Scarlet Crusade + float x, y, z; + m_targets.getDestination(x, y, z); + unitTarget->CastSpell(x, y, z, 54522, true, NULL, NULL, m_originalCasterGUID); + return; + } + case 51910: // Kickin' Nass: Quest Completion + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + if (const SpellEntry* pSpell = sSpellStore.LookupEntry(51865)) + { + // Is this all to be done at completion? + if (Pet* pPet = m_caster->FindGuardianWithEntry(pSpell->GetEffectMiscValue(EFFECT_INDEX_0))) + pPet->Unsummon(PET_SAVE_AS_DELETED, m_caster); + } + return; + } + case 52479: // Gift of the Harvester + { + if (m_caster->GetTypeId() != TYPEID_PLAYER || !unitTarget) + return; + + // Each ghoul casts 52500 onto player, so use number of auras as check + Unit::SpellAuraHolderConstBounds bounds = m_caster->GetSpellAuraHolderBounds(52500); + uint32 summonedGhouls = std::distance(bounds.first, bounds.second); + + m_caster->CastSpell(unitTarget->GetPositionX(), unitTarget->GetPositionY(), unitTarget->GetPositionZ(), urand(0, 2) || summonedGhouls >= 5 ? 52505 : 52490, true); + return; + } + case 52555: // Dispel Scarlet Ghoul Credit Counter + { + if (!unitTarget) + return; + + unitTarget->RemoveAurasByCasterSpell(effect->CalculateSimpleValue(), m_caster->GetObjectGuid()); + return; + } + case 52694: // Recall Eye of Acherus + { + if (!m_caster || m_caster->GetTypeId() != TYPEID_UNIT) + return; + + Unit* charmer = m_caster->GetCharmer(); + if (!charmer || charmer->GetTypeId() != TYPEID_PLAYER) + return; + + charmer->RemoveAurasDueToSpell(51923); + + // HACK ALERT + // Replace with Spell Interrupting, when casting spells properly is possible in mangos + //charmer->InterruptNonMeleeSpells(true); + + Player* player = (Player*)charmer; + Creature* possessed = (Creature*)m_caster; + player->RemoveAurasDueToSpell(51852); + + player->SetCharm(NULL); + player->SetClientControl(possessed, 0); + player->SetMover(NULL); + player->GetCamera().ResetView(); + player->RemovePetActionBar(); + + possessed->clearUnitState(UNIT_STAT_CONTROLLED); + possessed->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + possessed->SetCharmerGuid(ObjectGuid()); + possessed->ForcedDespawn(); + + return; + } + case 52751: // Death Gate + { + if (!unitTarget || unitTarget->getClass() != CLASS_DEATH_KNIGHT) + return; + + // triggered spell is stored in m_spellInfo->EffectBasePoints[0] + unitTarget->CastSpell(unitTarget, damage, false); + break; + } + case 52941: // Song of Cleansing + { + uint32 spellId = 0; + + switch (m_caster->GetAreaId()) + { + case 4385: spellId = 52954; break; // Bittertide Lake + case 4290: spellId = 52958; break; // River's Heart + case 4388: spellId = 52959; break; // Wintergrasp River + } + + if (spellId) + m_caster->CastSpell(m_caster, spellId, true); + + break; + } + case 54182: // An End to the Suffering: Quest Completion Script + { + if (!unitTarget) + return; + + // Remove aura (Mojo of Rhunok) given at quest accept / gossip + unitTarget->RemoveAurasDueToSpell(51967); + return; + } + case 54581: // Mammoth Explosion Spell Spawner + { + if (m_caster->GetTypeId() != TYPEID_UNIT) + return; + + // Summons misc npc's. They are expected to summon GO from 54625 + // but there is no way to get the summoned, to trigger a spell + // cast (workaround can be done with ai script). + + // Quest - Mammoth Explosion Summon Object + for (int i = 0; i < 2; ++i) + m_caster->CastSpell(m_caster, 54623, true); + + for (int i = 0; i < 2; ++i) + m_caster->CastSpell(m_caster, 54627, true); + + for (int i = 0; i < 2; ++i) + m_caster->CastSpell(m_caster, 54628, true); + + // Summon Main Mammoth Meat + m_caster->CastSpell(m_caster, 57444, true); + return; + } + case 54436: // Demonic Empowerment (succubus Vanish effect) + { + if (!unitTarget) + return; + + unitTarget->RemoveSpellsCausingAura(SPELL_AURA_MOD_ROOT); + unitTarget->RemoveSpellsCausingAura(SPELL_AURA_MOD_DECREASE_SPEED); + unitTarget->RemoveSpellsCausingAura(SPELL_AURA_MOD_STALKED); + unitTarget->RemoveSpellsCausingAura(SPELL_AURA_MOD_STUN); + return; + } + case 55693: // Remove Collapsing Cave Aura + { + if (!unitTarget) + return; + + unitTarget->RemoveAurasDueToSpell(effect->CalculateSimpleValue()); + break; + } + case 56072: // Ride Red Dragon Buddy + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + break; + } + case 57082: // Crystal Spikes (h1) + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 57077, true); + unitTarget->CastSpell(unitTarget, 57078, true); + unitTarget->CastSpell(unitTarget, 57080, true); + unitTarget->CastSpell(unitTarget, 57081, true); + return; + } + case 57337: // Great Feast + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 58067, true); + break; + } + case 57397: // Fish Feast + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 45548, true); + unitTarget->CastSpell(unitTarget, 57073, true); + unitTarget->CastSpell(unitTarget, 57398, true); + break; + } + case 58466: // Gigantic Feast + case 58475: // Small Feast + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 57085, true); + break; + } + case 58418: // Portal to Orgrimmar + case 58420: // Portal to Stormwind + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER || effect->EffectIndex != EFFECT_INDEX_0) + return; + + uint32 spellID = m_spellInfo->CalculateSimpleValue(EFFECT_INDEX_0); + uint32 questID = m_spellInfo->CalculateSimpleValue(EFFECT_INDEX_1); + + if (((Player*)unitTarget)->GetQuestStatus(questID) == QUEST_STATUS_COMPLETE && !((Player*)unitTarget)->GetQuestRewardStatus(questID)) + unitTarget->CastSpell(unitTarget, spellID, true); + + return; + } + case 59317: // Teleporting + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // return from top + if (((Player*)unitTarget)->GetAreaId() == 4637) + unitTarget->CastSpell(unitTarget, 59316, true); + // teleport atop + else + unitTarget->CastSpell(unitTarget, 59314, true); + + return; + } // random spell learn instead placeholder + case 59789: // Oracle Ablutions + { + if (!unitTarget) + return; + + switch (unitTarget->getPowerType()) + { + case POWER_RUNIC_POWER: + { + unitTarget->CastSpell(unitTarget, 59812, true); + break; + } + case POWER_MANA: + { + int32 manapool = unitTarget->GetMaxPower(POWER_MANA) * 0.05; + unitTarget->CastCustomSpell(unitTarget, 59813, &manapool, NULL, NULL, true); + break; + } + case POWER_RAGE: + { + unitTarget->CastSpell(unitTarget, 59814, true); + break; + } + case POWER_ENERGY: + { + unitTarget->CastSpell(unitTarget, 59815, true); + break; + } + // These are not restored + case POWER_FOCUS: + case POWER_RUNE: + case POWER_HEALTH: + break; + } + return; + } + case 60893: // Northrend Alchemy Research + case 61177: // Northrend Inscription Research + case 61288: // Minor Inscription Research + case 61756: // Northrend Inscription Research (FAST QA VERSION) + case 64323: // Book of Glyph Mastery + { + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + // learn random explicit discovery recipe (if any) + if (uint32 discoveredSpell = GetExplicitDiscoverySpell(m_spellInfo->Id, (Player*)m_caster)) + ((Player*)m_caster)->learnSpell(discoveredSpell, false); + + return; + } + case 62042: // Stormhammer + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + unitTarget->CastSpell(unitTarget, 62470, true); + unitTarget->CastSpell(m_caster, 64909, true); + return; + } + case 62381: // Chill + { + if (!unitTarget) + return; + + unitTarget->RemoveAurasDueToSpell(62373); + unitTarget->CastSpell(unitTarget, 62382, true); + return; + } + case 62488: // Activate Construct + { + if (!unitTarget || !unitTarget->HasAura(62468)) + return; + + unitTarget->RemoveAurasDueToSpell(62468); + unitTarget->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + unitTarget->CastSpell(unitTarget, 64474, true); + + if (m_caster->getVictim()) + ((Creature*)unitTarget)->AI()->AttackStart(m_caster->getVictim()); + return; + } + case 62524: // Attuned to Nature 2 Dose Reduction + case 62525: // Attuned to Nature 10 Dose Reduction + case 62521: // Attuned to Nature 25 Dose Reduction + { + if (!unitTarget) + return; + + uint32 numStacks = 0; + + switch (m_spellInfo->Id) + { + case 62524: numStacks = 2; break; + case 62525: numStacks = 10; break; + case 62521: numStacks = 25; break; + }; + + uint32 spellId = effect->CalculateSimpleValue(); + unitTarget->RemoveAuraHolderFromStack(spellId, numStacks); + return; + } + case 62678: // Summon Allies of Nature + { + const uint32 randSpells[] = + { + 62685, // Summon Wave - 1 Mob + 62686, // Summon Wave - 3 Mob + 62688, // Summon Wave - 10 Mob + }; + + m_caster->CastSpell(m_caster, randSpells[urand(0, countof(randSpells) - 1)], true); + return; + } + case 62688: // Summon Wave - 10 Mob + { + uint32 spellId = effect->CalculateSimpleValue(); + + for (uint32 i = 0; i < 10; ++i) + m_caster->CastSpell(m_caster, spellId, true); + + return; + } + case 62707: // Grab + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + unitTarget->CastSpell(unitTarget, 62708, true); + return; + } + case 63633: // Summon Rubble + { + if (!unitTarget) + return; + + for (uint8 i = 0; i < 5; ++i) + unitTarget->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + return; + } + case 64456: // Feral Essence Application Removal + { + if (!unitTarget) + return; + + uint32 spellId = effect->CalculateSimpleValue(); + unitTarget->RemoveAuraHolderFromStack(spellId); + return; + } + case 64475: // Strength of the Creator + { + if (!unitTarget) + return; + + unitTarget->RemoveAuraHolderFromStack(64473); + return; + } + case 64767: // Stormhammer + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT) + return; + + if (Creature* target = (Creature*)unitTarget) + { + target->AI()->EnterEvadeMode(); + target->CastSpell(target, 62470, true); + target->CastSpell(m_caster, 64909, true); + target->CastSpell(target, 64778, true); + target->ForcedDespawn(10000); + } + return; + } + case 66477: // Bountiful Feast + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 65422, true); + unitTarget->CastSpell(unitTarget, 66622, true); + break; + } + case 66741: // Chum the Water + { + // maybe this check should be done sooner? + if (!m_caster->IsInWater()) + return; + + uint32 spellId = 0; + + // too low/high? + if (roll_chance_i(33)) + spellId = 66737; // angry + else + { + switch (rand() % 3) + { + case 0: spellId = 66740; break; // blue + case 1: spellId = 66739; break; // tresher + case 2: spellId = 66738; break; // mako + } + } + + if (spellId) + m_caster->CastSpell(m_caster, spellId, true); + + return; + } + case 66744: // Make Player Destroy Totems + { + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // Totem of the Earthen Ring does not really require or take reagents. + // Expecting RewardQuest() to already destroy them or we need additional code here to destroy. + unitTarget->CastSpell(unitTarget, 66747, true); + return; + } + case 67009: // Nether Power (ToC25: Lord Jaraxxus) + { + if (!unitTarget) + return; + + for (uint8 i = 0; i < 11; ++i) + unitTarget->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + + return; + } + case 68861: // Consume Soul (ICC FoS: Bronjahm) + if (unitTarget) + unitTarget->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + return; + case 68871: // Wailing Souls + // Left or Right direction? + m_caster->CastSpell(m_caster, urand(0, 1) ? 68875 : 68876, false); + // Clear TargetGuid for sweeping + m_caster->SetTargetGuid(ObjectGuid()); + return; + case 69048: // Mirrored Soul + { + if (!unitTarget) + return; + + // This is extremely strange! + // The spell should send SMSG_CHANNEL_START, SMSG_SPELL_START + // However it has cast time 2s, but should send SMSG_SPELL_GO instantly. + m_caster->CastSpell(unitTarget, 69051, true); + return; + } + case 69051: // Mirrored Soul + { + if (!unitTarget) + return; + + // Actually this spell should be sent with SMSG_SPELL_START + unitTarget->CastSpell(m_caster, 69023, true); + return; + } + case 69140: // Coldflame (random target selection) + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + return; + } + case 69147: // Coldflame + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + return; + } + case 69377: // Fortitude + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, 72590, true); + return; + } + case 69378: // Blessing of Forgotten Kings + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, 72586, true); + return; + } + case 69381: // Gift of the Wild + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, 72588, true); + return; + } + case 71806: // Glittering Sparks + { + if (!unitTarget) + return; + + m_caster->CastSpell(unitTarget, effect->CalculateSimpleValue(), true); + return; + } + case 72034: // Whiteout + case 72096: // Whiteout (heroic) + { + // cast Whiteout visual + m_caster->CastSpell(unitTarget, 72036, true); + return; + } + case 72705: // Coldflame (summon around the caster) + { + if (!unitTarget) + return; + + // Cast summon spells 72701, 72702, 72703, 72704 + for (uint32 triggeredSpell = effect->CalculateSimpleValue(); triggeredSpell < m_spellInfo->Id; ++triggeredSpell) + unitTarget->CastSpell(unitTarget, triggeredSpell, true); + + return; + } + case 74455: // Conflagration + { + if (!unitTarget) + return; + + unitTarget->CastSpell(m_caster, effect->CalculateSimpleValue(), true); + return; + } + } + break; + } + case SPELLFAMILY_WARLOCK: + { + switch (m_spellInfo->Id) + { + case 6201: // Healthstone creating spells + case 6202: + case 5699: + case 11729: + case 11730: + case 27230: + case 47871: + case 47878: + { + if (!unitTarget) + return; + + uint32 itemtype; + uint32 rank = 0; + Unit::AuraList const& mDummyAuras = unitTarget->GetAurasByType(SPELL_AURA_DUMMY); + for (Unit::AuraList::const_iterator i = mDummyAuras.begin(); i != mDummyAuras.end(); ++i) + { + if ((*i)->GetId() == 18692) + { + rank = 1; + break; + } + else if ((*i)->GetId() == 18693) + { + rank = 2; + break; + } + } + + static uint32 const itypes[8][3] = + { + { 5512, 19004, 19005}, // Minor Healthstone + { 5511, 19006, 19007}, // Lesser Healthstone + { 5509, 19008, 19009}, // Healthstone + { 5510, 19010, 19011}, // Greater Healthstone + { 9421, 19012, 19013}, // Major Healthstone + {22103, 22104, 22105}, // Master Healthstone + {36889, 36890, 36891}, // Demonic Healthstone + {36892, 36893, 36894} // Fel Healthstone + }; + + switch (m_spellInfo->Id) + { + case 6201: + itemtype = itypes[0][rank]; break; // Minor Healthstone + case 6202: + itemtype = itypes[1][rank]; break; // Lesser Healthstone + case 5699: + itemtype = itypes[2][rank]; break; // Healthstone + case 11729: + itemtype = itypes[3][rank]; break; // Greater Healthstone + case 11730: + itemtype = itypes[4][rank]; break; // Major Healthstone + case 27230: + itemtype = itypes[5][rank]; break; // Master Healthstone + case 47871: + itemtype = itypes[6][rank]; break; // Demonic Healthstone + case 47878: + itemtype = itypes[7][rank]; break; // Fel Healthstone + default: + return; + } + DoCreateItem( effect, itemtype ); + return; + } + case 47193: // Demonic Empowerment + { + if (!unitTarget) + return; + + uint32 entry = unitTarget->GetEntry(); + uint32 spellID; + switch (entry) + { + case 416: spellID = 54444; break; // imp + case 417: spellID = 54509; break; // fellhunter + case 1860: spellID = 54443; break; // void + case 1863: spellID = 54435; break; // succubus + case 17252: spellID = 54508; break; // fellguard + default: + return; + } + unitTarget->CastSpell(unitTarget, spellID, true); + return; + } + case 47422: // Everlasting Affliction + { + // Need refresh caster corruption auras on target + Unit::SpellAuraHolderMap& suAuras = unitTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::iterator itr = suAuras.begin(); itr != suAuras.end(); ++itr) + { + SpellEntry const *spellInfo = (*itr).second->GetSpellProto(); + SpellClassOptionsEntry const* eaClassOptions = spellInfo->GetSpellClassOptions(); + if(eaClassOptions && eaClassOptions->SpellFamilyName == SPELLFAMILY_WARLOCK && + (eaClassOptions->SpellFamilyFlags & UI64LIT(0x0000000000000002)) && + (*itr).second->GetCasterGuid() == m_caster->GetObjectGuid()) + (*itr).second->RefreshHolder(); + } + return; + } + case 63521: // Guarded by The Light (Paladin spell with SPELLFAMILY_WARLOCK) + { + // Divine Plea, refresh on target (3 aura slots) + if (SpellAuraHolder* holder = unitTarget->GetSpellAuraHolder(54428)) + holder->RefreshHolder(); + + return; + } + } + break; + } + case SPELLFAMILY_PRIEST: + { + switch (m_spellInfo->Id) + { + case 47948: // Pain and Suffering + { + if (!unitTarget) + return; + + // Refresh Shadow Word: Pain on target + Unit::SpellAuraHolderMap& auras = unitTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + SpellEntry const *spellInfo = (*itr).second->GetSpellProto(); + SpellClassOptionsEntry const* swpClassOptions = spellInfo->GetSpellClassOptions(); + if (swpClassOptions && swpClassOptions->SpellFamilyName == SPELLFAMILY_PRIEST && + (swpClassOptions->SpellFamilyFlags & UI64LIT(0x0000000000008000)) && + (*itr).second->GetCasterGuid() == m_caster->GetObjectGuid()) + { + (*itr).second->RefreshHolder(); + return; + } + } + return; + } + default: + break; + } + break; + } + case SPELLFAMILY_HUNTER: + { + switch (m_spellInfo->Id) + { + case 53209: // Chimera Shot + { + if (!unitTarget) + return; + + uint32 spellId = 0; + int32 basePoint = 0; + Unit* target = unitTarget; + Unit::SpellAuraHolderMap& Auras = unitTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::iterator i = Auras.begin(); i != Auras.end(); ++i) + { + SpellAuraHolder* holder = i->second; + if (holder->GetCasterGuid() != m_caster->GetObjectGuid()) + continue; + + // Search only Serpent Sting, Viper Sting, Scorpid Sting auras + SpellClassOptionsEntry const* stingClassOptions = holder->GetSpellProto()->GetSpellClassOptions(); + if (!stingClassOptions || !stingClassOptions->SpellFamilyFlags.IsFitToFamilyMask(UI64LIT(0x000000800000C000))) + continue; + + // Refresh aura duration + holder->RefreshHolder(); + + Aura* aura = holder->GetAuraByEffectIndex(EFFECT_INDEX_0); + + if (!aura) + continue; + + // Serpent Sting - Instantly deals 40% of the damage done by your Serpent Sting. + if (stingClassOptions->IsFitToFamilyMask(UI64LIT(0x0000000000004000))) + { + // m_amount already include RAP bonus + basePoint = aura->GetModifier()->m_amount * aura->GetAuraMaxTicks() * 40 / 100; + spellId = 53353; // Chimera Shot - Serpent + } + + // Viper Sting - Instantly restores mana to you equal to 60% of the total amount drained by your Viper Sting. + if (stingClassOptions->IsFitToFamilyMask(UI64LIT(0x0000008000000000))) + { + uint32 target_max_mana = unitTarget->GetMaxPower(POWER_MANA); + if (!target_max_mana) + continue; + + // ignore non positive values (can be result apply spellmods to aura damage + uint32 pdamage = aura->GetModifier()->m_amount > 0 ? aura->GetModifier()->m_amount : 0; + + // Special case: draining x% of mana (up to a maximum of 2*x% of the caster's maximum mana) + uint32 maxmana = m_caster->GetMaxPower(POWER_MANA) * pdamage * 2 / 100; + + pdamage = target_max_mana * pdamage / 100; + if (pdamage > maxmana) + pdamage = maxmana; + + pdamage *= 4; // total aura damage + basePoint = pdamage * 60 / 100; + spellId = 53358; // Chimera Shot - Viper + target = m_caster; + } + + // Scorpid Sting - Attempts to Disarm the target for 10 sec. This effect cannot occur more than once per 1 minute. + if (stingClassOptions->IsFitToFamilyMask(UI64LIT(0x0000000000008000))) + spellId = 53359; // Chimera Shot - Scorpid + // ?? nothing say in spell desc (possibly need addition check) + // if ((familyFlag & UI64LIT(0x0000010000000000)) || // dot + // (familyFlag & UI64LIT(0x0000100000000000))) // stun + //{ + // spellId = 53366; // 53366 Chimera Shot - Wyvern + //} + } + + if (spellId) + m_caster->CastCustomSpell(target, spellId, &basePoint, 0, 0, false); + + return; + } + case 53412: // Invigoration (pet triggered script, master targeted) + { + if (!unitTarget) + return; + + Unit::AuraList const& auras = unitTarget->GetAurasByType(SPELL_AURA_DUMMY); + for (Unit::AuraList::const_iterator i = auras.begin(); i != auras.end(); ++i) + { + // Invigoration (master talent) + if ((*i)->GetModifier()->m_miscvalue == 8 && (*i)->GetSpellProto()->SpellIconID == 3487) + { + if (roll_chance_i((*i)->GetModifier()->m_amount)) + { + unitTarget->CastSpell(unitTarget, 53398, true, NULL, (*i), m_caster->GetObjectGuid()); + break; + } + } + } + return; + } + case 53271: // Master's Call + { + if (!unitTarget) + return; + + // script effect have in value, but this outdated removed part + unitTarget->CastSpell(unitTarget, 62305, true); + return; + } + default: + break; + } + break; + } + case SPELLFAMILY_PALADIN: + { + // Judgement (seal trigger) + if (m_spellInfo->GetCategory() == SPELLCATEGORY_JUDGEMENT) + { + if (!unitTarget || !unitTarget->isAlive()) + return; + + uint32 spellId1 = 0; + uint32 spellId2 = 0; + + // Judgement self add switch + switch (m_spellInfo->Id) + { + case 53407: spellId1 = 20184; break; // Judgement of Justice + case 20271: // Judgement of Light + case 57774: spellId1 = 20185; break; // Judgement of Light + case 53408: spellId1 = 20186; break; // Judgement of Wisdom + default: + sLog.outError("Unsupported Judgement (seal trigger) spell (Id: %u) in Spell::EffectScriptEffect", m_spellInfo->Id); + return; + } + + // offensive seals have aura dummy in 2 effect + Unit::AuraList const& m_dummyAuras = m_caster->GetAurasByType(SPELL_AURA_DUMMY); + for (Unit::AuraList::const_iterator itr = m_dummyAuras.begin(); itr != m_dummyAuras.end(); ++itr) + { + // search seal (offensive seals have judgement's aura dummy spell id in 2 effect + if ((*itr)->GetEffIndex() != EFFECT_INDEX_2 || !IsSealSpell((*itr)->GetSpellProto())) + continue; + spellId2 = (*itr)->GetModifier()->m_amount; + SpellEntry const* judge = sSpellStore.LookupEntry(spellId2); + if (!judge) + continue; + break; + } + + // if there were no offensive seals than there is seal with proc trigger aura + if (!spellId2) + { + Unit::AuraList const& procTriggerAuras = m_caster->GetAurasByType(SPELL_AURA_PROC_TRIGGER_SPELL); + for (Unit::AuraList::const_iterator itr = procTriggerAuras.begin(); itr != procTriggerAuras.end(); ++itr) + { + if ((*itr)->GetEffIndex() != EFFECT_INDEX_0 || !IsSealSpell((*itr)->GetSpellProto())) + continue; + spellId2 = 54158; + break; + } + } + + if (spellId1) + m_caster->CastSpell(unitTarget, spellId1, true); + + if (spellId2) + m_caster->CastSpell(unitTarget, spellId2, true); + + return; + } + break; + } + case SPELLFAMILY_POTION: + { + switch (m_spellInfo->Id) + { + case 28698: // Dreaming Glory + { + if (!unitTarget) + return; + + unitTarget->CastSpell(unitTarget, 28694, true); + break; + } + case 28702: // Netherbloom + { + if (!unitTarget) + return; + + // 25% chance of casting a random buff + if (roll_chance_i(75)) + return; + + // triggered spells are 28703 to 28707 + // Note: some sources say, that there was the possibility of + // receiving a debuff. However, this seems to be removed by a patch. + const uint32 spellid = 28703; + + // don't overwrite an existing aura + for (uint8 i = 0; i < 5; ++i) + if (unitTarget->HasAura(spellid + i, EFFECT_INDEX_0)) + return; + + unitTarget->CastSpell(unitTarget, spellid + urand(0, 4), true); + break; + } + case 28720: // Nightmare Vine + { + if (!unitTarget) + return; + + // 25% chance of casting Nightmare Pollen + if (roll_chance_i(75)) + return; + + unitTarget->CastSpell(unitTarget, 28721, true); + break; + } + } + break; + } + case SPELLFAMILY_DEATHKNIGHT: + { + switch (m_spellInfo->Id) + { + case 50842: // Pestilence + { + if (!unitTarget) + return; + + Unit* mainTarget = m_targets.getUnitTarget(); + if (!mainTarget) + return; + + // do only refresh diseases on main target if caster has Glyph of Disease + if (mainTarget == unitTarget && !m_caster->HasAura(63334)) + return; + + // Blood Plague + if (mainTarget->HasAura(55078)) + m_caster->CastSpell(unitTarget, 55078, true); + + // Frost Fever + if (mainTarget->HasAura(55095)) + m_caster->CastSpell(unitTarget, 55095, true); + + break; + } + } + break; + } + } + + // normal DB scripted effect + if (!unitTarget) + return; + + // Script based implementation. Must be used only for not good for implementation in core spell effects + // So called only for not processed cases + if (unitTarget->GetTypeId() == TYPEID_UNIT) + { + if (sScriptMgr.OnEffectScriptEffect(m_caster, m_spellInfo->Id, SpellEffectIndex(effect->EffectIndex), (Creature*)unitTarget, m_originalCasterGUID)) + return; + } + + // Previous effect might have started script + if (!ScriptMgr::CanSpellEffectStartDBScript(m_spellInfo, SpellEffectIndex(effect->EffectIndex))) + return; + + DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell ScriptStart spellid %u in EffectScriptEffect", m_spellInfo->Id); + m_caster->GetMap()->ScriptsStart(sSpellScripts, m_spellInfo->Id, m_caster, unitTarget); +} + +void Spell::EffectSanctuary(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget) + return; + // unitTarget->CombatStop(); + + unitTarget->CombatStop(); + unitTarget->getHostileRefManager().deleteReferences(); // stop all fighting + + // Vanish allows to remove all threat and cast regular stealth so other spells can be used + if (m_spellInfo->IsFitToFamily(SPELLFAMILY_ROGUE, UI64LIT(0x0000000000000800))) + ((Player*)m_caster)->RemoveSpellsCausingAura(SPELL_AURA_MOD_ROOT); +} + +void Spell::EffectAddComboPoints(SpellEffectEntry const* effect /*effect*/) +{ + if (!unitTarget) + return; + + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + if (damage <= 0) + return; + + ((Player*)m_caster)->AddComboPoints(unitTarget, damage); +} + +void Spell::EffectDuel(SpellEffectEntry const* effect) +{ + if (!m_caster || !unitTarget || m_caster->GetTypeId() != TYPEID_PLAYER || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + Player* caster = (Player*)m_caster; + Player* target = (Player*)unitTarget; + + // caster or target already have requested duel + if (caster->duel || target->duel || !target->GetSocial() || target->GetSocial()->HasIgnore(caster->GetObjectGuid())) + return; + + // Players can only fight a duel with each other outside (=not inside dungeons and not in capital cities) + AreaTableEntry const* casterAreaEntry = GetAreaEntryByAreaID(caster->GetAreaId()); + if (casterAreaEntry && !(casterAreaEntry->flags & AREA_FLAG_DUEL)) + { + SendCastResult(SPELL_FAILED_NO_DUELING); // Dueling isn't allowed here + return; + } + + AreaTableEntry const* targetAreaEntry = GetAreaEntryByAreaID(target->GetAreaId()); + if (targetAreaEntry && !(targetAreaEntry->flags & AREA_FLAG_DUEL)) + { + SendCastResult(SPELL_FAILED_NO_DUELING); // Dueling isn't allowed here + return; + } + + // CREATE DUEL FLAG OBJECT + GameObject* pGameObj = new GameObject; + + uint32 gameobject_id = effect->EffectMiscValue; + + Map* map = m_caster->GetMap(); + float x = (m_caster->GetPositionX() + unitTarget->GetPositionX()) * 0.5f; + float y = (m_caster->GetPositionY() + unitTarget->GetPositionY()) * 0.5f; + float z = m_caster->GetPositionZ(); + m_caster->UpdateAllowedPositionZ(x, y, z); + if (!pGameObj->Create(map->GenerateLocalLowGuid(HIGHGUID_GAMEOBJECT), gameobject_id, map, m_caster->GetPhaseMask(), x, y, z, m_caster->GetOrientation())) + { + delete pGameObj; + return; + } + + pGameObj->SetUInt32Value(GAMEOBJECT_FACTION, m_caster->getFaction()); + pGameObj->SetUInt32Value(GAMEOBJECT_LEVEL, m_caster->getLevel() + 1); + + pGameObj->SetRespawnTime(m_duration > 0 ? m_duration / IN_MILLISECONDS : 0); + pGameObj->SetSpellId(m_spellInfo->Id); + + m_caster->AddGameObject(pGameObj); + map->Add(pGameObj); + // END + + // Send request + WorldPacket data(SMSG_DUEL_REQUESTED, 8 + 8); + data << pGameObj->GetObjectGuid(); + data << caster->GetObjectGuid(); + caster->GetSession()->SendPacket(&data); + target->GetSession()->SendPacket(&data); + + // create duel-info + DuelInfo* duel = new DuelInfo; + duel->initiator = caster; + duel->opponent = target; + duel->startTime = 0; + duel->startTimer = 0; + caster->duel = duel; + + DuelInfo* duel2 = new DuelInfo; + duel2->initiator = caster; + duel2->opponent = caster; + duel2->startTime = 0; + duel2->startTimer = 0; + target->duel = duel2; + + caster->SetGuidValue(PLAYER_DUEL_ARBITER, pGameObj->GetObjectGuid()); + target->SetGuidValue(PLAYER_DUEL_ARBITER, pGameObj->GetObjectGuid()); +} + +void Spell::EffectStuck(SpellEffectEntry const* effect /*effect*/) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + if (!sWorld.getConfig(CONFIG_BOOL_CAST_UNSTUCK)) + return; + + Player* pTarget = (Player*)unitTarget; + + DEBUG_LOG("Spell Effect: Stuck"); + DETAIL_LOG("Player %s (guid %u) used auto-unstuck future at map %u (%f, %f, %f)", pTarget->GetName(), pTarget->GetGUIDLow(), m_caster->GetMapId(), m_caster->GetPositionX(), pTarget->GetPositionY(), pTarget->GetPositionZ()); + + if (pTarget->IsTaxiFlying()) + return; + + // homebind location is loaded always + pTarget->TeleportToHomebind(unitTarget == m_caster ? TELE_TO_SPELL : 0); + + // Stuck spell trigger Hearthstone cooldown + SpellEntry const* spellInfo = sSpellStore.LookupEntry(8690); + if (!spellInfo) + return; + Spell spell(pTarget, spellInfo, true); + spell.SendSpellCooldown(); +} + +void Spell::EffectSummonPlayer(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // Evil Twin (ignore player summon, but hide this for summoner) + if (unitTarget->GetDummyAura(23445)) + return; + + float x, y, z; + m_caster->GetClosePoint(x, y, z, unitTarget->GetObjectBoundingRadius()); + + ((Player*)unitTarget)->SetSummonPoint(m_caster->GetMapId(), x, y, z); + + WorldPacket data(SMSG_SUMMON_REQUEST, 8 + 4 + 4); + data << m_caster->GetObjectGuid(); // summoner guid + data << uint32(m_caster->GetZoneId()); // summoner zone + data << uint32(MAX_PLAYER_SUMMON_DELAY * IN_MILLISECONDS); // auto decline after msecs + ((Player*)unitTarget)->GetSession()->SendPacket(&data); +} + +static ScriptInfo generateActivateCommand() +{ + ScriptInfo si; + si.command = SCRIPT_COMMAND_ACTIVATE_OBJECT; + si.id = 0; + si.buddyEntry = 0; + si.searchRadiusOrGuid = 0; + si.data_flags = 0x00; + return si; +} + +void Spell::EffectActivateObject(SpellEffectEntry const* effect) +{ + if (!gameObjTarget) + return; + + uint32 misc_value = uint32(effect->EffectMiscValue); + + switch (misc_value) + { + case 1: // GO simple use + case 2: // unk - 2 spells + case 4: // unk - 1 spell + case 5: // GO trap usage + case 7: // unk - 2 spells + case 8: // GO usage with TargetB = none or random + case 10: // GO explosions + case 11: // unk - 1 spell + case 19: // unk - 1 spell + case 20: // unk - 2 spells + { + static ScriptInfo activateCommand = generateActivateCommand(); + + int32 delay_secs = effect->CalculateSimpleValue(); + gameObjTarget->GetMap()->ScriptCommandStart(activateCommand, delay_secs, m_caster, gameObjTarget); + break; + } + case 3: // GO custom anim - found mostly in Lunar Fireworks spells + gameObjTarget->SendGameObjectCustomAnim(gameObjTarget->GetObjectGuid()); + break; + case 12: // GO state active alternative - found mostly in Simon Game spells + gameObjTarget->UseDoorOrButton(0, true); + break; + case 13: // GO state ready - found only in Simon Game spells + gameObjTarget->ResetDoorOrButton(); + break; + case 15: // GO destroy + gameObjTarget->SetLootState(GO_JUST_DEACTIVATED); + break; + case 16: // GO custom use - found mostly in Wind Stones spells, Simon Game spells and other GO target summoning spells + { + switch (m_spellInfo->Id) + { + case 24734: // Summon Templar Random + case 24744: // Summon Templar (fire) + case 24756: // Summon Templar (air) + case 24758: // Summon Templar (earth) + case 24760: // Summon Templar (water) + case 24763: // Summon Duke Random + case 24765: // Summon Duke (fire) + case 24768: // Summon Duke (air) + case 24770: // Summon Duke (earth) + case 24772: // Summon Duke (water) + case 24784: // Summon Royal Random + case 24786: // Summon Royal (fire) + case 24788: // Summon Royal (air) + case 24789: // Summon Royal (earth) + case 24790: // Summon Royal (water) + { + uint32 npcEntry = 0; + uint32 templars[] = {15209, 15211, 15212, 15307}; + uint32 dukes[] = {15206, 15207, 15208, 15220}; + uint32 royals[] = {15203, 15204, 15205, 15305}; + + switch (m_spellInfo->Id) + { + case 24734: npcEntry = templars[urand(0, 3)]; break; + case 24763: npcEntry = dukes[urand(0, 3)]; break; + case 24784: npcEntry = royals[urand(0, 3)]; break; + case 24744: npcEntry = 15209; break; + case 24756: npcEntry = 15212; break; + case 24758: npcEntry = 15307; break; + case 24760: npcEntry = 15211; break; + case 24765: npcEntry = 15206; break; + case 24768: npcEntry = 15220; break; + case 24770: npcEntry = 15208; break; + case 24772: npcEntry = 15207; break; + case 24786: npcEntry = 15203; break; + case 24788: npcEntry = 15204; break; + case 24789: npcEntry = 15205; break; + case 24790: npcEntry = 15305; break; + } + + gameObjTarget->SummonCreature(npcEntry, gameObjTarget->GetPositionX(), gameObjTarget->GetPositionY(), gameObjTarget->GetPositionZ(), gameObjTarget->GetAngle(m_caster), TEMPSUMMON_TIMED_OOC_OR_DEAD_DESPAWN, MINUTE * IN_MILLISECONDS); + gameObjTarget->SetLootState(GO_JUST_DEACTIVATED); + break; + } + case 40176: // Simon Game pre-game Begin, blue + case 40177: // Simon Game pre-game Begin, green + case 40178: // Simon Game pre-game Begin, red + case 40179: // Simon Game pre-game Begin, yellow + case 40283: // Simon Game END, blue + case 40284: // Simon Game END, green + case 40285: // Simon Game END, red + case 40286: // Simon Game END, yellow + case 40494: // Simon Game, switched ON + case 40495: // Simon Game, switched OFF + case 40512: // Simon Game, switch...disable Off switch + gameObjTarget->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_NO_INTERACT); + break; + case 40632: // Summon Gezzarak the Huntress + case 40640: // Summon Karrog + case 40642: // Summon Darkscreecher Akkarai + case 40644: // Summon Vakkiz the Windrager + case 41004: // Summon Terokk + gameObjTarget->SetLootState(GO_JUST_DEACTIVATED); + break; + case 46085: // Place Fake Fur + { + float x, y, z; + gameObjTarget->GetClosePoint(x, y, z, gameObjTarget->GetObjectBoundingRadius(), 2 * INTERACTION_DISTANCE, frand(0, M_PI_F * 2)); + + // Note: event script is implemented in script library + gameObjTarget->SummonCreature(25835, x, y, z, gameObjTarget->GetOrientation(), TEMPSUMMON_TIMED_OOC_OR_DEAD_DESPAWN, 15000); + gameObjTarget->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE); + break; + } + case 46592: // Summon Ahune Lieutenant + { + uint32 npcEntry = 0; + + switch (gameObjTarget->GetEntry()) + { + case 188049: npcEntry = 26116; break; // Frostwave Lieutenant (Ashenvale) + case 188137: npcEntry = 26178; break; // Hailstone Lieutenant (Desolace) + case 188138: npcEntry = 26204; break; // Chillwind Lieutenant (Stranglethorn) + case 188148: npcEntry = 26214; break; // Frigid Lieutenant (Searing Gorge) + case 188149: npcEntry = 26215; break; // Glacial Lieutenant (Silithus) + case 188150: npcEntry = 26216; break; // Glacial Templar (Hellfire Peninsula) + } + + gameObjTarget->SummonCreature(npcEntry, gameObjTarget->GetPositionX(), gameObjTarget->GetPositionY(), gameObjTarget->GetPositionZ(), gameObjTarget->GetAngle(m_caster), TEMPSUMMON_TIMED_OOC_OR_DEAD_DESPAWN, MINUTE * IN_MILLISECONDS); + gameObjTarget->SetLootState(GO_JUST_DEACTIVATED); + break; + } + } + break; + } + case 17: // GO unlock - found mostly in Simon Game spells + gameObjTarget->RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_NO_INTERACT); + break; + default: + sLog.outError("Spell::EffectActivateObject called with unknown misc value. Spell Id %u", m_spellInfo->Id); + break; + } +} + +void Spell::EffectApplyGlyph(SpellEffectEntry const* effect) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Player* player = (Player*)m_caster; + + // glyph sockets level requirement + uint8 minLevel = 0; + switch (m_glyphIndex) + { + case 0: + case 1: + case 6: minLevel = 25; break; + case 2: + case 3: + case 7: minLevel = 50; break; + case 4: + case 5: + case 8: minLevel = 75; break; + } + + if (minLevel && m_caster->getLevel() < minLevel) + { + SendCastResult(SPELL_FAILED_GLYPH_SOCKET_LOCKED); + return; + } + + // apply new one + if(uint32 glyph = effect->EffectMiscValue) + { + if (GlyphPropertiesEntry const* gp = sGlyphPropertiesStore.LookupEntry(glyph)) + { + if (GlyphSlotEntry const* gs = sGlyphSlotStore.LookupEntry(player->GetGlyphSlot(m_glyphIndex))) + { + if (gp->TypeFlags != gs->TypeFlags) + { + SendCastResult(SPELL_FAILED_INVALID_GLYPH); + return; // glyph slot mismatch + } + } + + // remove old glyph + player->ApplyGlyph(m_glyphIndex, false); + player->SetGlyph(m_glyphIndex, glyph); + player->ApplyGlyph(m_glyphIndex, true); + player->SendTalentsInfoData(false); + } + } +} + +void Spell::EffectEnchantHeldItem(SpellEffectEntry const* effect) +{ + // this is only item spell effect applied to main-hand weapon of target player (players in area) + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + Player* item_owner = (Player*)unitTarget; + Item* item = item_owner->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + + if (!item) + return; + + // must be equipped + if (!item ->IsEquipped()) + return; + + if (effect->EffectMiscValue) + { + uint32 enchant_id = effect->EffectMiscValue; + int32 duration = m_duration; // Try duration index first... + if (!duration) + duration = m_currentBasePoints[SpellEffectIndex(effect->EffectIndex)]; // Base points after... + if (!duration) + duration = 10; // 10 seconds for enchants which don't have listed duration + + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + return; + + // Always go to temp enchantment slot + EnchantmentSlot slot = TEMP_ENCHANTMENT_SLOT; + + // Enchantment will not be applied if a different one already exists + if (item->GetEnchantmentId(slot) && item->GetEnchantmentId(slot) != enchant_id) + return; + + // Apply the temporary enchantment + item->SetEnchantment(slot, enchant_id, duration * IN_MILLISECONDS, 0); + item_owner->ApplyEnchantment(item, slot, true); + } +} + +void Spell::EffectDisEnchant(SpellEffectEntry const* /*effect*/) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Player* p_caster = (Player*)m_caster; + if (!itemTarget || !itemTarget->GetProto()->DisenchantID) + return; + + p_caster->UpdateCraftSkill(m_spellInfo->Id); + + ((Player*)m_caster)->SendLoot(itemTarget->GetObjectGuid(), LOOT_DISENCHANTING); + + // item will be removed at disenchanting end +} + +void Spell::EffectInebriate(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + Player* player = (Player*)unitTarget; + + uint8 drunkValue = player->GetDrunkValue() + (uint8)damage; + if (drunkValue > 100) + { + drunkValue = 100; + if (roll_chance_i(25)) + player->CastSpell(player, 67468, false); // Drunken Vomit + } + player->SetDrunkValue(drunkValue, m_CastItem ? m_CastItem->GetEntry() : 0); +} + +void Spell::EffectFeedPet(SpellEffectEntry const* effect) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Player* _player = (Player*)m_caster; + + Item* foodItem = m_targets.getItemTarget(); + if (!foodItem) + return; + + Pet* pet = _player->GetPet(); + if (!pet) + return; + + if (!pet->isAlive()) + return; + + int32 benefit = pet->GetCurrentFoodBenefitLevel(foodItem->GetProto()->ItemLevel); + if (benefit <= 0) + return; + + uint32 count = 1; + _player->DestroyItemCount(foodItem, count, true); + // TODO: fix crash when a spell has two effects, both pointed at the same item target + + m_caster->CastCustomSpell(pet, effect->EffectTriggerSpell, &benefit, NULL, NULL, true); +} + +void Spell::EffectDismissPet(SpellEffectEntry const* /*effect*/) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Pet* pet = m_caster->GetPet(); + + // not let dismiss dead pet + if (!pet || !pet->isAlive()) + return; + + pet->Unsummon(PET_SAVE_NOT_IN_SLOT, m_caster); +} + +void Spell::EffectSummonObject(SpellEffectEntry const* effect) +{ + uint32 go_id = effect->EffectMiscValue; + uint8 slot = effect->EffectMiscValueB; + if (slot >= MAX_OBJECT_SLOT) + return; + + if (ObjectGuid guid = m_caster->m_ObjectSlotGuid[slot]) + { + if (GameObject* obj = m_caster ? m_caster->GetMap()->GetGameObject(guid) : NULL) + obj->SetLootState(GO_JUST_DEACTIVATED); + m_caster->m_ObjectSlotGuid[slot].Clear(); + } + + GameObject* pGameObj = new GameObject; + + float x, y, z; + // If dest location if present + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) + m_targets.getDestination(x, y, z); + // Summon in random point all other units if location present + else + m_caster->GetClosePoint(x, y, z, DEFAULT_WORLD_OBJECT_SIZE); + + Map* map = m_caster->GetMap(); + if (!pGameObj->Create(map->GenerateLocalLowGuid(HIGHGUID_GAMEOBJECT), go_id, map, + m_caster->GetPhaseMask(), x, y, z, m_caster->GetOrientation())) + { + delete pGameObj; + return; + } + + pGameObj->SetUInt32Value(GAMEOBJECT_LEVEL, m_caster->getLevel()); + pGameObj->SetRespawnTime(m_duration > 0 ? m_duration / IN_MILLISECONDS : 0); + pGameObj->SetSpellId(m_spellInfo->Id); + m_caster->AddGameObject(pGameObj); + + map->Add(pGameObj); + + m_caster->m_ObjectSlotGuid[slot] = pGameObj->GetObjectGuid(); + + pGameObj->SummonLinkedTrapIfAny(); + + if (m_caster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_caster)->AI()) + ((Creature*)m_caster)->AI()->JustSummoned(pGameObj); + if (m_originalCaster && m_originalCaster != m_caster && m_originalCaster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_originalCaster)->AI()) + ((Creature*)m_originalCaster)->AI()->JustSummoned(pGameObj); +} + +void Spell::EffectResurrect(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + if (unitTarget->isAlive() || !unitTarget->IsInWorld()) + return; + + switch (m_spellInfo->Id) + { + case 8342: // Defibrillate (Goblin Jumper Cables) has 33% chance on success + case 22999: // Defibrillate (Goblin Jumper Cables XL) has 50% chance on success + case 54732: // Defibrillate (Gnomish Army Knife) has 67% chance on success + { + uint32 failChance = 0; + uint32 failSpellId = 0; + switch (m_spellInfo->Id) + { + case 8342: failChance = 67; failSpellId = 8338; break; + case 22999: failChance = 50; failSpellId = 23055; break; + case 54732: failChance = 33; failSpellId = 0; break; + } + + if (roll_chance_i(failChance)) + { + if (failSpellId) + m_caster->CastSpell(m_caster, failSpellId, true, m_CastItem); + return; + } + break; + } + default: + break; + } + + Player* pTarget = ((Player*)unitTarget); + + if (pTarget->isRessurectRequested()) // already have one active request + return; + + uint32 health = pTarget->GetMaxHealth() * damage / 100; + uint32 mana = pTarget->GetMaxPower(POWER_MANA) * damage / 100; + + pTarget->setResurrectRequestData(m_caster->GetObjectGuid(), m_caster->GetMapId(), m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ(), health, mana); + SendResurrectRequest(pTarget); +} + +void Spell::EffectAddExtraAttacks(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget || !unitTarget->isAlive()) + return; + + if (unitTarget->m_extraAttacks) + return; + + unitTarget->m_extraAttacks = damage; +} + +void Spell::EffectParry(SpellEffectEntry const* /*effect*/) +{ + if (unitTarget && unitTarget->GetTypeId() == TYPEID_PLAYER) + ((Player*)unitTarget)->SetCanParry(true); +} + +void Spell::EffectBlock(SpellEffectEntry const* /*effect*/) +{ + if (unitTarget && unitTarget->GetTypeId() == TYPEID_PLAYER) + ((Player*)unitTarget)->SetCanBlock(true); +} + +void Spell::EffectLeapForward(SpellEffectEntry const* effect) +{ + if (unitTarget->IsTaxiFlying()) + return; + + if (m_spellInfo->rangeIndex == SPELL_RANGE_IDX_SELF_ONLY) + { + float dis = GetSpellRadius(sSpellRadiusStore.LookupEntry(effect->GetRadiusIndex())); + + // before caster + float fx, fy, fz; + unitTarget->GetClosePoint(fx, fy, fz, unitTarget->GetObjectBoundingRadius(), dis); + float ox, oy, oz; + unitTarget->GetPosition(ox, oy, oz); + + if (unitTarget->GetMap()->GetHitPosition(ox, oy, oz + 0.5f, fx, fy, fz, unitTarget->GetPhaseMask(), -0.5f)) + unitTarget->UpdateAllowedPositionZ(fx, fy, fz); + + unitTarget->NearTeleportTo(fx, fy, fz, unitTarget->GetOrientation(), unitTarget == m_caster); + } +} + +void Spell::EffectLeapBack(SpellEffectEntry const* effect) +{ + if (unitTarget->IsTaxiFlying()) + return; + + m_caster->KnockBackFrom(unitTarget, float(effect->EffectMiscValue) / 10, float(damage) / 10); +} + +void Spell::EffectReputation(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + Player* _player = (Player*)unitTarget; + + int32 rep_change = m_currentBasePoints[effect->EffectIndex]; + uint32 faction_id = effect->EffectMiscValue; + + FactionEntry const* factionEntry = sFactionStore.LookupEntry(faction_id); + + if (!factionEntry) + return; + + rep_change = _player->CalculateReputationGain(REPUTATION_SOURCE_SPELL, rep_change, faction_id); + + _player->GetReputationMgr().ModifyReputation(factionEntry, rep_change); +} + +void Spell::EffectQuestComplete(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + // A few spells has additional value from basepoints, check condition here. + switch (m_spellInfo->Id) + { + case 43458: // Secrets of Nifflevar + { + if (!unitTarget->HasAura(effect->CalculateSimpleValue())) + return; + + break; + } + // TODO: implement these! + // "this spell awards credit for the entire raid (all spell targets as this is area target) if just ONE member has both auras (yes, both effect's basepoints)" + // case 72155: // Harvest Blight Specimen + // case 72162: // Harvest Blight Specimen + // break; + default: + break; + } + + uint32 quest_id = effect->EffectMiscValue; + ((Player*)unitTarget)->AreaExploredOrEventHappens(quest_id); +} + +void Spell::EffectSelfResurrect(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->isAlive()) + return; + if (unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + if (!unitTarget->IsInWorld()) + return; + + uint32 health = 0; + uint32 mana = 0; + + // flat case + if (damage < 0) + { + health = uint32(-damage); + mana = effect->EffectMiscValue; + } + // percent case + else + { + health = uint32(damage / 100.0f * unitTarget->GetMaxHealth()); + if (unitTarget->GetMaxPower(POWER_MANA) > 0) + mana = uint32(damage / 100.0f * unitTarget->GetMaxPower(POWER_MANA)); + } + + Player* plr = ((Player*)unitTarget); + plr->ResurrectPlayer(0.0f); + + plr->SetHealth(health); + plr->SetPower(POWER_MANA, mana); + plr->SetPower(POWER_RAGE, 0); + plr->SetPower(POWER_ENERGY, plr->GetMaxPower(POWER_ENERGY)); + + plr->SpawnCorpseBones(); +} + +void Spell::EffectSkinning(SpellEffectEntry const* /*effect*/) +{ + if (unitTarget->GetTypeId() != TYPEID_UNIT) + return; + if (!m_caster || m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Creature* creature = (Creature*) unitTarget; + int32 targetLevel = creature->getLevel(); + + uint32 skill = creature->GetCreatureInfo()->GetRequiredLootSkill(); + + ((Player*)m_caster)->SendLoot(creature->GetObjectGuid(), LOOT_SKINNING); + creature->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); + + int32 reqValue = targetLevel < 10 ? 0 : targetLevel < 20 ? (targetLevel - 10) * 10 : targetLevel * 5; + + int32 skillValue = ((Player*)m_caster)->GetPureSkillValue(skill); + + // Double chances for elites + ((Player*)m_caster)->UpdateGatherSkill(skill, skillValue, reqValue, creature->IsElite() ? 2 : 1); +} + +void Spell::EffectCharge(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget) + return; + + // TODO: research more ContactPoint/attack distance. + // 3.666666 instead of ATTACK_DISTANCE(5.0f) in below seem to give more accurate result. + float x, y, z; + unitTarget->GetContactPoint(m_caster, x, y, z, 3.666666f); + + if (unitTarget->GetTypeId() != TYPEID_PLAYER) + ((Creature*)unitTarget)->StopMoving(); + + // Only send MOVEMENTFLAG_WALK_MODE, client has strange issues with other move flags + m_caster->MonsterMoveWithSpeed(x, y, z, 24.f, true, true); + + // not all charge effects used in negative spells + if (unitTarget != m_caster && !IsPositiveSpell(m_spellInfo->Id)) + m_caster->Attack(unitTarget, true); +} + +void Spell::EffectCharge2(SpellEffectEntry const* /*effect*/) +{ + float x, y, z; + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) + { + m_targets.getDestination(x, y, z); + + if (unitTarget->GetTypeId() != TYPEID_PLAYER) + ((Creature*)unitTarget)->StopMoving(); + } + else if (unitTarget && unitTarget != m_caster) + unitTarget->GetContactPoint(m_caster, x, y, z, 3.666666f); + else + return; + + // Only send MOVEMENTFLAG_WALK_MODE, client has strange issues with other move flags + m_caster->MonsterMoveWithSpeed(x, y, z, 24.f, true, true); + + // not all charge effects used in negative spells + if (unitTarget && unitTarget != m_caster && !IsPositiveSpell(m_spellInfo->Id)) + m_caster->Attack(unitTarget, true); +} + +void Spell::EffectKnockBack(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + + unitTarget->KnockBackFrom(m_caster, float(effect->EffectMiscValue) / 10, float(damage) / 10); +} + +void Spell::EffectSendTaxi(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)unitTarget)->ActivateTaxiPathTo(effect->EffectMiscValue, m_spellInfo->Id); +} + +void Spell::EffectPlayerPull(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + + float dist = unitTarget->GetDistance2d(m_caster); + if (damage && dist > damage) + dist = float(damage); + + unitTarget->KnockBackFrom(m_caster, -dist, float(effect->EffectMiscValue) / 10); +} + +void Spell::EffectDispelMechanic(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + + uint32 mechanic = effect->EffectMiscValue; + + Unit::SpellAuraHolderMap& Auras = unitTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::iterator iter = Auras.begin(), next; iter != Auras.end(); iter = next) + { + next = iter; + ++next; + SpellEntry const* spell = iter->second->GetSpellProto(); + if (iter->second->HasMechanic(mechanic)) + { + unitTarget->RemoveAurasDueToSpell(spell->Id); + if (Auras.empty()) + break; + else + next = Auras.begin(); + } + } +} + +void Spell::EffectSummonDeadPet(SpellEffectEntry const* /*effect*/) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + Player* _player = (Player*)m_caster; + Pet* pet = _player->GetPet(); + if (!pet) + return; + if (pet->isAlive()) + return; + if (damage < 0) + return; + + pet->SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE); + pet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); + pet->SetDeathState(ALIVE); + pet->clearUnitState(UNIT_STAT_ALL_STATE); + pet->SetHealth(uint32(pet->GetMaxHealth() * (float(damage) / 100))); + + pet->AIM_Initialize(); + + // _player->PetSpellInitialize(); -- action bar not removed at death and not required send at revive + pet->SavePetToDB(PET_SAVE_AS_CURRENT); +} + +void Spell::EffectSummonAllTotems(SpellEffectEntry const* effect) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + int32 start_button = ACTION_BUTTON_SHAMAN_TOTEMS_BAR + effect->EffectMiscValue; + int32 amount_buttons = effect->EffectMiscValueB; + + for (int32 slot = 0; slot < amount_buttons; ++slot) + if (ActionButton const* actionButton = ((Player*)m_caster)->GetActionButton(start_button + slot)) + if (actionButton->GetType() == ACTION_BUTTON_SPELL) + if (uint32 spell_id = actionButton->GetAction()) + m_caster->CastSpell(unitTarget, spell_id, true); +} + +void Spell::EffectDestroyAllTotems(SpellEffectEntry const* /*effect*/) +{ + int32 mana = 0; + for (int slot = 0; slot < MAX_TOTEM_SLOT; ++slot) + { + if (Totem* totem = m_caster->GetTotem(TotemSlot(slot))) + { + if (damage) + { + uint32 spell_id = totem->GetUInt32Value(UNIT_CREATED_BY_SPELL); + if (SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id)) + { + uint32 manacost = m_caster->GetCreateMana() * spellInfo->GetManaCostPercentage() / 100; + mana += manacost * damage / 100; + } + } + totem->UnSummon(); + } + } + + if (mana) + m_caster->CastCustomSpell(m_caster, 39104, &mana, NULL, NULL, true); +} + +void Spell::EffectBreakPlayerTargeting (SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget) + return; + + WorldPacket data(SMSG_CLEAR_TARGET, 8); + data << unitTarget->GetObjectGuid(); + unitTarget->SendMessageToSet(&data, false); +} + +void Spell::EffectDurabilityDamage(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + int32 slot = effect->EffectMiscValue; + + // FIXME: some spells effects have value -1/-2 + // Possibly its mean -1 all player equipped items and -2 all items + if (slot < 0) + { + ((Player*)unitTarget)->DurabilityPointsLossAll(damage, (slot < -1)); + return; + } + + // invalid slot value + if (slot >= INVENTORY_SLOT_BAG_END) + return; + + if (Item* item = ((Player*)unitTarget)->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + ((Player*)unitTarget)->DurabilityPointsLoss(item, damage); +} + +void Spell::EffectDurabilityDamagePCT(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + int32 slot = effect->EffectMiscValue; + + // FIXME: some spells effects have value -1/-2 + // Possibly its mean -1 all player equipped items and -2 all items + if (slot < 0) + { + ((Player*)unitTarget)->DurabilityLossAll(double(damage) / 100.0f, (slot < -1)); + return; + } + + // invalid slot value + if (slot >= INVENTORY_SLOT_BAG_END) + return; + + if (damage <= 0) + return; + + if (Item* item = ((Player*)unitTarget)->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + ((Player*)unitTarget)->DurabilityLoss(item, double(damage) / 100.0f); +} + +void Spell::EffectModifyThreatPercent(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget) + return; + + unitTarget->getThreatManager().modifyThreatPercent(m_caster, damage); +} + +void Spell::EffectTransmitted(SpellEffectEntry const* effect) +{ + uint32 name_id = effect->EffectMiscValue; + + GameObjectInfo const* goinfo = ObjectMgr::GetGameObjectInfo(name_id); + + if (!goinfo) + { + sLog.outErrorDb("Gameobject (Entry: %u) not exist and not created at spell (ID: %u) cast", name_id, m_spellInfo->Id); + return; + } + + float fx, fy, fz; + + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) + m_targets.getDestination(fx, fy, fz); + // FIXME: this can be better check for most objects but still hack + else if (effect->GetRadiusIndex() && m_spellInfo->speed == 0) + { + float dis = GetSpellRadius(sSpellRadiusStore.LookupEntry(effect->GetRadiusIndex())); + m_caster->GetClosePoint(fx, fy, fz, DEFAULT_WORLD_OBJECT_SIZE, dis); + } + else + { + float min_dis = GetSpellMinRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex)); + float max_dis = GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex)); + float dis = rand_norm_f() * (max_dis - min_dis) + min_dis; + + // special code for fishing bobber (TARGET_SELF_FISHING), should not try to avoid objects + // nor try to find ground level, but randomly vary in angle + if (goinfo->type == GAMEOBJECT_TYPE_FISHINGNODE) + { + // calculate angle variation for roughly equal dimensions of target area + float max_angle = (max_dis - min_dis) / (max_dis + m_caster->GetObjectBoundingRadius()); + float angle_offset = max_angle * (rand_norm_f() - 0.5f); + m_caster->GetNearPoint2D(fx, fy, dis + m_caster->GetObjectBoundingRadius(), m_caster->GetOrientation() + angle_offset); + + GridMapLiquidData liqData; + if (!m_caster->GetTerrain()->IsInWater(fx, fy, m_caster->GetPositionZ() + 1.f, &liqData)) + { + SendCastResult(SPELL_FAILED_NOT_FISHABLE); + SendChannelUpdate(0); + return; + } + + fz = liqData.level; + // finally, check LoS + if (!m_caster->IsWithinLOS(fx, fy, fz)) + { + SendCastResult(SPELL_FAILED_LINE_OF_SIGHT); + SendChannelUpdate(0); + return; + } + } + else + m_caster->GetClosePoint(fx, fy, fz, DEFAULT_WORLD_OBJECT_SIZE, dis); + } + + Map* cMap = m_caster->GetMap(); + + // if gameobject is summoning object, it should be spawned right on caster's position + if (goinfo->type == GAMEOBJECT_TYPE_SUMMONING_RITUAL) + { + m_caster->GetPosition(fx, fy, fz); + } + + GameObject* pGameObj = new GameObject; + + if (!pGameObj->Create(cMap->GenerateLocalLowGuid(HIGHGUID_GAMEOBJECT), name_id, cMap, + m_caster->GetPhaseMask(), fx, fy, fz, m_caster->GetOrientation())) + { + delete pGameObj; + return; + } + + int32 duration = m_duration; + + switch (goinfo->type) + { + case GAMEOBJECT_TYPE_FISHINGNODE: + { + m_caster->SetChannelObjectGuid(pGameObj->GetObjectGuid()); + m_caster->AddGameObject(pGameObj); // will removed at spell cancel + + // end time of range when possible catch fish (FISHING_BOBBER_READY_TIME..GetDuration(m_spellInfo)) + // start time == fish-FISHING_BOBBER_READY_TIME (0..GetDuration(m_spellInfo)-FISHING_BOBBER_READY_TIME) + int32 lastSec = 0; + switch (urand(0, 3)) + { + case 0: lastSec = 3; break; + case 1: lastSec = 7; break; + case 2: lastSec = 13; break; + case 3: lastSec = 17; break; + } + + duration = duration - lastSec * IN_MILLISECONDS + FISHING_BOBBER_READY_TIME * IN_MILLISECONDS; + break; + } + case GAMEOBJECT_TYPE_SUMMONING_RITUAL: + { + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + pGameObj->AddUniqueUse((Player*)m_caster); + m_caster->AddGameObject(pGameObj); // will removed at spell cancel + } + break; + } + case GAMEOBJECT_TYPE_FISHINGHOLE: + case GAMEOBJECT_TYPE_CHEST: + default: + break; + } + + pGameObj->SetRespawnTime(duration > 0 ? duration / IN_MILLISECONDS : 0); + + pGameObj->SetOwnerGuid(m_caster->GetObjectGuid()); + + pGameObj->SetUInt32Value(GAMEOBJECT_LEVEL, m_caster->getLevel()); + pGameObj->SetSpellId(m_spellInfo->Id); + + DEBUG_LOG("AddObject at SpellEfects.cpp EffectTransmitted"); + // m_caster->AddGameObject(pGameObj); + // m_ObjToDel.push_back(pGameObj); + + cMap->Add(pGameObj); + + pGameObj->SummonLinkedTrapIfAny(); + + if (m_caster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_caster)->AI()) + ((Creature*)m_caster)->AI()->JustSummoned(pGameObj); + if (m_originalCaster && m_originalCaster != m_caster && m_originalCaster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_originalCaster)->AI()) + ((Creature*)m_originalCaster)->AI()->JustSummoned(pGameObj); +} + +void Spell::EffectProspecting(SpellEffectEntry const* /*effect*/) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER || !itemTarget) + return; + + Player* p_caster = (Player*)m_caster; + + if (sWorld.getConfig(CONFIG_BOOL_SKILL_PROSPECTING)) + { + uint32 SkillValue = p_caster->GetPureSkillValue(SKILL_JEWELCRAFTING); + uint32 reqSkillValue = itemTarget->GetProto()->RequiredSkillRank; + p_caster->UpdateGatherSkill(SKILL_JEWELCRAFTING, SkillValue, reqSkillValue); + } + + ((Player*)m_caster)->SendLoot(itemTarget->GetObjectGuid(), LOOT_PROSPECTING); +} + +void Spell::EffectMilling(SpellEffectEntry const* /*effect*/) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER || !itemTarget) + return; + + Player* p_caster = (Player*)m_caster; + + if (sWorld.getConfig(CONFIG_BOOL_SKILL_MILLING)) + { + uint32 SkillValue = p_caster->GetPureSkillValue(SKILL_INSCRIPTION); + uint32 reqSkillValue = itemTarget->GetProto()->RequiredSkillRank; + p_caster->UpdateGatherSkill(SKILL_INSCRIPTION, SkillValue, reqSkillValue); + } + + ((Player*)m_caster)->SendLoot(itemTarget->GetObjectGuid(), LOOT_MILLING); +} + +void Spell::EffectSkill(SpellEffectEntry const* /*effect*/) +{ + DEBUG_LOG("WORLD: SkillEFFECT"); +} + +void Spell::EffectSpiritHeal(SpellEffectEntry const* /*effect*/) +{ + // TODO player can't see the heal-animation - he should respawn some ticks later + if (!unitTarget || unitTarget->isAlive()) + return; + if (unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + if (!unitTarget->IsInWorld()) + return; + if (m_spellInfo->Id == 22012 && !unitTarget->HasAura(2584)) + return; + + ((Player*)unitTarget)->ResurrectPlayer(1.0f); + ((Player*)unitTarget)->SpawnCorpseBones(); +} + +// remove insignia spell effect +void Spell::EffectSkinPlayerCorpse(SpellEffectEntry const* /*effect*/) +{ + DEBUG_LOG("Effect: SkinPlayerCorpse"); + if ((m_caster->GetTypeId() != TYPEID_PLAYER) || (unitTarget->GetTypeId() != TYPEID_PLAYER) || (unitTarget->isAlive())) + return; + + ((Player*)unitTarget)->RemovedInsignia((Player*)m_caster); +} + +void Spell::EffectStealBeneficialBuff(SpellEffectEntry const* effect) +{ + DEBUG_LOG("Effect: StealBeneficialBuff"); + + if (!unitTarget || unitTarget == m_caster) // can't steal from self + return; + + typedef std::vector StealList; + StealList steal_list; + // Create dispel mask by dispel type + uint32 dispelMask = GetDispellMask( DispelType(effect->EffectMiscValue) ); + Unit::SpellAuraHolderMap const& auras = unitTarget->GetSpellAuraHolderMap(); + for (Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + SpellAuraHolder *holder = itr->second; + if (holder && (1<GetSpellProto()->GetDispel()) & dispelMask) + { + // Need check for passive? this + if (holder->IsPositive() && !holder->IsPassive() && !holder->GetSpellProto()->HasAttribute(SPELL_ATTR_EX4_NOT_STEALABLE)) + steal_list.push_back(holder); + } + } + // Ok if exist some buffs for dispel try dispel it + if (!steal_list.empty()) + { + typedef std::list < std::pair > SuccessList; + SuccessList success_list; + int32 list_size = steal_list.size(); + // Dispell N = damage buffs (or while exist buffs for dispel) + for (int32 count = 0; count < damage && list_size > 0; ++count) + { + // Random select buff for dispel + SpellAuraHolder* holder = steal_list[urand(0, list_size - 1)]; + // Not use chance for steal + // TODO possible need do it + success_list.push_back(SuccessList::value_type(holder->GetId(), holder->GetCasterGuid())); + + // Remove buff from list for prevent doubles + for (StealList::iterator j = steal_list.begin(); j != steal_list.end();) + { + SpellAuraHolder* stealed = *j; + if (stealed->GetId() == holder->GetId() && stealed->GetCasterGuid() == holder->GetCasterGuid()) + { + j = steal_list.erase(j); + --list_size; + } + else + ++j; + } + } + // Really try steal and send log + if (!success_list.empty()) + { + int32 count = success_list.size(); + WorldPacket data(SMSG_SPELLSTEALLOG, 8 + 8 + 4 + 1 + 4 + count * 5); + data << unitTarget->GetPackGUID(); // Victim GUID + data << m_caster->GetPackGUID(); // Caster GUID + data << uint32(m_spellInfo->Id); // Dispell spell id + data << uint8(0); // not used + data << uint32(count); // count + for (SuccessList::iterator j = success_list.begin(); j != success_list.end(); ++j) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(j->first); + data << uint32(spellInfo->Id); // Spell Id + data << uint8(0); // 0 - steals !=0 transfers + unitTarget->RemoveAurasDueToSpellBySteal(spellInfo->Id, j->second, m_caster); + } + m_caster->SendMessageToSet(&data, true); + } + } +} + +void Spell::EffectWMODamage(SpellEffectEntry const* effect) +{ + DEBUG_LOG("Effect: WMODamage"); + + if (!gameObjTarget || gameObjTarget->GetGoType() != GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING) + { + sLog.outError("Spell::EffectWMODamage called without valid targets. Spell Id %u", m_spellInfo->Id); + return; + } + + if (!gameObjTarget->GetHealth()) + return; + + Unit* caster = GetAffectiveCaster(); + if (!caster) + return; + + DEBUG_LOG("Spell::EffectWMODamage, spell Id %u, go entry %u, damage %u", m_spellInfo->Id, gameObjTarget->GetEntry(), uint32(damage)); + gameObjTarget->DealGameObjectDamage(uint32(damage), m_spellInfo->Id, caster); +} + +void Spell::EffectWMORepair(SpellEffectEntry const* effect) +{ + DEBUG_LOG("Effect: WMORepair"); + + if (!gameObjTarget || gameObjTarget->GetGoType() != GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING) + { + sLog.outError("Spell::EffectWMORepair called without valid targets. Spell Id %u", m_spellInfo->Id); + return; + } + + Unit* caster = GetAffectiveCaster(); + if (!caster) + return; + + DEBUG_LOG("Spell::EffectWMORepair, spell Id %u, go entry %u", m_spellInfo->Id, gameObjTarget->GetEntry()); + gameObjTarget->RebuildGameObject(m_spellInfo->Id, caster); +} + +void Spell::EffectWMOChange(SpellEffectEntry const* effect) +{ + DEBUG_LOG("Effect: WMOChange"); + + if (!gameObjTarget || gameObjTarget->GetGoType() != GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING) + { + sLog.outError("Spell::EffectWMOChange called without valid targets. Spell Id %u", m_spellInfo->Id); + return; + } + + DEBUG_LOG("Spell::EffectWMOChange, spell Id %u, object %u, misc-value %u", m_spellInfo->Id, gameObjTarget->GetEntry(), effect->EffectMiscValue); + + Unit* caster = GetAffectiveCaster(); + if (!caster) + return; + + switch (effect->EffectMiscValue) + { + case 0: // Set to full health + gameObjTarget->ForceGameObjectHealth(gameObjTarget->GetMaxHealth(), caster); + break; + case 1: // Set to damaged + gameObjTarget->ForceGameObjectHealth(gameObjTarget->GetGOInfo()->destructibleBuilding.damagedNumHits, caster); + break; + case 2: // Set to destroyed + gameObjTarget->ForceGameObjectHealth(-int32(gameObjTarget->GetHealth()), caster); + break; + case 3: // Set to rebuilding + gameObjTarget->ForceGameObjectHealth(0, caster); + break; + default: + sLog.outError("Spell::EffectWMOChange, spell Id %u with undefined change value %u", m_spellInfo->Id, effect->EffectMiscValue); + break; + } +} + +void Spell::EffectKillCreditPersonal(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)unitTarget)->KilledMonsterCredit(effect->EffectMiscValue); +} + +void Spell::EffectKillCreditGroup(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)unitTarget)->RewardPlayerAndGroupAtEvent(effect->EffectMiscValue, unitTarget); +} + +void Spell::EffectQuestFail(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)unitTarget)->FailQuest(effect->EffectMiscValue); +} + +void Spell::EffectActivateRune(SpellEffectEntry const* effect) +{ + if (m_caster->GetTypeId() != TYPEID_PLAYER) + return; + + Player* plr = (Player*)m_caster; + + if (plr->getClass() != CLASS_DEATH_KNIGHT) + return; + + int32 count = damage; // max amount of reset runes + plr->ResyncRunes(); +} + +void Spell::EffectTitanGrip(SpellEffectEntry const* effect) +{ + // Make sure "Titan's Grip" (49152) penalty spell does not silently change + if (effect->EffectMiscValue != 49152) + sLog.outError("Spell::EffectTitanGrip: Spell %u has unexpected EffectMiscValue '%u'", m_spellInfo->Id, effect->EffectMiscValue); + if (unitTarget && unitTarget->GetTypeId() == TYPEID_PLAYER) + { + Player* plr = (Player*)m_caster; + plr->SetCanTitanGrip(true); + if (plr->HasTwoHandWeaponInOneHand() && !plr->HasAura(49152)) + plr->CastSpell(plr, 49152, true); + } +} + +void Spell::EffectRenamePet(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT || + !((Creature*)unitTarget)->IsPet() || ((Pet*)unitTarget)->getPetType() != HUNTER_PET) + return; + + unitTarget->RemoveByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED); +} + +void Spell::EffectPlaySound(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 soundId = effect->EffectMiscValue; + if (!sSoundEntriesStore.LookupEntry(soundId)) + { + sLog.outError("EffectPlaySound: Sound (Id: %u) in spell %u does not exist.", soundId, m_spellInfo->Id); + return; + } + + unitTarget->PlayDirectSound(soundId, (Player*)unitTarget); +} + +void Spell::EffectPlayMusic(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 soundId = effect->EffectMiscValue; + if (!sSoundEntriesStore.LookupEntry(soundId)) + { + sLog.outError("EffectPlayMusic: Sound (Id: %u) in spell %u does not exist.", soundId, m_spellInfo->Id); + return; + } + + WorldPacket data(SMSG_PLAY_MUSIC, 4); + data << uint32(soundId); + data << ObjectGuid(); + ((Player*)unitTarget)->GetSession()->SendPacket(&data); +} + +void Spell::EffectSpecCount(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + ((Player*)unitTarget)->UpdateSpecCount(damage); +} + +void Spell::EffectActivateSpec(SpellEffectEntry const* /*effect*/) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + uint32 spec = damage - 1; + + ((Player*)unitTarget)->ActivateSpec(spec); +} + +void Spell::EffectBind(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + Player* player = (Player*)unitTarget; + + uint32 area_id = uint32(effect->EffectMiscValue); + WorldLocation loc; + if (effect->EffectImplicitTargetA == TARGET_TABLE_X_Y_Z_COORDINATES || + effect->EffectImplicitTargetB == TARGET_TABLE_X_Y_Z_COORDINATES) + { + SpellTargetPosition const* st = sSpellMgr.GetSpellTargetPosition(m_spellInfo->Id); + if (!st) + { + sLog.outError("Spell::EffectBind - unknown Teleport coordinates for spell ID %u", m_spellInfo->Id); + return; + } + + loc.mapid = st->target_mapId; + loc.coord_x = st->target_X; + loc.coord_y = st->target_Y; + loc.coord_z = st->target_Z; + loc.orientation = st->target_Orientation; + if (!area_id) + area_id = sTerrainMgr.GetAreaId(loc.mapid, loc.coord_x, loc.coord_y, loc.coord_z); + } + else + { + player->GetPosition(loc); + if (!area_id) + area_id = player->GetAreaId(); + } + + player->SetHomebindToLocation(loc, area_id); + + // binding + WorldPacket data(SMSG_BINDPOINTUPDATE, (4 + 4 + 4 + 4 + 4)); + data << float(loc.coord_x); + data << float(loc.coord_y); + data << float(loc.coord_z); + data << uint32(loc.mapid); + data << uint32(area_id); + player->SendDirectMessage(&data); + + DEBUG_LOG("New Home Position for %s: XYZ: %f %f %f on Map %u", player->GetGuidStr().c_str(), loc.coord_x, loc.coord_y, loc.coord_z, loc.mapid); + + // zone update + data.Initialize(SMSG_PLAYERBOUND, 8 + 4); + data << m_caster->GetObjectGuid(); + data << uint32(area_id); + player->SendDirectMessage(&data); +} + +void Spell::EffectRestoreItemCharges(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + Player* player = (Player*)unitTarget; + + ItemPrototype const* itemProto = ObjectMgr::GetItemPrototype(effect->EffectItemType); + if (!itemProto) + return; + + // In case item from limited category recharge any from category, is this valid checked early in spell checks + Item* item; + if (itemProto->ItemLimitCategory) + item = ((Player*)unitTarget)->GetItemByLimitedCategory(itemProto->ItemLimitCategory); + else + item = player->GetItemByEntry(effect->EffectItemType); + + if (!item) + return; + + item->RestoreCharges(); +} + +void Spell::EffectRedirectThreat(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + + if (m_spellInfo->Id == 59665) // Vigilance + if (Aura* glyph = unitTarget->GetDummyAura(63326)) // Glyph of Vigilance + damage += glyph->GetModifier()->m_amount; + + m_caster->getHostileRefManager().SetThreatRedirection(unitTarget->GetObjectGuid(), uint32(damage)); +} + +void Spell::EffectTeachTaxiNode(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + Player* player = (Player*)unitTarget; + + uint32 taxiNodeId = effect->EffectMiscValue; + if (!sTaxiNodesStore.LookupEntry(taxiNodeId)) + return; + + if (player->m_taxi.SetTaximaskNode(taxiNodeId)) + { + WorldPacket data(SMSG_NEW_TAXI_PATH, 0); + player->SendDirectMessage(&data); + + data.Initialize(SMSG_TAXINODE_STATUS, 9); + data << m_caster->GetObjectGuid(); + data << uint8(1); + player->SendDirectMessage(&data); + } +} + +void Spell::EffectQuestOffer(SpellEffectEntry const* effect) +{ + if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) + return; + + if (Quest const* quest = sObjectMgr.GetQuestTemplate(effect->EffectMiscValue)) + { + Player* player = (Player*)unitTarget; + + if (player->CanTakeQuest(quest, false)) + player->PlayerTalkClass->SendQuestGiverQuestDetails(quest, player->GetObjectGuid(), true); + } +} + +void Spell::EffectCancelAura(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + + uint32 spellId = effect->EffectTriggerSpell; + + if (!sSpellStore.LookupEntry(spellId)) + { + sLog.outError("Spell::EffectCancelAura: spell %u doesn't exist", spellId); + return; + } + + unitTarget->RemoveAurasDueToSpell(spellId); +} + +void Spell::EffectKnockBackFromPosition(SpellEffectEntry const* effect) +{ + if (!unitTarget) + return; + + float x, y, z; + if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) + m_targets.getDestination(x, y, z); + else + m_caster->GetPosition(x, y, z); + + float angle = unitTarget->GetAngle(x, y) + M_PI_F; + float horizontalSpeed = effect->EffectMiscValue * 0.1f; + float verticalSpeed = damage * 0.1f; + unitTarget->KnockBackWithAngle(angle, horizontalSpeed, verticalSpeed); +}