server/src/game/Group.cpp
VladimirMangos 9c96949da9 [10713] player/group instance binding fixes.
* Use anum instead raw uint8 type in args
* Fixed crash when gm at continent invite to group gm in instance
  and then teleport to instance using .goname.
  When group leader teleport to instance it must get group bind instead solo bind.
* In other semilar cases detection report error as before but replace solo by group bind instead
  assert crash at enter to map.
2010-11-10 04:36:01 +03:00

1920 lines
68 KiB
C++

/*
* Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "Common.h"
#include "Opcodes.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "Player.h"
#include "World.h"
#include "ObjectMgr.h"
#include "ObjectGuid.h"
#include "Group.h"
#include "Formulas.h"
#include "ObjectAccessor.h"
#include "BattleGround.h"
#include "MapManager.h"
#include "InstanceSaveMgr.h"
#include "MapInstanced.h"
#include "Util.h"
#include "LootMgr.h"
#define LOOT_ROLL_TIMEOUT (1*MINUTE*IN_MILLISECONDS)
//===================================================
//============== Roll ===============================
//===================================================
void Roll::targetObjectBuildLink()
{
// called from link()
getTarget()->addLootValidatorRef(this);
}
void Roll::CalculateCommonVoteMask(uint32 max_enchanting_skill)
{
m_commonVoteMask = ROLL_VOTE_MASK_ALL;
ItemPrototype const* itemProto = ObjectMgr::GetItemPrototype(itemid);
if (itemProto->Flags2 & ITEM_FLAG2_NEED_ROLL_DISABLED)
m_commonVoteMask = RollVoteMask(m_commonVoteMask & ~ROLL_VOTE_MASK_NEED);
if (!itemProto->DisenchantID || uint32(itemProto->RequiredDisenchantSkill) > max_enchanting_skill)
m_commonVoteMask = RollVoteMask(m_commonVoteMask & ~ROLL_VOTE_MASK_DISENCHANT);
}
RollVoteMask Roll::GetVoteMaskFor(Player* player) const
{
ItemPrototype const* itemProto = ObjectMgr::GetItemPrototype(itemid);
// In NEED_BEFORE_GREED need disabled for non-usable item for player
if (m_method != NEED_BEFORE_GREED || player->CanUseItem(itemProto) == EQUIP_ERR_OK)
return m_commonVoteMask;
else
return RollVoteMask(m_commonVoteMask & ~ROLL_VOTE_MASK_NEED);
}
//===================================================
//============== Group ==============================
//===================================================
Group::Group() : m_Id(0), m_groupType(GROUPTYPE_NORMAL),
m_dungeonDifficulty(REGULAR_DIFFICULTY), m_raidDifficulty(REGULAR_DIFFICULTY),
m_bgGroup(NULL), m_lootMethod(FREE_FOR_ALL), m_lootThreshold(ITEM_QUALITY_UNCOMMON),
m_subGroupsCounts(NULL)
{
}
Group::~Group()
{
if(m_bgGroup)
{
DEBUG_LOG("Group::~Group: battleground group being deleted.");
if(m_bgGroup->GetBgRaid(ALLIANCE) == this)
m_bgGroup->SetBgRaid(ALLIANCE, NULL);
else if(m_bgGroup->GetBgRaid(HORDE) == this)
m_bgGroup->SetBgRaid(HORDE, NULL);
else
sLog.outError("Group::~Group: battleground group is not linked to the correct battleground.");
}
Rolls::iterator itr;
while(!RollId.empty())
{
itr = RollId.begin();
Roll *r = *itr;
RollId.erase(itr);
delete(r);
}
// it is undefined whether objectmgr (which stores the groups) or instancesavemgr
// will be unloaded first so we must be prepared for both cases
// this may unload some instance saves
for(uint8 i = 0; i < MAX_DIFFICULTY; ++i)
for(BoundInstancesMap::iterator itr2 = m_boundInstances[i].begin(); itr2 != m_boundInstances[i].end(); ++itr2)
itr2->second.save->RemoveGroup(this);
// Sub group counters clean up
if (m_subGroupsCounts)
delete[] m_subGroupsCounts;
}
bool Group::Create(ObjectGuid guid, const char * name)
{
m_leaderGuid = guid;
m_leaderName = name;
m_groupType = isBGGroup() ? GROUPTYPE_BGRAID : GROUPTYPE_NORMAL;
if (m_groupType & GROUPTYPE_RAID)
_initRaidSubGroupsCounter();
m_lootMethod = GROUP_LOOT;
m_lootThreshold = ITEM_QUALITY_UNCOMMON;
m_looterGuid = guid;
m_dungeonDifficulty = DUNGEON_DIFFICULTY_NORMAL;
m_raidDifficulty = RAID_DIFFICULTY_10MAN_NORMAL;
if (!isBGGroup())
{
m_Id = sObjectMgr.GenerateGroupId();
Player *leader = sObjectMgr.GetPlayer(guid);
if(leader)
{
m_dungeonDifficulty = leader->GetDungeonDifficulty();
m_raidDifficulty = leader->GetRaidDifficulty();
}
Player::ConvertInstancesToGroup(leader, this, guid);
// store group in database
CharacterDatabase.BeginTransaction();
CharacterDatabase.PExecute("DELETE FROM groups WHERE groupId ='%u'", m_Id);
CharacterDatabase.PExecute("DELETE FROM group_member WHERE groupId ='%u'", m_Id);
CharacterDatabase.PExecute("INSERT INTO groups (groupId,leaderGuid,mainTank,mainAssistant,lootMethod,looterGuid,lootThreshold,icon1,icon2,icon3,icon4,icon5,icon6,icon7,icon8,groupType,difficulty,raiddifficulty) "
"VALUES ('%u','%u','%u','%u','%u','%u','%u','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','" UI64FMTD "','%u','%u','%u')",
m_Id, m_leaderGuid.GetCounter(), m_mainTankGuid.GetCounter(), m_mainAssistantGuid.GetCounter(), uint32(m_lootMethod),
m_looterGuid.GetCounter(), uint32(m_lootThreshold),
m_targetIcons[0].GetRawValue(), m_targetIcons[1].GetRawValue(),
m_targetIcons[2].GetRawValue(), m_targetIcons[3].GetRawValue(),
m_targetIcons[4].GetRawValue(), m_targetIcons[5].GetRawValue(),
m_targetIcons[6].GetRawValue(), m_targetIcons[7].GetRawValue(),
uint8(m_groupType), uint32(m_dungeonDifficulty), uint32(m_raidDifficulty));
}
if (!AddMember(guid, name))
return false;
if (!isBGGroup())
CharacterDatabase.CommitTransaction();
return true;
}
bool Group::LoadGroupFromDB(Field* fields)
{
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// result = CharacterDatabase.Query("SELECT mainTank, mainAssistant, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raiddifficulty, leaderGuid, groupId FROM groups");
m_Id = fields[17].GetUInt32();
m_leaderGuid = ObjectGuid(HIGHGUID_PLAYER, fields[16].GetUInt32());
// group leader not exist
if (!sObjectMgr.GetPlayerNameByGUID(m_leaderGuid, m_leaderName))
return false;
m_groupType = GroupType(fields[13].GetUInt8());
if (m_groupType & GROUPTYPE_RAID)
_initRaidSubGroupsCounter();
uint32 diff = fields[14].GetUInt8();
if (diff >= MAX_DUNGEON_DIFFICULTY)
diff = DUNGEON_DIFFICULTY_NORMAL;
m_dungeonDifficulty = Difficulty(diff);
uint32 r_diff = fields[15].GetUInt8();
if (r_diff >= MAX_RAID_DIFFICULTY)
r_diff = RAID_DIFFICULTY_10MAN_NORMAL;
m_raidDifficulty = Difficulty(r_diff);
m_mainTankGuid = ObjectGuid(HIGHGUID_PLAYER, fields[0].GetUInt32());
m_mainAssistantGuid = ObjectGuid(HIGHGUID_PLAYER, fields[1].GetUInt32());
m_lootMethod = LootMethod(fields[2].GetUInt8());
m_looterGuid = ObjectGuid(HIGHGUID_PLAYER, fields[3].GetUInt32());
m_lootThreshold = ItemQualities(fields[4].GetUInt16());
for(int i = 0; i < TARGET_ICON_COUNT; ++i)
m_targetIcons[i] = ObjectGuid(fields[5+i].GetUInt64());
return true;
}
bool Group::LoadMemberFromDB(uint32 guidLow, uint8 subgroup, bool assistant)
{
MemberSlot member;
member.guid = ObjectGuid(HIGHGUID_PLAYER, guidLow);
// skip nonexistent member
if (!sObjectMgr.GetPlayerNameByGUID(member.guid, member.name))
return false;
member.group = subgroup;
member.assistant = assistant;
m_memberSlots.push_back(member);
SubGroupCounterIncrease(subgroup);
return true;
}
void Group::ConvertToRaid()
{
m_groupType = GroupType(m_groupType | GROUPTYPE_RAID);
_initRaidSubGroupsCounter();
if(!isBGGroup())
CharacterDatabase.PExecute("UPDATE groups SET groupType = %u WHERE groupId='%u'", uint8(m_groupType), m_Id);
SendUpdate();
// update quest related GO states (quest activity dependent from raid membership)
for(member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr)
if(Player* player = sObjectMgr.GetPlayer(citr->guid))
player->UpdateForQuestWorldObjects();
}
bool Group::AddInvite(Player *player)
{
if( !player || player->GetGroupInvite() )
return false;
Group* group = player->GetGroup();
if( group && group->isBGGroup() )
group = player->GetOriginalGroup();
if( group )
return false;
RemoveInvite(player);
m_invitees.insert(player);
player->SetGroupInvite(this);
return true;
}
bool Group::AddLeaderInvite(Player *player)
{
if(!AddInvite(player))
return false;
m_leaderGuid = player->GetObjectGuid();
m_leaderName = player->GetName();
return true;
}
uint32 Group::RemoveInvite(Player *player)
{
m_invitees.erase(player);
player->SetGroupInvite(NULL);
return GetMembersCount();
}
void Group::RemoveAllInvites()
{
for(InvitesList::iterator itr = m_invitees.begin(); itr!=m_invitees.end(); ++itr)
(*itr)->SetGroupInvite(NULL);
m_invitees.clear();
}
Player* Group::GetInvited(ObjectGuid guid) const
{
for(InvitesList::const_iterator itr = m_invitees.begin(); itr != m_invitees.end(); ++itr)
if ((*itr)->GetObjectGuid() == guid)
return (*itr);
return NULL;
}
Player* Group::GetInvited(const std::string& name) const
{
for(InvitesList::const_iterator itr = m_invitees.begin(); itr != m_invitees.end(); ++itr)
{
if((*itr)->GetName() == name)
return (*itr);
}
return NULL;
}
bool Group::AddMember(ObjectGuid guid, const char* name)
{
if (!_addMember(guid, name))
return false;
SendUpdate();
if (Player *player = sObjectMgr.GetPlayer(guid))
{
if (!IsLeader(player->GetObjectGuid()) && !isBGGroup())
{
// reset the new member's instances, unless he is currently in one of them
// including raid/heroic instances that they are not permanently bound to!
player->ResetInstances(INSTANCE_RESET_GROUP_JOIN,false);
player->ResetInstances(INSTANCE_RESET_GROUP_JOIN,true);
if (player->getLevel() >= LEVELREQUIREMENT_HEROIC)
{
if (player->GetDungeonDifficulty() != GetDungeonDifficulty())
{
player->SetDungeonDifficulty(GetDungeonDifficulty());
player->SendDungeonDifficulty(true);
}
if (player->GetRaidDifficulty() != GetRaidDifficulty())
{
player->SetRaidDifficulty(GetRaidDifficulty());
player->SendRaidDifficulty(true);
}
}
}
player->SetGroupUpdateFlag(GROUP_UPDATE_FULL);
UpdatePlayerOutOfRange(player);
// quest related GO state dependent from raid membership
if(isRaidGroup())
player->UpdateForQuestWorldObjects();
}
return true;
}
uint32 Group::RemoveMember(ObjectGuid guid, uint8 method)
{
// remove member and change leader (if need) only if strong more 2 members _before_ member remove
if (GetMembersCount() > uint32(isBGGroup() ? 1 : 2)) // in BG group case allow 1 members group
{
bool leaderChanged = _removeMember(guid);
if (Player *player = sObjectMgr.GetPlayer( guid ))
{
// quest related GO state dependent from raid membership
if (isRaidGroup())
player->UpdateForQuestWorldObjects();
WorldPacket data;
if (method == 1)
{
data.Initialize( SMSG_GROUP_UNINVITE, 0 );
player->GetSession()->SendPacket( &data );
}
//we already removed player from group and in player->GetGroup() is his original group!
if (Group* group = player->GetGroup())
{
group->SendUpdate();
}
else
{
data.Initialize(SMSG_GROUP_LIST, 1+1+1+1+8+4+4+8);
data << uint8(0x10) << uint8(0) << uint8(0) << uint8(0);
data << uint64(0) << uint32(0) << uint32(0) << uint64(0);
player->GetSession()->SendPacket(&data);
}
_homebindIfInstance(player);
}
if (leaderChanged)
{
WorldPacket data(SMSG_GROUP_SET_LEADER, (m_memberSlots.front().name.size()+1));
data << m_memberSlots.front().name;
BroadcastPacket(&data, true);
}
SendUpdate();
}
// if group before remove <= 2 disband it
else
Disband(true);
return m_memberSlots.size();
}
void Group::ChangeLeader(ObjectGuid guid)
{
member_citerator slot = _getMemberCSlot(guid);
if (slot == m_memberSlots.end())
return;
_setLeader(guid);
WorldPacket data(SMSG_GROUP_SET_LEADER, slot->name.size()+1);
data << slot->name;
BroadcastPacket(&data, true);
SendUpdate();
}
void Group::Disband(bool hideDestroy)
{
Player *player;
for(member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr)
{
player = sObjectMgr.GetPlayer(citr->guid);
if(!player)
continue;
//we cannot call _removeMember because it would invalidate member iterator
//if we are removing player from battleground raid
if( isBGGroup() )
player->RemoveFromBattleGroundRaid();
else
{
//we can remove player who is in battleground from his original group
if( player->GetOriginalGroup() == this )
player->SetOriginalGroup(NULL);
else
player->SetGroup(NULL);
}
// quest related GO state dependent from raid membership
if(isRaidGroup())
player->UpdateForQuestWorldObjects();
if(!player->GetSession())
continue;
WorldPacket data;
if(!hideDestroy)
{
data.Initialize(SMSG_GROUP_DESTROYED, 0);
player->GetSession()->SendPacket(&data);
}
//we already removed player from group and in player->GetGroup() is his original group, send update
if( Group* group = player->GetGroup() )
{
group->SendUpdate();
}
else
{
data.Initialize(SMSG_GROUP_LIST, 1+1+1+1+8+4+4+8);
data << uint8(0x10) << uint8(0) << uint8(0) << uint8(0);
data << uint64(0) << uint32(0) << uint32(0) << uint64(0);
player->GetSession()->SendPacket(&data);
}
_homebindIfInstance(player);
}
RollId.clear();
m_memberSlots.clear();
RemoveAllInvites();
if(!isBGGroup())
{
CharacterDatabase.BeginTransaction();
CharacterDatabase.PExecute("DELETE FROM groups WHERE groupId='%u'", m_Id);
CharacterDatabase.PExecute("DELETE FROM group_member WHERE groupId='%u'", m_Id);
CharacterDatabase.CommitTransaction();
ResetInstances(INSTANCE_RESET_GROUP_DISBAND, false, NULL);
ResetInstances(INSTANCE_RESET_GROUP_DISBAND, true, NULL);
}
m_leaderGuid.Clear();
m_leaderName = "";
}
/*********************************************************/
/*** LOOT SYSTEM ***/
/*********************************************************/
void Group::SendLootStartRoll(uint32 CountDown, uint32 mapid, const Roll &r)
{
WorldPacket data(SMSG_LOOT_START_ROLL, (8+4+4+4+4+4+4+1));
data << r.lootedTargetGUID; // creature guid what we're looting
data << uint32(mapid); // 3.3.3 mapid
data << uint32(r.itemSlot); // item slot in loot
data << uint32(r.itemid); // the itemEntryId for the item that shall be rolled for
data << uint32(r.itemRandomSuffix); // randomSuffix
data << uint32(r.itemRandomPropId); // item random property ID
data << uint32(r.itemCount); // items in stack
data << uint32(CountDown); // the countdown time to choose "need" or "greed"
size_t voteMaskPos = data.wpos();
data << uint8(0); // roll type mask, allowed choices (placeholder)
for (Roll::PlayerVote::const_iterator itr = r.playerVote.begin(); itr != r.playerVote.end(); ++itr)
{
Player *p = sObjectMgr.GetPlayer(itr->first);
if(!p || !p->GetSession())
continue;
if(itr->second == ROLL_NOT_VALID)
continue;
// dependent from player
RollVoteMask mask = r.GetVoteMaskFor(p);
data.put<uint8>(voteMaskPos,uint8(mask));
p->GetSession()->SendPacket( &data );
}
}
void Group::SendLootRoll(ObjectGuid const& targetGuid, uint8 rollNumber, uint8 rollType, const Roll &r)
{
WorldPacket data(SMSG_LOOT_ROLL, (8+4+8+4+4+4+1+1+1));
data << r.lootedTargetGUID; // creature guid what we're looting
data << uint32(r.itemSlot); // unknown, maybe amount of players, or item slot in loot
data << targetGuid;
data << uint32(r.itemid); // the itemEntryId for the item that shall be rolled for
data << uint32(r.itemRandomSuffix); // randomSuffix
data << uint32(r.itemRandomPropId); // Item random property ID
data << uint8(rollNumber); // 0: "Need for: [item name]" > 127: "you passed on: [item name]" Roll number
data << uint8(rollType); // 0: "Need for: [item name]" 0: "You have selected need for [item name] 1: need roll 2: greed roll
data << uint8(0); // auto pass on loot
for( Roll::PlayerVote::const_iterator itr = r.playerVote.begin(); itr != r.playerVote.end(); ++itr)
{
Player *p = sObjectMgr.GetPlayer(itr->first);
if(!p || !p->GetSession())
continue;
if(itr->second != ROLL_NOT_VALID)
p->GetSession()->SendPacket( &data );
}
}
void Group::SendLootRollWon(ObjectGuid const& targetGuid, uint8 rollNumber, RollVote rollType, const Roll &r)
{
WorldPacket data(SMSG_LOOT_ROLL_WON, (8+4+4+4+4+8+1+1));
data << r.lootedTargetGUID; // creature guid what we're looting
data << uint32(r.itemSlot); // item slot in loot
data << uint32(r.itemid); // the itemEntryId for the item that shall be rolled for
data << uint32(r.itemRandomSuffix); // randomSuffix
data << uint32(r.itemRandomPropId); // Item random property
data << targetGuid; // guid of the player who won.
data << uint8(rollNumber); // rollnumber related to SMSG_LOOT_ROLL
data << uint8(rollType); // Rolltype related to SMSG_LOOT_ROLL
for( Roll::PlayerVote::const_iterator itr = r.playerVote.begin(); itr != r.playerVote.end(); ++itr)
{
Player *p = sObjectMgr.GetPlayer(itr->first);
if(!p || !p->GetSession())
continue;
if(itr->second != ROLL_NOT_VALID)
p->GetSession()->SendPacket( &data );
}
}
void Group::SendLootAllPassed(Roll const& r)
{
WorldPacket data(SMSG_LOOT_ALL_PASSED, (8+4+4+4+4));
data << r.lootedTargetGUID; // creature guid what we're looting
data << uint32(r.itemSlot); // item slot in loot
data << uint32(r.itemid); // The itemEntryId for the item that shall be rolled for
data << uint32(r.itemRandomPropId); // Item random property ID
data << uint32(r.itemRandomSuffix); // Item random suffix ID
for( Roll::PlayerVote::const_iterator itr=r.playerVote.begin(); itr!=r.playerVote.end(); ++itr)
{
Player *p = sObjectMgr.GetPlayer(itr->first);
if(!p || !p->GetSession())
continue;
if(itr->second != ROLL_NOT_VALID)
p->GetSession()->SendPacket( &data );
}
}
void Group::GroupLoot(Creature *creature, Loot *loot)
{
uint32 maxEnchantingSkill = GetMaxSkillValueForGroup(SKILL_ENCHANTING);
for(uint8 itemSlot = 0; itemSlot < loot->items.size(); ++itemSlot)
{
LootItem& lootItem = loot->items[itemSlot];
ItemPrototype const *itemProto = ObjectMgr::GetItemPrototype(lootItem.itemid);
if (!itemProto)
{
DEBUG_LOG("Group::GroupLoot: missing item prototype for item with id: %d", lootItem.itemid);
continue;
}
//roll for over-threshold item if it's one-player loot
if (itemProto->Quality >= uint32(m_lootThreshold) && !lootItem.freeforall)
StartLootRool(creature, GROUP_LOOT, loot, itemSlot, maxEnchantingSkill);
else
lootItem.is_underthreshold = 1;
}
}
void Group::NeedBeforeGreed(Creature *creature, Loot *loot)
{
uint32 maxEnchantingSkill = GetMaxSkillValueForGroup(SKILL_ENCHANTING);
for(uint8 itemSlot = 0; itemSlot < loot->items.size(); ++itemSlot)
{
LootItem& lootItem = loot->items[itemSlot];
ItemPrototype const *itemProto = ObjectMgr::GetItemPrototype(lootItem.itemid);
if (!itemProto)
{
DEBUG_LOG("Group::NeedBeforeGreed: missing item prototype for item with id: %d", lootItem.itemid);
continue;
}
//only roll for one-player items, not for ones everyone can get
if (itemProto->Quality >= uint32(m_lootThreshold) && !lootItem.freeforall)
StartLootRool(creature, NEED_BEFORE_GREED, loot, itemSlot, maxEnchantingSkill);
else
lootItem.is_underthreshold = 1;
}
}
void Group::MasterLoot(Creature *creature, Loot* loot)
{
for (LootItemList::iterator i=loot->items.begin(); i != loot->items.end(); ++i)
{
ItemPrototype const *item = ObjectMgr::GetItemPrototype(i->itemid);
if (!item)
continue;
if (item->Quality < uint32(m_lootThreshold))
i->is_underthreshold = 1;
}
uint32 real_count = 0;
WorldPacket data(SMSG_LOOT_MASTER_LIST, 330);
data << uint8(GetMembersCount());
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player *looter = itr->getSource();
if (!looter->IsInWorld())
continue;
if (looter->IsWithinDist(creature, sWorld.getConfig(CONFIG_FLOAT_GROUP_XP_DISTANCE), false))
{
data << looter->GetObjectGuid();
++real_count;
}
}
data.put<uint8>(0, real_count);
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player *looter = itr->getSource();
if (looter->IsWithinDist(creature, sWorld.getConfig(CONFIG_FLOAT_GROUP_XP_DISTANCE), false))
looter->GetSession()->SendPacket(&data);
}
}
bool Group::CountRollVote(Player* player, ObjectGuid const& lootedTarget, uint32 itemSlot, RollVote vote)
{
Rolls::iterator rollI = RollId.begin();
for (; rollI != RollId.end(); ++rollI)
if ((*rollI)->isValid() && (*rollI)->lootedTargetGUID == lootedTarget && (*rollI)->itemSlot == itemSlot)
break;
if (rollI == RollId.end())
return false;
// possible cheating
RollVoteMask voteMask = (*rollI)->GetVoteMaskFor(player);
if ((voteMask & (1 << vote)) == 0)
return false;
CountRollVote(player->GetObjectGuid(), rollI, vote); // result not related this function result meaning, ignore
return true;
}
bool Group::CountRollVote(ObjectGuid const& playerGUID, Rolls::iterator& rollI, RollVote vote)
{
Roll* roll = *rollI;
Roll::PlayerVote::iterator itr = roll->playerVote.find(playerGUID);
// this condition means that player joins to the party after roll begins
if (itr == roll->playerVote.end())
return true; // result used for need iterator ++, so avoid for end of list
if (roll->getLoot())
if (roll->getLoot()->items.empty())
return false;
switch (vote)
{
case ROLL_PASS: // Player choose pass
{
SendLootRoll(playerGUID, 128, 128, *roll);
++roll->totalPass;
itr->second = ROLL_PASS;
break;
}
case ROLL_NEED: // player choose Need
{
SendLootRoll(playerGUID, 0, 0, *roll);
++roll->totalNeed;
itr->second = ROLL_NEED;
break;
}
case ROLL_GREED: // player choose Greed
{
SendLootRoll(playerGUID, 128, ROLL_GREED, *roll);
++roll->totalGreed;
itr->second = ROLL_GREED;
break;
}
case ROLL_DISENCHANT: // player choose Disenchant
{
SendLootRoll(playerGUID, 128, ROLL_DISENCHANT, *roll);
++roll->totalGreed;
itr->second = ROLL_DISENCHANT;
break;
}
default: // Roll removed case
break;
}
if (roll->totalPass + roll->totalNeed + roll->totalGreed >= roll->totalPlayersRolling)
{
CountTheRoll(rollI);
return true;
}
return false;
}
void Group::StartLootRool(Creature* lootTarget, LootMethod method, Loot* loot, uint8 itemSlot, uint32 maxEnchantingSkill)
{
if (itemSlot >= loot->items.size())
return;
LootItem const& lootItem = loot->items[itemSlot];
Roll* r = new Roll(lootTarget->GetGUID(), method, lootItem);
//a vector is filled with only near party members
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player *playerToRoll = itr->getSource();
if(!playerToRoll || !playerToRoll->GetSession())
continue;
if (lootItem.AllowedForPlayer(playerToRoll))
{
if (playerToRoll->IsWithinDist(lootTarget, sWorld.getConfig(CONFIG_FLOAT_GROUP_XP_DISTANCE), false))
{
r->playerVote[playerToRoll->GetObjectGuid()] = ROLL_NOT_EMITED_YET;
++r->totalPlayersRolling;
}
}
}
if (r->totalPlayersRolling > 0) // has looters
{
r->setLoot(loot);
r->itemSlot = itemSlot;
if (r->totalPlayersRolling == 1) // single looter
r->playerVote.begin()->second = ROLL_NEED;
else
{
r->CalculateCommonVoteMask(maxEnchantingSkill); // dependent from item and possible skill
SendLootStartRoll(LOOT_ROLL_TIMEOUT, lootTarget->GetMapId(), *r);
loot->items[itemSlot].is_blocked = true;
lootTarget->StartGroupLoot(this,LOOT_ROLL_TIMEOUT);
}
RollId.push_back(r);
}
else // no looters??
delete r;
}
// called when roll timer expires
void Group::EndRoll()
{
while(!RollId.empty())
{
//need more testing here, if rolls disappear
Rolls::iterator itr = RollId.begin();
CountTheRoll(itr); //i don't have to edit player votes, who didn't vote ... he will pass
}
}
void Group::CountTheRoll(Rolls::iterator& rollI)
{
Roll* roll = *rollI;
if(!roll->isValid()) // is loot already deleted ?
{
rollI = RollId.erase(rollI);
delete roll;
return;
}
//end of the roll
if (roll->totalNeed > 0)
{
if(!roll->playerVote.empty())
{
uint8 maxresul = 0;
ObjectGuid maxguid = (*roll->playerVote.begin()).first;
Player *player;
for(Roll::PlayerVote::const_iterator itr = roll->playerVote.begin(); itr != roll->playerVote.end(); ++itr)
{
if (itr->second != ROLL_NEED)
continue;
uint8 randomN = urand(1, 100);
SendLootRoll(itr->first, randomN, ROLL_NEED, *roll);
if (maxresul < randomN)
{
maxguid = itr->first;
maxresul = randomN;
}
}
SendLootRollWon(maxguid, maxresul, ROLL_NEED, *roll);
player = sObjectMgr.GetPlayer(maxguid);
if(player && player->GetSession())
{
player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED_ON_LOOT, roll->itemid, maxresul);
ItemPosCountVec dest;
LootItem *item = &(roll->getLoot()->items[roll->itemSlot]);
uint8 msg = player->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, roll->itemid, item->count );
if ( msg == EQUIP_ERR_OK )
{
item->is_looted = true;
roll->getLoot()->NotifyItemRemoved(roll->itemSlot);
--roll->getLoot()->unlootedCount;
player->StoreNewItem( dest, roll->itemid, true, item->randomPropertyId);
player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM, roll->itemid, item->count);
player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_TYPE, roll->getLoot()->loot_type, item->count);
player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_EPIC_ITEM, roll->itemid, item->count);
}
else
{
item->is_blocked = false;
player->SendEquipError( msg, NULL, NULL, roll->itemid );
}
}
}
}
else if (roll->totalGreed > 0)
{
if(!roll->playerVote.empty())
{
uint8 maxresul = 0;
ObjectGuid maxguid = (*roll->playerVote.begin()).first;
Player *player;
RollVote rollvote = ROLL_PASS; //Fixed: Using uninitialized memory 'rollvote'
Roll::PlayerVote::iterator itr;
for (itr = roll->playerVote.begin(); itr != roll->playerVote.end(); ++itr)
{
if (itr->second != ROLL_GREED && itr->second != ROLL_DISENCHANT)
continue;
uint8 randomN = urand(1, 100);
SendLootRoll(itr->first, randomN, itr->second, *roll);
if (maxresul < randomN)
{
maxguid = itr->first;
maxresul = randomN;
rollvote = itr->second;
}
}
SendLootRollWon(maxguid, maxresul, rollvote, *roll);
player = sObjectMgr.GetPlayer(maxguid);
if(player && player->GetSession())
{
player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED_ON_LOOT, roll->itemid, maxresul);
LootItem *item = &(roll->getLoot()->items[roll->itemSlot]);
if(rollvote == ROLL_GREED)
{
ItemPosCountVec dest;
uint8 msg = player->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, roll->itemid, item->count );
if ( msg == EQUIP_ERR_OK )
{
item->is_looted = true;
roll->getLoot()->NotifyItemRemoved(roll->itemSlot);
--roll->getLoot()->unlootedCount;
player->StoreNewItem( dest, roll->itemid, true, item->randomPropertyId);
player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM, roll->itemid, item->count);
player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_TYPE, roll->getLoot()->loot_type, item->count);
player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_EPIC_ITEM, roll->itemid, item->count);
}
else
{
item->is_blocked = false;
player->SendEquipError( msg, NULL, NULL, roll->itemid );
}
}
else if(rollvote == ROLL_DISENCHANT)
{
item->is_looted = true;
roll->getLoot()->NotifyItemRemoved(roll->itemSlot);
--roll->getLoot()->unlootedCount;
ItemPrototype const *pProto = ObjectMgr::GetItemPrototype(roll->itemid);
player->AutoStoreLoot(pProto->DisenchantID, LootTemplates_Disenchant, true);
}
}
}
}
else
{
SendLootAllPassed(*roll);
LootItem *item = &(roll->getLoot()->items[roll->itemSlot]);
if(item) item->is_blocked = false;
}
rollI = RollId.erase(rollI);
delete roll;
}
void Group::SetTargetIcon(uint8 id, ObjectGuid whoGuid, ObjectGuid targetGuid)
{
if (id >= TARGET_ICON_COUNT)
return;
// clean other icons
if (!targetGuid.IsEmpty())
for(int i = 0; i < TARGET_ICON_COUNT; ++i)
if (m_targetIcons[i] == targetGuid)
SetTargetIcon(i, ObjectGuid(), ObjectGuid());
m_targetIcons[id] = targetGuid;
WorldPacket data(MSG_RAID_TARGET_UPDATE, (1+8+1+8));
data << uint8(0); // set targets
data << whoGuid;
data << uint8(id);
data << targetGuid;
BroadcastPacket(&data, true);
}
static void GetDataForXPAtKill_helper(Player* player, Unit const* victim, uint32& sum_level, Player* & member_with_max_level, Player* & not_gray_member_with_max_level)
{
sum_level += player->getLevel();
if(!member_with_max_level || member_with_max_level->getLevel() < player->getLevel())
member_with_max_level = player;
uint32 gray_level = MaNGOS::XP::GetGrayLevel(player->getLevel());
if( victim->getLevel() > gray_level && (!not_gray_member_with_max_level
|| not_gray_member_with_max_level->getLevel() < player->getLevel()))
not_gray_member_with_max_level = player;
}
void Group::GetDataForXPAtKill(Unit const* victim, uint32& count,uint32& sum_level, Player* & member_with_max_level, Player* & not_gray_member_with_max_level, Player* additional)
{
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player* member = itr->getSource();
if (!member || !member->isAlive()) // only for alive
continue;
// will proccesed later
if (member == additional)
continue;
if (!member->IsAtGroupRewardDistance(victim)) // at req. distance
continue;
++count;
GetDataForXPAtKill_helper(member,victim,sum_level,member_with_max_level,not_gray_member_with_max_level);
}
if (additional)
{
if (additional->IsAtGroupRewardDistance(victim)) // at req. distance
{
++count;
GetDataForXPAtKill_helper(additional,victim,sum_level,member_with_max_level,not_gray_member_with_max_level);
}
}
}
void Group::SendTargetIconList(WorldSession *session)
{
if(!session)
return;
WorldPacket data(MSG_RAID_TARGET_UPDATE, (1+TARGET_ICON_COUNT*9));
data << uint8(1); // list targets
for(int i = 0; i < TARGET_ICON_COUNT; ++i)
{
if (m_targetIcons[i].IsEmpty())
continue;
data << uint8(i);
data << m_targetIcons[i];
}
session->SendPacket(&data);
}
void Group::SendUpdate()
{
Player *player;
for(member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr)
{
player = sObjectMgr.GetPlayer(citr->guid);
if(!player || !player->GetSession() || player->GetGroup() != this )
continue;
// guess size
WorldPacket data(SMSG_GROUP_LIST, (1+1+1+1+8+4+GetMembersCount()*20));
data << uint8(m_groupType); // group type (flags in 3.3)
data << uint8(citr->group); // groupid
data << uint8(GetFlags(*citr)); // group flags
data << uint8(isBGGroup() ? 1 : 0); // 2.0.x, isBattleGroundGroup?
if(m_groupType & GROUPTYPE_LFD)
{
data << uint8(0);
data << uint32(0);
}
data << uint64(0x50000000FFFFFFFELL); // related to voice chat?
data << uint32(0); // 3.3, this value increments every time SMSG_GROUP_LIST is sent
data << uint32(GetMembersCount()-1);
for(member_citerator citr2 = m_memberSlots.begin(); citr2 != m_memberSlots.end(); ++citr2)
{
if(citr->guid == citr2->guid)
continue;
Player* member = sObjectMgr.GetPlayer(citr2->guid);
uint8 onlineState = (member) ? MEMBER_STATUS_ONLINE : MEMBER_STATUS_OFFLINE;
onlineState = onlineState | ((isBGGroup()) ? MEMBER_STATUS_PVP : 0);
data << citr2->name;
data << citr2->guid;
data << uint8(onlineState); // online-state
data << uint8(citr2->group); // groupid
data << uint8(GetFlags(*citr2)); // group flags
data << uint8(0); // 3.3, role?
}
data << m_leaderGuid; // leader guid
if(GetMembersCount()-1)
{
data << uint8(m_lootMethod); // loot method
data << m_looterGuid; // looter guid
data << uint8(m_lootThreshold); // loot threshold
data << uint8(m_dungeonDifficulty); // Dungeon Difficulty
data << uint8(m_raidDifficulty); // Raid Difficulty
data << uint8(0); // 3.3, dynamic difficulty?
}
player->GetSession()->SendPacket( &data );
}
}
void Group::UpdatePlayerOutOfRange(Player* pPlayer)
{
if(!pPlayer || !pPlayer->IsInWorld())
return;
if (pPlayer->GetGroupUpdateFlag() == GROUP_UPDATE_FLAG_NONE)
return;
WorldPacket data;
pPlayer->GetSession()->BuildPartyMemberStatsChangedPacket(pPlayer, &data);
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
if (Player *player = itr->getSource())
if (player != pPlayer && !player->HaveAtClient(pPlayer))
player->GetSession()->SendPacket(&data);
}
void Group::BroadcastPacket(WorldPacket *packet, bool ignorePlayersInBGRaid, int group, ObjectGuid ignore)
{
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player *pl = itr->getSource();
if (!pl || (!ignore.IsEmpty() && pl->GetObjectGuid() == ignore) || (ignorePlayersInBGRaid && pl->GetGroup() != this) )
continue;
if (pl->GetSession() && (group == -1 || itr->getSubGroup() == group))
pl->GetSession()->SendPacket(packet);
}
}
void Group::BroadcastReadyCheck(WorldPacket *packet)
{
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player *pl = itr->getSource();
if (pl && pl->GetSession())
if (IsLeader(pl->GetObjectGuid()) || IsAssistant(pl->GetObjectGuid()))
pl->GetSession()->SendPacket(packet);
}
}
void Group::OfflineReadyCheck()
{
for(member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr)
{
Player *pl = sObjectMgr.GetPlayer(citr->guid);
if (!pl || !pl->GetSession())
{
WorldPacket data(MSG_RAID_READY_CHECK_CONFIRM, 9);
data << citr->guid;
data << uint8(0);
BroadcastReadyCheck(&data);
}
}
}
bool Group::_addMember(ObjectGuid guid, const char* name, bool isAssistant)
{
// get first not-full group
uint8 groupid = 0;
if (m_subGroupsCounts)
{
bool groupFound = false;
for (; groupid < MAX_RAID_SUBGROUPS; ++groupid)
{
if (m_subGroupsCounts[groupid] < MAX_GROUP_SIZE)
{
groupFound = true;
break;
}
}
// We are raid group and no one slot is free
if (!groupFound)
return false;
}
return _addMember(guid, name, isAssistant, groupid);
}
bool Group::_addMember(ObjectGuid guid, const char* name, bool isAssistant, uint8 group)
{
if(IsFull())
return false;
if (guid.IsEmpty())
return false;
Player *player = sObjectMgr.GetPlayer(guid);
MemberSlot member;
member.guid = guid;
member.name = name;
member.group = group;
member.assistant = isAssistant;
m_memberSlots.push_back(member);
SubGroupCounterIncrease(group);
if(player)
{
player->SetGroupInvite(NULL);
//if player is in group and he is being added to BG raid group, then call SetBattleGroundRaid()
if( player->GetGroup() && isBGGroup() )
player->SetBattleGroundRaid(this, group);
//if player is in bg raid and we are adding him to normal group, then call SetOriginalGroup()
else if ( player->GetGroup() )
player->SetOriginalGroup(this, group);
//if player is not in group, then call set group
else
player->SetGroup(this, group);
// if the same group invites the player back, cancel the homebind timer
InstanceGroupBind *bind = GetBoundInstance(player->GetMapId(), player);
if(bind && bind->save->GetInstanceId() == player->GetInstanceId())
player->m_InstanceValid = true;
}
if(!isRaidGroup()) // reset targetIcons for non-raid-groups
{
for(int i = 0; i < TARGET_ICON_COUNT; ++i)
m_targetIcons[i].Clear();
}
if(!isBGGroup())
{
// insert into group table
CharacterDatabase.PExecute("INSERT INTO group_member(groupId,memberGuid,assistant,subgroup) VALUES('%u','%u','%u','%u')",
m_Id, member.guid.GetCounter(), ((member.assistant==1)?1:0), member.group);
}
return true;
}
bool Group::_removeMember(ObjectGuid guid)
{
Player *player = sObjectMgr.GetPlayer(guid);
if (player)
{
//if we are removing player from battleground raid
if (isBGGroup())
player->RemoveFromBattleGroundRaid();
else
{
//we can remove player who is in battleground from his original group
if (player->GetOriginalGroup() == this)
player->SetOriginalGroup(NULL);
else
player->SetGroup(NULL);
}
}
_removeRolls(guid);
member_witerator slot = _getMemberWSlot(guid);
if (slot != m_memberSlots.end())
{
SubGroupCounterDecrease(slot->group);
m_memberSlots.erase(slot);
}
if (!isBGGroup())
CharacterDatabase.PExecute("DELETE FROM group_member WHERE memberGuid='%u'", guid.GetCounter());
if (m_leaderGuid == guid) // leader was removed
{
if (GetMembersCount() > 0)
_setLeader(m_memberSlots.front().guid);
return true;
}
return false;
}
void Group::_setLeader(ObjectGuid guid)
{
member_citerator slot = _getMemberCSlot(guid);
if (slot == m_memberSlots.end())
return;
if (!isBGGroup())
{
uint32 slot_lowguid = slot->guid.GetCounter();
uint32 leader_lowguid = m_leaderGuid.GetCounter();
// TODO: set a time limit to have this function run rarely cause it can be slow
CharacterDatabase.BeginTransaction();
// update the group's bound instances when changing leaders
// remove all permanent binds from the group
// in the DB also remove solo binds that will be replaced with permbinds
// from the new leader
CharacterDatabase.PExecute(
"DELETE FROM group_instance WHERE leaderguid='%u' AND (permanent = 1 OR "
"instance IN (SELECT instance FROM character_instance WHERE guid = '%u')"
")", leader_lowguid, slot_lowguid);
Player *player = sObjectMgr.GetPlayer(slot->guid);
if (player)
{
for(uint8 i = 0; i < MAX_DIFFICULTY; ++i)
{
for(BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end();)
{
if(itr->second.perm)
{
itr->second.save->RemoveGroup(this);
m_boundInstances[i].erase(itr++);
}
else
++itr;
}
}
}
// update the group's solo binds to the new leader
CharacterDatabase.PExecute("UPDATE group_instance SET leaderGuid='%u' WHERE leaderGuid = '%u'",
slot_lowguid, leader_lowguid);
// copy the permanent binds from the new leader to the group
// overwriting the solo binds with permanent ones if necessary
// in the DB those have been deleted already
Player::ConvertInstancesToGroup(player, this, slot->guid);
// update the group leader
CharacterDatabase.PExecute("UPDATE groups SET leaderGuid='%u' WHERE groupId='%u'", slot_lowguid, m_Id);
CharacterDatabase.CommitTransaction();
}
m_leaderGuid = slot->guid;
m_leaderName = slot->name;
}
void Group::_removeRolls(ObjectGuid guid)
{
for (Rolls::iterator it = RollId.begin(); it != RollId.end(); )
{
Roll* roll = *it;
Roll::PlayerVote::iterator itr2 = roll->playerVote.find(guid);
if(itr2 == roll->playerVote.end())
{
++it;
continue;
}
if (itr2->second == ROLL_GREED || itr2->second == ROLL_DISENCHANT)
--roll->totalGreed;
if (itr2->second == ROLL_NEED)
--roll->totalNeed;
if (itr2->second == ROLL_PASS)
--roll->totalPass;
if (itr2->second != ROLL_NOT_VALID)
--roll->totalPlayersRolling;
roll->playerVote.erase(itr2);
if (!CountRollVote(guid, it, ROLL_NOT_EMITED_YET))
++it;
}
}
bool Group::_setMembersGroup(ObjectGuid guid, uint8 group)
{
member_witerator slot = _getMemberWSlot(guid);
if (slot == m_memberSlots.end())
return false;
slot->group = group;
SubGroupCounterIncrease(group);
if (!isBGGroup())
CharacterDatabase.PExecute("UPDATE group_member SET subgroup='%u' WHERE memberGuid='%u'", group, guid.GetCounter());
return true;
}
bool Group::_setAssistantFlag(ObjectGuid guid, const bool &state)
{
member_witerator slot = _getMemberWSlot(guid);
if (slot == m_memberSlots.end())
return false;
slot->assistant = state;
if (!isBGGroup())
CharacterDatabase.PExecute("UPDATE group_member SET assistant='%u' WHERE memberGuid='%u'", (state==true)?1:0, guid.GetCounter());
return true;
}
bool Group::_setMainTank(ObjectGuid guid)
{
if (m_mainTankGuid == guid)
return false;
if (!guid.IsEmpty())
{
member_citerator slot = _getMemberCSlot(guid);
if (slot == m_memberSlots.end())
return false;
if (m_mainAssistantGuid == guid)
_setMainAssistant(ObjectGuid());
}
m_mainTankGuid = guid;
if (!isBGGroup())
CharacterDatabase.PExecute("UPDATE groups SET mainTank='%u' WHERE groupId='%u'", m_mainTankGuid.GetCounter(), m_Id);
return true;
}
bool Group::_setMainAssistant(ObjectGuid guid)
{
if (m_mainAssistantGuid == guid)
return false;
if (!guid.IsEmpty())
{
member_witerator slot = _getMemberWSlot(guid);
if (slot == m_memberSlots.end())
return false;
if (m_mainTankGuid == guid)
_setMainTank(ObjectGuid());
}
m_mainAssistantGuid = guid;
if (!isBGGroup())
CharacterDatabase.PExecute("UPDATE groups SET mainAssistant='%u' WHERE groupId='%u'",
m_mainAssistantGuid.GetCounter(), m_Id);
return true;
}
bool Group::SameSubGroup(Player const* member1, Player const* member2) const
{
if(!member1 || !member2)
return false;
if (member1->GetGroup() != this || member2->GetGroup() != this)
return false;
else
return member1->GetSubGroup() == member2->GetSubGroup();
}
// allows setting subgroup for offline members
void Group::ChangeMembersGroup(ObjectGuid guid, uint8 group)
{
if (!isRaidGroup())
return;
Player *player = sObjectMgr.GetPlayer(guid);
if (!player)
{
uint8 prevSubGroup = GetMemberGroup(guid);
if (prevSubGroup == group)
return;
if (_setMembersGroup(guid, group))
{
SubGroupCounterDecrease(prevSubGroup);
SendUpdate();
}
}
else
// This methods handles itself groupcounter decrease
ChangeMembersGroup(player, group);
}
// only for online members
void Group::ChangeMembersGroup(Player *player, uint8 group)
{
if (!player || !isRaidGroup())
return;
uint8 prevSubGroup = player->GetSubGroup();
if (prevSubGroup == group)
return;
if (_setMembersGroup(player->GetObjectGuid(), group))
{
if (player->GetGroup() == this)
player->GetGroupRef().setSubGroup(group);
//if player is in BG raid, it is possible that he is also in normal raid - and that normal raid is stored in m_originalGroup reference
else
{
prevSubGroup = player->GetOriginalSubGroup();
player->GetOriginalGroupRef().setSubGroup(group);
}
SubGroupCounterDecrease(prevSubGroup);
SendUpdate();
}
}
uint32 Group::GetMaxSkillValueForGroup( SkillType skill )
{
uint32 maxvalue = 0;
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player *member = itr->getSource();
if (!member)
continue;
uint32 value = member->GetSkillValue(skill);
if (maxvalue < value)
maxvalue = value;
}
return maxvalue;
}
void Group::UpdateLooterGuid( Creature* creature, bool ifneed )
{
switch (GetLootMethod())
{
case MASTER_LOOT:
case FREE_FOR_ALL:
return;
default:
// round robin style looting applies for all low
// quality items in each loot method except free for all and master loot
break;
}
member_citerator guid_itr = _getMemberCSlot(GetLooterGuid());
if (guid_itr != m_memberSlots.end())
{
if (ifneed)
{
// not update if only update if need and ok
Player* looter = ObjectAccessor::FindPlayer(guid_itr->guid);
if (looter && looter->IsWithinDist(creature, sWorld.getConfig(CONFIG_FLOAT_GROUP_XP_DISTANCE), false))
return;
}
++guid_itr;
}
// search next after current
if (guid_itr != m_memberSlots.end())
{
for(member_citerator itr = guid_itr; itr != m_memberSlots.end(); ++itr)
{
if (Player* pl = ObjectAccessor::FindPlayer(itr->guid))
{
if (pl->IsWithinDist(creature, sWorld.getConfig(CONFIG_FLOAT_GROUP_XP_DISTANCE), false))
{
bool refresh = pl->GetLootGUID() == creature->GetGUID();
//if(refresh) // update loot for new looter
// pl->GetSession()->DoLootRelease(pl->GetLootGUID());
SetLooterGuid(pl->GetObjectGuid());
SendUpdate();
if (refresh) // update loot for new looter
pl->SendLoot(creature->GetObjectGuid(), LOOT_CORPSE);
return;
}
}
}
}
// search from start
for(member_citerator itr = m_memberSlots.begin(); itr != guid_itr; ++itr)
{
if (Player* pl = ObjectAccessor::FindPlayer(itr->guid))
{
if (pl->IsWithinDist(creature, sWorld.getConfig(CONFIG_FLOAT_GROUP_XP_DISTANCE), false))
{
bool refresh = pl->GetLootGUID()==creature->GetGUID();
//if(refresh) // update loot for new looter
// pl->GetSession()->DoLootRelease(pl->GetLootGUID());
SetLooterGuid(pl->GetObjectGuid());
SendUpdate();
if (refresh) // update loot for new looter
pl->SendLoot(creature->GetObjectGuid(), LOOT_CORPSE);
return;
}
}
}
SetLooterGuid(ObjectGuid());
SendUpdate();
}
GroupJoinBattlegroundResult Group::CanJoinBattleGroundQueue(BattleGround const* bgOrTemplate, BattleGroundQueueTypeId bgQueueTypeId, uint32 MinPlayerCount, uint32 MaxPlayerCount, bool isRated, uint32 arenaSlot)
{
BattlemasterListEntry const* bgEntry = sBattlemasterListStore.LookupEntry(bgOrTemplate->GetTypeID());
if(!bgEntry)
return ERR_GROUP_JOIN_BATTLEGROUND_FAIL; // shouldn't happen
// check for min / max count
uint32 memberscount = GetMembersCount();
// only check for MinPlayerCount since MinPlayerCount == MaxPlayerCount for arenas...
if(bgOrTemplate->isArena() && memberscount != MinPlayerCount)
return ERR_ARENA_TEAM_PARTY_SIZE;
if(memberscount > bgEntry->maxGroupSize) // no MinPlayerCount for battlegrounds
return ERR_BATTLEGROUND_NONE; // ERR_GROUP_JOIN_BATTLEGROUND_TOO_MANY handled on client side
// get a player as reference, to compare other players' stats to (arena team id, queue id based on level, etc.)
Player * reference = GetFirstMember()->getSource();
// no reference found, can't join this way
if(!reference)
return ERR_BATTLEGROUND_JOIN_FAILED;
PvPDifficultyEntry const* bracketEntry = GetBattlegroundBracketByLevel(bgOrTemplate->GetMapId(), reference->getLevel());
if(!bracketEntry)
return ERR_BATTLEGROUND_JOIN_FAILED;
uint32 arenaTeamId = reference->GetArenaTeamId(arenaSlot);
uint32 team = reference->GetTeam();
// check every member of the group to be able to join
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player *member = itr->getSource();
// offline member? don't let join
if(!member)
return ERR_BATTLEGROUND_JOIN_FAILED;
// don't allow cross-faction join as group
if(member->GetTeam() != team)
return ERR_BATTLEGROUND_JOIN_TIMED_OUT;
// not in the same battleground level bracket, don't let join
PvPDifficultyEntry const* memberBracketEntry = GetBattlegroundBracketByLevel(bracketEntry->mapId, member->getLevel());
if(memberBracketEntry != bracketEntry)
return ERR_BATTLEGROUND_JOIN_RANGE_INDEX;
// don't let join rated matches if the arena team id doesn't match
if(isRated && member->GetArenaTeamId(arenaSlot) != arenaTeamId)
return ERR_BATTLEGROUND_JOIN_FAILED;
// don't let join if someone from the group is already in that bg queue
if(member->InBattleGroundQueueForBattleGroundQueueType(bgQueueTypeId))
return ERR_BATTLEGROUND_JOIN_FAILED; // not blizz-like
// check for deserter debuff in case not arena queue
if(bgOrTemplate->GetTypeID() != BATTLEGROUND_AA && !member->CanJoinToBattleground())
return ERR_GROUP_JOIN_BATTLEGROUND_DESERTERS;
// check if member can join any more battleground queues
if(!member->HasFreeBattleGroundQueueId())
return ERR_BATTLEGROUND_TOO_MANY_QUEUES; // not blizz-like
}
return GroupJoinBattlegroundResult(bgOrTemplate->GetTypeID());
}
void Group::SetDungeonDifficulty(Difficulty difficulty)
{
m_dungeonDifficulty = difficulty;
if(!isBGGroup())
CharacterDatabase.PExecute("UPDATE groups SET difficulty = %u WHERE groupId='%u'", m_dungeonDifficulty, m_Id);
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player *player = itr->getSource();
if(!player->GetSession() || player->getLevel() < LEVELREQUIREMENT_HEROIC)
continue;
player->SetDungeonDifficulty(difficulty);
player->SendDungeonDifficulty(true);
}
}
void Group::SetRaidDifficulty(Difficulty difficulty)
{
m_raidDifficulty = difficulty;
if(!isBGGroup())
CharacterDatabase.PExecute("UPDATE groups SET raiddifficulty = %u WHERE groupId='%u'", m_raidDifficulty, m_Id);
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player *player = itr->getSource();
if(!player->GetSession() || player->getLevel() < LEVELREQUIREMENT_HEROIC)
continue;
player->SetRaidDifficulty(difficulty);
player->SendRaidDifficulty(true);
}
}
bool Group::InCombatToInstance(uint32 instanceId)
{
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player *pPlayer = itr->getSource();
if(pPlayer->getAttackers().size() && pPlayer->GetInstanceId() == instanceId)
return true;
}
return false;
}
void Group::ResetInstances(InstanceResetMethod method, bool isRaid, Player* SendMsgTo)
{
if(isBGGroup())
return;
// method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_DISBAND
// we assume that when the difficulty changes, all instances that can be reset will be
Difficulty diff = GetDifficulty(isRaid);
for(BoundInstancesMap::iterator itr = m_boundInstances[diff].begin(); itr != m_boundInstances[diff].end();)
{
InstanceSave *p = itr->second.save;
const MapEntry *entry = sMapStore.LookupEntry(itr->first);
if (!entry || entry->IsRaid() != isRaid || (!p->CanReset() && method != INSTANCE_RESET_GROUP_DISBAND))
{
++itr;
continue;
}
if(method == INSTANCE_RESET_ALL)
{
// the "reset all instances" method can only reset normal maps
if (entry->map_type == MAP_RAID || diff == DUNGEON_DIFFICULTY_HEROIC)
{
++itr;
continue;
}
}
bool isEmpty = true;
// if the map is loaded, reset it
Map *map = sMapMgr.FindMap(p->GetMapId(), p->GetInstanceId());
if(map && map->IsDungeon() && !(method == INSTANCE_RESET_GROUP_DISBAND && !p->CanReset()))
isEmpty = ((InstanceMap*)map)->Reset(method);
if(SendMsgTo)
{
if(isEmpty)
SendMsgTo->SendResetInstanceSuccess(p->GetMapId());
else
SendMsgTo->SendResetInstanceFailed(0, p->GetMapId());
}
if(isEmpty || method == INSTANCE_RESET_GROUP_DISBAND || method == INSTANCE_RESET_CHANGE_DIFFICULTY)
{
// do not reset the instance, just unbind if others are permanently bound to it
if(p->CanReset())
p->DeleteFromDB();
else
CharacterDatabase.PExecute("DELETE FROM group_instance WHERE instance = '%u'", p->GetInstanceId());
// i don't know for sure if hash_map iterators
m_boundInstances[diff].erase(itr);
itr = m_boundInstances[diff].begin();
// this unloads the instance save unless online players are bound to it
// (eg. permanent binds or GM solo binds)
p->RemoveGroup(this);
}
else
++itr;
}
}
InstanceGroupBind* Group::GetBoundInstance(uint32 mapid, Player* player)
{
MapEntry const* mapEntry = sMapStore.LookupEntry(mapid);
if(!mapEntry)
return NULL;
Difficulty difficulty = player->GetDifficulty(mapEntry->IsRaid());
// some instances only have one difficulty
MapDifficulty const* mapDiff = GetMapDifficultyData(mapid,difficulty);
if(!mapDiff)
difficulty = DUNGEON_DIFFICULTY_NORMAL;
BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid);
if(itr != m_boundInstances[difficulty].end())
return &itr->second;
else
return NULL;
}
InstanceGroupBind* Group::GetBoundInstance(Map* aMap, Difficulty difficulty)
{
// some instances only have one difficulty
MapDifficulty const* mapDiff = GetMapDifficultyData(aMap->GetId(),difficulty);
if(!mapDiff)
return NULL;
BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(aMap->GetId());
if(itr != m_boundInstances[difficulty].end())
return &itr->second;
else
return NULL;
}
InstanceGroupBind* Group::BindToInstance(InstanceSave *save, bool permanent, bool load)
{
if (save && !isBGGroup())
{
InstanceGroupBind& bind = m_boundInstances[save->GetDifficulty()][save->GetMapId()];
if (bind.save)
{
// when a boss is killed or when copying the players's binds to the group
if (permanent != bind.perm || save != bind.save)
if (!load)
CharacterDatabase.PExecute("UPDATE group_instance SET instance = '%u', permanent = '%u' WHERE leaderGuid = '%u' AND instance = '%u'",
save->GetInstanceId(), permanent, GetLeaderGuid().GetCounter(), bind.save->GetInstanceId());
}
else if (!load)
CharacterDatabase.PExecute("INSERT INTO group_instance (leaderGuid, instance, permanent) VALUES ('%u', '%u', '%u')",
GetLeaderGuid().GetCounter(), save->GetInstanceId(), permanent);
if(bind.save != save)
{
if(bind.save)
bind.save->RemoveGroup(this);
save->AddGroup(this);
}
bind.save = save;
bind.perm = permanent;
if (!load)
DEBUG_LOG("Group::BindToInstance: Group (Id: %d) is now bound to map %d, instance %d, difficulty %d",
GetId(), save->GetMapId(), save->GetInstanceId(), save->GetDifficulty());
return &bind;
}
else
return NULL;
}
void Group::UnbindInstance(uint32 mapid, uint8 difficulty, bool unload)
{
BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid);
if (itr != m_boundInstances[difficulty].end())
{
if (!unload)
CharacterDatabase.PExecute("DELETE FROM group_instance WHERE leaderGuid = '%u' AND instance = '%u'",
GetLeaderGuid().GetCounter(), itr->second.save->GetInstanceId());
itr->second.save->RemoveGroup(this); // save can become invalid
m_boundInstances[difficulty].erase(itr);
}
}
void Group::_homebindIfInstance(Player *player)
{
if (player && !player->isGameMaster())
{
Map* map = player->GetMap();
if (map->IsDungeon())
{
// leaving the group in an instance, the homebind timer is started
// unless the player is permanently saved to the instance
InstancePlayerBind *playerBind = player->GetBoundInstance(map->GetId(), map->GetDifficulty());
if(!playerBind || !playerBind->perm)
player->m_InstanceValid = false;
}
}
}
static void RewardGroupAtKill_helper(Player* pGroupGuy, Unit* pVictim, uint32 count, bool PvP, float group_rate, uint32 sum_level, bool is_dungeon, Player* not_gray_member_with_max_level, Player* member_with_max_level, uint32 xp )
{
// honor can be in PvP and !PvP (racial leader) cases (for alive)
if (pGroupGuy->isAlive())
pGroupGuy->RewardHonor(pVictim,count);
// xp and reputation only in !PvP case
if(!PvP)
{
float rate = group_rate * float(pGroupGuy->getLevel()) / sum_level;
// if is in dungeon then all receive full reputation at kill
// rewarded any alive/dead/near_corpse group member
pGroupGuy->RewardReputation(pVictim,is_dungeon ? 1.0f : rate);
// XP updated only for alive group member
if(pGroupGuy->isAlive() && not_gray_member_with_max_level &&
pGroupGuy->getLevel() <= not_gray_member_with_max_level->getLevel())
{
uint32 itr_xp = (member_with_max_level == not_gray_member_with_max_level) ? uint32(xp*rate) : uint32((xp*rate/2)+1);
pGroupGuy->GiveXP(itr_xp, pVictim);
if(Pet* pet = pGroupGuy->GetPet())
pet->GivePetXP(itr_xp/2);
}
// quest objectives updated only for alive group member or dead but with not released body
if(pGroupGuy->isAlive()|| !pGroupGuy->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
{
// normal creature (not pet/etc) can be only in !PvP case
if(pVictim->GetTypeId()==TYPEID_UNIT)
if(CreatureInfo const* normalInfo = ObjectMgr::GetCreatureTemplate(pVictim->GetEntry()))
pGroupGuy->KilledMonster(normalInfo, pVictim->GetObjectGuid());
}
}
}
/** Provide rewards to group members at unit kill
*
* @param pVictim Killed unit
* @param player_tap Player who tap unit if online, it can be group member or can be not if leaved after tap but before kill target
*
* Rewards received by group members and player_tap
*/
void Group::RewardGroupAtKill(Unit* pVictim, Player* player_tap)
{
bool PvP = pVictim->isCharmedOwnedByPlayerOrPlayer();
// prepare data for near group iteration (PvP and !PvP cases)
uint32 xp = 0;
uint32 count = 0;
uint32 sum_level = 0;
Player* member_with_max_level = NULL;
Player* not_gray_member_with_max_level = NULL;
GetDataForXPAtKill(pVictim,count,sum_level,member_with_max_level,not_gray_member_with_max_level,player_tap);
if(member_with_max_level)
{
/// not get Xp in PvP or no not gray players in group
xp = (PvP || !not_gray_member_with_max_level) ? 0 : MaNGOS::XP::Gain(not_gray_member_with_max_level, pVictim);
/// skip in check PvP case (for speed, not used)
bool is_raid = PvP ? false : sMapStore.LookupEntry(pVictim->GetMapId())->IsRaid() && isRaidGroup();
bool is_dungeon = PvP ? false : sMapStore.LookupEntry(pVictim->GetMapId())->IsDungeon();
float group_rate = MaNGOS::XP::xp_in_group_rate(count,is_raid);
for(GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next())
{
Player* pGroupGuy = itr->getSource();
if(!pGroupGuy)
continue;
// will proccessed later
if(pGroupGuy==player_tap)
continue;
if(!pGroupGuy->IsAtGroupRewardDistance(pVictim))
continue; // member (alive or dead) or his corpse at req. distance
RewardGroupAtKill_helper(pGroupGuy, pVictim, count, PvP, group_rate, sum_level, is_dungeon, not_gray_member_with_max_level, member_with_max_level, xp);
}
if(player_tap)
{
// member (alive or dead) or his corpse at req. distance
if(player_tap->IsAtGroupRewardDistance(pVictim))
RewardGroupAtKill_helper(player_tap, pVictim, count, PvP, group_rate, sum_level, is_dungeon, not_gray_member_with_max_level, member_with_max_level, xp);
}
}
}