TrinityCore
Player.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "Player.h"
19#include "AreaTrigger.h"
20#include "AccountMgr.h"
21#include "AchievementMgr.h"
22#include "ArenaTeam.h"
23#include "ArenaTeamMgr.h"
25#include "AzeriteItem.h"
26#include "Bag.h"
27#include "Battlefield.h"
28#include "BattlefieldMgr.h"
29#include "Battleground.h"
30#include "BattlegroundMgr.h"
31#include "BattlegroundPackets.h"
32#include "BattlegroundScore.h"
33#include "BattlePetMgr.h"
34#include "CellImpl.h"
35#include "Channel.h"
36#include "ChannelMgr.h"
37#include "CharacterCache.h"
40#include "CharacterPackets.h"
41#include "CharmInfo.h"
42#include "Chat.h"
43#include "ChatPackets.h"
44#include "ChatTextBuilder.h"
45#include "CinematicMgr.h"
46#include "ClubUtils.h"
47#include "CombatLogPackets.h"
48#include "CombatPackets.h"
49#include "Common.h"
50#include "ConditionMgr.h"
51#include "Containers.h"
52#include "CreatureAI.h"
53#include "DB2Stores.h"
54#include "DatabaseEnv.h"
55#include "DisableMgr.h"
56#include "DuelPackets.h"
57#include "EquipmentSetPackets.h"
58#include "Formulas.h"
59#include "GameEventMgr.h"
60#include "GameEventSender.h"
61#include "GameObjectAI.h"
62#include "Garrison.h"
63#include "GarrisonMgr.h"
64#include "GitRevision.h"
65#include "GossipDef.h"
66#include "GridNotifiers.h"
67#include "GridNotifiersImpl.h"
68#include "Group.h"
69#include "GroupMgr.h"
70#include "GameTables.h"
71#include "GameTime.h"
72#include "Guild.h"
73#include "GuildMgr.h"
74#include "InstanceLockMgr.h"
75#include "InstancePackets.h"
76#include "InstanceScript.h"
77#include "ItemPackets.h"
78#include "Language.h"
79#include "LanguageMgr.h"
80#include "LFGMgr.h"
81#include "ListUtils.h"
82#include "Log.h"
83#include "Loot.h"
84#include "LootItemStorage.h"
85#include "LootMgr.h"
86#include "LootPackets.h"
87#include "Mail.h"
88#include "MailPackets.h"
89#include "MapManager.h"
90#include "MiscPackets.h"
91#include "MotionMaster.h"
92#include "MovementPackets.h"
93#include "ObjectAccessor.h"
94#include "ObjectMgr.h"
95#include "Opcodes.h"
96#include "OutdoorPvP.h"
97#include "OutdoorPvPMgr.h"
98#include "PartyPackets.h"
99#include "Pet.h"
100#include "PetPackets.h"
101#include "PoolMgr.h"
102#include "PetitionMgr.h"
103#include "PhasingHandler.h"
104#include "QueryCallback.h"
105#include "QueryHolder.h"
106#include "QuestDef.h"
108#include "QuestPackets.h"
109#include "RealmList.h"
110#include "ReputationMgr.h"
111#include "RestMgr.h"
112#include "Scenario.h"
113#include "SkillDiscovery.h"
114#include "SocialMgr.h"
115#include "Spell.h"
116#include "SpellAuraEffects.h"
117#include "SpellAuras.h"
118#include "SpellCastRequest.h"
119#include "SpellHistory.h"
120#include "SpellMgr.h"
121#include "SpellPackets.h"
122#include "StringConvert.h"
123#include "TalentPackets.h"
124#include "TerrainMgr.h"
125#include "ToyPackets.h"
126#include "TradeData.h"
127#include "TraitMgr.h"
128#include "TraitPacketsCommon.h"
129#include "Transport.h"
130#include "UpdateData.h"
131#include "Util.h"
132#include "Vehicle.h"
133#include "VehiclePackets.h"
134#include "Vignette.h"
135#include "VignettePackets.h"
136#include "World.h"
137#include "WorldPacket.h"
138#include "WorldSession.h"
139#include "WorldStateMgr.h"
140#include "WorldStatePackets.h"
141#include <boost/dynamic_bitset.hpp>
142#include <G3D/g3dmath.h>
143#include <sstream>
144
145#define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS)
146
147// corpse reclaim times
148#define DEATH_EXPIRE_STEP (5*MINUTE)
149#define MAX_DEATH_COUNT 3
150
152{
157
158static uint32 copseReclaimDelay[MAX_DEATH_COUNT] = { 30, 60, 120 };
159
160uint64 const MAX_MONEY_AMOUNT = 99999999999ULL;
161
162Player::Player(WorldSession* session) : Unit(true), m_sceneMgr(this)
163{
166
167 m_session = session;
168
169 m_modMeleeHitChance = 7.5f;
171 m_modSpellHitChance = 15.0f;
172
173 m_ingametime = 0;
174 m_sharedQuestId = 0;
175
176 m_ExtraFlags = 0;
177
178 m_spellModTakingSpell = nullptr;
179
180 // players always accept
181 if (!GetSession()->HasPermission(rbac::RBAC_PERM_CAN_FILTER_WHISPERS))
182 SetAcceptWhispers(true);
183
185 m_regenTimer = 0;
188
191
192 m_areaUpdateId = 0;
194
195 m_nextSave = sWorld->getIntConfig(CONFIG_INTERVAL_SAVE);
197
198 memset(m_items, 0, sizeof(Item*)*PLAYER_SLOTS_COUNT);
199
200 m_social = nullptr;
201
202 // group is initialized in the reference constructor
203 SetGroupInvite(nullptr);
205 m_bPassOnGroupLoot = false;
206
209
211
214
216 m_bCanDelayTeleport = false;
217 m_bHasDelayedTeleport = false;
220
221 m_trade = nullptr;
222
223 m_createTime = 0;
225 m_cinematic = 0;
226
227 m_movie = 0;
228
229 PlayerTalkClass = std::make_unique<PlayerMenu>(GetSession());
231
232 m_DailyQuestChanged = false;
234
238
240 m_drunkTimer = 0;
241 m_deathTimer = 0;
243
244 for (uint8 j = 0; j < PLAYER_MAX_BATTLEGROUND_QUEUES; ++j)
245 {
249 }
250
253 m_Played_time = { };
256 m_canParry = false;
257 m_canBlock = false;
258 m_canTitanGrip = false;
260
262 //cache for CreatedBySpell to allow
263 //returning reagents for temporarily removed pets
264 //when dying/logging out
265 m_oldpetspell = 0;
266 m_lastpetnumber = 0;
267
268 m_mailsUpdated = false;
269 unReadMails = 0;
271
273
275
277
279
280 m_HomebindTimer = 0;
281 m_InstanceValid = true;
285
286 m_lastPotionId = 0;
288
289 m_auraBaseFlatMod.fill(0.0f);
290 m_auraBasePctMod.fill(1.0f);
291 m_baseRatingValue = { };
292
294 m_baseManaRegen = 0;
297
298 // Honor System
300
301 m_IsBGRandomWinner = false;
302
303 // Player summoning
304 m_summon_expire = 0;
305
306 m_unitMovedByMe = this;
307 m_playerMovingMe = this;
308 m_seer = this;
309
311
313
314 m_isActive = true;
315
316 m_lastFallTime = 0;
317 m_lastFallZ = 0;
318
319 m_fishingSteps = 0;
320
322
323 sWorld->IncreasePlayerCount();
324
326
327 m_powerFraction.fill(0.0f);
328
329 isDebugAreaTriggers = false;
330
331 m_WeeklyQuestChanged = false;
332 m_MonthlyQuestChanged = false;
334
335 SetPendingBind(0, 0);
336
339 manaBeforeDuel = 0;
340
342
343 _cinematicMgr = std::make_unique<CinematicMgr>(this);
344
345 m_achievementMgr = std::make_unique<PlayerAchievementMgr>(this);
346 m_reputationMgr = std::make_unique<ReputationMgr>(this);
347 m_questObjectiveCriteriaMgr = std::make_unique<QuestObjectiveCriteriaMgr>(this);
348
349 for (uint8 i = 0; i < MAX_CUF_PROFILES; ++i)
350 _CUFProfiles[i] = nullptr;
351
353
354 _restMgr = std::make_unique<RestMgr>(this);
355
356 _usePvpItemLevels = false;
357}
358
360{
361 // it must be unloaded already in PlayerLogout and accessed only for logged in player
362 //m_social = nullptr;
363
364 // Note: buy back item already deleted from DB when player was saved
365 for (uint8 i = 0; i < PLAYER_SLOTS_COUNT; ++i)
366 delete m_items[i];
367
368 //all mailed items should be deleted, also all mail should be deallocated
369 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
370 delete *itr;
371
372 for (ItemMap::iterator iter = mMitems.begin(); iter != mMitems.end(); ++iter)
373 delete iter->second; //if item is duplicated... then server may crash ... but that item should be deallocated
374
375 for (size_t x = 0; x < ItemSetEff.size(); x++)
376 delete ItemSetEff[x];
377
378 for (uint8 i = 0; i < VOID_STORAGE_MAX_SLOT; ++i)
379 delete _voidStorageItems[i];
380
381 sWorld->DecreasePlayerCount();
382}
383
384void Player::CleanupsBeforeDelete(bool finalCleanup)
385{
386 TradeCancel(false);
388
389 Unit::CleanupsBeforeDelete(finalCleanup);
390}
391
393{
394 //FIXME: outfitId not used in player creating
396
397 Object::_Create(ObjectGuid::Create<HighGuid::Player>(guidlow));
398
399 m_name = createInfo->Name;
400
401 PlayerInfo const* info = sObjectMgr->GetPlayerInfo(createInfo->Race, createInfo->Class);
402 if (!info)
403 {
404 TC_LOG_ERROR("entities.player.cheat", "Player::Create: Possible hacking attempt: Account {} tried to create a character named '{}' with an invalid race/class pair ({}/{}) - refusing to do so.",
405 GetSession()->GetAccountId(), m_name, createInfo->Race, createInfo->Class);
406 return false;
407 }
408
409 for (uint8 i = 0; i < PLAYER_SLOTS_COUNT; i++)
410 m_items[i] = nullptr;
411
412 ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(createInfo->Class);
413 if (!cEntry)
414 {
415 TC_LOG_ERROR("entities.player.cheat", "Player::Create: Possible hacking attempt: Account {} tried to create a character named '{}' with an invalid character class ({}) - refusing to do so (wrong DBC-files?)",
416 GetSession()->GetAccountId(), m_name, createInfo->Class);
417 return false;
418 }
419
420 if (!GetSession()->ValidateAppearance(Races(createInfo->Race), Classes(createInfo->Class), Gender(createInfo->Sex), MakeChrCustomizationChoiceRange(createInfo->Customizations)))
421 {
422 TC_LOG_ERROR("entities.player.cheat", "Player::Create: Possible hacking-attempt: Account {} tried creating a character named '{}' with invalid appearance attributes - refusing to do so",
423 GetSession()->GetAccountId(), m_name);
424 return false;
425 }
426
427 PlayerInfo::CreatePosition const& position = createInfo->UseNPE && info->createPositionNPE ? *info->createPositionNPE : info->createPosition;
428
431
432 Relocate(position.Loc);
433
434 SetMap(sMapMgr->CreateMap(position.Loc.GetMapId(), this));
435
436 if (position.TransportGuid)
437 {
438 if (Transport* transport = ObjectAccessor::GetTransport(*this, ObjectGuid::Create<HighGuid::Transport>(*position.TransportGuid)))
439 {
440 transport->AddPassenger(this);
442 float x, y, z, o;
443 position.Loc.GetPosition(x, y, z, o);
444 transport->CalculatePassengerPosition(x, y, z, &o);
445 Relocate(x, y, z, o);
446 }
447 }
448
449 // set initial homebind position
450 SetHomebind(*this, GetAreaId());
451
452 uint8 powertype = cEntry->DisplayPower;
453
454 SetObjectScale(1.0f);
455
456 SetFactionForRace(createInfo->Race);
457
458 if (!IsValidGender(createInfo->Sex))
459 {
460 TC_LOG_ERROR("entities.player.cheat", "Player::Create: Possible hacking attempt: Account {} tried to create a character named '{}' with an invalid gender ({}) - refusing to do so",
461 GetSession()->GetAccountId(), m_name, createInfo->Sex);
462 return false;
463 }
464
465 SetRace(createInfo->Race);
466 SetClass(createInfo->Class);
467 SetGender(Gender(createInfo->Sex));
468 SetPowerType(Powers(powertype), false);
470 if (sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_PVP || sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_RPPVP)
471 {
474 }
475
477
479
481 SetRestState(REST_TYPE_XP, (GetSession()->IsARecruiter() || GetSession()->GetRecruiterId() != 0) ? REST_STATE_RAF_LINKED : REST_STATE_NORMAL);
483 SetNativeGender(Gender(createInfo->Sex));
485
486 // set starting level
487 SetLevel(GetStartLevel(createInfo->Race, createInfo->Class, createInfo->TemplateSet));
488
489 InitRunes();
490
492
493 // Played time
497
498 // base stats and related field values
503 InitPrimaryProfessions(); // to max set before any spell added
504
505 // apply original stats mods before spell loading or item equipment that call before equip _RemoveStatsMods()
506 UpdateMaxHealth(); // Update max Health (for add bonus from stamina)
509
510 // original spells
513
514 // Original action bar. Do not use Player::AddActionButton because we do not have skill spells loaded at this time
515 // but checks will still be performed later when loading character from db in Player::_LoadActions
516 for (PlayerCreateInfoActions::const_iterator action_itr = info->action.begin(); action_itr != info->action.end(); ++action_itr)
517 {
518 // create new button
519 ActionButton& ab = m_actionButtons[action_itr->button];
520
521 // set data
522 ab.SetActionAndType(action_itr->action, ActionButtonType(action_itr->type));
523 }
524
525 // original items
526 for (PlayerCreateInfoItem initialItem : info->item)
527 StoreNewItemInBestSlots(initialItem.item_id, initialItem.item_amount, info->itemContext);
528
529 // bags and main-hand weapon must equipped at this moment
530 // now second pass for not equipped (offhand weapon/shield if it attempt equipped before main-hand weapon)
531 // or ammo not equipped in special bag
533 for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; i++)
534 {
535 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
536 {
537 uint16 eDest;
538 // equip offhand weapon/shield if it attempt equipped before main-hand weapon
539 InventoryResult msg = CanEquipItem(NULL_SLOT, eDest, pItem, false);
540 if (msg == EQUIP_ERR_OK)
541 {
543 EquipItem(eDest, pItem, true);
544 }
545 // move other items to more appropriate slots
546 else
547 {
548 ItemPosCountVec sDest;
549 msg = CanStoreItem(NULL_BAG, NULL_SLOT, sDest, pItem, false);
550 if (msg == EQUIP_ERR_OK)
551 {
553 StoreItem(sDest, pItem, true);
554 }
555 }
556 }
557 }
558 // all item positions resolved
559
560 if (ChrSpecializationEntry const* defaultSpec = sDB2Manager.GetDefaultChrSpecializationForClass(GetClass()))
561 {
562 SetActiveTalentGroup(defaultSpec->OrderIndex);
563 SetPrimarySpecialization(defaultSpec->ID);
564 }
565
567
568 return true;
569}
570
572{
573 TC_LOG_DEBUG("entities.player.items", "Player::StoreNewItemInBestSlots: Player '{}' ({}) creates initial item (ItemID: {}, Count: {})",
574 GetName(), GetGUID().ToString(), itemId, amount);
575
576 // attempt equip by one
577 while (amount > 0)
578 {
579 uint16 eDest;
580 InventoryResult msg = CanEquipNewItem(NULL_SLOT, eDest, itemId, false);
581 if (msg != EQUIP_ERR_OK)
582 break;
583
584 EquipNewItem(eDest, itemId, context, true);
586 --amount;
587 }
588
589 if (amount == 0)
590 return true; // equipped
591
592 // attempt store
593 ItemPosCountVec sDest;
594 // store in main bag to simplify second pass (special bags can be not equipped yet at this moment)
595 InventoryResult msg = CanStoreNewItem(INVENTORY_SLOT_BAG_0, NULL_SLOT, sDest, itemId, amount);
596 if (msg == EQUIP_ERR_OK)
597 {
598 StoreNewItem(sDest, itemId, true, GenerateItemRandomBonusListId(itemId), GuidSet(), context);
599 return true; // stored
600 }
601
602 // item can't be added
603 TC_LOG_ERROR("entities.player.items", "Player::StoreNewItemInBestSlots: Player '{}' ({}) can't equip or store initial item (ItemID: {}, Race: {}, Class: {}, InventoryResult: {})",
604 GetName(), GetGUID().ToString(), itemId, GetRace(), GetClass(), msg);
605 return false;
606}
607
608void Player::SendMirrorTimer(MirrorTimerType Type, uint32 MaxValue, uint32 CurrentValue, int32 Regen)
609{
610 if (int(MaxValue) == DISABLED_MIRROR_TIMER)
611 {
612 if (int(CurrentValue) != DISABLED_MIRROR_TIMER)
614 return;
615 }
616
617 SendDirectMessage(WorldPackets::Misc::StartMirrorTimer(Type, CurrentValue, MaxValue, Regen, 0, false).Write());
618}
619
621{
624}
625
627{
628 // check for GM and death state included in isAttackableByAOE
629 return !isTargetableForAttack(false);
630}
631
633{
635 return 0;
636
638
639 // Absorb, resist some environmental damage type
640 uint32 absorb = 0;
641 uint32 resist = 0;
642 switch (type)
643 {
644 case DAMAGE_LAVA:
645 case DAMAGE_SLIME:
646 {
647 DamageInfo dmgInfo(this, this, damage, nullptr, type == DAMAGE_LAVA ? SPELL_SCHOOL_MASK_FIRE : SPELL_SCHOOL_MASK_NATURE, DIRECT_DAMAGE, BASE_ATTACK);
648 Unit::CalcAbsorbResist(dmgInfo);
649 absorb = dmgInfo.GetAbsorb();
650 resist = dmgInfo.GetResist();
651 damage = dmgInfo.GetDamage();
652 break;
653 }
654 default:
655 break;
656 }
657
658 Unit::DealDamageMods(nullptr, this, damage, &absorb);
659
661 packet.Victim = GetGUID();
662 packet.Type = type != DAMAGE_FALL_TO_VOID ? type : DAMAGE_FALL;
663 packet.Amount = damage;
664 packet.Absorbed = absorb;
665 packet.Resisted = resist;
666
667 uint32 final_damage = Unit::DealDamage(this, this, damage, nullptr, SELF_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, nullptr, false);
668
669 packet.LogData.Initialize(this);
670 SendCombatLogMessage(&packet);
671
672 if (!IsAlive())
673 {
674 if (type == DAMAGE_FALL) // DealDamage does not apply item durability loss from self-induced damage.
675 {
676 TC_LOG_DEBUG("entities.player", "Player::EnvironmentalDamage: Player '{}' ({}) fall to death, losing {}% durability",
679 // durability lost message
681 }
682
684 }
685
686 return final_damage;
687}
688
690{
691 switch (timer)
692 {
693 case FATIGUE_TIMER:
694 return MINUTE * IN_MILLISECONDS;
695 case BREATH_TIMER:
696 {
699
700 int32 UnderWaterTime = 3 * MINUTE * IN_MILLISECONDS;
702 return UnderWaterTime;
703 }
704 case FIRE_TIMER:
705 {
706 if (!IsAlive())
708 return 1 * IN_MILLISECONDS;
709 }
710 default:
711 return 0;
712 }
713}
714
716{
717 // Desync flags for update on next HandleDrowning
719 m_MirrorTimerFlagsLast = ~m_MirrorTimerFlags;
720}
721
723{
727}
728
730{
731 return m_MirrorTimer[type] == getMaxTimer(type);
732}
733
735{
737 return;
738
739 auto getEnvironmentalDamage = [&](EnviromentalDamage damageType)
740 {
741 uint8 damagePercent = 10;
742 if (damageType == DAMAGE_DROWNING || damageType == DAMAGE_EXHAUSTED)
743 damagePercent *= 2;
744
745 uint32 damage = GetMaxHealth() * damagePercent / 100;
746
747 // Randomize damage
748 damage += urand(0, pow(10, std::max(0, (int32)log10(damage) - 1)));
749
750 return damage;
751 };
752
753 // In water
755 {
756 // Breath timer not activated - activate it
758 {
761 }
762 else // If activated - do tick
763 {
764 m_MirrorTimer[BREATH_TIMER] -= time_diff;
765 // Timer limit - need deal damage
767 {
769 // Calculate and deal damage
770 uint32 damage = getEnvironmentalDamage(DAMAGE_DROWNING);
772 }
773 else if (!(m_MirrorTimerFlagsLast & UNDERWATER_INWATER)) // Update time in client if need
775 }
776 }
777 else if (m_MirrorTimer[BREATH_TIMER] != DISABLED_MIRROR_TIMER) // Regen timer
778 {
779 int32 UnderWaterTime = getMaxTimer(BREATH_TIMER);
780 // Need breath regen
781 m_MirrorTimer[BREATH_TIMER] += 10 * time_diff;
782 if (m_MirrorTimer[BREATH_TIMER] >= UnderWaterTime || !IsAlive())
786 }
787
788 // In dark water
790 {
791 // Fatigue timer not activated - activate it
793 {
796 }
797 else
798 {
799 m_MirrorTimer[FATIGUE_TIMER] -= time_diff;
800 // Timer limit - need deal damage or teleport ghost to graveyard
802 {
804 if (IsAlive()) // Calculate and deal damage
805 {
806 uint32 damage = getEnvironmentalDamage(DAMAGE_EXHAUSTED);
808 }
809 else if (HasPlayerFlag(PLAYER_FLAGS_GHOST)) // Teleport ghost to graveyard
811 }
814 }
815 }
816 else if (m_MirrorTimer[FATIGUE_TIMER] != DISABLED_MIRROR_TIMER) // Regen timer
817 {
818 int32 DarkWaterTime = getMaxTimer(FATIGUE_TIMER);
819 m_MirrorTimer[FATIGUE_TIMER] += 10 * time_diff;
820 if (m_MirrorTimer[FATIGUE_TIMER] >= DarkWaterTime || !IsAlive())
824 }
825
826 if (m_MirrorTimerFlags & (UNDERWATER_INLAVA /*| UNDERWATER_INSLIME*/) && !(_lastLiquid && _lastLiquid->SpellID))
827 {
828 // Breath timer not activated - activate it
831 else
832 {
833 m_MirrorTimer[FIRE_TIMER] -= time_diff;
834 if (m_MirrorTimer[FIRE_TIMER] < 0)
835 {
837 // Calculate and deal damage
838 uint32 damage = getEnvironmentalDamage(DAMAGE_LAVA);
841 // need to skip Slime damage in Undercity,
842 // maybe someone can find better way to handle environmental damage
843 //else if (m_zoneUpdateId != 1497)
844 // EnvironmentalDamage(DAMAGE_SLIME, damage);
845 }
846 }
847 }
848 else
850
851 // Recheck timers flag
852 m_MirrorTimerFlags &= ~UNDERWATER_EXIST_TIMERS;
853 for (uint8 i = 0; i < MAX_TIMERS; ++i)
854 {
856 {
858 break;
859 }
860 }
862}
863
866{
867 m_drunkTimer = 0;
868
869 uint8 currentDrunkValue = GetDrunkValue();
870 uint8 drunk = currentDrunkValue ? --currentDrunkValue : 0;
871 SetDrunkValue(drunk);
872}
873
875{
876 if (value >= 90)
877 return DRUNKEN_SMASHED;
878 if (value >= 50)
879 return DRUNKEN_DRUNK;
880 if (value)
881 return DRUNKEN_TIPSY;
882 return DRUNKEN_SOBER;
883}
884
885void Player::SetDrunkValue(uint8 newDrunkValue, uint32 itemId /*= 0*/)
886{
887 bool isSobering = newDrunkValue < GetDrunkValue();
889 if (newDrunkValue > 100)
890 newDrunkValue = 100;
891
892 // select drunk percent or total SPELL_AURA_MOD_FAKE_INEBRIATE amount, whichever is higher for visibility updates
893 int32 drunkPercent = std::max<int32>(newDrunkValue, GetTotalAuraModifier(SPELL_AURA_MOD_FAKE_INEBRIATE));
894 if (drunkPercent)
895 {
898 }
899 else if (!HasAuraType(SPELL_AURA_MOD_FAKE_INEBRIATE) && !newDrunkValue)
901
902 uint32 newDrunkenState = Player::GetDrunkenstateByValue(newDrunkValue);
905
906 if (!isSobering)
907 m_drunkTimer = 0; // reset sobering timer
908
909 if (newDrunkenState == oldDrunkenState)
910 return;
911
913 data.Guid = GetGUID();
914 data.Threshold = newDrunkenState;
915 data.ItemID = itemId;
916
917 SendMessageToSet(data.Write(), true);
918}
919
921{
922 if (!IsInWorld())
923 return;
924
925 // undelivered mail
927 {
928 SendNewMail();
929 ++unReadMails;
930
931 // It will be recalculate at mailbox open (for unReadMails important non-0 until mailbox open, it also will be recalculated)
933 }
934
935 // Update cinematic location, if 500ms have passed and we're doing a cinematic now.
936 _cinematicMgr->m_cinematicDiff += p_time;
937 if (_cinematicMgr->m_cinematicCamera && _cinematicMgr->m_activeCinematic && GetMSTimeDiffToNow(_cinematicMgr->m_lastCinematicCheck) > CINEMATIC_UPDATEDIFF)
938 {
939 _cinematicMgr->m_lastCinematicCheck = GameTime::GetGameTimeMS();
940 _cinematicMgr->UpdateCinematicLocation(p_time);
941 }
942
943 //used to implement delayed far teleport
945 Unit::Update(p_time);
946 SetCanDelayTeleport(false);
947
948 // Unit::Update updates the spell history and spell states. We can now check if we can launch another pending cast.
951
952 time_t now = GameTime::GetGameTime();
953
954 UpdatePvPFlag(now);
955
956 UpdateContestedPvP(p_time);
957
958 UpdateDuelFlag(now);
959
961
962 UpdateAfkReport(now);
963
964 if (GetCombatManager().HasPvPCombat())
966 if (!aura->IsPermanent())
967 aura->SetDuration(aura->GetSpellInfo()->GetMaxDuration());
968
969 Unit::AIUpdateTick(p_time);
970
971 // Update items that have just a limited lifetime
972 if (now > m_Last_tick)
974
975 // check every second
976 if (now > m_Last_tick + 1)
978
979 // If mute expired, remove it from the DB
980 if (GetSession()->m_muteTime && GetSession()->m_muteTime < now)
981 {
982 GetSession()->m_muteTime = 0;
984 stmt->setInt64(0, 0); // Set the mute time to 0
985 stmt->setString(1, "");
986 stmt->setString(2, "");
987 stmt->setUInt32(3, GetSession()->GetAccountId());
988 LoginDatabase.Execute(stmt);
989 }
990
991 if (!m_timedquests.empty())
992 {
993 QuestSet::iterator iter = m_timedquests.begin();
994 while (iter != m_timedquests.end())
995 {
996 QuestStatusData& q_status = m_QuestStatus[*iter];
997 if (q_status.Timer <= p_time)
998 {
999 uint32 quest_id = *iter;
1000 ++iter; // current iter will be removed in FailQuest
1001 FailQuest(quest_id);
1002 }
1003 else
1004 {
1005 q_status.Timer -= p_time;
1007 ++iter;
1008 }
1009 }
1010 }
1011
1012 m_achievementMgr->UpdateTimedCriteria(Milliseconds(p_time));
1013
1015
1017 _restMgr->Update(now);
1018
1019 if (m_weaponChangeTimer > 0)
1020 {
1021 if (p_time >= m_weaponChangeTimer)
1023 else
1024 m_weaponChangeTimer -= p_time;
1025 }
1026
1027 if (m_zoneUpdateTimer > 0)
1028 {
1029 if (p_time >= m_zoneUpdateTimer)
1030 {
1031 // On zone update tick check if we are still in an inn if we are supposed to be in one
1032 if (_restMgr->HasRestFlag(REST_FLAG_IN_TAVERN))
1033 {
1034 AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(_restMgr->GetInnTriggerID());
1035 if (!atEntry || !IsInAreaTrigger(atEntry))
1036 _restMgr->RemoveRestFlag(REST_FLAG_IN_TAVERN);
1037 }
1038
1039 uint32 newzone, newarea;
1040 GetZoneAndAreaId(newzone, newarea);
1041
1042 if (m_zoneUpdateId != newzone)
1043 UpdateZone(newzone, newarea); // also update area
1044 else
1045 {
1046 // use area updates as well
1047 // needed for free far all arenas for example
1048 if (m_areaUpdateId != newarea)
1049 UpdateArea(newarea);
1050
1052 }
1053 }
1054 else
1055 m_zoneUpdateTimer -= p_time;
1056 }
1057
1058 if (IsAlive())
1059 {
1060 m_regenTimer += p_time;
1061 RegenerateAll();
1062 }
1063
1064 if (m_deathState == JUST_DIED)
1065 KillPlayer();
1066
1067 if (m_nextSave > 0)
1068 {
1069 if (p_time >= m_nextSave)
1070 {
1071 // m_nextSave reset in SaveToDB call
1072 SaveToDB();
1073 TC_LOG_DEBUG("entities.player", "Player::Update: Player '{}' ({}) saved", GetName(), GetGUID().ToString());
1074 }
1075 else
1076 m_nextSave -= p_time;
1077 }
1078
1079 //Handle Water/drowning
1080 HandleDrowning(p_time);
1081
1082 // Played time
1083 if (now > m_Last_tick)
1084 {
1085 uint32 elapsed = uint32(now - m_Last_tick);
1086 m_Played_time[PLAYED_TIME_TOTAL] += elapsed; // Total played time
1087 m_Played_time[PLAYED_TIME_LEVEL] += elapsed; // Level played time
1088 m_Last_tick = now;
1089 }
1090
1091 if (GetDrunkValue())
1092 {
1093 m_drunkTimer += p_time;
1094 if (m_drunkTimer > 9 * IN_MILLISECONDS)
1096 }
1097
1098 if (HasPendingBind())
1099 {
1100 if (_pendingBindTimer <= p_time)
1101 {
1102 // Player left the instance
1105 SetPendingBind(0, 0);
1106 }
1107 else
1108 _pendingBindTimer -= p_time;
1109 }
1110
1111 // not auto-free ghost from body in instances
1112 if (m_deathTimer > 0 && !GetMap()->Instanceable() && !HasAuraType(SPELL_AURA_PREVENT_RESURRECTION))
1113 {
1114 if (p_time >= m_deathTimer)
1115 {
1116 m_deathTimer = 0;
1119 }
1120 else
1121 m_deathTimer -= p_time;
1122 }
1123
1124 UpdateEnchantTime(p_time);
1125 UpdateHomebindTime(p_time);
1126
1127 if (!_instanceResetTimes.empty())
1128 {
1129 for (InstanceTimeMap::iterator itr = _instanceResetTimes.begin(); itr != _instanceResetTimes.end();)
1130 {
1131 if (itr->second < now)
1132 _instanceResetTimes.erase(itr++);
1133 else
1134 ++itr;
1135 }
1136 }
1137
1138 Pet* pet = GetPet();
1139 if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityRange()) && !pet->isPossessed())
1140 //if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityDistance()) && (GetCharmGUID() && (pet->GetGUID() != GetCharmGUID())))
1141 RemovePet(pet, PET_SAVE_NOT_IN_SLOT, true);
1142
1143 if (IsAlive())
1144 {
1145 if (m_hostileReferenceCheckTimer <= p_time)
1146 {
1148 if (!GetMap()->IsDungeon())
1150 }
1151 else
1153 }
1154
1155 //we should execute delayed teleports only for alive(!) players
1156 //because we don't want player's ghost teleported from graveyard
1157 if (IsHasDelayedTeleport() && IsAlive())
1159}
1160
1162{
1164
1165 // Group update
1167
1168 // Indoor/Outdoor aura requirements
1170}
1171
1173{
1174 bool oldIsAlive = IsAlive();
1175
1176 if (s == JUST_DIED)
1177 {
1178 if (!oldIsAlive)
1179 {
1180 TC_LOG_ERROR("entities.player", "Player::setDeathState: Attempted to kill a dead player '{}' ({})", GetName(), GetGUID().ToString());
1181 return;
1182 }
1183
1184 // clear all pending spell cast requests when dying
1186
1187 // drunken state is cleared on death
1188 SetDrunkValue(0);
1190
1192
1193 //FIXME: is pet dismissed at dying or releasing spirit? if second, add setDeathState(DEAD) to HandleRepopRequest and define pet unsummon here with (s == DEAD)
1194 RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true);
1195
1197
1199
1203
1204 // reset all death criterias
1206 }
1207
1209
1210 if (IsAlive() && !oldIsAlive)
1211 //clear aura case after resurrection by another way (spells will be applied before next death)
1213}
1214
1216{
1217 if (isAFK())
1219 else
1221
1222 // afk player not allowed in battleground
1223 if (!IsGameMaster() && isAFK() && InBattleground() && !InArena())
1225}
1226
1228{
1229 if (isDND())
1231 else
1233}
1234
1236{
1237 uint16 tag = CHAT_FLAG_NONE;
1238
1239 if (isGMChat())
1240 tag |= CHAT_FLAG_GM;
1241 if (isDND())
1242 tag |= CHAT_FLAG_DND;
1243 if (isAFK())
1244 tag |= CHAT_FLAG_AFK;
1245 if (IsDeveloper())
1246 tag |= CHAT_FLAG_DEV;
1247 if (m_activePlayerData->TimerunningSeasonID)
1248 tag |= CHAT_FLAG_TIMERUNNING;
1249
1250 return tag;
1251}
1252
1253bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientation, TeleportToOptions options /*= TELE_TO_NONE*/, Optional<uint32> instanceId /*= {}*/)
1254{
1255 return TeleportTo({ .Location = WorldLocation(mapid, x, y, z, orientation), .InstanceId = instanceId }, options);
1256}
1257
1259{
1260 return TeleportTo({ .Location = loc, .InstanceId = instanceId }, options);
1261}
1262
1263bool Player::TeleportTo(TeleportLocation const& teleportLocation, TeleportToOptions options /*= TELE_TO_NONE*/)
1264{
1265 if (!MapManager::IsValidMapCoord(teleportLocation.Location))
1266 {
1267 TC_LOG_ERROR("maps", "Player::TeleportTo: Invalid map ({}) or invalid coordinates ({}) given when teleporting player '{}' ({}, MapID: {}, {}).",
1268 teleportLocation.Location.GetMapId(), teleportLocation.Location.ToString(), GetGUID().ToString(), GetName(), GetMapId(), GetPosition().ToString());
1269 return false;
1270 }
1271
1273 {
1274 TC_LOG_ERROR("entities.player.cheat", "Player::TeleportTo: Player '{}' ({}) tried to enter a forbidden map (MapID: {})", GetGUID().ToString(), GetName(), teleportLocation.Location.GetMapId());
1276 return false;
1277 }
1278
1279 // preparing unsummon pet if lost (we must get pet before teleportation or will not find it later)
1280 Pet* pet = GetPet();
1281
1282 MapEntry const* mEntry = sMapStore.LookupEntry(teleportLocation.Location.GetMapId());
1283
1284 // don't let enter battlegrounds without assigned battleground id (for example through areatrigger)...
1285 // don't let gm level > 1 either
1286 if (!InBattleground() && mEntry->IsBattlegroundOrArena())
1287 return false;
1288
1289 // client without expansion support
1290 if (GetSession()->GetExpansion() < mEntry->Expansion())
1291 {
1292 TC_LOG_DEBUG("maps", "Player '{}' ({}) using client without required expansion tried teleporting to non accessible map (MapID: {})",
1293 GetName(), GetGUID().ToString(), teleportLocation.Location.GetMapId());
1294
1295 if (TransportBase* transport = GetTransport())
1296 {
1297 transport->RemovePassenger(this);
1298 RepopAtGraveyard(); // teleport to near graveyard if on transport, looks blizz like :)
1299 }
1300
1302
1303 return false; // normal client can't teleport to this map...
1304 }
1305 else
1306 TC_LOG_DEBUG("maps", "Player {} ({}) is being teleported to map (MapID: {})", GetName(), GetGUID().ToString(), teleportLocation.Location.GetMapId());
1307
1308 if (m_vehicle)
1309 ExitVehicle();
1310
1311 // reset movement flags at teleport, because player will continue move with these flags after teleport
1314 DisableSpline();
1316
1317 if (TransportBase* transport = GetTransport())
1318 if (!teleportLocation.TransportGuid || teleportLocation.TransportGuid != transport->GetTransportGUID())
1319 if (!(options & TELE_TO_NOT_LEAVE_TRANSPORT))
1320 transport->RemovePassenger(this);
1321
1322 // The player was ported to another map and loses the duel immediately.
1323 // We have to perform this check before the teleport, otherwise the
1324 // ObjectAccessor won't find the flag.
1325 if (duel && GetMapId() != teleportLocation.Location.GetMapId() && GetMap()->GetGameObject(m_playerData->DuelArbiter))
1327
1328 if (GetMapId() == teleportLocation.Location.GetMapId() && (!teleportLocation.InstanceId || GetInstanceId() == teleportLocation.InstanceId))
1329 {
1330 //lets reset far teleport flag if it wasn't reset during chained teleport
1332 //setup delayed teleport flag
1334 //if teleport spell is cast in Unit::Update() func
1335 //then we need to delay it until update process will be finished
1337 {
1339 //lets save teleport destination for player
1340 m_teleport_dest = teleportLocation;
1341 m_teleport_options = options;
1342 return true;
1343 }
1344
1345 if (!(options & TELE_TO_NOT_UNSUMMON_PET))
1346 {
1347 //same map, only remove pet if out of range for new position
1348 if (pet && !pet->IsWithinDist3d(&teleportLocation.Location, GetMap()->GetVisibilityRange()))
1350 }
1351
1352 if (!IsAlive() && options & TELE_REVIVE_AT_TELEPORT)
1353 ResurrectPlayer(0.5f);
1354
1355 if (!(options & TELE_TO_NOT_LEAVE_COMBAT))
1356 CombatStop();
1357
1358 // this will be used instead of the current location in SaveToDB
1359 m_teleport_dest = teleportLocation;
1360 m_teleport_options = options;
1362
1363 // code for finish transfer called in WorldSession::HandleMovementOpcodes()
1364 // at client packet CMSG_MOVE_TELEPORT_ACK
1366 // near teleport, triggering send CMSG_MOVE_TELEPORT_ACK from client at landing
1367 if (!GetSession()->PlayerLogout())
1369 }
1370 else
1371 {
1372 if (GetClass() == CLASS_DEATH_KNIGHT && GetMapId() == 609 && !IsGameMaster() && !HasSpell(50977))
1373 {
1375 return false;
1376 }
1377
1378 // far teleport to another map
1379 Map* oldmap = IsInWorld() ? GetMap() : nullptr;
1380 // check if we can enter before stopping combat / removing pet / totems / interrupting spells
1381
1382 // Check enter rights before map getting to avoid creating instance copy for player
1383 // this check not dependent from map instance copy and same for all instance copies of selected map
1384 if (TransferAbortParams abortParams = Map::PlayerCannotEnter(teleportLocation.Location.GetMapId(), this))
1385 {
1386 SendTransferAborted(teleportLocation.Location.GetMapId(), abortParams.Reason, abortParams.Arg, abortParams.MapDifficultyXConditionId);
1387 return false;
1388 }
1389
1390 // Seamless teleport can happen only if cosmetic maps match
1391 if (!oldmap ||
1392 (oldmap->GetEntry()->CosmeticParentMapID != int32(teleportLocation.Location.GetMapId()) && int32(GetMapId()) != mEntry->CosmeticParentMapID &&
1393 !((oldmap->GetEntry()->CosmeticParentMapID != -1) ^ (oldmap->GetEntry()->CosmeticParentMapID != mEntry->CosmeticParentMapID))))
1394 options &= ~TELE_TO_SEAMLESS;
1395
1396 //lets reset near teleport flag if it wasn't reset during chained teleports
1398 //setup delayed teleport flag
1400 //if teleport spell is cast in Unit::Update() func
1401 //then we need to delay it until update process will be finished
1403 {
1405 //lets save teleport destination for player
1406 m_teleport_dest = teleportLocation;
1407 m_teleport_options = options;
1408 return true;
1409 }
1410
1412
1413 CombatStop();
1414
1416
1417 // remove player from battleground on far teleport (when changing maps)
1418 if (Battleground const* bg = GetBattleground())
1419 {
1420 // Note: at battleground join battleground id set before teleport
1421 // and we already will found "current" battleground
1422 // just need check that this is targeted map or leave
1423 if (bg->GetMapId() != teleportLocation.Location.GetMapId())
1424 LeaveBattleground(false); // don't teleport to entry point
1425 }
1426
1427 // remove arena spell coldowns/buffs now to also remove pet's cooldowns before it's temporarily unsummoned
1428 if (mEntry->IsBattleArena() && !IsGameMaster())
1429 {
1432 if (pet)
1433 pet->RemoveArenaAuras();
1434 }
1435
1436 // remove pet on map change
1437 if (pet)
1439
1440 // remove all dyn objects
1442
1443 // remove all areatriggers entities
1445
1446 // stop spellcasting
1447 // not attempt interrupt teleportation spell at caster teleport
1448 if (!(options & TELE_TO_SPELL))
1449 if (IsNonMeleeSpellCast(true))
1451
1452 //remove auras before removing from map...
1454
1455 if (!GetSession()->PlayerLogout() && !(options & TELE_TO_SEAMLESS))
1456 {
1457 // send transfer packets
1459 transferPending.MapID = teleportLocation.Location.GetMapId();
1460 transferPending.OldMapPosition = teleportLocation.Location.GetPosition();
1461 if (teleportLocation.TransportGuid.has_value())
1462 {
1463 transferPending.Ship.emplace();
1464 if (TransportSpawn const* transportSpawn = sTransportMgr->GetTransportSpawn(teleportLocation.TransportGuid->GetCounter()))
1465 {
1466 transferPending.Ship->ID = transportSpawn->TransportGameObjectId;
1467 if (dynamic_cast<Transport*>(GetTransport()))
1468 transferPending.Ship->OriginMapID = GetMapId();
1469 else
1470 transferPending.Ship->OriginMapID = -1;
1471 }
1472 }
1473
1474 SendDirectMessage(transferPending.Write());
1475
1478 }
1479
1480 // remove from old map now
1481 if (oldmap)
1482 oldmap->RemovePlayerFromMap(this, false);
1483
1484 m_teleport_dest = teleportLocation;
1485 m_teleport_options = options;
1487 // if the player is saved before worldportack (at logout for example)
1488 // this will be used instead of the current location in SaveToDB
1489
1490 if (!GetSession()->PlayerLogout())
1491 {
1493
1495 suspendToken.SequenceIndex = m_movementCounter; // not incrementing
1496 suspendToken.Reason = options & TELE_TO_SEAMLESS ? 2 : 1;
1497 SendDirectMessage(suspendToken.Write());
1498 }
1499
1500 // move packet sent by client always after far teleport
1501 // code for finish transfer to new map called in WorldSession::HandleMoveWorldportAckOpcode at client packet
1503 }
1504 return true;
1505}
1506
1508{
1510 return false;
1511
1515 return TeleportTo(m_bgData.joinPos);
1516}
1517
1519{
1520 if (m_DelayedOperations == 0)
1521 return;
1522
1525
1527 SaveToDB();
1528
1530 CastSpell(this, 26013, true); // Deserter
1531
1533 {
1534 if (m_bgData.mountSpell)
1535 {
1536 CastSpell(this, m_bgData.mountSpell, true);
1537 m_bgData.mountSpell = 0;
1538 }
1539 }
1540
1542 {
1543 if (m_bgData.HasTaxiPath())
1544 {
1548
1550 }
1551 }
1552
1554 {
1555 if (Group* g = GetGroup())
1556 g->SendUpdateToPlayer(GetGUID());
1557 }
1558
1559 //we have executed ALL delayed ops, so clear the flag
1561}
1562
1564{
1569
1570 for (uint8 i = PLAYER_SLOT_START; i < PLAYER_SLOT_END; ++i)
1571 if (m_items[i])
1572 m_items[i]->AddToWorld();
1573}
1574
1576{
1577 // cleanup
1578 if (IsInWorld())
1579 {
1587 m_lootRolls.clear();
1588 sOutdoorPvPMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId);
1589 sBattlefieldMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId);
1590 }
1591
1592 // Remove items from world before self - player must be found in Item::RemoveFromObjectUpdate
1593 for (uint8 i = PLAYER_SLOT_START; i < PLAYER_SLOT_END; ++i)
1594 if (m_items[i])
1596
1601
1602 for (ItemMap::iterator iter = mMitems.begin(); iter != mMitems.end(); ++iter)
1603 iter->second->RemoveFromWorld();
1604
1605 if (WorldObject* viewpoint = GetViewpoint())
1606 {
1607 TC_LOG_ERROR("entities.player", "Player::RemoveFromWorld: Player '{}' ({}) has viewpoint (Entry:{}, Type: {}) when removed from world",
1608 GetName(), GetGUID().ToString(), viewpoint->GetEntry(), viewpoint->GetTypeId());
1609 SetViewpoint(viewpoint, false);
1610 }
1611}
1612
1613void Player::SetObjectScale(float scale)
1614{
1615 Unit::SetObjectScale(scale);
1618 if (IsInWorld())
1620}
1621
1622bool Player::IsImmunedToSpellEffect(SpellInfo const* spellInfo, SpellEffectInfo const& spellEffectInfo, WorldObject const* caster,
1623 bool requireImmunityPurgesEffectAttribute /*= false*/) const
1624{
1625 // players are immune to taunt (the aura and the spell effect).
1626 if (spellEffectInfo.IsAura(SPELL_AURA_MOD_TAUNT))
1627 return true;
1628 if (spellEffectInfo.IsEffect(SPELL_EFFECT_ATTACK_ME))
1629 return true;
1630
1631 return Unit::IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster, requireImmunityPurgesEffectAttribute);
1632}
1633
1635{
1637
1638 for (Powers power = POWER_MANA; power < MAX_POWERS; power = Powers(power + 1))
1639 if (power != POWER_RUNES)
1640 Regenerate(power);
1641
1642 // Runes act as cooldowns, and they don't need to send any data
1644 {
1645 uint32 regeneratedRunes = 0;
1646 uint32 regenIndex = 0;
1647 while (regeneratedRunes < MAX_RECHARGING_RUNES && m_runes->CooldownOrder.size() > regenIndex)
1648 {
1649 uint8 runeToRegen = m_runes->CooldownOrder[regenIndex];
1650 uint32 runeCooldown = GetRuneCooldown(runeToRegen);
1651 if (runeCooldown > m_regenTimer)
1652 {
1653 SetRuneCooldown(runeToRegen, runeCooldown - m_regenTimer);
1654 ++regenIndex;
1655 }
1656 else
1657 SetRuneCooldown(runeToRegen, 0);
1658
1659 ++regeneratedRunes;
1660 }
1661 }
1662
1663 if (m_regenTimerCount >= 2000)
1664 {
1665 // Not in combat or they have regeneration
1668
1669 m_regenTimerCount -= 2000;
1670 }
1671
1672 m_regenTimer = 0;
1673}
1674
1676{
1677 // Skip regeneration for power type we cannot have
1678 uint32 powerIndex = GetPowerIndex(power);
1679 if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS)
1680 return;
1681
1684 return;
1685
1686 int32 curValue = GetPower(power);
1687
1688 // TODO: updating haste should update UnitData::PowerRegenFlatModifier for certain power types
1689 PowerTypeEntry const* powerType = sDB2Manager.GetPowerTypeEntry(power);
1690 if (!powerType)
1691 return;
1692
1693 float addvalue = 0.0f;
1694 if (!IsInCombat())
1695 {
1697 return;
1698
1699 addvalue = (powerType->RegenPeace + m_unitData->PowerRegenFlatModifier[powerIndex]) * 0.001f * m_regenTimer;
1700 }
1701 else
1702 addvalue = (powerType->RegenCombat + m_unitData->PowerRegenInterruptedFlatModifier[powerIndex]) * 0.001f * m_regenTimer;
1703
1704 static Rates const RatesForPower[MAX_POWERS] =
1705 {
1711 MAX_RATES, // runes
1716 MAX_RATES, // alternate
1720 MAX_RATES, // burning embers, unused
1721 MAX_RATES, // demonic fury, unused
1726 MAX_RATES, // runes
1727 MAX_RATES, // runes
1728 MAX_RATES, // runes
1729 MAX_RATES, // alternate
1730 MAX_RATES, // alternate
1731 MAX_RATES, // alternate
1732 };
1733
1734 if (RatesForPower[power] != MAX_RATES)
1735 addvalue *= sWorld->getRate(RatesForPower[power]);
1736
1737 // Mana regen calculated in Player::UpdateManaRegen()
1738 if (power != POWER_MANA)
1739 {
1741
1743 }
1744
1745 int32 minPower = powerType->MinPower;
1746 int32 maxPower = GetMaxPower(power);
1747
1748 if (powerType->CenterPower)
1749 {
1750 if (curValue > powerType->CenterPower)
1751 {
1752 addvalue = -std::abs(addvalue);
1753 minPower = powerType->CenterPower;
1754 }
1755 else if (curValue < powerType->CenterPower)
1756 {
1757 addvalue = std::abs(addvalue);
1758 maxPower = powerType->CenterPower;
1759 }
1760 else
1761 return;
1762 }
1763
1764 addvalue += m_powerFraction[powerIndex];
1765 int32 integerValue = int32(std::fabs(addvalue));
1766
1767 if (addvalue < 0.0f)
1768 {
1769 if (curValue <= minPower)
1770 return;
1771 }
1772 else if (addvalue > 0.0f)
1773 {
1774 if (curValue >= maxPower)
1775 return;
1776 }
1777 else
1778 return;
1779
1780 bool forcesSetPower = false;
1781 if (addvalue < 0.0f)
1782 {
1783 if (curValue > minPower + integerValue)
1784 {
1785 curValue -= integerValue;
1786 m_powerFraction[powerIndex] = addvalue + integerValue;
1787 }
1788 else
1789 {
1790 curValue = minPower;
1791 m_powerFraction[powerIndex] = 0;
1792 forcesSetPower = true;
1793 }
1794 }
1795 else
1796 {
1797 if (curValue + integerValue <= maxPower)
1798 {
1799 curValue += integerValue;
1800 m_powerFraction[powerIndex] = addvalue - integerValue;
1801 }
1802 else
1803 {
1804 curValue = maxPower;
1805 m_powerFraction[powerIndex] = 0;
1806 forcesSetPower = true;
1807 }
1808 }
1809
1811 curValue = maxPower;
1812
1813 if (m_regenTimerCount >= 2000 || forcesSetPower)
1814 SetPower(power, curValue);
1815 else
1816 {
1817 // throttle packet sending
1819 {
1820 SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Power, powerIndex), curValue);
1821 const_cast<UF::UnitData&>(*m_unitData).ClearChanged(&UF::UnitData::Power, powerIndex);
1822 });
1823 }
1824}
1825
1827{
1828 uint32 powerIndex = GetPowerIndex(power);
1829 if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS)
1830 return;
1831
1833 m_powerFraction[powerIndex] = 0.0f;
1835}
1836
1838{
1839 uint32 curValue = GetHealth();
1840 uint32 maxValue = GetMaxHealth();
1841
1842 if (curValue >= maxValue)
1843 return;
1844
1845 float HealthIncreaseRate = sWorld->getRate(RATE_HEALTH);
1846 float addValue = 0.0f;
1847
1848 // polymorphed case
1849 if (IsPolymorphed())
1850 addValue = float(GetMaxHealth()) / 3.0f;
1851 // normal regen case (maybe partly in combat case)
1853 {
1854 addValue = HealthIncreaseRate;
1855
1856 if (!IsInCombat())
1857 {
1858 if (GetLevel() < 15)
1859 addValue = (0.20f * ((float)GetMaxHealth()) / GetLevel() * HealthIncreaseRate);
1860 else
1861 addValue = 0.015f * ((float)GetMaxHealth()) * HealthIncreaseRate;
1862
1864
1865 addValue += GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * 0.4f;
1866 }
1869
1870 if (!IsStandState())
1871 addValue *= 1.5f;
1872 }
1873
1874 // always regeneration bonus (including combat)
1876 addValue += m_baseHealthRegen / 2.5f;
1877
1878 if (addValue < 0.0f)
1879 addValue = 0.0f;
1880
1881 ModifyHealth(int32(addValue));
1882}
1883
1885{
1886 SetFullHealth();
1887
1888 switch (GetPowerType())
1889 {
1890 case POWER_MANA:
1892 break;
1893 case POWER_RAGE:
1894 SetPower(POWER_RAGE, 0);
1895 break;
1896 case POWER_ENERGY:
1898 break;
1899 case POWER_RUNIC_POWER:
1901 break;
1902 case POWER_LUNAR_POWER:
1904 break;
1905 default:
1906 break;
1907 }
1908}
1909
1911{
1912 switch (questGiver->GetTypeId())
1913 {
1914 case TYPEID_UNIT:
1916 case TYPEID_GAMEOBJECT:
1917 return GetGameObjectIfCanInteractWith(questGiver->GetGUID(), GAMEOBJECT_TYPE_QUESTGIVER) != nullptr;
1918 case TYPEID_PLAYER:
1919 return IsAlive() && questGiver->ToPlayer()->IsAlive();
1920 case TYPEID_ITEM:
1921 return IsAlive();
1922 default:
1923 break;
1924 }
1925 return false;
1926}
1927
1929{
1930 // unit checks
1931 if (!guid)
1932 return nullptr;
1933
1934 if (!IsInWorld())
1935 return nullptr;
1936
1937 if (IsInFlight())
1938 return nullptr;
1939
1940 // exist (we need look pets also for some interaction (quest/etc)
1941 Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, guid);
1942 if (!creature)
1943 return nullptr;
1944
1945 // Deathstate checks
1947 return nullptr;
1948
1949 // alive or spirit healer
1951 return nullptr;
1952
1953 // appropriate npc type
1954 auto hasNpcFlags = [&]()
1955 {
1956 if (!npcFlags && !npcFlags2)
1957 return true;
1958 if (creature->HasNpcFlag(npcFlags))
1959 return true;
1960 if (creature->HasNpcFlag2(npcFlags2))
1961 return true;
1962 return false;
1963 };
1964 if (!hasNpcFlags())
1965 return nullptr;
1966
1967 // not allow interaction under control, but allow with own pets
1968 if (!creature->GetCharmerGUID().IsEmpty())
1969 return nullptr;
1970
1971 // not unfriendly/hostile
1972 if (!creature->IsInteractionAllowedWhileHostile() && creature->GetReactionTo(this) <= REP_UNFRIENDLY)
1973 return nullptr;
1974
1975 if (creature->IsInCombat() && !creature->IsInteractionAllowedInCombat())
1976 return nullptr;
1977
1978 // not too far, taken from CGGameUI::SetInteractTarget
1979 if (!creature->IsWithinDistInMap(this, creature->GetCombatReach() + 4.0f))
1980 return nullptr;
1981
1982 return creature;
1983}
1984
1986{
1987 if (!guid)
1988 return nullptr;
1989
1990 if (!IsInWorld())
1991 return nullptr;
1992
1993 if (IsInFlight())
1994 return nullptr;
1995
1996 // exist
1997 GameObject* go = ObjectAccessor::GetGameObject(*this, guid);
1998 if (!go)
1999 return nullptr;
2000
2001 // Players cannot interact with gameobjects that use the "Point" icon
2002 if (go->GetGOInfo()->IconName == "Point")
2003 return nullptr;
2004
2005 if (!go->IsWithinDistInMap(this))
2006 return nullptr;
2007
2008 return go;
2009}
2010
2012{
2014 if (!go)
2015 return nullptr;
2016
2017 if (go->GetGoType() != type)
2018 return nullptr;
2019
2020 return go;
2021}
2022
2023bool Player::IsInAreaTrigger(AreaTriggerEntry const* areaTrigger) const
2024{
2025 if (!areaTrigger)
2026 return false;
2027
2028 if (int32(GetMapId()) != areaTrigger->ContinentID && !GetPhaseShift().HasVisibleMapId(areaTrigger->ContinentID))
2029 return false;
2030
2031 if (areaTrigger->PhaseID || areaTrigger->PhaseGroupID || areaTrigger->PhaseUseFlags)
2032 if (!PhasingHandler::InDbPhaseShift(this, areaTrigger->PhaseUseFlags, areaTrigger->PhaseID, areaTrigger->PhaseGroupID))
2033 return false;
2034
2035 auto hasActionSetFlag = [=](AreaTriggerActionSetFlag flag)
2036 {
2037 if (AreaTriggerActionSetEntry const* areaTriggerActionSet = sAreaTriggerActionSetStore.LookupEntry(areaTrigger->AreaTriggerActionSetID))
2038 return areaTriggerActionSet->GetFlags().HasFlag(flag);
2039 return false;
2040 };
2041
2042 switch (getDeathState())
2043 {
2044 case DEAD:
2045 if (!hasActionSetFlag(AreaTriggerActionSetFlag::AllowWhileGhost))
2046 return false;
2047 break;
2048 case CORPSE:
2049 if (!hasActionSetFlag(AreaTriggerActionSetFlag::AllowWhileDead))
2050 return false;
2051 break;
2052 default:
2053 break;
2054 }
2055
2056 Position areaTriggerPos(areaTrigger->Pos.X, areaTrigger->Pos.Y, areaTrigger->Pos.Z, areaTrigger->BoxYaw);
2057 switch (areaTrigger->GetShapeType())
2058 {
2060 if (!IsInDist(&areaTriggerPos, areaTrigger->Radius))
2061 return false;
2062 break;
2064 if (!IsWithinBox(areaTriggerPos, areaTrigger->BoxLength / 2.f, areaTrigger->BoxWidth / 2.f, areaTrigger->BoxHeight / 2.f))
2065 return false;
2066 break;
2068 {
2069 AreaTriggerPolygon const* polygon = sObjectMgr->GetAreaTriggerPolygon(areaTrigger->ID);
2070 if (!polygon || (polygon->Height && GetPositionZ() > areaTrigger->Pos.Z + *polygon->Height) || !IsInPolygon2D(areaTriggerPos, polygon->Vertices))
2071 return false;
2072 break;
2073 }
2075 if (!IsWithinVerticalCylinder(areaTriggerPos, areaTrigger->Radius, areaTrigger->BoxHeight))
2076 return false;
2077 break;
2078 default:
2079 return false;
2080 }
2081
2082 return true;
2083}
2084
2086{
2087 if (on)
2088 {
2093
2094 if (Pet* pet = GetPet())
2095 pet->SetFaction(FACTION_FRIENDLY);
2096
2099
2101
2102 PhasingHandler::SetAlwaysVisible(this, true, false);
2104 }
2105 else
2106 {
2108
2109 m_ExtraFlags &= ~ PLAYER_EXTRA_GM_ON;
2113
2114 if (Pet* pet = GetPet())
2115 pet->SetFaction(GetFaction());
2116
2117 // restore FFA PvP Server state
2118 if (sWorld->IsFFAPvPRealm())
2120
2121 // restore FFA PvP area state, remove not allowed for GM mounts
2123
2125 }
2126
2128}
2129
2131{
2133}
2134
2136{
2137 if (on)
2138 {
2139 m_ExtraFlags &= ~PLAYER_EXTRA_GM_INVISIBLE; //remove flag
2141 }
2142 else
2143 {
2145
2146 SetAcceptWhispers(false);
2147 SetGameMaster(true);
2148
2150 }
2151
2152 for (Channel* channel : m_channels)
2153 channel->SetInvisible(this, !on);
2154}
2155
2157{
2158 switch (sWorld->getIntConfig(CONFIG_GROUP_VISIBILITY))
2159 {
2160 default: return IsInSameGroupWith(p);
2161 case 1: return IsInSameRaidWith(p);
2162 case 2: return GetEffectiveTeam() == p->GetEffectiveTeam();
2163 case 3: return false;
2164 }
2165}
2166
2168{
2169 return p == this || (GetGroup() != nullptr &&
2170 GetGroup() == p->GetGroup() &&
2171 GetGroup()->SameSubGroup(this, p));
2172}
2173
2175{
2176 return p == this || (GetGroup() != nullptr && GetGroup() == p->GetGroup());
2177}
2178
2181{
2182 Group* group = GetGroupInvite();
2183 if (!group)
2184 return;
2185
2186 group->RemoveInvite(this);
2187
2188 if (group->IsCreated())
2189 {
2190 if (group->GetMembersCount() <= 1) // group has just 1 member => disband
2191 group->Disband(true);
2192 }
2193 else
2194 {
2195 if (group->GetInviteeCount() <= 1)
2196 {
2197 group->RemoveAllInvites();
2198 delete group;
2199 }
2200 }
2201}
2202
2203void Player::RemoveFromGroup(Group* group, ObjectGuid guid, RemoveMethod method /* = GROUP_REMOVEMETHOD_DEFAULT*/, ObjectGuid kicker /* = ObjectGuid::Empty */, char const* reason /* = nullptr */)
2204{
2205 if (!group)
2206 return;
2207
2208 group->RemoveMember(guid, method, kicker, reason);
2209}
2210
2212{
2214
2215 int32 playerLevelDelta = 0;
2216
2217 // If XP < 50%, player should see scaling creature with -1 level except for level max
2218 if (GetLevel() < MAX_LEVEL && xp < uint32(*m_activePlayerData->NextLevelXP / 2))
2219 playerLevelDelta = -1;
2220
2222}
2223
2224void Player::GiveXP(uint32 xp, Unit* victim, float group_rate)
2225{
2226 if (xp < 1)
2227 return;
2228
2229 if (!IsAlive() && !GetBattlegroundId())
2230 return;
2231
2233 return;
2234
2235 if (victim && victim->GetTypeId() == TYPEID_UNIT && !victim->ToCreature()->hasLootRecipient())
2236 return;
2237
2238 uint8 level = GetLevel();
2239
2240 sScriptMgr->OnGivePlayerXP(this, xp, victim);
2241
2242 // XP to money conversion processed in Player::RewardQuest
2243 if (IsMaxLevel())
2244 return;
2245
2246 uint32 bonus_xp;
2247 bool recruitAFriend = GetsRecruitAFriendBonus(true);
2248
2249 // RaF does NOT stack with rested experience
2250 if (recruitAFriend)
2251 bonus_xp = 2 * xp; // xp + bonus_xp must add up to 3 * xp for RaF; calculation for quests done client-side
2252 else
2253 bonus_xp = victim ? _restMgr->GetRestBonusFor(REST_TYPE_XP, xp) : 0; // XP resting bonus
2254
2256 packet.Victim = victim ? victim->GetGUID() : ObjectGuid::Empty;
2257 packet.Original = xp + bonus_xp;
2259 packet.Amount = xp;
2260 packet.GroupBonus = group_rate;
2261 SendDirectMessage(packet.Write());
2262
2263 uint32 nextLvlXP = GetXPForNextLevel();
2264 uint32 newXP = GetXP() + xp + bonus_xp;
2265
2266 while (newXP >= nextLvlXP && !IsMaxLevel())
2267 {
2268 newXP -= nextLvlXP;
2269
2270 if (!IsMaxLevel())
2271 GiveLevel(level + 1);
2272
2273 level = GetLevel();
2274 nextLvlXP = GetXPForNextLevel();
2275 }
2276
2277 SetXP(newXP);
2278}
2279
2280// Update player to next level
2281// Current player experience not update (must be update by caller)
2283{
2284 uint8 oldLevel = GetLevel();
2285 if (level == oldLevel)
2286 return;
2287
2288 if (Guild* guild = GetGuild())
2289 guild->UpdateMemberData(this, GUILD_MEMBER_DATA_LEVEL, level);
2290
2291 PlayerLevelInfo info;
2292 sObjectMgr->GetPlayerLevelInfo(GetRace(), GetClass(), level, &info);
2293
2294 uint32 basemana = 0;
2295 sObjectMgr->GetPlayerClassLevelInfo(GetClass(), level, basemana);
2296
2298 packet.Level = level;
2299 packet.HealthDelta = 0;
2300
2302 // for (int i = 0; i < MAX_STORED_POWERS; ++i)
2303 packet.PowerDelta[0] = int32(basemana) - int32(GetCreateMana());
2304 packet.PowerDelta[1] = 0;
2305 packet.PowerDelta[2] = 0;
2306 packet.PowerDelta[3] = 0;
2307 packet.PowerDelta[4] = 0;
2308 packet.PowerDelta[5] = 0;
2309 packet.PowerDelta[6] = 0;
2310
2311 for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
2312 packet.StatDelta[i] = int32(info.stats[i]) - GetCreateStat(Stats(i));
2313
2315 packet.NumNewPvpTalentSlots = sDB2Manager.GetPvpTalentNumSlotsAtLevel(level, Classes(GetClass())) - sDB2Manager.GetPvpTalentNumSlotsAtLevel(oldLevel, Classes(GetClass()));
2316
2317 SendDirectMessage(packet.Write());
2318
2320
2321 //update level, max level of skills
2322 m_Played_time[PLAYED_TIME_LEVEL] = 0; // Level Played Time reset
2323
2325
2326 SetLevel(level);
2327
2331
2332 // save base values (bonuses already included in stored stats
2333 for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
2334 SetCreateStat(Stats(i), info.stats[i]);
2335
2336 SetCreateHealth(0);
2337 SetCreateMana(basemana);
2338
2341
2343
2344 _ApplyAllLevelScaleItemMods(true); // Moved to above SetFullHealth so player will have full health from Heirlooms
2345
2347 if (Item* artifact = GetItemByGuid(artifactAura->GetCastItemGUID()))
2348 artifact->CheckArtifactRelicSlotUnlock(this);
2349
2350 // Only health and mana are set to maximum.
2351 SetFullHealth();
2352 for (PowerTypeEntry const* powerType : sPowerTypeStore)
2353 if (powerType->GetFlags().HasFlag(PowerTypeFlags::SetToMaxOnLevelUp))
2354 SetFullPower(Powers(powerType->PowerTypeEnum));
2355
2356 // update level to hunter/summon pet
2357 if (Pet* pet = GetPet())
2358 pet->SynchronizeLevelWithOwner();
2359
2360 if (MailLevelReward const* mailReward = sObjectMgr->GetMailLevelReward(level, GetRace()))
2361 {
2363 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
2364 MailDraft(mailReward->mailTemplateId).SendMailTo(trans, this, MailSender(MAIL_CREATURE, uint64(mailReward->senderEntry)));
2365 CharacterDatabase.CommitTransaction(trans);
2366 }
2367
2371 if (level > oldLevel)
2372 UpdateCriteria(CriteriaType::GainLevels, level - oldLevel);
2373
2374 PushQuests();
2375
2376 sScriptMgr->OnPlayerLevelChanged(this, oldLevel);
2377}
2378
2380{
2381 return GetLevel() >= m_activePlayerData->MaxLevel;
2382}
2383
2385{
2386 uint8 level = GetLevel();
2387 // talents base at level diff (talents = level - 9 but some can be used already)
2388 if (level < MIN_SPECIALIZATION_LEVEL)
2390
2391 int32 talentTiers = DB2Manager::GetNumTalentsAtLevel(level, Classes(GetClass()));
2392 if (level < 15)
2393 {
2394 // Remove all talent points
2395 ResetTalents(true);
2396 }
2397 else
2398 {
2400 for (int32 t = talentTiers; t < MAX_TALENT_TIERS; ++t)
2401 for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c)
2402 for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(GetClass(), t, c))
2403 RemoveTalent(talent);
2404 }
2405
2407
2409 for (uint8 spec = 0; spec < MAX_SPECIALIZATIONS; ++spec)
2410 for (size_t slot = sDB2Manager.GetPvpTalentNumSlotsAtLevel(level, Classes(GetClass())); slot < MAX_PVP_TALENT_SLOTS; ++slot)
2411 if (PvpTalentEntry const* pvpTalent = sPvpTalentStore.LookupEntry(GetPvpTalentMap(spec)[slot]))
2412 RemovePvpTalent(pvpTalent, spec);
2413
2414 if (!GetSession()->PlayerLoading())
2415 SendTalentsInfoData(); // update at client
2416}
2417
2418void Player::InitStatsForLevel(bool reapplyMods)
2419{
2420 if (reapplyMods) //reapply stats values only on .reset stats (level) command
2422
2423 uint32 basemana = 0;
2424 sObjectMgr->GetPlayerClassLevelInfo(GetClass(), GetLevel(), basemana);
2425
2426 PlayerLevelInfo info;
2427 sObjectMgr->GetPlayerLevelInfo(GetRace(), GetClass(), GetLevel(), &info);
2428
2429 uint8 exp_max_lvl = GetMaxLevelForExpansion(GetSession()->GetExpansion());
2430 uint8 conf_max_lvl = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
2431 if (exp_max_lvl == DEFAULT_MAX_LEVEL || exp_max_lvl >= conf_max_lvl)
2433 else
2436 if (m_activePlayerData->XP >= m_activePlayerData->NextLevelXP)
2438
2439 // reset before any aura state sources (health set/aura apply)
2441
2443
2444 // set default cast time multiplier
2445 SetModCastingSpeed(1.0f);
2446 SetModSpellHaste(1.0f);
2447 SetModHaste(1.0f);
2448 SetModRangedHaste(1.0f);
2449 SetModHasteRegen(1.0f);
2450 SetModTimeRate(1.0f);
2452
2453 // reset size before reapply auras
2454 SetObjectScale(1.0f);
2455
2456 // save base values (bonuses already included in stored stats
2457 for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
2458 SetCreateStat(Stats(i), info.stats[i]);
2459
2460 for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
2461 SetStat(Stats(i), info.stats[i]);
2462
2463 SetCreateHealth(0);
2464
2465 //set create powers
2466 SetCreateMana(basemana);
2467
2469
2471
2472 //reset rating fields values
2473 for (uint16 index = 0; index < MAX_COMBAT_RATING; ++index)
2475
2479 for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
2480 {
2485 }
2486
2488
2489 //reset attack power, damage and attack speed fields
2490 for (uint8 i = BASE_ATTACK; i < MAX_ATTACK; ++i)
2492
2499 for (uint16 i = 0; i < 3; ++i)
2500 {
2503 }
2504
2505 SetAttackPower(0);
2509
2510 // Base crit values (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset
2514
2515 // Init spell schools (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset
2517
2520
2522
2523 // Dodge percentage
2525
2526 // set armor (resistance 0) to original value (create_agility*2)
2529 // set other resistance to original value (0)
2530 for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
2531 {
2534 }
2535
2538 for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
2540
2541 // Reset no reagent cost field
2543
2544 // Init data for form but skip reapply item mods for form
2545 InitDataForForm(reapplyMods);
2546
2547 // save new stats
2548 for (uint8 i = POWER_MANA; i < MAX_POWERS; ++i)
2550
2551 SetMaxHealth(0); // stamina bonus will applied later
2552
2553 // cleanup mounted state (it will set correctly at aura loading if player saved at mount.
2555
2556 // cleanup unit flags (will be re-applied if need at aura load).
2565
2567
2568 // cleanup player flags (will be re-applied if need at aura load), to avoid have ghost flag without ghost aura, for example.
2570
2571 RemoveVisFlag(UNIT_VIS_FLAGS_ALL); // one form stealth modified bytes
2573
2574 // restore if need some important flags
2577
2578 if (reapplyMods) // reapply stats values only on .reset stats (level) command
2580
2581 // set current level health and mana/energy to maximum after applying all mods.
2582 SetFullHealth();
2589
2590 // update level to hunter/summon pet
2591 if (Pet* pet = GetPet())
2592 pet->SynchronizeLevelWithOwner();
2593}
2594
2596{
2598 knownSpells.InitialLogin = IsLoading();
2599
2600 knownSpells.KnownSpells.reserve(m_spells.size());
2601 for (PlayerSpellMap::value_type const& spell : m_spells)
2602 {
2603 if (spell.second.state == PLAYERSPELL_REMOVED)
2604 continue;
2605
2606 if (!spell.second.active || spell.second.disabled)
2607 continue;
2608
2609 knownSpells.KnownSpells.push_back(spell.first);
2610 if (spell.second.favorite)
2611 knownSpells.FavoriteSpells.push_back(spell.first);
2612 }
2613
2614 SendDirectMessage(knownSpells.Write());
2615}
2616
2618{
2620 SendDirectMessage(sendUnlearnSpells.Write());
2621}
2622
2624{
2625 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
2626 {
2627 if ((*itr)->messageID == id)
2628 {
2629 //do not delete item, because Player::removeMail() is called when returning mail to sender.
2630 m_mail.erase(itr);
2631 return;
2632 }
2633 }
2634}
2635
2636void Player::SendMailResult(uint64 mailId, MailResponseType mailAction, MailResponseResult mailError, uint32 equipError, ObjectGuid::LowType itemGuid, uint32 itemCount) const
2637{
2639
2640 result.MailID = mailId;
2641 result.Command = mailAction;
2642 result.ErrorCode = mailError;
2643
2644 if (mailError == MAIL_ERR_EQUIP_ERROR)
2645 result.BagResult = equipError;
2646 else if (mailAction == MAIL_ITEM_TAKEN)
2647 {
2648 result.AttachID = itemGuid;
2649 result.QtyInInventory = itemCount;
2650 }
2651 SendDirectMessage(result.Write());
2652}
2653
2655{
2656 // deliver undelivered mail
2658 notify.Delay = 0.0f;
2659
2660 SendDirectMessage(notify.Write());
2661}
2662
2664{
2665 // calculate next delivery time (min. from non-delivered mails
2666 // and recalculate unReadMail
2667 time_t cTime = GameTime::GetGameTime();
2669 unReadMails = 0;
2670 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
2671 {
2672 if ((*itr)->deliver_time > cTime)
2673 {
2674 if (!m_nextMailDelivereTime || m_nextMailDelivereTime > (*itr)->deliver_time)
2675 m_nextMailDelivereTime = (*itr)->deliver_time;
2676 }
2677 else if (((*itr)->checked & MAIL_CHECK_MASK_READ) == 0)
2678 ++unReadMails;
2679 }
2680}
2681
2682void Player::AddNewMailDeliverTime(time_t deliver_time)
2683{
2684 if (deliver_time <= GameTime::GetGameTime()) // ready now
2685 {
2686 ++unReadMails;
2687 SendNewMail();
2688 }
2689 else // not ready and no have ready mails
2690 {
2691 if (!m_nextMailDelivereTime || m_nextMailDelivereTime > deliver_time)
2692 m_nextMailDelivereTime = deliver_time;
2693 }
2694}
2695
2697{
2699 stmt->setUInt32(0, spellId);
2700 CharacterDatabase.Execute(stmt);
2701}
2702
2703bool Player::AddTalent(TalentEntry const* talent, uint8 spec, bool learning)
2704{
2705 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE);
2706 if (!spellInfo)
2707 {
2708 TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) does not exist.", talent->SpellID);
2709 return false;
2710 }
2711
2712 if (!SpellMgr::IsSpellValid(spellInfo, this, false))
2713 {
2714 TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) is invalid", talent->SpellID);
2715 return false;
2716 }
2717
2718 PlayerTalentMap::iterator itr = GetTalentMap(spec)->find(talent->ID);
2719 if (itr != GetTalentMap(spec)->end())
2720 itr->second = PLAYERSPELL_UNCHANGED;
2721 else
2722 (*GetTalentMap(spec))[talent->ID] = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
2723
2724 if (spec == GetActiveTalentGroup())
2725 {
2726 LearnSpell(talent->SpellID, true);
2727 if (talent->OverridesSpellID)
2728 AddOverrideSpell(talent->OverridesSpellID, talent->SpellID);
2729 }
2730
2731 if (learning)
2733
2734 return true;
2735}
2736
2738{
2739 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE);
2740 if (!spellInfo)
2741 return;
2742
2743 RemoveSpell(talent->SpellID, true);
2744
2745 // search for spells that the talent teaches and unlearn them
2746 for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
2747 if (spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL) && spellEffectInfo.TriggerSpell > 0)
2748 RemoveSpell(spellEffectInfo.TriggerSpell, true);
2749
2750 if (talent->OverridesSpellID)
2752
2753 // if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted
2754 PlayerTalentMap::iterator plrTalent = GetTalentMap(GetActiveTalentGroup())->find(talent->ID);
2755 if (plrTalent != GetTalentMap(GetActiveTalentGroup())->end())
2756 plrTalent->second = PLAYERSPELL_REMOVED;
2757}
2758
2760{
2762 storedLocation.Loc.WorldRelocate(this);
2764}
2765
2767{
2769 storedLocation->State = StoredAuraTeleportLocation::DELETED;
2770}
2771
2773{
2775 return &auraLocation->Loc;
2776
2777 return nullptr;
2778}
2779
2780bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading /*= false*/, int32 fromSkill /*= 0*/, bool favorite /*= false*/, Optional<int32> traitDefinitionId /*= {}*/)
2781{
2782 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE);
2783 if (!spellInfo)
2784 {
2785 // do character spell book cleanup (all characters)
2786 if (!IsInWorld() && !learning) // spell load case
2787 {
2788 TC_LOG_ERROR("spells", "Player::AddSpell: Spell (ID: {}) does not exist. deleting for all characters in `character_spell`.", spellId);
2789
2791 }
2792 else
2793 TC_LOG_ERROR("spells", "Player::AddSpell: Spell (ID: {}) does not exist", spellId);
2794
2795 return false;
2796 }
2797
2798 if (!SpellMgr::IsSpellValid(spellInfo, this, false))
2799 {
2800 // do character spell book cleanup (all characters)
2801 if (!IsInWorld() && !learning) // spell load case
2802 {
2803 TC_LOG_ERROR("spells", "Player::AddSpell: Spell (ID: {}) is invalid. deleting for all characters in `character_spell`.", spellId);
2804
2806 }
2807 else
2808 TC_LOG_ERROR("spells", "Player::AddSpell: Spell (ID: {}) is invalid", spellId);
2809
2810 return false;
2811 }
2812
2814
2815 bool dependent_set = false;
2816 bool disabled_case = false;
2817 bool superceded_old = false;
2818
2819 PlayerSpellMap::iterator itr = m_spells.find(spellId);
2820
2821 // Remove temporary spell if found to prevent conflicts
2822 if (itr != m_spells.end() && itr->second.state == PLAYERSPELL_TEMPORARY)
2823 RemoveTemporarySpell(spellId);
2824 else if (itr != m_spells.end())
2825 {
2826 uint32 next_active_spell_id = 0;
2827 // fix activate state for non-stackable low rank (and find next spell for !active case)
2828 if (spellInfo->IsRanked())
2829 {
2830 if (uint32 next = sSpellMgr->GetNextSpellInChain(spellId))
2831 {
2832 if (HasSpell(next))
2833 {
2834 // high rank already known so this must !active
2835 active = false;
2836 next_active_spell_id = next;
2837 }
2838 }
2839 }
2840
2841 // not do anything if already known in expected state
2842 if (itr->second.state != PLAYERSPELL_REMOVED && itr->second.active == active &&
2843 itr->second.dependent == dependent && itr->second.disabled == disabled)
2844 {
2845 if (!IsInWorld() && !learning) // explicitly load from DB and then exist in it already and set correctly
2846 itr->second.state = PLAYERSPELL_UNCHANGED;
2847
2848 return false;
2849 }
2850
2851 // dependent spell known as not dependent, overwrite state
2852 if (itr->second.state != PLAYERSPELL_REMOVED && !itr->second.dependent && dependent)
2853 {
2854 itr->second.dependent = dependent;
2855 if (itr->second.state != PLAYERSPELL_NEW)
2856 itr->second.state = PLAYERSPELL_CHANGED;
2857 dependent_set = true;
2858 }
2859
2860 if (itr->second.TraitDefinitionId != traitDefinitionId)
2861 {
2862 if (itr->second.TraitDefinitionId)
2863 if (TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(*itr->second.TraitDefinitionId))
2864 RemoveOverrideSpell(traitDefinition->OverridesSpellID, spellId);
2865
2866 itr->second.TraitDefinitionId = traitDefinitionId;
2867 }
2868
2869 itr->second.favorite = favorite;
2870
2871 // update active state for known spell
2872 if (itr->second.active != active && itr->second.state != PLAYERSPELL_REMOVED && !itr->second.disabled)
2873 {
2874 itr->second.active = active;
2875
2876 if (!IsInWorld() && !learning && !dependent_set) // explicitly load from DB and then exist in it already and set correctly
2877 itr->second.state = PLAYERSPELL_UNCHANGED;
2878 else if (itr->second.state != PLAYERSPELL_NEW)
2879 itr->second.state = PLAYERSPELL_CHANGED;
2880
2881 if (active)
2882 {
2883 if (spellInfo->IsPassive() && HandlePassiveSpellLearn(spellInfo))
2884 CastSpell(this, spellId, true);
2885 }
2886 else if (IsInWorld())
2887 {
2888 if (next_active_spell_id)
2889 SendSupercededSpell(spellId, next_active_spell_id);
2890 else
2891 {
2893 unlearnedSpells.SpellID.push_back(spellId);
2894 SendDirectMessage(unlearnedSpells.Write());
2895 }
2896 }
2897
2898 return active; // learn (show in spell book if active now)
2899 }
2900
2901 if (itr->second.disabled != disabled && itr->second.state != PLAYERSPELL_REMOVED)
2902 {
2903 if (itr->second.state != PLAYERSPELL_NEW)
2904 itr->second.state = PLAYERSPELL_CHANGED;
2905 itr->second.disabled = disabled;
2906
2907 if (disabled)
2908 return false;
2909
2910 disabled_case = true;
2911 }
2912 else switch (itr->second.state)
2913 {
2914 case PLAYERSPELL_UNCHANGED: // known saved spell
2915 return false;
2916 case PLAYERSPELL_REMOVED: // re-learning removed not saved spell
2917 {
2918 m_spells.erase(itr);
2919 state = PLAYERSPELL_CHANGED;
2920 break; // need re-add
2921 }
2922 default: // known not saved yet spell (new or modified)
2923 {
2924 // can be in case spell loading but learned at some previous spell loading
2925 if (!IsInWorld() && !learning && !dependent_set)
2926 itr->second.state = PLAYERSPELL_UNCHANGED;
2927
2928 return false;
2929 }
2930 }
2931 }
2932
2933 if (!disabled_case) // skip new spell adding if spell already known (disabled spells case)
2934 {
2935 // non talent spell: learn low ranks (recursive call)
2936 if (uint32 prev_spell = sSpellMgr->GetPrevSpellInChain(spellId))
2937 {
2938 if (!IsInWorld() || disabled) // at spells loading, no output, but allow save
2939 AddSpell(prev_spell, active, true, true, disabled, false, fromSkill);
2940 else // at normal learning
2941 LearnSpell(prev_spell, true, fromSkill);
2942 }
2943
2944 std::pair<PlayerSpellMap::iterator, bool> inserted = m_spells.emplace(std::piecewise_construct, std::forward_as_tuple(spellId), std::forward_as_tuple());
2945 PlayerSpell& newspell = inserted.first->second;
2946 // learning a previous rank might have given us this spell already from a skill autolearn, most likely with PLAYERSPELL_NEW state
2947 // we dont want to do double insert if this happened during load from db so we force state to CHANGED, just in case
2948 newspell.state = inserted.second ? state : PLAYERSPELL_CHANGED;
2949 newspell.active = active;
2950 newspell.dependent = dependent;
2951 newspell.disabled = disabled;
2952 newspell.favorite = favorite;
2953 if (traitDefinitionId)
2954 newspell.TraitDefinitionId = *traitDefinitionId;
2955
2956 // replace spells in action bars and spellbook to bigger rank if only one spell rank must be accessible
2957 if (newspell.active && !newspell.disabled && spellInfo->IsRanked())
2958 {
2959 for (PlayerSpellMap::iterator itr2 = m_spells.begin(); itr2 != m_spells.end(); ++itr2)
2960 {
2961 if (itr2->second.state == PLAYERSPELL_REMOVED)
2962 continue;
2963
2964 SpellInfo const* i_spellInfo = sSpellMgr->GetSpellInfo(itr2->first, DIFFICULTY_NONE);
2965 if (!i_spellInfo)
2966 continue;
2967
2968 if (spellInfo->IsDifferentRankOf(i_spellInfo))
2969 {
2970 if (itr2->second.active)
2971 {
2972 if (spellInfo->IsHighRankOf(i_spellInfo))
2973 {
2974 if (IsInWorld()) // not send spell (re-/over-)learn packets at loading
2975 SendSupercededSpell(itr2->first, spellId);
2976
2977 // mark old spell as disable (SMSG_SUPERCEDED_SPELL replace it in client by new)
2978 itr2->second.active = false;
2979 if (itr2->second.state != PLAYERSPELL_NEW)
2980 itr2->second.state = PLAYERSPELL_CHANGED;
2981 superceded_old = true; // new spell replace old in action bars and spell book.
2982 }
2983 else
2984 {
2985 if (IsInWorld()) // not send spell (re-/over-)learn packets at loading
2986 SendSupercededSpell(spellId, itr2->first);
2987
2988 // mark new spell as disable (not learned yet for client and will not learned)
2989 newspell.active = false;
2990 if (newspell.state != PLAYERSPELL_NEW)
2991 newspell.state = PLAYERSPELL_CHANGED;
2992 }
2993 }
2994 }
2995 }
2996 }
2997
2998 // return false if spell disabled
2999 if (newspell.disabled)
3000 return false;
3001 }
3002
3003 bool castSpell = false;
3004
3005 // cast talents with SPELL_EFFECT_LEARN_SPELL (other dependent spells will learned later as not auto-learned)
3006 // note: all spells with SPELL_EFFECT_LEARN_SPELL isn't passive
3007 if (!loading && spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT) && spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL))
3008 // ignore stance requirement for talent learn spell (stance set for spell only for client spell description show)
3009 castSpell = true;
3010 // also cast passive spells (including all talents without SPELL_EFFECT_LEARN_SPELL) with additional checks
3011 else if (spellInfo->IsPassive())
3012 castSpell = HandlePassiveSpellLearn(spellInfo);
3013 else if (spellInfo->HasEffect(SPELL_EFFECT_SKILL_STEP))
3014 castSpell = true;
3015 else if (spellInfo->HasAttribute(SPELL_ATTR1_CAST_WHEN_LEARNED))
3016 castSpell = true;
3017
3018 if (castSpell)
3019 {
3020 CastSpellExtraArgs args;
3022
3023 if (traitDefinitionId)
3024 {
3025 if (UF::TraitConfig const* traitConfig = GetTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID))
3026 {
3027 int32 traitEntryIndex = traitConfig->Entries.FindIndexIf([traitDefinitionId](UF::TraitEntry const& traitEntry)
3028 {
3029 return sTraitNodeEntryStore.AssertEntry(traitEntry.TraitNodeEntryID)->TraitDefinitionID == traitDefinitionId;
3030 });
3031 int32 rank = 0;
3032 if (traitEntryIndex >= 0)
3033 rank = traitConfig->Entries[traitEntryIndex].Rank + traitConfig->Entries[traitEntryIndex].GrantedRanks;
3034
3035 if (rank > 0)
3036 {
3037 if (std::vector<TraitDefinitionEffectPointsEntry const*> const* traitDefinitionEffectPoints = TraitMgr::GetTraitDefinitionEffectPointModifiers(*traitDefinitionId))
3038 {
3039 for (TraitDefinitionEffectPointsEntry const* traitDefinitionEffectPoint : *traitDefinitionEffectPoints)
3040 {
3041 if (traitDefinitionEffectPoint->EffectIndex >= int32(spellInfo->GetEffects().size()))
3042 continue;
3043
3044 float basePoints = sDB2Manager.GetCurveValueAt(traitDefinitionEffectPoint->CurveID, rank);
3045 if (traitDefinitionEffectPoint->GetOperationType() == TraitPointsOperationType::Multiply)
3046 basePoints *= spellInfo->GetEffect(SpellEffIndex(traitDefinitionEffectPoint->EffectIndex)).CalcBaseValue(this, nullptr, 0, -1);
3047
3048 args.AddSpellMod(SpellValueMod(SPELLVALUE_BASE_POINT0 + traitDefinitionEffectPoint->EffectIndex), basePoints);
3049 }
3050 }
3051 }
3052 }
3053 }
3054
3055 CastSpell(this, spellId, args);
3056 if (spellInfo->HasEffect(SPELL_EFFECT_SKILL_STEP))
3057 return false;
3058 }
3059
3060 if (traitDefinitionId)
3061 if (TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(*traitDefinitionId))
3062 if (traitDefinition->OverridesSpellID)
3063 AddOverrideSpell(traitDefinition->OverridesSpellID, spellId);
3064
3065 // update free primary prof.points (if any, can be none in case GM .learn prof. learning)
3066 if (uint32 freeProfs = GetFreePrimaryProfessionPoints())
3067 {
3068 if (spellInfo->IsPrimaryProfessionFirstRank())
3069 SetFreePrimaryProfessions(freeProfs - 1);
3070 }
3071
3072 SkillLineAbilityMapBounds skill_bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId);
3073
3074 if (SpellLearnSkillNode const* spellLearnSkill = sSpellMgr->GetSpellLearnSkill(spellId))
3075 {
3076 // add dependent skills if this spell is not learned from adding skill already
3077 if (spellLearnSkill->skill != fromSkill)
3078 {
3079 uint16 skill_value = GetPureSkillValue(spellLearnSkill->skill);
3080 uint16 skill_max_value = GetPureMaxSkillValue(spellLearnSkill->skill);
3081
3082 if (skill_value < spellLearnSkill->value)
3083 skill_value = spellLearnSkill->value;
3084
3085 uint16 new_skill_max_value = spellLearnSkill->maxvalue;
3086
3087 if (new_skill_max_value == 0)
3088 {
3089 if (SkillRaceClassInfoEntry const* rcInfo = sDB2Manager.GetSkillRaceClassInfo(spellLearnSkill->skill, GetRace(), GetClass()))
3090 {
3091 switch (GetSkillRangeType(rcInfo))
3092 {
3094 skill_value = 300;
3095 new_skill_max_value = 300;
3096 break;
3097 case SKILL_RANGE_LEVEL:
3098 new_skill_max_value = GetMaxSkillValueForLevel();
3099 break;
3100 case SKILL_RANGE_MONO:
3101 new_skill_max_value = 1;
3102 break;
3103 case SKILL_RANGE_RANK:
3104 {
3105 SkillTiersEntry const* tier = sObjectMgr->GetSkillTier(rcInfo->SkillTierID);
3106 new_skill_max_value = tier->GetValueForTierIndex(spellLearnSkill->step - 1);
3107 break;
3108 }
3109 default:
3110 break;
3111 }
3112
3113 if (rcInfo->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE)
3114 skill_value = new_skill_max_value;
3115 }
3116 }
3117
3118 if (skill_max_value < new_skill_max_value)
3119 skill_max_value = new_skill_max_value;
3120
3121 SetSkill(spellLearnSkill->skill, spellLearnSkill->step, skill_value, skill_max_value);
3122 }
3123 }
3124 else
3125 {
3126 // not ranked skills
3127 for (SkillLineAbilityMap::const_iterator _spell_idx = skill_bounds.first; _spell_idx != skill_bounds.second; ++_spell_idx)
3128 {
3129 SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(_spell_idx->second->SkillLine);
3130 if (!pSkill)
3131 continue;
3132
3133 if (_spell_idx->second->SkillLine == fromSkill)
3134 continue;
3135
3136 // Runeforging special case
3137 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))
3138 if (SkillRaceClassInfoEntry const* rcInfo = sDB2Manager.GetSkillRaceClassInfo(_spell_idx->second->SkillLine, GetRace(), GetClass()))
3139 LearnDefaultSkill(rcInfo);
3140 }
3141 }
3142
3143 // learn dependent spells
3144 SpellLearnSpellMapBounds spell_bounds = sSpellMgr->GetSpellLearnSpellMapBounds(spellId);
3145
3146 for (SpellLearnSpellMap::const_iterator itr2 = spell_bounds.first; itr2 != spell_bounds.second; ++itr2)
3147 {
3148 if (!itr2->second.AutoLearned)
3149 {
3150 if (!IsInWorld() || !itr2->second.Active) // at spells loading, no output, but allow save
3151 AddSpell(itr2->second.Spell, itr2->second.Active, true, true, false);
3152 else // at normal learning
3153 LearnSpell(itr2->second.Spell, true);
3154 }
3155
3156 if (itr2->second.OverridesSpell && itr2->second.Active)
3157 AddOverrideSpell(itr2->second.OverridesSpell, itr2->second.Spell);
3158 }
3159
3160 if (!GetSession()->PlayerLoading())
3161 {
3162 // not ranked skills
3163 for (SkillLineAbilityMap::const_iterator _spell_idx = skill_bounds.first; _spell_idx != skill_bounds.second; ++_spell_idx)
3164 {
3165 UpdateCriteria(CriteriaType::LearnTradeskillSkillLine, _spell_idx->second->SkillLine);
3166 UpdateCriteria(CriteriaType::LearnSpellFromSkillLine, _spell_idx->second->SkillLine);
3167 }
3168
3170 }
3171
3172 // needs to be when spell is already learned, to prevent infinite recursion crashes
3173 if (sDB2Manager.GetMount(spellId))
3174 GetSession()->GetCollectionMgr()->AddMount(spellId, MOUNT_STATUS_NONE, false, IsInWorld() ? false : true);
3175
3176 // return true (for send learn packet) only if spell active (in case ranked spells) and not replace old spell
3177 return active && !disabled && !superceded_old;
3178}
3179
3181{
3182 PlayerSpellMap::iterator itr = m_spells.find(spellId);
3183 // spell already added - do not do anything
3184 if (itr != m_spells.end())
3185 return;
3186 PlayerSpell* newspell = &m_spells[spellId];
3187 newspell->state = PLAYERSPELL_TEMPORARY;
3188 newspell->active = true;
3189 newspell->dependent = false;
3190 newspell->disabled = false;
3191}
3192
3194{
3195 PlayerSpellMap::iterator itr = m_spells.find(spellId);
3196 // spell already not in list - do not do anything
3197 if (itr == m_spells.end())
3198 return;
3199 // spell has other state than temporary - do not change it
3200 if (itr->second.state != PLAYERSPELL_TEMPORARY)
3201 return;
3202 m_spells.erase(itr);
3203}
3204
3206{
3207 // note: form passives activated with shapeshift spells be implemented by HandleShapeshiftBoosts instead of spell_learn_spell
3208 // talent dependent passives activated at form apply have proper stance data
3210 bool need_cast = (!spellInfo->Stances || (form && (spellInfo->Stances & (UI64LIT(1) << (form - 1)))) ||
3212
3213 // Check EquippedItemClass
3214 // passive spells which apply aura and have an item requirement are to be added manually, instead of casted
3215 if (spellInfo->EquippedItemClass >= 0)
3216 {
3217 for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
3218 {
3219 if (spellEffectInfo.IsAura())
3220 {
3221 if (!HasAura(spellInfo->Id) && HasItemFitToSpellRequirements(spellInfo))
3222 AddAura(spellInfo->Id, this);
3223 return false;
3224 }
3225 }
3226 }
3227
3228 //Check CasterAuraStates
3229 return need_cast && (!spellInfo->CasterAuraState || HasAuraState(AuraStateType(spellInfo->CasterAuraState)));
3230}
3231
3232void Player::LearnSpell(uint32 spell_id, bool dependent, int32 fromSkill /*= 0*/, bool suppressMessaging /*= false*/, Optional<int32> traitDefinitionId /*= {}*/)
3233{
3234 PlayerSpellMap::iterator itr = m_spells.find(spell_id);
3235
3236 bool disabled = (itr != m_spells.end()) ? itr->second.disabled : false;
3237 bool active = disabled ? itr->second.active : true;
3238 bool favorite = itr != m_spells.end() ? itr->second.favorite : false;
3239
3240 bool learning = AddSpell(spell_id, active, true, dependent, false, false, fromSkill, favorite, traitDefinitionId);
3241
3242 // prevent duplicated entires in spell book, also not send if not in world (loading)
3243 if (learning && IsInWorld())
3244 {
3246 WorldPackets::Spells::LearnedSpellInfo& learnedSpellInfo = learnedSpells.ClientLearnedSpellData.emplace_back();
3247 learnedSpellInfo.SpellID = spell_id;
3248 learnedSpellInfo.IsFavorite = favorite;
3249 learnedSpellInfo.TraitDefinitionID = traitDefinitionId;
3250 learnedSpells.SuppressMessaging = suppressMessaging;
3251 SendDirectMessage(learnedSpells.Write());
3252 }
3253
3254 // learn all disabled higher ranks and required spells (recursive)
3255 if (disabled)
3256 {
3257 if (uint32 nextSpell = sSpellMgr->GetNextSpellInChain(spell_id))
3258 {
3259 PlayerSpellMap::iterator iter = m_spells.find(nextSpell);
3260 if (iter != m_spells.end() && iter->second.disabled)
3261 LearnSpell(nextSpell, false, fromSkill);
3262 }
3263
3264 SpellsRequiringSpellMapBounds spellsRequiringSpell = sSpellMgr->GetSpellsRequiringSpellBounds(spell_id);
3265 for (SpellsRequiringSpellMap::const_iterator itr2 = spellsRequiringSpell.first; itr2 != spellsRequiringSpell.second; ++itr2)
3266 {
3267 PlayerSpellMap::iterator iter2 = m_spells.find(itr2->second);
3268 if (iter2 != m_spells.end() && iter2->second.disabled)
3269 LearnSpell(itr2->second, false, fromSkill);
3270 }
3271 }
3272 else
3274}
3275
3276void Player::RemoveSpell(uint32 spell_id, bool disabled /*= false*/, bool learn_low_rank /*= true*/, bool suppressMessaging /*= false*/)
3277{
3278 PlayerSpellMap::iterator itr = m_spells.find(spell_id);
3279 if (itr == m_spells.end())
3280 return;
3281
3282 if (itr->second.state == PLAYERSPELL_REMOVED || (disabled && itr->second.disabled) || itr->second.state == PLAYERSPELL_TEMPORARY)
3283 return;
3284
3285 // unlearn non talent higher ranks (recursive)
3286 if (uint32 nextSpell = sSpellMgr->GetNextSpellInChain(spell_id))
3287 {
3288 SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(nextSpell, DIFFICULTY_NONE);
3289 if (HasSpell(nextSpell) && !spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT))
3290 RemoveSpell(nextSpell, disabled, false);
3291 }
3292 //unlearn spells dependent from recently removed spells
3293 SpellsRequiringSpellMapBounds spellsRequiringSpell = sSpellMgr->GetSpellsRequiringSpellBounds(spell_id);
3294 for (SpellsRequiringSpellMap::const_iterator itr2 = spellsRequiringSpell.first; itr2 != spellsRequiringSpell.second; ++itr2)
3295 RemoveSpell(itr2->second, disabled);
3296
3297 // re-search, it can be corrupted in prev loop
3298 itr = m_spells.find(spell_id);
3299 if (itr == m_spells.end())
3300 return; // already unleared
3301
3302 bool cur_active = itr->second.active;
3303 bool cur_dependent = itr->second.dependent;
3304 Optional<int32> traitDefinitionId = itr->second.TraitDefinitionId;
3305
3306 if (disabled)
3307 {
3308 itr->second.disabled = disabled;
3309 if (itr->second.state != PLAYERSPELL_NEW)
3310 itr->second.state = PLAYERSPELL_CHANGED;
3311 }
3312 else
3313 {
3314 if (itr->second.state == PLAYERSPELL_NEW)
3315 m_spells.erase(itr);
3316 else
3317 itr->second.state = PLAYERSPELL_REMOVED;
3318 }
3319
3320 RemoveOwnedAura(spell_id, GetGUID());
3321
3322 // remove pet auras
3323 for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
3324 if (PetAura const* petSpell = sSpellMgr->GetPetAura(spell_id, i))
3325 RemovePetAura(petSpell);
3326
3327 // update free primary prof.points (if not overflow setting, can be in case GM use before .learn prof. learning)
3328 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id, DIFFICULTY_NONE);
3329 if (spellInfo && spellInfo->IsPrimaryProfessionFirstRank())
3330 {
3331 uint32 freeProfs = GetFreePrimaryProfessionPoints()+1;
3332 if (freeProfs <= sWorld->getIntConfig(CONFIG_MAX_PRIMARY_TRADE_SKILL))
3333 SetFreePrimaryProfessions(freeProfs);
3334 }
3335
3336 // remove dependent skill
3337 SpellLearnSkillNode const* spellLearnSkill = sSpellMgr->GetSpellLearnSkill(spell_id);
3338 if (spellLearnSkill)
3339 {
3340 uint32 prev_spell = sSpellMgr->GetPrevSpellInChain(spell_id);
3341 if (!prev_spell) // first rank, remove skill
3342 SetSkill(spellLearnSkill->skill, 0, 0, 0);
3343 else
3344 {
3345 // search prev. skill setting by spell ranks chain
3346 SpellLearnSkillNode const* prevSkill = sSpellMgr->GetSpellLearnSkill(prev_spell);
3347 while (!prevSkill && prev_spell)
3348 {
3349 prev_spell = sSpellMgr->GetPrevSpellInChain(prev_spell);
3350 prevSkill = sSpellMgr->GetSpellLearnSkill(sSpellMgr->GetFirstSpellInChain(prev_spell));
3351 }
3352
3353 if (!prevSkill) // not found prev skill setting, remove skill
3354 SetSkill(spellLearnSkill->skill, 0, 0, 0);
3355 else // set to prev. skill setting values
3356 {
3357 uint16 skill_value = GetPureSkillValue(prevSkill->skill);
3358 uint16 skill_max_value = GetPureMaxSkillValue(prevSkill->skill);
3359
3360 uint16 new_skill_max_value = prevSkill->maxvalue;
3361
3362 if (new_skill_max_value == 0)
3363 {
3364 if (SkillRaceClassInfoEntry const* rcInfo = sDB2Manager.GetSkillRaceClassInfo(prevSkill->skill, GetRace(), GetClass()))
3365 {
3366 switch (GetSkillRangeType(rcInfo))
3367 {
3369 skill_value = 300;
3370 new_skill_max_value = 300;
3371 break;
3372 case SKILL_RANGE_LEVEL:
3373 new_skill_max_value = GetMaxSkillValueForLevel();
3374 break;
3375 case SKILL_RANGE_MONO:
3376 new_skill_max_value = 1;
3377 break;
3378 case SKILL_RANGE_RANK:
3379 {
3380 SkillTiersEntry const* tier = sObjectMgr->GetSkillTier(rcInfo->SkillTierID);
3381 new_skill_max_value = tier->GetValueForTierIndex(prevSkill->step - 1);
3382 break;
3383 }
3384 default:
3385 break;
3386 }
3387
3388 if (rcInfo->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE)
3389 skill_value = new_skill_max_value;
3390 }
3391 }
3392 else if (skill_value > prevSkill->value)
3393 skill_value = prevSkill->value;
3394
3395 if (skill_max_value > new_skill_max_value)
3396 skill_max_value = new_skill_max_value;
3397
3398 if (skill_value > new_skill_max_value)
3399 skill_value = new_skill_max_value;
3400
3401 SetSkill(prevSkill->skill, prevSkill->step, skill_value, skill_max_value);
3402 }
3403 }
3404 }
3405
3406 // remove dependent spells
3407 SpellLearnSpellMapBounds spell_bounds = sSpellMgr->GetSpellLearnSpellMapBounds(spell_id);
3408
3409 for (SpellLearnSpellMap::const_iterator itr2 = spell_bounds.first; itr2 != spell_bounds.second; ++itr2)
3410 {
3411 bool hasOtherSpellTeachingThis = std::ranges::any_of(sSpellMgr->GetSpellLearnedBySpellMapBounds(itr2->second.Spell), [&](SpellLearnSpellNode const* learnNode)
3412 {
3413 if (learnNode->SourceSpell == spell_id)
3414 return false;
3415 if (!learnNode->Active)
3416 return false;
3417 return HasSpell(learnNode->SourceSpell);
3418 }, &SpellLearnedBySpellMap::value_type::second);
3419
3420 if (hasOtherSpellTeachingThis)
3421 continue;
3422
3423 RemoveSpell(itr2->second.Spell, disabled);
3424 if (itr2->second.OverridesSpell)
3425 RemoveOverrideSpell(itr2->second.OverridesSpell, itr2->second.Spell);
3426 }
3427
3428 // activate lesser rank in spellbook/action bar, and cast it if need
3429 bool prev_activate = false;
3430
3431 if (uint32 prev_id = sSpellMgr->GetPrevSpellInChain(spell_id))
3432 {
3433 // if ranked non-stackable spell: need activate lesser rank and update dendence state
3435 if (cur_active && spellInfo->IsRanked())
3436 {
3437 // need manually update dependence state (learn spell ignore like attempts)
3438 PlayerSpellMap::iterator prev_itr = m_spells.find(prev_id);
3439 if (prev_itr != m_spells.end())
3440 {
3441 if (prev_itr->second.dependent != cur_dependent)
3442 {
3443 prev_itr->second.dependent = cur_dependent;
3444 if (prev_itr->second.state != PLAYERSPELL_NEW)
3445 prev_itr->second.state = PLAYERSPELL_CHANGED;
3446 }
3447
3448 // now re-learn if need re-activate
3449 if (!prev_itr->second.active && learn_low_rank)
3450 {
3451 if (AddSpell(prev_id, true, false, prev_itr->second.dependent, prev_itr->second.disabled))
3452 {
3453 // downgrade spell ranks in spellbook and action bar
3454 SendSupercededSpell(spell_id, prev_id);
3455 prev_activate = true;
3456 }
3457 }
3458 }
3459 }
3460 }
3461
3462 if (traitDefinitionId)
3463 if (TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(*traitDefinitionId))
3464 RemoveOverrideSpell(traitDefinition->OverridesSpellID, spell_id);
3465
3466 m_overrideSpells.erase(spell_id);
3467
3468 if (m_canTitanGrip)
3469 {
3470 if (spellInfo && spellInfo->IsPassive() && spellInfo->HasEffect(SPELL_EFFECT_TITAN_GRIP))
3471 {
3473 SetCanTitanGrip(false);
3474 }
3475 }
3476
3477 if (m_canDualWield)
3478 {
3479 if (spellInfo && spellInfo->IsPassive() && spellInfo->HasEffect(SPELL_EFFECT_DUAL_WIELD))
3480 SetCanDualWield(false);
3481 }
3482
3485
3486 // remove from spell book if not replaced by lesser rank
3487 if (!prev_activate)
3488 {
3490 unlearnedSpells.SpellID.push_back(spell_id);
3491 unlearnedSpells.SuppressMessaging = suppressMessaging;
3492 SendDirectMessage(unlearnedSpells.Write());
3493 }
3494}
3495
3496void Player::SetSpellFavorite(uint32 spellId, bool favorite)
3497{
3498 auto itr = m_spells.find(spellId);
3499 if (itr == m_spells.end())
3500 return;
3501
3502 itr->second.favorite = favorite;
3503 if (itr->second.state == PLAYERSPELL_UNCHANGED)
3504 itr->second.state = PLAYERSPELL_CHANGED;
3505}
3506
3507void Player::RemoveArenaSpellCooldowns(bool removeActivePetCooldowns)
3508{
3509 // remove cooldowns on spells that have < 10 min CD
3510 GetSpellHistory()->ResetCooldowns([](SpellHistory::CooldownStorageType::iterator itr)
3511 {
3512 SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(itr->first, DIFFICULTY_NONE);
3513 return spellInfo->RecoveryTime < 10 * MINUTE * IN_MILLISECONDS
3514 && spellInfo->CategoryRecoveryTime < 10 * MINUTE * IN_MILLISECONDS
3516 }, true);
3517
3518 // pet cooldowns
3519 if (removeActivePetCooldowns)
3520 if (Pet* pet = GetPet())
3521 pet->GetSpellHistory()->ResetAllCooldowns();
3522}
3523
3525{
3526 // The first time reset costs 1 gold
3527 if (GetTalentResetCost() < 1*GOLD)
3528 return 1*GOLD;
3529 // then 5 gold
3530 else if (GetTalentResetCost() < 5*GOLD)
3531 return 5*GOLD;
3532 // After that it increases in increments of 5 gold
3533 else if (GetTalentResetCost() < 10*GOLD)
3534 return 10*GOLD;
3535 else
3536 {
3538 if (months > 0)
3539 {
3540 // This cost will be reduced by a rate of 5 gold per month
3541 int32 new_cost = int32(GetTalentResetCost() - 5*GOLD*months);
3542 // to a minimum of 10 gold.
3543 return (new_cost < 10*GOLD ? 10*GOLD : new_cost);
3544 }
3545 else
3546 {
3547 // After that it increases in increments of 5 gold
3548 int32 new_cost = GetTalentResetCost() + 5*GOLD;
3549 // until it hits a cap of 50 gold.
3550 if (new_cost > 50*GOLD)
3551 new_cost = 50*GOLD;
3552 return new_cost;
3553 }
3554 }
3555}
3556
3557bool Player::ResetTalents(bool noCost)
3558{
3559 sScriptMgr->OnPlayerTalentsReset(this, noCost);
3560
3561 // not need after this call
3564
3565 uint32 cost = 0;
3566
3567 if (!noCost && !sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST))
3568 {
3569 cost = GetNextResetTalentsCost();
3570
3571 if (!HasEnoughMoney(uint64(cost)))
3572 {
3574 return false;
3575 }
3576 }
3577
3578 RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true);
3579
3580 for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
3581 {
3582 TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
3583 if (!talentInfo)
3584 continue;
3585
3586 // unlearn only talents for character class
3587 // some spell learned by one class as normal spells or know at creation but another class learn it as talent,
3588 // to prevent unexpected lost normal learned spell skip another class talents
3589 if (talentInfo->ClassID != GetClass())
3590 continue;
3591
3592 // skip non-existent talent ranks
3593 if (talentInfo->SpellID == 0)
3594 continue;
3595
3596 RemoveTalent(talentInfo);
3597 }
3598
3599 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
3600 _SaveTalents(trans);
3601 _SaveSpells(trans);
3602 CharacterDatabase.CommitTransaction(trans);
3603
3604 if (!noCost)
3605 {
3606 ModifyMoney(-(int64)cost);
3609
3610 SetTalentResetCost(cost);
3612 }
3613
3614 /* when prev line will dropped use next line
3615 if (Pet* pet = GetPet())
3616 {
3617 if (pet->getPetType() == HUNTER_PET && !pet->GetCreatureTemplate()->IsTameable(CanTameExoticPets()))
3618 RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true);
3619 }
3620 */
3621
3622 return true;
3623}
3624
3626{
3627 for (uint8 spec = 0; spec < MAX_SPECIALIZATIONS; ++spec)
3628 for (uint32 talentId : GetPvpTalentMap(spec))
3629 if (PvpTalentEntry const* talentInfo = sPvpTalentStore.LookupEntry(talentId))
3630 RemovePvpTalent(talentInfo, spec);
3631}
3632
3634{
3635 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
3636 if ((*itr)->messageID == id)
3637 return (*itr);
3638
3639 return nullptr;
3640}
3641
3643{
3644 if (target == this)
3645 {
3646 for (uint8 i = EQUIPMENT_SLOT_START; i < BANK_SLOT_BAG_END; ++i)
3647 {
3648 if (m_items[i] == nullptr)
3649 continue;
3650
3651 m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
3652 }
3653
3655 {
3656 if (m_items[i] == nullptr)
3657 continue;
3658
3659 m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
3660 }
3661 }
3662
3664}
3665
3667{
3669 if (IsInSameRaidWith(target))
3671
3672 return flags;
3673}
3674
3675void Player::BuildValuesCreate(ByteBuffer* data, Player const* target) const
3676{
3678 std::size_t sizePos = data->wpos();
3679 *data << uint32(0);
3680 *data << uint8(flags);
3681 m_objectData->WriteCreate(*data, flags, this, target);
3682 m_unitData->WriteCreate(*data, flags, this, target);
3683 m_playerData->WriteCreate(*data, flags, this, target);
3684 if (target == this)
3685 m_activePlayerData->WriteCreate(*data, flags, this, target);
3686
3687 data->put<uint32>(sizePos, data->wpos() - sizePos - 4);
3688}
3689
3690void Player::BuildValuesUpdate(ByteBuffer* data, Player const* target) const
3691{
3693 std::size_t sizePos = data->wpos();
3694 *data << uint32(0);
3695 *data << uint32(m_values.GetChangedObjectTypeMask() & ~(uint32(target != this) << TYPEID_ACTIVE_PLAYER));
3696
3698 m_objectData->WriteUpdate(*data, flags, this, target);
3699
3701 m_unitData->WriteUpdate(*data, flags, this, target);
3702
3704 m_playerData->WriteUpdate(*data, flags, this, target);
3705
3706 if (target == this && m_values.HasChanged(TYPEID_ACTIVE_PLAYER))
3707 m_activePlayerData->WriteUpdate(*data, flags, this, target);
3708
3709 data->put<uint32>(sizePos, data->wpos() - sizePos - 4);
3710}
3711
3713{
3715 valuesMask.Set(TYPEID_UNIT);
3716 valuesMask.Set(TYPEID_PLAYER);
3717
3718 std::size_t sizePos = data->wpos();
3719 *data << uint32(0);
3720 *data << uint32(valuesMask.GetBlock(0));
3721
3722 UF::UnitData::Mask mask;
3723 m_unitData->AppendAllowedFieldsMaskForFlag(mask, flags);
3724 m_unitData->WriteUpdate(*data, mask, true, this, target);
3725
3727 m_playerData->AppendAllowedFieldsMaskForFlag(mask2, flags);
3728 m_playerData->WriteUpdate(*data, mask2, true, this, target);
3729
3730 data->put<uint32>(sizePos, data->wpos() - sizePos - 4);
3731}
3732
3734 UF::UnitData::Mask const& requestedUnitMask, UF::PlayerData::Mask const& requestedPlayerMask,
3735 UF::ActivePlayerData::Mask const& requestedActivePlayerMask, Player const* target) const
3736{
3739 if (requestedObjectMask.IsAnySet())
3740 valuesMask.Set(TYPEID_OBJECT);
3741
3742 UF::UnitData::Mask unitMask = requestedUnitMask;
3743 m_unitData->FilterDisallowedFieldsMaskForFlag(unitMask, flags);
3744 if (unitMask.IsAnySet())
3745 valuesMask.Set(TYPEID_UNIT);
3746
3747 UF::PlayerData::Mask playerMask = requestedPlayerMask;
3748 m_playerData->FilterDisallowedFieldsMaskForFlag(playerMask, flags);
3749 if (playerMask.IsAnySet())
3750 valuesMask.Set(TYPEID_PLAYER);
3751
3752 if (target == this && requestedActivePlayerMask.IsAnySet())
3753 valuesMask.Set(TYPEID_ACTIVE_PLAYER);
3754
3755 ByteBuffer& buffer = PrepareValuesUpdateBuffer(data);
3756 std::size_t sizePos = buffer.wpos();
3757 buffer << uint32(0);
3758 buffer << uint32(valuesMask.GetBlock(0));
3759
3760 if (valuesMask[TYPEID_OBJECT])
3761 m_objectData->WriteUpdate(buffer, requestedObjectMask, true, this, target);
3762
3763 if (valuesMask[TYPEID_UNIT])
3764 m_unitData->WriteUpdate(buffer, unitMask, true, this, target);
3765
3766 if (valuesMask[TYPEID_PLAYER])
3767 m_playerData->WriteUpdate(buffer, playerMask, true, this, target);
3768
3769 if (valuesMask[TYPEID_ACTIVE_PLAYER])
3770 m_activePlayerData->WriteUpdate(buffer, requestedActivePlayerMask, true, this, target);
3771
3772 buffer.put<uint32>(sizePos, buffer.wpos() - sizePos - 4);
3773
3774 data->AddUpdateBlock();
3775}
3776
3778{
3779 UpdateData udata(Owner->GetMapId());
3780 WorldPacket packet;
3781
3784
3785 udata.BuildPacket(&packet);
3786 player->SendDirectMessage(&packet);
3787}
3788
3790{
3791 Unit::DestroyForPlayer(target);
3792
3793 if (target == this)
3794 {
3795 for (uint8 i = EQUIPMENT_SLOT_START; i < BANK_SLOT_BAG_END; ++i)
3796 {
3797 if (m_items[i] == nullptr)
3798 continue;
3799
3800 m_items[i]->DestroyForPlayer(target);
3801 }
3802
3804 {
3805 if (m_items[i] == nullptr)
3806 continue;
3807
3808 m_items[i]->DestroyForPlayer(target);
3809 }
3810 }
3811}
3812
3814{
3817 Unit::ClearUpdateMask(remove);
3818}
3819
3820bool Player::HasSpell(uint32 spell) const
3821{
3822 PlayerSpellMap::const_iterator itr = m_spells.find(spell);
3823 return (itr != m_spells.end() && itr->second.state != PLAYERSPELL_REMOVED &&
3824 !itr->second.disabled);
3825}
3826
3827bool Player::HasTalent(uint32 talentId, uint8 group) const
3828{
3829 PlayerTalentMap::const_iterator itr = GetTalentMap(group)->find(talentId);
3830 return (itr != GetTalentMap(group)->end() && itr->second != PLAYERSPELL_REMOVED);
3831}
3832
3834{
3835 PlayerSpellMap::const_iterator itr = m_spells.find(spell);
3836 return (itr != m_spells.end() && itr->second.state != PLAYERSPELL_REMOVED &&
3837 itr->second.active && !itr->second.disabled);
3838}
3839
3852void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRealmChars, bool deleteFinally)
3853{
3854 // Avoid realm-update for non-existing account
3855 if (accountId == 0)
3856 updateRealmChars = false;
3857
3858 // Convert guid to low GUID for CharacterNameData, but also other methods on success
3859 ObjectGuid::LowType guid = playerguid.GetCounter();
3860 uint32 charDeleteMethod = sWorld->getIntConfig(CONFIG_CHARDELETE_METHOD);
3861 CharacterCacheEntry const* characterInfo = sCharacterCache->GetCharacterCacheByGuid(playerguid);
3862 std::string name;
3863 if (characterInfo)
3864 name = characterInfo->Name;
3865
3866 if (deleteFinally)
3867 charDeleteMethod = CHAR_DELETE_REMOVE;
3868 else if (characterInfo) // To avoid a query, we select loaded data. If it doesn't exist, return.
3869 {
3870 // Define the required variables
3871 uint32 charDeleteMinLvl;
3872
3873 if (characterInfo->Class == CLASS_DEATH_KNIGHT)
3874 charDeleteMinLvl = sWorld->getIntConfig(CONFIG_CHARDELETE_DEATH_KNIGHT_MIN_LEVEL);
3875 else if (characterInfo->Class == CLASS_DEMON_HUNTER)
3876 charDeleteMinLvl = sWorld->getIntConfig(CONFIG_CHARDELETE_DEMON_HUNTER_MIN_LEVEL);
3877 else
3878 charDeleteMinLvl = sWorld->getIntConfig(CONFIG_CHARDELETE_MIN_LEVEL);
3879
3880 // if we want to finalize the character removal or the character does not meet the level requirement of either heroic or non-heroic settings,
3881 // we set it to mode CHAR_DELETE_REMOVE
3882 if (characterInfo->Level < charDeleteMinLvl)
3883 charDeleteMethod = CHAR_DELETE_REMOVE;
3884 }
3885
3886 LoginDatabaseTransaction loginTransaction = LoginDatabase.BeginTransaction();
3887 LoginDatabasePreparedStatement* loginStmt = nullptr;
3888
3889 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
3890 if (ObjectGuid::LowType guildId = sCharacterCache->GetCharacterGuildIdByGuid(playerguid))
3891 if (Guild* guild = sGuildMgr->GetGuildById(guildId))
3892 guild->DeleteMember(trans, playerguid, false, false);
3893
3894 // remove from arena teams
3895 LeaveAllArenaTeams(playerguid);
3896
3897 // the player was uninvited already on logout so just remove from group
3899 stmt->setUInt64(0, guid);
3900 PreparedQueryResult resultGroup = CharacterDatabase.Query(stmt);
3901
3902 if (resultGroup)
3903 if (Group* group = sGroupMgr->GetGroupByDbStoreId((*resultGroup)[0].GetUInt32()))
3904 RemoveFromGroup(group, playerguid);
3905
3906 // Remove signs from petitions (also remove petitions if owner);
3907 RemovePetitionsAndSigns(playerguid);
3908
3909 switch (charDeleteMethod)
3910 {
3911 // Completely remove from the database
3912 case CHAR_DELETE_REMOVE:
3913 {
3914 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_COD_ITEM_MAIL);
3915 stmt->setUInt64(0, guid);
3916 PreparedQueryResult resultMail = CharacterDatabase.Query(stmt);
3917
3918 if (resultMail)
3919 {
3920 std::unordered_map<uint64, std::vector<Item*>> itemsByMail;
3921
3922 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS);
3923 stmt->setUInt64(0, guid);
3924 PreparedQueryResult resultItems = CharacterDatabase.Query(stmt);
3925
3926 if (resultItems)
3927 {
3928 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS_ARTIFACT);
3929 stmt->setUInt64(0, guid);
3930 PreparedQueryResult artifactResult = CharacterDatabase.Query(stmt);
3931
3932 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS_AZERITE);
3933 stmt->setUInt64(0, guid);
3934 PreparedQueryResult azeriteResult = CharacterDatabase.Query(stmt);
3935
3937 stmt->setUInt64(0, guid);
3938 PreparedQueryResult azeriteItemMilestonePowersResult = CharacterDatabase.Query(stmt);
3939
3941 stmt->setUInt64(0, guid);
3942 PreparedQueryResult azeriteItemUnlockedEssencesResult = CharacterDatabase.Query(stmt);
3943
3944 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS_AZERITE_EMPOWERED);
3945 stmt->setUInt64(0, guid);
3946 PreparedQueryResult azeriteEmpoweredItemResult = CharacterDatabase.Query(stmt);
3947
3948 std::unordered_map<ObjectGuid::LowType, ItemAdditionalLoadInfo> additionalData;
3949 ItemAdditionalLoadInfo::Init(&additionalData, artifactResult, azeriteResult, azeriteItemMilestonePowersResult,
3950 azeriteItemUnlockedEssencesResult, azeriteEmpoweredItemResult);
3951
3952 do
3953 {
3954 Field* fields = resultItems->Fetch();
3955 uint64 mailId = fields[53].GetUInt64();
3956 if (Item* mailItem = _LoadMailedItem(playerguid, nullptr, mailId, nullptr, fields, Trinity::Containers::MapGetValuePtr(additionalData, fields[0].GetUInt64())))
3957 itemsByMail[mailId].push_back(mailItem);
3958
3959 } while (resultItems->NextRow());
3960 }
3961
3962 do
3963 {
3964 Field* mailFields = resultMail->Fetch();
3965
3966 uint64 mail_id = mailFields[0].GetUInt64();
3967 uint8 mailType = mailFields[1].GetUInt8();
3968 uint16 mailTemplateId= mailFields[2].GetUInt16();
3969 ObjectGuid::LowType sender = mailFields[3].GetUInt64();
3970 std::string subject = mailFields[4].GetString();
3971 std::string body = mailFields[5].GetString();
3972 uint64 money = mailFields[6].GetUInt64();
3973 bool has_items = mailFields[7].GetBool();
3974
3975 // We can return mail now
3976 // So firstly delete the old one
3977 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
3978 stmt->setUInt64(0, mail_id);
3979 trans->Append(stmt);
3980
3981 // Mail is not from player
3982 if (mailType != MAIL_NORMAL)
3983 {
3984 if (has_items)
3985 {
3986 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
3987 stmt->setUInt64(0, mail_id);
3988 trans->Append(stmt);
3989 }
3990 continue;
3991 }
3992
3993 MailDraft draft(subject, body);
3994 if (mailTemplateId)
3995 draft = MailDraft(mailTemplateId, false); // items are already included
3996
3997 auto itemsItr = itemsByMail.find(mail_id);
3998 if (itemsItr != itemsByMail.end())
3999 {
4000 for (Item* item : itemsItr->second)
4001 draft.AddItem(item);
4002
4003 // MailDraft will take care of freeing memory
4004 itemsByMail.erase(itemsItr);
4005 }
4006
4007 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
4008 stmt->setUInt64(0, mail_id);
4009 trans->Append(stmt);
4010
4011 uint32 pl_account = sCharacterCache->GetCharacterAccountIdByGuid(playerguid);
4012
4013 draft.AddMoney(money).SendReturnToSender(pl_account, guid, sender, trans);
4014 }
4015 while (resultMail->NextRow());
4016
4017 // Free remaining items
4018 for (auto&& kvp : itemsByMail)
4019 for (Item* item : kvp.second)
4020 delete item;
4021 }
4022
4023 // Unsummon and delete for pets in world is not required: player deleted from CLI or character list with not loaded pet.
4024 // NOW we can finally clear other DB data related to character
4025 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_IDS);
4026 stmt->setUInt64(0, guid);
4027 PreparedQueryResult resultPets = CharacterDatabase.Query(stmt);
4028
4029 if (resultPets)
4030 {
4031 do
4032 {
4033 uint32 petguidlow = (*resultPets)[0].GetUInt32();
4034 Pet::DeleteFromDB(petguidlow);
4035 } while (resultPets->NextRow());
4036 }
4037
4038 // Delete char from social list of online chars
4039 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_SOCIAL);
4040 stmt->setUInt64(0, guid);
4041
4042 if (PreparedQueryResult resultFriends = CharacterDatabase.Query(stmt))
4043 {
4044 do
4045 {
4046 if (Player* playerFriend = ObjectAccessor::FindPlayer(ObjectGuid::Create<HighGuid::Player>((*resultFriends)[0].GetUInt64())))
4047 {
4048 playerFriend->GetSocial()->RemoveFromSocialList(playerguid, SOCIAL_FLAG_ALL);
4049 sSocialMgr->SendFriendStatus(playerFriend, FRIEND_REMOVED, playerguid);
4050 }
4051 } while (resultFriends->NextRow());
4052 }
4053
4054 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER);
4055 stmt->setUInt64(0, guid);
4056 trans->Append(stmt);
4057
4058 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_CUSTOMIZATIONS);
4059 stmt->setUInt64(0, guid);
4060 trans->Append(stmt);
4061
4062 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_ACCOUNT_DATA);
4063 stmt->setUInt64(0, guid);
4064 trans->Append(stmt);
4065
4066 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_DECLINED_NAME);
4067 stmt->setUInt64(0, guid);
4068 trans->Append(stmt);
4069
4070 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION);
4071 stmt->setUInt64(0, guid);
4072 trans->Append(stmt);
4073
4074 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_ARENA_STATS);
4075 stmt->setUInt64(0, guid);
4076 trans->Append(stmt);
4077
4078 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_AURA_EFFECT);
4079 stmt->setUInt64(0, guid);
4080 trans->Append(stmt);
4081
4082 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_AURA);
4083 stmt->setUInt64(0, guid);
4084 trans->Append(stmt);
4085
4086 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_BGDATA);
4087 stmt->setUInt64(0, guid);
4088 trans->Append(stmt);
4089
4090 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_BATTLEGROUND_RANDOM);
4091 stmt->setUInt64(0, guid);
4092 trans->Append(stmt);
4093
4094 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_CUF_PROFILES);
4095 stmt->setUInt64(0, guid);
4096 trans->Append(stmt);
4097
4098 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_CURRENCY);
4099 stmt->setUInt64(0, guid);
4100 trans->Append(stmt);
4101
4102 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_GIFT);
4103 stmt->setUInt64(0, guid);
4104 trans->Append(stmt);
4105
4106 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_HOMEBIND);
4107 stmt->setUInt64(0, guid);
4108 trans->Append(stmt);
4109
4111 stmt->setUInt64(0, guid);
4112 trans->Append(stmt);
4113
4114 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY);
4115 stmt->setUInt64(0, guid);
4116 trans->Append(stmt);
4117
4118 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS);
4119 stmt->setUInt64(0, guid);
4120 trans->Append(stmt);
4121
4122 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES);
4123 stmt->setUInt64(0, guid);
4124 trans->Append(stmt);
4125
4126 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_REWARDED);
4127 stmt->setUInt64(0, guid);
4128 trans->Append(stmt);
4129
4130 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_REPUTATION);
4131 stmt->setUInt64(0, guid);
4132 trans->Append(stmt);
4133
4134 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL);
4135 stmt->setUInt64(0, guid);
4136 trans->Append(stmt);
4137
4138 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_COOLDOWNS);
4139 stmt->setUInt64(0, guid);
4140 trans->Append(stmt);
4141
4142 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_CHARGES);
4143 stmt->setUInt64(0, guid);
4144 trans->Append(stmt);
4145
4146 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_GEMS_BY_OWNER);
4147 stmt->setUInt64(0, guid);
4148 trans->Append(stmt);
4149
4151 stmt->setUInt64(0, guid);
4152 trans->Append(stmt);
4153
4155 stmt->setUInt64(0, guid);
4156 trans->Append(stmt);
4157
4159 stmt->setUInt64(0, guid);
4160 trans->Append(stmt);
4161
4163 stmt->setUInt64(0, guid);
4164 trans->Append(stmt);
4165
4167 stmt->setUInt64(0, guid);
4168 trans->Append(stmt);
4169
4171 stmt->setUInt64(0, guid);
4172 trans->Append(stmt);
4173
4175 stmt->setUInt64(0, guid);
4176 trans->Append(stmt);
4177
4179 stmt->setUInt64(0, guid);
4180 trans->Append(stmt);
4181
4182 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_BY_OWNER);
4183 stmt->setUInt64(0, guid);
4184 trans->Append(stmt);
4185
4186 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SOCIAL_BY_FRIEND);
4187 stmt->setUInt64(0, guid);
4188 trans->Append(stmt);
4189
4190 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SOCIAL_BY_GUID);
4191 stmt->setUInt64(0, guid);
4192 trans->Append(stmt);
4193
4194 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL);
4195 stmt->setUInt64(0, guid);
4196 trans->Append(stmt);
4197
4198 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEMS);
4199 stmt->setUInt64(0, guid);
4200 trans->Append(stmt);
4201
4202 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_OWNER);
4203 stmt->setUInt64(0, guid);
4204 trans->Append(stmt);
4205
4207 stmt->setUInt64(0, guid);
4208 trans->Append(stmt);
4209
4210 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENTS);
4211 stmt->setUInt64(0, guid);
4212 trans->Append(stmt);
4213
4214 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS);
4215 stmt->setUInt64(0, guid);
4216 trans->Append(stmt);
4217
4218 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_EQUIPMENTSETS);
4219 stmt->setUInt64(0, guid);
4220 trans->Append(stmt);
4221
4222 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRANSMOG_OUTFITS);
4223 stmt->setUInt64(0, guid);
4224 trans->Append(stmt);
4225
4226 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_EVENTLOG_BY_PLAYER);
4227 stmt->setUInt64(0, guid);
4228 stmt->setUInt64(1, guid);
4229 trans->Append(stmt);
4230
4231 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_EVENTLOG_BY_PLAYER);
4232 stmt->setUInt64(0, guid);
4233 trans->Append(stmt);
4234
4235 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_GLYPHS);
4236 stmt->setUInt64(0, guid);
4237 trans->Append(stmt);
4238
4239 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_DAILY);
4240 stmt->setUInt64(0, guid);
4241 trans->Append(stmt);
4242
4243 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_WEEKLY);
4244 stmt->setUInt64(0, guid);
4245 trans->Append(stmt);
4246
4247 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_MONTHLY);
4248 stmt->setUInt64(0, guid);
4249 trans->Append(stmt);
4250
4252 stmt->setUInt64(0, guid);
4253 trans->Append(stmt);
4254
4255 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT);
4256 stmt->setUInt64(0, guid);
4257 trans->Append(stmt);
4258
4259 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SKILLS);
4260 stmt->setUInt64(0, guid);
4261 trans->Append(stmt);
4262
4263 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_STATS);
4264 stmt->setUInt64(0, guid);
4265 trans->Append(stmt);
4266
4268 stmt->setUInt64(0, guid);
4269 trans->Append(stmt);
4270
4271 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_FISHINGSTEPS);
4272 stmt->setUInt64(0, guid);
4273 trans->Append(stmt);
4274
4276 stmt->setUInt64(0, guid);
4277 trans->Append(stmt);
4278
4280 stmt->setUInt64(0, guid);
4281 trans->Append(stmt);
4282
4283 loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_BATTLE_PET_DECLINED_NAME_BY_OWNER);
4284 loginStmt->setInt64(0, guid);
4285 loginStmt->setInt32(1, sRealmList->GetCurrentRealmId().Realm);
4286 loginTransaction->Append(loginStmt);
4287
4288 loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_BATTLE_PETS_BY_OWNER);
4289 loginStmt->setInt64(0, guid);
4290 loginStmt->setInt32(1, sRealmList->GetCurrentRealmId().Realm);
4291 loginTransaction->Append(loginStmt);
4292
4293 Corpse::DeleteFromDB(playerguid, trans);
4294
4295 Garrison::DeleteFromDB(guid, trans);
4296
4297 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_ENTRIES_BY_CHAR);
4298 stmt->setUInt64(0, guid);
4299 trans->Append(stmt);
4300
4301 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_CONFIGS_BY_CHAR);
4302 stmt->setUInt64(0, guid);
4303 trans->Append(stmt);
4304
4305 sCharacterCache->DeleteCharacterCacheEntry(playerguid, name);
4306 break;
4307 }
4308 // The character gets unlinked from the account, the name gets freed up and appears as deleted ingame
4309 case CHAR_DELETE_UNLINK:
4310 {
4311 stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_DELETE_INFO);
4312 stmt->setUInt64(0, guid);
4313 trans->Append(stmt);
4314 sCharacterCache->UpdateCharacterInfoDeleted(playerguid, true, "");
4315 break;
4316 }
4317 default:
4318 TC_LOG_ERROR("entities.player.cheat", "Player::DeleteFromDB: Tried to delete player ({}) with unsupported delete method ({}).",
4319 playerguid.ToString(), charDeleteMethod);
4320
4321 if (trans->GetSize() > 0)
4322 CharacterDatabase.CommitTransaction(trans);
4323 return;
4324 }
4325
4326 LoginDatabase.CommitTransaction(loginTransaction);
4327 CharacterDatabase.CommitTransaction(trans);
4328
4329 if (updateRealmChars)
4330 sWorld->UpdateRealmCharCount(accountId);
4331}
4332
4339{
4340 uint32 keepDays = sWorld->getIntConfig(CONFIG_CHARDELETE_KEEP_DAYS);
4341 if (!keepDays)
4342 return;
4343
4345}
4346
4355{
4356 TC_LOG_INFO("entities.player", "Player::DeleteOldCharacters: Deleting all characters which have been deleted {} days before...", keepDays);
4357
4359 stmt->setUInt32(0, static_cast<uint32>(GameTime::GetGameTime() - static_cast<time_t>(keepDays) * DAY));
4360 PreparedQueryResult result = CharacterDatabase.Query(stmt);
4361
4362 if (result)
4363 {
4364 TC_LOG_DEBUG("entities.player", "Player::DeleteOldCharacters: Found {} character(s) to delete", result->GetRowCount());
4365 do
4366 {
4367 Field* fields = result->Fetch();
4368 Player::DeleteFromDB(ObjectGuid::Create<HighGuid::Player>(fields[0].GetUInt64()), fields[1].GetUInt32(), true, true);
4369 }
4370 while (result->NextRow());
4371 }
4372}
4373
4374/* Preconditions:
4375 - a resurrectable corpse must not be loaded for the player (only bones)
4376 - the player must be in world
4377*/
4379{
4381 packet.PlayerGUID = GetGUID();
4382 SendDirectMessage(packet.Write());
4383
4384 // If the player has the Wisp racial then cast the Wisp aura on them
4385 if (HasSpell(20585))
4386 CastSpell(this, 20584, true);
4387 CastSpell(this, 8326, true);
4388
4390
4391 // there must be SMSG.FORCE_RUN_SPEED_CHANGE, SMSG.FORCE_SWIM_SPEED_CHANGE, SMSG.MOVE_SET_WATER_WALK
4392 // there must be SMSG.STOP_MIRROR_TIMER
4393
4394 // the player cannot have a corpse already on current map, only bones which are not returned by GetCorpse
4395 WorldLocation corpseLocation = GetCorpseLocation();
4396 if (corpseLocation.GetMapId() == GetMapId())
4397 {
4398 TC_LOG_ERROR("entities.player", "Player::BuildPlayerRepop: Player '{}' ({}) already has a corpse", GetName(), GetGUID().ToString());
4399 return;
4400 }
4401
4402 // create a corpse and place it at the player's location
4403 Corpse* corpse = CreateCorpse();
4404 if (!corpse)
4405 {
4406 TC_LOG_ERROR("entities.player", "Player::BuildPlayerRepop: Error creating corpse for player '{}' ({})", GetName(), GetGUID().ToString());
4407 return;
4408 }
4409 GetMap()->AddToMap(corpse);
4410
4411 // convert player body to ghost
4413 SetHealth(1);
4414
4415 SetWaterWalking(true);
4416 if (!GetSession()->isLogingOut() && !HasUnitState(UNIT_STATE_STUNNED))
4417 SetRooted(false);
4418
4419 // BG - remove insignia related
4421
4422 int32 corpseReclaimDelay = CalculateCorpseReclaimDelay();
4423
4424 if (corpseReclaimDelay >= 0)
4425 SendCorpseReclaimDelay(corpseReclaimDelay);
4426
4427 // to prevent cheating
4428 corpse->ResetGhostTime();
4429
4430 StopMirrorTimers(); //disable timers(bars)
4431
4432 // OnPlayerRepop hook
4433 sScriptMgr->OnPlayerRepop(this);
4434}
4435
4436void Player::ResurrectPlayer(float restore_percent, bool applySickness)
4437{
4438 SetAreaSpiritHealer(nullptr);
4439
4441 packet.MapID = -1;
4442 SendDirectMessage(packet.Write());
4443
4444 // speed change, land walk
4445
4446 // remove death flag + set aura
4448
4449 // This must be called always even on Players with race != RACE_NIGHTELF in case of faction change
4450 RemoveAurasDueToSpell(20584); // RACE_NIGHTELF speed bonuses
4451 RemoveAurasDueToSpell(8326); // SPELL_AURA_GHOST
4452
4453 if (GetSession()->IsARecruiter() || (GetSession()->GetRecruiterId() != 0))
4455
4457
4458 // add the flag to make sure opcode is always sent
4460 SetWaterWalking(false);
4462 SetRooted(false);
4463
4464 m_deathTimer = 0;
4465
4466 // set health/powers (0- will be set in caller)
4467 if (restore_percent > 0.0f)
4468 {
4469 SetHealth(GetMaxHealth() * restore_percent);
4470 SetPower(POWER_MANA, GetMaxPower(POWER_MANA) * restore_percent);
4471 SetPower(POWER_RAGE, 0);
4472 SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY) * restore_percent);
4473 SetPower(POWER_FOCUS, GetMaxPower(POWER_FOCUS) * restore_percent);
4475 }
4476
4477 // trigger update zone for alive state zone updates
4478 uint32 newzone, newarea;
4479 GetZoneAndAreaId(newzone, newarea);
4480 UpdateZone(newzone, newarea);
4481 sOutdoorPvPMgr->HandlePlayerResurrects(this, newzone);
4482
4483 // update visibility
4485
4486 // recast lost by death auras of any items held in the inventory
4488
4489 if (!applySickness)
4490 return;
4491
4492 //Characters from level 1-10 are not affected by resurrection sickness.
4493 //Characters from level 11-19 will suffer from one minute of sickness
4494 //for each level they are above 10.
4495 //Characters level 20 and up suffer from ten minutes of sickness.
4496 int32 startLevel = sWorld->getIntConfig(CONFIG_DEATH_SICKNESS_LEVEL);
4497 ChrRacesEntry const* raceEntry = sChrRacesStore.AssertEntry(GetRace());
4498
4499 if (int32(GetLevel()) >= startLevel)
4500 {
4501 // set resurrection sickness
4502 CastSpell(this, raceEntry->ResSicknessSpellID, true);
4503
4504 // not full duration
4505 if (int32(GetLevel()) < startLevel+9)
4506 {
4507 int32 delta = (int32(GetLevel()) - startLevel + 1)*MINUTE;
4508
4509 if (Aura* aur = GetAura(raceEntry->ResSicknessSpellID, GetGUID()))
4510 {
4511 aur->SetDuration(delta*IN_MILLISECONDS);
4512 }
4513 }
4514 }
4515}
4516
4518{
4519 if (IsFlying() && !GetTransport())
4521
4522 SetRooted(true);
4523
4524 StopMirrorTimers(); //disable timers(bars)
4525
4527 //SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_IN_PVP);
4528
4530 if (!sMapStore.LookupEntry(GetMapId())->Instanceable() && !HasAuraType(SPELL_AURA_PREVENT_RESURRECTION))
4532 else
4534
4535 // 6 minutes until repop at graveyard
4537
4538 UpdateCorpseReclaimDelay(); // dependent at use SetDeathPvP() call before kill
4539
4540 int32 corpseReclaimDelay = CalculateCorpseReclaimDelay();
4541
4542 if (corpseReclaimDelay >= 0)
4543 SendCorpseReclaimDelay(corpseReclaimDelay);
4544
4545 // don't create corpse at this moment, player might be falling
4546
4547 // update visibility
4549}
4550
4552{
4553 Corpse::DeleteFromDB(guid, trans);
4556 stmt->setUInt64(1, guid.GetCounter());
4557 CharacterDatabase.ExecuteOrAppend(trans, stmt);
4558}
4559
4561{
4562 // prevent the existence of 2 corpses for one player
4564
4566 SetPvPDeath(false);
4567
4568 if (!corpse->Create(GetMap()->GenerateLowGuid<HighGuid::Corpse>(), this))
4569 {
4570 delete corpse;
4571 return nullptr;
4572 }
4573
4575
4576 uint32 flags = 0;
4577 if (*m_unitData->PvpFlags & UNIT_BYTE2_FLAG_PVP)
4579 if (InBattleground() && !InArena())
4580 flags |= CORPSE_FLAG_SKINNABLE; // to be able to remove insignia
4581 if (*m_unitData->PvpFlags & UNIT_BYTE2_FLAG_FFA_PVP)
4583
4584 corpse->SetRace(GetRace());
4585 corpse->SetSex(GetNativeGender());
4586 corpse->SetClass(GetClass());
4587 corpse->SetCustomizations(Trinity::Containers::MakeIteratorPair(m_playerData->Customizations.begin(), m_playerData->Customizations.end()));
4588 corpse->ReplaceAllFlags(flags);
4590 corpse->SetFactionTemplate(sChrRacesStore.AssertEntry(GetRace())->FactionID);
4591
4593 {
4594 if (m_items[i])
4595 {
4596 uint32 itemDisplayId = m_items[i]->GetDisplayId(this);
4597 uint32 itemInventoryType;
4598 if (ItemEntry const* itemEntry = sItemStore.LookupEntry(m_items[i]->GetVisibleEntry(this)))
4599 itemInventoryType = itemEntry->InventoryType;
4600 else
4601 itemInventoryType = m_items[i]->GetTemplate()->GetInventoryType();
4602
4603 corpse->SetItem(i, itemDisplayId | (itemInventoryType << 24));
4604 }
4605 }
4606
4607 // register for player, but not show
4608 GetMap()->AddCorpse(corpse);
4609
4610 corpse->UpdatePositionData();
4611 corpse->SetZoneScript();
4612
4613 // we do not need to save corpses for instances
4614 if (!GetMap()->Instanceable())
4615 corpse->SaveToDB();
4616
4617 return corpse;
4618}
4619
4620void Player::SpawnCorpseBones(bool triggerSave /*= true*/)
4621{
4623 if (GetMap()->ConvertCorpseToBones(GetGUID()))
4624 if (triggerSave && !GetSession()->PlayerLogoutWithSave()) // at logout we will already store the player
4625 SaveToDB(); // prevent loading as ghost without corpse
4626}
4627
4629{
4630 return GetMap()->GetCorpseByPlayer(GetGUID());
4631}
4632
4633void Player::DurabilityLossAll(double percent, bool inventory)
4634{
4636 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
4637 DurabilityLoss(pItem, percent);
4638
4639 if (inventory)
4640 {
4641 // bags not have durability
4642 // for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++)
4643
4645 for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; i++)
4646 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
4647 DurabilityLoss(pItem, percent);
4648
4650 if (Bag* pBag = GetBagByPos(i))
4651 for (uint32 j = 0; j < pBag->GetBagSize(); j++)
4652 if (Item* pItem = GetItemByPos(i, j))
4653 DurabilityLoss(pItem, percent);
4654 }
4655}
4656
4657void Player::DurabilityLoss(Item* item, double percent)
4658{
4659 if (!item)
4660 return;
4661
4662 uint32 pMaxDurability = item->m_itemData->MaxDurability;
4663
4664 if (!pMaxDurability)
4665 return;
4666
4668
4669 uint32 pDurabilityLoss = uint32(pMaxDurability*percent);
4670
4671 if (pDurabilityLoss < 1)
4672 pDurabilityLoss = 1;
4673
4674 DurabilityPointsLoss(item, pDurabilityLoss);
4675}
4676
4677void Player::DurabilityPointsLossAll(int32 points, bool inventory)
4678{
4680 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
4681 DurabilityPointsLoss(pItem, points);
4682
4683 if (inventory)
4684 {
4685 // bags not have durability
4686 // for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++)
4687
4689 for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; i++)
4690 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
4691 DurabilityPointsLoss(pItem, points);
4692
4694 if (Bag* pBag = static_cast<Bag*>(GetItemByPos(INVENTORY_SLOT_BAG_0, i)))
4695 for (uint32 j = 0; j < pBag->GetBagSize(); j++)
4696 if (Item* pItem = GetItemByPos(i, j))
4697 DurabilityPointsLoss(pItem, points);
4698 }
4699}
4700
4702{
4704 return;
4705
4706 int32 pMaxDurability = item->m_itemData->MaxDurability;
4707 int32 pOldDurability = item->m_itemData->Durability;
4708 int32 pNewDurability = pOldDurability - points;
4709
4710 if (pNewDurability < 0)
4711 pNewDurability = 0;
4712 else if (pNewDurability > pMaxDurability)
4713 pNewDurability = pMaxDurability;
4714
4715 if (pOldDurability != pNewDurability)
4716 {
4717 // modify item stats _before_ Durability set to 0 to pass _ApplyItemMods internal check
4718 if (pNewDurability == 0 && pOldDurability > 0 && item->IsEquipped())
4719 _ApplyItemMods(item, item->GetSlot(), false);
4720
4721 item->SetDurability(pNewDurability);
4722
4723 // modify item stats _after_ restore durability to pass _ApplyItemMods internal check
4724 if (pNewDurability > 0 && pOldDurability == 0 && item->IsEquipped())
4725 _ApplyItemMods(item, item->GetSlot(), true);
4726
4727 item->SetState(ITEM_CHANGED, this);
4728 }
4729}
4730
4732{
4734 return;
4735
4736 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
4737 DurabilityPointsLoss(pItem, 1);
4738}
4739
4740void Player::DurabilityRepairAll(bool takeCost, float discountMod, bool guildBank)
4741{
4742 // Collecting all items that can be repaired and repair costs
4743 std::list<std::pair<Item*, uint64>> itemRepairCostStore;
4744
4745 // equipped, backpack, bags itself
4747 for (uint8 i = EQUIPMENT_SLOT_START; i < inventoryEnd; i++)
4748 if (Item* item = GetItemByPos(((INVENTORY_SLOT_BAG_0 << 8) | i)))
4749 if (uint64 cost = item->CalculateDurabilityRepairCost(discountMod))
4750 itemRepairCostStore.push_back(std::make_pair(item, cost));
4751
4752 // bank, buyback and keys not repaired
4753
4754 // items in inventory bags
4756 for (uint8 i = 0; i < MAX_BAG_SIZE; i++)
4757 if (Item* item = GetItemByPos(((j << 8) | i)))
4758 if (uint64 cost = item->CalculateDurabilityRepairCost(discountMod))
4759 itemRepairCostStore.push_back(std::make_pair(item, cost));
4760
4761 // Handling a free repair case - just repair every item without taking cost.
4762 if (!takeCost)
4763 {
4764 for (auto const& [item, cost] : itemRepairCostStore)
4765 DurabilityRepair(item->GetPos(), false, 0.f);
4766
4767 return;