eqemu-server/zone/groups.cpp
Mitch Freeman 45da8cab61
[Bots] Add Basic Bot Raiding Functionality (#2782)
* Fix for GENERIC_9_STRINGS

* Add Bot Heal Message Display

Creates a new rule to display Bot heal messages to the Bot Owner

* 2021-03-25 11L04pm

Spell and Heal Rule added to allow for Bot spell and heal damage to be sent to the Bot Owner's Group.  Also added a check to remove duplicate message for #damage on self.

* Update .gitignore

* BOT work

Added BOT logging damage/heals to owner
Added BOT message to owner for harmony fails
Made var Critical global to remove duplicate crit messages
Added a NULL check to Mob:GetCleanname()

* Bot Group Work

Fixed botid=charid spawn on zone issue
Added a group_list update on zone to refresh from database to fix a dangling pointer to a Bot object that was camped but was previously in a group within the zone being entered.
Modified Bot::ProcessBotGroupInvite to use the client of the bot when doing the Bot initialization so that a leader can invite another owner's Bot

* Jan 4

Basic structure in place for Raid::AddBot though not working

* Basement Jan 5

* End of day Jan 5
Working Raid Invite to a Bot.

* Update to Client::QueuePacket to not attempt to send a packet to a BoT.  Not clean, but a broad solution.

* Updated Raid::VerifyRaid

* Some Bot Raid working

* Before VS Crash

* Use Case 1, 2, 3,4,7 working.
Need to fix 5, 6, 8

* Work on usecase 5

* A few more use cases working

* New work on Raid invite with a invitor having a group

* Bot Raid inviting working for all use cases

* A few changes

* end of day jan 10

* Jan 11

* end of day Jan 11

* Bot Invite/Accept cleanup

* Start of moving raid bot functions to their own methods

* More bot raid changes

* More raid spell work

* end of day Jan 16

* spawn work

* Spawn on login working

* End of Day Jan 18

* Raid leader and mana/hp updates fixed

* Spell Tracking

* Issue with Bot Death in raid when casted upon.  1741 raid.cpp

* Bot Death fixed and few other crashes

* Working on botgroup removal

* Bot Disbanding Work 90%

* Looks like BOTs are working

* Fixed a bot crash

* bug tracing on entity list mismatch

* safe_delete resoves problem.  No to track down leak

* seems to be working

* Memory corruption found - sending packets to BoTs using Client class

* added Raid::IsRaidMemberBot()

* Update p_raid_instance

* g3

* Final - Bot Raid Working

* Fixed IsRaidMemberBot to remove memory leak
Fixed altcombat crash though RaidMainAssist (428) needs fixing

* add RaidMember.IsBot

* Repaired IsBot function to be more preformant.  Now works on standard performance machine

* Fixed Bard AE Target Spells
Removed assert for buffs

* updated based on Feb 2022 master updates

* Added bot_db_updates and version increment

* Cleanup of bot raid work and inclusion of bot_raid in cmake

* Delete .gitignore

* Revert "Delete .gitignore"

This reverts commit 8523658d3bacdc068bcafaa652d2100afecddfc2.

* Fixed a packet issue

* Merged upstream/master

Merged upstream/master and removed ifdef BOTS as per recent dev approach for BOTS.  Functionality is there, compiles and tests ok.  A few problems to be resolved though this is a good baseline.

* Added sql update for raid_members to add isbot

* Updated Bot Follow Function

Bot will now follow the Group Leader if IsClient, otherwise follows the Bot Owner

* Updates to Bot Raid System

When camping a client, remove them from the raid.  If they are leader, place leadership to the next client.
Update a few crash checks in bot_raid.cpp

* [BOTS] Added RuleB Enabled checks and updated base repo for raid_members

Updated several RuleB(Bots, Enabled) checks
Updated the base repo to be autogenerated.
Raid functionality should work with a non-bots enabled database.

* Few quick updates

* Updates

Corrected a number of comments.  Compiled and tested against bot and non-bot database though requires the isbot column in raid_members for both.
Moved the db update out of the bot stream to make bot check code easier.

* Formatting and other small updates

* A few more RuleB(Bots, Enabled) additions

* Fix issue with conflict of bot ID versus character ID.

* Delete CMakeSettings.json

* Comment Updates and other

Several updates including
- fixed comments from PR
- added id to raid_members and unique index on name to avoid botid and charid conflicts
- updated a few raid functions for iterators
- reordered several raid operations to ensure send leader packet to be the last item to ensure proper updating on the client
- update sql to use Replace instead of Insert for botid conflicting with charid

* Exploit fix for Raid Bots

Added item from @Nite to disallow spawning or camping bots if Raid is engaged.  Avoids abusive situations.

* Initial Commit

* fix Raid Window after zoning

The raid window was not fully updating for clients not in the zone.

* Cleanup

* Update

Fixed comments

* Resolve crash for MOTD

Fixed a crash situation sending a raid MOTD to BOTS.

* Update ruletypes.h

* Updated to resolve a few recent comments

Fixed some comments within attack.cpp

* fix sql query

* update repository

* prevent duplicate entries in raid after group invite, and cleanup

* fixes for botgroups not following, and add already in raid messages.

* fix messagestring

* fixes

* Cleanup, and resolving issues with disbanding

* refactoring

* more cleanup/fixing.

* fixes for removing from ground in raid

* Refactoring/fixing multiple clients

* fix for compiling

* Bugs from refactoring fixed

* Testing completed, cleaning up unwanted items/duplicate code.

* Cleaned up AICastSpell

* fix typos

* Refactoring

* Adding Raid checks to AI_Process/cleanup

* Fix a typo

Was getting a SQL error on BOT spawn.  Fixed typo.

* fix for crash

* Fixed crash when inviting player, more refactoring

* AI_Process Refactoring work

* More Refactoring/fixes for follow

* Finish Refactoring AI_Process

* cleanup

* cleanup

* cleanup

* cleanup

* fix melee attack loop

* fix for leashowner.

* fix for leashowner.

* Bots persist in raid after client death/LD/Camp

* Fix Bot Groups when zoning after death.

* Fix Bots in group following after client death

* remove unnecessary query

* Allow Raid members to invite Bots if owner is in raid. cleanup

* optimization of raid verification

* remove this

* Code Cleanup

* formatting

* formatting

* formatting

* fix for macro

* add return for TryClassAttacks

* fix query

* fix for crash

* restrict camping/spawn in combat.

* Fix other crash issue.

* update learnmembers to use Strings::To, cleanup magic numbers

* fix for merge.

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
Co-authored-by: Alex King <89047260+Kinglykrab@users.noreply.github.com>
Co-authored-by: Aeadoin <109764533+Aeadoin@users.noreply.github.com>
2023-03-17 11:19:59 -04:00

2489 lines
60 KiB
C++

/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org)
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; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
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/global_define.h"
#include "../common/eqemu_logsys.h"
#include "expedition.h"
#include "masterentity.h"
#include "../common/packet_functions.h"
#include "../common/packet_dump.h"
#include "../common/strings.h"
#include "worldserver.h"
#include "string_ids.h"
#include "../common/events/player_event_logs.h"
#include "../common/repositories/group_id_repository.h"
extern EntityList entity_list;
extern WorldServer worldserver;
/*
note about how groups work:
A group contains 2 list, a list of pointers to members and a
list of member names. All members of a group should have their
name in the membername array, whether they are in the zone or not.
Only members in this zone will have non-null pointers in the
members array.
*/
//create a group which should already exist in the database
Group::Group(uint32 gid)
: GroupIDConsumer(gid)
{
leader = nullptr;
mentoree = nullptr;
memset(members,0,sizeof(Mob*) * MAX_GROUP_MEMBERS);
AssistTargetID = 0;
TankTargetID = 0;
PullerTargetID = 0;
disbandcheck = false;
memset(&LeaderAbilities, 0, sizeof(GroupLeadershipAA_Struct));
uint32 i;
for(i=0;i<MAX_GROUP_MEMBERS;i++)
{
memset(membername[i],0,64);
MemberRoles[i] = 0;
}
if(gid != 0) {
if(!LearnMembers())
SetID(0);
}
for(int i = 0; i < MAX_MARKED_NPCS; ++i)
MarkedNPCs[i] = 0;
NPCMarkerID = 0;
m_autohatermgr.SetOwner(nullptr, this, nullptr);
}
//creating a new group
Group::Group(Mob* leader)
: GroupIDConsumer()
{
memset(members, 0, sizeof(members));
members[0] = leader;
leader->SetGrouped(true);
SetLeader(leader);
AssistTargetID = 0;
TankTargetID = 0;
PullerTargetID = 0;
disbandcheck = false;
memset(&LeaderAbilities, 0, sizeof(GroupLeadershipAA_Struct));
mentoree = nullptr;
uint32 i;
for(i=0;i<MAX_GROUP_MEMBERS;i++)
{
memset(membername[i],0,64);
MemberRoles[i] = 0;
}
strcpy(membername[0],leader->GetName());
if(leader->IsClient())
strcpy(leader->CastToClient()->GetPP().groupMembers[0],leader->GetName());
for(int i = 0; i < MAX_MARKED_NPCS; ++i)
MarkedNPCs[i] = 0;
NPCMarkerID = 0;
m_autohatermgr.SetOwner(nullptr, this, nullptr);
}
Group::~Group()
{
for(int i = 0; i < MAX_MARKED_NPCS; ++i)
if(MarkedNPCs[i])
{
Mob* m = entity_list.GetMob(MarkedNPCs[i]);
if(m)
m->IsTargeted(-1);
}
}
//Split money used in OP_Split (/split and /autosplit).
void Group::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter) {
//avoid unneeded work
if (
!copper &&
!silver &&
!gold &&
!platinum
) {
return;
}
uint8 member_count = 0;
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) {
// Don't split with Mercs or Bots
if (members[i] && members[i]->IsClient()) {
member_count++;
}
}
if (!member_count) {
return;
}
uint32 modifier;
if (member_count > 1) {
modifier = platinum % member_count;
if (modifier) {
platinum -= modifier;
gold += 10 * modifier;
}
modifier = gold % member_count;
if (modifier) {
gold -= modifier;
silver += 10 * modifier;
}
modifier = silver % member_count;
if (modifier) {
silver -= modifier;
copper += 10 * modifier;
}
}
auto copper_split = copper / member_count;
auto silver_split = silver / member_count;
auto gold_split = gold / member_count;
auto platinum_split = platinum / member_count;
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (members[i] && members[i]->IsClient()) { // If Group Member is Client
members[i]->CastToClient()->AddMoneyToPP(
copper_split,
silver_split,
gold_split,
platinum_split,
true
);
if (player_event_logs.IsEventEnabled(PlayerEvent::SPLIT_MONEY)) {
auto e = PlayerEvent::SplitMoneyEvent{
.copper = copper_split,
.silver = silver_split,
.gold = gold_split,
.platinum = platinum_split,
.player_money_balance = members[i]->CastToClient()->GetCarriedMoney(),
};
RecordPlayerEventLogWithClient(members[i]->CastToClient(), PlayerEvent::SPLIT_MONEY, e);
}
members[i]->CastToClient()->MessageString(
Chat::MoneySplit,
YOU_RECEIVE_AS_SPLIT,
Strings::Money(
platinum_split,
gold_split,
silver_split,
copper_split
).c_str()
);
}
}
}
bool Group::AddMember(Mob* newmember, const char *NewMemberName, uint32 CharacterID, bool ismerc)
{
bool InZone = true;
// This method should either be passed a Mob*, if the new member is in this zone, or a nullptr Mob*
// and the name and CharacterID of the new member, if they are out of zone.
if(!newmember && !NewMemberName)
{
return false;
}
if(GroupCount() >= MAX_GROUP_MEMBERS) //Sanity check for merging groups together.
{
return false;
}
if(!newmember)
{
InZone = false;
}
else
{
NewMemberName = newmember->GetCleanName();
if(newmember->IsClient())
{
CharacterID = newmember->CastToClient()->CharacterID();
}
if(newmember->IsMerc())
{
Client* owner = newmember->CastToMerc()->GetMercOwner();
if(owner)
{
CharacterID = owner->CastToClient()->CharacterID();
}
ismerc = true;
}
}
// See if they are already in the group
uint32 i = 0;
for (i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if (!strcasecmp(membername[i], NewMemberName))
{
return false;
}
}
// Put them in the group
for (i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if (membername[i][0] == '\0')
{
if(InZone)
{
members[i] = newmember;
}
strcpy(membername[i], NewMemberName);
MemberRoles[i] = 0;
break;
}
}
// Is this even possible based on the above loops? Remove?
if (i == MAX_GROUP_MEMBERS)
{
return false;
}
int x=1;
//build the template join packet
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct));
GroupJoin_Struct* gj = (GroupJoin_Struct*) outapp->pBuffer;
strcpy(gj->membername, NewMemberName);
gj->action = groupActJoin;
gj->leader_aas = LeaderAbilities;
for (i = 0;i < MAX_GROUP_MEMBERS; i++)
{
if (members[i] != nullptr && members[i] != newmember)
{
//fill in group join & send it
strcpy(gj->yourname, members[i]->GetCleanName());
if(members[i]->IsClient())
{
members[i]->CastToClient()->QueuePacket(outapp);
//put new member into existing group members' list(s)
strcpy(members[i]->CastToClient()->GetPP().groupMembers[GroupCount()-1], NewMemberName);
}
//put existing group member(s) into the new member's list
if(InZone && newmember->IsClient())
{
if(IsLeader(members[i]))
{
strcpy(newmember->CastToClient()->GetPP().groupMembers[0], members[i]->GetCleanName());
}
else
{
strcpy(newmember->CastToClient()->GetPP().groupMembers[x], members[i]->GetCleanName());
x++;
}
}
}
}
if(InZone)
{
//put new member in his own list.
newmember->SetGrouped(true);
if(newmember->IsClient())
{
strcpy(newmember->CastToClient()->GetPP().groupMembers[x], NewMemberName);
newmember->CastToClient()->Save();
database.SetGroupID(NewMemberName, GetID(), newmember->CastToClient()->CharacterID(), false);
SendMarkedNPCsToMember(newmember->CastToClient());
NotifyMainTank(newmember->CastToClient(), 1);
NotifyMainAssist(newmember->CastToClient(), 1);
NotifyPuller(newmember->CastToClient(), 1);
}
if(newmember->IsMerc())
{
Client* owner = newmember->CastToMerc()->GetMercOwner();
if(owner)
{
database.SetGroupID(NewMemberName, GetID(), owner->CharacterID(), true);
}
}
Group* group = newmember->CastToClient()->GetGroup();
if (group) {
group->SendHPManaEndPacketsTo(newmember);
group->SendHPPacketsFrom(newmember);
}
}
else
{
database.SetGroupID(NewMemberName, GetID(), CharacterID, ismerc);
}
if (newmember && newmember->IsClient())
newmember->CastToClient()->JoinGroupXTargets(this);
safe_delete(outapp);
if (RuleB(Bots, Enabled)) {
Bot::UpdateGroupCastingRoles(this);
}
return true;
}
void Group::AddMember(const char *NewMemberName)
{
// This method should be called when both the new member and the group leader are in a different zone to this one.
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
if(!strcasecmp(membername[i], NewMemberName))
{
return;
}
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if (membername[i][0] == '\0')
{
strcpy(membername[i], NewMemberName);
MemberRoles[i] = 0;
break;
}
}
}
void Group::QueuePacket(const EQApplicationPacket *app, bool ack_req)
{
uint32 i;
for(i = 0; i < MAX_GROUP_MEMBERS; i++)
if(members[i] && members[i]->IsClient())
members[i]->CastToClient()->QueuePacket(app, ack_req);
}
// Sends the rest of the group's hps to member. this is useful when someone
// first joins a group, but otherwise there shouldn't be a need to call it
void Group::SendHPManaEndPacketsTo(Mob *member)
{
if(member && member->IsClient()) {
EQApplicationPacket hpapp;
EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct));
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) {
if(members[i] && members[i] != member) {
members[i]->CreateHPPacket(&hpapp);
member->CastToClient()->QueuePacket(&hpapp, false);
safe_delete_array(hpapp.pBuffer);
hpapp.size = 0;
if (member->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD) {
outapp.SetOpcode(OP_MobManaUpdate);
MobManaUpdate_Struct *mana_update = (MobManaUpdate_Struct *)outapp.pBuffer;
mana_update->spawn_id = members[i]->GetID();
mana_update->mana = members[i]->GetManaPercent();
member->CastToClient()->QueuePacket(&outapp, false);
MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer;
outapp.SetOpcode(OP_MobEnduranceUpdate);
endurance_update->endurance = members[i]->GetEndurancePercent();
member->CastToClient()->QueuePacket(&outapp, false);
}
}
}
}
}
void Group::SendHPPacketsFrom(Mob *member)
{
EQApplicationPacket hp_app;
if(!member)
return;
member->CreateHPPacket(&hp_app);
EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct));
uint32 i;
for(i = 0; i < MAX_GROUP_MEMBERS; i++) {
if(members[i] && members[i] != member && members[i]->IsClient()) {
members[i]->CastToClient()->QueuePacket(&hp_app);
if (members[i]->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD) {
outapp.SetOpcode(OP_MobManaUpdate);
MobManaUpdate_Struct *mana_update = (MobManaUpdate_Struct *)outapp.pBuffer;
mana_update->spawn_id = member->GetID();
mana_update->mana = member->GetManaPercent();
members[i]->CastToClient()->QueuePacket(&outapp, false);
MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer;
outapp.SetOpcode(OP_MobEnduranceUpdate);
endurance_update->endurance = member->GetEndurancePercent();
members[i]->CastToClient()->QueuePacket(&outapp, false);
}
}
}
}
void Group::SendManaPacketFrom(Mob *member)
{
if (!member)
return;
EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct));
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (members[i] && members[i] != member && members[i]->IsClient()) {
if (members[i]->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD) {
outapp.SetOpcode(OP_MobManaUpdate);
MobManaUpdate_Struct *mana_update = (MobManaUpdate_Struct *)outapp.pBuffer;
mana_update->spawn_id = member->GetID();
mana_update->mana = member->GetManaPercent();
members[i]->CastToClient()->QueuePacket(&outapp, false);
}
}
}
}
void Group::SendEndurancePacketFrom(Mob* member)
{
if (!member)
return;
EQApplicationPacket outapp(OP_MobEnduranceUpdate, sizeof(MobManaUpdate_Struct));
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (members[i] && members[i] != member && members[i]->IsClient()) {
if (members[i]->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD) {
MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer;
endurance_update->spawn_id = member->GetID();
endurance_update->endurance = member->GetEndurancePercent();
members[i]->CastToClient()->QueuePacket(&outapp, false);
}
}
}
}
//updates a group member's client pointer when they zone in
//if the group was in the zone already
bool Group::UpdatePlayer(Mob* update) {
if (!update)
return false;
bool updateSuccess = false;
VerifyGroup();
uint32 i=0;
if(update->IsClient()) {
//update their player profile
PlayerProfile_Struct &pp = update->CastToClient()->GetPP();
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
if(membername[i][0] == '\0')
memset(pp.groupMembers[i], 0, 64);
else
strn0cpy(pp.groupMembers[i], membername[i], 64);
}
if(IsNPCMarker(update->CastToClient()))
{
NPCMarkerID = update->GetID();
SendLeadershipAAUpdate();
}
}
for (i = 0; i < MAX_GROUP_MEMBERS; i++)
{
if (!strcasecmp(membername[i],update->GetCleanName()))
{
members[i] = update;
members[i]->SetGrouped(true);
updateSuccess = true;
break;
}
}
// mentoree isn't set, the name has a length and the name is ours! update the pointer
if (update->IsClient() && !mentoree && mentoree_name.length() && !mentoree_name.compare(update->GetName()))
mentoree = update->CastToClient();
if (RuleB(Bots, Enabled)) {
Bot::UpdateGroupCastingRoles(this);
}
return updateSuccess;
}
void Group::MemberZoned(Mob* removemob) {
uint32 i;
if (removemob == nullptr)
return;
if(removemob == GetLeader())
SetLeader(nullptr);
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (members[i] == removemob) {
members[i] = nullptr;
//should NOT clear the name, it is used for world communication.
break;
}
}
if(removemob->IsClient() && HasRole(removemob, RoleAssist))
SetGroupAssistTarget(0);
if(removemob->IsClient() && HasRole(removemob, RoleTank))
SetGroupTankTarget(0);
if(removemob->IsClient() && HasRole(removemob, RolePuller))
SetGroupPullerTarget(0);
if (removemob->IsClient() && removemob == mentoree)
mentoree = nullptr;
if (RuleB(Bots, Enabled)) {
Bot::UpdateGroupCastingRoles(this);
}
}
void Group::SendGroupJoinOOZ(Mob* NewMember) {
if (NewMember == nullptr)
{
return;
}
if (!NewMember->HasGroup())
{
return;
}
//send updates to clients out of zone...
auto pack = new ServerPacket(ServerOP_GroupJoin, sizeof(ServerGroupJoin_Struct));
ServerGroupJoin_Struct* gj = (ServerGroupJoin_Struct*)pack->pBuffer;
gj->gid = GetID();
gj->zoneid = zone->GetZoneID();
gj->instance_id = zone->GetInstanceID();
strcpy(gj->member_name, NewMember->GetCleanName());
worldserver.SendPacket(pack);
safe_delete(pack);
}
bool Group::DelMemberOOZ(const char *Name) {
if(!Name) return false;
// If a member out of zone has disbanded, clear out their name.
//
for(unsigned int i = 0; i < MAX_GROUP_MEMBERS; i++) {
if(!strcasecmp(Name, membername[i]))
// This shouldn't be called if the member is in this zone.
if(!members[i]) {
if(!strncmp(GetLeaderName(), Name, 64))
{
//TODO: Transfer leadership if leader disbands OOZ.
UpdateGroupAAs();
}
memset(membername[i], 0, 64);
MemberRoles[i] = 0;
if(GroupCount() < 3)
{
UnDelegateMarkNPC(NPCMarkerName.c_str());
if (GetLeader() && GetLeader()->IsClient() && GetLeader()->CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoD) {
UnDelegateMainAssist(MainAssistName.c_str());
}
ClearAllNPCMarks();
}
if (Name == mentoree_name)
ClearGroupMentor();
return true;
}
}
return false;
}
bool Group::DelMember(Mob* oldmember, bool ignoresender)
{
if (oldmember == nullptr)
{
return false;
}
// TODO: fix this shit
// okay, so there is code below that tries to handle this. It does not.
// So instead of figuring it out now, lets just disband the group so the client doesn't
// sit there with a broken group and there isn't any group leader shuffling going on
// since the code below doesn't work.
if (oldmember == GetLeader()) {
DisbandGroup();
return true;
}
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++)
{
if (members[i] == oldmember)
{
members[i] = nullptr;
membername[i][0] = '\0';
memset(membername[i],0,64);
MemberRoles[i] = 0;
break;
}
}
/* This may seem pointless but the case above does not cover the following situation:
* Group has Leader a, member b, member c
* b and c are out of zone
* a disconnects/quits
* b or c zone back in and disconnects/quits
* a is still "leader" from GetLeader()'s perspective and will crash the zone when we DelMember(b)
* Ultimately we should think up a better solution to this.
*/
if(oldmember == GetLeader())
{
SetLeader(nullptr);
}
//handle leader quitting group gracefully
if (oldmember == GetLeader() && GroupCount() >= 2)
{
for(uint32 nl = 0; nl < MAX_GROUP_MEMBERS; nl++)
{
if(members[nl])
{
if (members[nl]->IsClient())
{
ChangeLeader(members[nl]);
break;
}
}
}
}
if (!GetLeaderName())
{
DisbandGroup();
return true;
}
auto pack = new ServerPacket(ServerOP_GroupLeave, sizeof(ServerGroupLeave_Struct));
ServerGroupLeave_Struct* gl = (ServerGroupLeave_Struct*)pack->pBuffer;
gl->gid = GetID();
gl->zoneid = zone->GetZoneID();
gl->instance_id = zone->GetInstanceID();
strcpy(gl->member_name, oldmember->GetCleanName());
worldserver.SendPacket(pack);
safe_delete(pack);
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct));
GroupJoin_Struct* gu = (GroupJoin_Struct*) outapp->pBuffer;
gu->action = groupActLeave;
strcpy(gu->membername, oldmember->GetCleanName());
strcpy(gu->yourname, oldmember->GetCleanName());
gu->leader_aas = LeaderAbilities;
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (members[i] == nullptr) {
//if (DEBUG>=5) LogFile->write(EQEMuLog::Debug, "Group::DelMember() null member at slot %i", i);
continue;
}
if (members[i] != oldmember) {
strcpy(gu->yourname, members[i]->GetCleanName());
if(members[i]->IsClient())
members[i]->CastToClient()->QueuePacket(outapp);
}
}
if (!ignoresender)
{
strcpy(gu->yourname,oldmember->GetCleanName());
strcpy(gu->membername,oldmember->GetCleanName());
gu->action = groupActLeave;
if(oldmember->IsClient())
oldmember->CastToClient()->QueuePacket(outapp);
}
safe_delete(outapp);
if(oldmember->IsClient())
{
database.SetGroupID(oldmember->GetCleanName(), 0, oldmember->CastToClient()->CharacterID(), false);
}
if(oldmember->IsMerc())
{
Client* owner = oldmember->CastToMerc()->GetMercOwner();
if(owner)
{
database.SetGroupID(oldmember->GetCleanName(), 0, owner->CharacterID(), true);
}
}
oldmember->SetGrouped(false);
disbandcheck = true;
if(HasRole(oldmember, RoleTank))
{
SetGroupTankTarget(0);
UnDelegateMainTank(oldmember->GetCleanName());
}
if(HasRole(oldmember, RoleAssist))
{
SetGroupAssistTarget(0);
UnDelegateMainAssist(oldmember->GetCleanName());
}
if(HasRole(oldmember, RolePuller))
{
SetGroupPullerTarget(0);
UnDelegatePuller(oldmember->GetCleanName());
}
if (oldmember->GetName() == mentoree_name)
ClearGroupMentor();
if(oldmember->IsClient()) {
SendMarkedNPCsToMember(oldmember->CastToClient(), true);
oldmember->CastToClient()->LeaveGroupXTargets(this);
}
if(GroupCount() < 3)
{
UnDelegateMarkNPC(NPCMarkerName.c_str());
if (GetLeader() && GetLeader()->IsClient() && GetLeader()->CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoD) {
UnDelegateMainAssist(MainAssistName.c_str());
}
ClearAllNPCMarks();
}
if (RuleB(Bots, Enabled)) {
Bot::UpdateGroupCastingRoles(this);
}
return true;
}
// does the caster + group
void Group::CastGroupSpell(Mob* caster, uint16 spell_id) {
uint32 z;
float range, distance;
if(!caster)
return;
castspell = true;
range = caster->GetAOERange(spell_id);
float range2 = range*range;
float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range;
// caster->SpellOnTarget(spell_id, caster);
for(z=0; z < MAX_GROUP_MEMBERS; z++)
{
if(members[z] == caster) {
caster->SpellOnTarget(spell_id, caster);
#ifdef GROUP_BUFF_PETS
if(spells[spell_id].target_type != ST_GroupNoPets && caster->GetPet() && caster->HasPetAffinity() && !caster->GetPet()->IsCharmed())
caster->SpellOnTarget(spell_id, caster->GetPet());
#endif
}
else if(members[z] != nullptr)
{
distance = DistanceSquared(caster->GetPosition(), members[z]->GetPosition());
if(distance <= range2 && distance >= min_range2) {
members[z]->CalcSpellPowerDistanceMod(spell_id, distance);
caster->SpellOnTarget(spell_id, members[z]);
#ifdef GROUP_BUFF_PETS
if(spells[spell_id].target_type != ST_GroupNoPets && members[z]->GetPet() && members[z]->HasPetAffinity() && !members[z]->GetPet()->IsCharmed())
caster->SpellOnTarget(spell_id, members[z]->GetPet());
#endif
} else
LogSpells("Group spell: [{}] is out of range [{}] at distance [{}] from [{}]", members[z]->GetName(), range, distance, caster->GetName());
}
}
castspell = false;
disbandcheck = true;
}
bool Group::IsGroupMember(Mob* c)
{
if (c) {
for (const auto &m: members) {
if (m == c) {
return true;
}
}
}
return false;
}
bool Group::IsGroupMember(const char *name)
{
if (name) {
for (const auto& m : membername) {
if (!strcmp(m, name)) {
return true;
}
}
}
return false;
}
void Group::GroupMessage(Mob* sender, uint8 language, uint8 lang_skill, const char* message) {
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
if(!members[i])
continue;
if (members[i]->IsClient() && members[i]->CastToClient()->GetFilter(FilterGroupChat)!=0)
members[i]->CastToClient()->ChannelMessageSend(sender->GetName(),members[i]->GetName(),ChatChannel_Group,language,lang_skill,message);
}
auto pack =
new ServerPacket(ServerOP_OOZGroupMessage, sizeof(ServerGroupChannelMessage_Struct) + strlen(message) + 1);
ServerGroupChannelMessage_Struct* gcm = (ServerGroupChannelMessage_Struct*)pack->pBuffer;
gcm->zoneid = zone->GetZoneID();
gcm->groupid = GetID();
gcm->instanceid = zone->GetInstanceID();
strcpy(gcm->from, sender->GetCleanName());
strcpy(gcm->message, message);
worldserver.SendPacket(pack);
safe_delete(pack);
}
uint32 Group::GetTotalGroupDamage(Mob* other) {
uint32 total = 0;
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
if(!members[i])
continue;
if (other->CheckAggro(members[i]))
total += other->GetHateAmount(members[i],true);
}
return total;
}
void Group::DisbandGroup(bool joinraid) {
if (RuleB(Bots, Enabled)) {
Bot::UpdateGroupCastingRoles(this, true);
}
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupUpdate_Struct));
GroupUpdate_Struct* gu = (GroupUpdate_Struct*) outapp->pBuffer;
gu->action = groupActDisband;
Client *Leader = nullptr;
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++)
{
if (members[i] == nullptr)
{
continue;
}
if (members[i]->IsClient())
{
if(IsLeader(members[i]))
{
Leader = members[i]->CastToClient();
}
strcpy(gu->yourname, members[i]->GetCleanName());
database.SetGroupID(members[i]->GetCleanName(), 0, members[i]->CastToClient()->CharacterID(), false);
members[i]->CastToClient()->QueuePacket(outapp);
SendMarkedNPCsToMember(members[i]->CastToClient(), true);
if (!joinraid)
members[i]->CastToClient()->LeaveGroupXTargets(this);
}
if (members[i]->IsMerc())
{
Client* owner = members[i]->CastToMerc()->GetMercOwner();
if(owner)
{
database.SetGroupID(members[i]->GetCleanName(), 0, owner->CharacterID(), true);
}
}
members[i]->SetGrouped(false);
members[i] = nullptr;
membername[i][0] = '\0';
}
ClearAllNPCMarks();
auto pack = new ServerPacket(ServerOP_DisbandGroup, sizeof(ServerDisbandGroup_Struct));
ServerDisbandGroup_Struct* dg = (ServerDisbandGroup_Struct*)pack->pBuffer;
dg->zoneid = zone->GetZoneID();
dg->groupid = GetID();
dg->instance_id = zone->GetInstanceID();
worldserver.SendPacket(pack);
safe_delete(pack);
if(GetID() != 0)
{
database.ClearGroup(GetID());
}
if(Leader && (Leader->IsLFP()))
{
Leader->UpdateLFP();
}
SetLeader(nullptr);
safe_delete(outapp);
entity_list.RemoveGroup(GetID());
}
void Group::GetMemberList(std::list<Mob*>& member_list, bool clear_list)
{
if (clear_list)
member_list.clear();
for (auto member_iter : members) {
if (member_iter)
member_list.push_back(member_iter);
}
}
void Group::GetClientList(std::list<Client*>& client_list, bool clear_list)
{
if (clear_list)
client_list.clear();
for (auto client_iter : members) {
if (client_iter && client_iter->IsClient())
client_list.push_back(client_iter->CastToClient());
}
}
void Group::GetBotList(std::list<Bot*>& bot_list, bool clear_list)
{
if (clear_list)
bot_list.clear();
for (auto bot_iter : members) {
if (bot_iter && bot_iter->IsBot())
bot_list.push_back(bot_iter->CastToBot());
}
}
bool Group::Process() {
if(disbandcheck && !GroupCount())
return false;
else if(disbandcheck && GroupCount())
disbandcheck = false;
return true;
}
void Group::SendUpdate(uint32 type, Mob* member)
{
if(!member->IsClient())
return;
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupUpdate2_Struct));
GroupUpdate2_Struct* gu = (GroupUpdate2_Struct*)outapp->pBuffer;
gu->action = type;
strcpy(gu->yourname,member->GetName());
int x = 0;
gu->leader_aas = LeaderAbilities;
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
if((members[i] != nullptr) && IsLeader(members[i]))
{
strcpy(gu->leadersname, members[i]->GetName());
break;
}
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
if (members[i] != nullptr && members[i] != member)
strcpy(gu->membername[x++], members[i]->GetCleanName());
member->CastToClient()->QueuePacket(outapp);
safe_delete(outapp);
}
void Group::SendLeadershipAAUpdate()
{
// This method updates other members of the group in the current zone with the Leader's group leadership AAs.
//
// It is called when the leader purchases a leadership AA or enters a zone.
//
// If a group member is not in the same zone as the leader when the leader purchases a new AA, they will not become
// aware of it until they are next in the same zone as the leader.
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct));
GroupJoin_Struct* gu = (GroupJoin_Struct*)outapp->pBuffer;
gu->action = groupActAAUpdate;
gu->leader_aas = LeaderAbilities;
gu->NPCMarkerID = GetNPCMarkerID();
uint32 i = 0;
for (i = 0;i < MAX_GROUP_MEMBERS; ++i)
{
if(members[i] && members[i]->IsClient())
{
strcpy(gu->yourname, members[i]->GetName());
strcpy(gu->membername, members[i]->GetName());
members[i]->CastToClient()->QueuePacket(outapp);
}
}
safe_delete(outapp);
}
uint8 Group::GroupCount() {
uint8 MemberCount = 0;
for(uint8 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if(membername[i][0])
{
++MemberCount;
}
}
return MemberCount;
}
uint32 Group::GetHighestLevel()
{
uint32 level = 1;
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++)
{
if (members[i])
{
if(members[i]->GetLevel() > level)
level = members[i]->GetLevel();
}
}
return level;
}
uint32 Group::GetLowestLevel()
{
uint32 level = 255;
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++)
{
if (members[i])
{
if(members[i]->GetLevel() < level)
level = members[i]->GetLevel();
}
}
return level;
}
void Group::TeleportGroup(Mob* sender, uint32 zoneID, uint16 instance_id, float x, float y, float z, float heading)
{
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++)
{
if (members[i] != nullptr && members[i]->IsClient() && members[i] != sender)
{
members[i]->CastToClient()->MovePC(zoneID, instance_id, x, y, z, heading, 0, ZoneSolicited);
}
}
}
bool Group::LearnMembers() {
auto rows = GroupIdRepository::GetWhere(
database,
fmt::format(
"groupid = {}",
GetID()
)
);
if (rows.empty()) {
LogError(
"Error getting group members for group [{}]",
GetID()
);
return false;
}
int memberIndex = 0;
for (const auto& member : rows) {
if (member.name.empty()) {
continue;
}
members[memberIndex] = nullptr;
strn0cpy(membername[memberIndex], member.name.c_str(), 64);
memberIndex++;
}
return true;
}
void Group::VerifyGroup() {
/*
The purpose of this method is to make sure that a group
is in a valid state, to prevent dangling pointers.
Only called every once in a while (on member re-join for now).
*/
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (membername[i][0] == '\0') {
members[i] = nullptr;
continue;
}
Mob *them = entity_list.GetMob(membername[i]);
if(them == nullptr && members[i] != nullptr) { //they aren't in zone
membername[i][0] = '\0';
members[i] = nullptr;
continue;
}
if(them != nullptr && members[i] != them) { //our pointer is out of date... not so good.
members[i] = them;
continue;
}
}
}
void Group::GroupMessageString(Mob* sender, uint32 type, uint32 string_id, const char* message,const char* message2,const char* message3,const char* message4,const char* message5,const char* message6,const char* message7,const char* message8,const char* message9, uint32 distance) {
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
if(members[i] == nullptr)
continue;
if(members[i] == sender)
continue;
if(!members[i]->IsClient())
continue;
members[i]->MessageString(type, string_id, message, message2, message3, message4, message5, message6, message7, message8, message9, 0);
}
}
void Client::LeaveGroup() {
Group *g = GetGroup();
if(g)
{
int32 MemberCount = g->GroupCount();
// Account for both client and merc leaving the group
if (GetMerc() && g == GetMerc()->GetGroup())
{
MemberCount -= 1;
}
if(MemberCount < 3)
{
g->DisbandGroup();
}
else
{
g->DelMember(this);
if (GetMerc() != nullptr && g == GetMerc()->GetGroup() )
{
GetMerc()->RemoveMercFromGroup(GetMerc(), GetMerc()->GetGroup());
}
}
}
else
{
//force things a little
database.SetGroupID(GetCleanName(), 0, CharacterID(), false);
if (GetMerc())
{
database.SetGroupID(GetMerc()->GetCleanName(), 0, CharacterID(), true);
}
}
isgrouped = false;
}
void Group::HealGroup(uint32 heal_amt, Mob* caster, float range)
{
if (!caster)
return;
if (!range)
range = 200;
float distance;
float range2 = range*range;
int numMem = 0;
unsigned int gi = 0;
for(; gi < MAX_GROUP_MEMBERS; gi++)
{
if(members[gi]){
distance = DistanceSquared(caster->GetPosition(), members[gi]->GetPosition());
if(distance <= range2){
numMem += 1;
}
}
}
heal_amt /= numMem;
for(gi = 0; gi < MAX_GROUP_MEMBERS; gi++)
{
if(members[gi]){
distance = DistanceSquared(caster->GetPosition(), members[gi]->GetPosition());
if(distance <= range2){
members[gi]->HealDamage(heal_amt, caster);
members[gi]->SendHPUpdate();
}
}
}
}
void Group::BalanceHP(int32 penalty, float range, Mob* caster, int32 limit)
{
if (!caster)
return;
if (!range)
range = 200;
int64 dmgtaken = 0, numMem = 0, dmgtaken_tmp = 0;
float distance;
float range2 = range*range;
unsigned int gi = 0;
for(; gi < MAX_GROUP_MEMBERS; gi++)
{
if(members[gi]){
distance = DistanceSquared(caster->GetPosition(), members[gi]->GetPosition());
if(distance <= range2){
dmgtaken_tmp = members[gi]->GetMaxHP() - members[gi]->GetHP();
if (limit && (dmgtaken_tmp > limit))
dmgtaken_tmp = limit;
dmgtaken += (dmgtaken_tmp);
numMem += 1;
}
}
}
dmgtaken += dmgtaken * penalty / 100;
dmgtaken /= numMem;
for(gi = 0; gi < MAX_GROUP_MEMBERS; gi++)
{
if(members[gi]){
distance = DistanceSquared(caster->GetPosition(), members[gi]->GetPosition());
if(distance <= range2){
if((members[gi]->GetMaxHP() - dmgtaken) < 1){ //this way the ability will never kill someone
members[gi]->SetHP(1); //but it will come darn close
members[gi]->SendHPUpdate();
}
else{
members[gi]->SetHP(members[gi]->GetMaxHP() - dmgtaken);
members[gi]->SendHPUpdate();
}
}
}
}
}
void Group::BalanceMana(int32 penalty, float range, Mob* caster, int32 limit)
{
if (!caster)
return;
if (!range)
range = 200;
float distance;
float range2 = range*range;
int manataken = 0, numMem = 0, manataken_tmp = 0;
unsigned int gi = 0;
for(; gi < MAX_GROUP_MEMBERS; gi++)
{
if(members[gi] && (members[gi]->GetMaxMana() > 0)){
distance = DistanceSquared(caster->GetPosition(), members[gi]->GetPosition());
if(distance <= range2){
manataken_tmp = members[gi]->GetMaxMana() - members[gi]->GetMana();
if (limit && (manataken_tmp > limit))
manataken_tmp = limit;
manataken += (manataken_tmp);
numMem += 1;
}
}
}
manataken += manataken * penalty / 100;
manataken /= numMem;
if (limit && (manataken > limit))
manataken = limit;
for(gi = 0; gi < MAX_GROUP_MEMBERS; gi++)
{
if(members[gi]){
distance = DistanceSquared(caster->GetPosition(), members[gi]->GetPosition());
if(distance <= range2){
if((members[gi]->GetMaxMana() - manataken) < 1){
members[gi]->SetMana(1);
if (members[gi]->IsClient())
members[gi]->CastToClient()->SendManaUpdate();
}
else{
members[gi]->SetMana(members[gi]->GetMaxMana() - manataken);
if (members[gi]->IsClient())
members[gi]->CastToClient()->SendManaUpdate();
}
}
}
}
}
uint16 Group::GetAvgLevel()
{
double levelHolder = 0;
uint8 i = 0;
uint8 numMem = 0;
while(i < MAX_GROUP_MEMBERS)
{
if (members[i])
{
numMem++;
levelHolder = levelHolder + (members[i]->GetLevel());
}
i++;
}
levelHolder = ((levelHolder/numMem)+.5); // total levels divided by num of characters
return (uint16(levelHolder));
}
void Group::MarkNPC(Mob* Target, int Number)
{
// Send a packet to all group members in this zone causing the client to prefix the Target mob's name
// with the specified Number.
//
if(!Target || Target->IsClient() || Target->IsMerc())
return;
if((Number < 1) || (Number > MAX_MARKED_NPCS))
return;
bool AlreadyMarked = false;
uint16 EntityID = Target->GetID();
for(int i = 0; i < MAX_MARKED_NPCS; ++i)
if(MarkedNPCs[i] == EntityID)
{
if(i == (Number - 1))
return;
UpdateXTargetMarkedNPC(i+1, nullptr);
MarkedNPCs[i] = 0;
AlreadyMarked = true;
break;
}
if(!AlreadyMarked)
{
if(MarkedNPCs[Number - 1])
{
Mob* m = entity_list.GetMob(MarkedNPCs[Number-1]);
if(m)
m->IsTargeted(-1);
UpdateXTargetMarkedNPC(Number, nullptr);
}
if(EntityID)
{
Mob* m = entity_list.GetMob(Target->GetID());
if(m)
m->IsTargeted(1);
}
}
MarkedNPCs[Number - 1] = EntityID;
auto outapp = new EQApplicationPacket(OP_MarkNPC, sizeof(MarkNPC_Struct));
MarkNPC_Struct* mnpcs = (MarkNPC_Struct *)outapp->pBuffer;
mnpcs->TargetID = EntityID;
mnpcs->Number = Number;
Mob *m = entity_list.GetMob(EntityID);
if(m)
sprintf(mnpcs->Name, "%s", m->GetCleanName());
QueuePacket(outapp);
safe_delete(outapp);
UpdateXTargetMarkedNPC(Number, m);
}
void Group::DelegateMainTank(const char *NewMainTankName, uint8 toggle)
{
// This method is called when the group leader Delegates the Main Tank role to a member of the group
// (or himself). All group members in the zone are notified of the new Main Tank and it is recorded
// in the group_leaders table so as to persist across zones.
//
bool updateDB = false;
if(!NewMainTankName)
return;
Mob *m = entity_list.GetMob(NewMainTankName);
if(!m)
return;
if(MainTankName != NewMainTankName || !toggle)
updateDB = true;
if(m->GetTarget())
TankTargetID = m->GetTarget()->GetID();
else
TankTargetID = 0;
Mob *mtt = TankTargetID ? entity_list.GetMob(TankTargetID) : 0;
SetMainTank(NewMainTankName);
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if(members[i] && members[i]->IsClient())
{
NotifyMainTank(members[i]->CastToClient(), toggle);
members[i]->CastToClient()->UpdateXTargetType(GroupTank, m, NewMainTankName);
members[i]->CastToClient()->UpdateXTargetType(GroupTankTarget, mtt);
}
}
if(updateDB) {
std::string query = StringFormat("UPDATE group_leaders SET maintank = '%s' WHERE gid = %i LIMIT 1",
MainTankName.c_str(), GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
LogError("Unable to set group main tank: [{}]\n", results.ErrorMessage().c_str());
}
}
void Group::DelegateMainAssist(const char *NewMainAssistName, uint8 toggle)
{
// This method is called when the group leader Delegates the Main Assist role to a member of the group
// (or himself). All group members in the zone are notified of the new Main Assist and it is recorded
// in the group_leaders table so as to persist across zones.
//
bool updateDB = false;
if(!NewMainAssistName)
return;
Mob *m = entity_list.GetMob(NewMainAssistName);
if(!m)
return;
if(MainAssistName != NewMainAssistName || !toggle)
updateDB = true;
if(m->GetTarget())
AssistTargetID = m->GetTarget()->GetID();
else
AssistTargetID = 0;
SetMainAssist(NewMainAssistName);
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) {
if(members[i] && members[i]->IsClient())
{
NotifyMainAssist(members[i]->CastToClient(), toggle);
members[i]->CastToClient()->UpdateXTargetType(GroupAssist, m, NewMainAssistName);
members[i]->CastToClient()->UpdateXTargetType(GroupAssistTarget, m->GetTarget());
}
}
if(updateDB) {
std::string query = StringFormat("UPDATE group_leaders SET assist = '%s' WHERE gid = %i LIMIT 1",
MainAssistName.c_str(), GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
LogError("Unable to set group main assist: [{}]\n", results.ErrorMessage().c_str());
}
}
void Group::DelegatePuller(const char *NewPullerName, uint8 toggle)
{
// This method is called when the group leader Delegates the Puller role to a member of the group
// (or himself). All group members in the zone are notified of the new Puller and it is recorded
// in the group_leaders table so as to persist across zones.
//
bool updateDB = false;
if(!NewPullerName)
return;
Mob *m = entity_list.GetMob(NewPullerName);
if(!m)
return;
if(PullerName != NewPullerName || !toggle)
updateDB = true;
if(m->GetTarget())
PullerTargetID = m->GetTarget()->GetID();
else
PullerTargetID = 0;
SetPuller(NewPullerName);
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) {
if(members[i] && members[i]->IsClient())
{
NotifyPuller(members[i]->CastToClient(), toggle);
members[i]->CastToClient()->UpdateXTargetType(Puller, m, NewPullerName);
members[i]->CastToClient()->UpdateXTargetType(PullerTarget, m->GetTarget());
}
}
if(updateDB) {
std::string query = StringFormat("UPDATE group_leaders SET puller = '%s' WHERE gid = %i LIMIT 1",
PullerName.c_str(), GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
LogError("Unable to set group main puller: [{}]\n", results.ErrorMessage().c_str());
}
}
void Group::NotifyMainTank(Client *c, uint8 toggle)
{
// Send a packet to the specified Client notifying them who the new Main Tank is. This causes the client to display
// a message with the name of the Main Tank.
//
if(!c)
return;
if(!MainTankName.size())
return;
if (c->ClientVersion() < EQ::versions::ClientVersion::SoD)
{
if(toggle)
c->Message(Chat::White, "%s is now Main Tank.", MainTankName.c_str());
else
c->Message(Chat::White, "%s is no longer Main Tank.", MainTankName.c_str());
}
else
{
auto outapp = new EQApplicationPacket(OP_GroupRoles, sizeof(GroupRole_Struct));
GroupRole_Struct *grs = (GroupRole_Struct*)outapp->pBuffer;
strn0cpy(grs->Name1, MainTankName.c_str(), sizeof(grs->Name1));
strn0cpy(grs->Name2, GetLeaderName(), sizeof(grs->Name2));
grs->RoleNumber = 1;
grs->Toggle = toggle;
c->QueuePacket(outapp);
safe_delete(outapp);
}
}
void Group::NotifyMainAssist(Client *c, uint8 toggle)
{
// Send a packet to the specified Client notifying them who the new Main Assist is. This causes the client to display
// a message with the name of the Main Assist.
//
if(!c)
return;
if(!MainAssistName.size())
return;
if (c->ClientVersion() < EQ::versions::ClientVersion::SoD)
{
auto outapp = new EQApplicationPacket(OP_DelegateAbility, sizeof(DelegateAbility_Struct));
DelegateAbility_Struct* das = (DelegateAbility_Struct*)outapp->pBuffer;
das->DelegateAbility = 0;
das->MemberNumber = 0;
das->Action = 0;
das->EntityID = 0;
strn0cpy(das->Name, MainAssistName.c_str(), sizeof(das->Name));
c->QueuePacket(outapp);
safe_delete(outapp);
}
else
{
auto outapp = new EQApplicationPacket(OP_GroupRoles, sizeof(GroupRole_Struct));
GroupRole_Struct *grs = (GroupRole_Struct*)outapp->pBuffer;
strn0cpy(grs->Name1, MainAssistName.c_str(), sizeof(grs->Name1));
strn0cpy(grs->Name2, GetLeaderName(), sizeof(grs->Name2));
grs->RoleNumber = 2;
grs->Toggle = toggle;
c->QueuePacket(outapp);
safe_delete(outapp);
}
NotifyAssistTarget(c);
}
void Group::NotifyPuller(Client *c, uint8 toggle)
{
// Send a packet to the specified Client notifying them who the new Puller is. This causes the client to display
// a message with the name of the Puller.
//
if(!c)
return;
if(!PullerName.size())
return;
if (c->ClientVersion() < EQ::versions::ClientVersion::SoD)
{
if(toggle)
c->Message(Chat::White, "%s is now Puller.", PullerName.c_str());
else
c->Message(Chat::White, "%s is no longer Puller.", PullerName.c_str());
}
else
{
auto outapp = new EQApplicationPacket(OP_GroupRoles, sizeof(GroupRole_Struct));
GroupRole_Struct *grs = (GroupRole_Struct*)outapp->pBuffer;
strn0cpy(grs->Name1, PullerName.c_str(), sizeof(grs->Name1));
strn0cpy(grs->Name2, GetLeaderName(), sizeof(grs->Name2));
grs->RoleNumber = 3;
grs->Toggle = toggle;
c->QueuePacket(outapp);
safe_delete(outapp);
}
}
void Group::UnDelegateMainTank(const char *OldMainTankName, uint8 toggle)
{
// Called when the group Leader removes the Main Tank delegation. Sends a packet to each group member in the zone
// informing them of the change and update the group_leaders table.
//
if(OldMainTankName == MainTankName) {
std::string query = StringFormat("UPDATE group_leaders SET maintank = '' WHERE gid = %i LIMIT 1", GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
LogError("Unable to clear group main tank: [{}]\n", results.ErrorMessage().c_str());
if(!toggle) {
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) {
if(members[i] && members[i]->IsClient())
{
NotifyMainTank(members[i]->CastToClient(), toggle);
members[i]->CastToClient()->UpdateXTargetType(GroupTank, nullptr, "");
members[i]->CastToClient()->UpdateXTargetType(GroupTankTarget, nullptr);
}
}
}
SetMainTank("");
}
}
void Group::UnDelegateMainAssist(const char *OldMainAssistName, uint8 toggle)
{
// Called when the group Leader removes the Main Assist delegation. Sends a packet to each group member in the zone
// informing them of the change and update the group_leaders table.
//
if(OldMainAssistName == MainAssistName) {
auto outapp = new EQApplicationPacket(OP_DelegateAbility, sizeof(DelegateAbility_Struct));
DelegateAbility_Struct* das = (DelegateAbility_Struct*)outapp->pBuffer;
das->DelegateAbility = 0;
das->MemberNumber = 0;
das->Action = 1;
das->EntityID = 0;
strn0cpy(das->Name, OldMainAssistName, sizeof(das->Name));
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
if(members[i] && members[i]->IsClient())
{
members[i]->CastToClient()->QueuePacket(outapp);
members[i]->CastToClient()->UpdateXTargetType(GroupAssist, nullptr, "");
}
safe_delete(outapp);
std::string query = StringFormat("UPDATE group_leaders SET assist = '' WHERE gid = %i LIMIT 1", GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
LogError("Unable to clear group main assist: [{}]\n", results.ErrorMessage().c_str());
if(!toggle)
{
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if(members[i] && members[i]->IsClient())
{
NotifyMainAssist(members[i]->CastToClient(), toggle);
members[i]->CastToClient()->UpdateXTargetType(GroupAssistTarget, nullptr);
}
}
}
SetMainAssist("");
}
}
void Group::UnDelegatePuller(const char *OldPullerName, uint8 toggle)
{
// Called when the group Leader removes the Puller delegation. Sends a packet to each group member in the zone
// informing them of the change and update the group_leaders table.
//
if(OldPullerName == PullerName) {
std::string query = StringFormat("UPDATE group_leaders SET puller = '' WHERE gid = %i LIMIT 1", GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
LogError("Unable to clear group main puller: [{}]\n", results.ErrorMessage().c_str());
if(!toggle) {
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) {
if(members[i] && members[i]->IsClient())
{
NotifyPuller(members[i]->CastToClient(), toggle);
members[i]->CastToClient()->UpdateXTargetType(Puller, nullptr, "");
members[i]->CastToClient()->UpdateXTargetType(PullerTarget, nullptr);
}
}
}
SetPuller("");
}
}
bool Group::IsNPCMarker(Client *c)
{
// Returns true if the specified client has been delegated the NPC Marker Role
//
if(!c)
return false;
if(NPCMarkerName.size())
return(c->GetName() == NPCMarkerName);
return false;
}
void Group::SetGroupAssistTarget(Mob *m)
{
// Notify all group members in the zone of the new target the Main Assist has selected.
//
AssistTargetID = m ? m->GetID() : 0;
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if(members[i] && members[i]->IsClient())
{
NotifyAssistTarget(members[i]->CastToClient());
}
}
}
void Group::SetGroupTankTarget(Mob *m)
{
TankTargetID = m ? m->GetID() : 0;
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if(members[i] && members[i]->IsClient())
{
members[i]->CastToClient()->UpdateXTargetType(GroupTankTarget, m);
}
}
}
void Group::SetGroupPullerTarget(Mob *m)
{
PullerTargetID = m ? m->GetID() : 0;
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if(members[i] && members[i]->IsClient())
{
members[i]->CastToClient()->UpdateXTargetType(PullerTarget, m);
}
}
}
void Group::SetGroupMentor(int percent, char *name)
{
mentoree_name = name;
mentor_percent = percent;
Client *client = entity_list.GetClientByName(name);
mentoree = client ? client : nullptr;
std::string query = StringFormat("UPDATE group_leaders SET mentoree = '%s', mentor_percent = %i WHERE gid = %i LIMIT 1",
mentoree_name.c_str(), mentor_percent, GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
LogError("Unable to set group mentor: [{}]\n", results.ErrorMessage().c_str());
}
void Group::ClearGroupMentor()
{
mentoree_name.clear();
mentor_percent = 0;
mentoree = nullptr;
std::string query = StringFormat("UPDATE group_leaders SET mentoree = '', mentor_percent = 0 WHERE gid = %i LIMIT 1", GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
LogError("Unable to clear group mentor: [{}]\n", results.ErrorMessage().c_str());
}
void Group::NotifyAssistTarget(Client *c)
{
// Send a packet to the specified client notifying them of the group target selected by the Main Assist.
if(!c)
return;
auto outapp = new EQApplicationPacket(OP_SetGroupTarget, sizeof(MarkNPC_Struct));
MarkNPC_Struct* mnpcs = (MarkNPC_Struct *)outapp->pBuffer;
mnpcs->TargetID = AssistTargetID;
mnpcs->Number = 0;
c->QueuePacket(outapp);
safe_delete(outapp);
Mob *m = entity_list.GetMob(AssistTargetID);
c->UpdateXTargetType(GroupAssistTarget, m);
}
void Group::NotifyTankTarget(Client *c)
{
if(!c)
return;
Mob *m = entity_list.GetMob(TankTargetID);
c->UpdateXTargetType(GroupTankTarget, m);
}
void Group::NotifyPullerTarget(Client *c)
{
if(!c)
return;
Mob *m = entity_list.GetMob(PullerTargetID);
c->UpdateXTargetType(PullerTarget, m);
}
void Group::DelegateMarkNPC(const char *NewNPCMarkerName)
{
// Called when the group leader has delegated the Mark NPC ability to a group member.
// Notify all group members in the zone of the change and save the change in the group_leaders
// table to persist across zones.
//
if(NPCMarkerName.size() > 0)
UnDelegateMarkNPC(NPCMarkerName.c_str());
if(!NewNPCMarkerName)
return;
SetNPCMarker(NewNPCMarkerName);
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
if(members[i] && members[i]->IsClient())
NotifyMarkNPC(members[i]->CastToClient());
std::string query = StringFormat("UPDATE group_leaders SET marknpc = '%s' WHERE gid = %i LIMIT 1",
NewNPCMarkerName, GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
LogError("Unable to set group mark npc: [{}]\n", results.ErrorMessage().c_str());
}
void Group::NotifyMarkNPC(Client *c)
{
// Notify the specified client who the group member is who has been delgated the Mark NPC ability.
if(!c)
return;
if(!NPCMarkerName.size())
return;
auto outapp = new EQApplicationPacket(OP_DelegateAbility, sizeof(DelegateAbility_Struct));
DelegateAbility_Struct* das = (DelegateAbility_Struct*)outapp->pBuffer;
das->DelegateAbility = 1;
das->MemberNumber = 0;
das->Action = 0;
das->EntityID = NPCMarkerID;
strn0cpy(das->Name, NPCMarkerName.c_str(), sizeof(das->Name));
c->QueuePacket(outapp);
safe_delete(outapp);
}
void Group::SetNPCMarker(const char *NewNPCMarkerName)
{
NPCMarkerName = NewNPCMarkerName;
Client *m = entity_list.GetClientByName(NPCMarkerName.c_str());
if(!m)
NPCMarkerID = 0;
else
NPCMarkerID = m->GetID();
}
void Group::UnDelegateMarkNPC(const char *OldNPCMarkerName)
{
// Notify all group members in the zone that the Mark NPC ability has been rescinded from the specified
// group member.
if(!OldNPCMarkerName)
return;
if(!NPCMarkerName.size())
return;
auto outapp = new EQApplicationPacket(OP_DelegateAbility, sizeof(DelegateAbility_Struct));
DelegateAbility_Struct* das = (DelegateAbility_Struct*)outapp->pBuffer;
das->DelegateAbility = 1;
das->MemberNumber = 0;
das->Action = 1;
das->EntityID = 0;
strn0cpy(das->Name, OldNPCMarkerName, sizeof(das->Name));
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
if(members[i] && members[i]->IsClient())
members[i]->CastToClient()->QueuePacket(outapp);
safe_delete(outapp);
NPCMarkerName.clear();
std::string query = StringFormat("UPDATE group_leaders SET marknpc = '' WHERE gid = %i LIMIT 1", GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
LogError("Unable to clear group marknpc: [{}]\n", results.ErrorMessage().c_str());
}
void Group::SaveGroupLeaderAA()
{
// Stores the Group Leaders Leadership AA data from the Player Profile as a blob in the group_leaders table.
// This is done so that group members not in the same zone as the Leader still have access to this information.
auto queryBuffer = new char[sizeof(GroupLeadershipAA_Struct) * 2 + 1];
database.DoEscapeString(queryBuffer, (char *)&LeaderAbilities, sizeof(GroupLeadershipAA_Struct));
std::string query = "UPDATE group_leaders SET leadershipaa = '";
query += queryBuffer;
query += StringFormat("' WHERE gid = %i LIMIT 1", GetID());
safe_delete_array(queryBuffer);
auto results = database.QueryDatabase(query);
if (!results.Success())
LogError("Unable to store LeadershipAA: [{}]\n", results.ErrorMessage().c_str());
}
void Group::UnMarkNPC(uint16 ID)
{
// Called from entity_list when the mob with the specified ID is being destroyed.
//
// If the given mob has been marked by this group, it is removed from the list of marked NPCs.
// The primary reason for doing this is so that when a new group member joins or zones in, we
// send them correct details of which NPCs are currently marked.
if(AssistTargetID == ID)
AssistTargetID = 0;
if(TankTargetID == ID)
TankTargetID = 0;
if(PullerTargetID == ID)
PullerTargetID = 0;
for(int i = 0; i < MAX_MARKED_NPCS; ++i)
{
if(MarkedNPCs[i] == ID)
{
MarkedNPCs[i] = 0;
UpdateXTargetMarkedNPC(i + 1, nullptr);
}
}
}
void Group::SendMarkedNPCsToMember(Client *c, bool Clear)
{
// Send the Entity IDs of the NPCs marked by the Group Leader or delegate to the specified client.
// If Clear == true, then tell the client to unmark the NPCs (when a member disbands).
//
//
if(!c)
return;
auto outapp = new EQApplicationPacket(OP_MarkNPC, sizeof(MarkNPC_Struct));
MarkNPC_Struct *mnpcs = (MarkNPC_Struct *)outapp->pBuffer;
for(int i = 0; i < MAX_MARKED_NPCS; ++i)
{
if(MarkedNPCs[i])
{
mnpcs->TargetID = MarkedNPCs[i];
Mob *m = entity_list.GetMob(MarkedNPCs[i]);
if(m)
sprintf(mnpcs->Name, "%s", m->GetCleanName());
if(!Clear)
mnpcs->Number = i + 1;
else
mnpcs->Number = 0;
c->QueuePacket(outapp);
c->UpdateXTargetType((mnpcs->Number == 1) ? GroupMarkTarget1 : ((mnpcs->Number == 2) ? GroupMarkTarget2 : GroupMarkTarget3), m);
}
}
safe_delete(outapp);
}
void Group::ClearAllNPCMarks()
{
// This method is designed to be called when the number of members in the group drops below 3 and leadership AA
// may no longer be used. It removes all NPC marks.
//
for(uint8 i = 0; i < MAX_GROUP_MEMBERS; ++i)
if(members[i] && members[i]->IsClient())
SendMarkedNPCsToMember(members[i]->CastToClient(), true);
for(int i = 0; i < MAX_MARKED_NPCS; ++i)
{
if(MarkedNPCs[i])
{
Mob* m = entity_list.GetMob(MarkedNPCs[i]);
if(m)
m->IsTargeted(-1);
}
MarkedNPCs[i] = 0;
}
}
int8 Group::GetNumberNeedingHealedInGroup(int8 hpr, bool includePets) {
int8 needHealed = 0;
for( int i = 0; i<MAX_GROUP_MEMBERS; i++) {
if(members[i] && !members[i]->qglobal) {
if(members[i]->GetHPRatio() <= hpr)
needHealed++;
if(includePets) {
if(members[i]->GetPet() && members[i]->GetPet()->GetHPRatio() <= hpr) {
needHealed++;
}
}
}
}
return needHealed;
}
void Group::UpdateGroupAAs()
{
// This method updates the Groups Leadership abilities from the Player Profile of the Leader.
//
Mob *m = GetLeader();
if(m && m->IsClient())
m->CastToClient()->GetGroupAAs(&LeaderAbilities);
else
memset(&LeaderAbilities, 0, sizeof(GroupLeadershipAA_Struct));
SaveGroupLeaderAA();
}
void Group::QueueHPPacketsForNPCHealthAA(Mob* sender, const EQApplicationPacket* app)
{
// Send a mobs HP packets to group members if the leader has the NPC Health AA and the mob is the
// target of the group's main assist, or is marked, and the member doesn't already have the mob targeted.
if(!sender || !app || !GetLeadershipAA(groupAANPCHealth))
return;
uint16 SenderID = sender->GetID();
if(SenderID != AssistTargetID)
{
bool Marked = false;
for(int i = 0; i < MAX_MARKED_NPCS; ++i)
{
if(MarkedNPCs[i] == SenderID)
{
Marked = true;
break;
}
}
if(!Marked)
return;
}
for(unsigned int i = 0; i < MAX_GROUP_MEMBERS; ++i)
if(members[i] && members[i]->IsClient())
{
if(!members[i]->GetTarget() || (members[i]->GetTarget()->GetID() != SenderID))
{
members[i]->CastToClient()->QueuePacket(app);
}
}
}
void Group::ChangeLeader(Mob* newleader)
{
// this changes the current group leader, notifies other members, and updates leadship AA
// if the new leader is invalid, do nothing
if (!newleader || !newleader->IsClient())
return;
Mob* oldleader = GetLeader();
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct));
GroupJoin_Struct* gu = (GroupJoin_Struct*) outapp->pBuffer;
gu->action = groupActMakeLeader;
strcpy(gu->membername, newleader->GetName());
strcpy(gu->yourname, oldleader->GetName());
SetLeader(newleader);
database.SetGroupLeaderName(GetID(), newleader->GetName());
UpdateGroupAAs();
gu->leader_aas = LeaderAbilities;
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (members[i] && members[i]->IsClient())
{
if (members[i]->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD)
members[i]->CastToClient()->SendGroupLeaderChangePacket(newleader->GetName());
members[i]->CastToClient()->QueuePacket(outapp);
}
}
safe_delete(outapp);
}
const char *Group::GetClientNameByIndex(uint8 index)
{
return membername[index];
}
void Group::UpdateXTargetMarkedNPC(uint32 Number, Mob *m)
{
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if(members[i] && members[i]->IsClient())
{
members[i]->CastToClient()->UpdateXTargetType((Number == 1) ? GroupMarkTarget1 : ((Number == 2) ? GroupMarkTarget2 : GroupMarkTarget3), m);
}
}
}
void Group::SetDirtyAutoHaters()
{
for (int i = 0; i < MAX_GROUP_MEMBERS; ++i)
if (members[i] && members[i]->IsClient())
members[i]->CastToClient()->SetDirtyAutoHaters();
}
void Group::JoinRaidXTarget(Raid *raid, bool first)
{
if (!GetXTargetAutoMgr()->empty())
raid->GetXTargetAutoMgr()->merge(*GetXTargetAutoMgr());
for (int i = 0; i < MAX_GROUP_MEMBERS; ++i) {
if (members[i] && members[i]->IsClient()) {
auto *client = members[i]->CastToClient();
if (!first)
client->RemoveAutoXTargets();
client->SetXTargetAutoMgr(raid->GetXTargetAutoMgr());
if (!client->GetXTargetAutoMgr()->empty())
client->SetDirtyAutoHaters();
}
}
}
void Group::SetMainTank(const char *NewMainTankName)
{
MainTankName = NewMainTankName;
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if(!strncasecmp(membername[i], NewMainTankName, 64))
MemberRoles[i] |= RoleTank;
else
MemberRoles[i] &= ~RoleTank;
}
}
void Group::SetMainAssist(const char *NewMainAssistName)
{
MainAssistName = NewMainAssistName;
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if(!strncasecmp(membername[i], NewMainAssistName, 64))
MemberRoles[i] |= RoleAssist;
else
MemberRoles[i] &= ~RoleAssist;
}
}
void Group::SetPuller(const char *NewPullerName)
{
PullerName = NewPullerName;
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if(!strncasecmp(membername[i], NewPullerName, 64))
MemberRoles[i] |= RolePuller;
else
MemberRoles[i] &= ~RolePuller;
}
}
bool Group::AmIMainTank(const char *mob_name)
{
if (!mob_name)
return false;
return !((bool)MainTankName.compare(mob_name));
}
bool Group::AmIMainAssist(const char *mob_name)
{
if (!mob_name)
return false;
return !((bool)MainTankName.compare(mob_name));
}
bool Group::AmIPuller(const char *mob_name)
{
if (!mob_name)
return false;
return !((bool)PullerName.compare(mob_name));
}
bool Group::HasRole(Mob *m, uint8 Role)
{
if(!m)
return false;
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if((m == members[i]) && (MemberRoles[i] & Role))
return true;
}
return false;
}
void Group::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required /*= true*/, bool ignore_sender /*= true*/, float distance /*= 0*/) {
if (sender && sender->IsClient()) {
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (!members[i])
continue;
if (!members[i]->IsClient())
continue;
if (ignore_sender && members[i] == sender)
continue;
/* If we don't have a distance requirement - send to all members */
if (distance == 0) {
members[i]->CastToClient()->QueuePacket(app, ack_required);
}
else {
/* If negative distance - we check if current distance is greater than X */
if (distance <= 0 && DistanceSquared(sender->GetPosition(), members[i]->GetPosition()) >= (distance * distance)) {
members[i]->CastToClient()->QueuePacket(app, ack_required);
}
/* If positive distance - we check if current distance is less than X */
else if (distance >= 0 && DistanceSquared(sender->GetPosition(), members[i]->GetPosition()) <= (distance * distance)) {
members[i]->CastToClient()->QueuePacket(app, ack_required);
}
}
}
}
}
bool Group::DoesAnyMemberHaveExpeditionLockout(
const std::string& expedition_name, const std::string& event_name, int max_check_count)
{
if (max_check_count <= 0)
{
max_check_count = MAX_GROUP_MEMBERS;
}
for (int i = 0; i < MAX_GROUP_MEMBERS && i < max_check_count; ++i)
{
if (membername[i][0])
{
if (Expedition::HasLockoutByCharacterName(membername[i], expedition_name, event_name))
{
return true;
}
}
}
return false;
}
bool Group::IsLeader(const char* name) {
if (name) {
for (const auto& m : membername) {
if (!strcmp(m, name)) {
return true;
}
}
}
return false;
}