Updated Copyright year to 2013
[getmangos.git] / src / game / Player.cpp
blobc458e61dd8f558461945fb8480c1efd813cef876
1 /*
2 * Copyright (C) 2005-2013 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include "Player.h"
20 #include "Language.h"
21 #include "Database/DatabaseEnv.h"
22 #include "Log.h"
23 #include "Opcodes.h"
24 #include "SpellMgr.h"
25 #include "World.h"
26 #include "WorldPacket.h"
27 #include "WorldSession.h"
28 #include "UpdateMask.h"
29 #include "SkillDiscovery.h"
30 #include "QuestDef.h"
31 #include "GossipDef.h"
32 #include "UpdateData.h"
33 #include "Channel.h"
34 #include "ChannelMgr.h"
35 #include "MapManager.h"
36 #include "MapPersistentStateMgr.h"
37 #include "InstanceData.h"
38 #include "GridNotifiers.h"
39 #include "GridNotifiersImpl.h"
40 #include "CellImpl.h"
41 #include "ObjectMgr.h"
42 #include "ObjectAccessor.h"
43 #include "CreatureAI.h"
44 #include "Formulas.h"
45 #include "Group.h"
46 #include "Guild.h"
47 #include "GuildMgr.h"
48 #include "Pet.h"
49 #include "Util.h"
50 #include "Transports.h"
51 #include "Weather.h"
52 #include "BattleGround/BattleGround.h"
53 #include "BattleGround/BattleGroundMgr.h"
54 #include "BattleGround/BattleGroundAV.h"
55 #include "OutdoorPvP/OutdoorPvP.h"
56 #include "ArenaTeam.h"
57 #include "Chat.h"
58 #include "Database/DatabaseImpl.h"
59 #include "Spell.h"
60 #include "ScriptMgr.h"
61 #include "SocialMgr.h"
62 #include "AchievementMgr.h"
63 #include "Mail.h"
64 #include "SpellAuras.h"
65 #include "DBCStores.h"
66 #include "DB2Stores.h"
67 #include "SQLStorages.h"
68 #include "Vehicle.h"
70 #include <cmath>
72 #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS)
74 enum CharacterFlags
76 CHARACTER_FLAG_NONE = 0x00000000,
77 CHARACTER_FLAG_UNK1 = 0x00000001,
78 CHARACTER_FLAG_UNK2 = 0x00000002,
79 CHARACTER_LOCKED_FOR_TRANSFER = 0x00000004,
80 CHARACTER_FLAG_UNK4 = 0x00000008,
81 CHARACTER_FLAG_UNK5 = 0x00000010,
82 CHARACTER_FLAG_UNK6 = 0x00000020,
83 CHARACTER_FLAG_UNK7 = 0x00000040,
84 CHARACTER_FLAG_UNK8 = 0x00000080,
85 CHARACTER_FLAG_UNK9 = 0x00000100,
86 CHARACTER_FLAG_UNK10 = 0x00000200,
87 CHARACTER_FLAG_HIDE_HELM = 0x00000400,
88 CHARACTER_FLAG_HIDE_CLOAK = 0x00000800,
89 CHARACTER_FLAG_UNK13 = 0x00001000,
90 CHARACTER_FLAG_GHOST = 0x00002000,
91 CHARACTER_FLAG_RENAME = 0x00004000,
92 CHARACTER_FLAG_UNK16 = 0x00008000,
93 CHARACTER_FLAG_UNK17 = 0x00010000,
94 CHARACTER_FLAG_UNK18 = 0x00020000,
95 CHARACTER_FLAG_UNK19 = 0x00040000,
96 CHARACTER_FLAG_UNK20 = 0x00080000,
97 CHARACTER_FLAG_UNK21 = 0x00100000,
98 CHARACTER_FLAG_UNK22 = 0x00200000,
99 CHARACTER_FLAG_UNK23 = 0x00400000,
100 CHARACTER_FLAG_UNK24 = 0x00800000,
101 CHARACTER_FLAG_LOCKED_BY_BILLING = 0x01000000,
102 CHARACTER_FLAG_DECLINED = 0x02000000,
103 CHARACTER_FLAG_UNK27 = 0x04000000,
104 CHARACTER_FLAG_UNK28 = 0x08000000,
105 CHARACTER_FLAG_UNK29 = 0x10000000,
106 CHARACTER_FLAG_UNK30 = 0x20000000,
107 CHARACTER_FLAG_UNK31 = 0x40000000,
108 CHARACTER_FLAG_UNK32 = 0x80000000
111 enum CharacterCustomizeFlags
113 CHAR_CUSTOMIZE_FLAG_NONE = 0x00000000,
114 CHAR_CUSTOMIZE_FLAG_CUSTOMIZE = 0x00000001, // name, gender, etc...
115 CHAR_CUSTOMIZE_FLAG_FACTION = 0x00010000, // name, gender, faction, etc...
116 CHAR_CUSTOMIZE_FLAG_RACE = 0x00100000 // name, gender, race, etc...
119 // corpse reclaim times
120 #define DEATH_EXPIRE_STEP (5*MINUTE)
121 #define MAX_DEATH_COUNT 3
123 static const uint32 corpseReclaimDelay[MAX_DEATH_COUNT] = {30, 60, 120};
125 //== PlayerTaxi ================================================
127 PlayerTaxi::PlayerTaxi()
129 // Taxi nodes
130 memset(m_taximask, 0, sizeof(m_taximask));
133 void PlayerTaxi::InitTaxiNodesForLevel(uint32 race, uint32 chrClass, uint32 level)
135 // class specific initial known nodes
136 switch (chrClass)
138 case CLASS_DEATH_KNIGHT:
140 for (int i = 0; i < TaxiMaskSize; ++i)
141 m_taximask[i] |= sOldContinentsNodesMask[i];
142 break;
146 // race specific initial known nodes: capital and taxi hub masks
147 switch (race)
149 case RACE_HUMAN: SetTaximaskNode(2); break; // Human
150 case RACE_ORC: SetTaximaskNode(23); break; // Orc
151 case RACE_DWARF: SetTaximaskNode(6); break; // Dwarf
152 case RACE_NIGHTELF: SetTaximaskNode(26);
153 SetTaximaskNode(27); break; // Night Elf
154 case RACE_UNDEAD: SetTaximaskNode(11); break; // Undead
155 case RACE_TAUREN: SetTaximaskNode(22); break; // Tauren
156 case RACE_GNOME: SetTaximaskNode(6); break; // Gnome
157 case RACE_TROLL: SetTaximaskNode(23); break; // Troll
158 case RACE_BLOODELF: SetTaximaskNode(82); break; // Blood Elf
159 case RACE_DRAENEI: SetTaximaskNode(94); break; // Draenei
162 // new continent starting masks (It will be accessible only at new map)
163 switch (Player::TeamForRace(race))
165 case ALLIANCE: SetTaximaskNode(100); break;
166 case HORDE: SetTaximaskNode(99); break;
167 default: break;
169 // level dependent taxi hubs
170 if (level >= 68)
171 SetTaximaskNode(213); // Shattered Sun Staging Area
174 void PlayerTaxi::LoadTaxiMask(const char* data)
176 Tokens tokens = StrSplit(data, " ");
178 int index;
179 Tokens::iterator iter;
180 for (iter = tokens.begin(), index = 0;
181 (index < TaxiMaskSize) && (iter != tokens.end()); ++iter, ++index)
183 // load and set bits only for existing taxi nodes
184 m_taximask[index] = sTaxiNodesMask[index] & uint8(atol((*iter).c_str()));
188 void PlayerTaxi::AppendTaximaskTo(ByteBuffer& data, bool all)
190 data << uint32(TaxiMaskSize);
191 if (all)
193 for (uint8 i = 0; i < TaxiMaskSize; ++i)
194 data << uint8(sTaxiNodesMask[i]); // all existing nodes
196 else
198 for (uint8 i = 0; i < TaxiMaskSize; ++i)
199 data << uint8(m_taximask[i]); // known nodes
203 bool PlayerTaxi::LoadTaxiDestinationsFromString(const std::string& values, Team team)
205 ClearTaxiDestinations();
207 Tokens tokens = StrSplit(values, " ");
209 for (Tokens::iterator iter = tokens.begin(); iter != tokens.end(); ++iter)
211 uint32 node = uint32(atol(iter->c_str()));
212 AddTaxiDestination(node);
215 if (m_TaxiDestinations.empty())
216 return true;
218 // Check integrity
219 if (m_TaxiDestinations.size() < 2)
220 return false;
222 for (size_t i = 1; i < m_TaxiDestinations.size(); ++i)
224 uint32 cost;
225 uint32 path;
226 sObjectMgr.GetTaxiPath(m_TaxiDestinations[i - 1], m_TaxiDestinations[i], path, cost);
227 if (!path)
228 return false;
231 // can't load taxi path without mount set (quest taxi path?)
232 if (!sObjectMgr.GetTaxiMountDisplayId(GetTaxiSource(), team, true))
233 return false;
235 return true;
238 std::string PlayerTaxi::SaveTaxiDestinationsToString()
240 if (m_TaxiDestinations.empty())
241 return "";
243 std::ostringstream ss;
245 for (size_t i = 0; i < m_TaxiDestinations.size(); ++i)
246 ss << m_TaxiDestinations[i] << " ";
248 return ss.str();
251 uint32 PlayerTaxi::GetCurrentTaxiPath() const
253 if (m_TaxiDestinations.size() < 2)
254 return 0;
256 uint32 path;
257 uint32 cost;
259 sObjectMgr.GetTaxiPath(m_TaxiDestinations[0], m_TaxiDestinations[1], path, cost);
261 return path;
264 std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi)
266 for (int i = 0; i < TaxiMaskSize; ++i)
267 ss << uint32(taxi.m_taximask[i]) << " "; // cast to prevent conversion to char
268 return ss;
271 //== TradeData =================================================
273 TradeData* TradeData::GetTraderData() const
275 return m_trader->GetTradeData();
278 Item* TradeData::GetItem(TradeSlots slot) const
280 return m_items[slot] ? m_player->GetItemByGuid(m_items[slot]) : NULL;
283 bool TradeData::HasItem(ObjectGuid item_guid) const
285 for (int i = 0; i < TRADE_SLOT_COUNT; ++i)
286 if (m_items[i] == item_guid)
287 return true;
288 return false;
292 Item* TradeData::GetSpellCastItem() const
294 return m_spellCastItem ? m_player->GetItemByGuid(m_spellCastItem) : NULL;
297 void TradeData::SetItem(TradeSlots slot, Item* item)
299 ObjectGuid itemGuid = item ? item->GetObjectGuid() : ObjectGuid();
301 if (m_items[slot] == itemGuid)
302 return;
304 m_items[slot] = itemGuid;
306 SetAccepted(false);
307 GetTraderData()->SetAccepted(false);
309 Update();
311 // need remove possible trader spell applied to changed item
312 if (slot == TRADE_SLOT_NONTRADED)
313 GetTraderData()->SetSpell(0);
315 // need remove possible player spell applied (possible move reagent)
316 SetSpell(0);
319 void TradeData::SetSpell(uint32 spell_id, Item* castItem /*= NULL*/)
321 ObjectGuid itemGuid = castItem ? castItem->GetObjectGuid() : ObjectGuid();
323 if (m_spell == spell_id && m_spellCastItem == itemGuid)
324 return;
326 m_spell = spell_id;
327 m_spellCastItem = itemGuid;
329 SetAccepted(false);
330 GetTraderData()->SetAccepted(false);
332 Update(true); // send spell info to item owner
333 Update(false); // send spell info to caster self
336 void TradeData::SetMoney(uint64 money)
338 if (m_money == money)
339 return;
341 m_money = money;
343 SetAccepted(false);
344 GetTraderData()->SetAccepted(false);
346 Update();
349 void TradeData::Update(bool for_trader /*= true*/)
351 if (for_trader)
352 m_trader->GetSession()->SendUpdateTrade(true); // player state for trader
353 else
354 m_player->GetSession()->SendUpdateTrade(false); // player state for player
357 void TradeData::SetAccepted(bool state, bool crosssend /*= false*/)
359 m_accepted = state;
361 if (!state)
363 if (crosssend)
364 m_trader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE);
365 else
366 m_player->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE);
370 //== Player ====================================================
372 UpdateMask Player::updateVisualBits;
374 Player::Player(WorldSession* session): Unit(), m_mover(this), m_camera(this), m_achievementMgr(this), m_reputationMgr(this)
376 m_transport = 0;
378 m_speakTime = 0;
379 m_speakCount = 0;
381 m_objectType |= TYPEMASK_PLAYER;
382 m_objectTypeId = TYPEID_PLAYER;
384 m_valuesCount = PLAYER_END;
386 SetActiveObjectState(true); // player is always active object
388 m_session = session;
390 m_ExtraFlags = 0;
391 if (GetSession()->GetSecurity() >= SEC_GAMEMASTER)
392 SetAcceptTicket(true);
394 // players always accept
395 if (GetSession()->GetSecurity() == SEC_PLAYER)
396 SetAcceptWhispers(true);
398 m_comboPoints = 0;
400 m_usedTalentCount = 0;
401 m_questRewardTalentCount = 0;
402 m_freeTalentPoints = 0;
404 m_regenTimer = 0;
405 m_holyPowerRegenTimer = REGEN_TIME_HOLY_POWER;
406 m_weaponChangeTimer = 0;
408 m_zoneUpdateId = 0;
409 m_zoneUpdateTimer = 0;
411 m_areaUpdateId = 0;
413 m_nextSave = sWorld.getConfig(CONFIG_UINT32_INTERVAL_SAVE);
415 // randomize first save time in range [CONFIG_UINT32_INTERVAL_SAVE] around [CONFIG_UINT32_INTERVAL_SAVE]
416 // this must help in case next save after mass player load after server startup
417 m_nextSave = urand(m_nextSave / 2, m_nextSave * 3 / 2);
419 clearResurrectRequestData();
421 memset(m_items, 0, sizeof(Item*)*PLAYER_SLOTS_COUNT);
423 m_social = NULL;
425 // group is initialized in the reference constructor
426 SetGroupInvite(NULL);
427 m_groupUpdateMask = 0;
428 m_auraUpdateMask = 0;
430 duel = NULL;
432 m_GuildIdInvited = 0;
433 m_ArenaTeamIdInvited = 0;
435 m_atLoginFlags = AT_LOGIN_NONE;
437 mSemaphoreTeleport_Near = false;
438 mSemaphoreTeleport_Far = false;
440 m_DelayedOperations = 0;
441 m_bCanDelayTeleport = false;
442 m_bHasDelayedTeleport = false;
443 m_bHasBeenAliveAtDelayedTeleport = true; // overwrite always at setup teleport data, so not used infact
444 m_teleport_options = 0;
446 m_trade = NULL;
448 m_cinematic = 0;
450 PlayerTalkClass = new PlayerMenu(GetSession());
451 m_currentBuybackSlot = BUYBACK_SLOT_START;
453 m_DailyQuestChanged = false;
454 m_WeeklyQuestChanged = false;
456 for (int i = 0; i < MAX_TIMERS; ++i)
457 m_MirrorTimer[i] = DISABLED_MIRROR_TIMER;
459 m_MirrorTimerFlags = UNDERWATER_NONE;
460 m_MirrorTimerFlagsLast = UNDERWATER_NONE;
462 m_isInWater = false;
463 m_drunkTimer = 0;
464 m_drunk = 0;
465 m_restTime = 0;
466 m_deathTimer = 0;
467 m_deathExpireTime = 0;
469 m_swingErrorMsg = 0;
471 m_DetectInvTimer = 1 * IN_MILLISECONDS;
473 for (int j = 0; j < PLAYER_MAX_BATTLEGROUND_QUEUES; ++j)
475 m_bgBattleGroundQueueID[j].bgQueueTypeId = BATTLEGROUND_QUEUE_NONE;
476 m_bgBattleGroundQueueID[j].invitedToInstance = 0;
479 m_logintime = time(NULL);
480 m_Last_tick = m_logintime;
481 m_WeaponProficiency = 0;
482 m_ArmorProficiency = 0;
483 m_canParry = false;
484 m_canBlock = false;
485 m_canDualWield = false;
486 m_canTitanGrip = false;
487 m_ammoDPS = 0.0f;
489 m_temporaryUnsummonedPetNumber = 0;
491 //////////////////// Rest System/////////////////////
492 time_inn_enter = 0;
493 inn_trigger_id = 0;
494 m_rest_bonus = 0;
495 rest_type = REST_TYPE_NO;
496 //////////////////// Rest System/////////////////////
498 m_mailsUpdated = false;
499 unReadMails = 0;
500 m_nextMailDelivereTime = 0;
502 m_resetTalentsCost = 0;
503 m_resetTalentsTime = 0;
504 m_itemUpdateQueueBlocked = false;
506 for (int i = 0; i < MAX_MOVE_TYPE; ++i)
507 m_forced_speed_changes[i] = 0;
509 m_stableSlots = 0;
511 /////////////////// Instance System /////////////////////
513 m_HomebindTimer = 0;
514 m_InstanceValid = true;
515 m_dungeonDifficulty = DUNGEON_DIFFICULTY_NORMAL;
516 m_raidDifficulty = RAID_DIFFICULTY_10MAN_NORMAL;
518 m_lastPotionId = 0;
520 m_activeSpec = 0;
521 m_specsCount = 1;
522 for (int i = 0; i < MAX_TALENT_SPEC_COUNT; ++i)
523 m_talentsPrimaryTree[i] = 0;
525 for (int i = 0; i < BASEMOD_END; ++i)
527 m_auraBaseMod[i][FLAT_MOD] = 0.0f;
528 m_auraBaseMod[i][PCT_MOD] = 1.0f;
531 for (int i = 0; i < MAX_COMBAT_RATING; ++i)
532 m_baseRatingValue[i] = 0;
534 m_baseSpellPower = 0;
535 m_baseHealthRegen = 0;
536 m_baseManaRegen = 0;
537 m_armorPenetrationPct = 0.0f;
538 m_spellPenetrationItemMod = 0;
540 m_lastHonorKillsUpdateTime = time(NULL);
542 // Player summoning
543 m_summon_expire = 0;
544 m_summon_mapid = 0;
545 m_summon_x = 0.0f;
546 m_summon_y = 0.0f;
547 m_summon_z = 0.0f;
549 m_contestedPvPTimer = 0;
551 m_declinedname = NULL;
552 m_runes = NULL;
554 m_lastFallTime = 0;
555 m_lastFallZ = 0;
557 m_cachedGS = 0;
559 m_slot = 255;
562 Player::~Player()
564 CleanupsBeforeDelete();
566 // it must be unloaded already in PlayerLogout and accessed only for loggined player
567 // m_social = NULL;
569 // Note: buy back item already deleted from DB when player was saved
570 for (int i = 0; i < PLAYER_SLOTS_COUNT; ++i)
572 delete m_items[i];
574 CleanupChannels();
576 // all mailed items should be deleted, also all mail should be deallocated
577 for (PlayerMails::const_iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
578 delete *itr;
580 for (ItemMap::const_iterator iter = mMitems.begin(); iter != mMitems.end(); ++iter)
581 delete iter->second; // if item is duplicated... then server may crash ... but that item should be deallocated
583 delete PlayerTalkClass;
585 if (m_transport)
587 m_transport->RemovePassenger(this);
590 for (size_t x = 0; x < ItemSetEff.size(); ++x)
591 delete ItemSetEff[x];
593 // clean up player-instance binds, may unload some instance saves
594 for (uint8 i = 0; i < MAX_DIFFICULTY; ++i)
595 for (BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr)
596 itr->second.state->RemovePlayer(this);
598 delete m_declinedname;
599 delete m_runes;
602 void Player::CleanupsBeforeDelete()
604 if (m_uint32Values) // only for fully created Object
606 TradeCancel(false);
607 DuelComplete(DUEL_INTERRUPTED);
610 // notify zone scripts for player logout
611 sOutdoorPvPMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId);
613 Unit::CleanupsBeforeDelete();
616 bool Player::Create(uint32 guidlow, const std::string& name, uint8 race, uint8 class_, uint8 gender, uint8 skin, uint8 face, uint8 hairStyle, uint8 hairColor, uint8 facialHair, uint8 /*outfitId */)
618 // FIXME: outfitId not used in player creating
620 Object::_Create(guidlow, 0, HIGHGUID_PLAYER);
622 m_name = name;
624 PlayerInfo const* info = sObjectMgr.GetPlayerInfo(race, class_);
625 if (!info)
627 sLog.outError("Player have incorrect race/class pair. Can't be loaded.");
628 return false;
631 ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(class_);
632 if (!cEntry)
634 sLog.outError("Class %u not found in DBC (Wrong DBC files?)", class_);
635 return false;
638 // player store gender in single bit
639 if (gender != uint8(GENDER_MALE) && gender != uint8(GENDER_FEMALE))
641 sLog.outError("Invalid gender %u at player creating", uint32(gender));
642 return false;
645 for (int i = 0; i < PLAYER_SLOTS_COUNT; ++i)
646 m_items[i] = NULL;
648 SetLocationMapId(info->mapId);
649 Relocate(info->positionX, info->positionY, info->positionZ, info->orientation);
651 SetMap(sMapMgr.CreateMap(info->mapId, this));
653 uint8 powertype = cEntry->powerType;
655 setFactionForRace(race);
657 SetByteValue(UNIT_FIELD_BYTES_0, 0, race);
658 SetByteValue(UNIT_FIELD_BYTES_0, 1, class_);
659 SetByteValue(UNIT_FIELD_BYTES_0, 2, gender);
660 SetByteValue(UNIT_FIELD_BYTES_0, 3, powertype);
662 InitDisplayIds(); // model, scale and model data
664 SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_PVP);
665 SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE);
666 SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_REGENERATE_POWER);
667 SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f); // fix cast time showed in spell tooltip on client
668 SetFloatValue(UNIT_FIELD_HOVERHEIGHT, 1.0f); // default for players in 3.0.3
670 SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, -1); // -1 is default value
672 SetByteValue(PLAYER_BYTES, 0, skin);
673 SetByteValue(PLAYER_BYTES, 1, face);
674 SetByteValue(PLAYER_BYTES, 2, hairStyle);
675 SetByteValue(PLAYER_BYTES, 3, hairColor);
677 SetByteValue(PLAYER_BYTES_2, 0, facialHair);
678 SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_NORMAL);
680 SetUInt16Value(PLAYER_BYTES_3, 0, gender); // only GENDER_MALE/GENDER_FEMALE (1 bit) allowed, drunk state = 0
681 SetByteValue(PLAYER_BYTES_3, 3, 0); // BattlefieldArenaFaction (0 or 1)
683 SetInGuild(0);
684 SetGuildLevel(0);
685 SetUInt32Value(PLAYER_GUILDRANK, 0);
686 SetUInt32Value(PLAYER_GUILD_TIMESTAMP, 0);
688 for (int i = 0; i < KNOWN_TITLES_SIZE; ++i)
689 SetUInt64Value(PLAYER__FIELD_KNOWN_TITLES + i, 0); // 0=disabled
690 SetUInt32Value(PLAYER_CHOSEN_TITLE, 0);
692 SetUInt32Value(PLAYER_FIELD_KILLS, 0);
693 SetUInt32Value(PLAYER_FIELD_LIFETIME_HONORBALE_KILLS, 0);
695 // set starting level
696 uint32 start_level = getClass() != CLASS_DEATH_KNIGHT
697 ? sWorld.getConfig(CONFIG_UINT32_START_PLAYER_LEVEL)
698 : sWorld.getConfig(CONFIG_UINT32_START_HEROIC_PLAYER_LEVEL);
700 if (GetSession()->GetSecurity() >= SEC_MODERATOR)
702 uint32 gm_level = sWorld.getConfig(CONFIG_UINT32_START_GM_LEVEL);
703 if (gm_level > start_level)
704 start_level = gm_level;
707 SetUInt32Value(UNIT_FIELD_LEVEL, start_level);
709 InitRunes();
711 SetUInt32Value(PLAYER_FIELD_COINAGE, sWorld.getConfig(CONFIG_UINT32_START_PLAYER_MONEY));
712 SetCurrencyCount(CURRENCY_HONOR_POINTS,sWorld.getConfig(CONFIG_UINT32_CURRENCY_START_HONOR_POINTS));
713 SetCurrencyCount(CURRENCY_CONQUEST_POINTS, sWorld.getConfig(CONFIG_UINT32_CURRENCY_START_CONQUEST_POINTS));
715 // Played time
716 m_Last_tick = time(NULL);
717 m_Played_time[PLAYED_TIME_TOTAL] = 0;
718 m_Played_time[PLAYED_TIME_LEVEL] = 0;
720 // base stats and related field values
721 InitStatsForLevel();
722 InitTaxiNodesForLevel();
723 InitGlyphsForLevel();
724 InitTalentForLevel();
725 InitPrimaryProfessions(); // to max set before any spell added
727 // apply original stats mods before spell loading or item equipment that call before equip _RemoveStatsMods()
728 UpdateMaxHealth(); // Update max Health (for add bonus from stamina)
729 SetHealth(GetMaxHealth());
731 if (getPowerType() == POWER_MANA)
733 UpdateMaxPower(POWER_MANA); // Update max Mana (for add bonus from intellect)
734 SetPower(POWER_MANA, GetMaxPower(POWER_MANA));
737 if (getPowerType() != POWER_MANA) // hide additional mana bar if we have no mana
739 SetPower(POWER_MANA, 0);
740 SetMaxPower(POWER_MANA, 0);
743 // original spells
744 learnDefaultSpells();
746 // original action bar
747 for (PlayerCreateInfoActions::const_iterator action_itr = info->action.begin(); action_itr != info->action.end(); ++action_itr)
748 addActionButton(0, action_itr->button, action_itr->action, action_itr->type);
750 // original items
751 uint32 raceClassGender = GetUInt32Value(UNIT_FIELD_BYTES_0) & 0x00FFFFFF;
753 CharStartOutfitEntry const* oEntry = NULL;
754 for (uint32 i = 1; i < sCharStartOutfitStore.GetNumRows(); ++i)
756 if (CharStartOutfitEntry const* entry = sCharStartOutfitStore.LookupEntry(i))
758 if (entry->RaceClassGender == raceClassGender)
760 oEntry = entry;
761 break;
766 if (oEntry)
768 for (int j = 0; j < MAX_OUTFIT_ITEMS; ++j)
770 if (oEntry->ItemId[j] <= 0)
771 continue;
773 uint32 item_id = oEntry->ItemId[j];
775 // just skip, reported in ObjectMgr::LoadItemPrototypes
776 ItemPrototype const* iProto = ObjectMgr::GetItemPrototype(item_id);
777 if (!iProto)
778 continue;
780 // BuyCount by default
781 int32 count = iProto->BuyCount;
783 // special amount for foor/drink
784 if (iProto->Class == ITEM_CLASS_CONSUMABLE && iProto->SubClass == ITEM_SUBCLASS_FOOD)
786 switch (iProto->Spells[0].SpellCategory)
788 case 11: // food
789 count = getClass() == CLASS_DEATH_KNIGHT ? 10 : 4;
790 break;
791 case 59: // drink
792 count = 2;
793 break;
795 if (iProto->Stackable < count)
796 count = iProto->Stackable;
799 StoreNewItemInBestSlots(item_id, count);
803 for (PlayerCreateInfoItems::const_iterator item_id_itr = info->item.begin(); item_id_itr != info->item.end(); ++item_id_itr)
804 StoreNewItemInBestSlots(item_id_itr->item_id, item_id_itr->item_amount);
806 // bags and main-hand weapon must equipped at this moment
807 // now second pass for not equipped (offhand weapon/shield if it attempt equipped before main-hand weapon)
808 // or ammo not equipped in special bag
809 for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
811 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
813 uint16 eDest;
814 // equip offhand weapon/shield if it attempt equipped before main-hand weapon
815 InventoryResult msg = CanEquipItem(NULL_SLOT, eDest, pItem, false);
816 if (msg == EQUIP_ERR_OK)
818 RemoveItem(INVENTORY_SLOT_BAG_0, i, true);
819 EquipItem(eDest, pItem, true);
821 // move other items to more appropriate slots (ammo not equipped in special bag)
822 else
824 ItemPosCountVec sDest;
825 msg = CanStoreItem(NULL_BAG, NULL_SLOT, sDest, pItem, false);
826 if (msg == EQUIP_ERR_OK)
828 RemoveItem(INVENTORY_SLOT_BAG_0, i, true);
829 pItem = StoreItem(sDest, pItem, true);
832 // if this is ammo then use it
833 msg = CanUseAmmo(pItem->GetEntry());
834 if (msg == EQUIP_ERR_OK)
835 SetAmmo(pItem->GetEntry());
839 // all item positions resolved
841 if (info->phaseMap != 0)
842 CharacterDatabase.PExecute("REPLACE INTO `character_phase_data` (`guid`, `map`) VALUES (%u, %u)", guidlow, info->phaseMap);
844 return true;
847 bool Player::StoreNewItemInBestSlots(uint32 titem_id, uint32 titem_amount)
849 DEBUG_LOG("STORAGE: Creating initial item, itemId = %u, count = %u", titem_id, titem_amount);
851 // attempt equip by one
852 while (titem_amount > 0)
854 uint16 eDest;
855 uint8 msg = CanEquipNewItem(NULL_SLOT, eDest, titem_id, false);
856 if (msg != EQUIP_ERR_OK)
857 break;
859 EquipNewItem(eDest, titem_id, true);
860 AutoUnequipOffhandIfNeed();
861 --titem_amount;
864 if (titem_amount == 0)
865 return true; // equipped
867 // attempt store
868 ItemPosCountVec sDest;
869 // store in main bag to simplify second pass (special bags can be not equipped yet at this moment)
870 uint8 msg = CanStoreNewItem(INVENTORY_SLOT_BAG_0, NULL_SLOT, sDest, titem_id, titem_amount);
871 if (msg == EQUIP_ERR_OK)
873 StoreNewItem(sDest, titem_id, true, Item::GenerateItemRandomPropertyId(titem_id));
874 return true; // stored
877 // item can't be added
878 sLog.outError("STORAGE: Can't equip or store initial item %u for race %u class %u , error msg = %u", titem_id, getRace(), getClass(), msg);
879 return false;
882 // helper function, mainly for script side, but can be used for simple task in mangos also.
883 Item* Player::StoreNewItemInInventorySlot(uint32 itemEntry, uint32 amount)
885 ItemPosCountVec vDest;
887 uint8 msg = CanStoreNewItem(INVENTORY_SLOT_BAG_0, NULL_SLOT, vDest, itemEntry, amount);
889 if (msg == EQUIP_ERR_OK)
891 if (Item* pItem = StoreNewItem(vDest, itemEntry, true, Item::GenerateItemRandomPropertyId(itemEntry)))
892 return pItem;
895 return NULL;
898 void Player::SendMirrorTimer(MirrorTimerType Type, uint32 MaxValue, uint32 CurrentValue, int32 Regen)
900 if (int(MaxValue) == DISABLED_MIRROR_TIMER)
902 if (int(CurrentValue) != DISABLED_MIRROR_TIMER)
903 StopMirrorTimer(Type);
904 return;
906 WorldPacket data(SMSG_START_MIRROR_TIMER, (21));
907 data << (uint32)Type;
908 data << CurrentValue;
909 data << MaxValue;
910 data << Regen;
911 data << (uint8)0;
912 data << (uint32)0; // spell id
913 GetSession()->SendPacket(&data);
916 void Player::StopMirrorTimer(MirrorTimerType Type)
918 m_MirrorTimer[Type] = DISABLED_MIRROR_TIMER;
919 WorldPacket data(SMSG_STOP_MIRROR_TIMER, 4);
920 data << (uint32)Type;
921 GetSession()->SendPacket(&data);
924 uint32 Player::EnvironmentalDamage(EnviromentalDamage type, uint32 damage)
926 if (!isAlive() || isGameMaster())
927 return 0;
929 // Absorb, resist some environmental damage type
930 uint32 absorb = 0;
931 uint32 resist = 0;
932 if (type == DAMAGE_LAVA)
933 CalculateDamageAbsorbAndResist(this, SPELL_SCHOOL_MASK_FIRE, DIRECT_DAMAGE, damage, &absorb, &resist);
934 else if (type == DAMAGE_SLIME)
935 CalculateDamageAbsorbAndResist(this, SPELL_SCHOOL_MASK_NATURE, DIRECT_DAMAGE, damage, &absorb, &resist);
937 damage -= absorb + resist;
939 DealDamageMods(this, damage, &absorb);
941 WorldPacket data(SMSG_ENVIRONMENTALDAMAGELOG, (21));
942 data << GetObjectGuid();
943 data << uint8(type != DAMAGE_FALL_TO_VOID ? type : DAMAGE_FALL);
944 data << uint32(damage);
945 data << uint32(absorb);
946 data << uint32(resist);
947 SendMessageToSet(&data, true);
949 uint32 final_damage = DealDamage(this, damage, NULL, SELF_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false);
951 if (!isAlive())
953 if (type == DAMAGE_FALL) // DealDamage not apply item durability loss at self damage
955 DEBUG_LOG("We are fall to death, loosing 10 percents durability");
956 DurabilityLossAll(0.10f, false);
957 // durability lost message
958 WorldPacket data2(SMSG_DURABILITY_DAMAGE_DEATH, 0);
959 GetSession()->SendPacket(&data2);
962 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATHS_FROM, 1, type);
965 return final_damage;
968 int32 Player::getMaxTimer(MirrorTimerType timer)
970 switch (timer)
972 case FATIGUE_TIMER:
973 if (GetSession()->GetSecurity() >= (AccountTypes)sWorld.getConfig(CONFIG_UINT32_TIMERBAR_FATIGUE_GMLEVEL))
974 return DISABLED_MIRROR_TIMER;
975 return sWorld.getConfig(CONFIG_UINT32_TIMERBAR_FATIGUE_MAX) * IN_MILLISECONDS;
976 case BREATH_TIMER:
978 if (!isAlive() || HasAuraType(SPELL_AURA_WATER_BREATHING) ||
979 GetSession()->GetSecurity() >= (AccountTypes)sWorld.getConfig(CONFIG_UINT32_TIMERBAR_BREATH_GMLEVEL))
980 return DISABLED_MIRROR_TIMER;
981 int32 UnderWaterTime = sWorld.getConfig(CONFIG_UINT32_TIMERBAR_BREATH_MAX) * IN_MILLISECONDS;
982 AuraList const& mModWaterBreathing = GetAurasByType(SPELL_AURA_MOD_WATER_BREATHING);
983 for (AuraList::const_iterator i = mModWaterBreathing.begin(); i != mModWaterBreathing.end(); ++i)
984 UnderWaterTime = uint32(UnderWaterTime * (100.0f + (*i)->GetModifier()->m_amount) / 100.0f);
985 return UnderWaterTime;
987 case FIRE_TIMER:
989 if (!isAlive() || GetSession()->GetSecurity() >= (AccountTypes)sWorld.getConfig(CONFIG_UINT32_TIMERBAR_FIRE_GMLEVEL))
990 return DISABLED_MIRROR_TIMER;
991 return sWorld.getConfig(CONFIG_UINT32_TIMERBAR_FIRE_MAX) * IN_MILLISECONDS;
993 default:
994 return 0;
996 return 0;
999 void Player::UpdateMirrorTimers()
1001 // Desync flags for update on next HandleDrowning
1002 if (m_MirrorTimerFlags)
1003 m_MirrorTimerFlagsLast = ~m_MirrorTimerFlags;
1006 void Player::HandleDrowning(uint32 time_diff)
1008 if (!m_MirrorTimerFlags)
1009 return;
1011 // In water
1012 if (m_MirrorTimerFlags & UNDERWATER_INWATER)
1014 // Breath timer not activated - activate it
1015 if (m_MirrorTimer[BREATH_TIMER] == DISABLED_MIRROR_TIMER)
1017 m_MirrorTimer[BREATH_TIMER] = getMaxTimer(BREATH_TIMER);
1018 SendMirrorTimer(BREATH_TIMER, m_MirrorTimer[BREATH_TIMER], m_MirrorTimer[BREATH_TIMER], -1);
1020 else
1022 m_MirrorTimer[BREATH_TIMER] -= time_diff;
1023 // Timer limit - need deal damage
1024 if (m_MirrorTimer[BREATH_TIMER] < 0)
1026 m_MirrorTimer[BREATH_TIMER] += 2 * IN_MILLISECONDS;
1027 // Calculate and deal damage
1028 // TODO: Check this formula
1029 uint32 damage = GetMaxHealth() / 5 + urand(0, getLevel() - 1);
1030 EnvironmentalDamage(DAMAGE_DROWNING, damage);
1032 else if (!(m_MirrorTimerFlagsLast & UNDERWATER_INWATER)) // Update time in client if need
1033 SendMirrorTimer(BREATH_TIMER, getMaxTimer(BREATH_TIMER), m_MirrorTimer[BREATH_TIMER], -1);
1036 else if (m_MirrorTimer[BREATH_TIMER] != DISABLED_MIRROR_TIMER) // Regen timer
1038 int32 UnderWaterTime = getMaxTimer(BREATH_TIMER);
1039 // Need breath regen
1040 m_MirrorTimer[BREATH_TIMER] += 10 * time_diff;
1041 if (m_MirrorTimer[BREATH_TIMER] >= UnderWaterTime || !isAlive())
1042 StopMirrorTimer(BREATH_TIMER);
1043 else if (m_MirrorTimerFlagsLast & UNDERWATER_INWATER)
1044 SendMirrorTimer(BREATH_TIMER, UnderWaterTime, m_MirrorTimer[BREATH_TIMER], 10);
1047 // In dark water
1048 if (m_MirrorTimerFlags & UNDERWATER_INDARKWATER)
1050 // Fatigue timer not activated - activate it
1051 if (m_MirrorTimer[FATIGUE_TIMER] == DISABLED_MIRROR_TIMER)
1053 m_MirrorTimer[FATIGUE_TIMER] = getMaxTimer(FATIGUE_TIMER);
1054 SendMirrorTimer(FATIGUE_TIMER, m_MirrorTimer[FATIGUE_TIMER], m_MirrorTimer[FATIGUE_TIMER], -1);
1056 else
1058 m_MirrorTimer[FATIGUE_TIMER] -= time_diff;
1059 // Timer limit - need deal damage or teleport ghost to graveyard
1060 if (m_MirrorTimer[FATIGUE_TIMER] < 0)
1062 m_MirrorTimer[FATIGUE_TIMER] += 2 * IN_MILLISECONDS;
1063 if (isAlive()) // Calculate and deal damage
1065 uint32 damage = GetMaxHealth() / 5 + urand(0, getLevel() - 1);
1066 EnvironmentalDamage(DAMAGE_EXHAUSTED, damage);
1068 else if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) // Teleport ghost to graveyard
1069 RepopAtGraveyard();
1071 else if (!(m_MirrorTimerFlagsLast & UNDERWATER_INDARKWATER))
1072 SendMirrorTimer(FATIGUE_TIMER, getMaxTimer(FATIGUE_TIMER), m_MirrorTimer[FATIGUE_TIMER], -1);
1075 else if (m_MirrorTimer[FATIGUE_TIMER] != DISABLED_MIRROR_TIMER) // Regen timer
1077 int32 DarkWaterTime = getMaxTimer(FATIGUE_TIMER);
1078 m_MirrorTimer[FATIGUE_TIMER] += 10 * time_diff;
1079 if (m_MirrorTimer[FATIGUE_TIMER] >= DarkWaterTime || !isAlive())
1080 StopMirrorTimer(FATIGUE_TIMER);
1081 else if (m_MirrorTimerFlagsLast & UNDERWATER_INDARKWATER)
1082 SendMirrorTimer(FATIGUE_TIMER, DarkWaterTime, m_MirrorTimer[FATIGUE_TIMER], 10);
1085 if (m_MirrorTimerFlags & (UNDERWATER_INLAVA | UNDERWATER_INSLIME))
1087 // Breath timer not activated - activate it
1088 if (m_MirrorTimer[FIRE_TIMER] == DISABLED_MIRROR_TIMER)
1089 m_MirrorTimer[FIRE_TIMER] = getMaxTimer(FIRE_TIMER);
1090 else
1092 m_MirrorTimer[FIRE_TIMER] -= time_diff;
1093 if (m_MirrorTimer[FIRE_TIMER] < 0)
1095 m_MirrorTimer[FIRE_TIMER] += 2 * IN_MILLISECONDS;
1096 // Calculate and deal damage
1097 // TODO: Check this formula
1098 uint32 damage = urand(600, 700);
1099 if (m_MirrorTimerFlags & UNDERWATER_INLAVA)
1100 EnvironmentalDamage(DAMAGE_LAVA, damage);
1101 // need to skip Slime damage in Undercity,
1102 // maybe someone can find better way to handle environmental damage
1103 else if (m_zoneUpdateId != 1497)
1104 EnvironmentalDamage(DAMAGE_SLIME, damage);
1108 else
1109 m_MirrorTimer[FIRE_TIMER] = DISABLED_MIRROR_TIMER;
1111 // Recheck timers flag
1112 m_MirrorTimerFlags &= ~UNDERWATER_EXIST_TIMERS;
1113 for (int i = 0; i < MAX_TIMERS; ++i)
1114 if (m_MirrorTimer[i] != DISABLED_MIRROR_TIMER)
1116 m_MirrorTimerFlags |= UNDERWATER_EXIST_TIMERS;
1117 break;
1119 m_MirrorTimerFlagsLast = m_MirrorTimerFlags;
1122 /// The player sobers by 256 every 10 seconds
1123 void Player::HandleSobering()
1125 m_drunkTimer = 0;
1127 uint32 drunk = (m_drunk <= 256) ? 0 : (m_drunk - 256);
1128 SetDrunkValue(drunk);
1131 DrunkenState Player::GetDrunkenstateByValue(uint16 value)
1133 if (value >= 23000)
1134 return DRUNKEN_SMASHED;
1135 if (value >= 12800)
1136 return DRUNKEN_DRUNK;
1137 if (value & 0xFFFE)
1138 return DRUNKEN_TIPSY;
1139 return DRUNKEN_SOBER;
1142 void Player::SetDrunkValue(uint16 newDrunkenValue, uint32 itemId)
1144 uint32 oldDrunkenState = Player::GetDrunkenstateByValue(m_drunk);
1146 m_drunk = newDrunkenValue;
1147 SetUInt16Value(PLAYER_BYTES_3, 0, uint16(getGender()) | (m_drunk & 0xFFFE));
1149 uint32 newDrunkenState = Player::GetDrunkenstateByValue(m_drunk);
1151 // special drunk invisibility detection
1152 if (newDrunkenState >= DRUNKEN_DRUNK)
1153 m_detectInvisibilityMask |= (1 << 6);
1154 else
1155 m_detectInvisibilityMask &= ~(1 << 6);
1157 if (newDrunkenState == oldDrunkenState)
1158 return;
1160 WorldPacket data(SMSG_CROSSED_INEBRIATION_THRESHOLD, (8 + 4 + 4));
1161 data << GetObjectGuid();
1162 data << uint32(newDrunkenState);
1163 data << uint32(itemId);
1165 SendMessageToSet(&data, true);
1168 void Player::Update(uint32 update_diff, uint32 p_time)
1170 if (!IsInWorld())
1171 return;
1173 // Remove failed timed Achievements
1174 GetAchievementMgr().DoFailedTimedAchievementCriterias();
1176 // Undelivered mail
1177 if (m_nextMailDelivereTime && m_nextMailDelivereTime <= time(NULL))
1179 SendNewMail();
1180 ++unReadMails;
1182 // It will be recalculate at mailbox open (for unReadMails important non-0 until mailbox open, it also will be recalculated)
1183 m_nextMailDelivereTime = 0;
1186 // Used to implement delayed far teleports
1187 SetCanDelayTeleport(true);
1188 Unit::Update(update_diff, p_time);
1189 SetCanDelayTeleport(false);
1191 // Update player only attacks
1192 if (uint32 ranged_att = getAttackTimer(RANGED_ATTACK))
1193 setAttackTimer(RANGED_ATTACK, (update_diff >= ranged_att ? 0 : ranged_att - update_diff));
1195 time_t now = time(NULL);
1197 UpdatePvPFlag(now);
1199 UpdateContestedPvP(update_diff);
1201 UpdateDuelFlag(now);
1203 CheckDuelDistance(now);
1205 UpdateAfkReport(now);
1207 // Update items that have just a limited lifetime
1208 if (now > m_Last_tick)
1209 UpdateItemDuration(uint32(now - m_Last_tick));
1211 if (!m_timedquests.empty())
1213 QuestSet::iterator iter = m_timedquests.begin();
1214 while (iter != m_timedquests.end())
1216 QuestStatusData& q_status = mQuestStatus[*iter];
1217 if (q_status.m_timer <= update_diff)
1219 uint32 quest_id = *iter;
1220 ++iter; // Current iter will be removed in FailQuest
1221 FailQuest(quest_id);
1223 else
1225 q_status.m_timer -= update_diff;
1226 if (q_status.uState != QUEST_NEW) q_status.uState = QUEST_CHANGED;
1227 ++iter;
1232 if (hasUnitState(UNIT_STAT_MELEE_ATTACKING))
1234 UpdateMeleeAttackingState();
1236 Unit* pVictim = getVictim();
1237 if (pVictim && !IsNonMeleeSpellCasted(false))
1239 Player* vOwner = pVictim->GetCharmerOrOwnerPlayerOrPlayerItself();
1240 if (vOwner && vOwner->IsPvP() && !IsInDuelWith(vOwner))
1242 UpdatePvP(true);
1243 RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT);
1248 if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING))
1250 if (roll_chance_i(3) && GetTimeInnEnter() > 0) // Freeze update
1252 time_t time_inn = time(NULL) - GetTimeInnEnter();
1253 if (time_inn >= 10) // Freeze update
1255 float bubble = 0.125f * sWorld.getConfig(CONFIG_FLOAT_RATE_REST_INGAME);
1256 // Speed collect rest bonus (section/in hour)
1257 SetRestBonus(float(GetRestBonus() + time_inn * (GetUInt32Value(PLAYER_NEXT_LEVEL_XP) / 72000) * bubble));
1258 UpdateInnerTime(time(NULL));
1263 if (m_regenTimer)
1265 if (update_diff >= m_regenTimer)
1266 m_regenTimer = 0;
1267 else
1268 m_regenTimer -= update_diff;
1271 if (m_weaponChangeTimer > 0)
1273 if (update_diff >= m_weaponChangeTimer)
1274 m_weaponChangeTimer = 0;
1275 else
1276 m_weaponChangeTimer -= update_diff;
1279 if (m_zoneUpdateTimer > 0)
1281 if (update_diff >= m_zoneUpdateTimer)
1283 uint32 newzone, newarea;
1284 GetZoneAndAreaId(newzone, newarea);
1286 if (m_zoneUpdateId != newzone)
1287 UpdateZone(newzone, newarea); // Also update area
1288 else
1290 // Use area updates as well
1291 // Needed for free for all arenas for example
1292 if (m_areaUpdateId != newarea)
1293 UpdateArea(newarea);
1295 m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL;
1298 else
1299 m_zoneUpdateTimer -= update_diff;
1302 if (m_timeSyncTimer > 0)
1304 if (update_diff >= m_timeSyncTimer)
1305 SendTimeSync();
1306 else
1307 m_timeSyncTimer -= update_diff;
1310 if (isAlive())
1312 if (!HasAuraType(SPELL_AURA_STOP_NATURAL_MANA_REGEN))
1313 SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_REGENERATE_POWER);
1315 if (!m_regenTimer)
1316 RegenerateAll();
1319 if (m_deathState == JUST_DIED)
1320 KillPlayer();
1322 if (m_nextSave > 0)
1324 if (update_diff >= m_nextSave)
1326 // m_nextSave reseted in SaveToDB call
1327 SaveToDB();
1328 DETAIL_LOG("Player '%s' (GUID: %u) saved", GetName(), GetGUIDLow());
1330 else
1331 m_nextSave -= update_diff;
1334 // Handle Water/drowning
1335 HandleDrowning(update_diff);
1337 // Handle detect stealth players
1338 if (m_DetectInvTimer > 0)
1340 if (update_diff >= m_DetectInvTimer)
1342 HandleStealthedUnitsDetection();
1343 m_DetectInvTimer = 3000;
1345 else
1346 m_DetectInvTimer -= update_diff;
1349 // Played time
1350 if (now > m_Last_tick)
1352 uint32 elapsed = uint32(now - m_Last_tick);
1353 m_Played_time[PLAYED_TIME_TOTAL] += elapsed; // Total played time
1354 m_Played_time[PLAYED_TIME_LEVEL] += elapsed; // Level played time
1355 m_Last_tick = now;
1358 if (m_drunk)
1360 m_drunkTimer += update_diff;
1362 if (m_drunkTimer > 10 * IN_MILLISECONDS)
1363 HandleSobering();
1366 // Not auto-free ghost from body in instances
1367 if (m_deathTimer > 0 && !GetMap()->Instanceable())
1369 if (p_time >= m_deathTimer)
1371 m_deathTimer = 0;
1372 BuildPlayerRepop();
1373 RepopAtGraveyard();
1375 else
1376 m_deathTimer -= p_time;
1379 UpdateEnchantTime(update_diff);
1380 UpdateHomebindTime(update_diff);
1382 // Group update
1383 SendUpdateToOutOfRangeGroupMembers();
1385 Pet* pet = GetPet();
1386 if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityDistance()) && (GetCharmGuid() && (pet->GetObjectGuid() != GetCharmGuid())))
1387 pet->Unsummon(PET_SAVE_REAGENTS, this);
1389 if (IsHasDelayedTeleport())
1390 TeleportTo(m_teleport_dest, m_teleport_options);
1393 void Player::SetDeathState(DeathState s)
1395 uint32 ressSpellId = 0;
1397 bool cur = isAlive();
1399 if (s == JUST_DIED && cur)
1401 // drunken state is cleared on death
1402 SetDrunkValue(0);
1403 // lost combo points at any target (targeted combo points clear in Unit::SetDeathState)
1404 ClearComboPoints();
1406 clearResurrectRequestData();
1408 // remove form before other mods to prevent incorrect stats calculation
1409 RemoveSpellsCausingAura(SPELL_AURA_MOD_SHAPESHIFT);
1411 // FIXME: is pet dismissed at dying or releasing spirit? if second, add SetDeathState(DEAD) to HandleRepopRequestOpcode and define pet unsummon here with (s == DEAD)
1412 RemovePet(PET_SAVE_REAGENTS);
1414 // save value before aura remove in Unit::SetDeathState
1415 ressSpellId = GetUInt32Value(PLAYER_SELF_RES_SPELL);
1417 // passive spell
1418 if (!ressSpellId)
1419 ressSpellId = GetResurrectionSpellId();
1421 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP, 1);
1422 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATH, 1);
1423 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DEATH_IN_DUNGEON, 1);
1425 if (InstanceData* mapInstance = GetInstanceData())
1426 mapInstance->OnPlayerDeath(this);
1429 Unit::SetDeathState(s);
1431 // restore resurrection spell id for player after aura remove
1432 if (s == JUST_DIED && cur && ressSpellId)
1433 SetUInt32Value(PLAYER_SELF_RES_SPELL, ressSpellId);
1435 if (isAlive() && !cur)
1437 // clear aura case after resurrection by another way (spells will be applied before next death)
1438 SetUInt32Value(PLAYER_SELF_RES_SPELL, 0);
1440 // restore default warrior stance
1441 if (getClass() == CLASS_WARRIOR)
1442 CastSpell(this, SPELL_ID_PASSIVE_BATTLE_STANCE, true);
1446 bool Player::BuildEnumData(QueryResult* result, ByteBuffer* data, ByteBuffer* buffer)
1448 // 0 1 2 3 4 5 6 7
1449 // "SELECT characters.guid, characters.name, characters.race, characters.class, characters.gender, characters.playerBytes, characters.playerBytes2, characters.level, "
1450 // 8 9 10 11 12 13 14
1451 // "characters.zone, characters.map, characters.position_x, characters.position_y, characters.position_z, guild_member.guildid, characters.playerFlags, "
1452 // 15 16 17 18 19 20 21
1453 // "characters.at_login, character_pet.entry, character_pet.modelid, character_pet.level, characters.equipmentCache, characters.slot, character_declinedname.genitive "
1455 Field* fields = result->Fetch();
1456 ObjectGuid guid = ObjectGuid(HIGHGUID_PLAYER, fields[0].GetUInt32());
1457 uint8 pRace = fields[2].GetUInt8();
1458 uint8 pClass = fields[3].GetUInt8();
1460 PlayerInfo const* info = sObjectMgr.GetPlayerInfo(pRace, pClass);
1461 if (!info)
1463 sLog.outError("Player %u has incorrect race/class pair. Don't build enum.", guid);
1464 return false;
1467 ObjectGuid guildGuid = ObjectGuid(HIGHGUID_GUILD, fields[13].GetUInt32());
1468 std::string name = fields[1].GetCppString();
1469 uint8 gender = fields[4].GetUInt8();
1470 uint32 playerBytes = fields[5].GetUInt32();
1471 uint8 level = fields[7].GetUInt8();
1472 uint32 playerFlags = fields[14].GetUInt32();
1473 uint32 atLoginFlags = fields[15].GetUInt32();
1474 uint32 zone = fields[8].GetUInt32();
1475 uint32 petDisplayId = 0;
1476 uint32 petLevel = 0;
1477 uint32 petFamily = 0;
1478 uint32 char_flags = 0;
1480 data->WriteGuidMask<3>(guid);
1481 data->WriteGuidMask<1, 7, 2>(guildGuid);
1482 data->WriteBits(name.length(), 7);
1483 data->WriteGuidMask<4, 7>(guid);
1484 data->WriteGuidMask<3>(guildGuid);
1485 data->WriteGuidMask<5>(guid);
1486 data->WriteGuidMask<6>(guildGuid);
1487 data->WriteGuidMask<1>(guid);
1488 data->WriteGuidMask<5, 4>(guildGuid);
1489 data->WriteBit(atLoginFlags & AT_LOGIN_FIRST);
1490 data->WriteGuidMask<0, 2, 6>(guid);
1491 data->WriteGuidMask<0>(guildGuid);
1493 // show pet at selection character in character list only for non-ghost character
1494 if (result && !(playerFlags & PLAYER_FLAGS_GHOST) && (pClass == CLASS_WARLOCK || pClass == CLASS_HUNTER || pClass == CLASS_DEATH_KNIGHT))
1496 uint32 entry = fields[16].GetUInt32();
1497 CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(entry);
1498 if(cInfo)
1500 petDisplayId = fields[17].GetUInt32();
1501 petLevel = fields[18].GetUInt32();
1502 petFamily = cInfo->family;
1506 if(playerFlags & PLAYER_FLAGS_HIDE_HELM)
1507 char_flags |= CHARACTER_FLAG_HIDE_HELM;
1508 if(playerFlags & PLAYER_FLAGS_HIDE_CLOAK)
1509 char_flags |= CHARACTER_FLAG_HIDE_CLOAK;
1510 if(playerFlags & PLAYER_FLAGS_GHOST)
1511 char_flags |= CHARACTER_FLAG_GHOST;
1512 if(atLoginFlags & AT_LOGIN_RENAME)
1513 char_flags |= CHARACTER_FLAG_RENAME;
1514 if(sWorld.getConfig(CONFIG_BOOL_DECLINED_NAMES_USED))
1516 if(!fields[21].GetCppString().empty())
1517 char_flags |= CHARACTER_FLAG_DECLINED;
1519 else
1520 char_flags |= CHARACTER_FLAG_DECLINED;
1522 *buffer << uint8(pClass); // class
1524 Tokens tdata = StrSplit(fields[19].GetCppString(), " ");
1525 for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot)
1527 uint32 visualbase = slot * 2;
1528 uint32 item_id = GetUInt32ValueFromArray(tdata, visualbase);
1529 const ItemPrototype * proto = ObjectMgr::GetItemPrototype(item_id);
1530 if(!proto)
1532 *buffer << uint8(0);
1533 *buffer << uint32(0);
1534 *buffer << uint32(0);
1535 continue;
1538 SpellItemEnchantmentEntry const *enchant = NULL;
1540 uint32 enchants = GetUInt32ValueFromArray(tdata, visualbase + 1);
1541 for(uint8 enchantSlot = PERM_ENCHANTMENT_SLOT; enchantSlot <= TEMP_ENCHANTMENT_SLOT; ++enchantSlot)
1543 // values stored in 2 uint16
1544 uint32 enchantId = 0x0000FFFF & (enchants >> enchantSlot*16);
1545 if(!enchantId)
1546 continue;
1548 if ((enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId)))
1549 break;
1552 *buffer << uint8(proto->InventoryType);
1553 *buffer << uint32(proto->DisplayInfoID);
1554 *buffer << uint32(enchant ? enchant->aura_id : 0);
1557 for (int32 i = 0; i < 4; i++)
1559 *buffer << uint8(0);
1560 *buffer << uint32(0);
1561 *buffer << uint32(0);
1564 *buffer << uint32(petFamily); // Pet DisplayID
1565 buffer->WriteGuidBytes<2>(guildGuid);
1566 *buffer << uint8(fields[20].GetUInt8()); // char order id
1567 *buffer << uint8((playerBytes >> 16) & 0xFF); // Hair style
1568 buffer->WriteGuidBytes<3>(guildGuid);
1569 *buffer << uint32(petDisplayId); // Pet DisplayID
1570 *buffer << uint32(char_flags); // character flags
1571 *buffer << uint8((playerBytes >> 24) & 0xFF); // Hair color
1572 buffer->WriteGuidBytes<4>(guid);
1573 *buffer << uint32(fields[9].GetUInt32()); // map
1574 buffer->WriteGuidBytes<5>(guildGuid);
1575 *buffer << fields[12].GetFloat(); // z
1576 buffer->WriteGuidBytes<6>(guildGuid);
1577 *buffer << uint32(petLevel); // pet level
1578 buffer->WriteGuidBytes<3>(guid);
1579 *buffer << fields[11].GetFloat(); // y
1580 // character customize flags
1581 *buffer << uint32(atLoginFlags & AT_LOGIN_CUSTOMIZE ? CHAR_CUSTOMIZE_FLAG_CUSTOMIZE : CHAR_CUSTOMIZE_FLAG_NONE);
1583 uint32 playerBytes2 = fields[6].GetUInt32();
1584 *buffer << uint8(playerBytes2 & 0xFF); // facial hair
1585 buffer->WriteGuidBytes<7>(guid);
1586 *buffer << uint8(gender); // Gender
1587 buffer->append(name.c_str(), name.length());
1588 *buffer << uint8((playerBytes >> 8) & 0xFF); // face
1590 buffer->WriteGuidBytes<0, 2>(guid);
1591 buffer->WriteGuidBytes<1, 7>(guildGuid);
1593 *buffer << fields[10].GetFloat(); // x
1594 *buffer << uint8(playerBytes & 0xFF); // skin
1595 *buffer << uint8(pRace); // Race
1596 *buffer << uint8(level); // Level
1597 buffer->WriteGuidBytes<6>(guid);
1598 buffer->WriteGuidBytes<4, 0>(guildGuid);
1599 buffer->WriteGuidBytes<5, 1>(guid);
1601 *buffer << uint32(zone); // Zone id
1603 return true;
1606 void Player::ToggleAFK()
1608 ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_AFK);
1610 // afk player not allowed in battleground
1611 if (isAFK() && InBattleGround() && !InArena())
1612 LeaveBattleground();
1615 void Player::ToggleDND()
1617 ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_DND);
1620 uint8 Player::GetChatTag() const
1622 uint8 tag = CHAT_TAG_NONE;
1624 if (isAFK())
1625 tag |= CHAT_TAG_AFK;
1626 if (isDND())
1627 tag |= CHAT_TAG_DND;
1628 if (isGMChat())
1629 tag |= CHAT_TAG_GM;
1630 if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_COMMENTATOR))
1631 tag |= CHAT_TAG_COM;
1632 if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_DEVELOPER))
1633 tag |= CHAT_TAG_DEV;
1635 return tag;
1638 void Player::SendTeleportPacket(float oldX, float oldY, float oldZ, float oldO)
1640 ObjectGuid guid = GetObjectGuid();
1641 ObjectGuid transportGuid = m_movementInfo.GetTransportGuid();
1643 WorldPacket data(SMSG_MOVE_TELEPORT, 38);
1644 data.WriteGuidMask<6, 0, 3, 2>(guid);
1645 data.WriteBit(0); // unknown
1646 data.WriteBit(!transportGuid.IsEmpty());
1647 data.WriteGuidMask<1>(guid);
1648 if (transportGuid)
1649 data.WriteGuidMask<1, 3, 2, 5, 0, 7, 6, 4>(transportGuid);
1651 data.WriteGuidMask<4, 7, 5>(guid);
1653 if (transportGuid)
1654 data.WriteGuidBytes<5, 6, 1, 7, 0, 2, 4, 3>(transportGuid);
1656 data << uint32(0); // counter
1657 data.WriteGuidBytes<1, 2, 3, 5>(guid);
1658 data << float(GetPositionX());
1659 data.WriteGuidBytes<4>(guid);
1660 data << float(GetOrientation());
1661 data.WriteGuidBytes<7>(guid);
1662 data << float(GetPositionZ());
1663 data.WriteGuidBytes<0, 6>(guid);
1664 data << float(GetPositionY());
1666 Relocate(oldX, oldY, oldZ, oldO);
1667 SendDirectMessage(&data);
1670 bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientation, uint32 options /*=0*/, AreaTrigger const* at /*=NULL*/)
1672 orientation = NormalizeOrientation(orientation);
1674 if (!MapManager::IsValidMapCoord(mapid, x, y, z, orientation))
1676 sLog.outError("TeleportTo: invalid map %d or absent instance template.", mapid);
1677 return false;
1680 MapEntry const* mEntry = sMapStore.LookupEntry(mapid); // Validity checked in IsValidMapCoord
1682 // preparing unsummon pet if lost (we must get pet before teleportation or will not find it later)
1683 Pet* pet = GetPet();
1685 // don't let enter battlegrounds without assigned battleground id (for example through areatrigger)...
1686 // don't let gm level > 1 either
1687 if (!InBattleGround() && mEntry->IsBattleGroundOrArena())
1688 return false;
1690 // Get MapEntrance trigger if teleport to other -nonBG- map
1691 bool assignedAreaTrigger = false;
1692 if (GetMapId() != mapid && !mEntry->IsBattleGroundOrArena() && !at)
1694 at = sObjectMgr.GetMapEntranceTrigger(mapid);
1695 assignedAreaTrigger = true;
1698 // Check requirements for teleport
1699 if (at)
1701 uint32 miscRequirement = 0;
1702 AreaLockStatus lockStatus = GetAreaTriggerLockStatus(at, GetDifficulty(mEntry->IsRaid()), miscRequirement);
1703 if (lockStatus != AREA_LOCKSTATUS_OK)
1705 // Teleport not requested by area-trigger
1706 // TODO - Assume a player with expansion 0 travels from BootyBay to Ratched, and he is attempted to be teleported to outlands
1707 // then he will repop near BootyBay instead of normally continuing his journey
1708 // This code is probably added to catch passengers on ships to northrend who shouldn't go there
1709 if (lockStatus == AREA_LOCKSTATUS_INSUFFICIENT_EXPANSION && !assignedAreaTrigger && GetTransport())
1710 RepopAtGraveyard(); // Teleport to near graveyard if on transport, looks blizz like :)
1712 SendTransferAbortedByLockStatus(mEntry, lockStatus, miscRequirement);
1713 return false;
1717 if (Group* grp = GetGroup()) // TODO: Verify that this is correct place
1718 grp->SetPlayerMap(GetObjectGuid(), mapid);
1720 // if we were on a transport, leave
1721 if (!(options & TELE_TO_NOT_LEAVE_TRANSPORT) && m_transport)
1723 m_transport->RemovePassenger(this);
1724 m_transport = NULL;
1725 m_movementInfo.ClearTransportData();
1728 // The player was ported to another map and looses the duel immediately.
1729 // We have to perform this check before the teleport, otherwise the
1730 // ObjectAccessor won't find the flag.
1731 if (duel && GetMapId() != mapid)
1732 if (GetMap()->GetGameObject(GetGuidValue(PLAYER_DUEL_ARBITER)))
1733 DuelComplete(DUEL_FLED);
1735 // reset movement flags at teleport, because player will continue move with these flags after teleport
1736 m_movementInfo.SetMovementFlags(MOVEFLAG_NONE);
1737 DisableSpline();
1739 if ((GetMapId() == mapid) && (!m_transport)) // TODO the !m_transport might have unexpected effects when teleporting from transport to other place on same map
1741 // lets reset far teleport flag if it wasn't reset during chained teleports
1742 SetSemaphoreTeleportFar(false);
1743 // setup delayed teleport flag
1744 // if teleport spell is casted in Unit::Update() func
1745 // then we need to delay it until update process will be finished
1746 if (SetDelayedTeleportFlagIfCan())
1748 SetSemaphoreTeleportNear(true);
1749 // lets save teleport destination for player
1750 m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
1751 m_teleport_options = options;
1752 return true;
1755 if (!(options & TELE_TO_NOT_UNSUMMON_PET))
1757 // same map, only remove pet if out of range for new position
1758 if (pet && !pet->IsWithinDist3d(x, y, z, GetMap()->GetVisibilityDistance()))
1759 UnsummonPetTemporaryIfAny();
1762 if (!(options & TELE_TO_NOT_LEAVE_COMBAT))
1763 CombatStop();
1765 // this will be used instead of the current location in SaveToDB
1766 m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
1767 SetFallInformation(0, z);
1769 // code for finish transfer called in WorldSession::HandleMovementOpcodes()
1770 // at client packet CMSG_MOVE_TELEPORT_ACK
1771 SetSemaphoreTeleportNear(true);
1772 // near teleport, triggering send CMSG_MOVE_TELEPORT_ACK from client at landing
1773 if (!GetSession()->PlayerLogout())
1775 float oldX, oldY, oldZ;
1776 float oldO = GetOrientation();
1777 GetPosition(oldX, oldY, oldZ);;
1778 Relocate(x, y, z, orientation);
1779 SendTeleportPacket(oldX, oldY, oldZ, oldO);
1782 else
1784 // far teleport to another map
1785 Map* oldmap = IsInWorld() ? GetMap() : NULL;
1786 // check if we can enter before stopping combat / removing pet / totems / interrupting spells
1788 // If the map is not created, assume it is possible to enter it.
1789 // It will be created in the WorldPortAck.
1790 DungeonPersistentState* state = GetBoundInstanceSaveForSelfOrGroup(mapid);
1791 Map* map = sMapMgr.FindMap(mapid, state ? state->GetInstanceId() : 0);
1792 if (!map || map->CanEnter(this))
1794 // lets reset near teleport flag if it wasn't reset during chained teleports
1795 SetSemaphoreTeleportNear(false);
1796 // setup delayed teleport flag
1797 // if teleport spell is casted in Unit::Update() func
1798 // then we need to delay it until update process will be finished
1799 if (SetDelayedTeleportFlagIfCan())
1801 SetSemaphoreTeleportFar(true);
1802 // lets save teleport destination for player
1803 m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
1804 m_teleport_options = options;
1805 return true;
1808 SetSelectionGuid(ObjectGuid());
1810 CombatStop();
1812 ResetContestedPvP();
1814 // remove player from battleground on far teleport (when changing maps)
1815 if (BattleGround const* bg = GetBattleGround())
1817 // Note: at battleground join battleground id set before teleport
1818 // and we already will found "current" battleground
1819 // just need check that this is targeted map or leave
1820 if (bg->GetMapId() != mapid)
1821 LeaveBattleground(false); // don't teleport to entry point
1824 // remove pet on map change
1825 if (pet)
1826 UnsummonPetTemporaryIfAny();
1828 // remove vehicle accessories on map change
1829 if (IsVehicle())
1830 GetVehicleInfo()->RemoveAccessoriesFromMap();
1832 // remove all dyn objects
1833 RemoveAllDynObjects();
1835 // stop spellcasting
1836 // not attempt interrupt teleportation spell at caster teleport
1837 if (!(options & TELE_TO_SPELL))
1838 if (IsNonMeleeSpellCasted(true))
1839 InterruptNonMeleeSpells(true);
1841 // remove auras before removing from map...
1842 RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_CHANGE_MAP | AURA_INTERRUPT_FLAG_MOVE | AURA_INTERRUPT_FLAG_TURNING);
1844 if (!GetSession()->PlayerLogout())
1846 // send transfer packet to display load screen
1847 WorldPacket data(SMSG_TRANSFER_PENDING, (4 + 4 + 4));
1848 data.WriteBit(0); // unknown
1849 if (m_transport)
1851 data.WriteBit(1); // has transport
1852 data << uint32(GetMapId());
1853 data << uint32(m_transport->GetEntry());
1855 else
1856 data.WriteBit(0); // has transport
1858 data << uint32(mapid);
1859 GetSession()->SendPacket(&data);
1862 // remove from old map now
1863 if (oldmap)
1864 oldmap->Remove(this, false);
1866 // new final coordinates
1867 float final_x = x;
1868 float final_y = y;
1869 float final_z = z;
1870 float final_o = orientation;
1872 if (m_transport)
1874 final_x += m_movementInfo.GetTransportPos()->x;
1875 final_y += m_movementInfo.GetTransportPos()->y;
1876 final_z += m_movementInfo.GetTransportPos()->z;
1877 final_o += m_movementInfo.GetTransportPos()->o;
1880 m_teleport_dest = WorldLocation(mapid, final_x, final_y, final_z, final_o);
1881 SetFallInformation(0, final_z);
1882 // if the player is saved before worldport ack (at logout for example)
1883 // this will be used instead of the current location in SaveToDB
1885 // move packet sent by client always after far teleport
1886 // code for finish transfer to new map called in WorldSession::HandleMoveWorldportAckOpcode at client packet
1887 SetSemaphoreTeleportFar(true);
1889 if (!GetSession()->PlayerLogout())
1891 // transfer finished, inform client to start load
1892 WorldPacket data(SMSG_NEW_WORLD, 20);
1893 if (m_transport)
1895 data << float(m_movementInfo.GetTransportPos()->x);
1896 data << float(m_movementInfo.GetTransportPos()->o);
1897 data << float(m_movementInfo.GetTransportPos()->z);
1899 else
1901 data << float(final_x);
1902 data << float(final_o);
1903 data << float(final_z);
1906 data << uint32(mapid);
1908 if (m_transport)
1909 data << float(m_movementInfo.GetTransportPos()->y);
1910 else
1911 data << float(final_y);
1913 GetSession()->SendPacket(&data);
1914 SendSavedInstances();
1917 else // !map->CanEnter(this)
1918 return false;
1920 return true;
1923 bool Player::TeleportToBGEntryPoint()
1925 ScheduleDelayedOperation(DELAYED_BG_MOUNT_RESTORE);
1926 ScheduleDelayedOperation(DELAYED_BG_TAXI_RESTORE);
1927 return TeleportTo(m_bgData.joinPos);
1930 void Player::ProcessDelayedOperations()
1932 if (m_DelayedOperations == 0)
1933 return;
1935 if (m_DelayedOperations & DELAYED_RESURRECT_PLAYER)
1937 ResurrectPlayer(0.0f, false);
1939 if (GetMaxHealth() > m_resurrectHealth)
1940 SetHealth(m_resurrectHealth);
1941 else
1942 SetHealth(GetMaxHealth());
1944 if (GetMaxPower(POWER_MANA) > m_resurrectMana)
1945 SetPower(POWER_MANA, m_resurrectMana);
1946 else
1947 SetPower(POWER_MANA, GetMaxPower(POWER_MANA));
1949 SetPower(POWER_RAGE, 0);
1950 SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY));
1952 SpawnCorpseBones();
1955 if (m_DelayedOperations & DELAYED_SAVE_PLAYER)
1957 SaveToDB();
1960 if (m_DelayedOperations & DELAYED_SPELL_CAST_DESERTER)
1962 CastSpell(this, 26013, true); // Deserter
1965 if (m_DelayedOperations & DELAYED_BG_MOUNT_RESTORE)
1967 if (m_bgData.mountSpell)
1969 CastSpell(this, m_bgData.mountSpell, true);
1970 m_bgData.mountSpell = 0;
1974 if (m_DelayedOperations & DELAYED_BG_TAXI_RESTORE)
1976 if (m_bgData.HasTaxiPath())
1978 m_taxi.AddTaxiDestination(m_bgData.taxiPath[0]);
1979 m_taxi.AddTaxiDestination(m_bgData.taxiPath[1]);
1980 m_bgData.ClearTaxiPath();
1982 ContinueTaxiFlight();
1986 // we have executed ALL delayed ops, so clear the flag
1987 m_DelayedOperations = 0;
1990 void Player::AddToWorld()
1992 ///- Do not add/remove the player from the object storage
1993 ///- It will crash when updating the ObjectAccessor
1994 ///- The player should only be added when logging in
1995 Unit::AddToWorld();
1997 for (int i = PLAYER_SLOT_START; i < PLAYER_SLOT_END; ++i)
1999 if (m_items[i])
2000 m_items[i]->AddToWorld();
2004 void Player::RemoveFromWorld()
2006 for (int i = PLAYER_SLOT_START; i < PLAYER_SLOT_END; ++i)
2008 if (m_items[i])
2009 m_items[i]->RemoveFromWorld();
2012 ///- Do not add/remove the player from the object storage
2013 ///- It will crash when updating the ObjectAccessor
2014 ///- The player should only be removed when logging out
2015 if (IsInWorld())
2016 GetCamera().ResetView();
2018 Unit::RemoveFromWorld();
2021 void Player::RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker)
2023 float addRage;
2025 float rageconversion = float((0.0091107836 * getLevel() * getLevel()) + 3.225598133 * getLevel()) + 4.2652911f;
2027 if (attacker)
2029 addRage = ((damage / rageconversion * 7.5f + weaponSpeedHitFactor) / 2.0f);
2031 // talent who gave more rage on attack
2032 addRage *= 1.0f + GetTotalAuraModifier(SPELL_AURA_MOD_RAGE_FROM_DAMAGE_DEALT) / 100.0f;
2034 else
2036 addRage = damage / rageconversion * 2.5f;
2038 // Berserker Rage effect
2039 if (HasAura(18499, EFFECT_INDEX_0))
2040 addRage *= 1.3f;
2043 addRage *= sWorld.getConfig(CONFIG_FLOAT_RATE_POWER_RAGE_INCOME);
2045 ModifyPower(POWER_RAGE, uint32(addRage * 10));
2048 void Player::RegenerateAll(uint32 diff)
2050 // Not in combat or they have regeneration
2051 if (!isInCombat() || HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT) ||
2052 HasAuraType(SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT) || IsPolymorphed() || m_baseHealthRegen)
2054 RegenerateHealth(diff);
2055 if (!isInCombat() && !HasAuraType(SPELL_AURA_INTERRUPT_REGEN))
2057 Regenerate(POWER_RAGE, diff);
2058 if (getClass() == CLASS_DEATH_KNIGHT)
2059 Regenerate(POWER_RUNIC_POWER, diff);
2063 Regenerate(POWER_ENERGY, diff);
2065 Regenerate(POWER_MANA, diff);
2067 if (getClass() == CLASS_DEATH_KNIGHT)
2068 Regenerate(POWER_RUNE, diff);
2070 if (getClass() == CLASS_HUNTER)
2071 Regenerate(POWER_FOCUS, diff);
2073 if (getClass() == CLASS_PALADIN)
2075 if (isInCombat())
2076 ResetHolyPowerRegenTimer();
2077 else if (m_holyPowerRegenTimer <= diff)
2078 m_holyPowerRegenTimer = 0;
2079 else
2080 m_holyPowerRegenTimer -= diff;
2082 if (!m_holyPowerRegenTimer)
2084 Regenerate(POWER_HOLY_POWER, diff);
2085 ResetHolyPowerRegenTimer();
2089 m_regenTimer = REGEN_TIME_FULL;
2092 // diff contains the time in milliseconds since last regen.
2093 void Player::Regenerate(Powers power, uint32 diff)
2095 uint32 powerIndex = GetPowerIndex(power);
2096 if (powerIndex == INVALID_POWER_INDEX)
2097 return;
2099 uint32 maxValue = GetMaxPowerByIndex(powerIndex);
2100 if (!maxValue)
2101 return;
2103 uint32 curValue = GetPowerByIndex(powerIndex);
2105 float addvalue = 0.0f;
2107 switch (power)
2109 case POWER_MANA:
2111 if (HasAuraType(SPELL_AURA_STOP_NATURAL_MANA_REGEN))
2112 break;
2113 float ManaIncreaseRate = sWorld.getConfig(CONFIG_FLOAT_RATE_POWER_MANA);
2115 if (isInCombat())
2117 // Mangos Updates Mana in intervals of 2s, which is correct
2118 addvalue = GetFloatValue(UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER) * ManaIncreaseRate * 2.00f;
2120 else
2122 addvalue = GetFloatValue(UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER) * ManaIncreaseRate * 2.00f;
2124 break;
2126 case POWER_RAGE: // Regenerate rage
2128 float RageDecreaseRate = sWorld.getConfig(CONFIG_FLOAT_RATE_POWER_RAGE_LOSS);
2129 addvalue = 20 * RageDecreaseRate; // 2 rage by tick (= 2 seconds => 1 rage/sec)
2130 break;
2132 case POWER_FOCUS:
2133 addvalue = 12;
2134 break;
2135 case POWER_HOLY_POWER:
2136 if (!m_holyPowerRegenTimer)
2137 addvalue = 1;
2138 else
2139 return;
2140 break;
2141 case POWER_ENERGY: // Regenerate energy
2143 float EnergyRate = sWorld.getConfig(CONFIG_FLOAT_RATE_POWER_ENERGY);
2144 addvalue = 20 * EnergyRate;
2145 break;
2147 case POWER_RUNIC_POWER:
2149 float RunicPowerDecreaseRate = sWorld.getConfig(CONFIG_FLOAT_RATE_POWER_RUNICPOWER_LOSS);
2150 addvalue = 30 * RunicPowerDecreaseRate; // 3 RunicPower by tick
2151 break;
2153 case POWER_RUNE:
2155 if (getClass() != CLASS_DEATH_KNIGHT)
2156 break;
2158 for (uint32 rune = 0; rune < MAX_RUNES; ++rune)
2160 if (uint16 cd = GetRuneCooldown(rune)) // if we have cooldown, reduce it...
2162 uint32 cd_diff = diff;
2163 AuraList const& ModPowerRegenPCTAuras = GetAurasByType(SPELL_AURA_MOD_POWER_REGEN_PERCENT);
2164 for (AuraList::const_iterator i = ModPowerRegenPCTAuras.begin(); i != ModPowerRegenPCTAuras.end(); ++i)
2165 if ((*i)->GetModifier()->m_miscvalue == int32(power) && (*i)->GetMiscBValue() == GetCurrentRune(rune))
2166 cd_diff = cd_diff * ((*i)->GetModifier()->m_amount + 100) / 100;
2168 SetRuneCooldown(rune, (cd < cd_diff) ? 0 : cd - cd_diff);
2171 break;
2173 case POWER_HEALTH:
2174 break;
2177 // Mana regen calculated in Player::UpdateManaRegen()
2178 // Exist only for POWER_MANA, POWER_ENERGY, POWER_FOCUS auras
2179 if (power != POWER_MANA)
2181 AuraList const& ModPowerRegenPCTAuras = GetAurasByType(SPELL_AURA_MOD_POWER_REGEN_PERCENT);
2182 for (AuraList::const_iterator i = ModPowerRegenPCTAuras.begin(); i != ModPowerRegenPCTAuras.end(); ++i)
2183 if ((*i)->GetModifier()->m_miscvalue == int32(power))
2184 addvalue *= ((*i)->GetModifier()->m_amount + 100) / 100.0f;
2187 // addvalue computed on a 2sec basis. => update to diff time
2188 addvalue *= float(diff) / REGEN_TIME_FULL;
2190 if (power != POWER_RAGE && power != POWER_RUNIC_POWER && power != POWER_HOLY_POWER)
2192 curValue += uint32(addvalue);
2193 if (curValue > maxValue)
2194 curValue = maxValue;
2196 else
2198 if (curValue <= uint32(addvalue))
2199 curValue = 0;
2200 else
2201 curValue -= uint32(addvalue);
2203 SetPower(power, curValue);
2206 void Player::RegenerateHealth(uint32 diff)
2208 uint32 curValue = GetHealth();
2209 uint32 maxValue = GetMaxHealth();
2211 if (curValue >= maxValue) return;
2213 float HealthIncreaseRate = sWorld.getConfig(CONFIG_FLOAT_RATE_HEALTH);
2215 float addvalue = 0.0f;
2217 // polymorphed case
2218 if (IsPolymorphed())
2219 addvalue = (float)GetMaxHealth() / 3;
2220 // normal regen case (maybe partly in combat case)
2221 else if (!isInCombat() || HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT))
2223 addvalue = HealthIncreaseRate;
2224 if (!isInCombat())
2226 if (getLevel() < 15)
2227 addvalue = 0.20f * GetMaxHealth() * addvalue / getLevel();
2228 else
2229 addvalue = 0.015f * GetMaxHealth() * addvalue;
2231 AuraList const& mModHealthRegenPct = GetAurasByType(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT);
2232 for (AuraList::const_iterator i = mModHealthRegenPct.begin(); i != mModHealthRegenPct.end(); ++i)
2233 addvalue *= (100.0f + (*i)->GetModifier()->m_amount) / 100.0f;
2235 addvalue += GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * 2.0f / 5.0f;
2237 else if (HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT))
2238 addvalue *= GetTotalAuraModifier(SPELL_AURA_MOD_REGEN_DURING_COMBAT) / 100.0f;
2240 if (!IsStandState())
2241 addvalue *= 1.33f;
2244 // always regeneration bonus (including combat)
2245 addvalue += GetTotalAuraModifier(SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT);
2247 addvalue += m_baseHealthRegen / 2.5f;
2249 if (addvalue < 0)
2250 addvalue = 0;
2252 addvalue *= (float)diff / REGEN_TIME_FULL;
2254 ModifyHealth(int32(addvalue));
2257 Creature* Player::GetNPCIfCanInteractWith(ObjectGuid guid, uint32 npcflagmask)
2259 // some basic checks
2260 if (!guid || !IsInWorld() || IsTaxiFlying())
2261 return NULL;
2263 // not in interactive state
2264 if (hasUnitState(UNIT_STAT_CAN_NOT_REACT_OR_LOST_CONTROL))
2265 return NULL;
2267 // exist (we need look pets also for some interaction (quest/etc)
2268 Creature* unit = GetMap()->GetAnyTypeCreature(guid);
2269 if (!unit)
2270 return NULL;
2272 // appropriate npc type
2273 if (npcflagmask && !unit->HasFlag(UNIT_NPC_FLAGS, npcflagmask))
2274 return NULL;
2276 if (npcflagmask == UNIT_NPC_FLAG_STABLEMASTER)
2278 if (getClass() != CLASS_HUNTER)
2279 return NULL;
2282 // if a dead unit should be able to talk - the creature must be alive and have special flags
2283 if (!unit->isAlive())
2284 return NULL;
2286 if (isAlive() && unit->isInvisibleForAlive())
2287 return NULL;
2289 // not allow interaction under control, but allow with own pets
2290 if (unit->GetCharmerGuid())
2291 return NULL;
2293 // not enemy
2294 if (unit->IsHostileTo(this))
2295 return NULL;
2297 // not too far
2298 if (!unit->IsWithinDistInMap(this, INTERACTION_DISTANCE))
2299 return NULL;
2301 return unit;
2304 GameObject* Player::GetGameObjectIfCanInteractWith(ObjectGuid guid, uint32 gameobject_type) const
2306 // some basic checks
2307 if (!guid || !IsInWorld() || IsTaxiFlying())
2308 return NULL;
2310 // not in interactive state
2311 if (hasUnitState(UNIT_STAT_CAN_NOT_REACT_OR_LOST_CONTROL))
2312 return NULL;
2314 if (GameObject* go = GetMap()->GetGameObject(guid))
2316 if (uint32(go->GetGoType()) == gameobject_type || gameobject_type == MAX_GAMEOBJECT_TYPE)
2318 float maxdist;
2319 switch (go->GetGoType())
2321 // TODO: find out how the client calculates the maximal usage distance to spellless working
2322 // gameobjects like guildbanks and mailboxes - 10.0 is a just an abitrary choosen number
2323 case GAMEOBJECT_TYPE_GUILD_BANK:
2324 case GAMEOBJECT_TYPE_MAILBOX:
2325 maxdist = 10.0f;
2326 break;
2327 case GAMEOBJECT_TYPE_FISHINGHOLE:
2328 maxdist = 20.0f + CONTACT_DISTANCE; // max spell range
2329 break;
2330 default:
2331 maxdist = INTERACTION_DISTANCE;
2332 break;
2335 if (go->IsWithinDistInMap(this, maxdist) && go->isSpawned())
2336 return go;
2338 sLog.outError("GetGameObjectIfCanInteractWith: GameObject '%s' [GUID: %u] is too far away from player %s [GUID: %u] to be used by him (distance=%f, maximal %f is allowed)",
2339 go->GetGOInfo()->name, go->GetGUIDLow(), GetName(), GetGUIDLow(), go->GetDistance(this), maxdist);
2342 return NULL;
2345 bool Player::IsUnderWater() const
2347 return GetTerrain()->IsUnderWater(GetPositionX(), GetPositionY(), GetPositionZ() + 2);
2350 void Player::SetInWater(bool apply)
2352 if (m_isInWater == apply)
2353 return;
2355 // define player in water by opcodes
2356 // move player's guid into HateOfflineList of those mobs
2357 // which can't swim and move guid back into ThreatList when
2358 // on surface.
2359 // TODO: exist also swimming mobs, and function must be symmetric to enter/leave water
2360 m_isInWater = apply;
2362 // remove auras that need water/land
2363 RemoveAurasWithInterruptFlags(apply ? AURA_INTERRUPT_FLAG_NOT_ABOVEWATER : AURA_INTERRUPT_FLAG_NOT_UNDERWATER);
2365 getHostileRefManager().updateThreatTables();
2368 struct SetGameMasterOnHelper
2370 explicit SetGameMasterOnHelper() {}
2371 void operator()(Unit* unit) const
2373 unit->setFaction(35);
2374 unit->getHostileRefManager().setOnlineOfflineState(false);
2378 struct SetGameMasterOffHelper
2380 explicit SetGameMasterOffHelper(uint32 _faction) : faction(_faction) {}
2381 void operator()(Unit* unit) const
2383 unit->setFaction(faction);
2384 unit->getHostileRefManager().setOnlineOfflineState(true);
2386 uint32 faction;
2389 void Player::SetGameMaster(bool on)
2391 if (on)
2393 m_ExtraFlags |= PLAYER_EXTRA_GM_ON;
2394 setFaction(35);
2395 SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_GM);
2397 CallForAllControlledUnits(SetGameMasterOnHelper(), CONTROLLED_PET | CONTROLLED_TOTEMS | CONTROLLED_GUARDIANS | CONTROLLED_CHARM);
2399 SetFFAPvP(false);
2400 ResetContestedPvP();
2402 getHostileRefManager().setOnlineOfflineState(false);
2403 CombatStopWithPets();
2405 SetPhaseMask(PHASEMASK_ANYWHERE, false); // see and visible in all phases
2407 else
2409 m_ExtraFlags &= ~ PLAYER_EXTRA_GM_ON;
2410 setFactionForRace(getRace());
2411 RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_GM);
2413 // restore phase
2414 AuraList const& phases = GetAurasByType(SPELL_AURA_PHASE);
2415 SetPhaseMask(!phases.empty() ? phases.front()->GetMiscValue() : uint32(PHASEMASK_NORMAL), false);
2417 CallForAllControlledUnits(SetGameMasterOffHelper(getFaction()), CONTROLLED_PET | CONTROLLED_TOTEMS | CONTROLLED_GUARDIANS | CONTROLLED_CHARM);
2419 // restore FFA PvP Server state
2420 if (sWorld.IsFFAPvPRealm())
2421 SetFFAPvP(true);
2423 // restore FFA PvP area state, remove not allowed for GM mounts
2424 UpdateArea(m_areaUpdateId);
2426 getHostileRefManager().setOnlineOfflineState(true);
2429 m_camera.UpdateVisibilityForOwner();
2430 UpdateObjectVisibility();
2431 UpdateForQuestWorldObjects();
2434 void Player::SetGMVisible(bool on)
2436 if (on)
2438 m_ExtraFlags &= ~PLAYER_EXTRA_GM_INVISIBLE; // remove flag
2440 // Reapply stealth/invisibility if active or show if not any
2441 if (HasAuraType(SPELL_AURA_MOD_STEALTH))
2442 SetVisibility(VISIBILITY_GROUP_STEALTH);
2443 else if (HasAuraType(SPELL_AURA_MOD_INVISIBILITY))
2444 SetVisibility(VISIBILITY_GROUP_INVISIBILITY);
2445 else
2446 SetVisibility(VISIBILITY_ON);
2448 else
2450 m_ExtraFlags |= PLAYER_EXTRA_GM_INVISIBLE; // add flag
2452 SetAcceptWhispers(false);
2453 SetGameMaster(true);
2455 SetVisibility(VISIBILITY_OFF);
2459 bool Player::IsGroupVisibleFor(Player* p) const
2461 switch (sWorld.getConfig(CONFIG_UINT32_GROUP_VISIBILITY))
2463 default: return IsInSameGroupWith(p);
2464 case 1: return IsInSameRaidWith(p);
2465 case 2: return GetTeam() == p->GetTeam();
2469 bool Player::IsInSameGroupWith(Player const* p) const
2471 return (p == this || (GetGroup() != NULL &&
2472 GetGroup()->SameSubGroup(this, p)));
2475 ///- If the player is invited, remove him. If the group if then only 1 person, disband the group.
2476 /// \todo Shouldn't we also check if there is no other invitees before disbanding the group?
2477 void Player::UninviteFromGroup()
2479 Group* group = GetGroupInvite();
2480 if (!group)
2481 return;
2483 group->RemoveInvite(this);
2485 if (group->GetMembersCount() <= 1) // group has just 1 member => disband
2487 if (group->IsCreated())
2489 group->Disband(true);
2490 sObjectMgr.RemoveGroup(group);
2492 else
2493 group->RemoveAllInvites();
2495 delete group;
2499 void Player::RemoveFromGroup(Group* group, ObjectGuid guid)
2501 if (group)
2503 if (group->RemoveMember(guid, 0) <= 1)
2505 // group->Disband(); already disbanded in RemoveMember
2506 sObjectMgr.RemoveGroup(group);
2507 delete group;
2508 // removemember sets the player's group pointer to NULL
2513 void Player::SendLogXPGain(uint32 GivenXP, Unit* victim, uint32 RestXP)
2515 WorldPacket data(SMSG_LOG_XPGAIN, 21);
2516 data << (victim ? victim->GetObjectGuid() : ObjectGuid());// guid
2517 data << uint32(GivenXP + RestXP); // given experience
2518 data << uint8(victim ? 0 : 1); // 00-kill_xp type, 01-non_kill_xp type
2519 if (victim)
2521 data << uint32(GivenXP); // experience without rested bonus
2522 data << float(1); // 1 - none 0 - 100% group bonus output
2524 data << uint8(0); // new 2.4.0
2525 GetSession()->SendPacket(&data);
2528 void Player::GiveXP(uint32 xp, Unit* victim)
2530 if (xp < 1)
2531 return;
2533 if (!isAlive())
2534 return;
2536 uint32 level = getLevel();
2538 // XP to money conversion processed in Player::RewardQuest
2539 if (level >= sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL))
2540 return;
2542 if (victim)
2544 // handle SPELL_AURA_MOD_KILL_XP_PCT auras
2545 Unit::AuraList const& ModXPPctAuras = GetAurasByType(SPELL_AURA_MOD_KILL_XP_PCT);
2546 for (Unit::AuraList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i)
2547 xp = uint32(xp * (1.0f + (*i)->GetModifier()->m_amount / 100.0f));
2549 else
2551 // handle SPELL_AURA_MOD_QUEST_XP_PCT auras
2552 Unit::AuraList const& ModXPPctAuras = GetAurasByType(SPELL_AURA_MOD_QUEST_XP_PCT);
2553 for (Unit::AuraList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i)
2554 xp = uint32(xp * (1.0f + (*i)->GetModifier()->m_amount / 100.0f));
2557 // XP resting bonus for kill
2558 uint32 rested_bonus_xp = victim ? GetXPRestBonus(xp) : 0;
2560 SendLogXPGain(xp, victim, rested_bonus_xp);
2562 uint32 curXP = GetUInt32Value(PLAYER_XP);
2563 uint32 nextLvlXP = GetUInt32Value(PLAYER_NEXT_LEVEL_XP);
2564 uint32 newXP = curXP + xp + rested_bonus_xp;
2566 while (newXP >= nextLvlXP && level < sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL))
2568 newXP -= nextLvlXP;
2570 if (level < sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL))
2571 GiveLevel(level + 1);
2573 level = getLevel();
2574 nextLvlXP = GetUInt32Value(PLAYER_NEXT_LEVEL_XP);
2577 SetUInt32Value(PLAYER_XP, newXP);
2580 // Update player to next level
2581 // Current player experience not update (must be update by caller)
2582 void Player::GiveLevel(uint32 level)
2584 if (level == getLevel())
2585 return;
2587 PlayerLevelInfo info;
2588 sObjectMgr.GetPlayerLevelInfo(getRace(), getClass(), level, &info);
2590 uint32 basehp = 0, basemana = 0;
2591 sObjectMgr.GetPlayerClassLevelInfo(getClass(), level, basehp, basemana);
2593 // send levelup info to client
2594 WorldPacket data(SMSG_LEVELUP_INFO, (4 + 4 + MAX_STORED_POWERS * 4 + MAX_STATS * 4));
2595 data << uint32(level);
2596 data << uint32(int32(basehp) - int32(GetCreateHealth()));
2597 // for(int i = 0; i < MAX_POWERS; ++i) // Powers loop (0-4)
2598 data << uint32(int32(basemana) - int32(GetCreateMana()));
2599 data << uint32(0);
2600 data << uint32(0);
2601 data << uint32(0);
2602 data << uint32(0);
2603 // end for
2604 for (int i = STAT_STRENGTH; i < MAX_STATS; ++i) // Stats loop (0-4)
2605 data << uint32(int32(info.stats[i]) - GetCreateStat(Stats(i)));
2607 GetSession()->SendPacket(&data);
2609 SetUInt32Value(PLAYER_NEXT_LEVEL_XP, sObjectMgr.GetXPForLevel(level));
2611 // update level, max level of skills
2612 m_Played_time[PLAYED_TIME_LEVEL] = 0; // Level Played Time reset
2614 _ApplyAllLevelScaleItemMods(false);
2616 SetLevel(level);
2618 UpdateSkillsForLevel();
2620 // save base values (bonuses already included in stored stats
2621 for (int i = STAT_STRENGTH; i < MAX_STATS; ++i)
2622 SetCreateStat(Stats(i), info.stats[i]);
2624 SetCreateHealth(basehp);
2625 SetCreateMana(basemana);
2627 InitTalentForLevel();
2628 InitTaxiNodesForLevel();
2629 InitGlyphsForLevel();
2631 UpdateAllStats();
2633 // set current level health and mana/energy to maximum after applying all mods.
2634 if (isAlive())
2635 SetHealth(GetMaxHealth());
2636 SetPower(POWER_MANA, GetMaxPower(POWER_MANA));
2637 SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY));
2638 if (GetPower(POWER_RAGE) > GetMaxPower(POWER_RAGE))
2639 SetPower(POWER_RAGE, GetMaxPower(POWER_RAGE));
2640 SetPower(POWER_FOCUS, 0);
2642 _ApplyAllLevelScaleItemMods(true);
2644 // update level to hunter/summon pet
2645 if (Pet* pet = GetPet())
2646 pet->SynchronizeLevelWithOwner();
2648 if (MailLevelReward const* mailReward = sObjectMgr.GetMailLevelReward(level, getRaceMask()))
2649 MailDraft(mailReward->mailTemplateId).SendMailTo(this, MailSender(MAIL_CREATURE, mailReward->senderEntry));
2651 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL);
2654 void Player::UpdateFreeTalentPoints(bool resetIfNeed)
2656 uint32 level = getLevel();
2657 // talents base at level diff ( talents = level - 9 but some can be used already)
2658 if (level < 10)
2660 // Remove all talent points
2661 if (m_usedTalentCount > 0) // Free any used talents
2663 if (resetIfNeed)
2664 resetTalents(true);
2665 SetFreeTalentPoints(0);
2668 else
2670 if (m_specsCount == 0)
2672 m_specsCount = 1;
2673 m_activeSpec = 0;
2676 uint32 talentPointsForLevel = CalculateTalentsPoints();
2678 // if used more that have then reset
2679 if (m_usedTalentCount > talentPointsForLevel)
2681 if (resetIfNeed && GetSession()->GetSecurity() < SEC_ADMINISTRATOR)
2682 resetTalents(true);
2683 else
2684 SetFreeTalentPoints(0);
2686 // else update amount of free points
2687 else
2688 SetFreeTalentPoints(talentPointsForLevel - m_usedTalentCount);
2692 void Player::InitTalentForLevel()
2694 UpdateFreeTalentPoints();
2696 if (!GetSession()->PlayerLoading())
2697 SendTalentsInfoData(false); // update at client
2700 void Player::InitStatsForLevel(bool reapplyMods)
2702 if (reapplyMods) // reapply stats values only on .reset stats (level) command
2703 _RemoveAllStatBonuses();
2705 uint32 basehp = 0, basemana = 0;
2706 sObjectMgr.GetPlayerClassLevelInfo(getClass(), getLevel(), basehp, basemana);
2708 PlayerLevelInfo info;
2709 sObjectMgr.GetPlayerLevelInfo(getRace(), getClass(), getLevel(), &info);
2711 SetUInt32Value(PLAYER_FIELD_MAX_LEVEL, sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL));
2712 SetUInt32Value(PLAYER_NEXT_LEVEL_XP, sObjectMgr.GetXPForLevel(getLevel()));
2714 // reset before any aura state sources (health set/aura apply)
2715 SetUInt32Value(UNIT_FIELD_AURASTATE, 0);
2717 UpdateSkillsForLevel();
2719 // set default cast time multiplier
2720 SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f);
2722 // save base values (bonuses already included in stored stats
2723 for (int i = STAT_STRENGTH; i < MAX_STATS; ++i)
2724 SetCreateStat(Stats(i), info.stats[i]);
2726 for (int i = STAT_STRENGTH; i < MAX_STATS; ++i)
2727 SetStat(Stats(i), info.stats[i]);
2729 SetCreateHealth(basehp);
2731 // set create powers
2732 SetCreateMana(basemana);
2734 SetArmor(int32(m_createStats[STAT_AGILITY] * 2));
2736 InitStatBuffMods();
2738 // reset rating fields values
2739 for (uint16 index = PLAYER_FIELD_COMBAT_RATING_1; index < PLAYER_FIELD_COMBAT_RATING_1 + MAX_COMBAT_RATING; ++index)
2740 SetUInt32Value(index, 0);
2742 SetUInt32Value(PLAYER_FIELD_MOD_HEALING_DONE_POS, 0);
2743 SetFloatValue(PLAYER_FIELD_MOD_HEALING_PCT, 1.0f);
2744 SetFloatValue(PLAYER_FIELD_MOD_HEALING_DONE_PCT, 1.0f);
2745 for (int i = 0; i < MAX_SPELL_SCHOOL; ++i)
2747 SetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG + i, 0);
2748 SetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + i, 0);
2749 SetFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT + i, 1.00f);
2752 SetFloatValue(PLAYER_FIELD_MOD_SPELL_POWER_PCT, 1.0f);
2754 // reset attack power, damage and attack speed fields
2755 SetFloatValue(UNIT_FIELD_BASEATTACKTIME, 2000.0f);
2756 SetFloatValue(UNIT_FIELD_BASEATTACKTIME + 1, 2000.0f); // offhand attack time
2757 SetFloatValue(UNIT_FIELD_RANGEDATTACKTIME, 2000.0f);
2759 SetFloatValue(UNIT_FIELD_MINDAMAGE, 0.0f);
2760 SetFloatValue(UNIT_FIELD_MAXDAMAGE, 0.0f);
2761 SetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE, 0.0f);
2762 SetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE, 0.0f);
2763 SetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE, 0.0f);
2764 SetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE, 0.0f);
2765 SetFloatValue(PLAYER_FIELD_WEAPON_DMG_MULTIPLIERS, 1.0f);
2767 SetInt32Value(UNIT_FIELD_ATTACK_POWER, 0 );
2768 SetInt32Value(UNIT_FIELD_ATTACK_POWER_MOD_POS, 0 );
2769 SetInt32Value(UNIT_FIELD_ATTACK_POWER_MOD_NEG, 0 );
2770 SetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER,0.0f);
2771 SetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER, 0 );
2772 SetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MOD_POS,0 );
2773 SetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MOD_NEG,0 );
2774 SetFloatValue(UNIT_FIELD_RANGED_ATTACK_POWER_MULTIPLIER,0.0f);
2776 // Base crit values (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset
2777 SetFloatValue(PLAYER_CRIT_PERCENTAGE, 0.0f);
2778 SetFloatValue(PLAYER_OFFHAND_CRIT_PERCENTAGE, 0.0f);
2779 SetFloatValue(PLAYER_RANGED_CRIT_PERCENTAGE, 0.0f);
2781 // Init spell schools (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset
2782 for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
2783 SetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1 + i, 0.0f);
2785 SetFloatValue(PLAYER_PARRY_PERCENTAGE, 0.0f);
2786 SetFloatValue(PLAYER_BLOCK_PERCENTAGE, 0.0f);
2787 SetUInt32Value(PLAYER_SHIELD_BLOCK, uint32(BASE_BLOCK_DAMAGE_PERCENT));
2789 // Dodge percentage
2790 SetFloatValue(PLAYER_DODGE_PERCENTAGE, 0.0f);
2792 // set armor (resistance 0) to original value (create_agility*2)
2793 SetArmor(int32(m_createStats[STAT_AGILITY] * 2));
2794 SetResistanceBuffMods(SpellSchools(0), true, 0.0f);
2795 SetResistanceBuffMods(SpellSchools(0), false, 0.0f);
2796 // set other resistance to original value (0)
2797 for (int i = 1; i < MAX_SPELL_SCHOOL; ++i)
2799 SetResistance(SpellSchools(i), 0);
2800 SetResistanceBuffMods(SpellSchools(i), true, 0.0f);
2801 SetResistanceBuffMods(SpellSchools(i), false, 0.0f);
2804 SetUInt32Value(PLAYER_FIELD_MOD_TARGET_RESISTANCE, 0);
2805 SetUInt32Value(PLAYER_FIELD_MOD_TARGET_PHYSICAL_RESISTANCE, 0);
2806 for (int i = 0; i < MAX_SPELL_SCHOOL; ++i)
2808 SetUInt32Value(UNIT_FIELD_POWER_COST_MODIFIER + i, 0);
2809 SetFloatValue(UNIT_FIELD_POWER_COST_MULTIPLIER + i, 0.0f);
2811 // Reset no reagent cost field
2812 for (int i = 0; i < 3; ++i)
2813 SetUInt32Value(PLAYER_NO_REAGENT_COST_1 + i, 0);
2814 // Init data for form but skip reapply item mods for form
2815 InitDataForForm(reapplyMods);
2817 // save new stats
2818 for (int i = POWER_MANA; i < MAX_POWERS; ++i)
2819 SetMaxPower(Powers(i), GetCreateMaxPowers(Powers(i)));
2821 SetMaxHealth(basehp); // stamina bonus will applied later
2823 // cleanup mounted state (it will set correctly at aura loading if player saved at mount.
2824 SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, 0);
2826 // cleanup unit flags (will be re-applied if need at aura load).
2827 RemoveFlag(UNIT_FIELD_FLAGS,
2828 UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_NOT_ATTACKABLE_1 |
2829 UNIT_FLAG_OOC_NOT_ATTACKABLE | UNIT_FLAG_PASSIVE | UNIT_FLAG_LOOTING |
2830 UNIT_FLAG_PET_IN_COMBAT | UNIT_FLAG_SILENCED | UNIT_FLAG_PACIFIED |
2831 UNIT_FLAG_STUNNED | UNIT_FLAG_IN_COMBAT | UNIT_FLAG_DISARMED |
2832 UNIT_FLAG_CONFUSED | UNIT_FLAG_FLEEING | UNIT_FLAG_NOT_SELECTABLE |
2833 UNIT_FLAG_SKINNABLE | UNIT_FLAG_MOUNT | UNIT_FLAG_TAXI_FLIGHT);
2834 SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE); // must be set
2836 SetFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_REGENERATE_POWER); // must be set
2838 // cleanup player flags (will be re-applied if need at aura load), to avoid have ghost flag without ghost aura, for example.
2839 RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_AFK | PLAYER_FLAGS_DND | PLAYER_FLAGS_GM | PLAYER_FLAGS_GHOST);
2841 RemoveStandFlags(UNIT_STAND_FLAGS_ALL); // one form stealth modified bytes
2842 RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP | UNIT_BYTE2_FLAG_SANCTUARY);
2844 // restore if need some important flags
2845 SetUInt32Value(PLAYER_FIELD_BYTES2, 0); // flags empty by default
2847 if (reapplyMods) // reapply stats values only on .reset stats (level) command
2848 _ApplyAllStatBonuses();
2850 // set current level health and mana/energy to maximum after applying all mods.
2851 SetHealth(GetMaxHealth());
2852 SetPower(POWER_MANA, GetMaxPower(POWER_MANA));
2853 SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY));
2854 if (GetPower(POWER_RAGE) > GetMaxPower(POWER_RAGE))
2855 SetPower(POWER_RAGE, GetMaxPower(POWER_RAGE));
2856 SetPower(POWER_FOCUS, 0);
2857 SetPower(POWER_RUNIC_POWER, 0);
2859 // update level to hunter/summon pet
2860 if (Pet* pet = GetPet())
2861 pet->SynchronizeLevelWithOwner();
2864 void Player::SendInitialSpells()
2866 time_t curTime = time(NULL);
2867 time_t infTime = curTime + infinityCooldownDelayCheck;
2869 uint16 spellCount = 0;
2871 WorldPacket data(SMSG_INITIAL_SPELLS, (1 + 2 + 4 * m_spells.size() + 2 + m_spellCooldowns.size() * (2 + 2 + 2 + 4 + 4)));
2872 data << uint8(0);
2874 size_t countPos = data.wpos();
2875 data << uint16(spellCount); // spell count placeholder
2877 for (PlayerSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr)
2879 if (itr->second.state == PLAYERSPELL_REMOVED)
2880 continue;
2882 if (!itr->second.active || itr->second.disabled)
2883 continue;
2885 data << uint32(itr->first);
2886 data << uint16(0); // it's not slot id
2888 spellCount += 1;
2891 data.put<uint16>(countPos, spellCount); // write real count value
2893 uint16 spellCooldowns = m_spellCooldowns.size();
2894 data << uint16(spellCooldowns);
2895 for (SpellCooldowns::const_iterator itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); ++itr)
2897 SpellEntry const* sEntry = sSpellStore.LookupEntry(itr->first);
2898 if (!sEntry)
2899 continue;
2901 data << uint32(itr->first);
2903 data << uint16(itr->second.itemid); // cast item id
2904 data << uint16(sEntry->GetCategory()); // spell category
2906 // send infinity cooldown in special format
2907 if (itr->second.end >= infTime)
2909 data << uint32(1); // cooldown
2910 data << uint32(0x80000000); // category cooldown
2911 continue;
2914 time_t cooldown = itr->second.end > curTime ? (itr->second.end - curTime) * IN_MILLISECONDS : 0;
2916 if(sEntry->GetCategory()) // may be wrong, but anyway better than nothing...
2918 data << uint32(0); // cooldown
2919 data << uint32(cooldown); // category cooldown
2921 else
2923 data << uint32(cooldown); // cooldown
2924 data << uint32(0); // category cooldown
2928 GetSession()->SendPacket(&data);
2930 DETAIL_LOG("CHARACTER: Sent Initial Spells");
2933 void Player::RemoveMail(uint32 id)
2935 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
2937 if ((*itr)->messageID == id)
2939 // do not delete item, because Player::removeMail() is called when returning mail to sender.
2940 m_mail.erase(itr);
2941 return;
2946 void Player::SendMailResult(uint32 mailId, MailResponseType mailAction, MailResponseResult mailError, uint32 equipError, uint32 item_guid, uint32 item_count)
2948 WorldPacket data(SMSG_SEND_MAIL_RESULT, (4 + 4 + 4 + (mailError == MAIL_ERR_EQUIP_ERROR ? 4 : (mailAction == MAIL_ITEM_TAKEN ? 4 + 4 : 0))));
2949 data << (uint32) mailId;
2950 data << (uint32) mailAction;
2951 data << (uint32) mailError;
2952 if (mailError == MAIL_ERR_EQUIP_ERROR)
2953 data << (uint32) equipError;
2954 else if (mailAction == MAIL_ITEM_TAKEN)
2956 data << (uint32) item_guid; // item guid low?
2957 data << (uint32) item_count; // item count?
2959 GetSession()->SendPacket(&data);
2962 void Player::SendNewMail()
2964 // deliver undelivered mail
2965 WorldPacket data(SMSG_RECEIVED_MAIL, 4);
2966 data << float(0.0f);
2967 GetSession()->SendPacket(&data);
2970 void Player::UpdateNextMailTimeAndUnreads()
2972 // calculate next delivery time (min. from non-delivered mails
2973 // and recalculate unReadMail
2974 time_t cTime = time(NULL);
2975 m_nextMailDelivereTime = 0;
2976 unReadMails = 0;
2977 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
2979 if ((*itr)->deliver_time > cTime)
2981 if (!m_nextMailDelivereTime || m_nextMailDelivereTime > (*itr)->deliver_time)
2982 m_nextMailDelivereTime = (*itr)->deliver_time;
2984 else if (((*itr)->checked & MAIL_CHECK_MASK_READ) == 0)
2985 ++unReadMails;
2989 void Player::AddNewMailDeliverTime(time_t deliver_time)
2991 if (deliver_time <= time(NULL)) // ready now
2993 ++unReadMails;
2994 SendNewMail();
2996 else // not ready and no have ready mails
2998 if (!m_nextMailDelivereTime || m_nextMailDelivereTime > deliver_time)
2999 m_nextMailDelivereTime = deliver_time;
3003 bool Player::addSpell(uint32 spell_id, bool active, bool learning, bool dependent, bool disabled)
3005 SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id);
3006 if (!spellInfo)
3008 // do character spell book cleanup (all characters)
3009 if (!IsInWorld() && !learning) // spell load case
3011 sLog.outError("Player::addSpell: nonexistent in SpellStore spell #%u request, deleting for all characters in `character_spell`.", spell_id);
3012 CharacterDatabase.PExecute("DELETE FROM character_spell WHERE spell = '%u'", spell_id);
3014 else
3015 sLog.outError("Player::addSpell: nonexistent in SpellStore spell #%u request.", spell_id);
3017 return false;
3020 if (!SpellMgr::IsSpellValid(spellInfo, this, false))
3022 // do character spell book cleanup (all characters)
3023 if (!IsInWorld() && !learning) // spell load case
3025 sLog.outError("Player::addSpell: Broken spell #%u learning not allowed, deleting for all characters in `character_spell`.", spell_id);
3026 CharacterDatabase.PExecute("DELETE FROM character_spell WHERE spell = '%u'", spell_id);
3028 else
3029 sLog.outError("Player::addSpell: Broken spell #%u learning not allowed.", spell_id);
3031 return false;
3034 PlayerSpellState state = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
3036 bool dependent_set = false;
3037 bool disabled_case = false;
3038 bool superceded_old = false;
3040 PlayerSpellMap::iterator itr = m_spells.find(spell_id);
3041 if (itr != m_spells.end())
3043 uint32 next_active_spell_id = 0;
3044 // fix activate state for non-stackable low rank (and find next spell for !active case)
3045 if (sSpellMgr.IsRankedSpellNonStackableInSpellBook(spellInfo))
3047 SpellChainMapNext const& nextMap = sSpellMgr.GetSpellChainNext();
3048 for (SpellChainMapNext::const_iterator next_itr = nextMap.lower_bound(spell_id); next_itr != nextMap.upper_bound(spell_id); ++next_itr)
3050 if (HasSpell(next_itr->second))
3052 // high rank already known so this must !active
3053 active = false;
3054 next_active_spell_id = next_itr->second;
3055 break;
3060 // not do anything if already known in expected state
3061 if (itr->second.state != PLAYERSPELL_REMOVED && itr->second.active == active &&
3062 itr->second.dependent == dependent && itr->second.disabled == disabled)
3064 if (!IsInWorld() && !learning) // explicitly load from DB and then exist in it already and set correctly
3065 itr->second.state = PLAYERSPELL_UNCHANGED;
3067 return false;
3070 // dependent spell known as not dependent, overwrite state
3071 if (itr->second.state != PLAYERSPELL_REMOVED && !itr->second.dependent && dependent)
3073 itr->second.dependent = dependent;
3074 if (itr->second.state != PLAYERSPELL_NEW)
3075 itr->second.state = PLAYERSPELL_CHANGED;
3076 dependent_set = true;
3079 // update active state for known spell
3080 if (itr->second.active != active && itr->second.state != PLAYERSPELL_REMOVED && !itr->second.disabled)
3082 itr->second.active = active;
3084 if (!IsInWorld() && !learning && !dependent_set)// explicitly load from DB and then exist in it already and set correctly
3085 itr->second.state = PLAYERSPELL_UNCHANGED;
3086 else if (itr->second.state != PLAYERSPELL_NEW)
3087 itr->second.state = PLAYERSPELL_CHANGED;
3089 if (active)
3091 if (IsNeedCastPassiveLikeSpellAtLearn(spellInfo))
3092 CastSpell(this, spell_id, true);
3094 else if (IsInWorld())
3096 if (next_active_spell_id)
3098 // update spell ranks in spellbook and action bar
3099 WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4);
3100 data << uint32(spell_id);
3101 data << uint32(next_active_spell_id);
3102 GetSession()->SendPacket(&data);
3104 else
3106 WorldPacket data(SMSG_REMOVED_SPELL, 4);
3107 data << uint32(spell_id);
3108 GetSession()->SendPacket(&data);
3112 return active; // learn (show in spell book if active now)
3115 if (itr->second.disabled != disabled && itr->second.state != PLAYERSPELL_REMOVED)
3117 if (itr->second.state != PLAYERSPELL_NEW)
3118 itr->second.state = PLAYERSPELL_CHANGED;
3119 itr->second.disabled = disabled;
3121 if (disabled)
3122 return false;
3124 disabled_case = true;
3126 else switch (itr->second.state)
3128 case PLAYERSPELL_UNCHANGED: // known saved spell
3129 return false;
3130 case PLAYERSPELL_REMOVED: // re-learning removed not saved spell
3132 m_spells.erase(itr);
3133 state = PLAYERSPELL_CHANGED;
3134 break; // need re-add
3136 default: // known not saved yet spell (new or modified)
3138 // can be in case spell loading but learned at some previous spell loading
3139 if (!IsInWorld() && !learning && !dependent_set)
3140 itr->second.state = PLAYERSPELL_UNCHANGED;
3142 return false;
3147 TalentSpellPos const* talentPos = GetTalentSpellPos(spell_id);
3149 if (!disabled_case) // skip new spell adding if spell already known (disabled spells case)
3151 // talent: unlearn all other talent ranks (high and low)
3152 if (talentPos)
3154 if (TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentPos->talent_id))
3156 for (int i = 0; i < MAX_TALENT_RANK; ++i)
3158 // skip learning spell and no rank spell case
3159 uint32 rankSpellId = talentInfo->RankID[i];
3160 if (!rankSpellId || rankSpellId == spell_id)
3161 continue;
3163 removeSpell(rankSpellId, false, false);
3167 // non talent spell: learn low ranks (recursive call)
3168 else if (uint32 prev_spell = sSpellMgr.GetPrevSpellInChain(spell_id))
3170 if (!IsInWorld() || disabled) // at spells loading, no output, but allow save
3171 addSpell(prev_spell, active, true, true, disabled);
3172 else // at normal learning
3173 learnSpell(prev_spell, true);
3176 PlayerSpell newspell;
3177 newspell.state = state;
3178 newspell.active = active;
3179 newspell.dependent = dependent;
3180 newspell.disabled = disabled;
3182 // replace spells in action bars and spellbook to bigger rank if only one spell rank must be accessible
3183 if (newspell.active && !newspell.disabled && sSpellMgr.IsRankedSpellNonStackableInSpellBook(spellInfo))
3185 for (PlayerSpellMap::iterator itr2 = m_spells.begin(); itr2 != m_spells.end(); ++itr2)
3187 if (itr2->second.state == PLAYERSPELL_REMOVED) continue;
3188 SpellEntry const* i_spellInfo = sSpellStore.LookupEntry(itr2->first);
3189 if (!i_spellInfo) continue;
3191 if (sSpellMgr.IsRankSpellDueToSpell(spellInfo, itr2->first))
3193 if (itr2->second.active)
3195 if (sSpellMgr.IsHighRankOfSpell(spell_id, itr2->first))
3197 if (IsInWorld()) // not send spell (re-/over-)learn packets at loading
3199 WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4);
3200 data << uint32(itr2->first);
3201 data << uint32(spell_id);
3202 GetSession()->SendPacket(&data);
3205 // mark old spell as disable (SMSG_SUPERCEDED_SPELL replace it in client by new)
3206 itr2->second.active = false;
3207 if (itr2->second.state != PLAYERSPELL_NEW)
3208 itr2->second.state = PLAYERSPELL_CHANGED;
3209 superceded_old = true; // new spell replace old in action bars and spell book.
3211 else if (sSpellMgr.IsHighRankOfSpell(itr2->first, spell_id))
3213 if (IsInWorld()) // not send spell (re-/over-)learn packets at loading
3215 WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4);
3216 data << uint32(spell_id);
3217 data << uint32(itr2->first);
3218 GetSession()->SendPacket(&data);
3221 // mark new spell as disable (not learned yet for client and will not learned)
3222 newspell.active = false;
3223 if (newspell.state != PLAYERSPELL_NEW)
3224 newspell.state = PLAYERSPELL_CHANGED;
3231 m_spells[spell_id] = newspell;
3233 // return false if spell disabled
3234 if (newspell.disabled)
3235 return false;
3238 if (talentPos)
3240 // update talent map
3241 PlayerTalentMap::iterator iter = m_talents[m_activeSpec].find(talentPos->talent_id);
3242 if (iter != m_talents[m_activeSpec].end())
3244 // check if ranks different or removed
3245 if ((*iter).second.state == PLAYERSPELL_REMOVED || talentPos->rank != (*iter).second.currentRank)
3247 (*iter).second.currentRank = talentPos->rank;
3249 if ((*iter).second.state != PLAYERSPELL_NEW)
3250 (*iter).second.state = PLAYERSPELL_CHANGED;
3253 else
3255 PlayerTalent talent;
3256 talent.currentRank = talentPos->rank;
3257 talent.talentEntry = sTalentStore.LookupEntry(talentPos->talent_id);
3258 talent.state = IsInWorld() ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
3259 m_talents[m_activeSpec][talentPos->talent_id] = talent;
3262 // update used talent points count
3263 m_usedTalentCount += GetTalentSpellCost(talentPos);
3264 UpdateFreeTalentPoints(false);
3267 // update free primary prof.points (if any, can be none in case GM .learn prof. learning)
3268 if (uint32 freeProfs = GetFreePrimaryProfessionPoints())
3270 if (sSpellMgr.IsPrimaryProfessionFirstRankSpell(spell_id))
3271 SetFreePrimaryProfessions(freeProfs - 1);
3274 // cast talents with SPELL_EFFECT_LEARN_SPELL (other dependent spells will learned later as not auto-learned)
3275 // note: all spells with SPELL_EFFECT_LEARN_SPELL isn't passive
3276 if (talentPos && IsSpellHaveEffect(spellInfo, SPELL_EFFECT_LEARN_SPELL))
3278 // ignore stance requirement for talent learn spell (stance set for spell only for client spell description show)
3279 CastSpell(this, spell_id, true);
3281 // also cast passive (and passive like) spells (including all talents without SPELL_EFFECT_LEARN_SPELL) with additional checks
3282 else if (IsNeedCastPassiveLikeSpellAtLearn(spellInfo))
3284 CastSpell(this, spell_id, true);
3286 else if (IsSpellHaveEffect(spellInfo, SPELL_EFFECT_SKILL_STEP))
3288 CastSpell(this, spell_id, true);
3289 return false;
3292 // add dependent skills
3293 uint16 maxskill = GetMaxSkillValueForLevel();
3295 SpellLearnSkillNode const* spellLearnSkill = sSpellMgr.GetSpellLearnSkill(spell_id);
3297 SkillLineAbilityMapBounds skill_bounds = sSpellMgr.GetSkillLineAbilityMapBounds(spell_id);
3299 if (spellLearnSkill)
3301 uint32 skill_value = GetPureSkillValue(spellLearnSkill->skill);
3302 uint32 skill_max_value = GetPureMaxSkillValue(spellLearnSkill->skill);
3304 if (skill_value < spellLearnSkill->value)
3305 skill_value = spellLearnSkill->value;
3307 uint32 new_skill_max_value = spellLearnSkill->maxvalue == 0 ? maxskill : spellLearnSkill->maxvalue;
3309 if (skill_max_value < new_skill_max_value)
3310 skill_max_value = new_skill_max_value;
3312 SetSkill(spellLearnSkill->skill, skill_value, skill_max_value, spellLearnSkill->step);
3314 else
3316 // not ranked skills
3317 for (SkillLineAbilityMap::const_iterator _spell_idx = skill_bounds.first; _spell_idx != skill_bounds.second; ++_spell_idx)
3319 SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(_spell_idx->second->skillId);
3320 if (!pSkill)
3321 continue;
3323 if (HasSkill(pSkill->id))
3324 continue;
3326 if (_spell_idx->second->learnOnGetSkill == ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL ||
3327 // lockpicking/runeforging special case, not have ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL
3328 (pSkill->id == SKILL_RUNEFORGING && _spell_idx->second->max_value == 0))
3330 switch (GetSkillRangeType(pSkill, _spell_idx->second->racemask != 0))
3332 case SKILL_RANGE_LANGUAGE:
3333 SetSkill(pSkill->id, 300, 300, GetSkillStep(pSkill->id));
3334 break;
3335 case SKILL_RANGE_LEVEL:
3336 SetSkill(pSkill->id, 1, GetMaxSkillValueForLevel(), GetSkillStep(pSkill->id));
3337 break;
3338 case SKILL_RANGE_MONO:
3339 SetSkill(pSkill->id, 1, 1, GetSkillStep(pSkill->id));
3340 break;
3341 default:
3342 break;
3348 // learn dependent spells
3349 SpellLearnSpellMapBounds spell_bounds = sSpellMgr.GetSpellLearnSpellMapBounds(spell_id);
3351 for (SpellLearnSpellMap::const_iterator itr2 = spell_bounds.first; itr2 != spell_bounds.second; ++itr2)
3353 if (!itr2->second.autoLearned)
3355 if (!IsInWorld() || !itr2->second.active) // at spells loading, no output, but allow save
3356 addSpell(itr2->second.spell, itr2->second.active, true, true, false);
3357 else // at normal learning
3358 learnSpell(itr2->second.spell, true);
3362 if (!GetSession()->PlayerLoading())
3364 // not ranked skills
3365 for (SkillLineAbilityMap::const_iterator _spell_idx = skill_bounds.first; _spell_idx != skill_bounds.second; ++_spell_idx)
3367 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LINE, _spell_idx->second->skillId);
3368 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILLLINE_SPELLS, _spell_idx->second->skillId);
3371 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL, spell_id);
3374 // return true (for send learn packet) only if spell active (in case ranked spells) and not replace old spell
3375 return active && !disabled && !superceded_old;
3378 bool Player::IsNeedCastPassiveLikeSpellAtLearn(SpellEntry const* spellInfo) const
3380 ShapeshiftForm form = GetShapeshiftForm();
3382 if (IsNeedCastSpellAtFormApply(spellInfo, form)) // SPELL_ATTR_PASSIVE | SPELL_ATTR_UNK7 spells
3383 return true; // all stance req. cases, not have auarastate cases
3385 if (!spellInfo->HasAttribute(SPELL_ATTR_PASSIVE))
3386 return false;
3388 // note: form passives activated with shapeshift spells be implemented by HandleShapeshiftBoosts instead of spell_learn_spell
3389 // talent dependent passives activated at form apply have proper stance data
3390 SpellShapeshiftEntry const* shapeShift = spellInfo->GetSpellShapeshift();
3391 bool need_cast = (!shapeShift || !shapeShift->Stances || (!form && spellInfo->HasAttribute(SPELL_ATTR_EX2_NOT_NEED_SHAPESHIFT)));
3393 // Check CasterAuraStates
3394 SpellAuraRestrictionsEntry const* auraRestrictions = spellInfo->GetSpellAuraRestrictions();
3395 return need_cast && (!auraRestrictions || !auraRestrictions->CasterAuraState || HasAuraState(AuraState(auraRestrictions->CasterAuraState)));
3398 void Player::learnSpell(uint32 spell_id, bool dependent)
3400 PlayerSpellMap::iterator itr = m_spells.find(spell_id);
3402 bool disabled = (itr != m_spells.end()) ? itr->second.disabled : false;
3403 bool active = disabled ? itr->second.active : true;
3405 bool learning = addSpell(spell_id, active, true, dependent, false);
3407 // prevent duplicated entires in spell book, also not send if not in world (loading)
3408 if (learning && IsInWorld())
3410 WorldPacket data(SMSG_LEARNED_SPELL, 6);
3411 data << uint32(spell_id);
3412 data << uint32(0); // 3.3.3 unk
3413 GetSession()->SendPacket(&data);
3416 // learn all disabled higher ranks (recursive)
3417 if (disabled)
3419 SpellChainMapNext const& nextMap = sSpellMgr.GetSpellChainNext();
3420 for (SpellChainMapNext::const_iterator i = nextMap.lower_bound(spell_id); i != nextMap.upper_bound(spell_id); ++i)
3422 PlayerSpellMap::iterator iter = m_spells.find(i->second);
3423 if (iter != m_spells.end() && iter->second.disabled)
3424 learnSpell(i->second, false);
3428 if (IsInWorld())
3429 SpellAddedQuestCheck(spell_id);
3432 void Player::removeSpell(uint32 spell_id, bool disabled, bool learn_low_rank, bool sendUpdate)
3434 PlayerSpellMap::iterator itr = m_spells.find(spell_id);
3435 if (itr == m_spells.end())
3436 return;
3438 if (itr->second.state == PLAYERSPELL_REMOVED || (disabled && itr->second.disabled))
3439 return;
3441 // unlearn non talent higher ranks (recursive)
3442 SpellChainMapNext const& nextMap = sSpellMgr.GetSpellChainNext();
3443 for (SpellChainMapNext::const_iterator itr2 = nextMap.lower_bound(spell_id); itr2 != nextMap.upper_bound(spell_id); ++itr2)
3444 if (HasSpell(itr2->second) && !GetTalentSpellPos(itr2->second))
3445 removeSpell(itr2->second, disabled, false);
3447 // re-search, it can be corrupted in prev loop
3448 itr = m_spells.find(spell_id);
3449 if (itr == m_spells.end() || itr->second.state == PLAYERSPELL_REMOVED)
3450 return; // already unleared
3452 bool cur_active = itr->second.active;
3453 bool cur_dependent = itr->second.dependent;
3455 if (disabled)
3457 itr->second.disabled = disabled;
3458 if (itr->second.state != PLAYERSPELL_NEW)
3459 itr->second.state = PLAYERSPELL_CHANGED;
3461 else
3463 if (itr->second.state == PLAYERSPELL_NEW)
3464 m_spells.erase(itr);
3465 else
3466 itr->second.state = PLAYERSPELL_REMOVED;
3469 RemoveAurasDueToSpell(spell_id);
3471 // remove pet auras
3472 for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
3473 if (PetAura const* petSpell = sSpellMgr.GetPetAura(spell_id, SpellEffectIndex(i)))
3474 RemovePetAura(petSpell);
3476 TalentSpellPos const* talentPos = GetTalentSpellPos(spell_id);
3477 if (talentPos)
3479 // update talent map
3480 PlayerTalentMap::iterator iter = m_talents[m_activeSpec].find(talentPos->talent_id);
3481 if (iter != m_talents[m_activeSpec].end())
3483 if ((*iter).second.state != PLAYERSPELL_NEW)
3484 (*iter).second.state = PLAYERSPELL_REMOVED;
3485 else
3486 m_talents[m_activeSpec].erase(iter);
3488 else
3489 sLog.outError("removeSpell: Player (GUID: %u) has talent spell (id: %u) but doesn't have talent", GetGUIDLow(), spell_id);
3491 // free talent points
3492 uint32 talentCosts = GetTalentSpellCost(talentPos);
3494 if (talentCosts < m_usedTalentCount)
3495 m_usedTalentCount -= talentCosts;
3496 else
3497 m_usedTalentCount = 0;
3499 UpdateFreeTalentPoints(false);
3502 // update free primary prof.points (if not overflow setting, can be in case GM use before .learn prof. learning)
3503 if (sSpellMgr.IsPrimaryProfessionFirstRankSpell(spell_id))
3505 uint32 freeProfs = GetFreePrimaryProfessionPoints() + 1;
3506 uint32 maxProfs = GetSession()->GetSecurity() < AccountTypes(sWorld.getConfig(CONFIG_UINT32_TRADE_SKILL_GMIGNORE_MAX_PRIMARY_COUNT)) ? sWorld.getConfig(CONFIG_UINT32_MAX_PRIMARY_TRADE_SKILL) : 10;
3507 if (freeProfs <= maxProfs)
3508 SetFreePrimaryProfessions(freeProfs);
3511 // remove dependent skill
3512 SpellLearnSkillNode const* spellLearnSkill = sSpellMgr.GetSpellLearnSkill(spell_id);
3513 if (spellLearnSkill)
3515 uint32 prev_spell = sSpellMgr.GetPrevSpellInChain(spell_id);
3516 if (!prev_spell) // first rank, remove skill
3517 SetSkill(spellLearnSkill->skill, 0, 0);
3518 else
3520 // search prev. skill setting by spell ranks chain
3521 SpellLearnSkillNode const* prevSkill = sSpellMgr.GetSpellLearnSkill(prev_spell);
3522 while (!prevSkill && prev_spell)
3524 prev_spell = sSpellMgr.GetPrevSpellInChain(prev_spell);
3525 prevSkill = sSpellMgr.GetSpellLearnSkill(sSpellMgr.GetFirstSpellInChain(prev_spell));
3528 if (!prevSkill) // not found prev skill setting, remove skill
3529 SetSkill(spellLearnSkill->skill, 0, 0);
3530 else // set to prev. skill setting values
3532 uint32 skill_value = GetPureSkillValue(prevSkill->skill);
3533 uint32 skill_max_value = GetPureMaxSkillValue(prevSkill->skill);
3535 if (skill_value > prevSkill->value)
3536 skill_value = prevSkill->value;
3538 uint32 new_skill_max_value = prevSkill->maxvalue == 0 ? GetMaxSkillValueForLevel() : prevSkill->maxvalue;
3540 if (skill_max_value > new_skill_max_value)
3541 skill_max_value = new_skill_max_value;
3543 SetSkill(prevSkill->skill, skill_value, skill_max_value, prevSkill->step);
3548 else
3550 // not ranked skills
3551 SkillLineAbilityMapBounds bounds = sSpellMgr.GetSkillLineAbilityMapBounds(spell_id);
3553 for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
3555 SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(_spell_idx->second->skillId);
3556 if (!pSkill)
3557 continue;
3559 if ((_spell_idx->second->learnOnGetSkill == ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL &&
3560 pSkill->categoryId != SKILL_CATEGORY_CLASS) ||// not unlearn class skills (spellbook/talent pages)
3561 // lockpicking/runeforging special case, not have ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL
3562 (pSkill->id == SKILL_RUNEFORGING && _spell_idx->second->max_value == 0))
3564 // not reset skills for professions and racial abilities
3565 if ((pSkill->categoryId == SKILL_CATEGORY_SECONDARY || pSkill->categoryId == SKILL_CATEGORY_PROFESSION) &&
3566 (IsProfessionSkill(pSkill->id) || _spell_idx->second->racemask != 0))
3567 continue;
3569 SetSkill(pSkill->id, 0, 0, GetSkillStep(pSkill->id));
3574 // remove dependent spells
3575 SpellLearnSpellMapBounds spell_bounds = sSpellMgr.GetSpellLearnSpellMapBounds(spell_id);
3577 for (SpellLearnSpellMap::const_iterator itr2 = spell_bounds.first; itr2 != spell_bounds.second; ++itr2)
3578 removeSpell(itr2->second.spell, disabled);
3580 // activate lesser rank in spellbook/action bar, and cast it if need
3581 bool prev_activate = false;
3583 if (uint32 prev_id = sSpellMgr.GetPrevSpellInChain(spell_id))
3585 SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id);
3587 // if talent then lesser rank also talent and need learn
3588 if (talentPos)
3590 if (learn_low_rank)
3591 learnSpell(prev_id, false);
3593 // if ranked non-stackable spell: need activate lesser rank and update dependence state
3594 else if (cur_active && sSpellMgr.IsRankedSpellNonStackableInSpellBook(spellInfo))
3596 // need manually update dependence state (learn spell ignore like attempts)
3597 PlayerSpellMap::iterator prev_itr = m_spells.find(prev_id);
3598 if (prev_itr != m_spells.end())
3600 if (prev_itr->second.dependent != cur_dependent)
3602 prev_itr->second.dependent = cur_dependent;
3603 if (prev_itr->second.state != PLAYERSPELL_NEW)
3604 prev_itr->second.state = PLAYERSPELL_CHANGED;
3607 // now re-learn if need re-activate
3608 if (cur_active && !prev_itr->second.active && learn_low_rank)
3610 if (addSpell(prev_id, true, false, prev_itr->second.dependent, prev_itr->second.disabled))
3612 // downgrade spell ranks in spellbook and action bar
3613 WorldPacket data(SMSG_SUPERCEDED_SPELL, 4 + 4);
3614 data << uint32(spell_id);
3615 data << uint32(prev_id);
3616 GetSession()->SendPacket(&data);
3617 prev_activate = true;
3624 // for Titan's Grip and shaman Dual-wield
3625 if (CanDualWield() || CanTitanGrip())
3627 SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id);
3629 if (CanDualWield() && IsSpellHaveEffect(spellInfo, SPELL_EFFECT_DUAL_WIELD))
3630 SetCanDualWield(false);
3632 if (CanTitanGrip() && IsSpellHaveEffect(spellInfo, SPELL_EFFECT_TITAN_GRIP))
3634 SetCanTitanGrip(false);
3635 // Remove Titan's Grip damage penalty now
3636 RemoveAurasDueToSpell(49152);
3640 // for talents and normal spell unlearn that allow offhand use for some weapons
3641 if (sWorld.getConfig(CONFIG_BOOL_OFFHAND_CHECK_AT_TALENTS_RESET))
3642 AutoUnequipOffhandIfNeed();
3644 // remove from spell book if not replaced by lesser rank
3645 if (!prev_activate && sendUpdate)
3647 WorldPacket data(SMSG_REMOVED_SPELL, 4);
3648 data << uint32(spell_id);
3649 GetSession()->SendPacket(&data);
3652 if (IsInWorld())
3653 SpellRemovedQuestCheck(spell_id);
3656 void Player::RemoveSpellCooldown(uint32 spell_id, bool update /* = false */)
3658 m_spellCooldowns.erase(spell_id);
3660 if (update)
3661 SendClearCooldown(spell_id, this);
3664 void Player::RemoveSpellCategoryCooldown(uint32 cat, bool update /* = false */)
3666 SpellCategoryStore::const_iterator ct = sSpellCategoryStore.find(cat);
3667 if (ct == sSpellCategoryStore.end())
3668 return;
3670 const SpellCategorySet& ct_set = ct->second;
3671 for (SpellCooldowns::const_iterator i = m_spellCooldowns.begin(); i != m_spellCooldowns.end();)
3673 if (ct_set.find(i->first) != ct_set.end())
3674 RemoveSpellCooldown((i++)->first, update);
3675 else
3676 ++i;
3680 void Player::RemoveArenaSpellCooldowns()
3682 // remove cooldowns on spells that has < 15 min CD
3683 SpellCooldowns::iterator itr, next;
3684 // iterate spell cooldowns
3685 for (itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); itr = next)
3687 next = itr;
3688 ++next;
3689 SpellEntry const* entry = sSpellStore.LookupEntry(itr->first);
3690 // check if spellentry is present and if the cooldown is less than 15 mins
3691 if( entry &&
3692 entry->GetRecoveryTime() <= 15 * MINUTE * IN_MILLISECONDS &&
3693 entry->GetCategoryRecoveryTime() <= 15 * MINUTE * IN_MILLISECONDS )
3695 // remove & notify
3696 RemoveSpellCooldown(itr->first, true);
3701 void Player::RemoveAllSpellCooldown()
3703 if (!m_spellCooldowns.empty())
3705 ObjectGuid guid = GetObjectGuid();
3707 WorldPacket data(SMSG_CLEAR_COOLDOWNS, 1 + 8 + m_spellCooldowns.size() * 4);
3708 data.WriteGuidMask<1, 3, 6>(guid);
3709 data.WriteBits(m_spellCooldowns.size(), 24); // cooldown count
3710 data.WriteGuidMask<7, 5, 2, 4, 0>(guid);
3712 data.WriteGuidBytes<7, 2, 4, 5, 1, 3>(guid);
3714 for (SpellCooldowns::const_iterator itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); ++itr)
3715 data << uint32(itr->first);
3717 data.WriteGuidBytes<0, 6>(guid);
3719 SendDirectMessage(&data);
3721 m_spellCooldowns.clear();
3725 void Player::_LoadSpellCooldowns(QueryResult* result)
3727 // some cooldowns can be already set at aura loading...
3729 // QueryResult *result = CharacterDatabase.PQuery("SELECT spell,item,time FROM character_spell_cooldown WHERE guid = '%u'",GetGUIDLow());
3731 if (result)
3733 time_t curTime = time(NULL);
3737 Field* fields = result->Fetch();
3739 uint32 spell_id = fields[0].GetUInt32();
3740 uint32 item_id = fields[1].GetUInt32();
3741 time_t db_time = (time_t)fields[2].GetUInt64();
3743 if (!sSpellStore.LookupEntry(spell_id))
3745 sLog.outError("Player %u has unknown spell %u in `character_spell_cooldown`, skipping.", GetGUIDLow(), spell_id);
3746 continue;
3749 // skip outdated cooldown
3750 if (db_time <= curTime)
3751 continue;
3753 AddSpellCooldown(spell_id, item_id, db_time);
3755 DEBUG_LOG("Player (GUID: %u) spell %u, item %u cooldown loaded (%u secs).", GetGUIDLow(), spell_id, item_id, uint32(db_time - curTime));
3757 while (result->NextRow());
3759 delete result;
3763 void Player::_SaveSpellCooldowns()
3765 static SqlStatementID deleteSpellCooldown ;
3766 static SqlStatementID insertSpellCooldown ;
3768 SqlStatement stmt = CharacterDatabase.CreateStatement(deleteSpellCooldown, "DELETE FROM character_spell_cooldown WHERE guid = ?");
3769 stmt.PExecute(GetGUIDLow());
3771 time_t curTime = time(NULL);
3772 time_t infTime = curTime + infinityCooldownDelayCheck;
3774 // remove outdated and save active
3775 for (SpellCooldowns::iterator itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end();)
3777 if (itr->second.end <= curTime)
3778 m_spellCooldowns.erase(itr++);
3779 else if (itr->second.end <= infTime) // not save locked cooldowns, it will be reset or set at reload
3781 stmt = CharacterDatabase.CreateStatement(insertSpellCooldown, "INSERT INTO character_spell_cooldown (guid,spell,item,time) VALUES( ?, ?, ?, ?)");
3782 stmt.PExecute(GetGUIDLow(), itr->first, itr->second.itemid, uint64(itr->second.end));
3783 ++itr;
3785 else
3786 ++itr;
3790 uint32 Player::resetTalentsCost() const
3792 // The first time reset costs 1 gold
3793 if (m_resetTalentsCost < 1 * GOLD)
3794 return 1 * GOLD;
3795 // then 5 gold
3796 else if (m_resetTalentsCost < 5 * GOLD)
3797 return 5 * GOLD;
3798 // After that it increases in increments of 5 gold
3799 else if (m_resetTalentsCost < 10 * GOLD)
3800 return 10 * GOLD;
3801 else
3803 time_t months = (sWorld.GetGameTime() - m_resetTalentsTime) / MONTH;
3804 if (months > 0)
3806 // This cost will be reduced by a rate of 5 gold per month
3807 int32 new_cost = int32((m_resetTalentsCost) - 5 * GOLD * months);
3808 // to a minimum of 10 gold.
3809 return uint32(new_cost < 10 * GOLD ? 10 * GOLD : new_cost);
3811 else
3813 // After that it increases in increments of 5 gold
3814 int32 new_cost = m_resetTalentsCost + 5 * GOLD;
3815 // until it hits a cap of 50 gold.
3816 if (new_cost > 50 * GOLD)
3817 new_cost = 50 * GOLD;
3818 return new_cost;
3823 bool Player::resetTalents(bool no_cost, bool all_specs)
3825 // not need after this call
3826 if (HasAtLoginFlag(AT_LOGIN_RESET_TALENTS) && all_specs)
3827 RemoveAtLoginFlag(AT_LOGIN_RESET_TALENTS, true);
3829 if (m_usedTalentCount == 0 && !all_specs)
3831 UpdateFreeTalentPoints(false); // for fix if need counter
3832 return false;
3835 uint32 cost = 0;
3837 if (!no_cost)
3839 cost = resetTalentsCost();
3841 if (GetMoney() < cost)
3843 SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0);
3844 return false;
3848 for (PlayerTalentMap::iterator iter = m_talents[m_activeSpec].begin(); iter != m_talents[m_activeSpec].end();)
3850 if (iter->second.state == PLAYERSPELL_REMOVED)
3852 ++iter;
3853 continue;
3856 TalentEntry const* talentInfo = iter->second.talentEntry;
3857 if (!talentInfo)
3859 m_talents[m_activeSpec].erase(iter++);
3860 continue;
3863 TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab);
3865 if (!talentTabInfo)
3867 m_talents[m_activeSpec].erase(iter++);
3868 continue;
3871 // unlearn only talents for character class
3872 // some spell learned by one class as normal spells or know at creation but another class learn it as talent,
3873 // to prevent unexpected lost normal learned spell skip another class talents
3874 if ((getClassMask() & talentTabInfo->ClassMask) == 0)
3876 ++iter;
3877 continue;
3880 for (int j = 0; j < MAX_TALENT_RANK; ++j)
3881 if (talentInfo->RankID[j])
3882 removeSpell(talentInfo->RankID[j], !IsPassiveSpell(talentInfo->RankID[j]), false);
3884 iter = m_talents[m_activeSpec].begin();
3887 // Remove spec specific spells
3888 for (uint32 i = 0; i < MAX_TALENT_TABS; ++i)
3890 if (std::vector<uint32> const* specSpells = GetTalentTreeMasterySpells(GetTalentTabPages(getClass())[i]))
3891 for (size_t i = 0; i < specSpells->size(); ++i)
3892 removeSpell(specSpells->at(i), true);
3894 if (std::vector<uint32> const* specSpells = GetTalentTreePrimarySpells(GetTalentTabPages(getClass())[i]))
3895 for (size_t i = 0; i < specSpells->size(); ++i)
3896 removeSpell(specSpells->at(i), true);
3899 for (uint8 spec = 0; spec < MAX_TALENT_SPEC_COUNT; ++spec)
3901 if (!all_specs && spec != m_activeSpec)
3902 continue;
3904 m_talentsPrimaryTree[spec] = 0;
3907 // for not current spec just mark removed all saved to DB case and drop not saved
3908 if (all_specs)
3910 for (uint8 spec = 0; spec < MAX_TALENT_SPEC_COUNT; ++spec)
3912 if (spec == m_activeSpec)
3913 continue;
3915 for (PlayerTalentMap::iterator iter = m_talents[spec].begin(); iter != m_talents[spec].end();)
3917 switch (iter->second.state)
3919 case PLAYERSPELL_REMOVED:
3920 ++iter;
3921 break;
3922 case PLAYERSPELL_NEW:
3923 m_talents[spec].erase(iter++);
3924 break;
3925 default:
3926 iter->second.state = PLAYERSPELL_REMOVED;
3927 ++iter;
3928 break;
3934 UpdateFreeTalentPoints(false);
3936 if (!no_cost)
3938 ModifyMoney(-(int64)cost);
3939 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TALENTS, cost);
3940 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_NUMBER_OF_TALENT_RESETS, 1);
3942 m_resetTalentsCost = cost;
3943 m_resetTalentsTime = time(NULL);
3946 // Update talent tree role-dependent mana regen
3947 UpdateManaRegen();
3949 UpdateArmorSpecializations();
3951 // FIXME: remove pet before or after unlearn spells? for now after unlearn to allow removing of talent related, pet affecting auras
3952 RemovePet(PET_SAVE_REAGENTS);
3953 /* when prev line will dropped use next line
3954 if(Pet* pet = GetPet())
3956 if(pet->getPetType()==HUNTER_PET && !pet->GetCreatureInfo()->isTameable(CanTameExoticPets()))
3957 pet->Unsummon(PET_SAVE_REAGENTS, this);
3960 return true;
3963 Mail* Player::GetMail(uint32 id)
3965 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
3967 if ((*itr)->messageID == id)
3969 return (*itr);
3972 return NULL;
3975 void Player::_SetCreateBits(UpdateMask* updateMask, Player* target) const
3977 if (target == this)
3979 Object::_SetCreateBits(updateMask, target);
3981 else
3983 for (uint16 index = 0; index < m_valuesCount; ++index)
3985 if (GetUInt32Value(index) != 0 && updateVisualBits.GetBit(index))
3986 updateMask->SetBit(index);
3991 void Player::_SetUpdateBits(UpdateMask* updateMask, Player* target) const
3993 if (target == this)
3995 Object::_SetUpdateBits(updateMask, target);
3997 else
3999 Object::_SetUpdateBits(updateMask, target);
4000 *updateMask &= updateVisualBits;
4004 void Player::InitVisibleBits()
4006 updateVisualBits.SetCount(PLAYER_END);
4008 updateVisualBits.SetBit(OBJECT_FIELD_GUID + 0);
4009 updateVisualBits.SetBit(OBJECT_FIELD_GUID + 1);
4010 updateVisualBits.SetBit(OBJECT_FIELD_TYPE);
4011 updateVisualBits.SetBit(OBJECT_FIELD_ENTRY);
4012 updateVisualBits.SetBit(OBJECT_FIELD_DATA + 0);
4013 updateVisualBits.SetBit(OBJECT_FIELD_DATA + 1);
4014 updateVisualBits.SetBit(OBJECT_FIELD_SCALE_X);
4015 updateVisualBits.SetBit(UNIT_FIELD_CHARM + 0);
4016 updateVisualBits.SetBit(UNIT_FIELD_CHARM + 1);
4017 updateVisualBits.SetBit(UNIT_FIELD_SUMMON + 0);
4018 updateVisualBits.SetBit(UNIT_FIELD_SUMMON + 1);
4019 updateVisualBits.SetBit(UNIT_FIELD_CHARMEDBY + 0);
4020 updateVisualBits.SetBit(UNIT_FIELD_CHARMEDBY + 1);
4021 updateVisualBits.SetBit(UNIT_FIELD_TARGET + 0);
4022 updateVisualBits.SetBit(UNIT_FIELD_TARGET + 1);
4023 updateVisualBits.SetBit(UNIT_FIELD_CHANNEL_OBJECT + 0);
4024 updateVisualBits.SetBit(UNIT_FIELD_CHANNEL_OBJECT + 1);
4025 updateVisualBits.SetBit(UNIT_FIELD_BYTES_0);
4026 updateVisualBits.SetBit(UNIT_FIELD_HEALTH);
4027 updateVisualBits.SetBit(UNIT_FIELD_POWER1);
4028 updateVisualBits.SetBit(UNIT_FIELD_POWER2);
4029 updateVisualBits.SetBit(UNIT_FIELD_POWER3);
4030 updateVisualBits.SetBit(UNIT_FIELD_POWER4);
4031 updateVisualBits.SetBit(UNIT_FIELD_POWER5);
4032 updateVisualBits.SetBit(UNIT_FIELD_MAXHEALTH);
4033 updateVisualBits.SetBit(UNIT_FIELD_MAXPOWER1);
4034 updateVisualBits.SetBit(UNIT_FIELD_MAXPOWER2);
4035 updateVisualBits.SetBit(UNIT_FIELD_MAXPOWER3);
4036 updateVisualBits.SetBit(UNIT_FIELD_MAXPOWER4);
4037 updateVisualBits.SetBit(UNIT_FIELD_MAXPOWER5);
4038 updateVisualBits.SetBit(UNIT_FIELD_LEVEL);
4039 updateVisualBits.SetBit(UNIT_FIELD_FACTIONTEMPLATE);
4040 updateVisualBits.SetBit(UNIT_VIRTUAL_ITEM_SLOT_ID + 0);
4041 updateVisualBits.SetBit(UNIT_VIRTUAL_ITEM_SLOT_ID + 1);
4042 updateVisualBits.SetBit(UNIT_VIRTUAL_ITEM_SLOT_ID + 2);
4043 updateVisualBits.SetBit(UNIT_FIELD_FLAGS);
4044 updateVisualBits.SetBit(UNIT_FIELD_FLAGS_2);
4045 updateVisualBits.SetBit(UNIT_FIELD_AURASTATE);
4046 updateVisualBits.SetBit(UNIT_FIELD_BASEATTACKTIME + 0);
4047 updateVisualBits.SetBit(UNIT_FIELD_BASEATTACKTIME + 1);
4048 updateVisualBits.SetBit(UNIT_FIELD_BOUNDINGRADIUS);
4049 updateVisualBits.SetBit(UNIT_FIELD_COMBATREACH);
4050 updateVisualBits.SetBit(UNIT_FIELD_DISPLAYID);
4051 updateVisualBits.SetBit(UNIT_FIELD_NATIVEDISPLAYID);
4052 updateVisualBits.SetBit(UNIT_FIELD_MOUNTDISPLAYID);
4053 updateVisualBits.SetBit(UNIT_FIELD_BYTES_1);
4054 updateVisualBits.SetBit(UNIT_FIELD_PETNUMBER);
4055 updateVisualBits.SetBit(UNIT_FIELD_PET_NAME_TIMESTAMP);
4056 updateVisualBits.SetBit(UNIT_DYNAMIC_FLAGS);
4057 updateVisualBits.SetBit(UNIT_CHANNEL_SPELL);
4058 updateVisualBits.SetBit(UNIT_MOD_CAST_SPEED);
4059 updateVisualBits.SetBit(UNIT_NPC_FLAGS);
4060 updateVisualBits.SetBit(UNIT_FIELD_BASE_MANA);
4061 updateVisualBits.SetBit(UNIT_FIELD_BYTES_2);
4062 updateVisualBits.SetBit(UNIT_FIELD_HOVERHEIGHT);
4064 updateVisualBits.SetBit(PLAYER_DUEL_ARBITER + 0);
4065 updateVisualBits.SetBit(PLAYER_DUEL_ARBITER + 1);
4066 updateVisualBits.SetBit(PLAYER_FLAGS);
4067 //updateVisualBits.SetBit(PLAYER_GUILDID);
4068 updateVisualBits.SetBit(PLAYER_GUILDRANK);
4069 updateVisualBits.SetBit(PLAYER_GUILDLEVEL);
4070 updateVisualBits.SetBit(PLAYER_BYTES);
4071 updateVisualBits.SetBit(PLAYER_BYTES_2);
4072 updateVisualBits.SetBit(PLAYER_BYTES_3);
4073 updateVisualBits.SetBit(PLAYER_DUEL_TEAM);
4074 updateVisualBits.SetBit(PLAYER_GUILD_TIMESTAMP);
4075 updateVisualBits.SetBit(UNIT_NPC_FLAGS);
4077 // PLAYER_QUEST_LOG_x also visible bit on official (but only on party/raid)...
4078 for (uint16 i = PLAYER_QUEST_LOG_1_1; i < PLAYER_QUEST_LOG_25_2; i += MAX_QUEST_OFFSET)
4079 updateVisualBits.SetBit(i);
4081 // Players visible items are not inventory stuff
4082 for (uint16 i = 0; i < EQUIPMENT_SLOT_END; ++i)
4084 uint32 offset = i * 2;
4086 // item entry
4087 updateVisualBits.SetBit(PLAYER_VISIBLE_ITEM_1_ENTRYID + offset);
4088 // enchant
4089 updateVisualBits.SetBit(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + offset);
4092 updateVisualBits.SetBit(PLAYER_CHOSEN_TITLE);
4095 void Player::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const
4097 if (target == this)
4099 for (int i = 0; i < EQUIPMENT_SLOT_END; ++i)
4101 if (m_items[i] == NULL)
4102 continue;
4104 m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
4106 for (int i = INVENTORY_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
4108 if (m_items[i] == NULL)
4109 continue;
4111 m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
4115 SetPhaseAndMap(target);
4116 Unit::BuildCreateUpdateBlockForPlayer(data, target);
4119 void Player::SetPhaseAndMap(Player* target) const
4121 if (QueryResult *result = CharacterDatabase.PQuery("SELECT map, phase FROM character_phase_data WHERE guid = '%u'", target->GetGUIDLow()))
4123 Field *fields = result->Fetch();
4125 uint16 mapId = fields[0].GetUInt16();
4126 uint32 phase = fields[1].GetUInt32();
4128 target->GetSession()->SendSetPhaseShift(phase, mapId);
4130 delete result;
4134 void Player::DestroyForPlayer(Player* target, bool anim) const
4136 Unit::DestroyForPlayer(target, anim);
4138 for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
4140 if (m_items[i] == NULL)
4141 continue;
4143 m_items[i]->DestroyForPlayer(target);
4146 if (target == this)
4148 for (int i = INVENTORY_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
4150 if (m_items[i] == NULL)
4151 continue;
4153 m_items[i]->DestroyForPlayer(target);
4158 bool Player::HasSpell(uint32 spell) const
4160 PlayerSpellMap::const_iterator itr = m_spells.find(spell);
4161 return (itr != m_spells.end() && itr->second.state != PLAYERSPELL_REMOVED &&
4162 !itr->second.disabled);
4165 bool Player::HasActiveSpell(uint32 spell) const
4167 PlayerSpellMap::const_iterator itr = m_spells.find(spell);
4168 return (itr != m_spells.end() && itr->second.state != PLAYERSPELL_REMOVED &&
4169 itr->second.active && !itr->second.disabled);
4172 TrainerSpellState Player::GetTrainerSpellState(TrainerSpell const* trainer_spell, uint32 reqLevel) const
4174 if (!trainer_spell)
4175 return TRAINER_SPELL_RED;
4177 if (!trainer_spell->learnedSpell)
4178 return TRAINER_SPELL_RED;
4180 // known spell
4181 if (HasSpell(trainer_spell->learnedSpell))
4182 return TRAINER_SPELL_GRAY;
4184 // check race/class requirement
4185 if (!IsSpellFitByClassAndRace(trainer_spell->learnedSpell))
4186 return TRAINER_SPELL_RED;
4188 bool prof = SpellMgr::IsProfessionSpell(trainer_spell->learnedSpell);
4190 // check level requirement
4191 if (!prof || GetSession()->GetSecurity() < AccountTypes(sWorld.getConfig(CONFIG_UINT32_TRADE_SKILL_GMIGNORE_LEVEL)))
4192 if (getLevel() < reqLevel)
4193 return TRAINER_SPELL_RED;
4195 if (SpellChainNode const* spell_chain = sSpellMgr.GetSpellChainNode(trainer_spell->learnedSpell))
4197 // check prev.rank requirement
4198 if (spell_chain->prev && !HasSpell(spell_chain->prev))
4199 return TRAINER_SPELL_RED;
4201 // check additional spell requirement
4202 if (spell_chain->req && !HasSpell(spell_chain->req))
4203 return TRAINER_SPELL_RED;
4206 // check skill requirement
4207 if (!prof || GetSession()->GetSecurity() < AccountTypes(sWorld.getConfig(CONFIG_UINT32_TRADE_SKILL_GMIGNORE_SKILL)))
4208 if (trainer_spell->reqSkill && GetBaseSkillValue(trainer_spell->reqSkill) < trainer_spell->reqSkillValue)
4209 return TRAINER_SPELL_RED;
4211 // exist, already checked at loading
4212 SpellEntry const* spell = sSpellStore.LookupEntry(trainer_spell->learnedSpell);
4214 // secondary prof. or not prof. spell
4215 SpellEffectEntry const* spellEffect = spell->GetSpellEffect(EFFECT_INDEX_1);
4216 uint32 skill = spellEffect ? spellEffect->EffectMiscValue : 0;
4218 if(spellEffect && (spellEffect->Effect != SPELL_EFFECT_SKILL || !IsPrimaryProfessionSkill(skill)))
4219 return TRAINER_SPELL_GREEN;
4221 // check primary prof. limit
4222 if (sSpellMgr.IsPrimaryProfessionFirstRankSpell(spell->Id) && GetFreePrimaryProfessionPoints() == 0)
4223 return TRAINER_SPELL_GREEN_DISABLED;
4225 return TRAINER_SPELL_GREEN;
4229 * Deletes a character from the database
4231 * The way, how the characters will be deleted is decided based on the config option.
4233 * @see Player::DeleteOldCharacters
4235 * @param playerguid the low-GUID from the player which should be deleted
4236 * @param accountId the account id from the player
4237 * @param updateRealmChars when this flag is set, the amount of characters on that realm will be updated in the realmlist
4238 * @param deleteFinally if this flag is set, the config option will be ignored and the character will be permanently removed from the database
4240 void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRealmChars, bool deleteFinally)
4242 // for nonexistent account avoid update realm
4243 if (accountId == 0)
4244 updateRealmChars = false;
4246 uint32 charDelete_method = sWorld.getConfig(CONFIG_UINT32_CHARDELETE_METHOD);
4247 uint32 charDelete_minLvl = sWorld.getConfig(CONFIG_UINT32_CHARDELETE_MIN_LEVEL);
4249 // if we want to finally delete the character or the character does not meet the level requirement, we set it to mode 0
4250 if (deleteFinally || Player::GetLevelFromDB(playerguid) < charDelete_minLvl)
4251 charDelete_method = 0;
4253 uint32 lowguid = playerguid.GetCounter();
4255 // convert corpse to bones if exist (to prevent exiting Corpse in World without DB entry)
4256 // bones will be deleted by corpse/bones deleting thread shortly
4257 sObjectAccessor.ConvertCorpseForPlayer(playerguid);
4259 // remove from guild
4260 if (uint32 guildId = GetGuildIdFromDB(playerguid))
4262 if (Guild* guild = sGuildMgr.GetGuildById(guildId))
4264 if (guild->DelMember(playerguid))
4266 guild->Disband();
4267 delete guild;
4272 // remove from arena teams
4273 LeaveAllArenaTeams(playerguid);
4275 // the player was uninvited already on logout so just remove from group
4276 QueryResult* resultGroup = CharacterDatabase.PQuery("SELECT groupId FROM group_member WHERE memberGuid='%u'", lowguid);
4277 if (resultGroup)
4279 uint32 groupId = (*resultGroup)[0].GetUInt32();
4280 delete resultGroup;
4281 if (Group* group = sObjectMgr.GetGroupById(groupId))
4282 RemoveFromGroup(group, playerguid);
4285 // remove signs from petitions (also remove petitions if owner);
4286 RemovePetitionsAndSigns(playerguid);
4288 switch (charDelete_method)
4290 // completely remove from the database
4291 case 0:
4293 // return back all mails with COD and Item 0 1 2 3 4 5 6 7
4294 QueryResult* resultMail = CharacterDatabase.PQuery("SELECT id,messageType,mailTemplateId,sender,subject,body,money,has_items FROM mail WHERE receiver='%u' AND has_items<>0 AND cod<>0", lowguid);
4295 if (resultMail)
4299 Field* fields = resultMail->Fetch();
4301 uint32 mail_id = fields[0].GetUInt32();
4302 uint16 mailType = fields[1].GetUInt16();
4303 uint16 mailTemplateId = fields[2].GetUInt16();
4304 uint32 sender = fields[3].GetUInt32();
4305 std::string subject = fields[4].GetCppString();
4306 std::string body = fields[5].GetCppString();
4307 uint64 money = fields[6].GetUInt32();
4308 bool has_items = fields[7].GetBool();
4310 // we can return mail now
4311 // so firstly delete the old one
4312 CharacterDatabase.PExecute("DELETE FROM mail WHERE id = '%u'", mail_id);
4314 // mail not from player
4315 if (mailType != MAIL_NORMAL)
4317 if (has_items)
4318 CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mail_id);
4319 continue;
4322 MailDraft draft;
4323 if (mailTemplateId)
4324 draft.SetMailTemplate(mailTemplateId, false);// items already included
4325 else
4326 draft.SetSubjectAndBody(subject, body);
4328 if (has_items)
4330 // data needs to be at first place for Item::LoadFromDB
4331 // 0 1 2 3
4332 QueryResult* resultItems = CharacterDatabase.PQuery("SELECT data,text,item_guid,item_template FROM mail_items JOIN item_instance ON item_guid = guid WHERE mail_id='%u'", mail_id);
4333 if (resultItems)
4337 Field* fields2 = resultItems->Fetch();
4339 uint32 item_guidlow = fields2[2].GetUInt32();
4340 uint32 item_template = fields2[3].GetUInt32();
4342 ItemPrototype const* itemProto = ObjectMgr::GetItemPrototype(item_template);
4343 if (!itemProto)
4345 CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid = '%u'", item_guidlow);
4346 continue;
4349 Item* pItem = NewItemOrBag(itemProto);
4350 if (!pItem->LoadFromDB(item_guidlow, fields2, playerguid))
4352 pItem->FSetState(ITEM_REMOVED);
4353 pItem->SaveToDB(); // it also deletes item object !
4354 continue;
4357 draft.AddItem(pItem);
4359 while (resultItems->NextRow());
4361 delete resultItems;
4365 CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mail_id);
4367 uint32 pl_account = sObjectMgr.GetPlayerAccountIdByGUID(playerguid);
4369 draft.SetMoney(money).SendReturnToSender(pl_account, playerguid, ObjectGuid(HIGHGUID_PLAYER, sender));
4371 while (resultMail->NextRow());
4373 delete resultMail;
4376 // unsummon and delete for pets in world is not required: player deleted from CLI or character list with not loaded pet.
4377 // Get guids of character's pets, will deleted in transaction
4378 QueryResult* resultPets = CharacterDatabase.PQuery("SELECT id FROM character_pet WHERE owner = '%u'", lowguid);
4380 // delete char from friends list when selected chars is online (non existing - error)
4381 QueryResult* resultFriend = CharacterDatabase.PQuery("SELECT DISTINCT guid FROM character_social WHERE friend = '%u'", lowguid);
4383 // NOW we can finally clear other DB data related to character
4384 CharacterDatabase.BeginTransaction();
4385 if (resultPets)
4389 Field* fields3 = resultPets->Fetch();
4390 uint32 petguidlow = fields3[0].GetUInt32();
4391 // do not create separate transaction for pet delete otherwise we will get fatal error!
4392 Pet::DeleteFromDB(petguidlow, false);
4394 while (resultPets->NextRow());
4395 delete resultPets;
4398 // cleanup friends for online players, offline case will cleanup later in code
4399 if (resultFriend)
4403 Field* fieldsFriend = resultFriend->Fetch();
4404 if (Player* sFriend = sObjectAccessor.FindPlayer(ObjectGuid(HIGHGUID_PLAYER, fieldsFriend[0].GetUInt32())))
4406 if (sFriend->IsInWorld())
4408 sFriend->GetSocial()->RemoveFromSocialList(playerguid, false);
4409 sSocialMgr.SendFriendStatus(sFriend, FRIEND_REMOVED, playerguid, false);
4413 while (resultFriend->NextRow());
4414 delete resultFriend;
4417 CharacterDatabase.PExecute("DELETE FROM characters WHERE guid = '%u'", lowguid);
4418 CharacterDatabase.PExecute("DELETE FROM character_account_data WHERE guid = '%u'", lowguid);
4419 CharacterDatabase.PExecute("DELETE FROM character_declinedname WHERE guid = '%u'", lowguid);
4420 CharacterDatabase.PExecute("DELETE FROM character_action WHERE guid = '%u'", lowguid);
4421 CharacterDatabase.PExecute("DELETE FROM character_aura WHERE guid = '%u'", lowguid);
4422 CharacterDatabase.PExecute("DELETE FROM character_battleground_data WHERE guid = '%u'", lowguid);
4423 CharacterDatabase.PExecute("DELETE FROM character_gifts WHERE guid = '%u'", lowguid);
4424 CharacterDatabase.PExecute("DELETE FROM character_glyphs WHERE guid = '%u'", lowguid);
4425 CharacterDatabase.PExecute("DELETE FROM character_homebind WHERE guid = '%u'", lowguid);
4426 CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u'", lowguid);
4427 CharacterDatabase.PExecute("DELETE FROM group_instance WHERE leaderGuid = '%u'", lowguid);
4428 CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE guid = '%u'", lowguid);
4429 CharacterDatabase.PExecute("DELETE FROM character_queststatus WHERE guid = '%u'", lowguid);
4430 CharacterDatabase.PExecute("DELETE FROM character_queststatus_daily WHERE guid = '%u'", lowguid);
4431 CharacterDatabase.PExecute("DELETE FROM character_queststatus_weekly WHERE guid = '%u'", lowguid);
4432 CharacterDatabase.PExecute("DELETE FROM character_reputation WHERE guid = '%u'", lowguid);
4433 CharacterDatabase.PExecute("DELETE FROM character_skills WHERE guid = '%u'", lowguid);
4434 CharacterDatabase.PExecute("DELETE FROM character_spell WHERE guid = '%u'", lowguid);
4435 CharacterDatabase.PExecute("DELETE FROM character_spell_cooldown WHERE guid = '%u'", lowguid);
4436 CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u'", lowguid);
4437 CharacterDatabase.PExecute("DELETE FROM character_ticket WHERE guid = '%u'", lowguid);
4438 CharacterDatabase.PExecute("DELETE FROM item_instance WHERE owner_guid = '%u'", lowguid);
4439 CharacterDatabase.PExecute("DELETE FROM character_social WHERE guid = '%u' OR friend='%u'", lowguid, lowguid);
4440 CharacterDatabase.PExecute("DELETE FROM mail WHERE receiver = '%u'", lowguid);
4441 CharacterDatabase.PExecute("DELETE FROM mail_items WHERE receiver = '%u'", lowguid);
4442 CharacterDatabase.PExecute("DELETE FROM character_pet WHERE owner = '%u'", lowguid);
4443 CharacterDatabase.PExecute("DELETE FROM character_pet_declinedname WHERE owner = '%u'", lowguid);
4444 CharacterDatabase.PExecute("DELETE FROM character_achievement WHERE guid = '%u'", lowguid);
4445 CharacterDatabase.PExecute("DELETE FROM character_achievement_progress WHERE guid = '%u'", lowguid);
4446 CharacterDatabase.PExecute("DELETE FROM character_equipmentsets WHERE guid = '%u'", lowguid);
4447 CharacterDatabase.PExecute("DELETE FROM guild_eventlog WHERE PlayerGuid1 = '%u' OR PlayerGuid2 = '%u'", lowguid, lowguid);
4448 CharacterDatabase.PExecute("DELETE FROM guild_bank_eventlog WHERE PlayerGuid = '%u'", lowguid);
4449 CharacterDatabase.PExecute("DELETE FROM character_currencies WHERE guid = '%u'", lowguid);
4450 CharacterDatabase.CommitTransaction();
4451 break;
4453 // The character gets unlinked from the account, the name gets freed up and appears as deleted ingame
4454 case 1:
4455 CharacterDatabase.PExecute("UPDATE characters SET deleteInfos_Name=name, deleteInfos_Account=account, deleteDate='" UI64FMTD "', name='', account=0 WHERE guid=%u", uint64(time(NULL)), lowguid);
4456 break;
4457 default:
4458 sLog.outError("Player::DeleteFromDB: Unsupported delete method: %u.", charDelete_method);
4461 if (updateRealmChars)
4462 sWorld.UpdateRealmCharCount(accountId);
4466 * Characters which were kept back in the database after being deleted and are now too old (see config option "CharDelete.KeepDays"), will be completely deleted.
4468 * @see Player::DeleteFromDB
4470 void Player::DeleteOldCharacters()
4472 uint32 keepDays = sWorld.getConfig(CONFIG_UINT32_CHARDELETE_KEEP_DAYS);
4473 if (!keepDays)
4474 return;
4476 Player::DeleteOldCharacters(keepDays);
4480 * Characters which were kept back in the database after being deleted and are older than the specified amount of days, will be completely deleted.
4482 * @see Player::DeleteFromDB
4484 * @param keepDays overrite the config option by another amount of days
4486 void Player::DeleteOldCharacters(uint32 keepDays)
4488 sLog.outString("Player::DeleteOldChars: Deleting all characters which have been deleted %u days before...", keepDays);
4490 QueryResult* resultChars = CharacterDatabase.PQuery("SELECT guid, deleteInfos_Account FROM characters WHERE deleteDate IS NOT NULL AND deleteDate < '" UI64FMTD "'", uint64(time(NULL) - time_t(keepDays * DAY)));
4491 if (resultChars)
4493 sLog.outString("Player::DeleteOldChars: Found %u character(s) to delete", uint32(resultChars->GetRowCount()));
4496 Field* charFields = resultChars->Fetch();
4497 ObjectGuid guid = ObjectGuid(HIGHGUID_PLAYER, charFields[0].GetUInt32());
4498 Player::DeleteFromDB(guid, charFields[1].GetUInt32(), true, true);
4500 while (resultChars->NextRow());
4501 delete resultChars;
4505 void Player::SetRoot(bool enable)
4507 WorldPacket data;
4508 BuildForceMoveRootPacket(&data, enable, 0);
4509 GetSession()->SendPacket(&data);
4512 void Player::SetWaterWalk(bool enable)
4514 WorldPacket data;
4515 BuildMoveWaterWalkPacket(&data, enable, 0);
4516 GetSession()->SendPacket(&data);
4519 /* Preconditions:
4520 - a resurrectable corpse must not be loaded for the player (only bones)
4521 - the player must be in world
4523 void Player::BuildPlayerRepop()
4525 WorldPacket data(SMSG_PRE_RESURRECT, GetPackGUID().size());
4526 data << GetPackGUID();
4527 GetSession()->SendPacket(&data);
4529 if (getRace() == RACE_NIGHTELF)
4530 CastSpell(this, 20584, true); // auras SPELL_AURA_INCREASE_SPEED(+speed in wisp form), SPELL_AURA_INCREASE_SWIM_SPEED(+swim speed in wisp form), SPELL_AURA_TRANSFORM (to wisp form)
4531 CastSpell(this, 8326, true); // auras SPELL_AURA_GHOST, SPELL_AURA_INCREASE_SPEED(why?), SPELL_AURA_INCREASE_SWIM_SPEED(why?)
4533 // there must be SMSG.FORCE_RUN_SPEED_CHANGE, SMSG.FORCE_SWIM_SPEED_CHANGE, SMSG.MOVE_WATER_WALK
4534 // there must be SMSG.STOP_MIRROR_TIMER
4535 // there we must send 888 opcode
4537 // the player cannot have a corpse already, only bones which are not returned by GetCorpse
4538 if (GetCorpse())
4540 sLog.outError("BuildPlayerRepop: player %s(%d) already has a corpse", GetName(), GetGUIDLow());
4541 MANGOS_ASSERT(false);
4544 // create a corpse and place it at the player's location
4545 Corpse* corpse = CreateCorpse();
4546 if (!corpse)
4548 sLog.outError("Error creating corpse for Player %s [%u]", GetName(), GetGUIDLow());
4549 return;
4551 GetMap()->Add(corpse);
4553 // convert player body to ghost
4554 SetHealth(1);
4556 SetWaterWalk(true);
4557 if (!GetSession()->isLogingOut())
4558 SetRoot(false);
4560 // BG - remove insignia related
4561 RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE);
4563 SendCorpseReclaimDelay();
4565 // to prevent cheating
4566 corpse->ResetGhostTime();
4568 StopMirrorTimers(); // disable timers(bars)
4570 // set and clear other
4571 SetByteValue(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_ALWAYS_STAND);
4574 void Player::ResurrectPlayer(float restore_percent, bool applySickness)
4576 WorldPacket data(SMSG_DEATH_RELEASE_LOC, 4 * 4); // remove spirit healer position
4577 data << uint32(-1);
4578 data << float(0);
4579 data << float(0);
4580 data << float(0);
4581 GetSession()->SendPacket(&data);
4583 // speed change, land walk
4585 // remove death flag + set aura
4586 SetByteValue(UNIT_FIELD_BYTES_1, 3, 0x00);
4588 SetDeathState(ALIVE);
4590 if (getRace() == RACE_NIGHTELF)
4591 RemoveAurasDueToSpell(20584); // speed bonuses
4592 RemoveAurasDueToSpell(8326); // SPELL_AURA_GHOST
4594 SetWaterWalk(false);
4595 SetRoot(false);
4597 m_deathTimer = 0;
4599 // set health/powers (0- will be set in caller)
4600 if (restore_percent > 0.0f)
4602 SetHealth(uint32(GetMaxHealth()*restore_percent));
4603 SetPower(POWER_MANA, uint32(GetMaxPower(POWER_MANA)*restore_percent));
4604 SetPower(POWER_RAGE, 0);
4605 SetPower(POWER_ENERGY, uint32(GetMaxPower(POWER_ENERGY)*restore_percent));
4608 // trigger update zone for alive state zone updates
4609 uint32 newzone, newarea;
4610 GetZoneAndAreaId(newzone, newarea);
4611 UpdateZone(newzone, newarea);
4613 // update visibility of world around viewpoint
4614 m_camera.UpdateVisibilityForOwner();
4615 // update visibility of player for nearby cameras
4616 UpdateObjectVisibility();
4618 if (!applySickness)
4619 return;
4621 // Characters from level 1-10 are not affected by resurrection sickness.
4622 // Characters from level 11-19 will suffer from one minute of sickness
4623 // for each level they are above 10.
4624 // Characters level 20 and up suffer from ten minutes of sickness.
4625 int32 startLevel = sWorld.getConfig(CONFIG_INT32_DEATH_SICKNESS_LEVEL);
4627 if (int32(getLevel()) >= startLevel)
4629 // set resurrection sickness
4630 CastSpell(this, SPELL_ID_PASSIVE_RESURRECTION_SICKNESS, true);
4632 // not full duration
4633 if (int32(getLevel()) < startLevel + 9)
4635 int32 delta = (int32(getLevel()) - startLevel + 1) * MINUTE;
4637 if (SpellAuraHolder* holder = GetSpellAuraHolder(SPELL_ID_PASSIVE_RESURRECTION_SICKNESS))
4639 holder->SetAuraDuration(delta * IN_MILLISECONDS);
4640 holder->SendAuraUpdate(false);
4646 void Player::KillPlayer()
4648 SetRoot(true);
4650 StopMirrorTimers(); // disable timers(bars)
4652 SetDeathState(CORPSE);
4653 // SetFlag( UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_IN_PVP );
4655 SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE);
4656 ApplyModByteFlag(PLAYER_FIELD_BYTES, 0, PLAYER_FIELD_BYTE_RELEASE_TIMER, !sMapStore.LookupEntry(GetMapId())->Instanceable());
4658 // 6 minutes until repop at graveyard
4659 m_deathTimer = 6 * MINUTE * IN_MILLISECONDS;
4661 UpdateCorpseReclaimDelay(); // dependent at use SetDeathPvP() call before kill
4663 // don't create corpse at this moment, player might be falling
4665 // update visibility
4666 UpdateObjectVisibility();
4669 Corpse* Player::CreateCorpse()
4671 // prevent existence 2 corpse for player
4672 SpawnCorpseBones();
4674 Corpse* corpse = new Corpse((m_ExtraFlags & PLAYER_EXTRA_PVP_DEATH) ? CORPSE_RESURRECTABLE_PVP : CORPSE_RESURRECTABLE_PVE);
4675 SetPvPDeath(false);
4677 if (!corpse->Create(sObjectMgr.GenerateCorpseLowGuid(), this))
4679 delete corpse;
4680 return NULL;
4683 uint8 skin = GetByteValue(PLAYER_BYTES, 0);
4684 uint8 face = GetByteValue(PLAYER_BYTES, 1);
4685 uint8 hairstyle = GetByteValue(PLAYER_BYTES, 2);
4686 uint8 haircolor = GetByteValue(PLAYER_BYTES, 3);
4687 uint8 facialhair = GetByteValue(PLAYER_BYTES_2, 0);
4689 corpse->SetByteValue(CORPSE_FIELD_BYTES_1, 1, getRace());
4690 corpse->SetByteValue(CORPSE_FIELD_BYTES_1, 2, getGender());
4691 corpse->SetByteValue(CORPSE_FIELD_BYTES_1, 3, skin);
4693 corpse->SetByteValue(CORPSE_FIELD_BYTES_2, 0, face);
4694 corpse->SetByteValue(CORPSE_FIELD_BYTES_2, 1, hairstyle);
4695 corpse->SetByteValue(CORPSE_FIELD_BYTES_2, 2, haircolor);
4696 corpse->SetByteValue(CORPSE_FIELD_BYTES_2, 3, facialhair);
4698 uint32 flags = CORPSE_FLAG_UNK2;
4699 if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_HELM))
4700 flags |= CORPSE_FLAG_HIDE_HELM;
4701 if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_CLOAK))
4702 flags |= CORPSE_FLAG_HIDE_CLOAK;
4703 if (InBattleGround() && !InArena())
4704 flags |= CORPSE_FLAG_LOOTABLE; // to be able to remove insignia
4705 corpse->SetUInt32Value(CORPSE_FIELD_FLAGS, flags);
4707 corpse->SetUInt32Value(CORPSE_FIELD_DISPLAY_ID, GetNativeDisplayId());
4709 uint32 iDisplayID;
4710 uint32 iIventoryType;
4711 uint32 _cfi;
4712 for (int i = 0; i < EQUIPMENT_SLOT_END; ++i)
4714 if (m_items[i])
4716 iDisplayID = m_items[i]->GetProto()->DisplayInfoID;
4717 iIventoryType = m_items[i]->GetProto()->InventoryType;
4719 _cfi = iDisplayID | (iIventoryType << 24);
4720 corpse->SetUInt32Value(CORPSE_FIELD_ITEM + i, _cfi);
4724 // we not need saved corpses for BG/arenas
4725 if (!GetMap()->IsBattleGroundOrArena())
4726 corpse->SaveToDB();
4728 // register for player, but not show
4729 sObjectAccessor.AddCorpse(corpse);
4730 return corpse;
4733 void Player::SpawnCorpseBones()
4735 if (sObjectAccessor.ConvertCorpseForPlayer(GetObjectGuid()))
4736 if (!GetSession()->PlayerLogoutWithSave()) // at logout we will already store the player
4737 SaveToDB(); // prevent loading as ghost without corpse
4740 Corpse* Player::GetCorpse() const
4742 return sObjectAccessor.GetCorpseForPlayerGUID(GetObjectGuid());
4745 void Player::DurabilityLossAll(double percent, bool inventory)
4747 for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
4748 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
4749 DurabilityLoss(pItem, percent);
4751 if (inventory)
4753 // bags not have durability
4754 // for(int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
4756 for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
4757 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
4758 DurabilityLoss(pItem, percent);
4760 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
4761 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
4762 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
4763 if (Item* pItem = GetItemByPos(i, j))
4764 DurabilityLoss(pItem, percent);
4768 void Player::DurabilityLoss(Item* item, double percent)
4770 if (!item)
4771 return;
4773 uint32 pMaxDurability = item ->GetUInt32Value(ITEM_FIELD_MAXDURABILITY);
4775 if (!pMaxDurability)
4776 return;
4778 uint32 pDurabilityLoss = uint32(pMaxDurability * percent);
4780 if (pDurabilityLoss < 1)
4781 pDurabilityLoss = 1;
4783 DurabilityPointsLoss(item, pDurabilityLoss);
4786 void Player::DurabilityPointsLossAll(int32 points, bool inventory)
4788 for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
4789 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
4790 DurabilityPointsLoss(pItem, points);
4792 if (inventory)
4794 // bags not have durability
4795 // for(int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
4797 for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
4798 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
4799 DurabilityPointsLoss(pItem, points);
4801 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
4802 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
4803 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
4804 if (Item* pItem = GetItemByPos(i, j))
4805 DurabilityPointsLoss(pItem, points);
4809 void Player::DurabilityPointsLoss(Item* item, int32 points)
4811 int32 pMaxDurability = item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY);
4812 int32 pOldDurability = item->GetUInt32Value(ITEM_FIELD_DURABILITY);
4813 int32 pNewDurability = pOldDurability - points;
4815 if (pNewDurability < 0)
4816 pNewDurability = 0;
4817 else if (pNewDurability > pMaxDurability)
4818 pNewDurability = pMaxDurability;
4820 if (pOldDurability != pNewDurability)
4822 // modify item stats _before_ Durability set to 0 to pass _ApplyItemMods internal check
4823 if (pNewDurability == 0 && pOldDurability > 0 && item->IsEquipped())
4824 _ApplyItemMods(item, item->GetSlot(), false);
4826 item->SetUInt32Value(ITEM_FIELD_DURABILITY, pNewDurability);
4828 // modify item stats _after_ restore durability to pass _ApplyItemMods internal check
4829 if (pNewDurability > 0 && pOldDurability == 0 && item->IsEquipped())
4830 _ApplyItemMods(item, item->GetSlot(), true);
4832 item->SetState(ITEM_CHANGED, this);
4836 void Player::DurabilityPointLossForEquipSlot(EquipmentSlots slot)
4838 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
4839 DurabilityPointsLoss(pItem, 1);
4842 uint32 Player::DurabilityRepairAll(bool cost, float discountMod, bool guildBank)
4844 uint32 TotalCost = 0;
4845 // equipped, backpack, bags itself
4846 for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i)
4847 TotalCost += DurabilityRepair(((INVENTORY_SLOT_BAG_0 << 8) | i), cost, discountMod, guildBank);
4849 // bank, buyback and keys not repaired
4851 // items in inventory bags
4852 for (int j = INVENTORY_SLOT_BAG_START; j < INVENTORY_SLOT_BAG_END; ++j)
4853 for (int i = 0; i < MAX_BAG_SIZE; ++i)
4854 TotalCost += DurabilityRepair(((j << 8) | i), cost, discountMod, guildBank);
4855 return TotalCost;
4858 uint32 Player::DurabilityRepair(uint16 pos, bool cost, float discountMod, bool guildBank)
4860 Item* item = GetItemByPos(pos);
4862 uint32 TotalCost = 0;
4863 if (!item)
4864 return TotalCost;
4866 uint32 maxDurability = item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY);
4867 if (!maxDurability)
4868 return TotalCost;
4870 uint32 curDurability = item->GetUInt32Value(ITEM_FIELD_DURABILITY);
4872 if (cost)
4874 uint32 LostDurability = maxDurability - curDurability;
4875 if (LostDurability > 0)
4877 ItemPrototype const* ditemProto = item->GetProto();
4879 DurabilityCostsEntry const* dcost = sDurabilityCostsStore.LookupEntry(ditemProto->ItemLevel);
4880 if (!dcost)
4882 sLog.outError("RepairDurability: Wrong item lvl %u", ditemProto->ItemLevel);
4883 return TotalCost;
4886 uint32 dQualitymodEntryId = (ditemProto->Quality + 1) * 2;
4887 DurabilityQualityEntry const* dQualitymodEntry = sDurabilityQualityStore.LookupEntry(dQualitymodEntryId);
4888 if (!dQualitymodEntry)
4890 sLog.outError("RepairDurability: Wrong dQualityModEntry %u", dQualitymodEntryId);
4891 return TotalCost;
4894 uint32 dmultiplier = dcost->multiplier[ItemSubClassToDurabilityMultiplierId(ditemProto->Class, ditemProto->SubClass)];
4895 uint32 costs = uint32(LostDurability * dmultiplier * double(dQualitymodEntry->quality_mod));
4897 costs = uint32(costs * discountMod);
4899 if (costs == 0) // fix for ITEM_QUALITY_ARTIFACT
4900 costs = 1;
4902 if (guildBank)
4904 if (GetGuildId() == 0)
4906 DEBUG_LOG("You are not member of a guild");
4907 return TotalCost;
4910 Guild* pGuild = sGuildMgr.GetGuildById(GetGuildId());
4911 if (!pGuild)
4912 return TotalCost;
4914 if (!pGuild->HasRankRight(GetRank(), GR_RIGHT_WITHDRAW_REPAIR))
4916 DEBUG_LOG("You do not have rights to withdraw for repairs");
4917 return TotalCost;
4920 if (pGuild->GetMemberMoneyWithdrawRem(GetGUIDLow()) < costs)
4922 DEBUG_LOG("You do not have enough money withdraw amount remaining");
4923 return TotalCost;
4926 if (pGuild->GetGuildBankMoney() < costs)
4928 DEBUG_LOG("There is not enough money in bank");
4929 return TotalCost;
4932 pGuild->MemberMoneyWithdraw(costs, GetGUIDLow());
4933 TotalCost = costs;
4935 else if (GetMoney() < costs)
4937 DEBUG_LOG("You do not have enough money");
4938 return TotalCost;
4940 else
4941 ModifyMoney(-int64(costs));
4945 item->SetUInt32Value(ITEM_FIELD_DURABILITY, maxDurability);
4946 item->SetState(ITEM_CHANGED, this);
4948 // reapply mods for total broken and repaired item if equipped
4949 if (IsEquipmentPos(pos) && !curDurability)
4950 _ApplyItemMods(item, pos & 255, true);
4951 return TotalCost;
4954 void Player::RepopAtGraveyard()
4956 // note: this can be called also when the player is alive
4957 // for example from WorldSession::HandleMovementOpcodes
4959 AreaTableEntry const* zone = GetAreaEntryByAreaID(GetAreaId());
4961 // Such zones are considered unreachable as a ghost and the player must be automatically revived
4962 if ((!isAlive() && zone && zone->flags & AREA_FLAG_NEED_FLY) || GetTransport())
4964 ResurrectPlayer(0.5f);
4965 SpawnCorpseBones();
4968 WorldSafeLocsEntry const* ClosestGrave = NULL;
4970 // Special handle for battleground maps
4971 if (BattleGround* bg = GetBattleGround())
4972 ClosestGrave = bg->GetClosestGraveYard(this);
4973 else
4974 ClosestGrave = sObjectMgr.GetClosestGraveYard(GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId(), GetTeam());
4976 // stop countdown until repop
4977 m_deathTimer = 0;
4979 // if no grave found, stay at the current location
4980 // and don't show spirit healer location
4981 if (ClosestGrave)
4983 TeleportTo(ClosestGrave->map_id, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, GetOrientation());
4984 if (isDead()) // not send if alive, because it used in TeleportTo()
4986 WorldPacket data(SMSG_DEATH_RELEASE_LOC, 4 * 4);// show spirit healer position on minimap
4987 data << ClosestGrave->map_id;
4988 data << ClosestGrave->x;
4989 data << ClosestGrave->y;
4990 data << ClosestGrave->z;
4991 GetSession()->SendPacket(&data);
4996 void Player::JoinedChannel(Channel* c)
4998 m_channels.push_back(c);
5001 void Player::LeftChannel(Channel* c)
5003 m_channels.remove(c);
5006 void Player::CleanupChannels()
5008 while (!m_channels.empty())
5010 Channel* ch = *m_channels.begin();
5011 m_channels.erase(m_channels.begin()); // remove from player's channel list
5012 ch->Leave(GetObjectGuid(), false); // not send to client, not remove from player's channel list
5013 if (ChannelMgr* cMgr = channelMgr(GetTeam()))
5014 cMgr->LeftChannel(ch->GetName()); // deleted channel if empty
5017 DEBUG_LOG("Player: channels cleaned up!");
5020 void Player::UpdateLocalChannels(uint32 newZone)
5022 if (m_channels.empty())
5023 return;
5025 AreaTableEntry const* current_zone = GetAreaEntryByAreaID(newZone);
5026 if (!current_zone)
5027 return;
5029 ChannelMgr* cMgr = channelMgr(GetTeam());
5030 if (!cMgr)
5031 return;
5033 std::string current_zone_name = current_zone->area_name[GetSession()->GetSessionDbcLocale()];
5035 for (JoinedChannelsList::iterator i = m_channels.begin(), next; i != m_channels.end(); i = next)
5037 next = i; ++next;
5039 // skip non built-in channels
5040 if (!(*i)->IsConstant())
5041 continue;
5043 ChatChannelsEntry const* ch = GetChannelEntryFor((*i)->GetChannelId());
5044 if (!ch)
5045 continue;
5047 if ((ch->flags & 4) == 4) // global channel without zone name in pattern
5048 continue;
5050 // new channel
5051 char new_channel_name_buf[100];
5052 snprintf(new_channel_name_buf, 100, ch->pattern[m_session->GetSessionDbcLocale()], current_zone_name.c_str());
5053 Channel* new_channel = cMgr->GetJoinChannel(new_channel_name_buf, ch->ChannelID);
5055 if ((*i) != new_channel)
5057 new_channel->Join(GetObjectGuid(), ""); // will output Changed Channel: N. Name
5059 // leave old channel
5060 (*i)->Leave(GetObjectGuid(), false); // not send leave channel, it already replaced at client
5061 std::string name = (*i)->GetName(); // store name, (*i)erase in LeftChannel
5062 LeftChannel(*i); // remove from player's channel list
5063 cMgr->LeftChannel(name); // delete if empty
5066 DEBUG_LOG("Player: channels cleaned up!");
5069 void Player::LeaveLFGChannel()
5071 for (JoinedChannelsList::iterator i = m_channels.begin(); i != m_channels.end(); ++i)
5073 if ((*i)->IsLFG())
5075 (*i)->Leave(GetObjectGuid());
5076 break;
5081 void Player::HandleBaseModValue(BaseModGroup modGroup, BaseModType modType, float amount, bool apply)
5083 if (modGroup >= BASEMOD_END || modType >= MOD_END)
5085 sLog.outError("ERROR in HandleBaseModValue(): nonexistent BaseModGroup of wrong BaseModType!");
5086 return;
5089 float val = 1.0f;
5091 switch (modType)
5093 case FLAT_MOD:
5094 m_auraBaseMod[modGroup][modType] += apply ? amount : -amount;
5095 break;
5096 case PCT_MOD:
5097 if (amount <= -100.0f)
5098 amount = -200.0f;
5100 val = (100.0f + amount) / 100.0f;
5101 m_auraBaseMod[modGroup][modType] *= apply ? val : (1.0f / val);
5102 break;
5105 if (!CanModifyStats())
5106 return;
5108 switch (modGroup)
5110 case CRIT_PERCENTAGE: UpdateCritPercentage(BASE_ATTACK); break;
5111 case RANGED_CRIT_PERCENTAGE: UpdateCritPercentage(RANGED_ATTACK); break;
5112 case OFFHAND_CRIT_PERCENTAGE: UpdateCritPercentage(OFF_ATTACK); break;
5113 case SHIELD_BLOCK_DAMAGE_VALUE: UpdateShieldBlockDamageValue(); break;
5114 default: break;
5118 float Player::GetBaseModValue(BaseModGroup modGroup, BaseModType modType) const
5120 if (modGroup >= BASEMOD_END || modType > MOD_END)
5122 sLog.outError("trial to access nonexistent BaseModGroup or wrong BaseModType!");
5123 return 0.0f;
5126 if (modType == PCT_MOD && m_auraBaseMod[modGroup][PCT_MOD] <= 0.0f)
5127 return 0.0f;
5129 return m_auraBaseMod[modGroup][modType];
5132 float Player::GetTotalBaseModValue(BaseModGroup modGroup) const
5134 if (modGroup >= BASEMOD_END)
5136 sLog.outError("wrong BaseModGroup in GetTotalBaseModValue()!");
5137 return 0.0f;
5140 if (m_auraBaseMod[modGroup][PCT_MOD] <= 0.0f)
5141 return 0.0f;
5143 return m_auraBaseMod[modGroup][FLAT_MOD] * m_auraBaseMod[modGroup][PCT_MOD];
5146 uint32 Player::GetShieldBlockDamageValue() const
5148 float value = m_canBlock ? BASE_BLOCK_DAMAGE_PERCENT : 0;
5149 value = (value + m_auraBaseMod[SHIELD_BLOCK_DAMAGE_VALUE][FLAT_MOD]) * m_auraBaseMod[SHIELD_BLOCK_DAMAGE_VALUE][PCT_MOD];
5151 value = (value < 0) ? 0 : value;
5153 return uint32(value);
5156 float Player::GetMeleeCritFromAgility()
5158 uint32 level = getLevel();
5159 uint32 pclass = getClass();
5161 if (level > GT_MAX_LEVEL) level = GT_MAX_LEVEL;
5163 GtChanceToMeleeCritBaseEntry const* critBase = sGtChanceToMeleeCritBaseStore.LookupEntry(pclass - 1);
5164 GtChanceToMeleeCritEntry const* critRatio = sGtChanceToMeleeCritStore.LookupEntry((pclass - 1) * GT_MAX_LEVEL + level - 1);
5165 if (critBase == NULL || critRatio == NULL)
5166 return 0.0f;
5168 float crit = critBase->base + GetStat(STAT_AGILITY) * critRatio->ratio;
5169 return crit * 100.0f;
5172 void Player::GetDodgeFromAgility(float& diminishing, float& nondiminishing)
5174 // 4.2.0: these classes no longer receive dodge from agility and have 5% base
5175 if (getClass() == CLASS_WARRIOR || getClass() == CLASS_PALADIN || getClass() == CLASS_DEATH_KNIGHT)
5177 nondiminishing += 5.0f;
5178 return;
5181 // Table for base dodge values
5182 const float dodge_base[MAX_CLASSES] =
5184 0.036640f, // Warrior
5185 0.034943f, // Paladin
5186 -0.040873f, // Hunter
5187 0.020957f, // Rogue
5188 0.034178f, // Priest
5189 0.036640f, // DK
5190 0.021080f, // Shaman
5191 0.036587f, // Mage
5192 0.024211f, // Warlock
5193 0.0f, // ??
5194 0.056097f // Druid
5196 // Crit/agility to dodge/agility coefficient multipliers; 3.2.0 increased required agility by 15%
5197 const float crit_to_dodge[MAX_CLASSES] =
5199 0.85f / 1.15f, // Warrior
5200 1.00f / 1.15f, // Paladin
5201 1.11f / 1.15f, // Hunter
5202 2.00f / 1.15f, // Rogue
5203 1.00f / 1.15f, // Priest
5204 0.85f / 1.15f, // DK
5205 1.60f / 1.15f, // Shaman
5206 1.00f / 1.15f, // Mage
5207 0.97f / 1.15f, // Warlock (?)
5208 0.0f, // ??
5209 2.00f / 1.15f // Druid
5212 uint32 level = getLevel();
5213 uint32 pclass = getClass();
5215 if (level > GT_MAX_LEVEL) level = GT_MAX_LEVEL;
5217 // Dodge per agility is proportional to crit per agility, which is available from DBC files
5218 GtChanceToMeleeCritEntry const* dodgeRatio = sGtChanceToMeleeCritStore.LookupEntry((pclass - 1) * GT_MAX_LEVEL + level - 1);
5219 if (dodgeRatio == NULL || pclass > MAX_CLASSES)
5220 return;
5222 // TODO: research if talents/effects that increase total agility by x% should increase non-diminishing part
5223 float base_agility = GetCreateStat(STAT_AGILITY) * m_auraModifiersGroup[UNIT_MOD_STAT_START + STAT_AGILITY][BASE_PCT];
5224 float bonus_agility = GetStat(STAT_AGILITY) - base_agility;
5225 // calculate diminishing (green in char screen) and non-diminishing (white) contribution
5226 diminishing += 100.0f * bonus_agility * dodgeRatio->ratio * crit_to_dodge[pclass - 1];
5227 nondiminishing += 100.0f * (dodge_base[pclass - 1] + base_agility * dodgeRatio->ratio * crit_to_dodge[pclass - 1]);
5230 void Player::GetParryFromStrength(float& diminishing, float& nondiminishing)
5232 // other classes than these do not receive parry bonus from strength
5233 if (getClass() != CLASS_WARRIOR && getClass() != CLASS_PALADIN && getClass() != CLASS_DEATH_KNIGHT)
5234 return;
5236 float base_strength = GetCreateStat(STAT_STRENGTH) * m_auraModifiersGroup[UNIT_MOD_STAT_START + STAT_STRENGTH][BASE_PCT];
5237 float bonus_strength = GetStat(STAT_STRENGTH) - base_strength;
5238 // calculate diminishing (green in char screen) and non-diminishing (white) contribution
5239 diminishing += bonus_strength * GetRatingMultiplier(CR_PARRY) * 0.27f;
5240 nondiminishing += base_strength * GetRatingMultiplier(CR_PARRY) * 0.27f;
5243 float Player::GetSpellCritFromIntellect()
5245 uint32 level = getLevel();
5246 uint32 pclass = getClass();
5248 if (level > GT_MAX_LEVEL) level = GT_MAX_LEVEL;
5250 GtChanceToSpellCritBaseEntry const* critBase = sGtChanceToSpellCritBaseStore.LookupEntry(pclass - 1);
5251 GtChanceToSpellCritEntry const* critRatio = sGtChanceToSpellCritStore.LookupEntry((pclass - 1) * GT_MAX_LEVEL + level - 1);
5252 if (critBase == NULL || critRatio == NULL)
5253 return 0.0f;
5255 float crit = critBase->base + GetStat(STAT_INTELLECT) * critRatio->ratio;
5256 return crit * 100.0f;
5259 float Player::GetRatingMultiplier(CombatRating cr) const
5261 uint32 level = getLevel();
5263 if (level > GT_MAX_LEVEL) level = GT_MAX_LEVEL;
5265 GtCombatRatingsEntry const* Rating = sGtCombatRatingsStore.LookupEntry(cr * GT_MAX_LEVEL + level - 1);
5266 // gtOCTClassCombatRatingScalarStore.dbc starts with 1, CombatRating with zero, so cr+1
5267 GtOCTClassCombatRatingScalarEntry const* classRating = sGtOCTClassCombatRatingScalarStore.LookupEntry((getClass() - 1) * GT_MAX_RATING + cr + 1);
5268 if (!Rating || !classRating)
5269 return 1.0f; // By default use minimum coefficient (not must be called)
5271 return classRating->ratio / Rating->ratio;
5274 float Player::GetRatingBonusValue(CombatRating cr) const
5276 return float(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + cr)) * GetRatingMultiplier(cr);
5279 float Player::GetExpertiseDodgeOrParryReduction(WeaponAttackType attType) const
5281 switch (attType)
5283 case BASE_ATTACK:
5284 return GetUInt32Value(PLAYER_EXPERTISE) / 4.0f;
5285 case OFF_ATTACK:
5286 return GetUInt32Value(PLAYER_OFFHAND_EXPERTISE) / 4.0f;
5287 default:
5288 break;
5290 return 0.0f;
5293 float Player::OCTRegenMPPerSpirit()
5295 // Only healers have regen bonus from spirit. Others regenerate by combat regen.
5296 uint32 rolesMask = GetTalentTreeRolesMask(m_talentsPrimaryTree[m_activeSpec]);
5297 if ((rolesMask & TALENT_ROLE_HEALER) == 0)
5298 return 0.0f;
5300 uint32 level = getLevel();
5301 uint32 pclass = getClass();
5303 if (level > GT_MAX_LEVEL) level = GT_MAX_LEVEL;
5305 // GtOCTRegenMPEntry const *baseRatio = sGtOCTRegenMPStore.LookupEntry((pclass-1)*GT_MAX_LEVEL + level-1);
5306 GtRegenMPPerSptEntry const* moreRatio = sGtRegenMPPerSptStore.LookupEntry((pclass - 1) * GT_MAX_LEVEL + level - 1);
5307 if (moreRatio == NULL)
5308 return 0.0f;
5310 // Formula get from PaperDollFrame script
5311 float spirit = GetStat(STAT_SPIRIT);
5312 float regen = spirit * moreRatio->ratio;
5313 return regen;
5316 void Player::ApplyRatingMod(CombatRating cr, int32 value, bool apply)
5318 m_baseRatingValue[cr] += (apply ? value : -value);
5320 // explicit affected values
5321 switch (cr)
5323 case CR_HASTE_MELEE:
5325 float RatingChange = value * GetRatingMultiplier(cr);
5326 ApplyAttackTimePercentMod(BASE_ATTACK, RatingChange, apply);
5327 ApplyAttackTimePercentMod(OFF_ATTACK, RatingChange, apply);
5328 break;
5330 case CR_HASTE_RANGED:
5332 float RatingChange = value * GetRatingMultiplier(cr);
5333 ApplyAttackTimePercentMod(RANGED_ATTACK, RatingChange, apply);
5334 break;
5336 case CR_HASTE_SPELL:
5338 float RatingChange = value * GetRatingMultiplier(cr);
5339 ApplyCastTimePercentMod(RatingChange, apply);
5340 break;
5342 default:
5343 break;
5346 UpdateRating(cr);
5349 void Player::UpdateRating(CombatRating cr)
5351 int32 amount = m_baseRatingValue[cr];
5352 // Apply bonus from SPELL_AURA_MOD_RATING_FROM_STAT
5353 // stat used stored in miscValueB for this aura
5354 AuraList const& modRatingFromStat = GetAurasByType(SPELL_AURA_MOD_RATING_FROM_STAT);
5355 for (AuraList::const_iterator i = modRatingFromStat.begin(); i != modRatingFromStat.end(); ++i)
5356 if ((*i)->GetMiscValue() & (1 << cr))
5357 amount += int32(GetStat(Stats((*i)->GetMiscBValue())) * (*i)->GetModifier()->m_amount / 100.0f);
5358 if (amount < 0)
5359 amount = 0;
5360 SetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + cr, uint32(amount));
5362 bool affectStats = CanModifyStats();
5364 switch (cr)
5366 case CR_WEAPON_SKILL:
5367 case CR_DEFENSE_SKILL:
5368 break;
5369 case CR_DODGE:
5370 UpdateDodgePercentage();
5371 break;
5372 case CR_PARRY:
5373 UpdateParryPercentage();
5374 break;
5375 case CR_BLOCK:
5376 UpdateBlockPercentage();
5377 break;
5378 case CR_HIT_MELEE:
5379 UpdateMeleeHitChances();
5380 break;
5381 case CR_HIT_RANGED:
5382 UpdateRangedHitChances();
5383 break;
5384 case CR_HIT_SPELL:
5385 UpdateSpellHitChances();
5386 break;
5387 case CR_CRIT_MELEE:
5388 if (affectStats)
5390 UpdateCritPercentage(BASE_ATTACK);
5391 UpdateCritPercentage(OFF_ATTACK);
5393 break;
5394 case CR_CRIT_RANGED:
5395 if (affectStats)
5396 UpdateCritPercentage(RANGED_ATTACK);
5397 break;
5398 case CR_CRIT_SPELL:
5399 if (affectStats)
5400 UpdateAllSpellCritChances();
5401 break;
5402 case CR_RESILIENCE_DAMAGE_TAKEN:
5403 break;
5404 case CR_HASTE_MELEE: // Implemented in Player::ApplyRatingMod
5405 case CR_HASTE_RANGED:
5406 case CR_HASTE_SPELL:
5407 break;
5408 case CR_EXPERTISE:
5409 if (affectStats)
5411 UpdateExpertise(BASE_ATTACK);
5412 UpdateExpertise(OFF_ATTACK);
5414 break;
5415 case CR_ARMOR_PENETRATION:
5416 if (affectStats)
5417 UpdateArmorPenetration();
5418 break;
5419 case CR_MASTERY:
5420 UpdateMasteryAuras();
5421 break;
5422 // deprecated
5423 case CR_HIT_TAKEN_MELEE:
5424 case CR_HIT_TAKEN_RANGED:
5425 case CR_HIT_TAKEN_SPELL:
5426 case CR_CRIT_TAKEN_SPELL:
5427 case CR_CRIT_TAKEN_MELEE:
5428 case CR_WEAPON_SKILL_MAINHAND:
5429 case CR_WEAPON_SKILL_OFFHAND:
5430 case CR_WEAPON_SKILL_RANGED:
5431 break;
5435 void Player::UpdateAllRatings()
5437 for (int cr = 0; cr < MAX_COMBAT_RATING; ++cr)
5438 UpdateRating(CombatRating(cr));
5441 void Player::SetRegularAttackTime()
5443 for (int i = 0; i < MAX_ATTACK; ++i)
5445 Item* tmpitem = GetWeaponForAttack(WeaponAttackType(i), true, false);
5446 if (tmpitem)
5448 ItemPrototype const* proto = tmpitem->GetProto();
5449 if (proto->Delay)
5450 SetAttackTime(WeaponAttackType(i), proto->Delay);
5451 else
5452 SetAttackTime(WeaponAttackType(i), BASE_ATTACK_TIME);
5457 // skill+step, checking for max value
5458 bool Player::UpdateSkill(uint32 skill_id, uint32 step)
5460 if (!skill_id)
5461 return false;
5463 if (skill_id == SKILL_FIST_WEAPONS)
5464 skill_id = SKILL_UNARMED;
5466 SkillStatusMap::iterator itr = mSkillStatus.find(skill_id);
5467 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
5468 return false;
5470 uint16 field = itr->second.pos / 2;
5471 uint8 offset = itr->second.pos & 1; // itr->second.pos % 2
5473 uint16 value = GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset);
5474 uint16 max = GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset);
5476 if (!max || !value || value >= max)
5477 return false;
5479 uint32 new_value = value + step;
5480 if (new_value > max)
5481 new_value = max;
5483 SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, new_value);
5484 if (itr->second.uState != SKILL_NEW)
5485 itr->second.uState = SKILL_CHANGED;
5486 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, skill_id);
5488 return true;
5491 inline int SkillGainChance(uint32 SkillValue, uint32 GrayLevel, uint32 GreenLevel, uint32 YellowLevel)
5493 if (SkillValue >= GrayLevel)
5494 return sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_GREY) * 10;
5495 if (SkillValue >= GreenLevel)
5496 return sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_GREEN) * 10;
5497 if (SkillValue >= YellowLevel)
5498 return sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_YELLOW) * 10;
5499 return sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_ORANGE) * 10;
5502 bool Player::UpdateCraftSkill(uint32 spellid)
5504 DEBUG_LOG("UpdateCraftSkill spellid %d", spellid);
5506 SkillLineAbilityMapBounds bounds = sSpellMgr.GetSkillLineAbilityMapBounds(spellid);
5508 for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
5510 if (_spell_idx->second->skillId)
5512 uint32 SkillValue = GetPureSkillValue(_spell_idx->second->skillId);
5514 // Alchemy Discoveries here
5515 SpellEntry const* spellEntry = sSpellStore.LookupEntry(spellid);
5516 if (spellEntry && spellEntry->GetMechanic() == MECHANIC_DISCOVERY)
5518 if (uint32 discoveredSpell = GetSkillDiscoverySpell(_spell_idx->second->skillId, spellid, this))
5519 learnSpell(discoveredSpell, false);
5522 uint32 craft_skill_gain = sWorld.getConfig(CONFIG_UINT32_SKILL_GAIN_CRAFTING);
5524 return UpdateSkillPro(_spell_idx->second->skillId, SkillGainChance(SkillValue,
5525 _spell_idx->second->max_value,
5526 (_spell_idx->second->max_value + _spell_idx->second->min_value) / 2,
5527 _spell_idx->second->min_value),
5528 craft_skill_gain);
5531 return false;
5534 bool Player::UpdateGatherSkill(uint32 SkillId, uint32 SkillValue, uint32 RedLevel, uint32 Multiplicator)
5536 DEBUG_LOG("UpdateGatherSkill(SkillId %d SkillLevel %d RedLevel %d)", SkillId, SkillValue, RedLevel);
5538 uint32 gathering_skill_gain = sWorld.getConfig(CONFIG_UINT32_SKILL_GAIN_GATHERING);
5540 // For skinning and Mining chance decrease with level. 1-74 - no decrease, 75-149 - 2 times, 225-299 - 8 times
5541 switch (SkillId)
5543 case SKILL_HERBALISM:
5544 case SKILL_JEWELCRAFTING:
5545 case SKILL_INSCRIPTION:
5546 return UpdateSkillPro(SkillId, SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * Multiplicator, gathering_skill_gain);
5547 case SKILL_SKINNING:
5548 if (sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_SKINNING_STEPS) == 0)
5549 return UpdateSkillPro(SkillId, SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * Multiplicator, gathering_skill_gain);
5550 else
5551 return UpdateSkillPro(SkillId, (SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * Multiplicator) >> (SkillValue / sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_SKINNING_STEPS)), gathering_skill_gain);
5552 case SKILL_MINING:
5553 if (sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_MINING_STEPS) == 0)
5554 return UpdateSkillPro(SkillId, SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * Multiplicator, gathering_skill_gain);
5555 else
5556 return UpdateSkillPro(SkillId, (SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * Multiplicator) >> (SkillValue / sWorld.getConfig(CONFIG_UINT32_SKILL_CHANCE_MINING_STEPS)), gathering_skill_gain);
5558 return false;
5561 bool Player::UpdateFishingSkill()
5563 DEBUG_LOG("UpdateFishingSkill");
5565 uint32 SkillValue = GetPureSkillValue(SKILL_FISHING);
5567 int32 chance = SkillValue < 75 ? 100 : 2500 / (SkillValue - 50);
5569 uint32 gathering_skill_gain = sWorld.getConfig(CONFIG_UINT32_SKILL_GAIN_GATHERING);
5571 return UpdateSkillPro(SKILL_FISHING, chance * 10, gathering_skill_gain);
5574 // levels sync. with spell requirement for skill levels to learn
5575 // bonus abilities in sSkillLineAbilityStore
5576 // Used only to avoid scan DBC at each skill grow
5577 static uint32 bonusSkillLevels[] = {75, 150, 225, 300, 375, 450};
5579 bool Player::UpdateSkillPro(uint16 SkillId, int32 Chance, uint32 step)
5581 DEBUG_LOG("UpdateSkillPro(SkillId %d, Chance %3.1f%%)", SkillId, Chance / 10.0);
5582 if (!SkillId)
5583 return false;
5585 if (Chance <= 0) // speedup in 0 chance case
5587 DEBUG_LOG("Player::UpdateSkillPro Chance=%3.1f%% missed", Chance / 10.0);
5588 return false;
5591 SkillStatusMap::iterator itr = mSkillStatus.find(SkillId);
5592 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
5593 return false;
5595 uint16 field = itr->second.pos / 2;
5596 uint8 offset = itr->second.pos & 1; // itr->second.pos % 2
5598 uint16 SkillValue = GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset);
5599 uint16 MaxValue = GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset);
5601 if (!MaxValue || !SkillValue || SkillValue >= MaxValue)
5602 return false;
5604 int32 Roll = irand(1, 1000);
5606 if (Roll <= Chance)
5608 uint16 new_value = SkillValue + step;
5609 if (new_value > MaxValue)
5610 new_value = MaxValue;
5612 SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, new_value);
5613 if (itr->second.uState != SKILL_NEW)
5614 itr->second.uState = SKILL_CHANGED;
5615 for (uint32* bsl = &bonusSkillLevels[0]; *bsl; ++bsl)
5617 if (SkillValue < *bsl && new_value >= *bsl)
5619 learnSkillRewardedSpells(SkillId, new_value);
5620 break;
5623 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, SkillId);
5624 DEBUG_LOG("Player::UpdateSkillPro Chance=%3.1f%% taken", Chance / 10.0);
5625 return true;
5628 DEBUG_LOG("Player::UpdateSkillPro Chance=%3.1f%% missed", Chance / 10.0);
5629 return false;
5632 void Player::ModifySkillBonus(uint32 skillid, int32 val, bool talent)
5634 SkillStatusMap::const_iterator itr = mSkillStatus.find(skillid);
5635 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
5636 return;
5638 uint16 field = itr->second.pos / 2 + (talent ? PLAYER_SKILL_TALENT_0 : PLAYER_SKILL_MODIFIER_0);
5639 uint8 offset = itr->second.pos & 1; // itr->second.pos % 2
5641 uint16 bonus = GetUInt16Value(field, offset);
5643 SetUInt16Value(field, offset, bonus + val);
5646 void Player::UpdateSkillsForLevel()
5648 uint32 maxSkill = GetMaxSkillValueForLevel();
5650 for (SkillStatusMap::iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end(); ++itr)
5652 if (itr->second.uState == SKILL_DELETED)
5653 continue;
5655 uint32 pskill = itr->first;
5657 SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(pskill);
5658 if (!pSkill)
5659 continue;
5661 if (GetSkillRangeType(pSkill, false) != SKILL_RANGE_LEVEL)
5662 continue;
5663 // only weapons skills curently have SKILL_RANGE_LEVEL
5665 uint16 field = itr->second.pos / 2;
5666 uint8 offset = itr->second.pos & 1; // itr->second.pos % 2
5668 uint16 max = GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset);
5670 /// update only level dependent max skill values
5671 if (max != 1)
5673 SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, maxSkill);
5674 if (itr->second.uState != SKILL_NEW)
5675 itr->second.uState = SKILL_CHANGED;
5680 void Player::UpdateSkillsToMaxSkillsForLevel()
5682 for (SkillStatusMap::iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end(); ++itr)
5684 if (itr->second.uState == SKILL_DELETED)
5685 continue;
5687 uint32 pskill = itr->first;
5688 if (IsProfessionOrRidingSkill(pskill))
5689 continue;
5690 uint16 field = itr->second.pos / 2;
5691 uint8 offset = itr->second.pos & 1; // itr->second.pos % 2
5693 uint16 max = GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset);
5695 if (max > 1)
5697 SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, max);
5698 if (itr->second.uState != SKILL_NEW)
5699 itr->second.uState = SKILL_CHANGED;
5700 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, pskill);
5705 // This functions sets a skill line value (and adds if doesn't exist yet)
5706 // To "remove" a skill line, set it's values to zero
5707 void Player::SetSkill(uint16 id, uint16 currVal, uint16 maxVal, uint16 step /*=0*/)
5709 if (!id)
5710 return;
5712 uint16 oldVal;
5713 SkillStatusMap::iterator itr = mSkillStatus.find(id);
5715 // has skill
5716 if (itr != mSkillStatus.end() && itr->second.uState != SKILL_DELETED)
5718 uint16 field = itr->second.pos / 2;
5719 uint8 offset = itr->second.pos & 1; // itr->second.pos % 2
5720 oldVal = GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset);
5721 if (currVal)
5723 SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, step);
5724 // update value
5725 SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, currVal);
5726 SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, maxVal);
5727 if (itr->second.uState != SKILL_NEW)
5728 itr->second.uState = SKILL_CHANGED;
5729 learnSkillRewardedSpells(id, oldVal);
5730 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, id);
5731 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL, id);
5733 else // remove
5735 // clear skill fields
5736 SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, 0);
5737 SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, 0);
5738 SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, 0);
5739 SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, 0);
5740 SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, 0);
5741 SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, 0);
5743 // mark as deleted or simply remove from map if not saved yet
5744 if (itr->second.uState != SKILL_NEW)
5745 itr->second.uState = SKILL_DELETED;
5746 else
5747 mSkillStatus.erase(itr);
5749 // remove all spells that related to this skill
5750 for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
5751 if (SkillLineAbilityEntry const* pAbility = sSkillLineAbilityStore.LookupEntry(j))
5752 if (pAbility->skillId == id)
5753 removeSpell(sSpellMgr.GetFirstSpellInChain(pAbility->spellId));
5756 else if (currVal) // add
5758 for (int i = 0; i < PLAYER_MAX_SKILLS; ++i)
5760 uint16 field = i / 2;
5761 uint8 offset = i & 1; // i % 2
5763 if (!GetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset))
5765 SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(id);
5766 if (!pSkill)
5768 sLog.outError("Skill not found in SkillLineStore: skill #%u", id);
5769 return;
5772 SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, id);
5773 SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, step);
5774 SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, currVal);
5775 SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, maxVal);
5776 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, id);
5777 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL, id);
5779 // insert new entry or update if not deleted old entry yet
5780 if (itr != mSkillStatus.end())
5782 itr->second.pos = i;
5783 itr->second.uState = SKILL_CHANGED;
5785 else
5786 mSkillStatus.insert(SkillStatusMap::value_type(id, SkillStatusData(i, SKILL_NEW)));
5788 // apply skill bonuses
5789 SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, 0);
5790 SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, 0);
5792 // temporary bonuses
5793 AuraList const& mModSkill = GetAurasByType(SPELL_AURA_MOD_SKILL);
5794 for (AuraList::const_iterator j = mModSkill.begin(); j != mModSkill.end(); ++j)
5795 if ((*j)->GetModifier()->m_miscvalue == int32(id))
5796 (*j)->ApplyModifier(true);
5798 // permanent bonuses
5799 AuraList const& mModSkillTalent = GetAurasByType(SPELL_AURA_MOD_SKILL_TALENT);
5800 for (AuraList::const_iterator j = mModSkillTalent.begin(); j != mModSkillTalent.end(); ++j)
5801 if ((*j)->GetModifier()->m_miscvalue == int32(id))
5802 (*j)->ApplyModifier(true);
5804 // Learn all spells for skill
5805 learnSkillRewardedSpells(id, currVal);
5806 return;
5812 bool Player::HasSkill(uint32 skill) const
5814 if (!skill)
5815 return false;
5817 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
5818 return (itr != mSkillStatus.end() && itr->second.uState != SKILL_DELETED);
5821 uint16 Player::GetSkillStep(uint16 skill) const
5823 if (!skill)
5824 return 0;
5826 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
5827 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
5828 return 0;
5830 return GetUInt16Value(PLAYER_SKILL_STEP_0 + itr->second.pos / 2, itr->second.pos & 1);
5833 uint16 Player::GetSkillValue(uint32 skill) const
5835 if (!skill)
5836 return 0;
5838 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
5839 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
5840 return 0;
5842 uint16 field = itr->second.pos / 2;
5843 uint8 offset = itr->second.pos & 1;
5845 int32 result = int32(GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset));
5846 result += int32(GetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset));
5847 result += int32(GetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset));
5848 return result < 0 ? 0 : result;
5851 uint16 Player::GetMaxSkillValue(uint32 skill) const
5853 if (!skill)
5854 return 0;
5856 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
5857 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
5858 return 0;
5860 uint16 field = itr->second.pos / 2;
5861 uint8 offset = itr->second.pos & 1;
5863 int32 result = int32(GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset));
5864 result += int32(GetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset));
5865 result += int32(GetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset));
5866 return result < 0 ? 0 : result;
5869 uint16 Player::GetPureMaxSkillValue(uint32 skill) const
5871 if (!skill)
5872 return 0;
5874 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
5875 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
5876 return 0;
5878 uint16 field = itr->second.pos / 2;
5879 uint8 offset = itr->second.pos & 1;
5881 return GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset);
5884 uint16 Player::GetBaseSkillValue(uint32 skill) const
5886 if (!skill)
5887 return 0;
5889 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
5890 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
5891 return 0;
5893 uint16 field = itr->second.pos / 2;
5894 uint8 offset = itr->second.pos & 1;
5896 int32 result = int32(GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset));
5897 result += int32(GetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset));
5898 return result < 0 ? 0 : result;
5901 uint16 Player::GetPureSkillValue(uint32 skill) const
5903 if (!skill)
5904 return 0;
5906 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
5907 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
5908 return 0;
5910 uint16 field = itr->second.pos / 2;
5911 uint8 offset = itr->second.pos & 1;
5913 return GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset);
5916 int16 Player::GetSkillPermBonusValue(uint32 skill) const
5918 if (!skill)
5919 return 0;
5921 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
5922 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
5923 return 0;
5925 uint16 field = itr->second.pos / 2;
5926 uint8 offset = itr->second.pos & 1;
5928 return GetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset);
5931 int16 Player::GetSkillTempBonusValue(uint32 skill) const
5933 if (!skill)
5934 return 0;
5936 SkillStatusMap::const_iterator itr = mSkillStatus.find(skill);
5937 if (itr == mSkillStatus.end() || itr->second.uState == SKILL_DELETED)
5938 return 0;
5940 uint16 field = itr->second.pos / 2;
5941 uint8 offset = itr->second.pos & 1;
5943 return GetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset);
5946 void Player::SendInitialActionButtons() const
5948 DETAIL_LOG("Initializing Action Buttons for '%u' spec '%u'", GetGUIDLow(), m_activeSpec);
5950 WorldPacket data(SMSG_ACTION_BUTTONS, 1 + (MAX_ACTION_BUTTONS * 4));
5951 ActionButtonList const& currentActionButtonList = m_actionButtons[m_activeSpec];
5952 for (uint8 button = 0; button < MAX_ACTION_BUTTONS; ++button)
5954 ActionButtonList::const_iterator itr = currentActionButtonList.find(button);
5955 if (itr != currentActionButtonList.end() && itr->second.uState != ACTIONBUTTON_DELETED)
5956 data << uint32(itr->second.packedData);
5957 else
5958 data << uint32(0);
5960 data << uint8(1); // talent spec amount (in packet)
5961 GetSession()->SendPacket(&data);
5962 DETAIL_LOG("Action Buttons for '%u' spec '%u' Initialized", GetGUIDLow(), m_activeSpec);
5965 void Player::SendLockActionButtons() const
5967 DETAIL_LOG("Locking Action Buttons for '%u' spec '%u'", GetGUIDLow(), m_activeSpec);
5968 WorldPacket data(SMSG_ACTION_BUTTONS, 1);
5969 // sending 2 locks actions bars, neither user can remove buttons, nor client removes buttons at spell unlearn
5970 // they remain locked until server sends new action buttons
5971 data << uint8(2);
5972 GetSession()->SendPacket(&data);
5975 bool Player::IsActionButtonDataValid(uint8 button, uint32 action, uint8 type, Player* player, bool msg)
5977 if (button >= MAX_ACTION_BUTTONS)
5979 if (msg)
5981 if (player)
5982 sLog.outError("Action %u not added into button %u for player %s: button must be < %u", action, button, player->GetName(), MAX_ACTION_BUTTONS);
5983 else
5984 sLog.outError("Table `playercreateinfo_action` have action %u into button %u : button must be < %u", action, button, MAX_ACTION_BUTTONS);
5987 return false;
5990 if (action >= MAX_ACTION_BUTTON_ACTION_VALUE)
5992 if (msg)
5994 if (player)
5995 sLog.outError("Action %u not added into button %u for player %s: action must be < %u", action, button, player->GetName(), MAX_ACTION_BUTTON_ACTION_VALUE);
5996 else
5997 sLog.outError("Table `playercreateinfo_action` have action %u into button %u : action must be < %u", action, button, MAX_ACTION_BUTTON_ACTION_VALUE);
5999 return false;
6002 switch (type)
6004 case ACTION_BUTTON_SPELL:
6006 SpellEntry const* spellProto = sSpellStore.LookupEntry(action);
6007 if (!spellProto)
6009 if (msg)
6011 if (player)
6012 sLog.outError("Spell action %u not added into button %u for player %s: spell not exist", action, button, player->GetName());
6013 else
6014 sLog.outError("Table `playercreateinfo_action` have spell action %u into button %u: spell not exist", action, button);
6016 return false;
6019 if (player)
6021 if (!player->HasSpell(spellProto->Id))
6023 if (msg)
6024 sLog.outError("Spell action %u not added into button %u for player %s: player don't known this spell", action, button, player->GetName());
6025 return false;
6027 else if (IsPassiveSpell(spellProto))
6029 if (msg)
6030 sLog.outError("Spell action %u not added into button %u for player %s: spell is passive", action, button, player->GetName());
6031 return false;
6033 // current range for button of totem bar is from ACTION_BUTTON_SHAMAN_TOTEMS_BAR to (but not including) ACTION_BUTTON_SHAMAN_TOTEMS_BAR + 12
6034 else if (button >= ACTION_BUTTON_SHAMAN_TOTEMS_BAR && button < (ACTION_BUTTON_SHAMAN_TOTEMS_BAR + 12)
6035 && !spellProto->HasAttribute(SPELL_ATTR_EX7_TOTEM_SPELL))
6037 if (msg)
6038 sLog.outError("Spell action %u not added into button %u for player %s: attempt to add non totem spell to totem bar", action, button, player->GetName());
6039 return false;
6042 break;
6044 case ACTION_BUTTON_ITEM:
6046 if (!ObjectMgr::GetItemPrototype(action))
6048 if (msg)
6050 if (player)
6051 sLog.outError("Item action %u not added into button %u for player %s: item not exist", action, button, player->GetName());
6052 else
6053 sLog.outError("Table `playercreateinfo_action` have item action %u into button %u: item not exist", action, button);
6055 return false;
6057 break;
6059 default:
6060 break; // other cases not checked at this moment
6063 return true;
6066 ActionButton* Player::addActionButton(uint8 spec, uint8 button, uint32 action, uint8 type)
6068 // check action only for active spec (so not check at copy/load passive spec)
6069 if (spec == GetActiveSpec() && !IsActionButtonDataValid(button, action, type, this))
6070 return NULL;
6072 // it create new button (NEW state) if need or return existing
6073 ActionButton& ab = m_actionButtons[spec][button];
6075 // set data and update to CHANGED if not NEW
6076 ab.SetActionAndType(action, ActionButtonType(type));
6078 DETAIL_LOG("Player '%u' Added Action '%u' (type %u) to Button '%u' for spec %u", GetGUIDLow(), action, uint32(type), button, spec);
6079 return &ab;
6082 void Player::removeActionButton(uint8 spec, uint8 button)
6084 ActionButtonList& currentActionButtonList = m_actionButtons[spec];
6085 ActionButtonList::iterator buttonItr = currentActionButtonList.find(button);
6086 if (buttonItr == currentActionButtonList.end() || buttonItr->second.uState == ACTIONBUTTON_DELETED)
6087 return;
6089 if (buttonItr->second.uState == ACTIONBUTTON_NEW)
6090 currentActionButtonList.erase(buttonItr); // new and not saved
6091 else
6092 buttonItr->second.uState = ACTIONBUTTON_DELETED; // saved, will deleted at next save
6094 DETAIL_LOG("Action Button '%u' Removed from Player '%u' for spec %u", button, GetGUIDLow(), spec);
6097 ActionButton const* Player::GetActionButton(uint8 button)
6099 ActionButtonList& currentActionButtonList = m_actionButtons[m_activeSpec];
6100 ActionButtonList::iterator buttonItr = currentActionButtonList.find(button);
6101 if (buttonItr == currentActionButtonList.end() || buttonItr->second.uState == ACTIONBUTTON_DELETED)
6102 return NULL;
6104 return &buttonItr->second;
6107 bool Player::SetPosition(float x, float y, float z, float orientation, bool teleport)
6109 // prevent crash when a bad coord is sent by the client
6110 if (!MaNGOS::IsValidMapCoord(x, y, z, orientation))
6112 DEBUG_LOG("Player::SetPosition(%f, %f, %f, %f, %d) .. bad coordinates for player %d!", x, y, z, orientation, teleport, GetGUIDLow());
6113 return false;
6116 Map* m = GetMap();
6118 const float old_x = GetPositionX();
6119 const float old_y = GetPositionY();
6120 const float old_z = GetPositionZ();
6121 const float old_r = GetOrientation();
6123 if (teleport || old_x != x || old_y != y || old_z != z || old_r != orientation)
6125 if (teleport || old_x != x || old_y != y || old_z != z)
6126 RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_MOVE | AURA_INTERRUPT_FLAG_TURNING);
6127 else
6128 RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TURNING);
6130 RemoveSpellsCausingAura(SPELL_AURA_FEIGN_DEATH);
6132 // move and update visible state if need
6133 m->PlayerRelocation(this, x, y, z, orientation);
6135 // reread after Map::Relocation
6136 m = GetMap();
6137 x = GetPositionX();
6138 y = GetPositionY();
6139 z = GetPositionZ();
6141 // group update
6142 if (GetGroup() && (old_x != x || old_y != y))
6143 SetGroupUpdateFlag(GROUP_UPDATE_FLAG_POSITION);
6145 if (GetTrader() && !IsWithinDistInMap(GetTrader(), INTERACTION_DISTANCE))
6146 GetSession()->SendCancelTrade(); // will close both side trade windows
6149 // code block for underwater state update
6150 UpdateUnderwaterState(m, x, y, z);
6152 CheckAreaExploreAndOutdoor();
6154 return true;
6157 void Player::SaveRecallPosition()
6159 m_recallMap = GetMapId();
6160 m_recallX = GetPositionX();
6161 m_recallY = GetPositionY();
6162 m_recallZ = GetPositionZ();
6163 m_recallO = GetOrientation();
6166 void Player::SendMessageToSet(WorldPacket* data, bool self)
6168 if (IsInWorld())
6169 GetMap()->MessageBroadcast(this, data, false);
6171 // if player is not in world and map in not created/already destroyed
6172 // no need to create one, just send packet for itself!
6173 if (self)
6174 GetSession()->SendPacket(data);
6177 void Player::SendMessageToSetInRange(WorldPacket* data, float dist, bool self)
6179 if (IsInWorld())
6180 GetMap()->MessageDistBroadcast(this, data, dist, false);
6182 if (self)
6183 GetSession()->SendPacket(data);
6186 void Player::SendMessageToSetInRange(WorldPacket* data, float dist, bool self, bool own_team_only)
6188 if (IsInWorld())
6189 GetMap()->MessageDistBroadcast(this, data, dist, false, own_team_only);
6191 if (self)
6192 GetSession()->SendPacket(data);
6195 void Player::SendDirectMessage(WorldPacket* data)
6197 GetSession()->SendPacket(data);
6200 void Player::SendCinematicStart(uint32 CinematicSequenceId)
6202 WorldPacket data(SMSG_TRIGGER_CINEMATIC, 4);
6203 data << uint32(CinematicSequenceId);
6204 SendDirectMessage(&data);
6207 void Player::SendMovieStart(uint32 MovieId)
6209 WorldPacket data(SMSG_TRIGGER_MOVIE, 4);
6210 data << uint32(MovieId);
6211 SendDirectMessage(&data);
6214 void Player::CheckAreaExploreAndOutdoor()
6216 if (!isAlive())
6217 return;
6219 if (IsTaxiFlying())
6220 return;
6222 bool isOutdoor;
6223 uint16 areaFlag = GetTerrain()->GetAreaFlag(GetPositionX(), GetPositionY(), GetPositionZ(), &isOutdoor);
6225 if (isOutdoor)
6227 if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) && GetRestType() == REST_TYPE_IN_TAVERN)
6229 AreaTriggerEntry const* at = sAreaTriggerStore.LookupEntry(inn_trigger_id);
6230 if (!at || !IsPointInAreaTriggerZone(at, GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ()))
6232 // Player left inn (REST_TYPE_IN_CITY overrides REST_TYPE_IN_TAVERN, so just clear rest)
6233 SetRestType(REST_TYPE_NO);
6237 else if (sWorld.getConfig(CONFIG_BOOL_VMAP_INDOOR_CHECK) && !isGameMaster())
6238 RemoveAurasWithAttribute(SPELL_ATTR_OUTDOORS_ONLY);
6240 if (areaFlag == 0xffff)
6241 return;
6242 int offset = areaFlag / 32;
6244 if (offset >= PLAYER_EXPLORED_ZONES_SIZE)
6246 sLog.outError("Wrong area flag %u in map data for (X: %f Y: %f) point to field PLAYER_EXPLORED_ZONES_1 + %u ( %u must be < %u ).", areaFlag, GetPositionX(), GetPositionY(), offset, offset, PLAYER_EXPLORED_ZONES_SIZE);
6247 return;
6250 uint32 val = (uint32)(1 << (areaFlag % 32));
6251 uint32 currFields = GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + offset);
6253 if (!(currFields & val))
6255 SetUInt32Value(PLAYER_EXPLORED_ZONES_1 + offset, (uint32)(currFields | val));
6257 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA);
6259 AreaTableEntry const* p = GetAreaEntryByAreaFlagAndMap(areaFlag, GetMapId());
6260 if (!p)
6262 sLog.outError("PLAYER: Player %u discovered unknown area (x: %f y: %f map: %u", GetGUIDLow(), GetPositionX(), GetPositionY(), GetMapId());
6264 else if (p->area_level > 0)
6266 uint32 area = p->ID;
6267 if (getLevel() >= sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL))
6269 SendExplorationExperience(area, 0);
6271 else
6273 int32 diff = int32(getLevel()) - p->area_level;
6274 uint32 XP = 0;
6275 if (diff < -5)
6277 XP = uint32(sObjectMgr.GetBaseXP(getLevel() + 5) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE));
6279 else if (diff > 5)
6281 int32 exploration_percent = (100 - ((diff - 5) * 5));
6282 if (exploration_percent > 100)
6283 exploration_percent = 100;
6284 else if (exploration_percent < 0)
6285 exploration_percent = 0;
6287 XP = uint32(sObjectMgr.GetBaseXP(p->area_level) * exploration_percent / 100 * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE));
6289 else
6291 XP = uint32(sObjectMgr.GetBaseXP(p->area_level) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE));
6294 GiveXP(XP, NULL);
6295 SendExplorationExperience(area, XP);
6297 DETAIL_LOG("PLAYER: Player %u discovered a new area: %u", GetGUIDLow(), area);
6302 Team Player::TeamForRace(uint8 race)
6304 ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race);
6305 if (!rEntry)
6307 sLog.outError("Race %u not found in DBC: wrong DBC files?", uint32(race));
6308 return ALLIANCE;
6311 switch (rEntry->TeamID)
6313 case 7: return ALLIANCE;
6314 case 1: return HORDE;
6317 sLog.outError("Race %u have wrong teamid %u in DBC: wrong DBC files?", uint32(race), rEntry->TeamID);
6318 return TEAM_NONE;
6321 uint32 Player::getFactionForRace(uint8 race)
6323 ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race);
6324 if (!rEntry)
6326 sLog.outError("Race %u not found in DBC: wrong DBC files?", uint32(race));
6327 return 0;
6330 return rEntry->FactionID;
6333 void Player::setFactionForRace(uint8 race)
6335 m_team = TeamForRace(race);
6336 setFaction(getFactionForRace(race));
6339 ReputationRank Player::GetReputationRank(uint32 faction) const
6341 FactionEntry const* factionEntry = sFactionStore.LookupEntry(faction);
6342 return GetReputationMgr().GetRank(factionEntry);
6345 // Calculate total reputation percent player gain with quest/creature level
6346 int32 Player::CalculateReputationGain(ReputationSource source, int32 rep, int32 faction, uint32 creatureOrQuestLevel, bool noAuraBonus)
6348 float percent = 100.0f;
6350 float repMod = noAuraBonus ? 0.0f : (float)GetTotalAuraModifier(SPELL_AURA_MOD_REPUTATION_GAIN);
6352 // faction specific auras only seem to apply to kills
6353 if (source == REPUTATION_SOURCE_KILL)
6354 repMod += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_FACTION_REPUTATION_GAIN, faction);
6356 percent += rep > 0 ? repMod : -repMod;
6358 float rate;
6359 switch (source)
6361 case REPUTATION_SOURCE_KILL:
6362 rate = sWorld.getConfig(CONFIG_FLOAT_RATE_REPUTATION_LOWLEVEL_KILL);
6363 break;
6364 case REPUTATION_SOURCE_QUEST:
6365 rate = sWorld.getConfig(CONFIG_FLOAT_RATE_REPUTATION_LOWLEVEL_QUEST);
6366 break;
6367 case REPUTATION_SOURCE_SPELL:
6368 default:
6369 rate = 1.0f;
6370 break;
6373 if (rate != 1.0f && creatureOrQuestLevel <= MaNGOS::XP::GetGrayLevel(getLevel()))
6374 percent *= rate;
6376 if (percent <= 0.0f)
6377 return 0;
6379 // Multiply result with the faction specific rate
6380 if (const RepRewardRate* repData = sObjectMgr.GetRepRewardRate(faction))
6382 float repRate = 0.0f;
6383 switch (source)
6385 case REPUTATION_SOURCE_KILL:
6386 repRate = repData->creature_rate;
6387 break;
6388 case REPUTATION_SOURCE_QUEST:
6389 repRate = repData->quest_rate;
6390 break;
6391 case REPUTATION_SOURCE_SPELL:
6392 repRate = repData->spell_rate;
6393 break;
6396 // for custom, a rate of 0.0 will totally disable reputation gain for this faction/type
6397 if (repRate <= 0.0f)
6398 return 0;
6400 percent *= repRate;
6403 return int32(sWorld.getConfig(CONFIG_FLOAT_RATE_REPUTATION_GAIN) * rep * percent / 100.0f);
6406 // Calculates how many reputation points player gains in victim's enemy factions
6407 void Player::RewardReputation(Unit* pVictim, float rate)
6409 if (!pVictim || pVictim->GetTypeId() == TYPEID_PLAYER)
6410 return;
6412 // used current difficulty creature entry instead normal version (GetEntry())
6413 ReputationOnKillEntry const* Rep = sObjectMgr.GetReputationOnKillEntry(((Creature*)pVictim)->GetCreatureInfo()->Entry);
6415 if (!Rep)
6416 return;
6418 uint32 repFaction1 = Rep->repfaction1;
6419 uint32 repFaction2 = Rep->repfaction2;
6421 // Championning tabard reputation system
6422 // Aura 57818 is a hidden aura common to tabards allowing championning.
6423 if (GetMap()->IsNonRaidDungeon() && HasAura(57818))
6425 MapEntry const* storedMap = sMapStore.LookupEntry(GetMapId());
6426 InstanceTemplate const* instance = ObjectMgr::GetInstanceTemplate(GetMapId());
6427 Item const* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_TABARD);
6428 if (storedMap && instance && pItem)
6430 ItemPrototype const* pProto = pItem->GetProto();// Checked on load
6431 // The required MinLevel for the tabard to work is related to the item level of the tabard
6432 if ((instance->levelMin + 1 >= pProto->ItemLevel || !GetMap()->IsRegularDifficulty())
6433 // For ItemLevel == 75 (or 85) need to check expansion
6434 && (pProto->ItemLevel == 75 && storedMap->Expansion() == 2))
6436 if (uint32 tabardFactionID = pItem->GetProto()->RequiredReputationFaction)
6438 repFaction1 = tabardFactionID;
6439 repFaction2 = tabardFactionID;
6445 if (repFaction1 && (!Rep->team_dependent || GetTeam() == ALLIANCE))
6447 int32 donerep1 = CalculateReputationGain(REPUTATION_SOURCE_KILL, Rep->repvalue1, repFaction1, pVictim->getLevel());
6448 donerep1 = int32(donerep1 * rate);
6449 FactionEntry const* factionEntry1 = sFactionStore.LookupEntry(repFaction1);
6450 uint32 current_reputation_rank1 = GetReputationMgr().GetRank(factionEntry1);
6451 if (factionEntry1 && current_reputation_rank1 <= Rep->reputation_max_cap1)
6452 GetReputationMgr().ModifyReputation(factionEntry1, donerep1);
6454 // Wiki: Team factions value divided by 2
6455 if (factionEntry1 && Rep->is_teamaward1)
6457 FactionEntry const* team1_factionEntry = sFactionStore.LookupEntry(factionEntry1->team);
6458 if (team1_factionEntry)
6459 GetReputationMgr().ModifyReputation(team1_factionEntry, donerep1 / 2);
6463 if (repFaction2 && (!Rep->team_dependent || GetTeam() == HORDE))
6465 int32 donerep2 = CalculateReputationGain(REPUTATION_SOURCE_KILL, Rep->repvalue2, repFaction2, pVictim->getLevel());
6466 donerep2 = int32(donerep2 * rate);
6467 FactionEntry const* factionEntry2 = sFactionStore.LookupEntry(repFaction2);
6468 uint32 current_reputation_rank2 = GetReputationMgr().GetRank(factionEntry2);
6469 if (factionEntry2 && current_reputation_rank2 <= Rep->reputation_max_cap2)
6470 GetReputationMgr().ModifyReputation(factionEntry2, donerep2);
6472 // Wiki: Team factions value divided by 2
6473 if (factionEntry2 && Rep->is_teamaward2)
6475 FactionEntry const* team2_factionEntry = sFactionStore.LookupEntry(factionEntry2->team);
6476 if (team2_factionEntry)
6477 GetReputationMgr().ModifyReputation(team2_factionEntry, donerep2 / 2);
6482 // Calculate how many reputation points player gain with the quest
6483 void Player::RewardReputation(Quest const* pQuest)
6485 // quest reputation reward/loss
6486 for (int i = 0; i < QUEST_REPUTATIONS_COUNT; ++i)
6488 if (!pQuest->RewRepFaction[i])
6489 continue;
6491 // No diplomacy mod are applied to the final value (flat). Note the formula (finalValue = DBvalue/100)
6492 if (pQuest->RewRepValue[i])
6494 int32 rep = CalculateReputationGain(REPUTATION_SOURCE_QUEST, pQuest->RewRepValue[i] / 100, pQuest->RewRepFaction[i], GetQuestLevelForPlayer(pQuest), true);
6496 if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(pQuest->RewRepFaction[i]))
6497 GetReputationMgr().ModifyReputation(factionEntry, rep);
6499 else
6501 uint32 row = ((pQuest->RewRepValueId[i] < 0) ? 1 : 0) + 1;
6502 uint32 field = abs(pQuest->RewRepValueId[i]);
6504 if (const QuestFactionRewardEntry* pRow = sQuestFactionRewardStore.LookupEntry(row))
6506 int32 repPoints = pRow->rewardValue[field];
6508 if (!repPoints)
6509 continue;
6511 repPoints = CalculateReputationGain(REPUTATION_SOURCE_QUEST, repPoints, pQuest->RewRepFaction[i], GetQuestLevelForPlayer(pQuest));
6513 if (const FactionEntry* factionEntry = sFactionStore.LookupEntry(pQuest->RewRepFaction[i]))
6514 GetReputationMgr().ModifyReputation(factionEntry, repPoints);
6519 // TODO: implement reputation spillover
6522 void Player::UpdateHonorKills()
6524 /// called when rewarding honor and at each save
6525 time_t now = time(NULL);
6526 time_t today = (time(NULL) / DAY) * DAY;
6528 if (m_lastHonorKillsUpdateTime < today)
6530 time_t yesterday = today - DAY;
6532 uint16 kills_today = GetUInt16Value(PLAYER_FIELD_KILLS, 0);
6534 // update yesterday's contribution
6535 if (m_lastHonorKillsUpdateTime >= yesterday)
6537 // this is the first update today, reset today's contribution
6538 SetUInt16Value(PLAYER_FIELD_KILLS, 0, 0);
6539 SetUInt16Value(PLAYER_FIELD_KILLS, 1, kills_today);
6541 else
6543 // no honor/kills yesterday or today, reset
6544 SetUInt32Value(PLAYER_FIELD_KILLS, 0);
6548 m_lastHonorKillsUpdateTime = now;
6551 /// Calculate the amount of honor gained based on the victim
6552 /// and the size of the group for which the honor is divided
6553 /// An exact honor value can also be given (overriding the calcs)
6554 bool Player::RewardHonor(Unit* uVictim, uint32 groupsize, float honor)
6556 // do not reward honor in arenas, but enable onkill spellproc
6557 if (InArena())
6559 if (!uVictim || uVictim == this || uVictim->GetTypeId() != TYPEID_PLAYER)
6560 return false;
6562 if (GetBGTeam() == ((Player*)uVictim)->GetBGTeam())
6563 return false;
6565 return true;
6568 // 'Inactive' this aura prevents the player from gaining honor points and battleground tokens
6569 if (GetDummyAura(SPELL_AURA_PLAYER_INACTIVE))
6570 return false;
6572 ObjectGuid victim_guid;
6573 uint32 victim_rank = 0;
6575 // need call before fields update to have chance move yesterday data to appropriate fields before today data change.
6576 UpdateHonorKills();
6578 if (honor <= 0)
6580 if (!uVictim || uVictim == this || uVictim->HasAuraType(SPELL_AURA_NO_PVP_CREDIT))
6581 return false;
6583 victim_guid = uVictim->GetObjectGuid();
6585 if (uVictim->GetTypeId() == TYPEID_PLAYER)
6587 Player* pVictim = (Player*)uVictim;
6589 if (GetTeam() == pVictim->GetTeam() && !sWorld.IsFFAPvPRealm())
6590 return false;
6592 float f = 1; // need for total kills (?? need more info)
6593 uint32 k_grey = 0;
6594 uint32 k_level = getLevel();
6595 uint32 v_level = pVictim->getLevel();
6598 // PLAYER_CHOSEN_TITLE VALUES DESCRIPTION
6599 // [0] Just name
6600 // [1..14] Alliance honor titles and player name
6601 // [15..28] Horde honor titles and player name
6602 // [29..38] Other title and player name
6603 // [39+] Nothing
6604 uint32 victim_title = pVictim->GetUInt32Value(PLAYER_CHOSEN_TITLE);
6605 // Get Killer titles, CharTitlesEntry::bit_index
6606 // Ranks:
6607 // title[1..14] -> rank[5..18]
6608 // title[15..28] -> rank[5..18]
6609 // title[other] -> 0
6610 if (victim_title == 0)
6611 victim_guid.Clear(); // Don't show HK: <rank> message, only log.
6612 else if (victim_title < 15)
6613 victim_rank = victim_title + 4;
6614 else if (victim_title < 29)
6615 victim_rank = victim_title - 14 + 4;
6616 else
6617 victim_guid.Clear(); // Don't show HK: <rank> message, only log.
6620 k_grey = MaNGOS::XP::GetGrayLevel(k_level);
6622 if (v_level <= k_grey)
6623 return false;
6625 float diff_level = (k_level == k_grey) ? 1 : ((float(v_level) - float(k_grey)) / (float(k_level) - float(k_grey)));
6627 int32 v_rank = 1; // need more info
6629 honor = ((f * diff_level * (190 + v_rank * 10)) / 6);
6630 honor *= float(k_level) / 70.0f; // factor of dependence on levels of the killer
6632 // count the number of playerkills in one day
6633 ApplyModUInt32Value(PLAYER_FIELD_KILLS, 1, true);
6634 // and those in a lifetime
6635 ApplyModUInt32Value(PLAYER_FIELD_LIFETIME_HONORBALE_KILLS, 1, true);
6636 UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EARN_HONORABLE_KILL);
6637 UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_CLASS, pVictim->getClass());
6638 UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_RACE, pVictim->getRace());
6640 else
6642 Creature* cVictim = (Creature*)uVictim;
6644 if (!cVictim->IsRacialLeader())
6645 return false;
6647 honor = 100; // ??? need more info
6648 victim_rank = 19; // HK: Leader
6652 if (uVictim != NULL)
6654 honor *= sWorld.getConfig(CONFIG_FLOAT_RATE_HONOR);
6655 honor *= (GetMaxPositiveAuraModifier(SPELL_AURA_MOD_HONOR_GAIN) + 100.0f) / 100.0f;
6657 if (groupsize > 1)
6658 honor /= groupsize;
6660 honor *= (((float)urand(8, 12)) / 10); // approx honor: 80% - 120% of real honor
6663 // honor - for show honor points in log
6664 // victim_guid - for show victim name in log
6665 // victim_rank [1..4] HK: <dishonored rank>
6666 // victim_rank [5..19] HK: <alliance\horde rank>
6667 // victim_rank [0,20+] HK: <>
6668 WorldPacket data(SMSG_PVP_CREDIT, 4 + 8 + 4);
6669 data << uint32(honor);
6670 data << ObjectGuid(victim_guid);
6671 data << uint32(victim_rank);
6672 GetSession()->SendPacket(&data);
6674 // add honor points
6675 ModifyCurrencyCount(CURRENCY_HONOR_POINTS, int32(honor));
6677 return true;
6680 void Player::SetInGuild(uint32 GuildId)
6682 if (GuildId)
6683 SetGuidValue(OBJECT_FIELD_DATA, ObjectGuid(HIGHGUID_GUILD, 0, GuildId));
6684 else
6685 SetGuidValue(OBJECT_FIELD_DATA, ObjectGuid());
6687 ApplyModFlag(PLAYER_FLAGS, PLAYER_FLAGS_GUILD_LEVELING_ENABLED, GuildId != 0 && sWorld.getConfig(CONFIG_BOOL_GUILD_LEVELING_ENABLED));
6688 SetUInt16Value(OBJECT_FIELD_TYPE, 1, GuildId != 0);
6691 std::string Player::GetGuildName() const
6693 if (Guild* guild = sGuildMgr.GetGuildById(GetGuildId()))
6694 return guild->GetName();
6696 return "";
6699 uint32 Player::GetGuildIdFromDB(ObjectGuid guid)
6701 uint32 lowguid = guid.GetCounter();
6703 QueryResult* result = CharacterDatabase.PQuery("SELECT guildid FROM guild_member WHERE guid='%u'", lowguid);
6704 if (!result)
6705 return 0;
6707 uint32 id = result->Fetch()[0].GetUInt32();
6708 delete result;
6709 return id;
6712 ObjectGuid Player::GetGuildGuidFromDB(ObjectGuid guid)
6714 if (uint32 guildId = GetGuildIdFromDB(guid))
6715 return ObjectGuid(HIGHGUID_GUILD, GetGuildIdFromDB(guid));
6716 else
6717 return ObjectGuid();
6720 uint32 Player::GetRankFromDB(ObjectGuid guid)
6722 QueryResult* result = CharacterDatabase.PQuery("SELECT rank FROM guild_member WHERE guid='%u'", guid.GetCounter());
6723 if (result)
6725 uint32 v = result->Fetch()[0].GetUInt32();
6726 delete result;
6727 return v;
6729 else
6730 return 0;
6733 void Player::SendGuildDeclined(std::string name, bool autodecline)
6735 WorldPacket data(SMSG_GUILD_DECLINE, 10);
6736 data << name;
6737 data << uint8(autodecline);
6738 GetSession()->SendPacket(&data);
6742 uint32 Player::GetArenaTeamIdFromDB(ObjectGuid guid, ArenaType type)
6744 QueryResult* result = CharacterDatabase.PQuery("SELECT arena_team_member.arenateamid FROM arena_team_member JOIN arena_team ON arena_team_member.arenateamid = arena_team.arenateamid WHERE guid='%u' AND type='%u' LIMIT 1", guid.GetCounter(), type);
6745 if (!result)
6746 return 0;
6748 uint32 id = (*result)[0].GetUInt32();
6749 delete result;
6750 return id;
6753 uint32 Player::GetZoneIdFromDB(ObjectGuid guid)
6755 uint32 lowguid = guid.GetCounter();
6756 QueryResult* result = CharacterDatabase.PQuery("SELECT zone FROM characters WHERE guid='%u'", lowguid);
6757 if (!result)
6758 return 0;
6759 Field* fields = result->Fetch();
6760 uint32 zone = fields[0].GetUInt32();
6761 delete result;
6763 if (!zone)
6765 // stored zone is zero, use generic and slow zone detection
6766 result = CharacterDatabase.PQuery("SELECT map,position_x,position_y,position_z FROM characters WHERE guid='%u'", lowguid);
6767 if (!result)
6768 return 0;
6769 fields = result->Fetch();
6770 uint32 map = fields[0].GetUInt32();
6771 float posx = fields[1].GetFloat();
6772 float posy = fields[2].GetFloat();
6773 float posz = fields[3].GetFloat();
6774 delete result;
6776 zone = sTerrainMgr.GetZoneId(map, posx, posy, posz);
6778 if (zone > 0)
6779 CharacterDatabase.PExecute("UPDATE characters SET zone='%u' WHERE guid='%u'", zone, lowguid);
6782 return zone;
6785 uint32 Player::GetLevelFromDB(ObjectGuid guid)
6787 uint32 lowguid = guid.GetCounter();
6789 QueryResult* result = CharacterDatabase.PQuery("SELECT level FROM characters WHERE guid='%u'", lowguid);
6790 if (!result)
6791 return 0;
6793 Field* fields = result->Fetch();
6794 uint32 level = fields[0].GetUInt32();
6795 delete result;
6797 return level;
6800 void Player::UpdateArea(uint32 newArea)
6802 m_areaUpdateId = newArea;
6804 AreaTableEntry const* area = GetAreaEntryByAreaID(newArea);
6806 // FFA_PVP flags are area and not zone id dependent
6807 // so apply them accordingly
6808 if (area && (area->flags & AREA_FLAG_ARENA))
6810 if (!isGameMaster())
6811 SetFFAPvP(true);
6813 else
6815 // remove ffa flag only if not ffapvp realm
6816 // removal in sanctuaries and capitals is handled in zone update
6817 if (IsFFAPvP() && !sWorld.IsFFAPvPRealm())
6818 SetFFAPvP(false);
6821 if (area)
6823 // Dalaran restricted flight zone
6824 if ((area->flags & AREA_FLAG_CANNOT_FLY) && IsFreeFlying() && !isGameMaster() && !HasAura(58600))
6825 CastSpell(this, 58600, true); // Restricted Flight Area
6827 // TODO: implement wintergrasp parachute when battle in progress
6828 /* if ((area->flags & AREA_FLAG_OUTDOOR_PVP) && IsFreeFlying() && <WINTERGRASP_BATTLE_IN_PROGRESS> && !isGameMaster())
6829 CastSpell(this, 58730, true); */
6832 UpdateAreaDependentAuras();
6835 bool Player::CanUseCapturePoint()
6837 return isAlive() && // living
6838 !HasStealthAura() && // not stealthed
6839 !HasInvisibilityAura() && // visible
6840 (IsPvP() || sWorld.IsPvPRealm()) &&
6841 !HasMovementFlag(MOVEFLAG_FLYING) &&
6842 !IsTaxiFlying() &&
6843 !isGameMaster();
6846 void Player::UpdateZone(uint32 newZone, uint32 newArea)
6848 AreaTableEntry const* zone = GetAreaEntryByAreaID(newZone);
6849 if (!zone)
6850 return;
6852 if (m_zoneUpdateId != newZone)
6854 // handle outdoor pvp zones
6855 sOutdoorPvPMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId);
6856 sOutdoorPvPMgr.HandlePlayerEnterZone(this, newZone);
6858 SendInitWorldStates(newZone, newArea); // only if really enters to new zone, not just area change, works strange...
6860 if (sWorld.getConfig(CONFIG_BOOL_WEATHER))
6862 if (Weather* wth = sWorld.FindWeather(zone->ID))
6863 wth->SendWeatherUpdateToPlayer(this);
6864 else if (!sWorld.AddWeather(zone->ID))
6866 // send fine weather packet to remove old zone's weather
6867 Weather::SendFineWeatherUpdateToPlayer(this);
6872 m_zoneUpdateId = newZone;
6873 m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL;
6875 // zone changed, so area changed as well, update it
6876 UpdateArea(newArea);
6878 // in PvP, any not controlled zone (except zone->team == 6, default case)
6879 // in PvE, only opposition team capital
6880 switch (zone->team)
6882 case AREATEAM_ALLY:
6883 pvpInfo.inHostileArea = GetTeam() != ALLIANCE && (sWorld.IsPvPRealm() || zone->flags & AREA_FLAG_CAPITAL);
6884 break;
6885 case AREATEAM_HORDE:
6886 pvpInfo.inHostileArea = GetTeam() != HORDE && (sWorld.IsPvPRealm() || zone->flags & AREA_FLAG_CAPITAL);
6887 break;
6888 case AREATEAM_NONE:
6889 // overwrite for battlegrounds, maybe batter some zone flags but current known not 100% fit to this
6890 pvpInfo.inHostileArea = sWorld.IsPvPRealm() || InBattleGround();
6891 break;
6892 default: // 6 in fact
6893 pvpInfo.inHostileArea = false;
6894 break;
6897 if (pvpInfo.inHostileArea) // in hostile area
6899 if (!IsPvP() || pvpInfo.endTimer != 0)
6900 UpdatePvP(true, true);
6902 else // in friendly area
6904 if (IsPvP() && !HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_IN_PVP) && pvpInfo.endTimer == 0)
6905 pvpInfo.endTimer = time(0); // start toggle-off
6908 if (zone->flags & AREA_FLAG_SANCTUARY) // in sanctuary
6910 SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY);
6911 if (sWorld.IsFFAPvPRealm())
6912 SetFFAPvP(false);
6914 else
6916 RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY);
6919 if (zone->flags & AREA_FLAG_CAPITAL) // in capital city
6920 SetRestType(REST_TYPE_IN_CITY);
6921 else if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) && GetRestType() != REST_TYPE_IN_TAVERN)
6922 // resting and not in tavern (leave city then); tavern leave handled in CheckAreaExploreAndOutdoor
6923 SetRestType(REST_TYPE_NO);
6925 // remove items with area/map limitations (delete only for alive player to allow back in ghost mode)
6926 // if player resurrected at teleport this will be applied in resurrect code
6927 if (isAlive())
6928 DestroyZoneLimitedItem(true, newZone);
6930 // check some item equip limitations (in result lost CanTitanGrip at talent reset, for example)
6931 AutoUnequipOffhandIfNeed();
6933 // recent client version not send leave/join channel packets for built-in local channels
6934 UpdateLocalChannels(newZone);
6936 // group update
6937 if (GetGroup())
6938 SetGroupUpdateFlag(GROUP_UPDATE_FLAG_ZONE);
6940 UpdateZoneDependentAuras();
6941 UpdateZoneDependentPets();
6944 // If players are too far way of duel flag... then player loose the duel
6945 void Player::CheckDuelDistance(time_t currTime)
6947 if (!duel)
6948 return;
6950 GameObject* obj = GetMap()->GetGameObject(GetGuidValue(PLAYER_DUEL_ARBITER));
6951 if (!obj)
6953 // player not at duel start map
6954 DuelComplete(DUEL_FLED);
6955 return;
6958 if (duel->outOfBound == 0)
6960 if (!IsWithinDistInMap(obj, 50))
6962 duel->outOfBound = currTime;
6964 WorldPacket data(SMSG_DUEL_OUTOFBOUNDS, 0);
6965 GetSession()->SendPacket(&data);
6968 else
6970 if (IsWithinDistInMap(obj, 40))
6972 duel->outOfBound = 0;
6974 WorldPacket data(SMSG_DUEL_INBOUNDS, 0);
6975 GetSession()->SendPacket(&data);
6977 else if (currTime >= (duel->outOfBound + 10))
6979 DuelComplete(DUEL_FLED);
6984 void Player::DuelComplete(DuelCompleteType type)
6986 // duel not requested
6987 if (!duel)
6988 return;
6990 WorldPacket data(SMSG_DUEL_COMPLETE, (1));
6991 data << (uint8)((type != DUEL_INTERRUPTED) ? 1 : 0);
6992 GetSession()->SendPacket(&data);
6993 duel->opponent->GetSession()->SendPacket(&data);
6995 if (type != DUEL_INTERRUPTED)
6997 data.Initialize(SMSG_DUEL_WINNER, (1 + 20)); // we guess size
6998 data << (uint8)((type == DUEL_WON) ? 0 : 1); // 0 = just won; 1 = fled
6999 data << duel->opponent->GetName();
7000 data << GetName();
7001 SendMessageToSet(&data, true);
7004 if (type == DUEL_WON)
7006 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOSE_DUEL, 1);
7007 if (duel->opponent)
7008 duel->opponent->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_DUEL, 1);
7011 // Remove Duel Flag object
7012 if (GameObject* obj = GetMap()->GetGameObject(GetGuidValue(PLAYER_DUEL_ARBITER)))
7013 duel->initiator->RemoveGameObject(obj, true);
7015 /* remove auras */
7016 std::vector<uint32> auras2remove;
7017 SpellAuraHolderMap const& vAuras = duel->opponent->GetSpellAuraHolderMap();
7018 for (SpellAuraHolderMap::const_iterator i = vAuras.begin(); i != vAuras.end(); ++i)
7020 if (!i->second->IsPositive() && i->second->GetCasterGuid() == GetObjectGuid() && i->second->GetAuraApplyTime() >= duel->startTime)
7021 auras2remove.push_back(i->second->GetId());
7024 for (size_t i = 0; i < auras2remove.size(); ++i)
7025 duel->opponent->RemoveAurasDueToSpell(auras2remove[i]);
7027 auras2remove.clear();
7028 SpellAuraHolderMap const& auras = GetSpellAuraHolderMap();
7029 for (SpellAuraHolderMap::const_iterator i = auras.begin(); i != auras.end(); ++i)
7031 if (!i->second->IsPositive() && i->second->GetCasterGuid() == duel->opponent->GetObjectGuid() && i->second->GetAuraApplyTime() >= duel->startTime)
7032 auras2remove.push_back(i->second->GetId());
7034 for (size_t i = 0; i < auras2remove.size(); ++i)
7035 RemoveAurasDueToSpell(auras2remove[i]);
7037 // cleanup combo points
7038 if (GetComboTargetGuid() == duel->opponent->GetObjectGuid())
7039 ClearComboPoints();
7040 else if (GetComboTargetGuid() == duel->opponent->GetPetGuid())
7041 ClearComboPoints();
7043 if (duel->opponent->GetComboTargetGuid() == GetObjectGuid())
7044 duel->opponent->ClearComboPoints();
7045 else if (duel->opponent->GetComboTargetGuid() == GetPetGuid())
7046 duel->opponent->ClearComboPoints();
7048 // cleanups
7049 SetGuidValue(PLAYER_DUEL_ARBITER, ObjectGuid());
7050 SetUInt32Value(PLAYER_DUEL_TEAM, 0);
7051 duel->opponent->SetGuidValue(PLAYER_DUEL_ARBITER, ObjectGuid());
7052 duel->opponent->SetUInt32Value(PLAYER_DUEL_TEAM, 0);
7054 delete duel->opponent->duel;
7055 duel->opponent->duel = NULL;
7056 delete duel;
7057 duel = NULL;
7060 //---------------------------------------------------------//
7062 void Player::_ApplyItemMods(Item* item, uint8 slot, bool apply)
7064 if (slot >= INVENTORY_SLOT_BAG_END || !item)
7065 return;
7067 // not apply/remove mods for broken item
7068 if (item->IsBroken())
7069 return;
7071 ItemPrototype const* proto = item->GetProto();
7073 if (!proto)
7074 return;
7076 DETAIL_LOG("applying mods for item %u ", item->GetGUIDLow());
7078 uint32 attacktype = Player::GetAttackBySlot(slot);
7079 if (attacktype < MAX_ATTACK)
7080 _ApplyWeaponDependentAuraMods(item, WeaponAttackType(attacktype), apply);
7082 _ApplyItemBonuses(proto, slot, apply);
7084 if (slot == EQUIPMENT_SLOT_RANGED)
7085 _ApplyAmmoBonuses();
7087 ApplyItemEquipSpell(item, apply);
7088 ApplyEnchantment(item, apply);
7090 if (proto->Socket[0].Color) // only (un)equipping of items with sockets can influence metagems, so no need to waste time with normal items
7091 CorrectMetaGemEnchants(slot, apply);
7093 DEBUG_LOG("_ApplyItemMods complete.");
7096 void Player::_ApplyItemBonuses(ItemPrototype const* proto, uint8 slot, bool apply, bool only_level_scale /*= false*/)
7098 if (slot >= INVENTORY_SLOT_BAG_END || !proto)
7099 return;
7101 ScalingStatDistributionEntry const* ssd = proto->ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(proto->ScalingStatDistribution) : NULL;
7102 if (only_level_scale && !ssd)
7103 return;
7105 // req. check at equip, but allow use for extended range if range limit max level, set proper level
7106 uint32 ssd_level = getLevel();
7107 if (ssd && ssd_level > ssd->MaxLevel)
7108 ssd_level = ssd->MaxLevel;
7110 ScalingStatValuesEntry const* ssv = proto->StatScalingFactor ? sScalingStatValuesStore.LookupEntry(ssd_level) : NULL;
7111 if (only_level_scale && !ssv)
7112 return;
7114 for (uint32 i = 0; i < MAX_ITEM_PROTO_STATS; ++i)
7116 uint32 statType = 0;
7117 int32 val = 0;
7118 // If set ScalingStatDistribution need get stats and values from it
7119 if (ssd && ssv)
7121 if (ssd->StatMod[i] < 0)
7122 continue;
7123 statType = ssd->StatMod[i];
7124 val = (ssv->getssdMultiplier(proto->StatScalingFactor) * ssd->Modifier[i]) / 10000;
7126 else
7128 statType = proto->ItemStat[i].ItemStatType;
7129 val = proto->ItemStat[i].ItemStatValue;
7132 if (val == 0)
7133 continue;
7135 switch (statType)
7137 /*case ITEM_MOD_MANA:
7138 HandleStatModifier(UNIT_MOD_MANA, BASE_VALUE, float(val), apply);
7139 break;*/
7140 case ITEM_MOD_AGILITY: // modify agility
7141 HandleStatModifier(UNIT_MOD_STAT_AGILITY, BASE_VALUE, float(val), apply);
7142 ApplyStatBuffMod(STAT_AGILITY, float(val), apply);
7143 break;
7144 case ITEM_MOD_STRENGTH: // modify strength
7145 HandleStatModifier(UNIT_MOD_STAT_STRENGTH, BASE_VALUE, float(val), apply);
7146 ApplyStatBuffMod(STAT_STRENGTH, float(val), apply);
7147 break;
7148 case ITEM_MOD_INTELLECT: // modify intellect
7149 HandleStatModifier(UNIT_MOD_STAT_INTELLECT, BASE_VALUE, float(val), apply);
7150 ApplyStatBuffMod(STAT_INTELLECT, float(val), apply);
7151 break;
7152 case ITEM_MOD_SPIRIT: // modify spirit
7153 HandleStatModifier(UNIT_MOD_STAT_SPIRIT, BASE_VALUE, float(val), apply);
7154 ApplyStatBuffMod(STAT_SPIRIT, float(val), apply);
7155 break;
7156 case ITEM_MOD_STAMINA: // modify stamina
7157 HandleStatModifier(UNIT_MOD_STAT_STAMINA, BASE_VALUE, float(val), apply);
7158 ApplyStatBuffMod(STAT_STAMINA, float(val), apply);
7159 break;
7160 case ITEM_MOD_DODGE_RATING:
7161 ApplyRatingMod(CR_DODGE, int32(val), apply);
7162 break;
7163 case ITEM_MOD_PARRY_RATING:
7164 ApplyRatingMod(CR_PARRY, int32(val), apply);
7165 break;
7166 case ITEM_MOD_CRIT_RANGED_RATING:
7167 ApplyRatingMod(CR_CRIT_RANGED, int32(val), apply);
7168 break;
7169 case ITEM_MOD_HIT_RATING:
7170 ApplyRatingMod(CR_HIT_MELEE, int32(val), apply);
7171 ApplyRatingMod(CR_HIT_RANGED, int32(val), apply);
7172 ApplyRatingMod(CR_HIT_SPELL, int32(val), apply);
7173 break;
7174 case ITEM_MOD_CRIT_RATING:
7175 ApplyRatingMod(CR_CRIT_MELEE, int32(val), apply);
7176 ApplyRatingMod(CR_CRIT_RANGED, int32(val), apply);
7177 ApplyRatingMod(CR_CRIT_SPELL, int32(val), apply);
7178 break;
7179 case ITEM_MOD_RESILIENCE_RATING:
7180 ApplyRatingMod(CR_RESILIENCE_DAMAGE_TAKEN, int32(val), apply);
7181 break;
7182 case ITEM_MOD_HASTE_RATING:
7183 ApplyRatingMod(CR_HASTE_MELEE, int32(val), apply);
7184 ApplyRatingMod(CR_HASTE_RANGED, int32(val), apply);
7185 ApplyRatingMod(CR_HASTE_SPELL, int32(val), apply);
7186 break;
7187 case ITEM_MOD_EXPERTISE_RATING:
7188 ApplyRatingMod(CR_EXPERTISE, int32(val), apply);
7189 break;
7190 case ITEM_MOD_ATTACK_POWER:
7191 HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(val), apply);
7192 HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(val), apply);
7193 break;
7194 case ITEM_MOD_RANGED_ATTACK_POWER:
7195 HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(val), apply);
7196 break;
7197 case ITEM_MOD_SPELL_POWER:
7198 ApplySpellPowerBonus(int32(val), apply);
7199 break;
7200 case ITEM_MOD_HEALTH_REGEN:
7201 ApplyHealthRegenBonus(int32(val), apply);
7202 break;
7203 case ITEM_MOD_SPELL_PENETRATION:
7204 ApplyModInt32Value(PLAYER_FIELD_MOD_TARGET_RESISTANCE, -int32(val), apply);
7205 m_spellPenetrationItemMod += apply ? val : -val;
7206 break;
7207 case ITEM_MOD_MASTERY_RATING:
7208 ApplyRatingMod(CR_MASTERY, int32(val), apply);
7209 break;
7210 // deprecated item mods
7211 case ITEM_MOD_HEALTH:
7212 case ITEM_MOD_DEFENSE_SKILL_RATING:
7213 case ITEM_MOD_BLOCK_RATING:
7214 case ITEM_MOD_HIT_MELEE_RATING:
7215 case ITEM_MOD_HIT_RANGED_RATING:
7216 case ITEM_MOD_HIT_SPELL_RATING:
7217 case ITEM_MOD_CRIT_MELEE_RATING:
7218 case ITEM_MOD_CRIT_SPELL_RATING:
7219 case ITEM_MOD_HIT_TAKEN_MELEE_RATING:
7220 case ITEM_MOD_HIT_TAKEN_RANGED_RATING:
7221 case ITEM_MOD_HIT_TAKEN_SPELL_RATING:
7222 case ITEM_MOD_CRIT_TAKEN_MELEE_RATING:
7223 case ITEM_MOD_CRIT_TAKEN_RANGED_RATING:
7224 case ITEM_MOD_CRIT_TAKEN_SPELL_RATING:
7225 case ITEM_MOD_HASTE_MELEE_RATING:
7226 case ITEM_MOD_HASTE_RANGED_RATING:
7227 case ITEM_MOD_HASTE_SPELL_RATING:
7228 case ITEM_MOD_HIT_TAKEN_RATING:
7229 case ITEM_MOD_CRIT_TAKEN_RATING:
7230 case ITEM_MOD_FERAL_ATTACK_POWER:
7231 case ITEM_MOD_SPELL_HEALING_DONE:
7232 case ITEM_MOD_SPELL_DAMAGE_DONE:
7233 case ITEM_MOD_MANA_REGENERATION:
7234 case ITEM_MOD_ARMOR_PENETRATION_RATING:
7235 case ITEM_MOD_BLOCK_VALUE:
7236 break;
7240 // Apply Spell Power from ScalingStatValue if set
7241 if (ssv)
7243 if (int32 spellbonus = ssv->getSpellBonus(proto->StatScalingFactor))
7244 ApplySpellPowerBonus(spellbonus, apply);
7247 // If set ScalingStatValue armor get it or use item armor
7248 uint32 armor = proto->GetArmor();
7249 if (ssv)
7251 if (uint32 ssvarmor = ssv->getArmorMod(proto->StatScalingFactor))
7252 armor = ssvarmor;
7254 // Add armor bonus from ArmorDamageModifier if > 0
7255 if (proto->ArmorDamageModifier > 0)
7256 armor += uint32(proto->ArmorDamageModifier);
7258 if (armor)
7260 switch (proto->InventoryType)
7262 case INVTYPE_TRINKET:
7263 case INVTYPE_NECK:
7264 case INVTYPE_CLOAK:
7265 case INVTYPE_FINGER:
7266 HandleStatModifier(UNIT_MOD_ARMOR, TOTAL_VALUE, float(armor), apply);
7267 break;
7268 default:
7269 HandleStatModifier(UNIT_MOD_ARMOR, BASE_VALUE, float(armor), apply);
7270 break;
7274 WeaponAttackType attType = BASE_ATTACK;
7275 float damage = 0.0f;
7277 if (slot == EQUIPMENT_SLOT_RANGED && (
7278 proto->InventoryType == INVTYPE_RANGED || proto->InventoryType == INVTYPE_THROWN ||
7279 proto->InventoryType == INVTYPE_RANGEDRIGHT))
7281 attType = RANGED_ATTACK;
7283 else if (slot == EQUIPMENT_SLOT_OFFHAND)
7285 attType = OFF_ATTACK;
7288 float minDamage = proto->GetMinDamage();
7289 float maxDamage = proto->GetMaxDamage();
7290 int32 extraDPS = 0;
7291 // If set dpsMod in ScalingStatValue use it for min (70% from average), max (130% from average) damage
7292 if (ssv)
7294 if ((extraDPS = ssv->getDPSMod(proto->StatScalingFactor)))
7296 float average = extraDPS * proto->Delay / 1000.0f;
7297 minDamage = 0.7f * average;
7298 maxDamage = 1.3f * average;
7301 if (minDamage > 0)
7303 damage = apply ? minDamage : BASE_MINDAMAGE;
7304 SetBaseWeaponDamage(attType, MINDAMAGE, damage);
7305 // sLog.outError("applying mindam: assigning %f to weapon mindamage, now is: %f", damage, GetWeaponDamageRange(attType, MINDAMAGE));
7308 if (maxDamage > 0)
7310 damage = apply ? maxDamage : BASE_MAXDAMAGE;
7311 SetBaseWeaponDamage(attType, MAXDAMAGE, damage);
7314 if (!CanUseEquippedWeapon(attType))
7315 return;
7317 if (proto->Delay)
7319 if (slot == EQUIPMENT_SLOT_RANGED)
7320 SetAttackTime(RANGED_ATTACK, apply ? proto->Delay : BASE_ATTACK_TIME);
7321 else if (slot == EQUIPMENT_SLOT_MAINHAND)
7322 SetAttackTime(BASE_ATTACK, apply ? proto->Delay : BASE_ATTACK_TIME);
7323 else if (slot == EQUIPMENT_SLOT_OFFHAND)
7324 SetAttackTime(OFF_ATTACK, apply ? proto->Delay : BASE_ATTACK_TIME);
7327 if (CanModifyStats() && (damage || proto->Delay))
7328 UpdateDamagePhysical(attType);
7331 void Player::_ApplyWeaponDependentAuraMods(Item* item, WeaponAttackType attackType, bool apply)
7333 AuraList const& auraCritList = GetAurasByType(SPELL_AURA_MOD_CRIT_PERCENT);
7334 for (AuraList::const_iterator itr = auraCritList.begin(); itr != auraCritList.end(); ++itr)
7335 _ApplyWeaponDependentAuraCritMod(item, attackType, *itr, apply);
7337 AuraList const& auraDamageFlatList = GetAurasByType(SPELL_AURA_MOD_DAMAGE_DONE);
7338 for (AuraList::const_iterator itr = auraDamageFlatList.begin(); itr != auraDamageFlatList.end(); ++itr)
7339 _ApplyWeaponDependentAuraDamageMod(item, attackType, *itr, apply);
7341 AuraList const& auraDamagePCTList = GetAurasByType(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE);
7342 for (AuraList::const_iterator itr = auraDamagePCTList.begin(); itr != auraDamagePCTList.end(); ++itr)
7343 _ApplyWeaponDependentAuraDamageMod(item, attackType, *itr, apply);
7346 void Player::_ApplyWeaponDependentAuraCritMod(Item* item, WeaponAttackType attackType, Aura* aura, bool apply)
7348 // generic not weapon specific case processes in aura code
7349 if(aura->GetSpellProto()->GetEquippedItemClass() == -1)
7350 return;
7352 BaseModGroup mod = BASEMOD_END;
7353 switch (attackType)
7355 case BASE_ATTACK: mod = CRIT_PERCENTAGE; break;
7356 case OFF_ATTACK: mod = OFFHAND_CRIT_PERCENTAGE; break;
7357 case RANGED_ATTACK: mod = RANGED_CRIT_PERCENTAGE; break;
7358 default: return;
7361 if (item->IsFitToSpellRequirements(aura->GetSpellProto()))
7363 HandleBaseModValue(mod, FLAT_MOD, float(aura->GetModifier()->m_amount), apply);
7367 void Player::_ApplyWeaponDependentAuraDamageMod(Item* item, WeaponAttackType attackType, Aura* aura, bool apply)
7369 // ignore spell mods for not wands
7370 Modifier const* modifier = aura->GetModifier();
7371 if ((modifier->m_miscvalue & SPELL_SCHOOL_MASK_NORMAL) == 0 && (getClassMask() & CLASSMASK_WAND_USERS) == 0)
7372 return;
7374 // generic not weapon specific case processes in aura code
7375 if(aura->GetSpellProto()->GetEquippedItemClass() == -1)
7376 return;
7378 UnitMods unitMod = UNIT_MOD_END;
7379 switch (attackType)
7381 case BASE_ATTACK: unitMod = UNIT_MOD_DAMAGE_MAINHAND; break;
7382 case OFF_ATTACK: unitMod = UNIT_MOD_DAMAGE_OFFHAND; break;
7383 case RANGED_ATTACK: unitMod = UNIT_MOD_DAMAGE_RANGED; break;
7384 default: return;
7387 UnitModifierType unitModType = TOTAL_VALUE;
7388 switch (modifier->m_auraname)
7390 case SPELL_AURA_MOD_DAMAGE_DONE: unitModType = TOTAL_VALUE; break;
7391 case SPELL_AURA_MOD_DAMAGE_PERCENT_DONE: unitModType = TOTAL_PCT; break;
7392 default: return;
7395 if (item->IsFitToSpellRequirements(aura->GetSpellProto()))
7397 HandleStatModifier(unitMod, unitModType, float(modifier->m_amount), apply);
7401 void Player::ApplyItemEquipSpell(Item* item, bool apply, bool form_change)
7403 if (!item)
7404 return;
7406 ItemPrototype const* proto = item->GetProto();
7407 if (!proto)
7408 return;
7410 for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
7412 _Spell const& spellData = proto->Spells[i];
7414 // no spell
7415 if (!spellData.SpellId)
7416 continue;
7418 if (apply)
7420 // apply only at-equip spells
7421 if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_EQUIP)
7422 continue;
7424 else
7426 // at un-apply remove all spells (not only at-apply, so any at-use active affects from item and etc)
7427 // except with at-use with negative charges, so allow consuming item spells (including with extra flag that prevent consume really)
7428 // applied to player after item remove from equip slot
7429 if (spellData.SpellTrigger == ITEM_SPELLTRIGGER_ON_USE && spellData.SpellCharges < 0)
7430 continue;
7433 // check if it is valid spell
7434 SpellEntry const* spellproto = sSpellStore.LookupEntry(spellData.SpellId);
7435 if (!spellproto)
7436 continue;
7438 ApplyEquipSpell(spellproto, item, apply, form_change);
7442 void Player::ApplyEquipSpell(SpellEntry const* spellInfo, Item* item, bool apply, bool form_change)
7444 if (apply)
7446 // Cannot be used in this stance/form
7447 if (GetErrorAtShapeshiftedCast(spellInfo, GetShapeshiftForm()) != SPELL_CAST_OK)
7448 return;
7450 if (form_change) // check aura active state from other form
7452 bool found = false;
7453 for (int k = 0; k < MAX_EFFECT_INDEX; ++k)
7455 SpellAuraHolderBounds spair = GetSpellAuraHolderBounds(spellInfo->Id);
7456 for (SpellAuraHolderMap::const_iterator iter = spair.first; iter != spair.second; ++iter)
7458 if (!item || iter->second->GetCastItemGuid() == item->GetObjectGuid())
7460 found = true;
7461 break;
7464 if (found)
7465 break;
7468 if (found) // and skip re-cast already active aura at form change
7469 return;
7472 DEBUG_LOG("WORLD: cast %s Equip spellId - %i", (item ? "item" : "itemset"), spellInfo->Id);
7474 CastSpell(this, spellInfo, true, item);
7476 else
7478 if (form_change) // check aura compatibility
7480 // Cannot be used in this stance/form
7481 if (GetErrorAtShapeshiftedCast(spellInfo, GetShapeshiftForm()) == SPELL_CAST_OK)
7482 return; // and remove only not compatible at form change
7485 if (item)
7486 RemoveAurasDueToItemSpell(item, spellInfo->Id); // un-apply all spells , not only at-equipped
7487 else
7488 RemoveAurasDueToSpell(spellInfo->Id); // un-apply spell (item set case)
7492 void Player::UpdateEquipSpellsAtFormChange()
7494 for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
7496 if (m_items[i] && !m_items[i]->IsBroken())
7498 ApplyItemEquipSpell(m_items[i], false, true); // remove spells that not fit to form
7499 ApplyItemEquipSpell(m_items[i], true, true); // add spells that fit form but not active
7503 // item set bonuses not dependent from item broken state
7504 for (size_t setindex = 0; setindex < ItemSetEff.size(); ++setindex)
7506 ItemSetEffect* eff = ItemSetEff[setindex];
7507 if (!eff)
7508 continue;
7510 for (uint32 y = 0; y < 8; ++y)
7512 SpellEntry const* spellInfo = eff->spells[y];
7513 if (!spellInfo)
7514 continue;
7516 ApplyEquipSpell(spellInfo, NULL, false, true); // remove spells that not fit to form
7517 ApplyEquipSpell(spellInfo, NULL, true, true); // add spells that fit form but not active
7523 * (un-)Apply item spells triggered at adding item to inventory ITEM_SPELLTRIGGER_ON_STORE
7525 * @param item added/removed item to/from inventory
7526 * @param apply (un-)apply spell affects.
7528 * Note: item moved from slot to slot in 2 steps RemoveItem and StoreItem/EquipItem
7529 * In result function not called in RemoveItem for prevent unexpected re-apply auras from related spells
7530 * with duration reset and etc. Instead unapply done in StoreItem/EquipItem and in specialized
7531 * functions for item final remove/destroy from inventory. If new RemoveItem calls added need be sure that
7532 * function will call after it in some way if need.
7535 void Player::ApplyItemOnStoreSpell(Item* item, bool apply)
7537 if (!item)
7538 return;
7540 ItemPrototype const* proto = item->GetProto();
7541 if (!proto)
7542 return;
7544 for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
7546 _Spell const& spellData = proto->Spells[i];
7548 // no spell
7549 if (!spellData.SpellId)
7550 continue;
7552 // apply/unapply only at-store spells
7553 if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_STORE)
7554 continue;
7556 if (apply)
7558 // can be attempt re-applied at move in inventory slots
7559 if (!HasAura(spellData.SpellId))
7560 CastSpell(this, spellData.SpellId, true, item);
7562 else
7563 RemoveAurasDueToItemSpell(item, spellData.SpellId);
7567 void Player::DestroyItemWithOnStoreSpell(Item* item, uint32 spellId)
7569 if (!item)
7570 return;
7572 ItemPrototype const* proto = item->GetProto();
7573 if (!proto)
7574 return;
7576 for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
7578 _Spell const& spellData = proto->Spells[i];
7580 if (spellData.SpellId != spellId)
7581 continue;
7583 // apply/unapply only at-store spells
7584 if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_STORE)
7585 continue;
7587 DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
7588 break;
7593 /// handles unique effect of Deadly Poison: apply poison of the other weapon when already at max. stack
7594 void Player::_HandleDeadlyPoison(Unit* Target, WeaponAttackType attType, SpellEntry const* spellInfo)
7596 SpellAuraHolder const* dPoison = NULL;
7597 SpellAuraHolderConstBounds holders = Target->GetSpellAuraHolderBounds(spellInfo->Id);
7598 for (SpellAuraHolderMap::const_iterator iter = holders.first; iter != holders.second; ++iter)
7600 if (iter->second->GetCaster() == this)
7602 dPoison = iter->second;
7603 break;
7606 if (dPoison && dPoison->GetStackAmount() == spellInfo->GetStackAmount())
7608 Item* otherWeapon = GetWeaponForAttack(attType == BASE_ATTACK ? OFF_ATTACK : BASE_ATTACK);
7609 if (!otherWeapon)
7610 return;
7612 // all poison enchantments are temporary
7613 uint32 enchant_id = otherWeapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT);
7614 if (!enchant_id)
7615 return;
7617 SpellItemEnchantmentEntry const* pSecondEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
7618 if (!pSecondEnchant)
7619 return;
7621 for (int s = 0; s < 3; ++s)
7623 if (pSecondEnchant->type[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL)
7624 continue;
7626 if (SpellEntry const* combatEntry = sSpellStore.LookupEntry(pSecondEnchant->spellid[s]))
7627 if (combatEntry->GetDispel() == DISPEL_POISON)
7628 CastSpell(Target, combatEntry, true, otherWeapon);
7633 void Player::CastItemCombatSpell(Unit* Target, WeaponAttackType attType)
7635 Item* item = GetWeaponForAttack(attType, true, false);
7636 if (!item)
7637 return;
7639 ItemPrototype const* proto = item->GetProto();
7640 if (!proto)
7641 return;
7643 if (!Target || Target == this)
7644 return;
7646 for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
7648 _Spell const& spellData = proto->Spells[i];
7650 // no spell
7651 if (!spellData.SpellId)
7652 continue;
7654 // wrong triggering type
7655 if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_CHANCE_ON_HIT)
7656 continue;
7658 SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellData.SpellId);
7659 if (!spellInfo)
7661 sLog.outError("WORLD: unknown Item spellid %i", spellData.SpellId);
7662 continue;
7665 // not allow proc extra attack spell at extra attack
7666 if (m_extraAttacks && IsSpellHaveEffect(spellInfo, SPELL_EFFECT_ADD_EXTRA_ATTACKS))
7667 return;
7669 float chance = (float)spellInfo->GetProcChance();
7671 if (spellData.SpellPPMRate)
7673 uint32 WeaponSpeed = proto->Delay;
7674 chance = GetPPMProcChance(WeaponSpeed, spellData.SpellPPMRate);
7676 else if (chance > 100.0f)
7678 chance = GetWeaponProcChance();
7681 if (roll_chance_f(chance))
7682 CastSpell(Target, spellInfo->Id, true, item);
7685 // item combat enchantments
7686 for (int e_slot = 0; e_slot < MAX_ENCHANTMENT_SLOT; ++e_slot)
7688 if (e_slot > PRISMATIC_ENCHANTMENT_SLOT && e_slot < PROP_ENCHANTMENT_SLOT_0)
7689 continue;
7691 uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(e_slot));
7692 SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
7693 if (!pEnchant) continue;
7694 for (int s = 0; s < 3; ++s)
7696 if (pEnchant->type[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL)
7697 continue;
7699 uint32 proc_spell_id = pEnchant->spellid[s];
7700 SpellEntry const* spellInfo = sSpellStore.LookupEntry(proc_spell_id);
7701 if (!spellInfo)
7703 sLog.outError("Player::CastItemCombatSpell Enchant %i, cast unknown spell %i", pEnchant->ID, proc_spell_id);
7704 continue;
7707 // Use first rank to access spell item enchant procs
7708 float ppmRate = sSpellMgr.GetItemEnchantProcChance(spellInfo->Id);
7710 float chance = ppmRate
7711 ? GetPPMProcChance(proto->Delay, ppmRate)
7712 : pEnchant->amount[s] != 0 ? float(pEnchant->amount[s]) : GetWeaponProcChance();
7715 ApplySpellMod(spellInfo->Id, SPELLMOD_CHANCE_OF_SUCCESS, chance);
7716 ApplySpellMod(spellInfo->Id, SPELLMOD_FREQUENCY_OF_SUCCESS, chance);
7718 if (roll_chance_f(chance))
7720 if (IsPositiveSpell(spellInfo->Id))
7721 CastSpell(this, spellInfo->Id, true, item);
7722 else
7724 // Deadly Poison, unique effect needs to be handled before casting triggered spell
7725 if (spellInfo->IsFitToFamily(SPELLFAMILY_ROGUE, UI64LIT(0x0000000000010000)))
7726 _HandleDeadlyPoison(Target, attType, spellInfo);
7728 CastSpell(Target, spellInfo->Id, true, item);
7735 void Player::CastItemUseSpell(Item* item, SpellCastTargets const& targets, uint8 cast_count, uint32 glyphIndex)
7737 ItemPrototype const* proto = item->GetProto();
7738 // special learning case
7739 if (proto->Spells[0].SpellId == SPELL_ID_GENERIC_LEARN || proto->Spells[0].SpellId == SPELL_ID_GENERIC_LEARN_PET)
7741 uint32 learn_spell_id = proto->Spells[0].SpellId;
7742 uint32 learning_spell_id = proto->Spells[1].SpellId;
7744 SpellEntry const* spellInfo = sSpellStore.LookupEntry(learn_spell_id);
7745 if (!spellInfo)
7747 sLog.outError("Player::CastItemUseSpell: Item (Entry: %u) in have wrong spell id %u, ignoring ", proto->ItemId, learn_spell_id);
7748 SendEquipError(EQUIP_ERR_NONE, item);
7749 return;
7752 Spell* spell = new Spell(this, spellInfo, false);
7753 spell->m_CastItem = item;
7754 spell->m_cast_count = cast_count; // set count of casts
7755 spell->m_currentBasePoints[EFFECT_INDEX_0] = learning_spell_id;
7756 spell->prepare(&targets);
7757 return;
7760 // use triggered flag only for items with many spell casts and for not first cast
7761 int count = 0;
7763 // item spells casted at use
7764 for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
7766 _Spell const& spellData = proto->Spells[i];
7768 // no spell
7769 if (!spellData.SpellId)
7770 continue;
7772 // wrong triggering type
7773 if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_USE)
7774 continue;
7776 SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellData.SpellId);
7777 if (!spellInfo)
7779 sLog.outError("Player::CastItemUseSpell: Item (Entry: %u) in have wrong spell id %u, ignoring", proto->ItemId, spellData.SpellId);
7780 continue;
7783 Spell* spell = new Spell(this, spellInfo, (count > 0));
7784 spell->m_CastItem = item;
7785 spell->m_cast_count = cast_count; // set count of casts
7786 spell->m_glyphIndex = glyphIndex; // glyph index
7787 spell->prepare(&targets);
7789 ++count;
7792 // Item enchantments spells casted at use
7793 for (int e_slot = 0; e_slot < MAX_ENCHANTMENT_SLOT; ++e_slot)
7795 if (e_slot > PRISMATIC_ENCHANTMENT_SLOT && e_slot < PROP_ENCHANTMENT_SLOT_0)
7796 continue;
7798 uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(e_slot));
7799 SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
7800 if (!pEnchant)
7801 continue;
7803 for (int s = 0; s < 3; ++s)
7805 if (pEnchant->type[s] != ITEM_ENCHANTMENT_TYPE_USE_SPELL)
7806 continue;
7808 SpellEntry const* spellInfo = sSpellStore.LookupEntry(pEnchant->spellid[s]);
7809 if (!spellInfo)
7811 sLog.outError("Player::CastItemUseSpell Enchant %i, cast unknown spell %i", pEnchant->ID, pEnchant->spellid[s]);
7812 continue;
7815 Spell* spell = new Spell(this, spellInfo, (count > 0));
7816 spell->m_CastItem = item;
7817 spell->m_cast_count = cast_count; // set count of casts
7818 spell->m_glyphIndex = glyphIndex; // glyph index
7819 spell->prepare(&targets);
7821 ++count;
7826 void Player::_RemoveAllItemMods()
7828 DEBUG_LOG("_RemoveAllItemMods start.");
7830 for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
7832 if (m_items[i])
7834 ItemPrototype const* proto = m_items[i]->GetProto();
7835 if (!proto)
7836 continue;
7838 // item set bonuses not dependent from item broken state
7839 if (proto->ItemSet)
7840 RemoveItemsSetItem(this, proto);
7842 if (m_items[i]->IsBroken())
7843 continue;
7845 ApplyItemEquipSpell(m_items[i], false);
7846 ApplyEnchantment(m_items[i], false);
7850 for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
7852 if (m_items[i])
7854 if (m_items[i]->IsBroken())
7855 continue;
7856 ItemPrototype const* proto = m_items[i]->GetProto();
7857 if (!proto)
7858 continue;
7860 uint32 attacktype = Player::GetAttackBySlot(i);
7861 if (attacktype < MAX_ATTACK)
7862 _ApplyWeaponDependentAuraMods(m_items[i], WeaponAttackType(attacktype), false);
7864 _ApplyItemBonuses(proto, i, false);
7866 if (i == EQUIPMENT_SLOT_RANGED)
7867 _ApplyAmmoBonuses();
7871 DEBUG_LOG("_RemoveAllItemMods complete.");
7874 void Player::_ApplyAllItemMods()
7876 DEBUG_LOG("_ApplyAllItemMods start.");
7878 for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
7880 if (m_items[i])
7882 if (m_items[i]->IsBroken())
7883 continue;
7885 ItemPrototype const* proto = m_items[i]->GetProto();
7886 if (!proto)
7887 continue;
7889 uint32 attacktype = Player::GetAttackBySlot(i);
7890 if (attacktype < MAX_ATTACK)
7891 _ApplyWeaponDependentAuraMods(m_items[i], WeaponAttackType(attacktype), true);
7893 _ApplyItemBonuses(proto, i, true);
7895 if (i == EQUIPMENT_SLOT_RANGED)
7896 _ApplyAmmoBonuses();
7900 for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
7902 if (m_items[i])
7904 ItemPrototype const* proto = m_items[i]->GetProto();
7905 if (!proto)
7906 continue;
7908 // item set bonuses not dependent from item broken state
7909 if (proto->ItemSet)
7910 AddItemsSetItem(this, m_items[i]);
7912 if (m_items[i]->IsBroken())
7913 continue;
7915 ApplyItemEquipSpell(m_items[i], true);
7916 ApplyEnchantment(m_items[i], true);
7920 DEBUG_LOG("_ApplyAllItemMods complete.");
7923 void Player::_ApplyAllLevelScaleItemMods(bool apply)
7925 for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
7927 if (m_items[i])
7929 if (m_items[i]->IsBroken())
7930 continue;
7932 ItemPrototype const* proto = m_items[i]->GetProto();
7933 if (!proto)
7934 continue;
7936 _ApplyItemBonuses(proto, i, apply, true);
7941 void Player::_ApplyAmmoBonuses()
7943 //// check ammo
7944 //uint32 ammo_id = GetUInt32Value(PLAYER_AMMO_ID);
7945 //if(!ammo_id)
7946 // return;
7948 //float currentAmmoDPS;
7950 //ItemPrototype const *ammo_proto = ObjectMgr::GetItemPrototype( ammo_id );
7951 //if( !ammo_proto || ammo_proto->Class!=ITEM_CLASS_PROJECTILE || !CheckAmmoCompatibility(ammo_proto))
7952 // currentAmmoDPS = 0.0f;
7953 //else
7954 // currentAmmoDPS = ammo_proto->Damage[0].DamageMin;
7956 //if(currentAmmoDPS == GetAmmoDPS())
7957 // return;
7959 //m_ammoDPS = currentAmmoDPS;
7961 //if(CanModifyStats())
7962 // UpdateDamagePhysical(RANGED_ATTACK);
7965 bool Player::CheckAmmoCompatibility(const ItemPrototype* ammo_proto) const
7967 if (!ammo_proto)
7968 return false;
7970 // check ranged weapon
7971 Item* weapon = GetWeaponForAttack(RANGED_ATTACK, true, false);
7972 if (!weapon)
7973 return false;
7975 ItemPrototype const* weapon_proto = weapon->GetProto();
7976 if (!weapon_proto || weapon_proto->Class != ITEM_CLASS_WEAPON)
7977 return false;
7979 // check ammo ws. weapon compatibility
7980 switch (weapon_proto->SubClass)
7982 case ITEM_SUBCLASS_WEAPON_BOW:
7983 case ITEM_SUBCLASS_WEAPON_CROSSBOW:
7984 if (ammo_proto->SubClass != ITEM_SUBCLASS_ARROW)
7985 return false;
7986 break;
7987 case ITEM_SUBCLASS_WEAPON_GUN:
7988 if (ammo_proto->SubClass != ITEM_SUBCLASS_BULLET)
7989 return false;
7990 break;
7991 default:
7992 return false;
7995 return true;
7998 /* If in a battleground a player dies, and an enemy removes the insignia, the player's bones is lootable
7999 Called by remove insignia spell effect */
8000 void Player::RemovedInsignia(Player* looterPlr)
8002 if (!GetBattleGroundId())
8003 return;
8005 // If not released spirit, do it !
8006 if (m_deathTimer > 0)
8008 m_deathTimer = 0;
8009 BuildPlayerRepop();
8010 RepopAtGraveyard();
8013 Corpse* corpse = GetCorpse();
8014 if (!corpse)
8015 return;
8017 // We have to convert player corpse to bones, not to be able to resurrect there
8018 // SpawnCorpseBones isn't handy, 'cos it saves player while he in BG
8019 Corpse* bones = sObjectAccessor.ConvertCorpseForPlayer(GetObjectGuid(), true);
8020 if (!bones)
8021 return;
8023 // Now we must make bones lootable, and send player loot
8024 bones->SetFlag(CORPSE_FIELD_DYNAMIC_FLAGS, CORPSE_DYNFLAG_LOOTABLE);
8026 // We store the level of our player in the gold field
8027 // We retrieve this information at Player::SendLoot()
8028 bones->loot.gold = getLevel();
8029 bones->lootRecipient = looterPlr;
8030 looterPlr->SendLoot(bones->GetObjectGuid(), LOOT_INSIGNIA);
8033 void Player::SendLootRelease(ObjectGuid guid)
8035 WorldPacket data(SMSG_LOOT_RELEASE_RESPONSE, (8 + 1));
8036 data << guid;
8037 data << uint8(1);
8038 SendDirectMessage(&data);
8041 void Player::SendLoot(ObjectGuid guid, LootType loot_type)
8043 if (ObjectGuid lootGuid = GetLootGuid())
8044 m_session->DoLootRelease(lootGuid);
8046 Loot* loot = NULL;
8047 PermissionTypes permission = ALL_PERMISSION;
8049 DEBUG_LOG("Player::SendLoot");
8050 switch (guid.GetHigh())
8052 case HIGHGUID_GAMEOBJECT:
8054 DEBUG_LOG(" IS_GAMEOBJECT_GUID(guid)");
8055 GameObject* go = GetMap()->GetGameObject(guid);
8057 // not check distance for GO in case owned GO (fishing bobber case, for example)
8058 // And permit out of range GO with no owner in case fishing hole
8059 if (!go || (loot_type != LOOT_FISHINGHOLE && (loot_type != LOOT_FISHING && loot_type != LOOT_FISHING_FAIL || go->GetOwnerGuid() != GetObjectGuid()) && !go->IsWithinDistInMap(this, INTERACTION_DISTANCE)))
8061 SendLootRelease(guid);
8062 return;
8065 loot = &go->loot;
8067 Player* recipient = go->GetLootRecipient();
8068 if (!recipient)
8070 go->SetLootRecipient(this);
8071 recipient = this;
8074 // generate loot only if ready for open and spawned in world
8075 if (go->getLootState() == GO_READY && go->isSpawned())
8077 uint32 lootid = go->GetGOInfo()->GetLootId();
8078 if ((go->GetEntry() == BG_AV_OBJECTID_MINE_N || go->GetEntry() == BG_AV_OBJECTID_MINE_S))
8079 if (BattleGround* bg = GetBattleGround())
8080 if (bg->GetTypeID() == BATTLEGROUND_AV)
8081 if (!(((BattleGroundAV*)bg)->PlayerCanDoMineQuest(go->GetEntry(), GetTeam())))
8083 SendLootRelease(guid);
8084 return;
8087 // Entry 0 in fishing loot template used for store junk fish loot at fishing fail it junk allowed by config option
8088 // this is overwrite fishinghole loot for example
8089 if (loot_type == LOOT_FISHING_FAIL)
8090 loot->FillLoot(0, LootTemplates_Fishing, this, true);
8091 else if (lootid)
8093 DEBUG_LOG(" if(lootid)");
8094 loot->clear();
8095 loot->FillLoot(lootid, LootTemplates_Gameobject, this, false);
8096 loot->generateMoneyLoot(go->GetGOInfo()->MinMoneyLoot, go->GetGOInfo()->MaxMoneyLoot);
8098 if (go->GetGoType() == GAMEOBJECT_TYPE_CHEST && go->GetGOInfo()->chest.groupLootRules)
8100 if (Group* group = go->GetGroupLootRecipient())
8102 group->UpdateLooterGuid(go, true);
8104 switch (group->GetLootMethod())
8106 case GROUP_LOOT:
8107 // GroupLoot delete items over threshold (threshold even not implemented), and roll them. Items with quality<threshold, round robin
8108 group->GroupLoot(go, loot);
8109 permission = GROUP_PERMISSION;
8110 break;
8111 case NEED_BEFORE_GREED:
8112 group->NeedBeforeGreed(go, loot);
8113 permission = GROUP_PERMISSION;
8114 break;
8115 case MASTER_LOOT:
8116 group->MasterLoot(go, loot);
8117 permission = MASTER_PERMISSION;
8118 break;
8119 default:
8120 break;
8125 else if (loot_type == LOOT_FISHING)
8126 go->getFishLoot(loot, this);
8128 go->SetLootState(GO_ACTIVATED);
8130 if (go->getLootState() == GO_ACTIVATED && go->GetGoType() == GAMEOBJECT_TYPE_CHEST && go->GetGOInfo()->chest.groupLootRules)
8132 if (Group* group = go->GetGroupLootRecipient())
8134 if (group == GetGroup())
8136 if (group->GetLootMethod() == FREE_FOR_ALL)
8137 permission = ALL_PERMISSION;
8138 else if (group->GetLooterGuid() == GetObjectGuid())
8140 if (group->GetLootMethod() == MASTER_LOOT)
8141 permission = MASTER_PERMISSION;
8142 else
8143 permission = ALL_PERMISSION;
8145 else
8146 permission = GROUP_PERMISSION;
8148 else
8149 permission = NONE_PERMISSION;
8151 else if (recipient == this)
8152 permission = ALL_PERMISSION;
8153 else
8154 permission = NONE_PERMISSION;
8156 break;
8158 case HIGHGUID_ITEM:
8160 Item* item = GetItemByGuid(guid);
8162 if (!item)
8164 SendLootRelease(guid);
8165 return;
8168 permission = OWNER_PERMISSION;
8170 loot = &item->loot;
8172 if (!item->HasGeneratedLoot())
8174 item->loot.clear();
8176 switch (loot_type)
8178 case LOOT_DISENCHANTING:
8179 loot->FillLoot(item->GetProto()->DisenchantID, LootTemplates_Disenchant, this, true);
8180 item->SetLootState(ITEM_LOOT_TEMPORARY);
8181 break;
8182 case LOOT_PROSPECTING:
8183 loot->FillLoot(item->GetEntry(), LootTemplates_Prospecting, this, true);
8184 item->SetLootState(ITEM_LOOT_TEMPORARY);
8185 break;
8186 case LOOT_MILLING:
8187 loot->FillLoot(item->GetEntry(), LootTemplates_Milling, this, true);
8188 item->SetLootState(ITEM_LOOT_TEMPORARY);
8189 break;
8190 default:
8191 loot->FillLoot(item->GetEntry(), LootTemplates_Item, this, true, item->GetProto()->MaxMoneyLoot == 0);
8192 loot->generateMoneyLoot(item->GetProto()->MinMoneyLoot, item->GetProto()->MaxMoneyLoot);
8193 item->SetLootState(ITEM_LOOT_CHANGED);
8194 break;
8197 break;
8199 case HIGHGUID_CORPSE: // remove insignia
8201 Corpse* bones = GetMap()->GetCorpse(guid);
8203 if (!bones || !((loot_type == LOOT_CORPSE) || (loot_type == LOOT_INSIGNIA)) || (bones->GetType() != CORPSE_BONES))
8205 SendLootRelease(guid);
8206 return;
8209 loot = &bones->loot;
8211 if (!bones->lootForBody)
8213 bones->lootForBody = true;
8214 uint32 pLevel = bones->loot.gold;
8215 bones->loot.clear();
8216 if (GetBattleGround()->GetTypeID() == BATTLEGROUND_AV)
8217 loot->FillLoot(0, LootTemplates_Creature, this, false);
8218 // It may need a better formula
8219 // Now it works like this: lvl10: ~6copper, lvl70: ~9silver
8220 bones->loot.gold = (uint32)(urand(50, 150) * 0.016f * pow(((float)pLevel) / 5.76f, 2.5f) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
8223 if (bones->lootRecipient != this)
8224 permission = NONE_PERMISSION;
8225 else
8226 permission = OWNER_PERMISSION;
8227 break;
8229 case HIGHGUID_UNIT:
8230 case HIGHGUID_VEHICLE:
8232 Creature* creature = GetMap()->GetCreature(guid);
8234 // must be in range and creature must be alive for pickpocket and must be dead for another loot
8235 if (!creature || creature->isAlive() != (loot_type == LOOT_PICKPOCKETING) || !creature->IsWithinDistInMap(this, INTERACTION_DISTANCE))
8237 SendLootRelease(guid);
8238 return;
8241 if (loot_type == LOOT_PICKPOCKETING && IsFriendlyTo(creature))
8243 SendLootRelease(guid);
8244 return;
8247 loot = &creature->loot;
8249 if (loot_type == LOOT_PICKPOCKETING)
8251 if (!creature->lootForPickPocketed)
8253 creature->lootForPickPocketed = true;
8254 loot->clear();
8256 if (uint32 lootid = creature->GetCreatureInfo()->pickpocketLootId)
8257 loot->FillLoot(lootid, LootTemplates_Pickpocketing, this, false);
8259 // Generate extra money for pick pocket loot
8260 const uint32 a = urand(0, creature->getLevel() / 2);
8261 const uint32 b = urand(0, getLevel() / 2);
8262 loot->gold = uint32(10 * (a + b) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
8263 permission = OWNER_PERMISSION;
8266 else
8268 // the player whose group may loot the corpse
8269 Player* recipient = creature->GetLootRecipient();
8270 if (!recipient)
8272 creature->SetLootRecipient(this);
8273 recipient = this;
8276 if (creature->lootForPickPocketed)
8278 creature->lootForPickPocketed = false;
8279 loot->clear();
8282 if (!creature->lootForBody)
8284 creature->lootForBody = true;
8285 loot->clear();
8287 if (uint32 lootid = creature->GetCreatureInfo()->lootid)
8288 loot->FillLoot(lootid, LootTemplates_Creature, recipient, false);
8290 loot->generateMoneyLoot(creature->GetCreatureInfo()->mingold, creature->GetCreatureInfo()->maxgold);
8292 if (Group* group = creature->GetGroupLootRecipient())
8294 group->UpdateLooterGuid(creature, true);
8296 switch (group->GetLootMethod())
8298 case GROUP_LOOT:
8299 // GroupLoot delete items over threshold (threshold even not implemented), and roll them. Items with quality<threshold, round robin
8300 group->GroupLoot(creature, loot);
8301 break;
8302 case NEED_BEFORE_GREED:
8303 group->NeedBeforeGreed(creature, loot);
8304 break;
8305 case MASTER_LOOT:
8306 group->MasterLoot(creature, loot);
8307 break;
8308 default:
8309 break;
8314 // possible only if creature->lootForBody && loot->empty() at spell cast check
8315 if (loot_type == LOOT_SKINNING)
8317 if (!creature->lootForSkin)
8319 creature->lootForSkin = true;
8320 loot->clear();
8321 loot->FillLoot(creature->GetCreatureInfo()->SkinLootId, LootTemplates_Skinning, this, false);
8323 // let reopen skinning loot if will closed.
8324 if (!loot->empty())
8325 creature->SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
8327 permission = OWNER_PERMISSION;
8330 // set group rights only for loot_type != LOOT_SKINNING
8331 else
8333 if (Group* group = creature->GetGroupLootRecipient())
8335 if (group == GetGroup())
8337 if (group->GetLootMethod() == FREE_FOR_ALL)
8338 permission = ALL_PERMISSION;
8339 else if (group->GetLooterGuid() == GetObjectGuid())
8341 if (group->GetLootMethod() == MASTER_LOOT)
8342 permission = MASTER_PERMISSION;
8343 else
8344 permission = ALL_PERMISSION;
8346 else
8347 permission = GROUP_PERMISSION;
8349 else
8350 permission = NONE_PERMISSION;
8352 else if (recipient == this)
8353 permission = OWNER_PERMISSION;
8354 else
8355 permission = NONE_PERMISSION;
8358 break;
8360 default:
8362 sLog.outError("%s is unsupported for looting.", guid.GetString().c_str());
8363 return;
8367 SetLootGuid(guid);
8369 // LOOT_INSIGNIA and LOOT_FISHINGHOLE unsupported by client
8370 switch (loot_type)
8372 case LOOT_INSIGNIA: loot_type = LOOT_SKINNING; break;
8373 case LOOT_FISHING_FAIL: loot_type = LOOT_FISHING; break;
8374 case LOOT_FISHINGHOLE: loot_type = LOOT_FISHING; break;
8375 default: break;
8378 // need know merged fishing/corpse loot type for achievements
8379 loot->loot_type = loot_type;
8381 WorldPacket data(SMSG_LOOT_RESPONSE, (9 + 50)); // we guess size
8382 data << ObjectGuid(guid);
8383 data << uint8(loot_type);
8384 data << LootView(*loot, this, permission);
8385 SendDirectMessage(&data);
8387 // add 'this' player as one of the players that are looting 'loot'
8388 if (permission != NONE_PERMISSION)
8389 loot->AddLooter(GetObjectGuid());
8391 if (loot_type == LOOT_CORPSE && !guid.IsItem())
8392 SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_LOOTING);
8395 void Player::SendNotifyLootMoneyRemoved()
8397 WorldPacket data(SMSG_LOOT_CLEAR_MONEY, 0);
8398 GetSession()->SendPacket(&data);
8401 void Player::SendNotifyLootItemRemoved(uint8 lootSlot, bool currency)
8403 WorldPacket data(currency ? SMSG_LOOT_CURRENCY_REMOVED : SMSG_LOOT_REMOVED, 1);
8404 data << uint8(lootSlot);
8405 GetSession()->SendPacket(&data);
8408 void Player::SendUpdateWorldState(uint32 Field, uint32 Value)
8410 WorldPacket data(SMSG_UPDATE_WORLD_STATE, 8 + 1);
8411 data << Field;
8412 data << Value;
8413 data << uint8(0);
8414 GetSession()->SendPacket(&data);
8417 static WorldStatePair AV_world_states[] =
8419 { 0x7ae, 0x1 }, // 1966 7 snowfall n
8420 { 0x532, 0x1 }, // 1330 8 frostwolfhut hc
8421 { 0x531, 0x0 }, // 1329 9 frostwolfhut ac
8422 { 0x52e, 0x0 }, // 1326 10 stormpike firstaid a_a
8423 { 0x571, 0x0 }, // 1393 11 east frostwolf tower horde assaulted -unused
8424 { 0x570, 0x0 }, // 1392 12 west frostwolf tower horde assaulted - unused
8425 { 0x567, 0x1 }, // 1383 13 frostwolfe c
8426 { 0x566, 0x1 }, // 1382 14 frostwolfw c
8427 { 0x550, 0x1 }, // 1360 15 irondeep (N) ally
8428 { 0x544, 0x0 }, // 1348 16 ice grave a_a
8429 { 0x536, 0x0 }, // 1334 17 stormpike grave h_c
8430 { 0x535, 0x1 }, // 1333 18 stormpike grave a_c
8431 { 0x518, 0x0 }, // 1304 19 stoneheart grave a_a
8432 { 0x517, 0x0 }, // 1303 20 stoneheart grave h_a
8433 { 0x574, 0x0 }, // 1396 21 unk
8434 { 0x573, 0x0 }, // 1395 22 iceblood tower horde assaulted -unused
8435 { 0x572, 0x0 }, // 1394 23 towerpoint horde assaulted - unused
8436 { 0x56f, 0x0 }, // 1391 24 unk
8437 { 0x56e, 0x0 }, // 1390 25 iceblood a
8438 { 0x56d, 0x0 }, // 1389 26 towerp a
8439 { 0x56c, 0x0 }, // 1388 27 frostwolfe a
8440 { 0x56b, 0x0 }, // 1387 28 froswolfw a
8441 { 0x56a, 0x1 }, // 1386 29 unk
8442 { 0x569, 0x1 }, // 1385 30 iceblood c
8443 { 0x568, 0x1 }, // 1384 31 towerp c
8444 { 0x565, 0x0 }, // 1381 32 stoneh tower a
8445 { 0x564, 0x0 }, // 1380 33 icewing tower a
8446 { 0x563, 0x0 }, // 1379 34 dunn a
8447 { 0x562, 0x0 }, // 1378 35 duns a
8448 { 0x561, 0x0 }, // 1377 36 stoneheart bunker alliance assaulted - unused
8449 { 0x560, 0x0 }, // 1376 37 icewing bunker alliance assaulted - unused
8450 { 0x55f, 0x0 }, // 1375 38 dunbaldar south alliance assaulted - unused
8451 { 0x55e, 0x0 }, // 1374 39 dunbaldar north alliance assaulted - unused
8452 { 0x55d, 0x0 }, // 1373 40 stone tower d
8453 { 0x3c6, 0x0 }, // 966 41 unk
8454 { 0x3c4, 0x0 }, // 964 42 unk
8455 { 0x3c2, 0x0 }, // 962 43 unk
8456 { 0x516, 0x1 }, // 1302 44 stoneheart grave a_c
8457 { 0x515, 0x0 }, // 1301 45 stonheart grave h_c
8458 { 0x3b6, 0x0 }, // 950 46 unk
8459 { 0x55c, 0x0 }, // 1372 47 icewing tower d
8460 { 0x55b, 0x0 }, // 1371 48 dunn d
8461 { 0x55a, 0x0 }, // 1370 49 duns d
8462 { 0x559, 0x0 }, // 1369 50 unk
8463 { 0x558, 0x0 }, // 1368 51 iceblood d
8464 { 0x557, 0x0 }, // 1367 52 towerp d
8465 { 0x556, 0x0 }, // 1366 53 frostwolfe d
8466 { 0x555, 0x0 }, // 1365 54 frostwolfw d
8467 { 0x554, 0x1 }, // 1364 55 stoneh tower c
8468 { 0x553, 0x1 }, // 1363 56 icewing tower c
8469 { 0x552, 0x1 }, // 1362 57 dunn c
8470 { 0x551, 0x1 }, // 1361 58 duns c
8471 { 0x54f, 0x0 }, // 1359 59 irondeep (N) horde
8472 { 0x54e, 0x0 }, // 1358 60 irondeep (N) ally
8473 { 0x54d, 0x1 }, // 1357 61 mine (S) neutral
8474 { 0x54c, 0x0 }, // 1356 62 mine (S) horde
8475 { 0x54b, 0x0 }, // 1355 63 mine (S) ally
8476 { 0x545, 0x0 }, // 1349 64 iceblood h_a
8477 { 0x543, 0x1 }, // 1347 65 iceblod h_c
8478 { 0x542, 0x0 }, // 1346 66 iceblood a_c
8479 { 0x540, 0x0 }, // 1344 67 snowfall h_a
8480 { 0x53f, 0x0 }, // 1343 68 snowfall a_a
8481 { 0x53e, 0x0 }, // 1342 69 snowfall h_c
8482 { 0x53d, 0x0 }, // 1341 70 snowfall a_c
8483 { 0x53c, 0x0 }, // 1340 71 frostwolf g h_a
8484 { 0x53b, 0x0 }, // 1339 72 frostwolf g a_a
8485 { 0x53a, 0x1 }, // 1338 73 frostwolf g h_c
8486 { 0x539, 0x0 }, // l33t 74 frostwolf g a_c
8487 { 0x538, 0x0 }, // 1336 75 stormpike grave h_a
8488 { 0x537, 0x0 }, // 1335 76 stormpike grave a_a
8489 { 0x534, 0x0 }, // 1332 77 frostwolf hut h_a
8490 { 0x533, 0x0 }, // 1331 78 frostwolf hut a_a
8491 { 0x530, 0x0 }, // 1328 79 stormpike first aid h_a
8492 { 0x52f, 0x0 }, // 1327 80 stormpike first aid h_c
8493 { 0x52d, 0x1 }, // 1325 81 stormpike first aid a_c
8494 { 0x0, 0x0 }
8497 static WorldStatePair WS_world_states[] =
8499 { 0x62d, 0x0 }, // 1581 7 alliance flag captures
8500 { 0x62e, 0x0 }, // 1582 8 horde flag captures
8501 { 0x609, 0x0 }, // 1545 9 unk, set to 1 on alliance flag pickup...
8502 { 0x60a, 0x0 }, // 1546 10 unk, set to 1 on horde flag pickup, after drop it's -1
8503 { 0x60b, 0x2 }, // 1547 11 unk
8504 { 0x641, 0x3 }, // 1601 12 unk (max flag captures?)
8505 { 0x922, 0x1 }, // 2338 13 horde (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing)
8506 { 0x923, 0x1 }, // 2339 14 alliance (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing)
8507 { 0x1097, 0x1 }, // 4247 15 show time limit?
8508 { 0x1098, 0x19 }, // 4248 16 time remaining in minutes
8509 { 0x0, 0x0 }
8512 static WorldStatePair AB_world_states[] =
8514 { 0x6e7, 0x0 }, // 1767 7 stables alliance
8515 { 0x6e8, 0x0 }, // 1768 8 stables horde
8516 { 0x6e9, 0x0 }, // 1769 9 unk, ST?
8517 { 0x6ea, 0x0 }, // 1770 10 stables (show/hide)
8518 { 0x6ec, 0x0 }, // 1772 11 farm (0 - horde controlled, 1 - alliance controlled)
8519 { 0x6ed, 0x0 }, // 1773 12 farm (show/hide)
8520 { 0x6ee, 0x0 }, // 1774 13 farm color
8521 { 0x6ef, 0x0 }, // 1775 14 gold mine color, may be FM?
8522 { 0x6f0, 0x0 }, // 1776 15 alliance resources
8523 { 0x6f1, 0x0 }, // 1777 16 horde resources
8524 { 0x6f2, 0x0 }, // 1778 17 horde bases
8525 { 0x6f3, 0x0 }, // 1779 18 alliance bases
8526 { 0x6f4, 0x7d0 }, // 1780 19 max resources (2000)
8527 { 0x6f6, 0x0 }, // 1782 20 blacksmith color
8528 { 0x6f7, 0x0 }, // 1783 21 blacksmith (show/hide)
8529 { 0x6f8, 0x0 }, // 1784 22 unk, bs?
8530 { 0x6f9, 0x0 }, // 1785 23 unk, bs?
8531 { 0x6fb, 0x0 }, // 1787 24 gold mine (0 - horde contr, 1 - alliance contr)
8532 { 0x6fc, 0x0 }, // 1788 25 gold mine (0 - conflict, 1 - horde)
8533 { 0x6fd, 0x0 }, // 1789 26 gold mine (1 - show/0 - hide)
8534 { 0x6fe, 0x0 }, // 1790 27 gold mine color
8535 { 0x700, 0x0 }, // 1792 28 gold mine color, wtf?, may be LM?
8536 { 0x701, 0x0 }, // 1793 29 lumber mill color (0 - conflict, 1 - horde contr)
8537 { 0x702, 0x0 }, // 1794 30 lumber mill (show/hide)
8538 { 0x703, 0x0 }, // 1795 31 lumber mill color color
8539 { 0x732, 0x1 }, // 1842 32 stables (1 - uncontrolled)
8540 { 0x733, 0x1 }, // 1843 33 gold mine (1 - uncontrolled)
8541 { 0x734, 0x1 }, // 1844 34 lumber mill (1 - uncontrolled)
8542 { 0x735, 0x1 }, // 1845 35 farm (1 - uncontrolled)
8543 { 0x736, 0x1 }, // 1846 36 blacksmith (1 - uncontrolled)
8544 { 0x745, 0x2 }, // 1861 37 unk
8545 { 0x7a3, 0x708 }, // 1955 38 warning limit (1800)
8546 { 0x0, 0x0 }
8549 static WorldStatePair EY_world_states[] =
8551 { 2753, 0 }, // WORLD_STATE_EY_TOWER_COUNT_HORDE
8552 { 2752, 0 }, // WORLD_STATE_EY_TOWER_COUNT_ALLIANCE
8553 { 2733, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_DRAENEI_RUINS_HORDE
8554 { 2732, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_DRAENEI_RUINS_ALLIANCE
8555 { 2731, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_DRAENEI_RUINS_NEUTRAL
8556 { 2730, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_MAGE_TOWER_ALLIANCE
8557 { 2729, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_MAGE_TOWER_HORDE
8558 { 2728, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_MAGE_TOWER_NEUTRAL
8559 { 2727, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_FEL_REAVER_HORDE
8560 { 2726, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_FEL_REAVER_ALLIANCE
8561 { 2725, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_FEL_REAVER_NEUTRAL
8562 { 2724, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_BLOOD_ELF_HORDE
8563 { 2723, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_BLOOD_ELF_ALLIANCE
8564 { 2722, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_BLOOD_ELF_NEUTRAL
8565 { 2757, WORLD_STATE_REMOVE }, // WORLD_STATE_EY_NETHERSTORM_FLAG_READY
8566 { 2770, 1 }, // WORLD_STATE_EY_NETHERSTORM_FLAG_STATE_HORDE
8567 { 2769, 1 }, // WORLD_STATE_EY_NETHERSTORM_FLAG_STATE_ALLIANCE
8568 { 2750, 0 }, // WORLD_STATE_EY_RESOURCES_HORDE
8569 { 2749, 0 }, // WORLD_STATE_EY_RESOURCES_ALLIANCE
8570 { 2565, 0x8e }, // global unk -- TODO: move to global world state
8571 { 3085, 0x17b } // global unk -- TODO: move to global world state
8574 static WorldStatePair SI_world_states[] = // Silithus
8576 { 2313, 0 }, // WORLD_STATE_SI_GATHERED_A
8577 { 2314, 0 }, // WORLD_STATE_SI_GATHERED_H
8578 { 2317, 0 } // WORLD_STATE_SI_SILITHYST_MAX
8581 static WorldStatePair EP_world_states[] = // Eastern Plaguelands
8583 { 2327, 0 }, // WORLD_STATE_EP_TOWER_COUNT_ALLIANCE
8584 { 2328, 0 }, // WORLD_STATE_EP_TOWER_COUNT_HORDE
8585 { 2355, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_NEUTRAL
8586 { 2374, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_CONTEST_ALLIANCE
8587 { 2375, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_CONTEST_HORDE
8588 { 2376, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_PROGRESS_ALLIANCE
8589 { 2377, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_PROGRESS_HORDE
8590 { 2378, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_ALLIANCE
8591 { 2379, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_CROWNGUARD_HORDE
8592 { 2354, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_ALLIANCE
8593 { 2356, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_HORDE
8594 { 2357, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_PROGRESS_ALLIANCE
8595 { 2358, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_PROGRESS_HORDE
8596 { 2359, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_CONTEST_ALLIANCE
8597 { 2360, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_CONTEST_HORDE
8598 { 2361, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_EASTWALL_NEUTRAL
8599 { 2352, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_NEUTRAL
8600 { 2362, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_CONTEST_ALLIANCE
8601 { 2363, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_CONTEST_HORDE
8602 { 2364, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_PROGRESS_ALLIANCE
8603 { 2365, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_PROGRESS_HORDE
8604 { 2372, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_ALLIANCE
8605 { 2373, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_NORTHPASS_HORDE
8606 { 2353, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_PLAGUEWOOD_NEUTRAL
8607 { 2366, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_PLAGUEWOOD_CONTEST_ALLIANCE
8608 { 2367, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_PLAGUEWOOD_CONTEST_HORDE - not in dbc! sent for consistency's sake, and to match field count
8609 { 2368, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_PLAGUEWOOD_PROGRESS_ALLIANCE
8610 { 2369, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_PLAGUEWOOD_PROGRESS_HORDE
8611 { 2370, WORLD_STATE_REMOVE }, // WORLD_STATE_EP_PLAGUEWOOD_ALLIANCE
8612 { 2371, WORLD_STATE_REMOVE } // WORLD_STATE_EP_PLAGUEWOOD_HORDE
8615 static WorldStatePair HP_world_states[] = // Hellfire Peninsula
8617 { 2490, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_TOWER_DISPLAY_A
8618 { 2489, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_TOWER_DISPLAY_H
8619 { 2485, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_BROKEN_HILL_NEUTRAL
8620 { 2484, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_BROKEN_HILL_HORDE
8621 { 2483, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_BROKEN_HILL_ALLIANCE
8622 { 2482, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_OVERLOOK_NEUTRAL
8623 { 2481, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_OVERLOOK_HORDE
8624 { 2480, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_OVERLOOK_ALLIANCE
8625 { 2478, 0 }, // WORLD_STATE_HP_TOWER_COUNT_HORDE
8626 { 2476, 0 }, // WORLD_STATE_HP_TOWER_COUNT_ALLIANCE
8627 { 2472, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_STADIUM_NEUTRAL
8628 { 2471, WORLD_STATE_REMOVE }, // WORLD_STATE_HP_STADIUM_ALLIANCE
8629 { 2470, WORLD_STATE_REMOVE } // WORLD_STATE_HP_STADIUM_HORDE
8632 static WorldStatePair TF_world_states[] = // Terokkar Forest
8634 { 2622, 0 }, // WORLD_STATE_TF_TOWER_COUNT_H
8635 { 2621, 0 }, // WORLD_STATE_TF_TOWER_COUNT_A
8636 { 2620, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_TOWERS_CONTROLLED
8637 { 2695, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_SOUTH_EAST_TOWER_HORDE
8638 { 2694, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_SOUTH_EAST_TOWER_ALLIANCE
8639 { 2693, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_SOUTH_TOWER_NEUTRAL
8640 { 2692, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_SOUTH_TOWER_HORDE
8641 { 2691, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_SOUTH_TOWER_ALLIANCE
8642 { 2690, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_EAST_TOWER_NEUTRAL
8643 { 2689, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_EAST_TOWER_HORDE
8644 { 2688, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_EAST_TOWER_ALLIANCE
8645 { 2686, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_NORTH_TOWER_NEUTRAL
8646 { 2685, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_NORTH_TOWER_HORDE
8647 { 2684, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_NORTH_TOWER_ALLIANCE
8648 { 2683, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_WEST_TOWER_ALLIANCE
8649 { 2682, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_WEST_TOWER_HORDE
8650 { 2681, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_WEST_TOWER_NEUTRAL
8651 { 2512, 0 }, // WORLD_STATE_TF_TIME_MIN_FIRST_DIGIT
8652 { 2510, 0 }, // WORLD_STATE_TF_TIME_MIN_SECOND_DIGIT
8653 { 2509, 0 }, // WORLD_STATE_TF_TIME_HOURS
8654 { 2508, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_LOCKED_NEUTRAL
8655 { 2696, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_SOUTH_EAST_TOWER_NEUTRAL
8656 { 2768, WORLD_STATE_REMOVE }, // WORLD_STATE_TF_LOCKED_HORDE
8657 { 2767, WORLD_STATE_REMOVE } // WORLD_STATE_TF_LOCKED_ALLIANCE
8660 static WorldStatePair ZM_world_states[] = // Zangarmarsh
8662 { 2653, 0x1 }, // WORLD_STATE_ZM_UNK
8663 { 2652, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_EAST_NEUTRAL
8664 { 2651, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_EAST_HORDE
8665 { 2650, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_EAST_ALLIANCE
8666 { 2649, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_GRAVEYARD_HORDE
8667 { 2648, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_GRAVEYARD_ALLIANCE
8668 { 2647, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_GRAVEYARD_NEUTRAL
8669 { 2646, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_WEST_NEUTRAL
8670 { 2645, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_WEST_HORDE
8671 { 2644, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_WEST_ALLIANCE
8672 { 2560, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_EAST_UI_NEUTRAL
8673 { 2559, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_EAST_UI_HORDE
8674 { 2558, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_EAST_UI_ALLIANCE
8675 { 2557, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_WEST_UI_NEUTRAL
8676 { 2556, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_WEST_UI_HORDE
8677 { 2555, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_BEACON_WEST_UI_ALLIANCE
8678 { 2658, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_FLAG_READY_HORDE
8679 { 2657, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_FLAG_NOT_READY_HORDE
8680 { 2656, WORLD_STATE_REMOVE }, // WORLD_STATE_ZM_FLAG_NOT_READY_ALLIANCE
8681 { 2655, WORLD_STATE_REMOVE } // WORLD_STATE_ZM_FLAG_READY_ALLIANCE
8684 static WorldStatePair NA_world_states[] =
8686 { 2503, 0 }, // WORLD_STATE_NA_GUARDS_HORDE
8687 { 2502, 0 }, // WORLD_STATE_NA_GUARDS_ALLIANCE
8688 { 2493, 0 }, // WORLD_STATE_NA_GUARDS_MAX
8689 { 2491, 0 }, // WORLD_STATE_NA_GUARDS_LEFT
8690 { 2762, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_NORTH_NEUTRAL_H
8691 { 2662, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_NORTH_NEUTRAL_A
8692 { 2663, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_NORTH_H
8693 { 2664, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_NORTH_A
8694 { 2760, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_SOUTH_NEUTRAL_H
8695 { 2670, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_SOUTH_NEUTRAL_A
8696 { 2668, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_SOUTH_H
8697 { 2669, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_SOUTH_A
8698 { 2761, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_WEST_NEUTRAL_H
8699 { 2667, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_WEST_NEUTRAL_A
8700 { 2665, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_WEST_H
8701 { 2666, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_WEST_A
8702 { 2763, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_EAST_NEUTRAL_H
8703 { 2659, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_EAST_NEUTRAL_A
8704 { 2660, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_EAST_H
8705 { 2661, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_WYVERN_EAST_A
8706 { 2671, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_HALAA_NEUTRAL
8707 { 2676, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_HALAA_NEUTRAL_A
8708 { 2677, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_HALAA_NEUTRAL_H
8709 { 2672, WORLD_STATE_REMOVE }, // WORLD_STATE_NA_HALAA_HORDE
8710 { 2673, WORLD_STATE_REMOVE } // WORLD_STATE_NA_HALAA_ALLIANCE
8713 void Player::SendInitWorldStates(uint32 zoneid, uint32 areaid)
8715 // data depends on zoneid/mapid...
8716 BattleGround* bg = GetBattleGround();
8717 uint32 mapid = GetMapId();
8719 DEBUG_LOG("Sending SMSG_INIT_WORLD_STATES to Map:%u, Zone: %u", mapid, zoneid);
8721 uint32 count = 0; // count of world states in packet
8723 WorldPacket data(SMSG_INIT_WORLD_STATES, (4 + 4 + 4 + 2 + 8 * 8)); // guess
8724 data << uint32(mapid); // mapid
8725 data << uint32(zoneid); // zone id
8726 data << uint32(areaid); // area id, new 2.1.0
8727 size_t count_pos = data.wpos();
8728 data << uint16(0); // count of uint64 blocks, placeholder
8730 // common fields
8731 FillInitialWorldState(data, count, 0x8d8, 0x0); // 2264 1
8732 FillInitialWorldState(data, count, 0x8d7, 0x0); // 2263 2
8733 FillInitialWorldState(data, count, 0x8d6, 0x0); // 2262 3
8734 FillInitialWorldState(data, count, 0x8d5, 0x0); // 2261 4
8735 FillInitialWorldState(data, count, 0x8d4, 0x0); // 2260 5
8736 FillInitialWorldState(data, count, 0x8d3, 0x0); // 2259 6
8737 // 3191 7 Current arena season
8738 FillInitialWorldState(data, count, 0xC77, sWorld.getConfig(CONFIG_UINT32_ARENA_SEASON_ID));
8739 // 3901 8 Previous arena season
8740 FillInitialWorldState(data, count, 0xF3D, sWorld.getConfig(CONFIG_UINT32_ARENA_SEASON_PREVIOUS_ID));
8741 FillInitialWorldState(data, count, 0xED9, 1); // 3801 9 0 - Battle for Wintergrasp in progress, 1 - otherwise
8742 // 4354 10 Time when next Battle for Wintergrasp starts
8743 FillInitialWorldState(data, count, 0x1102, uint32(time(NULL) + 9000));
8745 if (mapid == 530) // Outland
8747 FillInitialWorldState(data, count, 0x9bf, 0x0); // 2495
8748 FillInitialWorldState(data, count, 0x9bd, 0xF); // 2493
8749 FillInitialWorldState(data, count, 0x9bb, 0xF); // 2491
8752 switch (zoneid)
8754 case 1: // Dun Morogh
8755 case 11: // Wetlands
8756 case 12: // Elwynn Forest
8757 case 38: // Loch Modan
8758 case 40: // Westfall
8759 case 51: // Searing Gorge
8760 case 1519: // Stormwind City
8761 case 1537: // Ironforge
8762 case 2257: // Deeprun Tram
8763 case 3703: // Shattrath City
8764 break;
8765 case 139: // Eastern Plaguelands
8766 if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(zoneid))
8767 outdoorPvP->FillInitialWorldStates(data, count);
8768 else
8769 FillInitialWorldState(data, count, EP_world_states);
8770 break;
8771 case 1377: // Silithus
8772 if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(zoneid))
8773 outdoorPvP->FillInitialWorldStates(data, count);
8774 else
8775 FillInitialWorldState(data, count, SI_world_states);
8776 break;
8777 case 2597: // AV
8778 if (bg && bg->GetTypeID() == BATTLEGROUND_AV)
8779 bg->FillInitialWorldStates(data, count);
8780 else
8781 FillInitialWorldState(data, count, AV_world_states);
8782 break;
8783 case 3277: // WS
8784 if (bg && bg->GetTypeID() == BATTLEGROUND_WS)
8785 bg->FillInitialWorldStates(data, count);
8786 else
8787 FillInitialWorldState(data, count, WS_world_states);
8788 break;
8789 case 3358: // AB
8790 if (bg && bg->GetTypeID() == BATTLEGROUND_AB)
8791 bg->FillInitialWorldStates(data, count);
8792 else
8793 FillInitialWorldState(data, count, AB_world_states);
8794 break;
8795 case 3820: // EY
8796 if (bg && bg->GetTypeID() == BATTLEGROUND_EY)
8797 bg->FillInitialWorldStates(data, count);
8798 else
8799 FillInitialWorldState(data, count, EY_world_states);
8800 break;
8801 case 3483: // Hellfire Peninsula
8802 if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(zoneid))
8803 outdoorPvP->FillInitialWorldStates(data, count);
8804 else
8805 FillInitialWorldState(data, count, HP_world_states);
8806 break;
8807 case 3518: // Nagrand
8808 if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(zoneid))
8809 outdoorPvP->FillInitialWorldStates(data, count);
8810 else
8811 FillInitialWorldState(data, count, NA_world_states);
8812 break;
8813 case 3519: // Terokkar Forest
8814 if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(zoneid))
8815 outdoorPvP->FillInitialWorldStates(data, count);
8816 else
8817 FillInitialWorldState(data, count, TF_world_states);
8818 break;
8819 case 3521: // Zangarmarsh
8820 if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(zoneid))
8821 outdoorPvP->FillInitialWorldStates(data, count);
8822 else
8823 FillInitialWorldState(data, count, ZM_world_states);
8824 break;
8825 case 3698: // Nagrand Arena
8826 if (bg && bg->GetTypeID() == BATTLEGROUND_NA)
8827 bg->FillInitialWorldStates(data, count);
8828 else
8830 FillInitialWorldState(data, count, 0xa0f, 0x0); // 2575 7
8831 FillInitialWorldState(data, count, 0xa10, 0x0); // 2576 8
8832 FillInitialWorldState(data, count, 0xa11, 0x0); // 2577 9 show
8834 break;
8835 case 3702: // Blade's Edge Arena
8836 if (bg && bg->GetTypeID() == BATTLEGROUND_BE)
8837 bg->FillInitialWorldStates(data, count);
8838 else
8840 FillInitialWorldState(data, count, 0x9f0, 0x0); // 2544 7 gold
8841 FillInitialWorldState(data, count, 0x9f1, 0x0); // 2545 8 green
8842 FillInitialWorldState(data, count, 0x9f3, 0x0); // 2547 9 show
8844 break;
8845 case 3968: // Ruins of Lordaeron
8846 if (bg && bg->GetTypeID() == BATTLEGROUND_RL)
8847 bg->FillInitialWorldStates(data, count);
8848 else
8850 FillInitialWorldState(data, count, 0xbb8, 0x0); // 3000 7 gold
8851 FillInitialWorldState(data, count, 0xbb9, 0x0); // 3001 8 green
8852 FillInitialWorldState(data, count, 0xbba, 0x0); // 3002 9 show
8854 break;
8855 default:
8856 FillInitialWorldState(data, count, 0x914, 0x0); // 2324 7
8857 FillInitialWorldState(data, count, 0x913, 0x0); // 2323 8
8858 FillInitialWorldState(data, count, 0x912, 0x0); // 2322 9
8859 FillInitialWorldState(data, count, 0x915, 0x0); // 2325 10
8860 break;
8863 FillBGWeekendWorldStates(data, count);
8865 data.put<uint16>(count_pos, count); // set actual world state amount
8867 GetSession()->SendPacket(&data);
8870 void Player::FillBGWeekendWorldStates(WorldPacket& data, uint32& count)
8872 for (uint32 i = 1; i < sBattlemasterListStore.GetNumRows(); ++i)
8874 BattlemasterListEntry const* bl = sBattlemasterListStore.LookupEntry(i);
8875 if (bl && bl->HolidayWorldStateId)
8877 if (BattleGroundMgr::IsBGWeekend(BattleGroundTypeId(bl->id)))
8878 FillInitialWorldState(data, count, bl->HolidayWorldStateId, 1);
8879 else
8880 FillInitialWorldState(data, count, bl->HolidayWorldStateId, 0);
8885 uint32 Player::GetXPRestBonus(uint32 xp)
8887 uint32 rested_bonus = (uint32)GetRestBonus(); // xp for each rested bonus
8889 if (rested_bonus > xp) // max rested_bonus == xp or (r+x) = 200% xp
8890 rested_bonus = xp;
8892 SetRestBonus(GetRestBonus() - rested_bonus);
8894 DETAIL_LOG("Player gain %u xp (+ %u Rested Bonus). Rested points=%f", xp + rested_bonus, rested_bonus, GetRestBonus());
8895 return rested_bonus;
8898 void Player::SetBindPoint(ObjectGuid guid)
8900 WorldPacket data(SMSG_BINDER_CONFIRM, 8);
8901 data << ObjectGuid(guid);
8902 GetSession()->SendPacket(&data);
8905 void Player::SendTalentWipeConfirm(ObjectGuid guid)
8907 WorldPacket data(MSG_TALENT_WIPE_CONFIRM, (8 + 4));
8908 data << ObjectGuid(guid);
8909 data << uint32(resetTalentsCost());
8910 GetSession()->SendPacket(&data);
8913 void Player::SendPetSkillWipeConfirm()
8915 Pet* pet = GetPet();
8916 if (!pet)
8917 return;
8918 WorldPacket data(SMSG_PET_UNLEARN_CONFIRM, (8 + 4));
8919 data << ObjectGuid(pet->GetObjectGuid());
8920 data << uint32(pet->resetTalentsCost());
8921 GetSession()->SendPacket(&data);
8924 /*********************************************************/
8925 /*** STORAGE SYSTEM ***/
8926 /*********************************************************/
8928 void Player::SetVirtualItemSlot(uint8 i, Item* item)
8930 MANGOS_ASSERT(i < 3);
8931 if (i < 2 && item)
8933 if (!item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
8934 return;
8935 uint32 charges = item->GetEnchantmentCharges(TEMP_ENCHANTMENT_SLOT);
8936 if (charges == 0)
8937 return;
8938 if (charges > 1)
8939 item->SetEnchantmentCharges(TEMP_ENCHANTMENT_SLOT, charges - 1);
8940 else if (charges <= 1)
8942 ApplyEnchantment(item, TEMP_ENCHANTMENT_SLOT, false);
8943 item->ClearEnchantment(TEMP_ENCHANTMENT_SLOT);
8948 void Player::SetSheath(SheathState sheathed)
8950 switch (sheathed)
8952 case SHEATH_STATE_UNARMED: // no prepared weapon
8953 SetVirtualItemSlot(0, NULL);
8954 SetVirtualItemSlot(1, NULL);
8955 SetVirtualItemSlot(2, NULL);
8956 break;
8957 case SHEATH_STATE_MELEE: // prepared melee weapon
8959 SetVirtualItemSlot(0, GetWeaponForAttack(BASE_ATTACK, true, true));
8960 SetVirtualItemSlot(1, GetWeaponForAttack(OFF_ATTACK, true, true));
8961 SetVirtualItemSlot(2, NULL);
8962 }; break;
8963 case SHEATH_STATE_RANGED: // prepared ranged weapon
8964 SetVirtualItemSlot(0, NULL);
8965 SetVirtualItemSlot(1, NULL);
8966 SetVirtualItemSlot(2, GetWeaponForAttack(RANGED_ATTACK, true, true));
8967 break;
8968 default:
8969 SetVirtualItemSlot(0, NULL);
8970 SetVirtualItemSlot(1, NULL);
8971 SetVirtualItemSlot(2, NULL);
8972 break;
8974 Unit::SetSheath(sheathed); // this must visualize Sheath changing for other players...
8977 bool Player::GetSlotsForInventoryType(uint8 invType, uint8* slots, uint32 subClass) const
8979 uint8 pClass = getClass();
8981 slots[0] = NULL_SLOT;
8982 slots[1] = NULL_SLOT;
8983 slots[2] = NULL_SLOT;
8984 slots[3] = NULL_SLOT;
8985 switch (invType)
8987 case INVTYPE_HEAD:
8988 slots[0] = EQUIPMENT_SLOT_HEAD;
8989 break;
8990 case INVTYPE_NECK:
8991 slots[0] = EQUIPMENT_SLOT_NECK;
8992 break;
8993 case INVTYPE_SHOULDERS:
8994 slots[0] = EQUIPMENT_SLOT_SHOULDERS;
8995 break;
8996 case INVTYPE_BODY:
8997 slots[0] = EQUIPMENT_SLOT_BODY;
8998 break;
8999 case INVTYPE_CHEST:
9000 slots[0] = EQUIPMENT_SLOT_CHEST;
9001 break;
9002 case INVTYPE_ROBE:
9003 slots[0] = EQUIPMENT_SLOT_CHEST;
9004 break;
9005 case INVTYPE_WAIST:
9006 slots[0] = EQUIPMENT_SLOT_WAIST;
9007 break;
9008 case INVTYPE_LEGS:
9009 slots[0] = EQUIPMENT_SLOT_LEGS;
9010 break;
9011 case INVTYPE_FEET:
9012 slots[0] = EQUIPMENT_SLOT_FEET;
9013 break;
9014 case INVTYPE_WRISTS:
9015 slots[0] = EQUIPMENT_SLOT_WRISTS;
9016 break;
9017 case INVTYPE_HANDS:
9018 slots[0] = EQUIPMENT_SLOT_HANDS;
9019 break;
9020 case INVTYPE_FINGER:
9021 slots[0] = EQUIPMENT_SLOT_FINGER1;
9022 slots[1] = EQUIPMENT_SLOT_FINGER2;
9023 break;
9024 case INVTYPE_TRINKET:
9025 slots[0] = EQUIPMENT_SLOT_TRINKET1;
9026 slots[1] = EQUIPMENT_SLOT_TRINKET2;
9027 break;
9028 case INVTYPE_CLOAK:
9029 slots[0] = EQUIPMENT_SLOT_BACK;
9030 break;
9031 case INVTYPE_WEAPON:
9033 slots[0] = EQUIPMENT_SLOT_MAINHAND;
9035 // suggest offhand slot only if know dual wielding
9036 // (this will be replace mainhand weapon at auto equip instead unwanted "you don't known dual wielding" ...
9037 if (CanDualWield())
9038 slots[1] = EQUIPMENT_SLOT_OFFHAND;
9039 break;
9041 case INVTYPE_SHIELD:
9042 slots[0] = EQUIPMENT_SLOT_OFFHAND;
9043 break;
9044 case INVTYPE_RANGED:
9045 slots[0] = EQUIPMENT_SLOT_RANGED;
9046 break;
9047 case INVTYPE_2HWEAPON:
9048 slots[0] = EQUIPMENT_SLOT_MAINHAND;
9049 if (CanDualWield() && CanTitanGrip())
9050 slots[1] = EQUIPMENT_SLOT_OFFHAND;
9051 break;
9052 case INVTYPE_TABARD:
9053 slots[0] = EQUIPMENT_SLOT_TABARD;
9054 break;
9055 case INVTYPE_WEAPONMAINHAND:
9056 slots[0] = EQUIPMENT_SLOT_MAINHAND;
9057 break;
9058 case INVTYPE_WEAPONOFFHAND:
9059 slots[0] = EQUIPMENT_SLOT_OFFHAND;
9060 break;
9061 case INVTYPE_HOLDABLE:
9062 slots[0] = EQUIPMENT_SLOT_OFFHAND;
9063 break;
9064 case INVTYPE_THROWN:
9065 slots[0] = EQUIPMENT_SLOT_RANGED;
9066 break;
9067 case INVTYPE_RANGEDRIGHT:
9068 slots[0] = EQUIPMENT_SLOT_RANGED;
9069 break;
9070 case INVTYPE_BAG:
9071 slots[0] = INVENTORY_SLOT_BAG_START + 0;
9072 slots[1] = INVENTORY_SLOT_BAG_START + 1;
9073 slots[2] = INVENTORY_SLOT_BAG_START + 2;
9074 slots[3] = INVENTORY_SLOT_BAG_START + 3;
9075 break;
9076 case INVTYPE_RELIC:
9078 switch (subClass)
9080 case ITEM_SUBCLASS_ARMOR_LIBRAM:
9081 if (pClass == CLASS_PALADIN)
9082 slots[0] = EQUIPMENT_SLOT_RANGED;
9083 break;
9084 case ITEM_SUBCLASS_ARMOR_IDOL:
9085 if (pClass == CLASS_DRUID)
9086 slots[0] = EQUIPMENT_SLOT_RANGED;
9087 break;
9088 case ITEM_SUBCLASS_ARMOR_TOTEM:
9089 if (pClass == CLASS_SHAMAN)
9090 slots[0] = EQUIPMENT_SLOT_RANGED;
9091 break;
9092 case ITEM_SUBCLASS_ARMOR_MISC:
9093 if (pClass == CLASS_WARLOCK)
9094 slots[0] = EQUIPMENT_SLOT_RANGED;
9095 break;
9096 case ITEM_SUBCLASS_ARMOR_SIGIL:
9097 if (pClass == CLASS_DEATH_KNIGHT)
9098 slots[0] = EQUIPMENT_SLOT_RANGED;
9099 break;
9101 break;
9103 default:
9104 return false;
9107 return true;
9110 uint8 Player::FindEquipSlot(ItemPrototype const* proto, uint32 slot, bool swap) const
9112 uint8 slots[4];
9113 if (!GetSlotsForInventoryType(proto->InventoryType, slots, proto->SubClass))
9114 return NULL_SLOT;
9116 if (slot != NULL_SLOT)
9118 if (swap || !GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
9120 for (int i = 0; i < 4; ++i)
9122 if (slots[i] == slot)
9123 return slot;
9127 else
9129 // search free slot at first
9130 for (int i = 0; i < 4; ++i)
9132 if (slots[i] != NULL_SLOT && !GetItemByPos(INVENTORY_SLOT_BAG_0, slots[i]))
9134 // in case 2hand equipped weapon (without titan grip) offhand slot empty but not free
9135 if (slots[i] != EQUIPMENT_SLOT_OFFHAND || !IsTwoHandUsed())
9136 return slots[i];
9140 // if not found free and can swap return first appropriate from used
9141 for (int i = 0; i < 4; ++i)
9143 if (slots[i] != NULL_SLOT && swap)
9144 return slots[i];
9148 // no free position
9149 return NULL_SLOT;
9152 InventoryResult Player::CanUnequipItems(uint32 item, uint32 count) const
9154 Item* pItem;
9155 uint32 tempcount = 0;
9157 InventoryResult res = EQUIP_ERR_OK;
9159 for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_BAG_END; ++i)
9161 pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9162 if (pItem && pItem->GetEntry() == item)
9164 InventoryResult ires = CanUnequipItem(INVENTORY_SLOT_BAG_0 << 8 | i, false);
9165 if (ires == EQUIP_ERR_OK)
9167 tempcount += pItem->GetCount();
9168 if (tempcount >= count)
9169 return EQUIP_ERR_OK;
9171 else
9172 res = ires;
9175 for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
9177 pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9178 if (pItem && pItem->GetEntry() == item)
9180 tempcount += pItem->GetCount();
9181 if (tempcount >= count)
9182 return EQUIP_ERR_OK;
9185 Bag* pBag;
9186 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
9188 pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9189 if (pBag)
9191 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
9193 pItem = GetItemByPos(i, j);
9194 if (pItem && pItem->GetEntry() == item)
9196 tempcount += pItem->GetCount();
9197 if (tempcount >= count)
9198 return EQUIP_ERR_OK;
9204 // not found req. item count and have unequippable items
9205 return res;
9208 uint32 Player::GetItemCount(uint32 item, bool inBankAlso, Item* skipItem) const
9210 uint32 count = 0;
9211 for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i)
9213 Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9214 if (pItem && pItem != skipItem && pItem->GetEntry() == item)
9215 count += pItem->GetCount();
9217 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
9219 Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9220 if (pBag)
9221 count += pBag->GetItemCount(item, skipItem);
9224 if (skipItem && skipItem->GetProto()->GemProperties)
9226 for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i)
9228 Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9229 if (pItem && pItem != skipItem && pItem->GetProto()->Socket[0].Color)
9230 count += pItem->GetGemCountWithID(item);
9234 if (inBankAlso)
9236 for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i)
9238 Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9239 if (pItem && pItem != skipItem && pItem->GetEntry() == item)
9240 count += pItem->GetCount();
9242 for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
9244 Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9245 if (pBag)
9246 count += pBag->GetItemCount(item, skipItem);
9249 if (skipItem && skipItem->GetProto()->GemProperties)
9251 for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i)
9253 Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9254 if (pItem && pItem != skipItem && pItem->GetProto()->Socket[0].Color)
9255 count += pItem->GetGemCountWithID(item);
9260 return count;
9263 uint32 Player::GetItemCountWithLimitCategory(uint32 limitCategory, Item* skipItem) const
9265 uint32 count = 0;
9266 for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i)
9267 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9268 if (pItem->GetProto()->ItemLimitCategory == limitCategory && pItem != skipItem)
9269 count += pItem->GetCount();
9271 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
9272 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9273 count += pBag->GetItemCountWithLimitCategory(limitCategory, skipItem);
9275 for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i)
9276 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9277 if (pItem->GetProto()->ItemLimitCategory == limitCategory && pItem != skipItem)
9278 count += pItem->GetCount();
9280 for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
9281 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9282 count += pBag->GetItemCountWithLimitCategory(limitCategory, skipItem);
9284 return count;
9287 Item* Player::GetItemByEntry(uint32 item) const
9289 for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i)
9290 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9291 if (pItem->GetEntry() == item)
9292 return pItem;
9294 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
9295 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9296 if (Item* itemPtr = pBag->GetItemByEntry(item))
9297 return itemPtr;
9299 return NULL;
9302 Item* Player::GetItemByLimitedCategory(uint32 limitedCategory) const
9304 for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i)
9305 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9306 if (pItem->GetProto()->ItemLimitCategory == limitedCategory)
9307 return pItem;
9309 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
9310 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9311 if (Item* itemPtr = pBag->GetItemByLimitedCategory(limitedCategory))
9312 return itemPtr;
9314 return NULL;
9317 Item* Player::GetItemByGuid(ObjectGuid guid) const
9319 for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i)
9320 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9321 if (pItem->GetObjectGuid() == guid)
9322 return pItem;
9324 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
9325 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9326 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
9327 if (Item* pItem = pBag->GetItemByPos(j))
9328 if (pItem->GetObjectGuid() == guid)
9329 return pItem;
9331 for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i)
9332 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9333 if (pItem->GetObjectGuid() == guid)
9334 return pItem;
9336 for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
9337 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9338 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
9339 if (Item* pItem = pBag->GetItemByPos(j))
9340 if (pItem->GetObjectGuid() == guid)
9341 return pItem;
9343 return NULL;
9346 Item* Player::GetItemByPos(uint16 pos) const
9348 uint8 bag = pos >> 8;
9349 uint8 slot = pos & 255;
9350 return GetItemByPos(bag, slot);
9353 Item* Player::GetItemByPos(uint8 bag, uint8 slot) const
9355 if (bag == INVENTORY_SLOT_BAG_0 && slot < BANK_SLOT_BAG_END)
9356 return m_items[slot];
9357 else if ((bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END)
9358 || (bag >= BANK_SLOT_BAG_START && bag < BANK_SLOT_BAG_END))
9360 Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag);
9361 if (pBag)
9362 return pBag->GetItemByPos(slot);
9364 return NULL;
9367 uint32 Player::GetItemDisplayIdInSlot(uint8 bag, uint8 slot) const
9369 const Item* pItem = GetItemByPos(bag, slot);
9371 if (!pItem)
9372 return 0;
9374 return pItem->GetProto()->DisplayInfoID;
9377 Item* Player::GetWeaponForAttack(WeaponAttackType attackType, bool nonbroken, bool useable) const
9379 uint8 slot;
9380 switch (attackType)
9382 case BASE_ATTACK: slot = EQUIPMENT_SLOT_MAINHAND; break;
9383 case OFF_ATTACK: slot = EQUIPMENT_SLOT_OFFHAND; break;
9384 case RANGED_ATTACK: slot = EQUIPMENT_SLOT_RANGED; break;
9385 default: return NULL;
9388 Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
9389 if (!item || item->GetProto()->Class != ITEM_CLASS_WEAPON)
9390 return NULL;
9392 if (useable && !CanUseEquippedWeapon(attackType))
9393 return NULL;
9395 if (nonbroken && item->IsBroken())
9396 return NULL;
9398 return item;
9401 Item* Player::GetShield(bool useable) const
9403 Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
9404 if (!item || item->GetProto()->Class != ITEM_CLASS_ARMOR)
9405 return NULL;
9407 if (!useable)
9408 return item;
9410 if (item->IsBroken() || !CanUseEquippedWeapon(OFF_ATTACK))
9411 return NULL;
9413 return item;
9416 uint32 Player::GetAttackBySlot(uint8 slot)
9418 switch (slot)
9420 case EQUIPMENT_SLOT_MAINHAND: return BASE_ATTACK;
9421 case EQUIPMENT_SLOT_OFFHAND: return OFF_ATTACK;
9422 case EQUIPMENT_SLOT_RANGED: return RANGED_ATTACK;
9423 default: return MAX_ATTACK;
9427 bool Player::IsInventoryPos(uint8 bag, uint8 slot)
9429 if (bag == INVENTORY_SLOT_BAG_0 && slot == NULL_SLOT)
9430 return true;
9431 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= INVENTORY_SLOT_ITEM_START && slot < INVENTORY_SLOT_ITEM_END))
9432 return true;
9433 if (bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END)
9434 return true;
9435 return false;
9438 bool Player::IsEquipmentPos(uint8 bag, uint8 slot)
9440 if (bag == INVENTORY_SLOT_BAG_0 && (slot < EQUIPMENT_SLOT_END))
9441 return true;
9442 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= INVENTORY_SLOT_BAG_START && slot < INVENTORY_SLOT_BAG_END))
9443 return true;
9444 return false;
9447 bool Player::IsBankPos(uint8 bag, uint8 slot)
9449 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= BANK_SLOT_ITEM_START && slot < BANK_SLOT_ITEM_END))
9450 return true;
9451 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END))
9452 return true;
9453 if (bag >= BANK_SLOT_BAG_START && bag < BANK_SLOT_BAG_END)
9454 return true;
9455 return false;
9458 bool Player::IsBagPos(uint16 pos)
9460 uint8 bag = pos >> 8;
9461 uint8 slot = pos & 255;
9462 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= INVENTORY_SLOT_BAG_START && slot < INVENTORY_SLOT_BAG_END))
9463 return true;
9464 if (bag == INVENTORY_SLOT_BAG_0 && (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END))
9465 return true;
9466 return false;
9469 bool Player::IsValidPos(uint8 bag, uint8 slot, bool explicit_pos) const
9471 // post selected
9472 if (bag == NULL_BAG && !explicit_pos)
9473 return true;
9475 if (bag == INVENTORY_SLOT_BAG_0)
9477 // any post selected
9478 if (slot == NULL_SLOT && !explicit_pos)
9479 return true;
9481 // equipment
9482 if (slot < EQUIPMENT_SLOT_END)
9483 return true;
9485 // bag equip slots
9486 if (slot >= INVENTORY_SLOT_BAG_START && slot < INVENTORY_SLOT_BAG_END)
9487 return true;
9489 // backpack slots
9490 if (slot >= INVENTORY_SLOT_ITEM_START && slot < INVENTORY_SLOT_ITEM_END)
9491 return true;
9493 // bank main slots
9494 if (slot >= BANK_SLOT_ITEM_START && slot < BANK_SLOT_ITEM_END)
9495 return true;
9497 // bank bag slots
9498 if (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END)
9499 return true;
9501 return false;
9504 // bag content slots
9505 if (bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END)
9507 Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag);
9508 if (!pBag)
9509 return false;
9511 // any post selected
9512 if (slot == NULL_SLOT && !explicit_pos)
9513 return true;
9515 return slot < pBag->GetBagSize();
9518 // bank bag content slots
9519 if (bag >= BANK_SLOT_BAG_START && bag < BANK_SLOT_BAG_END)
9521 Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag);
9522 if (!pBag)
9523 return false;
9525 // any post selected
9526 if (slot == NULL_SLOT && !explicit_pos)
9527 return true;
9529 return slot < pBag->GetBagSize();
9532 // where this?
9533 return false;
9537 bool Player::HasItemCount(uint32 item, uint32 count, bool inBankAlso) const
9539 uint32 tempcount = 0;
9540 for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i)
9542 Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9543 if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade())
9545 tempcount += pItem->GetCount();
9546 if (tempcount >= count)
9547 return true;
9550 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
9552 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9554 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
9556 Item* pItem = GetItemByPos(i, j);
9557 if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade())
9559 tempcount += pItem->GetCount();
9560 if (tempcount >= count)
9561 return true;
9567 if (inBankAlso)
9569 for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i)
9571 Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9572 if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade())
9574 tempcount += pItem->GetCount();
9575 if (tempcount >= count)
9576 return true;
9579 for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
9581 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
9583 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
9585 Item* pItem = GetItemByPos(i, j);
9586 if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade())
9588 tempcount += pItem->GetCount();
9589 if (tempcount >= count)
9590 return true;
9597 return false;
9600 bool Player::HasItemOrGemWithIdEquipped(uint32 item, uint32 count, uint8 except_slot) const
9602 uint32 tempcount = 0;
9603 for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
9605 if (i == int(except_slot))
9606 continue;
9608 Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9609 if (pItem && pItem->GetEntry() == item)
9611 tempcount += pItem->GetCount();
9612 if (tempcount >= count)
9613 return true;
9617 ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(item);
9618 if (pProto && pProto->GemProperties)
9620 for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
9622 if (i == int(except_slot))
9623 continue;
9625 Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9626 if (pItem && pItem->GetProto()->Socket[0].Color)
9628 tempcount += pItem->GetGemCountWithID(item);
9629 if (tempcount >= count)
9630 return true;
9635 return false;
9638 bool Player::HasItemOrGemWithLimitCategoryEquipped(uint32 limitCategory, uint32 count, uint8 except_slot) const
9640 uint32 tempcount = 0;
9641 for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
9643 if (i == int(except_slot))
9644 continue;
9646 Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
9647 if (!pItem)
9648 continue;
9650 ItemPrototype const* pProto = pItem->GetProto();
9651 if (!pProto)
9652 continue;
9654 if (pProto->ItemLimitCategory == limitCategory)
9656 tempcount += pItem->GetCount();
9657 if (tempcount >= count)
9658 return true;
9661 if (pProto->Socket[0].Color)
9663 tempcount += pItem->GetGemCountWithLimitCategory(limitCategory);
9664 if (tempcount >= count)
9665 return true;
9669 return false;
9672 InventoryResult Player::_CanTakeMoreSimilarItems(uint32 entry, uint32 count, Item* pItem, uint32* no_space_count) const
9674 ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(entry);
9675 if (!pProto)
9677 if (no_space_count)
9678 *no_space_count = count;
9679 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
9682 // no maximum
9683 if (pProto->MaxCount > 0)
9685 uint32 curcount = GetItemCount(pProto->ItemId, true, pItem);
9687 if (curcount + count > uint32(pProto->MaxCount))
9689 if (no_space_count)
9690 *no_space_count = count + curcount - pProto->MaxCount;
9691 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
9695 // check unique-equipped limit
9696 if (pProto->ItemLimitCategory)
9698 ItemLimitCategoryEntry const* limitEntry = sItemLimitCategoryStore.LookupEntry(pProto->ItemLimitCategory);
9699 if (!limitEntry)
9701 if (no_space_count)
9702 *no_space_count = count;
9703 return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;
9706 if (limitEntry->mode == ITEM_LIMIT_CATEGORY_MODE_HAVE)
9708 uint32 curcount = GetItemCountWithLimitCategory(pProto->ItemLimitCategory, pItem);
9710 if (curcount + count > uint32(limitEntry->maxCount))
9712 if (no_space_count)
9713 *no_space_count = count + curcount - limitEntry->maxCount;
9714 return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED_IS;
9719 return EQUIP_ERR_OK;
9722 InventoryResult Player::_CanStoreItem_InSpecificSlot(uint8 bag, uint8 slot, ItemPosCountVec& dest, ItemPrototype const* pProto, uint32& count, bool swap, Item* pSrcItem) const
9724 Item* pItem2 = GetItemByPos(bag, slot);
9726 // ignore move item (this slot will be empty at move)
9727 if (pItem2 == pSrcItem)
9728 pItem2 = NULL;
9730 uint32 need_space;
9732 // empty specific slot - check item fit to slot
9733 if (!pItem2 || swap)
9735 if (bag == INVENTORY_SLOT_BAG_0)
9737 // prevent cheating
9738 if ((slot >= BUYBACK_SLOT_START && slot < BUYBACK_SLOT_END) || slot >= PLAYER_SLOT_END)
9739 return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG;
9741 else
9743 Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag);
9744 if (!pBag)
9745 return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG;
9747 ItemPrototype const* pBagProto = pBag->GetProto();
9748 if (!pBagProto)
9749 return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG;
9751 if (slot >= pBagProto->ContainerSlots)
9752 return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG;
9754 if (!ItemCanGoIntoBag(pProto, pBagProto))
9755 return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG;
9758 // non empty stack with space
9759 need_space = pProto->GetMaxStackSize();
9761 // non empty slot, check item type
9762 else
9764 // can be merged at least partly
9765 InventoryResult res = pItem2->CanBeMergedPartlyWith(pProto);
9766 if (res != EQUIP_ERR_OK)
9767 return res;
9769 // free stack space or infinity
9770 need_space = pProto->GetMaxStackSize() - pItem2->GetCount();
9773 if (need_space > count)
9774 need_space = count;
9776 ItemPosCount newPosition = ItemPosCount((bag << 8) | slot, need_space);
9777 if (!newPosition.isContainedIn(dest))
9779 dest.push_back(newPosition);
9780 count -= need_space;
9782 return EQUIP_ERR_OK;
9785 InventoryResult Player::_CanStoreItem_InBag(uint8 bag, ItemPosCountVec& dest, ItemPrototype const* pProto, uint32& count, bool merge, bool non_specialized, Item* pSrcItem, uint8 skip_bag, uint8 skip_slot) const
9787 // skip specific bag already processed in first called _CanStoreItem_InBag
9788 if (bag == skip_bag)
9789 return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG;
9791 // skip nonexistent bag or self targeted bag
9792 Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag);
9793 if (!pBag || pBag == pSrcItem)
9794 return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG;
9796 ItemPrototype const* pBagProto = pBag->GetProto();
9797 if (!pBagProto)
9798 return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG;
9800 // specialized bag mode or non-specilized
9801 if (non_specialized != (pBagProto->Class == ITEM_CLASS_CONTAINER && pBagProto->SubClass == ITEM_SUBCLASS_CONTAINER))
9802 return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG;
9804 if (!ItemCanGoIntoBag(pProto, pBagProto))
9805 return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG;
9807 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
9809 // skip specific slot already processed in first called _CanStoreItem_InSpecificSlot
9810 if (j == skip_slot)
9811 continue;
9813 Item* pItem2 = GetItemByPos(bag, j);
9815 // ignore move item (this slot will be empty at move)
9816 if (pItem2 == pSrcItem)
9817 pItem2 = NULL;
9819 // if merge skip empty, if !merge skip non-empty
9820 if ((pItem2 != NULL) != merge)
9821 continue;
9823 uint32 need_space = pProto->GetMaxStackSize();
9825 if (pItem2)
9827 // can be merged at least partly
9828 uint8 res = pItem2->CanBeMergedPartlyWith(pProto);
9829 if (res != EQUIP_ERR_OK)
9830 continue;
9832 // decrease at current stacksize
9833 need_space -= pItem2->GetCount();
9836 if (need_space > count)
9837 need_space = count;
9839 ItemPosCount newPosition = ItemPosCount((bag << 8) | j, need_space);
9840 if (!newPosition.isContainedIn(dest))
9842 dest.push_back(newPosition);
9843 count -= need_space;
9845 if (count == 0)
9846 return EQUIP_ERR_OK;
9849 return EQUIP_ERR_OK;
9852 InventoryResult Player::_CanStoreItem_InInventorySlots(uint8 slot_begin, uint8 slot_end, ItemPosCountVec& dest, ItemPrototype const* pProto, uint32& count, bool merge, Item* pSrcItem, uint8 skip_bag, uint8 skip_slot) const
9854 for (uint32 j = slot_begin; j < slot_end; ++j)
9856 // skip specific slot already processed in first called _CanStoreItem_InSpecificSlot
9857 if (INVENTORY_SLOT_BAG_0 == skip_bag && j == skip_slot)
9858 continue;
9860 Item* pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, j);
9862 // ignore move item (this slot will be empty at move)
9863 if (pItem2 == pSrcItem)
9864 pItem2 = NULL;
9866 // if merge skip empty, if !merge skip non-empty
9867 if ((pItem2 != NULL) != merge)
9868 continue;
9870 uint32 need_space = pProto->GetMaxStackSize();
9872 if (pItem2)
9874 // can be merged at least partly
9875 uint8 res = pItem2->CanBeMergedPartlyWith(pProto);
9876 if (res != EQUIP_ERR_OK)
9877 continue;
9879 // descrease at current stacksize
9880 need_space -= pItem2->GetCount();
9883 if (need_space > count)
9884 need_space = count;
9886 ItemPosCount newPosition = ItemPosCount((INVENTORY_SLOT_BAG_0 << 8) | j, need_space);
9887 if (!newPosition.isContainedIn(dest))
9889 dest.push_back(newPosition);
9890 count -= need_space;
9892 if (count == 0)
9893 return EQUIP_ERR_OK;
9896 return EQUIP_ERR_OK;
9899 InventoryResult Player::_CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, uint32 entry, uint32 count, Item* pItem, bool swap, uint32* no_space_count) const
9901 DEBUG_LOG("STORAGE: CanStoreItem bag = %u, slot = %u, item = %u, count = %u", bag, slot, entry, count);
9903 ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(entry);
9904 if (!pProto)
9906 if (no_space_count)
9907 *no_space_count = count;
9908 return swap ? EQUIP_ERR_ITEMS_CANT_BE_SWAPPED : EQUIP_ERR_ITEM_NOT_FOUND;
9911 if (pItem)
9913 // item used
9914 if (pItem->HasTemporaryLoot())
9916 if (no_space_count)
9917 *no_space_count = count;
9918 return EQUIP_ERR_ALREADY_LOOTED;
9921 if (pItem->IsBindedNotWith(this))
9923 if (no_space_count)
9924 *no_space_count = count;
9925 return EQUIP_ERR_DONT_OWN_THAT_ITEM;
9929 // check count of items (skip for auto move for same player from bank)
9930 uint32 no_similar_count = 0; // can't store this amount similar items
9931 InventoryResult res = _CanTakeMoreSimilarItems(entry, count, pItem, &no_similar_count);
9932 if (res != EQUIP_ERR_OK)
9934 if (count == no_similar_count)
9936 if (no_space_count)
9937 *no_space_count = no_similar_count;
9938 return res;
9940 count -= no_similar_count;
9943 // in specific slot
9944 if (bag != NULL_BAG && slot != NULL_SLOT)
9946 res = _CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, count, swap, pItem);
9947 if (res != EQUIP_ERR_OK)
9949 if (no_space_count)
9950 *no_space_count = count + no_similar_count;
9951 return res;
9954 if (count == 0)
9956 if (no_similar_count == 0)
9957 return EQUIP_ERR_OK;
9959 if (no_space_count)
9960 *no_space_count = count + no_similar_count;
9961 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
9965 // not specific slot or have space for partly store only in specific slot
9967 // in specific bag
9968 if (bag != NULL_BAG)
9970 // search stack in bag for merge to
9971 if (pProto->Stackable != 1)
9973 if (bag == INVENTORY_SLOT_BAG_0) // inventory
9975 res = _CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, INVENTORY_SLOT_ITEM_END, dest, pProto, count, true, pItem, bag, slot);
9976 if (res != EQUIP_ERR_OK)
9978 if (no_space_count)
9979 *no_space_count = count + no_similar_count;
9980 return res;
9983 if (count == 0)
9985 if (no_similar_count == 0)
9986 return EQUIP_ERR_OK;
9988 if (no_space_count)
9989 *no_space_count = count + no_similar_count;
9990 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
9993 else // equipped bag
9995 // we need check 2 time (specialized/non_specialized), use NULL_BAG to prevent skipping bag
9996 res = _CanStoreItem_InBag(bag, dest, pProto, count, true, false, pItem, NULL_BAG, slot);
9997 if (res != EQUIP_ERR_OK)
9998 res = _CanStoreItem_InBag(bag, dest, pProto, count, true, true, pItem, NULL_BAG, slot);
10000 if (res != EQUIP_ERR_OK)
10002 if (no_space_count)
10003 *no_space_count = count + no_similar_count;
10004 return res;
10007 if (count == 0)
10009 if (no_similar_count == 0)
10010 return EQUIP_ERR_OK;
10012 if (no_space_count)
10013 *no_space_count = count + no_similar_count;
10014 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
10019 // search free slot in bag for place to
10020 if (bag == INVENTORY_SLOT_BAG_0) // inventory
10022 res = _CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, INVENTORY_SLOT_ITEM_END, dest, pProto, count, false, pItem, bag, slot);
10023 if (res != EQUIP_ERR_OK)
10025 if (no_space_count)
10026 *no_space_count = count + no_similar_count;
10027 return res;
10030 if (count == 0)
10032 if (no_similar_count == 0)
10033 return EQUIP_ERR_OK;
10035 if (no_space_count)
10036 *no_space_count = count + no_similar_count;
10037 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
10040 else // equipped bag
10042 res = _CanStoreItem_InBag(bag, dest, pProto, count, false, false, pItem, NULL_BAG, slot);
10043 if (res != EQUIP_ERR_OK)
10044 res = _CanStoreItem_InBag(bag, dest, pProto, count, false, true, pItem, NULL_BAG, slot);
10046 if (res != EQUIP_ERR_OK)
10048 if (no_space_count)
10049 *no_space_count = count + no_similar_count;
10050 return res;
10053 if (count == 0)
10055 if (no_similar_count == 0)
10056 return EQUIP_ERR_OK;
10058 if (no_space_count)
10059 *no_space_count = count + no_similar_count;
10060 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
10065 // not specific bag or have space for partly store only in specific bag
10067 // search stack for merge to
10068 if (pProto->Stackable != 1)
10070 res = _CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, INVENTORY_SLOT_ITEM_END, dest, pProto, count, true, pItem, bag, slot);
10071 if (res != EQUIP_ERR_OK)
10073 if (no_space_count)
10074 *no_space_count = count + no_similar_count;
10075 return res;
10078 if (count == 0)
10080 if (no_similar_count == 0)
10081 return EQUIP_ERR_OK;
10083 if (no_space_count)
10084 *no_space_count = count + no_similar_count;
10085 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
10088 if (pProto->BagFamily)
10090 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
10092 res = _CanStoreItem_InBag(i, dest, pProto, count, true, false, pItem, bag, slot);
10093 if (res != EQUIP_ERR_OK)
10094 continue;
10096 if (count == 0)
10098 if (no_similar_count == 0)
10099 return EQUIP_ERR_OK;
10101 if (no_space_count)
10102 *no_space_count = count + no_similar_count;
10103 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
10108 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
10110 res = _CanStoreItem_InBag(i, dest, pProto, count, true, true, pItem, bag, slot);
10111 if (res != EQUIP_ERR_OK)
10112 continue;
10114 if (count == 0)
10116 if (no_similar_count == 0)
10117 return EQUIP_ERR_OK;
10119 if (no_space_count)
10120 *no_space_count = count + no_similar_count;
10121 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
10126 // search free slot - special bag case
10127 if (pProto->BagFamily)
10129 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
10131 res = _CanStoreItem_InBag(i, dest, pProto, count, false, false, pItem, bag, slot);
10132 if (res != EQUIP_ERR_OK)
10133 continue;
10135 if (count == 0)
10137 if (no_similar_count == 0)
10138 return EQUIP_ERR_OK;
10140 if (no_space_count)
10141 *no_space_count = count + no_similar_count;
10142 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
10147 // Normally it would be impossible to autostore not empty bags
10148 if (pItem && pItem->IsBag() && !((Bag*)pItem)->IsEmpty())
10149 return EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG;
10151 // search free slot
10152 res = _CanStoreItem_InInventorySlots(INVENTORY_SLOT_ITEM_START, INVENTORY_SLOT_ITEM_END, dest, pProto, count, false, pItem, bag, slot);
10153 if (res != EQUIP_ERR_OK)
10155 if (no_space_count)
10156 *no_space_count = count + no_similar_count;
10157 return res;
10160 if (count == 0)
10162 if (no_similar_count == 0)
10163 return EQUIP_ERR_OK;
10165 if (no_space_count)
10166 *no_space_count = count + no_similar_count;
10167 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
10170 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
10172 res = _CanStoreItem_InBag(i, dest, pProto, count, false, true, pItem, bag, slot);
10173 if (res != EQUIP_ERR_OK)
10174 continue;
10176 if (count == 0)
10178 if (no_similar_count == 0)
10179 return EQUIP_ERR_OK;
10181 if (no_space_count)
10182 *no_space_count = count + no_similar_count;
10183 return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
10187 if (no_space_count)
10188 *no_space_count = count + no_similar_count;
10190 return EQUIP_ERR_INVENTORY_FULL;
10193 //////////////////////////////////////////////////////////////////////////
10194 InventoryResult Player::CanStoreItems(Item** pItems, int count) const
10196 Item* pItem2;
10198 // fill space table
10199 int inv_slot_items[INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START];
10200 int inv_bags[INVENTORY_SLOT_BAG_END - INVENTORY_SLOT_BAG_START][MAX_BAG_SIZE];
10202 memset(inv_slot_items, 0, sizeof(int) * (INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START));
10203 memset(inv_bags, 0, sizeof(int) * (INVENTORY_SLOT_BAG_END - INVENTORY_SLOT_BAG_START)*MAX_BAG_SIZE);
10205 for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
10207 pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
10209 if (pItem2 && !pItem2->IsInTrade())
10211 inv_slot_items[i - INVENTORY_SLOT_ITEM_START] = pItem2->GetCount();
10215 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
10217 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
10219 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
10221 pItem2 = GetItemByPos(i, j);
10222 if (pItem2 && !pItem2->IsInTrade())
10224 inv_bags[i - INVENTORY_SLOT_BAG_START][j] = pItem2->GetCount();
10230 // check free space for all items
10231 for (int k = 0; k < count; ++k)
10233 Item* pItem = pItems[k];
10235 // no item
10236 if (!pItem) continue;
10238 DEBUG_LOG("STORAGE: CanStoreItems %i. item = %u, count = %u", k + 1, pItem->GetEntry(), pItem->GetCount());
10239 ItemPrototype const* pProto = pItem->GetProto();
10241 // strange item
10242 if (!pProto)
10243 return EQUIP_ERR_ITEM_NOT_FOUND;
10245 // item used
10246 if (pItem->HasTemporaryLoot())
10247 return EQUIP_ERR_ALREADY_LOOTED;
10249 // item it 'bind'
10250 if (pItem->IsBindedNotWith(this))
10251 return EQUIP_ERR_DONT_OWN_THAT_ITEM;
10253 Bag* pBag;
10254 ItemPrototype const* pBagProto;
10256 // item is 'one item only'
10257 InventoryResult res = CanTakeMoreSimilarItems(pItem);
10258 if (res != EQUIP_ERR_OK)
10259 return res;
10261 // search stack for merge to
10262 if (pProto->Stackable != 1)
10264 bool b_found = false;
10266 for (int t = INVENTORY_SLOT_ITEM_START; t < INVENTORY_SLOT_ITEM_END; ++t)
10268 pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, t);
10269 if (pItem2 && pItem2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && inv_slot_items[t - INVENTORY_SLOT_ITEM_START] + pItem->GetCount() <= pProto->GetMaxStackSize())
10271 inv_slot_items[t - INVENTORY_SLOT_ITEM_START] += pItem->GetCount();
10272 b_found = true;
10273 break;
10276 if (b_found) continue;
10278 for (int t = INVENTORY_SLOT_BAG_START; !b_found && t < INVENTORY_SLOT_BAG_END; ++t)
10280 pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, t);
10281 if (pBag)
10283 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
10285 pItem2 = GetItemByPos(t, j);
10286 if (pItem2 && pItem2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && inv_bags[t - INVENTORY_SLOT_BAG_START][j] + pItem->GetCount() <= pProto->GetMaxStackSize())
10288 inv_bags[t - INVENTORY_SLOT_BAG_START][j] += pItem->GetCount();
10289 b_found = true;
10290 break;
10295 if (b_found) continue;
10298 // special bag case
10299 if (pProto->BagFamily)
10301 bool b_found = false;
10303 for (int t = INVENTORY_SLOT_BAG_START; !b_found && t < INVENTORY_SLOT_BAG_END; ++t)
10305 pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, t);
10306 if (pBag)
10308 pBagProto = pBag->GetProto();
10310 // not plain container check
10311 if (pBagProto && (pBagProto->Class != ITEM_CLASS_CONTAINER || pBagProto->SubClass != ITEM_SUBCLASS_CONTAINER) &&
10312 ItemCanGoIntoBag(pProto, pBagProto))
10314 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
10316 if (inv_bags[t - INVENTORY_SLOT_BAG_START][j] == 0)
10318 inv_bags[t - INVENTORY_SLOT_BAG_START][j] = 1;
10319 b_found = true;
10320 break;
10326 if (b_found) continue;
10329 // search free slot
10330 bool b_found = false;
10331 for (int t = INVENTORY_SLOT_ITEM_START; t < INVENTORY_SLOT_ITEM_END; ++t)
10333 if (inv_slot_items[t - INVENTORY_SLOT_ITEM_START] == 0)
10335 inv_slot_items[t - INVENTORY_SLOT_ITEM_START] = 1;
10336 b_found = true;
10337 break;
10340 if (b_found) continue;
10342 // search free slot in bags
10343 for (int t = INVENTORY_SLOT_BAG_START; !b_found && t < INVENTORY_SLOT_BAG_END; ++t)
10345 pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, t);
10346 if (pBag)
10348 pBagProto = pBag->GetProto();
10350 // special bag already checked
10351 if (pBagProto && (pBagProto->Class != ITEM_CLASS_CONTAINER || pBagProto->SubClass != ITEM_SUBCLASS_CONTAINER))
10352 continue;
10354 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
10356 if (inv_bags[t - INVENTORY_SLOT_BAG_START][j] == 0)
10358 inv_bags[t - INVENTORY_SLOT_BAG_START][j] = 1;
10359 b_found = true;
10360 break;
10366 // no free slot found?
10367 if (!b_found)
10368 return EQUIP_ERR_INVENTORY_FULL;
10371 return EQUIP_ERR_OK;
10374 //////////////////////////////////////////////////////////////////////////
10375 InventoryResult Player::CanEquipNewItem(uint8 slot, uint16& dest, uint32 item, bool swap) const
10377 dest = 0;
10378 Item* pItem = Item::CreateItem(item, 1, this);
10379 if (pItem)
10381 InventoryResult result = CanEquipItem(slot, dest, pItem, swap);
10382 delete pItem;
10383 return result;
10386 return EQUIP_ERR_ITEM_NOT_FOUND;
10389 InventoryResult Player::CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool direct_action) const
10391 dest = 0;
10392 if (pItem)
10394 DEBUG_LOG("STORAGE: CanEquipItem slot = %u, item = %u, count = %u", slot, pItem->GetEntry(), pItem->GetCount());
10395 ItemPrototype const* pProto = pItem->GetProto();
10396 if (pProto)
10398 // item used
10399 if (pItem->HasTemporaryLoot())
10400 return EQUIP_ERR_ALREADY_LOOTED;
10402 if (pItem->IsBindedNotWith(this))
10403 return EQUIP_ERR_DONT_OWN_THAT_ITEM;
10405 // check count of items (skip for auto move for same player from bank)
10406 InventoryResult res = CanTakeMoreSimilarItems(pItem);
10407 if (res != EQUIP_ERR_OK)
10408 return res;
10410 // check this only in game
10411 if (direct_action)
10413 // May be here should be more stronger checks; STUNNED checked
10414 // ROOT, CONFUSED, DISTRACTED, FLEEING this needs to be checked.
10415 if (hasUnitState(UNIT_STAT_STUNNED))
10416 return EQUIP_ERR_YOU_ARE_STUNNED;
10418 // do not allow equipping gear except weapons, offhands, projectiles, relics in
10419 // - combat
10420 // - in-progress arenas
10421 if (!pProto->CanChangeEquipStateInCombat())
10423 if (isInCombat())
10424 return EQUIP_ERR_NOT_IN_COMBAT;
10426 if (BattleGround* bg = GetBattleGround())
10427 if (bg->isArena() && bg->GetStatus() == STATUS_IN_PROGRESS)
10428 return EQUIP_ERR_NOT_DURING_ARENA_MATCH;
10431 // prevent equip item in process logout
10432 if (GetSession()->isLogingOut())
10433 return EQUIP_ERR_YOU_ARE_STUNNED;
10435 if (isInCombat() && pProto->Class == ITEM_CLASS_WEAPON && m_weaponChangeTimer != 0)
10436 return EQUIP_ERR_CANT_DO_RIGHT_NOW; // maybe exist better err
10438 if (IsNonMeleeSpellCasted(false))
10439 return EQUIP_ERR_CANT_DO_RIGHT_NOW;
10442 ScalingStatDistributionEntry const* ssd = pProto->ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(pProto->ScalingStatDistribution) : 0;
10443 // 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)
10444 if (ssd && ssd->MaxLevel < DEFAULT_MAX_LEVEL && ssd->MaxLevel < getLevel())
10445 return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;
10447 uint8 eslot = FindEquipSlot(pProto, slot, swap);
10448 if (eslot == NULL_SLOT)
10449 return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;
10451 InventoryResult msg = CanUseItem(pItem , direct_action);
10452 if (msg != EQUIP_ERR_OK)
10453 return msg;
10454 if (!swap && GetItemByPos(INVENTORY_SLOT_BAG_0, eslot))
10455 return EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE;
10457 // if swap ignore item (equipped also)
10458 if (InventoryResult res2 = CanEquipUniqueItem(pItem, swap ? eslot : uint8(NULL_SLOT)))
10459 return res2;
10461 // check unique-equipped special item classes
10462 if (pProto->Class == ITEM_CLASS_QUIVER)
10464 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
10466 if (Item* pBag = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
10468 if (pBag != pItem)
10470 if (ItemPrototype const* pBagProto = pBag->GetProto())
10472 if (pBagProto->Class == pProto->Class && (!swap || pBag->GetSlot() != eslot))
10473 return (pBagProto->SubClass == ITEM_SUBCLASS_AMMO_POUCH)
10474 ? EQUIP_ERR_CAN_EQUIP_ONLY1_AMMOPOUCH
10475 : EQUIP_ERR_CAN_EQUIP_ONLY1_QUIVER;
10482 uint32 type = pProto->InventoryType;
10484 if (eslot == EQUIPMENT_SLOT_OFFHAND)
10486 if (type == INVTYPE_WEAPON || type == INVTYPE_WEAPONOFFHAND)
10488 if (!CanDualWield())
10489 return EQUIP_ERR_CANT_DUAL_WIELD;
10491 else if (type == INVTYPE_2HWEAPON)
10493 if (!CanDualWield() || !CanTitanGrip())
10494 return EQUIP_ERR_CANT_DUAL_WIELD;
10497 if (IsTwoHandUsed())
10498 return EQUIP_ERR_CANT_EQUIP_WITH_TWOHANDED;
10501 // equip two-hand weapon case (with possible unequip 2 items)
10502 if (type == INVTYPE_2HWEAPON)
10504 if (eslot == EQUIPMENT_SLOT_OFFHAND)
10506 if (!CanTitanGrip())
10507 return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;
10509 else if (eslot != EQUIPMENT_SLOT_MAINHAND)
10510 return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;
10512 if (!CanTitanGrip())
10514 // offhand item must can be stored in inventory for offhand item and it also must be unequipped
10515 Item* offItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
10516 ItemPosCountVec off_dest;
10517 if (offItem && (!direct_action ||
10518 CanUnequipItem(uint16(INVENTORY_SLOT_BAG_0) << 8 | EQUIPMENT_SLOT_OFFHAND, false) != EQUIP_ERR_OK ||
10519 CanStoreItem(NULL_BAG, NULL_SLOT, off_dest, offItem, false) != EQUIP_ERR_OK))
10520 return swap ? EQUIP_ERR_ITEMS_CANT_BE_SWAPPED : EQUIP_ERR_INVENTORY_FULL;
10523 dest = ((INVENTORY_SLOT_BAG_0 << 8) | eslot);
10524 return EQUIP_ERR_OK;
10528 return !swap ? EQUIP_ERR_ITEM_NOT_FOUND : EQUIP_ERR_ITEMS_CANT_BE_SWAPPED;
10531 InventoryResult Player::CanUnequipItem(uint16 pos, bool swap) const
10533 // Applied only to equipped items and bank bags
10534 if (!IsEquipmentPos(pos) && !IsBagPos(pos))
10535 return EQUIP_ERR_OK;
10537 Item* pItem = GetItemByPos(pos);
10539 // Applied only to existing equipped item
10540 if (!pItem)
10541 return EQUIP_ERR_OK;
10543 DEBUG_LOG("STORAGE: CanUnequipItem slot = %u, item = %u, count = %u", pos, pItem->GetEntry(), pItem->GetCount());
10545 ItemPrototype const* pProto = pItem->GetProto();
10546 if (!pProto)
10547 return EQUIP_ERR_ITEM_NOT_FOUND;
10549 // item used
10550 if (pItem->HasTemporaryLoot())
10551 return EQUIP_ERR_ALREADY_LOOTED;
10553 // do not allow unequipping gear except weapons, offhands, projectiles, relics in
10554 // - combat
10555 // - in-progress arenas
10556 if (!pProto->CanChangeEquipStateInCombat())
10558 if (isInCombat())
10559 return EQUIP_ERR_NOT_IN_COMBAT;
10561 if (BattleGround* bg = GetBattleGround())
10562 if (bg->isArena() && bg->GetStatus() == STATUS_IN_PROGRESS)
10563 return EQUIP_ERR_NOT_DURING_ARENA_MATCH;
10566 // prevent unequip item in process logout
10567 if (GetSession()->isLogingOut())
10568 return EQUIP_ERR_YOU_ARE_STUNNED;
10570 if (!swap && pItem->IsBag() && !((Bag*)pItem)->IsEmpty())
10571 return EQUIP_ERR_CAN_ONLY_DO_WITH_EMPTY_BAGS;
10573 return EQUIP_ERR_OK;
10576 InventoryResult Player::CanBankItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap, bool not_loading) const
10578 if (!pItem)
10579 return swap ? EQUIP_ERR_ITEMS_CANT_BE_SWAPPED : EQUIP_ERR_ITEM_NOT_FOUND;
10581 uint32 count = pItem->GetCount();
10583 DEBUG_LOG("STORAGE: CanBankItem bag = %u, slot = %u, item = %u, count = %u", bag, slot, pItem->GetEntry(), pItem->GetCount());
10584 ItemPrototype const* pProto = pItem->GetProto();
10585 if (!pProto)
10586 return swap ? EQUIP_ERR_ITEMS_CANT_BE_SWAPPED : EQUIP_ERR_ITEM_NOT_FOUND;
10588 // item used
10589 if (pItem->HasTemporaryLoot())
10590 return EQUIP_ERR_ALREADY_LOOTED;
10592 if (pItem->IsBindedNotWith(this))
10593 return EQUIP_ERR_DONT_OWN_THAT_ITEM;
10595 // check count of items (skip for auto move for same player from bank)
10596 InventoryResult res = CanTakeMoreSimilarItems(pItem);
10597 if (res != EQUIP_ERR_OK)
10598 return res;
10600 // in specific slot
10601 if (bag != NULL_BAG && slot != NULL_SLOT)
10603 if (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END)
10605 if (!pItem->IsBag())
10606 return EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT;
10608 if (slot - BANK_SLOT_BAG_START >= GetBankBagSlotCount())
10609 return EQUIP_ERR_MUST_PURCHASE_THAT_BAG_SLOT;
10611 res = CanUseItem(pItem, not_loading);
10612 if (res != EQUIP_ERR_OK)
10613 return res;
10616 res = _CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, count, swap, pItem);
10617 if (res != EQUIP_ERR_OK)
10618 return res;
10620 if (count == 0)
10621 return EQUIP_ERR_OK;
10624 // not specific slot or have space for partly store only in specific slot
10626 // in specific bag
10627 if (bag != NULL_BAG)
10629 if (pProto->InventoryType == INVTYPE_BAG)
10631 Bag* pBag = (Bag*)pItem;
10632 if (pBag && !pBag->IsEmpty())
10633 return EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG;
10636 // search stack in bag for merge to
10637 if (pProto->Stackable != 1)
10639 if (bag == INVENTORY_SLOT_BAG_0)
10641 res = _CanStoreItem_InInventorySlots(BANK_SLOT_ITEM_START, BANK_SLOT_ITEM_END, dest, pProto, count, true, pItem, bag, slot);
10642 if (res != EQUIP_ERR_OK)
10643 return res;
10645 if (count == 0)
10646 return EQUIP_ERR_OK;
10648 else
10650 res = _CanStoreItem_InBag(bag, dest, pProto, count, true, false, pItem, NULL_BAG, slot);
10651 if (res != EQUIP_ERR_OK)
10652 res = _CanStoreItem_InBag(bag, dest, pProto, count, true, true, pItem, NULL_BAG, slot);
10654 if (res != EQUIP_ERR_OK)
10655 return res;
10657 if (count == 0)
10658 return EQUIP_ERR_OK;
10662 // search free slot in bag
10663 if (bag == INVENTORY_SLOT_BAG_0)
10665 res = _CanStoreItem_InInventorySlots(BANK_SLOT_ITEM_START, BANK_SLOT_ITEM_END, dest, pProto, count, false, pItem, bag, slot);
10666 if (res != EQUIP_ERR_OK)
10667 return res;
10669 if (count == 0)
10670 return EQUIP_ERR_OK;
10672 else
10674 res = _CanStoreItem_InBag(bag, dest, pProto, count, false, false, pItem, NULL_BAG, slot);
10675 if (res != EQUIP_ERR_OK)
10676 res = _CanStoreItem_InBag(bag, dest, pProto, count, false, true, pItem, NULL_BAG, slot);
10678 if (res != EQUIP_ERR_OK)
10679 return res;
10681 if (count == 0)
10682 return EQUIP_ERR_OK;
10686 // not specific bag or have space for partly store only in specific bag
10688 // search stack for merge to
10689 if (pProto->Stackable != 1)
10691 // in slots
10692 res = _CanStoreItem_InInventorySlots(BANK_SLOT_ITEM_START, BANK_SLOT_ITEM_END, dest, pProto, count, true, pItem, bag, slot);
10693 if (res != EQUIP_ERR_OK)
10694 return res;
10696 if (count == 0)
10697 return EQUIP_ERR_OK;
10699 // in special bags
10700 if (pProto->BagFamily)
10702 for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
10704 res = _CanStoreItem_InBag(i, dest, pProto, count, true, false, pItem, bag, slot);
10705 if (res != EQUIP_ERR_OK)
10706 continue;
10708 if (count == 0)
10709 return EQUIP_ERR_OK;
10713 for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
10715 res = _CanStoreItem_InBag(i, dest, pProto, count, true, true, pItem, bag, slot);
10716 if (res != EQUIP_ERR_OK)
10717 continue;
10719 if (count == 0)
10720 return EQUIP_ERR_OK;
10724 // search free place in special bag
10725 if (pProto->BagFamily)
10727 for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
10729 res = _CanStoreItem_InBag(i, dest, pProto, count, false, false, pItem, bag, slot);
10730 if (res != EQUIP_ERR_OK)
10731 continue;
10733 if (count == 0)
10734 return EQUIP_ERR_OK;
10738 // search free space
10739 res = _CanStoreItem_InInventorySlots(BANK_SLOT_ITEM_START, BANK_SLOT_ITEM_END, dest, pProto, count, false, pItem, bag, slot);
10740 if (res != EQUIP_ERR_OK)
10741 return res;
10743 if (count == 0)
10744 return EQUIP_ERR_OK;
10746 for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
10748 res = _CanStoreItem_InBag(i, dest, pProto, count, false, true, pItem, bag, slot);
10749 if (res != EQUIP_ERR_OK)
10750 continue;
10752 if (count == 0)
10753 return EQUIP_ERR_OK;
10755 return EQUIP_ERR_BANK_FULL;
10758 InventoryResult Player::CanUseItem(Item* pItem, bool direct_action) const
10760 if (pItem)
10762 DEBUG_LOG("STORAGE: CanUseItem item = %u", pItem->GetEntry());
10764 if (!isAlive() && direct_action)
10765 return EQUIP_ERR_YOU_ARE_DEAD;
10767 // if (isStunned())
10768 // return EQUIP_ERR_YOU_ARE_STUNNED;
10770 ItemPrototype const* pProto = pItem->GetProto();
10771 if (pProto)
10773 if (pItem->IsBindedNotWith(this))
10774 return EQUIP_ERR_DONT_OWN_THAT_ITEM;
10776 InventoryResult msg = CanUseItem(pProto);
10777 if (msg != EQUIP_ERR_OK)
10778 return msg;
10780 if (uint32 item_use_skill = pItem->GetSkill())
10782 if (GetSkillValue(item_use_skill) == 0)
10784 // armor items with scaling stats can downgrade armor skill reqs if related class can learn armor use at some level
10785 if (pProto->Class != ITEM_CLASS_ARMOR)
10786 return EQUIP_ERR_NO_REQUIRED_PROFICIENCY;
10788 ScalingStatDistributionEntry const* ssd = pProto->ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(pProto->ScalingStatDistribution) : NULL;
10789 if (!ssd)
10790 return EQUIP_ERR_NO_REQUIRED_PROFICIENCY;
10792 bool allowScaleSkill = false;
10793 for (uint32 i = 0; i < sSkillLineAbilityStore.GetNumRows(); ++i)
10795 SkillLineAbilityEntry const* skillInfo = sSkillLineAbilityStore.LookupEntry(i);
10796 if (!skillInfo)
10797 continue;
10799 if (skillInfo->skillId != item_use_skill)
10800 continue;
10802 // can't learn
10803 if (skillInfo->classmask && (skillInfo->classmask & getClassMask()) == 0)
10804 continue;
10806 if (skillInfo->racemask && (skillInfo->racemask & getRaceMask()) == 0)
10807 continue;
10809 allowScaleSkill = true;
10810 break;
10813 if (!allowScaleSkill)
10814 return EQUIP_ERR_NO_REQUIRED_PROFICIENCY;
10818 if (pProto->RequiredReputationFaction && uint32(GetReputationRank(pProto->RequiredReputationFaction)) < pProto->RequiredReputationRank)
10819 return EQUIP_ERR_CANT_EQUIP_REPUTATION;
10821 return EQUIP_ERR_OK;
10824 return EQUIP_ERR_ITEM_NOT_FOUND;
10827 InventoryResult Player::CanUseItem(ItemPrototype const* pProto) const
10829 // Used by group, function NeedBeforeGreed, to know if a prototype can be used by a player
10831 if (pProto)
10833 if ((pProto->Flags2 & ITEM_FLAG2_HORDE_ONLY) && GetTeam() != HORDE)
10834 return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM;
10836 if ((pProto->Flags2 & ITEM_FLAG2_ALLIANCE_ONLY) && GetTeam() != ALLIANCE)
10837 return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM;
10839 if ((pProto->AllowableClass & getClassMask()) == 0 || (pProto->AllowableRace & getRaceMask()) == 0)
10840 return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM;
10842 if (pProto->RequiredSkill != 0)
10844 if (GetSkillValue(pProto->RequiredSkill) == 0)
10845 return EQUIP_ERR_NO_REQUIRED_PROFICIENCY;
10846 else if (GetSkillValue(pProto->RequiredSkill) < pProto->RequiredSkillRank)
10847 return EQUIP_ERR_CANT_EQUIP_SKILL;
10850 if (pProto->RequiredSpell != 0 && !HasSpell(pProto->RequiredSpell))
10851 return EQUIP_ERR_NO_REQUIRED_PROFICIENCY;
10853 if (getLevel() < pProto->RequiredLevel)
10854 return EQUIP_ERR_CANT_EQUIP_LEVEL_I;
10856 return EQUIP_ERR_OK;
10858 return EQUIP_ERR_ITEM_NOT_FOUND;
10861 InventoryResult Player::CanUseAmmo(uint32 item) const
10863 DEBUG_LOG("STORAGE: CanUseAmmo item = %u", item);
10864 if (!isAlive())
10865 return EQUIP_ERR_YOU_ARE_DEAD;
10866 // if( isStunned() )
10867 // return EQUIP_ERR_YOU_ARE_STUNNED;
10868 ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(item);
10869 if (pProto)
10871 if (pProto->InventoryType != INVTYPE_AMMO)
10872 return EQUIP_ERR_ONLY_AMMO_CAN_GO_HERE;
10874 InventoryResult msg = CanUseItem(pProto);
10875 if (msg != EQUIP_ERR_OK)
10876 return msg;
10878 /*if ( GetReputationMgr().GetReputation() < pProto->RequiredReputation )
10879 return EQUIP_ERR_CANT_EQUIP_REPUTATION;
10882 // Requires No Ammo
10883 if (GetDummyAura(46699))
10884 return EQUIP_ERR_BAG_FULL6;
10886 return EQUIP_ERR_OK;
10888 return EQUIP_ERR_ITEM_NOT_FOUND;
10891 void Player::SetAmmo(uint32 item)
10893 //if(!item)
10894 // return;
10896 //// already set
10897 //if( GetUInt32Value(PLAYER_AMMO_ID) == item )
10898 // return;
10900 //// check ammo
10901 //if (item)
10903 // InventoryResult msg = CanUseAmmo( item );
10904 // if (msg != EQUIP_ERR_OK)
10905 // {
10906 // SendEquipError(msg, NULL, NULL, item);
10907 // return;
10908 // }
10911 //SetUInt32Value(PLAYER_AMMO_ID, item);
10913 //_ApplyAmmoBonuses();
10916 void Player::RemoveAmmo()
10918 //SetUInt32Value(PLAYER_AMMO_ID, 0);
10920 //m_ammoDPS = 0.0f;
10922 //if (CanModifyStats())
10923 // UpdateDamagePhysical(RANGED_ATTACK);
10926 // Return stored item (if stored to stack, it can diff. from pItem). And pItem ca be deleted in this case.
10927 Item* Player::StoreNewItem(ItemPosCountVec const& dest, uint32 item, bool update, int32 randomPropertyId)
10929 uint32 count = 0;
10930 for (ItemPosCountVec::const_iterator itr = dest.begin(); itr != dest.end(); ++itr)
10931 count += itr->count;
10933 Item* pItem = Item::CreateItem(item, count, this, randomPropertyId);
10934 if (pItem)
10936 ItemAddedQuestCheck(item, count);
10937 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_RECEIVE_EPIC_ITEM, item, count);
10938 pItem = StoreItem(dest, pItem, update);
10940 return pItem;
10943 Item* Player::StoreItem(ItemPosCountVec const& dest, Item* pItem, bool update)
10945 if (!pItem)
10946 return NULL;
10948 Item* lastItem = pItem;
10949 uint32 entry = pItem->GetEntry();
10951 for (ItemPosCountVec::const_iterator itr = dest.begin(); itr != dest.end();)
10953 uint16 pos = itr->pos;
10954 uint32 count = itr->count;
10956 ++itr;
10958 if (itr == dest.end())
10960 lastItem = _StoreItem(pos, pItem, count, false, update);
10961 break;
10964 lastItem = _StoreItem(pos, pItem, count, true, update);
10967 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM, entry);
10968 return lastItem;
10971 // Return stored item (if stored to stack, it can diff. from pItem). And pItem ca be deleted in this case.
10972 Item* Player::_StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool update)
10974 if (!pItem)
10975 return NULL;
10977 uint8 bag = pos >> 8;
10978 uint8 slot = pos & 255;
10980 DEBUG_LOG("STORAGE: StoreItem bag = %u, slot = %u, item = %u, count = %u", bag, slot, pItem->GetEntry(), count);
10982 Item* pItem2 = GetItemByPos(bag, slot);
10984 if (!pItem2)
10986 if (clone)
10987 pItem = pItem->CloneItem(count, this);
10988 else
10989 pItem->SetCount(count);
10991 if (!pItem)
10992 return NULL;
10994 if (pItem->GetProto()->Bonding == BIND_WHEN_PICKED_UP ||
10995 pItem->GetProto()->Bonding == BIND_QUEST_ITEM ||
10996 (pItem->GetProto()->Bonding == BIND_WHEN_EQUIPPED && IsBagPos(pos)))
10997 pItem->SetBinding(true);
10999 if (bag == INVENTORY_SLOT_BAG_0)
11001 m_items[slot] = pItem;
11002 SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), pItem->GetObjectGuid());
11003 pItem->SetGuidValue(ITEM_FIELD_CONTAINED, GetObjectGuid());
11004 pItem->SetGuidValue(ITEM_FIELD_OWNER, GetObjectGuid());
11006 pItem->SetSlot(slot);
11007 pItem->SetContainer(NULL);
11009 if (IsInWorld() && update)
11011 pItem->AddToWorld();
11012 pItem->SendCreateUpdateToPlayer(this);
11015 pItem->SetState(ITEM_CHANGED, this);
11017 else if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag))
11019 pBag->StoreItem(slot, pItem, update);
11020 if (IsInWorld() && update)
11022 pItem->AddToWorld();
11023 pItem->SendCreateUpdateToPlayer(this);
11025 pItem->SetState(ITEM_CHANGED, this);
11026 pBag->SetState(ITEM_CHANGED, this);
11029 AddEnchantmentDurations(pItem);
11030 AddItemDurations(pItem);
11032 // at place into not appropriate slot (bank, for example) remove aura
11033 ApplyItemOnStoreSpell(pItem, IsEquipmentPos(pItem->GetBagSlot(), pItem->GetSlot()) || IsInventoryPos(pItem->GetBagSlot(), pItem->GetSlot()));
11035 return pItem;
11037 else
11039 if (pItem2->GetProto()->Bonding == BIND_WHEN_PICKED_UP ||
11040 pItem2->GetProto()->Bonding == BIND_QUEST_ITEM ||
11041 (pItem2->GetProto()->Bonding == BIND_WHEN_EQUIPPED && IsBagPos(pos)))
11042 pItem2->SetBinding(true);
11044 pItem2->SetCount(pItem2->GetCount() + count);
11045 if (IsInWorld() && update)
11046 pItem2->SendCreateUpdateToPlayer(this);
11048 if (!clone)
11050 // delete item (it not in any slot currently)
11051 if (IsInWorld() && update)
11053 pItem->RemoveFromWorld();
11054 pItem->DestroyForPlayer(this);
11057 RemoveEnchantmentDurations(pItem);
11058 RemoveItemDurations(pItem);
11060 pItem->SetOwnerGuid(GetObjectGuid()); // prevent error at next SetState in case trade/mail/buy from vendor
11061 pItem->SetState(ITEM_REMOVED, this);
11064 // AddItemDurations(pItem2); - pItem2 already have duration listed for player
11065 AddEnchantmentDurations(pItem2);
11067 pItem2->SetState(ITEM_CHANGED, this);
11069 return pItem2;
11073 Item* Player::EquipNewItem(uint16 pos, uint32 item, bool update)
11075 if (Item* pItem = Item::CreateItem(item, 1, this))
11077 ItemAddedQuestCheck(item, 1);
11078 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_RECEIVE_EPIC_ITEM, item, 1);
11079 return EquipItem(pos, pItem, update);
11082 return NULL;
11085 Item* Player::EquipItem(uint16 pos, Item* pItem, bool update)
11087 AddEnchantmentDurations(pItem);
11088 AddItemDurations(pItem);
11090 uint8 bag = pos >> 8;
11091 uint8 slot = pos & 255;
11093 Item* pItem2 = GetItemByPos(bag, slot);
11094 if (!pItem2)
11096 VisualizeItem(slot, pItem);
11098 if (isAlive())
11100 ItemPrototype const* pProto = pItem->GetProto();
11102 // item set bonuses applied only at equip and removed at unequip, and still active for broken items
11103 if (pProto && pProto->ItemSet)
11104 AddItemsSetItem(this, pItem);
11106 _ApplyItemMods(pItem, slot, true);
11108 ApplyItemOnStoreSpell(pItem, true);
11110 // Weapons and also Totem/Relic/Sigil/etc
11111 if (pProto && isInCombat() && (pProto->Class == ITEM_CLASS_WEAPON || pProto->InventoryType == INVTYPE_RELIC) && m_weaponChangeTimer == 0)
11113 uint32 cooldownSpell = SPELL_ID_WEAPON_SWITCH_COOLDOWN_1_5s;
11115 if (getClass() == CLASS_ROGUE)
11116 cooldownSpell = SPELL_ID_WEAPON_SWITCH_COOLDOWN_1_0s;
11118 SpellEntry const* spellProto = sSpellStore.LookupEntry(cooldownSpell);
11120 if (!spellProto)
11121 sLog.outError("Weapon switch cooldown spell %u couldn't be found in Spell.dbc", cooldownSpell);
11122 else
11124 m_weaponChangeTimer = spellProto->GetStartRecoveryTime();
11126 WorldPacket data(SMSG_SPELL_COOLDOWN, 8 + 1 + 4);
11127 data << GetObjectGuid();
11128 data << uint8(1);
11129 data << uint32(cooldownSpell);
11130 data << uint32(0);
11131 GetSession()->SendPacket(&data);
11136 if (IsInWorld() && update)
11138 pItem->AddToWorld();
11139 pItem->SendCreateUpdateToPlayer(this);
11142 ApplyEquipCooldown(pItem);
11144 if (slot == EQUIPMENT_SLOT_MAINHAND)
11146 UpdateExpertise(BASE_ATTACK);
11147 UpdateArmorPenetration();
11149 else if (slot == EQUIPMENT_SLOT_OFFHAND)
11151 UpdateExpertise(OFF_ATTACK);
11152 UpdateArmorPenetration();
11155 UpdateArmorSpecializations();
11157 else
11159 pItem2->SetCount(pItem2->GetCount() + pItem->GetCount());
11160 if (IsInWorld() && update)
11161 pItem2->SendCreateUpdateToPlayer(this);
11163 // delete item (it not in any slot currently)
11164 // pItem->DeleteFromDB();
11165 if (IsInWorld() && update)
11167 pItem->RemoveFromWorld();
11168 pItem->DestroyForPlayer(this);
11171 RemoveEnchantmentDurations(pItem);
11172 RemoveItemDurations(pItem);
11174 pItem->SetOwnerGuid(GetObjectGuid()); // prevent error at next SetState in case trade/mail/buy from vendor
11175 pItem->SetState(ITEM_REMOVED, this);
11176 pItem2->SetState(ITEM_CHANGED, this);
11178 ApplyEquipCooldown(pItem2);
11180 return pItem2;
11182 // Apply Titan's Grip damage penalty if necessary
11183 if ((slot == EQUIPMENT_SLOT_MAINHAND || slot == EQUIPMENT_SLOT_OFFHAND) && CanTitanGrip() && HasTwoHandWeaponInOneHand() && !HasAura(49152))
11184 CastSpell(this, 49152, true);
11186 // only for full equip instead adding to stack
11187 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EQUIP_ITEM, pItem->GetEntry());
11188 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EQUIP_EPIC_ITEM, slot + 1);
11190 return pItem;
11193 void Player::QuickEquipItem(uint16 pos, Item* pItem)
11195 if (pItem)
11197 AddEnchantmentDurations(pItem);
11198 AddItemDurations(pItem);
11199 ApplyItemOnStoreSpell(pItem, true);
11201 uint8 slot = pos & 255;
11202 VisualizeItem(slot, pItem);
11204 if (IsInWorld())
11206 pItem->AddToWorld();
11207 pItem->SendCreateUpdateToPlayer(this);
11209 // Apply Titan's Grip damage penalty if necessary
11210 if ((slot == EQUIPMENT_SLOT_MAINHAND || slot == EQUIPMENT_SLOT_OFFHAND) && CanTitanGrip() && HasTwoHandWeaponInOneHand() && !HasAura(49152))
11211 CastSpell(this, 49152, true);
11213 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EQUIP_ITEM, pItem->GetEntry());
11214 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EQUIP_EPIC_ITEM, slot + 1);
11216 UpdateArmorSpecializations();
11220 void Player::SetVisibleItemSlot(uint8 slot, Item* pItem)
11222 if (pItem)
11224 SetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + (slot * 2), pItem->GetEntry());
11225 SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 0, pItem->GetEnchantmentId(PERM_ENCHANTMENT_SLOT));
11226 SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 1, pItem->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT));
11228 else
11230 SetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + (slot * 2), 0);
11231 SetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 0);
11235 void Player::VisualizeItem(uint8 slot, Item* pItem)
11237 if (!pItem)
11238 return;
11240 // check also BIND_WHEN_PICKED_UP and BIND_QUEST_ITEM for .additem or .additemset case by GM (not binded at adding to inventory)
11241 if (pItem->GetProto()->Bonding == BIND_WHEN_EQUIPPED || pItem->GetProto()->Bonding == BIND_WHEN_PICKED_UP || pItem->GetProto()->Bonding == BIND_QUEST_ITEM)
11242 pItem->SetBinding(true);
11244 DEBUG_LOG("STORAGE: EquipItem slot = %u, item = %u", slot, pItem->GetEntry());
11246 m_items[slot] = pItem;
11247 SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), pItem->GetObjectGuid());
11248 pItem->SetGuidValue(ITEM_FIELD_CONTAINED, GetObjectGuid());
11249 pItem->SetGuidValue(ITEM_FIELD_OWNER, GetObjectGuid());
11250 pItem->SetSlot(slot);
11251 pItem->SetContainer(NULL);
11253 if (slot < EQUIPMENT_SLOT_END)
11254 SetVisibleItemSlot(slot, pItem);
11256 pItem->SetState(ITEM_CHANGED, this);
11259 void Player::RemoveItem(uint8 bag, uint8 slot, bool update)
11261 // note: removeitem does not actually change the item
11262 // it only takes the item out of storage temporarily
11263 // note2: if removeitem is to be used for delinking
11264 // the item must be removed from the player's updatequeue
11266 if (Item* pItem = GetItemByPos(bag, slot))
11268 DEBUG_LOG("STORAGE: RemoveItem bag = %u, slot = %u, item = %u", bag, slot, pItem->GetEntry());
11270 RemoveEnchantmentDurations(pItem);
11271 RemoveItemDurations(pItem);
11273 if (bag == INVENTORY_SLOT_BAG_0)
11275 if (slot < INVENTORY_SLOT_BAG_END)
11277 ItemPrototype const* pProto = pItem->GetProto();
11278 // item set bonuses applied only at equip and removed at unequip, and still active for broken items
11280 if (pProto && pProto->ItemSet)
11281 RemoveItemsSetItem(this, pProto);
11283 _ApplyItemMods(pItem, slot, false);
11285 // remove item dependent auras and casts (only weapon and armor slots)
11286 if (slot < EQUIPMENT_SLOT_END)
11288 RemoveItemDependentAurasAndCasts(pItem);
11290 // remove held enchantments, update expertise
11291 if (slot == EQUIPMENT_SLOT_MAINHAND)
11293 if (pItem->GetItemSuffixFactor())
11295 pItem->ClearEnchantment(PROP_ENCHANTMENT_SLOT_3);
11296 pItem->ClearEnchantment(PROP_ENCHANTMENT_SLOT_4);
11298 else
11300 pItem->ClearEnchantment(PROP_ENCHANTMENT_SLOT_0);
11301 pItem->ClearEnchantment(PROP_ENCHANTMENT_SLOT_1);
11304 UpdateExpertise(BASE_ATTACK);
11305 UpdateArmorPenetration();
11307 else if (slot == EQUIPMENT_SLOT_OFFHAND)
11309 UpdateExpertise(OFF_ATTACK);
11310 UpdateArmorPenetration();
11315 m_items[slot] = NULL;
11316 SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), ObjectGuid());
11318 if (slot < EQUIPMENT_SLOT_END)
11320 SetVisibleItemSlot(slot, NULL);
11321 // Remove Titan's Grip damage penalty if necessary
11322 if ((slot == EQUIPMENT_SLOT_MAINHAND || slot == EQUIPMENT_SLOT_OFFHAND) && CanTitanGrip() && !HasTwoHandWeaponInOneHand())
11323 RemoveAurasDueToSpell(49152);
11326 UpdateArmorSpecializations();
11328 else
11330 Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag);
11331 if (pBag)
11332 pBag->RemoveItem(slot, update);
11334 pItem->SetGuidValue(ITEM_FIELD_CONTAINED, ObjectGuid());
11335 // pItem->SetGuidValue(ITEM_FIELD_OWNER, ObjectGuid()); not clear owner at remove (it will be set at store). This used in mail and auction code
11336 pItem->SetSlot(NULL_SLOT);
11338 // ApplyItemOnStoreSpell, for avoid re-apply will remove at _adding_ to not appropriate slot
11340 if (IsInWorld() && update)
11341 pItem->SendCreateUpdateToPlayer(this);
11345 // Common operation need to remove item from inventory without delete in trade, auction, guild bank, mail....
11346 void Player::MoveItemFromInventory(uint8 bag, uint8 slot, bool update)
11348 if (Item* it = GetItemByPos(bag, slot))
11350 ItemRemovedQuestCheck(it->GetEntry(), it->GetCount());
11351 RemoveItem(bag, slot, update);
11353 // item atStore spell not removed in RemoveItem (for avoid reappaly in slots changes), so do it directly
11354 if (IsEquipmentPos(bag, slot) || IsInventoryPos(bag, slot))
11355 ApplyItemOnStoreSpell(it, false);
11357 it->RemoveFromUpdateQueueOf(this);
11358 if (it->IsInWorld())
11360 it->RemoveFromWorld();
11361 it->DestroyForPlayer(this);
11366 // Common operation need to add item from inventory without delete in trade, guild bank, mail....
11367 void Player::MoveItemToInventory(ItemPosCountVec const& dest, Item* pItem, bool update, bool in_characterInventoryDB)
11369 // update quest counters
11370 ItemAddedQuestCheck(pItem->GetEntry(), pItem->GetCount());
11371 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_RECEIVE_EPIC_ITEM, pItem->GetEntry(), pItem->GetCount());
11373 // store item
11374 Item* pLastItem = StoreItem(dest, pItem, update);
11376 // only set if not merged to existing stack (pItem can be deleted already but we can compare pointers any way)
11377 if (pLastItem == pItem)
11379 // update owner for last item (this can be original item with wrong owner
11380 if (pLastItem->GetOwnerGuid() != GetObjectGuid())
11381 pLastItem->SetOwnerGuid(GetObjectGuid());
11383 // if this original item then it need create record in inventory
11384 // in case trade we already have item in other player inventory
11385 pLastItem->SetState(in_characterInventoryDB ? ITEM_CHANGED : ITEM_NEW, this);
11389 void Player::DestroyItem(uint8 bag, uint8 slot, bool update)
11391 Item* pItem = GetItemByPos(bag, slot);
11392 if (pItem)
11394 DEBUG_LOG("STORAGE: DestroyItem bag = %u, slot = %u, item = %u", bag, slot, pItem->GetEntry());
11396 // start from destroy contained items (only equipped bag can have its)
11397 if (pItem->IsBag() && pItem->IsEquipped()) // this also prevent infinity loop if empty bag stored in bag==slot
11399 for (int i = 0; i < MAX_BAG_SIZE; ++i)
11400 DestroyItem(slot, i, update);
11403 if (pItem->HasFlag(ITEM_FIELD_FLAGS, ITEM_DYNFLAG_WRAPPED))
11405 static SqlStatementID delGifts ;
11407 SqlStatement stmt = CharacterDatabase.CreateStatement(delGifts, "DELETE FROM character_gifts WHERE item_guid = ?");
11408 stmt.PExecute(pItem->GetGUIDLow());
11411 RemoveEnchantmentDurations(pItem);
11412 RemoveItemDurations(pItem);
11414 if (IsEquipmentPos(bag, slot) || IsInventoryPos(bag, slot))
11415 ApplyItemOnStoreSpell(pItem, false);
11417 ItemRemovedQuestCheck(pItem->GetEntry(), pItem->GetCount());
11419 if (bag == INVENTORY_SLOT_BAG_0)
11421 SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), ObjectGuid());
11423 // equipment and equipped bags can have applied bonuses
11424 if (slot < INVENTORY_SLOT_BAG_END)
11426 ItemPrototype const* pProto = pItem->GetProto();
11428 // item set bonuses applied only at equip and removed at unequip, and still active for broken items
11429 if (pProto && pProto->ItemSet)
11430 RemoveItemsSetItem(this, pProto);
11432 _ApplyItemMods(pItem, slot, false);
11435 if (slot < EQUIPMENT_SLOT_END)
11437 // remove item dependent auras and casts (only weapon and armor slots)
11438 RemoveItemDependentAurasAndCasts(pItem);
11440 // update expertise
11441 if (slot == EQUIPMENT_SLOT_MAINHAND)
11443 UpdateExpertise(BASE_ATTACK);
11444 UpdateArmorPenetration();
11446 else if (slot == EQUIPMENT_SLOT_OFFHAND)
11448 UpdateExpertise(OFF_ATTACK);
11449 UpdateArmorPenetration();
11452 // equipment visual show
11453 SetVisibleItemSlot(slot, NULL);
11456 m_items[slot] = NULL;
11458 else if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag))
11459 pBag->RemoveItem(slot, update);
11461 if (IsInWorld() && update)
11463 pItem->RemoveFromWorld();
11464 pItem->DestroyForPlayer(this);
11467 // pItem->SetOwnerGUID(0);
11468 pItem->SetGuidValue(ITEM_FIELD_CONTAINED, ObjectGuid());
11469 pItem->SetSlot(NULL_SLOT);
11470 pItem->SetState(ITEM_REMOVED, this);
11474 void Player::DestroyItemCount(uint32 item, uint32 count, bool update, bool unequip_check, bool inBankAlso)
11476 DEBUG_LOG("STORAGE: DestroyItemCount item = %u, count = %u", item, count);
11477 uint32 remcount = 0;
11479 // in inventory
11480 for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
11482 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
11484 if (pItem->GetEntry() == item && !pItem->IsInTrade())
11486 if (pItem->GetCount() + remcount <= count)
11488 // all items in inventory can unequipped
11489 remcount += pItem->GetCount();
11490 DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
11492 if (remcount >= count)
11493 return;
11495 else
11497 ItemRemovedQuestCheck(pItem->GetEntry(), count - remcount);
11498 pItem->SetCount(pItem->GetCount() - count + remcount);
11499 if (IsInWorld() && update)
11500 pItem->SendCreateUpdateToPlayer(this);
11501 pItem->SetState(ITEM_CHANGED, this);
11502 return;
11508 // in inventory bags
11509 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
11511 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
11513 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
11515 if (Item* pItem = pBag->GetItemByPos(j))
11517 if (pItem->GetEntry() == item && !pItem->IsInTrade())
11519 // all items in bags can be unequipped
11520 if (pItem->GetCount() + remcount <= count)
11522 remcount += pItem->GetCount();
11523 DestroyItem(i, j, update);
11525 if (remcount >= count)
11526 return;
11528 else
11530 ItemRemovedQuestCheck(pItem->GetEntry(), count - remcount);
11531 pItem->SetCount(pItem->GetCount() - count + remcount);
11532 if (IsInWorld() && update)
11533 pItem->SendCreateUpdateToPlayer(this);
11534 pItem->SetState(ITEM_CHANGED, this);
11535 return;
11543 // in equipment and bag list
11544 for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_BAG_END; ++i)
11546 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
11548 if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade())
11550 if (pItem->GetCount() + remcount <= count)
11552 if (!unequip_check || CanUnequipItem(INVENTORY_SLOT_BAG_0 << 8 | i, false) == EQUIP_ERR_OK)
11554 remcount += pItem->GetCount();
11555 DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
11557 if (remcount >= count)
11558 return;
11561 else
11563 ItemRemovedQuestCheck(pItem->GetEntry(), count - remcount);
11564 pItem->SetCount(pItem->GetCount() - count + remcount);
11565 if (IsInWorld() && update)
11566 pItem->SendCreateUpdateToPlayer(this);
11567 pItem->SetState(ITEM_CHANGED, this);
11568 return;
11574 if (inBankAlso) // Remove items from bank as well
11576 for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i)
11578 Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
11579 if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade())
11581 if (pItem->GetCount() + remcount <= count)
11583 remcount += pItem->GetCount();
11584 DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
11586 if (remcount >= count)
11587 return;
11589 else
11591 ItemRemovedQuestCheck(pItem->GetEntry(), count - remcount);
11592 pItem->SetCount(pItem->GetCount() - count + remcount);
11593 if (IsInWorld() && update)
11594 pItem->SendCreateUpdateToPlayer(this);
11595 pItem->SetState(ITEM_CHANGED, this);
11596 return;
11601 for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
11603 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
11605 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
11607 Item* pItem = pBag->GetItemByPos(j);
11608 if (pItem && pItem->GetEntry() == item && !pItem->IsInTrade())
11610 if (pItem->GetCount() + remcount <= count)
11612 remcount += pItem->GetCount();
11613 DestroyItem(i, j, update);
11615 if (remcount >= count)
11616 return;
11618 else
11620 ItemRemovedQuestCheck(pItem->GetEntry(), count - remcount);
11621 pItem->SetCount(pItem->GetCount() - count + remcount);
11622 if (IsInWorld() && update)
11623 pItem->SendCreateUpdateToPlayer(this);
11624 pItem->SetState(ITEM_CHANGED, this);
11625 return;
11634 void Player::DestroyZoneLimitedItem(bool update, uint32 new_zone)
11636 DEBUG_LOG("STORAGE: DestroyZoneLimitedItem in map %u and area %u", GetMapId(), new_zone);
11638 // in inventory
11639 for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
11640 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
11641 if (pItem->IsLimitedToAnotherMapOrZone(GetMapId(), new_zone))
11642 DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
11644 // in inventory bags
11645 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
11646 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
11647 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
11648 if (Item* pItem = pBag->GetItemByPos(j))
11649 if (pItem->IsLimitedToAnotherMapOrZone(GetMapId(), new_zone))
11650 DestroyItem(i, j, update);
11652 // in equipment and bag list
11653 for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_BAG_END; ++i)
11654 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
11655 if (pItem->IsLimitedToAnotherMapOrZone(GetMapId(), new_zone))
11656 DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
11659 void Player::DestroyConjuredItems(bool update)
11661 // used when entering arena
11662 // destroys all conjured items
11663 DEBUG_LOG("STORAGE: DestroyConjuredItems");
11665 // in inventory
11666 for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
11667 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
11668 if (pItem->IsConjuredConsumable())
11669 DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
11671 // in inventory bags
11672 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
11673 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
11674 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
11675 if (Item* pItem = pBag->GetItemByPos(j))
11676 if (pItem->IsConjuredConsumable())
11677 DestroyItem(i, j, update);
11679 // in equipment and bag list
11680 for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_BAG_END; ++i)
11681 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
11682 if (pItem->IsConjuredConsumable())
11683 DestroyItem(INVENTORY_SLOT_BAG_0, i, update);
11686 void Player::DestroyItemCount(Item* pItem, uint32& count, bool update)
11688 if (!pItem)
11689 return;
11691 DEBUG_LOG("STORAGE: DestroyItemCount item (GUID: %u, Entry: %u) count = %u", pItem->GetGUIDLow(), pItem->GetEntry(), count);
11693 if (pItem->GetCount() <= count)
11695 count -= pItem->GetCount();
11697 DestroyItem(pItem->GetBagSlot(), pItem->GetSlot(), update);
11699 else
11701 ItemRemovedQuestCheck(pItem->GetEntry(), count);
11702 pItem->SetCount(pItem->GetCount() - count);
11703 count = 0;
11704 if (IsInWorld() && update)
11705 pItem->SendCreateUpdateToPlayer(this);
11706 pItem->SetState(ITEM_CHANGED, this);
11710 void Player::SplitItem(uint16 src, uint16 dst, uint32 count)
11712 uint8 srcbag = src >> 8;
11713 uint8 srcslot = src & 255;
11715 uint8 dstbag = dst >> 8;
11716 uint8 dstslot = dst & 255;
11718 Item* pSrcItem = GetItemByPos(srcbag, srcslot);
11719 if (!pSrcItem)
11721 SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pSrcItem, NULL);
11722 return;
11725 if (pSrcItem->HasGeneratedLoot()) // prevent split looting item (stackable items can has only temporary loot and this meaning that loot window open)
11727 // best error message found for attempting to split while looting
11728 SendEquipError(EQUIP_ERR_COULDNT_SPLIT_ITEMS, pSrcItem, NULL);
11729 return;
11732 // not let split all items (can be only at cheating)
11733 if (pSrcItem->GetCount() == count)
11735 SendEquipError(EQUIP_ERR_COULDNT_SPLIT_ITEMS, pSrcItem, NULL);
11736 return;
11739 // not let split more existing items (can be only at cheating)
11740 if (pSrcItem->GetCount() < count)
11742 SendEquipError(EQUIP_ERR_TRIED_TO_SPLIT_MORE_THAN_COUNT, pSrcItem, NULL);
11743 return;
11746 DEBUG_LOG("STORAGE: SplitItem bag = %u, slot = %u, item = %u, count = %u", dstbag, dstslot, pSrcItem->GetEntry(), count);
11747 Item* pNewItem = pSrcItem->CloneItem(count, this);
11748 if (!pNewItem)
11750 SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pSrcItem, NULL);
11751 return;
11754 if (IsInventoryPos(dst))
11756 // change item amount before check (for unique max count check)
11757 pSrcItem->SetCount(pSrcItem->GetCount() - count);
11759 ItemPosCountVec dest;
11760 InventoryResult msg = CanStoreItem(dstbag, dstslot, dest, pNewItem, false);
11761 if (msg != EQUIP_ERR_OK)
11763 delete pNewItem;
11764 pSrcItem->SetCount(pSrcItem->GetCount() + count);
11765 SendEquipError(msg, pSrcItem, NULL);
11766 return;
11769 if (IsInWorld())
11770 pSrcItem->SendCreateUpdateToPlayer(this);
11771 pSrcItem->SetState(ITEM_CHANGED, this);
11772 StoreItem(dest, pNewItem, true);
11774 else if (IsBankPos(dst))
11776 // change item amount before check (for unique max count check)
11777 pSrcItem->SetCount(pSrcItem->GetCount() - count);
11779 ItemPosCountVec dest;
11780 InventoryResult msg = CanBankItem(dstbag, dstslot, dest, pNewItem, false);
11781 if (msg != EQUIP_ERR_OK)
11783 delete pNewItem;
11784 pSrcItem->SetCount(pSrcItem->GetCount() + count);
11785 SendEquipError(msg, pSrcItem, NULL);
11786 return;
11789 if (IsInWorld())
11790 pSrcItem->SendCreateUpdateToPlayer(this);
11791 pSrcItem->SetState(ITEM_CHANGED, this);
11792 BankItem(dest, pNewItem, true);
11794 else if (IsEquipmentPos(dst))
11796 // change item amount before check (for unique max count check), provide space for splitted items
11797 pSrcItem->SetCount(pSrcItem->GetCount() - count);
11799 uint16 dest;
11800 InventoryResult msg = CanEquipItem(dstslot, dest, pNewItem, false);
11801 if (msg != EQUIP_ERR_OK)
11803 delete pNewItem;
11804 pSrcItem->SetCount(pSrcItem->GetCount() + count);
11805 SendEquipError(msg, pSrcItem, NULL);
11806 return;
11809 if (IsInWorld())
11810 pSrcItem->SendCreateUpdateToPlayer(this);
11811 pSrcItem->SetState(ITEM_CHANGED, this);
11812 EquipItem(dest, pNewItem, true);
11813 AutoUnequipOffhandIfNeed();
11817 void Player::SwapItem(uint16 src, uint16 dst)
11819 uint8 srcbag = src >> 8;
11820 uint8 srcslot = src & 255;
11822 uint8 dstbag = dst >> 8;
11823 uint8 dstslot = dst & 255;
11825 Item* pSrcItem = GetItemByPos(srcbag, srcslot);
11826 Item* pDstItem = GetItemByPos(dstbag, dstslot);
11828 if (!pSrcItem)
11829 return;
11831 DEBUG_LOG("STORAGE: SwapItem bag = %u, slot = %u, item = %u", dstbag, dstslot, pSrcItem->GetEntry());
11833 if (!isAlive())
11835 SendEquipError(EQUIP_ERR_YOU_ARE_DEAD, pSrcItem, pDstItem);
11836 return;
11839 // SRC checks
11841 // check unequip potability for equipped items and bank bags
11842 if (IsEquipmentPos(src) || IsBagPos(src))
11844 // bags can be swapped with empty bag slots, or with empty bag (items move possibility checked later)
11845 InventoryResult msg = CanUnequipItem(src, !IsBagPos(src) || IsBagPos(dst) || (pDstItem && pDstItem->IsBag() && ((Bag*)pDstItem)->IsEmpty()));
11846 if (msg != EQUIP_ERR_OK)
11848 SendEquipError(msg, pSrcItem, pDstItem);
11849 return;
11853 // prevent put equipped/bank bag in self
11854 if (IsBagPos(src) && srcslot == dstbag)
11856 SendEquipError(EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG, pSrcItem, pDstItem);
11857 return;
11860 // prevent put equipped/bank bag in self
11861 if (IsBagPos(dst) && dstslot == srcbag)
11863 SendEquipError(EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG, pDstItem, pSrcItem);
11864 return;
11867 // DST checks
11869 if (pDstItem)
11871 // check unequip potability for equipped items and bank bags
11872 if (IsEquipmentPos(dst) || IsBagPos(dst))
11874 // bags can be swapped with empty bag slots, or with empty bag (items move possibility checked later)
11875 InventoryResult msg = CanUnequipItem(dst, !IsBagPos(dst) || IsBagPos(src) || (pSrcItem->IsBag() && ((Bag*)pSrcItem)->IsEmpty()));
11876 if (msg != EQUIP_ERR_OK)
11878 SendEquipError(msg, pSrcItem, pDstItem);
11879 return;
11884 // NOW this is or item move (swap with empty), or swap with another item (including bags in bag possitions)
11885 // or swap empty bag with another empty or not empty bag (with items exchange)
11887 // Move case
11888 if (!pDstItem)
11890 if (IsInventoryPos(dst))
11892 ItemPosCountVec dest;
11893 InventoryResult msg = CanStoreItem(dstbag, dstslot, dest, pSrcItem, false);
11894 if (msg != EQUIP_ERR_OK)
11896 SendEquipError(msg, pSrcItem, NULL);
11897 return;
11900 RemoveItem(srcbag, srcslot, true);
11901 StoreItem(dest, pSrcItem, true);
11903 else if (IsBankPos(dst))
11905 ItemPosCountVec dest;
11906 InventoryResult msg = CanBankItem(dstbag, dstslot, dest, pSrcItem, false);
11907 if (msg != EQUIP_ERR_OK)
11909 SendEquipError(msg, pSrcItem, NULL);
11910 return;
11913 RemoveItem(srcbag, srcslot, true);
11914 BankItem(dest, pSrcItem, true);
11916 else if (IsEquipmentPos(dst))
11918 uint16 dest;
11919 InventoryResult msg = CanEquipItem(dstslot, dest, pSrcItem, false);
11920 if (msg != EQUIP_ERR_OK)
11922 SendEquipError(msg, pSrcItem, NULL);
11923 return;
11926 RemoveItem(srcbag, srcslot, true);
11927 EquipItem(dest, pSrcItem, true);
11928 AutoUnequipOffhandIfNeed();
11931 return;
11934 // attempt merge to / fill target item
11935 if (!pSrcItem->IsBag() && !pDstItem->IsBag())
11937 InventoryResult msg;
11938 ItemPosCountVec sDest;
11939 uint16 eDest;
11940 if (IsInventoryPos(dst))
11941 msg = CanStoreItem(dstbag, dstslot, sDest, pSrcItem, false);
11942 else if (IsBankPos(dst))
11943 msg = CanBankItem(dstbag, dstslot, sDest, pSrcItem, false);
11944 else if (IsEquipmentPos(dst))
11945 msg = CanEquipItem(dstslot, eDest, pSrcItem, false);
11946 else
11947 return;
11949 // can be merge/fill
11950 if (msg == EQUIP_ERR_OK)
11952 if (pSrcItem->GetCount() + pDstItem->GetCount() <= pSrcItem->GetProto()->GetMaxStackSize())
11954 RemoveItem(srcbag, srcslot, true);
11956 if (IsInventoryPos(dst))
11957 StoreItem(sDest, pSrcItem, true);
11958 else if (IsBankPos(dst))
11959 BankItem(sDest, pSrcItem, true);
11960 else if (IsEquipmentPos(dst))
11962 EquipItem(eDest, pSrcItem, true);
11963 AutoUnequipOffhandIfNeed();
11966 else
11968 pSrcItem->SetCount(pSrcItem->GetCount() + pDstItem->GetCount() - pSrcItem->GetProto()->GetMaxStackSize());
11969 pDstItem->SetCount(pSrcItem->GetProto()->GetMaxStackSize());
11970 pSrcItem->SetState(ITEM_CHANGED, this);
11971 pDstItem->SetState(ITEM_CHANGED, this);
11972 if (IsInWorld())
11974 pSrcItem->SendCreateUpdateToPlayer(this);
11975 pDstItem->SendCreateUpdateToPlayer(this);
11978 return;
11982 // impossible merge/fill, do real swap
11983 InventoryResult msg;
11985 // check src->dest move possibility
11986 ItemPosCountVec sDest;
11987 uint16 eDest = 0;
11988 if (IsInventoryPos(dst))
11989 msg = CanStoreItem(dstbag, dstslot, sDest, pSrcItem, true);
11990 else if (IsBankPos(dst))
11991 msg = CanBankItem(dstbag, dstslot, sDest, pSrcItem, true);
11992 else if (IsEquipmentPos(dst))
11994 msg = CanEquipItem(dstslot, eDest, pSrcItem, true);
11995 if (msg == EQUIP_ERR_OK)
11996 msg = CanUnequipItem(eDest, true);
11999 if (msg != EQUIP_ERR_OK)
12001 SendEquipError(msg, pSrcItem, pDstItem);
12002 return;
12005 // check dest->src move possibility
12006 ItemPosCountVec sDest2;
12007 uint16 eDest2 = 0;
12008 if (IsInventoryPos(src))
12009 msg = CanStoreItem(srcbag, srcslot, sDest2, pDstItem, true);
12010 else if (IsBankPos(src))
12011 msg = CanBankItem(srcbag, srcslot, sDest2, pDstItem, true);
12012 else if (IsEquipmentPos(src))
12014 msg = CanEquipItem(srcslot, eDest2, pDstItem, true);
12015 if (msg == EQUIP_ERR_OK)
12016 msg = CanUnequipItem(eDest2, true);
12019 if (msg != EQUIP_ERR_OK)
12021 SendEquipError(msg, pDstItem, pSrcItem);
12022 return;
12025 // Check bag swap with item exchange (one from empty in not bag possition (equipped (not possible in fact) or store)
12026 if (pSrcItem->IsBag() && pDstItem->IsBag())
12028 Bag* emptyBag = NULL;
12029 Bag* fullBag = NULL;
12030 if (((Bag*)pSrcItem)->IsEmpty() && !IsBagPos(src))
12032 emptyBag = (Bag*)pSrcItem;
12033 fullBag = (Bag*)pDstItem;
12035 else if (((Bag*)pDstItem)->IsEmpty() && !IsBagPos(dst))
12037 emptyBag = (Bag*)pDstItem;
12038 fullBag = (Bag*)pSrcItem;
12041 // bag swap (with items exchange) case
12042 if (emptyBag && fullBag)
12044 ItemPrototype const* emotyProto = emptyBag->GetProto();
12046 uint32 count = 0;
12048 for (uint32 i = 0; i < fullBag->GetBagSize(); ++i)
12050 Item* bagItem = fullBag->GetItemByPos(i);
12051 if (!bagItem)
12052 continue;
12054 ItemPrototype const* bagItemProto = bagItem->GetProto();
12055 if (!bagItemProto || !ItemCanGoIntoBag(bagItemProto, emotyProto))
12057 // one from items not go to empty target bag
12058 SendEquipError(EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG, pSrcItem, pDstItem);
12059 return;
12062 ++count;
12065 if (count > emptyBag->GetBagSize())
12067 // too small targeted bag
12068 SendEquipError(EQUIP_ERR_ITEMS_CANT_BE_SWAPPED, pSrcItem, pDstItem);
12069 return;
12072 // Items swap
12073 count = 0; // will pos in new bag
12074 for (uint32 i = 0; i < fullBag->GetBagSize(); ++i)
12076 Item* bagItem = fullBag->GetItemByPos(i);
12077 if (!bagItem)
12078 continue;
12080 fullBag->RemoveItem(i, true);
12081 emptyBag->StoreItem(count, bagItem, true);
12082 bagItem->SetState(ITEM_CHANGED, this);
12084 ++count;
12089 // now do moves, remove...
12090 RemoveItem(dstbag, dstslot, false);
12091 RemoveItem(srcbag, srcslot, false);
12093 // add to dest
12094 if (IsInventoryPos(dst))
12095 StoreItem(sDest, pSrcItem, true);
12096 else if (IsBankPos(dst))
12097 BankItem(sDest, pSrcItem, true);
12098 else if (IsEquipmentPos(dst))
12099 EquipItem(eDest, pSrcItem, true);
12101 // add to src
12102 if (IsInventoryPos(src))
12103 StoreItem(sDest2, pDstItem, true);
12104 else if (IsBankPos(src))
12105 BankItem(sDest2, pDstItem, true);
12106 else if (IsEquipmentPos(src))
12107 EquipItem(eDest2, pDstItem, true);
12109 AutoUnequipOffhandIfNeed();
12112 void Player::AddItemToBuyBackSlot(Item* pItem)
12114 if (pItem)
12116 uint32 slot = m_currentBuybackSlot;
12117 // if current back slot non-empty search oldest or free
12118 if (m_items[slot])
12120 uint32 oldest_time = GetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1);
12121 uint32 oldest_slot = BUYBACK_SLOT_START;
12123 for (uint32 i = BUYBACK_SLOT_START + 1; i < BUYBACK_SLOT_END; ++i)
12125 // found empty
12126 if (!m_items[i])
12128 slot = i;
12129 break;
12132 uint32 i_time = GetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1 + i - BUYBACK_SLOT_START);
12134 if (oldest_time > i_time)
12136 oldest_time = i_time;
12137 oldest_slot = i;
12141 // find oldest
12142 slot = oldest_slot;
12145 RemoveItemFromBuyBackSlot(slot, true);
12146 DEBUG_LOG("STORAGE: AddItemToBuyBackSlot item = %u, slot = %u", pItem->GetEntry(), slot);
12148 m_items[slot] = pItem;
12149 time_t base = time(NULL);
12150 uint32 etime = uint32(base - m_logintime + (30 * 3600));
12151 uint32 eslot = slot - BUYBACK_SLOT_START;
12153 SetGuidValue(PLAYER_FIELD_VENDORBUYBACK_SLOT_1 + (eslot * 2), pItem->GetObjectGuid());
12154 if (ItemPrototype const* pProto = pItem->GetProto())
12155 SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1 + eslot, pProto->SellPrice * pItem->GetCount());
12156 else
12157 SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1 + eslot, 0);
12158 SetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1 + eslot, (uint32)etime);
12160 // move to next (for non filled list is move most optimized choice)
12161 if (m_currentBuybackSlot < BUYBACK_SLOT_END - 1)
12162 ++m_currentBuybackSlot;
12166 Item* Player::GetItemFromBuyBackSlot(uint32 slot)
12168 DEBUG_LOG("STORAGE: GetItemFromBuyBackSlot slot = %u", slot);
12169 if (slot >= BUYBACK_SLOT_START && slot < BUYBACK_SLOT_END)
12170 return m_items[slot];
12171 return NULL;
12174 void Player::RemoveItemFromBuyBackSlot(uint32 slot, bool del)
12176 DEBUG_LOG("STORAGE: RemoveItemFromBuyBackSlot slot = %u", slot);
12177 if (slot >= BUYBACK_SLOT_START && slot < BUYBACK_SLOT_END)
12179 Item* pItem = m_items[slot];
12180 if (pItem)
12182 pItem->RemoveFromWorld();
12183 if (del) pItem->SetState(ITEM_REMOVED, this);
12186 m_items[slot] = NULL;
12188 uint32 eslot = slot - BUYBACK_SLOT_START;
12189 SetGuidValue(PLAYER_FIELD_VENDORBUYBACK_SLOT_1 + (eslot * 2), ObjectGuid());
12190 SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1 + eslot, 0);
12191 SetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1 + eslot, 0);
12193 // if current backslot is filled set to now free slot
12194 if (m_items[m_currentBuybackSlot])
12195 m_currentBuybackSlot = slot;
12199 void Player::SendEquipError(InventoryResult msg, Item* pItem, Item* pItem2, uint32 itemid /*= 0*/) const
12201 DEBUG_LOG("WORLD: Sent SMSG_INVENTORY_CHANGE_FAILURE (%u)", msg);
12202 WorldPacket data(SMSG_INVENTORY_CHANGE_FAILURE, 1 + 8 + 8 + 1);
12203 data << uint8(msg);
12205 if (msg != EQUIP_ERR_OK)
12207 data << (pItem ? pItem->GetObjectGuid() : ObjectGuid());
12208 data << (pItem2 ? pItem2->GetObjectGuid() : ObjectGuid());
12209 data << uint8(0); // bag type subclass, used with EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM and EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG2
12211 switch (msg)
12213 case EQUIP_ERR_CANT_EQUIP_LEVEL_I:
12214 case EQUIP_ERR_PURCHASE_LEVEL_TOO_LOW:
12216 ItemPrototype const* proto = pItem ? pItem->GetProto() : ObjectMgr::GetItemPrototype(itemid);
12217 data << uint32(proto ? proto->RequiredLevel : 0);
12218 break;
12220 case EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM: // no idea about this one...
12222 data << uint64(0);
12223 data << uint32(0);
12224 data << uint64(0);
12225 break;
12227 case EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED_IS:
12228 case EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_SOCKETED_EXCEEDED_IS:
12229 case EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED_IS:
12231 ItemPrototype const* proto = pItem ? pItem->GetProto() : ObjectMgr::GetItemPrototype(itemid);
12232 data << uint32(proto ? proto->ItemLimitCategory : 0);
12233 break;
12235 default:
12236 break;
12239 GetSession()->SendPacket(&data);
12242 void Player::SendBuyError(BuyResult msg, Creature* pCreature, uint32 item, uint32 param)
12244 DEBUG_LOG("WORLD: Sent SMSG_BUY_FAILED");
12245 WorldPacket data(SMSG_BUY_FAILED, (8 + 4 + 4 + 1));
12246 data << (pCreature ? pCreature->GetObjectGuid() : ObjectGuid());
12247 data << uint32(item);
12248 if (param > 0)
12249 data << uint32(param);
12250 data << uint8(msg);
12251 GetSession()->SendPacket(&data);
12254 void Player::SendSellError(SellResult msg, Creature* pCreature, ObjectGuid itemGuid, uint32 param)
12256 DEBUG_LOG("WORLD: Sent SMSG_SELL_ITEM");
12257 WorldPacket data(SMSG_SELL_ITEM, (8 + 8 + (param ? 4 : 0) + 1)); // last check 2.0.10
12258 data << (pCreature ? pCreature->GetObjectGuid() : ObjectGuid());
12259 data << ObjectGuid(itemGuid);
12260 if (param > 0)
12261 data << uint32(param);
12262 data << uint8(msg);
12263 GetSession()->SendPacket(&data);
12266 void Player::TradeCancel(bool sendback)
12268 if (m_trade)
12270 Player* trader = m_trade->GetTrader();
12272 // send yellow "Trade canceled" message to both traders
12273 if (sendback)
12274 GetSession()->SendCancelTrade();
12276 trader->GetSession()->SendCancelTrade();
12278 // cleanup
12279 delete m_trade;
12280 m_trade = NULL;
12281 delete trader->m_trade;
12282 trader->m_trade = NULL;
12286 void Player::UpdateItemDuration(uint32 time, bool realtimeonly)
12288 if (m_itemDuration.empty())
12289 return;
12291 DEBUG_LOG("Player::UpdateItemDuration(%u,%u)", time, realtimeonly);
12293 for (ItemDurationList::const_iterator itr = m_itemDuration.begin(); itr != m_itemDuration.end();)
12295 Item* item = *itr;
12296 ++itr; // current element can be erased in UpdateDuration
12298 if ((realtimeonly && (item->GetProto()->ExtraFlags & ITEM_EXTRA_REAL_TIME_DURATION)) || !realtimeonly)
12299 item->UpdateDuration(this, time);
12303 void Player::UpdateEnchantTime(uint32 time)
12305 for (EnchantDurationList::iterator itr = m_enchantDuration.begin(), next; itr != m_enchantDuration.end(); itr = next)
12307 MANGOS_ASSERT(itr->item);
12308 next = itr;
12309 if (!itr->item->GetEnchantmentId(itr->slot))
12311 next = m_enchantDuration.erase(itr);
12313 else if (itr->leftduration <= time)
12315 ApplyEnchantment(itr->item, itr->slot, false, false);
12316 itr->item->ClearEnchantment(itr->slot);
12317 next = m_enchantDuration.erase(itr);
12319 else if (itr->leftduration > time)
12321 itr->leftduration -= time;
12322 ++next;
12327 void Player::AddEnchantmentDurations(Item* item)
12329 for (int x = 0; x < MAX_ENCHANTMENT_SLOT; ++x)
12331 if (x > PRISMATIC_ENCHANTMENT_SLOT && x < PROP_ENCHANTMENT_SLOT_0)
12332 continue;
12334 if (!item->GetEnchantmentId(EnchantmentSlot(x)))
12335 continue;
12337 uint32 duration = item->GetEnchantmentDuration(EnchantmentSlot(x));
12338 if (duration > 0)
12339 AddEnchantmentDuration(item, EnchantmentSlot(x), duration);
12343 void Player::RemoveEnchantmentDurations(Item* item)
12345 for (EnchantDurationList::iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end();)
12347 if (itr->item == item)
12349 // save duration in item
12350 item->SetEnchantmentDuration(EnchantmentSlot(itr->slot), itr->leftduration);
12351 itr = m_enchantDuration.erase(itr);
12353 else
12354 ++itr;
12358 void Player::RemoveAllEnchantments(EnchantmentSlot slot)
12360 // remove enchantments from equipped items first to clean up the m_enchantDuration list
12361 for (EnchantDurationList::iterator itr = m_enchantDuration.begin(), next; itr != m_enchantDuration.end(); itr = next)
12363 next = itr;
12364 if (itr->slot == slot)
12366 if (itr->item && itr->item->GetEnchantmentId(slot))
12368 // remove from stats
12369 ApplyEnchantment(itr->item, slot, false, false);
12370 // remove visual
12371 itr->item->ClearEnchantment(slot);
12373 // remove from update list
12374 next = m_enchantDuration.erase(itr);
12376 else
12377 ++next;
12380 // remove enchants from inventory items
12381 // NOTE: no need to remove these from stats, since these aren't equipped
12382 // in inventory
12383 for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
12384 if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
12385 if (pItem->GetEnchantmentId(slot))
12386 pItem->ClearEnchantment(slot);
12388 // in inventory bags
12389 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
12390 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
12391 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
12392 if (Item* pItem = pBag->GetItemByPos(j))
12393 if (pItem->GetEnchantmentId(slot))
12394 pItem->ClearEnchantment(slot);
12397 // duration == 0 will remove item enchant
12398 void Player::AddEnchantmentDuration(Item* item, EnchantmentSlot slot, uint32 duration)
12400 if (!item)
12401 return;
12403 if (slot >= MAX_ENCHANTMENT_SLOT)
12404 return;
12406 for (EnchantDurationList::iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr)
12408 if (itr->item == item && itr->slot == slot)
12410 itr->item->SetEnchantmentDuration(itr->slot, itr->leftduration);
12411 m_enchantDuration.erase(itr);
12412 break;
12415 if (item && duration > 0)
12417 GetSession()->SendItemEnchantTimeUpdate(GetObjectGuid(), item->GetObjectGuid(), slot, uint32(duration / 1000));
12418 m_enchantDuration.push_back(EnchantDuration(item, slot, duration));
12422 void Player::ApplyEnchantment(Item* item, bool apply)
12424 for (uint32 slot = 0; slot < MAX_ENCHANTMENT_SLOT; ++slot)
12425 ApplyEnchantment(item, EnchantmentSlot(slot), apply);
12428 void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool apply_dur, bool ignore_condition)
12430 if (slot > PRISMATIC_ENCHANTMENT_SLOT && slot < PROP_ENCHANTMENT_SLOT_0)
12431 return;
12433 if (!item)
12434 return;
12436 if (!item->IsEquipped())
12437 return;
12439 if (slot >= MAX_ENCHANTMENT_SLOT)
12440 return;
12442 uint32 enchant_id = item->GetEnchantmentId(slot);
12443 if (!enchant_id)
12444 return;
12446 SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
12447 if (!pEnchant)
12448 return;
12450 if (!ignore_condition && pEnchant->EnchantmentCondition && !EnchantmentFitsRequirements(pEnchant->EnchantmentCondition, -1))
12451 return;
12453 if (!item->IsBroken())
12455 for (int s = 0; s < 3; ++s)
12457 uint32 enchant_display_type = pEnchant->type[s];
12458 uint32 enchant_amount = pEnchant->amount[s];
12459 uint32 enchant_spell_id = pEnchant->spellid[s];
12461 switch (enchant_display_type)
12463 case ITEM_ENCHANTMENT_TYPE_NONE:
12464 break;
12465 case ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL:
12466 // processed in Player::CastItemCombatSpell
12467 break;
12468 case ITEM_ENCHANTMENT_TYPE_DAMAGE:
12469 if (item->GetSlot() == EQUIPMENT_SLOT_MAINHAND)
12470 HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_VALUE, float(enchant_amount), apply);
12471 else if (item->GetSlot() == EQUIPMENT_SLOT_OFFHAND)
12472 HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_VALUE, float(enchant_amount), apply);
12473 else if (item->GetSlot() == EQUIPMENT_SLOT_RANGED)
12474 HandleStatModifier(UNIT_MOD_DAMAGE_RANGED, TOTAL_VALUE, float(enchant_amount), apply);
12475 break;
12476 case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL:
12478 if (enchant_spell_id)
12480 if (apply)
12482 int32 basepoints = 0;
12483 // Random Property Exist - try found basepoints for spell (basepoints depends from item suffix factor)
12484 if (item->GetItemRandomPropertyId())
12486 ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId()));
12487 if (item_rand)
12489 // Search enchant_amount
12490 for (int k = 0; k < 3; ++k)
12492 if (item_rand->enchant_id[k] == enchant_id)
12494 basepoints = int32((item_rand->prefix[k] * item->GetItemSuffixFactor()) / 10000);
12495 break;
12500 // Cast custom spell vs all equal basepoints got from enchant_amount
12501 if (basepoints)
12502 CastCustomSpell(this, enchant_spell_id, &basepoints, &basepoints, &basepoints, true, item);
12503 else
12504 CastSpell(this, enchant_spell_id, true, item);
12506 else
12507 RemoveAurasDueToItemSpell(item, enchant_spell_id);
12509 break;
12511 case ITEM_ENCHANTMENT_TYPE_RESISTANCE:
12512 if (!enchant_amount)
12514 ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId()));
12515 if (item_rand)
12517 for (int k = 0; k < 3; ++k)
12519 if (item_rand->enchant_id[k] == enchant_id)
12521 enchant_amount = uint32((item_rand->prefix[k] * item->GetItemSuffixFactor()) / 10000);
12522 break;
12528 HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + enchant_spell_id), TOTAL_VALUE, float(enchant_amount), apply);
12529 break;
12530 case ITEM_ENCHANTMENT_TYPE_STAT:
12532 if (!enchant_amount)
12534 ItemRandomSuffixEntry const* item_rand_suffix = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId()));
12535 if (item_rand_suffix)
12537 for (int k = 0; k < 3; ++k)
12539 if (item_rand_suffix->enchant_id[k] == enchant_id)
12541 enchant_amount = uint32((item_rand_suffix->prefix[k] * item->GetItemSuffixFactor()) / 10000);
12542 break;
12548 DEBUG_LOG("Adding %u to stat nb %u", enchant_amount, enchant_spell_id);
12549 switch (enchant_spell_id)
12551 /*case ITEM_MOD_MANA:
12552 DEBUG_LOG("+ %u MANA", enchant_amount);
12553 HandleStatModifier(UNIT_MOD_MANA, BASE_VALUE, float(enchant_amount), apply);
12554 break;*/
12555 case ITEM_MOD_HEALTH:
12556 DEBUG_LOG("+ %u HEALTH", enchant_amount);
12557 HandleStatModifier(UNIT_MOD_HEALTH, BASE_VALUE, float(enchant_amount), apply);
12558 break;
12559 case ITEM_MOD_AGILITY:
12560 DEBUG_LOG("+ %u AGILITY", enchant_amount);
12561 HandleStatModifier(UNIT_MOD_STAT_AGILITY, TOTAL_VALUE, float(enchant_amount), apply);
12562 ApplyStatBuffMod(STAT_AGILITY, float(enchant_amount), apply);
12563 break;
12564 case ITEM_MOD_STRENGTH:
12565 DEBUG_LOG("+ %u STRENGTH", enchant_amount);
12566 HandleStatModifier(UNIT_MOD_STAT_STRENGTH, TOTAL_VALUE, float(enchant_amount), apply);
12567 ApplyStatBuffMod(STAT_STRENGTH, float(enchant_amount), apply);
12568 break;
12569 case ITEM_MOD_INTELLECT:
12570 DEBUG_LOG("+ %u INTELLECT", enchant_amount);
12571 HandleStatModifier(UNIT_MOD_STAT_INTELLECT, TOTAL_VALUE, float(enchant_amount), apply);
12572 ApplyStatBuffMod(STAT_INTELLECT, float(enchant_amount), apply);
12573 break;
12574 case ITEM_MOD_SPIRIT:
12575 DEBUG_LOG("+ %u SPIRIT", enchant_amount);
12576 HandleStatModifier(UNIT_MOD_STAT_SPIRIT, TOTAL_VALUE, float(enchant_amount), apply);
12577 ApplyStatBuffMod(STAT_SPIRIT, float(enchant_amount), apply);
12578 break;
12579 case ITEM_MOD_STAMINA:
12580 DEBUG_LOG("+ %u STAMINA", enchant_amount);
12581 HandleStatModifier(UNIT_MOD_STAT_STAMINA, TOTAL_VALUE, float(enchant_amount), apply);
12582 ApplyStatBuffMod(STAT_STAMINA, float(enchant_amount), apply);
12583 break;
12584 case ITEM_MOD_DODGE_RATING:
12585 ApplyRatingMod(CR_DODGE, enchant_amount, apply);
12586 DEBUG_LOG("+ %u DODGE", enchant_amount);
12587 break;
12588 case ITEM_MOD_PARRY_RATING:
12589 ApplyRatingMod(CR_PARRY, enchant_amount, apply);
12590 DEBUG_LOG("+ %u PARRY", enchant_amount);
12591 break;
12592 case ITEM_MOD_CRIT_RANGED_RATING:
12593 ApplyRatingMod(CR_CRIT_RANGED, enchant_amount, apply);
12594 DEBUG_LOG("+ %u RANGED_CRIT", enchant_amount);
12595 break;
12596 case ITEM_MOD_HIT_RATING:
12597 ApplyRatingMod(CR_HIT_MELEE, enchant_amount, apply);
12598 ApplyRatingMod(CR_HIT_RANGED, enchant_amount, apply);
12599 ApplyRatingMod(CR_HIT_SPELL, enchant_amount, apply);
12600 DEBUG_LOG("+ %u HIT", enchant_amount);
12601 break;
12602 case ITEM_MOD_CRIT_RATING:
12603 ApplyRatingMod(CR_CRIT_MELEE, enchant_amount, apply);
12604 ApplyRatingMod(CR_CRIT_RANGED, enchant_amount, apply);
12605 ApplyRatingMod(CR_CRIT_SPELL, enchant_amount, apply);
12606 DEBUG_LOG("+ %u CRITICAL", enchant_amount);
12607 break;
12608 case ITEM_MOD_RESILIENCE_RATING:
12609 ApplyRatingMod(CR_RESILIENCE_DAMAGE_TAKEN, enchant_amount, apply);
12610 DEBUG_LOG("+ %u RESILIENCE", enchant_amount);
12611 break;
12612 case ITEM_MOD_HASTE_RATING:
12613 ApplyRatingMod(CR_HASTE_MELEE, enchant_amount, apply);
12614 ApplyRatingMod(CR_HASTE_RANGED, enchant_amount, apply);
12615 ApplyRatingMod(CR_HASTE_SPELL, enchant_amount, apply);
12616 DEBUG_LOG("+ %u HASTE", enchant_amount);
12617 break;
12618 case ITEM_MOD_EXPERTISE_RATING:
12619 ApplyRatingMod(CR_EXPERTISE, enchant_amount, apply);
12620 DEBUG_LOG("+ %u EXPERTISE", enchant_amount);
12621 break;
12622 case ITEM_MOD_ATTACK_POWER:
12623 HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(enchant_amount), apply);
12624 HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(enchant_amount), apply);
12625 DEBUG_LOG("+ %u ATTACK_POWER", enchant_amount);
12626 break;
12627 case ITEM_MOD_RANGED_ATTACK_POWER:
12628 HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(enchant_amount), apply);
12629 DEBUG_LOG("+ %u RANGED_ATTACK_POWER", enchant_amount);
12630 break;
12631 case ITEM_MOD_SPELL_POWER:
12632 ApplySpellPowerBonus(enchant_amount, apply);
12633 DEBUG_LOG("+ %u SPELL_POWER", enchant_amount);
12634 break;
12635 case ITEM_MOD_HEALTH_REGEN:
12636 ApplyHealthRegenBonus(enchant_amount, apply);
12637 DEBUG_LOG("+ %u HEALTH_REGENERATION", enchant_amount);
12638 break;
12639 case ITEM_MOD_SPELL_PENETRATION:
12640 ApplyModInt32Value(PLAYER_FIELD_MOD_TARGET_RESISTANCE, -int32(enchant_amount), apply);
12641 m_spellPenetrationItemMod += apply ? enchant_amount : -int32(enchant_amount);
12642 DEBUG_LOG("+ %u SPELL_PENETRATION", -int32(enchant_amount));
12643 break;
12644 case ITEM_MOD_MASTERY_RATING:
12645 ApplyRatingMod(CR_MASTERY, enchant_amount, apply);
12646 DEBUG_LOG("+ %u MASTERY_RATING", enchant_amount);
12647 break;
12648 // deprecated
12649 case ITEM_MOD_DEFENSE_SKILL_RATING:
12650 case ITEM_MOD_BLOCK_RATING:
12651 case ITEM_MOD_HIT_MELEE_RATING:
12652 case ITEM_MOD_HIT_RANGED_RATING:
12653 case ITEM_MOD_HIT_SPELL_RATING:
12654 case ITEM_MOD_CRIT_MELEE_RATING:
12655 case ITEM_MOD_CRIT_SPELL_RATING:
12656 case ITEM_MOD_HIT_TAKEN_MELEE_RATING:
12657 case ITEM_MOD_HIT_TAKEN_RANGED_RATING:
12658 case ITEM_MOD_HIT_TAKEN_SPELL_RATING:
12659 case ITEM_MOD_CRIT_TAKEN_MELEE_RATING:
12660 case ITEM_MOD_CRIT_TAKEN_RANGED_RATING:
12661 case ITEM_MOD_CRIT_TAKEN_SPELL_RATING:
12662 case ITEM_MOD_HASTE_MELEE_RATING:
12663 case ITEM_MOD_HASTE_RANGED_RATING:
12664 case ITEM_MOD_HASTE_SPELL_RATING:
12665 case ITEM_MOD_HIT_TAKEN_RATING:
12666 case ITEM_MOD_CRIT_TAKEN_RATING:
12667 case ITEM_MOD_FERAL_ATTACK_POWER:
12668 case ITEM_MOD_SPELL_HEALING_DONE:
12669 case ITEM_MOD_SPELL_DAMAGE_DONE:
12670 case ITEM_MOD_MANA_REGENERATION:
12671 case ITEM_MOD_ARMOR_PENETRATION_RATING:
12672 case ITEM_MOD_BLOCK_VALUE:
12673 default:
12674 break;
12676 break;
12678 case ITEM_ENCHANTMENT_TYPE_TOTEM: // Shaman Rockbiter Weapon
12680 if (getClass() == CLASS_SHAMAN)
12682 float addValue = 0.0f;
12683 if (item->GetSlot() == EQUIPMENT_SLOT_MAINHAND)
12685 addValue = float(enchant_amount * item->GetProto()->Delay / 1000.0f);
12686 HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_VALUE, addValue, apply);
12688 else if (item->GetSlot() == EQUIPMENT_SLOT_OFFHAND)
12690 addValue = float(enchant_amount * item->GetProto()->Delay / 1000.0f);
12691 HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_VALUE, addValue, apply);
12694 break;
12696 case ITEM_ENCHANTMENT_TYPE_USE_SPELL:
12697 // processed in Player::CastItemUseSpell
12698 break;
12699 case ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET:
12700 // nothing do..
12701 break;
12702 default:
12703 sLog.outError("Unknown item enchantment (id = %d) display type: %d", enchant_id, enchant_display_type);
12704 break;
12705 } /*switch(enchant_display_type)*/
12706 } /*for*/
12709 // visualize enchantment at player and equipped items
12710 if (slot == PERM_ENCHANTMENT_SLOT)
12711 SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (item->GetSlot() * 2), 0, apply ? item->GetEnchantmentId(slot) : 0);
12713 if (slot == TEMP_ENCHANTMENT_SLOT)
12714 SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (item->GetSlot() * 2), 1, apply ? item->GetEnchantmentId(slot) : 0);
12717 if (apply_dur)
12719 if (apply)
12721 // set duration
12722 uint32 duration = item->GetEnchantmentDuration(slot);
12723 if (duration > 0)
12724 AddEnchantmentDuration(item, slot, duration);
12726 else
12728 // duration == 0 will remove EnchantDuration
12729 AddEnchantmentDuration(item, slot, 0);
12734 void Player::SendEnchantmentDurations()
12736 for (EnchantDurationList::const_iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr)
12738 GetSession()->SendItemEnchantTimeUpdate(GetObjectGuid(), itr->item->GetObjectGuid(), itr->slot, uint32(itr->leftduration) / 1000);
12742 void Player::SendItemDurations()
12744 for (ItemDurationList::const_iterator itr = m_itemDuration.begin(); itr != m_itemDuration.end(); ++itr)
12746 (*itr)->SendTimeUpdate(this);
12750 void Player::SendNewItem(Item* item, uint32 count, bool received, bool created, bool broadcast)
12752 if (!item) // prevent crash
12753 return;
12755 // last check 2.0.10
12756 WorldPacket data(SMSG_ITEM_PUSH_RESULT, (8 + 4 + 4 + 4 + 1 + 4 + 4 + 4 + 4 + 4));
12757 data << GetObjectGuid(); // player GUID
12758 data << uint32(received); // 0=looted, 1=from npc
12759 data << uint32(created); // 0=received, 1=created
12760 data << uint32(1); // IsShowChatMessage
12761 data << uint8(item->GetBagSlot()); // bagslot
12762 // item slot, but when added to stack: 0xFFFFFFFF
12763 data << uint32((item->GetCount() == count) ? item->GetSlot() : -1);
12764 data << uint32(item->GetEntry()); // item id
12765 data << uint32(item->GetItemSuffixFactor()); // SuffixFactor
12766 data << uint32(item->GetItemRandomPropertyId()); // random item property id
12767 data << uint32(count); // count of items
12768 data << uint32(GetItemCount(item->GetEntry())); // count of items in inventory
12770 if (broadcast && GetGroup())
12771 GetGroup()->BroadcastPacket(&data, true);
12772 else
12773 GetSession()->SendPacket(&data);
12776 /*********************************************************/
12777 /*** GOSSIP SYSTEM ***/
12778 /*********************************************************/
12780 void Player::PrepareGossipMenu(WorldObject* pSource, uint32 menuId)
12782 PlayerMenu* pMenu = PlayerTalkClass;
12783 pMenu->ClearMenus();
12785 pMenu->GetGossipMenu().SetMenuId(menuId);
12787 GossipMenuItemsMapBounds pMenuItemBounds = sObjectMgr.GetGossipMenuItemsMapBounds(menuId);
12789 // prepares quest menu when true
12790 bool canSeeQuests = menuId == GetDefaultGossipMenuForSource(pSource);
12792 // if canSeeQuests (the default, top level menu) and no menu options exist for this, use options from default options
12793 if (pMenuItemBounds.first == pMenuItemBounds.second && canSeeQuests)
12794 pMenuItemBounds = sObjectMgr.GetGossipMenuItemsMapBounds(0);
12796 bool canTalkToCredit = pSource->GetTypeId() == TYPEID_UNIT;
12798 for (GossipMenuItemsMap::const_iterator itr = pMenuItemBounds.first; itr != pMenuItemBounds.second; ++itr)
12800 bool hasMenuItem = true;
12802 if (!isGameMaster()) // Let GM always see menu items regardless of conditions
12804 if (itr->second.conditionId && !sObjectMgr.IsPlayerMeetToCondition(this, itr->second.conditionId))
12806 if (itr->second.option_id == GOSSIP_OPTION_QUESTGIVER)
12807 canSeeQuests = false;
12808 continue;
12812 if (pSource->GetTypeId() == TYPEID_UNIT)
12814 Creature* pCreature = (Creature*)pSource;
12816 uint32 npcflags = pCreature->GetUInt32Value(UNIT_NPC_FLAGS);
12818 if (!(itr->second.npc_option_npcflag & npcflags))
12819 continue;
12821 switch (itr->second.option_id)
12823 case GOSSIP_OPTION_GOSSIP:
12824 if (itr->second.action_menu_id != 0) // has sub menu (or close gossip), so do not "talk" with this NPC yet
12825 canTalkToCredit = false;
12826 break;
12827 case GOSSIP_OPTION_QUESTGIVER:
12828 hasMenuItem = false;
12829 break;
12830 case GOSSIP_OPTION_ARMORER:
12831 hasMenuItem = false; // added in special mode
12832 break;
12833 case GOSSIP_OPTION_SPIRITHEALER:
12834 if (!isDead())
12835 hasMenuItem = false;
12836 break;
12837 case GOSSIP_OPTION_VENDOR:
12839 VendorItemData const* vItems = pCreature->GetVendorItems();
12840 VendorItemData const* tItems = pCreature->GetVendorTemplateItems();
12841 if ((!vItems || vItems->Empty()) && (!tItems || tItems->Empty()))
12843 sLog.outErrorDb("Creature %u (Entry: %u) have UNIT_NPC_FLAG_VENDOR but have empty trading item list.", pCreature->GetGUIDLow(), pCreature->GetEntry());
12844 hasMenuItem = false;
12846 break;
12848 case GOSSIP_OPTION_TRAINER:
12849 // pet trainers not have spells in fact now
12850 /* FIXME: gossip menu with single unlearn pet talents option not show by some reason
12851 if (pCreature->GetCreatureInfo()->trainer_type == TRAINER_TYPE_PETS)
12852 hasMenuItem = false;
12853 else */
12854 if (!pCreature->IsTrainerOf(this, false))
12855 hasMenuItem = false;
12856 break;
12857 case GOSSIP_OPTION_UNLEARNTALENTS:
12858 if (!pCreature->CanTrainAndResetTalentsOf(this))
12859 hasMenuItem = false;
12860 break;
12861 case GOSSIP_OPTION_UNLEARNPETSKILLS:
12862 if (pCreature->GetCreatureInfo()->trainer_type != TRAINER_TYPE_PETS || pCreature->GetCreatureInfo()->trainer_class != CLASS_HUNTER)
12863 hasMenuItem = false;
12864 else if (Pet* pet = GetPet())
12866 if (pet->getPetType() != HUNTER_PET || pet->m_spells.size() <= 1)
12867 hasMenuItem = false;
12869 else
12870 hasMenuItem = false;
12871 break;
12872 case GOSSIP_OPTION_TAXIVENDOR:
12873 if (GetSession()->SendLearnNewTaxiNode(pCreature))
12874 return;
12875 break;
12876 case GOSSIP_OPTION_BATTLEFIELD:
12877 if (!pCreature->CanInteractWithBattleMaster(this, false))
12878 hasMenuItem = false;
12879 break;
12880 case GOSSIP_OPTION_STABLEPET:
12881 if (getClass() != CLASS_HUNTER)
12882 hasMenuItem = false;
12883 break;
12884 case GOSSIP_OPTION_SPIRITGUIDE:
12885 case GOSSIP_OPTION_INNKEEPER:
12886 case GOSSIP_OPTION_BANKER:
12887 case GOSSIP_OPTION_PETITIONER:
12888 case GOSSIP_OPTION_TABARDDESIGNER:
12889 case GOSSIP_OPTION_AUCTIONEER:
12890 case GOSSIP_OPTION_MAILBOX:
12891 break; // no checks
12892 default:
12893 sLog.outErrorDb("Creature entry %u have unknown gossip option %u for menu %u", pCreature->GetEntry(), itr->second.option_id, itr->second.menu_id);
12894 hasMenuItem = false;
12895 break;
12898 else if (pSource->GetTypeId() == TYPEID_GAMEOBJECT)
12900 GameObject* pGo = (GameObject*)pSource;
12902 switch (itr->second.option_id)
12904 case GOSSIP_OPTION_QUESTGIVER:
12905 hasMenuItem = false;
12906 break;
12907 case GOSSIP_OPTION_GOSSIP:
12908 if (pGo->GetGoType() != GAMEOBJECT_TYPE_QUESTGIVER && pGo->GetGoType() != GAMEOBJECT_TYPE_GOOBER)
12909 hasMenuItem = false;
12910 break;
12911 default:
12912 hasMenuItem = false;
12913 break;
12917 if (hasMenuItem)
12919 std::string strOptionText = itr->second.option_text;
12920 std::string strBoxText = itr->second.box_text;
12922 int loc_idx = GetSession()->GetSessionDbLocaleIndex();
12924 if (loc_idx >= 0)
12926 uint32 idxEntry = MAKE_PAIR32(menuId, itr->second.id);
12928 if (GossipMenuItemsLocale const* no = sObjectMgr.GetGossipMenuItemsLocale(idxEntry))
12930 if (no->OptionText.size() > (size_t)loc_idx && !no->OptionText[loc_idx].empty())
12931 strOptionText = no->OptionText[loc_idx];
12933 if (no->BoxText.size() > (size_t)loc_idx && !no->BoxText[loc_idx].empty())
12934 strBoxText = no->BoxText[loc_idx];
12938 pMenu->GetGossipMenu().AddMenuItem(itr->second.option_icon, strOptionText, 0, itr->second.option_id, strBoxText, itr->second.box_money, itr->second.box_coded);
12939 pMenu->GetGossipMenu().AddGossipMenuItemData(itr->second.action_menu_id, itr->second.action_poi_id, itr->second.action_script_id);
12943 if (canSeeQuests)
12944 PrepareQuestMenu(pSource->GetObjectGuid());
12946 if (canTalkToCredit)
12948 if (pSource->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP) && !(((Creature*)pSource)->GetCreatureInfo()->flags_extra & CREATURE_FLAG_EXTRA_NO_TALKTO_CREDIT))
12949 TalkedToCreature(pSource->GetEntry(), pSource->GetObjectGuid());
12952 // some gossips aren't handled in normal way ... so we need to do it this way .. TODO: handle it in normal way ;-)
12953 /*if (pMenu->Empty())
12955 if (pCreature->HasFlag(UNIT_NPC_FLAGS,UNIT_NPC_FLAG_TRAINER))
12957 // output error message if need
12958 pCreature->IsTrainerOf(this, true);
12961 if (pCreature->HasFlag(UNIT_NPC_FLAGS,UNIT_NPC_FLAG_BATTLEMASTER))
12963 // output error message if need
12964 pCreature->CanInteractWithBattleMaster(this, true);
12969 void Player::SendPreparedGossip(WorldObject* pSource)
12971 if (!pSource)
12972 return;
12974 if (pSource->GetTypeId() == TYPEID_UNIT)
12976 // in case no gossip flag and quest menu not empty, open quest menu (client expect gossip menu with this flag)
12977 if (!((Creature*)pSource)->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP) && !PlayerTalkClass->GetQuestMenu().Empty())
12979 SendPreparedQuest(pSource->GetObjectGuid());
12980 return;
12983 else if (pSource->GetTypeId() == TYPEID_GAMEOBJECT)
12985 // probably need to find a better way here
12986 if (!PlayerTalkClass->GetGossipMenu().GetMenuId() && !PlayerTalkClass->GetQuestMenu().Empty())
12988 SendPreparedQuest(pSource->GetObjectGuid());
12989 return;
12993 // in case non empty gossip menu (that not included quests list size) show it
12994 // (quest entries from quest menu will be included in list)
12996 uint32 textId = GetGossipTextId(pSource);
12998 if (uint32 menuId = PlayerTalkClass->GetGossipMenu().GetMenuId())
12999 textId = GetGossipTextId(menuId, pSource);
13001 PlayerTalkClass->SendGossipMenu(textId, pSource->GetObjectGuid());
13004 void Player::OnGossipSelect(WorldObject* pSource, uint32 gossipListId, uint32 menuId)
13006 GossipMenu& gossipmenu = PlayerTalkClass->GetGossipMenu();
13008 if (gossipListId >= gossipmenu.MenuItemCount())
13009 return;
13011 // if not same, then something funky is going on
13012 if (menuId != gossipmenu.GetMenuId())
13013 return;
13015 GossipMenuItem const& menu_item = gossipmenu.GetItem(gossipListId);
13017 uint32 gossipOptionId = menu_item.m_gOptionId;
13018 ObjectGuid guid = pSource->GetObjectGuid();
13019 uint32 moneyTake = menu_item.m_gBoxMoney;
13021 // if this function called and player have money for pay MoneyTake or cheating, proccess both cases
13022 if (moneyTake > 0)
13024 if (GetMoney() >= moneyTake)
13025 ModifyMoney(-int64(moneyTake));
13026 else
13027 return; // cheating
13030 if (pSource->GetTypeId() == TYPEID_GAMEOBJECT)
13032 if (gossipOptionId > GOSSIP_OPTION_QUESTGIVER)
13034 sLog.outError("Player guid %u request invalid gossip option for GameObject entry %u", GetGUIDLow(), pSource->GetEntry());
13035 return;
13039 GossipMenuItemData pMenuData = gossipmenu.GetItemData(gossipListId);
13041 switch (gossipOptionId)
13043 case GOSSIP_OPTION_GOSSIP:
13045 if (pMenuData.m_gAction_poi)
13046 PlayerTalkClass->SendPointOfInterest(pMenuData.m_gAction_poi);
13048 // send new menu || close gossip || stay at current menu
13049 if (pMenuData.m_gAction_menu > 0)
13051 PrepareGossipMenu(pSource, uint32(pMenuData.m_gAction_menu));
13052 SendPreparedGossip(pSource);
13054 else if (pMenuData.m_gAction_menu < 0)
13056 PlayerTalkClass->CloseGossip();
13057 TalkedToCreature(pSource->GetEntry(), pSource->GetObjectGuid());
13060 break;
13062 case GOSSIP_OPTION_SPIRITHEALER:
13063 if (isDead())
13064 ((Creature*)pSource)->CastSpell(((Creature*)pSource), 17251, true, NULL, NULL, GetObjectGuid());
13065 break;
13066 case GOSSIP_OPTION_QUESTGIVER:
13067 PrepareQuestMenu(guid);
13068 SendPreparedQuest(guid);
13069 break;
13070 case GOSSIP_OPTION_VENDOR:
13071 case GOSSIP_OPTION_ARMORER:
13072 GetSession()->SendListInventory(guid);
13073 break;
13074 case GOSSIP_OPTION_STABLEPET:
13075 GetSession()->SendStablePet(guid);
13076 break;
13077 case GOSSIP_OPTION_TRAINER:
13078 GetSession()->SendTrainerList(guid);
13079 break;
13080 case GOSSIP_OPTION_UNLEARNTALENTS:
13081 PlayerTalkClass->CloseGossip();
13082 SendTalentWipeConfirm(guid);
13083 break;
13084 case GOSSIP_OPTION_UNLEARNPETSKILLS:
13085 PlayerTalkClass->CloseGossip();
13086 SendPetSkillWipeConfirm();
13087 break;
13088 case GOSSIP_OPTION_TAXIVENDOR:
13089 GetSession()->SendTaxiMenu(((Creature*)pSource));
13090 break;
13091 case GOSSIP_OPTION_INNKEEPER:
13092 PlayerTalkClass->CloseGossip();
13093 SetBindPoint(guid);
13094 break;
13095 case GOSSIP_OPTION_BANKER:
13096 GetSession()->SendShowBank(guid);
13097 break;
13098 case GOSSIP_OPTION_PETITIONER:
13099 PlayerTalkClass->CloseGossip();
13100 GetSession()->SendPetitionShowList(guid);
13101 break;
13102 case GOSSIP_OPTION_TABARDDESIGNER:
13103 PlayerTalkClass->CloseGossip();
13104 GetSession()->SendTabardVendorActivate(guid);
13105 break;
13106 case GOSSIP_OPTION_AUCTIONEER:
13107 GetSession()->SendAuctionHello(((Creature*)pSource));
13108 break;
13109 case GOSSIP_OPTION_MAILBOX:
13110 PlayerTalkClass->CloseGossip();
13111 GetSession()->SendShowMailBox(guid);
13112 break;
13113 case GOSSIP_OPTION_SPIRITGUIDE:
13114 PrepareGossipMenu(pSource);
13115 SendPreparedGossip(pSource);
13116 break;
13117 case GOSSIP_OPTION_BATTLEFIELD:
13119 BattleGroundTypeId bgTypeId = sBattleGroundMgr.GetBattleMasterBG(pSource->GetEntry());
13121 if (bgTypeId == BATTLEGROUND_TYPE_NONE)
13123 sLog.outError("a user (guid %u) requested battlegroundlist from a npc who is no battlemaster", GetGUIDLow());
13124 return;
13127 GetSession()->SendBattlegGroundList(guid, bgTypeId);
13128 break;
13132 if (pMenuData.m_gAction_script)
13134 if (pSource->GetTypeId() == TYPEID_UNIT)
13135 GetMap()->ScriptsStart(sGossipScripts, pMenuData.m_gAction_script, pSource, this);
13136 else if (pSource->GetTypeId() == TYPEID_GAMEOBJECT)
13137 GetMap()->ScriptsStart(sGossipScripts, pMenuData.m_gAction_script, this, pSource);
13141 uint32 Player::GetGossipTextId(WorldObject* pSource)
13143 if (!pSource || pSource->GetTypeId() != TYPEID_UNIT)
13144 return DEFAULT_GOSSIP_MESSAGE;
13146 if (uint32 pos = sObjectMgr.GetNpcGossip(((Creature*)pSource)->GetGUIDLow()))
13147 return pos;
13149 return DEFAULT_GOSSIP_MESSAGE;
13152 uint32 Player::GetGossipTextId(uint32 menuId, WorldObject* pSource)
13154 uint32 textId = DEFAULT_GOSSIP_MESSAGE;
13156 if (!menuId)
13157 return textId;
13159 uint32 scriptId = 0;
13160 uint32 lastConditionId = 0;
13162 GossipMenusMapBounds pMenuBounds = sObjectMgr.GetGossipMenusMapBounds(menuId);
13163 for (GossipMenusMap::const_iterator itr = pMenuBounds.first; itr != pMenuBounds.second; ++itr)
13165 // Take the text that has the highest conditionId of all fitting
13166 // No condition and no text with condition found OR higher and fitting condition found
13167 if ((!itr->second.conditionId && !lastConditionId) ||
13168 (itr->second.conditionId > lastConditionId && sObjectMgr.IsPlayerMeetToCondition(this, itr->second.conditionId)))
13170 lastConditionId = itr->second.conditionId;
13171 textId = itr->second.text_id;
13172 scriptId = itr->second.script_id;
13176 // Start related script
13177 if (scriptId)
13178 GetMap()->ScriptsStart(sGossipScripts, scriptId, this, pSource);
13180 return textId;
13183 uint32 Player::GetDefaultGossipMenuForSource(WorldObject* pSource)
13185 if (pSource->GetTypeId() == TYPEID_UNIT)
13186 return ((Creature*)pSource)->GetCreatureInfo()->GossipMenuId;
13187 else if (pSource->GetTypeId() == TYPEID_GAMEOBJECT)
13188 return((GameObject*)pSource)->GetGOInfo()->GetGossipMenuId();
13190 return 0;
13193 /*********************************************************/
13194 /*** QUEST SYSTEM ***/
13195 /*********************************************************/
13197 void Player::PrepareQuestMenu(ObjectGuid guid)
13199 QuestRelationsMapBounds rbounds;
13200 QuestRelationsMapBounds irbounds;
13202 // pets also can have quests
13203 if (Creature* pCreature = GetMap()->GetAnyTypeCreature(guid))
13205 rbounds = sObjectMgr.GetCreatureQuestRelationsMapBounds(pCreature->GetEntry());
13206 irbounds = sObjectMgr.GetCreatureQuestInvolvedRelationsMapBounds(pCreature->GetEntry());
13208 else
13210 // we should obtain map pointer from GetMap() in 99% of cases. Special case
13211 // only for quests which cast teleport spells on player
13212 Map* _map = IsInWorld() ? GetMap() : sMapMgr.FindMap(GetMapId(), GetInstanceId());
13213 MANGOS_ASSERT(_map);
13215 if (GameObject* pGameObject = _map->GetGameObject(guid))
13217 rbounds = sObjectMgr.GetGOQuestRelationsMapBounds(pGameObject->GetEntry());
13218 irbounds = sObjectMgr.GetGOQuestInvolvedRelationsMapBounds(pGameObject->GetEntry());
13220 else
13221 return;
13224 QuestMenu& qm = PlayerTalkClass->GetQuestMenu();
13225 qm.ClearMenu();
13227 for (QuestRelationsMap::const_iterator itr = irbounds.first; itr != irbounds.second; ++itr)
13229 uint32 quest_id = itr->second;
13231 Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id);
13233 if (!pQuest || !pQuest->IsActive())
13234 continue;
13236 QuestStatus status = GetQuestStatus(quest_id);
13238 if (status == QUEST_STATUS_COMPLETE && !GetQuestRewardStatus(quest_id))
13239 qm.AddMenuItem(quest_id, 4);
13240 else if (status == QUEST_STATUS_INCOMPLETE)
13241 qm.AddMenuItem(quest_id, 4);
13242 else if (status == QUEST_STATUS_AVAILABLE)
13243 qm.AddMenuItem(quest_id, 2);
13246 for (QuestRelationsMap::const_iterator itr = rbounds.first; itr != rbounds.second; ++itr)
13248 uint32 quest_id = itr->second;
13250 Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id);
13252 if (!pQuest || !pQuest->IsActive())
13253 continue;
13255 QuestStatus status = GetQuestStatus(quest_id);
13257 if (pQuest->IsAutoComplete() && CanTakeQuest(pQuest, false))
13258 qm.AddMenuItem(quest_id, 4);
13259 else if (status == QUEST_STATUS_NONE && CanTakeQuest(pQuest, false))
13260 qm.AddMenuItem(quest_id, 2);
13264 void Player::SendPreparedQuest(ObjectGuid guid)
13266 QuestMenu& questMenu = PlayerTalkClass->GetQuestMenu();
13268 if (questMenu.Empty())
13269 return;
13271 QuestMenuItem const& qmi0 = questMenu.GetItem(0);
13273 uint32 icon = qmi0.m_qIcon;
13275 // single element case
13276 if (questMenu.MenuItemCount() == 1)
13278 // Auto open -- maybe also should verify there is no greeting
13279 uint32 quest_id = qmi0.m_qId;
13280 Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id);
13282 if (pQuest)
13284 if (icon == 4 && !GetQuestRewardStatus(quest_id))
13285 PlayerTalkClass->SendQuestGiverRequestItems(pQuest, guid, CanRewardQuest(pQuest, false), true);
13286 else if (icon == 4)
13287 PlayerTalkClass->SendQuestGiverRequestItems(pQuest, guid, CanRewardQuest(pQuest, false), true);
13288 // Send completable on repeatable and autoCompletable quest if player don't have quest
13289 // TODO: verify if check for !pQuest->IsDaily() is really correct (possibly not)
13290 else if (pQuest->IsAutoComplete() && pQuest->IsRepeatable() && !pQuest->IsDailyOrWeekly())
13291 PlayerTalkClass->SendQuestGiverRequestItems(pQuest, guid, CanCompleteRepeatableQuest(pQuest), true);
13292 else
13293 PlayerTalkClass->SendQuestGiverQuestDetails(pQuest, guid, true);
13296 // multiply entries
13297 else
13299 QEmote qe;
13300 qe._Delay = 0;
13301 qe._Emote = 0;
13302 std::string title = "";
13304 // need pet case for some quests
13305 if (Creature* pCreature = GetMap()->GetAnyTypeCreature(guid))
13307 uint32 textid = GetGossipTextId(pCreature);
13309 GossipText const* gossiptext = sObjectMgr.GetGossipText(textid);
13310 if (!gossiptext)
13312 qe._Delay = 0; // TEXTEMOTE_MESSAGE; // zyg: player emote
13313 qe._Emote = 0; // TEXTEMOTE_HELLO; // zyg: NPC emote
13314 title = "";
13316 else
13318 qe = gossiptext->Options[0].Emotes[0];
13320 int loc_idx = GetSession()->GetSessionDbLocaleIndex();
13322 std::string title0 = gossiptext->Options[0].Text_0;
13323 std::string title1 = gossiptext->Options[0].Text_1;
13324 sObjectMgr.GetNpcTextLocaleStrings0(textid, loc_idx, &title0, &title1);
13326 title = !title0.empty() ? title0 : title1;
13329 PlayerTalkClass->SendQuestGiverQuestList(qe, title, guid);
13333 bool Player::IsActiveQuest(uint32 quest_id) const
13335 QuestStatusMap::const_iterator itr = mQuestStatus.find(quest_id);
13337 return itr != mQuestStatus.end() && itr->second.m_status != QUEST_STATUS_NONE;
13340 bool Player::IsCurrentQuest(uint32 quest_id, uint8 completed_or_not) const
13342 QuestStatusMap::const_iterator itr = mQuestStatus.find(quest_id);
13343 if (itr == mQuestStatus.end())
13344 return false;
13346 switch (completed_or_not)
13348 case 1:
13349 return itr->second.m_status == QUEST_STATUS_INCOMPLETE;
13350 case 2:
13351 return itr->second.m_status == QUEST_STATUS_COMPLETE && !itr->second.m_rewarded;
13352 default:
13353 return itr->second.m_status == QUEST_STATUS_INCOMPLETE || (itr->second.m_status == QUEST_STATUS_COMPLETE && !itr->second.m_rewarded);
13357 Quest const* Player::GetNextQuest(ObjectGuid guid, Quest const* pQuest)
13359 QuestRelationsMapBounds rbounds;
13361 if (Creature* pCreature = GetMap()->GetAnyTypeCreature(guid))
13363 rbounds = sObjectMgr.GetCreatureQuestRelationsMapBounds(pCreature->GetEntry());
13365 else
13367 // we should obtain map pointer from GetMap() in 99% of cases. Special case
13368 // only for quests which cast teleport spells on player
13369 Map* _map = IsInWorld() ? GetMap() : sMapMgr.FindMap(GetMapId(), GetInstanceId());
13370 MANGOS_ASSERT(_map);
13372 if (GameObject* pGameObject = _map->GetGameObject(guid))
13374 rbounds = sObjectMgr.GetGOQuestRelationsMapBounds(pGameObject->GetEntry());
13376 else
13377 return NULL;
13380 uint32 nextQuestID = pQuest->GetNextQuestInChain();
13381 for (QuestRelationsMap::const_iterator itr = rbounds.first; itr != rbounds.second; ++itr)
13383 if (itr->second == nextQuestID)
13384 return sObjectMgr.GetQuestTemplate(nextQuestID);
13387 return NULL;
13390 bool Player::CanSeeStartQuest(Quest const* pQuest) const
13392 if (SatisfyQuestClass(pQuest, false) && SatisfyQuestRace(pQuest, false) && SatisfyQuestSkill(pQuest, false) &&
13393 SatisfyQuestExclusiveGroup(pQuest, false) && SatisfyQuestReputation(pQuest, false) &&
13394 SatisfyQuestPreviousQuest(pQuest, false) && SatisfyQuestNextChain(pQuest, false) &&
13395 SatisfyQuestPrevChain(pQuest, false) && SatisfyQuestDay(pQuest, false) && SatisfyQuestWeek(pQuest, false) &&
13396 SatisfyQuestMonth(pQuest, false) &&
13397 pQuest->IsActive())
13399 return int32(getLevel()) + sWorld.getConfig(CONFIG_INT32_QUEST_HIGH_LEVEL_HIDE_DIFF) >= int32(pQuest->GetMinLevel());
13402 return false;
13405 bool Player::CanTakeQuest(Quest const* pQuest, bool msg) const
13407 return SatisfyQuestStatus(pQuest, msg) && SatisfyQuestExclusiveGroup(pQuest, msg) &&
13408 SatisfyQuestClass(pQuest, msg) && SatisfyQuestRace(pQuest, msg) && SatisfyQuestLevel(pQuest, msg) &&
13409 SatisfyQuestSkill(pQuest, msg) && SatisfyQuestReputation(pQuest, msg) &&
13410 SatisfyQuestPreviousQuest(pQuest, msg) && SatisfyQuestTimed(pQuest, msg) &&
13411 SatisfyQuestNextChain(pQuest, msg) && SatisfyQuestPrevChain(pQuest, msg) &&
13412 SatisfyQuestDay(pQuest, msg) && SatisfyQuestWeek(pQuest, msg) && SatisfyQuestMonth(pQuest, msg) &&
13413 pQuest->IsActive();
13416 bool Player::CanAddQuest(Quest const* pQuest, bool msg) const
13418 if (!SatisfyQuestLog(msg))
13419 return false;
13421 if (!CanGiveQuestSourceItemIfNeed(pQuest))
13422 return false;
13424 return true;
13427 bool Player::CanCompleteQuest(uint32 quest_id) const
13429 if (!quest_id)
13430 return false;
13432 QuestStatusMap::const_iterator q_itr = mQuestStatus.find(quest_id);
13434 // some quests can be auto taken and auto completed in one step
13435 QuestStatus status = q_itr != mQuestStatus.end() ? q_itr->second.m_status : QUEST_STATUS_NONE;
13437 if (status == QUEST_STATUS_COMPLETE)
13438 return false; // not allow re-complete quest
13440 Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest_id);
13442 if (!qInfo)
13443 return false;
13445 // only used for "flag" quests and not real in-game quests
13446 if (qInfo->HasQuestFlag(QUEST_FLAGS_AUTO_REWARDED))
13448 // a few checks, not all "satisfy" is needed
13449 if (SatisfyQuestPreviousQuest(qInfo, false) && SatisfyQuestLevel(qInfo, false) && SatisfyQuestSpell(qInfo, false) &&
13450 SatisfyQuestSkill(qInfo, false) && SatisfyQuestRace(qInfo, false) && SatisfyQuestClass(qInfo, false))
13451 return true;
13453 return false;
13456 // auto complete quest
13457 if (qInfo->IsAutoComplete() && CanTakeQuest(qInfo, false))
13458 return true;
13460 if (status != QUEST_STATUS_INCOMPLETE)
13461 return false;
13463 // incomplete quest have status data
13464 QuestStatusData const& q_status = q_itr->second;
13466 if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER))
13468 for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
13470 if (qInfo->ReqItemCount[i] != 0 && q_status.m_itemcount[i] < qInfo->ReqItemCount[i])
13471 return false;
13474 for (int i = 0; i < QUEST_REQUIRED_CURRENCY_COUNT; ++i)
13476 if (qInfo->ReqCurrencyId[i] && !HasCurrencyCount(qInfo->ReqCurrencyId[i], int32(qInfo->ReqCurrencyCount[i] * GetCurrencyPrecision(qInfo->ReqCurrencyId[i]))))
13477 return false;
13481 if (qInfo->HasSpecialFlag(QuestSpecialFlags(QUEST_SPECIAL_FLAG_KILL_OR_CAST | QUEST_SPECIAL_FLAG_SPEAKTO)))
13483 for (int i = 0; i < QUEST_OBJECTIVES_COUNT; ++i)
13485 if (qInfo->ReqCreatureOrGOId[i] == 0)
13486 continue;
13488 if (qInfo->ReqCreatureOrGOCount[i] != 0 && q_status.m_creatureOrGOcount[i] < qInfo->ReqCreatureOrGOCount[i])
13489 return false;
13493 if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_EXPLORATION_OR_EVENT) && !q_status.m_explored)
13494 return false;
13496 if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_TIMED) && q_status.m_timer == 0)
13497 return false;
13499 if (qInfo->GetRewOrReqMoney() < 0)
13501 if (GetMoney() < uint64(-qInfo->GetRewOrReqMoney()))
13502 return false;
13505 uint32 repFacId = qInfo->GetRepObjectiveFaction();
13506 if (repFacId && GetReputationMgr().GetReputation(repFacId) < qInfo->GetRepObjectiveValue())
13507 return false;
13509 if (uint32 spell = qInfo->GetReqSpellLearned())
13510 if (!HasSpell(spell))
13511 return false;
13513 return true;
13516 bool Player::CanCompleteRepeatableQuest(Quest const* pQuest) const
13518 // Solve problem that player don't have the quest and try complete it.
13519 // if repeatable she must be able to complete event if player don't have it.
13520 // Seem that all repeatable quest are DELIVER Flag so, no need to add more.
13521 if (!CanTakeQuest(pQuest, false))
13522 return false;
13524 if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER))
13526 for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
13527 if (pQuest->ReqItemId[i] && pQuest->ReqItemCount[i] && !HasItemCount(pQuest->ReqItemId[i], pQuest->ReqItemCount[i]))
13528 return false;
13530 for (int i = 0; i < QUEST_REQUIRED_CURRENCY_COUNT; ++i)
13532 if (pQuest->ReqCurrencyId[i] && !HasCurrencyCount(pQuest->ReqCurrencyId[i], int32(pQuest->ReqCurrencyCount[i] * GetCurrencyPrecision(pQuest->ReqCurrencyId[i]))))
13533 return false;
13537 if (!CanRewardQuest(pQuest, false))
13538 return false;
13540 return true;
13543 bool Player::CanRewardQuest(Quest const* pQuest, bool msg) const
13545 // not auto complete quest and not completed quest (only cheating case, then ignore without message)
13546 if (!pQuest->IsAutoComplete() && GetQuestStatus(pQuest->GetQuestId()) != QUEST_STATUS_COMPLETE)
13547 return false;
13549 // daily quest can't be rewarded (25 daily quest already completed)
13550 if (!SatisfyQuestDay(pQuest, true) || !SatisfyQuestWeek(pQuest, true) || !SatisfyQuestMonth(pQuest, true))
13551 return false;
13553 // rewarded and not repeatable quest (only cheating case, then ignore without message)
13554 if (GetQuestRewardStatus(pQuest->GetQuestId()))
13555 return false;
13557 if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER))
13559 // prevent receive reward with quest items in bank
13560 for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
13562 if (pQuest->ReqItemCount[i] != 0 &&
13563 GetItemCount(pQuest->ReqItemId[i]) < pQuest->ReqItemCount[i])
13565 if (msg)
13566 SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL, pQuest->ReqItemId[i]);
13568 return false;
13572 // prevent receive reward with low currency
13573 for (int i = 0; i < QUEST_REQUIRED_CURRENCY_COUNT; ++i)
13575 if (pQuest->ReqCurrencyId[i] &&
13576 !HasCurrencyCount(pQuest->ReqCurrencyId[i], int32(pQuest->ReqCurrencyCount[i] * GetCurrencyPrecision(pQuest->ReqCurrencyId[i]))))
13578 if (msg)
13579 SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, NULL);
13581 return false;
13586 // prevent receive reward with low money and GetRewOrReqMoney() < 0
13587 if (pQuest->GetRewOrReqMoney() < 0 && GetMoney() < uint64(-pQuest->GetRewOrReqMoney()))
13588 return false;
13590 if (uint32 spell = pQuest->GetReqSpellLearned())
13591 if (!HasSpell(spell))
13592 return false;
13594 return true;
13597 bool Player::CanRewardQuest(Quest const* pQuest, uint32 reward, bool msg) const
13599 // prevent receive reward with quest items in bank or for not completed quest
13600 if (!CanRewardQuest(pQuest, msg))
13601 return false;
13603 if (pQuest->GetRewChoiceItemsCount() > 0)
13605 if (pQuest->RewChoiceItemId[reward])
13607 ItemPosCountVec dest;
13608 InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, pQuest->RewChoiceItemId[reward], pQuest->RewChoiceItemCount[reward]);
13609 if (res != EQUIP_ERR_OK)
13611 SendEquipError(res, NULL, NULL, pQuest->RewChoiceItemId[reward]);
13612 return false;
13617 if (pQuest->GetRewItemsCount() > 0)
13619 for (uint32 i = 0; i < pQuest->GetRewItemsCount(); ++i)
13621 if (pQuest->RewItemId[i])
13623 ItemPosCountVec dest;
13624 InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, pQuest->RewItemId[i], pQuest->RewItemCount[i]);
13625 if (res != EQUIP_ERR_OK)
13627 SendEquipError(res, NULL, NULL);
13628 return false;
13634 return true;
13637 void Player::SendPetTameFailure(PetTameFailureReason reason)
13639 WorldPacket data(SMSG_PET_TAME_FAILURE, 1);
13640 data << uint8(reason);
13641 GetSession()->SendPacket(&data);
13644 void Player::AddQuest(Quest const* pQuest, Object* questGiver)
13646 uint16 log_slot = FindQuestSlot(0);
13647 MANGOS_ASSERT(log_slot < MAX_QUEST_LOG_SIZE);
13649 uint32 quest_id = pQuest->GetQuestId();
13651 // if not exist then created with set uState==NEW and rewarded=false
13652 QuestStatusData& questStatusData = mQuestStatus[quest_id];
13654 // check for repeatable quests status reset
13655 questStatusData.m_status = QUEST_STATUS_INCOMPLETE;
13656 questStatusData.m_explored = false;
13658 if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER))
13660 for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
13661 questStatusData.m_itemcount[i] = 0;
13664 if (pQuest->HasSpecialFlag(QuestSpecialFlags(QUEST_SPECIAL_FLAG_KILL_OR_CAST | QUEST_SPECIAL_FLAG_SPEAKTO)))
13666 for (int i = 0; i < QUEST_OBJECTIVES_COUNT; ++i)
13667 questStatusData.m_creatureOrGOcount[i] = 0;
13670 if (pQuest->GetRepObjectiveFaction())
13671 if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(pQuest->GetRepObjectiveFaction()))
13672 GetReputationMgr().SetVisible(factionEntry);
13674 uint32 qtime = 0;
13675 if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_TIMED))
13677 uint32 limittime = pQuest->GetLimitTime();
13679 // shared timed quest
13680 if (questGiver && questGiver->GetTypeId() == TYPEID_PLAYER)
13681 limittime = ((Player*)questGiver)->getQuestStatusMap()[quest_id].m_timer / IN_MILLISECONDS;
13683 AddTimedQuest(quest_id);
13684 questStatusData.m_timer = limittime * IN_MILLISECONDS;
13685 qtime = static_cast<uint32>(time(NULL)) + limittime;
13687 else
13688 questStatusData.m_timer = 0;
13690 SetQuestSlot(log_slot, quest_id, qtime);
13692 if (questStatusData.uState != QUEST_NEW)
13693 questStatusData.uState = QUEST_CHANGED;
13695 // quest accept scripts
13696 if (questGiver)
13698 switch (questGiver->GetTypeId())
13700 case TYPEID_UNIT:
13701 sScriptMgr.OnQuestAccept(this, (Creature*)questGiver, pQuest);
13702 break;
13703 case TYPEID_ITEM:
13704 case TYPEID_CONTAINER:
13705 sScriptMgr.OnQuestAccept(this, (Item*)questGiver, pQuest);
13706 break;
13707 case TYPEID_GAMEOBJECT:
13708 sScriptMgr.OnQuestAccept(this, (GameObject*)questGiver, pQuest);
13709 break;
13712 // starting initial DB quest script
13713 if (pQuest->GetQuestStartScript() != 0)
13714 GetMap()->ScriptsStart(sQuestStartScripts, pQuest->GetQuestStartScript(), questGiver, this);
13718 // remove start item if not need
13719 if (questGiver && questGiver->isType(TYPEMASK_ITEM))
13721 // destroy not required for quest finish quest starting item
13722 bool notRequiredItem = true;
13723 for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
13725 if (pQuest->ReqItemId[i] == questGiver->GetEntry())
13727 notRequiredItem = false;
13728 break;
13732 if (pQuest->GetSrcItemId() == questGiver->GetEntry())
13733 notRequiredItem = false;
13735 if (notRequiredItem)
13736 DestroyItem(((Item*)questGiver)->GetBagSlot(), ((Item*)questGiver)->GetSlot(), true);
13739 GiveQuestSourceItemIfNeed(pQuest);
13741 AdjustQuestReqItemCount(pQuest, questStatusData);
13743 // Some spells applied at quest activation
13744 SpellAreaForQuestMapBounds saBounds = sSpellMgr.GetSpellAreaForQuestMapBounds(quest_id, true);
13745 if (saBounds.first != saBounds.second)
13747 uint32 zone, area;
13748 GetZoneAndAreaId(zone, area);
13750 for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
13751 if (itr->second->autocast && itr->second->IsFitToRequirements(this, zone, area))
13752 if (!HasAura(itr->second->spellId, EFFECT_INDEX_0))
13753 CastSpell(this, itr->second->spellId, true);
13756 UpdateForQuestWorldObjects();
13759 void Player::CompleteQuest(uint32 quest_id)
13761 if (quest_id)
13763 SetQuestStatus(quest_id, QUEST_STATUS_COMPLETE);
13765 uint16 log_slot = FindQuestSlot(quest_id);
13766 if (log_slot < MAX_QUEST_LOG_SIZE)
13767 SetQuestSlotState(log_slot, QUEST_STATE_COMPLETE);
13769 if (Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest_id))
13771 if (qInfo->HasQuestFlag(QUEST_FLAGS_AUTO_REWARDED))
13772 RewardQuest(qInfo, 0, this, false);
13777 void Player::IncompleteQuest(uint32 quest_id)
13779 if (quest_id)
13781 SetQuestStatus(quest_id, QUEST_STATUS_INCOMPLETE);
13783 uint16 log_slot = FindQuestSlot(quest_id);
13784 if (log_slot < MAX_QUEST_LOG_SIZE)
13785 RemoveQuestSlotState(log_slot, QUEST_STATE_COMPLETE);
13789 void Player::RewardQuest(Quest const* pQuest, uint32 reward, Object* questGiver, bool announce)
13791 uint32 quest_id = pQuest->GetQuestId();
13793 for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
13795 if (pQuest->ReqItemId[i])
13796 DestroyItemCount(pQuest->ReqItemId[i], pQuest->ReqItemCount[i], true);
13799 for (int i = 0; i < QUEST_SOURCE_ITEM_IDS_COUNT; ++i)
13801 if (pQuest->ReqSourceId[i])
13803 ItemPrototype const* iProto = ObjectMgr::GetItemPrototype(pQuest->ReqSourceId[i]);
13804 if (iProto && iProto->Bonding == BIND_QUEST_ITEM)
13805 DestroyItemCount(pQuest->ReqSourceId[i], pQuest->ReqSourceCount[i], true, false, true);
13809 // take currency
13810 for (uint32 i = 0; i < QUEST_REQUIRED_CURRENCY_COUNT; ++i)
13812 if (pQuest->ReqCurrencyId[i])
13813 ModifyCurrencyCount(pQuest->ReqCurrencyId[i], -int32(pQuest->ReqCurrencyCount[i] * GetCurrencyPrecision(pQuest->ReqCurrencyId[i])));
13816 RemoveTimedQuest(quest_id);
13818 if (BattleGround* bg = GetBattleGround())
13819 if (bg->GetTypeID() == BATTLEGROUND_AV)
13820 ((BattleGroundAV*)bg)->HandleQuestComplete(pQuest->GetQuestId(), this);
13822 if (pQuest->GetRewChoiceItemsCount() > 0)
13824 if (uint32 itemId = pQuest->RewChoiceItemId[reward])
13826 ItemPosCountVec dest;
13827 if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, pQuest->RewChoiceItemCount[reward]) == EQUIP_ERR_OK)
13829 Item* item = StoreNewItem(dest, itemId, true, Item::GenerateItemRandomPropertyId(itemId));
13830 SendNewItem(item, pQuest->RewChoiceItemCount[reward], true, false);
13835 if (pQuest->GetRewItemsCount() > 0)
13837 for (uint32 i = 0; i < pQuest->GetRewItemsCount(); ++i)
13839 if (uint32 itemId = pQuest->RewItemId[i])
13841 ItemPosCountVec dest;
13842 if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, pQuest->RewItemCount[i]) == EQUIP_ERR_OK)
13844 Item* item = StoreNewItem(dest, itemId, true, Item::GenerateItemRandomPropertyId(itemId));
13845 SendNewItem(item, pQuest->RewItemCount[i], true, false);
13851 RewardReputation(pQuest);
13853 uint16 log_slot = FindQuestSlot(quest_id);
13854 if (log_slot < MAX_QUEST_LOG_SIZE)
13855 SetQuestSlot(log_slot, 0);
13857 QuestStatusData& q_status = mQuestStatus[quest_id];
13859 // Used for client inform but rewarded only in case not max level
13860 uint32 xp = uint32(pQuest->XPValue(this) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_QUEST));
13862 if (getLevel() < sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL))
13864 GiveXP(xp , NULL);
13866 // Give player extra money (for max level already included in pQuest->GetRewMoneyMaxLevel())
13867 if (pQuest->GetRewOrReqMoney() > 0)
13869 ModifyMoney(pQuest->GetRewOrReqMoney());
13870 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_MONEY_FROM_QUEST_REWARD, pQuest->GetRewOrReqMoney());
13873 else
13875 // reward money for max level already included in pQuest->GetRewMoneyMaxLevel()
13876 uint64 money = uint32(pQuest->GetRewMoneyMaxLevel() * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
13878 // reward money used if > xp replacement money
13879 if (pQuest->GetRewOrReqMoney() > int64(money))
13880 money = pQuest->GetRewOrReqMoney();
13882 ModifyMoney(money);
13883 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_MONEY_FROM_QUEST_REWARD, money);
13886 // req money case
13887 if (pQuest->GetRewOrReqMoney() < 0)
13888 ModifyMoney(pQuest->GetRewOrReqMoney());
13890 // reward currency
13891 for (uint32 i = 0; i < QUEST_REWARD_CURRENCY_COUNT; ++i)
13893 if (pQuest->RewCurrencyId[i])
13894 ModifyCurrencyCount(pQuest->RewCurrencyId[i], int32(pQuest->RewCurrencyCount[i] * GetCurrencyPrecision(pQuest->RewCurrencyId[i])));
13897 // reward skill
13898 if (uint32 skill = pQuest->GetRewSkill())
13899 UpdateSkill(skill, pQuest->GetRewSkillValue());
13901 // title reward
13902 if (pQuest->GetCharTitleId())
13904 if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(pQuest->GetCharTitleId()))
13905 SetTitle(titleEntry);
13908 if (pQuest->GetBonusTalents())
13910 m_questRewardTalentCount += pQuest->GetBonusTalents();
13911 InitTalentForLevel();
13914 // Send reward mail
13915 if (uint32 mail_template_id = pQuest->GetRewMailTemplateId())
13916 MailDraft(mail_template_id).SendMailTo(this, questGiver, MAIL_CHECK_MASK_HAS_BODY, pQuest->GetRewMailDelaySecs());
13918 if (pQuest->IsDaily())
13920 SetDailyQuestStatus(quest_id);
13921 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST, 1);
13924 if (pQuest->IsWeekly())
13925 SetWeeklyQuestStatus(quest_id);
13927 if (pQuest->IsMonthly())
13928 SetMonthlyQuestStatus(quest_id);
13930 if (!pQuest->IsRepeatable())
13931 SetQuestStatus(quest_id, QUEST_STATUS_COMPLETE);
13932 else
13933 SetQuestStatus(quest_id, QUEST_STATUS_NONE);
13935 q_status.m_rewarded = true;
13936 if (q_status.uState != QUEST_NEW)
13937 q_status.uState = QUEST_CHANGED;
13939 if (announce)
13940 SendQuestReward(pQuest, xp, questGiver);
13942 bool handled = false;
13944 switch (questGiver->GetTypeId())
13946 case TYPEID_UNIT:
13947 handled = sScriptMgr.OnQuestRewarded(this, (Creature*)questGiver, pQuest);
13948 break;
13949 case TYPEID_GAMEOBJECT:
13950 handled = sScriptMgr.OnQuestRewarded(this, (GameObject*)questGiver, pQuest);
13951 break;
13954 if (!handled && pQuest->GetQuestCompleteScript() != 0)
13955 GetMap()->ScriptsStart(sQuestEndScripts, pQuest->GetQuestCompleteScript(), questGiver, this);
13957 // cast spells after mark quest complete (some spells have quest completed state reqyurements in spell_area data)
13958 if (pQuest->GetRewSpellCast() > 0)
13959 CastSpell(this, pQuest->GetRewSpellCast(), true);
13960 else if (pQuest->GetRewSpell() > 0)
13961 CastSpell(this, pQuest->GetRewSpell(), true);
13963 if (pQuest->GetZoneOrSort() > 0)
13964 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE, pQuest->GetZoneOrSort());
13966 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT);
13967 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, pQuest->GetQuestId());
13969 uint32 zone = 0;
13970 uint32 area = 0;
13972 // remove auras from spells with quest reward state limitations
13973 SpellAreaForQuestMapBounds saEndBounds = sSpellMgr.GetSpellAreaForQuestEndMapBounds(quest_id);
13974 if (saEndBounds.first != saEndBounds.second)
13976 GetZoneAndAreaId(zone, area);
13978 for (SpellAreaForAreaMap::const_iterator itr = saEndBounds.first; itr != saEndBounds.second; ++itr)
13979 if (!itr->second->IsFitToRequirements(this, zone, area))
13980 RemoveAurasDueToSpell(itr->second->spellId);
13983 // Some spells applied at quest reward
13984 SpellAreaForQuestMapBounds saBounds = sSpellMgr.GetSpellAreaForQuestMapBounds(quest_id, false);
13985 if (saBounds.first != saBounds.second)
13987 if (!zone || !area)
13988 GetZoneAndAreaId(zone, area);
13990 for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
13991 if (itr->second->autocast && itr->second->IsFitToRequirements(this, zone, area))
13992 if (!HasAura(itr->second->spellId, EFFECT_INDEX_0))
13993 CastSpell(this, itr->second->spellId, true);
13997 void Player::FailQuest(uint32 questId)
13999 if (Quest const* pQuest = sObjectMgr.GetQuestTemplate(questId))
14001 SetQuestStatus(questId, QUEST_STATUS_FAILED);
14003 uint16 log_slot = FindQuestSlot(questId);
14005 if (log_slot < MAX_QUEST_LOG_SIZE)
14007 SetQuestSlotTimer(log_slot, 1);
14008 SetQuestSlotState(log_slot, QUEST_STATE_FAIL);
14011 if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_TIMED))
14013 QuestStatusData& q_status = mQuestStatus[questId];
14015 RemoveTimedQuest(questId);
14016 q_status.m_timer = 0;
14018 SendQuestTimerFailed(questId);
14020 else
14021 SendQuestFailed(questId);
14025 bool Player::SatisfyQuestSkill(Quest const* qInfo, bool msg) const
14027 uint32 skill = qInfo->GetRequiredSkill();
14029 // skip 0 case RequiredSkill
14030 if (skill == 0)
14031 return true;
14033 // check skill value
14034 if (GetSkillValue(skill) < qInfo->GetRequiredSkillValue())
14036 if (msg)
14037 SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
14039 return false;
14042 return true;
14045 bool Player::SatisfyQuestSpell(Quest const* qInfo, bool msg) const
14047 uint32 spell = qInfo->GetReqSpellLearned();
14049 // skip 0 case ReqSpellLearned
14050 if (spell == 0)
14051 return true;
14053 // check spell
14054 if (!HasSpell(spell))
14056 if (msg)
14057 SendCanTakeQuestResponse(INVALIDREASON_QUEST_FAILED_SPELL);
14059 return false;
14062 return true;
14065 bool Player::SatisfyQuestLevel(Quest const* qInfo, bool msg) const
14067 if (getLevel() < qInfo->GetMinLevel())
14069 if (msg)
14070 SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
14072 return false;
14075 return true;
14078 bool Player::SatisfyQuestLog(bool msg) const
14080 // exist free slot
14081 if (FindQuestSlot(0) < MAX_QUEST_LOG_SIZE)
14082 return true;
14084 if (msg)
14086 WorldPacket data(SMSG_QUESTLOG_FULL, 0);
14087 GetSession()->SendPacket(&data);
14088 DEBUG_LOG("WORLD: Sent SMSG_QUESTLOG_FULL");
14090 return false;
14093 bool Player::SatisfyQuestPreviousQuest(Quest const* qInfo, bool msg) const
14095 // No previous quest (might be first quest in a series)
14096 if (qInfo->prevQuests.empty())
14097 return true;
14099 for (Quest::PrevQuests::const_iterator iter = qInfo->prevQuests.begin(); iter != qInfo->prevQuests.end(); ++iter)
14101 uint32 prevId = abs(*iter);
14103 QuestStatusMap::const_iterator i_prevstatus = mQuestStatus.find(prevId);
14104 Quest const* qPrevInfo = sObjectMgr.GetQuestTemplate(prevId);
14106 if (qPrevInfo && i_prevstatus != mQuestStatus.end())
14108 // If any of the positive previous quests completed, return true
14109 if (*iter > 0 && i_prevstatus->second.m_rewarded)
14111 // skip one-from-all exclusive group
14112 if (qPrevInfo->GetExclusiveGroup() >= 0)
14113 return true;
14115 // each-from-all exclusive group ( < 0)
14116 // can be start if only all quests in prev quest exclusive group completed and rewarded
14117 ExclusiveQuestGroupsMapBounds bounds = sObjectMgr.GetExclusiveQuestGroupsMapBounds(qPrevInfo->GetExclusiveGroup());
14119 MANGOS_ASSERT(bounds.first != bounds.second); // always must be found if qPrevInfo->ExclusiveGroup != 0
14121 for (ExclusiveQuestGroupsMap::const_iterator iter2 = bounds.first; iter2 != bounds.second; ++iter2)
14123 uint32 exclude_Id = iter2->second;
14125 // skip checked quest id, only state of other quests in group is interesting
14126 if (exclude_Id == prevId)
14127 continue;
14129 QuestStatusMap::const_iterator i_exstatus = mQuestStatus.find(exclude_Id);
14131 // alternative quest from group also must be completed and rewarded(reported)
14132 if (i_exstatus == mQuestStatus.end() || !i_exstatus->second.m_rewarded)
14134 if (msg)
14135 SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
14137 return false;
14140 return true;
14142 // If any of the negative previous quests active, return true
14143 if (*iter < 0 && IsCurrentQuest(prevId))
14145 // skip one-from-all exclusive group
14146 if (qPrevInfo->GetExclusiveGroup() >= 0)
14147 return true;
14149 // each-from-all exclusive group ( < 0)
14150 // can be start if only all quests in prev quest exclusive group active
14151 ExclusiveQuestGroupsMapBounds bounds = sObjectMgr.GetExclusiveQuestGroupsMapBounds(qPrevInfo->GetExclusiveGroup());
14153 MANGOS_ASSERT(bounds.first != bounds.second); // always must be found if qPrevInfo->ExclusiveGroup != 0
14155 for (ExclusiveQuestGroupsMap::const_iterator iter2 = bounds.first; iter2 != bounds.second; ++iter2)
14157 uint32 exclude_Id = iter2->second;
14159 // skip checked quest id, only state of other quests in group is interesting
14160 if (exclude_Id == prevId)
14161 continue;
14163 // alternative quest from group also must be active
14164 if (!IsCurrentQuest(exclude_Id))
14166 if (msg)
14167 SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
14169 return false;
14172 return true;
14177 // Has only positive prev. quests in non-rewarded state
14178 // and negative prev. quests in non-active state
14179 if (msg)
14180 SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
14182 return false;
14185 bool Player::SatisfyQuestClass(Quest const* qInfo, bool msg) const
14187 uint32 reqClass = qInfo->GetRequiredClasses();
14189 if (reqClass == 0)
14190 return true;
14192 if ((reqClass & getClassMask()) == 0)
14194 if (msg)
14195 SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
14197 return false;
14200 return true;
14203 bool Player::SatisfyQuestRace(Quest const* qInfo, bool msg) const
14205 uint32 reqraces = qInfo->GetRequiredRaces();
14207 if (reqraces == 0)
14208 return true;
14210 if ((reqraces & getRaceMask()) == 0)
14212 if (msg)
14213 SendCanTakeQuestResponse(INVALIDREASON_QUEST_FAILED_WRONG_RACE);
14215 return false;
14218 return true;
14221 bool Player::SatisfyQuestReputation(Quest const* qInfo, bool msg) const
14223 uint32 fIdMin = qInfo->GetRequiredMinRepFaction(); // Min required rep
14224 if (fIdMin && GetReputationMgr().GetReputation(fIdMin) < qInfo->GetRequiredMinRepValue())
14226 if (msg)
14227 SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
14229 return false;
14232 uint32 fIdMax = qInfo->GetRequiredMaxRepFaction(); // Max required rep
14233 if (fIdMax && GetReputationMgr().GetReputation(fIdMax) >= qInfo->GetRequiredMaxRepValue())
14235 if (msg)
14236 SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
14238 return false;
14241 return true;
14244 bool Player::SatisfyQuestStatus(Quest const* qInfo, bool msg) const
14246 QuestStatusMap::const_iterator itr = mQuestStatus.find(qInfo->GetQuestId());
14248 if (itr != mQuestStatus.end() && itr->second.m_status != QUEST_STATUS_NONE)
14250 if (msg)
14251 SendCanTakeQuestResponse(INVALIDREASON_QUEST_ALREADY_ON);
14253 return false;
14256 return true;
14259 bool Player::SatisfyQuestTimed(Quest const* qInfo, bool msg) const
14261 if (!m_timedquests.empty() && qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_TIMED))
14263 if (msg)
14264 SendCanTakeQuestResponse(INVALIDREASON_QUEST_ONLY_ONE_TIMED);
14266 return false;
14269 return true;
14272 bool Player::SatisfyQuestExclusiveGroup(Quest const* qInfo, bool msg) const
14274 // non positive exclusive group, if > 0 then can be start if any other quest in exclusive group already started/completed
14275 if (qInfo->GetExclusiveGroup() <= 0)
14276 return true;
14278 ExclusiveQuestGroupsMapBounds bounds = sObjectMgr.GetExclusiveQuestGroupsMapBounds(qInfo->GetExclusiveGroup());
14280 MANGOS_ASSERT(bounds.first != bounds.second); // must always be found if qInfo->ExclusiveGroup != 0
14282 for (ExclusiveQuestGroupsMap::const_iterator iter = bounds.first; iter != bounds.second; ++iter)
14284 uint32 exclude_Id = iter->second;
14286 // skip checked quest id, only state of other quests in group is interesting
14287 if (exclude_Id == qInfo->GetQuestId())
14288 continue;
14290 // not allow have daily quest if daily quest from exclusive group already recently completed
14291 Quest const* Nquest = sObjectMgr.GetQuestTemplate(exclude_Id);
14292 if (!SatisfyQuestDay(Nquest, false) || !SatisfyQuestWeek(Nquest, false))
14294 if (msg)
14295 SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
14297 return false;
14300 QuestStatusMap::const_iterator i_exstatus = mQuestStatus.find(exclude_Id);
14302 // alternative quest already started or completed
14303 if (i_exstatus != mQuestStatus.end() &&
14304 (i_exstatus->second.m_status == QUEST_STATUS_COMPLETE || i_exstatus->second.m_status == QUEST_STATUS_INCOMPLETE))
14306 if (msg)
14307 SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
14309 return false;
14313 return true;
14316 bool Player::SatisfyQuestNextChain(Quest const* qInfo, bool msg) const
14318 if (!qInfo->GetNextQuestInChain())
14319 return true;
14321 // next quest in chain already started or completed
14322 QuestStatusMap::const_iterator itr = mQuestStatus.find(qInfo->GetNextQuestInChain());
14323 if (itr != mQuestStatus.end() &&
14324 (itr->second.m_status == QUEST_STATUS_COMPLETE || itr->second.m_status == QUEST_STATUS_INCOMPLETE))
14326 if (msg)
14327 SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
14329 return false;
14332 // check for all quests further up the chain
14333 // only necessary if there are quest chains with more than one quest that can be skipped
14334 // return SatisfyQuestNextChain( qInfo->GetNextQuestInChain(), msg );
14335 return true;
14338 bool Player::SatisfyQuestPrevChain(Quest const* qInfo, bool msg) const
14340 // No previous quest in chain
14341 if (qInfo->prevChainQuests.empty())
14342 return true;
14344 for (Quest::PrevChainQuests::const_iterator iter = qInfo->prevChainQuests.begin(); iter != qInfo->prevChainQuests.end(); ++iter)
14346 uint32 prevId = *iter;
14348 // If any of the previous quests in chain active, return false
14349 if (IsCurrentQuest(prevId))
14351 if (msg)
14352 SendCanTakeQuestResponse(INVALIDREASON_DONT_HAVE_REQ);
14354 return false;
14357 // check for all quests further down the chain
14358 // only necessary if there are quest chains with more than one quest that can be skipped
14359 // if( !SatisfyQuestPrevChain( prevId, msg ) )
14360 // return false;
14363 // No previous quest in chain active
14364 return true;
14367 bool Player::SatisfyQuestDay(Quest const* qInfo, bool msg) const
14369 if (!qInfo->IsDaily())
14370 return true;
14372 bool have_slot = false;
14373 for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx)
14375 uint32 id = GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx);
14376 if (qInfo->GetQuestId() == id)
14377 return false;
14379 if (!id)
14380 have_slot = true;
14383 if (!have_slot)
14385 if (msg)
14386 SendCanTakeQuestResponse(INVALIDREASON_QUEST_FAILED_TOO_MANY_DAILY_QUESTS);
14388 return false;
14391 return true;
14394 bool Player::SatisfyQuestWeek(Quest const* qInfo, bool /*msg*/) const
14396 if (!qInfo->IsWeekly() || m_weeklyquests.empty())
14397 return true;
14399 // if not found in cooldown list
14400 return m_weeklyquests.find(qInfo->GetQuestId()) == m_weeklyquests.end();
14403 bool Player::SatisfyQuestMonth(Quest const* qInfo, bool /*msg*/) const
14405 if (!qInfo->IsMonthly() || m_monthlyquests.empty())
14406 return true;
14408 // if not found in cooldown list
14409 return m_monthlyquests.find(qInfo->GetQuestId()) == m_monthlyquests.end();
14412 bool Player::CanGiveQuestSourceItemIfNeed(Quest const* pQuest, ItemPosCountVec* dest) const
14414 if (uint32 srcitem = pQuest->GetSrcItemId())
14416 uint32 count = pQuest->GetSrcItemCount();
14418 // player already have max amount required item (including bank), just report success
14419 uint32 has_count = GetItemCount(srcitem, true);
14420 if (has_count >= count)
14421 return true;
14423 count -= has_count; // real need amount
14425 InventoryResult msg;
14426 if (!dest)
14428 ItemPosCountVec destTemp;
14429 msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, destTemp, srcitem, count);
14431 else
14432 msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, *dest, srcitem, count);
14434 if (msg == EQUIP_ERR_OK)
14435 return true;
14436 else
14437 SendEquipError(msg, NULL, NULL, srcitem);
14438 return false;
14441 return true;
14444 void Player::GiveQuestSourceItemIfNeed(Quest const* pQuest)
14446 ItemPosCountVec dest;
14447 if (CanGiveQuestSourceItemIfNeed(pQuest, &dest) && !dest.empty())
14449 uint32 count = 0;
14450 for (ItemPosCountVec::const_iterator c_itr = dest.begin(); c_itr != dest.end(); ++c_itr)
14451 count += c_itr->count;
14453 Item* item = StoreNewItem(dest, pQuest->GetSrcItemId(), true);
14454 SendNewItem(item, count, true, false);
14459 bool Player::TakeQuestSourceItem(uint32 quest_id, bool msg)
14461 Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest_id);
14462 if (qInfo)
14464 uint32 srcitem = qInfo->GetSrcItemId();
14465 if (srcitem > 0)
14467 uint32 count = qInfo->GetSrcItemCount();
14468 if (count <= 0)
14469 count = 1;
14471 // exist one case when destroy source quest item not possible:
14472 // non un-equippable item (equipped non-empty bag, for example)
14473 InventoryResult res = CanUnequipItems(srcitem, count);
14474 if (res != EQUIP_ERR_OK)
14476 if (msg)
14477 SendEquipError(res, NULL, NULL, srcitem);
14478 return false;
14481 DestroyItemCount(srcitem, count, true, true);
14484 return true;
14487 bool Player::GetQuestRewardStatus(uint32 quest_id) const
14489 Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest_id);
14490 if (qInfo)
14492 // for repeatable quests: rewarded field is set after first reward only to prevent getting XP more than once
14493 QuestStatusMap::const_iterator itr = mQuestStatus.find(quest_id);
14494 if (itr != mQuestStatus.end() && itr->second.m_status != QUEST_STATUS_NONE
14495 && !qInfo->IsRepeatable())
14496 return itr->second.m_rewarded;
14498 return false;
14500 return false;
14503 QuestStatus Player::GetQuestStatus(uint32 quest_id) const
14505 if (quest_id)
14507 QuestStatusMap::const_iterator itr = mQuestStatus.find(quest_id);
14508 if (itr != mQuestStatus.end())
14509 return itr->second.m_status;
14511 return QUEST_STATUS_NONE;
14514 bool Player::CanShareQuest(uint32 quest_id) const
14516 if (Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest_id))
14517 if (qInfo->HasQuestFlag(QUEST_FLAGS_SHARABLE))
14518 return IsCurrentQuest(quest_id);
14520 return false;
14523 void Player::SetQuestStatus(uint32 quest_id, QuestStatus status)
14525 if (sObjectMgr.GetQuestTemplate(quest_id))
14527 QuestStatusData& q_status = mQuestStatus[quest_id];
14529 q_status.m_status = status;
14531 if (q_status.uState != QUEST_NEW)
14532 q_status.uState = QUEST_CHANGED;
14535 UpdateForQuestWorldObjects();
14538 // not used in MaNGOS, but used in scripting code
14539 uint32 Player::GetReqKillOrCastCurrentCount(uint32 quest_id, int32 entry)
14541 Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest_id);
14542 if (!qInfo)
14543 return 0;
14545 for (int j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
14546 if (qInfo->ReqCreatureOrGOId[j] == entry)
14547 return mQuestStatus[quest_id].m_creatureOrGOcount[j];
14549 return 0;
14552 void Player::AdjustQuestReqItemCount(Quest const* pQuest, QuestStatusData& questStatusData)
14554 if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER))
14556 for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
14558 uint32 reqitemcount = pQuest->ReqItemCount[i];
14559 if (reqitemcount != 0)
14561 uint32 curitemcount = GetItemCount(pQuest->ReqItemId[i], true);
14563 questStatusData.m_itemcount[i] = std::min(curitemcount, reqitemcount);
14564 if (questStatusData.uState != QUEST_NEW) questStatusData.uState = QUEST_CHANGED;
14570 uint16 Player::FindQuestSlot(uint32 quest_id) const
14572 for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
14573 if (GetQuestSlotQuestId(i) == quest_id)
14574 return i;
14576 return MAX_QUEST_LOG_SIZE;
14579 void Player::AreaExploredOrEventHappens(uint32 questId)
14581 if (questId)
14583 uint16 log_slot = FindQuestSlot(questId);
14584 if (log_slot < MAX_QUEST_LOG_SIZE)
14586 QuestStatusData& q_status = mQuestStatus[questId];
14588 if (!q_status.m_explored)
14590 SetQuestSlotState(log_slot, QUEST_STATE_COMPLETE);
14591 SendQuestCompleteEvent(questId);
14592 q_status.m_explored = true;
14594 if (q_status.uState != QUEST_NEW)
14595 q_status.uState = QUEST_CHANGED;
14598 if (CanCompleteQuest(questId))
14599 CompleteQuest(questId);
14603 // not used in mangosd, function for external script library
14604 void Player::GroupEventHappens(uint32 questId, WorldObject const* pEventObject)
14606 if (Group* pGroup = GetGroup())
14608 for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
14610 Player* pGroupGuy = itr->getSource();
14612 // for any leave or dead (with not released body) group member at appropriate distance
14613 if (pGroupGuy && pGroupGuy->IsAtGroupRewardDistance(pEventObject) && !pGroupGuy->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
14614 pGroupGuy->AreaExploredOrEventHappens(questId);
14617 else
14618 AreaExploredOrEventHappens(questId);
14621 void Player::CurrencyAddedQuestCheck(uint32 entry)
14623 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
14625 uint32 questid = GetQuestSlotQuestId(i);
14626 if (questid == 0)
14627 continue;
14629 QuestStatusData& q_status = mQuestStatus[questid];
14631 if (q_status.m_status != QUEST_STATUS_INCOMPLETE)
14632 continue;
14634 Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid);
14635 if (!qInfo || !qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER))
14636 continue;
14638 for (int j = 0; j < QUEST_REQUIRED_CURRENCY_COUNT; ++j)
14640 uint32 reqcurrency = qInfo->ReqCurrencyId[j];
14641 if (reqcurrency == entry)
14643 if (CanCompleteQuest(questid))
14644 CompleteQuest(questid);
14650 void Player::CurrencyRemovedQuestCheck(uint32 entry)
14652 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
14654 uint32 questid = GetQuestSlotQuestId(i);
14655 if (!questid)
14656 continue;
14657 Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid);
14658 if (!qInfo || !qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER))
14659 continue;
14661 for (int j = 0; j < QUEST_REQUIRED_CURRENCY_COUNT; ++j)
14663 uint32 reqcurrency = qInfo->ReqCurrencyId[j];
14664 if (reqcurrency == entry)
14666 if (!HasCurrencyCount(entry, int32(qInfo->ReqCurrencyCount[j] * GetCurrencyPrecision(entry))))
14667 IncompleteQuest(questid);
14673 void Player::ItemAddedQuestCheck(uint32 entry, uint32 count)
14675 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
14677 uint32 questid = GetQuestSlotQuestId(i);
14678 if (questid == 0)
14679 continue;
14681 QuestStatusData& q_status = mQuestStatus[questid];
14683 if (q_status.m_status != QUEST_STATUS_INCOMPLETE)
14684 continue;
14686 Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid);
14687 if (!qInfo || !qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER))
14688 continue;
14690 for (int j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j)
14692 uint32 reqitem = qInfo->ReqItemId[j];
14693 if (reqitem == entry)
14695 uint32 reqitemcount = qInfo->ReqItemCount[j];
14696 uint32 curitemcount = q_status.m_itemcount[j];
14697 if (curitemcount < reqitemcount)
14699 uint32 additemcount = (curitemcount + count <= reqitemcount ? count : reqitemcount - curitemcount);
14700 q_status.m_itemcount[j] += additemcount;
14701 if (q_status.uState != QUEST_NEW)
14702 q_status.uState = QUEST_CHANGED;
14704 if (CanCompleteQuest(questid))
14705 CompleteQuest(questid);
14706 return;
14710 UpdateForQuestWorldObjects();
14713 void Player::ItemRemovedQuestCheck(uint32 entry, uint32 count)
14715 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
14717 uint32 questid = GetQuestSlotQuestId(i);
14718 if (!questid)
14719 continue;
14720 Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid);
14721 if (!qInfo)
14722 continue;
14723 if (!qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_DELIVER))
14724 continue;
14726 for (int j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j)
14728 uint32 reqitem = qInfo->ReqItemId[j];
14729 if (reqitem == entry)
14731 QuestStatusData& q_status = mQuestStatus[questid];
14733 uint32 reqitemcount = qInfo->ReqItemCount[j];
14734 uint32 curitemcount;
14735 if (q_status.m_status != QUEST_STATUS_COMPLETE)
14736 curitemcount = q_status.m_itemcount[j];
14737 else
14738 curitemcount = GetItemCount(entry, true);
14739 if (curitemcount < reqitemcount + count)
14741 uint32 remitemcount = (curitemcount <= reqitemcount ? count : count + reqitemcount - curitemcount);
14742 q_status.m_itemcount[j] = curitemcount - remitemcount;
14743 if (q_status.uState != QUEST_NEW) q_status.uState = QUEST_CHANGED;
14745 IncompleteQuest(questid);
14747 return;
14751 UpdateForQuestWorldObjects();
14754 void Player::SpellAddedQuestCheck(uint32 entry)
14756 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
14758 uint32 questid = GetQuestSlotQuestId(i);
14759 if (questid == 0)
14760 continue;
14762 QuestStatusData& q_status = mQuestStatus[questid];
14764 if (q_status.m_status != QUEST_STATUS_INCOMPLETE)
14765 continue;
14767 Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid);
14768 if (!qInfo)
14769 continue;
14771 uint32 reqspelllearned = qInfo->GetReqSpellLearned();
14772 if (!reqspelllearned)
14773 continue;
14775 if (reqspelllearned == entry)
14777 if (CanCompleteQuest(questid))
14778 CompleteQuest(questid);
14783 void Player::SpellRemovedQuestCheck(uint32 entry)
14785 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
14787 uint32 questid = GetQuestSlotQuestId(i);
14788 if (!questid)
14789 continue;
14790 Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid);
14791 if (!qInfo)
14792 continue;
14794 uint32 reqspelllearned = qInfo->GetReqSpellLearned();
14795 if (!reqspelllearned)
14796 continue;
14798 if (reqspelllearned == entry)
14800 if (!HasSpell(entry))
14801 IncompleteQuest(questid);
14806 void Player::KilledMonster(CreatureInfo const* cInfo, ObjectGuid guid)
14808 if (cInfo->Entry)
14809 KilledMonsterCredit(cInfo->Entry, guid);
14811 for (int i = 0; i < MAX_KILL_CREDIT; ++i)
14812 if (cInfo->KillCredit[i])
14813 KilledMonsterCredit(cInfo->KillCredit[i], guid);
14816 void Player::KilledMonsterCredit(uint32 entry, ObjectGuid guid)
14818 uint32 addkillcount = 1;
14819 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE, entry, addkillcount);
14821 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
14823 uint32 questid = GetQuestSlotQuestId(i);
14824 if (!questid)
14825 continue;
14827 Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid);
14828 if (!qInfo)
14829 continue;
14830 // just if !ingroup || !noraidgroup || raidgroup
14831 QuestStatusData& q_status = mQuestStatus[questid];
14832 if (q_status.m_status == QUEST_STATUS_INCOMPLETE && (!GetGroup() || !GetGroup()->isRaidGroup() || qInfo->IsAllowedInRaid()))
14834 if (qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_KILL_OR_CAST))
14836 for (int j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
14838 // skip GO activate objective or none
14839 if (qInfo->ReqCreatureOrGOId[j] <= 0)
14840 continue;
14842 // skip Cast at creature objective
14843 if (qInfo->ReqSpell[j] != 0)
14844 continue;
14846 uint32 reqkill = qInfo->ReqCreatureOrGOId[j];
14848 if (reqkill == entry)
14850 uint32 reqkillcount = qInfo->ReqCreatureOrGOCount[j];
14851 uint32 curkillcount = q_status.m_creatureOrGOcount[j];
14852 if (curkillcount < reqkillcount)
14854 q_status.m_creatureOrGOcount[j] = curkillcount + addkillcount;
14855 if (q_status.uState != QUEST_NEW)
14856 q_status.uState = QUEST_CHANGED;
14858 SendQuestUpdateAddCreatureOrGo(qInfo, guid, j, q_status.m_creatureOrGOcount[j]);
14861 if (CanCompleteQuest(questid))
14862 CompleteQuest(questid);
14864 // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization).
14865 continue;
14873 void Player::CastedCreatureOrGO(uint32 entry, ObjectGuid guid, uint32 spell_id, bool original_caster)
14875 bool isCreature = guid.IsCreatureOrVehicle();
14877 uint32 addCastCount = 1;
14878 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
14880 uint32 questid = GetQuestSlotQuestId(i);
14881 if (!questid)
14882 continue;
14884 Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid);
14885 if (!qInfo)
14886 continue;
14888 if (!original_caster && !qInfo->HasQuestFlag(QUEST_FLAGS_SHARABLE))
14889 continue;
14891 if (!qInfo->HasSpecialFlag(QUEST_SPECIAL_FLAG_KILL_OR_CAST))
14892 continue;
14894 QuestStatusData& q_status = mQuestStatus[questid];
14896 if (q_status.m_status != QUEST_STATUS_INCOMPLETE)
14897 continue;
14899 for (int j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
14901 // skip kill creature objective (0) or wrong spell casts
14902 if (qInfo->ReqSpell[j] != spell_id)
14903 continue;
14905 uint32 reqTarget = 0;
14907 if (isCreature)
14909 // creature activate objectives
14910 if (qInfo->ReqCreatureOrGOId[j] > 0)
14911 // checked at quest_template loading
14912 reqTarget = qInfo->ReqCreatureOrGOId[j];
14914 else
14916 // GO activate objective
14917 if (qInfo->ReqCreatureOrGOId[j] < 0)
14918 // checked at quest_template loading
14919 reqTarget = - qInfo->ReqCreatureOrGOId[j];
14922 // other not this creature/GO related objectives
14923 if (reqTarget != entry)
14924 continue;
14926 uint32 reqCastCount = qInfo->ReqCreatureOrGOCount[j];
14927 uint32 curCastCount = q_status.m_creatureOrGOcount[j];
14928 if (curCastCount < reqCastCount)
14930 q_status.m_creatureOrGOcount[j] = curCastCount + addCastCount;
14931 if (q_status.uState != QUEST_NEW)
14932 q_status.uState = QUEST_CHANGED;
14934 SendQuestUpdateAddCreatureOrGo(qInfo, guid, j, q_status.m_creatureOrGOcount[j]);
14937 if (CanCompleteQuest(questid))
14938 CompleteQuest(questid);
14940 // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization).
14941 break;
14946 void Player::TalkedToCreature(uint32 entry, ObjectGuid guid)
14948 uint32 addTalkCount = 1;
14949 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
14951 uint32 questid = GetQuestSlotQuestId(i);
14952 if (!questid)
14953 continue;
14955 Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid);
14956 if (!qInfo)
14957 continue;
14959 QuestStatusData& q_status = mQuestStatus[questid];
14961 if (q_status.m_status == QUEST_STATUS_INCOMPLETE)
14963 if (qInfo->HasSpecialFlag(QuestSpecialFlags(QUEST_SPECIAL_FLAG_KILL_OR_CAST | QUEST_SPECIAL_FLAG_SPEAKTO)))
14965 for (int j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
14967 // skip spell casts and Gameobject objectives
14968 if (qInfo->ReqSpell[j] > 0 || qInfo->ReqCreatureOrGOId[j] < 0)
14969 continue;
14971 uint32 reqTarget = 0;
14973 if (qInfo->ReqCreatureOrGOId[j] > 0) // creature activate objectives
14974 // checked at quest_template loading
14975 reqTarget = qInfo->ReqCreatureOrGOId[j];
14976 else
14977 continue;
14979 if (reqTarget == entry)
14981 uint32 reqTalkCount = qInfo->ReqCreatureOrGOCount[j];
14982 uint32 curTalkCount = q_status.m_creatureOrGOcount[j];
14983 if (curTalkCount < reqTalkCount)
14985 q_status.m_creatureOrGOcount[j] = curTalkCount + addTalkCount;
14986 if (q_status.uState != QUEST_NEW) q_status.uState = QUEST_CHANGED;
14988 SendQuestUpdateAddCreatureOrGo(qInfo, guid, j, q_status.m_creatureOrGOcount[j]);
14990 if (CanCompleteQuest(questid))
14991 CompleteQuest(questid);
14993 // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization).
14994 continue;
15002 void Player::MoneyChanged(uint32 count)
15004 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
15006 uint32 questid = GetQuestSlotQuestId(i);
15007 if (!questid)
15008 continue;
15010 Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid);
15011 if (qInfo && qInfo->GetRewOrReqMoney() < 0)
15013 QuestStatusData& q_status = mQuestStatus[questid];
15015 if (q_status.m_status == QUEST_STATUS_INCOMPLETE)
15017 if (int32(count) >= -qInfo->GetRewOrReqMoney())
15019 if (CanCompleteQuest(questid))
15020 CompleteQuest(questid);
15023 else if (q_status.m_status == QUEST_STATUS_COMPLETE)
15025 if (int32(count) < -qInfo->GetRewOrReqMoney())
15026 IncompleteQuest(questid);
15032 void Player::ReputationChanged(FactionEntry const* factionEntry)
15034 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
15036 if (uint32 questid = GetQuestSlotQuestId(i))
15038 if (Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid))
15040 if (qInfo->GetRepObjectiveFaction() == factionEntry->ID)
15042 QuestStatusData& q_status = mQuestStatus[questid];
15043 if (q_status.m_status == QUEST_STATUS_INCOMPLETE)
15045 if (GetReputationMgr().GetReputation(factionEntry) >= qInfo->GetRepObjectiveValue())
15046 if (CanCompleteQuest(questid))
15047 CompleteQuest(questid);
15049 else if (q_status.m_status == QUEST_STATUS_COMPLETE)
15051 if (GetReputationMgr().GetReputation(factionEntry) < qInfo->GetRepObjectiveValue())
15052 IncompleteQuest(questid);
15060 bool Player::HasQuestForItem(uint32 itemid) const
15062 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
15064 uint32 questid = GetQuestSlotQuestId(i);
15065 if (questid == 0)
15066 continue;
15068 QuestStatusMap::const_iterator qs_itr = mQuestStatus.find(questid);
15069 if (qs_itr == mQuestStatus.end())
15070 continue;
15072 QuestStatusData const& q_status = qs_itr->second;
15074 if (q_status.m_status == QUEST_STATUS_INCOMPLETE)
15076 Quest const* qinfo = sObjectMgr.GetQuestTemplate(questid);
15077 if (!qinfo)
15078 continue;
15080 // hide quest if player is in raid-group and quest is no raid quest
15081 if (GetGroup() && GetGroup()->isRaidGroup() && !qinfo->IsAllowedInRaid() && !InBattleGround())
15082 continue;
15084 // There should be no mixed ReqItem/ReqSource drop
15085 // This part for ReqItem drop
15086 for (int j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j)
15088 if (itemid == qinfo->ReqItemId[j] && q_status.m_itemcount[j] < qinfo->ReqItemCount[j])
15089 return true;
15091 // This part - for ReqSource
15092 for (int j = 0; j < QUEST_SOURCE_ITEM_IDS_COUNT; ++j)
15094 // examined item is a source item
15095 if (qinfo->ReqSourceId[j] == itemid)
15097 ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(itemid);
15099 // 'unique' item
15100 if (pProto->MaxCount && (int32)GetItemCount(itemid, true) < pProto->MaxCount)
15101 return true;
15103 // allows custom amount drop when not 0
15104 if (qinfo->ReqSourceCount[j])
15106 if (GetItemCount(itemid, true) < qinfo->ReqSourceCount[j])
15107 return true;
15109 else if ((int32)GetItemCount(itemid, true) < pProto->Stackable)
15110 return true;
15115 return false;
15118 // Used for quests having some event (explore, escort, "external event") as quest objective.
15119 void Player::SendQuestCompleteEvent(uint32 quest_id)
15121 if (quest_id)
15123 WorldPacket data(SMSG_QUESTUPDATE_COMPLETE, 4);
15124 data << uint32(quest_id);
15125 GetSession()->SendPacket(&data);
15126 DEBUG_LOG("WORLD: Sent SMSG_QUESTUPDATE_COMPLETE quest = %u", quest_id);
15130 void Player::SendQuestReward(Quest const* pQuest, uint32 XP, Object* /*questGiver*/)
15132 uint32 questid = pQuest->GetQuestId();
15133 DEBUG_LOG("WORLD: Sent SMSG_QUESTGIVER_QUEST_COMPLETE quest = %u", questid);
15134 WorldPacket data(SMSG_QUESTGIVER_QUEST_COMPLETE, (4 + 4 + 4 + 4 + 4));
15135 data << uint32(pQuest->GetBonusTalents());
15136 data << uint32(pQuest->GetRewSkillValue());
15138 if (getLevel() < sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL))
15140 data << uint32(pQuest->GetRewOrReqMoney());
15141 data << uint32(XP);
15143 else
15145 data << uint32(pQuest->GetRewOrReqMoney() + int32(pQuest->GetRewMoneyMaxLevel() * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)));
15146 data << uint32(0);
15149 data << uint32(questid);
15150 data << uint32(pQuest->GetRewSkill());
15152 data.WriteBit(0); // unk
15153 data.WriteBit(1); // unk
15155 GetSession()->SendPacket(&data);
15157 if (QuestPhaseMapsVector const* QuestPhaseVector = sObjectMgr.GetQuestPhaseMapVector(questid))
15159 for (QuestPhaseMapsVector::const_iterator itr = QuestPhaseVector->begin(); itr != QuestPhaseVector->end(); ++itr)
15161 GetSession()->SendSetPhaseShift(itr->PhaseMask, itr->MapId);
15162 CharacterDatabase.PExecute("REPLACE INTO character_phase_data` (`guid`, `map`, `phase`) VALUES (%u, %u, %u)", GetSession()->GetPlayer()->GetGUIDLow(), itr->MapId, itr->PhaseMask);
15167 void Player::SendQuestFailed(uint32 quest_id, InventoryResult reason)
15169 if (quest_id)
15171 WorldPacket data(SMSG_QUESTGIVER_QUEST_FAILED, 4 + 4);
15172 data << uint32(quest_id);
15173 data << uint32(reason); // failed reason (valid reasons: 4, 16, 50, 17, 74, other values show default message)
15174 GetSession()->SendPacket(&data);
15175 DEBUG_LOG("WORLD: Sent SMSG_QUESTGIVER_QUEST_FAILED");
15179 void Player::SendQuestTimerFailed(uint32 quest_id)
15181 if (quest_id)
15183 WorldPacket data(SMSG_QUESTUPDATE_FAILEDTIMER, 4);
15184 data << uint32(quest_id);
15185 GetSession()->SendPacket(&data);
15186 DEBUG_LOG("WORLD: Sent SMSG_QUESTUPDATE_FAILEDTIMER");
15190 void Player::SendCanTakeQuestResponse(uint32 msg) const
15192 WorldPacket data(SMSG_QUESTGIVER_QUEST_INVALID, 4);
15193 data << uint32(msg);
15194 GetSession()->SendPacket(&data);
15195 DEBUG_LOG("WORLD: Sent SMSG_QUESTGIVER_QUEST_INVALID");
15198 void Player::SendQuestConfirmAccept(const Quest* pQuest, Player* pReceiver)
15200 if (pReceiver)
15202 int loc_idx = pReceiver->GetSession()->GetSessionDbLocaleIndex();
15203 std::string title = pQuest->GetTitle();
15204 sObjectMgr.GetQuestLocaleStrings(pQuest->GetQuestId(), loc_idx, &title);
15206 WorldPacket data(SMSG_QUEST_CONFIRM_ACCEPT, (4 + title.size() + 8));
15207 data << uint32(pQuest->GetQuestId());
15208 data << title;
15209 data << GetObjectGuid();
15210 pReceiver->GetSession()->SendPacket(&data);
15212 DEBUG_LOG("WORLD: Sent SMSG_QUEST_CONFIRM_ACCEPT");
15216 void Player::SendPushToPartyResponse(Player* pPlayer, uint32 msg)
15218 if (pPlayer)
15220 WorldPacket data(MSG_QUEST_PUSH_RESULT, (8 + 1));
15221 data << pPlayer->GetObjectGuid();
15222 data << uint8(msg); // valid values: 0-8
15223 GetSession()->SendPacket(&data);
15224 DEBUG_LOG("WORLD: Sent MSG_QUEST_PUSH_RESULT");
15228 void Player::SendQuestUpdateAddCreatureOrGo(Quest const* pQuest, ObjectGuid guid, uint32 creatureOrGO_idx, uint32 count)
15230 MANGOS_ASSERT(count < 65536 && "mob/GO count store in 16 bits 2^16 = 65536 (0..65536)");
15232 int32 entry = pQuest->ReqCreatureOrGOId[ creatureOrGO_idx ];
15233 if (entry < 0)
15234 // client expected gameobject template id in form (id|0x80000000)
15235 entry = (-entry) | 0x80000000;
15237 WorldPacket data(SMSG_QUESTUPDATE_ADD_KILL, (4 * 4 + 8));
15238 DEBUG_LOG("WORLD: Sent SMSG_QUESTUPDATE_ADD_KILL");
15239 data << uint32(pQuest->GetQuestId());
15240 data << uint32(entry);
15241 data << uint32(count);
15242 data << uint32(pQuest->ReqCreatureOrGOCount[ creatureOrGO_idx ]);
15243 data << guid;
15244 GetSession()->SendPacket(&data);
15246 uint16 log_slot = FindQuestSlot(pQuest->GetQuestId());
15247 if (log_slot < MAX_QUEST_LOG_SIZE)
15248 SetQuestSlotCounter(log_slot, creatureOrGO_idx, count);
15251 /*********************************************************/
15252 /*** LOAD SYSTEM ***/
15253 /*********************************************************/
15255 void Player::_LoadDeclinedNames(QueryResult* result)
15257 if (!result)
15258 return;
15260 delete m_declinedname;
15261 m_declinedname = new DeclinedName;
15263 Field* fields = result->Fetch();
15264 for (int i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
15265 m_declinedname->name[i] = fields[i].GetCppString();
15267 delete result;
15270 void Player::_LoadArenaTeamInfo(QueryResult* result)
15272 // arenateamid, played_week, played_season, personal_rating
15273 memset((void*)&m_uint32Values[PLAYER_FIELD_ARENA_TEAM_INFO_1_1], 0, sizeof(uint32) * MAX_ARENA_SLOT * ARENA_TEAM_END);
15274 if (!result)
15275 return;
15279 Field* fields = result->Fetch();
15281 uint32 arenateamid = fields[0].GetUInt32();
15282 uint32 played_week = fields[1].GetUInt32();
15283 uint32 played_season = fields[2].GetUInt32();
15284 uint32 wons_season = fields[3].GetUInt32();
15285 uint32 personal_rating = fields[4].GetUInt32();
15287 ArenaTeam* aTeam = sObjectMgr.GetArenaTeamById(arenateamid);
15288 if (!aTeam)
15290 sLog.outError("Player::_LoadArenaTeamInfo: couldn't load arenateam %u", arenateamid);
15291 continue;
15293 uint8 arenaSlot = aTeam->GetSlot();
15295 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_ID, arenateamid);
15296 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_TYPE, aTeam->GetType());
15297 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_MEMBER, (aTeam->GetCaptainGuid() == GetObjectGuid()) ? 0 : 1);
15298 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_GAMES_WEEK, played_week);
15299 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_GAMES_SEASON, played_season);
15300 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_WINS_SEASON, wons_season);
15301 SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_PERSONAL_RATING, personal_rating);
15304 while (result->NextRow());
15305 delete result;
15308 void Player::_LoadEquipmentSets(QueryResult* result)
15310 // SetPQuery(PLAYER_LOGIN_QUERY_LOADEQUIPMENTSETS, "SELECT setguid, setindex, name, iconname, ignore_mask, item0, item1, item2, item3, item4, item5, item6, item7, item8, item9, item10, item11, item12, item13, item14, item15, item16, item17, item18 FROM character_equipmentsets WHERE guid = '%u' ORDER BY setindex", GUID_LOPART(m_guid));
15311 if (!result)
15312 return;
15314 uint32 count = 0;
15317 Field* fields = result->Fetch();
15319 EquipmentSet eqSet;
15321 eqSet.Guid = fields[0].GetUInt64();
15322 uint32 index = fields[1].GetUInt32();
15323 eqSet.Name = fields[2].GetCppString();
15324 eqSet.IconName = fields[3].GetCppString();
15325 eqSet.IgnoreMask = fields[4].GetUInt32();
15326 eqSet.state = EQUIPMENT_SET_UNCHANGED;
15328 for (uint32 i = 0; i < EQUIPMENT_SLOT_END; ++i)
15329 eqSet.Items[i] = fields[5 + i].GetUInt32();
15331 m_EquipmentSets[index] = eqSet;
15333 ++count;
15335 if (count >= MAX_EQUIPMENT_SET_INDEX) // client limit
15336 break;
15338 while (result->NextRow());
15339 delete result;
15342 void Player::_LoadBGData(QueryResult* result)
15344 if (!result)
15345 return;
15347 // Expecting only one row
15348 Field* fields = result->Fetch();
15349 /* bgInstanceID, bgTeam, x, y, z, o, map, taxi[0], taxi[1], mountSpell */
15350 m_bgData.bgInstanceID = fields[0].GetUInt32();
15351 m_bgData.bgTeam = Team(fields[1].GetUInt32());
15352 m_bgData.joinPos = WorldLocation(fields[6].GetUInt32(), // Map
15353 fields[2].GetFloat(), // X
15354 fields[3].GetFloat(), // Y
15355 fields[4].GetFloat(), // Z
15356 fields[5].GetFloat()); // Orientation
15357 m_bgData.taxiPath[0] = fields[7].GetUInt32();
15358 m_bgData.taxiPath[1] = fields[8].GetUInt32();
15359 m_bgData.mountSpell = fields[9].GetUInt32();
15361 delete result;
15364 bool Player::LoadPositionFromDB(ObjectGuid guid, uint32& mapid, float& x, float& y, float& z, float& o, bool& in_flight)
15366 QueryResult* result = CharacterDatabase.PQuery("SELECT position_x,position_y,position_z,orientation,map,taxi_path FROM characters WHERE guid = '%u'", guid.GetCounter());
15367 if (!result)
15368 return false;
15370 Field* fields = result->Fetch();
15372 x = fields[0].GetFloat();
15373 y = fields[1].GetFloat();
15374 z = fields[2].GetFloat();
15375 o = fields[3].GetFloat();
15376 mapid = fields[4].GetUInt32();
15377 in_flight = !fields[5].GetCppString().empty();
15379 delete result;
15380 return true;
15383 void Player::_LoadIntoDataField(const char* data, uint32 startOffset, uint32 count)
15385 if (!data)
15386 return;
15388 Tokens tokens = StrSplit(data, " ");
15390 if (tokens.size() != count)
15391 return;
15393 Tokens::iterator iter;
15394 uint32 index;
15395 for (iter = tokens.begin(), index = 0; index < count; ++iter, ++index)
15397 m_uint32Values[startOffset + index] = atol((*iter).c_str());
15401 bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder)
15403 // 0 1 2 3 4 5 6 7 8 9 10 11
15404 // SELECT guid, account, name, race, class, gender, level, xp, money, playerBytes, playerBytes2, playerFlags,"
15405 // 12 13 14 15 16 17 18 19 20 21 22 23 24
15406 //"position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost,"
15407 // 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
15408 //"resettalents_time, primary_trees, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeon_difficulty,"
15409 // 40 41 42 43 44 45
15410 //"totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk,"
15411 // 46 47 48 49 50 51 52 53 54 55 56 57 58
15412 //"health, power1, power2, power3, power4, power5, specCount, activeSpec, exploredZones, equipmentCache, knownTitles, actionBars, slot FROM characters WHERE guid = '%u'", GUID_LOPART(m_guid));
15413 QueryResult *result = holder->GetResult(PLAYER_LOGIN_QUERY_LOADFROM);
15415 if (!result)
15417 sLog.outError("%s not found in table `characters`, can't load. ", guid.GetString().c_str());
15418 return false;
15421 Field* fields = result->Fetch();
15423 uint32 dbAccountId = fields[1].GetUInt32();
15425 // check if the character's account in the db and the logged in account match.
15426 // player should be able to load/delete character only with correct account!
15427 if (dbAccountId != GetSession()->GetAccountId())
15429 sLog.outError("%s loading from wrong account (is: %u, should be: %u)",
15430 guid.GetString().c_str(), GetSession()->GetAccountId(), dbAccountId);
15431 delete result;
15432 return false;
15435 Object::_Create(guid.GetCounter(), 0, HIGHGUID_PLAYER);
15437 m_name = fields[2].GetCppString();
15439 // check name limitations
15440 if (ObjectMgr::CheckPlayerName(m_name) != CHAR_NAME_SUCCESS ||
15441 (GetSession()->GetSecurity() == SEC_PLAYER && sObjectMgr.IsReservedName(m_name)))
15443 delete result;
15444 CharacterDatabase.PExecute("UPDATE characters SET at_login = at_login | '%u' WHERE guid ='%u'",
15445 uint32(AT_LOGIN_RENAME), guid.GetCounter());
15446 return false;
15449 // overwrite possible wrong/corrupted guid
15450 SetGuidValue(OBJECT_FIELD_GUID, guid);
15452 // overwrite some data fields
15453 SetByteValue(UNIT_FIELD_BYTES_0, 0, fields[3].GetUInt8()); // race
15454 SetByteValue(UNIT_FIELD_BYTES_0, 1, fields[4].GetUInt8()); // class
15456 uint8 gender = fields[5].GetUInt8() & 0x01; // allowed only 1 bit values male/female cases (for fit drunk gender part)
15457 SetByteValue(UNIT_FIELD_BYTES_0, 2, gender); // gender
15459 SetUInt32Value(UNIT_FIELD_LEVEL, fields[6].GetUInt8());
15460 SetUInt32Value(PLAYER_XP, fields[7].GetUInt32());
15462 _LoadIntoDataField(fields[54].GetString(), PLAYER_EXPLORED_ZONES_1, PLAYER_EXPLORED_ZONES_SIZE);
15463 _LoadIntoDataField(fields[56].GetString(), PLAYER__FIELD_KNOWN_TITLES, KNOWN_TITLES_SIZE*2);
15465 InitDisplayIds(); // model, scale and model data
15467 SetFloatValue(UNIT_FIELD_HOVERHEIGHT, 1.0f);
15469 // just load criteria/achievement data, safe call before any load, and need, because some spell/item/quest loading
15470 // can triggering achievement criteria update that will be lost if this call will later
15471 m_achievementMgr.LoadFromDB(holder->GetResult(PLAYER_LOGIN_QUERY_LOADACHIEVEMENTS), holder->GetResult(PLAYER_LOGIN_QUERY_LOADCRITERIAPROGRESS));
15473 uint64 money = fields[8].GetUInt64();
15474 if (money > MAX_MONEY_AMOUNT)
15475 money = MAX_MONEY_AMOUNT;
15476 SetMoney(money);
15478 SetUInt32Value(PLAYER_BYTES, fields[9].GetUInt32());
15479 SetUInt32Value(PLAYER_BYTES_2, fields[10].GetUInt32());
15481 m_drunk = fields[45].GetUInt16();
15483 SetUInt16Value(PLAYER_BYTES_3, 0, (m_drunk & 0xFFFE) | gender);
15485 SetUInt32Value(PLAYER_FLAGS, fields[11].GetUInt32());
15486 SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, fields[44].GetInt32());
15488 // Action bars state
15489 SetByteValue(PLAYER_FIELD_BYTES, 2, fields[57].GetUInt8());
15491 m_slot = fields[58].GetUInt8();
15493 // cleanup inventory related item value fields (its will be filled correctly in _LoadInventory)
15494 for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
15496 SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), ObjectGuid());
15497 SetVisibleItemSlot(slot, NULL);
15499 delete m_items[slot];
15500 m_items[slot] = NULL;
15503 DEBUG_FILTER_LOG(LOG_FILTER_PLAYER_STATS, "Load Basic value of player %s is: ", m_name.c_str());
15504 outDebugStatsValues();
15506 // Need to call it to initialize m_team (m_team can be calculated from race)
15507 // Other way is to saves m_team into characters table.
15508 setFactionForRace(getRace());
15509 SetCharm(NULL);
15511 // load home bind and check in same time class/race pair, it used later for restore broken positions
15512 if (!_LoadHomeBind(holder->GetResult(PLAYER_LOGIN_QUERY_LOADHOMEBIND)))
15514 delete result;
15515 return false;
15518 InitPrimaryProfessions(); // to max set before any spell loaded
15520 // init saved position, and fix it later if problematic
15521 uint32 transGUID = fields[31].GetUInt32();
15522 Relocate(fields[12].GetFloat(), fields[13].GetFloat(), fields[14].GetFloat(), fields[16].GetFloat());
15523 SetLocationMapId(fields[15].GetUInt32());
15525 uint32 difficulty = fields[39].GetUInt32();
15526 if (difficulty >= MAX_DUNGEON_DIFFICULTY || getLevel() < LEVELREQUIREMENT_HEROIC)
15527 difficulty = DUNGEON_DIFFICULTY_NORMAL;
15528 SetDungeonDifficulty(Difficulty(difficulty)); // may be changed in _LoadGroup
15530 _LoadGroup(holder->GetResult(PLAYER_LOGIN_QUERY_LOADGROUP));
15532 _LoadArenaTeamInfo(holder->GetResult(PLAYER_LOGIN_QUERY_LOADARENAINFO));
15534 // check arena teams integrity
15535 for (uint32 arena_slot = 0; arena_slot < MAX_ARENA_SLOT; ++arena_slot)
15537 uint32 arena_team_id = GetArenaTeamId(arena_slot);
15538 if (!arena_team_id)
15539 continue;
15541 if (ArenaTeam* at = sObjectMgr.GetArenaTeamById(arena_team_id))
15542 if (at->HaveMember(GetObjectGuid()))
15543 continue;
15545 // arena team not exist or not member, cleanup fields
15546 for (int j = 0; j < ARENA_TEAM_END; ++j)
15547 SetArenaTeamInfoField(arena_slot, ArenaTeamInfoType(j), 0);
15550 SetUInt32Value(PLAYER_FIELD_LIFETIME_HONORBALE_KILLS, fields[40].GetUInt32());
15551 SetUInt16Value(PLAYER_FIELD_KILLS, 0, fields[41].GetUInt16()); // today
15552 SetUInt16Value(PLAYER_FIELD_KILLS, 1, fields[42].GetUInt16()); // yesterday
15554 _LoadBoundInstances(holder->GetResult(PLAYER_LOGIN_QUERY_LOADBOUNDINSTANCES));
15556 if (!IsPositionValid())
15558 sLog.outError("%s have invalid coordinates (X: %f Y: %f Z: %f O: %f). Teleport to default race/class locations.",
15559 guid.GetString().c_str(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
15560 RelocateToHomebind();
15562 transGUID = 0;
15564 m_movementInfo.ClearTransportData();
15567 _LoadBGData(holder->GetResult(PLAYER_LOGIN_QUERY_LOADBGDATA));
15569 if (m_bgData.bgInstanceID) // saved in BattleGround
15571 BattleGround* currentBg = sBattleGroundMgr.GetBattleGround(m_bgData.bgInstanceID, BATTLEGROUND_TYPE_NONE);
15573 bool player_at_bg = currentBg && currentBg->IsPlayerInBattleGround(GetObjectGuid());
15575 if (player_at_bg && currentBg->GetStatus() != STATUS_WAIT_LEAVE)
15577 BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BGQueueTypeId(currentBg->GetTypeID(), currentBg->GetArenaType());
15578 AddBattleGroundQueueId(bgQueueTypeId);
15580 m_bgData.bgTypeID = currentBg->GetTypeID(); // bg data not marked as modified
15582 // join player to battleground group
15583 currentBg->EventPlayerLoggedIn(this, GetObjectGuid());
15584 currentBg->AddOrSetPlayerToCorrectBgGroup(this, GetObjectGuid(), m_bgData.bgTeam);
15586 SetInviteForBattleGroundQueueType(bgQueueTypeId, currentBg->GetInstanceID());
15588 else
15590 // leave bg
15591 if (player_at_bg)
15592 currentBg->RemovePlayerAtLeave(GetObjectGuid(), false, true);
15594 // move to bg enter point
15595 const WorldLocation& _loc = GetBattleGroundEntryPoint();
15596 SetLocationMapId(_loc.mapid);
15597 Relocate(_loc.coord_x, _loc.coord_y, _loc.coord_z, _loc.orientation);
15599 // We are not in BG anymore
15600 SetBattleGroundId(0, BATTLEGROUND_TYPE_NONE);
15601 // remove outdated DB data in DB
15602 _SaveBGData();
15605 else
15607 MapEntry const* mapEntry = sMapStore.LookupEntry(GetMapId());
15608 // if server restart after player save in BG or area
15609 // player can have current coordinates in to BG/Arena map, fix this
15610 if (!mapEntry || mapEntry->IsBattleGroundOrArena())
15612 const WorldLocation& _loc = GetBattleGroundEntryPoint();
15613 SetLocationMapId(_loc.mapid);
15614 Relocate(_loc.coord_x, _loc.coord_y, _loc.coord_z, _loc.orientation);
15616 // We are not in BG anymore
15617 SetBattleGroundId(0, BATTLEGROUND_TYPE_NONE);
15618 // remove outdated DB data in DB
15619 _SaveBGData();
15623 if (transGUID != 0)
15625 m_movementInfo.SetTransportData(ObjectGuid(HIGHGUID_MO_TRANSPORT, transGUID), fields[27].GetFloat(), fields[28].GetFloat(), fields[29].GetFloat(), fields[30].GetFloat(), 0, -1);
15627 if (!MaNGOS::IsValidMapCoord(
15628 GetPositionX() + m_movementInfo.GetTransportPos()->x, GetPositionY() + m_movementInfo.GetTransportPos()->y,
15629 GetPositionZ() + m_movementInfo.GetTransportPos()->z, GetOrientation() + m_movementInfo.GetTransportPos()->o) ||
15630 // transport size limited
15631 m_movementInfo.GetTransportPos()->x > 50 || m_movementInfo.GetTransportPos()->y > 50 || m_movementInfo.GetTransportPos()->z > 50)
15633 sLog.outError("%s have invalid transport coordinates (X: %f Y: %f Z: %f O: %f). Teleport to default race/class locations.",
15634 guid.GetString().c_str(), GetPositionX() + m_movementInfo.GetTransportPos()->x, GetPositionY() + m_movementInfo.GetTransportPos()->y,
15635 GetPositionZ() + m_movementInfo.GetTransportPos()->z, GetOrientation() + m_movementInfo.GetTransportPos()->o);
15637 RelocateToHomebind();
15639 m_movementInfo.ClearTransportData();
15641 transGUID = 0;
15645 if (transGUID != 0)
15647 for (MapManager::TransportSet::const_iterator iter = sMapMgr.m_Transports.begin(); iter != sMapMgr.m_Transports.end(); ++iter)
15649 if ((*iter)->GetGUIDLow() == transGUID)
15651 MapEntry const* transMapEntry = sMapStore.LookupEntry((*iter)->GetMapId());
15652 // client without expansion support
15653 if (GetSession()->Expansion() < transMapEntry->Expansion())
15655 DEBUG_LOG("Player %s using client without required expansion tried login at transport at non accessible map %u", GetName(), (*iter)->GetMapId());
15656 break;
15659 m_transport = *iter;
15660 m_transport->AddPassenger(this);
15661 SetLocationMapId(m_transport->GetMapId());
15662 break;
15666 if (!m_transport)
15668 sLog.outError("%s have problems with transport guid (%u). Teleport to default race/class locations.",
15669 guid.GetString().c_str(), transGUID);
15671 RelocateToHomebind();
15673 m_movementInfo.ClearTransportData();
15675 transGUID = 0;
15678 else // not transport case
15680 MapEntry const* mapEntry = sMapStore.LookupEntry(GetMapId());
15681 // client without expansion support
15682 if (GetSession()->Expansion() < mapEntry->Expansion())
15684 DEBUG_LOG("Player %s using client without required expansion tried login at non accessible map %u", GetName(), GetMapId());
15685 RelocateToHomebind();
15689 // player bounded instance saves loaded in _LoadBoundInstances, group versions at group loading
15690 DungeonPersistentState* state = GetBoundInstanceSaveForSelfOrGroup(GetMapId());
15692 // load the player's map here if it's not already loaded
15693 SetMap(sMapMgr.CreateMap(GetMapId(), this));
15695 // if the player is in an instance and it has been reset in the meantime teleport him to the entrance
15696 if (GetInstanceId() && !state)
15698 AreaTrigger const* at = sObjectMgr.GetMapEntranceTrigger(GetMapId());
15699 if (at)
15700 Relocate(at->target_X, at->target_Y, at->target_Z, at->target_Orientation);
15701 else
15702 sLog.outError("Player %s(GUID: %u) logged in to a reset instance (map: %u) and there is no area-trigger leading to this map. Thus he can't be ported back to the entrance. This _might_ be an exploit attempt.", GetName(), GetGUIDLow(), GetMapId());
15705 SaveRecallPosition();
15707 time_t now = time(NULL);
15708 time_t logoutTime = time_t(fields[22].GetUInt64());
15710 // since last logout (in seconds)
15711 uint32 time_diff = uint32(now - logoutTime);
15713 // set value, including drunk invisibility detection
15714 // calculate sobering. after 15 minutes logged out, the player will be sober again
15715 float soberFactor;
15716 if (time_diff > 15 * MINUTE)
15717 soberFactor = 0;
15718 else
15719 soberFactor = 1 - time_diff / (15.0f * MINUTE);
15720 uint16 newDrunkenValue = uint16(soberFactor * m_drunk);
15721 SetDrunkValue(newDrunkenValue);
15723 m_cinematic = fields[18].GetUInt32();
15724 m_Played_time[PLAYED_TIME_TOTAL] = fields[19].GetUInt32();
15725 m_Played_time[PLAYED_TIME_LEVEL] = fields[20].GetUInt32();
15727 m_resetTalentsCost = fields[24].GetUInt32();
15728 m_resetTalentsTime = time_t(fields[25].GetUInt64());
15730 // reserve some flags
15731 uint32 old_safe_flags = GetUInt32Value(PLAYER_FLAGS) & (PLAYER_FLAGS_HIDE_CLOAK | PLAYER_FLAGS_HIDE_HELM);
15733 if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GM))
15734 SetUInt32Value(PLAYER_FLAGS, 0 | old_safe_flags);
15736 m_taxi.LoadTaxiMask(fields[17].GetString()); // must be before InitTaxiNodesForLevel
15738 uint32 extraflags = fields[32].GetUInt32();
15740 m_stableSlots = fields[33].GetUInt32();
15741 if (m_stableSlots > MAX_PET_STABLES)
15743 sLog.outError("Player can have not more %u stable slots, but have in DB %u", MAX_PET_STABLES, uint32(m_stableSlots));
15744 m_stableSlots = MAX_PET_STABLES;
15747 m_atLoginFlags = fields[34].GetUInt32();
15749 // Update Honor kills data
15750 m_lastHonorKillsUpdateTime = logoutTime;
15751 UpdateHonorKills();
15753 m_deathExpireTime = (time_t)fields[37].GetUInt64();
15754 if (m_deathExpireTime > now + MAX_DEATH_COUNT * DEATH_EXPIRE_STEP)
15755 m_deathExpireTime = now + MAX_DEATH_COUNT * DEATH_EXPIRE_STEP - 1;
15757 std::string taxi_nodes = fields[38].GetCppString();
15759 // clear channel spell data (if saved at channel spell casting)
15760 SetChannelObjectGuid(ObjectGuid());
15761 SetUInt32Value(UNIT_CHANNEL_SPELL, 0);
15763 // clear charm/summon related fields
15764 SetCharm(NULL);
15765 SetPet(NULL);
15766 SetTargetGuid(ObjectGuid());
15767 SetCharmerGuid(ObjectGuid());
15768 SetOwnerGuid(ObjectGuid());
15769 SetCreatorGuid(ObjectGuid());
15771 // reset some aura modifiers before aura apply
15773 SetGuidValue(PLAYER_FARSIGHT, ObjectGuid());
15774 SetUInt32Value(PLAYER_TRACK_CREATURES, 0);
15775 SetUInt32Value(PLAYER_TRACK_RESOURCES, 0);
15777 // cleanup aura list explicitly before skill load where some spells can be applied
15778 RemoveAllAuras();
15780 // make sure the unit is considered out of combat for proper loading
15781 ClearInCombat();
15783 // make sure the unit is considered not in duel for proper loading
15784 SetGuidValue(PLAYER_DUEL_ARBITER, ObjectGuid());
15785 SetUInt32Value(PLAYER_DUEL_TEAM, 0);
15787 m_specsCount = fields[52].GetUInt8();
15788 m_activeSpec = fields[53].GetUInt8();
15790 Tokens talentTrees = StrSplit(fields[26].GetString(), " ");
15791 for (uint8 i = 0; i < MAX_TALENT_SPEC_COUNT; ++i)
15793 if (i >= talentTrees.size())
15794 break;
15796 uint32 talentTree = atol(talentTrees[i].c_str());
15797 if (!talentTree || sTalentTabStore.LookupEntry(talentTree))
15798 m_talentsPrimaryTree[i] = talentTree;
15799 else if (i == m_activeSpec)
15800 SetAtLoginFlag(AT_LOGIN_RESET_TALENTS); // invalid tree, reset talents
15803 // reset stats before loading any modifiers
15804 InitStatsForLevel();
15805 InitGlyphsForLevel();
15806 InitTaxiNodesForLevel();
15807 InitRunes();
15809 // rest bonus can only be calculated after InitStatsForLevel()
15810 m_rest_bonus = fields[21].GetFloat();
15812 if (time_diff > 0)
15814 // speed collect rest bonus in offline, in logout, far from tavern, city (section/in hour)
15815 float bubble0 = 0.031f;
15816 // speed collect rest bonus in offline, in logout, in tavern, city (section/in hour)
15817 float bubble1 = 0.125f;
15818 float bubble = fields[23].GetUInt32() > 0
15819 ? bubble1 * sWorld.getConfig(CONFIG_FLOAT_RATE_REST_OFFLINE_IN_TAVERN_OR_CITY)
15820 : bubble0 * sWorld.getConfig(CONFIG_FLOAT_RATE_REST_OFFLINE_IN_WILDERNESS);
15822 SetRestBonus(GetRestBonus() + time_diff * ((float)GetUInt32Value(PLAYER_NEXT_LEVEL_XP) / 72000)*bubble);
15825 // load skills after InitStatsForLevel because it triggering aura apply also
15826 _LoadSkills(holder->GetResult(PLAYER_LOGIN_QUERY_LOADSKILLS));
15828 // apply original stats mods before spell loading or item equipment that call before equip _RemoveStatsMods()
15830 // Mail
15831 _LoadMails(holder->GetResult(PLAYER_LOGIN_QUERY_LOADMAILS));
15832 _LoadMailedItems(holder->GetResult(PLAYER_LOGIN_QUERY_LOADMAILEDITEMS));
15833 UpdateNextMailTimeAndUnreads();
15835 _LoadGlyphs(holder->GetResult(PLAYER_LOGIN_QUERY_LOADGLYPHS));
15837 _LoadAuras(holder->GetResult(PLAYER_LOGIN_QUERY_LOADAURAS), time_diff);
15838 ApplyGlyphs(true);
15840 // add ghost flag (must be after aura load: PLAYER_FLAGS_GHOST set in aura)
15841 if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
15842 m_deathState = DEAD;
15844 _LoadCurrencies(holder->GetResult(PLAYER_LOGIN_QUERY_LOADCURRENCIES));
15845 _LoadSpells(holder->GetResult(PLAYER_LOGIN_QUERY_LOADSPELLS));
15847 // after spell load, learn rewarded spell if need also
15848 _LoadQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADQUESTSTATUS));
15849 _LoadDailyQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADDAILYQUESTSTATUS));
15850 _LoadWeeklyQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADWEEKLYQUESTSTATUS));
15851 _LoadMonthlyQuestStatus(holder->GetResult(PLAYER_LOGIN_QUERY_LOADMONTHLYQUESTSTATUS));
15853 _LoadTalents(holder->GetResult(PLAYER_LOGIN_QUERY_LOADTALENTS));
15855 // after spell and quest load
15856 InitTalentForLevel();
15857 learnDefaultSpells();
15859 // must be before inventory (some items required reputation check)
15860 m_reputationMgr.LoadFromDB(holder->GetResult(PLAYER_LOGIN_QUERY_LOADREPUTATION));
15862 _LoadInventory(holder->GetResult(PLAYER_LOGIN_QUERY_LOADINVENTORY), time_diff);
15863 _LoadItemLoot(holder->GetResult(PLAYER_LOGIN_QUERY_LOADITEMLOOT));
15865 // update items with duration and realtime
15866 UpdateItemDuration(time_diff, true);
15868 _LoadActions(holder->GetResult(PLAYER_LOGIN_QUERY_LOADACTIONS));
15870 m_social = sSocialMgr.LoadFromDB(holder->GetResult(PLAYER_LOGIN_QUERY_LOADSOCIALLIST), GetObjectGuid());
15872 // check PLAYER_CHOSEN_TITLE compatibility with PLAYER__FIELD_KNOWN_TITLES
15873 // note: PLAYER__FIELD_KNOWN_TITLES updated at quest status loaded
15874 uint32 curTitle = fields[43].GetUInt32();
15875 if (curTitle && !HasTitle(curTitle))
15876 curTitle = 0;
15878 SetUInt32Value(PLAYER_CHOSEN_TITLE, curTitle);
15880 // Not finish taxi flight path
15881 if (m_bgData.HasTaxiPath())
15883 m_taxi.ClearTaxiDestinations();
15884 for (int i = 0; i < 2; ++i)
15885 m_taxi.AddTaxiDestination(m_bgData.taxiPath[i]);
15887 else if (!m_taxi.LoadTaxiDestinationsFromString(taxi_nodes, GetTeam()))
15889 // problems with taxi path loading
15890 TaxiNodesEntry const* nodeEntry = NULL;
15891 if (uint32 node_id = m_taxi.GetTaxiSource())
15892 nodeEntry = sTaxiNodesStore.LookupEntry(node_id);
15894 if (!nodeEntry) // don't know taxi start node, to homebind
15896 sLog.outError("Character %u have wrong data in taxi destination list, teleport to homebind.", GetGUIDLow());
15897 RelocateToHomebind();
15899 else // have start node, to it
15901 sLog.outError("Character %u have too short taxi destination list, teleport to original node.", GetGUIDLow());
15902 SetLocationMapId(nodeEntry->map_id);
15903 Relocate(nodeEntry->x, nodeEntry->y, nodeEntry->z, 0.0f);
15906 // we can be relocated from taxi and still have an outdated Map pointer!
15907 // so we need to get a new Map pointer!
15908 SetMap(sMapMgr.CreateMap(GetMapId(), this));
15909 SaveRecallPosition(); // save as recall also to prevent recall and fall from sky
15911 m_taxi.ClearTaxiDestinations();
15914 if (uint32 node_id = m_taxi.GetTaxiSource())
15916 // save source node as recall coord to prevent recall and fall from sky
15917 TaxiNodesEntry const* nodeEntry = sTaxiNodesStore.LookupEntry(node_id);
15918 MANGOS_ASSERT(nodeEntry); // checked in m_taxi.LoadTaxiDestinationsFromString
15919 m_recallMap = nodeEntry->map_id;
15920 m_recallX = nodeEntry->x;
15921 m_recallY = nodeEntry->y;
15922 m_recallZ = nodeEntry->z;
15924 // flight will started later
15927 // has to be called after last Relocate() in Player::LoadFromDB
15928 SetFallInformation(0, GetPositionZ());
15930 _LoadSpellCooldowns(holder->GetResult(PLAYER_LOGIN_QUERY_LOADSPELLCOOLDOWNS));
15932 // Spell code allow apply any auras to dead character in load time in aura/spell/item loading
15933 // Do now before stats re-calculation cleanup for ghost state unexpected auras
15934 if (!isAlive())
15935 RemoveAllAurasOnDeath();
15937 // apply all stat bonuses from items and auras
15938 SetCanModifyStats(true);
15939 UpdateAllStats();
15941 // restore remembered power/health values (but not more max values)
15942 uint32 savedhealth = fields[46].GetUInt32();
15943 SetHealth(savedhealth > GetMaxHealth() ? GetMaxHealth() : savedhealth);
15945 static_assert(MAX_STORED_POWERS == 5, "Query not updated.");
15946 for (uint32 i = 0; i < MAX_STORED_POWERS; ++i)
15948 uint32 savedpower = fields[47 + i].GetUInt32();
15949 SetPowerByIndex(i, std::min(savedpower, GetMaxPowerByIndex(i)));
15952 DEBUG_FILTER_LOG(LOG_FILTER_PLAYER_STATS, "The value of player %s after load item and aura is: ", m_name.c_str());
15953 outDebugStatsValues();
15955 // all fields read
15956 delete result;
15958 // GM state
15959 if (GetSession()->GetSecurity() > SEC_PLAYER)
15961 switch (sWorld.getConfig(CONFIG_UINT32_GM_LOGIN_STATE))
15963 default:
15964 case 0: break; // disable
15965 case 1: SetGameMaster(true); break; // enable
15966 case 2: // save state
15967 if (extraflags & PLAYER_EXTRA_GM_ON)
15968 SetGameMaster(true);
15969 break;
15972 switch (sWorld.getConfig(CONFIG_UINT32_GM_VISIBLE_STATE))
15974 default:
15975 case 0: SetGMVisible(false); break; // invisible
15976 case 1: break; // visible
15977 case 2: // save state
15978 if (extraflags & PLAYER_EXTRA_GM_INVISIBLE)
15979 SetGMVisible(false);
15980 break;
15983 switch (sWorld.getConfig(CONFIG_UINT32_GM_ACCEPT_TICKETS))
15985 default:
15986 case 0: break; // disable
15987 case 1: SetAcceptTicket(true); break; // enable
15988 case 2: // save state
15989 if (extraflags & PLAYER_EXTRA_GM_ACCEPT_TICKETS)
15990 SetAcceptTicket(true);
15991 break;
15994 switch (sWorld.getConfig(CONFIG_UINT32_GM_CHAT))
15996 default:
15997 case 0: break; // disable
15998 case 1: SetGMChat(true); break; // enable
15999 case 2: // save state
16000 if (extraflags & PLAYER_EXTRA_GM_CHAT)
16001 SetGMChat(true);
16002 break;
16005 switch (sWorld.getConfig(CONFIG_UINT32_GM_WISPERING_TO))
16007 default:
16008 case 0: break; // disable
16009 case 1: SetAcceptWhispers(true); break; // enable
16010 case 2: // save state
16011 if (extraflags & PLAYER_EXTRA_ACCEPT_WHISPERS)
16012 SetAcceptWhispers(true);
16013 break;
16017 _LoadDeclinedNames(holder->GetResult(PLAYER_LOGIN_QUERY_LOADDECLINEDNAMES));
16019 m_achievementMgr.CheckAllAchievementCriteria();
16021 _LoadEquipmentSets(holder->GetResult(PLAYER_LOGIN_QUERY_LOADEQUIPMENTSETS));
16023 return true;
16026 bool Player::isAllowedToLoot(Creature* creature)
16028 // never tapped by any (mob solo kill)
16029 if (!creature->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TAPPED))
16030 return false;
16032 if (Player* recipient = creature->GetLootRecipient())
16034 if (recipient == this)
16035 return true;
16037 if (Group* otherGroup = recipient->GetGroup())
16039 Group* thisGroup = GetGroup();
16040 if (!thisGroup)
16041 return false;
16043 return thisGroup == otherGroup;
16045 return false;
16047 else
16048 // prevent other players from looting if the recipient got disconnected
16049 return !creature->HasLootRecipient();
16052 void Player::_LoadActions(QueryResult* result)
16054 for (int i = 0; i < MAX_TALENT_SPEC_COUNT; ++i)
16055 m_actionButtons[i].clear();
16057 // QueryResult *result = CharacterDatabase.PQuery("SELECT spec, button,action,type FROM character_action WHERE guid = '%u' ORDER BY button",GetGUIDLow());
16059 if (result)
16063 Field* fields = result->Fetch();
16065 uint8 spec = fields[0].GetUInt8();
16066 uint8 button = fields[1].GetUInt8();
16067 uint32 action = fields[2].GetUInt32();
16068 uint8 type = fields[3].GetUInt8();
16070 if (ActionButton* ab = addActionButton(spec, button, action, type))
16071 ab->uState = ACTIONBUTTON_UNCHANGED;
16072 else
16074 sLog.outError(" ...at loading, and will deleted in DB also");
16076 // Will deleted in DB at next save (it can create data until save but marked as deleted)
16077 m_actionButtons[spec][button].uState = ACTIONBUTTON_DELETED;
16080 while (result->NextRow());
16082 delete result;
16086 void Player::_LoadAuras(QueryResult* result, uint32 timediff)
16088 // RemoveAllAuras(); -- some spells casted before aura load, for example in LoadSkills, aura list explicitly cleaned early
16090 // QueryResult *result = CharacterDatabase.PQuery("SELECT caster_guid,item_guid,spell,stackcount,remaincharges,basepoints0,basepoints1,basepoints2,periodictime0,periodictime1,periodictime2,maxduration,remaintime,effIndexMask FROM character_aura WHERE guid = '%u'",GetGUIDLow());
16092 if (result)
16096 Field* fields = result->Fetch();
16097 ObjectGuid caster_guid = ObjectGuid(fields[0].GetUInt64());
16098 uint32 item_lowguid = fields[1].GetUInt32();
16099 uint32 spellid = fields[2].GetUInt32();
16100 uint32 stackcount = fields[3].GetUInt32();
16101 uint32 remaincharges = fields[4].GetUInt32();
16102 int32 damage[MAX_EFFECT_INDEX];
16103 uint32 periodicTime[MAX_EFFECT_INDEX];
16105 for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i)
16107 damage[i] = fields[i + 5].GetInt32();
16108 periodicTime[i] = fields[i + 8].GetUInt32();
16111 int32 maxduration = fields[11].GetInt32();
16112 int32 remaintime = fields[12].GetInt32();
16113 uint32 effIndexMask = fields[13].GetUInt32();
16115 SpellEntry const* spellproto = sSpellStore.LookupEntry(spellid);
16116 if (!spellproto)
16118 sLog.outError("Unknown spell (spellid %u), ignore.", spellid);
16119 continue;
16122 if (remaintime != -1 && !IsPositiveSpell(spellproto))
16124 if (remaintime / IN_MILLISECONDS <= int32(timediff))
16125 continue;
16127 remaintime -= timediff * IN_MILLISECONDS;
16130 // prevent wrong values of remaincharges
16131 if (uint32 procCharges = spellproto->GetProcCharges())
16133 if (remaincharges <= 0 || remaincharges > procCharges)
16134 remaincharges = procCharges;
16136 else
16137 remaincharges = 0;
16139 uint32 defstackamount = spellproto->GetStackAmount();
16140 if (!defstackamount)
16141 stackcount = 1;
16142 else if (defstackamount < stackcount)
16143 stackcount = defstackamount;
16144 else if (!stackcount)
16145 stackcount = 1;
16147 SpellAuraHolder* holder = CreateSpellAuraHolder(spellproto, this, NULL);
16148 holder->SetLoadedState(caster_guid, ObjectGuid(HIGHGUID_ITEM, item_lowguid), stackcount, remaincharges, maxduration, remaintime);
16150 for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i)
16152 if ((effIndexMask & (1 << i)) == 0)
16153 continue;
16155 Aura* aura = CreateAura(spellproto, SpellEffectIndex(i), NULL, holder, this);
16156 if (!damage[i])
16157 damage[i] = aura->GetModifier()->m_amount;
16159 aura->SetLoadedState(damage[i], periodicTime[i]);
16160 holder->AddAura(aura, SpellEffectIndex(i));
16163 if (!holder->IsEmptyHolder())
16165 // reset stolen single target auras
16166 if (caster_guid != GetObjectGuid() && holder->GetTrackedAuraType() == TRACK_AURA_TYPE_SINGLE_TARGET)
16167 holder->SetTrackedAuraType(TRACK_AURA_TYPE_NOT_TRACKED);
16169 AddSpellAuraHolder(holder);
16170 DETAIL_LOG("Added auras from spellid %u", spellproto->Id);
16172 else
16173 delete holder;
16175 while (result->NextRow());
16176 delete result;
16179 if (getClass() == CLASS_WARRIOR && !HasAuraType(SPELL_AURA_MOD_SHAPESHIFT))
16180 CastSpell(this, SPELL_ID_PASSIVE_BATTLE_STANCE, true);
16183 void Player::_LoadGlyphs(QueryResult* result)
16185 if (!result)
16186 return;
16188 // 0 1 2
16189 // "SELECT spec, slot, glyph FROM character_glyphs WHERE guid='%u'"
16193 Field* fields = result->Fetch();
16194 uint8 spec = fields[0].GetUInt8();
16195 uint8 slot = fields[1].GetUInt8();
16196 uint32 glyph = fields[2].GetUInt32();
16198 GlyphPropertiesEntry const* gp = sGlyphPropertiesStore.LookupEntry(glyph);
16199 if (!gp)
16201 sLog.outError("Player %s has not existing glyph entry %u on index %u, spec %u", m_name.c_str(), glyph, slot, spec);
16202 continue;
16205 GlyphSlotEntry const* gs = sGlyphSlotStore.LookupEntry(GetGlyphSlot(slot));
16206 if (!gs)
16208 sLog.outError("Player %s has not existing glyph slot entry %u on index %u, spec %u", m_name.c_str(), GetGlyphSlot(slot), slot, spec);
16209 continue;
16212 if (gp->TypeFlags != gs->TypeFlags)
16214 sLog.outError("Player %s has glyph with typeflags %u in slot with typeflags %u, removing.", m_name.c_str(), gp->TypeFlags, gs->TypeFlags);
16215 continue;
16218 m_glyphs[spec][slot].id = glyph;
16221 while (result->NextRow());
16223 delete result;
16226 void Player::LoadCorpse()
16228 if (isAlive())
16230 sObjectAccessor.ConvertCorpseForPlayer(GetObjectGuid());
16232 else
16234 if (Corpse* corpse = GetCorpse())
16236 ApplyModByteFlag(PLAYER_FIELD_BYTES, 0, PLAYER_FIELD_BYTE_RELEASE_TIMER, corpse && !sMapStore.LookupEntry(corpse->GetMapId())->Instanceable());
16238 else
16240 // Prevent Dead Player login without corpse
16241 ResurrectPlayer(0.5f);
16246 void Player::_LoadInventory(QueryResult* result, uint32 timediff)
16248 // QueryResult *result = CharacterDatabase.PQuery("SELECT data,text,bag,slot,item,item_template FROM character_inventory JOIN item_instance ON character_inventory.item = item_instance.guid WHERE character_inventory.guid = '%u' ORDER BY bag,slot", GetGUIDLow());
16249 std::map<uint32, Bag*> bagMap; // fast guid lookup for bags
16250 // NOTE: the "order by `bag`" is important because it makes sure
16251 // the bagMap is filled before items in the bags are loaded
16252 // NOTE2: the "order by `slot`" is needed because mainhand weapons are (wrongly?)
16253 // expected to be equipped before offhand items (TODO: fixme)
16255 uint32 zone = GetZoneId();
16257 if (result)
16259 std::list<Item*> problematicItems;
16261 // prevent items from being added to the queue when stored
16262 m_itemUpdateQueueBlocked = true;
16265 Field* fields = result->Fetch();
16266 uint32 bag_guid = fields[2].GetUInt32();
16267 uint8 slot = fields[3].GetUInt8();
16268 uint32 item_lowguid = fields[4].GetUInt32();
16269 uint32 item_id = fields[5].GetUInt32();
16271 ItemPrototype const* proto = ObjectMgr::GetItemPrototype(item_id);
16273 if (!proto)
16275 CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_lowguid);
16276 CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid = '%u'", item_lowguid);
16277 sLog.outError("Player::_LoadInventory: Player %s has an unknown item (id: #%u) in inventory, deleted.", GetName(), item_id);
16278 continue;
16281 Item* item = NewItemOrBag(proto);
16283 if (!item->LoadFromDB(item_lowguid, fields, GetObjectGuid()))
16285 sLog.outError("Player::_LoadInventory: Player %s has broken item (id: #%u) in inventory, deleted.", GetName(), item_id);
16286 CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_lowguid);
16287 item->FSetState(ITEM_REMOVED);
16288 item->SaveToDB(); // it also deletes item object !
16289 continue;
16292 // not allow have in alive state item limited to another map/zone
16293 if (isAlive() && item->IsLimitedToAnotherMapOrZone(GetMapId(), zone))
16295 CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_lowguid);
16296 item->FSetState(ITEM_REMOVED);
16297 item->SaveToDB(); // it also deletes item object !
16298 continue;
16301 // "Conjured items disappear if you are logged out for more than 15 minutes"
16302 if (timediff > 15 * MINUTE && (item->GetProto()->Flags & ITEM_FLAG_CONJURED))
16304 CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_lowguid);
16305 item->FSetState(ITEM_REMOVED);
16306 item->SaveToDB(); // it also deletes item object !
16307 continue;
16310 bool success = true;
16312 // the item/bag is not in a bag
16313 if (!bag_guid)
16315 item->SetContainer(NULL);
16316 item->SetSlot(slot);
16318 if (IsInventoryPos(INVENTORY_SLOT_BAG_0, slot))
16320 ItemPosCountVec dest;
16321 if (CanStoreItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false) == EQUIP_ERR_OK)
16322 item = StoreItem(dest, item, true);
16323 else
16324 success = false;
16326 else if (IsEquipmentPos(INVENTORY_SLOT_BAG_0, slot))
16328 uint16 dest;
16329 if (CanEquipItem(slot, dest, item, false, false) == EQUIP_ERR_OK)
16330 QuickEquipItem(dest, item);
16331 else
16332 success = false;
16334 else if (IsBankPos(INVENTORY_SLOT_BAG_0, slot))
16336 ItemPosCountVec dest;
16337 if (CanBankItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false, false) == EQUIP_ERR_OK)
16338 item = BankItem(dest, item, true);
16339 else
16340 success = false;
16343 if (success)
16345 // store bags that may contain items in them
16346 if (item->IsBag() && IsBagPos(item->GetPos()))
16347 bagMap[item_lowguid] = (Bag*)item;
16350 // the item/bag in a bag
16351 else
16353 item->SetSlot(NULL_SLOT);
16354 // the item is in a bag, find the bag
16355 std::map<uint32, Bag*>::const_iterator itr = bagMap.find(bag_guid);
16356 if (itr != bagMap.end() && slot < itr->second->GetBagSize())
16358 ItemPosCountVec dest;
16359 if (CanStoreItem(itr->second->GetSlot(), slot, dest, item, false) == EQUIP_ERR_OK)
16360 item = StoreItem(dest, item, true);
16361 else
16362 success = false;
16364 else
16365 success = false;
16368 // item's state may have changed after stored
16369 if (success)
16371 item->SetState(ITEM_UNCHANGED, this);
16373 // restore container unchanged state also
16374 if (item->GetContainer())
16375 item->GetContainer()->SetState(ITEM_UNCHANGED, this);
16377 // recharged mana gem
16378 if (timediff > 15 * MINUTE && proto->ItemLimitCategory == ITEM_LIMIT_CATEGORY_MANA_GEM)
16379 item->RestoreCharges();
16381 else
16383 sLog.outError("Player::_LoadInventory: Player %s has item (GUID: %u Entry: %u) can't be loaded to inventory (Bag GUID: %u Slot: %u) by some reason, will send by mail.", GetName(), item_lowguid, item_id, bag_guid, slot);
16384 CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_lowguid);
16385 problematicItems.push_back(item);
16388 while (result->NextRow());
16390 delete result;
16391 m_itemUpdateQueueBlocked = false;
16393 // send by mail problematic items
16394 while (!problematicItems.empty())
16396 std::string subject = GetSession()->GetMangosString(LANG_NOT_EQUIPPED_ITEM);
16398 // fill mail
16399 MailDraft draft(subject, "There's were problems with equipping item(s).");
16401 for (int i = 0; !problematicItems.empty() && i < MAX_MAIL_ITEMS; ++i)
16403 Item* item = problematicItems.front();
16404 problematicItems.pop_front();
16406 draft.AddItem(item);
16409 draft.SendMailTo(this, MailSender(this, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_COPIED);
16413 // if(isAlive())
16414 _ApplyAllItemMods();
16417 void Player::_LoadItemLoot(QueryResult* result)
16419 // QueryResult *result = CharacterDatabase.PQuery("SELECT guid,itemid,amount,suffix,property FROM item_loot WHERE guid = '%u'", GetGUIDLow());
16421 if (result)
16425 Field* fields = result->Fetch();
16426 uint32 item_guid = fields[0].GetUInt32();
16428 Item* item = GetItemByGuid(ObjectGuid(HIGHGUID_ITEM, item_guid));
16430 if (!item)
16432 CharacterDatabase.PExecute("DELETE FROM item_loot WHERE guid = '%u'", item_guid);
16433 sLog.outError("Player::_LoadItemLoot: Player %s has loot for nonexistent item (GUID: %u) in `item_loot`, deleted.", GetName(), item_guid);
16434 continue;
16437 item->LoadLootFromDB(fields);
16440 while (result->NextRow());
16442 delete result;
16446 // load mailed item which should receive current player
16447 void Player::_LoadMailedItems(QueryResult* result)
16449 // data needs to be at first place for Item::LoadFromDB
16450 // 0 1 2 3 4
16451 // "SELECT data, text, mail_id, item_guid, item_template FROM mail_items JOIN item_instance ON item_guid = guid WHERE receiver = '%u'", GUID_LOPART(m_guid)
16452 if (!result)
16453 return;
16457 Field* fields = result->Fetch();
16458 uint32 mail_id = fields[2].GetUInt32();
16459 uint32 item_guid_low = fields[3].GetUInt32();
16460 uint32 item_template = fields[4].GetUInt32();
16462 Mail* mail = GetMail(mail_id);
16463 if (!mail)
16464 continue;
16465 mail->AddItem(item_guid_low, item_template);
16467 ItemPrototype const* proto = ObjectMgr::GetItemPrototype(item_template);
16469 if (!proto)
16471 sLog.outError("Player %u has unknown item_template (ProtoType) in mailed items(GUID: %u template: %u) in mail (%u), deleted.", GetGUIDLow(), item_guid_low, item_template, mail->messageID);
16472 CharacterDatabase.PExecute("DELETE FROM mail_items WHERE item_guid = '%u'", item_guid_low);
16473 CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid = '%u'", item_guid_low);
16474 continue;
16477 Item* item = NewItemOrBag(proto);
16479 if (!item->LoadFromDB(item_guid_low, fields, GetObjectGuid()))
16481 sLog.outError("Player::_LoadMailedItems - Item in mail (%u) doesn't exist !!!! - item guid: %u, deleted from mail", mail->messageID, item_guid_low);
16482 CharacterDatabase.PExecute("DELETE FROM mail_items WHERE item_guid = '%u'", item_guid_low);
16483 item->FSetState(ITEM_REMOVED);
16484 item->SaveToDB(); // it also deletes item object !
16485 continue;
16488 AddMItem(item);
16490 while (result->NextRow());
16492 delete result;
16495 void Player::_LoadMails(QueryResult* result)
16497 m_mail.clear();
16498 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13
16499 //"SELECT id,messageType,sender,receiver,subject,body,expire_time,deliver_time,money,cod,checked,stationery,mailTemplateId,has_items FROM mail WHERE receiver = '%u' ORDER BY id DESC", GetGUIDLow()
16500 if (!result)
16501 return;
16505 Field* fields = result->Fetch();
16506 Mail* m = new Mail;
16507 m->messageID = fields[0].GetUInt32();
16508 m->messageType = fields[1].GetUInt8();
16509 m->sender = fields[2].GetUInt32();
16510 m->receiverGuid = ObjectGuid(HIGHGUID_PLAYER, fields[3].GetUInt32());
16511 m->subject = fields[4].GetCppString();
16512 m->body = fields[5].GetCppString();
16513 m->expire_time = (time_t)fields[6].GetUInt64();
16514 m->deliver_time = (time_t)fields[7].GetUInt64();
16515 m->money = fields[8].GetUInt32();
16516 m->COD = fields[9].GetUInt32();
16517 m->checked = fields[10].GetUInt32();
16518 m->stationery = fields[11].GetUInt8();
16519 m->mailTemplateId = fields[12].GetInt16();
16520 m->has_items = fields[13].GetBool(); // true, if mail have items or mail have template and items generated (maybe none)
16522 if (m->mailTemplateId && !sMailTemplateStore.LookupEntry(m->mailTemplateId))
16524 sLog.outError("Player::_LoadMail - Mail (%u) have nonexistent MailTemplateId (%u), remove at load", m->messageID, m->mailTemplateId);
16525 m->mailTemplateId = 0;
16528 m->state = MAIL_STATE_UNCHANGED;
16530 m_mail.push_back(m);
16532 if (m->mailTemplateId && !m->has_items)
16533 m->prepareTemplateItems(this);
16536 while (result->NextRow());
16537 delete result;
16540 void Player::LoadPet()
16542 // fixme: the pet should still be loaded if the player is not in world
16543 // just not added to the map
16544 if (IsInWorld())
16546 Pet* pet = new Pet;
16547 if (!pet->LoadPetFromDB(this, 0, 0, true))
16548 delete pet;
16552 void Player::_LoadQuestStatus(QueryResult* result)
16554 mQuestStatus.clear();
16556 uint32 slot = 0;
16558 //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
16559 // QueryResult *result = CharacterDatabase.PQuery("SELECT quest, status, rewarded, explored, timer, mobcount1, mobcount2, mobcount3, mobcount4, itemcount1, itemcount2, itemcount3, itemcount4, itemcount5, itemcount6 FROM character_queststatus WHERE guid = '%u'", GetGUIDLow());
16561 if (result)
16565 Field* fields = result->Fetch();
16567 uint32 quest_id = fields[0].GetUInt32();
16568 // used to be new, no delete?
16569 Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id);
16570 if (pQuest)
16572 // find or create
16573 QuestStatusData& questStatusData = mQuestStatus[quest_id];
16575 uint32 qstatus = fields[1].GetUInt32();
16576 if (qstatus < MAX_QUEST_STATUS)
16577 questStatusData.m_status = QuestStatus(qstatus);
16578 else
16580 questStatusData.m_status = QUEST_STATUS_NONE;
16581 sLog.outError("Player %s have invalid quest %d status (%d), replaced by QUEST_STATUS_NONE(0).", GetName(), quest_id, qstatus);
16584 questStatusData.m_rewarded = (fields[2].GetUInt8() > 0);
16585 questStatusData.m_explored = (fields[3].GetUInt8() > 0);
16587 time_t quest_time = time_t(fields[4].GetUInt64());
16589 if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_TIMED) && !GetQuestRewardStatus(quest_id) && questStatusData.m_status != QUEST_STATUS_NONE)
16591 AddTimedQuest(quest_id);
16593 if (quest_time <= sWorld.GetGameTime())
16594 questStatusData.m_timer = 1;
16595 else
16596 questStatusData.m_timer = uint32(quest_time - sWorld.GetGameTime()) * IN_MILLISECONDS;
16598 else
16599 quest_time = 0;
16601 questStatusData.m_creatureOrGOcount[0] = fields[5].GetUInt32();
16602 questStatusData.m_creatureOrGOcount[1] = fields[6].GetUInt32();
16603 questStatusData.m_creatureOrGOcount[2] = fields[7].GetUInt32();
16604 questStatusData.m_creatureOrGOcount[3] = fields[8].GetUInt32();
16605 questStatusData.m_itemcount[0] = fields[9].GetUInt32();
16606 questStatusData.m_itemcount[1] = fields[10].GetUInt32();
16607 questStatusData.m_itemcount[2] = fields[11].GetUInt32();
16608 questStatusData.m_itemcount[3] = fields[12].GetUInt32();
16609 questStatusData.m_itemcount[4] = fields[13].GetUInt32();
16610 questStatusData.m_itemcount[5] = fields[14].GetUInt32();
16612 questStatusData.uState = QUEST_UNCHANGED;
16614 // add to quest log
16615 if (slot < MAX_QUEST_LOG_SIZE &&
16616 ((questStatusData.m_status == QUEST_STATUS_INCOMPLETE ||
16617 questStatusData.m_status == QUEST_STATUS_COMPLETE ||
16618 questStatusData.m_status == QUEST_STATUS_FAILED) &&
16619 (!questStatusData.m_rewarded || pQuest->IsRepeatable())))
16621 SetQuestSlot(slot, quest_id, uint32(quest_time));
16623 if (questStatusData.m_explored)
16624 SetQuestSlotState(slot, QUEST_STATE_COMPLETE);
16626 if (questStatusData.m_status == QUEST_STATUS_COMPLETE)
16627 SetQuestSlotState(slot, QUEST_STATE_COMPLETE);
16629 if (questStatusData.m_status == QUEST_STATUS_FAILED)
16630 SetQuestSlotState(slot, QUEST_STATE_FAIL);
16632 for (uint8 idx = 0; idx < QUEST_OBJECTIVES_COUNT; ++idx)
16633 if (questStatusData.m_creatureOrGOcount[idx])
16634 SetQuestSlotCounter(slot, idx, questStatusData.m_creatureOrGOcount[idx]);
16636 ++slot;
16639 if (questStatusData.m_rewarded)
16641 // learn rewarded spell if unknown
16642 learnQuestRewardedSpells(pQuest);
16644 // set rewarded title if any
16645 if (pQuest->GetCharTitleId())
16647 if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(pQuest->GetCharTitleId()))
16648 SetTitle(titleEntry);
16651 if (pQuest->GetBonusTalents())
16652 m_questRewardTalentCount += pQuest->GetBonusTalents();
16655 DEBUG_LOG("Quest status is {%u} for quest {%u} for player (GUID: %u)", questStatusData.m_status, quest_id, GetGUIDLow());
16658 while (result->NextRow());
16660 delete result;
16663 // clear quest log tail
16664 for (uint16 i = slot; i < MAX_QUEST_LOG_SIZE; ++i)
16665 SetQuestSlot(i, 0);
16668 void Player::_LoadDailyQuestStatus(QueryResult* result)
16670 for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx)
16671 SetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx, 0);
16673 // QueryResult *result = CharacterDatabase.PQuery("SELECT quest FROM character_queststatus_daily WHERE guid = '%u'", GetGUIDLow());
16675 if (result)
16677 uint32 quest_daily_idx = 0;
16681 if (quest_daily_idx >= PLAYER_MAX_DAILY_QUESTS) // max amount with exist data in query
16683 sLog.outError("Player (GUID: %u) have more 25 daily quest records in `charcter_queststatus_daily`", GetGUIDLow());
16684 break;
16687 Field* fields = result->Fetch();
16689 uint32 quest_id = fields[0].GetUInt32();
16691 Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id);
16692 if (!pQuest)
16693 continue;
16695 SetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx, quest_id);
16696 ++quest_daily_idx;
16698 DEBUG_LOG("Daily quest {%u} cooldown for player (GUID: %u)", quest_id, GetGUIDLow());
16700 while (result->NextRow());
16702 delete result;
16705 m_DailyQuestChanged = false;
16708 void Player::_LoadWeeklyQuestStatus(QueryResult* result)
16710 m_weeklyquests.clear();
16712 // QueryResult *result = CharacterDatabase.PQuery("SELECT quest FROM character_queststatus_weekly WHERE guid = '%u'", GetGUIDLow());
16714 if (result)
16718 Field* fields = result->Fetch();
16720 uint32 quest_id = fields[0].GetUInt32();
16722 Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id);
16723 if (!pQuest)
16724 continue;
16726 m_weeklyquests.insert(quest_id);
16728 DEBUG_LOG("Weekly quest {%u} cooldown for player (GUID: %u)", quest_id, GetGUIDLow());
16730 while (result->NextRow());
16732 delete result;
16734 m_WeeklyQuestChanged = false;
16737 void Player::_LoadMonthlyQuestStatus(QueryResult* result)
16739 m_monthlyquests.clear();
16741 // QueryResult *result = CharacterDatabase.PQuery("SELECT quest FROM character_queststatus_weekly WHERE guid = '%u'", GetGUIDLow());
16743 if (result)
16747 Field* fields = result->Fetch();
16749 uint32 quest_id = fields[0].GetUInt32();
16751 Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id);
16752 if (!pQuest)
16753 continue;
16755 m_monthlyquests.insert(quest_id);
16757 DEBUG_LOG("Monthly quest {%u} cooldown for player (GUID: %u)", quest_id, GetGUIDLow());
16759 while (result->NextRow());
16761 delete result;
16764 m_MonthlyQuestChanged = false;
16767 void Player::_LoadSpells(QueryResult* result)
16769 // QueryResult *result = CharacterDatabase.PQuery("SELECT spell,active,disabled FROM character_spell WHERE guid = '%u'",GetGUIDLow());
16771 if (result)
16775 Field* fields = result->Fetch();
16777 uint32 spell_id = fields[0].GetUInt32();
16779 // skip talents & drop unneeded data
16780 if (GetTalentSpellPos(spell_id))
16782 sLog.outError("Player::_LoadSpells: %s has talent spell %u in character_spell, removing it.",
16783 GetGuidStr().c_str(), spell_id);
16784 CharacterDatabase.PExecute("DELETE FROM character_spell WHERE spell = '%u'", spell_id);
16785 continue;
16788 addSpell(spell_id, fields[1].GetBool(), false, false, fields[2].GetBool());
16790 while (result->NextRow());
16792 delete result;
16796 void Player::_LoadTalents(QueryResult* result)
16798 // QueryResult *result = CharacterDatabase.PQuery("SELECT talent_id, current_rank, spec FROM character_talent WHERE guid = '%u'",GetGUIDLow());
16799 if (result)
16803 Field* fields = result->Fetch();
16805 uint32 talent_id = fields[0].GetUInt32();
16806 TalentEntry const* talentInfo = sTalentStore.LookupEntry(talent_id);
16808 if (!talentInfo)
16810 sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talent_id: %u , this talent will be deleted from character_talent", GetGUIDLow(), talent_id);
16811 CharacterDatabase.PExecute("DELETE FROM character_talent WHERE talent_id = '%u'", talent_id);
16812 continue;
16815 TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab);
16817 if (!talentTabInfo)
16819 sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talentTabInfo: %u for talentID: %u , this talent will be deleted from character_talent", GetGUIDLow(), talentInfo->TalentTab, talentInfo->TalentID);
16820 CharacterDatabase.PExecute("DELETE FROM character_talent WHERE talent_id = '%u'", talent_id);
16821 continue;
16824 // prevent load talent for different class (cheating)
16825 if ((getClassMask() & talentTabInfo->ClassMask) == 0)
16827 sLog.outError("Player::_LoadTalents:Player (GUID: %u) has talent with ClassMask: %u , but Player's ClassMask is: %u , talentID: %u , this talent will be deleted from character_talent", GetGUIDLow(), talentTabInfo->ClassMask, getClassMask() , talentInfo->TalentID);
16828 CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u' AND talent_id = '%u'", GetGUIDLow(), talent_id);
16829 continue;
16832 uint32 currentRank = fields[1].GetUInt32();
16834 if (currentRank > MAX_TALENT_RANK || talentInfo->RankID[currentRank] == 0)
16836 sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talent rank: %u , talentID: %u , this talent will be deleted from character_talent", GetGUIDLow(), currentRank, talentInfo->TalentID);
16837 CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u' AND talent_id = '%u'", GetGUIDLow(), talent_id);
16838 continue;
16841 uint32 spec = fields[2].GetUInt32();
16843 if (spec > MAX_TALENT_SPEC_COUNT)
16845 sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talent spec: %u, spec will be deleted from character_talent", GetGUIDLow(), spec);
16846 CharacterDatabase.PExecute("DELETE FROM character_talent WHERE spec = '%u' ", spec);
16847 continue;
16850 if (spec >= m_specsCount)
16852 sLog.outError("Player::_LoadTalents:Player (GUID: %u) has invalid talent spec: %u , this spec will be deleted from character_talent.", GetGUIDLow(), spec);
16853 CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u' AND spec = '%u' ", GetGUIDLow(), spec);
16854 continue;
16857 if (m_activeSpec == spec)
16858 addSpell(talentInfo->RankID[currentRank], true, false, false, false);
16859 else
16861 PlayerTalent talent;
16862 talent.currentRank = currentRank;
16863 talent.talentEntry = talentInfo;
16864 talent.state = PLAYERSPELL_UNCHANGED;
16865 m_talents[spec][talentInfo->TalentID] = talent;
16868 while (result->NextRow());
16869 delete result;
16873 void Player::_LoadGroup(QueryResult* result)
16875 // QueryResult *result = CharacterDatabase.PQuery("SELECT groupId FROM group_member WHERE memberGuid='%u'", GetGUIDLow());
16876 if (result)
16878 uint32 groupId = (*result)[0].GetUInt32();
16879 delete result;
16881 if (Group* group = sObjectMgr.GetGroupById(groupId))
16883 uint8 subgroup = group->GetMemberGroup(GetObjectGuid());
16884 SetGroup(group, subgroup);
16885 if (getLevel() >= LEVELREQUIREMENT_HEROIC)
16887 // the group leader may change the instance difficulty while the player is offline
16888 SetDungeonDifficulty(group->GetDungeonDifficulty());
16889 SetRaidDifficulty(group->GetRaidDifficulty());
16895 void Player::_LoadBoundInstances(QueryResult* result)
16897 for (uint8 i = 0; i < MAX_DIFFICULTY; ++i)
16898 m_boundInstances[i].clear();
16900 Group* group = GetGroup();
16902 // QueryResult *result = CharacterDatabase.PQuery("SELECT id, permanent, map, difficulty, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = '%u'", GUID_LOPART(m_guid));
16903 if (result)
16907 Field* fields = result->Fetch();
16908 bool perm = fields[1].GetBool();
16909 uint32 mapId = fields[2].GetUInt32();
16910 uint32 instanceId = fields[0].GetUInt32();
16911 uint8 difficulty = fields[3].GetUInt8();
16913 time_t resetTime = (time_t)fields[4].GetUInt64();
16914 // the resettime for normal instances is only saved when the InstanceSave is unloaded
16915 // so the value read from the DB may be wrong here but only if the InstanceSave is loaded
16916 // and in that case it is not used
16918 MapEntry const* mapEntry = sMapStore.LookupEntry(mapId);
16919 if (!mapEntry || !mapEntry->IsDungeon())
16921 sLog.outError("_LoadBoundInstances: player %s(%d) has bind to nonexistent or not dungeon map %d", GetName(), GetGUIDLow(), mapId);
16922 CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u' AND instance = '%u'", GetGUIDLow(), instanceId);
16923 continue;
16926 if (difficulty >= MAX_DIFFICULTY)
16928 sLog.outError("_LoadBoundInstances: player %s(%d) has bind to nonexistent difficulty %d instance for map %u", GetName(), GetGUIDLow(), difficulty, mapId);
16929 CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u' AND instance = '%u'", GetGUIDLow(), instanceId);
16930 continue;
16933 MapDifficultyEntry const* mapDiff = GetMapDifficultyData(mapId, Difficulty(difficulty));
16934 if (!mapDiff)
16936 sLog.outError("_LoadBoundInstances: player %s(%d) has bind to nonexistent difficulty %d instance for map %u", GetName(), GetGUIDLow(), difficulty, mapId);
16937 CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u' AND instance = '%u'", GetGUIDLow(), instanceId);
16938 continue;
16941 if (!perm && group)
16943 sLog.outError("_LoadBoundInstances: %s is in group (Id: %d) but has a non-permanent character bind to map %d,%d,%d",
16944 GetGuidStr().c_str(), group->GetId(), mapId, instanceId, difficulty);
16945 CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u' AND instance = '%u'",
16946 GetGUIDLow(), instanceId);
16947 continue;
16950 // since non permanent binds are always solo bind, they can always be reset
16951 DungeonPersistentState* state = (DungeonPersistentState*)sMapPersistentStateMgr.AddPersistentState(mapEntry, instanceId, Difficulty(difficulty), resetTime, !perm, true);
16952 if (state) BindToInstance(state, perm, true);
16954 while (result->NextRow());
16955 delete result;
16959 InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty)
16961 // some instances only have one difficulty
16962 MapDifficultyEntry const* mapDiff = GetMapDifficultyData(mapid, difficulty);
16963 if (!mapDiff)
16964 return NULL;
16966 BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid);
16967 if (itr != m_boundInstances[difficulty].end())
16968 return &itr->second;
16969 else
16970 return NULL;
16973 void Player::UnbindInstance(uint32 mapid, Difficulty difficulty, bool unload)
16975 BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid);
16976 UnbindInstance(itr, difficulty, unload);
16979 void Player::UnbindInstance(BoundInstancesMap::iterator& itr, Difficulty difficulty, bool unload)
16981 if (itr != m_boundInstances[difficulty].end())
16983 if (!unload)
16984 CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u' AND instance = '%u'",
16985 GetGUIDLow(), itr->second.state->GetInstanceId());
16986 itr->second.state->RemovePlayer(this); // state can become invalid
16987 m_boundInstances[difficulty].erase(itr++);
16991 InstancePlayerBind* Player::BindToInstance(DungeonPersistentState* state, bool permanent, bool load)
16993 if (state)
16995 InstancePlayerBind& bind = m_boundInstances[state->GetDifficulty()][state->GetMapId()];
16996 if (bind.state)
16998 // update the state when the group kills a boss
16999 if (permanent != bind.perm || state != bind.state)
17000 if (!load)
17001 CharacterDatabase.PExecute("UPDATE character_instance SET instance = '%u', permanent = '%u' WHERE guid = '%u' AND instance = '%u'",
17002 state->GetInstanceId(), permanent, GetGUIDLow(), bind.state->GetInstanceId());
17004 else
17006 if (!load)
17007 CharacterDatabase.PExecute("INSERT INTO character_instance (guid, instance, permanent) VALUES ('%u', '%u', '%u')",
17008 GetGUIDLow(), state->GetInstanceId(), permanent);
17011 if (bind.state != state)
17013 if (bind.state)
17014 bind.state->RemovePlayer(this);
17015 state->AddPlayer(this);
17018 if (permanent)
17019 state->SetCanReset(false);
17021 bind.state = state;
17022 bind.perm = permanent;
17023 if (!load)
17024 DEBUG_LOG("Player::BindToInstance: %s(%d) is now bound to map %d, instance %d, difficulty %d",
17025 GetName(), GetGUIDLow(), state->GetMapId(), state->GetInstanceId(), state->GetDifficulty());
17026 return &bind;
17028 else
17029 return NULL;
17032 DungeonPersistentState* Player::GetBoundInstanceSaveForSelfOrGroup(uint32 mapid)
17034 MapEntry const* mapEntry = sMapStore.LookupEntry(mapid);
17035 if (!mapEntry)
17036 return NULL;
17038 InstancePlayerBind* pBind = GetBoundInstance(mapid, GetDifficulty(mapEntry->IsRaid()));
17039 DungeonPersistentState* state = pBind ? pBind->state : NULL;
17041 // the player's permanent player bind is taken into consideration first
17042 // then the player's group bind and finally the solo bind.
17043 if (!pBind || !pBind->perm)
17045 InstanceGroupBind* groupBind = NULL;
17046 // use the player's difficulty setting (it may not be the same as the group's)
17047 if (Group* group = GetGroup())
17048 if (groupBind = group->GetBoundInstance(mapid, this))
17049 state = groupBind->state;
17052 return state;
17055 void Player::SendRaidInfo()
17057 uint32 counter = 0;
17059 WorldPacket data(SMSG_RAID_INSTANCE_INFO, 4);
17061 size_t p_counter = data.wpos();
17062 data << uint32(counter); // placeholder
17064 time_t now = time(NULL);
17066 for (int i = 0; i < MAX_DIFFICULTY; ++i)
17068 for (BoundInstancesMap::const_iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr)
17070 if (itr->second.perm)
17072 DungeonPersistentState* state = itr->second.state;
17073 data << uint32(state->GetMapId()); // map id
17074 data << uint32(state->GetDifficulty()); // difficulty
17075 data << ObjectGuid(state->GetInstanceGuid());// instance guid
17076 data << uint8(1); // expired = 0
17077 data << uint8(0); // extended = 1
17078 data << uint32(state->GetResetTime() - now);// reset time
17079 ++counter;
17083 data.put<uint32>(p_counter, counter);
17084 GetSession()->SendPacket(&data);
17088 - called on every successful teleportation to a map
17090 void Player::SendSavedInstances()
17092 bool hasBeenSaved = false;
17093 WorldPacket data;
17095 for (uint8 i = 0; i < MAX_DIFFICULTY; ++i)
17097 for (BoundInstancesMap::const_iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr)
17099 if (itr->second.perm) // only permanent binds are sent
17101 hasBeenSaved = true;
17102 break;
17107 // Send opcode 811. true or false means, whether you have current raid/heroic instances
17108 data.Initialize(SMSG_UPDATE_INSTANCE_OWNERSHIP);
17109 data << uint32(hasBeenSaved);
17110 GetSession()->SendPacket(&data);
17112 if (!hasBeenSaved)
17113 return;
17115 for (uint8 i = 0; i < MAX_DIFFICULTY; ++i)
17117 for (BoundInstancesMap::const_iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr)
17119 if (itr->second.perm)
17121 data.Initialize(SMSG_UPDATE_LAST_INSTANCE);
17122 data << uint32(itr->second.state->GetMapId());
17123 GetSession()->SendPacket(&data);
17129 /// convert the player's binds to the group
17130 void Player::ConvertInstancesToGroup(Player* player, Group* group, ObjectGuid player_guid)
17132 bool has_binds = false;
17133 bool has_solo = false;
17135 if (player)
17137 player_guid = player->GetObjectGuid();
17138 if (!group)
17139 group = player->GetGroup();
17142 MANGOS_ASSERT(player_guid);
17144 // copy all binds to the group, when changing leader it's assumed the character
17145 // will not have any solo binds
17147 if (player)
17149 for (uint8 i = 0; i < MAX_DIFFICULTY; ++i)
17151 for (BoundInstancesMap::iterator itr = player->m_boundInstances[i].begin(); itr != player->m_boundInstances[i].end();)
17153 has_binds = true;
17155 if (group)
17156 group->BindToInstance(itr->second.state, itr->second.perm, true);
17158 // permanent binds are not removed
17159 if (!itr->second.perm)
17161 // increments itr in call
17162 player->UnbindInstance(itr, Difficulty(i), true);
17163 has_solo = true;
17165 else
17166 ++itr;
17171 uint32 player_lowguid = player_guid.GetCounter();
17173 // if the player's not online we don't know what binds it has
17174 if (!player || !group || has_binds)
17175 CharacterDatabase.PExecute("INSERT INTO group_instance SELECT guid, instance, permanent FROM character_instance WHERE guid = '%u'", player_lowguid);
17177 // the following should not get executed when changing leaders
17178 if (!player || has_solo)
17179 CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u' AND permanent = 0", player_lowguid);
17182 bool Player::_LoadHomeBind(QueryResult* result)
17184 PlayerInfo const* info = sObjectMgr.GetPlayerInfo(getRace(), getClass());
17185 if (!info)
17187 sLog.outError("Player have incorrect race/class pair. Can't be loaded.");
17188 return false;
17191 bool ok = false;
17192 // QueryResult *result = CharacterDatabase.PQuery("SELECT map,zone,position_x,position_y,position_z FROM character_homebind WHERE guid = '%u'", GUID_LOPART(playerGuid));
17193 if (result)
17195 Field* fields = result->Fetch();
17196 m_homebindMapId = fields[0].GetUInt32();
17197 m_homebindAreaId = fields[1].GetUInt16();
17198 m_homebindX = fields[2].GetFloat();
17199 m_homebindY = fields[3].GetFloat();
17200 m_homebindZ = fields[4].GetFloat();
17201 delete result;
17203 MapEntry const* bindMapEntry = sMapStore.LookupEntry(m_homebindMapId);
17205 // accept saved data only for valid position (and non instanceable), and accessable
17206 if (MapManager::IsValidMapCoord(m_homebindMapId, m_homebindX, m_homebindY, m_homebindZ) &&
17207 !bindMapEntry->Instanceable() && GetSession()->Expansion() >= bindMapEntry->Expansion())
17209 ok = true;
17211 else
17212 CharacterDatabase.PExecute("DELETE FROM character_homebind WHERE guid = '%u'", GetGUIDLow());
17215 if (!ok)
17217 m_homebindMapId = info->mapId;
17218 m_homebindAreaId = info->areaId;
17219 m_homebindX = info->positionX;
17220 m_homebindY = info->positionY;
17221 m_homebindZ = info->positionZ;
17223 CharacterDatabase.PExecute("INSERT INTO character_homebind (guid,map,zone,position_x,position_y,position_z) VALUES ('%u', '%u', '%u', '%f', '%f', '%f')", GetGUIDLow(), m_homebindMapId, (uint32)m_homebindAreaId, m_homebindX, m_homebindY, m_homebindZ);
17226 DEBUG_LOG("Setting player home position: mapid is: %u, zoneid is %u, X is %f, Y is %f, Z is %f",
17227 m_homebindMapId, m_homebindAreaId, m_homebindX, m_homebindY, m_homebindZ);
17229 return true;
17232 /*********************************************************/
17233 /*** SAVE SYSTEM ***/
17234 /*********************************************************/
17236 void Player::SaveToDB()
17238 // we should assure this: ASSERT((m_nextSave != sWorld.getConfig(CONFIG_UINT32_INTERVAL_SAVE)));
17239 // delay auto save at any saves (manual, in code, or autosave)
17240 m_nextSave = sWorld.getConfig(CONFIG_UINT32_INTERVAL_SAVE);
17242 // lets allow only players in world to be saved
17243 if (IsBeingTeleportedFar())
17245 ScheduleDelayedOperation(DELAYED_SAVE_PLAYER);
17246 return;
17249 // first save/honor gain after midnight will also update the player's honor fields
17250 UpdateHonorKills();
17252 DEBUG_FILTER_LOG(LOG_FILTER_PLAYER_STATS, "The value of player %s at save: ", m_name.c_str());
17253 outDebugStatsValues();
17255 CharacterDatabase.BeginTransaction();
17257 static SqlStatementID delChar ;
17258 static SqlStatementID insChar ;
17260 SqlStatement stmt = CharacterDatabase.CreateStatement(delChar, "DELETE FROM characters WHERE guid = ?");
17261 stmt.PExecute(GetGUIDLow());
17263 SqlStatement uberInsert = CharacterDatabase.CreateStatement(insChar, "INSERT INTO characters (guid,account,name,race,class,gender,level,xp,money,playerBytes,playerBytes2,playerFlags,"
17264 "map, dungeon_difficulty, position_x, position_y, position_z, orientation, "
17265 "taximask, online, cinematic, "
17266 "totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, primary_trees, "
17267 "trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, "
17268 "death_expire_time, taxi_path, totalKills, "
17269 "todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, health, power1, power2, power3, "
17270 "power4, power5, specCount, activeSpec, exploredZones, equipmentCache, knownTitles, actionBars, slot) "
17271 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "
17272 "?, ?, ?, ?, ?, ?, "
17273 "?, ?, ?, "
17274 "?, ?, ?, ?, ?, ?, ?, ?, "
17275 "?, ?, ?, ?, ?, ?, ?, ?, ?, "
17276 "?, ?, ?, "
17277 "?, ?, ?, ?, ?, ?, ?, ?, ?, "
17278 "?, ?, ?, ?, ?, ?, ?, ?, ?) ");
17280 uberInsert.addUInt32(GetGUIDLow());
17281 uberInsert.addUInt32(GetSession()->GetAccountId());
17282 uberInsert.addString(m_name);
17283 uberInsert.addUInt8(getRace());
17284 uberInsert.addUInt8(getClass());
17285 uberInsert.addUInt8(getGender());
17286 uberInsert.addUInt32(getLevel());
17287 uberInsert.addUInt32(GetUInt32Value(PLAYER_XP));
17288 uberInsert.addUInt64(GetMoney());
17289 uberInsert.addUInt32(GetUInt32Value(PLAYER_BYTES));
17290 uberInsert.addUInt32(GetUInt32Value(PLAYER_BYTES_2));
17291 uberInsert.addUInt32(GetUInt32Value(PLAYER_FLAGS));
17293 if (!IsBeingTeleported())
17295 uberInsert.addUInt32(GetMapId());
17296 uberInsert.addUInt32(uint32(GetDungeonDifficulty()));
17297 uberInsert.addFloat(finiteAlways(GetPositionX()));
17298 uberInsert.addFloat(finiteAlways(GetPositionY()));
17299 uberInsert.addFloat(finiteAlways(GetPositionZ()));
17300 uberInsert.addFloat(finiteAlways(GetOrientation()));
17302 else
17304 uberInsert.addUInt32(GetTeleportDest().mapid);
17305 uberInsert.addUInt32(uint32(GetDungeonDifficulty()));
17306 uberInsert.addFloat(finiteAlways(GetTeleportDest().coord_x));
17307 uberInsert.addFloat(finiteAlways(GetTeleportDest().coord_y));
17308 uberInsert.addFloat(finiteAlways(GetTeleportDest().coord_z));
17309 uberInsert.addFloat(finiteAlways(GetTeleportDest().orientation));
17312 std::ostringstream ss;
17313 ss << m_taxi; // string with TaxiMaskSize numbers
17314 uberInsert.addString(ss);
17316 uberInsert.addUInt32(IsInWorld() ? 1 : 0);
17318 uberInsert.addUInt32(m_cinematic);
17320 uberInsert.addUInt32(m_Played_time[PLAYED_TIME_TOTAL]);
17321 uberInsert.addUInt32(m_Played_time[PLAYED_TIME_LEVEL]);
17323 uberInsert.addFloat(finiteAlways(m_rest_bonus));
17324 uberInsert.addUInt64(uint64(time(NULL)));
17325 uberInsert.addUInt32(HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) ? 1 : 0);
17326 // save, far from tavern/city
17327 // save, but in tavern/city
17328 uberInsert.addUInt32(m_resetTalentsCost);
17329 uberInsert.addUInt64(uint64(m_resetTalentsTime));
17330 ss.str("");
17331 for (int i = 0; i < MAX_TALENT_SPEC_COUNT; ++i)
17332 ss << m_talentsPrimaryTree[i] << " ";
17333 uberInsert.addString(ss);
17335 uberInsert.addFloat(finiteAlways(m_movementInfo.GetTransportPos()->x));
17336 uberInsert.addFloat(finiteAlways(m_movementInfo.GetTransportPos()->y));
17337 uberInsert.addFloat(finiteAlways(m_movementInfo.GetTransportPos()->z));
17338 uberInsert.addFloat(finiteAlways(m_movementInfo.GetTransportPos()->o));
17339 if (m_transport)
17340 uberInsert.addUInt32(m_transport->GetGUIDLow());
17341 else
17342 uberInsert.addUInt32(0);
17344 uberInsert.addUInt32(m_ExtraFlags);
17346 uberInsert.addUInt32(uint32(m_stableSlots)); // to prevent save uint8 as char
17348 uberInsert.addUInt32(uint32(m_atLoginFlags));
17350 uberInsert.addUInt32(IsInWorld() ? GetZoneId() : GetCachedZoneId());
17352 uberInsert.addUInt64(uint64(m_deathExpireTime));
17354 ss << m_taxi.SaveTaxiDestinationsToString(); // string
17355 uberInsert.addString(ss);
17357 uberInsert.addUInt32(GetUInt32Value(PLAYER_FIELD_LIFETIME_HONORBALE_KILLS));
17359 uberInsert.addUInt16(GetUInt16Value(PLAYER_FIELD_KILLS, 0));
17361 uberInsert.addUInt16(GetUInt16Value(PLAYER_FIELD_KILLS, 1));
17363 uberInsert.addUInt32(GetUInt32Value(PLAYER_CHOSEN_TITLE));
17365 // FIXME: at this moment send to DB as unsigned, including unit32(-1)
17366 uberInsert.addUInt32(GetUInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX));
17368 uberInsert.addUInt16(uint16(GetUInt32Value(PLAYER_BYTES_3) & 0xFFFE));
17370 uberInsert.addUInt32(GetHealth());
17372 static_assert(MAX_STORED_POWERS == 5, "Query not updated.");
17373 for (uint32 i = 0; i < MAX_STORED_POWERS; ++i)
17374 uberInsert.addUInt32(GetPowerByIndex(i));
17376 uberInsert.addUInt32(uint32(m_specsCount));
17377 uberInsert.addUInt32(uint32(m_activeSpec));
17379 for (uint32 i = 0; i < PLAYER_EXPLORED_ZONES_SIZE; ++i) // string
17381 ss << GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + i) << " ";
17383 uberInsert.addString(ss);
17385 for (uint32 i = 0; i < EQUIPMENT_SLOT_END * 2; ++i) // string
17387 ss << GetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + i) << " ";
17389 uberInsert.addString(ss);
17391 for(uint32 i = 0; i < KNOWN_TITLES_SIZE*2; ++i ) //string
17393 ss << GetUInt32Value(PLAYER__FIELD_KNOWN_TITLES + i) << " ";
17395 uberInsert.addString(ss);
17397 uberInsert.addUInt32(uint32(GetByteValue(PLAYER_FIELD_BYTES, 2)));
17399 uberInsert.addUInt8(m_slot);
17401 uberInsert.Execute();
17403 if (m_mailsUpdated) // save mails only when needed
17404 _SaveMail();
17406 _SaveBGData();
17407 _SaveInventory();
17408 _SaveQuestStatus();
17409 _SaveDailyQuestStatus();
17410 _SaveWeeklyQuestStatus();
17411 _SaveMonthlyQuestStatus();
17412 _SaveSpells();
17413 _SaveSpellCooldowns();
17414 _SaveActions();
17415 _SaveAuras();
17416 _SaveSkills();
17417 m_achievementMgr.SaveToDB();
17418 m_reputationMgr.SaveToDB();
17419 _SaveCurrencies();
17420 _SaveEquipmentSets();
17421 GetSession()->SaveTutorialsData(); // changed only while character in game
17422 _SaveGlyphs();
17423 _SaveTalents();
17425 CharacterDatabase.CommitTransaction();
17427 // check if stats should only be saved on logout
17428 // save stats can be out of transaction
17429 if (m_session->isLogingOut() || !sWorld.getConfig(CONFIG_BOOL_STATS_SAVE_ONLY_ON_LOGOUT))
17430 _SaveStats();
17432 // save pet (hunter pet level and experience and all type pets health/mana).
17433 if (Pet* pet = GetPet())
17434 pet->SavePetToDB(PET_SAVE_AS_CURRENT);
17437 // fast save function for item/money cheating preventing - save only inventory and money state
17438 void Player::SaveInventoryAndGoldToDB()
17440 _SaveInventory();
17441 SaveGoldToDB();
17444 void Player::SaveGoldToDB()
17446 static SqlStatementID updateGold ;
17448 SqlStatement stmt = CharacterDatabase.CreateStatement(updateGold, "UPDATE characters SET money = ? WHERE guid = ?");
17449 stmt.PExecute(GetMoney(), GetGUIDLow());
17452 void Player::_SaveActions()
17454 static SqlStatementID insertAction ;
17455 static SqlStatementID updateAction ;
17456 static SqlStatementID deleteAction ;
17458 for (int i = 0; i < MAX_TALENT_SPEC_COUNT; ++i)
17460 for (ActionButtonList::iterator itr = m_actionButtons[i].begin(); itr != m_actionButtons[i].end();)
17462 switch (itr->second.uState)
17464 case ACTIONBUTTON_NEW:
17466 SqlStatement stmt = CharacterDatabase.CreateStatement(insertAction, "INSERT INTO character_action (guid,spec, button,action,type) VALUES (?, ?, ?, ?, ?)");
17467 stmt.addUInt32(GetGUIDLow());
17468 stmt.addUInt32(i);
17469 stmt.addUInt32(uint32(itr->first));
17470 stmt.addUInt32(itr->second.GetAction());
17471 stmt.addUInt32(uint32(itr->second.GetType()));
17472 stmt.Execute();
17473 itr->second.uState = ACTIONBUTTON_UNCHANGED;
17474 ++itr;
17476 break;
17477 case ACTIONBUTTON_CHANGED:
17479 SqlStatement stmt = CharacterDatabase.CreateStatement(updateAction, "UPDATE character_action SET action = ?, type = ? WHERE guid = ? AND button = ? AND spec = ?");
17480 stmt.addUInt32(itr->second.GetAction());
17481 stmt.addUInt32(uint32(itr->second.GetType()));
17482 stmt.addUInt32(GetGUIDLow());
17483 stmt.addUInt32(uint32(itr->first));
17484 stmt.addUInt32(i);
17485 stmt.Execute();
17486 itr->second.uState = ACTIONBUTTON_UNCHANGED;
17487 ++itr;
17489 break;
17490 case ACTIONBUTTON_DELETED:
17492 SqlStatement stmt = CharacterDatabase.CreateStatement(deleteAction, "DELETE FROM character_action WHERE guid = ? AND button = ? AND spec = ?");
17493 stmt.addUInt32(GetGUIDLow());
17494 stmt.addUInt32(uint32(itr->first));
17495 stmt.addUInt32(i);
17496 stmt.Execute();
17497 m_actionButtons[i].erase(itr++);
17499 break;
17500 default:
17501 ++itr;
17502 break;
17508 void Player::_SaveAuras()
17510 static SqlStatementID deleteAuras ;
17511 static SqlStatementID insertAuras ;
17513 SqlStatement stmt = CharacterDatabase.CreateStatement(deleteAuras, "DELETE FROM character_aura WHERE guid = ?");
17514 stmt.PExecute(GetGUIDLow());
17516 SpellAuraHolderMap const& auraHolders = GetSpellAuraHolderMap();
17518 if (auraHolders.empty())
17519 return;
17521 stmt = CharacterDatabase.CreateStatement(insertAuras, "INSERT INTO character_aura (guid, caster_guid, item_guid, spell, stackcount, remaincharges, "
17522 "basepoints0, basepoints1, basepoints2, periodictime0, periodictime1, periodictime2, maxduration, remaintime, effIndexMask) "
17523 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
17525 for (SpellAuraHolderMap::const_iterator itr = auraHolders.begin(); itr != auraHolders.end(); ++itr)
17527 SpellAuraHolder* holder = itr->second;
17528 // skip all holders from spells that are passive or channeled
17529 // save singleTarget auras if self cast.
17530 bool selfCastHolder = holder->GetCasterGuid() == GetObjectGuid();
17531 TrackedAuraType trackedType = holder->GetTrackedAuraType();
17532 if (!holder->IsPassive() && !IsChanneledSpell(holder->GetSpellProto()) &&
17533 (trackedType == TRACK_AURA_TYPE_NOT_TRACKED || (trackedType == TRACK_AURA_TYPE_SINGLE_TARGET && selfCastHolder)))
17535 int32 damage[MAX_EFFECT_INDEX];
17536 uint32 periodicTime[MAX_EFFECT_INDEX];
17537 uint32 effIndexMask = 0;
17539 for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i)
17541 damage[i] = 0;
17542 periodicTime[i] = 0;
17544 if (Aura* aur = holder->GetAuraByEffectIndex(SpellEffectIndex(i)))
17546 // don't save not own area auras
17547 if (aur->IsAreaAura() && holder->GetCasterGuid() != GetObjectGuid())
17548 continue;
17550 damage[i] = aur->GetModifier()->m_amount;
17551 periodicTime[i] = aur->GetModifier()->periodictime;
17552 effIndexMask |= (1 << i);
17556 if (!effIndexMask)
17557 continue;
17559 stmt.addUInt32(GetGUIDLow());
17560 stmt.addUInt64(holder->GetCasterGuid().GetRawValue());
17561 stmt.addUInt32(holder->GetCastItemGuid().GetCounter());
17562 stmt.addUInt32(holder->GetId());
17563 stmt.addUInt32(holder->GetStackAmount());
17564 stmt.addUInt8(holder->GetAuraCharges());
17566 for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i)
17567 stmt.addInt32(damage[i]);
17569 for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i)
17570 stmt.addUInt32(periodicTime[i]);
17572 stmt.addInt32(holder->GetAuraMaxDuration());
17573 stmt.addInt32(holder->GetAuraDuration());
17574 stmt.addUInt32(effIndexMask);
17575 stmt.Execute();
17580 void Player::_SaveGlyphs()
17582 static SqlStatementID insertGlyph ;
17583 static SqlStatementID updateGlyph ;
17584 static SqlStatementID deleteGlyph ;
17586 for (uint8 spec = 0; spec < m_specsCount; ++spec)
17588 for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot)
17590 switch (m_glyphs[spec][slot].uState)
17592 case GLYPH_NEW:
17594 SqlStatement stmt = CharacterDatabase.CreateStatement(insertGlyph, "INSERT INTO character_glyphs (guid, spec, slot, glyph) VALUES (?, ?, ?, ?)");
17595 stmt.PExecute(GetGUIDLow(), spec, slot, m_glyphs[spec][slot].GetId());
17596 break;
17598 case GLYPH_CHANGED:
17600 SqlStatement stmt = CharacterDatabase.CreateStatement(updateGlyph, "UPDATE character_glyphs SET glyph = ? WHERE guid = ? AND spec = ? AND slot = ?");
17601 stmt.PExecute(m_glyphs[spec][slot].GetId(), GetGUIDLow(), spec, slot);
17602 break;
17604 case GLYPH_DELETED:
17606 SqlStatement stmt = CharacterDatabase.CreateStatement(deleteGlyph, "DELETE FROM character_glyphs WHERE guid = ? AND spec = ? AND slot = ?");
17607 stmt.PExecute(GetGUIDLow(), spec, slot);
17608 break;
17610 case GLYPH_UNCHANGED:
17611 break;
17613 m_glyphs[spec][slot].uState = GLYPH_UNCHANGED;
17618 void Player::_SaveInventory()
17620 // force items in buyback slots to new state
17621 // and remove those that aren't already
17622 for (uint8 i = BUYBACK_SLOT_START; i < BUYBACK_SLOT_END; ++i)
17624 Item* item = m_items[i];
17625 if (!item || item->GetState() == ITEM_NEW) continue;
17627 static SqlStatementID delInv ;
17628 static SqlStatementID delItemInst ;
17630 SqlStatement stmt = CharacterDatabase.CreateStatement(delInv, "DELETE FROM character_inventory WHERE item = ?");
17631 stmt.PExecute(item->GetGUIDLow());
17633 stmt = CharacterDatabase.CreateStatement(delItemInst, "DELETE FROM item_instance WHERE guid = ?");
17634 stmt.PExecute(item->GetGUIDLow());
17636 m_items[i]->FSetState(ITEM_NEW);
17639 // update enchantment durations
17640 for (EnchantDurationList::const_iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr)
17642 itr->item->SetEnchantmentDuration(itr->slot, itr->leftduration);
17645 // if no changes
17646 if (m_itemUpdateQueue.empty()) return;
17648 // do not save if the update queue is corrupt
17649 bool error = false;
17650 for (size_t i = 0; i < m_itemUpdateQueue.size(); ++i)
17652 Item* item = m_itemUpdateQueue[i];
17653 if (!item || item->GetState() == ITEM_REMOVED) continue;
17654 Item* test = GetItemByPos(item->GetBagSlot(), item->GetSlot());
17656 if (test == NULL)
17658 sLog.outError("Player(GUID: %u Name: %s)::_SaveInventory - the bag(%d) and slot(%d) values for the item with guid %d are incorrect, the player doesn't have an item at that position!", GetGUIDLow(), GetName(), item->GetBagSlot(), item->GetSlot(), item->GetGUIDLow());
17659 error = true;
17661 else if (test != item)
17663 sLog.outError("Player(GUID: %u Name: %s)::_SaveInventory - the bag(%d) and slot(%d) values for the item with guid %d are incorrect, the item with guid %d is there instead!", GetGUIDLow(), GetName(), item->GetBagSlot(), item->GetSlot(), item->GetGUIDLow(), test->GetGUIDLow());
17664 error = true;
17668 if (error)
17670 sLog.outError("Player::_SaveInventory - one or more errors occurred save aborted!");
17671 ChatHandler(this).SendSysMessage(LANG_ITEM_SAVE_FAILED);
17672 return;
17675 static SqlStatementID insertInventory ;
17676 static SqlStatementID updateInventory ;
17677 static SqlStatementID deleteInventory ;
17679 for (size_t i = 0; i < m_itemUpdateQueue.size(); ++i)
17681 Item* item = m_itemUpdateQueue[i];
17682 if (!item) continue;
17684 Bag* container = item->GetContainer();
17685 uint32 bag_guid = container ? container->GetGUIDLow() : 0;
17687 switch (item->GetState())
17689 case ITEM_NEW:
17691 SqlStatement stmt = CharacterDatabase.CreateStatement(insertInventory, "INSERT INTO character_inventory (guid,bag,slot,item,item_template) VALUES (?, ?, ?, ?, ?)");
17692 stmt.addUInt32(GetGUIDLow());
17693 stmt.addUInt32(bag_guid);
17694 stmt.addUInt8(item->GetSlot());
17695 stmt.addUInt32(item->GetGUIDLow());
17696 stmt.addUInt32(item->GetEntry());
17697 stmt.Execute();
17699 break;
17700 case ITEM_CHANGED:
17702 SqlStatement stmt = CharacterDatabase.CreateStatement(updateInventory, "UPDATE character_inventory SET guid = ?, bag = ?, slot = ?, item_template = ? WHERE item = ?");
17703 stmt.addUInt32(GetGUIDLow());
17704 stmt.addUInt32(bag_guid);
17705 stmt.addUInt8(item->GetSlot());
17706 stmt.addUInt32(item->GetEntry());
17707 stmt.addUInt32(item->GetGUIDLow());
17708 stmt.Execute();
17710 break;
17711 case ITEM_REMOVED:
17713 SqlStatement stmt = CharacterDatabase.CreateStatement(deleteInventory, "DELETE FROM character_inventory WHERE item = ?");
17714 stmt.PExecute(item->GetGUIDLow());
17716 break;
17717 case ITEM_UNCHANGED:
17718 break;
17721 item->SaveToDB(); // item have unchanged inventory record and can be save standalone
17723 m_itemUpdateQueue.clear();
17726 void Player::_SaveMail()
17728 static SqlStatementID updateMail ;
17729 static SqlStatementID deleteMailItems ;
17731 static SqlStatementID deleteItem ;
17732 static SqlStatementID deleteMain ;
17733 static SqlStatementID deleteItems ;
17735 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
17737 Mail* m = (*itr);
17738 if (m->state == MAIL_STATE_CHANGED)
17740 SqlStatement stmt = CharacterDatabase.CreateStatement(updateMail, "UPDATE mail SET has_items = ?, expire_time = ?, deliver_time = ?, money = ?, cod = ?, checked = ? WHERE id = ?");
17741 stmt.addUInt32(m->HasItems() ? 1 : 0);
17742 stmt.addUInt64(uint64(m->expire_time));
17743 stmt.addUInt64(uint64(m->deliver_time));
17744 stmt.addUInt32(m->money);
17745 stmt.addUInt32(m->COD);
17746 stmt.addUInt32(m->checked);
17747 stmt.addUInt32(m->messageID);
17748 stmt.Execute();
17750 if (m->removedItems.size())
17752 stmt = CharacterDatabase.CreateStatement(deleteMailItems, "DELETE FROM mail_items WHERE item_guid = ?");
17754 for (std::vector<uint32>::const_iterator itr2 = m->removedItems.begin(); itr2 != m->removedItems.end(); ++itr2)
17755 stmt.PExecute(*itr2);
17757 m->removedItems.clear();
17759 m->state = MAIL_STATE_UNCHANGED;
17761 else if (m->state == MAIL_STATE_DELETED)
17763 if (m->HasItems())
17765 SqlStatement stmt = CharacterDatabase.CreateStatement(deleteItem, "DELETE FROM item_instance WHERE guid = ?");
17766 for (MailItemInfoVec::const_iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2)
17767 stmt.PExecute(itr2->item_guid);
17770 SqlStatement stmt = CharacterDatabase.CreateStatement(deleteMain, "DELETE FROM mail WHERE id = ?");
17771 stmt.PExecute(m->messageID);
17773 stmt = CharacterDatabase.CreateStatement(deleteItems, "DELETE FROM mail_items WHERE mail_id = ?");
17774 stmt.PExecute(m->messageID);
17778 // deallocate deleted mails...
17779 for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end();)
17781 if ((*itr)->state == MAIL_STATE_DELETED)
17783 Mail* m = *itr;
17784 m_mail.erase(itr);
17785 delete m;
17786 itr = m_mail.begin();
17788 else
17789 ++itr;
17792 m_mailsUpdated = false;
17795 void Player::_SaveQuestStatus()
17797 static SqlStatementID insertQuestStatus ;
17799 static SqlStatementID updateQuestStatus ;
17801 // we don't need transactions here.
17802 for (QuestStatusMap::iterator i = mQuestStatus.begin(); i != mQuestStatus.end(); ++i)
17804 switch (i->second.uState)
17806 case QUEST_NEW :
17808 SqlStatement stmt = CharacterDatabase.CreateStatement(insertQuestStatus, "INSERT INTO character_queststatus (guid,quest,status,rewarded,explored,timer,mobcount1,mobcount2,mobcount3,mobcount4,itemcount1,itemcount2,itemcount3,itemcount4,itemcount5,itemcount6) "
17809 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
17811 stmt.addUInt32(GetGUIDLow());
17812 stmt.addUInt32(i->first);
17813 stmt.addUInt8(i->second.m_status);
17814 stmt.addUInt8(i->second.m_rewarded);
17815 stmt.addUInt8(i->second.m_explored);
17816 stmt.addUInt64(uint64(i->second.m_timer / IN_MILLISECONDS + sWorld.GetGameTime()));
17817 for (int k = 0; k < QUEST_OBJECTIVES_COUNT; ++k)
17818 stmt.addUInt32(i->second.m_creatureOrGOcount[k]);
17819 for (int k = 0; k < QUEST_ITEM_OBJECTIVES_COUNT; ++k)
17820 stmt.addUInt32(i->second.m_itemcount[k]);
17821 stmt.Execute();
17823 break;
17824 case QUEST_CHANGED :
17826 SqlStatement stmt = CharacterDatabase.CreateStatement(updateQuestStatus, "UPDATE character_queststatus SET status = ?,rewarded = ?,explored = ?,timer = ?,"
17827 "mobcount1 = ?,mobcount2 = ?,mobcount3 = ?,mobcount4 = ?,itemcount1 = ?,itemcount2 = ?,itemcount3 = ?,itemcount4 = ?,itemcount5 = ?,itemcount6 = ? WHERE guid = ? AND quest = ?");
17829 stmt.addUInt8(i->second.m_status);
17830 stmt.addUInt8(i->second.m_rewarded);
17831 stmt.addUInt8(i->second.m_explored);
17832 stmt.addUInt64(uint64(i->second.m_timer / IN_MILLISECONDS + sWorld.GetGameTime()));
17833 for (int k = 0; k < QUEST_OBJECTIVES_COUNT; ++k)
17834 stmt.addUInt32(i->second.m_creatureOrGOcount[k]);
17835 for (int k = 0; k < QUEST_ITEM_OBJECTIVES_COUNT; ++k)
17836 stmt.addUInt32(i->second.m_itemcount[k]);
17837 stmt.addUInt32(GetGUIDLow());
17838 stmt.addUInt32(i->first);
17839 stmt.Execute();
17841 break;
17842 case QUEST_UNCHANGED:
17843 break;
17845 i->second.uState = QUEST_UNCHANGED;
17849 void Player::_SaveDailyQuestStatus()
17851 if (!m_DailyQuestChanged)
17852 return;
17854 // we don't need transactions here.
17855 static SqlStatementID delQuestStatus ;
17856 static SqlStatementID insQuestStatus ;
17858 SqlStatement stmtDel = CharacterDatabase.CreateStatement(delQuestStatus, "DELETE FROM character_queststatus_daily WHERE guid = ?");
17859 SqlStatement stmtIns = CharacterDatabase.CreateStatement(insQuestStatus, "INSERT INTO character_queststatus_daily (guid,quest) VALUES (?, ?)");
17861 stmtDel.PExecute(GetGUIDLow());
17863 for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx)
17864 if (GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx))
17865 stmtIns.PExecute(GetGUIDLow(), GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx));
17867 m_DailyQuestChanged = false;
17870 void Player::_SaveWeeklyQuestStatus()
17872 if (!m_WeeklyQuestChanged || m_weeklyquests.empty())
17873 return;
17875 // we don't need transactions here.
17876 static SqlStatementID delQuestStatus ;
17877 static SqlStatementID insQuestStatus ;
17879 SqlStatement stmtDel = CharacterDatabase.CreateStatement(delQuestStatus, "DELETE FROM character_queststatus_weekly WHERE guid = ?");
17880 SqlStatement stmtIns = CharacterDatabase.CreateStatement(insQuestStatus, "INSERT INTO character_queststatus_weekly (guid,quest) VALUES (?, ?)");
17882 stmtDel.PExecute(GetGUIDLow());
17884 for (QuestSet::const_iterator iter = m_weeklyquests.begin(); iter != m_weeklyquests.end(); ++iter)
17886 uint32 quest_id = *iter;
17887 stmtIns.PExecute(GetGUIDLow(), quest_id);
17890 m_WeeklyQuestChanged = false;
17893 void Player::_SaveMonthlyQuestStatus()
17895 if (!m_MonthlyQuestChanged || m_monthlyquests.empty())
17896 return;
17898 // we don't need transactions here.
17899 static SqlStatementID deleteQuest ;
17900 static SqlStatementID insertQuest ;
17902 SqlStatement stmtDel = CharacterDatabase.CreateStatement(deleteQuest, "DELETE FROM character_queststatus_monthly WHERE guid = ?");
17903 SqlStatement stmtIns = CharacterDatabase.CreateStatement(insertQuest, "INSERT INTO character_queststatus_monthly (guid, quest) VALUES (?, ?)");
17905 stmtDel.PExecute(GetGUIDLow());
17907 for (QuestSet::const_iterator iter = m_monthlyquests.begin(); iter != m_monthlyquests.end(); ++iter)
17909 uint32 quest_id = *iter;
17910 stmtIns.PExecute(GetGUIDLow(), quest_id);
17913 m_MonthlyQuestChanged = false;
17916 void Player::_SaveSkills()
17918 static SqlStatementID delSkills ;
17919 static SqlStatementID insSkills ;
17920 static SqlStatementID updSkills ;
17922 // we don't need transactions here.
17923 for (SkillStatusMap::iterator itr = mSkillStatus.begin(); itr != mSkillStatus.end();)
17925 if (itr->second.uState == SKILL_UNCHANGED)
17927 ++itr;
17928 continue;
17931 if (itr->second.uState == SKILL_DELETED)
17933 SqlStatement stmt = CharacterDatabase.CreateStatement(delSkills, "DELETE FROM character_skills WHERE guid = ? AND skill = ?");
17934 stmt.PExecute(GetGUIDLow(), itr->first);
17935 mSkillStatus.erase(itr++);
17936 continue;
17939 uint16 field = itr->second.pos / 2;
17940 uint8 offset = itr->second.pos & 1;
17942 uint16 value = GetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset);
17943 uint16 max = GetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset);
17945 switch (itr->second.uState)
17947 case SKILL_NEW:
17949 SqlStatement stmt = CharacterDatabase.CreateStatement(insSkills, "INSERT INTO character_skills (guid, skill, value, max) VALUES (?, ?, ?, ?)");
17950 stmt.PExecute(GetGUIDLow(), itr->first, value, max);
17952 break;
17953 case SKILL_CHANGED:
17955 SqlStatement stmt = CharacterDatabase.CreateStatement(updSkills, "UPDATE character_skills SET value = ?, max = ? WHERE guid = ? AND skill = ?");
17956 stmt.PExecute(value, max, GetGUIDLow(), itr->first);
17958 break;
17959 case SKILL_UNCHANGED:
17960 case SKILL_DELETED:
17961 MANGOS_ASSERT(false);
17962 break;
17964 itr->second.uState = SKILL_UNCHANGED;
17966 ++itr;
17970 void Player::_SaveSpells()
17972 static SqlStatementID delSpells ;
17973 static SqlStatementID insSpells ;
17975 SqlStatement stmtDel = CharacterDatabase.CreateStatement(delSpells, "DELETE FROM character_spell WHERE guid = ? and spell = ?");
17976 SqlStatement stmtIns = CharacterDatabase.CreateStatement(insSpells, "INSERT INTO character_spell (guid,spell,active,disabled) VALUES (?, ?, ?, ?)");
17978 for (PlayerSpellMap::iterator itr = m_spells.begin(), next = m_spells.begin(); itr != m_spells.end();)
17980 uint32 talentCosts = GetTalentSpellCost(itr->first);
17982 if (!talentCosts)
17984 if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.state == PLAYERSPELL_CHANGED)
17985 stmtDel.PExecute(GetGUIDLow(), itr->first);
17987 // add only changed/new not dependent spells
17988 if (!itr->second.dependent && (itr->second.state == PLAYERSPELL_NEW || itr->second.state == PLAYERSPELL_CHANGED))
17989 stmtIns.PExecute(GetGUIDLow(), itr->first, uint8(itr->second.active ? 1 : 0), uint8(itr->second.disabled ? 1 : 0));
17992 if (itr->second.state == PLAYERSPELL_REMOVED)
17993 m_spells.erase(itr++);
17994 else
17996 itr->second.state = PLAYERSPELL_UNCHANGED;
17997 ++itr;
18003 void Player::_SaveTalents()
18005 static SqlStatementID delTalents ;
18006 static SqlStatementID insTalents ;
18008 SqlStatement stmtDel = CharacterDatabase.CreateStatement(delTalents, "DELETE FROM character_talent WHERE guid = ? and talent_id = ? and spec = ?");
18009 SqlStatement stmtIns = CharacterDatabase.CreateStatement(insTalents, "INSERT INTO character_talent (guid, talent_id, current_rank , spec) VALUES (?, ?, ?, ?)");
18011 for (uint32 i = 0; i < MAX_TALENT_SPEC_COUNT; ++i)
18013 for (PlayerTalentMap::iterator itr = m_talents[i].begin(); itr != m_talents[i].end();)
18015 if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.state == PLAYERSPELL_CHANGED)
18016 stmtDel.PExecute(GetGUIDLow(), itr->first, i);
18018 // add only changed/new talents
18019 if (itr->second.state == PLAYERSPELL_NEW || itr->second.state == PLAYERSPELL_CHANGED)
18020 stmtIns.PExecute(GetGUIDLow(), itr->first, itr->second.currentRank, i);
18022 if (itr->second.state == PLAYERSPELL_REMOVED)
18023 m_talents[i].erase(itr++);
18024 else
18026 itr->second.state = PLAYERSPELL_UNCHANGED;
18027 ++itr;
18033 // save player stats -- only for external usage
18034 // real stats will be recalculated on player login
18035 void Player::_SaveStats()
18037 // check if stat saving is enabled and if char level is high enough
18038 if (!sWorld.getConfig(CONFIG_UINT32_MIN_LEVEL_STAT_SAVE) || getLevel() < sWorld.getConfig(CONFIG_UINT32_MIN_LEVEL_STAT_SAVE))
18039 return;
18041 static SqlStatementID delStats ;
18042 static SqlStatementID insertStats ;
18044 SqlStatement stmt = CharacterDatabase.CreateStatement(delStats, "DELETE FROM character_stats WHERE guid = ?");
18045 stmt.PExecute(GetGUIDLow());
18047 stmt = CharacterDatabase.CreateStatement(insertStats, "INSERT INTO character_stats (guid, maxhealth, maxpower1, maxpower2, maxpower3, maxpower4, maxpower5,"
18048 "strength, agility, stamina, intellect, spirit, armor, resHoly, resFire, resNature, resFrost, resShadow, resArcane, "
18049 "blockPct, dodgePct, parryPct, critPct, rangedCritPct, spellCritPct, attackPower, rangedAttackPower, spellPower) "
18050 "VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
18052 stmt.addUInt32(GetGUIDLow());
18053 stmt.addUInt32(GetMaxHealth());
18054 static_assert(MAX_STORED_POWERS == 5, "Query not updated.");
18055 for (uint32 i = 0; i < MAX_STORED_POWERS; ++i)
18056 stmt.addUInt32(GetMaxPowerByIndex(i));
18057 for (int i = 0; i < MAX_STATS; ++i)
18058 stmt.addFloat(GetStat(Stats(i)));
18059 // armor + school resistances
18060 for (int i = 0; i < MAX_SPELL_SCHOOL; ++i)
18061 stmt.addUInt32(GetResistance(SpellSchools(i)));
18062 stmt.addFloat(GetFloatValue(PLAYER_BLOCK_PERCENTAGE));
18063 stmt.addFloat(GetFloatValue(PLAYER_DODGE_PERCENTAGE));
18064 stmt.addFloat(GetFloatValue(PLAYER_PARRY_PERCENTAGE));
18065 stmt.addFloat(GetFloatValue(PLAYER_CRIT_PERCENTAGE));
18066 stmt.addFloat(GetFloatValue(PLAYER_RANGED_CRIT_PERCENTAGE));
18067 stmt.addFloat(GetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1));
18068 stmt.addUInt32(GetUInt32Value(UNIT_FIELD_ATTACK_POWER));
18069 stmt.addUInt32(GetUInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER));
18070 stmt.addUInt32(GetBaseSpellPowerBonus());
18072 stmt.Execute();
18075 void Player::outDebugStatsValues() const
18077 // optimize disabled debug output
18078 if (!sLog.HasLogLevelOrHigher(LOG_LVL_DEBUG) || sLog.HasLogFilter(LOG_FILTER_PLAYER_STATS))
18079 return;
18081 sLog.outDebug("HP is: \t\t\t%u\t\tMP is: \t\t\t%u", GetMaxHealth(), GetMaxPower(POWER_MANA));
18082 sLog.outDebug("AGILITY is: \t\t%f\t\tSTRENGTH is: \t\t%f", GetStat(STAT_AGILITY), GetStat(STAT_STRENGTH));
18083 sLog.outDebug("INTELLECT is: \t\t%f\t\tSPIRIT is: \t\t%f", GetStat(STAT_INTELLECT), GetStat(STAT_SPIRIT));
18084 sLog.outDebug("STAMINA is: \t\t%f", GetStat(STAT_STAMINA));
18085 sLog.outDebug("Armor is: \t\t%u\t\tBlock is: \t\t%f", GetArmor(), GetFloatValue(PLAYER_BLOCK_PERCENTAGE));
18086 sLog.outDebug("HolyRes is: \t\t%u\t\tFireRes is: \t\t%u", GetResistance(SPELL_SCHOOL_HOLY), GetResistance(SPELL_SCHOOL_FIRE));
18087 sLog.outDebug("NatureRes is: \t\t%u\t\tFrostRes is: \t\t%u", GetResistance(SPELL_SCHOOL_NATURE), GetResistance(SPELL_SCHOOL_FROST));
18088 sLog.outDebug("ShadowRes is: \t\t%u\t\tArcaneRes is: \t\t%u", GetResistance(SPELL_SCHOOL_SHADOW), GetResistance(SPELL_SCHOOL_ARCANE));
18089 sLog.outDebug("MIN_DAMAGE is: \t\t%f\tMAX_DAMAGE is: \t\t%f", GetFloatValue(UNIT_FIELD_MINDAMAGE), GetFloatValue(UNIT_FIELD_MAXDAMAGE));
18090 sLog.outDebug("MIN_OFFHAND_DAMAGE is: \t%f\tMAX_OFFHAND_DAMAGE is: \t%f", GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE), GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE));
18091 sLog.outDebug("MIN_RANGED_DAMAGE is: \t%f\tMAX_RANGED_DAMAGE is: \t%f", GetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE), GetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE));
18092 sLog.outDebug("ATTACK_TIME is: \t%u\t\tRANGE_ATTACK_TIME is: \t%u", GetAttackTime(BASE_ATTACK), GetAttackTime(RANGED_ATTACK));
18095 /*********************************************************/
18096 /*** FLOOD FILTER SYSTEM ***/
18097 /*********************************************************/
18099 void Player::UpdateSpeakTime()
18101 // ignore chat spam protection for GMs in any mode
18102 if (GetSession()->GetSecurity() > SEC_PLAYER)
18103 return;
18105 time_t current = time(NULL);
18106 if (m_speakTime > current)
18108 uint32 max_count = sWorld.getConfig(CONFIG_UINT32_CHATFLOOD_MESSAGE_COUNT);
18109 if (!max_count)
18110 return;
18112 ++m_speakCount;
18113 if (m_speakCount >= max_count)
18115 // prevent overwrite mute time, if message send just before mutes set, for example.
18116 time_t new_mute = current + sWorld.getConfig(CONFIG_UINT32_CHATFLOOD_MUTE_TIME);
18117 if (GetSession()->m_muteTime < new_mute)
18118 GetSession()->m_muteTime = new_mute;
18120 m_speakCount = 0;
18123 else
18124 m_speakCount = 0;
18126 m_speakTime = current + sWorld.getConfig(CONFIG_UINT32_CHATFLOOD_MESSAGE_DELAY);
18129 bool Player::CanSpeak() const
18131 return GetSession()->m_muteTime <= time(NULL);
18134 /*********************************************************/
18135 /*** LOW LEVEL FUNCTIONS:Notifiers ***/
18136 /*********************************************************/
18138 void Player::SendAttackSwingNotInRange()
18140 WorldPacket data(SMSG_ATTACKSWING_NOTINRANGE, 0);
18141 GetSession()->SendPacket(&data);
18144 void Player::SavePositionInDB(ObjectGuid guid, uint32 mapid, float x, float y, float z, float o, uint32 zone)
18146 std::ostringstream ss;
18147 ss << "UPDATE characters SET position_x='" << x << "',position_y='" << y
18148 << "',position_z='" << z << "',orientation='" << o << "',map='" << mapid
18149 << "',zone='" << zone << "',trans_x='0',trans_y='0',trans_z='0',"
18150 << "transguid='0',taxi_path='' WHERE guid='" << guid.GetCounter() << "'";
18151 DEBUG_LOG("%s", ss.str().c_str());
18152 CharacterDatabase.Execute(ss.str().c_str());
18155 void Player::SetUInt32ValueInArray(Tokens& tokens, uint16 index, uint32 value)
18157 char buf[11];
18158 snprintf(buf, 11, "%u", value);
18160 if (index >= tokens.size())
18161 return;
18163 tokens[index] = buf;
18166 void Player::Customize(ObjectGuid guid, uint8 gender, uint8 skin, uint8 face, uint8 hairStyle, uint8 hairColor, uint8 facialHair)
18168 // 0
18169 QueryResult* result = CharacterDatabase.PQuery("SELECT playerBytes2 FROM characters WHERE guid = '%u'", guid.GetCounter());
18170 if (!result)
18171 return;
18173 Field* fields = result->Fetch();
18175 uint32 player_bytes2 = fields[0].GetUInt32();
18176 player_bytes2 &= ~0xFF;
18177 player_bytes2 |= facialHair;
18179 CharacterDatabase.PExecute("UPDATE characters SET gender = '%u', playerBytes = '%u', playerBytes2 = '%u' WHERE guid = '%u'", gender, skin | (face << 8) | (hairStyle << 16) | (hairColor << 24), player_bytes2, guid.GetCounter());
18181 delete result;
18184 void Player::SendAttackSwingDeadTarget()
18186 WorldPacket data(SMSG_ATTACKSWING_DEADTARGET, 0);
18187 GetSession()->SendPacket(&data);
18190 void Player::SendAttackSwingCantAttack()
18192 WorldPacket data(SMSG_ATTACKSWING_CANT_ATTACK, 0);
18193 GetSession()->SendPacket(&data);
18196 void Player::SendAttackSwingCancelAttack()
18198 WorldPacket data(SMSG_CANCEL_COMBAT, 0);
18199 GetSession()->SendPacket(&data);
18202 void Player::SendAttackSwingBadFacingAttack()
18204 WorldPacket data(SMSG_ATTACKSWING_BADFACING, 0);
18205 GetSession()->SendPacket(&data);
18208 void Player::SendAutoRepeatCancel(Unit* target)
18210 WorldPacket data(SMSG_CANCEL_AUTO_REPEAT, target->GetPackGUID().size());
18211 data << target->GetPackGUID();
18212 GetSession()->SendPacket(&data);
18215 void Player::SendExplorationExperience(uint32 Area, uint32 Experience)
18217 WorldPacket data(SMSG_EXPLORATION_EXPERIENCE, 8);
18218 data << uint32(Area);
18219 data << uint32(Experience);
18220 GetSession()->SendPacket(&data);
18223 void Player::SendDungeonDifficulty(bool IsInGroup)
18225 uint8 val = 0x00000001;
18226 WorldPacket data(MSG_SET_DUNGEON_DIFFICULTY, 12);
18227 data << uint32(GetDungeonDifficulty());
18228 data << uint32(val);
18229 data << uint32(IsInGroup);
18230 GetSession()->SendPacket(&data);
18233 void Player::SendRaidDifficulty(bool IsInGroup)
18235 uint8 val = 0x00000001;
18236 WorldPacket data(MSG_SET_RAID_DIFFICULTY, 12);
18237 data << uint32(GetRaidDifficulty());
18238 data << uint32(val);
18239 data << uint32(IsInGroup);
18240 GetSession()->SendPacket(&data);
18243 void Player::SendResetFailedNotify(uint32 mapid)
18245 WorldPacket data(SMSG_RESET_FAILED_NOTIFY, 4);
18246 data << uint32(mapid);
18247 GetSession()->SendPacket(&data);
18250 /// Reset all solo instances and optionally send a message on success for each
18251 void Player::ResetInstances(InstanceResetMethod method, bool isRaid)
18253 // method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_JOIN
18255 // we assume that when the difficulty changes, all instances that can be reset will be
18256 Difficulty diff = GetDifficulty(isRaid);
18258 for (BoundInstancesMap::iterator itr = m_boundInstances[diff].begin(); itr != m_boundInstances[diff].end();)
18260 DungeonPersistentState* state = itr->second.state;
18261 const MapEntry* entry = sMapStore.LookupEntry(itr->first);
18262 if (!entry || entry->IsRaid() != isRaid || !state->CanReset())
18264 ++itr;
18265 continue;
18268 if (method == INSTANCE_RESET_ALL)
18270 // the "reset all instances" method can only reset normal maps
18271 if (entry->map_type == MAP_RAID || diff == DUNGEON_DIFFICULTY_HEROIC)
18273 ++itr;
18274 continue;
18278 // if the map is loaded, reset it
18279 if (Map* map = sMapMgr.FindMap(state->GetMapId(), state->GetInstanceId()))
18280 if (map->IsDungeon())
18281 ((DungeonMap*)map)->Reset(method);
18283 // since this is a solo instance there should not be any players inside
18284 if (method == INSTANCE_RESET_ALL || method == INSTANCE_RESET_CHANGE_DIFFICULTY)
18285 SendResetInstanceSuccess(state->GetMapId());
18287 state->DeleteFromDB();
18288 m_boundInstances[diff].erase(itr++);
18290 // the following should remove the instance save from the manager and delete it as well
18291 state->RemovePlayer(this);
18295 void Player::SendResetInstanceSuccess(uint32 MapId)
18297 WorldPacket data(SMSG_INSTANCE_RESET, 4);
18298 data << uint32(MapId);
18299 GetSession()->SendPacket(&data);
18302 void Player::SendResetInstanceFailed(uint32 reason, uint32 MapId)
18304 // TODO: find what other fail reasons there are besides players in the instance
18305 WorldPacket data(SMSG_INSTANCE_RESET_FAILED, 4);
18306 data << uint32(reason);
18307 data << uint32(MapId);
18308 GetSession()->SendPacket(&data);
18311 /*********************************************************/
18312 /*** Update timers ***/
18313 /*********************************************************/
18315 /// checks the 15 afk reports per 5 minutes limit
18316 void Player::UpdateAfkReport(time_t currTime)
18318 if (m_bgData.bgAfkReportedTimer <= currTime)
18320 m_bgData.bgAfkReportedCount = 0;
18321 m_bgData.bgAfkReportedTimer = currTime + 5 * MINUTE;
18325 void Player::UpdateContestedPvP(uint32 diff)
18327 if (!m_contestedPvPTimer || isInCombat())
18328 return;
18329 if (m_contestedPvPTimer <= diff)
18331 ResetContestedPvP();
18333 else
18334 m_contestedPvPTimer -= diff;
18337 void Player::UpdatePvPFlag(time_t currTime)
18339 if (!IsPvP())
18340 return;
18341 if (pvpInfo.endTimer == 0 || currTime < (pvpInfo.endTimer + 300))
18342 return;
18344 UpdatePvP(false);
18347 void Player::UpdateDuelFlag(time_t currTime)
18349 if (!duel || duel->startTimer == 0 || currTime < duel->startTimer + 3)
18350 return;
18352 SetUInt32Value(PLAYER_DUEL_TEAM, 1);
18353 duel->opponent->SetUInt32Value(PLAYER_DUEL_TEAM, 2);
18355 duel->startTimer = 0;
18356 duel->startTime = currTime;
18357 duel->opponent->duel->startTimer = 0;
18358 duel->opponent->duel->startTime = currTime;
18361 void Player::RemovePet(PetSaveMode mode)
18363 if (Pet* pet = GetPet())
18364 pet->Unsummon(mode, this);
18367 void Player::BuildPlayerChat(WorldPacket* data, uint8 msgtype, const std::string& text, uint32 language, const char* addonPrefix) const
18369 *data << uint8(msgtype);
18370 *data << uint32(language);
18371 *data << GetObjectGuid();
18372 *data << uint32(0); // constant unknown time 4.3.4
18373 if (addonPrefix)
18374 *data << addonPrefix;
18375 else
18376 *data << GetObjectGuid();
18377 *data << uint32(text.length() + 1);
18378 *data << text;
18379 *data << uint8(GetChatTag());
18382 void Player::Say(const std::string& text, const uint32 language)
18384 WorldPacket data(SMSG_MESSAGECHAT, 200);
18385 BuildPlayerChat(&data, CHAT_MSG_SAY, text, language);
18386 SendMessageToSetInRange(&data, sWorld.getConfig(CONFIG_FLOAT_LISTEN_RANGE_SAY), true);
18389 void Player::Yell(const std::string& text, const uint32 language)
18391 WorldPacket data(SMSG_MESSAGECHAT, 200);
18392 BuildPlayerChat(&data, CHAT_MSG_YELL, text, language);
18393 SendMessageToSetInRange(&data, sWorld.getConfig(CONFIG_FLOAT_LISTEN_RANGE_YELL), true);
18396 void Player::TextEmote(const std::string& text)
18398 WorldPacket data(SMSG_MESSAGECHAT, 200);
18399 BuildPlayerChat(&data, CHAT_MSG_EMOTE, text, LANG_UNIVERSAL);
18400 SendMessageToSetInRange(&data, sWorld.getConfig(CONFIG_FLOAT_LISTEN_RANGE_TEXTEMOTE), true, !sWorld.getConfig(CONFIG_BOOL_ALLOW_TWO_SIDE_INTERACTION_CHAT));
18403 void Player::Whisper(const std::string& text, uint32 language, ObjectGuid receiver)
18405 Player* rPlayer = sObjectMgr.GetPlayer(receiver);
18407 WorldPacket data(SMSG_MESSAGECHAT, 200);
18408 BuildPlayerChat(&data, CHAT_MSG_WHISPER, text, language);
18409 rPlayer->GetSession()->SendPacket(&data);
18411 data.Initialize(SMSG_MESSAGECHAT, 200);
18412 rPlayer->BuildPlayerChat(&data, CHAT_MSG_WHISPER_INFORM, text, language);
18413 GetSession()->SendPacket(&data);
18415 if (!isAcceptWhispers())
18417 SetAcceptWhispers(true);
18418 ChatHandler(this).SendSysMessage(LANG_COMMAND_WHISPERON);
18421 // announce afk or dnd message
18422 if (rPlayer->isAFK())
18423 ChatHandler(this).PSendSysMessage(LANG_PLAYER_AFK, rPlayer->GetName(), rPlayer->autoReplyMsg.c_str());
18424 else if (rPlayer->isDND())
18425 ChatHandler(this).PSendSysMessage(LANG_PLAYER_DND, rPlayer->GetName(), rPlayer->autoReplyMsg.c_str());
18428 void Player::WhisperAddon(const std::string& text, const std::string& prefix, ObjectGuid receiver)
18430 Player* rPlayer = sObjectMgr.GetPlayer(receiver);
18432 std::string _text(text);
18434 WorldPacket data(SMSG_MESSAGECHAT, 200);
18435 BuildPlayerChat(&data, CHAT_MSG_WHISPER, _text, LANG_UNIVERSAL, prefix.c_str());
18436 rPlayer->GetSession()->SendPacket(&data);
18439 void Player::PetSpellInitialize()
18441 Pet* pet = GetPet();
18443 if (!pet)
18444 return;
18446 DEBUG_LOG("Pet Spells Groups");
18448 CharmInfo* charmInfo = pet->GetCharmInfo();
18450 WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * MAX_UNIT_ACTION_BAR_INDEX + 1 + 1);
18451 data << pet->GetObjectGuid();
18452 data << uint16(pet->GetCreatureInfo()->family); // creature family (required for pet talents)
18453 data << uint32(0);
18454 data << uint8(charmInfo->GetReactState()) << uint8(charmInfo->GetCommandState()) << uint16(0);
18456 // action bar loop
18457 charmInfo->BuildActionBar(&data);
18459 size_t spellsCountPos = data.wpos();
18461 // spells count
18462 uint8 addlist = 0;
18463 data << uint8(addlist); // placeholder
18465 if (pet->IsPermanentPetFor(this))
18467 // spells loop
18468 for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr)
18470 if (itr->second.state == PETSPELL_REMOVED)
18471 continue;
18473 data << uint32(MAKE_UNIT_ACTION_BUTTON(itr->first, itr->second.active));
18474 ++addlist;
18478 data.put<uint8>(spellsCountPos, addlist);
18480 uint8 cooldownsCount = pet->m_CreatureSpellCooldowns.size() + pet->m_CreatureCategoryCooldowns.size();
18481 data << uint8(cooldownsCount);
18483 time_t curTime = time(NULL);
18485 for (CreatureSpellCooldowns::const_iterator itr = pet->m_CreatureSpellCooldowns.begin(); itr != pet->m_CreatureSpellCooldowns.end(); ++itr)
18487 time_t cooldown = (itr->second > curTime) ? (itr->second - curTime) * IN_MILLISECONDS : 0;
18489 data << uint32(itr->first); // spellid
18490 data << uint16(0); // spell category?
18491 data << uint32(cooldown); // cooldown
18492 data << uint32(0); // category cooldown
18495 for (CreatureSpellCooldowns::const_iterator itr = pet->m_CreatureCategoryCooldowns.begin(); itr != pet->m_CreatureCategoryCooldowns.end(); ++itr)
18497 time_t cooldown = (itr->second > curTime) ? (itr->second - curTime) * IN_MILLISECONDS : 0;
18499 data << uint32(itr->first); // spellid
18500 data << uint16(0); // spell category?
18501 data << uint32(0); // cooldown
18502 data << uint32(cooldown); // category cooldown
18505 GetSession()->SendPacket(&data);
18508 void Player::SendPetGUIDs()
18510 if (!GetPetGuid())
18511 return;
18513 // Later this function might get modified for multiple guids
18514 WorldPacket data(SMSG_PET_GUIDS, 12);
18515 data << uint32(1); // count
18516 data << ObjectGuid(GetPetGuid());
18517 GetSession()->SendPacket(&data);
18520 void Player::PossessSpellInitialize()
18522 Unit* charm = GetCharm();
18524 if (!charm)
18525 return;
18527 CharmInfo* charmInfo = charm->GetCharmInfo();
18529 if (!charmInfo)
18531 sLog.outError("Player::PossessSpellInitialize(): charm (GUID: %u TypeId: %u) has no charminfo!", charm->GetGUIDLow(), charm->GetTypeId());
18532 return;
18535 WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * MAX_UNIT_ACTION_BAR_INDEX + 1 + 1);
18536 data << charm->GetObjectGuid();
18537 data << uint16(0);
18538 data << uint32(0);
18539 data << uint32(0);
18541 charmInfo->BuildActionBar(&data);
18543 data << uint8(0); // spells count
18544 data << uint8(0); // cooldowns count
18546 GetSession()->SendPacket(&data);
18549 void Player::CharmSpellInitialize()
18551 Unit* charm = GetCharm();
18553 if (!charm)
18554 return;
18556 CharmInfo* charmInfo = charm->GetCharmInfo();
18557 if (!charmInfo)
18559 sLog.outError("Player::CharmSpellInitialize(): the player's charm (GUID: %u TypeId: %u) has no charminfo!", charm->GetGUIDLow(), charm->GetTypeId());
18560 return;
18563 uint8 addlist = 0;
18565 if (charm->GetTypeId() != TYPEID_PLAYER)
18567 CreatureInfo const* cinfo = ((Creature*)charm)->GetCreatureInfo();
18569 if (cinfo && cinfo->type == CREATURE_TYPE_DEMON && getClass() == CLASS_WARLOCK)
18571 for (uint32 i = 0; i < CREATURE_MAX_SPELLS; ++i)
18573 if (charmInfo->GetCharmSpell(i)->GetAction())
18574 ++addlist;
18579 WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * MAX_UNIT_ACTION_BAR_INDEX + 1 + 4 * addlist + 1);
18580 data << charm->GetObjectGuid();
18581 data << uint16(0);
18582 data << uint32(0);
18584 if (charm->GetTypeId() != TYPEID_PLAYER)
18585 data << uint8(charmInfo->GetReactState()) << uint8(charmInfo->GetCommandState()) << uint16(0);
18586 else
18587 data << uint8(0) << uint8(0) << uint16(0);
18589 charmInfo->BuildActionBar(&data);
18591 data << uint8(addlist);
18593 if (addlist)
18595 for (uint32 i = 0; i < CREATURE_MAX_SPELLS; ++i)
18597 CharmSpellEntry* cspell = charmInfo->GetCharmSpell(i);
18598 if (cspell->GetAction())
18599 data << uint32(cspell->packedData);
18603 data << uint8(0); // cooldowns count
18605 GetSession()->SendPacket(&data);
18608 void Player::RemovePetActionBar()
18610 WorldPacket data(SMSG_PET_SPELLS, 8);
18611 data << ObjectGuid();
18612 SendDirectMessage(&data);
18615 void Player::AddSpellMod(Aura* aura, bool apply)
18617 Modifier const* mod = aura->GetModifier();
18618 uint16 Opcode = (mod->m_auraname == SPELL_AURA_ADD_FLAT_MODIFIER) ? SMSG_SET_FLAT_SPELL_MODIFIER : SMSG_SET_PCT_SPELL_MODIFIER;
18620 for (int eff = 0; eff < 96; ++eff)
18622 uint64 _mask = 0;
18623 uint32 _mask2 = 0;
18625 if (eff < 64)
18626 _mask = uint64(1) << (eff - 0);
18627 else
18628 _mask2 = uint32(1) << (eff - 64);
18630 if (aura->GetAuraSpellClassMask().IsFitToFamilyMask(_mask, _mask2))
18632 int32 val = 0;
18633 for (AuraList::const_iterator itr = m_spellMods[mod->m_miscvalue].begin(); itr != m_spellMods[mod->m_miscvalue].end(); ++itr)
18635 if ((*itr)->GetModifier()->m_auraname == mod->m_auraname && ((*itr)->GetAuraSpellClassMask().IsFitToFamilyMask(_mask, _mask2)))
18636 val += (*itr)->GetModifier()->m_amount;
18638 val += apply ? mod->m_amount : -(mod->m_amount);
18639 WorldPacket data(Opcode, 4 + 4 + 1 + 1 + 4);
18640 data << uint32(1);
18641 data << uint32(1);
18642 data << uint8(mod->m_miscvalue);
18643 data << uint8(eff);
18644 data << int32(val);
18645 SendDirectMessage(&data);
18649 if (apply)
18650 m_spellMods[mod->m_miscvalue].push_back(aura);
18651 else
18652 m_spellMods[mod->m_miscvalue].remove(aura);
18655 template <class T> T Player::ApplySpellMod(uint32 spellId, SpellModOp op, T& basevalue, Spell const* /*spell*/)
18657 SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellId);
18658 if (!spellInfo)
18659 return 0;
18661 int32 totalpct = 0;
18662 int32 totalflat = 0;
18663 for (AuraList::iterator itr = m_spellMods[op].begin(); itr != m_spellMods[op].end(); ++itr)
18665 Aura* aura = *itr;
18667 Modifier const* mod = aura->GetModifier();
18669 if (!aura->isAffectedOnSpell(spellInfo))
18670 continue;
18672 if (mod->m_auraname == SPELL_AURA_ADD_FLAT_MODIFIER)
18673 totalflat += mod->m_amount;
18674 else
18676 // skip percent mods for null basevalue (most important for spell mods with charges )
18677 if (basevalue == T(0))
18678 continue;
18680 // special case (skip >10sec spell casts for instant cast setting)
18681 if (mod->m_miscvalue == SPELLMOD_CASTING_TIME
18682 && basevalue >= T(10 * IN_MILLISECONDS) && mod->m_amount <= -100)
18683 continue;
18685 totalpct += mod->m_amount;
18689 float diff = (float)basevalue * (float)totalpct / 100.0f + (float)totalflat;
18690 basevalue = T((float)basevalue + diff);
18691 return T(diff);
18694 template int32 Player::ApplySpellMod<int32>(uint32 spellId, SpellModOp op, int32& basevalue, Spell const* spell);
18695 template uint32 Player::ApplySpellMod<uint32>(uint32 spellId, SpellModOp op, uint32& basevalue, Spell const* spell);
18696 template float Player::ApplySpellMod<float>(uint32 spellId, SpellModOp op, float& basevalue, Spell const* spell);
18698 // send Proficiency
18699 void Player::SendProficiency(ItemClass itemClass, uint32 itemSubclassMask)
18701 WorldPacket data(SMSG_SET_PROFICIENCY, 1 + 4);
18702 data << uint8(itemClass) << uint32(itemSubclassMask);
18703 GetSession()->SendPacket(&data);
18706 void Player::RemovePetitionsAndSigns(ObjectGuid guid)
18708 uint32 lowguid = guid.GetCounter();
18710 QueryResult* result = NULL;
18711 result = CharacterDatabase.PQuery("SELECT ownerguid,petitionguid FROM petition_sign WHERE playerguid = '%u'", lowguid);
18712 if (result)
18714 do // this part effectively does nothing, since the deletion / modification only takes place _after_ the PetitionQuery. Though I don't know if the result remains intact if I execute the delete query beforehand.
18716 // and SendPetitionQueryOpcode reads data from the DB
18717 Field* fields = result->Fetch();
18718 ObjectGuid ownerguid = ObjectGuid(HIGHGUID_PLAYER, fields[0].GetUInt32());
18719 ObjectGuid petitionguid = ObjectGuid(HIGHGUID_ITEM, fields[1].GetUInt32());
18721 // send update if charter owner in game
18722 Player* owner = sObjectMgr.GetPlayer(ownerguid);
18723 if (owner)
18724 owner->GetSession()->SendPetitionQueryOpcode(petitionguid);
18727 while (result->NextRow());
18729 delete result;
18731 CharacterDatabase.PExecute("DELETE FROM petition_sign WHERE playerguid = '%u'", lowguid);
18734 CharacterDatabase.BeginTransaction();
18735 CharacterDatabase.PExecute("DELETE FROM petition WHERE ownerguid = '%u'", lowguid);
18736 CharacterDatabase.PExecute("DELETE FROM petition_sign WHERE ownerguid = '%u'", lowguid);
18737 CharacterDatabase.CommitTransaction();
18740 void Player::LeaveAllArenaTeams(ObjectGuid guid)
18742 uint32 lowguid = guid.GetCounter();
18743 QueryResult* result = CharacterDatabase.PQuery("SELECT arena_team_member.arenateamid FROM arena_team_member JOIN arena_team ON arena_team_member.arenateamid = arena_team.arenateamid WHERE guid='%u'", lowguid);
18744 if (!result)
18745 return;
18749 Field* fields = result->Fetch();
18750 if (uint32 at_id = fields[0].GetUInt32())
18751 if (ArenaTeam* at = sObjectMgr.GetArenaTeamById(at_id))
18752 at->DelMember(guid);
18755 while (result->NextRow());
18757 delete result;
18760 void Player::SetRestBonus(float rest_bonus_new)
18762 // Prevent resting on max level
18763 if (getLevel() >= sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL))
18764 rest_bonus_new = 0;
18766 if (rest_bonus_new < 0)
18767 rest_bonus_new = 0;
18769 float rest_bonus_max = (float)GetUInt32Value(PLAYER_NEXT_LEVEL_XP) * 1.5f / 2.0f;
18771 if (rest_bonus_new > rest_bonus_max)
18772 m_rest_bonus = rest_bonus_max;
18773 else
18774 m_rest_bonus = rest_bonus_new;
18776 // update data for client
18777 if (m_rest_bonus > 10)
18778 SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_RESTED);
18779 else if (m_rest_bonus <= 1)
18780 SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_NORMAL);
18782 // RestTickUpdate
18783 SetUInt32Value(PLAYER_REST_STATE_EXPERIENCE, uint32(m_rest_bonus));
18786 void Player::HandleStealthedUnitsDetection()
18788 std::list<Unit*> stealthedUnits;
18790 MaNGOS::AnyStealthedCheck u_check(this);
18791 MaNGOS::UnitListSearcher<MaNGOS::AnyStealthedCheck > searcher(stealthedUnits, u_check);
18792 Cell::VisitAllObjects(this, searcher, MAX_PLAYER_STEALTH_DETECT_RANGE);
18794 WorldObject const* viewPoint = GetCamera().GetBody();
18796 for (std::list<Unit*>::const_iterator i = stealthedUnits.begin(); i != stealthedUnits.end(); ++i)
18798 if ((*i) == this)
18799 continue;
18801 bool hasAtClient = HaveAtClient((*i));
18802 bool hasDetected = (*i)->isVisibleForOrDetect(this, viewPoint, true);
18804 if (hasDetected)
18806 if (!hasAtClient)
18808 ObjectGuid i_guid = (*i)->GetObjectGuid();
18809 (*i)->SendCreateUpdateToPlayer(this);
18810 m_clientGUIDs.insert(i_guid);
18812 DEBUG_FILTER_LOG(LOG_FILTER_VISIBILITY_CHANGES, "%s is detected in stealth by player %u. Distance = %f", i_guid.GetString().c_str(), GetGUIDLow(), GetDistance(*i));
18814 // target aura duration for caster show only if target exist at caster client
18815 // send data at target visibility change (adding to client)
18816 if ((*i) != this && (*i)->isType(TYPEMASK_UNIT))
18817 SendAurasForTarget(*i);
18820 else
18822 if (hasAtClient)
18824 (*i)->DestroyForPlayer(this);
18825 m_clientGUIDs.erase((*i)->GetObjectGuid());
18831 bool Player::ActivateTaxiPathTo(std::vector<uint32> const& nodes, Creature* npc /*= NULL*/, uint32 spellid /*= 0*/)
18833 if (nodes.size() < 2)
18834 return false;
18836 // not let cheating with start flight in time of logout process || if casting not finished || while in combat || if not use Spell's with EffectSendTaxi
18837 if (GetSession()->isLogingOut() || isInCombat())
18839 GetSession()->SendActivateTaxiReply(ERR_TAXIPLAYERBUSY);
18840 return false;
18843 if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_MOVE))
18844 return false;
18846 // taximaster case
18847 if (npc)
18849 // not let cheating with start flight mounted
18850 if (IsMounted())
18852 GetSession()->SendActivateTaxiReply(ERR_TAXIPLAYERALREADYMOUNTED);
18853 return false;
18856 if (IsInDisallowedMountForm())
18858 GetSession()->SendActivateTaxiReply(ERR_TAXIPLAYERSHAPESHIFTED);
18859 return false;
18862 // not let cheating with start flight in time of logout process || if casting not finished || while in combat || if not use Spell's with EffectSendTaxi
18863 if (IsNonMeleeSpellCasted(false))
18865 GetSession()->SendActivateTaxiReply(ERR_TAXIPLAYERBUSY);
18866 return false;
18869 // cast case or scripted call case
18870 else
18872 RemoveSpellsCausingAura(SPELL_AURA_MOUNTED);
18874 if (IsInDisallowedMountForm())
18875 RemoveSpellsCausingAura(SPELL_AURA_MOD_SHAPESHIFT);
18877 if (Spell* spell = GetCurrentSpell(CURRENT_GENERIC_SPELL))
18878 if (spell->m_spellInfo->Id != spellid)
18879 InterruptSpell(CURRENT_GENERIC_SPELL, false);
18881 InterruptSpell(CURRENT_AUTOREPEAT_SPELL, false);
18883 if (Spell* spell = GetCurrentSpell(CURRENT_CHANNELED_SPELL))
18884 if (spell->m_spellInfo->Id != spellid)
18885 InterruptSpell(CURRENT_CHANNELED_SPELL, true);
18888 uint32 sourcenode = nodes[0];
18890 // starting node too far away (cheat?)
18891 TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(sourcenode);
18892 if (!node)
18894 GetSession()->SendActivateTaxiReply(ERR_TAXINOSUCHPATH);
18895 return false;
18898 // check node starting pos data set case if provided
18899 if (node->x != 0.0f || node->y != 0.0f || node->z != 0.0f)
18901 if (node->map_id != GetMapId() ||
18902 (node->x - GetPositionX()) * (node->x - GetPositionX()) +
18903 (node->y - GetPositionY()) * (node->y - GetPositionY()) +
18904 (node->z - GetPositionZ()) * (node->z - GetPositionZ()) >
18905 (2 * INTERACTION_DISTANCE) * (2 * INTERACTION_DISTANCE) * (2 * INTERACTION_DISTANCE))
18907 GetSession()->SendActivateTaxiReply(ERR_TAXITOOFARAWAY);
18908 return false;
18911 // node must have pos if taxi master case (npc != NULL)
18912 else if (npc)
18914 GetSession()->SendActivateTaxiReply(ERR_TAXIUNSPECIFIEDSERVERERROR);
18915 return false;
18918 // Prepare to flight start now
18920 // stop combat at start taxi flight if any
18921 CombatStop();
18923 // stop trade (client cancel trade at taxi map open but cheating tools can be used for reopen it)
18924 TradeCancel(true);
18926 // clean not finished taxi path if any
18927 m_taxi.ClearTaxiDestinations();
18929 // 0 element current node
18930 m_taxi.AddTaxiDestination(sourcenode);
18932 // fill destinations path tail
18933 uint32 sourcepath = 0;
18934 uint32 totalcost = 0;
18936 uint32 prevnode = sourcenode;
18937 uint32 lastnode = 0;
18939 for (uint32 i = 1; i < nodes.size(); ++i)
18941 uint32 path, cost;
18943 lastnode = nodes[i];
18944 sObjectMgr.GetTaxiPath(prevnode, lastnode, path, cost);
18946 if (!path)
18948 m_taxi.ClearTaxiDestinations();
18949 return false;
18952 totalcost += cost;
18954 if (prevnode == sourcenode)
18955 sourcepath = path;
18957 m_taxi.AddTaxiDestination(lastnode);
18959 prevnode = lastnode;
18962 // get mount model (in case non taximaster (npc==NULL) allow more wide lookup)
18963 uint32 mount_display_id = sObjectMgr.GetTaxiMountDisplayId(sourcenode, GetTeam(), npc == NULL);
18965 // in spell case allow 0 model
18966 if ((mount_display_id == 0 && spellid == 0) || sourcepath == 0)
18968 GetSession()->SendActivateTaxiReply(ERR_TAXIUNSPECIFIEDSERVERERROR);
18970 m_taxi.ClearTaxiDestinations();
18971 return false;
18974 uint64 money = GetMoney();
18976 if (npc)
18977 totalcost = (uint32)ceil(totalcost * GetReputationPriceDiscount(npc));
18979 if (money < totalcost)
18981 GetSession()->SendActivateTaxiReply(ERR_TAXINOTENOUGHMONEY);
18983 m_taxi.ClearTaxiDestinations();
18984 return false;
18987 // Checks and preparations done, DO FLIGHT
18988 ModifyMoney(-(int64)totalcost);
18989 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TRAVELLING, totalcost);
18990 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_FLIGHT_PATHS_TAKEN, 1);
18992 // prevent stealth flight
18993 RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH);
18995 GetSession()->SendActivateTaxiReply(ERR_TAXIOK);
18996 GetSession()->SendDoFlight(mount_display_id, sourcepath);
18998 return true;
19001 bool Player::ActivateTaxiPathTo(uint32 taxi_path_id, uint32 spellid /*= 0*/)
19003 TaxiPathEntry const* entry = sTaxiPathStore.LookupEntry(taxi_path_id);
19004 if (!entry)
19005 return false;
19007 std::vector<uint32> nodes;
19009 nodes.resize(2);
19010 nodes[0] = entry->from;
19011 nodes[1] = entry->to;
19013 return ActivateTaxiPathTo(nodes, NULL, spellid);
19016 void Player::ContinueTaxiFlight()
19018 uint32 sourceNode = m_taxi.GetTaxiSource();
19019 if (!sourceNode)
19020 return;
19022 DEBUG_LOG("WORLD: Restart character %u taxi flight", GetGUIDLow());
19024 uint32 mountDisplayId = sObjectMgr.GetTaxiMountDisplayId(sourceNode, GetTeam(), true);
19025 uint32 path = m_taxi.GetCurrentTaxiPath();
19027 // search appropriate start path node
19028 uint32 startNode = 0;
19030 TaxiPathNodeList const& nodeList = sTaxiPathNodesByPath[path];
19032 float distPrev = MAP_SIZE * MAP_SIZE;
19033 float distNext =
19034 (nodeList[0].x - GetPositionX()) * (nodeList[0].x - GetPositionX()) +
19035 (nodeList[0].y - GetPositionY()) * (nodeList[0].y - GetPositionY()) +
19036 (nodeList[0].z - GetPositionZ()) * (nodeList[0].z - GetPositionZ());
19038 for (uint32 i = 1; i < nodeList.size(); ++i)
19040 TaxiPathNodeEntry const& node = nodeList[i];
19041 TaxiPathNodeEntry const& prevNode = nodeList[i - 1];
19043 // skip nodes at another map
19044 if (node.mapid != GetMapId())
19045 continue;
19047 distPrev = distNext;
19049 distNext =
19050 (node.x - GetPositionX()) * (node.x - GetPositionX()) +
19051 (node.y - GetPositionY()) * (node.y - GetPositionY()) +
19052 (node.z - GetPositionZ()) * (node.z - GetPositionZ());
19054 float distNodes =
19055 (node.x - prevNode.x) * (node.x - prevNode.x) +
19056 (node.y - prevNode.y) * (node.y - prevNode.y) +
19057 (node.z - prevNode.z) * (node.z - prevNode.z);
19059 if (distNext + distPrev < distNodes)
19061 startNode = i;
19062 break;
19066 GetSession()->SendDoFlight(mountDisplayId, path, startNode);
19069 void Player::ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs)
19071 // last check 4.3.4
19072 WorldPacket data(SMSG_SPELL_COOLDOWN, 8 + 1 + m_spells.size() * 8);
19073 data << GetObjectGuid();
19074 data << uint8(0x0); // flags (0x1, 0x2)
19075 time_t curTime = time(NULL);
19076 for (PlayerSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr)
19078 if (itr->second.state == PLAYERSPELL_REMOVED)
19079 continue;
19080 uint32 unSpellId = itr->first;
19081 SpellEntry const* spellInfo = sSpellStore.LookupEntry(unSpellId);
19082 if (!spellInfo)
19084 MANGOS_ASSERT(spellInfo);
19085 continue;
19088 // Not send cooldown for this spells
19089 if (spellInfo->HasAttribute(SPELL_ATTR_DISABLED_WHILE_ACTIVE))
19090 continue;
19092 if ((idSchoolMask & GetSpellSchoolMask(spellInfo)) && GetSpellCooldownDelay(unSpellId) < unTimeMs)
19094 data << uint32(unSpellId);
19095 data << uint32(unTimeMs); // in m.secs
19096 AddSpellCooldown(unSpellId, 0, curTime + unTimeMs / IN_MILLISECONDS);
19099 GetSession()->SendPacket(&data);
19102 void Player::InitDataForForm(bool reapplyMods)
19104 ShapeshiftForm form = GetShapeshiftForm();
19106 SpellShapeshiftFormEntry const* ssEntry = sSpellShapeshiftFormStore.LookupEntry(form);
19107 if (ssEntry && ssEntry->attackSpeed)
19109 SetAttackTime(BASE_ATTACK, ssEntry->attackSpeed);
19110 SetAttackTime(OFF_ATTACK, ssEntry->attackSpeed);
19111 SetAttackTime(RANGED_ATTACK, BASE_ATTACK_TIME);
19113 else
19114 SetRegularAttackTime();
19116 switch (form)
19118 case FORM_CAT:
19120 if (getPowerType() != POWER_ENERGY)
19121 setPowerType(POWER_ENERGY);
19122 break;
19124 case FORM_BEAR:
19126 if (getPowerType() != POWER_RAGE)
19127 setPowerType(POWER_RAGE);
19128 break;
19130 default: // 0, for example
19132 ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(getClass());
19133 if (cEntry && cEntry->powerType < MAX_POWERS && uint32(getPowerType()) != cEntry->powerType)
19134 setPowerType(Powers(cEntry->powerType));
19135 break;
19139 // update auras at form change, ignore this at mods reapply (.reset stats/etc) when form not change.
19140 if (!reapplyMods)
19141 UpdateEquipSpellsAtFormChange();
19143 UpdateAttackPowerAndDamage();
19144 UpdateAttackPowerAndDamage(true);
19147 void Player::InitDisplayIds()
19149 PlayerInfo const* info = sObjectMgr.GetPlayerInfo(getRace(), getClass());
19150 if (!info)
19152 sLog.outError("Player %u has incorrect race/class pair. Can't init display ids.", GetGUIDLow());
19153 return;
19156 // reset scale before reapply auras
19157 SetObjectScale(DEFAULT_OBJECT_SCALE);
19159 uint8 gender = getGender();
19160 switch (gender)
19162 case GENDER_FEMALE:
19163 SetDisplayId(info->displayId_f);
19164 SetNativeDisplayId(info->displayId_f);
19165 break;
19166 case GENDER_MALE:
19167 SetDisplayId(info->displayId_m);
19168 SetNativeDisplayId(info->displayId_m);
19169 break;
19170 default:
19171 sLog.outError("Invalid gender %u for player", gender);
19172 return;
19176 void Player::TakeExtendedCost(uint32 extendedCostId, uint32 count)
19178 ItemExtendedCostEntry const* extendedCost = sItemExtendedCostStore.LookupEntry(extendedCostId);
19180 for (uint8 i = 0; i < MAX_EXTENDED_COST_ITEMS; ++i)
19182 if (extendedCost->reqitem[i])
19183 DestroyItemCount(extendedCost->reqitem[i], extendedCost->reqitemcount[i] * count, true);
19186 for (int i = 0; i < MAX_EXTENDED_COST_CURRENCIES; ++i)
19188 if (extendedCost->reqcur[i] == CURRENCY_NONE)
19189 continue;
19191 if (extendedCost->IsSeasonCurrencyRequirement(i))
19192 continue;
19194 CurrencyTypesEntry const * entry = sCurrencyTypesStore.LookupEntry(extendedCost->reqcur[i]);
19195 if (!entry)
19196 continue;
19198 int32 cost = int32(extendedCost->reqcurrcount[i] * count);
19199 ModifyCurrencyCount(entry->ID, -cost);
19203 // Return true is the bought item has a max count to force refresh of window by caller
19204 bool Player::BuyItemFromVendorSlot(ObjectGuid vendorGuid, uint32 vendorslot, uint32 item, uint8 count, uint8 bag, uint8 slot)
19206 // cheating attempt
19207 if (count < 1) count = 1;
19209 if (!isAlive())
19210 return false;
19212 ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(item);
19213 if (!pProto)
19215 SendBuyError(BUY_ERR_CANT_FIND_ITEM, NULL, item, 0);
19216 return false;
19219 Creature* pCreature = GetNPCIfCanInteractWith(vendorGuid, UNIT_NPC_FLAG_VENDOR);
19220 if (!pCreature)
19222 DEBUG_LOG("WORLD: BuyItemFromVendor - %s not found or you can't interact with him.", vendorGuid.GetString().c_str());
19223 SendBuyError(BUY_ERR_DISTANCE_TOO_FAR, NULL, item, 0);
19224 return false;
19227 VendorItemData const* vItems = pCreature->GetVendorItems();
19228 VendorItemData const* tItems = pCreature->GetVendorTemplateItems();
19229 if ((!vItems || vItems->Empty()) && (!tItems || tItems->Empty()))
19231 SendBuyError(BUY_ERR_CANT_FIND_ITEM, pCreature, item, 0);
19232 return false;
19235 uint32 vCount = vItems ? vItems->GetItemCount() : 0;
19236 uint32 tCount = tItems ? tItems->GetItemCount() : 0;
19238 if (vendorslot >= vCount + tCount)
19240 SendBuyError(BUY_ERR_CANT_FIND_ITEM, pCreature, item, 0);
19241 return false;
19244 VendorItem const* crItem = vendorslot < vCount ? vItems->GetItem(vendorslot) : tItems->GetItem(vendorslot - vCount);
19245 if (!crItem) // store diff item (cheating)
19247 SendBuyError(BUY_ERR_CANT_FIND_ITEM, pCreature, item, 0);
19248 return false;
19251 if (crItem->item != item) // store diff item (cheating or special convert)
19253 bool converted = false;
19255 // possible item converted for BoA case
19256 ItemPrototype const* crProto = ObjectMgr::GetItemPrototype(crItem->item);
19257 if (crProto->Flags & ITEM_FLAG_BOA && crProto->RequiredReputationFaction &&
19258 uint32(GetReputationRank(crProto->RequiredReputationFaction)) >= crProto->RequiredReputationRank)
19259 converted = (sObjectMgr.GetItemConvert(crItem->item, getRaceMask()) != 0);
19261 if (!converted)
19263 SendBuyError(BUY_ERR_CANT_FIND_ITEM, pCreature, item, 0);
19264 return false;
19268 uint32 totalCount = count;
19270 // check current item amount if it limited
19271 if (crItem->maxcount != 0)
19273 if (pCreature->GetVendorItemCurrentCount(crItem) < totalCount)
19275 SendBuyError(BUY_ERR_ITEM_ALREADY_SOLD, pCreature, item, 0);
19276 return false;
19280 if (uint32(GetReputationRank(pProto->RequiredReputationFaction)) < pProto->RequiredReputationRank)
19282 SendBuyError(BUY_ERR_REPUTATION_REQUIRE, pCreature, item, 0);
19283 return false;
19286 if (uint32 extendedCostId = crItem->ExtendedCost)
19288 ItemExtendedCostEntry const* iece = sItemExtendedCostStore.LookupEntry(extendedCostId);
19289 if (!iece)
19291 sLog.outError("Item %u have wrong ExtendedCost field value %u", pProto->ItemId, extendedCostId);
19292 return false;
19295 // item base price
19296 for (uint8 i = 0; i < MAX_EXTENDED_COST_ITEMS; ++i)
19298 if (iece->reqitem[i] && !HasItemCount(iece->reqitem[i], iece->reqitemcount[i] * count))
19300 SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, NULL, NULL);
19301 return false;
19305 // currency price
19306 for (uint8 i = 0; i < MAX_EXTENDED_COST_CURRENCIES; ++i)
19308 if (iece->reqcur[i] == CURRENCY_NONE)
19309 continue;
19311 CurrencyTypesEntry const * costCurrency = sCurrencyTypesStore.LookupEntry(iece->reqcur[i]);
19312 if (!costCurrency)
19314 sLog.outError("Item %u has ExtendedCost %u with unexistent currency id %u", pProto->ItemId, extendedCostId, iece->reqcur[i]);
19315 continue;
19318 int32 cost = int32(iece->reqcurrcount[i] * count);
19320 bool hasCount = iece->IsSeasonCurrencyRequirement(i) ? HasCurrencySeasonCount(iece->reqcur[i], cost) : HasCurrencyCount(iece->reqcur[i], cost);
19321 if (!hasCount)
19323 SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, NULL);
19324 return false;
19328 // check for personal arena rating requirement
19329 if (GetMaxPersonalArenaRatingRequirement(iece->reqarenaslot) < iece->reqpersonalarenarating)
19331 // probably not the proper equip err
19332 SendEquipError(EQUIP_ERR_CANT_EQUIP_RANK, NULL, NULL);
19333 return false;
19337 uint64 price = (crItem->ExtendedCost == 0 || pProto->Flags2 & ITEM_FLAG2_EXT_COST_REQUIRES_GOLD) ? pProto->BuyPrice * count : 0;
19339 // reputation discount
19340 if (price)
19341 price = uint64(floor(price * GetReputationPriceDiscount(pCreature)));
19343 if (GetMoney() < price)
19345 SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, pCreature, item, 0);
19346 return false;
19349 Item* pItem = NULL;
19351 if ((bag == NULL_BAG && slot == NULL_SLOT) || IsInventoryPos(bag, slot))
19353 ItemPosCountVec dest;
19354 InventoryResult msg = CanStoreNewItem(bag, slot, dest, item, totalCount);
19355 if (msg != EQUIP_ERR_OK)
19357 SendEquipError(msg, NULL, NULL, item);
19358 return false;
19361 ModifyMoney(-int64(price));
19363 if (crItem->ExtendedCost)
19364 TakeExtendedCost(crItem->ExtendedCost, count);
19366 pItem = StoreNewItem(dest, item, true);
19368 else if (IsEquipmentPos(bag, slot))
19370 if (totalCount != 1)
19372 SendEquipError(EQUIP_ERR_ITEM_CANT_BE_EQUIPPED, NULL, NULL);
19373 return false;
19376 uint16 dest;
19377 InventoryResult msg = CanEquipNewItem(slot, dest, item, false);
19378 if (msg != EQUIP_ERR_OK)
19380 SendEquipError(msg, NULL, NULL, item);
19381 return false;
19384 ModifyMoney(-int64(price));
19386 if (crItem->ExtendedCost)
19387 TakeExtendedCost(crItem->ExtendedCost, count);
19389 pItem = EquipNewItem(dest, item, true);
19391 if (pItem)
19392 AutoUnequipOffhandIfNeed();
19394 else
19396 SendEquipError(EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT, NULL, NULL);
19397 return false;
19400 if (!pItem)
19401 return false;
19403 uint32 new_count = pCreature->UpdateVendorItemCurrentCount(crItem, totalCount);
19405 WorldPacket data(SMSG_BUY_ITEM, 8 + 4 + 4 + 4);
19406 data << pCreature->GetObjectGuid();
19407 data << uint32(vendorslot + 1); // numbered from 1 at client
19408 data << uint32(crItem->maxcount > 0 ? new_count : 0xFFFFFFFF);
19409 data << uint32(count);
19410 GetSession()->SendPacket(&data);
19412 SendNewItem(pItem, totalCount, true, false, false);
19414 return crItem->maxcount != 0;
19417 bool Player::BuyCurrencyFromVendorSlot(ObjectGuid vendorGuid, uint32 vendorslot, uint32 currencyId, uint8 count)
19419 // cheating attempt
19420 if (count < 1) count = 1;
19422 if (!isAlive())
19423 return false;
19425 CurrencyTypesEntry const* pCurrency = sCurrencyTypesStore.LookupEntry(currencyId);
19426 if (!pCurrency)
19427 return false;
19429 if (currencyId == CURRENCY_CONQUEST_ARENA_META || currencyId == CURRENCY_CONQUEST_BG_META)
19430 return false;
19432 Creature* pCreature = GetNPCIfCanInteractWith(vendorGuid, UNIT_NPC_FLAG_VENDOR);
19433 if (!pCreature)
19435 DEBUG_LOG("WORLD: BuyCurrencyFromVendorSlot - %s not found or you can't interact with him.", vendorGuid.GetString().c_str());
19436 return false;
19439 VendorItemData const* vItems = pCreature->GetVendorItems();
19440 VendorItemData const* tItems = pCreature->GetVendorTemplateItems();
19441 if ((!vItems || vItems->Empty()) && (!tItems || tItems->Empty()))
19442 return false;
19444 uint32 vCount = vItems ? vItems->GetItemCount() : 0;
19445 uint32 tCount = tItems ? tItems->GetItemCount() : 0;
19447 if (vendorslot >= vCount + tCount)
19448 return false;
19450 VendorItem const* crItem = vendorslot < vCount ? vItems->GetItem(vendorslot) : tItems->GetItem(vendorslot - vCount);
19451 if (!crItem) // store diff item (cheating)
19452 return false;
19454 if (crItem->item != currencyId) // store diff item (cheating)
19455 return false;
19457 if (!crItem->maxcount)
19459 DEBUG_LOG("WORLD: BuyCurrencyFromVendorSlot - %s: crItem->maxcount (%u) == 0 for currency %u and player %s.",
19460 vendorGuid.GetString().c_str(), crItem->maxcount, currencyId, GetGuidStr().c_str());
19461 return false;
19464 if (uint32 extendedCostId = crItem->ExtendedCost)
19466 ItemExtendedCostEntry const* iece = sItemExtendedCostStore.LookupEntry(extendedCostId);
19467 if (!iece)
19469 sLog.outError("WORLD: BuyCurrencyFromVendorSlot: Currency %u have wrong ExtendedCost field value %u for %s", currencyId, extendedCostId, vendorGuid.GetString().c_str());
19470 return false;
19473 // item base price
19474 for (uint8 i = 0; i < MAX_EXTENDED_COST_ITEMS; ++i)
19476 if (iece->reqitem[i] && !HasItemCount(iece->reqitem[i], iece->reqitemcount[i] * count))
19478 SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, NULL, NULL);
19479 return false;
19483 // currency price
19484 for (uint8 i = 0; i < MAX_EXTENDED_COST_CURRENCIES; ++i)
19486 if (iece->reqcur[i] == CURRENCY_NONE)
19487 continue;
19489 CurrencyTypesEntry const * costCurrency = sCurrencyTypesStore.LookupEntry(iece->reqcur[i]);
19490 if (!costCurrency)
19492 sLog.outError("Currency %u has ExtendedCost %u with unexistent currency id %u", currencyId, extendedCostId, iece->reqcur[i]);
19493 continue;
19496 int32 cost = int32(iece->reqcurrcount[i] * count);
19497 bool hasCount = iece->IsSeasonCurrencyRequirement(i) ? HasCurrencySeasonCount(iece->reqcur[i], cost) : HasCurrencyCount(iece->reqcur[i], cost);
19498 if (!hasCount)
19500 SendEquipError(EQUIP_ERR_VENDOR_MISSING_TURNINS, NULL);
19501 return false;
19505 // check for personal arena rating requirement
19506 if (GetMaxPersonalArenaRatingRequirement(iece->reqarenaslot) < iece->reqpersonalarenarating)
19508 // probably not the proper equip err
19509 SendEquipError(EQUIP_ERR_CANT_EQUIP_RANK, NULL, NULL);
19510 return false;
19513 else
19515 SendBuyError(BUY_ERR_ITEM_SOLD_OUT, 0, 0, 0);
19516 return false;
19519 if (uint32 totalCap = GetCurrencyTotalCap(pCurrency))
19521 if (GetCurrencyCount(currencyId) >= totalCap)
19524 SendBuyError(BUY_ERR_CANT_CARRY_MORE, 0, 0, 0);
19525 return false;
19529 if (uint32 weekCap = GetCurrencyWeekCap(pCurrency))
19531 if (GetCurrencyWeekCount(currencyId) >= weekCap)
19533 SendBuyError(BUY_ERR_CANT_CARRY_MORE, 0, 0, 0);
19534 return false;
19538 if (crItem->ExtendedCost)
19539 TakeExtendedCost(crItem->ExtendedCost, count);
19541 ModifyCurrencyCount(currencyId, crItem->maxcount, true, false);
19544 DEBUG_LOG("WORLD: BuyCurrencyFromVendorSlot - %s: Player %s buys currency %u amount %u count %u.",
19545 vendorGuid.GetString().c_str(), GetGuidStr().c_str(), currencyId, crItem->maxcount, count);
19547 return true;
19550 uint32 Player::GetMaxPersonalArenaRatingRequirement(uint32 minarenaslot)
19552 // returns the maximal personal arena rating that can be used to purchase items requiring this condition
19553 // the personal rating of the arena team must match the required limit as well
19554 // so return max[in arenateams](min(personalrating[teamtype], teamrating[teamtype]))
19555 uint32 max_personal_rating = 0;
19556 for (int i = minarenaslot; i < MAX_ARENA_SLOT; ++i)
19558 if (ArenaTeam* at = sObjectMgr.GetArenaTeamById(GetArenaTeamId(i)))
19560 uint32 p_rating = GetArenaPersonalRating(i);
19561 uint32 t_rating = at->GetRating();
19562 p_rating = p_rating < t_rating ? p_rating : t_rating;
19563 if (max_personal_rating < p_rating)
19564 max_personal_rating = p_rating;
19567 return max_personal_rating;
19570 void Player::UpdateHomebindTime(uint32 time)
19572 // GMs never get homebind timer online
19573 if (m_InstanceValid || isGameMaster())
19575 if (m_HomebindTimer) // instance valid, but timer not reset
19577 // hide reminder
19578 WorldPacket data(SMSG_RAID_GROUP_ONLY, 4 + 4);
19579 data << uint32(0);
19580 data << uint32(ERR_RAID_GROUP_NONE); // error used only when timer = 0
19581 GetSession()->SendPacket(&data);
19583 // instance is valid, reset homebind timer
19584 m_HomebindTimer = 0;
19586 else if (m_HomebindTimer > 0)
19588 if (time >= m_HomebindTimer)
19590 // teleport to nearest graveyard
19591 RepopAtGraveyard();
19593 else
19594 m_HomebindTimer -= time;
19596 else
19598 // instance is invalid, start homebind timer
19599 m_HomebindTimer = 60000;
19600 // send message to player
19601 WorldPacket data(SMSG_RAID_GROUP_ONLY, 4 + 4);
19602 data << uint32(m_HomebindTimer);
19603 data << uint32(ERR_RAID_GROUP_NONE); // error used only when timer = 0
19604 GetSession()->SendPacket(&data);
19605 DEBUG_LOG("PLAYER: Player '%s' (GUID: %u) will be teleported to homebind in 60 seconds", GetName(), GetGUIDLow());
19609 void Player::UpdatePvP(bool state, bool ovrride)
19611 if (!state || ovrride)
19613 SetPvP(state);
19614 pvpInfo.endTimer = 0;
19616 else
19618 if (pvpInfo.endTimer != 0)
19619 pvpInfo.endTimer = time(NULL);
19620 else
19621 SetPvP(state);
19625 void Player::AddSpellAndCategoryCooldowns(SpellEntry const* spellInfo, uint32 itemId, Spell* spell, bool infinityCooldown)
19627 // init cooldown values
19628 uint32 cat = 0;
19629 int32 rec = -1;
19630 int32 catrec = -1;
19632 // some special item spells without correct cooldown in SpellInfo
19633 // cooldown information stored in item prototype
19635 if (itemId)
19637 if (ItemPrototype const* proto = ObjectMgr::GetItemPrototype(itemId))
19639 for (int idx = 0; idx < MAX_ITEM_PROTO_SPELLS; ++idx)
19641 if (proto->Spells[idx].SpellId == spellInfo->Id)
19643 cat = proto->Spells[idx].SpellCategory;
19644 rec = proto->Spells[idx].SpellCooldown;
19645 catrec = proto->Spells[idx].SpellCategoryCooldown;
19646 break;
19652 // if no cooldown found above then base at DBC data
19653 if (rec < 0 && catrec < 0)
19655 cat = spellInfo->GetCategory();
19656 rec = spellInfo->GetRecoveryTime();
19657 catrec = spellInfo->GetCategoryRecoveryTime();
19660 time_t curTime = time(NULL);
19662 time_t catrecTime;
19663 time_t recTime;
19665 // overwrite time for selected category
19666 if (infinityCooldown)
19668 // use +MONTH as infinity mark for spell cooldown (will checked as MONTH/2 at save ans skipped)
19669 // but not allow ignore until reset or re-login
19670 catrecTime = catrec > 0 ? curTime + infinityCooldownDelay : 0;
19671 recTime = rec > 0 ? curTime + infinityCooldownDelay : catrecTime;
19673 else
19675 // shoot spells used equipped item cooldown values already assigned in GetAttackTime(RANGED_ATTACK)
19676 // prevent 0 cooldowns set by another way
19677 if (rec <= 0 && catrec <= 0 && (cat == 76 || (IsAutoRepeatRangedSpell(spellInfo) && spellInfo->Id != SPELL_ID_AUTOSHOT)))
19678 rec = GetAttackTime(RANGED_ATTACK);
19680 // Now we have cooldown data (if found any), time to apply mods
19681 if (rec > 0)
19682 ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, rec, spell);
19684 if (catrec > 0)
19685 ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, catrec, spell);
19687 // replace negative cooldowns by 0
19688 if (rec < 0) rec = 0;
19689 if (catrec < 0) catrec = 0;
19691 // no cooldown after applying spell mods
19692 if (rec == 0 && catrec == 0)
19693 return;
19695 catrecTime = catrec ? curTime + catrec / IN_MILLISECONDS : 0;
19696 recTime = rec ? curTime + rec / IN_MILLISECONDS : catrecTime;
19699 // self spell cooldown
19700 if (recTime > 0)
19701 AddSpellCooldown(spellInfo->Id, itemId, recTime);
19703 // category spells
19704 if (cat && catrec > 0)
19706 SpellCategoryStore::const_iterator i_scstore = sSpellCategoryStore.find(cat);
19707 if (i_scstore != sSpellCategoryStore.end())
19709 for (SpellCategorySet::const_iterator i_scset = i_scstore->second.begin(); i_scset != i_scstore->second.end(); ++i_scset)
19711 if (*i_scset == spellInfo->Id) // skip main spell, already handled above
19712 continue;
19714 AddSpellCooldown(*i_scset, itemId, catrecTime);
19720 void Player::AddSpellCooldown(uint32 spellid, uint32 itemid, time_t end_time)
19722 SpellCooldown sc;
19723 sc.end = end_time;
19724 sc.itemid = itemid;
19725 m_spellCooldowns[spellid] = sc;
19728 void Player::SendCooldownEvent(SpellEntry const* spellInfo, uint32 itemId, Spell* spell)
19730 // start cooldowns at server side, if any
19731 AddSpellAndCategoryCooldowns(spellInfo, itemId, spell);
19733 // Send activate cooldown timer (possible 0) at client side
19734 WorldPacket data(SMSG_COOLDOWN_EVENT, (4 + 8));
19735 data << uint32(spellInfo->Id);
19736 data << GetObjectGuid();
19737 SendDirectMessage(&data);
19740 void Player::UpdatePotionCooldown(Spell* spell)
19742 // no potion used in combat or still in combat
19743 if (!m_lastPotionId || isInCombat())
19744 return;
19746 // Call not from spell cast, send cooldown event for item spells if no in combat
19747 if (!spell)
19749 // spell/item pair let set proper cooldown (except nonexistent charged spell cooldown spellmods for potions)
19750 if (ItemPrototype const* proto = ObjectMgr::GetItemPrototype(m_lastPotionId))
19751 for (int idx = 0; idx < 5; ++idx)
19752 if (proto->Spells[idx].SpellId && proto->Spells[idx].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE)
19753 if (SpellEntry const* spellInfo = sSpellStore.LookupEntry(proto->Spells[idx].SpellId))
19754 SendCooldownEvent(spellInfo, m_lastPotionId);
19756 // from spell cases (m_lastPotionId set in Spell::SendSpellCooldown)
19757 else
19758 SendCooldownEvent(spell->m_spellInfo, m_lastPotionId, spell);
19760 m_lastPotionId = 0;
19763 // slot to be excluded while counting
19764 bool Player::EnchantmentFitsRequirements(uint32 enchantmentcondition, int8 slot)
19766 if (!enchantmentcondition)
19767 return true;
19769 SpellItemEnchantmentConditionEntry const* Condition = sSpellItemEnchantmentConditionStore.LookupEntry(enchantmentcondition);
19771 if (!Condition)
19772 return true;
19774 uint8 curcount[4] = {0, 0, 0, 0};
19776 // counting current equipped gem colors
19777 for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
19779 if (i == slot)
19780 continue;
19781 Item* pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
19782 if (pItem2 && !pItem2->IsBroken() && pItem2->GetProto()->Socket[0].Color)
19784 for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + 3; ++enchant_slot)
19786 uint32 enchant_id = pItem2->GetEnchantmentId(EnchantmentSlot(enchant_slot));
19787 if (!enchant_id)
19788 continue;
19790 SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
19791 if (!enchantEntry)
19792 continue;
19794 uint32 gemid = enchantEntry->GemID;
19795 if (!gemid)
19796 continue;
19798 ItemPrototype const* gemProto = sItemStorage.LookupEntry<ItemPrototype>(gemid);
19799 if (!gemProto)
19800 continue;
19802 GemPropertiesEntry const* gemProperty = sGemPropertiesStore.LookupEntry(gemProto->GemProperties);
19803 if (!gemProperty)
19804 continue;
19806 uint8 GemColor = gemProperty->color;
19808 for (uint8 b = 0, tmpcolormask = 1; b < 4; ++b, tmpcolormask <<= 1)
19810 if (tmpcolormask & GemColor)
19811 ++curcount[b];
19817 bool activate = true;
19819 for (int i = 0; i < 5; ++i)
19821 if (!Condition->Color[i])
19822 continue;
19824 uint32 _cur_gem = curcount[Condition->Color[i] - 1];
19826 // if have <CompareColor> use them as count, else use <value> from Condition
19827 uint32 _cmp_gem = Condition->CompareColor[i] ? curcount[Condition->CompareColor[i] - 1] : Condition->Value[i];
19829 switch (Condition->Comparator[i])
19831 case 2: // requires less <color> than (<value> || <comparecolor>) gems
19832 activate &= (_cur_gem < _cmp_gem) ? true : false;
19833 break;
19834 case 3: // requires more <color> than (<value> || <comparecolor>) gems
19835 activate &= (_cur_gem > _cmp_gem) ? true : false;
19836 break;
19837 case 5: // requires at least <color> than (<value> || <comparecolor>) gems
19838 activate &= (_cur_gem >= _cmp_gem) ? true : false;
19839 break;
19843 DEBUG_LOG("Checking Condition %u, there are %u Meta Gems, %u Red Gems, %u Yellow Gems and %u Blue Gems, Activate:%s", enchantmentcondition, curcount[0], curcount[1], curcount[2], curcount[3], activate ? "yes" : "no");
19845 return activate;
19848 void Player::CorrectMetaGemEnchants(uint8 exceptslot, bool apply)
19850 // cycle all equipped items
19851 for (uint32 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
19853 // enchants for the slot being socketed are handled by Player::ApplyItemMods
19854 if (slot == exceptslot)
19855 continue;
19857 Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
19859 if (!pItem || !pItem->GetProto()->Socket[0].Color)
19860 continue;
19862 for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + 3; ++enchant_slot)
19864 uint32 enchant_id = pItem->GetEnchantmentId(EnchantmentSlot(enchant_slot));
19865 if (!enchant_id)
19866 continue;
19868 SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
19869 if (!enchantEntry)
19870 continue;
19872 uint32 condition = enchantEntry->EnchantmentCondition;
19873 if (condition)
19875 // was enchant active with/without item?
19876 bool wasactive = EnchantmentFitsRequirements(condition, apply ? exceptslot : -1);
19877 // should it now be?
19878 if (wasactive != EnchantmentFitsRequirements(condition, apply ? -1 : exceptslot))
19880 // ignore item gem conditions
19881 // if state changed, (dis)apply enchant
19882 ApplyEnchantment(pItem, EnchantmentSlot(enchant_slot), !wasactive, true, true);
19889 // if false -> then toggled off if was on| if true -> toggled on if was off AND meets requirements
19890 void Player::ToggleMetaGemsActive(uint8 exceptslot, bool apply)
19892 // cycle all equipped items
19893 for (int slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
19895 // enchants for the slot being socketed are handled by WorldSession::HandleSocketOpcode(WorldPacket& recv_data)
19896 if (slot == exceptslot)
19897 continue;
19899 Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
19901 if (!pItem || !pItem->GetProto()->Socket[0].Color) // if item has no sockets or no item is equipped go to next item
19902 continue;
19904 // cycle all (gem)enchants
19905 for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + 3; ++enchant_slot)
19907 uint32 enchant_id = pItem->GetEnchantmentId(EnchantmentSlot(enchant_slot));
19908 if (!enchant_id) // if no enchant go to next enchant(slot)
19909 continue;
19911 SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
19912 if (!enchantEntry)
19913 continue;
19915 // only metagems to be (de)activated, so only enchants with condition
19916 uint32 condition = enchantEntry->EnchantmentCondition;
19917 if (condition)
19918 ApplyEnchantment(pItem, EnchantmentSlot(enchant_slot), apply);
19923 void Player::SetBattleGroundEntryPoint()
19925 // Taxi path store
19926 if (!m_taxi.empty())
19928 m_bgData.mountSpell = 0;
19929 m_bgData.taxiPath[0] = m_taxi.GetTaxiSource();
19930 m_bgData.taxiPath[1] = m_taxi.GetTaxiDestination();
19932 // On taxi we don't need check for dungeon
19933 m_bgData.joinPos = WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
19934 m_bgData.m_needSave = true;
19935 return;
19937 else
19939 m_bgData.ClearTaxiPath();
19941 // Mount spell id storing
19942 if (IsMounted())
19944 AuraList const& auras = GetAurasByType(SPELL_AURA_MOUNTED);
19945 if (!auras.empty())
19946 m_bgData.mountSpell = (*auras.begin())->GetId();
19948 else
19949 m_bgData.mountSpell = 0;
19951 // If map is dungeon find linked graveyard
19952 if (GetMap()->IsDungeon())
19954 if (const WorldSafeLocsEntry* entry = sObjectMgr.GetClosestGraveYard(GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId(), GetTeam()))
19956 m_bgData.joinPos = WorldLocation(entry->map_id, entry->x, entry->y, entry->z, 0.0f);
19957 m_bgData.m_needSave = true;
19958 return;
19960 else
19961 sLog.outError("SetBattleGroundEntryPoint: Dungeon map %u has no linked graveyard, setting home location as entry point.", GetMapId());
19963 // If new entry point is not BG or arena set it
19964 else if (!GetMap()->IsBattleGroundOrArena())
19966 m_bgData.joinPos = WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
19967 m_bgData.m_needSave = true;
19968 return;
19972 // In error cases use homebind position
19973 m_bgData.joinPos = WorldLocation(m_homebindMapId, m_homebindX, m_homebindY, m_homebindZ, 0.0f);
19974 m_bgData.m_needSave = true;
19977 void Player::LeaveBattleground(bool teleportToEntryPoint)
19979 if (BattleGround* bg = GetBattleGround())
19981 bg->RemovePlayerAtLeave(GetObjectGuid(), teleportToEntryPoint, true);
19983 // call after remove to be sure that player resurrected for correct cast
19984 if (bg->isBattleGround() && !isGameMaster() && sWorld.getConfig(CONFIG_BOOL_BATTLEGROUND_CAST_DESERTER))
19986 if (bg->GetStatus() == STATUS_IN_PROGRESS || bg->GetStatus() == STATUS_WAIT_JOIN)
19988 // lets check if player was teleported from BG and schedule delayed Deserter spell cast
19989 if (IsBeingTeleportedFar())
19991 ScheduleDelayedOperation(DELAYED_SPELL_CAST_DESERTER);
19992 return;
19995 CastSpell(this, 26013, true); // Deserter
20001 bool Player::CanJoinToBattleground() const
20003 // check Deserter debuff
20004 if (GetDummyAura(26013))
20005 return false;
20007 return true;
20010 bool Player::CanReportAfkDueToLimit()
20012 // a player can complain about 15 people per 5 minutes
20013 if (m_bgData.bgAfkReportedCount++ >= 15)
20014 return false;
20016 return true;
20019 /// This player has been blamed to be inactive in a battleground
20020 void Player::ReportedAfkBy(Player* reporter)
20022 BattleGround* bg = GetBattleGround();
20023 if (!bg || bg != reporter->GetBattleGround() || GetTeam() != reporter->GetTeam())
20024 return;
20026 // check if player has 'Idle' or 'Inactive' debuff
20027 if (m_bgData.bgAfkReporter.find(reporter->GetGUIDLow()) == m_bgData.bgAfkReporter.end() && !HasAura(43680, EFFECT_INDEX_0) && !HasAura(43681, EFFECT_INDEX_0) && reporter->CanReportAfkDueToLimit())
20029 m_bgData.bgAfkReporter.insert(reporter->GetGUIDLow());
20030 // 3 players have to complain to apply debuff
20031 if (m_bgData.bgAfkReporter.size() >= 3)
20033 // cast 'Idle' spell
20034 CastSpell(this, 43680, true);
20035 m_bgData.bgAfkReporter.clear();
20040 bool Player::IsVisibleInGridForPlayer(Player* pl) const
20042 // gamemaster in GM mode see all, including ghosts
20043 if (pl->isGameMaster() && GetSession()->GetSecurity() <= pl->GetSession()->GetSecurity())
20044 return true;
20046 // player see dead player/ghost from own group/raid
20047 if (IsInSameRaidWith(pl))
20048 return true;
20050 // Live player see live player or dead player with not realized corpse
20051 if (pl->isAlive() || pl->m_deathTimer > 0)
20052 return isAlive() || m_deathTimer > 0;
20054 // Ghost see other friendly ghosts, that's for sure
20055 if (!(isAlive() || m_deathTimer > 0) && IsFriendlyTo(pl))
20056 return true;
20058 // Dead player see live players near own corpse
20059 if (isAlive())
20061 if (Corpse* corpse = pl->GetCorpse())
20063 // 20 - aggro distance for same level, 25 - max additional distance if player level less that creature level
20064 if (corpse->IsWithinDistInMap(this, (20 + 25) * sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_AGGRO)))
20065 return true;
20069 // and not see any other
20070 return false;
20073 bool Player::IsVisibleGloballyFor(Player* u) const
20075 if (!u)
20076 return false;
20078 // Always can see self
20079 if (u == this)
20080 return true;
20082 // Visible units, always are visible for all players
20083 if (GetVisibility() == VISIBILITY_ON)
20084 return true;
20086 // GMs are visible for higher gms (or players are visible for gms)
20087 if (u->GetSession()->GetSecurity() > SEC_PLAYER)
20088 return GetSession()->GetSecurity() <= u->GetSession()->GetSecurity();
20090 // non faction visibility non-breakable for non-GMs
20091 if (GetVisibility() == VISIBILITY_OFF)
20092 return false;
20094 // non-gm stealth/invisibility not hide from global player lists
20095 return true;
20098 template<class T>
20099 inline void BeforeVisibilityDestroy(T* /*t*/, Player* /*p*/)
20103 template<>
20104 inline void BeforeVisibilityDestroy<Creature>(Creature* t, Player* p)
20106 if (p->GetPetGuid() == t->GetObjectGuid() && ((Creature*)t)->IsPet())
20107 ((Pet*)t)->Unsummon(PET_SAVE_REAGENTS);
20110 void Player::UpdateVisibilityOf(WorldObject const* viewPoint, WorldObject* target)
20112 if (HaveAtClient(target))
20114 if (!target->isVisibleForInState(this, viewPoint, true))
20116 ObjectGuid t_guid = target->GetObjectGuid();
20118 if (target->GetTypeId() == TYPEID_UNIT)
20120 BeforeVisibilityDestroy<Creature>((Creature*)target, this);
20122 // at remove from map (destroy) show kill animation (in different out of range/stealth case)
20123 target->DestroyForPlayer(this, !target->IsInWorld() && ((Creature*)target)->isDead());
20125 else
20126 target->DestroyForPlayer(this);
20128 m_clientGUIDs.erase(t_guid);
20130 DEBUG_FILTER_LOG(LOG_FILTER_VISIBILITY_CHANGES, "%s out of range for player %u. Distance = %f", t_guid.GetString().c_str(), GetGUIDLow(), GetDistance(target));
20133 else
20135 if (target->isVisibleForInState(this, viewPoint, false))
20137 target->SendCreateUpdateToPlayer(this);
20138 if (target->GetTypeId() != TYPEID_GAMEOBJECT || !((GameObject*)target)->IsTransport())
20139 m_clientGUIDs.insert(target->GetObjectGuid());
20141 DEBUG_FILTER_LOG(LOG_FILTER_VISIBILITY_CHANGES, "Object %u (Type: %u) is visible now for player %u. Distance = %f", target->GetGUIDLow(), target->GetTypeId(), GetGUIDLow(), GetDistance(target));
20143 // target aura duration for caster show only if target exist at caster client
20144 // send data at target visibility change (adding to client)
20145 if (target != this && target->isType(TYPEMASK_UNIT))
20146 SendAurasForTarget((Unit*)target);
20151 template<class T>
20152 inline void UpdateVisibilityOf_helper(GuidSet& s64, T* target)
20154 s64.insert(target->GetObjectGuid());
20157 template<>
20158 inline void UpdateVisibilityOf_helper(GuidSet& s64, GameObject* target)
20160 if (!target->IsTransport())
20161 s64.insert(target->GetObjectGuid());
20164 template<class T>
20165 void Player::UpdateVisibilityOf(WorldObject const* viewPoint, T* target, UpdateData& data, std::set<WorldObject*>& visibleNow)
20167 if (HaveAtClient(target))
20169 if (!target->isVisibleForInState(this, viewPoint, true))
20171 BeforeVisibilityDestroy<T>(target, this);
20173 ObjectGuid t_guid = target->GetObjectGuid();
20175 target->BuildOutOfRangeUpdateBlock(&data);
20176 m_clientGUIDs.erase(t_guid);
20178 DEBUG_FILTER_LOG(LOG_FILTER_VISIBILITY_CHANGES, "%s is out of range for %s. Distance = %f", t_guid.GetString().c_str(), GetGuidStr().c_str(), GetDistance(target));
20181 else
20183 if (target->isVisibleForInState(this, viewPoint, false))
20185 visibleNow.insert(target);
20186 target->BuildCreateUpdateBlockForPlayer(&data, this);
20187 UpdateVisibilityOf_helper(m_clientGUIDs, target);
20189 DEBUG_FILTER_LOG(LOG_FILTER_VISIBILITY_CHANGES, "%s is visible now for %s. Distance = %f", target->GetGuidStr().c_str(), GetGuidStr().c_str(), GetDistance(target));
20194 template void Player::UpdateVisibilityOf(WorldObject const* viewPoint, Player* target, UpdateData& data, std::set<WorldObject*>& visibleNow);
20195 template void Player::UpdateVisibilityOf(WorldObject const* viewPoint, Creature* target, UpdateData& data, std::set<WorldObject*>& visibleNow);
20196 template void Player::UpdateVisibilityOf(WorldObject const* viewPoint, Corpse* target, UpdateData& data, std::set<WorldObject*>& visibleNow);
20197 template void Player::UpdateVisibilityOf(WorldObject const* viewPoint, GameObject* target, UpdateData& data, std::set<WorldObject*>& visibleNow);
20198 template void Player::UpdateVisibilityOf(WorldObject const* viewPoint, DynamicObject* target, UpdateData& data, std::set<WorldObject*>& visibleNow);
20200 void Player::SetPhaseMask(uint32 newPhaseMask, bool update)
20202 // GM-mode have mask PHASEMASK_ANYWHERE always
20203 if (isGameMaster())
20204 newPhaseMask = PHASEMASK_ANYWHERE;
20206 // phase auras normally not expected at BG but anyway better check
20207 if (BattleGround* bg = GetBattleGround())
20208 bg->EventPlayerDroppedFlag(this);
20210 Unit::SetPhaseMask(newPhaseMask, update);
20211 if (IsInWorld())
20212 GetSession()->SendSetPhaseShift(GetPhaseMask());
20215 void Player::InitPrimaryProfessions()
20217 uint32 maxProfs = GetSession()->GetSecurity() < AccountTypes(sWorld.getConfig(CONFIG_UINT32_TRADE_SKILL_GMIGNORE_MAX_PRIMARY_COUNT))
20218 ? sWorld.getConfig(CONFIG_UINT32_MAX_PRIMARY_TRADE_SKILL) : 10;
20219 SetFreePrimaryProfessions(maxProfs);
20222 void Player::SendComboPoints()
20224 Unit* combotarget = ObjectAccessor::GetUnit(*this, m_comboTargetGuid);
20225 if (combotarget)
20227 WorldPacket data(SMSG_UPDATE_COMBO_POINTS, combotarget->GetPackGUID().size() + 1);
20228 data << combotarget->GetPackGUID();
20229 data << uint8(m_comboPoints);
20230 GetSession()->SendPacket(&data);
20232 /*else
20234 // can be NULL, and then points=0. Use unknown; to reset points of some sort?
20235 data << PackedGuid();
20236 data << uint8(0);
20237 GetSession()->SendPacket(&data);
20241 void Player::AddComboPoints(Unit* target, int8 count)
20243 if (!count)
20244 return;
20246 // without combo points lost (duration checked in aura)
20247 RemoveSpellsCausingAura(SPELL_AURA_RETAIN_COMBO_POINTS);
20249 if (target->GetObjectGuid() == m_comboTargetGuid)
20251 m_comboPoints += count;
20253 else
20255 if (m_comboTargetGuid)
20256 if (Unit* target2 = ObjectAccessor::GetUnit(*this, m_comboTargetGuid))
20257 target2->RemoveComboPointHolder(GetGUIDLow());
20259 m_comboTargetGuid = target->GetObjectGuid();
20260 m_comboPoints = count;
20262 target->AddComboPointHolder(GetGUIDLow());
20265 if (m_comboPoints > 5) m_comboPoints = 5;
20266 if (m_comboPoints < 0) m_comboPoints = 0;
20268 SendComboPoints();
20271 void Player::ClearComboPoints()
20273 if (!m_comboTargetGuid)
20274 return;
20276 // without combopoints lost (duration checked in aura)
20277 RemoveSpellsCausingAura(SPELL_AURA_RETAIN_COMBO_POINTS);
20279 m_comboPoints = 0;
20281 SendComboPoints();
20283 if (Unit* target = ObjectAccessor::GetUnit(*this, m_comboTargetGuid))
20284 target->RemoveComboPointHolder(GetGUIDLow());
20286 m_comboTargetGuid.Clear();
20289 void Player::SetGroup(Group* group, int8 subgroup)
20291 if (group == NULL)
20292 m_group.unlink();
20293 else
20295 // never use SetGroup without a subgroup unless you specify NULL for group
20296 MANGOS_ASSERT(subgroup >= 0);
20297 m_group.link(group, this);
20298 m_group.setSubGroup((uint8)subgroup);
20302 void Player::SendInitialPacketsBeforeAddToMap()
20304 GetSocial()->SendSocialList();
20306 // Homebind
20307 WorldPacket data(SMSG_BINDPOINTUPDATE, 5 * 4);
20308 data << m_homebindX << m_homebindY << m_homebindZ;
20309 data << (uint32) m_homebindMapId;
20310 data << (uint32) m_homebindAreaId;
20311 GetSession()->SendPacket(&data);
20313 // SMSG_SET_PROFICIENCY
20314 // SMSG_SET_PCT_SPELL_MODIFIER
20315 // SMSG_SET_FLAT_SPELL_MODIFIER
20317 SendTalentsInfoData(false);
20319 data.Initialize(SMSG_INSTANCE_DIFFICULTY, 4 + 4);
20320 data << uint32(GetMap()->GetDifficulty());
20321 GetSession()->SendPacket(&data);
20323 SendInitialSpells();
20325 data.Initialize(SMSG_SEND_UNLEARN_SPELLS, 4);
20326 data << uint32(0); // count, for(count) uint32;
20327 GetSession()->SendPacket(&data);
20329 SendInitialActionButtons();
20330 m_reputationMgr.SendInitialReputations();
20332 if (!isAlive())
20333 SendCorpseReclaimDelay(true);
20335 SendInitWorldStates(GetZoneId(), GetAreaId());
20337 SendEquipmentSetList();
20339 m_achievementMgr.SendAllAchievementData();
20341 data.Initialize(SMSG_LOGIN_SETTIMESPEED, 4 + 4 + 4);
20342 data << uint32(secsToTimeBitFields(sWorld.GetGameTime()));
20343 data << (float)0.01666667f; // game speed
20344 data << uint32(0); // added in 3.1.2
20345 GetSession()->SendPacket(&data);
20347 // SMSG_TALENTS_INFO x 2 for pet (unspent points and talents in separate packets...)
20348 // SMSG_PET_GUIDS
20349 // SMSG_POWER_UPDATE
20351 // set fly flag if in fly form or taxi flight to prevent visually drop at ground in showup moment
20352 if (IsFreeFlying() || IsTaxiFlying())
20353 m_movementInfo.AddMovementFlag(MOVEFLAG_FLYING);
20355 SendCurrencies();
20357 SetMover(this);
20360 void Player::SendInitialPacketsAfterAddToMap()
20362 // update zone
20363 uint32 newzone, newarea;
20364 GetZoneAndAreaId(newzone, newarea);
20365 UpdateZone(newzone, newarea); // also call SendInitWorldStates();
20367 ResetTimeSync();
20368 SendTimeSync();
20370 CastSpell(this, 836, true); // LOGINEFFECT
20372 // set some aura effects that send packet to player client after add player to map
20373 // SendMessageToSet not send it to player not it map, only for aura that not changed anything at re-apply
20374 // same auras state lost at far teleport, send it one more time in this case also
20375 static const AuraType auratypes[] =
20377 SPELL_AURA_MOD_FEAR, SPELL_AURA_TRANSFORM, SPELL_AURA_WATER_WALK,
20378 SPELL_AURA_FEATHER_FALL, SPELL_AURA_HOVER, SPELL_AURA_SAFE_FALL,
20379 SPELL_AURA_FLY, SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED, SPELL_AURA_NONE
20381 for (AuraType const* itr = &auratypes[0]; itr && itr[0] != SPELL_AURA_NONE; ++itr)
20383 Unit::AuraList const& auraList = GetAurasByType(*itr);
20384 if (!auraList.empty())
20385 auraList.front()->ApplyModifier(true, true);
20388 if (HasAuraType(SPELL_AURA_MOD_STUN))
20389 SetRoot(true);
20391 // manual send package (have code in ApplyModifier(true,true); that don't must be re-applied.
20392 if (HasAuraType(SPELL_AURA_MOD_ROOT))
20394 WorldPacket data2;
20395 BuildForceMoveRootPacket(&data2, true, 2);
20396 SendMessageToSet(&data2, true);
20399 SendAurasForTarget(this);
20400 SendEnchantmentDurations(); // must be after add to map
20401 SendItemDurations(); // must be after add to map
20404 void Player::SendUpdateToOutOfRangeGroupMembers()
20406 if (m_groupUpdateMask == GROUP_UPDATE_FLAG_NONE)
20407 return;
20408 if (Group* group = GetGroup())
20409 group->UpdatePlayerOutOfRange(this);
20411 m_groupUpdateMask = GROUP_UPDATE_FLAG_NONE;
20412 m_auraUpdateMask = 0;
20413 if (Pet* pet = GetPet())
20414 pet->ResetAuraUpdateMask();
20417 void Player::SendTransferAbortedByLockStatus(MapEntry const* mapEntry, AreaLockStatus lockStatus, uint32 miscRequirement)
20419 MANGOS_ASSERT(mapEntry);
20421 DEBUG_LOG("SendTransferAbortedByLockStatus: Called for %s on map %u, LockAreaStatus %u, miscRequirement %u)", GetGuidStr().c_str(), mapEntry->MapID, lockStatus, miscRequirement);
20423 switch (lockStatus)
20425 case AREA_LOCKSTATUS_TOO_LOW_LEVEL:
20426 GetSession()->SendAreaTriggerMessage(GetSession()->GetMangosString(LANG_LEVEL_MINREQUIRED), miscRequirement);
20427 break;
20428 case AREA_LOCKSTATUS_ZONE_IN_COMBAT:
20429 GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_ZONE_IN_COMBAT);
20430 break;
20431 case AREA_LOCKSTATUS_INSTANCE_IS_FULL:
20432 GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_MAX_PLAYERS);
20433 break;
20434 case AREA_LOCKSTATUS_QUEST_NOT_COMPLETED:
20435 if (mapEntry->MapID == 269) // Exception for Black Morass
20437 GetSession()->SendAreaTriggerMessage(GetSession()->GetMangosString(LANG_TELEREQ_QUEST_BLACK_MORASS));
20438 break;
20440 else if (mapEntry->IsContinent()) // do not report anything for quest areatrigge
20442 DEBUG_LOG("SendTransferAbortedByLockStatus: LockAreaStatus %u, do not teleport, no message sent (mapId %u)", lockStatus, mapEntry->MapID);
20443 break;
20445 // No break here!
20446 case AREA_LOCKSTATUS_MISSING_ITEM:
20447 GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_DIFFICULTY, GetDifficulty(mapEntry->IsRaid()));
20448 break;
20449 case AREA_LOCKSTATUS_MISSING_DIFFICULTY:
20451 Difficulty difficulty = GetDifficulty(mapEntry->IsRaid());
20452 GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_DIFFICULTY, difficulty > RAID_DIFFICULTY_10MAN_HEROIC ? RAID_DIFFICULTY_10MAN_HEROIC : difficulty);
20453 break;
20455 case AREA_LOCKSTATUS_INSUFFICIENT_EXPANSION:
20456 GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_INSUF_EXPAN_LVL, miscRequirement);
20457 break;
20458 case AREA_LOCKSTATUS_NOT_ALLOWED:
20459 GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_MAP_NOT_ALLOWED);
20460 break;
20461 case AREA_LOCKSTATUS_RAID_LOCKED:
20462 GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_NEED_GROUP);
20463 break;
20464 case AREA_LOCKSTATUS_UNKNOWN_ERROR:
20465 GetSession()->SendTransferAborted(mapEntry->MapID, TRANSFER_ABORT_ERROR);
20466 break;
20467 case AREA_LOCKSTATUS_OK:
20468 sLog.outError("SendTransferAbortedByLockStatus: LockAreaStatus AREA_LOCKSTATUS_OK received for %s (mapId %u)", GetGuidStr().c_str(), mapEntry->MapID);
20469 MANGOS_ASSERT(false);
20470 break;
20471 default:
20472 sLog.outError("SendTransfertAbortedByLockstatus: unhandled LockAreaStatus %u, when %s attempts to enter in map %u", lockStatus, GetGuidStr().c_str(), mapEntry->MapID);
20473 break;
20477 void Player::SendInstanceResetWarning(uint32 mapid, Difficulty difficulty, uint32 time)
20479 // type of warning, based on the time remaining until reset
20480 uint32 type;
20481 if (time > 3600)
20482 type = RAID_INSTANCE_WELCOME;
20483 else if (time > 900 && time <= 3600)
20484 type = RAID_INSTANCE_WARNING_HOURS;
20485 else if (time > 300 && time <= 900)
20486 type = RAID_INSTANCE_WARNING_MIN;
20487 else
20488 type = RAID_INSTANCE_WARNING_MIN_SOON;
20490 WorldPacket data(SMSG_RAID_INSTANCE_MESSAGE, 4 + 4 + 4 + 4);
20491 data << uint32(type);
20492 data << uint32(mapid);
20493 data << uint32(difficulty); // difficulty
20494 data << uint32(time);
20495 if (type == RAID_INSTANCE_WELCOME)
20497 data << uint8(0); // is your (1)
20498 data << uint8(0); // is extended (1), ignored if prev field is 0
20500 GetSession()->SendPacket(&data);
20503 void Player::ApplyEquipCooldown(Item* pItem)
20505 if (pItem->GetProto()->Flags & ITEM_FLAG_NO_EQUIP_COOLDOWN)
20506 return;
20508 for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
20510 _Spell const& spellData = pItem->GetProto()->Spells[i];
20512 // no spell
20513 if (!spellData.SpellId)
20514 continue;
20516 // wrong triggering type (note: ITEM_SPELLTRIGGER_ON_NO_DELAY_USE not have cooldown)
20517 if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_USE)
20518 continue;
20520 AddSpellCooldown(spellData.SpellId, pItem->GetEntry(), time(NULL) + 30);
20522 WorldPacket data(SMSG_ITEM_COOLDOWN, 12);
20523 data << pItem->GetObjectGuid();
20524 data << uint32(spellData.SpellId);
20525 GetSession()->SendPacket(&data);
20529 void Player::resetSpells()
20531 // not need after this call
20532 if (HasAtLoginFlag(AT_LOGIN_RESET_SPELLS))
20533 RemoveAtLoginFlag(AT_LOGIN_RESET_SPELLS, true);
20535 // make full copy of map (spells removed and marked as deleted at another spell remove
20536 // and we can't use original map for safe iterative with visit each spell at loop end
20537 PlayerSpellMap smap = GetSpellMap();
20539 for (PlayerSpellMap::const_iterator iter = smap.begin(); iter != smap.end(); ++iter)
20540 removeSpell(iter->first, false, false); // only iter->first can be accessed, object by iter->second can be deleted already
20542 learnDefaultSpells();
20543 learnQuestRewardedSpells();
20546 void Player::learnDefaultSpells()
20548 // learn default race/class spells
20549 PlayerInfo const* info = sObjectMgr.GetPlayerInfo(getRace(), getClass());
20550 for (PlayerCreateInfoSpells::const_iterator itr = info->spell.begin(); itr != info->spell.end(); ++itr)
20552 uint32 tspell = *itr;
20553 DEBUG_LOG("PLAYER (Class: %u Race: %u): Adding initial spell, id = %u", uint32(getClass()), uint32(getRace()), tspell);
20554 if (!IsInWorld()) // will send in INITIAL_SPELLS in list anyway at map add
20555 addSpell(tspell, true, true, true, false);
20556 else // but send in normal spell in game learn case
20557 learnSpell(tspell, true);
20561 void Player::learnQuestRewardedSpells(Quest const* quest)
20563 uint32 spell_id = quest->GetRewSpellCast();
20565 // skip quests without rewarded spell
20566 if (!spell_id)
20567 return;
20569 SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id);
20570 if (!spellInfo)
20571 return;
20573 // check learned spells state
20574 bool found = false;
20575 for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
20577 if(SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(i)))
20579 if(spellEffect->Effect == SPELL_EFFECT_LEARN_SPELL && !HasSpell(spellEffect->EffectTriggerSpell))
20581 found = true;
20582 break;
20587 // skip quests with not teaching spell or already known spell
20588 if (!found)
20589 return;
20591 // prevent learn non first rank unknown profession and second specialization for same profession)
20592 SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(EFFECT_INDEX_0);
20593 uint32 learned_0 = spellEffect ? spellEffect->EffectTriggerSpell : 0;
20595 if( sSpellMgr.GetSpellRank(learned_0) > 1 && !HasSpell(learned_0) )
20597 // not have first rank learned (unlearned prof?)
20598 uint32 first_spell = sSpellMgr.GetFirstSpellInChain(learned_0);
20599 if (!HasSpell(first_spell))
20600 return;
20602 SpellEntry const* learnedInfo = sSpellStore.LookupEntry(learned_0);
20603 if (!learnedInfo)
20604 return;
20606 // specialization
20607 SpellEffectEntry const* learnedSpellEffect0 = learnedInfo->GetSpellEffect(EFFECT_INDEX_0);
20608 SpellEffectEntry const* learnedSpellEffect1 = learnedInfo->GetSpellEffect(EFFECT_INDEX_1);
20609 if (learnedSpellEffect0 && learnedSpellEffect0->Effect == SPELL_EFFECT_TRADE_SKILL && learnedSpellEffect1 && learnedSpellEffect1->Effect == 0)
20611 // search other specialization for same prof
20612 for (PlayerSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr)
20614 if (itr->second.state == PLAYERSPELL_REMOVED || itr->first == learned_0)
20615 continue;
20617 SpellEntry const* itrInfo = sSpellStore.LookupEntry(itr->first);
20618 if (!itrInfo)
20619 return;
20621 // compare only specializations
20622 SpellEffectEntry const* itrSpellEffect0 = learnedInfo->GetSpellEffect(EFFECT_INDEX_0);
20623 SpellEffectEntry const* itrSpellEffect1 = learnedInfo->GetSpellEffect(EFFECT_INDEX_1);
20624 if ((itrSpellEffect0 && itrSpellEffect0->Effect != SPELL_EFFECT_TRADE_SKILL) || (itrSpellEffect1 && itrSpellEffect1->Effect != 0))
20625 continue;
20627 // compare same chain spells
20628 if (sSpellMgr.GetFirstSpellInChain(itr->first) != first_spell)
20629 continue;
20631 // now we have 2 specialization, learn possible only if found is lesser specialization rank
20632 if (!sSpellMgr.IsHighRankOfSpell(learned_0, itr->first))
20633 return;
20638 CastSpell(this, spell_id, true);
20641 void Player::learnQuestRewardedSpells()
20643 // learn spells received from quest completing
20644 for (QuestStatusMap::const_iterator itr = mQuestStatus.begin(); itr != mQuestStatus.end(); ++itr)
20646 // skip no rewarded quests
20647 if (!itr->second.m_rewarded)
20648 continue;
20650 Quest const* quest = sObjectMgr.GetQuestTemplate(itr->first);
20651 if (!quest)
20652 continue;
20654 learnQuestRewardedSpells(quest);
20658 void Player::learnSkillRewardedSpells(uint32 skill_id, uint32 skill_value)
20660 uint32 raceMask = getRaceMask();
20661 uint32 classMask = getClassMask();
20662 for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
20664 SkillLineAbilityEntry const* pAbility = sSkillLineAbilityStore.LookupEntry(j);
20665 if (!pAbility || pAbility->skillId != skill_id || pAbility->learnOnGetSkill != ABILITY_LEARNED_ON_GET_PROFESSION_SKILL)
20666 continue;
20667 // Check race if set
20668 if (pAbility->racemask && !(pAbility->racemask & raceMask))
20669 continue;
20670 // Check class if set
20671 if (pAbility->classmask && !(pAbility->classmask & classMask))
20672 continue;
20674 if (sSpellStore.LookupEntry(pAbility->spellId))
20676 // need unlearn spell
20677 if (skill_value < pAbility->req_skill_value)
20678 removeSpell(pAbility->spellId);
20679 // need learn
20680 else if (!IsInWorld())
20681 addSpell(pAbility->spellId, true, true, true, false);
20682 else
20683 learnSpell(pAbility->spellId, true);
20688 void Player::SendAurasForTarget(Unit* target)
20690 WorldPacket data(SMSG_AURA_UPDATE_ALL);
20691 data << target->GetPackGUID();
20693 Unit::VisibleAuraMap const& visibleAuras = target->GetVisibleAuras();
20694 for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras.begin(); itr != visibleAuras.end(); ++itr)
20695 itr->second->BuildUpdatePacket(data);
20697 GetSession()->SendPacket(&data);
20700 void Player::SetDailyQuestStatus(uint32 quest_id)
20702 for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx)
20704 if (!GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx))
20706 SetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx, quest_id);
20707 m_DailyQuestChanged = true;
20708 break;
20713 void Player::SetWeeklyQuestStatus(uint32 quest_id)
20715 m_weeklyquests.insert(quest_id);
20716 m_WeeklyQuestChanged = true;
20719 void Player::SetMonthlyQuestStatus(uint32 quest_id)
20721 m_monthlyquests.insert(quest_id);
20722 m_MonthlyQuestChanged = true;
20725 void Player::ResetDailyQuestStatus()
20727 for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx)
20728 SetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx, 0);
20730 // DB data deleted in caller
20731 m_DailyQuestChanged = false;
20734 void Player::ResetWeeklyQuestStatus()
20736 if (m_weeklyquests.empty())
20737 return;
20739 m_weeklyquests.clear();
20740 // DB data deleted in caller
20741 m_WeeklyQuestChanged = false;
20744 void Player::ResetMonthlyQuestStatus()
20746 if (m_monthlyquests.empty())
20747 return;
20749 m_monthlyquests.clear();
20750 // DB data deleted in caller
20751 m_MonthlyQuestChanged = false;
20754 BattleGround* Player::GetBattleGround() const
20756 if (GetBattleGroundId() == 0)
20757 return NULL;
20759 return sBattleGroundMgr.GetBattleGround(GetBattleGroundId(), m_bgData.bgTypeID);
20762 bool Player::InArena() const
20764 BattleGround* bg = GetBattleGround();
20765 if (!bg || !bg->isArena())
20766 return false;
20768 return true;
20771 bool Player::GetBGAccessByLevel(BattleGroundTypeId bgTypeId) const
20773 // get a template bg instead of running one
20774 BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId);
20775 if (!bg)
20776 return false;
20778 // limit check leel to dbc compatible level range
20779 uint32 level = getLevel();
20780 if (level > DEFAULT_MAX_LEVEL)
20781 level = DEFAULT_MAX_LEVEL;
20783 if (level < bg->GetMinLevel() || level > bg->GetMaxLevel())
20784 return false;
20786 return true;
20789 float Player::GetReputationPriceDiscount(Creature const* pCreature) const
20791 FactionTemplateEntry const* vendor_faction = pCreature->getFactionTemplateEntry();
20792 if (!vendor_faction || !vendor_faction->faction)
20793 return 1.0f;
20795 ReputationRank rank = GetReputationRank(vendor_faction->faction);
20796 if (rank <= REP_NEUTRAL)
20797 return 1.0f;
20799 return 1.0f - 0.05f * (rank - REP_NEUTRAL);
20803 * Check spell availability for training base at SkillLineAbility/SkillRaceClassInfo data.
20804 * Checked allowed race/class and dependent from race/class allowed min level
20806 * @param spell_id checked spell id
20807 * @param pReqlevel if arg provided then function work in view mode (level check not applied but detected minlevel returned to var by arg pointer.
20808 if arg not provided then considered train action mode and level checked
20809 * @return true if spell available for show in trainer list (with skip level check) or training.
20811 bool Player::IsSpellFitByClassAndRace(uint32 spell_id, uint32* pReqlevel /*= NULL*/) const
20813 uint32 racemask = getRaceMask();
20814 uint32 classmask = getClassMask();
20816 SkillLineAbilityMapBounds bounds = sSpellMgr.GetSkillLineAbilityMapBounds(spell_id);
20817 if (bounds.first == bounds.second)
20818 return true;
20820 for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
20822 SkillLineAbilityEntry const* abilityEntry = _spell_idx->second;
20823 // skip wrong race skills
20824 if (abilityEntry->racemask && (abilityEntry->racemask & racemask) == 0)
20825 continue;
20827 // skip wrong class skills
20828 if (abilityEntry->classmask && (abilityEntry->classmask & classmask) == 0)
20829 continue;
20831 SkillRaceClassInfoMapBounds bounds = sSpellMgr.GetSkillRaceClassInfoMapBounds(abilityEntry->skillId);
20832 for (SkillRaceClassInfoMap::const_iterator itr = bounds.first; itr != bounds.second; ++itr)
20834 SkillRaceClassInfoEntry const* skillRCEntry = itr->second;
20835 if ((skillRCEntry->raceMask & racemask) && (skillRCEntry->classMask & classmask))
20837 if (skillRCEntry->flags & ABILITY_SKILL_NONTRAINABLE)
20838 return false;
20840 if (pReqlevel) // show trainers list case
20842 if (skillRCEntry->reqLevel)
20844 *pReqlevel = skillRCEntry->reqLevel;
20845 return true;
20848 else // check availble case at train
20850 if (skillRCEntry->reqLevel && getLevel() < skillRCEntry->reqLevel)
20851 return false;
20856 return true;
20859 return false;
20862 bool Player::HasQuestForGO(int32 GOId) const
20864 for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
20866 uint32 questid = GetQuestSlotQuestId(i);
20867 if (questid == 0)
20868 continue;
20870 QuestStatusMap::const_iterator qs_itr = mQuestStatus.find(questid);
20871 if (qs_itr == mQuestStatus.end())
20872 continue;
20874 QuestStatusData const& qs = qs_itr->second;
20876 if (qs.m_status == QUEST_STATUS_INCOMPLETE)
20878 Quest const* qinfo = sObjectMgr.GetQuestTemplate(questid);
20879 if (!qinfo)
20880 continue;
20882 if (GetGroup() && GetGroup()->isRaidGroup() && !qinfo->IsAllowedInRaid())
20883 continue;
20885 for (int j = 0; j < QUEST_OBJECTIVES_COUNT; ++j)
20887 if (qinfo->ReqCreatureOrGOId[j] >= 0) // skip non GO case
20888 continue;
20890 if ((-1)*GOId == qinfo->ReqCreatureOrGOId[j] && qs.m_creatureOrGOcount[j] < qinfo->ReqCreatureOrGOCount[j])
20891 return true;
20895 return false;
20898 void Player::UpdateForQuestWorldObjects()
20900 if (m_clientGUIDs.empty())
20901 return;
20903 UpdateData udata(GetMapId());
20904 WorldPacket packet;
20905 for (GuidSet::const_iterator itr = m_clientGUIDs.begin(); itr != m_clientGUIDs.end(); ++itr)
20907 if (itr->IsGameObject())
20909 if (GameObject* obj = GetMap()->GetGameObject(*itr))
20910 obj->BuildValuesUpdateBlockForPlayer(&udata, this);
20912 else if (itr->IsCreatureOrVehicle())
20914 Creature* obj = GetMap()->GetAnyTypeCreature(*itr);
20915 if (!obj)
20916 continue;
20918 // check if this unit requires quest specific flags
20919 if (!obj->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK))
20920 continue;
20922 SpellClickInfoMapBounds clickPair = sObjectMgr.GetSpellClickInfoMapBounds(obj->GetEntry());
20923 for (SpellClickInfoMap::const_iterator _itr = clickPair.first; _itr != clickPair.second; ++_itr)
20925 if (_itr->second.questStart || _itr->second.questEnd)
20927 obj->BuildCreateUpdateBlockForPlayer(&udata, this);
20928 break;
20933 udata.BuildPacket(&packet);
20934 GetSession()->SendPacket(&packet);
20937 void Player::SummonIfPossible(bool agree)
20939 if (!agree)
20941 m_summon_expire = 0;
20942 return;
20945 // expire and auto declined
20946 if (m_summon_expire < time(NULL))
20947 return;
20949 // stop taxi flight at summon
20950 if (IsTaxiFlying())
20952 GetMotionMaster()->MovementExpired();
20953 m_taxi.ClearTaxiDestinations();
20956 // drop flag at summon
20957 // this code can be reached only when GM is summoning player who carries flag, because player should be immune to summoning spells when he carries flag
20958 if (BattleGround* bg = GetBattleGround())
20959 bg->EventPlayerDroppedFlag(this);
20961 m_summon_expire = 0;
20963 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ACCEPTED_SUMMONINGS, 1);
20965 TeleportTo(m_summon_mapid, m_summon_x, m_summon_y, m_summon_z, GetOrientation());
20968 void Player::RemoveItemDurations(Item* item)
20970 for (ItemDurationList::iterator itr = m_itemDuration.begin(); itr != m_itemDuration.end(); ++itr)
20972 if (*itr == item)
20974 m_itemDuration.erase(itr);
20975 break;
20980 void Player::AddItemDurations(Item* item)
20982 if (item->GetUInt32Value(ITEM_FIELD_DURATION))
20984 m_itemDuration.push_back(item);
20985 item->SendTimeUpdate(this);
20989 void Player::AutoUnequipOffhandIfNeed()
20991 Item* offItem = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
20992 if (!offItem)
20993 return;
20995 // need unequip offhand for 2h-weapon without TitanGrip (in any from hands)
20996 if ((CanDualWield() || offItem->GetProto()->InventoryType == INVTYPE_SHIELD || offItem->GetProto()->InventoryType == INVTYPE_HOLDABLE) &&
20997 (CanTitanGrip() || (offItem->GetProto()->InventoryType != INVTYPE_2HWEAPON && !IsTwoHandUsed())))
20998 return;
21000 ItemPosCountVec off_dest;
21001 uint8 off_msg = CanStoreItem(NULL_BAG, NULL_SLOT, off_dest, offItem, false);
21002 if (off_msg == EQUIP_ERR_OK)
21004 RemoveItem(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND, true);
21005 StoreItem(off_dest, offItem, true);
21007 else
21009 MoveItemFromInventory(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND, true);
21010 CharacterDatabase.BeginTransaction();
21011 offItem->DeleteFromInventoryDB(); // deletes item from character's inventory
21012 offItem->SaveToDB(); // recursive and not have transaction guard into self, item not in inventory and can be save standalone
21013 CharacterDatabase.CommitTransaction();
21015 std::string subject = GetSession()->GetMangosString(LANG_NOT_EQUIPPED_ITEM);
21016 MailDraft(subject, "There's were problems with equipping this item.").AddItem(offItem).SendMailTo(this, MailSender(this, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_COPIED);
21020 bool Player::HasItemFitToSpellReqirements(SpellEntry const* spellInfo, Item const* ignoreItem)
21022 int32 itemClass = spellInfo->GetEquippedItemClass();
21023 if(itemClass < 0)
21024 return true;
21026 // scan other equipped items for same requirements (mostly 2 daggers/etc)
21027 // for optimize check 2 used cases only
21028 switch(itemClass)
21030 case ITEM_CLASS_WEAPON:
21032 for (int i = EQUIPMENT_SLOT_MAINHAND; i < EQUIPMENT_SLOT_TABARD; ++i)
21033 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
21034 if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
21035 return true;
21036 break;
21038 case ITEM_CLASS_ARMOR:
21040 // tabard not have dependent spells
21041 for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_MAINHAND; ++i)
21042 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
21043 if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
21044 return true;
21046 // shields can be equipped to offhand slot
21047 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND))
21048 if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
21049 return true;
21051 // ranged slot can have some armor subclasses
21052 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
21053 if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
21054 return true;
21056 break;
21058 default:
21059 sLog.outError("HasItemFitToSpellReqirements: Not handled spell requirement for item class %u", itemClass);
21060 break;
21063 return false;
21066 bool Player::CanNoReagentCast(SpellEntry const* spellInfo) const
21068 // don't take reagents for spells with SPELL_ATTR_EX5_NO_REAGENT_WHILE_PREP
21069 if (spellInfo->HasAttribute(SPELL_ATTR_EX5_NO_REAGENT_WHILE_PREP) && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PREPARATION))
21070 return true;
21072 // Check no reagent use mask
21073 uint64 noReagentMask_0_1 = GetUInt64Value(PLAYER_NO_REAGENT_COST_1);
21074 uint32 noReagentMask_2 = GetUInt32Value(PLAYER_NO_REAGENT_COST_1 + 2);
21075 if (spellInfo->IsFitToFamilyMask(noReagentMask_0_1, noReagentMask_2))
21076 return true;
21078 return false;
21081 void Player::RemoveItemDependentAurasAndCasts(Item* pItem)
21083 SpellAuraHolderMap& auras = GetSpellAuraHolderMap();
21084 for (SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end();)
21086 SpellAuraHolder* holder = itr->second;
21088 // skip passive (passive item dependent spells work in another way) and not self applied auras
21089 SpellEntry const* spellInfo = holder->GetSpellProto();
21090 if (holder->IsPassive() || holder->GetCasterGuid() != GetObjectGuid())
21092 ++itr;
21093 continue;
21096 // skip if not item dependent or have alternative item
21097 if (HasItemFitToSpellReqirements(spellInfo, pItem))
21099 ++itr;
21100 continue;
21103 // no alt item, remove aura, restart check
21104 RemoveAurasDueToSpell(holder->GetId());
21105 itr = auras.begin();
21108 // currently casted spells can be dependent from item
21109 for (uint32 i = 0; i < CURRENT_MAX_SPELL; ++i)
21110 if (Spell* spell = GetCurrentSpell(CurrentSpellTypes(i)))
21111 if (spell->getState() != SPELL_STATE_DELAYED && !HasItemFitToSpellReqirements(spell->m_spellInfo, pItem))
21112 InterruptSpell(CurrentSpellTypes(i));
21115 uint32 Player::GetResurrectionSpellId()
21117 // search priceless resurrection possibilities
21118 uint32 prio = 0;
21119 uint32 spell_id = 0;
21120 AuraList const& dummyAuras = GetAurasByType(SPELL_AURA_DUMMY);
21121 for (AuraList::const_iterator itr = dummyAuras.begin(); itr != dummyAuras.end(); ++itr)
21123 // Soulstone Resurrection // prio: 3 (max, non death persistent)
21124 if (prio < 2 && (*itr)->GetSpellProto()->SpellVisual[0] == 99 && (*itr)->GetSpellProto()->SpellIconID == 92)
21126 switch ((*itr)->GetId())
21128 case 20707: spell_id = 3026; break; // rank 1
21129 case 20762: spell_id = 20758; break; // rank 2
21130 case 20763: spell_id = 20759; break; // rank 3
21131 case 20764: spell_id = 20760; break; // rank 4
21132 case 20765: spell_id = 20761; break; // rank 5
21133 case 27239: spell_id = 27240; break; // rank 6
21134 case 47883: spell_id = 47882; break; // rank 7
21135 default:
21136 sLog.outError("Unhandled spell %u: S.Resurrection", (*itr)->GetId());
21137 continue;
21140 prio = 3;
21142 // Twisting Nether // prio: 2 (max)
21143 else if ((*itr)->GetId() == 23701 && roll_chance_i(10))
21145 prio = 2;
21146 spell_id = 23700;
21150 // Reincarnation (passive spell) // prio: 1
21151 // Glyph of Renewed Life remove reagent requiremnnt
21152 if (prio < 1 && HasSpell(20608) && !HasSpellCooldown(21169) && (HasItemCount(17030, 1) || HasAura(58059, EFFECT_INDEX_0)))
21153 spell_id = 21169;
21155 return spell_id;
21158 // Used in triggers for check "Only to targets that grant experience or honor" req
21159 bool Player::isHonorOrXPTarget(Unit* pVictim) const
21161 uint32 v_level = pVictim->getLevel();
21162 uint32 k_grey = MaNGOS::XP::GetGrayLevel(getLevel());
21164 // Victim level less gray level
21165 if (v_level <= k_grey)
21166 return false;
21168 if (pVictim->GetTypeId() == TYPEID_UNIT)
21170 if (((Creature*)pVictim)->IsTotem() ||
21171 ((Creature*)pVictim)->IsPet() ||
21172 ((Creature*)pVictim)->GetCreatureInfo()->flags_extra & CREATURE_FLAG_EXTRA_NO_XP_AT_KILL)
21173 return false;
21175 return true;
21178 void Player::RewardSinglePlayerAtKill(Unit* pVictim)
21180 bool PvP = pVictim->isCharmedOwnedByPlayerOrPlayer();
21181 uint32 xp = PvP ? 0 : MaNGOS::XP::Gain(this, pVictim);
21183 // honor can be in PvP and !PvP (racial leader) cases
21184 RewardHonor(pVictim, 1);
21186 // xp and reputation only in !PvP case
21187 if (!PvP)
21189 RewardReputation(pVictim, 1);
21190 GiveXP(xp, pVictim);
21192 if (Pet* pet = GetPet())
21193 pet->GivePetXP(xp);
21195 // normal creature (not pet/etc) can be only in !PvP case
21196 if (pVictim->GetTypeId() == TYPEID_UNIT)
21197 if (CreatureInfo const* normalInfo = ObjectMgr::GetCreatureTemplate(pVictim->GetEntry()))
21198 KilledMonster(normalInfo, pVictim->GetObjectGuid());
21202 void Player::RewardPlayerAndGroupAtEvent(uint32 creature_id, WorldObject* pRewardSource)
21204 MANGOS_ASSERT((!GetGroup() || pRewardSource) && "Player::RewardPlayerAndGroupAtEvent called for Group-Case but no source for range searching provided");
21206 ObjectGuid creature_guid = pRewardSource && pRewardSource->GetTypeId() == TYPEID_UNIT ? pRewardSource->GetObjectGuid() : ObjectGuid();
21208 // prepare data for near group iteration
21209 if (Group* pGroup = GetGroup())
21211 for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
21213 Player* pGroupGuy = itr->getSource();
21214 if (!pGroupGuy)
21215 continue;
21217 if (!pGroupGuy->IsAtGroupRewardDistance(pRewardSource))
21218 continue; // member (alive or dead) or his corpse at req. distance
21220 // quest objectives updated only for alive group member or dead but with not released body
21221 if (pGroupGuy->isAlive() || !pGroupGuy->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
21222 pGroupGuy->KilledMonsterCredit(creature_id, creature_guid);
21225 else // if (!pGroup)
21226 KilledMonsterCredit(creature_id, creature_guid);
21229 void Player::RewardPlayerAndGroupAtCast(WorldObject* pRewardSource, uint32 spellid)
21231 // prepare data for near group iteration
21232 if (Group* pGroup = GetGroup())
21234 for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
21236 Player* pGroupGuy = itr->getSource();
21237 if (!pGroupGuy)
21238 continue;
21240 if (!pGroupGuy->IsAtGroupRewardDistance(pRewardSource))
21241 continue; // member (alive or dead) or his corpse at req. distance
21243 // quest objectives updated only for alive group member or dead but with not released body
21244 if (pGroupGuy->isAlive() || !pGroupGuy->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
21245 pGroupGuy->CastedCreatureOrGO(pRewardSource->GetEntry(), pRewardSource->GetObjectGuid(), spellid, pGroupGuy == this);
21248 else // if (!pGroup)
21249 CastedCreatureOrGO(pRewardSource->GetEntry(), pRewardSource->GetObjectGuid(), spellid);
21252 bool Player::IsAtGroupRewardDistance(WorldObject const* pRewardSource) const
21254 if (pRewardSource->IsWithinDistInMap(this, sWorld.getConfig(CONFIG_FLOAT_GROUP_XP_DISTANCE)))
21255 return true;
21257 if (isAlive())
21258 return false;
21260 Corpse* corpse = GetCorpse();
21261 if (!corpse)
21262 return false;
21264 return pRewardSource->IsWithinDistInMap(corpse, sWorld.getConfig(CONFIG_FLOAT_GROUP_XP_DISTANCE));
21267 void Player::ResurectUsingRequestData()
21269 /// Teleport before resurrecting by player, otherwise the player might get attacked from creatures near his corpse
21270 if (m_resurrectGuid.IsPlayer())
21271 TeleportTo(m_resurrectMap, m_resurrectX, m_resurrectY, m_resurrectZ, GetOrientation());
21273 // we cannot resurrect player when we triggered far teleport
21274 // player will be resurrected upon teleportation
21275 if (IsBeingTeleportedFar())
21277 ScheduleDelayedOperation(DELAYED_RESURRECT_PLAYER);
21278 return;
21281 ResurrectPlayer(0.0f, false);
21283 if (GetMaxHealth() > m_resurrectHealth)
21284 SetHealth(m_resurrectHealth);
21285 else
21286 SetHealth(GetMaxHealth());
21288 if (GetMaxPower(POWER_MANA) > m_resurrectMana)
21289 SetPower(POWER_MANA, m_resurrectMana);
21290 else
21291 SetPower(POWER_MANA, GetMaxPower(POWER_MANA));
21293 SetPower(POWER_RAGE, 0);
21295 SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY));
21297 SpawnCorpseBones();
21300 void Player::SetClientControl(Unit* target, uint8 allowMove)
21302 WorldPacket data(SMSG_CLIENT_CONTROL_UPDATE, target->GetPackGUID().size() + 1);
21303 data << target->GetPackGUID();
21304 data << uint8(allowMove);
21305 GetSession()->SendPacket(&data);
21308 void Player::UpdateZoneDependentAuras()
21310 // Some spells applied at enter into zone (with subzones), aura removed in UpdateAreaDependentAuras that called always at zone->area update
21311 SpellAreaForAreaMapBounds saBounds = sSpellMgr.GetSpellAreaForAreaMapBounds(m_zoneUpdateId);
21312 for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
21313 if (itr->second->autocast && itr->second->IsFitToRequirements(this, m_zoneUpdateId, 0))
21314 if (!HasAura(itr->second->spellId, EFFECT_INDEX_0))
21315 CastSpell(this, itr->second->spellId, true);
21318 void Player::UpdateAreaDependentAuras()
21320 // remove auras from spells with area limitations
21321 for (SpellAuraHolderMap::iterator iter = m_spellAuraHolders.begin(); iter != m_spellAuraHolders.end();)
21323 // use m_zoneUpdateId for speed: UpdateArea called from UpdateZone or instead UpdateZone in both cases m_zoneUpdateId up-to-date
21324 if (sSpellMgr.GetSpellAllowedInLocationError(iter->second->GetSpellProto(), GetMapId(), m_zoneUpdateId, m_areaUpdateId, this) != SPELL_CAST_OK)
21326 RemoveSpellAuraHolder(iter->second);
21327 iter = m_spellAuraHolders.begin();
21329 else
21330 ++iter;
21333 // some auras applied at subzone enter
21334 SpellAreaForAreaMapBounds saBounds = sSpellMgr.GetSpellAreaForAreaMapBounds(m_areaUpdateId);
21335 for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
21336 if (itr->second->autocast && itr->second->IsFitToRequirements(this, m_zoneUpdateId, m_areaUpdateId))
21337 if (!HasAura(itr->second->spellId, EFFECT_INDEX_0))
21338 CastSpell(this, itr->second->spellId, true);
21341 struct UpdateZoneDependentPetsHelper
21343 explicit UpdateZoneDependentPetsHelper(Player* _owner, uint32 zone, uint32 area) : owner(_owner), zone_id(zone), area_id(area) {}
21344 void operator()(Unit* unit) const
21346 if (unit->GetTypeId() == TYPEID_UNIT && ((Creature*)unit)->IsPet() && !((Pet*)unit)->IsPermanentPetFor(owner))
21347 if (uint32 spell_id = unit->GetUInt32Value(UNIT_CREATED_BY_SPELL))
21348 if (SpellEntry const* spellEntry = sSpellStore.LookupEntry(spell_id))
21349 if (sSpellMgr.GetSpellAllowedInLocationError(spellEntry, owner->GetMapId(), zone_id, area_id, owner) != SPELL_CAST_OK)
21350 ((Pet*)unit)->Unsummon(PET_SAVE_AS_DELETED, owner);
21352 Player* owner;
21353 uint32 zone_id;
21354 uint32 area_id;
21357 void Player::UpdateZoneDependentPets()
21359 // check pet (permanent pets ignored), minipet, guardians (including protector)
21360 CallForAllControlledUnits(UpdateZoneDependentPetsHelper(this, m_zoneUpdateId, m_areaUpdateId), CONTROLLED_PET | CONTROLLED_GUARDIANS | CONTROLLED_MINIPET);
21363 uint32 Player::GetCorpseReclaimDelay(bool pvp) const
21365 if ((pvp && !sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVP)) ||
21366 (!pvp && !sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVE)))
21368 return corpseReclaimDelay[0];
21371 time_t now = time(NULL);
21372 // 0..2 full period
21373 uint32 count = (now < m_deathExpireTime) ? uint32((m_deathExpireTime - now) / DEATH_EXPIRE_STEP) : 0;
21374 return corpseReclaimDelay[count];
21377 void Player::UpdateCorpseReclaimDelay()
21379 bool pvp = m_ExtraFlags & PLAYER_EXTRA_PVP_DEATH;
21381 if ((pvp && !sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVP)) ||
21382 (!pvp && !sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVE)))
21383 return;
21385 time_t now = time(NULL);
21386 if (now < m_deathExpireTime)
21388 // full and partly periods 1..3
21389 uint32 count = uint32((m_deathExpireTime - now) / DEATH_EXPIRE_STEP + 1);
21390 if (count < MAX_DEATH_COUNT)
21391 m_deathExpireTime = now + (count + 1) * DEATH_EXPIRE_STEP;
21392 else
21393 m_deathExpireTime = now + MAX_DEATH_COUNT * DEATH_EXPIRE_STEP;
21395 else
21396 m_deathExpireTime = now + DEATH_EXPIRE_STEP;
21399 void Player::SendCorpseReclaimDelay(bool load)
21401 Corpse* corpse = GetCorpse();
21402 if (!corpse)
21403 return;
21405 uint32 delay;
21406 if (load)
21408 if (corpse->GetGhostTime() > m_deathExpireTime)
21409 return;
21411 bool pvp = corpse->GetType() == CORPSE_RESURRECTABLE_PVP;
21413 uint32 count;
21414 if ((pvp && sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVP)) ||
21415 (!pvp && sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVE)))
21417 count = uint32(m_deathExpireTime - corpse->GetGhostTime()) / DEATH_EXPIRE_STEP;
21418 if (count >= MAX_DEATH_COUNT)
21419 count = MAX_DEATH_COUNT - 1;
21421 else
21422 count = 0;
21424 time_t expected_time = corpse->GetGhostTime() + corpseReclaimDelay[count];
21426 time_t now = time(NULL);
21427 if (now >= expected_time)
21428 return;
21430 delay = uint32(expected_time - now);
21432 else
21433 delay = GetCorpseReclaimDelay(corpse->GetType() == CORPSE_RESURRECTABLE_PVP);
21435 //! corpse reclaim delay 30 * 1000ms or longer at often deaths
21436 WorldPacket data(SMSG_CORPSE_RECLAIM_DELAY, 4);
21437 data << uint32(delay * IN_MILLISECONDS);
21438 GetSession()->SendPacket(&data);
21441 Player* Player::GetNextRandomRaidMember(float radius)
21443 Group* pGroup = GetGroup();
21444 if (!pGroup)
21445 return NULL;
21447 std::vector<Player*> nearMembers;
21448 nearMembers.reserve(pGroup->GetMembersCount());
21450 for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
21452 Player* Target = itr->getSource();
21454 // IsHostileTo check duel and controlled by enemy
21455 if (Target && Target != this && IsWithinDistInMap(Target, radius) &&
21456 !Target->HasInvisibilityAura() && !IsHostileTo(Target))
21457 nearMembers.push_back(Target);
21460 if (nearMembers.empty())
21461 return NULL;
21463 uint32 randTarget = urand(0, nearMembers.size() - 1);
21464 return nearMembers[randTarget];
21467 PartyResult Player::CanUninviteFromGroup() const
21469 const Group* grp = GetGroup();
21470 if (!grp)
21471 return ERR_NOT_IN_GROUP;
21473 if (!grp->IsLeader(GetObjectGuid()) && !grp->IsAssistant(GetObjectGuid()))
21474 return ERR_NOT_LEADER;
21476 if (InBattleGround())
21477 return ERR_INVITE_RESTRICTED;
21479 return ERR_PARTY_RESULT_OK;
21482 void Player::SetBattleGroundRaid(Group* group, int8 subgroup)
21484 // we must move references from m_group to m_originalGroup
21485 SetOriginalGroup(GetGroup(), GetSubGroup());
21487 m_group.unlink();
21488 m_group.link(group, this);
21489 m_group.setSubGroup((uint8)subgroup);
21492 void Player::RemoveFromBattleGroundRaid()
21494 // remove existing reference
21495 m_group.unlink();
21496 if (Group* group = GetOriginalGroup())
21498 m_group.link(group, this);
21499 m_group.setSubGroup(GetOriginalSubGroup());
21501 SetOriginalGroup(NULL);
21504 void Player::SetOriginalGroup(Group* group, int8 subgroup)
21506 if (group == NULL)
21507 m_originalGroup.unlink();
21508 else
21510 // never use SetOriginalGroup without a subgroup unless you specify NULL for group
21511 MANGOS_ASSERT(subgroup >= 0);
21512 m_originalGroup.link(group, this);
21513 m_originalGroup.setSubGroup((uint8)subgroup);
21517 void Player::UpdateUnderwaterState(Map* m, float x, float y, float z)
21519 GridMapLiquidData liquid_status;
21520 GridMapLiquidStatus res = m->GetTerrain()->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &liquid_status);
21521 if (!res)
21523 m_MirrorTimerFlags &= ~(UNDERWATER_INWATER | UNDERWATER_INLAVA | UNDERWATER_INSLIME | UNDERWATER_INDARKWATER);
21524 // Small hack for enable breath in WMO
21525 /* if (IsInWater())
21526 m_MirrorTimerFlags|=UNDERWATER_INWATER; */
21527 return;
21530 // All liquids type - check under water position
21531 if (liquid_status.type & (MAP_LIQUID_TYPE_WATER | MAP_LIQUID_TYPE_OCEAN | MAP_LIQUID_TYPE_MAGMA | MAP_LIQUID_TYPE_SLIME))
21533 if (res & LIQUID_MAP_UNDER_WATER)
21534 m_MirrorTimerFlags |= UNDERWATER_INWATER;
21535 else
21536 m_MirrorTimerFlags &= ~UNDERWATER_INWATER;
21539 // Allow travel in dark water on taxi or transport
21540 if ((liquid_status.type & MAP_LIQUID_TYPE_DARK_WATER) && !IsTaxiFlying() && !GetTransport())
21541 m_MirrorTimerFlags |= UNDERWATER_INDARKWATER;
21542 else
21543 m_MirrorTimerFlags &= ~UNDERWATER_INDARKWATER;
21545 // in lava check, anywhere in lava level
21546 if (liquid_status.type & MAP_LIQUID_TYPE_MAGMA)
21548 if (res & (LIQUID_MAP_UNDER_WATER | LIQUID_MAP_IN_WATER | LIQUID_MAP_WATER_WALK))
21549 m_MirrorTimerFlags |= UNDERWATER_INLAVA;
21550 else
21551 m_MirrorTimerFlags &= ~UNDERWATER_INLAVA;
21553 // in slime check, anywhere in slime level
21554 if (liquid_status.type & MAP_LIQUID_TYPE_SLIME)
21556 if (res & (LIQUID_MAP_UNDER_WATER | LIQUID_MAP_IN_WATER | LIQUID_MAP_WATER_WALK))
21557 m_MirrorTimerFlags |= UNDERWATER_INSLIME;
21558 else
21559 m_MirrorTimerFlags &= ~UNDERWATER_INSLIME;
21563 void Player::SetCanParry(bool value)
21565 if (m_canParry == value)
21566 return;
21568 m_canParry = value;
21569 UpdateParryPercentage();
21572 void Player::SetCanBlock(bool value)
21574 if (m_canBlock == value)
21575 return;
21577 m_canBlock = value;
21578 UpdateBlockPercentage();
21579 UpdateShieldBlockDamageValue();
21582 bool ItemPosCount::isContainedIn(ItemPosCountVec const& vec) const
21584 for (ItemPosCountVec::const_iterator itr = vec.begin(); itr != vec.end(); ++itr)
21585 if (itr->pos == pos)
21586 return true;
21588 return false;
21591 bool Player::CanUseBattleGroundObject()
21593 // TODO : some spells gives player ForceReaction to one faction (ReputationMgr::ApplyForceReaction)
21594 // maybe gameobject code should handle that ForceReaction usage
21595 // BUG: sometimes when player clicks on flag in AB - client won't send gameobject_use, only gameobject_report_use packet
21596 return (isAlive() && // living
21597 // the following two are incorrect, because invisible/stealthed players should get visible when they click on flag
21598 !HasStealthAura() && // not stealthed
21599 !HasInvisibilityAura() && // visible
21600 !isTotalImmune() && // vulnerable (not immune)
21601 !HasAura(SPELL_RECENTLY_DROPPED_FLAG, EFFECT_INDEX_0));
21604 uint32 Player::GetBarberShopCost(uint8 newhairstyle, uint8 newhaircolor, uint8 newfacialhair, uint32 newskintone)
21606 uint32 level = getLevel();
21608 if (level > GT_MAX_LEVEL)
21609 level = GT_MAX_LEVEL; // max level in this dbc
21611 uint8 hairstyle = GetByteValue(PLAYER_BYTES, 2);
21612 uint8 haircolor = GetByteValue(PLAYER_BYTES, 3);
21613 uint8 facialhair = GetByteValue(PLAYER_BYTES_2, 0);
21614 uint8 skintone = GetByteValue(PLAYER_BYTES, 0);
21616 if (hairstyle == newhairstyle && haircolor == newhaircolor && facialhair == newfacialhair &&
21617 (skintone == newskintone || newskintone == -1))
21618 return 0;
21620 GtBarberShopCostBaseEntry const* bsc = sGtBarberShopCostBaseStore.LookupEntry(level - 1);
21622 if (!bsc) // shouldn't happen
21623 return 0xFFFFFFFF;
21625 float cost = 0;
21627 if (hairstyle != newhairstyle)
21628 cost += bsc->cost; // full price
21630 if (haircolor != newhaircolor && hairstyle == newhairstyle)
21631 cost += bsc->cost * 0.5f; // +1/2 of price
21633 if (facialhair != newfacialhair)
21634 cost += bsc->cost * 0.75f; // +3/4 of price
21636 if (skintone != newskintone && newskintone != -1)
21637 cost += bsc->cost * 0.5f; // +1/2 of price
21639 return uint32(cost);
21642 void Player::InitGlyphsForLevel()
21644 uint32 slot = 0;
21645 for (uint32 i = 0; i < sGlyphSlotStore.GetNumRows() && slot < MAX_GLYPH_SLOT_INDEX; ++i)
21646 if (GlyphSlotEntry const* gs = sGlyphSlotStore.LookupEntry(i))
21647 SetGlyphSlot(slot++, gs->Id);
21649 uint32 level = getLevel();
21650 uint32 value = 0;
21652 // 0x3F = 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 for 80 level
21653 if (level >= 25)
21654 value |= 0x01 | 0x02 | 0x40;
21655 if (level >= 50)
21656 value |= 0x04 | 0x08 | 0x80;
21657 if (level >= 75)
21658 value |= 0x10 | 0x20 | 0x100;
21660 SetUInt32Value(PLAYER_GLYPHS_ENABLED, value);
21663 void Player::ApplyGlyph(uint8 slot, bool apply)
21665 if (uint32 glyph = GetGlyph(slot))
21667 if (GlyphPropertiesEntry const* gp = sGlyphPropertiesStore.LookupEntry(glyph))
21669 if (apply)
21671 CastSpell(this, gp->SpellId, true);
21672 SetUInt32Value(PLAYER_FIELD_GLYPHS_1 + slot, glyph);
21674 else
21676 RemoveAurasDueToSpell(gp->SpellId);
21677 SetUInt32Value(PLAYER_FIELD_GLYPHS_1 + slot, 0);
21683 void Player::ApplyGlyphs(bool apply)
21685 for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
21686 ApplyGlyph(i, apply);
21689 bool Player::isTotalImmune()
21691 AuraList const& immune = GetAurasByType(SPELL_AURA_SCHOOL_IMMUNITY);
21693 uint32 immuneMask = 0;
21694 for (AuraList::const_iterator itr = immune.begin(); itr != immune.end(); ++itr)
21696 immuneMask |= (*itr)->GetModifier()->m_miscvalue;
21697 if (immuneMask & SPELL_SCHOOL_MASK_ALL) // total immunity
21698 return true;
21700 return false;
21703 bool Player::HasTitle(uint32 bitIndex) const
21705 if (bitIndex > MAX_TITLE_INDEX)
21706 return false;
21708 uint32 fieldIndexOffset = bitIndex / 32;
21709 uint32 flag = 1 << (bitIndex % 32);
21710 return HasFlag(PLAYER__FIELD_KNOWN_TITLES + fieldIndexOffset, flag);
21713 void Player::SetTitle(CharTitlesEntry const* title, bool lost)
21715 uint32 fieldIndexOffset = title->bit_index / 32;
21716 uint32 flag = 1 << (title->bit_index % 32);
21718 if (lost)
21720 if (!HasFlag(PLAYER__FIELD_KNOWN_TITLES + fieldIndexOffset, flag))
21721 return;
21723 RemoveFlag(PLAYER__FIELD_KNOWN_TITLES + fieldIndexOffset, flag);
21725 else
21727 if (HasFlag(PLAYER__FIELD_KNOWN_TITLES + fieldIndexOffset, flag))
21728 return;
21730 SetFlag(PLAYER__FIELD_KNOWN_TITLES + fieldIndexOffset, flag);
21733 WorldPacket data(SMSG_TITLE_EARNED, 4 + 4);
21734 data << uint32(title->bit_index);
21735 data << uint32(lost ? 0 : 1); // 1 - earned, 0 - lost
21736 GetSession()->SendPacket(&data);
21739 void Player::ConvertRune(uint8 index, RuneType newType)
21741 SetCurrentRune(index, newType);
21743 WorldPacket data(SMSG_CONVERT_RUNE, 2);
21744 data << uint8(index);
21745 data << uint8(newType);
21746 GetSession()->SendPacket(&data);
21749 bool Player::ActivateRunes(RuneType type, uint32 count)
21751 bool modify = false;
21752 for (uint32 j = 0; count > 0 && j < MAX_RUNES; ++j)
21754 if (GetRuneCooldown(j) && GetCurrentRune(j) == type)
21756 SetRuneCooldown(j, 0);
21757 --count;
21758 modify = true;
21762 return modify;
21765 void Player::ResyncRunes()
21767 WorldPacket data(SMSG_RESYNC_RUNES, 4 + MAX_RUNES * 2);
21768 data << uint32(MAX_RUNES);
21769 for (uint32 i = 0; i < MAX_RUNES; ++i)
21771 data << uint8(GetCurrentRune(i)); // rune type
21772 data << uint8(255 - ((GetRuneCooldown(i) / REGEN_TIME_FULL) * 51)); // passed cooldown time (0-255)
21774 GetSession()->SendPacket(&data);
21777 void Player::AddRunePower(uint8 index)
21779 WorldPacket data(SMSG_ADD_RUNE_POWER, 4);
21780 data << uint32(1 << index); // mask (0x00-0x3F probably)
21781 GetSession()->SendPacket(&data);
21784 static RuneType runeSlotTypes[MAX_RUNES] =
21786 /*0*/ RUNE_BLOOD,
21787 /*1*/ RUNE_BLOOD,
21788 /*2*/ RUNE_UNHOLY,
21789 /*3*/ RUNE_UNHOLY,
21790 /*4*/ RUNE_FROST,
21791 /*5*/ RUNE_FROST
21794 void Player::InitRunes()
21796 if (getClass() != CLASS_DEATH_KNIGHT)
21797 return;
21799 m_runes = new Runes;
21801 m_runes->runeState = 0;
21803 for (uint32 i = 0; i < MAX_RUNES; ++i)
21805 SetBaseRune(i, runeSlotTypes[i]); // init base types
21806 SetCurrentRune(i, runeSlotTypes[i]); // init current types
21807 SetRuneCooldown(i, 0); // reset cooldowns
21808 m_runes->SetRuneState(i);
21811 for (uint32 i = 0; i < NUM_RUNE_TYPES; ++i)
21812 SetFloatValue(PLAYER_RUNE_REGEN_1 + i, 0.1f);
21816 bool Player::IsBaseRuneSlotsOnCooldown(RuneType runeType) const
21818 for (uint32 i = 0; i < MAX_RUNES; ++i)
21819 if (GetBaseRune(i) == runeType && GetRuneCooldown(i) == 0)
21820 return false;
21822 return true;
21825 void Player::AutoStoreLoot(uint32 loot_id, LootStore const& store, bool broadcast, uint8 bag, uint8 slot)
21827 Loot loot;
21828 loot.FillLoot(loot_id, store, this, true);
21830 AutoStoreLoot(loot, broadcast, bag, slot);
21833 void Player::AutoStoreLoot(Loot& loot, bool broadcast, uint8 bag, uint8 slot)
21835 uint32 max_slot = loot.GetMaxSlotInLootFor(this);
21836 for (uint32 i = 0; i < max_slot; ++i)
21838 LootItem* lootItem = loot.LootItemInSlot(i, this);
21840 ItemPosCountVec dest;
21841 InventoryResult msg = CanStoreNewItem(bag, slot, dest, lootItem->itemid, lootItem->count);
21842 if (msg != EQUIP_ERR_OK && slot != NULL_SLOT)
21843 msg = CanStoreNewItem(bag, NULL_SLOT, dest, lootItem->itemid, lootItem->count);
21844 if (msg != EQUIP_ERR_OK && bag != NULL_BAG)
21845 msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, lootItem->itemid, lootItem->count);
21846 if (msg != EQUIP_ERR_OK)
21848 SendEquipError(msg, NULL, NULL, lootItem->itemid);
21849 continue;
21852 Item* pItem = StoreNewItem(dest, lootItem->itemid, true, lootItem->randomPropertyId);
21853 SendNewItem(pItem, lootItem->count, false, false, broadcast);
21857 Item* Player::ConvertItem(Item* item, uint32 newItemId)
21859 uint16 pos = item->GetPos();
21861 Item* pNewItem = Item::CreateItem(newItemId, 1, this);
21862 if (!pNewItem)
21863 return NULL;
21865 // copy enchantments
21866 for (uint8 j = PERM_ENCHANTMENT_SLOT; j <= TEMP_ENCHANTMENT_SLOT; ++j)
21868 if (item->GetEnchantmentId(EnchantmentSlot(j)))
21869 pNewItem->SetEnchantment(EnchantmentSlot(j), item->GetEnchantmentId(EnchantmentSlot(j)),
21870 item->GetEnchantmentDuration(EnchantmentSlot(j)), item->GetEnchantmentCharges(EnchantmentSlot(j)));
21873 // copy durability
21874 if (item->GetUInt32Value(ITEM_FIELD_DURABILITY) < item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY))
21876 double loosePercent = 1 - item->GetUInt32Value(ITEM_FIELD_DURABILITY) / double(item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY));
21877 DurabilityLoss(pNewItem, loosePercent);
21880 if (IsInventoryPos(pos))
21882 ItemPosCountVec dest;
21883 InventoryResult msg = CanStoreItem(item->GetBagSlot(), item->GetSlot(), dest, pNewItem, true);
21884 // ignore cast/combat time restriction
21885 if (msg == EQUIP_ERR_OK)
21887 DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
21888 return StoreItem(dest, pNewItem, true);
21891 else if (IsBankPos(pos))
21893 ItemPosCountVec dest;
21894 InventoryResult msg = CanBankItem(item->GetBagSlot(), item->GetSlot(), dest, pNewItem, true);
21895 // ignore cast/combat time restriction
21896 if (msg == EQUIP_ERR_OK)
21898 DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
21899 return BankItem(dest, pNewItem, true);
21902 else if (IsEquipmentPos(pos))
21904 uint16 dest;
21905 InventoryResult msg = CanEquipItem(item->GetSlot(), dest, pNewItem, true, false);
21906 // ignore cast/combat time restriction
21907 if (msg == EQUIP_ERR_OK)
21909 DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
21910 pNewItem = EquipItem(dest, pNewItem, true);
21911 AutoUnequipOffhandIfNeed();
21912 return pNewItem;
21916 // fail
21917 delete pNewItem;
21918 return NULL;
21921 uint32 Player::CalculateTalentsPoints() const
21923 // this dbc file has entries only up to level 100
21924 NumTalentsAtLevelEntry const* count = sNumTalentsAtLevelStore.LookupEntry(std::min<uint32>(getLevel(), 100));
21925 if (!count)
21926 return 0;
21928 float baseForLevel = count->Talents;
21930 if (getClass() != CLASS_DEATH_KNIGHT)
21931 return uint32(baseForLevel * sWorld.getConfig(CONFIG_FLOAT_RATE_TALENT));
21933 // Death Knight starting level
21934 // hardcoded here - number of quest awarded talents is equal to number of talents any other class would have at level 55
21935 if (getLevel() < 55)
21936 return 0;
21938 NumTalentsAtLevelEntry const* dkBase = sNumTalentsAtLevelStore.LookupEntry(55);
21939 if (!dkBase)
21940 return 0;
21942 float talentPointsForLevel = count->Talents - dkBase->Talents;
21943 talentPointsForLevel += float(m_questRewardTalentCount);
21945 if (talentPointsForLevel > baseForLevel)
21946 talentPointsForLevel = baseForLevel;
21948 return uint32(talentPointsForLevel * sWorld.getConfig(CONFIG_FLOAT_RATE_TALENT));
21951 bool Player::CanStartFlyInArea(uint32 mapid, uint32 zone, uint32 area) const
21953 if (isGameMaster())
21954 return true;
21955 // continent checked in SpellMgr::GetSpellAllowedInLocationError at cast and area update
21956 uint32 v_map = GetVirtualMapForMapAndZone(mapid, zone);
21958 if (v_map == 571 && !HasSpell(54197)) // Cold Weather Flying
21959 return false;
21961 // don't allow flying in Dalaran restricted areas
21962 // (no other zones currently has areas with AREA_FLAG_CANNOT_FLY)
21963 if (AreaTableEntry const* atEntry = GetAreaEntryByAreaID(area))
21964 return (!(atEntry->flags & AREA_FLAG_CANNOT_FLY));
21966 // TODO: disallow mounting in wintergrasp too when battle is in progress
21967 // forced dismount part in Player::UpdateArea()
21968 return true;
21971 struct DoPlayerLearnSpell
21973 DoPlayerLearnSpell(Player& _player) : player(_player) {}
21974 void operator()(uint32 spell_id) { player.learnSpell(spell_id, false); }
21975 Player& player;
21978 void Player::learnSpellHighRank(uint32 spellid)
21980 learnSpell(spellid, false);
21982 DoPlayerLearnSpell worker(*this);
21983 sSpellMgr.doForHighRanks(spellid, worker);
21986 void Player::_LoadSkills(QueryResult* result)
21988 // 0 1 2
21989 // SetPQuery(PLAYER_LOGIN_QUERY_LOADSKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = '%u'", GUID_LOPART(m_guid));
21991 uint32 count = 0;
21992 uint8 professionCount = 0;
21993 if (result)
21997 Field* fields = result->Fetch();
21999 uint16 skill = fields[0].GetUInt16();
22000 uint16 value = fields[1].GetUInt16();
22001 uint16 max = fields[2].GetUInt16();
22003 SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(skill);
22004 if (!pSkill)
22006 sLog.outError("Character %u has skill %u that does not exist.", GetGUIDLow(), skill);
22007 continue;
22010 // set fixed skill ranges
22011 switch (GetSkillRangeType(pSkill, false))
22013 case SKILL_RANGE_LANGUAGE: // 300..300
22014 value = max = 300;
22015 break;
22016 case SKILL_RANGE_MONO: // 1..1, grey monolite bar
22017 value = max = 1;
22018 break;
22019 default:
22020 break;
22023 if (value == 0)
22025 sLog.outError("Character %u has skill %u with value 0. Will be deleted.", GetGUIDLow(), skill);
22026 CharacterDatabase.PExecute("DELETE FROM character_skills WHERE guid = '%u' AND skill = '%u' ", GetGUIDLow(), skill);
22027 continue;
22030 uint16 field = count / 2;
22031 uint8 offset = count & 1;
22033 SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, skill);
22034 uint16 step = 0;
22036 if (pSkill->categoryId == SKILL_CATEGORY_SECONDARY)
22037 step = max / 75;
22039 if (pSkill->categoryId == SKILL_CATEGORY_PROFESSION)
22041 step = max / 75;
22043 if (professionCount < 2)
22044 SetUInt32Value(PLAYER_PROFESSION_SKILL_LINE_1 + professionCount++, skill);
22047 SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, step);
22048 SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, value);
22049 SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, max);
22050 SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, 0);
22051 SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, 0);
22053 mSkillStatus.insert(SkillStatusMap::value_type(skill, SkillStatusData(count, SKILL_UNCHANGED)));
22055 learnSkillRewardedSpells(skill, value);
22057 ++count;
22059 if (count >= PLAYER_MAX_SKILLS) // client limit
22061 sLog.outError("Character %u has more than %u skills.", GetGUIDLow(), PLAYER_MAX_SKILLS);
22062 break;
22065 while (result->NextRow());
22066 delete result;
22069 for (; count < PLAYER_MAX_SKILLS; ++count)
22071 uint16 field = count / 2;
22072 uint8 offset = count & 1;
22074 SetUInt16Value(PLAYER_SKILL_LINEID_0 + field, offset, 0);
22075 SetUInt16Value(PLAYER_SKILL_STEP_0 + field, offset, 0);
22076 SetUInt16Value(PLAYER_SKILL_RANK_0 + field, offset, 0);
22077 SetUInt16Value(PLAYER_SKILL_MAX_RANK_0 + field, offset, 0);
22078 SetUInt16Value(PLAYER_SKILL_MODIFIER_0 + field, offset, 0);
22079 SetUInt16Value(PLAYER_SKILL_TALENT_0 + field, offset, 0);
22082 // special settings
22083 if (getClass() == CLASS_DEATH_KNIGHT)
22085 uint32 base_level = std::min(getLevel(), sWorld.getConfig(CONFIG_UINT32_START_HEROIC_PLAYER_LEVEL));
22086 if (base_level < 1)
22087 base_level = 1;
22088 uint32 base_skill = (base_level - 1) * 5; // 270 at starting level 55
22089 if (base_skill < 1)
22090 base_skill = 1; // skill mast be known and then > 0 in any case
22092 if (GetPureSkillValue(SKILL_FIRST_AID) < base_skill)
22093 SetSkill(SKILL_FIRST_AID, base_skill, base_skill);
22094 if (GetPureSkillValue(SKILL_AXES) < base_skill)
22095 SetSkill(SKILL_AXES, base_skill, base_skill);
22096 if (GetPureSkillValue(SKILL_DEFENSE) < base_skill)
22097 SetSkill(SKILL_DEFENSE, base_skill, base_skill);
22098 if (GetPureSkillValue(SKILL_POLEARMS) < base_skill)
22099 SetSkill(SKILL_POLEARMS, base_skill, base_skill);
22100 if (GetPureSkillValue(SKILL_SWORDS) < base_skill)
22101 SetSkill(SKILL_SWORDS, base_skill, base_skill);
22102 if (GetPureSkillValue(SKILL_2H_AXES) < base_skill)
22103 SetSkill(SKILL_2H_AXES, base_skill, base_skill);
22104 if (GetPureSkillValue(SKILL_2H_SWORDS) < base_skill)
22105 SetSkill(SKILL_2H_SWORDS, base_skill, base_skill);
22106 if (GetPureSkillValue(SKILL_UNARMED) < base_skill)
22107 SetSkill(SKILL_UNARMED, base_skill, base_skill);
22111 uint32 Player::GetPhaseMaskForSpawn() const
22113 uint32 phase = PHASEMASK_NORMAL;
22114 if (!isGameMaster())
22115 phase = GetPhaseMask();
22116 else
22118 AuraList const& phases = GetAurasByType(SPELL_AURA_PHASE);
22119 if (!phases.empty())
22120 phase = phases.front()->GetMiscValue();
22123 // some aura phases include 1 normal map in addition to phase itself
22124 if (uint32 n_phase = phase & ~PHASEMASK_NORMAL)
22125 return n_phase;
22127 return PHASEMASK_NORMAL;
22130 InventoryResult Player::CanEquipUniqueItem(Item* pItem, uint8 eslot, uint32 limit_count) const
22132 ItemPrototype const* pProto = pItem->GetProto();
22134 // proto based limitations
22135 if (InventoryResult res = CanEquipUniqueItem(pProto, eslot, limit_count))
22136 return res;
22138 // check unique-equipped on gems
22139 for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + 3; ++enchant_slot)
22141 uint32 enchant_id = pItem->GetEnchantmentId(EnchantmentSlot(enchant_slot));
22142 if (!enchant_id)
22143 continue;
22144 SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
22145 if (!enchantEntry)
22146 continue;
22148 ItemPrototype const* pGem = ObjectMgr::GetItemPrototype(enchantEntry->GemID);
22149 if (!pGem)
22150 continue;
22152 // include for check equip another gems with same limit category for not equipped item (and then not counted)
22153 uint32 gem_limit_count = !pItem->IsEquipped() && pGem->ItemLimitCategory
22154 ? pItem->GetGemCountWithLimitCategory(pGem->ItemLimitCategory) : 1;
22156 if (InventoryResult res = CanEquipUniqueItem(pGem, eslot, gem_limit_count))
22157 return res;
22160 return EQUIP_ERR_OK;
22163 InventoryResult Player::CanEquipUniqueItem(ItemPrototype const* itemProto, uint8 except_slot, uint32 limit_count) const
22165 // check unique-equipped on item
22166 if (itemProto->Flags & ITEM_FLAG_UNIQUE_EQUIPPED)
22168 // there is an equip limit on this item
22169 if (HasItemOrGemWithIdEquipped(itemProto->ItemId, 1, except_slot))
22170 return EQUIP_ERR_ITEM_UNIQUE_EQUIPABLE;
22173 // check unique-equipped limit
22174 if (itemProto->ItemLimitCategory)
22176 ItemLimitCategoryEntry const* limitEntry = sItemLimitCategoryStore.LookupEntry(itemProto->ItemLimitCategory);
22177 if (!limitEntry)
22178 return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED;
22180 // NOTE: limitEntry->mode not checked because if item have have-limit then it applied and to equip case
22182 if (limit_count > limitEntry->maxCount)
22183 return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED_IS;
22185 // there is an equip limit on this item
22186 if (HasItemOrGemWithLimitCategoryEquipped(itemProto->ItemLimitCategory, limitEntry->maxCount - limit_count + 1, except_slot))
22187 return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED_IS;
22190 return EQUIP_ERR_OK;
22193 void Player::HandleFall(MovementInfo const& movementInfo)
22195 // calculate total z distance of the fall
22196 float z_diff = m_lastFallZ - movementInfo.GetPos()->z;
22197 DEBUG_LOG("zDiff = %f", z_diff);
22199 // Players with low fall distance, Feather Fall or physical immunity (charges used) are ignored
22200 // 14.57 can be calculated by resolving damageperc formula below to 0
22201 if (z_diff >= 14.57f && !isDead() && !isGameMaster() &&
22202 !HasAuraType(SPELL_AURA_HOVER) && !HasAuraType(SPELL_AURA_FEATHER_FALL) &&
22203 !HasAuraType(SPELL_AURA_FLY) && !IsImmunedToDamage(SPELL_SCHOOL_MASK_NORMAL))
22205 // Safe fall, fall height reduction
22206 int32 safe_fall = GetTotalAuraModifier(SPELL_AURA_SAFE_FALL);
22208 float damageperc = 0.018f * (z_diff - safe_fall) - 0.2426f;
22210 if (damageperc > 0)
22212 uint32 damage = (uint32)(damageperc * GetMaxHealth() * sWorld.getConfig(CONFIG_FLOAT_RATE_DAMAGE_FALL));
22214 float height = movementInfo.GetPos()->z;
22215 UpdateAllowedPositionZ(movementInfo.GetPos()->x, movementInfo.GetPos()->y, height);
22217 if (damage > 0)
22219 // Prevent fall damage from being more than the player maximum health
22220 if (damage > GetMaxHealth())
22221 damage = GetMaxHealth();
22223 // Gust of Wind
22224 if (GetDummyAura(43621))
22225 damage = GetMaxHealth() / 2;
22227 uint32 original_health = GetHealth();
22228 uint32 final_damage = EnvironmentalDamage(DAMAGE_FALL, damage);
22230 // recheck alive, might have died of EnvironmentalDamage, avoid cases when player die in fact like Spirit of Redemption case
22231 if (isAlive() && final_damage < original_health)
22232 GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING, uint32(z_diff * 100));
22235 // Z given by moveinfo, LastZ, FallTime, WaterZ, MapZ, Damage, Safefall reduction
22236 DEBUG_LOG("FALLDAMAGE z=%f sz=%f pZ=%f FallTime=%d mZ=%f damage=%d SF=%d" , movementInfo.GetPos()->z, height, GetPositionZ(), movementInfo.GetFallTime(), height, damage, safe_fall);
22241 void Player::UpdateAchievementCriteria(AchievementCriteriaTypes type, uint32 miscvalue1/*=0*/, uint32 miscvalue2/*=0*/, Unit* unit/*=NULL*/, uint32 time/*=0*/)
22243 GetAchievementMgr().UpdateAchievementCriteria(type, miscvalue1, miscvalue2, unit, time);
22246 void Player::StartTimedAchievementCriteria(AchievementCriteriaTypes type, uint32 timedRequirementId, time_t startTime /*= 0*/)
22248 GetAchievementMgr().StartTimedAchievementCriteria(type, timedRequirementId, startTime);
22251 PlayerTalent const* Player::GetKnownTalentById(int32 talentId) const
22253 PlayerTalentMap::const_iterator itr = m_talents[m_activeSpec].find(talentId);
22254 if (itr != m_talents[m_activeSpec].end() && itr->second.state != PLAYERSPELL_REMOVED)
22255 return &itr->second;
22256 else
22257 return NULL;
22260 SpellEntry const* Player::GetKnownTalentRankById(int32 talentId) const
22262 if (PlayerTalent const* talent = GetKnownTalentById(talentId))
22263 return sSpellStore.LookupEntry(talent->talentEntry->RankID[talent->currentRank]);
22264 else
22265 return NULL;
22268 bool Player::LearnTalent(uint32 talentId, uint32 talentRank)
22270 uint32 CurTalentPoints = GetFreeTalentPoints();
22272 if (CurTalentPoints == 0)
22273 return false;
22275 if (talentRank >= MAX_TALENT_RANK)
22276 return false;
22278 TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
22280 if (!talentInfo)
22281 return false;
22283 TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab);
22285 if (!talentTabInfo)
22286 return false;
22288 // prevent learn talent for different class (cheating)
22289 if ((getClassMask() & talentTabInfo->ClassMask) == 0)
22290 return false;
22292 // find current max talent rank
22293 uint32 curtalent_maxrank = 0;
22294 if (PlayerTalent const* talent = GetKnownTalentById(talentId))
22295 curtalent_maxrank = talent->currentRank + 1;
22297 // we already have same or higher talent rank learned
22298 if (curtalent_maxrank >= (talentRank + 1))
22299 return false;
22301 // check if we have enough talent points
22302 if (CurTalentPoints < (talentRank - curtalent_maxrank + 1))
22303 return false;
22305 // Check if it requires another talent
22306 if (talentInfo->DependsOn > 0)
22308 if (TalentEntry const* depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn))
22310 bool hasEnoughRank = false;
22311 PlayerTalentMap::iterator dependsOnTalent = m_talents[m_activeSpec].find(depTalentInfo->TalentID);
22312 if (dependsOnTalent != m_talents[m_activeSpec].end() && dependsOnTalent->second.state != PLAYERSPELL_REMOVED)
22314 PlayerTalent depTalent = (*dependsOnTalent).second;
22315 if (depTalent.currentRank >= talentInfo->DependsOnRank)
22316 hasEnoughRank = true;
22319 if (!hasEnoughRank)
22320 return false;
22324 // Find out how many points we have in this field
22325 uint32 spentPoints = 0;
22327 uint32 primaryTreeTalents = 0;
22328 uint32 tTab = talentInfo->TalentTab;
22329 bool isMainTree = m_talentsPrimaryTree[m_activeSpec] == tTab || !m_talentsPrimaryTree[m_activeSpec];
22331 if (talentInfo->Row > 0 || !isMainTree)
22333 for (uint32 i = 0; i < sTalentStore.GetNumRows(); i++) // Loop through all talents.
22335 if (TalentEntry const* tmpTalent = sTalentStore.LookupEntry(i)) // Someday, someone needs to revamp the way talents are tracked
22337 for (uint8 rank = 0; rank < MAX_TALENT_RANK; rank++)
22339 if (tmpTalent->RankID[rank] != 0)
22341 if (HasSpell(tmpTalent->RankID[rank]))
22343 if (tmpTalent->TalentTab == tTab)
22344 spentPoints += (rank + 1);
22345 if (tmpTalent->TalentTab == m_talentsPrimaryTree[m_activeSpec])
22346 primaryTreeTalents += (rank + 1);
22354 // not have required min points spent in talent tree
22355 if (spentPoints < (talentInfo->Row * MAX_TALENT_RANK))
22356 return false;
22358 // player has not spent 31 talents in main tree before attempting to learn other tree's talents
22359 if (!isMainTree && primaryTreeTalents < REQ_PRIMARY_TREE_TALENTS)
22360 return false;
22362 // spell not set in talent.dbc
22363 uint32 spellid = talentInfo->RankID[talentRank];
22364 if (spellid == 0)
22366 sLog.outError("Talent.dbc have for talent: %u Rank: %u spell id = 0", talentId, talentRank);
22367 return false;
22370 // already known
22371 if (HasSpell(spellid))
22372 return false;
22374 // learn! (other talent ranks will unlearned at learning)
22375 learnSpell(spellid, false);
22376 DETAIL_LOG("TalentID: %u Rank: %u Spell: %u\n", talentId, talentRank, spellid);
22378 // set talent tree for player
22379 if (!m_talentsPrimaryTree[m_activeSpec])
22381 m_talentsPrimaryTree[m_activeSpec] = talentInfo->TalentTab;
22382 if (std::vector<uint32> const* specSpells = GetTalentTreeMasterySpells(talentInfo->TalentTab))
22383 for (size_t i = 0; i < specSpells->size(); ++i)
22384 learnSpell(specSpells->at(i), false);
22386 if (std::vector<uint32> const* specSpells = GetTalentTreePrimarySpells(talentInfo->TalentTab))
22387 for (size_t i = 0; i < specSpells->size(); ++i)
22388 learnSpell(specSpells->at(i), false);
22390 // Update talent tree role-dependent mana regen
22391 UpdateManaRegen();
22392 UpdateArmorSpecializations();
22395 return true;
22398 void Player::LearnPetTalent(ObjectGuid petGuid, uint32 talentId, uint32 talentRank)
22400 Pet* pet = GetPet();
22401 if (!pet)
22402 return;
22404 if (petGuid != pet->GetObjectGuid())
22405 return;
22407 uint32 CurTalentPoints = pet->GetFreeTalentPoints();
22409 if (CurTalentPoints == 0)
22410 return;
22412 if (talentRank >= MAX_PET_TALENT_RANK)
22413 return;
22415 TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
22417 if (!talentInfo)
22418 return;
22420 TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab);
22422 if (!talentTabInfo)
22423 return;
22425 CreatureInfo const* ci = pet->GetCreatureInfo();
22427 if (!ci)
22428 return;
22430 CreatureFamilyEntry const* pet_family = sCreatureFamilyStore.LookupEntry(ci->family);
22432 if (!pet_family)
22433 return;
22435 if (pet_family->petTalentType < 0) // not hunter pet
22436 return;
22438 // prevent learn talent for different family (cheating)
22439 if (!((1 << pet_family->petTalentType) & talentTabInfo->petTalentMask))
22440 return;
22442 // find current max talent rank
22443 int32 curtalent_maxrank = 0;
22444 for (int32 k = MAX_TALENT_RANK - 1; k > -1; --k)
22446 if (talentInfo->RankID[k] && pet->HasSpell(talentInfo->RankID[k]))
22448 curtalent_maxrank = k + 1;
22449 break;
22453 // we already have same or higher talent rank learned
22454 if (curtalent_maxrank >= int32(talentRank + 1))
22455 return;
22457 // check if we have enough talent points
22458 if (CurTalentPoints < (talentRank - curtalent_maxrank + 1))
22459 return;
22461 // Check if it requires another talent
22462 if (talentInfo->DependsOn > 0)
22464 if (TalentEntry const* depTalentInfo = sTalentStore.LookupEntry(talentInfo->DependsOn))
22466 bool hasEnoughRank = false;
22467 for (int i = talentInfo->DependsOnRank; i < MAX_TALENT_RANK; ++i)
22469 if (depTalentInfo->RankID[i] != 0)
22470 if (pet->HasSpell(depTalentInfo->RankID[i]))
22471 hasEnoughRank = true;
22473 if (!hasEnoughRank)
22474 return;
22478 // Find out how many points we have in this field
22479 uint32 spentPoints = 0;
22481 uint32 tTab = talentInfo->TalentTab;
22482 if (talentInfo->Row > 0)
22484 unsigned int numRows = sTalentStore.GetNumRows();
22485 for (unsigned int i = 0; i < numRows; ++i) // Loop through all talents.
22487 // Someday, someone needs to revamp
22488 const TalentEntry* tmpTalent = sTalentStore.LookupEntry(i);
22489 if (tmpTalent) // the way talents are tracked
22491 if (tmpTalent->TalentTab == tTab)
22493 for (int j = 0; j < MAX_TALENT_RANK; ++j)
22495 if (tmpTalent->RankID[j] != 0)
22497 if (pet->HasSpell(tmpTalent->RankID[j]))
22499 spentPoints += j + 1;
22508 // not have required min points spent in talent tree
22509 if (spentPoints < (talentInfo->Row * MAX_PET_TALENT_RANK))
22510 return;
22512 // spell not set in talent.dbc
22513 uint32 spellid = talentInfo->RankID[talentRank];
22514 if (spellid == 0)
22516 sLog.outError("Talent.dbc have for talent: %u Rank: %u spell id = 0", talentId, talentRank);
22517 return;
22520 // already known
22521 if (pet->HasSpell(spellid))
22522 return;
22524 // learn! (other talent ranks will unlearned at learning)
22525 pet->learnSpell(spellid);
22526 DETAIL_LOG("PetTalentID: %u Rank: %u Spell: %u\n", talentId, talentRank, spellid);
22529 void Player::UpdateFallInformationIfNeed(MovementInfo const& minfo, uint16 opcode)
22531 if (m_lastFallTime >= minfo.GetFallTime() || m_lastFallZ <= minfo.GetPos()->z || opcode == CMSG_MOVE_FALL_LAND)
22532 SetFallInformation(minfo.GetFallTime(), minfo.GetPos()->z);
22535 void Player::UnsummonPetTemporaryIfAny()
22537 Pet* pet = GetPet();
22538 if (!pet)
22539 return;
22541 if (!m_temporaryUnsummonedPetNumber && pet->isControlled() && !pet->isTemporarySummoned())
22542 m_temporaryUnsummonedPetNumber = pet->GetCharmInfo()->GetPetNumber();
22544 pet->Unsummon(PET_SAVE_AS_CURRENT, this);
22547 void Player::ResummonPetTemporaryUnSummonedIfAny()
22549 if (!m_temporaryUnsummonedPetNumber)
22550 return;
22552 // not resummon in not appropriate state
22553 if (IsPetNeedBeTemporaryUnsummoned())
22554 return;
22556 if (GetPetGuid())
22557 return;
22559 Pet* NewPet = new Pet;
22560 if (!NewPet->LoadPetFromDB(this, 0, m_temporaryUnsummonedPetNumber, true))
22561 delete NewPet;
22563 m_temporaryUnsummonedPetNumber = 0;
22566 bool Player::canSeeSpellClickOn(Creature const* c) const
22568 if (!c->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK))
22569 return false;
22571 SpellClickInfoMapBounds clickPair = sObjectMgr.GetSpellClickInfoMapBounds(c->GetEntry());
22572 for (SpellClickInfoMap::const_iterator itr = clickPair.first; itr != clickPair.second; ++itr)
22573 if (itr->second.IsFitToRequirements(this))
22574 return true;
22576 return false;
22579 void Player::BuildPlayerTalentsInfoData(WorldPacket* data)
22581 *data << uint32(GetFreeTalentPoints()); // unspentTalentPoints
22582 *data << uint8(m_specsCount); // talent group count (0, 1 or 2)
22583 *data << uint8(m_activeSpec); // talent group index (0 or 1)
22585 if (m_specsCount)
22587 if (m_specsCount > MAX_TALENT_SPEC_COUNT)
22588 m_specsCount = MAX_TALENT_SPEC_COUNT;
22590 // loop through all specs (only 1 for now)
22591 for (uint32 specIdx = 0; specIdx < m_specsCount; ++specIdx)
22593 *data << uint32(m_talentsPrimaryTree[specIdx]);
22594 uint8 talentIdCount = 0;
22595 size_t pos = data->wpos();
22596 *data << uint8(talentIdCount); // [PH], talentIdCount
22598 // find class talent tabs (all players have 3 talent tabs)
22599 uint32 const* talentTabIds = GetTalentTabPages(getClass());
22601 for (uint32 i = 0; i < MAX_TALENT_TABS; ++i)
22603 uint32 talentTabId = talentTabIds[i];
22604 for (PlayerTalentMap::iterator iter = m_talents[specIdx].begin(); iter != m_talents[specIdx].end(); ++iter)
22606 PlayerTalent talent = (*iter).second;
22608 if (talent.state == PLAYERSPELL_REMOVED)
22609 continue;
22611 // skip another tab talents
22612 if (talent.talentEntry->TalentTab != talentTabId)
22613 continue;
22615 *data << uint32(talent.talentEntry->TalentID); // Talent.dbc
22616 *data << uint8(talent.currentRank); // talentMaxRank (0-4)
22618 ++talentIdCount;
22622 data->put<uint8>(pos, talentIdCount); // put real count
22624 *data << uint8(MAX_GLYPH_SLOT_INDEX); // glyphs count
22626 // GlyphProperties.dbc
22627 for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
22628 *data << uint16(m_glyphs[specIdx][i].GetId());
22633 void Player::BuildPetTalentsInfoData(WorldPacket* data)
22635 uint32 unspentTalentPoints = 0;
22636 size_t pointsPos = data->wpos();
22637 *data << uint32(unspentTalentPoints); // [PH], unspentTalentPoints
22639 uint8 talentIdCount = 0;
22640 size_t countPos = data->wpos();
22641 *data << uint8(talentIdCount); // [PH], talentIdCount
22643 Pet* pet = GetPet();
22644 if (!pet)
22645 return;
22647 unspentTalentPoints = pet->GetFreeTalentPoints();
22649 data->put<uint32>(pointsPos, unspentTalentPoints); // put real points
22651 CreatureInfo const* ci = pet->GetCreatureInfo();
22652 if (!ci)
22653 return;
22655 CreatureFamilyEntry const* pet_family = sCreatureFamilyStore.LookupEntry(ci->family);
22656 if (!pet_family || pet_family->petTalentType < 0)
22657 return;
22659 for (uint32 talentTabId = 1; talentTabId < sTalentTabStore.GetNumRows(); ++talentTabId)
22661 TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentTabId);
22662 if (!talentTabInfo)
22663 continue;
22665 if (!((1 << pet_family->petTalentType) & talentTabInfo->petTalentMask))
22666 continue;
22668 for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
22670 TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
22671 if (!talentInfo)
22672 continue;
22674 // skip another tab talents
22675 if (talentInfo->TalentTab != talentTabId)
22676 continue;
22678 // find max talent rank
22679 int32 curtalent_maxrank = -1;
22680 for (int32 k = 4; k > -1; --k)
22682 if (talentInfo->RankID[k] && pet->HasSpell(talentInfo->RankID[k]))
22684 curtalent_maxrank = k;
22685 break;
22689 // not learned talent
22690 if (curtalent_maxrank < 0)
22691 continue;
22693 *data << uint32(talentInfo->TalentID); // Talent.dbc
22694 *data << uint8(curtalent_maxrank); // talentMaxRank (0-4)
22696 ++talentIdCount;
22699 data->put<uint8>(countPos, talentIdCount); // put real count
22701 break;
22705 void Player::SendTalentsInfoData(bool pet)
22707 WorldPacket data(SMSG_TALENT_UPDATE, 50);
22708 data << uint8(pet ? 1 : 0);
22709 if (pet)
22710 BuildPetTalentsInfoData(&data);
22711 else
22712 BuildPlayerTalentsInfoData(&data);
22713 GetSession()->SendPacket(&data);
22716 void Player::BuildEnchantmentsInfoData(WorldPacket* data)
22718 uint32 slotUsedMask = 0;
22719 size_t slotUsedMaskPos = data->wpos();
22720 *data << uint32(slotUsedMask); // slotUsedMask < 0x80000
22722 for (uint32 i = 0; i < EQUIPMENT_SLOT_END; ++i)
22724 Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
22726 if (!item)
22727 continue;
22729 slotUsedMask |= (1 << i);
22731 *data << uint32(item->GetEntry()); // item entry
22733 uint16 enchantmentMask = 0;
22734 size_t enchantmentMaskPos = data->wpos();
22735 *data << uint16(enchantmentMask); // enchantmentMask < 0x1000
22737 for (uint32 j = 0; j < MAX_ENCHANTMENT_SLOT; ++j)
22739 uint32 enchId = item->GetEnchantmentId(EnchantmentSlot(j));
22741 if (!enchId)
22742 continue;
22744 enchantmentMask |= (1 << j);
22746 *data << uint16(enchId); // enchantmentId?
22749 data->put<uint16>(enchantmentMaskPos, enchantmentMask);
22751 *data << uint16(item->GetItemRandomPropertyId());
22752 *data << item->GetGuidValue(ITEM_FIELD_CREATOR).WriteAsPacked();
22753 *data << uint32(item->GetItemSuffixFactor());
22756 data->put<uint32>(slotUsedMaskPos, slotUsedMask);
22759 void Player::SendEquipmentSetList()
22761 uint32 count = 0;
22762 WorldPacket data(SMSG_LOAD_EQUIPMENT_SET, 4);
22763 size_t count_pos = data.wpos();
22764 data << uint32(count); // count placeholder
22765 for (EquipmentSets::iterator itr = m_EquipmentSets.begin(); itr != m_EquipmentSets.end(); ++itr)
22767 if (itr->second.state == EQUIPMENT_SET_DELETED)
22768 continue;
22769 data.appendPackGUID(itr->second.Guid);
22770 data << uint32(itr->first);
22771 data << itr->second.Name;
22772 data << itr->second.IconName;
22773 for (uint32 i = 0; i < EQUIPMENT_SLOT_END; ++i)
22775 // ignored slots stored in IgnoreMask, client wants "1" as raw GUID, so no HIGHGUID_ITEM
22776 if (itr->second.IgnoreMask & (1 << i))
22777 data << ObjectGuid(uint64(1)).WriteAsPacked();
22778 else
22779 data << ObjectGuid(HIGHGUID_ITEM, itr->second.Items[i]).WriteAsPacked();
22782 ++count; // client have limit but it checked at loading and set
22784 data.put<uint32>(count_pos, count);
22785 GetSession()->SendPacket(&data);
22788 void Player::SetEquipmentSet(uint32 index, EquipmentSet eqset)
22790 if (eqset.Guid != 0)
22792 bool found = false;
22794 for (EquipmentSets::iterator itr = m_EquipmentSets.begin(); itr != m_EquipmentSets.end(); ++itr)
22796 if ((itr->second.Guid == eqset.Guid) && (itr->first == index))
22798 found = true;
22799 break;
22803 if (!found) // something wrong...
22805 sLog.outError("Player %s tried to save equipment set " UI64FMTD " (index %u), but that equipment set not found!", GetName(), eqset.Guid, index);
22806 return;
22810 EquipmentSet& eqslot = m_EquipmentSets[index];
22812 EquipmentSetUpdateState old_state = eqslot.state;
22814 eqslot = eqset;
22816 if (eqset.Guid == 0)
22818 eqslot.Guid = sObjectMgr.GenerateEquipmentSetGuid();
22820 WorldPacket data(SMSG_EQUIPMENT_SET_ID, 4 + 1);
22821 data << uint32(index);
22822 data.appendPackGUID(eqslot.Guid);
22823 GetSession()->SendPacket(&data);
22826 eqslot.state = old_state == EQUIPMENT_SET_NEW ? EQUIPMENT_SET_NEW : EQUIPMENT_SET_CHANGED;
22829 void Player::_SaveEquipmentSets()
22831 static SqlStatementID updSets ;
22832 static SqlStatementID insSets ;
22833 static SqlStatementID delSets ;
22835 for (EquipmentSets::iterator itr = m_EquipmentSets.begin(); itr != m_EquipmentSets.end();)
22837 uint32 index = itr->first;
22838 EquipmentSet& eqset = itr->second;
22839 switch (eqset.state)
22841 case EQUIPMENT_SET_UNCHANGED:
22842 ++itr;
22843 break; // nothing do
22844 case EQUIPMENT_SET_CHANGED:
22846 SqlStatement stmt = CharacterDatabase.CreateStatement(updSets, "UPDATE character_equipmentsets SET name=?, iconname=?, ignore_mask=?, item0=?, item1=?, item2=?, item3=?, item4=?, "
22847 "item5=?, item6=?, item7=?, item8=?, item9=?, item10=?, item11=?, item12=?, item13=?, item14=?, "
22848 "item15=?, item16=?, item17=?, item18=? WHERE guid=? AND setguid=? AND setindex=?");
22850 stmt.addString(eqset.Name);
22851 stmt.addString(eqset.IconName);
22852 stmt.addUInt32(eqset.IgnoreMask);
22854 for (int i = 0; i < EQUIPMENT_SLOT_END; ++i)
22855 stmt.addUInt32(eqset.Items[i]);
22857 stmt.addUInt32(GetGUIDLow());
22858 stmt.addUInt64(eqset.Guid);
22859 stmt.addUInt32(index);
22861 stmt.Execute();
22863 eqset.state = EQUIPMENT_SET_UNCHANGED;
22864 ++itr;
22865 break;
22867 case EQUIPMENT_SET_NEW:
22869 SqlStatement stmt = CharacterDatabase.CreateStatement(insSets, "INSERT INTO character_equipmentsets VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
22870 stmt.addUInt32(GetGUIDLow());
22871 stmt.addUInt64(eqset.Guid);
22872 stmt.addUInt32(index);
22873 stmt.addString(eqset.Name);
22874 stmt.addString(eqset.IconName);
22875 stmt.addUInt32(eqset.IgnoreMask);
22877 for (int i = 0; i < EQUIPMENT_SLOT_END; ++i)
22878 stmt.addUInt32(eqset.Items[i]);
22880 stmt.Execute();
22882 eqset.state = EQUIPMENT_SET_UNCHANGED;
22883 ++itr;
22884 break;
22886 case EQUIPMENT_SET_DELETED:
22888 SqlStatement stmt = CharacterDatabase.CreateStatement(delSets, "DELETE FROM character_equipmentsets WHERE setguid = ?");
22889 stmt.PExecute(eqset.Guid);
22890 m_EquipmentSets.erase(itr++);
22891 break;
22897 void Player::_SaveBGData()
22899 // nothing save
22900 if (!m_bgData.m_needSave)
22901 return;
22903 static SqlStatementID delBGData ;
22904 static SqlStatementID insBGData ;
22906 SqlStatement stmt = CharacterDatabase.CreateStatement(delBGData, "DELETE FROM character_battleground_data WHERE guid = ?");
22908 stmt.PExecute(GetGUIDLow());
22910 if (m_bgData.bgInstanceID)
22912 stmt = CharacterDatabase.CreateStatement(insBGData, "INSERT INTO character_battleground_data VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
22913 /* guid, bgInstanceID, bgTeam, x, y, z, o, map, taxi[0], taxi[1], mountSpell */
22914 stmt.addUInt32(GetGUIDLow());
22915 stmt.addUInt32(m_bgData.bgInstanceID);
22916 stmt.addUInt32(uint32(m_bgData.bgTeam));
22917 stmt.addFloat(m_bgData.joinPos.coord_x);
22918 stmt.addFloat(m_bgData.joinPos.coord_y);
22919 stmt.addFloat(m_bgData.joinPos.coord_z);
22920 stmt.addFloat(m_bgData.joinPos.orientation);
22921 stmt.addUInt32(m_bgData.joinPos.mapid);
22922 stmt.addUInt32(m_bgData.taxiPath[0]);
22923 stmt.addUInt32(m_bgData.taxiPath[1]);
22924 stmt.addUInt32(m_bgData.mountSpell);
22926 stmt.Execute();
22929 m_bgData.m_needSave = false;
22932 void Player::DeleteEquipmentSet(uint64 setGuid)
22934 for (EquipmentSets::iterator itr = m_EquipmentSets.begin(); itr != m_EquipmentSets.end(); ++itr)
22936 if (itr->second.Guid == setGuid)
22938 if (itr->second.state == EQUIPMENT_SET_NEW)
22939 m_EquipmentSets.erase(itr);
22940 else
22941 itr->second.state = EQUIPMENT_SET_DELETED;
22942 break;
22947 void Player::ActivateSpec(uint8 specNum)
22949 if (GetActiveSpec() == specNum)
22950 return;
22952 if (specNum >= GetSpecsCount())
22953 return;
22955 UnsummonPetTemporaryIfAny();
22957 // prevent deletion of action buttons by client at spell unlearn or by player while spec change in progress
22958 SendLockActionButtons();
22960 // Remove spec specific spells
22961 for (uint32 i = 0; i < MAX_TALENT_TABS; ++i)
22963 if (std::vector<uint32> const* specSpells = GetTalentTreeMasterySpells(GetTalentTabPages(getClass())[i]))
22964 for (size_t i = 0; i < specSpells->size(); ++i)
22965 removeSpell(specSpells->at(i), true);
22967 if (std::vector<uint32> const* specSpells = GetTalentTreePrimarySpells(GetTalentTabPages(getClass())[i]))
22968 for (size_t i = 0; i < specSpells->size(); ++i)
22969 removeSpell(specSpells->at(i), true);
22972 ApplyGlyphs(false);
22974 // copy of new talent spec (we will use it as model for converting current tlanet state to new)
22975 PlayerTalentMap tempSpec = m_talents[specNum];
22977 // copy old spec talents to new one, must be before spec switch to have previous spec num(as m_activeSpec)
22978 m_talents[specNum] = m_talents[m_activeSpec];
22980 SetActiveSpec(specNum);
22982 // remove all talent spells that don't exist in next spec but exist in old
22983 for (PlayerTalentMap::iterator specIter = m_talents[m_activeSpec].begin(); specIter != m_talents[m_activeSpec].end();)
22985 PlayerTalent& talent = specIter->second;
22987 if (talent.state == PLAYERSPELL_REMOVED)
22989 ++specIter;
22990 continue;
22993 PlayerTalentMap::iterator iterTempSpec = tempSpec.find(specIter->first);
22995 // remove any talent rank if talent not listed in temp spec
22996 if (iterTempSpec == tempSpec.end() || iterTempSpec->second.state == PLAYERSPELL_REMOVED)
22998 TalentEntry const* talentInfo = talent.talentEntry;
23000 for (int r = 0; r < MAX_TALENT_RANK; ++r)
23001 if (talentInfo->RankID[r])
23002 removeSpell(talentInfo->RankID[r], !IsPassiveSpell(talentInfo->RankID[r]), false);
23004 specIter = m_talents[m_activeSpec].begin();
23006 else
23007 ++specIter;
23010 // now new spec data have only talents (maybe different rank) as in temp spec data, sync ranks then.
23011 for (PlayerTalentMap::const_iterator tempIter = tempSpec.begin(); tempIter != tempSpec.end(); ++tempIter)
23013 PlayerTalent const& talent = tempIter->second;
23015 // removed state talent already unlearned in prev. loop
23016 // but we need restore it if it deleted for finish removed-marked data in DB
23017 if (talent.state == PLAYERSPELL_REMOVED)
23019 m_talents[m_activeSpec][tempIter->first] = talent;
23020 continue;
23023 uint32 talentSpellId = talent.talentEntry->RankID[talent.currentRank];
23025 // learn talent spells if they not in new spec (old spec copy)
23026 // and if they have different rank
23027 if (PlayerTalent const* cur_talent = GetKnownTalentById(tempIter->first))
23029 if (cur_talent->currentRank != talent.currentRank)
23030 learnSpell(talentSpellId, false);
23032 else
23033 learnSpell(talentSpellId, false);
23035 // sync states - original state is changed in addSpell that learnSpell calls
23036 PlayerTalentMap::iterator specIter = m_talents[m_activeSpec].find(tempIter->first);
23037 if (specIter != m_talents[m_activeSpec].end())
23038 specIter->second.state = talent.state;
23039 else
23041 sLog.outError("ActivateSpec: Talent spell %u expected to learned at spec switch but not listed in talents at final check!", talentSpellId);
23043 // attempt resync DB state (deleted lost spell from DB)
23044 if (talent.state != PLAYERSPELL_NEW)
23046 PlayerTalent& talentNew = m_talents[m_activeSpec][tempIter->first];
23047 talentNew = talent;
23048 talentNew.state = PLAYERSPELL_REMOVED;
23053 InitTalentForLevel();
23055 // recheck action buttons (not checked at loading/spec copy)
23056 ActionButtonList const& currentActionButtonList = m_actionButtons[m_activeSpec];
23057 for (ActionButtonList::const_iterator itr = currentActionButtonList.begin(); itr != currentActionButtonList.end();)
23059 if (itr->second.uState != ACTIONBUTTON_DELETED)
23061 // remove broken without any output (it can be not correct because talents not copied at spec creating)
23062 if (!IsActionButtonDataValid(itr->first, itr->second.GetAction(), itr->second.GetType(), this, false))
23064 removeActionButton(m_activeSpec, itr->first);
23065 itr = currentActionButtonList.begin();
23066 continue;
23069 ++itr;
23072 ResummonPetTemporaryUnSummonedIfAny();
23074 if (std::vector<uint32> const* specSpells = GetTalentTreeMasterySpells(m_talentsPrimaryTree[m_activeSpec]))
23075 for (size_t i = 0; i < specSpells->size(); ++i)
23076 learnSpell(specSpells->at(i), false);
23078 if (std::vector<uint32> const* specSpells = GetTalentTreePrimarySpells(m_talentsPrimaryTree[m_activeSpec]))
23079 for (size_t i = 0; i < specSpells->size(); ++i)
23080 learnSpell(specSpells->at(i), false);
23082 ApplyGlyphs(true);
23084 SendInitialActionButtons();
23086 Powers pw = getPowerType();
23087 if (pw != POWER_MANA)
23088 SetPower(POWER_MANA, 0);
23090 SetPower(pw, 0);
23092 if (m_talentsPrimaryTree[m_activeSpec] && !sTalentTabStore.LookupEntry(m_talentsPrimaryTree[m_activeSpec]))
23093 resetTalents(true);
23095 // Update talent tree role-dependent mana regen
23096 UpdateManaRegen();
23098 UpdateArmorSpecializations();
23101 void Player::UpdateSpecCount(uint8 count)
23103 uint8 curCount = GetSpecsCount();
23104 if (curCount == count)
23105 return;
23107 // maybe current spec data must be copied to 0 spec?
23108 if (m_activeSpec >= count)
23109 ActivateSpec(0);
23111 // copy spec data from new specs
23112 if (count > curCount)
23114 // copy action buttons from active spec (more easy in this case iterate first by button)
23115 ActionButtonList const& currentActionButtonList = m_actionButtons[m_activeSpec];
23117 for (ActionButtonList::const_iterator itr = currentActionButtonList.begin(); itr != currentActionButtonList.end(); ++itr)
23119 if (itr->second.uState != ACTIONBUTTON_DELETED)
23121 for (uint8 spec = curCount; spec < count; ++spec)
23122 addActionButton(spec, itr->first, itr->second.GetAction(), itr->second.GetType());
23126 // delete spec data for removed specs
23127 else if (count < curCount)
23129 // delete action buttons for removed spec
23130 for (uint8 spec = count; spec < curCount; ++spec)
23132 // delete action buttons for removed spec
23133 for (uint8 button = 0; button < MAX_ACTION_BUTTONS; ++button)
23134 removeActionButton(spec, button);
23138 SetSpecsCount(count);
23140 SendTalentsInfoData(false);
23143 void Player::RemoveAtLoginFlag(AtLoginFlags f, bool in_db_also /*= false*/)
23145 m_atLoginFlags &= ~f;
23147 if (in_db_also)
23148 CharacterDatabase.PExecute("UPDATE characters set at_login = at_login & ~ %u WHERE guid ='%u'", uint32(f), GetGUIDLow());
23151 void Player::SendClearCooldown(uint32 spell_id, Unit* target)
23153 ObjectGuid guid = target->GetObjectGuid();
23155 WorldPacket data(SMSG_CLEAR_COOLDOWNS, 1 + 8 + 4);
23156 data.WriteGuidMask<1, 3, 6>(guid);
23157 data.WriteBits(1, 24); // cooldown count
23158 data.WriteGuidMask<7, 5, 2, 4, 0>(guid);
23160 data.WriteGuidBytes<7, 2, 4, 5, 1, 3>(guid);
23161 data << uint32(spell_id);
23162 data.WriteGuidBytes<0, 6>(guid);
23164 SendDirectMessage(&data);
23167 bool Player::HasMovementFlag(MovementFlags f) const
23169 return m_movementInfo.HasMovementFlag(f);
23172 void Player::ResetTimeSync()
23174 m_timeSyncCounter = 0;
23175 m_timeSyncTimer = 0;
23176 m_timeSyncClient = 0;
23177 m_timeSyncServer = WorldTimer::getMSTime();
23180 void Player::SendTimeSync()
23182 WorldPacket data(SMSG_TIME_SYNC_REQ, 4);
23183 data << uint32(m_timeSyncCounter++);
23184 GetSession()->SendPacket(&data);
23186 // Schedule next sync in 10 sec
23187 m_timeSyncTimer = 10000;
23188 m_timeSyncServer = WorldTimer::getMSTime();
23191 void Player::SendDuelCountdown(uint32 counter)
23193 WorldPacket data(SMSG_DUEL_COUNTDOWN, 4);
23194 data << uint32(counter); // seconds
23195 GetSession()->SendPacket(&data);
23198 bool Player::IsImmuneToSpellEffect(SpellEntry const* spellInfo, SpellEffectIndex index, bool castOnSelf) const
23200 SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(index);
23201 if(spellEffect)
23203 switch(spellEffect->Effect)
23205 case SPELL_EFFECT_ATTACK_ME:
23206 return true;
23207 default:
23208 break;
23210 switch(spellEffect->EffectApplyAuraName)
23212 case SPELL_AURA_MOD_TAUNT:
23213 return true;
23214 default:
23215 break;
23219 return Unit::IsImmuneToSpellEffect(spellInfo, index, castOnSelf);
23222 void Player::SetHomebindToLocation(WorldLocation const& loc, uint32 area_id)
23224 m_homebindMapId = loc.mapid;
23225 m_homebindAreaId = area_id;
23226 m_homebindX = loc.coord_x;
23227 m_homebindY = loc.coord_y;
23228 m_homebindZ = loc.coord_z;
23230 // update sql homebind
23231 CharacterDatabase.PExecute("UPDATE character_homebind SET map = '%u', zone = '%u', position_x = '%f', position_y = '%f', position_z = '%f' WHERE guid = '%u'",
23232 m_homebindMapId, m_homebindAreaId, m_homebindX, m_homebindY, m_homebindZ, GetGUIDLow());
23235 Object* Player::GetObjectByTypeMask(ObjectGuid guid, TypeMask typemask)
23237 switch (guid.GetHigh())
23239 case HIGHGUID_ITEM:
23240 if (typemask & TYPEMASK_ITEM)
23241 return GetItemByGuid(guid);
23242 break;
23243 case HIGHGUID_PLAYER:
23244 if (GetObjectGuid() == guid)
23245 return this;
23246 if ((typemask & TYPEMASK_PLAYER) && IsInWorld())
23247 return ObjectAccessor::FindPlayer(guid);
23248 break;
23249 case HIGHGUID_GAMEOBJECT:
23250 if ((typemask & TYPEMASK_GAMEOBJECT) && IsInWorld())
23251 return GetMap()->GetGameObject(guid);
23252 break;
23253 case HIGHGUID_UNIT:
23254 case HIGHGUID_VEHICLE:
23255 if ((typemask & TYPEMASK_UNIT) && IsInWorld())
23256 return GetMap()->GetCreature(guid);
23257 break;
23258 case HIGHGUID_PET:
23259 if ((typemask & TYPEMASK_UNIT) && IsInWorld())
23260 return GetMap()->GetPet(guid);
23261 break;
23262 case HIGHGUID_DYNAMICOBJECT:
23263 if ((typemask & TYPEMASK_DYNAMICOBJECT) && IsInWorld())
23264 return GetMap()->GetDynamicObject(guid);
23265 break;
23266 case HIGHGUID_TRANSPORT:
23267 case HIGHGUID_CORPSE:
23268 case HIGHGUID_MO_TRANSPORT:
23269 case HIGHGUID_INSTANCE:
23270 case HIGHGUID_GROUP:
23271 default:
23272 break;
23275 return NULL;
23278 void Player::SetRestType(RestType n_r_type, uint32 areaTriggerId /*= 0*/)
23280 rest_type = n_r_type;
23282 if (rest_type == REST_TYPE_NO)
23284 RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING);
23286 // Set player to FFA PVP when not in rested environment.
23287 if (sWorld.IsFFAPvPRealm())
23288 SetFFAPvP(true);
23290 else
23292 SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING);
23294 inn_trigger_id = areaTriggerId;
23295 time_inn_enter = time(NULL);
23297 if (sWorld.IsFFAPvPRealm())
23298 SetFFAPvP(false);
23302 uint32 Player::GetEquipGearScore(bool withBags, bool withBank)
23304 if (withBags && withBank && m_cachedGS > 0)
23305 return m_cachedGS;
23307 GearScoreVec gearScore(EQUIPMENT_SLOT_END);
23308 uint32 twoHandScore = 0;
23310 for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
23312 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
23313 _fillGearScoreData(item, &gearScore, twoHandScore);
23316 if (withBags)
23318 // check inventory
23319 for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
23321 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
23322 _fillGearScoreData(item, &gearScore, twoHandScore);
23325 // check bags
23326 for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
23328 if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i))
23330 for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
23332 if (Item* item2 = pBag->GetItemByPos(j))
23333 _fillGearScoreData(item2, &gearScore, twoHandScore);
23339 if (withBank)
23341 for (uint8 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i)
23343 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
23344 _fillGearScoreData(item, &gearScore, twoHandScore);
23347 for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
23349 if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
23351 if (item->IsBag())
23353 Bag* bag = (Bag*)item;
23354 for (uint8 j = 0; j < bag->GetBagSize(); ++j)
23356 if (Item* item2 = bag->GetItemByPos(j))
23357 _fillGearScoreData(item2, &gearScore, twoHandScore);
23364 uint8 count = EQUIPMENT_SLOT_END - 2; // ignore body and tabard slots
23365 uint32 sum = 0;
23367 // check if 2h hand is higher level than main hand + off hand
23368 if (gearScore[EQUIPMENT_SLOT_MAINHAND] + gearScore[EQUIPMENT_SLOT_OFFHAND] < twoHandScore * 2)
23370 gearScore[EQUIPMENT_SLOT_OFFHAND] = 0; // off hand is ignored in calculations if 2h weapon has higher score
23371 --count;
23372 gearScore[EQUIPMENT_SLOT_MAINHAND] = twoHandScore;
23375 for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
23377 sum += gearScore[i];
23380 if (count)
23382 uint32 res = uint32(sum / count);
23383 DEBUG_LOG("Player: calculating gear score for %u. Result is %u", GetObjectGuid().GetCounter(), res);
23385 if (withBags && withBank)
23386 m_cachedGS = res;
23388 return res;
23390 else
23391 return 0;
23394 void Player::_fillGearScoreData(Item* item, GearScoreVec* gearScore, uint32& twoHandScore)
23396 if (!item)
23397 return;
23399 if (CanUseItem(item->GetProto()) != EQUIP_ERR_OK)
23400 return;
23402 uint8 type = item->GetProto()->InventoryType;
23403 uint32 level = item->GetProto()->ItemLevel;
23405 switch (type)
23407 case INVTYPE_2HWEAPON:
23408 twoHandScore = std::max(twoHandScore, level);
23409 break;
23410 case INVTYPE_WEAPON:
23411 case INVTYPE_WEAPONMAINHAND:
23412 (*gearScore)[EQUIPMENT_SLOT_MAINHAND] = std::max((*gearScore)[EQUIPMENT_SLOT_MAINHAND], level);
23413 break;
23414 case INVTYPE_SHIELD:
23415 case INVTYPE_WEAPONOFFHAND:
23416 (*gearScore)[EQUIPMENT_SLOT_OFFHAND] = std::max((*gearScore)[EQUIPMENT_SLOT_OFFHAND], level);
23417 break;
23418 case INVTYPE_THROWN:
23419 case INVTYPE_RANGEDRIGHT:
23420 case INVTYPE_RANGED:
23421 case INVTYPE_QUIVER:
23422 case INVTYPE_RELIC:
23423 (*gearScore)[EQUIPMENT_SLOT_RANGED] = std::max((*gearScore)[EQUIPMENT_SLOT_RANGED], level);
23424 break;
23425 case INVTYPE_HEAD:
23426 (*gearScore)[EQUIPMENT_SLOT_HEAD] = std::max((*gearScore)[EQUIPMENT_SLOT_HEAD], level);
23427 break;
23428 case INVTYPE_NECK:
23429 (*gearScore)[EQUIPMENT_SLOT_NECK] = std::max((*gearScore)[EQUIPMENT_SLOT_NECK], level);
23430 break;
23431 case INVTYPE_SHOULDERS:
23432 (*gearScore)[EQUIPMENT_SLOT_SHOULDERS] = std::max((*gearScore)[EQUIPMENT_SLOT_SHOULDERS], level);
23433 break;
23434 case INVTYPE_BODY:
23435 (*gearScore)[EQUIPMENT_SLOT_BODY] = std::max((*gearScore)[EQUIPMENT_SLOT_BODY], level);
23436 break;
23437 case INVTYPE_CHEST:
23438 (*gearScore)[EQUIPMENT_SLOT_CHEST] = std::max((*gearScore)[EQUIPMENT_SLOT_CHEST], level);
23439 break;
23440 case INVTYPE_WAIST:
23441 (*gearScore)[EQUIPMENT_SLOT_WAIST] = std::max((*gearScore)[EQUIPMENT_SLOT_WAIST], level);
23442 break;
23443 case INVTYPE_LEGS:
23444 (*gearScore)[EQUIPMENT_SLOT_LEGS] = std::max((*gearScore)[EQUIPMENT_SLOT_LEGS], level);
23445 break;
23446 case INVTYPE_FEET:
23447 (*gearScore)[EQUIPMENT_SLOT_FEET] = std::max((*gearScore)[EQUIPMENT_SLOT_FEET], level);
23448 break;
23449 case INVTYPE_WRISTS:
23450 (*gearScore)[EQUIPMENT_SLOT_WRISTS] = std::max((*gearScore)[EQUIPMENT_SLOT_WRISTS], level);
23451 break;
23452 case INVTYPE_HANDS:
23453 (*gearScore)[EQUIPMENT_SLOT_HEAD] = std::max((*gearScore)[EQUIPMENT_SLOT_HEAD], level);
23454 break;
23455 // equipped gear score check uses both rings and trinkets for calculation, assume that for bags/banks it is the same
23456 // with keeping second highest score at second slot
23457 case INVTYPE_FINGER:
23459 if ((*gearScore)[EQUIPMENT_SLOT_FINGER1] < level)
23461 (*gearScore)[EQUIPMENT_SLOT_FINGER2] = (*gearScore)[EQUIPMENT_SLOT_FINGER1];
23462 (*gearScore)[EQUIPMENT_SLOT_FINGER1] = level;
23464 else if ((*gearScore)[EQUIPMENT_SLOT_FINGER2] < level)
23465 (*gearScore)[EQUIPMENT_SLOT_FINGER2] = level;
23466 break;
23468 case INVTYPE_TRINKET:
23470 if ((*gearScore)[EQUIPMENT_SLOT_TRINKET1] < level)
23472 (*gearScore)[EQUIPMENT_SLOT_TRINKET2] = (*gearScore)[EQUIPMENT_SLOT_TRINKET1];
23473 (*gearScore)[EQUIPMENT_SLOT_TRINKET1] = level;
23475 else if ((*gearScore)[EQUIPMENT_SLOT_TRINKET2] < level)
23476 (*gearScore)[EQUIPMENT_SLOT_TRINKET2] = level;
23477 break;
23479 case INVTYPE_CLOAK:
23480 (*gearScore)[EQUIPMENT_SLOT_BACK] = std::max((*gearScore)[EQUIPMENT_SLOT_BACK], level);
23481 break;
23482 default:
23483 break;
23487 void Player::SendCurrencies() const
23489 WorldPacket data(SMSG_SEND_CURRENCIES, m_currencies.size() * 4);
23490 data.WriteBits(m_currencies.size(), 23);
23492 for (PlayerCurrenciesMap::const_iterator itr = m_currencies.begin(); itr != m_currencies.end(); ++itr)
23494 uint32 weekCap = GetCurrencyWeekCap(itr->second.currencyEntry);
23495 data.WriteBit(weekCap && itr->second.weekCount);
23496 data.WriteBits(itr->second.flags, 4);
23497 data.WriteBit(weekCap);
23498 data.WriteBit(itr->second.currencyEntry->HasSeasonCount());
23501 for (PlayerCurrenciesMap::const_iterator itr = m_currencies.begin(); itr != m_currencies.end(); ++itr)
23503 data << uint32(floor(itr->second.totalCount / itr->second.currencyEntry->GetPrecision()));
23505 uint32 weekCap = GetCurrencyWeekCap(itr->second.currencyEntry);
23506 if (weekCap)
23507 data << uint32(floor(weekCap / itr->second.currencyEntry->GetPrecision()));
23508 if (itr->second.currencyEntry->HasSeasonCount())
23509 data << uint32(floor(itr->second.seasonCount / itr->second.currencyEntry->GetPrecision()));
23510 data << uint32(itr->first);
23511 if (weekCap && itr->second.weekCount)
23512 data << uint32(floor(itr->second.weekCount / itr->second.currencyEntry->GetPrecision()));
23515 GetSession()->SendPacket(&data);
23518 uint32 Player::GetCurrencyWeekCap(CurrencyTypesEntry const * currency) const
23520 uint32 cap = currency->WeekCap;
23521 switch (currency->ID)
23523 case CURRENCY_CONQUEST_POINTS:
23524 cap = sWorld.getConfig(CONFIG_UINT32_CURRENCY_CONQUEST_POINTS_DEFAULT_WEEK_CAP);
23525 break;
23528 return cap;
23531 void Player::SendCurrencyWeekCap(uint32 id) const
23533 SendCurrencyWeekCap(sCurrencyTypesStore.LookupEntry(id));
23536 void Player::SendCurrencyWeekCap(CurrencyTypesEntry const * currency) const
23538 if (!currency || !IsInWorld() || GetSession()->PlayerLoading())
23539 return;
23541 uint32 cap = GetCurrencyWeekCap(currency);
23542 if (!cap)
23543 return;
23545 WorldPacket packet(SMSG_SET_CURRENCY_WEEK_LIMIT, 8);
23546 packet << uint32(floor(cap / currency->GetPrecision()));
23547 packet << uint32(currency->ID);
23548 GetSession()->SendPacket(&packet);
23551 uint32 Player::GetCurrencyTotalCap(CurrencyTypesEntry const* currency) const
23553 uint32 cap = currency->TotalCap;
23555 return cap;
23558 uint32 Player::GetCurrencyCount(uint32 id) const
23560 PlayerCurrenciesMap::const_iterator itr = m_currencies.find(id);
23561 return itr != m_currencies.end() ? itr->second.totalCount : 0;
23564 uint32 Player::GetCurrencySeasonCount(uint32 id) const
23566 PlayerCurrenciesMap::const_iterator itr = m_currencies.find(id);
23567 return itr != m_currencies.end() ? itr->second.seasonCount : 0;
23570 uint32 Player::GetCurrencyWeekCount(uint32 id) const
23572 PlayerCurrenciesMap::const_iterator itr = m_currencies.find(id);
23573 return itr != m_currencies.end() ? itr->second.weekCount : 0;
23576 void Player::ModifyCurrencyCount(uint32 id, int32 count, bool modifyWeek, bool modifySeason)
23578 if (!count)
23579 return;
23581 CurrencyTypesEntry const * currency = NULL;
23583 int32 oldTotalCount = 0;
23584 int32 oldWeekCount = 0;
23585 PlayerCurrenciesMap::iterator itr = m_currencies.find(id);
23587 bool initWeek = false;
23588 if (itr == m_currencies.end())
23590 currency = sCurrencyTypesStore.LookupEntry(id);
23591 MANGOS_ASSERT(currency);
23593 PlayerCurrency cur;
23594 cur.state = PLAYERCURRENCY_NEW;
23595 cur.totalCount = 0;
23596 cur.weekCount = 0;
23597 cur.seasonCount = 0;
23598 cur.flags = 0;
23599 cur.currencyEntry = currency;
23600 m_currencies[id] = cur;
23601 initWeek = true;
23602 itr = m_currencies.find(id);
23604 else
23606 oldTotalCount = itr->second.totalCount;
23607 oldWeekCount = itr->second.weekCount;
23608 currency = itr->second.currencyEntry;
23611 int32 newTotalCount = oldTotalCount + count;
23612 if (newTotalCount < 0)
23613 newTotalCount = 0;
23615 int32 newWeekCount = oldWeekCount + (modifyWeek && count > 0 ? count : 0);
23616 if (newWeekCount < 0)
23617 newWeekCount = 0;
23619 int32 totalCap = GetCurrencyTotalCap(currency);
23620 if (totalCap && int32(totalCap) < newTotalCount)
23622 int32 delta = newTotalCount - totalCap;
23623 newTotalCount = totalCap;
23624 newWeekCount -= delta;
23627 int32 weekCap = GetCurrencyWeekCap(currency);
23628 if (modifyWeek && weekCap && newWeekCount > weekCap)
23630 int32 delta = newWeekCount - weekCap;
23631 newWeekCount = weekCap;
23632 newTotalCount -= delta;
23634 initWeek &= weekCap != currency->WeekCap;
23636 if (newTotalCount != oldTotalCount)
23638 if (itr->second.state != PLAYERCURRENCY_NEW)
23639 itr->second.state = PLAYERCURRENCY_CHANGED;
23641 itr->second.totalCount = newTotalCount;
23642 itr->second.weekCount = newWeekCount;
23644 int32 diff = newTotalCount - oldTotalCount;
23645 if (diff > 0 && modifySeason)
23646 itr->second.seasonCount += diff;
23648 // probably excessive checks
23649 if (IsInWorld() && !GetSession()->PlayerLoading())
23651 if (diff > 0)
23652 UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_CURRENCY_EARNED, id, newTotalCount);
23654 WorldPacket packet(SMSG_SET_CURRENCY, 13);
23655 bool bit0 = modifyWeek && weekCap && diff > 0;
23656 bool bit1 = currency->HasSeasonCount();
23657 bool bit2 = currency->ID == CURRENCY_CONQUEST_ARENA_META || currency->ID == CURRENCY_CONQUEST_BG_META; // hides message in client when set
23658 packet.WriteBit(bit0);
23659 packet.WriteBit(bit1);
23660 packet.WriteBit(bit2);
23662 if (bit1)
23663 packet << uint32(floor(itr->second.seasonCount / currency->GetPrecision()));
23664 packet << uint32(floor(newTotalCount / currency->GetPrecision()));
23665 packet << uint32(id);
23666 if (bit0)
23667 packet << uint32(floor(newWeekCount / currency->GetPrecision()));
23668 GetSession()->SendPacket(&packet);
23670 // init currency week limit for new currencies
23671 if (initWeek)
23672 SendCurrencyWeekCap(currency);
23674 if (diff > 0)
23675 CurrencyAddedQuestCheck(id);
23676 else
23677 CurrencyRemovedQuestCheck(id);
23680 if (itr->first == CURRENCY_CONQUEST_ARENA_META || itr->first == CURRENCY_CONQUEST_BG_META)
23681 ModifyCurrencyCount(CURRENCY_CONQUEST_POINTS, diff, modifyWeek);
23685 void Player::SetCurrencyCount(uint32 id, uint32 count)
23687 ModifyCurrencyCount(id, int32(count) - GetCurrencyCount(id));
23690 void Player::_LoadCurrencies(QueryResult* result)
23692 // 0 1 2 4 5
23693 // "SELECT id, totalCount, weekCount, seasonCount, flags FROM character_currencies WHERE guid = '%u'"
23695 if (result)
23699 Field* fields = result->Fetch();
23701 uint32 currency_id = fields[0].GetUInt16();
23702 uint32 totalCount = fields[1].GetUInt32();
23703 uint32 weekCount = fields[2].GetUInt32();
23704 uint32 seasonCount = fields[3].GetUInt32();
23705 uint8 flags = fields[4].GetUInt8();
23707 CurrencyTypesEntry const * entry = sCurrencyTypesStore.LookupEntry(currency_id);
23708 if (!entry)
23710 sLog.outError("Player::_LoadCurrencies: %s has not existing currency id %u, removing.", GetGuidStr().c_str(), currency_id);
23711 CharacterDatabase.PExecute("DELETE FROM character_currencies WHERE id = '%u'", currency_id);
23712 continue;
23715 uint32 weekCap = GetCurrencyWeekCap(entry);
23716 uint32 totalCap = GetCurrencyTotalCap(entry);
23718 PlayerCurrency cur;
23720 cur.state = PLAYERCURRENCY_UNCHANGED;
23722 if (totalCap && totalCount > totalCap)
23723 cur.totalCount = totalCap;
23724 else
23725 cur.totalCount = totalCount;
23727 if (weekCap && weekCount > weekCap)
23728 cur.weekCount = weekCap;
23729 else
23730 cur.weekCount = weekCount;
23732 cur.seasonCount = seasonCount;
23734 cur.flags = flags & PLAYERCURRENCY_MASK_USED_BY_CLIENT;
23735 cur.currencyEntry = entry;
23737 m_currencies[currency_id] = cur;
23739 while (result->NextRow());
23743 void Player::_SaveCurrencies()
23745 for (PlayerCurrenciesMap::iterator itr = m_currencies.begin(); itr != m_currencies.end();)
23747 if (itr->second.state == PLAYERCURRENCY_CHANGED)
23748 CharacterDatabase.PExecute("UPDATE `character_currencies` SET `totalCount` = '%u', `weekCount` = '%u', `seasonCount` = '%u', `flags` = '%u' WHERE `guid` = '%u' AND `id` = '%u'", itr->second.totalCount, itr->second.weekCount, itr->second.seasonCount, itr->second.flags, GetGUIDLow(), itr->first);
23749 else if (itr->second.state == PLAYERCURRENCY_NEW)
23750 CharacterDatabase.PExecute("INSERT INTO `character_currencies` (`guid`, `id`, `totalCount`, `weekCount`, `seasonCount`, `flags`) VALUES ('%u', '%u', '%u', '%u', '%u', '%u')", GetGUIDLow(), itr->first, itr->second.totalCount, itr->second.weekCount, itr->second.seasonCount, itr->second.flags);
23752 if (itr->second.state == PLAYERCURRENCY_REMOVED)
23753 m_currencies.erase(itr++);
23754 else
23756 itr->second.state = PLAYERCURRENCY_UNCHANGED;
23757 ++itr;
23762 void Player::SetCurrencyFlags(uint32 currencyId, uint8 flags)
23764 PlayerCurrenciesMap::iterator itr = m_currencies.find(currencyId);
23765 if (itr == m_currencies.end())
23766 return;
23768 itr->second.flags = flags;
23769 itr->second.state = PLAYERCURRENCY_CHANGED;
23772 void Player::ResetCurrencyWeekCounts()
23774 for (PlayerCurrenciesMap::iterator itr = m_currencies.begin(); itr != m_currencies.end(); ++itr)
23776 itr->second.weekCount = 0;
23777 itr->second.state = PLAYERCURRENCY_CHANGED;
23780 WorldPacket data(SMSG_WEEKLY_RESET_CURRENCIES, 0);
23781 SendDirectMessage(&data);
23784 void Player::SendPvPRewards()
23786 // Placeholder
23788 WorldPacket data(SMSG_PVP_REWARDS, 6 * 4);
23789 data << uint32(1650); // rbg conquest cap
23790 data << uint32(0); // total conquest earned
23791 data << uint32(1350); // arena conquest cap
23792 data << uint32(0); // rbg conquest earned
23793 data << uint32(0); // arena conquest earned
23794 data << uint32(1650); // total conquest cap
23796 SendDirectMessage(&data);
23799 void Player::SendRatedBGStats()
23801 // Placeholder
23803 WorldPacket data(SMSG_RATED_BG_STATS, 18 * 4);
23804 for (int i = 0; i < 18; ++i)
23805 data << uint32(0);
23807 SendDirectMessage(&data);
23810 AreaLockStatus Player::GetAreaTriggerLockStatus(AreaTrigger const* at, Difficulty difficulty, uint32& miscRequirement)
23812 miscRequirement = 0;
23814 if (!at)
23815 return AREA_LOCKSTATUS_UNKNOWN_ERROR;
23817 MapEntry const* mapEntry = sMapStore.LookupEntry(at->target_mapId);
23818 if (!mapEntry)
23819 return AREA_LOCKSTATUS_UNKNOWN_ERROR;
23821 bool isRegularTargetMap = !mapEntry->IsDungeon() || GetDifficulty(mapEntry->IsRaid()) == REGULAR_DIFFICULTY;
23823 MapDifficultyEntry const* mapDiff = GetMapDifficultyData(at->target_mapId, difficulty);
23824 if (mapEntry->IsDungeon() && !mapDiff)
23825 return AREA_LOCKSTATUS_MISSING_DIFFICULTY;
23827 // Expansion requirement
23828 if (GetSession()->Expansion() < mapEntry->Expansion())
23830 miscRequirement = mapEntry->Expansion();
23831 return AREA_LOCKSTATUS_INSUFFICIENT_EXPANSION;
23834 // Gamemaster can always enter
23835 if (isGameMaster())
23836 return AREA_LOCKSTATUS_OK;
23838 // Level Requirements
23839 if (getLevel() < at->requiredLevel && !sWorld.getConfig(CONFIG_BOOL_INSTANCE_IGNORE_LEVEL))
23841 miscRequirement = at->requiredLevel;
23842 return AREA_LOCKSTATUS_TOO_LOW_LEVEL;
23844 if (!isRegularTargetMap && !sWorld.getConfig(CONFIG_BOOL_INSTANCE_IGNORE_LEVEL) && getLevel() < uint32(maxLevelForExpansion[mapEntry->Expansion()]))
23846 miscRequirement = maxLevelForExpansion[mapEntry->Expansion()];
23847 return AREA_LOCKSTATUS_TOO_LOW_LEVEL;
23850 // Raid Requirements
23851 if (mapEntry->IsRaid() && !sWorld.getConfig(CONFIG_BOOL_INSTANCE_IGNORE_RAID))
23852 if (!GetGroup() || !GetGroup()->isRaidGroup())
23853 return AREA_LOCKSTATUS_RAID_LOCKED;
23855 // Item Requirements: must have requiredItem OR requiredItem2, report the first one that's missing
23856 if (at->requiredItem)
23858 if (!HasItemCount(at->requiredItem, 1) &&
23859 (!at->requiredItem2 || !HasItemCount(at->requiredItem2, 1)))
23861 miscRequirement = at->requiredItem;
23862 return AREA_LOCKSTATUS_MISSING_ITEM;
23865 else if (at->requiredItem2 && !HasItemCount(at->requiredItem2, 1))
23867 miscRequirement = at->requiredItem2;
23868 return AREA_LOCKSTATUS_MISSING_ITEM;
23870 // Heroic item requirements
23871 if (!isRegularTargetMap && at->heroicKey)
23873 if (!HasItemCount(at->heroicKey, 1) && (!at->heroicKey2 || !HasItemCount(at->heroicKey2, 1)))
23875 miscRequirement = at->heroicKey;
23876 return AREA_LOCKSTATUS_MISSING_ITEM;
23879 else if (!isRegularTargetMap && at->heroicKey2 && !HasItemCount(at->heroicKey2, 1))
23881 miscRequirement = at->heroicKey2;
23882 return AREA_LOCKSTATUS_MISSING_ITEM;
23885 // Quest Requirements
23886 if (isRegularTargetMap && at->requiredQuest && !GetQuestRewardStatus(at->requiredQuest))
23888 miscRequirement = at->requiredQuest;
23889 return AREA_LOCKSTATUS_QUEST_NOT_COMPLETED;
23891 if (!isRegularTargetMap && at->requiredQuestHeroic && !GetQuestRewardStatus(at->requiredQuestHeroic))
23893 miscRequirement = at->requiredQuestHeroic;
23894 return AREA_LOCKSTATUS_QUEST_NOT_COMPLETED;
23897 // If the map is not created, assume it is possible to enter it.
23898 DungeonPersistentState* state = GetBoundInstanceSaveForSelfOrGroup(at->target_mapId);
23899 Map* map = sMapMgr.FindMap(at->target_mapId, state ? state->GetInstanceId() : 0);
23901 // ToDo add achievement check
23903 // Map's state check
23904 if (map && map->IsDungeon())
23906 // cannot enter if the instance is full (player cap), GMs don't count
23907 if (((DungeonMap*)map)->GetPlayersCountExceptGMs() >= ((DungeonMap*)map)->GetMaxPlayers())
23908 return AREA_LOCKSTATUS_INSTANCE_IS_FULL;
23910 // In Combat check
23911 if (map && map->GetInstanceData() && map->GetInstanceData()->IsEncounterInProgress())
23912 return AREA_LOCKSTATUS_ZONE_IN_COMBAT;
23914 // Bind Checks
23915 InstancePlayerBind* pBind = GetBoundInstance(at->target_mapId, GetDifficulty(mapEntry->IsRaid()));
23916 if (pBind && pBind->perm && pBind->state != state)
23917 return AREA_LOCKSTATUS_HAS_BIND;
23918 if (pBind && pBind->perm && pBind->state != map->GetPersistentState())
23919 return AREA_LOCKSTATUS_HAS_BIND;
23922 return AREA_LOCKSTATUS_OK;
23925 const uint32 armorSpecToClass[MAX_CLASSES] =
23928 86526, // CLASS_WARRIOR
23929 86525, // CLASS_PALADIN
23930 86528, // CLASS_HUNTER
23931 86531, // CLASS_ROGUE
23932 0, // CLASS_PRIEST
23933 86524, // CLASS_DEATH_KNIGHT
23934 86529, // CLASS_SHAMAN
23935 0, // CLASS_MAGE
23936 0, // CLASS_WARLOCK
23937 0, // CLASS_UNK2
23938 86530, // CLASS_DRUID
23941 #define MAX_ARMOR_SPECIALIZATION_SPELLS 18
23942 struct armorSpecToTabInfo
23944 uint32 spellId;
23945 uint8 Class;
23946 uint16 tab;
23949 const armorSpecToTabInfo armorSpecToTab[MAX_ARMOR_SPECIALIZATION_SPELLS] =
23951 { 86537, CLASS_DEATH_KNIGHT, 398 }, // blood
23952 { 86113, CLASS_DEATH_KNIGHT, 399 }, // frost
23953 { 86536, CLASS_DEATH_KNIGHT, 400 }, // unholy
23954 { 86093, CLASS_DRUID, 752 }, // balance
23955 { 86096, CLASS_DRUID, 750 }, // feral
23956 { 86097, CLASS_DRUID, 750 }, // feral
23957 { 86104, CLASS_DRUID, 748 }, // resto
23958 { 86538, CLASS_HUNTER, 0 }, //
23959 { 86103, CLASS_PALADIN, 831 }, // holy
23960 { 86102, CLASS_PALADIN, 839 }, // prot
23961 { 86539, CLASS_PALADIN, 855 }, // retro
23962 { 86092, CLASS_ROGUE, 0 }, //
23963 { 86100, CLASS_SHAMAN, 261 }, // elem
23964 { 86099, CLASS_SHAMAN, 263 }, // ench
23965 { 86108, CLASS_SHAMAN, 262 }, // restor
23966 { 86101, CLASS_WARRIOR, 746 }, // arms
23967 { 86110, CLASS_WARRIOR, 815 }, // fury
23968 { 86535, CLASS_WARRIOR, 845 }, // prot
23971 void Player::UpdateArmorSpecializations()
23973 uint32 specPassive = armorSpecToClass[getClass()];
23974 // return class has no armor specialization
23975 if (!specPassive)
23976 return;
23978 for (int i = 0; i < MAX_ARMOR_SPECIALIZATION_SPELLS; ++i)
23980 if (armorSpecToTab[i].Class != getClass())
23981 continue;
23983 SpellEntry const * spellProto = sSpellStore.LookupEntry(armorSpecToTab[i].spellId);
23984 if (!spellProto || !spellProto->HasAttribute(SPELL_ATTR_EX8_ARMOR_SPECIALIZATION))
23986 sLog.outError("Player::UpdateArmorSpecializations: unexistent or wrong spell %u for class %u",
23987 armorSpecToTab[i].spellId, armorSpecToTab[i].Class);
23988 continue;
23991 // remove if base passive is unlearned
23992 if (!HasSpell(specPassive))
23994 RemoveAurasDueToSpell(spellProto->Id);
23995 continue;
23998 SpellAuraHolder* holder = GetSpellAuraHolder(spellProto->Id);
23999 if (!holder)
24001 // cast absent spells that may be missing due to shapeshift form dependency
24002 CastSpell(this, spellProto->Id, true);
24003 continue;
24006 Aura* aura = holder->GetAuraByEffectIndex(EFFECT_INDEX_0);
24007 if (!aura)
24008 continue;
24010 // recalculate modifier depending on current tree
24011 aura->ApplyModifier(false, false);
24012 aura->GetModifier()->m_amount = CalculateSpellDamage(this, spellProto, EFFECT_INDEX_0);
24013 aura->ApplyModifier(true, false);
24017 bool Player::FitArmorSpecializationRules(SpellEntry const * spellProto) const
24019 if (!spellProto || !spellProto->HasAttribute(SPELL_ATTR_EX8_ARMOR_SPECIALIZATION))
24020 return true;
24022 int i = 0;
24023 for (; i < MAX_ARMOR_SPECIALIZATION_SPELLS; ++i)
24025 if (spellProto->Id == armorSpecToTab[i].spellId)
24027 if (!armorSpecToTab[i].tab && m_talentsPrimaryTree[m_activeSpec] == 0 ||
24028 armorSpecToTab[i].tab && armorSpecToTab[i].tab != m_talentsPrimaryTree[m_activeSpec])
24029 return false;
24031 break;
24035 if (i == MAX_ARMOR_SPECIALIZATION_SPELLS)
24036 return false;
24038 if (!HasSpell(armorSpecToClass[getClass()]))
24039 return false;
24041 if (SpellEquippedItemsEntry const * itemsEntry = spellProto->GetSpellEquippedItems())
24043 // there spells check items with inventory types which are in EquippedItemInventoryTypeMask
24044 uint32 inventoryTypeMask = itemsEntry->EquippedItemInventoryTypeMask;
24045 // get slots that should be check for item presence and SpellEquippedItemsEntry match
24046 uint32 slotMask = 0;
24047 uint8 slots[4];
24048 for (int i = 0; i < MAX_INVTYPE; ++i)
24050 if (inventoryTypeMask & (1 << i))
24052 if (!GetSlotsForInventoryType(i, slots))
24053 continue;
24054 for (int j = 0; j < 4; ++j)
24055 if (slots[j] != NULL_SLOT)
24056 slotMask |= 1 << slots[j];
24060 for (int i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i)
24062 if (slotMask & (1 << i))
24064 Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
24065 // item must be present for specialization to work
24066 if (!item)
24067 return false;
24069 if (item->GetProto()->Class != itemsEntry->EquippedItemClass)
24070 return false;
24072 if (((1 << item->GetProto()->SubClass) & itemsEntry->EquippedItemSubClassMask) == 0)
24073 return false;
24078 return true;
24081 void Player::SendPetitionSignResult(ObjectGuid petitionGuid, Player* player, uint32 result)
24083 WorldPacket data(SMSG_PETITION_SIGN_RESULTS, 8 + 8 + 4);
24084 data << petitionGuid;
24085 data << player->GetObjectGuid();
24086 data << uint32(result);
24087 GetSession()->SendPacket(&data);
24090 void Player::SendPetitionTurnInResult(uint32 result)
24092 WorldPacket data(SMSG_TURN_IN_PETITION_RESULTS, 4);
24093 data << uint32(result);
24094 GetSession()->SendPacket(&data);