Files
TrinityCore/src/server/game/Entities/Player/Player.cpp

29157 lines
1.1 MiB

/*
* This file is part of the TrinityCore 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 General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Player.h"
#include "AreaTrigger.h"
#include "AccountMgr.h"
#include "AchievementMgr.h"
#include "ArenaTeam.h"
#include "ArenaTeamMgr.h"
#include "AzeriteEmpoweredItem.h"
#include "AzeriteItem.h"
#include "Bag.h"
#include "Battlefield.h"
#include "BattlefieldMgr.h"
#include "Battleground.h"
#include "BattlegroundMgr.h"
#include "BattlegroundPackets.h"
#include "BattlegroundScore.h"
#include "BattlePetMgr.h"
#include "CellImpl.h"
#include "Channel.h"
#include "ChannelMgr.h"
#include "CharacterCache.h"
#include "CharacterDatabaseCleaner.h"
#include "CharacterTemplateDataStore.h"
#include "CharacterPackets.h"
#include "Chat.h"
#include "ChatPackets.h"
#include "ChatTextBuilder.h"
#include "CinematicMgr.h"
#include "CombatLogPackets.h"
#include "CombatPackets.h"
#include "Common.h"
#include "ConditionMgr.h"
#include "CreatureAI.h"
#include "DB2Stores.h"
#include "DatabaseEnv.h"
#include "DisableMgr.h"
#include "DuelPackets.h"
#include "EquipmentSetPackets.h"
#include "Formulas.h"
#include "GameEventMgr.h"
#include "GameObjectAI.h"
#include "Garrison.h"
#include "GitRevision.h"
#include "GossipDef.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "Group.h"
#include "GroupMgr.h"
#include "GameTables.h"
#include "GameTime.h"
#include "Guild.h"
#include "GuildMgr.h"
#include "InstancePackets.h"
#include "InstanceSaveMgr.h"
#include "InstanceScript.h"
#include "ItemPackets.h"
#include "KillRewarder.h"
#include "LanguageMgr.h"
#include "LFGMgr.h"
#include "Log.h"
#include "LootItemStorage.h"
#include "LootMgr.h"
#include "LootPackets.h"
#include "Mail.h"
#include "MailPackets.h"
#include "MapManager.h"
#include "MiscPackets.h"
#include "MotionMaster.h"
#include "MovementPackets.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "OutdoorPvP.h"
#include "OutdoorPvPMgr.h"
#include "Pet.h"
#include "PetPackets.h"
#include "PoolMgr.h"
#include "PetitionMgr.h"
#include "PhasingHandler.h"
#include "QueryCallback.h"
#include "QueryHolder.h"
#include "QuestDef.h"
#include "QuestObjectiveCriteriaMgr.h"
#include "QuestPackets.h"
#include "RealmList.h"
#include "ReputationMgr.h"
#include "RestMgr.h"
#include "Scenario.h"
#include "SkillDiscovery.h"
#include "SocialMgr.h"
#include "Spell.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
#include "SpellHistory.h"
#include "SpellMgr.h"
#include "SpellPackets.h"
#include "TalentPackets.h"
#include "ToyPackets.h"
#include "TradeData.h"
#include "TransmogrificationPackets.h"
#include "Transport.h"
#include "UpdateData.h"
#include "Util.h"
#include "Vehicle.h"
#include "VehiclePackets.h"
#include "Weather.h"
#include "WeatherMgr.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "WorldStatePackets.h"
#include <G3D/g3dmath.h>
#include <sstream>
#define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS)
// corpse reclaim times
#define DEATH_EXPIRE_STEP (5*MINUTE)
#define MAX_DEATH_COUNT 3
static uint32 copseReclaimDelay[MAX_DEATH_COUNT] = { 30, 60, 120 };
uint64 const MAX_MONEY_AMOUNT = 99999999999ULL;
Player::Player(WorldSession* session) : Unit(true), m_sceneMgr(this)
{
m_speakTime = 0;
m_speakCount = 0;
m_objectType |= TYPEMASK_PLAYER;
m_objectTypeId = TYPEID_PLAYER;
m_session = session;
m_ingametime = 0;
m_sharedQuestId = 0;
m_ExtraFlags = 0;
m_spellModTakingSpell = nullptr;
// players always accept
if (!GetSession()->HasPermission(rbac::RBAC_PERM_CAN_FILTER_WHISPERS))
SetAcceptWhispers(true);
m_combatExitTime = 0;
m_regenTimer = 0;
m_regenTimerCount = 0;
m_foodEmoteTimerCount = 0;
m_weaponChangeTimer = 0;
m_zoneUpdateId = uint32(-1);
m_zoneUpdateTimer = 0;
m_areaUpdateId = 0;
m_team = 0;
m_nextSave = sWorld->getIntConfig(CONFIG_INTERVAL_SAVE);
m_customizationsChanged = false;
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_bPassOnGroupLoot = false;
m_GuildIdInvited = UI64LIT(0);
m_ArenaTeamIdInvited = 0;
m_atLoginFlags = AT_LOGIN_NONE;
mSemaphoreTeleport_Near = false;
mSemaphoreTeleport_Far = false;
m_DelayedOperations = 0;
m_bCanDelayTeleport = false;
m_bHasDelayedTeleport = false;
m_teleport_options = 0;
m_trade = nullptr;
m_createTime = 0;
m_createMode = PlayerCreateMode::Normal;
m_cinematic = 0;
m_movie = 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_hostileReferenceCheckTimer = 0;
m_drunkTimer = 0;
m_deathTimer = 0;
m_deathExpireTime = 0;
m_swingErrorMsg = 0;
for (uint8 j = 0; j < PLAYER_MAX_BATTLEGROUND_QUEUES; ++j)
{
m_bgBattlegroundQueueID[j].bgQueueTypeId = BATTLEGROUND_QUEUE_NONE;
m_bgBattlegroundQueueID[j].invitedToInstance = 0;
m_bgBattlegroundQueueID[j].joinTime = 0;
}
m_logintime = GameTime::GetGameTime();
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_titanGripPenaltySpellId = 0;
m_temporaryUnsummonedPetNumber = 0;
//cache for CreatedBySpell to allow
//returning reagents for temporarily removed pets
//when dying/logging out
m_oldpetspell = 0;
m_lastpetnumber = 0;
m_mailsUpdated = false;
unReadMails = 0;
m_nextMailDelivereTime = 0;
m_itemUpdateQueueBlocked = false;
for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
m_forced_speed_changes[i] = 0;
m_movementForceModMagnitudeChanges = 0;
m_stableSlots = 0;
/////////////////// Instance System /////////////////////
m_HomebindTimer = 0;
m_InstanceValid = true;
m_dungeonDifficulty = DIFFICULTY_NORMAL;
m_raidDifficulty = DIFFICULTY_NORMAL_RAID;
m_legacyRaidDifficulty = DIFFICULTY_10_N;
m_prevMapDifficulty = DIFFICULTY_NORMAL_RAID;
m_lastPotionId = 0;
for (uint8 i = 0; i < BASEMOD_END; ++i)
{
m_auraBaseFlatMod[i] = 0.0f;
m_auraBasePctMod[i] = 1.0f;
}
for (uint8 i = 0; i < MAX_COMBAT_RATING; i++)
m_baseRatingValue[i] = 0;
m_baseSpellPower = 0;
m_baseManaRegen = 0;
m_baseHealthRegen = 0;
m_spellPenetrationItemMod = 0;
// Honor System
m_lastHonorUpdateTime = GameTime::GetGameTime();
m_IsBGRandomWinner = false;
// Player summoning
m_summon_expire = 0;
m_unitMovedByMe = this;
m_playerMovingMe = this;
m_seer = this;
m_homebindAreaId = 0;
m_contestedPvPTimer = 0;
m_declinedname = nullptr;
m_isActive = true;
m_runes = nullptr;
m_lastFallTime = 0;
m_lastFallZ = 0;
m_fishingSteps = 0;
m_ControlledByPlayer = true;
sWorld->IncreasePlayerCount();
m_ChampioningFaction = 0;
for (uint8 i = 0; i < MAX_POWERS_PER_CLASS; ++i)
m_powerFraction[i] = 0;
isDebugAreaTriggers = false;
m_WeeklyQuestChanged = false;
m_MonthlyQuestChanged = false;
m_SeasonalQuestChanged = false;
SetPendingBind(0, 0);
_activeCheats = CHEAT_NONE;
healthBeforeDuel = 0;
manaBeforeDuel = 0;
memset(_voidStorageItems, 0, VOID_STORAGE_MAX_SLOT * sizeof(VoidStorageItem*));
_cinematicMgr = new CinematicMgr(this);
m_achievementMgr = new PlayerAchievementMgr(this);
m_reputationMgr = new ReputationMgr(this);
m_questObjectiveCriteriaMgr = std::make_unique<QuestObjectiveCriteriaMgr>(this);
for (uint8 i = 0; i < MAX_CUF_PROFILES; ++i)
_CUFProfiles[i] = nullptr;
m_groupUpdateTimer.Reset(5000);
_advancedCombatLoggingEnabled = false;
_restMgr = std::make_unique<RestMgr>(this);
_usePvpItemLevels = false;
}
Player::~Player()
{
// it must be unloaded already in PlayerLogout and accessed only for logged in 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;
//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;
delete _cinematicMgr;
for (uint8 i = 0; i < VOID_STORAGE_MAX_SLOT; ++i)
delete _voidStorageItems[i];
sWorld->DecreasePlayerCount();
}
void Player::CleanupsBeforeDelete(bool finalCleanup)
{
TradeCancel(false);
DuelComplete(DUEL_INTERRUPTED);
Unit::CleanupsBeforeDelete(finalCleanup);
// clean up player-instance binds, may unload some instance saves
for (auto difficultyItr = m_boundInstances.begin(); difficultyItr != m_boundInstances.end(); ++difficultyItr)
for (auto itr = difficultyItr->second.begin(); itr != difficultyItr->second.end(); ++itr)
itr->second.save->RemovePlayer(this);
}
bool Player::Create(ObjectGuid::LowType guidlow, WorldPackets::Character::CharacterCreateInfo const* createInfo)
{
//FIXME: outfitId not used in player creating
/// @todo need more checks against packet modifications
Object::_Create(ObjectGuid::Create<HighGuid::Player>(guidlow));
m_name = createInfo->Name;
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(createInfo->Race, createInfo->Class);
if (!info)
{
TC_LOG_ERROR("entities.player.cheat", "Player::Create: Possible hacking attempt: Account %u tried to create a character named '%s' with an invalid race/class pair (%u/%u) - refusing to do so.",
GetSession()->GetAccountId(), m_name.c_str(), createInfo->Race, createInfo->Class);
return false;
}
for (uint8 i = 0; i < PLAYER_SLOTS_COUNT; i++)
m_items[i] = nullptr;
ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(createInfo->Class);
if (!cEntry)
{
TC_LOG_ERROR("entities.player.cheat", "Player::Create: Possible hacking attempt: Account %u tried to create a character named '%s' with an invalid character class (%u) - refusing to do so (wrong DBC-files?)",
GetSession()->GetAccountId(), m_name.c_str(), createInfo->Class);
return false;
}
if (!GetSession()->ValidateAppearance(Races(createInfo->Race), Classes(createInfo->Class), Gender(createInfo->Sex), MakeChrCustomizationChoiceRange(createInfo->Customizations)))
{
TC_LOG_ERROR("entities.player.cheat", "Player::Create: Possible hacking-attempt: Account %u tried creating a character named '%s' with invalid appearance attributes - refusing to do so",
GetSession()->GetAccountId(), m_name.c_str());
return false;
}
PlayerInfo::CreatePosition const& position = createInfo->UseNPE && info->createPositionNPE ? *info->createPositionNPE : info->createPosition;
m_createTime = GameTime::GetGameTime();
m_createMode = createInfo->UseNPE && info->createPositionNPE ? PlayerCreateMode::NPE : PlayerCreateMode::Normal;
Relocate(position.Loc);
SetMap(sMapMgr->CreateMap(position.Loc.GetMapId(), this));
if (position.TransportGuid)
{
if (Transport* transport = HashMapHolder<Transport>::Find(ObjectGuid::Create<HighGuid::Transport>(*position.TransportGuid)))
{
transport->AddPassenger(this);
m_movementInfo.transport.pos.Relocate(position.Loc);
float x, y, z, o;
position.Loc.GetPosition(x, y, z, o);
transport->CalculatePassengerPosition(x, y, z, &o);
Relocate(x, y, z, o);
}
}
// set initial homebind position
SetHomebind(*this, GetAreaId());
uint8 powertype = cEntry->DisplayPower;
SetObjectScale(1.0f);
SetFactionForRace(createInfo->Race);
if (!IsValidGender(createInfo->Sex))
{
TC_LOG_ERROR("entities.player.cheat", "Player::Create: Possible hacking attempt: Account %u tried to create a character named '%s' with an invalid gender (%u) - refusing to do so",
GetSession()->GetAccountId(), m_name.c_str(), createInfo->Sex);
return false;
}
SetRace(createInfo->Race);
SetClass(createInfo->Class);
SetGender(Gender(createInfo->Sex));
SetPowerType(Powers(powertype), false);
InitDisplayIds();
if (sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_PVP || sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_RPPVP)
{
AddPvpFlag(UNIT_BYTE2_FLAG_PVP);
AddUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED);
}
AddUnitFlag2(UNIT_FLAG2_REGENERATE_POWER);
SetHoverHeight(1.0f); // default for players in 3.0.3
SetWatchedFactionIndex(-1);
SetCustomizations(Trinity::Containers::MakeIteratorPair(createInfo->Customizations.begin(), createInfo->Customizations.end()));
SetRestState(REST_TYPE_XP, (GetSession()->IsARecruiter() || GetSession()->GetRecruiterId() != 0) ? REST_STATE_RAF_LINKED : REST_STATE_NOT_RAF_LINKED);
SetRestState(REST_TYPE_HONOR, REST_STATE_NOT_RAF_LINKED);
SetNativeGender(Gender(createInfo->Sex));
SetInventorySlotCount(INVENTORY_DEFAULT_SIZE);
// set starting level
SetLevel(GetStartLevel(createInfo->Race, createInfo->Class, createInfo->TemplateSet));
InitRunes();
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::Coinage), sWorld->getIntConfig(CONFIG_START_PLAYER_MONEY));
SetCreateCurrency(CURRENCY_TYPE_APEXIS_CRYSTALS, sWorld->getIntConfig(CONFIG_CURRENCY_START_APEXIS_CRYSTALS));
SetCreateCurrency(CURRENCY_TYPE_JUSTICE_POINTS, sWorld->getIntConfig(CONFIG_CURRENCY_START_JUSTICE_POINTS));
// Played time
m_Last_tick = GameTime::GetGameTime();
m_Played_time[PLAYED_TIME_TOTAL] = 0;
m_Played_time[PLAYED_TIME_LEVEL] = 0;
// base stats and related field values
InitStatsForLevel();
InitTaxiNodesForLevel();
InitTalentForLevel();
InitializeSkillFields();
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();
SetFullPower(POWER_MANA);
// original spells
LearnDefaultSkills();
LearnCustomSpells();
// Original action bar. Do not use Player::AddActionButton because we do not have skill spells loaded at this time
// but checks will still be performed later when loading character from db in Player::_LoadActions
for (PlayerCreateInfoActions::const_iterator action_itr = info->action.begin(); action_itr != info->action.end(); ++action_itr)
{
// create new button
ActionButton& ab = m_actionButtons[action_itr->button];
// set data
ab.SetActionAndType(action_itr->action, ActionButtonType(action_itr->type));
}
// original items
for (PlayerCreateInfoItem initialItem : info->item)
StoreNewItemInBestSlots(initialItem.item_id, initialItem.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
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; 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
else
{
ItemPosCountVec sDest;
msg = CanStoreItem(NULL_BAG, NULL_SLOT, sDest, pItem, false);
if (msg == EQUIP_ERR_OK)
{
RemoveItem(INVENTORY_SLOT_BAG_0, i, true);
StoreItem(sDest, pItem, true);
}
}
}
}
// all item positions resolved
if (ChrSpecializationEntry const* defaultSpec = sDB2Manager.GetDefaultChrSpecializationForClass(GetClass()))
{
SetActiveTalentGroup(defaultSpec->OrderIndex);
SetPrimarySpecialization(defaultSpec->ID);
}
GetThreatManager().Initialize();
return true;
}
bool Player::StoreNewItemInBestSlots(uint32 titem_id, uint32 titem_amount)
{
TC_LOG_DEBUG("entities.player.items", "Player::StoreNewItemInBestSlots: Player '%s' (%s) creates initial item (ItemID: %u, Count: %u)",
GetName().c_str(), GetGUID().ToString().c_str(), 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, ItemContext::NONE, 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, GenerateItemRandomBonusListId(titem_id));
return true; // stored
}
// item can't be added
TC_LOG_ERROR("entities.player.items", "Player::StoreNewItemInBestSlots: Player '%s' (%s) can't equip or store initial item (ItemID: %u, Race: %u, Class: %u, InventoryResult: %u)",
GetName().c_str(), GetGUID().ToString().c_str(), titem_id, GetRace(), 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, false).Write());
}
void Player::StopMirrorTimer(MirrorTimerType Type)
{
m_MirrorTimer[Type] = DISABLED_MIRROR_TIMER;
SendDirectMessage(WorldPackets::Misc::StopMirrorTimer(Type).Write());
}
bool Player::IsImmuneToEnvironmentalDamage() const
{
// check for GM and death state included in isAttackableByAOE
return !isTargetableForAttack(false);
}
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, BASE_ATTACK);
Unit::CalcAbsorbResist(dmgInfo);
absorb = dmgInfo.GetAbsorb();
resist = dmgInfo.GetResist();
damage = dmgInfo.GetDamage();
break;
}
default:
break;
}
Unit::DealDamageMods(nullptr, 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;
uint32 final_damage = Unit::DealDamage(this, this, damage, nullptr, SELF_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, nullptr, false);
packet.LogData.Initialize(this);
SendCombatLogMessage(&packet);
if (!IsAlive())
{
if (type == DAMAGE_FALL) // DealDamage does not apply item durability loss from self-induced damage.
{
TC_LOG_DEBUG("entities.player", "Player::EnvironmentalDamage: Player '%s' (%s) fall to death, losing %f%% durability",
GetName().c_str(), GetGUID().ToString().c_str(), sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH));
DurabilityLossAll(sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH), false);
// durability lost message
SendDurabilityLoss(this, uint32(sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH) * 100.0f));
}
UpdateCriteria(CriteriaType::DieFromEnviromentalDamage, 1, type);
}
return final_damage;
}
int32 Player::getMaxTimer(MirrorTimerType timer) const
{
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 = 3 * MINUTE * IN_MILLISECONDS;
UnderWaterTime *= GetTotalAuraMultiplier(SPELL_AURA_MOD_WATER_BREATHING);
return UnderWaterTime;
}
case FIRE_TIMER:
{
if (!IsAlive())
return DISABLED_MIRROR_TIMER;
return 1 * IN_MILLISECONDS;
}
default:
return 0;
}
}
void Player::UpdateMirrorTimers()
{
// Desync flags for update on next HandleDrowning
if (m_MirrorTimerFlags)
m_MirrorTimerFlagsLast = ~m_MirrorTimerFlags;
}
void Player::StopMirrorTimers()
{
StopMirrorTimer(FATIGUE_TIMER);
StopMirrorTimer(BREATH_TIMER);
StopMirrorTimer(FIRE_TIMER);
}
bool Player::IsMirrorTimerActive(MirrorTimerType type) const
{
return m_MirrorTimer[type] == getMaxTimer(type);
}
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<int32>(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);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::Inebriation), newDrunkValue);
UpdateObjectVisibility();
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::Update(uint32 p_time)
{
if (!IsInWorld())
return;
// undelivered mail
if (m_nextMailDelivereTime && m_nextMailDelivereTime <= GameTime::GetGameTime())
{
SendNewMail();
++unReadMails;
// It will be recalculate at mailbox open (for unReadMails important non-0 until mailbox open, it also will be recalculated)
m_nextMailDelivereTime = 0;
}
// Update cinematic location, if 500ms have passed and we're doing a cinematic now.
_cinematicMgr->m_cinematicDiff += p_time;
if (_cinematicMgr->m_cinematicCamera && _cinematicMgr->m_activeCinematic && GetMSTimeDiffToNow(_cinematicMgr->m_lastCinematicCheck) > CINEMATIC_UPDATEDIFF)
{
_cinematicMgr->m_lastCinematicCheck = GameTime::GetGameTimeMS();
_cinematicMgr->UpdateCinematicLocation(p_time);
}
//used to implement delayed far teleport
SetCanDelayTeleport(true);
Unit::Update(p_time);
SetCanDelayTeleport(false);
time_t now = GameTime::GetGameTime();
UpdatePvPFlag(now);
UpdateContestedPvP(p_time);
UpdateDuelFlag(now);
CheckDuelDistance(now);
UpdateAfkReport(now);
if (GetCombatManager().HasPvPCombat())
if (Aura* aura = GetAura(SPELL_PVP_RULES_ENABLED))
if (!aura->IsPermanent())
aura->SetDuration(aura->GetSpellInfo()->GetMaxDuration());
Unit::AIUpdateTick(p_time);
// Update items that have just a limited lifetime
if (now > m_Last_tick)
UpdateItemDuration(uint32(now - m_Last_tick));
// check every second
if (now > m_Last_tick + 1)
UpdateSoulboundTradeItems();
// If mute expired, remove it from the DB
if (GetSession()->m_muteTime && GetSession()->m_muteTime < now)
{
GetSession()->m_muteTime = 0;
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_MUTE_TIME);
stmt->setInt64(0, 0); // Set the mute time to 0
stmt->setString(1, "");
stmt->setString(2, "");
stmt->setUInt32(3, GetSession()->GetAccountId());
LoginDatabase.Execute(stmt);
}
if (!m_timedquests.empty())
{
QuestSet::iterator iter = m_timedquests.begin();
while (iter != m_timedquests.end())
{
QuestStatusData& q_status = m_QuestStatus[*iter];
if (q_status.Timer <= p_time)
{
uint32 quest_id = *iter;
++iter; // current iter will be removed in FailQuest
FailQuest(quest_id);
}
else
{
q_status.Timer -= p_time;
m_QuestStatusSave[*iter] = QUEST_DEFAULT_SAVE_TYPE;
++iter;
}
}
}
m_achievementMgr->UpdateTimedCriteria(p_time);
if (HasUnitState(UNIT_STATE_MELEE_ATTACKING) && !HasUnitState(UNIT_STATE_CASTING))
{
if (Unit* victim = GetVictim())
{
// default combat reach 10
/// @todo add weapon, skill check
if (isAttackReady(BASE_ATTACK))
{
if (!IsWithinMeleeRange(victim))
{
setAttackTimer(BASE_ATTACK, 100);
if (m_swingErrorMsg != 1) // send single time (client auto repeat)
{
SendAttackSwingNotInRange();
m_swingErrorMsg = 1;
}
}
//120 degrees of radiant range, if player is not in boundary radius
else if (!IsWithinBoundaryRadius(victim) && !HasInArc(2 * float(M_PI) / 3, victim))
{
setAttackTimer(BASE_ATTACK, 100);
if (m_swingErrorMsg != 2) // send single time (client auto repeat)
{
SendAttackSwingBadFacingAttack();
m_swingErrorMsg = 2;
}
}
else
{
m_swingErrorMsg = 0; // reset swing error state
// prevent base and off attack in same time, delay attack at 0.2 sec
if (haveOffhandWeapon())
if (getAttackTimer(OFF_ATTACK) < ATTACK_DISPLAY_DELAY)
setAttackTimer(OFF_ATTACK, ATTACK_DISPLAY_DELAY);
// do attack
AttackerStateUpdate(victim, BASE_ATTACK);
resetAttackTimer(BASE_ATTACK);
}
}
if (!IsInFeralForm() && haveOffhandWeapon() && isAttackReady(OFF_ATTACK))
{
if (!IsWithinMeleeRange(victim))
setAttackTimer(OFF_ATTACK, 100);
else if (!IsWithinBoundaryRadius(victim) && !HasInArc(2 * float(M_PI) / 3, victim))
{
setAttackTimer(BASE_ATTACK, 100);
}
else
{
// prevent base and off attack in same time, delay attack at 0.2 sec
if (getAttackTimer(BASE_ATTACK) < ATTACK_DISPLAY_DELAY)
setAttackTimer(BASE_ATTACK, ATTACK_DISPLAY_DELAY);
// do attack
AttackerStateUpdate(victim, OFF_ATTACK);
resetAttackTimer(OFF_ATTACK);
}
}
/*Unit* owner = victim->GetOwner();
Unit* u = owner ? owner : victim;
if (u->IsPvP() && (!duel || duel->opponent != u))
{
UpdatePvP(true);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::PvPActive);
}*/
}
}
if (HasPlayerFlag(PLAYER_FLAGS_RESTING))
_restMgr->Update(now);
if (m_weaponChangeTimer > 0)
{
if (p_time >= m_weaponChangeTimer)
m_weaponChangeTimer = 0;
else
m_weaponChangeTimer -= p_time;
}
if (m_zoneUpdateTimer > 0)
{
if (p_time >= m_zoneUpdateTimer)
{
// On zone update tick check if we are still in an inn if we are supposed to be in one
if (_restMgr->HasRestFlag(REST_FLAG_IN_TAVERN))
{
AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(_restMgr->GetInnTriggerID());
if (!atEntry || !IsInAreaTriggerRadius(atEntry))
_restMgr->RemoveRestFlag(REST_FLAG_IN_TAVERN);
}
uint32 newzone, newarea;
GetZoneAndAreaId(newzone, newarea);
if (m_zoneUpdateId != newzone)
UpdateZone(newzone, newarea); // also update area
else
{
// use area updates as well
// needed for free far all arenas for example
if (m_areaUpdateId != newarea)
UpdateArea(newarea);
m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL;
}
}
else
m_zoneUpdateTimer -= p_time;
}
if (IsAlive())
{
m_regenTimer += p_time;
RegenerateAll();
}
if (m_deathState == JUST_DIED)
KillPlayer();
if (m_nextSave > 0)
{
if (p_time >= m_nextSave)
{
// m_nextSave reset in SaveToDB call
SaveToDB();
TC_LOG_DEBUG("entities.player", "Player::Update: Player '%s' (%s) saved", GetName().c_str(), GetGUID().ToString().c_str());
}
else
m_nextSave -= p_time;
}
//Handle Water/drowning
HandleDrowning(p_time);
// Played time
if (now > m_Last_tick)
{
uint32 elapsed = uint32(now - m_Last_tick);
m_Played_time[PLAYED_TIME_TOTAL] += elapsed; // Total played time
m_Played_time[PLAYED_TIME_LEVEL] += elapsed; // Level played time
m_Last_tick = now;
}
if (GetDrunkValue())
{
m_drunkTimer += p_time;
if (m_drunkTimer > 9 * IN_MILLISECONDS)
HandleSobering();
}
if (HasPendingBind())
{
if (_pendingBindTimer <= p_time)
{
// Player left the instance
if (_pendingBindId == GetInstanceId())
BindToInstance();
SetPendingBind(0, 0);
}
else
_pendingBindTimer -= p_time;
}
// not auto-free ghost from body in instances
if (m_deathTimer > 0 && !GetMap()->Instanceable() && !HasAuraType(SPELL_AURA_PREVENT_RESURRECTION))
{
if (p_time >= m_deathTimer)
{
m_deathTimer = 0;
BuildPlayerRepop();
RepopAtGraveyard();
}
else
m_deathTimer -= p_time;
}
UpdateEnchantTime(p_time);
UpdateHomebindTime(p_time);
if (!_instanceResetTimes.empty())
{
for (InstanceTimeMap::iterator itr = _instanceResetTimes.begin(); itr != _instanceResetTimes.end();)
{
if (itr->second < now)
_instanceResetTimes.erase(itr++);
else
++itr;
}
}
// group update
m_groupUpdateTimer.Update(p_time);
if (m_groupUpdateTimer.Passed())
{
SendUpdateToOutOfRangeGroupMembers();
m_groupUpdateTimer.Reset(5000);
}
Pet* pet = GetPet();
if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityRange()) && !pet->isPossessed())
//if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityDistance()) && (GetCharmGUID() && (pet->GetGUID() != GetCharmGUID())))
RemovePet(pet, PET_SAVE_NOT_IN_SLOT, true);
if (IsAlive())
{
if (m_hostileReferenceCheckTimer <= p_time)
{
m_hostileReferenceCheckTimer = 15 * IN_MILLISECONDS;
if (!GetMap()->IsDungeon())
GetCombatManager().EndCombatBeyondRange(GetVisibilityRange(), true);
}
else
m_hostileReferenceCheckTimer -= p_time;
}
//we should execute delayed teleports only for alive(!) players
//because we don't want player's ghost teleported from graveyard
if (IsHasDelayedTeleport() && IsAlive())
TeleportTo(m_teleport_dest, m_teleport_options);
}
void Player::setDeathState(DeathState s)
{
bool oldIsAlive = IsAlive();
if (s == JUST_DIED)
{
if (!oldIsAlive)
{
TC_LOG_ERROR("entities.player", "Player::setDeathState: Attempted to kill a dead player '%s' (%s)", GetName().c_str(), GetGUID().ToString().c_str());
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 HandleRepopRequest and define pet unsummon here with (s == DEAD)
RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true);
InitializeSelfResurrectionSpells();
UpdateCriteria(CriteriaType::DieOnMap, 1);
UpdateCriteria(CriteriaType::DieAnywhere, 1);
UpdateCriteria(CriteriaType::DieInInstance, 1);
// reset all death criterias
ResetCriteria(CriteriaFailEvent::Death, 0);
}
Unit::setDeathState(s);
if (IsAlive() && !oldIsAlive)
//clear aura case after resurrection by another way (spells will be applied before next death)
ClearSelfResSpell();
}
void Player::ToggleAFK()
{
if (isAFK())
RemovePlayerFlag(PLAYER_FLAGS_AFK);
else
AddPlayerFlag(PLAYER_FLAGS_AFK);
// afk player not allowed in battleground
if (!IsGameMaster() && isAFK() && InBattleground() && !InArena())
LeaveBattleground();
}
void Player::ToggleDND()
{
if (isDND())
RemovePlayerFlag(PLAYER_FLAGS_DND);
else
AddPlayerFlag(PLAYER_FLAGS_DND);
}
uint8 Player::GetChatFlags() const
{
uint8 tag = CHAT_FLAG_NONE;
if (isGMChat())
tag |= CHAT_FLAG_GM;
if (isDND())
tag |= CHAT_FLAG_DND;
if (isAFK())
tag |= CHAT_FLAG_AFK;
if (IsDeveloper())
tag |= CHAT_FLAG_DEV;
return tag;
}
bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientation, uint32 options)
{
if (!MapManager::IsValidMapCoord(mapid, x, y, z, orientation))
{
TC_LOG_ERROR("maps", "Player::TeleportTo: Invalid map (%d) or invalid coordinates (X: %f, Y: %f, Z: %f, O: %f) given when teleporting player '%s' (%s, MapID: %d, X: %f, Y: %f, Z: %f, O: %f).",
mapid, x, y, z, orientation, GetGUID().ToString().c_str(), GetName().c_str(), GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
return false;
}
if (!GetSession()->HasPermission(rbac::RBAC_PERM_SKIP_CHECK_DISABLE_MAP) && DisableMgr::IsDisabledFor(DISABLE_TYPE_MAP, mapid, this))
{
TC_LOG_ERROR("entities.player.cheat", "Player::TeleportTo: Player '%s' (%s) tried to enter a forbidden map (MapID: %u)", GetGUID().ToString().c_str(), GetName().c_str(), 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)...
// don't let gm level > 1 either
if (!InBattleground() && mEntry->IsBattlegroundOrArena())
return false;
// client without expansion support
if (GetSession()->GetExpansion() < mEntry->Expansion())
{
TC_LOG_DEBUG("maps", "Player '%s' (%s) using client without required expansion tried teleporting to non accessible map (MapID: %u)",
GetName().c_str(), GetGUID().ToString().c_str(), mapid);
if (Transport* transport = GetTransport())
{
transport->RemovePassenger(this);
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
TC_LOG_DEBUG("maps", "Player %s (%s) is being teleported to map (MapID: %u)", GetName().c_str(), GetGUID().ToString().c_str(), mapid);
if (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);
m_movementInfo.ResetJump();
DisableSpline();
GetMotionMaster()->Remove(EFFECT_MOTION_TYPE);
if (Transport* transport = GetTransport())
{
if (!(options & TELE_TO_NOT_LEAVE_TRANSPORT))
transport->RemovePassenger(this);
}
// 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(m_playerData->DuelArbiter))
DuelComplete(DUEL_FLED);
if (GetMapId() == mapid)
{
//lets reset far teleport flag if it wasn't reset during chained teleport
SetSemaphoreTeleportFar(false);
//setup delayed teleport flag
SetDelayedTeleportFlag(IsCanDelayTeleport());
//if teleport spell is cast in Unit::Update() func
//then we need to delay it until update process will be finished
if (IsHasDelayedTeleport())
{
SetSemaphoreTeleportNear(true);
//lets save teleport destination for player
m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
m_teleport_options = options;
return true;
}
if (!(options & TELE_TO_NOT_UNSUMMON_PET))
{
//same map, only remove pet if out of range for new position
if (pet && !pet->IsWithinDist3d(x, y, z, GetMap()->GetVisibilityRange()))
UnsummonPetTemporaryIfAny();
}
if (!IsAlive() && options & TELE_REVIVE_AT_TELEPORT)
ResurrectPlayer(0.5f);
if (!(options & TELE_TO_NOT_LEAVE_COMBAT))
CombatStop();
// this will be used instead of the current location in SaveToDB
m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
m_teleport_options = options;
SetFallInformation(0, GetPositionZ());
// code for finish transfer called in WorldSession::HandleMovementOpcodes()
// at client packet CMSG_MOVE_TELEPORT_ACK
SetSemaphoreTeleportNear(true);
// near teleport, triggering send CMSG_MOVE_TELEPORT_ACK from client at landing
if (!GetSession()->PlayerLogout())
SendTeleportPacket(m_teleport_dest);
}
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 (sMapMgr->PlayerCannotEnter(mapid, this, false))
return false;
// Seamless teleport can happen only if cosmetic maps match
if (!oldmap ||
(oldmap->GetEntry()->CosmeticParentMapID != int32(mapid) && int32(GetMapId()) != mEntry->CosmeticParentMapID &&
!((oldmap->GetEntry()->CosmeticParentMapID != -1) ^ (oldmap->GetEntry()->CosmeticParentMapID != mEntry->CosmeticParentMapID))))
options &= ~TELE_TO_SEAMLESS;
//I think this always returns true. Correct me if I am wrong.
// If the map is not created, assume it is possible to enter it.
// It will be created in the WorldPortAck.
//Map* map = sMapMgr->FindBaseNonInstanceMap(mapid);
//if (!map || map->CanEnter(this))
{
//lets reset near teleport flag if it wasn't reset during chained teleports
SetSemaphoreTeleportNear(false);
//setup delayed teleport flag
SetDelayedTeleportFlag(IsCanDelayTeleport());
//if teleport spell is cast in Unit::Update() func
//then we need to delay it until update process will be finished
if (IsHasDelayedTeleport())
{
SetSemaphoreTeleportFar(true);
//lets save teleport destination for player
m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
m_teleport_options = options;
return true;
}
SetSelection(ObjectGuid::Empty);
CombatStop();
ResetContestedPvP();
// remove player from battleground on far teleport (when changing maps)
if (Battleground const* bg = GetBattleground())
{
// Note: at battleground join battleground id set before teleport
// and we already will found "current" battleground
// just need check that this is targeted map or leave
if (bg->GetMapId() != mapid)
LeaveBattleground(false); // don't teleport to entry point
}
// remove arena spell coldowns/buffs now to also remove pet's cooldowns before it's temporarily unsummoned
if (mEntry->IsBattleArena() && !IsGameMaster())
{
RemoveArenaSpellCooldowns(true);
RemoveArenaAuras();
if (pet)
pet->RemoveArenaAuras();
}
// remove pet on map change
if (pet)
UnsummonPetTemporaryIfAny();
// remove all dyn objects
RemoveAllDynObjects();
// remove all areatriggers entities
RemoveAllAreaTriggers();
// 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(SpellAuraInterruptFlags::Moving | SpellAuraInterruptFlags::Turning);
if (!GetSession()->PlayerLogout() && !(options & TELE_TO_SEAMLESS))
{
// send transfer packets
WorldPackets::Movement::TransferPending transferPending;
transferPending.MapID = mapid;
transferPending.OldMapPosition = GetPosition();
if (Transport* transport = GetTransport())
{
transferPending.Ship.emplace();
transferPending.Ship->ID = transport->GetEntry();
transferPending.Ship->OriginMapID = GetMapId();
}
SendDirectMessage(transferPending.Write());
}
// remove from old map now
if (oldmap)
oldmap->RemovePlayerFromMap(this, false);
m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
m_teleport_options = options;
SetFallInformation(0, GetPositionZ());
// 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())
{
WorldPackets::Movement::SuspendToken suspendToken;
suspendToken.SequenceIndex = m_movementCounter; // not incrementing
suspendToken.Reason = options & TELE_TO_SEAMLESS ? 2 : 1;
SendDirectMessage(suspendToken.Write());
}
// move packet sent by client always after far teleport
// code for finish transfer to new map called in WorldSession::HandleMoveWorldportAckOpcode at client packet
SetSemaphoreTeleportFar(true);
}
//else
// return false;
}
return true;
}
bool Player::TeleportTo(WorldLocation const& loc, uint32 options /*= 0*/)
{
return TeleportTo(loc.GetMapId(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ(), loc.GetOrientation(), options);
}
bool Player::TeleportToBGEntryPoint()
{
if (m_bgData.joinPos.m_mapId == MAPID_INVALID)
return false;
ScheduleDelayedOperation(DELAYED_BG_MOUNT_RESTORE);
ScheduleDelayedOperation(DELAYED_BG_TAXI_RESTORE);
ScheduleDelayedOperation(DELAYED_BG_GROUP_RESTORE);
return TeleportTo(m_bgData.joinPos);
}
void Player::ProcessDelayedOperations()
{
if (m_DelayedOperations == 0)
return;
if (m_DelayedOperations & DELAYED_RESURRECT_PLAYER)
ResurrectUsingRequestDataImpl();
if (m_DelayedOperations & DELAYED_SAVE_PLAYER)
SaveToDB();
if (m_DelayedOperations & DELAYED_SPELL_CAST_DESERTER)
CastSpell(this, 26013, true); // Deserter
if (m_DelayedOperations & DELAYED_BG_MOUNT_RESTORE)
{
if (m_bgData.mountSpell)
{
CastSpell(this, m_bgData.mountSpell, true);
m_bgData.mountSpell = 0;
}
}
if (m_DelayedOperations & DELAYED_BG_TAXI_RESTORE)
{
if (m_bgData.HasTaxiPath())
{
m_taxi.AddTaxiDestination(m_bgData.taxiPath[0]);
m_taxi.AddTaxiDestination(m_bgData.taxiPath[1]);
m_bgData.ClearTaxiPath();
ContinueTaxiFlight();
}
}
if (m_DelayedOperations & DELAYED_BG_GROUP_RESTORE)
{
if (Group* g = GetGroup())
g->SendUpdateToPlayer(GetGUID());
}
//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();
ObjectGuid lootGuid = GetLootGUID();
if (!lootGuid.IsEmpty())
m_session->DoLootRelease(lootGuid);
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();
///- 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();
for (ItemMap::iterator iter = mMitems.begin(); iter != mMitems.end(); ++iter)
iter->second->RemoveFromWorld();
if (WorldObject* viewpoint = GetViewpoint())
{
TC_LOG_ERROR("entities.player", "Player::RemoveFromWorld: Player '%s' (%s) has viewpoint (Entry:%u, Type: %u) when removed from world",
GetName().c_str(), GetGUID().ToString().c_str(), viewpoint->GetEntry(), viewpoint->GetTypeId());
SetViewpoint(viewpoint, false);
}
}
void Player::SetObjectScale(float scale)
{
Unit::SetObjectScale(scale);
SetBoundingRadius(scale * DEFAULT_PLAYER_BOUNDING_RADIUS);
SetCombatReach(scale * DEFAULT_PLAYER_COMBAT_REACH);
if (IsInWorld())
SendMovementSetCollisionHeight(scale * GetCollisionHeight(), WorldPackets::Movement::UpdateCollisionHeightReason::Scale);
}
bool Player::IsImmunedToSpellEffect(SpellInfo const* spellInfo, SpellEffectInfo const& spellEffectInfo, WorldObject const* caster) const
{
// players are immune to taunt (the aura and the spell effect).
if (spellEffectInfo.IsAura(SPELL_AURA_MOD_TAUNT))
return true;
if (spellEffectInfo.IsEffect(SPELL_EFFECT_ATTACK_ME))
return true;
return Unit::IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster);
}
void Player::RegenerateAll()
{
m_regenTimerCount += m_regenTimer;
m_foodEmoteTimerCount += m_regenTimer;
for (Powers power = POWER_MANA; power < MAX_POWERS; power = Powers(power + 1))
if (power != POWER_RUNES)
Regenerate(power);
// Runes act as cooldowns, and they don't need to send any data
if (GetClass() == CLASS_DEATH_KNIGHT)
{
uint32 regeneratedRunes = 0;
uint32 regenIndex = 0;
while (regeneratedRunes < MAX_RECHARGING_RUNES && m_runes->CooldownOrder.size() > regenIndex)
{
uint8 runeToRegen = m_runes->CooldownOrder[regenIndex];
uint32 runeCooldown = GetRuneCooldown(runeToRegen);
if (runeCooldown > m_regenTimer)
{
SetRuneCooldown(runeToRegen, runeCooldown - m_regenTimer);
++regenIndex;
}
else
SetRuneCooldown(runeToRegen, 0);
++regeneratedRunes;
}
}
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();
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<AuraEffect*> 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()->HasAuraInterruptFlag(SpellAuraInterruptFlags::Standing))
{
SendPlaySpellVisualKit(SPELL_VISUAL_KIT_FOOD, 0, 0);
break;
}
else if ((*itr)->GetBase()->HasEffectType(SPELL_AURA_MOD_POWER_REGEN) && (*itr)->GetSpellInfo()->HasAuraInterruptFlag(SpellAuraInterruptFlags::Standing))
{
SendPlaySpellVisualKit(SPELL_VISUAL_KIT_DRINK, 0, 0);
break;
}
}
m_foodEmoteTimerCount -= 5000;
}
}
void Player::Regenerate(Powers power)
{
// Skip regeneration for power type we cannot have
uint32 powerIndex = GetPowerIndex(power);
if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS)
return;
/// @todo possible use of miscvalueb instead of amount
if (HasAuraTypeWithValue(SPELL_AURA_PREVENT_REGENERATE_POWER, power))
return;
int32 curValue = GetPower(power);
// TODO: updating haste should update UnitData::PowerRegenFlatModifier for certain power types
PowerTypeEntry const* powerType = sDB2Manager.GetPowerTypeEntry(power);
if (!powerType)
return;
float addvalue = 0.0f;
if (!IsInCombat())
{
if (powerType->RegenInterruptTimeMS && GetMSTimeDiffToNow(m_combatExitTime) < uint32(powerType->RegenInterruptTimeMS))
return;
addvalue = (powerType->RegenPeace + m_unitData->PowerRegenFlatModifier[powerIndex]) * 0.001f * m_regenTimer;
}
else
addvalue = (powerType->RegenCombat + m_unitData->PowerRegenInterruptedFlatModifier[powerIndex]) * 0.001f * m_regenTimer;
static Rates const RatesForPower[MAX_POWERS] =
{
RATE_POWER_MANA,
RATE_POWER_RAGE_LOSS,
RATE_POWER_FOCUS,
RATE_POWER_ENERGY,
RATE_POWER_COMBO_POINTS_LOSS,
MAX_RATES, // runes
RATE_POWER_RUNIC_POWER_LOSS,
RATE_POWER_SOUL_SHARDS,
RATE_POWER_LUNAR_POWER,
RATE_POWER_HOLY_POWER,
MAX_RATES, // alternate
RATE_POWER_MAELSTROM,
RATE_POWER_CHI,
RATE_POWER_INSANITY,
MAX_RATES, // burning embers, unused
MAX_RATES, // demonic fury, unused
RATE_POWER_ARCANE_CHARGES,
RATE_POWER_FURY,
RATE_POWER_PAIN,
};
if (RatesForPower[power] != MAX_RATES)
addvalue *= sWorld->getRate(RatesForPower[power]);
// Mana regen calculated in Player::UpdateManaRegen()
if (power != POWER_MANA)
{
addvalue *= GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, power);
addvalue += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, power) * ((power != POWER_ENERGY) ? m_regenTimerCount : m_regenTimer) / (5 * IN_MILLISECONDS);
}
int32 minPower = powerType->MinPower;
int32 maxPower = GetMaxPower(power);
if (powerType->CenterPower)
{
if (curValue > powerType->CenterPower)
{
addvalue = -std::abs(addvalue);
minPower = powerType->CenterPower;
}
else if (curValue < powerType->CenterPower)
{
addvalue = std::abs(addvalue);
maxPower = powerType->CenterPower;
}
else
return;
}
addvalue += m_powerFraction[powerIndex];
int32 integerValue = int32(std::fabs(addvalue));
if (addvalue < 0.0f)
{
if (curValue <= minPower)
return;
}
else if (addvalue > 0.0f)
{
if (curValue >= maxPower)
return;
}
else
return;
bool forcesSetPower = false;
if (addvalue < 0.0f)
{
if (curValue > minPower + integerValue)
{
curValue -= integerValue;
m_powerFraction[powerIndex] = addvalue + integerValue;
}
else
{
curValue = minPower;
m_powerFraction[powerIndex] = 0;
forcesSetPower = true;
}
}
else
{
if (curValue + integerValue <= maxPower)
{
curValue += integerValue;
m_powerFraction[powerIndex] = addvalue - integerValue;
}
else
{
curValue = maxPower;
m_powerFraction[powerIndex] = 0;
forcesSetPower = true;
}
}
if (GetCommandStatus(CHEAT_POWER))
curValue = maxPower;
if (m_regenTimerCount >= 2000 || forcesSetPower)
SetPower(power, curValue);
else
{
// throttle packet sending
DoWithSuppressingObjectUpdates([&]()
{
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Power, powerIndex), curValue);
const_cast<UF::UnitData&>(*m_unitData).ClearChanged(&UF::UnitData::Power, powerIndex);
});
}
}
void Player::RegenerateHealth()
{
uint32 curValue = GetHealth();
uint32 maxValue = GetMaxHealth();
if (curValue >= maxValue)
return;
float HealthIncreaseRate = sWorld->getRate(RATE_HEALTH);
float addValue = 0.0f;
// polymorphed case
if (IsPolymorphed())
addValue = float(GetMaxHealth()) / 3.0f;
// normal regen case (maybe partly in combat case)
else if (!IsInCombat() || HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT))
{
addValue = HealthIncreaseRate;
if (!IsInCombat())
{
if (GetLevel() < 15)
addValue = (0.20f * ((float)GetMaxHealth()) / GetLevel() * HealthIncreaseRate);
else
addValue = 0.015f * ((float)GetMaxHealth()) * HealthIncreaseRate;
addValue *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT);
addValue += GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * 0.4f;
}
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.0f)
addValue = 0.0f;
ModifyHealth(int32(addValue));
}
void Player::ResetAllPowers()
{
SetFullHealth();
switch (GetPowerType())
{
case POWER_MANA:
SetFullPower(POWER_MANA);
break;
case POWER_RAGE:
SetPower(POWER_RAGE, 0);
break;
case POWER_ENERGY:
SetFullPower(POWER_ENERGY);
break;
case POWER_RUNIC_POWER:
SetPower(POWER_RUNIC_POWER, 0);
break;
case POWER_LUNAR_POWER:
SetPower(POWER_LUNAR_POWER, 0);
break;
default:
break;
}
}
bool Player::CanInteractWithQuestGiver(Object* questGiver) const
{
switch (questGiver->GetTypeId())
{
case TYPEID_UNIT:
return GetNPCIfCanInteractWith(questGiver->GetGUID(), UNIT_NPC_FLAG_QUESTGIVER, UNIT_NPC_FLAG_2_NONE) != 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 const& guid, NPCFlags npcFlags, NPCFlags2 npcFlags2) const
{
// 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_GHOST_VISIBLE))
return nullptr;
// alive or spirit healer
if (!creature->IsAlive() && !(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_CAN_INTERACT_WHILE_DEAD))
return nullptr;
// appropriate npc type
auto hasNpcFlags = [&]()
{
if (!npcFlags && !npcFlags2)
return true;
if (creature->HasNpcFlag(npcFlags))
return true;
if (creature->HasNpcFlag2(npcFlags2))
return true;
return false;
};
if (!hasNpcFlags())
return nullptr;
// not allow interaction under control, but allow with own pets
if (!creature->GetCharmerGUID().IsEmpty())
return nullptr;
// not unfriendly/hostile
if (creature->GetReactionTo(this) <= REP_UNFRIENDLY)
return nullptr;
// not too far, taken from CGGameUI::SetInteractTarget
if (!creature->IsWithinDistInMap(this, creature->GetCombatReach() + 4.0f))
return nullptr;
return creature;
}
GameObject* Player::GetGameObjectIfCanInteractWith(ObjectGuid const& guid) const
{
if (!guid)
return nullptr;
if (!IsInWorld())
return nullptr;
if (IsInFlight())
return nullptr;
// exist
GameObject* go = ObjectAccessor::GetGameObject(*this, guid);
if (!go)
return nullptr;
// Players cannot interact with gameobjects that use the "Point" icon
if (go->GetGOInfo()->IconName == "Point")
return nullptr;
if (!go->IsWithinDistInMap(this))
return nullptr;
return go;
}
GameObject* Player::GetGameObjectIfCanInteractWith(ObjectGuid const& guid, GameobjectTypes type) const
{
GameObject* go = GetGameObjectIfCanInteractWith(guid);
if (!go)
return nullptr;
if (go->GetGoType() != type)
return nullptr;
return go;
}
bool Player::IsInAreaTriggerRadius(AreaTriggerEntry const* trigger) const
{
if (!trigger)
return false;
if (int32(GetMapId()) != trigger->ContinentID && !GetPhaseShift().HasVisibleMapId(trigger->ContinentID))
return false;
if (trigger->PhaseID || trigger->PhaseGroupID || trigger->PhaseUseFlags)
if (!PhasingHandler::InDbPhaseShift(this, trigger->PhaseUseFlags, trigger->PhaseID, trigger->PhaseGroupID))
return false;
if (trigger->Radius > 0.f)
{
// if we have radius check it
float dist = GetDistance(trigger->Pos.X, trigger->Pos.Y, trigger->Pos.Z);
if (dist > trigger->Radius)
return false;
}
else
{
Position center(trigger->Pos.X, trigger->Pos.Y, trigger->Pos.Z, trigger->BoxYaw);
if (!IsWithinBox(center, trigger->BoxLength / 2.f, trigger->BoxWidth / 2.f, trigger->BoxHeight / 2.f))
return false;
}
return true;
}
void Player::SetGameMaster(bool on)
{
if (on)
{
m_ExtraFlags |= PLAYER_EXTRA_GM_ON;
SetFaction(FACTION_FRIENDLY);
AddPlayerFlag(PLAYER_FLAGS_GM);
AddUnitFlag2(UNIT_FLAG2_ALLOW_CHEAT_SPELLS);
if (Pet* pet = GetPet())
pet->SetFaction(FACTION_FRIENDLY);
RemovePvpFlag(UNIT_BYTE2_FLAG_FFA_PVP);
ResetContestedPvP();
CombatStopWithPets();
PhasingHandler::SetAlwaysVisible(this, true, false);
m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GM, GetSession()->GetSecurity());
}
else
{
PhasingHandler::SetAlwaysVisible(this, HasAuraType(SPELL_AURA_PHASE_ALWAYS_VISIBLE), false);
m_ExtraFlags &= ~ PLAYER_EXTRA_GM_ON;
SetFactionForRace(GetRace());
RemovePlayerFlag(PLAYER_FLAGS_GM);
RemoveUnitFlag2(UNIT_FLAG2_ALLOW_CHEAT_SPELLS);
if (Pet* pet = GetPet())
pet->SetFaction(GetFaction());
// restore FFA PvP Server state
if (sWorld->IsFFAPvPRealm())
AddPvpFlag(UNIT_BYTE2_FLAG_FFA_PVP);
// restore FFA PvP area state, remove not allowed for GM mounts
UpdateArea(m_areaUpdateId);
m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
}
UpdateObjectVisibility();
}
bool Player::CanBeGameMaster() const
{
return GetSession()->HasPermission(rbac::RBAC_PERM_COMMAND_GM);
}
void Player::SetGMVisible(bool on)
{
if (on)
{
m_ExtraFlags &= ~PLAYER_EXTRA_GM_INVISIBLE; //remove flag
m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
}
else
{
m_ExtraFlags |= PLAYER_EXTRA_GM_INVISIBLE; //add flag
SetAcceptWhispers(false);
SetGameMaster(true);
m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, GetSession()->GetSecurity());
}
for (Channel* channel : m_channels)
channel->SetInvisible(this, !on);
}
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 GetTeam() == p->GetTeam();
case 3: return false;
}
}
bool Player::IsInSameGroupWith(Player const* p) const
{
return p == this || (GetGroup() != nullptr &&
GetGroup() == p->GetGroup() &&
GetGroup()->SameSubGroup(this, p));
}
bool Player::IsInSameRaidWith(Player const* p) const
{
return p == this || (GetGroup() != nullptr && GetGroup() == p->GetGroup());
}
///- If the player is invited, remove him. If the group if then only 1 person, disband the group.
void Player::UninviteFromGroup()
{
Group* group = GetGroupInvite();
if (!group)
return;
group->RemoveInvite(this);
if (group->IsCreated())
{
if (group->GetMembersCount() <= 1) // group has just 1 member => disband
group->Disband(true);
}
else
{
if (group->GetInviteeCount() <= 1)
{
group->RemoveAllInvites();
delete group;
}
}
}
void Player::RemoveFromGroup(Group* group, ObjectGuid guid, RemoveMethod method /* = GROUP_REMOVEMETHOD_DEFAULT*/, ObjectGuid kicker /* = ObjectGuid::Empty */, char const* reason /* = nullptr */)
{
if (!group)
return;
group->RemoveMember(guid, method, kicker, reason);
}
void Player::SetXP(uint32 xp)
{
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::XP), xp);
int32 playerLevelDelta = 0;
// If XP < 50%, player should see scaling creature with -1 level except for level max
if (GetLevel() < MAX_LEVEL && xp < uint32(*m_activePlayerData->NextLevelXP / 2))
playerLevelDelta = -1;
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ScalingPlayerLevelDelta), playerLevelDelta);
}
void Player::GiveXP(uint32 xp, Unit* victim, float group_rate)
{
if (xp < 1)
return;
if (!IsAlive() && !GetBattlegroundId())
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);
// XP to money conversion processed in Player::RewardQuest
if (IsMaxLevel())
return;
uint32 bonus_xp;
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 ? _restMgr->GetRestBonusFor(REST_TYPE_XP, xp) : 0; // XP resting bonus
WorldPackets::Character::LogXPGain packet;
packet.Victim = victim ? victim->GetGUID() : ObjectGuid::Empty;
packet.Original = xp + bonus_xp;
packet.Reason = victim ? LOG_XP_REASON_KILL : LOG_XP_REASON_NO_KILL;
packet.Amount = xp;
packet.GroupBonus = group_rate;
packet.ReferAFriendBonusType = recruitAFriend ? 1 : 0;
SendDirectMessage(packet.Write());
uint32 nextLvlXP = GetXPForNextLevel();
uint32 newXP = GetXP() + xp + bonus_xp;
while (newXP >= nextLvlXP && !IsMaxLevel())
{
newXP -= nextLvlXP;
if (!IsMaxLevel())
GiveLevel(level + 1);
level = GetLevel();
nextLvlXP = GetXPForNextLevel();
}
SetXP(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(), GetClass(), level, &info);
uint32 basemana = 0;
sObjectMgr->GetPlayerClassLevelInfo(GetClass(), level, basemana);
WorldPackets::Misc::LevelUpInfo packet;
packet.Level = level;
packet.HealthDelta = 0;
/// @todo find some better solution
// for (int i = 0; i < MAX_STORED_POWERS; ++i)
packet.PowerDelta[0] = int32(basemana) - int32(GetCreateMana());
packet.PowerDelta[1] = 0;
packet.PowerDelta[2] = 0;
packet.PowerDelta[3] = 0;
packet.PowerDelta[4] = 0;
packet.PowerDelta[5] = 0;
packet.PowerDelta[6] = 0;
for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
packet.StatDelta[i] = int32(info.stats[i]) - GetCreateStat(Stats(i));
packet.NumNewTalents = DB2Manager::GetNumTalentsAtLevel(level, Classes(GetClass())) - DB2Manager::GetNumTalentsAtLevel(oldLevel, Classes(GetClass()));
packet.NumNewPvpTalentSlots = sDB2Manager.GetPvpTalentNumSlotsAtLevel(level, Classes(GetClass())) - sDB2Manager.GetPvpTalentNumSlotsAtLevel(oldLevel, Classes(GetClass()));
SendDirectMessage(packet.Write());
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::NextLevelXP), 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();
LearnDefaultSkills();
LearnSpecializationSpells();
// 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(0);
SetCreateMana(basemana);
InitTalentForLevel();
InitTaxiNodesForLevel();
UpdateAllStats();
_ApplyAllLevelScaleItemMods(true); // Moved to above SetFullHealth so player will have full health from Heirlooms
if (Aura const* artifactAura = GetAura(ARTIFACTS_ALL_WEAPONS_GENERAL_WEAPON_EQUIPPED_PASSIVE))
if (Item* artifact = GetItemByGuid(artifactAura->GetCastItemGUID()))
artifact->CheckArtifactRelicSlotUnlock(this);
// Only health and mana are set to maximum.
SetFullHealth();
SetFullPower(POWER_MANA);
// update level to hunter/summon pet
if (Pet* pet = GetPet())
pet->SynchronizeLevelWithOwner();
if (MailLevelReward const* mailReward = sObjectMgr->GetMailLevelReward(level, GetRace()))
{
/// @todo Poor design of mail system
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
MailDraft(mailReward->mailTemplateId).SendMailTo(trans, this, MailSender(MAIL_CREATURE, uint64(mailReward->senderEntry)));
CharacterDatabase.CommitTransaction(trans);
}
UpdateCriteria(CriteriaType::ReachLevel);
UpdateCriteria(CriteriaType::ActivelyReachLevel, level);
PushQuests();
sScriptMgr->OnPlayerLevelChanged(this, oldLevel);
}
bool Player::IsMaxLevel() const
{
return GetLevel() >= m_activePlayerData->MaxLevel;
}
void Player::InitTalentForLevel()
{
uint8 level = GetLevel();
// talents base at level diff (talents = level - 9 but some can be used already)
if (level < MIN_SPECIALIZATION_LEVEL)
ResetTalentSpecialization();
int32 talentTiers = DB2Manager::GetNumTalentsAtLevel(level, Classes(GetClass()));
if (level < 15)
{
// Remove all talent points
ResetTalents(true);
}
else
{
if (!GetSession()->HasPermission(rbac::RBAC_PERM_SKIP_CHECK_MORE_TALENTS_THAN_ALLOWED))
for (int32 t = talentTiers; t < MAX_TALENT_TIERS; ++t)
for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c)
for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(GetClass(), t, c))
RemoveTalent(talent);
}
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::MaxTalentTiers), talentTiers);
if (!GetSession()->HasPermission(rbac::RBAC_PERM_SKIP_CHECK_MORE_TALENTS_THAN_ALLOWED))
for (uint8 spec = 0; spec < MAX_SPECIALIZATIONS; ++spec)
for (size_t slot = sDB2Manager.GetPvpTalentNumSlotsAtLevel(level, Classes(GetClass())); slot < MAX_PVP_TALENT_SLOTS; ++slot)
if (PvpTalentEntry const* pvpTalent = sPvpTalentStore.LookupEntry(GetPvpTalentMap(spec)[slot]))
RemovePvpTalent(pvpTalent, spec);
if (!GetSession()->PlayerLoading())
SendTalentsInfoData(); // update at client
}
void Player::InitStatsForLevel(bool reapplyMods)
{
if (reapplyMods) //reapply stats values only on .reset stats (level) command
_RemoveAllStatBonuses();
uint32 basemana = 0;
sObjectMgr->GetPlayerClassLevelInfo(GetClass(), GetLevel(), basemana);
PlayerLevelInfo info;
sObjectMgr->GetPlayerLevelInfo(GetRace(), GetClass(), GetLevel(), &info);
uint8 exp_max_lvl = GetMaxLevelForExpansion(GetSession()->GetExpansion());
uint8 conf_max_lvl = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
if (exp_max_lvl == DEFAULT_MAX_LEVEL || exp_max_lvl >= conf_max_lvl)
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::MaxLevel), conf_max_lvl);
else
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::MaxLevel), exp_max_lvl);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::NextLevelXP), sObjectMgr->GetXPForLevel(GetLevel()));
if (m_activePlayerData->XP >= m_activePlayerData->NextLevelXP)
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::XP), m_activePlayerData->NextLevelXP - 1);
// reset before any aura state sources (health set/aura apply)
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::AuraState), 0);
UpdateSkillsForLevel();
// set default cast time multiplier
SetModCastingSpeed(1.0f);
SetModSpellHaste(1.0f);
SetModHaste(1.0f);
SetModRangedHaste(1.0f);
SetModHasteRegen(1.0f);
SetModTimeRate(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(0);
//set create powers
SetCreateMana(basemana);
SetArmor(int32(m_createStats[STAT_AGILITY]*2), 0);
InitStatBuffMods();
//reset rating fields values
for (uint16 index = 0; index < MAX_COMBAT_RATING; ++index)
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::CombatRatings, index), 0);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ModHealingDonePos), 0);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ModHealingPercent), 1.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ModPeriodicHealingDonePercent), 1.0f);
for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
{
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ModDamageDoneNeg, i), 0);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ModDamageDonePos, i), 0);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ModDamageDonePercent, i), 1.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ModHealingDonePercent, i), 1.0f);
}
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ModSpellPowerPercent), 1.0f);
//reset attack power, damage and attack speed fields
for (uint8 i = BASE_ATTACK; i < MAX_ATTACK; ++i)
SetBaseAttackTime(WeaponAttackType(i), BASE_ATTACK_TIME);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::MinDamage), 0.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::MaxDamage), 0.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::MinOffHandDamage), 0.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::MaxOffHandDamage), 0.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::MinRangedDamage), 0.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::MaxRangedDamage), 0.0f);
for (uint16 i = 0; i < 3; ++i)
{
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::WeaponDmgMultipliers, i), 1.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::WeaponAtkSpeedMultipliers, i), 1.0f);
}
SetAttackPower(0);
SetAttackPowerMultiplier(0.0f);
SetRangedAttackPower(0);
SetRangedAttackPowerMultiplier(0.0f);
// Base crit values (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::CritPercentage), 0.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::OffhandCritPercentage), 0.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::RangedCritPercentage), 0.0f);
// Init spell schools (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::SpellCritPercentage), 0.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ParryPercentage), 0.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::BlockPercentage), 0.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ShieldBlock), 0);
// Dodge percentage
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::DodgePercentage), 0.0f);
// set armor (resistance 0) to original value (create_agility*2)
SetArmor(int32(m_createStats[STAT_AGILITY] * 2), 0);
SetBonusResistanceMod(SPELL_SCHOOL_NORMAL, 0);
// set other resistance to original value (0)
for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
{
SetResistance(SpellSchools(i), 0);
SetBonusResistanceMod(SpellSchools(i), 0);
}
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ModTargetResistance), 0);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ModTargetPhysicalResistance), 0);
for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ManaCostModifier, i), 0);
// Reset no reagent cost field
SetNoRegentCostMask(flag128());
// 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(GetCreatePowerValue(Powers(i))));
SetMaxHealth(0); // stamina bonus will applied later
// cleanup mounted state (it will set correctly at aura loading if player saved at mount.
SetMountDisplayId(0);
// cleanup unit flags (will be re-applied if need at aura load).
RemoveUnitFlag(UnitFlags(
UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_REMOVE_CLIENT_CONTROL | UNIT_FLAG_NOT_ATTACKABLE_1 |
UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC | 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 ));
AddUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED); // must be set
AddUnitFlag2(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(PlayerFlags(PLAYER_FLAGS_AFK | PLAYER_FLAGS_DND | PLAYER_FLAGS_GM | PLAYER_FLAGS_GHOST));
RemoveVisFlags(UNIT_VIS_FLAGS_ALL); // one form stealth modified bytes
RemovePvpFlag(UnitPVPStateFlags(UNIT_BYTE2_FLAG_FFA_PVP | UNIT_BYTE2_FLAG_SANCTUARY));
// restore if need some important flags
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::LocalRegenFlags), 0);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::AuraVision), 0);
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();
SetFullPower(POWER_MANA);
SetFullPower(POWER_ENERGY);
if (GetPower(POWER_RAGE) > GetMaxPower(POWER_RAGE))
SetFullPower(POWER_RAGE);
SetFullPower(POWER_FOCUS);
SetPower(POWER_RUNIC_POWER, 0);
// update level to hunter/summon pet
if (Pet* pet = GetPet())
pet->SynchronizeLevelWithOwner();
}
void Player::SendKnownSpells()
{
WorldPackets::Spells::SendKnownSpells knownSpells;
knownSpells.InitialLogin = false; /// @todo
knownSpells.KnownSpells.reserve(m_spells.size());
for (PlayerSpellMap::value_type const& spell : m_spells)
{
if (spell.second->state == PLAYERSPELL_REMOVED)
continue;
if (!spell.second->active || spell.second->disabled)
continue;
knownSpells.KnownSpells.push_back(spell.first);
}
SendDirectMessage(knownSpells.Write());
}
void Player::SendUnlearnSpells()
{
WorldPackets::Spells::SendUnlearnSpells sendUnlearnSpells;
SendDirectMessage(sendUnlearnSpells.Write());
}
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) const
{
WorldPackets::Mail::MailCommandResult result;
result.MailID = mailId;
result.Command = mailAction;
result.ErrorCode = mailError;
if (mailError == MAIL_ERR_EQUIP_ERROR)
result.BagResult = equipError;
else if (mailAction == MAIL_ITEM_TAKEN)
{
result.AttachID = item_guid;
result.QtyInInventory = item_count;
}
SendDirectMessage(result.Write());
}
void Player::SendNewMail() const
{
// deliver undelivered mail
WorldPackets::Mail::NotifyReceivedMail notify;
notify.Delay = 0.0f;
SendDirectMessage(notify.Write());
}
void Player::UpdateNextMailTimeAndUnreads()
{
// calculate next delivery time (min. from non-delivered mails
// and recalculate unReadMail
time_t cTime = GameTime::GetGameTime();
m_nextMailDelivereTime = 0;
unReadMails = 0;
for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
{
if ((*itr)->deliver_time > cTime)
{
if (!m_nextMailDelivereTime || m_nextMailDelivereTime > (*itr)->deliver_time)
m_nextMailDelivereTime = (*itr)->deliver_time;
}
else if (((*itr)->checked & MAIL_CHECK_MASK_READ) == 0)
++unReadMails;
}
}
void Player::AddNewMailDeliverTime(time_t deliver_time)
{
if (deliver_time <= GameTime::GetGameTime()) // ready now
{
++unReadMails;
SendNewMail();
}
else // not ready and no have ready mails
{
if (!m_nextMailDelivereTime || m_nextMailDelivereTime > deliver_time)
m_nextMailDelivereTime = deliver_time;
}
}
void DeleteSpellFromAllPlayers(uint32 spellId)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_SPELL_SPELLS);
stmt->setUInt32(0, spellId);
CharacterDatabase.Execute(stmt);
}
bool Player::AddTalent(TalentEntry const* talent, uint8 spec, bool learning)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE);
if (!spellInfo)
{
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: %u) does not exist.", talent->SpellID);
return false;
}
if (!SpellMgr::IsSpellValid(spellInfo, this, false))
{
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: %u) is invalid", talent->SpellID);
return false;
}
if (talent->OverridesSpellID)
AddOverrideSpell(talent->OverridesSpellID, talent->SpellID);
PlayerTalentMap::iterator itr = GetTalentMap(spec)->find(talent->ID);
if (itr != GetTalentMap(spec)->end())
itr->second = PLAYERSPELL_UNCHANGED;
else
(*GetTalentMap(spec))[talent->ID] = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
if (learning)
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::ChangeTalent);
return true;
}
void Player::RemoveTalent(TalentEntry const* talent)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE);
if (!spellInfo)
return;
RemoveSpell(talent->SpellID, true);
// search for spells that the talent teaches and unlearn them
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
if (spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL) && spellEffectInfo.TriggerSpell > 0)
RemoveSpell(spellEffectInfo.TriggerSpell, true);
if (talent->OverridesSpellID)
RemoveOverrideSpell(talent->OverridesSpellID, talent->SpellID);
// if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted
PlayerTalentMap::iterator plrTalent = GetTalentMap(GetActiveTalentGroup())->find(talent->ID);
if (plrTalent != GetTalentMap(GetActiveTalentGroup())->end())
plrTalent->second = PLAYERSPELL_REMOVED;
}
void Player::AddStoredAuraTeleportLocation(uint32 spellId)
{
StoredAuraTeleportLocation& storedLocation = m_storedAuraTeleportLocations[spellId];
storedLocation.Loc.WorldRelocate(this);
storedLocation.State = StoredAuraTeleportLocation::CHANGED;
}
void Player::RemoveStoredAuraTeleportLocation(uint32 spellId)
{
if (StoredAuraTeleportLocation* storedLocation = Trinity::Containers::MapGetValuePtr(m_storedAuraTeleportLocations, spellId))
storedLocation->State = StoredAuraTeleportLocation::DELETED;
}
WorldLocation const* Player::GetStoredAuraTeleportLocation(uint32 spellId) const
{
if (StoredAuraTeleportLocation const* auraLocation = Trinity::Containers::MapGetValuePtr(m_storedAuraTeleportLocations, spellId))
return &auraLocation->Loc;
return nullptr;
}
bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading /*= false*/, int32 fromSkill /*= 0*/)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE);
if (!spellInfo)
{
// do character spell book cleanup (all characters)
if (!IsInWorld() && !learning) // spell load case
{
TC_LOG_ERROR("spells", "Player::AddSpell: Spell (ID: %u) does not exist. deleting for all characters in `character_spell`.", spellId);
DeleteSpellFromAllPlayers(spellId);
}
else
TC_LOG_ERROR("spells", "Player::AddSpell: Spell (ID: %u) does not exist", spellId);
return false;
}
if (!SpellMgr::IsSpellValid(spellInfo, this, false))
{
// do character spell book cleanup (all characters)
if (!IsInWorld() && !learning) // spell load case
{
TC_LOG_ERROR("spells", "Player::AddSpell: Spell (ID: %u) is invalid. deleting for all characters in `character_spell`.", spellId);
DeleteSpellFromAllPlayers(spellId);
}
else
TC_LOG_ERROR("spells", "Player::AddSpell: Spell (ID: %u) is invalid", spellId);
return false;
}
PlayerSpellState state = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
bool dependent_set = false;
bool disabled_case = false;
bool superceded_old = false;
PlayerSpellMap::iterator itr = m_spells.find(spellId);
// Remove temporary spell if found to prevent conflicts
if (itr != m_spells.end() && itr->second->state == PLAYERSPELL_TEMPORARY)
RemoveTemporarySpell(spellId);
else if (itr != m_spells.end())
{
uint32 next_active_spell_id = 0;
// fix activate state for non-stackable low rank (and find next spell for !active case)
if (spellInfo->IsRanked())
{
if (uint32 next = sSpellMgr->GetNextSpellInChain(spellId))
{
if (HasSpell(next))
{
// high rank already known so this must !active
active = false;
next_active_spell_id = next;
}
}
}
// not do anything if already known in expected state
if (itr->second->state != PLAYERSPELL_REMOVED && itr->second->active == active &&
itr->second->dependent == dependent && itr->second->disabled == disabled)
{
if (!IsInWorld() && !learning) // explicitly load from DB and then exist in it already and set correctly
itr->second->state = PLAYERSPELL_UNCHANGED;
return false;
}
// dependent spell known as not dependent, overwrite state
if (itr->second->state != PLAYERSPELL_REMOVED && !itr->second->dependent && dependent)
{
itr->second->dependent = dependent;
if (itr->second->state != PLAYERSPELL_NEW)
itr->second->state = PLAYERSPELL_CHANGED;
dependent_set = true;
}
// update active state for known spell
if (itr->second->active != active && itr->second->state != PLAYERSPELL_REMOVED && !itr->second->disabled)
{
itr->second->active = active;
if (!IsInWorld() && !learning && !dependent_set) // explicitly load from DB and then exist in it already and set correctly
itr->second->state = PLAYERSPELL_UNCHANGED;
else if (itr->second->state != PLAYERSPELL_NEW)
itr->second->state = PLAYERSPELL_CHANGED;
if (active)
{
if (spellInfo->IsPassive() && HandlePassiveSpellLearn(spellInfo))
CastSpell(this, spellId, true);
}
else if (IsInWorld())
{
if (next_active_spell_id)
SendSupercededSpell(spellId, next_active_spell_id);
else
{
WorldPackets::Spells::UnlearnedSpells unlearnedSpells;
unlearnedSpells.SpellID.push_back(spellId);
SendDirectMessage(unlearnedSpells.Write());
}
}
return active; // learn (show in spell book if active now)
}
if (itr->second->disabled != disabled && itr->second->state != PLAYERSPELL_REMOVED)
{
if (itr->second->state != PLAYERSPELL_NEW)
itr->second->state = PLAYERSPELL_CHANGED;
itr->second->disabled = disabled;
if (disabled)
return false;
disabled_case = true;
}
else switch (itr->second->state)
{
case PLAYERSPELL_UNCHANGED: // known saved spell
return false;
case PLAYERSPELL_REMOVED: // re-learning removed not saved spell
{
delete itr->second;
m_spells.erase(itr);
state = PLAYERSPELL_CHANGED;
break; // need re-add
}
default: // known not saved yet spell (new or modified)
{
// can be in case spell loading but learned at some previous spell loading
if (!IsInWorld() && !learning && !dependent_set)
itr->second->state = PLAYERSPELL_UNCHANGED;
return false;
}
}
}
if (!disabled_case) // skip new spell adding if spell already known (disabled spells case)
{
// non talent spell: learn low ranks (recursive call)
if (uint32 prev_spell = sSpellMgr->GetPrevSpellInChain(spellId))
{
if (!IsInWorld() || disabled) // at spells loading, no output, but allow save
AddSpell(prev_spell, active, true, true, disabled, false, fromSkill);
else // at normal learning
LearnSpell(prev_spell, true, fromSkill);
}
PlayerSpell* newspell = new PlayerSpell;
newspell->state = state;
newspell->active = active;
newspell->dependent = dependent;
newspell->disabled = disabled;
// replace spells in action bars and spellbook to bigger rank if only one spell rank must be accessible
if (newspell->active && !newspell->disabled && spellInfo->IsRanked())
{
for (PlayerSpellMap::iterator itr2 = m_spells.begin(); itr2 != m_spells.end(); ++itr2)
{
if (itr2->second->state == PLAYERSPELL_REMOVED)
continue;
SpellInfo const* i_spellInfo = sSpellMgr->GetSpellInfo(itr2->first, DIFFICULTY_NONE);
if (!i_spellInfo)
continue;
if (spellInfo->IsDifferentRankOf(i_spellInfo))
{
if (itr2->second->active)
{
if (spellInfo->IsHighRankOf(i_spellInfo))
{
if (IsInWorld()) // not send spell (re-/over-)learn packets at loading
SendSupercededSpell(itr2->first, spellId);
// mark old spell as disable (SMSG_SUPERCEDED_SPELL replace it in client by new)
itr2->second->active = false;
if (itr2->second->state != PLAYERSPELL_NEW)
itr2->second->state = PLAYERSPELL_CHANGED;
superceded_old = true; // new spell replace old in action bars and spell book.
}
else
{
if (IsInWorld()) // not send spell (re-/over-)learn packets at loading
SendSupercededSpell(spellId, itr2->first);
// mark new spell as disable (not learned yet for client and will not learned)
newspell->active = false;
if (newspell->state != PLAYERSPELL_NEW)
newspell->state = PLAYERSPELL_CHANGED;
}
}
}
}
}
m_spells[spellId] = newspell;
// return false if spell disabled
if (newspell->disabled)
return false;
}
// cast talents with SPELL_EFFECT_LEARN_SPELL (other dependent spells will learned later as not auto-learned)
// note: all spells with SPELL_EFFECT_LEARN_SPELL isn't passive
if (!loading && spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT) && spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL))
{
// ignore stance requirement for talent learn spell (stance set for spell only for client spell description show)
CastSpell(this, spellId, true);
}
// also cast passive spells (including all talents without SPELL_EFFECT_LEARN_SPELL) with additional checks
else if (spellInfo->IsPassive())
{
if (HandlePassiveSpellLearn(spellInfo))
CastSpell(this, spellId, true);
}
else if (spellInfo->HasEffect(SPELL_EFFECT_SKILL_STEP))
{
CastSpell(this, spellId, true);
return false;
}
else if (spellInfo->HasAttribute(SPELL_ATTR1_CAST_WHEN_LEARNED))
CastSpell(this, spellId, true);
// update free primary prof.points (if any, can be none in case GM .learn prof. learning)
if (uint32 freeProfs = GetFreePrimaryProfessionPoints())
{
if (spellInfo->IsPrimaryProfessionFirstRank())
SetFreePrimaryProfessions(freeProfs - 1);
}
SkillLineAbilityMapBounds skill_bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId);
if (SpellLearnSkillNode const* spellLearnSkill = sSpellMgr->GetSpellLearnSkill(spellId))
{
// add dependent skills if this spell is not learned from adding skill already
if (spellLearnSkill->skill != fromSkill)
{
uint32 skill_value = GetPureSkillValue(spellLearnSkill->skill);
uint32 skill_max_value = GetPureMaxSkillValue(spellLearnSkill->skill);
if (skill_value < spellLearnSkill->value)
skill_value = spellLearnSkill->value;
uint32 new_skill_max_value = spellLearnSkill->maxvalue == 0 ? GetMaxSkillValueForLevel() : spellLearnSkill->maxvalue;
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;
if (_spell_idx->second->SkillLine == fromSkill)
continue;
// Runeforging special case
if ((_spell_idx->second->AcquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN && !HasSkill(_spell_idx->second->SkillLine)) || ((_spell_idx->second->SkillLine == SKILL_RUNEFORGING) && _spell_idx->second->TrivialSkillLineRankHigh == 0))
if (SkillRaceClassInfoEntry const* rcInfo = sDB2Manager.GetSkillRaceClassInfo(_spell_idx->second->SkillLine, GetRace(), GetClass()))
LearnDefaultSkill(rcInfo);
}
}
// learn dependent spells
SpellLearnSpellMapBounds spell_bounds = sSpellMgr->GetSpellLearnSpellMapBounds(spellId);
for (SpellLearnSpellMap::const_iterator itr2 = spell_bounds.first; itr2 != spell_bounds.second; ++itr2)
{
if (!itr2->second.AutoLearned)
{
if (!IsInWorld() || !itr2->second.Active) // at spells loading, no output, but allow save
AddSpell(itr2->second.Spell, itr2->second.Active, true, true, false);
else // at normal learning
LearnSpell(itr2->second.Spell, true);
}
if (itr2->second.OverridesSpell && itr2->second.Active)
AddOverrideSpell(itr2->second.OverridesSpell, itr2->second.Spell);
}
if (!GetSession()->PlayerLoading())
{
// not ranked skills
for (SkillLineAbilityMap::const_iterator _spell_idx = skill_bounds.first; _spell_idx != skill_bounds.second; ++_spell_idx)
{
UpdateCriteria(CriteriaType::LearnTradeskillSkillLine, _spell_idx->second->SkillLine);
UpdateCriteria(CriteriaType::LearnSpellFromSkillLine, _spell_idx->second->SkillLine);
}
UpdateCriteria(CriteriaType::LearnOrKnowSpell, spellId);
}
// needs to be when spell is already learned, to prevent infinite recursion crashes
if (sDB2Manager.GetMount(spellId))
GetSession()->GetCollectionMgr()->AddMount(spellId, MOUNT_STATUS_NONE, false, IsInWorld() ? false : true);
// return true (for send learn packet) only if spell active (in case ranked spells) and not replace old spell
return active && !disabled && !superceded_old;
}
void Player::AddTemporarySpell(uint32 spellId)
{
PlayerSpellMap::iterator itr = m_spells.find(spellId);
// spell already added - do not do anything
if (itr != m_spells.end())
return;
PlayerSpell* newspell = new PlayerSpell;
newspell->state = PLAYERSPELL_TEMPORARY;
newspell->active = true;
newspell->dependent = false;
newspell->disabled = false;
m_spells[spellId] = newspell;
}
void Player::RemoveTemporarySpell(uint32 spellId)
{
PlayerSpellMap::iterator itr = m_spells.find(spellId);
// spell already not in list - do not do anything
if (itr == m_spells.end())
return;
// spell has other state than temporary - do not change it
if (itr->second->state != PLAYERSPELL_TEMPORARY)
return;
delete itr->second;
m_spells.erase(itr);
}
bool Player::HandlePassiveSpellLearn(SpellInfo const* spellInfo)
{
// 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();
bool need_cast = (!spellInfo->Stances || (form && (spellInfo->Stances & (UI64LIT(1) << (form - 1)))) ||
(!form && (spellInfo->HasAttribute(SPELL_ATTR2_NOT_NEED_SHAPESHIFT))));
// Check EquippedItemClass
// passive spells which apply aura and have an item requirement are to be added manually, instead of casted
if (spellInfo->EquippedItemClass >= 0)
{
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
{
if (spellEffectInfo.IsAura())
{
if (!HasAura(spellInfo->Id) && HasItemFitToSpellRequirements(spellInfo))
AddAura(spellInfo->Id, this);
return false;
}
}
}
//Check CasterAuraStates
return need_cast && (!spellInfo->CasterAuraState || HasAuraState(AuraStateType(spellInfo->CasterAuraState)));
}
void Player::LearnSpell(uint32 spell_id, bool dependent, int32 fromSkill /*= 0*/, bool suppressMessaging /*= false*/)
{
PlayerSpellMap::iterator itr = m_spells.find(spell_id);
bool disabled = (itr != m_spells.end()) ? itr->second->disabled : false;
bool active = disabled ? itr->second->active : true;
bool learning = AddSpell(spell_id, active, true, dependent, false, false, fromSkill);
// prevent duplicated entires in spell book, also not send if not in world (loading)
if (learning && IsInWorld())
{
WorldPackets::Spells::LearnedSpells packet;
packet.SpellID.push_back(spell_id);
packet.SuppressMessaging = suppressMessaging;
SendDirectMessage(packet.Write());
}
// learn all disabled higher ranks and required spells (recursive)
if (disabled)
{
if (uint32 nextSpell = sSpellMgr->GetNextSpellInChain(spell_id))
{
PlayerSpellMap::iterator iter = m_spells.find(nextSpell);
if (iter != m_spells.end() && iter->second->disabled)
LearnSpell(nextSpell, false, fromSkill);
}
SpellsRequiringSpellMapBounds spellsRequiringSpell = sSpellMgr->GetSpellsRequiringSpellBounds(spell_id);
for (SpellsRequiringSpellMap::const_iterator itr2 = spellsRequiringSpell.first; itr2 != spellsRequiringSpell.second; ++itr2)
{
PlayerSpellMap::iterator iter2 = m_spells.find(itr2->second);
if (iter2 != m_spells.end() && iter2->second->disabled)
LearnSpell(itr2->second, false, fromSkill);
}
}
else
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_LEARNSPELL, spell_id, 1);
}
void Player::RemoveSpell(uint32 spell_id, bool disabled /*= false*/, bool learn_low_rank /*= true*/, bool suppressMessaging /*= false*/)
{
PlayerSpellMap::iterator itr = m_spells.find(spell_id);
if (itr == m_spells.end())
return;
if (itr->second->state == PLAYERSPELL_REMOVED || (disabled && itr->second->disabled) || itr->second->state == PLAYERSPELL_TEMPORARY)
return;
// unlearn non talent higher ranks (recursive)
if (uint32 nextSpell = sSpellMgr->GetNextSpellInChain(spell_id))
{
SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(nextSpell, DIFFICULTY_NONE);
if (HasSpell(nextSpell) && !spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT))
RemoveSpell(nextSpell, disabled, false);
}
//unlearn spells dependent from recently removed spells
SpellsRequiringSpellMapBounds spellsRequiringSpell = sSpellMgr->GetSpellsRequiringSpellBounds(spell_id);
for (SpellsRequiringSpellMap::const_iterator itr2 = spellsRequiringSpell.first; itr2 != spellsRequiringSpell.second; ++itr2)
RemoveSpell(itr2->second, disabled);
// re-search, it can be corrupted in prev loop
itr = m_spells.find(spell_id);
if (itr == m_spells.end())
return; // already unleared
bool cur_active = itr->second->active;
bool cur_dependent = itr->second->dependent;
if (disabled)
{
itr->second->disabled = disabled;
if (itr->second->state != PLAYERSPELL_NEW)
itr->second->state = PLAYERSPELL_CHANGED;
}
else
{
if (itr->second->state == PLAYERSPELL_NEW)
{
delete itr->second;
m_spells.erase(itr);
}
else
itr->second->state = PLAYERSPELL_REMOVED;
}
RemoveOwnedAura(spell_id, GetGUID());
// remove pet auras
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
if (PetAura const* petSpell = sSpellMgr->GetPetAura(spell_id, i))
RemovePetAura(petSpell);
// update free primary prof.points (if not overflow setting, can be in case GM use before .learn prof. learning)
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id, DIFFICULTY_NONE);
if (spellInfo && spellInfo->IsPrimaryProfessionFirstRank())
{
uint32 freeProfs = GetFreePrimaryProfessionPoints()+1;
if (freeProfs <= sWorld->getIntConfig(CONFIG_MAX_PRIMARY_TRADE_SKILL))
SetFreePrimaryProfessions(freeProfs);
}
// remove dependent skill
SpellLearnSkillNode const* spellLearnSkill = sSpellMgr->GetSpellLearnSkill(spell_id);
if (spellLearnSkill)
{
uint32 prev_spell = sSpellMgr->GetPrevSpellInChain(spell_id);
if (!prev_spell) // first rank, remove skill
SetSkill(spellLearnSkill->skill, 0, 0, 0);
else
{
// search prev. skill setting by spell ranks chain
SpellLearnSkillNode const* prevSkill = sSpellMgr->GetSpellLearnSkill(prev_spell);
while (!prevSkill && prev_spell)
{
prev_spell = sSpellMgr->GetPrevSpellInChain(prev_spell);
prevSkill = sSpellMgr->GetSpellLearnSkill(sSpellMgr->GetFirstSpellInChain(prev_spell));
}
if (!prevSkill) // not found prev skill setting, remove skill
SetSkill(spellLearnSkill->skill, 0, 0, 0);
else // set to prev. skill setting values
{
uint32 skill_value = GetPureSkillValue(prevSkill->skill);
uint32 skill_max_value = GetPureMaxSkillValue(prevSkill->skill);
if (skill_value > prevSkill->value)
skill_value = prevSkill->value;
uint32 new_skill_max_value = prevSkill->maxvalue == 0 ? GetMaxSkillValueForLevel() : prevSkill->maxvalue;
if (skill_max_value > new_skill_max_value)
skill_max_value = new_skill_max_value;
SetSkill(prevSkill->skill, prevSkill->step, skill_value, skill_max_value);
}
}
}
// remove dependent spells
SpellLearnSpellMapBounds spell_bounds = sSpellMgr->GetSpellLearnSpellMapBounds(spell_id);
for (SpellLearnSpellMap::const_iterator itr2 = spell_bounds.first; itr2 != spell_bounds.second; ++itr2)
{
RemoveSpell(itr2->second.Spell, disabled);
if (itr2->second.OverridesSpell)
RemoveOverrideSpell(itr2->second.OverridesSpell, itr2->second.Spell);
}
// activate lesser rank in spellbook/action bar, and cast it if need
bool prev_activate = false;
if (uint32 prev_id = sSpellMgr->GetPrevSpellInChain(spell_id))
{
// if ranked non-stackable spell: need activate lesser rank and update dendence state
/// No need to check for spellInfo != nullptr here because if cur_active is true, then that means that the spell was already in m_spells, and only valid spells can be pushed there.
if (cur_active && spellInfo->IsRanked())
{
// need manually update dependence state (learn spell ignore like attempts)
PlayerSpellMap::iterator prev_itr = m_spells.find(prev_id);
if (prev_itr != m_spells.end())
{
if (prev_itr->second->dependent != cur_dependent)
{
prev_itr->second->dependent = cur_dependent;
if (prev_itr->second->state != PLAYERSPELL_NEW)
prev_itr->second->state = PLAYERSPELL_CHANGED;
}
// now re-learn if need re-activate
if (!prev_itr->second->active && learn_low_rank)
{
if (AddSpell(prev_id, true, false, prev_itr->second->dependent, prev_itr->second->disabled))
{
// downgrade spell ranks in spellbook and action bar
SendSupercededSpell(spell_id, prev_id);
prev_activate = true;
}
}
}
}
}
m_overrideSpells.erase(spell_id);
if (m_canTitanGrip)
{
if (spellInfo && spellInfo->IsPassive() && spellInfo->HasEffect(SPELL_EFFECT_TITAN_GRIP))
{
RemoveAurasDueToSpell(m_titanGripPenaltySpellId);
SetCanTitanGrip(false);
}
}
if (m_canDualWield)
{
if (spellInfo && spellInfo->IsPassive() && spellInfo->HasEffect(SPELL_EFFECT_DUAL_WIELD))
SetCanDualWield(false);
}
if (sWorld->getBoolConfig(CONFIG_OFFHAND_CHECK_AT_SPELL_UNLEARN))
AutoUnequipOffhandIfNeed();
// remove from spell book if not replaced by lesser rank
if (!prev_activate)
{
WorldPackets::Spells::UnlearnedSpells unlearnedSpells;
unlearnedSpells.SpellID.push_back(spell_id);
unlearnedSpells.SuppressMessaging = suppressMessaging;
SendDirectMessage(unlearnedSpells.Write());
}
}
void Player::RemoveArenaSpellCooldowns(bool removeActivePetCooldowns)
{
// remove cooldowns on spells that have < 10 min CD
GetSpellHistory()->ResetCooldowns([](SpellHistory::CooldownStorageType::iterator itr)
{
SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(itr->first, DIFFICULTY_NONE);
return spellInfo->RecoveryTime < 10 * MINUTE * IN_MILLISECONDS && spellInfo->CategoryRecoveryTime < 10 * MINUTE * IN_MILLISECONDS;
}, true);
// pet cooldowns
if (removeActivePetCooldowns)
if (Pet* pet = GetPet())
pet->GetSpellHistory()->ResetAllCooldowns();
}
uint32 Player::GetNextResetTalentsCost() const
{
// The first time reset costs 1 gold
if (GetTalentResetCost() < 1*GOLD)
return 1*GOLD;
// then 5 gold
else if (GetTalentResetCost() < 5*GOLD)
return 5*GOLD;
// After that it increases in increments of 5 gold
else if (GetTalentResetCost() < 10*GOLD)
return 10*GOLD;
else
{
uint64 months = (GameTime::GetGameTime() - GetTalentResetTime())/MONTH;
if (months > 0)
{
// This cost will be reduced by a rate of 5 gold per month
int32 new_cost = int32(GetTalentResetCost() - 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 = GetTalentResetCost() + 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 noCost)
{
sScriptMgr->OnPlayerTalentsReset(this, noCost);
// not need after this call
if (HasAtLoginFlag(AT_LOGIN_RESET_TALENTS))
RemoveAtLoginFlag(AT_LOGIN_RESET_TALENTS, true);
uint32 cost = 0;
if (!noCost && !sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST))
{
cost = GetNextResetTalentsCost();
if (!HasEnoughMoney(uint64(cost)))
{
SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, nullptr, 0, 0);
return false;
}
}
RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true);
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
if (!talentInfo)
continue;
// unlearn only talents for character class
// some spell learned by one class as normal spells or know at creation but another class learn it as talent,
// to prevent unexpected lost normal learned spell skip another class talents
if (talentInfo->ClassID != GetClass())
continue;
// skip non-existent talent ranks
if (talentInfo->SpellID == 0)
continue;
RemoveTalent(talentInfo);
}
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
_SaveTalents(trans);
_SaveSpells(trans);
CharacterDatabase.CommitTransaction(trans);
if (!noCost)
{
ModifyMoney(-(int64)cost);
UpdateCriteria(CriteriaType::MoneySpentOnRespecs, cost);
UpdateCriteria(CriteriaType::TotalRespecs, 1);
SetTalentResetCost(cost);
SetTalentResetTime(GameTime::GetGameTime());
}
/* when prev line will dropped use next line
if (Pet* pet = GetPet())
{
if (pet->getPetType() == HUNTER_PET && !pet->GetCreatureTemplate()->IsTameable(CanTameExoticPets()))
RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true);
}
*/
return true;
}
void Player::ResetPvpTalents()
{
for (uint8 spec = 0; spec < MAX_SPECIALIZATIONS; ++spec)
for (uint32 talentId : GetPvpTalentMap(spec))
if (PvpTalentEntry const* talentInfo = sPvpTalentStore.LookupEntry(talentId))
RemovePvpTalent(talentInfo, spec);
}
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] == nullptr)
continue;
m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
}
for (uint8 i = INVENTORY_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
{
if (m_items[i] == nullptr)
continue;
m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
}
for (uint8 i = REAGENT_SLOT_START; i < REAGENT_SLOT_END; ++i)
{
if (m_items[i] == nullptr)
continue;
m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
}
for (uint8 i = CHILD_EQUIPMENT_SLOT_START; i < CHILD_EQUIPMENT_SLOT_END; ++i)
{
if (m_items[i] == nullptr)
continue;
m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
}
}
Unit::BuildCreateUpdateBlockForPlayer(data, target);
}
UF::UpdateFieldFlag Player::GetUpdateFieldFlagsFor(Player const* target) const
{
UF::UpdateFieldFlag flags = Unit::GetUpdateFieldFlagsFor(target);
if (IsInSameRaidWith(target))
flags |= UF::UpdateFieldFlag::PartyMember;
return flags;
}
void Player::BuildValuesCreate(ByteBuffer* data, Player const* target) const
{
UF::UpdateFieldFlag flags = GetUpdateFieldFlagsFor(target);
std::size_t sizePos = data->wpos();
*data << uint32(0);
*data << uint8(flags);
m_objectData->WriteCreate(*data, flags, this, target);
m_unitData->WriteCreate(*data, flags, this, target);
m_playerData->WriteCreate(*data, flags, this, target);
if (target == this)
m_activePlayerData->WriteCreate(*data, flags, this, target);
data->put<uint32>(sizePos, data->wpos() - sizePos - 4);
}
void Player::BuildValuesUpdate(ByteBuffer* data, Player const* target) const
{
UF::UpdateFieldFlag flags = GetUpdateFieldFlagsFor(target);
std::size_t sizePos = data->wpos();
*data << uint32(0);
*data << uint32(m_values.GetChangedObjectTypeMask() & ~(uint32(target != this) << TYPEID_ACTIVE_PLAYER));
if (m_values.HasChanged(TYPEID_OBJECT))
m_objectData->WriteUpdate(*data, flags, this, target);
if (m_values.HasChanged(TYPEID_UNIT))
m_unitData->WriteUpdate(*data, flags, this, target);
if (m_values.HasChanged(TYPEID_PLAYER))
m_playerData->WriteUpdate(*data, flags, this, target);
if (target == this && m_values.HasChanged(TYPEID_ACTIVE_PLAYER))
m_activePlayerData->WriteUpdate(*data, flags, this, target);
data->put<uint32>(sizePos, data->wpos() - sizePos - 4);
}
void Player::BuildValuesUpdateWithFlag(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const
{
UpdateMask<NUM_CLIENT_OBJECT_TYPES> valuesMask;
valuesMask.Set(TYPEID_UNIT);
valuesMask.Set(TYPEID_PLAYER);
std::size_t sizePos = data->wpos();
*data << uint32(0);
*data << uint32(valuesMask.GetBlock(0));
UF::UnitData::Mask mask;
m_unitData->AppendAllowedFieldsMaskForFlag(mask, flags);
m_unitData->WriteUpdate(*data, mask, true, this, target);
UF::PlayerData::Mask mask2;
m_playerData->AppendAllowedFieldsMaskForFlag(mask2, flags);
m_playerData->WriteUpdate(*data, mask2, true, this, target);
data->put<uint32>(sizePos, data->wpos() - sizePos - 4);
}
void Player::BuildValuesUpdateForPlayerWithMask(UpdateData* data, UF::ObjectData::Mask const& requestedObjectMask,
UF::UnitData::Mask const& requestedUnitMask, UF::PlayerData::Mask const& requestedPlayerMask,
UF::ActivePlayerData::Mask const& requestedActivePlayerMask, Player const* target) const
{
UF::UpdateFieldFlag flags = GetUpdateFieldFlagsFor(target);
UpdateMask<NUM_CLIENT_OBJECT_TYPES> valuesMask;
if (requestedObjectMask.IsAnySet())
valuesMask.Set(TYPEID_OBJECT);
UF::UnitData::Mask unitMask = requestedUnitMask;
m_unitData->FilterDisallowedFieldsMaskForFlag(unitMask, flags);
if (unitMask.IsAnySet())
valuesMask.Set(TYPEID_UNIT);
UF::PlayerData::Mask playerMask = requestedPlayerMask;
m_playerData->FilterDisallowedFieldsMaskForFlag(playerMask, flags);
if (playerMask.IsAnySet())
valuesMask.Set(TYPEID_PLAYER);
if (target == this && requestedActivePlayerMask.IsAnySet())
valuesMask.Set(TYPEID_ACTIVE_PLAYER);
ByteBuffer buffer = PrepareValuesUpdateBuffer();
std::size_t sizePos = buffer.wpos();
buffer << uint32(0);
buffer << uint32(valuesMask.GetBlock(0));
if (valuesMask[TYPEID_OBJECT])
m_objectData->WriteUpdate(buffer, requestedObjectMask, true, this, target);
if (valuesMask[TYPEID_UNIT])
m_unitData->WriteUpdate(buffer, unitMask, true, this, target);
if (valuesMask[TYPEID_PLAYER])
m_playerData->WriteUpdate(buffer, playerMask, true, this, target);
if (valuesMask[TYPEID_ACTIVE_PLAYER])
m_activePlayerData->WriteUpdate(buffer, requestedActivePlayerMask, true, this, target);
buffer.put<uint32>(sizePos, buffer.wpos() - sizePos - 4);
data->AddUpdateBlock(buffer);
}
void Player::DestroyForPlayer(Player* target) const
{
Unit::DestroyForPlayer(target);
if (target == this)
{
for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i)
{
if (m_items[i] == nullptr)
continue;
m_items[i]->DestroyForPlayer(target);
}
for (uint8 i = INVENTORY_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
{
if (m_items[i] == nullptr)
continue;
m_items[i]->DestroyForPlayer(target);
}
for (uint8 i = REAGENT_SLOT_START; i < REAGENT_SLOT_END; ++i)
{
if (m_items[i] == nullptr)
continue;
m_items[i]->DestroyForPlayer(target);
}
for (uint8 i = CHILD_EQUIPMENT_SLOT_START; i < CHILD_EQUIPMENT_SLOT_END; ++i)
{
if (m_items[i] == nullptr)
continue;
m_items[i]->DestroyForPlayer(target);
}
}
}
void Player::ClearUpdateMask(bool remove)
{
m_values.ClearChangesMask(&Player::m_playerData);
m_values.ClearChangesMask(&Player::m_activePlayerData);
Unit::ClearUpdateMask(remove);
}
bool Player::HasSpell(uint32 spell) const
{
PlayerSpellMap::const_iterator itr = m_spells.find(spell);
return (itr != m_spells.end() && itr->second->state != PLAYERSPELL_REMOVED &&
!itr->second->disabled);
}
bool Player::HasTalent(uint32 talentId, uint8 group) const
{
PlayerTalentMap::const_iterator itr = GetTalentMap(group)->find(talentId);
return (itr != GetTalentMap(group)->end() && itr->second != PLAYERSPELL_REMOVED);
}
bool Player::HasActiveSpell(uint32 spell) const
{
PlayerSpellMap::const_iterator itr = m_spells.find(spell);
return (itr != m_spells.end() && itr->second->state != PLAYERSPELL_REMOVED &&
itr->second->active && !itr->second->disabled);
}
/**
* Deletes a character from the database
*
* The way characters will be deleted is decided based on the config option.
*
* @see Player::DeleteOldCharacters
*
* @param playerguid the low-GUID from the player which should be deleted
* @param accountId the account id from the player
* @param updateRealmChars when this flag is set, the amount of characters on that realm will be updated in the realmlist
* @param deleteFinally if this flag is set, the config option will be ignored and the character will be permanently removed from the database
*/
void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRealmChars, bool deleteFinally)
{
// Avoid realm-update for non-existing account
if (accountId == 0)
updateRealmChars = false;
// Convert guid to low GUID for CharacterNameData, but also other methods on success
ObjectGuid::LowType guid = playerguid.GetCounter();
uint32 charDeleteMethod = sWorld->getIntConfig(CONFIG_CHARDELETE_METHOD);
CharacterCacheEntry const* characterInfo = sCharacterCache->GetCharacterCacheByGuid(playerguid);
std::string name;
if (characterInfo)
name = characterInfo->Name;
if (deleteFinally)
charDeleteMethod = CHAR_DELETE_REMOVE;
else if (characterInfo) // To avoid a query, we select loaded data. If it doesn't exist, return.
{
// Define the required variables
uint32 charDeleteMinLvl;
if (characterInfo->Class == CLASS_DEATH_KNIGHT)
charDeleteMinLvl = sWorld->getIntConfig(CONFIG_CHARDELETE_DEATH_KNIGHT_MIN_LEVEL);
else if (characterInfo->Class == CLASS_DEMON_HUNTER)
charDeleteMinLvl = sWorld->getIntConfig(CONFIG_CHARDELETE_DEMON_HUNTER_MIN_LEVEL);
else
charDeleteMinLvl = sWorld->getIntConfig(CONFIG_CHARDELETE_MIN_LEVEL);
// if we want to finalize the character removal or the character does not meet the level requirement of either heroic or non-heroic settings,
// we set it to mode CHAR_DELETE_REMOVE
if (characterInfo->Level < charDeleteMinLvl)
charDeleteMethod = CHAR_DELETE_REMOVE;
}
LoginDatabaseTransaction loginTransaction = LoginDatabase.BeginTransaction();
LoginDatabasePreparedStatement* loginStmt = nullptr;
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
if (ObjectGuid::LowType guildId = sCharacterCache->GetCharacterGuildIdByGuid(playerguid))
if (Guild* guild = sGuildMgr->GetGuildById(guildId))
guild->DeleteMember(trans, playerguid, false, false, true);
// remove from arena teams
LeaveAllArenaTeams(playerguid);
// the player was uninvited already on logout so just remove from group
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_GROUP_MEMBER);
stmt->setUInt64(0, guid);
PreparedQueryResult resultGroup = CharacterDatabase.Query(stmt);
if (resultGroup)
if (Group* group = sGroupMgr->GetGroupByDbStoreId((*resultGroup)[0].GetUInt32()))
RemoveFromGroup(group, playerguid);
// Remove signs from petitions (also remove petitions if owner);
RemovePetitionsAndSigns(playerguid);
switch (charDeleteMethod)
{
// Completely remove from the database
case CHAR_DELETE_REMOVE:
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_COD_ITEM_MAIL);
stmt->setUInt64(0, guid);
PreparedQueryResult resultMail = CharacterDatabase.Query(stmt);
if (resultMail)
{
std::unordered_map<uint32, std::vector<Item*>> itemsByMail;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS);
stmt->setUInt64(0, guid);
PreparedQueryResult resultItems = CharacterDatabase.Query(stmt);
if (resultItems)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS_ARTIFACT);
stmt->setUInt64(0, guid);
PreparedQueryResult artifactResult = CharacterDatabase.Query(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS_AZERITE);
stmt->setUInt64(0, guid);
PreparedQueryResult azeriteResult = CharacterDatabase.Query(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS_AZERITE_MILESTONE_POWER);
stmt->setUInt64(0, guid);
PreparedQueryResult azeriteItemMilestonePowersResult = CharacterDatabase.Query(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS_AZERITE_UNLOCKED_ESSENCE);
stmt->setUInt64(0, guid);
PreparedQueryResult azeriteItemUnlockedEssencesResult = CharacterDatabase.Query(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS_AZERITE_EMPOWERED);
stmt->setUInt64(0, guid);
PreparedQueryResult azeriteEmpoweredItemResult = CharacterDatabase.Query(stmt);
std::unordered_map<ObjectGuid::LowType, ItemAdditionalLoadInfo> additionalData;
ItemAdditionalLoadInfo::Init(&additionalData, artifactResult, azeriteResult, azeriteItemMilestonePowersResult,
azeriteItemUnlockedEssencesResult, azeriteEmpoweredItemResult);
do
{
Field* fields = resultItems->Fetch();
uint32 mailId = fields[44].GetUInt32();
if (Item* mailItem = _LoadMailedItem(playerguid, nullptr, mailId, nullptr, fields, Trinity::Containers::MapGetValuePtr(additionalData, fields[0].GetUInt64())))
itemsByMail[mailId].push_back(mailItem);
} while (resultItems->NextRow());
}
do
{
Field* mailFields = resultMail->Fetch();
uint32 mail_id = mailFields[0].GetUInt32();
uint8 mailType = mailFields[1].GetUInt8();
uint16 mailTemplateId= mailFields[2].GetUInt16();
ObjectGuid::LowType sender = mailFields[3].GetUInt64();
std::string subject = mailFields[4].GetString();
std::string body = mailFields[5].GetString();
uint64 money = mailFields[6].GetUInt64();
bool has_items = mailFields[7].GetBool();
// We can return mail now
// So firstly delete the old one
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
stmt->setUInt32(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->setUInt32(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->setUInt32(0, mail_id);
trans->Append(stmt);
uint32 pl_account = sCharacterCache->GetCharacterAccountIdByGuid(playerguid);
draft.AddMoney(money).SendReturnToSender(pl_account, guid, sender, trans);
}
while (resultMail->NextRow());
// Free remaining items
for (auto&& kvp : itemsByMail)
for (Item* item : kvp.second)
delete item;
}
// 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_PETS);
stmt->setUInt64(0, guid);
PreparedQueryResult resultPets = CharacterDatabase.Query(stmt);
if (resultPets)
{
do
{
uint32 petguidlow = (*resultPets)[0].GetUInt32();
Pet::DeleteFromDB(petguidlow);
} while (resultPets->NextRow());
}
// Delete char from social list of online chars
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_SOCIAL);
stmt->setUInt64(0, guid);
if (PreparedQueryResult resultFriends = CharacterDatabase.Query(stmt))
{
do
{
if (Player* playerFriend = ObjectAccessor::FindPlayer(ObjectGuid::Create<HighGuid::Player>((*resultFriends)[0].GetUInt64())))
{
playerFriend->GetSocial()->RemoveFromSocialList(playerguid, SOCIAL_FLAG_ALL);
sSocialMgr->SendFriendStatus(playerFriend, FRIEND_REMOVED, playerguid);
}
} while (resultFriends->NextRow());
}
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_CUSTOMIZATIONS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_ACCOUNT_DATA);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_DECLINED_NAME);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_ARENA_STATS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_AURA_EFFECT);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_AURA);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_BGDATA);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_BATTLEGROUND_RANDOM);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_CUF_PROFILES);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_CURRENCY);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_GIFT);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_HOMEBIND);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_REWARDED);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_REPUTATION);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_COOLDOWNS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_CHARGES);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_GEMS_BY_OWNER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_TRANSMOG_BY_OWNER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_ARTIFACT_BY_OWNER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_ARTIFACT_POWERS_BY_OWNER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_MODIFIERS_BY_OWNER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_AZERITE_BY_OWNER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_AZERITE_MILESTONE_POWER_BY_OWNER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_AZERITE_UNLOCKED_ESSENCE_BY_OWNER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_AZERITE_EMPOWERED_BY_OWNER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_BY_OWNER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SOCIAL_BY_FRIEND);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SOCIAL_BY_GUID);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEMS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_OWNER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENTS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_EQUIPMENTSETS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRANSMOG_OUTFITS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_EVENTLOG_BY_PLAYER);
stmt->setUInt64(0, guid);
stmt->setUInt64(1, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_EVENTLOG_BY_PLAYER);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_GLYPHS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_DAILY);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_WEEKLY);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_MONTHLY);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_SEASONAL);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SKILLS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_STATS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_VOID_STORAGE_ITEM_BY_CHAR_GUID);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_FISHINGSTEPS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_FAVORITE_AUCTIONS_BY_CHAR);
stmt->setUInt64(0, guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_AURA_STORED_LOCATIONS_BY_GUID);
stmt->setUInt64(0, guid);
trans->Append(stmt);
loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_BATTLE_PETS_BY_OWNER);
loginStmt->setInt64(0, guid);
loginStmt->setInt32(0, realm.Id.Realm);
loginTransaction->Append(loginStmt);
Corpse::DeleteFromDB(playerguid, trans);
Garrison::DeleteFromDB(guid, trans);
sCharacterCache->DeleteCharacterCacheEntry(playerguid, name);
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->setUInt64(0, guid);
trans->Append(stmt);
sCharacterCache->UpdateCharacterInfoDeleted(playerguid, true);
break;
}
default:
TC_LOG_ERROR("entities.player.cheat", "Player::DeleteFromDB: Tried to delete player (%s) with unsupported delete method (%u).",
playerguid.ToString().c_str(), charDeleteMethod);
if (trans->GetSize() > 0)
CharacterDatabase.CommitTransaction(trans);
return;
}
LoginDatabase.CommitTransaction(loginTransaction);
CharacterDatabase.CommitTransaction(trans);
if (updateRealmChars)
sWorld->UpdateRealmCharCount(accountId);
}
/**
* Characters which were kept back in the database after being deleted and are now too old (see config option "CharDelete.KeepDays"), will be completely deleted.
*
* @see Player::DeleteFromDB
*/
void Player::DeleteOldCharacters()
{
uint32 keepDays = sWorld->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.
*
* @see Player::DeleteFromDB
*
* @param keepDays overwrite the config option by another amount of days
*/
void Player::DeleteOldCharacters(uint32 keepDays)
{
TC_LOG_INFO("entities.player", "Player::DeleteOldCharacters: Deleting all characters which have been deleted %u days before...", keepDays);
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_OLD_CHARS);
stmt->setUInt32(0, uint32(GameTime::GetGameTime() - time_t(keepDays * DAY)));
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (result)
{
TC_LOG_DEBUG("entities.player", "Player::DeleteOldCharacters: Found " UI64FMTD " character(s) to delete", result->GetRowCount());
do
{
Field* fields = result->Fetch();
Player::DeleteFromDB(ObjectGuid::Create<HighGuid::Player>(fields[0].GetUInt64()), fields[1].GetUInt32(), true, true);
}
while (result->NextRow());
}
}
/* Preconditions:
- a resurrectable corpse must not be loaded for the player (only bones)
- the player must be in world
*/
void Player::BuildPlayerRepop()
{
WorldPackets::Misc::PreRessurect packet;
packet.PlayerGUID = GetGUID();
SendDirectMessage(packet.Write());
// If the player has the Wisp racial then cast the Wisp aura on them
if (HasSpell(20585))
CastSpell(this, 20584, true);
CastSpell(this, 8326, true);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Release);
// there must be SMSG.FORCE_RUN_SPEED_CHANGE, SMSG.FORCE_SWIM_SPEED_CHANGE, SMSG.MOVE_SET_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 (corpseLocation.GetMapId() == GetMapId())
{
TC_LOG_ERROR("entities.player", "Player::BuildPlayerRepop: Player '%s' (%s) already has a corpse", GetName().c_str(), GetGUID().ToString().c_str());
return;
}
// create a corpse and place it at the player's location
Corpse* corpse = CreateCorpse();
if (!corpse)
{
TC_LOG_ERROR("entities.player", "Player::BuildPlayerRepop: Error creating corpse for player '%s' (%s)", GetName().c_str(), GetGUID().ToString().c_str());
return;
}
GetMap()->AddToMap(corpse);
// convert player body to ghost
setDeathState(DEAD);
SetHealth(1);
SetWaterWalking(true);
if (!GetSession()->isLogingOut() && !HasUnitState(UNIT_STATE_STUNNED))
SetRooted(false);
// BG - remove insignia related
RemoveUnitFlag(UNIT_FLAG_SKINNABLE);
int32 corpseReclaimDelay = CalculateCorpseReclaimDelay();
if (corpseReclaimDelay >= 0)
SendCorpseReclaimDelay(corpseReclaimDelay);
// to prevent cheating
corpse->ResetGhostTime();
StopMirrorTimers(); //disable timers(bars)
// set and clear other
SetAnimTier(UNIT_BYTE1_FLAG_ALWAYS_STAND, false);
// OnPlayerRepop hook
sScriptMgr->OnPlayerRepop(this);
}
void Player::ResurrectPlayer(float restore_percent, bool applySickness)
{
WorldPackets::Misc::DeathReleaseLoc packet;
packet.MapID = -1;
SendDirectMessage(packet.Write());
// speed change, land walk
// remove death flag + set aura
SetAnimTier(UNIT_BYTE1_FLAG_NONE, false);
RemovePlayerFlag(PLAYER_FLAGS_IS_OUT_OF_BOUNDS);
// This must be called always even on Players with race != RACE_NIGHTELF in case of faction change
RemoveAurasDueToSpell(20584); // RACE_NIGHTELF speed bonuses
RemoveAurasDueToSpell(8326); // SPELL_AURA_GHOST
if (GetSession()->IsARecruiter() || (GetSession()->GetRecruiterId() != 0))
AddDynamicFlag(UNIT_DYNFLAG_REFER_A_FRIEND);
setDeathState(ALIVE);
// add the flag to make sure opcode is always sent
AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
SetWaterWalking(false);
if (!HasUnitState(UNIT_STATE_STUNNED))
SetRooted(false);
m_deathTimer = 0;
// set health/powers (0- will be set in caller)
if (restore_percent > 0.0f)
{
SetHealth(GetMaxHealth() * restore_percent);
SetPower(POWER_MANA, GetMaxPower(POWER_MANA) * restore_percent);
SetPower(POWER_RAGE, 0);
SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY) * restore_percent);
SetPower(POWER_FOCUS, GetMaxPower(POWER_FOCUS) * restore_percent);
SetPower(POWER_LUNAR_POWER, 0);
}
// trigger update zone for alive state zone updates
uint32 newzone, newarea;
GetZoneAndAreaId(newzone, newarea);
UpdateZone(newzone, newarea);
sOutdoorPvPMgr->HandlePlayerResurrects(this, newzone);
if (InBattleground())
{
if (Battleground* bg = GetBattleground())
bg->HandlePlayerResurrect(this);
}
// update visibility
UpdateObjectVisibility();
// recast lost by death auras of any items held in the inventory
CastAllObtainSpells();
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);
ChrRacesEntry const* raceEntry = sChrRacesStore.AssertEntry(GetRace());
if (int32(GetLevel()) >= startLevel)
{
// set resurrection sickness
CastSpell(this, raceEntry->ResSicknessSpellID, true);
// not full duration
if (int32(GetLevel()) < startLevel+9)
{
int32 delta = (int32(GetLevel()) - startLevel + 1)*MINUTE;
if (Aura* aur = GetAura(raceEntry->ResSicknessSpellID, GetGUID()))
{
aur->SetDuration(delta*IN_MILLISECONDS);
}
}
}
}
void Player::KillPlayer()
{
if (IsFlying() && !GetTransport())
GetMotionMaster()->MoveFall();
SetRooted(true);
StopMirrorTimers(); //disable timers(bars)
setDeathState(CORPSE);
//SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_IN_PVP);
SetDynamicFlags(UNIT_DYNFLAG_NONE);
if (!sMapStore.LookupEntry(GetMapId())->Instanceable() && !HasAuraType(SPELL_AURA_PREVENT_RESURRECTION))
AddPlayerLocalFlag(PLAYER_LOCAL_FLAG_RELEASE_TIMER);
else
RemovePlayerLocalFlag(PLAYER_LOCAL_FLAG_RELEASE_TIMER);
// 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();
}
void Player::OfflineResurrect(ObjectGuid const& guid, CharacterDatabaseTransaction& trans)
{
Corpse::DeleteFromDB(guid, trans);
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG);
stmt->setUInt16(0, uint16(AT_LOGIN_RESURRECT));
stmt->setUInt64(1, guid.GetCounter());
CharacterDatabase.ExecuteOrAppend(trans, stmt);
}
Corpse* Player::CreateCorpse()
{
// prevent the existence of 2 corpses for one player
SpawnCorpseBones();
Corpse* corpse = new Corpse((m_ExtraFlags & PLAYER_EXTRA_PVP_DEATH) ? CORPSE_RESURRECTABLE_PVP : CORPSE_RESURRECTABLE_PVE);
SetPvPDeath(false);
if (!corpse->Create(GetMap()->GenerateLowGuid<HighGuid::Corpse>(), this))
{
delete corpse;
return nullptr;
}
_corpseLocation.WorldRelocate(*this);
uint32 flags = 0;
if (*m_unitData->PvpFlags & UNIT_BYTE2_FLAG_PVP)
flags |= CORPSE_FLAG_PVP;
if (InBattleground() && !InArena())
flags |= CORPSE_FLAG_SKINNABLE; // to be able to remove insignia
if (*m_unitData->PvpFlags & UNIT_BYTE2_FLAG_FFA_PVP)
flags |= CORPSE_FLAG_FFA_PVP;
corpse->SetRace(GetRace());
corpse->SetSex(GetNativeGender());
corpse->SetClass(GetClass());
corpse->SetCustomizations(Trinity::Containers::MakeIteratorPair(m_playerData->Customizations.begin(), m_playerData->Customizations.end()));
corpse->SetFlags(flags);
corpse->SetDisplayId(GetNativeDisplayId());
corpse->SetFactionTemplate(sChrRacesStore.AssertEntry(GetRace())->FactionID);
for (uint8 i = 0; i < EQUIPMENT_SLOT_END; i++)
{
if (m_items[i])
{
uint32 itemDisplayId = m_items[i]->GetDisplayId(this);
uint32 itemInventoryType;
if (ItemEntry const* itemEntry = sItemStore.LookupEntry(m_items[i]->GetVisibleEntry(this)))
itemInventoryType = itemEntry->InventoryType;
else
itemInventoryType = m_items[i]->GetTemplate()->GetInventoryType();
corpse->SetItem(i, itemDisplayId | (itemInventoryType << 24));
}
}
// register for player, but not show
GetMap()->AddCorpse(corpse);
// we do not need to save corpses for BG/arenas
if (!GetMap()->IsBattlegroundOrArena())
corpse->SaveToDB();
return corpse;
}
void Player::SpawnCorpseBones(bool triggerSave /*= true*/)
{
_corpseLocation.WorldRelocate();
if (GetMap()->ConvertCorpseToBones(GetGUID()))
if (triggerSave && !GetSession()->PlayerLogoutWithSave()) // at logout we will already store the player
SaveToDB(); // prevent loading as ghost without corpse
}
Corpse* Player::GetCorpse() const
{
return GetMap()->GetCorpseByPlayer(GetGUID());
}
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++)
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; i++)
if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
DurabilityLoss(pItem, percent);
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->m_itemData->MaxDurability;
if (!pMaxDurability)
return;
percent /= GetTotalAuraMultiplier(SPELL_AURA_MOD_DURABILITY_LOSS);
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++)
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; i++)
if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
DurabilityPointsLoss(pItem, points);
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++)
if (Bag* pBag = static_cast<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->m_itemData->MaxDurability;
int32 pOldDurability = item->m_itemData->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->SetDurability(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 (HasAuraType(SPELL_AURA_PREVENT_DURABILITY_LOSS_FROM_COMBAT))
return;
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
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
for (uint8 i = EQUIPMENT_SLOT_START; i < inventoryEnd; 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->m_itemData->MaxDurability;
if (!maxDurability)
return TotalCost;
uint32 curDurability = item->m_itemData->Durability;
if (cost)
{
uint32 LostDurability = maxDurability - curDurability;
if (LostDurability>0)
{
ItemTemplate const* ditemProto = item->GetTemplate();
DurabilityCostsEntry const* dcost = sDurabilityCostsStore.LookupEntry(ditemProto->GetBaseItemLevel());
if (!dcost)
{
TC_LOG_ERROR("entities.player.items", "Player::DurabilityRepair: Player '%s' (%s) tried to repair an item (ItemID: %u) with invalid item level %u",
GetName().c_str(), GetGUID().ToString().c_str(), ditemProto->GetId(), ditemProto->GetBaseItemLevel());
return TotalCost;
}
uint32 dQualitymodEntryId = (ditemProto->GetQuality() + 1) * 2;
DurabilityQualityEntry const* dQualitymodEntry = sDurabilityQualityStore.LookupEntry(dQualitymodEntryId);
if (!dQualitymodEntry)
{
TC_LOG_ERROR("entities.player.items", "Player::DurabilityRepair: Player '%s' (%s) tried to repair an item (ItemID: %u) with invalid QualitymodEntry %u",
GetName().c_str(), GetGUID().ToString().c_str(), ditemProto->GetId(), dQualitymodEntryId);
return TotalCost;
}
uint32 dmultiplier = 0;
if (ditemProto->GetClass() == ITEM_CLASS_WEAPON)
dmultiplier = dcost->WeaponSubClassCost[ditemProto->GetSubClass()];
else if (ditemProto->GetClass() == ITEM_CLASS_ARMOR)
dmultiplier = dcost->ArmorSubClassCost[ditemProto->GetSubClass()];
uint32 costs = uint32(LostDurability * dmultiplier * double(dQualitymodEntry->Data) * item->GetRepairCostMultiplier());
costs = uint32(costs * discountMod * sWorld->getRate(RATE_REPAIRCOST));
if (costs == 0) //fix for ITEM_QUALITY_ARTIFACT
costs = 1;
if (guildBank)
{
if (!GetGuildId())
{
TC_LOG_DEBUG("entities.player.items", "Player::DurabilityRepair: Player '%s' (%s) tried to repair item in a guild bank but is not member of a guild",
GetName().c_str(), GetGUID().ToString().c_str());
return TotalCost;
}
Guild* guild = sGuildMgr->GetGuildById(GetGuildId());
if (!guild)
return TotalCost;
if (!guild->HandleMemberWithdrawMoney(GetSession(), costs, true))
return TotalCost;
TotalCost = costs;
}
else if (!HasEnoughMoney(uint64(costs)))
{
TC_LOG_DEBUG("entities.player.items", "Player::DurabilityRepair: Player '%s' (%s) has not enough money to repair item",
GetName().c_str(), GetGUID().ToString().c_str());
return TotalCost;
}
else
ModifyMoney(-int64(costs));
}
}
item->SetDurability(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());
bool shouldResurrect = false;
// Such zones are considered unreachable as a ghost and the player must be automatically revived
if ((!IsAlive() && zone && zone->Flags[0] & AREA_FLAG_NEED_FLY) || GetTransport() || GetPositionZ() < GetMap()->GetMinHeight(GetPhaseShift(), GetPositionX(), GetPositionY()))
{
shouldResurrect = true;
SpawnCorpseBones();
}
WorldSafeLocsEntry const* ClosestGrave;
// Special handle for battleground maps
if (Battleground* bg = GetBattleground())
ClosestGrave = bg->GetClosestGraveyard(this);
else
{
if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(GetZoneId()))
ClosestGrave = bf->GetClosestGraveyard(this);
else
ClosestGrave = sObjectMgr->GetClosestGraveyard(*this, GetTeam(), this);
}
// 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->Loc, shouldResurrect ? TELE_REVIVE_AT_TELEPORT : 0);
if (isDead()) // not send if alive, because it used in TeleportTo()
{
WorldPackets::Misc::DeathReleaseLoc packet;
packet.MapID = ClosestGrave->Loc.GetMapId();
packet.Loc = ClosestGrave->Loc;
SendDirectMessage(packet.Write());
}
}
else if (GetPositionZ() < GetMap()->GetMinHeight(GetPhaseShift(), GetPositionX(), GetPositionY()))
TeleportTo(m_homebind);
RemovePlayerFlag(PLAYER_FLAGS_IS_OUT_OF_BOUNDS);
}
bool Player::CanJoinConstantChannelInZone(ChatChannelsEntry const* channel, AreaTableEntry const* zone) const
{
if (channel->Flags & CHANNEL_DBC_FLAG_ZONE_DEP && zone->Flags[0] & AREA_FLAG_ARENA_INSTANCE)
return false;
if ((channel->Flags & CHANNEL_DBC_FLAG_CITY_ONLY) && (!(zone->Flags[0] & AREA_FLAG_SLAVE_CAPITAL)))
return false;
if ((channel->Flags & CHANNEL_DBC_FLAG_GUILD_REQ) && GetGuildId())
return false;
if (channel->Flags & CHANNEL_DBC_FLAG_NO_CLIENT_JOIN)
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
// delete channel if empty
if (ChannelMgr* cMgr = ChannelMgr::ForTeam(GetTeam()))
if (ch->IsConstant())
cMgr->LeftChannel(ch->GetChannelId(), ch->GetZoneEntry());
}
TC_LOG_DEBUG("chat.system", "Player::CleanupChannels: Channels of player '%s' (%s) cleaned up.", GetName().c_str(), GetGUID().ToString().c_str());
}
void Player::UpdateLocalChannels(uint32 newZone)
{
if (GetSession()->PlayerLoading() && !IsBeingTeleportedFar())
return; // The client handles it automatically after loading, but not after teleporting
AreaTableEntry const* current_zone = sAreaTableStore.LookupEntry(newZone);
if (!current_zone)
return;
ChannelMgr* cMgr = ChannelMgr::ForTeam(GetTeam());
if (!cMgr)
return;
for (uint32 i = 0; i < sChatChannelsStore.GetNumRows(); ++i)
{
ChatChannelsEntry const* channelEntry = sChatChannelsStore.LookupEntry(i);
if (!channelEntry)
continue;
if (!(channelEntry->Flags & CHANNEL_DBC_FLAG_INITIAL))
continue;
Channel* usedChannel = nullptr;
for (Channel* channel : m_channels)
{
if (channel->GetChannelId() == i)
{
usedChannel = channel;
break;
}
}
Channel* removeChannel = nullptr;
Channel* joinChannel = nullptr;
bool sendRemove = true;
if (CanJoinConstantChannelInZone(channelEntry, current_zone))
{
if (!(channelEntry->Flags & CHANNEL_DBC_FLAG_GLOBAL))
{
if (channelEntry->Flags & CHANNEL_DBC_FLAG_CITY_ONLY && usedChannel)
continue; // Already on the channel, as city channel names are not changing
joinChannel = cMgr->GetSystemChannel(channelEntry->ID, current_zone);
if (usedChannel)
{
if (joinChannel != usedChannel)
{
removeChannel = usedChannel;
sendRemove = false; // Do not send leave channel, it already replaced at client
}
else
joinChannel = nullptr;
}
}
else
joinChannel = cMgr->GetSystemChannel(channelEntry->ID);
}
else
removeChannel = usedChannel;
if (joinChannel)
joinChannel->JoinChannel(this); // Changed Channel: ... or Joined Channel: ...
if (removeChannel)
{
removeChannel->LeaveChannel(this, sendRemove, true); // Leave old channel
LeftChannel(removeChannel); // Remove from player's channel list
cMgr->LeftChannel(removeChannel->GetChannelId(), removeChannel->GetZoneEntry()); // Delete if empty
}
}
}
void Player::LeaveLFGChannel()
{
for (JoinedChannelsList::iterator i = m_channels.begin(); i != m_channels.end(); ++i)
{
if ((*i)->IsLFG())
{
(*i)->LeaveChannel(this);
break;
}
}
}
void Player::HandleBaseModFlatValue(BaseModGroup modGroup, float amount, bool apply)
{
if (modGroup >= BASEMOD_END)
{
TC_LOG_ERROR("spells", "Player::HandleBaseModValue: Invalid BaseModGroup/BaseModType (%u/%u) for player '%s' (%s)",
modGroup, FLAT_MOD, GetName().c_str(), GetGUID().ToString().c_str());
return;
}
m_auraBaseFlatMod[modGroup] += apply ? amount : -amount;
UpdateBaseModGroup(modGroup);
}
void Player::ApplyBaseModPctValue(BaseModGroup modGroup, float pct)
{
if (modGroup >= BASEMOD_END)
{
TC_LOG_ERROR("spells", "Player::HandleBaseModValue: Invalid BaseModGroup/BaseModType (%u/%u) for player '%s' (%s)",
modGroup, FLAT_MOD, GetName().c_str(), GetGUID().ToString().c_str());
return;
}
AddPct(m_auraBasePctMod[modGroup], pct);
UpdateBaseModGroup(modGroup);
}
void Player::SetBaseModFlatValue(BaseModGroup modGroup, float val)
{
if (m_auraBaseFlatMod[modGroup] == val)
return;
m_auraBaseFlatMod[modGroup] = val;
UpdateBaseModGroup(modGroup);
}
void Player::SetBaseModPctValue(BaseModGroup modGroup, float val)
{
if (m_auraBasePctMod[modGroup] == val)
return;
m_auraBasePctMod[modGroup] = val;
UpdateBaseModGroup(modGroup);
}
void Player::UpdateDamageDoneMods(WeaponAttackType attackType, int32 skipEnchantSlot /*= -1*/)
{
Unit::UpdateDamageDoneMods(attackType, skipEnchantSlot);
UnitMods unitMod;
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:
ABORT();
break;
}
float amount = 0.0f;
Item* item = GetWeaponForAttack(attackType, true);
if (!item)
return;
for (uint8 slot = 0; slot < MAX_ENCHANTMENT_SLOT; ++slot)
{
if (skipEnchantSlot == slot)
continue;
SpellItemEnchantmentEntry const* enchantmentEntry = sSpellItemEnchantmentStore.LookupEntry(item->GetEnchantmentId(EnchantmentSlot(slot)));
if (!enchantmentEntry)
continue;
for (uint8 i = 0; i < MAX_ITEM_ENCHANTMENT_EFFECTS; ++i)
{
switch (enchantmentEntry->Effect[i])
{
case ITEM_ENCHANTMENT_TYPE_DAMAGE:
amount += enchantmentEntry->EffectScalingPoints[i];
break;
case ITEM_ENCHANTMENT_TYPE_TOTEM:
if (GetClass() == CLASS_SHAMAN)
amount += enchantmentEntry->EffectScalingPoints[i] * item->GetTemplate()->GetDelay() / 1000.0f;
break;
default:
break;
}
}
}
HandleStatFlatModifier(unitMod, TOTAL_VALUE, amount, true);
}
void Player::UpdateBaseModGroup(BaseModGroup modGroup)
{
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;
default: break;
}
}
float Player::GetBaseModValue(BaseModGroup modGroup, BaseModType modType) const
{
if (modGroup >= BASEMOD_END || modType >= MOD_END)
{
TC_LOG_ERROR("spells", "Player::GetBaseModValue: Invalid BaseModGroup/BaseModType (%u/%u) for player '%s' (%s)",
modGroup, modType, GetName().c_str(), GetGUID().ToString().c_str());
return 0.0f;
}
return (modType == FLAT_MOD ? m_auraBaseFlatMod[modGroup] : m_auraBasePctMod[modGroup]);
}
float Player::GetTotalBaseModValue(BaseModGroup modGroup) const
{
if (modGroup >= BASEMOD_END)
{
TC_LOG_ERROR("spells", "Player::GetTotalBaseModValue: Invalid BaseModGroup (%u) for player '%s' (%s)",
modGroup, GetName().c_str(), GetGUID().ToString().c_str());
return 0.0f;
}
return m_auraBaseFlatMod[modGroup] * m_auraBasePctMod[modGroup];
}
void Player::GetDodgeFromAgility(float &/*diminishing*/, float &/*nondiminishing*/) const
{
//// Table for base dodge values
//const float dodge_base[MAX_CLASSES] =
//{
// 0.037580f, // Warrior
// 0.036520f, // Paladin
// -0.054500f, // Hunter
// -0.005900f, // Rogue
// 0.031830f, // Priest
// 0.036640f, // DK
// 0.016750f, // Shaman
// 0.034575f, // Mage
// 0.020350f, // Warlock
// 0.0f, // ??
// 0.049510f // 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 >= sGtChanceToMeleeCritStore.GetTableRowCount())
// level = sGtChanceToMeleeCritStore.GetTableRowCount() - 1;
//// Dodge per agility is proportional to crit per agility, which is available from DBC files
//GtChanceToMeleeCritEntry const* dodgeRatio = sGtChanceToMeleeCritStore.EvaluateTable(level - 1, pclass - 1);
//if (dodgeRatio == nullptr || 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) * GetPctModifierValue(UnitMods(UNIT_MOD_STAT_START + STAT_AGILITY), BASE_PCT);
//float bonus_agility = GetStat(STAT_AGILITY) - base_agility;
//// calculate diminishing (green in char screen) and non-diminishing (white) contribution
//diminishing = 100.0f * bonus_agility * dodgeRatio->ratio * crit_to_dodge[pclass-1];
//nondiminishing = 100.0f * (dodge_base[pclass-1] + base_agility * dodgeRatio->ratio * crit_to_dodge[pclass-1]);
}
inline float GetGameTableColumnForCombatRating(GtCombatRatingsEntry const* row, uint32 rating)
{
switch (rating)
{
case CR_AMPLIFY:
return row->Amplify;
case CR_DEFENSE_SKILL:
return row->DefenseSkill;
case CR_DODGE:
return row->Dodge;
case CR_PARRY:
return row->Parry;
case CR_BLOCK:
return row->Block;
case CR_HIT_MELEE:
return row->HitMelee;
case CR_HIT_RANGED:
return row->HitRanged;
case CR_HIT_SPELL:
return row->HitSpell;
case CR_CRIT_MELEE:
return row->CritMelee;
case CR_CRIT_RANGED:
return row->CritRanged;
case CR_CRIT_SPELL:
return row->CritSpell;
case CR_CORRUPTION:
return row->Corruption;
case CR_CORRUPTION_RESISTANCE:
return row->CorruptionResistance;
case CR_SPEED:
return row->Speed;
case CR_RESILIENCE_CRIT_TAKEN:
return row->ResilienceCritTaken;
case CR_RESILIENCE_PLAYER_DAMAGE:
return row->ResiliencePlayerDamage;
case CR_LIFESTEAL:
return row->Lifesteal;
case CR_HASTE_MELEE:
return row->HasteMelee;
case CR_HASTE_RANGED:
return row->HasteRanged;
case CR_HASTE_SPELL:
return row->HasteSpell;
case CR_AVOIDANCE:
return row->Avoidance;
case CR_STURDINESS:
return row->Sturdiness;
case CR_UNUSED_7:
return row->Unused7;
case CR_EXPERTISE:
return row->Expertise;
case CR_ARMOR_PENETRATION:
return row->ArmorPenetration;
case CR_MASTERY:
return row->Mastery;
case CR_PVP_POWER:
return row->PvPPower;
case CR_CLEAVE:
return row->Cleave;
case CR_VERSATILITY_DAMAGE_DONE:
return row->VersatilityDamageDone;
case CR_VERSATILITY_HEALING_DONE:
return row->VersatilityHealingDone;
case CR_VERSATILITY_DAMAGE_TAKEN:
return row->VersatilityDamageTaken;
case CR_UNUSED_12:
return row->Unused12;
default:
break;
}
return 1.0f;
}
float Player::GetRatingMultiplier(CombatRating cr) const
{
GtCombatRatingsEntry const* Rating = sCombatRatingsGameTable.GetRow(GetLevel());
if (!Rating)
return 1.0f;
float value = GetGameTableColumnForCombatRating(Rating, cr);
if (!value)
return 1.0f; // By default use minimum coefficient (not must be called)
return 1.0f / value;
}
float Player::GetRatingBonusValue(CombatRating cr) const
{
float baseResult = ApplyRatingDiminishing(cr, float(m_activePlayerData->CombatRatings[cr]) * GetRatingMultiplier(cr));
if (cr != CR_RESILIENCE_PLAYER_DAMAGE)
return baseResult;
return float(1.0f - pow(0.99f, baseResult)) * 100.0f;
}
float Player::ApplyRatingDiminishing(CombatRating cr, float bonusValue) const
{
uint32 diminishingCurveId = 0;
switch (cr)
{
case CR_DODGE:
diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::DodgeDiminishing);
break;
case CR_PARRY:
diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::ParryDiminishing);
break;
case CR_BLOCK:
diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::BlockDiminishing);
break;
case CR_CRIT_MELEE:
case CR_CRIT_RANGED:
case CR_CRIT_SPELL:
diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::CritDiminishing);
break;
case CR_SPEED:
diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::SpeedDiminishing);
break;
case CR_LIFESTEAL:
diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::LifestealDiminishing);
break;
case CR_HASTE_MELEE:
case CR_HASTE_RANGED:
case CR_HASTE_SPELL:
diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::HasteDiminishing);
break;
case CR_AVOIDANCE:
diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::AvoidanceDiminishing);
break;
case CR_MASTERY:
diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::MasteryDiminishing);
break;
case CR_VERSATILITY_DAMAGE_DONE:
case CR_VERSATILITY_HEALING_DONE:
diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::VersatilityDoneDiminishing);
break;
case CR_VERSATILITY_DAMAGE_TAKEN:
diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::VersatilityTakenDiminishing);
break;
default:
break;
}
if (diminishingCurveId)
return sDB2Manager.GetCurveValueAt(diminishingCurveId, bonusValue);
return bonusValue;
}
float Player::GetExpertiseDodgeOrParryReduction(WeaponAttackType attType) const
{
float baseExpertise = 7.5f;
switch (attType)
{
case BASE_ATTACK:
return baseExpertise + m_activePlayerData->MainhandExpertise / 4.0f;
case OFF_ATTACK:
return baseExpertise + m_activePlayerData->OffhandExpertise / 4.0f;
default:
break;
}
return 0.0f;
}
void Player::ApplyRatingMod(CombatRating combatRating, int32 value, bool apply)
{
m_baseRatingValue[combatRating] += (apply ? value : -value);
UpdateRating(combatRating);
}
void Player::UpdateRating(CombatRating cr)
{
int32 amount = m_baseRatingValue[cr];
for (AuraEffect const* aurEff : GetAuraEffectsByType(SPELL_AURA_MOD_COMBAT_RATING_FROM_COMBAT_RATING))
{
if (aurEff->GetMiscValueB() & (1 << cr))
{
Optional<int16> highestRating;
for (uint8 dependentRating = 0; dependentRating < MAX_COMBAT_RATING; ++dependentRating)
if (aurEff->GetMiscValue() & (1 << dependentRating))
highestRating = std::max(highestRating.value_or(m_baseRatingValue[dependentRating]), m_baseRatingValue[dependentRating]);
if (highestRating)
amount += int32(CalculatePct(*highestRating, aurEff->GetAmount()));
}
}
for (AuraEffect const* aurEff : GetAuraEffectsByType(SPELL_AURA_MOD_RATING_PCT))
if (aurEff->GetMiscValue() & (1 << cr))
amount += int32(CalculatePct(amount, aurEff->GetAmount()));
if (amount < 0)
amount = 0;
uint32 oldRating = m_activePlayerData->CombatRatings[cr];
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::CombatRatings, cr), amount);
bool affectStats = CanModifyStats();
switch (cr)
{
case CR_AMPLIFY:
case CR_DEFENSE_SKILL:
break;
case CR_DODGE:
UpdateDodgePercentage();
break;
case CR_PARRY:
UpdateParryPercentage();
break;
case CR_BLOCK:
UpdateBlockPercentage();
break;
case CR_HIT_MELEE:
UpdateMeleeHitChances();
break;
case CR_HIT_RANGED:
UpdateRangedHitChances();
break;
case CR_HIT_SPELL:
UpdateSpellHitChances();
break;
case CR_CRIT_MELEE:
if (affectStats)
{
UpdateCritPercentage(BASE_ATTACK);
UpdateCritPercentage(OFF_ATTACK);
}
break;
case CR_CRIT_RANGED:
if (affectStats)
UpdateCritPercentage(RANGED_ATTACK);
break;
case CR_CRIT_SPELL:
if (affectStats)
UpdateSpellCritChance();
break;
case CR_CORRUPTION:
case CR_CORRUPTION_RESISTANCE:
UpdateCorruption();
break;
case CR_SPEED:
case CR_RESILIENCE_PLAYER_DAMAGE:
case CR_RESILIENCE_CRIT_TAKEN:
case CR_LIFESTEAL:
break;
case CR_HASTE_MELEE:
case CR_HASTE_RANGED:
case CR_HASTE_SPELL:
{
// explicit affected values
float const multiplier = GetRatingMultiplier(cr);
float const oldVal = ApplyRatingDiminishing(cr, oldRating * multiplier);
float const newVal = ApplyRatingDiminishing(cr, amount * multiplier);
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);
if (GetClass() == CLASS_DEATH_KNIGHT)
UpdateAllRunesRegen();
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;
}
break;
}
case CR_AVOIDANCE:
case CR_STURDINESS:
case CR_UNUSED_7:
break;
case CR_EXPERTISE:
if (affectStats)
{
UpdateExpertise(BASE_ATTACK);
UpdateExpertise(OFF_ATTACK);
}
break;
case CR_ARMOR_PENETRATION:
if (affectStats)
UpdateArmorPenetration(amount);
break;
case CR_MASTERY:
UpdateMastery();
break;
case CR_PVP_POWER:
case CR_CLEAVE:
break;
case CR_VERSATILITY_DAMAGE_DONE:
UpdateVersatilityDamageDone();
break;
case CR_VERSATILITY_HEALING_DONE:
UpdateHealingDonePercentMod();
break;
case CR_VERSATILITY_DAMAGE_TAKEN:
case CR_UNUSED_12:
break;
}
}
void Player::UpdateAllRatings()
{
for (uint8 cr = 0; cr < MAX_COMBAT_RATING; ++cr)
UpdateRating(CombatRating(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->GetDelay())
SetBaseAttackTime(WeaponAttackType(i), proto->GetDelay());
}
else
SetBaseAttackTime(WeaponAttackType(i), BASE_ATTACK_TIME); // If there is no weapon reset attack time to base (might have been changed from forms)
}
}
inline int SkillGainChance(uint32 SkillValue, uint32 GrayLevel, uint32 GreenLevel, uint32 YellowLevel)
{
if (SkillValue >= GrayLevel)
return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_GREY)*10;
if (SkillValue >= GreenLevel)
return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_GREEN)*10;
if (SkillValue >= YellowLevel)
return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_YELLOW)*10;
return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_ORANGE)*10;
}
bool Player::UpdateCraftSkill(uint32 spellid)
{
TC_LOG_DEBUG("entities.player.skills", "Player::UpdateCraftSkill: Player '%s' (%s), SpellID: %d",
GetName().c_str(), GetGUID().ToString().c_str(), spellid);
SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellid);
for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
{
if (_spell_idx->second->SkillupSkillLineID)
{
uint32 SkillValue = GetPureSkillValue(_spell_idx->second->SkillupSkillLineID);
// Alchemy Discoveries here
SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(spellid, DIFFICULTY_NONE);
if (spellEntry && spellEntry->Mechanic == MECHANIC_DISCOVERY)
{
if (uint32 discoveredSpell = GetSkillDiscoverySpell(_spell_idx->second->SkillupSkillLineID, spellid, this))
LearnSpell(discoveredSpell, false);
}
uint32 craft_skill_gain = _spell_idx->second->NumSkillUps * sWorld->getIntConfig(CONFIG_SKILL_GAIN_CRAFTING);
return UpdateSkillPro(_spell_idx->second->SkillupSkillLineID, SkillGainChance(SkillValue,
_spell_idx->second->TrivialSkillLineRankHigh,
(_spell_idx->second->TrivialSkillLineRankHigh + _spell_idx->second->TrivialSkillLineRankLow)/2,
_spell_idx->second->TrivialSkillLineRankLow),
craft_skill_gain);
}
}
return false;
}
bool Player::UpdateGatherSkill(uint32 SkillId, uint32 SkillValue, uint32 RedLevel, uint32 Multiplicator)
{
TC_LOG_DEBUG("entities.player.skills", "Player::UpdateGatherSkill: Player '%s' (%s), SkillID: %u, SkillLevel: %u, RedLevel: %u)",
GetName().c_str(), GetGUID().ToString().c_str(), SkillId, SkillValue, RedLevel);
uint32 gathering_skill_gain = sWorld->getIntConfig(CONFIG_SKILL_GAIN_GATHERING);
// For skinning and Mining chance decrease with level. 1-74 - no decrease, 75-149 - 2 times, 225-299 - 8 times
switch (SkillId)
{
case SKILL_HERBALISM:
case SKILL_HERBALISM_2:
case SKILL_OUTLAND_HERBALISM:
case SKILL_NORTHREND_HERBALISM:
case SKILL_CATACLYSM_HERBALISM:
case SKILL_PANDARIA_HERBALISM:
case SKILL_DRAENOR_HERBALISM:
case SKILL_LEGION_HERBALISM:
case SKILL_KUL_TIRAN_HERBALISM:
case SKILL_JEWELCRAFTING:
case SKILL_INSCRIPTION:
return UpdateSkillPro(SkillId, SkillGainChance(SkillValue, RedLevel+100, RedLevel+50, RedLevel+25)*Multiplicator, gathering_skill_gain);
case SKILL_SKINNING:
case SKILL_SKINNING_2:
case SKILL_OUTLAND_SKINNING:
case SKILL_NORTHREND_SKINNING:
case SKILL_CATACLYSM_SKINNING:
case SKILL_PANDARIA_SKINNING:
case SKILL_DRAENOR_SKINNING:
case SKILL_LEGION_SKINNING:
case SKILL_KUL_TIRAN_SKINNING:
if (sWorld->getIntConfig(CONFIG_SKILL_CHANCE_SKINNING_STEPS) == 0)
return UpdateSkillPro(SkillId, SkillGainChance(SkillValue, RedLevel+100, RedLevel+50, RedLevel+25)*Multiplicator, gathering_skill_gain);
else
return UpdateSkillPro(SkillId, (SkillGainChance(SkillValue, RedLevel+100, RedLevel+50, RedLevel+25)*Multiplicator) >> (SkillValue/sWorld->getIntConfig(CONFIG_SKILL_CHANCE_SKINNING_STEPS)), gathering_skill_gain);
case SKILL_MINING:
case SKILL_MINING_2:
case SKILL_OUTLAND_MINING:
case SKILL_NORTHREND_MINING:
case SKILL_CATACLYSM_MINING:
case SKILL_PANDARIA_MINING:
case SKILL_DRAENOR_MINING:
case SKILL_LEGION_MINING:
case SKILL_KUL_TIRAN_MINING:
if (sWorld->getIntConfig(CONFIG_SKILL_CHANCE_MINING_STEPS) == 0)
return UpdateSkillPro(SkillId, SkillGainChance(SkillValue, RedLevel+100, RedLevel+50, RedLevel+25)*Multiplicator, gathering_skill_gain);
else
return UpdateSkillPro(SkillId, (SkillGainChance(SkillValue, RedLevel+100, RedLevel+50, RedLevel+25)*Multiplicator) >> (SkillValue/sWorld->getIntConfig(CONFIG_SKILL_CHANCE_MINING_STEPS)), gathering_skill_gain);
}
return false;
}
uint8 GetFishingStepsNeededToLevelUp(uint32 SkillValue)
{
// These formulas are guessed to be as close as possible to how the skill difficulty curve for fishing was on Retail.
if (SkillValue < 75)
return 1;
if (SkillValue <= 300)
return SkillValue / 44;
return SkillValue / 31;
}
bool Player::UpdateFishingSkill()
{
TC_LOG_DEBUG("entities.player.skills", "Player::UpdateFishingSkill: Player '%s' (%s)", GetName().c_str(), GetGUID().ToString().c_str());
uint32 SkillValue = GetPureSkillValue(SKILL_FISHING);
if (SkillValue >= GetMaxSkillValue(SKILL_FISHING))
return false;
uint8 stepsNeededToLevelUp = GetFishingStepsNeededToLevelUp(SkillValue);
++m_fishingSteps;
if (m_fishingSteps >= stepsNeededToLevelUp)
{
m_fishingSteps = 0;
uint32 gathering_skill_gain = sWorld->getIntConfig(CONFIG_SKILL_GAIN_GATHERING);
return UpdateSkillPro(SKILL_FISHING, 100*10, gathering_skill_gain);
}
return false;
}
bool Player::UpdateSkillPro(uint16 skillId, int32 chance, uint32 step)
{
// levels sync. with spell requirement for skill levels to learn
// bonus abilities in sSkillLineAbilityStore
// Used only to avoid scan DBC at each skill grow
uint32 const bonusSkillLevels[] = { 75, 150, 225, 300, 375, 450, 525, 600, 700, 850 };
TC_LOG_DEBUG("entities.player.skills", "Player::UpdateSkillPro: Player '%s' (%s), SkillID: %u, Chance: %3.1f%%)",
GetName().c_str(), GetGUID().ToString().c_str(), skillId, chance / 10.0f);
if (!skillId)
return false;
if (chance <= 0) // speedup in 0 chance case
{
TC_LOG_DEBUG("entities.player.skills", "Player::UpdateSkillPro: Player '%s' (%s), SkillID: %u, Chance: %3.1f%% missed",
GetName().c_str(), GetGUID().ToString().c_str(), skillId, chance / 10.0f);
return false;
}
SkillStatusMap::iterator itr = mSkillStatus.find(skillId);
if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
return false;
uint16 value = m_activePlayerData->Skill->SkillRank[itr->second.pos];
uint16 max = m_activePlayerData->Skill->SkillMaxRank[itr->second.pos];
if (!max || !value || value >= max)
return false;
if (irand(1, 1000) > chance)
{
TC_LOG_DEBUG("entities.player.skills", "Player::UpdateSkillPro: Player '%s' (%s), SkillID: %u, Chance: %3.1f%% missed",
GetName().c_str(), GetGUID().ToString().c_str(), skillId, chance / 10.0f);
return false;
}
uint16 new_value = value + step;
if (new_value > max)
new_value = max;
SetSkillRank(itr->second.pos, new_value);
if (itr->second.uState != SKILL_NEW)
itr->second.uState = SKILL_CHANGED;
for (uint32 bsl : bonusSkillLevels)
{
if (value < bsl && new_value >= bsl)
{
LearnSkillRewardedSpells(skillId, new_value);
break;
}
}
UpdateSkillEnchantments(skillId, value, new_value);
UpdateCriteria(CriteriaType::SkillRaised, skillId);
TC_LOG_DEBUG("entities.player.skills", "Player::UpdateSkillPro: Player '%s' (%s), SkillID: %u, Chance: %3.1f%% taken",
GetName().c_str(), GetGUID().ToString().c_str(), skillId, chance / 10.0f);
return true;
}
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 || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
return;
if (talent)
SetSkillPermBonus(itr->second.pos, m_activePlayerData->Skill->SkillPermBonus[itr->second.pos] + val);
else
SetSkillTempBonus(itr->second.pos, m_activePlayerData->Skill->SkillTempBonus[itr->second.pos] + val);
}
void Player::UpdateSkillsForLevel()
{
uint32 maxSkill = GetMaxSkillValueForLevel();
for (SkillStatusMap::iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end(); ++itr)
{
if (itr->second.uState == SKILL_DELETED || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
continue;
uint32 pskill = itr->first;
SkillRaceClassInfoEntry const* rcEntry = sDB2Manager.GetSkillRaceClassInfo(pskill, GetRace(), GetClass());
if (!rcEntry)
continue;
if (GetSkillRangeType(rcEntry) == SKILL_RANGE_LEVEL)
{
if (rcEntry->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE)
SetSkillRank(itr->second.pos, maxSkill);
SetSkillMaxRank(itr->second.pos, maxSkill);
if (itr->second.uState != SKILL_NEW)
itr->second.uState = SKILL_CHANGED;
}
// Update level dependent skillline spells
LearnSkillRewardedSpells(rcEntry->SkillID, m_activePlayerData->Skill->SkillRank[itr->second.pos]);
}
}
void Player::InitializeSkillFields()
{
uint32 i = 0;
for (SkillLineEntry const* skillLine : sSkillLineStore)
{
if (sDB2Manager.GetSkillRaceClassInfo(skillLine->ID, GetRace(), GetClass()))
{
SetSkillLineId(i, skillLine->ID);
SetSkillStartingRank(i, 1);
mSkillStatus.insert(SkillStatusMap::value_type(skillLine->ID, SkillStatusData(i, SKILL_UNCHANGED)));
if (++i >= PLAYER_MAX_SKILLS)
break;
}
}
}
// 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);
// Handle already stored skills
if (itr != mSkillStatus.end())
{
currVal = m_activePlayerData->Skill->SkillRank[itr->second.pos];
// Activate and update skill line
if (newVal)
{
// if skill value is going down, update enchantments before setting the new value
if (newVal < currVal)
UpdateSkillEnchantments(id, currVal, newVal);
// update step
SetSkillStep(itr->second.pos, step);
// update value
SetSkillRank(itr->second.pos, newVal);
SetSkillMaxRank(itr->second.pos, maxVal);
LearnSkillRewardedSpells(id, newVal);
// if skill value is going up, update enchantments after setting the new value
if (newVal > currVal)
{
UpdateSkillEnchantments(id, currVal, newVal);
if (id == SKILL_RIDING)
UpdateMountCapability();
}
UpdateCriteria(CriteriaType::SkillRaised, id);
UpdateCriteria(CriteriaType::AchieveSkillStep, id);
// update skill state
if (itr->second.uState == SKILL_UNCHANGED)
{
if (currVal == 0) // activated skill, mark as new to save into database
itr->second.uState = SKILL_NEW;
else // updated skill, mark as changed to save into database
itr->second.uState = SKILL_CHANGED;
}
}
else if (currVal && !newVal) // Deactivate skill line
{
//remove enchantments needing this skill
UpdateSkillEnchantments(id, currVal, 0);
// clear skill fields
SetSkillStep(itr->second.pos, 0);
SetSkillRank(itr->second.pos, 0);
SetSkillStartingRank(itr->second.pos, 1);
SetSkillMaxRank(itr->second.pos, 0);
SetSkillTempBonus(itr->second.pos, 0);
SetSkillPermBonus(itr->second.pos, 0);
// mark as deleted so the next save will delete the data from the database
if (itr->second.uState != SKILL_NEW)
itr->second.uState = SKILL_DELETED;
else
itr->second.uState = SKILL_UNCHANGED;
// remove all spells that related to this skill
if (std::vector<SkillLineAbilityEntry const*> const* skillLineAbilities = sDB2Manager.GetSkillLineAbilitiesBySkill(id))
for (SkillLineAbilityEntry const* skillLineAbility : *skillLineAbilities)
RemoveSpell(sSpellMgr->GetFirstSpellInChain(skillLineAbility->Spell));
if (std::vector<SkillLineEntry const*> const* childSkillLines = sDB2Manager.GetSkillLinesForParentSkill(id))
for (SkillLineEntry const* childSkillLine : *childSkillLines)
SetSkill(childSkillLine->ID, 0, 0, 0);
// Clear profession lines
if (m_activePlayerData->ProfessionSkillLine[0] == id)
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ProfessionSkillLine, 0), 0);
else if (m_activePlayerData->ProfessionSkillLine[1] == id)
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ProfessionSkillLine, 1), 0);
}
}
else
{
// We are about to learn a skill that has been added outside of normal circumstances (Game Master command, scripts etc.)
uint8 skillSlot = 0;
// Find a free skill slot
for (uint32 i = 0; i < PLAYER_MAX_SKILLS; ++i)
{
if (!m_activePlayerData->Skill->SkillLineID[i])
{
skillSlot = i;
break;
}
}
if (!skillSlot)
{
TC_LOG_ERROR("misc", "Tried to add skill %u but player %s (%s) cannot have additional skills", id, GetName().c_str(), GetGUID().ToString().c_str());
return;
}
SkillLineEntry const* skillEntry = sSkillLineStore.LookupEntry(id);
if (!skillEntry)
{
TC_LOG_ERROR("misc", "Player::SetSkill: Skill (SkillID: %u) not found in SkillLineStore for player '%s' (%s)",
id, GetName().c_str(), GetGUID().ToString().c_str());
return;
}
if (skillEntry->ParentSkillLineID)
{
if (skillEntry->ParentTierIndex > 0)
{
if (SkillRaceClassInfoEntry const* rcEntry = sDB2Manager.GetSkillRaceClassInfo(skillEntry->ParentSkillLineID, GetRace(), GetClass()))
{
if (SkillTiersEntry const* tier = sObjectMgr->GetSkillTier(rcEntry->SkillTierID))
{
uint16 skillval = GetPureSkillValue(skillEntry->ParentSkillLineID);
SetSkill(skillEntry->ParentSkillLineID, skillEntry->ParentTierIndex, std::max<uint16>(skillval, 1), tier->Value[skillEntry->ParentTierIndex - 1]);
}
}
}
}
else
{
// also learn missing child skills at 0 value
if (std::vector<SkillLineEntry const*> const* childSkillLines = sDB2Manager.GetSkillLinesForParentSkill(id))
for (SkillLineEntry const* childSkillLine : *childSkillLines)
if (!HasSkill(childSkillLine->ID))
SetSkill(childSkillLine->ID, 0, 0, 0);
if (skillEntry->CategoryID == SKILL_CATEGORY_PROFESSION)
{
int32 freeProfessionSlot = FindProfessionSlotFor(id);
if (freeProfessionSlot != -1)
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ProfessionSkillLine, freeProfessionSlot), id);
}
}
if (itr == mSkillStatus.end())
SetSkillLineId(skillSlot, id);
SetSkillStep(skillSlot, step);
SetSkillRank(skillSlot, newVal);
SetSkillStartingRank(skillSlot, 1);
SetSkillMaxRank(skillSlot, maxVal);
// apply skill bonuses
SetSkillTempBonus(skillSlot, 0);
SetSkillPermBonus(skillSlot, 0);
UpdateSkillEnchantments(id, 0, newVal);
mSkillStatus.insert(SkillStatusMap::value_type(id, SkillStatusData(skillSlot, SKILL_NEW)));
if (newVal)
{
// temporary bonuses
for (AuraEffect* effect : GetAuraEffectsByType(SPELL_AURA_MOD_SKILL))
if (effect->GetMiscValue() == int32(id))
effect->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true);
for (AuraEffect* effect : GetAuraEffectsByType(SPELL_AURA_MOD_SKILL_2))
if (effect->GetMiscValue() == int32(id))
effect->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true);
// permanent bonuses
for (AuraEffect* effect : GetAuraEffectsByType(SPELL_AURA_MOD_SKILL_TALENT))
if (effect->GetMiscValue() == int32(id))
effect->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true);
// Learn all spells for skill
LearnSkillRewardedSpells(id, newVal);
UpdateCriteria(CriteriaType::SkillRaised, id);
UpdateCriteria(CriteriaType::AchieveSkillStep, id);
}
}
}
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 && m_activePlayerData->Skill->SkillRank[itr->second.pos]);
}
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 || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
return 0;
return m_activePlayerData->Skill->SkillStep[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 || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
return 0;
int32 result = int32(m_activePlayerData->Skill->SkillRank[itr->second.pos]);
result += int32(m_activePlayerData->Skill->SkillTempBonus[itr->second.pos]);
result += int32(m_activePlayerData->Skill->SkillPermBonus[itr->second.pos]);
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 || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
return 0;
int32 result = int32(m_activePlayerData->Skill->SkillMaxRank[itr->second.pos]);
result += int32(m_activePlayerData->Skill->SkillTempBonus[itr->second.pos]);
result += int32(m_activePlayerData->Skill->SkillPermBonus[itr->second.pos]);
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 || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
return 0;
return m_activePlayerData->Skill->SkillMaxRank[itr->second.pos];
}
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 || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
return 0;
int32 result = int32(m_activePlayerData->Skill->SkillRank[itr->second.pos]);
result += int32(m_activePlayerData->Skill->SkillPermBonus[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 || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
return 0;
return m_activePlayerData->Skill->SkillRank[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 || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
return 0;
return m_activePlayerData->Skill->SkillPermBonus[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 || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
return 0;
return m_activePlayerData->Skill->SkillTempBonus[itr->second.pos];
}
void Player::SendActionButtons(uint32 state) const
{
WorldPackets::Spells::UpdateActionButtons packet;
for (auto itr = m_actionButtons.begin(); itr != m_actionButtons.end(); ++itr)
if (itr->second.uState != ACTIONBUTTON_DELETED && itr->first < packet.ActionButtons.size())
packet.ActionButtons[itr->first] = itr->second.packedData;
packet.Reason = state;
SendDirectMessage(packet.Write());
}
bool Player::IsActionButtonDataValid(uint8 button, uint32 action, uint8 type) const
{
if (button >= MAX_ACTION_BUTTONS)
{
TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Action %u not added into button %u for player %s (%s): button must be < %u",
action, button, GetName().c_str(), GetGUID().ToString().c_str(), MAX_ACTION_BUTTONS);
return false;
}
if (action >= MAX_ACTION_BUTTON_ACTION_VALUE)
{
TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Action %u not added into button %u for player %s (%s): action must be < %u",
action, button, GetName().c_str(), GetGUID().ToString().c_str(), MAX_ACTION_BUTTON_ACTION_VALUE);
return false;
}
switch (type)
{
case ACTION_BUTTON_SPELL:
if (!sSpellMgr->GetSpellInfo(action, DIFFICULTY_NONE))
{
TC_LOG_DEBUG("entities.player", "Player::IsActionButtonDataValid: Spell action %u not added into button %u for player %s (%s): spell does not exist. This can be due to a character imported from a different expansion",
action, button, GetName().c_str(), GetGUID().ToString().c_str());
return false;
}
if (!HasSpell(action))
{
TC_LOG_DEBUG("entities.player", "Player::IsActionButtonDataValid: Spell action %u not added into button %u for player %s (%s): player does not known this spell, this can be due to a player changing their talents",
action, button, GetName().c_str(), GetGUID().ToString().c_str());
return false;
}
break;
case ACTION_BUTTON_ITEM:
if (!sObjectMgr->GetItemTemplate(action))
{
TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Item action %u not added into button %u for player %s (%s): item not exist",
action, button, GetName().c_str(), GetGUID().ToString().c_str());
return false;
}
break;
case ACTION_BUTTON_MOUNT:
{
auto mount = sDB2Manager.GetMountById(action);
if (!mount)
{
TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Mount action %u not added into button %u for player %s (%s): mount does not exist",
action, button, GetName().c_str(), GetGUID().ToString().c_str());
return false;
}
if (!HasSpell(mount->SourceSpellID))
{
TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Mount action %u not added into button %u for player %s (%s): Player does not know this mount",
action, button, GetName().c_str(), GetGUID().ToString().c_str());
return false;
}
break;
}
case ACTION_BUTTON_C:
case ACTION_BUTTON_CMACRO:
case ACTION_BUTTON_MACRO:
case ACTION_BUTTON_EQSET:
case ACTION_BUTTON_DROPDOWN:
break;
default:
TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Unknown action type %u", type);
return false; // 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 existing
ActionButton& ab = m_actionButtons[button];
// set data and update to CHANGED if not NEW
ab.SetActionAndType(action, ActionButtonType(type));
TC_LOG_DEBUG("entities.player", "Player::AddActionButton: Player '%s' (%s) added action '%u' (type %u) to button '%u'",
GetName().c_str(), GetGUID().ToString().c_str(), 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
TC_LOG_DEBUG("entities.player", "Player::RemoveActionButton: Player '%s' (%s) removed action button '%u'",
GetName().c_str(), GetGUID().ToString().c_str(), button);
}
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;
}
bool Player::UpdatePosition(float x, float y, float z, float orientation, bool teleport)
{
if (!Unit::UpdatePosition(x, y, z, orientation, teleport))
return false;
//if (movementInfo.flags & MOVEMENTFLAG_MOVING)
// mover->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Moving);
//if (movementInfo.flags & MOVEMENTFLAG_TURNING)
// mover->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Turning);
// group update
if (GetGroup())
SetGroupUpdateFlag(GROUP_UPDATE_FLAG_POSITION);
CheckAreaExploreAndOutdoor();
return true;
}
void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) const
{
if (self)
SendDirectMessage(data);
Trinity::PacketSenderRef sender(data);
Trinity::MessageDistDeliverer<Trinity::PacketSenderRef> notifier(this, sender, dist);
Cell::VisitWorldObjects(this, notifier, dist);
}
void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self, bool own_team_only) const
{
if (self)
SendDirectMessage(data);
Trinity::PacketSenderRef sender(data);
Trinity::MessageDistDeliverer<Trinity::PacketSenderRef> notifier(this, sender, dist, own_team_only);
Cell::VisitWorldObjects(this, notifier, dist);
}
void Player::SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) const
{
if (skipped_rcvr != this)
SendDirectMessage(data);
// we use World::GetMaxVisibleDistance() because i cannot see why not use a distance
// update: replaced by GetMap()->GetVisibilityDistance()
Trinity::PacketSenderRef sender(data);
Trinity::MessageDistDeliverer<Trinity::PacketSenderRef> notifier(this, sender, GetVisibilityRange(), false, skipped_rcvr);
Cell::VisitWorldObjects(this, notifier, GetVisibilityRange());
}
void Player::SendDirectMessage(WorldPacket const* data) const
{
m_session->SendPacket(data);
}
void Player::SendCinematicStart(uint32 CinematicSequenceId) const
{
WorldPackets::Misc::TriggerCinematic packet;
packet.CinematicID = CinematicSequenceId;
SendDirectMessage(packet.Write());
if (CinematicSequencesEntry const* sequence = sCinematicSequencesStore.LookupEntry(CinematicSequenceId))
_cinematicMgr->BeginCinematic(sequence);
}
void Player::SendMovieStart(uint32 movieId)
{
SetMovie(movieId);
WorldPackets::Misc::TriggerMovie packet;
packet.MovieID = movieId;
SendDirectMessage(packet.Write());
}
void Player::CheckAreaExploreAndOutdoor()
{
if (!IsAlive())
return;
if (IsInFlight())
return;
if (sWorld->getBoolConfig(CONFIG_VMAP_INDOOR_CHECK) && !IsOutdoors())
RemoveAurasWithAttribute(SPELL_ATTR0_OUTDOORS_ONLY);
uint32 const areaId = GetAreaId();
if (!areaId)
return;
AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId);
if (!areaEntry)
{
TC_LOG_ERROR("entities.player", "Player '%s' (%s) discovered unknown area (x: %f y: %f z: %f map: %u)",
GetName().c_str(), GetGUID().ToString().c_str(), GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId());
return;
}
uint32 offset = areaEntry->AreaBit / 64;
if (offset >= PLAYER_EXPLORED_ZONES_SIZE)
{
TC_LOG_ERROR("entities.player", "Player::CheckAreaExploreAndOutdoor: Wrong area flag %u in map data for (X: %f Y: %f) point to field PLAYER_EXPLORED_ZONES_1 + %u ( %u must be < %u ).",
areaId, GetPositionX(), GetPositionY(), offset, offset, PLAYER_EXPLORED_ZONES_SIZE);
return;
}
uint64 val = UI64LIT(1) << (areaEntry->AreaBit % 64);
uint64 currFields = m_activePlayerData->ExploredZones[offset];
if (!(currFields & val))
{
SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ExploredZones, offset), val);
UpdateCriteria(CriteriaType::RevealWorldMapOverlay, GetAreaId());
if (Optional<ContentTuningLevels> areaLevels = sDB2Manager.GetContentTuningData(areaEntry->ContentTuningID, m_playerData->CtrOptions->ContentTuningConditionMask))
{
if (IsMaxLevel())
{
SendExplorationExperience(areaId, 0);
}
else
{
int16 areaLevel = std::min(std::max(int16(GetLevel()), areaLevels->MinLevel), areaLevels->MaxLevel);
int32 diff = int32(GetLevel()) - areaLevel;
uint32 XP;
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 < 0)
exploration_percent = 0;
XP = uint32(sObjectMgr->GetBaseXP(areaLevel) * exploration_percent / 100 * sWorld->getRate(RATE_XP_EXPLORE));
}
else
{
XP = uint32(sObjectMgr->GetBaseXP(areaLevel) * sWorld->getRate(RATE_XP_EXPLORE));
}
if (sWorld->getIntConfig(CONFIG_MIN_DISCOVERED_SCALED_XP_RATIO))
{
uint32 minScaledXP = uint32(sObjectMgr->GetBaseXP(areaLevel)*sWorld->getRate(RATE_XP_EXPLORE)) * sWorld->getIntConfig(CONFIG_MIN_DISCOVERED_SCALED_XP_RATIO) / 100;
XP = std::max(minScaledXP, XP);
}
GiveXP(XP, nullptr);
SendExplorationExperience(areaId, XP);
}
TC_LOG_DEBUG("entities.player", "Player '%s' (%s) discovered a new area: %u", GetName().c_str(),GetGUID().ToString().c_str(), areaId);
}
}
}
uint32 Player::TeamForRace(uint8 race)
{
if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race))
{
switch (rEntry->Alliance)
{
case 0: return ALLIANCE;
case 1: return HORDE;
}
TC_LOG_ERROR("entities.player", "Race (%u) has wrong teamid (%u) in DBC: wrong DBC files?", uint32(race), rEntry->Alliance);
}
else
TC_LOG_ERROR("entities.player", "Race (%u) not found in DBC: wrong DBC files?", uint32(race));
return ALLIANCE;
}
TeamId Player::TeamIdForRace(uint8 race)
{
if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race))
return TeamId(rEntry->Alliance);
TC_LOG_ERROR("entities.player", "Race (%u) not found in DBC: wrong DBC files?", race);
return TEAM_NEUTRAL;
}
void Player::SetFactionForRace(uint8 race)
{
m_team = TeamForRace(race);
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
int32 Player::CalculateReputationGain(ReputationSource source, uint32 creatureOrQuestLevel, int32 rep, int32 faction, bool noQuestBonus)
{
bool noBonuses = false;
if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(faction))
if (FriendshipReputationEntry const* friendshipReputation = sFriendshipReputationStore.LookupEntry(factionEntry->FriendshipRepID))
if (friendshipReputation->GetFlags().HasFlag(FriendshipReputationFlags::NoRepGainModifiers))
noBonuses = true;
float percent = 100.0f;
if (!noBonuses)
{
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 ? 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 < Trinity::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 (LFGDungeonsEntry const* dungeon = DB2Manager::GetLfgDungeon(map->GetId(), map->GetDifficultyID()))
if (Optional<ContentTuningLevels> dungeonLevels = sDB2Manager.GetContentTuningData(dungeon->ContentTuningID, m_playerData->CtrOptions->ContentTuningConditionMask))
if (dungeonLevels->TargetLevelMax == int16(GetMaxLevelForExpansion(EXPANSION_WRATH_OF_THE_LICH_KING)))
ChampioningFaction = GetChampioningFaction();
}
uint32 team = GetTeam();
if (Rep->RepFaction1 && (!Rep->TeamDependent || team == ALLIANCE))
{
int32 donerep1 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->GetLevelForTarget(this), Rep->RepValue1, ChampioningFaction ? ChampioningFaction : Rep->RepFaction1);
donerep1 = int32(donerep1 * rate);
FactionEntry const* factionEntry1 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : Rep->RepFaction1);
uint32 current_reputation_rank1 = GetReputationMgr().GetRank(factionEntry1);
if (factionEntry1)
GetReputationMgr().ModifyReputation(factionEntry1, donerep1, current_reputation_rank1 > Rep->ReputationMaxCap1);
}
if (Rep->RepFaction2 && (!Rep->TeamDependent || team == HORDE))
{
int32 donerep2 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->GetLevelForTarget(this), Rep->RepValue2, ChampioningFaction ? ChampioningFaction : Rep->RepFaction2);
donerep2 = int32(donerep2 * rate);
FactionEntry const* factionEntry2 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : Rep->RepFaction2);
uint32 current_reputation_rank2 = GetReputationMgr().GetRank(factionEntry2);
if (factionEntry2)
GetReputationMgr().ModifyReputation(factionEntry2, donerep2, current_reputation_rank2 > Rep->ReputationMaxCap2);
}
}
// Calculate how many reputation points player gain with the quest
void Player::RewardReputation(Quest const* quest)
{
for (uint8 i = 0; i < QUEST_REWARD_REPUTATIONS_COUNT; ++i)
{
if (!quest->RewardFactionId[i])
continue;
FactionEntry const* factionEntry = sFactionStore.LookupEntry(quest->RewardFactionId[i]);
if (!factionEntry)
continue;
int32 rep = 0;
bool noQuestBonus = false;
if (quest->RewardFactionOverride[i])
{
rep = quest->RewardFactionOverride[i] / 100;
noQuestBonus = true;
}
else
{
uint32 row = ((quest->RewardFactionValue[i] < 0) ? 1 : 0) + 1;
if (QuestFactionRewardEntry const* questFactionRewEntry = sQuestFactionRewardStore.LookupEntry(row))
{
uint32 field = abs(quest->RewardFactionValue[i]);
rep = questFactionRewEntry->Difficulty[field];
}
}
if (!rep)
continue;
if (quest->RewardFactionCapIn[i] && rep > 0 && GetReputationMgr().GetRank(factionEntry) >= quest->RewardFactionCapIn[i])
continue;
if (quest->IsDaily())
rep = CalculateReputationGain(REPUTATION_SOURCE_DAILY_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], noQuestBonus);
else if (quest->IsWeekly())
rep = CalculateReputationGain(REPUTATION_SOURCE_WEEKLY_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], noQuestBonus);
else if (quest->IsMonthly())
rep = CalculateReputationGain(REPUTATION_SOURCE_MONTHLY_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], noQuestBonus);
else if (quest->IsRepeatable())
rep = CalculateReputationGain(REPUTATION_SOURCE_REPEATABLE_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], noQuestBonus);
else
rep = CalculateReputationGain(REPUTATION_SOURCE_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], noQuestBonus);
bool noSpillover = (quest->GetRewardReputationMask() & (1 << i)) != 0;
GetReputationMgr().ModifyReputation(factionEntry, rep, false, noSpillover);
}
}
void Player::UpdateHonorFields()
{
/// called when rewarding honor and at each save
time_t now = GameTime::GetGameTime();
time_t today = GameTime::GetGameTime() / DAY * DAY;
if (m_lastHonorUpdateTime < today)
{
time_t yesterday = today - DAY;
// update yesterday's contribution
if (m_lastHonorUpdateTime >= yesterday)
{
// this is the first update today, reset today's contribution
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::YesterdayHonorableKills), m_activePlayerData->TodayHonorableKills);
}
else
{
// no honor/kills yesterday or today, reset
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::YesterdayHonorableKills), 0);
}
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::TodayHonorableKills), 0);
}
m_lastHonorUpdateTime = now;
}
///Calculate the amount of honor gained based on the victim
///and the size of the group for which the honor is divided
///An exact honor value can also be given (overriding the calcs)
bool Player::RewardHonor(Unit* victim, uint32 groupsize, int32 honor, bool pvptoken)
{
// do not reward honor in arenas, but enable onkill spellproc
if (InArena())
{
if (!victim || victim == this || victim->GetTypeId() != TYPEID_PLAYER)
return false;
if (GetBGTeam() == victim->ToPlayer()->GetBGTeam())
return false;
return true;
}
// 'Inactive' this aura prevents the player from gaining honor points and battleground Tokenizer
if (HasAura(SPELL_AURA_PLAYER_INACTIVE))
return false;
ObjectGuid victim_guid;
uint32 victim_rank = 0;
// need call before fields update to have chance move yesterday data to appropriate fields before today data change.
UpdateHonorFields();
// do not reward honor in arenas, but return true to enable onkill spellproc
if (InBattleground() && GetBattleground() && GetBattleground()->isArena())
return true;
// Promote to float for calculations
float honor_f = (float)honor;
if (honor_f <= 0)
{
if (!victim || victim == this || victim->HasAuraType(SPELL_AURA_NO_PVP_CREDIT))
return false;
victim_guid = victim->GetGUID();
if (Player* plrVictim = victim->ToPlayer())
{
if (GetTeam() == plrVictim->GetTeam() && !sWorld->IsFFAPvPRealm())
return false;
uint8 k_level = GetLevel();
uint8 k_grey = Trinity::XP::GetGrayLevel(k_level);
uint8 v_level = victim->GetLevelForTarget(this);
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
// this is all wrong, should be going off PvpTitle, not PlayerTitle
uint32 victim_title = plrVictim->m_playerData->PlayerTitle;
// Get Killer titles, CharTitlesEntry::MaskID
// 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: <rank> 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: <rank> message, only log.
honor_f = std::ceil(Trinity::Honor::hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey));
// count the number of playerkills in one day
ApplyModUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::TodayHonorableKills), 1, true);
// and those in a lifetime
ApplyModUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::LifetimeHonorableKills), 1, true);
UpdateCriteria(CriteriaType::HonorableKills);
UpdateCriteria(CriteriaType::DeliverKillingBlowToClass, victim->GetClass());
UpdateCriteria(CriteriaType::DeliverKillingBlowToRace, victim->GetRace());
UpdateCriteria(CriteriaType::PVPKillInArea, GetAreaId());
UpdateCriteria(CriteriaType::EarnHonorableKill, 1, 0, 0, victim);
UpdateCriteria(CriteriaType::KillPlayer, 1, 0, 0, victim);
}
else
{
if (!victim->ToCreature()->IsRacialLeader())
return false;
honor_f = 100.0f; // ??? need more info
victim_rank = 19; // HK: Leader
}
}
if (victim != nullptr)
{
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 += _restMgr->GetRestBonusFor(REST_TYPE_HONOR, honor_f);
}
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: <dishonored rank>
// victim_rank [5..19] HK: <alliance\horde rank>
// victim_rank [0, 20+] HK: <>
WorldPackets::Combat::PvPCredit data;
data.Honor = honor;
data.OriginalHonor = honor;
data.Target = victim_guid;
data.Rank = victim_rank;
SendDirectMessage(data.Write());
AddHonorXP(honor);
if (InBattleground() && honor > 0)
{
if (Battleground* bg = GetBattleground())
{
bg->UpdatePlayerScore(this, SCORE_BONUS_HONOR, honor, false); //false: prevent looping
}
}
if (sWorld->getBoolConfig(CONFIG_PVP_TOKEN_ENABLE) && pvptoken)
{
if (!victim || victim == this || victim->HasAuraType(SPELL_AURA_NO_PVP_CREDIT))
return true;
if (victim->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::ResetHonorStats()
{
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::TodayHonorableKills), 0);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::YesterdayHonorableKills), 0);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::LifetimeHonorableKills), 0);
}
void Player::_InitHonorLevelOnLoadFromDB(uint32 honor, uint32 honorLevel)
{
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::HonorLevel), honorLevel);
UpdateHonorNextLevel();
AddHonorXP(honor);
}
void Player::RewardPlayerWithRewardPack(uint32 rewardPackID)
{
RewardPlayerWithRewardPack(sRewardPackStore.LookupEntry(rewardPackID));
}
void Player::RewardPlayerWithRewardPack(RewardPackEntry const* rewardPackEntry)
{
if (!rewardPackEntry)
return;
if (CharTitlesEntry const* charTitlesEntry = sCharTitlesStore.LookupEntry(rewardPackEntry->CharTitleID))
SetTitle(charTitlesEntry);
ModifyMoney(rewardPackEntry->Money);
if (std::vector<RewardPackXCurrencyTypeEntry const*> const* rewardCurrencyTypes = sDB2Manager.GetRewardPackCurrencyTypesByRewardID(rewardPackEntry->ID))
for (RewardPackXCurrencyTypeEntry const* currency : *rewardCurrencyTypes)
ModifyCurrency(currency->CurrencyTypeID, currency->Quantity);
if (std::vector<RewardPackXItemEntry const*> const* rewardPackXItems = sDB2Manager.GetRewardPackItemsByRewardID(rewardPackEntry->ID))
for (RewardPackXItemEntry const* rewardPackXItem : *rewardPackXItems)
AddItem(rewardPackXItem->ItemID, rewardPackXItem->ItemQuantity);
}
void Player::AddHonorXP(uint32 xp)
{
uint32 currentHonorXP = m_activePlayerData->Honor;
uint32 nextHonorLevelXP = m_activePlayerData->HonorNextLevel;
uint32 newHonorXP = currentHonorXP + xp;
uint32 honorLevel = GetHonorLevel();
if (xp < 1 || GetLevel() < PLAYER_LEVEL_MIN_HONOR || IsMaxHonorLevel())
return;
while (newHonorXP >= nextHonorLevelXP)
{
newHonorXP -= nextHonorLevelXP;
if (honorLevel < PLAYER_MAX_HONOR_LEVEL)
SetHonorLevel(honorLevel + 1);
honorLevel = GetHonorLevel();
nextHonorLevelXP = m_activePlayerData->HonorNextLevel;
}
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::Honor), IsMaxHonorLevel() ? 0 : newHonorXP);
}
void Player::SetHonorLevel(uint8 level)
{
uint8 oldHonorLevel = GetHonorLevel();
if (level == oldHonorLevel)
return;
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::HonorLevel), level);
UpdateHonorNextLevel();
UpdateCriteria(CriteriaType::HonorLevelIncrease);
}
void Player::UpdateHonorNextLevel()
{
// 5500 at honor level 1
// no idea what between here
// 8800 at honor level ~14 (never goes above 8800)
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::HonorNextLevel), 8800);
}
void Player::_LoadCurrency(PreparedQueryResult result)
{
if (!result)
return;
do
{
Field* fields = result->Fetch();
uint16 currencyID = fields[0].GetUInt16();
CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(currencyID);
if (!currency)
continue;
PlayerCurrency cur;
cur.state = PLAYERCURRENCY_UNCHANGED;
cur.Quantity = fields[1].GetUInt32();
cur.WeeklyQuantity = fields[2].GetUInt32();
cur.TrackedQuantity = fields[3].GetUInt32();
cur.Flags = fields[4].GetUInt8();
_currencyStorage.insert(PlayerCurrenciesMap::value_type(currencyID, cur));
} while (result->NextRow());
}
void Player::_SaveCurrency(CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt;
for (PlayerCurrenciesMap::iterator itr = _currencyStorage.begin(); itr != _currencyStorage.end(); ++itr)
{
CurrencyTypesEntry const* entry = sCurrencyTypesStore.LookupEntry(itr->first);
if (!entry) // should never happen
continue;
switch (itr->second.state)
{
case PLAYERCURRENCY_NEW:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_PLAYER_CURRENCY);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt16(1, itr->first);
stmt->setUInt32(2, itr->second.Quantity);
stmt->setUInt32(3, itr->second.WeeklyQuantity);
stmt->setUInt32(4, itr->second.TrackedQuantity);
stmt->setUInt8(5, itr->second.Flags);
trans->Append(stmt);
break;
case PLAYERCURRENCY_CHANGED:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_PLAYER_CURRENCY);
stmt->setUInt32(0, itr->second.Quantity);
stmt->setUInt32(1, itr->second.WeeklyQuantity);
stmt->setUInt32(2, itr->second.TrackedQuantity);
stmt->setUInt8(3, itr->second.Flags);
stmt->setUInt64(4, GetGUID().GetCounter());
stmt->setUInt16(5, itr->first);
trans->Append(stmt);
break;
default:
break;
}
itr->second.state = PLAYERCURRENCY_UNCHANGED;
}
}
void Player::SendNewCurrency(uint32 id) const
{
PlayerCurrenciesMap::const_iterator itr = _currencyStorage.find(id);
if (itr == _currencyStorage.end())
return;
CurrencyTypesEntry const* entry = sCurrencyTypesStore.LookupEntry(id);
if (!entry) // should never happen
return;
WorldPackets::Misc::SetupCurrency packet;
WorldPackets::Misc::SetupCurrency::Record record;
record.Type = entry->ID;
record.Quantity = itr->second.Quantity;
record.WeeklyQuantity = itr->second.WeeklyQuantity;
record.MaxWeeklyQuantity = GetCurrencyWeekCap(entry);
record.TrackedQuantity = itr->second.TrackedQuantity;
record.Flags = itr->second.Flags;
packet.Data.push_back(record);
SendDirectMessage(packet.Write());
}
void Player::SendCurrencies() const
{
WorldPackets::Misc::SetupCurrency packet;
packet.Data.reserve(_currencyStorage.size());
for (PlayerCurrenciesMap::const_iterator itr = _currencyStorage.begin(); itr != _currencyStorage.end(); ++itr)
{
CurrencyTypesEntry const* entry = sCurrencyTypesStore.LookupEntry(itr->first);
// not send init meta currencies.
if (!entry || entry->CategoryID == CURRENCY_CATEGORY_META_CONQUEST)
continue;
WorldPackets::Misc::SetupCurrency::Record record;
record.Type = entry->ID;
record.Quantity = itr->second.Quantity;
record.WeeklyQuantity = itr->second.WeeklyQuantity;
record.MaxWeeklyQuantity = GetCurrencyWeekCap(entry);
record.TrackedQuantity = itr->second.TrackedQuantity;
record.Flags = itr->second.Flags;
packet.Data.push_back(record);
}
SendDirectMessage(packet.Write());
}
void Player::SendPvpRewards() const
{
//WorldPacket packet(SMSG_REQUEST_PVP_REWARDS_RESPONSE, 24);
//GetSession()->SendPacket(&packet);
}
uint32 Player::GetCurrency(uint32 id) const
{
PlayerCurrenciesMap::const_iterator itr = _currencyStorage.find(id);
if (itr == _currencyStorage.end())
return 0;
return itr->second.Quantity;
}
uint32 Player::GetCurrencyOnWeek(uint32 id) const
{
PlayerCurrenciesMap::const_iterator itr = _currencyStorage.find(id);
if (itr == _currencyStorage.end())
return 0;
return itr->second.WeeklyQuantity;
}
uint32 Player::GetTrackedCurrencyCount(uint32 id) const
{
PlayerCurrenciesMap::const_iterator itr = _currencyStorage.find(id);
if (itr == _currencyStorage.end())
return 0;
return itr->second.TrackedQuantity;
}
bool Player::HasCurrency(uint32 id, uint32 count) const
{
PlayerCurrenciesMap::const_iterator itr = _currencyStorage.find(id);
return itr != _currencyStorage.end() && itr->second.Quantity >= count;
}
void Player::ModifyCurrency(uint32 id, int32 count, bool printLog/* = true*/, bool ignoreMultipliers/* = false*/)
{
if (!count)
return;
CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(id);
ASSERT(currency);
if (!ignoreMultipliers)
count *= GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_CURRENCY_GAIN, id);
// Currency that is immediately converted into reputation with that faction instead
if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(currency->FactionID))
{
if (currency->Flags[0] & CURRENCY_FLAG_HIGH_PRECISION)
count /= 100;
GetReputationMgr().ModifyReputation(factionEntry, count, false, true);
return;
}
if (id == CURRENCY_TYPE_AZERITE)
{
if (count > 0)
if (Item* heartOfAzeroth = GetItemByEntry(ITEM_ID_HEART_OF_AZEROTH, ItemSearchLocation::Everywhere))
heartOfAzeroth->ToAzeriteItem()->GiveXP(uint64(count));
return;
}
uint32 oldTotalCount = 0;
uint32 oldWeekCount = 0;
uint32 oldTrackedCount = 0;
PlayerCurrenciesMap::iterator itr = _currencyStorage.find(id);
if (itr == _currencyStorage.end())
{
PlayerCurrency cur;
cur.state = PLAYERCURRENCY_NEW;
cur.Quantity = 0;
cur.WeeklyQuantity = 0;
cur.TrackedQuantity = 0;
cur.Flags = 0;
_currencyStorage[id] = cur;
itr = _currencyStorage.find(id);
}
else
{
oldTotalCount = itr->second.Quantity;
oldWeekCount = itr->second.WeeklyQuantity;
oldTrackedCount = itr->second.TrackedQuantity;
}
// count can't be more then weekCap if used (weekCap > 0)
uint32 weekCap = GetCurrencyWeekCap(currency);
if (weekCap && count > int32(weekCap))
count = weekCap;
// count can't be more then totalCap if used (totalCap > 0)
uint32 totalCap = GetCurrencyTotalCap(currency);
if (totalCap && count > int32(totalCap))
count = totalCap;
int32 newTrackedCount = int32(oldTrackedCount) + (count > 0 ? count : 0);
if (newTrackedCount < 0)
newTrackedCount = 0;
int32 newTotalCount = int32(oldTotalCount) + count;
if (newTotalCount < 0)
newTotalCount = 0;
int32 newWeekCount = int32(oldWeekCount) + (count > 0 ? count : 0);
if (newWeekCount < 0)
newWeekCount = 0;
// if we get more then weekCap just set to limit
if (weekCap && int32(weekCap) < newWeekCount)
{
newWeekCount = int32(weekCap);
// weekCap - oldWeekCount always >= 0 as we set limit before!
newTotalCount = oldTotalCount + (weekCap - oldWeekCount);
}
// if we get more then totalCap set to maximum;
if (totalCap && int32(totalCap) < newTotalCount)
{
newTotalCount = int32(totalCap);
newWeekCount = weekCap;
}
if (uint32(newTotalCount) != oldTotalCount)
{
if (itr->second.state != PLAYERCURRENCY_NEW)
itr->second.state = PLAYERCURRENCY_CHANGED;
CurrencyChanged(id, newTotalCount - itr->second.Quantity);
itr->second.Quantity = newTotalCount;
itr->second.WeeklyQuantity = newWeekCount;
itr->second.TrackedQuantity = newTrackedCount;
if (count > 0)
UpdateCriteria(CriteriaType::CurrencyGained, id, count);
WorldPackets::Misc::SetCurrency packet;
packet.Type = id;
packet.Quantity = newTotalCount;
packet.SuppressChatLog = !printLog;
packet.WeeklyQuantity = newWeekCount;
packet.TrackedQuantity = newTrackedCount;
packet.Flags = itr->second.Flags;
packet.QuantityChange = count;
SendDirectMessage(packet.Write());
}
}
void Player::SetCreateCurrency(uint32 id, uint32 count, bool /*printLog*/ /*= true*/)
{
PlayerCurrenciesMap::iterator itr = _currencyStorage.find(id);
if (itr == _currencyStorage.end())
{
PlayerCurrency cur;
cur.state = PLAYERCURRENCY_NEW;
cur.Quantity = count;
cur.WeeklyQuantity = 0;
cur.TrackedQuantity = 0;
cur.Flags = 0;
_currencyStorage[id] = cur;
}
}
uint32 Player::GetCurrencyWeekCap(uint32 id) const
{
CurrencyTypesEntry const* entry = sCurrencyTypesStore.LookupEntry(id);
if (!entry)
return 0;
return GetCurrencyWeekCap(entry);
}
void Player::ResetCurrencyWeekCap()
{
for (uint32 arenaSlot = 0; arenaSlot < MAX_ARENA_SLOT; arenaSlot++)
{
if (uint32 arenaTeamId = GetArenaTeamId(arenaSlot))
{
ArenaTeam* arenaTeam = sArenaTeamMgr->GetArenaTeamById(arenaTeamId);
ASSERT(arenaTeam);
arenaTeam->FinishWeek(); // set played this week etc values to 0 in memory, too
arenaTeam->SaveToDB(); // save changes
arenaTeam->NotifyStatsChanged(); // notify the players of the changes
}
}
for (PlayerCurrenciesMap::iterator itr = _currencyStorage.begin(); itr != _currencyStorage.end(); ++itr)
{
itr->second.WeeklyQuantity = 0;
itr->second.state = PLAYERCURRENCY_CHANGED;
}
SendDirectMessage(WorldPackets::Misc::ResetWeeklyCurrency().Write());
}
uint32 Player::GetCurrencyWeekCap(CurrencyTypesEntry const* currency) const
{
return currency->MaxEarnablePerWeek;
}
uint32 Player::GetCurrencyTotalCap(CurrencyTypesEntry const* currency) const
{
uint32 cap = currency->MaxQty;
switch (currency->ID)
{
case CURRENCY_TYPE_APEXIS_CRYSTALS:
{
uint32 apexiscap = sWorld->getIntConfig(CONFIG_CURRENCY_MAX_APEXIS_CRYSTALS);
if (apexiscap > 0)
cap = apexiscap;
break;
}
case CURRENCY_TYPE_JUSTICE_POINTS:
{
uint32 justicecap = sWorld->getIntConfig(CONFIG_CURRENCY_MAX_JUSTICE_POINTS);
if (justicecap > 0)
cap = justicecap;
break;
}
}
return cap;
}
void Player::SetInGuild(ObjectGuid::LowType guildId)
{
if (guildId)
{
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::GuildGUID), ObjectGuid::Create<HighGuid::Guild>(guildId));
AddPlayerFlag(PLAYER_FLAGS_GUILD_LEVEL_ENABLED);
}
else
{
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::GuildGUID), ObjectGuid::Empty);
RemovePlayerFlag(PLAYER_FLAGS_GUILD_LEVEL_ENABLED);
}
sCharacterCache->UpdateCharacterGuildId(GetGUID(), guildId);
}
void Player::SetArenaTeamInfoField(uint8 /*slot*/, ArenaTeamInfoType /*type*/, uint32 /*value*/)
{
}
void Player::SetInArenaTeam(uint32 ArenaTeamId, uint8 slot, uint8 type)
{
SetArenaTeamInfoField(slot, ARENA_TEAM_ID, ArenaTeamId);
SetArenaTeamInfoField(slot, ARENA_TEAM_TYPE, type);
}
uint32 Player::GetZoneIdFromDB(ObjectGuid guid)
{
ObjectGuid::LowType guidLow = guid.GetCounter();
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_ZONE);
stmt->setUInt64(0, guidLow);
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (!result)
return 0;
Field* fields = result->Fetch();
uint32 zone = fields[0].GetUInt16();
if (!zone)
{
// stored zone is zero, use generic and slow zone detection
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_POSITION_XYZ);
stmt->setUInt64(0, guidLow);
result = CharacterDatabase.Query(stmt);
if (!result)
return 0;
fields = result->Fetch();
uint32 map = fields[0].GetUInt16();
float posx = fields[1].GetFloat();
float posy = fields[2].GetFloat();
float posz = fields[3].GetFloat();
if (!sMapStore.LookupEntry(map))
return 0;
zone = sMapMgr->GetZoneId(PhasingHandler::GetEmptyPhaseShift(), map, posx, posy, posz);
if (zone > 0)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ZONE);
stmt->setUInt16(0, uint16(zone));
stmt->setUInt64(1, guidLow);
CharacterDatabase.Execute(stmt);
}
}
return zone;
}
void Player::UpdateArea(uint32 newArea)
{
// FFA_PVP flags are area and not zone id dependent
// so apply them accordingly
m_areaUpdateId = newArea;
AreaTableEntry const* area = sAreaTableStore.LookupEntry(newArea);
bool oldFFAPvPArea = pvpInfo.IsInFFAPvPArea;
pvpInfo.IsInFFAPvPArea = area && (area->Flags[0] & AREA_FLAG_ARENA);
UpdatePvPState(true);
// check if we were in ffa arena and we left
if (oldFFAPvPArea && !pvpInfo.IsInFFAPvPArea)
ValidateAttackersAndOwnTarget();
PhasingHandler::OnAreaChange(this);
UpdateAreaDependentAuras(newArea);
if (IsAreaThatActivatesPvpTalents(newArea))
EnablePvpRules();
else
DisablePvpRules();
// previously this was in UpdateZone (but after UpdateArea) so nothing will break
pvpInfo.IsInNoPvPArea = false;
if (area && area->IsSanctuary()) // in sanctuary
{
AddPvpFlag(UNIT_BYTE2_FLAG_SANCTUARY);
pvpInfo.IsInNoPvPArea = true;
if (!duel && GetCombatManager().HasPvPCombat())
CombatStopWithPets();
}
else
RemovePvpFlag(UNIT_BYTE2_FLAG_SANCTUARY);
uint32 const areaRestFlag = (GetTeam() == ALLIANCE) ? AREA_FLAG_REST_ZONE_ALLIANCE : AREA_FLAG_REST_ZONE_HORDE;
if (area && area->Flags[0] & areaRestFlag)
_restMgr->SetRestFlag(REST_FLAG_IN_FACTION_AREA);
else
_restMgr->RemoveRestFlag(REST_FLAG_IN_FACTION_AREA);
PushQuests();
UpdateCriteria(CriteriaType::EnterTopLevelArea, newArea);
UpdateMountCapability();
}
void Player::UpdateZone(uint32 newZone, uint32 newArea)
{
if (!IsInWorld())
return;
uint32 const oldZone = m_zoneUpdateId;
m_zoneUpdateId = newZone;
m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL;
GetMap()->UpdatePlayerZoneStats(oldZone, newZone);
// call leave script hooks immedately (before updating flags)
if (oldZone != newZone)
{
sOutdoorPvPMgr->HandlePlayerLeaveZone(this, oldZone);
sBattlefieldMgr->HandlePlayerLeaveZone(this, oldZone);
}
// group update
if (GetGroup())
{
SetGroupUpdateFlag(GROUP_UPDATE_FULL);
if (Pet* pet = GetPet())
pet->SetGroupUpdateFlag(GROUP_UPDATE_PET_FULL);
}
// zone changed, so area changed as well, update it.
UpdateArea(newArea);
AreaTableEntry const* zone = sAreaTableStore.LookupEntry(newZone);
if (!zone)
return;
if (sWorld->getBoolConfig(CONFIG_WEATHER))
GetMap()->GetOrGenerateZoneDefaultWeather(newZone);
GetMap()->SendZoneDynamicInfo(newZone, this);
UpdateWarModeAuras();
UpdateHostileAreaState(zone);
if (zone->Flags[0] & AREA_FLAG_CAPITAL) // Is in a capital city
{
if (!pvpInfo.IsHostile || zone->IsSanctuary())
_restMgr->SetRestFlag(REST_FLAG_IN_CITY);
pvpInfo.IsInNoPvPArea = true;
}
else
_restMgr->RemoveRestFlag(REST_FLAG_IN_CITY);
UpdatePvPState();
// remove items with area/map limitations (delete only for alive player to allow back in ghost mode)
// if player resurrected at teleport this will be applied in resurrect code
if (IsAlive())
DestroyZoneLimitedItem(true, newZone);
// check some item equip limitations (in result lost CanTitanGrip at talent reset, for example)
AutoUnequipOffhandIfNeed();
// recent client version not send leave/join channel packets for built-in local channels
UpdateLocalChannels(newZone);
UpdateZoneDependentAuras(newZone);
// call enter script hooks after everyting else has processed
sScriptMgr->OnPlayerUpdateZone(this, newZone, newArea);
if (oldZone != newZone)
{
sOutdoorPvPMgr->HandlePlayerEnterZone(this, newZone);
sBattlefieldMgr->HandlePlayerEnterZone(this, newZone);
SendInitWorldStates(newZone, newArea); // only if really enters to new zone, not just area change, works strange...
if (Guild* guild = GetGuild())
guild->UpdateMemberData(this, GUILD_MEMBER_DATA_ZONEID, newZone);
}
}
void Player::UpdateHostileAreaState(AreaTableEntry const* area)
{
ZonePVPTypeOverride overrideZonePvpType = GetOverrideZonePVPType();
pvpInfo.IsInHostileArea = false;
if (area->IsSanctuary()) // sanctuary and arena cannot be overriden
pvpInfo.IsInHostileArea = false;
else if (area->Flags[0] & AREA_FLAG_ARENA)
pvpInfo.IsInHostileArea = true;
else if (overrideZonePvpType == ZonePVPTypeOverride::None)
{
if (area)
{
if (InBattleground() || area->Flags[0] & AREA_FLAG_COMBAT || (area->PvpCombatWorldStateID != -1 && sWorld->getWorldState(area->PvpCombatWorldStateID) != 0))
pvpInfo.IsInHostileArea = true;
else if (IsWarModeLocalActive() || (area->Flags[0] & AREA_FLAG_UNK3))
{
if (area->Flags[0] & AREA_FLAG_CONTESTED_AREA)
pvpInfo.IsInHostileArea = IsWarModeLocalActive();
else
{
FactionTemplateEntry const* factionTemplate = GetFactionTemplateEntry();
if (!factionTemplate || factionTemplate->FriendGroup & area->FactionGroupMask)
pvpInfo.IsInHostileArea = false; // friend area are considered hostile if war mode is active
else if (factionTemplate->EnemyGroup & area->FactionGroupMask)
pvpInfo.IsInHostileArea = true;
else
pvpInfo.IsInHostileArea = sWorld->IsPvPRealm();
}
}
}
}
else
{
switch (overrideZonePvpType)
{
case ZonePVPTypeOverride::Friendly:
pvpInfo.IsInHostileArea = false;
break;
case ZonePVPTypeOverride::Hostile:
case ZonePVPTypeOverride::Contested:
case ZonePVPTypeOverride::Combat:
pvpInfo.IsInHostileArea = true;
break;
default:
break;
}
}
// Treat players having a quest flagging for PvP as always in hostile area
pvpInfo.IsHostile = pvpInfo.IsInHostileArea || HasPvPForcingQuest() || IsWarModeLocalActive();
}
//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 = m_playerData->DuelArbiter;
GameObject* obj = GetMap()->GetGameObject(duelFlagGUID);
if (!obj)
return;
if (!duel->OutOfBoundsTime)
{
if (!IsWithinDistInMap(obj, 50))
{
duel->OutOfBoundsTime = currTime + 10;
SendDirectMessage(WorldPackets::Duel::DuelOutOfBounds().Write());
}
}
else
{
if (IsWithinDistInMap(obj, 40))
{
duel->OutOfBoundsTime = 0;
SendDirectMessage(WorldPackets::Duel::DuelInBounds().Write());
}
else if (currTime >= duel->OutOfBoundsTime)
DuelComplete(DUEL_FLED);
}
}
bool Player::IsOutdoorPvPActive() const
{
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;
TC_LOG_DEBUG("entities.unit", "Player::DuelComplete: Player '%s' (%s), Opponent: '%s' (%s)",
GetName().c_str(), GetGUID().ToString().c_str(), opponent->GetName().c_str(), opponent->GetGUID().ToString().c_str());
WorldPackets::Duel::DuelComplete duelCompleted;
duelCompleted.Started = type != DUEL_INTERRUPTED;
SendDirectMessage(duelCompleted.Write());
if (opponent->GetSession())
opponent->SendDirectMessage(duelCompleted.GetRawPacket());
if (type != DUEL_INTERRUPTED)
{
WorldPackets::Duel::DuelWinner duelWinner;
duelWinner.BeatenName = (type == DUEL_WON ? opponent->GetName() : GetName());
duelWinner.WinnerName = (type == DUEL_WON ? GetName() : opponent->GetName());
duelWinner.BeatenVirtualRealmAddress = GetVirtualRealmAddress();
duelWinner.WinnerVirtualRealmAddress = GetVirtualRealmAddress();
duelWinner.Fled = type != DUEL_WON;
SendMessageToSet(duelWinner.Write(), true);
}
opponent->DisablePvpRules();
DisablePvpRules();
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 (GetTeam() == opponent->GetTeam())
{
AttackStop();
opponent->AttackStop();
}
else
{
if (!IsPvP())
AttackStop();
if (!opponent->IsPvP())
opponent->AttackStop();
}
break;
case DUEL_WON:
UpdateCriteria(CriteriaType::LoseDuel, 1);
opponent->UpdateCriteria(CriteriaType::WinDuel, 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(m_playerData->DuelArbiter);
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
ClearComboPoints();
opponent->ClearComboPoints();
//cleanups
SetDuelArbiter(ObjectGuid::Empty);
SetDuelTeam(0);
opponent->SetDuelArbiter(ObjectGuid::Empty);
opponent->SetDuelTeam(0);
opponent->duel.reset(nullptr);
duel.reset(nullptr);
}
//---------------------------------------------------------//
void Player::_ApplyItemMods(Item* item, uint8 slot, bool apply, bool updateItemAuras /*= true*/)
{
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;
TC_LOG_DEBUG("entities.player.items", "Player::_ApplyItemMods: Applying mods for item %s", item->GetGUID().ToString().c_str());
if (item->GetSocketColor(0)) //only (un)equipping of items with sockets can influence metagems, so no need to waste time with normal items
CorrectMetaGemEnchants(slot, apply);
_ApplyItemBonuses(item, slot, apply);
ApplyItemEquipSpell(item, apply);
if (updateItemAuras)
{
ApplyItemDependentAuras(item, apply);
WeaponAttackType const attackType = Player::GetAttackBySlot(slot, item->GetTemplate()->GetInventoryType());
if (attackType != MAX_ATTACK)
UpdateWeaponDependentAuras(attackType);
}
ApplyArtifactPowers(item, apply);
ApplyAzeritePowers(item, apply);
ApplyEnchantment(item, apply);
TC_LOG_DEBUG("entities.player.items", "Player::_ApplyItemMods: completed");
}
void Player::_ApplyItemBonuses(Item* item, uint8 slot, bool apply)
{
ItemTemplate const* proto = item->GetTemplate();
if (slot >= INVENTORY_SLOT_BAG_END || !proto)
return;
uint32 itemLevel = item->GetItemLevel(this);
float combatRatingMultiplier = 1.0f;
if (GtCombatRatingsMultByILvl const* ratingMult = sCombatRatingsMultByILvlGameTable.GetRow(itemLevel))
combatRatingMultiplier = GetIlvlStatMultiplier(ratingMult, proto->GetInventoryType());
for (uint8 i = 0; i < MAX_ITEM_PROTO_STATS; ++i)
{
int32 statType = item->GetItemStatType(i);
if (statType == -1)
continue;
float val = item->GetItemStatValue(i, this);
if (val == 0)
continue;
switch (statType)
{
case ITEM_MOD_MANA:
HandleStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, float(val), apply);
break;
case ITEM_MOD_HEALTH: // modify HP
HandleStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, float(val), apply);
break;
case ITEM_MOD_AGILITY: // modify agility
HandleStatFlatModifier(UNIT_MOD_STAT_AGILITY, BASE_VALUE, float(val), apply);
UpdateStatBuffMod(STAT_AGILITY);
break;
case ITEM_MOD_STRENGTH: //modify strength
HandleStatFlatModifier(UNIT_MOD_STAT_STRENGTH, BASE_VALUE, float(val), apply);
UpdateStatBuffMod(STAT_STRENGTH);
break;
case ITEM_MOD_INTELLECT: //modify intellect
HandleStatFlatModifier(UNIT_MOD_STAT_INTELLECT, BASE_VALUE, float(val), apply);
UpdateStatBuffMod(STAT_INTELLECT);
break;
// case ITEM_MOD_SPIRIT: //modify spirit
// HandleStatModifier(UNIT_MOD_STAT_SPIRIT, BASE_VALUE, float(val), apply);
// ApplyStatBuffMod(STAT_SPIRIT, CalculatePct(val, GetModifierValue(UNIT_MOD_STAT_SPIRIT, BASE_PCT_EXCLUDE_CREATE)), apply);
// break;
case ITEM_MOD_STAMINA: //modify stamina
{
if (GtStaminaMultByILvl const* staminaMult = sStaminaMultByILvlGameTable.GetRow(itemLevel))
val = int32(val * GetIlvlStatMultiplier(staminaMult, proto->GetInventoryType()));
HandleStatFlatModifier(UNIT_MOD_STAT_STAMINA, BASE_VALUE, float(val), apply);
UpdateStatBuffMod(STAT_STAMINA);
break;
}
case ITEM_MOD_DEFENSE_SKILL_RATING:
ApplyRatingMod(CR_DEFENSE_SKILL, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_DODGE_RATING:
ApplyRatingMod(CR_DODGE, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_PARRY_RATING:
ApplyRatingMod(CR_PARRY, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_BLOCK_RATING:
ApplyRatingMod(CR_BLOCK, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_HIT_MELEE_RATING:
ApplyRatingMod(CR_HIT_MELEE, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_HIT_RANGED_RATING:
ApplyRatingMod(CR_HIT_RANGED, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_HIT_SPELL_RATING:
ApplyRatingMod(CR_HIT_SPELL, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_CRIT_MELEE_RATING:
ApplyRatingMod(CR_CRIT_MELEE, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_CRIT_RANGED_RATING:
ApplyRatingMod(CR_CRIT_RANGED, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_CRIT_SPELL_RATING:
ApplyRatingMod(CR_CRIT_SPELL, int32(val * combatRatingMultiplier), 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_RESILIENCE_PLAYER_DAMAGE, 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 * combatRatingMultiplier), apply);
ApplyRatingMod(CR_HIT_RANGED, int32(val * combatRatingMultiplier), apply);
ApplyRatingMod(CR_HIT_SPELL, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_CRIT_RATING:
ApplyRatingMod(CR_CRIT_MELEE, int32(val * combatRatingMultiplier), apply);
ApplyRatingMod(CR_CRIT_RANGED, int32(val * combatRatingMultiplier), apply);
ApplyRatingMod(CR_CRIT_SPELL, int32(val * combatRatingMultiplier), apply);
break;
// case ITEM_MOD_HIT_TAKEN_RATING: // Unused since 3.3.5
// 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: // Unused since 3.3.5
// 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_RESILIENCE_RATING:
ApplyRatingMod(CR_RESILIENCE_PLAYER_DAMAGE, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_HASTE_RATING:
ApplyRatingMod(CR_HASTE_MELEE, int32(val * combatRatingMultiplier), apply);
ApplyRatingMod(CR_HASTE_RANGED, int32(val * combatRatingMultiplier), apply);
ApplyRatingMod(CR_HASTE_SPELL, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_EXPERTISE_RATING:
ApplyRatingMod(CR_EXPERTISE, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_ATTACK_POWER:
HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(val), apply);
HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(val), apply);
break;
case ITEM_MOD_RANGED_ATTACK_POWER:
HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(val), apply);
break;
case ITEM_MOD_VERSATILITY:
ApplyRatingMod(CR_VERSATILITY_DAMAGE_DONE, int32(val * combatRatingMultiplier), apply);
ApplyRatingMod(CR_VERSATILITY_DAMAGE_TAKEN, int32(val * combatRatingMultiplier), apply);
ApplyRatingMod(CR_VERSATILITY_HEALING_DONE, int32(val * combatRatingMultiplier), 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_MASTERY_RATING:
ApplyRatingMod(CR_MASTERY, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_EXTRA_ARMOR:
HandleStatFlatModifier(UNIT_MOD_ARMOR, TOTAL_VALUE, float(val), apply);
break;
case ITEM_MOD_FIRE_RESISTANCE:
HandleStatFlatModifier(UNIT_MOD_RESISTANCE_FIRE, BASE_VALUE, float(val), apply);
break;
case ITEM_MOD_FROST_RESISTANCE:
HandleStatFlatModifier(UNIT_MOD_RESISTANCE_FROST, BASE_VALUE, float(val), apply);
break;
case ITEM_MOD_HOLY_RESISTANCE:
HandleStatFlatModifier(UNIT_MOD_RESISTANCE_HOLY, BASE_VALUE, float(val), apply);
break;
case ITEM_MOD_SHADOW_RESISTANCE:
HandleStatFlatModifier(UNIT_MOD_RESISTANCE_SHADOW, BASE_VALUE, float(val), apply);
break;
case ITEM_MOD_NATURE_RESISTANCE:
HandleStatFlatModifier(UNIT_MOD_RESISTANCE_NATURE, BASE_VALUE, float(val), apply);
break;
case ITEM_MOD_ARCANE_RESISTANCE:
HandleStatFlatModifier(UNIT_MOD_RESISTANCE_ARCANE, BASE_VALUE, float(val), apply);
break;
case ITEM_MOD_PVP_POWER:
ApplyRatingMod(CR_PVP_POWER, int32(val), apply);
break;
case ITEM_MOD_CORRUPTION:
ApplyRatingMod(CR_CORRUPTION, int32(val), apply);
break;
case ITEM_MOD_CORRUPTION_RESISTANCE:
ApplyRatingMod(CR_CORRUPTION_RESISTANCE, int32(val), apply);
break;
case ITEM_MOD_CR_SPEED:
ApplyRatingMod(CR_SPEED, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_CR_LIFESTEAL:
ApplyRatingMod(CR_LIFESTEAL, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_CR_AVOIDANCE:
ApplyRatingMod(CR_AVOIDANCE, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_CR_STURDINESS:
ApplyRatingMod(CR_STURDINESS, int32(val * combatRatingMultiplier), apply);
break;
case ITEM_MOD_AGI_STR_INT:
HandleStatFlatModifier(UNIT_MOD_STAT_AGILITY, BASE_VALUE, float(val), apply);
HandleStatFlatModifier(UNIT_MOD_STAT_STRENGTH, BASE_VALUE, float(val), apply);
HandleStatFlatModifier(UNIT_MOD_STAT_INTELLECT, BASE_VALUE, float(val), apply);
UpdateStatBuffMod(STAT_AGILITY);
UpdateStatBuffMod(STAT_STRENGTH);
UpdateStatBuffMod(STAT_INTELLECT);
break;
case ITEM_MOD_AGI_STR:
HandleStatFlatModifier(UNIT_MOD_STAT_AGILITY, BASE_VALUE, float(val), apply);
HandleStatFlatModifier(UNIT_MOD_STAT_STRENGTH, BASE_VALUE, float(val), apply);
UpdateStatBuffMod(STAT_AGILITY);
UpdateStatBuffMod(STAT_STRENGTH);
break;
case ITEM_MOD_AGI_INT:
HandleStatFlatModifier(UNIT_MOD_STAT_AGILITY, BASE_VALUE, float(val), apply);
HandleStatFlatModifier(UNIT_MOD_STAT_INTELLECT, BASE_VALUE, float(val), apply);
UpdateStatBuffMod(STAT_AGILITY);
UpdateStatBuffMod(STAT_INTELLECT);
break;
case ITEM_MOD_STR_INT:
HandleStatFlatModifier(UNIT_MOD_STAT_STRENGTH, BASE_VALUE, float(val), apply);
HandleStatFlatModifier(UNIT_MOD_STAT_INTELLECT, BASE_VALUE, float(val), apply);
UpdateStatBuffMod(STAT_STRENGTH);
UpdateStatBuffMod(STAT_INTELLECT);
break;
}
}
if (uint32 armor = proto->GetArmor(itemLevel))
{
HandleStatFlatModifier(UNIT_MOD_ARMOR, TOTAL_VALUE, float(armor), apply);
if (proto->GetClass() == ITEM_CLASS_ARMOR && proto->GetSubClass() == ITEM_SUBCLASS_ARMOR_SHIELD)
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ShieldBlock), apply ? int32(armor * 2.5f) : 0);
}
WeaponAttackType attType = Player::GetAttackBySlot(slot, proto->GetInventoryType());
if (attType != MAX_ATTACK)
_ApplyWeaponDamage(slot, item, apply);
}
void Player::_ApplyWeaponDamage(uint8 slot, Item* item, bool apply)
{
ItemTemplate const* proto = item->GetTemplate();
WeaponAttackType attType = Player::GetAttackBySlot(slot, proto->GetInventoryType());
if (!IsInFeralForm() && apply && !CanUseAttackType(attType))
return;
float damage = 0.0f;
uint32 itemLevel = item->GetItemLevel(this);
float minDamage, maxDamage;
proto->GetDamage(itemLevel, minDamage, maxDamage);
if (minDamage > 0)
{
damage = apply ? minDamage : BASE_MINDAMAGE;
SetBaseWeaponDamage(attType, MINDAMAGE, damage);
}
if (maxDamage > 0)
{
damage = apply ? maxDamage : BASE_MAXDAMAGE;
SetBaseWeaponDamage(attType, MAXDAMAGE, damage);
}
SpellShapeshiftFormEntry const* shapeshift = sSpellShapeshiftFormStore.LookupEntry(GetShapeshiftForm());
if (proto->GetDelay() && !(shapeshift && shapeshift->CombatRoundTime))
SetBaseAttackTime(attType, apply ? proto->GetDelay() : BASE_ATTACK_TIME);
int32 weaponBasedAttackPower = apply ? int32(proto->GetDPS(itemLevel) * 6.0f) : 0;
switch (attType)
{
case BASE_ATTACK:
SetMainHandWeaponAttackPower(weaponBasedAttackPower);
break;
case OFF_ATTACK:
SetOffHandWeaponAttackPower(weaponBasedAttackPower);
break;
case RANGED_ATTACK:
SetRangedWeaponAttackPower(weaponBasedAttackPower);
break;
default:
break;
}
if (CanModifyStats() && (damage || proto->GetDelay()))
UpdateDamagePhysical(attType);
}
SpellSchoolMask Player::GetMeleeDamageSchoolMask(WeaponAttackType attackType /*= BASE_ATTACK*/) const
{
if (Item const* weapon = GetWeaponForAttack(attackType, true))
return SpellSchoolMask(1 << weapon->GetTemplate()->GetDamageType());
return SPELL_SCHOOL_MASK_NORMAL;
}
void Player::CastAllObtainSpells()
{
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < inventoryEnd; ++slot)
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
ApplyItemObtainSpells(item, true);
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
{
Bag* bag = GetBagByPos(i);
if (!bag)
continue;
for (uint32 slot = 0; slot < bag->GetBagSize(); ++slot)
if (Item* item = bag->GetItemByPos(slot))
ApplyItemObtainSpells(item, true);
}
}
void Player::ApplyItemObtainSpells(Item* item, bool apply)
{
if (item->GetTemplate()->HasFlag(ITEM_FLAG_LEGACY))
return;
for (ItemEffectEntry const* effect : item->GetEffects())
{
if (effect->TriggerType != ITEM_SPELLTRIGGER_ON_OBTAIN) // On obtain trigger
continue;
int32 const spellId = effect->SpellID;
if (spellId <= 0)
continue;
if (apply)
{
if (!HasAura(spellId))
CastSpell(this, spellId, item);
}
else
RemoveAurasDueToSpell(spellId);
}
}
// this one rechecks weapon auras and stores them in BaseModGroup container
// needed for things like axe specialization applying only to axe weapons in case of dual-wield
void Player::UpdateWeaponDependentCritAuras(WeaponAttackType attackType)
{
BaseModGroup modGroup;
switch (attackType)
{
case BASE_ATTACK:
modGroup = CRIT_PERCENTAGE;
break;
case OFF_ATTACK:
modGroup = OFFHAND_CRIT_PERCENTAGE;
break;
case RANGED_ATTACK:
modGroup = RANGED_CRIT_PERCENTAGE;
break;
default:
ABORT();
break;
}
float amount = 0.0f;
amount += GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT, std::bind(&Unit::CheckAttackFitToAuraRequirement, this, attackType, std::placeholders::_1));
// these auras don't have item requirement (only Combat Expertise in 3.3.5a)
amount += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT);
SetBaseModFlatValue(modGroup, amount);
}
void Player::UpdateAllWeaponDependentCritAuras()
{
for (uint8 i = BASE_ATTACK; i < MAX_ATTACK; ++i)
UpdateWeaponDependentCritAuras(WeaponAttackType(i));
}
void Player::UpdateWeaponDependentAuras(WeaponAttackType attackType)
{
UpdateWeaponDependentCritAuras(attackType);
UpdateDamageDoneMods(attackType);
UpdateDamagePctDoneMods(attackType);
}
void Player::ApplyItemDependentAuras(Item* item, bool apply)
{
if (apply)
{
PlayerSpellMap const& spells = GetSpellMap();
for (auto itr = spells.begin(); itr != spells.end(); ++itr)
{
if (itr->second->state == PLAYERSPELL_REMOVED || itr->second->disabled)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first, DIFFICULTY_NONE);
if (!spellInfo || !spellInfo->IsPassive() || spellInfo->EquippedItemClass < 0)
continue;
if (!HasAura(itr->first) && HasItemFitToSpellRequirements(spellInfo))
AddAura(itr->first, this); // no SMSG_SPELL_GO in sniff found
}
}
else
RemoveItemDependentAurasAndCasts(item);
}
bool Player::CheckAttackFitToAuraRequirement(WeaponAttackType attackType, AuraEffect const* aurEff) const
{
SpellInfo const* spellInfo = aurEff->GetSpellInfo();
if (spellInfo->EquippedItemClass == -1)
return true;
Item* item = GetWeaponForAttack(attackType, true);
if (!item || !item->IsFitToSpellRequirements(spellInfo))
return false;
return true;
}
void Player::ApplyItemEquipSpell(Item* item, bool apply, bool formChange /*= false*/)
{
if (!item || item->GetTemplate()->HasFlag(ITEM_FLAG_LEGACY))
return;
for (ItemEffectEntry const* effectData : item->GetEffects())
{
// wrong triggering type
if (apply && effectData->TriggerType != ITEM_SPELLTRIGGER_ON_EQUIP)
continue;
// check if it is valid spell
SpellInfo const* spellproto = sSpellMgr->GetSpellInfo(effectData->SpellID, DIFFICULTY_NONE);
if (!spellproto)
continue;
if (effectData->ChrSpecializationID && effectData->ChrSpecializationID != GetPrimarySpecialization())
continue;
ApplyEquipSpell(spellproto, item, apply, formChange);
}
}
void Player::ApplyEquipSpell(SpellInfo const* spellInfo, Item* item, bool apply, bool formChange /*= false*/)
{
if (apply)
{
// Cannot be used in this stance/form
if (spellInfo->CheckShapeshift(GetShapeshiftForm()) != SPELL_CAST_OK)
return;
if (formChange) // 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;
}
TC_LOG_DEBUG("entities.player", "Player::ApplyEquipSpell: Player '%s' (%s) cast %s equip spell (ID: %i)",
GetName().c_str(), GetGUID().ToString().c_str(), (item ? "item" : "itemset"), spellInfo->Id);
CastSpell(this, spellInfo->Id, item);
}
else
{
if (formChange) // 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)
}
}
void Player::UpdateEquipSpellsAtFormChange()
{
for (uint8 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (m_items[i] && !m_items[i]->IsBroken() && CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
{
ApplyItemEquipSpell(m_items[i], false, true); // remove spells that not fit to form
ApplyItemEquipSpell(m_items[i], true, true); // add spells that fit form but not active
}
}
UpdateItemSetAuras(true);
}
void Player::UpdateItemSetAuras(bool formChange /*= false*/)
{
// item set bonuses not dependent from item broken state
for (size_t setindex = 0; setindex < ItemSetEff.size(); ++setindex)
{
ItemSetEffect* eff = ItemSetEff[setindex];
if (!eff)
continue;
for (ItemSetSpellEntry const* itemSetSpell : eff->SetBonuses)
{
SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(itemSetSpell->SpellID, DIFFICULTY_NONE);
if (itemSetSpell->ChrSpecID && itemSetSpell->ChrSpecID != GetPrimarySpecialization())
ApplyEquipSpell(spellInfo, nullptr, false, false); // item set aura is not for current spec
else
{
ApplyEquipSpell(spellInfo, nullptr, false, formChange); // remove spells that not fit to form - removal is skipped if shapeshift condition is satisfied
ApplyEquipSpell(spellInfo, nullptr, true, formChange); // add spells that fit form but not active
}
}
}
}
void Player::ApplyArtifactPowers(Item* item, bool apply)
{
if (item->IsArtifactDisabled())
return;
for (UF::ArtifactPower const& artifactPower : item->m_itemData->ArtifactPowers)
{
uint8 rank = artifactPower.CurrentRankWithBonus;
if (!rank)
continue;
if (sArtifactPowerStore.AssertEntry(artifactPower.ArtifactPowerID)->Flags & ARTIFACT_POWER_FLAG_SCALES_WITH_NUM_POWERS)
rank = 1;
ArtifactPowerRankEntry const* artifactPowerRank = sDB2Manager.GetArtifactPowerRank(artifactPower.ArtifactPowerID, rank - 1);
if (!artifactPowerRank)
continue;
ApplyArtifactPowerRank(item, artifactPowerRank, apply);
}
if (ArtifactAppearanceEntry const* artifactAppearance = sArtifactAppearanceStore.LookupEntry(item->GetModifier(ITEM_MODIFIER_ARTIFACT_APPEARANCE_ID)))
if (artifactAppearance->OverrideShapeshiftDisplayID && GetShapeshiftForm() == ShapeshiftForm(artifactAppearance->OverrideShapeshiftFormID))
RestoreDisplayId();
}
void Player::ApplyArtifactPowerRank(Item* artifact, ArtifactPowerRankEntry const* artifactPowerRank, bool apply)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(artifactPowerRank->SpellID, DIFFICULTY_NONE);
if (!spellInfo)
return;
if (spellInfo->IsPassive())
{
AuraApplication* powerAura = GetAuraApplication(artifactPowerRank->SpellID, ObjectGuid::Empty, artifact->GetGUID());
if (powerAura)
{
if (apply)
{
for (AuraEffect* auraEffect : powerAura->GetBase()->GetAuraEffects())
{
if (!auraEffect)
continue;
if (powerAura->HasEffect(auraEffect->GetEffIndex()))
auraEffect->ChangeAmount(artifactPowerRank->AuraPointsOverride ? artifactPowerRank->AuraPointsOverride : auraEffect->GetSpellEffectInfo().CalcValue());
}
}
else
RemoveAura(powerAura);
}
else if (apply)
{
CastSpellExtraArgs args;
args.TriggerFlags = TRIGGERED_FULL_MASK;
args.CastItem = artifact;
if (artifactPowerRank->AuraPointsOverride)
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
args.AddSpellMod(SpellValueMod(SPELLVALUE_BASE_POINT0 + spellEffectInfo.EffectIndex), artifactPowerRank->AuraPointsOverride);
CastSpell(this, artifactPowerRank->SpellID, args);
}
}
else
{
if (apply && !HasSpell(artifactPowerRank->SpellID))
{
AddTemporarySpell(artifactPowerRank->SpellID);
WorldPackets::Spells::LearnedSpells learnedSpells;
learnedSpells.SuppressMessaging = true;
learnedSpells.SpellID.push_back(artifactPowerRank->SpellID);
SendDirectMessage(learnedSpells.Write());
}
else if (!apply)
{
RemoveTemporarySpell(artifactPowerRank->SpellID);
WorldPackets::Spells::UnlearnedSpells unlearnedSpells;
unlearnedSpells.SuppressMessaging = true;
unlearnedSpells.SpellID.push_back(artifactPowerRank->SpellID);
SendDirectMessage(unlearnedSpells.Write());
}
}
}
void Player::ApplyAzeritePowers(Item* item, bool apply)
{
if (AzeriteItem* azeriteItem = item->ToAzeriteItem())
{
// milestone powers
for (uint32 azeriteItemMilestonePowerId : azeriteItem->m_azeriteItemData->UnlockedEssenceMilestones)
ApplyAzeriteItemMilestonePower(azeriteItem, sAzeriteItemMilestonePowerStore.AssertEntry(azeriteItemMilestonePowerId), apply);
// essences
if (UF::SelectedAzeriteEssences const* selectedEssences = azeriteItem->GetSelectedAzeriteEssences())
for (uint8 slot = 0; slot < MAX_AZERITE_ESSENCE_SLOT; ++slot)
if (selectedEssences->AzeriteEssenceID[slot])
ApplyAzeriteEssence(azeriteItem, selectedEssences->AzeriteEssenceID[slot], azeriteItem->GetEssenceRank(selectedEssences->AzeriteEssenceID[slot]),
AzeriteItemMilestoneType(sDB2Manager.GetAzeriteItemMilestonePower(slot)->Type) == AzeriteItemMilestoneType::MajorEssence, apply);
}
else if (AzeriteEmpoweredItem* azeriteEmpoweredItem = item->ToAzeriteEmpoweredItem())
{
if (!apply || GetItemByEntry(ITEM_ID_HEART_OF_AZEROTH, ItemSearchLocation::Equipment))
for (int32 i = 0; i < MAX_AZERITE_EMPOWERED_TIER; ++i)
if (AzeritePowerEntry const* azeritePower = sAzeritePowerStore.LookupEntry(azeriteEmpoweredItem->GetSelectedAzeritePower(i)))
ApplyAzeritePower(azeriteEmpoweredItem, azeritePower, apply);
}
}
void Player::ApplyAzeriteItemMilestonePower(AzeriteItem* item, AzeriteItemMilestonePowerEntry const* azeriteItemMilestonePower, bool apply)
{
AzeriteItemMilestoneType type = AzeriteItemMilestoneType(azeriteItemMilestonePower->Type);
if (type == AzeriteItemMilestoneType::BonusStamina)
{
if (AzeritePowerEntry const* azeritePower = sAzeritePowerStore.LookupEntry(azeriteItemMilestonePower->AzeritePowerID))
{
if (apply)
CastSpell(this, azeritePower->SpellID, item);
else
RemoveAurasDueToItemSpell(azeritePower->SpellID, item->GetGUID());
}
}
}
void Player::ApplyAzeriteEssence(AzeriteItem* item, uint32 azeriteEssenceId, uint32 rank, bool major, bool apply)
{
for (uint32 currentRank = 1; currentRank <= rank; ++currentRank)
{
if (AzeriteEssencePowerEntry const* azeriteEssencePower = sDB2Manager.GetAzeriteEssencePower(azeriteEssenceId, currentRank))
{
ApplyAzeriteEssencePower(item, azeriteEssencePower, major, apply);
if (major && currentRank == 1)
{
if (apply)
{
CastSpellExtraArgs args(TRIGGERED_FULL_MASK);
args.AddSpellMod(SPELLVALUE_BASE_POINT0, azeriteEssencePower->MajorPowerDescription);
CastSpell(this, SPELL_ID_HEART_ESSENCE_ACTION_BAR_OVERRIDE, args);
}
else
RemoveAurasDueToSpell(SPELL_ID_HEART_ESSENCE_ACTION_BAR_OVERRIDE);
}
}
}
}
void Player::ApplyAzeriteEssencePower(AzeriteItem* item, AzeriteEssencePowerEntry const* azeriteEssencePower, bool major, bool apply)
{
if (SpellInfo const* powerSpell = sSpellMgr->GetSpellInfo(azeriteEssencePower->MinorPowerDescription, DIFFICULTY_NONE))
{
if (apply)
CastSpell(this, powerSpell->Id, item);
else
RemoveAurasDueToItemSpell(powerSpell->Id, item->GetGUID());
}
if (major)
{
if (SpellInfo const* powerSpell = sSpellMgr->GetSpellInfo(azeriteEssencePower->MajorPowerDescription, DIFFICULTY_NONE))
{
if (powerSpell->IsPassive())
{
if (apply)
CastSpell(this, powerSpell->Id, item);
else
RemoveAurasDueToItemSpell(powerSpell->Id, item->GetGUID());
}
else
{
if (apply)
LearnSpell(powerSpell->Id, true, 0, true);
else
RemoveSpell(powerSpell->Id, false, false, true);
}
}
}
}
void Player::ApplyAzeritePower(AzeriteEmpoweredItem* item, AzeritePowerEntry const* azeritePower, bool apply)
{
if (apply)
{
if (!azeritePower->SpecSetID || sDB2Manager.IsSpecSetMember(azeritePower->SpecSetID, GetPrimarySpecialization()))
CastSpell(this, azeritePower->SpellID, item);
}
else
RemoveAurasDueToItemSpell(azeritePower->SpellID, item->GetGUID());
}
void Player::CastItemCombatSpell(DamageInfo const& damageInfo)
{
Unit* target = damageInfo.GetVictim();
if (!target || !target->IsAlive() || target == this)
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() && CanUseAttackType(damageInfo.GetAttackType()))
{
if (ItemTemplate const* proto = item->GetTemplate())
{
// Additional check for weapons
if (proto->GetClass() == ITEM_CLASS_WEAPON)
{
// offhand item cannot proc from main hand hit etc
EquipmentSlots slot;
switch (damageInfo.GetAttackType())
{
case BASE_ATTACK:
slot = EQUIPMENT_SLOT_MAINHAND;
break;
case OFF_ATTACK:
slot = EQUIPMENT_SLOT_OFFHAND;
break;
case RANGED_ATTACK:
slot = EQUIPMENT_SLOT_MAINHAND;
break;
default:
slot = EQUIPMENT_SLOT_END;
break;
}
if (slot != i)
continue;
// Check if item is useable (forms or disarm)
if (damageInfo.GetAttackType() == BASE_ATTACK)
if (!IsUseEquipedWeapon(true) && !IsInFeralForm())
continue;
}
CastItemCombatSpell(damageInfo, item, proto);
}
}
}
}
}
void Player::CastItemCombatSpell(DamageInfo const& damageInfo, Item* item, ItemTemplate const* proto)
{
// Can do effect if any damage done to target
// for done procs allow normal + critical + absorbs by default
bool canTrigger = (damageInfo.GetHitMask() & (PROC_HIT_NORMAL | PROC_HIT_CRITICAL | PROC_HIT_ABSORB)) != 0;
if (canTrigger)
{
if (!item->GetTemplate()->HasFlag(ITEM_FLAG_LEGACY))
{
for (ItemEffectEntry const* effectData : item->GetEffects())
{
// wrong triggering type
if (effectData->TriggerType != ITEM_SPELLTRIGGER_CHANCE_ON_HIT)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(effectData->SpellID, DIFFICULTY_NONE);
if (!spellInfo)
{
TC_LOG_ERROR("entities.player.items", "Player::CastItemCombatSpell: Player '%s' (%s) cast unknown item spell (ID: %i)",
GetName().c_str(), GetGUID().ToString().c_str(), effectData->SpellID);
continue;
}
// not allow proc extra attack spell at extra attack
if (m_extraAttacks && spellInfo->HasEffect(SPELL_EFFECT_ADD_EXTRA_ATTACKS))
return;
float chance = (float)spellInfo->ProcChance;
if (proto->SpellPPMRate)
{
uint32 WeaponSpeed = GetBaseAttackTime(damageInfo.GetAttackType());
chance = GetPPMProcChance(WeaponSpeed, proto->SpellPPMRate, spellInfo);
}
else if (chance > 100.0f)
chance = GetWeaponProcChance();
if (roll_chance_f(chance) && sScriptMgr->OnCastItemCombatSpell(this, damageInfo.GetVictim(), spellInfo, item))
CastSpell(damageInfo.GetVictim(), spellInfo->Id, 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_ITEM_ENCHANTMENT_EFFECTS; ++s)
{
if (pEnchant->Effect[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL)
continue;
SpellEnchantProcEntry const* entry = sSpellMgr->GetSpellEnchantProcEvent(enchant_id);
if (entry && entry->HitMask)
{
// Check hit/crit/dodge/parry requirement
if ((entry->HitMask & damageInfo.GetHitMask()) == 0)
continue;
}
else
{
// Can do effect if any damage done to target
// for done procs allow normal + critical + absorbs by default
if (!canTrigger)
continue;
}
// check if enchant procs only on white hits
if (entry && (entry->AttributesMask & ENCHANT_PROC_ATTR_WHITE_HIT) && damageInfo.GetSpellInfo())
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(pEnchant->EffectArg[s], DIFFICULTY_NONE);
if (!spellInfo)
{
TC_LOG_ERROR("entities.player.items", "Player::CastItemCombatSpell: Player '%s' (%s) cast unknown spell (EnchantID: %u, SpellID: %i), ignoring",
GetName().c_str(), GetGUID().ToString().c_str(), pEnchant->ID, pEnchant->EffectArg[s]);
continue;
}
float chance = pEnchant->EffectPointsMin[s] != 0 ? float(pEnchant->EffectPointsMin[s]) : GetWeaponProcChance();
if (entry)
{
if (entry->ProcsPerMinute)
chance = GetPPMProcChance(proto->GetDelay(), entry->ProcsPerMinute, spellInfo);
else if (entry->Chance)
chance = (float)entry->Chance;
}
// Apply spell mods
ApplySpellMod(spellInfo, SpellModOp::ProcChance, 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))
{
if (spellInfo->IsPositive())
CastSpell(this, spellInfo->Id, item);
else
CastSpell(damageInfo.GetVictim(), spellInfo->Id, item);
}
if (roll_chance_f(chance))
{
Unit* target = spellInfo->IsPositive() ? this : damageInfo.GetVictim();
CastSpellExtraArgs args(item);
// reduce effect values if enchant is limited
if (entry && (entry->AttributesMask & ENCHANT_PROC_ATTR_LIMIT_60) && target->GetLevelForTarget(this) > 60)
{
int32 const lvlDifference = target->GetLevelForTarget(this) - 60;
int32 const lvlPenaltyFactor = 4; // 4% lost effectiveness per level
int32 const effectPct = std::max(0, 100 - (lvlDifference * lvlPenaltyFactor));
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
if (spellEffectInfo.IsEffect())
args.AddSpellMod(static_cast<SpellValueMod>(SPELLVALUE_BASE_POINT0 + spellEffectInfo.EffectIndex), CalculatePct(spellEffectInfo.CalcValue(this), effectPct));
}
CastSpell(target, spellInfo->Id, args);
}
}
}
}
void Player::CastItemUseSpell(Item* item, SpellCastTargets const& targets, ObjectGuid castCount, int32* misc)
{
if (!item->GetTemplate()->HasFlag(ITEM_FLAG_LEGACY))
{
// item spells cast at use
for (ItemEffectEntry const* effectData : item->GetEffects())
{
// wrong triggering type
if (effectData->TriggerType != ITEM_SPELLTRIGGER_ON_USE)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(effectData->SpellID, DIFFICULTY_NONE);
if (!spellInfo)
{
TC_LOG_ERROR("entities.player", "Player::CastItemUseSpell: Item (Entry: %u) has wrong spell id %u, ignoring", item->GetEntry(), effectData->SpellID);
continue;
}
Spell* spell = new Spell(this, spellInfo, TRIGGERED_NONE);
WorldPackets::Spells::SpellPrepare spellPrepare;
spellPrepare.ClientCastID = castCount;
spellPrepare.ServerCastID = spell->m_castId;
SendDirectMessage(spellPrepare.Write());
spell->m_fromClient = true;
spell->m_CastItem = item;
spell->m_misc.Raw.Data[0] = misc[0];
spell->m_misc.Raw.Data[1] = misc[1];
spell->prepare(targets);
return;
}
}
// Item enchantments spells cast 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_ITEM_ENCHANTMENT_EFFECTS; ++s)
{
if (pEnchant->Effect[s] != ITEM_ENCHANTMENT_TYPE_USE_SPELL)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(pEnchant->EffectArg[s], DIFFICULTY_NONE);
if (!spellInfo)
{
TC_LOG_ERROR("entities.player", "Player::CastItemUseSpell: Enchant %i, cast unknown spell %i", pEnchant->ID, pEnchant->EffectArg[s]);
continue;
}
Spell* spell = new Spell(this, spellInfo, TRIGGERED_NONE);
WorldPackets::Spells::SpellPrepare spellPrepare;
spellPrepare.ClientCastID = castCount;
spellPrepare.ServerCastID = spell->m_castId;
SendDirectMessage(spellPrepare.Write());
spell->m_fromClient = true;
spell->m_CastItem = item;
spell->m_misc.Raw.Data[0] = misc[0];
spell->m_misc.Raw.Data[1] = misc[1];
spell->prepare(targets);
return;
}
}
}
void Player::_RemoveAllItemMods()
{
TC_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->GetItemSet())
RemoveItemsSetItem(this, m_items[i]);
if (m_items[i]->IsBroken() || !CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
continue;
ApplyItemEquipSpell(m_items[i], false);
ApplyEnchantment(m_items[i], false);
ApplyArtifactPowers(m_items[i], false);
}
}
for (uint8 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (m_items[i])
{
if (m_items[i]->IsBroken() || !CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
continue;
ApplyItemDependentAuras(m_items[i], false);
_ApplyItemBonuses(m_items[i], i, false);
}
}
TC_LOG_DEBUG("entities.player.items", "_RemoveAllItemMods complete.");
}
void Player::_ApplyAllItemMods()
{
TC_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(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
continue;
ApplyItemDependentAuras(m_items[i], true);
_ApplyItemBonuses(m_items[i], i, true);
WeaponAttackType const attackType = Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType());
if (attackType != MAX_ATTACK)
UpdateWeaponDependentAuras(attackType);
}
}
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->GetItemSet())
AddItemsSetItem(this, m_items[i]);
if (m_items[i]->IsBroken() || !CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
continue;
ApplyItemEquipSpell(m_items[i], true);
ApplyArtifactPowers(m_items[i], true);
ApplyEnchantment(m_items[i], true);
}
}
TC_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 (!CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
continue;
_ApplyItemMods(m_items[i], i, apply);
// Update item sets for heirlooms
if (sDB2Manager.GetHeirloomByItemId(m_items[i]->GetEntry()) && m_items[i]->GetTemplate()->GetItemSet())
{
if (apply)
AddItemsSetItem(this, m_items[i]);
else
RemoveItemsSetItem(this, m_items[i]);
}
}
}
}
void Player::ApplyAllAzeriteItemMods(bool apply)
{
for (uint8 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (m_items[i])
{
if (!m_items[i]->IsAzeriteItem() || m_items[i]->IsBroken() || !CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
continue;
ApplyAzeritePowers(m_items[i], apply);
}
}
}
void Player::ApplyAllAzeriteEmpoweredItemMods(bool apply)
{
for (uint8 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (m_items[i])
{
if (!m_items[i]->IsAzeriteEmpoweredItem() || m_items[i]->IsBroken() || !CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
continue;
ApplyAzeritePowers(m_items[i], apply);
}
}
}
ObjectGuid Player::GetLootWorldObjectGUID(ObjectGuid const& lootObjectGuid) const
{
auto itr = m_AELootView.find(lootObjectGuid);
if (itr != m_AELootView.end())
return itr->second;
return ObjectGuid::Empty;
}
void Player::RemoveAELootedWorldObject(ObjectGuid const& lootWorldObjectGuid)
{
auto itr = std::find_if(m_AELootView.begin(), m_AELootView.end(), [&lootWorldObjectGuid](std::pair<ObjectGuid const, ObjectGuid> const& lootView)
{
return lootView.second == lootWorldObjectGuid;
});
if (itr != m_AELootView.end())
m_AELootView.erase(itr);
}
bool Player::HasLootWorldObjectGUID(ObjectGuid const& lootWorldObjectGuid) const
{
return m_AELootView.end() != std::find_if(m_AELootView.begin(), m_AELootView.end(), [&lootWorldObjectGuid](std::pair<ObjectGuid const, ObjectGuid> const& lootView)
{
return lootView.second == lootWorldObjectGuid;
});
}
/* If in a battleground a player dies, and an enemy removes the insignia, the player's bones is lootable
Called by remove insignia spell effect */
void Player::RemovedInsignia(Player* looterPlr)
{
// If player is not in battleground and not in worldpvpzone
if (!GetBattlegroundId() && !IsInWorldPvpZone())
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->AddCorpseDynamicFlag(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) const
{
WorldPackets::Loot::LootReleaseResponse packet;
packet.LootObj = guid;
packet.Owner = GetGUID();
SendDirectMessage(packet.Write());
}
void Player::SendLootReleaseAll() const
{
SendDirectMessage(WorldPackets::Loot::LootReleaseAll().Write());
}
void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = false*/)
{
if (!GetLootGUID().IsEmpty() && !aeLooting)
m_session->DoLootReleaseAll();
Loot* loot;
PermissionTypes permission = ALL_PERMISSION;
TC_LOG_DEBUG("loot", "Player::SendLoot: Player: '%s' (%s), Loot: %s",
GetName().c_str(), GetGUID().ToString().c_str(), guid.ToString().c_str());
if (guid.IsGameObject())
{
GameObject* go = GetMap()->GetGameObject(guid);
auto shouldLootRelease = [this](GameObject* go, LootType lootType) -> bool
{
// 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)
return true;
if (lootType == LOOT_SKINNING)
{
// Disarm Trap
if (!go->IsWithinDistInMap(this, 20.f))
return true;
}
else
{
if (lootType != LOOT_FISHINGHOLE && ((lootType != LOOT_FISHING && lootType != LOOT_FISHING_JUNK) || go->GetOwnerGUID() != GetGUID()) && !go->IsWithinDistInMap(this))
return true;
if (lootType == LOOT_CORPSE && go->GetRespawnTime() && go->isSpawnedByDefault())
return true;
}
return false;
};
if (shouldLootRelease(go, loot_type))
{
SendLootRelease(guid);
return;
}
loot = &go->loot;
// loot was generated and respawntime has passed since then, allow to recreate loot
// to avoid bugs, this rule covers spawned gameobjects only
// Don't allow to regenerate chest loot inside instances and raids, to avoid exploits with duplicate boss loot being given for some encounters
if (go->isSpawnedByDefault() && go->getLootState() == GO_ACTIVATED && !go->loot.isLooted() && !go->GetMap()->Instanceable() && go->GetLootGenerationTime() + go->GetRespawnDelay() < GameTime::GetGameTime())
go->SetLootState(GO_READY);
if (go->getLootState() == GO_READY)
{
uint32 lootid = go->GetGOInfo()->GetLootId();
if (Battleground* bg = GetBattleground())
if (!bg->CanActivateGO(go->GetEntry(), GetTeam()))
{
SendLootRelease(guid);
return;
}
if (lootid)
{
loot->clear();
Group* group = GetGroup();
bool groupRules = (group && go->GetGOInfo()->type == GAMEOBJECT_TYPE_CHEST && go->GetGOInfo()->chest.usegrouplootrules);
// 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(), GetMap()->GetDifficultyLootItemContext());
go->SetLootGenerationTime();
// get next RR player (for next loot)
if (groupRules && !go->loot.empty())
group->UpdateLooterGuid(go);
}
if (go->GetLootMode() > 0)
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.usegrouplootrules)
{
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 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;
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;
// Store container id
loot->containerID = item->GetGUID();
// If item doesn't already have loot, attempt to load it. If that
// fails then this is first time opening, generate loot
if (!item->m_lootGenerated && !sLootItemStorage->LoadStoredLoot(item, this))
{
item->m_lootGenerated = true;
loot->clear();
switch (loot_type)
{
case LOOT_DISENCHANTING:
loot->FillLoot(ASSERT_NOTNULL(item->GetDisenchantLoot(this))->ID, 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);
// Force save the loot and money items that were just rolled
// Also saves the container item ID in Loot struct (not to DB)
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)
{
SendLootRelease(guid);
return;
}
loot = &bones->loot;
if (loot->loot_type == LOOT_NONE)
{
uint32 pLevel = bones->loot.gold;
bones->loot.clear();
// For AV Achievement
if (Battleground* bg = GetBattleground())
{
if (bg->GetTypeID(true) == BATTLEGROUND_AV)
loot->FillLoot(PLAYER_CORPSE_LOOT_ENTRY, LootTemplates_Creature, this, true);
}
// For wintergrasp Quests
else if (GetZoneId() == AREA_WINTERGRASP)
loot->FillLoot(PLAYER_CORPSE_LOOT_ENTRY, LootTemplates_Creature, 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 * std::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) || (!aeLooting && !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_type != LOOT_PICKPOCKETING)
{
if (creature->CanGeneratePickPocketLoot())
{
creature->StartPickPocketRefillTimer();
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
{
SendLootError(loot->GetGUID(), guid, LOOT_ERROR_ALREADY_PICKPOCKETED);
return;
}
} // else - still has pickpocket loot generated & not fully taken
}
else
{
// exploit fix
if (!creature->HasDynamicFlag(UNIT_DYNFLAG_LOOTABLE))
{
SendLootError(loot->GetGUID(), 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)
{
SendLootError(loot->GetGUID(), guid, LOOT_ERROR_DIDNT_KILL);
return;
}
if (loot->loot_type == LOOT_NONE)
{
// for creature, loot is filled when creature is killed.
if (Group* group = creature->GetLootRecipientGroup())
{
switch (group->GetLootMethod())
{
case GROUP_LOOT:
// GroupLoot: rolls items over threshold. Items with quality < threshold, round robin
group->GroupLoot(loot, creature);
break;
case MASTER_LOOT:
group->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;
// Set new loot recipient
creature->SetLootRecipient(this, false);
}
// set group rights only for loot_type != LOOT_SKINNING
else
{
if (creature->GetLootRecipientGroup())
{
Group* group = GetGroup();
if (group == creature->GetLootRecipientGroup())
{
switch (group->GetLootMethod())
{
case MASTER_LOOT:
permission = group->GetMasterLooterGuid() == GetGUID() ? MASTER_PERMISSION : RESTRICTED_PERMISSION;
break;
case FREE_FOR_ALL:
permission = ALL_PERMISSION;
break;
default:
permission = GROUP_PERMISSION;
break;
}
}
else
permission = NONE_PERMISSION;
}
else if (creature->GetLootRecipient() == 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)
{
LootMethod _lootMethod = FREE_FOR_ALL;
if (Group* group = GetGroup())
{
if (Creature* creature = GetMap()->GetCreature(guid))
{
if (Player* recipient = creature->GetLootRecipient())
{
if (group == recipient->GetGroup())
_lootMethod = group->GetLootMethod();
}
}
}
if (!aeLooting)
SetLootGUID(guid);
WorldPackets::Loot::LootResponse packet;
packet.Owner = guid;
packet.LootObj = loot->GetGUID();
packet._LootMethod = _lootMethod;
packet.AcquireReason = loot_type;
packet.Acquired = true; // false == No Loot (this too^^)
packet.AELooting = aeLooting;
loot->BuildLootResponse(packet, this, permission);
SendDirectMessage(packet.Write());
// add 'this' player as one of the players that are looting 'loot'
loot->AddLooter(GetGUID());
m_AELootView[loot->GetGUID()] = guid;
if (loot_type == LOOT_CORPSE && !guid.IsItem())
AddUnitFlag(UNIT_FLAG_LOOTING);
}
else
SendLootError(loot->GetGUID(), guid, LOOT_ERROR_DIDNT_KILL);
}
void Player::SendLootError(ObjectGuid const& lootObj, ObjectGuid const& owner, LootError error) const
{
WorldPackets::Loot::LootResponse lootResponse;
lootResponse.LootObj = lootObj;
lootResponse.Owner = owner;
lootResponse.Acquired = false;
lootResponse.FailureReason = error;
SendDirectMessage(lootResponse.Write());
}
void Player::SendNotifyLootMoneyRemoved(ObjectGuid lootObj) const
{
WorldPackets::Loot::CoinRemoved packet;
packet.LootObj = lootObj;
SendDirectMessage(packet.Write());
}
void Player::SendNotifyLootItemRemoved(ObjectGuid lootObj, uint8 lootSlot) const
{
WorldPackets::Loot::LootRemoved packet;
packet.Owner = GetLootWorldObjectGUID(lootObj);
packet.LootObj = lootObj;
packet.LootListID = lootSlot + 1;
SendDirectMessage(packet.Write());
}
void Player::SendUpdateWorldState(uint32 variable, uint32 value, bool hidden /*= false*/) const
{
WorldPackets::WorldState::UpdateWorldState worldstate;
worldstate.VariableID = variable;
worldstate.Value = value;
worldstate.Hidden = hidden;
SendDirectMessage(worldstate.Write());
}
// TODO - InitWorldStates should NOT always send the same states
// Some should keep the same value between different zoneIds and areaIds on the same map
void Player::SendInitWorldStates(uint32 zoneId, uint32 areaId)
{
uint32 mapId = GetMapId();
Battleground* battleground = GetBattleground();
OutdoorPvP* outdoorPvP = sOutdoorPvPMgr->GetOutdoorPvPToZoneId(zoneId);
InstanceScript* instance = GetInstanceScript();
Battlefield* battlefield = sBattlefieldMgr->GetBattlefieldToZoneId(zoneId);
TC_LOG_DEBUG("network", "Player::SendInitWorldStates: Sending SMSG_INIT_WORLD_STATES for Map: %u, Zone: %u", mapId, zoneId);
WorldPackets::WorldState::InitWorldStates packet;
packet.MapID = mapId;
packet.AreaID = zoneId;
packet.SubareaID = areaId;
packet.Worldstates.emplace_back(2264, 0); // SCOURGE_EVENT_WORLDSTATE_EASTERN_PLAGUELANDS
packet.Worldstates.emplace_back(2263, 0); // SCOURGE_EVENT_WORLDSTATE_TANARIS
packet.Worldstates.emplace_back(2262, 0); // SCOURGE_EVENT_WORLDSTATE_BURNING_STEPPES
packet.Worldstates.emplace_back(2261, 0); // SCOURGE_EVENT_WORLDSTATE_BLASTED_LANDS
packet.Worldstates.emplace_back(2260, 0); // SCOURGE_EVENT_WORLDSTATE_AZSHARA
packet.Worldstates.emplace_back(2259, 0); // SCOURGE_EVENT_WORLDSTATE_WINTERSPRING
// ARENA_SEASON_IN_PROGRESS
// 7 - arena season in progress
// 0 - end of season
packet.Worldstates.emplace_back(3191, sWorld->getBoolConfig(CONFIG_ARENA_SEASON_IN_PROGRESS) ? sWorld->getIntConfig(CONFIG_ARENA_SEASON_ID) : 0);
// Previous arena season id
int32 previousArenaSeason = 0;
if (sWorld->getBoolConfig(CONFIG_ARENA_SEASON_IN_PROGRESS) && sWorld->getIntConfig(CONFIG_ARENA_SEASON_ID) > 0)
previousArenaSeason = sWorld->getIntConfig(CONFIG_ARENA_SEASON_ID) - 1;
packet.Worldstates.emplace_back(3901, previousArenaSeason);
if (mapId == 530) // Outland
{
packet.Worldstates.emplace_back(2495, 0); // NA_UI_OUTLAND_01 "Progress: %2494w"
packet.Worldstates.emplace_back(2493, 15); // NA_UI_GUARDS_MAX
packet.Worldstates.emplace_back(2491, 15); // NA_UI_GUARDS_LEFT
}
// Horde War Mode bonus
packet.Worldstates.emplace_back(17042, 10 + (sWorld->GetWarModeDominantFaction() == TEAM_ALLIANCE ? sWorld->GetWarModeOutnumberedFactionReward() : 0));
// Alliance War Mode bonus
packet.Worldstates.emplace_back(17043, 10 + (sWorld->GetWarModeDominantFaction() == TEAM_HORDE ? sWorld->GetWarModeOutnumberedFactionReward() : 0));
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 1377: // Silithus
if (outdoorPvP && outdoorPvP->GetTypeId() == OUTDOOR_PVP_SI)
outdoorPvP->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(2313, 0); // SI_GATHERED_A
packet.Worldstates.emplace_back(2314, 0); // SI_GATHERED_H
packet.Worldstates.emplace_back(2317, 0); // SI_SILITHYST_MAX
}
// unknown, aq opening?
packet.Worldstates.emplace_back(2322, 0); // AQ_SANDWORM_N
packet.Worldstates.emplace_back(2323, 0); // AQ_SANDWORM_S
packet.Worldstates.emplace_back(2324, 0); // AQ_SANDWORM_SW
packet.Worldstates.emplace_back(2325, 0); // AQ_SANDWORM_E
break;
case 2597: // Alterac Valley
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_AV)
battleground->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(1966, 1); // AV_SNOWFALL_N
packet.Worldstates.emplace_back(1330, 1); // AV_FROSTWOLFHUT_H_C
packet.Worldstates.emplace_back(1329, 0); // AV_FROSTWOLFHUT_A_C
packet.Worldstates.emplace_back(1326, 0); // AV_AID_A_A
packet.Worldstates.emplace_back(1393, 0); // East Frostwolf Tower Horde Assaulted - UNUSED
packet.Worldstates.emplace_back(1392, 0); // West Frostwolf Tower Horde Assaulted - UNUSED
packet.Worldstates.emplace_back(1383, 1); // AV_FROSTWOLFE_CONTROLLED
packet.Worldstates.emplace_back(1382, 1); // AV_FROSTWOLFW_CONTROLLED
packet.Worldstates.emplace_back(1360, 1); // AV_N_MINE_N
packet.Worldstates.emplace_back(1348, 0); // AV_ICEBLOOD_A_A
packet.Worldstates.emplace_back(1334, 0); // AV_PIKEGRAVE_H_C
packet.Worldstates.emplace_back(1333, 1); // AV_PIKEGRAVE_A_C
packet.Worldstates.emplace_back(1304, 0); // AV_STONEHEART_A_A
packet.Worldstates.emplace_back(1303, 0); // AV_STONEHEART_H_A
packet.Worldstates.emplace_back(1396, 0); // unk
packet.Worldstates.emplace_back(1395, 0); // Iceblood Tower Horde Assaulted - UNUSED
packet.Worldstates.emplace_back(1394, 0); // Towerpoint Horde Assaulted - UNUSED
packet.Worldstates.emplace_back(1391, 0); // unk
packet.Worldstates.emplace_back(1390, 0); // AV_ICEBLOOD_ASSAULTED
packet.Worldstates.emplace_back(1389, 0); // AV_TOWERPOINT_ASSAULTED
packet.Worldstates.emplace_back(1388, 0); // AV_FROSTWOLFE_ASSAULTED
packet.Worldstates.emplace_back(1387, 0); // AV_FROSTWOLFW_ASSAULTED
packet.Worldstates.emplace_back(1386, 1); // unk
packet.Worldstates.emplace_back(1385, 1); // AV_ICEBLOOD_CONTROLLED
packet.Worldstates.emplace_back(1384, 1); // AV_TOWERPOINT_CONTROLLED
packet.Worldstates.emplace_back(1381, 0); // AV_STONEH_ASSAULTED
packet.Worldstates.emplace_back(1380, 0); // AV_ICEWING_ASSAULTED
packet.Worldstates.emplace_back(1379, 0); // AV_DUNN_ASSAULTED
packet.Worldstates.emplace_back(1378, 0); // AV_DUNS_ASSAULTED
packet.Worldstates.emplace_back(1377, 0); // Stoneheart Bunker Alliance Assaulted - UNUSED
packet.Worldstates.emplace_back(1376, 0); // Icewing Bunker Alliance Assaulted - UNUSED
packet.Worldstates.emplace_back(1375, 0); // Dunbaldar South Alliance Assaulted - UNUSED
packet.Worldstates.emplace_back(1374, 0); // Dunbaldar North Alliance Assaulted - UNUSED
packet.Worldstates.emplace_back(1373, 0); // AV_STONEH_DESTROYED
packet.Worldstates.emplace_back(966, 0); // AV_UNK_02
packet.Worldstates.emplace_back(964, 0); // AV_UNK_01
packet.Worldstates.emplace_back(962, 0); // AV_STORMPIKE_COMMANDERS
packet.Worldstates.emplace_back(1302, 1); // AV_STONEHEART_A_C
packet.Worldstates.emplace_back(1301, 0); // AV_STONEHEART_H_C
packet.Worldstates.emplace_back(950, 0); // AV_STORMPIKE_LIEUTENANTS
packet.Worldstates.emplace_back(1372, 0); // AV_ICEWING_DESTROYED
packet.Worldstates.emplace_back(1371, 0); // AV_DUNN_DESTROYED
packet.Worldstates.emplace_back(1370, 0); // AV_DUNS_DESTROYED
packet.Worldstates.emplace_back(1369, 0); // unk
packet.Worldstates.emplace_back(1368, 0); // AV_ICEBLOOD_DESTROYED
packet.Worldstates.emplace_back(1367, 0); // AV_TOWERPOINT_DESTROYED
packet.Worldstates.emplace_back(1366, 0); // AV_FROSTWOLFE_DESTROYED
packet.Worldstates.emplace_back(1365, 0); // AV_FROSTWOLFW_DESTROYED
packet.Worldstates.emplace_back(1364, 1); // AV_STONEH_CONTROLLED
packet.Worldstates.emplace_back(1363, 1); // AV_ICEWING_CONTROLLED
packet.Worldstates.emplace_back(1362, 1); // AV_DUNN_CONTROLLED
packet.Worldstates.emplace_back(1361, 1); // AV_DUNS_CONTROLLED
packet.Worldstates.emplace_back(1359, 0); // AV_N_MINE_H
packet.Worldstates.emplace_back(1358, 0); // AV_N_MINE_A
packet.Worldstates.emplace_back(1357, 1); // AV_S_MINE_N
packet.Worldstates.emplace_back(1356, 0); // AV_S_MINE_H
packet.Worldstates.emplace_back(1355, 0); // AV_S_MINE_A
packet.Worldstates.emplace_back(1349, 0); // AV_ICEBLOOD_H_A
packet.Worldstates.emplace_back(1347, 1); // AV_ICEBLOOD_H_C
packet.Worldstates.emplace_back(1346, 0); // AV_ICEBLOOD_A_C
packet.Worldstates.emplace_back(1344, 0); // AV_SNOWFALL_H_A
packet.Worldstates.emplace_back(1343, 0); // AV_SNOWFALL_A_A
packet.Worldstates.emplace_back(1342, 0); // AV_SNOWFALL_H_C
packet.Worldstates.emplace_back(1341, 0); // AV_SNOWFALL_A_C
packet.Worldstates.emplace_back(1340, 0); // AV_FROSTWOLF_H_A
packet.Worldstates.emplace_back(1339, 0); // AV_FROSTWOLF_A_A
packet.Worldstates.emplace_back(1338, 1); // AV_FROSTWOLF_H_C
packet.Worldstates.emplace_back(1337, 0); // AV_FROSTWOLF_A_C
packet.Worldstates.emplace_back(1336, 0); // AV_PIKEGRAVE_H_A
packet.Worldstates.emplace_back(1335, 0); // AV_PIKEGRAVE_A_A
packet.Worldstates.emplace_back(1332, 0); // AV_FROSTWOLFHUT_H_A
packet.Worldstates.emplace_back(1331, 0); // AV_FROSTWOLFHUT_A_A
packet.Worldstates.emplace_back(1328, 0); // AV_AID_H_A
packet.Worldstates.emplace_back(1327, 0); // AV_AID_H_C
packet.Worldstates.emplace_back(1325, 1); // AV_AID_A_C
}
break;
case 3277: // Warsong Gulch
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_WS)
battleground->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(1581, 0); // alliance flag captures
packet.Worldstates.emplace_back(1582, 0); // horde flag captures
packet.Worldstates.emplace_back(1545, 0); // unk, set to 1 on alliance flag pickup...
packet.Worldstates.emplace_back(1546, 0); // unk, set to 1 on horde flag pickup, after drop it's -1
packet.Worldstates.emplace_back(1547, 2); // unk
packet.Worldstates.emplace_back(1601, 3); // unk (max flag captures?)
packet.Worldstates.emplace_back(2338, 1); // horde (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing)
packet.Worldstates.emplace_back(2339, 1); // alliance (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing)
}
break;
case 3358: // Arathi Basin
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_AB)
battleground->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(1767, 0); // stables alliance
packet.Worldstates.emplace_back(1768, 0); // stables horde
packet.Worldstates.emplace_back(1769, 0); // stables alliance controlled
packet.Worldstates.emplace_back(1770, 0); // stables horde controlled
packet.Worldstates.emplace_back(1772, 0); // farm alliance
packet.Worldstates.emplace_back(1773, 0); // farm horde
packet.Worldstates.emplace_back(1774, 0); // farm alliance controlled
packet.Worldstates.emplace_back(1775, 0); // farm horde controlled
packet.Worldstates.emplace_back(1776, 0); // alliance resources
packet.Worldstates.emplace_back(1777, 0); // horde resources
packet.Worldstates.emplace_back(1778, 0); // horde bases
packet.Worldstates.emplace_back(1779, 0); // alliance bases
packet.Worldstates.emplace_back(1780, 2000); // max resources (2000)
packet.Worldstates.emplace_back(1782, 0); // blacksmith alliance
packet.Worldstates.emplace_back(1783, 0); // blacksmith horde
packet.Worldstates.emplace_back(1784, 0); // blacksmith alliance controlled
packet.Worldstates.emplace_back(1785, 0); // blacksmith horde controlled
packet.Worldstates.emplace_back(1787, 0); // gold mine alliance
packet.Worldstates.emplace_back(1788, 0); // gold mine horde
packet.Worldstates.emplace_back(1789, 0); // gold mine alliance controlled
packet.Worldstates.emplace_back(1790, 0); // gold mine horde controlled
packet.Worldstates.emplace_back(1792, 0); // lumber mill alliance
packet.Worldstates.emplace_back(1793, 0); // lumber mill horde
packet.Worldstates.emplace_back(1794, 0); // lumber mill alliance controlled
packet.Worldstates.emplace_back(1795, 0); // lumber mill horde controlled
packet.Worldstates.emplace_back(1842, 1); // stables (1 - uncontrolled)
packet.Worldstates.emplace_back(1843, 1); // gold mine (1 - uncontrolled)
packet.Worldstates.emplace_back(1844, 1); // lumber mill (1 - uncontrolled)
packet.Worldstates.emplace_back(1845, 1); // farm (1 - uncontrolled)
packet.Worldstates.emplace_back(1846, 1); // blacksmith (1 - uncontrolled)
packet.Worldstates.emplace_back(1861, 2); // unk
packet.Worldstates.emplace_back(1955, 1800); // warning limit (1800)
}
break;
case 3820: // Eye of the Storm
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_EY)
battleground->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(2753, 0); // Horde Bases
packet.Worldstates.emplace_back(2752, 0); // Alliance Bases
packet.Worldstates.emplace_back(2742, 0); // Mage Tower - Horde conflict
packet.Worldstates.emplace_back(2741, 0); // Mage Tower - Alliance conflict
packet.Worldstates.emplace_back(2740, 0); // Fel Reaver - Horde conflict
packet.Worldstates.emplace_back(2739, 0); // Fel Reaver - Alliance conflict
packet.Worldstates.emplace_back(2738, 0); // Draenei - Alliance conflict
packet.Worldstates.emplace_back(2737, 0); // Draenei - Horde conflict
packet.Worldstates.emplace_back(2736, 0); // unk (0 at start)
packet.Worldstates.emplace_back(2735, 0); // unk (0 at start)
packet.Worldstates.emplace_back(2733, 0); // Draenei - Horde control
packet.Worldstates.emplace_back(2732, 0); // Draenei - Alliance control
packet.Worldstates.emplace_back(2731, 1); // Draenei uncontrolled (1 - yes, 0 - no)
packet.Worldstates.emplace_back(2730, 0); // Mage Tower - Alliance control
packet.Worldstates.emplace_back(2729, 0); // Mage Tower - Horde control
packet.Worldstates.emplace_back(2728, 1); // Mage Tower uncontrolled (1 - yes, 0 - no)
packet.Worldstates.emplace_back(2727, 0); // Fel Reaver - Horde control
packet.Worldstates.emplace_back(2726, 0); // Fel Reaver - Alliance control
packet.Worldstates.emplace_back(2725, 1); // Fel Reaver uncontrolled (1 - yes, 0 - no)
packet.Worldstates.emplace_back(2724, 0); // Boold Elf - Horde control
packet.Worldstates.emplace_back(2723, 0); // Boold Elf - Alliance control
packet.Worldstates.emplace_back(2722, 1); // Boold Elf uncontrolled (1 - yes, 0 - no)
packet.Worldstates.emplace_back(2757, 1); // Flag (1 - show, 0 - hide) - doesn't work exactly this way!
packet.Worldstates.emplace_back(2770, 1); // Horde top-stats (1 - show, 0 - hide) // 02 -> horde picked up the flag
packet.Worldstates.emplace_back(2769, 1); // Alliance top-stats (1 - show, 0 - hide) // 02 -> alliance picked up the flag
packet.Worldstates.emplace_back(2750, 0); // Horde resources
packet.Worldstates.emplace_back(2749, 0); // Alliance resources
packet.Worldstates.emplace_back(2565, 142); // unk, constant?
packet.Worldstates.emplace_back(2720, 0); // Capturing progress-bar (100 -> empty (only grey), 0 -> blue|red (no grey), default 0)
packet.Worldstates.emplace_back(2719, 0); // Capturing progress-bar (0 - left, 100 - right)
packet.Worldstates.emplace_back(2718, 0); // Capturing progress-bar (1 - show, 0 - hide)
packet.Worldstates.emplace_back(3085, 379); // unk, constant?
// missing unknowns
}
break;
case 3483: // Hellfire Peninsula
if (outdoorPvP && outdoorPvP->GetTypeId() == OUTDOOR_PVP_HP)
outdoorPvP->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(2490, 1); // add ally tower main gui icon
packet.Worldstates.emplace_back(2489, 1); // add horde tower main gui icon
packet.Worldstates.emplace_back(2485, 0); // show neutral broken hill icon
packet.Worldstates.emplace_back(2484, 1); // show icon above broken hill
packet.Worldstates.emplace_back(2483, 0); // show ally broken hill icon
packet.Worldstates.emplace_back(2482, 0); // show neutral overlook icon
packet.Worldstates.emplace_back(2481, 1); // show the overlook arrow
packet.Worldstates.emplace_back(2480, 0); // show ally overlook icon
packet.Worldstates.emplace_back(2478, 0); // horde pvp objectives captured
packet.Worldstates.emplace_back(2476, 0); // ally pvp objectives captured
packet.Worldstates.emplace_back(2475, 100); // horde slider grey area
packet.Worldstates.emplace_back(2474, 50); // horde slider percentage, 100 for ally, 0 for horde
packet.Worldstates.emplace_back(2473, 0); // horde slider display
packet.Worldstates.emplace_back(2472, 0); // show the neutral stadium icon
packet.Worldstates.emplace_back(2471, 0); // show the ally stadium icon
packet.Worldstates.emplace_back(2470, 1); // show the horde stadium icon
}
break;
case 3518: // Nagrand
if (outdoorPvP && outdoorPvP->GetTypeId() == OUTDOOR_PVP_NA)
outdoorPvP->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(2503, 0); // NA_UI_HORDE_GUARDS_SHOW
packet.Worldstates.emplace_back(2502, 0); // NA_UI_ALLIANCE_GUARDS_SHOW
packet.Worldstates.emplace_back(2493, 0); // NA_UI_GUARDS_MAX
packet.Worldstates.emplace_back(2491, 0); // NA_UI_GUARDS_LEFT
packet.Worldstates.emplace_back(2495, 0); // NA_UI_OUTLAND_01
packet.Worldstates.emplace_back(2494, 0); // NA_UI_UNK_1
packet.Worldstates.emplace_back(2497, 0); // NA_UI_UNK_2
packet.Worldstates.emplace_back(2762, 0); // NA_MAP_WYVERN_NORTH_NEU_H
packet.Worldstates.emplace_back(2662, 0); // NA_MAP_WYVERN_NORTH_NEU_A
packet.Worldstates.emplace_back(2663, 0); // NA_MAP_WYVERN_NORTH_H
packet.Worldstates.emplace_back(2664, 0); // NA_MAP_WYVERN_NORTH_A
packet.Worldstates.emplace_back(2760, 0); // NA_MAP_WYVERN_SOUTH_NEU_H
packet.Worldstates.emplace_back(2670, 0); // NA_MAP_WYVERN_SOUTH_NEU_A
packet.Worldstates.emplace_back(2668, 0); // NA_MAP_WYVERN_SOUTH_H
packet.Worldstates.emplace_back(2669, 0); // NA_MAP_WYVERN_SOUTH_A
packet.Worldstates.emplace_back(2761, 0); // NA_MAP_WYVERN_WEST_NEU_H
packet.Worldstates.emplace_back(2667, 0); // NA_MAP_WYVERN_WEST_NEU_A
packet.Worldstates.emplace_back(2665, 0); // NA_MAP_WYVERN_WEST_H
packet.Worldstates.emplace_back(2666, 0); // NA_MAP_WYVERN_WEST_A
packet.Worldstates.emplace_back(2763, 0); // NA_MAP_WYVERN_EAST_NEU_H
packet.Worldstates.emplace_back(2659, 0); // NA_MAP_WYVERN_EAST_NEU_A
packet.Worldstates.emplace_back(2660, 0); // NA_MAP_WYVERN_EAST_H
packet.Worldstates.emplace_back(2661, 0); // NA_MAP_WYVERN_EAST_A
packet.Worldstates.emplace_back(2671, 0); // NA_MAP_HALAA_NEUTRAL
packet.Worldstates.emplace_back(2676, 0); // NA_MAP_HALAA_NEU_A
packet.Worldstates.emplace_back(2677, 0); // NA_MAP_HALAA_NEU_H
packet.Worldstates.emplace_back(2672, 0); // NA_MAP_HALAA_HORDE
packet.Worldstates.emplace_back(2673, 0); // NA_MAP_HALAA_ALLIANCE
}
break;
case 3519: // Terokkar Forest
if (outdoorPvP && outdoorPvP->GetTypeId() == OUTDOOR_PVP_TF)
outdoorPvP->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(2625, 0); // TF_UI_CAPTURE_BAR_POS
packet.Worldstates.emplace_back(2624, 20); // TF_UI_CAPTURE_BAR_NEUTRAL
packet.Worldstates.emplace_back(2623, 0); // TF_UI_SHOW CAPTURE BAR
packet.Worldstates.emplace_back(2622, 0); // TF_UI_TOWER_COUNT_H
packet.Worldstates.emplace_back(2621, 5); // TF_UI_TOWER_COUNT_A
packet.Worldstates.emplace_back(2620, 0); // TF_UI_TOWERS_CONTROLLED_DISPLAY
packet.Worldstates.emplace_back(2696, 0); // TF_TOWER_NUM_15 - SE Neutral
packet.Worldstates.emplace_back(2695, 0); // TF_TOWER_NUM_14 - SE Horde
packet.Worldstates.emplace_back(2694, 0); // TF_TOWER_NUM_13 - SE Alliance
packet.Worldstates.emplace_back(2693, 0); // TF_TOWER_NUM_12 - S Neutral
packet.Worldstates.emplace_back(2692, 0); // TF_TOWER_NUM_11 - S Horde
packet.Worldstates.emplace_back(2691, 0); // TF_TOWER_NUM_10 - S Alliance
packet.Worldstates.emplace_back(2690, 0); // TF_TOWER_NUM_09 - NE Neutral
packet.Worldstates.emplace_back(2689, 0); // TF_TOWER_NUM_08 - NE Horde
packet.Worldstates.emplace_back(2688, 0); // TF_TOWER_NUM_07 - NE Alliance
packet.Worldstates.emplace_back(2687, 0); // TF_TOWER_NUM_16 - unk
packet.Worldstates.emplace_back(2686, 0); // TF_TOWER_NUM_06 - N Neutral
packet.Worldstates.emplace_back(2685, 0); // TF_TOWER_NUM_05 - N Horde
packet.Worldstates.emplace_back(2684, 0); // TF_TOWER_NUM_04 - N Alliance
packet.Worldstates.emplace_back(2683, 0); // TF_TOWER_NUM_03 - NW Alliance
packet.Worldstates.emplace_back(2682, 0); // TF_TOWER_NUM_02 - NW Horde
packet.Worldstates.emplace_back(2681, 0); // TF_TOWER_NUM_01 - NW Neutral
packet.Worldstates.emplace_back(2512, 5); // TF_UI_LOCKED_TIME_MINUTES_FIRST_DIGIT
packet.Worldstates.emplace_back(2510, 0); // TF_UI_LOCKED_TIME_MINUTES_SECOND_DIGIT
packet.Worldstates.emplace_back(2509, 0); // TF_UI_LOCKED_TIME_HOURS
packet.Worldstates.emplace_back(2508, 0); // TF_UI_LOCKED_DISPLAY_NEUTRAL
packet.Worldstates.emplace_back(2768, 0); // TF_UI_LOCKED_DISPLAY_HORDE
packet.Worldstates.emplace_back(2767, 1); // TF_UI_LOCKED_DISPLAY_ALLIANCE
}
break;
case 3521: // Zangarmarsh
if (outdoorPvP && outdoorPvP->GetTypeId() == OUTDOOR_PVP_ZM)
outdoorPvP->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(2529, 0); // ZM_UNK_1
packet.Worldstates.emplace_back(2528, 0); // ZM_UNK_2
packet.Worldstates.emplace_back(2527, 0); // ZM_UNK_3
packet.Worldstates.emplace_back(2653, 1); // ZM_WORLDSTATE_UNK_1
packet.Worldstates.emplace_back(2652, 0); // ZM_MAP_TOWER_EAST_N
packet.Worldstates.emplace_back(2651, 1); // ZM_MAP_TOWER_EAST_H
packet.Worldstates.emplace_back(2650, 0); // ZM_MAP_TOWER_EAST_A
packet.Worldstates.emplace_back(2649, 1); // ZM_MAP_GRAVEYARD_H - Twin spire graveyard horde
packet.Worldstates.emplace_back(2648, 0); // ZM_MAP_GRAVEYARD_A
packet.Worldstates.emplace_back(2647, 0); // ZM_MAP_GRAVEYARD_N
packet.Worldstates.emplace_back(2646, 0); // ZM_MAP_TOWER_WEST_N
packet.Worldstates.emplace_back(2645, 1); // ZM_MAP_TOWER_WEST_H
packet.Worldstates.emplace_back(2644, 0); // ZM_MAP_TOWER_WEST_A
packet.Worldstates.emplace_back(2535, 0); // ZM_UNK_4
packet.Worldstates.emplace_back(2534, 0); // ZM_UNK_5
packet.Worldstates.emplace_back(2533, 0); // ZM_UNK_6
packet.Worldstates.emplace_back(2560, 0); // ZM_UI_TOWER_EAST_N
packet.Worldstates.emplace_back(2559, 1); // ZM_UI_TOWER_EAST_H
packet.Worldstates.emplace_back(2558, 0); // ZM_UI_TOWER_EAST_A
packet.Worldstates.emplace_back(2557, 0); // ZM_UI_TOWER_WEST_N
packet.Worldstates.emplace_back(2556, 1); // ZM_UI_TOWER_WEST_H
packet.Worldstates.emplace_back(2555, 0); // ZM_UI_TOWER_WEST_A
packet.Worldstates.emplace_back(2658, 0); // ZM_MAP_HORDE_FLAG_READY
packet.Worldstates.emplace_back(2657, 1); // ZM_MAP_HORDE_FLAG_NOT_READY
packet.Worldstates.emplace_back(2656, 1); // ZM_MAP_ALLIANCE_FLAG_NOT_READY
packet.Worldstates.emplace_back(2655, 0); // ZM_MAP_ALLIANCE_FLAG_READY
}
break;
case 3698: // Nagrand Arena
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_NA)
battleground->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(2575, 0); // BATTLEGROUND_NAGRAND_ARENA_GOLD
packet.Worldstates.emplace_back(2576, 0); // BATTLEGROUND_NAGRAND_ARENA_GREEN
packet.Worldstates.emplace_back(2577, 0); // BATTLEGROUND_NAGRAND_ARENA_SHOW
}
break;
case 3702: // Blade's Edge Arena
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_BE)
battleground->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(2544, 0); // BATTLEGROUND_BLADES_EDGE_ARENA_GOLD
packet.Worldstates.emplace_back(2545, 0); // BATTLEGROUND_BLADES_EDGE_ARENA_GREEN
packet.Worldstates.emplace_back(2547, 0); // BATTLEGROUND_BLADES_EDGE_ARENA_SHOW
}
break;
case 3968: // Ruins of Lordaeron
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_RL)
battleground->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(3000, 0); // BATTELGROUND_RUINS_OF_LORDAERNON_GOLD
packet.Worldstates.emplace_back(3001, 0); // BATTELGROUND_RUINS_OF_LORDAERNON_GREEN
packet.Worldstates.emplace_back(3002, 0); // BATTELGROUND_RUINS_OF_LORDAERNON_SHOW
}
break;
case 4378: // Dalaran Sewers
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_DS)
battleground->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(3601, 0); // ARENA_WORLD_STATE_ALIVE_PLAYERS_GOLD
packet.Worldstates.emplace_back(3600, 0); // ARENA_WORLD_STATE_ALIVE_PLAYERS_GREEN
packet.Worldstates.emplace_back(3610, 0); // ARENA_WORLD_STATE_ALIVE_PLAYERS_SHOW
}
break;
case 4384: // Strand of the Ancients
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_SA)
battleground->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(3849, 0); // Gate of Temple
packet.Worldstates.emplace_back(3638, 0); // Gate of Yellow Moon
packet.Worldstates.emplace_back(3623, 0); // Gate of Green Emerald
packet.Worldstates.emplace_back(3620, 0); // Gate of Blue Sapphire
packet.Worldstates.emplace_back(3617, 0); // Gate of Red Sun
packet.Worldstates.emplace_back(3614, 0); // Gate of Purple Ametyst
packet.Worldstates.emplace_back(3571, 0); // bonus timer (1 - on, 0 - off)
packet.Worldstates.emplace_back(3565, 0); // Horde Attacker
packet.Worldstates.emplace_back(3564, 0); // Alliance Attacker
// End Round timer, example: 19:59 -> A:BC
packet.Worldstates.emplace_back(3561, 0); // C
packet.Worldstates.emplace_back(3560, 0); // B
packet.Worldstates.emplace_back(3559, 0); // A
packet.Worldstates.emplace_back(3637, 0); // BG_SA_CENTER_GY_ALLIANCE
packet.Worldstates.emplace_back(3636, 0); // BG_SA_RIGHT_GY_ALLIANCE
packet.Worldstates.emplace_back(3635, 0); // BG_SA_LEFT_GY_ALLIANCE
packet.Worldstates.emplace_back(3634, 0); // BG_SA_CENTER_GY_HORDE
packet.Worldstates.emplace_back(3633, 0); // BG_SA_LEFT_GY_HORDE
packet.Worldstates.emplace_back(3632, 0); // BG_SA_RIGHT_GY_HORDE
packet.Worldstates.emplace_back(3631, 0); // BG_SA_HORDE_DEFENCE_TOKEN
packet.Worldstates.emplace_back(3630, 0); // BG_SA_ALLIANCE_DEFENCE_TOKEN
packet.Worldstates.emplace_back(3629, 0); // BG_SA_LEFT_ATT_TOKEN_HRD
packet.Worldstates.emplace_back(3628, 0); // BG_SA_RIGHT_ATT_TOKEN_HRD
packet.Worldstates.emplace_back(3627, 0); // BG_SA_RIGHT_ATT_TOKEN_ALL
packet.Worldstates.emplace_back(3626, 0); // BG_SA_LEFT_ATT_TOKEN_ALL
// missing unknowns
}
break;
case 4406: // Ring of Valor
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_RV)
battleground->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(3600, 0); // ARENA_WORLD_STATE_ALIVE_PLAYERS_GREEN
packet.Worldstates.emplace_back(3601, 0); // ARENA_WORLD_STATE_ALIVE_PLAYERS_GOLD
packet.Worldstates.emplace_back(3610, 0); // ARENA_WORLD_STATE_ALIVE_PLAYERS_SHOW
}
break;
case 4710: // Isle of Conquest
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_IC)
battleground->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(4221, 1); // BG_IC_ALLIANCE_RENFORT_SET
packet.Worldstates.emplace_back(4222, 1); // BG_IC_HORDE_RENFORT_SET
packet.Worldstates.emplace_back(4226, 300); // BG_IC_ALLIANCE_RENFORT
packet.Worldstates.emplace_back(4227, 300); // BG_IC_HORDE_RENFORT
packet.Worldstates.emplace_back(4322, 1); // BG_IC_GATE_FRONT_H_WS_OPEN
packet.Worldstates.emplace_back(4321, 1); // BG_IC_GATE_WEST_H_WS_OPEN
packet.Worldstates.emplace_back(4320, 1); // BG_IC_GATE_EAST_H_WS_OPEN
packet.Worldstates.emplace_back(4323, 1); // BG_IC_GATE_FRONT_A_WS_OPEN
packet.Worldstates.emplace_back(4324, 1); // BG_IC_GATE_WEST_A_WS_OPEN
packet.Worldstates.emplace_back(4325, 1); // BG_IC_GATE_EAST_A_WS_OPEN
packet.Worldstates.emplace_back(4317, 1); // unk
packet.Worldstates.emplace_back(4301, 1); // BG_IC_DOCKS_UNCONTROLLED
packet.Worldstates.emplace_back(4296, 1); // BG_IC_HANGAR_UNCONTROLLED
packet.Worldstates.emplace_back(4306, 1); // BG_IC_QUARRY_UNCONTROLLED
packet.Worldstates.emplace_back(4311, 1); // BG_IC_REFINERY_UNCONTROLLED
packet.Worldstates.emplace_back(4294, 1); // BG_IC_WORKSHOP_UNCONTROLLED
packet.Worldstates.emplace_back(4243, 1); // unk
packet.Worldstates.emplace_back(4345, 1); // unk
}
break;
case 4987: // The Ruby Sanctum
if (instance)
instance->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(5049, 50); // WORLDSTATE_CORPOREALITY_MATERIAL
packet.Worldstates.emplace_back(5050, 50); // WORLDSTATE_CORPOREALITY_TWILIGHT
packet.Worldstates.emplace_back(5051, 0); // WORLDSTATE_CORPOREALITY_TOGGLE
}
break;
case 4812: // Icecrown Citadel
if (instance)
instance->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(4903, 0); // WORLDSTATE_SHOW_TIMER (Blood Quickening weekly)
packet.Worldstates.emplace_back(4904, 30); // WORLDSTATE_EXECUTION_TIME
packet.Worldstates.emplace_back(4940, 0); // WORLDSTATE_SHOW_ATTEMPTS
packet.Worldstates.emplace_back(4941, 50); // WORLDSTATE_ATTEMPTS_REMAINING
packet.Worldstates.emplace_back(4942, 50); // WORLDSTATE_ATTEMPTS_MAX
}
break;
case 4100: // The Culling of Stratholme
if (instance)
instance->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(3479, 0); // WORLDSTATE_SHOW_CRATES
packet.Worldstates.emplace_back(3480, 0); // WORLDSTATE_CRATES_REVEALED
packet.Worldstates.emplace_back(3504, 0); // WORLDSTATE_WAVE_COUNT
packet.Worldstates.emplace_back(3931, 25); // WORLDSTATE_TIME_GUARDIAN
packet.Worldstates.emplace_back(3932, 0); // WORLDSTATE_TIME_GUARDIAN_SHOW
}
break;
case 4228: // The Oculus
if (instance)
instance->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(3524, 0); // WORLD_STATE_CENTRIFUGE_CONSTRUCT_SHOW
packet.Worldstates.emplace_back(3486, 0); // WORLD_STATE_CENTRIFUGE_CONSTRUCT_AMOUNT
}
break;
case 4273: // Ulduar
if (instance)
instance->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(4132, 0); // WORLDSTATE_ALGALON_TIMER_ENABLED
packet.Worldstates.emplace_back(4131, 0); // WORLDSTATE_ALGALON_DESPAWN_TIMER
}
break;
case 4415: // Violet Hold
if (instance)
instance->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(3816, 0); // WORLD_STATE_VH_SHOW
packet.Worldstates.emplace_back(3815, 100); // WORLD_STATE_VH_PRISON_STATE
packet.Worldstates.emplace_back(3810, 0); // WORLD_STATE_VH_WAVE_COUNT
}
break;
case 4820: // Halls of Refection
if (instance)
instance->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(4884, 0); // WORLD_STATE_HOR_WAVES_ENABLED
packet.Worldstates.emplace_back(4882, 0); // WORLD_STATE_HOR_WAVE_COUNT
}
break;
case AREA_WINTERGRASP: // Wintergrasp
if (battlefield && battlefield->GetTypeId() == BATTLEFIELD_WG)
battlefield->FillInitialWorldStates(packet);
break;
case 3805: // Zul Aman
if (instance)
instance->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(3104, 0); // WORLD_STATE_ZULAMAN_TIMER_ENABLED
packet.Worldstates.emplace_back(3106, 0); // WORLD_STATE_ZULAMAN_TIMER
}
break;
case 5031: // Twin Peaks
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_TP)
battleground->FillInitialWorldStates(packet);
else
{
packet.Worldstates.emplace_back(1581, 0x0); // alliance flag captures
packet.Worldstates.emplace_back(1582, 0x0); // horde flag captures
packet.Worldstates.emplace_back(1545, 0x0); // unk
packet.Worldstates.emplace_back(1546, 0x0); // unk
packet.Worldstates.emplace_back(1547, 0x2); // unk
packet.Worldstates.emplace_back(1601, 0x3); // unk
packet.Worldstates.emplace_back(2338, 0x1); // horde (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing)
packet.Worldstates.emplace_back(2339, 0x1); // alliance (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing)
}
break;
case 5449: // Battle for Gilneas
if (battleground && battleground->GetTypeID(true) == BATTLEGROUND_BFG)
battleground->FillInitialWorldStates(packet);
break;
case 5389: // Tol Barad Peninsula
if (sWorld->getBoolConfig(CONFIG_TOLBARAD_ENABLE))
{
packet.Worldstates.emplace_back(WS_BATTLEFIELD_TB_ALLIANCE_CONTROLS_SHOW, sWorld->getWorldState(WS_BATTLEFIELD_TB_ALLIANCE_CONTROLS_SHOW));
packet.Worldstates.emplace_back(WS_BATTLEFIELD_TB_HORDE_CONTROLS_SHOW, sWorld->getWorldState(WS_BATTLEFIELD_TB_HORDE_CONTROLS_SHOW));
packet.Worldstates.emplace_back(WS_BATTLEFIELD_TB_TIME_NEXT_BATTLE_SHOW, sWorld->getWorldState(WS_BATTLEFIELD_TB_TIME_NEXT_BATTLE_SHOW));
packet.Worldstates.emplace_back(WS_BATTLEFIELD_TB_ALLIANCE_ATTACKING_SHOW, sWorld->getWorldState(WS_BATTLEFIELD_TB_ALLIANCE_ATTACKING_SHOW));
packet.Worldstates.emplace_back(WS_BATTLEFIELD_TB_HORDE_ATTACKING_SHOW, sWorld->getWorldState(WS_BATTLEFIELD_TB_HORDE_ATTACKING_SHOW));
}
break;
case 5095: // Tol Barad
if (battlefield && battlefield->GetTypeId() == BATTLEFIELD_TB)
battlefield->FillInitialWorldStates(packet);
break;
default:
break;
}
SendDirectMessage(packet.Write());
SendBGWeekendWorldStates();
SendBattlefieldWorldStates();
}
void Player::SendBGWeekendWorldStates() const
{
for (uint32 i = 1; i < sBattlemasterListStore.GetNumRows(); ++i)
{
BattlemasterListEntry const* bl = sBattlemasterListStore.LookupEntry(i);
if (bl && bl->HolidayWorldState)
{
if (BattlegroundMgr::IsBGWeekend((BattlegroundTypeId)bl->ID))
SendUpdateWorldState(bl->HolidayWorldState, 1);
else
SendUpdateWorldState(bl->HolidayWorldState, 0);
}
}
}
void Player::SendBattlefieldWorldStates() const
{
/// Send misc stuff that needs to be sent on every login, like the battle timers.
if (sWorld->getBoolConfig(CONFIG_WINTERGRASP_ENABLE))
{
if (Battlefield* wg = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG))
{
SendUpdateWorldState(WS_BATTLEFIELD_WG_ACTIVE, wg->IsWarTime() ? 0 : 1);
uint32 timer = wg->IsWarTime() ? 0 : (wg->GetTimer() / 1000); // 0 - Time to next battle
SendUpdateWorldState(WS_BATTLEFIELD_WG_TIME_NEXT_BATTLE, uint32(GameTime::GetGameTime() + timer));
}
}
if (sWorld->getBoolConfig(CONFIG_TOLBARAD_ENABLE))
{
if (Battlefield* tb = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_TB))
{
SendUpdateWorldState(WS_BATTLEFIELD_TB_FACTION_CONTROLLING, uint32(tb->GetDefenderTeam() + 1));
uint32 timer = tb->GetTimer() / 1000;
SendUpdateWorldState(WS_BATTLEFIELD_TB_TIME_BATTLE_END, uint32(tb->IsWarTime() ? uint32(GameTime::GetGameTime() + timer) : 0));
SendUpdateWorldState(WS_BATTLEFIELD_TB_TIME_NEXT_BATTLE, uint32(!tb->IsWarTime() ? uint32(GameTime::GetGameTime() + timer) : 0));
}
}
}
void Player::SetBindPoint(ObjectGuid guid) const
{
WorldPackets::Misc::BinderConfirm packet(guid);
SendDirectMessage(packet.Write());
}
void Player::SendRespecWipeConfirm(ObjectGuid const& guid, uint32 cost) const
{
WorldPackets::Talent::RespecWipeConfirm respecWipeConfirm;
respecWipeConfirm.RespecMaster = guid;
respecWipeConfirm.Cost = cost;
respecWipeConfirm.RespecType = SPEC_RESET_TALENTS;
SendDirectMessage(respecWipeConfirm.Write());
}
/*********************************************************/
/*** STORAGE SYSTEM ***/
/*********************************************************/
uint8 Player::FindEquipSlot(Item const* item, uint32 slot, bool swap) const
{
uint8 slots[4];
slots[0] = NULL_SLOT;
slots[1] = NULL_SLOT;
slots[2] = NULL_SLOT;
slots[3] = NULL_SLOT;
switch (item->GetTemplate()->GetInventoryType())
{
case INVTYPE_HEAD:
slots[0] = EQUIPMENT_SLOT_HEAD;
break;
case INVTYPE_NECK:
slots[0] = EQUIPMENT_SLOT_NECK;
break;
case INVTYPE_SHOULDERS:
slots[0] = EQUIPMENT_SLOT_SHOULDERS;
break;
case INVTYPE_BODY:
slots[0] = EQUIPMENT_SLOT_BODY;
break;
case INVTYPE_CHEST:
slots[0] = EQUIPMENT_SLOT_CHEST;
break;
case INVTYPE_ROBE:
slots[0] = EQUIPMENT_SLOT_CHEST;
break;
case INVTYPE_WAIST:
slots[0] = EQUIPMENT_SLOT_WAIST;
break;
case INVTYPE_LEGS:
slots[0] = EQUIPMENT_SLOT_LEGS;
break;
case INVTYPE_FEET:
slots[0] = EQUIPMENT_SLOT_FEET;
break;
case INVTYPE_WRISTS:
slots[0] = EQUIPMENT_SLOT_WRISTS;
break;
case INVTYPE_HANDS:
slots[0] = EQUIPMENT_SLOT_HANDS;
break;
case INVTYPE_FINGER:
slots[0] = EQUIPMENT_SLOT_FINGER1;
slots[1] = EQUIPMENT_SLOT_FINGER2;
break;
case INVTYPE_TRINKET:
slots[0] = EQUIPMENT_SLOT_TRINKET1;
slots[1] = EQUIPMENT_SLOT_TRINKET2;
break;
case INVTYPE_CLOAK:
slots[0] = EQUIPMENT_SLOT_BACK;
break;
case INVTYPE_WEAPON:
{
slots[0] = EQUIPMENT_SLOT_MAINHAND;
// suggest offhand slot only if know dual wielding
// (this will be replace mainhand weapon at auto equip instead unwonted "you don't known dual wielding" ...
if (CanDualWield())
slots[1] = EQUIPMENT_SLOT_OFFHAND;
break;
}
case INVTYPE_SHIELD:
slots[0] = EQUIPMENT_SLOT_OFFHAND;
break;
case INVTYPE_RANGED:
slots[0] = EQUIPMENT_SLOT_MAINHAND;
break;
case INVTYPE_2HWEAPON:
slots[0] = EQUIPMENT_SLOT_MAINHAND;
if (CanDualWield() && CanTitanGrip())
slots[1] = EQUIPMENT_SLOT_OFFHAND;
break;
case INVTYPE_TABARD:
slots[0] = EQUIPMENT_SLOT_TABARD;
break;
case INVTYPE_WEAPONMAINHAND:
slots[0] = EQUIPMENT_SLOT_MAINHAND;
break;
case INVTYPE_WEAPONOFFHAND:
slots[0] = EQUIPMENT_SLOT_OFFHAND;
break;
case INVTYPE_HOLDABLE:
slots[0] = EQUIPMENT_SLOT_OFFHAND;
break;
case INVTYPE_RANGEDRIGHT:
slots[0] = EQUIPMENT_SLOT_MAINHAND;
break;
case INVTYPE_BAG:
slots[0] = INVENTORY_SLOT_BAG_START + 0;
slots[1] = INVENTORY_SLOT_BAG_START + 1;
slots[2] = INVENTORY_SLOT_BAG_START + 2;
slots[3] = INVENTORY_SLOT_BAG_START + 3;
break;
default:
return NULL_SLOT;
}
if (slot != NULL_SLOT)
{
if (swap || !GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
for (uint8 i = 0; i < 4; ++i)
if (slots[i] == slot)
return slot;
}
else
{
// search free slot at first
for (uint8 i = 0; i < 4; ++i)
if (slots[i] != NULL_SLOT && !GetItemByPos(INVENTORY_SLOT_BAG_0, slots[i]))
// in case 2hand equipped weapon (without titan grip) offhand slot empty but not free
if (slots[i] != EQUIPMENT_SLOT_OFFHAND || !IsTwoHandUsed())
return slots[i];
// if not found free and can swap return slot with lower item level equipped
if (swap)
{
uint32 minItemLevel = std::numeric_limits<uint32>::max();
uint8 minItemLevelIndex = 0;
for (uint8 i = 0; i < 4; ++i)
{
if (slots[i] != NULL_SLOT)
{
if (Item const* equipped = GetItemByPos(INVENTORY_SLOT_BAG_0, slots[i]))
{
uint32 itemLevel = equipped->GetItemLevel(this);
if (itemLevel < minItemLevel)
{
minItemLevel = itemLevel;
minItemLevelIndex = i;
}
}
}
}
return slots[minItemLevelIndex];
}
}
// no free position
return NULL_SLOT;
}
uint32 Player::GetFreeInventorySlotCount(EnumFlag<ItemSearchLocation> location /*= ItemSearchLocation::Inventory*/) const
{
uint32 freeSlotCount = 0;
if (location.HasFlag(ItemSearchLocation::Equipment))
for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
if (!GetItemByPos(INVENTORY_SLOT_BAG_0, i))
++freeSlotCount;
if (location.HasFlag(ItemSearchLocation::Inventory))
{
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
for (uint8 i = INVENTORY_SLOT_BAG_START; i < inventoryEnd; ++i)
if (!GetItemByPos(INVENTORY_SLOT_BAG_0, i))
++freeSlotCount;
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
if (Bag* bag = GetBagByPos(i))
for (uint32 j = 0; j < GetBagSize(bag); ++j)
if (!GetItemInBag(bag, j))
++freeSlotCount;
}
if (location.HasFlag(ItemSearchLocation::Bank))
{
for (uint8 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_BAG_END; ++i)
if (!GetItemByPos(INVENTORY_SLOT_BAG_0, i))
++freeSlotCount;
for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
if (Bag* bag = GetBagByPos(i))
for (uint32 j = 0; j < GetBagSize(bag); ++j)
if (!GetItemInBag(bag, j))
++freeSlotCount;
}
if (location.HasFlag(ItemSearchLocation::ReagentBank))
for (uint8 i = REAGENT_SLOT_START; i < REAGENT_SLOT_END; ++i)
if (!GetItemByPos(INVENTORY_SLOT_BAG_0, i))
++freeSlotCount;
return freeSlotCount;
}
InventoryResult Player::CanUnequipItems(uint32 item, uint32 count) const
{
InventoryResult res = EQUIP_ERR_OK;
uint32 tempcount = 0;
bool result = ForEachItem(ItemSearchLocation::Equipment, [this, item, &res, &tempcount, count](Item* pItem)
{
if (pItem->GetEntry() == item)
{
InventoryResult ires = CanUnequipItem(pItem->GetPos(), false);
if (ires == EQUIP_ERR_OK)
{
tempcount += pItem->GetCount();
if (tempcount >= count)
return ItemSearchCallbackResult::Stop;
}
else
res = ires;
}
return ItemSearchCallbackResult::Continue;
});
if (!result) // we stopped early due to a sucess
return EQUIP_ERR_OK;
return res; // return latest error if any
}
uint32 Player::GetItemCount(uint32 item, bool inBankAlso, Item* skipItem) const
{
bool countGems = skipItem && skipItem->GetTemplate()->GetGemProperties();
ItemSearchLocation location = ItemSearchLocation::Equipment | ItemSearchLocation::Inventory | ItemSearchLocation::ReagentBank;
if (inBankAlso)
location |= ItemSearchLocation::Bank;
uint32 count = 0;
ForEachItem(location, [&count, item, skipItem, countGems](Item* pItem)
{
if (pItem != skipItem)
{
if (pItem->GetEntry() == item)
count += pItem->GetCount();
if (countGems)
count += pItem->GetGemCountWithID(item);
}
return ItemSearchCallbackResult::Continue;
});
return count;
}
uint32 Player::GetItemCountWithLimitCategory(uint32 limitCategory, Item* skipItem) const
{
uint32 count = 0;
ForEachItem(ItemSearchLocation::Everywhere, [&count, limitCategory, skipItem](Item* item)
{
if (item != skipItem)
if (ItemTemplate const* pProto = item->GetTemplate())
if (pProto->GetItemLimitCategory() == limitCategory)
count += item->GetCount();
return ItemSearchCallbackResult::Continue;
});
return count;
}
std::vector<Item*> Player::GetCraftingReagentItemsToDeposit()
{
std::vector<Item*> itemList = std::vector<Item*>();
ForEachItem(ItemSearchLocation::Inventory, [&itemList](Item* item)
{
if (item->GetTemplate()->IsCraftingReagent())
itemList.push_back(item);
return ItemSearchCallbackResult::Continue;
});
return itemList;
}
Item* Player::GetItemByGuid(ObjectGuid guid) const
{
Item* result = nullptr;
ForEachItem(ItemSearchLocation::Everywhere, [&result, guid](Item* item)
{
if (item->GetGUID() == guid)
{
result = item;
return ItemSearchCallbackResult::Stop;
}
return ItemSearchCallbackResult::Continue;
});
return result;
}
Item* Player::GetItemByPos(uint16 pos) const
{
uint8 bag = pos >> 8;
uint8 slot = pos & 255;
return GetItemByPos(bag, slot);
}
Item* Player::GetItemByPos(uint8 bag, uint8 slot) const
{
if (bag == INVENTORY_SLOT_BAG_0 && slot < PLAYER_SLOT_END && (slot < BUYBACK_SLOT_START || slot >= BUYBACK_SLOT_END))
return m_items[slot];
if (Bag* pBag = GetBagByPos(bag))
return pBag->GetItemByPos(slot);
return nullptr;
}
//Does additional check for disarmed weapons
Item* Player::GetUseableItemByPos(uint8 bag, uint8 slot) const
{
Item* item = GetItemByPos(bag, slot);
if (!item)
return nullptr;
if (!CanUseAttackType(GetAttackBySlot(slot, item->GetTemplate()->GetInventoryType())))
return nullptr;
return item;
}
Bag* Player::GetBagByPos(uint8 bag) const
{
if ((bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END)
|| (bag >= BANK_SLOT_BAG_START && bag < BANK_SLOT_BAG_END))
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, bag))
return item->ToBag();
return nullptr;
}
uint32 Player::GetFreeInventorySpace() const
{
uint32 freeSpace = 0;
// Check backpack
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
{
Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
if (!item)
freeSpace += 1;
}
// Check bags
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++)
{
if (Bag* bag = GetBagByPos(i))
freeSpace += bag->GetFreeSlots();
}
return freeSpace;
}
Item* Player::GetWeaponForAttack(WeaponAttackType attackType, bool useable /*= false*/) const
{
uint8 slot;
switch (attackType)
{
case BASE_ATTACK: slot = EQUIPMENT_SLOT_MAINHAND; break;
case OFF_ATTACK: slot = EQUIPMENT_SLOT_OFFHAND; break;
case RANGED_ATTACK: slot = EQUIPMENT_SLOT_MAINHAND; break;
default: return nullptr;
}
Item* item;
if (useable)
item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, slot);
else
item = GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
if (!item || item->GetTemplate()->GetClass() != ITEM_CLASS_WEAPON)
return nullptr;
if (!useable)
return item;
if (item->IsBroken())
return nullptr;
return item;
}
Item* Player::GetShield(bool useable) const
{
Item* item;
if (useable)
item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
else
item = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
if (!item || item->GetTemplate()->GetClass() != ITEM_CLASS_ARMOR)
return nullptr;
if (!useable)
return item;
if (item->IsBroken())
return nullptr;
return item;
}
Item* Player::GetChildItemByGuid(ObjectGuid guid) const
{
Item* result = nullptr;
ForEachItem(ItemSearchLocation::Equipment | ItemSearchLocation::Inventory, [&result, guid](Item* item)
{
if (item->GetGUID() == guid)
{
result = item;
return ItemSearchCallbackResult::Stop;
}
return ItemSearchCallbackResult::Continue;
});
return result;
}
WeaponAttackType Player::GetAttackBySlot(uint8 slot, InventoryType inventoryType)
{
switch (slot)
{
case EQUIPMENT_SLOT_MAINHAND: return inventoryType != INVTYPE_RANGED && inventoryType != INVTYPE_RANGEDRIGHT ? BASE_ATTACK : RANGED_ATTACK;
case EQUIPMENT_SLOT_OFFHAND: return OFF_ATTACK;
default: return MAX_ATTACK;
}
}
bool Player::IsInventoryPos(uint8 bag, uint8 slot)
{
if (bag == INVENTORY_SLOT_BAG_0 && slot == NULL_SLOT)
return true;
if (bag == INVENTORY_SLOT_BAG_0 && (slot >= INVENTORY_SLOT_ITEM_START && slot < INVENTORY_SLOT_ITEM_END))
return true;
if (bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END)
return true;
if (bag == INVENTORY_SLOT_BAG_0 && (slot >= CHILD_EQUIPMENT_SLOT_START && slot < CHILD_EQUIPMENT_SLOT_END))
return true;
return false;
}
bool Player::IsEquipmentPos(uint8 bag, uint8 slot)
{
if (bag == INVENTORY_SLOT_BAG_0 && (slot < EQUIPMENT_SLOT_END))
return true;
if (bag == INVENTORY_SLOT_BAG_0 && (slot >= INVENTORY_SLOT_BAG_START && slot < INVENTORY_SLOT_BAG_END))
return true;
return false;
}
bool Player::IsBankPos(uint8 bag, uint8 slot)
{
if (bag == INVENTORY_SLOT_BAG_0 && (slot >= BANK_SLOT_ITEM_START && slot < BANK_SLOT_ITEM_END))
return true;
if (bag == INVENTORY_SLOT_BAG_0 && (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END))
return true;
if (bag >= BANK_SLOT_BAG_START && bag < BANK_SLOT_BAG_END)
return true;
if (bag == INVENTORY_SLOT_BAG_0 && (slot >= REAGENT_SLOT_START && slot < REAGENT_SLOT_END))
return true;
return false;
}
bool Player::IsReagentBankPos(uint8 bag, uint8 slot)
{
if (bag == INVENTORY_SLOT_BAG_0 && (slot >= REAGENT_SLOT_START && slot < REAGENT_SLOT_END))
return true;
return false;
}
bool Player::IsBagPos(uint16 pos)
{
uint8 bag = pos >> 8;
uint8 slot = pos & 255;
if (bag == INVENTORY_SLOT_BAG_0 && (slot >= INVENTORY_SLOT_BAG_START && slot < INVENTORY_SLOT_BAG_END))
return true;
if (bag == INVENTORY_SLOT_BAG_0 && (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END))
return true;
return false;
}
bool Player::IsChildEquipmentPos(uint8 bag, uint8 slot)
{
return bag == INVENTORY_SLOT_BAG_0 && (slot >= CHILD_EQUIPMENT_SLOT_START && slot < CHILD_EQUIPMENT_SLOT_END);
}
bool Player::IsValidPos(uint8 bag, uint8 slot, bool explicit_pos) const
{
// post selected
if (bag == NULL_BAG && !explicit_pos)
return true;
if (bag == INVENTORY_SLOT_BAG_0)
{
// any post selected
if (slot == NULL_SLOT && !explicit_pos)
return true;
// equipment
if (slot < EQUIPMENT_SLOT_END)
return true;
// bag equip slots
if (slot >= INVENTORY_SLOT_BAG_START && slot < INVENTORY_SLOT_BAG_END)
return true;
// backpack slots
if (slot >= INVENTORY_SLOT_ITEM_START && slot < INVENTORY_SLOT_ITEM_START + GetInventorySlotCount())
return true;
// bank main slots
if (slot >= BANK_SLOT_ITEM_START && slot < BANK_SLOT_ITEM_END)
return true;
// bank bag slots
if (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END)
return true;
// reagent bank bag slots
if (slot >= REAGENT_SLOT_START && slot < REAGENT_SLOT_END)
return true;
return false;
}
// bag content slots
// bank bag content slots
if (Bag* pBag = GetBagByPos(bag))
{
// any post selected
if (slot == NULL_SLOT && !explicit_pos)
return true;
return slot < pBag->GetBagSize();
}
// where this?
return false;
}
void Player::SetInventorySlotCount(uint8 slots)
{
ASSERT(slots <= (INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START));
if (slots < GetInventorySlotCount())
{
std::vector<Item*> unstorableItems;
for (uint8 slot = INVENTORY_SLOT_ITEM_START + slots; slot < INVENTORY_SLOT_ITEM_END; ++slot)
if (Item* unstorableItem = GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
unstorableItems.push_back(unstorableItem);
if (!unstorableItems.empty())
{
std::size_t fullBatches = unstorableItems.size() / MAX_MAIL_ITEMS;
std::size_t remainder = unstorableItems.size() % MAX_MAIL_ITEMS;
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
auto sendItemsBatch = [this, &trans, &unstorableItems](std::size_t batchNumber, std::size_t batchSize)
{
MailDraft draft(GetSession()->GetTrinityString(LANG_NOT_EQUIPPED_ITEM), "There were problems with equipping item(s).");
for (std::size_t j = 0; j < batchSize; ++j)
draft.AddItem(unstorableItems[batchNumber * MAX_MAIL_ITEMS + j]);
draft.SendMailTo(trans, this, MailSender(this, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_COPIED);
};
for (std::size_t batch = 0; batch < fullBatches; ++batch)
sendItemsBatch(batch, MAX_MAIL_ITEMS);
if (remainder)
sendItemsBatch(fullBatches, remainder);
CharacterDatabase.CommitTransaction(trans);
SendDirectMessage(WorldPackets::Item::InventoryFullOverflow().Write());
}
}
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::NumBackpackSlots), slots);
}
bool Player::HasItemCount(uint32 item, uint32 count, bool inBankAlso) const
{
ItemSearchLocation location = ItemSearchLocation::Equipment | ItemSearchLocation::Inventory | ItemSearchLocation::ReagentBank;
if (inBankAlso)
location |= ItemSearchLocation::Bank;
uint32 currentCount = 0;
return !ForEachItem(location, [item, count, &currentCount](Item* pItem)
{
if (pItem->GetEntry() == item && !pItem->IsInTrade())
{
currentCount += pItem->GetCount();
if (currentCount >= count)
return ItemSearchCallbackResult::Stop;
}
return ItemSearchCallbackResult::Continue;
});
}
bool Player::HasItemOrGemWithIdEquipped(uint32 item, uint32 count, uint8 except_slot) const
{
uint32 tempcount = 0;
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(item);
bool includeGems = pProto && pProto->GetGemProperties();
return !ForEachItem(ItemSearchLocation::Equipment, [item, &tempcount, count, except_slot, includeGems](Item* pItem)
{
if (pItem->GetSlot() != except_slot)
{
if (pItem->GetEntry() == item)
tempcount += pItem->GetCount();
if (includeGems)
tempcount += pItem->GetGemCountWithID(item);
if (tempcount >= count)
return ItemSearchCallbackResult::Stop;
}
return ItemSearchCallbackResult::Continue;
});
}
bool Player::HasItemWithLimitCategoryEquipped(uint32 limitCategory, uint32 count, uint8 except_slot) const
{
uint32 tempcount = 0;
return !ForEachItem(ItemSearchLocation::Equipment, [&tempcount, limitCategory, count, except_slot](Item* pItem)
{
if (pItem->GetSlot() == except_slot)
return ItemSearchCallbackResult::Continue;
if (pItem->GetTemplate()->GetItemLimitCategory() != limitCategory)
return ItemSearchCallbackResult::Continue;
tempcount += pItem->GetCount();
if (tempcount >= count)
return ItemSearchCallbackResult::Stop;
return ItemSearchCallbackResult::Continue;
});
}
bool Player::HasGemWithLimitCategoryEquipped(uint32 limitCategory, uint32 count, uint8 except_slot) const
{
uint32 tempcount = 0;
return !ForEachItem(ItemSearchLocation::Equipment, [&tempcount, limitCategory, count, except_slot](Item* pItem)
{
if (pItem->GetSlot() == except_slot)
return ItemSearchCallbackResult::Continue;
ItemTemplate const* pProto = pItem->GetTemplate();
if (!pProto)
return ItemSearchCallbackResult::Continue;
tempcount += pItem->GetGemCountWithLimitCategory(limitCategory);
if (tempcount >= count)
return ItemSearchCallbackResult::Stop;
return ItemSearchCallbackResult::Continue;
});
}
InventoryResult Player::CanTakeMoreSimilarItems(uint32 entry, uint32 count, Item* pItem, uint32* no_space_count /*= nullptr*/, uint32* offendingItemId /*= nullptr*/) const
{
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(entry);
if (!pProto)
{
if (no_space_count)
*no_space_count = count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
if (pItem && pItem->m_lootGenerated)
return EQUIP_ERR_LOOT_GONE;
// no maximum
if ((pProto->GetMaxCount() <= 0 && pProto->GetItemLimitCategory() == 0) || pProto->GetMaxCount() == 2147483647)
return EQUIP_ERR_OK;
if (pProto->GetMaxCount() > 0)
{
uint32 curcount = GetItemCount(pProto->GetId(), true, pItem);
if (curcount + count > uint32(pProto->GetMaxCount()))
{
if (no_space_count)
*no_space_count = count + curcount - pProto->GetMaxCount();
return EQUIP_ERR_ITEM_MAX_COUNT;
}
}
// check unique-equipped limit
if (pProto->GetItemLimitCategory())
{
ItemLimitCategoryEntry const* limitEntry = sItemLimitCategoryStore.LookupEntry(pProto->GetItemLimitCategory());
if (!limitEntry)
{
if (no_space_count)
*no_space_count = count;
return EQUIP_ERR_NOT_EQUIPPABLE;
}
if (limitEntry->Flags == ITEM_LIMIT_CATEGORY_MODE_HAVE)
{
uint8 limitQuantity = GetItemLimitCategoryQuantity(limitEntry);
uint32 curcount = GetItemCountWithLimitCategory(pProto->GetItemLimitCategory(), pItem);
if (curcount + count > uint32(limitQuantity))
{
if (no_space_count)
*no_space_count = count + curcount - limitQuantity;
if (offendingItemId)
*offendingItemId = pProto->GetId();
return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED_IS;
}
}
}
return EQUIP_ERR_OK;
}
InventoryResult Player::CanTakeMoreSimilarItems(Item* pItem, uint32* offendingItemId /*= nullptr*/) const
{
return CanTakeMoreSimilarItems(pItem->GetEntry(), pItem->GetCount(), pItem, nullptr, offendingItemId);
}
InventoryResult Player::CanStoreNewItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, uint32 item, uint32 count, uint32* no_space_count /*= nullptr*/) const
{
return CanStoreItem(bag, slot, dest, item, count, nullptr, false, no_space_count);
}
InventoryResult Player::CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap /*= false*/) const
{
if (!pItem)
return EQUIP_ERR_ITEM_NOT_FOUND;
uint32 count = pItem->GetCount();
return CanStoreItem(bag, slot, dest, pItem->GetEntry(), count, pItem, swap, nullptr);
}
bool Player::HasItemTotemCategory(uint32 TotemCategory) const
{
for (AuraEffect const* providedTotemCategory : GetAuraEffectsByType(SPELL_AURA_PROVIDE_TOTEM_CATEGORY))
if (DB2Manager::IsTotemCategoryCompatibleWith(providedTotemCategory->GetMiscValueB(), TotemCategory))
return true;
Item* item;
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
for (uint8 i = EQUIPMENT_SLOT_START; i < inventoryEnd; ++i)
{
item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, i);
if (item && DB2Manager::IsTotemCategoryCompatibleWith(item->GetTemplate()->GetTotemCategory(), TotemCategory))
return true;
}
Bag* bag;
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
{
bag = GetBagByPos(i);
if (bag)
{
for (uint32 j = 0; j < bag->GetBagSize(); ++j)
{
item = GetUseableItemByPos(i, j);
if (item && DB2Manager::IsTotemCategoryCompatibleWith(item->GetTemplate()->GetTotemCategory(), TotemCategory))
return true;
}
}
}
for (uint8 i = CHILD_EQUIPMENT_SLOT_START; i < CHILD_EQUIPMENT_SLOT_END; ++i)
{
item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, i);
if (item && DB2Manager::IsTotemCategoryCompatibleWith(item->GetTemplate()->GetTotemCategory(), TotemCategory))
return true;
}
return false;
}
InventoryResult Player::CanStoreItem_InSpecificSlot(uint8 bag, uint8 slot, ItemPosCountVec &dest, ItemTemplate const* pProto, uint32& count, bool swap, Item* pSrcItem) const
{
Item* pItem2 = GetItemByPos(bag, slot);
// ignore move item (this slot will be empty at move)
if (pItem2 == pSrcItem)
pItem2 = nullptr;
uint32 need_space;
if (pSrcItem)
{
if (pSrcItem->IsNotEmptyBag() && !IsBagPos(uint16(bag) << 8 | slot))
return EQUIP_ERR_DESTROY_NONEMPTY_BAG;
if (pSrcItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD) && !IsEquipmentPos(bag, slot) && !IsChildEquipmentPos(bag, slot))
return EQUIP_ERR_WRONG_BAG_TYPE_3;
if (!pSrcItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD) && IsChildEquipmentPos(bag, slot))
return EQUIP_ERR_WRONG_BAG_TYPE_3;
}
// empty specific slot - check item fit to slot
if (!pItem2 || swap)
{
if (bag == INVENTORY_SLOT_BAG_0)
{
// prevent cheating
if ((slot >= BUYBACK_SLOT_START && slot < BUYBACK_SLOT_END) || slot >= PLAYER_SLOT_END)
return EQUIP_ERR_WRONG_BAG_TYPE;
// can't store anything else than crafting reagents in Reagent Bank
if (IsReagentBankPos(bag, slot) && (!IsReagentBankUnlocked() || !pProto->IsCraftingReagent()))
return EQUIP_ERR_WRONG_BAG_TYPE;
}
else
{
Bag* pBag = GetBagByPos(bag);
if (!pBag)
return EQUIP_ERR_WRONG_BAG_TYPE;
ItemTemplate const* pBagProto = pBag->GetTemplate();
if (!pBagProto)
return EQUIP_ERR_WRONG_BAG_TYPE;
if (slot >= pBagProto->GetContainerSlots())
return EQUIP_ERR_WRONG_BAG_TYPE;
if (!ItemCanGoIntoBag(pProto, pBagProto))
return EQUIP_ERR_WRONG_BAG_TYPE;
}
// non empty stack with space
need_space = pProto->GetMaxStackSize();
}
// non empty slot, check item type
else
{
// can be merged at least partly
InventoryResult res = pItem2->CanBeMergedPartlyWith(pProto);
if (res != EQUIP_ERR_OK)
return res;
// free stack space or infinity
need_space = pProto->GetMaxStackSize() - pItem2->GetCount();
}
if (need_space > count)
need_space = count;
ItemPosCount newPosition = ItemPosCount((bag << 8) | slot, need_space);
if (!newPosition.isContainedIn(dest))
{
dest.push_back(newPosition);
count -= need_space;
}
return EQUIP_ERR_OK;
}
InventoryResult Player::CanStoreItem_InBag(uint8 bag, ItemPosCountVec &dest, ItemTemplate const* pProto, uint32& count, bool merge, bool non_specialized, Item* pSrcItem, uint8 skip_bag, uint8 skip_slot) const
{
// skip specific bag already processed in first called CanStoreItem_InBag
if (bag == skip_bag)
return EQUIP_ERR_WRONG_BAG_TYPE;
// skip non-existing bag or self targeted bag
Bag* pBag = GetBagByPos(bag);
if (!pBag || pBag == pSrcItem)
return EQUIP_ERR_WRONG_BAG_TYPE;
if (pSrcItem)
{
if (pSrcItem->IsNotEmptyBag())
return EQUIP_ERR_DESTROY_NONEMPTY_BAG;
if (pSrcItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD))
return EQUIP_ERR_WRONG_BAG_TYPE_3;
}
ItemTemplate const* pBagProto = pBag->GetTemplate();
if (!pBagProto)
return EQUIP_ERR_WRONG_BAG_TYPE;
// specialized bag mode or non-specialized
if (non_specialized != (pBagProto->GetClass() == ITEM_CLASS_CONTAINER && pBagProto->GetSubClass() == ITEM_SUBCLASS_CONTAINER))
return EQUIP_ERR_WRONG_BAG_TYPE;
if (!ItemCanGoIntoBag(pProto, pBagProto))
return EQUIP_ERR_WRONG_BAG_TYPE;
for (uint32 j = 0; j < pBag->GetBagSize(); j++)
{
// skip specific slot already processed in first called CanStoreItem_InSpecificSlot
if (j == skip_slot)
continue;
Item* pItem2 = GetItemByPos(bag, j);
// ignore move item (this slot will be empty at move)
if (pItem2 == pSrcItem)
pItem2 = nullptr;
// if merge skip empty, if !merge skip non-empty
if ((pItem2 != nullptr) != merge)
continue;
uint32 need_space = pProto->GetMaxStackSize();
if (pItem2)
{
// can be merged at least partly
if (pItem2->CanBeMergedPartlyWith(pProto) != EQUIP_ERR_OK)
continue;
// descrease at current stacksize
need_space -= pItem2->GetCount();
}
if (need_space > count)
need_space = count;
ItemPosCount newPosition = ItemPosCount((bag << 8) | j, need_space);
if (!newPosition.isContainedIn(dest))
{
dest.push_back(newPosition);
count -= need_space;
if (count==0)
return EQUIP_ERR_OK;
}
}
return EQUIP_ERR_OK;
}
InventoryResult Player::CanStoreItem_InInventorySlots(uint8 slot_begin, uint8 slot_end, ItemPosCountVec &dest, ItemTemplate const* pProto, uint32& count, bool merge, Item* pSrcItem, uint8 skip_bag, uint8 skip_slot) const
{
//this is never called for non-bag slots so we can do this
if (pSrcItem && pSrcItem->IsNotEmptyBag())
return EQUIP_ERR_DESTROY_NONEMPTY_BAG;
for (uint32 j = slot_begin; j < slot_end; j++)
{
// skip specific slot already processed in first called CanStoreItem_InSpecificSlot
if (INVENTORY_SLOT_BAG_0 == skip_bag && j == skip_slot)
continue;
Item* pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, j);
// ignore move item (this slot will be empty at move)
if (pItem2 == pSrcItem)
pItem2 = nullptr;
// if merge skip empty, if !merge skip non-empty
if ((pItem2 != nullptr) != merge)
continue;
uint32 need_space = pProto->GetMaxStackSize();
if (pItem2)
{
// can be merged at least partly
if (pItem2->CanBeMergedPartlyWith(pProto) != EQUIP_ERR_OK)
continue;
// descrease at current stacksize
need_space -= pItem2->GetCount();
}
if (need_space > count)
need_space = count;
ItemPosCount newPosition = ItemPosCount((INVENTORY_SLOT_BAG_0 << 8) | j, need_space);
if (!newPosition.isContainedIn(dest))
{
dest.push_back(newPosition);
count -= need_space;
if (count==0)
return EQUIP_ERR_OK;
}
}
return EQUIP_ERR_OK;
}
InventoryResult Player::CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec &dest, uint32 entry, uint32 count, Item* pItem, bool swap, uint32* no_space_count) const
{
TC_LOG_DEBUG("entities.player.items", "Player::CanStoreItem: Bag: %u, Slot: %u, Item: %u, Count: %u", bag, slot, entry, count);
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(entry);
if (!pProto)
{
if (no_space_count)
*no_space_count = count;
return swap ? EQUIP_ERR_CANT_SWAP : EQUIP_ERR_ITEM_NOT_FOUND;
}
if (pItem)
{
// item used
if (pItem->m_lootGenerated)
{
if (no_space_count)
*no_space_count = count;
return EQUIP_ERR_LOOT_GONE;
}
if (pItem->IsBindedNotWith(this))
{
if (no_space_count)
*no_space_count = count;
return EQUIP_ERR_NOT_OWNER;
}
}
// check count of items (skip for auto move for same player from bank)
uint32 no_similar_count = 0; // can't store this amount similar items
InventoryResult res = CanTakeMoreSimilarItems(entry, count, pItem, &no_similar_count);
if (res != EQUIP_ERR_OK)
{
if (count == no_similar_count)
{
if (no_space_count)
*no_space_count = no_similar_count;
return res;
}
count -= no_similar_count;
}
// in specific slot
if (bag != NULL_BAG && slot != NULL_SLOT)
{
res = CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, count, swap, pItem);
if (res != EQUIP_ERR_OK)
{
if (no_space_count)
*no_space_count = count + no_similar_count;
return res;
}
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
}
// not specific slot or have space for partly store only in specific slot
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
// in specific bag
if (bag != NULL_BAG)
{
// search stack in bag for merge to
if (pProto->GetMaxStackSize() != 1)
{
if (bag == INVENTORY_SLOT_BAG_0) // inventory
{
res = CanStoreItem_InInventorySlots(CHILD_EQUIPMENT_SLOT_START, CHILD_EQUIPMENT_SLOT_END, dest, pProto, count, true, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
{
if (no_space_count)
*no_space_count = count + no_similar_count;
return res;
}
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
res = CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, inventoryEnd, dest, pProto, count, true, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
{
if (no_space_count)
*no_space_count = count + no_similar_count;
return res;
}
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
}
else // equipped bag
{
// we need check 2 time (specialized/non_specialized), use NULL_BAG to prevent skipping bag
res = CanStoreItem_InBag(bag, dest, pProto, count, true, false, pItem, NULL_BAG, slot);
if (res != EQUIP_ERR_OK)
res = CanStoreItem_InBag(bag, dest, pProto, count, true, true, pItem, NULL_BAG, slot);
if (res != EQUIP_ERR_OK)
{
if (no_space_count)
*no_space_count = count + no_similar_count;
return res;
}
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
}
}
// search free slot in bag for place to
if (bag == INVENTORY_SLOT_BAG_0) // inventory
{
if (pItem && pItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD))
{
res = CanStoreItem_InInventorySlots(CHILD_EQUIPMENT_SLOT_START, CHILD_EQUIPMENT_SLOT_END, dest, pProto, count, false, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
{
if (no_space_count)
*no_space_count = count + no_similar_count;
return res;
}
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
}
res = CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, inventoryEnd, dest, pProto, count, false, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
{
if (no_space_count)
*no_space_count = count + no_similar_count;
return res;
}
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
}
else // equipped bag
{
res = CanStoreItem_InBag(bag, dest, pProto, count, false, false, pItem, NULL_BAG, slot);
if (res != EQUIP_ERR_OK)
res = CanStoreItem_InBag(bag, dest, pProto, count, false, true, pItem, NULL_BAG, slot);
if (res != EQUIP_ERR_OK)
{
if (no_space_count)
*no_space_count = count + no_similar_count;
return res;
}
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
}
}
// not specific bag or have space for partly store only in specific bag
// search stack for merge to
if (pProto->GetMaxStackSize() != 1)
{
res = CanStoreItem_InInventorySlots(CHILD_EQUIPMENT_SLOT_START, CHILD_EQUIPMENT_SLOT_END, dest, pProto, count, true, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
{
if (no_space_count)
*no_space_count = count + no_similar_count;
return res;
}
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
res = CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, inventoryEnd, dest, pProto, count, true, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
{
if (no_space_count)
*no_space_count = count + no_similar_count;
return res;
}
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
if (pProto->GetBagFamily())
{
for (uint32 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++)
{
res = CanStoreItem_InBag(i, dest, pProto, count, true, false, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
continue;
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
}
}
for (uint32 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++)
{
res = CanStoreItem_InBag(i, dest, pProto, count, true, true, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
continue;
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
}
}
// search free slot - special bag case
if (pProto->GetBagFamily())
{
for (uint32 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++)
{
res = CanStoreItem_InBag(i, dest, pProto, count, false, false, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
continue;
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
}
}
if (pItem && pItem->IsNotEmptyBag())
return EQUIP_ERR_BAG_IN_BAG;
if (pItem && pItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD))
{
res = CanStoreItem_InInventorySlots(CHILD_EQUIPMENT_SLOT_START, CHILD_EQUIPMENT_SLOT_END, dest, pProto, count, false, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
{
if (no_space_count)
*no_space_count = count + no_similar_count;
return res;
}
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
}
// search free slot
uint8 searchSlotStart = INVENTORY_SLOT_ITEM_START;
// new bags can be directly equipped
if (!pItem && pProto->GetClass() == ITEM_CLASS_CONTAINER && pProto->GetSubClass() == ITEM_SUBCLASS_CONTAINER &&
(pProto->GetBonding() == BIND_NONE || pProto->GetBonding() == BIND_ON_ACQUIRE))
searchSlotStart = INVENTORY_SLOT_BAG_START;
res = CanStoreItem_InInventorySlots(searchSlotStart, inventoryEnd, dest, pProto, count, false, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
{
if (no_space_count)
*no_space_count = count + no_similar_count;
return res;
}
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++)
{
res = CanStoreItem_InBag(i, dest, pProto, count, false, true, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
continue;
if (count == 0)
{
if (no_similar_count == 0)
return EQUIP_ERR_OK;
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_ITEM_MAX_COUNT;
}
}
if (no_space_count)
*no_space_count = count + no_similar_count;
return EQUIP_ERR_INV_FULL;
}
//////////////////////////////////////////////////////////////////////////
InventoryResult Player::CanStoreItems(Item** items, int count, uint32* offendingItemId) const
{
Item* item2;
// fill space tables, creating a mock-up of the player's inventory
// counts
uint32 inventoryCounts[INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START] = {};
uint32 bagCounts[INVENTORY_SLOT_BAG_END - INVENTORY_SLOT_BAG_START][MAX_BAG_SIZE] = {};
// Item pointers
Item* inventoryPointers[INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START] = {};
Item* bagPointers[INVENTORY_SLOT_BAG_END - INVENTORY_SLOT_BAG_START][MAX_BAG_SIZE] = {};
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
// filling inventory
for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; i++)
{
// build items in stock backpack
item2 = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
if (item2 && !item2->IsInTrade())
{
inventoryCounts[i - INVENTORY_SLOT_ITEM_START] = item2->GetCount();
inventoryPointers[i - INVENTORY_SLOT_ITEM_START] = item2;
}
}
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++)
{
// build item counts in equippable bags
item2 = GetItemByPos(i, j);
if (item2 && !item2->IsInTrade())
{
bagCounts[i - INVENTORY_SLOT_BAG_START][j] = item2->GetCount();
bagPointers[i - INVENTORY_SLOT_BAG_START][j] = item2;
}
}
// check free space for all items that we wish to add
for (int k = 0; k < count; ++k)
{
// Incoming item
Item* item = items[k];
// no item
if (!item)
continue;
uint32_t remaining_count = item->GetCount();
TC_LOG_DEBUG("entities.player.items", "Player::CanStoreItems: Player '%s' (%s), Index: %i ItemID: %u, Count: %u",
GetName().c_str(), GetGUID().ToString().c_str(), k + 1, item->GetEntry(), remaining_count);
ItemTemplate const* pProto = item->GetTemplate();
// strange item
if (!pProto)
return EQUIP_ERR_ITEM_NOT_FOUND;
// item used
if (item->m_lootGenerated)
return EQUIP_ERR_LOOT_GONE;
// item it 'bind'
if (item->IsBindedNotWith(this))
return EQUIP_ERR_NOT_OWNER;
ItemTemplate const* pBagProto;
// item is 'one item only'
InventoryResult res = CanTakeMoreSimilarItems(item, offendingItemId);
if (res != EQUIP_ERR_OK)
return res;
// search stack for merge to
if (pProto->GetMaxStackSize() != 1)
{
bool b_found = false;
for (int t = INVENTORY_SLOT_ITEM_START; t < inventoryEnd; ++t)
{
item2 = inventoryPointers[t-INVENTORY_SLOT_ITEM_START];
if (item2 && item2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && inventoryCounts[t-INVENTORY_SLOT_ITEM_START] < pProto->GetMaxStackSize())
{
inventoryCounts[t-INVENTORY_SLOT_ITEM_START] += remaining_count;
remaining_count = inventoryCounts[t-INVENTORY_SLOT_ITEM_START] < pProto->GetMaxStackSize() ? 0 : inventoryCounts[t-INVENTORY_SLOT_ITEM_START] - pProto->GetMaxStackSize();
b_found = remaining_count == 0;
// if no pieces of the stack remain, then stop checking stock bag
if (b_found)
break;
}
}
if (b_found)
continue;
for (int t = INVENTORY_SLOT_BAG_START; !b_found && t < INVENTORY_SLOT_BAG_END; ++t)
{
if (Bag* bag = GetBagByPos(t))
{
if (!ItemCanGoIntoBag(item->GetTemplate(), bag->GetTemplate()))
continue;
for (uint32 j = 0; j < bag->GetBagSize(); j++)
{
item2 = bagPointers[t-INVENTORY_SLOT_BAG_START][j];
if (item2 && item2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && bagCounts[t-INVENTORY_SLOT_BAG_START][j] < pProto->GetMaxStackSize())
{
// add count to stack so that later items in the list do not double-book
bagCounts[t-INVENTORY_SLOT_BAG_START][j] += remaining_count;
remaining_count = bagCounts[t-INVENTORY_SLOT_BAG_START][j] < pProto->GetMaxStackSize() ? 0 : bagCounts[t-INVENTORY_SLOT_BAG_START][j] - pProto->GetMaxStackSize();
b_found = remaining_count == 0;
// if no pieces of the stack remain, then stop checking equippable bags
if (b_found)
break;
}
}
}
}
if (b_found)
continue;
}
// special bag case
if (pProto->GetBagFamily())
{
bool b_found = false;
for (int t = INVENTORY_SLOT_BAG_START; !b_found && t < INVENTORY_SLOT_BAG_END; ++t)
{
if (Bag* bag = GetBagByPos(t))
{
pBagProto = bag->GetTemplate();
// not plain container check
if (pBagProto && (pBagProto->GetClass() != ITEM_CLASS_CONTAINER || pBagProto->GetSubClass() != ITEM_SUBCLASS_CONTAINER) &&
ItemCanGoIntoBag(pProto, pBagProto))
{
for (uint32 j = 0; j < bag->GetBagSize(); j++)
{
if (bagCounts[t-INVENTORY_SLOT_BAG_START][j] == 0)
{
bagCounts[t-INVENTORY_SLOT_BAG_START][j] = remaining_count;
bagPointers[t-INVENTORY_SLOT_BAG_START][j] = item;
b_found = true;
break;
}
}
}
}
}
if (b_found)
continue;
}
// search free slot
bool b_found = false;
for (int t = INVENTORY_SLOT_ITEM_START; t < inventoryEnd; ++t)
{
if (inventoryCounts[t-INVENTORY_SLOT_ITEM_START] == 0)
{
inventoryCounts[t-INVENTORY_SLOT_ITEM_START] = remaining_count;
inventoryPointers[t-INVENTORY_SLOT_ITEM_START] = item;
b_found = true;
break;
}
}
if (b_found)
continue;
// search free slot in bags
for (uint8 t = INVENTORY_SLOT_BAG_START; !b_found && t < INVENTORY_SLOT_BAG_END; ++t)
{
if (Bag* bag = GetBagByPos(t))
{
pBagProto = bag->GetTemplate();
// special bag already checked
if (pBagProto && (pBagProto->GetClass() != ITEM_CLASS_CONTAINER || pBagProto->GetSubClass() != ITEM_SUBCLASS_CONTAINER))
continue;
for (uint32 j = 0; j < bag->GetBagSize(); j++)
{
if (bagCounts[t - INVENTORY_SLOT_BAG_START][j] == 0)
{
bagCounts[t-INVENTORY_SLOT_BAG_START][j] = remaining_count;
bagPointers[t-INVENTORY_SLOT_BAG_START][j] = item;
b_found = true;
break;
}
}
}
}
// if no free slot found for all pieces of the item, then return an error
if (!b_found)
return EQUIP_ERR_BAG_FULL;
}
return EQUIP_ERR_OK;
}
//////////////////////////////////////////////////////////////////////////
InventoryResult Player::CanEquipNewItem(uint8 slot, uint16 &dest, uint32 item, bool swap) const
{
dest = 0;
Item* pItem = Item::CreateItem(item, 1, ItemContext::NONE, this);
if (pItem)
{
InventoryResult result = CanEquipItem(slot, dest, pItem, swap);
delete pItem;
return result;
}
return EQUIP_ERR_ITEM_NOT_FOUND;
}
InventoryResult Player::CanEquipItem(uint8 slot, uint16 &dest, Item* pItem, bool swap, bool not_loading) const
{
dest = 0;
if (pItem)
{
TC_LOG_DEBUG("entities.player.items", "Player::CanEquipItem: Player '%s' (%s), Slot: %u, Item: %u, Count: %u",
GetName().c_str(), GetGUID().ToString().c_str(), slot, pItem->GetEntry(), pItem->GetCount());
ItemTemplate const* pProto = pItem->GetTemplate();
if (pProto)
{
// item used
if (pItem->m_lootGenerated)
return EQUIP_ERR_LOOT_GONE;
if (pItem->IsBindedNotWith(this))
return EQUIP_ERR_NOT_OWNER;
// check count of items (skip for auto move for same player from bank)
InventoryResult res = CanTakeMoreSimilarItems(pItem);
if (res != EQUIP_ERR_OK)
return res;
// check this only in game
if (not_loading)
{
// May be here should be more stronger checks; STUNNED checked
// ROOT, CONFUSED, DISTRACTED, FLEEING this needs to be checked.
if (HasUnitState(UNIT_STATE_STUNNED))
return EQUIP_ERR_GENERIC_STUNNED;
if (IsCharmed())
return EQUIP_ERR_CLIENT_LOCKED_OUT; // @todo is this the correct error?
// do not allow equipping gear except weapons, offhands, projectiles, relics in
// - combat
// - in-progress arenas
if (!pProto->CanChangeEquipStateInCombat())
{
if (IsInCombat())
return EQUIP_ERR_NOT_IN_COMBAT;
if (Battleground* bg = GetBattleground())
if (bg->isArena() && bg->GetStatus() == STATUS_IN_PROGRESS)
return EQUIP_ERR_NOT_DURING_ARENA_MATCH;
}
if (IsInCombat() && (pProto->GetClass() == ITEM_CLASS_WEAPON || pProto->GetInventoryType() == INVTYPE_RELIC) && m_weaponChangeTimer != 0)
return EQUIP_ERR_CLIENT_LOCKED_OUT; // maybe exist better err
if (IsNonMeleeSpellCast(false))
return EQUIP_ERR_CLIENT_LOCKED_OUT;
}
Optional<ContentTuningLevels> requiredLevels;
// check allowed level (extend range to upper values if MaxLevel more or equal max player level, this let GM set high level with 1...max range items)
if (pItem->GetQuality() == ITEM_QUALITY_HEIRLOOM)
requiredLevels = sDB2Manager.GetContentTuningData(pItem->GetScalingContentTuningId(), 0, true);
if (requiredLevels && requiredLevels->MaxLevel < DEFAULT_MAX_LEVEL && requiredLevels->MaxLevel < GetLevel() && !sDB2Manager.GetHeirloomByItemId(pProto->GetId()))
return EQUIP_ERR_NOT_EQUIPPABLE;
uint8 eslot = FindEquipSlot(pItem, slot, swap);
if (eslot == NULL_SLOT)
return EQUIP_ERR_NOT_EQUIPPABLE;
res = CanUseItem(pItem, not_loading);
if (res != EQUIP_ERR_OK)
return res;
if (!swap && GetItemByPos(INVENTORY_SLOT_BAG_0, eslot))
return EQUIP_ERR_NO_SLOT_AVAILABLE;
// if we are swapping 2 equiped items, CanEquipUniqueItem check
// should ignore the item we are trying to swap, and not the
// destination item. CanEquipUniqueItem should ignore destination
// item only when we are swapping weapon from bag
uint8 ignore = uint8(NULL_SLOT);
switch (eslot)
{
case EQUIPMENT_SLOT_MAINHAND:
ignore = EQUIPMENT_SLOT_OFFHAND;
break;
case EQUIPMENT_SLOT_OFFHAND:
ignore = EQUIPMENT_SLOT_MAINHAND;
break;
case EQUIPMENT_SLOT_FINGER1:
ignore = EQUIPMENT_SLOT_FINGER2;
break;
case EQUIPMENT_SLOT_FINGER2:
ignore = EQUIPMENT_SLOT_FINGER1;
break;
case EQUIPMENT_SLOT_TRINKET1:
ignore = EQUIPMENT_SLOT_TRINKET2;
break;
case EQUIPMENT_SLOT_TRINKET2:
ignore = EQUIPMENT_SLOT_TRINKET1;
break;
}
if (ignore == uint8(NULL_SLOT) || pItem != GetItemByPos(INVENTORY_SLOT_BAG_0, ignore))
ignore = eslot;
InventoryResult res2 = CanEquipUniqueItem(pItem, swap ? ignore : uint8(NULL_SLOT));
if (res2 != EQUIP_ERR_OK)
return res2;
// check unique-equipped special item classes
if (pProto->GetClass() == ITEM_CLASS_QUIVER)
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
if (Item* pBag = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
if (pBag != pItem)
if (ItemTemplate const* pBagProto = pBag->GetTemplate())
if (pBagProto->GetClass() == pProto->GetClass() && (!swap || pBag->GetSlot() != eslot))
return (pBagProto->GetSubClass() == ITEM_SUBCLASS_AMMO_POUCH)
? EQUIP_ERR_ONLY_ONE_AMMO
: EQUIP_ERR_ONLY_ONE_QUIVER;
uint32 type = pProto->GetInventoryType();
if (eslot == EQUIPMENT_SLOT_OFFHAND)
{
// Do not allow polearm to be equipped in the offhand (rare case for the only 1h polearm 41750)
if (type == INVTYPE_WEAPON && pProto->GetSubClass() == ITEM_SUBCLASS_WEAPON_POLEARM)
return EQUIP_ERR_2HSKILLNOTFOUND;
else if (type == INVTYPE_WEAPON)
{
if (!CanDualWield())
return EQUIP_ERR_2HSKILLNOTFOUND;
}
else if (type == INVTYPE_WEAPONOFFHAND)
{
if (!CanDualWield() && !pProto->HasFlag(ITEM_FLAG3_ALWAYS_ALLOW_DUAL_WIELD))
return EQUIP_ERR_2HSKILLNOTFOUND;
}
else if (type == INVTYPE_2HWEAPON)
{
if (!CanDualWield() || !CanTitanGrip())
return EQUIP_ERR_2HSKILLNOTFOUND;
}
if (IsTwoHandUsed())
return EQUIP_ERR_2HANDED_EQUIPPED;
}
// equip two-hand weapon case (with possible unequip 2 items)
if (type == INVTYPE_2HWEAPON)
{
if (eslot == EQUIPMENT_SLOT_OFFHAND)
{
if (!CanTitanGrip())
return EQUIP_ERR_NOT_EQUIPPABLE;
}
else if (eslot != EQUIPMENT_SLOT_MAINHAND)
return EQUIP_ERR_NOT_EQUIPPABLE;
if (!CanTitanGrip())
{
// offhand item must can be stored in inventory for offhand item and it also must be unequipped
Item* offItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
ItemPosCountVec off_dest;
if (offItem && (!not_loading ||
CanUnequipItem(uint16(INVENTORY_SLOT_BAG_0) << 8 | EQUIPMENT_SLOT_OFFHAND, false) != EQUIP_ERR_OK ||
CanStoreItem(NULL_BAG, NULL_SLOT, off_dest, offItem, false) != EQUIP_ERR_OK))
return swap ? EQUIP_ERR_CANT_SWAP : EQUIP_ERR_INV_FULL;
}
}
dest = ((INVENTORY_SLOT_BAG_0 << 8) | eslot);
return EQUIP_ERR_OK;
}
}
return !swap ? EQUIP_ERR_ITEM_NOT_FOUND : EQUIP_ERR_CANT_SWAP;
}
InventoryResult Player::CanEquipChildItem(Item* parentItem) const
{
Item* childItem = GetChildItemByGuid(parentItem->GetChildItem());
if (!childItem)
return EQUIP_ERR_OK;
ItemChildEquipmentEntry const* childEquipement = sDB2Manager.GetItemChildEquipment(parentItem->GetEntry());
if (!childEquipement)
return EQUIP_ERR_OK;
Item* dstItem = GetItemByPos(INVENTORY_SLOT_BAG_0, childEquipement->ChildItemEquipSlot);
if (!dstItem)
return EQUIP_ERR_OK;
uint16 childDest = (INVENTORY_SLOT_BAG_0 << 8) | childEquipement->ChildItemEquipSlot;
InventoryResult msg = CanUnequipItem(childDest, !childItem->IsBag());
if (msg != EQUIP_ERR_OK)
return msg;
// check dest->src move possibility
uint16 src = parentItem->GetPos();
ItemPosCountVec dest;
if (IsInventoryPos(src))
{
msg = CanStoreItem(parentItem->GetBagSlot(), NULL_SLOT, dest, dstItem, true);
if (msg != EQUIP_ERR_OK)
msg = CanStoreItem(NULL_BAG, NULL_SLOT, dest, dstItem, true);
}
else if (IsBankPos(src))
{
msg = CanBankItem(parentItem->GetBagSlot(), NULL_SLOT, dest, dstItem, true);
if (msg != EQUIP_ERR_OK)
msg = CanBankItem(NULL_BAG, NULL_SLOT, dest, dstItem, true);
}
else if (IsEquipmentPos(src))
return EQUIP_ERR_CANT_SWAP;
return msg;
}
InventoryResult Player::CanUnequipItem(uint16 pos, bool swap) const
{
// Applied only to equipped items and bank bags
if (!IsEquipmentPos(pos) && !IsBagPos(pos))
return EQUIP_ERR_OK;
Item* pItem = GetItemByPos(pos);
// Applied only to existing equipped item
if (!pItem)
return EQUIP_ERR_OK;
TC_LOG_DEBUG("entities.player.items", "Player::CanUnequipItem: Player '%s' (%s), Slot: %u, Item: %u, Count: %u",
GetName().c_str(), GetGUID().ToString().c_str(), pos, pItem->GetEntry(), pItem->GetCount());
ItemTemplate const* pProto = pItem->GetTemplate();
if (!pProto)
return EQUIP_ERR_ITEM_NOT_FOUND;
// item used
if (pItem->m_lootGenerated)
return EQUIP_ERR_LOOT_GONE;
if (IsCharmed())
return EQUIP_ERR_CLIENT_LOCKED_OUT; // @todo is this the correct error?
// do not allow unequipping gear except weapons, offhands, projectiles, relics in
// - combat
// - in-progress arenas
if (!pProto->CanChangeEquipStateInCombat())
{
if (IsInCombat())
return EQUIP_ERR_NOT_IN_COMBAT;
if (Battleground* bg = GetBattleground())
if (bg->isArena() && bg->GetStatus() == STATUS_IN_PROGRESS)
return EQUIP_ERR_NOT_DURING_ARENA_MATCH;
}
if (!swap && pItem->IsNotEmptyBag())
return EQUIP_ERR_DESTROY_NONEMPTY_BAG;
return EQUIP_ERR_OK;
}
InventoryResult Player::CanBankItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap, bool not_loading /*= true*/, bool reagentBankOnly /*= false*/) const
{
if (!pItem)
return swap ? EQUIP_ERR_CANT_SWAP : EQUIP_ERR_ITEM_NOT_FOUND;
// different slots range if we're trying to store item in Reagent Bank
if (reagentBankOnly)
{
ASSERT(bag == NULL_BAG && slot == NULL_SLOT); // when reagentBankOnly is true then bag & slot must be hardcoded constants, not client input
}
if ((IsReagentBankPos(bag, slot) || reagentBankOnly) && !IsReagentBankUnlocked())
return EQUIP_ERR_REAGENT_BANK_LOCKED;
uint8 slotStart = reagentBankOnly ? uint8(REAGENT_SLOT_START) : uint8(BANK_SLOT_ITEM_START);
uint8 slotEnd = reagentBankOnly ? uint8(REAGENT_SLOT_END) : uint8(BANK_SLOT_ITEM_END);
uint32 count = pItem->GetCount();
TC_LOG_DEBUG("entities.player.items", "Player::CanBankItem: Player '%s' (%s), Bag: %u, Slot: %u, Item: %u, Count: %u",
GetName().c_str(), GetGUID().ToString().c_str(), bag, slot, pItem->GetEntry(), pItem->GetCount());
ItemTemplate const* pProto = pItem->GetTemplate();
if (!pProto)
return swap ? EQUIP_ERR_CANT_SWAP : EQUIP_ERR_ITEM_NOT_FOUND;
// item used
if (pItem->m_lootGenerated)
return EQUIP_ERR_LOOT_GONE;
if (pItem->IsBindedNotWith(this))
return EQUIP_ERR_NOT_OWNER;
// Currency Tokenizer are not supposed to be swapped out of their hidden bag
if (pItem->IsCurrencyToken())
{
TC_LOG_ERROR("entities.player.cheat", "Possible hacking attempt: Player %s (%s) tried to move token [%s entry: %u] out of the currency bag!",
GetName().c_str(), GetGUID().ToString().c_str(), pItem->GetGUID().ToString().c_str(), pProto->GetId());
return EQUIP_ERR_CANT_SWAP;
}
// check count of items (skip for auto move for same player from bank)
InventoryResult res = CanTakeMoreSimilarItems(pItem);
if (res != EQUIP_ERR_OK)
return res;
// in specific slot
if (bag != NULL_BAG && slot != NULL_SLOT)
{
if (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END)
{
if (!pItem->IsBag())
return EQUIP_ERR_WRONG_SLOT;
if (slot - BANK_SLOT_BAG_START >= GetBankBagSlotCount())
return EQUIP_ERR_NO_BANK_SLOT;
res = CanUseItem(pItem, not_loading);
if (res != EQUIP_ERR_OK)
return res;
}
res = CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, count, swap, pItem);
if (res != EQUIP_ERR_OK)
return res;
if (count == 0)
return EQUIP_ERR_OK;
}
// not specific slot or have space for partly store only in specific slot
// in specific bag
if (bag != NULL_BAG)
{
if (pItem->IsNotEmptyBag())
return EQUIP_ERR_BAG_IN_BAG;
// search stack in bag for merge to
if (pProto->GetMaxStackSize() != 1)
{
if (bag == INVENTORY_SLOT_BAG_0)
{
res = CanStoreItem_InInventorySlots(slotStart, slotEnd, dest, pProto, count, true, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
return res;
if (count == 0)
return EQUIP_ERR_OK;
}
else
{
res = CanStoreItem_InBag(bag, dest, pProto, count, true, false, pItem, NULL_BAG, slot);
if (res != EQUIP_ERR_OK)
res = CanStoreItem_InBag(bag, dest, pProto, count, true, true, pItem, NULL_BAG, slot);
if (res != EQUIP_ERR_OK)
return res;
if (count == 0)
return EQUIP_ERR_OK;
}
}
// search free slot in bag
if (bag == INVENTORY_SLOT_BAG_0)
{
res = CanStoreItem_InInventorySlots(slotStart, slotEnd, dest, pProto, count, false, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
return res;
if (count == 0)
return EQUIP_ERR_OK;
}
else
{
res = CanStoreItem_InBag(bag, dest, pProto, count, false, false, pItem, NULL_BAG, slot);
if (res != EQUIP_ERR_OK)
res = CanStoreItem_InBag(bag, dest, pProto, count, false, true, pItem, NULL_BAG, slot);
if (res != EQUIP_ERR_OK)
return res;
if (count == 0)
return EQUIP_ERR_OK;
}
}
// not specific bag or have space for partly store only in specific bag
// search stack for merge to
if (pProto->GetMaxStackSize() != 1)
{
// in slots
res = CanStoreItem_InInventorySlots(slotStart, slotEnd, dest, pProto, count, true, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
return res;
if (count == 0)
return EQUIP_ERR_OK;
// don't try to store reagents anywhere else than in Reagent Bank if we're on it
if (!reagentBankOnly)
{
// in special bags
if (pProto->GetBagFamily())
{
for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++)
{
res = CanStoreItem_InBag(i, dest, pProto, count, true, false, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
continue;
if (count == 0)
return EQUIP_ERR_OK;
}
}
// in regular bags
for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++)
{
res = CanStoreItem_InBag(i, dest, pProto, count, true, true, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
continue;
if (count == 0)
return EQUIP_ERR_OK;
}
}
}
// search free space in special bags (don't try to store reagents anywhere else than in Reagent Bank if we're on it)
if (!reagentBankOnly && pProto->GetBagFamily())
{
for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++)
{
res = CanStoreItem_InBag(i, dest, pProto, count, false, false, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
continue;
if (count == 0)
return EQUIP_ERR_OK;
}
}
// search free space
res = CanStoreItem_InInventorySlots(slotStart, slotEnd, dest, pProto, count, false, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
return res;
if (count == 0)
return EQUIP_ERR_OK;
// search free space in regular bags (don't try to store reagents anywhere else than in Reagent Bank if we're on it)
if (!reagentBankOnly)
{
for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++)
{
res = CanStoreItem_InBag(i, dest, pProto, count, false, true, pItem, bag, slot);
if (res != EQUIP_ERR_OK)
continue;
if (count == 0)
return EQUIP_ERR_OK;
}
}
return reagentBankOnly ? EQUIP_ERR_REAGENT_BANK_FULL : EQUIP_ERR_BANK_FULL;
}
InventoryResult Player::CanUseItem(Item* pItem, bool not_loading) const
{
if (pItem)
{
TC_LOG_DEBUG("entities.player.items", "Player::CanUseItem: Player '%s' (%s), Item: %u",
GetName().c_str(), GetGUID().ToString().c_str(), pItem->GetEntry());
if (!IsAlive() && not_loading)
return EQUIP_ERR_PLAYER_DEAD;
//if (isStunned())
// return EQUIP_ERR_GENERIC_STUNNED;
ItemTemplate const* pProto = pItem->GetTemplate();
if (pProto)
{
if (pItem->IsBindedNotWith(this))
return EQUIP_ERR_NOT_OWNER;
if (GetLevel() < pItem->GetRequiredLevel())
return EQUIP_ERR_CANT_EQUIP_LEVEL_I;
InventoryResult res = CanUseItem(pProto, true);
if (res != EQUIP_ERR_OK)
return res;
if (pItem->GetSkill() != 0)
{
bool allowEquip = false;
uint32 itemSkill = pItem->GetSkill();
// Armor that is binded to account can "morph" from plate to mail, etc. if skill is not learned yet.
if (pProto->GetQuality() == ITEM_QUALITY_HEIRLOOM && pProto->GetClass() == ITEM_CLASS_ARMOR && !HasSkill(itemSkill))
{
/// @todo when you right-click already equipped item it throws EQUIP_ERR_PROFICIENCY_NEEDED.
// In fact it's a visual bug, everything works properly... I need sniffs of operations with
// binded to account items from off server.
switch (GetClass())
{
case CLASS_HUNTER:
case CLASS_SHAMAN:
allowEquip = (itemSkill == SKILL_MAIL);
break;
case CLASS_PALADIN:
case CLASS_WARRIOR:
allowEquip = (itemSkill == SKILL_PLATE_MAIL);
break;
}
}
if (!allowEquip && GetSkillValue(itemSkill) == 0)
return EQUIP_ERR_PROFICIENCY_NEEDED;
}
return EQUIP_ERR_OK;
}
}
return EQUIP_ERR_ITEM_NOT_FOUND;
}
InventoryResult Player::CanUseItem(ItemTemplate const* proto, bool skipRequiredLevelCheck /*= false*/) const
{
// Used by group, function GroupLoot, to know if a prototype can be used by a player
if (!proto)
return EQUIP_ERR_ITEM_NOT_FOUND;
if (proto->HasFlag(ITEM_FLAG2_INTERNAL_ITEM))
return EQUIP_ERR_CANT_EQUIP_EVER;
if (proto->HasFlag(ITEM_FLAG2_FACTION_HORDE) && GetTeam() != HORDE)
return EQUIP_ERR_CANT_EQUIP_EVER;
if (proto->HasFlag(ITEM_FLAG2_FACTION_ALLIANCE) && GetTeam() != ALLIANCE)
return EQUIP_ERR_CANT_EQUIP_EVER;
if ((proto->GetAllowableClass() & GetClassMask()) == 0 || !proto->GetAllowableRace().HasRace(GetRace()))
return EQUIP_ERR_CANT_EQUIP_EVER;
if (proto->GetRequiredSkill() != 0)
{
if (GetSkillValue(proto->GetRequiredSkill()) == 0)
return EQUIP_ERR_PROFICIENCY_NEEDED;
else if (GetSkillValue(proto->GetRequiredSkill()) < proto->GetRequiredSkillRank())
return EQUIP_ERR_CANT_EQUIP_SKILL;
}
if (proto->GetRequiredSpell() != 0 && !HasSpell(proto->GetRequiredSpell()))
return EQUIP_ERR_PROFICIENCY_NEEDED;
if (!skipRequiredLevelCheck && GetLevel() < proto->GetBaseRequiredLevel())
return EQUIP_ERR_CANT_EQUIP_LEVEL_I;
// If World Event is not active, prevent using event dependant items
if (proto->GetHolidayID() && !IsHolidayActive(proto->GetHolidayID()))
return EQUIP_ERR_CLIENT_LOCKED_OUT;
if (proto->GetRequiredReputationFaction() && uint32(GetReputationRank(proto->GetRequiredReputationFaction())) < proto->GetRequiredReputationRank())
return EQUIP_ERR_CANT_EQUIP_REPUTATION;
// learning (recipes, mounts, pets, etc.)
if (proto->Effects.size() >= 2)
if (proto->Effects[0]->SpellID == 483 || proto->Effects[0]->SpellID == 55884)
if (HasSpell(proto->Effects[1]->SpellID))
return EQUIP_ERR_INTERNAL_BAG_ERROR;
if (ArtifactEntry const* artifact = sArtifactStore.LookupEntry(proto->GetArtifactID()))
if (artifact->ChrSpecializationID != GetPrimarySpecialization())
return EQUIP_ERR_CANT_USE_ITEM;
return EQUIP_ERR_OK;
}
InventoryResult Player::CanRollForItemInLFG(ItemTemplate const* proto, WorldObject const* lootedObject) const
{
if (!GetGroup() || !GetGroup()->isLFGGroup())
return EQUIP_ERR_OK; // not in LFG group
// check if looted object is inside the lfg dungeon
Map const* map = lootedObject->GetMap();
if (!sLFGMgr->inLfgDungeonMap(GetGroup()->GetGUID(), map->GetId(), map->GetDifficultyID()))
return EQUIP_ERR_OK;
if (!proto)
return EQUIP_ERR_ITEM_NOT_FOUND;
// Used by group, function GroupLoot, to know if a prototype can be used by a player
if ((proto->GetAllowableClass() & GetClassMask()) == 0 || !proto->GetAllowableRace().HasRace(GetRace()))
return EQUIP_ERR_CANT_EQUIP_EVER;
if (proto->GetRequiredSpell() != 0 && !HasSpell(proto->GetRequiredSpell()))
return EQUIP_ERR_PROFICIENCY_NEEDED;
if (proto->GetRequiredSkill() != 0)
{
if (!GetSkillValue(proto->GetRequiredSkill()))
return EQUIP_ERR_PROFICIENCY_NEEDED;
else if (GetSkillValue(proto->GetRequiredSkill()) < proto->GetRequiredSkillRank())
return EQUIP_ERR_CANT_EQUIP_SKILL;
}
uint8 _class = GetClass();
if (proto->GetClass() == ITEM_CLASS_WEAPON && GetSkillValue(proto->GetSkill()) == 0)
return EQUIP_ERR_PROFICIENCY_NEEDED;
if (proto->GetClass() == ITEM_CLASS_ARMOR && proto->GetSubClass() > ITEM_SUBCLASS_ARMOR_MISCELLANEOUS && proto->GetSubClass() < ITEM_SUBCLASS_ARMOR_COSMETIC && proto->GetInventoryType() != INVTYPE_CLOAK)
{
if (_class == CLASS_WARRIOR || _class == CLASS_PALADIN || _class == CLASS_DEATH_KNIGHT)
{
if (GetLevel() < 40)
{
if (proto->GetSubClass() != ITEM_SUBCLASS_ARMOR_MAIL)
return EQUIP_ERR_CLIENT_LOCKED_OUT;
}
else if (proto->GetSubClass() != ITEM_SUBCLASS_ARMOR_PLATE)
return EQUIP_ERR_CLIENT_LOCKED_OUT;
}
else if (_class == CLASS_HUNTER || _class == CLASS_SHAMAN)
{
if (GetLevel() < 40)
{
if (proto->GetSubClass() != ITEM_SUBCLASS_ARMOR_LEATHER)
return EQUIP_ERR_CLIENT_LOCKED_OUT;
}
else if (proto->GetSubClass() != ITEM_SUBCLASS_ARMOR_MAIL)
return EQUIP_ERR_CLIENT_LOCKED_OUT;
}
if (_class == CLASS_ROGUE || _class == CLASS_DRUID)
if (proto->GetSubClass() != ITEM_SUBCLASS_ARMOR_LEATHER)
return EQUIP_ERR_CLIENT_LOCKED_OUT;
if (_class == CLASS_MAGE || _class == CLASS_PRIEST || _class == CLASS_WARLOCK)
if (proto->GetSubClass() != ITEM_SUBCLASS_ARMOR_CLOTH)
return EQUIP_ERR_CLIENT_LOCKED_OUT;
}
return EQUIP_ERR_OK;
}
// Return stored item (if stored to stack, it can diff. from pItem). And pItem ca be deleted in this case.
Item* Player::StoreNewItem(ItemPosCountVec const& pos, uint32 itemId, bool update, ItemRandomBonusListId randomBonusListId /*= 0*/,
GuidSet const& allowedLooters /*= GuidSet()*/, ItemContext context /*= ItemContext::NONE*/, std::vector<int32> const& bonusListIDs /*= std::vector<int32>()*/, bool addToCollection /*= true*/)
{
uint32 count = 0;
for (ItemPosCountVec::const_iterator itr = pos.begin(); itr != pos.end(); ++itr)
count += itr->count;
Item* item = Item::CreateItem(itemId, count, context, this);
if (item)
{
item->AddItemFlag(ITEM_FIELD_FLAG_NEW_ITEM);
item->SetBonuses(bonusListIDs);
item = StoreItem(pos, item, update);
ItemAddedQuestCheck(itemId, count);
UpdateCriteria(CriteriaType::ObtainAnyItem, itemId, count);
UpdateCriteria(CriteriaType::AcquireItem, itemId, count);
item->SetFixedLevel(GetLevel());
item->SetItemRandomBonusList(randomBonusListId);
if (allowedLooters.size() > 1 && item->GetTemplate()->GetMaxStackSize() == 1 && item->IsSoulBound())
{
item->SetSoulboundTradeable(allowedLooters);
item->SetCreatePlayedTime(GetTotalPlayedTime());
AddTradeableItem(item);
// save data
std::ostringstream ss;
GuidSet::const_iterator itr = allowedLooters.begin();
ss << itr->GetCounter();
for (++itr; itr != allowedLooters.end(); ++itr)
ss << ' ' << itr->GetCounter();
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEM_BOP_TRADE);
stmt->setUInt64(0, item->GetGUID().GetCounter());
stmt->setString(1, ss.str());
CharacterDatabase.Execute(stmt);
}
if (addToCollection)
GetSession()->GetCollectionMgr()->OnItemAdded(item);
if (ItemChildEquipmentEntry const* childItemEntry = sDB2Manager.GetItemChildEquipment(itemId))
{
if (ItemTemplate const* childTemplate = sObjectMgr->GetItemTemplate(childItemEntry->ChildItemID))
{
ItemPosCountVec childDest;
CanStoreItem_InInventorySlots(CHILD_EQUIPMENT_SLOT_START, CHILD_EQUIPMENT_SLOT_END, childDest, childTemplate, count, false, nullptr, NULL_BAG, NULL_SLOT);
if (Item* childItem = StoreNewItem(childDest, childTemplate->GetId(), update, {}, {}, context, {}, addToCollection))
{
childItem->SetCreator(item->GetGUID());
childItem->AddItemFlag(ITEM_FIELD_FLAG_CHILD);
item->SetChildItem(childItem->GetGUID());
}
}
}
if (item->GetTemplate()->GetInventoryType() != INVTYPE_NON_EQUIP)
UpdateAverageItemLevelTotal();
}
return item;
}
Item* Player::StoreItem(ItemPosCountVec const& dest, Item* pItem, bool update)
{
if (!pItem)
return nullptr;
Item* lastItem = pItem;
for (ItemPosCountVec::const_iterator itr = dest.begin(); itr != dest.end();)
{
uint16 pos = itr->pos;
uint32 count = itr->count;
++itr;
if (itr == dest.end())
{
lastItem = _StoreItem(pos, pItem, count, false, update);
break;
}
lastItem = _StoreItem(pos, pItem, count, true, update);
}
AutoUnequipChildItem(lastItem);
return lastItem;
}
// Return stored item (if stored to stack, it can diff. from pItem). And pItem ca be deleted in this case.
Item* Player::_StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool update)
{
if (!pItem)
return nullptr;
uint8 bag = pos >> 8;
uint8 slot = pos & 255;
TC_LOG_DEBUG("entities.player.items", "Player::_StoreItem: Player '%s' (%s), Bag: %u, Slot: %u, Item: %u (%s), Count: %u",
GetName().c_str(), GetGUID().ToString().c_str(), bag, slot, pItem->GetEntry(), pItem->GetGUID().ToString().c_str(), count);
Item* pItem2 = GetItemByPos(bag, slot);
if (!pItem2)
{
if (clone)
pItem = pItem->CloneItem(count, this);
else
pItem->SetCount(count);
if (!pItem)
return nullptr;
if (pItem->GetBonding() == BIND_ON_ACQUIRE ||
pItem->GetBonding() == BIND_QUEST ||
(pItem->GetBonding() == BIND_ON_EQUIP && IsBagPos(pos)))
pItem->SetBinding(true);
Bag* pBag = (bag == INVENTORY_SLOT_BAG_0) ? nullptr : GetBagByPos(bag);
if (!pBag)
{
m_items[slot] = pItem;
SetInvSlot(slot, pItem->GetGUID());
pItem->SetContainedIn(GetGUID());
pItem->SetOwnerGUID(GetGUID());
pItem->SetSlot(slot);
pItem->SetContainer(nullptr);
}
else
pBag->StoreItem(slot, pItem, update);
if (IsInWorld() && update)
{
pItem->AddToWorld();
pItem->SendUpdateToPlayer(this);
}
pItem->SetState(ITEM_CHANGED, this);
if (pBag)
pBag->SetState(ITEM_CHANGED, this);
AddEnchantmentDurations(pItem);
AddItemDurations(pItem);
if (bag == INVENTORY_SLOT_BAG_0 || (bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END))
ApplyItemObtainSpells(pItem, true);
return pItem;
}
else
{
if (pItem2->GetBonding() == BIND_ON_ACQUIRE ||
pItem2->GetBonding() == BIND_QUEST ||
(pItem2->GetBonding() == BIND_ON_EQUIP && IsBagPos(pos)))
pItem2->SetBinding(true);
pItem2->SetCount(pItem2->GetCount() + count);
if (IsInWorld() && update)
pItem2->SendUpdateToPlayer(this);
if (!clone)
{
// delete item (it not in any slot currently)
if (IsInWorld() && update)
{
pItem->RemoveFromWorld();
pItem->DestroyForPlayer(this);
}
RemoveEnchantmentDurations(pItem);
RemoveItemDurations(pItem);
pItem->SetOwnerGUID(GetGUID()); // prevent error at next SetState in case trade/mail/buy from vendor
pItem->SetNotRefundable(this);
pItem->ClearSoulboundTradeable(this);
RemoveTradeableItem(pItem);
pItem->SetState(ITEM_REMOVED, this);
}
AddEnchantmentDurations(pItem2);
pItem2->SetState(ITEM_CHANGED, this);
if (bag == INVENTORY_SLOT_BAG_0 || (bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END))
ApplyItemObtainSpells(pItem2, true);
return pItem2;
}
}
Item* Player::EquipNewItem(uint16 pos, uint32 item, ItemContext context, bool update)
{
if (Item* pItem = Item::CreateItem(item, 1, context, this))
{
UpdateCriteria(CriteriaType::ObtainAnyItem, item, 1);
Item* equippedItem = EquipItem(pos, pItem, update);
ItemAddedQuestCheck(item, 1);
return equippedItem;
}
return nullptr;
}
Item* Player::EquipItem(uint16 pos, Item* pItem, bool update)
{
AddEnchantmentDurations(pItem);
AddItemDurations(pItem);
uint8 bag = pos >> 8;
uint8 slot = pos & 255;
Item* pItem2 = GetItemByPos(bag, slot);
if (!pItem2)
{
VisualizeItem(slot, pItem);
if (IsAlive())
{
ItemTemplate const* pProto = pItem->GetTemplate();
// item set bonuses applied only at equip and removed at unequip, and still active for broken items
if (pProto && pProto->GetItemSet())
AddItemsSetItem(this, pItem);
_ApplyItemMods(pItem, slot, true);
if (pProto && IsInCombat() && (pProto->GetClass() == ITEM_CLASS_WEAPON || pProto->GetInventoryType() == INVTYPE_RELIC) && m_weaponChangeTimer == 0)
{
uint32 cooldownSpell = GetClass() == CLASS_ROGUE ? 6123 : 6119;
SpellInfo const* spellProto = sSpellMgr->GetSpellInfo(cooldownSpell, DIFFICULTY_NONE);
if (!spellProto)
TC_LOG_ERROR("entities.player", "Player::EquipItem: Weapon switch cooldown spell %u for player '%s' (%s) couldn't be found in Spell.dbc",
cooldownSpell, GetName().c_str(), GetGUID().ToString().c_str());
else
{
m_weaponChangeTimer = spellProto->StartRecoveryTime;
GetSpellHistory()->AddGlobalCooldown(spellProto, Milliseconds(m_weaponChangeTimer));
WorldPackets::Spells::SpellCooldown spellCooldown;
spellCooldown.Caster = GetGUID();
spellCooldown.Flags = SPELL_COOLDOWN_FLAG_INCLUDE_GCD;
spellCooldown.SpellCooldowns.emplace_back(cooldownSpell, 0);
SendDirectMessage(spellCooldown.Write());
}
}
}
pItem->AddItemFlag2(ITEM_FIELD_FLAG2_EQUIPPED);
if (IsInWorld() && update)
{
pItem->AddToWorld();
pItem->SendUpdateToPlayer(this);
}
ApplyEquipCooldown(pItem);
// update expertise and armor penetration - passive auras may need it
if (slot == EQUIPMENT_SLOT_MAINHAND)
UpdateExpertise(BASE_ATTACK);
else if (slot == EQUIPMENT_SLOT_OFFHAND)
UpdateExpertise(OFF_ATTACK);
switch (slot)
{
case EQUIPMENT_SLOT_MAINHAND:
case EQUIPMENT_SLOT_OFFHAND:
RecalculateRating(CR_ARMOR_PENETRATION);
break;
default:
break;
}
}
else
{
pItem2->SetCount(pItem2->GetCount() + pItem->GetCount());
if (IsInWorld() && update)
pItem2->SendUpdateToPlayer(this);
// delete item (it not in any slot currently)
//pItem->DeleteFromDB();
if (IsInWorld() && update)
{
pItem->RemoveFromWorld();
pItem->DestroyForPlayer(this);
}
RemoveEnchantmentDurations(pItem);
RemoveItemDurations(pItem);
pItem->SetOwnerGUID(GetGUID()); // prevent error at next SetState in case trade/mail/buy from vendor
pItem->SetNotRefundable(this);
pItem->ClearSoulboundTradeable(this);
RemoveTradeableItem(pItem);
pItem->SetState(ITEM_REMOVED, this);
pItem2->SetState(ITEM_CHANGED, this);
ApplyEquipCooldown(pItem2);
return pItem2;
}
if (slot == EQUIPMENT_SLOT_MAINHAND || slot == EQUIPMENT_SLOT_OFFHAND)
CheckTitanGripPenalty();
// only for full equip instead adding to stack
UpdateCriteria(CriteriaType::EquipItem, pItem->GetEntry());
UpdateCriteria(CriteriaType::EquipItemInSlot, slot, pItem->GetEntry());
UpdateAverageItemLevelEquipped();
return pItem;
}
void Player::EquipChildItem(uint8 parentBag, uint8 parentSlot, Item* parentItem)
{
if (ItemChildEquipmentEntry const* itemChildEquipment = sDB2Manager.GetItemChildEquipment(parentItem->GetEntry()))
{
if (Item* childItem = GetChildItemByGuid(parentItem->GetChildItem()))
{
uint16 childDest = (INVENTORY_SLOT_BAG_0 << 8) | itemChildEquipment->ChildItemEquipSlot;
if (childItem->GetPos() != childDest)
{
Item* dstItem = GetItemByPos(childDest);
if (!dstItem) // empty slot, simple case
{
RemoveItem(childItem->GetBagSlot(), childItem->GetSlot(), true);
EquipItem(childDest, childItem, true);
AutoUnequipOffhandIfNeed();
}
else // have currently equipped item, not simple case
{
uint8 dstbag = dstItem->GetBagSlot();
uint8 dstslot = dstItem->GetSlot();
InventoryResult msg = CanUnequipItem(childDest, !childItem->IsBag());
if (msg != EQUIP_ERR_OK)
{
SendEquipError(msg, dstItem);
return;
}
// check dest->src move possibility but try to store currently equipped item in the bag where the parent item is
ItemPosCountVec sSrc;
uint16 eSrc = 0;
if (IsInventoryPos(parentBag, parentSlot))
{
msg = CanStoreItem(parentBag, NULL_SLOT, sSrc, dstItem, true);
if (msg != EQUIP_ERR_OK)
msg = CanStoreItem(NULL_BAG, NULL_SLOT, sSrc, dstItem, true);
}
else if (IsBankPos(parentBag, parentSlot))
{
msg = CanBankItem(parentBag, NULL_SLOT, sSrc, dstItem, true);
if (msg != EQUIP_ERR_OK)
msg = CanBankItem(NULL_BAG, NULL_SLOT, sSrc, dstItem, true);
}
else if (IsEquipmentPos(parentBag, parentSlot))
{
msg = CanEquipItem(parentSlot, eSrc, dstItem, true);
if (msg == EQUIP_ERR_OK)
msg = CanUnequipItem(eSrc, true);
}
if (msg != EQUIP_ERR_OK)
{
SendEquipError(msg, dstItem, childItem);
return;
}
// now do moves, remove...
RemoveItem(dstbag, dstslot, false);
RemoveItem(childItem->GetBagSlot(), childItem->GetSlot(), false);
// add to dest
EquipItem(childDest, childItem, true);
// add to src
if (IsInventoryPos(parentBag, parentSlot))
StoreItem(sSrc, dstItem, true);
else if (IsBankPos(parentBag, parentSlot))
BankItem(sSrc, dstItem, true);
else if (IsEquipmentPos(parentBag, parentSlot))
EquipItem(eSrc, dstItem, true);
AutoUnequipOffhandIfNeed();
}
}
}
}
}
void Player::AutoUnequipChildItem(Item* parentItem)
{
if (sDB2Manager.GetItemChildEquipment(parentItem->GetEntry()))
{
if (Item* childItem = GetChildItemByGuid(parentItem->GetChildItem()))
{
if (IsChildEquipmentPos(childItem->GetPos()))
return;
ItemPosCountVec dest;
uint32 count = childItem->GetCount();
InventoryResult result = CanStoreItem_InInventorySlots(CHILD_EQUIPMENT_SLOT_START, CHILD_EQUIPMENT_SLOT_END, dest, childItem->GetTemplate(), count, false, childItem, NULL_BAG, NULL_SLOT);
if (result != EQUIP_ERR_OK)
return;
RemoveItem(childItem->GetBagSlot(), childItem->GetSlot(), true);
StoreItem(dest, childItem, true);
}
}
}
void Player::QuickEquipItem(uint16 pos, Item* pItem)
{
if (pItem)
{
AddEnchantmentDurations(pItem);
AddItemDurations(pItem);
uint8 slot = pos & 255;
VisualizeItem(slot, pItem);
pItem->AddItemFlag2(ITEM_FIELD_FLAG2_EQUIPPED);
if (IsInWorld())
{
pItem->AddToWorld();
pItem->SendUpdateToPlayer(this);
}
if (slot == EQUIPMENT_SLOT_MAINHAND || slot == EQUIPMENT_SLOT_OFFHAND)
CheckTitanGripPenalty();
UpdateCriteria(CriteriaType::EquipItem, pItem->GetEntry());
UpdateCriteria(CriteriaType::EquipItemInSlot, slot, pItem->GetEntry());
}
}
void Player::SetVisibleItemSlot(uint8 slot, Item* pItem)
{
auto itemField = m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::VisibleItems, slot);
if (pItem)
{
SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemID), pItem->GetVisibleEntry(this));
SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::SecondaryItemModifiedAppearanceID), pItem->GetVisibleSecondaryModifiedAppearanceId(this));
SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemAppearanceModID), pItem->GetVisibleAppearanceModId(this));
SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemVisual), pItem->GetVisibleItemVisual(this));
}
else
{
SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemID), 0);
SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::SecondaryItemModifiedAppearanceID), 0);
SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemAppearanceModID), 0);
SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemVisual), 0);
}
}
void Player::VisualizeItem(uint8 slot, Item* pItem)
{
if (!pItem)
return;
// check also BIND_ON_ACQUIRE and BIND_QUEST for .additem or .additemset case by GM (not binded at adding to inventory)
if (pItem->GetBonding() == BIND_ON_EQUIP || pItem->GetBonding() == BIND_ON_ACQUIRE || pItem->GetBonding() == BIND_QUEST)
{
pItem->SetBinding(true);
if (IsInWorld())
GetSession()->GetCollectionMgr()->AddItemAppearance(pItem);
}
TC_LOG_DEBUG("entities.player.items", "Player::SetVisibleItemSlot: Player '%s' (%s), Slot: %u, Item: %u",
GetName().c_str(), GetGUID().ToString().c_str(), slot, pItem->GetEntry());
m_items[slot] = pItem;
SetInvSlot(slot, pItem->GetGUID());
pItem->SetContainedIn(GetGUID());
pItem->SetOwnerGUID(GetGUID());
pItem->SetSlot(slot);
pItem->SetContainer(nullptr);
if (slot < EQUIPMENT_SLOT_END)
SetVisibleItemSlot(slot, pItem);
pItem->SetState(ITEM_CHANGED, this);
}
Item* Player::BankItem(ItemPosCountVec const& dest, Item* pItem, bool update)
{
return StoreItem(dest, pItem, update);
}
void Player::RemoveItem(uint8 bag, uint8 slot, bool update)
{
// note: removeitem does not actually change the item
// it only takes the item out of storage temporarily
// note2: if removeitem is to be used for delinking
// the item must be removed from the player's updatequeue
Item* pItem = GetItemByPos(bag, slot);
if (pItem)
{
TC_LOG_DEBUG("entities.player.items", "Player::RemoveItem: Player '%s' (%s), Bag: %u, Slot: %u, Item: %u",
GetName().c_str(), GetGUID().ToString().c_str(), bag, slot, pItem->GetEntry());
RemoveEnchantmentDurations(pItem);
RemoveItemDurations(pItem);
RemoveTradeableItem(pItem);
if (bag == INVENTORY_SLOT_BAG_0)
{
if (slot < INVENTORY_SLOT_BAG_END)
{
// item set bonuses applied only at equip and removed at unequip, and still active for broken items
ItemTemplate const* pProto = ASSERT_NOTNULL(pItem->GetTemplate());
if (pProto->GetItemSet())
RemoveItemsSetItem(this, pItem);
_ApplyItemMods(pItem, slot, false, update);
pItem->RemoveItemFlag2(ITEM_FIELD_FLAG2_EQUIPPED);
// remove item dependent auras and casts (only weapon and armor slots)
if (slot < EQUIPMENT_SLOT_END)
{
// update expertise
if (slot == EQUIPMENT_SLOT_MAINHAND)
{
// clear main hand only enchantments
for (uint32 enchantSlot = 0; enchantSlot < MAX_ENCHANTMENT_SLOT; ++enchantSlot)
if (SpellItemEnchantmentEntry const* enchantment = sSpellItemEnchantmentStore.LookupEntry(pItem->GetEnchantmentId(EnchantmentSlot(enchantSlot))))
if (enchantment->GetFlags().HasFlag(SpellItemEnchantmentFlags::MainhandOnly))
pItem->ClearEnchantment(EnchantmentSlot(enchantSlot));
UpdateExpertise(BASE_ATTACK);
}
else if (slot == EQUIPMENT_SLOT_OFFHAND)
UpdateExpertise(OFF_ATTACK);
// update armor penetration - passive auras may need it
switch (slot)
{
case EQUIPMENT_SLOT_MAINHAND:
case EQUIPMENT_SLOT_OFFHAND:
RecalculateRating(CR_ARMOR_PENETRATION);
break;
default:
break;
}
}
}
m_items[slot] = nullptr;
SetInvSlot(slot, ObjectGuid::Empty);
if (slot < EQUIPMENT_SLOT_END)
{
SetVisibleItemSlot(slot, nullptr);
if (slot == EQUIPMENT_SLOT_MAINHAND || slot == EQUIPMENT_SLOT_OFFHAND)
CheckTitanGripPenalty();
}
}
else if (Bag* pBag = GetBagByPos(bag))
pBag->RemoveItem(slot, update);
pItem->SetContainedIn(ObjectGuid::Empty);
// pItem->SetUInt64Value(ITEM_FIELD_OWNER, 0); not clear owner at remove (it will be set at store). This used in mail and auction code
pItem->SetSlot(NULL_SLOT);
if (IsInWorld() && update)
pItem->SendUpdateToPlayer(this);
AutoUnequipChildItem(pItem);
if (bag == INVENTORY_SLOT_BAG_0)
UpdateAverageItemLevelEquipped();
}
}
// Common operation need to remove item from inventory without delete in trade, auction, guild bank, mail....
void Player::MoveItemFromInventory(uint8 bag, uint8 slot, bool update)
{
if (Item* it = GetItemByPos(bag, slot))
{
RemoveItem(bag, slot, update);
ItemRemovedQuestCheck(it->GetEntry(), it->GetCount());
it->SetNotRefundable(this, false, nullptr, false);
RemoveItemFromUpdateQueueOf(it, this);
GetSession()->GetCollectionMgr()->RemoveTemporaryAppearance(it);
if (it->IsInWorld())
{
it->RemoveFromWorld();
it->DestroyForPlayer(this);
}
}
}
// Common operation need to add item from inventory without delete in trade, guild bank, mail....
void Player::MoveItemToInventory(ItemPosCountVec const& dest, Item* pItem, bool update, bool in_characterInventoryDB)
{
uint32 itemId = pItem->GetEntry();
uint32 count = pItem->GetCount();
// store item
Item* pLastItem = StoreItem(dest, pItem, update);
// only set if not merged to existing stack (pItem can be deleted already but we can compare pointers any way)
if (pLastItem == pItem)
{
// update owner for last item (this can be original item with wrong owner
if (pLastItem->GetOwnerGUID() != GetGUID())
pLastItem->SetOwnerGUID(GetGUID());
// if this original item then it need create record in inventory
// in case trade we already have item in other player inventory
pLastItem->SetState(in_characterInventoryDB ? ITEM_CHANGED : ITEM_NEW, this);
if (pLastItem->IsBOPTradeable())
AddTradeableItem(pLastItem);
}
// update quest counters
ItemAddedQuestCheck(itemId, count);
UpdateCriteria(CriteriaType::ObtainAnyItem, itemId, count);
}
void Player::DestroyItem(uint8 bag, uint8 slot, bool update)
{
Item* pItem = GetItemByPos(bag, slot);
if (pItem)
{
TC_LOG_DEBUG("entities.player.items", "Player::DestroyItem: Player '%s' (%s), Bag: %u, Slot: %u, Item: %u",
GetName().c_str(), GetGUID().ToString().c_str(), bag, slot, pItem->GetEntry());
// Also remove all contained items if the item is a bag.
// This if () prevents item saving crashes if the condition for a bag to be empty before being destroyed was bypassed somehow.
if (pItem->IsNotEmptyBag())
for (uint8 i = 0; i < MAX_BAG_SIZE; ++i)
DestroyItem(slot, i, update);
if (pItem->IsWrapped())
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GIFT);
stmt->setUInt64(0, pItem->GetGUID().GetCounter());
CharacterDatabase.Execute(stmt);
}
RemoveEnchantmentDurations(pItem);
RemoveItemDurations(pItem);
pItem->SetNotRefundable(this);
pItem->ClearSoulboundTradeable(this);
RemoveTradeableItem(pItem);
ApplyItemObtainSpells(pItem, false);
sScriptMgr->OnItemRemove(this, pItem);
ItemTemplate const* pProto = pItem->GetTemplate();
if (bag == INVENTORY_SLOT_BAG_0)
{
SetInvSlot(slot, ObjectGuid::Empty);
// equipment and equipped bags can have applied bonuses
if (slot < INVENTORY_SLOT_BAG_END)
{
// item set bonuses applied only at equip and removed at unequip, and still active for broken items
if (pProto->GetItemSet())
RemoveItemsSetItem(this, pItem);
_ApplyItemMods(pItem, slot, false);
}
if (slot < EQUIPMENT_SLOT_END)
{
// update expertise and armor penetration - passive auras may need it
switch (slot)
{
case EQUIPMENT_SLOT_MAINHAND:
case EQUIPMENT_SLOT_OFFHAND:
RecalculateRating(CR_ARMOR_PENETRATION);
break;
default:
break;
}
if (slot == EQUIPMENT_SLOT_MAINHAND)
UpdateExpertise(BASE_ATTACK);
else if (slot == EQUIPMENT_SLOT_OFFHAND)
UpdateExpertise(OFF_ATTACK);
// equipment visual show
SetVisibleItemSlot(slot, nullptr);
}
m_items[slot] = nullptr;
}
else if (Bag* pBag = GetBagByPos(bag))
pBag->RemoveItem(slot, update);
// Delete rolled money / loot from db.
// MUST be done before RemoveFromWorld() or GetTemplate() fails
if (pProto->HasFlag(ITEM_FLAG_HAS_LOOT))
sLootItemStorage->RemoveStoredLootForContainer(pItem->GetGUID().GetCounter());
ItemRemovedQuestCheck(pItem->GetEntry(), pItem->GetCount());
if (IsInWorld() && update)
{
pItem->RemoveFromWorld();
pItem->DestroyForPlayer(this);
}
//pItem->SetOwnerGUID(0);
pItem->SetContainedIn(ObjectGuid::Empty);
pItem->SetSlot(NULL_SLOT);
pItem->SetState(ITEM_REMOVED, this);
if (pProto->GetInventoryType() != INVTYPE_NON_EQUIP)
UpdateAverageItemLevelTotal();
if (bag == INVENTORY_SLOT_BAG_0)
UpdateAverageItemLevelEquipped();
}
}
uint32 Player::DestroyItemCount(uint32 itemEntry, uint32 count, bool update, bool unequip_check)
{
TC_LOG_DEBUG("entities.player.items", "Player::DestroyItemCount: Player '%s' (%s), Item: %u, Count: %u",
GetName().c_str(), GetGUID().ToString().c_str(), itemEntry, count);
uint32 remcount = 0;
// in inventory
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; ++i)
{
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
if (item->GetEntry() == itemEntry && !item->IsInTrade())
{
if (item->GetCount() + remcount <= count)
{
// all items in inventory can unequipped
remcount += item->GetCount();
DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
if (remcount >= count)
return remcount;
}
else
{
item->SetCount(item->GetCount() - count + remcount);
ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
if (IsInWorld() && update)
item->SendUpdateToPlayer(this);
item->SetState(ITEM_CHANGED, this);
return remcount;
}
}
}
}
// in inventory bags
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++)
{
if (Bag* bag = GetBagByPos(i))
{
for (uint32 j = 0; j < bag->GetBagSize(); j++)
{
if (Item* item = bag->GetItemByPos(j))
{
if (item->GetEntry() == itemEntry && !item->IsInTrade())
{
// all items in bags can be unequipped
if (item->GetCount() + remcount <= count)
{
remcount += item->GetCount();
DestroyItem(i, j, update);
if (remcount >= count)
return remcount;
}
else
{
item->SetCount(item->GetCount() - count + remcount);
ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
if (IsInWorld() && update)
item->SendUpdateToPlayer(this);
item->SetState(ITEM_CHANGED, this);
return remcount;
}
}
}
}
}
}
// in equipment and bag list
for (uint8 i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_BAG_END; i++)
{
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
if (item->GetEntry() == itemEntry && !item->IsInTrade())
{
if (item->GetCount() + remcount <= count)
{
if (!unequip_check || CanUnequipItem(INVENTORY_SLOT_BAG_0 << 8 | i, false) == EQUIP_ERR_OK)
{
remcount += item->GetCount();
DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
if (remcount >= count)
return remcount;
}
}
else
{
item->SetCount(item->GetCount() - count + remcount);
ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
if (IsInWorld() && update)
item->SendUpdateToPlayer(this);
item->SetState(ITEM_CHANGED, this);
return remcount;
}
}
}
}
// in bank
for (uint8 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; i++)
{
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
if (item->GetEntry() == itemEntry && !item->IsInTrade())
{
if (item->GetCount() + remcount <= count)
{
remcount += item->GetCount();
DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
if (remcount >= count)
return remcount;
}
else
{
item->SetCount(item->GetCount() - count + remcount);
ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
if (IsInWorld() && update)
item->SendUpdateToPlayer(this);
item->SetState(ITEM_CHANGED, this);
return remcount;
}
}
}
}
// in bank bags
for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++)
{
if (Bag* bag = GetBagByPos(i))
{
for (uint32 j = 0; j < bag->GetBagSize(); j++)
{
if (Item* item = bag->GetItemByPos(j))
{
if (item->GetEntry() == itemEntry && !item->IsInTrade())
{
// all items in bags can be unequipped
if (item->GetCount() + remcount <= count)
{
remcount += item->GetCount();
DestroyItem(i, j, update);
if (remcount >= count)
return remcount;
}
else
{
item->SetCount(item->GetCount() - count + remcount);
ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
if (IsInWorld() && update)
item->SendUpdateToPlayer(this);
item->SetState(ITEM_CHANGED, this);
return remcount;
}
}
}
}
}
}
// in bank bag list
for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++)
{
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
if (item->GetEntry() == itemEntry && !item->IsInTrade())
{
if (item->GetCount() + remcount <= count)
{
if (!unequip_check || CanUnequipItem(INVENTORY_SLOT_BAG_0 << 8 | i, false) == EQUIP_ERR_OK)
{
remcount += item->GetCount();
DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
if (remcount >= count)
return remcount;
}
}
else
{
item->SetCount(item->GetCount() - count + remcount);
ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
if (IsInWorld() && update)
item->SendUpdateToPlayer(this);
item->SetState(ITEM_CHANGED, this);
return remcount;
}
}
}
}
for (uint8 i = REAGENT_SLOT_START; i < REAGENT_SLOT_END; ++i)
{
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
if (item->GetEntry() == itemEntry && !item->IsInTrade())
{
if (item->GetCount() + remcount <= count)
{
// all keys can be unequipped
remcount += item->GetCount();
DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
if (remcount >= count)
return remcount;
}
else
{
item->SetCount(item->GetCount() - count + remcount);
ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
if (IsInWorld() && update)
item->SendUpdateToPlayer(this);
item->SetState(ITEM_CHANGED, this);
return remcount;
}
}
}
}
for (uint8 i = CHILD_EQUIPMENT_SLOT_START; i < CHILD_EQUIPMENT_SLOT_END; ++i)
{
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
if (item->GetEntry() == itemEntry && !item->IsInTrade())
{
if (item->GetCount() + remcount <= count)
{
// all keys can be unequipped
remcount += item->GetCount();
DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
if (remcount >= count)
return remcount;
}
else
{
item->SetCount(item->GetCount() - count + remcount);
ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
if (IsInWorld() && update)
item->SendUpdateToPlayer(this);
item->SetState(ITEM_CHANGED, this);
return remcount;
}
}
}
}
return remcount;
}
void Player::DestroyZoneLimitedItem(bool update, uint32 new_zone)
{
TC_LOG_DEBUG("entities.player.items", "Player::DestroyZoneLimitedItem: In map %u and area %u for player '%s' (%s)",
GetMapId(), new_zone, GetName().c_str(), GetGUID().ToString().c_str());
// in inventory
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; i++)
if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
if (pItem->IsLimitedToAnotherMapOrZone(GetMapId(), new_zone))
DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
// in inventory bags
for (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 = pBag->GetItemByPos(j))
if (pItem->IsLimitedToAnotherMapOrZone(GetMapId(), new_zone))
DestroyItem(i, j, update);
// in equipment and bag list
for (uint8 i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_BAG_END; i++)
if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
if (pItem->IsLimitedToAnotherMapOrZone(GetMapId(), new_zone))
DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
}
void Player::DestroyConjuredItems(bool update)
{
// used when entering arena
// destroys all conjured items
TC_LOG_DEBUG("entities.player.items", "Player::DestroyConjuredItems: Player '%s' (%s)",
GetName().c_str(), GetGUID().ToString().c_str());
// in inventory
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; i++)
if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
if (pItem->IsConjuredConsumable())
DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
// in inventory bags
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 = pBag->GetItemByPos(j))
if (pItem->IsConjuredConsumable())
DestroyItem(i, j, update);
// in equipment and bag list
for (uint8 i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_BAG_END; i++)
if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
if (pItem->IsConjuredConsumable())
DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
}
Item* Player::GetItemByEntry(uint32 entry, ItemSearchLocation where /*= ItemSearchLocation::Default */) const
{
Item* result = nullptr;
ForEachItem(where, [&result, entry](Item* item)
{
if (item->GetEntry() == entry)
{
result = item;
return ItemSearchCallbackResult::Stop;
}
return ItemSearchCallbackResult::Continue;
});
return result;
}
std::vector<Item*> Player::GetItemListByEntry(uint32 entry, bool inBankAlso) const
{
ItemSearchLocation location = ItemSearchLocation::Equipment | ItemSearchLocation::Inventory | ItemSearchLocation::ReagentBank;
if (inBankAlso)
location |= ItemSearchLocation::Bank;
std::vector<Item*> itemList = std::vector<Item*>();
ForEachItem(location, [&itemList, entry](Item* item)
{
if (item->GetEntry() == entry)
itemList.push_back(item);
return ItemSearchCallbackResult::Continue;
});
return itemList;
}
void Player::DestroyItemCount(Item* pItem, uint32 &count, bool update)
{
if (!pItem)
return;
TC_LOG_DEBUG("entities.player.items", "Player::DestroyItemCount: Player '%s' (%s), Item (%s, Entry: %u), Count: %u",
GetName().c_str(), GetGUID().ToString().c_str(), pItem->GetGUID().ToString().c_str(), pItem->GetEntry(), count);
if (pItem->GetCount() <= count)
{
count -= pItem->GetCount();
DestroyItem(pItem->GetBagSlot(), pItem->GetSlot(), update);
}
else
{
pItem->SetCount(pItem->GetCount() - count);
ItemRemovedQuestCheck(pItem->GetEntry(), count);
count = 0;
if (IsInWorld() && update)
pItem->SendUpdateToPlayer(this);
pItem->SetState(ITEM_CHANGED, this);
}
}
void Player::SplitItem(uint16 src, uint16 dst, uint32 count)
{
uint8 srcbag = src >> 8;
uint8 srcslot = src & 255;
uint8 dstbag = dst >> 8;
uint8 dstslot = dst & 255;
Item* pSrcItem = GetItemByPos(srcbag, srcslot);
if (!pSrcItem)
{
SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pSrcItem, nullptr);
return;
}
if (pSrcItem->m_lootGenerated) // prevent split looting item (item
{
//best error message found for attempting to split while looting
SendEquipError(EQUIP_ERR_SPLIT_FAILED, pSrcItem, nullptr);
return;
}
// not let split all items (can be only at cheating)
if (pSrcItem->GetCount() == count)
{
SendEquipError(EQUIP_ERR_SPLIT_FAILED, pSrcItem, nullptr);
return;
}
// not let split more existing items (can be only at cheating)
if (pSrcItem->GetCount() < count)
{
SendEquipError(EQUIP_ERR_TOO_FEW_TO_SPLIT, pSrcItem, nullptr);
return;
}
//! If trading
if (TradeData* tradeData = GetTradeData())
{
//! If current item is in trade window (only possible with packet spoofing - silent return)
if (tradeData->GetTradeSlotForItem(pSrcItem->GetGUID()) != TRADE_SLOT_INVALID)
return;
}
TC_LOG_DEBUG("entities.player.items", "Player::SplitItem: Player '%s' (%s), Bag: %u, Slot: %u, Item: %u, Count: %u",
GetName().c_str(), GetGUID().ToString().c_str(), dstbag, dstslot, pSrcItem->GetEntry(), count);
Item* pNewItem = pSrcItem->CloneItem(count, this);
if (!pNewItem)
{
SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pSrcItem, nullptr);
return;
}
if (IsInventoryPos(dst))
{
// change item amount before check (for unique max count check)
pSrcItem->SetCount(pSrcItem->GetCount() - count);
ItemPosCountVec dest;
InventoryResult msg = CanStoreItem(dstbag, dstslot, dest, pNewItem, false);
if (msg != EQUIP_ERR_OK)
{
delete pNewItem;
pSrcItem->SetCount(pSrcItem->GetCount() + count);
SendEquipError(msg, pSrcItem, nullptr);
return;
}
if (IsInWorld())
pSrcItem->SendUpdateToPlayer(this);
pSrcItem->SetState(ITEM_CHANGED, this);
StoreItem(dest, pNewItem, true);
}
else if (IsBankPos(dst))
{
// change item amount before check (for unique max count check)
pSrcItem->SetCount(pSrcItem->GetCount() - count);
ItemPosCountVec dest;
InventoryResult msg = CanBankItem(dstbag, dstslot, dest, pNewItem, false);
if (msg != EQUIP_ERR_OK)
{
delete pNewItem;
pSrcItem->SetCount(pSrcItem->GetCount() + count);
SendEquipError(msg, pSrcItem, nullptr);
return;
}
if (IsInWorld())
pSrcItem->SendUpdateToPlayer(this);
pSrcItem->SetState(ITEM_CHANGED, this);
BankItem(dest, pNewItem, true);
}
else if (IsEquipmentPos(dst))
{
// change item amount before check (for unique max count check), provide space for splitted items
pSrcItem->SetCount(pSrcItem->GetCount() - count);
uint16 dest;
InventoryResult msg = CanEquipItem(dstslot, dest, pNewItem, false);
if (msg != EQUIP_ERR_OK)
{
delete pNewItem;
pSrcItem->SetCount(pSrcItem->GetCount() + count);
SendEquipError(msg, pSrcItem, nullptr);
return;
}
if (IsInWorld())
pSrcItem->SendUpdateToPlayer(this);
pSrcItem->SetState(ITEM_CHANGED, this);
EquipItem(dest, pNewItem, true);
AutoUnequipOffhandIfNeed();
}
}
void Player::SwapItem(uint16 src, uint16 dst)
{
uint8 srcbag = src >> 8;
uint8 srcslot = src & 255;
uint8 dstbag = dst >> 8;
uint8 dstslot = dst & 255;
Item* pSrcItem = GetItemByPos(srcbag, srcslot);
Item* pDstItem = GetItemByPos(dstbag, dstslot);
if (!pSrcItem)
return;
if (pSrcItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD))
{
if (Item* parentItem = GetItemByGuid(pSrcItem->m_itemData->Creator))
{
if (IsEquipmentPos(src))
{
AutoUnequipChildItem(parentItem); // we need to unequip child first since it cannot go into whatever is going to happen next
SwapItem(dst, src); // src is now empty
SwapItem(parentItem->GetPos(), dst);// dst is now empty
return;
}
}
}
else if (pDstItem && pDstItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD))
{
if (Item* parentItem = GetItemByGuid(pDstItem->m_itemData->Creator))
{
if (IsEquipmentPos(dst))
{
AutoUnequipChildItem(parentItem); // we need to unequip child first since it cannot go into whatever is going to happen next
SwapItem(src, dst); // dst is now empty
SwapItem(parentItem->GetPos(), src);// src is now empty
return;
}
}
}
TC_LOG_DEBUG("entities.player.items", "Player::SwapItem: Player '%s' (%s), Bag: %u, Slot: %u, Item: %u",
GetName().c_str(), GetGUID().ToString().c_str(), dstbag, dstslot, pSrcItem->GetEntry());
if (!IsAlive())
{
SendEquipError(EQUIP_ERR_PLAYER_DEAD, pSrcItem, pDstItem);
return;
}
// SRC checks
// check unequip potability for equipped items and bank bags
if (IsEquipmentPos(src) || IsBagPos(src))
{
// bags can be swapped with empty bag slots, or with empty bag (items move possibility checked later)
InventoryResult msg = CanUnequipItem(src, !IsBagPos(src) || IsBagPos(dst) || (pDstItem && pDstItem->ToBag() && pDstItem->ToBag()->IsEmpty()));
if (msg != EQUIP_ERR_OK)
{
SendEquipError(msg, pSrcItem, pDstItem);
return;
}
}
// prevent put equipped/bank bag in self
if (IsBagPos(src) && srcslot == dstbag)
{
SendEquipError(EQUIP_ERR_BAG_IN_BAG, pSrcItem, pDstItem);
return;
}
// prevent equipping bag in the same slot from its inside
if (IsBagPos(dst) && srcbag == dstslot)
{
SendEquipError(EQUIP_ERR_CANT_SWAP, pSrcItem, pDstItem);
return;
}
// DST checks
if (pDstItem)
{
// check unequip potability for equipped items and bank bags
if (IsEquipmentPos(dst) || IsBagPos(dst))
{
// bags can be swapped with empty bag slots, or with empty bag (items move possibility checked later)
InventoryResult msg = CanUnequipItem(dst, !IsBagPos(dst) || IsBagPos(src) || (pSrcItem->ToBag() && pSrcItem->ToBag()->IsEmpty()));
if (msg != EQUIP_ERR_OK)
{
SendEquipError(msg, pSrcItem, pDstItem);
return;
}
}
}
if (IsReagentBankPos(dst) && !IsReagentBankUnlocked())
{
SendEquipError(EQUIP_ERR_REAGENT_BANK_LOCKED, pSrcItem, pDstItem);
return;
}
// NOW this is or item move (swap with empty), or swap with another item (including bags in bag possitions)
// or swap empty bag with another empty or not empty bag (with items exchange)
// Move case
if (!pDstItem)
{
if (IsInventoryPos(dst))
{
ItemPosCountVec dest;
InventoryResult msg = CanStoreItem(dstbag, dstslot, dest, pSrcItem, false);
if (msg != EQUIP_ERR_OK)
{
SendEquipError(msg, pSrcItem, nullptr);
return;
}
RemoveItem(srcbag, srcslot, true);
StoreItem(dest, pSrcItem, true);
if (IsBankPos(src))
ItemAddedQuestCheck(pSrcItem->GetEntry(), pSrcItem->GetCount());
}
else if (IsBankPos(dst))
{
ItemPosCountVec dest;
InventoryResult msg = CanBankItem(dstbag, dstslot, dest, pSrcItem, false);
if (msg != EQUIP_ERR_OK)
{
SendEquipError(msg, pSrcItem, nullptr);
return;
}
RemoveItem(srcbag, srcslot, true);
BankItem(dest, pSrcItem, true);
if (!IsReagentBankPos(dst))
ItemRemovedQuestCheck(pSrcItem->GetEntry(), pSrcItem->GetCount());
}
else if (IsEquipmentPos(dst))
{
uint16 dest;
InventoryResult msg = CanEquipItem(dstslot, dest, pSrcItem, false);
if (msg != EQUIP_ERR_OK)
{
SendEquipError(msg, pSrcItem, nullptr);
return;
}
RemoveItem(srcbag, srcslot, true);
EquipItem(dest, pSrcItem, true);
AutoUnequipOffhandIfNeed();
}
return;
}
// attempt merge to / fill target item
if (!pSrcItem->IsBag() && !pDstItem->IsBag())
{
InventoryResult msg;
ItemPosCountVec sDest;
uint16 eDest = 0;
if (IsInventoryPos(dst))
msg = CanStoreItem(dstbag, dstslot, sDest, pSrcItem, false);
else if (IsBankPos(dst))
msg = CanBankItem(dstbag, dstslot, sDest, pSrcItem, false);
else if (IsEquipmentPos(dst))
msg = CanEquipItem(dstslot, eDest, pSrcItem, false);
else
return;
if (msg == EQUIP_ERR_OK && IsEquipmentPos(dst) && !pSrcItem->GetChildItem().IsEmpty())
msg = CanEquipChildItem(pSrcItem);
// can be merge/fill
if (msg == EQUIP_ERR_OK)
{
if (pSrcItem->GetCount() + pDstItem->GetCount() <= pSrcItem->GetTemplate()->GetMaxStackSize())
{
RemoveItem(srcbag, srcslot, true);
if (IsInventoryPos(dst))
StoreItem(sDest, pSrcItem, true);
else if (IsBankPos(dst))
BankItem(sDest, pSrcItem, true);
else if (IsEquipmentPos(dst))
{
EquipItem(eDest, pSrcItem, true);
if (!pSrcItem->GetChildItem().IsEmpty())
EquipChildItem(srcbag, srcslot, pSrcItem);
AutoUnequipOffhandIfNeed();
}
}
else
{
pSrcItem->SetCount(pSrcItem->GetCount() + pDstItem->GetCount() - pSrcItem->GetTemplate()->GetMaxStackSize());
pDstItem->SetCount(pSrcItem->GetTemplate()->GetMaxStackSize());
pSrcItem->SetState(ITEM_CHANGED, this);
pDstItem->SetState(ITEM_CHANGED, this);
if (IsInWorld())
{
pSrcItem->SendUpdateToPlayer(this);
pDstItem->SendUpdateToPlayer(this);
}
}
SendRefundInfo(pDstItem);
return;
}
}
// impossible merge/fill, do real swap
InventoryResult msg = EQUIP_ERR_OK;
// check src->dest move possibility
ItemPosCountVec sDest;
uint16 eDest = 0;
if (IsInventoryPos(dst))
msg = CanStoreItem(dstbag, dstslot, sDest, pSrcItem, true);
else if (IsBankPos(dst))
msg = CanBankItem(dstbag, dstslot, sDest, pSrcItem, true);
else if (IsEquipmentPos(dst))
{
msg = CanEquipItem(dstslot, eDest, pSrcItem, true);
if (msg == EQUIP_ERR_OK)
msg = CanUnequipItem(eDest, true);
}
if (msg != EQUIP_ERR_OK)
{
SendEquipError(msg, pSrcItem, pDstItem);
return;
}
// check dest->src move possibility
ItemPosCountVec sDest2;
uint16 eDest2 = 0;
if (IsInventoryPos(src))
msg = CanStoreItem(srcbag, srcslot, sDest2, pDstItem, true);
else if (IsBankPos(src))
msg = CanBankItem(srcbag, srcslot, sDest2, pDstItem, true);
else if (IsEquipmentPos(src))
{
msg = CanEquipItem(srcslot, eDest2, pDstItem, true);
if (msg == EQUIP_ERR_OK)
msg = CanUnequipItem(eDest2, true);
}
if (msg == EQUIP_ERR_OK && IsEquipmentPos(dst) && !pSrcItem->GetChildItem().IsEmpty())
msg = CanEquipChildItem(pSrcItem);
if (msg != EQUIP_ERR_OK)
{
SendEquipError(msg, pDstItem, pSrcItem);
return;
}
// Check bag swap with item exchange (one from empty in not bag possition (equipped (not possible in fact) or store)
if (Bag* srcBag = pSrcItem->ToBag())
{
if (Bag* dstBag = pDstItem->ToBag())
{
Bag* emptyBag = nullptr;
Bag* fullBag = nullptr;
if (srcBag->IsEmpty() && !IsBagPos(src))
{
emptyBag = srcBag;
fullBag = dstBag;
}
else if (dstBag->IsEmpty() && !IsBagPos(dst))
{
emptyBag = dstBag;
fullBag = srcBag;
}
// bag swap (with items exchange) case
if (emptyBag && fullBag)
{
ItemTemplate const* emptyProto = emptyBag->GetTemplate();
uint32 count = 0;
for (uint32 i=0; i < fullBag->GetBagSize(); ++i)
{
Item* bagItem = fullBag->GetItemByPos(i);
if (!bagItem)
continue;
ItemTemplate const* bagItemProto = bagItem->GetTemplate();
if (!bagItemProto || !ItemCanGoIntoBag(bagItemProto, emptyProto))
{
// one from items not go to empty target bag
SendEquipError(EQUIP_ERR_BAG_IN_BAG, pSrcItem, pDstItem);
return;
}
++count;
}
if (count > emptyBag->GetBagSize())
{
// too small targeted bag
SendEquipError(EQUIP_ERR_CANT_SWAP, pSrcItem, pDstItem);
return;
}
// Items swap
count = 0; // will pos in new bag
for (uint32 i = 0; i< fullBag->GetBagSize(); ++i)
{
Item* bagItem = fullBag->GetItemByPos(i);
if (!bagItem)
continue;
fullBag->RemoveItem(i, true);
emptyBag->StoreItem(count, bagItem, true);
bagItem->SetState(ITEM_CHANGED, this);
++count;
}
}
}
}
// now do moves, remove...
RemoveItem(dstbag, dstslot, false);
RemoveItem(srcbag, srcslot, false);
// add to dest
if (IsInventoryPos(dst))
StoreItem(sDest, pSrcItem, true);
else if (IsBankPos(dst))
BankItem(sDest, pSrcItem, true);
else if (IsEquipmentPos(dst))
{
EquipItem(eDest, pSrcItem, true);
if (!pSrcItem->GetChildItem().IsEmpty())
EquipChildItem(srcbag, srcslot, pSrcItem);
}
// add to src
if (IsInventoryPos(src))
StoreItem(sDest2, pDstItem, true);
else if (IsBankPos(src))
BankItem(sDest2, pDstItem, true);
else if (IsEquipmentPos(src))
EquipItem(eDest2, pDstItem, true);
// if inventory item was moved, check if we can remove dependent auras, because they were not removed in Player::RemoveItem (update was set to false)
// do this after swaps are done, we pass nullptr because both weapons could be swapped and none of them should be ignored
if ((srcbag == INVENTORY_SLOT_BAG_0 && srcslot < INVENTORY_SLOT_BAG_END) || (dstbag == INVENTORY_SLOT_BAG_0 && dstslot < INVENTORY_SLOT_BAG_END))
ApplyItemDependentAuras((Item*)nullptr, false);
// if player is moving bags and is looting an item inside this bag
// release the loot
if (!GetLootGUID().IsEmpty())
{
bool released = false;
if (IsBagPos(src))
{
Bag* bag = pSrcItem->ToBag();
for (uint32 i = 0; i < bag->GetBagSize(); ++i)
{
if (Item* bagItem = bag->GetItemByPos(i))
{
if (bagItem->GetGUID() == GetLootGUID())
{
m_session->DoLootRelease(GetLootGUID());
released = true; // so we don't need to look at dstBag
break;
}
}
}
}
if (!released && IsBagPos(dst))
{
Bag* bag = pDstItem->ToBag();
for (uint32 i = 0; i < bag->GetBagSize(); ++i)
{
if (Item* bagItem = bag->GetItemByPos(i))
{
if (bagItem->GetGUID() == GetLootGUID())
{
m_session->DoLootRelease(GetLootGUID());
break;
}
}
}
}
}
AutoUnequipOffhandIfNeed();
}
void Player::AddItemToBuyBackSlot(Item* pItem)
{
if (pItem)
{
uint32 slot = m_currentBuybackSlot;
// if current back slot non-empty search oldest or free
if (m_items[slot])
{
uint32 oldest_time = m_activePlayerData->BuybackTimestamp[0];
uint32 oldest_slot = BUYBACK_SLOT_START;
for (uint32 i = BUYBACK_SLOT_START + 1; i < BUYBACK_SLOT_END; ++i)
{
// found empty
if (!m_items[i])
{
oldest_slot = i;
break;
}
uint32 i_time = m_activePlayerData->BuybackTimestamp[i - BUYBACK_SLOT_START];
if (oldest_time > i_time)
{
oldest_time = i_time;
oldest_slot = i;
}
}
// find oldest
slot = oldest_slot;
}
RemoveItemFromBuyBackSlot(slot, true);
TC_LOG_DEBUG("entities.player.items", "Player::AddItemToBuyBackSlot: Player '%s' (%s), Item: %u, Slot: %u",
GetName().c_str(), GetGUID().ToString().c_str(), pItem->GetEntry(), slot);
m_items[slot] = pItem;
time_t base = GameTime::GetGameTime();
uint32 etime = uint32(base - m_logintime + (30 * 3600));
uint32 eslot = slot - BUYBACK_SLOT_START;
SetInvSlot(slot, pItem->GetGUID());
if (ItemTemplate const* proto = pItem->GetTemplate())
SetBuybackPrice(eslot, proto->GetSellPrice() * pItem->GetCount());
else
SetBuybackPrice(eslot, 0);
SetBuybackTimestamp(eslot, (uint32)etime);
// move to next (for non filled list is move most optimized choice)
if (m_currentBuybackSlot < BUYBACK_SLOT_END - 1)
++m_currentBuybackSlot;
}
}
Item* Player::GetItemFromBuyBackSlot(uint32 slot)
{
TC_LOG_DEBUG("entities.player.items", "Player::GetItemFromBuyBackSlot: Player '%s' (%s), Slot: %u",
GetName().c_str(), GetGUID().ToString().c_str(), slot);
if (slot >= BUYBACK_SLOT_START && slot < BUYBACK_SLOT_END)
return m_items[slot];
return nullptr;
}
void Player::RemoveItemFromBuyBackSlot(uint32 slot, bool del)
{
TC_LOG_DEBUG("entities.player.items", "Player::RemoveItemFromBuyBackSlot: Player '%s' (%s), Slot: %u",
GetName().c_str(), GetGUID().ToString().c_str(), slot);
if (slot >= BUYBACK_SLOT_START && slot < BUYBACK_SLOT_END)
{
Item* pItem = m_items[slot];
if (pItem)
{
pItem->RemoveFromWorld();
if (del)
{
if (ItemTemplate const* itemTemplate = pItem->GetTemplate())
if (itemTemplate->HasFlag(ITEM_FLAG_HAS_LOOT))
sLootItemStorage->RemoveStoredLootForContainer(pItem->GetGUID().GetCounter());
pItem->SetState(ITEM_REMOVED, this);
}
}
m_items[slot] = nullptr;
uint32 eslot = slot - BUYBACK_SLOT_START;
SetInvSlot(slot, ObjectGuid::Empty);
SetBuybackPrice(eslot, 0);
SetBuybackTimestamp(eslot, 0);
// if current backslot is filled set to now free slot
if (m_items[m_currentBuybackSlot])
m_currentBuybackSlot = slot;
}
}
void Player::SendEquipError(InventoryResult msg, Item const* item1 /*= nullptr*/, Item const* item2 /*= nullptr*/, uint32 itemId /*= 0*/) const
{
WorldPackets::Item::InventoryChangeFailure failure;
failure.BagResult = msg;
if (msg != EQUIP_ERR_OK)
{
if (item1)
failure.Item[0] = item1->GetGUID();
if (item2)
failure.Item[1] = item2->GetGUID();
failure.ContainerBSlot = 0; // bag equip slot, used with EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM and EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG2
switch (msg)
{
case EQUIP_ERR_CANT_EQUIP_LEVEL_I:
case EQUIP_ERR_PURCHASE_LEVEL_TOO_LOW:
{
failure.Level = uint32(item1 ? item1->GetRequiredLevel() : 0);
break;
}
case EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM: // no idea about this one...
{
//failure.SrcContainer
//failure.SrcSlot
//failure.DstContainer
break;
}
case EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED_IS:
case EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_SOCKETED_EXCEEDED_IS:
case EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED_IS:
{
ItemTemplate const* proto = item1 ? item1->GetTemplate() : sObjectMgr->GetItemTemplate(itemId);
failure.LimitCategory = proto ? proto->GetItemLimitCategory() : 0;
break;
}
default:
break;
}
}
SendDirectMessage(failure.Write());
}
void Player::SendBuyError(BuyResult msg, Creature* creature, uint32 item, uint32 /*param*/) const
{
WorldPackets::Item::BuyFailed packet;
packet.VendorGUID = creature ? creature->GetGUID() : ObjectGuid::Empty;
packet.Muid = item;
packet.Reason = msg;
SendDirectMessage(packet.Write());
}
void Player::SendSellError(SellResult msg, Creature* creature, ObjectGuid guid) const
{
WorldPackets::Item::SellResponse sellResponse;
sellResponse.VendorGUID = (creature ? creature->GetGUID() : ObjectGuid::Empty);
sellResponse.ItemGUID = guid;
sellResponse.Reason = msg;
SendDirectMessage(sellResponse.Write());
}
bool Player::IsUseEquipedWeapon(bool mainhand) const
{
// disarm applied only to mainhand weapon
return !IsInFeralForm() && (!mainhand || !HasUnitFlag(UNIT_FLAG_DISARMED));
}
void Player::SetCanTitanGrip(bool value, uint32 penaltySpellId /*= 0*/)
{
if (value == m_canTitanGrip)
return;
m_canTitanGrip = value;
m_titanGripPenaltySpellId = penaltySpellId;
}
void Player::CheckTitanGripPenalty()
{
if (!CanTitanGrip())
return;
bool apply = IsUsingTwoHandedWeaponInOneHand();
if (apply)
{
if (!HasAura(m_titanGripPenaltySpellId))
CastSpell(nullptr, m_titanGripPenaltySpellId, true);
}
else
RemoveAurasDueToSpell(m_titanGripPenaltySpellId);
}
bool Player::IsTwoHandUsed() const
{
Item* mainItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
if (!mainItem)
return false;
ItemTemplate const* itemTemplate = mainItem->GetTemplate();
return (itemTemplate->GetInventoryType() == INVTYPE_2HWEAPON && !CanTitanGrip()) ||
itemTemplate->GetInventoryType() == INVTYPE_RANGED ||
(itemTemplate->GetInventoryType() == INVTYPE_RANGEDRIGHT && itemTemplate->GetClass() == ITEM_CLASS_WEAPON && itemTemplate->GetSubClass() != ITEM_SUBCLASS_WEAPON_WAND);
}
bool Player::IsUsingTwoHandedWeaponInOneHand() const
{
Item* offItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
if (offItem && offItem->GetTemplate()->GetInventoryType() == INVTYPE_2HWEAPON)
return true;
Item* mainItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
if (!mainItem || mainItem->GetTemplate()->GetInventoryType() != INVTYPE_2HWEAPON)
return false;
if (!offItem)
return false;
return true;
}
void Player::TradeCancel(bool sendback)
{
if (m_trade)
{
Player* trader = m_trade->GetTrader();
// send yellow "Trade canceled" message to both traders
if (sendback)
GetSession()->SendCancelTrade();
trader->GetSession()->SendCancelTrade();
// cleanup
delete m_trade;
m_trade = nullptr;
delete trader->m_trade;
trader->m_trade = nullptr;
}
}
void Player::UpdateSoulboundTradeItems()
{
// also checks for garbage data
for (GuidUnorderedSet::iterator itr = m_itemSoulboundTradeable.begin(); itr != m_itemSoulboundTradeable.end();)
{
Item* item = GetItemByGuid(*itr);
if (!item || item->GetOwnerGUID() != GetGUID() || item->CheckSoulboundTradeExpire())
itr = m_itemSoulboundTradeable.erase(itr);
else
++itr;
}
}
void Player::AddTradeableItem(Item* item)
{
m_itemSoulboundTradeable.insert(item->GetGUID());
}
void Player::RemoveTradeableItem(Item* item)
{
m_itemSoulboundTradeable.erase(item->GetGUID());
}
void Player::UpdateItemDuration(uint32 time, bool realtimeonly)
{
if (m_itemDuration.empty())
return;
TC_LOG_DEBUG("entities.player.items", "Player::UpdateItemDuration: Player '%s' (%s), Time: %u, RealTimeOnly: %u",
GetName().c_str(), GetGUID().ToString().c_str(), time, realtimeonly);
for (ItemDurationList::const_iterator itr = m_itemDuration.begin(); itr != m_itemDuration.end();)
{
Item* item = *itr;
++itr; // current element can be erased in UpdateDuration
if (!realtimeonly || item->GetTemplate()->HasFlag(ITEM_FLAG_REAL_DURATION))
item->UpdateDuration(this, time);
}
}
void Player::UpdateEnchantTime(uint32 time)
{
for (EnchantDurationList::iterator itr = m_enchantDuration.begin(), next; itr != m_enchantDuration.end(); itr = next)
{
ASSERT(itr->item);
next = itr;
if (!itr->item->GetEnchantmentId(itr->slot))
{
next = m_enchantDuration.erase(itr);
}
else if (itr->leftduration <= time)
{
ApplyEnchantment(itr->item, itr->slot, false, false);
itr->item->ClearEnchantment(itr->slot);
next = m_enchantDuration.erase(itr);
}
else if (itr->leftduration > time)
{
itr->leftduration -= time;
++next;
}
}
}
void Player::AddEnchantmentDurations(Item* item)
{
for (int x = 0; x < MAX_ENCHANTMENT_SLOT; ++x)
{
if (!item->GetEnchantmentId(EnchantmentSlot(x)))
continue;
uint32 duration = item->GetEnchantmentDuration(EnchantmentSlot(x));
if (duration > 0)
AddEnchantmentDuration(item, EnchantmentSlot(x), duration);
}
}
void Player::RemoveEnchantmentDurations(Item* item)
{
for (EnchantDurationList::iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end();)
{
if (itr->item == item)
{
// save duration in item
item->SetEnchantmentDuration(EnchantmentSlot(itr->slot), itr->leftduration, this);
itr = m_enchantDuration.erase(itr);
}
else
++itr;
}
}
void Player::RemoveEnchantmentDurationsReferences(Item* item)
{
for (EnchantDurationList::iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end();)
{
if (itr->item == item)
itr = m_enchantDuration.erase(itr);
else
++itr;
}
}
void Player::RemoveArenaEnchantments(EnchantmentSlot slot)
{
// remove enchantments from equipped items first to clean up the m_enchantDuration list
for (EnchantDurationList::iterator itr = m_enchantDuration.begin(), next; itr != m_enchantDuration.end(); itr = next)
{
next = itr;
if (itr->slot == slot)
{
if (itr->item && itr->item->GetEnchantmentId(slot))
{
// Poisons and DK runes are enchants which are allowed on arenas
if (sSpellMgr->IsArenaAllowedEnchancment(itr->item->GetEnchantmentId(slot)))
{
++next;
continue;
}
// remove from stats
ApplyEnchantment(itr->item, slot, false, false);
// remove visual
itr->item->ClearEnchantment(slot);
}
// remove from update list
next = m_enchantDuration.erase(itr);
}
else
++next;
}
// remove enchants from inventory items
// NOTE: no need to remove these from stats, since these aren't equipped
// in inventory
uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount();
for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; ++i)
if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
if (!sSpellMgr->IsArenaAllowedEnchancment(pItem->GetEnchantmentId(slot)))
pItem->ClearEnchantment(slot);
// in inventory bags
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 = pBag->GetItemByPos(j))
if (!sSpellMgr->IsArenaAllowedEnchancment(pItem->GetEnchantmentId(slot)))
pItem->ClearEnchantment(slot);
}
// duration == 0 will remove item enchant
void Player::AddEnchantmentDuration(Item* item, EnchantmentSlot slot, uint32 duration)
{
if (!item)
return;
if (slot >= MAX_ENCHANTMENT_SLOT)
return;
for (EnchantDurationList::iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr)
{
if (itr->item == item && itr->slot == slot)
{
itr->item->SetEnchantmentDuration(itr->slot, itr->leftduration, this);
m_enchantDuration.erase(itr);
break;
}
}
if (duration > 0)
{
GetSession()->SendItemEnchantTimeUpdate(GetGUID(), item->GetGUID(), slot, uint32(duration/1000));
m_enchantDuration.push_back(EnchantDuration(item, slot, duration));
}
}
void Player::ApplyEnchantment(Item* item, bool apply)
{
for (uint32 slot = 0; slot < MAX_ENCHANTMENT_SLOT; ++slot)
ApplyEnchantment(item, EnchantmentSlot(slot), apply);
}
void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool apply_dur, bool ignore_condition)
{
if (!item || !item->IsEquipped())
return;
if (slot >= MAX_ENCHANTMENT_SLOT)
return;
uint32 enchant_id = item->GetEnchantmentId(slot);
if (!enchant_id)
return;
SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
if (!pEnchant)
return;
if (!ignore_condition && pEnchant->ConditionID && !EnchantmentFitsRequirements(pEnchant->ConditionID, -1))
return;
if (pEnchant->MinLevel > GetLevel())
return;
if (pEnchant->RequiredSkillID > 0 && pEnchant->RequiredSkillRank > GetSkillValue(pEnchant->RequiredSkillID))
return;
// If we're dealing with a gem inside a prismatic socket we need to check the prismatic socket requirements
// rather than the gem requirements itself. If the socket has no color it is a prismatic socket.
if ((slot == SOCK_ENCHANTMENT_SLOT || slot == SOCK_ENCHANTMENT_SLOT_2 || slot == SOCK_ENCHANTMENT_SLOT_3))
{
if (!item->GetSocketColor(slot - SOCK_ENCHANTMENT_SLOT))
{
// Check if the requirements for the prismatic socket are met before applying the gem stats
SpellItemEnchantmentEntry const* pPrismaticEnchant = sSpellItemEnchantmentStore.LookupEntry(item->GetEnchantmentId(PRISMATIC_ENCHANTMENT_SLOT));
if (!pPrismaticEnchant || (pPrismaticEnchant->RequiredSkillID > 0 && pPrismaticEnchant->RequiredSkillRank > GetSkillValue(pPrismaticEnchant->RequiredSkillID)))
return;
}
// Cogwheel gems dont have requirement data set in SpellItemEnchantment.dbc, but they do have it in Item-sparse.db2
if (UF::SocketedGem const* gem = item->GetGem(uint16(slot - SOCK_ENCHANTMENT_SLOT)))
if (ItemTemplate const* gemTemplate = sObjectMgr->GetItemTemplate(gem->ItemID))
if (gemTemplate->GetRequiredSkill() && GetSkillValue(gemTemplate->GetRequiredSkill()) < gemTemplate->GetRequiredSkillRank())
return;
}
if (!item->IsBroken())
{
for (int s = 0; s < MAX_ITEM_ENCHANTMENT_EFFECTS; ++s)
{
uint32 enchant_display_type = pEnchant->Effect[s];
uint32 enchant_amount = pEnchant->EffectPointsMin[s];
uint32 enchant_spell_id = pEnchant->EffectArg[s];
switch (enchant_display_type)
{
case ITEM_ENCHANTMENT_TYPE_NONE:
break;
case ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL:
// processed in Player::CastItemCombatSpell
break;
case ITEM_ENCHANTMENT_TYPE_DAMAGE:
{
WeaponAttackType const attackType = Player::GetAttackBySlot(item->GetSlot(), item->GetTemplate()->GetInventoryType());
if (attackType != MAX_ATTACK)
UpdateDamageDoneMods(attackType, apply ? -1 : slot);
break;
}
case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL:
if (enchant_spell_id)
{
if (apply)
CastSpell(this, enchant_spell_id, item);
else
RemoveAurasDueToItemSpell(enchant_spell_id, item->GetGUID());
}
break;
case ITEM_ENCHANTMENT_TYPE_RESISTANCE:
if (pEnchant->ScalingClass)
{
int32 scalingClass = pEnchant->ScalingClass;
if ((*m_unitData->MinItemLevel || *m_unitData->MaxItemLevel) && pEnchant->ScalingClassRestricted)
scalingClass = pEnchant->ScalingClassRestricted;
uint8 minLevel = pEnchant->GetFlags().HasFlag(SpellItemEnchantmentFlags::ScaleAsAGem) ? 1 : 60;
uint8 scalingLevel = GetLevel();
uint8 maxLevel = uint8(pEnchant->MaxLevel ? pEnchant->MaxLevel : sSpellScalingGameTable.GetTableRowCount() - 1);
if (minLevel > GetLevel())
scalingLevel = minLevel;
else if (maxLevel < GetLevel())
scalingLevel = maxLevel;
if (GtSpellScalingEntry const* spellScaling = sSpellScalingGameTable.GetRow(scalingLevel))
enchant_amount = uint32(pEnchant->EffectScalingPoints[s] * GetSpellScalingColumnForClass(spellScaling, scalingClass));
}
enchant_amount = std::max(enchant_amount, 1u);
HandleStatFlatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + enchant_spell_id), TOTAL_VALUE, float(enchant_amount), apply);
break;
case ITEM_ENCHANTMENT_TYPE_STAT:
{
if (pEnchant->ScalingClass)
{
int32 scalingClass = pEnchant->ScalingClass;
if ((*m_unitData->MinItemLevel || *m_unitData->MaxItemLevel) && pEnchant->ScalingClassRestricted)
scalingClass = pEnchant->ScalingClassRestricted;
uint8 minLevel = pEnchant->GetFlags().HasFlag(SpellItemEnchantmentFlags::ScaleAsAGem) ? 1 : 60;
uint8 scalingLevel = GetLevel();
uint8 maxLevel = uint8(pEnchant->MaxLevel ? pEnchant->MaxLevel : sSpellScalingGameTable.GetTableRowCount() - 1);
if (minLevel > GetLevel())
scalingLevel = minLevel;
else if (maxLevel < GetLevel())
scalingLevel = maxLevel;
if (GtSpellScalingEntry const* spellScaling = sSpellScalingGameTable.GetRow(scalingLevel))
enchant_amount = uint32(pEnchant->EffectScalingPoints[s] * GetSpellScalingColumnForClass(spellScaling, scalingClass));
}
enchant_amount = std::max(enchant_amount, 1u);
TC_LOG_DEBUG("entities.player.items", "Adding %u to stat nb %u", enchant_amount, enchant_spell_id);
switch (enchant_spell_id)
{
case ITEM_MOD_MANA:
TC_LOG_DEBUG("entities.player.items", "+ %u MANA", enchant_amount);
HandleStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, float(enchant_amount), apply);
break;
case ITEM_MOD_HEALTH:
TC_LOG_DEBUG("entities.player.items", "+ %u HEALTH", enchant_amount);
HandleStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, float(enchant_amount), apply);
break;
case ITEM_MOD_AGILITY:
TC_LOG_DEBUG("entities.player.items", "+ %u AGILITY", enchant_amount);
HandleStatFlatModifier(UNIT_MOD_STAT_AGILITY, TOTAL_VALUE, float(enchant_amount), apply);
UpdateStatBuffMod(STAT_AGILITY);
break;
case ITEM_MOD_STRENGTH:
TC_LOG_DEBUG("entities.player.items", "+ %u STRENGTH", enchant_amount);
HandleStatFlatModifier(UNIT_MOD_STAT_STRENGTH, TOTAL_VALUE, float(enchant_amount), apply);
UpdateStatBuffMod(STAT_STRENGTH);
break;
case ITEM_MOD_INTELLECT:
TC_LOG_DEBUG("entities.player.items", "+ %u INTELLECT", enchant_amount);
HandleStatFlatModifier(UNIT_MOD_STAT_INTELLECT, TOTAL_VALUE, float(enchant_amount), apply);
UpdateStatBuffMod(STAT_INTELLECT);
break;
// case ITEM_MOD_SPIRIT:
// TC_LOG_DEBUG("entities.player.items", "+ %u SPIRIT", enchant_amount);
// HandleStatModifier(UNIT_MOD_STAT_SPIRIT, TOTAL_VALUE, float(enchant_amount), apply);
// ApplyStatBuffMod(STAT_SPIRIT, (float)enchant_amount, apply);
// break;
case ITEM_MOD_STAMINA:
TC_LOG_DEBUG("entities.player.items", "+ %u STAMINA", enchant_amount);
HandleStatFlatModifier(UNIT_MOD_STAT_STAMINA, TOTAL_VALUE, float(enchant_amount), apply);
UpdateStatBuffMod(STAT_STAMINA);
break;
case ITEM_MOD_DEFENSE_SKILL_RATING:
ApplyRatingMod(CR_DEFENSE_SKILL, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u DEFENSE", enchant_amount);
break;
case ITEM_MOD_DODGE_RATING:
ApplyRatingMod(CR_DODGE, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u DODGE", enchant_amount);
break;
case ITEM_MOD_PARRY_RATING:
ApplyRatingMod(CR_PARRY, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u PARRY", enchant_amount);
break;
case ITEM_MOD_BLOCK_RATING:
ApplyRatingMod(CR_BLOCK, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u SHIELD_BLOCK", enchant_amount);
break;
case ITEM_MOD_HIT_MELEE_RATING:
ApplyRatingMod(CR_HIT_MELEE, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u MELEE_HIT", enchant_amount);
break;
case ITEM_MOD_HIT_RANGED_RATING:
ApplyRatingMod(CR_HIT_RANGED, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u RANGED_HIT", enchant_amount);
break;
case ITEM_MOD_HIT_SPELL_RATING:
ApplyRatingMod(CR_HIT_SPELL, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u SPELL_HIT", enchant_amount);
break;
case ITEM_MOD_CRIT_MELEE_RATING:
ApplyRatingMod(CR_CRIT_MELEE, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u MELEE_CRIT", enchant_amount);
break;
case ITEM_MOD_CRIT_RANGED_RATING:
ApplyRatingMod(CR_CRIT_RANGED, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u RANGED_CRIT", enchant_amount);
break;
case ITEM_MOD_CRIT_SPELL_RATING:
ApplyRatingMod(CR_CRIT_SPELL, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u SPELL_CRIT", enchant_amount);
break;
// Values from ITEM_STAT_MELEE_HA_RATING to ITEM_MOD_HASTE_RANGED_RATING are never used
// in Enchantments
// case ITEM_MOD_HIT_TAKEN_MELEE_RATING:
// ApplyRatingMod(CR_HIT_TAKEN_MELEE, enchant_amount, apply);
// break;
// case ITEM_MOD_HIT_TAKEN_RANGED_RATING:
// ApplyRatingMod(CR_HIT_TAKEN_RANGED, enchant_amount, apply);
// break;
// case ITEM_MOD_HIT_TAKEN_SPELL_RATING:
// ApplyRatingMod(CR_HIT_TAKEN_SPELL, enchant_amount, apply);
// break;
// case ITEM_MOD_CRIT_TAKEN_MELEE_RATING:
// ApplyRatingMod(CR_CRIT_TAKEN_MELEE, enchant_amount, apply);
// break;
// case ITEM_MOD_CRIT_TAKEN_RANGED_RATING:
// ApplyRatingMod(CR_CRIT_TAKEN_RANGED, enchant_amount, apply);
// break;
// case ITEM_MOD_CRIT_TAKEN_SPELL_RATING:
// ApplyRatingMod(CR_CRIT_TAKEN_SPELL, enchant_amount, apply);
// break;
// case ITEM_MOD_HASTE_MELEE_RATING:
// ApplyRatingMod(CR_HASTE_MELEE, enchant_amount, apply);
// break;
// case ITEM_MOD_HASTE_RANGED_RATING:
// ApplyRatingMod(CR_HASTE_RANGED, enchant_amount, apply);
// break;
case ITEM_MOD_HASTE_SPELL_RATING:
ApplyRatingMod(CR_HASTE_SPELL, enchant_amount, apply);
break;
case ITEM_MOD_HIT_RATING:
ApplyRatingMod(CR_HIT_MELEE, enchant_amount, apply);
ApplyRatingMod(CR_HIT_RANGED, enchant_amount, apply);
ApplyRatingMod(CR_HIT_SPELL, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u HIT", enchant_amount);
break;
case ITEM_MOD_CRIT_RATING:
ApplyRatingMod(CR_CRIT_MELEE, enchant_amount, apply);
ApplyRatingMod(CR_CRIT_RANGED, enchant_amount, apply);
ApplyRatingMod(CR_CRIT_SPELL, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u CRITICAL", enchant_amount);
break;
// case ITEM_MOD_HIT_TAKEN_RATING: // Unused since 3.3.5
// ApplyRatingMod(CR_HIT_TAKEN_MELEE, enchant_amount, apply);
// ApplyRatingMod(CR_HIT_TAKEN_RANGED, enchant_amount, apply);
// ApplyRatingMod(CR_HIT_TAKEN_SPELL, enchant_amount, apply);
// break;
// case ITEM_MOD_CRIT_TAKEN_RATING: // Unused since 3.3.5
// ApplyRatingMod(CR_CRIT_TAKEN_MELEE, enchant_amount, apply);
// ApplyRatingMod(CR_CRIT_TAKEN_RANGED, enchant_amount, apply);
// ApplyRatingMod(CR_CRIT_TAKEN_SPELL, enchant_amount, apply);
// break;
case ITEM_MOD_RESILIENCE_RATING:
ApplyRatingMod(CR_RESILIENCE_PLAYER_DAMAGE, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u RESILIENCE", enchant_amount);
break;
case ITEM_MOD_HASTE_RATING:
ApplyRatingMod(CR_HASTE_MELEE, enchant_amount, apply);
ApplyRatingMod(CR_HASTE_RANGED, enchant_amount, apply);
ApplyRatingMod(CR_HASTE_SPELL, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u HASTE", enchant_amount);
break;
case ITEM_MOD_EXPERTISE_RATING:
ApplyRatingMod(CR_EXPERTISE, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u EXPERTISE", enchant_amount);
break;
case ITEM_MOD_ATTACK_POWER:
HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(enchant_amount), apply);
HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(enchant_amount), apply);
TC_LOG_DEBUG("entities.player.items", "+ %u ATTACK_POWER", enchant_amount);
break;
case ITEM_MOD_RANGED_ATTACK_POWER:
HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(enchant_amount), apply);
TC_LOG_DEBUG("entities.player.items", "+ %u RANGED_ATTACK_POWER", enchant_amount);
break;
case ITEM_MOD_MANA_REGENERATION:
ApplyManaRegenBonus(enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u MANA_REGENERATION", enchant_amount);
break;
case ITEM_MOD_ARMOR_PENETRATION_RATING:
ApplyRatingMod(CR_ARMOR_PENETRATION, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u ARMOR PENETRATION", enchant_amount);
break;
case ITEM_MOD_SPELL_POWER:
ApplySpellPowerBonus(enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u SPELL_POWER", enchant_amount);
break;
case ITEM_MOD_HEALTH_REGEN:
ApplyHealthRegenBonus(enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u HEALTH_REGENERATION", enchant_amount);
break;
case ITEM_MOD_SPELL_PENETRATION:
ApplySpellPenetrationBonus(enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u SPELL_PENETRATION", enchant_amount);
break;
case ITEM_MOD_BLOCK_VALUE:
HandleBaseModFlatValue(SHIELD_BLOCK_VALUE, float(enchant_amount), apply);
TC_LOG_DEBUG("entities.player.items", "+ %u BLOCK_VALUE", enchant_amount);
break;
case ITEM_MOD_MASTERY_RATING:
ApplyRatingMod(CR_MASTERY, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u MASTERY", enchant_amount);
break;
case ITEM_MOD_VERSATILITY:
ApplyRatingMod(CR_VERSATILITY_DAMAGE_DONE, enchant_amount, apply);
ApplyRatingMod(CR_VERSATILITY_HEALING_DONE, enchant_amount, apply);
ApplyRatingMod(CR_VERSATILITY_DAMAGE_TAKEN, enchant_amount, apply);
TC_LOG_DEBUG("entities.player.items", "+ %u VERSATILITY", enchant_amount);
break;
default:
break;
}
break;
}
case ITEM_ENCHANTMENT_TYPE_TOTEM: // Shaman Rockbiter Weapon
{
WeaponAttackType const attackType = Player::GetAttackBySlot(item->GetSlot(), item->GetTemplate()->GetInventoryType());
if (attackType != MAX_ATTACK)
UpdateDamageDoneMods(attackType, apply ? -1 : slot);
break;
}
case ITEM_ENCHANTMENT_TYPE_USE_SPELL:
// processed in Player::CastItemUseSpell
break;
case ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET:
case ITEM_ENCHANTMENT_TYPE_ARTIFACT_POWER_BONUS_RANK_BY_TYPE:
case ITEM_ENCHANTMENT_TYPE_ARTIFACT_POWER_BONUS_RANK_BY_ID:
case ITEM_ENCHANTMENT_TYPE_BONUS_LIST_ID:
case ITEM_ENCHANTMENT_TYPE_BONUS_LIST_CURVE:
case ITEM_ENCHANTMENT_TYPE_ARTIFACT_POWER_BONUS_RANK_PICKER:
// nothing do..
break;
default:
TC_LOG_ERROR("entities.player", "Player::ApplyEnchantment: Unknown item enchantment (ID: %u, DisplayType: %u) for player '%s' (%s)",
enchant_id, enchant_display_type, GetName().c_str(), GetGUID().ToString().c_str());
break;
}
}
}
// visualize enchantment at player and equipped items
if (slot == PERM_ENCHANTMENT_SLOT)
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::VisibleItems, item->GetSlot()).ModifyValue(&UF::VisibleItem::ItemVisual), item->GetVisibleItemVisual(this));
if (apply_dur)
{
if (apply)
{
// set duration
uint32 duration = item->GetEnchantmentDuration(slot);
if (duration > 0)
AddEnchantmentDuration(item, slot, duration);
}
else
{
// duration == 0 will remove EnchantDuration
AddEnchantmentDuration(item, slot, 0);
}
}
}
void Player::UpdateSkillEnchantments(uint16 skill_id, uint16 curr_value, uint16 new_value)
{
for (uint8 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (m_items[i])
{
for (uint8 slot = 0; slot < MAX_ENCHANTMENT_SLOT; ++slot)
{
uint32 ench_id = m_items[i]->GetEnchantmentId(EnchantmentSlot(slot));
if (!ench_id)
continue;
SpellItemEnchantmentEntry const* Enchant = sSpellItemEnchantmentStore.LookupEntry(ench_id);
if (!Enchant)
return;
if (Enchant->RequiredSkillID == skill_id)
{
// Checks if the enchantment needs to be applied or removed
if (curr_value < Enchant->RequiredSkillRank && new_value >= Enchant->RequiredSkillRank)
ApplyEnchantment(m_items[i], EnchantmentSlot(slot), true);
else if (new_value < Enchant->RequiredSkillRank && curr_value >= Enchant->RequiredSkillRank)
ApplyEnchantment(m_items[i], EnchantmentSlot(slot), false);
}
// If we're dealing with a gem inside a prismatic socket we need to check the prismatic socket requirements
// rather than the gem requirements itself. If the socket has no color it is a prismatic socket.
if ((slot == SOCK_ENCHANTMENT_SLOT || slot == SOCK_ENCHANTMENT_SLOT_2 || slot == SOCK_ENCHANTMENT_SLOT_3)
&& !m_items[i]->GetSocketColor(slot - SOCK_ENCHANTMENT_SLOT))
{
SpellItemEnchantmentEntry const* pPrismaticEnchant = sSpellItemEnchantmentStore.LookupEntry(m_items[i]->GetEnchantmentId(PRISMATIC_ENCHANTMENT_SLOT));
if (pPrismaticEnchant && pPrismaticEnchant->RequiredSkillID == skill_id)
{
if (curr_value < pPrismaticEnchant->RequiredSkillRank && new_value >= pPrismaticEnchant->RequiredSkillRank)
ApplyEnchantment(m_items[i], EnchantmentSlot(slot), true);
else if (new_value < pPrismaticEnchant->RequiredSkillRank && curr_value >= pPrismaticEnchant->RequiredSkillRank)
ApplyEnchantment(m_items[i], EnchantmentSlot(slot), false);
}
}
}
}
}
}
void Player::SendEnchantmentDurations()
{
for (EnchantDurationList::const_iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr)
GetSession()->SendItemEnchantTimeUpdate(GetGUID(), itr->item->GetGUID(), itr->slot, uint32(itr->leftduration) / 1000);
}
void Player::SendItemDurations()
{
for (ItemDurationList::const_iterator itr = m_itemDuration.begin(); itr != m_itemDuration.end(); ++itr)
(*itr)->SendTimeUpdate(this);
}
void Player::SendNewItem(Item* item, uint32 quantity, bool pushed, bool created, bool broadcast)
{
if (!item) // prevent crash
return;
WorldPackets::Item::ItemPushResult packet;
packet.PlayerGUID = GetGUID();
packet.Slot = item->GetBagSlot();
packet.SlotInBag = item->GetCount() == quantity ? item->GetSlot() : -1;
packet.Item.Initialize(item);
//packet.QuestLogItemID;
packet.Quantity = quantity;
packet.QuantityInInventory = GetItemCount(item->GetEntry());
//packet.DungeonEncounterID;
packet.BattlePetSpeciesID = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID);
packet.BattlePetBreedID = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) & 0xFFFFFF;
packet.BattlePetBreedQuality = (item->GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA) >> 24) & 0xFF;
packet.BattlePetLevel = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL);
packet.ItemGUID = item->GetGUID();
packet.Pushed = pushed;
packet.DisplayText = WorldPackets::Item::ItemPushResult::DISPLAY_TYPE_NORMAL;
packet.Created = created;
//packet.IsBonusRoll;
//packet.IsEncounterLoot;
if (broadcast && GetGroup())
GetGroup()->BroadcastPacket(packet.Write(), true);
else
SendDirectMessage(packet.Write());
}
/*********************************************************/
/*** GOSSIP SYSTEM ***/
/*********************************************************/
void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool showQuests /*= false*/)
{
PlayerMenu* menu = PlayerTalkClass;
menu->ClearMenus();
menu->GetGossipMenu().SetMenuId(menuId);
GossipMenuItemsMapBounds menuItemBounds = sObjectMgr->GetGossipMenuItemsMapBounds(menuId);
// if default menuId and no menu options exist for this, use options from default options
if (menuItemBounds.first == menuItemBounds.second && menuId == GetDefaultGossipMenuForSource(source))
menuItemBounds = sObjectMgr->GetGossipMenuItemsMapBounds(0);
uint64 npcflags = 0;
if (source->GetTypeId() == TYPEID_UNIT)
{
npcflags = (uint64(source->ToUnit()->m_unitData->NpcFlags[1]) << 32) | source->ToUnit()->m_unitData->NpcFlags[0];
if (showQuests && npcflags & UNIT_NPC_FLAG_QUESTGIVER)
PrepareQuestMenu(source->GetGUID());
}
else if (source->GetTypeId() == TYPEID_GAMEOBJECT)
if (showQuests && source->ToGameObject()->GetGoType() == GAMEOBJECT_TYPE_QUESTGIVER)
PrepareQuestMenu(source->GetGUID());
for (GossipMenuItemsContainer::const_iterator itr = menuItemBounds.first; itr != menuItemBounds.second; ++itr)
{
if (!sConditionMgr->IsObjectMeetToConditions(this, source, itr->second.Conditions))
continue;
bool canTalk = true;
if (Creature* creature = source->ToCreature())
{
if (!(itr->second.OptionNpcFlag & npcflags))
continue;
switch (itr->second.OptionType)
{
case GOSSIP_OPTION_ARMORER:
canTalk = false; // added in special mode
break;
case GOSSIP_OPTION_SPIRITHEALER:
if (!isDead())
canTalk = false;
break;
case GOSSIP_OPTION_LEARNDUALSPEC:
canTalk = false;
break;
case GOSSIP_OPTION_UNLEARNTALENTS:
if (!creature->CanResetTalents(this))
canTalk = false;
break;
case GOSSIP_OPTION_TAXIVENDOR:
if (GetSession()->SendLearnNewTaxiNode(creature))
return;
break;
case GOSSIP_OPTION_BATTLEFIELD:
if (!creature->isCanInteractWithBattleMaster(this, false))
canTalk = false;
break;
case GOSSIP_OPTION_STABLEPET:
if (GetClass() != CLASS_HUNTER)
canTalk = false;
break;
case GOSSIP_OPTION_QUESTGIVER:
canTalk = false;
break;
case GOSSIP_OPTION_GOSSIP:
case GOSSIP_OPTION_VENDOR:
case GOSSIP_OPTION_TRAINER:
case GOSSIP_OPTION_SPIRITGUIDE:
case GOSSIP_OPTION_INNKEEPER:
case GOSSIP_OPTION_BANKER:
case GOSSIP_OPTION_PETITIONER:
case GOSSIP_OPTION_TABARDDESIGNER:
case GOSSIP_OPTION_AUCTIONEER:
case GOSSIP_OPTION_TRANSMOGRIFIER:
break; // no checks
case GOSSIP_OPTION_OUTDOORPVP:
if (!sOutdoorPvPMgr->CanTalkTo(this, creature, itr->second))
canTalk = false;
break;
default:
TC_LOG_ERROR("sql.sql", "Creature entry %u has unknown gossip option %u for menu %u.", creature->GetEntry(), itr->second.OptionType, itr->second.MenuID);
canTalk = false;
break;
}
}
else if (GameObject* go = source->ToGameObject())
{
switch (itr->second.OptionType)
{
case GOSSIP_OPTION_GOSSIP:
if (go->GetGoType() != GAMEOBJECT_TYPE_QUESTGIVER && go->GetGoType() != GAMEOBJECT_TYPE_GOOBER)
canTalk = false;
break;
default:
canTalk = false;
break;
}
}
if (canTalk)
{
std::string strOptionText, strBoxText;
BroadcastTextEntry const* optionBroadcastText = sBroadcastTextStore.LookupEntry(itr->second.OptionBroadcastTextID);
BroadcastTextEntry const* boxBroadcastText = sBroadcastTextStore.LookupEntry(itr->second.BoxBroadcastTextID);
LocaleConstant locale = GetSession()->GetSessionDbLocaleIndex();
if (optionBroadcastText)
strOptionText = DB2Manager::GetBroadcastTextValue(optionBroadcastText, locale, GetGender());
else
strOptionText = itr->second.OptionText;
if (boxBroadcastText)
strBoxText = DB2Manager::GetBroadcastTextValue(boxBroadcastText, locale, GetGender());
else
strBoxText = itr->second.BoxText;
if (locale != DEFAULT_LOCALE)
{
if (!optionBroadcastText)
{
/// Find localizations from database.
if (GossipMenuItemsLocale const* gossipMenuLocale = sObjectMgr->GetGossipMenuItemsLocale(menuId, itr->second.OptionID))
ObjectMgr::GetLocaleString(gossipMenuLocale->OptionText, locale, strOptionText);
}
if (!boxBroadcastText)
{
/// Find localizations from database.
if (GossipMenuItemsLocale const* gossipMenuLocale = sObjectMgr->GetGossipMenuItemsLocale(menuId, itr->second.OptionID))
ObjectMgr::GetLocaleString(gossipMenuLocale->BoxText, locale, strBoxText);
}
}
menu->GetGossipMenu().AddMenuItem(itr->second.OptionID, itr->second.OptionIcon, strOptionText, 0, itr->second.OptionType, strBoxText, itr->second.BoxMoney, itr->second.BoxCoded);
menu->GetGossipMenu().AddGossipMenuItemData(itr->second.OptionID, itr->second.ActionMenuID, itr->second.ActionPoiID);
}
}
}
void Player::SendPreparedGossip(WorldObject* source)
{
if (!source)
return;
if (source->GetTypeId() == TYPEID_UNIT || source->GetTypeId() == TYPEID_GAMEOBJECT)
{
if (PlayerTalkClass->GetGossipMenu().Empty() && !PlayerTalkClass->GetQuestMenu().Empty())
{
SendPreparedQuest(source);
return;
}
}
// in case non empty gossip menu (that not included quests list size) show it
// (quest entries from quest menu will be included in list)
uint32 textId = GetGossipTextId(source);
if (uint32 menuId = PlayerTalkClass->GetGossipMenu().GetMenuId())
textId = GetGossipTextId(menuId, source);
PlayerTalkClass->SendGossipMenu(textId, source->GetGUID());
}
void Player::OnGossipSelect(WorldObject* source, uint32 gossipListId, uint32 menuId)
{
GossipMenu& gossipMenu = PlayerTalkClass->GetGossipMenu();
// if not same, then something funky is going on
if (menuId != gossipMenu.GetMenuId())
return;
GossipMenuItem const* item = gossipMenu.GetItem(gossipListId);
if (!item)
return;
uint32 gossipOptionType = item->OptionType;
ObjectGuid guid = source->GetGUID();
if (source->GetTypeId() == TYPEID_GAMEOBJECT)
{
if (gossipOptionType > GOSSIP_OPTION_QUESTGIVER)
{
TC_LOG_ERROR("entities.player", "Player '%s' (%s) requests invalid gossip option for GameObject (Entry: %u)",
GetName().c_str(), GetGUID().ToString().c_str(), source->GetEntry());
return;
}
}
GossipMenuItemData const* menuItemData = gossipMenu.GetItemData(gossipListId);
if (!menuItemData)
return;
int64 cost = int64(item->BoxMoney);
if (!HasEnoughMoney(cost))
{
SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, nullptr, 0, 0);
PlayerTalkClass->SendCloseGossip();
return;
}
switch (gossipOptionType)
{
case GOSSIP_OPTION_GOSSIP:
{
if (menuItemData->GossipActionPoi)
PlayerTalkClass->SendPointOfInterest(menuItemData->GossipActionPoi);
if (menuItemData->GossipActionMenuId)
{
PrepareGossipMenu(source, menuItemData->GossipActionMenuId);
SendPreparedGossip(source);
}
break;
}
case GOSSIP_OPTION_OUTDOORPVP:
sOutdoorPvPMgr->HandleGossipOption(this, source->ToCreature(), gossipListId);
break;
case GOSSIP_OPTION_SPIRITHEALER:
if (isDead())
source->ToCreature()->CastSpell(source->ToCreature(), 17251, CastSpellExtraArgs(TRIGGERED_FULL_MASK)
.SetOriginalCaster(GetGUID()));
break;
case GOSSIP_OPTION_QUESTGIVER:
PrepareQuestMenu(guid);
SendPreparedQuest(source);
break;
case GOSSIP_OPTION_VENDOR:
case GOSSIP_OPTION_ARMORER:
GetSession()->SendListInventory(guid);
break;
case GOSSIP_OPTION_STABLEPET:
GetSession()->SendStablePet(guid);
break;
case GOSSIP_OPTION_TRAINER:
GetSession()->SendTrainerList(source->ToCreature(), sObjectMgr->GetCreatureTrainerForGossipOption(source->GetEntry(), menuId, gossipListId));
break;
case GOSSIP_OPTION_LEARNDUALSPEC:
break;
case GOSSIP_OPTION_UNLEARNTALENTS:
PlayerTalkClass->SendCloseGossip();
SendRespecWipeConfirm(guid, sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST) ? 0 : GetNextResetTalentsCost());
break;
case GOSSIP_OPTION_TAXIVENDOR:
GetSession()->SendTaxiMenu(source->ToCreature());
break;
case GOSSIP_OPTION_INNKEEPER:
PlayerTalkClass->SendCloseGossip();
SetBindPoint(guid);
break;
case GOSSIP_OPTION_BANKER:
GetSession()->SendShowBank(guid);
break;
case GOSSIP_OPTION_PETITIONER:
PlayerTalkClass->SendCloseGossip();
GetSession()->SendPetitionShowList(guid);
break;
case GOSSIP_OPTION_TABARDDESIGNER:
PlayerTalkClass->SendCloseGossip();
GetSession()->SendTabardVendorActivate(guid);
break;
case GOSSIP_OPTION_AUCTIONEER:
GetSession()->SendAuctionHello(guid, source->ToCreature());
break;
case GOSSIP_OPTION_SPIRITGUIDE:
PrepareGossipMenu(source);
SendPreparedGossip(source);
break;
case GOSSIP_OPTION_BATTLEFIELD:
{
BattlegroundTypeId bgTypeId = sBattlegroundMgr->GetBattleMasterBG(source->GetEntry());
if (bgTypeId == BATTLEGROUND_TYPE_NONE)
{
TC_LOG_ERROR("entities.player", "Player '%s' (%s) requested battlegroundlist from an invalid creature (%s)",
GetName().c_str(), GetGUID().ToString().c_str(), source->GetGUID().ToString().c_str());
return;
}
sBattlegroundMgr->SendBattlegroundList(this, guid, bgTypeId);
break;
}
case GOSSIP_OPTION_TRANSMOGRIFIER:
GetSession()->SendOpenTransmogrifier(guid);
break;
}
ModifyMoney(-cost);
}
uint32 Player::GetGossipTextId(WorldObject* source)
{
if (!source)
return DEFAULT_GOSSIP_MESSAGE;
return GetGossipTextId(GetDefaultGossipMenuForSource(source), source);
}
uint32 Player::GetGossipTextId(uint32 menuId, WorldObject* source)
{
uint32 textId = DEFAULT_GOSSIP_MESSAGE;
if (!menuId)
return textId;
GossipMenusMapBounds menuBounds = sObjectMgr->GetGossipMenusMapBounds(menuId);
for (GossipMenusContainer::const_iterator itr = menuBounds.first; itr != menuBounds.second; ++itr)
{
if (sConditionMgr->IsObjectMeetToConditions(this, source, itr->second.Conditions))
textId = itr->second.TextID;
}
return textId;
}
uint32 Player::GetDefaultGossipMenuForSource(WorldObject* source)
{
switch (source->GetTypeId())
{
case TYPEID_UNIT:
return source->ToCreature()->GetCreatureTemplate()->GossipMenuId;
case TYPEID_GAMEOBJECT:
return source->ToGameObject()->GetGOInfo()->GetGossipMenuId();
default:
break;
}
return 0;
}
/*********************************************************/
/*** QUEST SYSTEM ***/
/*********************************************************/
int32 Player::GetQuestMinLevel(Quest const* quest) const
{
if (Optional<ContentTuningLevels> questLevels = sDB2Manager.GetContentTuningData(quest->GetContentTuningId(), m_playerData->CtrOptions->ContentTuningConditionMask))
{
ChrRacesEntry const* race = sChrRacesStore.AssertEntry(GetRace());
FactionTemplateEntry const* raceFaction = sFactionTemplateStore.AssertEntry(race->FactionID);
int32 questFactionGroup = sContentTuningStore.AssertEntry(quest->GetContentTuningId())->GetScalingFactionGroup();
if (questFactionGroup && raceFaction->FactionGroup != questFactionGroup)
return questLevels->MaxLevel;
return questLevels->MinLevelWithDelta;
}
return 0;
}
int32 Player::GetQuestLevel(Quest const* quest) const
{
if (!quest)
return 0;
if (Optional<ContentTuningLevels> questLevels = sDB2Manager.GetContentTuningData(quest->GetContentTuningId(), m_playerData->CtrOptions->ContentTuningConditionMask))
{
int32 minLevel = GetQuestMinLevel(quest);
int32 maxLevel = questLevels->MaxLevel;
int32 level = GetLevel();
if (level >= minLevel)
return std::min(level, maxLevel);
return minLevel;
}
return 0;
}
void Player::PrepareQuestMenu(ObjectGuid guid)
{
QuestRelationResult objectQR;
QuestRelationResult objectQIR;
// pets also can have quests
Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, guid);
if (creature)
{
objectQR = sObjectMgr->GetCreatureQuestRelations(creature->GetEntry());
objectQIR = sObjectMgr->GetCreatureQuestInvolvedRelations(creature->GetEntry());
}
else
{
//we should obtain map pointer from GetMap() in 99% of cases. Special case
//only for quests which cast teleport spells on player
Map* _map = IsInWorld() ? GetMap() : sMapMgr->FindMap(GetMapId(), GetInstanceId());
ASSERT(_map);
GameObject* gameObject = _map->GetGameObject(guid);
if (gameObject)
{
objectQR = sObjectMgr->GetGOQuestRelations(gameObject->GetEntry());
objectQIR = sObjectMgr->GetGOQuestInvolvedRelations(gameObject->GetEntry());
}
else
return;
}
QuestMenu &qm = PlayerTalkClass->GetQuestMenu();
qm.ClearMenu();
for (uint32 quest_id : objectQIR)
{
QuestStatus status = GetQuestStatus(quest_id);
if (status == QUEST_STATUS_COMPLETE)
qm.AddMenuItem(quest_id, 4);
else if (status == QUEST_STATUS_INCOMPLETE)
qm.AddMenuItem(quest_id, 4);
//else if (status == QUEST_STATUS_AVAILABLE)
// qm.AddMenuItem(quest_id, 2);
}
for (uint32 quest_id : objectQR)
{
Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
if (!quest)
continue;
if (!CanTakeQuest(quest, false))
continue;
if (quest->IsAutoComplete() && (!quest->IsRepeatable() || quest->IsDaily() || quest->IsWeekly() || quest->IsMonthly()))
qm.AddMenuItem(quest_id, 0);
else if (quest->IsAutoComplete())
qm.AddMenuItem(quest_id, 4);
else if (GetQuestStatus(quest_id) == QUEST_STATUS_NONE)
qm.AddMenuItem(quest_id, 2);
}
}
void Player::SendPreparedQuest(WorldObject* source)
{
QuestMenu& questMenu = PlayerTalkClass->GetQuestMenu();
if (questMenu.Empty())
return;
// single element case
if (questMenu.GetMenuItemCount() == 1)
{
QuestMenuItem const& qmi0 = questMenu.GetItem(0);
uint32 questId = qmi0.QuestId;
// Auto open
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
if (qmi0.QuestIcon == 4)
PlayerTalkClass->SendQuestGiverRequestItems(quest, source->GetGUID(), CanRewardQuest(quest, false), true);
// Send completable on repeatable and autoCompletable quest if player don't have quest
/// @todo verify if check for !quest->IsDaily() is really correct (possibly not)
else if (!source->hasQuest(questId) && !source->hasInvolvedQuest(questId))
PlayerTalkClass->SendCloseGossip();
else
{
if (quest->IsAutoAccept() && CanAddQuest(quest, true) && CanTakeQuest(quest, true))
AddQuestAndCheckCompletion(quest, source);
if (quest->IsAutoComplete() && quest->IsRepeatable() && !quest->IsDailyOrWeekly())
PlayerTalkClass->SendQuestGiverRequestItems(quest, source->GetGUID(), CanCompleteRepeatableQuest(quest), true);
else
PlayerTalkClass->SendQuestGiverQuestDetails(quest, source->GetGUID(), true, false);
}
return;
}
}
PlayerTalkClass->SendQuestGiverQuestListMessage(source);
}
bool Player::IsActiveQuest(uint32 quest_id) const
{
return m_QuestStatus.find(quest_id) != m_QuestStatus.end();
}
Quest const* Player::GetNextQuest(ObjectGuid guid, Quest const* quest) const
{
QuestRelationResult quests;
uint32 nextQuestID = quest->GetNextQuestInChain();
switch (guid.GetHigh())
{
case HighGuid::Player:
ASSERT(quest->HasFlag(QUEST_FLAGS_AUTOCOMPLETE));
return sObjectMgr->GetQuestTemplate(nextQuestID);
case HighGuid::Creature:
case HighGuid::Pet:
case HighGuid::Vehicle:
{
if (Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, guid))
quests = sObjectMgr->GetCreatureQuestRelations(creature->GetEntry());
else
return nullptr;
break;
}
case HighGuid::GameObject:
{
//we should obtain map pointer from GetMap() in 99% of cases. Special case
//only for quests which cast teleport spells on player
Map* _map = IsInWorld() ? GetMap() : sMapMgr->FindMap(GetMapId(), GetInstanceId());
ASSERT(_map);
if (GameObject* gameObject = _map->GetGameObject(guid))
quests = sObjectMgr->GetGOQuestRelations(gameObject->GetEntry());
else
return nullptr;
break;
}
default:
return nullptr;
}
if (uint32 nextQuestID = quest->GetNextQuestInChain())
if (quests.HasQuest(nextQuestID))
return sObjectMgr->GetQuestTemplate(nextQuestID);
return nullptr;
}
bool Player::CanSeeStartQuest(Quest const* quest) const
{
if (!DisableMgr::IsDisabledFor(DISABLE_TYPE_QUEST, quest->GetQuestId(), this) && SatisfyQuestClass(quest, false) && SatisfyQuestRace(quest, false) &&
SatisfyQuestSkill(quest, false) && SatisfyQuestExclusiveGroup(quest, false) && SatisfyQuestReputation(quest, false) &&
SatisfyQuestDependentQuests(quest, false) &&
SatisfyQuestDay(quest, false) && SatisfyQuestWeek(quest, false) &&
SatisfyQuestMonth(quest, false) && SatisfyQuestSeasonal(quest, false) && SatisfyQuestExpansion(quest, false))
{
return int32(GetLevel() + sWorld->getIntConfig(CONFIG_QUEST_HIGH_LEVEL_HIDE_DIFF)) >= GetQuestMinLevel(quest);
}
return false;
}
bool Player::CanTakeQuest(Quest const* quest, bool msg) const
{
return !DisableMgr::IsDisabledFor(DISABLE_TYPE_QUEST, quest->GetQuestId(), this)
&& SatisfyQuestStatus(quest, msg) && SatisfyQuestExclusiveGroup(quest, msg)
&& SatisfyQuestClass(quest, msg) && SatisfyQuestRace(quest, msg) && SatisfyQuestLevel(quest, msg)
&& SatisfyQuestSkill(quest, msg) && SatisfyQuestReputation(quest, msg)
&& SatisfyQuestDependentQuests(quest, msg) && SatisfyQuestTimed(quest, msg)
&& SatisfyQuestDay(quest, msg) && SatisfyQuestWeek(quest, msg)
&& SatisfyQuestMonth(quest, msg) && SatisfyQuestSeasonal(quest, msg)
&& SatisfyQuestConditions(quest, msg) && SatisfyQuestExpansion(quest, msg);
}
bool Player::CanAddQuest(Quest const* quest, bool msg) const
{
if (!SatisfyQuestLog(msg))
return false;
uint32 srcitem = quest->GetSrcItemId();
if (srcitem > 0)
{
uint32 count = quest->GetSrcItemCount();
ItemPosCountVec dest;
InventoryResult msg2 = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, srcitem, count);
// player already have max number (in most case 1) source item, no additional item needed and quest can be added.
if (msg2 == EQUIP_ERR_ITEM_MAX_COUNT)
return true;
if (msg2 != EQUIP_ERR_OK)
{
SendEquipError(msg2, nullptr, nullptr, srcitem);
return false;
}
}
return true;
}
bool Player::CanCompleteQuest(uint32 quest_id, uint32 ignoredQuestObjectiveId /*= 0*/)
{
if (quest_id)
{
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
if (!qInfo)
return false;
if (!qInfo->IsRepeatable() && GetQuestRewardStatus(quest_id))
return false; // not allow re-complete quest
// auto complete quest
if (qInfo->IsAutoComplete() && CanTakeQuest(qInfo, false))
return true;
QuestStatusMap::iterator itr = m_QuestStatus.find(quest_id);
if (itr == m_QuestStatus.end())
return false;
QuestStatusData &q_status = itr->second;
if (q_status.Status == QUEST_STATUS_INCOMPLETE)
{
for (QuestObjective const& obj : qInfo->GetObjectives())
{
if (ignoredQuestObjectiveId && obj.ID == ignoredQuestObjectiveId)
continue;
if (!(obj.Flags & QUEST_OBJECTIVE_FLAG_OPTIONAL) && !(obj.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR))
{
if (!IsQuestObjectiveComplete(q_status.Slot, qInfo, obj))
return false;
}
}
if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT) && !q_status.Explored)
return false;
if (qInfo->GetLimitTime() && q_status.Timer == 0)
return false;
return true;
}
}
return false;
}
bool Player::CanCompleteRepeatableQuest(Quest const* quest)
{
// Solve problem that player don't have the quest and try complete it.
// if repeatable she must be able to complete event if player don't have it.
// Seem that all repeatable quest are DELIVER Flag so, no need to add more.
if (!CanTakeQuest(quest, false))
return false;
if (quest->HasQuestObjectiveType(QUEST_OBJECTIVE_ITEM))
for (QuestObjective const& obj : quest->GetObjectives())
if (obj.Type == QUEST_OBJECTIVE_ITEM && !HasItemCount(obj.ObjectID, obj.Amount))
return false;
if (!CanRewardQuest(quest, false))
return false;
return true;
}
bool Player::CanRewardQuest(Quest const* quest, bool msg) const
{
// quest is disabled
if (DisableMgr::IsDisabledFor(DISABLE_TYPE_QUEST, quest->GetQuestId(), this))
return false;
// not auto complete quest and not completed quest (only cheating case, then ignore without message)
if (!quest->IsDFQuest() && !quest->IsAutoComplete() && GetQuestStatus(quest->GetQuestId()) != QUEST_STATUS_COMPLETE)
return false;
// daily quest can't be rewarded (25 daily quest already completed)
if (!SatisfyQuestDay(quest, msg) || !SatisfyQuestWeek(quest, msg) || !SatisfyQuestMonth(quest, msg) || !SatisfyQuestSeasonal(quest, msg))
return false;
// player no longer satisfies the quest's requirements (skill level etc.)
if (!SatisfyQuestLevel(quest, msg) || !SatisfyQuestSkill(quest, msg) || !SatisfyQuestReputation(quest, msg))
return false;
// rewarded and not repeatable quest (only cheating case, then ignore without message)
if (GetQuestRewardStatus(quest->GetQuestId()))
return false;
// prevent receive reward with quest items in bank
if (quest->HasQuestObjectiveType(QUEST_OBJECTIVE_ITEM))
{
for (QuestObjective const& obj : quest->GetObjectives())
{
if (obj.Type != QUEST_OBJECTIVE_ITEM)
continue;
if (GetItemCount(obj.ObjectID) < uint32(obj.Amount))
{
if (msg)
SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr, obj.ObjectID);
return false;
}
}
}
for (QuestObjective const& obj : quest->GetObjectives())
{
switch (obj.Type)
{
case QUEST_OBJECTIVE_CURRENCY:
if (!HasCurrency(obj.ObjectID, obj.Amount))
return false;
break;
case QUEST_OBJECTIVE_MONEY:
if (!HasEnoughMoney(uint64(obj.Amount)))
return false;
break;
}
}
return true;
}
void Player::AddQuestAndCheckCompletion(Quest const* quest, Object* questGiver)
{
AddQuest(quest, questGiver);
for (QuestObjective const& obj : quest->GetObjectives())
if (obj.Type == QUEST_OBJECTIVE_CRITERIA_TREE)
if (m_questObjectiveCriteriaMgr->HasCompletedObjective(&obj))
KillCreditCriteriaTreeObjective(obj);
if (CanCompleteQuest(quest->GetQuestId()))
CompleteQuest(quest->GetQuestId());
if (!questGiver)
return;
switch (questGiver->GetTypeId())
{
case TYPEID_UNIT:
PlayerTalkClass->ClearMenus();
questGiver->ToCreature()->AI()->QuestAccept(this, quest);
break;
case TYPEID_ITEM:
case TYPEID_CONTAINER:
case TYPEID_AZERITE_ITEM:
case TYPEID_AZERITE_EMPOWERED_ITEM:
{
Item* item = static_cast<Item*>(questGiver);
sScriptMgr->OnQuestAccept(this, item, quest);
// There are two cases where the source item is not destroyed when the quest is accepted:
// - It is required to finish the quest, and is an unique item
// - It is the same item present in the source item field (item that would be given on quest accept)
bool destroyItem = true;
for (QuestObjective const& obj : quest->GetObjectives())
{
if (obj.Type == QUEST_OBJECTIVE_ITEM && uint32(obj.ObjectID) == item->GetEntry() && item->GetTemplate()->GetMaxCount() > 0)
{
destroyItem = false;
break;
}
}
if (quest->GetSrcItemId() == item->GetEntry())
destroyItem = false;
if (destroyItem)
DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
break;
}
case TYPEID_GAMEOBJECT:
PlayerTalkClass->ClearMenus();
questGiver->ToGameObject()->AI()->QuestAccept(this, quest);
break;
default:
break;
}
}
bool Player::CanRewardQuest(Quest const* quest, LootItemType rewardType, uint32 rewardId, bool msg) const
{
ItemPosCountVec dest;
if (quest->GetRewChoiceItemsCount() > 0)
{
switch (rewardType)
{
case LootItemType::Item:
{
for (uint32 i = 0; i < QUEST_REWARD_CHOICES_COUNT; ++i)
{
if (quest->RewardChoiceItemId[i] && quest->RewardChoiceItemType[i] == LootItemType::Item && quest->RewardChoiceItemId[i] == rewardId)
{
InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, quest->RewardChoiceItemId[i], quest->RewardChoiceItemCount[i]);
if (res != EQUIP_ERR_OK)
{
if (msg)
SendQuestFailed(quest->GetQuestId(), res);
return false;
}
}
}
break;
}
case LootItemType::Currency:
break;
default:
break;
}
}
if (quest->GetRewItemsCount() > 0)
{
for (uint32 i = 0; i < quest->GetRewItemsCount(); ++i)
{
if (quest->RewardItemId[i])
{
InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, quest->RewardItemId[i], quest->RewardItemCount[i]);
if (res != EQUIP_ERR_OK)
{
if (msg)
SendQuestFailed(quest->GetQuestId(), res);
return false;
}
}
}
}
// QuestPackageItem.db2
if (quest->GetQuestPackageID())
{
bool hasFilteredQuestPackageReward = false;
if (std::vector<QuestPackageItemEntry const*> const* questPackageItems = sDB2Manager.GetQuestPackageItems(quest->GetQuestPackageID()))
{
for (QuestPackageItemEntry const* questPackageItem : *questPackageItems)
{
if (questPackageItem->ItemID != int32(rewardId))
continue;
if (CanSelectQuestPackageItem(questPackageItem))
{
hasFilteredQuestPackageReward = true;
InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, questPackageItem->ItemID, questPackageItem->ItemQuantity);
if (res != EQUIP_ERR_OK)
{
SendEquipError(res, nullptr, nullptr, questPackageItem->ItemID);
return false;
}
}
}
}
if (!hasFilteredQuestPackageReward)
{
if (std::vector<QuestPackageItemEntry const*> const* questPackageItems = sDB2Manager.GetQuestPackageItemsFallback(quest->GetQuestPackageID()))
{
for (QuestPackageItemEntry const* questPackageItem : *questPackageItems)
{
if (questPackageItem->ItemID != int32(rewardId))
continue;
InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, questPackageItem->ItemID, questPackageItem->ItemQuantity);
if (res != EQUIP_ERR_OK)
{
SendEquipError(res, nullptr, nullptr, questPackageItem->ItemID);
return false;
}
}
}
}
}
return true;
}
void Player::AddQuest(Quest const* quest, Object* questGiver)
{
uint16 log_slot = FindQuestSlot(0);
if (log_slot >= MAX_QUEST_LOG_SIZE) // Player does not have any free slot in the quest log
return;
uint32 quest_id = quest->GetQuestId();
// if not exist then created with set uState == NEW and rewarded=false
auto questStatusItr = m_QuestStatus.emplace(quest_id, QuestStatusData{}).first;
QuestStatusData& questStatusData = questStatusItr->second;
QuestStatus oldStatus = questStatusData.Status;
// check for repeatable quests status reset
SetQuestSlot(log_slot, quest_id);
questStatusData.Slot = log_slot;
questStatusData.Status = QUEST_STATUS_INCOMPLETE;
questStatusData.Explored = false;
for (QuestObjective const& obj : quest->GetObjectives())
{
m_questObjectiveStatus.emplace(std::make_pair(QuestObjectiveType(obj.Type), obj.ObjectID), QuestObjectiveStatusData { questStatusItr, &obj });
switch (obj.Type)
{
case QUEST_OBJECTIVE_MIN_REPUTATION:
case QUEST_OBJECTIVE_MAX_REPUTATION:
if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(obj.ObjectID))
GetReputationMgr().SetVisible(factionEntry);
break;
case QUEST_OBJECTIVE_CRITERIA_TREE:
m_questObjectiveCriteriaMgr->ResetCriteriaTree(obj.ObjectID);
break;
default:
break;
}
}
GiveQuestSourceItem(quest);
AdjustQuestObjectiveProgress(quest);
time_t endTime = 0;
if (uint32 limittime = quest->GetLimitTime())
{
// shared timed quest
if (questGiver && questGiver->GetTypeId() == TYPEID_PLAYER)
limittime = questGiver->ToPlayer()->getQuestStatusMap()[quest_id].Timer / IN_MILLISECONDS;
AddTimedQuest(quest_id);
questStatusData.Timer = limittime * IN_MILLISECONDS;
endTime = GameTime::GetGameTime() + limittime;
}
else
questStatusData.Timer = 0;
if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP))
{
pvpInfo.IsHostile = true;
UpdatePvPState();
}
if (quest->GetSrcSpell() > 0)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(quest->GetSrcSpell(), GetMap()->GetDifficultyID());
Unit* caster = this;
if (questGiver && questGiver->isType(TYPEMASK_UNIT) && !quest->HasFlag(QUEST_FLAGS_PLAYER_CAST_ON_ACCEPT) && !spellInfo->HasTargetType(TARGET_UNIT_CASTER) && !spellInfo->HasTargetType(TARGET_DEST_CASTER_SUMMON))
if (Unit* unit = questGiver->ToUnit())
caster = unit;
caster->CastSpell(this, spellInfo->Id, CastSpellExtraArgs(TRIGGERED_FULL_MASK).SetCastDifficulty(spellInfo->Difficulty));
}
SetQuestSlotEndTime(log_slot, endTime);
SetQuestSlotAcceptTime(log_slot, GameTime::GetGameTime());
m_QuestStatusSave[quest_id] = QUEST_DEFAULT_SAVE_TYPE;
StartCriteriaTimer(CriteriaStartEvent::AcceptQuest, quest_id);
SendQuestUpdate(quest_id);
if (sWorld->getBoolConfig(CONFIG_QUEST_ENABLE_QUEST_TRACKER)) // check if Quest Tracker is enabled
{
// prepare Quest Tracker datas
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_QUEST_TRACK);
stmt->setUInt32(0, quest_id);
stmt->setUInt64(1, GetGUID().GetCounter());
stmt->setString(2, GitRevision::GetHash());
stmt->setString(3, GitRevision::GetDate());
// add to Quest Tracker
CharacterDatabase.Execute(stmt);
}
sScriptMgr->OnQuestStatusChange(this, quest_id);
sScriptMgr->OnQuestStatusChange(this, quest, oldStatus, questStatusData.Status);
}
void Player::CompleteQuest(uint32 quest_id)
{
if (quest_id)
{
SetQuestStatus(quest_id, QUEST_STATUS_COMPLETE);
if (QuestStatusData const* questStatus = Trinity::Containers::MapGetValuePtr(m_QuestStatus, quest_id))
SetQuestSlotState(questStatus->Slot, QUEST_STATE_COMPLETE);
if (Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id))
if (qInfo->HasFlag(QUEST_FLAGS_TRACKING))
RewardQuest(qInfo, LootItemType::Item, 0, this, false);
}
if (sWorld->getBoolConfig(CONFIG_QUEST_ENABLE_QUEST_TRACKER)) // check if Quest Tracker is enabled
{
// prepare Quest Tracker data
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_QUEST_TRACK_COMPLETE_TIME);
stmt->setUInt32(0, quest_id);
stmt->setUInt64(1, GetGUID().GetCounter());
// add to Quest Tracker
CharacterDatabase.Execute(stmt);
}
}
void Player::IncompleteQuest(uint32 quest_id)
{
if (quest_id)
{
SetQuestStatus(quest_id, QUEST_STATUS_INCOMPLETE);
uint16 log_slot = FindQuestSlot(quest_id);
if (log_slot < MAX_QUEST_LOG_SIZE)
RemoveQuestSlotState(log_slot, QUEST_STATE_COMPLETE);
}
}
uint32 Player::GetQuestMoneyReward(Quest const* quest) const
{
return quest->MoneyValue(this) * sWorld->getRate(RATE_MONEY_QUEST);
}
uint32 Player::GetQuestXPReward(Quest const* quest)
{
bool rewarded = IsQuestRewarded(quest->GetQuestId()) && !quest->IsDFQuest();
// Not give XP in case already completed once repeatable quest
if (rewarded)
return 0;
uint32 XP = quest->XPValue(this) * sWorld->getRate(RATE_XP_QUEST);
// handle SPELL_AURA_MOD_XP_QUEST_PCT auras
Unit::AuraEffectList const& ModXPPctAuras = GetAuraEffectsByType(SPELL_AURA_MOD_XP_QUEST_PCT);
for (Unit::AuraEffectList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i)
AddPct(XP, (*i)->GetAmount());
return XP;
}
bool Player::CanSelectQuestPackageItem(QuestPackageItemEntry const* questPackageItem) const
{
ItemTemplate const* rewardProto = sObjectMgr->GetItemTemplate(questPackageItem->ItemID);
if (!rewardProto)
return false;
if ((rewardProto->HasFlag(ITEM_FLAG2_FACTION_ALLIANCE) && GetTeam() != ALLIANCE) ||
(rewardProto->HasFlag(ITEM_FLAG2_FACTION_HORDE) && GetTeam() != HORDE))
return false;
switch (questPackageItem->DisplayType)
{
case QUEST_PACKAGE_FILTER_LOOT_SPECIALIZATION:
return rewardProto->IsUsableByLootSpecialization(this, true);
case QUEST_PACKAGE_FILTER_CLASS:
return !rewardProto->ItemSpecClassMask || (rewardProto->ItemSpecClassMask & GetClassMask()) != 0;
case QUEST_PACKAGE_FILTER_EVERYONE:
return true;
default:
break;
}
return false;
}
void Player::RewardQuestPackage(uint32 questPackageId, uint32 onlyItemId /*= 0*/)
{
bool hasFilteredQuestPackageReward = false;
if (std::vector<QuestPackageItemEntry const*> const* questPackageItems = sDB2Manager.GetQuestPackageItems(questPackageId))
{
for (QuestPackageItemEntry const* questPackageItem : *questPackageItems)
{
if (onlyItemId && questPackageItem->ItemID != int32(onlyItemId))
continue;
if (CanSelectQuestPackageItem(questPackageItem))
{
hasFilteredQuestPackageReward = true;
ItemPosCountVec dest;
if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, questPackageItem->ItemID, questPackageItem->ItemQuantity) == EQUIP_ERR_OK)
{
Item* item = StoreNewItem(dest, questPackageItem->ItemID, true, GenerateItemRandomBonusListId(questPackageItem->ItemID));
SendNewItem(item, questPackageItem->ItemQuantity, true, false);
}
}
}
}
if (!hasFilteredQuestPackageReward)
{
if (std::vector<QuestPackageItemEntry const*> const* questPackageItems = sDB2Manager.GetQuestPackageItemsFallback(questPackageId))
{
for (QuestPackageItemEntry const* questPackageItem : *questPackageItems)
{
if (onlyItemId && questPackageItem->ItemID != int32(onlyItemId))
continue;
ItemPosCountVec dest;
if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, questPackageItem->ItemID, questPackageItem->ItemQuantity) == EQUIP_ERR_OK)
{
Item* item = StoreNewItem(dest, questPackageItem->ItemID, true, GenerateItemRandomBonusListId(questPackageItem->ItemID));
SendNewItem(item, questPackageItem->ItemQuantity, true, false);
}
}
}
}
}
void Player::RewardQuest(Quest const* quest, LootItemType rewardType, uint32 rewardId, Object* questGiver, bool announce)
{
//this THING should be here to protect code from quest, which cast on player far teleport as a reward
//should work fine, cause far teleport will be executed in Player::Update()
SetCanDelayTeleport(true);
uint32 quest_id = quest->GetQuestId();
QuestStatus oldStatus = GetQuestStatus(quest_id);
for (QuestObjective const& obj : quest->GetObjectives())
{
switch (obj.Type)
{
case QUEST_OBJECTIVE_ITEM:
if (!(quest->GetFlagsEx() & QUEST_FLAGS_EX_KEEP_ADDITIONAL_ITEMS))
DestroyItemCount(obj.ObjectID, obj.Amount, true);
break;
case QUEST_OBJECTIVE_CURRENCY:
ModifyCurrency(obj.ObjectID, -int32(obj.Amount), false, true);
break;
}
}
if (!(quest->GetFlagsEx() & QUEST_FLAGS_EX_KEEP_ADDITIONAL_ITEMS))
{
for (uint8 i = 0; i < QUEST_ITEM_DROP_COUNT; ++i)
{
if (quest->ItemDrop[i])
{
uint32 count = quest->ItemDropQuantity[i];
DestroyItemCount(quest->ItemDrop[i], count ? count : 9999, true);
}
}
}
RemoveTimedQuest(quest_id);
if (quest->GetRewItemsCount() > 0)
{
for (uint32 i = 0; i < quest->GetRewItemsCount(); ++i)
{
if (uint32 itemId = quest->RewardItemId[i])
{
ItemPosCountVec dest;
if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, quest->RewardItemCount[i]) == EQUIP_ERR_OK)
{
Item* item = StoreNewItem(dest, itemId, true, GenerateItemRandomBonusListId(itemId));
SendNewItem(item, quest->RewardItemCount[i], true, false);
}
else if (quest->IsDFQuest())
SendItemRetrievalMail(itemId, quest->RewardItemCount[i], ItemContext::Quest_Reward);
}
}
}
switch (rewardType)
{
case LootItemType::Item:
{
ItemTemplate const* rewardProto = sObjectMgr->GetItemTemplate(rewardId);
if (rewardProto && quest->GetRewChoiceItemsCount())
{
for (uint32 i = 0; i < QUEST_REWARD_CHOICES_COUNT; ++i)
{
if (quest->RewardChoiceItemId[i] && quest->RewardChoiceItemType[i] == LootItemType::Item && quest->RewardChoiceItemId[i] == rewardId)
{
ItemPosCountVec dest;
if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, rewardId, quest->RewardChoiceItemCount[i]) == EQUIP_ERR_OK)
{
Item* item = StoreNewItem(dest, rewardId, true, GenerateItemRandomBonusListId(rewardId));
SendNewItem(item, quest->RewardChoiceItemCount[i], true, false);
}
}
}
}
// QuestPackageItem.db2
if (rewardProto && quest->GetQuestPackageID())
RewardQuestPackage(quest->GetQuestPackageID(), rewardId);
break;
}
case LootItemType::Currency:
{
if (sCurrencyTypesStore.HasRecord(rewardId) && quest->GetRewChoiceItemsCount())
for (uint32 i = 0; i < QUEST_REWARD_CHOICES_COUNT; ++i)
if (quest->RewardChoiceItemId[i] && quest->RewardChoiceItemType[i] == LootItemType::Currency && quest->RewardChoiceItemId[i] == rewardId)
ModifyCurrency(quest->RewardChoiceItemId[i], quest->RewardChoiceItemCount[i]);
break;
}
default:
break;
}
for (uint8 i = 0; i < QUEST_REWARD_CURRENCY_COUNT; ++i)
if (quest->RewardCurrencyId[i])
ModifyCurrency(quest->RewardCurrencyId[i], quest->RewardCurrencyCount[i]);
if (uint32 skill = quest->GetRewardSkillId())
UpdateSkillPro(skill, 1000, quest->GetRewardSkillPoints());
uint16 log_slot = FindQuestSlot(quest_id);
if (log_slot < MAX_QUEST_LOG_SIZE)
SetQuestSlot(log_slot, 0);
uint32 XP = GetQuestXPReward(quest);
int32 moneyRew = 0;
if (!IsMaxLevel())
GiveXP(XP, nullptr);
else
moneyRew = int32(quest->GetRewMoneyMaxLevel() * sWorld->getRate(RATE_DROP_MONEY));
moneyRew += GetQuestMoneyReward(quest);
if (moneyRew)
{
ModifyMoney(moneyRew);
if (moneyRew > 0)
UpdateCriteria(CriteriaType::MoneyEarnedFromQuesting, uint32(moneyRew));
}
// honor reward
if (uint32 honor = quest->CalculateHonorGain(GetLevel()))
RewardHonor(nullptr, 0, honor);
// title reward
if (quest->GetRewTitle())
{
if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(quest->GetRewTitle()))
SetTitle(titleEntry);
}
// Send reward mail
if (uint32 mail_template_id = quest->GetRewMailTemplateId())
{
/// @todo Poor design of mail system
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
if (uint32 questMailSender = quest->GetRewMailSenderEntry())
MailDraft(mail_template_id).SendMailTo(trans, this, questMailSender, MAIL_CHECK_MASK_HAS_BODY, quest->GetRewMailDelaySecs());
else
MailDraft(mail_template_id).SendMailTo(trans, this, questGiver, MAIL_CHECK_MASK_HAS_BODY, quest->GetRewMailDelaySecs());
CharacterDatabase.CommitTransaction(trans);
}
if (quest->IsDaily() || quest->IsDFQuest())
{
SetDailyQuestStatus(quest_id);
if (quest->IsDaily())
{
UpdateCriteria(CriteriaType::CompleteDailyQuest, quest_id);
UpdateCriteria(CriteriaType::CompleteAnyDailyQuestPerDay, quest_id);
}
}
else if (quest->IsWeekly())
SetWeeklyQuestStatus(quest_id);
else if (quest->IsMonthly())
SetMonthlyQuestStatus(quest_id);
else if (quest->IsSeasonal())
SetSeasonalQuestStatus(quest_id);
RemoveActiveQuest(quest_id, false);
if (quest->CanIncreaseRewardedQuestCounters())
SetRewardedQuest(quest_id);
SendQuestReward(quest, questGiver ? questGiver->ToCreature() : nullptr, XP, !announce);
RewardReputation(quest);
// cast spells after mark quest complete (some spells have quest completed state requirements in spell_area data)
if (quest->GetRewSpell() > 0)
{
SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(quest->GetRewSpell(), GetMap()->GetDifficultyID());
Unit* caster = this;
if (questGiver && questGiver->isType(TYPEMASK_UNIT) && !quest->HasFlag(QUEST_FLAGS_PLAYER_CAST_ON_COMPLETE) && !spellInfo->HasTargetType(TARGET_UNIT_CASTER))
if (Unit* unit = questGiver->ToUnit())
caster = unit;
caster->CastSpell(this, spellInfo->Id, CastSpellExtraArgs(TRIGGERED_FULL_MASK).SetCastDifficulty(spellInfo->Difficulty));
}
else
{
for (QuestRewardDisplaySpell displaySpell : quest->RewardDisplaySpell)
{
if (PlayerConditionEntry const* playerCondition = sPlayerConditionStore.LookupEntry(displaySpell.PlayerConditionId))
if (!ConditionMgr::IsPlayerMeetingCondition(this, playerCondition))
continue;
SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(displaySpell.SpellId, GetMap()->GetDifficultyID());
Unit* caster = this;
if (questGiver && questGiver->isType(TYPEMASK_UNIT) && !quest->HasFlag(QUEST_FLAGS_PLAYER_CAST_ON_COMPLETE) && !spellInfo->HasTargetType(TARGET_UNIT_CASTER))
if (Unit* unit = questGiver->ToUnit())
caster = unit;
caster->CastSpell(this, spellInfo->Id, CastSpellExtraArgs(TRIGGERED_FULL_MASK).SetCastDifficulty(spellInfo->Difficulty));
}
}
if (quest->GetZoneOrSort() > 0)
UpdateCriteria(CriteriaType::CompleteQuestsInZone, quest->GetQuestId());
UpdateCriteria(CriteriaType::CompleteQuestsCount);
UpdateCriteria(CriteriaType::CompleteQuest, quest->GetQuestId());
UpdateCriteria(CriteriaType::CompleteAnyReplayQuest, 1);
// make full db save
SaveToDB(false);
if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(quest_id))
SetQuestCompletedBit(questBit, true);
if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP))
{
pvpInfo.IsHostile = pvpInfo.IsInHostileArea || HasPvPForcingQuest();
UpdatePvPState();
}
SendQuestUpdate(quest_id);
SendQuestGiverStatusMultiple();
//lets remove flag for delayed teleports
SetCanDelayTeleport(false);
sScriptMgr->OnQuestStatusChange(this, quest_id);
sScriptMgr->OnQuestStatusChange(this, quest, oldStatus, QUEST_STATUS_REWARDED);
}
void Player::SetRewardedQuest(uint32 quest_id)
{
m_RewardedQuests.insert(quest_id);
m_RewardedQuestsSave[quest_id] = QUEST_DEFAULT_SAVE_TYPE;
}
void Player::FailQuest(uint32 questId)
{
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
QuestStatus qStatus = GetQuestStatus(questId);
// we can only fail incomplete quest or...
if (qStatus != QUEST_STATUS_INCOMPLETE)
{
// completed timed quest with no requirements
if (qStatus != QUEST_STATUS_COMPLETE || !quest->GetLimitTime() || !quest->GetObjectives().empty())
return;
}
SetQuestStatus(questId, QUEST_STATUS_FAILED);
uint16 log_slot = FindQuestSlot(questId);
if (log_slot < MAX_QUEST_LOG_SIZE)
SetQuestSlotState(log_slot, QUEST_STATE_FAIL);
if (quest->GetLimitTime())
{
QuestStatusData& q_status = m_QuestStatus[questId];
RemoveTimedQuest(questId);
q_status.Timer = 0;
SendQuestTimerFailed(questId);
}
else
SendQuestFailed(questId);
// Destroy quest items on quest failure.
for (QuestObjective const& obj : quest->GetObjectives())
if (obj.Type == QUEST_OBJECTIVE_ITEM)
if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(obj.ObjectID))
if (itemTemplate->GetBonding() == BIND_QUEST)
DestroyItemCount(obj.ObjectID, obj.Amount, true, true);
// Destroy items received during the quest.
for (uint8 i = 0; i < QUEST_ITEM_DROP_COUNT; ++i)
if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(quest->ItemDrop[i]))
if (quest->ItemDropQuantity[i] && itemTemplate->GetBonding() == BIND_QUEST)
DestroyItemCount(quest->ItemDrop[i], quest->ItemDropQuantity[i], true, true);
}
}
void Player::AbandonQuest(uint32 questId)
{
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
// Destroy quest items on quest abandon.
for (QuestObjective const& obj : quest->GetObjectives())
if (obj.Type == QUEST_OBJECTIVE_ITEM)
if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(obj.ObjectID))
if (itemTemplate->GetBonding() == BIND_QUEST)
DestroyItemCount(obj.ObjectID, obj.Amount, true, true);
// Destroy items received during the quest.
for (uint8 i = 0; i < QUEST_ITEM_DROP_COUNT; ++i)
if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(quest->ItemDrop[i]))
if (quest->ItemDropQuantity[i] && itemTemplate->GetBonding() == BIND_QUEST)
DestroyItemCount(quest->ItemDrop[i], quest->ItemDropQuantity[i], true, true);
}
}
bool Player::SatisfyQuestSkill(Quest const* qInfo, bool msg) const
{
uint32 skill = qInfo->GetRequiredSkill();
// skip 0 case RequiredSkill
if (skill == 0)
return true;
// check skill value
if (GetSkillValue(skill) < qInfo->GetRequiredSkillValue())
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestSkill: Sent QUEST_ERR_NONE (QuestID: %u) because player '%s' (%s) doesn't have the required skill value.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
return true;
}
bool Player::SatisfyQuestLevel(Quest const* qInfo, bool msg) const
{
return SatisfyQuestMinLevel(qInfo, msg) && SatisfyQuestMaxLevel(qInfo, msg);
}
bool Player::SatisfyQuestMinLevel(Quest const* qInfo, bool msg) const
{
if (GetLevel() < GetQuestMinLevel(qInfo))
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_FAILED_LOW_LEVEL);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestMinLevel: Sent QUEST_ERR_FAILED_LOW_LEVEL (QuestID: %u) because player '%s' (%s) doesn't have the required (min) level.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
return true;
}
bool Player::SatisfyQuestMaxLevel(Quest const* qInfo, bool msg) const
{
if (qInfo->GetMaxLevel() > 0 && GetLevel() > qInfo->GetMaxLevel())
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE); // There doesn't seem to be a specific response for too high player level
TC_LOG_DEBUG("misc", "Player::SatisfyQuestMaxLevel: Sent QUEST_ERR_FAILED_LOW_LEVEL (QuestID: %u) because player '%s' (%s) doesn't have the required (max) level.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
return true;
}
bool Player::SatisfyQuestLog(bool msg) const
{
// exist free slot
if (FindQuestSlot(0) < MAX_QUEST_LOG_SIZE)
return true;
if (msg)
{
WorldPackets::Quest::QuestLogFull data;
SendDirectMessage(data.Write());
}
return false;
}
bool Player::SatisfyQuestDependentQuests(Quest const* qInfo, bool msg) const
{
return SatisfyQuestPreviousQuest(qInfo, msg) && SatisfyQuestDependentPreviousQuests(qInfo, msg) &&
SatisfyQuestBreadcrumbQuest(qInfo, msg) && SatisfyQuestDependentBreadcrumbQuests(qInfo, msg);
}
bool Player::SatisfyQuestPreviousQuest(Quest const* qInfo, bool msg) const
{
// No previous quest (might be first quest in a series)
if (!qInfo->GetPrevQuestId())
return true;
uint32 prevId = std::abs(qInfo->GetPrevQuestId());
// If positive previous quest rewarded, return true
if (qInfo->GetPrevQuestId() > 0 && m_RewardedQuests.count(prevId) > 0)
return true;
// If negative previous quest active, return true
if (qInfo->GetPrevQuestId() < 0 && GetQuestStatus(prevId) == QUEST_STATUS_INCOMPLETE)
return true;
// Has positive prev. quest in non-rewarded state
// and negative prev. quest in non-active state
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestPreviousQuest: Sent QUEST_ERR_NONE (QuestID: %u) because player '%s' (%s) doesn't have required quest %u.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str(), prevId);
}
return false;
}
bool Player::SatisfyQuestDependentPreviousQuests(Quest const* qInfo, bool msg) const
{
// No previous quest (might be first quest in a series)
if (qInfo->DependentPreviousQuests.empty())
return true;
for (uint32 prevId : qInfo->DependentPreviousQuests)
{
// checked in startup
Quest const* questInfo = sObjectMgr->GetQuestTemplate(prevId);
ASSERT(questInfo);
// If any of the previous quests completed, return true
if (IsQuestRewarded(prevId))
{
// skip one-from-all exclusive group
if (questInfo->GetExclusiveGroup() >= 0)
return true;
// each-from-all exclusive group (< 0)
// can be start if only all quests in prev quest exclusive group completed and rewarded
auto bounds = sObjectMgr->GetExclusiveQuestGroupBounds(questInfo->GetExclusiveGroup());
for (auto itr = bounds.first; itr != bounds.second; ++itr)
{
// skip checked quest id, only state of other quests in group is interesting
uint32 exclusiveQuestId = itr->second;
if (exclusiveQuestId == prevId)
continue;
// alternative quest from group also must be completed and rewarded (reported)
if (!IsQuestRewarded(exclusiveQuestId))
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestDependentPreviousQuests: Sent QUEST_ERR_NONE (QuestID: %u) because player '%s' (%s) doesn't have the required quest (1).",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
}
return true;
}
}
// Has only prev. quests in non-rewarded state
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestDependentPreviousQuests: Sent QUEST_ERR_NONE (QuestID: %u) because player '%s' (%s) doesn't have required quest (2).",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
bool Player::SatisfyQuestBreadcrumbQuest(Quest const* qInfo, bool msg) const
{
uint32 breadcrumbTargetQuestId = std::abs(qInfo->GetBreadcrumbForQuestId());
//If this is not a breadcrumb quest.
if (!breadcrumbTargetQuestId)
return true;
// If the target quest is not available
if (!CanTakeQuest(sObjectMgr->GetQuestTemplate(breadcrumbTargetQuestId), false))
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestBreadcrumbQuest: Sent INVALIDREASON_DONT_HAVE_REQ (QuestID: %u) because target quest (QuestID: %u) is not available to player '%s' (%s).",
qInfo->GetQuestId(), breadcrumbTargetQuestId, GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
return true;
}
bool Player::SatisfyQuestDependentBreadcrumbQuests(Quest const* qInfo, bool msg) const
{
for (uint32 breadcrumbQuestId : qInfo->DependentBreadcrumbQuests)
{
QuestStatus status = GetQuestStatus(breadcrumbQuestId);
// If any of the breadcrumb quests are in the quest log, return false.
if (status == QUEST_STATUS_INCOMPLETE || status == QUEST_STATUS_COMPLETE || status == QUEST_STATUS_FAILED)
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestDependentBreadcrumbQuests: Sent INVALIDREASON_DONT_HAVE_REQ (QuestID: %u) because player '%s' (%s) has a breadcrumb quest towards this quest in the quest log.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
}
return true;
}
bool Player::SatisfyQuestClass(Quest const* qInfo, bool msg) const
{
uint32 reqClass = qInfo->GetAllowableClasses();
if (reqClass == 0)
return true;
if ((reqClass & GetClassMask()) == 0)
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestClass: Sent QUEST_ERR_NONE (QuestID: %u) because player '%s' (%s) doesn't have required class.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
return true;
}
bool Player::SatisfyQuestRace(Quest const* qInfo, bool msg) const
{
if (!qInfo->GetAllowableRaces().HasRace(GetRace()))
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_FAILED_WRONG_RACE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestRace: Sent QUEST_ERR_FAILED_WRONG_RACE (QuestID: %u) because player '%s' (%s) doesn't have required race.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
return true;
}
bool Player::SatisfyQuestReputation(Quest const* qInfo, bool msg) const
{
uint32 fIdMin = qInfo->GetRequiredMinRepFaction(); //Min required rep
if (fIdMin && GetReputationMgr().GetReputation(fIdMin) < qInfo->GetRequiredMinRepValue())
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestReputation: Sent QUEST_ERR_NONE (QuestID: %u) because player '%s' (%s) doesn't required reputation (min).",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
uint32 fIdMax = qInfo->GetRequiredMaxRepFaction(); //Max required rep
if (fIdMax && GetReputationMgr().GetReputation(fIdMax) >= qInfo->GetRequiredMaxRepValue())
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE);
TC_LOG_DEBUG("misc", "SatisfyQuestReputation: Sent QUEST_ERR_NONE (QuestID: %u) because player '%s' (%s) doesn't required reputation (max).",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
return true;
}
bool Player::SatisfyQuestStatus(Quest const* qInfo, bool msg) const
{
if (GetQuestStatus(qInfo->GetQuestId()) == QUEST_STATUS_REWARDED)
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_ALREADY_DONE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestStatus: Sent QUEST_STATUS_REWARDED (QuestID: %u) because player '%s' (%s) quest status is already REWARDED.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
if (GetQuestStatus(qInfo->GetQuestId()) != QUEST_STATUS_NONE)
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_ALREADY_ON1);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestStatus: Sent QUEST_ERR_ALREADY_ON1 (QuestID: %u) because player '%s' (%s) quest status is not NONE.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
return true;
}
bool Player::SatisfyQuestConditions(Quest const* qInfo, bool msg) const
{
if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_QUEST_AVAILABLE, qInfo->GetQuestId(), const_cast<Player*>(this)))
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestConditions: Sent QUEST_ERR_NONE (QuestID: %u) because player '%s' (%s) doesn't meet conditions.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
TC_LOG_DEBUG("condition", "Player::SatisfyQuestConditions: conditions not met for quest %u", qInfo->GetQuestId());
return false;
}
return true;
}
bool Player::SatisfyQuestTimed(Quest const* qInfo, bool msg) const
{
if (!m_timedquests.empty() && qInfo->GetLimitTime())
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_ONLY_ONE_TIMED);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestTimed: Sent QUEST_ERR_ONLY_ONE_TIMED (QuestID: %u) because player '%s' (%s) is already on a timed quest.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
return true;
}
bool Player::SatisfyQuestExclusiveGroup(Quest const* qInfo, bool msg) const
{
// non positive exclusive group, if > 0 then can be start if any other quest in exclusive group already started/completed
if (qInfo->GetExclusiveGroup() <= 0)
return true;
auto bounds = sObjectMgr->GetExclusiveQuestGroupBounds(qInfo->GetExclusiveGroup());
for (auto itr = bounds.first; itr != bounds.second; ++itr)
{
uint32 exclude_Id = itr->second;
// skip checked quest id, only state of other quests in group is interesting
if (exclude_Id == qInfo->GetQuestId())
continue;
// not allow have daily quest if daily quest from exclusive group already recently completed
Quest const* Nquest = sObjectMgr->GetQuestTemplate(exclude_Id);
ASSERT(Nquest);
if (!SatisfyQuestDay(Nquest, false) || !SatisfyQuestWeek(Nquest, false) || !SatisfyQuestSeasonal(Nquest, false))
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestExclusiveGroup: Sent QUEST_ERR_NONE (QuestID: %u) because player '%s' (%s) already did daily quests in exclusive group.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
// alternative quest already started or completed - but don't check rewarded states if both are repeatable
if (GetQuestStatus(exclude_Id) != QUEST_STATUS_NONE || (!(qInfo->IsRepeatable() && Nquest->IsRepeatable()) && GetQuestRewardStatus(exclude_Id)))
{
if (msg)
{
SendCanTakeQuestResponse(QUEST_ERR_NONE);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestExclusiveGroup: Sent QUEST_ERR_NONE (QuestID: %u) because player '%s' (%s) already did quest in exclusive group.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
}
return false;
}
}
return true;
}
bool Player::SatisfyQuestDay(Quest const* qInfo, bool /*msg*/) const
{
if (!qInfo->IsDaily() && !qInfo->IsDFQuest())
return true;
if (qInfo->IsDFQuest())
{
if (m_DFQuests.find(qInfo->GetQuestId()) != m_DFQuests.end())
return false;
return true;
}
return m_activePlayerData->DailyQuestsCompleted.FindIndex(qInfo->GetQuestId()) == -1;
}
bool Player::SatisfyQuestWeek(Quest const* qInfo, bool /*msg*/) const
{
if (!qInfo->IsWeekly() || m_weeklyquests.empty())
return true;
// if not found in cooldown list
return m_weeklyquests.find(qInfo->GetQuestId()) == m_weeklyquests.end();
}
bool Player::SatisfyQuestSeasonal(Quest const* qInfo, bool /*msg*/) const
{
if (!qInfo->IsSeasonal() || m_seasonalquests.empty())
return true;
auto itr = m_seasonalquests.find(qInfo->GetEventIdForQuest());
if (itr == m_seasonalquests.end() || itr->second.empty())
return true;
// if not found in cooldown list
return itr->second.find(qInfo->GetQuestId()) == itr->second.end();
}
bool Player::SatisfyQuestExpansion(Quest const* qInfo, bool msg) const
{
if (GetSession()->GetExpansion() < qInfo->GetExpansion())
{
if (msg)
SendCanTakeQuestResponse(QUEST_ERR_FAILED_EXPANSION);
TC_LOG_DEBUG("misc", "Player::SatisfyQuestExpansion: Sent QUEST_ERR_FAILED_EXPANSION (QuestID: %u) because player '%s' (%s) does not have required expansion.",
qInfo->GetQuestId(), GetName().c_str(), GetGUID().ToString().c_str());
return false;
}
return true;
}
bool Player::SatisfyQuestMonth(Quest const* qInfo, bool /*msg*/) const
{
if (!qInfo->IsMonthly() || m_monthlyquests.empty())
return true;
// if not found in cooldown list
return m_monthlyquests.find(qInfo->GetQuestId()) == m_monthlyquests.end();
}
bool Player::GiveQuestSourceItem(Quest const* quest)
{
uint32 srcitem = quest->GetSrcItemId();
if (srcitem > 0)
{
// Don't give source item if it is the same item used to start the quest
ItemTemplate const* itemTemplate = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(srcitem));
if (quest->GetQuestId() == itemTemplate->GetStartQuest())
return true;
uint32 count = quest->GetSrcItemCount();
if (count <= 0)
count = 1;
ItemPosCountVec dest;
InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, srcitem, count);
if (msg == EQUIP_ERR_OK)
{
Item* item = StoreNewItem(dest, srcitem, true);
SendNewItem(item, count, true, false);
return true;
}
// player already have max amount required item, just report success
if (msg == EQUIP_ERR_ITEM_MAX_COUNT)
return true;
SendEquipError(msg, nullptr, nullptr, srcitem);
return false;
}
return true;
}
bool Player::TakeQuestSourceItem(uint32 questId, bool msg)
{
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (quest)
{
uint32 srcItemId = quest->GetSrcItemId();
ItemTemplate const* item = sObjectMgr->GetItemTemplate(srcItemId);
if (srcItemId > 0)
{
uint32 count = quest->GetSrcItemCount();
if (count <= 0)
count = 1;
// There are two cases where the source item is not destroyed:
// - Item cannot be unequipped (example: non-empty bags)
// - The source item is the item that started the quest, so the player is supposed to keep it (otherwise it was already destroyed in AddQuestAndCheckCompletion())
InventoryResult res = CanUnequipItems(srcItemId, count);
if (res != EQUIP_ERR_OK)
{
if (msg)
SendEquipError(res, nullptr, nullptr, srcItemId);
return false;
}
ASSERT(item);
if (item->GetStartQuest() != questId)
DestroyItemCount(srcItemId, count, true, true);
}
}
return true;
}
bool Player::GetQuestRewardStatus(uint32 quest_id) const
{
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
if (qInfo)
{
if (qInfo->IsSeasonal() && !qInfo->IsRepeatable())
return !SatisfyQuestSeasonal(qInfo, false);
// for repeatable quests: rewarded field is set after first reward only to prevent getting XP more than once
if (!qInfo->IsRepeatable())
return IsQuestRewarded(quest_id);
return false;
}
return false;
}
QuestStatus Player::GetQuestStatus(uint32 quest_id) const
{
if (quest_id)
{
QuestStatusMap::const_iterator itr = m_QuestStatus.find(quest_id);
if (itr != m_QuestStatus.end())
return itr->second.Status;
if (GetQuestRewardStatus(quest_id))
return QUEST_STATUS_REWARDED;
}
return QUEST_STATUS_NONE;
}
bool Player::CanShareQuest(uint32 quest_id) const
{
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
if (qInfo && qInfo->HasFlag(QUEST_FLAGS_SHARABLE))
{
QuestStatusMap::const_iterator itr = m_QuestStatus.find(quest_id);
return (itr != m_QuestStatus.end());
}
return false;
}
void Player::SetQuestStatus(uint32 questId, QuestStatus status, bool update /*= true*/)
{
if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
{
QuestStatus oldStatus = m_QuestStatus[questId].Status;
m_QuestStatus[questId].Status = status;
if (!quest->IsAutoComplete())
m_QuestStatusSave[questId] = QUEST_DEFAULT_SAVE_TYPE;
sScriptMgr->OnQuestStatusChange(this, questId);
sScriptMgr->OnQuestStatusChange(this, quest, oldStatus, status);
}
if (update)
SendQuestUpdate(questId);
}
void Player::RemoveActiveQuest(uint32 questId, bool update /*= true*/)
{
QuestStatusMap::iterator itr = m_QuestStatus.find(questId);
if (itr != m_QuestStatus.end())
{
for (auto objectiveItr = m_questObjectiveStatus.begin(); objectiveItr != m_questObjectiveStatus.end(); )
{
if (objectiveItr->second.QuestStatusItr == itr)
objectiveItr = m_questObjectiveStatus.erase(objectiveItr);
else
++objectiveItr;
}
m_QuestStatus.erase(itr);
m_QuestStatusSave[questId] = QUEST_DELETE_SAVE_TYPE;
}
if (update)
SendQuestUpdate(questId);
}
void Player::RemoveRewardedQuest(uint32 questId, bool update /*= true*/)
{
RewardedQuestSet::iterator rewItr = m_RewardedQuests.find(questId);
if (rewItr != m_RewardedQuests.end())
{
m_RewardedQuests.erase(rewItr);
m_RewardedQuestsSave[questId] = QUEST_FORCE_DELETE_SAVE_TYPE;
}
if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(questId))
SetQuestCompletedBit(questBit, false);
// Remove seasonal quest also
Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId);
ASSERT(qInfo);
if (qInfo->IsSeasonal())
{
uint16 eventId = qInfo->GetEventIdForQuest();
if (m_seasonalquests.find(eventId) != m_seasonalquests.end())
{
m_seasonalquests[eventId].erase(questId);
m_SeasonalQuestChanged = true;
}
}
if (update)
SendQuestUpdate(questId);
}
void Player::SendQuestUpdate(uint32 questId)
{
SpellAreaForQuestMapBounds saBounds = sSpellMgr->GetSpellAreaForQuestMapBounds(questId);
if (saBounds.first != saBounds.second)
{
std::set<uint32> aurasToRemove, aurasToCast;
uint32 zone = 0, area = 0;
GetZoneAndAreaId(zone, area);
for (SpellAreaForQuestMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
{
if (itr->second->flags & SPELL_AREA_FLAG_AUTOREMOVE && !itr->second->IsFitToRequirements(this, zone, area))
aurasToRemove.insert(itr->second->spellId);
else if (itr->second->flags & SPELL_AREA_FLAG_AUTOCAST && !(itr->second->flags & SPELL_AREA_FLAG_IGNORE_AUTOCAST_ON_QUEST_STATUS_CHANGE))
aurasToCast.insert(itr->second->spellId);
}
// Auras matching the requirements will be inside the aurasToCast container.
// Auras not matching the requirements may prevent using auras matching the requirements.
// aurasToCast will erase conflicting auras in aurasToRemove container to handle spells used by multiple quests.
for (auto itr = aurasToRemove.begin(); itr != aurasToRemove.end();)
{
bool auraRemoved = false;
for (const auto i : aurasToCast)
{
if (*itr == i)
{
itr = aurasToRemove.erase(itr);
auraRemoved = true;
break;
}
}
if (!auraRemoved)
++itr;
}
for (auto spellId : aurasToCast)
if (!HasAura(spellId))
CastSpell(this, spellId, true);
for (auto spellId : aurasToRemove)
RemoveAurasDueToSpell(spellId);
}
UpdateVisibleGameobjectsOrSpellClicks();
PhasingHandler::OnConditionChange(this);
}
QuestGiverStatus Player::GetQuestDialogStatus(Object* questgiver)
{
QuestRelationResult qr, qir;
switch (questgiver->GetTypeId())
{
case TYPEID_GAMEOBJECT:
{
if (GameObjectAI* ai = questgiver->ToGameObject()->AI())
if (Optional<QuestGiverStatus> questStatus = ai->GetDialogStatus(this))
return *questStatus;
qr = sObjectMgr->GetGOQuestRelations(questgiver->GetEntry());
qir = sObjectMgr->GetGOQuestInvolvedRelations(questgiver->GetEntry());
break;
}
case TYPEID_UNIT:
{
if (CreatureAI* ai = questgiver->ToCreature()->AI())
if (Optional<QuestGiverStatus> questStatus = ai->GetDialogStatus(this))
return *questStatus;
qr = sObjectMgr->GetCreatureQuestRelations(questgiver->GetEntry());
qir = sObjectMgr->GetCreatureQuestInvolvedRelations(questgiver->GetEntry());
break;
}
default:
// it's impossible, but check
TC_LOG_ERROR("entities.player.quest", "Player::GetQuestDialogStatus: Called with unexpected type (Entry: %u, Type: %u) by player '%s' (%s)",
questgiver->GetEntry(), questgiver->GetTypeId(), GetName().c_str(), GetGUID().ToString().c_str());
return QuestGiverStatus::None;
}
QuestGiverStatus result = QuestGiverStatus::None;
for (uint32 questId : qir)
{
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest)
continue;
switch (GetQuestStatus(questId))
{
case QUEST_STATUS_COMPLETE:
if (quest->GetQuestTag() == QuestTagType::CovenantCalling)
result |= quest->HasFlag(QUEST_FLAGS_HIDE_REWARD_POI) ? QuestGiverStatus::CovenantCallingRewardCompleteNoPOI : QuestGiverStatus::CovenantCallingRewardCompletePOI;
else if (quest->HasFlagEx(QUEST_FLAGS_EX_LEGENDARY_QUEST))
result |= quest->HasFlag(QUEST_FLAGS_HIDE_REWARD_POI) ? QuestGiverStatus::LegendaryRewardCompleteNoPOI : QuestGiverStatus::LegendaryRewardCompletePOI;
else
result |= quest->HasFlag(QUEST_FLAGS_HIDE_REWARD_POI) ? QuestGiverStatus::RewardCompleteNoPOI : QuestGiverStatus::RewardCompletePOI;
break;
case QUEST_STATUS_INCOMPLETE:
if (quest->GetQuestTag() == QuestTagType::CovenantCalling)
result |= QuestGiverStatus::CovenantCallingReward;
else
result |= QuestGiverStatus::Reward;
break;
default:
break;
}
if (quest->IsAutoComplete() && CanTakeQuest(quest, false) && quest->IsRepeatable() && !quest->IsDailyOrWeekly() && !quest->IsMonthly())
{
if (GetLevel() <= (GetQuestLevel(quest) + sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF)))
result |= QuestGiverStatus::RepeatableTurnin;
else
result |= QuestGiverStatus::TrivialRepeatableTurnin;
}
}
for (uint32 questId : qr)
{
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest)
continue;
if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_QUEST_AVAILABLE, quest->GetQuestId(), this))
continue;
if (GetQuestStatus(questId) == QUEST_STATUS_NONE)
{
if (CanSeeStartQuest(quest))
{
if (SatisfyQuestLevel(quest, false))
{
if (GetLevel() <= (GetQuestLevel(quest) + sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF)))
{
if (quest->GetQuestTag() == QuestTagType::CovenantCalling)
result |= QuestGiverStatus::CovenantCallingQuest;
else if (quest->HasFlagEx(QUEST_FLAGS_EX_LEGENDARY_QUEST))
result |= QuestGiverStatus::LegendaryQuest;
else if (quest->IsDaily())
result |= QuestGiverStatus::DailyQuest;
else
result |= QuestGiverStatus::Quest;
}
else if (quest->IsDaily())
result |= QuestGiverStatus::TrivialDailyQuest;
else
result |= QuestGiverStatus::Trivial;
}
else
result |= QuestGiverStatus::Future;
}
}
}
return result;
}
// not used in Trinity, but used in scripting code
uint16 Player::GetReqKillOrCastCurrentCount(uint32 quest_id, int32 entry) const
{
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
if (!qInfo)
return 0;
uint16 slot = FindQuestSlot(quest_id);
if (slot >= MAX_QUEST_LOG_SIZE)
return 0;
for (QuestObjective const& obj : qInfo->GetObjectives())
if (obj.ObjectID == entry)
return GetQuestSlotObjectiveData(slot, obj);
return 0;
}
void Player::AdjustQuestObjectiveProgress(Quest const* quest)
{
// adjust progress of quest objectives that rely on external counters, like items
if (quest->HasQuestObjectiveType(QUEST_OBJECTIVE_ITEM))
{
for (QuestObjective const& obj : quest->GetObjectives())
{
if (obj.Type == QUEST_OBJECTIVE_ITEM)
{
uint32 reqItemCount = obj.Amount;
uint32 curItemCount = GetItemCount(obj.ObjectID, true);
SetQuestObjectiveData(obj, std::min(curItemCount, reqItemCount));
}
else if (obj.Type == QUEST_OBJECTIVE_HAVE_CURRENCY)
{
uint32 reqCurrencyCount = obj.Amount;
uint32 curCurrencyCount = GetCurrency(obj.ObjectID);
SetQuestObjectiveData(obj, std::min(reqCurrencyCount, curCurrencyCount));
}
}
}
}
uint16 Player::FindQuestSlot(uint32 quest_id) const
{
for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
if (GetQuestSlotQuestId(i) == quest_id)
return i;
return MAX_QUEST_LOG_SIZE;
}
uint32 Player::GetQuestSlotQuestId(uint16 slot) const
{
return m_playerData->QuestLog[slot].QuestID;
}
uint32 Player::GetQuestSlotState(uint16 slot) const
{
return m_playerData->QuestLog[slot].StateFlags;
}
uint16 Player::GetQuestSlotCounter(uint16 slot, uint8 counter) const
{
if (counter < MAX_QUEST_COUNTS)
return m_playerData->QuestLog[slot].ObjectiveProgress[counter];
return 0;
}
uint32 Player::GetQuestSlotEndTime(uint16 slot) const
{
return m_playerData->QuestLog[slot].EndTime;
}
uint32 Player::GetQuestSlotAcceptTime(uint16 slot) const
{
return m_playerData->QuestLog[slot].AcceptTime;
}
bool Player::GetQuestSlotObjectiveFlag(uint16 slot, int8 objectiveIndex) const
{
if (objectiveIndex < MAX_QUEST_COUNTS)
return (*m_playerData->QuestLog[slot].ObjectiveFlags) & (1 << objectiveIndex);
return false;
}
int32 Player::GetQuestSlotObjectiveData(uint16 slot, QuestObjective const& objective) const
{
if (objective.StorageIndex < 0)
{
TC_LOG_ERROR("entities.player.quest", "Player::GetQuestObjectiveData: Called for quest %u with invalid StorageIndex %d (objective data is not tracked)",
objective.QuestID, int32(objective.StorageIndex));
return 0;
}
if (objective.StorageIndex >= MAX_QUEST_COUNTS)
{
TC_LOG_ERROR("entities.player.quest", "Player::GetQuestObjectiveData: Player '%s' (%s) quest %u out of range StorageIndex %u",
GetName().c_str(), GetGUID().ToString().c_str(), objective.QuestID, uint32(objective.StorageIndex));
return 0;
}
if (!objective.IsStoringFlag())
return GetQuestSlotCounter(slot, objective.StorageIndex);
return GetQuestSlotObjectiveFlag(slot, objective.StorageIndex) ? 1 : 0;
}
void Player::SetQuestSlot(uint16 slot, uint32 quest_id)
{
auto questLogField = m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::QuestLog, slot);
SetUpdateFieldValue(questLogField.ModifyValue(&UF::QuestLog::QuestID), quest_id);
SetUpdateFieldValue(questLogField.ModifyValue(&UF::QuestLog::StateFlags), 0);
SetUpdateFieldValue(questLogField.ModifyValue(&UF::QuestLog::EndTime), 0);
SetUpdateFieldValue(questLogField.ModifyValue(&UF::QuestLog::AcceptTime), 0);
SetUpdateFieldValue(questLogField.ModifyValue(&UF::QuestLog::ObjectiveFlags), 0);
for (uint32 i = 0; i < MAX_QUEST_COUNTS; ++i)
SetUpdateFieldValue(questLogField.ModifyValue(&UF::QuestLog::ObjectiveProgress, i), 0);
}
void Player::SetQuestSlotCounter(uint16 slot, uint8 counter, uint16 count)
{
if (counter >= MAX_QUEST_COUNTS)
return;
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData)
.ModifyValue(&UF::PlayerData::QuestLog, slot)
.ModifyValue(&UF::QuestLog::ObjectiveProgress, counter), count);
}
void Player::SetQuestSlotState(uint16 slot, uint32 state)
{
SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_playerData)
.ModifyValue(&UF::PlayerData::QuestLog, slot)
.ModifyValue(&UF::QuestLog::StateFlags), state);
}
void Player::RemoveQuestSlotState(uint16 slot, uint32 state)
{
RemoveUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_playerData)
.ModifyValue(&UF::PlayerData::QuestLog, slot)
.ModifyValue(&UF::QuestLog::StateFlags), state);
}
void Player::SetQuestSlotEndTime(uint16 slot, time_t endTime)
{
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData)
.ModifyValue(&UF::PlayerData::QuestLog, slot)
.ModifyValue(&UF::QuestLog::EndTime), uint32(endTime));
}
void Player::SetQuestSlotAcceptTime(uint16 slot, time_t acceptTime)
{
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData)
.ModifyValue(&UF::PlayerData::QuestLog, slot)
.ModifyValue(&UF::QuestLog::AcceptTime), uint32(acceptTime));
}
void Player::SetQuestSlotObjectiveFlag(uint16 slot, int8 objectiveIndex)
{
SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_playerData)
.ModifyValue(&UF::PlayerData::QuestLog, slot)
.ModifyValue(&UF::QuestLog::ObjectiveFlags), 1 << objectiveIndex);
}
void Player::RemoveQuestSlotObjectiveFlag(uint16 slot, int8 objectiveIndex)
{
RemoveUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_playerData)
.ModifyValue(&UF::PlayerData::QuestLog, slot)
.ModifyValue(&UF::QuestLog::ObjectiveFlags), 1 << objectiveIndex);
}
void Player::SetQuestCompletedBit(uint32 questBit, bool completed)
{
if (!questBit)
return;
uint32 fieldOffset = (questBit - 1) >> 6;
if (fieldOffset >= QUESTS_COMPLETED_BITS_SIZE)
return;
uint64 flag = UI64LIT(1) << ((questBit - 1) & 63);
if (completed)
SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::QuestCompleted, fieldOffset), flag);
else
RemoveUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::QuestCompleted, fieldOffset), flag);
}
void Player::AreaExploredOrEventHappens(uint32 questId)
{
if (questId)
{
if (QuestStatusData* status = Trinity::Containers::MapGetValuePtr(m_QuestStatus, questId))
{
// Dont complete failed quest
if (!status->Explored && status->Status != QUEST_STATUS_FAILED)
{
status->Explored = true;
m_QuestStatusSave[questId] = QUEST_DEFAULT_SAVE_TYPE;
SendQuestComplete(questId);
}
}
if (CanCompleteQuest(questId))
CompleteQuest(questId);
}
}
//not used in Trinityd, function for external script library
void Player::GroupEventHappens(uint32 questId, WorldObject const* pEventObject)
{
if (Group* group = GetGroup())
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* player = itr->GetSource();
// for any leave or dead (with not released body) group member at appropriate distance
if (player && player->IsAtGroupRewardDistance(pEventObject) && !player->GetCorpse())
player->AreaExploredOrEventHappens(questId);
}
}
else
AreaExploredOrEventHappens(questId);
}
void Player::ItemAddedQuestCheck(uint32 entry, uint32 count)
{
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_ITEM, entry, count);
}
void Player::ItemRemovedQuestCheck(uint32 entry, uint32 count)
{
for (QuestObjectiveStatusMap::value_type const& objectiveItr : Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { QUEST_OBJECTIVE_ITEM, entry }))
{
uint32 questId = objectiveItr.second.QuestStatusItr->first;
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
uint16 logSlot = objectiveItr.second.QuestStatusItr->second.Slot;
QuestObjective const& objective = *objectiveItr.second.Objective;
if (!IsQuestObjectiveCompletable(logSlot, quest, objective))
continue;
int32 curItemCount = GetQuestSlotObjectiveData(logSlot, objective);
if (curItemCount >= objective.Amount) // we may have more than what the status shows
curItemCount = GetItemCount(entry, false);
int32 newItemCount = (int32(count) > curItemCount) ? 0 : curItemCount - count;
if (newItemCount < objective.Amount)
{
SetQuestObjectiveData(objective, newItemCount);
IncompleteQuest(questId);
}
}
UpdateVisibleGameobjectsOrSpellClicks();
}
void Player::KilledMonster(CreatureTemplate const* cInfo, ObjectGuid guid)
{
ASSERT(cInfo);
if (cInfo->Entry)
KilledMonsterCredit(cInfo->Entry, guid);
for (uint8 i = 0; i < MAX_KILL_CREDIT; ++i)
if (cInfo->KillCredit[i])
KilledMonsterCredit(cInfo->KillCredit[i], ObjectGuid::Empty);
}
void Player::KilledMonsterCredit(uint32 entry, ObjectGuid guid /*= ObjectGuid::Empty*/)
{
uint16 addKillCount = 1;
uint32 real_entry = entry;
Creature* killed = nullptr;
if (!guid.IsEmpty())
{
killed = GetMap()->GetCreature(guid);
if (killed && killed->GetEntry())
real_entry = killed->GetEntry();
}
StartCriteriaTimer(CriteriaStartEvent::KillNPC, real_entry); // MUST BE CALLED FIRST
UpdateCriteria(CriteriaType::KillCreature, real_entry, addKillCount, 0, killed);
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_MONSTER, entry, 1, guid);
}
void Player::KilledPlayerCredit(ObjectGuid victimGuid)
{
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_PLAYERKILLS, 0, 1, victimGuid);
}
void Player::KillCreditGO(uint32 entry, ObjectGuid guid)
{
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_GAMEOBJECT, entry, 1, guid);
}
void Player::TalkedToCreature(uint32 entry, ObjectGuid guid)
{
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_TALKTO, entry, 1, guid);
}
void Player::KillCreditCriteriaTreeObjective(QuestObjective const& questObjective)
{
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_CRITERIA_TREE, questObjective.ObjectID, 1);
}
void Player::MoneyChanged(uint64 value)
{
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_MONEY, 0, int64(value) - int64(GetMoney()));
}
void Player::ReputationChanged(FactionEntry const* factionEntry, int32 change)
{
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_MIN_REPUTATION, factionEntry->ID, change);
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_MAX_REPUTATION, factionEntry->ID, change);
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_INCREASE_REPUTATION, factionEntry->ID, change);
}
void Player::CurrencyChanged(uint32 currencyId, int32 change)
{
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_CURRENCY, currencyId, change);
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_HAVE_CURRENCY, currencyId, change);
UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_OBTAIN_CURRENCY, currencyId, change);
}
void Player::UpdateQuestObjectiveProgress(QuestObjectiveType objectiveType, int32 objectId, int64 addCount, ObjectGuid victimGuid)
{
bool anyObjectiveChangedCompletionState = false;
for (QuestObjectiveStatusMap::value_type const& objectiveItr : Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { objectiveType, objectId }))
{
uint32 questId = objectiveItr.second.QuestStatusItr->first;
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!QuestObjective::CanAlwaysBeProgressedInRaid(objectiveType))
if (GetGroup() && GetGroup()->isRaidGroup() && quest->IsAllowedInRaid(GetMap()->GetDifficultyID()))
continue;
uint16 logSlot = objectiveItr.second.QuestStatusItr->second.Slot;
QuestObjective const& objective = *objectiveItr.second.Objective;
if (!IsQuestObjectiveCompletable(logSlot, quest, objective))
continue;
bool objectiveWasComplete = IsQuestObjectiveComplete(logSlot, quest, objective);
if (!objectiveWasComplete || addCount < 0)
{
bool objectiveIsNowComplete = false;
if (objective.IsStoringValue())
{
if (objectiveType == QUEST_OBJECTIVE_PLAYERKILLS && objective.Flags & QUEST_OBJECTIVE_FLAG_KILL_PLAYERS_SAME_FACTION)
if (Player const* victim = ObjectAccessor::GetPlayer(GetMap(), victimGuid))
if (victim->GetTeam() != GetTeam())
continue;
int32 currentProgress = GetQuestSlotObjectiveData(logSlot, objective);
if (addCount > 0 ? (currentProgress < objective.Amount) : (currentProgress > 0))
{
int32 newProgress = std::clamp<int32>(currentProgress + addCount, 0, objective.Amount);
SetQuestObjectiveData(objective, newProgress);
if (addCount > 0 && !(objective.Flags & QUEST_OBJECTIVE_FLAG_HIDE_CREDIT_MSG))
{
if (objectiveType != QUEST_OBJECTIVE_PLAYERKILLS)
SendQuestUpdateAddCredit(quest, victimGuid, objective, newProgress);
else
SendQuestUpdateAddPlayer(quest, newProgress);
}
objectiveIsNowComplete = IsQuestObjectiveComplete(logSlot, quest, objective);
}
}
else if (objective.IsStoringFlag())
{
SetQuestObjectiveData(objective, addCount > 0);
if (addCount > 0 && !(objective.Flags & QUEST_OBJECTIVE_FLAG_HIDE_CREDIT_MSG))
SendQuestUpdateAddCreditSimple(objective);
objectiveIsNowComplete = IsQuestObjectiveComplete(logSlot, quest, objective);
}
else
{
switch (objectiveType)
{
case QUEST_OBJECTIVE_CURRENCY:
objectiveIsNowComplete = GetCurrency(objectId) + addCount >= objective.Amount;
break;
case QUEST_OBJECTIVE_LEARNSPELL:
objectiveIsNowComplete = addCount != 0;
break;
case QUEST_OBJECTIVE_MIN_REPUTATION:
objectiveIsNowComplete = GetReputationMgr().GetReputation(objectId) + addCount >= objective.Amount;
break;
case QUEST_OBJECTIVE_MAX_REPUTATION:
objectiveIsNowComplete = GetReputationMgr().GetReputation(objectId) + addCount <= objective.Amount;
break;
case QUEST_OBJECTIVE_MONEY:
objectiveIsNowComplete = int64(GetMoney()) + addCount >= objective.Amount;
break;
case QUEST_OBJECTIVE_PROGRESS_BAR:
objectiveIsNowComplete = IsQuestObjectiveProgressBarComplete(logSlot, quest);
break;
default:
ABORT_MSG("Unhandled quest objective type %u", uint32(objectiveType));
break;
}
}
if (objective.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR)
{
if (IsQuestObjectiveProgressBarComplete(logSlot, quest))
{
auto progressBarObjectiveItr = std::find_if(quest->GetObjectives().begin(), quest->GetObjectives().end(), [](QuestObjective const& otherObjective)
{
return otherObjective.Type == QUEST_OBJECTIVE_PROGRESS_BAR && !(otherObjective.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR);
});
if (progressBarObjectiveItr != quest->GetObjectives().end())
SendQuestUpdateAddCreditSimple(*progressBarObjectiveItr);
objectiveIsNowComplete = true;
}
}
if (objectiveWasComplete != objectiveIsNowComplete)
anyObjectiveChangedCompletionState = true;
if (objectiveIsNowComplete && CanCompleteQuest(questId, objective.ID))
CompleteQuest(questId);
else if (objectiveItr.second.QuestStatusItr->second.Status == QUEST_STATUS_COMPLETE)
IncompleteQuest(questId);
}
}
if (anyObjectiveChangedCompletionState)
UpdateVisibleGameobjectsOrSpellClicks();
PhasingHandler::OnConditionChange(this);
}
bool Player::HasQuestForItem(uint32 itemid) const
{
// Search incomplete objective first
for (QuestObjectiveStatusMap::value_type const& objectiveItr : Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { QUEST_OBJECTIVE_ITEM, itemid }))
{
Quest const* qInfo = ASSERT_NOTNULL(sObjectMgr->GetQuestTemplate(objectiveItr.second.QuestStatusItr->first));
QuestObjective const& objective = *objectiveItr.second.Objective;
if (!IsQuestObjectiveCompletable(objectiveItr.second.QuestStatusItr->second.Slot, qInfo, objective))
continue;
// hide quest if player is in raid-group and quest is no raid quest
if (GetGroup() && GetGroup()->isRaidGroup() && !qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID()))
if (!InBattleground()) //there are two ways.. we can make every bg-quest a raidquest, or add this code here.. i don't know if this can be exploited by other quests, but i think all other quests depend on a specific area.. but keep this in mind, if something strange happens later
continue;
if (!IsQuestObjectiveComplete(objectiveItr.second.QuestStatusItr->second.Slot, qInfo, objective))
return true;
}
// This part - for ItemDrop
for (std::pair<uint32 const, QuestStatusData> const& questStatus : m_QuestStatus)
{
if (questStatus.second.Status != QUEST_STATUS_INCOMPLETE)
continue;
Quest const* qInfo = ASSERT_NOTNULL(sObjectMgr->GetQuestTemplate(questStatus.first));
// hide quest if player is in raid-group and quest is no raid quest
if (GetGroup() && GetGroup()->isRaidGroup() && !qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID()))
if (!InBattleground())
continue;
for (uint8 j = 0; j < QUEST_ITEM_DROP_COUNT; ++j)
{
// examined item is a source item
if (qInfo->ItemDrop[j] != itemid)
continue;
ItemTemplate const* pProto = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(itemid));
// allows custom amount drop when not 0
uint32 maxAllowedCount = qInfo->ItemDropQuantity[j] ? qInfo->ItemDropQuantity[j] : pProto->GetMaxStackSize();
// 'unique' item
if (pProto->GetMaxCount() && pProto->GetMaxCount() < maxAllowedCount)
maxAllowedCount = pProto->GetMaxCount();
if (GetItemCount(itemid, true) < maxAllowedCount)
return true;
}
}
return false;
}
int32 Player::GetQuestObjectiveData(QuestObjective const& objective) const
{
uint16 slot = FindQuestSlot(objective.QuestID);
if (slot >= MAX_QUEST_LOG_SIZE)
return 0;
return GetQuestSlotObjectiveData(slot, objective);
}
void Player::SetQuestObjectiveData(QuestObjective const& objective, int32 data)
{
if (objective.StorageIndex < 0)
{
TC_LOG_ERROR("entities.player.quest", "Player::SetQuestObjectiveData: called for quest %u with invalid StorageIndex %d (objective data is not tracked)",
objective.QuestID, objective.StorageIndex);
return;
}
auto itr = m_QuestStatus.find(objective.QuestID);
if (itr == m_QuestStatus.end())
{
TC_LOG_ERROR("entities.player.quest", "Player::SetQuestObjectiveData: player '%s' (%s) doesn't have quest status data (QuestID: %u)",
GetName().c_str(), GetGUID().ToString().c_str(), objective.QuestID);
return;
}
QuestStatusData& status = itr->second;
if (uint8(objective.StorageIndex) >= MAX_QUEST_COUNTS)
{
TC_LOG_ERROR("entities.player.quest", "Player::SetQuestObjectiveData: player '%s' (%s) quest %u out of range StorageIndex %u",
GetName().c_str(), GetGUID().ToString().c_str(), objective.QuestID, objective.StorageIndex);
return;
}
if (status.Slot >= MAX_QUEST_LOG_SIZE)
return;
// No change
int32 oldData = GetQuestSlotObjectiveData(status.Slot, objective);
if (oldData == data)
return;
if (Quest const* quest = sObjectMgr->GetQuestTemplate(objective.QuestID))
sScriptMgr->OnQuestObjectiveChange(this, quest, objective, oldData, data);
// Add to save
m_QuestStatusSave[objective.QuestID] = QUEST_DEFAULT_SAVE_TYPE;
// Update quest fields
if (!objective.IsStoringFlag())
SetQuestSlotCounter(status.Slot, objective.StorageIndex, data);
else if (data)
SetQuestSlotObjectiveFlag(status.Slot, objective.StorageIndex);
else
RemoveQuestSlotObjectiveFlag(status.Slot, objective.StorageIndex);
}
bool Player::IsQuestObjectiveCompletable(uint16 slot, Quest const* quest, QuestObjective const& objective) const
{
ASSERT(objective.QuestID == quest->GetQuestId());
if (objective.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR)
{
// delegate check to actual progress bar objective
auto progressBarObjectiveItr = std::find_if(quest->GetObjectives().begin(), quest->GetObjectives().end(), [](QuestObjective const& otherObjective)
{
return otherObjective.Type == QUEST_OBJECTIVE_PROGRESS_BAR && !(otherObjective.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR);
});
if (progressBarObjectiveItr == quest->GetObjectives().end())
return false;
return IsQuestObjectiveCompletable(slot, quest, *progressBarObjectiveItr) && !IsQuestObjectiveComplete(slot, quest, *progressBarObjectiveItr);
}
int32 objectiveIndex = int32(std::distance(&quest->GetObjectives()[0], &objective));
if (!objectiveIndex)
return true;
// check sequenced objectives
int32 previousIndex = objectiveIndex - 1;
bool objectiveSequenceSatisfied = true;
bool previousSequencedObjectiveComplete = false;
int32 previousSequencedObjectiveIndex = -1;
do
{
QuestObjective const& previousObjective = quest->GetObjectives()[previousIndex];
if (previousObjective.Flags & QUEST_OBJECTIVE_FLAG_SEQUENCED)
{
previousSequencedObjectiveIndex = previousIndex;
previousSequencedObjectiveComplete = IsQuestObjectiveComplete(slot, quest, previousObjective);
break;
}
if (objectiveSequenceSatisfied)
objectiveSequenceSatisfied = IsQuestObjectiveComplete(slot, quest, previousObjective) || (previousObjective.Flags & (QUEST_OBJECTIVE_FLAG_OPTIONAL | QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR));
--previousIndex;
} while (previousIndex >= 0);
if (objective.Flags & QUEST_OBJECTIVE_FLAG_SEQUENCED)
{
if (previousSequencedObjectiveIndex == -1)
return objectiveSequenceSatisfied;
if (!previousSequencedObjectiveComplete || !objectiveSequenceSatisfied)
return false;
}
else if (!previousSequencedObjectiveComplete && previousSequencedObjectiveIndex != -1)
{
if (!IsQuestObjectiveCompletable(slot, quest, quest->GetObjectives()[previousSequencedObjectiveIndex]))
return false;
}
return true;
}
bool Player::IsQuestObjectiveComplete(uint16 slot, Quest const* quest, QuestObjective const& objective) const
{
switch (objective.Type)
{
case QUEST_OBJECTIVE_MONSTER:
case QUEST_OBJECTIVE_ITEM:
case QUEST_OBJECTIVE_GAMEOBJECT:
case QUEST_OBJECTIVE_TALKTO:
case QUEST_OBJECTIVE_PLAYERKILLS:
case QUEST_OBJECTIVE_WINPVPPETBATTLES:
case QUEST_OBJECTIVE_HAVE_CURRENCY:
case QUEST_OBJECTIVE_OBTAIN_CURRENCY:
case QUEST_OBJECTIVE_INCREASE_REPUTATION:
if (GetQuestSlotObjectiveData(slot, objective) < objective.Amount)
return false;
break;
case QUEST_OBJECTIVE_MIN_REPUTATION:
if (GetReputationMgr().GetReputation(objective.ObjectID) < objective.Amount)
return false;
break;
case QUEST_OBJECTIVE_MAX_REPUTATION:
if (GetReputationMgr().GetReputation(objective.ObjectID) > objective.Amount)
return false;
break;
case QUEST_OBJECTIVE_MONEY:
if (!HasEnoughMoney(uint64(objective.Amount)))
return false;
break;
case QUEST_OBJECTIVE_AREATRIGGER:
case QUEST_OBJECTIVE_WINPETBATTLEAGAINSTNPC:
case QUEST_OBJECTIVE_DEFEATBATTLEPET:
case QUEST_OBJECTIVE_CRITERIA_TREE:
case QUEST_OBJECTIVE_AREA_TRIGGER_ENTER:
case QUEST_OBJECTIVE_AREA_TRIGGER_EXIT:
if (!GetQuestSlotObjectiveData(slot, objective))
return false;
break;
case QUEST_OBJECTIVE_LEARNSPELL:
if (!HasSpell(objective.ObjectID))
return false;
break;
case QUEST_OBJECTIVE_CURRENCY:
if (!HasCurrency(objective.ObjectID, objective.Amount))
return false;
break;
case QUEST_OBJECTIVE_PROGRESS_BAR:
if (!IsQuestObjectiveProgressBarComplete(slot, quest))
return false;
break;
default:
TC_LOG_ERROR("entities.player.quest", "Player::CanCompleteQuest: Player '%s' (%s) tried to complete a quest (ID: %u) with an unknown objective type %u",
GetName().c_str(), GetGUID().ToString().c_str(), objective.QuestID, objective.Type);
return false;
}
return true;
}
bool Player::IsQuestObjectiveProgressBarComplete(uint16 slot, Quest const* quest) const
{
float progress = 0.0f;
for (QuestObjective const& obj : quest->GetObjectives())
{
if (obj.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR)
{
progress += GetQuestSlotObjectiveData(slot, obj) * obj.ProgressBarWeight;
if (progress >= 100.0f)
return true;
}
}
return false;
}
void Player::SendQuestComplete(uint32 questId) const
{
if (questId)
{
WorldPackets::Quest::QuestUpdateComplete data;
data.QuestID = questId;
SendDirectMessage(data.Write());
}
}
void Player::SendQuestReward(Quest const* quest, Creature const* questGiver, uint32 xp, bool hideChatMessage) const
{
uint32 questId = quest->GetQuestId();
sGameEventMgr->HandleQuestComplete(questId);
uint32 moneyReward;
if (!IsMaxLevel())
{
moneyReward = GetQuestMoneyReward(quest);
}
else // At max level, increase gold reward
{
xp = 0;
moneyReward = uint32(GetQuestMoneyReward(quest) + int32(quest->GetRewMoneyMaxLevel() * sWorld->getRate(RATE_DROP_MONEY)));
}
WorldPackets::Quest::QuestGiverQuestComplete packet;
packet.QuestID = questId;
packet.MoneyReward = moneyReward;
packet.XPReward = xp;
packet.SkillLineIDReward = quest->GetRewardSkillId();
packet.NumSkillUpsReward = quest->GetRewardSkillPoints();
if (questGiver)
{
if (questGiver->IsGossip())
packet.LaunchGossip = true;
else if (questGiver->IsQuestGiver())
packet.LaunchQuest = true;
else if (quest->GetNextQuestInChain() && !quest->HasFlag(QUEST_FLAGS_AUTOCOMPLETE))
packet.UseQuestReward = true;
}
packet.HideChatMessage = hideChatMessage;
SendDirectMessage(packet.Write());
}
void Player::SendQuestFailed(uint32 questID, InventoryResult reason) const
{
if (questID)
{
WorldPackets::Quest::QuestGiverQuestFailed questGiverQuestFailed;
questGiverQuestFailed.QuestID = questID;
questGiverQuestFailed.Reason = reason; // failed reason (valid reasons: 4, 16, 50, 17, other values show default message)
SendDirectMessage(questGiverQuestFailed.Write());
}
}
void Player::SendQuestTimerFailed(uint32 questID) const
{
if (questID)
{
WorldPackets::Quest::QuestUpdateFailedTimer questUpdateFailedTimer;
questUpdateFailedTimer.QuestID = questID;
SendDirectMessage(questUpdateFailedTimer.Write());
}
}
void Player::SendCanTakeQuestResponse(QuestFailedReason reason, bool sendErrorMessage /*= true*/, std::string reasonText /*= ""*/) const
{
WorldPackets::Quest::QuestGiverInvalidQuest questGiverInvalidQuest;
questGiverInvalidQuest.Reason = reason;
questGiverInvalidQuest.SendErrorMessage = sendErrorMessage;
questGiverInvalidQuest.ReasonText = reasonText;
SendDirectMessage(questGiverInvalidQuest.Write());
}
void Player::SendQuestConfirmAccept(Quest const* quest, Player* receiver) const
{
if (!receiver)
return;
WorldPackets::Quest::QuestConfirmAcceptResponse packet;
packet.QuestTitle = quest->GetLogTitle();
uint32 questID = quest->GetQuestId();
LocaleConstant localeConstant = receiver->GetSession()->GetSessionDbLocaleIndex();
if (localeConstant != LOCALE_enUS)
if (QuestTemplateLocale const* questTemplateLocale = sObjectMgr->GetQuestLocale(questID))
ObjectMgr::GetLocaleString(questTemplateLocale->LogTitle, localeConstant, packet.QuestTitle);
packet.QuestID = questID;
packet.InitiatedBy = GetGUID();
receiver->SendDirectMessage(packet.Write());
}
void Player::SendPushToPartyResponse(Player const* player, QuestPushReason reason, Quest const* quest /*= nullptr*/) const
{
if (player)
{
WorldPackets::Quest::QuestPushResultResponse response;
response.SenderGUID = player->GetGUID();
response.Result = AsUnderlyingType(reason);
if (quest)
{
response.QuestTitle = quest->GetLogTitle();
LocaleConstant localeConstant = GetSession()->GetSessionDbLocaleIndex();
if (localeConstant != LOCALE_enUS)
if (QuestTemplateLocale const* questTemplateLocale = sObjectMgr->GetQuestLocale(quest->GetQuestId()))
ObjectMgr::GetLocaleString(questTemplateLocale->LogTitle, localeConstant, response.QuestTitle);
}
SendDirectMessage(response.Write());
}
}
void Player::SendQuestUpdateAddCredit(Quest const* quest, ObjectGuid guid, QuestObjective const& obj, uint16 count) const
{
WorldPackets::Quest::QuestUpdateAddCredit packet;
packet.VictimGUID = guid;
packet.QuestID = quest->GetQuestId();
packet.ObjectID = obj.ObjectID;
packet.Count = count;
packet.Required = obj.Amount;
packet.ObjectiveType = obj.Type;
SendDirectMessage(packet.Write());
}
void Player::SendQuestUpdateAddCreditSimple(QuestObjective const& obj) const
{
WorldPackets::Quest::QuestUpdateAddCreditSimple packet;
packet.QuestID = obj.QuestID;
packet.ObjectID = obj.ObjectID;
packet.ObjectiveType = obj.Type;
SendDirectMessage(packet.Write());
}
void Player::SendQuestUpdateAddPlayer(Quest const* quest, uint16 newCount) const
{
WorldPackets::Quest::QuestUpdateAddPvPCredit questUpdateAddPvpCredit;
questUpdateAddPvpCredit.QuestID = quest->GetQuestId();
questUpdateAddPvpCredit.Count = newCount;
SendDirectMessage(questUpdateAddPvpCredit.Write());
}
void Player::SendQuestGiverStatusMultiple()
{
WorldPackets::Quest::QuestGiverStatusMultiple response;
for (auto itr = m_clientGUIDs.begin(); itr != m_clientGUIDs.end(); ++itr)
{
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;
response.QuestGiver.emplace_back(questgiver->GetGUID(), GetQuestDialogStatus(questgiver));
}
else if (itr->IsGameObject())
{
GameObject* questgiver = GetMap()->GetGameObject(*itr);
if (!questgiver || questgiver->GetGoType() != GAMEOBJECT_TYPE_QUESTGIVER)
continue;
response.QuestGiver.emplace_back(questgiver->GetGUID(), GetQuestDialogStatus(questgiver));
}
}
SendDirectMessage(response.Write());
}
bool Player::HasPvPForcingQuest() const
{
for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
{
uint32 questId = GetQuestSlotQuestId(i);
if (questId == 0)
continue;
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest)
continue;
if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP))
return true;
}
return false;
}
/*********************************************************/
/*** LOAD SYSTEM ***/
/*********************************************************/
void Player::_LoadDeclinedNames(PreparedQueryResult result)
{
if (!result)
return;
delete m_declinedname;
m_declinedname = new DeclinedName;
for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
m_declinedname->name[i] = (*result)[i].GetString();
}
void Player::_LoadArenaTeamInfo(PreparedQueryResult result)
{
// arenateamid, played_week, played_season, personal_rating
uint16 personalRatingCache[] = {0, 0, 0};
if (result)
{
do
{
Field* fields = result->Fetch();
uint32 arenaTeamId = fields[0].GetUInt32();
ArenaTeam* arenaTeam = sArenaTeamMgr->GetArenaTeamById(arenaTeamId);
if (!arenaTeam)
{
TC_LOG_ERROR("entities.player", "Player::_LoadArenaTeamInfo: couldn't load arenateam %u", arenaTeamId);
continue;
}
uint8 arenaSlot = arenaTeam->GetSlot();
ASSERT(arenaSlot < 3);
personalRatingCache[arenaSlot] = fields[4].GetUInt16();
SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_ID, arenaTeamId);
SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_TYPE, arenaTeam->GetType());
SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_MEMBER, (arenaTeam->GetCaptain() == GetGUID()) ? 0 : 1);
SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_GAMES_WEEK, uint32(fields[1].GetUInt16()));
SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_GAMES_SEASON, uint32(fields[2].GetUInt16()));
SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_WINS_SEASON, uint32(fields[3].GetUInt16()));
}
while (result->NextRow());
}
for (uint8 slot = 0; slot <= 2; ++slot)
{
SetArenaTeamInfoField(slot, ARENA_TEAM_PERSONAL_RATING, uint32(personalRatingCache[slot]));
}
}
void Player::_LoadEquipmentSets(PreparedQueryResult result)
{
// SetPQuery(PLAYER_LOGIN_QUERY_LOADEQUIPMENTSETS, "SELECT setguid, setindex, name, iconname, ignore_mask, AssignedSpecIndex, item0, item1, item2, item3, item4, item5, item6, item7, item8, item9, item10, item11, item12, item13, item14, item15, item16, item17, item18 FROM character_equipmentsets WHERE guid = '%u' ORDER BY setindex", GUID_LOPART(m_guid));
if (!result)
return;
do
{
Field* fields = result->Fetch();
EquipmentSetInfo eqSet;
eqSet.Data.Guid = fields[0].GetUInt64();
eqSet.Data.Type = EquipmentSetInfo::EQUIPMENT;
eqSet.Data.SetID = fields[1].GetUInt8();
eqSet.Data.SetName = fields[2].GetString();
eqSet.Data.SetIcon = fields[3].GetString();
eqSet.Data.IgnoreMask = fields[4].GetUInt32();
eqSet.Data.AssignedSpecIndex = fields[5].GetInt32();
eqSet.State = EQUIPMENT_SET_UNCHANGED;
for (uint32 i = 0; i < EQUIPMENT_SLOT_END; ++i)
if (ObjectGuid::LowType guid = fields[6 + i].GetUInt64())
eqSet.Data.Pieces[i] = ObjectGuid::Create<HighGuid::Item>(guid);
eqSet.Data.Appearances.fill(0);
eqSet.Data.Enchants.fill(0);
if (eqSet.Data.SetID >= MAX_EQUIPMENT_SET_INDEX) // client limit
continue;
_equipmentSets[eqSet.Data.Guid] = eqSet;
} while (result->NextRow());
}
void Player::_LoadTransmogOutfits(PreparedQueryResult result)
{
// 0 1 2 3 4 5 6 7 8 9
//SELECT setguid, setindex, name, iconname, ignore_mask, appearance0, appearance1, appearance2, appearance3, appearance4,
// 10 11 12 13 14 15 16 17 18 19 20 21
// appearance5, appearance6, appearance7, appearance8, appearance9, appearance10, appearance11, appearance12, appearance13, appearance14, appearance15, appearance16,
// 22 23 24 25
// appearance17, appearance18, mainHandEnchant, offHandEnchant FROM character_transmog_outfits WHERE guid = ? ORDER BY setindex
if (!result)
return;
do
{
Field* fields = result->Fetch();
EquipmentSetInfo eqSet;
eqSet.Data.Guid = fields[0].GetUInt64();
eqSet.Data.Type = EquipmentSetInfo::TRANSMOG;
eqSet.Data.SetID = fields[1].GetUInt8();
eqSet.Data.SetName = fields[2].GetString();
eqSet.Data.SetIcon = fields[3].GetString();
eqSet.Data.IgnoreMask = fields[4].GetUInt32();
eqSet.State = EQUIPMENT_SET_UNCHANGED;
eqSet.Data.Pieces.fill(ObjectGuid::Empty);
for (uint32 i = 0; i < EQUIPMENT_SLOT_END; ++i)
eqSet.Data.Appearances[i] = fields[5 + i].GetInt32();
for (std::size_t i = 0; i < eqSet.Data.Enchants.size(); ++i)
eqSet.Data.Enchants[i] = fields[24 + i].GetInt32();
if (eqSet.Data.SetID >= MAX_EQUIPMENT_SET_INDEX) // client limit
continue;
_equipmentSets[eqSet.Data.Guid] = eqSet;
} while (result->NextRow());
}
void Player::_LoadBGData(PreparedQueryResult result)
{
if (!result)
return;
Field* fields = result->Fetch();
// Expecting only one row
// 0 1 2 3 4 5 6 7 8 9
// SELECT instanceId, team, joinX, joinY, joinZ, joinO, joinMapId, taxiStart, taxiEnd, mountSpell FROM character_battleground_data WHERE guid = ?
m_bgData.bgInstanceID = fields[0].GetUInt32();
m_bgData.bgTeam = fields[1].GetUInt16();
m_bgData.joinPos = WorldLocation(fields[6].GetUInt16(), // Map
fields[2].GetFloat(), // X
fields[3].GetFloat(), // Y
fields[4].GetFloat(), // Z
fields[5].GetFloat()); // Orientation
m_bgData.taxiPath[0] = fields[7].GetUInt32();
m_bgData.taxiPath[1] = fields[8].GetUInt32();
m_bgData.mountSpell = fields[9].GetUInt32();
}
bool Player::LoadPositionFromDB(uint32& mapid, float& x, float& y, float& z, float& o, bool& in_flight, ObjectGuid guid)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_POSITION);
stmt->setUInt64(0, guid.GetCounter());
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (!result)
return false;
Field* fields = result->Fetch();
x = fields[0].GetFloat();
y = fields[1].GetFloat();
z = fields[2].GetFloat();
o = fields[3].GetFloat();
mapid = fields[4].GetUInt16();
in_flight = !fields[5].GetString().empty();
return true;
}
void Player::SetHomebind(WorldLocation const& loc, uint32 areaId)
{
m_homebind.WorldRelocate(loc);
m_homebindAreaId = areaId;
// update sql homebind
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_PLAYER_HOMEBIND);
stmt->setUInt16(0, m_homebind.GetMapId());
stmt->setUInt16(1, m_homebindAreaId);
stmt->setFloat (2, m_homebind.GetPositionX());
stmt->setFloat (3, m_homebind.GetPositionY());
stmt->setFloat (4, m_homebind.GetPositionZ());
stmt->setFloat (5, m_homebind.GetOrientation());
stmt->setUInt64(6, GetGUID().GetCounter());
CharacterDatabase.Execute(stmt);
}
void Player::SendBindPointUpdate() const
{
WorldPackets::Misc::BindPointUpdate packet;
packet.BindPosition = Position(m_homebind.GetPositionX(), m_homebind.GetPositionY(), m_homebind.GetPositionZ());
packet.BindMapID = m_homebind.GetMapId();
packet.BindAreaID = m_homebindAreaId;
SendDirectMessage(packet.Write());
}
void Player::SendPlayerBound(ObjectGuid const& binderGuid, uint32 areaId) const
{
WorldPackets::Misc::PlayerBound packet(binderGuid, areaId);
SendDirectMessage(packet.Write());
}
uint32 Player::GetUInt32ValueFromArray(Tokenizer const& data, uint16 index)
{
if (index >= data.size())
return 0;
return (uint32)atoi(data[index]);
}
float Player::GetFloatValueFromArray(Tokenizer const& data, uint16 index)
{
float result;
uint32 temp = Player::GetUInt32ValueFromArray(data, index);
memcpy(&result, &temp, sizeof(result));
return result;
}
bool Player::IsLoading() const
{
return GetSession()->PlayerLoading();
}
bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder* holder)
{
PreparedQueryResult result = holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_FROM);
if (!result)
{
std::string name = "<unknown>";
sCharacterCache->GetCharacterNameByGuid(guid, name);
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player '%s' (%s) not found in table `characters`, can't load. ", name.c_str(), guid.ToString().c_str());
return false;
}
struct PlayerLoadData
{
// "SELECT c.guid, account, name, race, class, gender, level, xp, money, inventorySlots, bankSlots, restState, playerFlags, playerFlagsEx, "
// "position_x, position_y, position_z, map, orientation, taximask, createTime, createMode, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, "
// "resettalents_time, primarySpecialization, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeonDifficulty, "
// "totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, "
// "health, power1, power2, power3, power4, power5, power6, power7, instance_id, activeTalentGroup, lootSpecId, exploredZones, knownTitles, actionBars, raidDifficulty, legacyRaidDifficulty, fishingSteps, "
// "honor, honorLevel, honorRestState, honorRestBonus, numRespecs "
// "FROM characters c LEFT JOIN character_fishingsteps cfs ON c.guid = cfs.guid WHERE c.guid = ?", CONNECTION_ASYNC);
ObjectGuid::LowType guid;
uint32 account;
std::string name;
uint8 race;
uint8 class_;
Gender gender;
uint8 level;
uint32 xp;
uint64 money;
uint8 inventorySlots;
uint8 bankSlots;
PlayerRestState restState;
PlayerFlags playerFlags;
PlayerFlagsEx playerFlagsEx;
float position_x;
float position_y;
float position_z;
uint16 map;
float orientation;
std::string taximask;
time_t createTime;
PlayerCreateMode createMode;
uint8 cinematic;
uint32 totaltime;
uint32 leveltime;
float rest_bonus;
time_t logout_time;
uint8 is_logout_resting;
uint32 resettalents_cost;
time_t resettalents_time;
uint32 primarySpecialization;
float trans_x;
float trans_y;
float trans_z;
float trans_o;
ObjectGuid::LowType transguid;
uint16 extra_flags;
uint8 stable_slots;
uint16 at_login;
uint16 zone;
uint8 online;
time_t death_expire_time;
std::string taxi_path;
Difficulty dungeonDifficulty;
uint32 totalKills;
uint16 todayKills;
uint16 yesterdayKills;
uint32 chosenTitle;
uint32 watchedFaction;
uint8 drunk;
uint32 health;
std::array<uint32, MAX_POWERS_PER_CLASS> powers;
uint32 instance_id;
uint8 activeTalentGroup;
uint32 lootSpecId;
std::string exploredZones;
std::string knownTitles;
uint8 actionBars;
Difficulty raidDifficulty;
Difficulty legacyRaidDifficulty;
uint8 fishingSteps;
uint32 honor;
uint32 honorLevel;
PlayerRestState honorRestState;
float honorRestBonus;
uint8 numRespecs;
PlayerLoadData(Field* fields)
{
std::size_t i = 0;
guid = fields[i++].GetUInt64();
account = fields[i++].GetUInt32();
name = fields[i++].GetString();
race = fields[i++].GetUInt8();
class_ = fields[i++].GetUInt8();
gender = Gender(fields[i++].GetUInt8());
level = fields[i++].GetUInt8();
xp = fields[i++].GetUInt32();
money = fields[i++].GetUInt64();
inventorySlots = fields[i++].GetUInt8();
bankSlots = fields[i++].GetUInt8();
restState = PlayerRestState(fields[i++].GetUInt8());
playerFlags = PlayerFlags(fields[i++].GetUInt32());
playerFlagsEx = PlayerFlagsEx(fields[i++].GetUInt32());
position_x = fields[i++].GetFloat();
position_y = fields[i++].GetFloat();
position_z = fields[i++].GetFloat();
map = fields[i++].GetUInt16();
orientation = fields[i++].GetFloat();
taximask = fields[i++].GetString();
createTime = fields[i++].GetInt64();
createMode = PlayerCreateMode(fields[i++].GetInt8());
cinematic = fields[i++].GetUInt8();
totaltime = fields[i++].GetUInt32();
leveltime = fields[i++].GetUInt32();
rest_bonus = fields[i++].GetFloat();
logout_time = fields[i++].GetInt64();
is_logout_resting = fields[i++].GetUInt8();
resettalents_cost = fields[i++].GetUInt32();
resettalents_time = fields[i++].GetInt64();
primarySpecialization = fields[i++].GetUInt32();
trans_x = fields[i++].GetFloat();
trans_y = fields[i++].GetFloat();
trans_z = fields[i++].GetFloat();
trans_o = fields[i++].GetFloat();
transguid = fields[i++].GetUInt64();
extra_flags = fields[i++].GetUInt16();
stable_slots = fields[i++].GetUInt8();
at_login = fields[i++].GetUInt16();
zone = fields[i++].GetUInt16();
online = fields[i++].GetUInt8();
death_expire_time = fields[i++].GetInt64();
taxi_path = fields[i++].GetString();
dungeonDifficulty = Difficulty(fields[i++].GetUInt8());
totalKills = fields[i++].GetUInt32();
todayKills = fields[i++].GetUInt16();
yesterdayKills = fields[i++].GetUInt16();
chosenTitle = fields[i++].GetUInt32();
watchedFaction = fields[i++].GetUInt32();
drunk = fields[i++].GetUInt8();
health = fields[i++].GetUInt32();
for (uint32& power : powers)
power = fields[i++].GetUInt32();
instance_id = fields[i++].GetUInt32();
activeTalentGroup = fields[i++].GetUInt8();
lootSpecId = fields[i++].GetUInt32();
exploredZones = fields[i++].GetString();
knownTitles = fields[i++].GetString();
actionBars = fields[i++].GetUInt8();
raidDifficulty = Difficulty(fields[i++].GetUInt8());
legacyRaidDifficulty = Difficulty(fields[i++].GetUInt8());
fishingSteps = fields[i++].GetUInt8();
honor = fields[i++].GetUInt32();
honorLevel = fields[i++].GetUInt32();
honorRestState = PlayerRestState(fields[i++].GetUInt8());
honorRestBonus = fields[i++].GetFloat();
numRespecs = fields[i++].GetUInt8();
}
} fields(result->Fetch());
// check if the character's account in the db and the logged in account match.
// player should be able to load/delete character only with correct account!
if (fields.account != GetSession()->GetAccountId())
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) loading from wrong account (is: %u, should be: %u)", guid.ToString().c_str(), GetSession()->GetAccountId(), fields.account);
return false;
}
if (holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_BANNED))
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) is banned, can't load.", guid.ToString().c_str());
return false;
}
Object::_Create(guid);
m_name = std::move(fields.name);
// check name limitations
if (ObjectMgr::CheckPlayerName(m_name, GetSession()->GetSessionDbcLocale()) != CHAR_NAME_SUCCESS ||
(!GetSession()->HasPermission(rbac::RBAC_PERM_SKIP_CHECK_CHARACTER_CREATION_RESERVEDNAME) && sObjectMgr->IsReservedName(m_name)))
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG);
stmt->setUInt16(0, uint16(AT_LOGIN_RENAME));
stmt->setUInt64(1, guid.GetCounter());
CharacterDatabase.Execute(stmt);
return false;
}
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::WowAccount), GetSession()->GetAccountGUID());
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::BnetAccount), GetSession()->GetBattlenetAccountGUID());
if (!IsValidGender(fields.gender))
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) has wrong gender (%u), can't load.", guid.ToString().c_str(), uint32(fields.gender));
return false;
}
SetRace(fields.race);
SetClass(fields.class_);
SetGender(fields.gender);
// check if race/class combination is valid
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(GetRace(), GetClass());
if (!info)
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) has wrong race/class (%u/%u), can't load.", guid.ToString().c_str(), GetRace(), GetClass());
return false;
}
SetLevel(fields.level, false);
SetXP(fields.xp);
Tokenizer exploredZones(fields.exploredZones, ' ');
if (exploredZones.size() == PLAYER_EXPLORED_ZONES_SIZE * 2)
for (std::size_t i = 0; i < exploredZones.size(); ++i)
SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ExploredZones, i / 2),
(uint64(atoul(exploredZones[i])) << (32 * (i % 2))));
Tokenizer knownTitles(fields.knownTitles, ' ');
if (!(knownTitles.size() % 2))
{
for (std::size_t i = 0; i < knownTitles.size(); ++i)
SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::KnownTitles, i / 2),
(uint64(atoul(knownTitles[i])) << (32 * (i % 2))));
}
SetObjectScale(1.0f);
SetHoverHeight(1.0f);
// load achievements before anything else to prevent multiple gains for the same achievement/criteria on every loading (as loading does call UpdateCriteria)
m_achievementMgr->LoadFromDB(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ACHIEVEMENTS), holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_CRITERIA_PROGRESS));
m_questObjectiveCriteriaMgr->LoadFromDB(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_OBJECTIVES_CRITERIA), holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_OBJECTIVES_CRITERIA_PROGRESS));
SetMoney(std::min(fields.money, MAX_MONEY_AMOUNT));
std::vector<UF::ChrCustomizationChoice> customizations;
if (PreparedQueryResult customizationsResult = holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_CUSTOMIZATIONS))
{
do
{
Field* fields = customizationsResult->Fetch();
customizations.emplace_back();
UF::ChrCustomizationChoice& choice = customizations.back();
choice.ChrCustomizationOptionID = fields[0].GetUInt32();
choice.ChrCustomizationChoiceID = fields[1].GetUInt32();
} while (customizationsResult->NextRow());
}
SetCustomizations(Trinity::Containers::MakeIteratorPair(customizations.begin(), customizations.end()), false);
SetInventorySlotCount(fields.inventorySlots);
SetBankBagSlotCount(fields.bankSlots);
SetNativeGender(fields.gender);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::Inebriation), fields.drunk);
SetPlayerFlags(fields.playerFlags);
SetPlayerFlagsEx(fields.playerFlagsEx);
SetWatchedFactionIndex(fields.watchedFaction);
m_atLoginFlags = fields.at_login;
if (!GetSession()->ValidateAppearance(Races(GetRace()), Classes(GetClass()), fields.gender, MakeChrCustomizationChoiceRange(customizations)))
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) has wrong Appearance values (Hair/Skin/Color), can't load.", guid.ToString().c_str());
return false;
}
// set which actionbars the client has active - DO NOT REMOVE EVER AGAIN (can be changed though, if it does change fieldwise)
SetMultiActionBars(fields.actionBars);
m_fishingSteps = fields.fishingSteps;
InitDisplayIds();
// cleanup inventory related item value fields (it will be filled correctly in _LoadInventory)
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
{
SetInvSlot(slot, ObjectGuid::Empty);
SetVisibleItemSlot(slot, nullptr);
delete m_items[slot];
m_items[slot] = nullptr;
}
TC_LOG_DEBUG("entities.player.loading", "Player::LoadFromDB: Load Basic value of player '%s' is: ", m_name.c_str());
outDebugValues();
//Need to call it to initialize m_team (m_team can be calculated from race)
//Other way is to saves m_team into characters table.
SetFactionForRace(GetRace());
// load home bind and check in same time class/race pair, it used later for restore broken positions
if (!_LoadHomeBind(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_HOME_BIND)))
return false;
InitializeSkillFields();
InitPrimaryProfessions(); // to max set before any spell loaded
// init saved position, and fix it later if problematic
Relocate(fields.position_x, fields.position_y, fields.position_z, fields.orientation);
uint32 mapId = fields.map;
uint32 instanceId = fields.instance_id;
SetDungeonDifficultyID(CheckLoadedDungeonDifficultyID(fields.dungeonDifficulty));
SetRaidDifficultyID(CheckLoadedRaidDifficultyID(fields.raidDifficulty));
SetLegacyRaidDifficultyID(CheckLoadedLegacyRaidDifficultyID(fields.legacyRaidDifficulty));
#define RelocateToHomebind(){ mapId = m_homebind.GetMapId(); instanceId = 0; WorldRelocate(m_homebind); }
_LoadGroup(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_GROUP));
_LoadCurrency(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_CURRENCY));
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::LifetimeHonorableKills), fields.totalKills);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::TodayHonorableKills), fields.totalKills);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::YesterdayHonorableKills), fields.yesterdayKills);
_LoadBoundInstances(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_BOUND_INSTANCES));
_LoadInstanceTimeRestrictions(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_INSTANCE_LOCK_TIMES));
_LoadBGData(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_BG_DATA));
GetSession()->SetPlayer(this);
MapEntry const* mapEntry = sMapStore.LookupEntry(mapId);
Map* map = nullptr;
bool player_at_bg = false;
if (!mapEntry || !IsPositionValid())
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) has invalid coordinates (MapId: %u X: %f Y: %f Z: %f O: %f). Teleport to default race/class locations.",
guid.ToString().c_str(), mapId, GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
RelocateToHomebind();
}
// Player was saved in Arena or Bg
else if (mapEntry->IsBattlegroundOrArena())
{
Battleground* currentBg = nullptr;
if (m_bgData.bgInstanceID) //saved in Battleground
currentBg = sBattlegroundMgr->GetBattleground(m_bgData.bgInstanceID, BATTLEGROUND_TYPE_NONE);
player_at_bg = currentBg && currentBg->IsPlayerInBattleground(GetGUID());
if (player_at_bg && currentBg->GetStatus() != STATUS_WAIT_LEAVE)
{
map = currentBg->GetBgMap();
BattlegroundQueueTypeId bgQueueTypeId = currentBg->GetQueueId();
AddBattlegroundQueueId(bgQueueTypeId);
m_bgData.bgTypeID = currentBg->GetTypeID();
//join player to battleground group
currentBg->EventPlayerLoggedIn(this);
SetInviteForBattlegroundQueueType(bgQueueTypeId, currentBg->GetInstanceID());
}
// Bg was not found - go to Entry Point
else
{
// leave bg
if (player_at_bg)
{
player_at_bg = false;
currentBg->RemovePlayerAtLeave(GetGUID(), false, true);
}
// Do not look for instance if bg not found
WorldLocation const& _loc = GetBattlegroundEntryPoint();
mapId = _loc.GetMapId();
instanceId = 0;
// Db field type is type int16, so it can never be MAPID_INVALID
//if (mapId == MAPID_INVALID) -- code kept for reference
if (int16(mapId) == int16(-1)) // Battleground Entry Point not found (???)
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) was in BG in database, but BG was not found and entry point was invalid! Teleport to default race/class locations.",
guid.ToString().c_str());
RelocateToHomebind();
}
else
Relocate(&_loc);
// We are not in BG anymore
m_bgData.bgInstanceID = 0;
}
}
// currently we do not support transport in bg
else if (fields.transguid)
{
ObjectGuid transGUID = ObjectGuid::Create<HighGuid::Transport>(fields.transguid);
Transport* transport = nullptr;
if (Transport* go = HashMapHolder<Transport>::Find(transGUID))
transport = go;
if (transport)
{
float x = fields.trans_x, y = fields.trans_y, z = fields.trans_z, o = fields.trans_o;
m_movementInfo.transport.pos.Relocate(x, y, z, o);
transport->CalculatePassengerPosition(x, y, z, &o);
if (!Trinity::IsValidMapCoord(x, y, z, o) ||
// transport size limited
std::fabs(m_movementInfo.transport.pos.GetPositionX()) > 250.0f ||
std::fabs(m_movementInfo.transport.pos.GetPositionY()) > 250.0f ||
std::fabs(m_movementInfo.transport.pos.GetPositionZ()) > 250.0f)
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) has invalid transport coordinates (X: %f Y: %f Z: %f O: %f). Teleport to bind location.",
guid.ToString().c_str(), x, y, z, o);
m_movementInfo.transport.Reset();
RelocateToHomebind();
}
else
{
Relocate(x, y, z, o);
mapId = transport->GetMapId();
transport->AddPassenger(this);
}
}
else
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) has problems with transport guid (" UI64FMTD "). Teleport to bind location.",
guid.ToString().c_str(), fields.transguid);
RelocateToHomebind();
}
}
// currently we do not support taxi in instance
else if (!fields.taxi_path.empty())
{
instanceId = 0;
// Not finish taxi flight path
if (m_bgData.HasTaxiPath())
{
for (int i = 0; i < 2; ++i)
m_taxi.AddTaxiDestination(m_bgData.taxiPath[i]);
}
else if (!m_taxi.LoadTaxiDestinationsFromString(fields.taxi_path, GetTeam()))
{
// problems with taxi path loading
TaxiNodesEntry const* nodeEntry = nullptr;
if (uint32 node_id = m_taxi.GetTaxiSource())
nodeEntry = sTaxiNodesStore.LookupEntry(node_id);
if (!nodeEntry) // don't know taxi start node, teleport to homebind
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) has wrong data in taxi destination list, teleport to homebind.", GetGUID().ToString().c_str());
RelocateToHomebind();
}
else // has start node, teleport to it
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) has too short taxi destination list, teleport to original node.", GetGUID().ToString().c_str());
mapId = nodeEntry->ContinentID;
Relocate(nodeEntry->Pos.X, nodeEntry->Pos.Y, nodeEntry->Pos.Z, 0.0f);
}
m_taxi.ClearTaxiDestinations();
}
if (uint32 node_id = m_taxi.GetTaxiSource())
{
// save source node as recall coord to prevent recall and fall from sky
TaxiNodesEntry const* nodeEntry = sTaxiNodesStore.LookupEntry(node_id);
if (nodeEntry && nodeEntry->ContinentID == GetMapId())
{
ASSERT(nodeEntry); // checked in m_taxi.LoadTaxiDestinationsFromString
mapId = nodeEntry->ContinentID;
Relocate(nodeEntry->Pos.X, nodeEntry->Pos.Y, nodeEntry->Pos.Z, 0.0f);
}
// flight will started later
}
}
// Map could be changed before
mapEntry = sMapStore.LookupEntry(mapId);
// client without expansion support
if (mapEntry)
{
if (GetSession()->GetExpansion() < mapEntry->Expansion())
{
TC_LOG_DEBUG("entities.player.loading", "Player::LoadFromDB: Player '%s' (%s) using client without required expansion tried login at non accessible map %u",
GetName().c_str(), GetGUID().ToString().c_str(), mapId);
RelocateToHomebind();
}
// fix crash (because of if (Map* map = _FindMap(instanceId)) in MapInstanced::CreateInstance)
if (instanceId)
if (InstanceSave* save = GetInstanceSave(mapId))
if (save->GetInstanceId() != instanceId)
instanceId = 0;
}
// NOW player must have valid map
// load the player's map here if it's not already loaded
if (!map)
map = sMapMgr->CreateMap(mapId, this, instanceId);
AreaTriggerStruct const* areaTrigger = nullptr;
bool check = false;
if (!map)
{
areaTrigger = sObjectMgr->GetGoBackTrigger(mapId);
check = true;
}
else if (map->IsDungeon()) // if map is dungeon...
{
if (Map::EnterState denyReason = ((InstanceMap*)map)->CannotEnter(this)) // ... and can't enter map, then look for entry point.
{
switch (denyReason)
{
case Map::CANNOT_ENTER_DIFFICULTY_UNAVAILABLE:
SendTransferAborted(map->GetId(), TRANSFER_ABORT_DIFFICULTY, map->GetDifficultyID());
break;
case Map::CANNOT_ENTER_INSTANCE_BIND_MISMATCH:
ChatHandler(GetSession()).PSendSysMessage(GetSession()->GetTrinityString(LANG_INSTANCE_BIND_MISMATCH), map->GetMapName());
break;
case Map::CANNOT_ENTER_TOO_MANY_INSTANCES:
SendTransferAborted(map->GetId(), TRANSFER_ABORT_TOO_MANY_INSTANCES);
break;
case Map::CANNOT_ENTER_MAX_PLAYERS:
SendTransferAborted(map->GetId(), TRANSFER_ABORT_MAX_PLAYERS);
break;
case Map::CANNOT_ENTER_ZONE_IN_COMBAT:
SendTransferAborted(map->GetId(), TRANSFER_ABORT_ZONE_IN_COMBAT);
break;
default:
break;
}
areaTrigger = sObjectMgr->GetGoBackTrigger(mapId);
check = true;
}
else if (instanceId && !sInstanceSaveMgr->GetInstanceSave(instanceId)) // ... and instance is reseted then look for entrance.
{
areaTrigger = sObjectMgr->GetMapEntranceTrigger(mapId);
check = true;
}
}
if (check) // in case of special event when creating map...
{
if (areaTrigger) // ... if we have an areatrigger, then relocate to new map/coordinates.
{
Relocate(areaTrigger->target_X, areaTrigger->target_Y, areaTrigger->target_Z, GetOrientation());
if (mapId != areaTrigger->target_mapId)
{
mapId = areaTrigger->target_mapId;
map = sMapMgr->CreateMap(mapId, this);
}
}
}
if (!map)
{
RelocateToHomebind();
map = sMapMgr->CreateMap(mapId, this);
if (!map)
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player '%s' (%s) Map: %u, X: %f, Y: %f, Z: %f, O: %f. Invalid default map coordinates or instance couldn't be created.",
m_name.c_str(), guid.ToString().c_str(), mapId, GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
return false;
}
}
SetMap(map);
UpdatePositionData();
// now that map position is determined, check instance validity
if (!CheckInstanceValidity(true) && !IsInstanceLoginGameMasterException())
m_InstanceValid = false;
if (player_at_bg)
map->ToBattlegroundMap()->GetBG()->AddPlayer(this);
// randomize first save time in range [CONFIG_INTERVAL_SAVE] around [CONFIG_INTERVAL_SAVE]
// this must help in case next save after mass player load after server startup
m_nextSave = urand(m_nextSave / 2, m_nextSave * 3 / 2);
SaveRecallPosition();
time_t now = GameTime::GetGameTime();
time_t logoutTime = time_t(fields.logout_time);
// since last logout (in seconds)
uint32 time_diff = uint32(now - logoutTime); //uint64 is excessive for a time_diff in seconds.. uint32 allows for 136~ year difference.
// set value, including drunk invisibility detection
// calculate sobering. after 15 minutes logged out, the player will be sober again
if (time_diff < uint32(GetDrunkValue()) * 9)
SetDrunkValue(GetDrunkValue() - time_diff / 9);
else
SetDrunkValue(0);
m_createTime = fields.createTime;
m_createMode = fields.createMode;
m_cinematic = fields.cinematic;
m_Played_time[PLAYED_TIME_TOTAL] = fields.totaltime;
m_Played_time[PLAYED_TIME_LEVEL] = fields.leveltime;
SetTalentResetCost(fields.resettalents_cost);
SetTalentResetTime(fields.resettalents_time);
m_taxi.LoadTaxiMask(fields.taximask); // must be before InitTaxiNodesForLevel
uint32 extraflags = fields.extra_flags;
m_stableSlots = fields.stable_slots;
if (m_stableSlots > MAX_PET_STABLES)
{
TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) can't have more stable slots than %u, but has %u in DB",
GetGUID().ToString().c_str(), MAX_PET_STABLES, uint32(m_stableSlots));
m_stableSlots = MAX_PET_STABLES;
}
if (HasAtLoginFlag(AT_LOGIN_RENAME))
{
TC_LOG_ERROR("entities.player.cheat", "Player::LoadFromDB: Player (%s) tried to login while forced to rename, can't load.'", GetGUID().ToString().c_str());
return false;
}
// Honor system
// Update Honor kills data
m_lastHonorUpdateTime = logoutTime;
UpdateHonorFields();
m_deathExpireTime = fields.death_expire_time;
if (m_deathExpireTime > now + MAX_DEATH_COUNT * DEATH_EXPIRE_STEP)
m_deathExpireTime = now + MAX_DEATH_COUNT * DEATH_EXPIRE_STEP - 1;
RemoveUnitFlag2(UNIT_FLAG2_FORCE_MOVEMENT);
// make sure the unit is considered out of combat for proper loading
ClearInCombat();
// reset stats before loading any modifiers
InitStatsForLevel();
InitTaxiNodesForLevel();
InitRunes();
// rest bonus can only be calculated after InitStatsForLevel()
_restMgr->LoadRestBonus(REST_TYPE_XP, fields.restState, fields.rest_bonus);
// load skills after InitStatsForLevel because it triggering aura apply also
_LoadSkills(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SKILLS));
UpdateSkillsForLevel(); //update skills after load, to make sure they are correctly update at player load
SetNumRespecs(fields.numRespecs);
SetPrimarySpecialization(fields.primarySpecialization);
SetActiveTalentGroup(fields.activeTalentGroup);
ChrSpecializationEntry const* primarySpec = sChrSpecializationStore.LookupEntry(GetPrimarySpecialization());
if (!primarySpec || primarySpec->ClassID != GetClass() || GetActiveTalentGroup() >= MAX_SPECIALIZATIONS)
ResetTalentSpecialization();
uint32 lootSpecId = fields.lootSpecId;
if (ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(lootSpecId))
if (chrSpec->ClassID == GetClass())
SetLootSpecId(lootSpecId);
UpdateDisplayPower();
_LoadTalents(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TALENTS));
_LoadPvpTalents(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_PVP_TALENTS));
_LoadSpells(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELLS));
GetSession()->GetCollectionMgr()->LoadToys();
GetSession()->GetCollectionMgr()->LoadHeirlooms();
GetSession()->GetCollectionMgr()->LoadMounts();
GetSession()->GetCollectionMgr()->LoadItemAppearances();
LearnSpecializationSpells();
_LoadGlyphs(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_GLYPHS));
_LoadAuras(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_AURAS), holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_AURA_EFFECTS), time_diff);
_LoadGlyphAuras();
// add ghost flag (must be after aura load: PLAYER_FLAGS_GHOST set in aura)
if (HasPlayerFlag(PLAYER_FLAGS_GHOST))
m_deathState = DEAD;
// Load spell locations - must be after loading auras
_LoadStoredAuraTeleportLocations(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_AURA_STORED_LOCATIONS));
// after spell load, learn rewarded spell if need also
_LoadQuestStatus(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS));
_LoadQuestStatusObjectives(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_OBJECTIVES));
_LoadQuestStatusRewarded(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_REW));
_LoadDailyQuestStatus(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_DAILY_QUEST_STATUS));
_LoadWeeklyQuestStatus(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_WEEKLY_QUEST_STATUS));
_LoadSeasonalQuestStatus(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SEASONAL_QUEST_STATUS));
_LoadMonthlyQuestStatus(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MONTHLY_QUEST_STATUS));
_LoadRandomBGStatus(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_RANDOM_BG));
// after spell and quest load
InitTalentForLevel();
LearnDefaultSkills();
LearnCustomSpells();
// must be before inventory (some items required reputation check)
m_reputationMgr->LoadFromDB(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_REPUTATION));
_LoadInventory(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_INVENTORY),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ARTIFACTS),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_AZERITE),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_AZERITE_MILESTONE_POWERS),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_AZERITE_UNLOCKED_ESSENCES),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_AZERITE_EMPOWERED),
time_diff);
if (IsVoidStorageUnlocked())
_LoadVoidStorage(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_VOID_STORAGE));
// update items with duration and realtime
UpdateItemDuration(time_diff, true);
_LoadActions(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ACTIONS));
// unread mails and next delivery time, actual mails not loaded
_LoadMail(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAILS),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS_ARTIFACT),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS_AZERITE),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS_AZERITE_MILESTONE_POWER),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS_AZERITE_UNLOCKED_ESSENCE),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS_AZERITE_EMPOWERED));
m_social = sSocialMgr->LoadFromDB(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SOCIAL_LIST), GetGUID());
// check PLAYER_CHOSEN_TITLE compatibility with PLAYER__FIELD_KNOWN_TITLES
// note: PLAYER__FIELD_KNOWN_TITLES updated at quest status loaded
uint32 curTitle = fields.chosenTitle;
if (curTitle && !HasTitle(curTitle))
curTitle = 0;
SetChosenTitle(curTitle);
// has to be called after last Relocate() in Player::LoadFromDB
SetFallInformation(0, GetPositionZ());
GetSpellHistory()->LoadFromDB<Player>(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELL_COOLDOWNS), holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELL_CHARGES));
uint32 savedHealth = fields.health;
if (!savedHealth)
m_deathState = CORPSE;
// Spell code allow apply any auras to dead character in load time in aura/spell/item loading
// Do now before stats re-calculation cleanup for ghost state unexpected auras
if (!IsAlive())
RemoveAllAurasOnDeath();
else
RemoveAllAurasRequiringDeadTarget();
//apply all stat bonuses from items and auras
SetCanModifyStats(true);
UpdateAllStats();
// restore remembered power/health values (but not more max values)
SetHealth(savedHealth > GetMaxHealth() ? GetMaxHealth() : savedHealth);
uint32 loadedPowers = 0;
for (uint32 i = 0; i < MAX_POWERS; ++i)
{
if (GetPowerIndex(Powers(i)) != MAX_POWERS)
{
uint32 savedPower = fields.powers[loadedPowers];
uint32 maxPower = m_unitData->MaxPower[loadedPowers];
SetPower(Powers(i), (savedPower > maxPower) ? maxPower : savedPower);
if (++loadedPowers >= MAX_POWERS_PER_CLASS)
break;
}
}
for (; loadedPowers < MAX_POWERS_PER_CLASS; ++loadedPowers)
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Power, loadedPowers), 0);
SetPower(POWER_LUNAR_POWER, 0);
// Init rune recharge
if (GetPowerIndex(POWER_RUNES) != MAX_POWERS)
{
int32 runes = GetPower(POWER_RUNES);
int32 maxRunes = GetMaxPower(POWER_RUNES);
uint32 runeCooldown = GetRuneBaseCooldown();
while (runes < maxRunes)
{
SetRuneCooldown(runes, runeCooldown);
++runes;
}
}
TC_LOG_DEBUG("entities.player.loading", "Player::LoadFromDB: The value of player '%s' after load item and aura is: ", m_name.c_str());
outDebugValues();
// GM state
if (GetSession()->HasPermission(rbac::RBAC_PERM_RESTORE_SAVED_GM_STATE))
{
switch (sWorld->getIntConfig(CONFIG_GM_LOGIN_STATE))
{
default:
case 0: break; // disable
case 1: SetGameMaster(true); break; // enable
case 2: // save state
if (extraflags & PLAYER_EXTRA_GM_ON)
SetGameMaster(true);
break;
}
switch (sWorld->getIntConfig(CONFIG_GM_VISIBLE_STATE))
{
default:
case 0: SetGMVisible(false); break; // invisible
case 1: break; // visible
case 2: // save state
if (extraflags & PLAYER_EXTRA_GM_INVISIBLE)
SetGMVisible(false);
break;
}
switch (sWorld->getIntConfig(CONFIG_GM_CHAT))
{
default:
case 0: break; // disable
case 1: SetGMChat(true); break; // enable
case 2: // save state
if (extraflags & PLAYER_EXTRA_GM_CHAT)
SetGMChat(true);
break;
}
switch (sWorld->getIntConfig(CONFIG_GM_WHISPERING_TO))
{
default:
case 0: break; // disable
case 1: SetAcceptWhispers(true); break; // enable
case 2: // save state
if (extraflags & PLAYER_EXTRA_ACCEPT_WHISPERS)
SetAcceptWhispers(true);
break;
}
}
InitPvP();
// RaF stuff.
if (GetSession()->IsARecruiter() || (GetSession()->GetRecruiterId() != 0))
AddDynamicFlag(UNIT_DYNFLAG_REFER_A_FRIEND);
_LoadDeclinedNames(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_DECLINED_NAMES));
_LoadEquipmentSets(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_EQUIPMENT_SETS));
_LoadTransmogOutfits(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TRANSMOG_OUTFITS));
_LoadCUFProfiles(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_CUF_PROFILES));
std::unique_ptr<Garrison> garrison = std::make_unique<Garrison>(this);
if (garrison->LoadFromDB(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_GARRISON),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_GARRISON_BLUEPRINTS),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_GARRISON_BUILDINGS),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_GARRISON_FOLLOWERS),
holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_GARRISON_FOLLOWER_ABILITIES)))
_garrison = std::move(garrison);
_InitHonorLevelOnLoadFromDB(fields.honor, fields.honorLevel);
_restMgr->LoadRestBonus(REST_TYPE_HONOR, fields.honorRestState, fields.honorRestBonus);
if (time_diff > 0)
{
//speed collect rest bonus in offline, in logout, far from tavern, city (section/in hour)
float bubble0 = 0.031f;
//speed collect rest bonus in offline, in logout, in tavern, city (section/in hour)
float bubble1 = 0.125f;
float bubble = fields.is_logout_resting > 0
? bubble1 * sWorld->getRate(RATE_REST_OFFLINE_IN_TAVERN_OR_CITY)
: bubble0 * sWorld->getRate(RATE_REST_OFFLINE_IN_WILDERNESS);
_restMgr->AddRestBonus(REST_TYPE_XP, time_diff * _restMgr->CalcExtraPerSec(REST_TYPE_XP, bubble));
}
// Unlock battle pet system if it's enabled in bnet account
if (GetSession()->GetBattlePetMgr()->IsBattlePetSystemEnabled())
LearnSpell(BattlePets::SPELL_BATTLE_PET_TRAINING, false);
m_achievementMgr->CheckAllAchievementCriteria(this);
m_questObjectiveCriteriaMgr->CheckAllQuestObjectiveCriteria(this);
PushQuests();
return true;
}
void Player::PushQuests()
{
for (Quest const* quest : sObjectMgr->GetQuestTemplatesAutoPush())
{
if (quest->GetQuestTag() && quest->GetQuestTag() != QuestTagType::Tag)
continue;
if (!quest->IsUnavailable() && CanTakeQuest(quest, false))
AddQuestAndCheckCompletion(quest, nullptr);
}
}
void Player::_LoadCUFProfiles(PreparedQueryResult result)
{
if (!result)
return;
do
{
// SELECT id, name, frameHeight, frameWidth, sortBy, healthText, boolOptions, unk146, unk147, unk148, unk150, unk152, unk154 FROM character_cuf_profiles WHERE guid = ?
Field* fields = result->Fetch();
uint8 id = fields[0].GetUInt8();
std::string name = fields[1].GetString();
uint16 frameHeight = fields[2].GetUInt16();
uint16 frameWidth = fields[3].GetUInt16();
uint8 sortBy = fields[4].GetUInt8();
uint8 healthText = fields[5].GetUInt8();
uint32 boolOptions = fields[6].GetUInt32();
uint8 topPoint = fields[7].GetUInt8();
uint8 bottomPoint = fields[8].GetUInt8();
uint8 leftPoint = fields[9].GetUInt8();
uint16 topOffset = fields[10].GetUInt16();
uint16 bottomOffset = fields[11].GetUInt16();
uint16 leftOffset = fields[12].GetUInt16();
if (id > MAX_CUF_PROFILES)
{
TC_LOG_ERROR("entities.player", "Player::_LoadCUFProfiles: Player '%s' (%s) has an CUF profile with invalid id (ID: %u), max is %i.", GetName().c_str(), GetGUID().ToString().c_str(), id, MAX_CUF_PROFILES);
continue;
}
_CUFProfiles[id] = std::make_unique<CUFProfile>(name, frameHeight, frameWidth, sortBy, healthText, boolOptions, topPoint, bottomPoint, leftPoint, topOffset, bottomOffset, leftOffset);
}
while (result->NextRow());
}
bool Player::isAllowedToLoot(const Creature* creature) const
{
if (!creature->isDead() || !creature->IsDamageEnoughForLootingAndReward())
return false;
if (HasPendingBind())
return false;
Loot const* loot = &creature->loot;
if (loot->isLooted()) // nothing to loot or everything looted.
return false;
if (!loot->hasItemForAll() && !loot->hasItemFor(this)) // no loot in creature for this player
return false;
if (loot->loot_type == LOOT_SKINNING)
return creature->GetLootRecipientGUID() == GetGUID();
Group const* thisGroup = GetGroup();
if (!thisGroup)
return this == creature->GetLootRecipient();
else if (thisGroup != creature->GetLootRecipientGroup())
return false;
switch (thisGroup->GetLootMethod())
{
case PERSONAL_LOOT: /// @todo implement personal loot (http://wow.gamepedia.com/Loot#Personal_Loot)
return false;
case MASTER_LOOT:
case FREE_FOR_ALL:
return true;
case GROUP_LOOT:
// may only loot if the player is the loot roundrobin player
// or item over threshold (so roll(s) can be launched)
// or if there are free/quest/conditional item for the player
if (loot->roundRobinPlayer.IsEmpty() || loot->roundRobinPlayer == GetGUID())
return true;
if (loot->hasOverThresholdItem())
return true;
return loot->hasItemFor(this);
}
return false;
}
void Player::_LoadActions(PreparedQueryResult result)
{
m_actionButtons.clear();
if (result)
{
do
{
Field* fields = result->Fetch();
uint8 button = fields[0].GetUInt8();
uint32 action = fields[1].GetUInt32();
uint8 type = fields[2].GetUInt8();
if (ActionButton* ab = AddActionButton(button, action, type))
ab->uState = ACTIONBUTTON_UNCHANGED;
else
{
TC_LOG_DEBUG("entities.player", "Player::_LoadActions: Player '%s' (%s) has an invalid action button (Button: %u, Action: %u, Type: %u). It will be deleted at next save. This can be due to a player changing their talents.",
GetName().c_str(), GetGUID().ToString().c_str(), button, action, type);
// Will be deleted in DB at next save (it can create data until save but marked as deleted).
m_actionButtons[button].uState = ACTIONBUTTON_DELETED;
}
} while (result->NextRow());
}
}
void Player::_LoadAuras(PreparedQueryResult auraResult, PreparedQueryResult effectResult, uint32 timediff)
{
TC_LOG_DEBUG("entities.player.loading", "Player::_LoadAuras: Loading auras for %s", GetGUID().ToString().c_str());
/*
0 1 2 3 4 5 6
SELECT casterGuid, itemGuid, spell, effectMask, effectIndex, amount, baseAmount FROM character_aura_effect WHERE guid = ?
*/
ObjectGuid casterGuid, itemGuid;
std::map<AuraKey, AuraLoadEffectInfo> effectInfo;
if (effectResult)
{
do
{
Field* fields = effectResult->Fetch();
uint32 effectIndex = fields[4].GetUInt8();
if (effectIndex < MAX_SPELL_EFFECTS)
{
casterGuid.SetRawValue(fields[0].GetBinary());
itemGuid.SetRawValue(fields[1].GetBinary());
AuraKey key{ casterGuid, itemGuid, fields[2].GetUInt32(), fields[3].GetUInt32() };
AuraLoadEffectInfo& info = effectInfo[key];
info.Amounts[effectIndex] = fields[5].GetInt32();
info.BaseAmounts[effectIndex] = fields[6].GetInt32();
}
}
while (effectResult->NextRow());
}
/*
0 1 2 3 4 5 6 7 8 9 10 11
SELECT casterGuid, itemGuid, spell, effectMask, recalculateMask, difficulty, stackCount, maxDuration, remainTime, remainCharges, castItemId, castItemLevel FROM character_aura WHERE guid = ?
*/
if (auraResult)
{
do
{
Field* fields = auraResult->Fetch();
casterGuid.SetRawValue(fields[0].GetBinary());
itemGuid.SetRawValue(fields[1].GetBinary());
AuraKey key{ casterGuid, itemGuid, fields[2].GetUInt32(), fields[3].GetUInt32() };
uint32 recalculateMask = fields[4].GetUInt32();
Difficulty difficulty = Difficulty(fields[5].GetUInt8());
uint8 stackCount = fields[6].GetUInt8();
int32 maxDuration = fields[7].GetInt32();
int32 remainTime = fields[8].GetInt32();
uint8 remainCharges = fields[9].GetUInt8();
uint32 castItemId = fields[10].GetUInt32();
int32 castItemLevel = fields[11].GetInt32();
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(key.SpellId, difficulty);
if (!spellInfo)
{
TC_LOG_ERROR("entities.player", "Player::_LoadAuras: Player '%s' (%s) has an invalid aura (SpellID: %u), ignoring.",
GetName().c_str(), GetGUID().ToString().c_str(), key.SpellId);
continue;
}
if (difficulty != DIFFICULTY_NONE && !sDifficultyStore.LookupEntry(difficulty))
{
TC_LOG_ERROR("entities.player", "Player::_LoadAuras: Player '%s' (%s) has an invalid aura difficulty %u (SpellID: %u), ignoring.",
GetName().c_str(), GetGUID().ToString().c_str(), uint32(difficulty), key.SpellId);
continue;
}
// negative effects should continue counting down after logout
if (remainTime != -1 && (!spellInfo->IsPositive() || spellInfo->HasAttribute(SPELL_ATTR4_AURA_EXPIRES_OFFLINE)))
{
if (remainTime/IN_MILLISECONDS <= int32(timediff))
continue;
remainTime -= timediff*IN_MILLISECONDS;
}
// prevent wrong values of remainCharges
if (spellInfo->ProcCharges)
{
// we have no control over the order of applying auras and modifiers allow auras
// to have more charges than value in SpellInfo
if (remainCharges <= 0/* || remainCharges > spellproto->procCharges*/)
remainCharges = spellInfo->ProcCharges;
}
else
remainCharges = 0;
AuraLoadEffectInfo& info = effectInfo[key];
ObjectGuid castId = ObjectGuid::Create<HighGuid::Cast>(SPELL_CAST_SOURCE_NORMAL, GetMapId(), spellInfo->Id, GetMap()->GenerateLowGuid<HighGuid::Cast>());
AuraCreateInfo createInfo(castId, spellInfo, difficulty, key.EffectMask, this);
createInfo
.SetCasterGUID(casterGuid)
.SetBaseAmount(info.BaseAmounts.data())
.SetCastItem(itemGuid, castItemId, castItemLevel);
if (Aura* aura = Aura::TryCreate(createInfo))
{
if (!aura->CanBeSaved())
{
aura->Remove();
continue;
}
aura->SetLoadedState(maxDuration, remainTime, remainCharges, stackCount, recalculateMask, info.Amounts.data());
aura->ApplyForTargets();
TC_LOG_DEBUG("entities.player", "Player::_LoadAuras: Added aura (SpellID: %u, EffectMask: %u) to player '%s (%s)",
spellInfo->Id, key.EffectMask, GetName().c_str(), GetGUID().ToString().c_str());
}
}
while (auraResult->NextRow());
}
}
void Player::_LoadGlyphAuras()
{
for (uint32 glyphId : GetGlyphs(GetActiveTalentGroup()))
CastSpell(this, sGlyphPropertiesStore.AssertEntry(glyphId)->SpellID, true);
}
void Player::LoadCorpse(PreparedQueryResult result)
{
if (IsAlive() || HasAtLoginFlag(AT_LOGIN_RESURRECT))
SpawnCorpseBones(false);
if (!IsAlive())
{
if (HasAtLoginFlag(AT_LOGIN_RESURRECT))
ResurrectPlayer(0.5f);
else if (result)
{
Field* fields = result->Fetch();
_corpseLocation.WorldRelocate(fields[0].GetUInt16(), fields[1].GetFloat(), fields[2].GetFloat(), fields[3].GetFloat(), fields[4].GetFloat());
if (!sMapStore.AssertEntry(_corpseLocation.GetMapId())->Instanceable())
AddPlayerLocalFlag(PLAYER_LOCAL_FLAG_RELEASE_TIMER);
else
RemovePlayerLocalFlag(PLAYER_LOCAL_FLAG_RELEASE_TIMER);
}
}
RemoveAtLoginFlag(AT_LOGIN_RESURRECT);
}
void Player::_LoadInventory(PreparedQueryResult result, PreparedQueryResult artifactsResult, PreparedQueryResult azeriteResult,
PreparedQueryResult azeriteItemMilestonePowersResult, PreparedQueryResult azeriteItemUnlockedEssencesResult,
PreparedQueryResult azeriteEmpoweredItemResult, uint32 timeDiff)
{
// 0 1 2 3 4 5 6 7 8 9 10 11 12
// SELECT guid, itemEntry, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text,
// 13 14 15 16 17 18
// battlePetSpeciesId, battlePetBreedData, battlePetLevel, battlePetDisplayId, context, bonusListIDs,
// 19 20 21 22 23
// itemModifiedAppearanceAllSpecs, itemModifiedAppearanceSpec1, itemModifiedAppearanceSpec2, itemModifiedAppearanceSpec3, itemModifiedAppearanceSpec4,
// 24 25 26 27 28
// spellItemEnchantmentAllSpecs, spellItemEnchantmentSpec1, spellItemEnchantmentSpec2, spellItemEnchantmentSpec3, spellItemEnchantmentSpec4,
// 29 30 31 32 33 34 35 36 37 38 39 40
// gemItemId1, gemBonuses1, gemContext1, gemScalingLevel1, gemItemId2, gemBonuses2, gemContext2, gemScalingLevel2, gemItemId3, gemBonuses3, gemContext3, gemScalingLevel3
// 41 42
// fixedScalingLevel, artifactKnowledgeLevel FROM item_instance
// 43 44
// bag, slot
// FROM character_inventory ci
// JOIN item_instance ii ON ci.item = ii.guid
// LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid
// LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid
// LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid
// WHERE ci.guid = ?
// ORDER BY (ii.flags & 0x80000) ASC, bag ASC, slot ASC
//NOTE: ORDER BY ii.flags & 0x80000 makes child items load last - they need their parents to be already loaded
//NOTE: the "order by `bag`" is important because it makes sure
//the bagMap is filled before items in the bags are loaded
//NOTE2: the "order by `slot`" is needed because mainhand weapons are (wrongly?)
//expected to be equipped before offhand items (@todo fixme)
std::unordered_map<ObjectGuid::LowType, ItemAdditionalLoadInfo> additionalData;
ItemAdditionalLoadInfo::Init(&additionalData, artifactsResult, azeriteResult, azeriteItemMilestonePowersResult,
azeriteItemUnlockedEssencesResult, azeriteEmpoweredItemResult);
if (result)
{
uint32 zoneId = GetZoneId();
std::map<ObjectGuid, Bag*> bagMap; // fast guid lookup for bags
std::map<ObjectGuid, Item*> invalidBagMap; // fast guid lookup for bags
std::list<Item*> problematicItems;
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
// Prevent items from being added to the queue while loading
m_itemUpdateQueueBlocked = true;
do
{
Field* fields = result->Fetch();
if (Item* item = _LoadItem(trans, zoneId, timeDiff, fields))
{
if (ItemAdditionalLoadInfo* addionalDataPtr = Trinity::Containers::MapGetValuePtr(additionalData, fields[0].GetUInt64()))
{
if (item->GetTemplate()->GetArtifactID() && addionalDataPtr->Artifact)
item->LoadArtifactData(this, addionalDataPtr->Artifact->Xp, addionalDataPtr->Artifact->ArtifactAppearanceId,
addionalDataPtr->Artifact->ArtifactTierId, addionalDataPtr->Artifact->ArtifactPowers);
if (addionalDataPtr->AzeriteItem)
if (AzeriteItem* azeriteItem = item->ToAzeriteItem())
azeriteItem->LoadAzeriteItemData(this, *addionalDataPtr->AzeriteItem);
if (addionalDataPtr->AzeriteEmpoweredItem)
if (AzeriteEmpoweredItem* azeriteEmpoweredItem = item->ToAzeriteEmpoweredItem())
azeriteEmpoweredItem->LoadAzeriteEmpoweredItemData(this, *addionalDataPtr->AzeriteEmpoweredItem);
}
ObjectGuid bagGuid = fields[51].GetUInt64() ? ObjectGuid::Create<HighGuid::Item>(fields[51].GetUInt64()) : ObjectGuid::Empty;
uint8 slot = fields[52].GetUInt8();
GetSession()->GetCollectionMgr()->CheckHeirloomUpgrades(item);
GetSession()->GetCollectionMgr()->AddItemAppearance(item);
InventoryResult err = EQUIP_ERR_OK;
if (item->HasItemFlag(ITEM_FIELD_FLAG_CHILD))
{
if (Item* parent = GetItemByGuid(item->GetCreator()))
{
parent->SetChildItem(item->GetGUID());
item->CopyArtifactDataFromParent(parent);
}
else
{
TC_LOG_ERROR("entities.player", "Player::_LoadInventory: Player '%s' (%s) has child item (%s, entry: %u) which can't be loaded into inventory because parent item was not found (Bag %s, slot: %u). Item will be sent by mail.",
GetName().c_str(), GetGUID().ToString().c_str(), item->GetGUID().ToString().c_str(), item->GetEntry(), bagGuid.ToString().c_str(), slot);
item->DeleteFromInventoryDB(trans);
problematicItems.push_back(item);
continue;
}
}
// Item is not in bag
if (!bagGuid)
{
item->SetContainer(nullptr);
item->SetSlot(slot);
if (IsInventoryPos(INVENTORY_SLOT_BAG_0, slot))
{
ItemPosCountVec dest;
err = CanStoreItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false);
if (err == EQUIP_ERR_OK)
item = StoreItem(dest, item, true);
}
else if (IsEquipmentPos(INVENTORY_SLOT_BAG_0, slot))
{
uint16 dest;
err = CanEquipItem(slot, dest, item, false, false);
if (err == EQUIP_ERR_OK)
QuickEquipItem(dest, item);
}
else if (IsBankPos(INVENTORY_SLOT_BAG_0, slot))
{
ItemPosCountVec dest;
err = CanBankItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false, false);
if (err == EQUIP_ERR_OK)
item = BankItem(dest, item, true);
}
// Remember bags that may contain items in them
if (err == EQUIP_ERR_OK)
{
if (IsBagPos(item->GetPos()))
if (Bag* pBag = item->ToBag())
bagMap[item->GetGUID()] = pBag;
}
else
if (IsBagPos(item->GetPos()))
if (item->IsBag())
invalidBagMap[item->GetGUID()] = item;
}
else
{
item->SetSlot(NULL_SLOT);
// Item is in the bag, find the bag
std::map<ObjectGuid, Bag*>::iterator itr = bagMap.find(bagGuid);
if (itr != bagMap.end())
{
ItemPosCountVec dest;
err = CanStoreItem(itr->second->GetSlot(), slot, dest, item);
if (err == EQUIP_ERR_OK)
item = StoreItem(dest, item, true);
}
else if (invalidBagMap.find(bagGuid) != invalidBagMap.end())
{
std::map<ObjectGuid, Item*>::iterator invalidBagItr = invalidBagMap.find(bagGuid);
if (std::find(problematicItems.begin(), problematicItems.end(), invalidBagItr->second) != problematicItems.end())
err = EQUIP_ERR_INTERNAL_BAG_ERROR;
}
else
{
TC_LOG_ERROR("entities.player", "Player::_LoadInventory: Player '%s' (%s) has item (%s, entry: %u) which doesnt have a valid bag (Bag %s, slot: %u). Possible cheat?",
GetName().c_str(), GetGUID().ToString().c_str(), item->GetGUID().ToString().c_str(), item->GetEntry(), bagGuid.ToString().c_str(), slot);
item->DeleteFromInventoryDB(trans);
delete item;
continue;
}
}
// Item's state may have changed after storing
if (err == EQUIP_ERR_OK)
item->SetState(ITEM_UNCHANGED, this);
else
{
TC_LOG_ERROR("entities.player", "Player::_LoadInventory: Player '%s' (%s) has item (%s, entry: %u) which can't be loaded into inventory (Bag %s, slot: %u) by reason %u. Item will be sent by mail.",
GetName().c_str(), GetGUID().ToString().c_str(), item->GetGUID().ToString().c_str(), item->GetEntry(), bagGuid.ToString().c_str(), slot, uint32(err));
item->DeleteFromInventoryDB(trans);
problematicItems.push_back(item);
}
}
} while (result->NextRow());
m_itemUpdateQueueBlocked = false;
// Send problematic items by mail
while (!problematicItems.empty())
{
std::string subject = GetSession()->GetTrinityString(LANG_NOT_EQUIPPED_ITEM);
MailDraft draft(subject, "There were problems with equipping item(s).");
for (uint8 i = 0; !problematicItems.empty() && i < MAX_MAIL_ITEMS; ++i)
{
draft.AddItem(problematicItems.front());
problematicItems.pop_front();
}
draft.SendMailTo(trans, this, MailSender(this, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_COPIED);
}
CharacterDatabase.CommitTransaction(trans);
}
//if (IsAlive())
_ApplyAllItemMods();
// Apply all azerite item mods, azerite empowered item mods will get applied through its spell script
ApplyAllAzeriteItemMods(true);
}
void Player::_LoadVoidStorage(PreparedQueryResult result)
{
if (!result)
return;
do
{
// SELECT itemId, itemEntry, slot, creatorGuid, randomBonusListId, fixedScalingLevel, artifactKnowledgeLevel, context, bonusListIDs FROM character_void_storage WHERE playerGuid = ?
Field* fields = result->Fetch();
uint64 itemId = fields[0].GetUInt64();
uint32 itemEntry = fields[1].GetUInt32();
uint8 slot = fields[2].GetUInt8();
ObjectGuid creatorGuid = fields[3].GetUInt64() ? ObjectGuid::Create<HighGuid::Player>(fields[3].GetUInt64()) : ObjectGuid::Empty;
ItemRandomBonusListId randomBonusListId = fields[4].GetUInt32();
uint32 fixedScalingLevel = fields[5].GetUInt32();
uint32 artifactKnowledgeLevel = fields[6].GetUInt32();
ItemContext context = ItemContext(fields[7].GetUInt8());
std::vector<int32> bonusListIDs;
Tokenizer bonusListIdTokens(fields[8].GetString(), ' ');
for (char const* token : bonusListIdTokens)
bonusListIDs.push_back(atoul(token));
if (!itemId)
{
TC_LOG_ERROR("entities.player", "Player::_LoadVoidStorage: Player '%s' (%s) has an item with an invalid id (item id: " UI64FMTD ", entry: %u).",
GetName().c_str(), GetGUID().ToString().c_str(), itemId, itemEntry);
continue;
}
if (!sObjectMgr->GetItemTemplate(itemEntry))
{
TC_LOG_ERROR("entities.player", "Player::_LoadVoidStorage: Player '%s' (%s) has an item with an invalid entry (item id: " UI64FMTD ", entry: %u).",
GetName().c_str(), GetGUID().ToString().c_str(), itemId, itemEntry);
continue;
}
if (slot >= VOID_STORAGE_MAX_SLOT)
{
TC_LOG_ERROR("entities.player", "Player::_LoadVoidStorage: Player '%s' (%s) has an item with an invalid slot (item id: " UI64FMTD ", entry: %u, slot: %u).",
GetName().c_str(), GetGUID().ToString().c_str(), itemId, itemEntry, slot);
continue;
}
_voidStorageItems[slot] = new VoidStorageItem(itemId, itemEntry, creatorGuid, randomBonusListId, fixedScalingLevel, artifactKnowledgeLevel,
context, bonusListIDs);
WorldPackets::Item::ItemInstance voidInstance;
voidInstance.Initialize(_voidStorageItems[slot]);
BonusData bonus;
bonus.Initialize(voidInstance);
GetSession()->GetCollectionMgr()->AddItemAppearance(itemEntry, bonus.AppearanceModID);
}
while (result->NextRow());
}
Item* Player::_LoadItem(CharacterDatabaseTransaction& trans, uint32 zoneId, uint32 timeDiff, Field* fields)
{
Item* item = nullptr;
ObjectGuid::LowType itemGuid = fields[0].GetUInt64();
uint32 itemEntry = fields[1].GetUInt32();
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry))
{
bool remove = false;
item = NewItemOrBag(proto);
if (item->LoadFromDB(itemGuid, GetGUID(), fields, itemEntry))
{
CharacterDatabasePreparedStatement* stmt;
// Do not allow to have item limited to another map/zone in alive state
if (IsAlive() && item->IsLimitedToAnotherMapOrZone(GetMapId(), zoneId))
{
TC_LOG_DEBUG("entities.player.loading", "Player::_LoadInventory: player (%s, name: '%s', map: %u) has item (%s) limited to another map (%u). Deleting item.",
GetGUID().ToString().c_str(), GetName().c_str(), GetMapId(), item->GetGUID().ToString().c_str(), zoneId);
remove = true;
}
// "Conjured items disappear if you are logged out for more than 15 minutes"
else if (timeDiff > 15 * MINUTE && proto->HasFlag(ITEM_FLAG_CONJURED))
{
TC_LOG_DEBUG("entities.player.loading", "Player::_LoadInventory: player (%s, name: '%s', diff: %u) has conjured item (%s) with expired lifetime (15 minutes). Deleting item.",
GetGUID().ToString().c_str(), GetName().c_str(), timeDiff, item->GetGUID().ToString().c_str());
remove = true;
}
else if (item->IsRefundable())
{
if (item->GetPlayedTime() > (2 * HOUR))
{
TC_LOG_DEBUG("entities.player.loading", "Player::_LoadInventory: player (%s, name: '%s') has item (%s) with expired refund time (%u). Deleting refund data and removing refundable flag.",
GetGUID().ToString().c_str(), GetName().c_str(), item->GetGUID().ToString().c_str(), item->GetPlayedTime());
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_REFUND_INSTANCE);
stmt->setUInt64(0, item->GetGUID().GetCounter());
trans->Append(stmt);
item->RemoveItemFlag(ITEM_FIELD_FLAG_REFUNDABLE);
}
else
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEM_REFUNDS);
stmt->setUInt64(0, item->GetGUID().GetCounter());
stmt->setUInt64(1, GetGUID().GetCounter());
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
{
item->SetRefundRecipient(GetGUID());
item->SetPaidMoney((*result)[0].GetUInt64());
item->SetPaidExtendedCost((*result)[1].GetUInt16());
AddRefundReference(item->GetGUID());
}
else
{
TC_LOG_DEBUG("entities.player.loading", "Player::_LoadInventory: player (%s, name: '%s') has item (%s) with refundable flags, but without data in item_refund_instance. Removing flag.",
GetGUID().ToString().c_str(), GetName().c_str(), item->GetGUID().ToString().c_str());
item->RemoveItemFlag(ITEM_FIELD_FLAG_REFUNDABLE);
}
}
}
else if (item->IsBOPTradeable())
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEM_BOP_TRADE);
stmt->setUInt64(0, item->GetGUID().GetCounter());
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
{
std::string strGUID = (*result)[0].GetString();
Tokenizer GUIDlist(strGUID, ' ');
GuidSet looters;
for (Tokenizer::const_iterator itr = GUIDlist.begin(); itr != GUIDlist.end(); ++itr)
looters.insert(ObjectGuid::Create<HighGuid::Player>(uint64(strtoull(*itr, nullptr, 10))));
if (looters.size() > 1 && item->GetTemplate()->GetMaxStackSize() == 1 && item->IsSoulBound())
{
item->SetSoulboundTradeable(looters);
AddTradeableItem(item);
}
else
item->ClearSoulboundTradeable(this);
}
else
{
TC_LOG_DEBUG("entities.player.loading", "Player::_LoadInventory: player (%s, name: '%s') has item (%s) with ITEM_FLAG_BOP_TRADEABLE flag, but without data in item_soulbound_trade_data. Removing flag.",
GetGUID().ToString().c_str(), GetName().c_str(), item->GetGUID().ToString().c_str());
item->RemoveItemFlag(ITEM_FIELD_FLAG_BOP_TRADEABLE);
}
}
else if (proto->GetHolidayID())
{
remove = true;
GameEventMgr::GameEventDataMap const& events = sGameEventMgr->GetEventMap();
GameEventMgr::ActiveEvents const& activeEventsList = sGameEventMgr->GetActiveEventList();
for (GameEventMgr::ActiveEvents::const_iterator itr = activeEventsList.begin(); itr != activeEventsList.end(); ++itr)
{
if (events[*itr].holiday_id == proto->GetHolidayID())
{
remove = false;
break;
}
}
}
}
else
{
TC_LOG_ERROR("entities.player", "Player::_LoadInventory: player (%s, name: '%s') has a broken item (GUID: " UI64FMTD ", entry: %u) in inventory. Deleting item.",
GetGUID().ToString().c_str(), GetName().c_str(), itemGuid, itemEntry);
remove = true;
}
// Remove item from inventory if necessary
if (remove)
{
Item::DeleteFromInventoryDB(trans, itemGuid);
item->FSetState(ITEM_REMOVED);
item->SaveToDB(trans); // it also deletes item object!
item = nullptr;
}
}
else
{
TC_LOG_ERROR("entities.player", "Player::_LoadInventory: player (%s, name: '%s') has an unknown item (entry: %u) in inventory. Deleting item.",
GetGUID().ToString().c_str(), GetName().c_str(), itemEntry);
Item::DeleteFromInventoryDB(trans, itemGuid);
Item::DeleteFromDB(trans, itemGuid);
AzeriteItem::DeleteFromDB(trans, itemGuid);
AzeriteEmpoweredItem::DeleteFromDB(trans, itemGuid);
}
return item;
}
// load mailed item which should receive current player
Item* Player::_LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint32 mailId, Mail* mail, Field* fields, ItemAdditionalLoadInfo* addionalData)
{
ObjectGuid::LowType itemGuid = fields[0].GetUInt64();
uint32 itemEntry = fields[1].GetUInt32();
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry);
if (!proto)
{
TC_LOG_ERROR("entities.player", "Player '%s' (%s) has unknown item in mailed items (GUID: " UI64FMTD ", Entry: %u) in mail (%u), deleted.",
player ? player->GetName().c_str() : "<unknown>", playerGuid.ToString().c_str(), itemGuid, itemEntry, mailId);
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_MAIL_ITEM);
stmt->setUInt64(0, itemGuid);
trans->Append(stmt);
Item::DeleteFromDB(trans, itemGuid);
AzeriteItem::DeleteFromDB(trans, itemGuid);
AzeriteEmpoweredItem::DeleteFromDB(trans, itemGuid);
CharacterDatabase.CommitTransaction(trans);
return nullptr;
}
Item* item = NewItemOrBag(proto);
ObjectGuid ownerGuid = fields[51].GetUInt64() ? ObjectGuid::Create<HighGuid::Player>(fields[51].GetUInt64()) : ObjectGuid::Empty;
if (!item->LoadFromDB(itemGuid, ownerGuid, fields, itemEntry))
{
TC_LOG_ERROR("entities.player", "Player::_LoadMailedItems: Item (GUID: " UI64FMTD ") in mail (%u) doesn't exist, deleted from mail.", itemGuid, mailId);
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM);
stmt->setUInt64(0, itemGuid);
CharacterDatabase.Execute(stmt);
item->FSetState(ITEM_REMOVED);
CharacterDatabaseTransaction temp = CharacterDatabaseTransaction(nullptr);
item->SaveToDB(temp); // it also deletes item object !
return nullptr;
}
if (addionalData)
{
if (item->GetTemplate()->GetArtifactID() && addionalData->Artifact)
item->LoadArtifactData(player, addionalData->Artifact->Xp, addionalData->Artifact->ArtifactAppearanceId,
addionalData->Artifact->ArtifactTierId, addionalData->Artifact->ArtifactPowers);
if (addionalData->AzeriteItem)
if (AzeriteItem* azeriteItem = item->ToAzeriteItem())
azeriteItem->LoadAzeriteItemData(player, *addionalData->AzeriteItem);
if (addionalData->AzeriteEmpoweredItem)
if (AzeriteEmpoweredItem* azeriteEmpoweredItem = item->ToAzeriteEmpoweredItem())
azeriteEmpoweredItem->LoadAzeriteEmpoweredItemData(player, *addionalData->AzeriteEmpoweredItem);
}
if (mail)
mail->AddItem(itemGuid, itemEntry);
if (player)
player->AddMItem(item);
return item;
}
void Player::_LoadMail(PreparedQueryResult mailsResult, PreparedQueryResult mailItemsResult, PreparedQueryResult artifactResult, PreparedQueryResult azeriteItemResult,
PreparedQueryResult azeriteItemMilestonePowersResult, PreparedQueryResult azeriteItemUnlockedEssencesResult, PreparedQueryResult azeriteEmpoweredItemResult)
{
m_mail.clear();
std::unordered_map<uint32, Mail*> mailById;
if (mailsResult)
{
do
{
Field* fields = mailsResult->Fetch();
Mail* m = new Mail();
m->messageID = fields[0].GetUInt32();
m->messageType = fields[1].GetUInt8();
m->sender = fields[2].GetUInt64();
m->receiver = fields[3].GetUInt64();
m->subject = fields[4].GetString();
m->body = fields[5].GetString();
m->expire_time = fields[6].GetInt64();
m->deliver_time = fields[7].GetInt64();
m->money = fields[8].GetUInt64();
m->COD = fields[9].GetUInt64();
m->checked = fields[10].GetUInt8();
m->stationery = fields[11].GetUInt8();
m->mailTemplateId = fields[12].GetInt16();
if (m->mailTemplateId && !sMailTemplateStore.LookupEntry(m->mailTemplateId))
{
TC_LOG_ERROR("entities.player", "Player::_LoadMail: Mail (%u) has nonexistent MailTemplateId (%u), remove at load", m->messageID, m->mailTemplateId);
m->mailTemplateId = 0;
}
m->state = MAIL_STATE_UNCHANGED;
m_mail.push_back(m);
mailById[m->messageID] = m;
}
while (mailsResult->NextRow());
}
if (mailItemsResult)
{
std::unordered_map<ObjectGuid::LowType, ItemAdditionalLoadInfo> additionalData;
ItemAdditionalLoadInfo::Init(&additionalData, artifactResult, azeriteItemResult, azeriteItemMilestonePowersResult,
azeriteItemUnlockedEssencesResult, azeriteEmpoweredItemResult);
do
{
Field* fields = mailItemsResult->Fetch();
uint32 mailId = fields[52].GetUInt32();
_LoadMailedItem(GetGUID(), this, mailId, mailById[mailId], fields, Trinity::Containers::MapGetValuePtr(additionalData, fields[0].GetUInt64()));
} while (mailItemsResult->NextRow());
}
UpdateNextMailTimeAndUnreads();
}
void Player::LoadPet()
{
//fixme: the pet should still be loaded if the player is not in world
// just not added to the map
if (IsInWorld())
{
Pet* pet = new Pet(this);
if (!pet->LoadPetFromDB(this, 0, 0, true))
delete pet;
}
}
void Player::_LoadQuestStatus(PreparedQueryResult result)
{
uint16 slot = 0;
//// 0 1 2 3 4
//QueryResult* result = CharacterDatabase.PQuery("SELECT quest, status, explored, acceptTime, endTime WHERE guid = '%u' AND status <> 0", GetGUIDLow());
if (result)
{
do
{
Field* fields = result->Fetch();
uint32 quest_id = fields[0].GetUInt32();
// used to be new, no delete?
Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
if (quest)
{
// find or create
auto questStatusItr = m_QuestStatus.emplace(quest_id, QuestStatusData{}).first;
QuestStatusData& questStatusData = questStatusItr->second;
uint8 qstatus = fields[1].GetUInt8();
if (qstatus < MAX_QUEST_STATUS)
questStatusData.Status = QuestStatus(qstatus);
else
{
questStatusData.Status = QUEST_STATUS_INCOMPLETE;
TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatus: Player '%s' (%s) has invalid quest %d status (%u), replaced by QUEST_STATUS_INCOMPLETE(3).",
GetName().c_str(), GetGUID().ToString().c_str(), quest_id, qstatus);
}
questStatusData.Explored = (fields[2].GetUInt8() > 0);
time_t acceptTime = time_t(fields[3].GetInt64());
time_t endTime = time_t(fields[4].GetInt64());
if (quest->GetLimitTime() && !GetQuestRewardStatus(quest_id))
{
AddTimedQuest(quest_id);
if (endTime <= GameTime::GetGameTime())
questStatusData.Timer = 1;
else
questStatusData.Timer = uint32((endTime - GameTime::GetGameTime()) * IN_MILLISECONDS);
}
else
endTime = 0;
// add to quest log
if (slot < MAX_QUEST_LOG_SIZE && questStatusData.Status != QUEST_STATUS_NONE)
{
questStatusData.Slot = slot;
for (QuestObjective const& obj : quest->GetObjectives())
m_questObjectiveStatus.emplace(std::make_pair(QuestObjectiveType(obj.Type), obj.ObjectID), QuestObjectiveStatusData{ questStatusItr, &obj });
SetQuestSlot(slot, quest_id);
SetQuestSlotEndTime(slot, endTime);
SetQuestSlotAcceptTime(slot, acceptTime);
if (questStatusData.Status == QUEST_STATUS_COMPLETE)
SetQuestSlotState(slot, QUEST_STATE_COMPLETE);
else if (questStatusData.Status == QUEST_STATUS_FAILED)
SetQuestSlotState(slot, QUEST_STATE_FAIL);
++slot;
}
TC_LOG_DEBUG("entities.player.loading", "Player::_LoadQuestStatus: Quest status is {%u} for quest {%u} for player (%s)", questStatusData.Status, quest_id, GetGUID().ToString().c_str());
}
}
while (result->NextRow());
}
// clear quest log tail
for (uint16 i = slot; i < MAX_QUEST_LOG_SIZE; ++i)
SetQuestSlot(i, 0);
}
void Player::_LoadQuestStatusObjectives(PreparedQueryResult result)
{
//// 0 1 2
//QueryResult* result = CharacterDatabase.PQuery("SELECT quest, objective, data WHERE guid = '%u'", GetGUIDLow());
if (result)
{
do
{
Field* fields = result->Fetch();
uint32 questID = fields[0].GetUInt32();
Quest const* quest = sObjectMgr->GetQuestTemplate(questID);
auto itr = m_QuestStatus.find(questID);
if (itr != m_QuestStatus.end() && itr->second.Slot < MAX_QUEST_LOG_SIZE && quest)
{
QuestStatusData& questStatusData = itr->second;
uint8 storageIndex = fields[1].GetUInt8();
auto objectiveItr = std::find_if(quest->Objectives.begin(), quest->Objectives.end(), [=](QuestObjective const& objective) { return uint8(objective.StorageIndex) == storageIndex; });
if (objectiveItr != quest->Objectives.end())
{
int32 data = fields[2].GetInt32();
if (!objectiveItr->IsStoringFlag())
SetQuestSlotCounter(questStatusData.Slot, storageIndex, data);
else if (data)
SetQuestSlotObjectiveFlag(questStatusData.Slot, storageIndex);
}
else
TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectives: Player '%s' (%s) has quest %d out of range objective index %u.", GetName().c_str(), GetGUID().ToString().c_str(), questID, storageIndex);
}
else
TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectives: Player %s (%s) does not have quest %d but has objective data for it.", GetName().c_str(), GetGUID().ToString().c_str(), questID);
}
while (result->NextRow());
}
}
void Player::_LoadQuestStatusRewarded(PreparedQueryResult result)
{
// SELECT quest FROM character_queststatus_rewarded WHERE guid = ?
if (result)
{
do
{
Field* fields = result->Fetch();
uint32 quest_id = fields[0].GetUInt32();
// used to be new, no delete?
Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
if (quest)
{
// learn rewarded spell if unknown
LearnQuestRewardedSpells(quest);
// set rewarded title if any
if (quest->GetRewTitle())
if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(quest->GetRewTitle()))
SetTitle(titleEntry);
// Skip loading special quests - they are also added to rewarded quests but only once and remain there forever
// instead add them separately from load daily/weekly/monthly/seasonal
if (!quest->IsDailyOrWeekly() && !quest->IsMonthly() && !quest->IsSeasonal())
if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(quest_id))
SetQuestCompletedBit(questBit, true);
for (uint32 i = 0; i < quest->GetRewChoiceItemsCount(); ++i)
GetSession()->GetCollectionMgr()->AddItemAppearance(quest->RewardChoiceItemId[i]);
for (uint32 i = 0; i < quest->GetRewItemsCount(); ++i)
GetSession()->GetCollectionMgr()->AddItemAppearance(quest->RewardItemId[i]);
if (std::vector<QuestPackageItemEntry const*> const* questPackageItems = sDB2Manager.GetQuestPackageItems(quest->GetQuestPackageID()))
for (QuestPackageItemEntry const* questPackageItem : *questPackageItems)
if (ItemTemplate const* rewardProto = sObjectMgr->GetItemTemplate(questPackageItem->ItemID))
if (rewardProto->ItemSpecClassMask & GetClassMask())
GetSession()->GetCollectionMgr()->AddItemAppearance(questPackageItem->ItemID);
if (quest->CanIncreaseRewardedQuestCounters())
m_RewardedQuests.insert(quest_id);
}
}
while (result->NextRow());
}
}
void Player::_LoadDailyQuestStatus(PreparedQueryResult result)
{
m_DFQuests.clear();
//QueryResult* result = CharacterDatabase.PQuery("SELECT quest, time FROM character_queststatus_daily WHERE guid = '%u'", GetGUIDLow());
if (result)
{
do
{
Field* fields = result->Fetch();
uint32 quest_id = fields[0].GetUInt32();
if (Quest const* qQuest = sObjectMgr->GetQuestTemplate(quest_id))
{
if (qQuest->IsDFQuest())
{
m_DFQuests.insert(qQuest->GetQuestId());
m_lastDailyQuestTime = fields[1].GetInt64();
continue;
}
}
// save _any_ from daily quest times (it must be after last reset anyway)
m_lastDailyQuestTime = fields[1].GetInt64();
Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
if (!quest)
continue;
AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::DailyQuestsCompleted)) = quest_id;
if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(quest_id))
SetQuestCompletedBit(questBit, true);
TC_LOG_DEBUG("entities.player.loading", "Player::_LoadDailyQuestStatus: Loaded daily quest cooldown (QuestID: %u) for player '%s' (%s)",
quest_id, GetName().c_str(), GetGUID().ToString().c_str());
}
while (result->NextRow());
}
m_DailyQuestChanged = false;
}
void Player::_LoadWeeklyQuestStatus(PreparedQueryResult result)
{
m_weeklyquests.clear();
if (result)
{
do
{
Field* fields = result->Fetch();
uint32 quest_id = fields[0].GetUInt32();
Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
if (!quest)
continue;
m_weeklyquests.insert(quest_id);
if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(quest_id))
SetQuestCompletedBit(questBit, true);
TC_LOG_DEBUG("entities.player.loading", "Player::_LoadWeeklyQuestStatus: Loaded weekly quest cooldown (QuestID: %u) for player '%s' (%s)",
quest_id, GetName().c_str(), GetGUID().ToString().c_str());
}
while (result->NextRow());
}
m_WeeklyQuestChanged = false;
}
void Player::_LoadSeasonalQuestStatus(PreparedQueryResult result)
{
m_seasonalquests.clear();
if (result)
{
do
{
Field* fields = result->Fetch();
uint32 quest_id = fields[0].GetUInt32();
uint32 event_id = fields[1].GetUInt32();
Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
if (!quest)
continue;
m_seasonalquests[event_id].insert(quest_id);
if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(quest_id))
SetQuestCompletedBit(questBit, true);
TC_LOG_DEBUG("entities.player.loading", "Player::_LoadSeasonalQuestStatus: Loaded seasonal quest cooldown (QuestID: %u) for player '%s' (%s)",
quest_id, GetName().c_str(), GetGUID().ToString().c_str());
}
while (result->NextRow());
}
m_SeasonalQuestChanged = false;
}
void Player::_LoadMonthlyQuestStatus(PreparedQueryResult result)
{
m_monthlyquests.clear();
if (result)
{
do
{
Field* fields = result->Fetch();
uint32 quest_id = fields[0].GetUInt32();
Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
if (!quest)
continue;
m_monthlyquests.insert(quest_id);
if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(quest_id))
SetQuestCompletedBit(questBit, true);
TC_LOG_DEBUG("entities.player.loading", "Player::_LoadMonthlyQuestStatus: Loaded monthly quest cooldown (QuestID: %u) for player '%s' (%s)",
quest_id, GetName().c_str(), GetGUID().ToString().c_str());
}
while (result->NextRow());
}
m_MonthlyQuestChanged = false;
}
void Player::_LoadSpells(PreparedQueryResult result)
{
//QueryResult* result = CharacterDatabase.PQuery("SELECT spell, active, disabled FROM character_spell WHERE guid = '%u'", GetGUIDLow());
if (result)
{
do
AddSpell((*result)[0].GetUInt32(), (*result)[1].GetBool(), false, false, (*result)[2].GetBool(), true);
while (result->NextRow());
}
}
void Player::_LoadStoredAuraTeleportLocations(PreparedQueryResult result)
{
// 0 1 2 3 4 5
//QueryResult* result = CharacterDatabase.PQuery("SELECT Spell, MapId, PositionX, PositionY, PositionZ, Orientation FROM character_spell_location WHERE Guid = ?", GetGUIDLow());
m_storedAuraTeleportLocations.clear();
if (result)
{
do
{
Field* fields = result->Fetch();
uint32 spellId = fields[0].GetUInt32();
if (!sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE))
{
TC_LOG_ERROR("spells", "Player::_LoadStoredAuraTeleportLocations: Player %s (%s) spell (ID: %u) does not exist",
GetName().c_str(), GetGUID().ToString().c_str(), spellId);
continue;
}
WorldLocation location(fields[1].GetUInt32(), fields[2].GetFloat(), fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat());
if (!MapManager::IsValidMapCoord(location))
{
TC_LOG_ERROR("spells", "Player::_LoadStoredAuraTeleportLocations: Player %s (%s) spell (ID: %u) has invalid position on map %u, {%s}.",
GetName().c_str(), GetGUID().ToString().c_str(), spellId, location.GetMapId(), location.ToString().c_str());
continue;
}
StoredAuraTeleportLocation& storedLocation = m_storedAuraTeleportLocations[spellId];
storedLocation.Loc = location;
storedLocation.State = StoredAuraTeleportLocation::UNCHANGED;
}
while (result->NextRow());
}
}
void Player::_LoadGroup(PreparedQueryResult result)
{
//QueryResult* result = CharacterDatabase.PQuery("SELECT guid FROM group_member WHERE memberGuid=%u", GetGUIDLow());
if (result)
{
if (Group* group = sGroupMgr->GetGroupByDbStoreId((*result)[0].GetUInt32()))
{
if (group->IsLeader(GetGUID()))
AddPlayerFlag(PLAYER_FLAGS_GROUP_LEADER);
uint8 subgroup = group->GetMemberGroup(GetGUID());
SetGroup(group, subgroup);
SetPartyType(group->GetGroupCategory(), GROUP_TYPE_NORMAL);
ResetGroupUpdateSequenceIfNeeded(group);
// the group leader may change the instance difficulty while the player is offline
SetDungeonDifficultyID(group->GetDungeonDifficultyID());
SetRaidDifficultyID(group->GetRaidDifficultyID());
SetLegacyRaidDifficultyID(group->GetLegacyRaidDifficultyID());
}
}
if (!GetGroup() || !GetGroup()->IsLeader(GetGUID()))
RemovePlayerFlag(PLAYER_FLAGS_GROUP_LEADER);
}
void Player::_LoadBoundInstances(PreparedQueryResult result)
{
m_boundInstances.clear();
Group* group = GetGroup();
// 0 1 2 3 4 5 6
// SELECT id, permanent, map, difficulty, extendState, resettime, entranceId FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = ?
if (result)
{
do
{
Field* fields = result->Fetch();
bool perm = fields[1].GetBool();
uint32 mapId = fields[2].GetUInt16();
uint32 instanceId = fields[0].GetUInt32();
uint8 difficulty = fields[3].GetUInt8();
BindExtensionState extendState = BindExtensionState(fields[4].GetUInt8());
time_t resetTime = fields[5].GetInt64();
// the resettime for normal instances is only saved when the InstanceSave is unloaded
// so the value read from the DB may be wrong here but only if the InstanceSave is loaded
// and in that case it is not used
uint32 entranceId = fields[6].GetUInt32();
bool deleteInstance = false;
MapEntry const* mapEntry = sMapStore.LookupEntry(mapId);
std::string mapname = mapEntry ? mapEntry->MapName[sWorld->GetDefaultDbcLocale()] : "Unknown";
if (!mapEntry || !mapEntry->IsDungeon())
{
TC_LOG_ERROR("entities.player", "Player::_LoadBoundInstances: Player '%s' (%s) has bind to not existed or not dungeon map %d (%s)",
GetName().c_str(), GetGUID().ToString().c_str(), mapId, mapname.c_str());
deleteInstance = true;
}
else if (!sDifficultyStore.HasRecord(difficulty))
{
TC_LOG_ERROR("entities.player", "Player::_LoadBoundInstances: player '%s' (%s) has bind to not existed difficulty %d instance for map %u (%s)",
GetName().c_str(), GetGUID().ToString().c_str(), difficulty, mapId, mapname.c_str());
deleteInstance = true;
}
else
{
MapDifficultyEntry const* mapDiff = sDB2Manager.GetMapDifficultyData(mapId, Difficulty(difficulty));
if (!mapDiff)
{
TC_LOG_ERROR("entities.player", "Player::_LoadBoundInstances: player '%s' (%s) has bind to not existed difficulty %d instance for map %u (%s)",
GetName().c_str(), GetGUID().ToString().c_str(), difficulty, mapId, mapname.c_str());
deleteInstance = true;
}
else if (!perm && group)
{
TC_LOG_ERROR("entities.player", "Player::_LoadBoundInstances: player '%s' (%s) is in group %s but has a non-permanent character bind to map %d (%s), %d, %d",
GetName().c_str(), GetGUID().ToString().c_str(), group->GetGUID().ToString().c_str(), mapId, mapname.c_str(), instanceId, difficulty);
deleteInstance = true;
}
}
if (deleteInstance)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, instanceId);
CharacterDatabase.Execute(stmt);
continue;
}
// since non permanent binds are always solo bind, they can always be reset
if (InstanceSave* save = sInstanceSaveMgr->AddInstanceSave(mapId, instanceId, Difficulty(difficulty), resetTime, entranceId, !perm, true))
BindToInstance(save, perm, extendState, true);
}
while (result->NextRow());
}
}
InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty, bool withExpired)
{
// some instances only have one difficulty
MapDifficultyEntry const* mapDiff = sDB2Manager.GetDownscaledMapDifficultyData(mapid, difficulty);
if (!mapDiff)
return nullptr;
auto difficultyItr = m_boundInstances.find(difficulty);
if (difficultyItr == m_boundInstances.end())
return nullptr;
auto itr = difficultyItr->second.find(mapid);
if (itr != difficultyItr->second.end())
if (itr->second.extendState || withExpired)
return &itr->second;
return nullptr;
}
InstancePlayerBind const* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty) const
{
// some instances only have one difficulty
MapDifficultyEntry const* mapDiff = sDB2Manager.GetDownscaledMapDifficultyData(mapid, difficulty);
if (!mapDiff)
return nullptr;
auto difficultyItr = m_boundInstances.find(difficulty);
if (difficultyItr == m_boundInstances.end())
return nullptr;
auto itr = difficultyItr->second.find(mapid);
if (itr != difficultyItr->second.end())
return &itr->second;
return nullptr;
}
InstanceSave* Player::GetInstanceSave(uint32 mapid)
{
MapEntry const* mapEntry = sMapStore.LookupEntry(mapid);
InstancePlayerBind* pBind = GetBoundInstance(mapid, GetDifficultyID(mapEntry));
InstanceSave* pSave = pBind ? pBind->save : nullptr;
if (!pBind || !pBind->perm)
if (Group* group = GetGroup())
if (InstanceGroupBind* groupBind = group->GetBoundInstance(GetDifficultyID(mapEntry), mapid))
pSave = groupBind->save;
return pSave;
}
void Player::UnbindInstance(uint32 mapid, Difficulty difficulty, bool unload)
{
auto difficultyItr = m_boundInstances.find(difficulty);
if (difficultyItr != m_boundInstances.end())
{
auto itr = difficultyItr->second.find(mapid);
if (itr != difficultyItr->second.end())
UnbindInstance(itr, difficultyItr, unload);
}
}
void Player::UnbindInstance(BoundInstancesMap::mapped_type::iterator& itr, BoundInstancesMap::iterator& difficultyItr, bool unload)
{
if (itr != difficultyItr->second.end())
{
if (!unload)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, itr->second.save->GetInstanceId());
CharacterDatabase.Execute(stmt);
}
if (itr->second.perm)
GetSession()->SendCalendarRaidLockout(itr->second.save, false);
itr->second.save->RemovePlayer(this); // save can become invalid
difficultyItr->second.erase(itr++);
}
}
InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, BindExtensionState extendState, bool load)
{
if (save)
{
InstancePlayerBind& bind = m_boundInstances[save->GetDifficultyID()][save->GetMapId()];
if (extendState == EXTEND_STATE_KEEP) // special flag, keep the player's current extend state when updating for new boss down
{
if (save == bind.save)
extendState = bind.extendState;
else
extendState = EXTEND_STATE_NORMAL;
}
if (!load)
{
if (bind.save)
{
// update the save when the group kills a boss
if (permanent != bind.perm || save != bind.save || extendState != bind.extendState)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_INSTANCE);
stmt->setUInt32(0, save->GetInstanceId());
stmt->setBool(1, permanent);
stmt->setUInt8(2, extendState);
stmt->setUInt64(3, GetGUID().GetCounter());
stmt->setUInt32(4, bind.save->GetInstanceId());
CharacterDatabase.Execute(stmt);
}
}
else
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_INSTANCE);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, save->GetInstanceId());
stmt->setBool(2, permanent);
stmt->setUInt8(3, extendState);
CharacterDatabase.Execute(stmt);
}
}
if (bind.save != save)
{
if (bind.save)
bind.save->RemovePlayer(this);
save->AddPlayer(this);
}
if (permanent)
save->SetCanReset(false);
bind.save = save;
bind.perm = permanent;
bind.extendState = extendState;
if (!load)
TC_LOG_DEBUG("maps", "Player::BindToInstance: Player '%s' (%s) is now bound to map (ID: %d, Instance %d, Difficulty %d)", GetName().c_str(), GetGUID().ToString().c_str(), save->GetMapId(), save->GetInstanceId(), save->GetDifficultyID());
sScriptMgr->OnPlayerBindToInstance(this, save->GetDifficultyID(), save->GetMapId(), permanent, uint8(extendState));
return &bind;
}
return nullptr;
}
void Player::BindToInstance()
{
InstanceSave* mapSave = sInstanceSaveMgr->GetInstanceSave(_pendingBindId);
if (!mapSave) //it seems sometimes mapSave is nullptr, but I did not check why
return;
WorldPackets::Instance::InstanceSaveCreated data;
data.Gm = IsGameMaster();
SendDirectMessage(data.Write());
if (!IsGameMaster())
{
BindToInstance(mapSave, true, EXTEND_STATE_KEEP);
GetSession()->SendCalendarRaidLockout(mapSave, true);
}
}
void Player::SetPendingBind(uint32 instanceId, uint32 bindTimer)
{
_pendingBindId = instanceId;
_pendingBindTimer = bindTimer;
}
void Player::SendRaidInfo()
{
WorldPackets::Instance::InstanceInfo instanceInfo;
time_t now = GameTime::GetGameTime();
for (auto difficultyItr = m_boundInstances.begin(); difficultyItr != m_boundInstances.end(); ++difficultyItr)
{
for (auto itr = difficultyItr->second.begin(); itr != difficultyItr->second.end(); ++itr)
{
InstancePlayerBind const& bind = itr->second;
if (bind.perm)
{
InstanceSave* save = itr->second.save;
WorldPackets::Instance::InstanceLock lockInfos;
lockInfos.InstanceID = save->GetInstanceId();
lockInfos.MapID = save->GetMapId();
lockInfos.DifficultyID = save->GetDifficultyID();
if (bind.extendState != EXTEND_STATE_EXTENDED)
lockInfos.TimeRemaining = save->GetResetTime() - now;
else
lockInfos.TimeRemaining = sInstanceSaveMgr->GetSubsequentResetTime(save->GetMapId(), save->GetDifficultyID(), save->GetResetTime()) - now;
lockInfos.CompletedMask = 0;
if (Map* map = sMapMgr->FindMap(save->GetMapId(), save->GetInstanceId()))
if (InstanceScript* instanceScript = ((InstanceMap*)map)->GetInstanceScript())
lockInfos.CompletedMask = instanceScript->GetCompletedEncounterMask();
lockInfos.Locked = bind.extendState != EXTEND_STATE_EXPIRED;
lockInfos.Extended = bind.extendState == EXTEND_STATE_EXTENDED;
instanceInfo.LockList.push_back(lockInfos);
}
}
}
SendDirectMessage(instanceInfo.Write());
}
bool Player::Satisfy(AccessRequirement const* ar, uint32 target_map, bool report)
{
if (!IsGameMaster())
{
uint8 LevelMin = 0;
uint8 LevelMax = 0;
int32 failedMapDifficultyXCondition = 0;
uint32 missingItem = 0;
uint32 missingQuest = 0;
uint32 missingAchievement = 0;
MapEntry const* mapEntry = sMapStore.LookupEntry(target_map);
if (!mapEntry)
return false;
Difficulty target_difficulty = GetDifficultyID(mapEntry);
MapDifficultyEntry const* mapDiff = sDB2Manager.GetDownscaledMapDifficultyData(target_map, target_difficulty);
if (!sWorld->getBoolConfig(CONFIG_INSTANCE_IGNORE_LEVEL))
{
if (DB2Manager::MapDifficultyConditionsContainer const* mapDifficultyConditions = sDB2Manager.GetMapDifficultyConditions(mapDiff->ID))
{
for (auto&& itr : *mapDifficultyConditions)
{
if (!ConditionMgr::IsPlayerMeetingCondition(this, itr.second))
{
failedMapDifficultyXCondition = itr.first;
break;
}
}
}
}
if (ar)
{
if (!sWorld->getBoolConfig(CONFIG_INSTANCE_IGNORE_LEVEL))
{
if (ar->levelMin && GetLevel() < ar->levelMin)
LevelMin = ar->levelMin;
if (ar->levelMax && GetLevel() > ar->levelMax)
LevelMax = ar->levelMax;
}
if (ar->item)
{
if (!HasItemCount(ar->item) &&
(!ar->item2 || !HasItemCount(ar->item2)))
missingItem = ar->item;
}
else if (ar->item2 && !HasItemCount(ar->item2))
missingItem = ar->item2;
if (DisableMgr::IsDisabledFor(DISABLE_TYPE_MAP, target_map, this))
{
GetSession()->SendNotification("%s", GetSession()->GetTrinityString(LANG_INSTANCE_CLOSED));
return false;
}
if (GetTeam() == ALLIANCE && ar->quest_A && !GetQuestRewardStatus(ar->quest_A))
missingQuest = ar->quest_A;
else if (GetTeam() == HORDE && ar->quest_H && !GetQuestRewardStatus(ar->quest_H))
missingQuest = ar->quest_H;
Player* leader = this;
ObjectGuid leaderGuid = GetGroup() ? GetGroup()->GetLeaderGUID() : GetGUID();
if (leaderGuid != GetGUID())
leader = ObjectAccessor::FindPlayer(leaderGuid);
if (ar->achievement)
if (!leader || !leader->HasAchieved(ar->achievement))
missingAchievement = ar->achievement;
}
if (LevelMin || LevelMax || failedMapDifficultyXCondition || missingItem || missingQuest || missingAchievement)
{
if (report)
{
if (missingQuest && !ar->questFailedText.empty())
ChatHandler(GetSession()).PSendSysMessage("%s", ar->questFailedText.c_str());
else if (mapDiff->Message[sWorld->GetDefaultDbcLocale()][0] != '\0' || failedMapDifficultyXCondition) // if (missingAchievement) covered by this case
SendTransferAborted(target_map, TRANSFER_ABORT_DIFFICULTY, target_difficulty, failedMapDifficultyXCondition);
else if (missingItem)
GetSession()->SendNotification(GetSession()->GetTrinityString(LANG_LEVEL_MINREQUIRED_AND_ITEM), LevelMin, ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(missingItem))->GetName(GetSession()->GetSessionDbcLocale()));
else if (LevelMin)
GetSession()->SendNotification(GetSession()->GetTrinityString(LANG_LEVEL_MINREQUIRED), LevelMin);
}
return false;
}
}
return true;
}
bool Player::IsInstanceLoginGameMasterException() const
{
if (CanBeGameMaster())
{
ChatHandler(GetSession()).SendSysMessage(LANG_INSTANCE_LOGIN_GAMEMASTER_EXCEPTION);
return true;
}
else
return false;
}
bool Player::CheckInstanceValidity(bool /*isLogin*/)
{
// game masters' instances are always valid
if (IsGameMaster())
return true;
// non-instances are always valid
Map* map = FindMap();
if (!map || !map->IsDungeon())
return true;
// raid instances require the player to be in a raid group to be valid
if (map->IsRaid() && !sWorld->getBoolConfig(CONFIG_INSTANCE_IGNORE_RAID) && (map->GetEntry()->Expansion() >= sWorld->getIntConfig(CONFIG_EXPANSION)))
if (!GetGroup() || !GetGroup()->isRaidGroup())
return false;
if (Group* group = GetGroup())
{
// check if player's group is bound to this instance
InstanceGroupBind* bind = group->GetBoundInstance(map->GetDifficultyID(), map->GetId());
if (!bind || !bind->save || bind->save->GetInstanceId() != map->GetInstanceId())
return false;
Map::PlayerList const& players = map->GetPlayers();
if (!players.isEmpty())
for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
{
if (Player* otherPlayer = it->GetSource())
{
if (otherPlayer->IsGameMaster())
continue;
if (!otherPlayer->m_InstanceValid) // ignore players that currently have a homebind timer active
continue;
if (group != otherPlayer->GetGroup())
return false;
}
}
}
else
{
// instance is invalid if we are not grouped and there are other players
if (map->GetPlayersCountExceptGMs() > 1)
return false;
// check if the player is bound to this instance
InstancePlayerBind* bind = GetBoundInstance(map->GetId(), map->GetDifficultyID());
if (!bind || !bind->save || bind->save->GetInstanceId() != map->GetInstanceId())
return false;
}
return true;
}
bool Player::CheckInstanceCount(uint32 instanceId) const
{
if (_instanceResetTimes.size() < sWorld->getIntConfig(CONFIG_MAX_INSTANCES_PER_HOUR))
return true;
return _instanceResetTimes.find(instanceId) != _instanceResetTimes.end();
}
void Player::AddInstanceEnterTime(uint32 instanceId, time_t enterTime)
{
if (_instanceResetTimes.find(instanceId) == _instanceResetTimes.end())
_instanceResetTimes.insert(InstanceTimeMap::value_type(instanceId, enterTime + HOUR));
}
bool Player::_LoadHomeBind(PreparedQueryResult result)
{
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(GetRace(), GetClass());
if (!info)
{
TC_LOG_ERROR("entities.player", "Player::_LoadHomeBind: Player '%s' (%s) has incorrect race/class (%u/%u) pair. Can't load.",
GetGUID().ToString().c_str(), GetName().c_str(), uint32(GetRace()), uint32(GetClass()));
return false;
}
bool ok = false;
// 0 1 2 3 4 5
// SELECT mapId, zoneId, posX, posY, posZ, orientation FROM character_homebind WHERE guid = ?
if (result)
{
Field* fields = result->Fetch();
m_homebind.WorldRelocate(fields[0].GetUInt16(), fields[2].GetFloat(), fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat());
m_homebindAreaId = fields[1].GetUInt16();
MapEntry const* bindMapEntry = sMapStore.LookupEntry(m_homebind.GetMapId());
// accept saved data only for valid position (and non instanceable), and accessable
if (MapManager::IsValidMapCoord(m_homebind) &&
!bindMapEntry->Instanceable() && GetSession()->GetExpansion() >= bindMapEntry->Expansion())
ok = true;
else
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_HOMEBIND);
stmt->setUInt64(0, GetGUID().GetCounter());
CharacterDatabase.Execute(stmt);
}
}
auto saveHomebindToDb = [&]()
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PLAYER_HOMEBIND);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt16(1, m_homebind.GetMapId());
stmt->setUInt16(2, m_homebindAreaId);
stmt->setFloat(3, m_homebind.GetPositionX());
stmt->setFloat(4, m_homebind.GetPositionY());
stmt->setFloat(5, m_homebind.GetPositionZ());
stmt->setFloat(6, m_homebind.GetOrientation());
CharacterDatabase.Execute(stmt);
};
if (!ok && HasAtLoginFlag(AT_LOGIN_FIRST))
{
PlayerInfo::CreatePosition const& createPosition = m_createMode == PlayerCreateMode::NPE && info->createPositionNPE ? *info->createPositionNPE : info->createPosition;
m_homebind.WorldRelocate(createPosition.Loc);
if (createPosition.TransportGuid)
{
if (Transport* transport = HashMapHolder<Transport>::Find(ObjectGuid::Create<HighGuid::Transport>(*createPosition.TransportGuid)))
{
float orientation = m_homebind.GetOrientation();
transport->CalculatePassengerPosition(m_homebind.m_positionX, m_homebind.m_positionY, m_homebind.m_positionZ, &orientation);
m_homebind.SetOrientation(orientation);
}
}
m_homebindAreaId = sMapMgr->GetAreaId(PhasingHandler::GetEmptyPhaseShift(), m_homebind);
saveHomebindToDb();
ok = true;
}
if (!ok)
{
WorldSafeLocsEntry const* loc = sObjectMgr->GetDefaultGraveyard(GetTeam());
if (!loc && GetRace() == RACE_PANDAREN_NEUTRAL)
loc = sObjectMgr->GetWorldSafeLoc(3295); // The Wandering Isle, Starting Area GY
ASSERT(loc, "Missing fallback graveyard location for faction %u", uint32(GetTeamId()));
m_homebind.WorldRelocate(loc->Loc);
m_homebindAreaId = sMapMgr->GetAreaId(PhasingHandler::GetEmptyPhaseShift(), loc->Loc);
saveHomebindToDb();
}
TC_LOG_DEBUG("entities.player", "Player::_LoadHomeBind: Setting home position (MapID: %u, AreaID: %u, X: %f, Y: %f, Z: %f O: %f) of player '%s' (%s)",
m_homebind.GetMapId(), m_homebindAreaId, m_homebind.GetPositionX(), m_homebind.GetPositionY(), m_homebind.GetPositionZ(), m_homebind.GetOrientation(), GetName().c_str(), GetGUID().ToString().c_str());
return true;
}
/*********************************************************/
/*** SAVE SYSTEM ***/
/*********************************************************/
void Player::SaveToDB(bool create /*=false*/)
{
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
LoginDatabaseTransaction loginTransaction = LoginDatabase.BeginTransaction();
SaveToDB(loginTransaction, trans, create);
CharacterDatabase.CommitTransaction(trans);
LoginDatabase.CommitTransaction(loginTransaction);
}
void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDatabaseTransaction trans, bool create /* = false */)
{
// delay auto save at any saves (manual, in code, or autosave)
m_nextSave = sWorld->getIntConfig(CONFIG_INTERVAL_SAVE);
//lets allow only players in world to be saved
if (IsBeingTeleportedFar())
{
ScheduleDelayedOperation(DELAYED_SAVE_PLAYER);
return;
}
// first save/honor gain after midnight will also update the player's honor fields
UpdateHonorFields();
TC_LOG_DEBUG("entities.unit", "Player::SaveToDB: The value of player %s at save: ", m_name.c_str());
outDebugValues();
if (!create)
sScriptMgr->OnPlayerSave(this);
CharacterDatabasePreparedStatement* stmt = nullptr;
uint8 index = 0;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_FISHINGSTEPS);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
auto finiteAlways = [](float f) { return std::isfinite(f) ? f : 0.0f; };
if (create)
{
//! Insert query
/// @todo: Filter out more redundant fields that can take their default value at player create
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER);
stmt->setUInt64(index++, GetGUID().GetCounter());
stmt->setUInt32(index++, GetSession()->GetAccountId());
stmt->setString(index++, GetName());
stmt->setUInt8(index++, GetRace());
stmt->setUInt8(index++, GetClass());
stmt->setUInt8(index++, GetNativeGender()); // save gender from PLAYER_BYTES_3, UNIT_BYTES_0 changes with every transform effect
stmt->setUInt8(index++, GetLevel());
stmt->setUInt32(index++, GetXP());
stmt->setUInt64(index++, GetMoney());
stmt->setUInt8(index++, GetInventorySlotCount());
stmt->setUInt8(index++, GetBankBagSlotCount());
stmt->setUInt8(index++, m_activePlayerData->RestInfo[REST_TYPE_XP].StateID);
stmt->setUInt32(index++, m_playerData->PlayerFlags);
stmt->setUInt32(index++, m_playerData->PlayerFlagsEx);
stmt->setUInt16(index++, (uint16)GetMapId());
stmt->setUInt32(index++, (uint32)GetInstanceId());
stmt->setUInt8(index++, uint8(GetDungeonDifficultyID()));
stmt->setUInt8(index++, uint8(GetRaidDifficultyID()));
stmt->setUInt8(index++, uint8(GetLegacyRaidDifficultyID()));
stmt->setFloat(index++, finiteAlways(GetPositionX()));
stmt->setFloat(index++, finiteAlways(GetPositionY()));
stmt->setFloat(index++, finiteAlways(GetPositionZ()));
stmt->setFloat(index++, finiteAlways(GetOrientation()));
stmt->setFloat(index++, finiteAlways(GetTransOffsetX()));
stmt->setFloat(index++, finiteAlways(GetTransOffsetY()));
stmt->setFloat(index++, finiteAlways(GetTransOffsetZ()));
stmt->setFloat(index++, finiteAlways(GetTransOffsetO()));
ObjectGuid::LowType transLowGUID = UI64LIT(0);
if (GetTransport())
transLowGUID = GetTransport()->GetGUID().GetCounter();
stmt->setUInt64(index++, transLowGUID);
std::ostringstream ss;
ss << m_taxi;
stmt->setString(index++, ss.str());
stmt->setInt64(index++, m_createTime);
stmt->setInt8(index++, AsUnderlyingType(m_createMode));
stmt->setUInt8(index++, m_cinematic);
stmt->setUInt32(index++, m_Played_time[PLAYED_TIME_TOTAL]);
stmt->setUInt32(index++, m_Played_time[PLAYED_TIME_LEVEL]);
stmt->setFloat(index++, finiteAlways(_restMgr->GetRestBonus(REST_TYPE_XP)));
stmt->setUInt64(index++, GameTime::GetGameTime());
stmt->setUInt8(index++, (HasPlayerFlag(PLAYER_FLAGS_RESTING) ? 1 : 0));
//save, far from tavern/city
//save, but in tavern/city
stmt->setUInt32(index++, GetTalentResetCost());
stmt->setInt64(index++, GetTalentResetTime());
stmt->setUInt32(index++, GetPrimarySpecialization());
stmt->setUInt16(index++, (uint16)m_ExtraFlags);
stmt->setUInt8(index++, m_stableSlots);
stmt->setUInt16(index++, (uint16)m_atLoginFlags);
stmt->setInt64(index++, m_deathExpireTime);
ss.str("");
ss << m_taxi.SaveTaxiDestinationsToString();
stmt->setString(index++, ss.str());
stmt->setUInt32(index++, m_activePlayerData->LifetimeHonorableKills);
stmt->setUInt16(index++, m_activePlayerData->TodayHonorableKills);
stmt->setUInt16(index++, m_activePlayerData->YesterdayHonorableKills);
stmt->setUInt32(index++, m_playerData->PlayerTitle);
stmt->setUInt32(index++, m_activePlayerData->WatchedFactionIndex);
stmt->setUInt8(index++, GetDrunkValue());
stmt->setUInt32(index++, GetHealth());
uint32 storedPowers = 0;
for (uint32 i = 0; i < MAX_POWERS; ++i)
{
if (GetPowerIndex(Powers(i)) != MAX_POWERS)
{
stmt->setUInt32(index++, m_unitData->Power[storedPowers]);
if (++storedPowers >= MAX_POWERS_PER_CLASS)
break;
}
}
for (; storedPowers < MAX_POWERS_PER_CLASS; ++storedPowers)
stmt->setUInt32(index++, 0);
stmt->setUInt32(index++, GetSession()->GetLatency());
stmt->setUInt8(index++, GetActiveTalentGroup());
stmt->setUInt32(index++, GetLootSpecId());
ss.str("");
for (uint32 i = 0; i < PLAYER_EXPLORED_ZONES_SIZE; ++i)
{
ss << uint32(m_activePlayerData->ExploredZones[i] & 0xFFFFFFFF) << ' ';
ss << uint32((m_activePlayerData->ExploredZones[i] >> 32) & 0xFFFFFFFF) << ' ';
}
stmt->setString(index++, ss.str());
ss.str("");
// cache equipment...
for (uint32 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
ss << uint32(item->GetTemplate()->GetInventoryType()) << ' ' << item->GetDisplayId(this) << ' ';
if (SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(item->GetVisibleEnchantmentId(this)))
ss << enchant->ItemVisual;
else
ss << '0';
ss << ' '
<< uint32(sItemStore.AssertEntry(item->GetVisibleEntry(this))->SubclassID) << ' '
<< uint32(item->GetVisibleSecondaryModifiedAppearanceId(this)) << ' ';
}
else
ss << "0 0 0 0 0 ";
}
stmt->setString(index++, ss.str());
ss.str("");
for (uint32 i = 0; i < m_activePlayerData->KnownTitles.size(); ++i)
{
ss << uint32(m_activePlayerData->KnownTitles[i] & 0xFFFFFFFF) << ' ';
ss << uint32((m_activePlayerData->KnownTitles[i] >> 32) & 0xFFFFFFFF) << ' ';
}
stmt->setString(index++, ss.str());
stmt->setUInt8(index++, m_activePlayerData->MultiActionBars);
stmt->setUInt32(index++, sRealmList->GetMinorMajorBugfixVersionForBuild(realm.Build));
}
else
{
// Update query
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHARACTER);
stmt->setString(index++, GetName());
stmt->setUInt8(index++, GetRace());
stmt->setUInt8(index++, GetClass());
stmt->setUInt8(index++, GetNativeGender()); // save gender from PLAYER_BYTES_3, UNIT_BYTES_0 changes with every transform effect
stmt->setUInt8(index++, GetLevel());
stmt->setUInt32(index++, GetXP());
stmt->setUInt64(index++, GetMoney());
stmt->setUInt8(index++, GetInventorySlotCount());
stmt->setUInt8(index++, GetBankBagSlotCount());
stmt->setUInt8(index++, m_activePlayerData->RestInfo[REST_TYPE_XP].StateID);
stmt->setUInt32(index++, m_playerData->PlayerFlags);
stmt->setUInt32(index++, m_playerData->PlayerFlagsEx);
if (!IsBeingTeleported())
{
stmt->setUInt16(index++, (uint16)GetMapId());
stmt->setUInt32(index++, (uint32)GetInstanceId());
stmt->setUInt8(index++, uint8(GetDungeonDifficultyID()));
stmt->setUInt8(index++, uint8(GetRaidDifficultyID()));
stmt->setUInt8(index++, uint8(GetLegacyRaidDifficultyID()));
stmt->setFloat(index++, finiteAlways(GetPositionX()));
stmt->setFloat(index++, finiteAlways(GetPositionY()));
stmt->setFloat(index++, finiteAlways(GetPositionZ()));
stmt->setFloat(index++, finiteAlways(GetOrientation()));
}
else
{
stmt->setUInt16(index++, (uint16)GetTeleportDest().GetMapId());
stmt->setUInt32(index++, (uint32)0);
stmt->setUInt8(index++, uint8(GetDungeonDifficultyID()));
stmt->setUInt8(index++, uint8(GetRaidDifficultyID()));
stmt->setUInt8(index++, uint8(GetLegacyRaidDifficultyID()));
stmt->setFloat(index++, finiteAlways(GetTeleportDest().GetPositionX()));
stmt->setFloat(index++, finiteAlways(GetTeleportDest().GetPositionY()));
stmt->setFloat(index++, finiteAlways(GetTeleportDest().GetPositionZ()));
stmt->setFloat(index++, finiteAlways(GetTeleportDest().GetOrientation()));
}
stmt->setFloat(index++, finiteAlways(GetTransOffsetX()));
stmt->setFloat(index++, finiteAlways(GetTransOffsetY()));
stmt->setFloat(index++, finiteAlways(GetTransOffsetZ()));
stmt->setFloat(index++, finiteAlways(GetTransOffsetO()));
ObjectGuid::LowType transLowGUID = UI64LIT(0);
if (GetTransport())
transLowGUID = GetTransport()->GetGUID().GetCounter();
stmt->setUInt64(index++, transLowGUID);
std::ostringstream ss;
ss << m_taxi;
stmt->setString(index++, ss.str());
stmt->setUInt8(index++, m_cinematic);
stmt->setUInt32(index++, m_Played_time[PLAYED_TIME_TOTAL]);
stmt->setUInt32(index++, m_Played_time[PLAYED_TIME_LEVEL]);
stmt->setFloat(index++, finiteAlways(_restMgr->GetRestBonus(REST_TYPE_XP)));
stmt->setUInt64(index++, GameTime::GetGameTime());
stmt->setUInt8(index++, (HasPlayerFlag(PLAYER_FLAGS_RESTING) ? 1 : 0));
//save, far from tavern/city
//save, but in tavern/city
stmt->setUInt32(index++, GetTalentResetCost());
stmt->setInt64(index++, GetTalentResetTime());
stmt->setUInt8(index++, GetNumRespecs());
stmt->setUInt32(index++, GetPrimarySpecialization());
stmt->setUInt16(index++, (uint16)m_ExtraFlags);
stmt->setUInt8(index++, m_stableSlots);
stmt->setUInt16(index++, (uint16)m_atLoginFlags);
stmt->setUInt16(index++, GetZoneId());
stmt->setInt64(index++, m_deathExpireTime);
ss.str("");
ss << m_taxi.SaveTaxiDestinationsToString();
stmt->setString(index++, ss.str());
stmt->setUInt32(index++, m_activePlayerData->LifetimeHonorableKills);
stmt->setUInt16(index++, m_activePlayerData->TodayHonorableKills);
stmt->setUInt16(index++, m_activePlayerData->YesterdayHonorableKills);
stmt->setUInt32(index++, m_playerData->PlayerTitle);
stmt->setUInt32(index++, m_activePlayerData->WatchedFactionIndex);
stmt->setUInt8(index++, GetDrunkValue());
stmt->setUInt32(index++, GetHealth());
uint32 storedPowers = 0;
for (uint32 i = 0; i < MAX_POWERS; ++i)
{
if (GetPowerIndex(Powers(i)) != MAX_POWERS)
{
stmt->setUInt32(index++, m_unitData->Power[storedPowers]);
if (++storedPowers >= MAX_POWERS_PER_CLASS)
break;
}
}
for (; storedPowers < MAX_POWERS_PER_CLASS; ++storedPowers)
stmt->setUInt32(index++, 0);
stmt->setUInt32(index++, GetSession()->GetLatency());
stmt->setUInt8(index++, GetActiveTalentGroup());
stmt->setUInt32(index++, GetLootSpecId());
ss.str("");
for (uint32 i = 0; i < PLAYER_EXPLORED_ZONES_SIZE; ++i)
{
ss << uint32(m_activePlayerData->ExploredZones[i] & 0xFFFFFFFF) << ' ';
ss << uint32((m_activePlayerData->ExploredZones[i] >> 32) & 0xFFFFFFFF) << ' ';
}
stmt->setString(index++, ss.str());
ss.str("");
// cache equipment...
for (uint32 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
{
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
ss << uint32(item->GetTemplate()->GetInventoryType()) << ' ' << item->GetDisplayId(this) << ' ';
if (SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(item->GetVisibleEnchantmentId(this)))
ss << enchant->ItemVisual;
else
ss << '0';
ss << ' '
<< uint32(sItemStore.AssertEntry(item->GetVisibleEntry(this))->SubclassID) << ' '
<< uint32(item->GetVisibleSecondaryModifiedAppearanceId(this)) << ' ';
}
else
ss << "0 0 0 0 0 ";
}
stmt->setString(index++, ss.str());
ss.str("");
for (uint32 i = 0; i < m_activePlayerData->KnownTitles.size(); ++i)
{
ss << uint32(m_activePlayerData->KnownTitles[i] & 0xFFFFFFFF) << ' ';
ss << uint32((m_activePlayerData->KnownTitles[i] >> 32) & 0xFFFFFFFF) << ' ';
}
stmt->setString(index++, ss.str());
stmt->setUInt8(index++, m_activePlayerData->MultiActionBars);
stmt->setUInt8(index++, IsInWorld() && !GetSession()->PlayerLogout() ? 1 : 0);
stmt->setUInt32(index++, m_activePlayerData->Honor);
stmt->setUInt32(index++, GetHonorLevel());
stmt->setUInt8(index++, m_activePlayerData->RestInfo[REST_TYPE_HONOR].StateID);
stmt->setFloat(index++, finiteAlways(_restMgr->GetRestBonus(REST_TYPE_HONOR)));
stmt->setUInt32(index++, sRealmList->GetMinorMajorBugfixVersionForBuild(realm.Build));
// Index
stmt->setUInt64(index, GetGUID().GetCounter());
}
trans->Append(stmt);
if (m_fishingSteps != 0)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_FISHINGSTEPS);
index = 0;
stmt->setUInt64(index++, GetGUID().GetCounter());
stmt->setUInt32(index++, m_fishingSteps);
trans->Append(stmt);
}
if (m_mailsUpdated) //save mails only when needed
_SaveMail(trans);
_SaveCustomizations(trans);
_SaveBGData(trans);
_SaveInventory(trans);
_SaveVoidStorage(trans);
_SaveQuestStatus(trans);
_SaveDailyQuestStatus(trans);
_SaveWeeklyQuestStatus(trans);
_SaveSeasonalQuestStatus(trans);
_SaveMonthlyQuestStatus(trans);
_SaveGlyphs(trans);
_SaveTalents(trans);
_SaveSpells(trans);
GetSpellHistory()->SaveToDB<Player>(trans);
_SaveActions(trans);
_SaveAuras(trans);
_SaveSkills(trans);
_SaveStoredAuraTeleportLocations(trans);
m_achievementMgr->SaveToDB(trans);
m_reputationMgr->SaveToDB(trans);
m_questObjectiveCriteriaMgr->SaveToDB(trans);
_SaveEquipmentSets(trans);
GetSession()->SaveTutorialsData(trans); // changed only while character in game
_SaveInstanceTimeRestrictions(trans);
_SaveCurrency(trans);
_SaveCUFProfiles(trans);
if (_garrison)
_garrison->SaveToDB(trans);
// check if stats should only be saved on logout
// save stats can be out of transaction
if (m_session->isLogingOut() || !sWorld->getBoolConfig(CONFIG_STATS_SAVE_ONLY_ON_LOGOUT))
_SaveStats(trans);
// TODO: Move this out
GetSession()->GetCollectionMgr()->SaveAccountToys(loginTransaction);
GetSession()->GetBattlePetMgr()->SaveToDB(loginTransaction);
GetSession()->GetCollectionMgr()->SaveAccountHeirlooms(loginTransaction);
GetSession()->GetCollectionMgr()->SaveAccountMounts(loginTransaction);
GetSession()->GetCollectionMgr()->SaveAccountItemAppearances(loginTransaction);
LoginDatabasePreparedStatement* loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_BNET_LAST_PLAYER_CHARACTERS);
loginStmt->setUInt32(0, GetSession()->GetAccountId());
loginStmt->setUInt8(1, realm.Id.Region);
loginStmt->setUInt8(2, realm.Id.Site);
loginTransaction->Append(loginStmt);
loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BNET_LAST_PLAYER_CHARACTERS);
loginStmt->setUInt32(0, GetSession()->GetAccountId());
loginStmt->setUInt8(1, realm.Id.Region);
loginStmt->setUInt8(2, realm.Id.Site);
loginStmt->setUInt32(3, realm.Id.Realm);
loginStmt->setString(4, GetName());
loginStmt->setUInt64(5, GetGUID().GetCounter());
loginStmt->setUInt32(6, GameTime::GetGameTime());
loginTransaction->Append(loginStmt);
// save pet (hunter pet level and experience and all type pets health/mana).
if (Pet* pet = GetPet())
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
}
// fast save function for item/money cheating preventing - save only inventory and money state
void Player::SaveInventoryAndGoldToDB(CharacterDatabaseTransaction& trans)
{
_SaveInventory(trans);
_SaveCurrency(trans);
SaveGoldToDB(trans);
}
void Player::SaveGoldToDB(CharacterDatabaseTransaction& trans) const
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_MONEY);
stmt->setUInt64(0, GetMoney());
stmt->setUInt64(1, GetGUID().GetCounter());
trans->Append(stmt);
}
template<typename iterator>
void SavePlayerCustomizations(CharacterDatabaseTransaction trans, ObjectGuid::LowType guid, Trinity::IteratorPair<iterator> customizations)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_CUSTOMIZATIONS);
stmt->setUInt64(0, guid);
trans->Append(stmt);
for (auto&& customization : customizations)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_CUSTOMIZATION);
stmt->setUInt64(0, guid);
stmt->setUInt32(1, customization.ChrCustomizationOptionID);
stmt->setUInt32(2, customization.ChrCustomizationChoiceID);
trans->Append(stmt);
}
}
void Player::SaveCustomizations(CharacterDatabaseTransaction trans, ObjectGuid::LowType guid,
Trinity::IteratorPair<UF::ChrCustomizationChoice const*> customizations)
{
SavePlayerCustomizations(trans, guid, customizations);
}
void Player::_SaveCustomizations(CharacterDatabaseTransaction trans)
{
if (!m_customizationsChanged)
return;
m_customizationsChanged = false;
SavePlayerCustomizations(trans, GetGUID().GetCounter(), Trinity::Containers::MakeIteratorPair(m_playerData->Customizations.begin(), m_playerData->Customizations.end()));
}
void Player::_SaveActions(CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt;
for (ActionButtonList::iterator itr = m_actionButtons.begin(); itr != m_actionButtons.end();)
{
switch (itr->second.uState)
{
case ACTIONBUTTON_NEW:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_ACTION);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt8(1, GetActiveTalentGroup());
stmt->setUInt8(2, itr->first);
stmt->setUInt32(3, itr->second.GetAction());
stmt->setUInt8(4, uint8(itr->second.GetType()));
trans->Append(stmt);
itr->second.uState = ACTIONBUTTON_UNCHANGED;
++itr;
break;
case ACTIONBUTTON_CHANGED:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_ACTION);
stmt->setUInt32(0, itr->second.GetAction());
stmt->setUInt8(1, uint8(itr->second.GetType()));
stmt->setUInt64(2, GetGUID().GetCounter());
stmt->setUInt8(3, itr->first);
stmt->setUInt8(4, GetActiveTalentGroup());
trans->Append(stmt);
itr->second.uState = ACTIONBUTTON_UNCHANGED;
++itr;
break;
case ACTIONBUTTON_DELETED:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION_BY_BUTTON_SPEC);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt8(1, itr->first);
stmt->setUInt8(2, GetActiveTalentGroup());
trans->Append(stmt);
m_actionButtons.erase(itr++);
break;
default:
++itr;
break;
}
}
}
void Player::_SaveAuras(CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_AURA_EFFECT);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_AURA);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
uint8 index;
for (AuraMap::const_iterator itr = m_ownedAuras.begin(); itr != m_ownedAuras.end(); ++itr)
{
if (!itr->second->CanBeSaved())
continue;
Aura* aura = itr->second;
uint32 recalculateMask = 0;
AuraKey key = aura->GenerateKey(recalculateMask);
index = 0;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AURA);
stmt->setUInt64(index++, GetGUID().GetCounter());
stmt->setBinary(index++, key.Caster.GetRawValue());
stmt->setBinary(index++, key.Item.GetRawValue());
stmt->setUInt32(index++, key.SpellId);
stmt->setUInt32(index++, key.EffectMask);
stmt->setUInt32(index++, recalculateMask);
stmt->setUInt8(index++, aura->GetCastDifficulty());
stmt->setUInt8(index++, aura->GetStackAmount());
stmt->setInt32(index++, aura->GetMaxDuration());
stmt->setInt32(index++, aura->GetDuration());
stmt->setUInt8(index++, aura->GetCharges());
stmt->setUInt32(index++, aura->GetCastItemId());
stmt->setInt32(index++, aura->GetCastItemLevel());
trans->Append(stmt);
for (AuraEffect const* effect : aura->GetAuraEffects())
{
if (effect)
{
index = 0;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AURA_EFFECT);
stmt->setUInt64(index++, GetGUID().GetCounter());
stmt->setBinary(index++, key.Caster.GetRawValue());
stmt->setBinary(index++, key.Item.GetRawValue());
stmt->setUInt32(index++, key.SpellId);
stmt->setUInt32(index++, key.EffectMask);
stmt->setUInt8(index++, effect->GetEffIndex());
stmt->setInt32(index++, effect->GetAmount());
stmt->setInt32(index++, effect->GetBaseAmount());
trans->Append(stmt);
}
}
}
}
void Player::_SaveInventory(CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt;
// force items in buyback slots to new state
// and remove those that aren't already
for (uint8 i = BUYBACK_SLOT_START; i < BUYBACK_SLOT_END; ++i)
{
Item* item = m_items[i];
if (!item)
continue;
if (item->GetState() == ITEM_NEW)
{
if (ItemTemplate const* itemTemplate = item->GetTemplate())
if (itemTemplate->HasFlag(ITEM_FLAG_HAS_LOOT))
sLootItemStorage->RemoveStoredLootForContainer(item->GetGUID().GetCounter());
continue;
}
item->DeleteFromInventoryDB(trans);
item->DeleteFromDB(trans);
m_items[i]->FSetState(ITEM_NEW);
if (ItemTemplate const* itemTemplate = item->GetTemplate())
if (itemTemplate->HasFlag(ITEM_FLAG_HAS_LOOT))
sLootItemStorage->RemoveStoredLootForContainer(item->GetGUID().GetCounter());
}
// Updated played time for refundable items. We don't do this in Player::Update because there's simply no need for it,
// the client auto counts down in real time after having received the initial played time on the first
// SMSG_ITEM_REFUND_INFO_RESPONSE packet.
// Item::UpdatePlayedTime is only called when needed, which is in DB saves, and item refund info requests.
GuidSet::iterator i_next;
for (GuidSet::iterator itr = m_refundableItems.begin(); itr!= m_refundableItems.end(); itr = i_next)
{
// use copy iterator because itr may be invalid after operations in this loop
i_next = itr;
++i_next;
Item* iPtr = GetItemByGuid(*itr);
if (iPtr)
{
iPtr->UpdatePlayedTime(this);
continue;
}
else
{
TC_LOG_ERROR("entities.player", "Player::_SaveInventory: Can't find item (%s) in refundable storage for player '%s' (%s), removing.",
itr->ToString().c_str(), GetName().c_str(), GetGUID().ToString().c_str());
m_refundableItems.erase(itr);
}
}
// update enchantment durations
for (EnchantDurationList::iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr)
itr->item->SetEnchantmentDuration(itr->slot, itr->leftduration, this);
// if no changes
if (m_itemUpdateQueue.empty())
return;
for (size_t i = 0; i < m_itemUpdateQueue.size(); ++i)
{
Item* item = m_itemUpdateQueue[i];
if (!item)
continue;
Bag* container = item->GetContainer();
if (item->GetState() != ITEM_REMOVED)
{
Item* test = GetItemByPos(item->GetBagSlot(), item->GetSlot());
if (test == nullptr)
{
ObjectGuid::LowType bagTestGUID = UI64LIT(0);
if (Item* test2 = GetItemByPos(INVENTORY_SLOT_BAG_0, item->GetBagSlot()))
bagTestGUID = test2->GetGUID().GetCounter();
TC_LOG_ERROR("entities.player", "Player::_SaveInventory: Player '%s' (%s) has incorrect values (Bag: %u, Slot: %u) for the item (%s, State: %d). The player doesn't have an item at that position.",
GetName().c_str(), GetGUID().ToString().c_str(), item->GetBagSlot(), item->GetSlot(), item->GetGUID().ToString().c_str(), (int32)item->GetState());
// according to the test that was just performed nothing should be in this slot, delete
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_BAG_SLOT);
stmt->setUInt64(0, bagTestGUID);
stmt->setUInt8(1, item->GetSlot());
stmt->setUInt64(2, GetGUID().GetCounter());
trans->Append(stmt);
RemoveTradeableItem(item);
RemoveEnchantmentDurationsReferences(item);
RemoveItemDurations(item);
// also THIS item should be somewhere else, cheat attempt
item->FSetState(ITEM_REMOVED); // we are IN updateQueue right now, can't use SetState which modifies the queue
DeleteRefundReference(item->GetGUID());
// don't skip, let the switch delete it
//continue;
}
else if (test != item)
{
TC_LOG_ERROR("entities.player", "Player::_SaveInventory: Player '%s' (%s) has incorrect values (Bag: %u, Slot: %u) for the item (%s). %s is there instead!",
GetName().c_str(), GetGUID().ToString().c_str(), item->GetBagSlot(), item->GetSlot(), item->GetGUID().ToString().c_str(), test->GetGUID().ToString().c_str());
// save all changes to the item...
if (item->GetState() != ITEM_NEW) // only for existing items, no duplicates
item->SaveToDB(trans);
// ...but do not save position in inventory
continue;
}
}
switch (item->GetState())
{
case ITEM_NEW:
case ITEM_CHANGED:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_INVENTORY_ITEM);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt64(1, container ? container->GetGUID().GetCounter() : UI64LIT(0));
stmt->setUInt8 (2, item->GetSlot());
stmt->setUInt64(3, item->GetGUID().GetCounter());
trans->Append(stmt);
break;
case ITEM_REMOVED:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM);
stmt->setUInt64(0, item->GetGUID().GetCounter());
trans->Append(stmt);
break;
case ITEM_UNCHANGED:
break;
}
item->SaveToDB(trans); // item have unchanged inventory record and can be save standalone
}
m_itemUpdateQueue.clear();
}
void Player::_SaveVoidStorage(CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt = nullptr;
for (uint8 i = 0; i < VOID_STORAGE_MAX_SLOT; ++i)
{
if (!_voidStorageItems[i]) // unused item
{
// DELETE FROM void_storage WHERE slot = ? AND playerGuid = ?
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_VOID_STORAGE_ITEM_BY_SLOT);
stmt->setUInt8(0, i);
stmt->setUInt64(1, GetGUID().GetCounter());
}
else
{
// REPLACE INTO character_void_storage (itemId, playerGuid, itemEntry, slot, creatorGuid, randomBonusListId, upgradeId, fixedScalingLevel, artifactKnowledgeLevel, bonusListIDs) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_VOID_STORAGE_ITEM);
stmt->setUInt64(0, _voidStorageItems[i]->ItemId);
stmt->setUInt64(1, GetGUID().GetCounter());
stmt->setUInt32(2, _voidStorageItems[i]->ItemEntry);
stmt->setUInt8(3, i);
stmt->setUInt64(4, _voidStorageItems[i]->CreatorGuid.GetCounter());
stmt->setUInt32(5, _voidStorageItems[i]->RandomBonusListId);
stmt->setUInt32(6, _voidStorageItems[i]->FixedScalingLevel);
stmt->setUInt32(7, _voidStorageItems[i]->ArtifactKnowledgeLevel);
stmt->setUInt8(8, AsUnderlyingType(_voidStorageItems[i]->Context));
std::ostringstream bonusListIDs;
for (int32 bonusListID : _voidStorageItems[i]->BonusListIDs)
bonusListIDs << bonusListID << ' ';
stmt->setString(9, bonusListIDs.str());
}
trans->Append(stmt);
}
}
void Player::_SaveCUFProfiles(CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt;
for (uint8 i = 0; i < MAX_CUF_PROFILES; ++i)
{
if (!_CUFProfiles[i]) // unused profile
{
// DELETE FROM character_cuf_profiles WHERE guid = ? and id = ?
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_CUF_PROFILES_BY_ID);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt8(1, i);
}
else
{
// REPLACE INTO character_cuf_profiles (guid, id, name, frameHeight, frameWidth, sortBy, healthText, boolOptions, unk146, unk147, unk148, unk150, unk152, unk154) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_CUF_PROFILES);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt8(1, i);
stmt->setString(2, _CUFProfiles[i]->ProfileName);
stmt->setUInt16(3, _CUFProfiles[i]->FrameHeight);
stmt->setUInt16(4, _CUFProfiles[i]->FrameWidth);
stmt->setUInt8(5, _CUFProfiles[i]->SortBy);
stmt->setUInt8(6, _CUFProfiles[i]->HealthText);
stmt->setUInt32(7, _CUFProfiles[i]->BoolOptions.to_ulong()); // 25 of 32 fields used, fits in an int
stmt->setUInt8(8, _CUFProfiles[i]->TopPoint);
stmt->setUInt8(9, _CUFProfiles[i]->BottomPoint);
stmt->setUInt8(10, _CUFProfiles[i]->LeftPoint);
stmt->setUInt16(11, _CUFProfiles[i]->TopOffset);
stmt->setUInt16(12, _CUFProfiles[i]->BottomOffset);
stmt->setUInt16(13, _CUFProfiles[i]->LeftOffset);
}
trans->Append(stmt);
}
}
void Player::_SaveMail(CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt;
for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
{
Mail* m = (*itr);
if (m->state == MAIL_STATE_CHANGED)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_MAIL);
stmt->setUInt8(0, uint8(m->HasItems() ? 1 : 0));
stmt->setInt64(1, m->expire_time);
stmt->setInt64(2, m->deliver_time);
stmt->setUInt64(3, m->money);
stmt->setUInt64(4, m->COD);
stmt->setUInt8(5, uint8(m->checked));
stmt->setUInt32(6, m->messageID);
trans->Append(stmt);
if (!m->removedItems.empty())
{
for (std::vector<ObjectGuid::LowType>::iterator itr2 = m->removedItems.begin(); itr2 != m->removedItems.end(); ++itr2)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM);
stmt->setUInt64(0, *itr2);
trans->Append(stmt);
}
m->removedItems.clear();
}
m->state = MAIL_STATE_UNCHANGED;
}
else if (m->state == MAIL_STATE_DELETED)
{
if (m->HasItems())
{
for (MailItemInfoVec::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2)
{
Item::DeleteFromDB(trans, itr2->item_guid);
AzeriteItem::DeleteFromDB(trans, itr2->item_guid);
AzeriteEmpoweredItem::DeleteFromDB(trans, itr2->item_guid);
}
}
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
stmt->setUInt32(0, m->messageID);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
stmt->setUInt32(0, m->messageID);
trans->Append(stmt);
}
}
//deallocate deleted mails...
for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end();)
{
if ((*itr)->state == MAIL_STATE_DELETED)
{
Mail* m = *itr;
m_mail.erase(itr);
delete m;
itr = m_mail.begin();
}
else
++itr;
}
m_mailsUpdated = false;
}
void Player::_SaveQuestStatus(CharacterDatabaseTransaction& trans)
{
bool isTransaction = bool(trans);
if (!isTransaction)
trans = CharacterDatabase.BeginTransaction();
CharacterDatabasePreparedStatement* stmt;
bool keepAbandoned = !(sWorld->GetCleaningFlags() & CharacterDatabaseCleaner::CLEANING_FLAG_QUESTSTATUS);
for (auto saveItr = m_QuestStatusSave.begin(); saveItr != m_QuestStatusSave.end(); ++saveItr)
{
if (saveItr->second == QUEST_DEFAULT_SAVE_TYPE)
{
auto statusItr = m_QuestStatus.find(saveItr->first);
if (statusItr != m_QuestStatus.end() && (keepAbandoned || statusItr->second.Status != QUEST_STATUS_NONE))
{
QuestStatusData const& qData = statusItr->second;
// Save main quest status and timer
stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_QUESTSTATUS);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, statusItr->first);
stmt->setUInt8(2, uint8(qData.Status));
stmt->setBool(3, qData.Explored);
stmt->setInt64(4, GetQuestSlotAcceptTime(qData.Slot));
stmt->setInt64(5, GetQuestSlotEndTime(qData.Slot));
trans->Append(stmt);
// Save objectives
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_BY_QUEST);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, saveItr->first);
trans->Append(stmt);
Quest const* quest = ASSERT_NOTNULL(sObjectMgr->GetQuestTemplate(saveItr->first));
for (QuestObjective const& obj : quest->GetObjectives())
{
int32 count = GetQuestSlotObjectiveData(qData.Slot, obj);
if (!count)
continue;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_QUESTSTATUS_OBJECTIVES);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, statusItr->first);
stmt->setUInt8(2, obj.StorageIndex);
stmt->setInt32(3, count);
trans->Append(stmt);
}
}
}
else
{
// Delete
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_BY_QUEST);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, saveItr->first);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_BY_QUEST);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, saveItr->first);
trans->Append(stmt);
}
}
m_QuestStatusSave.clear();
for (auto saveItr = m_RewardedQuestsSave.begin(); saveItr != m_RewardedQuestsSave.end(); ++saveItr)
{
if (saveItr->second == QUEST_DEFAULT_SAVE_TYPE)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_QUESTSTATUS_REWARDED);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, saveItr->first);
trans->Append(stmt);
}
else if (saveItr->second == QUEST_FORCE_DELETE_SAVE_TYPE || !keepAbandoned)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, saveItr->first);
trans->Append(stmt);
}
}
m_RewardedQuestsSave.clear();
if (!isTransaction)
CharacterDatabase.CommitTransaction(trans);
}
void Player::_SaveDailyQuestStatus(CharacterDatabaseTransaction& trans)
{
if (!m_DailyQuestChanged)
return;
m_DailyQuestChanged = false;
// save last daily quest time for all quests: we need only mostly reset time for reset check anyway
// we don't need transactions here.
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_DAILY);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
for (int32 questId : m_activePlayerData->DailyQuestsCompleted)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_QUESTSTATUS_DAILY);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, questId);
stmt->setInt64(2, m_lastDailyQuestTime);
trans->Append(stmt);
}
if (!m_DFQuests.empty())
{
for (DFQuestsDoneList::iterator itr = m_DFQuests.begin(); itr != m_DFQuests.end(); ++itr)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_QUESTSTATUS_DAILY);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, (*itr));
stmt->setInt64(2, m_lastDailyQuestTime);
trans->Append(stmt);
}
}
}
void Player::_SaveWeeklyQuestStatus(CharacterDatabaseTransaction& trans)
{
if (!m_WeeklyQuestChanged || m_weeklyquests.empty())
return;
// we don't need transactions here.
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_WEEKLY);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
for (QuestSet::const_iterator iter = m_weeklyquests.begin(); iter != m_weeklyquests.end(); ++iter)
{
uint32 questId = *iter;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_QUESTSTATUS_WEEKLY);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, questId);
trans->Append(stmt);
}
m_WeeklyQuestChanged = false;
}
void Player::_SaveSeasonalQuestStatus(CharacterDatabaseTransaction& trans)
{
if (!m_SeasonalQuestChanged)
return;
// we don't need transactions here.
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_SEASONAL);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
m_SeasonalQuestChanged = false;
if (m_seasonalquests.empty())
return;
for (SeasonalEventQuestMap::const_iterator iter = m_seasonalquests.begin(); iter != m_seasonalquests.end(); ++iter)
{
uint16 eventId = iter->first;
for (SeasonalQuestSet::const_iterator itr = iter->second.begin(); itr != iter->second.end(); ++itr)
{
uint32 questId = *itr;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_QUESTSTATUS_SEASONAL);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, questId);
stmt->setUInt32(2, eventId);
trans->Append(stmt);
}
}
}
void Player::_SaveMonthlyQuestStatus(CharacterDatabaseTransaction& trans)
{
if (!m_MonthlyQuestChanged || m_monthlyquests.empty())
return;
// we don't need transactions here.
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_MONTHLY);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
for (QuestSet::const_iterator iter = m_monthlyquests.begin(); iter != m_monthlyquests.end(); ++iter)
{
uint32 questId = *iter;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_QUESTSTATUS_MONTHLY);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, questId);
trans->Append(stmt);
}
m_MonthlyQuestChanged = false;
}
void Player::_SaveSkills(CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt;
// we don't need transactions here.
for (SkillStatusMap::iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end();)
{
if (itr->second.uState == SKILL_UNCHANGED)
{
++itr;
continue;
}
uint16 value = m_activePlayerData->Skill->SkillRank[itr->second.pos];
uint16 max = m_activePlayerData->Skill->SkillMaxRank[itr->second.pos];
switch (itr->second.uState)
{
case SKILL_NEW:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_SKILLS);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt16(1, uint16(itr->first));
stmt->setUInt16(2, value);
stmt->setUInt16(3, max);
trans->Append(stmt);
break;
case SKILL_CHANGED:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_SKILLS);
stmt->setUInt16(0, value);
stmt->setUInt16(1, max);
stmt->setUInt64(2, GetGUID().GetCounter());
stmt->setUInt16(3, uint16(itr->first));
trans->Append(stmt);
break;
case SKILL_DELETED:
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SKILL_BY_SKILL);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt16(1, uint16(itr->first));
trans->Append(stmt);
break;
default:
break;
}
itr->second.uState = SKILL_UNCHANGED;
++itr;
}
}
void Player::_SaveSpells(CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt;
for (PlayerSpellMap::iterator itr = m_spells.begin(); itr != m_spells.end();)
{
if (itr->second->state == PLAYERSPELL_REMOVED || itr->second->state == PLAYERSPELL_CHANGED)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_BY_SPELL);
stmt->setUInt32(0, itr->first);
stmt->setUInt64(1, GetGUID().GetCounter());
trans->Append(stmt);
}
// add only changed/new not dependent spells
if (!itr->second->dependent && (itr->second->state == PLAYERSPELL_NEW || itr->second->state == PLAYERSPELL_CHANGED))
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_SPELL);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, itr->first);
stmt->setBool(2, itr->second->active);
stmt->setBool(3, itr->second->disabled);
trans->Append(stmt);
}
if (itr->second->state == PLAYERSPELL_REMOVED)
{
delete itr->second;
itr = m_spells.erase(itr);
continue;
}
if (itr->second->state != PLAYERSPELL_TEMPORARY)
itr->second->state = PLAYERSPELL_UNCHANGED;
++itr;
}
}
void Player::_SaveStoredAuraTeleportLocations(CharacterDatabaseTransaction& trans)
{
for (auto itr = m_storedAuraTeleportLocations.begin(); itr != m_storedAuraTeleportLocations.end(); )
{
StoredAuraTeleportLocation& storedLocation = itr->second;
if (storedLocation.State == StoredAuraTeleportLocation::DELETED)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_AURA_STORED_LOCATION);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
itr = m_storedAuraTeleportLocations.erase(itr);
continue;
}
if (storedLocation.State == StoredAuraTeleportLocation::CHANGED)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_AURA_STORED_LOCATION);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_AURA_STORED_LOCATION);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, itr->first);
stmt->setUInt32(2, storedLocation.Loc.GetMapId());
stmt->setFloat(3, storedLocation.Loc.GetPositionX());
stmt->setFloat(4, storedLocation.Loc.GetPositionY());
stmt->setFloat(5, storedLocation.Loc.GetPositionZ());
stmt->setFloat(6, storedLocation.Loc.GetOrientation());
trans->Append(stmt);
}
++itr;
}
}
// save player stats -- only for external usage
// real stats will be recalculated on player login
void Player::_SaveStats(CharacterDatabaseTransaction& trans) const
{
// check if stat saving is enabled and if char level is high enough
if (!sWorld->getIntConfig(CONFIG_MIN_LEVEL_STAT_SAVE) || GetLevel() < sWorld->getIntConfig(CONFIG_MIN_LEVEL_STAT_SAVE))
return;
CharacterDatabasePreparedStatement* stmt;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_STATS);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
uint8 index = 0;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_STATS);
stmt->setUInt64(index++, GetGUID().GetCounter());
stmt->setUInt32(index++, GetMaxHealth());
for (uint8 i = 0; i < MAX_POWERS_PER_CLASS; ++i)
stmt->setUInt32(index++, m_unitData->MaxPower[i]);
for (uint8 i = 0; i < MAX_STATS; ++i)
stmt->setUInt32(index++, GetStat(Stats(i)));
for (int i = 0; i < MAX_SPELL_SCHOOL; ++i)
stmt->setUInt32(index++, GetResistance(SpellSchools(i)));
stmt->setFloat(index++, m_activePlayerData->BlockPercentage);
stmt->setFloat(index++, m_activePlayerData->DodgePercentage);
stmt->setFloat(index++, m_activePlayerData->ParryPercentage);
stmt->setFloat(index++, m_activePlayerData->CritPercentage);
stmt->setFloat(index++, m_activePlayerData->RangedCritPercentage);
stmt->setFloat(index++, m_activePlayerData->SpellCritPercentage);
stmt->setUInt32(index++, m_unitData->AttackPower);
stmt->setUInt32(index++, m_unitData->RangedAttackPower);
stmt->setUInt32(index++, GetBaseSpellPowerBonus());
stmt->setUInt32(index, m_activePlayerData->CombatRatings[CR_RESILIENCE_PLAYER_DAMAGE]);
trans->Append(stmt);
}
void Player::outDebugValues() const
{
if (!sLog->ShouldLog("entities.unit", LOG_LEVEL_DEBUG))
return;
TC_LOG_DEBUG("entities.unit", "HP is: \t\t\t" UI64FMTD "\t\tMP is: \t\t\t%u", GetMaxHealth(), GetMaxPower(POWER_MANA));
TC_LOG_DEBUG("entities.unit", "AGILITY is: \t\t%f\t\tSTRENGTH is: \t\t%f", GetStat(STAT_AGILITY), GetStat(STAT_STRENGTH));
TC_LOG_DEBUG("entities.unit", "INTELLECT is: \t\t%f", GetStat(STAT_INTELLECT));
TC_LOG_DEBUG("entities.unit", "STAMINA is: \t\t%f", GetStat(STAT_STAMINA));
TC_LOG_DEBUG("entities.unit", "Armor is: \t\t%u\t\tBlock is: \t\t%f", GetArmor(), *m_activePlayerData->BlockPercentage);
TC_LOG_DEBUG("entities.unit", "HolyRes is: \t\t%u\t\tFireRes is: \t\t%u", GetResistance(SPELL_SCHOOL_MASK_HOLY), GetResistance(SPELL_SCHOOL_MASK_FIRE));
TC_LOG_DEBUG("entities.unit", "NatureRes is: \t\t%u\t\tFrostRes is: \t\t%u", GetResistance(SPELL_SCHOOL_MASK_NATURE), GetResistance(SPELL_SCHOOL_MASK_FROST));
TC_LOG_DEBUG("entities.unit", "ShadowRes is: \t\t%u\t\tArcaneRes is: \t\t%u", GetResistance(SPELL_SCHOOL_MASK_SHADOW), GetResistance(SPELL_SCHOOL_MASK_ARCANE));
TC_LOG_DEBUG("entities.unit", "MIN_DAMAGE is: \t\t%f\tMAX_DAMAGE is: \t\t%f", *m_unitData->MinDamage, *m_unitData->MaxDamage);
TC_LOG_DEBUG("entities.unit", "MIN_OFFHAND_DAMAGE is: \t%f\tMAX_OFFHAND_DAMAGE is: \t%f", *m_unitData->MinOffHandDamage, *m_unitData->MaxOffHandDamage);
TC_LOG_DEBUG("entities.unit", "MIN_RANGED_DAMAGE is: \t%f\tMAX_RANGED_DAMAGE is: \t%f", *m_unitData->MinRangedDamage, *m_unitData->MaxRangedDamage);
TC_LOG_DEBUG("entities.unit", "ATTACK_TIME is: \t%u\t\tRANGE_ATTACK_TIME is: \t%u", GetBaseAttackTime(BASE_ATTACK), GetBaseAttackTime(RANGED_ATTACK));
}
/*********************************************************/
/*** FLOOD FILTER SYSTEM ***/
/*********************************************************/
void Player::UpdateSpeakTime()
{
// ignore chat spam protection for GMs in any mode
if (GetSession()->HasPermission(rbac::RBAC_PERM_SKIP_CHECK_CHAT_SPAM))
return;
time_t current = GameTime::GetGameTime();
if (m_speakTime > current)
{
uint32 max_count = sWorld->getIntConfig(CONFIG_CHATFLOOD_MESSAGE_COUNT);
if (!max_count)
return;
++m_speakCount;
if (m_speakCount >= max_count)
{
// prevent overwrite mute time, if message send just before mutes set, for example.
time_t new_mute = current + sWorld->getIntConfig(CONFIG_CHATFLOOD_MUTE_TIME);
if (GetSession()->m_muteTime < new_mute)
GetSession()->m_muteTime = new_mute;
m_speakCount = 0;
}
}
else
m_speakCount = 1;
m_speakTime = current + sWorld->getIntConfig(CONFIG_CHATFLOOD_MESSAGE_DELAY);
}
/*********************************************************/
/*** LOW LEVEL FUNCTIONS:Notifiers ***/
/*********************************************************/
void Player::SavePositionInDB(WorldLocation const& loc, uint16 zoneId, ObjectGuid guid, CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHARACTER_POSITION);
stmt->setFloat(0, loc.GetPositionX());
stmt->setFloat(1, loc.GetPositionY());
stmt->setFloat(2, loc.GetPositionZ());
stmt->setFloat(3, loc.GetOrientation());
stmt->setUInt16(4, uint16(loc.GetMapId()));
stmt->setUInt16(5, zoneId);
stmt->setUInt64(6, guid.GetCounter());
CharacterDatabase.ExecuteOrAppend(trans, stmt);
}
void Player::SetUInt32ValueInArray(Tokenizer& Tokenizer, uint16 index, uint32 value)
{
char buf[11];
snprintf(buf, 11, "%u", value);
if (index >= Tokenizer.size())
return;
Tokenizer[index] = buf;
}
void Player::SendAttackSwingCantAttack() const
{
SendDirectMessage(WorldPackets::Combat::AttackSwingError(WorldPackets::Combat::AttackSwingError::CantAttack).Write());
}
void Player::SendAttackSwingCancelAttack() const
{
SendDirectMessage(WorldPackets::Combat::CancelCombat().Write());
}
void Player::SendAttackSwingDeadTarget() const
{
SendDirectMessage(WorldPackets::Combat::AttackSwingError(WorldPackets::Combat::AttackSwingError::DeadTarget).Write());
}
void Player::SendAttackSwingNotInRange() const
{
SendDirectMessage(WorldPackets::Combat::AttackSwingError(WorldPackets::Combat::AttackSwingError::NotInRange).Write());
}
void Player::SendAttackSwingBadFacingAttack() const
{
SendDirectMessage(WorldPackets::Combat::AttackSwingError(WorldPackets::Combat::AttackSwingError::BadFacing).Write());
}
void Player::SendAutoRepeatCancel(Unit* target)
{
WorldPackets::Combat::CancelAutoRepeat cancelAutoRepeat;
cancelAutoRepeat.Guid = target->GetGUID(); // may be it's target guid
SendMessageToSet(cancelAutoRepeat.Write(), true);
}
void Player::SendExplorationExperience(uint32 Area, uint32 Experience) const
{
SendDirectMessage(WorldPackets::Misc::ExplorationExperience(Experience, Area).Write());
}
void Player::SendDungeonDifficulty(int32 forcedDifficulty /*= -1*/) const
{
WorldPackets::Misc::DungeonDifficultySet dungeonDifficultySet;
dungeonDifficultySet.DifficultyID = forcedDifficulty == -1 ? GetDungeonDifficultyID() : forcedDifficulty;
SendDirectMessage(dungeonDifficultySet.Write());
}
void Player::SendRaidDifficulty(bool legacy, int32 forcedDifficulty /*= -1*/) const
{
WorldPackets::Misc::RaidDifficultySet raidDifficultySet;
raidDifficultySet.DifficultyID = forcedDifficulty == -1 ? (legacy ? GetLegacyRaidDifficultyID() : GetRaidDifficultyID()) : forcedDifficulty;
raidDifficultySet.Legacy = legacy;
SendDirectMessage(raidDifficultySet.Write());
}
void Player::SendResetFailedNotify(uint32 /*mapid*/) const
{
WorldPackets::Instance::ResetFailedNotify data;
SendDirectMessage(data.Write());
}
/// Reset all solo instances and optionally send a message on success for each
void Player::ResetInstances(uint8 method, bool isRaid, bool isLegacy)
{
// method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_JOIN
// we assume that when the difficulty changes, all instances that can be reset will be
Difficulty diff = GetDungeonDifficultyID();
if (isRaid)
{
if (!isLegacy)
diff = GetRaidDifficultyID();
else
diff = GetLegacyRaidDifficultyID();
}
auto difficultyItr = m_boundInstances.find(diff);
if (difficultyItr == m_boundInstances.end())
return;
for (auto itr = difficultyItr->second.begin(); itr != difficultyItr->second.end();)
{
InstanceSave* p = itr->second.save;
MapEntry const* entry = sMapStore.LookupEntry(itr->first);
if (!entry || entry->IsRaid() != isRaid || !p->CanReset())
{
++itr;
continue;
}
if (method == INSTANCE_RESET_ALL)
{
// the "reset all instances" method can only reset normal maps
if (entry->IsRaid() || diff == DIFFICULTY_HEROIC)
{
++itr;
continue;
}
}
// if the map is loaded, reset it
Map* map = sMapMgr->FindMap(p->GetMapId(), p->GetInstanceId());
if (map && map->IsDungeon())
if (!map->ToInstanceMap()->Reset(method))
{
++itr;
continue;
}
// since this is a solo instance there should not be any players inside
if (method == INSTANCE_RESET_ALL || method == INSTANCE_RESET_CHANGE_DIFFICULTY)
SendResetInstanceSuccess(p->GetMapId());
p->DeleteFromDB();
difficultyItr->second.erase(itr++);
// the following should remove the instance save from the manager and delete it as well
p->RemovePlayer(this);
}
}
void Player::SendResetInstanceSuccess(uint32 MapId) const
{
WorldPackets::Instance::InstanceReset data;
data.MapID = MapId;
SendDirectMessage(data.Write());
}
void Player::SendResetInstanceFailed(ResetFailedReason reason, uint32 mapID) const
{
/*reasons for instance reset failure:
// 0: There are players inside the instance.
// 1: There are players offline in your party.
// 2>: There are players in your party attempting to zone into an instance.
*/
WorldPackets::Instance::InstanceResetFailed data;
data.MapID = mapID;
data.ResetFailedReason = reason;
SendDirectMessage(data.Write());
}
/*********************************************************/
/*** Update timers ***/
/*********************************************************/
///checks the 15 afk reports per 5 minutes limit
void Player::UpdateAfkReport(time_t currTime)
{
if (m_bgData.bgAfkReportedTimer <= currTime)
{
m_bgData.bgAfkReportedCount = 0;
m_bgData.bgAfkReportedTimer = currTime+5*MINUTE;
}
}
void Player::SetContestedPvP(Player* attackedPlayer)
{
if (attackedPlayer && (attackedPlayer == this || (duel && duel->Opponent == attackedPlayer)))
return;
SetContestedPvPTimer(30000);
if (!HasUnitState(UNIT_STATE_ATTACK_PLAYER))
{
AddUnitState(UNIT_STATE_ATTACK_PLAYER);
AddPlayerFlag(PLAYER_FLAGS_CONTESTED_PVP);
// call MoveInLineOfSight for nearby contested guards
Trinity::AIRelocationNotifier notifier(*this);
Cell::VisitWorldObjects(this, notifier, GetVisibilityRange());
}
for (Unit* unit : m_Controlled)
{
if (!unit->HasUnitState(UNIT_STATE_ATTACK_PLAYER))
{
unit->AddUnitState(UNIT_STATE_ATTACK_PLAYER);
Trinity::AIRelocationNotifier notifier(*unit);
Cell::VisitWorldObjects(this, notifier, GetVisibilityRange());
}
}
}
void Player::UpdateContestedPvP(uint32 diff)
{
if (!m_contestedPvPTimer || IsInCombat())
return;
if (m_contestedPvPTimer <= diff)
ResetContestedPvP();
else
m_contestedPvPTimer -= diff;
}
void Player::ResetContestedPvP()
{
ClearUnitState(UNIT_STATE_ATTACK_PLAYER);
RemovePlayerFlag(PLAYER_FLAGS_CONTESTED_PVP);
m_contestedPvPTimer = 0;
}
void Player::UpdatePvPFlag(time_t currTime)
{
if (!IsPvP())
return;
if (!pvpInfo.EndTimer || (currTime < pvpInfo.EndTimer +300) || pvpInfo.IsHostile)
return;
if (pvpInfo.EndTimer <= currTime)
{
pvpInfo.EndTimer = 0;
RemovePlayerFlag(PLAYER_FLAGS_PVP_TIMER);
}
UpdatePvP(false);
}
void Player::UpdateDuelFlag(time_t currTime)
{
if (duel && duel->State == DUEL_STATE_COUNTDOWN && duel->StartTime <= currTime)
{
sScriptMgr->OnPlayerDuelStart(this, duel->Opponent);
SetDuelTeam(1);
duel->Opponent->SetDuelTeam(2);
duel->State = DUEL_STATE_IN_PROGRESS;
duel->Opponent->duel->State = DUEL_STATE_IN_PROGRESS;
}
}
Pet* Player::GetPet() const
{
ObjectGuid pet_guid = GetPetGUID();
if (!pet_guid.IsEmpty())
{
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 this slot
//TC_LOG_ERROR("entities.player", "Player::GetPet: Pet %u does not exist.", GUID_LOPART(pet_guid));
//const_cast<Player*>(this)->SetPetGUID(0);
}
return nullptr;
}
void Player::RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent)
{
if (!pet)
pet = GetPet();
if (pet)
{
TC_LOG_DEBUG("entities.pet", "Player::RemovePet: Player '%s' (%s), Pet (Entry: %u, Mode: %u, ReturnReagent: %u)",
GetName().c_str(), GetGUID().ToString().c_str(), pet->GetEntry(), mode, returnreagent);
if (pet->m_removed)
return;
}
if (returnreagent && (pet || m_temporaryUnsummonedPetNumber) && !InBattleground())
{
//returning of reagents only for players, so best done here
uint32 spellId = pet ? *pet->m_unitData->CreatedBySpell : m_oldpetspell;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, GetMap()->GetDifficultyID());
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 || pet->GetOwnerGUID() != GetGUID())
return;
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);
SetMinion(pet, false);
pet->AddObjectToRemoveList();
pet->m_removed = true;
if (pet->isControlled())
{
WorldPackets::Pet::PetSpells petSpellsPacket;
SendDirectMessage(petSpellsPacket.Write());
if (GetGroup())
SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET);
}
}
void Player::SendTameFailure(PetTameResult result)
{
WorldPackets::Pet::PetTameFailure petTameFailure;
petTameFailure.Result = AsUnderlyingType(result);
SendDirectMessage(petTameFailure.Write());
}
void Player::AddPetAura(PetAura const* petSpell)
{
m_petAuras.insert(petSpell);
if (Pet* pet = GetPet())
pet->CastPetAura(petSpell);
}
void Player::RemovePetAura(PetAura const* petSpell)
{
m_petAuras.erase(petSpell);
if (Pet* pet = GetPet())
pet->RemoveAurasDueToSpell(petSpell->GetAura(pet->GetEntry()));
}
Creature* Player::GetSummonedBattlePet()
{
if (Creature* summonedBattlePet = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, GetCritterGUID()))
if (!GetSummonedBattlePetGUID().IsEmpty() && GetSummonedBattlePetGUID() == summonedBattlePet->GetBattlePetCompanionGUID())
return summonedBattlePet;
return nullptr;
}
void Player::SetBattlePetData(BattlePets::BattlePet const* pet)
{
if (pet)
{
SetSummonedBattlePetGUID(pet->PacketInfo.Guid);
SetCurrentBattlePetBreedQuality(pet->PacketInfo.Quality);
SetBattlePetCompanionExperience(pet->PacketInfo.Exp);
SetWildBattlePetLevel(pet->PacketInfo.Level);
}
else
{
SetSummonedBattlePetGUID(ObjectGuid::Empty);
SetCurrentBattlePetBreedQuality(AsUnderlyingType(BattlePets::BattlePetBreedQuality::Poor));
SetBattlePetCompanionExperience(0);
SetWildBattlePetLevel(0);
}
}
void Player::StopCastingCharm()
{
Unit* charm = GetCharmed();
if (!charm)
return;
if (charm->GetTypeId() == TYPEID_UNIT)
{
if (charm->ToCreature()->HasUnitTypeMask(UNIT_MASK_PUPPET))
static_cast<Puppet*>(charm)->UnSummon();
else if (charm->IsVehicle())
ExitVehicle();
}
if (!GetCharmedGUID().IsEmpty())
charm->RemoveCharmAuras();
if (!GetCharmedGUID().IsEmpty())
{
TC_LOG_FATAL("entities.player", "Player::StopCastingCharm: Player '%s' (%s) is not able to uncharm unit (%s)", GetName().c_str(), GetGUID().ToString().c_str(), GetCharmedGUID().ToString().c_str());
if (!charm->GetCharmerGUID().IsEmpty())
{
TC_LOG_FATAL("entities.player", "Player::StopCastingCharm: Charmed unit has charmer %s", charm->GetCharmerGUID().ToString().c_str());
ABORT();
}
SetCharm(charm, false);
}
}
void Player::Say(std::string const& text, Language language, WorldObject const* /*= nullptr*/)
{
std::string _text(text);
sScriptMgr->OnPlayerChat(this, CHAT_MSG_SAY, language, _text);
SendChatMessageToSetInRange(CHAT_MSG_SAY, language, std::move(_text), sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY));
}
void Player::SendChatMessageToSetInRange(ChatMsg chatMsg, Language language, std::string&& text, float range)
{
Trinity::CustomChatTextBuilder builder(this, chatMsg, std::move(text), language, this);
Trinity::LocalizedDo<Trinity::CustomChatTextBuilder> localizer(builder);
// Send to self
localizer(this);
// Send to players
Trinity::MessageDistDeliverer<Trinity::LocalizedDo<Trinity::CustomChatTextBuilder>> notifier(this, localizer, range);
Cell::VisitWorldObjects(this, notifier, range);
}
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 const& text, Language language, WorldObject const* /*= nullptr*/)
{
std::string _text(text);
sScriptMgr->OnPlayerChat(this, CHAT_MSG_YELL, language, _text);
SendChatMessageToSetInRange(CHAT_MSG_YELL, language, std::move(_text), sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL));
}
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 const& text, WorldObject const* /*= nullptr*/, bool /*= false*/)
{
std::string _text(text);
sScriptMgr->OnPlayerChat(this, CHAT_MSG_EMOTE, LANG_UNIVERSAL, _text);
WorldPackets::Chat::Chat packet;
packet.Initialize(CHAT_MSG_EMOTE, LANG_UNIVERSAL, this, this, _text);
SendMessageToSetInRange(packet.Write(), sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), true, !GetSession()->HasPermission(rbac::RBAC_PERM_TWO_SIDE_INTERACTION_CHAT));
}
void Player::WhisperAddon(std::string const& text, std::string const& prefix, bool isLogged, Player* receiver)
{
std::string _text(text);
sScriptMgr->OnPlayerChat(this, CHAT_MSG_WHISPER, uint32(isLogged ? LANG_ADDON_LOGGED : LANG_ADDON), _text, receiver);
if (!receiver->GetSession()->IsAddonRegistered(prefix))
return;
WorldPackets::Chat::Chat packet;
packet.Initialize(CHAT_MSG_WHISPER, isLogged ? LANG_ADDON_LOGGED : LANG_ADDON, this, this, text, 0, "", DEFAULT_LOCALE, prefix);
receiver->SendDirectMessage(packet.Write());
}
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 const& 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);
sScriptMgr->OnPlayerChat(this, CHAT_MSG_WHISPER, language, _text, target);
WorldPackets::Chat::Chat packet;
packet.Initialize(CHAT_MSG_WHISPER, language, this, this, _text);
target->SendDirectMessage(packet.Write());
// rest stuff shouldn't happen in case of addon message
if (isAddonMessage)
return;
packet.Initialize(CHAT_MSG_WHISPER_INFORM, language, target, target, _text);
SendDirectMessage(packet.Write());
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;
BroadcastTextEntry const* bct = sBroadcastTextStore.LookupEntry(textId);
if (!bct)
{
TC_LOG_ERROR("entities.unit", "WorldObject::Whisper: `broadcast_text` was not %u found", textId);
return;
}
LocaleConstant locale = target->GetSession()->GetSessionDbLocaleIndex();
WorldPackets::Chat::Chat packet;
packet.Initialize(CHAT_MSG_WHISPER, LANG_UNIVERSAL, this, target, DB2Manager::GetBroadcastTextValue(bct, locale, GetGender()));
target->SendDirectMessage(packet.Write());
}
bool Player::CanUnderstandLanguage(Language language) const
{
if (IsGameMaster())
return true;
for (std::pair<uint32 const, LanguageDesc> const& languageDesc : sLanguageMgr->GetLanguageDescById(language))
if (languageDesc.second.SkillId && HasSkill(languageDesc.second.SkillId))
return true;
if (HasAuraTypeWithMiscvalue(SPELL_AURA_COMPREHEND_LANGUAGE, language))
return true;
return false;
}
Item* Player::GetMItem(ObjectGuid::LowType id)
{
ItemMap::const_iterator itr = mMitems.find(id);
return itr != mMitems.end() ? itr->second : nullptr;
}
void Player::AddMItem(Item* it)
{
ASSERT(it);
//ASSERT deleted, because items can be added before loading
mMitems[it->GetGUID().GetCounter()] = it;
}
bool Player::RemoveMItem(ObjectGuid::LowType id)
{
return mMitems.erase(id) ? true : false;
}
void Player::SendOnCancelExpectedVehicleRideAura() const
{
SendDirectMessage(WorldPackets::Vehicle::OnCancelExpectedRideVehicleAura().Write());
}
void Player::PetSpellInitialize()
{
Pet* pet = GetPet();
if (!pet)
return;
CharmInfo* charmInfo = pet->GetCharmInfo();
WorldPackets::Pet::PetSpells petSpellsPacket;
petSpellsPacket.PetGUID = pet->GetGUID();
petSpellsPacket._CreatureFamily = pet->GetCreatureTemplate()->family; // creature family (required for pet talents)
petSpellsPacket.Specialization = pet->GetSpecialization();
petSpellsPacket.TimeLimit = pet->GetDuration();
petSpellsPacket.ReactState = pet->GetReactState();
petSpellsPacket.CommandState = charmInfo->GetCommandState();
// action bar loop
for (uint32 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i)
petSpellsPacket.ActionButtons[i] = charmInfo->GetActionBarEntry(i)->packedData;
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;
petSpellsPacket.Actions.push_back(MAKE_UNIT_ACTION_BUTTON(itr->first, itr->second.active));
}
}
// Cooldowns
pet->GetSpellHistory()->WritePacket(&petSpellsPacket);
SendDirectMessage(petSpellsPacket.Write());
}
void Player::PossessSpellInitialize()
{
Unit* charm = GetCharmed();
if (!charm)
return;
CharmInfo* charmInfo = charm->GetCharmInfo();
if (!charmInfo)
{
TC_LOG_ERROR("entities.player", "Player::PossessSpellInitialize: charm (%s) has no charminfo!", charm->GetGUID().ToString().c_str());
return;
}
WorldPackets::Pet::PetSpells petSpellsPacket;
petSpellsPacket.PetGUID = charm->GetGUID();
for (uint32 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i)
petSpellsPacket.ActionButtons[i] = charmInfo->GetActionBarEntry(i)->packedData;
// Cooldowns
charm->GetSpellHistory()->WritePacket(&petSpellsPacket);
SendDirectMessage(petSpellsPacket.Write());
}
void Player::VehicleSpellInitialize()
{
Creature* vehicle = GetVehicleCreatureBase();
if (!vehicle)
return;
WorldPackets::Pet::PetSpells petSpells;
petSpells.PetGUID = vehicle->GetGUID();
petSpells._CreatureFamily = 0; // Pet Family (0 for all vehicles)
petSpells.Specialization = 0;
petSpells.TimeLimit = vehicle->IsSummon() ? vehicle->ToTempSummon()->GetTimer() : 0;
petSpells.ReactState = vehicle->GetReactState();
petSpells.CommandState = COMMAND_FOLLOW;
petSpells.Flag = 0x8;
for (uint32 i = 0; i < MAX_SPELL_CONTROL_BAR; ++i)
petSpells.ActionButtons[i] = MAKE_UNIT_ACTION_BUTTON(0, i + 8);
for (uint32 i = 0; i < MAX_CREATURE_SPELLS; ++i)
{
uint32 spellId = vehicle->m_spells[i];
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, GetMap()->GetDifficultyID());
if (!spellInfo)
continue;
if (!sConditionMgr->IsObjectMeetingVehicleSpellConditions(vehicle->GetEntry(), spellId, this, vehicle))
{
TC_LOG_DEBUG("condition", "Player::VehicleSpellInitialize: Player '%s' (%s) doesn't meet conditions for vehicle (Entry: %u, Spell: %u)",
GetName().c_str(), GetGUID().ToString().c_str(), vehicle->ToCreature()->GetEntry(), spellId);
continue;
}
if (spellInfo->IsPassive())
vehicle->CastSpell(vehicle, spellInfo->Id, true);
petSpells.ActionButtons[i] = MAKE_UNIT_ACTION_BUTTON(spellId, i + 8);
}
// Cooldowns
vehicle->GetSpellHistory()->WritePacket(&petSpells);
SendDirectMessage(petSpells.Write());
}
void Player::CharmSpellInitialize()
{
Unit* charm = GetFirstControlled();
if (!charm)
return;
CharmInfo* charmInfo = charm->GetCharmInfo();
if (!charmInfo)
{
TC_LOG_ERROR("entities.player", "Player::CharmSpellInitialize(): Player '%s' (%s) has a charm (%s) but no no charminfo!",
GetName().c_str(), GetGUID().ToString().c_str(), charm->GetGUID().ToString().c_str());
return;
}
WorldPackets::Pet::PetSpells petSpells;
petSpells.PetGUID = charm->GetGUID();
if (charm->GetTypeId() == TYPEID_UNIT)
{
petSpells.ReactState = charm->ToCreature()->GetReactState();
petSpells.CommandState = charmInfo->GetCommandState();
}
for (uint32 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i)
petSpells.ActionButtons[i] = charmInfo->GetActionBarEntry(i)->packedData;
for (uint32 i = 0; i < MAX_SPELL_CHARM; ++i)
{
CharmSpellInfo* cspell = charmInfo->GetCharmSpell(i);
if (cspell->GetAction())
petSpells.Actions.push_back(cspell->packedData);
}
// Cooldowns
if (charm->GetTypeId() != TYPEID_PLAYER)
charm->GetSpellHistory()->WritePacket(&petSpells);
SendDirectMessage(petSpells.Write());
}
void Player::SendRemoveControlBar() const
{
WorldPackets::Pet::PetSpells packet;
SendDirectMessage(packet.Write());
}
bool Player::IsAffectedBySpellmod(SpellInfo const* spellInfo, SpellModifier const* mod, Spell* spell)
{
if (!mod || !spellInfo)
return false;
// First time this aura applies a mod to us and is out of charges
if (spell && mod->ownerAura->IsUsingCharges() && !mod->ownerAura->GetCharges() && !spell->m_appliedMods.count(mod->ownerAura))
return false;
// +duration to infinite duration spells making them limited
if (mod->op == SpellModOp::Duration && spellInfo->GetDuration() == -1)
return false;
// mod crit to spells that can't crit
if (mod->op == SpellModOp::CritChance && !spellInfo->HasAttribute(SPELL_ATTR0_CU_CAN_CRIT))
return false;
return spellInfo->IsAffectedBySpellMod(mod);
}
template <class T>
void Player::GetSpellModValues(SpellInfo const* spellInfo, SpellModOp op, Spell* spell, T base, int32* flat, float* pct) const
{
ASSERT(flat && pct);
*flat = 0;
*pct = 1.0f;
// Drop charges for triggering spells instead of triggered ones
if (m_spellModTakingSpell)
spell = m_spellModTakingSpell;
switch (op)
{
// special case, if a mod makes spell instant, only consume that mod
case SpellModOp::ChangeCastTime:
{
SpellModifier* modInstantSpell = nullptr;
for (SpellModifier* mod : m_spellMods[AsUnderlyingType(op)][SPELLMOD_PCT])
{
if (!IsAffectedBySpellmod(spellInfo, mod, spell))
continue;
if (base < T(10000) && static_cast<SpellModifierByClassMask*>(mod)->value <= -100)
{
modInstantSpell = mod;
break;
}
}
if (!modInstantSpell)
{
for (SpellModifier* mod : m_spellMods[AsUnderlyingType(op)][SPELLMOD_LABEL_PCT])
{
if (!IsAffectedBySpellmod(spellInfo, mod, spell))
continue;
if (base < T(10000) && static_cast<SpellPctModifierByLabel*>(mod)->value.ModifierValue <= -1.0f)
{
modInstantSpell = mod;
break;
}
}
}
if (modInstantSpell)
{
Player::ApplyModToSpell(modInstantSpell, spell);
*pct = 0.0f;
return;
}
break;
}
// special case if two mods apply 100% critical chance, only consume one
case SpellModOp::CritChance:
{
SpellModifier* modCritical = nullptr;
for (SpellModifier* mod : m_spellMods[AsUnderlyingType(op)][SPELLMOD_FLAT])
{
if (!IsAffectedBySpellmod(spellInfo, mod, spell))
continue;
if (static_cast<SpellModifierByClassMask*>(mod)->value >= 100)
{
modCritical = mod;
break;
}
}
if (!modCritical)
{
for (SpellModifier* mod : m_spellMods[AsUnderlyingType(op)][SPELLMOD_LABEL_FLAT])
{
if (!IsAffectedBySpellmod(spellInfo, mod, spell))
continue;
if (static_cast<SpellFlatModifierByLabel*>(mod)->value.ModifierValue >= 100)
{
modCritical = mod;
break;
}
}
}
if (modCritical)
{
Player::ApplyModToSpell(modCritical, spell);
*flat = 100;
return;
}
break;
}
default:
break;
}
for (SpellModifier* mod : m_spellMods[AsUnderlyingType(op)][SPELLMOD_FLAT])
{
if (!IsAffectedBySpellmod(spellInfo, mod, spell))
continue;
*flat += static_cast<SpellModifierByClassMask*>(mod)->value;
Player::ApplyModToSpell(mod, spell);
}
for (SpellModifier* mod : m_spellMods[AsUnderlyingType(op)][SPELLMOD_LABEL_FLAT])
{
if (!IsAffectedBySpellmod(spellInfo, mod, spell))
continue;
*flat += static_cast<SpellFlatModifierByLabel*>(mod)->value.ModifierValue;
Player::ApplyModToSpell(mod, spell);
}
for (SpellModifier* mod : m_spellMods[AsUnderlyingType(op)][SPELLMOD_PCT])
{
if (!IsAffectedBySpellmod(spellInfo, mod, spell))
continue;
// skip percent mods for null basevalue (most important for spell mods with charges)
if (base + *flat == T(0))
continue;
// special case (skip > 10sec spell casts for instant cast setting)
if (op == SpellModOp::ChangeCastTime)
{
if (base >= T(10000) && static_cast<SpellModifierByClassMask*>(mod)->value <= -100)
continue;
}
*pct *= 1.0f + CalculatePct(1.0f, static_cast<SpellModifierByClassMask*>(mod)->value);
Player::ApplyModToSpell(mod, spell);
}
for (SpellModifier* mod : m_spellMods[AsUnderlyingType(op)][SPELLMOD_LABEL_PCT])
{
if (!IsAffectedBySpellmod(spellInfo, mod, spell))
continue;
// skip percent mods for null basevalue (most important for spell mods with charges)
if (base + *flat == T(0))
continue;
// special case (skip > 10sec spell casts for instant cast setting)
if (op == SpellModOp::ChangeCastTime)
{
if (base >= T(10000) && static_cast<SpellPctModifierByLabel*>(mod)->value.ModifierValue <= -1.0f)
continue;
}
*pct *= static_cast<SpellPctModifierByLabel*>(mod)->value.ModifierValue;
Player::ApplyModToSpell(mod, spell);
}
}
template TC_GAME_API void Player::GetSpellModValues(SpellInfo const* spellInfo, SpellModOp op, Spell* spell, int32 base, int32* flat, float* pct) const;
template TC_GAME_API void Player::GetSpellModValues(SpellInfo const* spellInfo, SpellModOp op, Spell* spell, uint32 base, int32* flat, float* pct) const;
template TC_GAME_API void Player::GetSpellModValues(SpellInfo const* spellInfo, SpellModOp op, Spell* spell, float base, int32* flat, float* pct) const;
template <class T>
void Player::ApplySpellMod(SpellInfo const* spellInfo, SpellModOp op, T& basevalue, Spell* spell /*= nullptr*/) const
{
float totalmul = 1.0f;
int32 totalflat = 0;
GetSpellModValues(spellInfo, op, spell, basevalue, &totalflat, &totalmul);
basevalue = T(float(basevalue + totalflat) * totalmul);
}
template TC_GAME_API void Player::ApplySpellMod(SpellInfo const* spellInfo, SpellModOp op, int32& basevalue, Spell* spell) const;
template TC_GAME_API void Player::ApplySpellMod(SpellInfo const* spellInfo, SpellModOp op, uint32& basevalue, Spell* spell) const;
template TC_GAME_API void Player::ApplySpellMod(SpellInfo const* spellInfo, SpellModOp op, float& basevalue, Spell* spell) const;
void Player::AddSpellMod(SpellModifier* mod, bool apply)
{
TC_LOG_DEBUG("spells", "Player::AddSpellMod: Player '%s' (%s), SpellID: %d", GetName().c_str(), GetGUID().ToString().c_str(), mod->spellId);
/// First, manipulate our spellmodifier container
if (apply)
m_spellMods[AsUnderlyingType(mod->op)][mod->type].insert(mod);
else
m_spellMods[AsUnderlyingType(mod->op)][mod->type].erase(mod);
/// Now, send spellmodifier packet
switch (mod->type)
{
case SPELLMOD_FLAT:
case SPELLMOD_PCT:
if (!IsLoading())
{
OpcodeServer opcode = (mod->type == SPELLMOD_FLAT) ? SMSG_SET_FLAT_SPELL_MODIFIER : SMSG_SET_PCT_SPELL_MODIFIER;
WorldPackets::Spells::SetSpellModifier packet(opcode);
/// @todo Implement sending of bulk modifiers instead of single
packet.Modifiers.resize(1);
WorldPackets::Spells::SpellModifier& spellMod = packet.Modifiers[0];
spellMod.ModIndex = AsUnderlyingType(mod->op);
for (int eff = 0; eff < 128; ++eff)
{
flag128 mask;
mask[eff / 32] = 1u << (eff % 32);
if (static_cast<SpellModifierByClassMask const*>(mod)->mask & mask)
{
WorldPackets::Spells::SpellModifierData modData;
if (mod->type == SPELLMOD_FLAT)
{
modData.ModifierValue = 0.0f;
for (SpellModifier* spellMod : m_spellMods[AsUnderlyingType(mod->op)][SPELLMOD_FLAT])
if (static_cast<SpellModifierByClassMask const*>(spellMod)->mask & mask)
modData.ModifierValue += static_cast<SpellModifierByClassMask const*>(spellMod)->value;
}
else
{
modData.ModifierValue = 1.0f;
for (SpellModifier* spellMod : m_spellMods[AsUnderlyingType(mod->op)][SPELLMOD_PCT])
if (static_cast<SpellModifierByClassMask const*>(spellMod)->mask & mask)
modData.ModifierValue *= 1.0f + CalculatePct(1.0f, static_cast<SpellModifierByClassMask const*>(spellMod)->value);
}
modData.ClassIndex = eff;
spellMod.ModifierData.push_back(modData);
}
}
SendDirectMessage(packet.Write());
}
break;
case SPELLMOD_LABEL_FLAT:
if (apply)
{
AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData)
.ModifyValue(&UF::ActivePlayerData::SpellFlatModByLabel)) = static_cast<SpellFlatModifierByLabel const*>(mod)->value;
}
else
{
int32 firstIndex = m_activePlayerData->SpellFlatModByLabel.FindIndex(static_cast<SpellFlatModifierByLabel const*>(mod)->value);
if (firstIndex >= 0)
RemoveDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData)
.ModifyValue(&UF::ActivePlayerData::SpellFlatModByLabel), firstIndex);
}
break;
case SPELLMOD_LABEL_PCT:
if (apply)
{
AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData)
.ModifyValue(&UF::ActivePlayerData::SpellPctModByLabel)) = static_cast<SpellPctModifierByLabel const*>(mod)->value;
}
else
{
int32 firstIndex = m_activePlayerData->SpellPctModByLabel.FindIndex(static_cast<SpellPctModifierByLabel const*>(mod)->value);
if (firstIndex >= 0)
RemoveDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData)
.ModifyValue(&UF::ActivePlayerData::SpellPctModByLabel), firstIndex);
}
break;
default:
break;
}
}
void Player::ApplyModToSpell(SpellModifier* mod, Spell* spell)
{
if (!spell)
return;
// don't do anything with no charges
if (mod->ownerAura->IsUsingCharges() && !mod->ownerAura->GetCharges())
return;
// register inside spell, proc system uses this to drop charges
spell->m_appliedMods.insert(mod->ownerAura);
}
void Player::SetSpellModTakingSpell(Spell* spell, bool apply)
{
if (apply && m_spellModTakingSpell != nullptr)
return;
if (!apply && (!m_spellModTakingSpell || m_spellModTakingSpell != spell))
return;
m_spellModTakingSpell = apply ? spell : nullptr;
}
void Player::SendSpellModifiers() const
{
WorldPackets::Spells::SetSpellModifier flatMods(SMSG_SET_FLAT_SPELL_MODIFIER);
WorldPackets::Spells::SetSpellModifier pctMods(SMSG_SET_PCT_SPELL_MODIFIER);
for (uint8 i = 0; i < MAX_SPELLMOD; ++i)
{
WorldPackets::Spells::SpellModifier flatMod;
flatMod.ModifierData.resize(128);
WorldPackets::Spells::SpellModifier pctMod;
pctMod.ModifierData.resize(128);
flatMod.ModIndex = pctMod.ModIndex = i;
for (uint8 j = 0; j < 128; ++j)
{
flag128 mask;
mask[j / 32] = 1u << (j % 32);
flatMod.ModifierData[j].ClassIndex = j;
flatMod.ModifierData[j].ModifierValue = 0.0f;
pctMod.ModifierData[j].ClassIndex = j;
pctMod.ModifierData[j].ModifierValue = 1.0f;
for (SpellModifier* mod : m_spellMods[i][SPELLMOD_FLAT])
if (static_cast<SpellModifierByClassMask const*>(mod)->mask & mask)
flatMod.ModifierData[j].ModifierValue += static_cast<SpellModifierByClassMask const*>(mod)->value;
for (SpellModifier* mod : m_spellMods[i][SPELLMOD_PCT])
if (static_cast<SpellModifierByClassMask const*>(mod)->mask & mask)
pctMod.ModifierData[j].ModifierValue *= 1.0f + CalculatePct(1.0f, static_cast<SpellModifierByClassMask const*>(mod)->value);
}
flatMod.ModifierData.erase(std::remove_if(flatMod.ModifierData.begin(), flatMod.ModifierData.end(), [](WorldPackets::Spells::SpellModifierData const& mod)
{
return G3D::fuzzyEq(mod.ModifierValue, 0.0f);
}), flatMod.ModifierData.end());
pctMod.ModifierData.erase(std::remove_if(pctMod.ModifierData.begin(), pctMod.ModifierData.end(), [](WorldPackets::Spells::SpellModifierData const& mod)
{
return G3D::fuzzyEq(mod.ModifierValue, 1.0f);
}), pctMod.ModifierData.end());
flatMods.Modifiers.emplace_back(std::move(flatMod));
pctMods.Modifiers.emplace_back(std::move(pctMod));
}
if (!flatMods.Modifiers.empty())
SendDirectMessage(flatMods.Write());
if (!pctMods.Modifiers.empty())
SendDirectMessage(pctMods.Write());
}
// send Proficiency
void Player::SendProficiency(ItemClass itemClass, uint32 itemSubclassMask) const
{
WorldPackets::Item::SetProficiency packet;
packet.ProficiencyMask = itemSubclassMask;
packet.ProficiencyClass = itemClass;
SendDirectMessage(packet.Write());
}
void Player::RemovePetitionsAndSigns(ObjectGuid guid)
{
sPetitionMgr->RemoveSignaturesBySigner(guid);
sPetitionMgr->RemovePetitionsByOwner(guid);
}
void Player::LeaveAllArenaTeams(ObjectGuid guid)
{
CharacterCacheEntry const* characterInfo = sCharacterCache->GetCharacterCacheByGuid(guid);
if (!characterInfo)
return;
for (uint8 i = 0; i < MAX_ARENA_SLOT; ++i)
{
uint32 arenaTeamId = characterInfo->ArenaTeamId[i];
if (arenaTeamId != 0)
{
ArenaTeam* arenaTeam = sArenaTeamMgr->GetArenaTeamById(arenaTeamId);
if (arenaTeam)
arenaTeam->DelMember(guid, true);
}
}
}
bool Player::ActivateTaxiPathTo(std::vector<uint32> const& nodes, Creature* npc /*= nullptr*/, uint32 spellid /*= 0*/, uint32 preferredMountDisplay /*= 0*/)
{
if (nodes.size() < 2)
{
GetSession()->SendActivateTaxiReply(ERR_TAXINOSUCHPATH);
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_REMOVE_CLIENT_CONTROL))
return false;
// taximaster case
if (npc)
{
// not let cheating with start flight mounted
RemoveAurasByType(SPELL_AURA_MOUNTED);
if (GetDisplayId() != GetNativeDisplayId())
RestoreDisplayId(true);
if (IsDisallowedMountForm(GetTransformSpell(), FORM_NONE, GetDisplayId()))
{
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 (GetDisplayId() != GetNativeDisplayId())
RestoreDisplayId(true);
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;
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 == NULL) 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;
if (node->Flags & TAXI_NODE_FLAG_USE_FAVORITE_MOUNT && preferredMountDisplay)
mount_display_id = preferredMountDisplay;
else
mount_display_id = sObjectMgr->GetTaxiMountDisplayId(sourcenode, GetTeam(), 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;
}
uint64 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
UpdateCriteria(CriteriaType::BuyTaxi, 1);
// prevent stealth flight
//RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Interacting);
if (sWorld->getBoolConfig(CONFIG_INSTANT_TAXI))
{
TaxiNodesEntry const* lastPathNode = sTaxiNodesStore.LookupEntry(nodes[nodes.size()-1]);
ASSERT(lastPathNode);
m_taxi.ClearTaxiDestinations();
ModifyMoney(-int64(totalcost));
UpdateCriteria(CriteriaType::MoneySpentOnTaxis, totalcost);
TeleportTo(lastPathNode->ContinentID, lastPathNode->Pos.X, lastPathNode->Pos.Y, lastPathNode->Pos.Z, GetOrientation());
return false;
}
else
{
ModifyMoney(-int64(firstcost));
UpdateCriteria(CriteriaType::MoneySpentOnTaxis, firstcost);
GetSession()->SendActivateTaxiReply(ERR_TAXIOK);
GetSession()->SendDoFlight(mount_display_id, sourcepath);
}
return true;
}
bool Player::ActivateTaxiPathTo(uint32 taxi_path_id, uint32 spellid /*= 0*/)
{
TaxiPathEntry const* entry = sTaxiPathStore.LookupEntry(taxi_path_id);
if (!entry)
return false;
std::vector<uint32> nodes;
nodes.resize(2);
nodes[0] = entry->FromTaxiNode;
nodes[1] = entry->ToTaxiNode;
return ActivateTaxiPathTo(nodes, nullptr, spellid);
}
void Player::FinishTaxiFlight()
{
if (!IsInFlight())
return;
GetMotionMaster()->Remove(FLIGHT_MOTION_TYPE);
m_taxi.ClearTaxiDestinations(); // not destinations, clear source node
}
void Player::CleanupAfterTaxiFlight()
{
m_taxi.ClearTaxiDestinations(); // not destinations, clear source node
Dismount();
RemoveUnitFlag(UnitFlags(UNIT_FLAG_REMOVE_CLIENT_CONTROL | UNIT_FLAG_TAXI_FLIGHT));
}
void Player::ContinueTaxiFlight() const
{
uint32 sourceNode = m_taxi.GetTaxiSource();
if (!sourceNode)
return;
TC_LOG_DEBUG("entities.unit", "Player::ContinueTaxiFlight: Restart %s taxi flight", GetGUID().ToString().c_str());
uint32 mountDisplayId = sObjectMgr->GetTaxiMountDisplayId(sourceNode, GetTeam(), true);
if (!mountDisplayId)
return;
uint32 path = m_taxi.GetCurrentTaxiPath();
// search appropriate start path node
uint32 startNode = 0;
TaxiPathNodeList const& nodeList = sTaxiPathNodesByPath[path];
float distPrev;
float distNext = GetExactDistSq(nodeList[0]->Loc.X, nodeList[0]->Loc.Y, nodeList[0]->Loc.Z);
for (uint32 i = 1; i < nodeList.size(); ++i)
{
TaxiPathNodeEntry const* node = nodeList[i];
TaxiPathNodeEntry const* prevNode = nodeList[i-1];
// skip nodes at another map
if (node->ContinentID != GetMapId())
continue;
distPrev = distNext;
distNext = GetExactDistSq(node->Loc.X, node->Loc.Y, node->Loc.Z);
float distNodes =
(node->Loc.X - prevNode->Loc.X) * (node->Loc.X - prevNode->Loc.X) +
(node->Loc.Y - prevNode->Loc.Y) * (node->Loc.Y - prevNode->Loc.Y) +
(node->Loc.Z - prevNode->Loc.Z) * (node->Loc.Z - prevNode->Loc.Z);
if (distNext + distPrev < distNodes)
{
startNode = i;
break;
}
}
GetSession()->SendDoFlight(mountDisplayId, path, startNode);
}
void Player::InitDataForForm(bool reapplyMods)
{
ShapeshiftForm form = GetShapeshiftForm();
SpellShapeshiftFormEntry const* ssEntry = sSpellShapeshiftFormStore.LookupEntry(form);
if (ssEntry && ssEntry->CombatRoundTime)
{
SetBaseAttackTime(BASE_ATTACK, ssEntry->CombatRoundTime);
SetBaseAttackTime(OFF_ATTACK, ssEntry->CombatRoundTime);
SetBaseAttackTime(RANGED_ATTACK, BASE_ATTACK_TIME);
}
else
SetRegularAttackTime();
UpdateDisplayPower();
// 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()
{
ChrModelEntry const* model = sDB2Manager.GetChrModel(GetRace(), GetNativeGender());
if (!model)
{
TC_LOG_ERROR("entities.player", "Player::InitDisplayIds: Player '%s' (%s) has incorrect race/gender pair. Can't init display ids.", GetName().c_str(), GetGUID().ToString().c_str());
return;
}
SetDisplayId(model->DisplayID);
SetNativeDisplayId(model->DisplayID);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::StateAnimID), sDB2Manager.GetEmptyAnimStateID());
}
inline bool Player::_StoreOrEquipNewItem(uint32 vendorslot, uint32 item, uint8 count, uint8 bag, uint8 slot, int64 price, ItemTemplate const* pProto, Creature* pVendor, VendorItem const* crItem, bool bStore)
{
uint32 stacks = count / pProto->GetBuyCount();
ItemPosCountVec vDest;
uint16 uiDest = 0;
InventoryResult msg = bStore ?
CanStoreNewItem(bag, slot, vDest, item, 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);
ASSERT(iece);
for (int i = 0; i < MAX_ITEM_EXT_COST_CURRENCIES; ++i)
{
if (iece->ItemID[i])
DestroyItemCount(iece->ItemID[i], iece->ItemCount[i] * stacks, true);
}
for (int i = 0; i < MAX_ITEM_EXT_COST_CURRENCIES; ++i)
{
if (iece->Flags & (ITEM_EXT_COST_CURRENCY_REQ_IS_SEASON_EARNED_1 << i))
continue;
if (iece->CurrencyID[i])
ModifyCurrency(iece->CurrencyID[i], -int32(iece->CurrencyCount[i] * stacks), true, true);
}
}
Item* it = bStore ?
StoreNewItem(vDest, item, true, GenerateItemRandomBonusListId(item), {}, ItemContext::Vendor, crItem->BonusListIDs, false) :
EquipNewItem(uiDest, item, ItemContext::Vendor, true);
if (it)
{
uint32 new_count = pVendor->UpdateVendorItemCurrentCount(crItem, count);
WorldPackets::Item::BuySucceeded packet;
packet.VendorGUID = pVendor->GetGUID();
packet.Muid = vendorslot + 1;
packet.NewQuantity = crItem->maxcount > 0 ? new_count : 0xFFFFFFFF;
packet.QuantityBought = count;
SendDirectMessage(packet.Write());
SendNewItem(it, count, true, false, false);
if (!bStore)
AutoUnequipOffhandIfNeed();
if (pProto->HasFlag(ITEM_FLAG_ITEM_PURCHASE_RECORD) && crItem->ExtendedCost && pProto->GetMaxStackSize() == 1)
{
it->AddItemFlag(ITEM_FIELD_FLAG_REFUNDABLE);
it->SetRefundRecipient(GetGUID());
it->SetPaidMoney(price);
it->SetPaidExtendedCost(crItem->ExtendedCost);
it->SaveRefundDataToDB();
AddRefundReference(it->GetGUID());
}
GetSession()->GetCollectionMgr()->OnItemAdded(it);
}
return true;
}
bool Player::BuyCurrencyFromVendorSlot(ObjectGuid vendorGuid, uint32 vendorSlot, uint32 currency, uint32 count)
{
// cheating attempt
if (count < 1) count = 1;
if (!IsAlive())
return false;
CurrencyTypesEntry const* proto = sCurrencyTypesStore.LookupEntry(currency);
if (!proto)
{
SendBuyError(BUY_ERR_CANT_FIND_ITEM, nullptr, currency, 0);
return false;
}
Creature* creature = GetNPCIfCanInteractWith(vendorGuid, UNIT_NPC_FLAG_VENDOR, UNIT_NPC_FLAG_2_NONE);
if (!creature)
{
TC_LOG_DEBUG("network", "Player::BuyCurrencyFromVendorSlot: Vendor (%s) not found or player '%s' (%s) can't interact with him.",
vendorGuid.ToString().c_str(), GetName().c_str(), GetGUID().ToString().c_str());
SendBuyError(BUY_ERR_DISTANCE_TOO_FAR, nullptr, currency, 0);
return false;
}
VendorItemData const* vItems = creature->GetVendorItems();
if (!vItems || vItems->Empty())
{
SendBuyError(BUY_ERR_CANT_FIND_ITEM, creature, currency, 0);
return false;
}
if (vendorSlot >= vItems->GetItemCount())
{
SendBuyError(BUY_ERR_CANT_FIND_ITEM, creature, currency, 0);
return false;
}
VendorItem const* crItem = vItems->GetItem(vendorSlot);
// store diff item (cheating)
if (!crItem || crItem->item != currency || crItem->Type != ITEM_VENDOR_TYPE_CURRENCY)
{
SendBuyError(BUY_ERR_CANT_FIND_ITEM, creature, currency, 0);
return false;
}
if (count % crItem->maxcount)
{
SendEquipError(EQUIP_ERR_CANT_BUY_QUANTITY, nullptr, nullptr);
return false;
}
uint32 stacks = count / crItem->maxcount;
ItemExtendedCostEntry const* iece;
if (crItem->ExtendedCost)
{
iece = sItemExtendedCostStore.LookupEntry(crItem->ExtendedCost);
if (!iece)
{
TC_LOG_ERROR("entities.player", "Player::BuyCurrencyFromVendorSlot: Currency %u has wrong ExtendedCost field value %u", currency, crItem->ExtendedCost);
return false;
}
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_ITEMS; ++i)
{
if (iece->ItemID[i] && !HasItemCount(iece->ItemID[i], (iece->ItemCount[i] * stacks)))
{
SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, nullptr, nullptr);
return false;
}
}
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_CURRENCIES; ++i)
{
if (!iece->CurrencyID[i])
continue;
CurrencyTypesEntry const* entry = sCurrencyTypesStore.LookupEntry(iece->CurrencyID[i]);
if (!entry)
{
SendBuyError(BUY_ERR_CANT_FIND_ITEM, creature, currency, 0); // Find correct error
return false;
}
if (iece->Flags & (ITEM_EXT_COST_CURRENCY_REQ_IS_SEASON_EARNED_1 << i))
{
// Not implemented
SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, nullptr, nullptr); // Find correct error
return false;
}
else if (!HasCurrency(iece->CurrencyID[i], (iece->CurrencyCount[i] * stacks)))
{
SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, nullptr, nullptr); // Find correct error
return false;
}
}
// check for personal arena rating requirement
if (GetMaxPersonalArenaRatingRequirement(iece->ArenaBracket) < iece->RequiredArenaRating)
{
// probably not the proper equip err
SendEquipError(EQUIP_ERR_CANT_EQUIP_RANK, nullptr, nullptr);
return false;
}
if (iece->MinFactionID && uint32(GetReputationRank(iece->MinFactionID)) < iece->RequiredAchievement)
{
SendBuyError(BUY_ERR_REPUTATION_REQUIRE, creature, currency, 0);
return false;
}
if (iece->Flags & ITEM_EXT_COST_FLAG_REQUIRE_GUILD && !GetGuildId())
{
SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, nullptr, nullptr); // Find correct error
return false;
}
if (iece->RequiredAchievement && !HasAchieved(iece->RequiredAchievement))
{
SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, nullptr, nullptr); // Find correct error
return false;
}
}
else // currencies have no price defined, can only be bought with ExtendedCost
{
SendBuyError(BUY_ERR_CANT_FIND_ITEM, nullptr, currency, 0);
return false;
}
ModifyCurrency(currency, count, true, true);
if (iece)
{
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_ITEMS; ++i)
{
if (!iece->ItemID[i])
continue;
DestroyItemCount(iece->ItemID[i], iece->ItemCount[i] * stacks, true);
}
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_CURRENCIES; ++i)
{
if (!iece->CurrencyID[i])
continue;
if (iece->Flags & (ITEM_EXT_COST_CURRENCY_REQ_IS_SEASON_EARNED_1 << i))
continue;
ModifyCurrency(iece->CurrencyID[i], -int32(iece->CurrencyCount[i]) * stacks, false, true);
}
}
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)
{
// 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;
}
if (!(pProto->GetAllowableClass() & GetClassMask()) && pProto->GetBonding() == BIND_ON_ACQUIRE && !IsGameMaster())
{
SendBuyError(BUY_ERR_CANT_FIND_ITEM, nullptr, item, 0);
return false;
}
if (!IsGameMaster() && ((pProto->HasFlag(ITEM_FLAG2_FACTION_HORDE) && GetTeam() == ALLIANCE) || (pProto->HasFlag(ITEM_FLAG2_FACTION_ALLIANCE) && GetTeam() == HORDE)))
return false;
Creature* creature = GetNPCIfCanInteractWith(vendorguid, UNIT_NPC_FLAG_VENDOR, UNIT_NPC_FLAG_2_NONE);
if (!creature)
{
TC_LOG_DEBUG("network", "Player::BuyItemFromVendorSlot: Vendor (%s) not found or player '%s' (%s) can't interact with him.",
vendorguid.ToString().c_str(), GetName().c_str(), GetGUID().ToString().c_str());
SendBuyError(BUY_ERR_DISTANCE_TOO_FAR, nullptr, item, 0);
return false;
}
if (!sConditionMgr->IsObjectMeetingVendorItemConditions(creature->GetEntry(), item, this, creature))
{
TC_LOG_DEBUG("condition", "Player::BuyItemFromVendorSlot: Player '%s' (%s) doesn't meed conditions for creature (Entry: %u, Item: %u)",
GetName().c_str(), GetGUID().ToString().c_str(), creature->GetEntry(), item);
SendBuyError(BUY_ERR_CANT_FIND_ITEM, creature, item, 0);
return false;
}
VendorItemData const* vItems = 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;
}
if (PlayerConditionEntry const* playerCondition = sPlayerConditionStore.LookupEntry(crItem->PlayerConditionId))
{
if (!ConditionMgr::IsPlayerMeetingCondition(this, playerCondition))
{
SendEquipError(EQUIP_ERR_ITEM_LOCKED, nullptr, nullptr);
return false;
}
}
// check current item amount if it limited
if (crItem->maxcount != 0)
{
if (creature->GetVendorItemCurrentCount(crItem) < count)
{
SendBuyError(BUY_ERR_ITEM_ALREADY_SOLD, creature, item, 0);
return false;
}
}
if (pProto->GetRequiredReputationFaction() && (uint32(GetReputationRank(pProto->GetRequiredReputationFaction())) < pProto->GetRequiredReputationRank()))
{
SendBuyError(BUY_ERR_REPUTATION_REQUIRE, creature, item, 0);
return false;
}
if (crItem->ExtendedCost)
{
// Can only buy full stacks for extended cost
if (count % pProto->GetBuyCount())
{
SendEquipError(EQUIP_ERR_CANT_BUY_QUANTITY, nullptr, nullptr);
return false;
}
uint32 stacks = count / pProto->GetBuyCount();
ItemExtendedCostEntry const* iece = sItemExtendedCostStore.LookupEntry(crItem->ExtendedCost);
if (!iece)
{
TC_LOG_ERROR("entities.player", "Player::BuyItemFromVendorSlot: Item %u has wrong ExtendedCost field value %u", pProto->GetId(), crItem->ExtendedCost);
return false;
}
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_ITEMS; ++i)
{
if (iece->ItemID[i] && !HasItemCount(iece->ItemID[i], iece->ItemCount[i] * stacks))
{
SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, nullptr, nullptr);
return false;
}
}
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_CURRENCIES; ++i)
{
if (!iece->CurrencyID[i])
continue;
CurrencyTypesEntry const* entry = sCurrencyTypesStore.LookupEntry(iece->CurrencyID[i]);
if (!entry)
{
SendBuyError(BUY_ERR_CANT_FIND_ITEM, creature, item, 0);
return false;
}
if (iece->Flags & (ITEM_EXT_COST_CURRENCY_REQ_IS_SEASON_EARNED_1 << i))
{
SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, nullptr, nullptr); // Find correct error
return false;
}
else if (!HasCurrency(iece->CurrencyID[i], iece->CurrencyCount[i] * stacks))
{
SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, nullptr, nullptr);
return false;
}
}
// check for personal arena rating requirement
if (GetMaxPersonalArenaRatingRequirement(iece->ArenaBracket) < iece->RequiredArenaRating)
{
// probably not the proper equip err
SendEquipError(EQUIP_ERR_CANT_EQUIP_RANK, nullptr, nullptr);
return false;
}
if (iece->MinFactionID && uint32(GetReputationRank(iece->MinFactionID)) < iece->MinReputation)
{
SendBuyError(BUY_ERR_REPUTATION_REQUIRE, creature, item, 0);
return false;
}
if (iece->Flags & ITEM_EXT_COST_FLAG_REQUIRE_GUILD && !GetGuildId())
{
SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, nullptr, nullptr); // Find correct error
return false;
}
if (iece->RequiredAchievement && !HasAchieved(iece->RequiredAchievement))
{
SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, nullptr, nullptr); // Find correct error
return false;
}
}
uint64 price = 0;
if (crItem->IsGoldRequired(pProto) && pProto->GetBuyPrice() > 0) //Assume price cannot be negative (do not know why it is int32)
{
double buyPricePerItem = double(pProto->GetBuyPrice()) / pProto->GetBuyCount();
uint64 maxCount = MAX_MONEY_AMOUNT / buyPricePerItem;
if ((uint64)count > maxCount)
{
TC_LOG_ERROR("entities.player.cheat", "Player::BuyItemFromVendorSlot: Player '%s' (%s) tried to buy item (ItemID: %u, Count: %u), causing overflow",
GetName().c_str(), GetGUID().ToString().c_str(), pProto->GetId(), (uint32)count);
count = (uint8)maxCount;
}
price = uint64(buyPricePerItem * count); //it should not exceed MAX_MONEY_AMOUNT
// reputation discount
price = uint64(floor(price * GetReputationPriceDiscount(creature)));
if (int32 priceMod = GetTotalAuraModifier(SPELL_AURA_MOD_VENDOR_ITEMS_PRICES))
price -= CalculatePct(price, priceMod);
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 (count != 1)
{
SendEquipError(EQUIP_ERR_NOT_EQUIPPABLE, nullptr, nullptr);
return false;
}
if (!_StoreOrEquipNewItem(vendorslot, item, count, bag, slot, price, pProto, creature, crItem, false))
return false;
}
else
{
SendEquipError(EQUIP_ERR_WRONG_SLOT, nullptr, nullptr);
return false;
}
if (crItem->maxcount != 0) // bought
{
if (pProto->GetQuality() > ITEM_QUALITY_EPIC || (pProto->GetQuality() == ITEM_QUALITY_EPIC && pProto->GetBaseItemLevel() >= MinNewsItemLevel))
if (Guild* guild = GetGuild())
guild->AddGuildNews(GUILD_NEWS_ITEM_PURCHASED, GetGUID(), 0, item);
UpdateCriteria(CriteriaType::BuyItemsFromVendors, 1);
return true;
}
return false;
}
uint32 Player::GetMaxPersonalArenaRatingRequirement(uint32 minarenaslot) const
{
// returns the maximal personal arena rating that can be used to purchase items requiring this condition
// so return max[in arenateams](personalrating[teamtype])
uint32 max_personal_rating = 0;
for (uint8 i = minarenaslot; i < MAX_ARENA_SLOT; ++i)
{
uint32 p_rating = GetArenaPersonalRating(i);
if (max_personal_rating < p_rating)
max_personal_rating = p_rating;
}
return max_personal_rating;
}
void Player::UpdateHomebindTime(uint32 time)
{
// GMs never get homebind timer online
if (m_InstanceValid || IsGameMaster())
{
if (m_HomebindTimer) // instance valid, but timer not reset
SendRaidGroupOnlyMessage(RAID_GROUP_ERR_NONE, 0);
// instance is valid, reset homebind timer
m_HomebindTimer = 0;
}
else if (m_HomebindTimer > 0)
{
if (time >= m_HomebindTimer)
{
// teleport to nearest graveyard
RepopAtGraveyard();
}
else
m_HomebindTimer -= time;
}
else
{
// instance is invalid, start homebind timer
m_HomebindTimer = 60000;
// send message to player
SendRaidGroupOnlyMessage(RAID_GROUP_ERR_REQUIREMENTS_UNMATCH, m_HomebindTimer);
TC_LOG_DEBUG("maps", "Player::UpdateHomebindTime: Player '%s' (%s) will be teleported to homebind in 60 seconds",
GetName().c_str(), GetGUID().ToString().c_str());
}
}
void Player::InitPvP()
{
// pvp flag should stay after relog
if (HasPlayerFlag(PLAYER_FLAGS_IN_PVP))
UpdatePvP(true, true);
}
void Player::UpdatePvPState(bool onlyFFA)
{
/// @todo should we always synchronize UNIT_FIELD_BYTES_2, 1 of controller and controlled?
// no, we shouldn't, those are checked for affecting player by client
if (!pvpInfo.IsInNoPvPArea && !IsGameMaster()
&& (pvpInfo.IsInFFAPvPArea || sWorld->IsFFAPvPRealm() || HasAuraType(SPELL_AURA_SET_FFA_PVP)))
{
if (!IsFFAPvP())
{
AddPvpFlag(UNIT_BYTE2_FLAG_FFA_PVP);
for (ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
(*itr)->AddPvpFlag(UNIT_BYTE2_FLAG_FFA_PVP);
}
}
else if (IsFFAPvP())
{
RemovePvpFlag(UNIT_BYTE2_FLAG_FFA_PVP);
for (ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
(*itr)->RemovePvpFlag(UNIT_BYTE2_FLAG_FFA_PVP);
}
if (onlyFFA)
return;
if (pvpInfo.IsHostile) // in hostile area
{
if (!IsPvP() || pvpInfo.EndTimer)
UpdatePvP(true, true);
}
else // in friendly area
{
if (IsPvP() && !HasPlayerFlag(PLAYER_FLAGS_IN_PVP) && !pvpInfo.EndTimer)
pvpInfo.EndTimer = GameTime::GetGameTime(); // start toggle-off
}
}
void Player::SetPvP(bool state)
{
Unit::SetPvP(state);
for (ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
(*itr)->SetPvP(state);
}
void Player::UpdatePvP(bool state, bool _override)
{
if (!state || _override)
{
SetPvP(state);
pvpInfo.EndTimer = 0;
}
else
{
pvpInfo.EndTimer = GameTime::GetGameTime();
SetPvP(state);
}
}
void Player::UpdatePotionCooldown(Spell* spell)
{
// no potion used i combat or still in combat
if (!m_lastPotionId || IsInCombat())
return;
// Call not from spell cast, send cooldown event for item spells if no in combat
if (!spell)
{
// spell/item pair let set proper cooldown (except non-existing charged spell cooldown spellmods for potions)
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(m_lastPotionId))
for (uint8 idx = 0; idx < proto->Effects.size(); ++idx)
if (proto->Effects[idx]->TriggerType == ITEM_SPELLTRIGGER_ON_USE)
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Effects[idx]->SpellID, DIFFICULTY_NONE))
GetSpellHistory()->SendCooldownEvent(spellInfo, m_lastPotionId);
}
// from spell cases (m_lastPotionId set in Spell::SendSpellCooldown)
else
{
if (spell->IsIgnoringCooldowns())
return;
else
GetSpellHistory()->SendCooldownEvent(spell->m_spellInfo, m_lastPotionId, spell);
}
m_lastPotionId = 0;
}
void Player::UpdateReviveBattlePetCooldown()
{
SpellInfo const* reviveBattlePetSpellInfo = sSpellMgr->GetSpellInfo(BattlePets::SPELL_REVIVE_BATTLE_PETS, DIFFICULTY_NONE);
if (reviveBattlePetSpellInfo && HasSpell(BattlePets::SPELL_REVIVE_BATTLE_PETS))
{
SpellHistory::Duration remainingCooldown = GetSpellHistory()->GetRemainingCategoryCooldown(reviveBattlePetSpellInfo);
if (remainingCooldown > SpellHistory::Duration::zero())
{
if (remainingCooldown < BattlePets::REVIVE_BATTLE_PETS_COOLDOWN)
GetSpellHistory()->ModifyCooldown(reviveBattlePetSpellInfo, BattlePets::REVIVE_BATTLE_PETS_COOLDOWN - remainingCooldown);
}
else
{
GetSpellHistory()->StartCooldown(reviveBattlePetSpellInfo, 0, nullptr, false, BattlePets::REVIVE_BATTLE_PETS_COOLDOWN);
}
}
}
void Player::SetResurrectRequestData(WorldObject const* caster, uint32 health, uint32 mana, uint32 appliedAura)
{
ASSERT(!IsResurrectRequested());
_resurrectionData.reset(new ResurrectionData());
_resurrectionData->GUID = caster->GetGUID();
_resurrectionData->Location.WorldRelocate(*caster);
_resurrectionData->Health = health;
_resurrectionData->Mana = mana;
_resurrectionData->Aura = appliedAura;
}
//slot to be excluded while counting
bool Player::EnchantmentFitsRequirements(uint32 enchantmentcondition, int8 slot) const
{
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())
{
for (UF::SocketedGem const& gemData : pItem2->m_itemData->Gems)
{
ItemTemplate const* gemProto = sObjectMgr->GetItemTemplate(gemData.ItemID);
if (!gemProto)
continue;
GemPropertiesEntry const* gemProperty = sGemPropertiesStore.LookupEntry(gemProto->GetGemProperties());
if (!gemProperty)
continue;
uint32 GemColor = gemProperty->Type;
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->LtOperandType[i])
continue;
uint32 _cur_gem = curcount[Condition->LtOperandType[i] - 1];
// if have <CompareColor> use them as count, else use <value> from Condition
uint32 _cmp_gem = Condition->RtOperandType[i] ? curcount[Condition->RtOperandType[i] - 1]: Condition->RtOperand[i];
switch (Condition->Operator[i])
{
case 2: // requires less <color> than (<value> || <comparecolor>) gems
activate &= (_cur_gem < _cmp_gem) ? true : false;
break;
case 3: // requires more <color> than (<value> || <comparecolor>) gems
activate &= (_cur_gem > _cmp_gem) ? true : false;
break;
case 5: // requires at least <color> than (<value> || <comparecolor>) gems
activate &= (_cur_gem >= _cmp_gem) ? true : false;
break;
}
}
TC_LOG_DEBUG("entities.player.items", "Player::EnchantmentFitsRequirements: Checking Condition %u, there are %u Meta Gems, %u Red Gems, %u Yellow Gems and %u Blue Gems, Activate:%s",
enchantmentcondition, curcount[0], curcount[1], curcount[2], curcount[3], activate ? "yes" : "no");
return activate;
}
void Player::CorrectMetaGemEnchants(uint8 exceptslot, bool apply)
{
//cycle all equipped items
for (uint32 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
{
//enchants for the slot being socketed are handled by Player::ApplyItemMods
if (slot == exceptslot)
continue;
Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
if (!pItem || !pItem->GetSocketColor(0))
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->ConditionID;
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->GetSocketColor(0)) //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->ConditionID;
if (condition)
ApplyEnchantment(pItem, EnchantmentSlot(enchant_slot), apply);
}
}
}
void Player::SetBattlegroundEntryPoint()
{
// Taxi path store
if (!m_taxi.empty())
{
m_bgData.mountSpell = 0;
m_bgData.taxiPath[0] = m_taxi.GetTaxiSource();
m_bgData.taxiPath[1] = m_taxi.GetTaxiDestination();
// On taxi we don't need check for dungeon
m_bgData.joinPos = WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
}
else
{
m_bgData.ClearTaxiPath();
// Mount spell id storing
if (IsMounted())
{
AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_MOUNTED);
if (!auras.empty())
m_bgData.mountSpell = (*auras.begin())->GetId();
}
else
m_bgData.mountSpell = 0;
// If map is dungeon find linked graveyard
if (GetMap()->IsDungeon())
{
if (WorldSafeLocsEntry const* entry = sObjectMgr->GetClosestGraveyard(*this, GetTeam(), this))
m_bgData.joinPos.WorldRelocate(entry->Loc.GetMapId(), entry->Loc.GetPositionX(), entry->Loc.GetPositionY(), entry->Loc.GetPositionZ());
else
TC_LOG_ERROR("entities.player", "Player::SetBattlegroundEntryPoint: Dungeon (MapID: %u) has no linked graveyard, setting home location as entry point.", GetMapId());
}
// If new entry point is not BG or arena set it
else if (!GetMap()->IsBattlegroundOrArena())
m_bgData.joinPos = WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
}
if (m_bgData.joinPos.m_mapId == MAPID_INVALID) // In error cases use homebind position
m_bgData.joinPos.WorldRelocate(m_homebind);
}
void Player::SetBGTeam(uint32 team)
{
m_bgData.bgTeam = team;
SetArenaFaction(uint8(team == ALLIANCE ? 1 : 0));
}
uint32 Player::GetBGTeam() const
{
return m_bgData.bgTeam ? m_bgData.bgTeam : GetTeam();
}
void Player::LeaveBattleground(bool teleportToEntryPoint)
{
if (Battleground* bg = GetBattleground())
{
bg->RemovePlayerAtLeave(GetGUID(), teleportToEntryPoint, true);
// call after remove to be sure that player resurrected for correct cast
if (bg->isBattleground() && !IsGameMaster() && sWorld->getBoolConfig(CONFIG_BATTLEGROUND_CAST_DESERTER))
{
if (bg->GetStatus() == STATUS_IN_PROGRESS || bg->GetStatus() == STATUS_WAIT_JOIN)
{
//lets check if player was teleported from BG and schedule delayed Deserter spell cast
if (IsBeingTeleportedFar())
{
ScheduleDelayedOperation(DELAYED_SPELL_CAST_DESERTER);
return;
}
CastSpell(this, 26013, true); // Deserter
}
}
}
}
bool Player::CanJoinToBattleground(Battleground const* bg) const
{
uint32 perm = rbac::RBAC_PERM_JOIN_NORMAL_BG;
if (bg->isArena())
perm = rbac::RBAC_PERM_JOIN_ARENAS;
else if (bg->IsRandom())
perm = rbac::RBAC_PERM_JOIN_RANDOM_BG;
return GetSession()->HasPermission(perm);
}
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)
{
WorldPackets::Battleground::ReportPvPPlayerAFKResult reportAfkResult;
reportAfkResult.Offender = GetGUID();
Battleground* bg = GetBattleground();
// Battleground also must be in progress!
if (!bg || bg != reporter->GetBattleground() || GetTeam() != reporter->GetTeam() || bg->GetStatus() != STATUS_IN_PROGRESS)
{
reporter->SendDirectMessage(reportAfkResult.Write());
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();
reportAfkResult.NumBlackMarksOnOffender = m_bgData.bgAfkReporter.size();
reportAfkResult.NumPlayersIHaveReported = reporter->m_bgData.bgAfkReportedCount;
reportAfkResult.Result = WorldPackets::Battleground::ReportPvPPlayerAFKResult::PVP_REPORT_AFK_SUCCESS;
}
}
reporter->SendDirectMessage(reportAfkResult.Write());
}
uint8 Player::GetStartLevel(uint8 race, uint8 playerClass, Optional<int32> characterTemplateId) const
{
uint8 startLevel = sWorld->getIntConfig(CONFIG_START_PLAYER_LEVEL);
if (sChrRacesStore.AssertEntry(race)->GetFlags().HasFlag(ChrRacesFlag::IsAlliedRace))
startLevel = sWorld->getIntConfig(CONFIG_START_ALLIED_RACE_LEVEL);
if (playerClass == CLASS_DEATH_KNIGHT)
{
if (race == RACE_PANDAREN_ALLIANCE || race == RACE_PANDAREN_HORDE)
startLevel = std::max<uint8>(sWorld->getIntConfig(CONFIG_START_ALLIED_RACE_LEVEL), startLevel);
else
startLevel = std::max<uint8>(sWorld->getIntConfig(CONFIG_START_DEATH_KNIGHT_PLAYER_LEVEL), startLevel);
}
else if (playerClass == CLASS_DEMON_HUNTER)
startLevel = std::max<uint8>(sWorld->getIntConfig(CONFIG_START_DEMON_HUNTER_PLAYER_LEVEL), startLevel);
if (characterTemplateId)
{
if (m_session->HasPermission(rbac::RBAC_PERM_USE_CHARACTER_TEMPLATES))
{
if (CharacterTemplate const* charTemplate = sCharacterTemplateDataStore->GetCharacterTemplate(*characterTemplateId))
startLevel = std::max(charTemplate->Level, startLevel);
}
else
TC_LOG_WARN("cheat", "Account: %u (IP: %s) tried to use a character template without given permission. Possible cheating attempt.",
m_session->GetAccountId(), m_session->GetRemoteAddress().c_str());
}
if (m_session->HasPermission(rbac::RBAC_PERM_USE_START_GM_LEVEL))
startLevel = std::max<uint8>(sWorld->getIntConfig(CONFIG_START_GM_LEVEL), startLevel);
return startLevel;
}
bool Player::HaveAtClient(Object const* u) const
{
return u == this || m_clientGUIDs.find(u->GetGUID()) != m_clientGUIDs.end();
}
bool Player::IsNeverVisibleFor(WorldObject const* seer) const
{
if (Unit::IsNeverVisibleFor(seer))
return true;
if (GetSession()->PlayerLogout() || GetSession()->PlayerLoading())
return true;
return false;
}
bool Player::CanAlwaysSee(WorldObject const* obj) const
{
// Always can see self
if (GetUnitBeingMoved() == obj)
return true;
ObjectGuid guid = m_activePlayerData->FarsightObject;
if (!guid.IsEmpty())
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();
// non faction visibility non-breakable for non-GMs
return false;
}
template<class T>
inline void UpdateVisibilityOf_helper(GuidUnorderedSet& s64, T* target, std::set<Unit*>& /*v*/)
{
s64.insert(target->GetGUID());
}
template<>
inline void UpdateVisibilityOf_helper(GuidUnorderedSet& s64, GameObject* target, std::set<Unit*>& /*v*/)
{
// @HACK: This is to prevent objects like deeprun tram from disappearing when player moves far from its spawn point while riding it
// But exclude stoppable elevators from this hack - they would be teleporting from one end to another
// if affected transports move so far horizontally that it causes them to run out of visibility range then you are out of luck
// fix visibility instead of adding hacks here
if (!target->IsDynTransport())
s64.insert(target->GetGUID());
}
template<>
inline void UpdateVisibilityOf_helper(GuidUnorderedSet& s64, Creature* target, std::set<Unit*>& v)
{
s64.insert(target->GetGUID());
v.insert(target);
}
template<>
inline void UpdateVisibilityOf_helper(GuidUnorderedSet& s64, Player* target, std::set<Unit*>& v)
{
s64.insert(target->GetGUID());
v.insert(target);
}
template<class T>
inline void BeforeVisibilityDestroy(T* /*t*/, Player* /*p*/) { }
template<>
inline void BeforeVisibilityDestroy<Creature>(Creature* t, Player* p)
{
if (p->GetPetGUID() == t->GetGUID() && t->IsPet())
t->ToPet()->Remove(PET_SAVE_NOT_IN_SLOT, true);
}
void Player::UpdateVisibilityOf(WorldObject* target)
{
if (HaveAtClient(target))
{
if (!CanSeeOrDetect(target, false, true))
{
if (target->GetTypeId() == TYPEID_UNIT)
BeforeVisibilityDestroy<Creature>(target->ToCreature(), this);
target->DestroyForPlayer(this);
m_clientGUIDs.erase(target->GetGUID());
#ifdef TRINITY_DEBUG
TC_LOG_DEBUG("maps", "Object %s out of range for player %s. Distance = %f", target->GetGUID().ToString().c_str(), GetGUID().ToString().c_str(), GetDistance(target));
#endif
}
}
else
{
if (CanSeeOrDetect(target, false, true))
{
target->SendUpdateToPlayer(this);
m_clientGUIDs.insert(target->GetGUID());
#ifdef TRINITY_DEBUG
TC_LOG_DEBUG("maps", "Object %s is visible now for player %s. Distance = %f", target->GetGUID().ToString().c_str(), GetGUID().ToString().c_str(), GetDistance(target));
#endif
// target aura duration for caster show only if target exist at caster client
// send data at target visibility change (adding to client)
if (target->isType(TYPEMASK_UNIT))
SendInitialVisiblePackets(static_cast<Unit*>(target));
}
}
}
void Player::UpdateTriggerVisibility()
{
if (m_clientGUIDs.empty())
return;
if (!IsInWorld())
return;
UpdateData udata(GetMapId());
WorldPacket packet;
for (auto itr = m_clientGUIDs.begin(); itr != m_clientGUIDs.end(); ++itr)
{
if (itr->IsCreatureOrVehicle())
{
Creature* creature = GetMap()->GetCreature(*itr);
// Update fields of triggers, transformed units or unselectable units (values dependent on GM state)
if (!creature || (!creature->IsTrigger() && !creature->HasAuraType(SPELL_AURA_TRANSFORM) && !creature->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)))
continue;
creature->ForceUpdateFieldChange(creature->m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::DisplayID));
creature->ForceUpdateFieldChange(creature->m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Flags));
creature->BuildValuesUpdateBlockForPlayer(&udata, this);
}
else if (itr->IsAnyTypeGameObject())
{
GameObject* go = GetMap()->GetGameObject(*itr);
if (!go)
continue;
go->ForceUpdateFieldChange(go->m_values.ModifyValue(&Object::m_objectData).ModifyValue(&UF::ObjectData::DynamicFlags));
go->BuildValuesUpdateBlockForPlayer(&udata, this);
}
}
if (!udata.HasData())
return;
udata.BuildPacket(&packet);
SendDirectMessage(&packet);
}
void Player::SendInitialVisiblePackets(Unit* target) const
{
SendAurasForTarget(target);
if (target->IsAlive())
{
if (target->HasUnitState(UNIT_STATE_MELEE_ATTACKING) && target->GetVictim())
target->SendMeleeAttackStart(target->GetVictim());
}
}
template<class T>
void Player::UpdateVisibilityOf(T* target, UpdateData& data, std::set<Unit*>& visibleNow)
{
if (HaveAtClient(target))
{
if (!CanSeeOrDetect(target, false, true))
{
BeforeVisibilityDestroy<T>(target, this);
target->BuildOutOfRangeUpdateBlock(&data);
m_clientGUIDs.erase(target->GetGUID());
#ifdef TRINITY_DEBUG
TC_LOG_DEBUG("maps", "Object %s is out of range for player %s. Distance = %f", target->GetGUID().ToString().c_str(), GetGUID().ToString().c_str(), GetDistance(target));
#endif
}
}
else //if (visibleNow.size() < 30 || target->GetTypeId() == TYPEID_UNIT && target->ToCreature()->IsVehicle())
{
if (CanSeeOrDetect(target, false, true))
{
target->BuildCreateUpdateBlockForPlayer(&data, this);
UpdateVisibilityOf_helper(m_clientGUIDs, target, visibleNow);
#ifdef TRINITY_DEBUG
TC_LOG_DEBUG("maps", "Object %s is visible now for player %s. Distance = %f", target->GetGUID().ToString().c_str(), GetGUID().ToString().c_str(), GetDistance(target));
#endif
}
}
}
template void Player::UpdateVisibilityOf(Player* target, UpdateData& data, std::set<Unit*>& visibleNow);
template void Player::UpdateVisibilityOf(Creature* target, UpdateData& data, std::set<Unit*>& visibleNow);
template void Player::UpdateVisibilityOf(Corpse* target, UpdateData& data, std::set<Unit*>& visibleNow);
template void Player::UpdateVisibilityOf(GameObject* target, UpdateData& data, std::set<Unit*>& visibleNow);
template void Player::UpdateVisibilityOf(DynamicObject* target, UpdateData& data, std::set<Unit*>& visibleNow);
template void Player::UpdateVisibilityOf(AreaTrigger* target, UpdateData& data, std::set<Unit*>& visibleNow);
template void Player::UpdateVisibilityOf(SceneObject* target, UpdateData& data, std::set<Unit*>& visibleNow);
template void Player::UpdateVisibilityOf(Conversation* target, UpdateData& data, std::set<Unit*>& visibleNow);
void Player::UpdateObjectVisibility(bool forced)
{
// Prevent updating visibility if player is not in world (example: LoadFromDB sets drunkstate which updates invisibility while player is not in map)
if (!IsInWorld())
return;
if (!forced)
AddToNotify(NOTIFY_VISIBILITY_CHANGED);
else
{
Unit::UpdateObjectVisibility(true);
UpdateVisibilityForPlayer();
}
}
void Player::UpdateVisibilityForPlayer()
{
// updates visibility of all objects around point of view for current player
Trinity::VisibleNotifier notifier(*this);
Cell::VisitAllObjects(m_seer, notifier, GetSightRange());
notifier.SendToSelf(); // send gathered data
}
void Player::InitPrimaryProfessions()
{
SetFreePrimaryProfessions(sWorld->getIntConfig(CONFIG_MAX_PRIMARY_TRADE_SKILL));
}
bool Player::ModifyMoney(int64 amount, bool sendError /*= true*/)
{
if (!amount)
return true;
sScriptMgr->OnPlayerMoneyChanged(this, amount);
if (amount < 0)
SetMoney(GetMoney() > uint64(-amount) ? GetMoney() + amount : 0);
else
{
if (GetMoney() <= MAX_MONEY_AMOUNT - static_cast<uint64>(amount))
SetMoney(GetMoney() + amount);
else
{
sScriptMgr->OnPlayerMoneyLimit(this, amount);
if (sendError)
SendEquipError(EQUIP_ERR_TOO_MUCH_GOLD, nullptr, nullptr);
return false;
}
}
return true;
}
void Player::SetMoney(uint64 value)
{
MoneyChanged(value);
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::Coinage), value);
UpdateCriteria(CriteriaType::MostMoneyOwned);
}
bool Player::IsQuestRewarded(uint32 quest_id) const
{
return m_RewardedQuests.find(quest_id) != m_RewardedQuests.end();
}
Unit* Player::GetSelectedUnit() const
{
ObjectGuid selectionGUID = GetTarget();
if (!selectionGUID.IsEmpty())
return ObjectAccessor::GetUnit(*this, selectionGUID);
return nullptr;
}
Player* Player::GetSelectedPlayer() const
{
ObjectGuid selectionGUID = GetTarget();
if (!selectionGUID.IsEmpty())
return ObjectAccessor::FindConnectedPlayer(selectionGUID);
return nullptr;
}
bool Player::IsInGroup(ObjectGuid groupGuid) const
{
if (Group const* group = GetGroup())
if (group->GetGUID() == groupGuid)
return true;
if (Group const* group = GetOriginalGroup())
if (group->GetGUID() == groupGuid)
return true;
return false;
}
void Player::SetGroup(Group* group, int8 subgroup)
{
if (group == nullptr)
m_group.unlink();
else
{
// never use SetGroup without a subgroup unless you specify NULL for group
ASSERT(subgroup >= 0);
m_group.link(group, this);
m_group.setSubGroup((uint8)subgroup);
}
UpdateObjectVisibility(false);
}
void Player::SendInitialPacketsBeforeAddToMap()
{
if (!(m_teleport_options & TELE_TO_SEAMLESS))
{
m_movementCounter = 0;
GetSession()->ResetTimeSync();
}
GetSession()->SendTimeSync();
/// Pass 'this' as argument because we're not stored in ObjectAccessor yet
GetSocial()->SendSocialList(this, SOCIAL_FLAG_ALL);
/// SMSG_BINDPOINTUPDATE
SendBindPointUpdate();
// SMSG_SET_PROFICIENCY
// SMSG_SET_PCT_SPELL_MODIFIER
// SMSG_SET_FLAT_SPELL_MODIFIER
/// SMSG_TALENTS_INFO
SendTalentsInfoData();
/// SMSG_INITIAL_SPELLS
SendKnownSpells();
/// SMSG_SEND_UNLEARN_SPELLS
SendUnlearnSpells();
/// SMSG_SEND_SPELL_HISTORY
WorldPackets::Spells::SendSpellHistory sendSpellHistory;
GetSpellHistory()->WritePacket(&sendSpellHistory);
SendDirectMessage(sendSpellHistory.Write());
/// SMSG_SEND_SPELL_CHARGES
WorldPackets::Spells::SendSpellCharges sendSpellCharges;
GetSpellHistory()->WritePacket(&sendSpellCharges);
SendDirectMessage(sendSpellCharges.Write());
WorldPackets::Talent::ActiveGlyphs activeGlyphs;
activeGlyphs.Glyphs.reserve(GetGlyphs(GetActiveTalentGroup()).size());
for (uint32 glyphId : GetGlyphs(GetActiveTalentGroup()))
if (std::vector<uint32> const* bindableSpells = sDB2Manager.GetGlyphBindableSpells(glyphId))
for (uint32 bindableSpell : *bindableSpells)
if (HasSpell(bindableSpell) && m_overrideSpells.find(bindableSpell) == m_overrideSpells.end())
activeGlyphs.Glyphs.emplace_back(uint32(bindableSpell), uint16(glyphId));
activeGlyphs.IsFullUpdate = true;
SendDirectMessage(activeGlyphs.Write());
/// SMSG_ACTION_BUTTONS
SendInitialActionButtons();
/// SMSG_INITIALIZE_FACTIONS
m_reputationMgr->SendInitialReputations();
/// SMSG_SETUP_CURRENCY
SendCurrencies();
/// SMSG_EQUIPMENT_SET_LIST
SendEquipmentSetList();
m_achievementMgr->SendAllData(this);
m_questObjectiveCriteriaMgr->SendAllData(this);
/// SMSG_LOGIN_SETTIMESPEED
static float const TimeSpeed = 0.01666667f;
WorldPackets::Misc::LoginSetTimeSpeed loginSetTimeSpeed;
loginSetTimeSpeed.NewSpeed = TimeSpeed;
loginSetTimeSpeed.GameTime = GameTime::GetGameTime();
loginSetTimeSpeed.ServerTime = GameTime::GetGameTime();
loginSetTimeSpeed.GameTimeHolidayOffset = 0; /// @todo
loginSetTimeSpeed.ServerTimeHolidayOffset = 0; /// @todo
SendDirectMessage(loginSetTimeSpeed.Write());
/// SMSG_WORLD_SERVER_INFO
WorldPackets::Misc::WorldServerInfo worldServerInfo;
worldServerInfo.InstanceGroupSize = GetMap()->GetMapDifficulty()->MaxPlayers;
worldServerInfo.IsTournamentRealm = 0; /// @todo
// worldServerInfo.RestrictedAccountMaxLevel; /// @todo
// worldServerInfo.RestrictedAccountMaxMoney; /// @todo
worldServerInfo.DifficultyID = GetMap()->GetDifficultyID();
// worldServerInfo.XRealmPvpAlert; /// @todo
SendDirectMessage(worldServerInfo.Write());
// Spell modifiers
SendSpellModifiers();
// SMSG_ACCOUNT_MOUNT_UPDATE
WorldPackets::Misc::AccountMountUpdate mountUpdate;
mountUpdate.IsFullUpdate = true;
mountUpdate.Mounts = &GetSession()->GetCollectionMgr()->GetAccountMounts();
SendDirectMessage(mountUpdate.Write());
// SMSG_ACCOUNT_TOYS_UPDATE
WorldPackets::Toy::AccountToyUpdate toyUpdate;
toyUpdate.IsFullUpdate = true;
toyUpdate.Toys = &GetSession()->GetCollectionMgr()->GetAccountToys();
SendDirectMessage(toyUpdate.Write());
// SMSG_ACCOUNT_HEIRLOOM_UPDATE
WorldPackets::Misc::AccountHeirloomUpdate heirloomUpdate;
heirloomUpdate.IsFullUpdate = true;
heirloomUpdate.Heirlooms = &GetSession()->GetCollectionMgr()->GetAccountHeirlooms();
SendDirectMessage(heirloomUpdate.Write());
GetSession()->GetCollectionMgr()->SendFavoriteAppearances();
WorldPackets::Character::InitialSetup initialSetup;
initialSetup.ServerExpansionLevel = sWorld->getIntConfig(CONFIG_EXPANSION);
SendDirectMessage(initialSetup.Write());
SetMovedUnit(this);
}
void Player::SendInitialPacketsAfterAddToMap()
{
UpdateVisibilityForPlayer();
// update zone
uint32 newzone, newarea;
GetZoneAndAreaId(newzone, newarea);
UpdateZone(newzone, newarea); // also call SendInitWorldStates();
GetSession()->SendLoadCUFProfiles();
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);
}
if (HasAuraType(SPELL_AURA_MOD_STUN))
SetRooted(true);
WorldPackets::Movement::MoveSetCompoundState setCompoundState;
// 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) || HasAuraType(SPELL_AURA_MOD_ROOT_2))
setCompoundState.StateChanges.emplace_back(SMSG_MOVE_ROOT, m_movementCounter++);
if (HasAuraType(SPELL_AURA_FEATHER_FALL))
setCompoundState.StateChanges.emplace_back(SMSG_MOVE_SET_FEATHER_FALL, m_movementCounter++);
if (HasAuraType(SPELL_AURA_WATER_WALK))
setCompoundState.StateChanges.emplace_back(SMSG_MOVE_SET_WATER_WALK, m_movementCounter++);
if (HasAuraType(SPELL_AURA_HOVER))
setCompoundState.StateChanges.emplace_back(SMSG_MOVE_SET_HOVERING, m_movementCounter++);
if (HasAuraType(SPELL_AURA_CAN_TURN_WHILE_FALLING))
setCompoundState.StateChanges.emplace_back(SMSG_MOVE_SET_CAN_TURN_WHILE_FALLING, m_movementCounter++);
if (HasAura(SPELL_DH_DOUBLE_JUMP))
setCompoundState.StateChanges.emplace_back(SMSG_MOVE_ENABLE_DOUBLE_JUMP, m_movementCounter++);
if (!setCompoundState.StateChanges.empty())
{
setCompoundState.MoverGUID = GetGUID();
SendDirectMessage(setCompoundState.Write());
}
SendAurasForTarget(this);
SendEnchantmentDurations(); // must be after add to map
SendItemDurations(); // must be after add to map
if (GetMap()->IsRaid())
{
m_prevMapDifficulty = GetMap()->GetDifficultyID();
DifficultyEntry const* difficulty = sDifficultyStore.AssertEntry(m_prevMapDifficulty);
SendRaidDifficulty((difficulty->Flags & DIFFICULTY_FLAG_LEGACY) != 0, m_prevMapDifficulty);
}
else if (GetMap()->IsNonRaidDungeon())
{
m_prevMapDifficulty = GetMap()->GetDifficultyID();
SendDungeonDifficulty(m_prevMapDifficulty);
}
else if (!GetMap()->Instanceable())
{
DifficultyEntry const* difficulty = sDifficultyStore.AssertEntry(m_prevMapDifficulty);
SendRaidDifficulty((difficulty->Flags & DIFFICULTY_FLAG_LEGACY) != 0);
}
PhasingHandler::OnMapChange(this);
if (_garrison)
_garrison->SendRemoteInfo();
UpdateItemLevelAreaBasedScaling();
if (!GetPlayerSharingQuest().IsEmpty())
{
if (Quest const* quest = sObjectMgr->GetQuestTemplate(GetSharedQuestID()))
PlayerTalkClass->SendQuestGiverQuestDetails(quest, GetGUID(), true, false);
else
ClearQuestSharingInfo();
}
GetSceneMgr().TriggerDelayedScenes();
}
void Player::SendUpdateToOutOfRangeGroupMembers()
{
if (m_groupUpdateMask == GROUP_UPDATE_FLAG_NONE)
return;
if (Group* group = GetGroup())
group->UpdatePlayerOutOfRange(this);
m_groupUpdateMask = GROUP_UPDATE_FLAG_NONE;
if (Pet* pet = GetPet())
pet->ResetGroupUpdateFlag();
}
void Player::SendTransferAborted(uint32 mapid, TransferAbortReason reason, uint8 arg /*= 0*/, int32 mapDifficultyXConditionID /*= 0*/) const
{
WorldPackets::Movement::TransferAborted transferAborted;
transferAborted.MapID = mapid;
transferAborted.Arg = arg;
transferAborted.TransfertAbort = reason;
transferAborted.MapDifficultyXConditionID = mapDifficultyXConditionID;
SendDirectMessage(transferAborted.Write());
}
void Player::SendInstanceResetWarning(uint32 mapid, Difficulty difficulty, uint32 time, bool welcome) const
{
// type of warning, based on the time remaining until reset
uint32 type;
if (welcome)
type = RAID_INSTANCE_WELCOME;
else if (time > 21600)
type = RAID_INSTANCE_WELCOME;
else if (time > 3600)
type = RAID_INSTANCE_WARNING_HOURS;
else if (time > 300)
type = RAID_INSTANCE_WARNING_MIN;
else
type = RAID_INSTANCE_WARNING_MIN_SOON;
WorldPackets::Instance::RaidInstanceMessage raidInstanceMessage;
raidInstanceMessage.Type = type;
raidInstanceMessage.MapID = mapid;
raidInstanceMessage.DifficultyID = difficulty;
if (InstancePlayerBind const* bind = GetBoundInstance(mapid, difficulty))
raidInstanceMessage.Locked = bind->perm;
else
raidInstanceMessage.Locked = false;
raidInstanceMessage.Extended = false;
SendDirectMessage(raidInstanceMessage.Write());
}
void Player::ApplyEquipCooldown(Item* pItem)
{
if (pItem->GetTemplate()->HasFlag(ITEM_FLAG_NO_EQUIP_COOLDOWN))
return;
TimePoint now = GameTime::Now();
for (ItemEffectEntry const* effectData : pItem->GetEffects())
{
SpellInfo const* effectSpellInfo = sSpellMgr->GetSpellInfo(effectData->SpellID, DIFFICULTY_NONE);
if (!effectSpellInfo)
continue;
// apply proc cooldown to equip auras if we have any
if (effectData->TriggerType == ITEM_SPELLTRIGGER_ON_EQUIP)
{
SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(effectSpellInfo);
if (!procEntry)
continue;
if (Aura* itemAura = GetAura(effectData->SpellID, GetGUID(), pItem->GetGUID()))
itemAura->AddProcCooldown(now + procEntry->Cooldown);
continue;
}
// wrong triggering type
if (effectData->TriggerType != ITEM_SPELLTRIGGER_ON_USE)
continue;
// Don't replace longer cooldowns by equip cooldown if we have any.
if (GetSpellHistory()->GetRemainingCooldown(effectSpellInfo) > 30s)
continue;
GetSpellHistory()->AddCooldown(effectData->SpellID, pItem->GetEntry(), std::chrono::seconds(30));
WorldPackets::Item::ItemCooldown data;
data.ItemGuid = pItem->GetGUID();
data.SpellID = effectData->SpellID;
data.Cooldown = 30 * IN_MILLISECONDS; // Always 30secs?
SendDirectMessage(data.Write());
}
}
void Player::ResetSpells(bool myClassOnly)
{
// not need after this call
if (HasAtLoginFlag(AT_LOGIN_RESET_SPELLS))
RemoveAtLoginFlag(AT_LOGIN_RESET_SPELLS, true);
// make full copy of map (spells removed and marked as deleted at another spell remove
// and we can't use original map for safe iterative with visit each spell at loop end
PlayerSpellMap smap = GetSpellMap();
uint32 family;
if (myClassOnly)
{
ChrClassesEntry const* clsEntry = sChrClassesStore.LookupEntry(GetClass());
if (!clsEntry)
return;
family = clsEntry->SpellClassSet;
for (PlayerSpellMap::const_iterator iter = smap.begin(); iter != smap.end(); ++iter)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(iter->first, DIFFICULTY_NONE);
if (!spellInfo)
continue;
// skip server-side/triggered spells
if (spellInfo->SpellLevel == 0)
continue;
// skip wrong class/race skills
if (!IsSpellFitByClassAndRace(spellInfo->Id))
continue;
// skip other spell families
if (spellInfo->SpellFamilyName != family)
continue;
// skip broken spells
if (!SpellMgr::IsSpellValid(spellInfo, this, false))
continue;
}
}
else
for (PlayerSpellMap::const_iterator iter = smap.begin(); iter != smap.end(); ++iter)
RemoveSpell(iter->first, false, false); // only iter->first can be accessed, object by iter->second can be deleted already
LearnDefaultSkills();
LearnCustomSpells();
LearnQuestRewardedSpells();
}
void Player::LearnCustomSpells()
{
if (!sWorld->getBoolConfig(CONFIG_START_ALL_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;
TC_LOG_DEBUG("entities.player.loading", "Player::LearnCustomSpells: Player '%s' (%s, Class: %u Race: %u): Adding initial spell (SpellID: %u)",
GetName().c_str(), GetGUID().ToString().c_str(), uint32(GetClass()), uint32(GetRace()), tspell);
if (!IsInWorld()) // will send in INITIAL_SPELLS in list anyway at map add
AddSpell(tspell, true, true, true, false);
else // but send in normal spell in game learn case
LearnSpell(tspell, true);
}
}
void Player::LearnDefaultSkills()
{
// learn default race/class skills
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(GetRace(), GetClass());
ASSERT(info);
for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr)
{
SkillRaceClassInfoEntry const* rcInfo = *itr;
if (HasSkill(rcInfo->SkillID))
continue;
if (rcInfo->MinLevel > GetLevel())
continue;
LearnDefaultSkill(rcInfo);
}
}
void Player::LearnDefaultSkill(SkillRaceClassInfoEntry const* rcInfo)
{
uint16 skillId = rcInfo->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 (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, 0, skillValue, maxValue);
break;
}
case SKILL_RANGE_MONO:
SetSkill(skillId, 0, 1, 1);
break;
case SKILL_RANGE_RANK:
{
SkillTiersEntry const* tier = sObjectMgr->GetSkillTier(rcInfo->SkillTierID);
uint16 maxValue = tier->Value[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, 1, skillValue, maxValue);
break;
}
default:
break;
}
}
void Player::LearnQuestRewardedSpells(Quest const* quest)
{
int32 spell_id = quest->GetRewSpell();
uint32 src_spell_id = quest->GetSrcSpell();
// skip quests without rewarded spell
if (!spell_id)
return;
// if RewSpellCast = -1 we remove aura do to SrcSpell from player.
if (spell_id == -1 && src_spell_id)
{
RemoveAurasDueToSpell(src_spell_id);
return;
}
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id, DIFFICULTY_NONE);
if (!spellInfo)
return;
// check learned spells state
bool found = false;
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
{
if (spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL) && !HasSpell(spellEffectInfo.TriggerSpell))
{
found = true;
break;
}
}
// skip quests with not teaching spell or already known spell
if (!found)
return;
if (spellInfo->GetEffects().empty())
return;
SpellEffectInfo const& effect = spellInfo->GetEffect(EFFECT_0);
uint32 learned_0 = effect.TriggerSpell;
if (!HasSpell(learned_0))
{
found = false;
SkillLineAbilityMapBounds skills = sSpellMgr->GetSkillLineAbilityMapBounds(learned_0);
for (auto skillItr = skills.first; skillItr != skills.second; ++skillItr)
{
if (skillItr->second->AcquireMethod == SKILL_LINE_ABILITY_REWARDED_FROM_QUEST)
{
found = true;
break;
}
}
if (!found)
return;
}
CastSpell(this, spell_id, 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 skillId, uint32 skillValue)
{
uint8 race = GetRace();
uint32 classMask = GetClassMask();
std::vector<SkillLineAbilityEntry const*> const* skillLineAbilities = sDB2Manager.GetSkillLineAbilitiesBySkill(skillId);
if (!skillLineAbilities)
return;
for (SkillLineAbilityEntry const* ability : *skillLineAbilities)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(ability->Spell, DIFFICULTY_NONE);
if (!spellInfo)
continue;
switch (ability->AcquireMethod)
{
case SKILL_LINE_ABILITY_LEARNED_ON_SKILL_VALUE:
case SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN:
break;
case SKILL_LINE_ABILITY_REWARDED_FROM_QUEST:
if (!ability->GetFlags().HasFlag(SkillLineAbilityFlags::CanFallbackToLearnedOnSkillLearn) ||
!spellInfo->MeetsFutureSpellPlayerCondition(this))
continue;
break;
default:
continue;
}
// AcquireMethod == 2 && NumSkillUps == 1 --> automatically learn riding skill spell, else we skip it (client shows riding in spellbook as trainable).
if (skillId == SKILL_RIDING && (ability->AcquireMethod != SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN || ability->NumSkillUps != 1))
continue;
// Check race if set
if (ability->RaceMask && !ability->RaceMask.HasRace(race))
continue;
// Check class if set
if (ability->ClassMask && !(ability->ClassMask & classMask))
continue;
// check level, skip class spells if not high enough
if (GetLevel() < spellInfo->SpellLevel)
continue;
// need unlearn spell
if (int32(skillValue) < ability->MinSkillLineRank && ability->AcquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_VALUE)
RemoveSpell(ability->Spell);
// need learn
else if (!IsInWorld())
AddSpell(ability->Spell, true, true, true, false, false, ability->SkillLine);
else
LearnSpell(ability->Spell, true, ability->SkillLine);
}
}
int32 Player::FindProfessionSlotFor(uint32 skillId) const
{
SkillLineEntry const* skillEntry = sSkillLineStore.LookupEntry(skillId);
if (!skillEntry)
return -1;
int32 const* professionsBegin = m_activePlayerData->ProfessionSkillLine.begin();
int32 const* professionsEnd = m_activePlayerData->ProfessionSkillLine.end();
// if there is no same profession, find any free slot
auto freeSlot = std::find(professionsBegin, professionsEnd, 0);
return freeSlot != professionsEnd ? std::distance(professionsBegin, freeSlot) : -1;
}
void Player::SendAurasForTarget(Unit* target) const
{
if (!target || target->GetVisibleAuras().empty()) // speedup things
return;
Unit::VisibleAuraContainer const& visibleAuras = target->GetVisibleAuras();
WorldPackets::Spells::AuraUpdate update;
update.UpdateAll = true;
update.UnitGUID = target->GetGUID();
update.Auras.reserve(visibleAuras.size());
for (AuraApplication* auraApp : visibleAuras)
{
WorldPackets::Spells::AuraInfo auraInfo;
auraApp->BuildUpdatePacket(auraInfo, false);
update.Auras.push_back(auraInfo);
}
SendDirectMessage(update.Write());
}
void Player::SetDailyQuestStatus(uint32 quest_id)
{
if (Quest const* qQuest = sObjectMgr->GetQuestTemplate(quest_id))
{
if (!qQuest->IsDFQuest())
{
AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::DailyQuestsCompleted)) = quest_id;
m_lastDailyQuestTime = GameTime::GetGameTime(); // last daily quest time
m_DailyQuestChanged = true;
}
else
{
m_DFQuests.insert(quest_id);
m_lastDailyQuestTime = GameTime::GetGameTime();
m_DailyQuestChanged = true;
}
}
}
bool Player::IsDailyQuestDone(uint32 quest_id)
{
return m_activePlayerData->DailyQuestsCompleted.FindIndex(quest_id) >= 0;
}
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::DailyReset()
{
for (int32 questId : m_activePlayerData->DailyQuestsCompleted)
if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(questId))
SetQuestCompletedBit(questBit, false);
WorldPackets::Quest::DailyQuestsReset dailyQuestsReset;
dailyQuestsReset.Count = int32(m_activePlayerData->DailyQuestsCompleted.size());
SendDirectMessage(dailyQuestsReset.Write());
ClearDynamicUpdateFieldValues(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::DailyQuestsCompleted));
m_DFQuests.clear(); // Dungeon Finder Quests.
// DB data deleted in caller
m_DailyQuestChanged = false;
m_lastDailyQuestTime = 0;
if (_garrison)
_garrison->ResetFollowerActivationLimit();
}
void Player::ResetWeeklyQuestStatus()
{
if (m_weeklyquests.empty())
return;
for (uint32 questId : m_weeklyquests)
if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(questId))
SetQuestCompletedBit(questBit, false);
m_weeklyquests.clear();
// DB data deleted in caller
m_WeeklyQuestChanged = false;
}
void Player::ResetSeasonalQuestStatus(uint16 event_id)
{
auto eventItr = m_seasonalquests.find(event_id);
if (eventItr == m_seasonalquests.end())
return;
if (eventItr->second.empty())
return;
for (uint32 questId : eventItr->second)
if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(questId))
SetQuestCompletedBit(questBit, false);
m_seasonalquests.erase(eventItr);
// DB data deleted in caller
m_SeasonalQuestChanged = false;
}
void Player::ResetMonthlyQuestStatus()
{
if (m_monthlyquests.empty())
return;
for (uint32 questId : m_monthlyquests)
if (uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(questId))
SetQuestCompletedBit(questBit, false);
m_monthlyquests.clear();
// DB data deleted in caller
m_MonthlyQuestChanged = false;
}
Battleground* Player::GetBattleground() const
{
if (GetBattlegroundId() == 0)
return nullptr;
return sBattlegroundMgr->GetBattleground(GetBattlegroundId(), m_bgData.bgTypeID);
}
uint32 Player::GetBattlegroundQueueJoinTime(BattlegroundQueueTypeId bgQueueTypeId) const
{
for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
return m_bgBattlegroundQueueID[i].joinTime;
return 0;
}
bool Player::InBattlegroundQueue(bool ignoreArena) const
{
for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
if (m_bgBattlegroundQueueID[i].bgQueueTypeId != BATTLEGROUND_QUEUE_NONE
&& (!ignoreArena || m_bgBattlegroundQueueID[i].bgQueueTypeId.BattlemasterListId != BATTLEGROUND_AA))
return true;
return false;
}
BattlegroundQueueTypeId Player::GetBattlegroundQueueTypeId(uint32 index) const
{
if (index < PLAYER_MAX_BATTLEGROUND_QUEUES)
return m_bgBattlegroundQueueID[index].bgQueueTypeId;
return BATTLEGROUND_QUEUE_NONE;
}
uint32 Player::GetBattlegroundQueueIndex(BattlegroundQueueTypeId bgQueueTypeId) const
{
for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
if (m_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 (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
return m_bgBattlegroundQueueID[i].invitedToInstance != 0;
return false;
}
bool Player::InBattlegroundQueueForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId) const
{
return GetBattlegroundQueueIndex(bgQueueTypeId) < PLAYER_MAX_BATTLEGROUND_QUEUES;
}
void Player::SetBattlegroundId(uint32 val, BattlegroundTypeId bgTypeId)
{
m_bgData.bgInstanceID = val;
m_bgData.bgTypeID = bgTypeId;
}
uint32 Player::AddBattlegroundQueueId(BattlegroundQueueTypeId val)
{
for (uint8 i=0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
{
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == BATTLEGROUND_QUEUE_NONE || m_bgBattlegroundQueueID[i].bgQueueTypeId == val)
{
m_bgBattlegroundQueueID[i].bgQueueTypeId = val;
m_bgBattlegroundQueueID[i].invitedToInstance = 0;
m_bgBattlegroundQueueID[i].joinTime = GameTime::GetGameTime();
return i;
}
}
return PLAYER_MAX_BATTLEGROUND_QUEUES;
}
bool Player::HasFreeBattlegroundQueueId() const
{
for (uint8 i=0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
if (m_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 (m_bgBattlegroundQueueID[i].bgQueueTypeId == val)
{
m_bgBattlegroundQueueID[i].bgQueueTypeId = BATTLEGROUND_QUEUE_NONE;
m_bgBattlegroundQueueID[i].invitedToInstance = 0;
m_bgBattlegroundQueueID[i].joinTime = 0;
return;
}
}
}
void Player::SetInviteForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId, uint32 instanceId)
{
for (uint8 i=0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
m_bgBattlegroundQueueID[i].invitedToInstance = instanceId;
}
bool Player::IsInvitedForBattlegroundInstance(uint32 instanceId) const
{
for (uint8 i=0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
if (m_bgBattlegroundQueueID[i].invitedToInstance == instanceId)
return true;
return false;
}
bool Player::InArena() const
{
Battleground* bg = GetBattleground();
if (!bg || !bg->isArena())
return false;
return true;
}
bool Player::GetBGAccessByLevel(BattlegroundTypeId bgTypeId) const
{
// get a template bg instead of running one
Battleground* bg = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId);
if (!bg)
return false;
// limit check leel to dbc compatible level range
uint32 level = GetLevel();
if (level > DEFAULT_MAX_LEVEL)
level = DEFAULT_MAX_LEVEL;
if (level < bg->GetMinLevel() || level > bg->GetMaxLevel())
return false;
return true;
}
float Player::GetReputationPriceDiscount(Creature const* 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);
}
Player* Player::GetTrader() const
{
return m_trade ? m_trade->GetTrader() : nullptr;
}
bool Player::IsSpellFitByClassAndRace(uint32 spell_id) const
{
uint8 race = GetRace();
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.HasRace(race))
continue;
// skip wrong class skills
if (_spell_idx->second->ClassMask && (_spell_idx->second->ClassMask & classmask) == 0)
continue;
// skip wrong class and race skill saved in SkillRaceClassInfo.dbc
if (!sDB2Manager.GetSkillRaceClassInfo(_spell_idx->second->SkillLine, GetRace(), GetClass()))
continue;
return true;
}
return false;
}
bool Player::HasQuestForGO(int32 GOId) const
{
for (QuestObjectiveStatusMap::value_type const& objectiveItr : Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { QUEST_OBJECTIVE_GAMEOBJECT, GOId }))
{
Quest const* qInfo = ASSERT_NOTNULL(sObjectMgr->GetQuestTemplate(objectiveItr.second.QuestStatusItr->first));
QuestObjective const& objective = *objectiveItr.second.Objective;
if (!IsQuestObjectiveCompletable(objectiveItr.second.QuestStatusItr->second.Slot, qInfo, objective))
continue;
// hide quest if player is in raid-group and quest is no raid quest
if (GetGroup() && GetGroup()->isRaidGroup() && !qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID()))
if (!InBattleground()) //there are two ways.. we can make every bg-quest a raidquest, or add this code here.. i don't know if this can be exploited by other quests, but i think all other quests depend on a specific area.. but keep this in mind, if something strange happens later
continue;
if (!IsQuestObjectiveComplete(objectiveItr.second.QuestStatusItr->second.Slot, qInfo, objective))
return true;
}
return false;
}
void Player::UpdateVisibleGameobjectsOrSpellClicks()
{
if (m_clientGUIDs.empty())
return;
UpdateData udata(GetMapId());
WorldPacket packet;
for (auto itr = m_clientGUIDs.begin(); itr != m_clientGUIDs.end(); ++itr)
{
if (itr->IsGameObject())
{
if (GameObject* obj = ObjectAccessor::GetGameObject(*this, *itr))
{
UF::ObjectData::Base objMask;
UF::GameObjectData::Base goMask;
if (m_questObjectiveStatus.find({ QUEST_OBJECTIVE_GAMEOBJECT, int32(obj->GetEntry()) }) != m_questObjectiveStatus.end())
objMask.MarkChanged(&UF::ObjectData::DynamicFlags);
switch (obj->GetGoType())
{
case GAMEOBJECT_TYPE_QUESTGIVER:
case GAMEOBJECT_TYPE_CHEST:
case GAMEOBJECT_TYPE_GOOBER:
case GAMEOBJECT_TYPE_GENERIC:
if (sObjectMgr->IsGameObjectForQuests(obj->GetEntry()))
objMask.MarkChanged(&UF::ObjectData::DynamicFlags);
break;
default:
break;
}
if (objMask.GetChangesMask().IsAnySet() || goMask.GetChangesMask().IsAnySet())
obj->BuildValuesUpdateForPlayerWithMask(&udata, objMask.GetChangesMask(), goMask.GetChangesMask(), this);
}
}
else if (itr->IsCreatureOrVehicle())
{
Creature* obj = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, *itr);
if (!obj)
continue;
// check if this unit requires quest specific flags
if (!obj->HasNpcFlag(UNIT_NPC_FLAG_SPELLCLICK))
continue;
auto clickBounds = sObjectMgr->GetSpellClickInfoMapBounds(obj->GetEntry());
for (auto const& clickPair : clickBounds)
{
if (sConditionMgr->GetConditionsForSpellClickEvent(obj->GetEntry(), clickPair.second.spellId))
{
UF::ObjectData::Base objMask;
UF::UnitData::Base unitMask;
unitMask.MarkChanged(&UF::UnitData::NpcFlags, 0); // NpcFlags[0] has UNIT_NPC_FLAG_SPELLCLICK
obj->BuildValuesUpdateForPlayerWithMask(&udata, objMask.GetChangesMask(), unitMask.GetChangesMask(), this);
break;
}
}
}
}
udata.BuildPacket(&packet);
SendDirectMessage(&packet);
}
bool Player::HasSummonPending() const
{
return m_summon_expire >= GameTime::GetGameTime();
}
void Player::SendSummonRequestFrom(Unit* summoner)
{
if (!summoner)
return;
// Player already has active summon request
if (HasSummonPending())
return;
// Evil Twin (ignore player summon, but hide this for summoner)
if (HasAura(23445))
return;
m_summon_expire = GameTime::GetGameTime() + MAX_PLAYER_SUMMON_DELAY;
m_summon_location.WorldRelocate(*summoner);
WorldPackets::Movement::SummonRequest summonRequest;
summonRequest.SummonerGUID = summoner->GetGUID();
summonRequest.SummonerVirtualRealmAddress = GetVirtualRealmAddress();
summonRequest.AreaID = summoner->GetZoneId();
SendDirectMessage(summonRequest.Write());
}
void Player::SummonIfPossible(bool agree)
{
if (!agree)
{
m_summon_expire = 0;
return;
}
// expire and auto declined
if (m_summon_expire < GameTime::GetGameTime())
return;
// stop taxi flight at summon
FinishTaxiFlight();
// 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;
UpdateCriteria(CriteriaType::AcceptSummon, 1);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Summon);
TeleportTo(m_summon_location);
}
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->m_itemData->Expiration)
{
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)
return;
// unequip offhand weapon if player doesn't have dual wield anymore
if (!CanDualWield()
&& ((offItem->GetTemplate()->GetInventoryType() == INVTYPE_WEAPONOFFHAND && !offItem->GetTemplate()->HasFlag(ITEM_FLAG3_ALWAYS_ALLOW_DUAL_WIELD))
|| offItem->GetTemplate()->GetInventoryType() == INVTYPE_WEAPON))
force = true;
// need unequip offhand for 2h-weapon without TitanGrip (in any from hands)
if (!force && (CanTitanGrip() || (offItem->GetTemplate()->GetInventoryType() != INVTYPE_2HWEAPON && !IsTwoHandUsed())))
return;
ItemPosCountVec off_dest;
if (CanStoreItem(NULL_BAG, NULL_SLOT, off_dest, offItem, false) == 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()->GetTrinityString(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);
}
}
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:
{
if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND))
if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
return true;
if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND))
if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
return true;
break;
}
case ITEM_CLASS_ARMOR:
{
if (!spellInfo->HasAttribute(SPELL_ATTR8_ARMOR_SPECIALIZATION))
{
// most used check: shield only
if (spellInfo->EquippedItemSubClassMask & (1 << ITEM_SUBCLASS_ARMOR_SHIELD))
{
if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND))
if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
return true;
// special check to filter things like Shield Wall, the aura is not permanent and must stay even without required item
if (!spellInfo->IsPassive())
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
if (spellEffectInfo.IsAura())
return true;
}
// 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;
}
else
{
// requires item equipped in all armor slots
for (uint8 i : {EQUIPMENT_SLOT_HEAD, EQUIPMENT_SLOT_SHOULDERS, EQUIPMENT_SLOT_CHEST, EQUIPMENT_SLOT_WAIST, EQUIPMENT_SLOT_LEGS, EQUIPMENT_SLOT_FEET, EQUIPMENT_SLOT_WRISTS, EQUIPMENT_SLOT_HANDS})
{
Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, i);
if (!item || item == ignoreItem || !item->IsFitToSpellRequirements(spellInfo))
return false;
}
return true;
}
break;
}
default:
TC_LOG_ERROR("entities.player", "Player::HasItemFitToSpellRequirements: Not handled spell requirement for item class %u", spellInfo->EquippedItemClass);
break;
}
return false;
}
bool Player::CanNoReagentCast(SpellInfo const* spellInfo) const
{
// don't take reagents for spells with SPELL_ATTR5_NO_REAGENT_WHILE_PREP
if (spellInfo->HasAttribute(SPELL_ATTR5_NO_REAGENT_WHILE_PREP) &&
HasUnitFlag(UNIT_FLAG_PREPARATION))
return true;
// Check no reagent use mask
flag128 noReagentMask;
noReagentMask[0] = m_activePlayerData->NoReagentCostMask[0];
noReagentMask[1] = m_activePlayerData->NoReagentCostMask[1];
noReagentMask[2] = m_activePlayerData->NoReagentCostMask[2];
noReagentMask[3] = m_activePlayerData->NoReagentCostMask[3];
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 not self applied auras
SpellInfo const* spellInfo = aura->GetSpellInfo();
if (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 cast 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));
}
void Player::InitializeSelfResurrectionSpells()
{
ClearSelfResSpell();
uint32 spells[3] = { };
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 ((*itr)->GetSpellInfo()->SpellFamilyName == SPELLFAMILY_WARLOCK && (*itr)->GetSpellInfo()->SpellFamilyFlags[1] & 0x1000000)
spells[0] = 3026;
// Twisting Nether // prio: 2 (max)
else if ((*itr)->GetId() == 23701 && roll_chance_i(10))
spells[1] = 23700;
}
// Reincarnation (passive spell) // prio: 1
if (HasSpell(20608) && !GetSpellHistory()->HasCooldown(21169))
spells[2] = 21169;
for (uint32 selfResSpell : spells)
if (selfResSpell)
AddSelfResSpell(selfResSpell);
}
// Used in triggers for check "Only to targets that grant experience or honor" req
bool Player::isHonorOrXPTarget(Unit const* victim) const
{
uint8 v_level = victim->GetLevelForTarget(this);
uint8 k_grey = Trinity::XP::GetGrayLevel(GetLevel());
// Victim level less gray level
if (v_level < k_grey && !sWorld->getIntConfig(CONFIG_MIN_CREATURE_SCALED_XP_RATIO))
return false;
if (Creature const* const creature = victim->ToCreature())
{
if (creature->IsCritter() || creature->IsTotem())
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 = GetGroup())
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* player = itr->GetSource();
if (!player)
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
{
if (!pRewardSource || !IsInMap(pRewardSource))
return false;
WorldObject const* player = GetCorpse();
if (!player || IsAlive())
player = this;
if (player->GetMap()->IsDungeon())
return true;
return pRewardSource->GetDistance(player) <= sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE);
}
bool Player::IsAtRecruitAFriendDistance(WorldObject const* pOther) const
{
if (!pOther || !IsInMap(pOther))
return false;
WorldObject const* player = GetCorpse();
if (!player || IsAlive())
player = this;
return pOther->GetDistance(player) <= sWorld->getFloatConfig(CONFIG_MAX_RECRUIT_A_FRIEND_DISTANCE);
}
void Player::ResurrectUsingRequestData()
{
// Teleport before resurrecting by player, otherwise the player might get attacked from creatures near his corpse
TeleportTo(_resurrectionData->Location);
if (IsBeingTeleported())
{
ScheduleDelayedOperation(DELAYED_RESURRECT_PLAYER);
return;
}
ResurrectUsingRequestDataImpl();
}
void Player::ResurrectUsingRequestDataImpl()
{
// save health and mana before resurrecting, _resurrectionData can be erased
uint32 resurrectHealth = _resurrectionData->Health;
uint32 resurrectMana = _resurrectionData->Mana;
uint32 resurrectAura = _resurrectionData->Aura;
ObjectGuid resurrectGUID = _resurrectionData->GUID;
ResurrectPlayer(0.0f, false);
SetHealth(resurrectHealth);
SetPower(POWER_MANA, resurrectMana);
SetPower(POWER_RAGE, 0);
SetFullPower(POWER_ENERGY);
SetFullPower(POWER_FOCUS);
SetPower(POWER_LUNAR_POWER, 0);
if (uint32 aura = resurrectAura)
CastSpell(this, aura, CastSpellExtraArgs(TRIGGERED_FULL_MASK)
.SetOriginalCaster(resurrectGUID));
SpawnCorpseBones();
}
void Player::SetClientControl(Unit* target, bool allowMove)
{
// a player can never client control nothing
ASSERT(target);
// don't allow possession to be overridden
if (target->HasUnitState(UNIT_STATE_CHARMED) && (GetGUID() != target->GetCharmerGUID()))
{
TC_LOG_ERROR("entities.player", "Player '%s' attempt to client control '%s', which is charmed by GUID %s",
GetName().c_str(), target->GetName().c_str(), target->GetCharmerGUID().ToString().c_str());
return;
}
// still affected by some aura that shouldn't allow control, only allow on last such aura to be removed
if (target->HasUnitState(UNIT_STATE_FLEEING | UNIT_STATE_CONFUSED))
allowMove = false;
WorldPackets::Movement::ControlUpdate data;
data.Guid = target->GetGUID();
data.On = allowMove;
SendDirectMessage(data.Write());
WorldObject* viewpoint = GetViewpoint();
if (!viewpoint)
viewpoint = this;
if (target != viewpoint)
{
if (viewpoint != this)
SetViewpoint(viewpoint, false);
if (target != this)
SetViewpoint(target, true);
}
SetMovedUnit(target);
}
void Player::UpdateZoneDependentAuras(uint32 newZone)
{
// Some spells applied at enter into zone (with subzones), aura removed in UpdateAreaDependentAuras that called always at zone->area update
SpellAreaForAreaMapBounds saBounds = sSpellMgr->GetSpellAreaForAreaMapBounds(newZone);
for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
if (itr->second->flags & SPELL_AREA_FLAG_AUTOCAST && itr->second->IsFitToRequirements(this, newZone, 0))
if (!HasAura(itr->second->spellId))
CastSpell(this, itr->second->spellId, true);
}
void Player::UpdateAreaDependentAuras(uint32 newArea)
{
// remove auras from spells with area limitations
for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();)
{
// use m_zoneUpdateId for speed: UpdateArea called from UpdateZone or instead UpdateZone in both cases m_zoneUpdateId up-to-date
if (iter->second->GetSpellInfo()->CheckLocation(GetMapId(), m_zoneUpdateId, newArea, this) != SPELL_CAST_OK)
RemoveOwnedAura(iter);
else
++iter;
}
// some auras applied at subzone enter
SpellAreaForAreaMapBounds saBounds = sSpellMgr->GetSpellAreaForAreaMapBounds(newArea);
for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
if (itr->second->flags & SPELL_AREA_FLAG_AUTOCAST && itr->second->IsFitToRequirements(this, m_zoneUpdateId, newArea))
if (!HasAura(itr->second->spellId))
CastSpell(this, itr->second->spellId, true);
}
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();
// 0..2 full period
// should be ceil(x)-1 but not floor(x)
uint64 count = (now < m_deathExpireTime - 1) ? (m_deathExpireTime - 1 - now) / DEATH_EXPIRE_STEP : 0;
return copseReclaimDelay[count];
}
void Player::UpdateCorpseReclaimDelay()
{
bool pvp = (m_ExtraFlags & PLAYER_EXTRA_PVP_DEATH) != 0;
if ((pvp && !sWorld->getBoolConfig(CONFIG_DEATH_CORPSE_RECLAIM_DELAY_PVP)) ||
(!pvp && !sWorld->getBoolConfig(CONFIG_DEATH_CORPSE_RECLAIM_DELAY_PVE)))
return;
time_t now = GameTime::GetGameTime();
if (now < m_deathExpireTime)
{
// full and partly periods 1..3
uint64 count = (m_deathExpireTime - now) / DEATH_EXPIRE_STEP + 1;
if (count < MAX_DEATH_COUNT)
m_deathExpireTime = now+(count + 1) * DEATH_EXPIRE_STEP;
else
m_deathExpireTime = now + MAX_DEATH_COUNT*DEATH_EXPIRE_STEP;
}
else
m_deathExpireTime = now + DEATH_EXPIRE_STEP;
}
int32 Player::CalculateCorpseReclaimDelay(bool load) const
{
Corpse* corpse = GetCorpse();
if (load && !corpse)
return -1;
bool pvp = corpse ? corpse->GetType() == CORPSE_RESURRECTABLE_PVP : (m_ExtraFlags & PLAYER_EXTRA_PVP_DEATH) != 0;
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();
if (now >= expected_time)
return -1;
delay = expected_time - now;
}
else
delay = GetCorpseReclaimDelay(pvp);
return delay * IN_MILLISECONDS;
}
void Player::SendCorpseReclaimDelay(uint32 delay) const
{
WorldPackets::Misc::CorpseReclaimDelay packet;
packet.Remaining = delay;
SendDirectMessage(packet.Write());
}
Player* Player::GetNextRandomRaidMember(float radius)
{
Group* group = GetGroup();
if (!group)
return nullptr;
std::vector<Player*> 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 guidMember) const
{
Group const* grp = GetGroup();
if (!grp)
return ERR_NOT_IN_GROUP;
if (grp->isLFGGroup())
{
ObjectGuid gguid = grp->GetGUID();
if (!sLFGMgr->GetKicksLeft(gguid))
return ERR_PARTY_LFG_BOOT_LIMIT;
lfg::LfgState state = sLFGMgr->GetState(gguid);
if (sLFGMgr->IsVoteKickActive(gguid))
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;
/* Missing support for these types
return ERR_PARTY_LFG_BOOT_COOLDOWN_S;
return ERR_PARTY_LFG_BOOT_NOT_ELIGIBLE_S;
*/
}
else
{
if (!grp->IsLeader(GetGUID()) && !grp->IsAssistant(GetGUID()))
return ERR_NOT_LEADER;
if (InBattleground())
return ERR_INVITE_RESTRICTED;
if (grp->IsLeader(guidMember))
return ERR_NOT_LEADER;
}
return ERR_PARTY_RESULT_OK;
}
bool Player::isUsingLfg() const
{
return sLFGMgr->GetState(GetGUID()) != lfg::LFG_STATE_NONE;
}
bool Player::inRandomLfgDungeon() const
{
if (sLFGMgr->selectedRandomLfgDungeon(GetGUID()))
{
Map const* map = GetMap();
return sLFGMgr->inLfgDungeonMap(GetGUID(), map->GetId(), map->GetDifficultyID());
}
return false;
}
void Player::SetBattlegroundOrBattlefieldRaid(Group* group, int8 subgroup)
{
//we must move references from m_group to m_originalGroup
SetOriginalGroup(GetGroup(), GetSubGroup());
m_group.unlink();
m_group.link(group, this);
m_group.setSubGroup((uint8)subgroup);
}
void Player::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 == nullptr)
m_originalGroup.unlink();
else
{
// never use SetOriginalGroup without a subgroup unless you specify NULL for group
ASSERT(subgroup >= 0);
m_originalGroup.link(group, this);
m_originalGroup.setSubGroup((uint8)subgroup);
}
}
void Player::SetPartyType(GroupCategory category, uint8 type)
{
ASSERT(category < MAX_GROUP_CATEGORY);
uint8 value = m_playerData->PartyType;
value &= ~uint8(uint8(0xFF) << (category * 4));
value |= uint8(uint8(type) << (category * 4));
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::PartyType), value);
}
void Player::ResetGroupUpdateSequenceIfNeeded(Group const* group)
{
GroupCategory category = group->GetGroupCategory();
// Rejoining the last group should not reset the sequence
if (m_groupUpdateSequences[category].GroupGuid != group->GetGUID())
{
m_groupUpdateSequences[category].GroupGuid = group->GetGUID();
m_groupUpdateSequences[category].UpdateSequenceNumber = 1;
}
}
int32 Player::NextGroupUpdateSequenceNumber(GroupCategory category)
{
return m_groupUpdateSequences[category].UpdateSequenceNumber++;
}
void Player::ProcessTerrainStatusUpdate(ZLiquidStatus oldLiquidStatus, Optional<LiquidData> const& newLiquidData)
{
// process liquid auras using generic unit code
Unit::ProcessTerrainStatusUpdate(oldLiquidStatus, newLiquidData);
// player specific logic for mirror timers
if (GetLiquidStatus() && newLiquidData)
{
// Breath bar state (under water in any liquid type)
if (newLiquidData->type_flags.HasFlag(map_liquidHeaderTypeFlags::AllLiquids))
{
if (GetLiquidStatus() & LIQUID_MAP_UNDER_WATER)
m_MirrorTimerFlags |= UNDERWATER_INWATER;
else
m_MirrorTimerFlags &= ~UNDERWATER_INWATER;
}
// Fatigue bar state (if not on flight path or transport)
if (newLiquidData->type_flags.HasFlag(map_liquidHeaderTypeFlags::DarkWater) && !IsInFlight() && !GetTransport())
m_MirrorTimerFlags |= UNDERWATER_INDARKWATER;
else
m_MirrorTimerFlags &= ~UNDERWATER_INDARKWATER;
// Lava state (any contact)
if (newLiquidData->type_flags.HasFlag(map_liquidHeaderTypeFlags::Magma))
{
if (GetLiquidStatus() & MAP_LIQUID_STATUS_IN_CONTACT)
m_MirrorTimerFlags |= UNDERWATER_INLAVA;
else
m_MirrorTimerFlags &= ~UNDERWATER_INLAVA;
}
// Slime state (any contact)
if (newLiquidData->type_flags.HasFlag(map_liquidHeaderTypeFlags::Slime))
{
if (GetLiquidStatus() & MAP_LIQUID_STATUS_IN_CONTACT)
m_MirrorTimerFlags |= UNDERWATER_INSLIME;
else
m_MirrorTimerFlags &= ~UNDERWATER_INSLIME;
}
}
else
m_MirrorTimerFlags &= ~(UNDERWATER_INWATER | UNDERWATER_INLAVA | UNDERWATER_INSLIME | UNDERWATER_INDARKWATER);
}
void Player::AtEnterCombat()
{
Unit::AtEnterCombat();
if (GetCombatManager().HasPvPCombat())
EnablePvpRules(true);
}
void Player::AtExitCombat()
{
Unit::AtExitCombat();
UpdatePotionCooldown();
m_combatExitTime = getMSTime();
}
float Player::GetBlockPercent(uint8 attackerLevel) const
{
float blockArmor = float(*m_activePlayerData->ShieldBlock);
float armorConstant = sDB2Manager.EvaluateExpectedStat(ExpectedStatType::ArmorConstant, attackerLevel, -2, 0, CLASS_NONE);
if (!(blockArmor + armorConstant))
return 0;
return std::min(blockArmor / (blockArmor + armorConstant), 0.85f);
}
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();
}
bool ItemPosCount::isContainedIn(std::vector<ItemPosCount> 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() const
{
if (WorldObject* target = GetViewpoint())
{
if (target->isType(TYPEMASK_UNIT))
{
static_cast<Unit*>(target)->RemoveAurasByType(SPELL_AURA_BIND_SIGHT, GetGUID());
static_cast<Unit*>(target)->RemoveAurasByType(SPELL_AURA_MOD_POSSESS, GetGUID());
static_cast<Unit*>(target)->RemoveAurasByType(SPELL_AURA_MOD_POSSESS_PET, GetGUID());
}
}
}
void Player::SetViewpoint(WorldObject* target, bool apply)
{
if (apply)
{
TC_LOG_DEBUG("maps", "Player::CreateViewpoint: Player '%s' (%s) creates seer (Entry: %u, TypeId: %u).",
GetName().c_str(), GetGUID().ToString().c_str(), target->GetEntry(), target->GetTypeId());
if (ObjectGuid::Empty != m_activePlayerData->FarsightObject)
{
TC_LOG_FATAL("entities.player", "Player::CreateViewpoint: Player '%s' (%s) cannot add new viewpoint!", GetName().c_str(), GetGUID().ToString().c_str());
return;
}
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::FarsightObject), target->GetGUID());
// farsight dynobj or puppet may be very far away
UpdateVisibilityOf(target);
if (target->isType(TYPEMASK_UNIT) && target != GetVehicleBase())
static_cast<Unit*>(target)->AddPlayerToVision(this);
SetSeer(target);
}
else
{
TC_LOG_DEBUG("maps", "Player::CreateViewpoint: Player %s removed seer", GetName().c_str());
if (target->GetGUID() != m_activePlayerData->FarsightObject)
{
TC_LOG_FATAL("entities.player", "Player::CreateViewpoint: Player '%s' (%s) cannot remove current viewpoint!", GetName().c_str(), GetGUID().ToString().c_str());
return;
}
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::FarsightObject), ObjectGuid::Empty);
if (target->isType(TYPEMASK_UNIT) && target != GetVehicleBase())
static_cast<Unit*>(target)->RemovePlayerFromVision(this);
//must immediately set seer back otherwise may crash
SetSeer(this);
//WorldPacket data(SMSG_CLEAR_FAR_SIGHT_IMMEDIATE, 0);
//SendDirectMessage(&data);
}
}
WorldObject* Player::GetViewpoint() const
{
ObjectGuid guid = m_activePlayerData->FarsightObject;
if (!guid.IsEmpty())
return static_cast<WorldObject*>(ObjectAccessor::GetObjectByTypeMask(*this, guid, TYPEMASK_SEER));
return nullptr;
}
bool Player::CanUseBattlegroundObject(GameObject* gameobject) const
{
// It is possible to call this method with a null pointer, only skipping faction check.
if (gameobject)
{
FactionTemplateEntry const* playerFaction = GetFactionTemplateEntry();
FactionTemplateEntry const* faction = sFactionTemplateStore.LookupEntry(gameobject->GetFaction());
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
}
int64 Player::GetBarberShopCost(Trinity::IteratorPair<UF::ChrCustomizationChoice const*> newCustomizations) const
{
if (HasAuraType(SPELL_AURA_REMOVE_BARBER_SHOP_COST))
return 0;
GtBarberShopCostBaseEntry const* bsc = sBarberShopCostBaseGameTable.GetRow(GetLevel());
if (!bsc) // shouldn't happen
return 0;
int64 cost = 0;
for (UF::ChrCustomizationChoice const& newChoice : newCustomizations)
{
int32 currentCustomizationIndex = m_playerData->Customizations.FindIndexIf([&](UF::ChrCustomizationChoice const& currentCustomization)
{
return currentCustomization.ChrCustomizationOptionID == newChoice.ChrCustomizationOptionID;
});
if (currentCustomizationIndex == -1 || m_playerData->Customizations[currentCustomizationIndex].ChrCustomizationChoiceID != newChoice.ChrCustomizationChoiceID)
if (ChrCustomizationOptionEntry const* customizationOption = sChrCustomizationOptionStore.LookupEntry(newChoice.ChrCustomizationOptionID))
cost += bsc->Cost * customizationOption->BarberShopCostModifier;
}
return cost;
}
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
{
uint32 fieldIndexOffset = bitIndex / 64;
if (fieldIndexOffset >= m_activePlayerData->KnownTitles.size())
return false;
uint64 flag = UI64LIT(1) << (bitIndex % 64);
return (m_activePlayerData->KnownTitles[fieldIndexOffset] & flag) != 0;
}
bool Player::HasTitle(CharTitlesEntry const* title) const
{
return HasTitle(title->MaskID);
}
void Player::SetTitle(CharTitlesEntry const* title, bool lost)
{
uint32 fieldIndexOffset = title->MaskID / 64;
uint64 flag = UI64LIT(1) << (title->MaskID % 64);
if (lost)
{
if (!HasTitle(title))
return;
RemoveUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::KnownTitles, fieldIndexOffset), flag);
}
else
{
if (HasTitle(title))
return;
SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::KnownTitles, fieldIndexOffset), flag);
}
WorldPackets::Character::TitleEarned packet(lost ? SMSG_TITLE_LOST : SMSG_TITLE_EARNED);
packet.Index = title->MaskID;
SendDirectMessage(packet.Write());
}
uint8 Player::GetRunesState() const
{
return uint8(m_runes->RuneState & ((1 << GetMaxPower(POWER_RUNES)) - 1));
}
uint32 Player::GetRuneBaseCooldown() const
{
float cooldown = RUNE_BASE_COOLDOWN;
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_RUNES)
cooldown *= 1.0f - (*i)->GetAmount() / 100.0f;
// Runes cooldown are now affected by player's haste from equipment ...
float hastePct = GetRatingBonusValue(CR_HASTE_MELEE);
// ... and some auras.
hastePct += GetTotalAuraModifier(SPELL_AURA_MOD_MELEE_HASTE);
hastePct += GetTotalAuraModifier(SPELL_AURA_MOD_MELEE_HASTE_2);
hastePct += GetTotalAuraModifier(SPELL_AURA_MOD_MELEE_HASTE_3);
cooldown *= 1.0f - (hastePct / 100.0f);
return cooldown;
}
void Player::SetRuneCooldown(uint8 index, uint32 cooldown)
{
m_runes->Cooldown[index] = cooldown;
m_runes->SetRuneState(index, (cooldown == 0) ? true : false);
int32 activeRunes = std::count(std::begin(m_runes->Cooldown), &m_runes->Cooldown[std::min(GetMaxPower(POWER_RUNES), MAX_RUNES)], 0u);
if (activeRunes != GetPower(POWER_RUNES))
SetPower(POWER_RUNES, activeRunes);
}
void Runes::SetRuneState(uint8 index, bool set /*= true*/)
{
auto itr = std::find(CooldownOrder.begin(), CooldownOrder.end(), index);
if (set)
{
RuneState |= (1 << index); // usable
if (itr != CooldownOrder.end())
CooldownOrder.erase(itr);
}
else
{
RuneState &= ~(1 << index); // on cooldown
if (itr == CooldownOrder.end())
CooldownOrder.push_back(index);
}
}
void Player::ResyncRunes() const
{
uint32 maxRunes = uint32(GetMaxPower(POWER_RUNES));
WorldPackets::Spells::ResyncRunes data(maxRunes);
data.Runes.Start = uint8((1 << maxRunes) - 1);
data.Runes.Count = GetRunesState();
float baseCd = float(GetRuneBaseCooldown());
for (uint32 i = 0; i < maxRunes; ++i)
data.Runes.Cooldowns.push_back(uint8((baseCd - float(GetRuneCooldown(i))) / baseCd * 255));
SendDirectMessage(data.Write());
}
void Player::InitRunes()
{
if (GetClass() != CLASS_DEATH_KNIGHT)
return;
uint32 runeIndex = GetPowerIndex(POWER_RUNES);
if (runeIndex == MAX_POWERS)
return;
m_runes = new Runes();
m_runes->RuneState = 0;
for (uint8 i = 0; i < MAX_RUNES; ++i)
SetRuneCooldown(i, 0); // reset cooldowns
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenFlatModifier, runeIndex), 0.0f);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenInterruptedFlatModifier, runeIndex), 0.0f);
}
void Player::AutoStoreLoot(uint8 bag, uint8 slot, uint32 loot_id, LootStore const& store, ItemContext context, bool broadcast, bool createdByPlayer)
{
Loot loot;
loot.FillLoot (loot_id, store, this, true, false, LOOT_MODE_DEFAULT, context);
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->randomBonusListId, GuidSet(), lootItem->context, lootItem->BonusListIDs);
SendNewItem(pItem, lootItem->count, false, createdByPlayer, broadcast);
}
}
void Player::StoreLootItem(uint8 lootSlot, Loot* loot, AELootResult* aeResult/* = nullptr*/)
{
NotNormalLootItem* qitem = nullptr;
NotNormalLootItem* ffaitem = nullptr;
NotNormalLootItem* conditem = nullptr;
LootItem* item = loot->LootItemInSlot(lootSlot, this, &qitem, &ffaitem, &conditem);
if (!item || item->is_looted)
{
SendEquipError(EQUIP_ERR_LOOT_GONE, nullptr, nullptr);
return;
}
if (!item->AllowedForPlayer(this))
{
SendLootReleaseAll();
return;
}
// questitems use the blocked field for other purposes
if (!qitem && item->is_blocked)
{
SendLootReleaseAll();
return;
}
// dont allow protected item to be looted by someone else
if (!item->rollWinnerGUID.IsEmpty() && item->rollWinnerGUID != GetGUID())
{
SendLootReleaseAll();
return;
}
ItemPosCountVec dest;
InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->itemid, item->count);
if (msg == EQUIP_ERR_OK)
{
Item* newitem = StoreNewItem(dest, item->itemid, true, item->randomBonusListId, item->GetAllowedLooters(), item->context, item->BonusListIDs);
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(loot->GetGUID(), lootSlot);
else
loot->NotifyQuestItemRemoved(qitem->index);
}
else
{
if (ffaitem)
{
//freeforall case, notify only one player of the removal
ffaitem->is_looted = true;
SendNotifyLootItemRemoved(loot->GetGUID(), 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;
if (sObjectMgr->GetItemTemplate(item->itemid))
if (newitem->GetQuality() > ITEM_QUALITY_EPIC || (newitem->GetQuality() == ITEM_QUALITY_EPIC && newitem->GetItemLevel(this) >= MinNewsItemLevel))
if (Guild* guild = GetGuild())
guild->AddGuildNews(GUILD_NEWS_ITEM_LOOTED, GetGUID(), 0, item->itemid);
// if aeLooting then we must delay sending out item so that it appears properly stacked in chat
if (!aeResult)
{
SendNewItem(newitem, uint32(item->count), false, false, true);
UpdateCriteria(CriteriaType::LootItem, item->itemid, item->count);
UpdateCriteria(CriteriaType::GetLootByType, item->itemid, item->count, loot->loot_type);
UpdateCriteria(CriteriaType::LootAnyItem, item->itemid, item->count);
}
else
aeResult->Add(newitem, item->count, loot->loot_type);
// LootItem is being removed (looted) from the container, delete it from the DB.
if (!loot->containerID.IsEmpty())
sLootItemStorage->RemoveStoredLootItemForContainer(loot->containerID.GetCounter(), item->itemid, item->count);
}
else
SendEquipError(msg, nullptr, nullptr, item->itemid);
}
void Player::LearnSpellHighestRank(uint32 spellid)
{
LearnSpell(spellid, false);
if (uint32 next = sSpellMgr->GetNextSpellInChain(spellid))
LearnSpellHighestRank(next);
}
void Player::_LoadSkills(PreparedQueryResult result)
{
// 0 1 2
// SetPQuery(PLAYER_LOGIN_QUERY_LOADSKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = '%u'", GUID_LOPART(m_guid));
uint32 count = 0;
std::unordered_map<uint32, uint32> loadedSkillValues;
if (result)
{
do
{
if (mSkillStatus.size() >= PLAYER_MAX_SKILLS) // client limit
{
TC_LOG_ERROR("entities.player", "Player::_LoadSkills: Player '%s' (%s) has more than %u skills.",
GetName().c_str(), GetGUID().ToString().c_str(), PLAYER_MAX_SKILLS);
break;
}
Field* fields = result->Fetch();
uint16 skill = fields[0].GetUInt16();
uint16 value = fields[1].GetUInt16();
uint16 max = fields[2].GetUInt16();
SkillRaceClassInfoEntry const* rcEntry = sDB2Manager.GetSkillRaceClassInfo(skill, GetRace(), GetClass());
if (!rcEntry)
{
TC_LOG_ERROR("entities.player", "Player::_LoadSkills: Player '%s' (%s, Race: %u, Class: %u) has forbidden skill %u for his race/class combination",
GetName().c_str(), GetGUID().ToString().c_str(), uint32(GetRace()), uint32(GetClass()), skill);
mSkillStatus.insert(SkillStatusMap::value_type(skill, SkillStatusData(0, SKILL_DELETED)));
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();
break;
default:
break;
}
auto skillItr = mSkillStatus.find(skill);
if (skillItr == mSkillStatus.end())
skillItr = mSkillStatus.insert(SkillStatusMap::value_type(skill, SkillStatusData(mSkillStatus.size(), SKILL_UNCHANGED))).first;
uint16 step = 0;
if (SkillLineEntry const* skillLine = sSkillLineStore.LookupEntry(rcEntry->SkillID))
{
if (skillLine->CategoryID == SKILL_CATEGORY_SECONDARY)
step = max / 75;
if (skillLine->CategoryID == SKILL_CATEGORY_PROFESSION)
{
step = max / 75;
if (!skillLine->ParentSkillLineID)
{
int32 professionSlot = FindProfessionSlotFor(skill);
if (professionSlot != -1)
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ProfessionSkillLine, professionSlot), skill);
}
}
}
SetSkillLineId(skillItr->second.pos, skill);
SetSkillStep(skillItr->second.pos, step);
SetSkillRank(skillItr->second.pos, value);
SetSkillStartingRank(skillItr->second.pos, 1);
SetSkillMaxRank(skillItr->second.pos, max);
SetSkillTempBonus(skillItr->second.pos, 0);
SetSkillPermBonus(skillItr->second.pos, 0);
loadedSkillValues[skill] = value;
}
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);
if (std::vector<SkillLineEntry const*> const* childSkillLines = sDB2Manager.GetSkillLinesForParentSkill(skill.first))
{
for (auto childItr = childSkillLines->begin(); childItr != childSkillLines->end() && mSkillStatus.size() < PLAYER_MAX_SKILLS; ++childItr)
{
if (mSkillStatus.find((*childItr)->ID) == mSkillStatus.end())
{
SetSkillLineId(count, (*childItr)->ID);
SetSkillStartingRank(count, 1);
mSkillStatus.insert(SkillStatusMap::value_type((*childItr)->ID, SkillStatusData(count, SKILL_UNCHANGED)));
}
}
}
}
if (HasSkill(SKILL_FIST_WEAPONS))
SetSkill(SKILL_FIST_WEAPONS, 0, GetSkillValue(SKILL_UNARMED), GetMaxSkillValueForLevel());
}
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 (UF::SocketedGem const& gemData : pItem->m_itemData->Gems)
{
ItemTemplate const* pGem = sObjectMgr->GetItemTemplate(gemData.ItemID);
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->GetItemLimitCategory()
? pItem->GetGemCountWithLimitCategory(pGem->GetItemLimitCategory()) : 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->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE))
{
// there is an equip limit on this item
if (HasItemOrGemWithIdEquipped(itemProto->GetId(), 1, except_slot))
return EQUIP_ERR_ITEM_UNIQUE_EQUIPPABLE;
}
// check unique-equipped limit
if (itemProto->GetItemLimitCategory())
{
ItemLimitCategoryEntry const* limitEntry = sItemLimitCategoryStore.LookupEntry(itemProto->GetItemLimitCategory());
if (!limitEntry)
return EQUIP_ERR_NOT_EQUIPPABLE;
// NOTE: limitEntry->mode not checked because if item have have-limit then it applied and to equip case
uint8 limitQuantity = GetItemLimitCategoryQuantity(limitEntry);
if (limit_count > limitQuantity)
return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED_IS;
// there is an equip limit on this item
if (HasItemWithLimitCategoryEquipped(itemProto->GetItemLimitCategory(), limitQuantity - limit_count + 1, except_slot))
return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED_IS;
else if (HasGemWithLimitCategoryEquipped(itemProto->GetItemLimitCategory(), limitQuantity - limit_count + 1, except_slot))
return EQUIP_ERR_ITEM_MAX_COUNT_EQUIPPED_SOCKETED;
}
return EQUIP_ERR_OK;
}
void Player::SetFallInformation(uint32 time, float z)
{
m_lastFallTime = time;
m_lastFallZ = z;
}
void Player::HandleFall(MovementInfo const& movementInfo)
{
// calculate total z distance of the fall
float z_diff = m_lastFallZ - movementInfo.pos.GetPositionZ();
//TC_LOG_DEBUG("misc", "zDiff = %f", z_diff);
//Players with low fall distance, Feather Fall or physical immunity (charges used) are ignored
// 14.57 can be calculated by resolving damageperc formula below to 0
if (z_diff >= 14.57f && !isDead() && !IsGameMaster() &&
!HasAuraType(SPELL_AURA_HOVER) && !HasAuraType(SPELL_AURA_FEATHER_FALL) &&
!HasAuraType(SPELL_AURA_FLY) && !IsImmunedToDamage(SPELL_SCHOOL_MASK_NORMAL))
{
//Safe fall, fall height reduction
int32 safe_fall = GetTotalAuraModifier(SPELL_AURA_SAFE_FALL);
float damageperc = 0.018f*(z_diff-safe_fall)-0.2426f;
if (damageperc > 0)
{
uint32 damage = (uint32)(damageperc * GetMaxHealth()*sWorld->getRate(RATE_DAMAGE_FALL));
if (GetCommandStatus(CHEAT_GOD))
damage = 0;
float height = movementInfo.pos.m_positionZ;
UpdateGroundPositionZ(movementInfo.pos.m_positionX, movementInfo.pos.m_positionY, height);
damage *= GetTotalAuraMultiplier(SPELL_AURA_MODIFY_FALL_DAMAGE_PCT);
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;
uint32 original_health = GetHealth();
uint32 final_damage = EnvironmentalDamage(DAMAGE_FALL, damage);
// recheck alive, might have died of EnvironmentalDamage, avoid cases when player die in fact like Spirit of Redemption case
if (IsAlive() && final_damage < original_health)
UpdateCriteria(CriteriaType::MaxDistFallenWithoutDying, uint32(z_diff*100));
}
//Z given by moveinfo, LastZ, FallTime, WaterZ, MapZ, Damage, Safefall reduction
TC_LOG_DEBUG("entities.player", "FALLDAMAGE z=%f sz=%f pZ=%f FallTime=%d mZ=%f damage=%d SF=%d", movementInfo.pos.GetPositionZ(), height, GetPositionZ(), movementInfo.jump.fallTime, height, damage, safe_fall);
}
}
}
void Player::ResetAchievements()
{
m_achievementMgr->Reset();
}
void Player::SendRespondInspectAchievements(Player* player) const
{
m_achievementMgr->SendAchievementInfo(player);
}
uint32 Player::GetAchievementPoints() const
{
return m_achievementMgr->GetAchievementPoints();
}
std::vector<uint32> Player::GetCompletedAchievementIds() const
{
return m_achievementMgr->GetCompletedAchievementIds();
}
bool Player::HasAchieved(uint32 achievementId) const
{
return m_achievementMgr->HasAchieved(achievementId);
}
void Player::StartCriteriaTimer(CriteriaStartEvent startEvent, uint32 entry, uint32 timeLost/* = 0*/)
{
m_achievementMgr->StartCriteriaTimer(startEvent, entry, timeLost);
}
void Player::RemoveCriteriaTimer(CriteriaStartEvent startEvent, uint32 entry)
{
m_achievementMgr->RemoveCriteriaTimer(startEvent, entry);
}
void Player::ResetCriteria(CriteriaFailEvent condition, int32 failAsset, bool evenIfCriteriaComplete /* = false*/)
{
m_achievementMgr->ResetCriteria(condition, failAsset, evenIfCriteriaComplete);
m_questObjectiveCriteriaMgr->ResetCriteria(condition, failAsset, evenIfCriteriaComplete);
}
void Player::UpdateCriteria(CriteriaType type, uint64 miscValue1 /*= 0*/, uint64 miscValue2 /*= 0*/, uint64 miscValue3 /*= 0*/, WorldObject* ref /*= nullptr*/)
{
m_achievementMgr->UpdateCriteria(type, miscValue1, miscValue2, miscValue3, ref, this);
m_questObjectiveCriteriaMgr->UpdateCriteria(type, miscValue1, miscValue2, miscValue3, ref, this);
// Update only individual achievement criteria here, otherwise we may get multiple updates
// from a single boss kill
if (CriteriaMgr::IsGroupCriteriaType(type))
return;
if (Scenario* scenario = GetScenario())
scenario->UpdateCriteria(type, miscValue1, miscValue2, miscValue3, ref, this);
if (Guild* guild = sGuildMgr->GetGuildById(GetGuildId()))
guild->UpdateCriteria(type, miscValue1, miscValue2, miscValue3, ref, this);
}
void Player::CompletedAchievement(AchievementEntry const* entry)
{
m_achievementMgr->CompletedAchievement(entry, this);
}
bool Player::ModifierTreeSatisfied(uint32 modifierTreeId) const
{
return m_achievementMgr->ModifierTreeSatisfied(modifierTreeId);
}
TalentLearnResult Player::LearnTalent(uint32 talentId, int32* spellOnCooldown)
{
if (IsInCombat())
return TALENT_FAILED_AFFECTING_COMBAT;
if (isDead())
return TALENT_FAILED_CANT_DO_THAT_RIGHT_NOW;
if (!GetPrimarySpecialization())
return TALENT_FAILED_NO_PRIMARY_TREE_SELECTED;
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
if (!talentInfo)
return TALENT_FAILED_UNKNOWN;
if (talentInfo->SpecID && talentInfo->SpecID != GetPrimarySpecialization())
return TALENT_FAILED_UNKNOWN;
// prevent learn talent for different class (cheating)
if (talentInfo->ClassID != GetClass())
return TALENT_FAILED_UNKNOWN;
// check if we have enough talent points
if (talentInfo->TierID >= m_activePlayerData->MaxTalentTiers)
return TALENT_FAILED_UNKNOWN;
// TODO: prevent changing talents that are on cooldown
// Check if there is a different talent for us to learn in selected slot
// Example situation:
// Warrior talent row 2 slot 0
// Talent.dbc has an entry for each specialization
// but only 2 out of 3 have SpecID != 0
// We need to make sure that if player is in one of these defined specs he will not learn the other choice
TalentEntry const* bestSlotMatch = nullptr;
for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(GetClass(), talentInfo->TierID, talentInfo->ColumnIndex))
{
if (!talent->SpecID)
bestSlotMatch = talent;
else if (talent->SpecID == GetPrimarySpecialization())
{
bestSlotMatch = talent;
break;
}
}
if (talentInfo != bestSlotMatch)
return TALENT_FAILED_UNKNOWN;
// Check if player doesn't have any talent in current tier
for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c)
{
for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(GetClass(), talentInfo->TierID, c))
{
if (!HasTalent(talent->ID, GetActiveTalentGroup()))
continue;
if (!HasPlayerFlag(PLAYER_FLAGS_RESTING) && !HasUnitFlag2(UNIT_FLAG2_ALLOW_CHANGING_TALENTS))
return TALENT_FAILED_REST_AREA;
if (GetSpellHistory()->HasCooldown(talent->SpellID))
{
*spellOnCooldown = talent->SpellID;
return TALENT_FAILED_CANT_REMOVE_TALENT;
}
RemoveTalent(talent);
}
}
// spell not set in talent.dbc
uint32 spellid = talentInfo->SpellID;
if (!spellid)
{
TC_LOG_ERROR("entities.player", "Player::LearnTalent: Talent.dbc has no spellInfo for talent: %u (spell id = 0)", talentId);
return TALENT_FAILED_UNKNOWN;
}
// already known
if (HasTalent(talentId, GetActiveTalentGroup()) || HasSpell(spellid))
return TALENT_FAILED_UNKNOWN;
if (!AddTalent(talentInfo, GetActiveTalentGroup(), true))
return TALENT_FAILED_UNKNOWN;
LearnSpell(spellid, false);
TC_LOG_DEBUG("misc", "Player::LearnTalent: TalentID: %u Spell: %u Group: %u\n", talentId, spellid, GetActiveTalentGroup());
return TALENT_LEARN_OK;
}
void Player::ResetTalentSpecialization()
{
// Reset only talents that have different spells for each spec
uint32 class_ = GetClass();
for (uint32 t = 0; t < MAX_TALENT_TIERS; ++t)
for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c)
if (sDB2Manager.GetTalentsByPosition(class_, t, c).size() > 1)
for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(class_, t, c))
RemoveTalent(talent);
ResetPvpTalents();
RemoveSpecializationSpells();
ChrSpecializationEntry const* defaultSpec = ASSERT_NOTNULL(sDB2Manager.GetDefaultChrSpecializationForClass(GetClass()));
SetPrimarySpecialization(defaultSpec->ID);
SetActiveTalentGroup(defaultSpec->OrderIndex);
LearnSpecializationSpells();
SendTalentsInfoData();
UpdateItemSetAuras(false);
}
TalentLearnResult Player::LearnPvpTalent(uint32 talentID, uint8 slot, int32* spellOnCooldown)
{
if (slot >= MAX_PVP_TALENT_SLOTS)
return TALENT_FAILED_UNKNOWN;
if (IsInCombat())
return TALENT_FAILED_AFFECTING_COMBAT;
if (isDead())
return TALENT_FAILED_CANT_DO_THAT_RIGHT_NOW;
PvpTalentEntry const* talentInfo = sPvpTalentStore.LookupEntry(talentID);
if (!talentInfo)
return TALENT_FAILED_UNKNOWN;
if (talentInfo->SpecID != int32(GetPrimarySpecialization()))
return TALENT_FAILED_UNKNOWN;
if (talentInfo->LevelRequired > GetLevel())
return TALENT_FAILED_UNKNOWN;
if (sDB2Manager.GetRequiredLevelForPvpTalentSlot(slot, Classes(GetClass())) > GetLevel())
return TALENT_FAILED_UNKNOWN;
if (PvpTalentCategoryEntry const* talentCategory = sPvpTalentCategoryStore.LookupEntry(talentInfo->PvpTalentCategoryID))
if (!(talentCategory->TalentSlotMask & (1 << slot)))
return TALENT_FAILED_UNKNOWN;
// Check if player doesn't have this talent in other slot
if (HasPvpTalent(talentID, GetActiveTalentGroup()))
return TALENT_FAILED_UNKNOWN;
if (PvpTalentEntry const* talent = sPvpTalentStore.LookupEntry(GetPvpTalentMap(GetActiveTalentGroup())[slot]))
{
if (!HasPlayerFlag(PLAYER_FLAGS_RESTING) && !HasUnitFlag2(UNIT_FLAG2_ALLOW_CHANGING_TALENTS))
return TALENT_FAILED_REST_AREA;
if (GetSpellHistory()->HasCooldown(talent->SpellID))
{
*spellOnCooldown = talent->SpellID;
return TALENT_FAILED_CANT_REMOVE_TALENT;
}
RemovePvpTalent(talent, GetActiveTalentGroup());
}
if (!AddPvpTalent(talentInfo, GetActiveTalentGroup(), slot))
return TALENT_FAILED_UNKNOWN;
return TALENT_LEARN_OK;
}
bool Player::AddPvpTalent(PvpTalentEntry const* talent, uint8 activeTalentGroup, uint8 slot)
{
ASSERT(talent);
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE);
if (!spellInfo)
{
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: %u) does not exist.", talent->SpellID);
return false;
}
if (!SpellMgr::IsSpellValid(spellInfo, this, false))
{
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: %u) is invalid", talent->SpellID);
return false;
}
if (HasPvpRulesEnabled())
LearnSpell(talent->SpellID, false);
// Move this to toggle ?
if (talent->OverridesSpellID)
AddOverrideSpell(talent->OverridesSpellID, talent->SpellID);
GetPvpTalentMap(activeTalentGroup)[slot] = talent->ID;
return true;
}
void Player::RemovePvpTalent(PvpTalentEntry const* talent, uint8 activeTalentGroup)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE);
if (!spellInfo)
return;
RemoveSpell(talent->SpellID, true);
// Move this to toggle ?
if (talent->OverridesSpellID)
RemoveOverrideSpell(talent->OverridesSpellID, talent->SpellID);
// if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted
auto plrPvpTalent = std::find(GetPvpTalentMap(activeTalentGroup).begin(), GetPvpTalentMap(activeTalentGroup).end(), talent->ID);
if (plrPvpTalent != GetPvpTalentMap(activeTalentGroup).end())
*plrPvpTalent = 0;
}
void Player::TogglePvpTalents(bool enable)
{
PlayerPvpTalentMap const& pvpTalents = GetPvpTalentMap(GetActiveTalentGroup());
for (uint32 pvpTalentId : pvpTalents)
{
if (PvpTalentEntry const* pvpTalentInfo = sPvpTalentStore.LookupEntry(pvpTalentId))
{
if (enable)
LearnSpell(pvpTalentInfo->SpellID, false);
else
RemoveSpell(pvpTalentInfo->SpellID, true);
}
}
}
bool Player::HasPvpTalent(uint32 talentID, uint8 activeTalentGroup) const
{
return std::find(GetPvpTalentMap(activeTalentGroup).begin(), GetPvpTalentMap(activeTalentGroup).end(), talentID) != GetPvpTalentMap(activeTalentGroup).end();
}
void Player::EnablePvpRules(bool dueToCombat /*= false*/)
{
if (!HasPvpRulesEnabled())
{
if (!HasSpell(195710)) // Honorable Medallion
CastSpell(this, 208682, true); // Learn Gladiator's Medallion
CastSpell(this, SPELL_PVP_RULES_ENABLED, true);
}
if (!dueToCombat)
{
if (Aura* aura = GetAura(SPELL_PVP_RULES_ENABLED))
{
if (!aura->IsPermanent())
{
aura->SetMaxDuration(-1);
aura->SetDuration(-1);
}
}
}
UpdateItemLevelAreaBasedScaling();
}
void Player::DisablePvpRules()
{
// Don't disable pvp rules when in pvp zone.
if (IsInAreaThatActivatesPvpTalents())
return;
if (!GetCombatManager().HasPvPCombat())
{
RemoveAurasDueToSpell(SPELL_PVP_RULES_ENABLED);
UpdateItemLevelAreaBasedScaling();
}
else if (Aura* aura = GetAura(SPELL_PVP_RULES_ENABLED))
aura->SetDuration(aura->GetSpellInfo()->GetMaxDuration());
}
bool Player::HasPvpRulesEnabled() const
{
return HasAura(SPELL_PVP_RULES_ENABLED);
}
bool Player::IsInAreaThatActivatesPvpTalents() const
{
return IsAreaThatActivatesPvpTalents(GetAreaId());
}
bool Player::IsAreaThatActivatesPvpTalents(uint32 areaID) const
{
if (InBattleground())
return true;
if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaID))
{
do
{
if (area->IsSanctuary())
return false;
if (area->Flags[0] & AREA_FLAG_ARENA)
return true;
if (sBattlefieldMgr->GetBattlefieldToZoneId(area->ID))
return true;
area = sAreaTableStore.LookupEntry(area->ParentAreaID);
} while (area);
}
return false;
}
void Player::UpdateFallInformationIfNeed(MovementInfo const& minfo, uint16 opcode)
{
if (m_lastFallTime >= minfo.jump.fallTime || m_lastFallZ <= minfo.pos.GetPositionZ() || opcode == CMSG_MOVE_FALL_LAND)
SetFallInformation(minfo.jump.fallTime, minfo.pos.GetPositionZ());
}
void Player::UnsummonPetTemporaryIfAny()
{
Pet* pet = GetPet();
if (!pet)
return;
if (!m_temporaryUnsummonedPetNumber && pet->isControlled() && !pet->isTemporarySummoned())
{
m_temporaryUnsummonedPetNumber = pet->GetCharmInfo()->GetPetNumber();
m_oldpetspell = pet->m_unitData->CreatedBySpell;
}
RemovePet(pet, PET_SAVE_AS_CURRENT);
}
void Player::ResummonPetTemporaryUnSummonedIfAny()
{
if (!m_temporaryUnsummonedPetNumber)
return;
// not resummon in not appropriate state
if (IsPetNeedBeTemporaryUnsummoned())
return;
if (!GetPetGUID().IsEmpty())
return;
Pet* NewPet = new Pet(this);
if (!NewPet->LoadPetFromDB(this, 0, m_temporaryUnsummonedPetNumber, true))
delete NewPet;
m_temporaryUnsummonedPetNumber = 0;
}
bool Player::IsPetNeedBeTemporaryUnsummoned() const
{
return !IsInWorld() || !IsAlive() || IsMounted() /*+in flight*/;
}
bool Player::CanSeeSpellClickOn(Creature const* c) const
{
if (!c->HasNpcFlag(UNIT_NPC_FLAG_SPELLCLICK))
return false;
auto clickBounds = sObjectMgr->GetSpellClickInfoMapBounds(c->GetEntry());
if (clickBounds.begin() == clickBounds.end())
return false;
for (auto const& clickPair : clickBounds)
{
if (!clickPair.second.IsFitToRequirements(this, c))
return false;
if (sConditionMgr->IsObjectMeetingSpellClickConditions(c->GetEntry(), clickPair.second.spellId, const_cast<Player*>(this), const_cast<Creature*>(c)))
return true;
}
return false;
}
void Player::SendTalentsInfoData()
{
WorldPackets::Talent::UpdateTalentData packet;
packet.Info.PrimarySpecialization = GetPrimarySpecialization();
for (uint8 i = 0; i < MAX_SPECIALIZATIONS; ++i)
{
ChrSpecializationEntry const* spec = sDB2Manager.GetChrSpecializationByIndex(GetClass(), i);
if (!spec)
continue;
PlayerTalentMap* talents = GetTalentMap(i);
PlayerPvpTalentMap const& pvpTalents = GetPvpTalentMap(i);
WorldPackets::Talent::TalentGroupInfo groupInfoPkt;
groupInfoPkt.SpecID = spec->ID;
groupInfoPkt.TalentIDs.reserve(talents->size());
for (PlayerTalentMap::const_iterator itr = talents->begin(); itr != talents->end(); ++itr)
{
if (itr->second == PLAYERSPELL_REMOVED)
continue;
TalentEntry const* talentInfo = sTalentStore.LookupEntry(itr->first);
if (!talentInfo)
{
TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '%s' (%s) has unknown talent id: %u",
GetName().c_str(), GetGUID().ToString().c_str(), itr->first);
continue;
}
SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(talentInfo->SpellID, DIFFICULTY_NONE);
if (!spellEntry)
{
TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '%s' (%s) has unknown talent spell: %u",
GetName().c_str(), GetGUID().ToString().c_str(), talentInfo->SpellID);
continue;
}
groupInfoPkt.TalentIDs.push_back(uint16(itr->first));
}
for (std::size_t slot = 0; slot < MAX_PVP_TALENT_SLOTS; ++slot)
{
if (!pvpTalents[slot])
continue;
PvpTalentEntry const* talentInfo = sPvpTalentStore.LookupEntry(pvpTalents[slot]);
if (!talentInfo)
{
TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '%s' (%s) has unknown pvp talent id: %u",
GetName().c_str(), GetGUID().ToString().c_str(), pvpTalents[slot]);
continue;
}
SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(talentInfo->SpellID, DIFFICULTY_NONE);
if (!spellEntry)
{
TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '%s' (%s) has unknown pvp talent spell: %u",
GetName().c_str(), GetGUID().ToString().c_str(), talentInfo->SpellID);
continue;
}
groupInfoPkt.PvPTalents.emplace_back();
WorldPackets::Talent::PvPTalent& pvpTalent = groupInfoPkt.PvPTalents.back();
pvpTalent.PvPTalentID = pvpTalents[slot];
pvpTalent.Slot = slot;
}
if (i == GetActiveTalentGroup())
packet.Info.ActiveGroup = packet.Info.TalentGroups.size();
if (!groupInfoPkt.TalentIDs.empty() || !groupInfoPkt.PvPTalents.empty() || i == GetActiveTalentGroup())
packet.Info.TalentGroups.push_back(groupInfoPkt);
}
SendDirectMessage(packet.Write());
}
void Player::SendEquipmentSetList()
{
WorldPackets::EquipmentSet::LoadEquipmentSet data;
for (EquipmentSetContainer::value_type const& eqSet : _equipmentSets)
{
if (eqSet.second.State == EQUIPMENT_SET_DELETED)
continue;
data.SetData.push_back(&eqSet.second.Data);
}
SendDirectMessage(data.Write());
}
void Player::SetEquipmentSet(EquipmentSetInfo::EquipmentSetData const& newEqSet)
{
if (newEqSet.Guid != 0)
{
// something wrong...
EquipmentSetContainer::const_iterator itr = _equipmentSets.find(newEqSet.Guid);
if (itr == _equipmentSets.end() || itr->second.Data.Guid != newEqSet.Guid)
{
TC_LOG_ERROR("entities.player", "Player::SetEquipmentSet: Player '%s' (%s) tried to save nonexistent equipment set " UI64FMTD " (index: %u)",
GetName().c_str(), GetGUID().ToString().c_str(), newEqSet.Guid, newEqSet.SetID);
return;
}
}
uint64 setGuid = (newEqSet.Guid != 0) ? newEqSet.Guid : sObjectMgr->GenerateEquipmentSetGuid();
EquipmentSetInfo& eqSlot = _equipmentSets[setGuid];
eqSlot.Data = newEqSet;
if (eqSlot.Data.Guid == 0)
{
eqSlot.Data.Guid = setGuid;
WorldPackets::EquipmentSet::EquipmentSetID data;
data.GUID = eqSlot.Data.Guid;
data.Type = eqSlot.Data.Type;
data.SetID = eqSlot.Data.SetID;
SendDirectMessage(data.Write());
}
eqSlot.State = eqSlot.State == EQUIPMENT_SET_NEW ? EQUIPMENT_SET_NEW : EQUIPMENT_SET_CHANGED;
}
void Player::_SaveEquipmentSets(CharacterDatabaseTransaction& trans)
{
for (EquipmentSetContainer::iterator itr = _equipmentSets.begin(); itr != _equipmentSets.end();)
{
EquipmentSetInfo& eqSet = itr->second;
CharacterDatabasePreparedStatement* stmt;
uint8 j = 0;
switch (eqSet.State)
{
case EQUIPMENT_SET_UNCHANGED:
++itr;
break; // nothing do
case EQUIPMENT_SET_CHANGED:
{
if (eqSet.Data.Type == EquipmentSetInfo::EQUIPMENT)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_EQUIP_SET);
stmt->setString(j++, eqSet.Data.SetName);
stmt->setString(j++, eqSet.Data.SetIcon);
stmt->setUInt32(j++, eqSet.Data.IgnoreMask);
stmt->setInt32(j++, eqSet.Data.AssignedSpecIndex);
for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i)
stmt->setUInt64(j++, eqSet.Data.Pieces[i].GetCounter());
stmt->setUInt64(j++, GetGUID().GetCounter());
stmt->setUInt64(j++, eqSet.Data.Guid);
stmt->setUInt32(j, eqSet.Data.SetID);
}
else
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_TRANSMOG_OUTFIT);
stmt->setString(j++, eqSet.Data.SetName);
stmt->setString(j++, eqSet.Data.SetIcon);
stmt->setUInt32(j++, eqSet.Data.IgnoreMask);
for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i)
stmt->setInt32(j++, eqSet.Data.Appearances[i]);
for (std::size_t i = 0; i < eqSet.Data.Enchants.size(); ++i)
stmt->setInt32(j++, eqSet.Data.Enchants[i]);
stmt->setUInt64(j++, GetGUID().GetCounter());
stmt->setUInt64(j++, eqSet.Data.Guid);
stmt->setUInt32(j, eqSet.Data.SetID);
}
trans->Append(stmt);
eqSet.State = EQUIPMENT_SET_UNCHANGED;
++itr;
break;
}
case EQUIPMENT_SET_NEW:
{
if (eqSet.Data.Type == EquipmentSetInfo::EQUIPMENT)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_EQUIP_SET);
stmt->setUInt64(j++, GetGUID().GetCounter());
stmt->setUInt64(j++, eqSet.Data.Guid);
stmt->setUInt32(j++, eqSet.Data.SetID);
stmt->setString(j++, eqSet.Data.SetName);
stmt->setString(j++, eqSet.Data.SetIcon);
stmt->setUInt32(j++, eqSet.Data.IgnoreMask);
stmt->setInt32(j++, eqSet.Data.AssignedSpecIndex);
for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i)
stmt->setUInt64(j++, eqSet.Data.Pieces[i].GetCounter());
}
else
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_TRANSMOG_OUTFIT);
stmt->setUInt64(j++, GetGUID().GetCounter());
stmt->setUInt64(j++, eqSet.Data.Guid);
stmt->setUInt32(j++, eqSet.Data.SetID);
stmt->setString(j++, eqSet.Data.SetName);
stmt->setString(j++, eqSet.Data.SetIcon);
stmt->setUInt32(j++, eqSet.Data.IgnoreMask);
for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i)
stmt->setInt32(j++, eqSet.Data.Appearances[i]);
for (std::size_t i = 0; i < eqSet.Data.Enchants.size(); ++i)
stmt->setInt32(j++, eqSet.Data.Enchants[i]);
}
trans->Append(stmt);
eqSet.State = EQUIPMENT_SET_UNCHANGED;
++itr;
break;
}
case EQUIPMENT_SET_DELETED:
if (eqSet.Data.Type == EquipmentSetInfo::EQUIPMENT)
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_EQUIP_SET);
else
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_TRANSMOG_OUTFIT);
stmt->setUInt64(0, eqSet.Data.Guid);
trans->Append(stmt);
itr = _equipmentSets.erase(itr);
break;
}
}
}
void Player::_SaveBGData(CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_BGDATA);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
/* guid, bgInstanceID, bgTeam, x, y, z, o, map, taxi[0], taxi[1], mountSpell */
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PLAYER_BGDATA);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, m_bgData.bgInstanceID);
stmt->setUInt16(2, m_bgData.bgTeam);
stmt->setFloat (3, m_bgData.joinPos.GetPositionX());
stmt->setFloat (4, m_bgData.joinPos.GetPositionY());
stmt->setFloat (5, m_bgData.joinPos.GetPositionZ());
stmt->setFloat (6, m_bgData.joinPos.GetOrientation());
stmt->setUInt16(7, m_bgData.joinPos.GetMapId());
stmt->setUInt16(8, m_bgData.taxiPath[0]);
stmt->setUInt16(9, m_bgData.taxiPath[1]);
stmt->setUInt16(10, m_bgData.mountSpell);
trans->Append(stmt);
}
void Player::DeleteEquipmentSet(uint64 id)
{
for (EquipmentSetContainer::iterator itr = _equipmentSets.begin(); itr != _equipmentSets.end();)
{
if (itr->second.Data.Guid == id)
{
if (itr->second.State == EQUIPMENT_SET_NEW)
itr = _equipmentSets.erase(itr);
else
itr->second.State = EQUIPMENT_SET_DELETED;
break;
}
++itr;
}
}
void Player::RemoveAtLoginFlag(AtLoginFlags flags, bool persist /*= false*/)
{
m_atLoginFlags &= ~flags;
if (persist)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_REM_AT_LOGIN_FLAG);
stmt->setUInt16(0, uint16(flags));
stmt->setUInt64(1, GetGUID().GetCounter());
CharacterDatabase.Execute(stmt);
}
}
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 RefManager
// 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::_LoadGlyphs(PreparedQueryResult result)
{
// SELECT talentGroup, glyphId from character_glyphs WHERE guid = ?
if (!result)
return;
do
{
Field* fields = result->Fetch();
uint8 spec = fields[0].GetUInt8();
if (spec >= MAX_SPECIALIZATIONS || !sDB2Manager.GetChrSpecializationByIndex(GetClass(), spec))
continue;
uint16 glyphId = fields[1].GetUInt16();
if (!sGlyphPropertiesStore.LookupEntry(glyphId))
continue;
GetGlyphs(spec).push_back(glyphId);
} while (result->NextRow());
}
void Player::_SaveGlyphs(CharacterDatabaseTransaction& trans) const
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_GLYPHS);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
for (uint8 spec = 0; spec < MAX_SPECIALIZATIONS; ++spec)
{
for (uint32 glyphId : GetGlyphs(spec))
{
uint8 index = 0;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_GLYPHS);
stmt->setUInt64(index++, GetGUID().GetCounter());
stmt->setUInt8(index++, spec);
stmt->setUInt16(index++, uint16(glyphId));
trans->Append(stmt);
}
}
}
void Player::_LoadTalents(PreparedQueryResult result)
{
// "SELECT talentId, talentGroup FROM character_talent WHERE guid = ?"
if (result)
{
do
if (TalentEntry const* talent = sTalentStore.LookupEntry((*result)[0].GetUInt32()))
AddTalent(talent, (*result)[1].GetUInt8(), false);
while (result->NextRow());
}
}
void Player::_LoadPvpTalents(PreparedQueryResult result)
{
// "SELECT talentID0, talentID1, talentID2, talentID3, talentGroup FROM character_pvp_talent WHERE guid = ?"
if (result)
{
do
for (uint8 slot = 0; slot < MAX_PVP_TALENT_SLOTS; ++slot)
if (PvpTalentEntry const* talent = sPvpTalentStore.LookupEntry((*result)[slot].GetUInt32()))
AddPvpTalent(talent, (*result)[4].GetUInt8(), slot);
while (result->NextRow());
}
}
void Player::_SaveTalents(CharacterDatabaseTransaction& trans)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
for (uint8 group = 0; group < MAX_SPECIALIZATIONS; ++group)
{
PlayerTalentMap* talents = GetTalentMap(group);
for (auto itr = talents->begin(); itr != talents->end();)
{
if (itr->second == PLAYERSPELL_REMOVED)
{
itr = talents->erase(itr);
continue;
}
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TALENT);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, itr->first);
stmt->setUInt8(2, group);
trans->Append(stmt);
++itr;
}
}
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PVP_TALENT);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
for (uint8 group = 0; group < MAX_SPECIALIZATIONS; ++group)
{
PlayerPvpTalentMap const& talents = GetPvpTalentMap(group);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_PVP_TALENT);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, talents[0]);
stmt->setUInt32(2, talents[1]);
stmt->setUInt32(3, talents[2]);
stmt->setUInt32(4, talents[3]);
stmt->setUInt8(5, group);
trans->Append(stmt);
}
}
void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec)
{
if (GetActiveTalentGroup() == spec->OrderIndex)
return;
if (IsNonMeleeSpellCast(false))
InterruptNonMeleeSpells(false);
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
_SaveActions(trans);
CharacterDatabase.CommitTransaction(trans);
// TO-DO: We need more research to know what happens with warlock's reagent
if (Pet* pet = GetPet())
RemovePet(pet, PET_SAVE_NOT_IN_SLOT);
ClearAllReactives();
UnsummonAllTotems();
ExitVehicle();
RemoveAllControlled();
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::ChangeSpec);
// remove single target auras at other targets
AuraList& scAuras = GetSingleCastAuras();
for (AuraList::iterator iter = scAuras.begin(); iter != scAuras.end();)
{
Aura* aura = *iter;
if (aura->GetUnitOwner() != this)
{
aura->Remove();
iter = scAuras.begin();
}
else
++iter;
}
/*RemoveAllAurasOnDeath();
if (GetPet())
GetPet()->RemoveAllAurasOnDeath();*/
//RemoveAllAuras(GetGUID(), nullptr, false, true); // removes too many auras
//ExitVehicle(); // should be impossible to switch specs from inside a vehicle..
// Let client clear his current Actions
SendActionButtons(2);
// m_actionButtons.clear() is called in the next _LoadActionButtons
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
if (!talentInfo)
continue;
// unlearn only talents for character class
// some spell learned by one class as normal spells or know at creation but another class learn it as talent,
// to prevent unexpected lost normal learned spell skip another class talents
if (talentInfo->ClassID != GetClass())
continue;
if (talentInfo->SpellID == 0)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentInfo->SpellID, DIFFICULTY_NONE);
if (!spellInfo)
continue;
RemoveSpell(talentInfo->SpellID, true);
// search for spells that the talent teaches and unlearn them
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
if (spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL) && spellEffectInfo.TriggerSpell > 0)
RemoveSpell(spellEffectInfo.TriggerSpell, true);
if (talentInfo->OverridesSpellID)
RemoveOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID);
}
for (uint32 pvpTalentID = 0; pvpTalentID < sPvpTalentStore.GetNumRows(); ++pvpTalentID)
{
PvpTalentEntry const* talentInfo = sPvpTalentStore.LookupEntry(pvpTalentID);
if (!talentInfo)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentInfo->SpellID, DIFFICULTY_NONE);
if (!spellInfo)
continue;
RemoveSpell(talentInfo->SpellID, true);
// search for spells that the talent teaches and unlearn them
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
if (spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL) && spellEffectInfo.TriggerSpell > 0)
RemoveSpell(spellEffectInfo.TriggerSpell, true);
if (talentInfo->OverridesSpellID)
RemoveOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID);
}
// Remove spec specific spells
RemoveSpecializationSpells();
for (uint32 glyphId : GetGlyphs(GetActiveTalentGroup()))
RemoveAurasDueToSpell(sGlyphPropertiesStore.AssertEntry(glyphId)->SpellID);
SetActiveTalentGroup(spec->OrderIndex);
SetPrimarySpecialization(spec->ID);
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
if (!talentInfo)
continue;
// learn only talents for character class
if (talentInfo->ClassID != GetClass())
continue;
if (!talentInfo->SpellID)
continue;
// if the talent can be found in the newly activated PlayerTalentMap
if (HasTalent(talentInfo->ID, GetActiveTalentGroup()))
{
LearnSpell(talentInfo->SpellID, false); // add the talent to the PlayerSpellMap
if (talentInfo->OverridesSpellID)
AddOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID);
}
}
for (uint8 slot = 0; slot < MAX_PVP_TALENT_SLOTS; ++slot)
{
PvpTalentEntry const* talentInfo = sPvpTalentStore.LookupEntry(GetPvpTalentMap(GetActiveTalentGroup())[slot]);
if (!talentInfo)
continue;
if (!talentInfo->SpellID)
continue;
AddPvpTalent(talentInfo, GetActiveTalentGroup(), slot);
}
LearnSpecializationSpells();
if (CanUseMastery())
for (uint32 i = 0; i < MAX_MASTERY_SPELLS; ++i)
if (uint32 mastery = spec->MasterySpellID[i])
LearnSpell(mastery, false);
InitTalentForLevel();
// load them asynchronously
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt8(1, GetActiveTalentGroup());
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);
}));
}
UpdateDisplayPower();
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);
UpdateItemSetAuras(false);
// update visible transmog
for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
if (Item* equippedItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
SetVisibleItemSlot(i, equippedItem);
for (uint32 glyphId : GetGlyphs(spec->OrderIndex))
CastSpell(this, sGlyphPropertiesStore.AssertEntry(glyphId)->SpellID, true);
WorldPackets::Talent::ActiveGlyphs activeGlyphs;
activeGlyphs.Glyphs.reserve(GetGlyphs(spec->OrderIndex).size());
for (uint32 glyphId : GetGlyphs(spec->OrderIndex))
if (std::vector<uint32> const* bindableSpells = sDB2Manager.GetGlyphBindableSpells(glyphId))
for (uint32 bindableSpell : *bindableSpells)
if (HasSpell(bindableSpell) && m_overrideSpells.find(bindableSpell) == m_overrideSpells.end())
activeGlyphs.Glyphs.emplace_back(uint32(bindableSpell), uint16(glyphId));
activeGlyphs.IsFullUpdate = true;
SendDirectMessage(activeGlyphs.Write());
if (Item* item = GetItemByEntry(ITEM_ID_HEART_OF_AZEROTH, ItemSearchLocation::Everywhere))
{
if (AzeriteItem* azeriteItem = item->ToAzeriteItem())
{
if (azeriteItem->IsEquipped())
{
ApplyAllAzeriteEmpoweredItemMods(false);
ApplyAzeritePowers(azeriteItem, false);
}
azeriteItem->SetSelectedAzeriteEssences(spec->ID);
if (azeriteItem->IsEquipped())
{
ApplyAzeritePowers(azeriteItem, true);
ApplyAllAzeriteEmpoweredItemMods(true);
}
azeriteItem->SetState(ITEM_CHANGED, this);
}
}
Unit::AuraEffectList const& shapeshiftAuras = GetAuraEffectsByType(SPELL_AURA_MOD_SHAPESHIFT);
for (AuraEffect* aurEff : shapeshiftAuras)
{
aurEff->HandleShapeshiftBoosts(this, false);
aurEff->HandleShapeshiftBoosts(this, true);
}
}
void Player::LoadActions(PreparedQueryResult result)
{
_LoadActions(result);
SendActionButtons(1);
}
void Player::SetReputation(uint32 factionentry, int32 value)
{
GetReputationMgr().SetReputation(sFactionStore.LookupEntry(factionentry), value);
}
int32 Player::GetReputation(uint32 factionentry) const
{
return GetReputationMgr().GetReputation(sFactionStore.LookupEntry(factionentry));
}
std::string Player::GetGuildName() const
{
return GetGuildId() ? sGuildMgr->GetGuildById(GetGuildId())->GetName() : "";
}
void Player::AddRefundReference(ObjectGuid it)
{
m_refundableItems.insert(it);
}
void Player::DeleteRefundReference(ObjectGuid it)
{
GuidSet::iterator itr = m_refundableItems.find(it);
if (itr != m_refundableItems.end())
m_refundableItems.erase(itr);
}
void Player::SendRefundInfo(Item* item)
{
// This function call unsets ITEM_FIELD_FLAG_REFUNDABLE if played time is over 2 hours.
item->UpdatePlayedTime(this);
if (!item->IsRefundable())
{
TC_LOG_DEBUG("entities.player.items", "Item refund: item not refundable!");
return;
}
if (GetGUID() != item->GetRefundRecipient()) // Formerly refundable item got traded
{
TC_LOG_DEBUG("entities.player.items", "Item refund: item was traded!");
item->SetNotRefundable(this);
return;
}
ItemExtendedCostEntry const* iece = sItemExtendedCostStore.LookupEntry(item->GetPaidExtendedCost());
if (!iece)
{
TC_LOG_DEBUG("entities.player.items", "Item refund: cannot find extendedcost data.");
return;
}
WorldPackets::Item::SetItemPurchaseData setItemPurchaseData;
setItemPurchaseData.ItemGUID = item->GetGUID();
setItemPurchaseData.PurchaseTime = GetTotalPlayedTime() - item->GetPlayedTime();
setItemPurchaseData.Contents.Money = item->GetPaidMoney();
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_ITEMS; ++i) // item cost data
{
setItemPurchaseData.Contents.Items[i].ItemCount = iece->ItemCount[i];
setItemPurchaseData.Contents.Items[i].ItemID = iece->ItemID[i];
}
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_CURRENCIES; ++i) // currency cost data
{
if (iece->Flags & (ITEM_EXT_COST_CURRENCY_REQ_IS_SEASON_EARNED_1 << i))
continue;
setItemPurchaseData.Contents.Currencies[i].CurrencyCount = iece->CurrencyCount[i];
setItemPurchaseData.Contents.Currencies[i].CurrencyID = iece->CurrencyID[i];
}
SendDirectMessage(setItemPurchaseData.Write());
}
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, GenerateItemRandomBonusListId(itemId));
if (item)
SendNewItem(item, count, true, false);
else
return false;
return true;
}
void Player::SendItemRefundResult(Item* item, ItemExtendedCostEntry const* iece, uint8 error) const
{
WorldPackets::Item::ItemPurchaseRefundResult itemPurchaseRefundResult;
itemPurchaseRefundResult.ItemGUID = item->GetGUID();
itemPurchaseRefundResult.Result = error;
if (!error)
{
itemPurchaseRefundResult.Contents.emplace();
itemPurchaseRefundResult.Contents->Money = item->GetPaidMoney();
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_ITEMS; ++i) // item cost data
{
itemPurchaseRefundResult.Contents->Items[i].ItemCount = iece->ItemCount[i];
itemPurchaseRefundResult.Contents->Items[i].ItemID = iece->ItemID[i];
}
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_CURRENCIES; ++i) // currency cost data
{
if (iece->Flags & (ITEM_EXT_COST_CURRENCY_REQ_IS_SEASON_EARNED_1 << i))
continue;
itemPurchaseRefundResult.Contents->Currencies[i].CurrencyCount = iece->CurrencyCount[i];
itemPurchaseRefundResult.Contents->Currencies[i].CurrencyID = iece->CurrencyID[i];
}
}
SendDirectMessage(itemPurchaseRefundResult.Write());
}
void Player::RefundItem(Item* item)
{
if (!item->IsRefundable())
{
TC_LOG_DEBUG("entities.player.items", "Item refund: item not refundable!");
return;
}
if (item->IsRefundExpired()) // item refund has expired
{
item->SetNotRefundable(this);
SendItemRefundResult(item, nullptr, 10);
return;
}
if (GetGUID() != item->GetRefundRecipient()) // Formerly refundable item got traded
{
TC_LOG_DEBUG("entities.player.items", "Item refund: item was traded!");
item->SetNotRefundable(this);
return;
}
ItemExtendedCostEntry const* iece = sItemExtendedCostStore.LookupEntry(item->GetPaidExtendedCost());
if (!iece)
{
TC_LOG_DEBUG("entities.player.items", "Item refund: cannot find extendedcost data.");
return;
}
bool store_error = false;
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_ITEMS; ++i)
{
uint32 count = iece->ItemCount[i];
uint32 itemid = iece->ItemID[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)
{
SendItemRefundResult(item, iece, 10);
return;
}
SendItemRefundResult(item, iece, 0);
uint64 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, false);
GetSession()->GetCollectionMgr()->RemoveTemporaryAppearance(item);
// Destroy item
DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
// Grant back extendedcost items
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_ITEMS; ++i)
{
uint32 count = iece->ItemCount[i];
uint32 itemid = iece->ItemID[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 currencies
for (uint8 i = 0; i < MAX_ITEM_EXT_COST_CURRENCIES; ++i)
{
if (iece->Flags & (ITEM_EXT_COST_CURRENCY_REQ_IS_SEASON_EARNED_1 << i))
continue;
uint32 count = iece->CurrencyCount[i];
uint32 currencyid = iece->CurrencyID[i];
if (count && currencyid)
ModifyCurrency(currencyid, count, true, true);
}
// Grant back money
if (moneyRefund)
ModifyMoney(moneyRefund); // Saved in SaveInventoryAndGoldToDB
SaveInventoryAndGoldToDB(trans);
CharacterDatabase.CommitTransaction(trans);
}
void Player::SendItemRetrievalMail(uint32 itemEntry, uint32 count, ItemContext context)
{
MailSender sender(MAIL_CREATURE, UI64LIT(34337) /* The Postmaster */);
MailDraft draft("Recovered Item", "We recovered a lost item in the twisting nether and noted that it was yours.$B$BPlease find said object enclosed."); // This is the text used in Cataclysm, it probably wasn't changed.
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
if (Item* item = Item::CreateItem(itemEntry, count, context, nullptr))
{
item->SaveToDB(trans);
draft.AddItem(item);
}
draft.SendMailTo(trans, MailReceiver(this, GetGUID().GetCounter()), sender);
CharacterDatabase.CommitTransaction(trans);
}
void Player::SetRandomWinner(bool isWinner)
{
m_IsBGRandomWinner = isWinner;
if (m_IsBGRandomWinner)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_BATTLEGROUND_RANDOM);
stmt->setUInt64(0, GetGUID().GetCounter());
CharacterDatabase.Execute(stmt);
}
}
void Player::_LoadRandomBGStatus(PreparedQueryResult result)
{
//QueryResult result = CharacterDatabase.PQuery("SELECT guid FROM character_battleground_random WHERE guid = '%u'", GetGUIDLow());
if (result)
m_IsBGRandomWinner = true;
}
float Player::GetAverageItemLevel() const
{
float sum = 0;
uint32 count = 0;
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])
sum += m_items[i]->GetItemLevel(this);
++count;
}
return ((float)sum) / count;
}
void Player::_LoadInstanceTimeRestrictions(PreparedQueryResult result)
{
if (!result)
return;
do
{
Field* fields = result->Fetch();
_instanceResetTimes.insert(InstanceTimeMap::value_type(fields[0].GetUInt32(), fields[1].GetUInt64()));
} while (result->NextRow());
}
void Player::_SaveInstanceTimeRestrictions(CharacterDatabaseTransaction& trans)
{
if (_instanceResetTimes.empty())
return;
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ACCOUNT_INSTANCE_LOCK_TIMES);
stmt->setUInt32(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->setUInt32(0, GetSession()->GetAccountId());
stmt->setUInt32(1, itr->first);
stmt->setInt64(2, itr->second);
trans->Append(stmt);
}
}
bool Player::IsInWhisperWhiteList(ObjectGuid guid)
{
for (GuidList::const_iterator itr = WhisperList.begin(); itr != WhisperList.end(); ++itr)
if (*itr == guid)
return true;
return false;
}
uint8 Player::GetNextVoidStorageFreeSlot() const
{
for (uint8 i = 0; i < VOID_STORAGE_MAX_SLOT; ++i)
if (!_voidStorageItems[i]) // unused item
return i;
return VOID_STORAGE_MAX_SLOT;
}
uint8 Player::GetNumOfVoidStorageFreeSlots() const
{
uint8 count = 0;
for (uint8 i = 0; i < VOID_STORAGE_MAX_SLOT; ++i)
if (!_voidStorageItems[i])
count++;
return count;
}
uint8 Player::AddVoidStorageItem(VoidStorageItem&& item)
{
uint8 slot = GetNextVoidStorageFreeSlot();
if (slot >= VOID_STORAGE_MAX_SLOT)
{
GetSession()->SendVoidStorageTransferResult(VOID_TRANSFER_ERROR_FULL);
return 255;
}
_voidStorageItems[slot] = new VoidStorageItem(std::move(item));
return slot;
}
void Player::DeleteVoidStorageItem(uint8 slot)
{
if (slot >= VOID_STORAGE_MAX_SLOT)
{
GetSession()->SendVoidStorageTransferResult(VOID_TRANSFER_ERROR_INTERNAL_ERROR_1);
return;
}
delete _voidStorageItems[slot];
_voidStorageItems[slot] = nullptr;
}
bool Player::SwapVoidStorageItem(uint8 oldSlot, uint8 newSlot)
{
if (oldSlot >= VOID_STORAGE_MAX_SLOT || newSlot >= VOID_STORAGE_MAX_SLOT || oldSlot == newSlot)
return false;
std::swap(_voidStorageItems[newSlot], _voidStorageItems[oldSlot]);
return true;
}
VoidStorageItem* Player::GetVoidStorageItem(uint8 slot) const
{
if (slot >= VOID_STORAGE_MAX_SLOT)
{
GetSession()->SendVoidStorageTransferResult(VOID_TRANSFER_ERROR_INTERNAL_ERROR_1);
return nullptr;
}
return _voidStorageItems[slot];
}
VoidStorageItem* Player::GetVoidStorageItem(uint64 id, uint8& slot) const
{
for (uint8 i = 0; i < VOID_STORAGE_MAX_SLOT; ++i)
{
if (_voidStorageItems[i] && _voidStorageItems[i]->ItemId == id)
{
slot = i;
return _voidStorageItems[i];
}
}
return nullptr;
}
void Player::CreateGarrison(uint32 garrSiteId)
{
std::unique_ptr<Garrison> garrison(new Garrison(this));
if (garrison->Create(garrSiteId))
_garrison = std::move(garrison);
}
void Player::DeleteGarrison()
{
if (_garrison)
{
_garrison->Delete();
_garrison.reset();
}
}
void Player::SendMovementSetCollisionHeight(float height, WorldPackets::Movement::UpdateCollisionHeightReason reason)
{
WorldPackets::Movement::MoveSetCollisionHeight setCollisionHeight;
setCollisionHeight.MoverGUID = GetGUID();
setCollisionHeight.SequenceIndex = m_movementCounter++;
setCollisionHeight.Height = height;
setCollisionHeight.Scale = GetObjectScale();
setCollisionHeight.MountDisplayID = GetMountDisplayId();
setCollisionHeight.ScaleDuration = m_unitData->ScaleDuration;
setCollisionHeight.Reason = reason;
SendDirectMessage(setCollisionHeight.Write());
WorldPackets::Movement::MoveUpdateCollisionHeight updateCollisionHeight;
updateCollisionHeight.Status = &m_movementInfo;
updateCollisionHeight.Height = height;
updateCollisionHeight.Scale = GetObjectScale();
SendMessageToSet(updateCollisionHeight.Write(), false);
}
void Player::SendPlayerChoice(ObjectGuid sender, int32 choiceId)
{
PlayerChoice const* playerChoice = sObjectMgr->GetPlayerChoice(choiceId);
if (!playerChoice)
return;
LocaleConstant locale = GetSession()->GetSessionDbLocaleIndex();
PlayerChoiceLocale const* playerChoiceLocale = locale != DEFAULT_LOCALE ? sObjectMgr->GetPlayerChoiceLocale(choiceId) : nullptr;
PlayerTalkClass->GetInteractionData().Reset();
PlayerTalkClass->GetInteractionData().SourceGuid = sender;
PlayerTalkClass->GetInteractionData().PlayerChoiceId = uint32(choiceId);
WorldPackets::Quest::DisplayPlayerChoice displayPlayerChoice;
displayPlayerChoice.SenderGUID = sender;
displayPlayerChoice.ChoiceID = choiceId;
displayPlayerChoice.UiTextureKitID = playerChoice->UiTextureKitId;
displayPlayerChoice.SoundKitID = playerChoice->SoundKitId;
displayPlayerChoice.Question = playerChoice->Question;
if (playerChoiceLocale)
ObjectMgr::GetLocaleString(playerChoiceLocale->Question, locale, displayPlayerChoice.Question);
displayPlayerChoice.Responses.resize(playerChoice->Responses.size());
displayPlayerChoice.CloseChoiceFrame = false;
displayPlayerChoice.HideWarboardHeader = playerChoice->HideWarboardHeader;
displayPlayerChoice.KeepOpenAfterChoice = playerChoice->KeepOpenAfterChoice;
for (std::size_t i = 0; i < playerChoice->Responses.size(); ++i)
{
PlayerChoiceResponse const& playerChoiceResponseTemplate = playerChoice->Responses[i];
WorldPackets::Quest::PlayerChoiceResponse& playerChoiceResponse = displayPlayerChoice.Responses[i];
playerChoiceResponse.ResponseID = playerChoiceResponseTemplate.ResponseId;
playerChoiceResponse.ResponseIdentifier = playerChoiceResponseTemplate.ResponseIdentifier;
playerChoiceResponse.ChoiceArtFileID = playerChoiceResponseTemplate.ChoiceArtFileId;
playerChoiceResponse.Flags = playerChoiceResponseTemplate.Flags;
playerChoiceResponse.WidgetSetID = playerChoiceResponseTemplate.WidgetSetID;
playerChoiceResponse.UiTextureAtlasElementID = playerChoiceResponseTemplate.UiTextureAtlasElementID;
playerChoiceResponse.SoundKitID = playerChoiceResponseTemplate.SoundKitID;
playerChoiceResponse.GroupID = playerChoiceResponseTemplate.GroupID;
playerChoiceResponse.UiTextureKitID = playerChoiceResponseTemplate.UiTextureKitID;
playerChoiceResponse.Answer = playerChoiceResponseTemplate.Answer;
playerChoiceResponse.Header = playerChoiceResponseTemplate.Header;
playerChoiceResponse.SubHeader = playerChoiceResponseTemplate.SubHeader;
playerChoiceResponse.ButtonTooltip = playerChoiceResponseTemplate.ButtonTooltip;
playerChoiceResponse.Description = playerChoiceResponseTemplate.Description;
playerChoiceResponse.Confirmation = playerChoiceResponseTemplate.Confirmation;
if (playerChoiceLocale)
{
if (PlayerChoiceResponseLocale const* playerChoiceResponseLocale = Trinity::Containers::MapGetValuePtr(playerChoiceLocale->Responses, playerChoiceResponseTemplate.ResponseId))
{
ObjectMgr::GetLocaleString(playerChoiceResponseLocale->Answer, locale, playerChoiceResponse.Answer);
ObjectMgr::GetLocaleString(playerChoiceResponseLocale->Header, locale, playerChoiceResponse.Header);
ObjectMgr::GetLocaleString(playerChoiceResponseLocale->SubHeader, locale, playerChoiceResponse.SubHeader);
ObjectMgr::GetLocaleString(playerChoiceResponseLocale->ButtonTooltip, locale, playerChoiceResponse.ButtonTooltip);
ObjectMgr::GetLocaleString(playerChoiceResponseLocale->Description, locale, playerChoiceResponse.Description);
ObjectMgr::GetLocaleString(playerChoiceResponseLocale->Confirmation, locale, playerChoiceResponse.Confirmation);
}
}
if (playerChoiceResponseTemplate.Reward)
{
playerChoiceResponse.Reward.emplace();
playerChoiceResponse.Reward->TitleID = playerChoiceResponseTemplate.Reward->TitleId;
playerChoiceResponse.Reward->PackageID = playerChoiceResponseTemplate.Reward->PackageId;
playerChoiceResponse.Reward->SkillLineID = playerChoiceResponseTemplate.Reward->SkillLineId;
playerChoiceResponse.Reward->SkillPointCount = playerChoiceResponseTemplate.Reward->SkillPointCount;
playerChoiceResponse.Reward->ArenaPointCount = playerChoiceResponseTemplate.Reward->ArenaPointCount;
playerChoiceResponse.Reward->HonorPointCount = playerChoiceResponseTemplate.Reward->HonorPointCount;
playerChoiceResponse.Reward->Money = playerChoiceResponseTemplate.Reward->Money;
playerChoiceResponse.Reward->Xp = playerChoiceResponseTemplate.Reward->Xp;
for (PlayerChoiceResponseRewardItem const& item : playerChoiceResponseTemplate.Reward->Items)
{
playerChoiceResponse.Reward->Items.emplace_back();
WorldPackets::Quest::PlayerChoiceResponseRewardEntry& rewardEntry = playerChoiceResponse.Reward->Items.back();
rewardEntry.Item.ItemID = item.Id;
rewardEntry.Quantity = item.Quantity;
if (!item.BonusListIDs.empty())
{
rewardEntry.Item.ItemBonus.emplace();
rewardEntry.Item.ItemBonus->BonusListIDs = item.BonusListIDs;
}
}
for (PlayerChoiceResponseRewardEntry const& currency : playerChoiceResponseTemplate.Reward->Currency)
{
playerChoiceResponse.Reward->Items.emplace_back();
WorldPackets::Quest::PlayerChoiceResponseRewardEntry& rewardEntry = playerChoiceResponse.Reward->Items.back();
rewardEntry.Item.ItemID = currency.Id;
rewardEntry.Quantity = currency.Quantity;
}
for (PlayerChoiceResponseRewardEntry const& faction : playerChoiceResponseTemplate.Reward->Faction)
{
playerChoiceResponse.Reward->Items.emplace_back();
WorldPackets::Quest::PlayerChoiceResponseRewardEntry& rewardEntry = playerChoiceResponse.Reward->Items.back();
rewardEntry.Item.ItemID = faction.Id;
rewardEntry.Quantity = faction.Quantity;
}
for (PlayerChoiceResponseRewardItem const& item : playerChoiceResponseTemplate.Reward->ItemChoices)
{
playerChoiceResponse.Reward->ItemChoices.emplace_back();
WorldPackets::Quest::PlayerChoiceResponseRewardEntry& rewardEntry = playerChoiceResponse.Reward->ItemChoices.back();
rewardEntry.Item.ItemID = item.Id;
rewardEntry.Quantity = item.Quantity;
if (!item.BonusListIDs.empty())
{
rewardEntry.Item.ItemBonus.emplace();
rewardEntry.Item.ItemBonus->BonusListIDs = item.BonusListIDs;
}
}
}
playerChoiceResponse.RewardQuestID = playerChoiceResponseTemplate.RewardQuestID;
if (playerChoiceResponseTemplate.MawPower)
{
WorldPackets::Quest::PlayerChoiceResponseMawPower& mawPower = playerChoiceResponse.MawPower.emplace();
mawPower.TypeArtFileID = playerChoiceResponse.MawPower->TypeArtFileID;
mawPower.Rarity = playerChoiceResponse.MawPower->Rarity;
mawPower.RarityColor = playerChoiceResponse.MawPower->RarityColor;
mawPower.SpellID = playerChoiceResponse.MawPower->SpellID;
mawPower.MaxStacks = playerChoiceResponse.MawPower->MaxStacks;
}
}
SendDirectMessage(displayPlayerChoice.Write());
}
bool Player::MeetPlayerCondition(uint32 conditionId) const
{
if (PlayerConditionEntry const* playerCondition = sPlayerConditionStore.LookupEntry(conditionId))
if (!ConditionMgr::IsPlayerMeetingCondition(this, playerCondition))
return false;
return true;
}
bool Player::IsInFriendlyArea() const
{
if (AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(GetAreaId()))
return IsFriendlyArea(areaEntry);
return false;
}
bool Player::IsFriendlyArea(AreaTableEntry const* areaEntry) const
{
ASSERT(areaEntry != nullptr);
FactionTemplateEntry const* factionTemplate = GetFactionTemplateEntry();
if (!factionTemplate)
return false;
if (!(factionTemplate->FriendGroup & areaEntry->FactionGroupMask))
return false;
return true;
}
std::string Player::GetMapAreaAndZoneString() const
{
uint32 areaId = GetAreaId();
std::string areaName = "Unknown";
std::string zoneName = "Unknown";
if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId))
{
areaName = area->AreaName[GetSession()->GetSessionDbcLocale()];
if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(area->ParentAreaID))
zoneName = zone->AreaName[GetSession()->GetSessionDbcLocale()];
}
std::ostringstream str;
str << "Map: " << GetMapId() << " (" << (FindMap() ? FindMap()->GetMapName() : "Unknown") << ") Area: " << areaId << " (" << areaName.c_str() << ") Zone: " << zoneName.c_str();
return str.str();
}
std::string Player::GetCoordsMapAreaAndZoneString() const
{
std::ostringstream str;
str << Position::ToString() << " " << GetMapAreaAndZoneString();
return str.str();
}
Guild* Player::GetGuild()
{
ObjectGuid::LowType guildId = GetGuildId();
return guildId ? sGuildMgr->GetGuildById(guildId) : nullptr;
}
Guild const* Player::GetGuild() const
{
ObjectGuid::LowType guildId = GetGuildId();
return guildId ? sGuildMgr->GetGuildById(guildId) : nullptr;
}
Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, uint32 duration, bool aliveOnly)
{
Pet* pet = new Pet(this, petType);
if (petType == SUMMON_PET && pet->LoadPetFromDB(this, entry))
{
if (aliveOnly && !pet->IsAlive())
{
pet->DespawnOrUnsummon();
SendTameFailure(PetTameResult::Dead);
return nullptr;
}
if (duration > 0)
pet->SetDuration(duration);
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())
{
TC_LOG_ERROR("misc", "Player::SummonPet: Pet (%s, Entry: %d) not summoned. Suggested coordinates aren't valid (X: %f Y: %f)", pet->GetGUID().ToString().c_str(), pet->GetEntry(), pet->GetPositionX(), pet->GetPositionY());
delete pet;
return nullptr;
}
Map* map = GetMap();
uint32 pet_number = sObjectMgr->GeneratePetNumber();
if (!pet->Create(map->GenerateLowGuid<HighGuid::Pet>(), map, entry))
{
TC_LOG_ERROR("misc", "Player::SummonPet: No such creature entry %u", entry);
delete pet;
return nullptr;
}
PhasingHandler::InheritPhaseShift(pet, this);
pet->SetCreatorGUID(GetGUID());
pet->SetFaction(GetFaction());
pet->SetNpcFlags(UNIT_NPC_FLAG_NONE);
pet->SetNpcFlags2(UNIT_NPC_FLAG_2_NONE);
pet->InitStatsForLevel(GetLevel());
SetMinion(pet, true);
switch (petType)
{
case SUMMON_PET:
// this enables pet details window (Shift+P)
pet->GetCharmInfo()->SetPetNumber(pet_number, true);
pet->SetClass(CLASS_MAGE);
pet->SetPetExperience(0);
pet->SetPetNextLevelExperience(1000);
pet->SetFullHealth();
pet->SetFullPower(POWER_MANA);
pet->SetPetNameTimestamp(uint32(GameTime::GetGameTime()));
break;
default:
break;
}
map->AddToMap(pet->ToCreature());
switch (petType)
{
case SUMMON_PET:
pet->InitPetCreateSpells();
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
PetSpellInitialize();
break;
default:
break;
}
if (duration > 0)
pet->SetDuration(duration);
//ObjectAccessor::UpdateObjectVisibility(pet);
return pet;
}
bool Player::CanUseMastery() const
{
if (ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(GetPrimarySpecialization()))
return HasSpell(chrSpec->MasterySpellID[0]) || HasSpell(chrSpec->MasterySpellID[1]);
return false;
}
void Player::ValidateMovementInfo(MovementInfo* mi)
{
//! Anti-cheat checks. Please keep them in seperate if () blocks to maintain a clear overview.
//! Might be subject to latency, so just remove improper flags.
#ifdef TRINITY_DEBUG
#define REMOVE_VIOLATING_FLAGS(check, maskToRemove) \
{ \
if (check) \
{ \
TC_LOG_DEBUG("entities.unit", "Player::ValidateMovementInfo: Violation of MovementFlags found (%s). " \
"MovementFlags: %u, MovementFlags2: %u for player %s. Mask %u will be removed.", \
STRINGIZE(check), mi->GetMovementFlags(), mi->GetExtraMovementFlags(), GetGUID().ToString().c_str(), maskToRemove); \
mi->RemoveMovementFlag((maskToRemove)); \
} \
}
#else
#define REMOVE_VIOLATING_FLAGS(check, maskToRemove) \
if (check) \
mi->RemoveMovementFlag((maskToRemove));
#endif
if (!m_unitMovedByMe->GetVehicleBase() || !(m_unitMovedByMe->GetVehicle()->GetVehicleInfo()->Flags & VEHICLE_FLAG_FIXED_POSITION))
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_ROOT), MOVEMENTFLAG_ROOT);
/*! This must be a packet spoofing attempt. MOVEMENTFLAG_ROOT sent from the client is not valid
in conjunction with any of the moving movement flags such as MOVEMENTFLAG_FORWARD.
It will freeze clients that receive this player's movement info.
*/
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_ROOT) && mi->HasMovementFlag(MOVEMENTFLAG_MASK_MOVING),
MOVEMENTFLAG_MASK_MOVING);
//! Cannot hover without SPELL_AURA_HOVER
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_HOVER) && !m_unitMovedByMe->HasAuraType(SPELL_AURA_HOVER),
MOVEMENTFLAG_HOVER);
//! Cannot ascend and descend at the same time
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_ASCENDING) && mi->HasMovementFlag(MOVEMENTFLAG_DESCENDING),
MOVEMENTFLAG_ASCENDING | MOVEMENTFLAG_DESCENDING);
//! Cannot move left and right at the same time
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_LEFT) && mi->HasMovementFlag(MOVEMENTFLAG_RIGHT),
MOVEMENTFLAG_LEFT | MOVEMENTFLAG_RIGHT);
//! Cannot strafe left and right at the same time
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_STRAFE_LEFT) && mi->HasMovementFlag(MOVEMENTFLAG_STRAFE_RIGHT),
MOVEMENTFLAG_STRAFE_LEFT | MOVEMENTFLAG_STRAFE_RIGHT);
//! Cannot pitch up and down at the same time
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_PITCH_UP) && mi->HasMovementFlag(MOVEMENTFLAG_PITCH_DOWN),
MOVEMENTFLAG_PITCH_UP | MOVEMENTFLAG_PITCH_DOWN);
//! Cannot move forwards and backwards at the same time
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_FORWARD) && mi->HasMovementFlag(MOVEMENTFLAG_BACKWARD),
MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_BACKWARD);
//! Cannot walk on water without SPELL_AURA_WATER_WALK except for ghosts
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_WATERWALKING) &&
!m_unitMovedByMe->HasAuraType(SPELL_AURA_WATER_WALK) &&
!m_unitMovedByMe->HasAuraType(SPELL_AURA_GHOST),
MOVEMENTFLAG_WATERWALKING);
//! Cannot feather fall without SPELL_AURA_FEATHER_FALL
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_FALLING_SLOW) && !m_unitMovedByMe->HasAuraType(SPELL_AURA_FEATHER_FALL),
MOVEMENTFLAG_FALLING_SLOW);
/*! Cannot fly if no fly auras present. Exception is being a GM.
Note that we check for account level instead of Player::IsGameMaster() because in some
situations it may be feasable to use .gm fly on as a GM without having .gm on,
e.g. aerial combat.
*/
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY) && GetSession()->GetSecurity() == SEC_PLAYER &&
!m_unitMovedByMe->HasAuraType(SPELL_AURA_FLY) &&
!m_unitMovedByMe->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED),
MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY);
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY | MOVEMENTFLAG_CAN_FLY) && mi->HasMovementFlag(MOVEMENTFLAG_FALLING),
MOVEMENTFLAG_FALLING);
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_SPLINE_ELEVATION) && G3D::fuzzyEq(mi->splineElevation, 0.0f), MOVEMENTFLAG_SPLINE_ELEVATION);
// Client first checks if spline elevation != 0, then verifies flag presence
if (G3D::fuzzyNe(mi->splineElevation, 0.0f))
mi->AddMovementFlag(MOVEMENTFLAG_SPLINE_ELEVATION);
#undef REMOVE_VIOLATING_FLAGS
}
void Player::SendSupercededSpell(uint32 oldSpell, uint32 newSpell) const
{
WorldPackets::Spells::SupercededSpells supercededSpells;
supercededSpells.SpellID.push_back(newSpell);
supercededSpells.Superceded.push_back(oldSpell);
SendDirectMessage(supercededSpells.Write());
}
Difficulty Player::GetDifficultyID(MapEntry const* mapEntry) const
{
if (!mapEntry->IsRaid())
return m_dungeonDifficulty;
MapDifficultyEntry const* defaultDifficulty = sDB2Manager.GetDefaultMapDifficulty(mapEntry->ID);
if (!defaultDifficulty)
return m_legacyRaidDifficulty;
DifficultyEntry const* difficulty = sDifficultyStore.LookupEntry(defaultDifficulty->DifficultyID);
if (!difficulty || difficulty->Flags & DIFFICULTY_FLAG_LEGACY)
return m_legacyRaidDifficulty;
return m_raidDifficulty;
}
Difficulty Player::CheckLoadedDungeonDifficultyID(Difficulty difficulty)
{
DifficultyEntry const* difficultyEntry = sDifficultyStore.LookupEntry(difficulty);
if (!difficultyEntry)
return DIFFICULTY_NORMAL;
if (difficultyEntry->InstanceType != MAP_INSTANCE)
return DIFFICULTY_NORMAL;
if (!(difficultyEntry->Flags & DIFFICULTY_FLAG_CAN_SELECT))
return DIFFICULTY_NORMAL;
return difficulty;
}
Difficulty Player::CheckLoadedRaidDifficultyID(Difficulty difficulty)
{
DifficultyEntry const* difficultyEntry = sDifficultyStore.LookupEntry(difficulty);
if (!difficultyEntry)
return DIFFICULTY_NORMAL_RAID;
if (difficultyEntry->InstanceType != MAP_RAID)
return DIFFICULTY_NORMAL_RAID;
if (!(difficultyEntry->Flags & DIFFICULTY_FLAG_CAN_SELECT) || (difficultyEntry->Flags & DIFFICULTY_FLAG_LEGACY))
return DIFFICULTY_NORMAL_RAID;
return difficulty;
}
Difficulty Player::CheckLoadedLegacyRaidDifficultyID(Difficulty difficulty)
{
DifficultyEntry const* difficultyEntry = sDifficultyStore.LookupEntry(difficulty);
if (!difficultyEntry)
return DIFFICULTY_10_N;
if (difficultyEntry->InstanceType != MAP_RAID)
return DIFFICULTY_10_N;
if (!(difficultyEntry->Flags & DIFFICULTY_FLAG_CAN_SELECT) || !(difficultyEntry->Flags & DIFFICULTY_FLAG_LEGACY))
return DIFFICULTY_10_N;
return difficulty;
}
SpellInfo const* Player::GetCastSpellInfo(SpellInfo const* spellInfo) const
{
auto overrides = m_overrideSpells.find(spellInfo->Id);
if (overrides != m_overrideSpells.end())
for (uint32 spellId : overrides->second)
if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(spellId, GetMap()->GetDifficultyID()))
return Unit::GetCastSpellInfo(newInfo);
return Unit::GetCastSpellInfo(spellInfo);
}
void Player::AddOverrideSpell(uint32 overridenSpellId, uint32 newSpellId)
{
m_overrideSpells[overridenSpellId].insert(newSpellId);
}
void Player::RemoveOverrideSpell(uint32 overridenSpellId, uint32 newSpellId)
{
auto overrides = m_overrideSpells.find(overridenSpellId);
if (overrides == m_overrideSpells.end())
return;
overrides->second.erase(newSpellId);
if (overrides->second.empty())
m_overrideSpells.erase(overrides);
}
void Player::LearnSpecializationSpells()
{
if (std::vector<SpecializationSpellsEntry const*> const* specSpells = sDB2Manager.GetSpecializationSpells(GetPrimarySpecialization()))
{
for (size_t j = 0; j < specSpells->size(); ++j)
{
SpecializationSpellsEntry const* specSpell = (*specSpells)[j];
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(specSpell->SpellID, DIFFICULTY_NONE);
if (!spellInfo || spellInfo->SpellLevel > GetLevel())
continue;
LearnSpell(specSpell->SpellID, false);
if (specSpell->OverridesSpellID)
AddOverrideSpell(specSpell->OverridesSpellID, specSpell->SpellID);
}
}
}
void Player::RemoveSpecializationSpells()
{
for (uint32 i = 0; i < MAX_SPECIALIZATIONS; ++i)
{
if (ChrSpecializationEntry const* specialization = sDB2Manager.GetChrSpecializationByIndex(GetClass(), i))
{
if (std::vector<SpecializationSpellsEntry const*> const* specSpells = sDB2Manager.GetSpecializationSpells(specialization->ID))
{
for (size_t j = 0; j < specSpells->size(); ++j)
{
SpecializationSpellsEntry const* specSpell = (*specSpells)[j];
RemoveSpell(specSpell->SpellID, true);
if (specSpell->OverridesSpellID)
RemoveOverrideSpell(specSpell->OverridesSpellID, specSpell->SpellID);
}
}
for (uint32 j = 0; j < MAX_MASTERY_SPELLS; ++j)
if (uint32 mastery = specialization->MasterySpellID[j])
RemoveAurasDueToSpell(mastery);
}
}
}
void Player::RemoveSocial()
{
sSocialMgr->RemovePlayerSocial(GetGUID());
m_social = nullptr;
}
uint32 Player::GetDefaultSpecId() const
{
return ASSERT_NOTNULL(sDB2Manager.GetDefaultChrSpecializationForClass(GetClass()))->ID;
}
void Player::SendSpellCategoryCooldowns() const
{
WorldPackets::Spells::CategoryCooldown cooldowns;
Unit::AuraEffectList const& categoryCooldownAuras = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_CATEGORY_COOLDOWN);
for (AuraEffect* aurEff : categoryCooldownAuras)
{
uint32 categoryId = aurEff->GetMiscValue();
auto cItr = std::find_if(cooldowns.CategoryCooldowns.begin(), cooldowns.CategoryCooldowns.end(),
[categoryId](WorldPackets::Spells::CategoryCooldown::CategoryCooldownInfo const& cooldown)
{
return cooldown.Category == categoryId;
});
if (cItr == cooldowns.CategoryCooldowns.end())
cooldowns.CategoryCooldowns.emplace_back(categoryId, -aurEff->GetAmount());
else
cItr->ModCooldown -= aurEff->GetAmount();
}
SendDirectMessage(cooldowns.Write());
}
void Player::SendRaidGroupOnlyMessage(RaidGroupReason reason, int32 delay) const
{
WorldPackets::Instance::RaidGroupOnly raidGroupOnly;
raidGroupOnly.Delay = delay;
raidGroupOnly.Reason = reason;
SendDirectMessage(raidGroupOnly.Write());
}
uint32 Player::DoRandomRoll(uint32 minimum, uint32 maximum)
{
ASSERT(maximum <= 10000);
uint32 roll = urand(minimum, maximum);
WorldPackets::Misc::RandomRoll randomRoll;
randomRoll.Min = minimum;
randomRoll.Max = maximum;
randomRoll.Result = roll;
randomRoll.Roller = GetGUID();
randomRoll.RollerWowAccount = GetSession()->GetAccountGUID();
if (Group* group = GetGroup())
group->BroadcastPacket(randomRoll.Write(), false);
else
SendDirectMessage(randomRoll.Write());
return roll;
}
void Player::UpdateItemLevelAreaBasedScaling()
{
// @todo Activate pvp item levels during world pvp
Map* map = GetMap();
bool pvpActivity = map->IsBattlegroundOrArena() || map->GetEntry()->Flags[1] & 0x40 || HasPvpRulesEnabled();
if (_usePvpItemLevels != pvpActivity)
{
float healthPct = GetHealthPct();
_RemoveAllItemMods();
ActivatePvpItemLevels(pvpActivity);
_ApplyAllItemMods();
SetHealth(CalculatePct(GetMaxHealth(), healthPct));
}
// @todo other types of power scaling such as timewalking
}
uint8 Player::GetItemLimitCategoryQuantity(ItemLimitCategoryEntry const* limitEntry) const
{
uint8 limit = limitEntry->Quantity;
if (std::vector<ItemLimitCategoryConditionEntry const*> const* limitConditions = sDB2Manager.GetItemLimitCategoryConditions(limitEntry->ID))
{
for (ItemLimitCategoryConditionEntry const* limitCondition : *limitConditions)
{
PlayerConditionEntry const* playerCondition = sPlayerConditionStore.LookupEntry(limitCondition->PlayerConditionID);
if (!playerCondition || ConditionMgr::IsPlayerMeetingCondition(this, playerCondition))
limit += limitCondition->AddQuantity;
}
}
return limit;
}
template <typename T>
static bool ForEachEquipmentSlot(InventoryType inventoryType, bool canDualWield, bool canTitanGrip, T callback)
{
switch (inventoryType)
{
case INVTYPE_HEAD: callback(EQUIPMENT_SLOT_HEAD); return true;
case INVTYPE_NECK: callback(EQUIPMENT_SLOT_NECK); return true;
case INVTYPE_SHOULDERS: callback(EQUIPMENT_SLOT_SHOULDERS); return true;
case INVTYPE_BODY: callback(EQUIPMENT_SLOT_BODY); return true;
case INVTYPE_ROBE:
case INVTYPE_CHEST: callback(EQUIPMENT_SLOT_CHEST); return true;
case INVTYPE_WAIST: callback(EQUIPMENT_SLOT_WAIST); return true;
case INVTYPE_LEGS: callback(EQUIPMENT_SLOT_LEGS); return true;
case INVTYPE_FEET: callback(EQUIPMENT_SLOT_FEET); return true;
case INVTYPE_WRISTS: callback(EQUIPMENT_SLOT_WRISTS); return true;
case INVTYPE_HANDS: callback(EQUIPMENT_SLOT_HANDS); return true;
case INVTYPE_CLOAK: callback(EQUIPMENT_SLOT_BACK); return true;
case INVTYPE_FINGER:
callback(EQUIPMENT_SLOT_FINGER1);
callback(EQUIPMENT_SLOT_FINGER2, true);
return true;
case INVTYPE_TRINKET:
callback(EQUIPMENT_SLOT_TRINKET1);
callback(EQUIPMENT_SLOT_TRINKET2, true);
return true;
case INVTYPE_WEAPON:
callback(EQUIPMENT_SLOT_MAINHAND);
if (canDualWield)
callback(EQUIPMENT_SLOT_OFFHAND, true);
return true;
case INVTYPE_2HWEAPON:
callback(EQUIPMENT_SLOT_MAINHAND);
if (canDualWield && canTitanGrip)
callback(EQUIPMENT_SLOT_OFFHAND, true);
return true;
case INVTYPE_RANGED:
case INVTYPE_RANGEDRIGHT:
case INVTYPE_WEAPONMAINHAND: callback(EQUIPMENT_SLOT_MAINHAND); return true;
case INVTYPE_SHIELD:
case INVTYPE_HOLDABLE:
case INVTYPE_WEAPONOFFHAND: callback(EQUIPMENT_SLOT_OFFHAND); return true;
case INVTYPE_NON_EQUIP:
case INVTYPE_BAG:
case INVTYPE_TABARD:
case INVTYPE_AMMO:
case INVTYPE_THROWN:
case INVTYPE_QUIVER:
case INVTYPE_RELIC:
default:
return false;
}
}
void Player::UpdateAverageItemLevelTotal()
{
std::array<std::tuple<InventoryType, uint32, ObjectGuid>, EQUIPMENT_SLOT_END> bestItemLevels = { };
bestItemLevels.fill({ INVTYPE_NON_EQUIP, 0, ObjectGuid::Empty });
float sum = 0;
ForEachItem(ItemSearchLocation::Everywhere, [this, &bestItemLevels, &sum](Item* item)
{
ItemTemplate const* itemTemplate = item->GetTemplate();
if (itemTemplate)
{
uint16 dest;
if (item->IsEquipped())
{
uint32 itemLevel = item->GetItemLevel(this);
InventoryType inventoryType = itemTemplate->GetInventoryType();
std::tuple<InventoryType, uint32, ObjectGuid>& slotData = bestItemLevels[item->GetSlot()];
if (itemLevel > std::get<1>(slotData))
{
sum += itemLevel - std::get<1>(slotData);
slotData = { inventoryType, itemLevel, item->GetGUID() };
}
}
else if (CanEquipItem(NULL_SLOT, dest, item, true, false) == EQUIP_ERR_OK)
{
uint32 itemLevel = item->GetItemLevel(this);
InventoryType inventoryType = itemTemplate->GetInventoryType();
ForEachEquipmentSlot(inventoryType, m_canDualWield, m_canTitanGrip, [&bestItemLevels, item, itemLevel, inventoryType, &sum](EquipmentSlots slot, bool checkDuplicateGuid = false)
{
if (checkDuplicateGuid)
{
for (std::tuple<InventoryType, uint32, ObjectGuid> const& slotData : bestItemLevels)
if (std::get<2>(slotData) == item->GetGUID())
return;
}
std::tuple<InventoryType, uint32, ObjectGuid>& slotData = bestItemLevels[slot];
if (itemLevel > std::get<1>(slotData))
{
sum += itemLevel - std::get<1>(slotData);
slotData = { inventoryType, itemLevel, item->GetGUID() };
}
});
}
}
return ItemSearchCallbackResult::Continue;
});
// If main hand is a 2h weapon, count it twice
std::tuple<InventoryType, uint32, ObjectGuid> const& mainHand = bestItemLevels[EQUIPMENT_SLOT_MAINHAND];
if (!m_canTitanGrip && std::get<0>(mainHand) == INVTYPE_2HWEAPON)
sum += std::get<1>(mainHand);
sum /= 16.0f;
SetAverageItemLevelTotal(sum);
}
void Player::UpdateAverageItemLevelEquipped()
{
float totalItemLevel = 0;
for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; i++)
{
if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
{
uint32 itemLevel = pItem->GetItemLevel(this);
totalItemLevel += itemLevel;
if (!m_canTitanGrip && i == EQUIPMENT_SLOT_MAINHAND && pItem->GetTemplate()->GetInventoryType() == INVTYPE_2HWEAPON) // 2h weapon counts twice
totalItemLevel += itemLevel;
}
}
totalItemLevel /= 16.0;
SetAverageItemLevelEquipped(totalItemLevel);
}
void Player::SetWarModeDesired(bool enabled)
{
// Only allow to toggle on when in stormwind/orgrimmar, and to toggle off in any rested place.
// Also disallow when in combat
if ((enabled == IsWarModeDesired()) || IsInCombat() || !HasPlayerFlag(PLAYER_FLAGS_RESTING))
return;
if (enabled && !CanEnableWarModeInArea())
return;
// Don't allow to chang when aura SPELL_PVP_RULES_ENABLED is on
if (HasAura(SPELL_PVP_RULES_ENABLED))
return;
if (enabled)
{
AddPlayerFlag(PLAYER_FLAGS_WAR_MODE_DESIRED);
TogglePvpTalents(true);
SetPvP(true);
}
else
{
RemovePlayerFlag(PLAYER_FLAGS_WAR_MODE_DESIRED);
TogglePvpTalents(false);
SetPvP(false);
}
UpdateWarModeAuras();
}
void Player::SetWarModeLocal(bool enabled)
{
if (enabled)
AddPlayerLocalFlag(PLAYER_LOCAL_FLAG_WAR_MODE);
else
RemovePlayerLocalFlag(PLAYER_LOCAL_FLAG_WAR_MODE);
}
bool Player::CanEnableWarModeInArea() const
{
AreaTableEntry const* zone = sAreaTableStore.LookupEntry(GetZoneId());
if (!zone || !IsFriendlyArea(zone))
return false;
AreaTableEntry const* area = sAreaTableStore.LookupEntry(GetAreaId());
if (!area)
area = zone;
do
{
if (area->Flags[1] & AREA_FLAG_2_CAN_ENABLE_WAR_MODE)
return true;
area = sAreaTableStore.LookupEntry(area->ParentAreaID);
} while (area);
return false;
}
void Player::UpdateWarModeAuras()
{
uint32 auraInside = 282559;
uint32 auraOutside = WARMODE_ENLISTED_SPELL_OUTSIDE;
if (IsWarModeDesired())
{
if (CanEnableWarModeInArea())
{
RemovePlayerFlag(PLAYER_FLAGS_WAR_MODE_ACTIVE);
RemoveAurasDueToSpell(auraOutside);
CastSpell(this, auraInside, true);
}
else
{
AddPlayerFlag(PLAYER_FLAGS_WAR_MODE_ACTIVE);
RemoveAurasDueToSpell(auraInside);
CastSpell(this, auraOutside, true);
}
SetWarModeLocal(true);
AddPvpFlag(UNIT_BYTE2_FLAG_PVP);
}
else
{
SetWarModeLocal(false);
RemoveAurasDueToSpell(auraOutside);
RemoveAurasDueToSpell(auraInside);
RemovePlayerFlag(PLAYER_FLAGS_WAR_MODE_ACTIVE);
RemovePvpFlag(UNIT_BYTE2_FLAG_PVP);
}
}
void Player::OnPhaseChange()
{
Unit::OnPhaseChange();
GetMap()->UpdatePersonalPhasesForPlayer(this);
}
std::string Player::GetDebugInfo() const
{
std::stringstream sstr;
sstr << Unit::GetDebugInfo();
return sstr.str();
}