mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-27 04:12:40 +01:00
- Logging System is asyncronous to improve performance.
- Each msg and Logger has a Log Type and Log Level assigned. Each msg is assigned the Logger of same Log Type or "root" Logger is selected if there is no Logger configured for the given Log Type
- Loggers have a list of Appenders to send the msg to. The Msg in the Logger is not sent to Appenders if the msg LogLevel is lower than Logger LogLevel.
- There are three (at the moment) types of Appenders: Console, File or DB (this is WIP, not working ATM). Msg is not written to the resource if msg LogLevel is lower than Appender LogLevel.
- Appender and Console Log levels can be changed while server is active with command '.set loglevel (a/l) name level'
Explanation of use with Sample config:
Appender.Console.Type=1 (1 = Console)
Appender.Console.Level=2 (2 = Debug)
Appender.Server.Type=2 (2 = File)
Appender.Server.Level=3 (3 = Info)
Appender.Server.File=Server.log
Appender.SQL.Type=2 (2 = File)
Appender.SQL.Level=1 (1 = Trace)
Appender.SQL.File=sql.log
Appenders=Console Server (NOTE: SQL has not been included here... that will make core ignore the config for "SQL" as it's not in this list)
Logger.root.Type=0 (0 = Default - if it's not created by config, server will create it with LogLevel = DISABLED)
Logger.root.Level=5 (5 = Error)
Logger.root.Appenders=Console
Logger.SQL.Type=26 (26 = SQL)
Logger.SQL.Level=3 (2 = Debug)
Logger.SQL.Appenders=Console Server SQL
Logger.SomeRandomName.Type=24 (24 = Guild)
Logger.SomeRandomName.Level=5 (5 = Error)
Loggers=root SQL SomeRandomName
* At loading Appender SQL will be ignored, as it's not present on "Appenders"
* sLog->outDebug(LOG_FILTER_GUILD, "Some log msg related to Guilds")
- Msg is sent to Logger of Type LOG_FILTER_GUILD (24). Logger with name SomeRandomName is found but it's LogLevel = 5 and Msg LogLevel=2... Msg is not logged
* sLog->outError(LOG_FILTER_GUILD, "Some error log msg related to Guilds")
- Msg is sent to Logger of Type LOG_FILTER_GUILD (24). Logger with name SomeRandomeName is found with proper LogLevel but Logger does not have any Appenders assigned to that logger... Msg is not logged
* sLog->outDebug(LOG_FILTER_SQL, "Some msg related to SQLs")
- Msg is sent to Logger SQL (matches type), as it matches LogLevel the msg is sent to Appenders Console, Server and SQL
- Appender Console has lower Log Level: Msg is logged to Console
- Appender Server has higher Log Level: Msg is not logged to file
- Appender SQL has lower Log Level: Msg is logged to file sql.log
* sLog->outDebug(LOG_FILTER_BATTLEGROUND, "Some msg related to Battelgrounds")
- Msg is sent to Logger root (Type 0) as no Logger was found with Type LOG_FILTER_BATTLEGROUND (13). As Logger has higher LogLevel msg is not sent to any appender
* sLog->outError(LOG_FILTER_BATTLEGROUND, "Some error msg related to Battelgrounds")
- Msg is sent to Logger root (Type 0) as no Logger was found with Type LOG_FILTER_BATTLEGROUND (13). Msg has lower LogLevel and is sent to Appender Console
- Appender Console has lower LogLevel: Msg is logged to Console
2402 lines
80 KiB
C++
Executable File
2402 lines
80 KiB
C++
Executable File
/*
|
|
* Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
|
|
* Copyright (C) 2005-2009 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "Common.h"
|
|
#include "Opcodes.h"
|
|
#include "WorldPacket.h"
|
|
#include "WorldSession.h"
|
|
#include "Player.h"
|
|
#include "World.h"
|
|
#include "ObjectMgr.h"
|
|
#include "GroupMgr.h"
|
|
#include "Group.h"
|
|
#include "Formulas.h"
|
|
#include "ObjectAccessor.h"
|
|
#include "Battleground.h"
|
|
#include "BattlegroundMgr.h"
|
|
#include "MapManager.h"
|
|
#include "InstanceSaveMgr.h"
|
|
#include "MapInstanced.h"
|
|
#include "Util.h"
|
|
#include "LFGMgr.h"
|
|
#include "UpdateFieldFlags.h"
|
|
|
|
Roll::Roll(uint64 _guid, LootItem const& li) : itemGUID(_guid), itemid(li.itemid),
|
|
itemRandomPropId(li.randomPropertyId), itemRandomSuffix(li.randomSuffix), itemCount(li.count),
|
|
totalPlayersRolling(0), totalNeed(0), totalGreed(0), totalPass(0), itemSlot(0),
|
|
rollVoteMask(ROLL_ALL_TYPE_NO_DISENCHANT)
|
|
{
|
|
}
|
|
|
|
Roll::~Roll()
|
|
{
|
|
}
|
|
|
|
void Roll::setLoot(Loot* pLoot)
|
|
{
|
|
link(pLoot, this);
|
|
}
|
|
|
|
Loot* Roll::getLoot()
|
|
{
|
|
return getTarget();
|
|
}
|
|
|
|
Group::Group() : m_leaderGuid(0), m_leaderName(""), m_groupType(GROUPTYPE_NORMAL),
|
|
m_dungeonDifficulty(DUNGEON_DIFFICULTY_NORMAL), m_raidDifficulty(RAID_DIFFICULTY_10MAN_NORMAL),
|
|
m_bgGroup(NULL), m_lootMethod(FREE_FOR_ALL), m_lootThreshold(ITEM_QUALITY_UNCOMMON), m_looterGuid(0),
|
|
m_subGroupsCounts(NULL), m_guid(0), m_counter(0), m_maxEnchantingLevel(0), m_dbStoreId(0)
|
|
{
|
|
for (uint8 i = 0; i < TARGETICONCOUNT; ++i)
|
|
m_targetIcons[i] = 0;
|
|
}
|
|
|
|
Group::~Group()
|
|
{
|
|
if (m_bgGroup)
|
|
{
|
|
sLog->outDebug(LOG_FILTER_BATTLEGROUND, "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(LOG_FILTER_GENERAL, "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
|
|
delete[] m_subGroupsCounts;
|
|
}
|
|
|
|
bool Group::Create(Player* leader)
|
|
{
|
|
uint64 leaderGuid = leader->GetGUID();
|
|
uint32 lowguid = sGroupMgr->GenerateGroupId();
|
|
|
|
m_guid = MAKE_NEW_GUID(lowguid, 0, HIGHGUID_GROUP);
|
|
m_leaderGuid = leaderGuid;
|
|
m_leaderName = leader->GetName();
|
|
|
|
m_groupType = isBGGroup() ? GROUPTYPE_BGRAID : GROUPTYPE_NORMAL;
|
|
|
|
if (m_groupType & GROUPTYPE_RAID)
|
|
_initRaidSubGroupsCounter();
|
|
|
|
m_lootMethod = GROUP_LOOT;
|
|
m_lootThreshold = ITEM_QUALITY_UNCOMMON;
|
|
m_looterGuid = leaderGuid;
|
|
|
|
m_dungeonDifficulty = DUNGEON_DIFFICULTY_NORMAL;
|
|
m_raidDifficulty = RAID_DIFFICULTY_10MAN_NORMAL;
|
|
|
|
if (!isBGGroup())
|
|
{
|
|
m_dungeonDifficulty = leader->GetDungeonDifficulty();
|
|
m_raidDifficulty = leader->GetRaidDifficulty();
|
|
|
|
m_dbStoreId = sGroupMgr->GenerateNewGroupDbStoreId();
|
|
|
|
sGroupMgr->RegisterGroupDbStoreId(m_dbStoreId, this);
|
|
|
|
// Store group in database
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GROUP);
|
|
|
|
uint8 index = 0;
|
|
|
|
stmt->setUInt32(index++, m_dbStoreId);
|
|
stmt->setUInt32(index++, GUID_LOPART(m_leaderGuid));
|
|
stmt->setUInt8(index++, uint8(m_lootMethod));
|
|
stmt->setUInt32(index++, GUID_LOPART(m_looterGuid));
|
|
stmt->setUInt8(index++, uint8(m_lootThreshold));
|
|
stmt->setUInt32(index++, uint32(m_targetIcons[0]));
|
|
stmt->setUInt32(index++, uint32(m_targetIcons[1]));
|
|
stmt->setUInt32(index++, uint32(m_targetIcons[2]));
|
|
stmt->setUInt32(index++, uint32(m_targetIcons[3]));
|
|
stmt->setUInt32(index++, uint32(m_targetIcons[4]));
|
|
stmt->setUInt32(index++, uint32(m_targetIcons[5]));
|
|
stmt->setUInt32(index++, uint32(m_targetIcons[6]));
|
|
stmt->setUInt32(index++, uint32(m_targetIcons[7]));
|
|
stmt->setUInt8(index++, uint8(m_groupType));
|
|
stmt->setUInt32(index++, uint8(m_dungeonDifficulty));
|
|
stmt->setUInt32(index++, uint8(m_raidDifficulty));
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
|
|
|
|
ASSERT(AddMember(leader)); // If the leader can't be added to a new group because it appears full, something is clearly wrong.
|
|
|
|
Player::ConvertInstancesToGroup(leader, this, false);
|
|
}
|
|
else if (!AddMember(leader))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Group::LoadGroupFromDB(Field* fields)
|
|
{
|
|
m_dbStoreId = fields[15].GetUInt32();
|
|
m_guid = MAKE_NEW_GUID(sGroupMgr->GenerateGroupId(), 0, HIGHGUID_GROUP);
|
|
m_leaderGuid = MAKE_NEW_GUID(fields[0].GetUInt32(), 0, HIGHGUID_PLAYER);
|
|
|
|
// group leader not exist
|
|
if (!sObjectMgr->GetPlayerNameByGUID(fields[0].GetUInt32(), m_leaderName))
|
|
return;
|
|
|
|
m_lootMethod = LootMethod(fields[1].GetUInt8());
|
|
m_looterGuid = MAKE_NEW_GUID(fields[2].GetUInt32(), 0, HIGHGUID_PLAYER);
|
|
m_lootThreshold = ItemQualities(fields[3].GetUInt8());
|
|
|
|
for (uint8 i = 0; i < TARGETICONCOUNT; ++i)
|
|
m_targetIcons[i] = fields[4+i].GetUInt32();
|
|
|
|
m_groupType = GroupType(fields[12].GetUInt8());
|
|
if (m_groupType & GROUPTYPE_RAID)
|
|
_initRaidSubGroupsCounter();
|
|
|
|
uint32 diff = fields[13].GetUInt8();
|
|
if (diff >= MAX_DUNGEON_DIFFICULTY)
|
|
m_dungeonDifficulty = DUNGEON_DIFFICULTY_NORMAL;
|
|
else
|
|
m_dungeonDifficulty = Difficulty(diff);
|
|
|
|
uint32 r_diff = fields[14].GetUInt8();
|
|
if (r_diff >= MAX_RAID_DIFFICULTY)
|
|
m_raidDifficulty = RAID_DIFFICULTY_10MAN_NORMAL;
|
|
else
|
|
m_raidDifficulty = Difficulty(r_diff);
|
|
|
|
if (m_groupType & GROUPTYPE_LFG)
|
|
sLFGMgr->_LoadFromDB(fields, GetGUID());
|
|
}
|
|
|
|
void Group::LoadMemberFromDB(uint32 guidLow, uint8 memberFlags, uint8 subgroup, uint8 roles)
|
|
{
|
|
MemberSlot member;
|
|
member.guid = MAKE_NEW_GUID(guidLow, 0, HIGHGUID_PLAYER);
|
|
|
|
// skip non-existed member
|
|
if (!sObjectMgr->GetPlayerNameByGUID(member.guid, member.name))
|
|
{
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_MEMBER);
|
|
stmt->setUInt32(0, guidLow);
|
|
CharacterDatabase.Execute(stmt);
|
|
return;
|
|
}
|
|
|
|
member.group = subgroup;
|
|
member.flags = memberFlags;
|
|
member.roles = roles;
|
|
|
|
m_memberSlots.push_back(member);
|
|
|
|
SubGroupCounterIncrease(subgroup);
|
|
|
|
if (isLFGGroup())
|
|
{
|
|
LfgDungeonSet Dungeons;
|
|
Dungeons.insert(sLFGMgr->GetDungeon(GetGUID()));
|
|
sLFGMgr->SetSelectedDungeons(member.guid, Dungeons);
|
|
sLFGMgr->SetState(member.guid, sLFGMgr->GetState(GetGUID()));
|
|
}
|
|
}
|
|
|
|
void Group::ConvertToLFG()
|
|
{
|
|
m_groupType = GroupType(m_groupType | GROUPTYPE_LFG | GROUPTYPE_UNK1);
|
|
m_lootMethod = NEED_BEFORE_GREED;
|
|
if (!isBGGroup())
|
|
{
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_TYPE);
|
|
|
|
stmt->setUInt8(0, uint8(m_groupType));
|
|
stmt->setUInt32(1, m_dbStoreId);
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
SendUpdate();
|
|
}
|
|
|
|
void Group::ConvertToRaid()
|
|
{
|
|
m_groupType = GroupType(m_groupType | GROUPTYPE_RAID);
|
|
|
|
_initRaidSubGroupsCounter();
|
|
|
|
if (!isBGGroup())
|
|
{
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_TYPE);
|
|
|
|
stmt->setUInt8(0, uint8(m_groupType));
|
|
stmt->setUInt32(1, m_dbStoreId);
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
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 = ObjectAccessor::FindPlayer(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);
|
|
|
|
sScriptMgr->OnGroupInviteMember(this, player->GetGUID());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Group::AddLeaderInvite(Player* player)
|
|
{
|
|
if (!AddInvite(player))
|
|
return false;
|
|
|
|
m_leaderGuid = player->GetGUID();
|
|
m_leaderName = player->GetName();
|
|
return true;
|
|
}
|
|
|
|
void Group::RemoveInvite(Player* player)
|
|
{
|
|
if (player)
|
|
{
|
|
m_invitees.erase(player);
|
|
player->SetGroupInvite(NULL);
|
|
}
|
|
}
|
|
|
|
void Group::RemoveAllInvites()
|
|
{
|
|
for (InvitesList::iterator itr=m_invitees.begin(); itr != m_invitees.end(); ++itr)
|
|
if (*itr)
|
|
(*itr)->SetGroupInvite(NULL);
|
|
|
|
m_invitees.clear();
|
|
}
|
|
|
|
Player* Group::GetInvited(uint64 guid) const
|
|
{
|
|
for (InvitesList::const_iterator itr = m_invitees.begin(); itr != m_invitees.end(); ++itr)
|
|
{
|
|
if ((*itr) && (*itr)->GetGUID() == 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) && (*itr)->GetName() == name)
|
|
return (*itr);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool Group::AddMember(Player* player)
|
|
{
|
|
// Get first not-full group
|
|
uint8 subGroup = 0;
|
|
if (m_subGroupsCounts)
|
|
{
|
|
bool groupFound = false;
|
|
for (; subGroup < MAX_RAID_SUBGROUPS; ++subGroup)
|
|
{
|
|
if (m_subGroupsCounts[subGroup] < MAXGROUPSIZE)
|
|
{
|
|
groupFound = true;
|
|
break;
|
|
}
|
|
}
|
|
// We are raid group and no one slot is free
|
|
if (!groupFound)
|
|
return false;
|
|
}
|
|
|
|
MemberSlot member;
|
|
member.guid = player->GetGUID();
|
|
member.name = player->GetName();
|
|
member.group = subGroup;
|
|
member.flags = 0;
|
|
member.roles = 0;
|
|
m_memberSlots.push_back(member);
|
|
|
|
SubGroupCounterIncrease(subGroup);
|
|
|
|
if (player)
|
|
{
|
|
player->SetGroupInvite(NULL);
|
|
if (player->GetGroup() && isBGGroup()) //if player is in group and he is being added to BG raid group, then call SetBattlegroundRaid()
|
|
player->SetBattlegroundRaid(this, subGroup);
|
|
else if (player->GetGroup()) //if player is in bg raid and we are adding him to normal group, then call SetOriginalGroup()
|
|
player->SetOriginalGroup(this, subGroup);
|
|
else //if player is not in group, then call set group
|
|
player->SetGroup(this, subGroup);
|
|
|
|
// if the same group invites the player back, cancel the homebind timer
|
|
InstanceGroupBind* bind = GetBoundInstance(player);
|
|
if (bind && bind->save->GetInstanceId() == player->GetInstanceId())
|
|
player->m_InstanceValid = true;
|
|
}
|
|
|
|
if (!isRaidGroup()) // reset targetIcons for non-raid-groups
|
|
{
|
|
for (uint8 i = 0; i < TARGETICONCOUNT; ++i)
|
|
m_targetIcons[i] = 0;
|
|
}
|
|
|
|
// insert into the table if we're not a battleground group
|
|
if (!isBGGroup())
|
|
{
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GROUP_MEMBER);
|
|
|
|
stmt->setUInt32(0, m_dbStoreId);
|
|
stmt->setUInt32(1, GUID_LOPART(member.guid));
|
|
stmt->setUInt8(2, member.flags);
|
|
stmt->setUInt8(3, member.group);
|
|
stmt->setUInt8(4, member.roles);
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
|
|
}
|
|
|
|
SendUpdate();
|
|
sScriptMgr->OnGroupAddMember(this, player->GetGUID());
|
|
|
|
if (player)
|
|
{
|
|
if (!IsLeader(player->GetGUID()) && !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();
|
|
|
|
{
|
|
// Broadcast new player group member fields to rest of the group
|
|
player->SetFieldNotifyFlag(UF_FLAG_PARTY_MEMBER);
|
|
|
|
UpdateData groupData;
|
|
WorldPacket groupDataPacket;
|
|
|
|
// Broadcast group members' fields to player
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next())
|
|
{
|
|
if (itr->getSource() == player)
|
|
continue;
|
|
|
|
if (Player* member = itr->getSource())
|
|
{
|
|
if (player->HaveAtClient(member))
|
|
{
|
|
member->SetFieldNotifyFlag(UF_FLAG_PARTY_MEMBER);
|
|
member->BuildValuesUpdateBlockForPlayer(&groupData, player);
|
|
member->RemoveFieldNotifyFlag(UF_FLAG_PARTY_MEMBER);
|
|
}
|
|
|
|
if (member->HaveAtClient(player))
|
|
{
|
|
UpdateData newData;
|
|
WorldPacket newDataPacket;
|
|
player->BuildValuesUpdateBlockForPlayer(&newData, member);
|
|
member->SendDirectMessage(&newDataPacket);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (groupData.HasData())
|
|
{
|
|
groupData.BuildPacket(&groupDataPacket);
|
|
player->SendDirectMessage(&groupDataPacket);
|
|
}
|
|
|
|
player->RemoveFieldNotifyFlag(UF_FLAG_PARTY_MEMBER);
|
|
}
|
|
|
|
if (m_maxEnchantingLevel < player->GetSkillValue(SKILL_ENCHANTING))
|
|
m_maxEnchantingLevel = player->GetSkillValue(SKILL_ENCHANTING);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Group::RemoveMember(uint64 guid, const RemoveMethod &method /*= GROUP_REMOVEMETHOD_DEFAULT*/, uint64 kicker /*= 0*/, const char* reason /*= NULL*/)
|
|
{
|
|
BroadcastGroupUpdate();
|
|
|
|
sScriptMgr->OnGroupRemoveMember(this, guid, method, kicker, reason);
|
|
|
|
// LFG group vote kick handled in scripts
|
|
if (isLFGGroup() && method == GROUP_REMOVEMETHOD_KICK)
|
|
return m_memberSlots.size();
|
|
|
|
// remove member and change leader (if need) only if strong more 2 members _before_ member remove (BG allow 1 member group)
|
|
if (GetMembersCount() > ((isBGGroup() || isLFGGroup()) ? 1u : 2u))
|
|
{
|
|
Player* player = ObjectAccessor::FindPlayer(guid);
|
|
if (player)
|
|
{
|
|
// Battleground group handling
|
|
if (isBGGroup())
|
|
player->RemoveFromBattlegroundRaid();
|
|
else
|
|
// Regular group
|
|
{
|
|
if (player->GetOriginalGroup() == this)
|
|
player->SetOriginalGroup(NULL);
|
|
else
|
|
player->SetGroup(NULL);
|
|
|
|
// quest related GO state dependent from raid membership
|
|
player->UpdateForQuestWorldObjects();
|
|
}
|
|
|
|
WorldPacket data;
|
|
|
|
if (method == GROUP_REMOVEMETHOD_KICK)
|
|
{
|
|
data.Initialize(SMSG_GROUP_UNINVITE, 0);
|
|
player->GetSession()->SendPacket(&data);
|
|
}
|
|
|
|
// Do we really need to send this opcode?
|
|
data.Initialize(SMSG_GROUP_LIST, 1+1+1+1+8+4+4+8);
|
|
data << uint8(0x10) << uint8(0) << uint8(0) << uint8(0);
|
|
data << uint64(m_guid) << uint32(m_counter) << uint32(0) << uint64(0);
|
|
player->GetSession()->SendPacket(&data);
|
|
|
|
_homebindIfInstance(player);
|
|
}
|
|
|
|
// Remove player from group in DB
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_MEMBER);
|
|
|
|
stmt->setUInt32(0, GUID_LOPART(guid));
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
|
|
DelinkMember(guid);
|
|
|
|
// Reevaluate group enchanter if the leaving player had enchanting skill or the player is offline
|
|
if ((player && player->GetSkillValue(SKILL_ENCHANTING)) || !player)
|
|
ResetMaxEnchantingLevel();
|
|
|
|
// Remove player from loot rolls
|
|
for (Rolls::iterator it = RollId.begin(); it != RollId.end(); ++it)
|
|
{
|
|
Roll* roll = *it;
|
|
Roll::PlayerVote::iterator itr2 = roll->playerVote.find(guid);
|
|
if (itr2 == roll->playerVote.end())
|
|
continue;
|
|
|
|
if (itr2->second == GREED || itr2->second == DISENCHANT)
|
|
--roll->totalGreed;
|
|
else if (itr2->second == NEED)
|
|
--roll->totalNeed;
|
|
else if (itr2->second == PASS)
|
|
--roll->totalPass;
|
|
|
|
if (itr2->second != NOT_VALID)
|
|
--roll->totalPlayersRolling;
|
|
|
|
roll->playerVote.erase(itr2);
|
|
|
|
CountRollVote(guid, roll->itemGUID, MAX_ROLL_TYPE);
|
|
}
|
|
|
|
// Update subgroups
|
|
member_witerator slot = _getMemberWSlot(guid);
|
|
if (slot != m_memberSlots.end())
|
|
{
|
|
SubGroupCounterDecrease(slot->group);
|
|
m_memberSlots.erase(slot);
|
|
}
|
|
|
|
// Pick new leader if necessary
|
|
if (m_leaderGuid == guid)
|
|
{
|
|
for (member_witerator itr = m_memberSlots.begin(); itr != m_memberSlots.end(); ++itr)
|
|
{
|
|
if (ObjectAccessor::FindPlayer(itr->guid))
|
|
{
|
|
ChangeLeader(itr->guid);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
SendUpdate();
|
|
|
|
if (isLFGGroup() && GetMembersCount() == 1)
|
|
{
|
|
Player* Leader = ObjectAccessor::FindPlayer(GetLeaderGUID());
|
|
LFGDungeonEntry const* dungeon = sLFGDungeonStore.LookupEntry(sLFGMgr->GetDungeon(GetGUID()));
|
|
if ((Leader && dungeon && Leader->isAlive() && Leader->GetMapId() != uint32(dungeon->map)) || !dungeon)
|
|
{
|
|
Disband();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (m_memberMgr.getSize() < ((isLFGGroup() || isBGGroup()) ? 1u : 2u))
|
|
Disband();
|
|
|
|
return true;
|
|
}
|
|
// If group size before player removal <= 2 then disband it
|
|
else
|
|
{
|
|
Disband();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Group::ChangeLeader(uint64 guid)
|
|
{
|
|
member_witerator slot = _getMemberWSlot(guid);
|
|
|
|
if (slot == m_memberSlots.end())
|
|
return;
|
|
|
|
Player* player = ObjectAccessor::FindPlayer(slot->guid);
|
|
|
|
// Don't allow switching leader to offline players
|
|
if (!player)
|
|
return;
|
|
|
|
sScriptMgr->OnGroupChangeLeader(this, m_leaderGuid, guid);
|
|
|
|
if (!isBGGroup())
|
|
{
|
|
// Remove the groups permanent instance bindings
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Same in the database
|
|
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_INSTANCE_PERM_BINDING);
|
|
|
|
stmt->setUInt32(0, m_dbStoreId);
|
|
stmt->setUInt32(1, player->GetGUIDLow());
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
|
|
// Copy the permanent binds from the new leader to the group
|
|
Player::ConvertInstancesToGroup(player, this, true);
|
|
|
|
// Update the group leader
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_LEADER);
|
|
|
|
stmt->setUInt32(0, player->GetGUIDLow());
|
|
stmt->setUInt32(1, m_dbStoreId);
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
m_leaderGuid = player->GetGUID();
|
|
m_leaderName = player->GetName();
|
|
ToggleGroupMemberFlag(slot, MEMBER_FLAG_ASSISTANT, false);
|
|
|
|
WorldPacket data(SMSG_GROUP_SET_LEADER, m_leaderName.size()+1);
|
|
data << slot->name;
|
|
BroadcastPacket(&data, true);
|
|
}
|
|
|
|
void Group::Disband(bool hideDestroy /* = false */)
|
|
{
|
|
sScriptMgr->OnGroupDisband(this);
|
|
|
|
Player* player;
|
|
for (member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr)
|
|
{
|
|
player = ObjectAccessor::FindPlayer(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(m_guid) << uint32(m_counter) << uint32(0) << uint64(0);
|
|
player->GetSession()->SendPacket(&data);
|
|
}
|
|
|
|
_homebindIfInstance(player);
|
|
}
|
|
RollId.clear();
|
|
m_memberSlots.clear();
|
|
|
|
RemoveAllInvites();
|
|
|
|
if (!isBGGroup())
|
|
{
|
|
SQLTransaction trans = CharacterDatabase.BeginTransaction();
|
|
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP);
|
|
stmt->setUInt32(0, m_dbStoreId);
|
|
trans->Append(stmt);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_MEMBER_ALL);
|
|
stmt->setUInt32(0, m_dbStoreId);
|
|
trans->Append(stmt);
|
|
|
|
CharacterDatabase.CommitTransaction(trans);
|
|
|
|
ResetInstances(INSTANCE_RESET_GROUP_DISBAND, false, NULL);
|
|
ResetInstances(INSTANCE_RESET_GROUP_DISBAND, true, NULL);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_LFG_DATA);
|
|
stmt->setUInt32(0, m_dbStoreId);
|
|
CharacterDatabase.Execute(stmt);
|
|
|
|
sGroupMgr->FreeGroupDbStoreId(this);
|
|
}
|
|
|
|
sGroupMgr->RemoveGroup(this);
|
|
delete this;
|
|
}
|
|
|
|
/*********************************************************/
|
|
/*** 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 << uint64(r.itemGUID); // guid of rolled item
|
|
data << uint32(mapid); // 3.3.3 mapid
|
|
data << uint32(r.itemSlot); // itemslot
|
|
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"
|
|
data << uint8(r.rollVoteMask); // roll type mask
|
|
|
|
for (Roll::PlayerVote::const_iterator itr=r.playerVote.begin(); itr != r.playerVote.end(); ++itr)
|
|
{
|
|
Player* p = ObjectAccessor::FindPlayer(itr->first);
|
|
if (!p || !p->GetSession())
|
|
continue;
|
|
|
|
if (itr->second == NOT_EMITED_YET)
|
|
p->GetSession()->SendPacket(&data);
|
|
}
|
|
}
|
|
|
|
void Group::SendLootStartRollToPlayer(uint32 countDown, uint32 mapId, Player* p, bool canNeed, Roll const& r)
|
|
{
|
|
if (!p || !p->GetSession())
|
|
return;
|
|
|
|
WorldPacket data(SMSG_LOOT_START_ROLL, (8 + 4 + 4 + 4 + 4 + 4 + 4 + 1));
|
|
data << uint64(r.itemGUID); // guid of rolled item
|
|
data << uint32(mapId); // 3.3.3 mapid
|
|
data << uint32(r.itemSlot); // itemslot
|
|
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"
|
|
uint8 voteMask = r.rollVoteMask;
|
|
if (!canNeed)
|
|
voteMask &= ~ROLL_FLAG_TYPE_NEED;
|
|
data << uint8(voteMask); // roll type mask
|
|
|
|
p->GetSession()->SendPacket(&data);
|
|
}
|
|
|
|
void Group::SendLootRoll(uint64 sourceGuid, uint64 targetGuid, uint8 rollNumber, uint8 rollType, Roll const& roll)
|
|
{
|
|
WorldPacket data(SMSG_LOOT_ROLL, (8+4+8+4+4+4+1+1+1));
|
|
data << uint64(sourceGuid); // guid of the item rolled
|
|
data << uint32(roll.itemSlot); // slot
|
|
data << uint64(targetGuid);
|
|
data << uint32(roll.itemid); // the itemEntryId for the item that shall be rolled for
|
|
data << uint32(roll.itemRandomSuffix); // randomSuffix
|
|
data << uint32(roll.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); // 1: "You automatically passed on: %s because you cannot loot that item." - Possibly used in need befor greed
|
|
|
|
for (Roll::PlayerVote::const_iterator itr = roll.playerVote.begin(); itr != roll.playerVote.end(); ++itr)
|
|
{
|
|
Player* p = ObjectAccessor::FindPlayer(itr->first);
|
|
if (!p || !p->GetSession())
|
|
continue;
|
|
|
|
if (itr->second != NOT_VALID)
|
|
p->GetSession()->SendPacket(&data);
|
|
}
|
|
}
|
|
|
|
void Group::SendLootRollWon(uint64 sourceGuid, uint64 targetGuid, uint8 rollNumber, uint8 rollType, Roll const& roll)
|
|
{
|
|
WorldPacket data(SMSG_LOOT_ROLL_WON, (8+4+4+4+4+8+1+1));
|
|
data << uint64(sourceGuid); // guid of the item rolled
|
|
data << uint32(roll.itemSlot); // slot
|
|
data << uint32(roll.itemid); // the itemEntryId for the item that shall be rolled for
|
|
data << uint32(roll.itemRandomSuffix); // randomSuffix
|
|
data << uint32(roll.itemRandomPropId); // Item random property
|
|
data << uint64(targetGuid); // guid of the player who won.
|
|
data << uint8(rollNumber); // rollnumber realted to SMSG_LOOT_ROLL
|
|
data << uint8(rollType); // rollType related to SMSG_LOOT_ROLL
|
|
|
|
for (Roll::PlayerVote::const_iterator itr = roll.playerVote.begin(); itr != roll.playerVote.end(); ++itr)
|
|
{
|
|
Player* p = ObjectAccessor::FindPlayer(itr->first);
|
|
if (!p || !p->GetSession())
|
|
continue;
|
|
|
|
if (itr->second != NOT_VALID)
|
|
p->GetSession()->SendPacket(&data);
|
|
}
|
|
}
|
|
|
|
void Group::SendLootAllPassed(Roll const& roll)
|
|
{
|
|
WorldPacket data(SMSG_LOOT_ALL_PASSED, (8+4+4+4+4));
|
|
data << uint64(roll.itemGUID); // Guid of the item rolled
|
|
data << uint32(roll.itemSlot); // Item loot slot
|
|
data << uint32(roll.itemid); // The itemEntryId for the item that shall be rolled for
|
|
data << uint32(roll.itemRandomPropId); // Item random property ID
|
|
data << uint32(roll.itemRandomSuffix); // Item random suffix ID
|
|
|
|
for (Roll::PlayerVote::const_iterator itr = roll.playerVote.begin(); itr != roll.playerVote.end(); ++itr)
|
|
{
|
|
Player* player = ObjectAccessor::FindPlayer(itr->first);
|
|
if (!player || !player->GetSession())
|
|
continue;
|
|
|
|
if (itr->second != NOT_VALID)
|
|
player->GetSession()->SendPacket(&data);
|
|
}
|
|
}
|
|
|
|
// notify group members which player is the allowed looter for the given creature
|
|
void Group::SendLooter(Creature* creature, Player* groupLooter)
|
|
{
|
|
ASSERT(creature);
|
|
|
|
WorldPacket data(SMSG_LOOT_LIST, (8+8));
|
|
data << uint64(creature->GetGUID());
|
|
data << uint8(0); // unk1
|
|
|
|
if (groupLooter)
|
|
data.append(groupLooter->GetPackGUID());
|
|
else
|
|
data << uint8(0);
|
|
|
|
BroadcastPacket(&data, false);
|
|
}
|
|
|
|
void Group::GroupLoot(Loot* loot, WorldObject* pLootedObject)
|
|
{
|
|
std::vector<LootItem>::iterator i;
|
|
ItemTemplate const* item;
|
|
uint8 itemSlot = 0;
|
|
|
|
for (i = loot->items.begin(); i != loot->items.end(); ++i, ++itemSlot)
|
|
{
|
|
if (i->freeforall)
|
|
continue;
|
|
|
|
item = sObjectMgr->GetItemTemplate(i->itemid);
|
|
if (!item)
|
|
{
|
|
//sLog->outDebug("Group::GroupLoot: missing item prototype for item with id: %d", i->itemid);
|
|
continue;
|
|
}
|
|
|
|
//roll for over-threshold item if it's one-player loot
|
|
if (item->Quality >= uint32(m_lootThreshold))
|
|
{
|
|
uint64 newitemGUID = MAKE_NEW_GUID(sObjectMgr->GenerateLowGuid(HIGHGUID_ITEM), 0, HIGHGUID_ITEM);
|
|
Roll* r = new Roll(newitemGUID, *i);
|
|
|
|
//a vector is filled with only near party members
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next())
|
|
{
|
|
Player* member = itr->getSource();
|
|
if (!member || !member->GetSession())
|
|
continue;
|
|
if (i->AllowedForPlayer(member))
|
|
{
|
|
if (member->IsWithinDistInMap(pLootedObject, sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE), false))
|
|
{
|
|
r->totalPlayersRolling++;
|
|
|
|
if (member->GetPassOnGroupLoot())
|
|
{
|
|
r->playerVote[member->GetGUID()] = PASS;
|
|
r->totalPass++;
|
|
// can't broadcast the pass now. need to wait until all rolling players are known.
|
|
}
|
|
else
|
|
r->playerVote[member->GetGUID()] = NOT_EMITED_YET;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (r->totalPlayersRolling > 0)
|
|
{
|
|
r->setLoot(loot);
|
|
r->itemSlot = itemSlot;
|
|
if (item->DisenchantID && m_maxEnchantingLevel >= item->RequiredDisenchantSkill)
|
|
r->rollVoteMask |= ROLL_FLAG_TYPE_DISENCHANT;
|
|
|
|
loot->items[itemSlot].is_blocked = true;
|
|
|
|
// If there is any "auto pass", broadcast the pass now.
|
|
if (r->totalPass)
|
|
{
|
|
for (Roll::PlayerVote::const_iterator itr=r->playerVote.begin(); itr != r->playerVote.end(); ++itr)
|
|
{
|
|
Player* p = ObjectAccessor::FindPlayer(itr->first);
|
|
if (!p || !p->GetSession())
|
|
continue;
|
|
|
|
if (itr->second == PASS)
|
|
SendLootRoll(newitemGUID, p->GetGUID(), 128, ROLL_PASS, *r);
|
|
}
|
|
}
|
|
|
|
SendLootStartRoll(60000, pLootedObject->GetMapId(), *r);
|
|
|
|
RollId.push_back(r);
|
|
|
|
if (Creature* creature = pLootedObject->ToCreature())
|
|
{
|
|
creature->m_groupLootTimer = 60000;
|
|
creature->lootingGroupLowGUID = GetLowGUID();
|
|
}
|
|
else if (GameObject* go = pLootedObject->ToGameObject())
|
|
{
|
|
go->m_groupLootTimer = 60000;
|
|
go->lootingGroupLowGUID = GetLowGUID();
|
|
}
|
|
}
|
|
else
|
|
delete r;
|
|
}
|
|
else
|
|
i->is_underthreshold = true;
|
|
}
|
|
|
|
for (i = loot->quest_items.begin(); i != loot->quest_items.end(); ++i, ++itemSlot)
|
|
{
|
|
if (!i->follow_loot_rules)
|
|
continue;
|
|
|
|
item = sObjectMgr->GetItemTemplate(i->itemid);
|
|
if (!item)
|
|
{
|
|
//sLog->outDebug("Group::GroupLoot: missing item prototype for item with id: %d", i->itemid);
|
|
continue;
|
|
}
|
|
|
|
uint64 newitemGUID = MAKE_NEW_GUID(sObjectMgr->GenerateLowGuid(HIGHGUID_ITEM), 0, HIGHGUID_ITEM);
|
|
Roll* r = new Roll(newitemGUID, *i);
|
|
|
|
//a vector is filled with only near party members
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next())
|
|
{
|
|
Player* member = itr->getSource();
|
|
if (!member || !member->GetSession())
|
|
continue;
|
|
|
|
if (i->AllowedForPlayer(member))
|
|
{
|
|
if (member->IsWithinDistInMap(pLootedObject, sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE), false))
|
|
{
|
|
r->totalPlayersRolling++;
|
|
r->playerVote[member->GetGUID()] = NOT_EMITED_YET;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (r->totalPlayersRolling > 0)
|
|
{
|
|
r->setLoot(loot);
|
|
r->itemSlot = itemSlot;
|
|
|
|
loot->quest_items[itemSlot - loot->items.size()].is_blocked = true;
|
|
|
|
SendLootStartRoll(60000, pLootedObject->GetMapId(), *r);
|
|
|
|
RollId.push_back(r);
|
|
|
|
if (Creature* creature = pLootedObject->ToCreature())
|
|
{
|
|
creature->m_groupLootTimer = 60000;
|
|
creature->lootingGroupLowGUID = GetLowGUID();
|
|
}
|
|
else if (GameObject* go = pLootedObject->ToGameObject())
|
|
{
|
|
go->m_groupLootTimer = 60000;
|
|
go->lootingGroupLowGUID = GetLowGUID();
|
|
}
|
|
}
|
|
else
|
|
delete r;
|
|
}
|
|
}
|
|
|
|
void Group::NeedBeforeGreed(Loot* loot, WorldObject* lootedObject)
|
|
{
|
|
ItemTemplate const* item;
|
|
uint8 itemSlot = 0;
|
|
for (std::vector<LootItem>::iterator i = loot->items.begin(); i != loot->items.end(); ++i, ++itemSlot)
|
|
{
|
|
if (i->freeforall)
|
|
continue;
|
|
|
|
item = sObjectMgr->GetItemTemplate(i->itemid);
|
|
|
|
//roll for over-threshold item if it's one-player loot
|
|
if (item->Quality >= uint32(m_lootThreshold))
|
|
{
|
|
uint64 newitemGUID = MAKE_NEW_GUID(sObjectMgr->GenerateLowGuid(HIGHGUID_ITEM), 0, HIGHGUID_ITEM);
|
|
Roll* r = new Roll(newitemGUID, *i);
|
|
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next())
|
|
{
|
|
Player* playerToRoll = itr->getSource();
|
|
if (!playerToRoll || !playerToRoll->GetSession())
|
|
continue;
|
|
|
|
bool allowedForPlayer = i->AllowedForPlayer(playerToRoll);
|
|
if (allowedForPlayer && playerToRoll->IsWithinDistInMap(lootedObject, sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE), false))
|
|
{
|
|
r->totalPlayersRolling++;
|
|
if (playerToRoll->GetPassOnGroupLoot())
|
|
{
|
|
r->playerVote[playerToRoll->GetGUID()] = PASS;
|
|
r->totalPass++;
|
|
// can't broadcast the pass now. need to wait until all rolling players are known.
|
|
}
|
|
else
|
|
r->playerVote[playerToRoll->GetGUID()] = NOT_EMITED_YET;
|
|
}
|
|
}
|
|
|
|
if (r->totalPlayersRolling > 0)
|
|
{
|
|
r->setLoot(loot);
|
|
r->itemSlot = itemSlot;
|
|
if (item->DisenchantID && m_maxEnchantingLevel >= item->RequiredDisenchantSkill)
|
|
r->rollVoteMask |= ROLL_FLAG_TYPE_DISENCHANT;
|
|
|
|
if (item->Flags2 & ITEM_FLAGS_EXTRA_NEED_ROLL_DISABLED)
|
|
r->rollVoteMask &= ~ROLL_FLAG_TYPE_NEED;
|
|
|
|
loot->items[itemSlot].is_blocked = true;
|
|
|
|
//Broadcast Pass and Send Rollstart
|
|
for (Roll::PlayerVote::const_iterator itr = r->playerVote.begin(); itr != r->playerVote.end(); ++itr)
|
|
{
|
|
Player* p = ObjectAccessor::FindPlayer(itr->first);
|
|
if (!p || !p->GetSession())
|
|
continue;
|
|
|
|
if (itr->second == PASS)
|
|
SendLootRoll(newitemGUID, p->GetGUID(), 128, ROLL_PASS, *r);
|
|
else
|
|
SendLootStartRollToPlayer(60000, lootedObject->GetMapId(), p, p->CanRollForItemInLFG(item, lootedObject) == EQUIP_ERR_OK, *r);
|
|
}
|
|
|
|
RollId.push_back(r);
|
|
|
|
if (Creature* creature = lootedObject->ToCreature())
|
|
{
|
|
creature->m_groupLootTimer = 60000;
|
|
creature->lootingGroupLowGUID = GetLowGUID();
|
|
}
|
|
else if (GameObject* go = lootedObject->ToGameObject())
|
|
{
|
|
go->m_groupLootTimer = 60000;
|
|
go->lootingGroupLowGUID = GetLowGUID();
|
|
}
|
|
}
|
|
else
|
|
delete r;
|
|
}
|
|
else
|
|
i->is_underthreshold = true;
|
|
}
|
|
|
|
for (std::vector<LootItem>::iterator i = loot->quest_items.begin(); i != loot->quest_items.end(); ++i, ++itemSlot)
|
|
{
|
|
if (!i->follow_loot_rules)
|
|
continue;
|
|
|
|
item = sObjectMgr->GetItemTemplate(i->itemid);
|
|
uint64 newitemGUID = MAKE_NEW_GUID(sObjectMgr->GenerateLowGuid(HIGHGUID_ITEM), 0, HIGHGUID_ITEM);
|
|
Roll* r = new Roll(newitemGUID, *i);
|
|
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next())
|
|
{
|
|
Player* playerToRoll = itr->getSource();
|
|
if (!playerToRoll || !playerToRoll->GetSession())
|
|
continue;
|
|
|
|
bool allowedForPlayer = i->AllowedForPlayer(playerToRoll);
|
|
if (allowedForPlayer && playerToRoll->IsWithinDistInMap(lootedObject, sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE), false))
|
|
{
|
|
r->totalPlayersRolling++;
|
|
r->playerVote[playerToRoll->GetGUID()] = NOT_EMITED_YET;
|
|
}
|
|
}
|
|
|
|
if (r->totalPlayersRolling > 0)
|
|
{
|
|
r->setLoot(loot);
|
|
r->itemSlot = itemSlot;
|
|
|
|
loot->quest_items[itemSlot - loot->items.size()].is_blocked = true;
|
|
|
|
//Broadcast Pass and Send Rollstart
|
|
for (Roll::PlayerVote::const_iterator itr = r->playerVote.begin(); itr != r->playerVote.end(); ++itr)
|
|
{
|
|
Player* p = ObjectAccessor::FindPlayer(itr->first);
|
|
if (!p || !p->GetSession())
|
|
continue;
|
|
|
|
if (itr->second == PASS)
|
|
SendLootRoll(newitemGUID, p->GetGUID(), 128, ROLL_PASS, *r);
|
|
else
|
|
SendLootStartRollToPlayer(60000, lootedObject->GetMapId(), p, p->CanRollForItemInLFG(item, lootedObject) == EQUIP_ERR_OK, *r);
|
|
}
|
|
|
|
RollId.push_back(r);
|
|
|
|
if (Creature* creature = lootedObject->ToCreature())
|
|
{
|
|
creature->m_groupLootTimer = 60000;
|
|
creature->lootingGroupLowGUID = GetLowGUID();
|
|
}
|
|
else if (GameObject* go = lootedObject->ToGameObject())
|
|
{
|
|
go->m_groupLootTimer = 60000;
|
|
go->lootingGroupLowGUID = GetLowGUID();
|
|
}
|
|
}
|
|
else
|
|
delete r;
|
|
}
|
|
}
|
|
|
|
void Group::MasterLoot(Loot* /*loot*/, WorldObject* pLootedObject)
|
|
{
|
|
sLog->outDebug(LOG_FILTER_NETWORKIO, "Group::MasterLoot (SMSG_LOOT_MASTER_LIST, 330)");
|
|
|
|
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->IsWithinDistInMap(pLootedObject, sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE), false))
|
|
{
|
|
data << uint64(looter->GetGUID());
|
|
++real_count;
|
|
}
|
|
}
|
|
|
|
data.put<uint8>(0, real_count);
|
|
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next())
|
|
{
|
|
Player* looter = itr->getSource();
|
|
if (looter->IsWithinDistInMap(pLootedObject, sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE), false))
|
|
looter->GetSession()->SendPacket(&data);
|
|
}
|
|
}
|
|
|
|
void Group::CountRollVote(uint64 playerGUID, uint64 Guid, uint8 Choice)
|
|
{
|
|
Rolls::iterator rollI = GetRoll(Guid);
|
|
if (rollI == RollId.end())
|
|
return;
|
|
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;
|
|
|
|
if (roll->getLoot())
|
|
if (roll->getLoot()->items.empty())
|
|
return;
|
|
|
|
switch (Choice)
|
|
{
|
|
case ROLL_PASS: // Player choose pass
|
|
SendLootRoll(0, playerGUID, 128, ROLL_PASS, *roll);
|
|
++roll->totalPass;
|
|
itr->second = PASS;
|
|
break;
|
|
case ROLL_NEED: // player choose Need
|
|
SendLootRoll(0, playerGUID, 0, 0, *roll);
|
|
++roll->totalNeed;
|
|
itr->second = NEED;
|
|
break;
|
|
case ROLL_GREED: // player choose Greed
|
|
SendLootRoll(0, playerGUID, 128, ROLL_GREED, *roll);
|
|
++roll->totalGreed;
|
|
itr->second = GREED;
|
|
break;
|
|
case ROLL_DISENCHANT: // player choose Disenchant
|
|
SendLootRoll(0, playerGUID, 128, ROLL_DISENCHANT, *roll);
|
|
++roll->totalGreed;
|
|
itr->second = DISENCHANT;
|
|
break;
|
|
}
|
|
|
|
if (roll->totalPass + roll->totalNeed + roll->totalGreed >= roll->totalPlayersRolling)
|
|
CountTheRoll(rollI);
|
|
}
|
|
|
|
//called when roll timer expires
|
|
void Group::EndRoll(Loot* pLoot)
|
|
{
|
|
for (Rolls::iterator itr = RollId.begin(); itr != RollId.end();)
|
|
{
|
|
if ((*itr)->getLoot() == pLoot) {
|
|
CountTheRoll(itr); //i don't have to edit player votes, who didn't vote ... he will pass
|
|
itr = RollId.begin();
|
|
}
|
|
else
|
|
++itr;
|
|
}
|
|
}
|
|
|
|
void Group::CountTheRoll(Rolls::iterator rollI)
|
|
{
|
|
Roll* roll = *rollI;
|
|
if (!roll->isValid()) // is loot already deleted ?
|
|
{
|
|
RollId.erase(rollI);
|
|
delete roll;
|
|
return;
|
|
}
|
|
|
|
//end of the roll
|
|
if (roll->totalNeed > 0)
|
|
{
|
|
if (!roll->playerVote.empty())
|
|
{
|
|
uint8 maxresul = 0;
|
|
uint64 maxguid = (*roll->playerVote.begin()).first;
|
|
Player* player;
|
|
|
|
for (Roll::PlayerVote::const_iterator itr=roll->playerVote.begin(); itr != roll->playerVote.end(); ++itr)
|
|
{
|
|
if (itr->second != NEED)
|
|
continue;
|
|
|
|
uint8 randomN = urand(1, 100);
|
|
SendLootRoll(0, itr->first, randomN, ROLL_NEED, *roll);
|
|
if (maxresul < randomN)
|
|
{
|
|
maxguid = itr->first;
|
|
maxresul = randomN;
|
|
}
|
|
}
|
|
SendLootRollWon(0, maxguid, maxresul, ROLL_NEED, *roll);
|
|
player = ObjectAccessor::FindPlayer(maxguid);
|
|
|
|
if (player && player->GetSession())
|
|
{
|
|
player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED_ON_LOOT, roll->itemid, maxresul);
|
|
|
|
ItemPosCountVec dest;
|
|
LootItem* item = &(roll->itemSlot >= roll->getLoot()->items.size() ? roll->getLoot()->quest_items[roll->itemSlot - roll->getLoot()->items.size()] : roll->getLoot()->items[roll->itemSlot]);
|
|
InventoryResult 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--;
|
|
AllowedLooterSet looters = item->GetAllowedLooters();
|
|
player->StoreNewItem(dest, roll->itemid, true, item->randomPropertyId, looters);
|
|
}
|
|
else
|
|
{
|
|
item->is_blocked = false;
|
|
player->SendEquipError(msg, NULL, NULL, roll->itemid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (roll->totalGreed > 0)
|
|
{
|
|
if (!roll->playerVote.empty())
|
|
{
|
|
uint8 maxresul = 0;
|
|
uint64 maxguid = (*roll->playerVote.begin()).first;
|
|
Player* player;
|
|
RollVote rollvote = NOT_VALID;
|
|
|
|
Roll::PlayerVote::iterator itr;
|
|
for (itr = roll->playerVote.begin(); itr != roll->playerVote.end(); ++itr)
|
|
{
|
|
if (itr->second != GREED && itr->second != DISENCHANT)
|
|
continue;
|
|
|
|
uint8 randomN = urand(1, 100);
|
|
SendLootRoll(0, itr->first, randomN, itr->second, *roll);
|
|
if (maxresul < randomN)
|
|
{
|
|
maxguid = itr->first;
|
|
maxresul = randomN;
|
|
rollvote = itr->second;
|
|
}
|
|
}
|
|
SendLootRollWon(0, maxguid, maxresul, rollvote, *roll);
|
|
player = ObjectAccessor::FindPlayer(maxguid);
|
|
|
|
if (player && player->GetSession())
|
|
{
|
|
player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED_ON_LOOT, roll->itemid, maxresul);
|
|
|
|
LootItem* item = &(roll->itemSlot >= roll->getLoot()->items.size() ? roll->getLoot()->quest_items[roll->itemSlot - roll->getLoot()->items.size()] : roll->getLoot()->items[roll->itemSlot]);
|
|
|
|
if (rollvote == GREED)
|
|
{
|
|
ItemPosCountVec dest;
|
|
InventoryResult 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--;
|
|
AllowedLooterSet looters = item->GetAllowedLooters();
|
|
player->StoreNewItem(dest, roll->itemid, true, item->randomPropertyId, looters);
|
|
}
|
|
else
|
|
{
|
|
item->is_blocked = false;
|
|
player->SendEquipError(msg, NULL, NULL, roll->itemid);
|
|
}
|
|
}
|
|
else if (rollvote == DISENCHANT)
|
|
{
|
|
item->is_looted = true;
|
|
roll->getLoot()->NotifyItemRemoved(roll->itemSlot);
|
|
roll->getLoot()->unlootedCount--;
|
|
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(roll->itemid);
|
|
player->AutoStoreLoot(pProto->DisenchantID, LootTemplates_Disenchant, true);
|
|
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL, 13262); // Disenchant
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SendLootAllPassed(*roll);
|
|
|
|
// remove is_blocked so that the item is lootable by all players
|
|
LootItem* item = &(roll->itemSlot >= roll->getLoot()->items.size() ? roll->getLoot()->quest_items[roll->itemSlot - roll->getLoot()->items.size()] : roll->getLoot()->items[roll->itemSlot]);
|
|
if (item)
|
|
item->is_blocked = false;
|
|
}
|
|
|
|
RollId.erase(rollI);
|
|
delete roll;
|
|
}
|
|
|
|
void Group::SetTargetIcon(uint8 id, uint64 whoGuid, uint64 targetGuid)
|
|
{
|
|
if (id >= TARGETICONCOUNT)
|
|
return;
|
|
|
|
// clean other icons
|
|
if (targetGuid != 0)
|
|
for (int i=0; i<TARGETICONCOUNT; ++i)
|
|
if (m_targetIcons[i] == targetGuid)
|
|
SetTargetIcon(i, 0, 0);
|
|
|
|
m_targetIcons[id] = targetGuid;
|
|
|
|
WorldPacket data(MSG_RAID_TARGET_UPDATE, (1+8+1+8));
|
|
data << uint8(0); // set targets
|
|
data << uint64(whoGuid);
|
|
data << uint8(id);
|
|
data << uint64(targetGuid);
|
|
BroadcastPacket(&data, true);
|
|
}
|
|
|
|
void Group::SendTargetIconList(WorldSession* session)
|
|
{
|
|
if (!session)
|
|
return;
|
|
|
|
WorldPacket data(MSG_RAID_TARGET_UPDATE, (1+TARGETICONCOUNT*9));
|
|
data << uint8(1); // list targets
|
|
|
|
for (uint8 i = 0; i < TARGETICONCOUNT; ++i)
|
|
{
|
|
if (m_targetIcons[i] == 0)
|
|
continue;
|
|
|
|
data << uint8(i);
|
|
data << uint64(m_targetIcons[i]);
|
|
}
|
|
|
|
session->SendPacket(&data);
|
|
}
|
|
|
|
void Group::SendUpdate()
|
|
{
|
|
for (member_witerator witr = m_memberSlots.begin(); witr != m_memberSlots.end(); ++witr)
|
|
SendUpdateToPlayer(witr->guid, &(*witr));
|
|
}
|
|
|
|
void Group::SendUpdateToPlayer(uint64 playerGUID, MemberSlot* slot)
|
|
{
|
|
Player* player = ObjectAccessor::FindPlayer(playerGUID);
|
|
|
|
if (!player || !player->GetSession() || player->GetGroup() != this)
|
|
return;
|
|
|
|
// if MemberSlot wasn't provided
|
|
if (!slot)
|
|
{
|
|
member_witerator witr = _getMemberWSlot(playerGUID);
|
|
|
|
if (witr == m_memberSlots.end()) // if there is no MemberSlot for such a player
|
|
return;
|
|
|
|
slot = &(*witr);
|
|
}
|
|
|
|
WorldPacket data(SMSG_GROUP_LIST, (1+1+1+1+1+4+8+4+4+(GetMembersCount()-1)*(13+8+1+1+1+1)+8+1+8+1+1+1+1));
|
|
data << uint8(m_groupType); // group type (flags in 3.3)
|
|
data << uint8(slot->group);
|
|
data << uint8(slot->flags);
|
|
data << uint8(slot->roles);
|
|
if (isLFGGroup())
|
|
{
|
|
data << uint8(sLFGMgr->GetState(m_guid) == LFG_STATE_FINISHED_DUNGEON ? 2 : 0); // FIXME - Dungeon save status? 2 = done
|
|
data << uint32(sLFGMgr->GetDungeon(m_guid));
|
|
}
|
|
|
|
data << uint64(m_guid);
|
|
data << uint32(m_counter++); // 3.3, value increases every time this packet gets sent
|
|
data << uint32(GetMembersCount()-1);
|
|
for (member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr)
|
|
{
|
|
if (slot->guid == citr->guid)
|
|
continue;
|
|
|
|
Player* member = ObjectAccessor::FindPlayer(citr->guid);
|
|
|
|
uint8 onlineState = (member) ? MEMBER_STATUS_ONLINE : MEMBER_STATUS_OFFLINE;
|
|
onlineState = onlineState | ((isBGGroup()) ? MEMBER_STATUS_PVP : 0);
|
|
|
|
data << citr->name;
|
|
data << uint64(citr->guid); // guid
|
|
data << uint8(onlineState); // online-state
|
|
data << uint8(citr->group); // groupid
|
|
data << uint8(citr->flags); // See enum GroupMemberFlags
|
|
data << uint8(citr->roles); // Lfg Roles
|
|
}
|
|
|
|
data << uint64(m_leaderGuid); // leader guid
|
|
|
|
if (GetMembersCount() - 1)
|
|
{
|
|
data << uint8(m_lootMethod); // loot method
|
|
data << uint64(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
|
|
}
|
|
|
|
player->GetSession()->SendPacket(&data);
|
|
}
|
|
|
|
void Group::UpdatePlayerOutOfRange(Player* player)
|
|
{
|
|
if (!player || !player->IsInWorld())
|
|
return;
|
|
|
|
WorldPacket data;
|
|
player->GetSession()->BuildPartyMemberStatsChangedPacket(player, &data);
|
|
|
|
Player* member;
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next())
|
|
{
|
|
member = itr->getSource();
|
|
if (member && !member->IsWithinDist(player, member->GetSightRange(), false))
|
|
member->GetSession()->SendPacket(&data);
|
|
}
|
|
}
|
|
|
|
void Group::BroadcastPacket(WorldPacket* packet, bool ignorePlayersInBGRaid, int group, uint64 ignore)
|
|
{
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next())
|
|
{
|
|
Player* player = itr->getSource();
|
|
if (!player || (ignore != 0 && player->GetGUID() == ignore) || (ignorePlayersInBGRaid && player->GetGroup() != this))
|
|
continue;
|
|
|
|
if (player->GetSession() && (group == -1 || itr->getSubGroup() == group))
|
|
player->GetSession()->SendPacket(packet);
|
|
}
|
|
}
|
|
|
|
void Group::BroadcastReadyCheck(WorldPacket* packet)
|
|
{
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next())
|
|
{
|
|
Player* player = itr->getSource();
|
|
if (player && player->GetSession())
|
|
if (IsLeader(player->GetGUID()) || IsAssistant(player->GetGUID()))
|
|
player->GetSession()->SendPacket(packet);
|
|
}
|
|
}
|
|
|
|
void Group::OfflineReadyCheck()
|
|
{
|
|
for (member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr)
|
|
{
|
|
Player* player = ObjectAccessor::FindPlayer(citr->guid);
|
|
if (!player || !player->GetSession())
|
|
{
|
|
WorldPacket data(MSG_RAID_READY_CHECK_CONFIRM, 9);
|
|
data << uint64(citr->guid);
|
|
data << uint8(0);
|
|
BroadcastReadyCheck(&data);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Group::_setMembersGroup(uint64 guid, uint8 group)
|
|
{
|
|
member_witerator slot = _getMemberWSlot(guid);
|
|
if (slot == m_memberSlots.end())
|
|
return false;
|
|
|
|
slot->group = group;
|
|
|
|
SubGroupCounterIncrease(group);
|
|
|
|
if (!isBGGroup())
|
|
{
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_MEMBER_SUBGROUP);
|
|
|
|
stmt->setUInt8(0, group);
|
|
stmt->setUInt32(1, GUID_LOPART(guid));
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
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 sub groups both for online or offline members
|
|
void Group::ChangeMembersGroup(uint64 guid, uint8 group)
|
|
{
|
|
// Only raid groups have sub groups
|
|
if (!isRaidGroup())
|
|
return;
|
|
|
|
// Check if player is really in the raid
|
|
member_witerator slot = _getMemberWSlot(guid);
|
|
if (slot == m_memberSlots.end())
|
|
return;
|
|
|
|
// Abort if the player is already in the target sub group
|
|
uint8 prevSubGroup = GetMemberGroup(guid);
|
|
if (prevSubGroup == group)
|
|
return;
|
|
|
|
// Update the player slot with the new sub group setting
|
|
slot->group = group;
|
|
|
|
// Increase the counter of the new sub group..
|
|
SubGroupCounterIncrease(group);
|
|
|
|
// ..and decrease the counter of the previous one
|
|
SubGroupCounterDecrease(prevSubGroup);
|
|
|
|
// Preserve new sub group in database for non-raid groups
|
|
if (!isBGGroup())
|
|
{
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_MEMBER_SUBGROUP);
|
|
|
|
stmt->setUInt8(0, group);
|
|
stmt->setUInt32(1, GUID_LOPART(guid));
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
// In case the moved player is online, update the player object with the new sub group references
|
|
if (Player* player = ObjectAccessor::FindPlayer(guid))
|
|
{
|
|
if (player->GetGroup() == this)
|
|
player->GetGroupRef().setSubGroup(group);
|
|
else
|
|
{
|
|
// 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
|
|
prevSubGroup = player->GetOriginalSubGroup();
|
|
player->GetOriginalGroupRef().setSubGroup(group);
|
|
}
|
|
}
|
|
|
|
// Broadcast the changes to the group
|
|
SendUpdate();
|
|
}
|
|
|
|
// Retrieve the next Round-Roubin player for the group
|
|
//
|
|
// No update done if loot method is Master or FFA.
|
|
//
|
|
// If the RR player is not yet set for the group, the first group member becomes the round-robin player.
|
|
// If the RR player is set, the next player in group becomes the round-robin player.
|
|
//
|
|
// If ifneed is true,
|
|
// the current RR player is checked to be near the looted object.
|
|
// if yes, no update done.
|
|
// if not, he loses his turn.
|
|
void Group::UpdateLooterGuid(WorldObject* pLootedObject, 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;
|
|
}
|
|
|
|
uint64 oldLooterGUID = GetLooterGuid();
|
|
member_citerator guid_itr = _getMemberCSlot(oldLooterGUID);
|
|
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->IsWithinDistInMap(pLootedObject, sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE), false))
|
|
return;
|
|
}
|
|
++guid_itr;
|
|
}
|
|
|
|
// search next after current
|
|
Player* pNewLooter = NULL;
|
|
for (member_citerator itr = guid_itr; itr != m_memberSlots.end(); ++itr)
|
|
{
|
|
if (Player* player = ObjectAccessor::FindPlayer(itr->guid))
|
|
if (player->IsWithinDistInMap(pLootedObject, sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE), false))
|
|
{
|
|
pNewLooter = player;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!pNewLooter)
|
|
{
|
|
// search from start
|
|
for (member_citerator itr = m_memberSlots.begin(); itr != guid_itr; ++itr)
|
|
{
|
|
if (Player* player = ObjectAccessor::FindPlayer(itr->guid))
|
|
if (player->IsWithinDistInMap(pLootedObject, sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE), false))
|
|
{
|
|
pNewLooter = player;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pNewLooter)
|
|
{
|
|
if (oldLooterGUID != pNewLooter->GetGUID())
|
|
{
|
|
SetLooterGuid(pNewLooter->GetGUID());
|
|
SendUpdate();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetLooterGuid(0);
|
|
SendUpdate();
|
|
}
|
|
}
|
|
|
|
GroupJoinBattlegroundResult Group::CanJoinBattlegroundQueue(Battleground const* bgOrTemplate, BattlegroundQueueTypeId bgQueueTypeId, uint32 MinPlayerCount, uint32 /*MaxPlayerCount*/, bool isRated, uint32 arenaSlot)
|
|
{
|
|
// check if this group is LFG group
|
|
if (isLFGGroup())
|
|
return ERR_LFG_CANT_USE_BATTLEGROUND;
|
|
|
|
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();
|
|
|
|
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();
|
|
|
|
BattlegroundQueueTypeId bgQueueTypeIdRandom = BattlegroundMgr::BGQueueTypeId(BATTLEGROUND_RB, 0);
|
|
|
|
// check every member of the group to be able to join
|
|
memberscount = 0;
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next(), ++memberscount)
|
|
{
|
|
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 braket, 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
|
|
// don't let join if someone from the group is in bg queue random
|
|
if (member->InBattlegroundQueueForBattlegroundQueueType(bgQueueTypeIdRandom))
|
|
return ERR_IN_RANDOM_BG;
|
|
// don't let join to bg queue random if someone from the group is already in bg queue
|
|
if (bgOrTemplate->GetTypeID() == BATTLEGROUND_RB && member->InBattlegroundQueue())
|
|
return ERR_IN_NON_RANDOM_BG;
|
|
// 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
|
|
// check if someone in party is using dungeon system
|
|
if (member->isUsingLfg())
|
|
return ERR_LFG_CANT_USE_BATTLEGROUND;
|
|
}
|
|
|
|
// only check for MinPlayerCount since MinPlayerCount == MaxPlayerCount for arenas...
|
|
if (bgOrTemplate->isArena() && memberscount != MinPlayerCount)
|
|
return ERR_ARENA_TEAM_PARTY_SIZE;
|
|
|
|
return GroupJoinBattlegroundResult(bgOrTemplate->GetTypeID());
|
|
}
|
|
|
|
//===================================================
|
|
//============== Roll ===============================
|
|
//===================================================
|
|
|
|
void Roll::targetObjectBuildLink()
|
|
{
|
|
// called from link()
|
|
getTarget()->addLootValidatorRef(this);
|
|
}
|
|
|
|
void Group::SetDungeonDifficulty(Difficulty difficulty)
|
|
{
|
|
m_dungeonDifficulty = difficulty;
|
|
if (!isBGGroup())
|
|
{
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_DIFFICULTY);
|
|
|
|
stmt->setUInt8(0, uint8(m_dungeonDifficulty));
|
|
stmt->setUInt32(1, m_dbStoreId);
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next())
|
|
{
|
|
Player* player = itr->getSource();
|
|
if (!player->GetSession())
|
|
continue;
|
|
|
|
player->SetDungeonDifficulty(difficulty);
|
|
player->SendDungeonDifficulty(true);
|
|
}
|
|
}
|
|
|
|
void Group::SetRaidDifficulty(Difficulty difficulty)
|
|
{
|
|
m_raidDifficulty = difficulty;
|
|
if (!isBGGroup())
|
|
{
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_RAID_DIFFICULTY);
|
|
|
|
stmt->setUInt8(0, uint8(m_raidDifficulty));
|
|
stmt->setUInt32(1, m_dbStoreId);
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next())
|
|
{
|
|
Player* player = itr->getSource();
|
|
if (!player->GetSession())
|
|
continue;
|
|
|
|
player->SetRaidDifficulty(difficulty);
|
|
player->SendRaidDifficulty(true);
|
|
}
|
|
}
|
|
|
|
bool Group::InCombatToInstance(uint32 instanceId)
|
|
{
|
|
for (GroupReference* itr = GetFirstMember(); itr != NULL; itr = itr->next())
|
|
{
|
|
Player* player = itr->getSource();
|
|
if (player && !player->getAttackers().empty() && player->GetInstanceId() == instanceId && (player->GetMap()->IsRaidOrHeroicDungeon()))
|
|
for (std::set<Unit*>::const_iterator i = player->getAttackers().begin(); i != player->getAttackers().end(); ++i)
|
|
if ((*i) && (*i)->GetTypeId() == TYPEID_UNIT && (*i)->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_INSTANCE_BIND)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Group::ResetInstances(uint8 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* instanceSave = itr->second.save;
|
|
const MapEntry* entry = sMapStore.LookupEntry(itr->first);
|
|
if (!entry || entry->IsRaid() != isRaid || (!instanceSave->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(instanceSave->GetMapId(), instanceSave->GetInstanceId());
|
|
if (map && map->IsDungeon() && !(method == INSTANCE_RESET_GROUP_DISBAND && !instanceSave->CanReset()))
|
|
{
|
|
if (instanceSave->CanReset())
|
|
isEmpty = ((InstanceMap*)map)->Reset(method);
|
|
else
|
|
isEmpty = !map->HavePlayers();
|
|
}
|
|
|
|
if (SendMsgTo)
|
|
{
|
|
if (isEmpty)
|
|
SendMsgTo->SendResetInstanceSuccess(instanceSave->GetMapId());
|
|
else
|
|
SendMsgTo->SendResetInstanceFailed(0, instanceSave->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 (instanceSave->CanReset())
|
|
instanceSave->DeleteFromDB();
|
|
else
|
|
{
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_INSTANCE_BY_INSTANCE);
|
|
|
|
stmt->setUInt32(0, instanceSave->GetInstanceId());
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
|
|
// 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)
|
|
instanceSave->RemoveGroup(this);
|
|
}
|
|
else
|
|
++itr;
|
|
}
|
|
}
|
|
|
|
InstanceGroupBind* Group::GetBoundInstance(Player* player)
|
|
{
|
|
uint32 mapid = player->GetMapId();
|
|
MapEntry const* mapEntry = sMapStore.LookupEntry(mapid);
|
|
return GetBoundInstance(mapEntry);
|
|
}
|
|
|
|
InstanceGroupBind* Group::GetBoundInstance(Map* aMap)
|
|
{
|
|
// Currently spawn numbering not different from map difficulty
|
|
Difficulty difficulty = GetDifficulty(aMap->IsRaid());
|
|
|
|
// some instances only have one difficulty
|
|
GetDownscaledMapDifficultyData(aMap->GetId(), difficulty);
|
|
|
|
BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(aMap->GetId());
|
|
if (itr != m_boundInstances[difficulty].end())
|
|
return &itr->second;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
InstanceGroupBind* Group::GetBoundInstance(MapEntry const* mapEntry)
|
|
{
|
|
if (!mapEntry)
|
|
return NULL;
|
|
|
|
Difficulty difficulty = GetDifficulty(mapEntry->IsRaid());
|
|
|
|
// some instances only have one difficulty
|
|
GetDownscaledMapDifficultyData(mapEntry->MapID, difficulty);
|
|
|
|
BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapEntry->MapID);
|
|
if (itr != m_boundInstances[difficulty].end())
|
|
return &itr->second;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
InstanceGroupBind* Group::BindToInstance(InstanceSave* save, bool permanent, bool load)
|
|
{
|
|
if (!save || isBGGroup())
|
|
return NULL;
|
|
|
|
InstanceGroupBind& bind = m_boundInstances[save->GetDifficulty()][save->GetMapId()];
|
|
if (!load && (!bind.save || permanent != bind.perm || save != bind.save))
|
|
{
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_GROUP_INSTANCE);
|
|
|
|
stmt->setUInt32(0, m_dbStoreId);
|
|
stmt->setUInt32(1, save->GetInstanceId());
|
|
stmt->setBool(2, permanent);
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
if (bind.save != save)
|
|
{
|
|
if (bind.save)
|
|
bind.save->RemoveGroup(this);
|
|
save->AddGroup(this);
|
|
}
|
|
|
|
bind.save = save;
|
|
bind.perm = permanent;
|
|
if (!load)
|
|
sLog->outDebug(LOG_FILTER_MAPS, "Group::BindToInstance: Group (guid: %u, storage id: %u) is now bound to map %d, instance %d, difficulty %d",
|
|
GUID_LOPART(GetGUID()), m_dbStoreId, save->GetMapId(), save->GetInstanceId(), save->GetDifficulty());
|
|
|
|
return &bind;
|
|
}
|
|
|
|
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)
|
|
{
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_INSTANCE_BY_GUID);
|
|
|
|
stmt->setUInt32(0, m_dbStoreId);
|
|
stmt->setUInt32(1, itr->second.save->GetInstanceId());
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
itr->second.save->RemoveGroup(this); // save can become invalid
|
|
m_boundInstances[difficulty].erase(itr);
|
|
}
|
|
}
|
|
|
|
void Group::_homebindIfInstance(Player* player)
|
|
{
|
|
if (player && !player->isGameMaster() && sMapStore.LookupEntry(player->GetMapId())->IsDungeon())
|
|
player->m_InstanceValid = false;
|
|
}
|
|
|
|
void Group::BroadcastGroupUpdate(void)
|
|
{
|
|
// FG: HACK: force flags update on group leave - for values update hack
|
|
// -- not very efficient but safe
|
|
for (member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr)
|
|
{
|
|
Player* pp = ObjectAccessor::FindPlayer(citr->guid);
|
|
if (pp && pp->IsInWorld())
|
|
{
|
|
pp->ForceValuesUpdateAtIndex(UNIT_FIELD_BYTES_2);
|
|
pp->ForceValuesUpdateAtIndex(UNIT_FIELD_FACTIONTEMPLATE);
|
|
sLog->outDebug(LOG_FILTER_GENERAL, "-- Forced group value update for '%s'", pp->GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Group::ResetMaxEnchantingLevel()
|
|
{
|
|
m_maxEnchantingLevel = 0;
|
|
Player* pMember = NULL;
|
|
for (member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr)
|
|
{
|
|
pMember = ObjectAccessor::FindPlayer(citr->guid);
|
|
if (pMember && m_maxEnchantingLevel < pMember->GetSkillValue(SKILL_ENCHANTING))
|
|
m_maxEnchantingLevel = pMember->GetSkillValue(SKILL_ENCHANTING);
|
|
}
|
|
}
|
|
|
|
void Group::SetLootMethod(LootMethod method)
|
|
{
|
|
m_lootMethod = method;
|
|
}
|
|
|
|
void Group::SetLooterGuid(uint64 guid)
|
|
{
|
|
m_looterGuid = guid;
|
|
}
|
|
|
|
void Group::SetLootThreshold(ItemQualities threshold)
|
|
{
|
|
m_lootThreshold = threshold;
|
|
}
|
|
|
|
void Group::SetLfgRoles(uint64 guid, const uint8 roles)
|
|
{
|
|
member_witerator slot = _getMemberWSlot(guid);
|
|
if (slot == m_memberSlots.end())
|
|
return;
|
|
|
|
slot->roles = roles;
|
|
SendUpdate();
|
|
}
|
|
|
|
bool Group::IsFull() const
|
|
{
|
|
return isRaidGroup() ? (m_memberSlots.size() >= MAXRAIDSIZE) : (m_memberSlots.size() >= MAXGROUPSIZE);
|
|
}
|
|
|
|
bool Group::isLFGGroup() const
|
|
{
|
|
return m_groupType & GROUPTYPE_LFG;
|
|
}
|
|
|
|
bool Group::isRaidGroup() const
|
|
{
|
|
return m_groupType & GROUPTYPE_RAID;
|
|
}
|
|
|
|
bool Group::isBGGroup() const
|
|
{
|
|
return m_bgGroup != NULL;
|
|
}
|
|
|
|
bool Group::IsCreated() const
|
|
{
|
|
return GetMembersCount() > 0;
|
|
}
|
|
|
|
uint64 Group::GetLeaderGUID() const
|
|
{
|
|
return m_leaderGuid;
|
|
}
|
|
|
|
uint64 Group::GetGUID() const
|
|
{
|
|
return m_guid;
|
|
}
|
|
|
|
uint32 Group::GetLowGUID() const
|
|
{
|
|
return GUID_LOPART(m_guid);
|
|
}
|
|
|
|
const char * Group::GetLeaderName() const
|
|
{
|
|
return m_leaderName.c_str();
|
|
}
|
|
|
|
LootMethod Group::GetLootMethod() const
|
|
{
|
|
return m_lootMethod;
|
|
}
|
|
|
|
uint64 Group::GetLooterGuid() const
|
|
{
|
|
return m_looterGuid;
|
|
}
|
|
|
|
ItemQualities Group::GetLootThreshold() const
|
|
{
|
|
return m_lootThreshold;
|
|
}
|
|
|
|
bool Group::IsMember(uint64 guid) const
|
|
{
|
|
return _getMemberCSlot(guid) != m_memberSlots.end();
|
|
}
|
|
|
|
bool Group::IsLeader(uint64 guid) const
|
|
{
|
|
return (GetLeaderGUID() == guid);
|
|
}
|
|
|
|
uint64 Group::GetMemberGUID(const std::string& name)
|
|
{
|
|
for (member_citerator itr = m_memberSlots.begin(); itr != m_memberSlots.end(); ++itr)
|
|
if (itr->name == name)
|
|
return itr->guid;
|
|
return 0;
|
|
}
|
|
|
|
bool Group::IsAssistant(uint64 guid) const
|
|
{
|
|
member_citerator mslot = _getMemberCSlot(guid);
|
|
if (mslot == m_memberSlots.end())
|
|
return false;
|
|
return mslot->flags & MEMBER_FLAG_ASSISTANT;
|
|
}
|
|
|
|
bool Group::SameSubGroup(uint64 guid1, uint64 guid2) const
|
|
{
|
|
member_citerator mslot2 = _getMemberCSlot(guid2);
|
|
if (mslot2 == m_memberSlots.end())
|
|
return false;
|
|
return SameSubGroup(guid1, &*mslot2);
|
|
}
|
|
|
|
bool Group::SameSubGroup(uint64 guid1, MemberSlot const* slot2) const
|
|
{
|
|
member_citerator mslot1 = _getMemberCSlot(guid1);
|
|
if (mslot1 == m_memberSlots.end() || !slot2)
|
|
return false;
|
|
return (mslot1->group == slot2->group);
|
|
}
|
|
|
|
bool Group::HasFreeSlotSubGroup(uint8 subgroup) const
|
|
{
|
|
return (m_subGroupsCounts && m_subGroupsCounts[subgroup] < MAXGROUPSIZE);
|
|
}
|
|
|
|
|
|
uint8 Group::GetMemberGroup(uint64 guid) const
|
|
{
|
|
member_citerator mslot = _getMemberCSlot(guid);
|
|
if (mslot == m_memberSlots.end())
|
|
return (MAX_RAID_SUBGROUPS+1);
|
|
return mslot->group;
|
|
}
|
|
|
|
void Group::SetBattlegroundGroup(Battleground* bg)
|
|
{
|
|
m_bgGroup = bg;
|
|
}
|
|
|
|
void Group::SetGroupMemberFlag(uint64 guid, bool apply, GroupMemberFlags flag)
|
|
{
|
|
// Assistants, main assistants and main tanks are only available in raid groups
|
|
if (!isRaidGroup())
|
|
return;
|
|
|
|
// Check if player is really in the raid
|
|
member_witerator slot = _getMemberWSlot(guid);
|
|
if (slot == m_memberSlots.end())
|
|
return;
|
|
|
|
// Do flag specific actions, e.g ensure uniqueness
|
|
switch (flag) {
|
|
case MEMBER_FLAG_MAINASSIST:
|
|
RemoveUniqueGroupMemberFlag(MEMBER_FLAG_MAINASSIST); // Remove main assist flag from current if any.
|
|
break;
|
|
case MEMBER_FLAG_MAINTANK:
|
|
RemoveUniqueGroupMemberFlag(MEMBER_FLAG_MAINTANK); // Remove main tank flag from current if any.
|
|
break;
|
|
case MEMBER_FLAG_ASSISTANT:
|
|
break;
|
|
default:
|
|
return; // This should never happen
|
|
}
|
|
|
|
// Switch the actual flag
|
|
ToggleGroupMemberFlag(slot, flag, apply);
|
|
|
|
// Preserve the new setting in the db
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_MEMBER_FLAG);
|
|
|
|
stmt->setUInt8(0, slot->flags);
|
|
stmt->setUInt32(1, GUID_LOPART(guid));
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
|
|
// Broadcast the changes to the group
|
|
SendUpdate();
|
|
}
|
|
|
|
Difficulty Group::GetDifficulty(bool isRaid) const
|
|
{
|
|
return isRaid ? m_raidDifficulty : m_dungeonDifficulty;
|
|
}
|
|
|
|
Difficulty Group::GetDungeonDifficulty() const
|
|
{
|
|
return m_dungeonDifficulty;
|
|
}
|
|
|
|
Difficulty Group::GetRaidDifficulty() const
|
|
{
|
|
return m_raidDifficulty;
|
|
}
|
|
|
|
bool Group::isRollLootActive() const
|
|
{
|
|
return !RollId.empty();
|
|
}
|
|
|
|
Group::Rolls::iterator Group::GetRoll(uint64 Guid)
|
|
{
|
|
Rolls::iterator iter;
|
|
for (iter=RollId.begin(); iter != RollId.end(); ++iter)
|
|
if ((*iter)->itemGUID == Guid && (*iter)->isValid())
|
|
return iter;
|
|
return RollId.end();
|
|
}
|
|
|
|
void Group::LinkMember(GroupReference* pRef)
|
|
{
|
|
m_memberMgr.insertFirst(pRef);
|
|
}
|
|
|
|
void Group::DelinkMember(uint64 guid)
|
|
{
|
|
GroupReference* ref = m_memberMgr.getFirst();
|
|
while (ref)
|
|
{
|
|
GroupReference* nextRef = ref->next();
|
|
if (ref->getSource()->GetGUID() == guid)
|
|
{
|
|
ref->unlink();
|
|
break;
|
|
}
|
|
ref = nextRef;
|
|
}
|
|
}
|
|
|
|
Group::BoundInstancesMap& Group::GetBoundInstances(Difficulty difficulty)
|
|
{
|
|
return m_boundInstances[difficulty];
|
|
}
|
|
|
|
void Group::_initRaidSubGroupsCounter()
|
|
{
|
|
// Sub group counters initialization
|
|
if (!m_subGroupsCounts)
|
|
m_subGroupsCounts = new uint8[MAX_RAID_SUBGROUPS];
|
|
|
|
memset((void*)m_subGroupsCounts, 0, (MAX_RAID_SUBGROUPS)*sizeof(uint8));
|
|
|
|
for (member_citerator itr = m_memberSlots.begin(); itr != m_memberSlots.end(); ++itr)
|
|
++m_subGroupsCounts[itr->group];
|
|
}
|
|
|
|
Group::member_citerator Group::_getMemberCSlot(uint64 Guid) const
|
|
{
|
|
for (member_citerator itr = m_memberSlots.begin(); itr != m_memberSlots.end(); ++itr)
|
|
if (itr->guid == Guid)
|
|
return itr;
|
|
return m_memberSlots.end();
|
|
}
|
|
|
|
Group::member_witerator Group::_getMemberWSlot(uint64 Guid)
|
|
{
|
|
for (member_witerator itr = m_memberSlots.begin(); itr != m_memberSlots.end(); ++itr)
|
|
if (itr->guid == Guid)
|
|
return itr;
|
|
return m_memberSlots.end();
|
|
}
|
|
|
|
void Group::SubGroupCounterIncrease(uint8 subgroup)
|
|
{
|
|
if (m_subGroupsCounts)
|
|
++m_subGroupsCounts[subgroup];
|
|
}
|
|
|
|
void Group::SubGroupCounterDecrease(uint8 subgroup)
|
|
{
|
|
if (m_subGroupsCounts)
|
|
--m_subGroupsCounts[subgroup];
|
|
}
|
|
|
|
void Group::RemoveUniqueGroupMemberFlag(GroupMemberFlags flag)
|
|
{
|
|
for (member_witerator itr = m_memberSlots.begin(); itr != m_memberSlots.end(); ++itr)
|
|
if (itr->flags & flag)
|
|
itr->flags &= ~flag;
|
|
}
|
|
|
|
void Group::ToggleGroupMemberFlag(member_witerator slot, uint8 flag, bool apply)
|
|
{
|
|
if (apply)
|
|
slot->flags |= flag;
|
|
else
|
|
slot->flags &= ~flag;
|
|
}
|
|
|