TrinityCore
Player.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "Player.h"
19#include "AreaTrigger.h"
20#include "AccountMgr.h"
21#include "AchievementMgr.h"
22#include "ArenaTeam.h"
23#include "ArenaTeamMgr.h"
25#include "AzeriteItem.h"
26#include "Bag.h"
27#include "Battlefield.h"
28#include "BattlefieldMgr.h"
29#include "Battleground.h"
30#include "BattlegroundMgr.h"
31#include "BattlegroundPackets.h"
32#include "BattlegroundScore.h"
33#include "BattlePetMgr.h"
34#include "CellImpl.h"
35#include "Channel.h"
36#include "ChannelMgr.h"
37#include "CharacterCache.h"
40#include "CharacterPackets.h"
41#include "CharmInfo.h"
42#include "Chat.h"
43#include "ChatPackets.h"
44#include "ChatTextBuilder.h"
45#include "CinematicMgr.h"
46#include "ClubUtils.h"
47#include "CombatLogPackets.h"
48#include "CombatPackets.h"
49#include "Common.h"
50#include "ConditionMgr.h"
51#include "Containers.h"
52#include "CreatureAI.h"
53#include "DB2Stores.h"
54#include "DatabaseEnv.h"
55#include "DisableMgr.h"
56#include "DuelPackets.h"
57#include "EquipmentSetPackets.h"
58#include "Formulas.h"
59#include "GameEventMgr.h"
60#include "GameEventSender.h"
61#include "GameObjectAI.h"
62#include "Garrison.h"
63#include "GarrisonMgr.h"
64#include "GitRevision.h"
65#include "GossipDef.h"
66#include "GridNotifiers.h"
67#include "GridNotifiersImpl.h"
68#include "Group.h"
69#include "GroupMgr.h"
70#include "GameTables.h"
71#include "GameTime.h"
72#include "Guild.h"
73#include "GuildMgr.h"
74#include "InstanceLockMgr.h"
75#include "InstancePackets.h"
76#include "InstanceScript.h"
77#include "ItemPackets.h"
78#include "Language.h"
79#include "LanguageMgr.h"
80#include "LFGMgr.h"
81#include "ListUtils.h"
82#include "Log.h"
83#include "Loot.h"
84#include "LootItemStorage.h"
85#include "LootMgr.h"
86#include "LootPackets.h"
87#include "Mail.h"
88#include "MailPackets.h"
89#include "MapManager.h"
90#include "MapUtils.h"
91#include "MiscPackets.h"
92#include "MotionMaster.h"
93#include "MovementPackets.h"
94#include "ObjectAccessor.h"
95#include "ObjectMgr.h"
96#include "Opcodes.h"
97#include "OutdoorPvP.h"
98#include "OutdoorPvPMgr.h"
99#include "PartyPackets.h"
100#include "Pet.h"
101#include "PetPackets.h"
102#include "PoolMgr.h"
103#include "PetitionMgr.h"
104#include "PhasingHandler.h"
105#include "QueryCallback.h"
106#include "QueryHolder.h"
107#include "QuestDef.h"
109#include "QuestPackets.h"
110#include "RealmList.h"
111#include "ReputationMgr.h"
112#include "RestMgr.h"
113#include "Scenario.h"
114#include "SkillDiscovery.h"
115#include "SocialMgr.h"
116#include "Spell.h"
117#include "SpellAuraEffects.h"
118#include "SpellAuras.h"
119#include "SpellCastRequest.h"
120#include "SpellHistory.h"
121#include "SpellMgr.h"
122#include "SpellPackets.h"
123#include "StringConvert.h"
124#include "TalentPackets.h"
125#include "TerrainMgr.h"
126#include "ToyPackets.h"
127#include "TradeData.h"
128#include "TraitMgr.h"
129#include "TraitPacketsCommon.h"
130#include "Transport.h"
131#include "UpdateData.h"
132#include "Util.h"
133#include "Vehicle.h"
134#include "VehiclePackets.h"
135#include "Vignette.h"
136#include "VignettePackets.h"
137#include "World.h"
138#include "WorldPacket.h"
139#include "WorldSession.h"
140#include "WorldStateMgr.h"
141#include "WorldStatePackets.h"
142#include <boost/dynamic_bitset.hpp>
143#include <G3D/g3dmath.h>
144#include <sstream>
145
146// corpse reclaim times
147#define DEATH_EXPIRE_STEP (5*MINUTE)
148#define MAX_DEATH_COUNT 3
149
151{
153};
154
155static uint32 copseReclaimDelay[MAX_DEATH_COUNT] = { 30, 60, 120 };
156
157uint64 const MAX_MONEY_AMOUNT = 99999999999ULL;
158
159Player::Player(WorldSession* session) : Unit(true), m_sceneMgr(this)
160{
163
165
166 m_session = session;
167
168 m_modMeleeHitChance = 7.5f;
170 m_modSpellHitChance = 15.0f;
171
172 m_ingametime = 0;
173 m_sharedQuestId = 0;
174
175 m_ExtraFlags = 0;
176
177 m_spellModTakingSpell = nullptr;
178
179 // players always accept
180 if (!GetSession()->HasPermission(rbac::RBAC_PERM_CAN_FILTER_WHISPERS))
181 SetAcceptWhispers(true);
182
184 m_regenTimer = 0;
187
189
190 m_areaUpdateId = 0;
192
193 m_nextSave = sWorld->getIntConfig(CONFIG_INTERVAL_SAVE);
195
196 memset(m_items, 0, sizeof(Item*)*PLAYER_SLOTS_COUNT);
197
198 m_social = nullptr;
199
200 // group is initialized in the reference constructor
201 SetGroupInvite(nullptr);
203 m_bPassOnGroupLoot = false;
204
207
209
212
214 m_bCanDelayTeleport = false;
215 m_bHasDelayedTeleport = false;
219
220 m_trade = nullptr;
221
222 m_createTime = 0;
224 m_cinematic = 0;
225
226 m_movie = 0;
227
228 PlayerTalkClass = std::make_unique<PlayerMenu>(GetSession());
230
231 m_DailyQuestChanged = false;
233
237
239 m_drunkTimer = 0;
240 m_deathTimer = 0;
242
243 for (uint8 j = 0; j < PLAYER_MAX_BATTLEGROUND_QUEUES; ++j)
244 {
248 }
249
252 m_Played_time = { };
255 m_canParry = false;
256 m_canBlock = false;
257 m_canTitanGrip = false;
259
261 //cache for CreatedBySpell to allow
262 //returning reagents for temporarily removed pets
263 //when dying/logging out
264 m_oldpetspell = 0;
265 m_lastpetnumber = 0;
266
267 m_mailsUpdated = false;
268 unReadMails = 0;
270
272
274
276
278
279 m_HomebindTimer = 0;
280 m_InstanceValid = true;
284
285 m_lastPotionId = 0;
287
288 m_auraBaseFlatMod.fill(0.0f);
289 m_auraBasePctMod.fill(1.0f);
290 m_baseRatingValue = { };
291
293 m_baseManaRegen = 0;
296
297 // Honor System
299
300 m_IsBGRandomWinner = false;
301
302 // Player summoning
303 m_summon_expire = 0;
304
305 m_unitMovedByMe = this;
306 m_playerMovingMe = this;
307 m_seer = this;
308
310
312
313 m_isActive = true;
314
315 m_lastFallTime = 0;
316 m_lastFallZ = 0;
317
318 m_fishingSteps = 0;
319
321
322 sWorld->IncreasePlayerCount();
323
325
326 m_powerFraction.fill(0.0f);
327
328 isDebugAreaTriggers = false;
329
330 m_WeeklyQuestChanged = false;
331 m_MonthlyQuestChanged = false;
333
334 SetPendingBind(0, 0);
335
338 manaBeforeDuel = 0;
339
341
342 _cinematicMgr = std::make_unique<CinematicMgr>(this);
343
344 m_achievementMgr = std::make_unique<PlayerAchievementMgr>(this);
345 m_reputationMgr = std::make_unique<ReputationMgr>(this);
346 m_questObjectiveCriteriaMgr = std::make_unique<QuestObjectiveCriteriaMgr>(this);
347
348 for (uint8 i = 0; i < MAX_CUF_PROFILES; ++i)
349 _CUFProfiles[i] = nullptr;
350
352
353 _restMgr = std::make_unique<RestMgr>(this);
354
355 _usePvpItemLevels = false;
356}
357
359{
360 // it must be unloaded already in PlayerLogout and accessed only for logged in player
361 //m_social = nullptr;
362
363 // Note: buy back item already deleted from DB when player was saved
364 for (uint8 i = 0; i < PLAYER_SLOTS_COUNT; ++i)
365 delete m_items[i];
366
367 //all mailed items should be deleted, also all mail should be deallocated
368 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
369 delete *itr;
370
371 for (ItemMap::iterator iter = mMitems.begin(); iter != mMitems.end(); ++iter)
372 delete iter->second; //if item is duplicated... then server may crash ... but that item should be deallocated
373
374 for (size_t x = 0; x < ItemSetEff.size(); x++)
375 delete ItemSetEff[x];
376
377 for (uint8 i = 0; i < VOID_STORAGE_MAX_SLOT; ++i)
378 delete _voidStorageItems[i];
379
380 sWorld->DecreasePlayerCount();
381}
382
383void Player::CleanupsBeforeDelete(bool finalCleanup)
384{
385 TradeCancel(false);
387
388 Unit::CleanupsBeforeDelete(finalCleanup);
389}
390
392{
393 //FIXME: outfitId not used in player creating
395
396 Object::_Create(ObjectGuid::Create<HighGuid::Player>(guidlow));
397
398 m_name = createInfo->Name;
399
400 PlayerInfo const* info = sObjectMgr->GetPlayerInfo(createInfo->Race, createInfo->Class);
401 if (!info)
402 {
403 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.",
404 GetSession()->GetAccountId(), m_name, createInfo->Race, createInfo->Class);
405 return false;
406 }
407
408 for (uint8 i = 0; i < PLAYER_SLOTS_COUNT; i++)
409 m_items[i] = nullptr;
410
411 ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(createInfo->Class);
412 if (!cEntry)
413 {
414 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?)",
415 GetSession()->GetAccountId(), m_name, createInfo->Class);
416 return false;
417 }
418
419 if (!GetSession()->ValidateAppearance(Races(createInfo->Race), Classes(createInfo->Class), Gender(createInfo->Sex), MakeChrCustomizationChoiceRange(createInfo->Customizations)))
420 {
421 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",
422 GetSession()->GetAccountId(), m_name);
423 return false;
424 }
425
426 PlayerInfo::CreatePosition const& position = createInfo->UseNPE && info->createPositionNPE ? *info->createPositionNPE : info->createPosition;
427
430
431 Relocate(position.Loc);
432
433 SetMap(sMapMgr->CreateMap(position.Loc.GetMapId(), this));
434
435 if (position.TransportGuid)
436 {
437 if (Transport* transport = ObjectAccessor::GetTransport(*this, ObjectGuid::Create<HighGuid::Transport>(*position.TransportGuid)))
438 {
439 transport->AddPassenger(this);
441 float x, y, z, o;
442 position.Loc.GetPosition(x, y, z, o);
443 transport->CalculatePassengerPosition(x, y, z, &o);
444 Relocate(x, y, z, o);
445 }
446 }
447
448 // set initial homebind position
449 SetHomebind(*this, GetAreaId());
450
451 uint8 powertype = cEntry->DisplayPower;
452
453 SetObjectScale(1.0f);
454
455 SetFactionForRace(createInfo->Race);
456
457 if (!IsValidGender(createInfo->Sex))
458 {
459 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",
460 GetSession()->GetAccountId(), m_name, createInfo->Sex);
461 return false;
462 }
463
464 SetRace(createInfo->Race);
465 SetClass(createInfo->Class);
466 SetGender(Gender(createInfo->Sex));
467 SetPowerType(Powers(powertype), false);
469 if (sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_PVP || sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_RPPVP)
470 {
473 }
474
476
478
480 SetRestState(REST_TYPE_XP, (GetSession()->IsARecruiter() || GetSession()->GetRecruiterId() != 0) ? REST_STATE_RAF_LINKED : REST_STATE_NORMAL);
482 SetNativeGender(Gender(createInfo->Sex));
484
485 // set starting level
486 SetLevel(GetStartLevel(createInfo->Race, createInfo->Class, createInfo->TemplateSet), false);
487
488 InitRunes();
489
491
492 // Played time
496
497 // base stats and related field values
502 InitPrimaryProfessions(); // to max set before any spell added
503
504 // apply original stats mods before spell loading or item equipment that call before equip _RemoveStatsMods()
505 UpdateMaxHealth(); // Update max Health (for add bonus from stamina)
508
509 // original spells
512
513 // Original action bar. Do not use Player::AddActionButton because we do not have skill spells loaded at this time
514 // but checks will still be performed later when loading character from db in Player::_LoadActions
515 for (PlayerCreateInfoActions::const_iterator action_itr = info->action.begin(); action_itr != info->action.end(); ++action_itr)
516 {
517 // create new button
518 ActionButton& ab = m_actionButtons[action_itr->button];
519
520 // set data
521 ab.SetActionAndType(action_itr->action, ActionButtonType(action_itr->type));
522 }
523
524 // original items
525 for (PlayerCreateInfoItem initialItem : info->item)
526 StoreNewItemInBestSlots(initialItem.item_id, initialItem.item_amount, info->itemContext);
527
528 // bags and main-hand weapon must equipped at this moment
529 // now second pass for not equipped (offhand weapon/shield if it attempt equipped before main-hand weapon)
530 // or ammo not equipped in special bag
532 for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; i++)
533 {
534 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
535 {
536 uint16 eDest;
537 // equip offhand weapon/shield if it attempt equipped before main-hand weapon
538 InventoryResult msg = CanEquipItem(NULL_SLOT, eDest, pItem, false);
539 if (msg == EQUIP_ERR_OK)
540 {
542 EquipItem(eDest, pItem, true);
543 }
544 // move other items to more appropriate slots
545 else
546 {
547 ItemPosCountVec sDest;
548 msg = CanStoreItem(NULL_BAG, NULL_SLOT, sDest, pItem, false);
549 if (msg == EQUIP_ERR_OK)
550 {
552 StoreItem(sDest, pItem, true);
553 }
554 }
555 }
556 }
557 // all item positions resolved
558
559 if (ChrSpecializationEntry const* defaultSpec = sDB2Manager.GetDefaultChrSpecializationForClass(GetClass()))
560 {
561 SetActiveTalentGroup(defaultSpec->OrderIndex);
562 SetPrimarySpecialization(defaultSpec->ID);
563 }
564
566
567 return true;
568}
569
571{
572 TC_LOG_DEBUG("entities.player.items", "Player::StoreNewItemInBestSlots: Player '{}' ({}) creates initial item (ItemID: {}, Count: {})",
573 GetName(), GetGUID().ToString(), itemId, amount);
574
575 // attempt equip by one
576 while (amount > 0)
577 {
578 uint16 eDest;
579 InventoryResult msg = CanEquipNewItem(NULL_SLOT, eDest, itemId, false);
580 if (msg != EQUIP_ERR_OK)
581 break;
582
583 EquipNewItem(eDest, itemId, context, true);
585 --amount;
586 }
587
588 if (amount == 0)
589 return true; // equipped
590
591 // attempt store
592 ItemPosCountVec sDest;
593 // store in main bag to simplify second pass (special bags can be not equipped yet at this moment)
594 InventoryResult msg = CanStoreNewItem(INVENTORY_SLOT_BAG_0, NULL_SLOT, sDest, itemId, amount);
595 if (msg == EQUIP_ERR_OK)
596 {
597 StoreNewItem(sDest, itemId, true, GenerateItemRandomBonusListId(itemId), GuidSet(), context);
598 return true; // stored
599 }
600
601 // item can't be added
602 TC_LOG_ERROR("entities.player.items", "Player::StoreNewItemInBestSlots: Player '{}' ({}) can't equip or store initial item (ItemID: {}, Race: {}, Class: {}, InventoryResult: {})",
603 GetName(), GetGUID().ToString(), itemId, GetRace(), GetClass(), msg);
604 return false;
605}
606
607void Player::SendMirrorTimer(MirrorTimerType Type, uint32 MaxValue, uint32 CurrentValue, int32 Regen)
608{
609 if (int(MaxValue) == DISABLED_MIRROR_TIMER)
610 {
611 if (int(CurrentValue) != DISABLED_MIRROR_TIMER)
613 return;
614 }
615
616 SendDirectMessage(WorldPackets::Misc::StartMirrorTimer(Type, CurrentValue, MaxValue, Regen, 0, false).Write());
617}
618
620{
623}
624
626{
627 // check for GM and death state included in isAttackableByAOE
628 return !isTargetableForAttack(false);
629}
630
632{
634 return 0;
635
637
638 // Absorb, resist some environmental damage type
639 uint32 absorb = 0;
640 uint32 resist = 0;
641 switch (type)
642 {
643 case DAMAGE_LAVA:
644 case DAMAGE_SLIME:
645 {
646 DamageInfo dmgInfo(this, this, damage, nullptr, type == DAMAGE_LAVA ? SPELL_SCHOOL_MASK_FIRE : SPELL_SCHOOL_MASK_NATURE, DIRECT_DAMAGE, BASE_ATTACK);
647 Unit::CalcAbsorbResist(dmgInfo);
648 absorb = dmgInfo.GetAbsorb();
649 resist = dmgInfo.GetResist();
650 damage = dmgInfo.GetDamage();
651 break;
652 }
653 default:
654 break;
655 }
656
657 Unit::DealDamageMods(nullptr, this, damage, &absorb);
658
660 packet.Victim = GetGUID();
661 packet.Type = type != DAMAGE_FALL_TO_VOID ? type : DAMAGE_FALL;
662 packet.Amount = damage;
663 packet.Absorbed = absorb;
664 packet.Resisted = resist;
665
666 uint32 final_damage = Unit::DealDamage(this, this, damage, nullptr, SELF_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, nullptr, false);
667
668 packet.LogData.Initialize(this);
669 SendCombatLogMessage(&packet);
670
671 if (!IsAlive())
672 {
673 if (type == DAMAGE_FALL) // DealDamage does not apply item durability loss from self-induced damage.
674 {
675 TC_LOG_DEBUG("entities.player", "Player::EnvironmentalDamage: Player '{}' ({}) fall to death, losing {}% durability",
678 // durability lost message
680 }
681
683 }
684
685 return final_damage;
686}
687
689{
690 switch (timer)
691 {
692 case FATIGUE_TIMER:
693 return MINUTE * IN_MILLISECONDS;
694 case BREATH_TIMER:
695 {
698
699 int32 UnderWaterTime = 3 * MINUTE * IN_MILLISECONDS;
701 return UnderWaterTime;
702 }
703 case FIRE_TIMER:
704 {
705 if (!IsAlive())
707 return 1 * IN_MILLISECONDS;
708 }
709 default:
710 return 0;
711 }
712}
713
715{
716 // Desync flags for update on next HandleDrowning
718 m_MirrorTimerFlagsLast = ~m_MirrorTimerFlags;
719}
720
722{
726}
727
729{
730 return m_MirrorTimer[type] == getMaxTimer(type);
731}
732
734{
736 return;
737
738 auto getEnvironmentalDamage = [&](EnviromentalDamage damageType)
739 {
740 uint8 damagePercent = 10;
741 if (damageType == DAMAGE_DROWNING || damageType == DAMAGE_EXHAUSTED)
742 damagePercent *= 2;
743
744 uint32 damage = GetMaxHealth() * damagePercent / 100;
745
746 // Randomize damage
747 damage += urand(0, pow(10, std::max(0, (int32)log10(damage) - 1)));
748
749 return damage;
750 };
751
752 // In water
754 {
755 // Breath timer not activated - activate it
757 {
760 }
761 else // If activated - do tick
762 {
763 m_MirrorTimer[BREATH_TIMER] -= time_diff;
764 // Timer limit - need deal damage
766 {
768 // Calculate and deal damage
769 uint32 damage = getEnvironmentalDamage(DAMAGE_DROWNING);
771 }
772 else if (!(m_MirrorTimerFlagsLast & UNDERWATER_INWATER)) // Update time in client if need
774 }
775 }
776 else if (m_MirrorTimer[BREATH_TIMER] != DISABLED_MIRROR_TIMER) // Regen timer
777 {
778 int32 UnderWaterTime = getMaxTimer(BREATH_TIMER);
779 // Need breath regen
780 m_MirrorTimer[BREATH_TIMER] += 10 * time_diff;
781 if (m_MirrorTimer[BREATH_TIMER] >= UnderWaterTime || !IsAlive())
785 }
786
787 // In dark water
789 {
790 // Fatigue timer not activated - activate it
792 {
795 }
796 else
797 {
798 m_MirrorTimer[FATIGUE_TIMER] -= time_diff;
799 // Timer limit - need deal damage or teleport ghost to graveyard
801 {
803 if (IsAlive()) // Calculate and deal damage
804 {
805 uint32 damage = getEnvironmentalDamage(DAMAGE_EXHAUSTED);
807 }
808 else if (HasPlayerFlag(PLAYER_FLAGS_GHOST)) // Teleport ghost to graveyard
810 }
813 }
814 }
815 else if (m_MirrorTimer[FATIGUE_TIMER] != DISABLED_MIRROR_TIMER) // Regen timer
816 {
817 int32 DarkWaterTime = getMaxTimer(FATIGUE_TIMER);
818 m_MirrorTimer[FATIGUE_TIMER] += 10 * time_diff;
819 if (m_MirrorTimer[FATIGUE_TIMER] >= DarkWaterTime || !IsAlive())
823 }
824
825 if (m_MirrorTimerFlags & (UNDERWATER_INLAVA /*| UNDERWATER_INSLIME*/) && !(_lastLiquid && _lastLiquid->SpellID))
826 {
827 // Breath timer not activated - activate it
830 else
831 {
832 m_MirrorTimer[FIRE_TIMER] -= time_diff;
833 if (m_MirrorTimer[FIRE_TIMER] < 0)
834 {
836 // Calculate and deal damage
837 uint32 damage = getEnvironmentalDamage(DAMAGE_LAVA);
840 // need to skip Slime damage in Undercity,
841 // maybe someone can find better way to handle environmental damage
842 //else if (m_zoneUpdateId != 1497)
843 // EnvironmentalDamage(DAMAGE_SLIME, damage);
844 }
845 }
846 }
847 else
849
850 // Recheck timers flag
851 m_MirrorTimerFlags &= ~UNDERWATER_EXIST_TIMERS;
852 for (uint8 i = 0; i < MAX_TIMERS; ++i)
853 {
855 {
857 break;
858 }
859 }
861}
862
865{
866 m_drunkTimer = 0;
867
868 uint8 currentDrunkValue = GetDrunkValue();
869 uint8 drunk = currentDrunkValue ? --currentDrunkValue : 0;
870 SetDrunkValue(drunk);
871}
872
874{
875 if (value >= 90)
876 return DRUNKEN_SMASHED;
877 if (value >= 50)
878 return DRUNKEN_DRUNK;
879 if (value)
880 return DRUNKEN_TIPSY;
881 return DRUNKEN_SOBER;
882}
883
884void Player::SetDrunkValue(uint8 newDrunkValue, uint32 itemId /*= 0*/)
885{
886 bool isSobering = newDrunkValue < GetDrunkValue();
888 if (newDrunkValue > 100)
889 newDrunkValue = 100;
890
891 // select drunk percent or total SPELL_AURA_MOD_FAKE_INEBRIATE amount, whichever is higher for visibility updates
892 int32 drunkPercent = std::max<int32>(newDrunkValue, GetTotalAuraModifier(SPELL_AURA_MOD_FAKE_INEBRIATE));
893 if (drunkPercent)
894 {
897 }
898 else if (!HasAuraType(SPELL_AURA_MOD_FAKE_INEBRIATE) && !newDrunkValue)
900
901 uint32 newDrunkenState = Player::GetDrunkenstateByValue(newDrunkValue);
904
905 if (!isSobering)
906 m_drunkTimer = 0; // reset sobering timer
907
908 if (newDrunkenState == oldDrunkenState)
909 return;
910
912 data.Guid = GetGUID();
913 data.Threshold = newDrunkenState;
914 data.ItemID = itemId;
915
916 SendMessageToSet(data.Write(), true);
917}
918
920{
921 if (!IsInWorld())
922 return;
923
924 // undelivered mail
926 {
927 SendNewMail();
928 ++unReadMails;
929
930 // It will be recalculate at mailbox open (for unReadMails important non-0 until mailbox open, it also will be recalculated)
932 }
933
934 // Update cinematic location, if 500ms have passed and we're doing a cinematic now.
935 _cinematicMgr->m_cinematicDiff += p_time;
936 if (_cinematicMgr->m_cinematicCamera && _cinematicMgr->m_activeCinematic && GetMSTimeDiffToNow(_cinematicMgr->m_lastCinematicCheck) > CINEMATIC_UPDATEDIFF)
937 {
938 _cinematicMgr->m_lastCinematicCheck = GameTime::GetGameTimeMS();
939 _cinematicMgr->UpdateCinematicLocation(p_time);
940 }
941
942 //used to implement delayed far teleport
944 Unit::Update(p_time);
945 SetCanDelayTeleport(false);
946
947 // Unit::Update updates the spell history and spell states. We can now check if we can launch another pending cast.
950
951 time_t now = GameTime::GetGameTime();
952
953 UpdatePvPFlag(now);
954
955 UpdateContestedPvP(p_time);
956
957 UpdateDuelFlag(now);
958
960
961 UpdateAfkReport(now);
962
963 if (GetCombatManager().HasPvPCombat())
965 if (!aura->IsPermanent())
966 aura->SetDuration(aura->GetSpellInfo()->GetMaxDuration());
967
968 Unit::AIUpdateTick(p_time);
969
970 // Update items that have just a limited lifetime
971 if (now > m_Last_tick)
973
974 // check every second
975 if (now > m_Last_tick + 1)
977
978 // If mute expired, remove it from the DB
979 if (GetSession()->m_muteTime && GetSession()->m_muteTime < now)
980 {
981 using namespace std::string_view_literals;
982
983 GetSession()->m_muteTime = 0;
985 stmt->setInt64(0, 0); // Set the mute time to 0
986 stmt->setString(1, ""sv);
987 stmt->setString(2, ""sv);
988 stmt->setUInt32(3, GetSession()->GetAccountId());
989 LoginDatabase.Execute(stmt);
990 }
991
992 if (!m_timedquests.empty())
993 {
994 QuestSet::iterator iter = m_timedquests.begin();
995 while (iter != m_timedquests.end())
996 {
997 QuestStatusData& q_status = m_QuestStatus[*iter];
998 if (q_status.Timer <= p_time)
999 {
1000 uint32 quest_id = *iter;
1001 ++iter; // current iter will be removed in FailQuest
1002 FailQuest(quest_id);
1003 }
1004 else
1005 {
1006 q_status.Timer -= p_time;
1008 ++iter;
1009 }
1010 }
1011 }
1012
1013 m_achievementMgr->UpdateTimedCriteria(Milliseconds(p_time));
1014
1016
1018 _restMgr->Update(now);
1019
1020 if (m_weaponChangeTimer > 0)
1021 {
1022 if (p_time >= m_weaponChangeTimer)
1024 else
1025 m_weaponChangeTimer -= p_time;
1026 }
1027
1028 if (IsAlive())
1029 {
1030 m_regenTimer += p_time;
1031 RegenerateAll();
1032 }
1033
1034 if (m_deathState == JUST_DIED)
1035 KillPlayer();
1036
1037 if (m_nextSave > 0)
1038 {
1039 if (p_time >= m_nextSave)
1040 {
1041 // m_nextSave reset in SaveToDB call
1042 SaveToDB();
1043 TC_LOG_DEBUG("entities.player", "Player::Update: Player '{}' ({}) saved", GetName(), GetGUID().ToString());
1044 }
1045 else
1046 m_nextSave -= p_time;
1047 }
1048
1049 //Handle Water/drowning
1050 HandleDrowning(p_time);
1051
1052 // Played time
1053 if (now > m_Last_tick)
1054 {
1055 uint32 elapsed = uint32(now - m_Last_tick);
1056 m_Played_time[PLAYED_TIME_TOTAL] += elapsed; // Total played time
1057 m_Played_time[PLAYED_TIME_LEVEL] += elapsed; // Level played time
1058 m_Last_tick = now;
1059 }
1060
1061 if (GetDrunkValue())
1062 {
1063 m_drunkTimer += p_time;
1064 if (m_drunkTimer > 9 * IN_MILLISECONDS)
1066 }
1067
1068 if (HasPendingBind())
1069 {
1070 if (_pendingBindTimer <= p_time)
1071 {
1072 // Player left the instance
1075 SetPendingBind(0, 0);
1076 }
1077 else
1078 _pendingBindTimer -= p_time;
1079 }
1080
1081 // not auto-free ghost from body in instances
1082 if (m_deathTimer > 0 && !GetMap()->Instanceable() && !HasAuraType(SPELL_AURA_PREVENT_RESURRECTION))
1083 {
1084 if (p_time >= m_deathTimer)
1085 {
1086 m_deathTimer = 0;
1089 }
1090 else
1091 m_deathTimer -= p_time;
1092 }
1093
1094 UpdateEnchantTime(p_time);
1095 UpdateHomebindTime(p_time);
1096
1097 if (!_instanceResetTimes.empty())
1098 {
1099 for (InstanceTimeMap::iterator itr = _instanceResetTimes.begin(); itr != _instanceResetTimes.end();)
1100 {
1101 if (itr->second < now)
1102 _instanceResetTimes.erase(itr++);
1103 else
1104 ++itr;
1105 }
1106 }
1107
1108 Pet* pet = GetPet();
1109 if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityRange()) && !pet->isPossessed())
1110 //if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityDistance()) && (GetCharmGUID() && (pet->GetGUID() != GetCharmGUID())))
1111 RemovePet(pet, PET_SAVE_NOT_IN_SLOT, true);
1112
1113 if (IsAlive())
1114 {
1115 if (m_hostileReferenceCheckTimer <= p_time)
1116 {
1118 if (!GetMap()->IsDungeon())
1120 }
1121 else
1123 }
1124
1125 //we should execute delayed teleports only for alive(!) players
1126 //because we don't want player's ghost teleported from graveyard
1127 if (IsHasDelayedTeleport() && IsAlive())
1129}
1130
1132{
1134
1135 // Group update
1137
1138 // Updating Zone and AreaId. This will also trigger spell_area and phasing related updates
1140
1141 // Updating auras which can only be used inside or outside (such as Mounts)
1143
1144 // Updating the resting state when entering resting places
1146}
1147
1149{
1150 bool oldIsAlive = IsAlive();
1151
1152 if (s == JUST_DIED)
1153 {
1154 if (!oldIsAlive)
1155 {
1156 TC_LOG_ERROR("entities.player", "Player::setDeathState: Attempted to kill a dead player '{}' ({})", GetName(), GetGUID().ToString());
1157 return;
1158 }
1159
1160 // clear all pending spell cast requests when dying
1162
1163 // drunken state is cleared on death
1164 SetDrunkValue(0);
1166
1168
1169 //FIXME: is pet dismissed at dying or releasing spirit? if second, add setDeathState(DEAD) to HandleRepopRequest and define pet unsummon here with (s == DEAD)
1170 RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true);
1171
1173
1175
1179
1180 // reset all death criterias
1182 }
1183
1185
1186 if (IsAlive() && !oldIsAlive)
1187 //clear aura case after resurrection by another way (spells will be applied before next death)
1189}
1190
1192{
1193 if (isAFK())
1195 else
1197
1198 // afk player not allowed in battleground
1199 if (!IsGameMaster() && isAFK() && InBattleground() && !InArena())
1201}
1202
1204{
1205 if (isDND())
1207 else
1209}
1210
1212{
1213 uint16 tag = CHAT_FLAG_NONE;
1214
1215 if (isGMChat())
1216 tag |= CHAT_FLAG_GM;
1217 if (isDND())
1218 tag |= CHAT_FLAG_DND;
1219 if (isAFK())
1220 tag |= CHAT_FLAG_AFK;
1221 if (IsDeveloper())
1222 tag |= CHAT_FLAG_DEV;
1223 if (m_activePlayerData->TimerunningSeasonID)
1224 tag |= CHAT_FLAG_TIMERUNNING;
1225
1226 return tag;
1227}
1228
1229bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientation, TeleportToOptions options /*= TELE_TO_NONE*/, Optional<uint32> instanceId /*= {}*/, uint32 teleportSpellId /*= 0*/)
1230{
1231 return TeleportTo({ .Location = WorldLocation(mapid, x, y, z, orientation), .InstanceId = instanceId }, options, teleportSpellId);
1232}
1233
1234bool Player::TeleportTo(WorldLocation const& loc, TeleportToOptions options, Optional<uint32> instanceId, uint32 teleportSpellId)
1235{
1236 return TeleportTo({ .Location = loc, .InstanceId = instanceId }, options, teleportSpellId);
1237}
1238
1239bool Player::TeleportTo(TeleportLocation const& teleportLocation, TeleportToOptions options /*= TELE_TO_NONE*/, uint32 teleportSpellId)
1240{
1241 if (!MapManager::IsValidMapCoord(teleportLocation.Location))
1242 {
1243 TC_LOG_ERROR("maps", "Player::TeleportTo: Invalid map ({}) or invalid coordinates ({}) given when teleporting player '{}' ({}, MapID: {}, {}).",
1244 teleportLocation.Location.GetMapId(), teleportLocation.Location.ToString(), GetGUID().ToString(), GetName(), GetMapId(), GetPosition().ToString());
1245 return false;
1246 }
1247
1249 {
1250 TC_LOG_ERROR("entities.player.cheat", "Player::TeleportTo: Player '{}' ({}) tried to enter a forbidden map (MapID: {})", GetGUID().ToString(), GetName(), teleportLocation.Location.GetMapId());
1252 return false;
1253 }
1254
1255 // preparing unsummon pet if lost (we must get pet before teleportation or will not find it later)
1256 Pet* pet = GetPet();
1257
1258 MapEntry const* mEntry = sMapStore.LookupEntry(teleportLocation.Location.GetMapId());
1259
1260 // don't let enter battlegrounds without assigned battleground id (for example through areatrigger)...
1261 // don't let gm level > 1 either
1262 if (!InBattleground() && mEntry->IsBattlegroundOrArena())
1263 return false;
1264
1265 // client without expansion support
1266 if (GetSession()->GetExpansion() < mEntry->Expansion())
1267 {
1268 TC_LOG_DEBUG("maps", "Player '{}' ({}) using client without required expansion tried teleporting to non accessible map (MapID: {})",
1269 GetName(), GetGUID().ToString(), teleportLocation.Location.GetMapId());
1270
1271 if (TransportBase* transport = GetTransport())
1272 {
1273 transport->RemovePassenger(this);
1274 RepopAtGraveyard(); // teleport to near graveyard if on transport, looks blizz like :)
1275 }
1276
1278
1279 return false; // normal client can't teleport to this map...
1280 }
1281 else
1282 TC_LOG_DEBUG("maps", "Player {} ({}) is being teleported to map (MapID: {})", GetName(), GetGUID().ToString(), teleportLocation.Location.GetMapId());
1283
1284 if (m_vehicle)
1285 ExitVehicle();
1286
1287 // reset movement flags at teleport, because player will continue move with these flags after teleport
1290 DisableSpline();
1292
1293 if (TransportBase* transport = GetTransport())
1294 if (!teleportLocation.TransportGuid || teleportLocation.TransportGuid != transport->GetTransportGUID())
1295 if (!(options & TELE_TO_NOT_LEAVE_TRANSPORT))
1296 transport->RemovePassenger(this);
1297
1298 // The player was ported to another map and loses the duel immediately.
1299 // We have to perform this check before the teleport, otherwise the
1300 // ObjectAccessor won't find the flag.
1301 if (duel && GetMapId() != teleportLocation.Location.GetMapId() && GetMap()->GetGameObject(m_playerData->DuelArbiter))
1303
1304 if (GetMapId() == teleportLocation.Location.GetMapId() && (!teleportLocation.InstanceId || GetInstanceId() == teleportLocation.InstanceId))
1305 {
1306 //lets reset far teleport flag if it wasn't reset during chained teleport
1308 //setup delayed teleport flag
1310 //if teleport spell is cast in Unit::Update() func
1311 //then we need to delay it until update process will be finished
1313 {
1315 //lets save teleport destination for player
1316 m_teleport_dest = teleportLocation;
1317 m_teleport_options = options;
1318 m_teleportSpellId = teleportSpellId;
1319 return true;
1320 }
1321
1322 if (!(options & TELE_TO_NOT_UNSUMMON_PET))
1323 {
1324 //same map, only remove pet if out of range for new position
1325 if (pet && !pet->IsWithinDist3d(&teleportLocation.Location, GetMap()->GetVisibilityRange()))
1327 }
1328
1329 if (!IsAlive() && options & TELE_REVIVE_AT_TELEPORT)
1330 ResurrectPlayer(0.5f);
1331
1332 if (!(options & TELE_TO_NOT_LEAVE_COMBAT))
1333 CombatStop();
1334
1335 // this will be used instead of the current location in SaveToDB
1336 m_teleport_dest = teleportLocation;
1337 m_teleport_options = options;
1338 m_teleportSpellId = teleportSpellId;
1340
1341 // code for finish transfer called in WorldSession::HandleMovementOpcodes()
1342 // at client packet CMSG_MOVE_TELEPORT_ACK
1344 // near teleport, triggering send CMSG_MOVE_TELEPORT_ACK from client at landing
1345 if (!GetSession()->PlayerLogout())
1347 }
1348 else
1349 {
1350 if (GetClass() == CLASS_DEATH_KNIGHT && GetMapId() == 609 && !IsGameMaster() && !HasSpell(50977))
1351 {
1353 return false;
1354 }
1355
1356 // far teleport to another map
1357 Map* oldmap = IsInWorld() ? GetMap() : nullptr;
1358 // check if we can enter before stopping combat / removing pet / totems / interrupting spells
1359
1360 // Check enter rights before map getting to avoid creating instance copy for player
1361 // this check not dependent from map instance copy and same for all instance copies of selected map
1362 if (TransferAbortParams abortParams = Map::PlayerCannotEnter(teleportLocation.Location.GetMapId(), this))
1363 {
1364 SendTransferAborted(teleportLocation.Location.GetMapId(), abortParams.Reason, abortParams.Arg, abortParams.MapDifficultyXConditionId);
1365 return false;
1366 }
1367
1368 // Seamless teleport can happen only if cosmetic maps match
1369 if (!oldmap ||
1370 (oldmap->GetEntry()->CosmeticParentMapID != int32(teleportLocation.Location.GetMapId()) && int32(GetMapId()) != mEntry->CosmeticParentMapID &&
1371 !((oldmap->GetEntry()->CosmeticParentMapID != -1) ^ (oldmap->GetEntry()->CosmeticParentMapID != mEntry->CosmeticParentMapID))))
1372 options &= ~TELE_TO_SEAMLESS;
1373
1374 //lets reset near teleport flag if it wasn't reset during chained teleports
1376 //setup delayed teleport flag
1378 //if teleport spell is cast in Unit::Update() func
1379 //then we need to delay it until update process will be finished
1381 {
1383 //lets save teleport destination for player
1384 m_teleport_dest = teleportLocation;
1385 m_teleport_options = options;
1386 m_teleportSpellId = teleportSpellId;
1387 return true;
1388 }
1389
1391
1392 CombatStop();
1393
1395
1396 // remove player from battleground on far teleport (when changing maps)
1397 if (Battleground const* bg = GetBattleground())
1398 {
1399 // Note: at battleground join battleground id set before teleport
1400 // and we already will found "current" battleground
1401 // just need check that this is targeted map or leave
1402 if (bg->GetMapId() != teleportLocation.Location.GetMapId())
1403 LeaveBattleground(false); // don't teleport to entry point
1404 }
1405
1406 // remove arena spell coldowns/buffs now to also remove pet's cooldowns before it's temporarily unsummoned
1407 if (mEntry->IsBattleArena() && !IsGameMaster())
1408 {
1411 if (pet)
1412 pet->RemoveArenaAuras();
1413 }
1414
1415 // remove pet on map change
1416 if (pet)
1418
1419 // remove all dyn objects
1421
1422 // remove all areatriggers entities
1424
1425 // stop spellcasting
1426 // not attempt interrupt teleportation spell at caster teleport
1427 if (!(options & TELE_TO_SPELL))
1428 if (IsNonMeleeSpellCast(true))
1430
1431 //remove auras before removing from map...
1433
1434 if (!GetSession()->PlayerLogout() && !(options & TELE_TO_SEAMLESS))
1435 {
1436 // send transfer packets
1438 transferPending.MapID = teleportLocation.Location.GetMapId();
1439 transferPending.OldMapPosition = teleportLocation.Location.GetPosition();
1440 if (teleportSpellId)
1441 transferPending.TransferSpellID = teleportSpellId;
1442 if (teleportLocation.TransportGuid.has_value())
1443 {
1444 transferPending.Ship.emplace();
1445 if (TransportSpawn const* transportSpawn = sTransportMgr->GetTransportSpawn(teleportLocation.TransportGuid->GetCounter()))
1446 {
1447 transferPending.Ship->ID = transportSpawn->TransportGameObjectId;
1448 if (dynamic_cast<Transport*>(GetTransport()))
1449 transferPending.Ship->OriginMapID = GetMapId();
1450 else
1451 transferPending.Ship->OriginMapID = -1;
1452 }
1453 }
1454
1455 SendDirectMessage(transferPending.Write());
1456
1459 }
1460
1461 // remove from old map now
1462 if (oldmap)
1463 oldmap->RemovePlayerFromMap(this, false);
1464
1465 m_teleport_dest = teleportLocation;
1466 m_teleport_options = options;
1467 m_teleportSpellId = teleportSpellId;
1469 // if the player is saved before worldportack (at logout for example)
1470 // this will be used instead of the current location in SaveToDB
1471
1472 if (!GetSession()->PlayerLogout())
1473 {
1475
1477 suspendToken.SequenceIndex = m_movementCounter; // not incrementing
1478 suspendToken.Reason = options & TELE_TO_SEAMLESS ? 2 : 1;
1479 SendDirectMessage(suspendToken.Write());
1480 }
1481
1482 // move packet sent by client always after far teleport
1483 // code for finish transfer to new map called in WorldSession::HandleMoveWorldportAckOpcode at client packet
1485 }
1486 return true;
1487}
1488
1490{
1492 return false;
1493
1497 return TeleportTo(m_bgData.joinPos);
1498}
1499
1501{
1502 if (m_DelayedOperations == 0)
1503 return;
1504
1507
1509 SaveToDB();
1510
1512 CastSpell(this, 26013, true); // Deserter
1513
1515 {
1516 if (m_bgData.mountSpell)
1517 {
1518 CastSpell(this, m_bgData.mountSpell, true);
1519 m_bgData.mountSpell = 0;
1520 }
1521 }
1522
1524 {
1525 if (m_bgData.HasTaxiPath())
1526 {
1530
1532 }
1533 }
1534
1536 {
1537 if (Group* g = GetGroup())
1538 g->SendUpdateToPlayer(GetGUID());
1539 }
1540
1541 //we have executed ALL delayed ops, so clear the flag
1543}
1544
1546{
1551
1552 for (uint8 i = PLAYER_SLOT_START; i < PLAYER_SLOT_END; ++i)
1553 if (m_items[i])
1554 m_items[i]->AddToWorld();
1555}
1556
1558{
1559 // cleanup
1560 if (IsInWorld())
1561 {
1569 m_lootRolls.clear();
1570 sOutdoorPvPMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId);
1571 sBattlefieldMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId);
1572 }
1573
1574 // Remove items from world before self - player must be found in Item::RemoveFromObjectUpdate
1575 for (uint8 i = PLAYER_SLOT_START; i < PLAYER_SLOT_END; ++i)
1576 if (m_items[i])
1578
1583
1584 for (ItemMap::iterator iter = mMitems.begin(); iter != mMitems.end(); ++iter)
1585 iter->second->RemoveFromWorld();
1586
1587 if (WorldObject* viewpoint = GetViewpoint())
1588 {
1589 TC_LOG_ERROR("entities.player", "Player::RemoveFromWorld: Player '{}' ({}) has viewpoint (Entry:{}, Type: {}) when removed from world",
1590 GetName(), GetGUID().ToString(), viewpoint->GetEntry(), viewpoint->GetTypeId());
1591 SetViewpoint(viewpoint, false);
1592 }
1593}
1594
1595void Player::SetObjectScale(float scale)
1596{
1597 Unit::SetObjectScale(scale);
1600 if (IsInWorld())
1602}
1603
1604bool Player::IsImmunedToSpellEffect(SpellInfo const* spellInfo, SpellEffectInfo const& spellEffectInfo, WorldObject const* caster,
1605 bool requireImmunityPurgesEffectAttribute /*= false*/) const
1606{
1607 // players are immune to taunt (the aura and the spell effect).
1608 if (spellEffectInfo.IsAura(SPELL_AURA_MOD_TAUNT))
1609 return true;
1610 if (spellEffectInfo.IsEffect(SPELL_EFFECT_ATTACK_ME))
1611 return true;
1612
1613 return Unit::IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster, requireImmunityPurgesEffectAttribute);
1614}
1615
1617{
1619
1620 for (Powers power = POWER_MANA; power < MAX_POWERS; power = Powers(power + 1))
1621 if (power != POWER_RUNES)
1622 Regenerate(power);
1623
1624 // Runes act as cooldowns, and they don't need to send any data
1626 {
1627 uint32 regeneratedRunes = 0;
1628 uint32 regenIndex = 0;
1629 while (regeneratedRunes < MAX_RECHARGING_RUNES && m_runes->CooldownOrder.size() > regenIndex)
1630 {
1631 uint8 runeToRegen = m_runes->CooldownOrder[regenIndex];
1632 uint32 runeCooldown = GetRuneCooldown(runeToRegen);
1633 if (runeCooldown > m_regenTimer)
1634 {
1635 SetRuneCooldown(runeToRegen, runeCooldown - m_regenTimer);
1636 ++regenIndex;
1637 }
1638 else
1639 SetRuneCooldown(runeToRegen, 0);
1640
1641 ++regeneratedRunes;
1642 }
1643 }
1644
1645 if (m_regenTimerCount >= 2000)
1646 {
1647 // Not in combat or they have regeneration
1650
1651 m_regenTimerCount -= 2000;
1652 }
1653
1654 m_regenTimer = 0;
1655}
1656
1658{
1659 // Skip regeneration for power type we cannot have
1660 uint32 powerIndex = GetPowerIndex(power);
1661 if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS)
1662 return;
1663
1666 return;
1667
1668 int32 curValue = GetPower(power);
1669
1670 // TODO: updating haste should update UnitData::PowerRegenFlatModifier for certain power types
1671 PowerTypeEntry const* powerType = sDB2Manager.GetPowerTypeEntry(power);
1672 if (!powerType)
1673 return;
1674
1675 float addvalue = 0.0f;
1676 if (!IsInCombat())
1677 {
1679 return;
1680
1681 addvalue = (powerType->RegenPeace + m_unitData->PowerRegenFlatModifier[powerIndex]) * 0.001f * m_regenTimer;
1682 }
1683 else
1684 addvalue = (powerType->RegenCombat + m_unitData->PowerRegenInterruptedFlatModifier[powerIndex]) * 0.001f * m_regenTimer;
1685
1686 static Rates const RatesForPower[MAX_POWERS] =
1687 {
1693 MAX_RATES, // runes
1698 MAX_RATES, // alternate
1702 MAX_RATES, // burning embers, unused
1703 MAX_RATES, // demonic fury, unused
1708 MAX_RATES, // runes
1709 MAX_RATES, // runes
1710 MAX_RATES, // runes
1711 MAX_RATES, // alternate
1712 MAX_RATES, // alternate
1713 MAX_RATES, // alternate
1714 };
1715
1716 if (RatesForPower[power] != MAX_RATES)
1717 addvalue *= sWorld->getRate(RatesForPower[power]);
1718
1719 // Mana regen calculated in Player::UpdateManaRegen()
1720 if (power != POWER_MANA)
1721 {
1723
1725 }
1726
1727 int32 minPower = powerType->MinPower;
1728 int32 maxPower = GetMaxPower(power);
1729
1730 if (powerType->CenterPower)
1731 {
1732 if (curValue > powerType->CenterPower)
1733 {
1734 addvalue = -std::abs(addvalue);
1735 minPower = powerType->CenterPower;
1736 }
1737 else if (curValue < powerType->CenterPower)
1738 {
1739 addvalue = std::abs(addvalue);
1740 maxPower = powerType->CenterPower;
1741 }
1742 else
1743 return;
1744 }
1745
1746 addvalue += m_powerFraction[powerIndex];
1747 int32 integerValue = int32(std::fabs(addvalue));
1748
1749 if (addvalue < 0.0f)
1750 {
1751 if (curValue <= minPower)
1752 return;
1753 }
1754 else if (addvalue > 0.0f)
1755 {
1756 if (curValue >= maxPower)
1757 return;
1758 }
1759 else
1760 return;
1761
1762 bool forcesSetPower = false;
1763 if (addvalue < 0.0f)
1764 {
1765 if (curValue > minPower + integerValue)
1766 {
1767 curValue -= integerValue;
1768 m_powerFraction[powerIndex] = addvalue + integerValue;
1769 }
1770 else
1771 {
1772 curValue = minPower;
1773 m_powerFraction[powerIndex] = 0;
1774 forcesSetPower = true;
1775 }
1776 }
1777 else
1778 {
1779 if (curValue + integerValue <= maxPower)
1780 {
1781 curValue += integerValue;
1782 m_powerFraction[powerIndex] = addvalue - integerValue;
1783 }
1784 else
1785 {
1786 curValue = maxPower;
1787 m_powerFraction[powerIndex] = 0;
1788 forcesSetPower = true;
1789 }
1790 }
1791
1793 curValue = maxPower;
1794
1795 if (m_regenTimerCount >= 2000 || forcesSetPower)
1796 SetPower(power, curValue);
1797 else
1798 {
1799 // throttle packet sending
1801 {
1802 SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Power, powerIndex), curValue);
1803 const_cast<UF::UnitData&>(*m_unitData).ClearChanged(&UF::UnitData::Power, powerIndex);
1804 });
1805 }
1806}
1807
1809{
1810 uint32 powerIndex = GetPowerIndex(power);
1811 if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS)
1812 return;
1813
1815 m_powerFraction[powerIndex] = 0.0f;
1817}
1818
1820{
1821 uint32 curValue = GetHealth();
1822 uint32 maxValue = GetMaxHealth();
1823
1824 if (curValue >= maxValue)
1825 return;
1826
1827 float HealthIncreaseRate = sWorld->getRate(RATE_HEALTH);
1828 float addValue = 0.0f;
1829
1830 // polymorphed case
1831 if (IsPolymorphed())
1832 addValue = float(GetMaxHealth()) / 3.0f;
1833 // normal regen case (maybe partly in combat case)
1835 {
1836 addValue = HealthIncreaseRate;
1837
1838 if (!IsInCombat())
1839 {
1840 if (GetLevel() < 15)
1841 addValue = (0.20f * ((float)GetMaxHealth()) / GetLevel() * HealthIncreaseRate);
1842 else
1843 addValue = 0.015f * ((float)GetMaxHealth()) * HealthIncreaseRate;
1844
1846
1847 addValue += GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * 0.4f;
1848 }
1851
1852 if (!IsStandState())
1853 addValue *= 1.5f;
1854 }
1855
1856 // always regeneration bonus (including combat)
1858 addValue += m_baseHealthRegen / 2.5f;
1859
1860 if (addValue < 0.0f)
1861 addValue = 0.0f;
1862
1863 ModifyHealth(int32(addValue));
1864}
1865
1867{
1868 SetFullHealth();
1869
1870 switch (GetPowerType())
1871 {
1872 case POWER_MANA:
1874 break;
1875 case POWER_RAGE:
1876 SetPower(POWER_RAGE, 0);
1877 break;
1878 case POWER_ENERGY:
1880 break;
1881 case POWER_RUNIC_POWER:
1883 break;
1884 case POWER_LUNAR_POWER:
1886 break;
1887 default:
1888 break;
1889 }
1890}
1891
1893{
1894 switch (questGiver->GetTypeId())
1895 {
1896 case TYPEID_UNIT:
1898 case TYPEID_GAMEOBJECT:
1899 return GetGameObjectIfCanInteractWith(questGiver->GetGUID(), GAMEOBJECT_TYPE_QUESTGIVER) != nullptr;
1900 case TYPEID_PLAYER:
1901 return IsAlive() && questGiver->ToPlayer()->IsAlive();
1902 case TYPEID_ITEM:
1903 return IsAlive();
1904 default:
1905 break;
1906 }
1907 return false;
1908}
1909
1911{
1912 // unit checks
1913 if (!guid)
1914 return nullptr;
1915
1916 if (!IsInWorld())
1917 return nullptr;
1918
1919 if (IsInFlight())
1920 return nullptr;
1921
1922 // exist (we need look pets also for some interaction (quest/etc)
1923 Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, guid);
1924 if (!creature)
1925 return nullptr;
1926
1927 // Deathstate checks
1929 return nullptr;
1930
1931 // alive or spirit healer
1933 return nullptr;
1934
1935 // appropriate npc type
1936 auto hasNpcFlags = [&]()
1937 {
1938 if (!npcFlags && !npcFlags2)
1939 return true;
1940 if (creature->HasNpcFlag(npcFlags))
1941 return true;
1942 if (creature->HasNpcFlag2(npcFlags2))
1943 return true;
1944 return false;
1945 };
1946 if (!hasNpcFlags())
1947 return nullptr;
1948
1949 // not allow interaction under control, but allow with own pets
1950 if (!creature->GetCharmerGUID().IsEmpty())
1951 return nullptr;
1952
1953 // not unfriendly/hostile
1954 if (!creature->IsInteractionAllowedWhileHostile() && creature->GetReactionTo(this) <= REP_UNFRIENDLY)
1955 return nullptr;
1956
1957 if (creature->IsInCombat() && !creature->IsInteractionAllowedInCombat())
1958 return nullptr;
1959
1960 // not too far, taken from CGGameUI::SetInteractTarget
1961 if (!creature->IsWithinDistInMap(this, creature->GetCombatReach() + 4.0f))
1962 return nullptr;
1963
1964 return creature;
1965}
1966
1968{
1969 if (!guid)
1970 return nullptr;
1971
1972 if (!IsInWorld())
1973 return nullptr;
1974
1975 if (IsInFlight())
1976 return nullptr;
1977
1978 // exist
1979 GameObject* go = ObjectAccessor::GetGameObject(*this, guid);
1980 if (!go)
1981 return nullptr;
1982
1983 // Players cannot interact with gameobjects that use the "Point" icon
1984 if (go->GetGOInfo()->IconName == "Point")
1985 return nullptr;
1986
1987 if (!go->IsWithinDistInMap(this))
1988 return nullptr;
1989
1990 return go;
1991}
1992
1994{
1996 if (!go)
1997 return nullptr;
1998
1999 if (go->GetGoType() != type)
2000 return nullptr;
2001
2002 return go;
2003}
2004
2005bool Player::IsInAreaTrigger(AreaTriggerEntry const* areaTrigger) const
2006{
2007 if (!areaTrigger)
2008 return false;
2009
2010 if (GetMapId() != areaTrigger->ContinentID && !GetPhaseShift().HasVisibleMapId(areaTrigger->ContinentID))
2011 return false;
2012
2013 if (areaTrigger->PhaseID || areaTrigger->PhaseGroupID || areaTrigger->PhaseUseFlags)
2014 if (!PhasingHandler::InDbPhaseShift(this, areaTrigger->PhaseUseFlags, areaTrigger->PhaseID, areaTrigger->PhaseGroupID))
2015 return false;
2016
2017 auto hasActionSetFlag = [=](AreaTriggerActionSetFlag flag)
2018 {
2019 if (AreaTriggerActionSetEntry const* areaTriggerActionSet = sAreaTriggerActionSetStore.LookupEntry(areaTrigger->AreaTriggerActionSetID))
2020 return areaTriggerActionSet->GetFlags().HasFlag(flag);
2021 return false;
2022 };
2023
2024 switch (getDeathState())
2025 {
2026 case DEAD:
2027 if (!hasActionSetFlag(AreaTriggerActionSetFlag::AllowWhileGhost))
2028 return false;
2029 break;
2030 case CORPSE:
2031 if (!hasActionSetFlag(AreaTriggerActionSetFlag::AllowWhileDead))
2032 return false;
2033 break;
2034 default:
2035 break;
2036 }
2037
2038 Position areaTriggerPos(areaTrigger->Pos.X, areaTrigger->Pos.Y, areaTrigger->Pos.Z, areaTrigger->BoxYaw);
2039 switch (areaTrigger->GetShapeType())
2040 {
2042 if (!IsInDist(&areaTriggerPos, areaTrigger->Radius))
2043 return false;
2044 break;
2046 if (!IsWithinBox(areaTriggerPos, areaTrigger->BoxLength / 2.f, areaTrigger->BoxWidth / 2.f, areaTrigger->BoxHeight / 2.f))
2047 return false;
2048 break;
2050 {
2051 AreaTriggerPolygon const* polygon = sObjectMgr->GetAreaTriggerPolygon(areaTrigger->ID);
2052 if (!polygon || (polygon->Height && GetPositionZ() > areaTrigger->Pos.Z + *polygon->Height) || !IsInPolygon2D(areaTriggerPos, polygon->Vertices))
2053 return false;
2054 break;
2055 }
2057 if (!IsWithinVerticalCylinder(areaTriggerPos, areaTrigger->Radius, areaTrigger->BoxHeight))
2058 return false;
2059 break;
2060 default:
2061 return false;
2062 }
2063
2064 return true;
2065}
2066
2068{
2069 if (on)
2070 {
2075
2076 if (Pet* pet = GetPet())
2077 pet->SetFaction(FACTION_FRIENDLY);
2078
2081
2083
2084 PhasingHandler::SetAlwaysVisible(this, true, false);
2086 }
2087 else
2088 {
2090
2091 m_ExtraFlags &= ~ PLAYER_EXTRA_GM_ON;
2095
2096 if (Pet* pet = GetPet())
2097 pet->SetFaction(GetFaction());
2098
2099 // restore FFA PvP Server state
2100 if (sWorld->IsFFAPvPRealm())
2102
2103 // restore FFA PvP area state, remove not allowed for GM mounts
2105
2107 }
2108
2110}
2111
2113{
2115}
2116
2118{
2119 if (on)
2120 {
2121 m_ExtraFlags &= ~PLAYER_EXTRA_GM_INVISIBLE; //remove flag
2123 }
2124 else
2125 {
2127
2128 SetAcceptWhispers(false);
2129 SetGameMaster(true);
2130
2132 }
2133
2134 for (Channel* channel : m_channels)
2135 channel->SetInvisible(this, !on);
2136}
2137
2139{
2140 switch (sWorld->getIntConfig(CONFIG_GROUP_VISIBILITY))
2141 {
2142 default: return IsInSameGroupWith(p);
2143 case 1: return IsInSameRaidWith(p);
2144 case 2: return GetEffectiveTeam() == p->GetEffectiveTeam();
2145 case 3: return false;
2146 }
2147}
2148
2150{
2151 return p == this || (GetGroup() != nullptr &&
2152 GetGroup() == p->GetGroup() &&
2153 GetGroup()->SameSubGroup(this, p));
2154}
2155
2157{
2158 return p == this || (GetGroup() != nullptr && GetGroup() == p->GetGroup());
2159}
2160
2163{
2164 Group* group = GetGroupInvite();
2165 if (!group)
2166 return;
2167
2168 group->RemoveInvite(this);
2169
2170 if (group->IsCreated())
2171 {
2172 if (group->GetMembersCount() <= 1) // group has just 1 member => disband
2173 group->Disband(true);
2174 }
2175 else
2176 {
2177 if (group->GetInviteeCount() <= 1)
2178 {
2179 group->RemoveAllInvites();
2180 delete group;
2181 }
2182 }
2183}
2184
2185void Player::RemoveFromGroup(Group* group, ObjectGuid guid, RemoveMethod method /* = GROUP_REMOVEMETHOD_DEFAULT*/, ObjectGuid kicker /* = ObjectGuid::Empty */, char const* reason /* = nullptr */)
2186{
2187 if (!group)
2188 return;
2189
2190 group->RemoveMember(guid, method, kicker, reason);
2191}
2192
2194{
2196
2197 int32 playerLevelDelta = 0;
2198
2199 // If XP < 50%, player should see scaling creature with -1 level except for level max
2200 if (GetLevel() < MAX_LEVEL && xp < uint32(*m_activePlayerData->NextLevelXP / 2))
2201 playerLevelDelta = -1;
2202
2204}
2205
2206void Player::GiveXP(uint32 xp, Unit* victim, float group_rate)
2207{
2208 if (xp < 1)
2209 return;
2210
2211 if (!IsAlive() && !GetBattlegroundId())
2212 return;
2213
2215 return;
2216
2217 if (victim && victim->GetTypeId() == TYPEID_UNIT && !victim->ToCreature()->hasLootRecipient())
2218 return;
2219
2220 uint8 level = GetLevel();
2221
2222 sScriptMgr->OnGivePlayerXP(this, xp, victim);
2223
2224 // XP to money conversion processed in Player::RewardQuest
2225 if (IsMaxLevel())
2226 return;
2227
2228 uint32 bonus_xp;
2229 bool recruitAFriend = GetsRecruitAFriendBonus(true);
2230
2231 // RaF does NOT stack with rested experience
2232 if (recruitAFriend)
2233 bonus_xp = 2 * xp; // xp + bonus_xp must add up to 3 * xp for RaF; calculation for quests done client-side
2234 else
2235 bonus_xp = victim ? _restMgr->GetRestBonusFor(REST_TYPE_XP, xp) : 0; // XP resting bonus
2236
2238 packet.Victim = victim ? victim->GetGUID() : ObjectGuid::Empty;
2239 packet.Original = xp + bonus_xp;
2241 packet.Amount = xp;
2242 packet.GroupBonus = group_rate;
2243 SendDirectMessage(packet.Write());
2244
2245 uint32 nextLvlXP = GetXPForNextLevel();
2246 uint32 newXP = GetXP() + xp + bonus_xp;
2247
2248 while (newXP >= nextLvlXP && !IsMaxLevel())
2249 {
2250 newXP -= nextLvlXP;
2251
2252 if (!IsMaxLevel())
2253 GiveLevel(level + 1);
2254
2255 level = GetLevel();
2256 nextLvlXP = GetXPForNextLevel();
2257 }
2258
2259 SetXP(newXP);
2260}
2261
2262// Update player to next level
2263// Current player experience not update (must be update by caller)
2265{
2266 uint8 oldLevel = GetLevel();
2267 if (level == oldLevel)
2268 return;
2269
2270 if (Guild* guild = GetGuild())
2271 guild->UpdateMemberData(this, GUILD_MEMBER_DATA_LEVEL, level);
2272
2273 PlayerLevelInfo info;
2274 sObjectMgr->GetPlayerLevelInfo(GetRace(), GetClass(), level, &info);
2275
2276 uint32 basemana = 0;
2277 sObjectMgr->GetPlayerClassLevelInfo(GetClass(), level, basemana);
2278
2280 packet.Level = level;
2281 packet.HealthDelta = 0;
2282
2284 // for (int i = 0; i < MAX_STORED_POWERS; ++i)
2285 packet.PowerDelta[0] = int32(basemana) - int32(GetCreateMana());
2286 packet.PowerDelta[1] = 0;
2287 packet.PowerDelta[2] = 0;
2288 packet.PowerDelta[3] = 0;
2289 packet.PowerDelta[4] = 0;
2290 packet.PowerDelta[5] = 0;
2291 packet.PowerDelta[6] = 0;
2292
2293 for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
2294 packet.StatDelta[i] = info.stats[i] - GetCreateStat(Stats(i));
2295
2297 packet.NumNewPvpTalentSlots = sDB2Manager.GetPvpTalentNumSlotsAtLevel(level, Classes(GetClass())) - sDB2Manager.GetPvpTalentNumSlotsAtLevel(oldLevel, Classes(GetClass()));
2298
2299 SendDirectMessage(packet.Write());
2300
2302
2303 //update level, max level of skills
2304 m_Played_time[PLAYED_TIME_LEVEL] = 0; // Level Played Time reset
2305
2307
2308 SetLevel(level);
2309
2313
2314 // save base values (bonuses already included in stored stats
2315 for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
2316 SetCreateStat(Stats(i), info.stats[i]);
2317
2318 SetCreateHealth(0);
2319 SetCreateMana(basemana);
2320
2323
2325
2326 _ApplyAllLevelScaleItemMods(true); // Moved to above SetFullHealth so player will have full health from Heirlooms
2327
2329 if (Item* artifact = GetItemByGuid(artifactAura->GetCastItemGUID()))
2330 artifact->CheckArtifactRelicSlotUnlock(this);
2331
2332 // Only health and mana are set to maximum.
2333 SetFullHealth();
2334 for (PowerTypeEntry const* powerType : sPowerTypeStore)
2335 if (powerType->GetFlags().HasFlag(PowerTypeFlags::SetToMaxOnLevelUp))
2336 SetFullPower(Powers(powerType->PowerTypeEnum));
2337
2338 // update level to hunter/summon pet
2339 if (Pet* pet = GetPet())
2340 pet->SynchronizeLevelWithOwner();
2341
2342 if (MailLevelReward const* mailReward = sObjectMgr->GetMailLevelReward(level, GetRace()))
2343 {
2345 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
2346 MailDraft(mailReward->mailTemplateId).SendMailTo(trans, this, MailSender(MAIL_CREATURE, uint64(mailReward->senderEntry)));
2347 CharacterDatabase.CommitTransaction(trans);
2348 }
2349
2353 if (level > oldLevel)
2354 UpdateCriteria(CriteriaType::GainLevels, level - oldLevel);
2355 if (IsMaxLevel())
2357
2358 PushQuests();
2359
2360 sScriptMgr->OnPlayerLevelChanged(this, oldLevel);
2361}
2362
2364{
2365 return GetLevel() >= m_activePlayerData->MaxLevel;
2366}
2367
2369{
2370 uint8 level = GetLevel();
2371 // talents base at level diff (talents = level - 9 but some can be used already)
2372 if (level < MIN_SPECIALIZATION_LEVEL)
2374
2375 int32 talentTiers = DB2Manager::GetNumTalentsAtLevel(level, Classes(GetClass()));
2376 if (level < 15)
2377 {
2378 // Remove all talent points
2379 ResetTalents(true);
2380 }
2381 else
2382 {
2384 for (int32 t = talentTiers; t < MAX_TALENT_TIERS; ++t)
2385 for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c)
2386 for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(GetClass(), t, c))
2387 RemoveTalent(talent);
2388 }
2389
2391
2393 for (uint8 spec = 0; spec < MAX_SPECIALIZATIONS; ++spec)
2394 for (size_t slot = sDB2Manager.GetPvpTalentNumSlotsAtLevel(level, Classes(GetClass())); slot < MAX_PVP_TALENT_SLOTS; ++slot)
2395 if (PvpTalentEntry const* pvpTalent = sPvpTalentStore.LookupEntry(GetPvpTalentMap(spec)[slot]))
2396 RemovePvpTalent(pvpTalent, spec);
2397
2398 if (!GetSession()->PlayerLoading())
2399 SendTalentsInfoData(); // update at client
2400}
2401
2402void Player::InitStatsForLevel(bool reapplyMods)
2403{
2404 if (reapplyMods) //reapply stats values only on .reset stats (level) command
2406
2407 uint32 basemana = 0;
2408 sObjectMgr->GetPlayerClassLevelInfo(GetClass(), GetLevel(), basemana);
2409
2410 PlayerLevelInfo info;
2411 sObjectMgr->GetPlayerLevelInfo(GetRace(), GetClass(), GetLevel(), &info);
2412
2413 uint8 exp_max_lvl = GetMaxLevelForExpansion(GetSession()->GetExpansion());
2414 uint8 conf_max_lvl = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
2415 if (exp_max_lvl == DEFAULT_MAX_LEVEL || exp_max_lvl >= conf_max_lvl)
2417 else
2420 if (m_activePlayerData->XP >= m_activePlayerData->NextLevelXP)
2422
2423 // reset before any aura state sources (health set/aura apply)
2425
2427
2428 // set default cast time multiplier
2429 SetModCastingSpeed(1.0f);
2430 SetModSpellHaste(1.0f);
2431 SetModHaste(1.0f);
2432 SetModRangedHaste(1.0f);
2433 SetModHasteRegen(1.0f);
2434 SetModTimeRate(1.0f);
2436
2437 // reset size before reapply auras
2438 SetObjectScale(1.0f);
2439
2440 // save base values (bonuses already included in stored stats
2441 for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
2442 SetCreateStat(Stats(i), info.stats[i]);
2443
2444 for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
2445 SetStat(Stats(i), info.stats[i]);
2446
2447 SetCreateHealth(0);
2448
2449 //set create powers
2450 SetCreateMana(basemana);
2451
2453
2455
2456 //reset rating fields values
2457 for (uint16 index = 0; index < MAX_COMBAT_RATING; ++index)
2459
2463 for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
2464 {
2469 }
2470
2472
2473 //reset attack power, damage and attack speed fields
2474 for (uint8 i = BASE_ATTACK; i < MAX_ATTACK; ++i)
2476
2483 for (uint16 i = 0; i < 3; ++i)
2484 {
2487 }
2488
2489 SetAttackPower(0);
2493
2494 // Base crit values (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset
2498
2499 // Init spell schools (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset
2501
2504
2506
2507 // Dodge percentage
2509
2510 // set armor (resistance 0) to original value (create_agility*2)
2513 // set other resistance to original value (0)
2514 for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
2515 {
2518 }
2519
2522 for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
2524
2525 // Reset no reagent cost field
2527
2528 // Init data for form but skip reapply item mods for form
2529 InitDataForForm(reapplyMods);
2530
2531 // save new stats
2532 for (uint8 i = POWER_MANA; i < MAX_POWERS; ++i)
2534
2535 SetMaxHealth(0); // stamina bonus will applied later
2536
2537 // cleanup mounted state (it will set correctly at aura loading if player saved at mount.
2539
2540 // cleanup unit flags (will be re-applied if need at aura load).
2549
2551
2552 // cleanup player flags (will be re-applied if need at aura load), to avoid have ghost flag without ghost aura, for example.
2554
2555 RemoveVisFlag(UNIT_VIS_FLAGS_ALL); // one form stealth modified bytes
2557
2558 // restore if need some important flags
2561
2562 if (reapplyMods) // reapply stats values only on .reset stats (level) command
2564
2565 // set current level health and mana/energy to maximum after applying all mods.
2566 SetFullHealth();
2573
2574 // update level to hunter/summon pet
2575 if (Pet* pet = GetPet())
2576 pet->SynchronizeLevelWithOwner();
2577}
2578
2580{
2582 knownSpells.InitialLogin = IsLoading();
2583
2584 knownSpells.KnownSpells.reserve(m_spells.size());
2585 for (PlayerSpellMap::value_type const& spell : m_spells)
2586 {
2587 if (spell.second.state == PLAYERSPELL_REMOVED)
2588 continue;
2589
2590 if (!spell.second.active || spell.second.disabled)
2591 continue;
2592
2593 knownSpells.KnownSpells.push_back(spell.first);
2594 if (spell.second.favorite)
2595 knownSpells.FavoriteSpells.push_back(spell.first);
2596 }
2597
2598 SendDirectMessage(knownSpells.Write());
2599}
2600
2602{
2604 SendDirectMessage(sendUnlearnSpells.Write());
2605}
2606
2608{
2609 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
2610 {
2611 if ((*itr)->messageID == id)
2612 {
2613 //do not delete item, because Player::removeMail() is called when returning mail to sender.
2614 m_mail.erase(itr);
2615 return;
2616 }
2617 }
2618}
2619
2620void Player::SendMailResult(uint64 mailId, MailResponseType mailAction, MailResponseResult mailError, uint32 equipError, ObjectGuid::LowType itemGuid, uint32 itemCount) const
2621{
2623
2624 result.MailID = mailId;
2625 result.Command = mailAction;
2626 result.ErrorCode = mailError;
2627
2628 if (mailError == MAIL_ERR_EQUIP_ERROR)
2629 result.BagResult = equipError;
2630 else if (mailAction == MAIL_ITEM_TAKEN)
2631 {
2632 result.AttachID = itemGuid;
2633 result.QtyInInventory = itemCount;
2634 }
2635 SendDirectMessage(result.Write());
2636}
2637
2639{
2640 // deliver undelivered mail
2642 notify.Delay = 0.0f;
2643
2644 SendDirectMessage(notify.Write());
2645}
2646
2648{
2649 // calculate next delivery time (min. from non-delivered mails
2650 // and recalculate unReadMail
2651 time_t cTime = GameTime::GetGameTime();
2653 unReadMails = 0;
2654 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
2655 {
2656 if ((*itr)->deliver_time > cTime)
2657 {
2658 if (!m_nextMailDelivereTime || m_nextMailDelivereTime > (*itr)->deliver_time)
2659 m_nextMailDelivereTime = (*itr)->deliver_time;
2660 }
2661 else if (((*itr)->checked & MAIL_CHECK_MASK_READ) == 0)
2662 ++unReadMails;
2663 }
2664}
2665
2666void Player::AddNewMailDeliverTime(time_t deliver_time)
2667{
2668 if (deliver_time <= GameTime::GetGameTime()) // ready now
2669 {
2670 ++unReadMails;
2671 SendNewMail();
2672 }
2673 else // not ready and no have ready mails
2674 {
2675 if (!m_nextMailDelivereTime || m_nextMailDelivereTime > deliver_time)
2676 m_nextMailDelivereTime = deliver_time;
2677 }
2678}
2679
2681{
2683 stmt->setUInt32(0, spellId);
2684 CharacterDatabase.Execute(stmt);
2685}
2686
2687bool Player::AddTalent(TalentEntry const* talent, uint8 spec, bool learning)
2688{
2689 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE);
2690 if (!spellInfo)
2691 {
2692 TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) does not exist.", talent->SpellID);
2693 return false;
2694 }
2695
2696 if (!SpellMgr::IsSpellValid(spellInfo, this, false))
2697 {
2698 TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) is invalid", talent->SpellID);
2699 return false;
2700 }
2701
2702 PlayerTalentMap::iterator itr = GetTalentMap(spec)->find(talent->ID);
2703 if (itr != GetTalentMap(spec)->end())
2704 itr->second = PLAYERSPELL_UNCHANGED;
2705 else
2706 (*GetTalentMap(spec))[talent->ID] = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
2707
2708 if (spec == GetActiveTalentGroup())
2709 {
2710 LearnSpell(talent->SpellID, true);
2711 if (talent->OverridesSpellID)
2712 AddOverrideSpell(talent->OverridesSpellID, talent->SpellID);
2713 }
2714
2715 if (learning)
2717
2718 return true;
2719}
2720
2722{
2723 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE);
2724 if (!spellInfo)
2725 return;
2726
2727 RemoveSpell(talent->SpellID);
2728
2729 // search for spells that the talent teaches and unlearn them
2730 for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
2731 if (spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL) && spellEffectInfo.TriggerSpell > 0)
2732 RemoveSpell(spellEffectInfo.TriggerSpell);
2733
2734 if (talent->OverridesSpellID)
2736
2737 // if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted
2738 PlayerTalentMap::iterator plrTalent = GetTalentMap(GetActiveTalentGroup())->find(talent->ID);
2739 if (plrTalent != GetTalentMap(GetActiveTalentGroup())->end())
2740 plrTalent->second = PLAYERSPELL_REMOVED;
2741}
2742
2744{
2746 storedLocation.Loc.WorldRelocate(this);
2748}
2749
2751{
2753 storedLocation->State = StoredAuraTeleportLocation::DELETED;
2754}
2755
2757{
2759 return &auraLocation->Loc;
2760
2761 return nullptr;
2762}
2763
2764bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading /*= false*/, int32 fromSkill /*= 0*/, bool favorite /*= false*/, Optional<int32> traitDefinitionId /*= {}*/)
2765{
2766 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE);
2767 if (!spellInfo)
2768 {
2769 // do character spell book cleanup (all characters)
2770 if (!IsInWorld() && !learning) // spell load case
2771 {
2772 TC_LOG_ERROR("spells", "Player::AddSpell: Spell (ID: {}) does not exist. deleting for all characters in `character_spell`.", spellId);
2773
2775 }
2776 else
2777 TC_LOG_ERROR("spells", "Player::AddSpell: Spell (ID: {}) does not exist", spellId);
2778
2779 return false;
2780 }
2781
2782 if (!SpellMgr::IsSpellValid(spellInfo, this, false))
2783 {
2784 // do character spell book cleanup (all characters)
2785 if (!IsInWorld() && !learning) // spell load case
2786 {
2787 TC_LOG_ERROR("spells", "Player::AddSpell: Spell (ID: {}) is invalid. deleting for all characters in `character_spell`.", spellId);
2788
2790 }
2791 else
2792 TC_LOG_ERROR("spells", "Player::AddSpell: Spell (ID: {}) is invalid", spellId);
2793
2794 return false;
2795 }
2796
2798
2799 bool dependent_set = false;
2800 bool disabled_case = false;
2801 bool superceded_old = false;
2802
2803 PlayerSpellMap::iterator itr = m_spells.find(spellId);
2804
2805 // Remove temporary spell if found to prevent conflicts
2806 if (itr != m_spells.end() && itr->second.state == PLAYERSPELL_TEMPORARY)
2807 RemoveTemporarySpell(spellId);
2808 else if (itr != m_spells.end())
2809 {
2810 uint32 next_active_spell_id = 0;
2811 // fix activate state for non-stackable low rank (and find next spell for !active case)
2812 if (spellInfo->IsRanked())
2813 {
2814 if (uint32 next = sSpellMgr->GetNextSpellInChain(spellId))
2815 {
2816 if (HasSpell(next))
2817 {
2818 // high rank already known so this must !active
2819 active = false;
2820 next_active_spell_id = next;
2821 }
2822 }
2823 }
2824
2825 // not do anything if already known in expected state
2826 if (itr->second.state != PLAYERSPELL_REMOVED && itr->second.active == active &&
2827 itr->second.dependent == dependent && itr->second.disabled == disabled)
2828 {
2829 if (!IsInWorld() && !learning) // explicitly load from DB and then exist in it already and set correctly
2830 itr->second.state = PLAYERSPELL_UNCHANGED;
2831
2832 return false;
2833 }
2834
2835 // dependent spell known as not dependent, overwrite state
2836 if (itr->second.state != PLAYERSPELL_REMOVED && !itr->second.dependent && dependent)
2837 {
2838 itr->second.dependent = dependent;
2839 if (itr->second.state != PLAYERSPELL_NEW)
2840 itr->second.state = PLAYERSPELL_CHANGED;
2841 dependent_set = true;
2842 }
2843
2844 if (itr->second.TraitDefinitionId != traitDefinitionId)
2845 {
2846 if (itr->second.TraitDefinitionId)
2847 if (TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(*itr->second.TraitDefinitionId))
2848 RemoveOverrideSpell(traitDefinition->OverridesSpellID, spellId);
2849
2850 itr->second.TraitDefinitionId = traitDefinitionId;
2851 }
2852
2853 itr->second.favorite = favorite;
2854
2855 // update active state for known spell
2856 if (itr->second.active != active && itr->second.state != PLAYERSPELL_REMOVED && !itr->second.disabled)
2857 {
2858 itr->second.active = active;
2859
2860 if (!IsInWorld() && !learning && !dependent_set) // explicitly load from DB and then exist in it already and set correctly
2861 itr->second.state = PLAYERSPELL_UNCHANGED;
2862 else if (itr->second.state != PLAYERSPELL_NEW)
2863 itr->second.state = PLAYERSPELL_CHANGED;
2864
2865 if (active)
2866 {
2867 if (spellInfo->IsPassive() && HandlePassiveSpellLearn(spellInfo))
2868 CastSpell(this, spellId, true);
2869 }
2870 else if (IsInWorld())
2871 {
2872 if (next_active_spell_id)
2873 SendSupercededSpell(spellId, next_active_spell_id);
2874 else
2875 {
2877 unlearnedSpells.SpellID.push_back(spellId);
2878 SendDirectMessage(unlearnedSpells.Write());
2879 }
2880 }
2881
2882 return active; // learn (show in spell book if active now)
2883 }
2884
2885 if (itr->second.disabled != disabled && itr->second.state != PLAYERSPELL_REMOVED)
2886 {
2887 if (itr->second.state != PLAYERSPELL_NEW)
2888 itr->second.state = PLAYERSPELL_CHANGED;
2889 itr->second.disabled = disabled;
2890
2891 if (disabled)
2892 return false;
2893
2894 disabled_case = true;
2895 }
2896 else switch (itr->second.state)
2897 {
2898 case PLAYERSPELL_UNCHANGED: // known saved spell
2899 return false;
2900 case PLAYERSPELL_REMOVED: // re-learning removed not saved spell
2901 {
2902 m_spells.erase(itr);
2903 state = PLAYERSPELL_CHANGED;
2904 break; // need re-add
2905 }
2906 default: // known not saved yet spell (new or modified)
2907 {
2908 // can be in case spell loading but learned at some previous spell loading
2909 if (!IsInWorld() && !learning && !dependent_set)
2910 itr->second.state = PLAYERSPELL_UNCHANGED;
2911
2912 return false;
2913 }
2914 }
2915 }
2916
2917 if (!disabled_case) // skip new spell adding if spell already known (disabled spells case)
2918 {
2919 // non talent spell: learn low ranks (recursive call)
2920 if (uint32 prev_spell = sSpellMgr->GetPrevSpellInChain(spellId))
2921 {
2922 if (!IsInWorld() || disabled) // at spells loading, no output, but allow save
2923 AddSpell(prev_spell, active, true, true, disabled, false, fromSkill);
2924 else // at normal learning
2925 LearnSpell(prev_spell, true, fromSkill);
2926 }
2927
2928 std::pair<PlayerSpellMap::iterator, bool> inserted = m_spells.emplace(std::piecewise_construct, std::forward_as_tuple(spellId), std::forward_as_tuple());
2929 PlayerSpell& newspell = inserted.first->second;
2930 // learning a previous rank might have given us this spell already from a skill autolearn, most likely with PLAYERSPELL_NEW state
2931 // we dont want to do double insert if this happened during load from db so we force state to CHANGED, just in case
2932 newspell.state = inserted.second ? state : PLAYERSPELL_CHANGED;
2933 newspell.active = active;
2934 newspell.dependent = dependent;
2935 newspell.disabled = disabled;
2936 newspell.favorite = favorite;
2937 if (traitDefinitionId)
2938 newspell.TraitDefinitionId = *traitDefinitionId;
2939
2940 // replace spells in action bars and spellbook to bigger rank if only one spell rank must be accessible
2941 if (newspell.active && !newspell.disabled && spellInfo->IsRanked())
2942 {
2943 for (PlayerSpellMap::iterator itr2 = m_spells.begin(); itr2 != m_spells.end(); ++itr2)
2944 {
2945 if (itr2->second.state == PLAYERSPELL_REMOVED)
2946 continue;
2947
2948 SpellInfo const* i_spellInfo = sSpellMgr->GetSpellInfo(itr2->first, DIFFICULTY_NONE);
2949 if (!i_spellInfo)
2950 continue;
2951
2952 if (spellInfo->IsDifferentRankOf(i_spellInfo))
2953 {
2954 if (itr2->second.active)
2955 {
2956 if (spellInfo->IsHighRankOf(i_spellInfo))
2957 {
2958 if (IsInWorld()) // not send spell (re-/over-)learn packets at loading
2959 SendSupercededSpell(itr2->first, spellId);
2960
2961 // mark old spell as disable (SMSG_SUPERCEDED_SPELL replace it in client by new)
2962 itr2->second.active = false;
2963 if (itr2->second.state != PLAYERSPELL_NEW)
2964 itr2->second.state = PLAYERSPELL_CHANGED;
2965 superceded_old = true; // new spell replace old in action bars and spell book.
2966 }
2967 else
2968 {
2969 if (IsInWorld()) // not send spell (re-/over-)learn packets at loading
2970 SendSupercededSpell(spellId, itr2->first);
2971
2972 // mark new spell as disable (not learned yet for client and will not learned)
2973 newspell.active = false;
2974 if (newspell.state != PLAYERSPELL_NEW)
2975 newspell.state = PLAYERSPELL_CHANGED;
2976 }
2977 }
2978 }
2979 }
2980 }
2981
2982 // return false if spell disabled
2983 if (newspell.disabled)
2984 return false;
2985 }
2986
2987 bool castSpell = false;
2988
2989 // cast talents with SPELL_EFFECT_LEARN_SPELL (other dependent spells will learned later as not auto-learned)
2990 // note: all spells with SPELL_EFFECT_LEARN_SPELL isn't passive
2991 if (!loading && spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT) && spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL))
2992 // ignore stance requirement for talent learn spell (stance set for spell only for client spell description show)
2993 castSpell = true;
2994 // also cast passive spells (including all talents without SPELL_EFFECT_LEARN_SPELL) with additional checks
2995 else if (spellInfo->IsPassive())
2996 castSpell = HandlePassiveSpellLearn(spellInfo);
2997 else if (spellInfo->HasEffect(SPELL_EFFECT_SKILL_STEP))
2998 castSpell = true;
2999 else if (spellInfo->HasAttribute(SPELL_ATTR1_CAST_WHEN_LEARNED))
3000 castSpell = true;
3001
3002 if (castSpell)
3003 {
3004 CastSpellExtraArgs args;
3006
3007 if (traitDefinitionId)
3008 {
3009 if (UF::TraitConfig const* traitConfig = GetTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID))
3010 {
3011 int32 traitEntryIndex = traitConfig->Entries.FindIndexIf([traitDefinitionId](UF::TraitEntry const& traitEntry)
3012 {
3013 return sTraitNodeEntryStore.AssertEntry(traitEntry.TraitNodeEntryID)->TraitDefinitionID == traitDefinitionId;
3014 });
3015 int32 rank = 0;
3016 if (traitEntryIndex >= 0)
3017 rank = traitConfig->Entries[traitEntryIndex].Rank + traitConfig->Entries[traitEntryIndex].GrantedRanks;
3018
3019 if (rank > 0)
3020 {
3021 if (std::vector<TraitDefinitionEffectPointsEntry const*> const* traitDefinitionEffectPoints = TraitMgr::GetTraitDefinitionEffectPointModifiers(*traitDefinitionId))
3022 {
3023 for (TraitDefinitionEffectPointsEntry const* traitDefinitionEffectPoint : *traitDefinitionEffectPoints)
3024 {
3025 if (traitDefinitionEffectPoint->EffectIndex >= int32(spellInfo->GetEffects().size()))
3026 continue;
3027
3028 float basePoints = sDB2Manager.GetCurveValueAt(traitDefinitionEffectPoint->CurveID, rank);
3029 if (traitDefinitionEffectPoint->GetOperationType() == TraitPointsOperationType::Multiply)
3030 basePoints *= spellInfo->GetEffect(SpellEffIndex(traitDefinitionEffectPoint->EffectIndex)).CalcBaseValue(this, nullptr, 0, -1);
3031
3032 args.AddSpellMod(SpellValueMod(SPELLVALUE_BASE_POINT0 + traitDefinitionEffectPoint->EffectIndex), basePoints);
3033 }
3034 }
3035 }
3036 }
3037 }
3038
3039 CastSpell(this, spellId, args);
3040 if (spellInfo->HasEffect(SPELL_EFFECT_SKILL_STEP))
3041 return false;
3042 }
3043
3044 if (traitDefinitionId)
3045 if (TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(*traitDefinitionId))
3046 if (traitDefinition->OverridesSpellID)
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.Favorite = 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 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id, DIFFICULTY_NONE);
3307
3308 // remove pet auras
3309 for (uint8 i = 0; i < spellInfo->GetEffects().size(); ++i)
3310 if (PetAura const* petSpell = sSpellMgr->GetPetAura(spell_id, i))
3311 RemovePetAura(petSpell);
3312
3313 // update free primary prof.points (if not overflow setting, can be in case GM use before .learn prof. learning)
3314 if (spellInfo && spellInfo->IsPrimaryProfessionFirstRank())
3315 {
3316 uint32 freeProfs = GetFreePrimaryProfessionPoints()+1;
3317 if (freeProfs <= sWorld->getIntConfig(CONFIG_MAX_PRIMARY_TRADE_SKILL))
3318 SetFreePrimaryProfessions(freeProfs);
3319 }
3320
3321 // remove dependent skill
3322 SpellLearnSkillNode const* spellLearnSkill = sSpellMgr->GetSpellLearnSkill(spell_id);
3323 if (spellLearnSkill)
3324 {
3325 uint32 prev_spell = sSpellMgr->GetPrevSpellInChain(spell_id);
3326 if (!prev_spell) // first rank, remove skill
3327 SetSkill(spellLearnSkill->skill, 0, 0, 0);
3328 else
3329 {
3330 // search prev. skill setting by spell ranks chain
3331 SpellLearnSkillNode const* prevSkill = sSpellMgr->GetSpellLearnSkill(prev_spell);
3332 while (!prevSkill && prev_spell)
3333 {
3334 prev_spell = sSpellMgr->GetPrevSpellInChain(prev_spell);
3335 prevSkill = sSpellMgr->GetSpellLearnSkill(sSpellMgr->GetFirstSpellInChain(prev_spell));
3336 }
3337
3338 if (!prevSkill) // not found prev skill setting, remove skill
3339 SetSkill(spellLearnSkill->skill, 0, 0, 0);
3340 else // set to prev. skill setting values
3341 {
3342 uint16 skill_value = GetPureSkillValue(prevSkill->skill);
3343 uint16 skill_max_value = GetPureMaxSkillValue(prevSkill->skill);
3344
3345 uint16 new_skill_max_value = prevSkill->maxvalue;
3346
3347 if (new_skill_max_value == 0)
3348 {
3349 if (SkillRaceClassInfoEntry const* rcInfo = sDB2Manager.GetSkillRaceClassInfo(prevSkill->skill, GetRace(), GetClass()))
3350 {
3351 switch (GetSkillRangeType(rcInfo))
3352 {
3354 skill_value = 300;
3355 new_skill_max_value = 300;
3356 break;
3357 case SKILL_RANGE_LEVEL:
3358 new_skill_max_value = GetMaxSkillValueForLevel();
3359 break;
3360 case SKILL_RANGE_MONO:
3361 new_skill_max_value = 1;
3362 break;
3363 case SKILL_RANGE_RANK:
3364 {
3365 SkillTiersEntry const* tier = sObjectMgr->GetSkillTier(rcInfo->SkillTierID);
3366 new_skill_max_value = tier->GetValueForTierIndex(prevSkill->step - 1);
3367 break;
3368 }
3369 default:
3370 break;
3371 }
3372
3373 if (rcInfo->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE)
3374 skill_value = new_skill_max_value;
3375 }
3376 }
3377 else if (skill_value > prevSkill->value)
3378 skill_value = prevSkill->value;
3379
3380 if (skill_max_value > new_skill_max_value)
3381 skill_max_value = new_skill_max_value;
3382
3383 if (skill_value > new_skill_max_value)
3384 skill_value = new_skill_max_value;
3385
3386 SetSkill(prevSkill->skill, prevSkill->step, skill_value, skill_max_value);
3387 }
3388 }
3389 }
3390
3391 // remove dependent spells
3392 SpellLearnSpellMapBounds spell_bounds = sSpellMgr->GetSpellLearnSpellMapBounds(spell_id);
3393
3394 for (SpellLearnSpellMap::const_iterator itr2 = spell_bounds.first; itr2 != spell_bounds.second; ++itr2)
3395 {
3396 bool hasOtherSpellTeachingThis = std::ranges::any_of(sSpellMgr->GetSpellLearnedBySpellMapBounds(itr2->second.Spell), [&](SpellLearnSpellNode const* learnNode)
3397 {
3398 if (learnNode->SourceSpell == spell_id)
3399 return false;
3400 if (!learnNode->Active)
3401 return false;
3402 return HasSpell(learnNode->SourceSpell);
3404
3405 if (hasOtherSpellTeachingThis)
3406 continue;
3407
3408 RemoveSpell(itr2->second.Spell, disabled);
3409 if (itr2->second.OverridesSpell)
3410 RemoveOverrideSpell(itr2->second.OverridesSpell, itr2->second.Spell);
3411 }
3412
3413 // activate lesser rank in spellbook/action bar, and cast it if need
3414 bool prev_activate = false;
3415
3416 if (uint32 prev_id = sSpellMgr->GetPrevSpellInChain(spell_id))
3417 {
3418 // if ranked non-stackable spell: need activate lesser rank and update dendence state
3420 if (cur_active && spellInfo->IsRanked())
3421 {
3422 // need manually update dependence state (learn spell ignore like attempts)
3423 PlayerSpellMap::iterator prev_itr = m_spells.find(prev_id);
3424 if (prev_itr != m_spells.end())
3425 {
3426 if (prev_itr->second.dependent != cur_dependent)
3427 {
3428 prev_itr->second.dependent = cur_dependent;
3429 if (prev_itr->second.state != PLAYERSPELL_NEW)
3430 prev_itr->second.state = PLAYERSPELL_CHANGED;
3431 }
3432
3433 // now re-learn if need re-activate
3434 if (!prev_itr->second.active && learn_low_rank)
3435 {
3436 if (AddSpell(prev_id, true, false, prev_itr->second.dependent, prev_itr->second.disabled))
3437 {
3438 // downgrade spell ranks in spellbook and action bar
3439 SendSupercededSpell(spell_id, prev_id);
3440 prev_activate = true;
3441 }
3442 }
3443 }
3444 }
3445 }
3446
3447 if (traitDefinitionId)
3448 if (TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(*traitDefinitionId))
3449 RemoveOverrideSpell(traitDefinition->OverridesSpellID, spell_id);
3450
3451 m_overrideSpells.erase(spell_id);
3452
3453 if (m_canTitanGrip)
3454 {
3455 if (spellInfo && spellInfo->IsPassive() && spellInfo->HasEffect(SPELL_EFFECT_TITAN_GRIP))
3456 {
3458 SetCanTitanGrip(false);
3459 }
3460 }
3461
3462 if (m_canDualWield)
3463 {
3464 if (spellInfo && spellInfo->IsPassive() && spellInfo->HasEffect(SPELL_EFFECT_DUAL_WIELD))
3465 SetCanDualWield(false);
3466 }
3467
3470
3471 // remove from spell book if not replaced by lesser rank
3472 if (!prev_activate)
3473 {
3475 unlearnedSpells.SpellID.push_back(spell_id);
3476 unlearnedSpells.SuppressMessaging = suppressMessaging;
3477 SendDirectMessage(unlearnedSpells.Write());
3478 }
3479}
3480
3481void Player::SetSpellFavorite(uint32 spellId, bool favorite)
3482{
3483 auto itr = m_spells.find(spellId);
3484 if (itr == m_spells.end())
3485 return;
3486
3487 itr->second.favorite = favorite;
3488 if (itr->second.state == PLAYERSPELL_UNCHANGED)
3489 itr->second.state = PLAYERSPELL_CHANGED;
3490}
3491
3492void Player::RemoveArenaSpellCooldowns(bool removeActivePetCooldowns)
3493{
3494 // remove cooldowns on spells that have < 10 min CD
3496 {
3497 SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(cooldownEntry.SpellId, DIFFICULTY_NONE);
3498 SpellHistory::Duration cooldown = 0s;
3499 SpellHistory::Duration categoryCooldown = 0s;
3500 SpellHistory::GetCooldownDurations(spellInfo, cooldownEntry.ItemId, &cooldown, nullptr, &categoryCooldown);
3501 return cooldown < 10min
3502 && categoryCooldown < 10min
3504 }, true);
3505
3506 // pet cooldowns
3507 if (removeActivePetCooldowns)
3508 if (Pet* pet = GetPet())
3509 pet->GetSpellHistory()->ResetAllCooldowns();
3510}
3511
3513{
3514 // The first time reset costs 1 gold
3515 if (GetTalentResetCost() < 1*GOLD)
3516 return 1*GOLD;
3517 // then 5 gold
3518 else if (GetTalentResetCost() < 5*GOLD)
3519 return 5*GOLD;
3520 // After that it increases in increments of 5 gold
3521 else if (GetTalentResetCost() < 10*GOLD)
3522 return 10*GOLD;
3523 else
3524 {
3526 if (months > 0)
3527 {
3528 // This cost will be reduced by a rate of 5 gold per month
3529 int32 new_cost = int32(GetTalentResetCost() - 5*GOLD*months);
3530 // to a minimum of 10 gold.
3531 return (new_cost < 10*GOLD ? 10*GOLD : new_cost);
3532 }
3533 else
3534 {
3535 // After that it increases in increments of 5 gold
3536 int32 new_cost = GetTalentResetCost() + 5*GOLD;
3537 // until it hits a cap of 50 gold.
3538 if (new_cost > 50*GOLD)
3539 new_cost = 50*GOLD;
3540 return new_cost;
3541 }
3542 }
3543}
3544
3545bool Player::ResetTalents(bool noCost)
3546{
3547 sScriptMgr->OnPlayerTalentsReset(this, noCost);
3548
3549 // not need after this call
3552
3553 uint32 cost = 0;
3554
3555 if (!noCost && !sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST))
3556 {
3557 cost = GetNextResetTalentsCost();
3558
3559 if (!HasEnoughMoney(uint64(cost)))
3560 {
3562 return false;
3563 }
3564 }
3565
3566 RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true);
3567
3568 for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
3569 {
3570 TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
3571 if (!talentInfo)
3572 continue;
3573
3574 // unlearn only talents for character class
3575 // some spell learned by one class as normal spells or know at creation but another class learn it as talent,
3576 // to prevent unexpected lost normal learned spell skip another class talents
3577 if (talentInfo->ClassID != GetClass())
3578 continue;
3579
3580 // skip non-existent talent ranks
3581 if (talentInfo->SpellID == 0)
3582 continue;
3583
3584 RemoveTalent(talentInfo);
3585 }
3586
3587 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
3588 _SaveTalents(trans);
3589 _SaveSpells(trans);
3590 CharacterDatabase.CommitTransaction(trans);
3591
3592 if (!noCost)
3593 {
3594 ModifyMoney(-(int64)cost);
3597
3598 SetTalentResetCost(cost);
3600 }
3601
3602 /* when prev line will dropped use next line
3603 if (Pet* pet = GetPet())
3604 {
3605 if (pet->getPetType() == HUNTER_PET && !pet->GetCreatureTemplate()->IsTameable(CanTameExoticPets()))
3606 RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true);
3607 }
3608 */
3609
3610 return true;
3611}
3612
3614{
3615 for (uint8 spec = 0; spec < MAX_SPECIALIZATIONS; ++spec)
3616 for (uint32 talentId : GetPvpTalentMap(spec))
3617 if (PvpTalentEntry const* talentInfo = sPvpTalentStore.LookupEntry(talentId))
3618 RemovePvpTalent(talentInfo, spec);
3619}
3620
3622{
3623 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
3624 if ((*itr)->messageID == id)
3625 return (*itr);
3626
3627 return nullptr;
3628}
3629
3631{
3632 if (target == this)
3633 {
3634 for (uint8 i = EQUIPMENT_SLOT_START; i < BANK_SLOT_BAG_END; ++i)
3635 {
3636 if (m_items[i] == nullptr)
3637 continue;
3638
3639 m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
3640 }
3641
3643 {
3644 if (m_items[i] == nullptr)
3645 continue;
3646
3647 m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
3648 }
3649 }
3650
3652}
3653
3655{
3657 if (IsInSameRaidWith(target))
3659
3660 return flags;
3661}
3662
3664{
3665 m_objectData->WriteCreate(*data, flags, this, target);
3666 m_unitData->WriteCreate(*data, flags, this, target);
3667 m_playerData->WriteCreate(*data, flags, this, target);
3668 if (target == this)
3669 m_activePlayerData->WriteCreate(*data, flags, this, target);
3670}
3671
3673{
3674 *data << uint32(m_values.GetChangedObjectTypeMask() & ~(uint32(target != this) << TYPEID_ACTIVE_PLAYER));
3675
3677 m_objectData->WriteUpdate(*data, flags, this, target);
3678
3680 m_unitData->WriteUpdate(*data, flags, this, target);
3681
3683 m_playerData->WriteUpdate(*data, flags, this, target);
3684
3685 if (target == this && m_values.HasChanged(TYPEID_ACTIVE_PLAYER))
3686 m_activePlayerData->WriteUpdate(*data, flags, this, target);
3687}
3688
3690{
3692 valuesMask.Set(TYPEID_UNIT);
3693 valuesMask.Set(TYPEID_PLAYER);
3694
3695 *data << uint32(valuesMask.GetBlock(0));
3696
3697 UF::UnitData::Mask mask;
3698 m_unitData->AppendAllowedFieldsMaskForFlag(mask, flags);
3699 m_unitData->WriteUpdate(*data, mask, true, this, target);
3700
3702 m_playerData->AppendAllowedFieldsMaskForFlag(mask2, flags);
3703 m_playerData->WriteUpdate(*data, mask2, true, this, target);
3704}
3705
3707 UF::UnitData::Mask const& requestedUnitMask, UF::PlayerData::Mask const& requestedPlayerMask,
3708 UF::ActivePlayerData::Mask const& requestedActivePlayerMask, Player const* target) const
3709{
3712 if (requestedObjectMask.IsAnySet())
3713 valuesMask.Set(TYPEID_OBJECT);
3714
3715 UF::UnitData::Mask unitMask = requestedUnitMask;
3716 m_unitData->FilterDisallowedFieldsMaskForFlag(unitMask, flags);
3717 if (unitMask.IsAnySet())
3718 valuesMask.Set(TYPEID_UNIT);
3719
3720 UF::PlayerData::Mask playerMask = requestedPlayerMask;
3721 m_playerData->FilterDisallowedFieldsMaskForFlag(playerMask, flags);
3722 if (playerMask.IsAnySet())
3723 valuesMask.Set(TYPEID_PLAYER);
3724
3725 if (target == this && requestedActivePlayerMask.IsAnySet())
3726 valuesMask.Set(TYPEID_ACTIVE_PLAYER);
3727
3728 ByteBuffer& buffer = PrepareValuesUpdateBuffer(data);
3729 std::size_t sizePos = buffer.wpos();
3730 buffer << uint32(0);
3732 buffer << uint32(valuesMask.GetBlock(0));
3733
3734 if (valuesMask[TYPEID_OBJECT])
3735 m_objectData->WriteUpdate(buffer, requestedObjectMask, true, this, target);
3736
3737 if (valuesMask[TYPEID_UNIT])
3738 m_unitData->WriteUpdate(buffer, unitMask, true, this, target);
3739
3740 if (valuesMask[TYPEID_PLAYER])
3741 m_playerData->WriteUpdate(buffer, playerMask, true, this, target);
3742
3743 if (valuesMask[TYPEID_ACTIVE_PLAYER])
3744 m_activePlayerData->WriteUpdate(buffer, requestedActivePlayerMask, true, this, target);
3745
3746 buffer.put<uint32>(sizePos, buffer.wpos() - sizePos - 4);
3747
3748 data->AddUpdateBlock();
3749}
3750
3752{
3753 UpdateData udata(Owner->GetMapId());
3754 WorldPacket packet;
3755
3758
3759 udata.BuildPacket(&packet);
3760 player->SendDirectMessage(&packet);
3761}
3762
3764{
3765 Unit::DestroyForPlayer(target);
3766
3767 if (target == this)
3768 {
3769 for (uint8 i = EQUIPMENT_SLOT_START; i < BANK_SLOT_BAG_END; ++i)
3770 {
3771 if (m_items[i] == nullptr)
3772 continue;
3773
3774 m_items[i]->DestroyForPlayer(target);
3775 }
3776
3778 {
3779 if (m_items[i] == nullptr)
3780 continue;
3781
3782 m_items[i]->DestroyForPlayer(target);
3783 }
3784 }
3785}
3786
3788{
3791 Unit::ClearUpdateMask(remove);
3792}
3793
3794bool Player::HasSpell(uint32 spell) const
3795{
3796 PlayerSpellMap::const_iterator itr = m_spells.find(spell);
3797 return (itr != m_spells.end() && itr->second.state != PLAYERSPELL_REMOVED &&
3798 !itr->second.disabled);
3799}
3800
3801bool Player::HasTalent(uint32 talentId, uint8 group) const
3802{
3803 PlayerTalentMap::const_iterator itr = GetTalentMap(group)->find(talentId);
3804 return (itr != GetTalentMap(group)->end() && itr->second != PLAYERSPELL_REMOVED);
3805}
3806
3808{
3809 PlayerSpellMap::const_iterator itr = m_spells.find(spell);
3810 return (itr != m_spells.end() && itr->second.state != PLAYERSPELL_REMOVED &&
3811 itr->second.active && !itr->second.disabled);
3812}
3813
3826void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRealmChars, bool deleteFinally)
3827{
3828 // Avoid realm-update for non-existing account
3829 if (accountId == 0)
3830 updateRealmChars = false;
3831
3832 // Convert guid to low GUID for CharacterNameData, but also other methods on success
3833 ObjectGuid::LowType guid = playerguid.GetCounter();
3834 uint32 charDeleteMethod = sWorld->getIntConfig(CONFIG_CHARDELETE_METHOD);
3835 CharacterCacheEntry const* characterInfo = sCharacterCache->GetCharacterCacheByGuid(playerguid);
3836 std::string name;
3837 if (characterInfo)
3838 name = characterInfo->Name;
3839
3840 if (deleteFinally)
3841 charDeleteMethod = CHAR_DELETE_REMOVE;
3842 else if (characterInfo) // To avoid a query, we select loaded data. If it doesn't exist, return.
3843 {
3844 // Define the required variables
3845 uint32 charDeleteMinLvl;
3846
3847 if (characterInfo->Class == CLASS_DEATH_KNIGHT)
3848 charDeleteMinLvl = sWorld->getIntConfig(CONFIG_CHARDELETE_DEATH_KNIGHT_MIN_LEVEL);
3849 else if (characterInfo->Class == CLASS_DEMON_HUNTER)
3850 charDeleteMinLvl = sWorld->getIntConfig(CONFIG_CHARDELETE_DEMON_HUNTER_MIN_LEVEL);
3851 else
3852 charDeleteMinLvl = sWorld->getIntConfig(CONFIG_CHARDELETE_MIN_LEVEL);
3853
3854 // if we want to finalize the character removal or the character does not meet the level requirement of either heroic or non-heroic settings,
3855 // we set it to mode CHAR_DELETE_REMOVE
3856 if (characterInfo->Level < charDeleteMinLvl)
3857 charDeleteMethod = CHAR_DELETE_REMOVE;
3858 }
3859
3860 LoginDatabaseTransaction loginTransaction = LoginDatabase.BeginTransaction();
3861 LoginDatabasePreparedStatement* loginStmt = nullptr;
3862
3863 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
3864 if (ObjectGuid::LowType guildId = sCharacterCache->GetCharacterGuildIdByGuid(playerguid))
3865 if (Guild* guild = sGuildMgr->GetGuildById(guildId))
3866 guild->DeleteMember(trans, playerguid, false, false);
3867
3868 // remove from arena teams
3869 LeaveAllArenaTeams(playerguid);
3870
3871 // the player was uninvited already on logout so just remove from group
3873 stmt->setUInt64(0, guid);
3874 PreparedQueryResult resultGroup = CharacterDatabase.Query(stmt);
3875
3876 if (resultGroup)
3877 if (Group* group = sGroupMgr->GetGroupByDbStoreId((*resultGroup)[0].GetUInt32()))
3878 RemoveFromGroup(group, playerguid);
3879
3880 // Remove signs from petitions (also remove petitions if owner);
3881 RemovePetitionsAndSigns(playerguid);
3882
3883 switch (charDeleteMethod)
3884 {
3885 // Completely remove from the database
3886 case CHAR_DELETE_REMOVE:
3887 {
3888 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_COD_ITEM_MAIL);
3889 stmt->setUInt64(0, guid);
3890 PreparedQueryResult resultMail = CharacterDatabase.Query(stmt);
3891
3892 if (resultMail)
3893 {
3894 std::unordered_map<uint64, std::vector<Item*>> itemsByMail;
3895
3896 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS);
3897 stmt->setUInt64(0, guid);
3898 PreparedQueryResult resultItems = CharacterDatabase.Query(stmt);
3899
3900 if (resultItems)
3901 {
3902 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS_ARTIFACT);
3903 stmt->setUInt64(0, guid);
3904 PreparedQueryResult artifactResult = CharacterDatabase.Query(stmt);
3905
3906 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS_AZERITE);
3907 stmt->setUInt64(0, guid);
3908 PreparedQueryResult azeriteResult = CharacterDatabase.Query(stmt);
3909
3911 stmt->setUInt64(0, guid);
3912 PreparedQueryResult azeriteItemMilestonePowersResult = CharacterDatabase.Query(stmt);
3913
3915 stmt->setUInt64(0, guid);
3916 PreparedQueryResult azeriteItemUnlockedEssencesResult = CharacterDatabase.Query(stmt);
3917
3918 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS_AZERITE_EMPOWERED);
3919 stmt->setUInt64(0, guid);
3920 PreparedQueryResult azeriteEmpoweredItemResult = CharacterDatabase.Query(stmt);
3921
3922 std::unordered_map<ObjectGuid::LowType, ItemAdditionalLoadInfo> additionalData;
3923 ItemAdditionalLoadInfo::Init(&additionalData, artifactResult, azeriteResult, azeriteItemMilestonePowersResult,
3924 azeriteItemUnlockedEssencesResult, azeriteEmpoweredItemResult);
3925
3926 do
3927 {
3928 Field* fields = resultItems->Fetch();
3929 uint64 mailId = fields[53].GetUInt64();
3930 if (Item* mailItem = _LoadMailedItem(playerguid, nullptr, mailId, nullptr, fields, Trinity::Containers::MapGetValuePtr(additionalData, fields[0].GetUInt64())))
3931 itemsByMail[mailId].push_back(mailItem);
3932
3933 } while (resultItems->NextRow());
3934 }
3935
3936 do
3937 {
3938 Field* mailFields = resultMail->Fetch();
3939
3940 uint64 mail_id = mailFields[0].GetUInt64();
3941 uint8 mailType = mailFields[1].GetUInt8();
3942 uint16 mailTemplateId= mailFields[2].GetUInt16();
3943 ObjectGuid::LowType sender = mailFields[3].GetUInt64();
3944 std::string subject = mailFields[4].GetString();
3945 std::string body = mailFields[5].GetString();
3946 uint64 money = mailFields[6].GetUInt64();
3947 bool has_items = mailFields[7].GetBool();
3948
3949 // We can return mail now
3950 // So firstly delete the old one
3951 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
3952 stmt->setUInt64(0, mail_id);
3953 trans->Append(stmt);
3954
3955 // Mail is not from player
3956 if (mailType != MAIL_NORMAL)
3957 {
3958 if (has_items)
3959 {
3960 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
3961 stmt->setUInt64(0, mail_id);
3962 trans->Append(stmt);
3963 }
3964 continue;
3965 }
3966
3967 MailDraft draft(subject, body);
3968 if (mailTemplateId)
3969 draft = MailDraft(mailTemplateId, false); // items are already included
3970
3971 auto itemsItr = itemsByMail.find(mail_id);
3972 if (itemsItr != itemsByMail.end())
3973 {
3974 for (Item* item : itemsItr->second)
3975 draft.AddItem(item);
3976
3977 // MailDraft will take care of freeing memory
3978 itemsByMail.erase(itemsItr);
3979 }
3980
3981 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
3982 stmt->setUInt64(0, mail_id);
3983 trans->Append(stmt);
3984
3985 uint32 pl_account = sCharacterCache->GetCharacterAccountIdByGuid(playerguid);
3986
3987 draft.AddMoney(money).SendReturnToSender(pl_account, guid, sender, trans);
3988 }
3989 while (resultMail->NextRow());
3990
3991 // Free remaining items
3992 for (auto&& kvp : itemsByMail)
3993 for (Item* item : kvp.second)
3994 delete item;
3995 }
3996
3997 // Unsummon and delete for pets in world is not required: player deleted from CLI or character list with not loaded pet.
3998 // NOW we can finally clear other DB data related to character
3999 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET_IDS);
4000 stmt->setUInt64(0, guid);
4001 PreparedQueryResult resultPets = CharacterDatabase.Query(stmt);
4002
4003 if (resultPets)
4004 {
4005 do
4006 {
4007 uint32 petguidlow = (*resultPets)[0].GetUInt32();
4008 Pet::DeleteFromDB(petguidlow);
4009 } while (resultPets->NextRow());
4010 }
4011
4012 // Delete char from social list of online chars
4013 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_SOCIAL);
4014 stmt->setUInt64(0, guid);
4015
4016 if (PreparedQueryResult resultFriends = CharacterDatabase.Query(stmt))
4017 {
4018 do
4019 {
4020 if (Player* playerFriend = ObjectAccessor::FindPlayer(ObjectGuid::Create<HighGuid::Player>((*resultFriends)[0].GetUInt64())))
4021 {
4022 playerFriend->GetSocial()->RemoveFromSocialList(playerguid, SOCIAL_FLAG_ALL);
4023 sSocialMgr->SendFriendStatus(playerFriend, FRIEND_REMOVED, playerguid);
4024 }
4025 } while (resultFriends->NextRow());
4026 }
4027
4028 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER);
4029 stmt->setUInt64(0, guid);
4030 trans->Append(stmt);
4031
4032 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_CUSTOMIZATIONS);
4033 stmt->setUInt64(0, guid);
4034 trans->Append(stmt);
4035
4036 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_ACCOUNT_DATA);
4037 stmt->setUInt64(0, guid);
4038 trans->Append(stmt);
4039
4040 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_DECLINED_NAME);
4041 stmt->setUInt64(0, guid);
4042 trans->Append(stmt);
4043
4044 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION);
4045 stmt->setUInt64(0, guid);
4046 trans->Append(stmt);
4047
4048 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_ARENA_STATS);
4049 stmt->setUInt64(0, guid);
4050 trans->Append(stmt);
4051
4052 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_AURA_EFFECT);
4053 stmt->setUInt64(0, guid);
4054 trans->Append(stmt);
4055
4056 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_AURA);
4057 stmt->setUInt64(0, guid);
4058 trans->Append(stmt);
4059
4060 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_BGDATA);
4061 stmt->setUInt64(0, guid);
4062 trans->Append(stmt);
4063
4064 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_BATTLEGROUND_RANDOM);
4065 stmt->setUInt64(0, guid);
4066 trans->Append(stmt);
4067
4068 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_CUF_PROFILES);
4069 stmt->setUInt64(0, guid);
4070 trans->Append(stmt);
4071
4072 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_CURRENCY);
4073 stmt->setUInt64(0, guid);
4074 trans->Append(stmt);
4075
4076 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_GIFT);
4077 stmt->setUInt64(0, guid);
4078 trans->Append(stmt);
4079
4080 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_HOMEBIND);
4081 stmt->setUInt64(0, guid);
4082 trans->Append(stmt);
4083
4085 stmt->setUInt64(0, guid);
4086 trans->Append(stmt);
4087
4088 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY);
4089 stmt->setUInt64(0, guid);
4090 trans->Append(stmt);
4091
4092 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS);
4093 stmt->setUInt64(0, guid);
4094 trans->Append(stmt);
4095
4096 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES);
4097 stmt->setUInt64(0, guid);
4098 trans->Append(stmt);
4099
4101 stmt->setUInt64(0, guid);
4102 trans->Append(stmt);
4103
4104 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_REWARDED);
4105 stmt->setUInt64(0, guid);
4106 trans->Append(stmt);
4107
4108 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_REPUTATION);
4109 stmt->setUInt64(0, guid);
4110 trans->Append(stmt);
4111
4112 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL);
4113 stmt->setUInt64(0, guid);
4114 trans->Append(stmt);
4115
4116 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_COOLDOWNS);
4117 stmt->setUInt64(0, guid);
4118 trans->Append(stmt);
4119
4120 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_CHARGES);
4121 stmt->setUInt64(0, guid);
4122 trans->Append(stmt);
4123
4124 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_GEMS_BY_OWNER);
4125 stmt->setUInt64(0, guid);
4126 trans->Append(stmt);
4127
4129 stmt->setUInt64(0, guid);
4130 trans->Append(stmt);
4131
4133 stmt->setUInt64(0, guid);
4134 trans->Append(stmt);
4135
4137 stmt->setUInt64(0, guid);
4138 trans->Append(stmt);
4139
4141 stmt->setUInt64(0, guid);
4142 trans->Append(stmt);
4143
4145 stmt->setUInt64(0, guid);
4146 trans->Append(stmt);
4147
4149 stmt->setUInt64(0, guid);
4150 trans->Append(stmt);
4151
4153 stmt->setUInt64(0, guid);
4154 trans->Append(stmt);
4155
4157 stmt->setUInt64(0, guid);
4158 trans->Append(stmt);
4159
4160 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_BY_OWNER);
4161 stmt->setUInt64(0, guid);
4162 trans->Append(stmt);
4163
4164 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SOCIAL_BY_FRIEND);
4165 stmt->setUInt64(0, guid);
4166 trans->Append(stmt);
4167
4168 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SOCIAL_BY_GUID);
4169 stmt->setUInt64(0, guid);
4170 trans->Append(stmt);
4171
4172 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL);
4173 stmt->setUInt64(0, guid);
4174 trans->Append(stmt);
4175
4176 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEMS);
4177 stmt->setUInt64(0, guid);
4178 trans->Append(stmt);
4179
4180 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_BY_OWNER);
4181 stmt->setUInt64(0, guid);
4182 trans->Append(stmt);
4183
4185 stmt->setUInt64(0, guid);
4186 trans->Append(stmt);
4187
4188 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENTS);
4189 stmt->setUInt64(0, guid);
4190 trans->Append(stmt);
4191
4192 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS);
4193 stmt->setUInt64(0, guid);
4194 trans->Append(stmt);
4195
4196 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_EQUIPMENTSETS);
4197 stmt->setUInt64(0, guid);
4198 trans->Append(stmt);
4199
4200 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRANSMOG_OUTFITS);
4201 stmt->setUInt64(0, guid);
4202 trans->Append(stmt);
4203
4204 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_EVENTLOG_BY_PLAYER);
4205 stmt->setUInt64(0, guid);
4206 stmt->setUInt64(1, guid);
4207 trans->Append(stmt);
4208
4209 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_EVENTLOG_BY_PLAYER);
4210 stmt->setUInt64(0, guid);
4211 trans->Append(stmt);
4212
4213 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_GLYPHS);
4214 stmt->setUInt64(0, guid);
4215 trans->Append(stmt);
4216
4217 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_DAILY);
4218 stmt->setUInt64(0, guid);
4219 trans->Append(stmt);
4220
4221 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_WEEKLY);
4222 stmt->setUInt64(0, guid);
4223 trans->Append(stmt);
4224
4225 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_QUESTSTATUS_MONTHLY);
4226 stmt->setUInt64(0, guid);
4227 trans->Append(stmt);
4228
4230 stmt->setUInt64(0, guid);
4231 trans->Append(stmt);
4232
4233 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT);
4234 stmt->setUInt64(0, guid);
4235 trans->Append(stmt);
4236
4237 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SKILLS);
4238 stmt->setUInt64(0, guid);
4239 trans->Append(stmt);
4240
4241 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_STATS);
4242 stmt->setUInt64(0, guid);
4243 trans->Append(stmt);
4244
4246 stmt->setUInt64(0, guid);
4247 trans->Append(stmt);
4248
4249 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_FISHINGSTEPS);
4250 stmt->setUInt64(0, guid);
4251 trans->Append(stmt);
4252
4254 stmt->setUInt64(0, guid);
4255 trans->Append(stmt);
4256
4258 stmt->setUInt64(0, guid);
4259 trans->Append(stmt);
4260
4261 loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_BATTLE_PET_DECLINED_NAME_BY_OWNER);
4262 loginStmt->setInt64(0, guid);
4263 loginStmt->setInt32(1, sRealmList->GetCurrentRealmId().Realm);
4264 loginTransaction->Append(loginStmt);
4265
4266 loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_BATTLE_PETS_BY_OWNER);
4267 loginStmt->setInt64(0, guid);
4268 loginStmt->setInt32(1, sRealmList->GetCurrentRealmId().Realm);
4269 loginTransaction->Append(loginStmt);
4270
4271 Corpse::DeleteFromDB(playerguid, trans);
4272
4273 Garrison::DeleteFromDB(guid, trans);
4274
4275 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_ENTRIES_BY_CHAR);
4276 stmt->setUInt64(0, guid);
4277 trans->Append(stmt);
4278
4279 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_CONFIGS_BY_CHAR);
4280 stmt->setUInt64(0, guid);
4281 trans->Append(stmt);
4282
4283 sCharacterCache->DeleteCharacterCacheEntry(playerguid, name);
4284 break;
4285 }
4286 // The character gets unlinked from the account, the name gets freed up and appears as deleted ingame
4287 case CHAR_DELETE_UNLINK:
4288 {
4289 stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_DELETE_INFO);
4290 stmt->setUInt64(0, guid);
4291 trans->Append(stmt);
4292 sCharacterCache->UpdateCharacterInfoDeleted(playerguid, true, "");
4293 break;
4294 }
4295 default:
4296 TC_LOG_ERROR("entities.player.cheat", "Player::DeleteFromDB: Tried to delete player ({}) with unsupported delete method ({}).",
4297 playerguid.ToString(), charDeleteMethod);
4298
4299 if (trans->GetSize() > 0)
4300 CharacterDatabase.CommitTransaction(trans);
4301 return;
4302 }
4303
4304 LoginDatabase.CommitTransaction(loginTransaction);
4305 CharacterDatabase.CommitTransaction(trans);
4306
4307 if (updateRealmChars)
4308 sWorld->UpdateRealmCharCount(accountId);
4309}
4310
4317{
4318 uint32 keepDays = sWorld->getIntConfig(CONFIG_CHARDELETE_KEEP_DAYS);
4319 if (!keepDays)
4320 return;
4321
4323}
4324
4333{
4334 TC_LOG_INFO("entities.player", "Player::DeleteOldCharacters: Deleting all characters which have been deleted {} days before...", keepDays);
4335
4337 stmt->setUInt32(0, static_cast<uint32>(GameTime::GetGameTime() - static_cast<time_t>(keepDays) * DAY));
4338 PreparedQueryResult result = CharacterDatabase.Query(stmt);
4339
4340 if (result)
4341 {
4342 TC_LOG_DEBUG("entities.player", "Player::DeleteOldCharacters: Found {} character(s) to delete", result->GetRowCount());
4343 do
4344 {
4345 Field* fields = result->Fetch();
4346 Player::DeleteFromDB(ObjectGuid::Create<HighGuid::Player>(fields[0].GetUInt64()), fields[1].GetUInt32(), true, true);
4347 }
4348 while (result->NextRow());
4349 }
4350}
4351
4352/* Preconditions:
4353 - a resurrectable corpse must not be loaded for the player (only bones)
4354 - the player must be in world
4355*/
4357{
4359 packet.PlayerGUID = GetGUID();
4360 SendDirectMessage(packet.Write());
4361
4362 // If the player has the Wisp racial then cast the Wisp aura on them
4363 if (HasSpell(20585))
4364 CastSpell(this, 20584, true);
4365 CastSpell(this, 8326, true);
4366
4368
4369 // there must be SMSG.FORCE_RUN_SPEED_CHANGE, SMSG.FORCE_SWIM_SPEED_CHANGE, SMSG.MOVE_SET_WATER_WALK
4370 // there must be SMSG.STOP_MIRROR_TIMER
4371
4372 // the player cannot have a corpse already on current map, only bones which are not returned by GetCorpse
4373 WorldLocation corpseLocation = GetCorpseLocation();
4374 if (corpseLocation.GetMapId() == GetMapId())
4375 {
4376 TC_LOG_ERROR("entities.player", "Player::BuildPlayerRepop: Player '{}' ({}) already has a corpse", GetName(), GetGUID().ToString());
4377 return;
4378 }
4379
4380 // create a corpse and place it at the player's location
4381 Corpse* corpse = CreateCorpse();
4382 if (!corpse)
4383 {
4384 TC_LOG_ERROR("entities.player", "Player::BuildPlayerRepop: Error creating corpse for player '{}' ({})", GetName(), GetGUID().ToString());
4385 return;
4386 }
4387 GetMap()->AddToMap(corpse);
4388
4389 // convert player body to ghost
4391 SetHealth(1);
4392
4393 SetWaterWalking(true);
4394 if (!GetSession()->isLogingOut() && !HasUnitState(UNIT_STATE_STUNNED))
4395 SetRooted(false);
4396
4397 // BG - remove insignia related
4399
4400 int32 corpseReclaimDelay = CalculateCorpseReclaimDelay();
4401
4402 if (corpseReclaimDelay >= 0)
4403 SendCorpseReclaimDelay(corpseReclaimDelay);
4404
4405 // to prevent cheating
4406 corpse->ResetGhostTime();
4407
4408 StopMirrorTimers(); //disable timers(bars)
4409
4410 // OnPlayerRepop hook
4411 sScriptMgr->OnPlayerRepop(this);
4412}
4413
4414void Player::ResurrectPlayer(float restore_percent, bool applySickness)
4415{
4416 SetAreaSpiritHealer(nullptr);
4417
4419 packet.MapID = -1;
4420 SendDirectMessage(packet.Write());
4421
4422 // speed change, land walk
4423
4424 // remove death flag + set aura
4426
4427 // This must be called always even on Players with race != RACE_NIGHTELF in case of faction change
4428 RemoveAurasDueToSpell(20584); // RACE_NIGHTELF speed bonuses
4429 RemoveAurasDueToSpell(8326); // SPELL_AURA_GHOST
4430
4431 if (GetSession()->IsARecruiter() || (GetSession()->GetRecruiterId() != 0))
4433
4435
4436 // add the flag to make sure opcode is always sent
4438 SetWaterWalking(false);
4440 SetRooted(false);
4441
4442 m_deathTimer = 0;
4443
4444 // set health/powers (0- will be set in caller)
4445 if (restore_percent > 0.0f)
4446 {
4447 SetHealth(GetMaxHealth() * restore_percent);
4448 SetPower(POWER_MANA, GetMaxPower(POWER_MANA) * restore_percent);
4449 SetPower(POWER_RAGE, 0);
4450 SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY) * restore_percent);
4451 SetPower(POWER_FOCUS, GetMaxPower(POWER_FOCUS) * restore_percent);
4453 }
4454
4455 // trigger update zone for alive state zone updates
4456 uint32 newzone, newarea;
4457 GetZoneAndAreaId(newzone, newarea);
4458 UpdateZone(newzone, newarea);
4459 sOutdoorPvPMgr->HandlePlayerResurrects(this, newzone);
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{
4600 _corpseLocation.WorldRelocate(MAPID_INVALID, 0.0f, 0.0f, 0.0f, 0.0f);
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 (!HasEnoughMoney(totalCost))
4792 return; // silent return, client should display error by itself and not send opcode.
4793
4794 ModifyMoney(-int32(totalCost));
4795
4796 // Payment for repair has already been taken, so just repair every item without taking cost.
4797 for (auto const& [item, cost] : itemRepairCostStore)
4798 DurabilityRepair(item->GetPos(), false, 0.f);
4799 }
4800}
4801
4802void Player::DurabilityRepair(uint16 pos, bool takeCost, float discountMod)
4803{
4804 Item* item = GetItemByPos(pos);
4805 if (!item)
4806 return;
4807
4808 if (takeCost)
4809 {
4810 uint64 cost = item->CalculateDurabilityRepairCost(discountMod);
4811
4812 if (!HasEnoughMoney(cost))
4813 {
4814 TC_LOG_DEBUG("entities.player.items", "Player::DurabilityRepair: Player '{}' ({}) has not enough money to repair item",
4815 GetName(), GetGUID().ToString());
4816 return;
4817 }
4818
4819 ModifyMoney(-int32(cost));
4820 }
4821
4822 bool isBroken = item->IsBroken();
4823
4824 item->SetDurability(item->m_itemData->MaxDurability);
4825 item->SetState(ITEM_CHANGED, this);
4826
4827 // reapply mods for total broken and repaired item if equipped
4828 if (IsEquipmentPos(pos) && isBroken)
4829 _ApplyItemMods(item, pos & 255, true);
4830}
4831
4833{
4834 // note: this can be called also when the player is alive
4835 // for example from WorldSession::HandleMovementOpcodes
4836
4837 AreaTableEntry const* zone = sAreaTableStore.LookupEntry(GetAreaId());
4838
4839 bool shouldResurrect = false;
4840 // Such zones are considered unreachable as a ghost and the player must be automatically revived
4841 if ((!IsAlive() && zone && zone->GetFlags().HasFlag(AreaFlags::NoGhostOnRelease)) || GetMap()->IsNonRaidDungeon() || GetMap()->IsRaid() || GetTransport() || GetPositionZ() < GetMap()->GetMinHeight(GetPhaseShift(), GetPositionX(), GetPositionY()))
4842 {
4843 shouldResurrect = true;
4845 }
4846
4847 WorldSafeLocsEntry const* closestGrave = nullptr;
4848 if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(GetMap(), GetZoneId()))
4849 closestGrave = bf->GetClosestGraveyard(this);
4850 else if (InstanceScript* instance = GetInstanceScript())
4851 closestGrave = sObjectMgr->GetWorldSafeLoc(instance->GetEntranceLocation());
4852
4853 if (!closestGrave)
4854 closestGrave = sObjectMgr->GetClosestGraveyard(*this, GetTeam(), this);
4855
4856 // stop countdown until repop
4857 m_deathTimer = 0;
4858
4859 // if no grave found, stay at the current location
4860 // and don't show spirit healer location
4861 if (closestGrave)
4862 {
4863 TeleportTo({ .Location = closestGrave->Loc, .TransportGuid = closestGrave->TransportSpawnId ? ObjectGuid::Create<HighGuid::Transport>(*closestGrave->TransportSpawnId) : ObjectGuid::Empty }, shouldResurrect ? TELE_REVIVE_AT_TELEPORT : TELE_TO_NONE);
4864 if (isDead()) // not send if alive, because it used in TeleportTo()
4865 {
4867 packet.MapID = closestGrave->Loc.GetMapId();
4868 packet.Loc = closestGrave->Loc;
4869 SendDirectMessage(packet.Write());
4870 }
4871 }
4872 else if (GetPositionZ() < GetMap()->GetMinHeight(GetPhaseShift(), GetPositionX(), GetPositionY()))
4874
4876}
4877
4879{
4880 if (channel->GetFlags().HasFlag(ChatChannelFlags::ZoneBased) && zone->GetFlags().HasFlag(AreaFlags::NoChatChannels))
4881 return false;
4882
4883 if (channel->GetFlags().HasFlag(ChatChannelFlags::OnlyInCities) && !zone->GetFlags().HasFlag(AreaFlags::AllowTradeChannel))
4884 return false;
4885
4886 if (channel->GetFlags().HasFlag(ChatChannelFlags::GuildRecruitment) && GetGuildId())
4887 return false;
4888
4889 if (channel->GetRuleset() == ChatChannelRuleset::Disabled)
4890 return false;
4891
4892 if (channel->GetFlags().HasFlag(ChatChannelFlags::Regional))
4893 return false;
4894
4895 return true;
4896}
4897
4899{
4900 m_channels.push_back(c);
4901}
4902
4904{
4905 m_channels.remove(c);
4906}
4907
4909{
4910 while (!m_channels.empty())
4911 {
4912 Channel* ch = *m_channels.begin();
4913 m_channels.erase(m_channels.begin()); // remove from player's channel list
4914 ch->LeaveChannel(this, false); // not send to client, not remove from player's channel list
4915
4916 // delete channel if empty
4917 if (ChannelMgr* cMgr = ChannelMgr::ForTeam(GetTeam()))
4918 if (ch->IsConstant())
4919 cMgr->LeftChannel(ch->GetChannelId(), ch->GetZoneEntry());
4920 }
4921 TC_LOG_DEBUG("chat.system", "Player::CleanupChannels: Channels of player '{}' ({}) cleaned up.", GetName(), GetGUID().ToString());
4922}
4923
4925{
4926 if (GetSession()->PlayerLoading() && !IsBeingTeleportedFar())
4927 return; // The client handles it automatically after loading, but not after teleporting
4928
4929 AreaTableEntry const* current_zone = sAreaTableStore.LookupEntry(newZone);
4930 if (!current_zone)
4931 return;
4932
4934 if (!cMgr)
4935 return;
4936
4937 for (ChatChannelsEntry const* channelEntry : sChatChannelsStore)
4938 {
4939 if (!channelEntry->GetFlags().HasFlag(ChatChannelFlags::AutoJoin))
4940 continue;
4941
4942 Channel* usedChannel = nullptr;
4943 for (Channel* channel : m_channels)
4944 {
4945 if (channel->GetChannelId() == channelEntry->ID)
4946 {
4947 usedChannel = channel;
4948 break;
4949 }
4950 }
4951
4952 Channel* removeChannel = nullptr;
4953 Channel* joinChannel = nullptr;
4954 bool sendRemove = true;
4955
4956 if (CanJoinConstantChannelInZone(channelEntry, current_zone))
4957 {
4958 if (channelEntry->GetFlags().HasFlag(ChatChannelFlags::ZoneBased))
4959 {
4960 if (channelEntry->GetFlags().HasFlag(ChatChannelFlags::LinkedChannel) && usedChannel)
4961 continue; // Already on the channel, as city channel names are not changing
4962
4963 joinChannel = cMgr->GetSystemChannel(channelEntry->ID, current_zone);
4964 if (usedChannel)
4965 {
4966 if (joinChannel != usedChannel)
4967 {
4968 removeChannel = usedChannel;
4969 sendRemove = false; // Do not send leave channel, it already replaced at client
4970 }
4971 else
4972 joinChannel = nullptr;
4973 }
4974 }
4975 else
4976 joinChannel = cMgr->GetSystemChannel(channelEntry->ID);
4977 }
4978 else
4979 removeChannel = usedChannel;
4980
4981 if (joinChannel)
4982 joinChannel->JoinChannel(this); // Changed Channel: ... or Joined Channel: ...
4983
4984 if (removeChannel)
4985 {
4986 removeChannel->LeaveChannel(this, sendRemove, true); // Leave old channel
4987
4988 LeftChannel(removeChannel); // Remove from player's channel list
4989 cMgr->LeftChannel(removeChannel->GetChannelId(), removeChannel->GetZoneEntry()); // Delete if empty
4990 }
4991 }
4992}
4993
4995{
4996 for (JoinedChannelsList::iterator i = m_channels.begin(); i != m_channels.end(); ++i)
4997 {
4998 if ((*i)->IsLFG())
4999 {
5000 (*i)->LeaveChannel(this);
5001 break;
5002 }
5003 }
5004}
5005
5006void Player::HandleBaseModFlatValue(BaseModGroup modGroup, float amount, bool apply)
5007{
5008 if (modGroup >= BASEMOD_END)
5009 {
5010 TC_LOG_ERROR("spells", "Player::HandleBaseModValue: Invalid BaseModGroup/BaseModType ({}/{}) for player '{}' ({})",
5011 modGroup, FLAT_MOD, GetName(), GetGUID().ToString());
5012 return;
5013 }
5014
5015 m_auraBaseFlatMod[modGroup] += apply ? amount : -amount;
5016 UpdateBaseModGroup(modGroup);
5017}
5018
5020{
5021 if (modGroup >= BASEMOD_END)
5022 {
5023 TC_LOG_ERROR("spells", "Player::HandleBaseModValue: Invalid BaseModGroup/BaseModType ({}/{}) for player '{}' ({})",
5024 modGroup, FLAT_MOD, GetName(), GetGUID().ToString());
5025 return;
5026 }
5027
5028 AddPct(m_auraBasePctMod[modGroup], pct);
5029 UpdateBaseModGroup(modGroup);
5030}
5031
5033{
5034 if (m_auraBaseFlatMod[modGroup] == val)
5035 return;
5036
5037 m_auraBaseFlatMod[modGroup] = val;
5038 UpdateBaseModGroup(modGroup);
5039}
5040
5042{
5043 if (m_auraBasePctMod[modGroup] == val)
5044 return;
5045
5046 m_auraBasePctMod[modGroup] = val;
5047 UpdateBaseModGroup(modGroup);
5048}
5049
5050void Player::UpdateDamageDoneMods(WeaponAttackType attackType, int32 skipEnchantSlot /*= -1*/)
5051{
5052 Unit::UpdateDamageDoneMods(attackType, skipEnchantSlot);
5053
5054 UnitMods unitMod;
5055 switch (attackType)
5056 {
5057 case BASE_ATTACK:
5058 unitMod = UNIT_MOD_DAMAGE_MAINHAND;
5059 break;
5060 case OFF_ATTACK:
5061 unitMod = UNIT_MOD_DAMAGE_OFFHAND;
5062 break;
5063 case RANGED_ATTACK:
5064 unitMod = UNIT_MOD_DAMAGE_RANGED;
5065 break;
5066 default:
5067 ABORT();
5068 break;
5069 }
5070
5071 float amount = 0.0f;
5072 Item* item = GetWeaponForAttack(attackType, true);
5073 if (!item)
5074 return;
5075
5076 for (uint8 slot = 0; slot < MAX_ENCHANTMENT_SLOT; ++slot)
5077 {
5078 if (skipEnchantSlot == slot)
5079 continue;
5080
5081 SpellItemEnchantmentEntry const* enchantmentEntry = sSpellItemEnchantmentStore.LookupEntry(item->GetEnchantmentId(EnchantmentSlot(slot)));
5082 if (!enchantmentEntry)
5083 continue;
5084
5085 for (uint8 i = 0; i < MAX_ITEM_ENCHANTMENT_EFFECTS; ++i)
5086 {
5087 switch (enchantmentEntry->Effect[i])
5088 {
5090 amount += enchantmentEntry->EffectScalingPoints[i];
5091 break;
5093 if (GetClass() == CLASS_SHAMAN)
5094 amount += enchantmentEntry->EffectScalingPoints[i] * item->GetTemplate()->GetDelay() / 1000.0f;
5095 break;
5096 default:
5097 break;
5098 }
5099 }
5100 }
5101
5102 HandleStatFlatModifier(unitMod, TOTAL_VALUE, amount, true);
5103}
5104
5106{
5107 if (!CanModifyStats())
5108 return;
5109
5110 switch (modGroup)
5111 {
5115 default: break;
5116 }
5117}
5118
5120{
5121 if (modGroup >= BASEMOD_END || modType >= MOD_END)
5122 {
5123 TC_LOG_ERROR("spells", "Player::GetBaseModValue: Invalid BaseModGroup/BaseModType ({}/{}) for player '{}' ({})",
5124 modGroup, modType, GetName(), GetGUID().ToString());
5125 return 0.0f;
5126 }
5127
5128 return (modType == FLAT_MOD ? m_auraBaseFlatMod[modGroup] : m_auraBasePctMod[modGroup]);
5129}
5130
5132{
5133 if (modGroup >= BASEMOD_END)
5134 {
5135 TC_LOG_ERROR("spells", "Player::GetTotalBaseModValue: Invalid BaseModGroup ({}) for player '{}' ({})",
5136 modGroup, GetName(), GetGUID().ToString());
5137 return 0.0f;
5138 }
5139
5140 return m_auraBaseFlatMod[modGroup] * m_auraBasePctMod[modGroup];
5141}
5142
5143void Player::GetDodgeFromAgility(float &/*diminishing*/, float &/*nondiminishing*/) const
5144{
5146 //const float dodge_base[MAX_CLASSES] =
5147 //{
5148 // 0.037580f, // Warrior
5149 // 0.036520f, // Paladin
5150 // -0.054500f, // Hunter
5151 // -0.005900f, // Rogue
5152 // 0.031830f, // Priest
5153 // 0.036640f, // DK
5154 // 0.016750f, // Shaman
5155 // 0.034575f, // Mage
5156 // 0.020350f, // Warlock
5157 // 0.0f, // ??
5158 // 0.049510f // Druid
5159 //};
5161 //const float crit_to_dodge[MAX_CLASSES] =
5162 //{
5163 // 0.85f/1.15f, // Warrior
5164 // 1.00f/1.15f, // Paladin
5165 // 1.11f/1.15f, // Hunter
5166 // 2.00f/1.15f, // Rogue
5167 // 1.00f/1.15f, // Priest
5168 // 0.85f/1.15f, // DK
5169 // 1.60f/1.15f, // Shaman
5170 // 1.00f/1.15f, // Mage
5171 // 0.97f/1.15f, // Warlock (?)
5172 // 0.0f, // ??
5173 // 2.00f/1.15f // Druid
5174 //};
5175
5176 //uint8 level = getLevel();
5177 //uint32 pclass = getClass();
5178
5179 //if (level >= sGtChanceToMeleeCritStore.GetTableRowCount())
5180 // level = sGtChanceToMeleeCritStore.GetTableRowCount() - 1;
5181
5183 //GtChanceToMeleeCritEntry const* dodgeRatio = sGtChanceToMeleeCritStore.EvaluateTable(level - 1, pclass - 1);
5184 //if (dodgeRatio == nullptr || pclass > MAX_CLASSES)
5185 // return;
5186
5188 //float base_agility = GetCreateStat(STAT_AGILITY) * GetPctModifierValue(UnitMods(UNIT_MOD_STAT_START + STAT_AGILITY), BASE_PCT);
5189 //float bonus_agility = GetStat(STAT_AGILITY) - base_agility;
5190
5192 //diminishing = 100.0f * bonus_agility * dodgeRatio->ratio * crit_to_dodge[pclass-1];
5193 //nondiminishing = 100.0f * (dodge_base[pclass-1] + base_agility * dodgeRatio->ratio * crit_to_dodge[pclass-1]);
5194}
5195
5197{
5198 switch (rating)
5199 {
5200 case CR_AMPLIFY:
5201 return row->Amplify;
5202 case CR_DEFENSE_SKILL:
5203 return row->DefenseSkill;
5204 case CR_DODGE:
5205 return row->Dodge;
5206 case CR_PARRY:
5207 return row->Parry;
5208 case CR_BLOCK:
5209 return row->Block;
5210 case CR_HIT_MELEE:
5211 return row->HitMelee;
5212 case CR_HIT_RANGED:
5213 return row->HitRanged;
5214 case CR_HIT_SPELL:
5215 return row->HitSpell;
5216 case CR_CRIT_MELEE:
5217 return row->CritMelee;
5218 case CR_CRIT_RANGED:
5219 return row->CritRanged;
5220 case CR_CRIT_SPELL:
5221 return row->CritSpell;
5222 case CR_CORRUPTION:
5223 return row->Corruption;
5225 return row->CorruptionResistance;
5226 case CR_SPEED:
5227 return row->Speed;
5229 return row->ResilienceCritTaken;
5231 return row->ResiliencePlayerDamage;
5232 case CR_LIFESTEAL:
5233 return row->Lifesteal;
5234 case CR_HASTE_MELEE:
5235 return row->HasteMelee;
5236 case CR_HASTE_RANGED:
5237 return row->HasteRanged;
5238 case CR_HASTE_SPELL:
5239 return row->HasteSpell;
5240 case CR_AVOIDANCE:
5241 return row->Avoidance;
5242 case CR_STURDINESS:
5243 return row->Sturdiness;
5244 case CR_UNUSED_7:
5245 return row->Unused7;
5246 case CR_EXPERTISE:
5247 return row->Expertise;
5249 return row->ArmorPenetration;
5250 case CR_MASTERY:
5251 return row->Mastery;
5252 case CR_PVP_POWER:
5253 return row->PvPPower;
5254 case CR_CLEAVE:
5255 return row->Cleave;
5257 return row->VersatilityDamageDone;
5259 return row->VersatilityHealingDone;
5261 return row->VersatilityDamageTaken;
5262 case CR_UNUSED_12:
5263 return row->Unused12;
5264 default:
5265 break;
5266 }
5267
5268 return 1.0f;
5269}
5270
5272{
5273 GtCombatRatingsEntry const* Rating = sCombatRatingsGameTable.GetRow(GetLevel());
5274 if (!Rating)
5275 return 1.0f;
5276
5277 float value = GetGameTableColumnForCombatRating(Rating, cr);
5278 if (!value)
5279 return 1.0f; // By default use minimum coefficient (not must be called)
5280
5281 return 1.0f / value;
5282}
5283
5285{
5286 float baseResult = ApplyRatingDiminishing(cr, float(m_activePlayerData->CombatRatings[cr]) * GetRatingMultiplier(cr));
5288 return baseResult;
5289 return float(1.0f - pow(0.99f, baseResult)) * 100.0f;
5290}
5291
5292float Player::ApplyRatingDiminishing(CombatRating cr, float bonusValue) const
5293{
5294 uint32 diminishingCurveId = 0;
5295 switch (cr)
5296 {
5297 case CR_DODGE:
5298 diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::DodgeDiminishing);
5299 break;
5300 case CR_PARRY:
5301 diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::ParryDiminishing);
5302 break;
5303 case CR_BLOCK:
5304 diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::BlockDiminishing);
5305 break;
5306 case CR_CRIT_MELEE:
5307 case CR_CRIT_RANGED:
5308 case CR_CRIT_SPELL:
5309 diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::CritDiminishing);
5310 break;
5311 case CR_SPEED:
5312 diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::SpeedDiminishing);
5313 break;
5314 case CR_LIFESTEAL:
5315 diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::LifestealDiminishing);
5316 break;
5317 case CR_HASTE_MELEE:
5318 case CR_HASTE_RANGED:
5319 case CR_HASTE_SPELL:
5320 diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::HasteDiminishing);
5321 break;
5322 case CR_AVOIDANCE:
5323 diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::AvoidanceDiminishing);
5324 break;
5325 case CR_MASTERY:
5326 diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::MasteryDiminishing);
5327 break;
5330 diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::VersatilityDoneDiminishing);
5331 break;
5333 diminishingCurveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::VersatilityTakenDiminishing);
5334 break;
5335 default:
5336 break;
5337 }
5338
5339 if (diminishingCurveId)
5340 return sDB2Manager.GetCurveValueAt(diminishingCurveId, bonusValue);
5341
5342 return bonusValue;
5343}
5344
5346{
5347 float baseExpertise = 7.5f;
5348 switch (attType)
5349 {
5350 case BASE_ATTACK:
5351 return baseExpertise + m_activePlayerData->MainhandExpertise / 4.0f;
5352 case OFF_ATTACK:
5353 return baseExpertise + m_activePlayerData->OffhandExpertise / 4.0f;
5354 default:
5355 break;
5356 }
5357 return 0.0f;
5358}
5359
5360void Player::ApplyRatingMod(CombatRating combatRating, int32 value, bool apply)
5361{
5362 m_baseRatingValue[combatRating] += (apply ? value : -value);
5363 UpdateRating(combatRating);
5364}
5365
5367{
5368 int32 amount = m_baseRatingValue[cr];
5370 {
5371 if (aurEff->GetMiscValueB() & (1 << cr))
5372 {
5373 Optional<int16> highestRating;
5374 for (uint8 dependentRating = 0; dependentRating < MAX_COMBAT_RATING; ++dependentRating)
5375 if (aurEff->GetMiscValue() & (1 << dependentRating))
5376 highestRating = std::max(highestRating.value_or(m_baseRatingValue[dependentRating]), m_baseRatingValue[dependentRating]);
5377
5378 if (highestRating)
5379 amount += int32(CalculatePct(*highestRating, aurEff->GetAmount()));
5380 }
5381 }
5382
5384 if (aurEff->GetMiscValue() & (1 << cr))
5385 amount += int32(CalculatePct(amount, aurEff->GetAmount()));
5386
5387 if (amount < 0)
5388 amount = 0;
5389
5390 uint32 oldRating = m_activePlayerData->CombatRatings[cr];
5392
5393 bool affectStats = CanModifyStats();
5394
5395 switch (cr)
5396 {
5397 case CR_AMPLIFY:
5398 case CR_DEFENSE_SKILL:
5399 break;
5400 case CR_DODGE:
5402 break;
5403 case CR_PARRY:
5405 break;
5406 case CR_BLOCK:
5408 break;
5409 case CR_HIT_MELEE:
5411 break;
5412 case CR_HIT_RANGED:
5414 break;
5415 case CR_HIT_SPELL:
5417 break;
5418 case CR_CRIT_MELEE:
5419 if (affectStats)
5420 {
5423 }
5424 break;
5425 case CR_CRIT_RANGED:
5426 if (affectStats)
5428 break;
5429 case CR_CRIT_SPELL:
5430 if (affectStats)
5432 break;
5433 case CR_CORRUPTION:
5436 break;
5437 case CR_SPEED:
5440 case CR_LIFESTEAL:
5441 break;
5442 case CR_HASTE_MELEE:
5443 case CR_HASTE_RANGED:
5444 case CR_HASTE_SPELL:
5445 {
5446 // explicit affected values
5447 float const multiplier = GetRatingMultiplier(cr);
5448 float const oldVal = ApplyRatingDiminishing(cr, oldRating * multiplier);
5449 float const newVal = ApplyRatingDiminishing(cr, amount * multiplier);
5450 switch (cr)
5451 {
5452 case CR_HASTE_MELEE:
5453 ApplyAttackTimePercentMod(BASE_ATTACK, oldVal, false);
5454 ApplyAttackTimePercentMod(OFF_ATTACK, oldVal, false);
5459 break;
5460 case CR_HASTE_RANGED:
5463 break;
5464 case CR_HASTE_SPELL:
5465 ApplyCastTimePercentMod(oldVal, false);
5466 ApplyCastTimePercentMod(newVal, true);
5467 break;
5468 default:
5469 break;
5470 }
5471 break;
5472 }
5473 case CR_AVOIDANCE:
5474 case CR_STURDINESS:
5475 case CR_UNUSED_7:
5476 break;
5477 case CR_EXPERTISE:
5478 if (affectStats)
5479 {
5482 }
5483 break;
5485 if (affectStats)
5486 UpdateArmorPenetration(amount);
5487 break;
5488 case CR_MASTERY:
5489 UpdateMastery();
5490 break;
5491 case CR_PVP_POWER:
5492 case CR_CLEAVE:
5493 break;
5496 break;
5499 break;
5501 case CR_UNUSED_12:
5502 break;
5503 }
5504}
5505
5507{
5508 for (uint8 cr = 0; cr < MAX_COMBAT_RATING; ++cr)
5510}
5511
5513{
5514 for (uint8 i = 0; i < MAX_ATTACK; ++i)
5515 {
5516 Item* tmpitem = GetWeaponForAttack(WeaponAttackType(i), true);
5517 if (tmpitem && !tmpitem->IsBroken())
5518 {
5519 ItemTemplate const* proto = tmpitem->GetTemplate();
5520 if (proto->GetDelay())
5522 }
5523 else
5524 SetBaseAttackTime(WeaponAttackType(i), BASE_ATTACK_TIME); // If there is no weapon reset attack time to base (might have been changed from forms)
5525 }
5526}
5527
5528inline int SkillGainChance(uint32 SkillValue, uint32 GrayLevel, uint32 GreenLevel, uint32 YellowLevel)
5529{
5530 if (SkillValue >= GrayLevel)
5531 return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_GREY)*10;
5532 if (SkillValue >= GreenLevel)
5533 return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_GREEN)*10;
5534 if (SkillValue >= YellowLevel)
5535 return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_YELLOW)*10;
5536 return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_ORANGE)*10;
5537}
5538
5540{
5542 return false;
5543
5544 TC_LOG_DEBUG("entities.player.skills", "Player::UpdateCraftSkill: Player '{}' ({}), SpellID: {}",
5545 GetName(), GetGUID().ToString(), spellInfo->Id);
5546
5547 SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellInfo->Id);
5548
5549 for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
5550 {
5551 if (_spell_idx->second->SkillupSkillLineID)
5552 {
5553 uint32 SkillValue = GetPureSkillValue(_spell_idx->second->SkillupSkillLineID);
5554
5555 // Alchemy Discoveries here
5556 if (spellInfo->Mechanic == MECHANIC_DISCOVERY)
5557 {
5558 if (uint32 discoveredSpell = GetSkillDiscoverySpell(_spell_idx->second->SkillupSkillLineID, spellInfo->Id, this))
5559 LearnSpell(discoveredSpell, false);
5560 }
5561
5562 uint32 craft_skill_gain = _spell_idx->second->NumSkillUps * sWorld->getIntConfig(CONFIG_SKILL_GAIN_CRAFTING);
5563
5564 return UpdateSkillPro(_spell_idx->second->SkillupSkillLineID, SkillGainChance(SkillValue,
5565 _spell_idx->second->TrivialSkillLineRankHigh,
5566 (_spell_idx->second->TrivialSkillLineRankHigh + _spell_idx->second->TrivialSkillLineRankLow)/2,
5567 _spell_idx->second->TrivialSkillLineRankLow),
5568 craft_skill_gain);
5569 }
5570 }
5571 return false;
5572}
5573
5574bool Player::UpdateGatherSkill(uint32 skillId, uint32 skillValue, uint32 redLevel, uint32 multiplicator /*= 1*/, WorldObject const* object /*= nullptr*/)
5575{
5576 TC_LOG_DEBUG("entities.player.skills", "Player::UpdateGatherSkill: Player '{}' ({}), SkillID: {}, SkillLevel: {}, RedLevel: {})",
5577 GetName(), GetGUID().ToString(), skillId, skillValue, redLevel);
5578
5579 SkillLineEntry const* skillEntry = sSkillLineStore.LookupEntry(skillId);
5580 if (!skillEntry)
5581 return false;
5582
5583 uint32 gatheringSkillGain = sWorld->getIntConfig(CONFIG_SKILL_GAIN_GATHERING);
5584
5585 uint32 baseSkillLevelStep = 30;
5586 uint32 yellowLevel = redLevel + baseSkillLevelStep;
5587 uint32 greenLevel = yellowLevel + baseSkillLevelStep;
5588 uint32 grayLevel = greenLevel + baseSkillLevelStep;
5589
5590 if (GameObject const* go = Object::ToGameObject(object))
5591 {
5592 if (go->GetGOInfo()->GetTrivialSkillLow())
5593 yellowLevel = go->GetGOInfo()->GetTrivialSkillLow();
5594
5595 if (go->GetGOInfo()->GetTrivialSkillHigh())
5596 grayLevel = go->GetGOInfo()->GetTrivialSkillHigh();
5597
5598 greenLevel = (yellowLevel + grayLevel) / 2;
5599 }
5600
5601 // For skinning and Mining chance decrease with level. 1-74 - no decrease, 75-149 - 2 times, 225-299 - 8 times
5602 switch (skillEntry->ParentSkillLineID)
5603 {
5604 case SKILL_HERBALISM:
5605 return UpdateSkillPro(skillId, SkillGainChance(skillValue, grayLevel, greenLevel, yellowLevel) * multiplicator, gatheringSkillGain);
5606 case SKILL_SKINNING:
5607 if (sWorld->getIntConfig(CONFIG_SKILL_CHANCE_SKINNING_STEPS) == 0)
5608 return UpdateSkillPro(skillId, SkillGainChance(skillValue, grayLevel, greenLevel, yellowLevel) * multiplicator, gatheringSkillGain);
5609 else
5610 return UpdateSkillPro(skillId, (SkillGainChance(skillValue, grayLevel, greenLevel, yellowLevel) * multiplicator) >> (skillValue / sWorld->getIntConfig(CONFIG_SKILL_CHANCE_SKINNING_STEPS)), gatheringSkillGain);
5611 case SKILL_MINING:
5612 if (sWorld->getIntConfig(CONFIG_SKILL_CHANCE_MINING_STEPS) == 0)
5613 return UpdateSkillPro(skillId, SkillGainChance(skillValue, grayLevel, greenLevel, yellowLevel) * multiplicator, gatheringSkillGain);
5614 else
5615 return UpdateSkillPro(skillId, (SkillGainChance(skillValue, grayLevel, greenLevel, yellowLevel) * multiplicator) >> (skillValue / sWorld->getIntConfig(CONFIG_SKILL_CHANCE_MINING_STEPS)), gatheringSkillGain);
5616 }
5617 return false;
5618}
5619
5621{
5622 // These formulas are guessed to be as close as possible to how the skill difficulty curve for fishing was on Retail.
5623 if (SkillValue < 75)
5624 return 1;
5625
5626 if (SkillValue <= 300)
5627 return SkillValue / 44;
5628
5629 return SkillValue / 31;
5630}
5631
5633{
5634 TC_LOG_DEBUG("entities.player.skills", "Player::UpdateFishingSkill: Player '{}' ({}) Expansion: {}", GetName(), GetGUID().ToString(), expansion);
5635
5636 uint32 fishingSkill = GetProfessionSkillForExp(SKILL_FISHING, expansion);
5637 if (!fishingSkill || !HasSkill(fishingSkill))
5638 return false;
5639
5640 uint32 skillValue = GetPureSkillValue(fishingSkill);
5641
5642 if (skillValue >= GetMaxSkillValue(fishingSkill))
5643 return false;
5644
5645 uint8 stepsNeededToLevelUp = GetFishingStepsNeededToLevelUp(skillValue);
5647
5648 if (m_fishingSteps >= stepsNeededToLevelUp)
5649 {
5650 m_fishingSteps = 0;
5651
5652 uint32 gatheringSkillGain = sWorld->getIntConfig(CONFIG_SKILL_GAIN_GATHERING);
5653 return UpdateSkillPro(fishingSkill, 100*10, gatheringSkillGain);
5654 }
5655
5656 return false;
5657}
5658
5659bool Player::UpdateSkillPro(uint16 skillId, int32 chance, uint32 step)
5660{
5661 // levels sync. with spell requirement for skill levels to learn
5662 // bonus abilities in sSkillLineAbilityStore
5663 // Used only to avoid scan DBC at each skill grow
5664 uint32 const bonusSkillLevels[] = { 75, 150, 225, 300, 375, 450, 525, 600, 700, 850 };
5665
5666 TC_LOG_DEBUG("entities.player.skills", "Player::UpdateSkillPro: Player '{}' ({}), SkillID: {}, Chance: {:3.1f}%)",
5667 GetName(), GetGUID().ToString(), skillId, chance / 10.0f);
5668 if (!skillId)
5669 return false;
5670
5671 if (chance <= 0) // speedup in 0 chance case
5672 {
5673 TC_LOG_DEBUG("entities.player.skills", "Player::UpdateSkillPro: Player '{}' ({}), SkillID: {}, Chance: {:3.1f}% missed",
5674 GetName(), GetGUID().ToString(), skillId, chance / 10.0f);
5675 return false;
5676 }
5677
5678 SkillStatusMap::iterator itr = mSkillStatus.find(skillId);
5679 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
5680 return false;
5681
5682 uint16 value = m_activePlayerData->Skill->SkillRank[itr->second.pos];
5683 uint16 max = m_activePlayerData->Skill->SkillMaxRank[itr->second.pos];
5684
5685 if (!max || !value || value >= max)
5686 return false;
5687
5688 if (irand(1, 1000) > chance)
5689 {
5690 TC_LOG_DEBUG("entities.player.skills", "Player::UpdateSkillPro: Player '{}' ({}), SkillID: {}, Chance: {:3.1f}% missed",
5691 GetName(), GetGUID().ToString(), skillId, chance / 10.0f);
5692 return false;
5693 }
5694
5695 uint16 new_value = value + step;
5696 if (new_value > max)
5697 new_value = max;
5698
5699 SetSkillRank(itr->second.pos, new_value);
5700 if (itr->second.uState != SKILL_NEW)
5701 itr->second.uState = SKILL_CHANGED;
5702
5703 for (uint32 bsl : bonusSkillLevels)
5704 {
5705 if (value < bsl && new_value >= bsl)
5706 {
5707 LearnSkillRewardedSpells(skillId, new_value, Races(GetRace()));
5708 break;
5709 }
5710 }
5711
5712 UpdateSkillEnchantments(skillId, value, new_value);
5714 TC_LOG_DEBUG("entities.player.skills", "Player::UpdateSkillPro: Player '{}' ({}), SkillID: {}, Chance: {:3.1f}% taken",
5715 GetName(), GetGUID().ToString(), skillId, chance / 10.0f);
5716 return true;
5717}
5718
5719void Player::ModifySkillBonus(uint32 skillid, int32 val, bool talent)
5720{
5721 SkillStatusMap::const_iterator itr = mSkillStatus.find(skillid);
5722 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
5723 return;
5724
5725 if (talent)
5726 SetSkillPermBonus(itr->second.pos, m_activePlayerData->Skill->SkillPermBonus[itr->second.pos] + val);
5727 else
5728 SetSkillTempBonus(itr->second.pos, m_activePlayerData->Skill->SkillTempBonus[itr->second.pos] + val);
5729
5730 // Apply/Remove bonus to child skill lines
5731 if (std::vector<SkillLineEntry const*> const* childSkillLines = sDB2Manager.GetSkillLinesForParentSkill(skillid))
5732 for (SkillLineEntry const* childSkillLine : *childSkillLines)
5733 ModifySkillBonus(childSkillLine->ID, val, talent);
5734}
5735
5737{
5738 Races race = Races(GetRace());
5739 uint32 maxSkill = GetMaxSkillValueForLevel();
5740
5741 for (SkillStatusMap::iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end(); ++itr)
5742 {
5743 if (itr->second.uState == SKILL_DELETED || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
5744 continue;
5745
5746 uint32 pskill = itr->first;
5747 SkillRaceClassInfoEntry const* rcEntry = sDB2Manager.GetSkillRaceClassInfo(pskill, GetRace(), GetClass());
5748 if (!rcEntry)
5749 continue;
5750
5751 if (GetSkillRangeType(rcEntry) == SKILL_RANGE_LEVEL)
5752 {
5753 if (rcEntry->Flags & SKILL_FLAG_ALWAYS_MAX_VALUE)
5754 SetSkillRank(itr->second.pos, maxSkill);
5755
5756 SetSkillMaxRank(itr->second.pos, maxSkill);
5757 if (itr->second.uState != SKILL_NEW)
5758 itr->second.uState = SKILL_CHANGED;
5759 }
5760
5761 // Update level dependent skillline spells
5762 LearnSkillRewardedSpells(rcEntry->SkillID, m_activePlayerData->Skill->SkillRank[itr->second.pos], race);
5763 }
5764}
5765
5767{
5768 uint32 i = 0;
5769 for (SkillLineEntry const* skillLine : sSkillLineStore)
5770 {
5771 if (sDB2Manager.GetSkillRaceClassInfo(skillLine->ID, GetRace(), GetClass()))
5772 {
5773 SetSkillLineId(i, skillLine->ID);
5775 mSkillStatus.insert(SkillStatusMap::value_type(skillLine->ID, SkillStatusData(i, SKILL_UNCHANGED)));
5776 if (++i >= PLAYER_MAX_SKILLS)
5777 break;
5778 }
5779 }
5780}
5781
5782// This functions sets a skill line value (and adds if doesn't exist yet)
5783// To "remove" a skill line, set it's values to zero
5784void Player::SetSkill(uint32 id, uint16 step, uint16 newVal, uint16 maxVal)
5785{
5786 SkillLineEntry const* skillEntry = sSkillLineStore.LookupEntry(id);
5787 if (!skillEntry)
5788 {
5789 TC_LOG_ERROR("misc", "Player::SetSkill: Skill (SkillID: {}) not found in SkillLineStore for player '{}' ({})",
5790 id, GetName(), GetGUID().ToString());
5791 return;
5792 }
5793
5794 uint16 currVal;
5795 SkillStatusMap::iterator itr = mSkillStatus.find(id);
5796
5797 auto refreshSkillBonusAuras = [&]
5798 {
5799 // Temporary bonuses
5801 if (effect->GetMiscValue() == int32(id))
5802 effect->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true);
5803
5805 if (effect->GetMiscValue() == int32(id))
5806 effect->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true);
5807
5808 // Permanent bonuses
5810 if (effect->GetMiscValue() == int32(id))
5811 effect->HandleEffect(this, AURA_EFFECT_HANDLE_SKILL, true);
5812 };
5813
5814 // Handle already stored skills
5815 if (itr != mSkillStatus.end())
5816 {
5817 currVal = m_activePlayerData->Skill->SkillRank[itr->second.pos];
5818
5819 // Activate and update skill line
5820 if (newVal)
5821 {
5822 // enable parent skill line if missing
5823 if (skillEntry->ParentSkillLineID && skillEntry->ParentTierIndex > 0 && GetSkillStep(skillEntry->ParentSkillLineID) < skillEntry->ParentTierIndex)
5824 if (SkillRaceClassInfoEntry const* rcEntry = sDB2Manager.GetSkillRaceClassInfo(skillEntry->ParentSkillLineID, GetRace(), GetClass()))
5825 if (SkillTiersEntry const* tier = sObjectMgr->GetSkillTier(rcEntry->SkillTierID))
5826 SetSkill(skillEntry->ParentSkillLineID, skillEntry->ParentTierIndex, std::max<uint16>(GetPureSkillValue(skillEntry->ParentSkillLineID), 1), tier->GetValueForTierIndex(skillEntry->ParentTierIndex - 1));
5827
5828 // if skill value is going down, update enchantments before setting the new value
5829 if (newVal < currVal)
5830 UpdateSkillEnchantments(id, currVal, newVal);
5831
5832 // update step
5833 SetSkillStep(itr->second.pos, step);
5834 // update value
5835 SetSkillRank(itr->second.pos, newVal);
5836 SetSkillMaxRank(itr->second.pos, maxVal);
5837
5838 LearnSkillRewardedSpells(id, newVal, Races(GetRace()));
5839 // if skill value is going up, update enchantments after setting the new value
5840 if (newVal > currVal)
5841 {
5842 UpdateSkillEnchantments(id, currVal, newVal);
5843 if (id == SKILL_RIDING)
5845 }
5846
5849
5850 // update skill state
5851 if (itr->second.uState == SKILL_UNCHANGED || itr->second.uState == SKILL_DELETED)
5852 {
5853 if (currVal == 0) // activated skill, mark as new to save into database
5854 {
5855 itr->second.uState = itr->second.uState != SKILL_DELETED
5856 ? SKILL_NEW
5857 : SKILL_CHANGED; // skills marked as SKILL_DELETED already exist in database, mark as changed instead of new
5858
5859 // Set profession line
5860 int32 freeProfessionSlot = FindEmptyProfessionSlotFor(id);
5861 if (freeProfessionSlot != -1)
5863
5864 refreshSkillBonusAuras();
5865 }
5866 else // updated skill, mark as changed to save into database
5867 itr->second.uState = SKILL_CHANGED;
5868 }
5869 }
5870 else if (currVal && !newVal) // Deactivate skill line
5871 {
5872 // Try to store profession tools and accessories into the bag
5873 // If we can't, we can't unlearn the profession
5874 int32 professionSlot = GetProfessionSlotFor(id);
5875 if (professionSlot != -1)
5876 {
5877 uint8 professionSlotStart = PROFESSION_SLOT_PROFESSION1_TOOL + professionSlot * PROFESSION_SLOT_MAX_COUNT;
5878
5879 // Get all profession items equipped
5880 for (uint8 slotOffset = 0; slotOffset < PROFESSION_SLOT_MAX_COUNT; ++slotOffset)
5881 {
5882 if (Item* professionItem = GetItemByPos(INVENTORY_SLOT_BAG_0, professionSlotStart + slotOffset))
5883 {
5884 // Store item in bag
5885 ItemPosCountVec professionItemDest;
5886
5887 if (CanStoreItem(NULL_BAG, NULL_SLOT, professionItemDest, professionItem, false) != EQUIP_ERR_OK)
5888 {
5890 return;
5891 }
5892
5893 RemoveItem(INVENTORY_SLOT_BAG_0, professionItem->GetSlot(), true);
5894 StoreItem(professionItemDest, professionItem, true);
5895 }
5896 }
5897
5898 // Clear profession lines
5900 }
5901
5902 //remove enchantments needing this skill
5903 UpdateSkillEnchantments(id, currVal, 0);
5904 // clear skill fields
5905 SetSkillStep(itr->second.pos, 0);
5906 SetSkillRank(itr->second.pos, 0);
5907 SetSkillStartingRank(itr->second.pos, 1);
5908 SetSkillMaxRank(itr->second.pos, 0);
5909 SetSkillTempBonus(itr->second.pos, 0);
5910 SetSkillPermBonus(itr->second.pos, 0);
5911
5912 // mark as deleted so the next save will delete the data from the database
5913 itr->second.uState = itr->second.uState != SKILL_NEW
5915 : SKILL_UNCHANGED; // skills marked as SKILL_NEW don't exist in database (this distinction is not neccessary for deletion but for re-learning the same skill before save to db happens)
5916
5917 // remove all spells that related to this skill
5918 if (std::vector<SkillLineAbilityEntry const*> const* skillLineAbilities = sDB2Manager.GetSkillLineAbilitiesBySkill(id))
5919 for (SkillLineAbilityEntry const* skillLineAbility : *skillLineAbilities)
5920 RemoveSpell(sSpellMgr->GetFirstSpellInChain(skillLineAbility->Spell));
5921
5922 if (std::vector<SkillLineEntry const*> const* childSkillLines = sDB2Manager.GetSkillLinesForParentSkill(id))
5923 for (SkillLineEntry const* childSkillLine : *childSkillLines)
5924 SetSkill(childSkillLine->ID, 0, 0, 0);
5925 }
5926 }
5927 else
5928 {
5929 // We are about to learn a skill that has been added outside of normal circumstances (Game Master command, scripts etc.)
5930 uint8 skillSlot = 0;
5931
5932 // Find a free skill slot
5933 for (uint32 i = 0; i < PLAYER_MAX_SKILLS; ++i)
5934 {
5935 if (!m_activePlayerData->Skill->SkillLineID[i])
5936 {
5937 skillSlot = i;
5938 break;
5939 }
5940 }
5941
5942 if (!skillSlot)
5943 {
5944 TC_LOG_ERROR("misc", "Tried to add skill {} but player {} ({}) cannot have additional skills", id, GetName(), GetGUID().ToString());
5945 return;
5946 }
5947
5948 if (skillEntry->ParentSkillLineID)
5949 {
5950 if (skillEntry->ParentTierIndex > 0)
5951 {
5952 if (SkillRaceClassInfoEntry const* rcEntry = sDB2Manager.GetSkillRaceClassInfo(skillEntry->ParentSkillLineID, GetRace(), GetClass()))
5953 {
5954 if (SkillTiersEntry const* tier = sObjectMgr->GetSkillTier(rcEntry->SkillTierID))
5955 {
5956 uint16 skillval = GetPureSkillValue(skillEntry->ParentSkillLineID);
5957 SetSkill(skillEntry->ParentSkillLineID, skillEntry->ParentTierIndex, std::max<uint16>(skillval, 1), tier->GetValueForTierIndex(skillEntry->ParentTierIndex - 1));
5958 }
5959 }
5960 }
5961 }
5962 else
5963 {
5964 // also learn missing child skills at 0 value
5965 if (std::vector<SkillLineEntry const*> const* childSkillLines = sDB2Manager.GetSkillLinesForParentSkill(id))
5966 for (SkillLineEntry const* childSkillLine : *childSkillLines)
5967 if (!HasSkill(childSkillLine->ID))
5968 SetSkill(childSkillLine->ID, 0, 0, 0);
5969
5970 int32 freeProfessionSlot = FindEmptyProfessionSlotFor(id);
5971 if (freeProfessionSlot != -1)
5973 }
5974
5975 if (itr == mSkillStatus.end())
5976 SetSkillLineId(skillSlot, id);
5977
5978 SetSkillStep(skillSlot, step);
5979 SetSkillRank(skillSlot, newVal);
5980 SetSkillStartingRank(skillSlot, 1);
5981 SetSkillMaxRank(skillSlot, maxVal);
5982
5983 // apply skill bonuses
5984 SetSkillTempBonus(skillSlot, 0);
5985 SetSkillPermBonus(skillSlot, 0);
5986
5987 UpdateSkillEnchantments(id, 0, newVal);
5988
5989 mSkillStatus.insert(SkillStatusMap::value_type(id, SkillStatusData(skillSlot, SKILL_NEW)));
5990
5991 if (newVal)
5992 {
5993 refreshSkillBonusAuras();
5994
5995 // Learn all spells for skill
5996 LearnSkillRewardedSpells(id, newVal, Races(GetRace()));
5999 }
6000 }
6001}
6002
6004{
6005 SkillLineEntry const* skillEntry = sSkillLineStore.LookupEntry(skill);
6006 if (!skillEntry)
6007 return 0;
6008
6009 if (skillEntry->ParentSkillLineID || (skillEntry->CategoryID != SKILL_CATEGORY_PROFESSION && skillEntry->CategoryID != SKILL_CATEGORY_SECONDARY))
6010 return 0;
6011
6012 // The value -3 from ContentTuning refers to the current expansion
6013 if (expansion < 0)
6014 expansion = CURRENT_EXPANSION;
6015
6016 if (std::vector<SkillLineEntry const*> const* childSkillLines = sDB2Manager.GetSkillLinesForParentSkill(skillEntry->ID))
6017 for (SkillLineEntry const* childSkillLine : *childSkillLines)
6018 {
6019 // Values of ParentTierIndex in SkillLine.db2 start at 4 (Classic) and increase by one for each expansion skillLine
6020 // Subtract 4 (BASE_PARENT_TIER_INDEX) from this value to obtain the expansion of the skillLine
6021 int32 skillLineExpansion = childSkillLine->ParentTierIndex - BASE_PARENT_TIER_INDEX;
6022 if (expansion == skillLineExpansion)
6023 return childSkillLine->ID;
6024 }
6025
6026 return 0;
6027}
6028
6029bool Player::HasSkill(uint32 skill) const
6030{
6031 if (!skill)
6032 return false;
6033
6034 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
6035 return (itr != mSkillStatus.end() && itr->second.uState != SKILL_DELETED && m_activePlayerData->Skill->SkillRank[itr->second.pos]);
6036}
6037
6039{
6040 if (!skill)
6041 return 0;
6042
6043 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
6044 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
6045 return 0;
6046
6047 return m_activePlayerData->Skill->SkillStep[itr->second.pos];
6048}
6049
6051{
6052 if (!skill)
6053 return 0;
6054
6055 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
6056 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
6057 return 0;
6058
6059 int32 result = int32(m_activePlayerData->Skill->SkillRank[itr->second.pos]);
6060 result += int32(m_activePlayerData->Skill->SkillTempBonus[itr->second.pos]);
6061 result += int32(m_activePlayerData->Skill->SkillPermBonus[itr->second.pos]);
6062 return result < 0 ? 0 : result;
6063}
6064
6066{
6067 if (!skill)
6068 return 0;
6069
6070 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
6071 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
6072 return 0;
6073
6074 int32 result = int32(m_activePlayerData->Skill->SkillMaxRank[itr->second.pos]);
6075 result += int32(m_activePlayerData->Skill->SkillTempBonus[itr->second.pos]);
6076 result += int32(m_activePlayerData->Skill->SkillPermBonus[itr->second.pos]);
6077 return result < 0 ? 0 : result;
6078}
6079
6081{
6082 if (!skill)
6083 return 0;
6084
6085 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
6086 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
6087 return 0;
6088
6089 return m_activePlayerData->Skill->SkillMaxRank[itr->second.pos];
6090}
6091
6093{
6094 if (!skill)
6095 return 0;
6096
6097 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
6098 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
6099 return 0;
6100
6101 int32 result = int32(m_activePlayerData->Skill->SkillRank[itr->second.pos]);
6102 result += int32(m_activePlayerData->Skill->SkillPermBonus[itr->second.pos]);
6103 return result < 0 ? 0 : result;
6104}
6105
6107{
6108 if (!skill)
6109 return 0;
6110
6111 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
6112 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
6113 return 0;
6114
6115 return m_activePlayerData->Skill->SkillRank[itr->second.pos];
6116}
6117
6119{
6120 if (!skill)
6121 return 0;
6122
6123 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
6124 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
6125 return 0;
6126
6127 return m_activePlayerData->Skill->SkillPermBonus[itr->second.pos];
6128}
6129
6131{
6132 if (!skill)
6133 return 0;
6134
6135 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
6136 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED || !m_activePlayerData->Skill->SkillRank[itr->second.pos])
6137 return 0;
6138
6139 return m_activePlayerData->Skill->SkillTempBonus[itr->second.pos];
6140}
6141
6143{
6145
6146 for (auto itr = m_actionButtons.begin(); itr != m_actionButtons.end(); ++itr)
6147 if (itr->second.uState != ACTIONBUTTON_DELETED && itr->first < packet.ActionButtons.size())
6148 packet.ActionButtons[itr->first] = itr->second.packedData;
6149
6150 packet.Reason = state;
6151
6152 SendDirectMessage(packet.Write());
6153}
6154
6155bool Player::IsActionButtonDataValid(uint8 button, uint64 action, uint8 type) const
6156{
6157 if (button >= MAX_ACTION_BUTTONS)
6158 {
6159 TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Action {} not added into button {} for player {} ({}): button must be < {}",
6160 action, button, GetName(), GetGUID().ToString(), MAX_ACTION_BUTTONS);
6161 return false;
6162 }
6163
6164 if (action >= MAX_ACTION_BUTTON_ACTION_VALUE)
6165 {
6166 TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Action {} not added into button {} for player {} ({}): action must be < {}",
6167 action, button, GetName(), GetGUID().ToString(), MAX_ACTION_BUTTON_ACTION_VALUE);
6168 return false;
6169 }
6170
6171 switch (type)
6172 {
6174 if (!sSpellMgr->GetSpellInfo(action, DIFFICULTY_NONE))
6175 {
6176 TC_LOG_DEBUG("entities.player", "Player::IsActionButtonDataValid: Spell action {} not added into button {} for player {} ({}): spell does not exist. This can be due to a character imported from a different expansion",
6177 action, button, GetName(), GetGUID().ToString());
6178 return false;
6179 }
6180 break;
6181 case ACTION_BUTTON_ITEM:
6182 if (!sObjectMgr->GetItemTemplate(action))
6183 {
6184 TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Item action {} not added into button {} for player {} ({}): item not exist",
6185 action, button, GetName(), GetGUID().ToString());
6186 return false;
6187 }
6188 break;
6190 {
6191 if (!GetSession()->GetBattlePetMgr()->GetPet(ObjectGuid::Create<HighGuid::BattlePet>(action)))
6192 {
6193 TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Companion action {} not added into button {} for player {} ({}): companion does not exist",
6194 action, button, GetName(), GetGUID().ToString());
6195 return false;
6196 }
6197 break;
6198 }
6200 {
6201 MountEntry const* mount = sDB2Manager.GetMountById(action);
6202 if (!mount)
6203 {
6204 TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Mount action {} not added into button {} for player {} ({}): mount does not exist",
6205 action, button, GetName(), GetGUID().ToString());
6206 return false;
6207 }
6208
6209 if (!HasSpell(mount->SourceSpellID))
6210 {
6211 TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Mount action {} not added into button {} for player {} ({}): Player does not know this mount",
6212 action, button, GetName(), GetGUID().ToString());
6213 return false;
6214 }
6215 break;
6216 }
6217 case ACTION_BUTTON_C:
6222 break;
6223 default:
6224 TC_LOG_ERROR("entities.player", "Player::IsActionButtonDataValid: Unknown action type {}", type);
6225 return false; // other cases not checked at this moment
6226 }
6227
6228 return true;
6229}
6230
6232{
6233 if (!IsActionButtonDataValid(button, action, type))
6234 return nullptr;
6235
6236 // it create new button (NEW state) if need or return existing
6237 ActionButton& ab = m_actionButtons[button];
6238
6239 // set data and update to CHANGED if not NEW
6240 ab.SetActionAndType(action, ActionButtonType(type));
6241
6242 TC_LOG_DEBUG("entities.player", "Player::AddActionButton: Player '{}' ({}) added action '{}' (type {}) to button '{}'",
6243 GetName(), GetGUID().ToString(), action, type, button);
6244 return &ab;
6245}
6246
6248{
6249 ActionButtonList::iterator buttonItr = m_actionButtons.find(button);
6250 if (buttonItr == m_actionButtons.end() || buttonItr->second.uState == ACTIONBUTTON_DELETED)
6251 return;
6252
6253 if (buttonItr->second.uState == ACTIONBUTTON_NEW)
6254 m_actionButtons.erase(buttonItr); // new and not saved
6255 else
6256 buttonItr->second.uState = ACTIONBUTTON_DELETED; // saved, will deleted at next save
6257
6258 TC_LOG_DEBUG("entities.player", "Player::RemoveActionButton: Player '{}' ({}) removed action button '{}'",
6259 GetName(), GetGUID().ToString(), button);
6260}
6261
6263{
6264 ActionButtonList::iterator buttonItr = m_actionButtons.find(button);
6265 if (buttonItr == m_actionButtons.end() || buttonItr->second.uState == ACTIONBUTTON_DELETED)
6266 return nullptr;
6267
6268 return &buttonItr->second;
6269}
6270
6271bool Player::UpdatePosition(float x, float y, float z, float orientation, bool teleport)
6272{
6273 if (!Unit::UpdatePosition(x, y, z, orientation, teleport))
6274 return false;
6275
6276 //if (movementInfo.flags & MOVEMENTFLAG_MOVING)
6277 // mover->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Moving);
6278 //if (movementInfo.flags & MOVEMENTFLAG_TURNING)
6279 // mover->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Turning);
6280
6281 // group update
6282 if (GetGroup())
6284
6286
6287 return true;
6288}
6289
6290void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) const
6291{
6292 if (self)
6293 SendDirectMessage(data);
6294
6295 Trinity::PacketSenderRef sender(data);
6297 Cell::VisitWorldObjects(this, notifier, dist);
6298}
6299
6300void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self, bool own_team_only, bool required3dDist /*= false*/) const
6301{
6302 if (self)
6303 SendDirectMessage(data);
6304
6305 Trinity::PacketSenderRef sender(data);
6306 Trinity::MessageDistDeliverer<Trinity::PacketSenderRef> notifier(this, sender, dist, own_team_only, nullptr, required3dDist);
6307 Cell::VisitWorldObjects(this, notifier, dist);
6308}
6309
6310void Player::SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) const
6311{
6312 if (skipped_rcvr != this)
6313 SendDirectMessage(data);
6314
6315 // we use World::GetMaxVisibleDistance() because i cannot see why not use a distance
6316 // update: replaced by GetMap()->GetVisibilityDistance()
6317 Trinity::PacketSenderRef sender(data);
6318 Trinity::MessageDistDeliverer<Trinity::PacketSenderRef> notifier(this, sender, GetVisibilityRange(), false, skipped_rcvr);
6320}
6321
6323{
6324 m_session->SendPacket(data);
6325}
6326
6327void Player::SendCinematicStart(uint32 CinematicSequenceId) const
6328{
6330 packet.CinematicID = CinematicSequenceId;
6331 SendDirectMessage(packet.Write());
6332 if (CinematicSequencesEntry const* sequence = sCinematicSequencesStore.LookupEntry(CinematicSequenceId))
6333 _cinematicMgr->BeginCinematic(sequence);
6334}
6335
6337{
6338 SetMovie(movieId);
6340 packet.MovieID = movieId;
6341 SendDirectMessage(packet.Write());
6342}
6343
6345{
6346 if (!IsAlive())
6347 return;
6348
6349 if (IsInFlight())
6350 return;
6351
6352 uint32 const areaId = GetAreaId();
6353 if (!areaId)
6354 return;
6355
6356 AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId);
6357 if (!areaEntry)
6358 {
6359 TC_LOG_ERROR("entities.player", "Player '{}' ({}) discovered unknown area (x: {} y: {} z: {} map: {})",
6361 return;
6362 }
6363
6364 uint32 offset = areaEntry->AreaBit / PLAYER_EXPLORED_ZONES_BITS;
6365 uint64 val = UI64LIT(1) << (areaEntry->AreaBit % PLAYER_EXPLORED_ZONES_BITS);
6366
6367 if (offset >= m_activePlayerData->BitVectors->Values[PLAYER_DATA_FLAG_EXPLORED_ZONES_INDEX].Values.size()
6368 || !(m_activePlayerData->BitVectors->Values[PLAYER_DATA_FLAG_EXPLORED_ZONES_INDEX].Values[offset] & val))
6369 {
6370 AddExploredZones(offset, val);
6371
6373
6374 if (Optional<ContentTuningLevels> areaLevels = sDB2Manager.GetContentTuningData(areaEntry->ContentTuningID, m_playerData->CtrOptions->ConditionalFlags))
6375 {
6376 if (IsMaxLevel())
6377 {
6378 SendExplorationExperience(areaId, 0);
6379 }
6380 else
6381 {
6382 int16 areaLevel = std::min(std::max(int16(GetLevel()), areaLevels->MinLevel), areaLevels->MaxLevel);
6383 int32 diff = int32(GetLevel()) - areaLevel;
6384 uint32 XP;
6385 if (diff < -5)
6386 {
6387 XP = uint32(sObjectMgr->GetBaseXP(GetLevel() + 5) * sWorld->getRate(RATE_XP_EXPLORE));
6388 }
6389 else if (diff > 5)
6390 {
6391 int32 exploration_percent = 100 - ((diff - 5) * 5);
6392 if (exploration_percent < 0)
6393 exploration_percent = 0;
6394
6395 XP = uint32(sObjectMgr->GetBaseXP(areaLevel) * exploration_percent / 100 * sWorld->getRate(RATE_XP_EXPLORE));
6396 }
6397 else
6398 {
6399 XP = uint32(sObjectMgr->GetBaseXP(areaLevel) * sWorld->getRate(RATE_XP_EXPLORE));
6400 }
6401
6403 {
6404 uint32 minScaledXP = uint32(sObjectMgr->GetBaseXP(areaLevel)*sWorld->getRate(RATE_XP_EXPLORE)) * sWorld->getIntConfig(CONFIG_MIN_DISCOVERED_SCALED_XP_RATIO) / 100;
6405 XP = std::max(minScaledXP, XP);
6406 }
6407
6409
6410 GiveXP(XP, nullptr);
6411 SendExplorationExperience(areaId, XP);
6412 }
6413 TC_LOG_DEBUG("entities.player", "Player '{}' ({}) discovered a new area: {}", GetName(),GetGUID().ToString(), areaId);
6414 }
6415 }
6416}
6417
6419{
6421 .ModifyValue(&Player::m_activePlayerData)
6424 .ModifyValue(&UF::BitVector::Values, pos), mask);
6425}
6426
6428{
6430 .ModifyValue(&Player::m_activePlayerData)
6433 .ModifyValue(&UF::BitVector::Values, pos), mask);
6434}
6435
6437{
6438 AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId);
6439 if (!area)
6440 return false;
6441
6442 if (area->AreaBit < 0)
6443 return false;
6444
6445 size_t playerIndexOffset = size_t(area->AreaBit) / PLAYER_EXPLORED_ZONES_BITS;
6446 if (playerIndexOffset >= m_activePlayerData->BitVectors->Values[PLAYER_DATA_FLAG_EXPLORED_ZONES_INDEX].Values.size())
6447 return false;
6448
6449 uint64 mask = uint64(1) << (area->AreaBit % PLAYER_EXPLORED_ZONES_BITS);
6450 return (m_activePlayerData->BitVectors->Values[PLAYER_DATA_FLAG_EXPLORED_ZONES_INDEX].Values[playerIndexOffset] & mask) != 0;
6451}
6452
6454{
6455 uint32 newzone = 0, newarea = 0;
6456 GetZoneAndAreaId(newzone, newarea);
6457
6458 if (m_zoneUpdateId != newzone)
6459 UpdateZone(newzone, newarea); // also update area
6460 else
6461 {
6462 // use area updates as well
6463 // needed for free far all arenas for example
6464 if (m_areaUpdateId != newarea)
6465 UpdateArea(newarea);
6466 }
6467}
6468
6470{
6471 if (sWorld->getBoolConfig(CONFIG_VMAP_INDOOR_CHECK))
6473}
6474
6476{
6477 AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(_restMgr->GetInnTriggerID());
6478
6479 if (_restMgr->HasRestFlag(REST_FLAG_IN_TAVERN) && (!atEntry || !IsInAreaTrigger(atEntry)))
6480 _restMgr->RemoveRestFlag(REST_FLAG_IN_TAVERN);
6481 else if (!_restMgr->HasRestFlag(REST_FLAG_IN_TAVERN) && IsInAreaTrigger(atEntry))
6482 _restMgr->SetRestFlag(REST_FLAG_IN_TAVERN);
6483}
6484
6486{
6487 if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race))
6488 {
6489 switch (rEntry->Alliance)
6490 {
6491 case 0: return ALLIANCE;
6492 case 1: return HORDE;
6493 case 2: return PANDARIA_NEUTRAL;
6494 }
6495 TC_LOG_ERROR("entities.player", "Race ({}) has wrong teamid ({}) in DBC: wrong DBC files?", uint32(race), rEntry->Alliance);
6496 }
6497 else
6498 TC_LOG_ERROR("entities.player", "Race ({}) not found in DBC: wrong DBC files?", uint32(race));
6499
6500 return ALLIANCE;
6501}
6502
6504{
6505 if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race))
6506 return TeamId(rEntry->Alliance);
6507
6508 TC_LOG_ERROR("entities.player", "Race ({}) not found in DBC: wrong DBC files?", race);
6509 return TEAM_NEUTRAL;
6510}
6511
6513{
6514 if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race))
6515 if (FactionTemplateEntry const* faction = sFactionTemplateStore.LookupEntry(rEntry->FactionID))
6516 return faction->FactionGroup;
6517
6518 return 1;
6519}
6520
6522{
6523 m_team = TeamForRace(race);
6524
6525 ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race);
6526 SetFaction(rEntry ? rEntry->FactionID : 0);
6527}
6528
6530{
6531 FactionEntry const* factionEntry = sFactionStore.LookupEntry(faction);
6532 return GetReputationMgr().GetRank(factionEntry);
6533}
6534
6535// Calculate total reputation percent player gain with quest/creature level
6536int32 Player::CalculateReputationGain(ReputationSource source, uint32 creatureOrQuestLevel, int32 rep, int32 faction, bool noQuestBonus)
6537{
6538 bool noBonuses = false;
6539 if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(faction))
6540 if (FriendshipReputationEntry const* friendshipReputation = sFriendshipReputationStore.LookupEntry(factionEntry->FriendshipRepID))
6541 if (friendshipReputation->GetFlags().HasFlag(FriendshipReputationFlags::NoRepGainModifiers))
6542 noBonuses = true;
6543
6544 float percent = 100.0f;
6545
6546 if (!noBonuses)
6547 {
6548 float repMod = noQuestBonus ? 0.0f : float(GetTotalAuraModifier(SPELL_AURA_MOD_REPUTATION_GAIN));
6549
6550 // faction specific auras only seem to apply to kills
6551 if (source == REPUTATION_SOURCE_KILL)
6553
6554 percent += rep > 0 ? repMod : -repMod;
6555 }
6556
6557 float rate;
6558 switch (source)
6559 {
6561 rate = sWorld->getRate(RATE_REPUTATION_LOWLEVEL_KILL);
6562 break;
6568 rate = sWorld->getRate(RATE_REPUTATION_LOWLEVEL_QUEST);
6569 break;
6571 default:
6572 rate = 1.0f;
6573 break;
6574 }
6575
6576 if (rate != 1.0f && creatureOrQuestLevel < Trinity::XP::GetGrayLevel(GetLevel()))
6577 percent *= rate;
6578
6579 if (percent <= 0.0f)
6580 return 0;
6581
6582 // Multiply result with the faction specific rate
6583 if (RepRewardRate const* repData = sObjectMgr->GetRepRewardRate(faction))
6584 {
6585 float repRate = 0.0f;
6586 switch (source)
6587 {
6589 repRate = repData->creatureRate;
6590 break;
6592 repRate = repData->questRate;
6593 break;
6595 repRate = repData->questDailyRate;
6596 break;
6598 repRate = repData->questWeeklyRate;
6599 break;
6601 repRate = repData->questMonthlyRate;
6602 break;
6604 repRate = repData->questRepeatableRate;
6605 break;
6607 repRate = repData->spellRate;
6608 break;
6609 }
6610
6611 // for custom, a rate of 0.0 will totally disable reputation gain for this faction/type
6612 if (repRate <= 0.0f)
6613 return 0;
6614
6615 percent *= repRate;
6616 }
6617
6618 if (source != REPUTATION_SOURCE_SPELL && GetsRecruitAFriendBonus(false))
6619 percent *= 1.0f + sWorld->getRate(RATE_REPUTATION_RECRUIT_A_FRIEND_BONUS);
6620
6621 return CalculatePct(rep, percent);
6622}
6623
6625{
6626 auto itr = std::ranges::find(m_playerData->ForcedReactions, int32(factionId), &UF::ZonePlayerForcedReaction::FactionID);
6627 if (itr == m_playerData->ForcedReactions.end())
6628 itr = std::ranges::find(m_playerData->ForcedReactions, 0, &UF::ZonePlayerForcedReaction::FactionID);
6629
6630 if (itr == m_playerData->ForcedReactions.end())
6631 return; // no more free slots
6632
6634 .ModifyValue(&UF::PlayerData::ForcedReactions, std::ranges::distance(m_playerData->ForcedReactions.begin(), itr));
6635
6636 SetUpdateFieldValue(setter.ModifyValue(&UF::ZonePlayerForcedReaction::FactionID), factionId);
6638}
6639
6641{
6642 auto itr = std::ranges::find(m_playerData->ForcedReactions, int32(factionId), &UF::ZonePlayerForcedReaction::FactionID);
6643 if (itr == m_playerData->ForcedReactions.end())
6644 return;
6645
6647 .ModifyValue(&UF::PlayerData::ForcedReactions, std::ranges::distance(m_playerData->ForcedReactions.begin(), itr));
6648
6651}
6652
6653// Calculates how many reputation points player gains in victim's enemy factions
6654void Player::RewardReputation(Unit* victim, float rate)
6655{
6656 if (!victim || victim->GetTypeId() == TYPEID_PLAYER)
6657 return;
6658
6659 if (victim->ToCreature()->IsReputationGainDisabled())
6660 return;
6661
6662 ReputationOnKillEntry const* Rep = sObjectMgr->GetReputationOnKilEntry(victim->ToCreature()->GetCreatureTemplate()->Entry);
6663 if (!Rep)
6664 return;
6665
6666 uint32 ChampioningFaction = 0;
6667
6669 {
6670 // support for: Championing - http://www.wowwiki.com/Championing
6671 Map const* map = GetMap();
6672 if (map->IsNonRaidDungeon())
6673 if (LFGDungeonsEntry const* dungeon = DB2Manager::GetLfgDungeon(map->GetId(), map->GetDifficultyID()))
6674 if (Optional<ContentTuningLevels> dungeonLevels = sDB2Manager.GetContentTuningData(dungeon->ContentTuningID, m_playerData->CtrOptions->ConditionalFlags))
6675 if (dungeonLevels->TargetLevelMax == int16(GetMaxLevelForExpansion(EXPANSION_WRATH_OF_THE_LICH_KING)))
6676 ChampioningFaction = GetChampioningFaction();
6677 }
6678
6679 uint32 team = GetTeam();
6680
6681 if (Rep->RepFaction1 && (!Rep->TeamDependent || team == ALLIANCE))
6682 {
6683 int32 donerep1 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->GetLevelForTarget(this), Rep->RepValue1, ChampioningFaction ? ChampioningFaction : Rep->RepFaction1);
6684 donerep1 = int32(donerep1 * rate);
6685
6686 FactionEntry const* factionEntry1 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : Rep->RepFaction1);
6687 uint32 current_reputation_rank1 = GetReputationMgr().GetRank(factionEntry1);
6688 if (factionEntry1)
6689 GetReputationMgr().ModifyReputation(factionEntry1, donerep1, current_reputation_rank1 > Rep->ReputationMaxCap1);
6690 }
6691
6692 if (Rep->RepFaction2 && (!Rep->TeamDependent || team == HORDE))
6693 {
6694 int32 donerep2 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->GetLevelForTarget(this), Rep->RepValue2, ChampioningFaction ? ChampioningFaction : Rep->RepFaction2);
6695 donerep2 = int32(donerep2 * rate);
6696
6697 FactionEntry const* factionEntry2 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : Rep->RepFaction2);
6698 uint32 current_reputation_rank2 = GetReputationMgr().GetRank(factionEntry2);
6699 if (factionEntry2)
6700 GetReputationMgr().ModifyReputation(factionEntry2, donerep2, current_reputation_rank2 > Rep->ReputationMaxCap2);
6701 }
6702}
6703
6704// Calculate how many reputation points player gain with the quest
6706{
6707 for (uint8 i = 0; i < QUEST_REWARD_REPUTATIONS_COUNT; ++i)
6708 {
6709 if (!quest->RewardFactionId[i])
6710 continue;
6711
6712 FactionEntry const* factionEntry = sFactionStore.LookupEntry(quest->RewardFactionId[i]);
6713 if (!factionEntry)
6714 continue;
6715
6716 int32 rep = 0;
6717 bool noQuestBonus = false;
6718
6719 if (quest->RewardFactionOverride[i])
6720 {
6721 rep = quest->RewardFactionOverride[i] / 100;
6722 noQuestBonus = true;
6723 }
6724 else
6725 {
6726 uint32 row = ((quest->RewardFactionValue[i] < 0) ? 1 : 0) + 1;
6727 if (QuestFactionRewardEntry const* questFactionRewEntry = sQuestFactionRewardStore.LookupEntry(row))
6728 {
6729 uint32 field = abs(quest->RewardFactionValue[i]);
6730 rep = questFactionRewEntry->Difficulty[field];
6731 }
6732 }
6733
6734 if (!rep)
6735 continue;
6736
6737 if (quest->RewardFactionCapIn[i] && rep > 0 && GetReputationMgr().GetRank(factionEntry) >= quest->RewardFactionCapIn[i])
6738 continue;
6739
6740 if (quest->IsDaily())
6741 rep = CalculateReputationGain(REPUTATION_SOURCE_DAILY_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], noQuestBonus);
6742 else if (quest->IsWeekly())
6743 rep = CalculateReputationGain(REPUTATION_SOURCE_WEEKLY_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], noQuestBonus);
6744 else if (quest->IsMonthly())
6745 rep = CalculateReputationGain(REPUTATION_SOURCE_MONTHLY_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], noQuestBonus);
6746 else if (quest->IsRepeatable())
6748 else
6749 rep = CalculateReputationGain(REPUTATION_SOURCE_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], noQuestBonus);
6750
6751 bool noSpillover = (quest->GetRewardReputationMask() & (1 << i)) != 0;
6752 GetReputationMgr().ModifyReputation(factionEntry, rep, false, noSpillover);
6753 }
6754}
6755
6757{
6759 time_t now = GameTime::GetGameTime();
6760 time_t today = GameTime::GetGameTime() / DAY * DAY;
6761
6762 if (m_lastHonorUpdateTime < today)
6763 {
6764 time_t yesterday = today - DAY;
6765
6766 // update yesterday's contribution
6767 if (m_lastHonorUpdateTime >= yesterday)
6768 {
6769 // this is the first update today, reset today's contribution
6771 }
6772 else
6773 {
6774 // no honor/kills yesterday or today, reset
6776 }
6777
6779 }
6780
6782}
6783
6787bool Player::RewardHonor(Unit* victim, uint32 groupsize, int32 honor, bool pvptoken)
6788{
6789 // do not reward honor in arenas, but enable onkill spellproc
6790 if (InArena())
6791 {
6792 if (!victim || victim == this || victim->GetTypeId() != TYPEID_PLAYER)
6793 return false;
6794
6795 if (GetBGTeam() == victim->ToPlayer()->GetBGTeam())
6796 return false;
6797
6798 return true;
6799 }
6800
6801 // 'Inactive' this aura prevents the player from gaining honor points and battleground tokens
6803 return false;
6804
6805 ObjectGuid victim_guid;
6806 uint32 victim_rank = 0;
6807
6808 // need call before fields update to have chance move yesterday data to appropriate fields before today data change.
6810
6811 // do not reward honor in arenas, but return true to enable onkill spellproc
6812 if (InBattleground() && GetBattleground() && GetBattleground()->isArena())
6813 return true;
6814
6815 // Promote to float for calculations
6816 float honor_f = (float)honor;
6817
6818 if (honor_f <= 0)
6819 {
6820 if (!victim || victim == this || victim->HasAuraType(SPELL_AURA_NO_PVP_CREDIT))
6821 return false;
6822
6823 victim_guid = victim->GetGUID();
6824
6825 if (Player* plrVictim = victim->ToPlayer())
6826 {
6827 if (GetEffectiveTeam() == plrVictim->GetEffectiveTeam() && !sWorld->IsFFAPvPRealm())
6828 return false;
6829
6830 uint8 k_level = GetLevel();
6831 uint8 k_grey = Trinity::XP::GetGrayLevel(k_level);
6832 uint8 v_level = victim->GetLevelForTarget(this);
6833
6834 if (v_level <= k_grey)
6835 return false;
6836
6837 // PLAYER_CHOSEN_TITLE VALUES DESCRIPTION
6838 // [0] Just name
6839 // [1..14] Alliance honor titles and player name
6840 // [15..28] Horde honor titles and player name
6841 // [29..38] Other title and player name
6842 // [39+] Nothing
6843 // this is all wrong, should be going off PvpTitle, not PlayerTitle
6844 uint32 victim_title = plrVictim->m_playerData->PlayerTitle;
6845 // Get Killer titles, CharTitlesEntry::MaskID
6846 // Ranks:
6847 // title[1..14] -> rank[5..18]
6848 // title[15..28] -> rank[5..18]
6849 // title[other] -> 0
6850 if (victim_title == 0)
6851 victim_guid.Clear(); // Don't show HK: <rank> message, only log.
6852 else if (victim_title < 15)
6853 victim_rank = victim_title + 4;
6854 else if (victim_title < 29)
6855 victim_rank = victim_title - 14 + 4;
6856 else
6857 victim_guid.Clear(); // Don't show HK: <rank> message, only log.
6858
6859 honor_f = std::ceil(Trinity::Honor::hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey));
6860
6861 // count the number of playerkills in one day
6863 // and those in a lifetime
6870 UpdateCriteria(CriteriaType::KillPlayer, 1, 0, 0, victim);
6871 }
6872 else
6873 {
6874 if (!victim->ToCreature()->IsRacialLeader())
6875 return false;
6876
6877 honor_f = 100.0f; // ??? need more info
6878 victim_rank = 19; // HK: Leader
6879 }
6880 }
6881
6882 if (victim != nullptr)
6883 {
6884 if (groupsize > 1)
6885 honor_f /= groupsize;
6886
6887 // apply honor multiplier from aura (not stacking-get highest)
6889 honor_f += _restMgr->GetRestBonusFor(REST_TYPE_HONOR, honor_f);
6890 }
6891
6892 honor_f *= sWorld->getRate(RATE_HONOR);
6893 // Back to int now
6894 honor = int32(honor_f);
6895 // honor - for show honor points in log
6896 // victim_guid - for show victim name in log
6897 // victim_rank [1..4] HK: <dishonored rank>
6898 // victim_rank [5..19] HK: <alliance\horde rank>
6899 // victim_rank [0, 20+] HK: <>
6901 data.Honor = honor;
6902 data.OriginalHonor = honor;
6903 data.Target = victim_guid;
6904 data.Rank = victim_rank;
6905
6906 SendDirectMessage(data.Write());
6907
6908 AddHonorXP(honor);
6909
6910 if (InBattleground() && honor > 0)
6911 {
6912 if (Battleground* bg = GetBattleground())
6913 {
6914 bg->UpdatePlayerScore(this, SCORE_BONUS_HONOR, honor, false); //false: prevent looping
6915 }
6916 }
6917
6918 if (sWorld->getBoolConfig(CONFIG_PVP_TOKEN_ENABLE) && pvptoken)
6919 {
6920 if (!victim || victim == this || victim->HasAuraType(SPELL_AURA_NO_PVP_CREDIT))
6921 return true;
6922
6923 if (victim->GetTypeId() == TYPEID_PLAYER)
6924 {
6925 // Check if allowed to receive it in current map
6927 if ((MapType == 1 && !InBattleground() && !IsFFAPvP())
6928 || (MapType == 2 && !IsFFAPvP())
6929 || (MapType == 3 && !InBattleground()))
6930 return true;
6931
6932 uint32 itemId = sWorld->getIntConfig(CONFIG_PVP_TOKEN_ID);
6933 int32 count = sWorld->getIntConfig(CONFIG_PVP_TOKEN_COUNT);
6934
6935 if (AddItem(itemId, count))
6936 ChatHandler(GetSession()).PSendSysMessage("You have been awarded a token for slaying another player.");
6937 }
6938 }
6939
6940 return true;
6941}
6942
6944{
6948}
6949
6951{
6954
6955 AddHonorXP(honor);
6956}
6957
6959{
6960 RewardPlayerWithRewardPack(sRewardPackStore.LookupEntry(rewardPackID));
6961}
6962
6964{
6965 if (!rewardPackEntry)
6966 return;
6967
6968 if (CharTitlesEntry const* charTitlesEntry = sCharTitlesStore.LookupEntry(rewardPackEntry->CharTitleID))
6969 SetTitle(charTitlesEntry);
6970
6971 ModifyMoney(rewardPackEntry->Money);
6972
6973 if (std::vector<RewardPackXCurrencyTypeEntry const*> const* rewardCurrencyTypes = sDB2Manager.GetRewardPackCurrencyTypesByRewardID(rewardPackEntry->ID))
6974 for (RewardPackXCurrencyTypeEntry const* currency : *rewardCurrencyTypes)
6975 AddCurrency(currency->CurrencyTypeID, currency->Quantity /* TODO: CurrencyGainSource */);
6976
6977 if (std::vector<RewardPackXItemEntry const*> const* rewardPackXItems = sDB2Manager.GetRewardPackItemsByRewardID(rewardPackEntry->ID))
6978 for (RewardPackXItemEntry const* rewardPackXItem : *rewardPackXItems)
6979 AddItem(rewardPackXItem->ItemID, rewardPackXItem->ItemQuantity);
6980}
6981
6983{
6984 uint32 currentHonorXP = m_activePlayerData->Honor;
6985 uint32 nextHonorLevelXP = m_activePlayerData->HonorNextLevel;
6986 uint32 newHonorXP = currentHonorXP + xp;
6987 uint32 honorLevel = GetHonorLevel();
6988
6989 if (xp < 1 || GetLevel() < PLAYER_LEVEL_MIN_HONOR || IsMaxHonorLevel())
6990 return;
6991
6992 while (newHonorXP >= nextHonorLevelXP)
6993 {
6994 newHonorXP -= nextHonorLevelXP;
6995
6996 if (honorLevel < PLAYER_MAX_HONOR_LEVEL)
6997 SetHonorLevel(honorLevel + 1);
6998
6999 honorLevel = GetHonorLevel();
7000 nextHonorLevelXP = m_activePlayerData->HonorNextLevel;
7001 }
7002
7004}
7005
7007{
7008 uint8 oldHonorLevel = GetHonorLevel();
7009 if (level == oldHonorLevel)
7010 return;
7011
7014
7016}
7017
7019{
7020 // 5500 at honor level 1
7021 // no idea what between here
7022 // 8800 at honor level ~14 (never goes above 8800)
7024}
7025
7027{
7028 if (!result)
7029 return;
7030
7031 do
7032 {
7033 Field* fields = result->Fetch();
7034
7035 uint16 currencyID = fields[0].GetUInt16();
7036
7037 CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(currencyID);
7038 if (!currency)
7039 continue;
7040
7041 PlayerCurrency cur;
7043 cur.Quantity = fields[1].GetUInt32();
7044 cur.WeeklyQuantity = fields[2].GetUInt32();
7045 cur.TrackedQuantity = fields[3].GetUInt32();
7046 cur.IncreasedCapQuantity = fields[4].GetUInt32();
7047 cur.EarnedQuantity = fields[5].GetUInt32();
7048 cur.Flags = CurrencyDbFlags(fields[6].GetUInt8());
7049
7050 _currencyStorage.insert(PlayerCurrenciesMap::value_type(currencyID, cur));
7051
7052 } while (result->NextRow());
7053}
7054
7056{
7058 for (PlayerCurrenciesMap::iterator itr = _currencyStorage.begin(); itr != _currencyStorage.end(); ++itr)
7059 {
7060 CurrencyTypesEntry const* entry = sCurrencyTypesStore.LookupEntry(itr->first);
7061 if (!entry) // should never happen
7062 continue;
7063
7064 switch (itr->second.state)
7065 {
7066 case PLAYERCURRENCY_NEW:
7067 stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_PLAYER_CURRENCY);
7068 stmt->setUInt64(0, GetGUID().GetCounter());
7069 stmt->setUInt16(1, itr->first);
7070 stmt->setUInt32(2, itr->second.Quantity);
7071 stmt->setUInt32(3, itr->second.WeeklyQuantity);
7072 stmt->setUInt32(4, itr->second.TrackedQuantity);
7073 stmt->setUInt32(5, itr->second.IncreasedCapQuantity);
7074 stmt->setUInt32(6, itr->second.EarnedQuantity);
7075 stmt->setUInt8(7, AsUnderlyingType(itr->second.Flags));
7076 trans->Append(stmt);
7077 break;
7079 stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_PLAYER_CURRENCY);
7080 stmt->setUInt32(0, itr->second.Quantity);
7081 stmt->setUInt32(1, itr->second.WeeklyQuantity);
7082 stmt->setUInt32(2, itr->second.TrackedQuantity);
7083 stmt->setUInt32(3, itr->second.IncreasedCapQuantity);
7084 stmt->setUInt32(4, itr->second.EarnedQuantity);
7085 stmt->setUInt8(5, AsUnderlyingType(itr->second.Flags));
7086 stmt->setUInt64(6, GetGUID().GetCounter());
7087 stmt->setUInt16(7, itr->first);
7088 trans->Append(stmt);
7089 break;
7090 default:
7091 break;
7092 }
7093
7094 itr->second.state = PLAYERCURRENCY_UNCHANGED;
7095 }
7096}
7097
7099{
7101 packet.Data.reserve(_currencyStorage.size());
7102
7103 for (PlayerCurrenciesMap::const_iterator itr = _currencyStorage.begin(); itr != _currencyStorage.end(); ++itr)
7104 {
7105 CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(itr->first);
7106
7107 if (!currency)
7108 continue;
7109
7110 // Check faction
7111 if ((currency->IsAlliance() && GetTeam() != ALLIANCE) ||
7112 (currency->IsHorde() && GetTeam() != HORDE))
7113 continue;
7114
7115 // Check award condition
7117 continue;
7118
7120 record.Type = currency->ID;
7121 record.Quantity = itr->second.Quantity;
7122
7123 if ((itr->second.WeeklyQuantity / currency->GetScaler()) > 0)
7124 record.WeeklyQuantity = itr->second.WeeklyQuantity;
7125
7126 if (currency->HasMaxEarnablePerWeek())
7127 record.MaxWeeklyQuantity = GetCurrencyWeeklyCap(currency);
7128
7129 if (currency->IsTrackingQuantity())
7130 record.TrackedQuantity = itr->second.TrackedQuantity;
7131
7132 if (currency->HasTotalEarned())
7133 record.TotalEarned = itr->second.EarnedQuantity;
7134
7135 if (currency->HasMaxQuantity(true))
7136 record.MaxQuantity = GetCurrencyMaxQuantity(currency, true);
7137
7138 record.Flags = AsUnderlyingType(itr->second.Flags);
7139 record.Flags &= ~AsUnderlyingType(CurrencyDbFlags::UnusedFlags);
7140
7141 packet.Data.push_back(record);
7142 }
7143
7144 SendDirectMessage(packet.Write());
7145}
7146
7148{
7149 //WorldPacket packet(SMSG_REQUEST_PVP_REWARDS_RESPONSE, 24);
7150 //GetSession()->SendPacket(&packet);
7151}
7152
7154{
7155 PlayerCurrenciesMap::iterator itr = _currencyStorage.find(id);
7156 if (itr == _currencyStorage.end())
7157 {
7158 itr = _currencyStorage.emplace(id, PlayerCurrency{}).first;
7159 itr->second.state = PLAYERCURRENCY_NEW;
7160 itr->second.Quantity = amount;
7161 itr->second.WeeklyQuantity = 0;
7162 itr->second.TrackedQuantity = 0;
7163 itr->second.IncreasedCapQuantity = 0;
7164 itr->second.EarnedQuantity = 0;
7165 itr->second.Flags = CurrencyDbFlags(0);
7166 }
7167}
7168
7169void Player::ModifyCurrency(uint32 id, int32 amount, CurrencyGainSource gainSource/* = CurrencyGainSource::Cheat*/, CurrencyDestroyReason destroyReason/* = CurrencyDestroyReason::Cheat*/)
7170{
7171 if (!amount)
7172 return;
7173
7174 CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(id);
7175 ASSERT(currency);
7176
7177 // Check faction
7178 if ((currency->IsAlliance() && GetTeam() != ALLIANCE) ||
7179 (currency->IsHorde() && GetTeam() != HORDE))
7180 return;
7181
7182 // Check award condition
7184 return;
7185
7186 bool isGainOnRefund = [&]() -> bool
7187 {
7188 if (gainSource == CurrencyGainSource::ItemRefund ||
7191 return true;
7192
7193 return false;
7194 }();
7195
7196 bool ignoreCaps = isGainOnRefund || gainSource == CurrencyGainSource::QuestRewardIgnoreCaps || gainSource == CurrencyGainSource::WorldQuestRewardIgnoreCaps;
7197
7198 if (amount > 0 && !isGainOnRefund && gainSource != CurrencyGainSource::Vendor)
7199 {
7202 }
7203
7204 int32 scaler = currency->GetScaler();
7205
7206 // Currency that is immediately converted into reputation with that faction instead
7207 if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(currency->FactionID))
7208 {
7209 amount /= scaler;
7210 GetReputationMgr().ModifyReputation(factionEntry, amount, false, true);
7211 return;
7212 }
7213
7214 // Azerite
7215 if (id == CURRENCY_TYPE_AZERITE)
7216 {
7217 if (amount > 0)
7219 heartOfAzeroth->ToAzeriteItem()->GiveXP(uint64(amount));
7220 return;
7221 }
7222
7223 PlayerCurrenciesMap::iterator itr = _currencyStorage.find(id);
7224 if (itr == _currencyStorage.end())
7225 {
7226 itr = _currencyStorage.emplace(id, PlayerCurrency{}).first;
7227 itr->second.state = PLAYERCURRENCY_NEW;
7228 itr->second.Quantity = 0;
7229 itr->second.WeeklyQuantity = 0;
7230 itr->second.TrackedQuantity = 0;
7231 itr->second.IncreasedCapQuantity = 0;
7232 itr->second.EarnedQuantity = 0;
7233 itr->second.Flags = CurrencyDbFlags(0);
7234 }
7235
7236 uint32 weeklyCap = GetCurrencyWeeklyCap(currency);
7237 if (!ignoreCaps) // Ignore weekly cap for refund
7238 {
7239 // Weekly cap
7240 if (weeklyCap && amount > 0 && (itr->second.WeeklyQuantity + amount) > weeklyCap)
7241 amount = weeklyCap - itr->second.WeeklyQuantity;
7242
7243 // Max cap
7244 uint32 maxCap = GetCurrencyMaxQuantity(currency, false, gainSource == CurrencyGainSource::UpdatingVersion);
7245 if (maxCap && amount > 0 && (itr->second.Quantity + amount) > maxCap)
7246 amount = maxCap - itr->second.Quantity;
7247 }
7248
7249 // Underflow protection
7250 if (amount < 0 && uint32(std::abs(amount)) > itr->second.Quantity)
7251 amount = itr->second.Quantity * -1;
7252
7253 if (!amount)
7254 return;
7255
7256 if (itr->second.state != PLAYERCURRENCY_NEW)
7257 itr->second.state = PLAYERCURRENCY_CHANGED;
7258
7259 itr->second.Quantity += amount;
7260
7261 if (amount > 0 && !ignoreCaps) // Ignore total values update for refund
7262 {
7263 if (weeklyCap)
7264 itr->second.WeeklyQuantity += amount;
7265
7266 if (currency->IsTrackingQuantity())
7267 itr->second.TrackedQuantity += amount;
7268
7269 if (currency->HasTotalEarned())
7270 itr->second.EarnedQuantity += amount;
7271
7272 if (!isGainOnRefund)
7273 {
7275 if (gainSource == CurrencyGainSource::RenownRepGain)
7276 UpdateCriteria(CriteriaType::ReachRenownLevel, id, itr->second.Quantity);
7277 }
7278 }
7279
7280 CurrencyChanged(id, amount);
7281
7283 packet.Type = currency->ID;
7284 packet.Quantity = itr->second.Quantity;
7285 packet.Flags = CurrencyGainFlags::None; // TODO: Check when flags are applied
7286
7287 if ((itr->second.WeeklyQuantity / currency->GetScaler()) > 0)
7288 packet.WeeklyQuantity = itr->second.WeeklyQuantity;
7289
7290 if (currency->HasMaxQuantity(false, gainSource == CurrencyGainSource::UpdatingVersion))
7291 packet.MaxQuantity = GetCurrencyMaxQuantity(currency);
7292
7293 if (currency->HasTotalEarned())
7294 packet.TotalEarned = itr->second.EarnedQuantity;
7295
7297 packet.QuantityChange = amount;
7298
7299 if (amount > 0)
7300 packet.QuantityGainSource = gainSource;
7301 else
7302 packet.QuantityLostSource = destroyReason;
7303
7304 // TODO: FirstCraftOperationID, LastSpendTime & Toasts
7305
7306 SendDirectMessage(packet.Write());
7307}
7308
7309void Player::AddCurrency(uint32 id, uint32 amount, CurrencyGainSource gainSource/* = CurrencyGainSource::Cheat*/)
7310{
7311 ModifyCurrency(id, amount, gainSource);
7312}
7313
7314void Player::RemoveCurrency(uint32 id, int32 amount, CurrencyDestroyReason destroyReason/* = CurrencyDestroyReason::Cheat*/)
7315{
7316 ModifyCurrency(id, -amount, {}, destroyReason);
7317}
7318
7320{
7321 if (!amount)
7322 return;
7323
7324 CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(id);
7325 ASSERT(currency);
7326
7327 // Check faction
7328 if ((currency->IsAlliance() && GetTeam() != ALLIANCE) ||
7329 (currency->IsHorde() && GetTeam() != HORDE))
7330 return;
7331
7332 // Check dynamic maximum flag
7333 if (!currency->GetFlags().HasFlag(CurrencyTypesFlags::DynamicMaximum))
7334 return;
7335
7336 // Ancient mana maximum cap
7338 {
7339 uint32 maxQuantity = GetCurrencyMaxQuantity(currency);
7340
7341 if ((maxQuantity + amount) > CURRENCY_MAX_CAP_ANCIENT_MANA)
7342 amount = CURRENCY_MAX_CAP_ANCIENT_MANA - maxQuantity;
7343 }
7344
7345 PlayerCurrenciesMap::iterator itr = _currencyStorage.find(id);
7346 if (itr == _currencyStorage.end())
7347 {
7348 PlayerCurrency cur;
7350 cur.Quantity = 0;
7351 cur.WeeklyQuantity = 0;
7352 cur.TrackedQuantity = 0;
7353 cur.IncreasedCapQuantity = amount;
7354 cur.EarnedQuantity = 0;
7355 cur.Flags = CurrencyDbFlags(0);
7356 _currencyStorage[id] = cur;
7357 itr = _currencyStorage.find(id);
7358 }
7359 else
7360 {
7361 itr->second.IncreasedCapQuantity += amount;
7362 }
7363
7364 if (itr->second.state != PLAYERCURRENCY_NEW)
7365 itr->second.state = PLAYERCURRENCY_CHANGED;
7366
7368 packet.Type = currency->ID;
7369 packet.Quantity = itr->second.Quantity;
7371
7372 if ((itr->second.WeeklyQuantity / currency->GetScaler()) > 0)
7373 packet.WeeklyQuantity = itr->second.WeeklyQuantity;
7374
7375 if (currency->IsTrackingQuantity())
7376 packet.TrackedQuantity = itr->second.TrackedQuantity;
7377
7378 packet.MaxQuantity = GetCurrencyMaxQuantity(currency);
7379 packet.SuppressChatLog = currency->IsSuppressingChatLog();
7380
7381 SendDirectMessage(packet.Write());
7382}
7383
7385{
7386 for (uint32 arenaSlot = 0; arenaSlot < MAX_ARENA_SLOT; arenaSlot++)
7387 {
7388 if (uint32 arenaTeamId = GetArenaTeamId(arenaSlot))
7389 {
7390 ArenaTeam* arenaTeam = sArenaTeamMgr->GetArenaTeamById(arenaTeamId);
7391 ASSERT(arenaTeam);
7392 arenaTeam->FinishWeek(); // set played this week etc values to 0 in memory, too
7393 arenaTeam->SaveToDB(); // save changes
7394 arenaTeam->NotifyStatsChanged(); // notify the players of the changes
7395 }
7396 }
7397
7398 for (PlayerCurrenciesMap::iterator itr = _currencyStorage.begin(); itr != _currencyStorage.end(); ++itr)
7399 {
7400 itr->second.WeeklyQuantity = 0;
7401 itr->second.state = PLAYERCURRENCY_CHANGED;
7402 }
7403
7405}
7406
7408{
7409 PlayerCurrenciesMap::const_iterator itr = _currencyStorage.find(id);
7410 if (itr == _currencyStorage.end())
7411 return 0;
7412
7413 return itr->second.Quantity;
7414}
7415
7417{
7418 PlayerCurrenciesMap::const_iterator itr = _currencyStorage.find(id);
7419 if (itr == _currencyStorage.end())
7420 return 0;
7421
7422 return itr->second.WeeklyQuantity;
7423}
7424
7426{
7427 PlayerCurrenciesMap::const_iterator itr = _currencyStorage.find(id);
7428 if (itr == _currencyStorage.end())
7429 return 0;
7430
7431 return itr->second.TrackedQuantity;
7432}
7433
7435{
7436 PlayerCurrenciesMap::const_iterator itr = _currencyStorage.find(id);
7437 if (itr == _currencyStorage.end())
7438 return 0;
7439
7440 return itr->second.IncreasedCapQuantity;
7441}
7442
7443uint32 Player::GetCurrencyMaxQuantity(CurrencyTypesEntry const* currency, bool onLoad/* = false*/, bool onUpdateVersion/* = false*/) const
7444{
7445 if (!currency->HasMaxQuantity(onLoad, onUpdateVersion))
7446 return 0;
7447
7448 uint32 maxQuantity = currency->MaxQty;
7449 if (currency->MaxQtyWorldStateID)
7450 maxQuantity = sWorldStateMgr->GetValue(currency->MaxQtyWorldStateID, GetMap());
7451
7452 uint32 increasedCap = 0;
7453 if (currency->GetFlags().HasFlag(CurrencyTypesFlags::DynamicMaximum))
7454 increasedCap = GetCurrencyIncreasedCapQuantity(currency->ID);
7455
7456 return maxQuantity + increasedCap;
7457}
7458
7460{
7461 CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(id);
7462 if (!currency)
7463 return 0;
7464
7465 return GetCurrencyWeeklyCap(currency);
7466}
7467
7469{
7470 // TODO: CurrencyTypeFlags::ComputedWeeklyMaximum
7471 return currency->MaxEarnablePerWeek;
7472}
7473
7474bool Player::HasCurrency(uint32 id, uint32 amount) const
7475{
7476 PlayerCurrenciesMap::const_iterator itr = _currencyStorage.find(id);
7477 return itr != _currencyStorage.end() && itr->second.Quantity >= amount;
7478}
7479
7481{
7482 if (guildId)
7483 {
7484 SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::GuildGUID), ObjectGuid::Create<HighGuid::Guild>(guildId));
7486 }
7487 else
7488 {
7491 }
7492
7493 sCharacterCache->UpdateCharacterGuildId(GetGUID(), guildId);
7494}
7495
7497{
7498}
7499
7500void Player::SetInArenaTeam(uint32 ArenaTeamId, uint8 slot, uint8 type)
7501{
7502 SetArenaTeamInfoField(slot, ARENA_TEAM_ID, ArenaTeamId);
7504}
7505
7507{
7508 ObjectGuid::LowType guidLow = guid.GetCounter();
7510 stmt->setUInt64(0, guidLow);
7511 PreparedQueryResult result = CharacterDatabase.Query(stmt);
7512
7513 if (!result)
7514 return 0;
7515 Field* fields = result->Fetch();
7516 uint32 zone = fields[0].GetUInt16();
7517
7518 if (!zone)
7519 {
7520 // stored zone is zero, use generic and slow zone detection
7521 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_POSITION_XYZ);
7522 stmt->setUInt64(0, guidLow);
7523 result = CharacterDatabase.Query(stmt);
7524
7525 if (!result)
7526 return 0;
7527 fields = result->Fetch();
7528 uint32 map = fields[0].GetUInt16();
7529 float posx = fields[1].GetFloat();
7530 float posy = fields[2].GetFloat();
7531 float posz = fields[3].GetFloat();
7532
7533 if (!sMapStore.LookupEntry(map))
7534 return 0;
7535
7536 zone = sTerrainMgr.GetZoneId(PhasingHandler::GetEmptyPhaseShift(), map, posx, posy, posz);
7537
7538 if (zone > 0)
7539 {
7540 stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ZONE);
7541
7542 stmt->setUInt16(0, uint16(zone));
7543 stmt->setUInt64(1, guidLow);
7544
7545 CharacterDatabase.Execute(stmt);
7546 }
7547 }
7548
7549 return zone;
7550}
7551
7553{
7554 // FFA_PVP flags are area and not zone id dependent
7555 // so apply them accordingly
7556 uint32 const oldArea = m_areaUpdateId;
7557 m_areaUpdateId = newArea;
7558
7559 AreaTableEntry const* oldAreaEntry = sAreaTableStore.LookupEntry(oldArea);
7560 AreaTableEntry const* area = sAreaTableStore.LookupEntry(newArea);
7561 bool oldFFAPvPArea = pvpInfo.IsInFFAPvPArea;
7562 pvpInfo.IsInFFAPvPArea = area && (area->GetFlags().HasFlag(AreaFlags::FreeForAllPvP));
7563 UpdatePvPState(true);
7564
7565 // check if we were in ffa arena and we left
7566 if (oldFFAPvPArea && !pvpInfo.IsInFFAPvPArea)
7568
7570 UpdateAreaDependentAuras(newArea);
7571
7572 if (IsAreaThatActivatesPvpTalents(newArea))
7574 else
7576
7577 // previously this was in UpdateZone (but after UpdateArea) so nothing will break
7578 pvpInfo.IsInNoPvPArea = false;
7579 if (area && area->IsSanctuary()) // in sanctuary
7580 {
7582 pvpInfo.IsInNoPvPArea = true;
7583 if (!duel && GetCombatManager().HasPvPCombat())
7585 }
7586 else
7588
7590 if (area && area->GetFlags().HasFlag(areaRestFlag))
7591 _restMgr->SetRestFlag(REST_FLAG_IN_FACTION_AREA);
7592 else
7593 _restMgr->RemoveRestFlag(REST_FLAG_IN_FACTION_AREA);
7594
7595 PushQuests();
7596
7598
7599 if ((oldAreaEntry && oldAreaEntry->GetFlags2().HasFlag(AreaFlags2::UseSubzoneForChatChannel))
7600 || (area && area->GetFlags2().HasFlag(AreaFlags2::UseSubzoneForChatChannel)))
7601 UpdateLocalChannels(newArea);
7602
7603 if (oldArea != newArea)
7604 {
7607 }
7608}
7609
7610void Player::UpdateZone(uint32 newZone, uint32 newArea)
7611{
7612 if (!IsInWorld())
7613 return;
7614
7615 uint32 const oldZone = m_zoneUpdateId;
7616 m_zoneUpdateId = newZone;
7617
7618 GetMap()->UpdatePlayerZoneStats(oldZone, newZone);
7619
7620 // call leave script hooks immedately (before updating flags)
7621 if (oldZone != newZone)
7622 {
7623 sOutdoorPvPMgr->HandlePlayerLeaveZone(this, oldZone);
7624 sBattlefieldMgr->HandlePlayerLeaveZone(this, oldZone);
7625 }
7626
7627 // group update
7628 if (GetGroup())
7629 {
7631 if (Pet* pet = GetPet())
7632 pet->SetGroupUpdateFlag(GROUP_UPDATE_PET_FULL);
7633 }
7634
7635 // zone changed, so area changed as well, update it.
7636 UpdateArea(newArea);
7637
7638 AreaTableEntry const* zone = sAreaTableStore.LookupEntry(newZone);
7639 if (!zone)
7640 return;
7641
7642 if (sWorld->getBoolConfig(CONFIG_WEATHER))
7644
7645 GetMap()->SendZoneDynamicInfo(newZone, this);
7646
7648
7650
7651 if (zone->GetFlags().HasFlag(AreaFlags::LinkedChat)) // Is in a capital city
7652 {
7653 if (!pvpInfo.IsHostile || zone->IsSanctuary())
7654 _restMgr->SetRestFlag(REST_FLAG_IN_CITY);
7655 pvpInfo.IsInNoPvPArea = true;
7656 }
7657 else
7658 _restMgr->RemoveRestFlag(REST_FLAG_IN_CITY);
7659
7661
7662 // remove items with area/map limitations (delete only for alive player to allow back in ghost mode)
7663 // if player resurrected at teleport this will be applied in resurrect code
7664 if (IsAlive())
7665 DestroyZoneLimitedItem(true, newZone);
7666
7667 // check some item equip limitations (in result lost CanTitanGrip at talent reset, for example)
7669
7670 // recent client version not send leave/join channel packets for built-in local channels
7671 AreaTableEntry const* newAreaEntry = sAreaTableStore.LookupEntry(newArea);
7672 if (!newAreaEntry || !newAreaEntry->GetFlags2().HasFlag(AreaFlags2::UseSubzoneForChatChannel))
7673 UpdateLocalChannels(newZone);
7674
7675 UpdateZoneDependentAuras(newZone);
7676
7677 // call enter script hooks after everyting else has processed
7678 sScriptMgr->OnPlayerUpdateZone(this, newZone, newArea);
7679 if (oldZone != newZone)
7680 {
7681 sOutdoorPvPMgr->HandlePlayerEnterZone(this, newZone);
7682 sBattlefieldMgr->HandlePlayerEnterZone(this, newZone);
7683 SendInitWorldStates(newZone, newArea); // only if really enters to new zone, not just area change, works strange...
7684 if (Guild* guild = GetGuild())
7685 guild->UpdateMemberData(this, GUILD_MEMBER_DATA_ZONEID, newZone);
7688
7689 {
7691
7692 for (Vignettes::VignetteData const* vignette : GetMap()->GetInfiniteAOIVignettes())
7693 {
7694 if (!vignette->Data->GetFlags().HasFlag(VignetteFlags::ZoneInfiniteAOI))
7695 continue;
7696
7697 if (vignette->ZoneID == newZone && Vignettes::CanSee(this, *vignette))
7698 vignette->FillPacket(vignetteUpdate.Added);
7699 else if (vignette->ZoneID == oldZone)
7700 vignetteUpdate.Removed.push_back(vignette->Guid);
7701 }
7702
7703 if (!vignetteUpdate.Added.IDs.empty() || !vignetteUpdate.Removed.empty())
7704 SendDirectMessage(vignetteUpdate.Write());
7705 }
7706 }
7707}
7708
7710{
7711 ZonePVPTypeOverride overrideZonePvpType = GetOverrideZonePVPType();
7712
7713 pvpInfo.IsInHostileArea = false;
7714
7715 if (area->IsSanctuary()) // sanctuary and arena cannot be overriden
7716 pvpInfo.IsInHostileArea = false;
7717 else if (area->GetFlags().HasFlag(AreaFlags::FreeForAllPvP))
7718 pvpInfo.IsInHostileArea = true;
7719 else if (overrideZonePvpType == ZonePVPTypeOverride::None)
7720 {
7721 if (area)
7722 {
7723 if (InBattleground() || area->GetFlags().HasFlag(AreaFlags::CombatZone) || (area->PvpCombatWorldStateID != -1 && sWorldStateMgr->GetValue(area->PvpCombatWorldStateID, GetMap()) != 0))
7724 pvpInfo.IsInHostileArea = true;
7725 else if (IsWarModeLocalActive() || (area->GetFlags().HasFlag(AreaFlags::EnemiesPvPFlagged)))
7726 {
7727 if (area->GetFlags().HasFlag(AreaFlags::Contested))
7729 else
7730 {
7731 FactionTemplateEntry const* factionTemplate = GetFactionTemplateEntry();
7732 if (!factionTemplate || factionTemplate->FriendGroup & area->FactionGroupMask)
7733 pvpInfo.IsInHostileArea = false; // friend area are considered hostile if war mode is active
7734 else if (factionTemplate->EnemyGroup & area->FactionGroupMask)
7735 pvpInfo.IsInHostileArea = true;
7736 else
7737 pvpInfo.IsInHostileArea = sWorld->IsPvPRealm();
7738 }
7739 }
7740 }
7741 }
7742 else
7743 {
7744 switch (overrideZonePvpType)
7745 {
7747 pvpInfo.IsInHostileArea = false;
7748 break;
7752 pvpInfo.IsInHostileArea = true;
7753 break;
7754 default:
7755 break;
7756 }
7757 }
7758
7759 // Treat players having a quest flagging for PvP as always in hostile area
7761}
7762
7763//If players are too far away from the duel flag... they lose the duel
7764void Player::CheckDuelDistance(time_t currTime)
7765{
7766 if (!duel)
7767 return;
7768
7769 ObjectGuid duelFlagGUID = m_playerData->DuelArbiter;
7770 GameObject* obj = GetMap()->GetGameObject(duelFlagGUID);
7771 if (!obj)
7772 return;
7773
7774 if (!duel->OutOfBoundsTime)
7775 {
7776 if (!IsWithinDistInMap(obj, 50))
7777 {
7778 duel->OutOfBoundsTime = currTime + 10;
7779
7781 }
7782 }
7783 else
7784 {
7785 if (IsWithinDistInMap(obj, 40))
7786 {
7787 duel->OutOfBoundsTime = 0;
7788
7790 }
7791 else if (currTime >= duel->OutOfBoundsTime)
7793 }
7794}
7795
7797{
7799}
7800
7802{
7803 // duel not requested
7804 if (!duel)
7805 return;
7806
7807 // Check if DuelComplete() has been called already up in the stack and in that case don't do anything else here
7808 if (duel->State == DUEL_STATE_COMPLETED)
7809 return;
7810
7811 Player* opponent = duel->Opponent;
7812 duel->State = DUEL_STATE_COMPLETED;
7813 opponent->duel->State = DUEL_STATE_COMPLETED;
7814
7815 TC_LOG_DEBUG("entities.unit", "Player::DuelComplete: Player '{}' ({}), Opponent: '{}' ({})",
7816 GetName(), GetGUID().ToString(), opponent->GetName(), opponent->GetGUID().ToString());
7817
7819 duelCompleted.Started = type != DUEL_INTERRUPTED;
7820 SendDirectMessage(duelCompleted.Write());
7821 if (opponent->GetSession())
7822 opponent->SendDirectMessage(duelCompleted.GetRawPacket());
7823
7824 if (type != DUEL_INTERRUPTED)
7825 {
7827 duelWinner.BeatenName = (type == DUEL_WON ? opponent : this)->GetName();
7828 duelWinner.WinnerName = (type == DUEL_WON ? this : opponent)->GetName();
7829 duelWinner.BeatenVirtualRealmAddress = (type == DUEL_WON ? opponent : this)->m_playerData->VirtualPlayerRealm;
7830 duelWinner.WinnerVirtualRealmAddress = (type == DUEL_WON ? this : opponent)->m_playerData->VirtualPlayerRealm;
7831 duelWinner.Fled = type != DUEL_WON;
7832
7833 SendMessageToSet(duelWinner.Write(), true);
7834 }
7835
7836 opponent->DisablePvpRules();
7838
7839 sScriptMgr->OnPlayerDuelEnd(opponent, this, type);
7840
7841 switch (type)
7842 {
7843 case DUEL_FLED:
7844 // if initiator and opponent are on the same team
7845 // or initiator and opponent are not PvP enabled, forcibly stop attacking
7846 if (GetEffectiveTeam() == opponent->GetEffectiveTeam())
7847 {
7848 AttackStop();
7849 opponent->AttackStop();
7850 }
7851 else
7852 {
7853 if (!IsPvP())
7854 AttackStop();
7855 if (!opponent->IsPvP())
7856 opponent->AttackStop();
7857 }
7858 break;
7859 case DUEL_WON:
7862
7863 // Credit for quest Death's Challenge
7864 if (GetClass() == CLASS_DEATH_KNIGHT && opponent->GetQuestStatus(12733) == QUEST_STATUS_INCOMPLETE)
7865 opponent->CastSpell(opponent, 52994, true);
7866
7867 // Honor points after duel (the winner) - ImpConfig
7868 if (uint32 amount = sWorld->getIntConfig(CONFIG_HONOR_AFTER_DUEL))
7869 opponent->RewardHonor(nullptr, 1, amount);
7870
7871 break;
7872 default:
7873 break;
7874 }
7875
7876 // Victory emote spell
7877 if (type != DUEL_INTERRUPTED)
7878 opponent->CastSpell(opponent, 52852, true);
7879
7880 //Remove Duel Flag object
7881 GameObject* obj = GetMap()->GetGameObject(m_playerData->DuelArbiter);
7882 if (obj)
7883 duel->Initiator->RemoveGameObject(obj, true);
7884
7885 /* remove auras */
7886 AuraApplicationMap &itsAuras = opponent->GetAppliedAuras();
7887 for (AuraApplicationMap::iterator i = itsAuras.begin(); i != itsAuras.end();)
7888 {
7889 Aura const* aura = i->second->GetBase();
7890 if (!i->second->IsPositive() && aura->GetCasterGUID() == GetGUID() && aura->GetApplyTime() >= duel->StartTime)
7891 opponent->RemoveAura(i);
7892 else
7893 ++i;
7894 }
7895
7897 for (AuraApplicationMap::iterator i = myAuras.begin(); i != myAuras.end();)
7898 {
7899 Aura const* aura = i->second->GetBase();
7900 if (!i->second->IsPositive() && aura->GetCasterGUID() == opponent->GetGUID() && aura->GetApplyTime() >= duel->StartTime)
7901 RemoveAura(i);
7902 else
7903 ++i;
7904 }
7905
7908
7909 // cleanup combo points
7911 opponent->SetPower(POWER_COMBO_POINTS, 0);
7912
7913 //cleanups
7915 SetDuelTeam(0);
7917 opponent->SetDuelTeam(0);
7918
7919 opponent->duel.reset(nullptr);
7920 duel.reset(nullptr);
7921}
7922
7923//---------------------------------------------------------//
7924
7925void Player::_ApplyItemMods(Item* item, uint8 slot, bool apply, bool updateItemAuras /*= true*/)
7926{
7927 if (slot >= REAGENT_BAG_SLOT_END || !item)
7928 return;
7929
7930 ItemTemplate const* proto = item->GetTemplate();
7931 if (!proto)
7932 return;
7933
7934 // not apply/remove mods for broken item
7935 if (item->IsBroken())
7936 return;
7937
7938 TC_LOG_DEBUG("entities.player.items", "Player::_ApplyItemMods: Applying mods for item {}", item->GetGUID().ToString());
7939
7940 if (item->GetSocketColor(0)) //only (un)equipping of items with sockets can influence metagems, so no need to waste time with normal items
7942
7943 _ApplyItemBonuses(item, slot, apply);
7945 if (updateItemAuras)
7946 {
7948
7949 WeaponAttackType const attackType = Player::GetAttackBySlot(slot, item->GetTemplate()->GetInventoryType());
7950 if (attackType != MAX_ATTACK)
7951 UpdateWeaponDependentAuras(attackType);
7952 }
7955 ApplyEnchantment(item, apply);
7956
7957 TC_LOG_DEBUG("entities.player.items", "Player::_ApplyItemMods: completed");
7958}
7959
7961{
7962 ItemTemplate const* proto = item->GetTemplate();
7963 if (slot >= REAGENT_BAG_SLOT_END || !proto)
7964 return;
7965
7966 uint32 itemLevel = item->GetItemLevel(this);
7967 float combatRatingMultiplier = 1.0f;
7968 if (GtCombatRatingsMultByILvl const* ratingMult = sCombatRatingsMultByILvlGameTable.GetRow(itemLevel))
7969 combatRatingMultiplier = GetIlvlStatMultiplier(ratingMult, proto->GetInventoryType());
7970
7971 for (uint8 i = 0; i < MAX_ITEM_PROTO_STATS; ++i)
7972 {
7973 int32 statType = item->GetItemStatType(i);
7974 if (statType == -1)
7975 continue;
7976
7977 float val = item->GetItemStatValue(i, this);
7978 if (val == 0)
7979 continue;
7980
7981 switch (statType)
7982 {
7983 case ITEM_MOD_MANA:
7985 break;
7986 case ITEM_MOD_HEALTH: // modify HP
7988 break;
7989 case ITEM_MOD_AGILITY: // modify agility
7992 break;
7993 case ITEM_MOD_STRENGTH: //modify strength
7996 break;
7997 case ITEM_MOD_INTELLECT: //modify intellect
8000 break;
8001 // case ITEM_MOD_SPIRIT: //modify spirit
8002 // HandleStatModifier(UNIT_MOD_STAT_SPIRIT, BASE_VALUE, float(val), apply);
8003 // ApplyStatBuffMod(STAT_SPIRIT, CalculatePct(val, GetModifierValue(UNIT_MOD_STAT_SPIRIT, BASE_PCT_EXCLUDE_CREATE)), apply);
8004 // break;
8005 case ITEM_MOD_STAMINA: //modify stamina
8006 {
8007 if (GtStaminaMultByILvl const* staminaMult = sStaminaMultByILvlGameTable.GetRow(itemLevel))
8008 val = int32(val * GetIlvlStatMultiplier(staminaMult, proto->GetInventoryType()));
8011 break;
8012 }
8014 ApplyRatingMod(CR_DEFENSE_SKILL, int32(val * combatRatingMultiplier), apply);
8015 break;
8017 ApplyRatingMod(CR_DODGE, int32(val * combatRatingMultiplier), apply);
8018 break;
8020 ApplyRatingMod(CR_PARRY, int32(val * combatRatingMultiplier), apply);
8021 break;
8023 ApplyRatingMod(CR_BLOCK, int32(val * combatRatingMultiplier), apply);
8024 break;
8026 ApplyRatingMod(CR_HIT_MELEE, int32(val * combatRatingMultiplier), apply);
8027 break;
8029 ApplyRatingMod(CR_HIT_RANGED, int32(val * combatRatingMultiplier), apply);
8030 break;
8032 ApplyRatingMod(CR_HIT_SPELL, int32(val * combatRatingMultiplier), apply);
8033 break;
8035 ApplyRatingMod(CR_CRIT_MELEE, int32(val * combatRatingMultiplier), apply);
8036 break;
8038 ApplyRatingMod(CR_CRIT_RANGED, int32(val * combatRatingMultiplier), apply);
8039 break;
8041 ApplyRatingMod(CR_CRIT_SPELL, int32(val * combatRatingMultiplier), apply);
8042 break;
8043 // case ITEM_MOD_HIT_TAKEN_MELEE_RATING:
8044 // ApplyRatingMod(CR_HIT_TAKEN_MELEE, int32(val), apply);
8045 // break;
8046 // case ITEM_MOD_HIT_TAKEN_RANGED_RATING:
8047 // ApplyRatingMod(CR_HIT_TAKEN_RANGED, int32(val), apply);
8048 // break;
8049 // case ITEM_MOD_HIT_TAKEN_SPELL_RATING:
8050 // ApplyRatingMod(CR_HIT_TAKEN_SPELL, int32(val), apply);
8051 // break;
8052 // case ITEM_MOD_CRIT_TAKEN_MELEE_RATING:
8053 // ApplyRatingMod(CR_CRIT_TAKEN_MELEE, int32(val), apply);
8054 // break;
8057 break;
8058 // case ITEM_MOD_CRIT_TAKEN_SPELL_RATING:
8059 // ApplyRatingMod(CR_CRIT_TAKEN_SPELL, int32(val), apply);
8060 // break;
8063 break;
8066 break;
8069 break;
8071 ApplyRatingMod(CR_HIT_MELEE, int32(val * combatRatingMultiplier), apply);
8072 ApplyRatingMod(CR_HIT_RANGED, int32(val * combatRatingMultiplier), apply);
8073 ApplyRatingMod(CR_HIT_SPELL, int32(val * combatRatingMultiplier), apply);
8074 break;
8076 ApplyRatingMod(CR_CRIT_MELEE, int32(val * combatRatingMultiplier), apply);
8077 ApplyRatingMod(CR_CRIT_RANGED, int32(val * combatRatingMultiplier), apply);
8078 ApplyRatingMod(CR_CRIT_SPELL, int32(val * combatRatingMultiplier), apply);
8079 break;
8080 // case ITEM_MOD_HIT_TAKEN_RATING: // Unused since 3.3.5
8081 // ApplyRatingMod(CR_HIT_TAKEN_MELEE, int32(val), apply);
8082 // ApplyRatingMod(CR_HIT_TAKEN_RANGED, int32(val), apply);
8083 // ApplyRatingMod(CR_HIT_TAKEN_SPELL, int32(val), apply);
8084 // break;
8085 // case ITEM_MOD_CRIT_TAKEN_RATING: // Unused since 3.3.5
8086 // ApplyRatingMod(CR_CRIT_TAKEN_MELEE, int32(val), apply);
8087 // ApplyRatingMod(CR_CRIT_TAKEN_RANGED, int32(val), apply);
8088 // ApplyRatingMod(CR_CRIT_TAKEN_SPELL, int32(val), apply);
8089 // break;
8091 ApplyRatingMod(CR_RESILIENCE_PLAYER_DAMAGE, int32(val * combatRatingMultiplier), apply);
8092 break;
8094 ApplyRatingMod(CR_HASTE_MELEE, int32(val * combatRatingMultiplier), apply);
8095 ApplyRatingMod(CR_HASTE_RANGED, int32(val * combatRatingMultiplier), apply);
8096 ApplyRatingMod(CR_HASTE_SPELL, int32(val * combatRatingMultiplier), apply);
8097 break;
8099 ApplyRatingMod(CR_EXPERTISE, int32(val * combatRatingMultiplier), apply);
8100 break;
8104 break;
8107 break;
8109 ApplyRatingMod(CR_VERSATILITY_DAMAGE_DONE, int32(val * combatRatingMultiplier), apply);
8110 ApplyRatingMod(CR_VERSATILITY_DAMAGE_TAKEN, int32(val * combatRatingMultiplier), apply);
8111 ApplyRatingMod(CR_VERSATILITY_HEALING_DONE, int32(val * combatRatingMultiplier), apply);
8112 break;
8115 break;
8118 break;
8121 break;
8124 break;
8127 break;
8129 ApplyRatingMod(CR_MASTERY, int32(val * combatRatingMultiplier), apply);
8130 break;
8133 break;
8136 break;
8139 break;
8142 break;
8145 break;
8148 break;
8151 break;
8152 case ITEM_MOD_PVP_POWER:
8154 break;
8157 break;
8160 break;
8161 case ITEM_MOD_CR_SPEED:
8162 ApplyRatingMod(CR_SPEED, int32(val * combatRatingMultiplier), apply);
8163 break;
8165 ApplyRatingMod(CR_LIFESTEAL, int32(val * combatRatingMultiplier), apply);
8166 break;
8168 ApplyRatingMod(CR_AVOIDANCE, int32(val * combatRatingMultiplier), apply);
8169 break;
8171 ApplyRatingMod(CR_STURDINESS, int32(val * combatRatingMultiplier), apply);
8172 break;
8180 break;
8181 case ITEM_MOD_AGI_STR:
8186 break;
8187 case ITEM_MOD_AGI_INT:
8192 break;
8193 case ITEM_MOD_STR_INT:
8198 break;
8199 }
8200 }
8201
8202 if (uint32 armor = proto->GetArmor(itemLevel))
8203 {
8205 if (proto->GetClass() == ITEM_CLASS_ARMOR && proto->GetSubClass() == ITEM_SUBCLASS_ARMOR_SHIELD)
8207 }
8208
8210 if (attType != MAX_ATTACK)
8211 _ApplyWeaponDamage(slot, item, apply);
8212}
8213
8215{
8216 ItemTemplate const* proto = item->GetTemplate();
8218 if (!IsInFeralForm() && apply && !CanUseAttackType(attType))
8219 return;
8220
8221 float damage = 0.0f;
8222 uint32 itemLevel = item->GetItemLevel(this);
8223 float minDamage, maxDamage;
8224 proto->GetDamage(itemLevel, minDamage, maxDamage);
8225
8226 if (minDamage > 0)
8227 {
8228 damage = apply ? minDamage : BASE_MINDAMAGE;
8229 SetBaseWeaponDamage(attType, MINDAMAGE, damage);
8230 }
8231
8232 if (maxDamage > 0)
8233 {
8234 damage = apply ? maxDamage : BASE_MAXDAMAGE;
8235 SetBaseWeaponDamage(attType, MAXDAMAGE, damage);
8236 }
8237
8238 SpellShapeshiftFormEntry const* shapeshift = sSpellShapeshiftFormStore.LookupEntry(GetShapeshiftForm());
8239 if (proto->GetDelay() && !(shapeshift && shapeshift->CombatRoundTime))
8240 SetBaseAttackTime(attType, apply ? proto->GetDelay() : BASE_ATTACK_TIME);
8241
8242 int32 weaponBasedAttackPower = apply ? int32(proto->GetDPS(itemLevel) * 6.0f) : 0;
8243 switch (attType)
8244 {
8245 case BASE_ATTACK:
8246 SetMainHandWeaponAttackPower(weaponBasedAttackPower);
8247 break;
8248 case OFF_ATTACK:
8249 SetOffHandWeaponAttackPower(weaponBasedAttackPower);
8250 break;
8251 case RANGED_ATTACK:
8252 SetRangedWeaponAttackPower(weaponBasedAttackPower);
8253 break;
8254 default:
8255 break;
8256 }
8257
8258 if (CanModifyStats() && (damage || proto->GetDelay()))
8259 UpdateDamagePhysical(attType);
8260}
8261
8263{
8264 if (Item const* weapon = GetWeaponForAttack(attackType, true))
8265 return SpellSchoolMask(1 << weapon->GetTemplate()->GetDamageType());
8266
8268}
8269
8271{
8273 for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < inventoryEnd; ++slot)
8274 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
8275 ApplyItemObtainSpells(item, true);
8276
8278 {
8279 Bag* bag = GetBagByPos(i);
8280 if (!bag)
8281 continue;
8282
8283 for (uint32 slot = 0; slot < bag->GetBagSize(); ++slot)
8284 if (Item* item = bag->GetItemByPos(slot))
8285 ApplyItemObtainSpells(item, true);
8286 }
8287}
8288
8290{
8291 if (item->GetTemplate()->HasFlag(ITEM_FLAG_LEGACY))
8292 return;
8293
8294 for (ItemEffectEntry const* effect : item->GetEffects())
8295 {
8296 if (effect->TriggerType != ITEM_SPELLTRIGGER_ON_PICKUP) // On obtain trigger
8297 continue;
8298
8299 int32 const spellId = effect->SpellID;
8300 if (spellId <= 0)
8301 continue;
8302
8303 if (apply)
8304 {
8305 if (!HasAura(spellId))
8306 CastSpell(this, spellId, CastSpellExtraArgs().SetCastItem(item));
8307 }
8308 else
8309 RemoveAurasDueToSpell(spellId);
8310 }
8311}
8312
8313// this one rechecks weapon auras and stores them in BaseModGroup container
8314// needed for things like axe specialization applying only to axe weapons in case of dual-wield
8316{
8317 BaseModGroup modGroup;
8318 switch (attackType)
8319 {
8320 case BASE_ATTACK:
8321 modGroup = CRIT_PERCENTAGE;
8322 break;
8323 case OFF_ATTACK:
8324 modGroup = OFFHAND_CRIT_PERCENTAGE;
8325 break;
8326 case RANGED_ATTACK:
8327 modGroup = RANGED_CRIT_PERCENTAGE;
8328 break;
8329 default:
8330 ABORT();
8331 break;
8332 }
8333
8334 float amount = 0.0f;
8335 amount += GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT, [this, attackType](AuraEffect const* aurEff)
8336 {
8337 return CheckAttackFitToAuraRequirement(attackType, aurEff);
8338 });
8339
8340 // these auras don't have item requirement (only Combat Expertise in 3.3.5a)
8342
8343 SetBaseModFlatValue(modGroup, amount);
8344}
8345
8347{
8348 for (uint8 i = BASE_ATTACK; i < MAX_ATTACK; ++i)
8350}
8351
8353{
8355 UpdateDamageDoneMods(attackType);
8356 UpdateDamagePctDoneMods(attackType);
8357}
8358
8360{
8361 if (apply)
8362 {
8363 PlayerSpellMap const& spells = GetSpellMap();
8364 for (auto itr = spells.begin(); itr != spells.end(); ++itr)
8365 {
8366 if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled)
8367 continue;
8368
8369 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first, DIFFICULTY_NONE);
8370 if (!spellInfo || !spellInfo->IsPassive() || spellInfo->EquippedItemClass < 0)
8371 continue;
8372
8373 if (!HasAura(itr->first) && HasItemFitToSpellRequirements(spellInfo))
8374 AddAura(itr->first, this); // no SMSG_SPELL_GO in sniff found
8375 }
8376 }
8377 else
8379}
8380
8382{
8383 SpellInfo const* spellInfo = aurEff->GetSpellInfo();
8384 if (spellInfo->EquippedItemClass == -1)
8385 return true;
8386
8387 Item* item = GetWeaponForAttack(attackType, true);
8388 if (!item || !item->IsFitToSpellRequirements(spellInfo))
8389 return false;
8390
8391 return true;
8392}
8393
8394void Player::ApplyItemEquipSpell(Item* item, bool apply, bool formChange /*= false*/)
8395{
8396 if (!item || item->GetTemplate()->HasFlag(ITEM_FLAG_LEGACY))
8397 return;
8398
8399 for (ItemEffectEntry const* effectData : item->GetEffects())
8400 {
8401 // wrong triggering type
8402 if (apply && effectData->TriggerType != ITEM_SPELLTRIGGER_ON_EQUIP)
8403 continue;
8404
8405 // check if it is valid spell
8406 SpellInfo const* spellproto = sSpellMgr->GetSpellInfo(effectData->SpellID, DIFFICULTY_NONE);
8407 if (!spellproto)
8408 continue;
8409
8410 if (effectData->ChrSpecializationID && ChrSpecialization(effectData->ChrSpecializationID) != GetPrimarySpecialization())
8411 continue;
8412
8413 ApplyEquipSpell(spellproto, item, apply, formChange);
8414 }
8415}
8416
8417void Player::ApplyEquipSpell(SpellInfo const* spellInfo, Item* item, bool apply, bool formChange /*= false*/)
8418{
8419 if (apply)
8420 {
8421 // Cannot be used in this stance/form
8422 if (spellInfo->CheckShapeshift(GetShapeshiftForm()) != SPELL_CAST_OK)
8423 return;
8424
8425 if (formChange) // check aura active state from other form
8426 {
8427 AuraApplicationMapBounds range = GetAppliedAuras().equal_range(spellInfo->Id);
8428 for (AuraApplicationMap::const_iterator itr = range.first; itr != range.second; ++itr)
8429 if (!item || itr->second->GetBase()->GetCastItemGUID() == item->GetGUID())
8430 return;
8431 }
8432
8433 TC_LOG_DEBUG("entities.player", "Player::ApplyEquipSpell: Player '{}' ({}) cast {} equip spell (ID: {})",
8434 GetName(), GetGUID().ToString(), (item ? "item" : "itemset"), spellInfo->Id);
8435
8437 {
8438 m_itemPassives.push_front(spellInfo->Id);
8439 if (IsInWorld())
8440 {
8442 addItemPassive.SpellID = spellInfo->Id;
8443 SendDirectMessage(addItemPassive.Write());
8444 }
8445 }
8446
8447 CastSpell(this, spellInfo->Id, item);
8448 }
8449 else
8450 {
8451 if (formChange) // check aura compatibility
8452 {
8453 // Cannot be used in this stance/form
8454 if (spellInfo->CheckShapeshift(GetShapeshiftForm()) == SPELL_CAST_OK)
8455 return; // and remove only not compatible at form change
8456 }
8457
8459 {
8461 if (IsInWorld())
8462 {
8463 WorldPackets::Item::RemoveItemPassive removeItemPassive;
8464 removeItemPassive.SpellID = spellInfo->Id;
8465 SendDirectMessage(removeItemPassive.Write());
8466 }
8467 }
8468
8469 if (item)
8470 RemoveAurasDueToItemSpell(spellInfo->Id, item->GetGUID()); // un-apply all spells, not only at-equipped
8471 else
8472 RemoveAurasDueToSpell(spellInfo->Id); // un-apply spell (item set case)
8473 }
8474}
8475
8477{
8478 for (uint8 i = 0; i < REAGENT_BAG_SLOT_END; ++i)
8479 {
8480 if (m_items[i] && !m_items[i]->IsBroken() && CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
8481 {
8482 ApplyItemEquipSpell(m_items[i], false, true); // remove spells that not fit to form
8483 ApplyItemEquipSpell(m_items[i], true, true); // add spells that fit form but not active
8484 }
8485 }
8486
8487 UpdateItemSetAuras(true);
8488}
8489
8490void Player::UpdateItemSetAuras(bool formChange /*= false*/)
8491{
8492 // item set bonuses not dependent from item broken state
8493 for (size_t setindex = 0; setindex < ItemSetEff.size(); ++setindex)
8494 {
8495 ItemSetEffect* eff = ItemSetEff[setindex];
8496 if (!eff)
8497 continue;
8498
8499 for (ItemSetSpellEntry const* itemSetSpell : eff->SetBonuses)
8500 {
8501 SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(itemSetSpell->SpellID, DIFFICULTY_NONE);
8502
8503 if (itemSetSpell->ChrSpecID && ChrSpecialization(itemSetSpell->ChrSpecID) != GetPrimarySpecialization())
8504 ApplyEquipSpell(spellInfo, nullptr, false, false); // item set aura is not for current spec
8505 else
8506 {
8507 ApplyEquipSpell(spellInfo, nullptr, false, formChange); // remove spells that not fit to form - removal is skipped if shapeshift condition is satisfied
8508 ApplyEquipSpell(spellInfo, nullptr, true, formChange); // add spells that fit form but not active
8509 }
8510 }
8511 }
8512}
8513
8515{
8516 if (item->IsArtifactDisabled())
8517 return;
8518
8519 for (UF::ArtifactPower const& artifactPower : item->m_itemData->ArtifactPowers)
8520 {
8521 uint8 rank = artifactPower.CurrentRankWithBonus;
8522 if (!rank)
8523 continue;
8524
8526 rank = 1;
8527
8528 ArtifactPowerRankEntry const* artifactPowerRank = sDB2Manager.GetArtifactPowerRank(artifactPower.ArtifactPowerID, rank - 1);
8529 if (!artifactPowerRank)
8530 continue;
8531
8532 ApplyArtifactPowerRank(item, artifactPowerRank, apply);
8533 }
8534
8536 if (artifactAppearance->OverrideShapeshiftDisplayID && GetShapeshiftForm() == ShapeshiftForm(artifactAppearance->OverrideShapeshiftFormID))
8538}
8539
8540void Player::ApplyArtifactPowerRank(Item* artifact, ArtifactPowerRankEntry const* artifactPowerRank, bool apply)
8541{
8542 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(artifactPowerRank->SpellID, DIFFICULTY_NONE);
8543 if (!spellInfo)
8544 return;
8545
8546 if (spellInfo->IsPassive())
8547 {
8548 AuraApplication* powerAura = GetAuraApplication(artifactPowerRank->SpellID, ObjectGuid::Empty, artifact->GetGUID());
8549 if (powerAura)
8550 {
8551 if (apply)
8552 {
8553 for (AuraEffect* auraEffect : powerAura->GetBase()->GetAuraEffects())
8554 if (powerAura->HasEffect(auraEffect->GetEffIndex()))
8555 auraEffect->ChangeAmount(artifactPowerRank->AuraPointsOverride ? artifactPowerRank->AuraPointsOverride : auraEffect->GetSpellEffectInfo().CalcValue());
8556 }
8557 else
8558 RemoveAura(powerAura);
8559 }
8560 else if (apply)
8561 {
8562 CastSpellExtraArgs args;
8564 args.SetCastItem(artifact);
8565 if (artifactPowerRank->AuraPointsOverride)
8566 for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
8567 args.AddSpellMod(SpellValueMod(SPELLVALUE_BASE_POINT0 + AsUnderlyingType(spellEffectInfo.EffectIndex)), artifactPowerRank->AuraPointsOverride);
8568
8569 CastSpell(this, artifactPowerRank->SpellID, args);
8570 }
8571 }
8572 else
8573 {
8574 if (apply && !HasSpell(artifactPowerRank->SpellID))
8575 {
8576 AddTemporarySpell(artifactPowerRank->SpellID);
8578 WorldPackets::Spells::LearnedSpellInfo& learnedSpellInfo = learnedSpells.ClientLearnedSpellData.emplace_back();
8579 learnedSpellInfo.SpellID = artifactPowerRank->SpellID;
8580 learnedSpells.SuppressMessaging = true;
8581 SendDirectMessage(learnedSpells.Write());
8582 }
8583 else if (!apply)
8584 {
8585 RemoveTemporarySpell(artifactPowerRank->SpellID);
8587 unlearnedSpells.SuppressMessaging = true;
8588 unlearnedSpells.SpellID.push_back(artifactPowerRank->SpellID);
8589 SendDirectMessage(unlearnedSpells.Write());
8590 }
8591 }
8592
8593}
8594
8596{
8597 if (AzeriteItem* azeriteItem = item->ToAzeriteItem())
8598 {
8599 // milestone powers
8600 for (uint32 azeriteItemMilestonePowerId : azeriteItem->m_azeriteItemData->UnlockedEssenceMilestones)
8601 ApplyAzeriteItemMilestonePower(azeriteItem, sAzeriteItemMilestonePowerStore.AssertEntry(azeriteItemMilestonePowerId), apply);
8602
8603 // essences
8604 if (UF::SelectedAzeriteEssences const* selectedEssences = azeriteItem->GetSelectedAzeriteEssences())
8605 for (uint8 slot = 0; slot < MAX_AZERITE_ESSENCE_SLOT; ++slot)
8606 if (selectedEssences->AzeriteEssenceID[slot])
8607 ApplyAzeriteEssence(azeriteItem, selectedEssences->AzeriteEssenceID[slot], azeriteItem->GetEssenceRank(selectedEssences->AzeriteEssenceID[slot]),
8608 AzeriteItemMilestoneType(sDB2Manager.GetAzeriteItemMilestonePower(slot)->Type) == AzeriteItemMilestoneType::MajorEssence, apply);
8609 }
8610 else if (AzeriteEmpoweredItem* azeriteEmpoweredItem = item->ToAzeriteEmpoweredItem())
8611 {
8613 for (int32 i = 0; i < MAX_AZERITE_EMPOWERED_TIER; ++i)
8614 if (AzeritePowerEntry const* azeritePower = sAzeritePowerStore.LookupEntry(azeriteEmpoweredItem->GetSelectedAzeritePower(i)))
8615 ApplyAzeritePower(azeriteEmpoweredItem, azeritePower, apply);
8616 }
8617}
8618
8620{
8621 AzeriteItemMilestoneType type = AzeriteItemMilestoneType(azeriteItemMilestonePower->Type);
8623 {
8624 if (AzeritePowerEntry const* azeritePower = sAzeritePowerStore.LookupEntry(azeriteItemMilestonePower->AzeritePowerID))
8625 {
8626 if (apply)
8627 CastSpell(this, azeritePower->SpellID, item);
8628 else
8629 RemoveAurasDueToItemSpell(azeritePower->SpellID, item->GetGUID());
8630 }
8631 }
8632}
8633
8634void Player::ApplyAzeriteEssence(AzeriteItem* item, uint32 azeriteEssenceId, uint32 rank, bool major, bool apply)
8635{
8636 for (uint32 currentRank = 1; currentRank <= rank; ++currentRank)
8637 {
8638 if (AzeriteEssencePowerEntry const* azeriteEssencePower = sDB2Manager.GetAzeriteEssencePower(azeriteEssenceId, currentRank))
8639 {
8640 ApplyAzeriteEssencePower(item, azeriteEssencePower, major, apply);
8641 if (major && currentRank == 1)
8642 {
8643 if (apply)
8644 {
8646 args.AddSpellMod(SPELLVALUE_BASE_POINT0, azeriteEssencePower->MajorPowerDescription);
8648 }
8649 else
8651 }
8652 }
8653 }
8654}
8655
8656void Player::ApplyAzeriteEssencePower(AzeriteItem* item, AzeriteEssencePowerEntry const* azeriteEssencePower, bool major, bool apply)
8657{
8658 if (SpellInfo const* powerSpell = sSpellMgr->GetSpellInfo(azeriteEssencePower->MinorPowerDescription, DIFFICULTY_NONE))
8659 {
8660 if (apply)
8661 CastSpell(this, powerSpell->Id, item);
8662 else
8663 RemoveAurasDueToItemSpell(powerSpell->Id, item->GetGUID());
8664 }
8665
8666 if (major)
8667 {
8668 if (SpellInfo const* powerSpell = sSpellMgr->GetSpellInfo(azeriteEssencePower->MajorPowerDescription, DIFFICULTY_NONE))
8669 {
8670 if (powerSpell->IsPassive())
8671 {
8672 if (apply)
8673 CastSpell(this, powerSpell->Id, item);
8674 else
8675 RemoveAurasDueToItemSpell(powerSpell->Id, item->GetGUID());
8676 }
8677 else
8678 {
8679 if (apply)
8680 LearnSpell(powerSpell->Id, true, 0, true);
8681 else
8682 RemoveSpell(powerSpell->Id, false, false, true);
8683 }
8684 }
8685 }
8686}
8687
8689{
8690 if (apply)
8691 {
8692 if (!azeritePower->SpecSetID || sDB2Manager.IsSpecSetMember(azeritePower->SpecSetID, AsUnderlyingType(GetPrimarySpecialization())))
8693 CastSpell(this, azeritePower->SpellID, item);
8694 }
8695 else
8696 RemoveAurasDueToItemSpell(azeritePower->SpellID, item->GetGUID());
8697}
8698
8700{
8701 Unit* target = damageInfo.GetVictim();
8702 if (!target || !target->IsAlive() || target == this)
8703 return;
8704
8706 {
8707 // If usable, try to cast item spell
8708 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
8709 {
8710 if (!item->IsBroken() && CanUseAttackType(damageInfo.GetAttackType()))
8711 {
8712 if (ItemTemplate const* proto = item->GetTemplate())
8713 {
8714 // Additional check for weapons
8715 if (proto->GetClass() == ITEM_CLASS_WEAPON)
8716 {
8717 // offhand item cannot proc from main hand hit etc
8718 EquipmentSlots slot;
8719 switch (damageInfo.GetAttackType())
8720 {
8721 case BASE_ATTACK:
8723 break;
8724 case OFF_ATTACK:
8726 break;
8727 case RANGED_ATTACK:
8729 break;
8730 default:
8731 slot = EQUIPMENT_SLOT_END;
8732 break;
8733 }
8734 if (slot != i)
8735 continue;
8736 // Check if item is useable (forms or disarm)
8737 if (damageInfo.GetAttackType() == BASE_ATTACK)
8738 if (!IsUseEquipedWeapon(true) && !IsInFeralForm())
8739 continue;
8740 }
8741
8742 CastItemCombatSpell(damageInfo, item, proto);
8743 }
8744 }
8745 }
8746 }
8747}
8748
8749void Player::CastItemCombatSpell(DamageInfo const& damageInfo, Item* item, ItemTemplate const* proto)
8750{
8751 // Can do effect if any damage done to target
8752 // for done procs allow normal + critical + absorbs by default
8753 bool canTrigger = (damageInfo.GetHitMask() & (PROC_HIT_NORMAL | PROC_HIT_CRITICAL | PROC_HIT_ABSORB)) != 0;
8754 if (canTrigger)
8755 {
8756 if (!item->GetTemplate()->HasFlag(ITEM_FLAG_LEGACY))
8757 {
8758 for (ItemEffectEntry const* effectData : item->GetEffects())
8759 {
8760 // wrong triggering type
8761 if (effectData->TriggerType != ITEM_SPELLTRIGGER_ON_PROC)
8762 continue;
8763
8764 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(effectData->SpellID, DIFFICULTY_NONE);
8765 if (!spellInfo)
8766 {
8767 TC_LOG_ERROR("entities.player.items", "Player::CastItemCombatSpell: Player '{}' ({}) cast unknown item spell (ID: {})",
8768 GetName(), GetGUID().ToString(), effectData->SpellID);
8769 continue;
8770 }
8771
8772 float chance = (float)spellInfo->ProcChance;
8773
8774 if (proto->SpellPPMRate)
8775 {
8776 uint32 WeaponSpeed = GetBaseAttackTime(damageInfo.GetAttackType());
8777 chance = GetPPMProcChance(WeaponSpeed, proto->SpellPPMRate, spellInfo);
8778 }
8779 else if (chance > 100.0f)
8780 chance = GetWeaponProcChance();
8781
8782 if (roll_chance_f(chance) && sScriptMgr->OnCastItemCombatSpell(this, damageInfo.GetVictim(), spellInfo, item))
8783 CastSpell(damageInfo.GetVictim(), spellInfo->Id, item);
8784 }
8785 }
8786 }
8787
8788 // item combat enchantments
8789 for (uint8 e_slot = 0; e_slot < MAX_ENCHANTMENT_SLOT; ++e_slot)
8790 {
8791 uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(e_slot));
8792 SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
8793 if (!pEnchant)
8794 continue;
8795
8796 for (uint8 s = 0; s < MAX_ITEM_ENCHANTMENT_EFFECTS; ++s)
8797 {
8798 if (pEnchant->Effect[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL)
8799 continue;
8800
8801 SpellEnchantProcEntry const* entry = sSpellMgr->GetSpellEnchantProcEvent(enchant_id);
8802 if (entry && entry->HitMask)
8803 {
8804 // Check hit/crit/dodge/parry requirement
8805 if ((entry->HitMask & damageInfo.GetHitMask()) == 0)
8806 continue;
8807 }
8808 else
8809 {
8810 // Can do effect if any damage done to target
8811 // for done procs allow normal + critical + absorbs by default
8812 if (!canTrigger)
8813 continue;
8814 }
8815
8816 // check if enchant procs only on white hits
8817 if (entry && (entry->AttributesMask & ENCHANT_PROC_ATTR_WHITE_HIT) && damageInfo.GetSpellInfo())
8818 continue;
8819
8820 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(pEnchant->EffectArg[s], DIFFICULTY_NONE);
8821 if (!spellInfo)
8822 {
8823 TC_LOG_ERROR("entities.player.items", "Player::CastItemCombatSpell: Player '{}' ({}) cast unknown spell (EnchantID: {}, SpellID: {}), ignoring",
8824 GetName(), GetGUID().ToString(), pEnchant->ID, pEnchant->EffectArg[s]);
8825 continue;
8826 }
8827
8828 float chance = pEnchant->EffectPointsMin[s] != 0 ? float(pEnchant->EffectPointsMin[s]) : GetWeaponProcChance();
8829
8830 if (entry)
8831 {
8832 if (entry->ProcsPerMinute)
8833 chance = GetPPMProcChance(proto->GetDelay(), entry->ProcsPerMinute, spellInfo);
8834 else if (entry->Chance)
8835 chance = (float)entry->Chance;
8836 }
8837
8838 // Apply spell mods
8839 ApplySpellMod(spellInfo, SpellModOp::ProcChance, chance);
8840
8841 // Shiv has 100% chance to apply the poison
8842 if (FindCurrentSpellBySpellId(5938) && e_slot == TEMP_ENCHANTMENT_SLOT)
8843 chance = 100.0f;
8844
8845 if (roll_chance_f(chance))
8846 {
8847 if (spellInfo->IsPositive())
8848 CastSpell(this, spellInfo->Id, item);
8849 else
8850 CastSpell(damageInfo.GetVictim(), spellInfo->Id, item);
8851 }
8852
8853 if (roll_chance_f(chance))
8854 {
8855 Unit* target = spellInfo->IsPositive() ? this : damageInfo.GetVictim();
8856
8857 CastSpellExtraArgs args(item);
8858 // reduce effect values if enchant is limited
8859 if (entry && (entry->AttributesMask & ENCHANT_PROC_ATTR_LIMIT_60) && target->GetLevelForTarget(this) > 60)
8860 {
8861 int32 const lvlDifference = target->GetLevelForTarget(this) - 60;
8862 int32 const lvlPenaltyFactor = 4; // 4% lost effectiveness per level
8863
8864 int32 const effectPct = std::max(0, 100 - (lvlDifference * lvlPenaltyFactor));
8865
8866 for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
8867 if (spellEffectInfo.IsEffect())
8868 args.AddSpellMod(static_cast<SpellValueMod>(SPELLVALUE_BASE_POINT0 + AsUnderlyingType(spellEffectInfo.EffectIndex)), CalculatePct(spellEffectInfo.CalcValue(this), effectPct));
8869 }
8870 CastSpell(target, spellInfo->Id, args);
8871 }
8872 }
8873 }
8874}
8875
8876void Player::CastItemUseSpell(Item* item, SpellCastTargets const& targets, ObjectGuid castCount, int32* misc)
8877{
8878 if (!item->GetTemplate()->HasFlag(ITEM_FLAG_LEGACY))
8879 {
8880 // item spells cast at use
8881 for (ItemEffectEntry const* effectData : item->GetEffects())
8882 {
8883 // wrong triggering type
8884 if (effectData->TriggerType != ITEM_SPELLTRIGGER_ON_USE)
8885 continue;
8886
8887 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(effectData->SpellID, DIFFICULTY_NONE);
8888 if (!spellInfo)
8889 {
8890 TC_LOG_ERROR("entities.player", "Player::CastItemUseSpell: Item (Entry: {}) has wrong spell id {}, ignoring", item->GetEntry(), effectData->SpellID);
8891 continue;
8892 }
8893
8894 Spell* spell = new Spell(this, spellInfo, TRIGGERED_NONE);
8895
8897 spellPrepare.ClientCastID = castCount;
8898 spellPrepare.ServerCastID = spell->m_castId;
8899 SendDirectMessage(spellPrepare.Write());
8900
8901 spell->m_fromClient = true;
8902 spell->m_CastItem = item;
8903 spell->m_misc.Raw.Data[0] = misc[0];
8904 spell->m_misc.Raw.Data[1] = misc[1];
8905 spell->prepare(targets);
8906 return;
8907 }
8908 }
8909
8910 // Item enchantments spells cast at use
8911 for (uint8 e_slot = 0; e_slot < MAX_ENCHANTMENT_SLOT; ++e_slot)
8912 {
8913 uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(e_slot));
8914 SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
8915 if (!pEnchant)
8916 continue;
8917 for (uint8 s = 0; s < MAX_ITEM_ENCHANTMENT_EFFECTS; ++s)
8918 {
8919 if (pEnchant->Effect[s] != ITEM_ENCHANTMENT_TYPE_USE_SPELL)
8920 continue;
8921
8922 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(pEnchant->EffectArg[s], DIFFICULTY_NONE);
8923 if (!spellInfo)
8924 {
8925 TC_LOG_ERROR("entities.player", "Player::CastItemUseSpell: Enchant {}, cast unknown spell {}", pEnchant->ID, pEnchant->EffectArg[s]);
8926 continue;
8927 }
8928
8929 Spell* spell = new Spell(this, spellInfo, TRIGGERED_NONE);
8930
8932 spellPrepare.ClientCastID = castCount;
8933 spellPrepare.ServerCastID = spell->m_castId;
8934 SendDirectMessage(spellPrepare.Write());
8935
8936 spell->m_fromClient = true;
8937 spell->m_CastItem = item;
8938 spell->m_misc.Raw.Data[0] = misc[0];
8939 spell->m_misc.Raw.Data[1] = misc[1];
8940 spell->prepare(targets);
8941 return;
8942 }
8943 }
8944}
8945
8947{
8948 if (item->GetTemplate()->HasFlag(ITEM_FLAG_LEGACY))
8949 return;
8950
8951 auto lootedEffectItr = std::find_if(item->GetEffects().begin(), item->GetEffects().end(), [](ItemEffectEntry const* effectData)
8952 {
8953 return effectData->TriggerType == ITEM_SPELLTRIGGER_ON_LOOTED;
8954 });
8955
8956 if (lootedEffectItr != item->GetEffects().end())
8957 {
8958 if (apply)
8959 CastSpell(this, (*lootedEffectItr)->SpellID, item);
8960 else
8961 RemoveAurasDueToItemSpell((*lootedEffectItr)->SpellID, item->GetGUID());
8962 }
8963}
8964
8966{
8967 if (itemTemplate->HasFlag(ITEM_FLAG_LEGACY))
8968 return;
8969
8970 for (ItemEffectEntry const* effect : itemTemplate->Effects)
8971 {
8973 continue;
8974
8975 CastSpell(this, effect->SpellID, true);
8976 }
8977}
8978
8980{
8981 TC_LOG_DEBUG("entities.player.items", "_RemoveAllItemMods start.");
8982
8983 for (uint8 i = 0; i < REAGENT_BAG_SLOT_END; ++i)
8984 {
8985 if (m_items[i])
8986 {
8987 ItemTemplate const* proto = m_items[i]->GetTemplate();
8988 if (!proto)
8989 continue;
8990
8991 // item set bonuses not dependent from item broken state
8992 if (proto->GetItemSet())
8993 RemoveItemsSetItem(this, m_items[i]);
8994
8995 if (m_items[i]->IsBroken() || !CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
8996 continue;
8997
8998 ApplyItemEquipSpell(m_items[i], false);
8999 ApplyEnchantment(m_items[i], false);
9000 ApplyArtifactPowers(m_items[i], false);
9001 }
9002 }
9003
9004 for (uint8 i = 0; i < REAGENT_BAG_SLOT_END; ++i)
9005 {
9006 if (m_items[i])
9007 {
9008 if (m_items[i]->IsBroken() || !CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
9009 continue;
9010
9012 _ApplyItemBonuses(m_items[i], i, false);
9013 }
9014 }
9015
9016 TC_LOG_DEBUG("entities.player.items", "_RemoveAllItemMods complete.");
9017}
9018
9020{
9021 TC_LOG_DEBUG("entities.player.items", "_ApplyAllItemMods start.");
9022
9023 for (uint8 i = 0; i < REAGENT_BAG_SLOT_END; ++i)
9024 {
9025 if (m_items[i])
9026 {
9027 if (m_items[i]->IsBroken() || !CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
9028 continue;
9029
9031 _ApplyItemBonuses(m_items[i], i, true);
9032
9033 WeaponAttackType const attackType = Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType());
9034 if (attackType != MAX_ATTACK)
9035 UpdateWeaponDependentAuras(attackType);
9036 }
9037 }
9038
9039 for (uint8 i = 0; i < REAGENT_BAG_SLOT_END; ++i)
9040 {
9041 if (m_items[i])
9042 {
9043 ItemTemplate const* proto = m_items[i]->GetTemplate();
9044 if (!proto)
9045 continue;
9046
9047 // item set bonuses not dependent from item broken state
9048 if (proto->GetItemSet())
9049 AddItemsSetItem(this, m_items[i]);
9050
9051 if (m_items[i]->IsBroken() || !CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
9052 continue;
9053
9054 ApplyItemEquipSpell(m_items[i], true);
9055 ApplyArtifactPowers(m_items[i], true);
9056 ApplyEnchantment(m_items[i], true);
9057 }
9058 }
9059
9060 TC_LOG_DEBUG("entities.player.items", "_ApplyAllItemMods complete.");
9061}
9062
9064{
9065 for (uint8 i = 0; i < REAGENT_BAG_SLOT_END; ++i)
9066 {
9067 if (m_items[i])
9068 {
9069 if (!CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
9070 continue;
9071
9073
9074 // Update item sets for heirlooms
9075 if (sDB2Manager.GetHeirloomByItemId(m_items[i]->GetEntry()) && m_items[i]->GetTemplate()->GetItemSet())
9076 {
9077 if (apply)
9078 AddItemsSetItem(this, m_items[i]);
9079 else
9080 RemoveItemsSetItem(this, m_items[i]);
9081 }
9082 }
9083 }
9084}
9085
9087{
9088 for (uint8 i = 0; i < REAGENT_BAG_SLOT_END; ++i)
9089 {
9090 if (m_items[i])
9091 {
9092 if (!m_items[i]->IsAzeriteItem() || m_items[i]->IsBroken() || !CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
9093 continue;
9094
9096 }
9097 }
9098}
9099
9101{
9102 for (uint8 i = 0; i < REAGENT_BAG_SLOT_END; ++i)
9103 {
9104 if (m_items[i])
9105 {
9106 if (!m_items[i]->IsAzeriteEmpoweredItem() || m_items[i]->IsBroken() || !CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
9107 continue;
9108
9110 }
9111 }
9112}
9113
9114Loot* Player::GetLootByWorldObjectGUID(ObjectGuid const& lootWorldObjectGuid) const
9115{
9116 auto itr = std::find_if(m_AELootView.begin(), m_AELootView.end(), [&lootWorldObjectGuid](std::pair<ObjectGuid const, Loot*> const& lootView)
9117 {
9118 return lootView.second->GetOwnerGUID() == lootWorldObjectGuid;
9119 });
9120 return itr != m_AELootView.end() ? itr->second : nullptr;
9121}
9122
9123LootRoll* Player::GetLootRoll(ObjectGuid const& lootObjectGuid, uint8 lootListId)
9124{
9125 auto itr = std::find_if(m_lootRolls.begin(), m_lootRolls.end(), [&](LootRoll const* roll)
9126 {
9127 return roll->IsLootItem(lootObjectGuid, lootListId);
9128 });
9129 return itr != m_lootRolls.end() ? *itr : nullptr;
9130}
9131
9133{
9134 m_lootRolls.erase(std::remove(m_lootRolls.begin(), m_lootRolls.end(), roll), m_lootRolls.end());
9135}
9136
9137/* If in a battleground a player dies, and an enemy removes the insignia, the player's bones is lootable
9138 Called by remove insignia spell effect */
9140{
9141 // If player is not in battleground and not in worldpvpzone
9143 return;
9144
9145 // If not released spirit, do it !
9146 if (m_deathTimer > 0)
9147 {
9148 m_deathTimer = 0;
9151 }
9152
9153 _corpseLocation.WorldRelocate(MAPID_INVALID, 0.0f, 0.0f, 0.0f, 0.0f);
9154
9155 // We have to convert player corpse to bones, not to be able to resurrect there
9156 // SpawnCorpseBones isn't handy, 'cos it saves player while he in BG
9157 Corpse* bones = GetMap()->ConvertCorpseToBones(GetGUID(), true);
9158 if (!bones)
9159 return;
9160
9161 // Now we must make bones lootable, and send player loot
9163
9164 bones->m_loot.reset(new Loot(GetMap(), bones->GetGUID(), LOOT_INSIGNIA, looterPlr->GetGroup()));
9165
9166 // For AV Achievement
9167 if (Battleground* bg = GetBattleground())
9168 {
9169 if (bg->GetTypeID() == BATTLEGROUND_AV)
9170 bones->m_loot->FillLoot(PLAYER_CORPSE_LOOT_ENTRY, LootTemplates_Creature, this, true);
9171 }
9172 // For wintergrasp Quests
9173 else if (GetZoneId() == AREA_WINTERGRASP)
9174 bones->m_loot->FillLoot(PLAYER_CORPSE_LOOT_ENTRY, LootTemplates_Creature, this, true);
9175
9176 // It may need a better formula
9177 // Now it works like this: lvl10: ~6copper, lvl70: ~9silver
9178 bones->m_loot->gold = uint32(urand(50, 150) * 0.016f * std::pow(float(GetLevel()) / 5.76f, 2.5f) * sWorld->getRate(RATE_DROP_MONEY));
9179 bones->lootRecipient = looterPlr;
9180 looterPlr->SendLoot(*bones->m_loot);
9181}
9182
9184{
9186 packet.LootObj = guid;
9187 packet.Owner = GetGUID();
9188 SendDirectMessage(packet.Write());
9189}
9190
9192{
9194}
9195
9196void Player::SendLoot(Loot& loot, bool aeLooting)
9197{
9198 if (!GetLootGUID().IsEmpty() && !aeLooting)
9200
9201 TC_LOG_DEBUG("loot", "Player::SendLoot: Player: '{}' ({}), Loot: {}",
9202 GetName(), GetGUID().ToString(), loot.GetOwnerGUID().ToString());
9203
9204 if (!loot.GetOwnerGUID().IsItem() && !aeLooting)
9205 SetLootGUID(loot.GetOwnerGUID());
9206
9208 packet.Owner = loot.GetOwnerGUID();
9209 packet.LootObj = loot.GetGUID();
9210 packet._LootMethod = loot.GetLootMethod();
9212 packet.Acquired = true; // false == No Loot (this too^^)
9213 packet.AELooting = aeLooting;
9214 loot.BuildLootResponse(packet, this);
9215 SendDirectMessage(packet.Write());
9216
9217 // add 'this' player as one of the players that are looting 'loot'
9218 loot.OnLootOpened(GetMap(), this);
9219 m_AELootView[loot.GetGUID()] = &loot;
9220
9221 if (loot.loot_type == LOOT_CORPSE && !loot.GetOwnerGUID().IsItem())
9223}
9224
9225void Player::SendLootError(ObjectGuid const& lootObj, ObjectGuid const& owner, LootError error) const
9226{
9228 lootResponse.LootObj = lootObj;
9229 lootResponse.Owner = owner;
9230 lootResponse.Acquired = false;
9231 lootResponse.FailureReason = error;
9232 SendDirectMessage(lootResponse.Write());
9233}
9234
9236{
9238 packet.LootObj = lootObj;
9239 SendDirectMessage(packet.Write());
9240}
9241
9242void Player::SendNotifyLootItemRemoved(ObjectGuid lootObj, ObjectGuid owner, uint8 lootListId) const
9243{
9245 packet.LootObj = lootObj;
9246 packet.Owner = owner;
9247 packet.LootListID = lootListId;
9248 SendDirectMessage(packet.Write());
9249}
9250
9251void Player::SendUpdateWorldState(uint32 variable, uint32 value, bool hidden /*= false*/) const
9252{
9254 worldstate.VariableID = variable;
9255 worldstate.Value = value;
9256 worldstate.Hidden = hidden;
9257 SendDirectMessage(worldstate.Write());
9258}
9259
9260// TODO - InitWorldStates should NOT always send the same states
9261// Some should keep the same value between different zoneIds and areaIds on the same map
9263{
9264 uint32 mapId = GetMapId();
9265
9266 TC_LOG_DEBUG("network", "Player::SendInitWorldStates: Sending SMSG_INIT_WORLD_STATES for Map: {}, Zone: {}", mapId, zoneId);
9267
9269 packet.MapID = mapId;
9270 packet.AreaID = zoneId;
9271 packet.SubareaID = areaId;
9272
9273 sWorldStateMgr->FillInitialWorldStates(packet, GetMap(), areaId);
9274
9275 SendDirectMessage(packet.Write());
9276}
9277
9279{
9281 npcInteraction.Npc = guid;
9283 npcInteraction.Success = true;
9284 SendDirectMessage(npcInteraction.Write());
9285}
9286
9287void Player::SendRespecWipeConfirm(ObjectGuid const& guid, uint32 cost, SpecResetType respecType) const
9288{
9290 respecWipeConfirm.RespecMaster = guid;
9291 respecWipeConfirm.Cost = cost;
9292 respecWipeConfirm.RespecType = respecType;
9293 SendDirectMessage(respecWipeConfirm.Write());
9294}
9295
9296/*********************************************************/
9297/*** STORAGE SYSTEM ***/
9298/*********************************************************/
9299
9300uint8 Player::FindEquipSlot(Item const* item, uint32 slot, bool swap) const
9301{
9302 std::array<uint8, 4> slots = { NULL_SLOT, NULL_SLOT, NULL_SLOT, NULL_SLOT };
9303 switch (item->GetTemplate()->GetInventoryType())
9304 {
9305 case INVTYPE_HEAD:
9306 slots[0] = EQUIPMENT_SLOT_HEAD;
9307 break;
9308 case INVTYPE_NECK:
9309 slots[0] = EQUIPMENT_SLOT_NECK;
9310 break;
9311 case INVTYPE_SHOULDERS:
9312 slots[0] = EQUIPMENT_SLOT_SHOULDERS;
9313 break;
9314 case INVTYPE_BODY:
9315 slots[0] = EQUIPMENT_SLOT_BODY;
9316 break;
9317 case INVTYPE_CHEST:
9318 slots[0] = EQUIPMENT_SLOT_CHEST;
9319 break;
9320 case INVTYPE_ROBE:
9321 slots[0] = EQUIPMENT_SLOT_CHEST;
9322 break;
9323 case INVTYPE_WAIST:
9324 slots[0] = EQUIPMENT_SLOT_WAIST;
9325 break;
9326 case INVTYPE_LEGS:
9327 slots[0] = EQUIPMENT_SLOT_LEGS;
9328 break;
9329 case INVTYPE_FEET:
9330 slots[0] = EQUIPMENT_SLOT_FEET;
9331 break;
9332 case INVTYPE_WRISTS:
9333 slots[0] = EQUIPMENT_SLOT_WRISTS;
9334 break;
9335 case INVTYPE_HANDS:
9336 slots[0] = EQUIPMENT_SLOT_HANDS;
9337 break;
9338 case INVTYPE_FINGER:
9339 slots[0] = EQUIPMENT_SLOT_FINGER1;
9340 slots[1] = EQUIPMENT_SLOT_FINGER2;
9341 break;
9342 case INVTYPE_TRINKET:
9343 slots[0] = EQUIPMENT_SLOT_TRINKET1;
9344 slots[1] = EQUIPMENT_SLOT_TRINKET2;
9345 break;
9346 case INVTYPE_CLOAK:
9347 slots[0] = EQUIPMENT_SLOT_BACK;
9348 break;
9349 case INVTYPE_WEAPON:
9350 {
9351 slots[0] = EQUIPMENT_SLOT_MAINHAND;
9352
9353 // suggest offhand slot only if know dual wielding
9354 // (this will be replace mainhand weapon at auto equip instead unwonted "you don't known dual wielding" ...
9355 if (CanDualWield())
9356 slots[1] = EQUIPMENT_SLOT_OFFHAND;
9357 break;
9358 }
9359 case INVTYPE_SHIELD:
9360 slots[0] = EQUIPMENT_SLOT_OFFHAND;
9361 break;
9362 case INVTYPE_RANGED:
9363 slots[0] = EQUIPMENT_SLOT_MAINHAND;
9364 break;
9365 case INVTYPE_2HWEAPON:
9366 slots[0] = EQUIPMENT_SLOT_MAINHAND;
9367 if (CanDualWield() && CanTitanGrip())
9368 slots[1] = EQUIPMENT_SLOT_OFFHAND;
9369 break;
9370 case INVTYPE_TABARD:
9371 slots[0] = EQUIPMENT_SLOT_TABARD;
9372 break;
9374 slots[0] = EQUIPMENT_SLOT_MAINHAND;
9375 break;
9377 slots[0] = EQUIPMENT_SLOT_OFFHAND;
9378 break;
9379 case INVTYPE_HOLDABLE:
9380 slots[0] = EQUIPMENT_SLOT_OFFHAND;
9381 break;
9383 slots[0] = EQUIPMENT_SLOT_MAINHAND;
9384 break;
9385 case INVTYPE_BAG:
9388 else
9389 slots[0] = REAGENT_BAG_SLOT_START;
9390 break;
9393 {
9394 bool isProfessionTool = item->GetTemplate()->GetInventoryType() == INVTYPE_PROFESSION_TOOL;
9395
9396 // Validate item class
9397 if (item->GetTemplate()->GetClass() != ITEM_CLASS_PROFESSION)
9398 return NULL_SLOT;
9399
9400 // Check if player has profession skill
9401 uint32 itemSkill = item->GetTemplate()->GetSkill();
9402 if (!HasSkill(itemSkill))
9403 return NULL_SLOT;
9404
9405 switch (item->GetTemplate()->GetSubClass())
9406 {
9408 slots[0] = isProfessionTool ? PROFESSION_SLOT_COOKING_TOOL : PROFESSION_SLOT_COOKING_GEAR1;
9409 break;
9411 {
9412 // Fishing doesn't make use of gear slots (clientside)
9413 if (!isProfessionTool)
9414 return NULL_SLOT;
9415
9417 break;
9418 }
9430 {
9431 int32 professionSlot = GetProfessionSlotFor(itemSkill);
9432 if (professionSlot == -1)
9433 return NULL_SLOT;
9434
9435 if (isProfessionTool)
9436 slots[0] = PROFESSION_SLOT_PROFESSION1_TOOL + professionSlot * PROFESSION_SLOT_MAX_COUNT;
9437 else
9438 {
9441 }
9442
9443 break;
9444 }
9445 default:
9446 return NULL_SLOT;
9447 }
9448 break;
9449 }
9450 default:
9451 return NULL_SLOT;
9452 }
9453
9454 if (slot != NULL_SLOT)
9455 {
9456 if (swap || !GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
9457 for (uint8 i = 0; i < 4; ++i)
9458 if (slots[i] == slot)
9459 return slot;
9460 }
9461 else
9462 {
9463 // search free slot at first
9464 for (uint8 i = 0; i < 4; ++i)
9465 if (slots[i] != NULL_SLOT && !GetItemByPos(INVENTORY_SLOT_BAG_0, slots[i]))
9466 // in case 2hand equipped weapon (without titan grip) offhand slot empty but not free
9467 if (slots[i] != EQUIPMENT_SLOT_OFFHAND || !IsTwoHandUsed())
9468 return slots[i];
9469
9470 // if not found free and can swap return slot with lower item level equipped
9471 if (swap)
9472 {
9473 uint32 minItemLevel = std::numeric_limits<uint32>::max();
9474 uint8 minItemLevelIndex = 0;
9475 for (uint8 i = 0; i < 4; ++i)
9476 {
9477 if (slots[i] != NULL_SLOT)
9478 {
9479 if (Item const* equipped = GetItemByPos(INVENTORY_SLOT_BAG_0, slots[i]))
9480 {
9481 uint32 itemLevel = equipped->GetItemLevel(this);
9482 if (itemLevel < minItemLevel)
9483 {
9484 minItemLevel = itemLevel;
9485 minItemLevelIndex = i;
9486 }
9487 }
9488 }
9489 }
9490
9491 return slots[minItemLevelIndex];
9492 }
9493 }
9494
9495 // no free position
9496 return NULL_SLOT;
9497}
9498
9499uint32 Player::GetFreeInventorySlotCount(EnumFlag<ItemSearchLocation> location /*= ItemSearchLocation::Inventory*/) const
9500{
9501 uint32 freeSlotCount = 0;
9502
9504 {
9507 ++freeSlotCount;
9508
9511 ++freeSlotCount;
9512 }
9513
9515 {
9517 for (uint8 i = INVENTORY_SLOT_BAG_START; i < inventoryEnd; ++i)
9519 ++freeSlotCount;
9520
9522 if (Bag* bag = GetBagByPos(i))
9523 for (uint32 j = 0; j < GetBagSize(bag); ++j)
9524 if (!GetItemInBag(bag, j))
9525 ++freeSlotCount;
9526 }
9527
9528 if (location.HasFlag(ItemSearchLocation::Bank))
9529 {
9530 for (uint8 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_BAG_END; ++i)
9532 ++freeSlotCount;
9533
9534 for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
9535 if (Bag* bag = GetBagByPos(i))
9536 for (uint32 j = 0; j < GetBagSize(bag); ++j)
9537 if (!GetItemInBag(bag, j))
9538 ++freeSlotCount;
9539 }
9540
9542 {
9544 if (Bag* bag = GetBagByPos(i))
9545 for (uint32 j = 0; j < GetBagSize(bag); ++j)
9546 if (!GetItemInBag(bag, j))
9547 ++freeSlotCount;
9548
9549 for (uint8 i = REAGENT_SLOT_START; i < REAGENT_SLOT_END; ++i)
9551 ++freeSlotCount;
9552 }
9553
9554 return freeSlotCount;
9555}
9556
9558{
9560 uint32 tempcount = 0;
9561 bool result = ForEachItem(ItemSearchLocation::Equipment, [this, item, &res, &tempcount, count](Item* pItem)
9562 {
9563 if (pItem->GetEntry() == item)
9564 {
9565 InventoryResult ires = CanUnequipItem(pItem->GetPos(), false);
9566 if (ires == EQUIP_ERR_OK)
9567 {
9568 tempcount += pItem->GetCount();
9569 if (tempcount >= count)
9570 return ItemSearchCallbackResult::Stop;
9571 }
9572 else
9573 res = ires;
9574 }
9576 });
9577
9578 if (!result) // we stopped early due to a sucess
9579 return EQUIP_ERR_OK;
9580
9581 return res; // return latest error if any
9582}
9583
9584uint32 Player::GetItemCount(uint32 item, bool inBankAlso, Item* skipItem) const
9585{
9586 bool countGems = skipItem && skipItem->GetTemplate()->GetGemProperties();
9587
9589 if (inBankAlso)
9590 location |= ItemSearchLocation::Bank;
9591
9592 uint32 count = 0;
9593 ForEachItem(location, [&count, item, skipItem, countGems](Item* pItem)
9594 {
9595 if (pItem != skipItem)
9596 {
9597 if (pItem->GetEntry() == item)
9598 count += pItem->GetCount();
9599
9600 if (countGems)
9601 count += pItem->GetGemCountWithID(item);
9602 }
9603
9605 });
9606
9607 return count;
9608}
9609
9611{
9612 uint32 count = 0;
9613 ForEachItem(ItemSearchLocation::Everywhere, [&count, limitCategory, skipItem](Item* item)
9614 {
9615 if (item != skipItem)
9616 if (ItemTemplate const* pProto = item->GetTemplate())
9617 if (pProto->GetItemLimitCategory() == limitCategory)
9618 count += item->GetCount();
9619
9621 });
9622 return count;
9623}
9624
9626{
9627 std::vector<Item*> itemList = std::vector<Item*>();
9629 {
9630 if (item->GetTemplate()->IsCraftingReagent())
9631 itemList.push_back(item);
9632
9634 });
9635
9636 return itemList;
9637}
9638
9640{
9641 Item* result = nullptr;
9642 ForEachItem(ItemSearchLocation::Everywhere, [&result, guid](Item* item)
9643 {
9644 if (item->GetGUID() == guid)
9645 {
9646 result = item;
9647 return ItemSearchCallbackResult::Stop;
9648 }
9649
9651 });
9652 return result;
9653}
9654
9656{
9657 uint8 bag = pos >> 8;
9658 uint8 slot = pos & 255;
9659 return GetItemByPos(bag, slot);
9660}
9661
9663{
9664 if (bag == INVENTORY_SLOT_BAG_0 && slot < PLAYER_SLOT_END && (slot < BUYBACK_SLOT_START || slot >= BUYBACK_SLOT_END))
9665 return m_items[slot];
9666 if (Bag* pBag = GetBagByPos(bag))
9667 return pBag->GetItemByPos(slot);
9668 return nullptr;
9669}
9670
9671//Does additional check for disarmed weapons
9673{
9674 Item* item = GetItemByPos(bag, slot);
9675 if (!item)
9676 return nullptr;
9677
9679 return nullptr;
9680
9681 return item;
9682}
9683
9685{
9687 || (bag >= BANK_SLOT_BAG_START && bag < BANK_SLOT_BAG_END)
9688 || (bag >= REAGENT_BAG_SLOT_START && bag < REAGENT_BAG_SLOT_END))
9689 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, bag))
9690 return item->ToBag();
9691 return nullptr;
9692}
9693
9694Item* Player::GetWeaponForAttack(WeaponAttackType attackType, bool useable /*= false*/) const
9695{
9696 uint8 slot;
9697 switch (attackType)
9698 {
9699 case BASE_ATTACK: slot = EQUIPMENT_SLOT_MAINHAND; break;
9700 case OFF_ATTACK: slot = EQUIPMENT_SLOT_OFFHAND; break;
9701 case RANGED_ATTACK: slot = EQUIPMENT_SLOT_MAINHAND; break;
9702 default: return nullptr;
9703 }
9704
9705 Item* item;
9706 if (useable)
9708 else
9709 item = GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
9710
9711 if (!item || item->GetTemplate()->GetClass() != ITEM_CLASS_WEAPON)
9712 return nullptr;
9713
9714 if ((attackType == RANGED_ATTACK) != item->GetTemplate()->IsRangedWeapon())
9715 return nullptr;
9716
9717 if (!useable)
9718 return item;
9719
9720 if (item->IsBroken())
9721 return nullptr;
9722
9723 return item;
9724}
9725
9726Item* Player::GetShield(bool useable) const
9727{
9728 Item* item;
9729 if (useable)
9731 else
9733
9734 if (!item || item->GetTemplate()->GetClass() != ITEM_CLASS_ARMOR)
9735 return nullptr;
9736
9737 if (!useable)
9738 return item;
9739
9740 if (item->IsBroken())
9741 return nullptr;
9742
9743 return item;
9744}
9745
9747{
9748 Item* result = nullptr;
9750 {
9751 if (item->GetGUID() == guid)
9752 {
9753 result = item;
9754 return ItemSearchCallbackResult::Stop;
9755 }
9757 });
9758 return result;
9759}
9760
9762{
9763 switch (slot)
9764 {
9765 case EQUIPMENT_SLOT_MAINHAND: return inventoryType != INVTYPE_RANGED && inventoryType != INVTYPE_RANGEDRIGHT ? BASE_ATTACK : RANGED_ATTACK;
9767 default: return MAX_ATTACK;
9768 }
9769}
9770
9772{
9773 if (bag == INVENTORY_SLOT_BAG_0 && slot == NULL_SLOT)
9774 return true;
9776 return true;
9778 return true;
9779 if (bag >= REAGENT_BAG_SLOT_START && bag < REAGENT_BAG_SLOT_END)
9780 return true;
9782 return true;
9783 return false;
9784}
9785
9787{
9788 if (bag == INVENTORY_SLOT_BAG_0 && (slot < EQUIPMENT_SLOT_END))
9789 return true;
9790 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= PROFESSION_SLOT_START && slot < PROFESSION_SLOT_END))
9791 return true;
9793 return true;
9794 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= REAGENT_BAG_SLOT_START && slot < REAGENT_BAG_SLOT_END))
9795 return true;
9796 return false;
9797}
9798
9800{
9801 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= BANK_SLOT_ITEM_START && slot < BANK_SLOT_ITEM_END))
9802 return true;
9803 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END))
9804 return true;
9805 if (bag >= BANK_SLOT_BAG_START && bag < BANK_SLOT_BAG_END)
9806 return true;
9807 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= REAGENT_SLOT_START && slot < REAGENT_SLOT_END))
9808 return true;
9809 return false;
9810}
9811
9813{
9814 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= REAGENT_SLOT_START && slot < REAGENT_SLOT_END))
9815 return true;
9816 return false;
9817}
9818
9820{
9821 uint8 bag = pos >> 8;
9822 uint8 slot = pos & 255;
9824 return true;
9825 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END))
9826 return true;
9827 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= REAGENT_BAG_SLOT_START && slot < REAGENT_BAG_SLOT_END))
9828 return true;
9829 return false;
9830}
9831
9833{
9835}
9836
9837bool Player::IsValidPos(uint8 bag, uint8 slot, bool explicit_pos) const
9838{
9839 // post selected
9840 if (bag == NULL_BAG && !explicit_pos)
9841 return true;
9842
9843 if (bag == INVENTORY_SLOT_BAG_0)
9844 {
9845 // any post selected
9846 if (slot == NULL_SLOT && !explicit_pos)
9847 return true;
9848
9849 // equipment
9850 if (slot < EQUIPMENT_SLOT_END)
9851 return true;
9852
9853 // profession equipment
9854 if (slot >= PROFESSION_SLOT_START && slot < PROFESSION_SLOT_END)
9855 return true;
9856
9857 // bag equip slots
9859 return true;
9860
9861 // reagent bag equip slots
9862 if (slot >= REAGENT_BAG_SLOT_START && slot < REAGENT_BAG_SLOT_END)
9863 return true;
9864
9865 // backpack slots
9867 return true;
9868
9869 // bank main slots
9870 if (slot >= BANK_SLOT_ITEM_START && slot < BANK_SLOT_ITEM_END)
9871 return true;
9872
9873 // bank bag slots
9874 if (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END)
9875 return true;
9876
9877 // reagent bank bag slots
9878 if (slot >= REAGENT_SLOT_START && slot < REAGENT_SLOT_END)
9879 return true;
9880
9881 return false;
9882 }
9883
9884 // bag content slots
9885 // bank bag content slots
9886 if (Bag* pBag = GetBagByPos(bag))
9887 {
9888 // any post selected
9889 if (slot == NULL_SLOT && !explicit_pos)
9890 return true;
9891
9892 return slot < pBag->GetBagSize();
9893 }
9894
9895 // where this?
9896 return false;
9897}
9898
9900{
9902
9903 if (slots < GetInventorySlotCount())
9904 {
9905 std::vector<Item*> unstorableItems;
9906
9907 for (uint8 slot = INVENTORY_SLOT_ITEM_START + slots; slot < INVENTORY_SLOT_ITEM_END; ++slot)
9908 if (Item* unstorableItem = GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
9909 unstorableItems.push_back(unstorableItem);
9910
9911 if (!unstorableItems.empty())
9912 {
9913 std::size_t fullBatches = unstorableItems.size() / MAX_MAIL_ITEMS;
9914 std::size_t remainder = unstorableItems.size() % MAX_MAIL_ITEMS;
9915 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
9916
9917 auto sendItemsBatch = [this, &trans, &unstorableItems](std::size_t batchNumber, std::size_t batchSize)
9918 {
9919 MailDraft draft(GetSession()->GetTrinityString(LANG_NOT_EQUIPPED_ITEM), "There were problems with equipping item(s).");
9920 for (std::size_t j = 0; j < batchSize; ++j)
9921 draft.AddItem(unstorableItems[batchNumber * MAX_MAIL_ITEMS + j]);
9922
9924 };
9925
9926 for (std::size_t batch = 0; batch < fullBatches; ++batch)
9927 sendItemsBatch(batch, MAX_MAIL_ITEMS);
9928
9929 if (remainder)
9930 sendItemsBatch(fullBatches, remainder);
9931
9932 CharacterDatabase.CommitTransaction(trans);
9933
9935 }
9936 }
9937
9939}
9940
9941bool Player::HasItemCount(uint32 item, uint32 count, bool inBankAlso) const
9942{
9944 if (inBankAlso)
9945 location |= ItemSearchLocation::Bank;
9946
9947 uint32 currentCount = 0;
9948 return !ForEachItem(location, [item, count, &currentCount](Item* pItem)
9949 {
9950 if (pItem->GetEntry() == item && !pItem->IsInTrade())
9951 {
9952 currentCount += pItem->GetCount();
9953 if (currentCount >= count)
9954 return ItemSearchCallbackResult::Stop;
9955 }
9957 });
9958}
9959
9960bool Player::HasItemOrGemWithIdEquipped(uint32 item, uint32 count, uint8 except_slot) const
9961{
9962 uint32 tempcount = 0;
9963
9964 ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(item);
9965 bool includeGems = pProto && pProto->GetGemProperties();
9966 return !ForEachItem(ItemSearchLocation::Equipment, [item, &tempcount, count, except_slot, includeGems](Item* pItem)
9967 {
9968 if (pItem->GetSlot() != except_slot)
9969 {
9970 if (pItem->GetEntry() == item)
9971 tempcount += pItem->GetCount();
9972
9973 if (includeGems)
9974 tempcount += pItem->GetGemCountWithID(item);
9975
9976 if (tempcount >= count)
9977 return ItemSearchCallbackResult::Stop;
9978 }
9979
9981 });
9982}
9983
9984bool Player::HasItemWithLimitCategoryEquipped(uint32 limitCategory, uint32 count, uint8 except_slot) const
9985{
9986 uint32 tempcount = 0;
9987 return !ForEachItem(ItemSearchLocation::Equipment, [&tempcount, limitCategory, count, except_slot](Item* pItem)
9988 {
9989 if (pItem->GetSlot() == except_slot)
9991
9992 if (pItem->GetTemplate()->GetItemLimitCategory() != limitCategory)
9994
9995 tempcount += pItem->GetCount();
9996 if (tempcount >= count)
9998
10000 });
10001}
10002
10003bool Player::HasGemWithLimitCategoryEquipped(uint32 limitCategory, uint32 count, uint8 except_slot) const
10004{
10005 uint32 tempcount = 0;
10006 return !ForEachItem(ItemSearchLocation::Equipment, [&tempcount, limitCategory, count, except_slot](Item* pItem)
10007 {
10008 if (pItem->GetSlot() == except_slot)
10010
10011 ItemTemplate const* pProto = pItem->GetTemplate();
10012 if (!pProto)
10014
10015 tempcount += pItem->GetGemCountWithLimitCategory(limitCategory);
10016 if (tempcount >= count)
10018
10020 });
10021}
10022
10023InventoryResult Player::CanTakeMoreSimilarItems(uint32 entry, uint32 count, Item* pItem, uint32* no_space_count /*= nullptr*/, uint32* offendingItemId /*= nullptr*/) const
10024{
10025 ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(entry);
10026 if (!pProto)
10027 {
10028 if (no_space_count)
10029 *no_space_count = count;
10031 }
10032
10033 if (pItem && pItem->m_lootGenerated)
10034 return EQUIP_ERR_LOOT_GONE;
10035
10036 // no maximum
10037 if ((pProto->GetMaxCount() <= 0 && pProto->GetItemLimitCategory() == 0) || pProto->GetMaxCount() == 2147483647)
10038 return EQUIP_ERR_OK;
10039
10040 if (pProto->GetMaxCount() > 0)
10041 {
10042 uint32 curcount = GetItemCount(pProto->GetId(), true, pItem);
10043 if (curcount + count > uint32(pProto->GetMaxCount()))
10044 {
10045 if (no_space_count)
10046 *no_space_count = count + curcount - pProto->GetMaxCount();
10048 }
10049 }
10050
10051 // check unique-equipped limit
10052 if (pProto->GetItemLimitCategory())
10053 {
10054 ItemLimitCategoryEntry const* limitEntry = sItemLimitCategoryStore.LookupEntry(pProto->GetItemLimitCategory());
10055 if (!limitEntry)
10056 {
10057 if (no_space_count)
10058 *no_space_count = count;
10060 }
10061
10062 if (limitEntry->Flags == ITEM_LIMIT_CATEGORY_MODE_HAVE)
10063 {
10064 uint8 limitQuantity = GetItemLimitCategoryQuantity(limitEntry);
10065 uint32 curcount = GetItemCountWithLimitCategory(pProto->GetItemLimitCategory(), pItem);
10066 if (curcount + count > uint32(limitQuantity))
10067 {
10068 if (no_space_count)
10069 *no_space_count = count + curcount - limitQuantity;
10070 if (offendingItemId)
10071 *offendingItemId = pProto->GetId();
10073 }
10074 }
10075 }
10076
10077 return EQUIP_ERR_OK;
10078}
10079
10080InventoryResult Player::CanTakeMoreSimilarItems(Item* pItem, uint32* offendingItemId /*= nullptr*/) const
10081{
10082 return CanTakeMoreSimilarItems(pItem->GetEntry(), pItem->GetCount(), pItem, nullptr, offendingItemId);
10083}
10084
10085InventoryResult Player::CanStoreNewItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, uint32 item, uint32 count, uint32* no_space_count /*= nullptr*/) const
10086{
10087 return CanStoreItem(bag, slot, dest, item, count, nullptr, false, no_space_count);
10088}
10089
10090InventoryResult Player::CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap /*= false*/) const
10091{
10092 if (!pItem)
10094 uint32 count = pItem->GetCount();
10095 return CanStoreItem(bag, slot, dest, pItem->GetEntry(), count, pItem, swap, nullptr);
10096}
10097
10099{
10100 for (AuraEffect const* providedTotemCategory : GetAuraEffectsByType(SPELL_AURA_PROVIDE_TOTEM_CATEGORY))
10101 if (DB2Manager::IsTotemCategoryCompatibleWith(providedTotemCategory->GetMiscValueB(), TotemCategory))
10102 return true;
10103
10104 Item* item;
10106 for (uint8 i = EQUIPMENT_SLOT_START; i < inventoryEnd; ++i)
10107 {
10110 return true;
10111 }
10112
10113 Bag* bag;
10115 {
10116 bag = GetBagByPos(i);
10117 if (bag)
10118 {
10119 for (uint32 j = 0; j < bag->GetBagSize(); ++j)
10120 {
10121 item = GetUseableItemByPos(i, j);
10123 return true;
10124 }
10125 }
10126 }
10127
10129 {
10132 return true;
10133 }
10134
10135 return false;
10136}
10137
10138InventoryResult Player::CanStoreItem_InSpecificSlot(uint8 bag, uint8 slot, ItemPosCountVec &dest, ItemTemplate const* pProto, uint32& count, bool swap, Item* pSrcItem) const
10139{
10140 Item* pItem2 = GetItemByPos(bag, slot);
10141
10142 // ignore move item (this slot will be empty at move)
10143 if (pItem2 == pSrcItem)
10144 pItem2 = nullptr;
10145
10146 uint32 need_space;
10147
10148 if (pSrcItem)
10149 {
10150 if (pSrcItem->IsNotEmptyBag() && !IsBagPos(uint16(bag) << 8 | slot))
10152
10153 if (pSrcItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD) && !IsEquipmentPos(bag, slot) && !IsChildEquipmentPos(bag, slot))
10155
10156 if (!pSrcItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD) && IsChildEquipmentPos(bag, slot))
10158 }
10159
10160 // empty specific slot - check item fit to slot
10161 if (!pItem2 || swap)
10162 {
10163 if (bag == INVENTORY_SLOT_BAG_0)
10164 {
10165 // prevent cheating
10166 if ((slot >= BUYBACK_SLOT_START && slot < BUYBACK_SLOT_END) || slot >= PLAYER_SLOT_END)
10168
10169 // can't store anything else than crafting reagents in Reagent Bank
10170 if (IsReagentBankPos(bag, slot) && (!IsReagentBankUnlocked() || !pProto->IsCraftingReagent()))
10172 }
10173 else
10174 {
10175 Bag* pBag = GetBagByPos(bag);
10176 if (!pBag)
10178
10179 ItemTemplate const* pBagProto = pBag->GetTemplate();
10180 if (!pBagProto)
10182
10183 if (slot >= pBagProto->GetContainerSlots())
10185
10186 if (!ItemCanGoIntoBag(pProto, pBagProto))
10188 }
10189
10190 // non empty stack with space
10191 need_space = pProto->GetMaxStackSize();
10192 }
10193 // non empty slot, check item type
10194 else
10195 {
10196 // can be merged at least partly
10197 InventoryResult res = pItem2->CanBeMergedPartlyWith(pProto);
10198 if (res != EQUIP_ERR_OK)
10199 return res;
10200
10201 // free stack space or infinity
10202 need_space = pProto->GetMaxStackSize() - pItem2->GetCount();
10203 }
10204
10205 if (need_space > count)
10206 need_space = count;
10207
10208 ItemPosCount newPosition = ItemPosCount((bag << 8) | slot, need_space);
10209 if (!newPosition.isContainedIn(dest))
10210 {
10211 dest.push_back(newPosition);
10212 count -= need_space;
10213 }
10214 return EQUIP_ERR_OK;
10215}
10216
10217InventoryResult Player::CanStoreItem_InBag(uint8 bag, ItemPosCountVec &dest, ItemTemplate const* pProto, uint32& count, bool merge, bool non_specialized, Item* pSrcItem, uint8 skip_bag, uint8 skip_slot) const
10218{
10219 // skip specific bag already processed in first called CanStoreItem_InBag
10220 if (bag == skip_bag)
10222
10223 // skip non-existing bag or self targeted bag
10224 Bag* pBag = GetBagByPos(bag);
10225 if (!pBag || pBag == pSrcItem)
10227
10228 if (pSrcItem)
10229 {
10230 if (pSrcItem->IsNotEmptyBag())
10232
10233 if (pSrcItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD))
10235 }
10236
10237 ItemTemplate const* pBagProto = pBag->GetTemplate();
10238 if (!pBagProto)
10240
10241 // specialized bag mode or non-specialized
10242 if (non_specialized != (pBagProto->GetClass() == ITEM_CLASS_CONTAINER && (pBagProto->GetSubClass() == ITEM_SUBCLASS_CONTAINER || pBagProto->GetSubClass() == ITEM_SUBCLASS_REAGENT_CONTAINER)))
10244
10245 if (!ItemCanGoIntoBag(pProto, pBagProto))
10247
10248 for (uint32 j = 0; j < pBag->GetBagSize(); j++)
10249 {
10250 // skip specific slot already processed in first called CanStoreItem_InSpecificSlot
10251 if (j == skip_slot)
10252 continue;
10253
10254 Item* pItem2 = GetItemByPos(bag, j);
10255
10256 // ignore move item (this slot will be empty at move)
10257 if (pItem2 == pSrcItem)
10258 pItem2 = nullptr;
10259
10260 // if merge skip empty, if !merge skip non-empty
10261 if ((pItem2 != nullptr) != merge)
10262 continue;
10263
10264 uint32 need_space = pProto->GetMaxStackSize();
10265
10266 if (pItem2)
10267 {
10268 // can be merged at least partly
10269 if (pItem2->CanBeMergedPartlyWith(pProto) != EQUIP_ERR_OK)
10270 continue;
10271
10272 // descrease at current stacksize
10273 need_space -= pItem2->GetCount();
10274 }
10275
10276 if (need_space > count)
10277 need_space = count;
10278
10279 ItemPosCount newPosition = ItemPosCount((bag << 8) | j, need_space);
10280 if (!newPosition.isContainedIn(dest))
10281 {
10282 dest.push_back(newPosition);
10283 count -= need_space;
10284
10285 if (count==0)
10286 return EQUIP_ERR_OK;
10287 }
10288 }
10289 return EQUIP_ERR_OK;
10290}
10291
10292InventoryResult Player::CanStoreItem_InInventorySlots(uint8 slot_begin, uint8 slot_end, ItemPosCountVec &dest, ItemTemplate const* pProto, uint32& count, bool merge, Item* pSrcItem, uint8 skip_bag, uint8 skip_slot) const
10293{
10294 //this is never called for non-bag slots so we can do this
10295 if (pSrcItem && pSrcItem->IsNotEmptyBag())
10297
10298 for (uint32 j = slot_begin; j < slot_end; j++)
10299 {
10300 // skip specific slot already processed in first called CanStoreItem_InSpecificSlot
10301 if (INVENTORY_SLOT_BAG_0 == skip_bag && j == skip_slot)
10302 continue;
10303
10305
10306 // ignore move item (this slot will be empty at move)
10307 if (pItem2 == pSrcItem)
10308 pItem2 = nullptr;
10309
10310 // if merge skip empty, if !merge skip non-empty
10311 if ((pItem2 != nullptr) != merge)
10312 continue;
10313
10314 uint32 need_space = pProto->GetMaxStackSize();
10315
10316 if (pItem2)
10317 {
10318 // can be merged at least partly
10319 if (pItem2->CanBeMergedPartlyWith(pProto) != EQUIP_ERR_OK)
10320 continue;
10321
10322 // descrease at current stacksize
10323 need_space -= pItem2->GetCount();
10324 }
10325
10326 if (need_space > count)
10327 need_space = count;
10328
10329 ItemPosCount newPosition = ItemPosCount((INVENTORY_SLOT_BAG_0 << 8) | j, need_space);
10330 if (!newPosition.isContainedIn(dest))
10331 {
10332 dest.push_back(newPosition);
10333 count -= need_space;
10334
10335 if (count==0)
10336 return EQUIP_ERR_OK;
10337 }
10338 }
10339 return EQUIP_ERR_OK;
10340}
10341
10342InventoryResult Player::CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec &dest, uint32 entry, uint32 count, Item* pItem, bool swap, uint32* no_space_count) const
10343{
10344 TC_LOG_DEBUG("entities.player.items", "Player::CanStoreItem: Bag: {}, Slot: {}, Item: {}, Count: {}", bag, slot, entry, count);
10345
10346 ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(entry);
10347 if (!pProto)
10348 {
10349 if (no_space_count)
10350 *no_space_count = count;
10352 }
10353
10354 if (pItem)
10355 {
10356 // item used
10357 if (pItem->m_lootGenerated)
10358 {
10359 if (no_space_count)
10360 *no_space_count = count;
10361 return EQUIP_ERR_LOOT_GONE;
10362 }
10363
10364 if (pItem->IsBindedNotWith(this))
10365 {
10366 if (no_space_count)
10367 *no_space_count = count;
10368 return EQUIP_ERR_NOT_OWNER;
10369 }
10370 }
10371
10372 // check count of items (skip for auto move for same player from bank)
10373 uint32 no_similar_count = 0; // can't store this amount similar items
10374 auto tryHandleInvStoreResult = [&](InventoryResult res) -> Optional<InventoryResult>
10375 {
10376 if (res != EQUIP_ERR_OK)
10377 {
10378 if (no_space_count)
10379 *no_space_count = count + no_similar_count;
10380 return res;
10381 }
10382
10383 if (count == 0)
10384 {
10385 if (no_similar_count == 0)
10386 return EQUIP_ERR_OK;
10387 if (no_space_count)
10388 *no_space_count = count + no_similar_count;
10390 }
10391
10392 // not handled
10393 return {};
10394 };
10395
10396 auto tryHandleBagStoreResult = [&](InventoryResult res) -> Optional<InventoryResult>
10397 {
10398 if (res == EQUIP_ERR_OK && count == 0)
10399 {
10400 if (no_similar_count == 0)
10401 return EQUIP_ERR_OK;
10402 if (no_space_count)
10403 *no_space_count = count + no_similar_count;
10405 }
10406
10407 // not handled
10408 return {};
10409 };
10410
10411 InventoryResult res = CanTakeMoreSimilarItems(entry, count, pItem, &no_similar_count);
10412 if (res != EQUIP_ERR_OK)
10413 {
10414 if (count == no_similar_count)
10415 {
10416 if (no_space_count)
10417 *no_space_count = no_similar_count;
10418 return res;
10419 }
10420 count -= no_similar_count;
10421 }
10422
10423 // in specific slot
10424 if (bag != NULL_BAG && slot != NULL_SLOT)
10425 {
10426 res = CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, count, swap, pItem);
10427 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10428 return *res2;
10429 }
10430
10431 // not specific slot or have space for partly store only in specific slot
10433
10434 // in specific bag
10435 if (bag != NULL_BAG)
10436 {
10437 // search stack in bag for merge to
10438 if (pProto->GetMaxStackSize() != 1)
10439 {
10440 if (bag == INVENTORY_SLOT_BAG_0) // inventory
10441 {
10442 res = CanStoreItem_InInventorySlots(CHILD_EQUIPMENT_SLOT_START, CHILD_EQUIPMENT_SLOT_END, dest, pProto, count, true, pItem, bag, slot);
10443 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10444 return *res2;
10445
10446 res = CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, inventorySlotEnd, dest, pProto, count, true, pItem, bag, slot);
10447 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10448 return *res2;
10449 }
10450 else // equipped bag
10451 {
10452 // we need check 2 time (specialized/non_specialized), use NULL_BAG to prevent skipping bag
10453 res = CanStoreItem_InBag(bag, dest, pProto, count, true, false, pItem, NULL_BAG, slot);
10454 if (res != EQUIP_ERR_OK)
10455 res = CanStoreItem_InBag(bag, dest, pProto, count, true, true, pItem, NULL_BAG, slot);
10456
10457 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10458 return *res2;
10459 }
10460 }
10461
10462 // search free slot in bag for place to
10463 if (bag == INVENTORY_SLOT_BAG_0) // inventory
10464 {
10465 if (pItem && pItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD))
10466 {
10467 res = CanStoreItem_InInventorySlots(CHILD_EQUIPMENT_SLOT_START, CHILD_EQUIPMENT_SLOT_END, dest, pProto, count, false, pItem, bag, slot);
10468 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10469 return *res2;
10470 }
10471
10472 res = CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, inventorySlotEnd, dest, pProto, count, false, pItem, bag, slot);
10473 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10474 return *res2;
10475 }
10476 else // equipped bag
10477 {
10478 res = CanStoreItem_InBag(bag, dest, pProto, count, false, false, pItem, NULL_BAG, slot);
10479 if (res != EQUIP_ERR_OK)
10480 res = CanStoreItem_InBag(bag, dest, pProto, count, false, true, pItem, NULL_BAG, slot);
10481
10482 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10483 return *res2;
10484 }
10485 }
10486
10487 // not specific bag or have space for partly store only in specific bag
10488
10489 // search stack for merge to
10490 if (pProto->GetMaxStackSize() != 1)
10491 {
10492 res = CanStoreItem_InInventorySlots(CHILD_EQUIPMENT_SLOT_START, CHILD_EQUIPMENT_SLOT_END, dest, pProto, count, true, pItem, bag, slot);
10493 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10494 return *res2;
10495
10496 res = CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, inventorySlotEnd, dest, pProto, count, true, pItem, bag, slot);
10497 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10498 return *res2;
10499
10500 if (pProto->GetBagFamily())
10501 {
10503 {
10504 res = CanStoreItem_InBag(i, dest, pProto, count, true, false, pItem, bag, slot);
10505 if (Optional<InventoryResult> res2 = tryHandleBagStoreResult(res))
10506 return *res2;
10507 }
10508 }
10509
10510 if (pProto->IsCraftingReagent())
10511 {
10513 {
10514 res = CanStoreItem_InBag(i, dest, pProto, count, true, true, pItem, bag, slot);
10515 if (Optional<InventoryResult> res2 = tryHandleBagStoreResult(res))
10516 return *res2;
10517 }
10518 }
10519
10521 {
10522 res = CanStoreItem_InBag(i, dest, pProto, count, true, true, pItem, bag, slot);
10523 if (Optional<InventoryResult> res2 = tryHandleBagStoreResult(res))
10524 return *res2;
10525 }
10526 }
10527
10528 // search free slot - special bag case
10529 if (pProto->GetBagFamily())
10530 {
10532 {
10533 res = CanStoreItem_InBag(i, dest, pProto, count, false, false, pItem, bag, slot);
10534 if (Optional<InventoryResult> res2 = tryHandleBagStoreResult(res))
10535 return *res2;
10536 }
10537 }
10538
10539 if (pProto->IsCraftingReagent())
10540 {
10542 {
10543 res = CanStoreItem_InBag(i, dest, pProto, count, false, true, pItem, bag, slot);
10544 if (Optional<InventoryResult> res2 = tryHandleBagStoreResult(res))
10545 return *res2;
10546 }
10547 }
10548
10549 if (pItem && pItem->IsNotEmptyBag())
10550 return EQUIP_ERR_BAG_IN_BAG;
10551
10552 if (pItem && pItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD))
10553 {
10554 res = CanStoreItem_InInventorySlots(CHILD_EQUIPMENT_SLOT_START, CHILD_EQUIPMENT_SLOT_END, dest, pProto, count, false, pItem, bag, slot);
10555 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10556 return *res2;
10557 }
10558
10559 // search free slot
10560 // new bags can be directly equipped
10561 if (!pItem && pProto->GetClass() == ITEM_CLASS_CONTAINER &&
10562 (pProto->GetBonding() == BIND_NONE || pProto->GetBonding() == BIND_ON_ACQUIRE))
10563 {
10564 switch (pProto->GetSubClass())
10565 {
10567 res = CanStoreItem_InInventorySlots(INVENTORY_SLOT_BAG_START, INVENTORY_SLOT_BAG_END, dest, pProto, count, false, pItem, bag, slot);
10568 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10569 return *res2;
10570 break;
10572 res = CanStoreItem_InInventorySlots(REAGENT_BAG_SLOT_START, REAGENT_BAG_SLOT_END, dest, pProto, count, false, pItem, bag, slot);
10573 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10574 return *res2;
10575 break;
10576 default:
10577 break;
10578 }
10579 }
10580
10581 res = CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, inventorySlotEnd, dest, pProto, count, false, pItem, bag, slot);
10582 if (Optional<InventoryResult> res2 = tryHandleInvStoreResult(res))
10583 return *res2;
10584
10586 {
10587 res = CanStoreItem_InBag(i, dest, pProto, count, false, true, pItem, bag, slot);
10588 if (Optional<InventoryResult> res2 = tryHandleBagStoreResult(res))
10589 return *res2;
10590 }
10591
10592 if (no_space_count)
10593 *no_space_count = count + no_similar_count;
10594
10595 return EQUIP_ERR_INV_FULL;
10596}
10597
10599InventoryResult Player::CanStoreItems(Item** items, int count, uint32* offendingItemId) const
10600{
10601 Item* item2;
10602
10603 // fill space tables, creating a mock-up of the player's inventory
10604
10605 // counts
10608
10609 // Item pointers
10610 Item* inventoryPointers[INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START] = {};
10612
10614
10615 // filling inventory
10616 for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; i++)
10617 {
10618 // build items in stock backpack
10620 if (item2 && !item2->IsInTrade())
10621 {
10622 inventoryCounts[i - INVENTORY_SLOT_ITEM_START] = item2->GetCount();
10623 inventoryPointers[i - INVENTORY_SLOT_ITEM_START] = item2;
10624 }
10625 }
10626
10628 if (Bag* pBag = GetBagByPos(i))
10629 for (uint32 j = 0; j < pBag->GetBagSize(); j++)
10630 {
10631 // build item counts in equippable bags
10632 item2 = GetItemByPos(i, j);
10633 if (item2 && !item2->IsInTrade())
10634 {
10635 bagCounts[i - INVENTORY_SLOT_BAG_START][j] = item2->GetCount();
10636 bagPointers[i - INVENTORY_SLOT_BAG_START][j] = item2;
10637 }
10638 }
10639
10640 // check free space for all items that we wish to add
10641 for (int k = 0; k < count; ++k)
10642 {
10643 // Incoming item
10644 Item* item = items[k];
10645
10646 // no item
10647 if (!item)
10648 continue;
10649
10650 uint32_t remaining_count = item->GetCount();
10651
10652 TC_LOG_DEBUG("entities.player.items", "Player::CanStoreItems: Player '{}' ({}), Index: {} ItemID: {}, Count: {}",
10653 GetName(), GetGUID().ToString(), k + 1, item->GetEntry(), remaining_count);
10654 ItemTemplate const* pProto = item->GetTemplate();
10655
10656 // strange item
10657 if (!pProto)
10659
10660 // item used
10661 if (item->m_lootGenerated)
10662 return EQUIP_ERR_LOOT_GONE;
10663
10664 // item it 'bind'
10665 if (item->IsBindedNotWith(this))
10666 return EQUIP_ERR_NOT_OWNER;
10667
10668 ItemTemplate const* pBagProto;
10669
10670 // item is 'one item only'
10671 InventoryResult res = CanTakeMoreSimilarItems(item, offendingItemId);
10672 if (res != EQUIP_ERR_OK)
10673 return res;
10674
10675 // search stack for merge to
10676 if (pProto->GetMaxStackSize() != 1)
10677 {
10678 bool b_found = false;
10679
10680 for (int t = INVENTORY_SLOT_ITEM_START; t < inventoryEnd; ++t)
10681 {
10682 item2 = inventoryPointers[t-INVENTORY_SLOT_ITEM_START];
10683 if (item2 && item2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && inventoryCounts[t-INVENTORY_SLOT_ITEM_START] < pProto->GetMaxStackSize())
10684 {
10685 inventoryCounts[t-INVENTORY_SLOT_ITEM_START] += remaining_count;
10686 remaining_count = inventoryCounts[t-INVENTORY_SLOT_ITEM_START] < pProto->GetMaxStackSize() ? 0 : inventoryCounts[t-INVENTORY_SLOT_ITEM_START] - pProto->GetMaxStackSize();
10687
10688 b_found = remaining_count == 0;
10689 // if no pieces of the stack remain, then stop checking stock bag
10690 if (b_found)
10691 break;
10692 }
10693 }
10694
10695 if (b_found)
10696 continue;
10697
10698 for (int t = INVENTORY_SLOT_BAG_START; !b_found && t < REAGENT_BAG_SLOT_END; ++t)
10699 {
10700 if (Bag* bag = GetBagByPos(t))
10701 {
10702 if (!ItemCanGoIntoBag(item->GetTemplate(), bag->GetTemplate()))
10703 continue;
10704
10705 for (uint32 j = 0; j < bag->GetBagSize(); j++)
10706 {
10707 item2 = bagPointers[t-INVENTORY_SLOT_BAG_START][j];
10708 if (item2 && item2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && bagCounts[t-INVENTORY_SLOT_BAG_START][j] < pProto->GetMaxStackSize())
10709 {
10710 // add count to stack so that later items in the list do not double-book
10711 bagCounts[t-INVENTORY_SLOT_BAG_START][j] += remaining_count;
10712 remaining_count = bagCounts[t-INVENTORY_SLOT_BAG_START][j] < pProto->GetMaxStackSize() ? 0 : bagCounts[t-INVENTORY_SLOT_BAG_START][j] - pProto->GetMaxStackSize();
10713
10714 b_found = remaining_count == 0;
10715
10716 // if no pieces of the stack remain, then stop checking equippable bags
10717 if (b_found)
10718 break;
10719 }
10720 }
10721 }
10722 }
10723
10724 if (b_found)
10725 continue;
10726 }
10727
10728 // special bag case
10729 if (pProto->GetBagFamily())
10730 {
10731 bool b_found = false;
10732
10733 for (int t = INVENTORY_SLOT_BAG_START; !b_found && t < REAGENT_BAG_SLOT_END; ++t)
10734 {
10735 if (Bag* bag = GetBagByPos(t))
10736 {
10737 pBagProto = bag->GetTemplate();
10738
10739 // not plain container check
10740 if (pBagProto && (pBagProto->GetClass() != ITEM_CLASS_CONTAINER || (pBagProto->GetSubClass() != ITEM_SUBCLASS_CONTAINER && pBagProto->GetSubClass() != ITEM_SUBCLASS_REAGENT_CONTAINER)) &&
10741 ItemCanGoIntoBag(pProto, pBagProto))
10742 {
10743 for (uint32 j = 0; j < bag->GetBagSize(); j++)
10744 {
10745 if (bagCounts[t-INVENTORY_SLOT_BAG_START][j] == 0)
10746 {
10747 bagCounts[t-INVENTORY_SLOT_BAG_START][j] = remaining_count;
10748 bagPointers[t-INVENTORY_SLOT_BAG_START][j] = item;
10749
10750 b_found = true;
10751 break;
10752 }
10753 }
10754 }
10755 }
10756 }
10757
10758 if (b_found)
10759 continue;
10760 }
10761
10762 // search free slot
10763 bool b_found = false;
10764 for (int t = INVENTORY_SLOT_ITEM_START; t < inventoryEnd; ++t)
10765 {
10766 if (inventoryCounts[t-INVENTORY_SLOT_ITEM_START] == 0)
10767 {
10768 inventoryCounts[t-INVENTORY_SLOT_ITEM_START] = remaining_count;
10769 inventoryPointers[t-INVENTORY_SLOT_ITEM_START] = item;
10770
10771 b_found = true;
10772 break;
10773 }
10774 }
10775
10776 if (b_found)
10777 continue;
10778
10779 // search free slot in bags
10780 for (uint8 t = INVENTORY_SLOT_BAG_START; !b_found && t < REAGENT_BAG_SLOT_END; ++t)
10781 {
10782 if (Bag* bag = GetBagByPos(t))
10783 {
10784 pBagProto = bag->GetTemplate();
10785
10786 // special bag already checked
10787 if (pBagProto && (pBagProto->GetClass() != ITEM_CLASS_CONTAINER || (pBagProto->GetSubClass() != ITEM_SUBCLASS_CONTAINER && pBagProto->GetSubClass() != ITEM_SUBCLASS_REAGENT_CONTAINER)))
10788 continue;
10789
10790 for (uint32 j = 0; j < bag->GetBagSize(); j++)
10791 {
10792 if (bagCounts[t - INVENTORY_SLOT_BAG_START][j] == 0)
10793 {
10794 bagCounts[t-INVENTORY_SLOT_BAG_START][j] = remaining_count;
10795 bagPointers[t-INVENTORY_SLOT_BAG_START][j] = item;
10796
10797 b_found = true;
10798 break;
10799 }
10800 }
10801 }
10802 }
10803
10804 // if no free slot found for all pieces of the item, then return an error
10805 if (!b_found)
10806 return EQUIP_ERR_BAG_FULL;
10807 }
10808
10809 return EQUIP_ERR_OK;
10810}
10811
10813InventoryResult Player::CanEquipNewItem(uint8 slot, uint16 &dest, uint32 item, bool swap) const
10814{
10815 dest = 0;
10816 Item* pItem = Item::CreateItem(item, 1, ItemContext::NONE, this);
10817 if (pItem)
10818 {
10819 InventoryResult result = CanEquipItem(slot, dest, pItem, swap);
10820 delete pItem;
10821 return result;
10822 }
10823
10825}
10826
10827InventoryResult Player::CanEquipItem(uint8 slot, uint16 &dest, Item* pItem, bool swap, bool not_loading) const
10828{
10829 dest = 0;
10830 if (pItem)
10831 {
10832 TC_LOG_DEBUG("entities.player.items", "Player::CanEquipItem: Player '{}' ({}), Slot: {}, Item: {}, Count: {}",
10833 GetName(), GetGUID().ToString(), slot, pItem->GetEntry(), pItem->GetCount());
10834 ItemTemplate const* pProto = pItem->GetTemplate();
10835 if (pProto)
10836 {
10837 // item used
10838 if (pItem->m_lootGenerated)
10839 return EQUIP_ERR_LOOT_GONE;
10840
10841 if (pItem->IsBindedNotWith(this))
10842 return EQUIP_ERR_NOT_OWNER;
10843
10844 // check count of items (skip for auto move for same player from bank)
10846 if (res != EQUIP_ERR_OK)
10847 return res;
10848
10849 // check this only in game
10850 if (not_loading)
10851 {
10852 // May be here should be more stronger checks; STUNNED checked
10853 // ROOT, CONFUSED, DISTRACTED, FLEEING this needs to be checked.
10856
10857 if (IsCharmed())
10858 return EQUIP_ERR_CLIENT_LOCKED_OUT; // @todo is this the correct error?
10859
10860 // do not allow equipping gear except weapons, offhands, projectiles, relics in
10861 // - combat
10862 // - in-progress arenas
10863 if (!pProto->CanChangeEquipStateInCombat())
10864 {
10865 if (IsInCombat())
10867
10868 if (Battleground* bg = GetBattleground())
10869 if (bg->isArena() && bg->GetStatus() == STATUS_IN_PROGRESS)
10871 }
10872
10873 if (IsInCombat() && (pProto->GetClass() == ITEM_CLASS_WEAPON || pProto->GetInventoryType() == INVTYPE_RELIC) && m_weaponChangeTimer != 0)
10875
10876 if (Spell* currentGenericSpell = GetCurrentSpell(CURRENT_GENERIC_SPELL))
10877 if (!currentGenericSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ALLOW_EQUIP_WHILE_CASTING))
10879
10880 if (Spell* currentChanneledSpell = GetCurrentSpell(CURRENT_CHANNELED_SPELL))
10881 if (!currentChanneledSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ALLOW_EQUIP_WHILE_CASTING))
10883 }
10884
10885 Optional<ContentTuningLevels> requiredLevels;
10886 // check allowed level (extend range to upper values if MaxLevel more or equal max player level, this let GM set high level with 1...max range items)
10887 if (pItem->GetQuality() == ITEM_QUALITY_HEIRLOOM)
10888 requiredLevels = sDB2Manager.GetContentTuningData(pItem->GetScalingContentTuningId(), 0, true);
10889
10890 if (requiredLevels && requiredLevels->MaxLevel < DEFAULT_MAX_LEVEL && requiredLevels->MaxLevel < GetLevel() && !sDB2Manager.GetHeirloomByItemId(pProto->GetId()))
10892
10893 uint8 eslot = FindEquipSlot(pItem, slot, swap);
10894 if (eslot == NULL_SLOT)
10896
10897 res = CanUseItem(pItem, not_loading);
10898 if (res != EQUIP_ERR_OK)
10899 return res;
10900
10901 if (!swap && GetItemByPos(INVENTORY_SLOT_BAG_0, eslot))
10903
10904 // if we are swapping 2 equiped items, CanEquipUniqueItem check
10905 // should ignore the item we are trying to swap, and not the
10906 // destination item. CanEquipUniqueItem should ignore destination
10907 // item only when we are swapping weapon from bag
10908 uint8 ignore = uint8(NULL_SLOT);
10909 switch (eslot)
10910 {
10912 ignore = EQUIPMENT_SLOT_OFFHAND;
10913 break;
10915 ignore = EQUIPMENT_SLOT_MAINHAND;
10916 break;
10918 ignore = EQUIPMENT_SLOT_FINGER2;
10919 break;
10921 ignore = EQUIPMENT_SLOT_FINGER1;
10922 break;
10924 ignore = EQUIPMENT_SLOT_TRINKET2;
10925 break;
10927 ignore = EQUIPMENT_SLOT_TRINKET1;
10928 break;
10931 break;
10934 break;
10937 break;
10940 break;
10941 }
10942
10943 if (ignore == uint8(NULL_SLOT) || pItem != GetItemByPos(INVENTORY_SLOT_BAG_0, ignore))
10944 ignore = eslot;
10945
10946 InventoryResult res2 = CanEquipUniqueItem(pItem, swap ? ignore : uint8(NULL_SLOT));
10947 if (res2 != EQUIP_ERR_OK)
10948 return res2;
10949
10950 // check unique-equipped special item classes
10951 if (pProto->GetClass() == ITEM_CLASS_QUIVER)
10953 if (Item* pBag = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
10954 if (pBag != pItem)
10955 if (ItemTemplate const* pBagProto = pBag->GetTemplate())
10956 if (pBagProto->GetClass() == pProto->GetClass() && (!swap || pBag->GetSlot() != eslot))
10957 return (pBagProto->GetSubClass() == ITEM_SUBCLASS_AMMO_POUCH)
10960
10961 uint32 type = pProto->GetInventoryType();
10962
10963 if (eslot == EQUIPMENT_SLOT_OFFHAND)
10964 {
10965 // Do not allow polearm to be equipped in the offhand (rare case for the only 1h polearm 41750)
10966 if (type == INVTYPE_WEAPON && pProto->GetSubClass() == ITEM_SUBCLASS_WEAPON_POLEARM)
10967 return EQUIP_ERR_WRONG_SLOT;
10968 else if (type == INVTYPE_WEAPON)
10969 {
10970 if (!CanDualWield())
10972 }
10973 else if (type == INVTYPE_WEAPONOFFHAND)
10974 {
10977 }
10978 else if (type == INVTYPE_2HWEAPON)
10979 {
10980 if (!CanDualWield() || !CanTitanGrip())
10982 }
10983
10984 if (IsTwoHandUsed())
10986 }
10987
10988 // equip two-hand weapon case (with possible unequip 2 items)
10989 if (type == INVTYPE_2HWEAPON)
10990 {
10991 if (eslot == EQUIPMENT_SLOT_OFFHAND)
10992 {
10993 if (!CanTitanGrip())
10995 }
10996 else if (eslot != EQUIPMENT_SLOT_MAINHAND)
10998
10999 if (!CanTitanGrip())
11000 {
11001 // offhand item must can be stored in inventory for offhand item and it also must be unequipped
11003 ItemPosCountVec off_dest;
11004 if (offItem && (!not_loading ||
11006 CanStoreItem(NULL_BAG, NULL_SLOT, off_dest, offItem, false) != EQUIP_ERR_OK))
11008 }
11009 }
11010 dest = ((INVENTORY_SLOT_BAG_0 << 8) | eslot);
11011 return EQUIP_ERR_OK;
11012 }
11013 }
11014
11016}
11017
11019{
11020 Item* childItem = GetChildItemByGuid(parentItem->GetChildItem());
11021 if (!childItem)
11022 return EQUIP_ERR_OK;
11023
11024 ItemChildEquipmentEntry const* childEquipement = sDB2Manager.GetItemChildEquipment(parentItem->GetEntry());
11025 if (!childEquipement)
11026 return EQUIP_ERR_OK;
11027
11028 Item* dstItem = GetItemByPos(INVENTORY_SLOT_BAG_0, childEquipement->ChildItemEquipSlot);
11029 if (!dstItem)
11030 return EQUIP_ERR_OK;
11031
11032 uint16 childDest = (INVENTORY_SLOT_BAG_0 << 8) | childEquipement->ChildItemEquipSlot;
11033 InventoryResult msg = CanUnequipItem(childDest, !childItem->IsBag());
11034 if (msg != EQUIP_ERR_OK)
11035 return msg;
11036
11037 // check dest->src move possibility
11038 uint16 src = parentItem->GetPos();
11039 ItemPosCountVec dest;
11040 if (IsInventoryPos(src))
11041 {
11042 msg = CanStoreItem(parentItem->GetBagSlot(), NULL_SLOT, dest, dstItem, true);
11043 if (msg != EQUIP_ERR_OK)
11044 msg = CanStoreItem(NULL_BAG, NULL_SLOT, dest, dstItem, true);
11045 }
11046 else if (IsBankPos(src))
11047 {
11048 msg = CanBankItem(parentItem->GetBagSlot(), NULL_SLOT, dest, dstItem, true);
11049 if (msg != EQUIP_ERR_OK)
11050 msg = CanBankItem(NULL_BAG, NULL_SLOT, dest, dstItem, true);
11051 }
11052 else if (IsEquipmentPos(src))
11053 return EQUIP_ERR_CANT_SWAP;
11054
11055 return msg;
11056}
11057
11059{
11060 // Applied only to equipped items and bank bags
11061 if (!IsEquipmentPos(pos) && !IsBagPos(pos))
11062 return EQUIP_ERR_OK;
11063
11064 Item* pItem = GetItemByPos(pos);
11065
11066 // Applied only to existing equipped item
11067 if (!pItem)
11068 return EQUIP_ERR_OK;
11069
11070 TC_LOG_DEBUG("entities.player.items", "Player::CanUnequipItem: Player '{}' ({}), Slot: {}, Item: {}, Count: {}",
11071 GetName(), GetGUID().ToString(), pos, pItem->GetEntry(), pItem->GetCount());
11072
11073 ItemTemplate const* pProto = pItem->GetTemplate();
11074 if (!pProto)
11076
11077 // item used
11078 if (pItem->m_lootGenerated)
11079 return EQUIP_ERR_LOOT_GONE;
11080
11081 if (IsCharmed())
11082 return EQUIP_ERR_CLIENT_LOCKED_OUT; // @todo is this the correct error?
11083
11084 // do not allow unequipping gear except weapons, offhands, projectiles, relics in
11085 // - combat
11086 // - in-progress arenas
11087 if (!pProto->CanChangeEquipStateInCombat())
11088 {
11089 if (IsInCombat())
11091
11092 if (Battleground* bg = GetBattleground())
11093 if (bg->isArena() && bg->GetStatus() == STATUS_IN_PROGRESS)
11095 }
11096
11097 if (!swap && pItem->IsNotEmptyBag())
11099
11100 return EQUIP_ERR_OK;
11101}
11102
11103InventoryResult Player::CanBankItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap, bool not_loading /*= true*/, bool reagentBankOnly /*= false*/) const
11104{
11105 if (!pItem)
11107
11108 // different slots range if we're trying to store item in Reagent Bank
11109 if (reagentBankOnly)
11110 {
11111 ASSERT(bag == NULL_BAG && slot == NULL_SLOT); // when reagentBankOnly is true then bag & slot must be hardcoded constants, not client input
11112 }
11113
11114 if ((IsReagentBankPos(bag, slot) || reagentBankOnly) && !IsReagentBankUnlocked())
11116
11117 uint8 slotStart = reagentBankOnly ? uint8(REAGENT_SLOT_START) : uint8(BANK_SLOT_ITEM_START);
11118 uint8 slotEnd = reagentBankOnly ? uint8(REAGENT_SLOT_END) : uint8(BANK_SLOT_ITEM_END);
11119
11120 uint32 count = pItem->GetCount();
11121
11122 TC_LOG_DEBUG("entities.player.items", "Player::CanBankItem: Player '{}' ({}), Bag: {}, Slot: {}, Item: {}, Count: {}",
11123 GetName(), GetGUID().ToString(), bag, slot, pItem->GetEntry(), pItem->GetCount());
11124 ItemTemplate const* pProto = pItem->GetTemplate();
11125 if (!pProto)
11127
11128 // item used
11129 if (pItem->m_lootGenerated)
11130 return EQUIP_ERR_LOOT_GONE;
11131
11132 if (pItem->IsBindedNotWith(this))
11133 return EQUIP_ERR_NOT_OWNER;
11134
11135 // Currency Tokenizer are not supposed to be swapped out of their hidden bag
11136 if (pItem->IsCurrencyToken())
11137 {
11138 TC_LOG_ERROR("entities.player.cheat", "Possible hacking attempt: Player {} ({}) tried to move token [{} entry: {}] out of the currency bag!",
11139 GetName(), GetGUID().ToString(), pItem->GetGUID().ToString(), pProto->GetId());
11140 return EQUIP_ERR_CANT_SWAP;
11141 }
11142
11143 // check count of items (skip for auto move for same player from bank)
11145 if (res != EQUIP_ERR_OK)
11146 return res;
11147
11148 // in specific slot
11149 if (bag != NULL_BAG && slot != NULL_SLOT)
11150 {
11151 if (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END)
11152 {
11153 if (!pItem->IsBag())
11154 return EQUIP_ERR_WRONG_SLOT;
11155
11158
11159 res = CanUseItem(pItem, not_loading);
11160 if (res != EQUIP_ERR_OK)
11161 return res;
11162 }
11163
11164 res = CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, count, swap, pItem);
11165 if (res != EQUIP_ERR_OK)
11166 return res;
11167
11168 if (count == 0)
11169 return EQUIP_ERR_OK;
11170 }
11171
11172 // not specific slot or have space for partly store only in specific slot
11173
11174 // in specific bag
11175 if (bag != NULL_BAG)
11176 {
11177 if (pItem->IsNotEmptyBag())
11178 return EQUIP_ERR_BAG_IN_BAG;
11179
11180 // search stack in bag for merge to
11181 if (pProto->GetMaxStackSize() != 1)
11182 {
11183 if (bag == INVENTORY_SLOT_BAG_0)
11184 {
11185 res = CanStoreItem_InInventorySlots(slotStart, slotEnd, dest, pProto, count, true, pItem, bag, slot);
11186 if (res != EQUIP_ERR_OK)
11187 return res;
11188
11189 if (count == 0)
11190 return EQUIP_ERR_OK;
11191 }
11192 else
11193 {
11194 res = CanStoreItem_InBag(bag, dest, pProto, count, true, false, pItem, NULL_BAG, slot);
11195 if (res != EQUIP_ERR_OK)
11196 res = CanStoreItem_InBag(bag, dest, pProto, count, true, true, pItem, NULL_BAG, slot);
11197
11198 if (res != EQUIP_ERR_OK)
11199 return res;
11200
11201 if (count == 0)
11202 return EQUIP_ERR_OK;
11203 }
11204 }
11205
11206 // search free slot in bag
11207 if (bag == INVENTORY_SLOT_BAG_0)
11208 {
11209 res = CanStoreItem_InInventorySlots(slotStart, slotEnd, dest, pProto, count, false, pItem, bag, slot);
11210 if (res != EQUIP_ERR_OK)
11211 return res;
11212
11213 if (count == 0)
11214 return EQUIP_ERR_OK;
11215 }
11216 else
11217 {
11218 res = CanStoreItem_InBag(bag, dest, pProto, count, false, false, pItem, NULL_BAG, slot);
11219 if (res != EQUIP_ERR_OK)
11220 res = CanStoreItem_InBag(bag, dest, pProto, count, false, true, pItem, NULL_BAG, slot);
11221
11222 if (res != EQUIP_ERR_OK)
11223 return res;
11224
11225 if (count == 0)
11226 return EQUIP_ERR_OK;
11227 }
11228 }
11229
11230 // not specific bag or have space for partly store only in specific bag
11231
11232 // search stack for merge to
11233 if (pProto->GetMaxStackSize() != 1)
11234 {
11235 // in slots
11236 res = CanStoreItem_InInventorySlots(slotStart, slotEnd, dest, pProto, count, true, pItem, bag, slot);
11237 if (res != EQUIP_ERR_OK)
11238 return res;
11239
11240 if (count == 0)
11241 return EQUIP_ERR_OK;
11242
11243 // don't try to store reagents anywhere else than in Reagent Bank if we're on it
11244 if (!reagentBankOnly)
11245 {
11246 // in special bags
11247 if (pProto->GetBagFamily())
11248 {
11249 for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++)
11250 {
11251 res = CanStoreItem_InBag(i, dest, pProto, count, true, false, pItem, bag, slot);
11252 if (res != EQUIP_ERR_OK)
11253 continue;
11254
11255 if (count == 0)
11256 return EQUIP_ERR_OK;
11257 }
11258 }
11259
11260 // in regular bags
11261 for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++)
11262 {
11263 res = CanStoreItem_InBag(i, dest, pProto, count, true, true, pItem, bag, slot);
11264 if (res != EQUIP_ERR_OK)
11265 continue;
11266
11267 if (count == 0)
11268 return EQUIP_ERR_OK;
11269 }
11270 }
11271 }
11272
11273 // search free space in special bags (don't try to store reagents anywhere else than in Reagent Bank if we're on it)
11274 if (!reagentBankOnly && pProto->GetBagFamily())
11275 {
11276 for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++)
11277 {
11278 res = CanStoreItem_InBag(i, dest, pProto, count, false, false, pItem, bag, slot);
11279 if (res != EQUIP_ERR_OK)
11280 continue;
11281
11282 if (count == 0)
11283 return EQUIP_ERR_OK;
11284 }
11285 }
11286
11287 // search free space
11288 res = CanStoreItem_InInventorySlots(slotStart, slotEnd, dest, pProto, count, false, pItem, bag, slot);
11289 if (res != EQUIP_ERR_OK)
11290 return res;
11291
11292 if (count == 0)
11293 return EQUIP_ERR_OK;
11294
11295 // search free space in regular bags (don't try to store reagents anywhere else than in Reagent Bank if we're on it)
11296 if (!reagentBankOnly)
11297 {
11298 for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++)
11299 {
11300 res = CanStoreItem_InBag(i, dest, pProto, count, false, true, pItem, bag, slot);
11301 if (res != EQUIP_ERR_OK)
11302 continue;
11303
11304 if (count == 0)
11305 return EQUIP_ERR_OK;
11306 }
11307 }
11308
11309 return reagentBankOnly ? EQUIP_ERR_REAGENT_BANK_FULL : EQUIP_ERR_BANK_FULL;
11310}
11311
11312InventoryResult Player::CanUseItem(Item* pItem, bool not_loading) const
11313{
11314 if (pItem)
11315 {
11316 TC_LOG_DEBUG("entities.player.items", "Player::CanUseItem: Player '{}' ({}), Item: {}",
11317 GetName(), GetGUID().ToString(), pItem->GetEntry());
11318
11319 if (!IsAlive() && not_loading)
11320 return EQUIP_ERR_PLAYER_DEAD;
11321
11322 //if (isStunned())
11323 // return EQUIP_ERR_GENERIC_STUNNED;
11324
11325 ItemTemplate const* pProto = pItem->GetTemplate();
11326 if (pProto)
11327 {
11328 if (pItem->IsBindedNotWith(this))
11329 return EQUIP_ERR_NOT_OWNER;
11330
11331 if (GetLevel() < pItem->GetRequiredLevel())
11333
11334 InventoryResult res = CanUseItem(pProto, true);
11335 if (res != EQUIP_ERR_OK)
11336 return res;
11337
11338 if (pItem->GetSkill() != 0)
11339 {
11340 bool allowEquip = false;
11341 uint32 itemSkill = pItem->GetSkill();
11342 // Armor that is binded to account can "morph" from plate to mail, etc. if skill is not learned yet.
11343 if (pProto->GetQuality() == ITEM_QUALITY_HEIRLOOM && pProto->GetClass() == ITEM_CLASS_ARMOR && !HasSkill(itemSkill))
11344 {
11346 // In fact it's a visual bug, everything works properly... I need sniffs of operations with
11347 // binded to account items from off server.
11348
11349 switch (GetClass())
11350 {
11351 case CLASS_HUNTER:
11352 case CLASS_SHAMAN:
11353 allowEquip = (itemSkill == SKILL_MAIL);
11354 break;
11355 case CLASS_PALADIN:
11356 case CLASS_WARRIOR:
11357 allowEquip = (itemSkill == SKILL_PLATE_MAIL);
11358 break;
11359 }
11360 }
11361 if (!allowEquip && GetSkillValue(itemSkill) == 0)
11363 }
11364
11365 return EQUIP_ERR_OK;
11366 }
11367 }
11369}
11370
11371InventoryResult Player::CanUseItem(ItemTemplate const* proto, bool skipRequiredLevelCheck /*= false*/) const
11372{
11373 // Used by group, function GroupLoot, to know if a prototype can be used by a player
11374
11375 if (!proto)
11377
11380
11381 if (proto->HasFlag(ITEM_FLAG2_FACTION_HORDE) && GetTeam() != HORDE)
11383
11386
11387 if ((proto->GetAllowableClass() & GetClassMask()) == 0 || !proto->GetAllowableRace().HasRace(GetRace()))
11389
11390 if (proto->GetRequiredSkill() != 0)
11391 {
11392 if (GetSkillValue(proto->GetRequiredSkill()) == 0)
11394 else if (GetSkillValue(proto->GetRequiredSkill()) < proto->GetRequiredSkillRank())
11396 }
11397
11398 if (proto->GetRequiredSpell() != 0 && !HasSpell(proto->GetRequiredSpell()))
11400
11401 if (!skipRequiredLevelCheck && GetLevel() < proto->GetBaseRequiredLevel())
11403
11404 // If World Event is not active, prevent using event dependant items
11405 if (proto->GetHolidayID() && !IsHolidayActive(proto->GetHolidayID()))
11407
11410
11411 // learning (recipes, mounts, pets, etc.)
11412 if (proto->Effects.size() >= 2)
11413 if (proto->Effects[0]->SpellID == 483 || proto->Effects[0]->SpellID == 55884)
11414 if (HasSpell(proto->Effects[1]->SpellID))
11416
11417 if (ArtifactEntry const* artifact = sArtifactStore.LookupEntry(proto->GetArtifactID()))
11418 if (ChrSpecialization(artifact->ChrSpecializationID) != GetPrimarySpecialization())
11420
11421 return EQUIP_ERR_OK;
11422}
11423
11424InventoryResult Player::CanRollNeedForItem(ItemTemplate const* proto, Map const* map, bool restrictOnlyLfg) const
11425{
11426 if (restrictOnlyLfg)
11427 {
11428 if (!GetGroup() || !GetGroup()->isLFGGroup())
11429 return EQUIP_ERR_OK; // not in LFG group
11430
11431 // check if looted object is inside the lfg dungeon
11432 if (!sLFGMgr->inLfgDungeonMap(GetGroup()->GetGUID(), map->GetId(), map->GetDifficultyID()))
11433 return EQUIP_ERR_OK;
11434 }
11435
11436 if (!proto)
11438
11439 // Used by group, function GroupLoot, to know if a prototype can be used by a player
11440 if ((proto->GetAllowableClass() & GetClassMask()) == 0 || !proto->GetAllowableRace().HasRace(GetRace()))
11442
11443 if (proto->GetRequiredSpell() != 0 && !HasSpell(proto->GetRequiredSpell()))
11445
11446 if (proto->GetRequiredSkill() != 0)
11447 {
11448 if (!GetSkillValue(proto->GetRequiredSkill()))
11450 else if (GetSkillValue(proto->GetRequiredSkill()) < proto->GetRequiredSkillRank())
11452 }
11453
11454 if (proto->GetClass() == ITEM_CLASS_WEAPON && GetSkillValue(proto->GetSkill()) == 0)
11456
11457 if (proto->GetClass() == ITEM_CLASS_ARMOR && proto->GetInventoryType() != INVTYPE_CLOAK)
11458 {
11459 ChrClassesEntry const* classesEntry = sChrClassesStore.AssertEntry(GetClass());
11460 if (!(classesEntry->ArmorTypeMask & 1 << proto->GetSubClass()))
11462 }
11463
11464 return EQUIP_ERR_OK;
11465}
11466
11467// Return stored item (if stored to stack, it can diff. from pItem). And pItem ca be deleted in this case.
11468Item* Player::StoreNewItem(ItemPosCountVec const& pos, uint32 itemId, bool update, ItemRandomBonusListId randomBonusListId /*= 0*/,
11469 GuidSet const& allowedLooters /*= GuidSet()*/, ItemContext context /*= ItemContext::NONE*/,
11470 std::vector<int32> const* bonusListIDs /*= std::vector<int32>()*/, bool addToCollection /*= true*/)
11471{
11472 uint32 count = 0;
11473 for (ItemPosCountVec::const_iterator itr = pos.begin(); itr != pos.end(); ++itr)
11474 count += itr->count;
11475
11476 // quest objectives must be processed twice - QUEST_OBJECTIVE_FLAG_2_QUEST_BOUND_ITEM prevents item creation
11477 bool hadBoundItemObjective = false;
11478 ItemAddedQuestCheck(itemId, count, true, &hadBoundItemObjective);
11479 if (hadBoundItemObjective)
11480 return nullptr;
11481
11482 Item* item = Item::CreateItem(itemId, count, context, this, bonusListIDs == nullptr);
11483 if (item)
11484 {
11486
11487 if (bonusListIDs)
11488 item->SetBonuses(*bonusListIDs);
11489
11490 item->SetFixedLevel(GetLevel());
11491 item->SetItemRandomBonusList(randomBonusListId);
11493
11494 item = StoreItem(pos, item, update);
11495
11496 ItemAddedQuestCheck(itemId, count, false);
11499
11500 if (allowedLooters.size() > 1 && item->GetTemplate()->GetMaxStackSize() == 1 && item->IsSoulBound())
11501 {
11502 item->SetSoulboundTradeable(allowedLooters);
11503 AddTradeableItem(item);
11504
11505 // save data
11506 std::ostringstream ss;
11507 GuidSet::const_iterator itr = allowedLooters.begin();
11508 ss << itr->GetCounter();
11509 for (++itr; itr != allowedLooters.end(); ++itr)
11510 ss << ' ' << itr->GetCounter();
11511
11513 stmt->setUInt64(0, item->GetGUID().GetCounter());
11514 stmt->setString(1, ss.str());
11515 CharacterDatabase.Execute(stmt);
11516 }
11517
11518 if (addToCollection)
11520
11521 if (ItemChildEquipmentEntry const* childItemEntry = sDB2Manager.GetItemChildEquipment(itemId))
11522 {
11523 if (ItemTemplate const* childTemplate = sObjectMgr->GetItemTemplate(childItemEntry->ChildItemID))
11524 {
11525 ItemPosCountVec childDest;
11526 CanStoreItem_InInventorySlots(CHILD_EQUIPMENT_SLOT_START, CHILD_EQUIPMENT_SLOT_END, childDest, childTemplate, count, false, nullptr, NULL_BAG, NULL_SLOT);
11527 if (Item* childItem = StoreNewItem(childDest, childTemplate->GetId(), update, {}, {}, context, nullptr, addToCollection))
11528 {
11529 childItem->SetCreator(item->GetGUID());
11530 childItem->SetItemFlag(ITEM_FIELD_FLAG_CHILD);
11531 item->SetChildItem(childItem->GetGUID());
11532 }
11533 }
11534 }
11535
11538 }
11539
11540 return item;
11541}
11542
11543Item* Player::StoreItem(ItemPosCountVec const& dest, Item* pItem, bool update)
11544{
11545 if (!pItem)
11546 return nullptr;
11547
11548 Item* lastItem = pItem;
11549 for (ItemPosCountVec::const_iterator itr = dest.begin(); itr != dest.end();)
11550 {
11551 uint16 pos = itr->pos;
11552 uint32 count = itr->count;
11553
11554 ++itr;
11555
11556 if (itr == dest.end())
11557 {
11558 lastItem = _StoreItem(pos, pItem, count, false, update);
11559 break;
11560 }
11561
11562 lastItem = _StoreItem(pos, pItem, count, true, update);
11563 }
11564
11565 AutoUnequipChildItem(lastItem);
11566
11567 return lastItem;
11568}
11569
11570// Return stored item (if stored to stack, it can diff. from pItem). And pItem ca be deleted in this case.
11571Item* Player::_StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool update)
11572{
11573 if (!pItem)
11574 return nullptr;
11575
11576 uint8 bag = pos >> 8;
11577 uint8 slot = pos & 255;
11578
11579 TC_LOG_DEBUG("entities.player.items", "Player::_StoreItem: Player '{}' ({}), Bag: {}, Slot: {}, Item: {} ({}), Count: {}",
11580 GetName(), GetGUID().ToString(), bag, slot, pItem->GetEntry(), pItem->GetGUID().ToString(), count);
11581
11582 Item* pItem2 = GetItemByPos(bag, slot);
11583
11584 if (!pItem2)
11585 {
11586 if (clone)
11587 pItem = pItem->CloneItem(count, this);
11588 else
11589 pItem->SetCount(count);
11590
11591 if (!pItem)
11592 return nullptr;
11593
11594 if (pItem->GetBonding() == BIND_ON_ACQUIRE ||
11595 pItem->GetBonding() == BIND_QUEST ||
11596 (pItem->GetBonding() == BIND_ON_EQUIP && IsBagPos(pos)))
11597 pItem->SetBinding(true);
11598
11599 Bag* pBag = (bag == INVENTORY_SLOT_BAG_0) ? nullptr : GetBagByPos(bag);
11600 if (!pBag)
11601 {
11602 m_items[slot] = pItem;
11603 SetInvSlot(slot, pItem->GetGUID());
11604 pItem->SetContainedIn(GetGUID());
11605 pItem->SetOwnerGUID(GetGUID());
11606
11607 pItem->SetSlot(slot);
11608 pItem->SetContainer(nullptr);
11609 }
11610 else
11611 pBag->StoreItem(slot, pItem, update);
11612
11613 if (IsInWorld() && update)
11614 {
11615 pItem->AddToWorld();
11616 pItem->SendUpdateToPlayer(this);
11617 }
11618
11619 pItem->SetState(ITEM_CHANGED, this);
11620 if (pBag)
11621 pBag->SetState(ITEM_CHANGED, this);
11622
11624 AddItemDurations(pItem);
11625
11627 ApplyItemObtainSpells(pItem, true);
11628
11629 return pItem;
11630 }
11631 else
11632 {
11633 if (pItem2->GetBonding() == BIND_ON_ACQUIRE ||
11634 pItem2->GetBonding() == BIND_QUEST ||
11635 (pItem2->GetBonding() == BIND_ON_EQUIP && IsBagPos(pos)))
11636 pItem2->SetBinding(true);
11637
11638 pItem2->SetCount(pItem2->GetCount() + count);
11639 if (IsInWorld() && update)
11640 pItem2->SendUpdateToPlayer(this);
11641
11642 if (!clone)
11643 {
11644 // delete item (it not in any slot currently)
11645 if (IsInWorld() && update)
11646 {
11647 pItem->RemoveFromWorld();
11648 pItem->DestroyForPlayer(this);
11649 }
11650
11652 RemoveItemDurations(pItem);
11653
11654 pItem->SetOwnerGUID(GetGUID()); // prevent error at next SetState in case trade/mail/buy from vendor
11655 pItem->SetNotRefundable(this);
11656 pItem->ClearSoulboundTradeable(this);
11657 RemoveTradeableItem(pItem);
11658 pItem->SetState(ITEM_REMOVED, this);
11659 }
11660
11662
11663 pItem2->SetState(ITEM_CHANGED, this);
11664
11666 ApplyItemObtainSpells(pItem2, true);
11667
11668 return pItem2;
11669 }
11670}
11671
11672Item* Player::EquipNewItem(uint16 pos, uint32 item, ItemContext context, bool update)
11673{
11674 if (Item* pItem = Item::CreateItem(item, 1, context, this))
11675 {
11677 Item* equippedItem = EquipItem(pos, pItem, update);
11678 ItemAddedQuestCheck(item, 1);
11679 return equippedItem;
11680 }
11681
11682 return nullptr;
11683}
11684
11685Item* Player::EquipItem(uint16 pos, Item* pItem, bool update)
11686{
11688 AddItemDurations(pItem);
11689
11690 uint8 bag = pos >> 8;
11691 uint8 slot = pos & 255;
11692
11693 Item* pItem2 = GetItemByPos(bag, slot);
11694
11695 if (!pItem2)
11696 {
11697 VisualizeItem(slot, pItem);
11698
11699 if (IsAlive())
11700 {
11701 ItemTemplate const* pProto = pItem->GetTemplate();
11702
11703 // item set bonuses applied only at equip and removed at unequip, and still active for broken items
11704 if (pProto && pProto->GetItemSet())
11705 AddItemsSetItem(this, pItem);
11706
11707 _ApplyItemMods(pItem, slot, true);
11708
11709 if (pProto && IsInCombat() && (pProto->GetClass() == ITEM_CLASS_WEAPON || pProto->GetInventoryType() == INVTYPE_RELIC) && m_weaponChangeTimer == 0)
11710 {
11711 uint32 cooldownSpell = GetClass() == CLASS_ROGUE ? 6123 : 6119;
11712 SpellInfo const* spellProto = sSpellMgr->GetSpellInfo(cooldownSpell, DIFFICULTY_NONE);
11713
11714 if (!spellProto)
11715 TC_LOG_ERROR("entities.player", "Player::EquipItem: Weapon switch cooldown spell {} for player '{}' ({}) couldn't be found in Spell.dbc",
11716 cooldownSpell, GetName(), GetGUID().ToString());
11717 else
11718 {
11720
11722
11724 spellCooldown.Caster = GetGUID();
11725 spellCooldown.Flags = SPELL_COOLDOWN_FLAG_INCLUDE_GCD;
11726 spellCooldown.SpellCooldowns.emplace_back(cooldownSpell, 0);
11727 SendDirectMessage(spellCooldown.Write());
11728 }
11729 }
11730 }
11731
11733
11734 if (IsInWorld() && update)
11735 {
11736 pItem->AddToWorld();
11737 pItem->SendUpdateToPlayer(this);
11738 }
11739
11740 ApplyEquipCooldown(pItem);
11741
11742 // update expertise and armor penetration - passive auras may need it
11743
11744 if (slot == EQUIPMENT_SLOT_MAINHAND)
11746
11747 else if (slot == EQUIPMENT_SLOT_OFFHAND)
11749
11750 switch (slot)
11751 {
11755 break;
11756 default:
11757 break;
11758 }
11759 }
11760 else
11761 {
11762 pItem2->SetCount(pItem2->GetCount() + pItem->GetCount());
11763 if (IsInWorld() && update)
11764 pItem2->SendUpdateToPlayer(this);
11765
11766 // delete item (it not in any slot currently)
11767 //pItem->DeleteFromDB();
11768 if (IsInWorld() && update)
11769 {
11770 pItem->RemoveFromWorld();
11771 pItem->DestroyForPlayer(this);
11772 }
11773
11775 RemoveItemDurations(pItem);
11776
11777 pItem->SetOwnerGUID(GetGUID()); // prevent error at next SetState in case trade/mail/buy from vendor
11778 pItem->SetNotRefundable(this);
11779 pItem->ClearSoulboundTradeable(this);
11780 RemoveTradeableItem(pItem);
11781 pItem->SetState(ITEM_REMOVED, this);
11782 pItem2->SetState(ITEM_CHANGED, this);
11783
11784 ApplyEquipCooldown(pItem2);
11785
11786 return pItem2;
11787 }
11788
11789 if (slot == EQUIPMENT_SLOT_MAINHAND || slot == EQUIPMENT_SLOT_OFFHAND)
11791
11792 // only for full equip instead adding to stack
11795
11797
11798 return pItem;
11799}
11800
11801void Player::EquipChildItem(uint8 parentBag, uint8 parentSlot, Item* parentItem)
11802{
11803 if (ItemChildEquipmentEntry const* itemChildEquipment = sDB2Manager.GetItemChildEquipment(parentItem->GetEntry()))
11804 {
11805 if (Item* childItem = GetChildItemByGuid(parentItem->GetChildItem()))
11806 {
11807 uint16 childDest = (INVENTORY_SLOT_BAG_0 << 8) | itemChildEquipment->ChildItemEquipSlot;
11808 if (childItem->GetPos() != childDest)
11809 {
11810 Item* dstItem = GetItemByPos(childDest);
11811 if (!dstItem) // empty slot, simple case
11812 {
11813 RemoveItem(childItem->GetBagSlot(), childItem->GetSlot(), true);
11814 EquipItem(childDest, childItem, true);
11816 }
11817 else // have currently equipped item, not simple case
11818 {
11819 uint8 dstbag = dstItem->GetBagSlot();
11820 uint8 dstslot = dstItem->GetSlot();
11821
11822 InventoryResult msg = CanUnequipItem(childDest, !childItem->IsBag());
11823 if (msg != EQUIP_ERR_OK)
11824 {
11825 SendEquipError(msg, dstItem);
11826 return;
11827 }
11828
11829 // check dest->src move possibility but try to store currently equipped item in the bag where the parent item is
11830 ItemPosCountVec sSrc;
11831 uint16 eSrc = 0;
11832 if (IsInventoryPos(parentBag, parentSlot))
11833 {
11834 msg = CanStoreItem(parentBag, NULL_SLOT, sSrc, dstItem, true);
11835 if (msg != EQUIP_ERR_OK)
11836 msg = CanStoreItem(NULL_BAG, NULL_SLOT, sSrc, dstItem, true);
11837 }
11838 else if (IsBankPos(parentBag, parentSlot))
11839 {
11840 msg = CanBankItem(parentBag, NULL_SLOT, sSrc, dstItem, true);
11841 if (msg != EQUIP_ERR_OK)
11842 msg = CanBankItem(NULL_BAG, NULL_SLOT, sSrc, dstItem, true);
11843 }
11844 else if (IsEquipmentPos(parentBag, parentSlot))
11845 {
11846 msg = CanEquipItem(parentSlot, eSrc, dstItem, true);
11847 if (msg == EQUIP_ERR_OK)
11848 msg = CanUnequipItem(eSrc, true);
11849 }
11850
11851 if (msg != EQUIP_ERR_OK)
11852 {
11853 SendEquipError(msg, dstItem, childItem);
11854 return;
11855 }
11856
11857 // now do moves, remove...
11858 RemoveItem(dstbag, dstslot, false);
11859 RemoveItem(childItem->GetBagSlot(), childItem->GetSlot(), false);
11860
11861 // add to dest
11862 EquipItem(childDest, childItem, true);
11863
11864 // add to src
11865 if (IsInventoryPos(parentBag, parentSlot))
11866 StoreItem(sSrc, dstItem, true);
11867 else if (IsBankPos(parentBag, parentSlot))
11868 BankItem(sSrc, dstItem, true);
11869 else if (IsEquipmentPos(parentBag, parentSlot))
11870 EquipItem(eSrc, dstItem, true);
11871
11873 }
11874 }
11875 }
11876 }
11877}
11878
11880{
11881 if (sDB2Manager.GetItemChildEquipment(parentItem->GetEntry()))
11882 {
11883 if (Item* childItem = GetChildItemByGuid(parentItem->GetChildItem()))
11884 {
11885 if (IsChildEquipmentPos(childItem->GetPos()))
11886 return;
11887
11888 ItemPosCountVec dest;
11889 uint32 count = childItem->GetCount();
11890 InventoryResult result = CanStoreItem_InInventorySlots(CHILD_EQUIPMENT_SLOT_START, CHILD_EQUIPMENT_SLOT_END, dest, childItem->GetTemplate(), count, false, childItem, NULL_BAG, NULL_SLOT);
11891 if (result != EQUIP_ERR_OK)
11892 return;
11893
11894 RemoveItem(childItem->GetBagSlot(), childItem->GetSlot(), true);
11895 StoreItem(dest, childItem, true);
11896 }
11897 }
11898}
11899
11901{
11902 if (pItem)
11903 {
11905 AddItemDurations(pItem);
11906
11907 uint8 slot = pos & 255;
11908 VisualizeItem(slot, pItem);
11909
11911
11912 if (IsInWorld())
11913 {
11914 pItem->AddToWorld();
11915 pItem->SendUpdateToPlayer(this);
11916 }
11917
11918 if (slot == EQUIPMENT_SLOT_MAINHAND || slot == EQUIPMENT_SLOT_OFFHAND)
11920
11923 }
11924}
11925
11927{
11928 auto itemField = m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::VisibleItems, slot);
11929 if (pItem)
11930 {
11931 SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemID), pItem->GetVisibleEntry(this));
11934 SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemVisual), pItem->GetVisibleItemVisual(this));
11935 }
11936 else
11937 {
11938 SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemID), 0);
11941 SetUpdateFieldValue(itemField.ModifyValue(&UF::VisibleItem::ItemVisual), 0);
11942 }
11943}
11944
11946{
11947 if (!pItem)
11948 return;
11949
11950 // check also BIND_ON_ACQUIRE and BIND_QUEST for .additem or .additemset case by GM (not binded at adding to inventory)
11951 if (pItem->GetBonding() == BIND_ON_EQUIP || pItem->GetBonding() == BIND_ON_ACQUIRE || pItem->GetBonding() == BIND_QUEST)
11952 {
11953 pItem->SetBinding(true);
11954 if (IsInWorld())
11956 }
11957
11958 TC_LOG_DEBUG("entities.player.items", "Player::SetVisibleItemSlot: Player '{}' ({}), Slot: {}, Item: {}",
11959 GetName(), GetGUID().ToString(), slot, pItem->GetEntry());
11960
11961 m_items[slot] = pItem;
11962 SetInvSlot(slot, pItem->GetGUID());
11963 pItem->SetContainedIn(GetGUID());
11964 pItem->SetOwnerGUID(GetGUID());
11965 pItem->SetSlot(slot);
11966 pItem->SetContainer(nullptr);
11967
11968 if (slot < EQUIPMENT_SLOT_END)
11969 SetVisibleItemSlot(slot, pItem);
11970
11971 pItem->SetState(ITEM_CHANGED, this);
11972}
11973
11974Item* Player::BankItem(ItemPosCountVec const& dest, Item* pItem, bool update)
11975{
11976 return StoreItem(dest, pItem, update);
11977}
11978
11979void Player::RemoveItem(uint8 bag, uint8 slot, bool update)
11980{
11981 // note: removeitem does not actually change the item
11982 // it only takes the item out of storage temporarily
11983 // note2: if removeitem is to be used for delinking
11984 // the item must be removed from the player's updatequeue
11985
11986 Item* pItem = GetItemByPos(bag, slot);
11987 if (pItem)
11988 {
11989 TC_LOG_DEBUG("entities.player.items", "Player::RemoveItem: Player '{}' ({}), Bag: {}, Slot: {}, Item: {}",
11990 GetName(), GetGUID().ToString(), bag, slot, pItem->GetEntry());
11991
11993 RemoveItemDurations(pItem);
11994 RemoveTradeableItem(pItem);
11995
11996 if (bag == INVENTORY_SLOT_BAG_0)
11997 {
11998 if (slot < REAGENT_BAG_SLOT_END)
11999 {
12000 // item set bonuses applied only at equip and removed at unequip, and still active for broken items
12001 ItemTemplate const* pProto = ASSERT_NOTNULL(pItem->GetTemplate());
12002 if (pProto->GetItemSet())
12003 RemoveItemsSetItem(this, pItem);
12004
12005 _ApplyItemMods(pItem, slot, false, update);
12006
12008
12009 // remove item dependent auras and casts (only weapon and armor slots)
12010 if (slot < PROFESSION_SLOT_END)
12011 {
12012 // update expertise
12013 if (slot == EQUIPMENT_SLOT_MAINHAND)
12014 {
12015 // clear main hand only enchantments
12016 for (uint32 enchantSlot = 0; enchantSlot < MAX_ENCHANTMENT_SLOT; ++enchantSlot)
12017 if (SpellItemEnchantmentEntry const* enchantment = sSpellItemEnchantmentStore.LookupEntry(pItem->GetEnchantmentId(EnchantmentSlot(enchantSlot))))
12018 if (enchantment->GetFlags().HasFlag(SpellItemEnchantmentFlags::MainhandOnly))
12019 pItem->ClearEnchantment(EnchantmentSlot(enchantSlot));
12020
12022 }
12023 else if (slot == EQUIPMENT_SLOT_OFFHAND)
12025 // update armor penetration - passive auras may need it
12026 switch (slot)
12027 {
12031 break;
12032 default:
12033 break;
12034 }
12035 }
12036 }
12037
12038 m_items[slot] = nullptr;
12040
12041 if (slot < EQUIPMENT_SLOT_END)
12042 {
12043 SetVisibleItemSlot(slot, nullptr);
12044 if (slot == EQUIPMENT_SLOT_MAINHAND || slot == EQUIPMENT_SLOT_OFFHAND)
12046 }
12047 }
12048 else if (Bag* pBag = GetBagByPos(bag))
12049 pBag->RemoveItem(slot, update);
12050
12052 // pItem->SetUInt64Value(ITEM_FIELD_OWNER, 0); not clear owner at remove (it will be set at store). This used in mail and auction code
12053 pItem->SetSlot(NULL_SLOT);
12054 if (IsInWorld() && update)
12055 pItem->SendUpdateToPlayer(this);
12056
12057 AutoUnequipChildItem(pItem);
12058
12059 if (bag == INVENTORY_SLOT_BAG_0)
12061 }
12062}
12063
12064// Common operation need to remove item from inventory without delete in trade, auction, guild bank, mail....
12065void Player::MoveItemFromInventory(uint8 bag, uint8 slot, bool update)
12066{
12067 if (Item* it = GetItemByPos(bag, slot))
12068 {
12069 RemoveItem(bag, slot, update);
12070 ItemRemovedQuestCheck(it->GetEntry(), it->GetCount());
12071 it->SetNotRefundable(this, false, nullptr, false);
12074 if (it->IsInWorld())
12075 {
12076 it->RemoveFromWorld();
12077 it->DestroyForPlayer(this);
12078 }
12079 }
12080}
12081
12082// Common operation need to add item from inventory without delete in trade, guild bank, mail....
12083void Player::MoveItemToInventory(ItemPosCountVec const& dest, Item* pItem, bool update, bool in_characterInventoryDB)
12084{
12085 uint32 itemId = pItem->GetEntry();
12086 uint32 count = pItem->GetCount();
12087
12088 // store item
12089 Item* pLastItem = StoreItem(dest, pItem, update);
12090
12091 // only set if not merged to existing stack (pItem can be deleted already but we can compare pointers any way)
12092 if (pLastItem == pItem)
12093 {
12094 // update owner for last item (this can be original item with wrong owner
12095 if (pLastItem->GetOwnerGUID() != GetGUID())
12096 pLastItem->SetOwnerGUID(GetGUID());
12097
12098 // if this original item then it need create record in inventory
12099 // in case trade we already have item in other player inventory
12100 pLastItem->SetState(in_characterInventoryDB ? ITEM_CHANGED : ITEM_NEW, this);
12101
12102 if (pLastItem->IsBOPTradeable())
12103 AddTradeableItem(pLastItem);
12104 }
12105
12106 // update quest counters
12107 ItemAddedQuestCheck(itemId, count);
12109}
12110
12111void Player::DestroyItem(uint8 bag, uint8 slot, bool update)
12112{
12113 Item* pItem = GetItemByPos(bag, slot);
12114 if (pItem)
12115 {
12116 TC_LOG_DEBUG("entities.player.items", "Player::DestroyItem: Player '{}' ({}), Bag: {}, Slot: {}, Item: {}",
12117 GetName(), GetGUID().ToString(), bag, slot, pItem->GetEntry());
12118 // Also remove all contained items if the item is a bag.
12119 // This if () prevents item saving crashes if the condition for a bag to be empty before being destroyed was bypassed somehow.
12120 if (pItem->IsNotEmptyBag())
12121 for (uint8 i = 0; i < MAX_BAG_SIZE; ++i)
12122 DestroyItem(slot, i, update);
12123
12124 if (pItem->IsWrapped())
12125 {
12127
12128 stmt->setUInt64(0, pItem->GetGUID().GetCounter());
12129
12130 CharacterDatabase.Execute(stmt);
12131 }
12132
12134 RemoveItemDurations(pItem);
12135
12136 pItem->SetNotRefundable(this);
12137 pItem->ClearSoulboundTradeable(this);
12138 RemoveTradeableItem(pItem);
12139
12140 ApplyItemObtainSpells(pItem, false);
12141 ApplyItemLootedSpell(pItem, false);
12142
12143 sScriptMgr->OnItemRemove(this, pItem);
12144
12145 ItemTemplate const* pProto = pItem->GetTemplate();
12146 if (bag == INVENTORY_SLOT_BAG_0)
12147 {
12149
12150 // equipment and equipped bags can have applied bonuses
12151 if (slot < REAGENT_BAG_SLOT_END)
12152 {
12153 // item set bonuses applied only at equip and removed at unequip, and still active for broken items
12154 if (pProto->GetItemSet())
12155 RemoveItemsSetItem(this, pItem);
12156
12157 _ApplyItemMods(pItem, slot, false);
12158 }
12159
12160 if (slot < EQUIPMENT_SLOT_END)
12161 {
12162 // update expertise and armor penetration - passive auras may need it
12163 switch (slot)
12164 {
12168 break;
12169 default:
12170 break;
12171 }
12172
12173 if (slot == EQUIPMENT_SLOT_MAINHAND)
12175 else if (slot == EQUIPMENT_SLOT_OFFHAND)
12177
12178 // equipment visual show
12179 SetVisibleItemSlot(slot, nullptr);
12180 }
12181
12182 m_items[slot] = nullptr;
12183 }
12184 else if (Bag* pBag = GetBagByPos(bag))
12185 pBag->RemoveItem(slot, update);
12186
12187 // Delete rolled money / loot from db.
12188 // MUST be done before RemoveFromWorld() or GetTemplate() fails
12189 if (pProto->HasFlag(ITEM_FLAG_HAS_LOOT))
12190 sLootItemStorage->RemoveStoredLootForContainer(pItem->GetGUID().GetCounter());
12191
12192 ItemRemovedQuestCheck(pItem->GetEntry(), pItem->GetCount());
12193
12194 if (IsInWorld() && update)
12195 {
12196 pItem->RemoveFromWorld();
12197 pItem->DestroyForPlayer(this);
12198 }
12199
12200 //pItem->SetOwnerGUID(0);
12202 pItem->SetSlot(NULL_SLOT);
12203 pItem->SetState(ITEM_REMOVED, this);
12204
12205 if (pProto->GetInventoryType() != INVTYPE_NON_EQUIP)
12207 if (bag == INVENTORY_SLOT_BAG_0)
12209 }
12210}
12211
12212uint32 Player::DestroyItemCount(uint32 itemEntry, uint32 count, bool update, bool unequip_check)
12213{
12214 TC_LOG_DEBUG("entities.player.items", "Player::DestroyItemCount: Player '{}' ({}), Item: {}, Count: {}",
12215 GetName(), GetGUID().ToString(), itemEntry, count);
12216 uint32 remcount = 0;
12217
12218 // in inventory
12220 for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; ++i)
12221 {
12222 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
12223 {
12224 if (item->GetEntry() == itemEntry && !item->IsInTrade())
12225 {
12226 if (item->GetCount() + remcount <= count)
12227 {
12228 // all items in inventory can unequipped
12229 remcount += item->GetCount();
12231
12232 if (remcount >= count)
12233 return remcount;
12234 }
12235 else
12236 {
12237 item->SetCount(item->GetCount() - count + remcount);
12238 ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
12239 if (IsInWorld() && update)
12240 item->SendUpdateToPlayer(this);
12241 item->SetState(ITEM_CHANGED, this);
12242 return count;
12243 }
12244 }
12245 }
12246 }
12247
12248 // in inventory bags
12250 {
12251 if (Bag* bag = GetBagByPos(i))
12252 {
12253 for (uint32 j = 0; j < bag->GetBagSize(); j++)
12254 {
12255 if (Item* item = bag->GetItemByPos(j))
12256 {
12257 if (item->GetEntry() == itemEntry && !item->IsInTrade())
12258 {
12259 // all items in bags can be unequipped
12260 if (item->GetCount() + remcount <= count)
12261 {
12262 remcount += item->GetCount();
12263 DestroyItem(i, j, update);
12264
12265 if (remcount >= count)
12266 return remcount;
12267 }
12268 else
12269 {
12270 item->SetCount(item->GetCount() - count + remcount);
12271 ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
12272 if (IsInWorld() && update)
12273 item->SendUpdateToPlayer(this);
12274 item->SetState(ITEM_CHANGED, this);
12275 return count;
12276 }
12277 }
12278 }
12279 }
12280 }
12281 }
12282
12283 // in equipment and bag list
12285 {
12286 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
12287 {
12288 if (item->GetEntry() == itemEntry && !item->IsInTrade())
12289 {
12290 if (item->GetCount() + remcount <= count)
12291 {
12292 if (!unequip_check || CanUnequipItem(INVENTORY_SLOT_BAG_0 << 8 | i, false) == EQUIP_ERR_OK)
12293 {
12294 remcount += item->GetCount();
12296
12297 if (remcount >= count)
12298 return remcount;
12299 }
12300 }
12301 else
12302 {
12303 item->SetCount(item->GetCount() - count + remcount);
12304 ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
12305 if (IsInWorld() && update)
12306 item->SendUpdateToPlayer(this);
12307 item->SetState(ITEM_CHANGED, this);
12308 return count;
12309 }
12310 }
12311 }
12312 }
12313
12314 // in bank
12316 {
12317 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
12318 {
12319 if (item->GetEntry() == itemEntry && !item->IsInTrade())
12320 {
12321 if (item->GetCount() + remcount <= count)
12322 {
12323 remcount += item->GetCount();
12325 if (remcount >= count)
12326 return remcount;
12327 }
12328 else
12329 {
12330 item->SetCount(item->GetCount() - count + remcount);
12331 ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
12332 if (IsInWorld() && update)
12333 item->SendUpdateToPlayer(this);
12334 item->SetState(ITEM_CHANGED, this);
12335 return count;
12336 }
12337 }
12338 }
12339 }
12340
12341 // in bank bags
12342 for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++)
12343 {
12344 if (Bag* bag = GetBagByPos(i))
12345 {
12346 for (uint32 j = 0; j < bag->GetBagSize(); j++)
12347 {
12348 if (Item* item = bag->GetItemByPos(j))
12349 {
12350 if (item->GetEntry() == itemEntry && !item->IsInTrade())
12351 {
12352 // all items in bags can be unequipped
12353 if (item->GetCount() + remcount <= count)
12354 {
12355 remcount += item->GetCount();
12356 DestroyItem(i, j, update);
12357
12358 if (remcount >= count)
12359 return remcount;
12360 }
12361 else
12362 {
12363 item->SetCount(item->GetCount() - count + remcount);
12364 ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
12365 if (IsInWorld() && update)
12366 item->SendUpdateToPlayer(this);
12367 item->SetState(ITEM_CHANGED, this);
12368 return count;
12369 }
12370 }
12371 }
12372 }
12373 }
12374 }
12375
12376 // in bank bag list
12377 for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++)
12378 {
12379 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
12380 {
12381 if (item->GetEntry() == itemEntry && !item->IsInTrade())
12382 {
12383 if (item->GetCount() + remcount <= count)
12384 {
12385 if (!unequip_check || CanUnequipItem(INVENTORY_SLOT_BAG_0 << 8 | i, false) == EQUIP_ERR_OK)
12386 {
12387 remcount += item->GetCount();
12389 if (remcount >= count)
12390 return remcount;
12391 }
12392 }
12393 else
12394 {
12395 item->SetCount(item->GetCount() - count + remcount);
12396 ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
12397 if (IsInWorld() && update)
12398 item->SendUpdateToPlayer(this);
12399 item->SetState(ITEM_CHANGED, this);
12400 return count;
12401 }
12402 }
12403 }
12404 }
12405
12406 for (uint8 i = REAGENT_SLOT_START; i < REAGENT_SLOT_END; ++i)
12407 {
12408 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
12409 {
12410 if (item->GetEntry() == itemEntry && !item->IsInTrade())
12411 {
12412 if (item->GetCount() + remcount <= count)
12413 {
12414 // all keys can be unequipped
12415 remcount += item->GetCount();
12417
12418 if (remcount >= count)
12419 return remcount;
12420 }
12421 else
12422 {
12423 item->SetCount(item->GetCount() - count + remcount);
12424 ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
12425 if (IsInWorld() && update)
12426 item->SendUpdateToPlayer(this);
12427 item->SetState(ITEM_CHANGED, this);
12428 return count;
12429 }
12430 }
12431 }
12432 }
12433
12435 {
12436 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
12437 {
12438 if (item->GetEntry() == itemEntry && !item->IsInTrade())
12439 {
12440 if (item->GetCount() + remcount <= count)
12441 {
12442 // all keys can be unequipped
12443 remcount += item->GetCount();
12445
12446 if (remcount >= count)
12447 return remcount;
12448 }
12449 else
12450 {
12451 item->SetCount(item->GetCount() - count + remcount);
12452 ItemRemovedQuestCheck(item->GetEntry(), count - remcount);
12453 if (IsInWorld() && update)
12454 item->SendUpdateToPlayer(this);
12455 item->SetState(ITEM_CHANGED, this);
12456 return count;
12457 }
12458 }
12459 }
12460 }
12461 return remcount;
12462}
12463
12464void Player::DestroyZoneLimitedItem(bool update, uint32 new_zone)
12465{
12466 TC_LOG_DEBUG("entities.player.items", "Player::DestroyZoneLimitedItem: In map {} and area {} for player '{}' ({})",
12467 GetMapId(), new_zone, GetName(), GetGUID().ToString());
12468
12469 // in inventory
12471 for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; i++)
12472 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
12473 if (pItem->IsLimitedToAnotherMapOrZone(GetMapId(), new_zone))
12475
12476 // in inventory bags
12478 if (Bag* pBag = GetBagByPos(i))
12479 for (uint32 j = 0; j < pBag->GetBagSize(); j++)
12480 if (Item* pItem = pBag->GetItemByPos(j))
12481 if (pItem->IsLimitedToAnotherMapOrZone(GetMapId(), new_zone))
12482 DestroyItem(i, j, update);
12483
12484 // in equipment and bag list
12486 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
12487 if (pItem->IsLimitedToAnotherMapOrZone(GetMapId(), new_zone))
12489}
12490
12492{
12493 // used when entering arena
12494 // destroys all conjured items
12495 TC_LOG_DEBUG("entities.player.items", "Player::DestroyConjuredItems: Player '{}' ({})",
12496 GetName(), GetGUID().ToString());
12497
12498 // in inventory
12500 for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; i++)
12501 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
12502 if (pItem->IsConjuredConsumable())
12504
12505 // in inventory bags
12507 if (Bag* pBag = GetBagByPos(i))
12508 for (uint32 j = 0; j < pBag->GetBagSize(); j++)
12509 if (Item* pItem = pBag->GetItemByPos(j))
12510 if (pItem->IsConjuredConsumable())
12511 DestroyItem(i, j, update);
12512
12513 // in equipment and bag list
12515 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
12516 if (pItem->IsConjuredConsumable())
12518}
12519
12520Item* Player::GetItemByEntry(uint32 entry, ItemSearchLocation where /*= ItemSearchLocation::Default */) const
12521{
12522 Item* result = nullptr;
12523 ForEachItem(where, [&result, entry](Item* item)
12524 {
12525 if (item->GetEntry() == entry)
12526 {
12527 result = item;
12528 return ItemSearchCallbackResult::Stop;
12529 }
12530
12532 });
12533 return result;
12534}
12535
12536std::vector<Item*> Player::GetItemListByEntry(uint32 entry, bool inBankAlso) const
12537{
12539 if (inBankAlso)
12540 location |= ItemSearchLocation::Bank;
12541
12542 std::vector<Item*> itemList = std::vector<Item*>();
12543 ForEachItem(location, [&itemList, entry](Item* item)
12544 {
12545 if (item->GetEntry() == entry)
12546 itemList.push_back(item);
12547
12549 });
12550 return itemList;
12551}
12552
12553void Player::DestroyItemCount(Item* pItem, uint32 &count, bool update)
12554{
12555 if (!pItem)
12556 return;
12557
12558 TC_LOG_DEBUG("entities.player.items", "Player::DestroyItemCount: Player '{}' ({}), Item ({}, Entry: {}), Count: {}",
12559 GetName(), GetGUID().ToString(), pItem->GetGUID().ToString(), pItem->GetEntry(), count);
12560
12561 if (pItem->GetCount() <= count)
12562 {
12563 count -= pItem->GetCount();
12564
12565 DestroyItem(pItem->GetBagSlot(), pItem->GetSlot(), update);
12566 }
12567 else
12568 {
12569 pItem->SetCount(pItem->GetCount() - count);
12570 ItemRemovedQuestCheck(pItem->GetEntry(), count);
12571 count = 0;
12572 if (IsInWorld() && update)
12573 pItem->SendUpdateToPlayer(this);
12574 pItem->SetState(ITEM_CHANGED, this);
12575 }
12576}
12577
12579{
12580 uint8 srcbag = src >> 8;
12581 uint8 srcslot = src & 255;
12582
12583 uint8 dstbag = dst >> 8;
12584 uint8 dstslot = dst & 255;
12585
12586 Item* pSrcItem = GetItemByPos(srcbag, srcslot);
12587 if (!pSrcItem)
12588 {
12589 SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pSrcItem, nullptr);
12590 return;
12591 }
12592
12593 if (pSrcItem->m_lootGenerated) // prevent split looting item (item
12594 {
12595 //best error message found for attempting to split while looting
12596 SendEquipError(EQUIP_ERR_SPLIT_FAILED, pSrcItem, nullptr);
12597 return;
12598 }
12599
12600 // not let split all items (can be only at cheating)
12601 if (pSrcItem->GetCount() == count)
12602 {
12603 SendEquipError(EQUIP_ERR_SPLIT_FAILED, pSrcItem, nullptr);
12604 return;
12605 }
12606
12607 // not let split more existing items (can be only at cheating)
12608 if (pSrcItem->GetCount() < count)
12609 {
12610 SendEquipError(EQUIP_ERR_TOO_FEW_TO_SPLIT, pSrcItem, nullptr);
12611 return;
12612 }
12613
12615 if (TradeData* tradeData = GetTradeData())
12616 {
12618 if (tradeData->GetTradeSlotForItem(pSrcItem->GetGUID()) != TRADE_SLOT_INVALID)
12619 return;
12620 }
12621
12622 TC_LOG_DEBUG("entities.player.items", "Player::SplitItem: Player '{}' ({}), Bag: {}, Slot: {}, Item: {}, Count: {}",
12623 GetName(), GetGUID().ToString(), dstbag, dstslot, pSrcItem->GetEntry(), count);
12624 Item* pNewItem = pSrcItem->CloneItem(count, this);
12625 if (!pNewItem)
12626 {
12627 SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pSrcItem, nullptr);
12628 return;
12629 }
12630
12631 if (IsInventoryPos(dst))
12632 {
12633 // change item amount before check (for unique max count check)
12634 pSrcItem->SetCount(pSrcItem->GetCount() - count);
12635
12636 ItemPosCountVec dest;
12637 InventoryResult msg = CanStoreItem(dstbag, dstslot, dest, pNewItem, false);
12638 if (msg != EQUIP_ERR_OK)
12639 {
12640 delete pNewItem;
12641 pSrcItem->SetCount(pSrcItem->GetCount() + count);
12642 SendEquipError(msg, pSrcItem, nullptr);
12643 return;
12644 }
12645
12646 if (IsInWorld())
12647 pSrcItem->SendUpdateToPlayer(this);
12648 pSrcItem->SetState(ITEM_CHANGED, this);
12649 StoreItem(dest, pNewItem, true);
12650 }
12651 else if (IsBankPos(dst))
12652 {
12653 // change item amount before check (for unique max count check)
12654 pSrcItem->SetCount(pSrcItem->GetCount() - count);
12655
12656 ItemPosCountVec dest;
12657 InventoryResult msg = CanBankItem(dstbag, dstslot, dest, pNewItem, false);
12658 if (msg != EQUIP_ERR_OK)
12659 {
12660 delete pNewItem;
12661 pSrcItem->SetCount(pSrcItem->GetCount() + count);
12662 SendEquipError(msg, pSrcItem, nullptr);
12663 return;
12664 }
12665
12666 if (IsInWorld())
12667 pSrcItem->SendUpdateToPlayer(this);
12668 pSrcItem->SetState(ITEM_CHANGED, this);
12669 BankItem(dest, pNewItem, true);
12670 }
12671 else if (IsEquipmentPos(dst))
12672 {
12673 // change item amount before check (for unique max count check), provide space for splitted items
12674 pSrcItem->SetCount(pSrcItem->GetCount() - count);
12675
12676 uint16 dest;
12677 InventoryResult msg = CanEquipItem(dstslot, dest, pNewItem, false);
12678 if (msg != EQUIP_ERR_OK)
12679 {
12680 delete pNewItem;
12681 pSrcItem->SetCount(pSrcItem->GetCount() + count);
12682 SendEquipError(msg, pSrcItem, nullptr);
12683 return;
12684 }
12685
12686 if (IsInWorld())
12687 pSrcItem->SendUpdateToPlayer(this);
12688 pSrcItem->SetState(ITEM_CHANGED, this);
12689 EquipItem(dest, pNewItem, true);
12691 }
12692}
12693
12695{
12696 uint8 srcbag = src >> 8;
12697 uint8 srcslot = src & 255;
12698
12699 uint8 dstbag = dst >> 8;
12700 uint8 dstslot = dst & 255;
12701
12702 Item* pSrcItem = GetItemByPos(srcbag, srcslot);
12703 Item* pDstItem = GetItemByPos(dstbag, dstslot);
12704
12705 if (!pSrcItem)
12706 return;
12707
12708 if (pSrcItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD))
12709 {
12710 if (Item* parentItem = GetItemByGuid(pSrcItem->m_itemData->Creator))
12711 {
12712 if (IsEquipmentPos(src))
12713 {
12714 AutoUnequipChildItem(parentItem); // we need to unequip child first since it cannot go into whatever is going to happen next
12715 SwapItem(dst, src); // src is now empty
12716 SwapItem(parentItem->GetPos(), dst);// dst is now empty
12717 return;
12718 }
12719 }
12720 }
12721 else if (pDstItem && pDstItem->HasItemFlag(ITEM_FIELD_FLAG_CHILD))
12722 {
12723 if (Item* parentItem = GetItemByGuid(pDstItem->m_itemData->Creator))
12724 {
12725 if (IsEquipmentPos(dst))
12726 {
12727 AutoUnequipChildItem(parentItem); // we need to unequip child first since it cannot go into whatever is going to happen next
12728 SwapItem(src, dst); // dst is now empty
12729 SwapItem(parentItem->GetPos(), src);// src is now empty
12730 return;
12731 }
12732 }
12733 }
12734
12735 TC_LOG_DEBUG("entities.player.items", "Player::SwapItem: Player '{}' ({}), Bag: {}, Slot: {}, Item: {}",
12736 GetName(), GetGUID().ToString(), dstbag, dstslot, pSrcItem->GetEntry());
12737
12738 if (!IsAlive())
12739 {
12740 SendEquipError(EQUIP_ERR_PLAYER_DEAD, pSrcItem, pDstItem);
12741 return;
12742 }
12743
12744 // SRC checks
12745
12746 // check unequip potability for equipped items and bank bags
12747 if (IsEquipmentPos(src) || IsBagPos(src))
12748 {
12749 // bags can be swapped with empty bag slots, or with empty bag (items move possibility checked later)
12750 InventoryResult msg = CanUnequipItem(src, !IsBagPos(src) || IsBagPos(dst) || (pDstItem && pDstItem->ToBag() && pDstItem->ToBag()->IsEmpty()));
12751 if (msg != EQUIP_ERR_OK)
12752 {
12753 SendEquipError(msg, pSrcItem, pDstItem);
12754 return;
12755 }
12756 }
12757
12758 // prevent put equipped/bank bag in self
12759 if (IsBagPos(src) && srcslot == dstbag)
12760 {
12761 SendEquipError(EQUIP_ERR_BAG_IN_BAG, pSrcItem, pDstItem);
12762 return;
12763 }
12764
12765 // prevent equipping bag in the same slot from its inside
12766 if (IsBagPos(dst) && srcbag == dstslot)
12767 {
12768 SendEquipError(EQUIP_ERR_CANT_SWAP, pSrcItem, pDstItem);
12769 return;
12770 }
12771
12772 // DST checks
12773 if (pDstItem)
12774 {
12775 // check unequip potability for equipped items and bank bags
12776 if (IsEquipmentPos(dst) || IsBagPos(dst))
12777 {
12778 // bags can be swapped with empty bag slots, or with empty bag (items move possibility checked later)
12779 InventoryResult msg = CanUnequipItem(dst, !IsBagPos(dst) || IsBagPos(src) || (pSrcItem->ToBag() && pSrcItem->ToBag()->IsEmpty()));
12780 if (msg != EQUIP_ERR_OK)
12781 {
12782 SendEquipError(msg, pSrcItem, pDstItem);
12783 return;
12784 }
12785 }
12786 }
12787
12789 {
12790 SendEquipError(EQUIP_ERR_REAGENT_BANK_LOCKED, pSrcItem, pDstItem);
12791 return;
12792 }
12793
12794 // NOW this is or item move (swap with empty), or swap with another item (including bags in bag possitions)
12795 // or swap empty bag with another empty or not empty bag (with items exchange)
12796
12797 // Move case
12798 if (!pDstItem)
12799 {
12800 if (IsInventoryPos(dst))
12801 {
12802 ItemPosCountVec dest;
12803 InventoryResult msg = CanStoreItem(dstbag, dstslot, dest, pSrcItem, false);
12804 if (msg != EQUIP_ERR_OK)
12805 {
12806 SendEquipError(msg, pSrcItem, nullptr);
12807 return;
12808 }
12809
12810 RemoveItem(srcbag, srcslot, true);
12811 StoreItem(dest, pSrcItem, true);
12812 if (IsBankPos(src))
12813 ItemAddedQuestCheck(pSrcItem->GetEntry(), pSrcItem->GetCount());
12814 }
12815 else if (IsBankPos(dst))
12816 {
12817 ItemPosCountVec dest;
12818 InventoryResult msg = CanBankItem(dstbag, dstslot, dest, pSrcItem, false);
12819 if (msg != EQUIP_ERR_OK)
12820 {
12821 SendEquipError(msg, pSrcItem, nullptr);
12822 return;
12823 }
12824
12825 RemoveItem(srcbag, srcslot, true);
12826 BankItem(dest, pSrcItem, true);
12827 if (!IsReagentBankPos(dst))
12828 ItemRemovedQuestCheck(pSrcItem->GetEntry(), pSrcItem->GetCount());
12829 }
12830 else if (IsEquipmentPos(dst))
12831 {
12832 uint16 dest;
12833 InventoryResult msg = CanEquipItem(dstslot, dest, pSrcItem, false);
12834 if (msg != EQUIP_ERR_OK)
12835 {
12836 SendEquipError(msg, pSrcItem, nullptr);
12837 return;
12838 }
12839
12840 RemoveItem(srcbag, srcslot, true);
12841 EquipItem(dest, pSrcItem, true);
12843 }
12844
12845 return;
12846 }
12847
12848 // attempt merge to / fill target item
12849 if (!pSrcItem->IsBag() && !pDstItem->IsBag())
12850 {
12851 InventoryResult msg;
12852 ItemPosCountVec sDest;
12853 uint16 eDest = 0;
12854 if (IsInventoryPos(dst))
12855 msg = CanStoreItem(dstbag, dstslot, sDest, pSrcItem, false);
12856 else if (IsBankPos(dst))
12857 msg = CanBankItem(dstbag, dstslot, sDest, pSrcItem, false);
12858 else if (IsEquipmentPos(dst))
12859 msg = CanEquipItem(dstslot, eDest, pSrcItem, false);
12860 else
12861 return;
12862
12863 if (msg == EQUIP_ERR_OK && IsEquipmentPos(dst) && !pSrcItem->GetChildItem().IsEmpty())
12864 msg = CanEquipChildItem(pSrcItem);
12865
12866 // can be merge/fill
12867 if (msg == EQUIP_ERR_OK)
12868 {
12869 if (pSrcItem->GetCount() + pDstItem->GetCount() <= pSrcItem->GetTemplate()->GetMaxStackSize())
12870 {
12871 RemoveItem(srcbag, srcslot, true);
12872
12873 if (IsInventoryPos(dst))
12874 StoreItem(sDest, pSrcItem, true);
12875 else if (IsBankPos(dst))
12876 BankItem(sDest, pSrcItem, true);
12877 else if (IsEquipmentPos(dst))
12878 {
12879 EquipItem(eDest, pSrcItem, true);
12880 if (!pSrcItem->GetChildItem().IsEmpty())
12881 EquipChildItem(srcbag, srcslot, pSrcItem);
12882
12884 }
12885 }
12886 else
12887 {
12888 pSrcItem->SetCount(pSrcItem->GetCount() + pDstItem->GetCount() - pSrcItem->GetTemplate()->GetMaxStackSize());
12889 pDstItem->SetCount(pSrcItem->GetTemplate()->GetMaxStackSize());
12890 pSrcItem->SetState(ITEM_CHANGED, this);
12891 pDstItem->SetState(ITEM_CHANGED, this);
12892 if (IsInWorld())
12893 {
12894 pSrcItem->SendUpdateToPlayer(this);
12895 pDstItem->SendUpdateToPlayer(this);
12896 }
12897 }
12898 SendRefundInfo(pDstItem);
12899 return;
12900 }
12901 }
12902
12903 // impossible merge/fill, do real swap
12905
12906 // check src->dest move possibility
12907 ItemPosCountVec sDest;
12908 uint16 eDest = 0;
12909 if (IsInventoryPos(dst))
12910 msg = CanStoreItem(dstbag, dstslot, sDest, pSrcItem, true);
12911 else if (IsBankPos(dst))
12912 msg = CanBankItem(dstbag, dstslot, sDest, pSrcItem, true);
12913 else if (IsEquipmentPos(dst))
12914 {
12915 msg = CanEquipItem(dstslot, eDest, pSrcItem, true);
12916 if (msg == EQUIP_ERR_OK)
12917 msg = CanUnequipItem(eDest, true);
12918 }
12919
12920 if (msg != EQUIP_ERR_OK)
12921 {
12922 SendEquipError(msg, pSrcItem, pDstItem);
12923 return;
12924 }
12925
12926 // check dest->src move possibility
12927 ItemPosCountVec sDest2;
12928 uint16 eDest2 = 0;
12929 if (IsInventoryPos(src))
12930 msg = CanStoreItem(srcbag, srcslot, sDest2, pDstItem, true);
12931 else if (IsBankPos(src))
12932 msg = CanBankItem(srcbag, srcslot, sDest2, pDstItem, true);
12933 else if (IsEquipmentPos(src))
12934 {
12935 msg = CanEquipItem(srcslot, eDest2, pDstItem, true);
12936 if (msg == EQUIP_ERR_OK)
12937 msg = CanUnequipItem(eDest2, true);
12938 }
12939
12940 if (msg == EQUIP_ERR_OK && IsEquipmentPos(dst) && !pSrcItem->GetChildItem().IsEmpty())
12941 msg = CanEquipChildItem(pSrcItem);
12942
12943 if (msg != EQUIP_ERR_OK)
12944 {
12945 SendEquipError(msg, pDstItem, pSrcItem);
12946 return;
12947 }
12948
12949 // Check bag swap with item exchange (one from empty in not bag possition (equipped (not possible in fact) or store)
12950 if (Bag* srcBag = pSrcItem->ToBag())
12951 {
12952 if (Bag* dstBag = pDstItem->ToBag())
12953 {
12954 Bag* emptyBag = nullptr;
12955 Bag* fullBag = nullptr;
12956 if (srcBag->IsEmpty() && !IsBagPos(src))
12957 {
12958 emptyBag = srcBag;
12959 fullBag = dstBag;
12960 }
12961 else if (dstBag->IsEmpty() && !IsBagPos(dst))
12962 {
12963 emptyBag = dstBag;
12964 fullBag = srcBag;
12965 }
12966
12967 // bag swap (with items exchange) case
12968 if (emptyBag && fullBag)
12969 {
12970 ItemTemplate const* emptyProto = emptyBag->GetTemplate();
12971
12972 uint32 count = 0;
12973
12974 for (uint32 i=0; i < fullBag->GetBagSize(); ++i)
12975 {
12976 Item* bagItem = fullBag->GetItemByPos(i);
12977 if (!bagItem)
12978 continue;
12979
12980 ItemTemplate const* bagItemProto = bagItem->GetTemplate();
12981 if (!bagItemProto || !ItemCanGoIntoBag(bagItemProto, emptyProto))
12982 {
12983 // one from items not go to empty target bag
12984 SendEquipError(EQUIP_ERR_BAG_IN_BAG, pSrcItem, pDstItem);
12985 return;
12986 }
12987
12988 ++count;
12989 }
12990
12991 if (count > emptyBag->GetBagSize())
12992 {
12993 // too small targeted bag
12994 SendEquipError(EQUIP_ERR_CANT_SWAP, pSrcItem, pDstItem);
12995 return;
12996 }
12997
12998 // Items swap
12999 count = 0; // will pos in new bag
13000 for (uint32 i = 0; i< fullBag->GetBagSize(); ++i)
13001 {
13002 Item* bagItem = fullBag->GetItemByPos(i);
13003 if (!bagItem)
13004 continue;
13005
13006 fullBag->RemoveItem(i, true);
13007 emptyBag->StoreItem(count, bagItem, true);
13008 bagItem->SetState(ITEM_CHANGED, this);
13009
13010 ++count;
13011 }
13012 }
13013 }
13014 }
13015
13016 // now do moves, remove...
13017 RemoveItem(dstbag, dstslot, false);
13018 RemoveItem(srcbag, srcslot, false);
13019
13020 // add to dest
13021 if (IsInventoryPos(dst))
13022 StoreItem(sDest, pSrcItem, true);
13023 else if (IsBankPos(dst))
13024 BankItem(sDest, pSrcItem, true);
13025 else if (IsEquipmentPos(dst))
13026 {
13027 EquipItem(eDest, pSrcItem, true);
13028 if (!pSrcItem->GetChildItem().IsEmpty())
13029 EquipChildItem(srcbag, srcslot, pSrcItem);
13030 }
13031
13032 // add to src
13033 if (IsInventoryPos(src))
13034 StoreItem(sDest2, pDstItem, true);
13035 else if (IsBankPos(src))
13036 BankItem(sDest2, pDstItem, true);
13037 else if (IsEquipmentPos(src))
13038 EquipItem(eDest2, pDstItem, true);
13039
13040 // if inventory item was moved, check if we can remove dependent auras, because they were not removed in Player::RemoveItem (update was set to false)
13041 // do this after swaps are done, we pass nullptr because both weapons could be swapped and none of them should be ignored
13042 if ((srcbag == INVENTORY_SLOT_BAG_0 && srcslot < REAGENT_BAG_SLOT_END) || (dstbag == INVENTORY_SLOT_BAG_0 && dstslot < REAGENT_BAG_SLOT_END))
13043 ApplyItemDependentAuras((Item*)nullptr, false);
13044
13045 // if player is moving bags and is looting an item inside this bag
13046 // release the loot
13047 if (!GetAELootView().empty())
13048 {
13049 bool released = false;
13050 if (IsBagPos(src))
13051 {
13052 Bag* bag = pSrcItem->ToBag();
13053 for (uint32 i = 0; i < bag->GetBagSize(); ++i)
13054 {
13055 if (Item* bagItem = bag->GetItemByPos(i))
13056 {
13057 if (GetLootByWorldObjectGUID(bagItem->GetGUID()))
13058 {
13060 released = true; // so we don't need to look at dstBag
13061 break;
13062 }
13063 }
13064 }
13065 }
13066
13067 if (!released && IsBagPos(dst))
13068 {
13069 Bag* bag = pDstItem->ToBag();
13070 for (uint32 i = 0; i < bag->GetBagSize(); ++i)
13071 {
13072 if (Item* bagItem = bag->GetItemByPos(i))
13073 {
13074 if (GetLootByWorldObjectGUID(bagItem->GetGUID()))
13075 {
13077 break;
13078 }
13079 }
13080 }
13081 }
13082 }
13083
13085}
13086
13088{
13089 if (pItem)
13090 {
13092 // if current back slot non-empty search oldest or free
13093 if (m_items[slot])
13094 {
13095 time_t oldest_time = m_activePlayerData->BuybackTimestamp[0];
13096 uint32 oldest_slot = BUYBACK_SLOT_START;
13097
13098 for (uint32 i = BUYBACK_SLOT_START + 1; i < BUYBACK_SLOT_END; ++i)
13099 {
13100 // found empty
13101 if (!m_items[i])
13102 {
13103 oldest_slot = i;
13104 break;
13105 }
13106
13107 time_t i_time = m_activePlayerData->BuybackTimestamp[i - BUYBACK_SLOT_START];
13108
13109 if (oldest_time > i_time)
13110 {
13111 oldest_time = i_time;
13112 oldest_slot = i;
13113 }
13114 }
13115
13116 // find oldest
13117 slot = oldest_slot;
13118 }
13119
13120 RemoveItemFromBuyBackSlot(slot, true);
13121 TC_LOG_DEBUG("entities.player.items", "Player::AddItemToBuyBackSlot: Player '{}' ({}), Item: {}, Slot: {}",
13122 GetName(), GetGUID().ToString(), pItem->GetEntry(), slot);
13123
13124 m_items[slot] = pItem;
13125 time_t base = GameTime::GetGameTime();
13126 uint32 etime = uint32(base - m_logintime + (30 * 3600));
13127 uint32 eslot = slot - BUYBACK_SLOT_START;
13128
13129 SetInvSlot(slot, pItem->GetGUID());
13130 SetBuybackPrice(eslot, pItem->GetSellPrice(this) * pItem->GetCount());
13131
13132 SetBuybackTimestamp(eslot, (uint32)etime);
13133
13134 // move to next (for non filled list is move most optimized choice)
13137 }
13138}
13139
13141{
13142 TC_LOG_DEBUG("entities.player.items", "Player::GetItemFromBuyBackSlot: Player '{}' ({}), Slot: {}",
13143 GetName(), GetGUID().ToString(), slot);
13144 if (slot >= BUYBACK_SLOT_START && slot < BUYBACK_SLOT_END)
13145 return m_items[slot];
13146 return nullptr;
13147}
13148
13150{
13151 TC_LOG_DEBUG("entities.player.items", "Player::RemoveItemFromBuyBackSlot: Player '{}' ({}), Slot: {}",
13152 GetName(), GetGUID().ToString(), slot);
13153 if (slot >= BUYBACK_SLOT_START && slot < BUYBACK_SLOT_END)
13154 {
13155 Item* pItem = m_items[slot];
13156 if (pItem)
13157 {
13158 pItem->RemoveFromWorld();
13159 if (del)
13160 {
13161 if (ItemTemplate const* itemTemplate = pItem->GetTemplate())
13162 if (itemTemplate->HasFlag(ITEM_FLAG_HAS_LOOT))
13163 sLootItemStorage->RemoveStoredLootForContainer(pItem->GetGUID().GetCounter());
13164
13165 pItem->SetState(ITEM_REMOVED, this);
13166 }
13167 }
13168
13169 m_items[slot] = nullptr;
13170
13171 uint32 eslot = slot - BUYBACK_SLOT_START;
13173 SetBuybackPrice(eslot, 0);
13174 SetBuybackTimestamp(eslot, 0);
13175
13176 // if current backslot is filled set to now free slot
13178 m_currentBuybackSlot = slot;
13179 }
13180}
13181
13182void Player::SendEquipError(InventoryResult msg, Item const* item1 /*= nullptr*/, Item const* item2 /*= nullptr*/, uint32 itemId /*= 0*/) const
13183{
13185 failure.BagResult = msg;
13186
13187 if (msg != EQUIP_ERR_OK)
13188 {
13189 if (item1)
13190 failure.Item[0] = item1->GetGUID();
13191
13192 if (item2)
13193 failure.Item[1] = item2->GetGUID();
13194
13195 failure.ContainerBSlot = 0; // bag equip slot, used with EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM and EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG2
13196
13197 switch (msg)
13198 {
13201 {
13202 failure.Level = uint32(item1 ? item1->GetRequiredLevel() : 0);
13203 break;
13204 }
13205 case EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM: // no idea about this one...
13206 {
13207 //failure.SrcContainer
13208 //failure.SrcSlot
13209 //failure.DstContainer
13210 break;
13211 }
13215 {
13216 ItemTemplate const* proto = item1 ? item1->GetTemplate() : sObjectMgr->GetItemTemplate(itemId);
13217 failure.LimitCategory = proto ? proto->GetItemLimitCategory() : 0;
13218 break;
13219 }
13220 default:
13221 break;
13222 }
13223 }
13224
13225 SendDirectMessage(failure.Write());
13226}
13227
13228void Player::SendBuyError(BuyResult msg, Creature* creature, uint32 item, uint32 /*param*/) const
13229{
13231 packet.VendorGUID = creature ? creature->GetGUID() : ObjectGuid::Empty;
13232 packet.Muid = item;
13233 packet.Reason = msg;
13234 SendDirectMessage(packet.Write());
13235}
13236
13237void Player::SendSellError(SellResult msg, Creature* creature, ObjectGuid guid) const
13238{
13240 sellResponse.VendorGUID = creature ? creature->GetGUID() : ObjectGuid::Empty;
13241 sellResponse.ItemGUIDs.push_back(guid);
13242 sellResponse.Reason = msg;
13243 SendDirectMessage(sellResponse.Write());
13244}
13245
13246bool Player::IsUseEquipedWeapon(bool mainhand) const
13247{
13248 // disarm applied only to mainhand weapon
13249 return !IsInFeralForm() && (!mainhand || !HasUnitFlag(UNIT_FLAG_DISARMED));
13250}
13251
13252void Player::SetCanTitanGrip(bool value, uint32 penaltySpellId /*= 0*/)
13253{
13254 if (value == m_canTitanGrip)
13255 return;
13256
13257 m_canTitanGrip = value;
13258 m_titanGripPenaltySpellId = penaltySpellId;
13259}
13260
13262{
13263 if (!CanTitanGrip())
13264 return;
13265
13267 if (apply)
13268 {
13270 CastSpell(nullptr, m_titanGripPenaltySpellId, true);
13271 }
13272 else
13274}
13275
13277{
13279 if (!mainItem)
13280 return false;
13281
13282 ItemTemplate const* itemTemplate = mainItem->GetTemplate();
13283 return (itemTemplate->GetInventoryType() == INVTYPE_2HWEAPON && !CanTitanGrip()) ||
13284 itemTemplate->GetInventoryType() == INVTYPE_RANGED ||
13285 (itemTemplate->GetInventoryType() == INVTYPE_RANGEDRIGHT && itemTemplate->GetClass() == ITEM_CLASS_WEAPON && itemTemplate->GetSubClass() != ITEM_SUBCLASS_WEAPON_WAND);
13286}
13287
13289{
13291 if (offItem && offItem->GetTemplate()->GetInventoryType() == INVTYPE_2HWEAPON)
13292 return true;
13293
13295 if (!mainItem || mainItem->GetTemplate()->GetInventoryType() != INVTYPE_2HWEAPON)
13296 return false;
13297
13298 if (!offItem)
13299 return false;
13300
13301 return true;
13302}
13303
13304void Player::TradeCancel(bool sendback)
13305{
13306 if (m_trade)
13307 {
13308 Player* trader = m_trade->GetTrader();
13309
13310 // send yellow "Trade canceled" message to both traders
13311 if (sendback)
13313
13314 trader->GetSession()->SendCancelTrade();
13315
13316 // cleanup
13317 delete m_trade;
13318 m_trade = nullptr;
13319 delete trader->m_trade;
13320 trader->m_trade = nullptr;
13321 }
13322}
13323
13325{
13326 // also checks for garbage data
13327 for (GuidUnorderedSet::iterator itr = m_itemSoulboundTradeable.begin(); itr != m_itemSoulboundTradeable.end();)
13328 {
13329 Item* item = GetItemByGuid(*itr);
13330 if (!item || item->GetOwnerGUID() != GetGUID() || item->CheckSoulboundTradeExpire())
13331 itr = m_itemSoulboundTradeable.erase(itr);
13332 else
13333 ++itr;
13334 }
13335}
13336
13338{
13339 m_itemSoulboundTradeable.insert(item->GetGUID());
13340}
13341
13343{
13344 m_itemSoulboundTradeable.erase(item->GetGUID());
13345}
13346
13347void Player::UpdateItemDuration(uint32 time, bool realtimeonly)
13348{
13349 if (m_itemDuration.empty())
13350 return;
13351
13352 TC_LOG_DEBUG("entities.player.items", "Player::UpdateItemDuration: Player '{}' ({}), Time: {}, RealTimeOnly: {}",
13353 GetName(), GetGUID().ToString(), time, realtimeonly);
13354
13355 for (ItemDurationList::const_iterator itr = m_itemDuration.begin(); itr != m_itemDuration.end();)
13356 {
13357 Item* item = *itr;
13358 ++itr; // current element can be erased in UpdateDuration
13359
13360 if (!realtimeonly || item->GetTemplate()->HasFlag(ITEM_FLAG_REAL_DURATION))
13361 item->UpdateDuration(this, time);
13362 }
13363}
13364
13366{
13367 for (EnchantDurationList::iterator itr = m_enchantDuration.begin(), next; itr != m_enchantDuration.end(); itr = next)
13368 {
13369 ASSERT(itr->item);
13370 next = itr;
13371 if (!itr->item->GetEnchantmentId(itr->slot))
13372 {
13373 next = m_enchantDuration.erase(itr);
13374 }
13375 else if (itr->leftduration <= time)
13376 {
13377 ApplyEnchantment(itr->item, itr->slot, false, false);
13378 itr->item->ClearEnchantment(itr->slot);
13379 next = m_enchantDuration.erase(itr);
13380 }
13381 else if (itr->leftduration > time)
13382 {
13383 itr->leftduration -= time;
13384 ++next;
13385 }
13386 }
13387}
13388
13390{
13391 for (int x = 0; x < MAX_ENCHANTMENT_SLOT; ++x)
13392 {
13393 if (!item->GetEnchantmentId(EnchantmentSlot(x)))
13394 continue;
13395
13396 uint32 duration = item->GetEnchantmentDuration(EnchantmentSlot(x));
13397 if (duration > 0)
13398 AddEnchantmentDuration(item, EnchantmentSlot(x), duration);
13399 }
13400}
13401
13403{
13404 for (EnchantDurationList::iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end();)
13405 {
13406 if (itr->item == item)
13407 {
13408 // save duration in item
13409 item->SetEnchantmentDuration(EnchantmentSlot(itr->slot), itr->leftduration, this);
13410 itr = m_enchantDuration.erase(itr);
13411 }
13412 else
13413 ++itr;
13414 }
13415}
13416
13418{
13419 for (EnchantDurationList::iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end();)
13420 {
13421 if (itr->item == item)
13422 itr = m_enchantDuration.erase(itr);
13423 else
13424 ++itr;
13425 }
13426}
13427
13429{
13430 // remove enchantments from equipped items first to clean up the m_enchantDuration list
13431 for (EnchantDurationList::iterator itr = m_enchantDuration.begin(), next; itr != m_enchantDuration.end(); itr = next)
13432 {
13433 next = itr;
13434 if (itr->slot == slot)
13435 {
13436 if (itr->item && itr->item->GetEnchantmentId(slot))
13437 {
13438 // Poisons and DK runes are enchants which are allowed on arenas
13439 if (sSpellMgr->IsArenaAllowedEnchancment(itr->item->GetEnchantmentId(slot)))
13440 {
13441 ++next;
13442 continue;
13443 }
13444 // remove from stats
13445 ApplyEnchantment(itr->item, slot, false, false);
13446 // remove visual
13447 itr->item->ClearEnchantment(slot);
13448 }
13449 // remove from update list
13450 next = m_enchantDuration.erase(itr);
13451 }
13452 else
13453 ++next;
13454 }
13455
13456 // remove enchants from inventory items
13457 // NOTE: no need to remove these from stats, since these aren't equipped
13458 // in inventory
13460 for (uint8 i = INVENTORY_SLOT_ITEM_START; i < inventoryEnd; ++i)
13461 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
13462 if (!sSpellMgr->IsArenaAllowedEnchancment(pItem->GetEnchantmentId(slot)))
13463 pItem->ClearEnchantment(slot);
13464
13465 // in inventory bags
13467 if (Bag* pBag = GetBagByPos(i))
13468 for (uint32 j = 0; j < pBag->GetBagSize(); j++)
13469 if (Item* pItem = pBag->GetItemByPos(j))
13470 if (!sSpellMgr->IsArenaAllowedEnchancment(pItem->GetEnchantmentId(slot)))
13471 pItem->ClearEnchantment(slot);
13472}
13473
13474// duration == 0 will remove item enchant
13476{
13477 if (!item)
13478 return;
13479
13480 if (slot >= MAX_ENCHANTMENT_SLOT)
13481 return;
13482
13483 for (EnchantDurationList::iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr)
13484 {
13485 if (itr->item == item && itr->slot == slot)
13486 {
13487 itr->item->SetEnchantmentDuration(itr->slot, itr->leftduration, this);
13488 m_enchantDuration.erase(itr);
13489 break;
13490 }
13491 }
13492 if (duration > 0)
13493 {
13494 GetSession()->SendItemEnchantTimeUpdate(GetGUID(), item->GetGUID(), slot, uint32(duration/1000));
13495 m_enchantDuration.push_back(EnchantDuration(item, slot, duration));
13496 }
13497}
13498
13500{
13501 for (uint32 slot = 0; slot < MAX_ENCHANTMENT_SLOT; ++slot)
13503}
13504
13505void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool apply_dur, bool ignore_condition)
13506{
13507 if (!item || !item->IsEquipped())
13508 return;
13509
13510 if (slot >= MAX_ENCHANTMENT_SLOT)
13511 return;
13512
13513 uint32 enchant_id = item->GetEnchantmentId(slot);
13514 if (!enchant_id)
13515 return;
13516
13517 SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
13518 if (!pEnchant)
13519 return;
13520
13521 if (!ignore_condition && pEnchant->ConditionID && !EnchantmentFitsRequirements(pEnchant->ConditionID, -1))
13522 return;
13523
13524 if (pEnchant->MinLevel > GetLevel())
13525 return;
13526
13527 if (pEnchant->RequiredSkillID > 0 && pEnchant->RequiredSkillRank > GetSkillValue(pEnchant->RequiredSkillID))
13528 return;
13529
13530 // If we're dealing with a gem inside a prismatic socket we need to check the prismatic socket requirements
13531 // rather than the gem requirements itself. If the socket has no color it is a prismatic socket.
13532 if ((slot == SOCK_ENCHANTMENT_SLOT || slot == SOCK_ENCHANTMENT_SLOT_2 || slot == SOCK_ENCHANTMENT_SLOT_3))
13533 {
13534 if (!item->GetSocketColor(slot - SOCK_ENCHANTMENT_SLOT))
13535 {
13536 // Check if the requirements for the prismatic socket are met before applying the gem stats
13538 if (!pPrismaticEnchant || (pPrismaticEnchant->RequiredSkillID > 0 && pPrismaticEnchant->RequiredSkillRank > GetSkillValue(pPrismaticEnchant->RequiredSkillID)))
13539 return;
13540 }
13541
13542 // Cogwheel gems dont have requirement data set in SpellItemEnchantment.dbc, but they do have it in Item-sparse.db2
13543 if (UF::SocketedGem const* gem = item->GetGem(uint16(slot - SOCK_ENCHANTMENT_SLOT)))
13544 if (ItemTemplate const* gemTemplate = sObjectMgr->GetItemTemplate(gem->ItemID))
13545 if (gemTemplate->GetRequiredSkill() && GetSkillValue(gemTemplate->GetRequiredSkill()) < gemTemplate->GetRequiredSkillRank())
13546 return;
13547 }
13548
13549 if (!item->IsBroken())
13550 {
13551 for (int s = 0; s < MAX_ITEM_ENCHANTMENT_EFFECTS; ++s)
13552 {
13553 uint32 enchant_display_type = pEnchant->Effect[s];
13554 uint32 enchant_amount = pEnchant->EffectPointsMin[s];
13555 uint32 enchant_spell_id = pEnchant->EffectArg[s];
13556
13557 switch (enchant_display_type)
13558 {
13560 break;
13562 // processed in Player::CastItemCombatSpell
13563 break;
13565 {
13566 WeaponAttackType const attackType = Player::GetAttackBySlot(item->GetSlot(), item->GetTemplate()->GetInventoryType());
13567 if (attackType != MAX_ATTACK)
13568 UpdateDamageDoneMods(attackType, apply ? -1 : slot);
13569 break;
13570 }
13572 if (enchant_spell_id)
13573 {
13574 if (apply)
13575 CastSpell(this, enchant_spell_id, item);
13576 else
13577 RemoveAurasDueToItemSpell(enchant_spell_id, item->GetGUID());
13578 }
13579 break;
13581 if (pEnchant->ScalingClass)
13582 {
13583 int32 scalingClass = pEnchant->ScalingClass;
13584 if ((*m_unitData->MinItemLevel || *m_unitData->MaxItemLevel) && pEnchant->ScalingClassRestricted)
13585 scalingClass = pEnchant->ScalingClassRestricted;
13586
13587 uint8 minLevel = pEnchant->GetFlags().HasFlag(SpellItemEnchantmentFlags::ScaleAsAGem) ? 1 : 60;
13588 uint8 scalingLevel = GetLevel();
13589 uint8 maxLevel = uint8(pEnchant->MaxLevel ? pEnchant->MaxLevel : sSpellScalingGameTable.GetTableRowCount() - 1);
13590
13591 if (minLevel > GetLevel())
13592 scalingLevel = minLevel;
13593 else if (maxLevel < GetLevel())
13594 scalingLevel = maxLevel;
13595
13596 if (GtSpellScalingEntry const* spellScaling = sSpellScalingGameTable.GetRow(scalingLevel))
13597 enchant_amount = uint32(pEnchant->EffectScalingPoints[s] * GetSpellScalingColumnForClass(spellScaling, scalingClass));
13598 }
13599 enchant_amount = std::max(enchant_amount, 1u);
13600 HandleStatFlatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + enchant_spell_id), TOTAL_VALUE, float(enchant_amount), apply);
13601 break;
13603 {
13604 if (pEnchant->ScalingClass)
13605 {
13606 int32 scalingClass = pEnchant->ScalingClass;
13607 if ((*m_unitData->MinItemLevel || *m_unitData->MaxItemLevel) && pEnchant->ScalingClassRestricted)
13608 scalingClass = pEnchant->ScalingClassRestricted;
13609
13610 uint8 minLevel = pEnchant->GetFlags().HasFlag(SpellItemEnchantmentFlags::ScaleAsAGem) ? 1 : 60;
13611 uint8 scalingLevel = GetLevel();
13612 uint8 maxLevel = uint8(pEnchant->MaxLevel ? pEnchant->MaxLevel : sSpellScalingGameTable.GetTableRowCount() - 1);
13613
13614 if (minLevel > GetLevel())
13615 scalingLevel = minLevel;
13616 else if (maxLevel < GetLevel())
13617 scalingLevel = maxLevel;
13618
13619 if (GtSpellScalingEntry const* spellScaling = sSpellScalingGameTable.GetRow(scalingLevel))
13620 enchant_amount = uint32(pEnchant->EffectScalingPoints[s] * GetSpellScalingColumnForClass(spellScaling, scalingClass));
13621 }
13622
13623 enchant_amount = std::max(enchant_amount, 1u);
13624
13625 TC_LOG_DEBUG("entities.player.items", "Adding {} to stat nb {}", enchant_amount, enchant_spell_id);
13626 switch (enchant_spell_id)
13627 {
13628 case ITEM_MOD_MANA:
13629 TC_LOG_DEBUG("entities.player.items", "+ {} MANA", enchant_amount);
13630 HandleStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, float(enchant_amount), apply);
13631 break;
13632 case ITEM_MOD_HEALTH:
13633 TC_LOG_DEBUG("entities.player.items", "+ {} HEALTH", enchant_amount);
13634 HandleStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, float(enchant_amount), apply);
13635 break;
13636 case ITEM_MOD_AGILITY:
13637 TC_LOG_DEBUG("entities.player.items", "+ {} AGILITY", enchant_amount);
13640 break;
13641 case ITEM_MOD_STRENGTH:
13642 TC_LOG_DEBUG("entities.player.items", "+ {} STRENGTH", enchant_amount);
13645 break;
13646 case ITEM_MOD_INTELLECT:
13647 TC_LOG_DEBUG("entities.player.items", "+ {} INTELLECT", enchant_amount);
13650 break;
13651 // case ITEM_MOD_SPIRIT:
13652 // TC_LOG_DEBUG("entities.player.items", "+ {} SPIRIT", enchant_amount);
13653 // HandleStatModifier(UNIT_MOD_STAT_SPIRIT, TOTAL_VALUE, float(enchant_amount), apply);
13654 // ApplyStatBuffMod(STAT_SPIRIT, (float)enchant_amount, apply);
13655 // break;
13656 case ITEM_MOD_STAMINA:
13657 TC_LOG_DEBUG("entities.player.items", "+ {} STAMINA", enchant_amount);
13660 break;
13662 ApplyRatingMod(CR_DEFENSE_SKILL, enchant_amount, apply);
13663 TC_LOG_DEBUG("entities.player.items", "+ {} DEFENSE", enchant_amount);
13664 break;
13666 ApplyRatingMod(CR_DODGE, enchant_amount, apply);
13667 TC_LOG_DEBUG("entities.player.items", "+ {} DODGE", enchant_amount);
13668 break;
13670 ApplyRatingMod(CR_PARRY, enchant_amount, apply);
13671 TC_LOG_DEBUG("entities.player.items", "+ {} PARRY", enchant_amount);
13672 break;
13674 ApplyRatingMod(CR_BLOCK, enchant_amount, apply);
13675 TC_LOG_DEBUG("entities.player.items", "+ {} SHIELD_BLOCK", enchant_amount);
13676 break;
13678 ApplyRatingMod(CR_HIT_MELEE, enchant_amount, apply);
13679 TC_LOG_DEBUG("entities.player.items", "+ {} MELEE_HIT", enchant_amount);
13680 break;
13682 ApplyRatingMod(CR_HIT_RANGED, enchant_amount, apply);
13683 TC_LOG_DEBUG("entities.player.items", "+ {} RANGED_HIT", enchant_amount);
13684 break;
13686 ApplyRatingMod(CR_HIT_SPELL, enchant_amount, apply);
13687 TC_LOG_DEBUG("entities.player.items", "+ {} SPELL_HIT", enchant_amount);
13688 break;
13690 ApplyRatingMod(CR_CRIT_MELEE, enchant_amount, apply);
13691 TC_LOG_DEBUG("entities.player.items", "+ {} MELEE_CRIT", enchant_amount);
13692 break;
13694 ApplyRatingMod(CR_CRIT_RANGED, enchant_amount, apply);
13695 TC_LOG_DEBUG("entities.player.items", "+ {} RANGED_CRIT", enchant_amount);
13696 break;
13698 ApplyRatingMod(CR_CRIT_SPELL, enchant_amount, apply);
13699 TC_LOG_DEBUG("entities.player.items", "+ {} SPELL_CRIT", enchant_amount);
13700 break;
13701 // Values from ITEM_STAT_MELEE_HA_RATING to ITEM_MOD_HASTE_RANGED_RATING are never used
13702 // in Enchantments
13703 // case ITEM_MOD_HIT_TAKEN_MELEE_RATING:
13704 // ApplyRatingMod(CR_HIT_TAKEN_MELEE, enchant_amount, apply);
13705 // break;
13706 // case ITEM_MOD_HIT_TAKEN_RANGED_RATING:
13707 // ApplyRatingMod(CR_HIT_TAKEN_RANGED, enchant_amount, apply);
13708 // break;
13709 // case ITEM_MOD_HIT_TAKEN_SPELL_RATING:
13710 // ApplyRatingMod(CR_HIT_TAKEN_SPELL, enchant_amount, apply);
13711 // break;
13712 // case ITEM_MOD_CRIT_TAKEN_MELEE_RATING:
13713 // ApplyRatingMod(CR_CRIT_TAKEN_MELEE, enchant_amount, apply);
13714 // break;
13715 // case ITEM_MOD_CRIT_TAKEN_RANGED_RATING:
13716 // ApplyRatingMod(CR_CRIT_TAKEN_RANGED, enchant_amount, apply);
13717 // break;
13718 // case ITEM_MOD_CRIT_TAKEN_SPELL_RATING:
13719 // ApplyRatingMod(CR_CRIT_TAKEN_SPELL, enchant_amount, apply);
13720 // break;
13721 // case ITEM_MOD_HASTE_MELEE_RATING:
13722 // ApplyRatingMod(CR_HASTE_MELEE, enchant_amount, apply);
13723 // break;
13724 // case ITEM_MOD_HASTE_RANGED_RATING:
13725 // ApplyRatingMod(CR_HASTE_RANGED, enchant_amount, apply);
13726 // break;
13728 ApplyRatingMod(CR_HASTE_SPELL, enchant_amount, apply);
13729 break;
13731 ApplyRatingMod(CR_HIT_MELEE, enchant_amount, apply);
13732 ApplyRatingMod(CR_HIT_RANGED, enchant_amount, apply);
13733 ApplyRatingMod(CR_HIT_SPELL, enchant_amount, apply);
13734 TC_LOG_DEBUG("entities.player.items", "+ {} HIT", enchant_amount);
13735 break;
13737 ApplyRatingMod(CR_CRIT_MELEE, enchant_amount, apply);
13738 ApplyRatingMod(CR_CRIT_RANGED, enchant_amount, apply);
13739 ApplyRatingMod(CR_CRIT_SPELL, enchant_amount, apply);
13740 TC_LOG_DEBUG("entities.player.items", "+ {} CRITICAL", enchant_amount);
13741 break;
13742 // case ITEM_MOD_HIT_TAKEN_RATING: // Unused since 3.3.5
13743 // ApplyRatingMod(CR_HIT_TAKEN_MELEE, enchant_amount, apply);
13744 // ApplyRatingMod(CR_HIT_TAKEN_RANGED, enchant_amount, apply);
13745 // ApplyRatingMod(CR_HIT_TAKEN_SPELL, enchant_amount, apply);
13746 // break;
13747 // case ITEM_MOD_CRIT_TAKEN_RATING: // Unused since 3.3.5
13748 // ApplyRatingMod(CR_CRIT_TAKEN_MELEE, enchant_amount, apply);
13749 // ApplyRatingMod(CR_CRIT_TAKEN_RANGED, enchant_amount, apply);
13750 // ApplyRatingMod(CR_CRIT_TAKEN_SPELL, enchant_amount, apply);
13751 // break;
13754 TC_LOG_DEBUG("entities.player.items", "+ {} RESILIENCE", enchant_amount);
13755 break;
13757 ApplyRatingMod(CR_HASTE_MELEE, enchant_amount, apply);
13758 ApplyRatingMod(CR_HASTE_RANGED, enchant_amount, apply);
13759 ApplyRatingMod(CR_HASTE_SPELL, enchant_amount, apply);
13760 TC_LOG_DEBUG("entities.player.items", "+ {} HASTE", enchant_amount);
13761 break;
13763 ApplyRatingMod(CR_EXPERTISE, enchant_amount, apply);
13764 TC_LOG_DEBUG("entities.player.items", "+ {} EXPERTISE", enchant_amount);
13765 break;
13769 TC_LOG_DEBUG("entities.player.items", "+ {} ATTACK_POWER", enchant_amount);
13770 break;
13773 TC_LOG_DEBUG("entities.player.items", "+ {} RANGED_ATTACK_POWER", enchant_amount);
13774 break;
13776 ApplyManaRegenBonus(enchant_amount, apply);
13777 TC_LOG_DEBUG("entities.player.items", "+ {} MANA_REGENERATION", enchant_amount);
13778 break;
13780 ApplyRatingMod(CR_ARMOR_PENETRATION, enchant_amount, apply);
13781 TC_LOG_DEBUG("entities.player.items", "+ {} ARMOR PENETRATION", enchant_amount);
13782 break;
13784 ApplySpellPowerBonus(enchant_amount, apply);
13785 TC_LOG_DEBUG("entities.player.items", "+ {} SPELL_POWER", enchant_amount);
13786 break;
13788 ApplyHealthRegenBonus(enchant_amount, apply);
13789 TC_LOG_DEBUG("entities.player.items", "+ {} HEALTH_REGENERATION", enchant_amount);
13790 break;
13792 ApplySpellPenetrationBonus(enchant_amount, apply);
13793 TC_LOG_DEBUG("entities.player.items", "+ {} SPELL_PENETRATION", enchant_amount);
13794 break;
13796 HandleBaseModFlatValue(SHIELD_BLOCK_VALUE, float(enchant_amount), apply);
13797 TC_LOG_DEBUG("entities.player.items", "+ {} BLOCK_VALUE", enchant_amount);
13798 break;
13800 ApplyRatingMod(CR_MASTERY, enchant_amount, apply);
13801 TC_LOG_DEBUG("entities.player.items", "+ {} MASTERY", enchant_amount);
13802 break;
13807 TC_LOG_DEBUG("entities.player.items", "+ {} VERSATILITY", enchant_amount);
13808 break;
13809 default:
13810 break;
13811 }
13812 break;
13813 }
13814 case ITEM_ENCHANTMENT_TYPE_TOTEM: // Shaman Rockbiter Weapon
13815 {
13816 WeaponAttackType const attackType = Player::GetAttackBySlot(item->GetSlot(), item->GetTemplate()->GetInventoryType());
13817 if (attackType != MAX_ATTACK)
13818 UpdateDamageDoneMods(attackType, apply ? -1 : slot);
13819 break;
13820 }
13822 // processed in Player::CastItemUseSpell
13823 break;
13830 // nothing do..
13831 break;
13832 default:
13833 TC_LOG_ERROR("entities.player", "Player::ApplyEnchantment: Unknown item enchantment (ID: {}, DisplayType: {}) for player '{}' ({})",
13834 enchant_id, enchant_display_type, GetName(), GetGUID().ToString());
13835 break;
13836 }
13837 }
13838 }
13839
13840 // visualize enchantment at player and equipped items
13841 if (slot == PERM_ENCHANTMENT_SLOT && item->GetSlot() < m_playerData->VisibleItems.size())
13843
13844 if (apply_dur)
13845 {
13846 if (apply)
13847 {
13848 // set duration
13849 uint32 duration = item->GetEnchantmentDuration(slot);
13850 if (duration > 0)
13851 AddEnchantmentDuration(item, slot, duration);
13852 }
13853 else
13854 {
13855 // duration == 0 will remove EnchantDuration
13856 AddEnchantmentDuration(item, slot, 0);
13857 }
13858 }
13859}
13860
13861void Player::UpdateSkillEnchantments(uint16 skill_id, uint16 curr_value, uint16 new_value)
13862{
13863 for (uint8 i = 0; i < REAGENT_BAG_SLOT_END; ++i)
13864 {
13865 if (m_items[i])
13866 {
13867 for (uint8 slot = 0; slot < MAX_ENCHANTMENT_SLOT; ++slot)
13868 {
13869 uint32 ench_id = m_items[i]->GetEnchantmentId(EnchantmentSlot(slot));
13870 if (!ench_id)
13871 continue;
13872
13873 SpellItemEnchantmentEntry const* Enchant = sSpellItemEnchantmentStore.LookupEntry(ench_id);
13874 if (!Enchant)
13875 return;
13876
13877 if (Enchant->RequiredSkillID == skill_id)
13878 {
13879 // Checks if the enchantment needs to be applied or removed
13880 if (curr_value < Enchant->RequiredSkillRank && new_value >= Enchant->RequiredSkillRank)
13881 ApplyEnchantment(m_items[i], EnchantmentSlot(slot), true);
13882 else if (new_value < Enchant->RequiredSkillRank && curr_value >= Enchant->RequiredSkillRank)
13883 ApplyEnchantment(m_items[i], EnchantmentSlot(slot), false);
13884 }
13885
13886 // If we're dealing with a gem inside a prismatic socket we need to check the prismatic socket requirements
13887 // rather than the gem requirements itself. If the socket has no color it is a prismatic socket.
13889 && !m_items[i]->GetSocketColor(slot - SOCK_ENCHANTMENT_SLOT))
13890 {
13891 SpellItemEnchantmentEntry const* pPrismaticEnchant = sSpellItemEnchantmentStore.LookupEntry(m_items[i]->GetEnchantmentId(PRISMATIC_ENCHANTMENT_SLOT));
13892
13893 if (pPrismaticEnchant && pPrismaticEnchant->RequiredSkillID == skill_id)
13894 {
13895 if (curr_value < pPrismaticEnchant->RequiredSkillRank && new_value >= pPrismaticEnchant->RequiredSkillRank)
13896 ApplyEnchantment(m_items[i], EnchantmentSlot(slot), true);
13897 else if (new_value < pPrismaticEnchant->RequiredSkillRank && curr_value >= pPrismaticEnchant->RequiredSkillRank)
13898 ApplyEnchantment(m_items[i], EnchantmentSlot(slot), false);
13899 }
13900 }
13901 }
13902 }
13903 }
13904}
13905
13907{
13908 for (EnchantDurationList::const_iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr)
13909 GetSession()->SendItemEnchantTimeUpdate(GetGUID(), itr->item->GetGUID(), itr->slot, uint32(itr->leftduration) / 1000);
13910}
13911
13913{
13914 for (ItemDurationList::const_iterator itr = m_itemDuration.begin(); itr != m_itemDuration.end(); ++itr)
13915 (*itr)->SendTimeUpdate(this);
13916}
13917
13919{
13920 if (m_itemPassives.empty())
13921 return;
13922
13923 WorldPackets::Item::SendItemPassives sendItemPassives;
13924 sendItemPassives.SpellID.assign(m_itemPassives.begin(), m_itemPassives.end());
13925 SendDirectMessage(sendItemPassives.Write());
13926}
13927
13928void Player::SendNewItem(Item* item, uint32 quantity, bool pushed, bool created, bool broadcast /*= false*/, uint32 dungeonEncounterId /*= 0*/)
13929{
13930 if (!item) // prevent crash
13931 return;
13932
13934
13935 packet.PlayerGUID = GetGUID();
13936
13937 packet.Slot = item->GetBagSlot();
13938 packet.SlotInBag = item->GetCount() == quantity ? item->GetSlot() : -1;
13939
13940 packet.Item.Initialize(item);
13941
13942 packet.ProxyItemID = item->GetTemplate()->QuestLogItemId;
13943 packet.Quantity = quantity;
13944 packet.QuantityInInventory = GetItemCount(item->GetEntry());
13945 if (QuestObjective const* questObjective = GetQuestObjectiveForItem(item->GetEntry(), false))
13946 packet.QuantityInQuestLog = GetQuestObjectiveData(*questObjective);
13947
13952
13953 packet.ItemGUID = item->GetGUID();
13954
13955 packet.Pushed = pushed;
13957 packet.Created = created;
13958 //packet.IsBonusRoll;
13959
13960 if (dungeonEncounterId)
13961 {
13963 packet.EncounterID = dungeonEncounterId;
13964 packet.IsPersonalLoot = true;
13965 }
13966
13968 GetGroup()->BroadcastPacket(packet.Write(), true);
13969 else
13970 SendDirectMessage(packet.Write());
13971}
13972
13973/*********************************************************/
13974/*** GOSSIP SYSTEM ***/
13975/*********************************************************/
13976
13977void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId, bool showQuests /*= false*/)
13978{
13979 PlayerTalkClass->ClearMenus();
13980 PlayerTalkClass->GetGossipMenu().SetMenuId(menuId);
13981
13982 Trinity::IteratorPair menuItemBounds = sObjectMgr->GetGossipMenuItemsMapBounds(menuId);
13983
13984 if (source->GetTypeId() == TYPEID_UNIT)
13985 {
13986 if (showQuests && source->ToUnit()->IsQuestGiver())
13987 PrepareQuestMenu(source->GetGUID());
13988 }
13989 else if (source->GetTypeId() == TYPEID_GAMEOBJECT)
13990 if (showQuests && source->ToGameObject()->GetGoType() == GAMEOBJECT_TYPE_QUESTGIVER)
13991 PrepareQuestMenu(source->GetGUID());
13992
13993 for (auto const& [_, gossipMenuItem] : menuItemBounds)
13994 {
13995 if (!gossipMenuItem.Conditions.Meets(this, source))
13996 continue;
13997
13998 bool canTalk = true;
13999 if (Creature* creature = source->ToCreature())
14000 {
14001 switch (gossipMenuItem.OptionNpc)
14002 {
14004 if (GetSession()->SendLearnNewTaxiNode(creature))
14005 return;
14006 break;
14008 if (!isDead())
14009 canTalk = false;
14010 break;
14012 if (!creature->isCanInteractWithBattleMaster(this, false))
14013 canTalk = false;
14014 break;
14018 if (!creature->CanResetTalents(this))
14019 canTalk = false;
14020 break;
14023 if (GetClass() != CLASS_HUNTER)
14024 canTalk = false;
14025 break;
14028 canTalk = false;
14029 break;
14032 canTalk = false;
14033 break;
14046 break; // No checks
14048 canTalk = false; // Deprecated
14049 break;
14050 default:
14051 if (gossipMenuItem.OptionNpc >= GossipOptionNpc::Count)
14052 {
14053 TC_LOG_ERROR("sql.sql", "Creature entry {} has an unknown gossip option icon {} for menu {}.", creature->GetEntry(), AsUnderlyingType(gossipMenuItem.OptionNpc), gossipMenuItem.MenuID);
14054 canTalk = false;
14055 }
14056 break; // NYI
14057 }
14058 }
14059 else if (GameObject* go = source->ToGameObject())
14060 {
14061 switch (gossipMenuItem.OptionNpc)
14062 {
14064 if (go->GetGoType() != GAMEOBJECT_TYPE_QUESTGIVER && go->GetGoType() != GAMEOBJECT_TYPE_GOOBER)
14065 canTalk = false;
14066 break;
14067 default:
14068 canTalk = false;
14069 break;
14070 }
14071 }
14072
14073 if (canTalk)
14074 PlayerTalkClass->GetGossipMenu().AddMenuItem(gossipMenuItem, gossipMenuItem.MenuID, gossipMenuItem.OrderIndex);
14075 }
14076}
14077
14079{
14080 if (!source)
14081 return;
14082
14083 if (source->GetTypeId() == TYPEID_UNIT || source->GetTypeId() == TYPEID_GAMEOBJECT)
14084 {
14085 if (PlayerTalkClass->GetGossipMenu().Empty() && !PlayerTalkClass->GetQuestMenu().Empty())
14086 {
14087 SendPreparedQuest(source);
14088 return;
14089 }
14090 }
14091
14092 // in case non empty gossip menu (that not included quests list size) show it
14093 // (quest entries from quest menu will be included in list)
14094
14095 uint32 textId = GetGossipTextId(source);
14096
14097 if (uint32 menuId = PlayerTalkClass->GetGossipMenu().GetMenuId())
14098 textId = GetGossipTextId(menuId, source);
14099
14100 PlayerTalkClass->SendGossipMenu(textId, source->GetGUID());
14101}
14102
14103void Player::OnGossipSelect(WorldObject* source, int32 gossipOptionId, uint32 menuId)
14104{
14105 GossipMenu& gossipMenu = PlayerTalkClass->GetGossipMenu();
14106
14107 // if not same, then something funky is going on
14108 if (menuId != gossipMenu.GetMenuId())
14109 return;
14110
14111 GossipMenuItem const* item = gossipMenu.GetItem(gossipOptionId);
14112 if (!item)
14113 return;
14114
14115 GossipOptionNpc gossipOptionNpc = item->OptionNpc;
14116 ObjectGuid guid = source->GetGUID();
14117
14118 if (source->GetTypeId() == TYPEID_GAMEOBJECT)
14119 {
14120 if (gossipOptionNpc != GossipOptionNpc::None)
14121 {
14122 TC_LOG_ERROR("entities.player", "Player '{}' ({}) requests invalid gossip option for GameObject (Entry: {})",
14123 GetName(), GetGUID().ToString(), source->GetEntry());
14124 return;
14125 }
14126 }
14127
14128 int64 cost = int64(item->BoxMoney);
14129 if (!HasEnoughMoney(cost))
14130 {
14132 PlayerTalkClass->SendCloseGossip();
14133 return;
14134 }
14135
14136 if (item->ActionPoiID)
14137 PlayerTalkClass->SendPointOfInterest(item->ActionPoiID);
14138
14139 if (item->ActionMenuID)
14140 {
14141 PrepareGossipMenu(source, item->ActionMenuID);
14142 SendPreparedGossip(source);
14143 }
14144
14145 // types that have their dedicated open opcode dont send WorldPackets::NPC::GossipOptionNPCInteraction
14146 bool handled = true;
14147 switch (gossipOptionNpc)
14148 {
14150 break;
14153 break;
14155 GetSession()->SendTaxiMenu(source->ToCreature());
14156 break;
14158 GetSession()->SendTrainerList(source->ToCreature(), sObjectMgr->GetCreatureTrainerForGossipOption(source->GetEntry(), menuId, item->OrderIndex));
14159 break;
14161 source->CastSpell(source->ToCreature(), 17251, CastSpellExtraArgs(TRIGGERED_FULL_MASK).SetOriginalCaster(GetGUID()));
14162 handled = false;
14163 break;
14165 PlayerTalkClass->SendCloseGossip();
14167 break;
14169 {
14170 BattlegroundTypeId bgTypeId = sBattlegroundMgr->GetBattleMasterBG(source->GetEntry());
14171
14172 if (bgTypeId == BATTLEGROUND_TYPE_NONE)
14173 {
14174 TC_LOG_ERROR("entities.player", "Player '{}' ({}) requested battlegroundlist from an invalid creature ({})",
14175 GetName(), GetGUID().ToString(), source->GetGUID().ToString());
14176 return;
14177 }
14178
14179 sBattlegroundMgr->SendBattlegroundList(this, guid, bgTypeId);
14180 break;
14181 }
14183 GetSession()->SendAuctionHello(guid, source->ToCreature());
14184 break;
14186 PlayerTalkClass->SendCloseGossip();
14188 break;
14190 SetStableMaster(guid);
14191 handled = false;
14192 break;
14194 PlayerTalkClass->SendCloseGossip();
14196 break;
14198 if (Guild* const guild = GetGuild())
14199 guild->SendBankList(GetSession(), 0, true);
14200 else
14202 break;
14204 if (Unit* sourceUnit = source->ToUnit())
14205 sourceUnit->HandleSpellClick(this);
14206 break;
14208 PlayerTalkClass->SendCloseGossip();
14209 CastSpell(nullptr, SPELL_EXPERIENCE_ELIMINATED, true);
14211 break;
14213 PlayerTalkClass->SendCloseGossip();
14216 break;
14218 PlayerTalkClass->SendCloseGossip();
14220 break;
14222 PlayerTalkClass->SendCloseGossip();
14224 break;
14226 break;
14228 break;
14230 break;
14232 break;
14234 break;
14236 break;
14238 break;
14239 case GossipOptionNpc::BarbersChoice: // NYI - unknown if needs sending
14240 break;
14241 default:
14242 handled = false;
14243 break;
14244 }
14245
14246 if (!handled)
14247 {
14248 if (item->GossipNpcOptionID)
14249 {
14250 GossipMenuAddon const* addon = sObjectMgr->GetGossipMenuAddon(menuId);
14251
14253 npcInteraction.GossipGUID = source->GetGUID();
14254 npcInteraction.GossipNpcOptionID = *item->GossipNpcOptionID;
14255 if (addon && addon->FriendshipFactionID)
14256 npcInteraction.FriendshipFactionID = addon->FriendshipFactionID;
14257
14258 SendDirectMessage(npcInteraction.Write());
14259 }
14260 else
14261 {
14262 static constexpr std::array<PlayerInteractionType, AsUnderlyingType(GossipOptionNpc::Count)> GossipOptionNpcToInteractionType =
14263 {
14284 };
14285
14286 PlayerInteractionType interactionType = GossipOptionNpcToInteractionType[AsUnderlyingType(gossipOptionNpc)];
14287 if (interactionType != PlayerInteractionType::None)
14288 {
14290 npcInteraction.Npc = source->GetGUID();
14291 npcInteraction.InteractionType = interactionType;
14292 npcInteraction.Success = true;
14293 SendDirectMessage(npcInteraction.Write());
14294 }
14295 }
14296 }
14297
14298 ModifyMoney(-cost);
14299}
14300
14302{
14303 if (!source)
14305
14306 return GetGossipTextId(GetGossipMenuForSource(source), source);
14307}
14308
14310{
14312
14313 if (!menuId)
14314 return textId;
14315
14316 GossipMenusMapBounds menuBounds = sObjectMgr->GetGossipMenusMapBounds(menuId);
14317
14318 for (GossipMenusContainer::const_iterator itr = menuBounds.first; itr != menuBounds.second; ++itr)
14319 {
14320 // continue if only checks menuid instead of text
14321 if (!itr->second.TextID)
14322 continue;
14323
14324 if (itr->second.Conditions.Meets(this, source))
14325 textId = itr->second.TextID;
14326 }
14327
14328 return textId;
14329}
14330
14332{
14333 switch (source->GetTypeId())
14334 {
14335 case TYPEID_UNIT:
14336 {
14337 uint32 menuIdToShow = source->ToCreature()->GetGossipMenuId();
14338
14339 // if menu id is set by script
14340 if (menuIdToShow)
14341 return menuIdToShow;
14342
14343 // otherwise pick from db based on conditions
14344 for (uint32 menuId : source->ToCreature()->GetCreatureTemplate()->GossipMenuIds)
14345 {
14346 GossipMenusMapBounds menuBounds = sObjectMgr->GetGossipMenusMapBounds(menuId);
14347
14348 for (GossipMenusContainer::const_iterator itr = menuBounds.first; itr != menuBounds.second; ++itr)
14349 {
14350 if (!itr->second.Conditions.Meets(this, source))
14351 continue;
14352
14353 menuIdToShow = menuId;
14354 }
14355 }
14356 return menuIdToShow;
14357 }
14358 case TYPEID_GAMEOBJECT:
14359 return source->ToGameObject()->GetGOInfo()->GetGossipMenuId();
14360 default:
14361 break;
14362 }
14363
14364 return 0;
14365}
14366
14367/*********************************************************/
14368/*** QUEST SYSTEM ***/
14369/*********************************************************/
14370
14372{
14373 return GetQuestMinLevel(quest->GetContentTuningId());
14374}
14375
14377{
14378 if (Optional<ContentTuningLevels> questLevels = sDB2Manager.GetContentTuningData(contentTuningId, m_playerData->CtrOptions->ConditionalFlags))
14379 {
14380 ChrRacesEntry const* race = sChrRacesStore.AssertEntry(GetRace());
14381 FactionTemplateEntry const* raceFaction = sFactionTemplateStore.AssertEntry(race->FactionID);
14382 int32 questFactionGroup = sContentTuningStore.AssertEntry(contentTuningId)->GetScalingFactionGroup();
14383 if (questFactionGroup && raceFaction->FactionGroup != questFactionGroup)
14384 return questLevels->MaxLevel;
14385
14386 return questLevels->MinLevelWithDelta;
14387 }
14388
14389 return 0;
14390}
14391
14393{
14394 if (!quest)
14395 return 0;
14396
14397 return GetQuestLevel(quest->GetContentTuningId());
14398}
14399
14400int32 Player::GetQuestLevel(uint32 contentTuningId) const
14401{
14402 if (Optional<ContentTuningLevels> questLevels = sDB2Manager.GetContentTuningData(contentTuningId, m_playerData->CtrOptions->ConditionalFlags))
14403 {
14404 int32 minLevel = GetQuestMinLevel(contentTuningId);
14405 int32 maxLevel = questLevels->MaxLevel;
14406 int32 level = GetLevel();
14407 if (level >= minLevel)
14408 return std::min(level, maxLevel);
14409
14410 return minLevel;
14411 }
14412
14413 return 0;
14414}
14415
14417{
14418 QuestRelationResult objectQR;
14419 QuestRelationResult objectQIR;
14420
14421 // pets also can have quests
14422 Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, guid);
14423 if (creature)
14424 {
14425 objectQR = sObjectMgr->GetCreatureQuestRelations(creature->GetEntry());
14426 objectQIR = sObjectMgr->GetCreatureQuestInvolvedRelations(creature->GetEntry());
14427 }
14428 else
14429 {
14430 //we should obtain map pointer from GetMap() in 99% of cases. Special case
14431 //only for quests which cast teleport spells on player
14432 Map* _map = IsInWorld() ? GetMap() : sMapMgr->FindMap(GetMapId(), GetInstanceId());
14433 ASSERT(_map);
14434 GameObject* gameObject = _map->GetGameObject(guid);
14435 if (gameObject)
14436 {
14437 objectQR = sObjectMgr->GetGOQuestRelations(gameObject->GetEntry());
14438 objectQIR = sObjectMgr->GetGOQuestInvolvedRelations(gameObject->GetEntry());
14439 }
14440 else
14441 return;
14442 }
14443
14444 QuestMenu &qm = PlayerTalkClass->GetQuestMenu();
14445 qm.ClearMenu();
14446
14447 for (uint32 quest_id : objectQIR)
14448 {
14449 QuestStatus status = GetQuestStatus(quest_id);
14450 if (status == QUEST_STATUS_COMPLETE)
14451 qm.AddMenuItem(quest_id, 4);
14452 else if (status == QUEST_STATUS_INCOMPLETE)
14453 qm.AddMenuItem(quest_id, 4);
14454 //else if (status == QUEST_STATUS_AVAILABLE)
14455 // qm.AddMenuItem(quest_id, 2);
14456 }
14457
14458 for (uint32 quest_id : objectQR)
14459 {
14460 Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
14461 if (!quest)
14462 continue;
14463
14464 if (!CanTakeQuest(quest, false))
14465 continue;
14466
14467 if (quest->IsTurnIn() && (!quest->IsRepeatable() || quest->IsDaily() || quest->IsWeekly() || quest->IsMonthly()))
14468 qm.AddMenuItem(quest_id, 0);
14469 else if (quest->IsTurnIn())
14470 qm.AddMenuItem(quest_id, 4);
14471 else if (GetQuestStatus(quest_id) == QUEST_STATUS_NONE)
14472 qm.AddMenuItem(quest_id, 2);
14473 }
14474}
14475
14477{
14478 QuestMenu& questMenu = PlayerTalkClass->GetQuestMenu();
14479 if (questMenu.Empty())
14480 return;
14481
14482 // single element case
14483 if (questMenu.GetMenuItemCount() == 1)
14484 {
14485 QuestMenuItem const& qmi0 = questMenu.GetItem(0);
14486 uint32 questId = qmi0.QuestId;
14487
14488 // Auto open
14489 if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
14490 {
14491 if (qmi0.QuestIcon == 4)
14492 PlayerTalkClass->SendQuestGiverRequestItems(quest, source->GetGUID(), CanRewardQuest(quest, false), true);
14493 // Send completable on repeatable and autoCompletable quest if player don't have quest
14495 else if (!source->hasQuest(questId) && !source->hasInvolvedQuest(questId))
14496 PlayerTalkClass->SendCloseGossip();
14497 else
14498 {
14499 if (quest->IsAutoAccept() && CanAddQuest(quest, true) && CanTakeQuest(quest, true))
14500 AddQuestAndCheckCompletion(quest, source);
14501
14502 if (quest->IsTurnIn() && quest->IsRepeatable() && !quest->IsDailyOrWeekly() && !quest->IsMonthly())
14503 PlayerTalkClass->SendQuestGiverRequestItems(quest, source->GetGUID(), CanCompleteRepeatableQuest(quest), true);
14504 else if (quest->IsTurnIn() && !quest->IsDailyOrWeekly() && !quest->IsMonthly())
14505 PlayerTalkClass->SendQuestGiverRequestItems(quest, source->GetGUID(), CanRewardQuest(quest, false), true);
14506 else
14507 PlayerTalkClass->SendQuestGiverQuestDetails(quest, source->GetGUID(), true, false);
14508 }
14509
14510 return;
14511 }
14512 }
14513
14514 PlayerTalkClass->SendQuestGiverQuestListMessage(source);
14515}
14516
14517bool Player::IsActiveQuest(uint32 quest_id) const
14518{
14519 return m_QuestStatus.find(quest_id) != m_QuestStatus.end();
14520}
14521
14522Quest const* Player::GetNextQuest(Object const* questGiver, Quest const* quest) const
14523{
14524 uint32 nextQuestID = quest->GetNextQuestInChain();
14525 if (!nextQuestID)
14526 return nullptr;
14527
14528 if (questGiver == this)
14529 {
14531 return nullptr;
14532
14533 return sObjectMgr->GetQuestTemplate(nextQuestID);
14534 }
14535
14536 //we should obtain map pointer from GetMap() in 99% of cases. Special case
14537 //only for quests which cast teleport spells on player
14538 if (WorldObject const* worldObjectQuestGiver = dynamic_cast<WorldObject const*>(questGiver))
14539 if (!IsInMap(worldObjectQuestGiver))
14540 return nullptr;
14541
14542 if (!questGiver->hasQuest(nextQuestID))
14543 return nullptr;
14544
14545 return sObjectMgr->GetQuestTemplate(nextQuestID);
14546}
14547
14548bool Player::CanSeeStartQuest(Quest const* quest) const
14549{
14550 if (!DisableMgr::IsDisabledFor(DISABLE_TYPE_QUEST, quest->GetQuestId(), this) && SatisfyQuestClass(quest, false) && SatisfyQuestRace(quest, false) &&
14551 SatisfyQuestSkill(quest, false) && SatisfyQuestExclusiveGroup(quest, false) && SatisfyQuestReputation(quest, false) &&
14552 SatisfyQuestDependentQuests(quest, false) &&
14553 SatisfyQuestDay(quest, false) && SatisfyQuestWeek(quest, false) &&
14554 SatisfyQuestMonth(quest, false) && SatisfyQuestSeasonal(quest, false) && SatisfyQuestExpansion(quest, false))
14555 {
14556 return int32(GetLevel() + sWorld->getIntConfig(CONFIG_QUEST_HIGH_LEVEL_HIDE_DIFF)) >= GetQuestMinLevel(quest);
14557 }
14558
14559 return false;
14560}
14561
14562bool Player::CanTakeQuest(Quest const* quest, bool msg) const
14563{
14565 && SatisfyQuestStatus(quest, msg) && SatisfyQuestExclusiveGroup(quest, msg)
14566 && SatisfyQuestClass(quest, msg) && SatisfyQuestRace(quest, msg) && SatisfyQuestLevel(quest, msg)
14567 && SatisfyQuestSkill(quest, msg) && SatisfyQuestReputation(quest, msg)
14568 && SatisfyQuestDependentQuests(quest, msg) && SatisfyQuestTimed(quest, msg)
14569 && SatisfyQuestDay(quest, msg) && SatisfyQuestWeek(quest, msg)
14570 && SatisfyQuestMonth(quest, msg) && SatisfyQuestSeasonal(quest, msg)
14571 && SatisfyQuestConditions(quest, msg) && SatisfyQuestExpansion(quest, msg);
14572}
14573
14574bool Player::CanAddQuest(Quest const* quest, bool msg) const
14575{
14576 if (!SatisfyQuestLog(msg))
14577 return false;
14578
14579 uint32 srcitem = quest->GetSrcItemId();
14580 if (srcitem > 0)
14581 {
14582 uint32 count = quest->GetSrcItemCount();
14583 ItemPosCountVec dest;
14584 InventoryResult msg2 = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, srcitem, count);
14585
14586 // player already have max number (in most case 1) source item, no additional item needed and quest can be added.
14587 if (msg2 == EQUIP_ERR_ITEM_MAX_COUNT)
14588 return true;
14589 if (msg2 != EQUIP_ERR_OK)
14590 {
14591 SendEquipError(msg2, nullptr, nullptr, srcitem);
14592 return false;
14593 }
14594 }
14595 return true;
14596}
14597
14598bool Player::CanCompleteQuest(uint32 quest_id, uint32 ignoredQuestObjectiveId /*= 0*/)
14599{
14600 if (quest_id)
14601 {
14602 Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
14603 if (!qInfo)
14604 return false;
14605
14606 if (!qInfo->IsRepeatable() && GetQuestRewardStatus(quest_id))
14607 return false; // not allow re-complete quest
14608
14609 // auto complete quest
14610 if (qInfo->IsTurnIn() && CanTakeQuest(qInfo, false))
14611 return true;
14612
14613 QuestStatusMap::iterator itr = m_QuestStatus.find(quest_id);
14614 if (itr == m_QuestStatus.end())
14615 return false;
14616
14617 QuestStatusData &q_status = itr->second;
14618
14619 if (q_status.Status == QUEST_STATUS_INCOMPLETE)
14620 {
14621 for (QuestObjective const& obj : qInfo->GetObjectives())
14622 {
14623 if (ignoredQuestObjectiveId && obj.ID == ignoredQuestObjectiveId)
14624 continue;
14625
14627 {
14628 if (!IsQuestObjectiveComplete(q_status.Slot, qInfo, obj))
14629 return false;
14630 }
14631 }
14632
14634 return false;
14635
14636 if (qInfo->GetLimitTime() && q_status.Timer == 0)
14637 return false;
14638
14639 return true;
14640 }
14641 }
14642 return false;
14643}
14644
14646{
14647 // Solve problem that player don't have the quest and try complete it.
14648 // if repeatable she must be able to complete event if player don't have it.
14649 // Seem that all repeatable quest are DELIVER Flag so, no need to add more.
14650 if (!CanTakeQuest(quest, false))
14651 return false;
14652
14653 if (!CanRewardQuest(quest, false))
14654 return false;
14655
14656 return true;
14657}
14658
14659bool Player::CanRewardQuest(Quest const* quest, bool msg) const
14660{
14661 // quest is disabled
14663 return false;
14664
14665 // not auto complete quest and not completed quest (only cheating case, then ignore without message)
14666 if (!quest->IsDFQuest() && !quest->IsTurnIn() && GetQuestStatus(quest->GetQuestId()) != QUEST_STATUS_COMPLETE)
14667 return false;
14668
14669 // daily quest can't be rewarded (25 daily quest already completed)
14670 if (!SatisfyQuestDay(quest, msg) || !SatisfyQuestWeek(quest, msg) || !SatisfyQuestMonth(quest, msg) || !SatisfyQuestSeasonal(quest, msg))
14671 return false;
14672
14673 // player no longer satisfies the quest's requirements (skill level etc.)
14674 if (!SatisfyQuestLevel(quest, msg) || !SatisfyQuestSkill(quest, msg) || !SatisfyQuestReputation(quest, msg))
14675 return false;
14676
14677 // rewarded and not repeatable quest (only cheating case, then ignore without message)
14678 if (GetQuestRewardStatus(quest->GetQuestId()))
14679 return false;
14680
14681 // prevent receive reward with quest items in bank
14683 {
14684 for (QuestObjective const& obj : quest->GetObjectives())
14685 {
14686 if (obj.Type != QUEST_OBJECTIVE_ITEM || obj.Flags2 & QUEST_OBJECTIVE_FLAG_2_QUEST_BOUND_ITEM)
14687 continue;
14688
14689 if (GetItemCount(obj.ObjectID) < uint32(obj.Amount))
14690 {
14691 if (msg)
14692 SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr, obj.ObjectID);
14693 return false;
14694 }
14695 }
14696 }
14697
14698 for (QuestObjective const& obj : quest->GetObjectives())
14699 {
14700 switch (obj.Type)
14701 {
14703 if (!HasCurrency(obj.ObjectID, obj.Amount))
14704 return false;
14705 break;
14707 if (!HasEnoughMoney(uint64(obj.Amount)))
14708 return false;
14709 break;
14710 }
14711 }
14712
14713 return true;
14714}
14715
14716void Player::AddQuestAndCheckCompletion(Quest const* quest, Object* questGiver)
14717{
14718 AddQuest(quest, questGiver);
14719
14720 if (CanCompleteQuest(quest->GetQuestId()))
14721 CompleteQuest(quest->GetQuestId());
14722
14723 if (!questGiver)
14724 return;
14725
14726 switch (questGiver->GetTypeId())
14727 {
14728 case TYPEID_UNIT:
14729 PlayerTalkClass->ClearMenus();
14730 questGiver->ToCreature()->AI()->OnQuestAccept(this, quest);
14731 break;
14732 case TYPEID_ITEM:
14733 case TYPEID_CONTAINER:
14736 {
14737 Item* item = static_cast<Item*>(questGiver);
14738 sScriptMgr->OnQuestAccept(this, item, quest);
14739
14740 // There are two cases where the source item is not destroyed when the quest is accepted:
14741 // - It is required to finish the quest, and is an unique item
14742 // - It is the same item present in the source item field (item that would be given on quest accept)
14743 bool destroyItem = true;
14744
14745 for (QuestObjective const& obj : quest->GetObjectives())
14746 {
14747 if (obj.Type == QUEST_OBJECTIVE_ITEM && uint32(obj.ObjectID) == item->GetEntry() && item->GetTemplate()->GetMaxCount() > 0)
14748 {
14749 destroyItem = false;
14750 break;
14751 }
14752 }
14753
14754 if (quest->GetSrcItemId() == item->GetEntry())
14755 destroyItem = false;
14756
14757 if (destroyItem)
14758 DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
14759
14760 break;
14761 }
14762 case TYPEID_GAMEOBJECT:
14763 PlayerTalkClass->ClearMenus();
14764 questGiver->ToGameObject()->AI()->OnQuestAccept(this, quest);
14765 break;
14766 default:
14767 break;
14768 }
14769}
14770
14771bool Player::CanRewardQuest(Quest const* quest, LootItemType rewardType, uint32 rewardId, bool msg) const
14772{
14773 ItemPosCountVec dest;
14774 if (quest->GetRewChoiceItemsCount() > 0)
14775 {
14776 switch (rewardType)
14777 {
14778 case LootItemType::Item:
14779 {
14780 for (uint32 i = 0; i < QUEST_REWARD_CHOICES_COUNT; ++i)
14781 {
14782 if (quest->RewardChoiceItemId[i] && quest->RewardChoiceItemType[i] == LootItemType::Item && quest->RewardChoiceItemId[i] == rewardId)
14783 {
14785 if (res != EQUIP_ERR_OK)
14786 {
14787 if (msg)
14788 SendQuestFailed(quest->GetQuestId(), res);
14789
14790 return false;
14791 }
14792 }
14793 }
14794 break;
14795 }
14797 break;
14798 default:
14799 break;
14800 }
14801 }
14802
14803 if (quest->GetRewItemsCount() > 0)
14804 {
14805 for (uint32 i = 0; i < quest->GetRewItemsCount(); ++i)
14806 {
14807 if (quest->RewardItemId[i])
14808 {
14810 if (res != EQUIP_ERR_OK)
14811 {
14812 if (msg)
14813 SendQuestFailed(quest->GetQuestId(), res);
14814
14815 return false;
14816 }
14817 }
14818 }
14819 }
14820
14821 // QuestPackageItem.db2
14822 if (quest->GetQuestPackageID())
14823 {
14824 bool hasFilteredQuestPackageReward = false;
14825 if (std::vector<QuestPackageItemEntry const*> const* questPackageItems = sDB2Manager.GetQuestPackageItems(quest->GetQuestPackageID()))
14826 {
14827 for (QuestPackageItemEntry const* questPackageItem : *questPackageItems)
14828 {
14829 if (questPackageItem->ItemID != int32(rewardId))
14830 continue;
14831
14832 if (CanSelectQuestPackageItem(questPackageItem))
14833 {
14834 hasFilteredQuestPackageReward = true;
14835 InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, questPackageItem->ItemID, questPackageItem->ItemQuantity);
14836 if (res != EQUIP_ERR_OK)
14837 {
14838 SendEquipError(res, nullptr, nullptr, questPackageItem->ItemID);
14839 return false;
14840 }
14841 }
14842 }
14843 }
14844
14845 if (!hasFilteredQuestPackageReward)
14846 {
14847 if (std::vector<QuestPackageItemEntry const*> const* questPackageItems = sDB2Manager.GetQuestPackageItemsFallback(quest->GetQuestPackageID()))
14848 {
14849 for (QuestPackageItemEntry const* questPackageItem : *questPackageItems)
14850 {
14851 if (questPackageItem->ItemID != int32(rewardId))
14852 continue;
14853
14854 InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, questPackageItem->ItemID, questPackageItem->ItemQuantity);
14855 if (res != EQUIP_ERR_OK)
14856 {
14857 SendEquipError(res, nullptr, nullptr, questPackageItem->ItemID);
14858 return false;
14859 }
14860 }
14861 }
14862 }
14863 }
14864
14865 return true;
14866}
14867
14868void Player::AddQuest(Quest const* quest, Object* questGiver)
14869{
14870 uint16 log_slot = FindQuestSlot(0);
14871
14872 if (log_slot >= MAX_QUEST_LOG_SIZE) // Player does not have any free slot in the quest log
14873 return;
14874
14875 uint32 quest_id = quest->GetQuestId();
14876
14877 // if not exist then created with set uState == NEW and rewarded=false
14878 auto questStatusItr = m_QuestStatus.emplace(quest_id, QuestStatusData{}).first;
14879 QuestStatusData& questStatusData = questStatusItr->second;
14880 QuestStatus oldStatus = questStatusData.Status;
14881
14883
14884 // check for repeatable quests status reset
14885 SetQuestSlot(log_slot, quest_id);
14886 questStatusData.Slot = log_slot;
14887 questStatusData.Status = QUEST_STATUS_INCOMPLETE;
14888 questStatusData.Explored = false;
14889
14890 for (QuestObjective const& obj : quest->GetObjectives())
14891 {
14892 m_questObjectiveStatus.emplace(std::make_pair(QuestObjectiveType(obj.Type), obj.ObjectID), QuestObjectiveStatusData { questStatusItr, obj.ID });
14893 switch (obj.Type)
14894 {
14897 if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(obj.ObjectID))
14898 GetReputationMgr().SetVisible(factionEntry);
14899 break;
14901 m_questObjectiveCriteriaMgr->ResetCriteriaTree(&obj);
14902 break;
14903 default:
14904 break;
14905 }
14906 }
14907
14908 GiveQuestSourceItem(quest);
14910
14911 time_t endTime = 0;
14912 if (uint32 limittime = quest->GetLimitTime())
14913 {
14914 // shared timed quest
14915 if (questGiver && questGiver->GetTypeId() == TYPEID_PLAYER)
14916 limittime = questGiver->ToPlayer()->getQuestStatusMap()[quest_id].Timer / IN_MILLISECONDS;
14917
14918 AddTimedQuest(quest_id);
14919 questStatusData.Timer = limittime * IN_MILLISECONDS;
14920 endTime = GameTime::GetGameTime() + limittime;
14921 }
14922 else
14923 questStatusData.Timer = 0;
14924
14925 if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP))
14926 {
14927 pvpInfo.IsHostile = true;
14929 }
14930
14931 if (quest->GetSrcSpell() > 0)
14932 {
14933 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(quest->GetSrcSpell(), GetMap()->GetDifficultyID());
14934 Unit* caster = this;
14935 if (questGiver && questGiver->IsUnit() && !quest->HasFlag(QUEST_FLAGS_PLAYER_CAST_ACCEPT) && !spellInfo->HasTargetType(TARGET_UNIT_CASTER) && !spellInfo->HasTargetType(TARGET_DEST_CASTER_SUMMON))
14936 caster = questGiver->ToUnit();
14937
14938 caster->CastSpell(this, spellInfo->Id, CastSpellExtraArgs(TRIGGERED_FULL_MASK).SetCastDifficulty(spellInfo->Difficulty));
14939 }
14940
14941 SetQuestSlotEndTime(log_slot, endTime);
14942 questStatusData.AcceptTime = GameTime::GetGameTime();
14943
14945
14947
14948 SendQuestUpdate(quest_id);
14949
14950 bool updateVisibility = false;
14952 updateVisibility = PhasingHandler::OnConditionChange(this, false);
14953
14954 if (sWorld->getBoolConfig(CONFIG_QUEST_ENABLE_QUEST_TRACKER)) // check if Quest Tracker is enabled
14955 {
14956 // prepare Quest Tracker datas
14958 stmt->setUInt32(0, quest_id);
14959 stmt->setUInt64(1, GetGUID().GetCounter());
14960 stmt->setString(2, std::string_view(GitRevision::GetHash()));
14961 stmt->setString(3, std::string_view(GitRevision::GetDate()));
14962
14963 // add to Quest Tracker
14964 CharacterDatabase.Execute(stmt);
14965 }
14966
14967 if (updateVisibility)
14969
14970 sScriptMgr->OnQuestStatusChange(this, quest_id);
14971 sScriptMgr->OnQuestStatusChange(this, quest, oldStatus, questStatusData.Status);
14972}
14973
14975{
14976 if (quest_id)
14977 {
14979
14981
14982 if (QuestStatusData const* questStatus = Trinity::Containers::MapGetValuePtr(m_QuestStatus, quest_id))
14983 SetQuestSlotState(questStatus->Slot, QUEST_STATE_COMPLETE);
14984
14985 if (Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id))
14986 if (qInfo->HasFlag(QUEST_FLAGS_TRACKING_EVENT))
14987 RewardQuest(qInfo, LootItemType::Item, 0, this, false);
14988 }
14989
14990 if (sWorld->getBoolConfig(CONFIG_QUEST_ENABLE_QUEST_TRACKER)) // check if Quest Tracker is enabled
14991 {
14992 // prepare Quest Tracker data
14994 stmt->setUInt32(0, quest_id);
14995 stmt->setUInt64(1, GetGUID().GetCounter());
14996
14997 // add to Quest Tracker
14998 CharacterDatabase.Execute(stmt);
14999 }
15000}
15001
15003{
15004 if (quest_id)
15005 {
15007
15008 uint16 log_slot = FindQuestSlot(quest_id);
15009 if (log_slot < MAX_QUEST_LOG_SIZE)
15011 }
15012}
15013
15015{
15016 return quest->MoneyValue(this) * sWorld->getRate(RATE_MONEY_QUEST);
15017}
15018
15020{
15021 bool rewarded = IsQuestRewarded(quest->GetQuestId()) && !quest->IsDFQuest();
15022
15023 // Not give XP in case already completed once repeatable quest
15024 if (rewarded)
15025 return 0;
15026
15027 uint32 XP = quest->XPValue(this) * sWorld->getRate(RATE_XP_QUEST);
15028
15029 // handle SPELL_AURA_MOD_XP_QUEST_PCT auras
15031 for (Unit::AuraEffectList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i)
15032 AddPct(XP, (*i)->GetAmount());
15033
15034 return XP;
15035}
15036
15038{
15039 ItemTemplate const* rewardProto = sObjectMgr->GetItemTemplate(questPackageItem->ItemID);
15040 if (!rewardProto)
15041 return false;
15042
15043 if ((rewardProto->HasFlag(ITEM_FLAG2_FACTION_ALLIANCE) && GetTeam() != ALLIANCE) ||
15044 (rewardProto->HasFlag(ITEM_FLAG2_FACTION_HORDE) && GetTeam() != HORDE))
15045 return false;
15046
15047 switch (questPackageItem->DisplayType)
15048 {
15050 return rewardProto->IsUsableByLootSpecialization(this, true);
15052 return !rewardProto->ItemSpecClassMask || (rewardProto->ItemSpecClassMask & GetClassMask()) != 0;
15054 return true;
15055 default:
15056 break;
15057 }
15058
15059 return false;
15060}
15061
15062void Player::RewardQuestPackage(uint32 questPackageId, ItemContext context, uint32 onlyItemId /*= 0*/)
15063{
15064 bool hasFilteredQuestPackageReward = false;
15065 if (std::vector<QuestPackageItemEntry const*> const* questPackageItems = sDB2Manager.GetQuestPackageItems(questPackageId))
15066 {
15067 for (QuestPackageItemEntry const* questPackageItem : *questPackageItems)
15068 {
15069 if (onlyItemId && questPackageItem->ItemID != int32(onlyItemId))
15070 continue;
15071
15072 if (CanSelectQuestPackageItem(questPackageItem))
15073 {
15074 hasFilteredQuestPackageReward = true;
15075 ItemPosCountVec dest;
15076 if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, questPackageItem->ItemID, questPackageItem->ItemQuantity) == EQUIP_ERR_OK)
15077 {
15078 Item* item = StoreNewItem(dest, questPackageItem->ItemID, true, GenerateItemRandomBonusListId(questPackageItem->ItemID), {}, context);
15079 SendNewItem(item, questPackageItem->ItemQuantity, true, false);
15080 }
15081 }
15082 }
15083 }
15084
15085 if (!hasFilteredQuestPackageReward)
15086 {
15087 if (std::vector<QuestPackageItemEntry const*> const* questPackageItems = sDB2Manager.GetQuestPackageItemsFallback(questPackageId))
15088 {
15089 for (QuestPackageItemEntry const* questPackageItem : *questPackageItems)
15090 {
15091 if (onlyItemId && questPackageItem->ItemID != int32(onlyItemId))
15092 continue;
15093
15094 ItemPosCountVec dest;
15095 if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, questPackageItem->ItemID, questPackageItem->ItemQuantity) == EQUIP_ERR_OK)
15096 {
15097 Item* item = StoreNewItem(dest, questPackageItem->ItemID, true, GenerateItemRandomBonusListId(questPackageItem->ItemID), {}, context);
15098 SendNewItem(item, questPackageItem->ItemQuantity, true, false);
15099 }
15100 }
15101 }
15102 }
15103}
15104
15105void Player::RewardQuest(Quest const* quest, LootItemType rewardType, uint32 rewardId, Object* questGiver, bool announce)
15106{
15107 //this THING should be here to protect code from quest, which cast on player far teleport as a reward
15108 //should work fine, cause far teleport will be executed in Player::Update()
15109 SetCanDelayTeleport(true);
15110
15111 uint32 quest_id = quest->GetQuestId();
15112 QuestStatus oldStatus = GetQuestStatus(quest_id);
15113
15114 for (QuestObjective const& obj : quest->GetObjectives())
15115 {
15116 switch (obj.Type)
15117 {
15119 {
15120 int32 amountToDestroy = obj.Amount;
15122 amountToDestroy = std::numeric_limits<uint32>::max();
15123 DestroyItemCount(obj.ObjectID, amountToDestroy, true);
15124 break;
15125 }
15127 RemoveCurrency(obj.ObjectID, obj.Amount, CurrencyDestroyReason::QuestTurnin);
15128 break;
15129 }
15130 }
15131
15133 {
15134 for (uint8 i = 0; i < QUEST_ITEM_DROP_COUNT; ++i)
15135 {
15136 if (quest->ItemDrop[i])
15137 {
15138 uint32 count = quest->ItemDropQuantity[i];
15139 if (!count)
15140 count = std::numeric_limits<uint32>::max();
15141 DestroyItemCount(quest->ItemDrop[i], count, true);
15142 }
15143 }
15144 }
15145
15146 RemoveTimedQuest(quest_id);
15147
15148 if (quest->GetRewItemsCount() > 0)
15149 {
15150 for (uint32 i = 0; i < quest->GetRewItemsCount(); ++i)
15151 {
15152 if (uint32 itemId = quest->RewardItemId[i])
15153 {
15154 ItemPosCountVec dest;
15155 if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, quest->RewardItemCount[i]) == EQUIP_ERR_OK)
15156 {
15157 Item* item = StoreNewItem(dest, itemId, true, GenerateItemRandomBonusListId(itemId), {}, ItemContext::Quest_Reward);
15158 SendNewItem(item, quest->RewardItemCount[i], true, false);
15159 }
15160 else if (quest->IsDFQuest())
15162 }
15163 }
15164 }
15165
15166 CurrencyGainSource currencyGainSource = [&]() -> CurrencyGainSource
15167 {
15169 {
15170 if (quest->IsWorldQuest())
15172
15174 }
15175
15177
15178 if (quest->IsDaily())
15180 else if (quest->IsWeekly())
15182 else if (quest->IsWorldQuest())
15184
15185 return gainSource;
15186 }();
15187
15188 switch (rewardType)
15189 {
15190 case LootItemType::Item:
15191 {
15192 ItemTemplate const* rewardProto = sObjectMgr->GetItemTemplate(rewardId);
15193 if (rewardProto && quest->GetRewChoiceItemsCount())
15194 {
15195 for (uint32 i = 0; i < QUEST_REWARD_CHOICES_COUNT; ++i)
15196 {
15197 if (quest->RewardChoiceItemId[i] && quest->RewardChoiceItemType[i] == LootItemType::Item && quest->RewardChoiceItemId[i] == rewardId)
15198 {
15199 ItemPosCountVec dest;
15200 if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, rewardId, quest->RewardChoiceItemCount[i]) == EQUIP_ERR_OK)
15201 {
15202 Item* item = StoreNewItem(dest, rewardId, true, GenerateItemRandomBonusListId(rewardId), {}, ItemContext::Quest_Reward);
15203 SendNewItem(item, quest->RewardChoiceItemCount[i], true, false);
15204 }
15205 }
15206 }
15207 }
15208
15209 // QuestPackageItem.db2
15210 if (rewardProto && quest->GetQuestPackageID())
15212 break;
15213 }
15215 {
15216 if (sCurrencyTypesStore.HasRecord(rewardId) && quest->GetRewChoiceItemsCount())
15217 for (uint32 i = 0; i < QUEST_REWARD_CHOICES_COUNT; ++i)
15218 if (quest->RewardChoiceItemId[i] && quest->RewardChoiceItemType[i] == LootItemType::Currency && quest->RewardChoiceItemId[i] == rewardId)
15219 AddCurrency(quest->RewardChoiceItemId[i], quest->RewardChoiceItemCount[i], currencyGainSource);
15220
15221 break;
15222 }
15223 default:
15224 break;
15225 }
15226
15227 for (uint8 i = 0; i < QUEST_REWARD_CURRENCY_COUNT; ++i)
15228 if (quest->RewardCurrencyId[i])
15229 AddCurrency(quest->RewardCurrencyId[i], quest->RewardCurrencyCount[i], currencyGainSource);
15230
15231 if (uint32 skill = quest->GetRewardSkillId())
15232 UpdateSkillPro(skill, 1000, quest->GetRewardSkillPoints());
15233
15234 uint16 log_slot = FindQuestSlot(quest_id);
15235 if (log_slot < MAX_QUEST_LOG_SIZE)
15236 SetQuestSlot(log_slot, 0);
15237
15238 uint32 XP = GetQuestXPReward(quest);
15239
15240 int32 moneyRew = 0;
15241 if (!IsMaxLevel())
15242 GiveXP(XP, nullptr);
15243 else
15244 moneyRew = int32(quest->GetRewMoneyMaxLevel() * sWorld->getRate(RATE_DROP_MONEY));
15245
15246 moneyRew += GetQuestMoneyReward(quest);
15247
15248 if (moneyRew)
15249 {
15250 ModifyMoney(moneyRew);
15251
15252 if (moneyRew > 0)
15254
15256 }
15257
15258 // honor reward
15259 if (uint32 honor = quest->CalculateHonorGain(GetLevel()))
15260 RewardHonor(nullptr, 0, honor);
15261
15262 // title reward
15263 if (quest->GetRewTitle())
15264 {
15265 if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(quest->GetRewTitle()))
15266 SetTitle(titleEntry);
15267 }
15268
15269 // Send reward mail
15270 if (uint32 mail_template_id = quest->GetRewMailTemplateId())
15271 {
15273 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
15274 if (uint32 questMailSender = quest->GetRewMailSenderEntry())
15275 MailDraft(mail_template_id).SendMailTo(trans, this, questMailSender, MAIL_CHECK_MASK_HAS_BODY, quest->GetRewMailDelaySecs());
15276 else
15277 MailDraft(mail_template_id).SendMailTo(trans, this, questGiver, MAIL_CHECK_MASK_HAS_BODY, quest->GetRewMailDelaySecs());
15278 CharacterDatabase.CommitTransaction(trans);
15279 }
15280
15281 if (quest->IsDaily() || quest->IsDFQuest())
15282 {
15283 SetDailyQuestStatus(quest_id);
15284 if (quest->IsDaily())
15285 {
15289 }
15290 }
15291 else if (quest->IsWeekly())
15292 SetWeeklyQuestStatus(quest_id);
15293 else if (quest->IsMonthly())
15294 SetMonthlyQuestStatus(quest_id);
15295 else if (quest->IsSeasonal())
15296 SetSeasonalQuestStatus(quest_id);
15297
15298 RemoveActiveQuest(quest_id, false);
15300 SetRewardedQuest(quest_id);
15301
15302 SendQuestReward(quest, questGiver ? questGiver->ToCreature() : nullptr, XP, !announce);
15303
15304 RewardReputation(quest);
15305
15306 // cast spells after mark quest complete (some spells have quest completed state requirements in spell_area data)
15307 if (quest->GetRewSpell() > 0)
15308 {
15309 SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(quest->GetRewSpell(), GetMap()->GetDifficultyID());
15310 Unit* caster = this;
15311 if (questGiver && questGiver->IsUnit() && !quest->HasFlag(QUEST_FLAGS_PLAYER_CAST_COMPLETE) && !spellInfo->HasTargetType(TARGET_UNIT_CASTER))
15312 caster = questGiver->ToUnit();
15313
15314 caster->CastSpell(this, spellInfo->Id, CastSpellExtraArgs(TRIGGERED_FULL_MASK).SetCastDifficulty(spellInfo->Difficulty));
15315 }
15316 else
15317 {
15318 for (QuestRewardDisplaySpell displaySpell : quest->RewardDisplaySpell)
15319 {
15321 continue;
15322
15323 SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(displaySpell.SpellId, GetMap()->GetDifficultyID());
15324 Unit* caster = this;
15325 if (questGiver && questGiver->IsUnit() && !quest->HasFlag(QUEST_FLAGS_PLAYER_CAST_COMPLETE) && !spellInfo->HasTargetType(TARGET_UNIT_CASTER))
15326 caster = questGiver->ToUnit();
15327
15328 caster->CastSpell(this, spellInfo->Id, CastSpellExtraArgs(TRIGGERED_FULL_MASK).SetCastDifficulty(spellInfo->Difficulty));
15329 }
15330 }
15331
15332 if (quest->GetZoneOrSort() > 0)
15337
15338 // make full db save
15339 SaveToDB(false);
15340
15341 SetQuestCompletedBit(quest_id, true);
15342
15343 if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP))
15344 {
15347 }
15348
15349 SendQuestUpdate(quest_id, true, true);
15350
15351 bool updateVisibility = false;
15353 updateVisibility = PhasingHandler::OnConditionChange(this, false);
15354
15355 //lets remove flag for delayed teleports
15356 SetCanDelayTeleport(false);
15357
15358 if (questGiver && questGiver->IsWorldObject())
15359 {
15360 //For AutoSubmition was added plr case there as it almost same exclute AI script cases.
15361 // Send next quest
15362 if (Quest const* nextQuest = GetNextQuest(questGiver, quest))
15363 {
15364 // Only send the quest to the player if the conditions are met
15365 if (CanTakeQuest(nextQuest, false))
15366 {
15367 if (nextQuest->IsAutoAccept() && CanAddQuest(nextQuest, true))
15368 AddQuestAndCheckCompletion(nextQuest, questGiver);
15369
15370 PlayerTalkClass->SendQuestGiverQuestDetails(nextQuest, questGiver->GetGUID(), true, false);
15371 }
15372 }
15373
15374 PlayerTalkClass->ClearMenus();
15375 if (Creature* creatureQGiver = questGiver->ToCreature())
15376 creatureQGiver->AI()->OnQuestReward(this, quest, rewardType, rewardId);
15377 else if (GameObject* goQGiver = questGiver->ToGameObject())
15378 goQGiver->AI()->OnQuestReward(this, quest, rewardType, rewardId);
15379 }
15380
15381 sScriptMgr->OnQuestStatusChange(this, quest_id);
15382 sScriptMgr->OnQuestStatusChange(this, quest, oldStatus, QUEST_STATUS_REWARDED);
15383
15384 if (updateVisibility)
15386}
15387
15389{
15390 m_RewardedQuests.insert(quest_id);
15392
15393 SetQuestCompletedBit(quest_id, true);
15394}
15395
15397{
15398 if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
15399 {
15400 QuestStatus qStatus = GetQuestStatus(questId);
15401
15402 // we can only fail incomplete quest or...
15403 if (qStatus != QUEST_STATUS_INCOMPLETE)
15404 {
15405 // completed timed quest with no requirements
15406 if (qStatus != QUEST_STATUS_COMPLETE || !quest->GetLimitTime() || !quest->GetObjectives().empty())
15407 return;
15408 }
15409
15411
15412 uint16 log_slot = FindQuestSlot(questId);
15413
15414 if (log_slot < MAX_QUEST_LOG_SIZE)
15416
15417 if (quest->GetLimitTime())
15418 {
15419 QuestStatusData& q_status = m_QuestStatus[questId];
15420
15421 RemoveTimedQuest(questId);
15422 q_status.Timer = 0;
15423
15424 SendQuestTimerFailed(questId);
15425 }
15426 else
15427 SendQuestFailed(questId);
15428
15429 // Destroy quest items on quest failure.
15430 for (QuestObjective const& obj : quest->GetObjectives())
15431 if (obj.Type == QUEST_OBJECTIVE_ITEM)
15432 if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(obj.ObjectID))
15433 if (itemTemplate->GetBonding() == BIND_QUEST)
15434 DestroyItemCount(obj.ObjectID, obj.Amount, true, true);
15435
15436 // Destroy items received during the quest.
15437 for (uint8 i = 0; i < QUEST_ITEM_DROP_COUNT; ++i)
15438 if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(quest->ItemDrop[i]))
15439 if (quest->ItemDropQuantity[i] && itemTemplate->GetBonding() == BIND_QUEST)
15440 DestroyItemCount(quest->ItemDrop[i], quest->ItemDropQuantity[i], true, true);
15441 }
15442}
15443
15445{
15446 for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
15447 {
15448 uint32 questId = GetQuestSlotQuestId(slot);
15449 if (!questId)
15450 continue;
15451
15452 if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
15453 if (quest->HasFlag(flag))
15454 FailQuest(questId);
15455 }
15456}
15457
15459{
15460 if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
15461 {
15462 // Destroy quest items on quest abandon.
15463 for (QuestObjective const& obj : quest->GetObjectives())
15464 if (obj.Type == QUEST_OBJECTIVE_ITEM)
15465 if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(obj.ObjectID))
15466 if (itemTemplate->GetBonding() == BIND_QUEST)
15467 DestroyItemCount(obj.ObjectID, obj.Amount, true, true);
15468
15469 // Destroy items received during the quest.
15470 for (uint8 i = 0; i < QUEST_ITEM_DROP_COUNT; ++i)
15471 if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(quest->ItemDrop[i]))
15472 if (quest->ItemDropQuantity[i] && itemTemplate->GetBonding() == BIND_QUEST)
15473 DestroyItemCount(quest->ItemDrop[i], quest->ItemDropQuantity[i], true, true);
15474 }
15475}
15476
15477bool Player::SatisfyQuestSkill(Quest const* qInfo, bool msg) const
15478{
15479 uint32 skill = qInfo->GetRequiredSkill();
15480
15481 // skip 0 case RequiredSkill
15482 if (skill == 0)
15483 return true;
15484
15485 // check skill value
15486 if (GetSkillValue(skill) < qInfo->GetRequiredSkillValue())
15487 {
15488 if (msg)
15489 {
15491 TC_LOG_DEBUG("misc", "Player::SatisfyQuestSkill: Sent QUEST_ERR_NONE (QuestID: {}) because player '{}' ({}) doesn't have the required skill value.",
15492 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15493 }
15494
15495 return false;
15496 }
15497
15498 return true;
15499}
15500
15501bool Player::SatisfyQuestLevel(Quest const* qInfo, bool msg) const
15502{
15503 return SatisfyQuestMinLevel(qInfo, msg) && SatisfyQuestMaxLevel(qInfo, msg);
15504}
15505
15506bool Player::SatisfyQuestMinLevel(Quest const* qInfo, bool msg) const
15507{
15508 if (GetLevel() < GetQuestMinLevel(qInfo))
15509 {
15510 if (msg)
15511 {
15513 TC_LOG_DEBUG("misc", "Player::SatisfyQuestMinLevel: Sent QUEST_ERR_FAILED_LOW_LEVEL (QuestID: {}) because player '{}' ({}) doesn't have the required (min) level.",
15514 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15515 }
15516 return false;
15517 }
15518 return true;
15519}
15520
15521bool Player::SatisfyQuestMaxLevel(Quest const* qInfo, bool msg) const
15522{
15523 if (qInfo->GetMaxLevel() > 0 && GetLevel() > qInfo->GetMaxLevel())
15524 {
15525 if (msg)
15526 {
15527 SendCanTakeQuestResponse(QUEST_ERR_NONE); // There doesn't seem to be a specific response for too high player level
15528 TC_LOG_DEBUG("misc", "Player::SatisfyQuestMaxLevel: Sent QUEST_ERR_FAILED_LOW_LEVEL (QuestID: {}) because player '{}' ({}) doesn't have the required (max) level.",
15529 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15530 }
15531 return false;
15532 }
15533 return true;
15534}
15535
15536bool Player::SatisfyQuestLog(bool msg) const
15537{
15538 // exist free slot
15540 return true;
15541
15542 if (msg)
15543 {
15545 SendDirectMessage(data.Write());
15546 }
15547 return false;
15548}
15549
15550bool Player::SatisfyQuestDependentQuests(Quest const* qInfo, bool msg) const
15551{
15552 return SatisfyQuestPreviousQuest(qInfo, msg) && SatisfyQuestDependentPreviousQuests(qInfo, msg) &&
15554}
15555
15556bool Player::SatisfyQuestPreviousQuest(Quest const* qInfo, bool msg) const
15557{
15558 // No previous quest (might be first quest in a series)
15559 if (!qInfo->GetPrevQuestId())
15560 return true;
15561
15562 uint32 prevId = std::abs(qInfo->GetPrevQuestId());
15563 // If positive previous quest rewarded, return true
15564 if (qInfo->GetPrevQuestId() > 0 && m_RewardedQuests.count(prevId) > 0)
15565 return true;
15566
15567 // If negative previous quest active, return true
15568 if (qInfo->GetPrevQuestId() < 0 && GetQuestStatus(prevId) == QUEST_STATUS_INCOMPLETE)
15569 return true;
15570
15571 // Has positive prev. quest in non-rewarded state
15572 // and negative prev. quest in non-active state
15573 if (msg)
15574 {
15576 TC_LOG_DEBUG("misc", "Player::SatisfyQuestPreviousQuest: Sent QUEST_ERR_NONE (QuestID: {}) because player '{}' ({}) doesn't have required quest {}.",
15577 qInfo->GetQuestId(), GetName(), GetGUID().ToString(), prevId);
15578 }
15579
15580 return false;
15581}
15582
15583bool Player::SatisfyQuestDependentPreviousQuests(Quest const* qInfo, bool msg) const
15584{
15585 // No previous quest (might be first quest in a series)
15586 if (qInfo->DependentPreviousQuests.empty())
15587 return true;
15588
15589 for (uint32 prevId : qInfo->DependentPreviousQuests)
15590 {
15591 // checked in startup
15592 Quest const* questInfo = sObjectMgr->GetQuestTemplate(prevId);
15593 ASSERT(questInfo);
15594
15595 // If any of the previous quests completed, return true
15596 if (IsQuestRewarded(prevId))
15597 {
15598 // skip one-from-all exclusive group
15599 if (questInfo->GetExclusiveGroup() >= 0)
15600 return true;
15601
15602 // each-from-all exclusive group (< 0)
15603 // can be start if only all quests in prev quest exclusive group completed and rewarded
15604 auto bounds = sObjectMgr->GetExclusiveQuestGroupBounds(questInfo->GetExclusiveGroup());
15605 for (auto itr = bounds.first; itr != bounds.second; ++itr)
15606 {
15607 // skip checked quest id, only state of other quests in group is interesting
15608 uint32 exclusiveQuestId = itr->second;
15609 if (exclusiveQuestId == prevId)
15610 continue;
15611
15612 // alternative quest from group also must be completed and rewarded (reported)
15613 if (!IsQuestRewarded(exclusiveQuestId))
15614 {
15615 if (msg)
15616 {
15618 TC_LOG_DEBUG("misc", "Player::SatisfyQuestDependentPreviousQuests: Sent QUEST_ERR_NONE (QuestID: {}) because player '{}' ({}) doesn't have the required quest (1).",
15619 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15620 }
15621
15622 return false;
15623 }
15624 }
15625
15626 return true;
15627 }
15628 }
15629
15630 // Has only prev. quests in non-rewarded state
15631 if (msg)
15632 {
15634 TC_LOG_DEBUG("misc", "Player::SatisfyQuestDependentPreviousQuests: Sent QUEST_ERR_NONE (QuestID: {}) because player '{}' ({}) doesn't have required quest (2).",
15635 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15636 }
15637
15638 return false;
15639}
15640
15641bool Player::SatisfyQuestBreadcrumbQuest(Quest const* qInfo, bool msg) const
15642{
15643 uint32 breadcrumbTargetQuestId = std::abs(qInfo->GetBreadcrumbForQuestId());
15644
15645 //If this is not a breadcrumb quest.
15646 if (!breadcrumbTargetQuestId)
15647 return true;
15648
15649 // If the target quest is not available
15650 if (!CanTakeQuest(sObjectMgr->GetQuestTemplate(breadcrumbTargetQuestId), false))
15651 {
15652 if (msg)
15653 {
15655 TC_LOG_DEBUG("misc", "Player::SatisfyQuestBreadcrumbQuest: Sent INVALIDREASON_DONT_HAVE_REQ (QuestID: {}) because target quest (QuestID: {}) is not available to player '{}' ({}).",
15656 qInfo->GetQuestId(), breadcrumbTargetQuestId, GetName(), GetGUID().ToString());
15657 }
15658
15659 return false;
15660 }
15661
15662 return true;
15663}
15664
15666{
15667 for (uint32 breadcrumbQuestId : qInfo->DependentBreadcrumbQuests)
15668 {
15669 QuestStatus status = GetQuestStatus(breadcrumbQuestId);
15670 // If any of the breadcrumb quests are in the quest log, return false.
15671 if (status == QUEST_STATUS_INCOMPLETE || status == QUEST_STATUS_COMPLETE || status == QUEST_STATUS_FAILED)
15672 {
15673 if (msg)
15674 {
15676 TC_LOG_DEBUG("misc", "Player::SatisfyQuestDependentBreadcrumbQuests: Sent INVALIDREASON_DONT_HAVE_REQ (QuestID: {}) because player '{}' ({}) has a breadcrumb quest towards this quest in the quest log.",
15677 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15678 }
15679
15680 return false;
15681 }
15682 }
15683 return true;
15684}
15685
15686bool Player::SatisfyQuestClass(Quest const* qInfo, bool msg) const
15687{
15688 uint32 reqClass = qInfo->GetAllowableClasses();
15689
15690 if (reqClass == 0)
15691 return true;
15692
15693 if ((reqClass & GetClassMask()) == 0)
15694 {
15695 if (msg)
15696 {
15698 TC_LOG_DEBUG("misc", "Player::SatisfyQuestClass: Sent QUEST_ERR_NONE (QuestID: {}) because player '{}' ({}) doesn't have required class.",
15699 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15700 }
15701
15702 return false;
15703 }
15704
15705 return true;
15706}
15707
15708bool Player::SatisfyQuestRace(Quest const* qInfo, bool msg) const
15709{
15710 if (!qInfo->GetAllowableRaces().HasRace(GetRace()))
15711 {
15712 if (msg)
15713 {
15715 TC_LOG_DEBUG("misc", "Player::SatisfyQuestRace: Sent QUEST_ERR_FAILED_WRONG_RACE (QuestID: {}) because player '{}' ({}) doesn't have required race.",
15716 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15717
15718 }
15719 return false;
15720 }
15721 return true;
15722}
15723
15724bool Player::SatisfyQuestMinReputation(Quest const* qInfo, bool msg) const
15725{
15726 uint32 fIdMin = qInfo->GetRequiredMinRepFaction(); //Min required rep
15727 if (fIdMin && GetReputationMgr().GetReputation(fIdMin) < qInfo->GetRequiredMinRepValue())
15728 {
15729 if (msg)
15730 {
15732 TC_LOG_DEBUG("misc", "Player::SatisfyQuestReputation: Sent QUEST_ERR_NONE (QuestID: {}) because player '{}' ({}) doesn't required reputation (min).",
15733 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15734 }
15735 return false;
15736 }
15737
15738 return true;
15739}
15740
15741bool Player::SatisfyQuestMaxReputation(Quest const* qInfo, bool msg) const
15742{
15743 uint32 fIdMax = qInfo->GetRequiredMaxRepFaction(); //Max required rep
15744 if (fIdMax && GetReputationMgr().GetReputation(fIdMax) >= qInfo->GetRequiredMaxRepValue())
15745 {
15746 if (msg)
15747 {
15749 TC_LOG_DEBUG("misc", "SatisfyQuestReputation: Sent QUEST_ERR_NONE (QuestID: {}) because player '{}' ({}) doesn't required reputation (max).",
15750 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15751 }
15752 return false;
15753 }
15754
15755 return true;
15756}
15757
15758bool Player::SatisfyQuestReputation(Quest const* qInfo, bool msg) const
15759{
15760 return SatisfyQuestMinReputation(qInfo, msg) && SatisfyQuestMaxReputation(qInfo, msg);
15761}
15762
15763bool Player::SatisfyQuestStatus(Quest const* qInfo, bool msg) const
15764{
15766 {
15767 if (msg)
15768 {
15770 TC_LOG_DEBUG("misc", "Player::SatisfyQuestStatus: Sent QUEST_STATUS_REWARDED (QuestID: {}) because player '{}' ({}) quest status is already REWARDED.",
15771 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15772 }
15773 return false;
15774 }
15775
15777 {
15778 if (msg)
15779 {
15781 TC_LOG_DEBUG("misc", "Player::SatisfyQuestStatus: Sent QUEST_ERR_ALREADY_ON1 (QuestID: {}) because player '{}' ({}) quest status is not NONE.",
15782 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15783 }
15784 return false;
15785 }
15786 return true;
15787}
15788
15789bool Player::SatisfyQuestConditions(Quest const* qInfo, bool msg) const
15790{
15791 if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_QUEST_AVAILABLE, qInfo->GetQuestId(), this))
15792 {
15793 if (msg)
15794 {
15796 TC_LOG_DEBUG("misc", "Player::SatisfyQuestConditions: Sent QUEST_ERR_NONE (QuestID: {}) because player '{}' ({}) doesn't meet conditions.",
15797 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15798 }
15799 TC_LOG_DEBUG("condition", "Player::SatisfyQuestConditions: conditions not met for quest {}", qInfo->GetQuestId());
15800 return false;
15801 }
15802 return true;
15803}
15804
15805bool Player::SatisfyQuestTimed(Quest const* qInfo, bool msg) const
15806{
15807 if (!m_timedquests.empty() && qInfo->GetLimitTime())
15808 {
15809 if (msg)
15810 {
15812 TC_LOG_DEBUG("misc", "Player::SatisfyQuestTimed: Sent QUEST_ERR_ONLY_ONE_TIMED (QuestID: {}) because player '{}' ({}) is already on a timed quest.",
15813 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15814 }
15815 return false;
15816 }
15817 return true;
15818}
15819
15820bool Player::SatisfyQuestExclusiveGroup(Quest const* qInfo, bool msg) const
15821{
15822 // non positive exclusive group, if > 0 then can be start if any other quest in exclusive group already started/completed
15823 if (qInfo->GetExclusiveGroup() <= 0)
15824 return true;
15825
15826 auto bounds = sObjectMgr->GetExclusiveQuestGroupBounds(qInfo->GetExclusiveGroup());
15827 for (auto itr = bounds.first; itr != bounds.second; ++itr)
15828 {
15829 uint32 exclude_Id = itr->second;
15830
15831 // skip checked quest id, only state of other quests in group is interesting
15832 if (exclude_Id == qInfo->GetQuestId())
15833 continue;
15834
15835 // not allow have daily quest if daily quest from exclusive group already recently completed
15836 Quest const* Nquest = sObjectMgr->GetQuestTemplate(exclude_Id);
15837 ASSERT(Nquest);
15838 if (!SatisfyQuestDay(Nquest, false) || !SatisfyQuestWeek(Nquest, false) || !SatisfyQuestSeasonal(Nquest, false))
15839 {
15840 if (msg)
15841 {
15843 TC_LOG_DEBUG("misc", "Player::SatisfyQuestExclusiveGroup: Sent QUEST_ERR_NONE (QuestID: {}) because player '{}' ({}) already did daily quests in exclusive group.",
15844 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15845 }
15846
15847 return false;
15848 }
15849
15850 // alternative quest already started or completed - but don't check rewarded states if both are repeatable
15851 if (GetQuestStatus(exclude_Id) != QUEST_STATUS_NONE || (!(qInfo->IsRepeatable() && Nquest->IsRepeatable()) && GetQuestRewardStatus(exclude_Id)))
15852 {
15853 if (msg)
15854 {
15856 TC_LOG_DEBUG("misc", "Player::SatisfyQuestExclusiveGroup: Sent QUEST_ERR_NONE (QuestID: {}) because player '{}' ({}) already did quest in exclusive group.",
15857 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15858 }
15859 return false;
15860 }
15861 }
15862 return true;
15863}
15864
15865bool Player::SatisfyQuestDay(Quest const* qInfo, bool /*msg*/) const
15866{
15867 if (!qInfo->IsDaily() && !qInfo->IsDFQuest())
15868 return true;
15869
15870 if (qInfo->IsDFQuest())
15871 {
15872 if (m_DFQuests.find(qInfo->GetQuestId()) != m_DFQuests.end())
15873 return false;
15874
15875 return true;
15876 }
15877
15878 return m_activePlayerData->DailyQuestsCompleted.FindIndex(qInfo->GetQuestId()) == -1;
15879}
15880
15881bool Player::SatisfyQuestWeek(Quest const* qInfo, bool /*msg*/) const
15882{
15883 if (!qInfo->IsWeekly() || m_weeklyquests.empty())
15884 return true;
15885
15886 // if not found in cooldown list
15887 return m_weeklyquests.find(qInfo->GetQuestId()) == m_weeklyquests.end();
15888}
15889
15890bool Player::SatisfyQuestSeasonal(Quest const* qInfo, bool /*msg*/) const
15891{
15892 if (!qInfo->IsSeasonal() || m_seasonalquests.empty())
15893 return true;
15894
15895 auto itr = m_seasonalquests.find(qInfo->GetEventIdForQuest());
15896 if (itr == m_seasonalquests.end() || itr->second.empty())
15897 return true;
15898
15899 // if not found in cooldown list
15900 return itr->second.find(qInfo->GetQuestId()) == itr->second.end();
15901}
15902
15903bool Player::SatisfyQuestExpansion(Quest const* qInfo, bool msg) const
15904{
15905 if (GetSession()->GetExpansion() < qInfo->GetExpansion())
15906 {
15907 if (msg)
15909
15910 TC_LOG_DEBUG("misc", "Player::SatisfyQuestExpansion: Sent QUEST_ERR_FAILED_EXPANSION (QuestID: {}) because player '{}' ({}) does not have required expansion.",
15911 qInfo->GetQuestId(), GetName(), GetGUID().ToString());
15912 return false;
15913 }
15914 return true;
15915}
15916
15917bool Player::SatisfyQuestMonth(Quest const* qInfo, bool /*msg*/) const
15918{
15919 if (!qInfo->IsMonthly() || m_monthlyquests.empty())
15920 return true;
15921
15922 // if not found in cooldown list
15923 return m_monthlyquests.find(qInfo->GetQuestId()) == m_monthlyquests.end();
15924}
15925
15927{
15928 uint32 srcitem = quest->GetSrcItemId();
15929 if (srcitem > 0)
15930 {
15931 // Don't give source item if it is the same item used to start the quest
15932 ItemTemplate const* itemTemplate = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(srcitem));
15933 if (quest->GetQuestId() == itemTemplate->GetStartQuest())
15934 return true;
15935
15936 uint32 count = quest->GetSrcItemCount();
15937 if (count <= 0)
15938 count = 1;
15939
15940 ItemPosCountVec dest;
15941 InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, srcitem, count);
15942 if (msg == EQUIP_ERR_OK)
15943 {
15944 Item* item = StoreNewItem(dest, srcitem, true);
15945 SendNewItem(item, count, true, false);
15946 return true;
15947 }
15948 // player already have max amount required item, just report success
15949 if (msg == EQUIP_ERR_ITEM_MAX_COUNT)
15950 return true;
15951
15952 SendEquipError(msg, nullptr, nullptr, srcitem);
15953 return false;
15954 }
15955
15956 return true;
15957}
15958
15960{
15961 Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
15962 if (quest)
15963 {
15964 uint32 srcItemId = quest->GetSrcItemId();
15965 ItemTemplate const* item = sObjectMgr->GetItemTemplate(srcItemId);
15966
15967 if (srcItemId > 0)
15968 {
15969 uint32 count = quest->GetSrcItemCount();
15970 if (count <= 0)
15971 count = 1;
15972
15973 // There are two cases where the source item is not destroyed:
15974 // - Item cannot be unequipped (example: non-empty bags)
15975 // - The source item is the item that started the quest, so the player is supposed to keep it (otherwise it was already destroyed in AddQuestAndCheckCompletion())
15976 InventoryResult res = CanUnequipItems(srcItemId, count);
15977 if (res != EQUIP_ERR_OK)
15978 {
15979 if (msg)
15980 SendEquipError(res, nullptr, nullptr, srcItemId);
15981 return false;
15982 }
15983
15984 ASSERT(item);
15985 if (item->GetStartQuest() != questId)
15986 DestroyItemCount(srcItemId, count, true, true);
15987 }
15988 }
15989
15990 return true;
15991}
15992
15994{
15995 Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
15996 if (qInfo)
15997 {
15998 if (qInfo->IsSeasonal() && !qInfo->IsRepeatable())
15999 return !SatisfyQuestSeasonal(qInfo, false);
16000
16001 // for repeatable quests: rewarded field is set after first reward only to prevent getting XP more than once
16002 if (!qInfo->IsRepeatable())
16003 return IsQuestRewarded(quest_id);
16004
16005 return false;
16006 }
16007 return false;
16008}
16009
16011{
16012 if (quest_id)
16013 {
16014 QuestStatusMap::const_iterator itr = m_QuestStatus.find(quest_id);
16015 if (itr != m_QuestStatus.end())
16016 return itr->second.Status;
16017
16018 if (GetQuestRewardStatus(quest_id))
16019 return QUEST_STATUS_REWARDED;
16020 }
16021 return QUEST_STATUS_NONE;
16022}
16023
16024bool Player::CanShareQuest(uint32 quest_id) const
16025{
16026 Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
16027 if (qInfo && qInfo->HasFlag(QUEST_FLAGS_SHARABLE))
16028 {
16029 QuestStatusMap::const_iterator itr = m_QuestStatus.find(quest_id);
16030 return (itr != m_QuestStatus.end());
16031 }
16032 return false;
16033}
16034
16035void Player::SetQuestStatus(uint32 questId, QuestStatus status, bool update /*= true*/)
16036{
16037 if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId))
16038 {
16039 QuestStatus oldStatus = m_QuestStatus[questId].Status;
16040 m_QuestStatus[questId].Status = status;
16041
16042 if (!quest->IsTurnIn())
16044
16045 sScriptMgr->OnQuestStatusChange(this, questId);
16046 sScriptMgr->OnQuestStatusChange(this, quest, oldStatus, status);
16047 }
16048
16049 if (update)
16050 SendQuestUpdate(questId);
16051}
16052
16053void Player::RemoveActiveQuest(uint32 questId, bool update /*= true*/)
16054{
16055 QuestStatusMap::iterator itr = m_QuestStatus.find(questId);
16056 if (itr != m_QuestStatus.end())
16057 {
16058 for (auto objectiveItr = m_questObjectiveStatus.begin(); objectiveItr != m_questObjectiveStatus.end(); )
16059 {
16060 if (objectiveItr->second.QuestStatusItr == itr)
16061 objectiveItr = m_questObjectiveStatus.erase(objectiveItr);
16062 else
16063 ++objectiveItr;
16064 }
16065 m_QuestStatus.erase(itr);
16067 }
16068
16069 Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
16070 bool updateVisibility = false;
16071
16072 if (update)
16073 {
16074 SendQuestUpdate(questId);
16075
16076 if (quest && quest->HasFlag(QUEST_FLAGS_UPDATE_PHASESHIFT))
16077 updateVisibility = PhasingHandler::OnConditionChange(this, false);
16078 }
16079
16080 if (updateVisibility)
16082}
16083
16084void Player::RemoveRewardedQuest(uint32 questId, bool update /*= true*/)
16085{
16086 RewardedQuestSet::iterator rewItr = m_RewardedQuests.find(questId);
16087 if (rewItr != m_RewardedQuests.end())
16088 {
16089 m_RewardedQuests.erase(rewItr);
16091 }
16092
16093 SetQuestCompletedBit(questId, false);
16094
16095 // Remove seasonal quest also
16096 Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
16097 bool updateVisibility = false;
16098
16099 ASSERT(quest);
16100
16101 if (quest->IsSeasonal())
16102 {
16103 uint16 eventId = quest->GetEventIdForQuest();
16104 if (m_seasonalquests.find(eventId) != m_seasonalquests.end())
16105 {
16106 m_seasonalquests[eventId].erase(questId);
16108 }
16109 }
16110
16111 if (update)
16112 {
16113 SendQuestUpdate(questId);
16114
16115 if (quest && quest->HasFlag(QUEST_FLAGS_UPDATE_PHASESHIFT))
16116 updateVisibility = PhasingHandler::OnConditionChange(this, false);
16117 }
16118
16119 if (updateVisibility)
16121}
16122
16123void Player::SendQuestUpdate(uint32 questId, bool updateInteractions /*= true*/, bool updateGameObjectQuestGiverStatus /*= false*/)
16124{
16125 SpellAreaForQuestMapBounds saBounds = sSpellMgr->GetSpellAreaForQuestMapBounds(questId);
16126
16127 if (saBounds.first != saBounds.second)
16128 {
16129 std::set<uint32> aurasToRemove, aurasToCast;
16130 uint32 zone = 0, area = 0;
16131 GetZoneAndAreaId(zone, area);
16132
16133 for (SpellAreaForQuestMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
16134 {
16135 if (itr->second->flags & SPELL_AREA_FLAG_AUTOREMOVE && !itr->second->IsFitToRequirements(this, zone, area))
16136 aurasToRemove.insert(itr->second->spellId);
16137 else if (itr->second->flags & SPELL_AREA_FLAG_AUTOCAST && !(itr->second->flags & SPELL_AREA_FLAG_IGNORE_AUTOCAST_ON_QUEST_STATUS_CHANGE))
16138 aurasToCast.insert(itr->second->spellId);
16139 }
16140
16141 // Auras matching the requirements will be inside the aurasToCast container.
16142 // Auras not matching the requirements may prevent using auras matching the requirements.
16143 // aurasToCast will erase conflicting auras in aurasToRemove container to handle spells used by multiple quests.
16144
16145 for (auto itr = aurasToRemove.begin(); itr != aurasToRemove.end();)
16146 {
16147 bool auraRemoved = false;
16148
16149 for (const auto i : aurasToCast)
16150 {
16151 if (*itr == i)
16152 {
16153 itr = aurasToRemove.erase(itr);
16154 auraRemoved = true;
16155 break;
16156 }
16157 }
16158
16159 if (!auraRemoved)
16160 ++itr;
16161 }
16162
16163 for (auto spellId : aurasToCast)
16164 if (!HasAura(spellId))
16165 CastSpell(this, spellId, true);
16166
16167 for (auto spellId : aurasToRemove)
16168 RemoveAurasDueToSpell(spellId);
16169 }
16170
16171 if (updateInteractions)
16172 UpdateVisibleObjectInteractions(true, false, updateGameObjectQuestGiverStatus, true);
16173}
16174
16176{
16177 QuestRelationResult qr, qir;
16178
16179 switch (questgiver->GetTypeId())
16180 {
16181 case TYPEID_GAMEOBJECT:
16182 {
16183 if (GameObjectAI* ai = questgiver->ToGameObject()->AI())
16184 if (Optional<QuestGiverStatus> questStatus = ai->GetDialogStatus(this))
16185 return *questStatus;
16186 qr = sObjectMgr->GetGOQuestRelations(questgiver->GetEntry());
16187 qir = sObjectMgr->GetGOQuestInvolvedRelations(questgiver->GetEntry());
16188 break;
16189 }
16190 case TYPEID_UNIT:
16191 {
16192 Creature const* questGiverCreature = questgiver->ToCreature();
16193 if (!questGiverCreature->IsInteractionAllowedWhileHostile() && questGiverCreature->IsHostileTo(this))
16195
16196 if (!questGiverCreature->IsInteractionAllowedInCombat() && questGiverCreature->IsInCombat())
16198
16199 if (CreatureAI* ai = questgiver->ToCreature()->AI())
16200 if (Optional<QuestGiverStatus> questStatus = ai->GetDialogStatus(this))
16201 return *questStatus;
16202 qr = sObjectMgr->GetCreatureQuestRelations(questgiver->GetEntry());
16203 qir = sObjectMgr->GetCreatureQuestInvolvedRelations(questgiver->GetEntry());
16204 break;
16205 }
16206 default:
16207 // it's impossible, but check
16208 TC_LOG_ERROR("entities.player.quest", "Player::GetQuestDialogStatus: Called with unexpected type (Entry: {}, Type: {}) by player '{}' ({})",
16209 questgiver->GetEntry(), questgiver->GetTypeId(), GetName(), GetGUID().ToString());
16211 }
16212
16214
16215 for (uint32 questId : qir)
16216 {
16217 Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
16218 if (!quest)
16219 continue;
16220
16221 switch (GetQuestStatus(questId))
16222 {
16224 if (quest->IsImportant())
16226 else if (quest->IsMeta())
16228 else if (quest->GetQuestTag() == QuestTagType::CovenantCalling)
16230 else if (quest->HasFlagEx(QUEST_FLAGS_EX_LEGENDARY))
16232 else if (quest->IsDailyOrWeekly())
16234 else
16236 break;
16238 if (quest->IsImportant())
16240 else if (quest->IsMeta())
16242 else if (quest->GetQuestTag() == QuestTagType::CovenantCalling)
16244 else if (quest->HasFlagEx(QUEST_FLAGS_EX_LEGENDARY))
16246 else if (quest->IsDailyOrWeekly())
16248 else
16249 result |= QuestGiverStatus::Reward;
16250 break;
16251 default:
16252 break;
16253 }
16254
16255 if (quest->IsTurnIn() && CanTakeQuest(quest, false))
16256 {
16257 bool isTrivial = GetLevel() > (GetQuestLevel(quest) + sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF));
16258 if (quest->IsRepeatable())
16260 else
16261 result |= isTrivial ? QuestGiverStatus::Trivial : QuestGiverStatus::Quest;
16262 }
16263 }
16264
16265 for (uint32 questId : qr)
16266 {
16267 Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
16268 if (!quest)
16269 continue;
16270
16271 if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_QUEST_AVAILABLE, quest->GetQuestId(), this))
16272 continue;
16273
16274 if (GetQuestStatus(questId) == QUEST_STATUS_NONE)
16275 {
16276 if (CanSeeStartQuest(quest))
16277 {
16278 if (SatisfyQuestLevel(quest, false))
16279 {
16280 bool isTrivial = GetLevel() > (GetQuestLevel(quest) + sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF));
16281 if (quest->IsImportant())
16283 else if (quest->IsMeta())
16285 else if (quest->GetQuestTag() == QuestTagType::CovenantCalling)
16287 else if (quest->HasFlagEx(QUEST_FLAGS_EX_LEGENDARY))
16289 else if (quest->IsDailyOrWeekly())
16291 else
16292 result |= isTrivial ? QuestGiverStatus::Trivial : QuestGiverStatus::Quest;
16293 }
16294 else if (quest->IsImportant())
16296 else if (quest->HasFlagEx(QUEST_FLAGS_EX_LEGENDARY))
16298 else
16299 result |= QuestGiverStatus::Future;
16300 }
16301 }
16302 }
16303
16304 return result;
16305}
16306
16307void Player::SkipQuests(std::vector<uint32> const& questIds)
16308{
16309 bool updateVisibility = false;
16310 for (uint32 const& questId : questIds)
16311 {
16312 Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
16313 if (!quest)
16314 return;
16315
16316 uint16 questSlot = FindQuestSlot(questId);
16317 QuestStatus oldStatus = GetQuestStatus(questSlot);
16318
16319 if (questSlot != MAX_QUEST_LOG_SIZE)
16320 {
16321 if (quest->GetLimitTime())
16322 RemoveTimedQuest(questId);
16323
16324 if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP))
16325 {
16328 }
16329
16330 SetQuestSlot(questSlot, 0);
16331 TakeQuestSourceItem(questId, true); // remove quest src item from player
16332 AbandonQuest(questId); // remove all quest items player received before abandoning quest. Note, this does not remove normal drop items that happen to be quest requirements.
16333 RemoveActiveQuest(questId);
16334 }
16335
16336 SetRewardedQuest(questId);
16337 SendQuestUpdate(questId, false);
16338
16339 if (!updateVisibility && quest->HasFlag(QUEST_FLAGS_UPDATE_PHASESHIFT))
16340 updateVisibility = PhasingHandler::OnConditionChange(this, false);
16341
16342 sScriptMgr->OnQuestStatusChange(this, questId);
16343 sScriptMgr->OnQuestStatusChange(this, quest, oldStatus, QUEST_STATUS_REWARDED);
16344 }
16345
16346 UpdateVisibleObjectInteractions(true, false, true, true);
16347
16348 // make full db save
16349 SaveToDB(false);
16350
16351 if (updateVisibility)
16353}
16354
16356{
16357 std::list<Creature*> creatureList;
16358 GetCreatureListWithOptionsInGrid(creatureList, 100.0f, { .IgnorePhases = true, .PrivateObjectOwnerGuid = GetGUID() }); // we might want to replace this with SummonList in Player at some point
16359
16360 for (Creature* creature : creatureList)
16361 {
16362 CreatureSummonedData const* summonedData = sObjectMgr->GetCreatureSummonedData(creature->GetEntry());
16363 if (!summonedData)
16364 continue;
16365
16366 if (summonedData->DespawnOnQuestsRemoved)
16367 {
16368 if (std::find(summonedData->DespawnOnQuestsRemoved->begin(), summonedData->DespawnOnQuestsRemoved->end(), questId) != summonedData->DespawnOnQuestsRemoved->end())
16369 creature->DespawnOrUnsummon();
16370 }
16371 }
16372}
16373
16374// not used in Trinity, but used in scripting code
16376{
16377 Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest_id);
16378 if (!qInfo)
16379 return 0;
16380
16381 uint16 slot = FindQuestSlot(quest_id);
16382 if (slot >= MAX_QUEST_LOG_SIZE)
16383 return 0;
16384
16385 for (QuestObjective const& obj : qInfo->GetObjectives())
16386 if (obj.ObjectID == entry)
16387 return GetQuestSlotObjectiveData(slot, obj);
16388
16389 return 0;
16390}
16391
16393{
16394 // adjust progress of quest objectives that rely on external counters, like items
16395 for (QuestObjective const& obj : quest->GetObjectives())
16396 {
16397 switch (obj.Type)
16398 {
16400 if (!(obj.Flags2 & QUEST_OBJECTIVE_FLAG_2_QUEST_BOUND_ITEM))
16401 {
16402 uint32 reqItemCount = obj.Amount;
16403 uint32 curItemCount = GetItemCount(obj.ObjectID, true);
16404 SetQuestObjectiveData(obj, std::min(curItemCount, reqItemCount));
16405 }
16406 break;
16408 {
16409 uint32 reqCurrencyCount = obj.Amount;
16410 uint32 curCurrencyCount = GetCurrencyQuantity(obj.ObjectID);
16411 SetQuestObjectiveData(obj, std::min(reqCurrencyCount, curCurrencyCount));
16412 break;
16413 }
16415 if (m_questObjectiveCriteriaMgr->HasCompletedObjective(&obj))
16416 SetQuestObjectiveData(obj, 1);
16417 break;
16418 default:
16419 break;
16420 }
16421 }
16422}
16423
16425{
16426 for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
16427 if (GetQuestSlotQuestId(i) == quest_id)
16428 return i;
16429
16430 return MAX_QUEST_LOG_SIZE;
16431}
16432
16434{
16435 return m_playerData->QuestLog[slot].QuestID;
16436}
16437
16439{
16440 return m_playerData->QuestLog[slot].StateFlags;
16441}
16442
16444{
16445 if (counter < MAX_QUEST_COUNTS)
16446 return m_playerData->QuestLog[slot].ObjectiveProgress[counter];
16447 return 0;
16448}
16449
16451{
16452 return m_playerData->QuestLog[slot].EndTime;
16453}
16454
16455bool Player::GetQuestSlotObjectiveFlag(uint16 slot, int8 objectiveIndex) const
16456{
16457 if (objectiveIndex < MAX_QUEST_COUNTS)
16458 return (*m_playerData->QuestLog[slot].ObjectiveFlags) & (1 << objectiveIndex);
16459 return false;
16460}
16461
16463{
16464 if (objective.StorageIndex < 0)
16465 {
16466 TC_LOG_ERROR("entities.player.quest", "Player::GetQuestObjectiveData: Called for quest {} with invalid StorageIndex {} (objective data is not tracked)",
16467 objective.QuestID, int32(objective.StorageIndex));
16468 return 0;
16469 }
16470
16471 if (objective.StorageIndex >= MAX_QUEST_COUNTS)
16472 {
16473 TC_LOG_ERROR("entities.player.quest", "Player::GetQuestObjectiveData: Player '{}' ({}) quest {} out of range StorageIndex {}",
16474 GetName(), GetGUID().ToString(), objective.QuestID, uint32(objective.StorageIndex));
16475 return 0;
16476 }
16477
16478 if (!objective.IsStoringFlag())
16479 return GetQuestSlotCounter(slot, objective.StorageIndex);
16480
16481 return GetQuestSlotObjectiveFlag(slot, objective.StorageIndex) ? 1 : 0;
16482}
16483
16485{
16486 uint16 slot = FindQuestSlot(questId);
16487 if (slot >= MAX_QUEST_LOG_SIZE)
16488 return 0;
16489
16490 QuestObjective const* obj = sObjectMgr->GetQuestObjective(objectiveId);
16491 if (!obj)
16492 return 0;
16493
16494 return GetQuestSlotObjectiveData(slot, *obj);
16495}
16496
16498{
16499 auto questLogField = m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::QuestLog, slot);
16500 SetUpdateFieldValue(questLogField.ModifyValue(&UF::QuestLog::QuestID), quest_id);
16501 SetUpdateFieldValue(questLogField.ModifyValue(&UF::QuestLog::StateFlags), 0);
16502 SetUpdateFieldValue(questLogField.ModifyValue(&UF::QuestLog::EndTime), 0);
16503 SetUpdateFieldValue(questLogField.ModifyValue(&UF::QuestLog::ObjectiveFlags), 0);
16504 for (uint32 i = 0; i < MAX_QUEST_COUNTS; ++i)
16505 SetUpdateFieldValue(questLogField.ModifyValue(&UF::QuestLog::ObjectiveProgress, i), 0);
16506}
16507
16509{
16510 if (counter >= MAX_QUEST_COUNTS)
16511 return;
16512
16514 .ModifyValue(&UF::PlayerData::QuestLog, slot)
16515 .ModifyValue(&UF::QuestLog::ObjectiveProgress, counter), count);
16516}
16517
16519{
16521 .ModifyValue(&UF::PlayerData::QuestLog, slot)
16522 .ModifyValue(&UF::QuestLog::StateFlags), state);
16523}
16524
16526{
16528 .ModifyValue(&UF::PlayerData::QuestLog, slot)
16529 .ModifyValue(&UF::QuestLog::StateFlags), state);
16530}
16531
16532void Player::SetQuestSlotEndTime(uint16 slot, time_t endTime)
16533{
16535 .ModifyValue(&UF::PlayerData::QuestLog, slot)
16536 .ModifyValue(&UF::QuestLog::EndTime), uint32(endTime));
16537}
16538
16540{
16542 .ModifyValue(&UF::PlayerData::QuestLog, slot)
16543 .ModifyValue(&UF::QuestLog::ObjectiveFlags), 1 << objectiveIndex);
16544}
16545
16547{
16549 .ModifyValue(&UF::PlayerData::QuestLog, slot)
16550 .ModifyValue(&UF::QuestLog::ObjectiveFlags), 1 << objectiveIndex);
16551}
16552
16554{
16555 uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(questId);
16556 if (!questBit)
16557 return false;
16558
16559 uint32 fieldOffset = (questBit - 1) / QUESTS_COMPLETED_BITS_PER_BLOCK;
16560 if (fieldOffset >= m_activePlayerData->BitVectors->Values[PLAYER_DATA_FLAG_CHARACTER_QUEST_COMPLETED_INDEX].Values.size())
16561 return false;
16562
16563 uint64 flag = UI64LIT(1) << ((questBit - 1) % QUESTS_COMPLETED_BITS_PER_BLOCK);
16564 return (m_activePlayerData->BitVectors->Values[PLAYER_DATA_FLAG_CHARACTER_QUEST_COMPLETED_INDEX].Values[fieldOffset] & flag) != 0;
16565}
16566
16567void Player::SetQuestCompletedBit(uint32 questId, bool completed)
16568{
16569 uint32 questBit = sDB2Manager.GetQuestUniqueBitFlag(questId);
16570 if (!questBit)
16571 return;
16572
16573 uint32 fieldOffset = (questBit - 1) / QUESTS_COMPLETED_BITS_PER_BLOCK;
16574 uint64 flag = UI64LIT(1) << ((questBit - 1) % QUESTS_COMPLETED_BITS_PER_BLOCK);
16575 if (fieldOffset < QUESTS_COMPLETED_BITS_SIZE)
16576 {
16577 if (completed)
16579 else
16581 }
16582
16583 if (completed)
16585 .ModifyValue(&Player::m_activePlayerData)
16588 .ModifyValue(&UF::BitVector::Values, fieldOffset), flag);
16589 else
16591 .ModifyValue(&Player::m_activePlayerData)
16594 .ModifyValue(&UF::BitVector::Values, fieldOffset), flag);
16595}
16596
16598{
16599 if (questId)
16600 {
16602 {
16603 // Dont complete failed quest
16604 if (!status->Explored && status->Status != QUEST_STATUS_FAILED)
16605 {
16606 status->Explored = true;
16608
16609 SendQuestComplete(questId);
16610 }
16611 }
16612 if (CanCompleteQuest(questId))
16613 CompleteQuest(questId);
16614 }
16615}
16616
16617//not used in Trinityd, function for external script library
16618void Player::GroupEventHappens(uint32 questId, WorldObject const* pEventObject)
16619{
16620 if (Group* group = GetGroup())
16621 {
16622 for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
16623 {
16624 Player* player = itr->GetSource();
16625
16626 // for any leave or dead (with not released body) group member at appropriate distance
16627 if (player && player->IsAtGroupRewardDistance(pEventObject) && !player->GetCorpse())
16628 player->AreaExploredOrEventHappens(questId);
16629 }
16630 }
16631 else
16633}
16634
16635namespace
16636{
16637struct
16638{
16639 std::function<bool(QuestObjective const*)> QuestBoundItem = [](QuestObjective const* objective) { return (objective->Flags2 & QUEST_OBJECTIVE_FLAG_2_QUEST_BOUND_ITEM) != 0; };
16640 std::function<bool(QuestObjective const*)> NotQuestBoundItem = [](QuestObjective const* objective) { return (objective->Flags2 & QUEST_OBJECTIVE_FLAG_2_QUEST_BOUND_ITEM) == 0; };
16641} const ItemQuestObjectiveFilters;
16642}
16643
16644void Player::ItemAddedQuestCheck(uint32 entry, uint32 count, Optional<bool> boundItemFlagRequirement /*= {}*/, bool* hadBoundItemObjective /*= nullptr*/)
16645{
16646 std::vector<QuestObjective const*> updatedObjectives;
16647 std::function<bool(QuestObjective const*)> const* objectiveFilter = nullptr;
16648 if (boundItemFlagRequirement)
16649 objectiveFilter = *boundItemFlagRequirement ? &ItemQuestObjectiveFilters.QuestBoundItem : &ItemQuestObjectiveFilters.NotQuestBoundItem;
16650
16651 ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(entry);
16652 UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_ITEM, itemTemplate->GetId(), count, ObjectGuid::Empty, &updatedObjectives, objectiveFilter);
16653 if (itemTemplate->QuestLogItemId && (updatedObjectives.size() != 1 || !(updatedObjectives[0]->Flags2 & QUEST_OBJECTIVE_FLAG_2_QUEST_BOUND_ITEM)))
16654 UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_ITEM, itemTemplate->QuestLogItemId, count, ObjectGuid::Empty, &updatedObjectives, objectiveFilter);
16655
16656 if (updatedObjectives.size() == 1 && updatedObjectives[0]->Flags2 & QUEST_OBJECTIVE_FLAG_2_QUEST_BOUND_ITEM)
16657 {
16658 // Quest source items should ignore QUEST_OBJECTIVE_FLAG_2_QUEST_BOUND_ITEM
16659 if (Quest const* quest = sObjectMgr->GetQuestTemplate(updatedObjectives[0]->QuestID))
16660 if (quest->GetSrcItemId() == entry)
16661 return;
16662
16663 if (hadBoundItemObjective)
16664 *hadBoundItemObjective = updatedObjectives.size() == 1 && updatedObjectives[0]->Flags2 & QUEST_OBJECTIVE_FLAG_2_QUEST_BOUND_ITEM;
16665
16666 SendQuestUpdateAddItem(itemTemplate, *updatedObjectives[0], count);
16667 }
16668}
16669
16671{
16672 for (QuestObjectiveStatusMap::value_type const& objectiveItr : Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { QUEST_OBJECTIVE_ITEM, entry }))
16673 {
16674 uint32 questId = objectiveItr.second.QuestStatusItr->first;
16675 uint16 logSlot = objectiveItr.second.QuestStatusItr->second.Slot;
16676 Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
16677 QuestObjective const* objective = sObjectMgr->GetQuestObjective(objectiveItr.second.ObjectiveId);
16678
16679 if (!quest || !objective || !IsQuestObjectiveCompletable(logSlot, quest, *objective))
16680 continue;
16681
16682 int32 newItemCount = GetItemCount(entry, false); // we may have more than what the status shows, so we have to iterate inventory
16683
16684 if (newItemCount < objective->Amount)
16685 {
16686 SetQuestObjectiveData(*objective, newItemCount);
16687 IncompleteQuest(questId);
16688 }
16689 }
16690 UpdateVisibleObjectInteractions(true, false, false, true);
16691}
16692
16693void Player::KilledMonster(Creature const* creature)
16694{
16695 ASSERT(creature);
16696
16697 CreatureTemplate const* cInfo = creature->GetCreatureTemplate();
16698
16699 KilledMonsterCredit(cInfo->Entry, creature->GetGUID());
16700
16701 for (uint8 i = 0; i < MAX_KILL_CREDIT; ++i)
16702 if (cInfo->KillCredit[i])
16704
16705 for (int32 label : creature->GetLabels())
16707}
16708
16709void Player::KilledMonsterCredit(uint32 entry, ObjectGuid guid /*= ObjectGuid::Empty*/)
16710{
16711 uint16 addKillCount = 1;
16712 uint32 real_entry = entry;
16713 Creature* killed = nullptr;
16714 if (!guid.IsEmpty())
16715 {
16716 killed = GetMap()->GetCreature(guid);
16717 if (killed && killed->GetEntry())
16718 real_entry = killed->GetEntry();
16719 }
16720
16721 StartCriteria(CriteriaStartEvent::KillNPC, real_entry); // MUST BE CALLED FIRST
16722 UpdateCriteria(CriteriaType::KillCreature, real_entry, addKillCount, 0, killed);
16723
16725}
16726
16728{
16731}
16732
16734{
16736}
16737
16739{
16741}
16742
16744{
16746}
16747
16749{
16751}
16752
16753void Player::ReputationChanged(FactionEntry const* factionEntry, int32 change)
16754{
16758}
16759
16760void Player::CurrencyChanged(uint32 currencyId, int32 change)
16761{
16765}
16766
16767void Player::UpdateQuestObjectiveProgress(QuestObjectiveType objectiveType, int32 objectId, int64 addCount, ObjectGuid victimGuid /*= ObjectGuid::Empty*/,
16768 std::vector<QuestObjective const*>* updatedObjectives /*= nullptr*/, std::function<bool(QuestObjective const*)> const* objectiveFilter /*= nullptr*/)
16769{
16770 bool anyObjectiveChangedCompletionState = false;
16771 bool updatePhaseShift = false;
16772 bool updateZoneAuras = false;
16773
16774 bool anyObjectiveChangedSpawnTrackingState = false;
16775 bool isObjectiveTypeForSpawnTracking = [&]() -> bool
16776 {
16777 if (objectiveType == QUEST_OBJECTIVE_MONSTER ||
16778 objectiveType == QUEST_OBJECTIVE_GAMEOBJECT ||
16779 objectiveType == QUEST_OBJECTIVE_TALKTO ||
16781 return true;
16782
16783 return false;
16784 }();
16785
16786 SpawnMetadata const* data = nullptr;
16787 if (isObjectiveTypeForSpawnTracking)
16788 {
16789 if (victimGuid.IsGameObject())
16790 {
16791 if (GameObject* gameObject = ObjectAccessor::GetGameObject(*this, victimGuid))
16792 data = sObjectMgr->GetSpawnMetadata(SPAWN_TYPE_GAMEOBJECT, gameObject->GetSpawnId());
16793 }
16794 else if (victimGuid.IsCreatureOrVehicle())
16795 {
16796 if (Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, victimGuid))
16797 data = sObjectMgr->GetSpawnMetadata(SPAWN_TYPE_CREATURE, creature->GetSpawnId());
16798 }
16799 }
16800
16801 for (QuestObjectiveStatusMap::value_type const& objectiveItr : Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { objectiveType, objectId }))
16802 {
16803 uint32 questId = objectiveItr.second.QuestStatusItr->first;
16804 Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
16805 if (!quest)
16806 continue;
16807
16809 if (GetGroup() && GetGroup()->isRaidGroup() && !quest->IsAllowedInRaid(GetMap()->GetDifficultyID()))
16810 continue;
16811
16812 uint16 logSlot = objectiveItr.second.QuestStatusItr->second.Slot;
16813 QuestObjective const* objective = sObjectMgr->GetQuestObjective(objectiveItr.second.ObjectiveId);
16814 if (!objective || !IsQuestObjectiveCompletable(logSlot, quest, *objective))
16815 continue;
16816
16818 if (objective->Type == QUEST_OBJECTIVE_MONSTER && victimGuid.IsEmpty())
16819 continue;
16820
16821 bool objectiveWasComplete = IsQuestObjectiveComplete(logSlot, quest, *objective);
16822 if (objectiveWasComplete && addCount >= 0)
16823 continue;
16824
16825 if (objectiveFilter && !(*objectiveFilter)(objective))
16826 continue;
16827
16828 bool objectiveIsNowComplete = false;
16829 if (objective->IsStoringValue())
16830 {
16832 if (Player const* victim = ObjectAccessor::GetPlayer(GetMap(), victimGuid))
16833 if (victim->GetEffectiveTeam() != GetEffectiveTeam())
16834 continue;
16835
16836 int32 currentProgress = GetQuestSlotObjectiveData(logSlot, *objective);
16837 if (addCount > 0 ? (currentProgress < objective->Amount) : (currentProgress > 0))
16838 {
16839 int32 newProgress = std::clamp<int32>(currentProgress + addCount, 0, objective->Amount);
16840 SetQuestObjectiveData(*objective, newProgress);
16841 if (addCount > 0 && !(objective->Flags & QUEST_OBJECTIVE_FLAG_HIDE_CREDIT_MSG))
16842 {
16843 switch (objectiveType)
16844 {
16846 break; // case handled by SMSG_ITEM_PUSH_RESULT
16848 SendQuestUpdateAddPlayer(quest, newProgress);
16849 break;
16850 default:
16851 SendQuestUpdateAddCredit(quest, victimGuid, *objective, newProgress);
16852 break;
16853 }
16854 }
16855
16856 objectiveIsNowComplete = IsQuestObjectiveComplete(logSlot, quest, *objective);
16857 }
16858 }
16859 else if (objective->IsStoringFlag())
16860 {
16861 SetQuestObjectiveData(*objective, addCount > 0);
16862
16863 if (addCount > 0 && !(objective->Flags & QUEST_OBJECTIVE_FLAG_HIDE_CREDIT_MSG))
16865
16866 objectiveIsNowComplete = IsQuestObjectiveComplete(logSlot, quest, *objective);
16867 }
16868 else
16869 {
16870 switch (objectiveType)
16871 {
16873 objectiveIsNowComplete = GetCurrencyQuantity(objectId) + addCount >= objective->Amount;
16874 break;
16876 objectiveIsNowComplete = addCount != 0;
16877 break;
16879 objectiveIsNowComplete = GetReputationMgr().GetReputation(objectId) + addCount >= objective->Amount;
16880 break;
16882 objectiveIsNowComplete = GetReputationMgr().GetReputation(objectId) + addCount <= objective->Amount;
16883 break;
16885 objectiveIsNowComplete = int64(GetMoney()) + addCount >= objective->Amount;
16886 break;
16888 objectiveIsNowComplete = IsQuestObjectiveProgressBarComplete(logSlot, quest);
16889 break;
16890 default:
16891 ABORT_MSG("Unhandled quest objective type %u", uint32(objectiveType));
16892 break;
16893 }
16894 }
16895
16897 {
16898 if (IsQuestObjectiveProgressBarComplete(logSlot, quest))
16899 {
16900 auto progressBarObjectiveItr = std::find_if(quest->GetObjectives().begin(), quest->GetObjectives().end(), [](QuestObjective const& otherObjective)
16901 {
16902 return otherObjective.Type == QUEST_OBJECTIVE_PROGRESS_BAR && !(otherObjective.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR);
16903 });
16904 if (progressBarObjectiveItr != quest->GetObjectives().end())
16905 SendQuestUpdateAddCreditSimple(*progressBarObjectiveItr);
16906
16907 objectiveIsNowComplete = true;
16908 }
16909 }
16910
16911 if (data && data->spawnTrackingQuestObjectiveId && data->spawnTrackingData)
16912 {
16913 if (objective->ID == data->spawnTrackingQuestObjectiveId)
16914 {
16915 // Store spawn tracking to return correct state in Player::GetSpawnTrackingStateByObjective
16916 QuestStatusData& questStatus = objectiveItr.second.QuestStatusItr->second;
16917 questStatus.SpawnTrackingList.insert(std::make_pair(objective->StorageIndex, data->spawnTrackingData->SpawnTrackingId));
16918
16919 // Send QuestPOIUpdateResponse for every spawn linked to same SpawnTrackingId
16920 for (auto const& [spawnTrackingId, data] : sObjectMgr->GetSpawnMetadataForSpawnTracking(data->spawnTrackingData->SpawnTrackingId))
16921 {
16922 SpawnData const* spawnData = data->ToSpawnData();
16923 if (!spawnData)
16924 continue;
16925
16927
16930 responseInfo.ObjectID = spawnData->id;
16931 responseInfo.PhaseID = spawnData->phaseId;
16932 responseInfo.PhaseGroupID = spawnData->phaseGroup;
16933 responseInfo.PhaseUseFlags = spawnData->phaseUseFlags;
16934
16936 responseInfo.Visible = data->spawnTrackingStates[AsUnderlyingType(state)].Visible;
16937
16938 response.SpawnTrackingResponses.push_back(std::move(responseInfo));
16939 SendDirectMessage(response.Write());
16940 }
16941
16942 anyObjectiveChangedSpawnTrackingState = true;
16943 }
16944 }
16945
16946 if (objectiveWasComplete != objectiveIsNowComplete)
16947 anyObjectiveChangedCompletionState = true;
16948
16949 if (objectiveIsNowComplete && objective->CompletionEffect)
16950 {
16951 if (objective->CompletionEffect->GameEventId)
16952 GameEvents::Trigger(*objective->CompletionEffect->GameEventId, this, nullptr);
16953 if (objective->CompletionEffect->SpellId)
16954 CastSpell(this, *objective->CompletionEffect->SpellId, true);
16955 if (objective->CompletionEffect->ConversationId)
16957 if (objective->CompletionEffect->UpdatePhaseShift)
16958 updatePhaseShift = true;
16959 if (objective->CompletionEffect->UpdateZoneAuras)
16960 updateZoneAuras = true;
16961 }
16962
16963 if (objectiveIsNowComplete)
16964 {
16965 if (CanCompleteQuest(questId, objective->ID))
16966 CompleteQuest(questId);
16967 }
16968 else if (!(objective->Flags & QUEST_OBJECTIVE_FLAG_OPTIONAL) && objectiveItr.second.QuestStatusItr->second.Status == QUEST_STATUS_COMPLETE)
16969 IncompleteQuest(questId);
16970
16971 if (updatedObjectives)
16972 updatedObjectives->push_back(objective);
16973
16974 if (objective->Type == QUEST_OBJECTIVE_ITEM && addCount >= 0 && objective->Flags2 & QUEST_OBJECTIVE_FLAG_2_QUEST_BOUND_ITEM)
16975 break;
16976 }
16977
16978 if (anyObjectiveChangedCompletionState || anyObjectiveChangedSpawnTrackingState)
16979 UpdateVisibleObjectInteractions(true, false, false, true);
16980
16981 if (updatePhaseShift)
16983
16984 if (updateZoneAuras)
16985 {
16988 }
16989}
16990
16992{
16993 // Search incomplete objective first
16994 if (GetQuestObjectiveForItem(itemid, true))
16995 return true;
16996
16997 // This part - for ItemDrop
16998 for (std::pair<uint32 const, QuestStatusData> const& questStatus : m_QuestStatus)
16999 {
17000 if (questStatus.second.Status != QUEST_STATUS_INCOMPLETE)
17001 continue;
17002
17003 Quest const* qInfo = ASSERT_NOTNULL(sObjectMgr->GetQuestTemplate(questStatus.first));
17004 // hide quest if player is in raid-group and quest is no raid quest
17005 if (GetGroup() && GetGroup()->isRaidGroup() && !qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID()))
17006 if (!InBattleground())
17007 continue;
17008
17009 for (uint8 j = 0; j < QUEST_ITEM_DROP_COUNT; ++j)
17010 {
17011 // examined item is a source item
17012 if (qInfo->ItemDrop[j] != itemid)
17013 continue;
17014
17015 ItemTemplate const* pProto = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(itemid));
17016
17017 // allows custom amount drop when not 0
17018 uint32 maxAllowedCount = qInfo->ItemDropQuantity[j] ? qInfo->ItemDropQuantity[j] : pProto->GetMaxStackSize();
17019
17020 // 'unique' item
17021 if (pProto->GetMaxCount() && pProto->GetMaxCount() < maxAllowedCount)
17022 maxAllowedCount = pProto->GetMaxCount();
17023
17024 if (GetItemCount(itemid, true) < maxAllowedCount)
17025 return true;
17026 }
17027 }
17028
17029 return false;
17030}
17031
17032QuestObjective const* Player::GetQuestObjectiveForItem(uint32 itemId, bool onlyIncomplete) const
17033{
17034 auto findObjectiveForItem = [this, onlyIncomplete](uint32 itemId) -> QuestObjective const*
17035 {
17036 for (QuestObjectiveStatusMap::value_type const& objectiveItr : Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { QUEST_OBJECTIVE_ITEM, itemId }))
17037 {
17038 Quest const* qInfo = sObjectMgr->GetQuestTemplate(objectiveItr.second.QuestStatusItr->first);
17039 QuestObjective const* objective = sObjectMgr->GetQuestObjective(objectiveItr.second.ObjectiveId);
17040 if (!qInfo || !objective || !IsQuestObjectiveCompletable(objectiveItr.second.QuestStatusItr->second.Slot, qInfo, *objective))
17041 continue;
17042
17043 // hide quest if player is in raid-group and quest is no raid quest
17044 if (GetGroup() && GetGroup()->isRaidGroup() && !qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID()))
17045 if (!InBattleground()) //there are two ways.. we can make every bg-quest a raidquest, or add this code here.. i don't know if this can be exploited by other quests, but i think all other quests depend on a specific area.. but keep this in mind, if something strange happens later
17046 continue;
17047
17048 if (!onlyIncomplete || !IsQuestObjectiveComplete(objectiveItr.second.QuestStatusItr->second.Slot, qInfo, *objective))
17049 return objective;
17050 }
17051 return nullptr;
17052 };
17053
17054 if (QuestObjective const* objective = findObjectiveForItem(itemId))
17055 return objective;
17056
17057 if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemId))
17058 if (itemTemplate->QuestLogItemId)
17059 if (QuestObjective const* objective = findObjectiveForItem(itemTemplate->QuestLogItemId))
17060 return objective;
17061
17062 return nullptr;
17063}
17064
17066{
17067 auto isCompletableObjective = [this](QuestObjectiveStatusData const& objectiveStatus)
17068 {
17069 Quest const* qInfo = sObjectMgr->GetQuestTemplate(objectiveStatus.QuestStatusItr->first);
17070 QuestObjective const* objective = sObjectMgr->GetQuestObjective(objectiveStatus.ObjectiveId);
17071 if (!qInfo || !objective || !IsQuestObjectiveCompletable(objectiveStatus.QuestStatusItr->second.Slot, qInfo, *objective))
17072 return false;
17073
17074 // hide quest if player is in raid-group and quest is no raid quest
17075 if (GetGroup() && GetGroup()->isRaidGroup() && !qInfo->IsAllowedInRaid(GetMap()->GetDifficultyID()))
17076 if (!InBattleground()) //there are two ways.. we can make every bg-quest a raidquest, or add this code here.. i don't know if this can be exploited by other quests, but i think all other quests depend on a specific area.. but keep this in mind, if something strange happens later
17077 return false;
17078
17079 if (!IsQuestObjectiveComplete(objectiveStatus.QuestStatusItr->second.Slot, qInfo, *objective))
17080 return true;
17081
17082 return false;
17083 };
17084
17085 auto hasObjectiveTypeForCurrency = [&](QuestObjectiveType type)
17086 {
17087 return std::ranges::any_of(Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { type, currencyId }),
17088 isCompletableObjective, Trinity::Containers::MapValue);
17089 };
17090
17091 if (hasObjectiveTypeForCurrency(QUEST_OBJECTIVE_CURRENCY))
17092 return true;
17093
17094 if (hasObjectiveTypeForCurrency(QUEST_OBJECTIVE_HAVE_CURRENCY))
17095 return true;
17096
17097 if (hasObjectiveTypeForCurrency(QUEST_OBJECTIVE_OBTAIN_CURRENCY))
17098 return true;
17099
17100 return false;
17101}
17102
17104{
17105 uint16 slot = FindQuestSlot(objective.QuestID);
17106 if (slot >= MAX_QUEST_LOG_SIZE)
17107 return 0;
17108
17109 return GetQuestSlotObjectiveData(slot, objective);
17110}
17111
17113{
17114 if (objective.StorageIndex < 0)
17115 {
17116 TC_LOG_ERROR("entities.player.quest", "Player::SetQuestObjectiveData: called for quest {} with invalid StorageIndex {} (objective data is not tracked)",
17117 objective.QuestID, objective.StorageIndex);
17118 return;
17119 }
17120
17121 auto itr = m_QuestStatus.find(objective.QuestID);
17122 if (itr == m_QuestStatus.end())
17123 {
17124 TC_LOG_ERROR("entities.player.quest", "Player::SetQuestObjectiveData: player '{}' ({}) doesn't have quest status data (QuestID: {})",
17125 GetName(), GetGUID().ToString(), objective.QuestID);
17126 return;
17127 }
17128
17129 QuestStatusData& status = itr->second;
17130
17131 if (uint8(objective.StorageIndex) >= MAX_QUEST_COUNTS)
17132 {
17133 TC_LOG_ERROR("entities.player.quest", "Player::SetQuestObjectiveData: player '{}' ({}) quest {} out of range StorageIndex {}",
17134 GetName(), GetGUID().ToString(), objective.QuestID, objective.StorageIndex);
17135 return;
17136 }
17137
17138 if (status.Slot >= MAX_QUEST_LOG_SIZE)
17139 return;
17140
17141 // No change
17142 int32 oldData = GetQuestSlotObjectiveData(status.Slot, objective);
17143 if (oldData == data)
17144 return;
17145
17146 if (Quest const* quest = sObjectMgr->GetQuestTemplate(objective.QuestID))
17147 sScriptMgr->OnQuestObjectiveChange(this, quest, objective, oldData, data);
17148
17149 // Add to save
17151
17152 // Update quest fields
17153 if (!objective.IsStoringFlag())
17154 SetQuestSlotCounter(status.Slot, objective.StorageIndex, data);
17155 else if (data)
17156 SetQuestSlotObjectiveFlag(status.Slot, objective.StorageIndex);
17157 else
17159}
17160
17161bool Player::IsQuestObjectiveCompletable(uint16 slot, Quest const* quest, QuestObjective const& objective) const
17162{
17163 ASSERT(objective.QuestID == quest->GetQuestId());
17164
17166 {
17167 // delegate check to actual progress bar objective
17168 auto progressBarObjectiveItr = std::find_if(quest->GetObjectives().begin(), quest->GetObjectives().end(), [](QuestObjective const& otherObjective)
17169 {
17170 return otherObjective.Type == QUEST_OBJECTIVE_PROGRESS_BAR && !(otherObjective.Flags & QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR);
17171 });
17172 if (progressBarObjectiveItr == quest->GetObjectives().end())
17173 return false;
17174
17175 return IsQuestObjectiveCompletable(slot, quest, *progressBarObjectiveItr) && !IsQuestObjectiveComplete(slot, quest, *progressBarObjectiveItr);
17176 }
17177
17178 int32 objectiveIndex = int32(std::distance(&quest->GetObjectives()[0], &objective));
17179 if (!objectiveIndex)
17180 return true;
17181
17182 // check sequenced objectives
17183 int32 previousIndex = objectiveIndex - 1;
17184 bool objectiveSequenceSatisfied = true;
17185 bool previousSequencedObjectiveComplete = false;
17186 int32 previousSequencedObjectiveIndex = -1;
17187 do
17188 {
17189 QuestObjective const& previousObjective = quest->GetObjectives()[previousIndex];
17190 if (previousObjective.Flags & QUEST_OBJECTIVE_FLAG_SEQUENCED)
17191 {
17192 previousSequencedObjectiveIndex = previousIndex;
17193 previousSequencedObjectiveComplete = IsQuestObjectiveComplete(slot, quest, previousObjective);
17194 break;
17195 }
17196
17197 if (objectiveSequenceSatisfied)
17198 objectiveSequenceSatisfied = IsQuestObjectiveComplete(slot, quest, previousObjective) || (previousObjective.Flags & (QUEST_OBJECTIVE_FLAG_OPTIONAL | QUEST_OBJECTIVE_FLAG_PART_OF_PROGRESS_BAR));
17199
17200 --previousIndex;
17201 } while (previousIndex >= 0);
17202
17203 if (objective.Flags & QUEST_OBJECTIVE_FLAG_SEQUENCED)
17204 {
17205 if (previousSequencedObjectiveIndex == -1)
17206 return objectiveSequenceSatisfied;
17207 if (!previousSequencedObjectiveComplete || !objectiveSequenceSatisfied)
17208 return false;
17209 }
17210 else if (!previousSequencedObjectiveComplete && previousSequencedObjectiveIndex != -1)
17211 {
17212 if (!IsQuestObjectiveCompletable(slot, quest, quest->GetObjectives()[previousSequencedObjectiveIndex]))
17213 return false;
17214 }
17215
17216 return true;
17217}
17218
17219bool Player::IsQuestObjectiveComplete(uint16 slot, Quest const* quest, QuestObjective const& objective) const
17220{
17221 switch (objective.Type)
17222 {
17233 if (GetQuestSlotObjectiveData(slot, objective) < objective.Amount)
17234 return false;
17235 break;
17237 if (GetReputationMgr().GetReputation(objective.ObjectID) < objective.Amount)
17238 return false;
17239 break;
17241 if (GetReputationMgr().GetReputation(objective.ObjectID) > objective.Amount)
17242 return false;
17243 break;
17245 if (!HasEnoughMoney(uint64(objective.Amount)))
17246 return false;
17247 break;
17254 if (!GetQuestSlotObjectiveData(slot, objective))
17255 return false;
17256 break;
17258 if (!HasSpell(objective.ObjectID))
17259 return false;
17260 break;
17262 if (!HasCurrency(objective.ObjectID, objective.Amount))
17263 return false;
17264 break;
17266 if (!IsQuestObjectiveProgressBarComplete(slot, quest))
17267 return false;
17268 break;
17269 default:
17270 TC_LOG_ERROR("entities.player.quest", "Player::CanCompleteQuest: Player '{}' ({}) tried to complete a quest (ID: {}) with an unknown objective type {}",
17271 GetName(), GetGUID().ToString(), objective.QuestID, objective.Type);
17272 return false;
17273 }
17274
17275 return true;
17276}
17277
17278bool Player::IsQuestObjectiveComplete(uint32 questId, uint32 objectiveId) const
17279{
17280 Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
17281 if (!quest)
17282 return false;
17283
17284 uint16 slot = FindQuestSlot(questId);
17285 if (slot >= MAX_QUEST_LOG_SIZE)
17286 return false;
17287
17288 QuestObjective const* obj = sObjectMgr->GetQuestObjective(objectiveId);
17289 if (!obj)
17290 return false;
17291
17292 return IsQuestObjectiveComplete(slot, quest, *obj);
17293}
17294
17296{
17297 float progress = 0.0f;
17298 for (QuestObjective const& obj : quest->GetObjectives())
17299 {
17301 {
17302 progress += GetQuestSlotObjectiveData(slot, obj) * obj.ProgressBarWeight;
17303 if (progress >= 100.0f)
17304 return true;
17305 }
17306 }
17307 return false;
17308}
17309
17311{
17312 if (questId)
17313 {
17315 data.QuestID = questId;
17316 SendDirectMessage(data.Write());
17317 }
17318}
17319
17320void Player::SendQuestReward(Quest const* quest, Creature const* questGiver, uint32 xp, bool hideChatMessage) const
17321{
17322 uint32 questId = quest->GetQuestId();
17323 sGameEventMgr->HandleQuestComplete(questId);
17324
17325 uint32 moneyReward;
17326
17327 if (!IsMaxLevel())
17328 {
17329 moneyReward = GetQuestMoneyReward(quest);
17330 }
17331 else // At max level, increase gold reward
17332 {
17333 xp = 0;
17334 moneyReward = uint32(GetQuestMoneyReward(quest) + int32(quest->GetRewMoneyMaxLevel() * sWorld->getRate(RATE_DROP_MONEY)));
17335 }
17336
17338
17340 return;
17341
17343
17344 packet.QuestID = questId;
17345 packet.MoneyReward = moneyReward;
17346 packet.XPReward = xp;
17347 packet.SkillLineIDReward = quest->GetRewardSkillId();
17348 packet.NumSkillUpsReward = quest->GetRewardSkillPoints();
17349
17350 if (questGiver)
17351 {
17352 if (questGiver->IsGossip())
17354
17355 if (questGiver->IsQuestGiver())
17356 packet.LaunchQuest = (GetQuestDialogStatus(questGiver) & ~QuestGiverStatusFutureMask) != QuestGiverStatus::None;
17357
17359 if (Quest const* rewardQuest = GetNextQuest(questGiver, quest))
17360 packet.UseQuestReward = CanTakeQuest(rewardQuest, false);
17361 }
17362
17363 packet.HideChatMessage = hideChatMessage;
17364
17365 SendDirectMessage(packet.Write());
17366}
17367
17369{
17370 if (questId)
17371 {
17372 WorldPackets::Quest::QuestGiverQuestFailed questGiverQuestFailed;
17373 questGiverQuestFailed.QuestID = questId;
17374 questGiverQuestFailed.Reason = reason; // failed reason (valid reasons: 4, 16, 50, 17, other values show default message)
17375 SendDirectMessage(questGiverQuestFailed.Write());
17376 }
17377}
17378
17380{
17381 if (questId)
17382 {
17383 WorldPackets::Quest::QuestUpdateFailedTimer questUpdateFailedTimer;
17384 questUpdateFailedTimer.QuestID = questId;
17385 SendDirectMessage(questUpdateFailedTimer.Write());
17386 }
17387}
17388
17389void Player::SendCanTakeQuestResponse(QuestFailedReason reason, bool sendErrorMessage /*= true*/, std::string reasonText /*= ""*/) const
17390{
17391 WorldPackets::Quest::QuestGiverInvalidQuest questGiverInvalidQuest;
17392
17393 questGiverInvalidQuest.Reason = reason;
17394 questGiverInvalidQuest.SendErrorMessage = sendErrorMessage;
17395 questGiverInvalidQuest.ReasonText = reasonText;
17396
17397 SendDirectMessage(questGiverInvalidQuest.Write());
17398}
17399
17400void Player::SendQuestConfirmAccept(Quest const* quest, Player* receiver) const
17401{
17402 if (!receiver)
17403 return;
17404
17406
17407 packet.QuestTitle = quest->GetLogTitle();
17408 uint32 questID = quest->GetQuestId();
17409
17410 LocaleConstant localeConstant = receiver->GetSession()->GetSessionDbLocaleIndex();
17411 if (localeConstant != LOCALE_enUS)
17412 if (QuestTemplateLocale const* questTemplateLocale = sObjectMgr->GetQuestLocale(questID))
17413 ObjectMgr::GetLocaleString(questTemplateLocale->LogTitle, localeConstant, packet.QuestTitle);
17414
17415 packet.QuestID = questID;
17416 packet.InitiatedBy = GetGUID();
17417
17418 receiver->SendDirectMessage(packet.Write());
17419}
17420
17421void Player::SendPushToPartyResponse(Player const* player, QuestPushReason reason, Quest const* quest /*= nullptr*/) const
17422{
17423 if (player)
17424 {
17426 response.SenderGUID = player->GetGUID();
17427 response.Result = AsUnderlyingType(reason);
17428 if (quest)
17429 {
17430 response.QuestTitle = quest->GetLogTitle();
17432 if (localeConstant != LOCALE_enUS)
17433 if (QuestTemplateLocale const* questTemplateLocale = sObjectMgr->GetQuestLocale(quest->GetQuestId()))
17434 ObjectMgr::GetLocaleString(questTemplateLocale->LogTitle, localeConstant, response.QuestTitle);
17435 }
17436
17437 SendDirectMessage(response.Write());
17438 }
17439}
17440
17441void Player::SendQuestUpdateAddCredit(Quest const* quest, ObjectGuid guid, QuestObjective const& obj, uint16 count) const
17442{
17444 packet.VictimGUID = guid;
17445 packet.QuestID = quest->GetQuestId();
17446 packet.ObjectID = obj.ObjectID;
17447 packet.Count = count;
17448 packet.Required = obj.Amount;
17449 packet.ObjectiveType = obj.Type;
17450 SendDirectMessage(packet.Write());
17451}
17452
17454{
17456 packet.QuestID = obj.QuestID;
17457 packet.ObjectID = obj.ObjectID;
17458 packet.ObjectiveType = obj.Type;
17459 SendDirectMessage(packet.Write());
17460}
17461
17462void Player::SendQuestUpdateAddItem(ItemTemplate const* itemTemplate, QuestObjective const& obj, uint16 count) const
17463{
17465
17466 packet.PlayerGUID = GetGUID();
17467
17468 packet.Slot = INVENTORY_SLOT_BAG_0;
17469 packet.SlotInBag = 0;
17470 packet.Item.ItemID = itemTemplate->GetId();
17471 packet.ProxyItemID = itemTemplate->QuestLogItemId;
17472 packet.Quantity = count;
17475 packet.FakeQuestItem = true;
17476
17478 GetGroup()->BroadcastPacket(packet.Write(), true);
17479 else
17480 SendDirectMessage(packet.Write());
17481}
17482
17483void Player::SendQuestUpdateAddPlayer(Quest const* quest, uint16 newCount) const
17484{
17485 WorldPackets::Quest::QuestUpdateAddPvPCredit questUpdateAddPvpCredit;
17486 questUpdateAddPvpCredit.QuestID = quest->GetQuestId();
17487 questUpdateAddPvpCredit.Count = newCount;
17488 SendDirectMessage(questUpdateAddPvpCredit.Write());
17489}
17490
17492{
17494
17495 for (auto itr = m_clientGUIDs.begin(); itr != m_clientGUIDs.end(); ++itr)
17496 {
17497 if (itr->IsAnyTypeCreature())
17498 {
17499 // need also pet quests case support
17500 Creature* questgiver = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, *itr);
17501 if (!questgiver)
17502 continue;
17503 if (!questgiver->HasNpcFlag(UNIT_NPC_FLAG_QUESTGIVER))
17504 continue;
17505
17506 response.QuestGiver.emplace_back(questgiver->GetGUID(), GetQuestDialogStatus(questgiver));
17507 }
17508 else if (itr->IsGameObject())
17509 {
17510 GameObject* questgiver = GetMap()->GetGameObject(*itr);
17511 if (!questgiver || questgiver->GetGoType() != GAMEOBJECT_TYPE_QUESTGIVER)
17512 continue;
17513
17514 response.QuestGiver.emplace_back(questgiver->GetGUID(), GetQuestDialogStatus(questgiver));
17515 }
17516 }
17517
17518 SendDirectMessage(response.Write());
17519}
17520
17522{
17523 for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
17524 {
17525 uint32 questId = GetQuestSlotQuestId(i);
17526 if (questId == 0)
17527 continue;
17528
17529 Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
17530 if (!quest)
17531 continue;
17532
17533 if (quest->HasFlag(QUEST_FLAGS_FLAGS_PVP))
17534 return true;
17535 }
17536
17537 return false;
17538}
17539
17541{
17542 if (questId)
17543 {
17545 data.QuestID = questId;
17546 SendDirectMessage(data.Write());
17547 }
17548}
17549
17551{
17552 if (std::vector<QuestObjective const*> const* questObjectiveList = sObjectMgr->GetSpawnTrackingQuestObjectiveList(spawnTrackingId))
17553 for (QuestObjective const* questObjective : *questObjectiveList)
17554 if (FindQuestSlot(questObjective->QuestID) < MAX_QUEST_LOG_SIZE)
17555 return questObjective;
17556
17557 return nullptr;
17558}
17559
17561{
17562 if (spawnTrackingId && questObjectiveId && sObjectMgr->IsQuestObjectiveForSpawnTracking(spawnTrackingId, questObjectiveId))
17563 {
17564 if (QuestObjective const* questObjective = sObjectMgr->GetQuestObjective(questObjectiveId))
17565 {
17566 if (IsQuestRewarded(questObjective->QuestID) || IsQuestObjectiveComplete(questObjective->QuestID, questObjective->ID))
17568 else if (GetQuestStatus(questObjective->QuestID) != QUEST_STATUS_NONE)
17569 {
17570 auto itr = m_QuestStatus.find(questObjective->QuestID);
17571 if (itr != m_QuestStatus.end() && itr->second.Slot < MAX_QUEST_LOG_SIZE)
17572 {
17573 auto itr2 = itr->second.SpawnTrackingList.find(std::make_pair(questObjective->StorageIndex, spawnTrackingId));
17574 if (itr2 != itr->second.SpawnTrackingList.end())
17576 }
17577
17579 }
17580 }
17581 }
17582
17584}
17585
17586/*********************************************************/
17587/*** LOAD SYSTEM ***/
17588/*********************************************************/
17589
17591{
17592 if (!result)
17593 return;
17594
17595 auto declinedNames = m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::DeclinedNames, 0);
17596 for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
17597 SetUpdateFieldValue(declinedNames.ModifyValue(&UF::DeclinedNames::Name, i), (*result)[i].GetString());
17598}
17599
17601{
17602 // arenateamid, played_week, played_season, personal_rating
17603 uint16 personalRatingCache[] = {0, 0, 0};
17604
17605 if (result)
17606 {
17607 do
17608 {
17609 Field* fields = result->Fetch();
17610
17611 uint32 arenaTeamId = fields[0].GetUInt32();
17612
17613 ArenaTeam* arenaTeam = sArenaTeamMgr->GetArenaTeamById(arenaTeamId);
17614 if (!arenaTeam)
17615 {
17616 TC_LOG_ERROR("entities.player.loading", "Player::_LoadArenaTeamInfo: couldn't load arenateam {}", arenaTeamId);
17617 continue;
17618 }
17619
17620 uint8 arenaSlot = arenaTeam->GetSlot();
17621 ASSERT(arenaSlot < 3);
17622
17623 personalRatingCache[arenaSlot] = fields[4].GetUInt16();
17624
17625 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_ID, arenaTeamId);
17626 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_TYPE, arenaTeam->GetType());
17627 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_MEMBER, (arenaTeam->GetCaptain() == GetGUID()) ? 0 : 1);
17628 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_GAMES_WEEK, uint32(fields[1].GetUInt16()));
17629 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_GAMES_SEASON, uint32(fields[2].GetUInt16()));
17630 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_WINS_SEASON, uint32(fields[3].GetUInt16()));
17631 }
17632 while (result->NextRow());
17633 }
17634
17635 for (uint8 slot = 0; slot <= 2; ++slot)
17636 {
17637 SetArenaTeamInfoField(slot, ARENA_TEAM_PERSONAL_RATING, uint32(personalRatingCache[slot]));
17638 }
17639}
17640
17642{
17643 // SetPQuery(PLAYER_LOGIN_QUERY_LOADEQUIPMENTSETS, "SELECT setguid, setindex, name, iconname, ignore_mask, AssignedSpecIndex, item0, item1, item2, item3, item4, item5, item6, item7, item8, item9, item10, item11, item12, item13, item14, item15, item16, item17, item18 FROM character_equipmentsets WHERE guid = '{}' ORDER BY setindex", GUID_LOPART(m_guid));
17644 if (!result)
17645 return;
17646
17647 do
17648 {
17649 Field* fields = result->Fetch();
17650 EquipmentSetInfo eqSet;
17651
17652 eqSet.Data.Guid = fields[0].GetUInt64();
17654 eqSet.Data.SetID = fields[1].GetUInt8();
17655 eqSet.Data.SetName = fields[2].GetString();
17656 eqSet.Data.SetIcon = fields[3].GetString();
17657 eqSet.Data.IgnoreMask = fields[4].GetUInt32();
17658 eqSet.Data.AssignedSpecIndex = fields[5].GetInt32();
17660
17662 if (ObjectGuid::LowType guid = fields[6 + i].GetUInt64())
17663 eqSet.Data.Pieces[i] = ObjectGuid::Create<HighGuid::Item>(guid);
17664
17665 eqSet.Data.Appearances.fill(0);
17666 eqSet.Data.Enchants.fill(0);
17667
17668 if (eqSet.Data.SetID >= MAX_EQUIPMENT_SET_INDEX) // client limit
17669 continue;
17670
17671 _equipmentSets[eqSet.Data.Guid] = eqSet;
17672 } while (result->NextRow());
17673}
17674
17676{
17677 // 0 1 2 3 4 5 6 7 8 9
17678 //SELECT setguid, setindex, name, iconname, ignore_mask, appearance0, appearance1, appearance2, appearance3, appearance4,
17679 // 10 11 12 13 14 15 16 17 18 19 20 21
17680 // appearance5, appearance6, appearance7, appearance8, appearance9, appearance10, appearance11, appearance12, appearance13, appearance14, appearance15, appearance16,
17681 // 22 23 24 25
17682 // appearance17, appearance18, mainHandEnchant, offHandEnchant FROM character_transmog_outfits WHERE guid = ? ORDER BY setindex
17683 if (!result)
17684 return;
17685
17686 do
17687 {
17688 Field* fields = result->Fetch();
17689 EquipmentSetInfo eqSet;
17690
17691 eqSet.Data.Guid = fields[0].GetUInt64();
17693 eqSet.Data.SetID = fields[1].GetUInt8();
17694 eqSet.Data.SetName = fields[2].GetString();
17695 eqSet.Data.SetIcon = fields[3].GetString();
17696 eqSet.Data.IgnoreMask = fields[4].GetUInt32();
17698 eqSet.Data.Pieces.fill(ObjectGuid::Empty);
17699
17701 eqSet.Data.Appearances[i] = fields[5 + i].GetInt32();
17702
17703 for (std::size_t i = 0; i < eqSet.Data.Enchants.size(); ++i)
17704 eqSet.Data.Enchants[i] = fields[24 + i].GetInt32();
17705
17706 if (eqSet.Data.SetID >= MAX_EQUIPMENT_SET_INDEX) // client limit
17707 continue;
17708
17709 _equipmentSets[eqSet.Data.Guid] = eqSet;
17710 } while (result->NextRow());
17711}
17712
17714{
17715 if (!result)
17716 return;
17717
17718 Field* fields = result->Fetch();
17719 // Expecting only one row
17720 // 0 1 2 3 4 5 6 7 8 9 10
17721 // SELECT instanceId, team, joinX, joinY, joinZ, joinO, joinMapId, taxiStart, taxiEnd, mountSpell, queueTypeId FROM character_battleground_data WHERE guid = ?
17722
17723 m_bgData.bgInstanceID = fields[0].GetUInt32();
17724 m_bgData.bgTeam = Team(fields[1].GetUInt16());
17725 m_bgData.joinPos = WorldLocation(fields[6].GetUInt16(), // Map
17726 fields[2].GetFloat(), // X
17727 fields[3].GetFloat(), // Y
17728 fields[4].GetFloat(), // Z
17729 fields[5].GetFloat()); // Orientation
17730 m_bgData.taxiPath[0] = fields[7].GetUInt32();
17731 m_bgData.taxiPath[1] = fields[8].GetUInt32();
17732 m_bgData.mountSpell = fields[9].GetUInt32();
17733 m_bgData.queueId = BattlegroundQueueTypeId::FromPacked(fields[10].GetUInt64());
17734}
17735
17736bool Player::LoadPositionFromDB(uint32& mapid, float& x, float& y, float& z, float& o, bool& in_flight, ObjectGuid guid)
17737{
17739 stmt->setUInt64(0, guid.GetCounter());
17740 PreparedQueryResult result = CharacterDatabase.Query(stmt);
17741
17742 if (!result)
17743 return false;
17744
17745 Field* fields = result->Fetch();
17746
17747 x = fields[0].GetFloat();
17748 y = fields[1].GetFloat();
17749 z = fields[2].GetFloat();
17750 o = fields[3].GetFloat();
17751 mapid = fields[4].GetUInt16();
17752 in_flight = !fields[5].GetString().empty();
17753
17754 return true;
17755}
17756
17758{
17760 m_homebindAreaId = areaId;
17761
17762 // update sql homebind
17764 stmt->setUInt16(0, m_homebind.GetMapId());
17765 stmt->setUInt16(1, m_homebindAreaId);
17766 stmt->setFloat (2, m_homebind.GetPositionX());
17767 stmt->setFloat (3, m_homebind.GetPositionY());
17768 stmt->setFloat (4, m_homebind.GetPositionZ());
17769 stmt->setFloat (5, m_homebind.GetOrientation());
17770 stmt->setUInt64(6, GetGUID().GetCounter());
17771 CharacterDatabase.Execute(stmt);
17772}
17773
17775{
17778 packet.BindMapID = m_homebind.GetMapId();
17780 SendDirectMessage(packet.Write());
17781}
17782
17783void Player::SendPlayerBound(ObjectGuid const& binderGuid, uint32 areaId) const
17784{
17785 WorldPackets::Misc::PlayerBound packet(binderGuid, areaId);
17786 SendDirectMessage(packet.Write());
17787}
17788
17790{
17791 return GetSession()->PlayerLoading();
17792}
17793
17795{
17797 if (!result)
17798 {
17799 std::string name = "<unknown>";
17800 sCharacterCache->GetCharacterNameByGuid(guid, name);
17801 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player '{}' ({}) not found in table `characters`, can't load. ", name, guid.ToString());
17802 return false;
17803 }
17804
17805 struct PlayerLoadData
17806 {
17807 // "SELECT c.guid, account, name, race, class, gender, level, xp, money, inventorySlots, inventoryBagFlags, bagSlotFlags1, bagSlotFlags2, bagSlotFlags3, bagSlotFlags4, bagSlotFlags5, "
17808 // "bankSlots, bankBagFlags, bankBagSlotFlags1, bankBagSlotFlags2, bankBagSlotFlags3, bankBagSlotFlags4, bankBagSlotFlags5, bankBagSlotFlags6, bankBagSlotFlags7, restState, playerFlags, playerFlagsEx, "
17809 // "position_x, position_y, position_z, map, orientation, taximask, createTime, createMode, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, "
17810 // "resettalents_time, primarySpecialization, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, summonedPetNumber, at_login, zone, online, death_expire_time, taxi_path, dungeonDifficulty, "
17811 // "totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, "
17812 // "health, power1, power2, power3, power4, power5, power6, power7, power8, power9, power10, instance_id, activeTalentGroup, lootSpecId, exploredZones, knownTitles, actionBars, "
17813 // "raidDifficulty, legacyRaidDifficulty, fishingSteps, honor, honorLevel, honorRestState, honorRestBonus, numRespecs, "
17814 // "personalTabardEmblemStyle, personalTabardEmblemColor, personalTabardBorderStyle, personalTabardBorderColor, personalTabardBackgroundColor "
17815 // "FROM characters c LEFT JOIN character_fishingsteps cfs ON c.guid = cfs.guid WHERE c.guid = ?", CONNECTION_ASYNC);
17816
17818 uint32 account;
17819 std::string name;
17820 uint8 race;
17821 uint8 class_;
17822 Gender gender;
17823 uint8 level;
17824 uint32 xp;
17825 uint64 money;
17826 uint8 inventorySlots;
17827 EnumFlag<BagSlotFlags> inventoryBagFlags = BagSlotFlags::None;
17828 std::array<BagSlotFlags, 5> bagSlotFlags;
17829 uint8 bankSlots;
17831 std::array<BagSlotFlags, 7> bankBagSlotFlags;
17832 PlayerRestState restState;
17833 PlayerFlags playerFlags;
17834 PlayerFlagsEx playerFlagsEx;
17835 float position_x;
17836 float position_y;
17837 float position_z;
17838 uint16 map;
17839 float orientation;
17840 std::string taximask;
17841 time_t createTime;
17842 PlayerCreateMode createMode;
17843 uint8 cinematic;
17844 uint32 totaltime;
17845 uint32 leveltime;
17846 float rest_bonus;
17847 time_t logout_time;
17848 uint8 is_logout_resting;
17849 uint32 resettalents_cost;
17850 time_t resettalents_time;
17851 uint32 primarySpecialization;
17852 float trans_x;
17853 float trans_y;
17854 float trans_z;
17855 float trans_o;
17856 ObjectGuid::LowType transguid;
17857 uint16 extra_flags;
17858 uint32 summonedPetNumber;
17859 uint16 at_login;
17860 uint16 zone;
17861 uint8 online;
17862 time_t death_expire_time;
17863 std::string taxi_path;
17864 Difficulty dungeonDifficulty;
17865 uint32 totalKills;
17866 uint16 todayKills;
17867 uint16 yesterdayKills;
17868 uint32 chosenTitle;
17869 uint32 watchedFaction;
17870 uint8 drunk;
17871 uint32 health;
17872 std::array<uint32, MAX_POWERS_PER_CLASS> powers;
17873 uint32 instance_id;
17874 uint8 activeTalentGroup;
17875 uint32 lootSpecId;
17876 std::string exploredZones;
17877 std::string knownTitles;
17878 uint8 actionBars;
17879 Difficulty raidDifficulty;
17880 Difficulty legacyRaidDifficulty;
17881 uint8 fishingSteps;
17882 uint32 honor;
17883 uint32 honorLevel;
17884 PlayerRestState honorRestState;
17885 float honorRestBonus;
17886 uint8 numRespecs;
17887 int32 personalTabardEmblemStyle;
17888 int32 personalTabardEmblemColor;
17889 int32 personalTabardBorderStyle;
17890 int32 personalTabardBorderColor;
17891 int32 personalTabardBackgroundColor;
17892
17893 explicit PlayerLoadData(Field const* fields)
17894 {
17895 std::size_t i = 0;
17896 guid = fields[i++].GetUInt64();
17897 account = fields[i++].GetUInt32();
17898 name = fields[i++].GetString();
17899 race = fields[i++].GetUInt8();
17900 class_ = fields[i++].GetUInt8();
17901 gender = Gender(fields[i++].GetUInt8());
17902 level = fields[i++].GetUInt8();
17903 xp = fields[i++].GetUInt32();
17904 money = fields[i++].GetUInt64();
17905 inventorySlots = fields[i++].GetUInt8();
17906 inventoryBagFlags = static_cast<BagSlotFlags>(fields[i++].GetUInt32());
17907 for (BagSlotFlags& flags : bagSlotFlags)
17908 flags = static_cast<BagSlotFlags>(fields[i++].GetUInt32());
17909 bankSlots = fields[i++].GetUInt8();
17910 bankBagFlags = static_cast<BagSlotFlags>(fields[i++].GetUInt32());
17911 for (BagSlotFlags& flags : bankBagSlotFlags)
17912 flags = static_cast<BagSlotFlags>(fields[i++].GetUInt32());
17913 restState = PlayerRestState(fields[i++].GetUInt8());
17914 playerFlags = PlayerFlags(fields[i++].GetUInt32());
17915 playerFlagsEx = PlayerFlagsEx(fields[i++].GetUInt32());
17916 position_x = fields[i++].GetFloat();
17917 position_y = fields[i++].GetFloat();
17918 position_z = fields[i++].GetFloat();
17919 map = fields[i++].GetUInt16();
17920 orientation = fields[i++].GetFloat();
17921 taximask = fields[i++].GetString();
17922 createTime = fields[i++].GetInt64();
17923 createMode = PlayerCreateMode(fields[i++].GetInt8());
17924 cinematic = fields[i++].GetUInt8();
17925 totaltime = fields[i++].GetUInt32();
17926 leveltime = fields[i++].GetUInt32();
17927 rest_bonus = fields[i++].GetFloat();
17928 logout_time = fields[i++].GetInt64();
17929 is_logout_resting = fields[i++].GetUInt8();
17930 resettalents_cost = fields[i++].GetUInt32();
17931 resettalents_time = fields[i++].GetInt64();
17932 primarySpecialization = fields[i++].GetUInt32();
17933 trans_x = fields[i++].GetFloat();
17934 trans_y = fields[i++].GetFloat();
17935 trans_z = fields[i++].GetFloat();
17936 trans_o = fields[i++].GetFloat();
17937 transguid = fields[i++].GetUInt64();
17938 extra_flags = fields[i++].GetUInt16();
17939 summonedPetNumber = fields[i++].GetUInt32();
17940 at_login = fields[i++].GetUInt16();
17941 zone = fields[i++].GetUInt16();
17942 online = fields[i++].GetUInt8();
17943 death_expire_time = fields[i++].GetInt64();
17944 taxi_path = fields[i++].GetString();
17945 dungeonDifficulty = Difficulty(fields[i++].GetUInt8());
17946 totalKills = fields[i++].GetUInt32();
17947 todayKills = fields[i++].GetUInt16();
17948 yesterdayKills = fields[i++].GetUInt16();
17949 chosenTitle = fields[i++].GetUInt32();
17950 watchedFaction = fields[i++].GetUInt32();
17951 drunk = fields[i++].GetUInt8();
17952 health = fields[i++].GetUInt32();
17953 for (uint32& power : powers)
17954 power = fields[i++].GetUInt32();
17955 instance_id = fields[i++].GetUInt32();
17956 activeTalentGroup = fields[i++].GetUInt8();
17957 lootSpecId = fields[i++].GetUInt32();
17958 exploredZones = fields[i++].GetString();
17959 knownTitles = fields[i++].GetString();
17960 actionBars = fields[i++].GetUInt8();
17961 raidDifficulty = Difficulty(fields[i++].GetUInt8());
17962 legacyRaidDifficulty = Difficulty(fields[i++].GetUInt8());
17963 fishingSteps = fields[i++].GetUInt8();
17964 honor = fields[i++].GetUInt32();
17965 honorLevel = fields[i++].GetUInt32();
17966 honorRestState = PlayerRestState(fields[i++].GetUInt8());
17967 honorRestBonus = fields[i++].GetFloat();
17968 numRespecs = fields[i++].GetUInt8();
17969 personalTabardEmblemStyle = fields[i++].GetInt32();
17970 personalTabardEmblemColor = fields[i++].GetInt32();
17971 personalTabardBorderStyle = fields[i++].GetInt32();
17972 personalTabardBorderColor = fields[i++].GetInt32();
17973 personalTabardBackgroundColor = fields[i++].GetInt32();
17974 }
17975
17976 } fields(result->Fetch());
17977
17978 // check if the character's account in the db and the logged in account match.
17979 // player should be able to load/delete character only with correct account!
17980 if (fields.account != GetSession()->GetAccountId())
17981 {
17982 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player ({}) loading from wrong account (is: {}, should be: {})", guid.ToString(), GetSession()->GetAccountId(), fields.account);
17983 return false;
17984 }
17985
17987 {
17988 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player ({}) is banned, can't load.", guid.ToString());
17989 return false;
17990 }
17991
17992 Object::_Create(guid);
17993
17994 m_name = std::move(fields.name);
17995
17996 // check name limitations
17997 if (ObjectMgr::CheckPlayerName(m_name, GetSession()->GetSessionDbcLocale()) != CHAR_NAME_SUCCESS ||
17999 {
18001 stmt->setUInt16(0, uint16(AT_LOGIN_RENAME));
18002 stmt->setUInt64(1, guid.GetCounter());
18003 CharacterDatabase.Execute(stmt);
18004 return false;
18005 }
18006
18008
18012
18013 if (!IsValidGender(fields.gender))
18014 {
18015 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player ({}) has wrong gender ({}), can't load.", guid.ToString(), uint32(fields.gender));
18016 return false;
18017 }
18018
18019 SetRace(fields.race);
18020 SetClass(fields.class_);
18021 SetGender(fields.gender);
18022
18023 // check if race/class combination is valid
18024 PlayerInfo const* info = sObjectMgr->GetPlayerInfo(GetRace(), GetClass());
18025 if (!info)
18026 {
18027 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player ({}) has wrong race/class ({}/{}), can't load.", guid.ToString(), GetRace(), GetClass());
18028 return false;
18029 }
18030
18031 SetLevel(fields.level, false);
18032 SetXP(fields.xp);
18033
18034 std::vector<std::string_view> exploredZones = Trinity::Tokenize(fields.exploredZones, ' ', false);
18035 for (std::size_t i = 0; i < exploredZones.size(); ++i)
18036 AddExploredZones(i / 2, Trinity::StringTo<uint64>(exploredZones[i]).value_or(UI64LIT(0)) << (32 * (i % 2)));
18037
18038 std::vector<std::string_view> knownTitles = Trinity::Tokenize(fields.knownTitles, ' ', false);
18039 for (std::size_t i = 0; i < knownTitles.size(); ++i)
18041 Trinity::StringTo<uint64>(knownTitles[i]).value_or(UI64LIT(0)) << (32 * (i % 2)));
18042
18043 SetObjectScale(1.0f);
18044
18045 // load achievements before anything else to prevent multiple gains for the same achievement/criteria on every loading (as loading does call UpdateCriteria)
18048
18049 SetMoney(std::min(fields.money, MAX_MONEY_AMOUNT));
18050
18051 std::vector<UF::ChrCustomizationChoice> customizations;
18053 {
18054 do
18055 {
18056 Field* fields = customizationsResult->Fetch();
18057 customizations.emplace_back();
18058 UF::ChrCustomizationChoice& choice = customizations.back();
18059 choice.ChrCustomizationOptionID = fields[0].GetUInt32();
18060 choice.ChrCustomizationChoiceID = fields[1].GetUInt32();
18061
18062 } while (customizationsResult->NextRow());
18063 }
18064
18065 SetCustomizations(Trinity::Containers::MakeIteratorPair(customizations.begin(), customizations.end()), false);
18066 SetInventorySlotCount(fields.inventorySlots);
18067 SetBackpackAutoSortDisabled(fields.inventoryBagFlags.HasFlag(BagSlotFlags::DisableAutoSort));
18068 SetBackpackSellJunkDisabled(fields.inventoryBagFlags.HasFlag(BagSlotFlags::ExcludeJunkSell));
18069 for (uint32 bagIndex = 0; bagIndex < fields.bagSlotFlags.size(); ++bagIndex)
18070 ReplaceAllBagSlotFlags(bagIndex, fields.bagSlotFlags[bagIndex]);
18071 SetBankBagSlotCount(fields.bankSlots);
18072 SetBankAutoSortDisabled(fields.bankBagFlags.HasFlag(BagSlotFlags::DisableAutoSort));
18073 for (uint32 bagIndex = 0; bagIndex < fields.bankBagSlotFlags.size(); ++bagIndex)
18074 ReplaceAllBankBagSlotFlags(bagIndex, fields.bankBagSlotFlags[bagIndex]);
18075 SetNativeGender(fields.gender);
18077 ReplaceAllPlayerFlags(fields.playerFlags);
18078 ReplaceAllPlayerFlagsEx(fields.playerFlagsEx);
18079 SetWatchedFactionIndex(fields.watchedFaction);
18080
18081 m_atLoginFlags = fields.at_login;
18082
18083 if (!GetSession()->ValidateAppearance(Races(GetRace()), Classes(GetClass()), fields.gender, MakeChrCustomizationChoiceRange(customizations)))
18084 {
18085 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player ({}) has wrong Appearance values (Hair/Skin/Color), can't load.", guid.ToString());
18086 return false;
18087 }
18088
18089 // set which actionbars the client has active - DO NOT REMOVE EVER AGAIN (can be changed though, if it does change fieldwise)
18090 SetMultiActionBars(fields.actionBars);
18091
18092 m_fishingSteps = fields.fishingSteps;
18093
18095
18096 TC_LOG_DEBUG("entities.player.loading", "Player::LoadFromDB: Load Basic value of player '{}' is: ", m_name);
18098
18099 //Need to call it to initialize m_team (m_team can be calculated from race)
18100 //Other way is to saves m_team into characters table.
18102
18103 // load home bind and check in same time class/race pair, it used later for restore broken positions
18105 return false;
18106
18108 InitPrimaryProfessions(); // to max set before any spell loaded
18109
18110 // init saved position, and fix it later if problematic
18111 Relocate(fields.position_x, fields.position_y, fields.position_z, fields.orientation);
18112
18113 uint32 mapId = fields.map;
18114 uint32 instanceId = fields.instance_id;
18115
18116 SetDungeonDifficultyID(CheckLoadedDungeonDifficultyID(fields.dungeonDifficulty));
18117 SetRaidDifficultyID(CheckLoadedRaidDifficultyID(fields.raidDifficulty));
18119
18120 auto RelocateToHomebind = [this, &mapId, &instanceId]() { mapId = m_homebind.GetMapId(); instanceId = 0; WorldRelocate(m_homebind); };
18121
18123
18128
18131
18132 GetSession()->SetPlayer(this);
18133 MapEntry const* mapEntry = sMapStore.LookupEntry(mapId);
18134 Map* map = nullptr;
18135 bool player_at_bg = false;
18136 if (!mapEntry || !IsPositionValid())
18137 {
18138 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player ({}) has invalid coordinates (MapId: {} X: {} Y: {} Z: {} O: {}). Teleport to default race/class locations.",
18140 RelocateToHomebind();
18141 }
18142 // Player was saved in Arena or Bg
18143 else if (mapEntry->IsBattlegroundOrArena())
18144 {
18145 Battleground* currentBg = nullptr;
18146 if (m_bgData.bgInstanceID) //saved in Battleground
18147 currentBg = sBattlegroundMgr->GetBattleground(m_bgData.bgInstanceID, BATTLEGROUND_TYPE_NONE);
18148
18149 player_at_bg = currentBg && currentBg->IsPlayerInBattleground(GetGUID());
18150
18151 if (player_at_bg && currentBg->GetStatus() != STATUS_WAIT_LEAVE)
18152 {
18153 map = currentBg->GetBgMap();
18154
18155 if (BattlegroundPlayer const* bgPlayer = currentBg->GetBattlegroundPlayerData(GetGUID()))
18156 {
18157 AddBattlegroundQueueId(bgPlayer->queueTypeId);
18158 m_bgData.bgTypeID = BattlegroundTypeId(bgPlayer->queueTypeId.BattlemasterListId);
18159
18160 //join player to battleground group
18161 currentBg->EventPlayerLoggedIn(this);
18162
18163 SetInviteForBattlegroundQueueType(bgPlayer->queueTypeId, currentBg->GetInstanceID());
18165 }
18166 }
18167 // Bg was not found - go to Entry Point
18168 else
18169 {
18170 // leave bg
18171 if (player_at_bg)
18172 {
18173 player_at_bg = false;
18174 currentBg->RemovePlayerAtLeave(GetGUID(), false, true);
18175 }
18176
18177 // Do not look for instance if bg not found
18179 mapId = _loc.GetMapId();
18180 instanceId = 0;
18181
18182 // Db field type is type int16, so it can never be MAPID_INVALID
18183 //if (mapId == MAPID_INVALID) -- code kept for reference
18184 if (int16(mapId) == int16(-1)) // Battleground Entry Point not found (???)
18185 {
18186 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player ({}) was in BG in database, but BG was not found and entry point was invalid! Teleport to default race/class locations.",
18187 guid.ToString());
18188 RelocateToHomebind();
18189 }
18190 else
18191 Relocate(&_loc);
18192
18193 // We are not in BG anymore
18195 }
18196 }
18197 // currently we do not support transport in bg
18198 else if (fields.transguid)
18199 {
18200 ObjectGuid transGUID = ObjectGuid::Create<HighGuid::Transport>(fields.transguid);
18201
18202 Transport* transport = nullptr;
18203 if (Map* transportMap = sMapMgr->CreateMap(mapId, this))
18204 {
18205 if (Transport* transportOnMap = transportMap->GetTransport(transGUID))
18206 {
18207 if (transportOnMap->GetExpectedMapId() != mapId)
18208 {
18209 mapId = transportOnMap->GetExpectedMapId();
18210 instanceId = 0;
18211 transportMap = sMapMgr->CreateMap(mapId, this);
18212 if (transportMap)
18213 transport = transportMap->GetTransport(transGUID);
18214 }
18215 else
18216 transport = transportOnMap;
18217 }
18218 }
18219
18220 if (transport)
18221 {
18222 float x = fields.trans_x, y = fields.trans_y, z = fields.trans_z, o = fields.trans_o;
18224 transport->CalculatePassengerPosition(x, y, z, &o);
18225
18226 if (!Trinity::IsValidMapCoord(x, y, z, o) ||
18227 // transport size limited
18228 std::fabs(m_movementInfo.transport.pos.GetPositionX()) > 250.0f ||
18229 std::fabs(m_movementInfo.transport.pos.GetPositionY()) > 250.0f ||
18230 std::fabs(m_movementInfo.transport.pos.GetPositionZ()) > 250.0f)
18231 {
18232 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player ({}) has invalid transport coordinates (X: {} Y: {} Z: {} O: {}). Teleport to bind location.",
18233 guid.ToString(), x, y, z, o);
18234
18236
18237 RelocateToHomebind();
18238 }
18239 else
18240 {
18241 Relocate(x, y, z, o);
18242 mapId = transport->GetMapId();
18243
18244 transport->AddPassenger(this);
18245 }
18246 }
18247 else
18248 {
18249 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player ({}) has problems with transport guid ({}). Teleport to bind location.",
18250 guid.ToString(), fields.transguid);
18251
18252 RelocateToHomebind();
18253 }
18254 }
18255 // currently we do not support taxi in instance
18256 else if (!fields.taxi_path.empty())
18257 {
18258 instanceId = 0;
18259
18260 // Not finish taxi flight path
18261 if (m_bgData.HasTaxiPath())
18262 {
18263 for (int i = 0; i < 2; ++i)
18265 }
18266 else if (!m_taxi.LoadTaxiDestinationsFromString(fields.taxi_path, GetTeam()))
18267 {
18268 // problems with taxi path loading
18269 TaxiNodesEntry const* nodeEntry = nullptr;
18270 if (uint32 node_id = m_taxi.GetTaxiSource())
18271 nodeEntry = sTaxiNodesStore.LookupEntry(node_id);
18272
18273 if (!nodeEntry) // don't know taxi start node, teleport to homebind
18274 {
18275 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player ({}) has wrong data in taxi destination list ({}), teleport to homebind.", GetGUID().ToString(), fields.taxi_path);
18276 RelocateToHomebind();
18277 }
18278 else // has start node, teleport to it
18279 {
18280 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player ({}) has too short taxi destination list ({}), teleport to original node.", GetGUID().ToString(), fields.taxi_path);
18281 mapId = nodeEntry->ContinentID;
18282 Relocate(nodeEntry->Pos.X, nodeEntry->Pos.Y, nodeEntry->Pos.Z, 0.0f);
18283 }
18285 }
18286
18287 if (uint32 node_id = m_taxi.GetTaxiSource())
18288 {
18289 // save source node as recall coord to prevent recall and fall from sky
18290 TaxiNodesEntry const* nodeEntry = sTaxiNodesStore.LookupEntry(node_id);
18291 if (nodeEntry && nodeEntry->ContinentID == GetMapId())
18292 {
18293 ASSERT(nodeEntry); // checked in m_taxi.LoadTaxiDestinationsFromString
18294 mapId = nodeEntry->ContinentID;
18295 Relocate(nodeEntry->Pos.X, nodeEntry->Pos.Y, nodeEntry->Pos.Z, 0.0f);
18296 }
18297
18298 // flight will started later
18299 }
18300 }
18301 else if (mapEntry->IsDungeon() && instanceId)
18302 {
18303 // try finding instance by id first
18304 map = sMapMgr->FindMap(mapId, instanceId);
18305 }
18306
18307 // Map could be changed before
18308 mapEntry = sMapStore.LookupEntry(mapId);
18309 // client without expansion support
18310 if (mapEntry)
18311 {
18312 if (GetSession()->GetExpansion() < mapEntry->Expansion())
18313 {
18314 TC_LOG_DEBUG("entities.player.loading", "Player::LoadFromDB: Player '{}' ({}) using client without required expansion tried login at non accessible map {}",
18315 GetName(), GetGUID().ToString(), mapId);
18316 RelocateToHomebind();
18317 }
18318 }
18319
18320 // NOW player must have valid map
18321 // load the player's map here if it's not already loaded
18322 if (!map)
18323 map = sMapMgr->CreateMap(mapId, this);
18324 AreaTriggerStruct const* areaTrigger = nullptr;
18325 bool check = false;
18326
18327 if (!map)
18328 {
18329 areaTrigger = sObjectMgr->GetGoBackTrigger(mapId);
18330 check = true;
18331 }
18332 else if (map->IsDungeon()) // if map is dungeon...
18333 {
18334 if (TransferAbortParams denyReason = map->CannotEnter(this)) // ... and can't enter map, then look for entry point.
18335 {
18336 SendTransferAborted(map->GetId(), denyReason.Reason, denyReason.Arg, denyReason.MapDifficultyXConditionId);
18337 areaTrigger = sObjectMgr->GetGoBackTrigger(mapId);
18338 check = true;
18339 }
18340 else if (instanceId && !sInstanceLockMgr.FindActiveInstanceLock(guid, { mapId, map->GetDifficultyID() })) // ... and instance is reseted then look for entrance.
18341 {
18342 areaTrigger = sObjectMgr->GetMapEntranceTrigger(mapId);
18343 check = true;
18344 }
18345 }
18346
18347 if (check) // in case of special event when creating map...
18348 {
18349 if (areaTrigger) // ... if we have an areatrigger, then relocate to new map/coordinates.
18350 {
18351 Relocate(areaTrigger->target_X, areaTrigger->target_Y, areaTrigger->target_Z, GetOrientation());
18352 if (mapId != areaTrigger->target_mapId)
18353 {
18354 mapId = areaTrigger->target_mapId;
18355 map = sMapMgr->CreateMap(mapId, this);
18356 }
18357 }
18358 }
18359
18360 if (!map)
18361 {
18362 RelocateToHomebind();
18363 map = sMapMgr->CreateMap(mapId, this);
18364 if (!map)
18365 {
18366 TC_LOG_ERROR("entities.player.loading", "Player::LoadFromDB: Player '{}' ({}) Map: {}, X: {}, Y: {}, Z: {}, O: {}. Invalid default map coordinates or instance couldn't be created.",
18368 return false;
18369 }
18370 }
18371
18372 SetMap(map);
18374
18375 // now that map position is determined, check instance validity
18377 m_InstanceValid = false;
18378
18379 if (player_at_bg)
18381
18382 // randomize first save time in range [CONFIG_INTERVAL_SAVE] around [CONFIG_INTERVAL_SAVE]
18383 // this must help in case next save after mass player load after server startup
18384 m_nextSave = urand(m_nextSave / 2, m_nextSave * 3 / 2);
18385
18387
18388 time_t now = GameTime::GetGameTime();
18389 time_t logoutTime = time_t(fields.logout_time);
18390
18392
18393 // since last logout (in seconds)
18394 uint32 time_diff = uint32(now - logoutTime); //uint64 is excessive for a time_diff in seconds.. uint32 allows for 136~ year difference.
18395
18396 // set value, including drunk invisibility detection
18397 // calculate sobering. after 15 minutes logged out, the player will be sober again
18398 if (time_diff < uint32(GetDrunkValue()) * 9)
18399 SetDrunkValue(GetDrunkValue() - time_diff / 9);
18400 else
18401 SetDrunkValue(0);
18402
18403 m_createTime = fields.createTime;
18404 m_createMode = fields.createMode;
18405 m_cinematic = fields.cinematic;
18406 m_Played_time[PLAYED_TIME_TOTAL] = fields.totaltime;
18407 m_Played_time[PLAYED_TIME_LEVEL] = fields.leveltime;
18408
18409 SetTalentResetCost(fields.resettalents_cost);
18410 SetTalentResetTime(fields.resettalents_time);
18411
18412 if (!m_taxi.LoadTaxiMask(fields.taximask)) // must be before InitTaxiNodesForLevel
18413 TC_LOG_WARN("entities.player.loading", "Player::LoadFromDB: Player ({}) has invalid taximask ({}) in DB. Forced partial load.", GetGUID().ToString(), fields.taximask);
18414
18415 uint32 extraflags = fields.extra_flags;
18416
18417 _LoadPetStable(fields.summonedPetNumber, holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS));
18418
18420 {
18421 TC_LOG_ERROR("entities.player.cheat", "Player::LoadFromDB: Player ({}) tried to login while forced to rename, can't load.'", GetGUID().ToString());
18422 return false;
18423 }
18424
18425 // Honor system
18426 // Update Honor kills data
18427 m_lastHonorUpdateTime = logoutTime;
18429
18430 m_deathExpireTime = fields.death_expire_time;
18431
18434
18436
18437 // make sure the unit is considered out of combat for proper loading
18438 ClearInCombat();
18439
18440 // reset stats before loading any modifiers
18443 InitRunes();
18444
18445 // rest bonus can only be calculated after InitStatsForLevel()
18446 _restMgr->LoadRestBonus(REST_TYPE_XP, fields.restState, fields.rest_bonus);
18447
18448 // load skills after InitStatsForLevel because it triggering aura apply also
18450 UpdateSkillsForLevel(); //update skills after load, to make sure they are correctly update at player load
18451
18452 SetNumRespecs(fields.numRespecs);
18453 SetPrimarySpecialization(fields.primarySpecialization);
18454 SetActiveTalentGroup(fields.activeTalentGroup);
18456 if (!primarySpec || primarySpec->ClassID != GetClass() || GetActiveTalentGroup() >= MAX_SPECIALIZATIONS)
18458
18459 uint32 lootSpecId = fields.lootSpecId;
18460 if (ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(lootSpecId))
18461 if (chrSpec->ClassID == GetClass())
18462 SetLootSpecId(lootSpecId);
18463
18474
18476
18480 // add ghost flag (must be after aura load: PLAYER_FLAGS_GHOST set in aura)
18483
18484 // Load spell locations - must be after loading auras
18486
18487 // after spell load, learn rewarded spell if need also
18497
18498 // after spell and quest load
18502
18504 holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TRAIT_ENTRIES)); // must be after loading spells
18505
18506 // must be before inventory (some items required reputation check)
18508
18515 time_diff);
18516
18519
18520 // update items with duration and realtime
18521 UpdateItemDuration(time_diff, true);
18522
18524
18525 // unread mails and next delivery time, actual mails not loaded
18533
18535
18536 // check PLAYER_CHOSEN_TITLE compatibility with PLAYER__FIELD_KNOWN_TITLES
18537 // note: PLAYER__FIELD_KNOWN_TITLES updated at quest status loaded
18538 uint32 curTitle = fields.chosenTitle;
18539 if (curTitle && !HasTitle(curTitle))
18540 curTitle = 0;
18541
18542 SetChosenTitle(curTitle);
18543
18544 // has to be called after last Relocate() in Player::LoadFromDB
18546
18548
18549 uint32 savedHealth = fields.health;
18550 if (!savedHealth)
18552
18553 // Spell code allow apply any auras to dead character in load time in aura/spell/item loading
18554 // Do now before stats re-calculation cleanup for ghost state unexpected auras
18555 if (!IsAlive())
18557 else
18559
18560 //apply all stat bonuses from items and auras
18561 SetCanModifyStats(true);
18563
18564 // restore remembered power/health values (but not more max values)
18565 SetHealth(savedHealth > GetMaxHealth() ? GetMaxHealth() : savedHealth);
18566 uint32 loadedPowers = 0;
18567 for (uint32 i = 0; i < MAX_POWERS; ++i)
18568 {
18569 if (GetPowerIndex(Powers(i)) != MAX_POWERS)
18570 {
18571 uint32 savedPower = fields.powers[loadedPowers];
18572 uint32 maxPower = m_unitData->MaxPower[loadedPowers];
18573 SetPower(Powers(i), (savedPower > maxPower) ? maxPower : savedPower);
18574 if (++loadedPowers >= MAX_POWERS_PER_CLASS)
18575 break;
18576 }
18577 }
18578
18579 for (; loadedPowers < MAX_POWERS_PER_CLASS; ++loadedPowers)
18581
18583 // Init rune recharge
18585 {
18586 int32 runes = GetPower(POWER_RUNES);
18587 int32 maxRunes = GetMaxPower(POWER_RUNES);
18588 uint32 runeCooldown = GetRuneBaseCooldown();
18589 while (runes < maxRunes)
18590 {
18591 SetRuneCooldown(runes, runeCooldown);
18592 ++runes;
18593 }
18594 }
18595
18596 SetPersonalTabard(fields.personalTabardEmblemStyle, fields.personalTabardEmblemColor, fields.personalTabardBorderStyle,
18597 fields.personalTabardBorderColor, fields.personalTabardBackgroundColor);
18598
18599 TC_LOG_DEBUG("entities.player.loading", "Player::LoadFromDB: The value of player '{}' after load item and aura is: ", m_name);
18601
18602 // GM state
18604 {
18605 switch (sWorld->getIntConfig(CONFIG_GM_LOGIN_STATE))
18606 {
18607 default:
18608 case 0: break; // disable
18609 case 1: SetGameMaster(true); break; // enable
18610 case 2: // save state
18611 if (extraflags & PLAYER_EXTRA_GM_ON)
18612 SetGameMaster(true);
18613 break;
18614 }
18615
18616 switch (sWorld->getIntConfig(CONFIG_GM_VISIBLE_STATE))
18617 {
18618 default:
18619 case 0: SetGMVisible(false); break; // invisible
18620 case 1: break; // visible
18621 case 2: // save state
18622 if (extraflags & PLAYER_EXTRA_GM_INVISIBLE)
18623 SetGMVisible(false);
18624 break;
18625 }
18626
18627 switch (sWorld->getIntConfig(CONFIG_GM_CHAT))
18628 {
18629 default:
18630 case 0: break; // disable
18631 case 1: SetGMChat(true); break; // enable
18632 case 2: // save state
18633 if (extraflags & PLAYER_EXTRA_GM_CHAT)
18634 SetGMChat(true);
18635 break;
18636 }
18637
18638 switch (sWorld->getIntConfig(CONFIG_GM_WHISPERING_TO))
18639 {
18640 default:
18641 case 0: break; // disable
18642 case 1: SetAcceptWhispers(true); break; // enable
18643 case 2: // save state
18644 if (extraflags & PLAYER_EXTRA_ACCEPT_WHISPERS)
18645 SetAcceptWhispers(true);
18646 break;
18647 }
18648 }
18649
18650 InitPvP();
18651
18652 // RaF stuff.
18653 if (GetSession()->IsARecruiter() || (GetSession()->GetRecruiterId() != 0))
18655
18657
18660
18662
18663 std::unique_ptr<Garrison> garrison = std::make_unique<Garrison>(this);
18664 if (garrison->LoadFromDB(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_GARRISON),
18669 _garrison = std::move(garrison);
18670
18671 _InitHonorLevelOnLoadFromDB(fields.honor, fields.honorLevel);
18672
18673 _restMgr->LoadRestBonus(REST_TYPE_HONOR, fields.honorRestState, fields.honorRestBonus);
18674 if (time_diff > 0)
18675 {
18676 //speed collect rest bonus in offline, in logout, far from tavern, city (section/in hour)
18677 float bubble0 = 0.031f;
18678 //speed collect rest bonus in offline, in logout, in tavern, city (section/in hour)
18679 float bubble1 = 0.125f;
18680 float bubble = fields.is_logout_resting > 0
18681 ? bubble1 * sWorld->getRate(RATE_REST_OFFLINE_IN_TAVERN_OR_CITY)
18682 : bubble0 * sWorld->getRate(RATE_REST_OFFLINE_IN_WILDERNESS);
18683
18684 _restMgr->AddRestBonus(REST_TYPE_XP, time_diff * _restMgr->CalcExtraPerSec(REST_TYPE_XP, bubble));
18685 }
18686
18687 // Unlock battle pet system if it's enabled in bnet account
18688 if (GetSession()->GetBattlePetMgr()->IsBattlePetSystemEnabled())
18690
18691 m_achievementMgr->CheckAllAchievementCriteria(this);
18692 m_questObjectiveCriteriaMgr->CheckAllQuestObjectiveCriteria(this);
18693
18694 PushQuests();
18695
18696 for (TransmogIllusionEntry const* transmogIllusion : sTransmogIllusionStore)
18697 {
18698 if (!transmogIllusion->GetFlags().HasFlag(TransmogIllusionFlags::PlayerConditionGrantsOnLogin))
18699 continue;
18700
18701 if (GetSession()->GetCollectionMgr()->HasTransmogIllusion(transmogIllusion->ID))
18702 continue;
18703
18704 if (!ConditionMgr::IsPlayerMeetingCondition(this, transmogIllusion->UnlockConditionID))
18705 continue;
18706
18707 GetSession()->GetCollectionMgr()->AddTransmogIllusion(transmogIllusion->ID);
18708 }
18709
18710 return true;
18711}
18712
18714{
18715 for (Quest const* quest : sObjectMgr->GetQuestTemplatesAutoPush())
18716 {
18717 if (quest->GetQuestTag() && quest->GetQuestTag() != QuestTagType::Tag)
18718 continue;
18719
18720 if (!quest->IsUnavailable() && CanTakeQuest(quest, false))
18721 AddQuestAndCheckCompletion(quest, nullptr);
18722 }
18723}
18724
18726{
18727 if (!result)
18728 return;
18729
18730 do
18731 {
18732 // SELECT id, name, frameHeight, frameWidth, sortBy, healthText, boolOptions, unk146, unk147, unk148, unk150, unk152, unk154 FROM character_cuf_profiles WHERE guid = ?
18733 Field* fields = result->Fetch();
18734
18735 uint8 id = fields[0].GetUInt8();
18736 std::string name = fields[1].GetString();
18737 uint16 frameHeight = fields[2].GetUInt16();
18738 uint16 frameWidth = fields[3].GetUInt16();
18739 uint8 sortBy = fields[4].GetUInt8();
18740 uint8 healthText = fields[5].GetUInt8();
18741 uint32 boolOptions = fields[6].GetUInt32();
18742 uint8 topPoint = fields[7].GetUInt8();
18743 uint8 bottomPoint = fields[8].GetUInt8();
18744 uint8 leftPoint = fields[9].GetUInt8();
18745 uint16 topOffset = fields[10].GetUInt16();
18746 uint16 bottomOffset = fields[11].GetUInt16();
18747 uint16 leftOffset = fields[12].GetUInt16();
18748
18749 if (id > MAX_CUF_PROFILES)
18750 {
18751 TC_LOG_ERROR("entities.player", "Player::_LoadCUFProfiles: Player '{}' ({}) has an CUF profile with invalid id (ID: {}), max is {}.", GetName(), GetGUID().ToString(), id, MAX_CUF_PROFILES);
18752 continue;
18753 }
18754
18755 _CUFProfiles[id] = std::make_unique<CUFProfile>(name, frameHeight, frameWidth, sortBy, healthText, boolOptions, topPoint, bottomPoint, leftPoint, topOffset, bottomOffset, leftOffset);
18756 }
18757 while (result->NextRow());
18758}
18759
18760bool Player::isAllowedToLoot(const Creature* creature) const
18761{
18762 if (!creature->isDead())
18763 return false;
18764
18765 if (HasPendingBind())
18766 return false;
18767
18768 Loot const* loot = creature->GetLootForPlayer(this);
18769 if (!loot || loot->isLooted()) // nothing to loot or everything looted.
18770 return false;
18771 if (!loot->HasAllowedLooter(GetGUID()) || (!loot->hasItemForAll() && !loot->hasItemFor(this))) // no loot in creature for this player
18772 return false;
18773
18774 switch (loot->GetLootMethod())
18775 {
18776 case PERSONAL_LOOT:
18777 case FREE_FOR_ALL:
18778 return true;
18779 case ROUND_ROBIN:
18780 // may only loot if the player is the loot roundrobin player
18781 // or if there are free/quest/conditional item for the player
18782 if (loot->roundRobinPlayer.IsEmpty() || loot->roundRobinPlayer == GetGUID())
18783 return true;
18784
18785 return loot->hasItemFor(this);
18786 case MASTER_LOOT:
18787 case GROUP_LOOT:
18788 case NEED_BEFORE_GREED:
18789 // may only loot if the player is the loot roundrobin player
18790 // or item over threshold (so roll(s) can be launched or to preview master looted items)
18791 // or if there are free/quest/conditional item for the player
18792 if (loot->roundRobinPlayer.IsEmpty() || loot->roundRobinPlayer == GetGUID())
18793 return true;
18794
18795 if (loot->hasOverThresholdItem())
18796 return true;
18797
18798 return loot->hasItemFor(this);
18799 }
18800
18801 return false;
18802}
18803
18805{
18806 m_actionButtons.clear();
18807
18808 if (result)
18809 {
18810 do
18811 {
18812 Field* fields = result->Fetch();
18813 uint8 button = fields[0].GetUInt8();
18814 uint64 action = fields[1].GetUInt64();
18815 uint8 type = fields[2].GetUInt8();
18816
18817 if (ActionButton* ab = AddActionButton(button, action, type))
18818 ab->uState = ACTIONBUTTON_UNCHANGED;
18819 else
18820 {
18821 TC_LOG_DEBUG("entities.player", "Player::_LoadActions: Player '{}' ({}) has an invalid action button (Button: {}, Action: {}, Type: {}). It will be deleted at next save. This can be due to a player changing their talents.",
18822 GetName(), GetGUID().ToString(), button, action, type);
18823
18824 // Will be deleted in DB at next save (it can create data until save but marked as deleted).
18825 m_actionButtons[button].uState = ACTIONBUTTON_DELETED;
18826 }
18827 } while (result->NextRow());
18828 }
18829}
18830
18832{
18833 TC_LOG_DEBUG("entities.player.loading", "Player::_LoadAuras: Loading auras for {}", GetGUID().ToString());
18834
18835 /*
18836 0 1 2 3 4 5 6
18837 SELECT casterGuid, itemGuid, spell, effectMask, effectIndex, amount, baseAmount FROM character_aura_effect WHERE guid = ?
18838 */
18839
18840 ObjectGuid casterGuid, itemGuid;
18841 std::map<AuraKey, AuraLoadEffectInfo> effectInfo;
18842 if (effectResult)
18843 {
18844 do
18845 {
18846 Field* fields = effectResult->Fetch();
18847 uint32 effectIndex = fields[4].GetUInt8();
18848 if (effectIndex < MAX_SPELL_EFFECTS)
18849 {
18850 std::span<uint8 const> rawGuidBytes = fields[0].GetBinaryView();
18851 if (rawGuidBytes.size() != ObjectGuid::BytesSize)
18852 continue;
18853
18854 casterGuid.SetRawValue(rawGuidBytes);
18855
18856 rawGuidBytes = fields[1].GetBinaryView();
18857 if (rawGuidBytes.size() != ObjectGuid::BytesSize)
18858 continue;
18859
18860 itemGuid.SetRawValue(rawGuidBytes);
18861 AuraKey key{ casterGuid, itemGuid, fields[2].GetUInt32(), fields[3].GetUInt32() };
18862 AuraLoadEffectInfo& info = effectInfo[key];
18863 info.Amounts[effectIndex] = fields[5].GetInt32();
18864 info.BaseAmounts[effectIndex] = fields[6].GetInt32();
18865 }
18866 }
18867 while (effectResult->NextRow());
18868 }
18869
18870 /*
18871 0 1 2 3 4 5 6 7 8 9 10 11
18872 SELECT casterGuid, itemGuid, spell, effectMask, recalculateMask, difficulty, stackCount, maxDuration, remainTime, remainCharges, castItemId, castItemLevel FROM character_aura WHERE guid = ?
18873 */
18874 if (auraResult)
18875 {
18876 do
18877 {
18878 Field* fields = auraResult->Fetch();
18879 std::span<uint8 const> rawGuidBytes = fields[0].GetBinaryView();
18880 if (rawGuidBytes.size() != ObjectGuid::BytesSize)
18881 continue;
18882
18883 casterGuid.SetRawValue(rawGuidBytes);
18884
18885 rawGuidBytes = fields[1].GetBinaryView();
18886 if (rawGuidBytes.size() != ObjectGuid::BytesSize)
18887 continue;
18888
18889 itemGuid.SetRawValue(rawGuidBytes);
18890 AuraKey key{ casterGuid, itemGuid, fields[2].GetUInt32(), fields[3].GetUInt32() };
18891 uint32 recalculateMask = fields[4].GetUInt32();
18892 Difficulty difficulty = Difficulty(fields[5].GetUInt8());
18893 uint8 stackCount = fields[6].GetUInt8();
18894 int32 maxDuration = fields[7].GetInt32();
18895 int32 remainTime = fields[8].GetInt32();
18896 uint8 remainCharges = fields[9].GetUInt8();
18897 uint32 castItemId = fields[10].GetUInt32();
18898 int32 castItemLevel = fields[11].GetInt32();
18899
18900 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(key.SpellId, difficulty);
18901 if (!spellInfo)
18902 {
18903 TC_LOG_ERROR("entities.player", "Player::_LoadAuras: Player '{}' ({}) has an invalid aura (SpellID: {}), ignoring.",
18904 GetName(), GetGUID().ToString(), key.SpellId);
18905 continue;
18906 }
18907
18908 if (difficulty != DIFFICULTY_NONE && !sDifficultyStore.LookupEntry(difficulty))
18909 {
18910 TC_LOG_ERROR("entities.player", "Player::_LoadAuras: Player '{}' ({}) has an invalid aura difficulty {} (SpellID: {}), ignoring.",
18911 GetName(), GetGUID().ToString(), uint32(difficulty), key.SpellId);
18912 continue;
18913 }
18914
18915 // negative effects should continue counting down after logout
18916 if (remainTime != -1 && (!spellInfo->IsPositive() || spellInfo->HasAttribute(SPELL_ATTR4_AURA_EXPIRES_OFFLINE)))
18917 {
18918 if (remainTime/IN_MILLISECONDS <= int32(timediff))
18919 continue;
18920
18921 remainTime -= timediff*IN_MILLISECONDS;
18922 }
18923
18924 // prevent wrong values of remainCharges
18925 if (spellInfo->ProcCharges)
18926 {
18927 // we have no control over the order of applying auras and modifiers allow auras
18928 // to have more charges than value in SpellInfo
18929 if (remainCharges <= 0/* || remainCharges > spellproto->procCharges*/)
18930 remainCharges = spellInfo->ProcCharges;
18931 }
18932 else
18933 remainCharges = 0;
18934
18935 AuraLoadEffectInfo& info = effectInfo[key];
18936 ObjectGuid castId = ObjectGuid::Create<HighGuid::Cast>(SPELL_CAST_SOURCE_NORMAL, GetMapId(), spellInfo->Id, GetMap()->GenerateLowGuid<HighGuid::Cast>());
18937 AuraCreateInfo createInfo(castId, spellInfo, difficulty, key.EffectMask, this);
18938 createInfo
18939 .SetCasterGUID(casterGuid)
18940 .SetBaseAmount(info.BaseAmounts.data())
18941 .SetCastItem(itemGuid, castItemId, castItemLevel);
18942
18943 if (Aura* aura = Aura::TryCreate(createInfo))
18944 {
18945 if (!aura->CanBeSaved())
18946 {
18947 aura->Remove();
18948 continue;
18949 }
18950
18951 aura->SetLoadedState(maxDuration, remainTime, remainCharges, stackCount, recalculateMask, info.Amounts.data());
18952 aura->ApplyForTargets();
18953 TC_LOG_DEBUG("entities.player", "Player::_LoadAuras: Added aura (SpellID: {}, EffectMask: {}) to player '{} ({})",
18954 spellInfo->Id, key.EffectMask, GetName(), GetGUID().ToString());
18955 }
18956 }
18957 while (auraResult->NextRow());
18958 }
18959
18960 // TODO: finish dragonriding - this forces old flight mode
18961 AddAura(404468, this);
18962}
18963
18965{
18966 for (uint32 glyphId : GetGlyphs(GetActiveTalentGroup()))
18967 CastSpell(this, sGlyphPropertiesStore.AssertEntry(glyphId)->SpellID, true);
18968}
18969
18971{
18973 SpawnCorpseBones(false);
18974
18975 if (!IsAlive())
18976 {
18978 ResurrectPlayer(0.5f);
18979 else if (result)
18980 {
18981 Field* fields = result->Fetch();
18982 _corpseLocation.WorldRelocate(fields[0].GetUInt16(), fields[1].GetFloat(), fields[2].GetFloat(), fields[3].GetFloat(), fields[4].GetFloat());
18983 if (!sMapStore.AssertEntry(_corpseLocation.GetMapId())->Instanceable())
18985 else
18987 }
18988 }
18989
18991}
18992
18994 PreparedQueryResult azeriteItemMilestonePowersResult, PreparedQueryResult azeriteItemUnlockedEssencesResult,
18995 PreparedQueryResult azeriteEmpoweredItemResult, uint32 timeDiff)
18996{
18997 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13
18998 // SELECT guid, itemEntry, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomBonusListId, durability, playedTime, createTime, text,
18999 // 14 15 16 17 18 19
19000 // battlePetSpeciesId, battlePetBreedData, battlePetLevel, battlePetDisplayId, context, bonusListIDs,
19001 // 20 21 22 23 24 25
19002 // itemModifiedAppearanceAllSpecs, itemModifiedAppearanceSpec1, itemModifiedAppearanceSpec2, itemModifiedAppearanceSpec3, itemModifiedAppearanceSpec4, itemModifiedAppearanceSpec5,
19003 // 26 27 28 29 30 31
19004 // spellItemEnchantmentAllSpecs, spellItemEnchantmentSpec1, spellItemEnchantmentSpec2, spellItemEnchantmentSpec3, spellItemEnchantmentSpec4, spellItemEnchantmentSpec5,
19005 // 32 33 34
19006 // secondaryItemModifiedAppearanceAllSpecs, secondaryItemModifiedAppearanceSpec1, secondaryItemModifiedAppearanceSpec2,
19007 // 35 36 37
19008 // secondaryItemModifiedAppearanceSpec3, secondaryItemModifiedAppearanceSpec4, secondaryItemModifiedAppearanceSpec5,
19009 // 38 39 40 41 42 43 44 45 46 47 48 49
19010 // gemItemId1, gemBonuses1, gemContext1, gemScalingLevel1, gemItemId2, gemBonuses2, gemContext2, gemScalingLevel2, gemItemId3, gemBonuses3, gemContext3, gemScalingLevel3
19011 // 50 51
19012 // fixedScalingLevel, artifactKnowledgeLevel FROM item_instance
19013 // 52 53
19014 // bag, slot
19015 // FROM character_inventory ci
19016 // JOIN item_instance ii ON ci.item = ii.guid
19017 // LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid
19018 // LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid
19019 // LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid
19020 // WHERE ci.guid = ?
19021 // ORDER BY (ii.flags & 0x80000) ASC, bag ASC, slot ASC
19022
19023 //NOTE: ORDER BY ii.flags & 0x80000 makes child items load last - they need their parents to be already loaded
19024 //NOTE: the "order by `bag`" is important because it makes sure
19025 //the bagMap is filled before items in the bags are loaded
19026 //NOTE2: the "order by `slot`" is needed because mainhand weapons are (wrongly?)
19027 //expected to be equipped before offhand items (@todo fixme)
19028
19029 std::unordered_map<ObjectGuid::LowType, ItemAdditionalLoadInfo> additionalData;
19030 ItemAdditionalLoadInfo::Init(&additionalData, artifactsResult, azeriteResult, azeriteItemMilestonePowersResult,
19031 azeriteItemUnlockedEssencesResult, azeriteEmpoweredItemResult);
19032
19033 if (result)
19034 {
19035 uint32 zoneId = GetZoneId();
19036
19037 std::map<ObjectGuid, Bag*> bagMap; // fast guid lookup for bags
19038 std::map<ObjectGuid, Item*> invalidBagMap; // fast guid lookup for bags
19039 std::list<Item*> problematicItems;
19040 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
19041
19042 // Prevent items from being added to the queue while loading
19044 do
19045 {
19046 Field* fields = result->Fetch();
19047 if (Item* item = _LoadItem(trans, zoneId, timeDiff, fields))
19048 {
19049 if (ItemAdditionalLoadInfo* addionalDataPtr = Trinity::Containers::MapGetValuePtr(additionalData, fields[0].GetUInt64()))
19050 {
19051 if (item->GetTemplate()->GetArtifactID() && addionalDataPtr->Artifact)
19052 item->LoadArtifactData(this, addionalDataPtr->Artifact->Xp, addionalDataPtr->Artifact->ArtifactAppearanceId,
19053 addionalDataPtr->Artifact->ArtifactTierId, addionalDataPtr->Artifact->ArtifactPowers);
19054
19055 if (addionalDataPtr->AzeriteItem)
19056 if (AzeriteItem* azeriteItem = item->ToAzeriteItem())
19057 azeriteItem->LoadAzeriteItemData(this, *addionalDataPtr->AzeriteItem);
19058
19059 if (addionalDataPtr->AzeriteEmpoweredItem)
19060 if (AzeriteEmpoweredItem* azeriteEmpoweredItem = item->ToAzeriteEmpoweredItem())
19061 azeriteEmpoweredItem->LoadAzeriteEmpoweredItemData(this, *addionalDataPtr->AzeriteEmpoweredItem);
19062 }
19063
19064 ObjectGuid bagGuid = fields[52].GetUInt64() ? ObjectGuid::Create<HighGuid::Item>(fields[52].GetUInt64()) : ObjectGuid::Empty;
19065 uint8 slot = fields[53].GetUInt8();
19066
19069
19071 if (item->HasItemFlag(ITEM_FIELD_FLAG_CHILD))
19072 {
19073 if (Item* parent = GetItemByGuid(item->GetCreator()))
19074 {
19075 parent->SetChildItem(item->GetGUID());
19076 item->CopyArtifactDataFromParent(parent);
19077 }
19078 else
19079 {
19080 TC_LOG_ERROR("entities.player", "Player::_LoadInventory: Player '{}' ({}) has child item ({}, entry: {}) which can't be loaded into inventory because parent item was not found (Bag {}, slot: {}). Item will be sent by mail.",
19081 GetName(), GetGUID().ToString(), item->GetGUID().ToString(), item->GetEntry(), bagGuid.ToString(), slot);
19082 item->DeleteFromInventoryDB(trans);
19083 problematicItems.push_back(item);
19084 continue;
19085 }
19086 }
19087
19088 // Item is not in bag
19089 if (!bagGuid)
19090 {
19091 item->SetContainer(nullptr);
19092 item->SetSlot(slot);
19093
19095 {
19096 ItemPosCountVec dest;
19097 err = CanStoreItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false);
19098 if (err == EQUIP_ERR_OK)
19099 item = StoreItem(dest, item, true);
19100 }
19101 else if (IsEquipmentPos(INVENTORY_SLOT_BAG_0, slot))
19102 {
19103 uint16 dest;
19104 err = CanEquipItem(slot, dest, item, false, false);
19105 if (err == EQUIP_ERR_OK)
19106 QuickEquipItem(dest, item);
19107 }
19108 else if (IsBankPos(INVENTORY_SLOT_BAG_0, slot))
19109 {
19110 ItemPosCountVec dest;
19111 err = CanBankItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false, false);
19112 if (err == EQUIP_ERR_OK)
19113 item = BankItem(dest, item, true);
19114 }
19115
19116 // Remember bags that may contain items in them
19117 if (err == EQUIP_ERR_OK)
19118 {
19119 if (IsBagPos(item->GetPos()))
19120 if (Bag* pBag = item->ToBag())
19121 bagMap[item->GetGUID()] = pBag;
19122 }
19123 else
19124 if (IsBagPos(item->GetPos()))
19125 if (item->IsBag())
19126 invalidBagMap[item->GetGUID()] = item;
19127 }
19128 else
19129 {
19130 item->SetSlot(NULL_SLOT);
19131 // Item is in the bag, find the bag
19132 std::map<ObjectGuid, Bag*>::iterator itr = bagMap.find(bagGuid);
19133 if (itr != bagMap.end())
19134 {
19135 ItemPosCountVec dest;
19136 err = CanStoreItem(itr->second->GetSlot(), slot, dest, item);
19137 if (err == EQUIP_ERR_OK)
19138 item = StoreItem(dest, item, true);
19139 }
19140 else if (invalidBagMap.find(bagGuid) != invalidBagMap.end())
19141 {
19142 std::map<ObjectGuid, Item*>::iterator invalidBagItr = invalidBagMap.find(bagGuid);
19143 if (std::find(problematicItems.begin(), problematicItems.end(), invalidBagItr->second) != problematicItems.end())
19145 }
19146 else
19147 {
19148 TC_LOG_ERROR("entities.player", "Player::_LoadInventory: Player '{}' ({}) has item ({}, entry: {}) which doesnt have a valid bag (Bag {}, slot: {}). Possible cheat?",
19149 GetName(), GetGUID().ToString(), item->GetGUID().ToString(), item->GetEntry(), bagGuid.ToString(), slot);
19150 item->DeleteFromInventoryDB(trans);
19151 delete item;
19152 continue;
19153 }
19154 }
19155
19156 // Item's state may have changed after storing
19157 if (err == EQUIP_ERR_OK)
19158 item->SetState(ITEM_UNCHANGED, this);
19159 else
19160 {
19161 TC_LOG_ERROR("entities.player", "Player::_LoadInventory: Player '{}' ({}) has item ({}, entry: {}) which can't be loaded into inventory (Bag {}, slot: {}) by reason {}. Item will be sent by mail.",
19162 GetName(), GetGUID().ToString(), item->GetGUID().ToString(), item->GetEntry(), bagGuid.ToString(), slot, uint32(err));
19163 item->DeleteFromInventoryDB(trans);
19164 problematicItems.push_back(item);
19165 }
19166 }
19167 } while (result->NextRow());
19168
19170
19171 // Send problematic items by mail
19172 while (!problematicItems.empty())
19173 {
19174 std::string subject = GetSession()->GetTrinityString(LANG_NOT_EQUIPPED_ITEM);
19175
19176 MailDraft draft(subject, "There were problems with equipping item(s).");
19177 for (uint8 i = 0; !problematicItems.empty() && i < MAX_MAIL_ITEMS; ++i)
19178 {
19179 draft.AddItem(problematicItems.front());
19180 problematicItems.pop_front();
19181 }
19183 }
19184
19185 CharacterDatabase.CommitTransaction(trans);
19186 }
19187 //if (IsAlive())
19189 // Apply all azerite item mods, azerite empowered item mods will get applied through its spell script
19191}
19192
19194{
19195 if (!result)
19196 return;
19197
19198 do
19199 {
19200 // SELECT itemId, itemEntry, slot, creatorGuid, randomBonusListId, fixedScalingLevel, artifactKnowledgeLevel, context, bonusListIDs FROM character_void_storage WHERE playerGuid = ?
19201 Field* fields = result->Fetch();
19202
19203 uint64 itemId = fields[0].GetUInt64();
19204 uint32 itemEntry = fields[1].GetUInt32();
19205 uint8 slot = fields[2].GetUInt8();
19206 ObjectGuid creatorGuid = fields[3].GetUInt64() ? ObjectGuid::Create<HighGuid::Player>(fields[3].GetUInt64()) : ObjectGuid::Empty;
19207 ItemRandomBonusListId randomBonusListId = fields[4].GetUInt32();
19208 uint32 fixedScalingLevel = fields[5].GetUInt32();
19209 uint32 artifactKnowledgeLevel = fields[6].GetUInt32();
19210 ItemContext context = ItemContext(fields[7].GetUInt8());
19211 std::vector<int32> bonusListIDs;
19212 for (std::string_view bonusListIDtoken : Trinity::Tokenize(fields[8].GetStringView(), ' ', false))
19213 if (Optional<int32> bonusListID = Trinity::StringTo<int32>(bonusListIDtoken))
19214 bonusListIDs.push_back(*bonusListID);
19215
19216 if (!itemId)
19217 {
19218 TC_LOG_ERROR("entities.player", "Player::_LoadVoidStorage: Player '{}' ({}) has an item with an invalid id (item id: {}, entry: {}).",
19219 GetName(), GetGUID().ToString(), itemId, itemEntry);
19220 continue;
19221 }
19222
19223 if (!sObjectMgr->GetItemTemplate(itemEntry))
19224 {
19225 TC_LOG_ERROR("entities.player", "Player::_LoadVoidStorage: Player '{}' ({}) has an item with an invalid entry (item id: {}, entry: {}).",
19226 GetName(), GetGUID().ToString(), itemId, itemEntry);
19227 continue;
19228 }
19229
19230 if (slot >= VOID_STORAGE_MAX_SLOT)
19231 {
19232 TC_LOG_ERROR("entities.player", "Player::_LoadVoidStorage: Player '{}' ({}) has an item with an invalid slot (item id: {}, entry: {}, slot: {}).",
19233 GetName(), GetGUID().ToString(), itemId, itemEntry, slot);
19234 continue;
19235 }
19236
19237 _voidStorageItems[slot] = new VoidStorageItem(itemId, itemEntry, creatorGuid, randomBonusListId, fixedScalingLevel, artifactKnowledgeLevel,
19238 context, bonusListIDs);
19239
19241 voidInstance.Initialize(_voidStorageItems[slot]);
19242 BonusData bonus;
19243 bonus.Initialize(voidInstance);
19244
19246 }
19247 while (result->NextRow());
19248}
19249
19251{
19252 Item* item = nullptr;
19253 ObjectGuid::LowType itemGuid = fields[0].GetUInt64();
19254 uint32 itemEntry = fields[1].GetUInt32();
19255 if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry))
19256 {
19257 bool remove = false;
19258 item = NewItemOrBag(proto);
19259 if (item->LoadFromDB(itemGuid, GetGUID(), fields, itemEntry))
19260 {
19262
19263 // Do not allow to have item limited to another map/zone in alive state
19264 if (IsAlive() && item->IsLimitedToAnotherMapOrZone(GetMapId(), zoneId))
19265 {
19266 TC_LOG_DEBUG("entities.player.loading", "Player::_LoadInventory: player ({}, name: '{}', map: {}) has item ({}) limited to another map ({}). Deleting item.",
19267 GetGUID().ToString(), GetName(), GetMapId(), item->GetGUID().ToString(), zoneId);
19268 remove = true;
19269 }
19270 // "Conjured items disappear if you are logged out for more than 15 minutes"
19271 else if (timeDiff > 15 * MINUTE && proto->HasFlag(ITEM_FLAG_CONJURED))
19272 {
19273 TC_LOG_DEBUG("entities.player.loading", "Player::_LoadInventory: player ({}, name: '{}', diff: {}) has conjured item ({}) with expired lifetime (15 minutes). Deleting item.",
19274 GetGUID().ToString(), GetName(), timeDiff, item->GetGUID().ToString());
19275 remove = true;
19276 }
19277 else if (item->IsRefundable())
19278 {
19279 if (item->IsRefundExpired())
19280 {
19281 TC_LOG_DEBUG("entities.player.loading", "Player::_LoadInventory: player ({}, name: '{}') has item ({}) with expired refund time ({}). Deleting refund data and removing refundable flag.",
19283
19284 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_REFUND_INSTANCE);
19285 stmt->setUInt64(0, item->GetGUID().GetCounter());
19286 trans->Append(stmt);
19287
19289 }
19290 else
19291 {
19292 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEM_REFUNDS);
19293 stmt->setUInt64(0, item->GetGUID().GetCounter());
19294 stmt->setUInt64(1, GetGUID().GetCounter());
19295 if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
19296 {
19297 item->SetRefundRecipient(GetGUID());
19298 item->SetPaidMoney((*result)[0].GetUInt64());
19299 item->SetPaidExtendedCost((*result)[1].GetUInt16());
19300 AddRefundReference(item->GetGUID());
19301 }
19302 else
19303 {
19304 TC_LOG_WARN("entities.player.loading", "Player::_LoadInventory: player ({}, name: '{}') has item ({}) with refundable flags, but without data in item_refund_instance. Removing flag.",
19305 GetGUID().ToString(), GetName(), item->GetGUID().ToString());
19307 }
19308 }
19309 }
19310 else if (item->IsBOPTradeable())
19311 {
19312 stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEM_BOP_TRADE);
19313 stmt->setUInt64(0, item->GetGUID().GetCounter());
19314 if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
19315 {
19316 GuidSet looters;
19317 for (std::string_view guidStr : Trinity::Tokenize((*result)[0].GetStringView(), ' ', false))
19318 looters.insert(ObjectGuid::Create<HighGuid::Player>(Trinity::StringTo<uint64>(guidStr).value_or(UI64LIT(0))));
19319
19320 if (looters.size() > 1 && item->GetTemplate()->GetMaxStackSize() == 1 && item->IsSoulBound())
19321 {
19322 item->SetSoulboundTradeable(looters);
19323 AddTradeableItem(item);
19324 }
19325 else
19326 item->ClearSoulboundTradeable(this);
19327 }
19328 else
19329 {
19330 TC_LOG_WARN("entities.player.loading", "Player::_LoadInventory: player ({}, name: '{}') has item ({}) with ITEM_FLAG_BOP_TRADEABLE flag, but without data in item_soulbound_trade_data. Removing flag.",
19331 GetGUID().ToString(), GetName(), item->GetGUID().ToString());
19333 }
19334 }
19335 else if (proto->GetHolidayID())
19336 {
19337 remove = true;
19338 GameEventMgr::GameEventDataMap const& events = sGameEventMgr->GetEventMap();
19339 GameEventMgr::ActiveEvents const& activeEventsList = sGameEventMgr->GetActiveEventList();
19340 for (GameEventMgr::ActiveEvents::const_iterator itr = activeEventsList.begin(); itr != activeEventsList.end(); ++itr)
19341 {
19342 if (events[*itr].holiday_id == proto->GetHolidayID())
19343 {
19344 remove = false;
19345 break;
19346 }
19347 }
19348 }
19349 }
19350 else
19351 {
19352 TC_LOG_ERROR("entities.player", "Player::_LoadInventory: player ({}, name: '{}') has a broken item (GUID: {}, entry: {}) in inventory. Deleting item.",
19353 GetGUID().ToString(), GetName(), itemGuid, itemEntry);
19354 remove = true;
19355 }
19356 // Remove item from inventory if necessary
19357 if (remove)
19358 {
19359 Item::DeleteFromInventoryDB(trans, itemGuid);
19360 item->FSetState(ITEM_REMOVED);
19361 item->SaveToDB(trans); // it also deletes item object!
19362 item = nullptr;
19363 }
19364 }
19365 else
19366 {
19367 TC_LOG_ERROR("entities.player", "Player::_LoadInventory: player ({}, name: '{}') has an unknown item (entry: {}) in inventory. Deleting item.",
19368 GetGUID().ToString(), GetName(), itemEntry);
19369 Item::DeleteFromInventoryDB(trans, itemGuid);
19370 Item::DeleteFromDB(trans, itemGuid);
19371 AzeriteItem::DeleteFromDB(trans, itemGuid);
19372 AzeriteEmpoweredItem::DeleteFromDB(trans, itemGuid);
19373 }
19374 return item;
19375}
19376
19377// load mailed item which should receive current player
19378Item* Player::_LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint64 mailId, Mail* mail, Field* fields, ItemAdditionalLoadInfo* addionalData)
19379{
19380 ObjectGuid::LowType itemGuid = fields[0].GetUInt64();
19381 uint32 itemEntry = fields[1].GetUInt32();
19382
19383 ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry);
19384 if (!proto)
19385 {
19386 TC_LOG_ERROR("entities.player", "Player '{}' ({}) has unknown item in mailed items (GUID: {}, Entry: {}) in mail ({}), deleted.",
19387 player ? player->GetName() : "<unknown>", playerGuid.ToString(), itemGuid, itemEntry, mailId);
19388
19389 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
19390
19392 stmt->setUInt64(0, itemGuid);
19393 trans->Append(stmt);
19394
19395 Item::DeleteFromDB(trans, itemGuid);
19396 AzeriteItem::DeleteFromDB(trans, itemGuid);
19397 AzeriteEmpoweredItem::DeleteFromDB(trans, itemGuid);
19398
19399 CharacterDatabase.CommitTransaction(trans);
19400 return nullptr;
19401 }
19402
19403 Item* item = NewItemOrBag(proto);
19404
19405 ObjectGuid ownerGuid = fields[52].GetUInt64() ? ObjectGuid::Create<HighGuid::Player>(fields[52].GetUInt64()) : ObjectGuid::Empty;
19406 if (!item->LoadFromDB(itemGuid, ownerGuid, fields, itemEntry))
19407 {
19408 TC_LOG_ERROR("entities.player", "Player::_LoadMailedItems: Item (GUID: {}) in mail ({}) doesn't exist, deleted from mail.", itemGuid, mailId);
19409
19411 stmt->setUInt64(0, itemGuid);
19412 CharacterDatabase.Execute(stmt);
19413
19414 item->FSetState(ITEM_REMOVED);
19415
19417 item->SaveToDB(temp); // it also deletes item object !
19418 return nullptr;
19419 }
19420
19421 if (addionalData)
19422 {
19423 if (item->GetTemplate()->GetArtifactID() && addionalData->Artifact)
19424 item->LoadArtifactData(player, addionalData->Artifact->Xp, addionalData->Artifact->ArtifactAppearanceId,
19425 addionalData->Artifact->ArtifactTierId, addionalData->Artifact->ArtifactPowers);
19426
19427 if (addionalData->AzeriteItem)
19428 if (AzeriteItem* azeriteItem = item->ToAzeriteItem())
19429 azeriteItem->LoadAzeriteItemData(player, *addionalData->AzeriteItem);
19430
19431 if (addionalData->AzeriteEmpoweredItem)
19432 if (AzeriteEmpoweredItem* azeriteEmpoweredItem = item->ToAzeriteEmpoweredItem())
19433 azeriteEmpoweredItem->LoadAzeriteEmpoweredItemData(player, *addionalData->AzeriteEmpoweredItem);
19434 }
19435
19436 if (mail)
19437 mail->AddItem(itemGuid, itemEntry);
19438
19439 if (player)
19440 player->AddMItem(item);
19441
19442 return item;
19443}
19444
19445void Player::_LoadMail(PreparedQueryResult mailsResult, PreparedQueryResult mailItemsResult, PreparedQueryResult artifactResult, PreparedQueryResult azeriteItemResult,
19446 PreparedQueryResult azeriteItemMilestonePowersResult, PreparedQueryResult azeriteItemUnlockedEssencesResult, PreparedQueryResult azeriteEmpoweredItemResult)
19447{
19448 std::unordered_map<uint64, Mail*> mailById;
19449
19450 if (mailsResult)
19451 {
19452 do
19453 {
19454 Field* fields = mailsResult->Fetch();
19455 Mail* m = new Mail();
19456
19457 m->messageID = fields[0].GetUInt64();
19458 m->messageType = fields[1].GetUInt8();
19459 m->sender = fields[2].GetUInt64();
19460 m->receiver = fields[3].GetUInt64();
19461 m->subject = fields[4].GetString();
19462 m->body = fields[5].GetString();
19463 m->expire_time = fields[6].GetInt64();
19464 m->deliver_time = fields[7].GetInt64();
19465 m->money = fields[8].GetUInt64();
19466 m->COD = fields[9].GetUInt64();
19467 m->checked = fields[10].GetUInt8();
19468 m->stationery = fields[11].GetUInt8();
19469 m->mailTemplateId = fields[12].GetInt16();
19470
19471 if (m->mailTemplateId && !sMailTemplateStore.LookupEntry(m->mailTemplateId))
19472 {
19473 TC_LOG_ERROR("entities.player", "Player::_LoadMail: Mail ({}) has nonexistent MailTemplateId ({}), remove at load", m->messageID, m->mailTemplateId);
19474 m->mailTemplateId = 0;
19475 }
19476
19478
19479 m_mail.push_back(m);
19480 mailById[m->messageID] = m;
19481 }
19482 while (mailsResult->NextRow());
19483 }
19484
19485 if (mailItemsResult)
19486 {
19487 std::unordered_map<ObjectGuid::LowType, ItemAdditionalLoadInfo> additionalData;
19488 ItemAdditionalLoadInfo::Init(&additionalData, artifactResult, azeriteItemResult, azeriteItemMilestonePowersResult,
19489 azeriteItemUnlockedEssencesResult, azeriteEmpoweredItemResult);
19490
19491 do
19492 {
19493 Field* fields = mailItemsResult->Fetch();
19494 uint64 mailId = fields[53].GetUInt64();
19495 _LoadMailedItem(GetGUID(), this, mailId, mailById[mailId], fields, Trinity::Containers::MapGetValuePtr(additionalData, fields[0].GetUInt64()));
19496 } while (mailItemsResult->NextRow());
19497 }
19498
19500}
19501
19503{
19504 uint16 slot = 0;
19505
19507 //QueryResult* result = CharacterDatabase.PQuery("SELECT quest, status, explored, acceptTime, endTime WHERE guid = '{}' AND status <> 0", GetGUIDLow());
19508
19509 time_t lastDailyReset = sWorld->GetNextDailyQuestsResetTime() - DAY;
19510 time_t lastWeeklyReset = sWorld->GetNextWeeklyQuestsResetTime() - WEEK;
19511
19512 if (result)
19513 {
19514 do
19515 {
19516 Field* fields = result->Fetch();
19517
19518 uint32 quest_id = fields[0].GetUInt32();
19519 // used to be new, no delete?
19520 Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
19521 if (!quest)
19522 continue;
19523
19524 // find or create
19525 QuestStatusData questStatusData;
19526
19527 uint8 qstatus = fields[1].GetUInt8();
19528 if (qstatus < MAX_QUEST_STATUS)
19529 questStatusData.Status = QuestStatus(qstatus);
19530 else
19531 {
19532 questStatusData.Status = QUEST_STATUS_INCOMPLETE;
19533 TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatus: Player '{}' ({}) has invalid quest {} status ({}), replaced by QUEST_STATUS_INCOMPLETE(3).",
19534 GetName(), GetGUID().ToString(), quest_id, qstatus);
19535 }
19536
19537 questStatusData.Explored = (fields[2].GetUInt8() > 0);
19538
19539 questStatusData.AcceptTime = time_t(fields[3].GetInt64());
19541 {
19542 if ((quest->IsDaily() && questStatusData.AcceptTime < lastDailyReset)
19543 || (quest->IsWeekly() && questStatusData.AcceptTime < lastWeeklyReset))
19544 {
19545 questStatusData.Status = QUEST_STATUS_NONE;
19548 }
19549 }
19550
19551 time_t endTime = time_t(fields[4].GetInt64());
19552
19553 if (quest->GetLimitTime() && !GetQuestRewardStatus(quest_id))
19554 {
19555 AddTimedQuest(quest_id);
19556
19557 if (endTime <= GameTime::GetGameTime())
19558 questStatusData.Timer = 1;
19559 else
19560 questStatusData.Timer = uint32((endTime - GameTime::GetGameTime()) * IN_MILLISECONDS);
19561 }
19562 else
19563 endTime = 0;
19564
19565 TC_LOG_DEBUG("entities.player.loading", "Player::_LoadQuestStatus: Quest status is {{{}}} for quest {{{}}} for player ({})", questStatusData.Status, quest_id, GetGUID().ToString());
19566
19567 // add to quest log
19568 if (slot < MAX_QUEST_LOG_SIZE && questStatusData.Status != QUEST_STATUS_NONE)
19569 {
19570 questStatusData.Slot = slot;
19571
19572 auto questStatusItr = m_QuestStatus.emplace(quest_id, std::move(questStatusData)).first;
19573 for (QuestObjective const& obj : quest->GetObjectives())
19574 m_questObjectiveStatus.emplace(std::make_pair(QuestObjectiveType(obj.Type), obj.ObjectID), QuestObjectiveStatusData{ questStatusItr, obj.ID });
19575
19576 SetQuestSlot(slot, quest_id);
19577 SetQuestSlotEndTime(slot, endTime);
19578
19579 if (questStatusItr->second.Status == QUEST_STATUS_COMPLETE)
19581 else if (questStatusItr->second.Status == QUEST_STATUS_FAILED)
19583
19585 CastSpell(this, quest->GetSrcSpell(), TRIGGERED_FULL_MASK);
19586
19587 ++slot;
19588 }
19589 }
19590 while (result->NextRow());
19591 }
19592
19593 // clear quest log tail
19594 for (uint16 i = slot; i < MAX_QUEST_LOG_SIZE; ++i)
19595 SetQuestSlot(i, 0);
19596}
19597
19599{
19600
19602 //QueryResult* result = CharacterDatabase.PQuery("SELECT quest, objective, data FROM character_queststatus_objectives WHERE guid = '{}'", GetGUIDLow());
19603
19604 if (result)
19605 {
19606 do
19607 {
19608 Field* fields = result->Fetch();
19609
19610 uint32 questID = fields[0].GetUInt32();
19611 Quest const* quest = sObjectMgr->GetQuestTemplate(questID);
19612 if (!quest)
19613 continue;
19614
19615 auto itr = m_QuestStatus.find(questID);
19616 if (itr != m_QuestStatus.end() && itr->second.Slot < MAX_QUEST_LOG_SIZE)
19617 {
19618 QuestStatusData& questStatusData = itr->second;
19619 uint8 storageIndex = fields[1].GetUInt8();
19620 auto objectiveItr = std::find_if(quest->Objectives.begin(), quest->Objectives.end(), [=](QuestObjective const& objective) { return uint8(objective.StorageIndex) == storageIndex; });
19621 if (objectiveItr != quest->Objectives.end())
19622 {
19623 int32 data = fields[2].GetInt32();
19624 if (!objectiveItr->IsStoringFlag())
19625 SetQuestSlotCounter(questStatusData.Slot, storageIndex, data);
19626 else if (data)
19627 SetQuestSlotObjectiveFlag(questStatusData.Slot, storageIndex);
19628 }
19629 else
19630 TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectives: Player '{}' ({}) has quest {} out of range objective index {}.", GetName(), GetGUID().ToString(), questID, storageIndex);
19631 }
19632 else
19633 TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectives: Player {} ({}) does not have quest {} but has objective data for it.", GetName(), GetGUID().ToString(), questID);
19634 }
19635 while (result->NextRow());
19636 }
19637}
19638
19640{
19641
19643 //QueryResult* result = CharacterDatabase.PQuery("SELECT quest, objective, spawnTrackingId FROM character_queststatus_objectives_spawn_tracking WHERE guid = '{}'", GetGUIDLow());
19644
19645 if (result)
19646 {
19647 do
19648 {
19649 Field* fields = result->Fetch();
19650
19651 uint32 questID = fields[0].GetUInt32();
19652 Quest const* quest = sObjectMgr->GetQuestTemplate(questID);
19653 if (!quest)
19654 continue;
19655
19656 auto itr = m_QuestStatus.find(questID);
19657 if (itr != m_QuestStatus.end() && itr->second.Slot < MAX_QUEST_LOG_SIZE)
19658 {
19659 QuestStatusData& questStatusData = itr->second;
19660 uint8 storageIndex = fields[1].GetUInt8();
19661 auto objectiveItr = std::find_if(quest->Objectives.begin(), quest->Objectives.end(), [=](QuestObjective const& objective) { return uint8(objective.StorageIndex) == storageIndex; });
19662 if (objectiveItr != quest->Objectives.end())
19663 {
19664 uint32 spawnTrackingId = fields[2].GetUInt32();
19665
19666 if (sObjectMgr->IsQuestObjectiveForSpawnTracking(spawnTrackingId, objectiveItr->ID))
19667 questStatusData.SpawnTrackingList.insert(std::make_pair(storageIndex, spawnTrackingId));
19668 else
19669 TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectiveSpawnTrackings: Player '{}' ({}) has objective {} (quest {}) with unrelated spawn tracking {}.", GetName(), GetGUID().ToString(), objectiveItr->ID, questID, spawnTrackingId);
19670 }
19671 else
19672 TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectiveSpawnTrackings: Player '{}' ({}) has quest {} out of range objective index {}.", GetName(), GetGUID().ToString(), questID, storageIndex);
19673 }
19674 else
19675 TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectiveSpawnTrackings: Player {} ({}) does not have quest {} but has objective spawn trackings for it.", GetName(), GetGUID().ToString(), questID);
19676 } while (result->NextRow());
19677 }
19678}
19679
19681{
19682 // SELECT quest FROM character_queststatus_rewarded WHERE guid = ?
19683
19684 if (result)
19685 {
19686 do
19687 {
19688 Field* fields = result->Fetch();
19689
19690 uint32 quest_id = fields[0].GetUInt32();
19691 // used to be new, no delete?
19692 Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
19693 if (quest)
19694 {
19695 // learn rewarded spell if unknown
19697
19698 // set rewarded title if any
19699 if (quest->GetRewTitle())
19700 if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(quest->GetRewTitle()))
19701 SetTitle(titleEntry);
19702
19703 // Skip loading special quests - they are also added to rewarded quests but only once and remain there forever
19704 // instead add them separately from load daily/weekly/monthly/seasonal
19705 if (!quest->IsDailyOrWeekly() && !quest->IsMonthly() && !quest->IsSeasonal())
19706 SetQuestCompletedBit(quest_id, true);
19707
19708 for (uint32 i = 0; i < quest->GetRewChoiceItemsCount(); ++i)
19710
19711 for (uint32 i = 0; i < quest->GetRewItemsCount(); ++i)
19713
19714 if (std::vector<QuestPackageItemEntry const*> const* questPackageItems = sDB2Manager.GetQuestPackageItems(quest->GetQuestPackageID()))
19715 for (QuestPackageItemEntry const* questPackageItem : *questPackageItems)
19716 if (ItemTemplate const* rewardProto = sObjectMgr->GetItemTemplate(questPackageItem->ItemID))
19717 if (rewardProto->ItemSpecClassMask & GetClassMask())
19718 GetSession()->GetCollectionMgr()->AddItemAppearance(questPackageItem->ItemID);
19719
19721 m_RewardedQuests.insert(quest_id);
19722 }
19723 }
19724 while (result->NextRow());
19725 }
19726}
19727
19729{
19730 m_DFQuests.clear();
19731
19732 //QueryResult* result = CharacterDatabase.PQuery("SELECT quest, time FROM character_queststatus_daily WHERE guid = '{}'", GetGUIDLow());
19733
19734 if (result)
19735 {
19736 do
19737 {
19738 Field* fields = result->Fetch();
19739 uint32 quest_id = fields[0].GetUInt32();
19740 if (Quest const* qQuest = sObjectMgr->GetQuestTemplate(quest_id))
19741 {
19742 if (qQuest->IsDFQuest())
19743 {
19744 m_DFQuests.insert(qQuest->GetQuestId());
19745 m_lastDailyQuestTime = fields[1].GetInt64();
19746 continue;
19747 }
19748 }
19749
19750 // save _any_ from daily quest times (it must be after last reset anyway)
19751 m_lastDailyQuestTime = fields[1].GetInt64();
19752
19753 Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
19754 if (!quest)
19755 continue;
19756
19758 SetQuestCompletedBit(quest_id, true);
19759
19760 TC_LOG_DEBUG("entities.player.loading", "Player::_LoadDailyQuestStatus: Loaded daily quest cooldown (QuestID: {}) for player '{}' ({})",
19761 quest_id, GetName(), GetGUID().ToString());
19762 }
19763 while (result->NextRow());
19764 }
19765
19766 m_DailyQuestChanged = false;
19767}
19768
19770{
19771 m_weeklyquests.clear();
19772
19773 if (result)
19774 {
19775 do
19776 {
19777 Field* fields = result->Fetch();
19778 uint32 quest_id = fields[0].GetUInt32();
19779 Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
19780 if (!quest)
19781 continue;
19782
19783 m_weeklyquests.insert(quest_id);
19784 SetQuestCompletedBit(quest_id, true);
19785
19786 TC_LOG_DEBUG("entities.player.loading", "Player::_LoadWeeklyQuestStatus: Loaded weekly quest cooldown (QuestID: {}) for player '{}' ({})",
19787 quest_id, GetName(), GetGUID().ToString());
19788 }
19789 while (result->NextRow());
19790 }
19791
19792 m_WeeklyQuestChanged = false;
19793}
19794
19796{
19797 m_seasonalquests.clear();
19798
19799 if (result)
19800 {
19801 do
19802 {
19803 Field* fields = result->Fetch();
19804 uint32 quest_id = fields[0].GetUInt32();
19805 uint32 event_id = fields[1].GetUInt32();
19806 uint32 completedTime = fields[2].GetInt64();
19807 Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
19808 if (!quest)
19809 continue;
19810
19811 m_seasonalquests[event_id][quest_id] = completedTime;
19812 SetQuestCompletedBit(quest_id, true);
19813
19814 TC_LOG_DEBUG("entities.player.loading", "Player::_LoadSeasonalQuestStatus: Loaded seasonal quest cooldown (QuestID: {}) for player '{}' ({})",
19815 quest_id, GetName(), GetGUID().ToString());
19816 }
19817 while (result->NextRow());
19818 }
19819
19820 m_SeasonalQuestChanged = false;
19821}
19822
19824{
19825 m_monthlyquests.clear();
19826
19827 if (result)
19828 {
19829 do
19830 {
19831 Field* fields = result->Fetch();
19832 uint32 quest_id = fields[0].GetUInt32();
19833 Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
19834 if (!quest)
19835 continue;
19836
19837 m_monthlyquests.insert(quest_id);
19838 SetQuestCompletedBit(quest_id, true);
19839
19840 TC_LOG_DEBUG("entities.player.loading", "Player::_LoadMonthlyQuestStatus: Loaded monthly quest cooldown (QuestID: {}) for player '{}' ({})",
19841 quest_id, GetName(), GetGUID().ToString());
19842 }
19843 while (result->NextRow());
19844 }
19845
19846 m_MonthlyQuestChanged = false;
19847}
19848
19850{
19851 //QueryResult* result = CharacterDatabase.PQuery("SELECT spell, active, disabled, favorite FROM character_spell WHERE guid = '{}'", GetGUIDLow());
19852 if (result)
19853 {
19854 do
19855 AddSpell((*result)[0].GetUInt32(), (*result)[1].GetBool(), false, false, (*result)[2].GetBool(), true);
19856 while (result->NextRow());
19857 }
19858
19859 if (favoritesResult)
19860 {
19861 do
19862 {
19863 auto itr = m_spells.find((*favoritesResult)[0].GetUInt32());
19864 if (itr != m_spells.end())
19865 itr->second.favorite = true;
19866 } while (favoritesResult->NextRow());
19867 }
19868}
19869
19871{
19872 // 0 1 2 3 4 5
19873 //QueryResult* result = CharacterDatabase.PQuery("SELECT Spell, MapId, PositionX, PositionY, PositionZ, Orientation FROM character_spell_location WHERE Guid = ?", GetGUIDLow());
19874
19876 if (result)
19877 {
19878 do
19879 {
19880 Field* fields = result->Fetch();
19881 uint32 spellId = fields[0].GetUInt32();
19882
19883 if (!sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE))
19884 {
19885 TC_LOG_ERROR("spells", "Player::_LoadStoredAuraTeleportLocations: Player {} ({}) spell (ID: {}) does not exist",
19886 GetName(), GetGUID().ToString(), spellId);
19887 continue;
19888 }
19889
19890 WorldLocation location(fields[1].GetUInt32(), fields[2].GetFloat(), fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat());
19891 if (!MapManager::IsValidMapCoord(location))
19892 {
19893 TC_LOG_ERROR("spells", "Player::_LoadStoredAuraTeleportLocations: Player {} ({}) spell (ID: {}) has invalid position on map {}, {{{}}}.",
19894 GetName(), GetGUID().ToString(), spellId, location.GetMapId(), location.ToString());
19895 continue;
19896 }
19897
19899 storedLocation.Loc = location;
19901 }
19902 while (result->NextRow());
19903 }
19904}
19905
19907{
19908 //QueryResult* result = CharacterDatabase.PQuery("SELECT guid FROM group_member WHERE memberGuid={}", GetGUIDLow());
19909 if (result)
19910 {
19911 if (Group* group = sGroupMgr->GetGroupByDbStoreId((*result)[0].GetUInt32()))
19912 {
19913 if (group->IsLeader(GetGUID()))
19915
19916 uint8 subgroup = group->GetMemberGroup(GetGUID());
19917 SetGroup(group, subgroup);
19918 SetPartyType(group->GetGroupCategory(), GROUP_TYPE_NORMAL);
19920 // the group leader may change the instance difficulty while the player is offline
19921 SetDungeonDifficultyID(group->GetDungeonDifficultyID());
19922 SetRaidDifficultyID(group->GetRaidDifficultyID());
19923 SetLegacyRaidDifficultyID(group->GetLegacyRaidDifficultyID());
19924 }
19925 }
19926
19927 if (!GetGroup() || !GetGroup()->IsLeader(GetGUID()))
19929}
19930
19932{
19933 InstanceMap* map = GetMap()->ToInstanceMap();
19934 if (!map || map->GetInstanceId() != _pendingBindId)
19935 return;
19936
19937 if (!IsGameMaster())
19938 map->CreateInstanceLockForPlayer(this);
19939}
19940
19941void Player::SetPendingBind(uint32 instanceId, uint32 bindTimer)
19942{
19943 _pendingBindId = instanceId;
19944 _pendingBindTimer = bindTimer;
19945}
19946
19948{
19950
19951 std::vector<InstanceLock const*> instanceLocks = sInstanceLockMgr.GetInstanceLocksForPlayer(GetGUID());
19952
19954 instanceInfo.LockList.reserve(instanceLocks.size());
19955
19956 for (InstanceLock const* instanceLock : instanceLocks)
19957 {
19958 instanceInfo.LockList.emplace_back();
19959
19960 WorldPackets::Instance::InstanceLock& lockInfos = instanceInfo.LockList.back();
19961 lockInfos.InstanceID = instanceLock->GetInstanceId();
19962 lockInfos.MapID = instanceLock->GetMapId();
19963 lockInfos.DifficultyID = instanceLock->GetDifficultyId();
19964 lockInfos.TimeRemaining = int32(std::max(std::chrono::duration_cast<Seconds>(instanceLock->GetEffectiveExpiryTime() - now).count(), SI64LIT(0)));
19965 lockInfos.CompletedMask = instanceLock->GetData()->CompletedEncountersMask;
19966
19967 lockInfos.Locked = !instanceLock->IsExpired();
19968 lockInfos.Extended = instanceLock->IsExtended();
19969 }
19970
19971 SendDirectMessage(instanceInfo.Write());
19972}
19973
19974bool Player::Satisfy(AccessRequirement const* ar, uint32 target_map, TransferAbortParams* params, bool report)
19975{
19976 if (!IsGameMaster())
19977 {
19978 uint8 LevelMin = 0;
19979 uint8 LevelMax = 0;
19980 int32 failedMapDifficultyXCondition = 0;
19981 uint32 missingItem = 0;
19982 uint32 missingQuest = 0;
19983 uint32 missingAchievement = 0;
19984
19985 MapEntry const* mapEntry = sMapStore.LookupEntry(target_map);
19986 if (!mapEntry)
19987 return false;
19988
19989 Difficulty target_difficulty = GetDifficultyID(mapEntry);
19990 MapDifficultyEntry const* mapDiff = sDB2Manager.GetDownscaledMapDifficultyData(target_map, target_difficulty);
19991 if (!sWorld->getBoolConfig(CONFIG_INSTANCE_IGNORE_LEVEL))
19992 {
19993 if (DB2Manager::MapDifficultyConditionsContainer const* mapDifficultyConditions = sDB2Manager.GetMapDifficultyConditions(mapDiff->ID))
19994 {
19995 for (auto&& itr : *mapDifficultyConditions)
19996 {
19997 if (!ConditionMgr::IsPlayerMeetingCondition(this, itr.second->ID))
19998 {
19999 failedMapDifficultyXCondition = itr.first;
20000 break;
20001 }
20002 }
20003 }
20004 }
20005
20006 if (ar)
20007 {
20008 if (!sWorld->getBoolConfig(CONFIG_INSTANCE_IGNORE_LEVEL))
20009 {
20010 if (ar->levelMin && GetLevel() < ar->levelMin)
20011 LevelMin = ar->levelMin;
20012 if (ar->levelMax && GetLevel() > ar->levelMax)
20013 LevelMax = ar->levelMax;
20014 }
20015
20016 if (ar->item)
20017 {
20018 if (!HasItemCount(ar->item) &&
20019 (!ar->item2 || !HasItemCount(ar->item2)))
20020 missingItem = ar->item;
20021 }
20022 else if (ar->item2 && !HasItemCount(ar->item2))
20023 missingItem = ar->item2;
20024
20025 if (GetTeam() == ALLIANCE && ar->quest_A && !GetQuestRewardStatus(ar->quest_A))
20026 missingQuest = ar->quest_A;
20027 else if (GetTeam() == HORDE && ar->quest_H && !GetQuestRewardStatus(ar->quest_H))
20028 missingQuest = ar->quest_H;
20029
20030 Player* leader = this;
20031 ObjectGuid leaderGuid = GetGroup() ? GetGroup()->GetLeaderGUID() : GetGUID();
20032 if (leaderGuid != GetGUID())
20033 leader = ObjectAccessor::FindPlayer(leaderGuid);
20034
20035 if (ar->achievement)
20036 if (!leader || !leader->HasAchieved(ar->achievement))
20037 missingAchievement = ar->achievement;
20038 }
20039
20040 if (LevelMin || LevelMax || failedMapDifficultyXCondition || missingItem || missingQuest || missingAchievement)
20041 {
20042 if (params)
20043 params->Reason = TRANSFER_ABORT_ERROR;
20044
20045 if (report)
20046 {
20047 if (missingQuest && !ar->questFailedText.empty())
20049 else if (mapDiff->Message[sWorld->GetDefaultDbcLocale()][0] != '\0' || failedMapDifficultyXCondition) // if (missingAchievement) covered by this case
20050 {
20051 if (params)
20052 {
20054 params->Arg = target_difficulty;
20055 params->MapDifficultyXConditionId = failedMapDifficultyXCondition;
20056 }
20057 }
20058 else if (missingItem)
20059 GetSession()->SendNotification(GetSession()->GetTrinityString(LANG_LEVEL_MINREQUIRED_AND_ITEM), LevelMin, ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(missingItem))->GetName(GetSession()->GetSessionDbcLocale()));
20060 else if (LevelMin)
20062 }
20063 return false;
20064 }
20065 }
20066 return true;
20067}
20068
20070{
20071 if (CanBeGameMaster())
20072 {
20074 return true;
20075 }
20076 else
20077 return false;
20078}
20079
20080bool Player::CheckInstanceValidity(bool /*isLogin*/)
20081{
20082 // game masters' instances are always valid
20083 if (IsGameMaster())
20084 return true;
20085
20086 // non-instances are always valid
20087 Map* map = FindMap();
20088 InstanceMap* instance = map ? map->ToInstanceMap() : nullptr;
20089 if (!instance)
20090 return true;
20091
20092 Group* group = GetGroup();
20093 // raid instances require the player to be in a raid group to be valid
20094 if (map->IsRaid() && !sWorld->getBoolConfig(CONFIG_INSTANCE_IGNORE_RAID) && (map->GetEntry()->Expansion() >= sWorld->getIntConfig(CONFIG_EXPANSION)))
20095 if (!group || !group->isRaidGroup())
20096 return false;
20097
20098 if (group)
20099 {
20100 if (group != instance->GetOwningGroup())
20101 return false;
20102 }
20103 else
20104 {
20105 // instance is invalid if we are not grouped and there are other players
20106 if (map->GetPlayersCountExceptGMs() > 1)
20107 return false;
20108 }
20109
20110 return true;
20111}
20112
20114{
20115 if (_instanceResetTimes.size() < sWorld->getIntConfig(CONFIG_MAX_INSTANCES_PER_HOUR))
20116 return true;
20117 return _instanceResetTimes.find(instanceId) != _instanceResetTimes.end();
20118}
20119
20120void Player::AddInstanceEnterTime(uint32 instanceId, time_t enterTime)
20121{
20122 if (_instanceResetTimes.find(instanceId) == _instanceResetTimes.end())
20123 _instanceResetTimes.insert(InstanceTimeMap::value_type(instanceId, enterTime + HOUR));
20124}
20125
20127{
20128 WorldSafeLocsEntry const* entranceLocation = nullptr;
20129 MapEntry const* mapEntry = sMapStore.AssertEntry(targetMapId);
20130
20131 if (mapEntry->Instanceable())
20132 {
20133 // Check if we can contact the instancescript of the instance for an updated entrance location
20134 if (uint32 targetInstanceId = sMapMgr->FindInstanceIdForPlayer(targetMapId, this))
20135 if (Map* map = sMapMgr->FindMap(targetMapId, targetInstanceId))
20136 if (InstanceMap* instanceMap = map->ToInstanceMap())
20137 if (InstanceScript* instanceScript = instanceMap->GetInstanceScript())
20138 entranceLocation = sObjectMgr->GetWorldSafeLoc(instanceScript->GetEntranceLocation());
20139
20140 // Finally check with the instancesave for an entrance location if we did not get a valid one from the instancescript
20141 if (!entranceLocation)
20142 {
20143 Group* group = GetGroup();
20144 Difficulty difficulty = group ? group->GetDifficultyID(mapEntry) : GetDifficultyID(mapEntry);
20145 ObjectGuid instanceOwnerGuid = group ? group->GetRecentInstanceOwner(targetMapId) : GetGUID();
20146 if (InstanceLock const* instanceLock = sInstanceLockMgr.FindActiveInstanceLock(instanceOwnerGuid, { mapEntry, sDB2Manager.GetDownscaledMapDifficultyData(targetMapId, difficulty) }))
20147 entranceLocation = sObjectMgr->GetWorldSafeLoc(instanceLock->GetData()->EntranceWorldSafeLocId);
20148 }
20149 }
20150 return entranceLocation;
20151}
20152
20154{
20155 PlayerInfo const* info = sObjectMgr->GetPlayerInfo(GetRace(), GetClass());
20156 if (!info)
20157 {
20158 TC_LOG_ERROR("entities.player", "Player::_LoadHomeBind: Player '{}' ({}) has incorrect race/class ({}/{}) pair. Can't load.",
20160 return false;
20161 }
20162
20163 bool ok = false;
20164 // 0 1 2 3 4 5
20165 // SELECT mapId, zoneId, posX, posY, posZ, orientation FROM character_homebind WHERE guid = ?
20166 if (result)
20167 {
20168 Field* fields = result->Fetch();
20169
20170 m_homebind.WorldRelocate(fields[0].GetUInt16(), fields[2].GetFloat(), fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat());
20171 m_homebindAreaId = fields[1].GetUInt16();
20172
20173 MapEntry const* bindMapEntry = sMapStore.LookupEntry(m_homebind.GetMapId());
20174
20175 // accept saved data only for valid position (and non instanceable), and accessable
20177 !bindMapEntry->Instanceable() && GetSession()->GetExpansion() >= bindMapEntry->Expansion())
20178 ok = true;
20179 else
20180 {
20182 stmt->setUInt64(0, GetGUID().GetCounter());
20183 CharacterDatabase.Execute(stmt);
20184 }
20185 }
20186
20187 auto saveHomebindToDb = [&]()
20188 {
20190 stmt->setUInt64(0, GetGUID().GetCounter());
20191 stmt->setUInt16(1, m_homebind.GetMapId());
20192 stmt->setUInt16(2, m_homebindAreaId);
20193 stmt->setFloat(3, m_homebind.GetPositionX());
20194 stmt->setFloat(4, m_homebind.GetPositionY());
20195 stmt->setFloat(5, m_homebind.GetPositionZ());
20197 CharacterDatabase.Execute(stmt);
20198 };
20199
20200 if (!ok && HasAtLoginFlag(AT_LOGIN_FIRST))
20201 {
20203 if (!createPosition.TransportGuid)
20204 {
20205 m_homebind.WorldRelocate(createPosition.Loc);
20207
20208 saveHomebindToDb();
20209 ok = true;
20210 }
20211 }
20212
20213 if (!ok)
20214 {
20215 WorldSafeLocsEntry const* loc = sObjectMgr->GetDefaultGraveyard(GetTeam());
20216 if (!loc && GetRace() == RACE_PANDAREN_NEUTRAL)
20217 loc = sObjectMgr->GetWorldSafeLoc(3295); // The Wandering Isle, Starting Area GY
20218
20219 ASSERT(loc, "Missing fallback graveyard location for faction %u", uint32(GetTeamId()));
20220
20223
20224 saveHomebindToDb();
20225 }
20226
20227 TC_LOG_DEBUG("entities.player", "Player::_LoadHomeBind: Setting home position (MapID: {}, AreaID: {}, X: {}, Y: {}, Z: {} O: {}) of player '{}' ({})",
20229
20230 return true;
20231}
20232
20233/*********************************************************/
20234/*** SAVE SYSTEM ***/
20235/*********************************************************/
20236
20237void Player::SaveToDB(bool create /*=false*/)
20238{
20239 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
20240 LoginDatabaseTransaction loginTransaction = LoginDatabase.BeginTransaction();
20241
20242 SaveToDB(loginTransaction, trans, create);
20243
20244 CharacterDatabase.CommitTransaction(trans);
20245 LoginDatabase.CommitTransaction(loginTransaction);
20246}
20247
20248void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDatabaseTransaction trans, bool create /* = false */)
20249{
20250 // delay auto save at any saves (manual, in code, or autosave)
20251 m_nextSave = sWorld->getIntConfig(CONFIG_INTERVAL_SAVE);
20252
20253 //lets allow only players in world to be saved
20255 {
20257 return;
20258 }
20259
20260 // first save/honor gain after midnight will also update the player's honor fields
20262
20263 TC_LOG_DEBUG("entities.unit", "Player::SaveToDB: The value of player {} at save: ", m_name);
20265
20266 if (!create)
20267 sScriptMgr->OnPlayerSave(this);
20268
20269 CharacterDatabasePreparedStatement* stmt = nullptr;
20270 uint8 index = 0;
20271
20272 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_FISHINGSTEPS);
20273 stmt->setUInt64(0, GetGUID().GetCounter());
20274 trans->Append(stmt);
20275
20276 auto finiteAlways = [](float f) { return std::isfinite(f) ? f : 0.0f; };
20277
20278 if (create)
20279 {
20282 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER);
20283 stmt->setUInt64(index++, GetGUID().GetCounter());
20284 stmt->setUInt32(index++, GetSession()->GetAccountId());
20285 stmt->setString(index++, GetName());
20286 stmt->setUInt8(index++, GetRace());
20287 stmt->setUInt8(index++, GetClass());
20288 stmt->setUInt8(index++, GetNativeGender()); // save gender from PLAYER_BYTES_3, UNIT_BYTES_0 changes with every transform effect
20289 stmt->setUInt8(index++, GetLevel());
20290 stmt->setUInt32(index++, GetXP());
20291 stmt->setUInt64(index++, GetMoney());
20292 stmt->setUInt8(index++, GetInventorySlotCount());
20293 stmt->setUInt32(index++, [&]
20294 {
20295 BagSlotFlags inventoryFlags = BagSlotFlags::None;
20296 if (m_activePlayerData->BackpackAutoSortDisabled)
20297 inventoryFlags |= BagSlotFlags::DisableAutoSort;
20298 if (m_activePlayerData->BackpackSellJunkDisabled)
20299 inventoryFlags |= BagSlotFlags::ExcludeJunkSell;
20300 return AsUnderlyingType(inventoryFlags);
20301 }());
20302 for (uint32 bagSlotFlag : m_activePlayerData->BagSlotFlags)
20303 stmt->setUInt32(index++, bagSlotFlag);
20304 stmt->setUInt8(index++, GetBankBagSlotCount());
20305 stmt->setUInt32(index++, [&]
20306 {
20307 BagSlotFlags inventoryFlags = BagSlotFlags::None;
20308 if (m_activePlayerData->BankAutoSortDisabled)
20309 inventoryFlags |= BagSlotFlags::DisableAutoSort;
20310 return AsUnderlyingType(inventoryFlags);
20311 }());
20312 for (uint32 bankBagSlotFlag : m_activePlayerData->BankBagSlotFlags)
20313 stmt->setUInt32(index++, bankBagSlotFlag);
20314 stmt->setUInt8(index++, m_activePlayerData->RestInfo[REST_TYPE_XP].StateID);
20315 stmt->setUInt32(index++, m_playerData->PlayerFlags);
20316 stmt->setUInt32(index++, m_playerData->PlayerFlagsEx);
20317 stmt->setUInt16(index++, (uint16)GetMapId());
20318 stmt->setUInt32(index++, (uint32)GetInstanceId());
20319 stmt->setUInt8(index++, uint8(GetDungeonDifficultyID()));
20320 stmt->setUInt8(index++, uint8(GetRaidDifficultyID()));
20321 stmt->setUInt8(index++, uint8(GetLegacyRaidDifficultyID()));
20322 stmt->setFloat(index++, finiteAlways(GetPositionX()));
20323 stmt->setFloat(index++, finiteAlways(GetPositionY()));
20324 stmt->setFloat(index++, finiteAlways(GetPositionZ()));
20325 stmt->setFloat(index++, finiteAlways(GetOrientation()));
20326 stmt->setFloat(index++, finiteAlways(GetTransOffsetX()));
20327 stmt->setFloat(index++, finiteAlways(GetTransOffsetY()));
20328 stmt->setFloat(index++, finiteAlways(GetTransOffsetZ()));
20329 stmt->setFloat(index++, finiteAlways(GetTransOffsetO()));
20330 ObjectGuid::LowType transLowGUID = UI64LIT(0);
20331 if (Transport* transport = dynamic_cast<Transport*>(GetTransport()))
20332 transLowGUID = transport->GetGUID().GetCounter();
20333 stmt->setUInt64(index++, transLowGUID);
20334
20335 std::ostringstream ss;
20336 ss << m_taxi;
20337 stmt->setString(index++, ss.str());
20338 stmt->setInt64(index++, m_createTime);
20339 stmt->setInt8(index++, AsUnderlyingType(m_createMode));
20340 stmt->setUInt8(index++, m_cinematic);
20341 stmt->setUInt32(index++, m_Played_time[PLAYED_TIME_TOTAL]);
20342 stmt->setUInt32(index++, m_Played_time[PLAYED_TIME_LEVEL]);
20343 stmt->setFloat(index++, finiteAlways(_restMgr->GetRestBonus(REST_TYPE_XP)));
20344 stmt->setUInt64(index++, GameTime::GetGameTime());
20345 stmt->setUInt8(index++, (HasPlayerFlag(PLAYER_FLAGS_RESTING) ? 1 : 0));
20346 //save, far from tavern/city
20347 //save, but in tavern/city
20348 stmt->setUInt32(index++, GetTalentResetCost());
20349 stmt->setInt64(index++, GetTalentResetTime());
20351 stmt->setUInt16(index++, (uint16)m_ExtraFlags);
20352 stmt->setUInt32(index++, 0); // summonedPetNumber
20353 stmt->setUInt16(index++, (uint16)m_atLoginFlags);
20354 stmt->setInt64(index++, m_deathExpireTime);
20355
20356 ss.str("");
20358
20359 stmt->setString(index++, ss.str());
20360 stmt->setUInt32(index++, m_activePlayerData->LifetimeHonorableKills);
20361 stmt->setUInt16(index++, m_activePlayerData->TodayHonorableKills);
20362 stmt->setUInt16(index++, m_activePlayerData->YesterdayHonorableKills);
20363 stmt->setUInt32(index++, m_playerData->PlayerTitle);
20364 stmt->setUInt32(index++, m_activePlayerData->WatchedFactionIndex);
20365 stmt->setUInt8(index++, GetDrunkValue());
20366 stmt->setUInt32(index++, GetHealth());
20367
20368 for (uint32 i = 0; i < MAX_POWERS_PER_CLASS; ++i)
20369 stmt->setUInt32(index++, m_unitData->Power[i]);
20370
20371 stmt->setUInt32(index++, GetSession()->GetLatency());
20372
20373 stmt->setUInt8(index++, GetActiveTalentGroup());
20374
20375 stmt->setUInt32(index++, GetLootSpecId());
20376
20377 ss.str("");
20378 for (size_t i = 0; i < m_activePlayerData->BitVectors->Values[PLAYER_DATA_FLAG_EXPLORED_ZONES_INDEX].Values.size(); ++i)
20379 {
20380 ss << uint32(m_activePlayerData->BitVectors->Values[PLAYER_DATA_FLAG_EXPLORED_ZONES_INDEX].Values[i] & 0xFFFFFFFF) << ' ';
20381 ss << uint32((m_activePlayerData->BitVectors->Values[PLAYER_DATA_FLAG_EXPLORED_ZONES_INDEX].Values[i] >> 32) & 0xFFFFFFFF) << ' ';
20382 }
20383 stmt->setString(index++, ss.str());
20384
20385 ss.str("");
20386 // cache equipment...
20387 for (uint32 i = 0; i < REAGENT_BAG_SLOT_END; ++i)
20388 {
20389 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
20390 {
20391 ss << uint32(item->GetTemplate()->GetInventoryType()) << ' ' << item->GetDisplayId(this) << ' ';
20392 if (SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(item->GetVisibleEnchantmentId(this)))
20393 ss << enchant->ItemVisual;
20394 else
20395 ss << '0';
20396
20397 ss << ' '
20398 << uint32(sItemStore.AssertEntry(item->GetVisibleEntry(this))->SubclassID) << ' '
20399 << uint32(item->GetVisibleSecondaryModifiedAppearanceId(this)) << ' ';
20400 }
20401 else
20402 ss << "0 0 0 0 0 ";
20403 }
20404
20405 stmt->setString(index++, ss.str());
20406
20407 ss.str("");
20408 for (uint32 i = 0; i < m_activePlayerData->KnownTitles.size(); ++i)
20409 {
20410 ss << uint32(m_activePlayerData->KnownTitles[i] & 0xFFFFFFFF) << ' ';
20411 ss << uint32((m_activePlayerData->KnownTitles[i] >> 32) & 0xFFFFFFFF) << ' ';
20412 }
20413 stmt->setString(index++, ss.str());
20414
20415 stmt->setUInt8(index++, m_activePlayerData->MultiActionBars);
20416 if (std::shared_ptr<Realm const> currentRealm = sRealmList->GetCurrentRealm())
20417 stmt->setUInt32(index++, ClientBuild::GetMinorMajorBugfixVersionForBuild(currentRealm->Build));
20418 else
20419 stmt->setUInt32(index++, 0);
20420 }
20421 else
20422 {
20423 // Update query
20424 stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHARACTER);
20425 stmt->setString(index++, GetName());
20426 stmt->setUInt8(index++, GetRace());
20427 stmt->setUInt8(index++, GetClass());
20428 stmt->setUInt8(index++, GetNativeGender()); // save gender from PLAYER_BYTES_3, UNIT_BYTES_0 changes with every transform effect
20429 stmt->setUInt8(index++, GetLevel());
20430 stmt->setUInt32(index++, GetXP());
20431 stmt->setUInt64(index++, GetMoney());
20432 stmt->setUInt8(index++, GetInventorySlotCount());
20433 stmt->setUInt32(index++, [&]
20434 {
20435 BagSlotFlags inventoryFlags = BagSlotFlags::None;
20436 if (m_activePlayerData->BackpackAutoSortDisabled)
20437 inventoryFlags |= BagSlotFlags::DisableAutoSort;
20438 if (m_activePlayerData->BackpackSellJunkDisabled)
20439 inventoryFlags |= BagSlotFlags::ExcludeJunkSell;
20440 return AsUnderlyingType(inventoryFlags);
20441 }());
20442 for (uint32 bagSlotFlag : m_activePlayerData->BagSlotFlags)
20443 stmt->setUInt32(index++, bagSlotFlag);
20444 stmt->setUInt8(index++, GetBankBagSlotCount());
20445 stmt->setUInt32(index++, [&]
20446 {
20447 BagSlotFlags inventoryFlags = BagSlotFlags::None;
20448 if (m_activePlayerData->BankAutoSortDisabled)
20449 inventoryFlags |= BagSlotFlags::DisableAutoSort;
20450 return AsUnderlyingType(inventoryFlags);
20451 }());
20452 for (uint32 bankBagSlotFlag : m_activePlayerData->BankBagSlotFlags)
20453 stmt->setUInt32(index++, bankBagSlotFlag);
20454 stmt->setUInt8(index++, m_activePlayerData->RestInfo[REST_TYPE_XP].StateID);
20455 stmt->setUInt32(index++, m_playerData->PlayerFlags);
20456 stmt->setUInt32(index++, m_playerData->PlayerFlagsEx);
20457
20458 if (!IsBeingTeleported())
20459 {
20460 stmt->setUInt16(index++, (uint16)GetMapId());
20461 stmt->setUInt32(index++, (uint32)GetInstanceId());
20462 stmt->setUInt8(index++, uint8(GetDungeonDifficultyID()));
20463 stmt->setUInt8(index++, uint8(GetRaidDifficultyID()));
20464 stmt->setUInt8(index++, uint8(GetLegacyRaidDifficultyID()));
20465 stmt->setFloat(index++, finiteAlways(GetPositionX()));
20466 stmt->setFloat(index++, finiteAlways(GetPositionY()));
20467 stmt->setFloat(index++, finiteAlways(GetPositionZ()));
20468 stmt->setFloat(index++, finiteAlways(GetOrientation()));
20469 }
20470 else
20471 {
20472 stmt->setUInt16(index++, (uint16)GetTeleportDest().Location.GetMapId());
20473 stmt->setUInt32(index++, (uint32)0);
20474 stmt->setUInt8(index++, uint8(GetDungeonDifficultyID()));
20475 stmt->setUInt8(index++, uint8(GetRaidDifficultyID()));
20476 stmt->setUInt8(index++, uint8(GetLegacyRaidDifficultyID()));
20477 stmt->setFloat(index++, finiteAlways(GetTeleportDest().Location.GetPositionX()));
20478 stmt->setFloat(index++, finiteAlways(GetTeleportDest().Location.GetPositionY()));
20479 stmt->setFloat(index++, finiteAlways(GetTeleportDest().Location.GetPositionZ()));
20480 stmt->setFloat(index++, finiteAlways(GetTeleportDest().Location.GetOrientation()));
20481 }
20482
20483 stmt->setFloat(index++, finiteAlways(GetTransOffsetX()));
20484 stmt->setFloat(index++, finiteAlways(GetTransOffsetY()));
20485 stmt->setFloat(index++, finiteAlways(GetTransOffsetZ()));
20486 stmt->setFloat(index++, finiteAlways(GetTransOffsetO()));
20487 ObjectGuid::LowType transLowGUID = UI64LIT(0);
20488 if (Transport* transport = dynamic_cast<Transport*>(GetTransport()))
20489 transLowGUID = transport->GetGUID().GetCounter();
20490 stmt->setUInt64(index++, transLowGUID);
20491
20492 std::ostringstream ss;
20493 ss << m_taxi;
20494 stmt->setString(index++, ss.str());
20495 stmt->setUInt8(index++, m_cinematic);
20496 stmt->setUInt32(index++, m_Played_time[PLAYED_TIME_TOTAL]);
20497 stmt->setUInt32(index++, m_Played_time[PLAYED_TIME_LEVEL]);
20498 stmt->setFloat(index++, finiteAlways(_restMgr->GetRestBonus(REST_TYPE_XP)));
20499 stmt->setUInt64(index++, GameTime::GetGameTime());
20500 stmt->setUInt8(index++, (HasPlayerFlag(PLAYER_FLAGS_RESTING) ? 1 : 0));
20501 //save, far from tavern/city
20502 //save, but in tavern/city
20503 stmt->setUInt32(index++, GetTalentResetCost());
20504 stmt->setInt64(index++, GetTalentResetTime());
20505 stmt->setUInt8(index++, GetNumRespecs());
20507 stmt->setUInt16(index++, (uint16)m_ExtraFlags);
20508 if (PetStable const* petStable = GetPetStable())
20509 stmt->setUInt32(index++, petStable->GetCurrentPet() && petStable->GetCurrentPet()->Health > 0 ? petStable->GetCurrentPet()->PetNumber : 0); // summonedPetNumber
20510 else
20511 stmt->setUInt32(index++, 0); // summonedPetNumber
20512 stmt->setUInt16(index++, (uint16)m_atLoginFlags);
20513 stmt->setUInt16(index++, GetZoneId());
20514 stmt->setInt64(index++, m_deathExpireTime);
20515
20516 ss.str("");
20518
20519 stmt->setString(index++, ss.str());
20520 stmt->setUInt32(index++, m_activePlayerData->LifetimeHonorableKills);
20521 stmt->setUInt16(index++, m_activePlayerData->TodayHonorableKills);
20522 stmt->setUInt16(index++, m_activePlayerData->YesterdayHonorableKills);
20523 stmt->setUInt32(index++, m_playerData->PlayerTitle);
20524 stmt->setUInt32(index++, m_activePlayerData->WatchedFactionIndex);
20525 stmt->setUInt8(index++, GetDrunkValue());
20526 stmt->setUInt32(index++, GetHealth());
20527
20528 for (uint32 i = 0; i < MAX_POWERS_PER_CLASS; ++i)
20529 stmt->setUInt32(index++, m_unitData->Power[i]);
20530
20531 stmt->setUInt32(index++, GetSession()->GetLatency());
20532
20533 stmt->setUInt8(index++, GetActiveTalentGroup());
20534
20535 stmt->setUInt32(index++, GetLootSpecId());
20536
20537 ss.str("");
20538 for (size_t i = 0; i < m_activePlayerData->BitVectors->Values[PLAYER_DATA_FLAG_EXPLORED_ZONES_INDEX].Values.size(); ++i)
20539 {
20540 ss << uint32(m_activePlayerData->BitVectors->Values[PLAYER_DATA_FLAG_EXPLORED_ZONES_INDEX].Values[i] & 0xFFFFFFFF) << ' ';
20541 ss << uint32((m_activePlayerData->BitVectors->Values[PLAYER_DATA_FLAG_EXPLORED_ZONES_INDEX].Values[i] >> 32) & 0xFFFFFFFF) << ' ';
20542 }
20543 stmt->setString(index++, ss.str());
20544
20545 ss.str("");
20546 // cache equipment...
20547 for (uint32 i = 0; i < REAGENT_BAG_SLOT_END; ++i)
20548 {
20549 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
20550 {
20551 ss << uint32(item->GetTemplate()->GetInventoryType()) << ' ' << item->GetDisplayId(this) << ' ';
20552 if (SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(item->GetVisibleEnchantmentId(this)))
20553 ss << enchant->ItemVisual;
20554 else
20555 ss << '0';
20556
20557 ss << ' '
20558 << uint32(sItemStore.AssertEntry(item->GetVisibleEntry(this))->SubclassID) << ' '
20559 << uint32(item->GetVisibleSecondaryModifiedAppearanceId(this)) << ' ';
20560 }
20561 else
20562 ss << "0 0 0 0 0 ";
20563 }
20564
20565 stmt->setString(index++, ss.str());
20566
20567 ss.str("");
20568 for (uint32 i = 0; i < m_activePlayerData->KnownTitles.size(); ++i)
20569 {
20570 ss << uint32(m_activePlayerData->KnownTitles[i] & 0xFFFFFFFF) << ' ';
20571 ss << uint32((m_activePlayerData->KnownTitles[i] >> 32) & 0xFFFFFFFF) << ' ';
20572 }
20573
20574 stmt->setString(index++, ss.str());
20575 stmt->setUInt8(index++, m_activePlayerData->MultiActionBars);
20576
20577 stmt->setUInt8(index++, IsInWorld() && !GetSession()->PlayerLogout() ? 1 : 0);
20578 stmt->setUInt32(index++, m_activePlayerData->Honor);
20579 stmt->setUInt32(index++, GetHonorLevel());
20580 stmt->setUInt8(index++, m_activePlayerData->RestInfo[REST_TYPE_HONOR].StateID);
20581 stmt->setFloat(index++, finiteAlways(_restMgr->GetRestBonus(REST_TYPE_HONOR)));
20582 if (std::shared_ptr<Realm const> currentRealm = sRealmList->GetCurrentRealm())
20583 stmt->setUInt32(index++, ClientBuild::GetMinorMajorBugfixVersionForBuild(currentRealm->Build));
20584 else
20585 stmt->setUInt32(index++, 0);
20586
20587 // Index
20588 stmt->setUInt64(index, GetGUID().GetCounter());
20589 }
20590
20591 trans->Append(stmt);
20592
20593 if (m_fishingSteps != 0)
20594 {
20595 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_FISHINGSTEPS);
20596 index = 0;
20597 stmt->setUInt64(index++, GetGUID().GetCounter());
20598 stmt->setUInt32(index++, m_fishingSteps);
20599 trans->Append(stmt);
20600 }
20601
20602 if (m_mailsUpdated) //save mails only when needed
20603 _SaveMail(trans);
20604
20605 _SaveCustomizations(trans);
20606 _SaveBGData(trans);
20607 _SaveInventory(trans);
20608 _SaveVoidStorage(trans);
20609 _SaveQuestStatus(trans);
20610 _SaveDailyQuestStatus(trans);
20614 _SaveGlyphs(trans);
20615 _SaveTalents(trans);
20616 _SaveTraits(trans);
20617 _SaveSpells(trans);
20618 GetSpellHistory()->SaveToDB<Player>(trans);
20619 _SaveActions(trans);
20620 _SaveAuras(trans);
20621 _SaveSkills(trans);
20623 m_achievementMgr->SaveToDB(trans);
20624 m_reputationMgr->SaveToDB(trans);
20625 m_questObjectiveCriteriaMgr->SaveToDB(trans);
20626 _SaveEquipmentSets(trans);
20627 GetSession()->SaveTutorialsData(trans); // changed only while character in game
20629 _SaveCurrency(trans);
20630 _SaveCUFProfiles(trans);
20631 if (_garrison)
20632 _garrison->SaveToDB(trans);
20633
20634 // check if stats should only be saved on logout
20635 // save stats can be out of transaction
20637 _SaveStats(trans);
20638
20639 // TODO: Move this out
20640 GetSession()->GetCollectionMgr()->SaveAccountToys(loginTransaction);
20641 GetSession()->GetBattlePetMgr()->SaveToDB(loginTransaction);
20642 GetSession()->GetCollectionMgr()->SaveAccountHeirlooms(loginTransaction);
20643 GetSession()->GetCollectionMgr()->SaveAccountMounts(loginTransaction);
20647
20648 Battlenet::RealmHandle currentRealmId = sRealmList->GetCurrentRealmId();
20649
20651 loginStmt->setUInt32(0, GetSession()->GetAccountId());
20652 loginStmt->setUInt8(1, currentRealmId.Region);
20653 loginStmt->setUInt8(2, currentRealmId.Site);
20654 loginTransaction->Append(loginStmt);
20655
20656 loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BNET_LAST_PLAYER_CHARACTERS);
20657 loginStmt->setUInt32(0, GetSession()->GetAccountId());
20658 loginStmt->setUInt8(1, currentRealmId.Region);
20659 loginStmt->setUInt8(2, currentRealmId.Site);
20660 loginStmt->setUInt32(3, currentRealmId.Realm);
20661 loginStmt->setString(4, GetName());
20662 loginStmt->setUInt64(5, GetGUID().GetCounter());
20663 loginStmt->setUInt32(6, GameTime::GetGameTime());
20664 loginTransaction->Append(loginStmt);
20665
20666 // save pet (hunter pet level and experience and all type pets health/mana).
20667 if (Pet* pet = GetPet())
20668 pet->SavePetToDB(PET_SAVE_AS_CURRENT);
20669}
20670
20671// fast save function for item/money cheating preventing - save only inventory and money state
20673{
20674 _SaveInventory(trans);
20675 _SaveCurrency(trans);
20676
20678 stmt->setUInt64(0, GetMoney());
20679 stmt->setUInt64(1, GetGUID().GetCounter());
20680 trans->Append(stmt);
20681}
20682
20683template<typename iterator>
20685{
20687 stmt->setUInt64(0, guid);
20688 trans->Append(stmt);
20689
20690 for (auto&& customization : customizations)
20691 {
20692 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_CUSTOMIZATION);
20693 stmt->setUInt64(0, guid);
20694 stmt->setUInt32(1, customization.ChrCustomizationOptionID);
20695 stmt->setUInt32(2, customization.ChrCustomizationChoiceID);
20696 trans->Append(stmt);
20697 }
20698}
20699
20702{
20703 SavePlayerCustomizations(trans, guid, customizations);
20704}
20705
20707{
20709 return;
20710
20712
20713 SavePlayerCustomizations(trans, GetGUID().GetCounter(), Trinity::Containers::MakeIteratorPair(m_playerData->Customizations.begin(), m_playerData->Customizations.end()));
20714}
20715
20717{
20718 int32 traitConfigId = [&]() -> int32
20719 {
20720 UF::TraitConfig const* traitConfig = GetTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID);
20721 if (!traitConfig)
20722 return 0;
20723
20724 int32 usedSavedTraitConfigIndex = m_activePlayerData->TraitConfigs.FindIndexIf([localIdent = *traitConfig->LocalIdentifier](UF::TraitConfig const& savedConfig)
20725 {
20726 return static_cast<TraitConfigType>(*savedConfig.Type) == TraitConfigType::Combat
20727 && (static_cast<TraitCombatConfigFlags>(*savedConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) == TraitCombatConfigFlags::None
20728 && (static_cast<TraitCombatConfigFlags>(*savedConfig.CombatConfigFlags) & TraitCombatConfigFlags::SharedActionBars) == TraitCombatConfigFlags::None
20729 && savedConfig.LocalIdentifier == localIdent;
20730 });
20731
20732 if (usedSavedTraitConfigIndex >= 0)
20733 return m_activePlayerData->TraitConfigs[usedSavedTraitConfigIndex].ID;
20734
20735 return 0;
20736 }();
20737
20739
20740 for (ActionButtonList::iterator itr = m_actionButtons.begin(); itr != m_actionButtons.end();)
20741 {
20742 switch (itr->second.uState)
20743 {
20744 case ACTIONBUTTON_NEW:
20745 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_ACTION);
20746 stmt->setUInt64(0, GetGUID().GetCounter());
20747 stmt->setUInt8(1, GetActiveTalentGroup());
20748 stmt->setInt32(2, traitConfigId);
20749 stmt->setUInt8(3, itr->first);
20750 stmt->setUInt64(4, itr->second.GetAction());
20751 stmt->setUInt8(5, uint8(itr->second.GetType()));
20752 trans->Append(stmt);
20753
20754 itr->second.uState = ACTIONBUTTON_UNCHANGED;
20755 ++itr;
20756 break;
20758 stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_ACTION);
20759 stmt->setUInt64(0, itr->second.GetAction());
20760 stmt->setUInt8(1, uint8(itr->second.GetType()));
20761 stmt->setUInt64(2, GetGUID().GetCounter());
20762 stmt->setUInt8(3, itr->first);
20763 stmt->setUInt8(4, GetActiveTalentGroup());
20764 stmt->setInt32(5, traitConfigId);
20765 trans->Append(stmt);
20766
20767 itr->second.uState = ACTIONBUTTON_UNCHANGED;
20768 ++itr;
20769 break;
20771 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION_BY_BUTTON_SPEC);
20772 stmt->setUInt64(0, GetGUID().GetCounter());
20773 stmt->setUInt8(1, itr->first);
20774 stmt->setUInt8(2, GetActiveTalentGroup());
20775 stmt->setInt32(3, traitConfigId);
20776 trans->Append(stmt);
20777
20778 m_actionButtons.erase(itr++);
20779 break;
20780 default:
20781 ++itr;
20782 break;
20783 }
20784 }
20785}
20786
20788{
20790 stmt->setUInt64(0, GetGUID().GetCounter());
20791 trans->Append(stmt);
20792
20793 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_AURA);
20794 stmt->setUInt64(0, GetGUID().GetCounter());
20795 trans->Append(stmt);
20796
20797 uint8 index;
20798 for (AuraMap::const_iterator itr = m_ownedAuras.begin(); itr != m_ownedAuras.end(); ++itr)
20799 {
20800 if (!itr->second->CanBeSaved())
20801 continue;
20802
20803 Aura* aura = itr->second;
20804 uint32 recalculateMask = 0;
20805 AuraKey key = aura->GenerateKey(recalculateMask);
20806
20807 index = 0;
20808 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AURA);
20809 stmt->setUInt64(index++, GetGUID().GetCounter());
20810 stmt->setBinary(index++, key.Caster.GetRawValue());
20811 stmt->setBinary(index++, key.Item.GetRawValue());
20812 stmt->setUInt32(index++, key.SpellId);
20813 stmt->setUInt32(index++, key.EffectMask);
20814 stmt->setUInt32(index++, recalculateMask);
20815 stmt->setUInt8(index++, aura->GetCastDifficulty());
20816 stmt->setUInt8(index++, aura->GetStackAmount());
20817 stmt->setInt32(index++, aura->GetMaxDuration());
20818 stmt->setInt32(index++, aura->GetDuration());
20819 stmt->setUInt8(index++, aura->GetCharges());
20820 stmt->setUInt32(index++, aura->GetCastItemId());
20821 stmt->setInt32(index++, aura->GetCastItemLevel());
20822 trans->Append(stmt);
20823
20824 for (AuraEffect const* effect : aura->GetAuraEffects())
20825 {
20826 index = 0;
20827 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AURA_EFFECT);
20828 stmt->setUInt64(index++, GetGUID().GetCounter());
20829 stmt->setBinary(index++, key.Caster.GetRawValue());
20830 stmt->setBinary(index++, key.Item.GetRawValue());
20831 stmt->setUInt32(index++, key.SpellId);
20832 stmt->setUInt32(index++, key.EffectMask);
20833 stmt->setUInt8(index++, effect->GetEffIndex());
20834 stmt->setInt32(index++, effect->GetAmount());
20835 stmt->setInt32(index++, effect->GetBaseAmount());
20836 trans->Append(stmt);
20837 }
20838 }
20839}
20840
20842{
20844 // force items in buyback slots to new state
20845 // and remove those that aren't already
20846 for (uint8 i = BUYBACK_SLOT_START; i < BUYBACK_SLOT_END; ++i)
20847 {
20848 Item* item = m_items[i];
20849 if (!item)
20850 continue;
20851
20852 if (item->GetState() == ITEM_NEW)
20853 {
20854 if (ItemTemplate const* itemTemplate = item->GetTemplate())
20855 if (itemTemplate->HasFlag(ITEM_FLAG_HAS_LOOT))
20856 sLootItemStorage->RemoveStoredLootForContainer(item->GetGUID().GetCounter());
20857
20858 continue;
20859 }
20860
20861 item->DeleteFromInventoryDB(trans);
20862 item->DeleteFromDB(trans);
20864
20865 if (ItemTemplate const* itemTemplate = item->GetTemplate())
20866 if (itemTemplate->HasFlag(ITEM_FLAG_HAS_LOOT))
20867 sLootItemStorage->RemoveStoredLootForContainer(item->GetGUID().GetCounter());
20868 }
20869
20870 // Updated played time for refundable items. We don't do this in Player::Update because there's simply no need for it,
20871 // the client auto counts down in real time after having received the initial played time on the first
20872 // SMSG_ITEM_REFUND_INFO_RESPONSE packet.
20873 // Item::UpdatePlayedTime is only called when needed, which is in DB saves, and item refund info requests.
20874 GuidSet::iterator i_next;
20875 for (GuidSet::iterator itr = m_refundableItems.begin(); itr!= m_refundableItems.end(); itr = i_next)
20876 {
20877 // use copy iterator because itr may be invalid after operations in this loop
20878 i_next = itr;
20879 ++i_next;
20880
20881 if (Item* iPtr = GetItemByGuid(*itr))
20882 {
20883 if (iPtr->IsRefundable() && iPtr->IsRefundExpired())
20884 iPtr->SetNotRefundable(this);
20885 continue;
20886 }
20887 else
20888 {
20889 TC_LOG_ERROR("entities.player", "Player::_SaveInventory: Can't find item ({}) in refundable storage for player '{}' ({}), removing.",
20890 itr->ToString(), GetName(), GetGUID().ToString());
20891 m_refundableItems.erase(itr);
20892 }
20893 }
20894
20895 // update enchantment durations
20896 for (EnchantDurationList::iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr)
20897 itr->item->SetEnchantmentDuration(itr->slot, itr->leftduration, this);
20898
20899 // if no changes
20900 if (m_itemUpdateQueue.empty())
20901 return;
20902
20903 for (size_t i = 0; i < m_itemUpdateQueue.size(); ++i)
20904 {
20905 Item* item = m_itemUpdateQueue[i];
20906 if (!item)
20907 continue;
20908
20909 Bag* container = item->GetContainer();
20910 if (item->GetState() != ITEM_REMOVED)
20911 {
20912 Item* test = GetItemByPos(item->GetBagSlot(), item->GetSlot());
20913 if (test == nullptr)
20914 {
20915 ObjectGuid::LowType bagTestGUID = UI64LIT(0);
20916 if (Item* test2 = GetItemByPos(INVENTORY_SLOT_BAG_0, item->GetBagSlot()))
20917 bagTestGUID = test2->GetGUID().GetCounter();
20918
20919 TC_LOG_ERROR("entities.player", "Player::_SaveInventory: Player '{}' ({}) has incorrect values (Bag: {}, Slot: {}) for the item ({}, State: {}). The player doesn't have an item at that position.",
20920 GetName(), GetGUID().ToString(), item->GetBagSlot(), item->GetSlot(), item->GetGUID().ToString(), (int32)item->GetState());
20921 // according to the test that was just performed nothing should be in this slot, delete
20922 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_BAG_SLOT);
20923 stmt->setUInt64(0, bagTestGUID);
20924 stmt->setUInt8(1, item->GetSlot());
20925 stmt->setUInt64(2, GetGUID().GetCounter());
20926 trans->Append(stmt);
20927
20928 RemoveTradeableItem(item);
20930 RemoveItemDurations(item);
20931
20932 // also THIS item should be somewhere else, cheat attempt
20933 item->FSetState(ITEM_REMOVED); // we are IN updateQueue right now, can't use SetState which modifies the queue
20935 // don't skip, let the switch delete it
20936 //continue;
20937 }
20938 else if (test != item)
20939 {
20940 TC_LOG_ERROR("entities.player", "Player::_SaveInventory: Player '{}' ({}) has incorrect values (Bag: {}, Slot: {}) for the item ({}). {} is there instead!",
20941 GetName(), GetGUID().ToString(), item->GetBagSlot(), item->GetSlot(), item->GetGUID().ToString(), test->GetGUID().ToString());
20942 // save all changes to the item...
20943 if (item->GetState() != ITEM_NEW) // only for existing items, no duplicates
20944 item->SaveToDB(trans);
20945 // ...but do not save position in inventory
20946 continue;
20947 }
20948 }
20949
20950 switch (item->GetState())
20951 {
20952 case ITEM_NEW:
20953 case ITEM_CHANGED:
20954 stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_INVENTORY_ITEM);
20955 stmt->setUInt64(0, GetGUID().GetCounter());
20956 stmt->setUInt64(1, container ? container->GetGUID().GetCounter() : UI64LIT(0));
20957 stmt->setUInt8 (2, item->GetSlot());
20958 stmt->setUInt64(3, item->GetGUID().GetCounter());
20959 trans->Append(stmt);
20960 break;
20961 case ITEM_REMOVED:
20962 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM);
20963 stmt->setUInt64(0, item->GetGUID().GetCounter());
20964 trans->Append(stmt);
20965 break;
20966 case ITEM_UNCHANGED:
20967 break;
20968 }
20969
20970 item->SaveToDB(trans); // item have unchanged inventory record and can be save standalone
20971 }
20972 m_itemUpdateQueue.clear();
20973}
20974
20976{
20977 CharacterDatabasePreparedStatement* stmt = nullptr;
20978
20979 for (uint8 i = 0; i < VOID_STORAGE_MAX_SLOT; ++i)
20980 {
20981 if (!_voidStorageItems[i]) // unused item
20982 {
20983 // DELETE FROM void_storage WHERE slot = ? AND playerGuid = ?
20985 stmt->setUInt8(0, i);
20986 stmt->setUInt64(1, GetGUID().GetCounter());
20987 }
20988 else
20989 {
20990 // REPLACE INTO character_void_storage (itemId, playerGuid, itemEntry, slot, creatorGuid, randomBonusListId, upgradeId, fixedScalingLevel, artifactKnowledgeLevel, bonusListIDs) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
20991 stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_VOID_STORAGE_ITEM);
20992 stmt->setUInt64(0, _voidStorageItems[i]->ItemId);
20993 stmt->setUInt64(1, GetGUID().GetCounter());
20995 stmt->setUInt8(3, i);
20996 stmt->setUInt64(4, _voidStorageItems[i]->CreatorGuid.GetCounter());
20997 stmt->setUInt32(5, _voidStorageItems[i]->RandomBonusListId);
20998 stmt->setUInt32(6, _voidStorageItems[i]->FixedScalingLevel);
20999 stmt->setUInt32(7, _voidStorageItems[i]->ArtifactKnowledgeLevel);
21000 stmt->setUInt8(8, AsUnderlyingType(_voidStorageItems[i]->Context));
21001 std::ostringstream bonusListIDs;
21002 for (int32 bonusListID : _voidStorageItems[i]->BonusListIDs)
21003 bonusListIDs << bonusListID << ' ';
21004 stmt->setString(9, bonusListIDs.str());
21005 }
21006
21007 trans->Append(stmt);
21008 }
21009}
21010
21012{
21014 for (uint8 i = 0; i < MAX_CUF_PROFILES; ++i)
21015 {
21016 if (!_CUFProfiles[i]) // unused profile
21017 {
21018 // DELETE FROM character_cuf_profiles WHERE guid = ? and id = ?
21019 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_CUF_PROFILES_BY_ID);
21020 stmt->setUInt64(0, GetGUID().GetCounter());
21021 stmt->setUInt8(1, i);
21022 }
21023 else
21024 {
21025 // REPLACE INTO character_cuf_profiles (guid, id, name, frameHeight, frameWidth, sortBy, healthText, boolOptions, unk146, unk147, unk148, unk150, unk152, unk154) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
21026 stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_CUF_PROFILES);
21027 stmt->setUInt64(0, GetGUID().GetCounter());
21028 stmt->setUInt8(1, i);
21029 stmt->setString(2, _CUFProfiles[i]->ProfileName);
21030 stmt->setUInt16(3, _CUFProfiles[i]->FrameHeight);
21031 stmt->setUInt16(4, _CUFProfiles[i]->FrameWidth);
21032 stmt->setUInt8(5, _CUFProfiles[i]->SortBy);
21033 stmt->setUInt8(6, _CUFProfiles[i]->HealthText);
21034 stmt->setUInt32(7, _CUFProfiles[i]->BoolOptions.to_ulong()); // 25 of 32 fields used, fits in an int
21035 stmt->setUInt8(8, _CUFProfiles[i]->TopPoint);
21036 stmt->setUInt8(9, _CUFProfiles[i]->BottomPoint);
21037 stmt->setUInt8(10, _CUFProfiles[i]->LeftPoint);
21038 stmt->setUInt16(11, _CUFProfiles[i]->TopOffset);
21039 stmt->setUInt16(12, _CUFProfiles[i]->BottomOffset);
21040 stmt->setUInt16(13, _CUFProfiles[i]->LeftOffset);
21041 }
21042
21043 trans->Append(stmt);
21044 }
21045}
21046
21048{
21050
21051 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
21052 {
21053 Mail* m = (*itr);
21054 if (m->state == MAIL_STATE_CHANGED)
21055 {
21056 stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_MAIL);
21057 stmt->setUInt8(0, uint8(m->HasItems() ? 1 : 0));
21058 stmt->setInt64(1, m->expire_time);
21059 stmt->setInt64(2, m->deliver_time);
21060 stmt->setUInt64(3, m->money);
21061 stmt->setUInt64(4, m->COD);
21062 stmt->setUInt8(5, uint8(m->checked));
21063 stmt->setUInt64(6, m->messageID);
21064
21065 trans->Append(stmt);
21066
21067 if (!m->removedItems.empty())
21068 {
21069 for (std::vector<ObjectGuid::LowType>::iterator itr2 = m->removedItems.begin(); itr2 != m->removedItems.end(); ++itr2)
21070 {
21071 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM);
21072 stmt->setUInt64(0, *itr2);
21073 trans->Append(stmt);
21074 }
21075 m->removedItems.clear();
21076 }
21078 }
21079 else if (m->state == MAIL_STATE_DELETED)
21080 {
21081 if (m->HasItems())
21082 {
21083 for (MailItemInfoVec::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2)
21084 {
21085 Item::DeleteFromDB(trans, itr2->item_guid);
21086 AzeriteItem::DeleteFromDB(trans, itr2->item_guid);
21087 AzeriteEmpoweredItem::DeleteFromDB(trans, itr2->item_guid);
21088 }
21089 }
21090 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
21091 stmt->setUInt64(0, m->messageID);
21092 trans->Append(stmt);
21093
21094 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
21095 stmt->setUInt64(0, m->messageID);
21096 trans->Append(stmt);
21097 }
21098 }
21099
21100 //deallocate deleted mails...
21101 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end();)
21102 {
21103 if ((*itr)->state == MAIL_STATE_DELETED)
21104 {
21105 Mail* m = *itr;
21106 m_mail.erase(itr);
21107 delete m;
21108 itr = m_mail.begin();
21109 }
21110 else
21111 ++itr;
21112 }
21113
21114 m_mailsUpdated = false;
21115}
21116
21118{
21119 bool isTransaction = bool(trans);
21120 if (!isTransaction)
21121 trans = CharacterDatabase.BeginTransaction();
21122
21124
21125 bool keepAbandoned = !(sWorld->GetCleaningFlags() & CharacterDatabaseCleaner::CLEANING_FLAG_QUESTSTATUS);
21126
21127 for (auto saveItr = m_QuestStatusSave.begin(); saveItr != m_QuestStatusSave.end(); ++saveItr)
21128 {
21129 if (saveItr->second == QUEST_DEFAULT_SAVE_TYPE)
21130 {
21131 auto statusItr = m_QuestStatus.find(saveItr->first);
21132 if (statusItr != m_QuestStatus.end() && (keepAbandoned || statusItr->second.Status != QUEST_STATUS_NONE))
21133 {
21134 QuestStatusData const& qData = statusItr->second;
21135
21136 // Save main quest status and timer
21137 stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_QUESTSTATUS);
21138 stmt->setUInt64(0, GetGUID().GetCounter());
21139 stmt->setUInt32(1, statusItr->first);
21140 stmt->setUInt8(2, uint8(qData.Status));
21141 stmt->setBool(3, qData.Explored);
21142 stmt->setInt64(4, qData.AcceptTime);
21143 stmt->setInt64(5, GetQuestSlotEndTime(qData.Slot));
21144 trans->Append(stmt);
21145
21146 // Save objectives
21148 stmt->setUInt64(0, GetGUID().GetCounter());
21149 stmt->setUInt32(1, saveItr->first);
21150 trans->Append(stmt);
21151
21152 Quest const* quest = ASSERT_NOTNULL(sObjectMgr->GetQuestTemplate(saveItr->first));
21153
21154 for (QuestObjective const& obj : quest->GetObjectives())
21155 {
21156 int32 count = GetQuestSlotObjectiveData(qData.Slot, obj);
21157 if (!count)
21158 continue;
21159
21160 stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_QUESTSTATUS_OBJECTIVES);
21161 stmt->setUInt64(0, GetGUID().GetCounter());
21162 stmt->setUInt32(1, statusItr->first);
21163 stmt->setUInt8(2, obj.StorageIndex);
21164 stmt->setInt32(3, count);
21165 trans->Append(stmt);
21166 }
21167
21168 // Save spawn trackings
21170 stmt->setUInt64(0, GetGUID().GetCounter());
21171 stmt->setUInt32(1, saveItr->first);
21172 trans->Append(stmt);
21173
21174 for (auto [questObjectiveStorageIndex, spawnTrackingId] : qData.SpawnTrackingList)
21175 {
21177 stmt->setUInt64(0, GetGUID().GetCounter());
21178 stmt->setUInt32(1, statusItr->first);
21179 stmt->setUInt8(2, questObjectiveStorageIndex);
21180 stmt->setUInt32(3, spawnTrackingId);
21181 trans->Append(stmt);
21182 }
21183 }
21184 }
21185 else
21186 {
21187 // Delete
21188 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_BY_QUEST);
21189 stmt->setUInt64(0, GetGUID().GetCounter());
21190 stmt->setUInt32(1, saveItr->first);
21191 trans->Append(stmt);
21192
21194 stmt->setUInt64(0, GetGUID().GetCounter());
21195 stmt->setUInt32(1, saveItr->first);
21196 trans->Append(stmt);
21197
21199 stmt->setUInt64(0, GetGUID().GetCounter());
21200 stmt->setUInt32(1, saveItr->first);
21201 trans->Append(stmt);
21202 }
21203 }
21204
21205 m_QuestStatusSave.clear();
21206
21207 for (auto saveItr = m_RewardedQuestsSave.begin(); saveItr != m_RewardedQuestsSave.end(); ++saveItr)
21208 {
21209 if (saveItr->second == QUEST_DEFAULT_SAVE_TYPE)
21210 {
21211 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_QUESTSTATUS_REWARDED);
21212 stmt->setUInt64(0, GetGUID().GetCounter());
21213 stmt->setUInt32(1, saveItr->first);
21214 trans->Append(stmt);
21215
21216 }
21217 else if (saveItr->second == QUEST_FORCE_DELETE_SAVE_TYPE || !keepAbandoned)
21218 {
21220 stmt->setUInt64(0, GetGUID().GetCounter());
21221 stmt->setUInt32(1, saveItr->first);
21222 trans->Append(stmt);
21223 }
21224 }
21225
21226 m_RewardedQuestsSave.clear();
21227
21228 if (!isTransaction)
21229 CharacterDatabase.CommitTransaction(trans);
21230}
21231
21233{
21235 return;
21236
21237 m_DailyQuestChanged = false;
21238
21239 // save last daily quest time for all quests: we need only mostly reset time for reset check anyway
21240
21241 // we don't need transactions here.
21243 stmt->setUInt64(0, GetGUID().GetCounter());
21244 trans->Append(stmt);
21245
21246 for (int32 questId : m_activePlayerData->DailyQuestsCompleted)
21247 {
21248 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_QUESTSTATUS_DAILY);
21249 stmt->setUInt64(0, GetGUID().GetCounter());
21250 stmt->setUInt32(1, questId);
21252 trans->Append(stmt);
21253 }
21254
21255 if (!m_DFQuests.empty())
21256 {
21257 for (DFQuestsDoneList::iterator itr = m_DFQuests.begin(); itr != m_DFQuests.end(); ++itr)
21258 {
21259 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_QUESTSTATUS_DAILY);
21260 stmt->setUInt64(0, GetGUID().GetCounter());
21261 stmt->setUInt32(1, (*itr));
21263 trans->Append(stmt);
21264 }
21265 }
21266}
21267
21269{
21270 if (!m_WeeklyQuestChanged || m_weeklyquests.empty())
21271 return;
21272
21273 // we don't need transactions here.
21275 stmt->setUInt64(0, GetGUID().GetCounter());
21276 trans->Append(stmt);
21277
21278 for (QuestSet::const_iterator iter = m_weeklyquests.begin(); iter != m_weeklyquests.end(); ++iter)
21279 {
21280 uint32 questId = *iter;
21281
21282 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_QUESTSTATUS_WEEKLY);
21283 stmt->setUInt64(0, GetGUID().GetCounter());
21284 stmt->setUInt32(1, questId);
21285 trans->Append(stmt);
21286 }
21287
21288 m_WeeklyQuestChanged = false;
21289}
21290
21292{
21294 return;
21295
21296 // we don't need transactions here.
21298 stmt->setUInt64(0, GetGUID().GetCounter());
21299 trans->Append(stmt);
21300
21301 m_SeasonalQuestChanged = false;
21302
21303 if (m_seasonalquests.empty())
21304 return;
21305
21306 for (SeasonalQuestMapByEvent::const_iterator iter = m_seasonalquests.begin(); iter != m_seasonalquests.end(); ++iter)
21307 {
21308 uint16 eventId = iter->first;
21309
21310 for (SeasonalQuestMapByQuest::const_iterator itr = iter->second.begin(); itr != iter->second.end(); ++itr)
21311 {
21312 uint32 questId = itr->first;
21313 time_t completedTime = itr->second;
21314
21316 stmt->setUInt64(0, GetGUID().GetCounter());
21317 stmt->setUInt32(1, questId);
21318 stmt->setUInt32(2, eventId);
21319 stmt->setInt64(3, completedTime);
21320 trans->Append(stmt);
21321 }
21322 }
21323}
21324
21326{
21327 if (!m_MonthlyQuestChanged || m_monthlyquests.empty())
21328 return;
21329
21330 // we don't need transactions here.
21332 stmt->setUInt64(0, GetGUID().GetCounter());
21333 trans->Append(stmt);
21334
21335 for (QuestSet::const_iterator iter = m_monthlyquests.begin(); iter != m_monthlyquests.end(); ++iter)
21336 {
21337 uint32 questId = *iter;
21338
21339 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_QUESTSTATUS_MONTHLY);
21340 stmt->setUInt64(0, GetGUID().GetCounter());
21341 stmt->setUInt32(1, questId);
21342 trans->Append(stmt);
21343 }
21344
21345 m_MonthlyQuestChanged = false;
21346}
21347
21349{
21351 // we don't need transactions here.
21352 for (SkillStatusMap::iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end();)
21353 {
21354 if (itr->second.uState == SKILL_UNCHANGED)
21355 {
21356 ++itr;
21357 continue;
21358 }
21359
21360 uint16 value = m_activePlayerData->Skill->SkillRank[itr->second.pos];
21361 uint16 max = m_activePlayerData->Skill->SkillMaxRank[itr->second.pos];
21362 int8 professionSlot = int8(GetProfessionSlotFor(itr->first));
21363
21364 switch (itr->second.uState)
21365 {
21366 case SKILL_NEW:
21367 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_SKILLS);
21368 stmt->setUInt64(0, GetGUID().GetCounter());
21369 stmt->setUInt16(1, uint16(itr->first));
21370 stmt->setUInt16(2, value);
21371 stmt->setUInt16(3, max);
21372 stmt->setInt8(4, professionSlot);
21373 trans->Append(stmt);
21374 break;
21375 case SKILL_CHANGED:
21376 stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_SKILLS);
21377 stmt->setUInt16(0, value);
21378 stmt->setUInt16(1, max);
21379 stmt->setInt8(2, professionSlot);
21380 stmt->setUInt64(3, GetGUID().GetCounter());
21381 stmt->setUInt16(4, uint16(itr->first));
21382 trans->Append(stmt);
21383 break;
21384 case SKILL_DELETED:
21385 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SKILL_BY_SKILL);
21386 stmt->setUInt64(0, GetGUID().GetCounter());
21387 stmt->setUInt16(1, uint16(itr->first));
21388 trans->Append(stmt);
21389 break;
21390 default:
21391 break;
21392 }
21393
21394 itr->second.uState = SKILL_UNCHANGED;
21395 ++itr;
21396 }
21397}
21398
21400{
21402
21403 for (PlayerSpellMap::iterator itr = m_spells.begin(); itr != m_spells.end();)
21404 {
21405 if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.state == PLAYERSPELL_CHANGED)
21406 {
21407 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_BY_SPELL);
21408 stmt->setUInt32(0, itr->first);
21409 stmt->setUInt64(1, GetGUID().GetCounter());
21410 trans->Append(stmt);
21411 }
21412
21413 if ((itr->second.state == PLAYERSPELL_NEW || itr->second.state == PLAYERSPELL_CHANGED))
21414 {
21415 // add only changed/new not dependent spells
21416 if (!itr->second.dependent)
21417 {
21418 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_SPELL);
21419 stmt->setUInt64(0, GetGUID().GetCounter());
21420 stmt->setUInt32(1, itr->first);
21421 stmt->setBool(2, itr->second.active);
21422 stmt->setBool(3, itr->second.disabled);
21423 trans->Append(stmt);
21424 }
21425
21426 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_FAVORITE);
21427 stmt->setUInt32(0, itr->first);
21428 stmt->setUInt64(1, GetGUID().GetCounter());
21429 trans->Append(stmt);
21430
21431 if (itr->second.favorite)
21432 {
21433 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_SPELL_FAVORITE);
21434 stmt->setUInt64(0, GetGUID().GetCounter());
21435 stmt->setUInt32(1, itr->first);
21436 trans->Append(stmt);
21437 }
21438 }
21439
21440 if (itr->second.state == PLAYERSPELL_REMOVED)
21441 {
21442 itr = m_spells.erase(itr);
21443 continue;
21444 }
21445
21446 if (itr->second.state != PLAYERSPELL_TEMPORARY)
21447 itr->second.state = PLAYERSPELL_UNCHANGED;
21448
21449 ++itr;
21450 }
21451}
21452
21454{
21455 for (auto itr = m_storedAuraTeleportLocations.begin(); itr != m_storedAuraTeleportLocations.end(); )
21456 {
21457 StoredAuraTeleportLocation& storedLocation = itr->second;
21458 if (storedLocation.State == StoredAuraTeleportLocation::DELETED)
21459 {
21461 stmt->setUInt64(0, GetGUID().GetCounter());
21462 trans->Append(stmt);
21463 itr = m_storedAuraTeleportLocations.erase(itr);
21464 continue;
21465 }
21466
21467 if (storedLocation.State == StoredAuraTeleportLocation::CHANGED)
21468 {
21470 stmt->setUInt64(0, GetGUID().GetCounter());
21471 trans->Append(stmt);
21472
21474 stmt->setUInt64(0, GetGUID().GetCounter());
21475 stmt->setUInt32(1, itr->first);
21476 stmt->setUInt32(2, storedLocation.Loc.GetMapId());
21477 stmt->setFloat(3, storedLocation.Loc.GetPositionX());
21478 stmt->setFloat(4, storedLocation.Loc.GetPositionY());
21479 stmt->setFloat(5, storedLocation.Loc.GetPositionZ());
21480 stmt->setFloat(6, storedLocation.Loc.GetOrientation());
21481 trans->Append(stmt);
21482 }
21483
21484 ++itr;
21485 }
21486}
21487
21488// save player stats -- only for external usage
21489// real stats will be recalculated on player login
21491{
21492 // check if stat saving is enabled and if char level is high enough
21493 if (!sWorld->getIntConfig(CONFIG_MIN_LEVEL_STAT_SAVE) || GetLevel() < sWorld->getIntConfig(CONFIG_MIN_LEVEL_STAT_SAVE))
21494 return;
21495
21497
21498 stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_STATS);
21499 stmt->setUInt64(0, GetGUID().GetCounter());
21500 trans->Append(stmt);
21501
21502 uint8 index = 0;
21503
21504 stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_STATS);
21505 stmt->setUInt64(index++, GetGUID().GetCounter());
21506 stmt->setUInt32(index++, GetMaxHealth());
21507
21508 for (uint8 i = 0; i < MAX_POWERS_PER_CLASS; ++i)
21509 stmt->setUInt32(index++, m_unitData->MaxPower[i]);
21510
21511 for (uint8 i = 0; i < MAX_STATS; ++i)
21512 stmt->setUInt32(index++, GetStat(Stats(i)));
21513
21514 for (int i = 0; i < MAX_SPELL_SCHOOL; ++i)
21515 stmt->setUInt32(index++, GetResistance(SpellSchools(i)));
21516
21517 stmt->setFloat(index++, m_activePlayerData->BlockPercentage);
21518 stmt->setFloat(index++, m_activePlayerData->DodgePercentage);
21519 stmt->setFloat(index++, m_activePlayerData->ParryPercentage);
21520 stmt->setFloat(index++, m_activePlayerData->CritPercentage);
21521 stmt->setFloat(index++, m_activePlayerData->RangedCritPercentage);
21522 stmt->setFloat(index++, m_activePlayerData->SpellCritPercentage);
21523 stmt->setUInt32(index++, m_unitData->AttackPower);
21524 stmt->setUInt32(index++, m_unitData->RangedAttackPower);
21525 stmt->setUInt32(index++, GetBaseSpellPowerBonus());
21526 stmt->setUInt32(index, m_activePlayerData->CombatRatings[CR_RESILIENCE_PLAYER_DAMAGE]);
21527 stmt->setFloat(index++, m_activePlayerData->Mastery);
21528 stmt->setInt32(index++, m_activePlayerData->Versatility);
21529
21530 trans->Append(stmt);
21531}
21532
21534{
21535 if (!sLog->ShouldLog("entities.unit", LOG_LEVEL_DEBUG))
21536 return;
21537
21538 sLog->OutMessage("entities.unit", LOG_LEVEL_DEBUG, "HP is: \t\t\t{}\t\tMP is: \t\t\t{}", GetMaxHealth(), GetMaxPower(POWER_MANA));
21539 sLog->OutMessage("entities.unit", LOG_LEVEL_DEBUG, "AGILITY is: \t\t{}\t\tSTRENGTH is: \t\t{}", GetStat(STAT_AGILITY), GetStat(STAT_STRENGTH));
21540 sLog->OutMessage("entities.unit", LOG_LEVEL_DEBUG, "INTELLECT is: \t\t{}", GetStat(STAT_INTELLECT));
21541 sLog->OutMessage("entities.unit", LOG_LEVEL_DEBUG, "STAMINA is: \t\t{}", GetStat(STAT_STAMINA));
21542 sLog->OutMessage("entities.unit", LOG_LEVEL_DEBUG, "Armor is: \t\t{}\t\tBlock is: \t\t{}", GetArmor(), *m_activePlayerData->BlockPercentage);
21543 sLog->OutMessage("entities.unit", LOG_LEVEL_DEBUG, "HolyRes is: \t\t{}\t\tFireRes is: \t\t{}", GetResistance(SPELL_SCHOOL_MASK_HOLY), GetResistance(SPELL_SCHOOL_MASK_FIRE));
21544 sLog->OutMessage("entities.unit", LOG_LEVEL_DEBUG, "NatureRes is: \t\t{}\t\tFrostRes is: \t\t{}", GetResistance(SPELL_SCHOOL_MASK_NATURE), GetResistance(SPELL_SCHOOL_MASK_FROST));
21545 sLog->OutMessage("entities.unit", LOG_LEVEL_DEBUG, "ShadowRes is: \t\t{}\t\tArcaneRes is: \t\t{}", GetResistance(SPELL_SCHOOL_MASK_SHADOW), GetResistance(SPELL_SCHOOL_MASK_ARCANE));
21546 sLog->OutMessage("entities.unit", LOG_LEVEL_DEBUG, "MIN_DAMAGE is: \t\t{}\tMAX_DAMAGE is: \t\t{}", *m_unitData->MinDamage, *m_unitData->MaxDamage);
21547 sLog->OutMessage("entities.unit", LOG_LEVEL_DEBUG, "MIN_OFFHAND_DAMAGE is: \t{}\tMAX_OFFHAND_DAMAGE is: \t{}", *m_unitData->MinOffHandDamage, *m_unitData->MaxOffHandDamage);
21548 sLog->OutMessage("entities.unit", LOG_LEVEL_DEBUG, "MIN_RANGED_DAMAGE is: \t{}\tMAX_RANGED_DAMAGE is: \t{}", *m_unitData->MinRangedDamage, *m_unitData->MaxRangedDamage);
21549 sLog->OutMessage("entities.unit", LOG_LEVEL_DEBUG, "ATTACK_TIME is: \t{}\t\tRANGE_ATTACK_TIME is: \t{}", GetBaseAttackTime(BASE_ATTACK), GetBaseAttackTime(RANGED_ATTACK));
21550}
21551
21552/*********************************************************/
21553/*** FLOOD FILTER SYSTEM ***/
21554/*********************************************************/
21555
21557{
21558 // ignore chat spam protection for GMs in any mode
21560 return;
21561
21562 uint32 limit;
21563 uint32 delay;
21564 switch (index)
21565 {
21567 limit = sWorld->getIntConfig(CONFIG_CHATFLOOD_MESSAGE_COUNT);
21568 delay = sWorld->getIntConfig(CONFIG_CHATFLOOD_MESSAGE_DELAY);
21569 break;
21571 limit = sWorld->getIntConfig(CONFIG_CHATFLOOD_ADDON_MESSAGE_COUNT);
21572 delay = sWorld->getIntConfig(CONFIG_CHATFLOOD_ADDON_MESSAGE_DELAY);
21573 break;
21574 default:
21575 return;
21576 }
21577
21578 time_t current = GameTime::GetGameTime();
21579 if (m_chatFloodData[index].Time > current)
21580 {
21581 if (!limit)
21582 return;
21583
21584 ++m_chatFloodData[index].Count;
21585 if (m_chatFloodData[index].Count >= limit)
21586 {
21587 // prevent overwrite mute time, if message send just before mutes set, for example.
21588 time_t new_mute = current + sWorld->getIntConfig(CONFIG_CHATFLOOD_MUTE_TIME);
21589 if (GetSession()->m_muteTime < new_mute)
21590 GetSession()->m_muteTime = new_mute;
21591
21592 m_chatFloodData[index].Count = 0;
21593 }
21594 }
21595 else
21596 m_chatFloodData[index].Count = 1;
21597
21598 m_chatFloodData[index].Time = current + delay;
21599}
21600
21601/*********************************************************/
21602/*** LOW LEVEL FUNCTIONS:Notifiers ***/
21603/*********************************************************/
21604
21606{
21608
21609 stmt->setFloat(0, loc.GetPositionX());
21610 stmt->setFloat(1, loc.GetPositionY());
21611 stmt->setFloat(2, loc.GetPositionZ());
21612 stmt->setFloat(3, loc.GetOrientation());
21613 stmt->setUInt16(4, uint16(loc.GetMapId()));
21614 stmt->setUInt16(5, zoneId);
21615 stmt->setUInt64(6, guid.GetCounter());
21616
21617 CharacterDatabase.ExecuteOrAppend(trans, stmt);
21618}
21619
21621{
21623}
21624
21626{
21627 if (err && err != m_swingErrorMsg)
21629
21630 m_swingErrorMsg = err;
21631}
21632
21634{
21636 cancelAutoRepeat.Guid = target->GetGUID(); // may be it's target guid
21637 SendMessageToSet(cancelAutoRepeat.Write(), true);
21638}
21639
21641{
21643}
21644
21645void Player::SendDungeonDifficulty(int32 forcedDifficulty /*= -1*/) const
21646{
21647 WorldPackets::Misc::DungeonDifficultySet dungeonDifficultySet;
21648 dungeonDifficultySet.DifficultyID = forcedDifficulty == -1 ? GetDungeonDifficultyID() : forcedDifficulty;
21649 SendDirectMessage(dungeonDifficultySet.Write());
21650}
21651
21652void Player::SendRaidDifficulty(bool legacy, int32 forcedDifficulty /*= -1*/) const
21653{
21654 WorldPackets::Misc::RaidDifficultySet raidDifficultySet;
21655 raidDifficultySet.DifficultyID = forcedDifficulty == -1 ? (legacy ? GetLegacyRaidDifficultyID() : GetRaidDifficultyID()) : forcedDifficulty;
21656 raidDifficultySet.Legacy = legacy;
21657 SendDirectMessage(raidDifficultySet.Write());
21658}
21659
21661{
21663 SendDirectMessage(data.Write());
21664}
21665
21668{
21669 for (auto itr = m_recentInstances.begin(); itr != m_recentInstances.end(); )
21670 {
21671 Map* map = sMapMgr->FindMap(itr->first, itr->second);
21672 bool forgetInstance = false;
21673 if (map)
21674 {
21675 if (InstanceMap* instance = map->ToInstanceMap())
21676 {
21677 switch (instance->Reset(method))
21678 {
21681 forgetInstance = true;
21682 break;
21684 if (method == InstanceResetMethod::Manual)
21686 else if (method == InstanceResetMethod::OnChangeDifficulty)
21687 forgetInstance = true;
21688 break;
21690 break;
21691 default:
21692 break;
21693 }
21694 }
21695 }
21696
21697 if (forgetInstance)
21698 itr = m_recentInstances.erase(itr);
21699 else
21700 ++itr;
21701 }
21702}
21703
21705{
21707 data.MapID = MapId;
21708 SendDirectMessage(data.Write());
21709}
21710
21712{
21713 /*reasons for instance reset failure:
21714 // 0: There are players inside the instance.
21715 // 1: There are players offline in your party.
21716 // 2>: There are players in your party attempting to zone into an instance.
21717 */
21718
21720 data.MapID = mapID;
21721 data.ResetFailedReason = reason;
21722 SendDirectMessage(data.Write());
21723}
21724
21725bool Player::IsLockedToDungeonEncounter(uint32 dungeonEncounterId) const
21726{
21727 DungeonEncounterEntry const* dungeonEncounter = sDungeonEncounterStore.LookupEntry(dungeonEncounterId);
21728 if (!dungeonEncounter)
21729 return false;
21730
21731 InstanceLock const* instanceLock = sInstanceLockMgr.FindActiveInstanceLock(GetGUID(), { GetMap()->GetEntry(), GetMap()->GetMapDifficulty() });
21732 if (!instanceLock)
21733 return false;
21734
21735 return (instanceLock->GetData()->CompletedEncountersMask & (1u << dungeonEncounter->Bit)) != 0;
21736}
21737
21738bool Player::IsLockedToDungeonEncounter(uint32 dungeonEncounterId, Difficulty difficulty) const
21739{
21740 DungeonEncounterEntry const* dungeonEncounter = sDungeonEncounterStore.LookupEntry(dungeonEncounterId);
21741 if (!dungeonEncounter)
21742 return false;
21743
21744 InstanceLock const* instanceLock = sInstanceLockMgr.FindActiveInstanceLock(GetGUID(), { uint32(dungeonEncounter->MapID), difficulty });
21745 if (!instanceLock)
21746 return false;
21747
21748 return (instanceLock->GetData()->CompletedEncountersMask & (1u << dungeonEncounter->Bit)) != 0;
21749}
21750
21751/*********************************************************/
21752/*** Update timers ***/
21753/*********************************************************/
21754
21756void Player::UpdateAfkReport(time_t currTime)
21757{
21758 if (m_bgData.bgAfkReportedTimer <= currTime)
21759 {
21761 m_bgData.bgAfkReportedTimer = currTime+5*MINUTE;
21762 }
21763}
21764
21765void Player::SetContestedPvP(Player* attackedPlayer)
21766{
21767 if (attackedPlayer && (attackedPlayer == this || (duel && duel->Opponent == attackedPlayer)))
21768 return;
21769
21770 SetContestedPvPTimer(30000);
21772 {
21775 // call MoveInLineOfSight for nearby contested guards
21776 Trinity::AIRelocationNotifier notifier(*this);
21778 }
21779 for (Unit* unit : m_Controlled)
21780 {
21781 if (!unit->HasUnitState(UNIT_STATE_ATTACK_PLAYER))
21782 {
21783 unit->AddUnitState(UNIT_STATE_ATTACK_PLAYER);
21784 Trinity::AIRelocationNotifier notifier(*unit);
21786 }
21787 }
21788}
21789
21791{
21793 return;
21794
21795 if (m_contestedPvPTimer <= diff)
21797 else
21798 m_contestedPvPTimer -= diff;
21799}
21800
21802{
21806}
21807
21808void Player::UpdatePvPFlag(time_t currTime)
21809{
21810 if (!IsPvP())
21811 return;
21812
21813 if (!pvpInfo.EndTimer || (currTime < pvpInfo.EndTimer +300) || pvpInfo.IsHostile)
21814 return;
21815
21816 if (pvpInfo.EndTimer <= currTime)
21817 {
21818 pvpInfo.EndTimer = 0;
21820 }
21821
21822 UpdatePvP(false);
21823}
21824
21825void Player::UpdateDuelFlag(time_t currTime)
21826{
21827 if (duel && duel->State == DUEL_STATE_COUNTDOWN && duel->StartTime <= currTime)
21828 {
21829 sScriptMgr->OnPlayerDuelStart(this, duel->Opponent);
21830
21831 SetDuelTeam(1);
21832 duel->Opponent->SetDuelTeam(2);
21833
21835 duel->Opponent->duel->State = DUEL_STATE_IN_PROGRESS;
21836 }
21837}
21838
21840{
21841 ObjectGuid pet_guid = GetPetGUID();
21842 if (!pet_guid.IsEmpty())
21843 {
21844 if (!pet_guid.IsPet())
21845 return nullptr;
21846
21847 Pet* pet = ObjectAccessor::GetPet(*this, pet_guid);
21848
21849 if (!pet)
21850 return nullptr;
21851
21852 if (IsInWorld())
21853 return pet;
21854
21855 // there may be a guardian in this slot
21856 //TC_LOG_ERROR("entities.player", "Player::GetPet: Pet {} does not exist.", GUID_LOPART(pet_guid));
21857 //const_cast<Player*>(this)->SetPetGUID(0);
21858 }
21859
21860 return nullptr;
21861}
21862
21863void Player::RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent)
21864{
21865 if (!pet)
21866 pet = GetPet();
21867
21868 if (pet)
21869 {
21870 TC_LOG_DEBUG("entities.pet", "Player::RemovePet: Player '{}' ({}), Pet (Entry: {}, Mode: {}, ReturnReagent: {})",
21871 GetName(), GetGUID().ToString(), pet->GetEntry(), mode, returnreagent);
21872
21873 if (pet->m_removed)
21874 return;
21875 }
21876
21877 if (returnreagent && (pet || m_temporaryUnsummonedPetNumber) && !InBattleground())
21878 {
21879 //returning of reagents only for players, so best done here
21880 uint32 spellId = pet ? *pet->m_unitData->CreatedBySpell : m_oldpetspell;
21881 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, GetMap()->GetDifficultyID());
21882
21883 if (spellInfo)
21884 {
21885 for (uint32 i = 0; i < MAX_SPELL_REAGENTS; ++i)
21886 {
21887 if (spellInfo->Reagent[i] > 0)
21888 {
21889 ItemPosCountVec dest; //for succubus, voidwalker, felhunter and felguard credit soulshard when despawn reason other than death (out of range, logout)
21890 InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, spellInfo->Reagent[i], spellInfo->ReagentCount[i]);
21891 if (msg == EQUIP_ERR_OK)
21892 {
21893 Item* item = StoreNewItem(dest, spellInfo->Reagent[i], true);
21894 if (IsInWorld())
21895 SendNewItem(item, spellInfo->ReagentCount[i], true, false);
21896 }
21897 }
21898 }
21899 }
21901 }
21902
21903 if (!pet)
21904 {
21905 // Handle removing pet while it is in "temporarily unsummoned" state, for example on mount
21906 if (mode == PET_SAVE_NOT_IN_SLOT && m_petStable && m_petStable->CurrentPetIndex)
21907 m_petStable->CurrentPetIndex.reset();
21908
21909 return;
21910 }
21911
21912 pet->CombatStop();
21913
21914 // only if current pet in slot
21915 pet->SavePetToDB(mode);
21916
21917 PetStable::PetInfo const* currentPet = m_petStable->GetCurrentPet();
21918 ASSERT(currentPet && currentPet->PetNumber == pet->GetCharmInfo()->GetPetNumber());
21919 if (mode == PET_SAVE_NOT_IN_SLOT)
21920 m_petStable->CurrentPetIndex.reset();
21921 else if (mode == PET_SAVE_AS_DELETED)
21922 {
21923 if (m_activePlayerData->PetStable.has_value())
21924 {
21925 int32 ufIndex = m_activePlayerData->PetStable->Pets.FindIndexIf([currentPet](UF::StablePetInfo const& p) { return p.PetNumber == currentPet->PetNumber; });
21926 if (ufIndex >= 0)
21928 .ModifyValue(&UF::ActivePlayerData::PetStable, 0)
21929 .ModifyValue(&UF::StableInfo::Pets), ufIndex);
21930 }
21931
21932 if (Optional<uint32> petIndex = m_petStable->GetCurrentActivePetIndex())
21933 m_petStable->ActivePets[*petIndex].reset();
21934
21935 m_petStable->CurrentPetIndex.reset();
21936 }
21937 // else if (stable slots) handled in opcode handlers due to required swaps
21938 // else (current pet) doesnt need to do anything
21939
21940 SetMinion(pet, false);
21941
21942 pet->AddObjectToRemoveList();
21943 pet->m_removed = true;
21944
21945 if (pet->isControlled())
21946 {
21947 WorldPackets::Pet::PetSpells petSpellsPacket;
21948 SendDirectMessage(petSpellsPacket.Write());
21949
21950 if (GetGroup())
21952 }
21953}
21954
21956{
21957 if (m_activePlayerData->PetStable.has_value())
21958 {
21959 int32 ufIndex = m_activePlayerData->PetStable->Pets.FindIndexIf([petNumber](UF::StablePetInfo const& p) { return p.PetNumber == petNumber; });
21960 if (ufIndex >= 0)
21962 .ModifyValue(&UF::ActivePlayerData::PetStable, 0)
21963 .ModifyValue(&UF::StableInfo::Pets), ufIndex);
21964 }
21965
21966 if (!m_petStable)
21967 return;
21968
21969 if (Optional<uint32> petIndex = m_petStable->GetCurrentActivePetIndex())
21970 if (m_petStable->ActivePets[*petIndex] && m_petStable->ActivePets[*petIndex]->PetNumber == petNumber)
21971 m_petStable->CurrentPetIndex.reset();
21972
21973 auto petNumberPred = [petNumber](Optional<PetStable::PetInfo> const& pet)
21974 {
21975 return pet && pet->PetNumber == petNumber && pet->Type == HUNTER_PET;
21976 };
21977
21978 bool foundPet = false;
21979 auto activeItr = std::ranges::find_if(m_petStable->ActivePets, petNumberPred);
21980 if (activeItr != m_petStable->ActivePets.end())
21981 {
21982 activeItr->reset();
21983 foundPet = true;
21984 }
21985
21986 auto stabledItr = std::ranges::find_if(m_petStable->StabledPets, petNumberPred);
21987 if (stabledItr != m_petStable->StabledPets.end())
21988 {
21989 stabledItr->reset();
21990 foundPet = true;
21991 }
21992
21993 if (foundPet)
21994 Pet::DeleteFromDB(petNumber);
21995}
21996
21998{
21999 WorldPackets::Pet::PetTameFailure petTameFailure;
22000 petTameFailure.Result = AsUnderlyingType(result);
22001 SendDirectMessage(petTameFailure.Write());
22002}
22003
22004void Player::AddPetAura(PetAura const* petSpell)
22005{
22006 m_petAuras.insert(petSpell);
22007 if (Pet* pet = GetPet())
22008 pet->CastPetAura(petSpell);
22009}
22010
22011void Player::RemovePetAura(PetAura const* petSpell)
22012{
22013 m_petAuras.erase(petSpell);
22014 if (Pet* pet = GetPet())
22015 pet->RemoveAurasDueToSpell(petSpell->GetAura(pet->GetEntry()));
22016}
22017
22019{
22020 if (Creature* summonedBattlePet = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, GetCritterGUID()))
22021 if (!GetSummonedBattlePetGUID().IsEmpty() && GetSummonedBattlePetGUID() == summonedBattlePet->GetBattlePetCompanionGUID())
22022 return summonedBattlePet;
22023
22024 return nullptr;
22025}
22026
22028{
22029 if (pet)
22030 {
22035 }
22036 else
22037 {
22042 }
22043}
22044
22046{
22047 Unit* charm = GetCharmed();
22048 if (!charm)
22049 return;
22050
22051 if (charm->GetTypeId() == TYPEID_UNIT)
22052 {
22054 static_cast<Puppet*>(charm)->UnSummon();
22055 else if (charm->IsVehicle())
22056 {
22057 ExitVehicle();
22058
22059 // Temporary for issue https://github.com/TrinityCore/TrinityCore/issues/24876
22061 {
22062 TC_LOG_FATAL("entities.player", "Player::StopCastingCharm Player '{}' ({}) is not able to uncharm vehicle ({}) because of missing SPELL_AURA_CONTROL_VEHICLE",
22064
22065 // attempt to recover from missing HandleAuraControlVehicle unapply handling
22066 // THIS IS A HACK, NEED TO FIND HOW IS IT EVEN POSSBLE TO NOT HAVE THE AURA
22067 _ExitVehicle();
22068 }
22069 }
22070 }
22071 if (!GetCharmedGUID().IsEmpty())
22072 charm->RemoveCharmAuras();
22073
22074 if (!GetCharmedGUID().IsEmpty())
22075 {
22076 TC_LOG_FATAL("entities.player", "Player::StopCastingCharm: Player '{}' ({}) is not able to uncharm unit ({})", GetName(), GetGUID().ToString(), GetCharmedGUID().ToString());
22077 if (!charm->GetCharmerGUID().IsEmpty())
22078 {
22079 TC_LOG_FATAL("entities.player", "Player::StopCastingCharm: Charmed unit has charmer {}\nPlayer debug info: {}\nCharm debug info: {}",
22080 charm->GetCharmerGUID().ToString(), GetDebugInfo(), charm->GetDebugInfo());
22081 ABORT();
22082 }
22083
22084 SetCharm(charm, false);
22085 }
22086}
22087
22088void Player::Say(std::string_view text, Language language, WorldObject const* /*= nullptr*/)
22089{
22090 std::string _text(text);
22091 sScriptMgr->OnPlayerChat(this, CHAT_MSG_SAY, language, _text);
22092
22093 SendChatMessageToSetInRange(CHAT_MSG_SAY, language, std::move(_text), sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY));
22094}
22095
22096void Player::SendChatMessageToSetInRange(ChatMsg chatMsg, Language language, std::string&& text, float range)
22097{
22098 Trinity::CustomChatTextBuilder builder(this, chatMsg, std::move(text), language, this);
22100
22101 // Send to self
22102 localizer(this);
22103
22104 // Send to players
22105 Trinity::MessageDistDeliverer<Trinity::LocalizedDo<Trinity::CustomChatTextBuilder>> notifier(this, localizer, range, false, nullptr, true);
22106 Cell::VisitWorldObjects(this, notifier, range);
22107}
22108
22109void Player::Say(uint32 textId, WorldObject const* target /*= nullptr*/)
22110{
22111 Talk(textId, CHAT_MSG_SAY, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), target);
22112}
22113
22114void Player::Yell(std::string_view text, Language language, WorldObject const* /*= nullptr*/)
22115{
22116 std::string _text(text);
22117 sScriptMgr->OnPlayerChat(this, CHAT_MSG_YELL, language, _text);
22118
22119 SendChatMessageToSetInRange(CHAT_MSG_YELL, language, std::move(_text), sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL));
22120}
22121
22122void Player::Yell(uint32 textId, WorldObject const* target /*= nullptr*/)
22123{
22124 Talk(textId, CHAT_MSG_YELL, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL), target);
22125}
22126
22127void Player::TextEmote(std::string_view text, WorldObject const* /*= nullptr*/, bool /*= false*/)
22128{
22129 std::string _text(text);
22130 sScriptMgr->OnPlayerChat(this, CHAT_MSG_EMOTE, LANG_UNIVERSAL, _text);
22131
22133 packet.Initialize(CHAT_MSG_EMOTE, LANG_UNIVERSAL, this, this, _text);
22135}
22136
22137void Player::WhisperAddon(std::string const& text, std::string const& prefix, bool isLogged, Player* receiver)
22138{
22139 std::string _text(text);
22140 sScriptMgr->OnPlayerChat(this, CHAT_MSG_WHISPER, uint32(isLogged ? LANG_ADDON_LOGGED : LANG_ADDON), _text, receiver);
22141
22142 if (!receiver->GetSession()->IsAddonRegistered(prefix))
22143 return;
22144
22146 packet.Initialize(CHAT_MSG_WHISPER, isLogged ? LANG_ADDON_LOGGED : LANG_ADDON, this, this, text, 0, "", DEFAULT_LOCALE, prefix);
22147 receiver->SendDirectMessage(packet.Write());
22148}
22149
22150void Player::TextEmote(uint32 textId, WorldObject const* target /*= nullptr*/, bool /*isBossEmote = false*/)
22151{
22152 Talk(textId, CHAT_MSG_EMOTE, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), target);
22153}
22154
22155void Player::Whisper(std::string_view text, Language language, Player* target, bool /*= false*/)
22156{
22157 ASSERT(target);
22158
22159 bool isAddonMessage = language == LANG_ADDON;
22160
22161 if (!isAddonMessage) // if not addon data
22162 language = LANG_UNIVERSAL; // whispers should always be readable
22163
22164 std::string _text(text);
22165 sScriptMgr->OnPlayerChat(this, CHAT_MSG_WHISPER, language, _text, target);
22166
22168 packet.Initialize(CHAT_MSG_WHISPER, language, this, this, _text);
22169 target->SendDirectMessage(packet.Write());
22170
22171 // rest stuff shouldn't happen in case of addon message
22172 if (isAddonMessage)
22173 return;
22174
22175 packet.Initialize(CHAT_MSG_WHISPER_INFORM, language, target, target, _text);
22176 SendDirectMessage(packet.Write());
22177
22178 if (!isAcceptWhispers() && !IsGameMaster() && !target->IsGameMaster())
22179 {
22180 SetAcceptWhispers(true);
22182 }
22183
22184 // announce afk or dnd message
22185 if (target->isAFK())
22186 ChatHandler(GetSession()).PSendSysMessage(LANG_PLAYER_AFK, target->GetName().c_str(), target->autoReplyMsg.c_str());
22187 else if (target->isDND())
22188 ChatHandler(GetSession()).PSendSysMessage(LANG_PLAYER_DND, target->GetName().c_str(), target->autoReplyMsg.c_str());
22189}
22190
22191void Player::Whisper(uint32 textId, Player* target, bool /*isBossWhisper = false*/)
22192{
22193 if (!target)
22194 return;
22195
22196 BroadcastTextEntry const* bct = sBroadcastTextStore.LookupEntry(textId);
22197 if (!bct)
22198 {
22199 TC_LOG_ERROR("entities.unit", "WorldObject::Whisper: `broadcast_text` was not {} found", textId);
22200 return;
22201 }
22202
22206 target->SendDirectMessage(packet.Write());
22207}
22208
22210{
22211 if (IsGameMaster())
22212 return true;
22213
22214 for (std::pair<uint32 const, LanguageDesc> const& languageDesc : sLanguageMgr->GetLanguageDescById(language))
22215 if (languageDesc.second.SkillId && HasSkill(languageDesc.second.SkillId))
22216 return true;
22217
22219 return true;
22220
22221 return false;
22222}
22223
22225{
22226 ItemMap::const_iterator itr = mMitems.find(id);
22227 return itr != mMitems.end() ? itr->second : nullptr;
22228}
22229
22231{
22232 ASSERT(it);
22233 //ASSERT deleted, because items can be added before loading
22234 mMitems[it->GetGUID().GetCounter()] = it;
22235}
22236
22238{
22239 return mMitems.erase(id) ? true : false;
22240}
22241
22243{
22245}
22246
22248{
22249 Pet* pet = GetPet();
22250
22251 if (!pet)
22252 return;
22253
22254 CharmInfo* charmInfo = pet->GetCharmInfo();
22255
22256 WorldPackets::Pet::PetSpells petSpellsPacket;
22257 petSpellsPacket.PetGUID = pet->GetGUID();
22258 petSpellsPacket._CreatureFamily = pet->GetCreatureTemplate()->family; // creature family (required for pet talents)
22259 petSpellsPacket.Specialization = pet->GetSpecialization();
22260 petSpellsPacket.TimeLimit = pet->GetDuration();
22261 petSpellsPacket.ReactState = pet->GetReactState();
22262 petSpellsPacket.CommandState = charmInfo->GetCommandState();
22263
22264 // action bar loop
22265 for (uint32 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i)
22266 petSpellsPacket.ActionButtons[i] = charmInfo->GetActionBarEntry(i)->packedData;
22267
22268 if (pet->IsPermanentPetFor(this))
22269 {
22270 // spells loop
22271 for (PetSpellMap::iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr)
22272 {
22273 if (itr->second.state == PETSPELL_REMOVED)
22274 continue;
22275
22276 petSpellsPacket.Actions.push_back(MAKE_UNIT_ACTION_BUTTON(itr->first, itr->second.active));
22277 }
22278 }
22279
22280 // Cooldowns
22281 pet->GetSpellHistory()->WritePacket(&petSpellsPacket);
22282
22283 SendDirectMessage(petSpellsPacket.Write());
22284}
22285
22287{
22288 Unit* charm = GetCharmed();
22289 if (!charm)
22290 return;
22291
22292 CharmInfo* charmInfo = charm->GetCharmInfo();
22293
22294 if (!charmInfo)
22295 {
22296 TC_LOG_ERROR("entities.player", "Player::PossessSpellInitialize: charm ({}) has no charminfo!", charm->GetGUID().ToString());
22297 return;
22298 }
22299
22300 WorldPackets::Pet::PetSpells petSpellsPacket;
22301 petSpellsPacket.PetGUID = charm->GetGUID();
22302
22303 for (uint32 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i)
22304 petSpellsPacket.ActionButtons[i] = charmInfo->GetActionBarEntry(i)->packedData;
22305
22306 // Cooldowns
22307 charm->GetSpellHistory()->WritePacket(&petSpellsPacket);
22308
22309 SendDirectMessage(petSpellsPacket.Write());
22310}
22311
22313{
22314 Creature* vehicle = GetVehicleCreatureBase();
22315 if (!vehicle)
22316 return;
22317
22319 petSpells.PetGUID = vehicle->GetGUID();
22320 petSpells._CreatureFamily = 0; // Pet Family (0 for all vehicles)
22321 petSpells.Specialization = 0;
22322 petSpells.TimeLimit = vehicle->IsSummon() ? vehicle->ToTempSummon()->GetTimer().count() : 0;
22323 petSpells.ReactState = vehicle->GetReactState();
22324 petSpells.CommandState = COMMAND_FOLLOW;
22325 petSpells.Flag = 0x8;
22326
22327 for (uint32 i = 0; i < MAX_SPELL_CONTROL_BAR; ++i)
22328 petSpells.ActionButtons[i] = MAKE_UNIT_ACTION_BUTTON(0, i + 8);
22329
22330 for (uint32 i = 0; i < MAX_CREATURE_SPELLS; ++i)
22331 {
22332 uint32 spellId = vehicle->m_spells[i];
22333 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, GetMap()->GetDifficultyID());
22334 if (!spellInfo)
22335 continue;
22336
22338 continue;
22339
22340 if (!sConditionMgr->IsObjectMeetingVehicleSpellConditions(vehicle->GetEntry(), spellId, this, vehicle))
22341 {
22342 TC_LOG_DEBUG("condition", "Player::VehicleSpellInitialize: Player '{}' ({}) doesn't meet conditions for vehicle (Entry: {}, Spell: {})",
22343 GetName(), GetGUID().ToString(), vehicle->ToCreature()->GetEntry(), spellId);
22344 continue;
22345 }
22346
22347 if (spellInfo->IsPassive())
22348 vehicle->CastSpell(vehicle, spellInfo->Id, true);
22349
22350 petSpells.ActionButtons[i] = MAKE_UNIT_ACTION_BUTTON(spellId, i + 8);
22351 }
22352
22353 // Cooldowns
22354 vehicle->GetSpellHistory()->WritePacket(&petSpells);
22355
22356 SendDirectMessage(petSpells.Write());
22357}
22358
22360{
22361 Unit* charm = GetFirstControlled();
22362 if (!charm)
22363 return;
22364
22365 CharmInfo* charmInfo = charm->GetCharmInfo();
22366 if (!charmInfo)
22367 {
22368 TC_LOG_ERROR("entities.player", "Player::CharmSpellInitialize(): Player '{}' ({}) has a charm ({}) but no no charminfo!",
22369 GetName(), GetGUID().ToString(), charm->GetGUID().ToString());
22370 return;
22371 }
22372
22374 petSpells.PetGUID = charm->GetGUID();
22375
22376 if (charm->GetTypeId() == TYPEID_UNIT)
22377 {
22378 petSpells.ReactState = charm->ToCreature()->GetReactState();
22379 petSpells.CommandState = charmInfo->GetCommandState();
22380 }
22381
22382 for (uint32 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i)
22383 petSpells.ActionButtons[i] = charmInfo->GetActionBarEntry(i)->packedData;
22384
22385 for (uint32 i = 0; i < MAX_SPELL_CHARM; ++i)
22386 {
22387 CharmSpellInfo* cspell = charmInfo->GetCharmSpell(i);
22388 if (cspell->GetAction())
22389 petSpells.Actions.push_back(cspell->packedData);
22390 }
22391
22392 // Cooldowns
22393 if (charm->GetTypeId() != TYPEID_PLAYER)
22394 charm->GetSpellHistory()->WritePacket(&petSpells);
22395
22396 SendDirectMessage(petSpells.Write());
22397}
22398
22400{
22402 SendDirectMessage(packet.Write());
22403}
22404
22405bool Player::IsAffectedBySpellmod(SpellInfo const* spellInfo, SpellModifier const* mod, Spell* spell)
22406{
22407 if (!mod || !spellInfo)
22408 return false;
22409
22410 // First time this aura applies a mod to us and is out of charges
22411 if (spell && mod->ownerAura->IsUsingCharges() && !mod->ownerAura->GetCharges() && !spell->m_appliedMods.count(mod->ownerAura))
22412 return false;
22413
22414 switch (mod->op)
22415 {
22416 case SpellModOp::Duration: // +duration to infinite duration spells making them limited
22417 if (spellInfo->GetDuration() == -1)
22418 return false;
22419 break;
22420 case SpellModOp::CritChance: // mod crit to spells that can't crit
22421 if (!spellInfo->HasAttribute(SPELL_ATTR0_CU_CAN_CRIT))
22422 return false;
22423 break;
22424 case SpellModOp::PointsIndex0: // check if spell has any effect at that index
22425 case SpellModOp::Points:
22426 if (spellInfo->GetEffects().size() <= EFFECT_0)
22427 return false;
22428 break;
22429 case SpellModOp::PointsIndex1: // check if spell has any effect at that index
22430 if (spellInfo->GetEffects().size() <= EFFECT_1)
22431 return false;
22432 break;
22433 case SpellModOp::PointsIndex2: // check if spell has any effect at that index
22434 if (spellInfo->GetEffects().size() <= EFFECT_2)
22435 return false;
22436 break;
22437 case SpellModOp::PointsIndex3: // check if spell has any effect at that index
22438 if (spellInfo->GetEffects().size() <= EFFECT_3)
22439 return false;
22440 break;
22441 case SpellModOp::PointsIndex4: // check if spell has any effect at that index
22442 if (spellInfo->GetEffects().size() <= EFFECT_4)
22443 return false;
22444 break;
22445 default:
22446 break;
22447 }
22448
22449 return spellInfo->IsAffectedBySpellMod(mod);
22450}
22451
22452template <class T>
22453void Player::GetSpellModValues(SpellInfo const* spellInfo, SpellModOp op, Spell* spell, T base, int32* flat, float* pct) const
22454{
22455 ASSERT(flat && pct);
22456
22457 *flat = 0;
22458 *pct = 1.0f;
22459
22460 auto spellModOpBegin = std::ranges::lower_bound(m_spellMods, op, std::ranges::less(), &SpellModifier::op);
22461 if (spellModOpBegin == m_spellMods.end() || (*spellModOpBegin)->op != op)
22462 return;
22463
22464 // Drop charges for triggering spells instead of triggered ones
22466 spell = m_spellModTakingSpell;
22467
22468 auto spellModTypeBegin = [&](SpellModType type)
22469 {
22470 auto typeBegin = spellModOpBegin;
22471 auto end = m_spellMods.end();
22472 while (typeBegin != end && (*typeBegin)->op == op)
22473 {
22474 if ((*typeBegin)->type == type)
22475 return typeBegin;
22476
22477 ++typeBegin;
22478 }
22479
22480 return end;
22481 };
22482
22483 // end of our iterable range will be when we reach a spellmod with different op or type than expected
22484 // we can do this because m_spellMods is sorted by op and type
22485 auto spellModTypeEnd = [&](SpellModType type)
22486 {
22487 struct EndSentinel
22488 {
22489 bool operator==(std::vector<SpellModifier*>::const_iterator const& itr) const
22490 {
22491 return itr == end || (*itr)->op != op || (*itr)->type != type;
22492 }
22493
22494 std::vector<SpellModifier*>::const_iterator end;
22495 SpellModOp op;
22496 SpellModType type;
22497 };
22498 return EndSentinel{ .end = m_spellMods.end(), .op = op, .type = type };
22499 };
22500
22501 auto spellModTypeRange = [&](SpellModType type) { return Trinity::IteratorPair(spellModTypeBegin(type), spellModTypeEnd(type)); };
22502
22503 switch (op)
22504 {
22505 // special case, if a mod makes spell instant, only consume that mod
22507 {
22508 SpellModifier* modInstantSpell = nullptr;
22509 for (SpellModifier* mod : spellModTypeRange(SPELLMOD_PCT))
22510 {
22511 if (!IsAffectedBySpellmod(spellInfo, mod, spell))
22512 continue;
22513
22514 if (base < T(10000) && static_cast<SpellModifierByClassMask*>(mod)->value <= -100)
22515 {
22516 modInstantSpell = mod;
22517 break;
22518 }
22519 }
22520
22521 if (!modInstantSpell)
22522 {
22523 for (SpellModifier* mod : spellModTypeRange(SPELLMOD_LABEL_PCT))
22524 {
22525 if (!IsAffectedBySpellmod(spellInfo, mod, spell))
22526 continue;
22527
22528 if (base < T(10000) && static_cast<SpellPctModifierByLabel*>(mod)->value.ModifierValue <= -1.0f)
22529 {
22530 modInstantSpell = mod;
22531 break;
22532 }
22533 }
22534 }
22535
22536 if (modInstantSpell)
22537 {
22538 Player::ApplyModToSpell(modInstantSpell, spell);
22539 *pct = 0.0f;
22540 return;
22541 }
22542 break;
22543 }
22544 // special case if two mods apply 100% critical chance, only consume one
22546 {
22547 SpellModifier* modCritical = nullptr;
22548 for (SpellModifier* mod : spellModTypeRange(SPELLMOD_FLAT))
22549 {
22550 if (!IsAffectedBySpellmod(spellInfo, mod, spell))
22551 continue;
22552
22553 if (static_cast<SpellModifierByClassMask*>(mod)->value >= 100)
22554 {
22555 modCritical = mod;
22556 break;
22557 }
22558 }
22559
22560 if (!modCritical)
22561 {
22562 for (SpellModifier* mod : spellModTypeRange(SPELLMOD_LABEL_FLAT))
22563 {
22564 if (!IsAffectedBySpellmod(spellInfo, mod, spell))
22565 continue;
22566
22567 if (static_cast<SpellFlatModifierByLabel*>(mod)->value.ModifierValue >= 100)
22568 {
22569 modCritical = mod;
22570 break;
22571 }
22572 }
22573 }
22574
22575 if (modCritical)
22576 {
22577 Player::ApplyModToSpell(modCritical, spell);
22578 *flat = 100;
22579 return;
22580 }
22581 break;
22582 }
22583 default:
22584 break;
22585 }
22586
22587 for (SpellModifier* mod : spellModTypeRange(SPELLMOD_FLAT))
22588 {
22589 if (!IsAffectedBySpellmod(spellInfo, mod, spell))
22590 continue;
22591
22592 int32 value = static_cast<SpellModifierByClassMask*>(mod)->value;
22593 if (value == 0)
22594 continue;
22595
22596 *flat += value;
22597 Player::ApplyModToSpell(mod, spell);
22598 }
22599
22600 for (SpellModifier* mod : spellModTypeRange(SPELLMOD_LABEL_FLAT))
22601 {
22602 if (!IsAffectedBySpellmod(spellInfo, mod, spell))
22603 continue;
22604
22605 int32 value = static_cast<SpellFlatModifierByLabel*>(mod)->value.ModifierValue;
22606 if (value == 0)
22607 continue;
22608
22609 *flat += value;
22610 Player::ApplyModToSpell(mod, spell);
22611 }
22612
22613 for (SpellModifier* mod : spellModTypeRange(SPELLMOD_PCT))
22614 {
22615 if (!IsAffectedBySpellmod(spellInfo, mod, spell))
22616 continue;
22617
22618 // skip percent mods for null basevalue (most important for spell mods with charges)
22619 if (base + *flat == T(0))
22620 continue;
22621
22622 int32 value = static_cast<SpellModifierByClassMask*>(mod)->value;
22623 if (value == 0)
22624 continue;
22625
22626 // special case (skip > 10sec spell casts for instant cast setting)
22628 {
22629 if (base >= T(10000) && value <= -100)
22630 continue;
22631 }
22632
22633 *pct *= 1.0f + CalculatePct(1.0f, value);
22634 Player::ApplyModToSpell(mod, spell);
22635 }
22636
22637 for (SpellModifier* mod : spellModTypeRange(SPELLMOD_LABEL_PCT))
22638 {
22639 if (!IsAffectedBySpellmod(spellInfo, mod, spell))
22640 continue;
22641
22642 // skip percent mods for null basevalue (most important for spell mods with charges)
22643 if (base + *flat == T(0))
22644 continue;
22645
22646 float value = static_cast<SpellPctModifierByLabel*>(mod)->value.ModifierValue;
22647 if (value == 1.0f)
22648 continue;
22649
22650 // special case (skip > 10sec spell casts for instant cast setting)
22652 {
22653 if (base >= T(10000) && value <= -1.0f)
22654 continue;
22655 }
22656
22657 *pct *= value;
22658 Player::ApplyModToSpell(mod, spell);
22659 }
22660}
22661
22662template TC_GAME_API void Player::GetSpellModValues(SpellInfo const* spellInfo, SpellModOp op, Spell* spell, int32 base, int32* flat, float* pct) const;
22663template TC_GAME_API void Player::GetSpellModValues(SpellInfo const* spellInfo, SpellModOp op, Spell* spell, uint32 base, int32* flat, float* pct) const;
22664template TC_GAME_API void Player::GetSpellModValues(SpellInfo const* spellInfo, SpellModOp op, Spell* spell, float base, int32* flat, float* pct) const;
22665template TC_GAME_API void Player::GetSpellModValues(SpellInfo const* spellInfo, SpellModOp op, Spell* spell, double base, int32* flat, float* pct) const;
22666
22667template <class T>
22668void Player::ApplySpellMod(SpellInfo const* spellInfo, SpellModOp op, T& basevalue, Spell* spell /*= nullptr*/) const
22669{
22670 float totalmul = 1.0f;
22671 int32 totalflat = 0;
22672
22673 GetSpellModValues(spellInfo, op, spell, basevalue, &totalflat, &totalmul);
22674
22675 basevalue = T(double(basevalue + totalflat) * totalmul);
22676}
22677
22678template TC_GAME_API void Player::ApplySpellMod(SpellInfo const* spellInfo, SpellModOp op, int32& basevalue, Spell* spell) const;
22679template TC_GAME_API void Player::ApplySpellMod(SpellInfo const* spellInfo, SpellModOp op, uint32& basevalue, Spell* spell) const;
22680template TC_GAME_API void Player::ApplySpellMod(SpellInfo const* spellInfo, SpellModOp op, float& basevalue, Spell* spell) const;
22681template TC_GAME_API void Player::ApplySpellMod(SpellInfo const* spellInfo, SpellModOp op, double& basevalue, Spell* spell) const;
22682
22684{
22685 TC_LOG_DEBUG("spells", "Player::AddSpellMod: Player '{}' ({}), SpellID: {}", GetName(), GetGUID().ToString(), mod->spellId);
22686
22688 if (apply)
22689 m_spellMods.insert(mod);
22690 else
22691 m_spellMods.erase(mod);
22692
22694 switch (mod->type)
22695 {
22696 case SPELLMOD_FLAT:
22697 case SPELLMOD_PCT:
22698 if (!IsLoading())
22699 {
22701
22703
22705 packet.Modifiers.resize(1);
22706 WorldPackets::Spells::SpellModifier& spellModifier = packet.Modifiers[0];
22707
22708 spellModifier.ModIndex = AsUnderlyingType(mod->op);
22709
22710 boost::dynamic_bitset<uint32> mask;
22711 mask.resize(128);
22712
22713 boost::from_block_range(
22714 &static_cast<SpellModifierByClassMask const*>(mod)->mask[0],
22715 &static_cast<SpellModifierByClassMask const*>(mod)->mask[0] + 4,
22716 mask);
22717
22718 for (std::size_t classIndex = mask.find_first(); classIndex != decltype(mask)::npos; classIndex = mask.find_next(classIndex))
22719 {
22720 WorldPackets::Spells::SpellModifierData& modData = spellModifier.ModifierData.emplace_back();
22721 if (mod->type == SPELLMOD_FLAT)
22722 {
22723 modData.ModifierValue = 0.0f;
22724 auto itr = std::ranges::lower_bound(m_spellMods, std::make_pair(mod->op, SPELLMOD_FLAT), std::ranges::less(), [](SpellModifier const* sm) { return std::make_pair(sm->op, sm->type); });
22725 while (itr != m_spellMods.end() && (*itr)->op == mod->op && (*itr)->type == SPELLMOD_FLAT)
22726 {
22727 SpellModifierByClassMask const* spellMod = static_cast<SpellModifierByClassMask const*>(*itr++);
22728 if (spellMod->mask[classIndex / 32] & (1u << (classIndex % 32)))
22729 modData.ModifierValue += spellMod->value;
22730 }
22731 }
22732 else
22733 {
22734 modData.ModifierValue = 1.0f;
22735 auto itr = std::ranges::lower_bound(m_spellMods, std::make_pair(mod->op, SPELLMOD_PCT), std::ranges::less(), [](SpellModifier const* sm) { return std::make_pair(sm->op, sm->type); });
22736 while (itr != m_spellMods.end() && (*itr)->op == mod->op && (*itr)->type == SPELLMOD_PCT)
22737 {
22738 SpellModifierByClassMask const* spellMod = static_cast<SpellModifierByClassMask const*>(*itr++);
22739 if (spellMod->mask[classIndex / 32] & (1u << (classIndex % 32)))
22740 modData.ModifierValue *= 1.0f + CalculatePct(1.0f, spellMod->value);
22741 }
22742 }
22743
22744 modData.ClassIndex = classIndex;
22745 }
22746
22747 SendDirectMessage(packet.Write());
22748 }
22749