diff --git a/Player.cpp b/Player.cpp
new file mode 100644
index 0000000..5cc24a8
--- /dev/null
+++ b/Player.cpp
@@ -0,0 +1,16415 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (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 Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include "Player.h"
+#include "AccountMgr.h"
+#include "AchievementMgr.h"
+#include "ArenaSpectator.h"
+#include "ArenaTeam.h"
+#include "ArenaTeamMgr.h"
+#include "Battlefield.h"
+#include "BattlefieldMgr.h"
+#include "BattlefieldWG.h"
+#include "Battleground.h"
+#include "BattlegroundAV.h"
+#include "BattlegroundMgr.h"
+#include "CellImpl.h"
+#include "Channel.h"
+#include "CharacterCache.h"
+#include "CharacterDatabaseCleaner.h"
+#include "Chat.h"
+#include "CombatLogPackets.h"
+#include "Common.h"
+#include "ConditionMgr.h"
+#include "Config.h"
+#include "CreatureAI.h"
+#include "DatabaseEnv.h"
+#include "DisableMgr.h"
+#include "Formulas.h"
+#include "GameEventMgr.h"
+#include "GameGraveyard.h"
+#include "GameObjectAI.h"
+#include "GameTime.h"
+#include "GossipDef.h"
+#include "GridNotifiers.h"
+#include "Group.h"
+#include "GroupMgr.h"
+#include "Guild.h"
+#include "GuildMgr.h"
+#include "InstanceSaveMgr.h"
+#include "InstanceScript.h"
+#include "LFGMgr.h"
+#include "Language.h"
+#include "Log.h"
+#include "LootItemStorage.h"
+#include "MapMgr.h"
+#include "MiscPackets.h"
+#include "ObjectAccessor.h"
+#include "ObjectMgr.h"
+#include "Opcodes.h"
+#include "OutdoorPvP.h"
+#include "OutdoorPvPMgr.h"
+#include "Pet.h"
+#include "PetitionMgr.h"
+#include "QueryHolder.h"
+#include "QuestDef.h"
+#include "Realm.h"
+#include "ReputationMgr.h"
+#include "ScriptMgr.h"
+#include "SocialMgr.h"
+#include "Spell.h"
+#include "SpellAuraEffects.h"
+#include "SpellAuras.h"
+#include "SpellMgr.h"
+#include "TicketMgr.h"
+#include "Transport.h"
+#include "UpdateData.h"
+#include "UpdateFieldFlags.h"
+#include "UpdateMask.h"
+#include "Util.h"
+#include "Vehicle.h"
+#include "Weather.h"
+#include "World.h"
+#include "WorldPacket.h"
+#include "WorldSession.h"
+#include "Tokenize.h"
+#include "StringConvert.h"
+
+// TODO: this import is not necessary for compilation and marked as unused by the IDE
+// however, for some reasons removing it would cause a damn linking issue
+// there is probably some underlying problem with imports which should properly addressed
+// see: https://github.com/azerothcore/azerothcore-wotlk/issues/9766
+#include "GridNotifiersImpl.h"
+
+//npcbot
+#include "botmgr.h"
+#include "botdatamgr.h"
+//end npcbot
+
+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...
+};
+
+static uint32 copseReclaimDelay[MAX_DEATH_COUNT] = { 30, 60, 120 };
+
+// we can disable this warning for this since it only
+// causes undefined behavior when passed to the base class constructor
+#ifdef _MSC_VER
+#pragma warning(disable:4355)
+#endif
+Player::Player(WorldSession* session): Unit(true), m_mover(this)
+{
+#ifdef _MSC_VER
+#pragma warning(default:4355)
+#endif
+
+ m_objectType |= TYPEMASK_PLAYER;
+ m_objectTypeId = TYPEID_PLAYER;
+
+ m_valuesCount = PLAYER_END;
+
+ m_session = session;
+
+ m_ingametime = 0;
+
+ m_ExtraFlags = 0;
+
+ m_spellModTakingSpell = nullptr;
+ //m_pad = 0;
+
+ // players always accept
+ if (AccountMgr::IsPlayerAccount(GetSession()->GetSecurity()))
+ SetAcceptWhispers(true);
+
+ m_usedTalentCount = 0;
+ m_questRewardTalentCount = 0;
+ m_extraBonusTalentCount = 0;
+
+ m_regenTimer = 0;
+ m_regenTimerCount = 0;
+ m_foodEmoteTimerCount = 0;
+ m_weaponChangeTimer = 0;
+
+ m_zoneUpdateId = uint32(-1);
+ m_zoneUpdateTimer = 0;
+
+ m_nextSave = sWorld->getIntConfig(CONFIG_INTERVAL_SAVE);
+
+ m_areaUpdateId = 0;
+ m_team = TEAM_NEUTRAL;
+
+ m_needZoneUpdate = false;
+
+ m_additionalSaveTimer = 0;
+ m_additionalSaveMask = 0;
+ m_hostileReferenceCheckTimer = 15000;
+
+ clearResurrectRequestData();
+
+ memset(m_items, 0, sizeof(Item*)*PLAYER_SLOTS_COUNT);
+
+ m_social = nullptr;
+
+ // group is initialized in the reference constructor
+ SetGroupInvite(nullptr);
+ m_groupUpdateMask = 0;
+ m_auraRaidUpdateMask = 0;
+ m_bPassOnGroupLoot = false;
+
+ m_GuildIdInvited = 0;
+ m_ArenaTeamIdInvited = 0;
+
+ m_atLoginFlags = AT_LOGIN_NONE;
+
+ mSemaphoreTeleport_Near = 0;
+ mSemaphoreTeleport_Far = 0;
+
+ m_DelayedOperations = 0;
+ m_bMustDelayTeleport = false;
+ m_bHasDelayedTeleport = false;
+ teleportStore_options = 0;
+ m_canTeleport = false;
+ m_canKnockback = false;
+
+ m_trade = nullptr;
+
+ m_cinematic = 0;
+
+ PlayerTalkClass = new PlayerMenu(GetSession());
+ m_currentBuybackSlot = BUYBACK_SLOT_START;
+
+ m_DailyQuestChanged = false;
+ m_lastDailyQuestTime = 0;
+
+ for (uint8 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_deathTimer = 0;
+ m_deathExpireTime = 0;
+
+ m_flightSpellActivated = 0;
+
+ m_swingErrorMsg = 0;
+
+ for (uint8 j = 0; j < PLAYER_MAX_BATTLEGROUND_QUEUES; ++j)
+ {
+ _BgBattlegroundQueueID[j].bgQueueTypeId = BATTLEGROUND_QUEUE_NONE;
+ _BgBattlegroundQueueID[j].invitedToInstance = 0;
+ }
+
+ m_logintime = GameTime::GetGameTime().count();
+ m_Last_tick = m_logintime;
+ m_Played_time[PLAYED_TIME_TOTAL] = 0;
+ m_Played_time[PLAYED_TIME_LEVEL] = 0;
+ m_WeaponProficiency = 0;
+ m_ArmorProficiency = 0;
+ m_canParry = false;
+ m_canBlock = false;
+ m_canTitanGrip = false;
+ m_ammoDPS = 0.0f;
+
+ m_temporaryUnsummonedPetNumber = 0;
+ //cache for UNIT_CREATED_BY_SPELL to allow
+ //returning reagents for temporarily removed pets
+ //when dying/logging out
+ m_oldpetspell = 0;
+ m_lastpetnumber = 0;
+
+ ////////////////////Rest System/////////////////////
+ _restTime = 0;
+ _innTriggerId = 0;
+ _restBonus = 0;
+ _restFlagMask = 0;
+ ////////////////////Rest System/////////////////////
+
+ m_mailsUpdated = false;
+ unReadMails = 0;
+ m_nextMailDelivereTime = time_t(0);
+
+ m_resetTalentsCost = 0;
+ m_resetTalentsTime = 0;
+ m_itemUpdateQueueBlocked = false;
+
+ for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
+ m_forced_speed_changes[i] = 0;
+
+ /////////////////// Instance System /////////////////////
+
+ m_HomebindTimer = 0;
+ m_InstanceValid = true;
+ m_dungeonDifficulty = DUNGEON_DIFFICULTY_NORMAL;
+ m_raidDifficulty = RAID_DIFFICULTY_10MAN_NORMAL;
+ m_raidMapDifficulty = RAID_DIFFICULTY_10MAN_NORMAL;
+
+ m_lastPotionId = 0;
+
+ m_activeSpec = 0;
+ m_specsCount = 1;
+
+ for (uint8 i = 0; i < MAX_TALENT_SPECS; ++i)
+ {
+ for (uint8 g = 0; g < MAX_GLYPH_SLOT_INDEX; ++g)
+ m_Glyphs[i][g] = 0;
+ }
+
+ for (uint8 i = 0; i < BASEMOD_END; ++i)
+ {
+ m_auraBaseMod[i][FLAT_MOD] = 0.0f;
+ m_auraBaseMod[i][PCT_MOD] = 1.0f;
+ }
+
+ for (uint8 i = 0; i < MAX_COMBAT_RATING; i++)
+ m_baseRatingValue[i] = 0;
+
+ m_baseSpellPower = 0;
+ m_baseFeralAP = 0;
+ m_baseManaRegen = 0;
+ m_baseHealthRegen = 0;
+ m_spellPenetrationItemMod = 0;
+
+ // Honor System
+ m_lastHonorUpdateTime = GameTime::GetGameTime().count();
+
+ m_IsBGRandomWinner = false;
+
+ // 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_summon_asSpectator = false;
+
+ //m_mover = this;
+ m_movedByPlayer.Initialize(this);
+ m_seer = this;
+
+ m_recallMap = 0;
+ m_recallX = 0;
+ m_recallY = 0;
+ m_recallZ = 0;
+ m_recallO = 0;
+
+ m_homebindMapId = 0;
+ m_homebindAreaId = 0;
+ m_homebindX = 0;
+ m_homebindY = 0;
+ m_homebindZ = 0;
+ m_homebindO = 0;
+
+ m_contestedPvPTimer = 0;
+
+ m_declinedname = nullptr;
+
+ m_isActive = true;
+
+ m_runes = nullptr;
+
+ m_lastFallTime = 0;
+ m_lastFallZ = 0;
+
+ m_grantableLevels = 0;
+
+ m_ControlledByPlayer = true;
+
+ sWorld->IncreasePlayerCount();
+
+ m_ChampioningFaction = 0;
+
+ for (uint8 i = 0; i < MAX_POWERS; ++i)
+ m_powerFraction[i] = 0;
+
+ isDebugAreaTriggers = false;
+
+ m_WeeklyQuestChanged = false;
+
+ m_MonthlyQuestChanged = false;
+
+ m_SeasonalQuestChanged = false;
+
+ SetPendingBind(0, 0);
+
+ _activeCheats = CHEAT_NONE;
+
+ _cinematicMgr = new CinematicMgr(this);
+
+ m_achievementMgr = new AchievementMgr(this);
+ m_reputationMgr = new ReputationMgr(this);
+
+ /////////////// NPCBot System //////////////////
+ _botMgr = nullptr;
+ ///////////// End NPCBot System ////////////////
+
+ // Ours
+ m_NeedToSaveGlyphs = false;
+ m_MountBlockId = 0;
+ m_realDodge = 0.0f;
+ m_realParry = 0.0f;
+ m_pendingSpectatorForBG = 0;
+ m_pendingSpectatorInviteInstanceId = 0;
+
+ m_charmUpdateTimer = 0;
+
+ for( int i = 0; i < NUM_CAI_SPELLS; ++i )
+ m_charmAISpells[i] = 0;
+
+ m_applyResilience = true;
+
+ m_isInstantFlightOn = true;
+
+ _wasOutdoor = true;
+ sScriptMgr->OnConstructPlayer(this);
+}
+
+Player::~Player()
+{
+ sScriptMgr->OnDestructPlayer(this);
+
+ // it must be unloaded already in PlayerLogout and accessed only for loggined player
+ //m_social = nullptr;
+
+ // Note: buy back item already deleted from DB when player was saved
+ for (uint8 i = 0; i < PLAYER_SLOTS_COUNT; ++i)
+ delete m_items[i];
+
+ for (PlayerSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr)
+ delete itr->second;
+
+ for (PlayerTalentMap::const_iterator itr = m_talents.begin(); itr != m_talents.end(); ++itr)
+ delete itr->second;
+
+ //all mailed items should be deleted, also all mail should be deallocated
+ for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
+ {
+ delete *itr;
+ }
+
+ for (ItemMap::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;
+
+ for (size_t x = 0; x < ItemSetEff.size(); x++)
+ delete ItemSetEff[x];
+
+ delete m_declinedname;
+ delete m_runes;
+ delete m_achievementMgr;
+ delete m_reputationMgr;
+
+ //npcbot
+ if (_botMgr)
+ {
+ delete _botMgr;
+ _botMgr = nullptr;
+ }
+ //end npcbot
+
+ sWorld->DecreasePlayerCount();
+
+ if (!m_isInSharedVisionOf.empty())
+ {
+ do
+ {
+ Unit* u = *(m_isInSharedVisionOf.begin());
+ u->RemovePlayerFromVision(this);
+ } while (!m_isInSharedVisionOf.empty());
+ }
+}
+
+void Player::CleanupsBeforeDelete(bool finalCleanup)
+{
+ TradeCancel(false);
+ DuelComplete(DUEL_INTERRUPTED);
+
+ Unit::CleanupsBeforeDelete(finalCleanup);
+}
+
+bool Player::Create(ObjectGuid::LowType guidlow, CharacterCreateInfo* createInfo)
+{
+ // FIXME: outfitId not used in player creating
+ // TODO: need more checks against packet modifications
+ // should check that skin, face, hair* are valid via DBC per race/class
+ // also do it in Player::BuildEnumData, Player::LoadFromDB
+
+ Object::_Create(guidlow, 0, HighGuid::Player);
+
+ m_name = createInfo->Name;
+
+ PlayerInfo const* info = sObjectMgr->GetPlayerInfo(createInfo->Race, createInfo->Class);
+ if (!info)
+ {
+ LOG_ERROR("entities.player", "Player::Create: Possible hacking-attempt: Account {} tried creating a character named '{}' with an invalid race/class pair ({}/{}) - refusing to do so.",
+ GetSession()->GetAccountId(), m_name, createInfo->Race, createInfo->Class);
+ return false;
+ }
+
+ for (uint8 i = 0; i < PLAYER_SLOTS_COUNT; i++)
+ m_items[i] = nullptr;
+
+ Relocate(info->positionX, info->positionY, info->positionZ, info->orientation);
+
+ ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(createInfo->Class);
+ if (!cEntry)
+ {
+ LOG_ERROR("entities.player", "Player::Create: Possible hacking-attempt: Account {} tried creating a character named '{}' with an invalid character class ({}) - refusing to do so (wrong DBC-files?)",
+ GetSession()->GetAccountId(), m_name, createInfo->Class);
+ return false;
+ }
+
+ SetMap(sMapMgr->CreateMap(info->mapId, this));
+
+ uint8 powertype = cEntry->powerType;
+
+ SetObjectScale(1.0f);
+
+ m_realRace = createInfo->Race; // set real race flag
+ m_race = createInfo->Race; // set real race flag
+
+ SetFactionForRace(createInfo->Race);
+
+ if (!IsValidGender(createInfo->Gender))
+ {
+ LOG_ERROR("entities.player", "Player::Create: Possible hacking-attempt: Account {} tried creating a character named '{}' with an invalid gender ({}) - refusing to do so",
+ GetSession()->GetAccountId(), m_name, createInfo->Gender);
+ return false;
+ }
+
+ uint32 RaceClassGender = (createInfo->Race) | (createInfo->Class << 8) | (createInfo->Gender << 16);
+
+ SetUInt32Value(UNIT_FIELD_BYTES_0, (RaceClassGender | (powertype << 24)));
+ InitDisplayIds();
+ if (sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_PVP || sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_RPPVP)
+ {
+ SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_PVP);
+ SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED);
+ }
+ SetUnitFlag2(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
+
+ // -1 is default value
+ SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, uint32(-1));
+
+ SetUInt32Value(PLAYER_BYTES, (createInfo->Skin | (createInfo->Face << 8) | (createInfo->HairStyle << 16) | (createInfo->HairColor << 24)));
+ SetUInt32Value(PLAYER_BYTES_2, (createInfo->FacialHair |
+ (0x00 << 8) |
+ (0x00 << 16) |
+ (((GetSession()->IsARecruiter() || GetSession()->GetRecruiterId() != 0) ? REST_STATE_RAF_LINKED : REST_STATE_NOT_RAF_LINKED) << 24)));
+ SetByteValue(PLAYER_BYTES_3, 0, createInfo->Gender);
+ SetByteValue(PLAYER_BYTES_3, 3, 0); // BattlefieldArenaFaction (0 or 1)
+
+ SetUInt32Value(PLAYER_GUILDID, 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_HONORABLE_KILLS, 0);
+ SetUInt32Value(PLAYER_FIELD_TODAY_CONTRIBUTION, 0);
+ SetUInt32Value(PLAYER_FIELD_YESTERDAY_CONTRIBUTION, 0);
+
+ // set starting level
+ uint32 start_level = getClass() != CLASS_DEATH_KNIGHT
+ ? sWorld->getIntConfig(CONFIG_START_PLAYER_LEVEL)
+ : sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL);
+
+ if (!AccountMgr::IsPlayerAccount(GetSession()->GetSecurity()))
+ {
+ uint32 gm_level = sWorld->getIntConfig(CONFIG_START_GM_LEVEL);
+ if (gm_level > start_level)
+ start_level = gm_level;
+ }
+
+ SetUInt32Value(UNIT_FIELD_LEVEL, start_level);
+
+ InitRunes();
+
+ SetUInt32Value(PLAYER_FIELD_COINAGE, getClass() != CLASS_DEATH_KNIGHT
+ ? sWorld->getIntConfig(CONFIG_START_PLAYER_MONEY)
+ : sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_MONEY));
+ SetHonorPoints(sWorld->getIntConfig(CONFIG_START_HONOR_POINTS));
+ SetArenaPoints(sWorld->getIntConfig(CONFIG_START_ARENA_POINTS));
+
+ // Played time
+ m_Last_tick = GameTime::GetGameTime().count();
+ 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)
+ SetFullHealth();
+ if (getPowerType() == POWER_MANA)
+ {
+ UpdateMaxPower(POWER_MANA); // Update max Mana (for add bonus from intellect)
+ SetPower(POWER_MANA, GetMaxPower(POWER_MANA));
+ }
+
+ if (getPowerType() == POWER_RUNIC_POWER)
+ {
+ SetPower(POWER_RUNE, 8);
+ SetMaxPower(POWER_RUNE, 8);
+ SetPower(POWER_RUNIC_POWER, 0);
+ SetMaxPower(POWER_RUNIC_POWER, 1000);
+ }
+
+ // original spells
+ LearnDefaultSkills();
+ LearnCustomSpells();
+
+ // original action bar
+ for (PlayerCreateInfoActions::const_iterator action_itr = info->action.begin(); action_itr != info->action.end(); ++action_itr)
+ addActionButton(action_itr->button, action_itr->action, action_itr->type);
+
+ // original items
+ if (CharStartOutfitEntry const* oEntry = GetCharStartOutfitEntry(createInfo->Race, createInfo->Class, createInfo->Gender))
+ {
+ for (int j = 0; j < MAX_OUTFIT_ITEMS; ++j)
+ {
+ if (oEntry->ItemId[j] <= 0)
+ continue;
+
+ uint32 itemId = oEntry->ItemId[j];
+
+ // just skip, reported in ObjectMgr::LoadItemTemplates
+ ItemTemplate const* iProto = sObjectMgr->GetItemTemplate(itemId);
+ if (!iProto)
+ continue;
+
+ // BuyCount by default
+ uint32 count = iProto->BuyCount;
+
+ // special amount for food/drink
+ if (iProto->Class == ITEM_CLASS_CONSUMABLE && iProto->SubClass == ITEM_SUBCLASS_FOOD)
+ {
+ switch (iProto->Spells[0].SpellCategory)
+ {
+ case SPELL_CATEGORY_FOOD: // food
+ count = getClass() == CLASS_DEATH_KNIGHT ? 10 : 4;
+ break;
+ case SPELL_CATEGORY_DRINK: // drink
+ count = 2;
+ break;
+ }
+ if (iProto->GetMaxStackSize() < count)
+ count = iProto->GetMaxStackSize();
+ }
+ StoreNewItemInBestSlots(itemId, 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 (uint8 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
+
+ CheckAllAchievementCriteria();
+
+ return true;
+}
+
+bool Player::StoreNewItemInBestSlots(uint32 titem_id, uint32 titem_amount)
+{
+ LOG_DEBUG("entities.player.items", "STORAGE: Creating initial item, itemId = {}, count = {}", titem_id, titem_amount);
+
+ // attempt equip by one
+ while (titem_amount > 0)
+ {
+ uint16 eDest;
+ InventoryResult 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)
+ InventoryResult msg = CanStoreNewItem(INVENTORY_SLOT_BAG_0, NULL_SLOT, sDest, titem_id, titem_amount);
+ if (msg == EQUIP_ERR_OK)
+ {
+ StoreNewItem(sDest, titem_id, true);
+ return true; // stored
+ }
+
+ // item can't be added
+ LOG_ERROR("entities.player", "STORAGE: Can't equip or store initial item {} for race {} class {}, error msg = {}", titem_id, getRace(true), getClass(), msg);
+ return false;
+}
+
+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;
+ }
+ SendDirectMessage(WorldPackets::Misc::StartMirrorTimer(Type, CurrentValue, MaxValue, Regen, 0, 0).Write());
+}
+
+void Player::StopMirrorTimer(MirrorTimerType Type)
+{
+ m_MirrorTimer[Type] = DISABLED_MIRROR_TIMER;
+ SendDirectMessage(WorldPackets::Misc::StopMirrorTimer(Type).Write());
+}
+
+bool Player::IsImmuneToEnvironmentalDamage()
+{
+ // check for GM and death state included in isAttackableByAOE
+ return (!isTargetableForAttack(false, nullptr)) || isTotalImmune();
+}
+
+uint32 Player::EnvironmentalDamage(EnviromentalDamage type, uint32 damage)
+{
+ if (IsImmuneToEnvironmentalDamage())
+ return 0;
+
+ // Absorb, resist some environmental damage type
+ uint32 absorb = 0;
+ uint32 resist = 0;
+
+ switch (type)
+ {
+ case DAMAGE_LAVA:
+ case DAMAGE_SLIME:
+ {
+ DamageInfo dmgInfo(this, this, damage, nullptr, type == DAMAGE_LAVA ? SPELL_SCHOOL_MASK_FIRE : SPELL_SCHOOL_MASK_NATURE, DIRECT_DAMAGE);
+ Unit::CalcAbsorbResist(dmgInfo);
+ absorb = dmgInfo.GetAbsorb();
+ resist = dmgInfo.GetResist();
+ damage = dmgInfo.GetDamage();
+ }
+ default:
+ break;
+ }
+
+ Unit::DealDamageMods(this, damage, &absorb);
+
+ WorldPackets::CombatLog::EnvironmentalDamageLog packet;
+ packet.Victim = GetGUID();
+ packet.Type = type != DAMAGE_FALL_TO_VOID ? type : DAMAGE_FALL;
+ packet.Amount = damage;
+ packet.Absorbed = absorb;
+ packet.Resisted = resist;
+ SendMessageToSet(packet.Write(), true);
+
+ uint32 final_damage = Unit::DealDamage(this, this, damage, nullptr, SELF_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, nullptr, false);
+
+ if (!IsAlive())
+ {
+ if (type == DAMAGE_FALL) // DealDamage not apply item durability loss at self damage
+ {
+ LOG_DEBUG("entities.player", "Player::EnvironmentalDamage: Player '{}' ({}) fall to death, losing {} durability",
+ GetName(), GetGUID().ToString(), sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH));
+ DurabilityLossAll(sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH), false);
+ // durability lost message
+ SendDurabilityLoss();
+ }
+
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATHS_FROM, 1, type);
+ }
+
+ return final_damage;
+}
+
+int32 Player::getMaxTimer(MirrorTimerType timer)
+{
+ switch (timer)
+ {
+ case FATIGUE_TIMER:
+ return MINUTE * IN_MILLISECONDS;
+ case BREATH_TIMER:
+ {
+ if (!IsAlive() || HasAuraType(SPELL_AURA_WATER_BREATHING) || GetSession()->GetSecurity() >= AccountTypes(sWorld->getIntConfig(CONFIG_DISABLE_BREATHING)))
+ return DISABLED_MIRROR_TIMER;
+ int32 UnderWaterTime = sWorld->getIntConfig(CONFIG_WATER_BREATH_TIMER);
+ AuraEffectList const& mModWaterBreathing = GetAuraEffectsByType(SPELL_AURA_MOD_WATER_BREATHING);
+ for (AuraEffectList::const_iterator i = mModWaterBreathing.begin(); i != mModWaterBreathing.end(); ++i)
+ AddPct(UnderWaterTime, (*i)->GetAmount());
+ return UnderWaterTime;
+ }
+ case FIRE_TIMER:
+ {
+ if (!IsAlive())
+ return DISABLED_MIRROR_TIMER;
+ return 1 * IN_MILLISECONDS;
+ }
+ default:
+ return 0;
+ }
+}
+
+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 // If activated - do tick
+ {
+ m_MirrorTimer[BREATH_TIMER] -= time_diff;
+ // Timer limit - need deal damage
+ if (m_MirrorTimer[BREATH_TIMER] < 0)
+ {
+ m_MirrorTimer[BREATH_TIMER] += 1 * 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] += 1 * IN_MILLISECONDS;
+ if (IsAlive()) // Calculate and deal damage
+ {
+ uint32 damage = GetMaxHealth() / 5 + urand(0, GetLevel() - 1);
+ EnvironmentalDamage(DAMAGE_EXHAUSTED, damage);
+ }
+ else if (HasPlayerFlag(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*/) && !(_lastLiquid && _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] += 1 * 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 (uint8 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()
+{
+ m_drunkTimer = 0;
+
+ uint8 currentDrunkValue = GetDrunkValue();
+ uint8 drunk = currentDrunkValue ? --currentDrunkValue : 0;
+ SetDrunkValue(drunk);
+}
+
+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*/)
+{
+ bool isSobering = newDrunkValue < GetDrunkValue();
+ uint32 oldDrunkenState = Player::GetDrunkenstateByValue(GetDrunkValue());
+ if (newDrunkValue > 100)
+ newDrunkValue = 100;
+
+ // select drunk percent or total SPELL_AURA_MOD_FAKE_INEBRIATE amount, whichever is higher for visibility updates
+ int32 drunkPercent = std::max(newDrunkValue, GetTotalAuraModifier(SPELL_AURA_MOD_FAKE_INEBRIATE));
+ if (drunkPercent)
+ {
+ m_invisibilityDetect.AddFlag(INVISIBILITY_DRUNK);
+ m_invisibilityDetect.SetValue(INVISIBILITY_DRUNK, drunkPercent);
+ }
+ else if (!HasAuraType(SPELL_AURA_MOD_FAKE_INEBRIATE) && !newDrunkValue)
+ m_invisibilityDetect.DelFlag(INVISIBILITY_DRUNK);
+
+ uint32 newDrunkenState = Player::GetDrunkenstateByValue(newDrunkValue);
+ SetByteValue(PLAYER_BYTES_3, 1, newDrunkValue);
+ UpdateObjectVisibility(false);
+
+ if (!isSobering)
+ m_drunkTimer = 0; // reset sobering timer
+
+ if (newDrunkenState == oldDrunkenState)
+ return;
+
+ WorldPackets::Misc::CrossedInebriationThreshold data;
+ data.Guid = GetGUID();
+ data.Threshold = newDrunkenState;
+ data.ItemID = itemId;
+
+ SendMessageToSet(data.Write(), true);
+}
+
+void Player::setDeathState(DeathState s, bool /*despawn = false*/)
+{
+ uint32 ressSpellId = 0;
+
+ bool cur = IsAlive();
+
+ if (s == JUST_DIED)
+ {
+ if (!cur)
+ {
+ LOG_ERROR("entities.player", "setDeathState: attempt to kill a dead player {} ({})", GetName(), GetGUID().ToString());
+ return;
+ }
+
+ // drunken state is cleared on death
+ SetDrunkValue(0);
+ // lost combo points at any target (targeted combo points clear in Unit::setDeathState)
+ ClearComboPoints();
+
+ clearResurrectRequestData();
+
+ //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(nullptr, PET_SAVE_NOT_IN_SLOT, true);
+
+ // save value before aura remove in Unit::setDeathState
+ ressSpellId = GetUInt32Value(PLAYER_SELF_RES_SPELL);
+
+ // xinef: disable passive area auras!
+ AddUnitState(UNIT_STATE_ISOLATED);
+
+ // passive spell
+ if (!ressSpellId)
+ ressSpellId = GetResurrectionSpellId();
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP, 1);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATH, 1);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATH_IN_DUNGEON, 1);
+
+ // Xinef: reset all death criterias
+ ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_CONDITION_NO_DEATH, 0);
+ }
+ // xinef: enable passive area auras!
+ else if (s == ALIVE)
+ ClearUnitState(UNIT_STATE_ISOLATED);
+
+ Unit::setDeathState(s);
+
+ if (NeedSendSpectatorData())
+ ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "STA", IsAlive() ? 1 : 0);
+
+ // 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);
+}
+
+void Player::SetRestState(uint32 triggerId)
+{
+ _innTriggerId = triggerId;
+ _restTime = GameTime::GetGameTime().count();
+ SetPlayerFlag(PLAYER_FLAGS_RESTING);
+}
+
+void Player::RemoveRestState()
+{
+ _innTriggerId = 0;
+ _restTime = 0;
+ RemovePlayerFlag(PLAYER_FLAGS_RESTING);
+}
+
+bool Player::BuildEnumData(PreparedQueryResult result, WorldPacket* data)
+{
+ // 0 1 2 3 4 5 6 7
+ // "SELECT characters.guid, characters.name, characters.race, characters.class, characters.gender, characters.skin, characters.face, characters.hairStyle,
+ // 8 9 10 11 12 13 14 15
+ // characters.hairColor, characters.facialStyle, character.level, characters.zone, characters.map, characters.position_x, characters.position_y, characters.position_z,
+ // 16 17 18 19 20 21 22 23
+ // guild_member.guildid, characters.playerFlags, characters.at_login, character_pet.entry, character_pet.modelid, character_pet.level, characters.equipmentCache, character_banned.guid,
+ // 24 25
+ // characters.extra_flags, character_declinedname.genitive
+
+ Field* fields = result->Fetch();
+
+ ObjectGuid::LowType guidLow = fields[0].Get();
+ uint8 plrRace = fields[2].Get();
+ uint8 plrClass = fields[3].Get();
+ uint8 gender = fields[4].Get();
+
+ ObjectGuid guid = ObjectGuid::Create(guidLow);
+
+ PlayerInfo const* info = sObjectMgr->GetPlayerInfo(plrRace, plrClass);
+ if (!info)
+ {
+ LOG_ERROR("entities.player", "Player {} has incorrect race/class pair. Don't build enum.", guid.ToString());
+ return false;
+ }
+ else if (!IsValidGender(gender))
+ {
+ LOG_ERROR("entities.player", "Player ({}) has incorrect gender ({}), don't build enum.", guid.ToString(), gender);
+ return false;
+ }
+
+ *data << guid;
+ *data << fields[1].Get(); // name
+ *data << uint8(plrRace); // race
+ *data << uint8(plrClass); // class
+ *data << uint8(gender); // gender
+
+ uint8 skin = fields[5].Get();
+ uint8 face = fields[6].Get();
+ uint8 hairStyle = fields[7].Get();
+ uint8 hairColor = fields[8].Get();
+ uint8 facialStyle = fields[9].Get();
+
+ uint32 charFlags = 0;
+ uint32 playerFlags = fields[17].Get();
+ uint16 atLoginFlags = fields[18].Get();
+ uint32 zone = (atLoginFlags & AT_LOGIN_FIRST) != 0 ? 0 : fields[11].Get(); // if first login do not show the zone
+
+ *data << uint8(skin);
+ *data << uint8(face);
+ *data << uint8(hairStyle);
+ *data << uint8(hairColor);
+ *data << uint8(facialStyle);
+
+ *data << uint8(fields[10].Get()); // level
+ *data << uint32(zone); // zone
+ *data << uint32(fields[12].Get()); // map
+
+ *data << fields[13].Get(); // x
+ *data << fields[14].Get(); // y
+ *data << fields[15].Get(); // z
+
+ *data << uint32(fields[16].Get()); // guild id
+
+ if (atLoginFlags & AT_LOGIN_RESURRECT)
+ playerFlags &= ~PLAYER_FLAGS_GHOST;
+ if (playerFlags & PLAYER_FLAGS_HIDE_HELM)
+ charFlags |= CHARACTER_FLAG_HIDE_HELM;
+ if (playerFlags & PLAYER_FLAGS_HIDE_CLOAK)
+ charFlags |= CHARACTER_FLAG_HIDE_CLOAK;
+ if (playerFlags & PLAYER_FLAGS_GHOST)
+ charFlags |= CHARACTER_FLAG_GHOST;
+ if (atLoginFlags & AT_LOGIN_RENAME)
+ charFlags |= CHARACTER_FLAG_RENAME;
+ if (fields[23].Get())
+ charFlags |= CHARACTER_FLAG_LOCKED_BY_BILLING;
+ if (sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED))
+ {
+ if (!fields[25].Get().empty())
+ charFlags |= CHARACTER_FLAG_DECLINED;
+ }
+ else
+ charFlags |= CHARACTER_FLAG_DECLINED;
+
+ *data << uint32(charFlags); // character flags
+
+ // character customize flags
+ if (atLoginFlags & AT_LOGIN_CUSTOMIZE)
+ *data << uint32(CHAR_CUSTOMIZE_FLAG_CUSTOMIZE);
+ else if (atLoginFlags & AT_LOGIN_CHANGE_FACTION)
+ *data << uint32(CHAR_CUSTOMIZE_FLAG_FACTION);
+ else if (atLoginFlags & AT_LOGIN_CHANGE_RACE)
+ *data << uint32(CHAR_CUSTOMIZE_FLAG_RACE);
+ else
+ *data << uint32(CHAR_CUSTOMIZE_FLAG_NONE);
+
+ // First login
+ *data << uint8(atLoginFlags & AT_LOGIN_FIRST ? 1 : 0);
+
+ // Pets info
+ uint32 petDisplayId = 0;
+ uint32 petLevel = 0;
+ uint32 petFamily = 0;
+
+ // show pet at selection character in character list only for non-ghost character
+ if (result && !(playerFlags & PLAYER_FLAGS_GHOST) && (plrClass == CLASS_WARLOCK || plrClass == CLASS_HUNTER || (plrClass == CLASS_DEATH_KNIGHT && (fields[21].Get()&PLAYER_EXTRA_SHOW_DK_PET))))
+ {
+ uint32 entry = fields[19].Get();
+ CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(entry);
+ if (creatureInfo)
+ {
+ petDisplayId = fields[20].Get();
+ petLevel = fields[21].Get();
+ petFamily = creatureInfo->family;
+ }
+ }
+
+ *data << uint32(petDisplayId);
+ *data << uint32(petLevel);
+ *data << uint32(petFamily);
+
+ std::vector equipment = Acore::Tokenize(fields[22].Get(), ' ', false);
+ for (uint8 slot = 0; slot < INVENTORY_SLOT_BAG_END; ++slot)
+ {
+ uint32 const visualBase = slot * 2;
+ Optional itemId;
+
+ if (visualBase < equipment.size())
+ {
+ itemId = Acore::StringTo(equipment[visualBase]);
+ }
+
+ ItemTemplate const* proto = nullptr;
+ if (itemId)
+ {
+ proto = sObjectMgr->GetItemTemplate(*itemId);
+ }
+
+ if (!proto)
+ {
+ if (!itemId || *itemId)
+ {
+ LOG_WARN("entities.player.loading", "Player {} has invalid equipment '{}' in `equipmentcache` at index {}. Skipped.",
+ guid.ToString(), (visualBase < equipment.size()) ? equipment[visualBase] : "", visualBase);
+ }
+
+ *data << uint32(0);
+ *data << uint8(0);
+ *data << uint32(0);
+
+ continue;
+ }
+
+ SpellItemEnchantmentEntry const* enchant = nullptr;
+
+ Optional enchants = {};
+ if ((visualBase + 1) < equipment.size())
+ {
+ enchants = Acore::StringTo(equipment[visualBase + 1]);
+ }
+
+ if (!enchants)
+ {
+ LOG_WARN("entities.player.loading", "Player {} has invalid enchantment info '{}' in `equipmentcache` at index {}. Skipped.",
+ guid.ToString(), ((visualBase + 1) < equipment.size()) ? equipment[visualBase + 1] : "", visualBase + 1);
+
+ enchants = 0;
+ }
+
+ 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;
+ }
+
+ enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId);
+ if (enchant)
+ {
+ break;
+ }
+ }
+
+ *data << uint32(proto->DisplayInfoID);
+ *data << uint8(proto->InventoryType);
+ *data << uint32(enchant ? enchant->aura_id : 0);
+ }
+
+ return true;
+}
+
+void Player::ToggleAFK()
+{
+ ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_AFK);
+
+ // afk player not allowed in battleground
+ if (!IsGameMaster() && isAFK() && InBattleground())
+ LeaveBattleground();
+}
+
+void Player::ToggleDND()
+{
+ ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_DND);
+}
+
+uint8 Player::GetChatTag() const
+{
+ uint8 tag = CHAT_TAG_NONE;
+
+ if (isGMChat())
+ tag |= CHAT_TAG_GM;
+ if (isDND())
+ tag |= CHAT_TAG_DND;
+ if (isAFK())
+ tag |= CHAT_TAG_AFK;
+ if (IsDeveloper())
+ tag |= CHAT_TAG_DEV;
+
+ return tag;
+}
+
+void Player::SendTeleportAckPacket()
+{
+ WorldPacket data(MSG_MOVE_TELEPORT_ACK, 41);
+ data << GetPackGUID();
+ data << uint32(0); // this value increments every time
+ BuildMovementPacket(&data);
+ GetSession()->SendPacket(&data);
+}
+
+bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientation, uint32 options /*= 0*/, Unit* target /*= nullptr*/, bool newInstance /*= false*/)
+{
+ // for except kick by antispeedhack
+ sScriptMgr->AnticheatSetSkipOnePacketForASH(this, true);
+
+ if (!MapMgr::IsValidMapCoord(mapid, x, y, z, orientation))
+ {
+ LOG_ERROR("entities.player", "TeleportTo: invalid map ({}) or invalid coordinates (X: {}, Y: {}, Z: {}, O: {}) given when teleporting player ({}, name: {}, map: {}, X: {}, Y: {}, Z: {}, O: {}).",
+ mapid, x, y, z, orientation, GetGUID().ToString(), GetName(), GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
+ return false;
+ }
+
+ if (AccountMgr::IsPlayerAccount(GetSession()->GetSecurity()) && DisableMgr::IsDisabledFor(DISABLE_TYPE_MAP, mapid, this))
+ {
+ LOG_ERROR("entities.player", "Player ({}, name: {}) tried to enter a forbidden map {}", GetGUID().ToString(), GetName(), mapid);
+ SendTransferAborted(mapid, TRANSFER_ABORT_MAP_NOT_ALLOWED);
+ return false;
+ }
+
+ // preparing unsummon pet if lost (we must get pet before teleportation or will not find it later)
+ Pet* pet = GetPet();
+
+ MapEntry const* mEntry = sMapStore.LookupEntry(mapid);
+
+ // don't let enter battlegrounds without assigned battleground id (for example through areatrigger)...
+ if (!InBattleground() && mEntry->IsBattlegroundOrArena())
+ return false;
+
+ // pussywizard: arena spectator, prevent teleporting from arena to instance/etc
+ if (GetMapId() != mapid && IsSpectator() && mEntry->Instanceable())
+ {
+ SendTransferAborted(mapid, TRANSFER_ABORT_MAP_NOT_ALLOWED);
+ return false;
+ }
+
+ // client without expansion support
+ if (GetSession()->Expansion() < mEntry->Expansion())
+ {
+ LOG_DEBUG("maps", "Player {} using client without required expansion tried teleport to non accessible map {}", GetName(), mapid);
+
+ if (GetTransport())
+ {
+ m_transport->RemovePassenger(this);
+ m_transport = nullptr;
+ m_movementInfo.transport.Reset();
+ m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ONTRANSPORT);
+ RepopAtGraveyard(); // teleport to near graveyard if on transport, looks blizz like :)
+ }
+
+ SendTransferAborted(mapid, TRANSFER_ABORT_INSUF_EXPAN_LVL, mEntry->Expansion());
+
+ return false; // normal client can't teleport to this map...
+ }
+ else
+ LOG_DEBUG("maps", "Player {} is being teleported to map {}", GetName(), mapid);
+
+ // xinef: do this here in case teleport failed in above checks
+ if (!(options & TELE_TO_NOT_LEAVE_TAXI) && IsInFlight())
+ {
+ GetMotionMaster()->MovementExpired();
+ CleanupAfterTaxiFlight();
+ }
+
+ if (!(options & TELE_TO_NOT_LEAVE_VEHICLE) && m_vehicle)
+ ExitVehicle();
+
+ // reset movement flags at teleport, because player will continue move with these flags after teleport
+ SetUnitMovementFlags(GetUnitMovementFlags() & MOVEMENTFLAG_MASK_HAS_PLAYER_STATUS_OPCODE);
+ DisableSpline();
+
+ // Xinef: Remove all movement imparing effects auras, skip small teleport like blink
+ if (mapid != GetMapId() || GetDistance2d(x, y) > 100)
+ {
+ RemoveAurasByType(SPELL_AURA_MOD_STUN);
+ RemoveAurasByType(SPELL_AURA_MOD_FEAR);
+ RemoveAurasByType(SPELL_AURA_MOD_CONFUSE);
+ RemoveAurasByType(SPELL_AURA_MOD_ROOT);
+ // remove auras that should be removed when being teleported
+ RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED);
+ }
+
+ if (m_transport)
+ {
+ if (options & TELE_TO_NOT_LEAVE_TRANSPORT)
+ AddUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT);
+ else
+ {
+ m_transport->RemovePassenger(this);
+ m_transport = nullptr;
+ m_movementInfo.transport.Reset();
+ m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ONTRANSPORT);
+ }
+ }
+
+ // The player was ported to another map and loses the duel immediately.
+ // We have to perform this check before the teleport, otherwise the
+ // ObjectAccessor won't find the flag.
+ if (duel && GetMapId() != mapid && GetMap()->GetGameObject(GetGuidValue(PLAYER_DUEL_ARBITER)))
+ DuelComplete(DUEL_FLED);
+
+ if (!sScriptMgr->OnBeforePlayerTeleport(this, mapid, x, y, z, orientation, options, target))
+ return false;
+
+ if (GetMapId() == mapid && !newInstance)
+ {
+ //lets reset far teleport flag if it wasn't reset during chained teleports
+ SetSemaphoreTeleportFar(0);
+
+ SetHasDelayedTeleport(false); // pussywizard: current teleport cancels stored one
+ //if teleport spell is casted in Unit::Update() func
+ //then we need to delay it until update process will be finished
+ if (MustDelayTeleport())
+ {
+ SetHasDelayedTeleport(true);
+ SetSemaphoreTeleportNear(GameTime::GetGameTime().count());
+ //lets save teleport destination for player
+ teleportStore_dest = WorldLocation(mapid, x, y, z, orientation);
+ teleportStore_options = options;
+ return true;
+ }
+
+ if (options & TELE_TO_WITH_PET)
+ UnsummonPetTemporaryIfAny();
+
+ 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()->GetVisibilityRange()))
+ UnsummonPetTemporaryIfAny();
+ }
+
+ if (!(options & TELE_TO_NOT_LEAVE_COMBAT))
+ CombatStop();
+
+ // this will be used instead of the current location in SaveToDB
+ teleportStore_dest = WorldLocation(mapid, x, y, z, orientation);
+ SetFallInformation(GameTime::GetGameTime().count(), z);
+
+ // code for finish transfer called in WorldSession::HandleMovementOpcodes()
+ // at client packet MSG_MOVE_TELEPORT_ACK
+ SetSemaphoreTeleportNear(GameTime::GetGameTime().count());
+ // near teleport, triggering send MSG_MOVE_TELEPORT_ACK from client at landing
+ if (!GetSession()->PlayerLogout())
+ {
+ SetCanTeleport(true);
+ Position oldPos = GetPosition();
+ Relocate(x, y, z, orientation);
+ SendTeleportAckPacket();
+ SendTeleportPacket(oldPos); // this automatically relocates to oldPos in order to broadcast the packet in the right place
+ }
+ }
+ else
+ {
+ if (getClass() == CLASS_DEATH_KNIGHT && GetMapId() == 609 && !IsGameMaster() && !HasSpell(50977))
+ return false;
+
+ // far teleport to another map
+ Map* oldmap = IsInWorld() ? GetMap() : nullptr;
+ // check if we can enter before stopping combat / removing pet / totems / interrupting spells
+
+ // Check enter rights before map getting to avoid creating instance copy for player
+ // this check not dependent from map instance copy and same for all instance copies of selected map
+ if (!(options & TELE_TO_GM_MODE) && sMapMgr->PlayerCannotEnter(mapid, this, false))
+ return false;
+
+ // if PlayerCannotEnter -> CanEnter: checked above
+ {
+ //lets reset near teleport flag if it wasn't reset during chained teleports
+ SetSemaphoreTeleportNear(0);
+
+ SetHasDelayedTeleport(false); // pussywizard: current teleport cancels stored one
+ //if teleport spell is casted in Unit::Update() func
+ //then we need to delay it until update process will be finished
+ if (MustDelayTeleport())
+ {
+ SetHasDelayedTeleport(true);
+ SetSemaphoreTeleportFar(GameTime::GetGameTime().count());
+ //lets save teleport destination for player
+ teleportStore_dest = WorldLocation(mapid, x, y, z, orientation);
+ teleportStore_options = options;
+ return true;
+ }
+
+ SetSelection(ObjectGuid::Empty);
+
+ CombatStop();
+
+ // remove arena spell coldowns/buffs now to also remove pet's cooldowns before it's temporarily unsummoned
+ if (mEntry->IsBattleArena() && (HasPendingSpectatorForBG(0) || !HasPendingSpectatorForBG(GetBattlegroundId())))
+ {
+ // KEEP THIS ORDER!
+ RemoveArenaAuras();
+ if (pet)
+ pet->RemoveArenaAuras();
+
+ RemoveArenaSpellCooldowns(true);
+ }
+
+ // remove pet on map change
+ if (pet)
+ UnsummonPetTemporaryIfAny();
+
+ //bot: teleport npcbots
+ if (HaveBot())
+ _botMgr->OnTeleportFar(mapid, x, y, z, orientation);
+ //end bot
+
+ // remove all dyn objects
+ RemoveAllDynObjects();
+
+ // stop spellcasting
+ // not attempt interrupt teleportation spell at caster teleport
+ if (!(options & TELE_TO_SPELL))
+ if (IsNonMeleeSpellCast(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 packets
+ WorldPacket data(SMSG_TRANSFER_PENDING, 4 + 4 + 4);
+ data << uint32(mapid);
+ if (m_transport)
+ data << m_transport->GetEntry() << GetMapId();
+
+ GetSession()->SendPacket(&data);
+ }
+
+ // remove from old map now
+ if (oldmap)
+ oldmap->RemovePlayerFromMap(this, false);
+
+ // xinef: do this before setting fall information!
+ if (IsMounted() && (!GetMap()->GetEntry()->IsDungeon() && !GetMap()->GetEntry()->IsBattlegroundOrArena()) && !m_transport)
+ {
+ AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_MOUNTED);
+ if (!auras.empty())
+ {
+ SetMountBlockId((*auras.begin())->GetId());
+ RemoveAurasByType(SPELL_AURA_MOUNTED);
+ }
+ }
+
+ teleportStore_dest = WorldLocation(mapid, x, y, z, orientation);
+ SetFallInformation(GameTime::GetGameTime().count(), z);
+ // if the player is saved before worldportack (at logout for example)
+ // this will be used instead of the current location in SaveToDB
+
+ if (!GetSession()->PlayerLogout())
+ {
+ SetCanTeleport(true);
+ WorldPacket data(SMSG_NEW_WORLD, 4 + 4 + 4 + 4 + 4);
+ data << uint32(mapid);
+ if (m_transport)
+ data << m_movementInfo.transport.pos.PositionXYZOStream();
+ else
+ data << teleportStore_dest.PositionXYZOStream();
+
+ GetSession()->SendPacket(&data);
+ SendSavedInstances();
+ }
+
+ // move packet sent by client always after far teleport
+ // code for finish transfer to new map called in WorldSession::HandleMoveWorldportAckOpcode at client packet
+ SetSemaphoreTeleportFar(GameTime::GetGameTime().count());
+ }
+ }
+ return true;
+}
+
+bool Player::TeleportToEntryPoint()
+{
+ ScheduleDelayedOperation(DELAYED_BG_MOUNT_RESTORE);
+ ScheduleDelayedOperation(DELAYED_BG_TAXI_RESTORE);
+ ScheduleDelayedOperation(DELAYED_BG_GROUP_RESTORE);
+
+ WorldLocation loc = m_entryPointData.joinPos;
+ m_entryPointData.joinPos.m_mapId = MAPID_INVALID;
+
+ if (loc.m_mapId == MAPID_INVALID)
+ {
+ return TeleportTo(m_homebindMapId, m_homebindX, m_homebindY, m_homebindZ, m_homebindO);
+ }
+
+ return TeleportTo(loc);
+}
+
+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
+ SetFullHealth();
+
+ 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(false, false);
+
+ if (m_DelayedOperations & DELAYED_SPELL_CAST_DESERTER)
+ {
+ Aura* aura = GetAura(26013);
+ if (!aura || aura->GetDuration() <= 900000)
+ CastSpell(this, 26013, true);
+ }
+
+ if (m_DelayedOperations & DELAYED_BG_MOUNT_RESTORE)
+ {
+ if (m_entryPointData.mountSpell)
+ {
+ // xinef: remove shapeshift auras
+ if (IsInDisallowedMountForm())
+ {
+ RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
+ }
+ AddAura(m_entryPointData.mountSpell, this);
+ m_entryPointData.mountSpell = 0;
+ }
+ }
+
+ if (m_DelayedOperations & DELAYED_BG_TAXI_RESTORE)
+ {
+ if (m_entryPointData.HasTaxiPath())
+ {
+ m_taxi.AddTaxiDestination(m_entryPointData.taxiPath[0]);
+ m_taxi.AddTaxiDestination(m_entryPointData.taxiPath[1]);
+ m_entryPointData.ClearTaxiPath();
+ ContinueTaxiFlight();
+ }
+ }
+
+ if (m_DelayedOperations & DELAYED_BG_GROUP_RESTORE)
+ {
+ if (Group* g = GetGroup())
+ g->SendUpdateToPlayer(GetGUID());
+ }
+
+ if (m_DelayedOperations & DELAYED_VEHICLE_TELEPORT)
+ {
+ if (Vehicle* vehicle = GetVehicle())
+ {
+ SeatMap::iterator itr = vehicle->GetSeatIteratorForPassenger(this);
+ if (itr != vehicle->Seats.end())
+ if (Unit* base = vehicle->GetBase())
+ {
+ ExitVehicle();
+ base->HandleSpellClick(this, itr->first);
+ }
+ }
+ }
+
+ //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 (uint8 i = PLAYER_SLOT_START; i < PLAYER_SLOT_END; ++i)
+ if (m_items[i])
+ m_items[i]->AddToWorld();
+}
+
+void Player::RemoveFromWorld()
+{
+ // cleanup
+ if (IsInWorld())
+ {
+ ///- Release charmed creatures, unsummon totems and remove pets/guardians
+ StopCastingCharm();
+ StopCastingBindSight();
+ UnsummonPetTemporaryIfAny();
+ ClearComboPoints(); // pussywizard: crashfix
+ ClearComboPointHolders(); // pussywizard: crashfix
+ if (ObjectGuid lguid = GetLootGUID()) // pussywizard: crashfix
+ m_session->DoLootRelease(lguid);
+ sOutdoorPvPMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId);
+ sBattlefieldMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId);
+ }
+
+ // Remove items from world before self - player must be found in Item::RemoveFromObjectUpdate
+ for (uint8 i = PLAYER_SLOT_START; i < PLAYER_SLOT_END; ++i)
+ {
+ if (m_items[i])
+ m_items[i]->RemoveFromWorld();
+ }
+
+ for (ItemMap::iterator iter = mMitems.begin(); iter != mMitems.end(); ++iter)
+ iter->second->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
+ Unit::RemoveFromWorld();
+
+ if (m_uint32Values)
+ {
+ if (WorldObject* viewpoint = GetViewpoint())
+ {
+ LOG_FATAL("entities.player", "Player {} has viewpoint {} {} when removed from world", GetName(), viewpoint->GetEntry(), viewpoint->GetTypeId());
+ SetViewpoint(viewpoint, false);
+ }
+ }
+}
+
+//NPCBOT
+bool Player::HaveBot() const
+{
+ return _botMgr && _botMgr->HaveBot();
+}
+uint8 Player::GetNpcBotsCount() const
+{
+ return _botMgr ? _botMgr->GetNpcBotsCount() : 0;
+}
+void Player::RemoveAllBots(uint8 removetype)
+{
+ if (_botMgr) _botMgr->RemoveAllBots(removetype);
+}
+void Player::UpdatePhaseForBots()
+{
+ if (_botMgr) _botMgr->UpdatePhaseForBots();
+}
+//END NPCBOT
+
+void Player::RegenerateAll()
+{
+ //if (m_regenTimer <= 500)
+ // return;
+
+ m_regenTimerCount += m_regenTimer;
+ m_foodEmoteTimerCount += m_regenTimer;
+
+ Regenerate(POWER_ENERGY);
+
+ Regenerate(POWER_MANA);
+
+ // Runes act as cooldowns, and they don't need to send any data
+ if (getClass() == CLASS_DEATH_KNIGHT)
+ for (uint8 i = 0; i < MAX_RUNES; ++i)
+ {
+ // xinef: implement grace
+ if (int32 cd = GetRuneCooldown(i))
+ {
+ SetRuneCooldown(i, (cd > m_regenTimer) ? cd - m_regenTimer : 0);
+ // start grace counter, player must be in combat and rune has to go off cooldown
+ if (IsInCombat() && cd <= m_regenTimer)
+ SetGracePeriod(i, m_regenTimer - cd + 1); // added 1 because m_regenTimer-cd can be equal 0
+ }
+ // xinef: if grace is started, increase it but no more than cap
+ else if (uint32 grace = GetGracePeriod(i))
+ {
+ if (grace < RUNE_GRACE_PERIOD)
+ SetGracePeriod(i, std::min(grace + m_regenTimer, RUNE_GRACE_PERIOD));
+ }
+ }
+
+ if (m_regenTimerCount >= 2000)
+ {
+ // Not in combat or they have regeneration
+ if (!IsInCombat() || IsPolymorphed() || m_baseHealthRegen ||
+ HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT) ||
+ HasAuraType(SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT))
+ {
+ RegenerateHealth();
+ }
+
+ Regenerate(POWER_RAGE);
+ if (getClass() == CLASS_DEATH_KNIGHT)
+ Regenerate(POWER_RUNIC_POWER);
+
+ m_regenTimerCount -= 2000;
+ }
+
+ m_regenTimer = 0;
+
+ // Handles the emotes for drinking and eating.
+ // According to sniffs there is a background timer going on that repeats independed from the time window where the aura applies.
+ // That's why we dont need to reset the timer on apply. In sniffs I have seen that the first call for the spell visual is totally random, then after
+ // 5 seconds over and over again which confirms my theory that we have a independed timer.
+ if (m_foodEmoteTimerCount >= 5000)
+ {
+ std::vector auraList;
+ AuraEffectList const& ModRegenAuras = GetAuraEffectsByType(SPELL_AURA_MOD_REGEN);
+ AuraEffectList const& ModPowerRegenAuras = GetAuraEffectsByType(SPELL_AURA_MOD_POWER_REGEN);
+
+ auraList.reserve(ModRegenAuras.size() + ModPowerRegenAuras.size());
+ auraList.insert(auraList.end(), ModRegenAuras.begin(), ModRegenAuras.end());
+ auraList.insert(auraList.end(), ModPowerRegenAuras.begin(), ModPowerRegenAuras.end());
+
+ for (auto itr = auraList.begin(); itr != auraList.end(); ++itr)
+ {
+ // Food emote comes above drinking emote if we have to decide (mage regen food for example)
+ if ((*itr)->GetBase()->HasEffectType(SPELL_AURA_MOD_REGEN) && (*itr)->GetSpellInfo()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED)
+ {
+ SendPlaySpellVisual(SPELL_VISUAL_KIT_FOOD);
+ break;
+ }
+ else if ((*itr)->GetBase()->HasEffectType(SPELL_AURA_MOD_POWER_REGEN) && (*itr)->GetSpellInfo()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED)
+ {
+ SendPlaySpellVisual(SPELL_VISUAL_KIT_DRINK);
+ break;
+ }
+ }
+ m_foodEmoteTimerCount -= 5000;
+ }
+}
+
+void Player::Regenerate(Powers power)
+{
+ uint32 maxValue = GetMaxPower(power);
+ if (!maxValue)
+ return;
+
+ //If .cheat power is on always have the max power
+ if (GetCommandStatus(CHEAT_POWER))
+ {
+ if (m_regenTimerCount >= 2000)
+ {
+ //Set the value to 0 first then set it to max to force resend of packet as for range clients keeps removing rage
+ if (power == POWER_RAGE || power == POWER_RUNIC_POWER)
+ {
+ UpdateUInt32Value(static_cast(UNIT_FIELD_POWER1) + power, 0);
+ }
+
+ SetPower(power, maxValue);
+ return;
+ }
+ }
+
+ uint32 curValue = GetPower(power);
+
+ // TODO: possible use of miscvalueb instead of amount
+ if (HasAuraTypeWithMiscvalue(SPELL_AURA_PREVENT_REGENERATE_POWER, power + 1))
+ return;
+
+ float addvalue = 0.0f;
+
+ switch (power)
+ {
+ case POWER_MANA:
+ {
+ bool recentCast = IsUnderLastManaUseEffect();
+ float ManaIncreaseRate = sWorld->getRate(RATE_POWER_MANA);
+
+ if (sWorld->getBoolConfig(CONFIG_LOW_LEVEL_REGEN_BOOST) && GetLevel() < 15)
+ ManaIncreaseRate = sWorld->getRate(RATE_POWER_MANA) * (2.066f - (GetLevel() * 0.066f));
+
+ if (recentCast) // Trinity Updates Mana in intervals of 2s, which is correct
+ addvalue += GetFloatValue(UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER) * ManaIncreaseRate * 0.001f * m_regenTimer;
+ else
+ addvalue += GetFloatValue(UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER) * ManaIncreaseRate * 0.001f * m_regenTimer;
+ }
+ break;
+ case POWER_RAGE: // Regenerate rage
+ {
+ if (!IsInCombat() && !HasAuraType(SPELL_AURA_INTERRUPT_REGEN))
+ {
+ float RageDecreaseRate = sWorld->getRate(RATE_POWER_RAGE_LOSS);
+ addvalue += -20 * RageDecreaseRate; // 2 rage by tick (= 2 seconds => 1 rage/sec)
+ }
+ }
+ break;
+ case POWER_ENERGY: // Regenerate energy (rogue)
+ addvalue += 0.01f * m_regenTimer * sWorld->getRate(RATE_POWER_ENERGY);
+ break;
+ case POWER_RUNIC_POWER:
+ {
+ if (!IsInCombat() && !HasAuraType(SPELL_AURA_INTERRUPT_REGEN))
+ {
+ float RunicPowerDecreaseRate = sWorld->getRate(RATE_POWER_RUNICPOWER_LOSS);
+ addvalue += -30 * RunicPowerDecreaseRate; // 3 RunicPower by tick
+ }
+ }
+ break;
+ case POWER_RUNE:
+ case POWER_FOCUS:
+ case POWER_HAPPINESS:
+ break;
+ case POWER_HEALTH:
+ return;
+ default:
+ break;
+ }
+
+ // Mana regen calculated in Player::UpdateManaRegen()
+ if (power != POWER_MANA)
+ {
+ AuraEffectList const& ModPowerRegenPCTAuras = GetAuraEffectsByType(SPELL_AURA_MOD_POWER_REGEN_PERCENT);
+ for (AuraEffectList::const_iterator i = ModPowerRegenPCTAuras.begin(); i != ModPowerRegenPCTAuras.end(); ++i)
+ if (Powers((*i)->GetMiscValue()) == power)
+ AddPct(addvalue, (*i)->GetAmount());
+
+ // Butchery requires combat for this effect
+ if (power != POWER_RUNIC_POWER || IsInCombat())
+ addvalue += float(GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, power) * ((power != POWER_ENERGY) ? m_regenTimerCount : m_regenTimer)) / (5.0f * IN_MILLISECONDS);
+ }
+
+ if (addvalue < 0.0f)
+ {
+ if (curValue == 0)
+ return;
+ }
+ else if (addvalue > 0.0f)
+ {
+ if (curValue == maxValue)
+ return;
+ }
+ else
+ return;
+
+ addvalue += m_powerFraction[power];
+ uint32 integerValue = uint32(std::fabs(addvalue));
+
+ bool forcedUpdate = false;
+ if (addvalue < 0.0f)
+ {
+ if (curValue > integerValue)
+ {
+ curValue -= integerValue;
+ m_powerFraction[power] = addvalue + integerValue;
+ }
+ else
+ {
+ curValue = 0;
+ m_powerFraction[power] = 0;
+ forcedUpdate = true;
+ }
+ }
+ else
+ {
+ curValue += integerValue;
+
+ if (curValue >= maxValue)
+ {
+ curValue = maxValue;
+ m_powerFraction[power] = 0;
+ forcedUpdate = true;
+ }
+ else
+ {
+ m_powerFraction[power] = addvalue - integerValue;
+ }
+ }
+
+ if (m_regenTimerCount >= 2000 || forcedUpdate)
+ {
+ SetPower(power, curValue, true, true);
+ }
+ else
+ {
+ UpdateUInt32Value(static_cast(UNIT_FIELD_POWER1) + power, curValue);
+ }
+}
+
+void Player::RegenerateHealth()
+{
+ uint32 curValue = GetHealth();
+ uint32 maxValue = GetMaxHealth();
+
+ if (curValue >= maxValue)
+ return;
+
+ float HealthIncreaseRate = sWorld->getRate(RATE_HEALTH);
+
+ if (sWorld->getBoolConfig(CONFIG_LOW_LEVEL_REGEN_BOOST) && GetLevel() < 15)
+ HealthIncreaseRate = sWorld->getRate(RATE_HEALTH) * (2.066f - (GetLevel() * 0.066f));
+
+ 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 = OCTRegenHPPerSpirit() * HealthIncreaseRate;
+ if (!IsInCombat())
+ {
+ AuraEffectList const& mModHealthRegenPct = GetAuraEffectsByType(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT);
+ for (AuraEffectList::const_iterator i = mModHealthRegenPct.begin(); i != mModHealthRegenPct.end(); ++i)
+ AddPct(addvalue, (*i)->GetAmount());
+
+ addvalue += GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * 2 * IN_MILLISECONDS / (5 * IN_MILLISECONDS);
+ }
+ else if (HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT))
+ ApplyPct(addvalue, GetTotalAuraModifier(SPELL_AURA_MOD_REGEN_DURING_COMBAT));
+
+ if (!IsStandState())
+ addvalue *= 1.5f;
+ }
+
+ // always regeneration bonus (including combat)
+ addvalue += GetTotalAuraModifier(SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT);
+ addvalue += m_baseHealthRegen / 2.5f;
+
+ if (addvalue < 0)
+ addvalue = 0;
+
+ ModifyHealth(int32(addvalue));
+}
+
+void Player::ResetAllPowers()
+{
+ SetHealth(GetMaxHealth());
+ switch (getPowerType())
+ {
+ case POWER_MANA:
+ SetPower(POWER_MANA, GetMaxPower(POWER_MANA));
+ break;
+ case POWER_RAGE:
+ SetPower(POWER_RAGE, 0);
+ break;
+ case POWER_ENERGY:
+ SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY));
+ break;
+ case POWER_RUNIC_POWER:
+ SetPower(POWER_RUNIC_POWER, 0);
+ break;
+ default:
+ break;
+ }
+}
+
+bool Player::CanInteractWithQuestGiver(Object* questGiver)
+{
+ switch (questGiver->GetTypeId())
+ {
+ case TYPEID_UNIT:
+ return GetNPCIfCanInteractWith(questGiver->GetGUID(), UNIT_NPC_FLAG_QUESTGIVER) != nullptr;
+ case TYPEID_GAMEOBJECT:
+ return GetGameObjectIfCanInteractWith(questGiver->GetGUID(), GAMEOBJECT_TYPE_QUESTGIVER) != nullptr;
+ case TYPEID_PLAYER:
+ return IsAlive() && questGiver->ToPlayer()->IsAlive();
+ case TYPEID_ITEM:
+ return IsAlive();
+ default:
+ break;
+ }
+ return false;
+}
+
+Creature* Player::GetNPCIfCanInteractWith(ObjectGuid guid, uint32 npcflagmask)
+{
+ // unit checks
+ if (!guid)
+ return nullptr;
+
+ if (!IsInWorld())
+ return nullptr;
+
+ if (IsInFlight())
+ return nullptr;
+
+ // exist (we need look pets also for some interaction (quest/etc)
+ Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, guid);
+ if (!creature)
+ return nullptr;
+
+ // Deathstate checks
+ if (!IsAlive() && !(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_VISIBLE_TO_GHOSTS))
+ return nullptr;
+
+ // alive or spirit healer
+ if (!creature->IsAlive() && !(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_INTERACT_WHILE_DEAD))
+ return nullptr;
+
+ // appropriate npc type
+ if (npcflagmask && !creature->HasNpcFlag(NPCFlags(npcflagmask)))
+ return nullptr;
+
+ // not allow interaction under control, but allow with own pets
+ if (creature->GetCharmerGUID())
+ return nullptr;
+
+ //npcbot
+ if (creature->IsNPCBot() && creature->IsWithinDistInMap(this, INTERACTION_DISTANCE))
+ return creature;
+ //end npcbot
+
+ // xinef: perform better check
+ if (creature->GetReactionTo(this) <= REP_UNFRIENDLY)
+ return nullptr;
+
+ // xinef: not needed, CORRECTLY checked above including forced reputations etc
+ // not unfriendly
+ //if (FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(creature->GetFaction()))
+ // if (factionTemplate->faction)
+ // if (FactionEntry const* faction = sFactionStore.LookupEntry(factionTemplate->faction))
+ // if (faction->reputationListID >= 0 && GetReputationMgr().GetRank(faction) <= REP_UNFRIENDLY)
+ // return nullptr;
+
+ // not too far
+ if (!creature->IsWithinDistInMap(this, INTERACTION_DISTANCE))
+ return nullptr;
+
+ // pussywizard: many npcs have missing conditions for class training and rogue trainer can for eg. train dual wield to a shaman :/ too many to change in sql and watch in the future
+ // pussywizard: this function is not used when talking, but when already taking action (buy spell, reset talents, show spell list)
+ if (npcflagmask & (UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_TRAINER_CLASS) && creature->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS && getClass() != creature->GetCreatureTemplate()->trainer_class)
+ return nullptr;
+
+ return creature;
+}
+
+GameObject* Player::GetGameObjectIfCanInteractWith(ObjectGuid guid, GameobjectTypes type) const
+{
+ if (GameObject* go = GetMap()->GetGameObject(guid))
+ {
+ if (go->GetGoType() == type)
+ {
+ // Players cannot interact with gameobjects that use the "Point" icon
+ if (go->GetGOInfo()->IconName == "Point")
+ {
+ return nullptr;
+ }
+
+ if (go->IsWithinDistInMap(this))
+ {
+ return go;
+ }
+
+ LOG_DEBUG("maps", "IsGameObjectOfTypeInRange: GameObject '{}' [{}] is too far away from player {} [{}] to be used by him (distance={}, maximal 10 is allowed)",
+ go->GetGOInfo()->name, go->GetGUID().ToString(), GetName(), GetGUID().ToString(), go->GetDistance(this));
+ }
+ }
+ return nullptr;
+}
+
+bool Player::IsFalling() const
+{
+ // Xinef: Added !IsInFlight check
+ return GetPositionZ() < m_lastFallZ && !IsInFlight();
+}
+
+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);
+
+ getHostileRefMgr().updateThreatTables();
+}
+
+bool Player::IsInAreaTriggerRadius(AreaTrigger const* trigger, float delta) const
+{
+ if (!trigger || GetMapId() != trigger->map)
+ return false;
+
+ if (trigger->radius > 0)
+ {
+ // if we have radius check it
+ float dist = GetDistance(trigger->x, trigger->y, trigger->z);
+ if (dist > trigger->radius + delta)
+ return false;
+ }
+ else
+ {
+ Position center(trigger->x, trigger->y, trigger->z, trigger->orientation);
+ if (!IsWithinBox(center, trigger->length / 2 + delta, trigger->width / 2 + delta, trigger->height / 2 + delta))
+ return false;
+ }
+
+ return true;
+}
+
+void Player::SetGameMaster(bool on)
+{
+ if (on)
+ {
+ m_ExtraFlags |= PLAYER_EXTRA_GM_ON;
+ if (AccountMgr::IsGMAccount(GetSession()->GetSecurity()))
+ SetFaction(FACTION_FRIENDLY);
+ SetPlayerFlag(PLAYER_FLAGS_GM);
+ SetUnitFlag2(UNIT_FLAG2_ALLOW_CHEAT_SPELLS);
+
+ if (Pet* pet = GetPet())
+ {
+ if (AccountMgr::IsGMAccount(GetSession()->GetSecurity()))
+ pet->SetFaction(FACTION_FRIENDLY);
+ pet->getHostileRefMgr().setOnlineOfflineState(false);
+ }
+ if (HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP))
+ {
+ RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP);
+ sScriptMgr->OnFfaPvpStateUpdate(this, false);
+ }
+ ResetContestedPvP();
+
+ getHostileRefMgr().setOnlineOfflineState(false);
+ CombatStopWithPets();
+
+ SetPhaseMask(uint32(PHASEMASK_ANYWHERE), false); // see and visible in all phases
+ m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GM, GetSession()->GetSecurity());
+ }
+ else
+ {
+ // restore phase
+ uint32 newPhase = GetPhaseByAuras();
+
+ if (!newPhase)
+ newPhase = PHASEMASK_NORMAL;
+
+ SetPhaseMask(newPhase, false);
+
+ m_ExtraFlags &= ~ PLAYER_EXTRA_GM_ON;
+ SetFactionForRace(getRace(true));
+ RemovePlayerFlag(PLAYER_FLAGS_GM);
+ RemoveUnitFlag2(UNIT_FLAG2_ALLOW_CHEAT_SPELLS);
+
+ if (Pet* pet = GetPet())
+ {
+ pet->SetFaction(GetFaction());
+ pet->getHostileRefMgr().setOnlineOfflineState(true);
+ }
+
+ // restore FFA PvP Server state
+ if (sWorld->IsFFAPvPRealm())
+ {
+ if (!HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP))
+ {
+ SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP);
+ sScriptMgr->OnFfaPvpStateUpdate(this, true);
+ }
+ }
+ // restore FFA PvP area state, remove not allowed for GM mounts
+ UpdateArea(m_areaUpdateId);
+
+ getHostileRefMgr().setOnlineOfflineState(true);
+ m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
+ }
+
+ //npcbot: pet is handled already, bots are not, so do it
+ if (HaveBot())
+ _botMgr->OnOwnerSetGameMaster(on);
+ //end npcbot
+
+ UpdateObjectVisibility();
+}
+
+void Player::SetGMVisible(bool on)
+{
+ const uint32 VISUAL_AURA = 37800;
+
+ if (on)
+ {
+ RemoveAurasDueToSpell(VISUAL_AURA);
+ m_ExtraFlags &= ~PLAYER_EXTRA_GM_INVISIBLE;
+ m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
+
+ getHostileRefMgr().setOnlineOfflineState(false);
+ CombatStopWithPets();
+ }
+ else
+ {
+ AddAura(VISUAL_AURA, this);
+ m_ExtraFlags |= PLAYER_EXTRA_GM_INVISIBLE;
+ m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, GetSession()->GetSecurity());
+ }
+}
+
+bool Player::IsGroupVisibleFor(Player const* p) const
+{
+ switch (sWorld->getIntConfig(CONFIG_GROUP_VISIBILITY))
+ {
+ default:
+ return IsInSameGroupWith(p);
+ case 1:
+ return IsInSameRaidWith(p);
+ case 2:
+ return GetTeamId() == p->GetTeamId();
+ }
+}
+
+bool Player::IsInSameGroupWith(Player const* p) const
+{
+ return p == this || (GetGroup() &&
+ GetGroup() == p->GetGroup() &&
+ 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);
+ group = nullptr; // gets deleted in disband
+ }
+ else
+ {
+ group->RemoveAllInvites();
+ delete group;
+ group = nullptr;
+ }
+ }
+}
+
+void Player::RemoveFromGroup(Group* group, ObjectGuid guid, RemoveMethod method /* = GROUP_REMOVEMETHOD_DEFAULT*/, ObjectGuid kicker /* = ObjectGuid::Empty */, const char* reason /* = nullptr */)
+{
+ if (group)
+ {
+ //npcbot - player is being removed from group - remove bots from that group
+ if (Player* player = ObjectAccessor::FindPlayer(guid))
+ {
+ if (player->HaveBot())
+ {
+ //uint8 players = 0;
+ //Group::MemberSlotList const& members = group->GetMemberSlots();
+ //for (Group::member_citerator itr = members.begin(); itr!= members.end(); ++itr)
+ //{
+ // if (Player* pl = ObjectAccessor::FindPlayer(itr->guid))
+ // ++players;
+ //}
+
+ //remove npcbots and set up new group if needed
+ player->GetBotMgr()->RemoveAllBotsFromGroup();
+ group = player->GetGroup();
+ if (!group)
+ return; //group has been disbanded
+ }
+ }
+ //npcbot - deleting player from db: remove bots
+ else if (guid.IsPlayer())
+ {
+ std::vector botguids;
+ botguids.reserve(BotMgr::GetMaxNpcBots() / 2 + 1);
+ BotDataMgr::GetNPCBotGuidsByOwner(botguids, guid);
+ for (std::vector::const_iterator ci = botguids.begin(); ci != botguids.end(); ++ci)
+ {
+ if (group->IsMember(*ci))
+ {
+ if (!group->RemoveMember(*ci, method, kicker, reason))
+ return;
+ }
+ }
+ }
+ //npcbot - bot is being removed from group - find master and remove bot through botmap
+ //else if (Creature* bot = ObjectAccessor::GetObjectInOrOutOfWorld(guid, (Creature*)NULL))
+ else if (guid.IsCreature())
+ {
+ for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
+ {
+ if (Player* member = itr->GetSource())
+ {
+ if (!member->HaveBot())
+ continue;
+
+ if (Creature* bot = member->GetBotMgr()->GetBot(guid))
+ {
+ member->GetBotMgr()->RemoveBotFromGroup(bot);
+ return;
+ }
+ }
+ }
+ //ASSERT(!bot->IsFreeBot());
+ //bot->GetBotOwner()->GetBotMgr()->RemoveBotFromGroup(bot, false);
+ //return;
+ }
+
+ group->RemoveMember(guid, method, kicker, reason);
+ group = nullptr;
+ }
+}
+
+void Player::SendLogXPGain(uint32 GivenXP, Unit* victim, uint32 BonusXP, bool recruitAFriend, float /*group_rate*/)
+{
+ WorldPacket data(SMSG_LOG_XPGAIN, 22); // guess size?
+ data << (victim ? victim->GetGUID() : ObjectGuid::Empty); // guid
+ data << uint32(GivenXP + BonusXP); // given experience
+ data << uint8(victim ? 0 : 1); // 00-kill_xp type, 01-non_kill_xp type
+
+ if (victim)
+ {
+ data << uint32(GivenXP); // experience without bonus
+
+ // should use group_rate here but can't figure out how
+ data << float(1); // 1 - none 0 - 100% group bonus output
+ }
+
+ data << uint8(recruitAFriend ? 1 : 0); // does the GivenXP include a RaF bonus?
+ GetSession()->SendPacket(&data);
+}
+
+void Player::GiveXP(uint32 xp, Unit* victim, float group_rate, bool isLFGReward)
+{
+ if (xp < 1)
+ {
+ return;
+ }
+
+ if (!IsAlive() && !GetBattlegroundId() && !isLFGReward)
+ {
+ return;
+ }
+
+ if (HasPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN))
+ {
+ return;
+ }
+
+ if (victim && victim->GetTypeId() == TYPEID_UNIT && !victim->ToCreature()->hasLootRecipient())
+ {
+ return;
+ }
+
+ uint8 level = GetLevel();
+
+ sScriptMgr->OnGivePlayerXP(this, xp, victim);
+
+ // Favored experience increase START
+ uint32 zone = GetZoneId();
+ float favored_exp_mult = 0;
+ if ((zone == 3483 || zone == 3562 || zone == 3836 || zone == 3713 || zone == 3714) && (HasAura(32096) || HasAura(32098)))
+ favored_exp_mult = 0.05f; // Thrallmar's Favor and Honor Hold's Favor
+
+ xp = uint32(xp * (1 + favored_exp_mult));
+ // Favored experience increase END
+
+ // XP to money conversion processed in Player::RewardQuest
+ if (level >= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
+ return;
+
+ uint32 bonus_xp = 0;
+ bool recruitAFriend = GetsRecruitAFriendBonus(true);
+
+ // RaF does NOT stack with rested experience
+ if (recruitAFriend)
+ bonus_xp = 2 * xp; // xp + bonus_xp must add up to 3 * xp for RaF; calculation for quests done client-side
+ else
+ bonus_xp = victim ? GetXPRestBonus(xp) : 0; // XP resting bonus
+
+ // hooks and multipliers can modify the xp with a zero or negative value
+ // check again before sending invalid xp to the client
+ if (xp < 1)
+ {
+ return;
+ }
+
+ SendLogXPGain(xp, victim, bonus_xp, recruitAFriend, group_rate);
+
+ uint32 curXP = GetUInt32Value(PLAYER_XP);
+ uint32 nextLvlXP = GetUInt32Value(PLAYER_NEXT_LEVEL_XP);
+ uint32 newXP = curXP + xp + bonus_xp;
+
+ while (newXP >= nextLvlXP && level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
+ {
+ newXP -= nextLvlXP;
+
+ if (level < sWorld->getIntConfig(CONFIG_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(uint8 level)
+{
+ uint8 oldLevel = GetLevel();
+ if (level == oldLevel)
+ return;
+
+ if (Guild* guild = GetGuild())
+ guild->UpdateMemberData(this, GUILD_MEMBER_DATA_LEVEL, level);
+
+ PlayerLevelInfo info;
+ sObjectMgr->GetPlayerLevelInfo(getRace(true), getClass(), level, &info);
+
+ PlayerClassLevelInfo classInfo;
+ sObjectMgr->GetPlayerClassLevelInfo(getClass(), level, &classInfo);
+
+ WorldPackets::Misc::LevelUpInfo packet;
+ packet.Level = level;
+ packet.HealthDelta = int32(classInfo.basehealth) - int32(GetCreateHealth());
+
+ /// @todo find some better solution
+ // for (int i = 0; i < MAX_POWERS; ++i)
+ packet.PowerDelta[0] = int32(classInfo.basemana) - int32(GetCreateMana());
+ packet.PowerDelta[1] = 0;
+ packet.PowerDelta[2] = 0;
+ packet.PowerDelta[3] = 0;
+ packet.PowerDelta[4] = 0;
+ packet.PowerDelta[5] = 0;
+
+ for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
+ packet.StatDelta[i] = int32(info.stats[i]) - GetCreateStat(Stats(i));
+
+ SendDirectMessage(packet.Write());
+
+ 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 (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
+ SetCreateStat(Stats(i), info.stats[i]);
+
+ SetCreateHealth(classInfo.basehealth);
+ SetCreateMana(classInfo.basemana);
+
+ InitTalentForLevel();
+ InitTaxiNodesForLevel();
+ InitGlyphsForLevel();
+
+ UpdateAllStats();
+
+ if (sWorld->getBoolConfig(CONFIG_ALWAYS_MAXSKILL)) // Max weapon skill when leveling up
+ UpdateSkillsToMaxSkillsForLevel();
+
+ _ApplyAllLevelScaleItemMods(true);
+
+ // set current level health and mana/energy to maximum after applying all mods.
+ SetFullHealth();
+ 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_HAPPINESS, 0);
+
+ // update level to hunter/summon pet
+ if (Pet* pet = GetPet())
+ pet->SynchronizeLevelWithOwner();
+
+ MailLevelReward const* mailReward = sObjectMgr->GetMailLevelReward(level, getRaceMask());
+ if (mailReward && sScriptMgr->CanGiveMailRewardAtGiveLevel(this, level))
+ {
+ //- TODO: Poor design of mail system
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+ MailDraft(mailReward->mailTemplateId).SendMailTo(trans, this, MailSender(MAIL_CREATURE, mailReward->senderEntry));
+ CharacterDatabase.CommitTransaction(trans);
+ }
+
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL);
+
+ // Refer-A-Friend
+ if (GetSession()->GetRecruiterId())
+ if (level < sWorld->getIntConfig(CONFIG_MAX_RECRUIT_A_FRIEND_BONUS_PLAYER_LEVEL))
+ if (level % 2 == 0)
+ {
+ ++m_grantableLevels;
+
+ if (!HasByteFlag(PLAYER_FIELD_BYTES, 1, 0x01))
+ SetByteFlag(PLAYER_FIELD_BYTES, 1, 0x01);
+ }
+
+ SendQuestGiverStatusMultiple();
+
+ sScriptMgr->OnPlayerLevelChanged(this, oldLevel);
+
+ //npcbot: force bots to update stats
+ if (HaveBot())
+ _botMgr->SetBotsShouldUpdateStats();
+ //end npcbot
+}
+
+void Player::InitTalentForLevel()
+{
+ uint32 talentPointsForLevel = CalculateTalentsPoints();
+
+ // xinef: more talent points that we have are used, reset
+ if (m_usedTalentCount > talentPointsForLevel)
+ resetTalents(true);
+ // xinef: else, recalculate free talent points count
+ else
+ SetFreeTalentPoints(talentPointsForLevel - m_usedTalentCount);
+
+ 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();
+
+ PlayerClassLevelInfo classInfo;
+ sObjectMgr->GetPlayerClassLevelInfo(getClass(), GetLevel(), &classInfo);
+
+ PlayerLevelInfo info;
+ sObjectMgr->GetPlayerLevelInfo(getRace(true), getClass(), GetLevel(), &info);
+
+ uint32 maxPlayerLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
+ sScriptMgr->OnSetMaxLevel(this, maxPlayerLevel);
+ SetUInt32Value(PLAYER_FIELD_MAX_LEVEL, maxPlayerLevel);
+ 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);
+
+ // reset size before reapply auras
+ SetObjectScale(1.0f);
+
+ // save base values (bonuses already included in stored stats
+ for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
+ SetCreateStat(Stats(i), info.stats[i]);
+
+ for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
+ SetStat(Stats(i), info.stats[i]);
+
+ SetCreateHealth(classInfo.basehealth);
+
+ //set create powers
+ SetCreateMana(classInfo.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);
+ for (uint8 i = 0; i < 7; ++i)
+ {
+ SetInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG + i, 0);
+ SetInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + i, 0);
+ SetFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT + i, 1.00f);
+ }
+
+ //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);
+
+ SetInt32Value(UNIT_FIELD_ATTACK_POWER, 0);
+ SetInt32Value(UNIT_FIELD_ATTACK_POWER_MODS, 0);
+ SetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER, 0.0f);
+ SetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER, 0);
+ SetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MODS, 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 < 7; ++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, 0);
+
+ // 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 (uint8 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 (uint8 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 (uint8 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 (uint8 i = POWER_MANA; i < MAX_POWERS; ++i)
+ SetMaxPower(Powers(i), uint32(GetCreatePowers(Powers(i))));
+
+ SetMaxHealth(classInfo.basehealth); // 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_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);
+ SetImmuneToAll(false);
+ SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED); // must be set
+
+ SetUnitFlag2(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.
+ RemovePlayerFlag(PLAYER_FLAGS_AFK | PLAYER_FLAGS_DND | PLAYER_FLAGS_GM | PLAYER_FLAGS_GHOST | PLAYER_ALLOW_ONLY_ABILITY);
+
+ RemoveStandFlags(UNIT_STAND_FLAGS_ALL); // one form stealth modified bytes
+ if (HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP))
+ {
+ RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP | UNIT_BYTE2_FLAG_SANCTUARY);
+ sScriptMgr->OnFfaPvpStateUpdate(this, false);
+
+ }
+ // 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.
+ SetFullHealth();
+ 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_HAPPINESS, 0);
+ SetPower(POWER_RUNIC_POWER, 0);
+
+ // update level to hunter/summon pet
+ if (Pet* pet = GetPet())
+ pet->SynchronizeLevelWithOwner();
+}
+
+void Player::SendInitialSpells()
+{
+ uint32 curTime = GameTime::GetGameTimeMS().count();
+ uint32 infTime = GameTime::GetGameTimeMS().count() + infinityCooldownDelayCheck;
+
+ uint16 spellCount = 0;
+
+ WorldPacket data(SMSG_INITIAL_SPELLS, (1 + 2 + 4 * m_spells.size() + 2 + m_spellCooldowns.size() * (4 + 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->IsInSpec(GetActiveSpec()))
+ continue;
+
+ data << uint32(itr->first);
+ data << uint16(0); // it's not slot id
+
+ ++spellCount;
+ }
+
+ // Added spells from glyphs too (needed by spell tooltips)
+ for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
+ {
+ if (uint32 glyph = GetGlyph(i))
+ {
+ if (GlyphPropertiesEntry const* glyphEntry = sGlyphPropertiesStore.LookupEntry(glyph))
+ {
+ data << uint32(glyphEntry->SpellId);
+ data << uint16(0); // it's not slot id
+
+ ++spellCount;
+ }
+ }
+ }
+
+ // xinef: we have to send talents, but not those on m_spells list
+ for (PlayerTalentMap::iterator itr = m_talents.begin(); itr != m_talents.end(); ++itr)
+ {
+ if (itr->second->State == PLAYERSPELL_REMOVED)
+ continue;
+
+ // xinef: remove all active talent auras
+ if (!(itr->second->specMask & GetActiveSpecMask()))
+ continue;
+
+ // xinef: already sent from m_spells
+ if (itr->second->inSpellBook)
+ continue;
+
+ data << uint32(itr->first);
+ data << uint16(0); // it's not slot id
+
+ ++spellCount;
+ }
+
+ 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)
+ {
+ if (!itr->second.needSendToClient)
+ continue;
+
+ SpellInfo const* sEntry = sSpellMgr->GetSpellInfo(itr->first);
+ if (!sEntry)
+ continue;
+
+ data << uint32(itr->first);
+
+ data << uint16(itr->second.itemid); // cast item id
+ data << uint16(itr->second.category); // spell category
+
+ // send infinity cooldown in special format
+ if (itr->second.end >= infTime)
+ {
+ data << uint32(1); // cooldown
+ data << uint32(0x80000000); // category cooldown
+ continue;
+ }
+
+ uint32 cooldown = itr->second.end > curTime ? itr->second.end - curTime : 0;
+ data << uint32(itr->second.category ? 0 : cooldown); // cooldown
+ data << uint32(itr->second.category ? cooldown : 0); // category cooldown
+ }
+
+ GetSession()->SendPacket(&data);
+}
+
+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, ObjectGuid::LowType 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 << (uint32) 0;
+ GetSession()->SendPacket(&data);
+}
+
+void Player::AddNewMailDeliverTime(time_t deliver_time)
+{
+ if (deliver_time <= GameTime::GetGameTime().count()) // 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::addTalent(uint32 spellId, uint8 addSpecMask, uint8 oldTalentRank)
+{
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (!SpellMgr::CheckSpellValid(spellInfo, spellId, true))
+ return false;
+
+ TalentSpellPos const* talentPos = GetTalentSpellPos(spellId);
+ if (!talentPos)
+ return false;
+
+ TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentPos->talent_id);
+ if (!talentInfo)
+ return false;
+
+ // xinef: remove old talent rank if any
+ if (oldTalentRank)
+ {
+ _removeTalent(talentInfo->RankID[oldTalentRank - 1], addSpecMask);
+ _removeTalentAurasAndSpells(talentInfo->RankID[oldTalentRank - 1]);
+ SendLearnPacket(talentInfo->RankID[oldTalentRank - 1], false);
+ }
+
+ // xinef: add talent auras and spells
+ if (GetActiveSpecMask() & addSpecMask)
+ _addTalentAurasAndSpells(spellId);
+
+ // xinef: find the spell on our talent map
+ PlayerTalentMap::iterator itr = m_talents.find(spellId);
+
+ // xinef: we do not have such a spell on our talent map
+ if (itr == m_talents.end())
+ {
+ PlayerSpellState state = isBeingLoaded() ? PLAYERSPELL_UNCHANGED : PLAYERSPELL_NEW;
+ PlayerTalent* newTalent = new PlayerTalent();
+ newTalent->State = state;
+ newTalent->specMask = addSpecMask;
+ newTalent->talentID = talentInfo->TalentID;
+ newTalent->inSpellBook = talentInfo->addToSpellBook && !spellInfo->HasAttribute(SPELL_ATTR0_PASSIVE) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL);
+
+ m_talents[spellId] = newTalent;
+ return true;
+ }
+ // xinef: if current mask does not cover addMask, add it to iterator and save changes to DB
+ else if (!(itr->second->specMask & addSpecMask))
+ {
+ itr->second->specMask |= addSpecMask;
+ if (itr->second->State != PLAYERSPELL_NEW)
+ itr->second->State = PLAYERSPELL_CHANGED;
+
+ return true;
+ }
+
+ return false;
+}
+
+void Player::_removeTalent(uint32 spellId, uint8 specMask)
+{
+ PlayerTalentMap::iterator itr = m_talents.find(spellId);
+ if (itr == m_talents.end() || itr->second->State == PLAYERSPELL_REMOVED)
+ return;
+
+ _removeTalent(itr, specMask);
+}
+
+void Player::_removeTalent(PlayerTalentMap::iterator& itr, uint8 specMask)
+{
+ // xinef: remove spec mask from iterator
+ itr->second->specMask &= ~specMask;
+
+ // xinef: if talent is not present in any spec - remove
+ if (itr->second->specMask == 0)
+ {
+ if (itr->second->State == PLAYERSPELL_NEW)
+ {
+ delete itr->second;
+ m_talents.erase(itr);
+ return;
+ }
+ else
+ itr->second->State = PLAYERSPELL_REMOVED;
+ }
+ // xinef: otherwise save changes to DB
+ else if (itr->second->State != PLAYERSPELL_NEW)
+ itr->second->State = PLAYERSPELL_CHANGED;
+}
+
+void Player::_removeTalentAurasAndSpells(uint32 spellId)
+{
+ RemoveOwnedAura(spellId);
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ {
+ // pussywizard: remove pet auras
+ if (PetAura const* petSpell = sSpellMgr->GetPetAura(spellId, i))
+ RemovePetAura(petSpell);
+
+ // pussywizard: remove all triggered auras
+ if (spellInfo->Effects[i].TriggerSpell > 0)
+ RemoveAurasDueToSpell(spellInfo->Effects[i].TriggerSpell);
+
+ // xinef: remove temporary spells added by talent
+ // xinef: recursively remove all learnt spells
+ if (spellInfo->Effects[i].TriggerSpell > 0 && spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL)
+ {
+ removeSpell(spellInfo->Effects[i].TriggerSpell, SPEC_MASK_ALL, true);
+ _removeTalentAurasAndSpells(spellInfo->Effects[i].TriggerSpell);
+ }
+ }
+}
+
+void Player::_addTalentAurasAndSpells(uint32 spellId)
+{
+ // pussywizard: spells learnt from talents are added as TEMPORARY, so not saved to db (only the talent itself is saved)
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL))
+ {
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && !sSpellMgr->IsAdditionalTalentSpell(spellInfo->Effects[i].TriggerSpell))
+ _addSpell(spellInfo->Effects[i].TriggerSpell, SPEC_MASK_ALL, true);
+ }
+ else if (spellInfo->IsPassive() || (spellInfo->HasAttribute(SPELL_ATTR0_DO_NOT_DISPLAY) && spellInfo->Stances))
+ {
+ if (IsNeedCastPassiveSpellAtLearn(spellInfo))
+ CastSpell(this, spellId, true);
+ }
+}
+
+void Player::SendLearnPacket(uint32 spellId, bool learn)
+{
+ if (learn)
+ {
+ WorldPacket data(SMSG_LEARNED_SPELL, 6);
+ data << uint32(spellId);
+ data << uint16(0);
+ GetSession()->SendPacket(&data);
+ }
+ else
+ {
+ WorldPacket data(SMSG_REMOVED_SPELL, 4);
+ data << uint32(spellId);
+ GetSession()->SendPacket(&data);
+ }
+}
+
+bool Player::addSpell(uint32 spellId, uint8 addSpecMask, bool updateActive, bool temporary /*= false*/, bool learnFromSkill /*= false*/)
+{
+ if (!_addSpell(spellId, addSpecMask, temporary, learnFromSkill))
+ return false;
+
+ if (!updateActive)
+ return true;
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); // must exist, checked in _addSpell
+
+ // pussywizard: now update active state for all ranks of this spell! and send packet to swap on action bar
+ // pussywizard: assumption - it's in all specs, can't be a talent
+ if (!spellInfo->IsStackableWithRanks() && spellInfo->IsRanked())
+ {
+ SpellInfo const* nextSpellInfo = sSpellMgr->GetSpellInfo(sSpellMgr->GetFirstSpellInChain(spellInfo->Id));
+ while (nextSpellInfo)
+ {
+ PlayerSpellMap::iterator itr = m_spells.find(nextSpellInfo->Id);
+ if (itr != m_spells.end() && itr->second->State != PLAYERSPELL_REMOVED && itr->second->Active)
+ {
+ if (nextSpellInfo->GetRank() < spellInfo->GetRank())
+ {
+ itr->second->Active = false;
+ if (IsInWorld())
+ {
+ WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4);
+ data << uint32(nextSpellInfo->Id);
+ data << uint32(spellInfo->Id);
+ GetSession()->SendPacket(&data);
+ }
+ return false;
+ }
+ else if (nextSpellInfo->GetRank() > spellInfo->GetRank())
+ {
+ PlayerSpellMap::iterator itr2 = m_spells.find(spellInfo->Id);
+ if (itr2 != m_spells.end())
+ itr2->second->Active = false;
+ return false;
+ }
+ }
+ nextSpellInfo = nextSpellInfo->GetNextRankSpell();
+ }
+ }
+
+ return true;
+}
+
+bool Player::_addSpell(uint32 spellId, uint8 addSpecMask, bool temporary, bool learnFromSkill /*= false*/)
+{
+ // pussywizard: this can be called to OVERWRITE currently existing spell params! usually to set active = false for lower ranks of a spell
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (!SpellMgr::CheckSpellValid(spellInfo, spellId, false))
+ return false;
+
+ // pussywizard: already found and temporary, nothing to do
+ PlayerSpellMap::iterator itr = m_spells.find(spellId);
+ if (itr != m_spells.end() && itr->second->State == PLAYERSPELL_TEMPORARY)
+ return false;
+
+ // xinef: send packet so client can properly recognize this new spell
+ // xinef: ignore passive spells and spells with learn effect
+ // xinef: send spells with no aura effects (ie dual wield)
+ if (IsInWorld() && !isBeingLoaded() && temporary && !learnFromSkill && (!spellInfo->HasAttribute(SpellAttr0(SPELL_ATTR0_PASSIVE | SPELL_ATTR0_DO_NOT_DISPLAY)) || !spellInfo->HasAnyAura()) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL))
+ SendLearnPacket(spellInfo->Id, true);
+
+ // xinef: DO NOT allow to learn spell with effect learn spell!
+ // xinef: if spell possess spell learn effects only, learn those spells as temporary (eg. Metamorphosis, Tree of Life)
+ if (temporary && spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL))
+ {
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ if (spellInfo->Effects[i].IsEffect())
+ {
+ if (spellInfo->Effects[i].Effect != SPELL_EFFECT_LEARN_SPELL)
+ {
+ LOG_INFO("entities.player", "TRYING TO LEARN SPELL WITH EFFECT LEARN: {}, PLAYER: {}", spellId, GetGUID().ToString());
+ return false;
+ //ABORT();
+ }
+ else if (SpellInfo const* learnSpell = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell))
+ _addSpell(learnSpell->Id, SPEC_MASK_ALL, true);
+ }
+
+ return false;
+ }
+
+ if (itr != m_spells.end()) // pussywizard: already know this spell, so update information
+ {
+ // pussywizard: do nothing if already set as wanted
+ if (itr->second->State != PLAYERSPELL_REMOVED && (itr->second->specMask & addSpecMask) == addSpecMask)
+ return false;
+
+ // pussywizard: need cast auras, learn linked spells, do professions stuff, etc.
+ // pussywizard: but only for spells that are really added (inactive -> active OR added to current spec)
+ bool spellIsNew = true;
+
+ // pussywizard: present in m_spells, not removed, already in current spec, already active
+ if (itr->second->State != PLAYERSPELL_REMOVED && itr->second->IsInSpec(m_activeSpec))
+ spellIsNew = false;
+
+ // pussywizard: update info in m_spells
+ if (itr->second->State != PLAYERSPELL_NEW && (itr->second->specMask & addSpecMask) != addSpecMask)
+ itr->second->State = PLAYERSPELL_CHANGED;
+ itr->second->Active = true;
+ itr->second->specMask |= addSpecMask;
+
+ if (!spellIsNew)
+ return true;
+ }
+ else // pussywizard: not found in m_spells
+ {
+ PlayerSpell* newspell = new PlayerSpell;
+ newspell->Active = true;
+ newspell->State = temporary ? PLAYERSPELL_TEMPORARY : (isBeingLoaded() ? PLAYERSPELL_UNCHANGED : PLAYERSPELL_NEW);
+ newspell->specMask = addSpecMask;
+
+ m_spells[spellId] = newspell;
+ }
+
+ // pussywizard: return if spell not in current spec
+ // pussywizard: return true to fix active for ranks, this condition is true only at loading, so no problems with learning packets
+ if (!((1 << GetActiveSpec()) & addSpecMask))
+ return true;
+
+ // xinef: do not add spells with effect learn spell
+ if (spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL))
+ {
+ LOG_INFO("entities.player", "TRYING TO LEARN SPELL WITH EFFECT LEARN 2: {}, PLAYER: {}", spellId, GetGUID().ToString());
+ m_spells.erase(spellInfo->Id); // mem leak, but should never happen
+ return false;
+ //ABORT();
+ }
+ // pussywizard: cast passive spells (including all talents without SPELL_EFFECT_LEARN_SPELL) with additional checks
+ else if (spellInfo->IsPassive() || (spellInfo->HasAttribute(SPELL_ATTR0_DO_NOT_DISPLAY) && spellInfo->Stances))
+ {
+ if (IsNeedCastPassiveSpellAtLearn(spellInfo))
+ CastSpell(this, spellId, true);
+ }
+ // pussywizard: cast and return, learnt spells will update profession count, etc.
+ else if (spellInfo->HasEffect(SPELL_EFFECT_SKILL_STEP))
+ {
+ CastSpell(this, spellId, true);
+ return false;
+ }
+
+ // xinef: unapply aura stats if dont meet requirements
+ // xinef: handle only if player is not loaded, loading is handled in loadfromdb
+ if (!isBeingLoaded())
+ if (Aura* aura = GetAura(spellId))
+ {
+ if (aura->GetSpellInfo()->CasterAuraState == AURA_STATE_HEALTHLESS_35_PERCENT ||
+ aura->GetSpellInfo()->CasterAuraState == AURA_STATE_HEALTH_ABOVE_75_PERCENT ||
+ aura->GetSpellInfo()->CasterAuraState == AURA_STATE_HEALTHLESS_20_PERCENT )
+ if (!HasAuraState((AuraStateType)aura->GetSpellInfo()->CasterAuraState))
+ aura->HandleAllEffects(aura->GetApplicationOfTarget(GetGUID()), AURA_EFFECT_HANDLE_REAL, false);
+ }
+
+ // pussywizard: update free primary prof points
+ if (uint32 freeProfs = GetFreePrimaryProfessionPoints())
+ {
+ if (spellInfo->IsPrimaryProfessionFirstRank())
+ SetFreePrimaryProfessions(freeProfs - 1);
+ }
+
+ uint16 maxskill = GetMaxSkillValueForLevel();
+ SpellLearnSkillNode const* spellLearnSkill = sSpellMgr->GetSpellLearnSkill(spellId);
+ SkillLineAbilityMapBounds skill_bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId);
+ // xinef: set appropriate skill value
+ if (spellLearnSkill)
+ {
+ uint32 skill_value = GetPureSkillValue(spellLearnSkill->skill);
+ uint32 skill_max_value = GetPureMaxSkillValue(spellLearnSkill->skill);
+ uint32 new_skill_max_value = spellLearnSkill->maxvalue == 0 ? maxskill : spellLearnSkill->maxvalue;
+
+ if (skill_value < spellLearnSkill->value)
+ skill_value = spellLearnSkill->value;
+ if (skill_max_value < new_skill_max_value)
+ skill_max_value = new_skill_max_value;
+
+ SetSkill(spellLearnSkill->skill, spellLearnSkill->step, skill_value, skill_max_value);
+ }
+ 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->SkillLine);
+ if (!pSkill)
+ {
+ continue;
+ }
+
+ // @todo confirm if rogues start wth lockpicking skill at level 1 but only recieve the spell to use it at level 16
+ // Added for runeforging, it is confirmed via sniff that this happens when death knights learn the spell, not on character creation.
+ if ((_spell_idx->second->AcquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN && !HasSkill(pSkill->id)) || ((pSkill->id == SKILL_LOCKPICKING || pSkill->id == SKILL_RUNEFORGING) && _spell_idx->second->TrivialSkillLineRankHigh == 0))
+ {
+ LearnDefaultSkill(pSkill->id, 0);
+ }
+
+ if (pSkill->id == SKILL_MOUNTS && !Has310Flyer(false))
+ {
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ {
+ if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED && spellInfo->Effects[i].CalcValue() == 310)
+ {
+ SetHas310Flyer(true);
+ }
+ }
+ }
+ }
+ }
+
+ // xinef: update achievement criteria
+ if (!GetSession()->PlayerLoading())
+ {
+ for (SkillLineAbilityMap::const_iterator _spell_idx = skill_bounds.first; _spell_idx != skill_bounds.second; ++_spell_idx)
+ {
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LINE, _spell_idx->second->SkillLine);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILLLINE_SPELLS, _spell_idx->second->SkillLine);
+ }
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL, spellId);
+ }
+
+ return true;
+}
+
+bool Player::IsNeedCastPassiveSpellAtLearn(SpellInfo const* spellInfo) const
+{
+ // 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
+ ShapeshiftForm form = GetShapeshiftForm();
+ return (!spellInfo->Stances || (form && (spellInfo->Stances & (1 << (form - 1)))) ||
+ (!form && spellInfo->HasAttribute(SPELL_ATTR2_ALLOW_WHILE_NOT_SHAPESHIFTED)));
+}
+
+void Player::learnSpell(uint32 spellId, bool temporary /*= false*/, bool learnFromSkill /*= false*/)
+{
+ // Xinef: don't allow to learn active spell once more
+ if (HasActiveSpell(spellId))
+ {
+ LOG_DEBUG("entities.player", "Player ({}) tries to learn already active spell: {}", GetGUID().ToString(), spellId);
+ return;
+ }
+
+ uint32 firstRankSpellId = sSpellMgr->GetFirstSpellInChain(spellId);
+ bool thisSpec = GetTalentSpellCost(firstRankSpellId) > 0 || sSpellMgr->IsAdditionalTalentSpell(firstRankSpellId);
+ bool added = addSpell(spellId, thisSpec ? GetActiveSpecMask() : SPEC_MASK_ALL, true, temporary, learnFromSkill);
+ if (added)
+ {
+ sScriptMgr->OnPlayerLearnSpell(this, spellId);
+
+ // pussywizard: a system message "you have learnt spell X (rank Y)"
+ if (IsInWorld())
+ SendLearnPacket(spellId, true);
+ }
+
+ // pussywizard: rank stuff at the end!
+ if (uint32 nextSpell = sSpellMgr->GetNextSpellInChain(spellId))
+ {
+ // pussywizard: lookup next rank in m_spells (the only talents on m_spella are for example pyroblast, that have all ranks restored upon learning rank 1)
+ // pussywizard: next ranks must not be in current spec (otherwise no need to learn already learnt)
+ PlayerSpellMap::iterator itr = m_spells.find(nextSpell);
+ if (itr != m_spells.end() && itr->second->State != PLAYERSPELL_REMOVED && !itr->second->IsInSpec(m_activeSpec))
+ learnSpell(nextSpell, temporary);
+ }
+
+ // xinef: if we learn new spell, check all spells requiring this spell, if we have such a spell, and it is not in current spec - learn it
+ SpellsRequiringSpellMapBounds spellsRequiringSpell = sSpellMgr->GetSpellsRequiringSpellBounds(spellId);
+ for (SpellsRequiringSpellMap::const_iterator itr = spellsRequiringSpell.first; itr != spellsRequiringSpell.second; ++itr)
+ {
+ PlayerSpellMap::iterator itr2 = m_spells.find(itr->second);
+ if (itr2 != m_spells.end() && itr2->second->State != PLAYERSPELL_REMOVED && !itr2->second->IsInSpec(m_activeSpec))
+ learnSpell(itr2->first, temporary);
+ }
+}
+
+void Player::removeSpell(uint32 spell_id, uint8 removeSpecMask, bool onlyTemporary)
+{
+ PlayerSpellMap::iterator itr = m_spells.find(spell_id);
+ if (itr == m_spells.end())
+ return;
+
+ // pussywizard: nothing to do if already removed or not in specs of removeSpecMask
+ if (itr->second->State == PLAYERSPELL_REMOVED || (itr->second->specMask & removeSpecMask) == 0)
+ return;
+
+ // pussywizard: avoid any possible bugs
+ if (onlyTemporary && itr->second->State != PLAYERSPELL_TEMPORARY)
+ return;
+
+ // pussywizard: remove non-talent higher ranks (recursive)
+ // pussywizard: do this at the beginning, not in the middle of removing!
+ if (uint32 nextSpell = sSpellMgr->GetNextSpellInChain(spell_id))
+ if (!GetTalentSpellPos(nextSpell))
+ removeSpell(nextSpell, removeSpecMask, onlyTemporary);
+
+ // xinef: if current spell has talentcost, remove spells requiring this spell
+ uint32 firstRankSpellId = sSpellMgr->GetFirstSpellInChain(spell_id);
+ if (GetTalentSpellCost(firstRankSpellId))
+ {
+ SpellsRequiringSpellMapBounds spellsRequiringSpell = sSpellMgr->GetSpellsRequiringSpellBounds(firstRankSpellId);
+ for (auto spellsItr = spellsRequiringSpell.first; spellsItr != spellsRequiringSpell.second; ++spellsItr)
+ {
+ removeSpell(spellsItr->second, removeSpecMask, onlyTemporary);
+ }
+ }
+
+ // pussywizard: re-search, it can be corrupted in prev loop
+ itr = m_spells.find(spell_id);
+ if (itr == m_spells.end())
+ return;
+
+ itr->second->specMask = (((uint8)itr->second->specMask) & ~removeSpecMask); // pussywizard: update specMask in map
+
+ // pussywizard: some more conditions needed for spells like pyroblast (shouldn't be fully removed when not available in any spec, should stay in db with specMask = 0)
+ if (GetTalentSpellCost(firstRankSpellId) == 0 && !sSpellMgr->IsAdditionalTalentSpell(firstRankSpellId) && itr->second->specMask == 0)
+ {
+ if (itr->second->State == PLAYERSPELL_NEW || itr->second->State == PLAYERSPELL_TEMPORARY)
+ {
+ delete itr->second;
+ m_spells.erase(itr);
+ }
+ else
+ itr->second->State = PLAYERSPELL_REMOVED;
+ }
+ else if (itr->second->State != PLAYERSPELL_NEW && itr->second->State != PLAYERSPELL_TEMPORARY)
+ itr->second->State = PLAYERSPELL_CHANGED;
+
+ // xinef: this is used for talents and they are not removed in removeSpell function...
+ // xinef: however ill leave this here just in case
+ // pussywizard: remove owned aura obtained from currently removed spell
+ RemoveOwnedAura(spell_id);
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id);
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ {
+ // pussywizard: remove pet auras
+ if (PetAura const* petSpell = sSpellMgr->GetPetAura(spell_id, i))
+ RemovePetAura(petSpell);
+
+ // pussywizard: remove all triggered auras
+ if (spellInfo->Effects[i].TriggerSpell > 0)
+ RemoveAurasDueToSpell(spellInfo->Effects[i].TriggerSpell);
+ }
+
+ // pussywizard: update free primary prof points
+ if (spellInfo->IsPrimaryProfessionFirstRank())
+ {
+ uint32 freeProfs = GetFreePrimaryProfessionPoints() + 1;
+ if (freeProfs <= sWorld->getIntConfig(CONFIG_MAX_PRIMARY_TRADE_SKILL))
+ SetFreePrimaryProfessions(freeProfs);
+ }
+
+ // pussywizard: update 310 flyer
+ if (Has310Flyer(false))
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED && spellInfo->Effects[i].CalcValue() == 310)
+ Has310Flyer(true, spell_id);
+
+ // pussywizard: remove dependent skill
+ SpellLearnSkillNode const* spellLearnSkill = sSpellMgr->GetSpellLearnSkill(spell_id);
+ if (spellLearnSkill)
+ {
+ uint32 prev_spell = sSpellMgr->GetPrevSpellInChain(spell_id);
+
+ if (!prev_spell) // pussywizard: first rank, remove skill
+ SetSkill(spellLearnSkill->skill, 0, 0, 0);
+ else // pussywizard: search previous ranks
+ {
+ 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) // pussywizard: not found prev skill setting, remove skill
+ SetSkill(spellLearnSkill->skill, 0, 0, 0);
+ else // pussywizard: set to prev skill setting values
+ {
+ uint32 skill_value = GetPureSkillValue(prevSkill->skill);
+ uint32 skill_max_value = GetPureMaxSkillValue(prevSkill->skill);
+ uint32 new_skill_max_value = prevSkill->maxvalue == 0 ? GetMaxSkillValueForLevel() : prevSkill->maxvalue;
+
+ if (skill_value > prevSkill->value)
+ skill_value = prevSkill->value;
+ if (skill_max_value > new_skill_max_value)
+ skill_max_value = new_skill_max_value;
+
+ SetSkill(prevSkill->skill, prevSkill->step, skill_value, skill_max_value);
+ }
+ }
+ }
+ else
+ {
+ SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spell_id);
+ // most likely will never be used, haven't heard of cases where players unlearn a mount
+ if (Has310Flyer(false) && spellInfo)
+ {
+ for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
+ {
+ SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(_spell_idx->second->SkillLine);
+ if (!pSkill)
+ continue;
+
+ if (_spell_idx->second->SkillLine == SKILL_MOUNTS)
+ {
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ {
+ if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED &&
+ spellInfo->Effects[i].CalcValue() == 310)
+ {
+ Has310Flyer(true, spell_id); // with true as first argument its also used to set/remove the flag
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // pussywizard: remove from spell book (can't be replaced by previous rank, because such spells can't be unlearnt)
+ if (!onlyTemporary || ((!spellInfo->HasAttribute(SpellAttr0(SPELL_ATTR0_PASSIVE | SPELL_ATTR0_DO_NOT_DISPLAY)) || !spellInfo->HasAnyAura()) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL)))
+ {
+ sScriptMgr->OnPlayerForgotSpell(this, spell_id);
+ SendLearnPacket(spell_id, false);
+ }
+}
+
+bool Player::Has310Flyer(bool checkAllSpells, uint32 excludeSpellId)
+{
+ if (!checkAllSpells)
+ return m_ExtraFlags & PLAYER_EXTRA_HAS_310_FLYER;
+ else
+ {
+ SetHas310Flyer(false);
+ SpellInfo const* spellInfo;
+ for (PlayerSpellMap::iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr)
+ {
+ // pussywizard:
+ if (itr->second->State == PLAYERSPELL_REMOVED)
+ continue;
+
+ if (itr->first == excludeSpellId)
+ continue;
+
+ SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(itr->first);
+ for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
+ {
+ if (_spell_idx->second->SkillLine != SKILL_MOUNTS)
+ break; // We can break because mount spells belong only to one skillline (at least 310 flyers do)
+
+ spellInfo = sSpellMgr->AssertSpellInfo(itr->first);
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED &&
+ spellInfo->Effects[i].CalcValue() == 310)
+ {
+ SetHas310Flyer(true);
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void Player::RemoveSpellCooldown(uint32 spell_id, bool update /* = false */)
+{
+ m_spellCooldowns.erase(spell_id);
+
+ if (update)
+ SendClearCooldown(spell_id, this);
+}
+
+void Player::RemoveCategoryCooldown(uint32 cat)
+{
+ SpellCategoryStore::const_iterator i_scstore = sSpellsByCategoryStore.find(cat);
+ if (i_scstore != sSpellsByCategoryStore.end())
+ for (SpellCategorySet::const_iterator i_scset = i_scstore->second.begin(); i_scset != i_scstore->second.end(); ++i_scset)
+ RemoveSpellCooldown(i_scset->second, true);
+}
+
+void Player::RemoveArenaSpellCooldowns(bool removeActivePetCooldowns)
+{
+ // remove cooldowns on spells that have < 10 min CD
+ uint32 infTime = GameTime::GetGameTimeMS().count() + infinityCooldownDelayCheck;
+ SpellCooldowns::iterator itr, next;
+ for (itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); itr = next)
+ {
+ next = itr;
+ ++next;
+ SpellInfo const* spellInfo = sSpellMgr->CheckSpellInfo(itr->first);
+ if (!spellInfo)
+ {
+ continue;
+ }
+
+ if (spellInfo->HasAttribute(SPELL_ATTR4_IGNORE_DEFAULT_ARENA_RESTRICTIONS))
+ RemoveSpellCooldown(itr->first, true);
+ else if (spellInfo->RecoveryTime < 10 * MINUTE * IN_MILLISECONDS && spellInfo->CategoryRecoveryTime < 10 * MINUTE * IN_MILLISECONDS && itr->second.end < infTime// xinef: dont remove active cooldowns - bugz
+ && itr->second.maxduration < 10 * MINUTE * IN_MILLISECONDS) // xinef: dont clear cooldowns that have maxduration > 10 minutes (eg item cooldowns with no spell.dbc cooldown info)
+ RemoveSpellCooldown(itr->first, true);
+ }
+
+ // pet cooldowns
+ if (removeActivePetCooldowns)
+ if (Pet* pet = GetPet())
+ {
+ // notify player
+ for (CreatureSpellCooldowns::const_iterator itr2 = pet->m_CreatureSpellCooldowns.begin(); itr2 != pet->m_CreatureSpellCooldowns.end(); ++itr2)
+ SendClearCooldown(itr2->first, pet);
+
+ // actually clear cooldowns
+ pet->m_CreatureSpellCooldowns.clear();
+ }
+}
+
+void Player::RemoveAllSpellCooldown()
+{
+ uint32 infTime = GameTime::GetGameTimeMS().count() + infinityCooldownDelayCheck;
+ if (!m_spellCooldowns.empty())
+ {
+ for (SpellCooldowns::const_iterator itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); ++itr)
+ if (itr->second.end < infTime)
+ SendClearCooldown(itr->first, this);
+
+ m_spellCooldowns.clear();
+ }
+}
+
+void Player::_LoadSpellCooldowns(PreparedQueryResult result)
+{
+ // some cooldowns can be already set at aura loading...
+
+ //QueryResult* result = CharacterDatabase.Query("SELECT spell, category, item, time FROM character_spell_cooldown WHERE guid = '{}'", GetGUID().GetCounter()());
+
+ if (result)
+ {
+ time_t curTime = GameTime::GetGameTime().count();
+
+ do
+ {
+ Field* fields = result->Fetch();
+ uint32 spell_id = fields[0].Get();
+ uint16 category = fields[1].Get();
+ uint32 item_id = fields[2].Get();
+ uint32 db_time = fields[3].Get();
+ bool needSend = fields[4].Get();
+
+ if (!sSpellMgr->GetSpellInfo(spell_id))
+ {
+ LOG_ERROR("entities.player", "Player {} has unknown spell {} in `character_spell_cooldown`, skipping.", GetGUID().ToString(), spell_id);
+ continue;
+ }
+
+ // skip outdated cooldown
+ if (db_time <= curTime)
+ continue;
+
+ _AddSpellCooldown(spell_id, category, item_id, (db_time - curTime) * IN_MILLISECONDS, needSend);
+
+ LOG_DEBUG("entities.player.loading", "Player ({}) spell {}, item {} cooldown loaded ({} secs).", GetGUID().ToString(), spell_id, item_id, uint32(db_time - curTime));
+ } while (result->NextRow());
+ }
+}
+
+void Player::_SaveSpellCooldowns(CharacterDatabaseTransaction trans, bool logout)
+{
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_COOLDOWN);
+ stmt->SetData(0, GetGUID().GetCounter());
+ trans->Append(stmt);
+
+ time_t curTime = GameTime::GetGameTime().count();
+ uint32 curMSTime = GameTime::GetGameTimeMS().count();
+ uint32 infTime = curMSTime + infinityCooldownDelayCheck;
+
+ bool first_round = true;
+ std::ostringstream ss;
+
+ // remove outdated and save active
+ for (SpellCooldowns::iterator itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end();)
+ {
+ // Xinef: dummy cooldown for procs
+ if (itr->first == uint32(-1))
+ {
+ ++itr;
+ continue;
+ }
+
+ if (itr->second.end <= curMSTime + 1000)
+ m_spellCooldowns.erase(itr++);
+ else if (itr->second.end <= infTime && (logout || itr->second.end > (curMSTime + 5 * MINUTE * IN_MILLISECONDS))) // not save locked cooldowns, it will be reset or set at reload
+ {
+ if (first_round)
+ {
+ ss << "INSERT INTO character_spell_cooldown (guid, spell, category, item, time, needSend) VALUES ";
+ first_round = false;
+ }
+ // next new/changed record prefix
+ else
+ ss << ',';
+
+ uint64 cooldown = uint64(((itr->second.end - curMSTime) / IN_MILLISECONDS) + curTime);
+ ss << '(' << GetGUID().GetCounter() << ',' << itr->first << ',' << itr->second.category << "," << itr->second.itemid << ',' << cooldown << ',' << (itr->second.needSendToClient ? '1' : '0') << ')';
+ ++itr;
+ }
+ else
+ ++itr;
+ }
+ // if something changed execute
+ if (!first_round)
+ trans->Append(ss.str().c_str());
+}
+
+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
+ {
+ uint64 months = (GameTime::GetGameTime().count() - 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 (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 noResetCost)
+{
+ sScriptMgr->OnPlayerTalentsReset(this, noResetCost);
+
+ // xinef: remove at login flag upon talents reset
+ if (HasAtLoginFlag(AT_LOGIN_RESET_TALENTS))
+ RemoveAtLoginFlag(AT_LOGIN_RESET_TALENTS, true);
+
+ // xinef: get max available talent points amount
+ uint32 talentPointsForLevel = CalculateTalentsPoints();
+
+ // xinef: no talent points are used, return
+ if (m_usedTalentCount == 0)
+ return false;
+ m_usedTalentCount = 0;
+
+ // xinef: check if we have enough money
+ uint32 resetCost = 0;
+ if (!noResetCost && !sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST))
+ {
+ resetCost = resetTalentsCost();
+ if (!HasEnoughMoney(resetCost))
+ {
+ SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0);
+ return false;
+ }
+ }
+
+ RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true);
+
+ // xinef: reset talents
+ for (PlayerTalentMap::iterator iter = m_talents.begin(); iter != m_talents.end(); )
+ {
+ PlayerTalentMap::iterator itr = iter++;
+
+ if (itr->second->State == PLAYERSPELL_REMOVED)
+ continue;
+
+ // xinef: talent not in current spec
+ if (!(itr->second->specMask & GetActiveSpecMask()))
+ continue;
+
+ // xinef: remove talent auras
+ _removeTalentAurasAndSpells(itr->first);
+
+ // xinef: check if talent learns spell to spell book
+ TalentEntry const* talentInfo = sTalentStore.LookupEntry(itr->second->talentID);
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first);
+
+ bool removed = false;
+ if (talentInfo->addToSpellBook)
+ if (!spellInfo->HasAttribute(SPELL_ATTR0_PASSIVE) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL))
+ {
+ removeSpell(itr->first, GetActiveSpecMask(), false);
+ removed = true;
+ }
+
+ // Xinef: send unlearn spell packet at talent remove
+ if (!removed)
+ SendLearnPacket(itr->first, false);
+
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL)
+ if (sSpellMgr->IsAdditionalTalentSpell(spellInfo->Effects[i].TriggerSpell))
+ removeSpell(spellInfo->Effects[i].TriggerSpell, GetActiveSpecMask(), false);
+
+ // xinef: remove talent modifies m_talents, move itr to map begin
+ _removeTalent(itr, GetActiveSpecMask());
+ }
+
+ // xinef: remove titan grip if player had it set
+ if (m_canTitanGrip)
+ SetCanTitanGrip(false);
+ // xinef: remove dual wield if player does not have dual wield spell (shamans)
+ if (!HasSpell(674) && m_canDualWield)
+ SetCanDualWield(false);
+
+ AutoUnequipOffhandIfNeed();
+
+ // pussywizard: removed saving to db, nothing important happens and saving only spells and talents may cause data integrity problems (eg. with skills saved to db)
+ SetFreeTalentPoints(talentPointsForLevel);
+
+ if (!noResetCost)
+ {
+ ModifyMoney(-(int32)resetCost);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TALENTS, resetCost);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_NUMBER_OF_TALENT_RESETS, 1);
+
+ m_resetTalentsCost = resetCost;
+ m_resetTalentsTime = GameTime::GetGameTime().count();
+ }
+
+ return true;
+}
+
+void Player::SetFreeTalentPoints(uint32 points)
+{
+ sScriptMgr->OnPlayerFreeTalentPointsChanged(this, points);
+ SetUInt32Value(PLAYER_CHARACTER_POINTS1, points);
+}
+
+Mail* Player::GetMail(uint32 id)
+{
+ for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
+ {
+ if ((*itr)->messageID == id)
+ {
+ return (*itr);
+ }
+ }
+ return nullptr;
+}
+
+void Player::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const
+{
+ if (target == this)
+ {
+ for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i)
+ {
+ if (!m_items[i])
+ continue;
+
+ m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
+ }
+
+ for (uint8 i = INVENTORY_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
+ {
+ if (!m_items[i])
+ continue;
+
+ m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
+ }
+ for (uint8 i = KEYRING_SLOT_START; i < CURRENCYTOKEN_SLOT_END; ++i)
+ {
+ if (!m_items[i])
+ continue;
+
+ m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
+ }
+ }
+
+ Unit::BuildCreateUpdateBlockForPlayer(data, target);
+}
+
+void Player::DestroyForPlayer(Player* target, bool onDeath) const
+{
+ Unit::DestroyForPlayer(target, onDeath);
+
+ for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i) // xinef: previously INVENTORY_SLOT_BAG_END
+ {
+ if (!m_items[i])
+ continue;
+
+ m_items[i]->DestroyForPlayer(target);
+ }
+
+ if (target == this)
+ {
+ for (uint8 i = INVENTORY_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
+ {
+ if (!m_items[i])
+ continue;
+
+ m_items[i]->DestroyForPlayer(target);
+ }
+ for (uint8 i = KEYRING_SLOT_START; i < CURRENCYTOKEN_SLOT_END; ++i)
+ {
+ if (!m_items[i])
+ 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->IsInSpec(m_activeSpec));
+}
+
+bool Player::HasTalent(uint32 spell, uint8 /*spec*/) const
+{
+ PlayerTalentMap::const_iterator itr = m_talents.find(spell);
+ return (itr != m_talents.end() && itr->second->State != PLAYERSPELL_REMOVED && itr->second->IsInSpec(m_activeSpec));
+}
+
+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->IsInSpec(m_activeSpec));
+}
+
+TrainerSpellState Player::GetTrainerSpellState(TrainerSpell const* trainer_spell) const
+{
+ if (!trainer_spell)
+ return TRAINER_SPELL_RED;
+
+ bool hasSpell = true;
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ {
+ if (!trainer_spell->learnedSpell[i])
+ continue;
+
+ if (!HasSpell(trainer_spell->learnedSpell[i]))
+ {
+ hasSpell = false;
+ break;
+ }
+ }
+ // known spell
+ if (hasSpell)
+ return TRAINER_SPELL_GRAY;
+
+ // check skill requirement
+ if (trainer_spell->reqSkill && GetBaseSkillValue(trainer_spell->reqSkill) < trainer_spell->reqSkillValue)
+ return TRAINER_SPELL_RED;
+
+ // check level requirement
+ if (GetLevel() < trainer_spell->reqLevel)
+ return TRAINER_SPELL_RED;
+
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ {
+ if (!trainer_spell->learnedSpell[i])
+ continue;
+
+ // check race/class requirement
+ if (!IsSpellFitByClassAndRace(trainer_spell->learnedSpell[i]))
+ return TRAINER_SPELL_RED;
+
+ if (uint32 prevSpell = sSpellMgr->GetPrevSpellInChain(trainer_spell->learnedSpell[i]))
+ {
+ // check prev.rank requirement
+ if (prevSpell && !HasSpell(prevSpell))
+ return TRAINER_SPELL_RED;
+ }
+
+ SpellsRequiringSpellMapBounds spellsRequired = sSpellMgr->GetSpellsRequiredForSpellBounds(trainer_spell->learnedSpell[i]);
+ for (SpellsRequiringSpellMap::const_iterator itr = spellsRequired.first; itr != spellsRequired.second; ++itr)
+ {
+ // check additional spell requirement
+ if (!HasSpell(itr->second))
+ return TRAINER_SPELL_RED;
+ }
+ }
+
+ // check primary prof. limit
+ // first rank of primary profession spell when there are no proffesions avalible is disabled
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ {
+ if (!trainer_spell->learnedSpell[i])
+ continue;
+ SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainer_spell->learnedSpell[i]);
+ if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank() && (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.
+ *
+ * @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::LowType lowGuid, uint32 accountId, bool updateRealmChars, bool deleteFinally)
+{
+ // for not existed account avoid update realm
+ if (!accountId)
+ updateRealmChars = false;
+
+ ObjectGuid playerGuid = ObjectGuid::Create(lowGuid);
+
+ uint32 charDelete_method = sWorld->getIntConfig(CONFIG_CHARDELETE_METHOD);
+ uint32 charDelete_minLvl = sWorld->getIntConfig(CONFIG_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 CHAR_DELETE_REMOVE
+ if (deleteFinally || sCharacterCache->GetCharacterLevelByGuid(playerGuid) < charDelete_minLvl)
+ charDelete_method = CHAR_DELETE_REMOVE;
+
+ if (uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(playerGuid))
+ if (Guild* guild = sGuildMgr->GetGuildById(guildId))
+ guild->DeleteMember(playerGuid, false, false, true);
+
+ // remove from arena teams
+ LeaveAllArenaTeams(playerGuid);
+
+ // close player ticket if any
+ GmTicket* ticket = sTicketMgr->GetTicketByPlayer(playerGuid);
+ if (ticket)
+ sTicketMgr->CloseTicket(ticket->GetId(), playerGuid);
+
+ // remove from group
+ if (uint32 groupId = sCharacterCache->GetCharacterGuildIdByGuid(playerGuid))
+ if (Group* group = sGroupMgr->GetGroupByGUID(groupId))
+ RemoveFromGroup(group, playerGuid);
+
+ // Remove signs from petitions (also remove petitions if owner);
+ RemovePetitionsAndSigns(playerGuid, 10);
+
+ CharacterDatabasePreparedStatement* stmt = nullptr;
+
+ switch (charDelete_method)
+ {
+ // Completely remove from the database
+ case CHAR_DELETE_REMOVE:
+ {
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_COD_ITEM_MAIL);
+ stmt->SetData(0, lowGuid);
+ PreparedQueryResult resultMail = CharacterDatabase.Query(stmt);
+
+ if (resultMail)
+ {
+ std::unordered_map> itemsByMail;
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS);
+ stmt->SetData(0, lowGuid);
+ PreparedQueryResult resultItems = CharacterDatabase.Query(stmt);
+
+ if (resultItems)
+ {
+ do
+ {
+ Field* fields = resultItems->Fetch();
+ uint32 mailId = fields[14].Get();
+ if (Item* mailItem = _LoadMailedItem(playerGuid, nullptr, mailId, nullptr, fields))
+ {
+ itemsByMail[mailId].push_back(mailItem);
+ }
+ } while (resultItems->NextRow());
+ }
+
+ do
+ {
+ Field* mailFields = resultMail->Fetch();
+
+ uint32 mail_id = mailFields[0].Get();
+ uint8 mailType = mailFields[1].Get();
+ uint16 mailTemplateId = mailFields[2].Get();
+ uint32 sender = mailFields[3].Get();
+ std::string subject = mailFields[4].Get();
+ std::string body = mailFields[5].Get();
+ uint32 money = mailFields[6].Get();
+ bool has_items = mailFields[7].Get();
+
+ // We can return mail now
+ // So firstly delete the old one
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
+ stmt->SetData(0, mail_id);
+ trans->Append(stmt);
+
+ // Mail is not from player
+ if (mailType != MAIL_NORMAL)
+ {
+ if (has_items)
+ {
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
+ stmt->SetData(0, mail_id);
+ trans->Append(stmt);
+ }
+ continue;
+ }
+
+ MailDraft draft(subject, body);
+ if (mailTemplateId)
+ draft = MailDraft(mailTemplateId, false); // items are already included
+
+ auto itemsItr = itemsByMail.find(mail_id);
+ if (itemsItr != itemsByMail.end())
+ {
+ for (Item* item : itemsItr->second)
+ {
+ draft.AddItem(item);
+ }
+
+ // MailDraft will take care of freeing memory.
+ itemsByMail.erase(itemsItr);
+ }
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
+ stmt->SetData(0, mail_id);
+ trans->Append(stmt);
+
+ uint32 pl_account = sCharacterCache->GetCharacterAccountIdByGuid(ObjectGuid(HighGuid::Player, lowGuid));
+
+ draft.AddMoney(money).SendReturnToSender(pl_account, lowGuid, sender, trans);
+ } while (resultMail->NextRow());
+ }
+
+ // Unsummon and delete for pets in world is not required: player deleted from CLI or character list with not loaded pet.
+ // NOW we can finally clear other DB data related to character
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_IDS);
+ stmt->SetData(0, lowGuid);
+ PreparedQueryResult resultPets = CharacterDatabase.Query(stmt);
+
+ if (resultPets)
+ {
+ do
+ {
+ ObjectGuid::LowType petguidlow = (*resultPets)[0].Get();
+ Pet::DeleteFromDB(petguidlow);
+ } while (resultPets->NextRow());
+ }
+
+ // Delete char from social list of online chars
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_SOCIAL);
+ stmt->SetData(0, lowGuid);
+ PreparedQueryResult resultFriends = CharacterDatabase.Query(stmt);
+
+ if (resultFriends)
+ {
+ do
+ {
+ if (Player* pFriend = ObjectAccessor::FindPlayerByLowGUID((*resultFriends)[0].Get()))
+ {
+ pFriend->GetSocial()->RemoveFromSocialList(playerGuid, SOCIAL_FLAG_ALL);
+ sSocialMgr->SendFriendStatus(pFriend, FRIEND_REMOVED, playerGuid, false);
+ }
+ } while (resultFriends->NextRow());
+ }
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_ACCOUNT_DATA);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_DECLINED_NAME);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_AURA);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_GIFT);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_HOMEBIND);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_REWARDED);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_REPUTATION);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_COOLDOWN);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ if (sWorld->getBoolConfig(CONFIG_DELETE_CHARACTER_TICKET_TRACE))
+ {
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_PLAYER_GM_TICKETS_ON_CHAR_DELETION);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+ }
+ else
+ {
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_GM_TICKETS);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+ }
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_BY_OWNER);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SOCIAL_BY_FRIEND);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SOCIAL_BY_GUID);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEMS);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_OWNER);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENTS);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_EQUIPMENTSETS);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_EVENTLOG_BY_PLAYER);
+ stmt->SetData(0, lowGuid);
+ stmt->SetData(1, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_EVENTLOG_BY_PLAYER);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_ENTRY_POINT);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_GLYPHS);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_STATUS_DAILY_CHAR);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_STATUS_WEEKLY_CHAR);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_STATUS_MONTHLY_CHAR);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_STATUS_SEASONAL_CHAR);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SKILLS);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SETTINGS);
+ stmt->SetData(0, lowGuid);
+ trans->Append(stmt);
+
+ Corpse::DeleteFromDB(playerGuid, trans);
+
+ //npcbot - erase npcbots
+ uint32 newOwner = 0;
+ BotDataMgr::UpdateNpcBotDataAll(lowGuid, NPCBOT_UPDATE_OWNER, &newOwner);
+ //end npcbot
+
+ sScriptMgr->OnDeleteFromDB(trans, lowGuid);
+
+ CharacterDatabase.CommitTransaction(trans);
+ break;
+ }
+ // The character gets unlinked from the account, the name gets freed up and appears as deleted ingame
+ case CHAR_DELETE_UNLINK:
+ {
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_DELETE_INFO);
+
+ stmt->SetData(0, lowGuid);
+
+ CharacterDatabase.Execute(stmt);
+ break;
+ }
+ default:
+ LOG_ERROR("entities.player", "Player::DeleteFromDB: Unsupported delete method: {}.", charDelete_method);
+ return;
+ }
+
+ if (CharacterCacheEntry const* cache = sCharacterCache->GetCharacterCacheByGuid(playerGuid))
+ {
+ std::string name = cache->Name;
+ sCharacterCache->DeleteCharacterCacheEntry(playerGuid, name);
+ }
+
+ 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.
+ */
+void Player::DeleteOldCharacters()
+{
+ uint32 keepDays = sWorld->getIntConfig(CONFIG_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.
+ */
+void Player::DeleteOldCharacters(uint32 keepDays)
+{
+ LOG_INFO("server.loading", "Player::DeleteOldChars: Deleting all characters which have been deleted {} days before...", keepDays);
+ LOG_INFO("server.loading", " ");
+
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_OLD_CHARS);
+ stmt->SetData(0, uint32(GameTime::GetGameTime().count() - time_t(keepDays * DAY)));
+ PreparedQueryResult result = CharacterDatabase.Query(stmt);
+
+ if (result)
+ {
+ LOG_INFO("server.loading", "Player::DeleteOldChars: Found {} character(s) to delete", result->GetRowCount());
+ do
+ {
+ Field* fields = result->Fetch();
+ Player::DeleteFromDB(fields[0].Get(), fields[1].Get(), true, true);
+ } while (result->NextRow());
+ }
+}
+
+void Player::SetMovement(PlayerMovementType pType)
+{
+ WorldPacket data;
+ switch (pType)
+ {
+ case MOVE_ROOT:
+ data.Initialize(SMSG_FORCE_MOVE_ROOT, GetPackGUID().size() + 4);
+ break;
+ case MOVE_UNROOT:
+ data.Initialize(SMSG_FORCE_MOVE_UNROOT, GetPackGUID().size() + 4);
+ break;
+ case MOVE_WATER_WALK:
+ data.Initialize(SMSG_MOVE_WATER_WALK, GetPackGUID().size() + 4);
+ break;
+ case MOVE_LAND_WALK:
+ data.Initialize(SMSG_MOVE_LAND_WALK, GetPackGUID().size() + 4);
+ break;
+ default:
+ LOG_ERROR("entities.player", "Player::SetMovement: Unsupported move type ({}), data not sent to client.", pType);
+ return;
+ }
+ data << GetPackGUID();
+ data << uint32(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(true) == RACE_NIGHTELF)
+ {
+ CastSpell(this, 20584, true);
+ }
+ CastSpell(this, 8326, true);
+
+ // there must be SMSG.FORCE_RUN_SPEED_CHANGE, SMSG.FORCE_SWIM_SPEED_CHANGE, SMSG.MOVE_WATER_WALK
+ // there must be SMSG.STOP_MIRROR_TIMER
+
+ // the player cannot have a corpse already on current map, only bones which are not returned by GetCorpse
+ WorldLocation corpseLocation = GetCorpseLocation();
+ if (GetCorpse() && corpseLocation.GetMapId() == GetMapId())
+ {
+ LOG_ERROR("entities.player", "BuildPlayerRepop: player {} ({}) already has a corpse", GetName(), GetGUID().ToString());
+ return;
+ }
+
+ // create a corpse and place it at the player's location
+ Corpse* corpse = CreateCorpse();
+ if (!corpse)
+ {
+ LOG_ERROR("entities.player", "Error creating corpse for Player {} [{}]", GetName(), GetGUID().ToString());
+ return;
+ }
+ GetMap()->AddToMap(corpse);
+ SetHealth(1); // convert player body to ghost
+ SetMovement(MOVE_WATER_WALK);
+ SetWaterWalking(true);
+ if (!GetSession()->isLogingOut())
+ {
+ SetMovement(MOVE_UNROOT);
+ }
+ RemoveUnitFlag(UNIT_FLAG_SKINNABLE); // BG - remove insignia related
+ int32 corpseReclaimDelay = CalculateCorpseReclaimDelay();
+ if (corpseReclaimDelay >= 0)
+ {
+ SendCorpseReclaimDelay(corpseReclaimDelay);
+ }
+ corpse->ResetGhostTime(); // to prevent cheating
+ StopMirrorTimers(); // disable timers on bars
+ SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, UNIT_BYTE1_FLAG_ALWAYS_STAND); // set and clear other
+ sScriptMgr->OnPlayerReleasedGhost(this);
+}
+
+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, UNIT_BYTES_1_OFFSET_ANIM_TIER, UNIT_BYTE1_FLAG_GROUND);
+ RemoveAurasDueToSpell(20584); // speed bonuses
+ RemoveAurasDueToSpell(8326); // SPELL_AURA_GHOST
+
+ if (GetSession()->IsARecruiter() || (GetSession()->GetRecruiterId() != 0))
+ SetDynamicFlag(UNIT_DYNFLAG_REFER_A_FRIEND);
+
+ setDeathState(ALIVE);
+ SetMovement(MOVE_LAND_WALK);
+ SetMovement(MOVE_UNROOT);
+ SetWaterWalking(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);
+ sOutdoorPvPMgr->HandlePlayerResurrects(this, newzone);
+
+ if (Battleground* bg = GetBattleground())
+ bg->HandlePlayerResurrect(this);
+
+ // update visibility
+ UpdateObjectVisibility();
+
+ sScriptMgr->OnPlayerResurrect(this, restore_percent, applySickness);
+
+ 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->getIntConfig(CONFIG_DEATH_SICKNESS_LEVEL);
+
+ if (int32(GetLevel()) >= startLevel)
+ {
+ // set resurrection sickness
+ CastSpell(this, 15007, true);
+
+ // not full duration
+ if (int32(GetLevel()) < startLevel + 9)
+ {
+ int32 delta = (int32(GetLevel()) - startLevel + 1) * MINUTE;
+
+ if (Aura* aur = GetAura(15007, GetGUID()))
+ {
+ aur->SetDuration(delta * IN_MILLISECONDS);
+ }
+ }
+ }
+}
+
+void Player::KillPlayer()
+{
+ if (IsFlying() && !GetTransport())
+ GetMotionMaster()->MoveFall();
+
+ SetMovement(MOVE_ROOT);
+
+ StopMirrorTimers(); //disable timers(bars)
+
+ setDeathState(CORPSE);
+ //SetUnitFlag(UNIT_FLAG_NOT_IN_PVP);
+
+ ReplaceAllDynamicFlags(UNIT_DYNFLAG_NONE);
+ ApplyModFlag(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTE_RELEASE_TIMER, !sMapStore.LookupEntry(GetMapId())->Instanceable() && !HasAuraType(SPELL_AURA_PREVENT_RESURRECTION));
+
+ // 6 minutes until repop at graveyard
+ m_deathTimer = 6 * MINUTE * IN_MILLISECONDS;
+
+ UpdateCorpseReclaimDelay(); // dependent at use SetDeathPvP() call before kill
+
+ int32 corpseReclaimDelay = CalculateCorpseReclaimDelay();
+
+ if (corpseReclaimDelay >= 0)
+ SendCorpseReclaimDelay(corpseReclaimDelay);
+
+ // don't create corpse at this moment, player might be falling
+
+ // update visibility
+ //UpdateObjectVisibility(); // pussywizard: not needed
+}
+
+void Player::OfflineResurrect(ObjectGuid const guid, CharacterDatabaseTransaction trans)
+{
+ Corpse::DeleteFromDB(guid, trans);
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG);
+ stmt->SetData(0, uint16(AT_LOGIN_RESURRECT));
+ stmt->SetData(1, guid.GetCounter());
+ CharacterDatabase.ExecuteOrAppend(trans, stmt);
+}
+
+Corpse* Player::CreateCorpse()
+{
+ // prevent existence 2 corpse for player
+ SpawnCorpseBones();
+
+ uint32 _uf, _pb, _pb2, _cfb1, _cfb2;
+
+ Corpse* corpse = new Corpse((m_ExtraFlags & PLAYER_EXTRA_PVP_DEATH) ? CORPSE_RESURRECTABLE_PVP : CORPSE_RESURRECTABLE_PVE);
+ SetPvPDeath(false);
+
+ if (!corpse->Create(GetMap()->GenerateLowGuid(), this))
+ {
+ delete corpse;
+ return nullptr;
+ }
+
+ _corpseLocation.WorldRelocate(*this);
+
+ _uf = getRace();
+ _pb = GetUInt32Value(PLAYER_BYTES);
+ _pb2 = GetUInt32Value(PLAYER_BYTES_2);
+
+ uint8 race = (uint8)(_uf);
+ uint8 skin = (uint8)(_pb);
+ uint8 face = (uint8)(_pb >> 8);
+ uint8 hairstyle = (uint8)(_pb >> 16);
+ uint8 haircolor = (uint8)(_pb >> 24);
+ uint8 facialhair = (uint8)(_pb2);
+
+ _cfb1 = ((0x00) | (race << 8) | (GetByteValue(PLAYER_BYTES_3, 0) << 16) | (skin << 24));
+ _cfb2 = ((face) | (hairstyle << 8) | (haircolor << 16) | (facialhair << 24));
+
+ corpse->SetUInt32Value(CORPSE_FIELD_BYTES_1, _cfb1);
+ corpse->SetUInt32Value(CORPSE_FIELD_BYTES_2, _cfb2);
+
+ uint32 flags = CORPSE_FLAG_UNK2;
+ if (HasPlayerFlag(PLAYER_FLAGS_HIDE_HELM))
+ flags |= CORPSE_FLAG_HIDE_HELM;
+ if (HasPlayerFlag(PLAYER_FLAGS_HIDE_CLOAK))
+ flags |= CORPSE_FLAG_HIDE_CLOAK;
+
+ // Xinef: Player can loop corpses while in BG or in WG
+ if (InBattleground() && !InArena())
+ flags |= CORPSE_FLAG_LOOTABLE;
+ Battlefield* Bf = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG);
+ if (Bf && Bf->IsWarTime())
+ flags |= CORPSE_FLAG_LOOTABLE;
+
+ corpse->SetUInt32Value(CORPSE_FIELD_FLAGS, flags);
+
+ corpse->SetUInt32Value(CORPSE_FIELD_DISPLAY_ID, GetNativeDisplayId());
+
+ corpse->SetUInt32Value(CORPSE_FIELD_GUILD, GetGuildId());
+
+ uint32 iDisplayID;
+ uint32 iIventoryType;
+ uint32 _cfi;
+ for (uint8 i = 0; i < EQUIPMENT_SLOT_END; i++)
+ {
+ if (m_items[i])
+ {
+ iDisplayID = m_items[i]->GetTemplate()->DisplayInfoID;
+ iIventoryType = m_items[i]->GetTemplate()->InventoryType;
+
+ _cfi = iDisplayID | (iIventoryType << 24);
+ corpse->SetUInt32Value(CORPSE_FIELD_ITEM + i, _cfi);
+ }
+ }
+
+ // register for player, but not show
+ GetMap()->AddCorpse(corpse);
+
+ UpdatePositionData();
+
+ // we do not need to save corpses for BG/arenas
+ if (!GetMap()->IsBattlegroundOrArena())
+ corpse->SaveToDB();
+
+ return corpse;
+}
+
+void Player::RemoveCorpse()
+{
+ if (GetCorpse())
+ {
+ GetCorpse()->RemoveFromWorld();
+ }
+
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+ Corpse::DeleteFromDB(GetGUID(), trans);
+ CharacterDatabase.CommitTransaction(trans);
+
+ _corpseLocation.WorldRelocate();
+}
+
+void Player::SpawnCorpseBones(bool triggerSave /*= true*/)
+{
+ _corpseLocation.WorldRelocate();
+ if (GetMap()->ConvertCorpseToBones(GetGUID()))
+ if (triggerSave && !GetSession()->PlayerLogoutWithSave()) // at logout we will already store the player
+ {
+ // prevent loading as ghost without corpse
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+
+ // pussywizard: update only ghost flag instead of whole character table entry! data integrity is crucial
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_REMOVE_GHOST);
+ stmt->SetData(0, GetGUID().GetCounter());
+ trans->Append(stmt);
+
+ _SaveAuras(trans, false);
+
+ CharacterDatabase.CommitTransaction(trans);
+ }
+}
+
+Corpse* Player::GetCorpse() const
+{
+ return GetMap()->GetCorpseByPlayer(GetGUID());
+}
+
+void Player::SendDurabilityLoss()
+{
+ SendDirectMessage(WorldPackets::Misc::DurabilityDamageDeath().Write());
+}
+
+void Player::DurabilityLossAll(double percent, bool inventory)
+{
+ for (uint8 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 (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; i++)
+ if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
+ DurabilityLoss(pItem, percent);
+
+ // keys not have durability
+ //for (int i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; i++)
+
+ for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++)
+ if (Bag* pBag = GetBagByPos(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 (uint8 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 (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; i++)
+ if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
+ DurabilityPointsLoss(pItem, points);
+
+ // keys not have durability
+ //for (int i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; i++)
+
+ for (uint8 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)
+{
+ if (HasAuraType(SPELL_AURA_PREVENT_DURABILITY_LOSS))
+ {
+ return;
+ }
+
+ 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 (uint8 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 (uint8 j = INVENTORY_SLOT_BAG_START; j < INVENTORY_SLOT_BAG_END; j++)
+ for (uint8 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)
+ {
+ ItemTemplate const* ditemProto = item->GetTemplate();
+
+ DurabilityCostsEntry const* dcost = sDurabilityCostsStore.LookupEntry(ditemProto->ItemLevel);
+ if (!dcost)
+ {
+ LOG_ERROR("entities.player", "RepairDurability: Wrong item lvl {}", ditemProto->ItemLevel);
+ return TotalCost;
+ }
+
+ uint32 dQualitymodEntryId = (ditemProto->Quality + 1) * 2;
+ DurabilityQualityEntry const* dQualitymodEntry = sDurabilityQualityStore.LookupEntry(dQualitymodEntryId);
+ if (!dQualitymodEntry)
+ {
+ LOG_ERROR("entities.player", "RepairDurability: Wrong dQualityModEntry {}", 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 * sWorld->getRate(RATE_REPAIRCOST));
+
+ if (costs == 0) //fix for ITEM_QUALITY_ARTIFACT
+ costs = 1;
+
+ if (guildBank)
+ {
+ if (GetGuildId() == 0)
+ {
+ // LOG_DEBUG("entities.player", "You are not member of a guild");
+ return TotalCost;
+ }
+
+ Guild* guild = sGuildMgr->GetGuildById(GetGuildId());
+ if (!guild)
+ return TotalCost;
+
+ if (!guild->HandleMemberWithdrawMoney(GetSession(), costs, true))
+ return TotalCost;
+
+ TotalCost = costs;
+ }
+ else if (!HasEnoughMoney(costs))
+ {
+ // LOG_DEBUG("entities.player", "You do not have enough money");
+ return TotalCost;
+ }
+ else
+ ModifyMoney(-int32(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 = sAreaTableStore.LookupEntry(GetAreaId());
+
+ if (!sScriptMgr->CanRepopAtGraveyard(this))
+ return;
+
+ // Such zones are considered unreachable as a ghost and the player must be automatically revived
+ // Xinef: Get Transport Check is not needed
+ if ((!IsAlive() && zone && zone->flags & AREA_FLAG_NEED_FLY) /*|| GetTransport()*/ || GetPositionZ() < GetMap()->GetMinHeight(GetPositionX(), GetPositionY()))
+ {
+ ResurrectPlayer(0.5f);
+ SpawnCorpseBones();
+ }
+
+ GraveyardStruct const* ClosestGrave = nullptr;
+
+ // Special handle for battleground maps
+ if (Battleground* bg = GetBattleground())
+ ClosestGrave = bg->GetClosestGraveyard(this);
+ else
+ {
+ if (sBattlefieldMgr->GetBattlefieldToZoneId(GetZoneId()))
+ ClosestGrave = sBattlefieldMgr->GetBattlefieldToZoneId(GetZoneId())->GetClosestGraveyard(this);
+ else
+ ClosestGrave = sGraveyard->GetClosestGraveyard(this, GetTeamId());
+ }
+
+ // 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)
+ {
+ TeleportTo(ClosestGrave->Map, 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;
+ data << ClosestGrave->x;
+ data << ClosestGrave->y;
+ data << ClosestGrave->z;
+ GetSession()->SendPacket(&data);
+ }
+ }
+ else if (GetPositionZ() < GetMap()->GetMinHeight(GetPositionX(), GetPositionY()))
+ TeleportTo(m_homebindMapId, m_homebindX, m_homebindY, m_homebindZ, m_homebindO);
+
+ RemovePlayerFlag(PLAYER_FLAGS_IS_OUT_OF_BOUNDS);
+}
+
+bool Player::CanJoinConstantChannelInZone(ChatChannelsEntry const* channel, AreaTableEntry const* zone)
+{
+ // Player can join LFG anywhere
+ if (channel->flags & CHANNEL_DBC_FLAG_LFG && sWorld->getBoolConfig(CONFIG_LFG_LOCATION_ALL))
+ return true;
+
+ if (channel->flags & CHANNEL_DBC_FLAG_ZONE_DEP && zone->flags & AREA_FLAG_ARENA_INSTANCE)
+ return false;
+
+ if ((channel->flags & CHANNEL_DBC_FLAG_CITY_ONLY) && (!(zone->flags & AREA_FLAG_SLAVE_CAPITAL)))
+ return false;
+
+ if ((channel->flags & CHANNEL_DBC_FLAG_GUILD_REQ) && GetGuildId())
+ return false;
+
+ return true;
+}
+
+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->LeaveChannel(this, false); // not send to client, not remove from player's channel list
+ }
+}
+
+void Player::ClearChannelWatch()
+{
+ for (JoinedChannelsList::iterator itr = m_channels.begin(); itr != m_channels.end(); ++itr)
+ (*itr)->RemoveWatching(this);
+}
+
+void Player::HandleBaseModValue(BaseModGroup modGroup, BaseModType modType, float amount, bool apply)
+{
+ if (modGroup >= BASEMOD_END)
+ {
+ LOG_ERROR("entities.player", "ERROR in HandleBaseModValue(): non existed BaseModGroup!");
+ return;
+ }
+
+ switch (modType)
+ {
+ case FLAT_MOD:
+ m_auraBaseMod[modGroup][modType] += apply ? amount : -amount;
+ break;
+ case PCT_MOD:
+ ApplyPercentModFloatVar(m_auraBaseMod[modGroup][modType], amount, apply);
+ 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_VALUE:
+ UpdateShieldBlockValue();
+ break;
+ default:
+ break;
+ }
+}
+
+float Player::GetBaseModValue(BaseModGroup modGroup, BaseModType modType) const
+{
+ if (modGroup >= BASEMOD_END)
+ {
+ LOG_ERROR("entities.player", "trial to access non existed BaseModGroup!");
+ 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)
+ {
+ LOG_ERROR("entities.player", "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::GetShieldBlockValue() const
+{
+ float value = (m_auraBaseMod[SHIELD_BLOCK_VALUE][FLAT_MOD] + GetStat(STAT_STRENGTH) * 0.5f - 10) * m_auraBaseMod[SHIELD_BLOCK_VALUE][PCT_MOD];
+
+ value = (value < 0) ? 0 : value;
+
+ return uint32(value);
+}
+
+float Player::GetMeleeCritFromAgility()
+{
+ uint8 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 || !critRatio)
+ return 0.0f;
+
+ float crit = critBase->base + GetStat(STAT_AGILITY) * critRatio->ratio;
+ return crit * 100.0f;
+}
+
+void Player::GetDodgeFromAgility(float& diminishing, float& nondiminishing)
+{
+ // Table for base dodge values
+ const float dodge_base[MAX_CLASSES] =
+ {
+ 0.036640f, // Warrior
+ 0.034943f, // Paladi
+ -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
+ };
+
+ uint8 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 || 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 + static_cast(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]);
+}
+
+float Player::GetSpellCritFromIntellect()
+{
+ uint8 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 || !critRatio)
+ return 0.0f;
+
+ float crit = critBase->base + GetStat(STAT_INTELLECT) * critRatio->ratio;
+ return crit * 100.0f;
+}
+
+float Player::GetRatingMultiplier(CombatRating cr) const
+{
+ uint8 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(static_cast(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::OCTRegenHPPerSpirit()
+{
+ uint8 level = GetLevel();
+ uint32 pclass = getClass();
+
+ if (level > GT_MAX_LEVEL)
+ level = GT_MAX_LEVEL;
+
+ GtOCTRegenHPEntry const* baseRatio = sGtOCTRegenHPStore.LookupEntry((pclass - 1) * GT_MAX_LEVEL + level - 1);
+ GtRegenHPPerSptEntry const* moreRatio = sGtRegenHPPerSptStore.LookupEntry((pclass - 1) * GT_MAX_LEVEL + level - 1);
+ if (!baseRatio || !moreRatio)
+ return 0.0f;
+
+ // Formula from PaperDollFrame script
+ float spirit = GetStat(STAT_SPIRIT);
+ float baseSpirit = spirit;
+ if (baseSpirit > 50)
+ baseSpirit = 50;
+ float moreSpirit = spirit - baseSpirit;
+ float regen = baseSpirit * baseRatio->ratio + moreSpirit * moreRatio->ratio;
+ return regen;
+}
+
+float Player::OCTRegenMPPerSpirit()
+{
+ uint8 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)
+ 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)
+{
+ float oldRating = m_baseRatingValue[cr];
+ m_baseRatingValue[cr] += (apply ? value : -value);
+ // explicit affected values
+ if (cr == CR_HASTE_MELEE || cr == CR_HASTE_RANGED || cr == CR_HASTE_SPELL)
+ {
+ float const mult = GetRatingMultiplier(cr);
+ float const oldVal = oldRating * mult;
+ float const newVal = m_baseRatingValue[cr] * mult;
+ switch (cr)
+ {
+ case CR_HASTE_MELEE:
+ ApplyAttackTimePercentMod(BASE_ATTACK, oldVal, false);
+ ApplyAttackTimePercentMod(OFF_ATTACK, oldVal, false);
+ ApplyAttackTimePercentMod(BASE_ATTACK, newVal, true);
+ ApplyAttackTimePercentMod(OFF_ATTACK, newVal, true);
+ break;
+ case CR_HASTE_RANGED:
+ ApplyAttackTimePercentMod(RANGED_ATTACK, oldVal, false);
+ ApplyAttackTimePercentMod(RANGED_ATTACK, newVal, true);
+ break;
+ case CR_HASTE_SPELL:
+ ApplyCastTimePercentMod(oldVal, false);
+ ApplyCastTimePercentMod(newVal, true);
+ break;
+ default:
+ break;
+ }
+ }
+
+ UpdateRating(cr);
+}
+
+void Player::SetRegularAttackTime()
+{
+ for (uint8 i = 0; i < MAX_ATTACK; ++i)
+ {
+ Item* tmpitem = GetWeaponForAttack(WeaponAttackType(i), true);
+ if (tmpitem && !tmpitem->IsBroken())
+ {
+ ItemTemplate const* proto = tmpitem->GetTemplate();
+ if (proto->Delay)
+ SetAttackTime(WeaponAttackType(i), proto->Delay);
+ }
+ else
+ SetAttackTime(WeaponAttackType(i), BASE_ATTACK_TIME); // If there is no weapon reset attack time to base (might have been changed from forms)
+ }
+}
+
+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;
+
+ uint32 bonusIndex = PLAYER_SKILL_BONUS_INDEX(itr->second.pos);
+
+ uint32 bonus_val = GetUInt32Value(bonusIndex);
+ int16 temp_bonus = SKILL_TEMP_BONUS(bonus_val);
+ int16 perm_bonus = SKILL_PERM_BONUS(bonus_val);
+
+ if (talent) // permanent bonus stored in high part
+ SetUInt32Value(bonusIndex, MAKE_SKILL_BONUS(temp_bonus, perm_bonus + val));
+ else // temporary/item bonus stored in low part
+ SetUInt32Value(bonusIndex, MAKE_SKILL_BONUS(temp_bonus + val, perm_bonus));
+}
+
+// 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 step, uint16 newVal, uint16 maxVal)
+{
+ if (!id)
+ return;
+
+ uint16 currVal;
+ SkillStatusMap::iterator itr = mSkillStatus.find(id);
+
+ //has skill
+ if (itr != mSkillStatus.end() && itr->second.uState != SKILL_DELETED)
+ {
+ currVal = SKILL_VALUE(GetUInt32Value(PLAYER_SKILL_VALUE_INDEX(itr->second.pos)));
+ if (newVal)
+ {
+ // if skill value is going down, update enchantments before setting the new value
+ if (newVal < currVal)
+ UpdateSkillEnchantments(id, currVal, newVal);
+ // update step
+ SetUInt32Value(PLAYER_SKILL_INDEX(itr->second.pos), MAKE_PAIR32(id, step));
+ // update value
+ SetUInt32Value(PLAYER_SKILL_VALUE_INDEX(itr->second.pos), MAKE_SKILL_VALUE(newVal, maxVal));
+ if (itr->second.uState != SKILL_NEW)
+ itr->second.uState = SKILL_CHANGED;
+ learnSkillRewardedSpells(id, newVal);
+ // if skill value is going up, update enchantments after setting the new value
+ if (newVal > currVal)
+ UpdateSkillEnchantments(id, currVal, newVal);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, id);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL, id);
+ }
+ else //remove
+ {
+ //remove enchantments needing this skill
+ UpdateSkillEnchantments(id, currVal, 0);
+ // clear skill fields
+ SetUInt32Value(PLAYER_SKILL_INDEX(itr->second.pos), 0);
+ SetUInt32Value(PLAYER_SKILL_VALUE_INDEX(itr->second.pos), 0);
+ SetUInt32Value(PLAYER_SKILL_BONUS_INDEX(itr->second.pos), 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->SkillLine == id)
+ removeSpell(sSpellMgr->GetFirstSpellInChain(pAbility->Spell), SPEC_MASK_ALL, false);
+ }
+ }
+ else if (newVal) //add
+ {
+ currVal = 0;
+ for (int i = 0; i < PLAYER_MAX_SKILLS; ++i)
+ if (!GetUInt32Value(PLAYER_SKILL_INDEX(i)))
+ {
+ SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(id);
+ if (!pSkill)
+ {
+ LOG_ERROR("entities.player", "Skill not found in SkillLineStore: skill #{}", id);
+ return;
+ }
+
+ SetUInt32Value(PLAYER_SKILL_INDEX(i), MAKE_PAIR32(id, step));
+ SetUInt32Value(PLAYER_SKILL_VALUE_INDEX(i), MAKE_SKILL_VALUE(newVal, maxVal));
+ UpdateSkillEnchantments(id, currVal, newVal);
+
+ // 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
+ SetUInt32Value(PLAYER_SKILL_BONUS_INDEX(i), 0);
+
+ // temporary bonuses
+ AuraEffectList const& mModSkill = GetAuraEffectsByType(SPELL_AURA_MOD_SKILL);
+ for (AuraEffectList::const_iterator j = mModSkill.begin(); j != mModSkill.end(); ++j)
+ if ((*j)->GetMiscValue() == int32(id))
+ (*j)->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true);
+
+ // permanent bonuses
+ AuraEffectList const& mModSkillTalent = GetAuraEffectsByType(SPELL_AURA_MOD_SKILL_TALENT);
+ for (AuraEffectList::const_iterator j = mModSkillTalent.begin(); j != mModSkillTalent.end(); ++j)
+ if ((*j)->GetMiscValue() == int32(id))
+ (*j)->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true);
+
+ // Learn all spells for skill
+ learnSkillRewardedSpells(id, newVal);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, id);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL, id);
+ 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 PAIR32_HIPART(GetUInt32Value(PLAYER_SKILL_INDEX(itr->second.pos)));
+}
+
+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;
+
+ uint32 bonus = GetUInt32Value(PLAYER_SKILL_BONUS_INDEX(itr->second.pos));
+
+ int32 result = int32(SKILL_VALUE(GetUInt32Value(PLAYER_SKILL_VALUE_INDEX(itr->second.pos))));
+ result += SKILL_TEMP_BONUS(bonus);
+ result += SKILL_PERM_BONUS(bonus);
+ 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;
+
+ uint32 bonus = GetUInt32Value(PLAYER_SKILL_BONUS_INDEX(itr->second.pos));
+
+ int32 result = int32(SKILL_MAX(GetUInt32Value(PLAYER_SKILL_VALUE_INDEX(itr->second.pos))));
+ sScriptMgr->OnGetMaxSkillValue(const_cast(this), skill, result, false);
+ result += SKILL_TEMP_BONUS(bonus);
+ result += SKILL_PERM_BONUS(bonus);
+ 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;
+
+ int32 result = int32(SKILL_MAX(GetUInt32Value(PLAYER_SKILL_VALUE_INDEX(itr->second.pos))));
+
+ sScriptMgr->OnGetMaxSkillValue(const_cast(this), skill, result, true);
+
+ return result < 0 ? 0 : result;
+}
+
+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;
+
+ int32 result = int32(SKILL_VALUE(GetUInt32Value(PLAYER_SKILL_VALUE_INDEX(itr->second.pos))));
+ result += SKILL_PERM_BONUS(GetUInt32Value(PLAYER_SKILL_BONUS_INDEX(itr->second.pos)));
+ 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;
+
+ return SKILL_VALUE(GetUInt32Value(PLAYER_SKILL_VALUE_INDEX(itr->second.pos)));
+}
+
+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;
+
+ return SKILL_PERM_BONUS(GetUInt32Value(PLAYER_SKILL_BONUS_INDEX(itr->second.pos)));
+}
+
+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;
+
+ return SKILL_TEMP_BONUS(GetUInt32Value(PLAYER_SKILL_BONUS_INDEX(itr->second.pos)));
+}
+
+void Player::SendActionButtons(uint32 state) const
+{
+ LOG_DEBUG("entities.player", "Sending Action Buttons for {} spec {}", GetGUID().ToString(), m_activeSpec);
+
+ WorldPacket data(SMSG_ACTION_BUTTONS, 1 + (MAX_ACTION_BUTTONS * 4));
+ data << uint8(state);
+ /*
+ state can be 0, 1, 2
+ 0 - Looks to be sent when initial action buttons get sent, however on Trinity we use 1 since 0 had some difficulties
+ 1 - Used in any SMSG_ACTION_BUTTONS packet with button data on Trinity. Only used after spec swaps on retail.
+ 2 - Clears the action bars client sided. This is sent during spec swap before unlearning and before sending the new buttons
+ */
+ if (state != 2)
+ {
+ for (uint8 button = 0; button < MAX_ACTION_BUTTONS; ++button)
+ {
+ ActionButtonList::const_iterator itr = m_actionButtons.find(button);
+ if (itr != m_actionButtons.end() && itr->second.uState != ACTIONBUTTON_DELETED)
+ data << uint32(itr->second.packedData);
+ else
+ data << uint32(0);
+ }
+ }
+
+ GetSession()->SendPacket(&data);
+ LOG_DEBUG("entities.player", "Action Buttons for {} spec {} Sent", GetGUID().ToString(), m_activeSpec);
+}
+
+bool Player::IsActionButtonDataValid(uint8 button, uint32 action, uint8 type)
+{
+ if (button >= MAX_ACTION_BUTTONS)
+ {
+ LOG_ERROR("entities.player", "Action {} not added into button {} for player {}: button must be < {}", action, button, GetName(), MAX_ACTION_BUTTONS);
+ return false;
+ }
+
+ if (action >= MAX_ACTION_BUTTON_ACTION_VALUE)
+ {
+ LOG_ERROR("entities.player", "Action {} not added into button {} for player {}: action must be < {}", action, button, GetName(), MAX_ACTION_BUTTON_ACTION_VALUE);
+ return false;
+ }
+
+ switch (type)
+ {
+ case ACTION_BUTTON_SPELL:
+ if (!sSpellMgr->GetSpellInfo(action))
+ {
+ LOG_ERROR("entities.player", "Spell action {} not added into button {} for player {}: spell not exist", action, button, GetName());
+ return false;
+ }
+
+ if (!HasSpell(action))
+ {
+ LOG_DEBUG("entities.player.loading", "Player::IsActionButtonDataValid Spell action {} not added into button {} for player {}: player don't known this spell", action, button, GetName());
+ return false;
+ }
+ break;
+ case ACTION_BUTTON_ITEM:
+ if (!sObjectMgr->GetItemTemplate(action))
+ {
+ LOG_ERROR("entities.player", "Item action {} not added into button {} for player {}: item not exist", action, button, GetName());
+ return false;
+ }
+ break;
+ default:
+ break; // other cases not checked at this moment
+ }
+
+ return true;
+}
+
+ActionButton* Player::addActionButton(uint8 button, uint32 action, uint8 type)
+{
+ if (!IsActionButtonDataValid(button, action, type))
+ return nullptr;
+
+ // it create new button (NEW state) if need or return existed
+ ActionButton& ab = m_actionButtons[button];
+
+ // set data and update to CHANGED if not NEW
+ ab.SetActionAndType(action, ActionButtonType(type));
+
+ LOG_DEBUG("entities.player", "Player {} Added Action {} (type {}) to Button {}", GetGUID().ToString(), action, type, button);
+ return &ab;
+}
+
+void Player::removeActionButton(uint8 button)
+{
+ ActionButtonList::iterator buttonItr = m_actionButtons.find(button);
+ if (buttonItr == m_actionButtons.end() || buttonItr->second.uState == ACTIONBUTTON_DELETED)
+ return;
+
+ if (buttonItr->second.uState == ACTIONBUTTON_NEW)
+ m_actionButtons.erase(buttonItr); // new and not saved
+ else
+ buttonItr->second.uState = ACTIONBUTTON_DELETED; // saved, will deleted at next save
+
+ LOG_DEBUG("entities.player", "Action Button {} Removed from Player {}", button, GetGUID().ToString());
+}
+
+ActionButton const* Player::GetActionButton(uint8 button)
+{
+ ActionButtonList::iterator buttonItr = m_actionButtons.find(button);
+ if (buttonItr == m_actionButtons.end() || buttonItr->second.uState == ACTIONBUTTON_DELETED)
+ return nullptr;
+
+ return &buttonItr->second;
+}
+
+void Player::SaveRecallPosition()
+{
+ m_recallMap = GetMapId();
+ m_recallX = GetPositionX();
+ m_recallY = GetPositionY();
+ m_recallZ = GetPositionZ();
+ m_recallO = GetOrientation();
+}
+
+void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self, bool includeMargin, Player const* skipped_rcvr) const
+{
+ if (self)
+ GetSession()->SendPacket(data);
+
+ dist += GetObjectSize();
+ if (includeMargin)
+ dist += VISIBILITY_COMPENSATION; // pussywizard: to ensure everyone receives all important packets
+ Acore::MessageDistDeliverer notifier(this, data, dist, false, skipped_rcvr);
+ Cell::VisitWorldObjects(this, notifier, dist);
+}
+
+void Player::SendMessageToSetInRange_OwnTeam(WorldPacket const* data, float dist, bool self) const
+{
+ if (self)
+ GetSession()->SendPacket(data);
+
+ Acore::MessageDistDeliverer notifier(this, data, dist, true);
+ Cell::VisitWorldObjects(this, notifier, dist);
+}
+
+void Player::SendDirectMessage(WorldPacket const* data) const
+{
+ m_session->SendPacket(data);
+}
+
+void Player::SendCinematicStart(uint32 CinematicSequenceId) const
+{
+ WorldPacket data(SMSG_TRIGGER_CINEMATIC, 4);
+ data << uint32(CinematicSequenceId);
+ SendDirectMessage(&data);
+ if (CinematicSequencesEntry const* sequence = sCinematicSequencesStore.LookupEntry(CinematicSequenceId))
+ {
+ _cinematicMgr->SetActiveCinematicCamera(sequence->cinematicCamera);
+ }
+}
+
+void Player::SendMovieStart(uint32 MovieId)
+{
+ WorldPacket data(SMSG_TRIGGER_MOVIE, 4);
+ data << uint32(MovieId);
+ SendDirectMessage(&data);
+}
+
+void Player::CheckAreaExploreAndOutdoor()
+{
+ if (!IsAlive())
+ return;
+
+ if (IsInFlight())
+ return;
+
+ bool isOutdoor = IsOutdoors();
+ uint32 areaId = GetAreaId();
+ AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId);
+
+ if (sWorld->getBoolConfig(CONFIG_VMAP_INDOOR_CHECK) && _wasOutdoor != isOutdoor)
+ {
+ _wasOutdoor = isOutdoor;
+
+ SpellAttr0 attrToRemove = isOutdoor ? SPELL_ATTR0_ONLY_INDOORS : SPELL_ATTR0_ONLY_OUTDOORS;
+ SpellAttr0 attrToRecalculate = isOutdoor ? SPELL_ATTR0_ONLY_OUTDOORS : SPELL_ATTR0_ONLY_INDOORS;
+ for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
+ {
+ Aura* aura = iter->second->GetBase();
+ SpellInfo const* spell = aura->GetSpellInfo();
+ if (spell->Attributes & attrToRemove)
+ {
+ // if passive - do not remove and just turn off all effects
+ if (aura->IsPassive())
+ {
+ aura->HandleAllEffects(iter->second, AURA_EFFECT_HANDLE_REAL, false);
+ ++iter;
+ continue;
+ }
+
+ RemoveAura(iter);
+ }
+ else if ((spell->Attributes & attrToRecalculate) && aura->IsPassive())
+ {
+ // if passive - turn on all effects
+ aura->HandleAllEffects(iter->second, AURA_EFFECT_HANDLE_REAL, true);
+ ++iter;
+ }
+ else
+ {
+ ++iter;
+ }
+ }
+ }
+
+ if (!sScriptMgr->CanAreaExploreAndOutdoor(this))
+ return;
+
+ if (!areaId)
+ return;
+
+ if (!areaEntry)
+ {
+ LOG_ERROR("entities.player", "Player '{}' ({}) discovered unknown area (x: {} y: {} z: {} map: {})",
+ GetName(), GetGUID().ToString(), GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId());
+ return;
+ }
+
+ uint32 offset = areaEntry->exploreFlag / 32;
+
+ if (offset >= PLAYER_EXPLORED_ZONES_SIZE)
+ {
+ LOG_ERROR("entities.player", "Wrong area flag {} in map data for (X: {} Y: {}) point to field PLAYER_EXPLORED_ZONES_1 + {} ( {} must be < {} ).", areaEntry->flags, GetPositionX(), GetPositionY(), offset, offset, PLAYER_EXPLORED_ZONES_SIZE);
+ return;
+ }
+
+ uint32 val = (uint32)(1 << (areaEntry->exploreFlag % 32));
+ uint32 currFields = GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + offset);
+
+ if (!(currFields & val))
+ {
+ SetUInt32Value(PLAYER_EXPLORED_ZONES_1 + offset, (uint32)(currFields | val));
+
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA, areaId);
+
+ if (areaEntry->area_level > 0)
+ {
+ if (GetLevel() >= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
+ {
+ SendExplorationExperience(areaId, 0);
+ }
+ else
+ {
+ int32 diff = int32(GetLevel()) - areaEntry->area_level;
+ uint32 XP = 0;
+ if (diff < -5)
+ {
+ XP = uint32(sObjectMgr->GetBaseXP(GetLevel() + 5) * sWorld->getRate(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(areaEntry->area_level) * exploration_percent / 100 * sWorld->getRate(RATE_XP_EXPLORE));
+ }
+ else
+ {
+ XP = uint32(sObjectMgr->GetBaseXP(areaEntry->area_level) * sWorld->getRate(RATE_XP_EXPLORE));
+ }
+
+ GiveXP(XP, nullptr);
+ SendExplorationExperience(areaId, XP);
+ }
+ LOG_DEBUG("entities.player", "Player {} discovered a new area: {}", GetGUID().ToString(), areaId);
+ }
+ }
+}
+
+TeamId Player::TeamIdForRace(uint8 race)
+{
+ if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race))
+ {
+ switch (rEntry->TeamID)
+ {
+ case 1:
+ return TEAM_HORDE;
+ case 7:
+ return TEAM_ALLIANCE;
+ }
+ LOG_ERROR("entities.player", "Race ({}) has wrong teamid ({}) in DBC: wrong DBC files?", uint32(race), rEntry->TeamID);
+ }
+ else
+ LOG_ERROR("entities.player", "Race ({}) not found in DBC: wrong DBC files?", uint32(race));
+
+ return TEAM_ALLIANCE;
+}
+
+void Player::SetFactionForRace(uint8 race)
+{
+ m_team = TeamIdForRace(race);
+
+ sScriptMgr->OnPlayerUpdateFaction(this);
+
+ if (GetTeamId(true) != GetTeamId())
+ return;
+
+ ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race);
+ SetFaction(rEntry ? rEntry->FactionID : 0);
+}
+
+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
+float Player::CalculateReputationGain(ReputationSource source, uint32 creatureOrQuestLevel, float rep, int32 faction, bool noQuestBonus)
+{
+ float percent = 100.0f;
+
+ float repMod = noQuestBonus ? 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.f ? repMod : -repMod;
+
+ float rate;
+ switch (source)
+ {
+ case REPUTATION_SOURCE_KILL:
+ rate = sWorld->getRate(RATE_REPUTATION_LOWLEVEL_KILL);
+ break;
+ case REPUTATION_SOURCE_QUEST:
+ case REPUTATION_SOURCE_DAILY_QUEST:
+ case REPUTATION_SOURCE_WEEKLY_QUEST:
+ case REPUTATION_SOURCE_MONTHLY_QUEST:
+ case REPUTATION_SOURCE_REPEATABLE_QUEST:
+ rate = sWorld->getRate(RATE_REPUTATION_LOWLEVEL_QUEST);
+ break;
+ case REPUTATION_SOURCE_SPELL:
+ default:
+ rate = 1.0f;
+ break;
+ }
+
+ if (rate != 1.0f && creatureOrQuestLevel <= Acore::XP::GetGrayLevel(GetLevel()))
+ percent *= rate;
+
+ if (percent <= 0.0f)
+ return 0;
+
+ // Multiply result with the faction specific rate
+ if (RepRewardRate const* repData = sObjectMgr->GetRepRewardRate(faction))
+ {
+ float repRate = 0.0f;
+ switch (source)
+ {
+ case REPUTATION_SOURCE_KILL:
+ repRate = repData->creatureRate;
+ break;
+ case REPUTATION_SOURCE_QUEST:
+ repRate = repData->questRate;
+ break;
+ case REPUTATION_SOURCE_DAILY_QUEST:
+ repRate = repData->questDailyRate;
+ break;
+ case REPUTATION_SOURCE_WEEKLY_QUEST:
+ repRate = repData->questWeeklyRate;
+ break;
+ case REPUTATION_SOURCE_MONTHLY_QUEST:
+ repRate = repData->questMonthlyRate;
+ break;
+ case REPUTATION_SOURCE_REPEATABLE_QUEST:
+ repRate = repData->questRepeatableRate;
+ break;
+ case REPUTATION_SOURCE_SPELL:
+ repRate = repData->spellRate;
+ 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;
+ }
+
+ if (source != REPUTATION_SOURCE_SPELL && GetsRecruitAFriendBonus(false))
+ percent *= 1.0f + sWorld->getRate(RATE_REPUTATION_RECRUIT_A_FRIEND_BONUS);
+
+ return CalculatePct(rep, percent);
+}
+
+// Calculates how many reputation points player gains in victim's enemy factions
+void Player::RewardReputation(Unit* victim, float rate)
+{
+ if (!victim || victim->GetTypeId() == TYPEID_PLAYER)
+ return;
+
+ if (victim->ToCreature()->IsReputationGainDisabled())
+ return;
+
+ ReputationOnKillEntry const* Rep = sObjectMgr->GetReputationOnKilEntry(victim->ToCreature()->GetCreatureTemplate()->Entry);
+ if (!Rep)
+ return;
+
+ uint32 ChampioningFaction = 0;
+
+ if (GetChampioningFaction())
+ {
+ // support for: Championing - http://www.wowwiki.com/Championing
+ Map const* map = GetMap();
+ if (map->IsNonRaidDungeon())
+ if (LFGDungeonEntry const* dungeon = GetLFGDungeon(map->GetId(), map->GetDifficulty()))
+ if (dungeon->reclevel == 80)
+ ChampioningFaction = GetChampioningFaction();
+ }
+
+ TeamId teamId = GetTeamId(true); // Always check player original reputation when rewarding
+
+ if (Rep->RepFaction1 && (!Rep->TeamDependent || teamId == TEAM_ALLIANCE))
+ {
+ float donerep1 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->GetLevel(), static_cast(Rep->RepValue1), ChampioningFaction ? ChampioningFaction : Rep->RepFaction1);
+ donerep1 *= rate;
+
+ FactionEntry const* factionEntry1 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : Rep->RepFaction1);
+ if (factionEntry1)
+ {
+ GetReputationMgr().ModifyReputation(factionEntry1, donerep1, false, static_cast(Rep->ReputationMaxCap1));
+ }
+ }
+
+ if (Rep->RepFaction2 && (!Rep->TeamDependent || teamId == TEAM_HORDE))
+ {
+ float donerep2 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->GetLevel(), static_cast(Rep->RepValue2), ChampioningFaction ? ChampioningFaction : Rep->RepFaction2);
+ donerep2 *= rate;
+
+ FactionEntry const* factionEntry2 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : Rep->RepFaction2);
+ if (factionEntry2)
+ {
+ GetReputationMgr().ModifyReputation(factionEntry2, donerep2, false, static_cast(Rep->ReputationMaxCap2));
+ }
+ }
+}
+
+// Calculate how many reputation points player gain with the quest
+void Player::RewardReputation(Quest const* quest)
+{
+ for (uint8 i = 0; i < QUEST_REPUTATIONS_COUNT; ++i)
+ {
+ if (!quest->RewardFactionId[i])
+ continue;
+
+ float rep = 0.f;
+
+ if (quest->RewardFactionValueIdOverride[i])
+ {
+ rep = quest->RewardFactionValueIdOverride[i] / 100.f;
+ }
+ else
+ {
+ uint32 row = ((quest->RewardFactionValueId[i] < 0) ? 1 : 0) + 1;
+ if (QuestFactionRewEntry const* questFactionRewEntry = sQuestFactionRewardStore.LookupEntry(row))
+ {
+ uint32 field = std::abs(quest->RewardFactionValueId[i]);
+ rep = static_cast(questFactionRewEntry->QuestRewFactionValue[field]);
+ }
+ }
+
+ if (rep == 0.f)
+ continue;
+
+ if (quest->IsDaily())
+ {
+ rep = CalculateReputationGain(REPUTATION_SOURCE_DAILY_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], false);
+ }
+ else if (quest->IsWeekly())
+ {
+ rep = CalculateReputationGain(REPUTATION_SOURCE_WEEKLY_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], false);
+ }
+ else if (quest->IsMonthly())
+ {
+ rep = CalculateReputationGain(REPUTATION_SOURCE_MONTHLY_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], false);
+ }
+ else if (quest->IsRepeatable())
+ {
+ rep = CalculateReputationGain(REPUTATION_SOURCE_REPEATABLE_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], false);
+ }
+ else
+ {
+ rep = CalculateReputationGain(REPUTATION_SOURCE_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], false);
+ }
+
+ if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(quest->RewardFactionId[i]))
+ {
+ GetReputationMgr().ModifyReputation(factionEntry, rep, quest->HasSpecialFlag(QUEST_SPECIAL_FLAGS_NO_REP_SPILLOVER));
+ }
+ }
+}
+
+void Player::RewardExtraBonusTalentPoints(uint32 bonusTalentPoints)
+{
+ if (bonusTalentPoints)
+ {
+ m_extraBonusTalentCount += bonusTalentPoints;
+ }
+}
+
+///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, int32 honor, bool awardXP)
+{
+ // do not reward honor in arenas, but enable onkill spellproc
+ if (InArena())
+ {
+ if (!uVictim || uVictim == this || uVictim->GetTypeId() != TYPEID_PLAYER)
+ return false;
+
+ if (GetBgTeamId() == uVictim->ToPlayer()->GetBgTeamId())
+ return false;
+
+ return true;
+ }
+
+ // 'Inactive' this aura prevents the player from gaining honor points and battleground tokens
+ if (HasAura(SPELL_AURA_PLAYER_INACTIVE))
+ return false;
+
+ /* check if player has same IP
+ if (uVictim && uVictim->GetTypeId() == TYPEID_PLAYER)
+ {
+ if (GetSession()->GetRemoteAddress() == uVictim->ToPlayer()->GetSession()->GetRemoteAddress())
+ 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.
+ UpdateHonorFields();
+
+ // do not reward honor in arenas, but return true to enable onkill spellproc
+ if (InArena())
+ return true;
+
+ // Promote to float for calculations
+ float honor_f = (float)honor;
+
+ if (honor_f <= 0)
+ {
+ if (!uVictim || uVictim == this || uVictim->HasAuraType(SPELL_AURA_NO_PVP_CREDIT))
+ return false;
+
+ victim_guid = uVictim->GetGUID();
+
+ if (uVictim->GetTypeId() == TYPEID_PLAYER)
+ {
+ Player* victim = uVictim->ToPlayer();
+
+ if (GetTeamId() == victim->GetTeamId() && !sWorld->IsFFAPvPRealm())
+ return false;
+
+ uint8 k_level = GetLevel();
+ uint8 k_grey = Acore::XP::GetGrayLevel(k_level);
+ uint8 v_level = victim->GetLevel();
+
+ if (v_level <= k_grey)
+ return false;
+
+ // 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 = victim->GetUInt32Value(PLAYER_CHOSEN_TITLE);
+ uint32 killer_title = 0;
+ sScriptMgr->OnVictimRewardBefore(this, victim, killer_title, victim_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.
+
+ honor_f = std::ceil(Acore::Honor::hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey));
+
+ // count the number of playerkills in one day
+ ApplyModUInt32Value(PLAYER_FIELD_KILLS, 1, true);
+ // and those in a lifetime
+ ApplyModUInt32Value(PLAYER_FIELD_LIFETIME_HONORABLE_KILLS, 1, true);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EARN_HONORABLE_KILL);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_CLASS, victim->getClass());
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_RACE, victim->getRace(true));
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL_AT_AREA, GetAreaId());
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL, 1, 0, victim);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_SPECIAL_PVP_KILL, 1, 0, victim);
+ sScriptMgr->OnVictimRewardAfter(this, victim, killer_title, victim_rank, honor_f);
+ }
+ //npcbot: honor for bots
+ else if (uVictim->ToCreature()->IsNPCBot() && !uVictim->ToCreature()->IsTempBot())
+ {
+ Creature const* bot = uVictim->ToCreature();
+
+ uint32 check1 = GetFaction();
+ uint32 check2 = bot->GetFaction();
+
+ if (!bot->IsFreeBot())
+ {
+ check1 = uint32(GetTeamId());
+ check2 = uint32(bot->GetBotOwner()->GetTeamId());
+ }
+
+ if (check1 == check2 && !sWorld->IsFFAPvPRealm())
+ return false;
+
+ uint8 k_level = GetLevel();
+ uint8 k_grey = Acore::XP::GetGrayLevel(k_level);
+ uint8 v_level = uVictim->GetLevel();
+
+ if (v_level <= k_grey)
+ return false;
+
+ victim_guid.Clear(); // Don't show HK: message, only log.
+
+ //TODO: honor gain rate
+ honor_f = ceil(Acore::Honor::hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey));
+ }
+ //end npcbot
+ else
+ {
+ if (!uVictim->ToCreature()->IsRacialLeader())
+ return false;
+
+ honor_f = 100.0f; // ??? need more info
+ victim_rank = 19; // HK: Leader
+ }
+ }
+
+ if (uVictim)
+ {
+ if (groupsize > 1)
+ honor_f /= groupsize;
+
+ // apply honor multiplier from aura (not stacking-get highest)
+ AddPct(honor_f, GetMaxPositiveAuraModifier(SPELL_AURA_MOD_HONOR_GAIN_PCT));
+ }
+
+ honor_f *= sWorld->getRate(RATE_HONOR);
+ // Back to int now
+ honor = int32(honor_f);
+ // 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 << honor;
+ data << victim_guid;
+ data << victim_rank;
+
+ // Xinef: non quest case, quest honor obtain is send in quest reward packet
+ if (uVictim || groupsize > 0)
+ GetSession()->SendPacket(&data);
+
+ // add honor points
+ ModifyHonorPoints(honor);
+
+ ApplyModUInt32Value(PLAYER_FIELD_TODAY_CONTRIBUTION, honor, true);
+
+ // Xinef: Battleground experience
+ if (awardXP)
+ if (Battleground* bg = GetBattleground())
+ {
+ bg->UpdatePlayerScore(this, SCORE_BONUS_HONOR, honor, false); //false: prevent looping
+ // Xinef: Only for BG activities
+ if (!uVictim)
+ GiveXP(uint32(honor * (3 + GetLevel() * 0.30f)), nullptr);
+ }
+
+ if (sWorld->getBoolConfig(CONFIG_PVP_TOKEN_ENABLE))
+ {
+ if (!uVictim || uVictim == this || uVictim->HasAuraType(SPELL_AURA_NO_PVP_CREDIT))
+ return true;
+
+ if (uVictim->GetTypeId() == TYPEID_PLAYER)
+ {
+ // Check if allowed to receive it in current map
+ uint8 MapType = sWorld->getIntConfig(CONFIG_PVP_TOKEN_MAP_TYPE);
+ if ((MapType == 1 && !InBattleground() && !IsFFAPvP())
+ || (MapType == 2 && !IsFFAPvP())
+ || (MapType == 3 && !InBattleground()))
+ return true;
+
+ uint32 itemID = sWorld->getIntConfig(CONFIG_PVP_TOKEN_ID);
+ int32 count = sWorld->getIntConfig(CONFIG_PVP_TOKEN_COUNT);
+
+ if (AddItem(itemID, count))
+ ChatHandler(GetSession()).PSendSysMessage("You have been awarded a token for slaying another player.");
+ }
+ }
+
+ return true;
+}
+
+void Player::SetHonorPoints(uint32 value)
+{
+ if (value > sWorld->getIntConfig(CONFIG_MAX_HONOR_POINTS))
+ {
+ if (int32 copperPerPoint = sWorld->getIntConfig(CONFIG_MAX_HONOR_POINTS_MONEY_PER_POINT))
+ {
+ // Only convert points on login, not when awarded honor points.
+ if (isBeingLoaded())
+ {
+ int32 excessPoints = value - sWorld->getIntConfig(CONFIG_MAX_HONOR_POINTS);
+ ModifyMoney(excessPoints * copperPerPoint);
+ }
+ }
+
+ value = sWorld->getIntConfig(CONFIG_MAX_HONOR_POINTS);
+ }
+ SetUInt32Value(PLAYER_FIELD_HONOR_CURRENCY, value);
+ if (value)
+ AddKnownCurrency(ITEM_HONOR_POINTS_ID);
+}
+
+void Player::SetArenaPoints(uint32 value)
+{
+ if (value > sWorld->getIntConfig(CONFIG_MAX_ARENA_POINTS))
+ value = sWorld->getIntConfig(CONFIG_MAX_ARENA_POINTS);
+ SetUInt32Value(PLAYER_FIELD_ARENA_CURRENCY, value);
+ if (value)
+ AddKnownCurrency(ITEM_ARENA_POINTS_ID);
+}
+
+void Player::ModifyHonorPoints(int32 value, CharacterDatabaseTransaction trans)
+{
+ int32 newValue = int32(GetHonorPoints()) + value;
+ if (newValue < 0)
+ newValue = 0;
+ SetHonorPoints(uint32(newValue));
+
+ if (trans)
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_HONOR_POINTS);
+ stmt->SetData(0, newValue);
+ stmt->SetData(1, GetGUID().GetCounter());
+ trans->Append(stmt);
+ }
+}
+
+void Player::ModifyArenaPoints(int32 value, CharacterDatabaseTransaction trans)
+{
+ int32 newValue = int32(GetArenaPoints()) + value;
+ if (newValue < 0)
+ newValue = 0;
+ SetArenaPoints(uint32(newValue));
+
+ if (trans)
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_ARENA_POINTS);
+ stmt->SetData(0, newValue);
+ stmt->SetData(1, GetGUID().GetCounter());
+ trans->Append(stmt);
+ }
+}
+
+uint32 Player::GetArenaTeamIdFromDB(ObjectGuid guid, uint8 type)
+{
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ARENA_TEAM_ID_BY_PLAYER_GUID);
+ stmt->SetData(0, guid.GetCounter());
+ stmt->SetData(1, type);
+ PreparedQueryResult result = CharacterDatabase.Query(stmt);
+
+ if (!result)
+ return 0;
+
+ uint32 id = (*result)[0].Get();
+ return id;
+}
+
+uint32 Player::GetZoneIdFromDB(ObjectGuid guid)
+{
+ ObjectGuid::LowType guidLow = guid.GetCounter();
+
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_ZONE);
+ stmt->SetData(0, guidLow);
+ PreparedQueryResult result = CharacterDatabase.Query(stmt);
+
+ if (!result)
+ return 0;
+
+ Field* fields = result->Fetch();
+ uint32 zone = fields[0].Get();
+
+ if (!zone)
+ {
+ // stored zone is zero, use generic and slow zone detection
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_POSITION_XYZ);
+ stmt->SetData(0, guidLow);
+ PreparedQueryResult posResult = CharacterDatabase.Query(stmt);
+
+ if (!posResult)
+ {
+ return 0;
+ }
+
+ fields = posResult->Fetch();
+ uint32 map = fields[0].Get();
+ float posx = fields[1].Get();
+ float posy = fields[2].Get();
+ float posz = fields[3].Get();
+
+ if (!sMapStore.LookupEntry(map))
+ return 0;
+
+ zone = sMapMgr->GetZoneId(PHASEMASK_NORMAL, map, posx, posy, posz);
+
+ if (zone > 0)
+ {
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ZONE);
+
+ stmt->SetData(0, uint16(zone));
+ stmt->SetData(1, guidLow);
+
+ CharacterDatabase.Execute(stmt);
+ }
+ }
+
+ return zone;
+}
+
+//If players are too far away from the duel flag... they lose the duel
+void Player::CheckDuelDistance(time_t currTime)
+{
+ if (!duel)
+ {
+ return;
+ }
+
+ ObjectGuid duelFlagGUID = GetGuidValue(PLAYER_DUEL_ARBITER);
+ GameObject* obj = GetMap()->GetGameObject(duelFlagGUID);
+ if (!obj)
+ return;
+
+ if (!duel->OutOfBoundsTime)
+ {
+ if (!IsWithinDistInMap(obj, 50))
+ {
+ duel->OutOfBoundsTime = currTime + 10;
+
+ WorldPacket data(SMSG_DUEL_OUTOFBOUNDS, 0);
+ GetSession()->SendPacket(&data);
+ }
+ }
+ else
+ {
+ if (IsWithinDistInMap(obj, 40))
+ {
+ duel->OutOfBoundsTime = 0;
+
+ WorldPacket data(SMSG_DUEL_INBOUNDS, 0);
+ GetSession()->SendPacket(&data);
+ }
+ else if (currTime >= duel->OutOfBoundsTime)
+ DuelComplete(DUEL_FLED);
+ }
+}
+
+bool Player::IsOutdoorPvPActive()
+{
+ return IsAlive() && !HasInvisibilityAura() && !HasStealthAura() && IsPvP() && !HasUnitMovementFlag(MOVEMENTFLAG_FLYING) && !IsInFlight();
+}
+
+void Player::DuelComplete(DuelCompleteType type)
+{
+ // duel not requested
+ if (!duel)
+ return;
+
+ // Check if DuelComplete() has been called already up in the stack and in that case don't do anything else here
+ if (duel->State == DUEL_STATE_COMPLETED)
+ return;
+
+ Player* opponent = duel->Opponent;
+ duel->State = DUEL_STATE_COMPLETED;
+ opponent->duel->State = DUEL_STATE_COMPLETED;
+
+ LOG_DEBUG("entities.unit", "Player::DuelComplete: Player '{}' ({}), Opponent: '{}' ({})", GetName(), GetGUID().ToString(), opponent->GetName(), opponent->GetGUID().ToString());
+
+ WorldPacket data(SMSG_DUEL_COMPLETE, (1));
+ data << uint8((type != DUEL_INTERRUPTED) ? 1 : 0);
+ SendDirectMessage(&data);
+ if (opponent->GetSession())
+ {
+ opponent->SendDirectMessage(&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 << opponent->GetName();
+ data << GetName();
+ SendMessageToSet(&data, true);
+ }
+
+ sScriptMgr->OnPlayerDuelEnd(opponent, this, type);
+
+ switch (type)
+ {
+ case DUEL_FLED:
+ // if initiator and opponent are on the same team
+ // or initiator and opponent are not PvP enabled, forcibly stop attacking
+ if (GetTeamId() == opponent->GetTeamId())
+ {
+ AttackStop();
+ opponent->AttackStop();
+ }
+ else
+ {
+ if (!IsPvP())
+ {
+ AttackStop();
+ }
+ if (!opponent->IsPvP())
+ {
+ opponent->AttackStop();
+ }
+ }
+ break;
+ case DUEL_WON:
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOSE_DUEL, 1);
+ opponent->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_DUEL, 1);
+
+ // Credit for quest Death's Challenge
+ if (getClass() == CLASS_DEATH_KNIGHT && opponent->GetQuestStatus(12733) == QUEST_STATUS_INCOMPLETE)
+ {
+ opponent->CastSpell(opponent, 52994, true);
+ }
+
+ // Honor points after duel (the winner) - ImpConfig
+ if (uint32 amount = sWorld->getIntConfig(CONFIG_HONOR_AFTER_DUEL))
+ {
+ opponent->RewardHonor(nullptr, 1, amount);
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ // Victory emote spell
+ if (type != DUEL_INTERRUPTED)
+ {
+ opponent->CastSpell(opponent, 52852, true);
+ }
+
+ // Remove Duel Flag object
+ GameObject* obj = GetMap()->GetGameObject(GetGuidValue(PLAYER_DUEL_ARBITER));
+ if (obj)
+ {
+ duel->Initiator->RemoveGameObject(obj, true);
+ }
+
+ /* remove auras */
+ AuraApplicationMap& itsAuras = opponent->GetAppliedAuras();
+ for (AuraApplicationMap::iterator i = itsAuras.begin(); i != itsAuras.end();)
+ {
+ Aura const* aura = i->second->GetBase();
+ if (!i->second->IsPositive() && aura->GetCasterGUID() == GetGUID() && aura->GetApplyTime() >= duel->StartTime)
+ {
+ opponent->RemoveAura(i);
+ }
+ else
+ {
+ ++i;
+ }
+ }
+
+ AuraApplicationMap& myAuras = GetAppliedAuras();
+ for (AuraApplicationMap::iterator i = myAuras.begin(); i != myAuras.end();)
+ {
+ Aura const* aura = i->second->GetBase();
+ if (!i->second->IsPositive() && aura->GetCasterGUID() == opponent->GetGUID() && aura->GetApplyTime() >= duel->StartTime)
+ RemoveAura(i);
+ else
+ ++i;
+ }
+
+ // cleanup combo points
+ if (GetComboTarget() == duel->Opponent)
+ {
+ ClearComboPoints();
+ }
+ else if (GetComboTargetGUID() == duel->Opponent->GetPetGUID())
+ {
+ ClearComboPoints();
+ }
+
+ if (duel->Opponent->GetComboTarget() == this)
+ {
+ duel->Opponent->ClearComboPoints();
+ }
+ else if (duel->Opponent->GetComboTargetGUID() == GetPetGUID())
+ {
+ duel->Opponent->ClearComboPoints();
+ }
+
+ //cleanups
+ SetGuidValue(PLAYER_DUEL_ARBITER, ObjectGuid::Empty);
+ SetUInt32Value(PLAYER_DUEL_TEAM, 0);
+ opponent->SetGuidValue(PLAYER_DUEL_ARBITER, ObjectGuid::Empty);
+ opponent->SetUInt32Value(PLAYER_DUEL_TEAM, 0);
+
+ opponent->duel.reset(nullptr);
+ duel.reset(nullptr);
+}
+
+//---------------------------------------------------------//
+
+void Player::_ApplyItemMods(Item* item, uint8 slot, bool apply)
+{
+ if (slot >= INVENTORY_SLOT_BAG_END || !item)
+ return;
+
+ ItemTemplate const* proto = item->GetTemplate();
+
+ if (!proto)
+ return;
+
+ // not apply/remove mods for broken item
+ if (item->IsBroken())
+ return;
+
+ LOG_DEBUG("entities.player", "applying mods for item {} ", item->GetGUID().ToString());
+
+ uint8 attacktype = Player::GetAttackBySlot(slot);
+
+ if (item->HasSocket()) //only (un)equipping of items with sockets can influence metagems, so no need to waste time with normal items
+ CorrectMetaGemEnchants(slot, apply);
+
+ 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);
+
+ LOG_DEBUG("entities.player.items", "_ApplyItemMods complete.");
+}
+
+void Player::_ApplyItemBonuses(ItemTemplate 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) : nullptr;
+ 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();
+ uint32 CustomScalingStatValue = 0;
+
+ sScriptMgr->OnCustomScalingStatValueBefore(this, proto, slot, apply, CustomScalingStatValue);
+
+ uint32 ScalingStatValue = proto->ScalingStatValue > 0 ? proto->ScalingStatValue : CustomScalingStatValue;
+
+ if (ssd && ssd_level > ssd->MaxLevel)
+ ssd_level = ssd->MaxLevel;
+
+ ScalingStatValuesEntry const* ssv = proto->ScalingStatValue ? sScalingStatValuesStore.LookupEntry(ssd_level) : nullptr;
+ if (only_level_scale && !ssv)
+ return;
+
+ for (uint8 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 (ssv)
+ {
+ if (ssd)
+ {
+ if (ssd->StatMod[i] < 0)
+ continue;
+
+ statType = ssd->StatMod[i];
+ val = (ssv->getssdMultiplier(ScalingStatValue) * ssd->Modifier[i]) / 10000;
+ }
+ else
+ {
+ if (i >= proto->StatsCount)
+ continue;
+
+ // OnCustomScalingStatValue(Player* player, ItemTemplate const* proto, uint32& statType, int32& val, uint8 itemProtoStatNumber, uint32 ScalingStatValue, ScalingStatValuesEntry const* ssv)
+ sScriptMgr->OnCustomScalingStatValue(this, proto, statType, val, i, ScalingStatValue, ssv);
+ }
+ }
+ else
+ {
+ if (i >= proto->StatsCount)
+ continue;
+
+ 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_HEALTH: // modify HP
+ HandleStatModifier(UNIT_MOD_HEALTH, 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_DEFENSE_SKILL_RATING:
+ ApplyRatingMod(CR_DEFENSE_SKILL, int32(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_BLOCK_RATING:
+ ApplyRatingMod(CR_BLOCK, int32(val), apply);
+ break;
+ case ITEM_MOD_HIT_MELEE_RATING:
+ ApplyRatingMod(CR_HIT_MELEE, int32(val), apply);
+ break;
+ case ITEM_MOD_HIT_RANGED_RATING:
+ ApplyRatingMod(CR_HIT_RANGED, int32(val), apply);
+ break;
+ case ITEM_MOD_HIT_SPELL_RATING:
+ ApplyRatingMod(CR_HIT_SPELL, int32(val), apply);
+ break;
+ case ITEM_MOD_CRIT_MELEE_RATING:
+ ApplyRatingMod(CR_CRIT_MELEE, int32(val), apply);
+ break;
+ case ITEM_MOD_CRIT_RANGED_RATING:
+ ApplyRatingMod(CR_CRIT_RANGED, int32(val), apply);
+ break;
+ case ITEM_MOD_CRIT_SPELL_RATING:
+ ApplyRatingMod(CR_CRIT_SPELL, int32(val), apply);
+ break;
+ case ITEM_MOD_HIT_TAKEN_MELEE_RATING:
+ ApplyRatingMod(CR_HIT_TAKEN_MELEE, int32(val), apply);
+ break;
+ case ITEM_MOD_HIT_TAKEN_RANGED_RATING:
+ ApplyRatingMod(CR_HIT_TAKEN_RANGED, int32(val), apply);
+ break;
+ case ITEM_MOD_HIT_TAKEN_SPELL_RATING:
+ ApplyRatingMod(CR_HIT_TAKEN_SPELL, int32(val), apply);
+ break;
+ case ITEM_MOD_CRIT_TAKEN_MELEE_RATING:
+ ApplyRatingMod(CR_CRIT_TAKEN_MELEE, int32(val), apply);
+ break;
+ case ITEM_MOD_CRIT_TAKEN_RANGED_RATING:
+ ApplyRatingMod(CR_CRIT_TAKEN_RANGED, int32(val), apply);
+ break;
+ case ITEM_MOD_CRIT_TAKEN_SPELL_RATING:
+ ApplyRatingMod(CR_CRIT_TAKEN_SPELL, int32(val), apply);
+ break;
+ case ITEM_MOD_HASTE_MELEE_RATING:
+ ApplyRatingMod(CR_HASTE_MELEE, int32(val), apply);
+ break;
+ case ITEM_MOD_HASTE_RANGED_RATING:
+ ApplyRatingMod(CR_HASTE_RANGED, int32(val), apply);
+ break;
+ case ITEM_MOD_HASTE_SPELL_RATING:
+ ApplyRatingMod(CR_HASTE_SPELL, 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_HIT_TAKEN_RATING:
+ ApplyRatingMod(CR_HIT_TAKEN_MELEE, int32(val), apply);
+ ApplyRatingMod(CR_HIT_TAKEN_RANGED, int32(val), apply);
+ ApplyRatingMod(CR_HIT_TAKEN_SPELL, int32(val), apply);
+ break;
+ case ITEM_MOD_CRIT_TAKEN_RATING:
+ case ITEM_MOD_RESILIENCE_RATING:
+ ApplyRatingMod(CR_CRIT_TAKEN_MELEE, int32(val), apply);
+ ApplyRatingMod(CR_CRIT_TAKEN_RANGED, int32(val), apply);
+ ApplyRatingMod(CR_CRIT_TAKEN_SPELL, 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_FERAL_ATTACK_POWER:
+ // ApplyFeralAPBonus(int32(val), apply);
+ // break;
+ case ITEM_MOD_MANA_REGENERATION:
+ ApplyManaRegenBonus(int32(val), apply);
+ break;
+ case ITEM_MOD_ARMOR_PENETRATION_RATING:
+ ApplyRatingMod(CR_ARMOR_PENETRATION, int32(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:
+ ApplySpellPenetrationBonus(val, apply);
+ break;
+ case ITEM_MOD_BLOCK_VALUE:
+ HandleBaseModValue(SHIELD_BLOCK_VALUE, FLAT_MOD, float(val), apply);
+ break;
+ /// @deprecated item mods
+ case ITEM_MOD_SPELL_HEALING_DONE:
+ case ITEM_MOD_SPELL_DAMAGE_DONE:
+ break;
+ }
+ }
+
+ // Apply Spell Power from ScalingStatValue if set
+ if (ssv)
+ if (int32 spellbonus = ssv->getSpellBonus(ScalingStatValue))
+ ApplySpellPowerBonus(spellbonus, apply);
+
+ // If set ScalingStatValue armor get it or use item armor
+ uint32 armor = proto->Armor;
+ if (ssv)
+ {
+ if (uint32 ssvarmor = ssv->getArmorMod(ScalingStatValue))
+ if (proto->ScalingStatValue > 0 || ssvarmor < proto->Armor) //Check to avoid higher values than stat itself (heirloom OR items with correct armor value)
+ armor = ssvarmor;
+ }
+ else if (armor && proto->ArmorDamageModifier)
+ armor -= uint32(proto->ArmorDamageModifier);
+
+ if (armor)
+ {
+ UnitModifierType modType = TOTAL_VALUE;
+ if (proto->Class == ITEM_CLASS_ARMOR)
+ {
+ switch (proto->SubClass)
+ {
+ case ITEM_SUBCLASS_ARMOR_CLOTH:
+ case ITEM_SUBCLASS_ARMOR_LEATHER:
+ case ITEM_SUBCLASS_ARMOR_MAIL:
+ case ITEM_SUBCLASS_ARMOR_PLATE:
+ case ITEM_SUBCLASS_ARMOR_SHIELD:
+ modType = BASE_VALUE;
+ break;
+ }
+ }
+ HandleStatModifier(UNIT_MOD_ARMOR, modType, float(armor), apply);
+ }
+
+ // Add armor bonus from ArmorDamageModifier if > 0
+ if (proto->ArmorDamageModifier > 0 && sScriptMgr->CanArmorDamageModifier(this))
+ HandleStatModifier(UNIT_MOD_ARMOR, TOTAL_VALUE, float(proto->ArmorDamageModifier), apply);
+
+ if (proto->Block)
+ HandleBaseModValue(SHIELD_BLOCK_VALUE, FLAT_MOD, float(proto->Block), apply);
+
+ if (proto->HolyRes)
+ HandleStatModifier(UNIT_MOD_RESISTANCE_HOLY, BASE_VALUE, float(proto->HolyRes), apply);
+
+ if (proto->FireRes)
+ HandleStatModifier(UNIT_MOD_RESISTANCE_FIRE, BASE_VALUE, float(proto->FireRes), apply);
+
+ if (proto->NatureRes)
+ HandleStatModifier(UNIT_MOD_RESISTANCE_NATURE, BASE_VALUE, float(proto->NatureRes), apply);
+
+ if (proto->FrostRes)
+ HandleStatModifier(UNIT_MOD_RESISTANCE_FROST, BASE_VALUE, float(proto->FrostRes), apply);
+
+ if (proto->ShadowRes)
+ HandleStatModifier(UNIT_MOD_RESISTANCE_SHADOW, BASE_VALUE, float(proto->ShadowRes), apply);
+
+ if (proto->ArcaneRes)
+ HandleStatModifier(UNIT_MOD_RESISTANCE_ARCANE, BASE_VALUE, float(proto->ArcaneRes), apply);
+
+ uint8 attType = Player::GetAttackBySlot(slot);
+ if (attType != MAX_ATTACK)
+ {
+ _ApplyWeaponDamage(slot, proto, ssv, apply);
+ }
+
+ // Druids get feral AP bonus from weapon dps (also use DPS from ScalingStatValue)
+ if (getClass() == CLASS_DRUID)
+ {
+ int32 dpsMod = 0;
+ int32 feral_bonus = 0;
+ if (ssv)
+ {
+ dpsMod = ssv->getDPSMod(ScalingStatValue);
+ feral_bonus += ssv->getFeralBonus(ScalingStatValue);
+ }
+
+ feral_bonus += proto->getFeralBonus(dpsMod);
+ sScriptMgr->OnGetFeralApBonus(this, feral_bonus, dpsMod, proto, ssv);
+ if (feral_bonus)
+ ApplyFeralAPBonus(feral_bonus, apply);
+ }
+}
+
+void Player::_ApplyWeaponDamage(uint8 slot, ItemTemplate const* proto, ScalingStatValuesEntry const* ssv, bool apply)
+{
+ uint32 CustomScalingStatValue = 0;
+
+ sScriptMgr->OnCustomScalingStatValueBefore(this, proto, slot, apply, CustomScalingStatValue);
+
+ uint32 ScalingStatValue = proto->ScalingStatValue > 0 ? proto->ScalingStatValue : CustomScalingStatValue;
+
+ // following part fix disarm issue
+ // that doesn't apply the scaling after disarmed
+ if (!ssv)
+ {
+ ScalingStatDistributionEntry const* ssd = proto->ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(proto->ScalingStatDistribution) : nullptr;
+
+ // 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;
+
+ ssv = ScalingStatValue ? sScalingStatValuesStore.LookupEntry(ssd_level) : nullptr;
+ }
+
+ uint8 attType = Player::GetAttackBySlot(slot);
+ if (!IsInFeralForm() && apply && !CanUseAttackType(attType))
+ {
+ return;
+ }
+
+ for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i)
+ {
+ float minDamage = proto->Damage[i].DamageMin;
+ float maxDamage = proto->Damage[i].DamageMax;
+
+ // If set dpsMod in ScalingStatValue use it for min (70% from average), max (130% from average) damage
+ if (ssv)
+ {
+ int32 extraDPS = ssv->getDPSMod(ScalingStatValue);
+ if (extraDPS)
+ {
+ float average = extraDPS * proto->Delay / 1000.0f;
+ minDamage = 0.7f * average;
+ maxDamage = 1.3f * average;
+ }
+ }
+
+ if (apply)
+ {
+ if (minDamage > 0.f)
+ {
+ SetBaseWeaponDamage(WeaponAttackType(attType), MINDAMAGE, minDamage, i);
+ }
+
+ if (maxDamage > 0.f)
+ {
+ SetBaseWeaponDamage(WeaponAttackType(attType), MAXDAMAGE, maxDamage, i);
+ }
+ }
+ }
+
+ if (!apply)
+ {
+ for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i)
+ {
+ SetBaseWeaponDamage(WeaponAttackType(attType), MINDAMAGE, 0.f, i);
+ SetBaseWeaponDamage(WeaponAttackType(attType), MAXDAMAGE, 0.f, i);
+ }
+
+ if (attType == BASE_ATTACK)
+ {
+ SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, BASE_MINDAMAGE);
+ SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, BASE_MAXDAMAGE);
+ }
+ }
+
+ if (proto->Delay && !IsInFeralForm())
+ {
+ 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);
+ }
+
+ // No need to modify any physical damage for ferals as it is calculated from stats only
+ if (IsInFeralForm())
+ return;
+
+ if (CanModifyStats() && (GetWeaponDamageRange(WeaponAttackType(attType), MAXDAMAGE) || proto->Delay))
+ UpdateDamagePhysical(WeaponAttackType(attType));
+}
+
+SpellSchoolMask Player::GetMeleeDamageSchoolMask(WeaponAttackType attackType /*= BASE_ATTACK*/, uint8 damageIndex /*= 0*/) const
+{
+ if (Item const* weapon = GetWeaponForAttack(attackType, true))
+ {
+ return SpellSchoolMask(1 << weapon->GetTemplate()->Damage[damageIndex].DamageType);
+ }
+
+ return SPELL_SCHOOL_MASK_NORMAL;
+}
+
+void Player::_ApplyWeaponDependentAuraMods(Item* item, WeaponAttackType attackType, bool apply)
+{
+ AuraEffectList const& auraCritList = GetAuraEffectsByType(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT);
+ for (AuraEffectList::const_iterator itr = auraCritList.begin(); itr != auraCritList.end(); ++itr)
+ _ApplyWeaponDependentAuraCritMod(item, attackType, *itr, apply);
+
+ AuraEffectList const& auraDamageFlatList = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_DONE);
+ for (AuraEffectList::const_iterator itr = auraDamageFlatList.begin(); itr != auraDamageFlatList.end(); ++itr)
+ _ApplyWeaponDependentAuraDamageMod(item, attackType, *itr, apply);
+
+ AuraEffectList const& auraDamagePctList = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE);
+ for (AuraEffectList::const_iterator itr = auraDamagePctList.begin(); itr != auraDamagePctList.end(); ++itr)
+ _ApplyWeaponDependentAuraDamageMod(item, attackType, *itr, apply);
+}
+
+void Player::_ApplyWeaponDependentAuraCritMod(Item* item, WeaponAttackType attackType, AuraEffect const* aura, bool apply)
+{
+ // don't apply mod if item is broken or cannot be used
+ if (item->IsBroken() || !CanUseAttackType(attackType))
+ return;
+
+ // generic not weapon specific case processes in aura code
+ if (aura->GetSpellInfo()->EquippedItemClass == -1)
+ return;
+
+ if (!sScriptMgr->CanApplyWeaponDependentAuraDamageMod(this, item, attackType, aura, apply))
+ 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->GetSpellInfo()))
+ HandleBaseModValue(mod, FLAT_MOD, float (aura->GetAmount()), apply);
+}
+
+void Player::_ApplyWeaponDependentAuraDamageMod(Item* item, WeaponAttackType attackType, AuraEffect const* aura, bool apply)
+{
+ // don't apply mod if item is broken or cannot be used
+ if (item->IsBroken() || !CanUseAttackType(attackType))
+ return;
+
+ // ignore spell mods for not wands
+ if ((aura->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) == 0 && (getClassMask() & CLASSMASK_WAND_USERS) == 0)
+ return;
+
+ // generic not weapon specific case processes in aura code
+ if (aura->GetSpellInfo()->EquippedItemClass == -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 (aura->GetAuraType())
+ {
+ 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->GetSpellInfo()))
+ {
+ HandleStatModifier(unitMod, unitModType, float(aura->GetAmount()), apply);
+ if (unitModType == TOTAL_VALUE)
+ {
+ if (aura->GetAmount() > 0)
+ ApplyModUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS, aura->GetAmount(), apply);
+ else
+ ApplyModUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG, aura->GetAmount(), apply);
+ }
+ }
+}
+
+void Player::ApplyItemEquipSpell(Item* item, bool apply, bool form_change)
+{
+ if (!item)
+ return;
+
+ ItemTemplate const* proto = item->GetTemplate();
+ if (!proto)
+ return;
+
+ for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
+ {
+ _Spell const& spellData = proto->Spells[i];
+
+ // no spell
+ if (!spellData.SpellId)
+ continue;
+
+ // Spells that should stay on the caster after removing the item.
+ constexpr std::array spellExceptions = { /*Electromagnetic Gigaflux Reactivator*/ 11826 };
+ const auto found = std::find(std::begin(spellExceptions), std::end(spellExceptions), spellData.SpellId);
+
+ // wrong triggering type
+ if (apply)
+ {
+ if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_EQUIP)
+ {
+ continue;
+ }
+ }
+ else
+ {
+ // If the spell is an exception do not remove it.
+ if (found != std::end(spellExceptions))
+ {
+ continue;
+ }
+ }
+
+ // check if it is valid spell
+ SpellInfo const* spellproto = sSpellMgr->GetSpellInfo(spellData.SpellId);
+ if (!spellproto)
+ continue;
+
+ ApplyEquipSpell(spellproto, item, apply, form_change);
+ }
+}
+
+void Player::ApplyEquipSpell(SpellInfo const* spellInfo, Item* item, bool apply, bool form_change)
+{
+ if (apply)
+ {
+ if (!sScriptMgr->CanApplyEquipSpell(this, spellInfo, item, apply, form_change))
+ return;
+
+ // Cannot be used in this stance/form
+ if (spellInfo->CheckShapeshift(GetShapeshiftForm()) != SPELL_CAST_OK)
+ return;
+
+ if (form_change) // check aura active state from other form
+ {
+ AuraApplicationMapBounds range = GetAppliedAuras().equal_range(spellInfo->Id);
+ for (AuraApplicationMap::const_iterator itr = range.first; itr != range.second; ++itr)
+ if (!item || itr->second->GetBase()->GetCastItemGUID() == item->GetGUID())
+ return;
+ }
+
+ LOG_DEBUG("entities.player", "WORLD: cast {} Equip spellId - {}", (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 (spellInfo->CheckShapeshift(GetShapeshiftForm()) == SPELL_CAST_OK)
+ return; // and remove only not compatible at form change
+ }
+
+ if (item)
+ RemoveAurasDueToItemSpell(spellInfo->Id, item->GetGUID()); // un-apply all spells, not only at-equipped
+ else
+ RemoveAurasDueToSpell(spellInfo->Id); // un-apply spell (item set case)
+
+ // Xinef: Remove Proc Spells and Summons
+ for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i)
+ {
+ // Xinef: Remove procs
+ if (spellInfo->Effects[i].TriggerSpell)
+ RemoveAurasDueToSpell(spellInfo->Effects[i].TriggerSpell);
+
+ // Xinef: remove minions summoned by item
+ if (spellInfo->Effects[i].Effect == SPELL_EFFECT_SUMMON)
+ RemoveAllMinionsByEntry(spellInfo->Effects[i].MiscValue);
+ }
+ }
+}
+
+void Player::CastItemCombatSpell(Unit* target, WeaponAttackType attType, uint32 procVictim, uint32 procEx)
+{
+ if (!target || !target->IsAlive() || target == this)
+ return;
+
+ // Xinef: do not use disarmed weapons, special exception - shaman ghost wolf form
+ // Xinef: normal forms proc on hit enchants / built in item bonuses
+ if (!CanUseAttackType(attType) || GetShapeshiftForm() == FORM_GHOSTWOLF)
+ return;
+
+ for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
+ {
+ // If usable, try to cast item spell
+ if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
+ if (!item->IsBroken())
+ if (ItemTemplate const* proto = item->GetTemplate())
+ {
+ // Additional check for weapons
+ if (proto->Class == ITEM_CLASS_WEAPON)
+ {
+ // offhand item cannot proc from main hand hit etc
+ EquipmentSlots slot;
+ switch (attType)
+ {
+ 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:
+ slot = EQUIPMENT_SLOT_END;
+ break;
+ }
+ if (slot != i)
+ continue;
+ }
+
+ CastItemCombatSpell(target, attType, procVictim, procEx, item, proto);
+ }
+ }
+}
+
+void Player::CastItemCombatSpell(Unit* target, WeaponAttackType attType, uint32 procVictim, uint32 procEx, Item* item, ItemTemplate const* proto)
+{
+ if (!sScriptMgr->CanCastItemCombatSpell(this, target, attType, procVictim, procEx, item, proto))
+ return;
+
+ // Can do effect if any damage done to target
+ if (procVictim & PROC_FLAG_TAKEN_DAMAGE)
+ //if (damageInfo->procVictim & PROC_FLAG_TAKEN_ANY_DAMAGE)
+ {
+ for (uint8 i = 0; i < MAX_ITEM_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;
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellData.SpellId);
+ if (!spellInfo)
+ {
+ LOG_ERROR("entities.player", "WORLD: unknown Item spellid {}", spellData.SpellId);
+ continue;
+ }
+
+ float chance = (float)spellInfo->ProcChance;
+
+ if (spellData.SpellPPMRate)
+ {
+ uint32 WeaponSpeed = GetAttackTime(attType);
+ chance = GetPPMProcChance(WeaponSpeed, spellData.SpellPPMRate, spellInfo);
+ }
+ else if (chance > 100.0f)
+ {
+ chance = GetWeaponProcChance();
+ }
+
+ if (roll_chance_f(chance) && sScriptMgr->OnCastItemCombatSpell(this, target, spellInfo, item))
+ CastSpell(target, spellInfo->Id, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD), item);
+ }
+ }
+
+ // item combat enchantments
+ for (uint8 e_slot = 0; e_slot < MAX_ENCHANTMENT_SLOT; ++e_slot)
+ {
+ uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(e_slot));
+ SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
+ if (!pEnchant)
+ continue;
+
+ for (uint8 s = 0; s < MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS; ++s)
+ {
+ if (pEnchant->type[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL)
+ continue;
+
+ SpellEnchantProcEntry const* entry = sSpellMgr->GetSpellEnchantProcEvent(enchant_id);
+
+ if (entry && entry->procEx)
+ {
+ // Check hit/crit/dodge/parry requirement
+ if ((entry->procEx & procEx) == 0)
+ continue;
+ }
+ else
+ {
+ // Can do effect if any damage done to target
+ if (!(procVictim & PROC_FLAG_TAKEN_DAMAGE))
+ //if (!(damageInfo->procVictim & PROC_FLAG_TAKEN_ANY_DAMAGE))
+ continue;
+ }
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(pEnchant->spellid[s]);
+ if (!spellInfo)
+ {
+ LOG_ERROR("entities.player", "Player::CastItemCombatSpell({}, name: {}, enchant: {}): unknown spell {} is casted, ignoring...",
+ GetGUID().ToString(), GetName(), pEnchant->ID, pEnchant->spellid[s]);
+ continue;
+ }
+
+ float chance = pEnchant->amount[s] != 0 ? float(pEnchant->amount[s]) : GetWeaponProcChance();
+
+ if (entry)
+ {
+ if (entry->PPMChance)
+ chance = GetPPMProcChance(proto->Delay, entry->PPMChance, spellInfo);
+ else if (entry->customChance)
+ chance = (float)entry->customChance;
+ }
+
+ // Apply spell mods
+ ApplySpellMod(pEnchant->spellid[s], SPELLMOD_CHANCE_OF_SUCCESS, chance);
+
+ // Shiv has 100% chance to apply the poison
+ if (FindCurrentSpellBySpellId(5938) && e_slot == TEMP_ENCHANTMENT_SLOT)
+ chance = 100.0f;
+
+ if (roll_chance_f(chance))
+ {
+ // Xinef: implement enchant charges
+ if (uint32 charges = item->GetEnchantmentCharges(EnchantmentSlot(e_slot)))
+ {
+ if (!--charges)
+ {
+ ApplyEnchantment(item, EnchantmentSlot(e_slot), false);
+ item->ClearEnchantment(EnchantmentSlot(e_slot));
+ }
+ else
+ item->SetEnchantmentCharges(EnchantmentSlot(e_slot), charges);
+ }
+
+ if (spellInfo->IsPositive())
+ CastSpell(this, spellInfo, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD), item);
+ else
+ CastSpell(target, spellInfo, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD), item);
+ }
+ }
+ }
+}
+
+void Player::CastItemUseSpell(Item* item, SpellCastTargets const& targets, uint8 cast_count, uint32 glyphIndex)
+{
+ if (!sScriptMgr->CanCastItemUseSpell(this, item, targets, cast_count, glyphIndex))
+ return;
+
+ ItemTemplate const* proto = item->GetTemplate();
+ // special learning case
+ if (proto->Spells[0].SpellId == 483 || proto->Spells[0].SpellId == 55884)
+ {
+ uint32 learn_spell_id = proto->Spells[0].SpellId;
+ uint32 learning_spell_id = proto->Spells[1].SpellId;
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(learn_spell_id);
+ if (!spellInfo)
+ {
+ LOG_ERROR("entities.player", "Player::CastItemUseSpell: Item (Entry: {}) in have wrong spell id {}, ignoring ", proto->ItemId, learn_spell_id);
+ SendEquipError(EQUIP_ERR_NONE, item, nullptr);
+ return;
+ }
+
+ Spell* spell = new Spell(this, spellInfo, TRIGGERED_NONE);
+ spell->m_CastItem = item;
+ spell->m_cast_count = cast_count; //set count of casts
+ spell->SetSpellValue(SPELLVALUE_BASE_POINT0, learning_spell_id);
+ spell->prepare(&targets);
+ return;
+ }
+
+ // use triggered flag only for items with many spell casts and for not first cast
+ uint8 count = 0;
+
+ std::list pushSpells;
+ // item spells casted at use
+ for (uint8 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;
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellData.SpellId);
+ if (!spellInfo)
+ {
+ LOG_ERROR("entities.player", "Player::CastItemUseSpell: Item (Entry: {}) in have wrong spell id {}, ignoring", proto->ItemId, spellData.SpellId);
+ continue;
+ }
+
+ if (HasSpellCooldown(spellInfo->Id))
+ {
+ continue;
+ }
+
+ if (!spellInfo->CheckElixirStacking(this))
+ {
+ Spell::SendCastResult(this, spellInfo, cast_count, SPELL_FAILED_AURA_BOUNCED);
+ continue;
+ }
+
+ Spell* spell = new Spell(this, spellInfo, (count > 0) ? TRIGGERED_FULL_MASK : TRIGGERED_NONE);
+ spell->m_CastItem = item;
+ spell->m_cast_count = cast_count; // set count of casts
+ spell->m_glyphIndex = glyphIndex; // glyph index
+ spell->InitExplicitTargets(targets);
+
+ // Xinef: dont allow to cast such spells, it may happen that spell possess 2 spells, one for players and one for items / gameobjects
+ // Xinef: if first one is cast on player, it may be deleted thus resulting in crash because second spell has saved pointer to the item
+ // Xinef: there is one problem with scripts which wont be loaded at the moment of call
+ SpellCastResult result = spell->CheckCast(true);
+ if (result != SPELL_CAST_OK)
+ {
+ spell->SendCastResult(result);
+ delete spell;
+ continue;
+ }
+
+ pushSpells.push_back(spell);
+ //spell->prepare(&targets);
+
+ ++count;
+ }
+
+ // Item enchantments spells casted at use
+ for (uint8 e_slot = 0; e_slot < MAX_ENCHANTMENT_SLOT; ++e_slot)
+ {
+ uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(e_slot));
+ SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
+ if (!pEnchant)
+ continue;
+ for (uint8 s = 0; s < MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS; ++s)
+ {
+ if (pEnchant->type[s] != ITEM_ENCHANTMENT_TYPE_USE_SPELL)
+ continue;
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(pEnchant->spellid[s]);
+ if (!spellInfo)
+ {
+ LOG_ERROR("entities.player", "Player::CastItemUseSpell Enchant {}, cast unknown spell {}", pEnchant->ID, pEnchant->spellid[s]);
+ continue;
+ }
+
+ if (HasSpellCooldown(spellInfo->Id))
+ continue;
+
+ Spell* spell = new Spell(this, spellInfo, (count > 0) ? TRIGGERED_FULL_MASK : TRIGGERED_NONE);
+ spell->m_CastItem = item;
+ spell->m_cast_count = cast_count; // set count of casts
+ spell->m_glyphIndex = glyphIndex; // glyph index
+ spell->InitExplicitTargets(targets);
+
+ // Xinef: dont allow to cast such spells, it may happen that spell possess 2 spells, one for players and one for items / gameobjects
+ // Xinef: if first one is cast on player, it may be deleted thus resulting in crash because second spell has saved pointer to the item
+ // Xinef: there is one problem with scripts which wont be loaded at the moment of call
+ SpellCastResult result = spell->CheckCast(true);
+ if (result != SPELL_CAST_OK)
+ {
+ spell->SendCastResult(result);
+ delete spell;
+ continue;
+ }
+
+ pushSpells.push_back(spell);
+ //spell->prepare(&targets);
+
+ ++count;
+ }
+ }
+
+ // xinef: send all spells in one go, prevents crash because container is not set
+ for (std::list::const_iterator itr = pushSpells.begin(); itr != pushSpells.end(); ++itr)
+ (*itr)->prepare(&targets);
+}
+
+void Player::_RemoveAllItemMods()
+{
+ LOG_DEBUG("entities.player.items", "_RemoveAllItemMods start.");
+
+ for (uint8 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
+ {
+ if (m_items[i])
+ {
+ ItemTemplate const* proto = m_items[i]->GetTemplate();
+ if (!proto)
+ continue;
+
+ // item set bonuses not dependent from item broken state
+ if (proto->ItemSet)
+ RemoveItemsSetItem(this, proto);
+
+ if (m_items[i]->IsBroken() || !CanUseAttackType(GetAttackBySlot(i)))
+ continue;
+
+ ApplyItemEquipSpell(m_items[i], false);
+ ApplyEnchantment(m_items[i], false);
+ }
+ }
+
+ for (uint8 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
+ {
+ if (m_items[i])
+ {
+ if (m_items[i]->IsBroken() || !CanUseAttackType(GetAttackBySlot(i)))
+ continue;
+ ItemTemplate const* proto = m_items[i]->GetTemplate();
+ 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();
+ }
+ }
+
+ LOG_DEBUG("entities.player.items", "_RemoveAllItemMods complete.");
+}
+
+void Player::_ApplyAllItemMods()
+{
+ LOG_DEBUG("entities.player.items", "_ApplyAllItemMods start.");
+
+ for (uint8 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
+ {
+ if (m_items[i])
+ {
+ if (m_items[i]->IsBroken() || !CanUseAttackType(GetAttackBySlot(i)))
+ continue;
+
+ ItemTemplate const* proto = m_items[i]->GetTemplate();
+ 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 (uint8 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
+ {
+ if (m_items[i])
+ {
+ ItemTemplate const* proto = m_items[i]->GetTemplate();
+ 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() || !CanUseAttackType(GetAttackBySlot(i)))
+ continue;
+
+ ApplyItemEquipSpell(m_items[i], true);
+ ApplyEnchantment(m_items[i], true);
+ }
+ }
+
+ LOG_DEBUG("entities.player.items", "_ApplyAllItemMods complete.");
+}
+
+void Player::_ApplyAllLevelScaleItemMods(bool apply)
+{
+ for (uint8 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
+ {
+ if (m_items[i])
+ {
+ if (m_items[i]->IsBroken() || !CanUseAttackType(GetAttackBySlot(i)))
+ continue;
+
+ ItemTemplate const* proto = m_items[i]->GetTemplate();
+ if (!proto)
+ continue;
+
+ _ApplyItemMods(m_items[i], i, apply);
+ }
+ }
+}
+
+void Player::_ApplyAmmoBonuses()
+{
+ // check ammo
+ uint32 ammo_id = GetUInt32Value(PLAYER_AMMO_ID);
+ if (!ammo_id)
+ return;
+
+ float currentAmmoDPS;
+
+ ItemTemplate const* ammo_proto = sObjectMgr->GetItemTemplate(ammo_id);
+ if (!ammo_proto || ammo_proto->Class != ITEM_CLASS_PROJECTILE || !CheckAmmoCompatibility(ammo_proto))
+ currentAmmoDPS = 0.0f;
+ else
+ currentAmmoDPS = (ammo_proto->Damage[0].DamageMin + ammo_proto->Damage[0].DamageMax) / 2;
+
+ sScriptMgr->OnApplyAmmoBonuses(this, ammo_proto, currentAmmoDPS);
+
+ if (currentAmmoDPS == GetAmmoDPS())
+ return;
+
+ m_ammoDPS = currentAmmoDPS;
+
+ if (CanModifyStats())
+ UpdateDamagePhysical(RANGED_ATTACK);
+}
+
+bool Player::CheckAmmoCompatibility(ItemTemplate const* ammo_proto) const
+{
+ if (!ammo_proto)
+ return false;
+
+ // check ranged weapon
+ Item* weapon = GetWeaponForAttack(RANGED_ATTACK);
+ if (!weapon || weapon->IsBroken())
+ return false;
+
+ ItemTemplate const* weapon_proto = weapon->GetTemplate();
+ 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;
+}
+
+void Player::SendQuestGiverStatusMultiple()
+{
+ uint32 count = 0;
+
+ WorldPacket data(SMSG_QUESTGIVER_STATUS_MULTIPLE, 4);
+ data << uint32(count); // placeholder
+
+ for (GuidUnorderedSet::const_iterator itr = m_clientGUIDs.begin(); itr != m_clientGUIDs.end(); ++itr)
+ {
+ uint32 questStatus = DIALOG_STATUS_NONE;
+
+ if ((*itr).IsAnyTypeCreature())
+ {
+ // need also pet quests case support
+ Creature* questgiver = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, *itr);
+ if (!questgiver || questgiver->IsHostileTo(this))
+ continue;
+ if (!questgiver->HasNpcFlag(UNIT_NPC_FLAG_QUESTGIVER))
+ continue;
+
+ questStatus = GetQuestDialogStatus(questgiver);
+
+ data << questgiver->GetGUID();
+ data << uint8(questStatus);
+ ++count;
+ }
+ else if ((*itr).IsGameObject())
+ {
+ GameObject* questgiver = GetMap()->GetGameObject(*itr);
+ if (!questgiver || questgiver->GetGoType() != GAMEOBJECT_TYPE_QUESTGIVER)
+ continue;
+
+ questStatus = GetQuestDialogStatus(questgiver);
+
+ data << questgiver->GetGUID();
+ data << uint8(questStatus);
+ ++count;
+ }
+ }
+
+ data.put(0, count); // write real count
+ GetSession()->SendPacket(&data);
+}
+
+/* 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)
+{
+ // Xinef: If player is not in battleground and not in wintergrasp
+ if (!GetBattlegroundId() && GetZoneId() != AREA_WINTERGRASP)
+ return;
+
+ // If not released spirit, do it !
+ if (m_deathTimer > 0)
+ {
+ m_deathTimer = 0;
+ BuildPlayerRepop();
+ RepopAtGraveyard();
+ }
+
+ _corpseLocation.WorldRelocate();
+
+ // 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 = GetMap()->ConvertCorpseToBones(GetGUID(), 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->GetGUID(), LOOT_INSIGNIA);
+}
+
+void Player::SendLootRelease(ObjectGuid guid)
+{
+ WorldPacket data(SMSG_LOOT_RELEASE_RESPONSE, (8 + 1));
+ data << guid << uint8(1);
+ SendDirectMessage(&data);
+}
+
+void Player::SendLoot(ObjectGuid guid, LootType loot_type)
+{
+ if (ObjectGuid lguid = GetLootGUID())
+ m_session->DoLootRelease(lguid);
+
+ Loot* loot = 0;
+ PermissionTypes permission = ALL_PERMISSION;
+
+ LOG_DEBUG("loot", "Player::SendLoot");
+
+ // remove FD and invisibility at all loots
+ constexpr std::array toRemove = {SPELL_AURA_MOD_INVISIBILITY, SPELL_AURA_FEIGN_DEATH};
+ for (const auto& aura : toRemove)
+ {
+ RemoveAurasByType(aura);
+ }
+ // remove stealth only if looting a corpse
+ if (loot_type == LOOT_CORPSE && !guid.IsItem())
+ {
+ RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
+ }
+
+ if (guid.IsGameObject())
+ {
+ LOG_DEBUG("loot", "guid.IsGameObject");
+ 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_JUNK) || go->GetOwnerGUID() != GetGUID()) && !go->IsWithinDistInMap(this)) || (loot_type == LOOT_CORPSE && go->GetRespawnTime() && go->isSpawnedByDefault()))
+ {
+ go->ForceValuesUpdateAtIndex(GAMEOBJECT_BYTES_1);
+ SendLootRelease(guid);
+ return;
+ }
+
+ loot = &go->loot;
+
+ // Xinef: loot was generated and respawntime has passed since then, allow to recreate loot
+ // Xinef: to avoid bugs, this rule covers spawned gameobjects only
+ if (go->isSpawnedByDefault() && go->getLootState() == GO_ACTIVATED && !go->loot.isLooted() && go->GetLootGenerationTime() + go->GetRespawnDelay() < GameTime::GetGameTime().count())
+ go->SetLootState(GO_READY);
+
+ if (go->getLootState() == GO_READY)
+ {
+ uint32 lootid = go->GetGOInfo()->GetLootId();
+
+ //TODO: fix this big hack
+ if ((go->GetEntry() == BG_AV_OBJECTID_MINE_N || go->GetEntry() == BG_AV_OBJECTID_MINE_S))
+ if (Battleground* bg = GetBattleground())
+ if (bg->GetBgTypeID(true) == BATTLEGROUND_AV)
+ if (!bg->ToBattlegroundAV()->PlayerCanDoMineQuest(go->GetEntry(), GetTeamId()))
+ {
+ go->ForceValuesUpdateAtIndex(GAMEOBJECT_BYTES_1);
+ SendLootRelease(guid);
+ return;
+ }
+
+ if (lootid)
+ {
+ loot->clear();
+
+ Group* group = GetGroup();
+ bool groupRules = (group && go->GetGOInfo()->type == GAMEOBJECT_TYPE_CHEST && go->GetGOInfo()->chest.groupLootRules);
+
+ // check current RR player and get next if necessary
+ if (groupRules)
+ group->UpdateLooterGuid(go, true);
+
+ loot->FillLoot(lootid, LootTemplates_Gameobject, this, !groupRules, false, go->GetLootMode(), go);
+ go->SetLootGenerationTime();
+
+ // get next RR player (for next loot)
+ if (groupRules && !go->loot.empty())
+ group->UpdateLooterGuid(go);
+ }
+ if (GameObjectTemplateAddon const* addon = go->GetTemplateAddon())
+ loot->generateMoneyLoot(addon->mingold, addon->maxgold);
+
+ if (loot_type == LOOT_FISHING)
+ go->GetFishLoot(loot, this);
+ else if (loot_type == LOOT_FISHING_JUNK)
+ go->GetFishLootJunk(loot, this);
+
+ if (go->GetGOInfo()->type == GAMEOBJECT_TYPE_CHEST && go->GetGOInfo()->chest.groupLootRules)
+ {
+ if (Group* group = GetGroup())
+ {
+ switch (group->GetLootMethod())
+ {
+ case GROUP_LOOT:
+ // GroupLoot: rolls items over threshold. Items with quality < threshold, round robin
+ group->GroupLoot(loot, go);
+ break;
+ case NEED_BEFORE_GREED:
+ group->NeedBeforeGreed(loot, go);
+ break;
+ case MASTER_LOOT:
+ group->MasterLoot(loot, go);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ go->SetLootState(GO_ACTIVATED, this);
+ }
+
+ if (go->getLootState() == GO_ACTIVATED)
+ {
+ if (Group* group = GetGroup())
+ {
+ switch (group->GetLootMethod())
+ {
+ case MASTER_LOOT:
+ permission = group->GetMasterLooterGuid() == GetGUID() ? MASTER_PERMISSION : RESTRICTED_PERMISSION;
+ break;
+ case FREE_FOR_ALL:
+ permission = ALL_PERMISSION;
+ break;
+ case ROUND_ROBIN:
+ permission = ROUND_ROBIN_PERMISSION;
+ break;
+ default:
+ permission = GROUP_PERMISSION;
+ break;
+ }
+ }
+ else
+ permission = ALL_PERMISSION;
+ }
+ }
+ else if (guid.IsItem())
+ {
+ Item* item = GetItemByGuid(guid);
+
+ if (!item)
+ {
+ SendLootRelease(guid);
+ return;
+ }
+
+ permission = OWNER_PERMISSION;
+
+ loot = &item->loot;
+
+ // Xinef: Store container id
+ loot->containerGUID = item->GetGUID();
+
+ if (!item->m_lootGenerated && !sLootItemStorage->LoadStoredLoot(item, this))
+ {
+ item->m_lootGenerated = true;
+ loot->clear();
+
+ switch (loot_type)
+ {
+ case LOOT_DISENCHANTING:
+ loot->FillLoot(item->GetTemplate()->DisenchantID, LootTemplates_Disenchant, this, true);
+ break;
+ case LOOT_PROSPECTING:
+ loot->FillLoot(item->GetEntry(), LootTemplates_Prospecting, this, true);
+ break;
+ case LOOT_MILLING:
+ loot->FillLoot(item->GetEntry(), LootTemplates_Milling, this, true);
+ break;
+ default:
+ loot->generateMoneyLoot(item->GetTemplate()->MinMoneyLoot, item->GetTemplate()->MaxMoneyLoot);
+ loot->FillLoot(item->GetEntry(), LootTemplates_Item, this, true, loot->gold != 0);
+
+ // Xinef: Add to storage
+ if (loot->gold > 0 || loot->unlootedCount > 0)
+ sLootItemStorage->AddNewStoredLoot(loot, this);
+
+ break;
+ }
+ }
+ }
+ else if (guid.IsCorpse()) // remove insignia
+ {
+ Corpse* bones = ObjectAccessor::GetCorpse(*this, guid);
+
+ if (!bones || !(loot_type == LOOT_CORPSE || loot_type == LOOT_INSIGNIA) || bones->GetType() != CORPSE_BONES || !bones->HasFlag(CORPSE_FIELD_DYNAMIC_FLAGS, CORPSE_DYNFLAG_LOOTABLE))
+ {
+ SendLootRelease(guid);
+ return;
+ }
+
+ loot = &bones->loot;
+
+ if (loot->loot_type == LOOT_NONE)
+ {
+ uint32 pLevel = bones->loot.gold;
+ bones->loot.clear();
+
+ loot->FillLoot(GetTeamId(), LootTemplates_Player, this, true);
+
+ // 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->getRate(RATE_DROP_MONEY));
+ }
+
+ if (bones->lootRecipient != this)
+ permission = NONE_PERMISSION;
+ else
+ permission = OWNER_PERMISSION;
+ }
+ else
+ {
+ 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 (!loot || loot->loot_type != LOOT_PICKPOCKETING)
+ {
+ if (creature->CanGeneratePickPocketLoot())
+ {
+ creature->SetPickPocketLootTime();
+ loot->clear();
+
+ if (uint32 lootid = creature->GetCreatureTemplate()->pickpocketLootId)
+ loot->FillLoot(lootid, LootTemplates_Pickpocketing, this, true);
+
+ // 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->getRate(RATE_DROP_MONEY));
+ permission = OWNER_PERMISSION;
+ }
+ else
+ {
+ permission = NONE_PERMISSION;
+ SendLootError(guid, LOOT_ERROR_ALREADY_PICKPOCKETED);
+ return;
+ }
+ }
+ }
+ else
+ {
+ // Xinef: Exploit fix
+ if (!creature->HasDynamicFlag(UNIT_DYNFLAG_LOOTABLE))
+ {
+ SendLootError(guid, LOOT_ERROR_DIDNT_KILL);
+ return;
+ }
+
+ // the player whose group may loot the corpse
+ Player* recipient = creature->GetLootRecipient();
+ Group* recipientGroup = creature->GetLootRecipientGroup();
+ if (!recipient && !recipientGroup)
+ return;
+
+ if (loot->loot_type == LOOT_NONE)
+ {
+ // for creature, loot is filled when creature is killed.
+ if (recipientGroup)
+ {
+ switch (recipientGroup->GetLootMethod())
+ {
+ case GROUP_LOOT:
+ // GroupLoot: rolls items over threshold. Items with quality < threshold, round robin
+ recipientGroup->GroupLoot(loot, creature);
+ break;
+ case NEED_BEFORE_GREED:
+ recipientGroup->NeedBeforeGreed(loot, creature);
+ break;
+ case MASTER_LOOT:
+ recipientGroup->MasterLoot(loot, creature);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // if loot is already skinning loot then don't do anything else
+ if (loot->loot_type == LOOT_SKINNING)
+ {
+ loot_type = LOOT_SKINNING;
+ permission = creature->GetLootRecipientGUID() == GetGUID() ? OWNER_PERMISSION : NONE_PERMISSION;
+ }
+ else if (loot_type == LOOT_SKINNING)
+ {
+ loot->clear();
+ loot->FillLoot(creature->GetCreatureTemplate()->SkinLootId, LootTemplates_Skinning, this, true);
+ permission = OWNER_PERMISSION;
+
+ //Inform instance if creature is skinned.
+ if (InstanceScript* mapInstance = creature->GetInstanceScript())
+ {
+ mapInstance->CreatureLooted(creature, LOOT_SKINNING);
+ }
+
+ // Xinef: Set new loot recipient
+ creature->SetLootRecipient(this, false);
+ }
+ // set group rights only for loot_type != LOOT_SKINNING
+ else
+ {
+ if (recipientGroup)
+ {
+ if (GetGroup() == recipientGroup)
+ {
+ switch (recipientGroup->GetLootMethod())
+ {
+ case MASTER_LOOT:
+ permission = recipientGroup->GetMasterLooterGuid() == GetGUID() ? MASTER_PERMISSION : RESTRICTED_PERMISSION;
+ break;
+ case FREE_FOR_ALL:
+ permission = ALL_PERMISSION;
+ break;
+ case ROUND_ROBIN:
+ permission = ROUND_ROBIN_PERMISSION;
+ break;
+ default:
+ permission = GROUP_PERMISSION;
+ break;
+ }
+ }
+ else
+ permission = NONE_PERMISSION;
+ }
+ else if (recipient == this)
+ permission = OWNER_PERMISSION;
+ else
+ permission = NONE_PERMISSION;
+ }
+ }
+ }
+
+ // LOOT_INSIGNIA and LOOT_FISHINGHOLE unsupported by client
+ switch (loot_type)
+ {
+ case LOOT_INSIGNIA:
+ loot_type = LOOT_SKINNING;
+ break;
+ case LOOT_FISHINGHOLE:
+ loot_type = LOOT_FISHING;
+ break;
+ case LOOT_FISHING_JUNK:
+ loot_type = LOOT_FISHING;
+ break;
+ default:
+ break;
+ }
+
+ // need know merged fishing/corpse loot type for achievements
+ loot->loot_type = loot_type;
+
+ if (permission != NONE_PERMISSION)
+ {
+ SetLootGUID(guid);
+
+ WorldPacket data(SMSG_LOOT_RESPONSE, (9 + 50)); // we guess size
+ data << guid;
+ data << uint8(loot_type);
+ data << LootView(*loot, this, permission);
+
+ SendDirectMessage(&data);
+
+ // add 'this' player as one of the players that are looting 'loot'
+ loot->AddLooter(GetGUID());
+
+ if (loot_type == LOOT_CORPSE && !guid.IsItem())
+ SetUnitFlag(UNIT_FLAG_LOOTING);
+ }
+ else
+ SendLootError(guid, LOOT_ERROR_DIDNT_KILL);
+}
+
+void Player::SendLootError(ObjectGuid guid, LootError error)
+{
+ WorldPacket data(SMSG_LOOT_RESPONSE, 10);
+ data << guid;
+ data << uint8(LOOT_NONE);
+ data << uint8(error);
+ SendDirectMessage(&data);
+}
+
+void Player::SendNotifyLootMoneyRemoved()
+{
+ WorldPacket data(SMSG_LOOT_CLEAR_MONEY, 0);
+ GetSession()->SendPacket(&data);
+}
+
+void Player::SendNotifyLootItemRemoved(uint8 lootSlot)
+{
+ WorldPacket data(SMSG_LOOT_REMOVED, 1);
+ data << uint8(lootSlot);
+ GetSession()->SendPacket(&data);
+}
+
+void Player::SendInitWorldStates(uint32 zoneid, uint32 areaid)
+{
+ // data depends on zoneid/mapid...
+ Battleground* bg = GetBattleground();
+ uint32 mapid = GetMapId();
+ OutdoorPvP* pvp = sOutdoorPvPMgr->GetOutdoorPvPToZoneId(zoneid);
+ InstanceScript* instance = GetInstanceScript();
+ Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(zoneid);
+
+ LOG_DEBUG("network", "Sending SMSG_INIT_WORLD_STATES to Map: {}, Zone: {}", mapid, zoneid);
+
+ WorldPacket data(SMSG_INIT_WORLD_STATES, (4 + 4 + 4 + 2 + (12 * 8)));
+ data << uint32(mapid); // mapid
+ data << uint32(zoneid); // zone id
+ data << uint32(areaid); // area id, new 2.1.0
+ size_t countPos = data.wpos();
+ data << uint16(0); // count of uint64 blocks
+ data << uint32(0x8d8) << uint32(0x0); // 1
+ data << uint32(0x8d7) << uint32(0x0); // 2
+ data << uint32(0x8d6) << uint32(0x0); // 3
+ data << uint32(0x8d5) << uint32(0x0); // 4
+ data << uint32(0x8d4) << uint32(0x0); // 5
+ data << uint32(0x8d3) << uint32(0x0); // 6
+ // 7 1 - Arena season in progress, 0 - end of season
+ data << uint32(0xC77) << uint32(sWorld->getBoolConfig(CONFIG_ARENA_SEASON_IN_PROGRESS));
+ // 8 Arena season id
+ data << uint32(0xF3D) << uint32(sWorld->getIntConfig(CONFIG_ARENA_SEASON_ID));
+
+ if (mapid == 530) // Outland
+ {
+ data << uint32(0x9bf) << uint32(0x0); // 7
+ data << uint32(0x9bd) << uint32(0xF); // 8
+ data << uint32(0x9bb) << uint32(0xF); // 9
+ }
+
+ if (Player::bgZoneIdToFillWorldStates.find(zoneid) != Player::bgZoneIdToFillWorldStates.end())
+ {
+ Player::bgZoneIdToFillWorldStates[zoneid](bg, data);
+ }
+ else
+ {
+ // insert
+ 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 (pvp && pvp->GetTypeId() == OUTDOOR_PVP_EP)
+ pvp->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(0x97a) << uint32(0x0); // 10 2426
+ data << uint32(0x917) << uint32(0x0); // 11 2327
+ data << uint32(0x918) << uint32(0x0); // 12 2328
+ data << uint32(0x97b) << uint32(0x32); // 13 2427
+ data << uint32(0x97c) << uint32(0x32); // 14 2428
+ data << uint32(0x933) << uint32(0x1); // 15 2355
+ data << uint32(0x946) << uint32(0x0); // 16 2374
+ data << uint32(0x947) << uint32(0x0); // 17 2375
+ data << uint32(0x948) << uint32(0x0); // 18 2376
+ data << uint32(0x949) << uint32(0x0); // 19 2377
+ data << uint32(0x94a) << uint32(0x0); // 20 2378
+ data << uint32(0x94b) << uint32(0x0); // 21 2379
+ data << uint32(0x932) << uint32(0x0); // 22 2354
+ data << uint32(0x934) << uint32(0x0); // 23 2356
+ data << uint32(0x935) << uint32(0x0); // 24 2357
+ data << uint32(0x936) << uint32(0x0); // 25 2358
+ data << uint32(0x937) << uint32(0x0); // 26 2359
+ data << uint32(0x938) << uint32(0x0); // 27 2360
+ data << uint32(0x939) << uint32(0x1); // 28 2361
+ data << uint32(0x930) << uint32(0x1); // 29 2352
+ data << uint32(0x93a) << uint32(0x0); // 30 2362
+ data << uint32(0x93b) << uint32(0x0); // 31 2363
+ data << uint32(0x93c) << uint32(0x0); // 32 2364
+ data << uint32(0x93d) << uint32(0x0); // 33 2365
+ data << uint32(0x944) << uint32(0x0); // 34 2372
+ data << uint32(0x945) << uint32(0x0); // 35 2373
+ data << uint32(0x931) << uint32(0x1); // 36 2353
+ data << uint32(0x93e) << uint32(0x0); // 37 2366
+ data << uint32(0x931) << uint32(0x1); // 38 2367 ?? grey horde not in dbc! send for consistency's sake, and to match field count
+ data << uint32(0x940) << uint32(0x0); // 39 2368
+ data << uint32(0x941) << uint32(0x0); // 7 2369
+ data << uint32(0x942) << uint32(0x0); // 8 2370
+ data << uint32(0x943) << uint32(0x0); // 9 2371
+ }
+ break;
+ case 1377: // Silithus
+ if (pvp && pvp->GetTypeId() == OUTDOOR_PVP_SI)
+ pvp->FillInitialWorldStates(data);
+ else
+ {
+ // states are always shown
+ data << uint32(2313) << uint32(0x0); // 7 ally silityst gathered
+ data << uint32(2314) << uint32(0x0); // 8 horde silityst gathered
+ data << uint32(2317) << uint32(0x0); // 9 max silithyst
+ }
+ // dunno about these... aq opening event maybe?
+ data << uint32(2322) << uint32(0x0); // 10 sandworm N
+ data << uint32(2323) << uint32(0x0); // 11 sandworm S
+ data << uint32(2324) << uint32(0x0); // 12 sandworm SW
+ data << uint32(2325) << uint32(0x0); // 13 sandworm E
+ break;
+ case 2597: // Alterac Valley
+ if (bg && bg->GetBgTypeID(true) == BATTLEGROUND_AV)
+ bg->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(0x7ae) << uint32(0x1); // 7 snowfall n
+ data << uint32(0x532) << uint32(0x1); // 8 frostwolfhut hc
+ data << uint32(0x531) << uint32(0x0); // 9 frostwolfhut ac
+ data << uint32(0x52e) << uint32(0x0); // 10 stormpike firstaid a_a
+ data << uint32(0x571) << uint32(0x0); // 11 east frostwolf tower horde assaulted -unused
+ data << uint32(0x570) << uint32(0x0); // 12 west frostwolf tower horde assaulted - unused
+ data << uint32(0x567) << uint32(0x1); // 13 frostwolfe c
+ data << uint32(0x566) << uint32(0x1); // 14 frostwolfw c
+ data << uint32(0x550) << uint32(0x1); // 15 irondeep (N) ally
+ data << uint32(0x544) << uint32(0x0); // 16 ice grave a_a
+ data << uint32(0x536) << uint32(0x0); // 17 stormpike grave h_c
+ data << uint32(0x535) << uint32(0x1); // 18 stormpike grave a_c
+ data << uint32(0x518) << uint32(0x0); // 19 stoneheart grave a_a
+ data << uint32(0x517) << uint32(0x0); // 20 stoneheart grave h_a
+ data << uint32(0x574) << uint32(0x0); // 21 1396 unk
+ data << uint32(0x573) << uint32(0x0); // 22 iceblood tower horde assaulted -unused
+ data << uint32(0x572) << uint32(0x0); // 23 towerpoint horde assaulted - unused
+ data << uint32(0x56f) << uint32(0x0); // 24 1391 unk
+ data << uint32(0x56e) << uint32(0x0); // 25 iceblood a
+ data << uint32(0x56d) << uint32(0x0); // 26 towerp a
+ data << uint32(0x56c) << uint32(0x0); // 27 frostwolfe a
+ data << uint32(0x56b) << uint32(0x0); // 28 froswolfw a
+ data << uint32(0x56a) << uint32(0x1); // 29 1386 unk
+ data << uint32(0x569) << uint32(0x1); // 30 iceblood c
+ data << uint32(0x568) << uint32(0x1); // 31 towerp c
+ data << uint32(0x565) << uint32(0x0); // 32 stoneh tower a
+ data << uint32(0x564) << uint32(0x0); // 33 icewing tower a
+ data << uint32(0x563) << uint32(0x0); // 34 dunn a
+ data << uint32(0x562) << uint32(0x0); // 35 duns a
+ data << uint32(0x561) << uint32(0x0); // 36 stoneheart bunker alliance assaulted - unused
+ data << uint32(0x560) << uint32(0x0); // 37 icewing bunker alliance assaulted - unused
+ data << uint32(0x55f) << uint32(0x0); // 38 dunbaldar south alliance assaulted - unused
+ data << uint32(0x55e) << uint32(0x0); // 39 dunbaldar north alliance assaulted - unused
+ data << uint32(0x55d) << uint32(0x0); // 40 stone tower d
+ data << uint32(0x3c6) << uint32(0x0); // 41 966 unk
+ data << uint32(0x3c4) << uint32(0x0); // 42 964 unk
+ data << uint32(0x3c2) << uint32(0x0); // 43 962 unk
+ data << uint32(0x516) << uint32(0x1); // 44 stoneheart grave a_c
+ data << uint32(0x515) << uint32(0x0); // 45 stonheart grave h_c
+ data << uint32(0x3b6) << uint32(0x0); // 46 950 unk
+ data << uint32(0x55c) << uint32(0x0); // 47 icewing tower d
+ data << uint32(0x55b) << uint32(0x0); // 48 dunn d
+ data << uint32(0x55a) << uint32(0x0); // 49 duns d
+ data << uint32(0x559) << uint32(0x0); // 50 1369 unk
+ data << uint32(0x558) << uint32(0x0); // 51 iceblood d
+ data << uint32(0x557) << uint32(0x0); // 52 towerp d
+ data << uint32(0x556) << uint32(0x0); // 53 frostwolfe d
+ data << uint32(0x555) << uint32(0x0); // 54 frostwolfw d
+ data << uint32(0x554) << uint32(0x1); // 55 stoneh tower c
+ data << uint32(0x553) << uint32(0x1); // 56 icewing tower c
+ data << uint32(0x552) << uint32(0x1); // 57 dunn c
+ data << uint32(0x551) << uint32(0x1); // 58 duns c
+ data << uint32(0x54f) << uint32(0x0); // 59 irondeep (N) horde
+ data << uint32(0x54e) << uint32(0x0); // 60 irondeep (N) ally
+ data << uint32(0x54d) << uint32(0x1); // 61 mine (S) neutral
+ data << uint32(0x54c) << uint32(0x0); // 62 mine (S) horde
+ data << uint32(0x54b) << uint32(0x0); // 63 mine (S) ally
+ data << uint32(0x545) << uint32(0x0); // 64 iceblood h_a
+ data << uint32(0x543) << uint32(0x1); // 65 iceblod h_c
+ data << uint32(0x542) << uint32(0x0); // 66 iceblood a_c
+ data << uint32(0x540) << uint32(0x0); // 67 snowfall h_a
+ data << uint32(0x53f) << uint32(0x0); // 68 snowfall a_a
+ data << uint32(0x53e) << uint32(0x0); // 69 snowfall h_c
+ data << uint32(0x53d) << uint32(0x0); // 70 snowfall a_c
+ data << uint32(0x53c) << uint32(0x0); // 71 frostwolf g h_a
+ data << uint32(0x53b) << uint32(0x0); // 72 frostwolf g a_a
+ data << uint32(0x53a) << uint32(0x1); // 73 frostwolf g h_c
+ data << uint32(0x539) << uint32(0x0); // 74 frostwolf g a_c
+ data << uint32(0x538) << uint32(0x0); // 75 stormpike grave h_a
+ data << uint32(0x537) << uint32(0x0); // 76 stormpike grave a_a
+ data << uint32(0x534) << uint32(0x0); // 77 frostwolf hut h_a
+ data << uint32(0x533) << uint32(0x0); // 78 frostwolf hut a_a
+ data << uint32(0x530) << uint32(0x0); // 79 stormpike first aid h_a
+ data << uint32(0x52f) << uint32(0x0); // 80 stormpike first aid h_c
+ data << uint32(0x52d) << uint32(0x1); // 81 stormpike first aid a_c
+ }
+ break;
+ case 3277: // Warsong Gulch
+ if (bg && bg->GetBgTypeID(true) == BATTLEGROUND_WS)
+ bg->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(0x62d) << uint32(0x0); // 7 1581 alliance flag captures
+ data << uint32(0x62e) << uint32(0x0); // 8 1582 horde flag captures
+ data << uint32(0x609) << uint32(0x0); // 9 1545 unk, set to 1 on alliance flag pickup...
+ data << uint32(0x60a) << uint32(0x0); // 10 1546 unk, set to 1 on horde flag pickup, after drop it's -1
+ data << uint32(0x60b) << uint32(0x2); // 11 1547 unk
+ data << uint32(0x641) << uint32(0x3); // 12 1601 unk (max flag captures?)
+ data << uint32(0x922) << uint32(0x1); // 13 2338 horde (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing)
+ data << uint32(0x923) << uint32(0x1); // 14 2339 alliance (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing)
+ }
+ break;
+ case 3358: // Arathi Basin
+ if (bg && bg->GetBgTypeID(true) == BATTLEGROUND_AB)
+ bg->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(0x6e7) << uint32(0x0); // 7 1767 stables alliance
+ data << uint32(0x6e8) << uint32(0x0); // 8 1768 stables horde
+ data << uint32(0x6e9) << uint32(0x0); // 9 1769 unk, ST?
+ data << uint32(0x6ea) << uint32(0x0); // 10 1770 stables (show/hide)
+ data << uint32(0x6ec) << uint32(0x0); // 11 1772 farm (0 - horde controlled, 1 - alliance controlled)
+ data << uint32(0x6ed) << uint32(0x0); // 12 1773 farm (show/hide)
+ data << uint32(0x6ee) << uint32(0x0); // 13 1774 farm color
+ data << uint32(0x6ef) << uint32(0x0); // 14 1775 gold mine color, may be FM?
+ data << uint32(0x6f0) << uint32(0x0); // 15 1776 alliance resources
+ data << uint32(0x6f1) << uint32(0x0); // 16 1777 horde resources
+ data << uint32(0x6f2) << uint32(0x0); // 17 1778 horde bases
+ data << uint32(0x6f3) << uint32(0x0); // 18 1779 alliance bases
+ data << uint32(0x6f4) << uint32(0x640); // 19 1780 max resources (1600)
+ data << uint32(0x6f6) << uint32(0x0); // 20 1782 blacksmith color
+ data << uint32(0x6f7) << uint32(0x0); // 21 1783 blacksmith (show/hide)
+ data << uint32(0x6f8) << uint32(0x0); // 22 1784 unk, bs?
+ data << uint32(0x6f9) << uint32(0x0); // 23 1785 unk, bs?
+ data << uint32(0x6fb) << uint32(0x0); // 24 1787 gold mine (0 - horde contr, 1 - alliance contr)
+ data << uint32(0x6fc) << uint32(0x0); // 25 1788 gold mine (0 - conflict, 1 - horde)
+ data << uint32(0x6fd) << uint32(0x0); // 26 1789 gold mine (1 - show/0 - hide)
+ data << uint32(0x6fe) << uint32(0x0); // 27 1790 gold mine color
+ data << uint32(0x700) << uint32(0x0); // 28 1792 gold mine color, may be LM?
+ data << uint32(0x701) << uint32(0x0); // 29 1793 lumber mill color (0 - conflict, 1 - horde contr)
+ data << uint32(0x702) << uint32(0x0); // 30 1794 lumber mill (show/hide)
+ data << uint32(0x703) << uint32(0x0); // 31 1795 lumber mill color color
+ data << uint32(0x732) << uint32(0x1); // 32 1842 stables (1 - uncontrolled)
+ data << uint32(0x733) << uint32(0x1); // 33 1843 gold mine (1 - uncontrolled)
+ data << uint32(0x734) << uint32(0x1); // 34 1844 lumber mill (1 - uncontrolled)
+ data << uint32(0x735) << uint32(0x1); // 35 1845 farm (1 - uncontrolled)
+ data << uint32(0x736) << uint32(0x1); // 36 1846 blacksmith (1 - uncontrolled)
+ data << uint32(0x745) << uint32(0x2); // 37 1861 unk
+ data << uint32(0x7a3) << uint32(0x578); // 38 1955 warning limit (1400)
+ }
+ break;
+ case 3820: // Eye of the Storm
+ if (bg && bg->GetBgTypeID(true) == BATTLEGROUND_EY)
+ bg->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(0xac1) << uint32(0x0); // 7 2753 Horde Bases
+ data << uint32(0xac0) << uint32(0x0); // 8 2752 Alliance Bases
+ data << uint32(0xab6) << uint32(0x0); // 9 2742 Mage Tower - Horde conflict
+ data << uint32(0xab5) << uint32(0x0); // 10 2741 Mage Tower - Alliance conflict
+ data << uint32(0xab4) << uint32(0x0); // 11 2740 Fel Reaver - Horde conflict
+ data << uint32(0xab3) << uint32(0x0); // 12 2739 Fel Reaver - Alliance conflict
+ data << uint32(0xab2) << uint32(0x0); // 13 2738 Draenei - Alliance conflict
+ data << uint32(0xab1) << uint32(0x0); // 14 2737 Draenei - Horde conflict
+ data << uint32(0xab0) << uint32(0x0); // 15 2736 unk // 0 at start
+ data << uint32(0xaaf) << uint32(0x0); // 16 2735 unk // 0 at start
+ data << uint32(0xaad) << uint32(0x0); // 17 2733 Draenei - Horde control
+ data << uint32(0xaac) << uint32(0x0); // 18 2732 Draenei - Alliance control
+ data << uint32(0xaab) << uint32(0x1); // 19 2731 Draenei uncontrolled (1 - yes, 0 - no)
+ data << uint32(0xaaa) << uint32(0x0); // 20 2730 Mage Tower - Alliance control
+ data << uint32(0xaa9) << uint32(0x0); // 21 2729 Mage Tower - Horde control
+ data << uint32(0xaa8) << uint32(0x1); // 22 2728 Mage Tower uncontrolled (1 - yes, 0 - no)
+ data << uint32(0xaa7) << uint32(0x0); // 23 2727 Fel Reaver - Horde control
+ data << uint32(0xaa6) << uint32(0x0); // 24 2726 Fel Reaver - Alliance control
+ data << uint32(0xaa5) << uint32(0x1); // 25 2725 Fel Reaver uncontrolled (1 - yes, 0 - no)
+ data << uint32(0xaa4) << uint32(0x0); // 26 2724 Boold Elf - Horde control
+ data << uint32(0xaa3) << uint32(0x0); // 27 2723 Boold Elf - Alliance control
+ data << uint32(0xaa2) << uint32(0x1); // 28 2722 Boold Elf uncontrolled (1 - yes, 0 - no)
+ data << uint32(0xac5) << uint32(0x1); // 29 2757 Flag (1 - show, 0 - hide) - doesn't work exactly this way!
+ data << uint32(0xad2) << uint32(0x1); // 30 2770 Horde top-stats (1 - show, 0 - hide) // 02 -> horde picked up the flag
+ data << uint32(0xad1) << uint32(0x1); // 31 2769 Alliance top-stats (1 - show, 0 - hide) // 02 -> alliance picked up the flag
+ data << uint32(0xabe) << uint32(0x0); // 32 2750 Horde resources
+ data << uint32(0xabd) << uint32(0x0); // 33 2749 Alliance resources
+ data << uint32(0xa05) << uint32(0x8e); // 34 2565 unk, constant?
+ data << uint32(0xaa0) << uint32(0x0); // 35 2720 Capturing progress-bar (100 -> empty (only grey), 0 -> blue|red (no grey), default 0)
+ data << uint32(0xa9f) << uint32(0x0); // 36 2719 Capturing progress-bar (0 - left, 100 - right)
+ data << uint32(0xa9e) << uint32(0x0); // 37 2718 Capturing progress-bar (1 - show, 0 - hide)
+ data << uint32(0xc0d) << uint32(0x17b); // 38 3085 unk
+ // and some more ... unknown
+ }
+ break;
+ // any of these needs change! the client remembers the prev setting!
+ // ON EVERY ZONE LEAVE, RESET THE OLD ZONE'S WORLD STATE, BUT AT LEAST THE UI STUFF!
+ case 3483: // Hellfire Peninsula
+ if (pvp && pvp->GetTypeId() == OUTDOOR_PVP_HP)
+ pvp->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(0x9ba) << uint32(0x1); // 10 // add ally tower main gui icon // maybe should be sent only on login?
+ data << uint32(0x9b9) << uint32(0x1); // 11 // add horde tower main gui icon // maybe should be sent only on login?
+ data << uint32(0x9b5) << uint32(0x0); // 12 // show neutral broken hill icon // 2485
+ data << uint32(0x9b4) << uint32(0x1); // 13 // show icon above broken hill // 2484
+ data << uint32(0x9b3) << uint32(0x0); // 14 // show ally broken hill icon // 2483
+ data << uint32(0x9b2) << uint32(0x0); // 15 // show neutral overlook icon // 2482
+ data << uint32(0x9b1) << uint32(0x1); // 16 // show the overlook arrow // 2481
+ data << uint32(0x9b0) << uint32(0x0); // 17 // show ally overlook icon // 2480
+ data << uint32(0x9ae) << uint32(0x0); // 18 // horde pvp objectives captured // 2478
+ data << uint32(0x9ac) << uint32(0x0); // 19 // ally pvp objectives captured // 2476
+ data << uint32(2475) << uint32(100); //: ally / horde slider grey area // show only in direct vicinity!
+ data << uint32(2474) << uint32(50); //: ally / horde slider percentage, 100 for ally, 0 for horde // show only in direct vicinity!
+ data << uint32(2473) << uint32(0); //: ally / horde slider display // show only in direct vicinity!
+ data << uint32(0x9a8) << uint32(0x0); // 20 // show the neutral stadium icon // 2472
+ data << uint32(0x9a7) << uint32(0x0); // 21 // show the ally stadium icon // 2471
+ data << uint32(0x9a6) << uint32(0x1); // 22 // show the horde stadium icon // 2470
+ }
+ break;
+ case 3518: // Nagrand
+ if (pvp && pvp->GetTypeId() == OUTDOOR_PVP_NA)
+ pvp->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(2503) << uint32(0x0); // 10
+ data << uint32(2502) << uint32(0x0); // 11
+ data << uint32(2493) << uint32(0x0); // 12
+ data << uint32(2491) << uint32(0x0); // 13
+
+ data << uint32(2495) << uint32(0x0); // 14
+ data << uint32(2494) << uint32(0x0); // 15
+ data << uint32(2497) << uint32(0x0); // 16
+
+ data << uint32(2762) << uint32(0x0); // 17
+ data << uint32(2662) << uint32(0x0); // 18
+ data << uint32(2663) << uint32(0x0); // 19
+ data << uint32(2664) << uint32(0x0); // 20
+
+ data << uint32(2760) << uint32(0x0); // 21
+ data << uint32(2670) << uint32(0x0); // 22
+ data << uint32(2668) << uint32(0x0); // 23
+ data << uint32(2669) << uint32(0x0); // 24
+
+ data << uint32(2761) << uint32(0x0); // 25
+ data << uint32(2667) << uint32(0x0); // 26
+ data << uint32(2665) << uint32(0x0); // 27
+ data << uint32(2666) << uint32(0x0); // 28
+
+ data << uint32(2763) << uint32(0x0); // 29
+ data << uint32(2659) << uint32(0x0); // 30
+ data << uint32(2660) << uint32(0x0); // 31
+ data << uint32(2661) << uint32(0x0); // 32
+
+ data << uint32(2671) << uint32(0x0); // 33
+ data << uint32(2676) << uint32(0x0); // 34
+ data << uint32(2677) << uint32(0x0); // 35
+ data << uint32(2672) << uint32(0x0); // 36
+ data << uint32(2673) << uint32(0x0); // 37
+ }
+ break;
+ case 3519: // Terokkar Forest
+ if (pvp && pvp->GetTypeId() == OUTDOOR_PVP_TF)
+ pvp->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(0xa41) << uint32(0x0); // 10 // 2625 capture bar pos
+ data << uint32(0xa40) << uint32(0x14); // 11 // 2624 capture bar neutral
+ data << uint32(0xa3f) << uint32(0x0); // 12 // 2623 show capture bar
+ data << uint32(0xa3e) << uint32(0x0); // 13 // 2622 horde towers controlled
+ data << uint32(0xa3d) << uint32(0x5); // 14 // 2621 ally towers controlled
+ data << uint32(0xa3c) << uint32(0x0); // 15 // 2620 show towers controlled
+ data << uint32(0xa88) << uint32(0x0); // 16 // 2696 SE Neu
+ data << uint32(0xa87) << uint32(0x0); // 17 // SE Horde
+ data << uint32(0xa86) << uint32(0x0); // 18 // SE Ally
+ data << uint32(0xa85) << uint32(0x0); // 19 //S Neu
+ data << uint32(0xa84) << uint32(0x0); // 20 S Horde
+ data << uint32(0xa83) << uint32(0x0); // 21 S Ally
+ data << uint32(0xa82) << uint32(0x0); // 22 NE Neu
+ data << uint32(0xa81) << uint32(0x0); // 23 NE Horde
+ data << uint32(0xa80) << uint32(0x0); // 24 NE Ally
+ data << uint32(0xa7e) << uint32(0x0); // 25 // 2686 N Neu
+ data << uint32(0xa7d) << uint32(0x0); // 26 N Horde
+ data << uint32(0xa7c) << uint32(0x0); // 27 N Ally
+ data << uint32(0xa7b) << uint32(0x0); // 28 NW Ally
+ data << uint32(0xa7a) << uint32(0x0); // 29 NW Horde
+ data << uint32(0xa79) << uint32(0x0); // 30 NW Neutral
+ data << uint32(0x9d0) << uint32(0x5); // 31 // 2512 locked time remaining seconds first digit
+ data << uint32(0x9ce) << uint32(0x0); // 32 // 2510 locked time remaining seconds second digit
+ data << uint32(0x9cd) << uint32(0x0); // 33 // 2509 locked time remaining minutes
+ data << uint32(0x9cc) << uint32(0x0); // 34 // 2508 neutral locked time show
+ data << uint32(0xad0) << uint32(0x0); // 35 // 2768 horde locked time show
+ data << uint32(0xacf) << uint32(0x1); // 36 // 2767 ally locked time show
+ }
+ break;
+ case 3521: // Zangarmarsh
+ if (pvp && pvp->GetTypeId() == OUTDOOR_PVP_ZM)
+ pvp->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(0x9e1) << uint32(0x0); // 10 //2529
+ data << uint32(0x9e0) << uint32(0x0); // 11
+ data << uint32(0x9df) << uint32(0x0); // 12
+ data << uint32(0xa5d) << uint32(0x1); // 13 //2653
+ data << uint32(0xa5c) << uint32(0x0); // 14 //2652 east beacon neutral
+ data << uint32(0xa5b) << uint32(0x1); // 15 horde
+ data << uint32(0xa5a) << uint32(0x0); // 16 ally
+ data << uint32(0xa59) << uint32(0x1); // 17 // 2649 Twin spire graveyard horde 12???
+ data << uint32(0xa58) << uint32(0x0); // 18 ally 14 ???
+ data << uint32(0xa57) << uint32(0x0); // 19 neutral 7???
+ data << uint32(0xa56) << uint32(0x0); // 20 // 2646 west beacon neutral
+ data << uint32(0xa55) << uint32(0x1); // 21 horde
+ data << uint32(0xa54) << uint32(0x0); // 22 ally
+ data << uint32(0x9e7) << uint32(0x0); // 23 // 2535
+ data << uint32(0x9e6) << uint32(0x0); // 24
+ data << uint32(0x9e5) << uint32(0x0); // 25
+ data << uint32(0xa00) << uint32(0x0); // 26 // 2560
+ data << uint32(0x9ff) << uint32(0x1); // 27
+ data << uint32(0x9fe) << uint32(0x0); // 28
+ data << uint32(0x9fd) << uint32(0x0); // 29
+ data << uint32(0x9fc) << uint32(0x1); // 30
+ data << uint32(0x9fb) << uint32(0x0); // 31
+ data << uint32(0xa62) << uint32(0x0); // 32 // 2658
+ data << uint32(0xa61) << uint32(0x1); // 33
+ data << uint32(0xa60) << uint32(0x1); // 34
+ data << uint32(0xa5f) << uint32(0x0); // 35
+ }
+ break;
+ case 3698: // Nagrand Arena
+ if (bg && bg->GetBgTypeID(true) == BATTLEGROUND_NA)
+ bg->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(0xa0f) << uint32(0x0); // 7
+ data << uint32(0xa10) << uint32(0x0); // 8
+ data << uint32(0xa11) << uint32(0x0); // 9 show
+ }
+ break;
+ case 3702: // Blade's Edge Arena
+ if (bg && bg->GetBgTypeID(true) == BATTLEGROUND_BE)
+ bg->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(0x9f0) << uint32(0x0); // 7 gold
+ data << uint32(0x9f1) << uint32(0x0); // 8 green
+ data << uint32(0x9f3) << uint32(0x0); // 9 show
+ }
+ break;
+ case 3968: // Ruins of Lordaeron
+ if (bg && bg->GetBgTypeID(true) == BATTLEGROUND_RL)
+ bg->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(0xbb8) << uint32(0x0); // 7 gold
+ data << uint32(0xbb9) << uint32(0x0); // 8 green
+ data << uint32(0xbba) << uint32(0x0); // 9 show
+ }
+ break;
+ case 4378: // Dalaran Sewers
+ if (bg && bg->GetBgTypeID(true) == BATTLEGROUND_DS)
+ bg->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(3601) << uint32(0x0); // 7 gold
+ data << uint32(3600) << uint32(0x0); // 8 green
+ data << uint32(3610) << uint32(0x0); // 9 show
+ }
+ break;
+ case 4384: // Strand of the Ancients
+ if (bg && bg->GetBgTypeID(true) == BATTLEGROUND_SA)
+ bg->FillInitialWorldStates(data);
+ else
+ {
+ // 1-3 A defend, 4-6 H defend, 7-9 unk defend, 1 - ok, 2 - half destroyed, 3 - destroyed
+ data << uint32(0xf09) << uint32(0x0); // 7 3849 Gate of Temple
+ data << uint32(0xe36) << uint32(0x0); // 8 3638 Gate of Yellow Moon
+ data << uint32(0xe27) << uint32(0x0); // 9 3623 Gate of Green Emerald
+ data << uint32(0xe24) << uint32(0x0); // 10 3620 Gate of Blue Sapphire
+ data << uint32(0xe21) << uint32(0x0); // 11 3617 Gate of Red Sun
+ data << uint32(0xe1e) << uint32(0x0); // 12 3614 Gate of Purple Ametyst
+
+ data << uint32(0xdf3) << uint32(0x0); // 13 3571 bonus timer (1 - on, 0 - off)
+ data << uint32(0xded) << uint32(0x0); // 14 3565 Horde Attacker
+ data << uint32(0xdec) << uint32(0x0); // 15 3564 Alliance Attacker
+ // End Round (timer), better explain this by example, eg. ends in 19:59 -> A:BC
+ data << uint32(0xde9) << uint32(0x0); // 16 3561 C
+ data << uint32(0xde8) << uint32(0x0); // 17 3560 B
+ data << uint32(0xde7) << uint32(0x0); // 18 3559 A
+ data << uint32(0xe35) << uint32(0x0); // 19 3637 East g - Horde control
+ data << uint32(0xe34) << uint32(0x0); // 20 3636 West g - Horde control
+ data << uint32(0xe33) << uint32(0x0); // 21 3635 South g - Horde control
+ data << uint32(0xe32) << uint32(0x0); // 22 3634 East g - Alliance control
+ data << uint32(0xe31) << uint32(0x0); // 23 3633 West g - Alliance control
+ data << uint32(0xe30) << uint32(0x0); // 24 3632 South g - Alliance control
+ data << uint32(0xe2f) << uint32(0x0); // 25 3631 Chamber of Ancients - Horde control
+ data << uint32(0xe2e) << uint32(0x0); // 26 3630 Chamber of Ancients - Alliance control
+ data << uint32(0xe2d) << uint32(0x0); // 27 3629 Beach1 - Horde control
+ data << uint32(0xe2c) << uint32(0x0); // 28 3628 Beach2 - Horde control
+ data << uint32(0xe2b) << uint32(0x0); // 29 3627 Beach1 - Alliance control
+ data << uint32(0xe2a) << uint32(0x0); // 30 3626 Beach2 - Alliance control
+ // and many unks...
+ }
+ break;
+ case 4406: // Ring of Valor
+ if (bg && bg->GetBgTypeID(true) == BATTLEGROUND_RV)
+ bg->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(0xe10) << uint32(0x0); // 7 gold
+ data << uint32(0xe11) << uint32(0x0); // 8 green
+ data << uint32(0xe1a) << uint32(0x0); // 9 show
+ }
+ break;
+ case 4710: // Isle of Conquest
+ if (bg && bg->GetBgTypeID(true) == BATTLEGROUND_IC)
+ bg->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(4221) << uint32(1); // 7 BG_IC_ALLIANCE_RENFORT_SET
+ data << uint32(4222) << uint32(1); // 8 BG_IC_HORDE_RENFORT_SET
+ data << uint32(4226) << uint32(300); // 9 BG_IC_ALLIANCE_RENFORT
+ data << uint32(4227) << uint32(300); // 10 BG_IC_HORDE_RENFORT
+ data << uint32(4322) << uint32(1); // 11 BG_IC_GATE_FRONT_H_WS_OPEN
+ data << uint32(4321) << uint32(1); // 12 BG_IC_GATE_WEST_H_WS_OPEN
+ data << uint32(4320) << uint32(1); // 13 BG_IC_GATE_EAST_H_WS_OPEN
+ data << uint32(4323) << uint32(1); // 14 BG_IC_GATE_FRONT_A_WS_OPEN
+ data << uint32(4324) << uint32(1); // 15 BG_IC_GATE_WEST_A_WS_OPEN
+ data << uint32(4325) << uint32(1); // 16 BG_IC_GATE_EAST_A_WS_OPEN
+ data << uint32(4317) << uint32(1); // 17 unknown
+
+ data << uint32(4301) << uint32(1); // 18 BG_IC_DOCKS_UNCONTROLLED
+ data << uint32(4296) << uint32(1); // 19 BG_IC_HANGAR_UNCONTROLLED
+ data << uint32(4306) << uint32(1); // 20 BG_IC_QUARRY_UNCONTROLLED
+ data << uint32(4311) << uint32(1); // 21 BG_IC_REFINERY_UNCONTROLLED
+ data << uint32(4294) << uint32(1); // 22 BG_IC_WORKSHOP_UNCONTROLLED
+ data << uint32(4243) << uint32(1); // 23 unknown
+ data << uint32(4345) << uint32(1); // 24 unknown
+ }
+ break;
+ // The Ruby Sanctum
+ case 4987:
+ if (instance && mapid == 724)
+ instance->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(5049) << uint32(50); // 9 WORLDSTATE_CORPOREALITY_MATERIAL
+ data << uint32(5050) << uint32(50); // 10 WORLDSTATE_CORPOREALITY_TWILIGHT
+ data << uint32(5051) << uint32(0); // 11 WORLDSTATE_CORPOREALITY_TOGGLE
+ }
+ break;
+ // Icecrown Citadel
+ case 4812:
+ if (instance && mapid == 631)
+ instance->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(4903) << uint32(0); // 9 WORLDSTATE_SHOW_TIMER (Blood Quickening weekly)
+ data << uint32(4904) << uint32(30); // 10 WORLDSTATE_EXECUTION_TIME
+ data << uint32(4940) << uint32(0); // 11 WORLDSTATE_SHOW_ATTEMPTS
+ data << uint32(4941) << uint32(50); // 12 WORLDSTATE_ATTEMPTS_REMAINING
+ data << uint32(4942) << uint32(50); // 13 WORLDSTATE_ATTEMPTS_MAX
+ }
+ break;
+ // The Culling of Stratholme
+ case 4100:
+ if (instance && mapid == 595)
+ instance->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(3479) << uint32(0); // 9 WORLDSTATE_SHOW_CRATES
+ data << uint32(3480) << uint32(0); // 10 WORLDSTATE_CRATES_REVEALED
+ data << uint32(3504) << uint32(0); // 11 WORLDSTATE_WAVE_COUNT
+ data << uint32(3931) << uint32(25); // 12 WORLDSTATE_TIME_GUARDIAN
+ data << uint32(3932) << uint32(0); // 13 WORLDSTATE_TIME_GUARDIAN_SHOW
+ }
+ break;
+ // The Oculus
+ case 4228:
+ if (instance && mapid == 578)
+ instance->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(3524) << uint32(0); // 9 WORLD_STATE_CENTRIFUGE_CONSTRUCT_SHOW
+ data << uint32(3486) << uint32(0); // 10 WORLD_STATE_CENTRIFUGE_CONSTRUCT_AMOUNT
+ }
+ break;
+ // Ulduar
+ case 4273:
+ if (instance && mapid == 603)
+ instance->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(4132) << uint32(0); // 9 WORLDSTATE_ALGALON_TIMER_ENABLED
+ data << uint32(4131) << uint32(0); // 10 WORLDSTATE_ALGALON_DESPAWN_TIMER
+ }
+ break;
+ // Halls of Refection
+ case 4820:
+ if (instance && mapid == 668)
+ instance->FillInitialWorldStates(data);
+ else
+ {
+ data << uint32(4884) << uint32(0); // 9 WORLD_STATE_HOR_WAVES_ENABLED
+ data << uint32(4882) << uint32(0); // 10 WORLD_STATE_HOR_WAVE_COUNT
+ }
+ break;
+ // Scarlet Enclave (DK starting zone)
+ case 4298:
+ // Get Mograine, GUID and ENTRY should NEVER change
+ if (Creature* mograine = ObjectAccessor::GetCreature(*this, ObjectGuid::Create(29173, 130956)))
+ {
+ if (CreatureAI* mograineAI = mograine->AI())
+ {
+ data << uint32(3590) << uint32(mograineAI->GetData(3590));
+ data << uint32(3591) << uint32(mograineAI->GetData(3591));
+ data << uint32(3592) << uint32(mograineAI->GetData(3592));
+ data << uint32(3603) << uint32(mograineAI->GetData(3603));
+ data << uint32(3604) << uint32(mograineAI->GetData(3604));
+ data << uint32(3605) << uint32(mograineAI->GetData(3605));
+ }
+ }
+ break;
+ // Wintergrasp
+ case 4197:
+ if (bf && bf->GetTypeId() == BATTLEFIELD_WG)
+ {
+ bf->FillInitialWorldStates(data);
+ break;
+ }
+ [[fallthrough]];
+ default:
+ data << uint32(0x914) << uint32(0x0); // 7
+ data << uint32(0x913) << uint32(0x0); // 8
+ data << uint32(0x912) << uint32(0x0); // 9
+ data << uint32(0x915) << uint32(0x0); // 10
+ break;
+ }
+ }
+
+ uint16 length = (data.wpos() - countPos) / 8;
+ data.put(countPos, length);
+
+ GetSession()->SendPacket(&data);
+ SendBGWeekendWorldStates();
+ SendBattlefieldWorldStates();
+}
+
+void Player::SendBGWeekendWorldStates()
+{
+ for (uint32 i = 1; i < sBattlemasterListStore.GetNumRows(); ++i)
+ {
+ BattlemasterListEntry const* bl = sBattlemasterListStore.LookupEntry(i);
+ if (bl && bl->HolidayWorldStateId)
+ {
+ if (BattlegroundMgr::IsBGWeekend((BattlegroundTypeId)bl->id))
+ SendUpdateWorldState(bl->HolidayWorldStateId, 1);
+ else
+ SendUpdateWorldState(bl->HolidayWorldStateId, 0);
+ }
+ }
+}
+
+void Player::SendBattlefieldWorldStates()
+{
+ /// Send misc stuff that needs to be sent on every login, like the battle timers.
+ if (sWorld->getIntConfig(CONFIG_WINTERGRASP_ENABLE) == 1)
+ {
+ if (BattlefieldWG* wg = (BattlefieldWG*)sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG))
+ {
+ SendUpdateWorldState(BATTLEFIELD_WG_WORLD_STATE_ATTACKER, wg->GetAttackerTeam());
+ SendUpdateWorldState(BATTLEFIELD_WG_WORLD_STATE_DEFENDER, wg->GetDefenderTeam());
+ SendUpdateWorldState(BATTLEFIELD_WG_WORLD_STATE_ACTIVE, wg->IsWarTime() ? 0 : 1); // Note: cleanup these two, their names look awkward
+ SendUpdateWorldState(BATTLEFIELD_WG_WORLD_STATE_SHOW_WORLDSTATE, wg->IsWarTime() ? 1 : 0);
+
+ for (uint32 i = 0; i < 2; ++i)
+ SendUpdateWorldState(ClockWorldState[i], uint32(GameTime::GetGameTime().count() + (wg->GetTimer() / 1000)));
+ }
+ }
+}
+
+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);
+
+ LOG_DEBUG("entities.player", "Player gain {} xp (+ {} Rested Bonus). Rested points={}", xp + rested_bonus, rested_bonus, GetRestBonus());
+ return rested_bonus;
+}
+
+void Player::SetBindPoint(ObjectGuid guid)
+{
+ WorldPacket data(SMSG_BINDER_CONFIRM, 8);
+ data << guid;
+ GetSession()->SendPacket(&data);
+}
+
+void Player::SendTalentWipeConfirm(ObjectGuid guid)
+{
+ WorldPacket data(MSG_TALENT_WIPE_CONFIRM, (8 + 4));
+ data << guid;
+ uint32 cost = sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST) ? 0 : resetTalentsCost();
+ data << cost;
+ GetSession()->SendPacket(&data);
+}
+
+void Player::ResetPetTalents()
+{
+ // This needs another gossip option + NPC text as a confirmation.
+ // The confirmation gossip listid has the text: "Yes, please do."
+ Pet* pet = GetPet();
+
+ if (!pet || pet->getPetType() != HUNTER_PET || pet->m_usedTalentCount == 0)
+ return;
+
+ CharmInfo* charmInfo = pet->GetCharmInfo();
+ if (!charmInfo)
+ {
+ LOG_ERROR("entities.player", "Object ({}) is considered pet-like but doesn't have a charminfo!", pet->GetGUID().ToString());
+ return;
+ }
+ pet->resetTalents();
+ SendTalentsInfoData(true);
+}
+
+Pet* Player::GetPet() const
+{
+ if (ObjectGuid pet_guid = GetPetGUID())
+ {
+ if (!pet_guid.IsPet())
+ return nullptr;
+
+ Pet* pet = ObjectAccessor::GetPet(*this, pet_guid);
+
+ if (!pet)
+ return nullptr;
+
+ if (IsInWorld())
+ return pet;
+
+ //there may be a guardian in slot
+ //LOG_ERROR("entities.player", "Player::GetPet: Pet {} not exist.", pet_guid.ToString());
+ //const_cast(this)->SetPetGUID(0);
+ }
+
+ return nullptr;
+}
+
+Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, Milliseconds duration /*= 0s*/, uint32 healthPct /*= 0*/)
+{
+ PetStable& petStable = GetOrInitPetStable();
+
+ Pet* pet = new Pet(this, petType);
+
+ if (petType == SUMMON_PET && pet->LoadPetFromDB(this, entry, 0, false, healthPct))
+ {
+ // Remove Demonic Sacrifice auras (known pet)
+ Unit::AuraEffectList const& auraClassScripts = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS);
+ for (Unit::AuraEffectList::const_iterator itr = auraClassScripts.begin(); itr != auraClassScripts.end();)
+ {
+ if ((*itr)->GetMiscValue() == 2228)
+ {
+ RemoveAurasDueToSpell((*itr)->GetId());
+ itr = auraClassScripts.begin();
+ }
+ else
+ ++itr;
+ }
+
+ if (duration > 0s)
+ pet->SetDuration(duration);
+
+ // Generate a new name for the newly summoned ghoul
+ if (pet->IsPetGhoul())
+ {
+ std::string new_name = sObjectMgr->GeneratePetName(entry);
+ if (!new_name.empty())
+ pet->SetName(new_name);
+ }
+
+ return nullptr;
+ }
+
+ // petentry == 0 for hunter "call pet" (current pet summoned if any)
+ if (!entry)
+ {
+ delete pet;
+ return nullptr;
+ }
+
+ pet->Relocate(x, y, z, ang);
+ if (!pet->IsPositionValid())
+ {
+ LOG_ERROR("misc", "Player::SummonPet: Pet ({}, Entry: {}) not summoned. Suggested coordinates aren't valid (X: {} Y: {})", pet->GetGUID().ToString(), pet->GetEntry(), pet->GetPositionX(), pet->GetPositionY());
+ delete pet;
+ return nullptr;
+ }
+
+ Map* map = GetMap();
+ uint32 pet_number = sObjectMgr->GeneratePetNumber();
+ if (!pet->Create(map->GenerateLowGuid(), map, GetPhaseMask(), entry, pet_number))
+ {
+ LOG_ERROR("misc", "Player::SummonPet: No such creature entry {}", entry);
+ delete pet;
+ return nullptr;
+ }
+
+ if (petType == SUMMON_PET && petStable.CurrentPet)
+ RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
+
+ pet->SetCreatorGUID(GetGUID());
+ pet->SetFaction(GetFaction());
+ pet->setPowerType(POWER_MANA);
+ pet->ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE);
+ pet->SetUInt32Value(UNIT_FIELD_BYTES_1, 0);
+ pet->InitStatsForLevel(GetLevel());
+
+ SetMinion(pet, true);
+
+ if (petType == SUMMON_PET)
+ {
+ if (pet->GetCreatureTemplate()->type == CREATURE_TYPE_DEMON || pet->GetCreatureTemplate()->type == CREATURE_TYPE_UNDEAD)
+ {
+ pet->GetCharmInfo()->SetPetNumber(pet_number, true); // Show pet details tab (Shift+P) only for demons & undead
+ }
+ else
+ {
+ pet->GetCharmInfo()->SetPetNumber(pet_number, false);
+ }
+
+ pet->SetUInt32Value(UNIT_FIELD_BYTES_0, 2048);
+ pet->SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0);
+ pet->SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, 1000);
+ pet->SetFullHealth();
+ pet->SetPower(POWER_MANA, pet->GetMaxPower(POWER_MANA));
+ pet->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(GameTime::GetGameTime().count())); // cast can't be helped in this case
+ }
+
+ map->AddToMap(pet->ToCreature(), true);
+
+ ASSERT(!petStable.CurrentPet && (petType != HUNTER_PET || !petStable.GetUnslottedHunterPet()));
+ pet->FillPetInfo(&petStable.CurrentPet.emplace());
+
+ if (petType == SUMMON_PET)
+ {
+ pet->InitPetCreateSpells();
+ pet->InitTalentForLevel();
+ pet->SavePetToDB(PET_SAVE_AS_CURRENT);
+ PetSpellInitialize();
+
+ // Remove Demonic Sacrifice auras (known pet)
+ Unit::AuraEffectList const& auraClassScripts = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS);
+ for (Unit::AuraEffectList::const_iterator itr = auraClassScripts.begin(); itr != auraClassScripts.end();)
+ {
+ if ((*itr)->GetMiscValue() == 2228)
+ {
+ RemoveAurasDueToSpell((*itr)->GetId());
+ itr = auraClassScripts.begin();
+ }
+ else
+ ++itr;
+ }
+ }
+
+ if (duration > 0s)
+ pet->SetDuration(duration);
+
+ if (NeedSendSpectatorData() && pet->GetCreatureTemplate()->family)
+ {
+ ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "PHP", (uint32)pet->GetHealthPct());
+ ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "PET", pet->GetCreatureTemplate()->family);
+ }
+
+ return pet;
+}
+
+void Player::RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent)
+{
+ if (!pet)
+ pet = GetPet();
+
+ if (pet)
+ {
+ // xinef: dont save dead pet as current, save him not in slot
+ if (!pet->IsAlive() && mode == PET_SAVE_AS_CURRENT && pet->getPetType() == HUNTER_PET)
+ {
+ mode = PET_SAVE_NOT_IN_SLOT;
+ m_temporaryUnsummonedPetNumber = 0;
+ }
+
+ LOG_DEBUG("entities.pet", "RemovePet {}, {}, {}", pet->GetEntry(), mode, returnreagent);
+ if (pet->m_removed)
+ return;
+ }
+
+ if (returnreagent && (pet || (m_temporaryUnsummonedPetNumber && (!m_session || !m_session->PlayerLogout()))) && !InBattleground())
+ {
+ //returning of reagents only for players, so best done here
+ uint32 spellId = pet ? pet->GetUInt32Value(UNIT_CREATED_BY_SPELL) : m_oldpetspell;
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+
+ if (spellInfo)
+ {
+ for (uint32 i = 0; i < MAX_SPELL_REAGENTS; ++i)
+ {
+ if (spellInfo->Reagent[i] > 0)
+ {
+ ItemPosCountVec dest; //for succubus, voidwalker, felhunter and felguard credit soulshard when despawn reason other than death (out of range, logout)
+ InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, spellInfo->Reagent[i], spellInfo->ReagentCount[i]);
+ if (msg == EQUIP_ERR_OK)
+ {
+ Item* item = StoreNewItem(dest, spellInfo->Reagent[i], true);
+ if (IsInWorld())
+ SendNewItem(item, spellInfo->ReagentCount[i], true, false);
+ }
+ }
+ }
+ }
+ m_temporaryUnsummonedPetNumber = 0;
+ }
+
+ if (!pet)
+ {
+ if (mode == PET_SAVE_NOT_IN_SLOT && m_petStable && m_petStable->CurrentPet)
+ {
+ // Handle removing pet while it is in "temporarily unsummoned" state, for example on mount
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID);
+ stmt->SetData(0, PET_SAVE_NOT_IN_SLOT);
+ stmt->SetData(1, GetGUID().GetCounter());
+ stmt->SetData(2, m_petStable->CurrentPet->PetNumber);
+ CharacterDatabase.Execute(stmt);
+
+ m_petStable->UnslottedPets.push_back(std::move(*m_petStable->CurrentPet));
+ m_petStable->CurrentPet.reset();
+ }
+
+ return;
+ }
+ else
+ {
+ pet->CombatStop();
+
+ if (returnreagent)
+ {
+ switch (pet->GetEntry())
+ {
+ //warlock pets except imp are removed(?) when logging out
+ case 1860:
+ case 1863:
+ case 417:
+ case 17252:
+ mode = PET_SAVE_NOT_IN_SLOT;
+ break;
+ }
+ }
+
+ // only if current pet in slot
+ pet->SavePetToDB(mode);
+
+ if (m_petStable->CurrentPet && m_petStable->CurrentPet->PetNumber == pet->GetCharmInfo()->GetPetNumber())
+ {
+ if (mode == PET_SAVE_NOT_IN_SLOT)
+ {
+ m_petStable->UnslottedPets.push_back(std::move(*m_petStable->CurrentPet));
+ m_petStable->CurrentPet.reset();
+ }
+ else if (mode == PET_SAVE_AS_DELETED)
+ m_petStable->CurrentPet.reset();
+ // else if (stable slots) handled in opcode handlers due to required swaps
+ // else (current pet) doesnt need to do anything
+ }
+
+ SetMinion(pet, false);
+
+ pet->AddObjectToRemoveList();
+ pet->m_removed = true;
+
+ if (pet->isControlled())
+ {
+ WorldPacket data(SMSG_PET_SPELLS, 8);
+ data << uint64(0);
+ GetSession()->SendPacket(&data);
+
+ if (GetGroup())
+ SetGroupUpdateFlag(GROUP_UPDATE_PET);
+ }
+
+ if (NeedSendSpectatorData() && pet->GetCreatureTemplate()->family)
+ {
+ ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "PHP", 0);
+ ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "PET", 0);
+ }
+ }
+}
+
+bool Player::CanPetResurrect()
+{
+ PetStable* const petStable = GetPetStable();
+ if (!petStable)
+ {
+ // No pets
+ return false;
+ }
+
+ auto const& currectPet = petStable->CurrentPet;
+ auto const& unslottedHunterPet = petStable->GetUnslottedHunterPet();
+
+ if (!currectPet && !unslottedHunterPet)
+ {
+ // No pets
+ return false;
+ }
+
+ // Check current pet
+ if (currectPet && !currectPet->Health)
+ {
+ return true;
+ }
+
+ // Check dismiss/unslotted hunter pet
+ if (unslottedHunterPet && !unslottedHunterPet->Health)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+bool Player::IsExistPet()
+{
+ PetStable* const petStable = GetPetStable();
+ return petStable && (petStable->CurrentPet || petStable->GetUnslottedHunterPet());
+}
+
+Pet* Player::CreatePet(Creature* creatureTarget, uint32 spellID /*= 0*/)
+{
+ if (IsExistPet())
+ {
+ return nullptr;
+ }
+
+ if (!creatureTarget || creatureTarget->IsPet() || creatureTarget->GetTypeId() == TYPEID_PLAYER)
+ {
+ return nullptr;
+ }
+
+ CreatureTemplate const* creatrueTemplate = sObjectMgr->GetCreatureTemplate(creatureTarget->GetEntry());
+ if (!creatrueTemplate->family)
+ {
+ // Creatures with family 0 crashes the server
+ return nullptr;
+ }
+
+ // Everything looks OK, create new pet
+ Pet* pet = CreateTamedPetFrom(creatureTarget, spellID);
+ if (!pet)
+ {
+ return nullptr;
+ }
+
+ // "kill" original creature
+ creatureTarget->DespawnOrUnsummon();
+
+ // calculate proper level
+ uint8 level = (creatureTarget->GetLevel() < (GetLevel() - 5)) ? (GetLevel() - 5) : GetLevel();
+
+ // prepare visual effect for levelup
+ pet->SetUInt32Value(UNIT_FIELD_LEVEL, level - 1);
+
+ // add to world
+ pet->GetMap()->AddToMap(pet->ToCreature());
+
+ // visual effect for levelup
+ pet->SetUInt32Value(UNIT_FIELD_LEVEL, level);
+
+ // caster have pet now
+ SetMinion(pet, true);
+
+ pet->InitTalentForLevel();
+
+ pet->SavePetToDB(PET_SAVE_AS_CURRENT);
+ PetSpellInitialize();
+
+ return pet;
+}
+
+Pet* Player::CreatePet(uint32 creatureEntry, uint32 spellID /*= 0*/)
+{
+ if (IsExistPet())
+ {
+ return nullptr;
+ }
+
+ CreatureTemplate const* creatrueTemplate = sObjectMgr->GetCreatureTemplate(creatureEntry);
+ if (!creatrueTemplate->family)
+ {
+ // Creatures with family 0 crashes the server
+ return nullptr;
+ }
+
+ // Everything looks OK, create new pet
+ Pet* pet = CreateTamedPetFrom(creatureEntry, spellID);
+ if (!pet)
+ {
+ return nullptr;
+ }
+
+ // prepare visual effect for levelup
+ pet->SetUInt32Value(UNIT_FIELD_LEVEL, GetLevel() - 1);
+
+ // add to world
+ pet->GetMap()->AddToMap(pet->ToCreature());
+
+ // visual effect for levelup
+ pet->SetUInt32Value(UNIT_FIELD_LEVEL, GetLevel());
+
+ // caster have pet now
+ SetMinion(pet, true);
+
+ pet->InitTalentForLevel();
+
+ pet->SavePetToDB(PET_SAVE_AS_CURRENT);
+ PetSpellInitialize();
+
+ return pet;
+}
+
+void Player::StopCastingCharm()
+{
+ Unit* charm = GetCharm();
+ if (!charm)
+ return;
+
+ if (charm->GetTypeId() == TYPEID_UNIT)
+ {
+ if (charm->ToCreature()->HasUnitTypeMask(UNIT_MASK_PUPPET))
+ ((Puppet*)charm)->UnSummon();
+ else if (charm->IsVehicle())
+ ExitVehicle();
+ }
+ if (GetCharmGUID())
+ charm->RemoveCharmAuras();
+
+ if (GetCharmGUID())
+ {
+ LOG_FATAL("entities.player", "Player {} ({} is not able to uncharm unit ({})", GetName(), GetGUID().ToString(), GetCharmGUID().ToString());
+ if (charm->GetCharmerGUID())
+ {
+ LOG_FATAL("entities.player", "Charmed unit has charmer {}", charm->GetCharmerGUID().ToString());
+ ABORT();
+ }
+ else
+ SetCharm(charm, false);
+ }
+}
+
+void Player::Say(std::string_view text, Language language, WorldObject const* /*= nullptr*/)
+{
+ std::string _text(text);
+ if (!sScriptMgr->CanPlayerUseChat(this, CHAT_MSG_SAY, language, _text))
+ {
+ return;
+ }
+
+ sScriptMgr->OnPlayerChat(this, CHAT_MSG_SAY, language, _text);
+
+ WorldPacket data;
+ ChatHandler::BuildChatPacket(data, CHAT_MSG_SAY, language, this, this, _text);
+ SendMessageToSetInRange(&data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), true);
+}
+
+void Player::Say(uint32 textId, WorldObject const* target /*= nullptr*/)
+{
+ Talk(textId, CHAT_MSG_SAY, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), target);
+}
+
+void Player::Yell(std::string_view text, Language language, WorldObject const* /*= nullptr*/)
+{
+ std::string _text(text);
+
+ if (!sScriptMgr->CanPlayerUseChat(this, CHAT_MSG_YELL, language, _text))
+ {
+ return;
+ }
+
+ sScriptMgr->OnPlayerChat(this, CHAT_MSG_YELL, language, _text);
+
+ WorldPacket data;
+ ChatHandler::BuildChatPacket(data, CHAT_MSG_YELL, language, this, this, _text);
+ SendMessageToSetInRange(&data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL), true);
+}
+
+void Player::Yell(uint32 textId, WorldObject const* target /*= nullptr*/)
+{
+ Talk(textId, CHAT_MSG_YELL, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL), target);
+}
+
+void Player::TextEmote(std::string_view text, WorldObject const* /*= nullptr*/, bool /*= false*/)
+{
+ std::string _text(text);
+
+ if (!sScriptMgr->CanPlayerUseChat(this, CHAT_MSG_EMOTE, LANG_UNIVERSAL, _text))
+ {
+ return;
+ }
+
+ sScriptMgr->OnPlayerChat(this, CHAT_MSG_EMOTE, LANG_UNIVERSAL, _text);
+
+ WorldPacket data;
+ ChatHandler::BuildChatPacket(data, CHAT_MSG_EMOTE, LANG_UNIVERSAL, this, this, _text);
+
+ if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_EMOTE))
+ {
+ SendMessageToSetInRange(&data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), true);
+ }
+ else
+ {
+ SendMessageToSetInRange_OwnTeam(&data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), true);
+ }
+}
+
+void Player::TextEmote(uint32 textId, WorldObject const* target /*= nullptr*/, bool /*isBossEmote = false*/)
+{
+ Talk(textId, CHAT_MSG_EMOTE, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), target);
+}
+
+void Player::Whisper(std::string_view text, Language language, Player* target, bool /*= false*/)
+{
+ ASSERT(target);
+
+ bool isAddonMessage = language == LANG_ADDON;
+
+ if (!isAddonMessage) // if not addon data
+ language = LANG_UNIVERSAL; // whispers should always be readable
+
+ std::string _text(text);
+
+ if (!sScriptMgr->CanPlayerUseChat(this, CHAT_MSG_WHISPER, language, _text, target))
+ {
+ return;
+ }
+
+ sScriptMgr->OnPlayerChat(this, CHAT_MSG_WHISPER, language, _text, target);
+
+ WorldPacket data;
+ ChatHandler::BuildChatPacket(data, CHAT_MSG_WHISPER, language, this, this, _text);
+ target->GetSession()->SendPacket(&data);
+
+ // rest stuff shouldn't happen in case of addon message
+ if (isAddonMessage)
+ return;
+
+ ChatHandler::BuildChatPacket(data, CHAT_MSG_WHISPER_INFORM, Language(language), target, target, _text);
+ GetSession()->SendPacket(&data);
+
+ if (!isAcceptWhispers() && !IsGameMaster() && !target->IsGameMaster())
+ {
+ SetAcceptWhispers(true);
+ ChatHandler(GetSession()).SendSysMessage(LANG_COMMAND_WHISPERON);
+ }
+
+ // announce afk or dnd message
+ if (target->isAFK())
+ {
+ ChatHandler(GetSession()).PSendSysMessage(LANG_PLAYER_AFK, target->GetName().c_str(), target->autoReplyMsg.c_str());
+ }
+ else if (target->isDND())
+ {
+ ChatHandler(GetSession()).PSendSysMessage(LANG_PLAYER_DND, target->GetName().c_str(), target->autoReplyMsg.c_str());
+ }
+}
+
+void Player::Whisper(uint32 textId, Player* target, bool /*isBossWhisper = false*/)
+{
+ if (!target)
+ {
+ return;
+ }
+
+ BroadcastText const* bct = sObjectMgr->GetBroadcastText(textId);
+ if (!bct)
+ {
+ LOG_ERROR("entities.unit", "Player::Whisper: `broadcast_text` was not {} found", textId);
+ return;
+ }
+
+ LocaleConstant locale = target->GetSession()->GetSessionDbLocaleIndex();
+ WorldPacket data;
+ ChatHandler::BuildChatPacket(data, CHAT_MSG_WHISPER, LANG_UNIVERSAL, this, target, bct->GetText(locale, getGender()), 0, "", locale);
+ target->SendDirectMessage(&data);
+}
+
+void Player::PetSpellInitialize()
+{
+ Pet* pet = GetPet();
+
+ if (!pet)
+ return;
+
+ LOG_DEBUG("entities.pet", "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->GetGUID();
+ data << uint16(pet->GetCreatureTemplate()->family); // creature family (required for pet talents)
+ data << uint32(pet->GetDuration().count());
+ data << uint8(pet->GetReactState());
+ data << uint8(charmInfo->GetCommandState());
+ data << uint16(0); // Flags, mostly unknown
+
+ // 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::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();
+ data << uint8(cooldownsCount);
+
+ uint32 curTime = GameTime::GetGameTimeMS().count();
+ uint32 infTime = GameTime::GetGameTimeMS().count() + infinityCooldownDelayCheck;
+
+ for (CreatureSpellCooldowns::const_iterator itr = pet->m_CreatureSpellCooldowns.begin(); itr != pet->m_CreatureSpellCooldowns.end(); ++itr)
+ {
+ uint16 category = itr->second.category;
+ uint32 cooldown = (itr->second.end > curTime) ? itr->second.end - curTime : 0;
+
+ data << uint32(itr->first); // spellid
+ data << uint16(itr->second.category); // spell category
+
+ // send infinity cooldown in special format
+ if (itr->second.end >= infTime)
+ {
+ data << uint32(1); // cooldown
+ data << uint32(0x80000000); // category cooldown
+ continue;
+ }
+
+ data << uint32(category ? 0 : cooldown); // cooldown
+ data << uint32(category ? cooldown : 0); // category cooldown
+ }
+
+ GetSession()->SendPacket(&data);
+}
+
+void Player::PossessSpellInitialize()
+{
+ Unit* charm = GetCharm();
+ if (!charm)
+ return;
+
+ CharmInfo* charmInfo = charm->GetCharmInfo();
+
+ if (!charmInfo)
+ {
+ LOG_ERROR("entities.player", "Player::PossessSpellInitialize(): charm ({}) has no charminfo!", charm->GetGUID().ToString());
+ return;
+ }
+
+ WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * MAX_UNIT_ACTION_BAR_INDEX + 1 + 1);
+ data << charm->GetGUID();
+ 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::VehicleSpellInitialize()
+{
+ Creature* vehicle = GetVehicleCreatureBase();
+ if (!vehicle)
+ return;
+
+ uint8 cooldownCount = vehicle->m_CreatureSpellCooldowns.size();
+
+ WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * 10 + 1 + 1 + cooldownCount * (4 + 2 + 4 + 4));
+ data << vehicle->GetGUID(); // Guid
+ data << uint16(0); // Pet Family (0 for all vehicles)
+ data << uint32(vehicle->IsSummon() ? vehicle->ToTempSummon()->GetTimer() : 0); // Duration
+ // The following three segments are read by the client as one uint32
+ data << uint8(vehicle->GetReactState()); // React State
+ data << uint8(0); // Command State
+ data << uint16(0x800); // DisableActions (set for all vehicles)
+
+ for (uint32 i = 0; i < MAX_CREATURE_SPELLS; ++i)
+ {
+ uint32 spellId = vehicle->m_spells[i];
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (!spellInfo)
+ {
+ data << uint16(0) << uint8(0) << uint8(i + 8);
+ continue;
+ }
+
+ ConditionList conditions = sConditionMgr->GetConditionsForVehicleSpell(vehicle->GetEntry(), spellId);
+ if (!sConditionMgr->IsObjectMeetToConditions(this, vehicle, conditions))
+ {
+ LOG_DEBUG("condition", "VehicleSpellInitialize: conditions not met for Vehicle entry {} spell {}", vehicle->ToCreature()->GetEntry(), spellId);
+ data << uint16(0) << uint8(0) << uint8(i + 8);
+ continue;
+ }
+
+ if (spellInfo->IsPassive())
+ vehicle->CastSpell(vehicle, spellId, true);
+
+ data << uint32(MAKE_UNIT_ACTION_BUTTON(spellId, i + 8));
+ }
+
+ for (uint32 i = MAX_CREATURE_SPELLS; i < MAX_SPELL_CONTROL_BAR; ++i)
+ data << uint32(0);
+
+ data << uint8(0); // Auras?
+
+ // Cooldowns
+ data << uint8(cooldownCount);
+
+ uint32 curTime = GameTime::GetGameTimeMS().count();
+ uint32 infTime = GameTime::GetGameTimeMS().count() + infinityCooldownDelayCheck;
+
+ for (CreatureSpellCooldowns::const_iterator itr = vehicle->m_CreatureSpellCooldowns.begin(); itr != vehicle->m_CreatureSpellCooldowns.end(); ++itr)
+ {
+ uint16 category = itr->second.category;
+ uint32 cooldown = (itr->second.end > curTime) ? itr->second.end - curTime : 0;
+
+ data << uint32(itr->first); // spellid
+ data << uint16(itr->second.category); // spell category
+
+ // send infinity cooldown in special format
+ if (itr->second.end >= infTime)
+ {
+ data << uint32(1); // cooldown
+ data << uint32(0x80000000); // category cooldown
+ continue;
+ }
+
+ data << uint32(category ? 0 : cooldown); // cooldown
+ data << uint32(category ? cooldown : 0); // category cooldown
+ }
+
+ GetSession()->SendPacket(&data);
+}
+
+void Player::CharmSpellInitialize()
+{
+ Unit* charm = GetFirstControlled();
+ if (!charm)
+ return;
+
+ CharmInfo* charmInfo = charm->GetCharmInfo();
+ if (!charmInfo)
+ {
+ LOG_ERROR("entities.player", "Player::CharmSpellInitialize(): the player's charm ({}) has no charminfo!", charm->GetGUID().ToString());
+ return;
+ }
+
+ uint8 addlist = 0;
+ if (charm->GetTypeId() != TYPEID_PLAYER)
+ {
+ //CreatureInfo const* cinfo = charm->ToCreature()->GetCreatureTemplate();
+ //if (cinfo && cinfo->type == CREATURE_TYPE_DEMON && getClass() == CLASS_WARLOCK)
+ {
+ for (uint32 i = 0; i < MAX_SPELL_CHARM; ++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->GetGUID();
+ data << uint16(0);
+ data << uint32(0);
+
+ if (charm->GetTypeId() != TYPEID_PLAYER)
+ data << uint8(charm->ToCreature()->GetReactState()) << uint8(charmInfo->GetCommandState()) << uint16(0);
+ else
+ data << uint32(0);
+
+ charmInfo->BuildActionBar(&data);
+
+ data << uint8(addlist);
+
+ if (addlist)
+ {
+ for (uint32 i = 0; i < MAX_SPELL_CHARM; ++i)
+ {
+ CharmSpellInfo* cspell = charmInfo->GetCharmSpell(i);
+ if (cspell->GetAction())
+ data << uint32(cspell->packedData);
+ }
+ }
+
+ data << uint8(0); // cooldowns count
+
+ GetSession()->SendPacket(&data);
+}
+
+void Player::SendRemoveControlBar()
+{
+ WorldPacket data(SMSG_PET_SPELLS, 8);
+ data << uint64(0);
+ GetSession()->SendPacket(&data);
+}
+
+bool Player::HasSpellMod(SpellModifier* mod, Spell* spell)
+{
+ if (!mod || !spell)
+ return false;
+
+ return spell->m_appliedMods.find(mod->ownerAura) != spell->m_appliedMods.end();
+}
+
+bool Player::IsAffectedBySpellmod(SpellInfo const* spellInfo, SpellModifier* mod, Spell* spell)
+{
+ if (!mod || !spellInfo)
+ return false;
+
+ // Mod out of charges
+ if (spell && mod->charges == -1 && spell->m_appliedMods.find(mod->ownerAura) == spell->m_appliedMods.end())
+ return false;
+
+ // +duration to infinite duration spells making them limited
+ if (mod->op == SPELLMOD_DURATION && spellInfo->GetDuration() == -1)
+ return false;
+
+ return spellInfo->IsAffectedBySpellMod(mod);
+}
+
+template
+void Player::ApplySpellMod(uint32 spellId, SpellModOp op, T& basevalue, Spell* spell, bool temporaryPet)
+{
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (!spellInfo)
+ {
+ return;
+ }
+
+ float totalmul = 1.0f;
+ int32 totalflat = 0;
+
+ auto calculateSpellMod = [&](SpellModifier* mod)
+ {
+ // xinef: temporary pets cannot use charged mods of owner, needed for mirror image QQ they should use their own auras
+ if (temporaryPet && mod->charges != 0)
+ {
+ return;
+ }
+
+ if (mod->type == SPELLMOD_FLAT)
+ {
+ // xinef: do not allow to consume more than one 100% crit increasing spell
+ if (mod->op == SPELLMOD_CRITICAL_CHANCE && totalflat >= 100)
+ {
+ return;
+ }
+
+ int32 flatValue = mod->value;
+
+ // SPELL_MOD_THREAT - divide by 100 (in packets we send threat * 100)
+ if (mod->op == SPELLMOD_THREAT)
+ {
+ flatValue /= 100;
+ }
+
+ totalflat += flatValue;
+ }
+ else if (mod->type == SPELLMOD_PCT)
+ {
+ // skip percent mods for null basevalue (most important for spell mods with charges)
+ if (basevalue == T(0) || totalmul == 0.0f)
+ {
+ return;
+ }
+
+ // special case (skip > 10sec spell casts for instant cast setting)
+ if (mod->op == SPELLMOD_CASTING_TIME && basevalue >= T(10000) && mod->value <= -100)
+ {
+ return;
+ }
+ // xinef: special exception for surge of light, dont affect crit chance if previous mods were not applied
+ else if (mod->op == SPELLMOD_CRITICAL_CHANCE && spell && !HasSpellMod(mod, spell))
+ {
+ return;
+ }
+ // xinef: special case for backdraft gcd reduce with backlast time reduction, dont affect gcd if cast time was not applied
+ else if (mod->op == SPELLMOD_GLOBAL_COOLDOWN && spell && !HasSpellMod(mod, spell))
+ {
+ return;
+ }
+
+ // xinef: those two mods should be multiplicative (Glyph of Renew)
+ if (mod->op == SPELLMOD_DAMAGE || mod->op == SPELLMOD_DOT)
+ {
+ totalmul *= CalculatePct(1.0f, 100.0f + mod->value);
+ }
+ else
+ {
+ totalmul += CalculatePct(1.0f, mod->value);
+ }
+ }
+
+ DropModCharge(mod, spell);
+ };
+
+ // Drop charges for triggering spells instead of triggered ones
+ if (m_spellModTakingSpell)
+ {
+ spell = m_spellModTakingSpell;
+ }
+
+ SpellModifier* chargedMod = nullptr;
+ for (auto mod : m_spellMods[op])
+ {
+ // Charges can be set only for mods with auras
+ if (!mod->ownerAura)
+ {
+ ASSERT(!mod->charges);
+ }
+
+ if (!IsAffectedBySpellmod(spellInfo, mod, spell))
+ {
+ continue;
+ }
+
+ if (mod->ownerAura->IsUsingCharges())
+ {
+ if (!chargedMod || (chargedMod->ownerAura->GetSpellInfo()->SpellPriority < mod->ownerAura->GetSpellInfo()->SpellPriority))
+ {
+ chargedMod = mod;
+ }
+
+ continue;
+ }
+
+ calculateSpellMod(mod);
+ }
+
+ if (chargedMod)
+ {
+ calculateSpellMod(chargedMod);
+ }
+
+ float diff = 0.0f;
+ if (op == SPELLMOD_CASTING_TIME || op == SPELLMOD_DURATION)
+ {
+ diff = ((float)basevalue + totalflat) * (totalmul - 1.0f) + (float)totalflat;
+ }
+ else
+ {
+ diff = (float)basevalue * (totalmul - 1.0f) + (float)totalflat;
+ }
+
+ basevalue = T((float)basevalue + diff);
+}
+
+template AC_GAME_API void Player::ApplySpellMod(uint32 spellId, SpellModOp op, int32& basevalue, Spell* spell, bool temporaryPet);
+template AC_GAME_API void Player::ApplySpellMod(uint32 spellId, SpellModOp op, uint32& basevalue, Spell* spell, bool temporaryPet);
+template AC_GAME_API void Player::ApplySpellMod(uint32 spellId, SpellModOp op, float& basevalue, Spell* spell, bool temporaryPet);
+
+// Binary predicate for sorting SpellModifiers
+class SpellModPred
+{
+public:
+ SpellModPred() {}
+ bool operator() (SpellModifier const* a, SpellModifier const* b) const
+ {
+ if (a->type != b->type)
+ return a->type == SPELLMOD_FLAT;
+ return a->value < b->value;
+ }
+};
+class MageSpellModPred
+{
+public:
+ MageSpellModPred() {}
+ bool operator() (SpellModifier const* a, SpellModifier const* b) const
+ {
+ if (a->type != b->type)
+ return a->type == SPELLMOD_FLAT;
+ if (a->spellId == 44401)
+ return true;
+ if (b->spellId == 44401)
+ return false;
+ return a->value < b->value;
+ }
+};
+
+void Player::AddSpellMod(SpellModifier* mod, bool apply)
+{
+ LOG_DEBUG("spells.aura", "Player::AddSpellMod {}", mod->spellId);
+ uint16 Opcode = (mod->type == SPELLMOD_FLAT) ? SMSG_SET_FLAT_SPELL_MODIFIER : SMSG_SET_PCT_SPELL_MODIFIER;
+
+ int i = 0;
+ flag96 _mask = 0;
+ for (int eff = 0; eff < 96; ++eff)
+ {
+ if (eff != 0 && eff % 32 == 0)
+ _mask[i++] = 0;
+
+ _mask[i] = uint32(1) << (eff - (32 * i));
+ if (mod->mask & _mask)
+ {
+ int32 val = 0;
+ for (SpellModList::iterator itr = m_spellMods[mod->op].begin(); itr != m_spellMods[mod->op].end(); ++itr)
+ {
+ if ((*itr)->type == mod->type && (*itr)->mask & _mask)
+ val += (*itr)->value;
+ }
+ val += apply ? mod->value : -(mod->value);
+ WorldPacket data(Opcode, (1 + 1 + 4));
+ data << uint8(eff);
+ data << uint8(mod->op);
+ data << int32(val);
+ SendDirectMessage(&data);
+ }
+ }
+
+ if (apply)
+ {
+ m_spellMods[mod->op].push_back(mod);
+ if (getClass() == CLASS_MAGE)
+ m_spellMods[mod->op].sort(MageSpellModPred());
+ else
+ m_spellMods[mod->op].sort(SpellModPred());
+ }
+ else
+ {
+ m_spellMods[mod->op].remove(mod);
+ // mods bound to aura will be removed in AuraEffect::~AuraEffect
+ if (!mod->ownerAura)
+ delete mod;
+ }
+}
+
+// Restore spellmods in case of failed cast
+void Player::RestoreSpellMods(Spell* spell, uint32 ownerAuraId, Aura* aura)
+{
+ if (!spell || spell->m_appliedMods.empty())
+ return;
+
+ std::list aurasQueue;
+
+ for (uint8 i = 0; i < MAX_SPELLMOD; ++i)
+ {
+ for (SpellModList::iterator itr = m_spellMods[i].begin(); itr != m_spellMods[i].end(); ++itr)
+ {
+ SpellModifier* mod = *itr;
+
+ // Spellmods without aura set cannot be charged
+ if (!mod->ownerAura || !mod->ownerAura->IsUsingCharges())
+ continue;
+
+ // Restore only specific owner aura mods
+ if (ownerAuraId && (ownerAuraId != mod->ownerAura->GetSpellInfo()->Id))
+ continue;
+
+ if (aura && mod->ownerAura != aura)
+ continue;
+
+ // Check if mod affected this spell
+ // First, check if the mod aura applied at least one spellmod to this spell
+ Spell::UsedSpellMods::iterator iterMod = spell->m_appliedMods.find(mod->ownerAura);
+ if (iterMod == spell->m_appliedMods.end())
+ continue;
+ // Second, check if the current mod is one of those applied by the mod aura
+ if (!(mod->mask & spell->m_spellInfo->SpellFamilyFlags))
+ continue;
+
+ // remove from list - This will be done after all mods have been gone through
+ // to ensure we iterate over all mods of an aura before removing said aura
+ // from applied mods (Else, an aura with two mods on the current spell would
+ // only see the first of its modifier restored)
+ aurasQueue.push_back(mod->ownerAura);
+
+ // add mod charges back to mod
+ if (mod->charges == -1)
+ mod->charges = 1;
+ else
+ mod->charges++;
+
+ // Do not set more spellmods than available
+ if (mod->ownerAura->GetCharges() < mod->charges)
+ mod->charges = mod->ownerAura->GetCharges();
+
+ // Skip this check for now - aura charges may change due to various reason
+ /// @todo track these changes correctly
+ //ASSERT (mod->ownerAura->GetCharges() <= mod->charges);
+ }
+ }
+
+ for (std::list::iterator itr = aurasQueue.begin(); itr != aurasQueue.end(); ++itr)
+ {
+ Spell::UsedSpellMods::iterator iterMod = spell->m_appliedMods.find(*itr);
+ if (iterMod != spell->m_appliedMods.end())
+ spell->m_appliedMods.erase(iterMod);
+ }
+
+ // Xinef: clear the list just do be sure
+ if (!ownerAuraId && !aura)
+ spell->m_appliedMods.clear();
+}
+
+void Player::RestoreAllSpellMods(uint32 ownerAuraId, Aura* aura)
+{
+ for (uint32 i = 0; i < CURRENT_MAX_SPELL; ++i)
+ if (m_currentSpells[i])
+ RestoreSpellMods(m_currentSpells[i], ownerAuraId, aura);
+}
+
+void Player::RemoveSpellMods(Spell* spell)
+{
+ if (!spell)
+ return;
+
+ if (spell->m_appliedMods.empty())
+ return;
+
+ SpellInfo const* const spellInfo = spell->m_spellInfo;
+
+ for (uint8 i = 0; i < MAX_SPELLMOD; ++i)
+ {
+ for (SpellModList::const_iterator itr = m_spellMods[i].begin(); itr != m_spellMods[i].end();)
+ {
+ SpellModifier* mod = *itr;
+ ++itr;
+
+ // don't handle spells with proc_event entry defined
+ // this is a temporary workaround, because all spellmods should be handled like that
+ if (sSpellMgr->GetSpellProcEvent(mod->spellId))
+ {
+ continue;
+ }
+
+ // spellmods without aura set cannot be charged
+ if (!mod->ownerAura || !mod->ownerAura->IsUsingCharges())
+ continue;
+
+ // check if mod affected this spell
+ Spell::UsedSpellMods::iterator iterMod = spell->m_appliedMods.find(mod->ownerAura);
+ if (iterMod == spell->m_appliedMods.end())
+ continue;
+
+ // remove from list
+ // leave this here, if spell have two mods it will remove 2 charges - wrong
+ spell->m_appliedMods.erase(iterMod);
+
+ // MAGE T8P4 BONUS
+ if( spellInfo->SpellFamilyName == SPELLFAMILY_MAGE )
+ {
+ SpellInfo const* sp = mod->ownerAura->GetSpellInfo();
+ // Missile Barrage, Hot Streak, Brain Freeze (trigger spell - Fireball!)
+ if( sp->SpellIconID == 3261 || sp->SpellIconID == 2999 || sp->SpellIconID == 2938 )
+ if( AuraEffect* aurEff = GetAuraEffectDummy(64869) )
+ if( roll_chance_i(aurEff->GetAmount()) )
+ {
+ mod->charges = 1;
+ continue;
+ }
+ }
+
+ if (mod->ownerAura->DropCharge(AURA_REMOVE_BY_EXPIRE))
+ itr = m_spellMods[i].begin();
+ }
+ }
+}
+
+void Player::DropModCharge(SpellModifier* mod, Spell* spell)
+{
+ if (spell && mod->ownerAura && mod->charges > 0)
+ {
+ if (--mod->charges == 0)
+ mod->charges = -1;
+
+ spell->m_appliedMods.insert(mod->ownerAura);
+ }
+}
+
+void Player::SetSpellModTakingSpell(Spell* spell, bool apply)
+{
+ if (apply && m_spellModTakingSpell)
+ {
+ LOG_INFO("misc", "Player::SetSpellModTakingSpell (A1) - {}, {}", spell->m_spellInfo->Id, m_spellModTakingSpell->m_spellInfo->Id);
+ return;
+ //ASSERT(m_spellModTakingSpell == nullptr);
+ }
+ else if (!apply)
+ {
+ if (!m_spellModTakingSpell)
+ LOG_INFO("misc", "Player::SetSpellModTakingSpell (B1) - {}", spell->m_spellInfo->Id);
+ else if (m_spellModTakingSpell != spell)
+ {
+ LOG_INFO("misc", "Player::SetSpellModTakingSpell (C1) - {}, {}", spell->m_spellInfo->Id, m_spellModTakingSpell->m_spellInfo->Id);
+ return;
+ }
+ //ASSERT(m_spellModTakingSpell && m_spellModTakingSpell == spell);
+ }
+
+ m_spellModTakingSpell = apply ? spell : nullptr;
+}
+
+// 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 type)
+{
+ SignatureContainer* signatureStore = sPetitionMgr->GetSignatureStore();
+ for (SignatureContainer::iterator itr = signatureStore->begin(); itr != signatureStore->end(); ++itr)
+ {
+ SignatureMap::iterator signItr = itr->second.signatureMap.find(guid);
+ if (signItr != itr->second.signatureMap.end())
+ {
+ Petition const* petition = sPetitionMgr->GetPetition(itr->first);
+ if (!petition || (type != 10 && type != petition->petitionType))
+ continue;
+
+ // erase this
+ itr->second.signatureMap.erase(signItr);
+
+ // send update if charter owner in game
+ Player* owner = ObjectAccessor::FindConnectedPlayer(petition->ownerGuid);
+ if (owner)
+ owner->GetSession()->SendPetitionQueryOpcode(petition->petitionGuid);
+ }
+ }
+
+ if (type == 10)
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ALL_PETITION_SIGNATURES);
+ stmt->SetData(0, guid.GetCounter());
+ CharacterDatabase.Execute(stmt);
+ }
+ else
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_SIGNATURE);
+ stmt->SetData(0, guid.GetCounter());
+ stmt->SetData(1, uint8(type));
+ CharacterDatabase.Execute(stmt);
+ }
+
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+ if (type == 10)
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_BY_OWNER);
+ stmt->SetData(0, guid.GetCounter());
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_SIGNATURE_BY_OWNER);
+ stmt->SetData(0, guid.GetCounter());
+ trans->Append(stmt);
+
+ // xinef: clear petition store
+ sPetitionMgr->RemovePetitionByOwnerAndType(guid, 0);
+ }
+ else
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_BY_OWNER_AND_TYPE);
+ stmt->SetData(0, guid.GetCounter());
+ stmt->SetData(1, uint8(type));
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_SIGNATURE_BY_OWNER_AND_TYPE);
+ stmt->SetData(0, guid.GetCounter());
+ stmt->SetData(1, uint8(type));
+ trans->Append(stmt);
+
+ // xinef: clear petition store
+ sPetitionMgr->RemovePetitionByOwnerAndType(guid, uint8(type));
+ }
+ CharacterDatabase.CommitTransaction(trans);
+}
+
+void Player::LeaveAllArenaTeams(ObjectGuid guid)
+{
+ // xinef: sync query
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PLAYER_ARENA_TEAMS);
+ stmt->SetData(0, guid.GetCounter());
+ PreparedQueryResult result = CharacterDatabase.Query(stmt);
+
+ if (!result)
+ return;
+
+ do
+ {
+ Field* fields = result->Fetch();
+ uint32 arenaTeamId = fields[0].Get();
+ if (arenaTeamId != 0)
+ {
+ ArenaTeam* arenaTeam = sArenaTeamMgr->GetArenaTeamById(arenaTeamId);
+ if (arenaTeam)
+ arenaTeam->DelMember(guid, true);
+ }
+ } while (result->NextRow());
+}
+
+void Player::SetRestBonus(float rest_bonus_new)
+{
+ // Prevent resting on max level
+ if (GetLevel() >= sWorld->getIntConfig(CONFIG_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;
+
+ if (rest_bonus_new > rest_bonus_max)
+ _restBonus = rest_bonus_max;
+ else
+ _restBonus = rest_bonus_new;
+
+ // update data for client
+ if ((GetsRecruitAFriendBonus(true) && (GetSession()->IsARecruiter() || GetSession()->GetRecruiterId() != 0)))
+ SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_RAF_LINKED);
+ else
+ {
+ if (_restBonus > 10)
+ SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_RESTED);
+ else if (_restBonus <= 1)
+ SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_NOT_RAF_LINKED);
+ }
+
+ //RestTickUpdate
+ SetUInt32Value(PLAYER_REST_STATE_EXPERIENCE, uint32(_restBonus));
+}
+
+bool Player::ActivateTaxiPathTo(std::vector const& nodes, Creature* npc /*= nullptr*/, uint32 spellid /*= 1*/)
+{
+ if (nodes.size() < 2)
+ return false;
+
+ // not let cheating with start flight in time of logout process || while in combat || has type state: stunned || has type state: root
+ if (GetSession()->isLogingOut() || IsInCombat() || HasUnitState(UNIT_STATE_STUNNED) || HasUnitState(UNIT_STATE_ROOT))
+ {
+ GetSession()->SendActivateTaxiReply(ERR_TAXIPLAYERBUSY);
+ return false;
+ }
+
+ if (HasUnitFlag(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 (IsNonMeleeSpellCast(false))
+ {
+ GetSession()->SendActivateTaxiReply(ERR_TAXIPLAYERBUSY);
+ return false;
+ }
+ }
+ // cast case or scripted call case
+ else
+ {
+ RemoveAurasByType(SPELL_AURA_MOUNTED);
+
+ if (IsInDisallowedMountForm())
+ RemoveAurasByType(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;
+ }
+
+ // Prepare to flight start now
+
+ // stop combat at start taxi flight if any
+ CombatStop();
+
+ StopCastingCharm();
+ StopCastingBindSight();
+ ExitVehicle();
+
+ // 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 firstcost = 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 (i == 1)
+ firstcost = cost;
+
+ if (prevnode == sourcenode)
+ sourcepath = path;
+
+ m_taxi.AddTaxiDestination(lastnode);
+
+ prevnode = lastnode;
+ }
+
+ // get mount model (in case non taximaster (npc == nullptr) allow more wide lookup)
+ //
+ // Hack-Fix for Alliance not being able to use Acherus taxi. There is
+ // only one mount ID for both sides. Probably not good to use 315 in case DBC nodes
+ // change but I couldn't find a suitable alternative. OK to use class because only DK
+ // can use this taxi.
+ uint32 mount_display_id = sObjectMgr->GetTaxiMountDisplayId(sourcenode, GetTeamId(true), npc == nullptr || (sourcenode == 315 && getClass() == CLASS_DEATH_KNIGHT));
+
+ // in spell case allow 0 model
+ if ((mount_display_id == 0 && spellid == 0) || sourcepath == 0)
+ {
+ GetSession()->SendActivateTaxiReply(ERR_TAXIUNSPECIFIEDSERVERERROR);
+ m_taxi.ClearTaxiDestinations();
+ return false;
+ }
+
+ uint32 money = GetMoney();
+
+ if (npc)
+ {
+ float discount = GetReputationPriceDiscount(npc);
+ totalcost = uint32(ceil(totalcost * discount));
+ firstcost = uint32(ceil(firstcost * discount));
+ m_taxi.SetFlightMasterFactionTemplateId(npc->GetFaction());
+ }
+ else
+ {
+ m_taxi.SetFlightMasterFactionTemplateId(0);
+ }
+
+ if (money < totalcost)
+ {
+ GetSession()->SendActivateTaxiReply(ERR_TAXINOTENOUGHMONEY);
+ m_taxi.ClearTaxiDestinations();
+ return false;
+ }
+
+ //Checks and preparations done, DO FLIGHT
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_FLIGHT_PATHS_TAKEN, 1);
+
+ // prevent stealth flight
+ //RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TALK);
+
+ // Xinef: dont use instant flight paths if spellid is present (custom calls use spellid = 1)
+ if ((sWorld->getIntConfig(CONFIG_INSTANT_TAXI) == 1 || (sWorld->getIntConfig(CONFIG_INSTANT_TAXI) == 2 && m_isInstantFlightOn)) && !spellid)
+ {
+ TaxiNodesEntry const* lastPathNode = sTaxiNodesStore.LookupEntry(nodes[nodes.size() - 1]);
+ m_taxi.ClearTaxiDestinations();
+ ModifyMoney(-(int32)totalcost);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TRAVELLING, totalcost);
+ TeleportTo(lastPathNode->map_id, lastPathNode->x, lastPathNode->y, lastPathNode->z, GetOrientation());
+ return false;
+ }
+ else
+ {
+ m_flightSpellActivated = spellid;
+ ModifyMoney(-(int32)firstcost);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TRAVELLING, firstcost);
+ GetSession()->SendActivateTaxiReply(ERR_TAXIOK);
+ GetSession()->SendDoFlight(mount_display_id, sourcepath);
+ }
+ return true;
+}
+
+bool Player::ActivateTaxiPathTo(uint32 taxi_path_id, uint32 spellid /*= 1*/)
+{
+ 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, nullptr, spellid);
+}
+
+void Player::CleanupAfterTaxiFlight()
+{
+ // For spells that trigger flying paths remove them at arrival
+ if (m_flightSpellActivated)
+ {
+ this->RemoveAurasDueToSpell(m_flightSpellActivated);
+ m_flightSpellActivated = 0;
+ }
+ m_taxi.ClearTaxiDestinations(); // not destinations, clear source node
+ Dismount();
+ RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_TAXI_FLIGHT);
+ getHostileRefMgr().setOnlineOfflineState(true);
+}
+
+void Player::ContinueTaxiFlight()
+{
+ uint32 sourceNode = m_taxi.GetTaxiSource();
+ if (!sourceNode)
+ return;
+
+ LOG_DEBUG("entities.unit", "WORLD: Restart character {} taxi flight", GetGUID().ToString());
+
+ uint32 mountDisplayId = sObjectMgr->GetTaxiMountDisplayId(sourceNode, GetTeamId(true), true);
+ if (!mountDisplayId)
+ return;
+
+ uint32 path = m_taxi.GetCurrentTaxiPath();
+
+ // search appropriate start path node
+ uint32 startNode = 0;
+
+ TaxiPathNodeList const& nodeList = sTaxiPathNodesByPath[path];
+
+ float bestDist = SIZE_OF_GRIDS * SIZE_OF_GRIDS; // xinef: large value
+ float currDist = 0.0f;
+
+ // xinef: changed to -1, we dont want to catch last node
+ for (uint32 i = 0; i < nodeList.size() - 1; ++i)
+ {
+ TaxiPathNodeEntry const* node = nodeList[i];
+ TaxiPathNodeEntry const* nextNode = nodeList[i + 1];
+
+ // xinef: skip nodes at another map, get last valid node on current map
+ if (nextNode->mapid != GetMapId() || node->mapid != GetMapId())
+ continue;
+
+ currDist = (node->x - GetPositionX()) * (node->x - GetPositionX()) + (node->y - GetPositionY()) * (node->y - GetPositionY()) + (node->z - GetPositionZ()) * (node->z - GetPositionZ());
+ if (currDist < bestDist)
+ {
+ startNode = i;
+ bestDist = currDist;
+ }
+ }
+
+ // xinef: no proper node was found
+ if (startNode == 0)
+ {
+ m_taxi.ClearTaxiDestinations();
+ return;
+ }
+
+ if (IsInDisallowedMountForm())
+ {
+ RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
+ }
+
+ if (IsMounted())
+ {
+ RemoveAurasByType(SPELL_AURA_MOUNTED);
+ }
+
+ SetCanTeleport(true);
+
+ GetSession()->SendDoFlight(mountDisplayId, path, startNode);
+}
+
+void Player::SendTaxiNodeStatusMultiple()
+{
+ for (auto itr = m_clientGUIDs.begin(); itr != m_clientGUIDs.end(); ++itr)
+ {
+ if (!itr->IsCreature())
+ {
+ continue;
+ }
+
+ Creature* creature = ObjectAccessor::GetCreature(*this, *itr);
+ if (!creature || creature->IsHostileTo(this))
+ {
+ continue;
+ }
+
+ if (!creature->HasNpcFlag(UNIT_NPC_FLAG_FLIGHTMASTER))
+ {
+ continue;
+ }
+
+ uint32 nearestNode = sObjectMgr->GetNearestTaxiNode(creature->GetPositionX(), creature->GetPositionY(), creature->GetPositionZ(), creature->GetMapId(), GetTeamId());
+ if (!nearestNode)
+ {
+ continue;
+ }
+
+ WorldPacket data(SMSG_TAXINODE_STATUS, 9);
+ data << *itr;
+ data << uint8(m_taxi.IsTaximaskNodeKnown(nearestNode) ? 1 : 0);
+ SendDirectMessage(&data);
+ }
+}
+
+void Player::ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs)
+{
+ PacketCooldowns cooldowns;
+ WorldPacket data;
+
+ for (PlayerSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr)
+ {
+ if (itr->second->State == PLAYERSPELL_REMOVED)
+ continue;
+ uint32 unSpellId = itr->first;
+ SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(unSpellId);
+
+ // Not send cooldown for this spells
+ if (spellInfo->IsCooldownStartedOnEvent())
+ continue;
+
+ if (spellInfo->PreventionType != SPELL_PREVENTION_TYPE_SILENCE)
+ continue;
+
+ if ((idSchoolMask & spellInfo->GetSchoolMask()) && GetSpellCooldownDelay(unSpellId) < unTimeMs)
+ {
+ cooldowns[unSpellId] = unTimeMs;
+ AddSpellCooldown(unSpellId, 0, unTimeMs, true);
+ }
+ }
+
+ if (!cooldowns.empty())
+ {
+ BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, cooldowns);
+ GetSession()->SendPacket(&data);
+ }
+}
+
+void Player::InitDataForForm(bool reapplyMods)
+{
+ ShapeshiftForm form = GetShapeshiftForm();
+
+ SpellShapeshiftEntry const* ssEntry = sSpellShapeshiftStore.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_GHOUL:
+ case FORM_CAT:
+ {
+ if (getPowerType() != POWER_ENERGY)
+ setPowerType(POWER_ENERGY);
+ break;
+ }
+ case FORM_BEAR:
+ case FORM_DIREBEAR:
+ {
+ 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(true), getClass());
+ if (!info)
+ {
+ LOG_ERROR("entities.player", "Player {} has incorrect race/class pair. Can't init display ids.", GetGUID().ToString());
+ return;
+ }
+
+ 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:
+ LOG_ERROR("entities.player", "Invalid gender {} for player", gender);
+ return;
+ }
+}
+
+inline bool Player::_StoreOrEquipNewItem(uint32 vendorslot, uint32 item, uint8 count, uint8 bag, uint8 slot, int32 price, ItemTemplate const* pProto, Creature* pVendor, VendorItem const* crItem, bool bStore)
+{
+ ItemPosCountVec vDest;
+ uint16 uiDest = 0;
+ InventoryResult msg = bStore ?
+ CanStoreNewItem(bag, slot, vDest, item, pProto->BuyCount * count) :
+ CanEquipNewItem(slot, uiDest, item, false);
+ if (msg != EQUIP_ERR_OK)
+ {
+ SendEquipError(msg, nullptr, nullptr, item);
+ return false;
+ }
+
+ ModifyMoney(-price);
+
+ if (crItem->ExtendedCost) // case for new honor system
+ {
+ ItemExtendedCostEntry const* iece = sItemExtendedCostStore.LookupEntry(crItem->ExtendedCost);
+ if (iece->reqhonorpoints)
+ ModifyHonorPoints(- int32(iece->reqhonorpoints * count));
+
+ if (iece->reqarenapoints)
+ ModifyArenaPoints(- int32(iece->reqarenapoints * count));
+
+ for (uint8 i = 0; i < MAX_ITEM_EXTENDED_COST_REQUIREMENTS; ++i)
+ {
+ if (iece->reqitem[i])
+ DestroyItemCount(iece->reqitem[i], (iece->reqitemcount[i] * count), true);
+ }
+ }
+
+ sScriptMgr->OnBeforeStoreOrEquipNewItem(this, vendorslot, item, count, bag, slot, pProto, pVendor, crItem, bStore);
+
+ Item* it = bStore ? StoreNewItem(vDest, item, true) : EquipNewItem(uiDest, item, true);
+ if (it)
+ {
+ uint32 new_count = pVendor->UpdateVendorItemCurrentCount(crItem, pProto->BuyCount * count);
+
+ WorldPacket data(SMSG_BUY_ITEM, (8 + 4 + 4 + 4));
+ data << pVendor->GetGUID();
+ data << uint32(vendorslot + 1); // numbered from 1 at client
+ data << int32(crItem->maxcount > 0 ? new_count : 0xFFFFFFFF);
+ data << uint32(count);
+ GetSession()->SendPacket(&data);
+ SendNewItem(it, pProto->BuyCount * count, true, false, false);
+
+ if (!bStore)
+ AutoUnequipOffhandIfNeed();
+
+ if (pProto->Flags & ITEM_FLAG_ITEM_PURCHASE_RECORD && crItem->ExtendedCost && pProto->GetMaxStackSize() == 1)
+ {
+ it->SetFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_REFUNDABLE);
+ it->SetRefundRecipient(GetGUID().GetCounter());
+ it->SetPaidMoney(price);
+ it->SetPaidExtendedCost(crItem->ExtendedCost);
+ it->SaveRefundDataToDB();
+ AddRefundReference(it->GetGUID());
+ }
+ }
+
+ sScriptMgr->OnAfterStoreOrEquipNewItem(this, vendorslot, it, count, bag, slot, pProto, pVendor, crItem, bStore);
+
+ return true;
+}
+
+// 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, uint8 count, uint8 bag, uint8 slot)
+{
+ sScriptMgr->OnBeforeBuyItemFromVendor(this, vendorguid, vendorslot, item, count, bag, slot);
+
+ // this check can be used from the hook to implement a custom vendor process
+ if (item == 0)
+ return true;
+
+ // cheating attempt
+ if (count < 1) count = 1;
+
+ // cheating attempt
+ if (slot > MAX_BAG_SIZE && slot != NULL_SLOT)
+ return false;
+
+ if (!IsAlive())
+ return false;
+
+ ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(item);
+ if (!pProto)
+ {
+ SendBuyError(BUY_ERR_CANT_FIND_ITEM, nullptr, item, 0);
+ return false;
+ }
+
+ // npcbot
+ if (HaveBot())
+ {
+ if (!(pProto->AllowableClass & (GetClassMask() | GetBotMgr()->GetAllNpcBotsClassMask())) &&
+ pProto->Bonding == BIND_WHEN_PICKED_UP && !IsGameMaster())
+ {
+ SendBuyError(BUY_ERR_CANT_FIND_ITEM, nullptr, item, 0);
+ return false;
+ }
+ }
+ else
+ // end npcbot
+ if (!(pProto->AllowableClass & getClassMask()) && pProto->Bonding == BIND_WHEN_PICKED_UP && !IsGameMaster())
+ {
+ SendBuyError(BUY_ERR_CANT_FIND_ITEM, nullptr, item, 0);
+ return false;
+ }
+
+ if (!IsGameMaster() && ((pProto->Flags2 & ITEM_FLAGS_EXTRA_HORDE_ONLY && GetTeamId(true) == TEAM_ALLIANCE) || (pProto->Flags2 & ITEM_FLAGS_EXTRA_ALLIANCE_ONLY && GetTeamId(true) == TEAM_HORDE)))
+ {
+ return false;
+ }
+
+ Creature* creature = GetNPCIfCanInteractWith(vendorguid, UNIT_NPC_FLAG_VENDOR);
+ if (!creature)
+ {
+ LOG_DEBUG("network", "WORLD: BuyItemFromVendor - Unit ({}) not found or you can't interact with him.", vendorguid.ToString());
+ SendBuyError(BUY_ERR_DISTANCE_TOO_FAR, nullptr, item, 0);
+ return false;
+ }
+
+ ConditionList conditions = sConditionMgr->GetConditionsForNpcVendorEvent(creature->GetEntry(), item);
+ if (!sConditionMgr->IsObjectMeetToConditions(this, creature, conditions))
+ {
+ //LOG_DEBUG("condition", "BuyItemFromVendor: conditions not met for creature entry {} item {}", creature->GetEntry(), item);
+ SendBuyError(BUY_ERR_CANT_FIND_ITEM, creature, item, 0);
+ return false;
+ }
+
+ VendorItemData const* vItems = GetSession()->GetCurrentVendor() ? sObjectMgr->GetNpcVendorItemList(GetSession()->GetCurrentVendor()) : creature->GetVendorItems();
+ if (!vItems || vItems->Empty())
+ {
+ SendBuyError(BUY_ERR_CANT_FIND_ITEM, creature, item, 0);
+ return false;
+ }
+
+ if (vendorslot >= vItems->GetItemCount())
+ {
+ SendBuyError(BUY_ERR_CANT_FIND_ITEM, creature, item, 0);
+ return false;
+ }
+
+ VendorItem const* crItem = vItems->GetItem(vendorslot);
+ // store diff item (cheating)
+ if (!crItem || crItem->item != item)
+ {
+ SendBuyError(BUY_ERR_CANT_FIND_ITEM, creature, item, 0);
+ return false;
+ }
+
+ // check current item amount if it limited
+ if (crItem->maxcount != 0)
+ {
+ if (creature->GetVendorItemCurrentCount(crItem) < pProto->BuyCount * count)
+ {
+ SendBuyError(BUY_ERR_ITEM_ALREADY_SOLD, creature, item, 0);
+ return false;
+ }
+ }
+
+ if (pProto->RequiredReputationFaction && (uint32(GetReputationRank(pProto->RequiredReputationFaction)) < pProto->RequiredReputationRank))
+ {
+ SendBuyError(BUY_ERR_REPUTATION_REQUIRE, creature, item, 0);
+ return false;
+ }
+
+ if (crItem->ExtendedCost)
+ {
+ ItemExtendedCostEntry const* iece = sItemExtendedCostStore.LookupEntry(crItem->ExtendedCost);
+ if (!iece)
+ {
+ LOG_ERROR("entities.player", "Item {} have wrong ExtendedCost field value {}", pProto->ItemId, crItem->ExtendedCost);
+ return false;
+ }
+
+ // honor points price
+ if (GetHonorPoints() < (iece->reqhonorpoints * count))
+ {
+ SendEquipError(EQUIP_ERR_NOT_ENOUGH_HONOR_POINTS, nullptr, nullptr);
+ return false;
+ }
+
+ // arena points price
+ if (GetArenaPoints() < (iece->reqarenapoints * count))
+ {
+ SendEquipError(EQUIP_ERR_NOT_ENOUGH_ARENA_POINTS, nullptr, nullptr);
+ return false;
+ }
+
+ // item base price
+ for (uint8 i = 0; i < MAX_ITEM_EXTENDED_COST_REQUIREMENTS; ++i)
+ {
+ if (iece->reqitem[i] && !HasItemCount(iece->reqitem[i], (iece->reqitemcount[i] * count)))
+ {
+ SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, nullptr, nullptr);
+ 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, nullptr, nullptr);
+ return false;
+ }
+ }
+
+ uint32 price = 0;
+ if (crItem->IsGoldRequired(pProto) && pProto->BuyPrice > 0) //Assume price cannot be negative (do not know why it is int32)
+ {
+ uint32 maxCount = MAX_MONEY_AMOUNT / pProto->BuyPrice;
+ if ((uint32)count > maxCount)
+ {
+ LOG_ERROR("entities.player", "Player {} tried to buy {} item id {}, causing overflow", GetName(), (uint32)count, pProto->ItemId);
+ count = (uint8)maxCount;
+ }
+ price = pProto->BuyPrice * count; //it should not exceed MAX_MONEY_AMOUNT
+
+ // reputation discount
+ price = uint32(floor(price * GetReputationPriceDiscount(creature)));
+
+ if (!HasEnoughMoney(price))
+ {
+ SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, creature, item, 0);
+ return false;
+ }
+ }
+
+ if ((bag == NULL_BAG && slot == NULL_SLOT) || IsInventoryPos(bag, slot))
+ {
+ if (!_StoreOrEquipNewItem(vendorslot, item, count, bag, slot, price, pProto, creature, crItem, true))
+ return false;
+ }
+ else if (IsEquipmentPos(bag, slot))
+ {
+ if (pProto->BuyCount * count != 1)
+ {
+ SendEquipError(EQUIP_ERR_ITEM_CANT_BE_EQUIPPED, nullptr, nullptr);
+ return false;
+ }
+ if (!_StoreOrEquipNewItem(vendorslot, item, count, bag, slot, price, pProto, creature, crItem, false))
+ return false;
+ }
+ else
+ {
+ SendEquipError(EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT, nullptr, nullptr);
+ return false;
+ }
+
+ return crItem->maxcount != 0;
+}
+
+uint32 Player::GetMaxPersonalArenaRatingRequirement(uint32 minarenaslot) const
+{
+ // 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 (uint8 i = minarenaslot; i < MAX_ARENA_SLOT; ++i)
+ {
+ if (ArenaTeam* at = sArenaTeamMgr->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;
+ }
+ }
+
+ sScriptMgr->OnGetMaxPersonalArenaRatingRequirement(this, minarenaslot, max_personal_rating);
+
+ return max_personal_rating;
+}
+
+void Player::AddSpellAndCategoryCooldowns(SpellInfo 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
+ // This used in same way in WorldSession::HandleItemQuerySingleOpcode data sending to client.
+
+ bool useSpellCooldown = spellInfo->HasAttribute(SPELL_ATTR7_CAN_BE_MULTI_CAST);
+ if (itemId)
+ {
+ if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId))
+ {
+ for (uint8 idx = 0; idx < MAX_ITEM_SPELLS; ++idx)
+ {
+ if (uint32(proto->Spells[idx].SpellId) == spellInfo->Id)
+ {
+ cat = proto->Spells[idx].SpellCategory;
+ rec = proto->Spells[idx].SpellCooldown;
+ catrec = proto->Spells[idx].SpellCategoryCooldown;
+
+ if (static_cast(cat) != catrec)
+ {
+ useSpellCooldown = true;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // if no cooldown found above then base at DBC data
+ if (rec < 0 && catrec < 0)
+ {
+ cat = spellInfo->GetCategory();
+ rec = spellInfo->RecoveryTime;
+ catrec = spellInfo->CategoryRecoveryTime;
+ }
+
+ time_t catrecTime;
+ time_t recTime;
+
+ bool needsCooldownPacket = false;
+
+ // 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 ? infinityCooldownDelay : 0;
+ recTime = rec > 0 ? 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 || (spellInfo->IsAutoRepeatRangedSpell() && spellInfo->Id != 75)))
+ rec = GetAttackTime(RANGED_ATTACK);
+
+ // Now we have cooldown data (if found any), time to apply mods
+ if (rec > 0)
+ {
+ int32 oldRec = rec;
+ ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, rec, spell);
+ if (oldRec != rec)
+ {
+ useSpellCooldown = true;
+ }
+ }
+
+ if (catrec > 0 && !spellInfo->HasAttribute(SPELL_ATTR6_NO_CATEGORY_COOLDOWN_MODS))
+ {
+ ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, catrec, spell);
+ }
+
+ if (int32 cooldownMod = GetTotalAuraModifier(SPELL_AURA_MOD_COOLDOWN))
+ {
+ // Apply SPELL_AURA_MOD_COOLDOWN only to own spells
+ if (HasSpell(spellInfo->Id))
+ {
+ needsCooldownPacket = true;
+ useSpellCooldown = true;
+ rec += cooldownMod * IN_MILLISECONDS; // SPELL_AURA_MOD_COOLDOWN does not affect category cooldows, verified with shaman shocks
+ }
+ }
+
+ // 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 ? catrec : 0;
+ recTime = rec ? rec : catrecTime;
+ }
+
+ // category spells
+ if (cat && catrec > 0)
+ {
+ _AddSpellCooldown(spellInfo->Id, cat, itemId, useSpellCooldown ? recTime : catrecTime, true, true);
+ if (needsCooldownPacket)
+ {
+ WorldPacket data;
+ BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, spellInfo->Id, useSpellCooldown ? recTime : catrecTime);
+ SendDirectMessage(&data);
+ }
+
+ PacketCooldowns forcedCategoryCooldowns;
+
+ SpellCategoryStore::const_iterator i_scstore = sSpellsByCategoryStore.find(cat);
+ if (i_scstore != sSpellsByCategoryStore.end())
+ {
+ for (SpellCategorySet::const_iterator i_scset = i_scstore->second.begin(); i_scset != i_scstore->second.end(); ++i_scset)
+ {
+ if (i_scset->second == spellInfo->Id) // skip main spell, already handled above
+ {
+ continue;
+ }
+
+ // If spell category is applied by item, then other spells should be exists in item templates
+ if ((itemId > 0) != i_scset->first)
+ {
+ continue;
+ }
+
+ // Only within the same spellfamily
+ SpellInfo const* categorySpellInfo = sSpellMgr->GetSpellInfo(i_scset->second);
+ if (!categorySpellInfo || categorySpellInfo->SpellFamilyName != spellInfo->SpellFamilyName)
+ {
+ continue;
+ }
+
+ _AddSpellCooldown(i_scset->second, cat, itemId, catrecTime, !spellInfo->IsCooldownStartedOnEvent() && catrec && rec && catrec != rec);
+
+ if (spellInfo->HasAttribute(SPELL_ATTR0_CU_FORCE_SEND_CATEGORY_COOLDOWNS))
+ {
+ forcedCategoryCooldowns[i_scset->second] = catrecTime;
+ }
+ }
+ }
+
+ if (!forcedCategoryCooldowns.empty())
+ {
+ WorldPacket data;
+ BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, forcedCategoryCooldowns);
+ SendDirectMessage(&data);
+ }
+ }
+ else
+ {
+ // self spell cooldown
+ if (recTime > 0)
+ {
+ _AddSpellCooldown(spellInfo->Id, 0, itemId, recTime, true, true);
+
+ if (needsCooldownPacket)
+ {
+ WorldPacket data;
+ BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, spellInfo->Id, rec);
+ SendDirectMessage(&data);
+ }
+ }
+ }
+}
+
+void Player::_AddSpellCooldown(uint32 spellid, uint16 categoryId, uint32 itemid, uint32 end_time, bool needSendToClient, bool forceSendToSpectator)
+{
+ SpellCooldown sc;
+ sc.end = GameTime::GetGameTimeMS().count() + end_time;
+ sc.category = categoryId;
+ sc.itemid = itemid;
+ sc.maxduration = end_time;
+ sc.sendToSpectator = false;
+ sc.needSendToClient = needSendToClient;
+
+ if (end_time >= SPECTATOR_COOLDOWN_MIN * IN_MILLISECONDS && end_time <= SPECTATOR_COOLDOWN_MAX * IN_MILLISECONDS)
+ {
+ if (NeedSendSpectatorData() && forceSendToSpectator && (itemid || HasActiveSpell(spellid)))
+ {
+ sc.sendToSpectator = true;
+ ArenaSpectator::SendCommand_Cooldown(FindMap(), GetGUID(), "ACD", spellid, end_time / IN_MILLISECONDS, end_time / IN_MILLISECONDS);
+ }
+ }
+
+ m_spellCooldowns[spellid] = std::move(sc);
+}
+
+void Player::AddSpellCooldown(uint32 spellid, uint32 itemid, uint32 end_time, bool needSendToClient, bool forceSendToSpectator)
+{
+ _AddSpellCooldown(spellid, 0, itemid, end_time, needSendToClient, forceSendToSpectator);
+}
+
+void Player::ModifySpellCooldown(uint32 spellId, int32 cooldown)
+{
+ SpellCooldowns::iterator itr = m_spellCooldowns.find(spellId);
+ if (itr == m_spellCooldowns.end())
+ return;
+
+ itr->second.end += cooldown;
+
+ WorldPacket data(SMSG_MODIFY_COOLDOWN, 4 + 8 + 4);
+ data << uint32(spellId); // Spell ID
+ data << GetGUID(); // Player GUID
+ data << int32(cooldown); // Cooldown mod in milliseconds
+ GetSession()->SendPacket(&data);
+}
+
+void Player::SendCooldownEvent(SpellInfo const* spellInfo, uint32 itemId /*= 0*/, Spell* spell /*= nullptr*/, bool setCooldown /*= true*/)
+{
+ // start cooldowns at server side, if any
+ if (setCooldown)
+ 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 << GetGUID();
+ SendDirectMessage(&data);
+}
+
+//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->HasSocket())
+ {
+ for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot <= PRISMATIC_ENCHANTMENT_SLOT; ++enchant_slot)
+ {
+ if (enchant_slot == BONUS_ENCHANTMENT_SLOT)
+ continue;
+
+ 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;
+
+ ItemTemplate const* gemProto = sObjectMgr->GetItemTemplate(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 (uint8 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);
+ break;
+ case 3: // requires more than ( || ) gems
+ activate &= (_cur_gem > _cmp_gem);
+ break;
+ case 5: // requires at least than ( || ) gems
+ activate &= (_cur_gem >= _cmp_gem);
+ break;
+ }
+ }
+
+ LOG_DEBUG("entities.player.items", "Checking Condition {}, there are {} Meta Gems, {} Red Gems, {} Yellow Gems and {} Blue Gems, Activate:{}", 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->HasSocket())
+ 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& recvData)
+ if (slot == exceptslot)
+ continue;
+
+ Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
+
+ if (!pItem || !pItem->GetTemplate()->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::SetEntryPoint()
+{
+ m_entryPointData.joinPos.m_mapId = MAPID_INVALID;
+ m_entryPointData.ClearTaxiPath();
+
+ if (!m_taxi.empty())
+ {
+ m_entryPointData.mountSpell = 0;
+ m_entryPointData.joinPos = WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
+
+ m_entryPointData.taxiPath[0] = m_taxi.GetTaxiSource();
+ m_entryPointData.taxiPath[1] = m_taxi.GetTaxiDestination();
+ }
+ else
+ {
+ if (IsMounted())
+ {
+ AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_MOUNTED);
+ if (!auras.empty())
+ m_entryPointData.mountSpell = (*auras.begin())->GetId();
+ }
+ else
+ m_entryPointData.mountSpell = 0;
+
+ if (GetMap()->IsDungeon())
+ {
+ if (const GraveyardStruct* entry = sGraveyard->GetClosestGraveyard(this, GetTeamId()))
+ m_entryPointData.joinPos = WorldLocation(entry->Map, entry->x, entry->y, entry->z, 0.0f);
+ }
+ else if (!GetMap()->IsBattlegroundOrArena())
+ m_entryPointData.joinPos = WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
+ }
+
+ if (m_entryPointData.joinPos.m_mapId == MAPID_INVALID)
+ m_entryPointData.joinPos = WorldLocation(m_homebindMapId, m_homebindX, m_homebindY, m_homebindZ, m_homebindO);
+}
+
+void Player::LeaveBattleground(Battleground* bg)
+{
+ if (!bg)
+ bg = GetBattleground();
+
+ if (!bg)
+ return;
+
+ // Deserter tracker - leave BG
+ if (bg->isBattleground() && (bg->GetStatus() == STATUS_IN_PROGRESS || bg->GetStatus() == STATUS_WAIT_JOIN))
+ {
+ if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_TRACK_DESERTERS))
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_DESERTER_TRACK);
+ stmt->SetData(0, GetGUID().GetCounter());
+ stmt->SetData(1, BG_DESERTION_TYPE_LEAVE_BG);
+ CharacterDatabase.Execute(stmt);
+ }
+ sScriptMgr->OnBattlegroundDesertion(this, BG_DESERTION_TYPE_LEAVE_BG);
+ }
+
+ bg->RemovePlayerAtLeave(this);
+
+ // xinef: reset corpse reclaim time
+ m_deathExpireTime = GameTime::GetGameTime().count();
+
+ // Remove all dots
+ RemoveAurasByType(SPELL_AURA_PERIODIC_DAMAGE);
+ RemoveAurasByType(SPELL_AURA_PERIODIC_DAMAGE_PERCENT);
+ RemoveAurasByType(SPELL_AURA_PERIODIC_LEECH);
+
+ // pussywizard: clear movement, because after porting player will move to arena cords
+ GetMotionMaster()->MovementExpired();
+ StopMoving();
+ TeleportToEntryPoint();
+}
+
+bool Player::CanJoinToBattleground() const
+{
+ // check Deserter debuff
+ if (HasAura(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();
+ // Battleground also must be in progress!
+ if (!bg || bg != reporter->GetBattleground() || GetTeamId() != reporter->GetTeamId() || bg->GetStatus() != STATUS_IN_PROGRESS)
+ return;
+
+ // Xinef: 2 minutes startup + 2 minute of match
+ if (bg->GetStartTime() < sWorld->getIntConfig(CONFIG_BATTLEGROUND_REPORT_AFK_TIMER) * MINUTE * IN_MILLISECONDS)
+ return;
+
+ // check if player has 'Idle' or 'Inactive' debuff
+ if (m_bgData.bgAfkReporter.find(reporter->GetGUID()) == m_bgData.bgAfkReporter.end() && !HasAura(43680) && !HasAura(43681) && reporter->CanReportAfkDueToLimit())
+ {
+ m_bgData.bgAfkReporter.insert(reporter->GetGUID());
+ // by default 3 players have to complain to apply debuff
+ if (m_bgData.bgAfkReporter.size() >= sWorld->getIntConfig(CONFIG_BATTLEGROUND_REPORT_AFK))
+ {
+ // cast 'Idle' spell
+ CastSpell(this, 43680, true);
+ m_bgData.bgAfkReporter.clear();
+ }
+ }
+}
+
+WorldLocation Player::GetStartPosition() const
+{
+ PlayerInfo const* info = sObjectMgr->GetPlayerInfo(getRace(true), getClass());
+ uint32 mapId = info->mapId;
+ if (getClass() == CLASS_DEATH_KNIGHT && HasSpell(50977))
+ return WorldLocation(0, 2352.0f, -5709.0f, 154.5f, 0.0f);
+ return WorldLocation(mapId, info->positionX, info->positionY, info->positionZ, 0);
+}
+
+bool Player::HaveAtClient(WorldObject const* u) const
+{
+ if (u == this)
+ {
+ return true;
+ }
+
+ // Motion Transports are always present in player's client
+ if (GameObject const* gameobject = u->ToGameObject())
+ {
+ if (gameobject->IsMotionTransport())
+ {
+ return true;
+ }
+ }
+
+ return m_clientGUIDs.find(u->GetGUID()) != m_clientGUIDs.end();
+}
+
+bool Player::HaveAtClient(ObjectGuid guid) const
+{
+ if (guid == GetGUID())
+ {
+ return true;
+ }
+
+ return m_clientGUIDs.find(guid) != m_clientGUIDs.end();
+}
+
+bool Player::IsNeverVisible() const
+{
+ if (Unit::IsNeverVisible())
+ return true;
+
+ if (GetSession()->PlayerLogout() || GetSession()->PlayerLoading())
+ return true;
+
+ return false;
+}
+
+bool Player::CanAlwaysSee(WorldObject const* obj) const
+{
+ // Always can see self
+ if (m_mover == obj)
+ return true;
+
+ if (ObjectGuid guid = GetGuidValue(PLAYER_FARSIGHT))
+ if (obj->GetGUID() == guid)
+ return true;
+
+ return false;
+}
+
+bool Player::IsAlwaysDetectableFor(WorldObject const* seer) const
+{
+ if (Unit::IsAlwaysDetectableFor(seer))
+ return true;
+
+ if (duel && duel->State != DUEL_STATE_CHALLENGED && duel->Opponent == seer)
+ {
+ return false;
+ }
+
+ if (Player const* seerPlayer = seer->ToPlayer())
+ {
+ if (IsGroupVisibleFor(seerPlayer))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool Player::IsVisibleGloballyFor(Player const* u) const
+{
+ if (!u)
+ return false;
+
+ // Always can see self
+ if (u == this)
+ return true;
+
+ // Visible units, always are visible for all players
+ if (IsVisible())
+ return true;
+
+ // GMs are visible for higher gms (or players are visible for gms)
+ if (!AccountMgr::IsPlayerAccount(u->GetSession()->GetSecurity()))
+ return GetSession()->GetSecurity() <= u->GetSession()->GetSecurity();
+
+ if (!sScriptMgr->NotVisibleGloballyFor(const_cast(this), u))
+ return true;
+
+ // non faction visibility non-breakable for non-GMs
+ return false;
+}
+
+void Player::InitPrimaryProfessions()
+{
+ SetFreePrimaryProfessions(sWorld->getIntConfig(CONFIG_MAX_PRIMARY_TRADE_SKILL));
+}
+
+bool Player::ModifyMoney(int32 amount, bool sendError /*= true*/)
+{
+ if (!amount)
+ return true;
+
+ sScriptMgr->OnPlayerMoneyChanged(this, amount);
+
+ if (amount < 0)
+ SetMoney (GetMoney() > uint32(-amount) ? GetMoney() + amount : 0);
+ else
+ {
+ if (GetMoney() < uint32(MAX_MONEY_AMOUNT - amount))
+ SetMoney(GetMoney() + amount);
+ else
+ {
+ if (sendError)
+ SendEquipError(EQUIP_ERR_TOO_MUCH_GOLD, nullptr, nullptr);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+Unit* Player::GetSelectedUnit() const
+{
+ if (ObjectGuid selectionGUID = GetGuidValue(UNIT_FIELD_TARGET))
+ return ObjectAccessor::GetUnit(*this, selectionGUID);
+
+ return nullptr;
+}
+
+Player* Player::GetSelectedPlayer() const
+{
+ if (ObjectGuid selectionGUID = GetGuidValue(UNIT_FIELD_TARGET))
+ return ObjectAccessor::GetPlayer(*this, selectionGUID);
+
+ return nullptr;
+}
+
+void Player::SetSelection(ObjectGuid guid)
+{
+ SetGuidValue(UNIT_FIELD_TARGET, guid);
+
+ if (NeedSendSpectatorData())
+ ArenaSpectator::SendCommand_GUID(FindMap(), GetGUID(), "TRG", guid);
+}
+
+void Player::SetGroup(Group* group, int8 subgroup)
+{
+ if (!group)
+ m_group.unlink();
+ else
+ {
+ // never use SetGroup without a subgroup unless you specify nullptr for group
+ ASSERT(subgroup >= 0);
+ m_group.link(group, this);
+ m_group.setSubGroup((uint8)subgroup);
+ }
+
+ UpdateObjectVisibility(false);
+}
+
+void Player::SendInitialPacketsBeforeAddToMap()
+{
+ /// Pass 'this' as argument because we're not stored in ObjectAccessor yet
+ GetSocial()->SendSocialList(this, SOCIAL_FLAG_ALL);
+
+ // guild bank list?
+
+ // 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
+ // SMSG_UPDATE_AURA_DURATION
+
+ SendTalentsInfoData(false);
+
+ // SMSG_INSTANCE_DIFFICULTY
+ data.Initialize(SMSG_INSTANCE_DIFFICULTY, 4 + 4);
+ data << uint32(GetMap()->GetDifficulty());
+ data << uint32(GetMap()->GetEntry()->IsDynamicDifficultyMap() && GetMap()->IsHeroic()); // Raid dynamic difficulty
+ 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();
+ m_achievementMgr->SendAllAchievementData();
+
+ SendEquipmentSetList();
+
+ data.Initialize(SMSG_LOGIN_SETTIMESPEED, 4 + 4 + 4);
+ data.AppendPackedTime(GameTime::GetGameTime().count());
+ data << float(0.01666667f); // game speed
+ data << uint32(0); // added in 3.1.2
+ GetSession()->SendPacket(&data);
+
+ GetReputationMgr().SendForceReactions(); // SMSG_SET_FORCED_REACTIONS
+
+ // SMSG_TALENTS_INFO x 2 for pet (unspent points and talents in separate packets...)
+ // SMSG_PET_GUIDS
+ // SMSG_UPDATE_WORLD_STATE
+ // SMSG_POWER_UPDATE
+
+ SetMover(this);
+
+ sScriptMgr->OnSendInitialPacketsBeforeAddToMap(this, data);
+}
+
+void Player::SendInitialPacketsAfterAddToMap()
+{
+ UpdateVisibilityForPlayer(true);
+
+ GetSession()->ResetTimeSync();
+ GetSession()->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_INCREASE_MOUNTED_FLIGHT_SPEED, SPELL_AURA_NONE
+ };
+ for (AuraType const* itr = &auratypes[0]; itr && itr[0] != SPELL_AURA_NONE; ++itr)
+ {
+ Unit::AuraEffectList const& auraList = GetAuraEffectsByType(*itr);
+ if (!auraList.empty())
+ auraList.front()->HandleEffect(this, AURA_EFFECT_HANDLE_SEND_FOR_CLIENT, true);
+ }
+
+ // Fix mount, update block gets messed somewhere
+ {
+ if (!isBeingLoaded() && GetMountBlockId() && !HasAuraType(SPELL_AURA_MOUNTED))
+ {
+ AddAura(GetMountBlockId(), this);
+ SetMountBlockId(0);
+ }
+ }
+
+ // update zone
+ uint32 newzone, newarea;
+ GetZoneAndAreaId(newzone, newarea);
+ UpdateZone(newzone, newarea); // also call SendInitWorldStates();
+
+ if (HasAuraType(SPELL_AURA_MOD_STUN))
+ SetMovement(MOVE_ROOT);
+
+ // manual send package (have code in HandleEffect(this, AURA_EFFECT_HANDLE_SEND_FOR_CLIENT, true); that must not be re-applied.
+ if (HasAuraType(SPELL_AURA_MOD_ROOT))
+ {
+ WorldPacket data2(SMSG_FORCE_MOVE_ROOT, 10);
+ data2 << GetPackGUID();
+ data2 << (uint32)2;
+ SendMessageToSet(&data2, true);
+ }
+
+ GetAurasForTarget(this);
+ SendEnchantmentDurations(); // must be after add to map
+ SendItemDurations(); // must be after add to map
+ SendQuestGiverStatusMultiple();
+ SendTaxiNodeStatusMultiple();
+
+ // raid downscaling - send difficulty to player
+ if (GetMap()->IsRaid())
+ {
+ if (GetMap()->GetDifficulty() != GetRaidDifficulty())
+ {
+ StoreRaidMapDifficulty();
+ SendRaidDifficulty(GetGroup() != nullptr, GetStoredRaidDifficulty());
+ }
+ }
+ else if (GetRaidDifficulty() != GetStoredRaidDifficulty())
+ SendRaidDifficulty(GetGroup() != nullptr);
+}
+
+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_auraRaidUpdateMask = 0;
+ if (Pet* pet = GetPet())
+ pet->ResetAuraUpdateMaskForRaid();
+}
+
+void Player::SendTransferAborted(uint32 mapid, TransferAbortReason reason, uint8 arg)
+{
+ WorldPacket data(SMSG_TRANSFER_ABORTED, 4 + 2);
+ data << uint32(mapid);
+ data << uint8(reason); // transfer abort reason
+ switch (reason)
+ {
+ case TRANSFER_ABORT_INSUF_EXPAN_LVL:
+ case TRANSFER_ABORT_DIFFICULTY:
+ case TRANSFER_ABORT_UNIQUE_MESSAGE:
+ // these are the ONLY cases that have an extra argument in the packet!!!
+ data << uint8(arg);
+ break;
+ default:
+ break;
+ }
+ GetSession()->SendPacket(&data);
+}
+
+void Player::SendInstanceResetWarning(uint32 mapid, Difficulty difficulty, uint32 time, bool onEnterMap)
+{
+ // pussywizard:
+ InstancePlayerBind* bind = sInstanceSaveMgr->PlayerGetBoundInstance(GetGUID(), mapid, difficulty);
+ if (bind && bind->extended)
+ {
+ if (!onEnterMap) // extended id player shouldn't be warned about lock expiration
+ return;
+ time += (bind->save->GetExtendedResetTime() - bind->save->GetResetTime()); // add lockout period to the time left
+ }
+
+ // type of warning, based on the time remaining until reset
+ uint32 type;
+ if (time > 3600)
+ type = RAID_INSTANCE_WELCOME;
+ else if (time > 900)
+ type = RAID_INSTANCE_WARNING_HOURS;
+ else if (time > 300)
+ 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(bind && bind->perm); // is locked
+ data << uint8(bind && bind->extended); // is extended, ignored if prev field is 0
+ }
+ GetSession()->SendPacket(&data);
+}
+
+void Player::ApplyEquipCooldown(Item* pItem)
+{
+ if (pItem->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_NO_EQUIP_COOLDOWN))
+ return;
+
+ for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
+ {
+ _Spell const& spellData = pItem->GetTemplate()->Spells[i];
+
+ // no spell
+ if (!spellData.SpellId)
+ continue;
+
+ // xinef: apply hidden cooldown for procs
+ if (spellData.SpellTrigger == ITEM_SPELLTRIGGER_ON_EQUIP)
+ {
+ // xinef: uint32(-1) special marker for proc cooldowns
+ AddSpellCooldown(spellData.SpellId, uint32(-1), 30 * IN_MILLISECONDS);
+ continue;
+ }
+
+ // wrong triggering type (note: ITEM_SPELLTRIGGER_ON_NO_DELAY_USE not have cooldown)
+ if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_USE)
+ continue;
+
+ // xinef: dont apply equip cooldown if spell on item has insignificant cooldown
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellData.SpellId);
+ if (spellData.SpellCooldown <= 3000 && spellData.SpellCategoryCooldown <= 3000 && (!spellInfo || (spellInfo->RecoveryTime <= 3000 && spellInfo->CategoryRecoveryTime <= 3000)))
+ continue;
+
+ // Don't replace longer cooldowns by equip cooldown if we have any.
+ SpellCooldowns::iterator itr = m_spellCooldowns.find(spellData.SpellId);
+ if (itr != m_spellCooldowns.end() && itr->second.itemid == pItem->GetEntry() && itr->second.end > GameTime::GetGameTimeMS().count() + 30 * IN_MILLISECONDS)
+ continue;
+
+ // xinef: dont apply eqiup cooldown for spells with this attribute
+ if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR0_NOT_IN_COMBAT_ONLY_PEACEFUL))
+ continue;
+
+ AddSpellCooldown(spellData.SpellId, pItem->GetEntry(), 30 * IN_MILLISECONDS, true, true);
+
+ WorldPacket data(SMSG_ITEM_COOLDOWN, 12);
+ data << pItem->GetGUID();
+ 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 spellMap = GetSpellMap();
+
+ for (PlayerSpellMap::const_iterator iter = spellMap.begin(); iter != spellMap.end(); ++iter)
+ removeSpell(iter->first, SPEC_MASK_ALL, false);
+
+ LearnDefaultSkills();
+ LearnCustomSpells();
+ learnQuestRewardedSpells();
+}
+
+void Player::LearnCustomSpells()
+{
+ if (!sWorld->getBoolConfig(CONFIG_START_CUSTOM_SPELLS))
+ {
+ return;
+ }
+
+ // learn default race/class spells
+ PlayerInfo const* info = sObjectMgr->GetPlayerInfo(getRace(), getClass());
+ ASSERT(info);
+ for (PlayerCreateInfoSpells::const_iterator itr = info->customSpells.begin(); itr != info->customSpells.end(); ++itr)
+ {
+ uint32 tspell = *itr;
+ LOG_DEBUG("entities.player.loading", "Player::LearnCustomSpells: Player '{}' ({}, Class: {} Race: {}): Adding initial spell (SpellID: {})",
+ GetName(), GetGUID().ToString(), uint32(getClass()), uint32(getRace()), tspell);
+ if (!IsInWorld()) // will send in INITIAL_SPELLS in list anyway at map add
+ {
+ addSpell(tspell, SPEC_MASK_ALL, true);
+ }
+ else // but send in normal spell in game learn case
+ {
+ learnSpell(tspell);
+ }
+ }
+}
+
+void Player::LearnDefaultSkills()
+{
+ // learn default race/class skills
+ PlayerInfo const* info = sObjectMgr->GetPlayerInfo(getRace(), getClass());
+ for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr)
+ {
+ uint32 skillId = itr->SkillId;
+ if (HasSkill(skillId))
+ continue;
+
+ LearnDefaultSkill(skillId, itr->Rank);
+ }
+}
+
+void Player::LearnDefaultSkill(uint32 skillId, uint16 rank)
+{
+ SkillRaceClassInfoEntry const* rcInfo = GetSkillRaceClassInfo(skillId, getRace(), getClass());
+ if (!rcInfo)
+ return;
+
+ LOG_DEBUG("entities.player.loading", "PLAYER (Class: {} Race: {}): Adding initial skill, id = {}", uint32(getClass()), uint32(getRace()), skillId);
+ switch (GetSkillRangeType(rcInfo))
+ {
+ case SKILL_RANGE_LANGUAGE:
+ SetSkill(skillId, 0, 300, 300);
+ break;
+ case SKILL_RANGE_LEVEL:
+ {
+ uint16 skillValue = 1;
+ uint16 maxValue = GetMaxSkillValueForLevel();
+ if (sWorld->getBoolConfig(CONFIG_ALWAYS_MAXSKILL) && !IsProfessionOrRidingSkill(skillId))
+ {
+ skillValue = maxValue;
+ }
+ else if (rcInfo->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE)
+ {
+ skillValue = maxValue;
+ }
+ else if (getClass() == CLASS_DEATH_KNIGHT)
+ {
+ skillValue = std::min(std::max({ 1, uint16((GetLevel() - 1) * 5) }), maxValue);
+ }
+ else if (skillId == SKILL_FIST_WEAPONS)
+ {
+ skillValue = std::max(1, GetSkillValue(SKILL_UNARMED));
+ }
+ else if (skillId == SKILL_LOCKPICKING)
+ {
+ skillValue = std::max(1, GetSkillValue(SKILL_LOCKPICKING));
+ }
+
+ SetSkill(skillId, 0, skillValue, maxValue);
+ break;
+ }
+ case SKILL_RANGE_MONO:
+ SetSkill(skillId, 0, 1, 1);
+ break;
+ case SKILL_RANGE_RANK:
+ {
+ if (!rank)
+ {
+ break;
+ }
+
+ SkillTiersEntry const* tier = sSkillTiersStore.LookupEntry(rcInfo->SkillTierID);
+ uint16 maxValue = tier->Value[std::max(rank - 1, 0)];
+ uint16 skillValue = 1;
+ if (rcInfo->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE)
+ {
+ skillValue = maxValue;
+ }
+ else if (getClass() == CLASS_DEATH_KNIGHT)
+ {
+ skillValue = std::min(std::max({ uint16(1), uint16((GetLevel() - 1) * 5) }), maxValue);
+ }
+
+ SetSkill(skillId, rank, skillValue, maxValue);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void Player::learnQuestRewardedSpells(Quest const* quest)
+{
+ // xinef: quest does not learn anything
+ int32 spellId = quest->GetRewSpellCast();
+ if (!spellId)
+ return;
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (!spellInfo)
+ return;
+
+ // xinef: find effect with learn spell and check if we have this spell
+ bool found = false;
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && spellInfo->Effects[i].TriggerSpell && !HasSpell(spellInfo->Effects[i].TriggerSpell))
+ {
+ // pusywizard: don't re-add profession specialties!
+ if (SpellInfo const* triggeredInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell))
+ if (triggeredInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL)
+ break; // pussywizard: break and not cast the spell (found is false)
+
+ found = true;
+ break;
+ }
+
+ // xinef: we know the spell, return
+ if (!found)
+ return;
+
+ CastSpell(this, spellId, true);
+}
+
+void Player::learnQuestRewardedSpells()
+{
+ // learn spells received from quest completing
+ for (RewardedQuestSet::const_iterator itr = m_RewardedQuests.begin(); itr != m_RewardedQuests.end(); ++itr)
+ {
+ Quest const* quest = sObjectMgr->GetQuestTemplate(*itr);
+ 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->SkillLine != skill_id)
+ {
+ continue;
+ }
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(pAbility->Spell);
+ if (!spellInfo)
+ {
+ continue;
+ }
+
+ if (pAbility->AcquireMethod != SKILL_LINE_ABILITY_LEARNED_ON_SKILL_VALUE && pAbility->AcquireMethod != SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN)
+ {
+ continue;
+ }
+
+ // Check race if set
+ if (pAbility->RaceMask && !(pAbility->RaceMask & raceMask))
+ {
+ continue;
+ }
+
+ // Check class if set
+ if (pAbility->ClassMask && !(pAbility->ClassMask & classMask))
+ {
+ continue;
+ }
+
+ // need unlearn spell
+ if (skill_value < pAbility->MinSkillLineRank && pAbility->AcquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_VALUE)
+ {
+ removeSpell(pAbility->Spell, GetActiveSpec(), true);
+ }
+ // need learn
+ else
+ {
+ //used to avoid double Seal of Righteousness on paladins, it's the only player spell which has both spell and forward spell in auto learn
+ if (pAbility->AcquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN && pAbility->SupercededBySpell)
+ {
+ bool skipCurrent = false;
+ auto bounds = sSpellMgr->GetSkillLineAbilityMapBounds(pAbility->SupercededBySpell);
+ for (auto itr = bounds.first; itr != bounds.second; ++itr)
+ {
+ if (itr->second->AcquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN && skill_value >= itr->second->MinSkillLineRank)
+ {
+ skipCurrent = true;
+ break;
+ }
+ }
+ if (skipCurrent)
+ {
+ continue;
+ }
+ }
+
+ if (!IsInWorld())
+ {
+ addSpell(pAbility->Spell, SPEC_MASK_ALL, true, true);
+ }
+ else
+ {
+ learnSpell(pAbility->Spell, true, true);
+ }
+ }
+ }
+}
+
+void Player::GetAurasForTarget(Unit* target) // pussywizard: contact before changing ANYTHING!
+{
+ if (!target/* || target->GetVisibleAuras()->empty()*/) // speedup things
+ return;
+
+ /*! Blizz sends certain movement packets sometimes even before CreateObject
+ These movement packets are usually found in SMSG_COMPRESSED_MOVES
+ */
+ if (target->HasAuraType(SPELL_AURA_FEATHER_FALL))
+ target->SendMovementFeatherFall(this);
+
+ if (target->HasAuraType(SPELL_AURA_WATER_WALK))
+ target->SendMovementWaterWalking(this);
+
+ if (target->HasAuraType(SPELL_AURA_HOVER))
+ target->SendMovementHover(this);
+
+ WorldPacket data(SMSG_AURA_UPDATE_ALL);
+ data<< target->GetPackGUID();
+
+ Unit::VisibleAuraMap const* visibleAuras = target->GetVisibleAuras();
+ for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras->begin(); itr != visibleAuras->end(); ++itr)
+ {
+ AuraApplication* auraApp = itr->second;
+ auraApp->BuildUpdatePacket(data, false);
+ }
+
+ GetSession()->SendPacket(&data);
+}
+
+void Player::SetDailyQuestStatus(uint32 quest_id)
+{
+ if (Quest const* qQuest = sObjectMgr->GetQuestTemplate(quest_id))
+ {
+ if (!qQuest->IsDFQuest())
+ {
+ 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_lastDailyQuestTime = GameTime::GetGameTime().count(); // last daily quest time
+ m_DailyQuestChanged = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ m_DFQuests.insert(quest_id);
+ m_lastDailyQuestTime = GameTime::GetGameTime().count();
+ m_DailyQuestChanged = true;
+ }
+ }
+}
+
+bool Player::IsDailyQuestDone(uint32 quest_id)
+{
+ if (sObjectMgr->GetQuestTemplate(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) == quest_id)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void Player::SetWeeklyQuestStatus(uint32 quest_id)
+{
+ m_weeklyquests.insert(quest_id);
+ m_WeeklyQuestChanged = true;
+}
+
+void Player::SetSeasonalQuestStatus(uint32 quest_id)
+{
+ Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
+ if (!quest)
+ return;
+
+ m_seasonalquests[quest->GetEventIdForQuest()].insert(quest_id);
+ m_SeasonalQuestChanged = 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);
+
+ m_DFQuests.clear(); // Dungeon Finder Quests.
+
+ // DB data deleted in caller
+ m_DailyQuestChanged = false;
+ m_lastDailyQuestTime = 0;
+}
+
+void Player::ResetWeeklyQuestStatus()
+{
+ if (m_weeklyquests.empty())
+ return;
+
+ m_weeklyquests.clear();
+ // DB data deleted in caller
+ m_WeeklyQuestChanged = false;
+}
+
+void Player::ResetSeasonalQuestStatus(uint16 event_id)
+{
+ if (m_seasonalquests.empty() || m_seasonalquests[event_id].empty())
+ return;
+
+ m_seasonalquests.erase(event_id);
+ // DB data deleted in caller
+ m_SeasonalQuestChanged = false;
+}
+
+void Player::ResetMonthlyQuestStatus()
+{
+ if (m_monthlyquests.empty())
+ return;
+
+ m_monthlyquests.clear();
+ // DB data deleted in caller
+ m_MonthlyQuestChanged = false;
+}
+
+Battleground* Player::GetBattleground(bool create) const
+{
+ if (GetBattlegroundId() == 0)
+ return nullptr;
+
+ Battleground* bg = sBattlegroundMgr->GetBattleground(GetBattlegroundId(), GetBattlegroundTypeId());
+ return (create || (bg && bg->FindBgMap()) ? bg : nullptr);
+}
+
+bool Player::InBattlegroundQueue(bool ignoreArena) const
+{
+ for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
+ if (_BgBattlegroundQueueID[i].bgQueueTypeId != BATTLEGROUND_QUEUE_NONE &&
+ (!ignoreArena || (_BgBattlegroundQueueID[i].bgQueueTypeId != BATTLEGROUND_QUEUE_2v2 &&
+ _BgBattlegroundQueueID[i].bgQueueTypeId != BATTLEGROUND_QUEUE_3v3 &&
+ _BgBattlegroundQueueID[i].bgQueueTypeId != BATTLEGROUND_QUEUE_5v5)))
+ return true;
+ return false;
+}
+
+BattlegroundQueueTypeId Player::GetBattlegroundQueueTypeId(uint32 index) const
+{
+ return _BgBattlegroundQueueID[index].bgQueueTypeId;
+}
+
+uint32 Player::GetBattlegroundQueueIndex(BattlegroundQueueTypeId bgQueueTypeId) const
+{
+ for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
+ if (_BgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
+ return i;
+
+ return PLAYER_MAX_BATTLEGROUND_QUEUES;
+}
+
+bool Player::IsInvitedForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId) const
+{
+ for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
+ if (_BgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
+ return _BgBattlegroundQueueID[i].invitedToInstance != 0;
+
+ return false;
+}
+
+bool Player::InBattlegroundQueueForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId) const
+{
+ return GetBattlegroundQueueIndex(bgQueueTypeId) < PLAYER_MAX_BATTLEGROUND_QUEUES;
+}
+
+uint32 Player::AddBattlegroundQueueId(BattlegroundQueueTypeId val)
+{
+ for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
+ {
+ if (_BgBattlegroundQueueID[i].bgQueueTypeId == BATTLEGROUND_QUEUE_NONE || _BgBattlegroundQueueID[i].bgQueueTypeId == val)
+ {
+ _BgBattlegroundQueueID[i].bgQueueTypeId = val;
+ _BgBattlegroundQueueID[i].invitedToInstance = 0;
+ return i;
+ }
+ }
+
+ return PLAYER_MAX_BATTLEGROUND_QUEUES;
+}
+
+bool Player::HasFreeBattlegroundQueueId() const
+{
+ for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
+ if (_BgBattlegroundQueueID[i].bgQueueTypeId == BATTLEGROUND_QUEUE_NONE)
+ return true;
+
+ return false;
+}
+
+void Player::RemoveBattlegroundQueueId(BattlegroundQueueTypeId val)
+{
+ for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
+ {
+ if (_BgBattlegroundQueueID[i].bgQueueTypeId == val)
+ {
+ _BgBattlegroundQueueID[i].bgQueueTypeId = BATTLEGROUND_QUEUE_NONE;
+ _BgBattlegroundQueueID[i].invitedToInstance = 0;
+ return;
+ }
+ }
+}
+
+void Player::SetInviteForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId, uint32 instanceId)
+{
+ for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
+ if (_BgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
+ _BgBattlegroundQueueID[i].invitedToInstance = instanceId;
+}
+
+bool Player::IsInvitedForBattlegroundInstance(uint32 instanceId) const
+{
+ for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
+ if (_BgBattlegroundQueueID[i].invitedToInstance == instanceId)
+ return true;
+
+ return false;
+}
+
+bool Player::InArena() const
+{
+ Battleground* bg = GetBattleground();
+ if (!bg || !bg->isArena())
+ return false;
+
+ return true;
+}
+
+void Player::SetBattlegroundId(uint32 id, BattlegroundTypeId bgTypeId, uint32 queueSlot, bool invited, bool isRandom, TeamId teamId)
+{
+ m_bgData.bgInstanceID = id;
+ m_bgData.bgTypeID = bgTypeId;
+ m_bgData.bgQueueSlot = queueSlot;
+ m_bgData.isInvited = invited;
+ m_bgData.bgIsRandom = isRandom;
+
+ m_bgData.bgTeamId = teamId;
+ SetByteValue(PLAYER_BYTES_3, 3, uint8(teamId == TEAM_ALLIANCE ? 1 : 0));
+}
+
+bool Player::GetBGAccessByLevel(BattlegroundTypeId bgTypeId) const
+{
+ // get a template bg instead of running one
+ Battleground* bgt = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId);
+ if (!bgt)
+ return false;
+
+ // limit check leel to dbc compatible level range
+ uint32 level = GetLevel();
+ if (level > DEFAULT_MAX_LEVEL)
+ level = DEFAULT_MAX_LEVEL;
+
+ if (level < bgt->GetMinLevel() || level > bgt->GetMaxLevel())
+ return false;
+
+ return true;
+}
+
+float Player::GetReputationPriceDiscount(Creature const* creature) const
+{
+ return GetReputationPriceDiscount(creature->GetFactionTemplateEntry());
+}
+
+float Player::GetReputationPriceDiscount(FactionTemplateEntry const* factionTemplate) const
+{
+ if (!factionTemplate || !factionTemplate->faction)
+ {
+ return 1.0f;
+ }
+
+ ReputationRank rank = GetReputationRank(factionTemplate->faction);
+ if (rank <= REP_NEUTRAL)
+ {
+ return 1.0f;
+ }
+
+ return 1.0f - 0.05f * (rank - REP_NEUTRAL);
+}
+
+bool Player::IsSpellFitByClassAndRace(uint32 spell_id) 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)
+ {
+ // skip wrong race skills
+ if (_spell_idx->second->RaceMask && (_spell_idx->second->RaceMask & racemask) == 0)
+ continue;
+
+ // skip wrong class skills
+ if (_spell_idx->second->ClassMask && (_spell_idx->second->ClassMask & classmask) == 0)
+ continue;
+
+ return true;
+ }
+
+ return false;
+}
+
+bool Player::HasQuestForGO(int32 GOId) const
+{
+ for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
+ {
+ uint32 questid = GetQuestSlotQuestId(i);
+ if (questid == 0)
+ continue;
+
+ QuestStatusMap::const_iterator qs_itr = m_QuestStatus.find(questid);
+ if (qs_itr == m_QuestStatus.end())
+ continue;
+
+ QuestStatusData const& qs = qs_itr->second;
+
+ if (qs.Status == QUEST_STATUS_INCOMPLETE)
+ {
+ Quest const* qinfo = sObjectMgr->GetQuestTemplate(questid);
+ if (!qinfo)
+ continue;
+
+ if (GetGroup() && GetGroup()->isRaidGroup() && !qinfo->IsAllowedInRaid(GetMap()->GetDifficulty()))
+ continue;
+
+ for (uint8 j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
+ {
+ if (qinfo->RequiredNpcOrGo[j] >= 0) //skip non GO case
+ continue;
+
+ if ((-1)*GOId == qinfo->RequiredNpcOrGo[j] && qs.CreatureOrGOCount[j] < qinfo->RequiredNpcOrGoCount[j])
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void Player::SummonIfPossible(bool agree, ObjectGuid summoner_guid)
+{
+ if (!agree)
+ {
+ m_summon_expire = 0;
+ return;
+ }
+
+ // expire and auto declined
+ if (m_summon_expire < GameTime::GetGameTime().count())
+ return;
+
+ // 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;
+
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ACCEPTED_SUMMONINGS, 1);
+
+ TeleportTo(m_summon_mapid, m_summon_x, m_summon_y, m_summon_z, GetOrientation(), 0, ObjectAccessor::FindPlayer(summoner_guid));
+}
+
+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(bool force /*= false*/)
+{
+ Item* offItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
+ if (!offItem)
+ {
+ UpdateTitansGrip();
+ return;
+ }
+
+ // unequip offhand weapon if player doesn't have dual wield anymore
+ if (!CanDualWield() && (offItem->GetTemplate()->InventoryType == INVTYPE_WEAPONOFFHAND || offItem->GetTemplate()->InventoryType == INVTYPE_WEAPON))
+ force = true;
+
+ // need unequip offhand for 2h-weapon without TitanGrip (in any from hands)
+ if (!force && (CanTitanGrip() || (offItem->GetTemplate()->InventoryType != INVTYPE_2HWEAPON && !IsTwoHandUsed())))
+ {
+ UpdateTitansGrip();
+ 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);
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+ offItem->DeleteFromInventoryDB(trans); // deletes item from character's inventory
+ offItem->SaveToDB(trans); // recursive and not have transaction guard into self, item not in inventory and can be save standalone
+
+ std::string subject = GetSession()->GetAcoreString(LANG_NOT_EQUIPPED_ITEM);
+ MailDraft(subject, "There were problems with equipping one or several items").AddItem(offItem).SendMailTo(trans, this, MailSender(this, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_COPIED);
+
+ CharacterDatabase.CommitTransaction(trans);
+ }
+ UpdateTitansGrip();
+}
+
+OutdoorPvP* Player::GetOutdoorPvP() const
+{
+ return sOutdoorPvPMgr->GetOutdoorPvPToZoneId(GetZoneId());
+}
+
+bool Player::HasItemFitToSpellRequirements(SpellInfo const* spellInfo, Item const* ignoreItem) const
+{
+ if (spellInfo->EquippedItemClass < 0)
+ return true;
+
+ // scan other equipped items for same requirements (mostly 2 daggers/etc)
+ // for optimize check 2 used cases only
+ switch (spellInfo->EquippedItemClass)
+ {
+ case ITEM_CLASS_WEAPON:
+ {
+ for (uint8 i = EQUIPMENT_SLOT_MAINHAND; i < EQUIPMENT_SLOT_TABARD; ++i)
+ if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, i))
+ if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
+ return true;
+ break;
+ }
+ case ITEM_CLASS_ARMOR:
+ {
+ // tabard not have dependent spells
+ for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_MAINHAND; ++i)
+ if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, i))
+ if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
+ return true;
+
+ // shields can be equipped to offhand slot
+ if (Item* item = GetUseableItemByPos(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 = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
+ if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
+ return true;
+
+ break;
+ }
+ default:
+ LOG_ERROR("entities.player", "HasItemFitToSpellRequirements: Not handled spell requirement for item class {}", spellInfo->EquippedItemClass);
+ break;
+ }
+
+ return false;
+}
+
+bool Player::CanNoReagentCast(SpellInfo const* spellInfo) const
+{
+ // don't take reagents for spells with SPELL_ATTR5_NO_REAGENT_COST_WITH_AURA
+ if (spellInfo->HasAttribute(SPELL_ATTR5_NO_REAGENT_COST_WITH_AURA) && HasUnitFlag(UNIT_FLAG_PREPARATION))
+ return true;
+
+ // Check no reagent use mask
+ flag96 noReagentMask;
+ noReagentMask[0] = GetUInt32Value(PLAYER_NO_REAGENT_COST_1);
+ noReagentMask[1] = GetUInt32Value(PLAYER_NO_REAGENT_COST_1 + 1);
+ noReagentMask[2] = GetUInt32Value(PLAYER_NO_REAGENT_COST_1 + 2);
+ if (spellInfo->SpellFamilyFlags & noReagentMask)
+ return true;
+
+ return false;
+}
+
+void Player::RemoveItemDependentAurasAndCasts(Item* pItem)
+{
+ for (AuraMap::iterator itr = m_ownedAuras.begin(); itr != m_ownedAuras.end();)
+ {
+ Aura* aura = itr->second;
+
+ // skip passive (passive item dependent spells work in another way) and not self applied auras
+ SpellInfo const* spellInfo = aura->GetSpellInfo();
+ if (aura->IsPassive() || aura->GetCasterGUID() != GetGUID())
+ {
+ ++itr;
+ continue;
+ }
+
+ // skip if not item dependent or have alternative item
+ if (HasItemFitToSpellRequirements(spellInfo, pItem))
+ {
+ ++itr;
+ continue;
+ }
+
+ // no alt item, remove aura, restart check
+ RemoveOwnedAura(itr);
+ }
+
+ // 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 && !HasItemFitToSpellRequirements(spell->m_spellInfo, pItem))
+ InterruptSpell(CurrentSpellTypes(i));
+}
+
+uint32 Player::GetResurrectionSpellId()
+{
+ // search priceless resurrection possibilities
+ uint32 prio = 0;
+ uint32 spell_id = 0;
+ AuraEffectList const& dummyAuras = GetAuraEffectsByType(SPELL_AURA_DUMMY);
+ for (AuraEffectList::const_iterator itr = dummyAuras.begin(); itr != dummyAuras.end(); ++itr)
+ {
+ // Soulstone Resurrection // prio: 3 (max, non death persistent)
+ if (prio < 2 && (*itr)->GetSpellInfo()->SpellVisual[0] == 99 && (*itr)->GetSpellInfo()->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:
+ LOG_ERROR("entities.player", "Unhandled spell {}: 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
+ if (prio < 1 && HasSpell(20608) && !HasSpellCooldown(21169) && (HasAura(58059) || HasItemCount(17030)))
+ spell_id = 21169;
+
+ return spell_id;
+}
+
+// Used in triggers for check "Only to targets that grant experience or honor" req
+bool Player::isHonorOrXPTarget(Unit* victim) const
+{
+ uint8 v_level = victim->GetLevel();
+ uint8 k_grey = Acore::XP::GetGrayLevel(GetLevel());
+
+ // Victim level less gray level
+ if (v_level <= k_grey)
+ return false;
+
+ if (victim->GetTypeId() == TYPEID_UNIT)
+ {
+ //npcbot: count npcbots at xp targets (DEPRECATED)
+ if (victim->ToCreature()->IsNPCBotOrPet())
+ return true;
+ //end npcbots
+
+ if (victim->IsTotem() ||
+ victim->IsPet() ||
+ victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_XP)
+ return false;
+ }
+ return true;
+}
+
+bool Player::GetsRecruitAFriendBonus(bool forXP)
+{
+ bool recruitAFriend = false;
+ if (GetLevel() <= sWorld->getIntConfig(CONFIG_MAX_RECRUIT_A_FRIEND_BONUS_PLAYER_LEVEL) || !forXP)
+ {
+ if (Group* group = this->GetGroup())
+ {
+ for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
+ {
+ Player* player = itr->GetSource();
+ if (!player || !player->IsInMap(this))
+ continue;
+
+ if (!player->IsAtRecruitAFriendDistance(this))
+ continue; // member (alive or dead) or his corpse at req. distance
+
+ if (forXP)
+ {
+ // level must be allowed to get RaF bonus
+ if (player->GetLevel() > sWorld->getIntConfig(CONFIG_MAX_RECRUIT_A_FRIEND_BONUS_PLAYER_LEVEL))
+ continue;
+
+ // level difference must be small enough to get RaF bonus, UNLESS we are lower level
+ if (player->GetLevel() < GetLevel())
+ if (uint8(GetLevel() - player->GetLevel()) > sWorld->getIntConfig(CONFIG_MAX_RECRUIT_A_FRIEND_BONUS_PLAYER_LEVEL_DIFFERENCE))
+ continue;
+ }
+
+ bool ARecruitedB = (player->GetSession()->GetRecruiterId() == GetSession()->GetAccountId());
+ bool BRecruitedA = (GetSession()->GetRecruiterId() == player->GetSession()->GetAccountId());
+ if (ARecruitedB || BRecruitedA)
+ {
+ recruitAFriend = true;
+ break;
+ }
+ }
+ }
+ }
+ return recruitAFriend;
+}
+
+void Player::RewardPlayerAndGroupAtKill(Unit* victim, bool isBattleGround)
+{
+ KillRewarder(this, victim, isBattleGround).Reward();
+}
+
+void Player::RewardPlayerAndGroupAtEvent(uint32 creature_id, WorldObject* pRewardSource)
+{
+ if (!pRewardSource)
+ return;
+
+ ObjectGuid creature_guid = (pRewardSource->GetTypeId() == TYPEID_UNIT) ? pRewardSource->GetGUID() : ObjectGuid::Empty;
+
+ // prepare data for near group iteration
+ if (Group* group = GetGroup())
+ {
+ for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
+ {
+ Player* player = itr->GetSource();
+ if (!player)
+ continue;
+
+ if (!player->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 (player->IsAlive() || !player->GetCorpse())
+ player->KilledMonsterCredit(creature_id, creature_guid);
+ }
+ }
+ else // if (!group)
+ KilledMonsterCredit(creature_id, creature_guid);
+}
+
+bool Player::IsAtGroupRewardDistance(WorldObject const* pRewardSource) const
+{
+ WorldObject const* player = GetCorpse();
+ if (!player || IsAlive())
+ {
+ player = this;
+ }
+
+ if (!pRewardSource || !player->IsInMap(pRewardSource))
+ {
+ return false;
+ }
+
+ if (pRewardSource->GetMap()->IsDungeon())
+ {
+ return true;
+ }
+
+ return pRewardSource->GetDistance(player) <= sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE);
+}
+
+bool Player::IsAtLootRewardDistance(WorldObject const* pRewardSource) const
+{
+ if (!IsAtGroupRewardDistance(pRewardSource))
+ {
+ return false;
+ }
+
+ if (HasPendingBind())
+ {
+ return false;
+ }
+
+ return pRewardSource->HasAllowedLooter(GetGUID());
+}
+
+bool Player::IsAtRecruitAFriendDistance(WorldObject const* pOther) const
+{
+ if (!pOther)
+ return false;
+ WorldObject const* player = GetCorpse();
+ if (!player || IsAlive())
+ player = this;
+
+ if (player->GetMapId() != pOther->GetMapId() || player->GetInstanceId() != pOther->GetInstanceId())
+ return false;
+
+ return pOther->GetDistance(player) <= sWorld->getFloatConfig(CONFIG_MAX_RECRUIT_A_FRIEND_DISTANCE);
+}
+
+uint32 Player::GetBaseWeaponSkillValue(WeaponAttackType attType) const
+{
+ Item* item = GetWeaponForAttack(attType, true);
+
+ // unarmed only with base attack
+ if (attType != BASE_ATTACK && !item)
+ return 0;
+
+ // weapon skill or (unarmed for base attack)
+ uint32 skill = item ? item->GetSkill() : uint32(SKILL_UNARMED);
+ return GetBaseSkillValue(skill);
+}
+
+void Player::ResurectUsingRequestData()
+{
+ /// Teleport before resurrecting by player, otherwise the player might get attacked from creatures near his corpse
+ TeleportTo(m_resurrectMap, m_resurrectX, m_resurrectY, m_resurrectZ, GetOrientation());
+
+ if (IsBeingTeleported())
+ {
+ ScheduleDelayedOperation(DELAYED_RESURRECT_PLAYER);
+ return;
+ }
+
+ ResurrectPlayer(0.0f, false);
+
+ if (GetMaxHealth() > m_resurrectHealth)
+ SetHealth(m_resurrectHealth);
+ else
+ SetFullHealth();
+
+ 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, bool allowMove, bool packetOnly /*= false*/)
+{
+ WorldPacket data(SMSG_CLIENT_CONTROL_UPDATE, target->GetPackGUID().size() + 1);
+ data << target->GetPackGUID();
+ data << uint8((allowMove && !target->HasUnitState(UNIT_STATE_FLEEING | UNIT_STATE_CONFUSED)) ? 1 : 0);
+ GetSession()->SendPacket(&data);
+
+ // We want to set the packet only
+ if (packetOnly)
+ return;
+
+ if (this != target)
+ SetViewpoint(target, allowMove);
+
+ if (allowMove)
+ SetMover(target);
+
+ // Xinef: disable moving if target has disable move flag
+ if (target->GetTypeId() != TYPEID_UNIT)
+ return;
+
+ if (allowMove && target->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE))
+ {
+ target->ClearUnitState(UNIT_STATE_ROOT);
+ target->SetControlled(true, UNIT_STATE_ROOT);
+ }
+ else if (!allowMove && target->HasUnitState(UNIT_STATE_ROOT) && !target->HasUnitTypeMask(UNIT_MASK_ACCESSORY))
+ {
+ if (target->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE))
+ {
+ // Xinef: restore original orientation, important for shooting vehicles!
+ Position pos = target->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && target->GetTransGUID() && target->GetTransGUID().IsMOTransport() ? target->ToCreature()->GetTransportHomePosition() : target->ToCreature()->GetHomePosition();
+ target->SetOrientation(pos.GetOrientation());
+ target->SetFacingTo(pos.GetOrientation());
+ target->DisableSpline();
+ }
+ else
+ target->SetControlled(false, UNIT_STATE_ROOT);
+ }
+}
+
+void Player::SetMover(Unit* target)
+{
+ if (this != target && target->m_movedByPlayer && target->m_movedByPlayer != target && target->m_movedByPlayer != this)
+ {
+ LOG_INFO("misc", "Player::SetMover (A1) - {}, {}, {}, {}, {}, {}, {}, {}", GetGUID().ToString(), GetMapId(), GetInstanceId(), FindMap()->GetId(), IsInWorld() ? 1 : 0, IsDuringRemoveFromWorld() ? 1 : 0, IsBeingTeleported() ? 1 : 0, isBeingLoaded() ? 1 : 0);
+ LOG_INFO("misc", "Player::SetMover (A2) - {}, {}, {}, {}, {}, {}, {}, {}", target->GetGUID().ToString(), target->GetMapId(), target->GetInstanceId(), target->FindMap()->GetId(), target->IsInWorld() ? 1 : 0, target->IsDuringRemoveFromWorld() ? 1 : 0, (target->ToPlayer() && target->ToPlayer()->IsBeingTeleported() ? 1 : 0), target->isBeingLoaded() ? 1 : 0);
+ LOG_INFO("misc", "Player::SetMover (A3) - {}, {}, {}, {}, {}, {}, {}, {}", target->m_movedByPlayer->GetGUID().ToString(), target->m_movedByPlayer->GetMapId(), target->m_movedByPlayer->GetInstanceId(), target->m_movedByPlayer->FindMap()->GetId(), target->m_movedByPlayer->IsInWorld() ? 1 : 0, target->m_movedByPlayer->IsDuringRemoveFromWorld() ? 1 : 0, target->m_movedByPlayer->ToPlayer()->IsBeingTeleported() ? 1 : 0, target->m_movedByPlayer->isBeingLoaded() ? 1 : 0);
+ }
+ if (this != target && (!target->IsInWorld() || target->IsDuringRemoveFromWorld() || GetMapId() != target->GetMapId() || GetInstanceId() != target->GetInstanceId()))
+ {
+ LOG_INFO("misc", "Player::SetMover (B1) - {}, {}, {}, {}, {}, {}, {}, {}", GetGUID().ToString(), GetMapId(), GetInstanceId(), FindMap()->GetId(), IsInWorld() ? 1 : 0, IsDuringRemoveFromWorld() ? 1 : 0, IsBeingTeleported() ? 1 : 0, isBeingLoaded() ? 1 : 0);
+ LOG_INFO("misc", "Player::SetMover (B2) - {}, {}, {}, {}, {}, {}, {}, {}", target->GetGUID().ToString(), target->GetMapId(), target->GetInstanceId(), target->FindMap()->GetId(), target->IsInWorld() ? 1 : 0, target->IsDuringRemoveFromWorld() ? 1 : 0, (target->ToPlayer() && target->ToPlayer()->IsBeingTeleported() ? 1 : 0), target->isBeingLoaded() ? 1 : 0);
+ }
+ m_mover->m_movedByPlayer = nullptr;
+ if (m_mover->GetTypeId() == TYPEID_UNIT)
+ m_mover->GetMotionMaster()->Initialize();
+
+ m_mover = target;
+ m_mover->m_movedByPlayer = this;
+ if (m_mover->GetTypeId() == TYPEID_UNIT)
+ m_mover->GetMotionMaster()->Initialize();
+}
+
+uint32 Player::GetCorpseReclaimDelay(bool pvp) const
+{
+ if (pvp)
+ {
+ if (!sWorld->getBoolConfig(CONFIG_DEATH_CORPSE_RECLAIM_DELAY_PVP))
+ return copseReclaimDelay[0];
+ }
+ else if (!sWorld->getBoolConfig(CONFIG_DEATH_CORPSE_RECLAIM_DELAY_PVE))
+ return 0;
+
+ time_t now = GameTime::GetGameTime().count();
+ // 0..2 full period
+ // should be std::ceil(x)-1 but not floor(x)
+ uint64 count = (now < m_deathExpireTime - 1) ? (m_deathExpireTime - 1 - now) / DEATH_EXPIRE_STEP : 0;
+ return copseReclaimDelay[count];
+}
+
+int32 Player::CalculateCorpseReclaimDelay(bool load)
+{
+ Corpse* corpse = GetCorpse();
+
+ if (load && !corpse)
+ return -1;
+
+ bool pvp = corpse ? corpse->GetType() == CORPSE_RESURRECTABLE_PVP : m_ExtraFlags & PLAYER_EXTRA_PVP_DEATH;
+
+ uint32 delay;
+
+ if (load)
+ {
+ if (corpse->GetGhostTime() > m_deathExpireTime)
+ return -1;
+
+ uint64 count = 0;
+
+ if ((pvp && sWorld->getBoolConfig(CONFIG_DEATH_CORPSE_RECLAIM_DELAY_PVP)) ||
+ (!pvp && sWorld->getBoolConfig(CONFIG_DEATH_CORPSE_RECLAIM_DELAY_PVE)))
+ {
+ count = (m_deathExpireTime - corpse->GetGhostTime()) / DEATH_EXPIRE_STEP;
+
+ if (count >= MAX_DEATH_COUNT)
+ count = MAX_DEATH_COUNT - 1;
+ }
+
+ time_t expected_time = corpse->GetGhostTime() + copseReclaimDelay[count];
+ time_t now = GameTime::GetGameTime().count();
+
+ if (now >= expected_time)
+ return -1;
+
+ delay = expected_time - now;
+ }
+ else
+ delay = GetCorpseReclaimDelay(pvp);
+
+ return delay * IN_MILLISECONDS;
+}
+
+void Player::SendCorpseReclaimDelay(uint32 delay)
+{
+ WorldPacket data(SMSG_CORPSE_RECLAIM_DELAY, 4);
+ data << uint32(delay);
+ GetSession()->SendPacket(&data);
+}
+
+Player* Player::GetNextRandomRaidMember(float radius)
+{
+ Group* group = GetGroup();
+ if (!group)
+ return nullptr;
+
+ std::vector nearMembers;
+ nearMembers.reserve(group->GetMembersCount());
+
+ for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; 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 nullptr;
+
+ uint32 randTarget = urand(0, nearMembers.size() - 1);
+ return nearMembers[randTarget];
+}
+
+PartyResult Player::CanUninviteFromGroup(ObjectGuid targetPlayerGUID) const
+{
+ Group const* grp = GetGroup();
+ if (!grp)
+ return ERR_NOT_IN_GROUP;
+
+ if (grp->isLFGGroup(true))
+ {
+ ObjectGuid gguid = grp->GetGUID();
+ if (!sLFGMgr->GetKicksLeft(gguid))
+ return ERR_PARTY_LFG_BOOT_LIMIT;
+
+ lfg::LfgState state = sLFGMgr->GetState(gguid);
+ if (state == lfg::LFG_STATE_BOOT)
+ return ERR_PARTY_LFG_BOOT_IN_PROGRESS;
+
+ if (grp->GetMembersCount() <= lfg::LFG_GROUP_KICK_VOTES_NEEDED)
+ return ERR_PARTY_LFG_BOOT_TOO_FEW_PLAYERS;
+
+ if (state == lfg::LFG_STATE_FINISHED_DUNGEON)
+ return ERR_PARTY_LFG_BOOT_DUNGEON_COMPLETE;
+
+ if (grp->isRollLootActive())
+ return ERR_PARTY_LFG_BOOT_LOOT_ROLLS;
+
+ // TODO: Should also be sent when anyone has recently left combat, with an aprox ~5 seconds timer.
+ for (GroupReference const* itr = grp->GetFirstMember(); itr != nullptr; itr = itr->next())
+ if (itr->GetSource() && itr->GetSource()->IsInMap(this) && itr->GetSource()->IsInCombat())
+ return ERR_PARTY_LFG_BOOT_IN_COMBAT;
+
+ if (Player* target = ObjectAccessor::FindConnectedPlayer(targetPlayerGUID))
+ {
+ if (Aura* dungeonCooldownAura = target->GetAura(lfg::LFG_SPELL_DUNGEON_COOLDOWN))
+ {
+ int32 elapsedTime = dungeonCooldownAura->GetMaxDuration() - dungeonCooldownAura->GetDuration();
+ if (static_cast(sWorld->getIntConfig(CONFIG_LFG_KICK_PREVENTION_TIMER)) > elapsedTime)
+ {
+ return ERR_PARTY_LFG_BOOT_NOT_ELIGIBLE_S;
+ }
+ }
+ }
+
+ /* Missing support for these types
+ return ERR_PARTY_LFG_BOOT_COOLDOWN_S;
+ */
+ }
+ else
+ {
+ if (!grp->IsLeader(GetGUID()) && !grp->IsAssistant(GetGUID()))
+ return ERR_NOT_LEADER;
+
+ if (InBattleground())
+ return ERR_INVITE_RESTRICTED;
+ }
+
+ return ERR_PARTY_RESULT_OK;
+}
+
+bool Player::isUsingLfg()
+{
+ return sLFGMgr->GetState(GetGUID()) != lfg::LFG_STATE_NONE;
+}
+
+bool Player::inRandomLfgDungeon()
+{
+ if (sLFGMgr->selectedRandomLfgDungeon(GetGUID()))
+ {
+ Map const* map = GetMap();
+ return sLFGMgr->inLfgDungeonMap(GetGUID(), map->GetId(), map->GetDifficulty());
+ }
+
+ return false;
+}
+
+void Player::SetBattlegroundOrBattlefieldRaid(Group* group, int8 subgroup)
+{
+ //we must move references from m_group to m_originalGroup
+ if (GetGroup() && (GetGroup()->isBGGroup() || GetGroup()->isBFGroup()))
+ {
+ LOG_INFO("misc", "Player::SetBattlegroundOrBattlefieldRaid - current group is {} group!", (GetGroup()->isBGGroup() ? "BG" : "BF"));
+ //ABORT(); // pussywizard: origanal group can never be bf/bg group
+ }
+
+ //npcbot: add bots to new group
+ if (HaveBot() && GetGroup())
+ {
+ BotMap const* map = GetBotMgr()->GetBotMap();
+ for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr)
+ {
+ Creature const* bot = itr->second;
+ if (!bot || !GetGroup()->IsMember(bot->GetGUID()))
+ continue;
+
+ ASSERT(group->AddMember((Player*)bot));
+ }
+ }
+ //end npcbot
+
+ SetOriginalGroup(GetGroup(), GetSubGroup());
+
+ m_group.unlink();
+ m_group.link(group, this);
+ m_group.setSubGroup((uint8)subgroup);
+}
+
+void Player::RemoveFromBattlegroundOrBattlefieldRaid()
+{
+ //remove existing reference
+ m_group.unlink();
+ if (Group* group = GetOriginalGroup())
+ {
+ m_group.link(group, this);
+ m_group.setSubGroup(GetOriginalSubGroup());
+ }
+ SetOriginalGroup(nullptr);
+}
+
+void Player::SetOriginalGroup(Group* group, int8 subgroup)
+{
+ if (!group)
+ m_originalGroup.unlink();
+ else
+ {
+ // never use SetOriginalGroup without a subgroup unless you specify nullptr for group
+ ASSERT(subgroup >= 0);
+ m_originalGroup.link(group, this);
+ m_originalGroup.setSubGroup((uint8)subgroup);
+ }
+}
+
+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();
+}
+
+void Player::SetCanTitanGrip(bool value)
+{
+ m_canTitanGrip = value;
+}
+
+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;
+}
+
+void Player::StopCastingBindSight()
+{
+ if (WorldObject* target = GetViewpoint())
+ {
+ if (target->isType(TYPEMASK_UNIT))
+ {
+ ((Unit*)target)->RemoveAurasByType(SPELL_AURA_BIND_SIGHT, GetGUID());
+ ((Unit*)target)->RemoveAurasByType(SPELL_AURA_MOD_POSSESS, GetGUID());
+ ((Unit*)target)->RemoveAurasByType(SPELL_AURA_MOD_POSSESS_PET, GetGUID());
+ }
+ }
+}
+
+void Player::SetViewpoint(WorldObject* target, bool apply)
+{
+ if (apply)
+ {
+ LOG_DEBUG("maps", "Player::CreateViewpoint: Player {} create seer {} (TypeId: {}).", GetName(), target->GetEntry(), target->GetTypeId());
+
+ if (!AddGuidValue(PLAYER_FARSIGHT, target->GetGUID()))
+ {
+ LOG_DEBUG("entities.player", "Player::CreateViewpoint: Player {} cannot add new viewpoint!", GetName());
+ return;
+ }
+
+ // farsight dynobj or puppet may be very far away
+ UpdateVisibilityOf(target);
+
+ if (target->isType(TYPEMASK_UNIT) && !GetVehicle())
+ ((Unit*)target)->AddPlayerToVision(this);
+ SetSeer(target);
+ }
+ else
+ {
+ //must immediately set seer back otherwise may crash
+ m_seer = this;
+
+ LOG_DEBUG("maps", "Player::CreateViewpoint: Player {} remove seer", GetName());
+
+ if (!RemoveGuidValue(PLAYER_FARSIGHT, target->GetGUID()))
+ {
+ LOG_DEBUG("entities.player", "Player::CreateViewpoint: Player {} cannot remove current viewpoint!", GetName());
+ return;
+ }
+
+ if (target->isType(TYPEMASK_UNIT) && !GetVehicle())
+ static_cast(target)->RemovePlayerFromVision(this);
+
+ // must immediately set seer back otherwise may crash
+ SetSeer(this);
+
+ //WorldPacket data(SMSG_CLEAR_FAR_SIGHT_IMMEDIATE, 0);
+ //GetSession()->SendPacket(&data);
+ }
+}
+
+WorldObject* Player::GetViewpoint() const
+{
+ if (ObjectGuid guid = GetGuidValue(PLAYER_FARSIGHT))
+ return static_cast(ObjectAccessor::GetObjectByTypeMask(*this, guid, TYPEMASK_SEER));
+ return nullptr;
+}
+
+bool Player::CanUseBattlegroundObject(GameObject* gameobject) const
+{
+ // It is possible to call this method will a nullptr pointer, only skipping faction check.
+ if (gameobject)
+ {
+ FactionTemplateEntry const* playerFaction = GetFactionTemplateEntry();
+ FactionTemplateEntry const* faction = sFactionTemplateStore.LookupEntry(gameobject->GetUInt32Value(GAMEOBJECT_FACTION));
+
+ if (playerFaction && faction && !playerFaction->IsFriendlyTo(*faction))
+ return false;
+ }
+
+ /**
+ * @bug
+ * sometimes when player clicks on flag in AB - client won't send gameobject_use, only gameobject_report_use packet
+ * Note: Mount, stealth and invisibility will be removed when used
+ */
+ return (!isTotalImmune() && // Damage immune
+ !HasAura(SPELL_RECENTLY_DROPPED_FLAG) && // Still has recently held flag debuff
+ IsAlive()); // Alive
+}
+
+bool Player::CanCaptureTowerPoint() const
+{
+ return (!HasStealthAura() && // not stealthed
+ !HasInvisibilityAura() && // not invisible
+ IsAlive() // live player
+ );
+}
+
+uint32 Player::GetBarberShopCost(uint8 newhairstyle, uint8 newhaircolor, uint8 newfacialhair, BarberShopStyleEntry const* newSkin)
+{
+ uint8 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 skincolor = GetByteValue(PLAYER_BYTES, 0);
+
+ if ((hairstyle == newhairstyle) && (haircolor == newhaircolor) && (facialhair == newfacialhair) && (!newSkin || (newSkin->hair_id == skincolor)))
+ 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 (newSkin && skincolor != newSkin->hair_id)
+ cost += bsc->cost * 0.75f; // +5/6 of price
+
+ return uint32(cost);
+}
+
+void Player::InitGlyphsForLevel()
+{
+ for (uint32 i = 0; i < sGlyphSlotStore.GetNumRows(); ++i)
+ if (GlyphSlotEntry const* gs = sGlyphSlotStore.LookupEntry(i))
+ if (gs->Order)
+ SetGlyphSlot(gs->Order - 1, gs->Id);
+
+ uint8 level = GetLevel();
+ uint32 value = 0;
+
+ // 0x3F = 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 for 80 level
+ if (level >= 15)
+ value |= (0x01 | 0x02);
+ if (level >= 30)
+ value |= 0x08;
+ if (level >= 50)
+ value |= 0x04;
+ if (level >= 70)
+ value |= 0x10;
+ if (level >= 80)
+ value |= 0x20;
+
+ SetUInt32Value(PLAYER_GLYPHS_ENABLED, value);
+}
+
+bool Player::isTotalImmune() const
+{
+ AuraEffectList const& immune = GetAuraEffectsByType(SPELL_AURA_SCHOOL_IMMUNITY);
+
+ uint32 immuneMask = 0;
+ for (AuraEffectList::const_iterator itr = immune.begin(); itr != immune.end(); ++itr)
+ {
+ immuneMask |= (*itr)->GetMiscValue();
+ 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;
+
+ // Clear the current title if it is the one being removed.
+ if (title->bit_index == GetUInt32Value(PLAYER_CHOSEN_TITLE))
+ {
+ SetCurrentTitle(nullptr, true);
+ }
+
+ 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);
+}
+
+uint32 Player::GetRuneBaseCooldown(uint8 index, bool skipGrace)
+{
+ uint8 rune = GetBaseRune(index);
+ uint32 cooldown = RUNE_BASE_COOLDOWN;
+ if (!skipGrace)
+ cooldown -= GetGracePeriod(index) < 250 ? 0 : GetGracePeriod(index) - 250; // xinef: reduce by grace period, treat first 250ms as instant use of rune
+
+ AuraEffectList const& regenAura = GetAuraEffectsByType(SPELL_AURA_MOD_POWER_REGEN_PERCENT);
+ for (AuraEffectList::const_iterator i = regenAura.begin(); i != regenAura.end(); ++i)
+ {
+ if ((*i)->GetMiscValue() == POWER_RUNE && (*i)->GetMiscValueB() == rune)
+ cooldown = cooldown * (100 - (*i)->GetAmount()) / 100;
+ }
+
+ return cooldown;
+}
+
+void Player::RemoveRunesByAuraEffect(AuraEffect const* aura)
+{
+ for (uint8 i = 0; i < MAX_RUNES; ++i)
+ {
+ if (m_runes->runes[i].ConvertAura == aura)
+ {
+ ConvertRune(i, GetBaseRune(i));
+ SetRuneConvertAura(i, nullptr);
+ }
+ }
+}
+
+void Player::RestoreBaseRune(uint8 index)
+{
+ AuraEffect 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 && !aura->GetSpellInfo()->HasAttribute(SPELL_ATTR0_PASSIVE))
+ return;
+ ConvertRune(index, GetBaseRune(index));
+ SetRuneConvertAura(index, nullptr);
+ // Don't drop passive talents providing rune convertion
+ if (!aura || aura->GetAuraType() != SPELL_AURA_CONVERT_RUNE)
+ return;
+ for (uint8 i = 0; i < MAX_RUNES; ++i)
+ {
+ if (aura == m_runes->runes[i].ConvertAura)
+ return;
+ }
+ aura->GetBase()->Remove();
+}
+
+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(uint8 count)
+{
+ WorldPacket data(SMSG_RESYNC_RUNES, 4 + count * 2);
+ data << uint32(count);
+ for (uint32 i = 0; i < count; ++i)
+ {
+ data << uint8(GetCurrentRune(i)); // rune type
+ data << uint8(255 - (GetRuneCooldown(i) * 51)); // passed cooldown time (0-255)
+ }
+ 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);
+}
+
+static RuneType runeSlotTypes[MAX_RUNES] =
+{
+ /*0*/ RUNE_BLOOD,
+ /*1*/ RUNE_BLOOD,
+ /*2*/ RUNE_UNHOLY,
+ /*3*/ RUNE_UNHOLY,
+ /*4*/ RUNE_FROST,
+ /*5*/ RUNE_FROST
+};
+
+void Player::InitRunes()
+{
+ if (getClass() != CLASS_DEATH_KNIGHT)
+ return;
+
+ m_runes = new Runes;
+
+ m_runes->runeState = 0;
+ m_runes->lastUsedRune = RUNE_BLOOD;
+
+ for (uint8 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
+ SetGracePeriod(i, 0); // xinef: reset grace period
+ SetRuneConvertAura(i, nullptr);
+ 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 (uint8 i = 0; i < MAX_RUNES; ++i)
+ if (GetBaseRune(i) == runeType && GetRuneCooldown(i) == 0)
+ return false;
+
+ return true;
+}
+
+void Player::AutoStoreLoot(uint8 bag, uint8 slot, uint32 loot_id, LootStore const& store, bool broadcast)
+{
+ Loot loot;
+ loot.FillLoot (loot_id, store, this, true);
+
+ 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, nullptr, nullptr, lootItem->itemid);
+ continue;
+ }
+
+ Item* pItem = StoreNewItem(dest, lootItem->itemid, true, lootItem->randomPropertyId);
+ SendNewItem(pItem, lootItem->count, false, false, broadcast);
+ }
+}
+
+LootItem* Player::StoreLootItem(uint8 lootSlot, Loot* loot, InventoryResult& msg)
+{
+ QuestItem* qitem = nullptr;
+ QuestItem* ffaitem = nullptr;
+ QuestItem* conditem = nullptr;
+
+ msg = EQUIP_ERR_OK;
+
+ LootItem* item = loot->LootItemInSlot(lootSlot, this, &qitem, &ffaitem, &conditem);
+ if (!item || item->is_looted)
+ {
+ SendEquipError(EQUIP_ERR_ALREADY_LOOTED, nullptr, nullptr);
+ return nullptr;
+ }
+
+ // Xinef: exploit protection, dont allow to loot normal items if player is not master loot and not below loot threshold
+ // Xinef: only quest, ffa and conditioned items
+ if (!item->is_underthreshold && loot->roundRobinPlayer && !GetLootGUID().IsItem() && GetGroup() && GetGroup()->GetLootMethod() == MASTER_LOOT && GetGUID() != GetGroup()->GetMasterLooterGuid())
+ if (!qitem && !ffaitem && !conditem)
+ {
+ SendLootRelease(GetLootGUID());
+ return nullptr;
+ }
+
+ if (!item->AllowedForPlayer(this, loot->sourceWorldObjectGUID))
+ {
+ SendLootRelease(GetLootGUID());
+ return nullptr;
+ }
+
+ // questitems use the blocked field for other purposes
+ if (!qitem && item->is_blocked)
+ {
+ SendLootRelease(GetLootGUID());
+ return nullptr;
+ }
+
+ // xinef: dont allow protected item to be looted by someone else
+ if (item->rollWinnerGUID && item->rollWinnerGUID != GetGUID())
+ {
+ SendLootRelease(GetLootGUID());
+ return nullptr;
+ }
+
+ ItemPosCountVec dest;
+ msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->itemid, item->count);
+ if (msg == EQUIP_ERR_OK)
+ {
+ AllowedLooterSet looters = item->GetAllowedLooters();
+ Item* newitem = StoreNewItem(dest, item->itemid, true, item->randomPropertyId, looters);
+
+ if (qitem)
+ {
+ qitem->is_looted = true;
+ //freeforall is 1 if everyone's supposed to get the quest item.
+ if (item->freeforall || loot->GetPlayerQuestItems().size() == 1)
+ SendNotifyLootItemRemoved(lootSlot);
+ else
+ loot->NotifyQuestItemRemoved(qitem->index);
+ }
+ else
+ {
+ if (ffaitem)
+ {
+ //freeforall case, notify only one player of the removal
+ ffaitem->is_looted = true;
+ SendNotifyLootItemRemoved(lootSlot);
+ }
+ else
+ {
+ //not freeforall, notify everyone
+ if (conditem)
+ conditem->is_looted = true;
+ loot->NotifyItemRemoved(lootSlot);
+ }
+ }
+
+ //if only one person is supposed to loot the item, then set it to looted
+ if (!item->freeforall)
+ item->is_looted = true;
+
+ --loot->unlootedCount;
+
+ SendNewItem(newitem, uint32(item->count), false, false, true);
+ UpdateLootAchievements(item, loot);
+
+ // LootItem is being removed (looted) from the container, delete it from the DB.
+ if (loot->containerGUID)
+ sLootItemStorage->RemoveStoredLootItem(loot->containerGUID, item->itemid, item->count, loot, item->itemIndex);
+
+ sScriptMgr->OnLootItem(this, newitem, item->count, this->GetLootGUID());
+ }
+ else
+ {
+ SendEquipError(msg, nullptr, nullptr, item->itemid);
+ }
+
+ return item;
+}
+
+uint32 Player::CalculateTalentsPoints() const
+{
+ uint32 base_talent = GetLevel() < 10 ? 0 : GetLevel() - 9;
+
+ uint32 talentPointsForLevel = 0;
+ if (getClass() != CLASS_DEATH_KNIGHT || GetMapId() != 609)
+ {
+ talentPointsForLevel = base_talent;
+ }
+ else
+ {
+ talentPointsForLevel = GetLevel() < 56 ? 0 : GetLevel() - 55;
+ talentPointsForLevel += m_questRewardTalentCount;
+
+ if (talentPointsForLevel > base_talent)
+ {
+ talentPointsForLevel = base_talent;
+ }
+ }
+
+ talentPointsForLevel += m_extraBonusTalentCount;
+ if (talentPointsForLevel > 71) talentPointsForLevel = 71;
+ return uint32(talentPointsForLevel * sWorld->getRate(RATE_TALENT));
+}
+
+bool Player::canFlyInZone(uint32 mapid, uint32 zone, SpellInfo const* bySpell) const
+{
+ // continent checked in SpellInfo::CheckLocation at cast and area update
+ uint32 v_map = GetVirtualMapForMapAndZone(mapid, zone);
+ if (v_map == 571 && !bySpell->HasAttribute(SPELL_ATTR7_IGNORES_COLD_WEATHER_FLYING_REQUIREMENT))
+ {
+ if (!HasSpell(54197)) // 54197 = Cold Weather Flying
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void Player::learnSpellHighRank(uint32 spellid)
+{
+ learnSpell(spellid);
+
+ if (uint32 next = sSpellMgr->GetNextSpellInChain(spellid))
+ learnSpellHighRank(next);
+}
+
+void Player::_LoadSkills(PreparedQueryResult result)
+{
+ // 0 1 2
+ // SetQuery(PLAYER_LOGIN_QUERY_LOADSKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = '{}'", m_guid.GetCounter());
+
+ uint32 count = 0;
+ std::unordered_map loadedSkillValues;
+ if (result)
+ {
+ do
+ {
+ Field* fields = result->Fetch();
+ uint16 skill = fields[0].Get();
+ uint16 value = fields[1].Get();
+ uint16 max = fields[2].Get();
+
+ SkillRaceClassInfoEntry const* rcEntry = GetSkillRaceClassInfo(skill, getRace(), getClass());
+ if (!rcEntry)
+ {
+ LOG_ERROR("entities.player", "Character {} has skill {} that does not exist.", GetGUID().ToString(), skill);
+ continue;
+ }
+
+ // set fixed skill ranges
+ switch (GetSkillRangeType(rcEntry))
+ {
+ case SKILL_RANGE_LANGUAGE: // 300..300
+ value = max = 300;
+ break;
+ case SKILL_RANGE_MONO: // 1..1, grey monolite bar
+ value = max = 1;
+ break;
+ case SKILL_RANGE_LEVEL:
+ max = GetMaxSkillValueForLevel();
+ default:
+ break;
+ }
+
+ if (value == 0)
+ {
+ LOG_ERROR("entities.player", "Character {} has skill {} with value 0. Will be deleted.", GetGUID().ToString(), skill);
+
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_SKILL);
+
+ stmt->SetData(0, GetGUID().GetCounter());
+ stmt->SetData(1, skill);
+
+ CharacterDatabase.Execute(stmt);
+
+ continue;
+ }
+
+ uint16 skillStep = 0;
+ if (SkillTiersEntry const* skillTier = sSkillTiersStore.LookupEntry(rcEntry->SkillTierID))
+ {
+ for (uint32 i = 0; i < MAX_SKILL_STEP; ++i)
+ {
+ if (skillTier->Value[skillStep] == max)
+ {
+ skillStep = i + 1;
+ break;
+ }
+ }
+ }
+
+ SetUInt32Value(PLAYER_SKILL_INDEX(count), MAKE_PAIR32(skill, skillStep));
+
+ SetUInt32Value(PLAYER_SKILL_VALUE_INDEX(count), MAKE_SKILL_VALUE(value, max));
+ SetUInt32Value(PLAYER_SKILL_BONUS_INDEX(count), 0);
+
+ mSkillStatus.insert(SkillStatusMap::value_type(skill, SkillStatusData(count, SKILL_UNCHANGED)));
+
+ loadedSkillValues[skill] = value;
+
+ ++count;
+
+ if (count >= PLAYER_MAX_SKILLS) // client limit
+ {
+ LOG_ERROR("entities.player", "Character {} has more than {} skills.", GetGUID().ToString(), PLAYER_MAX_SKILLS);
+ break;
+ }
+ } while (result->NextRow());
+ }
+
+ // Learn skill rewarded spells after all skills have been loaded to prevent learning a skill from them before its loaded with proper value from DB
+ for (auto& skill : loadedSkillValues)
+ {
+ learnSkillRewardedSpells(skill.first, skill.second);
+ }
+
+ for (; count < PLAYER_MAX_SKILLS; ++count)
+ {
+ SetUInt32Value(PLAYER_SKILL_INDEX(count), 0);
+ SetUInt32Value(PLAYER_SKILL_VALUE_INDEX(count), 0);
+ SetUInt32Value(PLAYER_SKILL_BONUS_INDEX(count), 0);
+ }
+}
+
+uint32 Player::GetPhaseMaskForSpawn() const
+{
+ uint32 phase = IsGameMaster() ? GetPhaseByAuras() : GetPhaseMask();
+
+ if (!phase)
+ phase = PHASEMASK_NORMAL;
+
+ // some aura phases include 1 normal map in addition to phase itself
+ uint32 n_phase = phase & ~PHASEMASK_NORMAL;
+ if (n_phase > 0)
+ return n_phase;
+
+ return phase;
+}
+
+InventoryResult Player::CanEquipUniqueItem(Item* pItem, uint8 eslot, uint32 limit_count) const
+{
+ ItemTemplate const* pProto = pItem->GetTemplate();
+
+ // 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;
+
+ ItemTemplate const* pGem = sObjectMgr->GetItemTemplate(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(ItemTemplate const* itemProto, uint8 except_slot, uint32 limit_count) const
+{
+ // check unique-equipped on item
+ if (itemProto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE)
+ {
+ // 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;
+
+ // there is an equip limit on this item
+ if (HasItemOrGemWithLimitCategoryEquipped(itemProto->ItemLimitCategory, limitEntry->maxCount - limit_count + 1, except_slot))
+ return EQUIP_ERR_ITEM_MAX_COUNT_EQUIPPED_SOCKETED;
+ }
+
+ return EQUIP_ERR_OK;
+}
+
+void Player::HandleFall(MovementInfo const& movementInfo)
+{
+ // calculate total z distance of the fall
+ float z_diff = m_lastFallZ - movementInfo.pos.GetPositionZ();
+
+ //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() && !GetCommandStatus(CHEAT_GOD) &&
+ !HasAuraType(SPELL_AURA_HOVER) && !HasAuraType(SPELL_AURA_FEATHER_FALL) &&
+ !HasAuraType(SPELL_AURA_FLY))
+ {
+ //Safe fall, fall height reduction
+ int32 safe_fall = GetTotalAuraModifier(SPELL_AURA_SAFE_FALL);
+
+ float damageperc = 0.018f * (z_diff - safe_fall) - 0.2426f;
+ uint32 original_health = GetHealth(), final_damage = 0;
+
+ if (damageperc > 0 && !IsImmunedToDamageOrSchool(SPELL_SCHOOL_MASK_NORMAL))
+ {
+ uint32 damage = (uint32)(damageperc * GetMaxHealth() * sWorld->getRate(RATE_DAMAGE_FALL));
+
+ //float height = movementInfo.pos.m_positionZ;
+ //UpdateGroundPositionZ(movementInfo.pos.m_positionX, movementInfo.pos.m_positionY, height);
+
+ if (damage > 0)
+ {
+ //Prevent fall damage from being more than the player maximum health
+ if (damage > GetMaxHealth())
+ damage = GetMaxHealth();
+
+ // Gust of Wind
+ if (HasAura(43621))
+ damage = GetMaxHealth() / 2;
+
+ // Divine Protection
+ if (HasAura(498))
+ {
+ damage /= 2;
+ }
+
+ final_damage = EnvironmentalDamage(DAMAGE_FALL, damage);
+ }
+
+ //Z given by moveinfo, LastZ, FallTime, WaterZ, MapZ, Damage, Safefall reduction
+ LOG_DEBUG("entities.player", "FALLDAMAGE mZ={} z={} fallTime={} damage={} SF={}", movementInfo.pos.GetPositionZ(), GetPositionZ(), movementInfo.fallTime, damage, safe_fall);
+ }
+
+ // 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)
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING, uint32(z_diff * 100));
+ }
+}
+
+void Player::CheckAllAchievementCriteria()
+{
+ m_achievementMgr->CheckAllAchievementCriteria();
+}
+
+void Player::ResetAchievements()
+{
+ m_achievementMgr->Reset();
+}
+
+void Player::SendRespondInspectAchievements(Player* player) const
+{
+ m_achievementMgr->SendRespondInspectAchievements(player);
+}
+
+bool Player::HasAchieved(uint32 achievementId) const
+{
+ return m_achievementMgr->HasAchieved(achievementId);
+}
+
+void Player::StartTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry, uint32 timeLost/* = 0*/)
+{
+ m_achievementMgr->StartTimedAchievement(type, entry, timeLost);
+}
+
+void Player::RemoveTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry)
+{
+ m_achievementMgr->RemoveTimedAchievement(type, entry);
+}
+
+void Player::ResetAchievementCriteria(AchievementCriteriaCondition condition, uint32 value, bool evenIfCriteriaComplete /* = false*/)
+{
+ m_achievementMgr->ResetAchievementCriteria(condition, value, evenIfCriteriaComplete);
+}
+
+void Player::CompletedAchievement(AchievementEntry const* entry)
+{
+ m_achievementMgr->CompletedAchievement(entry);
+}
+
+void Player::LearnTalent(uint32 talentId, uint32 talentRank, bool command /*= false*/)
+{
+ uint32 CurTalentPoints = GetFreeTalentPoints();
+
+ if (!command)
+ {
+ // xinef: check basic data
+ if (!CurTalentPoints)
+ {
+ return;
+ }
+
+ if (talentRank >= MAX_TALENT_RANK)
+ {
+ return;
+ }
+ }
+
+ TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
+ if (!talentInfo)
+ return;
+
+ TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab);
+ if (!talentTabInfo)
+ return;
+
+ // xinef: prevent learn talent for different class (cheating)
+ if ((getClassMask() & talentTabInfo->ClassMask) == 0)
+ return;
+
+ // xinef: find current talent rank
+ uint32 currentTalentRank = 0;
+ for (uint8 rank = 0; rank < MAX_TALENT_RANK; ++rank)
+ {
+ if (talentInfo->RankID[rank] && HasTalent(talentInfo->RankID[rank], GetActiveSpec()))
+ {
+ currentTalentRank = rank + 1;
+ break;
+ }
+ }
+
+ // xinef: we already have same or higher rank talent learned
+ if (currentTalentRank >= talentRank + 1)
+ return;
+
+ uint32 talentPointsChange = (talentRank - currentTalentRank + 1);
+ if (!command)
+ {
+ // xinef: check if we have enough free talent points
+ if (CurTalentPoints < talentPointsChange)
+ {
+ return;
+ }
+ }
+
+ // xinef: check if talent deponds on another talent
+ if (talentInfo->DependsOn > 0)
+ if (TalentEntry const* depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn))
+ {
+ bool hasEnoughRank = false;
+ for (uint8 rank = talentInfo->DependsOnRank; rank < MAX_TALENT_RANK; rank++)
+ {
+ if (depTalentInfo->RankID[rank] != 0)
+ if (HasTalent(depTalentInfo->RankID[rank], GetActiveSpec()))
+ {
+ hasEnoughRank = true;
+ break;
+ }
+ }
+
+ // xinef: does not have enough talent points spend in required talent
+ if (!hasEnoughRank)
+ return;
+ }
+
+ if (!command)
+ {
+ // xinef: check amount of points spent in current talent tree
+ // xinef: be smart and quick
+ uint32 spentPoints = 0;
+ if (talentInfo->Row > 0)
+ {
+ const PlayerTalentMap& talentMap = GetTalentMap();
+ for (PlayerTalentMap::const_iterator itr = talentMap.begin(); itr != talentMap.end(); ++itr)
+ if (TalentSpellPos const* talentPos = GetTalentSpellPos(itr->first))
+ if (TalentEntry const* itrTalentInfo = sTalentStore.LookupEntry(talentPos->talent_id))
+ if (itrTalentInfo->TalentTab == talentInfo->TalentTab)
+ if (itr->second->State != PLAYERSPELL_REMOVED && itr->second->IsInSpec(GetActiveSpec())) // pussywizard
+ spentPoints += talentPos->rank + 1;
+ }
+
+ // xinef: we do not have enough talent points to add talent of this tier
+ if (spentPoints < (talentInfo->Row * MAX_TALENT_RANK))
+ return;
+ }
+
+ // xinef: hacking attempt, tries to learn unknown rank
+ uint32 spellId = talentInfo->RankID[talentRank];
+ if (spellId == 0)
+ return;
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (!spellInfo)
+ return;
+
+ bool learned = false;
+
+ // xinef: if talent info has special marker in dbc - add to spell book
+ if (talentInfo->addToSpellBook)
+ if (!spellInfo->HasAttribute(SPELL_ATTR0_PASSIVE) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL))
+ {
+ learnSpell(spellId);
+ learned = true;
+ }
+
+ if (!learned)
+ SendLearnPacket(spellId, true);
+
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL)
+ if (sSpellMgr->IsAdditionalTalentSpell(spellInfo->Effects[i].TriggerSpell))
+ learnSpell(spellInfo->Effects[i].TriggerSpell);
+
+ addTalent(spellId, GetActiveSpecMask(), currentTalentRank);
+
+ // xinef: update free talent points count
+ m_usedTalentCount += talentPointsChange;
+
+ if (!command)
+ {
+ SetFreeTalentPoints(CurTalentPoints - talentPointsChange);
+ }
+
+ sScriptMgr->OnPlayerLearnTalents(this, talentId, talentRank, spellId);
+}
+
+void Player::LearnPetTalent(ObjectGuid petGuid, uint32 talentId, uint32 talentRank)
+{
+ Pet* pet = GetPet();
+
+ if (!pet)
+ return;
+
+ if (petGuid != pet->GetGUID())
+ 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;
+
+ CreatureTemplate const* ci = pet->GetCreatureTemplate();
+
+ 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 (0~5)
+ uint8 curtalent_maxrank = 0; // 0 = not learned any rank
+ for (int8 rank = MAX_TALENT_RANK - 1; rank >= 0; --rank)
+ {
+ if (talentInfo->RankID[rank] && pet->HasSpell(talentInfo->RankID[rank]))
+ {
+ curtalent_maxrank = (rank + 1);
+ break;
+ }
+ }
+
+ // we already have same or higher talent rank learned
+ if (curtalent_maxrank >= (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 (uint8 rank = talentInfo->DependsOnRank; rank < MAX_TALENT_RANK; rank++)
+ {
+ if (depTalentInfo->RankID[rank] != 0)
+ if (pet->HasSpell(depTalentInfo->RankID[rank]))
+ 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)
+ {
+ uint32 numRows = sTalentStore.GetNumRows();
+ for (uint32 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 (uint8 rank = 0; rank < MAX_TALENT_RANK; rank++)
+ {
+ if (tmpTalent->RankID[rank] != 0)
+ {
+ if (pet->HasSpell(tmpTalent->RankID[rank]))
+ {
+ spentPoints += (rank + 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)
+ {
+ LOG_ERROR("entities.player", "Talent.dbc have for talent: {} Rank: {} spell id = 0", talentId, talentRank);
+ return;
+ }
+
+ // already known
+ if (pet->HasSpell(spellid))
+ return;
+
+ // learn! (other talent ranks will unlearned at learning)
+ pet->learnSpell(spellid);
+ LOG_DEBUG("entities.player", "PetTalentID: {} Rank: {} Spell: {}\n", talentId, talentRank, spellid);
+
+ // update free talent points
+ pet->SetFreeTalentPoints(CurTalentPoints - (talentRank - curtalent_maxrank + 1));
+}
+
+void Player::AddKnownCurrency(uint32 itemId)
+{
+ if (CurrencyTypesEntry const* ctEntry = sCurrencyTypesStore.LookupEntry(itemId))
+ SetFlag64(PLAYER_FIELD_KNOWN_CURRENCIES, (1LL << (ctEntry->BitIndex - 1)));
+}
+
+void Player::UnsummonPetTemporaryIfAny()
+{
+ Pet* pet = GetPet();
+ if (!pet)
+ return;
+
+ if (!m_temporaryUnsummonedPetNumber && pet->isControlled() && !pet->isTemporarySummoned())
+ {
+ m_temporaryUnsummonedPetNumber = pet->GetCharmInfo()->GetPetNumber();
+ SetLastPetSpell(pet->GetUInt32Value(UNIT_CREATED_BY_SPELL));
+ }
+
+ RemovePet(pet, PET_SAVE_AS_CURRENT);
+}
+
+void Player::ResummonPetTemporaryUnSummonedIfAny()
+{
+ if (!m_temporaryUnsummonedPetNumber || IsSpectator())
+ return;
+
+ // not resummon in not appropriate state
+ if (IsPetNeedBeTemporaryUnsummoned())
+ return;
+
+ if (GetPetGUID())
+ return;
+
+ if (!CanResummonPet(GetLastPetSpell()))
+ return;
+
+ Pet* newPet = new Pet(this);
+ if (!newPet->LoadPetFromDB(this, 0, m_temporaryUnsummonedPetNumber, true))
+ delete newPet;
+
+ m_temporaryUnsummonedPetNumber = 0;
+}
+
+bool Player::CanResummonPet(uint32 spellid)
+{
+ switch (getClass())
+ {
+ case CLASS_DEATH_KNIGHT:
+ if (CanSeeDKPet())
+ return true;
+ else if (spellid == 52150) //Raise Dead
+ return false;
+ break;
+ case CLASS_MAGE:
+ if (HasSpell(31687) && HasAura(70937)) //Has [Summon Water Elemental] spell and [Glyph of Eternal Water].
+ return true;
+ break;
+ case CLASS_HUNTER:
+ case CLASS_WARLOCK:
+ return true;
+ break;
+ default:
+ break;
+ }
+
+ return HasSpell(spellid);
+}
+
+bool Player::CanSeeSpellClickOn(Creature const* c) const
+{
+ if (!c->HasNpcFlag(UNIT_NPC_FLAG_SPELLCLICK))
+ return false;
+
+ SpellClickInfoMapBounds clickPair = sObjectMgr->GetSpellClickInfoMapBounds(c->GetEntry());
+ if (clickPair.first == clickPair.second)
+ return true;
+
+ for (SpellClickInfoContainer::const_iterator itr = clickPair.first; itr != clickPair.second; ++itr)
+ {
+ if (!itr->second.IsFitToRequirements(this, c))
+ return false;
+
+ ConditionList conds = sConditionMgr->GetConditionsForSpellClickEvent(c->GetEntry(), itr->second.spellId);
+ ConditionSourceInfo info = ConditionSourceInfo(const_cast(this), const_cast(c));
+ if (sConditionMgr->IsObjectMeetToConditions(info, conds))
+ return true;
+ }
+
+ return false;
+}
+
+bool Player::CanSeeVendor(Creature const* creature) const
+{
+ if (!creature->HasNpcFlag(UNIT_NPC_FLAG_VENDOR))
+ return true;
+
+ ConditionList conditions = sConditionMgr->GetConditionsForNpcVendorEvent(creature->GetEntry(), 0);
+ if (!sConditionMgr->IsObjectMeetToConditions(const_cast(this), const_cast(creature), conditions))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+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 > MAX_TALENT_SPECS)
+ m_specsCount = MAX_TALENT_SPECS;
+
+ for (uint32 specIdx = 0; specIdx < m_specsCount; ++specIdx)
+ {
+ uint8 talentIdCount = 0;
+ size_t pos = data->wpos();
+ *data << uint8(talentIdCount); // [PH], talentIdCount
+
+ const PlayerTalentMap& talentMap = GetTalentMap();
+ for (PlayerTalentMap::const_iterator itr = talentMap.begin(); itr != talentMap.end(); ++itr)
+ if (TalentSpellPos const* talentPos = GetTalentSpellPos(itr->first))
+ if (itr->second->State != PLAYERSPELL_REMOVED && itr->second->IsInSpec(specIdx)) // pussywizard
+ {
+ *data << uint32(talentPos->talent_id); // Talent.dbc
+ *data << uint8(talentPos->rank); // talentMaxRank (0-4)
+ ++talentIdCount;
+ }
+
+ data->put(pos, talentIdCount); // put real count
+
+ *data << uint8(MAX_GLYPH_SLOT_INDEX); // glyphs count
+
+ for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
+ *data << uint16(m_Glyphs[specIdx][i]); // GlyphProperties.dbc
+ }
+}
+
+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
+
+ CreatureTemplate const* ci = pet->GetCreatureTemplate();
+ 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 (0~4)
+ int8 curtalent_maxrank = -1;
+ for (int8 rank = MAX_TALENT_RANK - 1; rank >= 0; --rank)
+ {
+ if (talentInfo->RankID[rank] && pet->HasSpell(talentInfo->RankID[rank]))
+ {
+ curtalent_maxrank = rank;
+ 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_TALENTS_INFO, 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 << int16(item->GetItemRandomPropertyId()); // item random property id
+ *data << item->GetGuidValue(ITEM_FIELD_CREATOR).WriteAsPacked(); // item creator
+ *data << uint32(item->GetItemSuffixFactor()); // item suffix factor
+ }
+
+ data->put(slotUsedMaskPos, slotUsedMask);
+}
+
+void Player::SendEquipmentSetList()
+{
+ uint32 count = 0;
+ WorldPacket data(SMSG_EQUIPMENT_SET_LIST, 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.appendPackGUID(uint64(1));
+ else // xinef: send proper data (do not append 0 with high guid)
+ data.appendPackGUID(itr->second.Items[i] ? itr->second.Items[i].GetRawValue() : uint64(0));
+ }
+
+ ++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...
+ {
+ LOG_ERROR("entities.player", "Player {} tried to save equipment set {} (index {}), 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_SAVED, 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(CharacterDatabaseTransaction trans)
+{
+ for (EquipmentSets::iterator itr = m_EquipmentSets.begin(); itr != m_EquipmentSets.end();)
+ {
+ uint32 index = itr->first;
+ EquipmentSet& eqset = itr->second;
+ CharacterDatabasePreparedStatement* stmt = nullptr;
+ uint8 j = 0;
+ switch (eqset.state)
+ {
+ case EQUIPMENT_SET_UNCHANGED:
+ ++itr;
+ break; // nothing do
+ case EQUIPMENT_SET_CHANGED:
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_EQUIP_SET);
+ stmt->SetData(j++, eqset.Name.c_str());
+ stmt->SetData(j++, eqset.IconName.c_str());
+ stmt->SetData(j++, eqset.IgnoreMask);
+ for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i)
+ stmt->SetData(j++, eqset.Items[i].GetCounter());
+ stmt->SetData(j++, GetGUID().GetCounter());
+ stmt->SetData(j++, eqset.Guid);
+ stmt->SetData(j, index);
+ trans->Append(stmt);
+ eqset.state = EQUIPMENT_SET_UNCHANGED;
+ ++itr;
+ break;
+ case EQUIPMENT_SET_NEW:
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_EQUIP_SET);
+ stmt->SetData(j++, GetGUID().GetCounter());
+ stmt->SetData(j++, eqset.Guid);
+ stmt->SetData(j++, index);
+ stmt->SetData(j++, eqset.Name.c_str());
+ stmt->SetData(j++, eqset.IconName.c_str());
+ stmt->SetData(j++, eqset.IgnoreMask);
+ for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i)
+ stmt->SetData(j++, eqset.Items[i].GetCounter());
+ trans->Append(stmt);
+ eqset.state = EQUIPMENT_SET_UNCHANGED;
+ ++itr;
+ break;
+ case EQUIPMENT_SET_DELETED:
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_EQUIP_SET);
+ stmt->SetData(0, eqset.Guid);
+ trans->Append(stmt);
+ m_EquipmentSets.erase(itr++);
+ break;
+ }
+ }
+}
+
+void Player::_SaveEntryPoint(CharacterDatabaseTransaction trans)
+{
+ // xinef: dont save joinpos with invalid mapid
+ MapEntry const* mEntry = sMapStore.LookupEntry(m_entryPointData.joinPos.GetMapId());
+ if (!mEntry)
+ return;
+
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_ENTRY_POINT);
+ stmt->SetData(0, GetGUID().GetCounter());
+ trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PLAYER_ENTRY_POINT);
+ stmt->SetData(0, GetGUID().GetCounter());
+ stmt->SetData (1, m_entryPointData.joinPos.GetPositionX());
+ stmt->SetData (2, m_entryPointData.joinPos.GetPositionY());
+ stmt->SetData (3, m_entryPointData.joinPos.GetPositionZ());
+ stmt->SetData (4, m_entryPointData.joinPos.GetOrientation());
+ stmt->SetData(5, m_entryPointData.joinPos.GetMapId());
+ stmt->SetData(6, m_entryPointData.taxiPath[0]);
+ stmt->SetData(7, m_entryPointData.taxiPath[1]);
+ stmt->SetData(8, m_entryPointData.mountSpell);
+ trans->Append(stmt);
+}
+
+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::RemoveAtLoginFlag(AtLoginFlags flags, bool persist /*= false*/)
+{
+ m_atLoginFlags &= ~flags;
+
+ if (persist)
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_REM_AT_LOGIN_FLAG);
+
+ stmt->SetData(0, uint16(flags));
+ stmt->SetData(1, GetGUID().GetCounter());
+
+ CharacterDatabase.Execute(stmt);
+ }
+}
+
+void Player::SendClearCooldown(uint32 spell_id, Unit* target)
+{
+ WorldPacket data(SMSG_CLEAR_COOLDOWN, 4 + 8);
+ data << uint32(spell_id);
+ data << target->GetGUID();
+ SendDirectMessage(&data);
+
+ if (target == this && NeedSendSpectatorData())
+ ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "RCD", spell_id);
+}
+
+void Player::ResetMap()
+{
+ // this may be called during Map::Update
+ // after decrement+unlink, ++m_mapRefIter will continue correctly
+ // when the first element of the list is being removed
+ // nocheck_prev will return the padding element of the RefMgr
+ // instead of nullptr in the case of prev
+ GetMap()->UpdateIteratorBack(this);
+ Unit::ResetMap();
+ GetMapRef().unlink();
+}
+
+void Player::SetMap(Map* map)
+{
+ Unit::SetMap(map);
+ m_mapRef.link(map, this);
+}
+
+void Player::_SaveCharacter(bool create, CharacterDatabaseTransaction trans)
+{
+ CharacterDatabasePreparedStatement* stmt = nullptr;
+ uint8 index = 0;
+
+ auto finiteAlways = [](float f) { return std::isfinite(f) ? f : 0.0f; };
+
+ if (create)
+ {
+ //! Insert query
+ //! TO DO: Filter out more redundant fields that can take their default value at player create
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER);
+ stmt->SetData(index++, GetGUID().GetCounter());
+ stmt->SetData(index++, GetSession()->GetAccountId());
+ stmt->SetData(index++, GetName());
+ stmt->SetData(index++, getRace(true));
+ stmt->SetData(index++, getClass());
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES_3, 0)); // save gender from PLAYER_BYTES_3, UNIT_BYTES_0 changes with every transform effect
+ stmt->SetData(index++, GetLevel());
+ stmt->SetData(index++, GetUInt32Value(PLAYER_XP));
+ stmt->SetData(index++, GetMoney());
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES, 0));
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES, 1));
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES, 2));
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES, 3));
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES_2, 0));
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES_2, 2));
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES_2, 3));
+ stmt->SetData(index++, (uint32)GetPlayerFlags());
+ stmt->SetData(index++, (uint16)GetMapId());
+ stmt->SetData(index++, (uint32)GetInstanceId());
+ stmt->SetData(index++, (uint8(GetDungeonDifficulty()) | uint8(GetRaidDifficulty()) << 4));
+ stmt->SetData(index++, finiteAlways(GetPositionX()));
+ stmt->SetData(index++, finiteAlways(GetPositionY()));
+ stmt->SetData(index++, finiteAlways(GetPositionZ()));
+ stmt->SetData(index++, finiteAlways(GetOrientation()));
+ stmt->SetData(index++, finiteAlways(GetTransOffsetX()));
+ stmt->SetData(index++, finiteAlways(GetTransOffsetY()));
+ stmt->SetData(index++, finiteAlways(GetTransOffsetZ()));
+ stmt->SetData(index++, finiteAlways(GetTransOffsetO()));
+
+ int32 lowGuidOrSpawnId = 0;
+ if (Transport* transport = GetTransport())
+ {
+ if (transport->IsMotionTransport())
+ lowGuidOrSpawnId = static_cast(transport->GetGUID().GetCounter());
+ else if (transport->IsStaticTransport())
+ lowGuidOrSpawnId = -static_cast(transport->GetSpawnId());
+ }
+ stmt->SetData(index++, lowGuidOrSpawnId);
+
+ std::ostringstream ss;
+ ss << m_taxi;
+ stmt->SetData(index++, ss.str());
+ stmt->SetData(index++, m_cinematic);
+ stmt->SetData(index++, m_Played_time[PLAYED_TIME_TOTAL]);
+ stmt->SetData(index++, m_Played_time[PLAYED_TIME_LEVEL]);
+ stmt->SetData(index++, finiteAlways(_restBonus));
+ stmt->SetData(index++, uint32(GameTime::GetGameTime().count()));
+ stmt->SetData(index++, (HasPlayerFlag(PLAYER_FLAGS_RESTING) ? 1 : 0));
+ //save, far from tavern/city
+ //save, but in tavern/city
+ stmt->SetData(index++, m_resetTalentsCost);
+ stmt->SetData(index++, uint32(m_resetTalentsTime));
+ stmt->SetData(index++, (uint16)m_ExtraFlags);
+ stmt->SetData(index++, m_petStable ? m_petStable->MaxStabledPets : 0);
+ stmt->SetData(index++, (uint16)m_atLoginFlags);
+ stmt->SetData(index++, GetZoneId());
+ stmt->SetData(index++, uint32(m_deathExpireTime));
+
+ ss.str("");
+ ss << m_taxi.SaveTaxiDestinationsToString();
+
+ stmt->SetData(index++, ss.str());
+ stmt->SetData(index++, GetArenaPoints());
+ stmt->SetData(index++, GetHonorPoints());
+ stmt->SetData(index++, GetUInt32Value(PLAYER_FIELD_TODAY_CONTRIBUTION));
+ stmt->SetData(index++, GetUInt32Value(PLAYER_FIELD_YESTERDAY_CONTRIBUTION));
+ stmt->SetData(index++, GetUInt32Value(PLAYER_FIELD_LIFETIME_HONORABLE_KILLS));
+ stmt->SetData(index++, GetUInt16Value(PLAYER_FIELD_KILLS, 0));
+ stmt->SetData(index++, GetUInt16Value(PLAYER_FIELD_KILLS, 1));
+ stmt->SetData(index++, GetUInt32Value(PLAYER_CHOSEN_TITLE));
+ stmt->SetData(index++, GetUInt64Value(PLAYER_FIELD_KNOWN_CURRENCIES));
+ stmt->SetData(index++, GetUInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX));
+ stmt->SetData(index++, GetDrunkValue());
+ stmt->SetData(index++, GetHealth());
+
+ for (uint32 i = 0; i < MAX_POWERS; ++i)
+ stmt->SetData(index++, GetPower(Powers(i)));
+
+ stmt->SetData(index++, GetSession()->GetLatency());
+
+ stmt->SetData(index++, m_specsCount);
+ stmt->SetData(index++, m_activeSpec);
+
+ ss.str("");
+ for (uint32 i = 0; i < PLAYER_EXPLORED_ZONES_SIZE; ++i)
+ ss << GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + i) << ' ';
+ stmt->SetData(index++, ss.str());
+
+ ss.str("");
+ // cache equipment...
+ for (uint32 i = 0; i < EQUIPMENT_SLOT_END * 2; ++i)
+ ss << GetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + i) << ' ';
+
+ // ...and bags for enum opcode
+ for (uint32 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
+ {
+ if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
+ ss << item->GetEntry();
+ else
+ ss << '0';
+ ss << " 0 ";
+ }
+
+ stmt->SetData(index++, ss.str());
+ stmt->SetData(index++, GetUInt32Value(PLAYER_AMMO_ID));
+
+ ss.str("");
+ for (uint32 i = 0; i < KNOWN_TITLES_SIZE * 2; ++i)
+ ss << GetUInt32Value(PLAYER__FIELD_KNOWN_TITLES + i) << ' ';
+
+ stmt->SetData(index++, ss.str());
+ stmt->SetData(index++, GetByteValue(PLAYER_FIELD_BYTES, 2));
+ stmt->SetData(index++, m_grantableLevels);
+ stmt->SetData(index++, _innTriggerId);
+ }
+ else
+ {
+ // Update query
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHARACTER);
+ stmt->SetData(index++, GetName());
+ stmt->SetData(index++, getRace(true));
+ stmt->SetData(index++, getClass());
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES_3, 0)); // save gender from PLAYER_BYTES_3, UNIT_BYTES_0 changes with every transform effect
+ stmt->SetData(index++, GetLevel());
+ stmt->SetData(index++, GetUInt32Value(PLAYER_XP));
+ stmt->SetData(index++, GetMoney());
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES, 0));
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES, 1));
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES, 2));
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES, 3));
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES_2, 0));
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES_2, 2));
+ stmt->SetData(index++, GetByteValue(PLAYER_BYTES_2, 3));
+ stmt->SetData(index++, GetPlayerFlags());
+
+ if (!IsBeingTeleported())
+ {
+ Difficulty dd = GetDungeonDifficulty(), rd = GetRaidDifficulty();
+ if (Map* m = FindMap())
+ if (m->IsDungeon())
+ {
+ if (m->IsNonRaidDungeon()) dd = m->GetDifficulty();
+ else rd = m->GetDifficulty();
+ }
+ stmt->SetData(index++, (uint16)GetMapId());
+ stmt->SetData(index++, (uint32)GetInstanceId());
+ stmt->SetData(index++, (uint8(dd) | uint8(rd) << 4));
+ stmt->SetData(index++, finiteAlways(GetPositionX()));
+ stmt->SetData(index++, finiteAlways(GetPositionY()));
+ stmt->SetData(index++, finiteAlways(GetPositionZ()));
+ stmt->SetData(index++, finiteAlways(GetOrientation()));
+ }
+ else
+ {
+ stmt->SetData(index++, (uint16)GetTeleportDest().GetMapId());
+ stmt->SetData(index++, (uint32)0);
+ stmt->SetData(index++, (uint8(GetDungeonDifficulty()) | uint8(GetRaidDifficulty()) << 4));
+ stmt->SetData(index++, finiteAlways(GetTeleportDest().GetPositionX()));
+ stmt->SetData(index++, finiteAlways(GetTeleportDest().GetPositionY()));
+ stmt->SetData(index++, finiteAlways(GetTeleportDest().GetPositionZ()));
+ stmt->SetData(index++, finiteAlways(GetTeleportDest().GetOrientation()));
+ }
+
+ stmt->SetData(index++, finiteAlways(GetTransOffsetX()));
+ stmt->SetData(index++, finiteAlways(GetTransOffsetY()));
+ stmt->SetData(index++, finiteAlways(GetTransOffsetZ()));
+ stmt->SetData(index++, finiteAlways(GetTransOffsetO()));
+
+ int32 lowGuidOrSpawnId = 0;
+ if (Transport* transport = GetTransport())
+ {
+ if (transport->IsMotionTransport())
+ lowGuidOrSpawnId = static_cast(transport->GetGUID().GetCounter());
+ else if (transport->IsStaticTransport())
+ lowGuidOrSpawnId = -static_cast(transport->GetSpawnId());
+ }
+ stmt->SetData(index++, lowGuidOrSpawnId);
+
+ std::ostringstream ss;
+ ss << m_taxi;
+ stmt->SetData(index++, ss.str());
+ stmt->SetData(index++, m_cinematic);
+ stmt->SetData(index++, m_Played_time[PLAYED_TIME_TOTAL]);
+ stmt->SetData(index++, m_Played_time[PLAYED_TIME_LEVEL]);
+ stmt->SetData(index++, finiteAlways(_restBonus));
+ stmt->SetData(index++, uint32(GameTime::GetGameTime().count()));
+ stmt->SetData(index++, (HasPlayerFlag(PLAYER_FLAGS_RESTING) ? 1 : 0));
+ //save, far from tavern/city
+ //save, but in tavern/city
+ stmt->SetData(index++, m_resetTalentsCost);
+ stmt->SetData(index++, uint32(m_resetTalentsTime));
+ stmt->SetData(index++, (uint16)m_ExtraFlags);
+ stmt->SetData(index++, m_petStable ? m_petStable->MaxStabledPets : 0);
+ stmt->SetData(index++, (uint16)m_atLoginFlags);
+ stmt->SetData(index++, GetZoneId());
+ stmt->SetData(index++, uint32(m_deathExpireTime));
+
+ ss.str("");
+ ss << m_taxi.SaveTaxiDestinationsToString();
+
+ stmt->SetData(index++, ss.str());
+ stmt->SetData(index++, GetArenaPoints());
+ stmt->SetData(index++, GetHonorPoints());
+ stmt->SetData(index++, GetUInt32Value(PLAYER_FIELD_TODAY_CONTRIBUTION));
+ stmt->SetData(index++, GetUInt32Value(PLAYER_FIELD_YESTERDAY_CONTRIBUTION));
+ stmt->SetData(index++, GetUInt32Value(PLAYER_FIELD_LIFETIME_HONORABLE_KILLS));
+ stmt->SetData(index++, GetUInt16Value(PLAYER_FIELD_KILLS, 0));
+ stmt->SetData(index++, GetUInt16Value(PLAYER_FIELD_KILLS, 1));
+ stmt->SetData(index++, GetUInt32Value(PLAYER_CHOSEN_TITLE));
+ stmt->SetData(index++, GetUInt64Value(PLAYER_FIELD_KNOWN_CURRENCIES));
+ stmt->SetData(index++, GetUInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX));
+ stmt->SetData(index++, GetDrunkValue());
+ stmt->SetData(index++, GetHealth());
+
+ for (uint32 i = 0; i < MAX_POWERS; ++i)
+ stmt->SetData(index++, GetPower(Powers(i)));
+
+ stmt->SetData(index++, GetSession()->GetLatency());
+
+ stmt->SetData(index++, m_specsCount);
+ stmt->SetData(index++, m_activeSpec);
+
+ ss.str("");
+ for (uint32 i = 0; i < PLAYER_EXPLORED_ZONES_SIZE; ++i)
+ ss << GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + i) << ' ';
+ stmt->SetData(index++, ss.str());
+
+ ss.str("");
+ // cache equipment...
+ for (uint32 i = 0; i < EQUIPMENT_SLOT_END * 2; ++i)
+ ss << GetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + i) << ' ';
+
+ // ...and bags for enum opcode
+ for (uint32 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
+ {
+ if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
+ ss << item->GetEntry();
+ else
+ ss << '0';
+ ss << " 0 ";
+ }
+
+ stmt->SetData(index++, ss.str());
+ stmt->SetData(index++, GetUInt32Value(PLAYER_AMMO_ID));
+
+ ss.str("");
+ for (uint32 i = 0; i < KNOWN_TITLES_SIZE * 2; ++i)
+ ss << GetUInt32Value(PLAYER__FIELD_KNOWN_TITLES + i) << ' ';
+
+ stmt->SetData(index++, ss.str());
+ stmt->SetData(index++, GetByteValue(PLAYER_FIELD_BYTES, 2));
+ stmt->SetData(index++, m_grantableLevels);
+ stmt->SetData(index++, _innTriggerId);
+
+ stmt->SetData(index++, IsInWorld() && !GetSession()->PlayerLogout() ? 1 : 0);
+ // Index
+ stmt->SetData(index++, GetGUID().GetCounter());
+ }
+
+ trans->Append(stmt);
+}
+
+void Player::_LoadGlyphs(PreparedQueryResult result)
+{
+ // SELECT talentGroup, glyph1, glyph2, glyph3, glyph4, glyph5, glyph6 from character_glyphs WHERE guid = '%u'
+ if (!result)
+ return;
+
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint8 spec = fields[0].Get();
+ if (spec >= m_specsCount)
+ continue;
+
+ m_Glyphs[spec][0] = fields[1].Get();
+ m_Glyphs[spec][1] = fields[2].Get();
+ m_Glyphs[spec][2] = fields[3].Get();
+ m_Glyphs[spec][3] = fields[4].Get();
+ m_Glyphs[spec][4] = fields[5].Get();
+ m_Glyphs[spec][5] = fields[6].Get();
+ } while (result->NextRow());
+}
+
+void Player::_SaveGlyphs(CharacterDatabaseTransaction trans)
+{
+ if (!NeedToSaveGlyphs())
+ return;
+
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_GLYPHS);
+ stmt->SetData(0, GetGUID().GetCounter());
+ trans->Append(stmt);
+
+ for (uint8 spec = 0; spec < m_specsCount; ++spec)
+ {
+ uint8 index = 0;
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_GLYPHS);
+ stmt->SetData(index++, GetGUID().GetCounter());
+ stmt->SetData(index++, spec);
+
+ for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
+ stmt->SetData(index++, uint16(m_Glyphs[spec][i]));
+
+ trans->Append(stmt);
+ }
+
+ SetNeedToSaveGlyphs(false);
+}
+
+void Player::_LoadTalents(PreparedQueryResult result)
+{
+ // SetQuery(PLAYER_LOGIN_QUERY_LOADTALENTS, "SELECT spell, specMask FROM character_talent WHERE guid = '{}'", m_guid.GetCounter());
+ if (result)
+ {
+ do
+ {
+ // xinef: checked
+ uint32 spellId = (*result)[0].Get();
+ uint8 specMask = (*result)[1].Get();
+ addTalent(spellId, specMask, 0);
+ TalentSpellPos const* talentPos = GetTalentSpellPos(spellId);
+ ASSERT(talentPos);
+
+ // xinef: increase used talent points count
+ if (GetActiveSpecMask() & specMask)
+ m_usedTalentCount += talentPos->rank + 1;
+ } while (result->NextRow());
+ }
+}
+
+void Player::_SaveTalents(CharacterDatabaseTransaction trans)
+{
+ CharacterDatabasePreparedStatement* stmt = nullptr;
+
+ for (PlayerTalentMap::iterator itr = m_talents.begin(); itr != m_talents.end();)
+ {
+ // xinef: skip temporary spells
+ if (itr->second->State == PLAYERSPELL_TEMPORARY)
+ {
+ ++itr;
+ continue;
+ }
+
+ // xinef: delete statement for removed / updated talent
+ if (itr->second->State == PLAYERSPELL_REMOVED || itr->second->State == PLAYERSPELL_CHANGED)
+ {
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT_BY_SPELL);
+ stmt->SetData(0, GetGUID().GetCounter());
+ stmt->SetData(1, itr->first);
+ trans->Append(stmt);
+ }
+
+ // xinef: insert statement for new / updated spell
+ if (itr->second->State == PLAYERSPELL_NEW || itr->second->State == PLAYERSPELL_CHANGED)
+ {
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TALENT);
+ stmt->SetData(0, GetGUID().GetCounter());
+ stmt->SetData(1, itr->first);
+ stmt->SetData(2, itr->second->specMask);
+ trans->Append(stmt);
+ }
+
+ if (itr->second->State == PLAYERSPELL_REMOVED)
+ {
+ delete itr->second;
+ m_talents.erase(itr++);
+ }
+ else
+ {
+ itr->second->State = PLAYERSPELL_UNCHANGED;
+ ++itr;
+ }
+ }
+}
+
+void Player::ActivateSpec(uint8 spec)
+{
+ // xinef: some basic checks
+ if (GetActiveSpec() == spec)
+ return;
+
+ if (spec > GetSpecsCount())
+ return;
+
+ // xinef: interrupt currently casted spell just in case
+ if (IsNonMeleeSpellCast(false))
+ InterruptNonMeleeSpells(false);
+
+ // xinef: save current actions order
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+ _SaveActions(trans);
+ CharacterDatabase.CommitTransaction(trans);
+
+ // xinef: remove pet, it will be resummoned later
+ if (Pet* pet = GetPet())
+ RemovePet(pet, PET_SAVE_NOT_IN_SLOT);
+
+ // xinef: remove other summoned units and clear reactives
+ ClearAllReactives();
+ UnsummonAllTotems();
+ RemoveAllControlled();
+
+ // xinef: let client clear his current Actions
+ SendActionButtons(2);
+ uint8 oldSpec = GetActiveSpec();
+
+ std::unordered_set removedSpecAuras;
+
+ // xinef: reset talent auras
+ for (PlayerTalentMap::iterator itr = m_talents.begin(); itr != m_talents.end(); ++itr)
+ {
+ if (itr->second->State == PLAYERSPELL_REMOVED)
+ continue;
+
+ // xinef: remove all active talent auras
+ if (!(itr->second->specMask & GetActiveSpecMask()))
+ continue;
+
+ _removeTalentAurasAndSpells(itr->first);
+
+ // pussywizard: was => isn't
+ if (!itr->second->IsInSpec(spec) && !itr->second->inSpellBook)
+ SendLearnPacket(itr->first, false);
+
+ removedSpecAuras.insert(itr->first);
+ }
+
+ // xinef: remove glyph auras
+ for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot)
+ if (uint32 glyphId = m_Glyphs[GetActiveSpec()][slot])
+ if (GlyphPropertiesEntry const* glyphEntry = sGlyphPropertiesStore.LookupEntry(glyphId))
+ {
+ RemoveAurasDueToSpell(glyphEntry->SpellId);
+ removedSpecAuras.insert(glyphEntry->SpellId);
+ }
+
+ // xinef: set active spec as new one
+ SetActiveSpec(spec);
+ uint32 spentTalents = 0;
+
+ // xinef: add talent auras
+ for (PlayerTalentMap::iterator itr = m_talents.begin(); itr != m_talents.end(); ++itr)
+ {
+ if (itr->second->State == PLAYERSPELL_REMOVED)
+ continue;
+
+ // xinef: talent not in new spec
+ if (!(itr->second->specMask & GetActiveSpecMask()))
+ continue;
+
+ // pussywizard: wasn't => is
+ if (!itr->second->IsInSpec(oldSpec) && !itr->second->inSpellBook)
+ SendLearnPacket(itr->first, true);
+
+ _addTalentAurasAndSpells(itr->first);
+ TalentSpellPos const* talentPos = GetTalentSpellPos(itr->first);
+ spentTalents += talentPos->rank + 1;
+
+ removedSpecAuras.erase(itr->first);
+ }
+
+ // pussywizard: remove spells that are in previous spec, but are not present in new one (or are in new spec, but not in the old one)
+ for (PlayerSpellMap::iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr)
+ {
+ if (!itr->second->Active || itr->second->State == PLAYERSPELL_REMOVED)
+ continue;
+
+ // pussywizard: was => isn't
+ if (itr->second->IsInSpec(oldSpec) && !itr->second->IsInSpec(spec))
+ {
+ SendLearnPacket(itr->first, false);
+ // We want to remove all auras of the unlearned spell
+ _removeTalentAurasAndSpells(itr->first);
+
+ removedSpecAuras.insert(itr->first);
+ }
+ // pussywizard: wasn't => is
+ else if (!itr->second->IsInSpec(oldSpec) && itr->second->IsInSpec(spec))
+ {
+ SendLearnPacket(itr->first, true);
+
+ removedSpecAuras.erase(itr->first);
+ }
+ }
+
+ // xinef: apply glyphs from second spec
+ if (GetActiveSpec() != oldSpec)
+ {
+ for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot)
+ {
+ uint32 glyphId = m_Glyphs[GetActiveSpec()][slot];
+ if (glyphId)
+ {
+ if (GlyphPropertiesEntry const* glyphEntry = sGlyphPropertiesStore.LookupEntry(glyphId))
+ {
+ CastSpell(this, glyphEntry->SpellId, TriggerCastFlags(TRIGGERED_FULL_MASK & ~(TRIGGERED_IGNORE_SHAPESHIFT | TRIGGERED_IGNORE_CASTER_AURASTATE)));
+ removedSpecAuras.erase(glyphEntry->SpellId);
+ }
+ }
+
+ SetGlyph(slot, glyphId, true);
+ }
+ }
+
+ // Remove auras triggered/activated by talents/glyphs
+ // Mostly explicit casts in dummy aura scripts
+ if (!removedSpecAuras.empty())
+ {
+ for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();)
+ {
+ Aura* aura = iter->second;
+ if (SpellInfo const* triggeredByAuraSpellInfo = aura->GetTriggeredByAuraSpellInfo())
+ {
+ if (removedSpecAuras.find(triggeredByAuraSpellInfo->Id) != removedSpecAuras.end())
+ {
+ RemoveOwnedAura(iter);
+ continue;
+ }
+ }
+ ++iter;
+ }
+ }
+
+ m_usedTalentCount = spentTalents;
+ InitTalentForLevel();
+
+ // load them asynchronously
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC);
+ stmt->SetData(0, GetGUID().GetCounter());
+ stmt->SetData(1, m_activeSpec);
+
+ WorldSession* mySess = GetSession();
+ mySess->GetQueryProcessor().AddCallback(CharacterDatabase.AsyncQuery(stmt)
+ .WithPreparedCallback([mySess](PreparedQueryResult result)
+ {
+ // safe callback, we can't pass this pointer directly
+ // in case player logs out before db response (player would be deleted in that case)
+ if (Player* thisPlayer = mySess->GetPlayer())
+ thisPlayer->LoadActions(result);
+ }));
+ }
+
+ // xinef: reset power
+ Powers pw = getPowerType();
+ if (pw != POWER_MANA)
+ SetPower(POWER_MANA, 0); // Mana must be 0 even if it isn't the active power type.
+ SetPower(pw, 0);
+
+ // xinef: remove titan grip if player had it set and does not have appropriate talent
+ if (!HasTalent(46917, GetActiveSpec()) && m_canTitanGrip)
+ SetCanTitanGrip(false);
+ // xinef: remove dual wield if player does not have dual wield spell (shamans)
+ if (!HasSpell(674) && m_canDualWield)
+ SetCanDualWield(false);
+
+ AutoUnequipOffhandIfNeed();
+
+ // Xinef: Patch 3.2.0: Switching spec removes paladins spell Righteous Fury (25780)
+ if (getClass() == CLASS_PALADIN)
+ RemoveAurasDueToSpell(25780);
+
+ // Xinef: Remove talented single target auras at other targets
+ AuraList& scAuras = GetSingleCastAuras();
+ for (AuraList::iterator iter = scAuras.begin(); iter != scAuras.end();)
+ {
+ Aura* aura = *iter;
+ if (!HasActiveSpell(aura->GetId()) && !HasTalent(aura->GetId(), GetActiveSpec()))
+ {
+ aura->Remove();
+ iter = scAuras.begin();
+ }
+ else
+ ++iter;
+ }
+}
+
+void Player::LoadActions(PreparedQueryResult result)
+{
+ if (result)
+ _LoadActions(result);
+
+ SendActionButtons(1);
+}
+
+void Player::GetTalentTreePoints(uint8 (&specPoints)[3]) const
+{
+ const PlayerTalentMap& talentMap = GetTalentMap();
+ for (PlayerTalentMap::const_iterator itr = talentMap.begin(); itr != talentMap.end(); ++itr)
+ if (itr->second->State != PLAYERSPELL_REMOVED && itr->second->IsInSpec(GetActiveSpec()))
+ if (TalentEntry const* talentInfo = sTalentStore.LookupEntry(itr->second->talentID))
+ if (TalentTabEntry const* tab = sTalentTabStore.LookupEntry(talentInfo->TalentTab))
+ if (tab->tabpage < 3)
+ {
+ // find current talent rank
+ uint8 currentTalentRank = 0;
+ for (uint8 rank = 0; rank < MAX_TALENT_RANK; ++rank)
+ if (talentInfo->RankID[rank] && itr->first == talentInfo->RankID[rank])
+ {
+ currentTalentRank = rank + 1;
+ break;
+ }
+ specPoints[tab->tabpage] += currentTalentRank;
+ }
+}
+
+uint8 Player::GetMostPointsTalentTree() const
+{
+ uint32 specPoints[3] = {0, 0, 0};
+ const PlayerTalentMap& talentMap = GetTalentMap();
+ for (PlayerTalentMap::const_iterator itr = talentMap.begin(); itr != talentMap.end(); ++itr)
+ if (itr->second->State != PLAYERSPELL_REMOVED && itr->second->IsInSpec(GetActiveSpec()))
+ if (TalentEntry const* talentInfo = sTalentStore.LookupEntry(itr->second->talentID))
+ if (TalentTabEntry const* tab = sTalentTabStore.LookupEntry(talentInfo->TalentTab))
+ if (tab->tabpage < 3)
+ {
+ // find current talent rank
+ uint8 currentTalentRank = 0;
+ for (uint8 rank = 0; rank < MAX_TALENT_RANK; ++rank)
+ if (talentInfo->RankID[rank] && itr->first == talentInfo->RankID[rank])
+ {
+ currentTalentRank = rank + 1;
+ break;
+ }
+ specPoints[tab->tabpage] += currentTalentRank;
+ }
+ uint8 maxIndex = 0;
+ uint8 maxCount = specPoints[0];
+ for (uint8 i = 1; i < 3; ++i)
+ if (specPoints[i] > maxCount)
+ {
+ maxIndex = i;
+ maxCount = specPoints[i];
+ }
+ return maxIndex;
+}
+
+void Player::SetReputation(uint32 factionentry, float value)
+{
+ GetReputationMgr().SetReputation(sFactionStore.LookupEntry(factionentry), value);
+}
+
+uint32 Player::GetReputation(uint32 factionentry) const
+{
+ return GetReputationMgr().GetReputation(sFactionStore.LookupEntry(factionentry));
+}
+
+std::string const& Player::GetGuildName()
+{
+ return sGuildMgr->GetGuildById(GetGuildId())->GetName();
+}
+
+void Player::SendDuelCountdown(uint32 counter)
+{
+ WorldPacket data(SMSG_DUEL_COUNTDOWN, 4);
+ data << uint32(counter); // seconds
+ GetSession()->SendPacket(&data);
+}
+
+void Player::SetIsSpectator(bool on)
+{
+ if (on)
+ {
+ AddAura(SPECTATOR_SPELL_SPEED, this);
+ m_ExtraFlags |= PLAYER_EXTRA_SPECTATOR_ON;
+ AddUnitState(UNIT_STATE_ISOLATED);
+ //SetFaction(1100);
+ SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
+ if (HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP))
+ {
+ RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP);
+ sScriptMgr->OnFfaPvpStateUpdate(this, false);
+ }
+ ResetContestedPvP();
+ SetDisplayId(23691);
+ }
+ else
+ {
+ RemoveAurasDueToSpell(SPECTATOR_SPELL_SPEED);
+ if (IsSpectator())
+ ClearUnitState(UNIT_STATE_ISOLATED);
+ m_ExtraFlags &= ~PLAYER_EXTRA_SPECTATOR_ON;
+ RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
+ RestoreDisplayId();
+
+ if (!IsGameMaster())
+ {
+ //SetFactionForRace(getRace());
+
+ // restore FFA PvP Server state
+ // Xinef: it will be removed if necessery in UpdateArea called in WorldPortOpcode
+ if (sWorld->IsFFAPvPRealm())
+ {
+ if (!HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP))
+ {
+ SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP);
+ sScriptMgr->OnFfaPvpStateUpdate(this, true);
+
+ }
+ }
+ }
+ }
+}
+
+bool Player::NeedSendSpectatorData() const
+{
+ if (FindMap() && FindMap()->IsBattleArena() && !IsSpectator())
+ {
+ Battleground* bg = ((BattlegroundMap*)FindMap())->GetBG();
+ if (bg && bg->HaveSpectators() && bg->GetStatus() == STATUS_IN_PROGRESS && !bg->GetPlayers().empty())
+ if (bg->GetPlayers().find(GetGUID()) != bg->GetPlayers().end())
+ return true;
+ }
+ return false;
+}
+
+void Player::PrepareCharmAISpells()
+{
+ for (int i = 0; i < NUM_CAI_SPELLS; ++i)
+ m_charmAISpells[i] = 0;
+
+ uint32 damage_type[4] = {0, 0, 0, 0};
+ uint32 periodic_damage = 0;
+
+ for (PlayerSpellMap::iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr)
+ {
+ if (itr->second->State == PLAYERSPELL_REMOVED || !itr->second->Active || !itr->second->IsInSpec(GetActiveSpec()))
+ continue;
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first);
+ if (!spellInfo)
+ continue;
+
+ if (!spellInfo->SpellFamilyName || spellInfo->IsPassive() || spellInfo->NeedsComboPoints() || (spellInfo->Stances && !spellInfo->HasAttribute(SPELL_ATTR2_ALLOW_WHILE_NOT_SHAPESHIFTED)))
+ continue;
+
+ float cast = spellInfo->CalcCastTime() / 1000.0f;
+ if (cast > 3.0f)
+ continue;
+
+ for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ {
+ if (spellInfo->Effects[i].Effect == SPELL_EFFECT_SCHOOL_DAMAGE)
+ {
+ int32 dmg = CalculateSpellDamage(this, spellInfo, i);
+ uint8 offset = 0;
+ if (cast)
+ {
+ dmg = dmg / cast;
+ offset = 2;
+ }
+
+ if ((int32)damage_type[offset] < dmg)
+ {
+ if (!m_charmAISpells[SPELL_INSTANT_DAMAGE + offset] || !spellInfo->IsHighRankOf(sSpellMgr->GetSpellInfo(m_charmAISpells[SPELL_INSTANT_DAMAGE + offset])) || urand(0, 1))
+ if (damage_type[1 + offset] < damage_type[offset])
+ {
+ m_charmAISpells[SPELL_INSTANT_DAMAGE2 + offset] = m_charmAISpells[SPELL_INSTANT_DAMAGE + offset];
+ damage_type[1 + offset] = damage_type[offset];
+ }
+
+ m_charmAISpells[SPELL_INSTANT_DAMAGE + offset] = spellInfo->Id;
+ damage_type[offset] = dmg;
+ }
+ else if ((int32)damage_type[1 + offset] < dmg)
+ {
+ if (m_charmAISpells[SPELL_INSTANT_DAMAGE + offset] && sSpellMgr->GetSpellInfo(m_charmAISpells[SPELL_INSTANT_DAMAGE + offset])->IsHighRankOf(spellInfo) && urand(0, 1))
+ continue;
+
+ m_charmAISpells[SPELL_INSTANT_DAMAGE2 + offset] = spellInfo->Id;
+ damage_type[1 + offset] = dmg;
+ }
+ break;
+ }
+ else if (spellInfo->HasAttribute(SPELL_ATTR7_ATTACK_ON_CHARGE_TO_UNIT))
+ {
+ m_charmAISpells[SPELL_T_CHARGE] = spellInfo->Id;
+ break;
+ }
+ else if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_INCREASE_SPEED)
+ {
+ m_charmAISpells[SPELL_FAST_RUN] = spellInfo->Id;
+ break;
+ }
+ else if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_SCHOOL_IMMUNITY)
+ {
+ m_charmAISpells[SPELL_IMMUNITY] = spellInfo->Id;
+ break;
+ }
+ else if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE)
+ {
+ if( (int32)periodic_damage < CalculateSpellDamage(this, spellInfo, i) )
+ {
+ m_charmAISpells[SPELL_DOT_DAMAGE] = spellInfo->Id;
+ break;
+ }
+ }
+ else if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_STUN)
+ {
+ m_charmAISpells[SPELL_T_STUN] = spellInfo->Id;
+ break;
+ }
+ else if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_ROOT || spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_FEAR)
+ {
+ m_charmAISpells[SPELL_ROOT_OR_FEAR] = spellInfo->Id;
+ break;
+ }
+ }
+ }
+}
+
+void Player::AddRefundReference(ObjectGuid itemGUID)
+{
+ m_refundableItems.insert(itemGUID);
+}
+
+void Player::DeleteRefundReference(ObjectGuid itemGUID)
+{
+ RefundableItemsSet::iterator itr = m_refundableItems.find(itemGUID);
+ if (itr != m_refundableItems.end())
+ m_refundableItems.erase(itr);
+}
+
+void Player::SendRefundInfo(Item* item)
+{
+ // This function call unsets ITEM_FLAGS_REFUNDABLE if played time is over 2 hours.
+ item->UpdatePlayedTime(this);
+
+ if (!item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_REFUNDABLE))
+ {
+ LOG_DEBUG("entities.player.items", "Item refund: item not refundable!");
+ return;
+ }
+
+ if (GetGUID().GetCounter() != item->GetRefundRecipient()) // Formerly refundable item got traded
+ {
+ LOG_DEBUG("entities.player.items", "Item refund: item was traded!");
+ item->SetNotRefundable(this);
+ return;
+ }
+
+ ItemExtendedCostEntry const* iece = sItemExtendedCostStore.LookupEntry(item->GetPaidExtendedCost());
+ if (!iece)
+ {
+ LOG_DEBUG("entities.player.items", "Item refund: cannot find extendedcost data.");
+ return;
+ }
+
+ WorldPacket data(SMSG_ITEM_REFUND_INFO_RESPONSE, 8 + 4 + 4 + 4 + 4 * 4 + 4 * 4 + 4 + 4);
+ data << item->GetGUID(); // item guid
+ data << uint32(item->GetPaidMoney()); // money cost
+ data << uint32(iece->reqhonorpoints); // honor point cost
+ data << uint32(iece->reqarenapoints); // arena point cost
+ for (uint8 i = 0; i < MAX_ITEM_EXTENDED_COST_REQUIREMENTS; ++i) // item cost data
+ {
+ data << uint32(iece->reqitem[i]);
+ data << uint32(iece->reqitemcount[i]);
+ }
+ data << uint32(0);
+ data << uint32(GetTotalPlayedTime() - item->GetPlayedTime());
+ GetSession()->SendPacket(&data);
+}
+
+bool Player::AddItem(uint32 itemId, uint32 count)
+{
+ uint32 noSpaceForCount = 0;
+ ItemPosCountVec dest;
+ InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, count, &noSpaceForCount);
+ if (msg != EQUIP_ERR_OK)
+ count -= noSpaceForCount;
+
+ if (count == 0 || dest.empty())
+ {
+ // -- TODO: Send to mailbox if no space
+ ChatHandler(GetSession()).PSendSysMessage("You don't have any space in your bags.");
+ return false;
+ }
+
+ Item* item = StoreNewItem(dest, itemId, true);
+ if (item)
+ SendNewItem(item, count, true, false);
+ else
+ return false;
+ return true;
+}
+
+PetStable& Player::GetOrInitPetStable()
+{
+ if (!m_petStable)
+ m_petStable = std::make_unique();
+
+ return *m_petStable;
+}
+
+void Player::RefundItem(Item* item)
+{
+ if (!item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_REFUNDABLE))
+ {
+ LOG_DEBUG("entities.player.items", "Item refund: item not refundable!");
+ return;
+ }
+
+ if (item->IsRefundExpired()) // item refund has expired
+ {
+ item->SetNotRefundable(this);
+ WorldPacket data(SMSG_ITEM_REFUND_RESULT, 8 + 4);
+ data << item->GetGUID(); // Guid
+ data << uint32(10); // Error!
+ GetSession()->SendPacket(&data);
+ return;
+ }
+
+ if (GetGUID().GetCounter() != item->GetRefundRecipient()) // Formerly refundable item got traded
+ {
+ LOG_DEBUG("entities.player.items", "Item refund: item was traded!");
+ item->SetNotRefundable(this);
+ return;
+ }
+
+ ItemExtendedCostEntry const* iece = sItemExtendedCostStore.LookupEntry(item->GetPaidExtendedCost());
+ if (!iece)
+ {
+ LOG_DEBUG("entities.player.items", "Item refund: cannot find extendedcost data.");
+ return;
+ }
+
+ bool store_error = false;
+ for (uint8 i = 0; i < MAX_ITEM_EXTENDED_COST_REQUIREMENTS; ++i)
+ {
+ uint32 count = iece->reqitemcount[i];
+ uint32 itemid = iece->reqitem[i];
+
+ if (count && itemid)
+ {
+ ItemPosCountVec dest;
+ InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemid, count);
+ if (msg != EQUIP_ERR_OK)
+ {
+ store_error = true;
+ break;
+ }
+ }
+ }
+
+ if (store_error)
+ {
+ WorldPacket data(SMSG_ITEM_REFUND_RESULT, 8 + 4);
+ data << item->GetGUID(); // Guid
+ data << uint32(10); // Error!
+ GetSession()->SendPacket(&data);
+ return;
+ }
+
+ WorldPacket data(SMSG_ITEM_REFUND_RESULT, 8 + 4 + 4 + 4 + 4 + 4 * 4 + 4 * 4);
+ data << item->GetGUID(); // item guid
+ data << uint32(0); // 0, or error code
+ data << uint32(item->GetPaidMoney()); // money cost
+ data << uint32(iece->reqhonorpoints); // honor point cost
+ data << uint32(iece->reqarenapoints); // arena point cost
+ for (uint8 i = 0; i < MAX_ITEM_EXTENDED_COST_REQUIREMENTS; ++i) // item cost data
+ {
+ data << uint32(iece->reqitem[i]);
+ data << uint32(iece->reqitemcount[i]);
+ }
+ GetSession()->SendPacket(&data);
+
+ uint32 moneyRefund = item->GetPaidMoney(); // item-> will be invalidated in DestroyItem
+
+ // Save all relevant data to DB to prevent desynchronisation exploits
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+
+ // Delete any references to the refund data
+ item->SetNotRefundable(this, true, &trans);
+
+ // Destroy item
+ DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
+
+ // Grant back extendedcost items
+ for (uint8 i = 0; i < MAX_ITEM_EXTENDED_COST_REQUIREMENTS; ++i)
+ {
+ uint32 count = iece->reqitemcount[i];
+ uint32 itemid = iece->reqitem[i];
+ if (count && itemid)
+ {
+ ItemPosCountVec dest;
+ InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemid, count);
+ ASSERT(msg == EQUIP_ERR_OK); /// Already checked before
+ Item* it = StoreNewItem(dest, itemid, true);
+ SendNewItem(it, count, true, false, true);
+ }
+ }
+
+ // Grant back money
+ if (moneyRefund)
+ ModifyMoney(moneyRefund); // Saved in SaveInventoryAndGoldToDB
+
+ // Grant back Honor points
+ if (uint32 honorRefund = iece->reqhonorpoints)
+ ModifyHonorPoints(honorRefund, trans);
+
+ // Grant back Arena points
+ if (uint32 arenaRefund = iece->reqarenapoints)
+ ModifyArenaPoints(arenaRefund, trans);
+
+ SaveInventoryAndGoldToDB(trans);
+
+ CharacterDatabase.CommitTransaction(trans);
+}
+
+void Player::SetRandomWinner(bool isWinner)
+{
+ m_IsBGRandomWinner = isWinner;
+ if (m_IsBGRandomWinner)
+ {
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_BATTLEGROUND_RANDOM);
+ stmt->SetData(0, GetGUID().GetCounter());
+ CharacterDatabase.Execute(stmt);
+ }
+}
+
+void Player::_LoadRandomBGStatus(PreparedQueryResult result)
+{
+ if (result)
+ m_IsBGRandomWinner = true;
+}
+
+float Player::GetAverageItemLevel()
+{
+ float sum = 0;
+ uint32 count = 0;
+ uint8 level = GetLevel();
+
+ for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
+ {
+ // don't check tabard, ranged, offhand or shirt
+ if (i == EQUIPMENT_SLOT_TABARD || i == EQUIPMENT_SLOT_RANGED || i == EQUIPMENT_SLOT_OFFHAND || i == EQUIPMENT_SLOT_BODY)
+ continue;
+
+ if (m_items[i] && m_items[i]->GetTemplate())
+ sum += m_items[i]->GetTemplate()->GetItemLevelIncludingQuality(level);
+
+ ++count;
+ }
+
+ return std::max(0.0f, sum / (float)count);
+}
+
+float Player::GetAverageItemLevelForDF()
+{
+ float sum = 0;
+ uint32 count = 0;
+ uint8 level = GetLevel();
+
+ for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
+ {
+ // don't check tabard, ranged, offhand or shirt
+ if (i == EQUIPMENT_SLOT_TABARD || i == EQUIPMENT_SLOT_RANGED || i == EQUIPMENT_SLOT_OFFHAND || i == EQUIPMENT_SLOT_BODY)
+ continue;
+
+ if (m_items[i] && m_items[i]->GetTemplate())
+ {
+ if (m_items[i]->GetTemplate()->Quality == ITEM_QUALITY_HEIRLOOM)
+ sum += level * 2.33f;
+ else
+ sum += m_items[i]->GetTemplate()->ItemLevel;
+ }
+
+ ++count;
+ }
+
+ return std::max(0.0f, sum / (float)count);
+}
+
+void Player::_LoadInstanceTimeRestrictions(PreparedQueryResult result)
+{
+ if (!result)
+ return;
+
+ do
+ {
+ Field* fields = result->Fetch();
+ _instanceResetTimes.insert(InstanceTimeMap::value_type(fields[0].Get(), fields[1].Get()));
+ } while (result->NextRow());
+}
+
+void Player::_LoadBrewOfTheMonth(PreparedQueryResult result)
+{
+ uint32 lastEventId = 0;
+ if (result)
+ {
+ Field* fields = result->Fetch();
+ lastEventId = fields[0].Get();
+ }
+
+ uint16 month = static_cast(Acore::Time::GetMonth());
+ uint16 eventId = month;
+ if (eventId < 9)
+ eventId += 3;
+ else
+ eventId -= 9;
+
+ // Brew of the Month October (first in list)
+ eventId += 34;
+
+ if (lastEventId != eventId && IsEventActive(eventId) && HasAchieved(2796 /* Brew of the Month*/))
+ {
+ // Send Mail
+ CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
+ MailSender sender(MAIL_CREATURE, 27487 /*NPC_BREW_OF_THE_MONTH_CLUB*/);
+ MailDraft draft(uint16(212 + month)); // 212 is starting template id
+ draft.SendMailTo(trans, MailReceiver(this, GetGUID().GetCounter()), sender);
+
+ // Update Event Id
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_BREW_OF_THE_MONTH);
+ stmt->SetData(0, GetGUID().GetCounter());
+ stmt->SetData(1, uint32(eventId));
+ trans->Append(stmt);
+
+ CharacterDatabase.CommitTransaction(trans);
+ }
+}
+
+void Player::_LoadPetStable(uint8 petStableSlots, PreparedQueryResult result)
+{
+ if (!petStableSlots && !result)
+ return;
+
+ m_petStable = std::make_unique();
+ m_petStable->MaxStabledPets = petStableSlots;
+
+ if (m_petStable->MaxStabledPets > MAX_PET_STABLES)
+ {
+ LOG_ERROR("entities.player", "Player::LoadFromDB: Player ({}) can't have more stable slots than {}, but has {} in DB",
+ GetGUID().ToString(), MAX_PET_STABLES, m_petStable->MaxStabledPets);
+
+ m_petStable->MaxStabledPets = MAX_PET_STABLES;
+ }
+
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ // SELECT id, entry, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ?
+ if (result)
+ {
+ do
+ {
+ Field* fields = result->Fetch();
+ PetStable::PetInfo petInfo;
+ petInfo.PetNumber = fields[0].Get();
+ petInfo.CreatureId = fields[1].Get();
+ petInfo.DisplayId = fields[2].Get();
+ petInfo.Level = fields[3].Get();
+ petInfo.Experience = fields[4].Get();
+ petInfo.ReactState = ReactStates(fields[5].Get());
+ PetSaveMode slot = PetSaveMode(fields[6].Get());
+ petInfo.Name = fields[7].Get();
+ petInfo.WasRenamed = fields[8].Get();
+ petInfo.Health = fields[9].Get();
+ petInfo.Mana = fields[10].Get();
+ petInfo.Happiness = fields[11].Get();
+ petInfo.ActionBar = fields[12].Get();
+ petInfo.LastSaveTime = fields[13].Get();
+ petInfo.CreatedBySpellId = fields[14].Get();
+ petInfo.Type = PetType(fields[15].Get());
+
+ if (slot == PET_SAVE_AS_CURRENT)
+ m_petStable->CurrentPet = std::move(petInfo);
+ else if (slot >= PET_SAVE_FIRST_STABLE_SLOT && slot <= PET_SAVE_LAST_STABLE_SLOT)
+ m_petStable->StabledPets[slot - 1] = std::move(petInfo);
+ else if (slot == PET_SAVE_NOT_IN_SLOT)
+ m_petStable->UnslottedPets.push_back(std::move(petInfo));
+
+ } while (result->NextRow());
+ }
+}
+
+void Player::_SaveInstanceTimeRestrictions(CharacterDatabaseTransaction trans)
+{
+ if (_instanceResetTimes.empty())
+ return;
+
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ACCOUNT_INSTANCE_LOCK_TIMES);
+ stmt->SetData(0, GetSession()->GetAccountId());
+ trans->Append(stmt);
+
+ for (InstanceTimeMap::const_iterator itr = _instanceResetTimes.begin(); itr != _instanceResetTimes.end(); ++itr)
+ {
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ACCOUNT_INSTANCE_LOCK_TIMES);
+ stmt->SetData(0, GetSession()->GetAccountId());
+ stmt->SetData(1, itr->first);
+ stmt->SetData(2, (int64)itr->second);
+ trans->Append(stmt);
+ }
+}
+
+bool Player::IsInWhisperWhiteList(ObjectGuid guid)
+{
+ for (auto const& itr : WhisperList)
+ {
+ if (itr == guid)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool Player::SetDisableGravity(bool disable, bool packetOnly /*= false*/, bool /*updateAnimationTier = true*/)
+{
+ if (!packetOnly && !Unit::SetDisableGravity(disable))
+ return false;
+
+ WorldPacket data(disable ? SMSG_MOVE_GRAVITY_DISABLE : SMSG_MOVE_GRAVITY_ENABLE, 12);
+ data << GetPackGUID();
+ data << uint32(0); //! movement counter
+ SendDirectMessage(&data);
+
+ data.Initialize(MSG_MOVE_GRAVITY_CHNG, 64);
+ data << GetPackGUID();
+ BuildMovementPacket(&data);
+ SendMessageToSet(&data, false);
+ return true;
+}
+
+bool Player::SetCanFly(bool apply, bool packetOnly /*= false*/)
+{
+ sScriptMgr->AnticheatSetCanFlybyServer(this, apply);
+
+ if (!packetOnly && !Unit::SetCanFly(apply))
+ return false;
+
+ if (!apply)
+ SetFallInformation(GameTime::GetGameTime().count(), GetPositionZ());
+
+ WorldPacket data(apply ? SMSG_MOVE_SET_CAN_FLY : SMSG_MOVE_UNSET_CAN_FLY, 12);
+ data << GetPackGUID();
+ data << uint32(0); //! movement counter
+ SendDirectMessage(&data);
+
+ data.Initialize(MSG_MOVE_UPDATE_CAN_FLY, 64);
+ data << GetPackGUID();
+ BuildMovementPacket(&data);
+ SendMessageToSet(&data, false);
+ return true;
+}
+
+bool Player::SetHover(bool apply, bool packetOnly /*= false*/, bool /*updateAnimationTier = true*/)
+{
+ // moved inside, flag can be removed on landing and wont send appropriate packet to client when aura is removed
+ if (!packetOnly /* && !Unit::SetHover(apply)*/)
+ {
+ Unit::SetHover(apply);
+ // return false;
+ }
+
+ WorldPacket data(apply ? SMSG_MOVE_SET_HOVER : SMSG_MOVE_UNSET_HOVER, 12);
+ data << GetPackGUID();
+ data << uint32(0); //! movement counter
+ SendDirectMessage(&data);
+
+ data.Initialize(MSG_MOVE_HOVER, 64);
+ data << GetPackGUID();
+ BuildMovementPacket(&data);
+ SendMessageToSet(&data, false);
+ return true;
+}
+
+bool Player::SetWaterWalking(bool apply, bool packetOnly /*= false*/)
+{
+ // moved inside, flag can be removed on landing and wont send appropriate packet to client when aura is removed
+ if (!packetOnly /* && !Unit::SetWaterWalking(apply)*/)
+ {
+ Unit::SetWaterWalking(apply);
+ // return false;
+ }
+
+ WorldPacket data(apply ? SMSG_MOVE_WATER_WALK : SMSG_MOVE_LAND_WALK, 12);
+ data << GetPackGUID();
+ data << uint32(0); //! movement counter
+ SendDirectMessage(&data);
+
+ data.Initialize(MSG_MOVE_WATER_WALK, 64);
+ data << GetPackGUID();
+ BuildMovementPacket(&data);
+ SendMessageToSet(&data, false);
+ return true;
+}
+
+bool Player::SetFeatherFall(bool apply, bool packetOnly /*= false*/)
+{
+ // Xinef: moved inside, flag can be removed on landing and wont send appropriate packet to client when aura is removed
+ if (!packetOnly/* && !Unit::SetFeatherFall(apply)*/)
+ {
+ Unit::SetFeatherFall(apply);
+ //return false;
+ }
+
+ WorldPacket data(apply ? SMSG_MOVE_FEATHER_FALL : SMSG_MOVE_NORMAL_FALL, 12);
+ data << GetPackGUID();
+ data << uint32(0); //! movement counter
+ SendDirectMessage(&data);
+
+ data.Initialize(MSG_MOVE_FEATHER_FALL, 64);
+ data << GetPackGUID();
+ BuildMovementPacket(&data);
+ SendMessageToSet(&data, false);
+ return true;
+}
+
+Guild* Player::GetGuild() const
+{
+ uint32 guildId = GetGuildId();
+ return guildId ? sGuildMgr->GetGuildById(guildId) : nullptr;
+}
+
+uint32 Player::GetSpec(int8 spec)
+{
+ uint32 mostTalentTabId = 0;
+ uint32 mostTalentCount = 0;
+ uint32 specIdx = 0;
+
+ if (m_specsCount) // not all instances of Player have a spec for some reason
+ {
+ if (spec < 0)
+ specIdx = m_activeSpec;
+ else
+ specIdx = spec;
+ // find class talent tabs (all players have 3 talent tabs)
+ uint32 const* talentTabIds = GetTalentTabPages(getClass());
+
+ for (uint8 i = 0; i < MAX_TALENT_TABS; ++i)
+ {
+ uint32 talentCount = 0;
+ uint32 talentTabId = talentTabIds[i];
+ 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 (0~4)
+ int8 curtalent_maxrank = -1;
+ for (int8 rank = MAX_TALENT_RANK - 1; rank >= 0; --rank)
+ {
+ if (talentInfo->RankID[rank] && HasTalent(talentInfo->RankID[rank], specIdx))
+ {
+ curtalent_maxrank = rank;
+ break;
+ }
+ }
+
+ // not learned talent
+ if (curtalent_maxrank < 0)
+ continue;
+
+ talentCount += curtalent_maxrank + 1;
+ }
+
+ if (mostTalentCount < talentCount)
+ {
+ mostTalentCount = talentCount;
+ mostTalentTabId = talentTabId;
+ }
+ }
+ }
+ return mostTalentTabId;
+}
+
+bool Player::HasTankSpec()
+{
+ switch (GetSpec())
+ {
+ case TALENT_TREE_WARRIOR_PROTECTION:
+ case TALENT_TREE_PALADIN_PROTECTION:
+ case TALENT_TREE_DEATH_KNIGHT_BLOOD:
+ return true;
+ case TALENT_TREE_DRUID_FERAL_COMBAT:
+ if (GetShapeshiftForm() == FORM_BEAR || GetShapeshiftForm() == FORM_DIREBEAR)
+ return true;
+ break;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Player::HasMeleeSpec()
+{
+ switch (GetSpec(GetActiveSpec()))
+ {
+ case TALENT_TREE_WARRIOR_ARMS:
+ case TALENT_TREE_WARRIOR_FURY:
+ case TALENT_TREE_PALADIN_RETRIBUTION:
+ case TALENT_TREE_ROGUE_ASSASSINATION:
+ case TALENT_TREE_ROGUE_COMBAT:
+ case TALENT_TREE_ROGUE_SUBTLETY:
+ case TALENT_TREE_DEATH_KNIGHT_FROST:
+ case TALENT_TREE_DEATH_KNIGHT_UNHOLY:
+ case TALENT_TREE_SHAMAN_ENHANCEMENT:
+ return true;
+ case TALENT_TREE_DRUID_FERAL_COMBAT:
+ if (GetShapeshiftForm() == FORM_CAT)
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Player::HasCasterSpec()
+{
+ switch (GetSpec(GetActiveSpec()))
+ {
+ case TALENT_TREE_PRIEST_SHADOW:
+ case TALENT_TREE_SHAMAN_ELEMENTAL:
+ case TALENT_TREE_MAGE_ARCANE:
+ case TALENT_TREE_MAGE_FIRE:
+ case TALENT_TREE_MAGE_FROST:
+ case TALENT_TREE_WARLOCK_AFFLICTION:
+ case TALENT_TREE_WARLOCK_DEMONOLOGY:
+ case TALENT_TREE_WARLOCK_DESTRUCTION:
+ case TALENT_TREE_DRUID_BALANCE:
+ case TALENT_TREE_HUNTER_BEAST_MASTERY:
+ case TALENT_TREE_HUNTER_MARKSMANSHIP:
+ case TALENT_TREE_HUNTER_SURVIVAL:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Player::HasHealSpec()
+{
+ switch (GetSpec(GetActiveSpec()))
+ {
+ case TALENT_TREE_PALADIN_HOLY:
+ case TALENT_TREE_PRIEST_DISCIPLINE:
+ case TALENT_TREE_PRIEST_HOLY:
+ case TALENT_TREE_SHAMAN_RESTORATION:
+ case TALENT_TREE_DRUID_RESTORATION:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+std::unordered_map Player::bgZoneIdToFillWorldStates = {};
+
+void Player::SetRestFlag(RestFlag restFlag, uint32 triggerId /*= 0*/)
+{
+ uint32 oldRestMask = _restFlagMask;
+ _restFlagMask |= restFlag;
+
+ if (!oldRestMask && _restFlagMask) // only set flag/time on the first rest state
+ {
+ _restTime = GameTime::GetGameTime().count();
+ SetPlayerFlag(PLAYER_FLAGS_RESTING);
+ }
+
+ if (triggerId)
+ _innTriggerId = triggerId;
+}
+
+void Player::RemoveRestFlag(RestFlag restFlag)
+{
+ uint32 oldRestMask = _restFlagMask;
+ _restFlagMask &= ~restFlag;
+
+ if (oldRestMask && !_restFlagMask) // only remove flag/time on the last rest state remove
+ {
+ _restTime = 0;
+ RemovePlayerFlag(PLAYER_FLAGS_RESTING);
+ }
+}
+
+uint32 Player::DoRandomRoll(uint32 minimum, uint32 maximum)
+{
+ ASSERT(minimum <= maximum);
+
+ uint32 roll = urand(minimum, maximum);
+
+ WorldPackets::Misc::RandomRoll randomRoll;
+ randomRoll.Min = minimum;
+ randomRoll.Max = maximum;
+ randomRoll.Result = roll;
+ randomRoll.Roller = GetGUID();
+ if (Group* group = GetGroup())
+ group->BroadcastPacket(randomRoll.Write(), false);
+ else
+ SendDirectMessage(randomRoll.Write());
+
+ return roll;
+}
+
+void Player::SetArenaTeamInfoField(uint8 slot, ArenaTeamInfoType type, uint32 value)
+{
+ if (sScriptMgr->NotSetArenaTeamInfoField(this, slot, type, value))
+ SetUInt32Value(PLAYER_FIELD_ARENA_TEAM_INFO_1_1 + (slot * ARENA_TEAM_END) + type, value);
+}
+
+uint32 Player::GetArenaPersonalRating(uint8 slot) const
+{
+ uint32 result = GetUInt32Value(PLAYER_FIELD_ARENA_TEAM_INFO_1_1 + (slot * ARENA_TEAM_END) + ARENA_TEAM_PERSONAL_RATING);
+
+ sScriptMgr->OnGetArenaPersonalRating(const_cast(this), slot, result);
+
+ return result;
+}
+
+uint32 Player::GetArenaTeamId(uint8 slot) const
+{
+ uint32 result = GetUInt32Value(PLAYER_FIELD_ARENA_TEAM_INFO_1_1 + (slot * ARENA_TEAM_END) + ARENA_TEAM_ID);
+
+ sScriptMgr->OnGetArenaTeamId(const_cast(this), slot, result);
+
+ return result;
+}
+
+bool Player::IsFFAPvP()
+{
+ bool result = Unit::IsFFAPvP();
+
+ sScriptMgr->OnIsFFAPvP(this, result);
+
+ return result;
+}
+
+bool Player::IsPvP()
+{
+ bool result = Unit::IsPvP();
+
+ sScriptMgr->OnIsPvP(this, result);
+
+ return result;
+}
+
+uint16 Player::GetMaxSkillValueForLevel() const
+{
+ uint16 result = Unit::GetMaxSkillValueForLevel();
+
+ sScriptMgr->OnGetMaxSkillValueForLevel(const_cast(this), result);
+
+ return result;
+}
+
+float Player::GetQuestRate(bool isDFQuest)
+{
+ float result = isDFQuest ? sWorld->getRate(RATE_XP_QUEST_DF) : sWorld->getRate(RATE_XP_QUEST);
+
+ sScriptMgr->OnGetQuestRate(this, result);
+
+ return result;
+}
+
+void Player::SetServerSideVisibility(ServerSideVisibilityType type, AccountTypes sec)
+{
+ sScriptMgr->OnSetServerSideVisibility(this, type, sec);
+
+ m_serverSideVisibility.SetValue(type, sec);
+}
+
+void Player::SetServerSideVisibilityDetect(ServerSideVisibilityType type, AccountTypes sec)
+{
+ sScriptMgr->OnSetServerSideVisibilityDetect(this, type, sec);
+
+ m_serverSideVisibilityDetect.SetValue(type, sec);
+}
+
+void Player::SetFarSightDistance(float radius)
+{
+ _farSightDistance = radius;
+}
+
+void Player::ResetFarSightDistance()
+{
+ _farSightDistance.reset();
+}
+
+Optional Player::GetFarSightDistance() const
+{
+ return _farSightDistance;
+}
+
+float Player::GetSightRange(WorldObject const* target) const
+{
+ float sightRange = WorldObject::GetSightRange(target);
+ if (_farSightDistance)
+ {
+ sightRange += *_farSightDistance;
+ }
+
+ return sightRange;
+}
+
+std::string Player::GetPlayerName()
+{
+ std::string name = GetName();
+ std::string color = "";
+
+ switch (getClass())
+ {
+ case CLASS_DEATH_KNIGHT: color = "|cffC41F3B"; break;
+ case CLASS_DRUID: color = "|cffFF7D0A"; break;
+ case CLASS_HUNTER: color = "|cffABD473"; break;
+ case CLASS_MAGE: color = "|cff69CCF0"; break;
+ case CLASS_PALADIN: color = "|cffF58CBA"; break;
+ case CLASS_PRIEST: color = "|cffFFFFFF"; break;
+ case CLASS_ROGUE: color = "|cffFFF569"; break;
+ case CLASS_SHAMAN: color = "|cff0070DE"; break;
+ case CLASS_WARLOCK: color = "|cff9482C9"; break;
+ case CLASS_WARRIOR: color = "|cffC79C6E"; break;
+ }
+
+ return "|Hplayer:" + name + "|h" + color + name + "|h|r";
+}
+
+void Player::SetSummonPoint(uint32 mapid, float x, float y, float z, uint32 delay /*= 0*/, bool asSpectator /*= false*/)
+{
+ m_summon_expire = GameTime::GetGameTime().count() + (delay ? delay : MAX_PLAYER_SUMMON_DELAY);
+ m_summon_mapid = mapid;
+ m_summon_x = x;
+ m_summon_y = y;
+ m_summon_z = z;
+ m_summon_asSpectator = asSpectator;
+}
+
+bool Player::IsSummonAsSpectator() const
+{
+ return m_summon_asSpectator && m_summon_expire >= GameTime::GetGameTime().count();
+}
+
+bool Player::HasSpellCooldown(uint32 spell_id) const
+{
+ SpellCooldowns::const_iterator itr = m_spellCooldowns.find(spell_id);
+ return itr != m_spellCooldowns.end() && itr->second.end > getMSTime();
+}
+
+bool Player::HasSpellItemCooldown(uint32 spell_id, uint32 itemid) const
+{
+ SpellCooldowns::const_iterator itr = m_spellCooldowns.find(spell_id);
+ return itr != m_spellCooldowns.end() && itr->second.end > getMSTime() && itr->second.itemid == itemid;
+}
+
+uint32 Player::GetSpellCooldownDelay(uint32 spell_id) const
+{
+ SpellCooldowns::const_iterator itr = m_spellCooldowns.find(spell_id);
+ return uint32(itr != m_spellCooldowns.end() && itr->second.end > getMSTime() ? itr->second.end - getMSTime() : 0);
+}
+
+std::string Player::GetDebugInfo() const
+{
+ std::stringstream sstr;
+ sstr << Unit::GetDebugInfo();
+ return sstr.str();
+}