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