zaela-Server/zone/groups.cpp

2502 lines
61 KiB
C++
Raw Permalink Normal View History

2013-05-09 10:44:08 -04:00
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org)
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
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.
2013-05-06 13:07:41 -04:00
2013-05-09 10:44:08 -04:00
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
2013-02-16 16:14:39 -08:00
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
2013-05-09 10:44:08 -04:00
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
2013-05-06 13:07:41 -04:00
2013-05-09 10:44:08 -04:00
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
2013-02-16 16:14:39 -08:00
*/
#include "../common/global_define.h"
#include "../common/eqemu_logsys.h"
2013-02-16 16:14:39 -08:00
#include "masterentity.h"
2014-08-21 23:46:01 -07:00
#include "npc_ai.h"
2013-02-16 16:14:39 -08:00
#include "../common/packet_functions.h"
#include "../common/packet_dump.h"
2014-08-21 19:33:02 -07:00
#include "../common/string_util.h"
2013-02-16 16:14:39 -08:00
#include "worldserver.h"
2013-02-16 16:14:39 -08:00
extern EntityList entity_list;
extern WorldServer worldserver;
/*
note about how groups work:
2013-05-06 13:07:41 -04:00
A group contains 2 list, a list of pointers to members and a
2013-02-16 16:14:39 -08:00
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.
2013-02-16 16:14:39 -08:00
Only members in this zone will have non-null pointers in the
2013-05-06 13:07:41 -04:00
members array.
2013-02-16 16:14:39 -08:00
*/
//create a group which should already exist in the database
2013-05-06 13:07:41 -04:00
Group::Group(uint32 gid)
2013-02-16 16:14:39 -08:00
: GroupIDConsumer(gid)
{
2013-05-04 18:06:58 -07:00
leader = nullptr;
mentoree = nullptr;
2013-02-16 16:14:39 -08:00
memset(members,0,sizeof(Mob*) * MAX_GROUP_MEMBERS);
AssistTargetID = 0;
TankTargetID = 0;
PullerTargetID = 0;
2017-03-18 00:54:03 -04:00
disbandcheck = false;
2013-02-16 16:14:39 -08:00
memset(&LeaderAbilities, 0, sizeof(GroupLeadershipAA_Struct));
uint32 i;
for(i=0;i<MAX_GROUP_MEMBERS;i++)
{
2013-05-06 13:07:41 -04:00
memset(membername[i],0,64);
2013-02-16 16:14:39 -08:00
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);
2013-02-16 16:14:39 -08:00
}
//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;
2017-03-18 00:54:03 -04:00
disbandcheck = false;
2013-02-16 16:14:39 -08:00
memset(&LeaderAbilities, 0, sizeof(GroupLeadershipAA_Struct));
mentoree = nullptr;
2013-02-16 16:14:39 -08:00
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);
2013-02-16 16:14:39 -08:00
}
Group::~Group()
2013-05-06 13:07:41 -04:00
{
2013-02-16 16:14:39 -08:00
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).
2013-02-16 16:14:39 -08:00
void Group::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter) {
//avoid unneeded work
if(copper == 0 && silver == 0 && gold == 0 && platinum == 0)
return;
2013-05-06 13:07:41 -04:00
2013-05-09 10:44:08 -04:00
uint32 i;
uint8 membercount = 0;
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
// Don't split with Mercs or Bots
if (members[i] != nullptr && members[i]->IsClient()) {
2013-05-09 10:44:08 -04:00
membercount++;
}
}
if (membercount == 0)
return;
uint32 mod;
//try to handle round off error a little better
if(membercount > 1) {
mod = platinum % membercount;
if((mod) > 0) {
platinum -= mod;
gold += 10 * mod;
}
mod = gold % membercount;
if((mod) > 0) {
gold -= mod;
silver += 10 * mod;
}
mod = silver % membercount;
if((mod) > 0) {
silver -= mod;
copper += 10 * mod;
}
}
//calculate the splits
//We can still round off copper pieces, but I dont care
uint32 sc;
uint32 cpsplit = copper / membercount;
sc = copper % membercount;
uint32 spsplit = silver / membercount;
uint32 gpsplit = gold / membercount;
uint32 ppsplit = platinum / membercount;
char buf[128];
buf[63] = '\0';
std::string msg = "You receive";
2013-05-09 10:44:08 -04:00
bool one = false;
if(ppsplit > 0) {
snprintf(buf, 63, " %u platinum", ppsplit);
msg += buf;
one = true;
}
if(gpsplit > 0) {
if(one)
msg += ",";
snprintf(buf, 63, " %u gold", gpsplit);
msg += buf;
one = true;
}
if(spsplit > 0) {
if(one)
msg += ",";
snprintf(buf, 63, " %u silver", spsplit);
msg += buf;
one = true;
}
if(cpsplit > 0) {
if(one)
msg += ",";
//this message is not 100% accurate for the splitter
//if they are receiving any roundoff
snprintf(buf, 63, " %u copper", cpsplit);
msg += buf;
one = true;
}
msg += " as your split";
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (members[i] != nullptr && members[i]->IsClient()) { // If Group Member is Client
Client *c = members[i]->CastToClient();
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
//I could not get MoneyOnCorpse to work, so we use this
c->AddMoneyToPP(cpsplit, spsplit, gpsplit, ppsplit, true);
c->Message(Chat::Green, msg.c_str());
2013-05-09 10:44:08 -04:00
}
}
2013-02-16 16:14:39 -08:00
}
bool Group::AddMember(Mob* newmember, const char *NewMemberName, uint32 CharacterID, bool ismerc)
2013-02-16 16:14:39 -08:00
{
2013-05-09 10:44:08 -04:00
bool InZone = true;
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
// 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;
}
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
if(GroupCount() >= MAX_GROUP_MEMBERS) //Sanity check for merging groups together.
{
2013-05-09 10:44:08 -04:00
return false;
}
2013-05-09 10:44:08 -04:00
if(!newmember)
{
2013-05-09 10:44:08 -04:00
InZone = false;
}
2013-05-09 10:44:08 -04:00
else
{
NewMemberName = newmember->GetCleanName();
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
if(newmember->IsClient())
{
2013-05-09 10:44:08 -04:00
CharacterID = newmember->CastToClient()->CharacterID();
}
2013-05-09 10:44:08 -04:00
if(newmember->IsMerc())
{
Client* owner = newmember->CastToMerc()->GetMercOwner();
if(owner)
{
CharacterID = owner->CastToClient()->CharacterID();
}
ismerc = true;
2013-05-09 10:44:08 -04:00
}
}
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
// See if they are already in the group
uint32 i = 0;
2013-05-09 10:44:08 -04:00
for (i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
2013-05-09 10:44:08 -04:00
if(!strcasecmp(membername[i], NewMemberName))
{
2013-05-09 10:44:08 -04:00
return false;
}
}
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
// Put them in the group
for (i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if (membername[i][0] == '\0')
{
if(InZone)
{
2013-05-09 10:44:08 -04:00
members[i] = newmember;
}
strcpy(membername[i], NewMemberName);
MemberRoles[i] = 0;
2013-05-09 10:44:08 -04:00
break;
}
}
2013-02-16 16:14:39 -08:00
// Is this even possible based on the above loops? Remove?
2013-05-09 10:44:08 -04:00
if (i == MAX_GROUP_MEMBERS)
{
2013-05-09 10:44:08 -04:00
return false;
}
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
int x=1;
//build the template join packet
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct));
2013-05-09 10:44:08 -04:00
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)
{
2013-05-09 10:44:08 -04:00
//fill in group join & send it
strcpy(gj->yourname, members[i]->GetCleanName());
if(members[i]->IsClient())
{
2013-05-09 10:44:08 -04:00
members[i]->CastToClient()->QueuePacket(outapp);
//put new member into existing group members' list(s)
2013-05-09 10:44:08 -04:00
strcpy(members[i]->CastToClient()->GetPP().groupMembers[this->GroupCount()-1], NewMemberName);
}
//put existing group member(s) into the new member's list
if(InZone && newmember->IsClient())
{
2013-05-09 10:44:08 -04:00
if(IsLeader(members[i]))
{
2013-05-09 10:44:08 -04:00
strcpy(newmember->CastToClient()->GetPP().groupMembers[0], members[i]->GetCleanName());
}
else
{
2013-05-09 10:44:08 -04:00
strcpy(newmember->CastToClient()->GetPP().groupMembers[x], members[i]->GetCleanName());
x++;
2013-02-16 16:14:39 -08:00
}
2013-05-09 10:44:08 -04:00
}
}
}
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);
2013-05-09 10:44:08 -04:00
}
}
Group* group = newmember->CastToClient()->GetGroup();
if (group) {
group->SendHPManaEndPacketsTo(newmember);
group->SendHPPacketsFrom(newmember);
}
2013-05-09 10:44:08 -04:00
}
else
{
2013-05-09 10:44:08 -04:00
database.SetGroupID(NewMemberName, GetID(), CharacterID, ismerc);
}
2013-02-16 16:14:39 -08:00
if (newmember && newmember->IsClient())
newmember->CastToClient()->JoinGroupXTargets(this);
2013-05-09 10:44:08 -04:00
safe_delete(outapp);
2013-02-16 16:14:39 -08:00
#ifdef BOTS
Bot::UpdateGroupCastingRoles(this);
#endif
2013-05-09 10:44:08 -04:00
return true;
2013-02-16 16:14:39 -08:00
}
void Group::AddMember(const char *NewMemberName)
{
2013-05-09 10:44:08 -04:00
// 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);
2013-02-16 16:14:39 -08:00
MemberRoles[i] = 0;
2013-05-09 10:44:08 -04:00
break;
}
}
2013-02-16 16:14:39 -08:00
}
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)
2013-02-16 16:14:39 -08:00
{
if(member && member->IsClient()) {
2013-02-16 16:14:39 -08:00
EQApplicationPacket hpapp;
EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct));
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) {
if(members[i] && members[i] != member) {
2013-02-16 16:14:39 -08:00
members[i]->CreateHPPacket(&hpapp);
2017-03-18 01:09:48 -04:00
member->CastToClient()->QueuePacket(&hpapp, false);
2017-03-18 00:34:49 -04:00
safe_delete_array(hpapp.pBuffer);
2017-03-18 00:51:54 -04:00
hpapp.size = 0;
if (member->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD) {
2013-02-16 16:14:39 -08:00
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();
2013-02-16 16:14:39 -08:00
member->CastToClient()->QueuePacket(&outapp, false);
MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer;
2013-02-16 16:14:39 -08:00
outapp.SetOpcode(OP_MobEnduranceUpdate);
endurance_update->endurance = members[i]->GetEndurancePercent();
2013-02-16 16:14:39 -08:00
member->CastToClient()->QueuePacket(&outapp, false);
}
}
}
}
}
void Group::SendHPPacketsFrom(Mob *member)
{
EQApplicationPacket hp_app;
if(!member)
return;
2013-05-09 10:44:08 -04:00
member->CreateHPPacket(&hp_app);
2013-02-16 16:14:39 -08:00
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()) {
2013-02-16 16:14:39 -08:00
members[i]->CastToClient()->QueuePacket(&hp_app);
if (members[i]->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD) {
2013-02-16 16:14:39 -08:00
outapp.SetOpcode(OP_MobManaUpdate);
MobManaUpdate_Struct *mana_update = (MobManaUpdate_Struct *)outapp.pBuffer;
mana_update->spawn_id = member->GetID();
mana_update->mana = member->GetManaPercent();
2013-02-16 16:14:39 -08:00
members[i]->CastToClient()->QueuePacket(&outapp, false);
MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer;
2013-02-16 16:14:39 -08:00
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();
2013-02-16 16:14:39 -08:00
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
2013-02-16 16:14:39 -08:00
bool Group::UpdatePlayer(Mob* update){
bool updateSuccess = false;
2013-02-16 16:14:39 -08:00
VerifyGroup();
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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')
2013-02-16 16:14:39 -08:00
memset(pp.groupMembers[i], 0, 64);
else
strn0cpy(pp.groupMembers[i], membername[i], 64);
}
if(IsNPCMarker(update->CastToClient()))
{
NPCMarkerID = update->GetID();
SendLeadershipAAUpdate();
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
for (i = 0; i < MAX_GROUP_MEMBERS; i++)
{
if (!strcasecmp(membername[i],update->GetCleanName()))
2013-02-16 16:14:39 -08:00
{
members[i] = update;
members[i]->SetGrouped(true);
updateSuccess = true;
break;
2013-02-16 16:14:39 -08:00
}
}
// 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();
#ifdef BOTS
Bot::UpdateGroupCastingRoles(this);
#endif
return updateSuccess;
2013-02-16 16:14:39 -08:00
}
void Group::MemberZoned(Mob* removemob) {
uint32 i;
2013-05-04 18:06:58 -07:00
if (removemob == nullptr)
2013-02-16 16:14:39 -08:00
return;
if(removemob == GetLeader())
2013-05-04 18:06:58 -07:00
SetLeader(nullptr);
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
2017-02-20 21:41:15 -05:00
if (members[i] == removemob) {
members[i] = nullptr;
//should NOT clear the name, it is used for world communication.
break;
2013-02-16 16:14:39 -08:00
}
2013-05-09 10:44:08 -04:00
}
2013-05-09 10:44:08 -04:00
if(removemob->IsClient() && HasRole(removemob, RoleAssist))
SetGroupAssistTarget(0);
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
if(removemob->IsClient() && HasRole(removemob, RoleTank))
SetGroupTankTarget(0);
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
if(removemob->IsClient() && HasRole(removemob, RolePuller))
SetGroupPullerTarget(0);
if (removemob->IsClient() && removemob == mentoree)
mentoree = nullptr;
#ifdef BOTS
Bot::UpdateGroupCastingRoles(this);
#endif
2013-02-16 16:14:39 -08:00
}
void Group::SendGroupJoinOOZ(Mob* NewMember) {
if (NewMember == nullptr)
{
return;
}
if (!NewMember->HasGroup())
{
return;
}
//send updates to clients out of zone...
2016-05-25 16:10:28 -04:00
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);
}
2013-02-16 16:14:39 -08:00
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) {
2013-02-16 16:14:39 -08:00
UnDelegateMainAssist(MainAssistName.c_str());
}
ClearAllNPCMarks();
}
if (Name == mentoree_name)
ClearGroupMentor();
2013-02-16 16:14:39 -08:00
return true;
}
}
return false;
}
bool Group::DelMember(Mob* oldmember, bool ignoresender)
2013-02-16 16:14:39 -08:00
{
if (oldmember == nullptr)
{
2013-02-16 16:14:39 -08:00
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)
{
2013-05-04 18:06:58 -07:00
members[i] = nullptr;
2013-02-16 16:14:39 -08:00
membername[i][0] = '\0';
memset(membername[i],0,64);
MemberRoles[i] = 0;
break;
2013-05-09 10:44:08 -04:00
}
2013-02-16 16:14:39 -08:00
}
/* 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;
}
}
}
}
2017-04-23 13:26:21 -04:00
if (!GetLeaderName())
{
DisbandGroup();
return true;
}
2016-05-25 16:10:28 -04:00
auto pack = new ServerPacket(ServerOP_GroupLeave, sizeof(ServerGroupLeave_Struct));
2013-02-16 16:14:39 -08:00
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());
2013-02-16 16:14:39 -08:00
worldserver.SendPacket(pack);
safe_delete(pack);
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct));
2013-02-16 16:14:39 -08:00
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++) {
2013-05-04 18:06:58 -07:00
if (members[i] == nullptr) {
2013-02-16 16:14:39 -08:00
//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)
{
2013-02-16 16:14:39 -08:00
strcpy(gu->yourname,oldmember->GetCleanName());
strcpy(gu->membername,oldmember->GetCleanName());
gu->action = groupActLeave;
if(oldmember->IsClient())
oldmember->CastToClient()->QueuePacket(outapp);
2013-05-09 10:44:08 -04:00
}
safe_delete(outapp);
2013-02-16 16:14:39 -08:00
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);
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
oldmember->SetGrouped(false);
disbandcheck = true;
if(HasRole(oldmember, RoleTank))
{
SetGroupTankTarget(0);
UnDelegateMainTank(oldmember->GetCleanName());
2013-02-16 16:14:39 -08:00
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(HasRole(oldmember, RoleAssist))
{
2013-05-09 10:44:08 -04:00
SetGroupAssistTarget(0);
UnDelegateMainAssist(oldmember->GetCleanName());
2013-02-16 16:14:39 -08:00
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(HasRole(oldmember, RolePuller))
{
SetGroupPullerTarget(0);
UnDelegatePuller(oldmember->GetCleanName());
2013-02-16 16:14:39 -08:00
}
2013-05-06 13:07:41 -04:00
if (oldmember->GetName() == mentoree_name)
ClearGroupMentor();
if(oldmember->IsClient()) {
2013-02-16 16:14:39 -08:00
SendMarkedNPCsToMember(oldmember->CastToClient(), true);
oldmember->CastToClient()->LeaveGroupXTargets(this);
}
2013-02-16 16:14:39 -08:00
if(GroupCount() < 3)
{
UnDelegateMarkNPC(NPCMarkerName.c_str());
if (GetLeader() && GetLeader()->IsClient() && GetLeader()->CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoD) {
2013-02-16 16:14:39 -08:00
UnDelegateMainAssist(MainAssistName.c_str());
}
ClearAllNPCMarks();
}
#ifdef BOTS
Bot::UpdateGroupCastingRoles(this);
#endif
2013-05-06 13:07:41 -04:00
return true;
2013-02-16 16:14:39 -08:00
}
// 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);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
float range2 = range*range;
2014-08-03 16:04:55 -04:00
float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range;
2013-02-16 16:14:39 -08:00
// 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].targettype != ST_GroupNoPets && caster->GetPet() && caster->HasPetAffinity() && !caster->GetPet()->IsCharmed())
2013-02-16 16:14:39 -08:00
caster->SpellOnTarget(spell_id, caster->GetPet());
#endif
}
2013-05-04 18:06:58 -07:00
else if(members[z] != nullptr)
2013-02-16 16:14:39 -08:00
{
distance = DistanceSquared(caster->GetPosition(), members[z]->GetPosition());
2014-08-03 16:04:55 -04:00
if(distance <= range2 && distance >= min_range2) {
members[z]->CalcSpellPowerDistanceMod(spell_id, distance);
2013-02-16 16:14:39 -08:00
caster->SpellOnTarget(spell_id, members[z]);
#ifdef GROUP_BUFF_PETS
if(spells[spell_id].targettype != ST_GroupNoPets && members[z]->GetPet() && members[z]->HasPetAffinity() && !members[z]->GetPet()->IsCharmed())
2013-02-16 16:14:39 -08:00
caster->SpellOnTarget(spell_id, members[z]->GetPet());
#endif
} else
2019-09-02 03:26:44 -05:00
LogSpells("Group spell: [{}] is out of range [{}] at distance [{}] from [{}]", members[z]->GetName(), range, distance, caster->GetName());
2013-02-16 16:14:39 -08:00
}
}
castspell = false;
disbandcheck = true;
}
// does the caster + group
void Group::GroupBardPulse(Mob* caster, uint16 spell_id) {
uint32 z;
float range, distance;
if(!caster)
return;
castspell = true;
range = caster->GetAOERange(spell_id);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
float range2 = range*range;
for(z=0; z < MAX_GROUP_MEMBERS; z++) {
if(members[z] == caster) {
caster->BardPulse(spell_id, caster);
#ifdef GROUP_BUFF_PETS
if(caster->GetPet() && caster->HasPetAffinity() && !caster->GetPet()->IsCharmed())
caster->BardPulse(spell_id, caster->GetPet());
#endif
}
2013-05-04 18:06:58 -07:00
else if(members[z] != nullptr)
2013-02-16 16:14:39 -08:00
{
distance = DistanceSquared(caster->GetPosition(), members[z]->GetPosition());
2013-02-16 16:14:39 -08:00
if(distance <= range2) {
members[z]->BardPulse(spell_id, caster);
#ifdef GROUP_BUFF_PETS
if(members[z]->GetPet() && members[z]->HasPetAffinity() && !members[z]->GetPet()->IsCharmed())
members[z]->GetPet()->BardPulse(spell_id, caster);
#endif
} else
2019-09-02 03:26:44 -05:00
LogSpells("Group bard pulse: [{}] is out of range [{}] at distance [{}] from [{}]", members[z]->GetName(), range, distance, caster->GetName());
2013-02-16 16:14:39 -08:00
}
}
}
bool Group::IsGroupMember(Mob* client)
{
bool Result = false;
if(client) {
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (members[i] == client)
Result = true;
}
}
return Result;
}
bool Group::IsGroupMember(const char *Name)
{
if(Name)
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; i++)
if((strlen(Name) == strlen(membername[i])) && !strncmp(membername[i], Name, strlen(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);
2013-02-16 16:14:39 -08:00
}
2016-05-25 16:10:28 -04:00
auto pack =
new ServerPacket(ServerOP_OOZGroupMessage, sizeof(ServerGroupChannelMessage_Struct) + strlen(message) + 1);
2013-02-16 16:14:39 -08:00
ServerGroupChannelMessage_Struct* gcm = (ServerGroupChannelMessage_Struct*)pack->pBuffer;
gcm->zoneid = zone->GetZoneID();
gcm->groupid = GetID();
gcm->instanceid = zone->GetInstanceID();
strcpy(gcm->from, sender->GetCleanName());
2013-02-16 16:14:39 -08:00
strcpy(gcm->message, message);
worldserver.SendPacket(pack);
2013-05-06 13:07:41 -04:00
safe_delete(pack);
2013-02-16 16:14:39 -08:00
}
uint32 Group::GetTotalGroupDamage(Mob* other) {
2013-05-09 10:44:08 -04:00
uint32 total = 0;
2013-02-16 16:14:39 -08:00
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) {
#ifdef BOTS
Bot::UpdateGroupCastingRoles(this, true);
#endif
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupUpdate_Struct));
2013-02-16 16:14:39 -08:00
GroupUpdate_Struct* gu = (GroupUpdate_Struct*) outapp->pBuffer;
gu->action = groupActDisband;
2013-05-04 18:06:58 -07:00
Client *Leader = nullptr;
2013-02-16 16:14:39 -08:00
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++)
{
if (members[i] == nullptr)
{
2013-02-16 16:14:39 -08:00
continue;
}
if (members[i]->IsClient())
{
2013-02-16 16:14:39 -08:00
if(IsLeader(members[i]))
{
2013-02-16 16:14:39 -08:00
Leader = members[i]->CastToClient();
}
2013-02-16 16:14:39 -08:00
strcpy(gu->yourname, members[i]->GetCleanName());
database.SetGroupID(members[i]->GetCleanName(), 0, members[i]->CastToClient()->CharacterID(), false);
2013-02-16 16:14:39 -08:00
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);
}
2013-02-16 16:14:39 -08:00
}
members[i]->SetGrouped(false);
2013-05-04 18:06:58 -07:00
members[i] = nullptr;
2013-02-16 16:14:39 -08:00
membername[i][0] = '\0';
}
ClearAllNPCMarks();
2013-05-06 13:07:41 -04:00
2016-05-25 16:10:28 -04:00
auto pack = new ServerPacket(ServerOP_DisbandGroup, sizeof(ServerDisbandGroup_Struct));
2013-02-16 16:14:39 -08:00
ServerDisbandGroup_Struct* dg = (ServerDisbandGroup_Struct*)pack->pBuffer;
dg->zoneid = zone->GetZoneID();
dg->groupid = GetID();
dg->instance_id = zone->GetInstanceID();
worldserver.SendPacket(pack);
2013-05-06 13:07:41 -04:00
safe_delete(pack);
2013-02-16 16:14:39 -08:00
if(GetID() != 0)
{
2013-05-09 10:44:08 -04:00
database.ClearGroup(GetID());
}
2013-02-16 16:14:39 -08:00
entity_list.RemoveGroup(GetID());
if(Leader && (Leader->IsLFP()))
{
2013-02-16 16:14:39 -08:00
Leader->UpdateLFP();
}
safe_delete(outapp);
}
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());
}
}
#ifdef BOTS
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());
}
}
#endif
2013-02-16 16:14:39 -08:00
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;
2013-05-06 13:07:41 -04:00
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupUpdate2_Struct));
2013-05-06 13:07:41 -04:00
GroupUpdate2_Struct* gu = (GroupUpdate2_Struct*)outapp->pBuffer;
2013-02-16 16:14:39 -08:00
gu->action = type;
strcpy(gu->yourname,member->GetName());
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
int x = 0;
gu->leader_aas = LeaderAbilities;
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
2013-05-04 18:06:58 -07:00
if((members[i] != nullptr) && IsLeader(members[i]))
2013-02-16 16:14:39 -08:00
{
strcpy(gu->leadersname, members[i]->GetName());
break;
}
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
2013-05-04 18:06:58 -07:00
if (members[i] != nullptr && members[i] != member)
strcpy(gu->membername[x++], members[i]->GetCleanName());
2013-02-16 16:14:39 -08:00
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.
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct));
2013-05-06 13:07:41 -04:00
GroupJoin_Struct* gu = (GroupJoin_Struct*)outapp->pBuffer;
2013-02-16 16:14:39 -08:00
gu->action = groupActAAUpdate;
gu->leader_aas = LeaderAbilities;
gu->NPCMarkerID = GetNPCMarkerID();
2013-05-06 13:07:41 -04:00
uint32 i = 0;
2013-02-16 16:14:39 -08:00
for (i = 0;i < MAX_GROUP_MEMBERS; ++i)
{
2013-02-16 16:14:39 -08:00
if(members[i] && members[i]->IsClient())
{
strcpy(gu->yourname, members[i]->GetName());
strcpy(gu->membername, members[i]->GetName());
members[i]->CastToClient()->QueuePacket(outapp);
}
}
2013-02-16 16:14:39 -08:00
safe_delete(outapp);
}
uint8 Group::GroupCount() {
uint8 MemberCount = 0;
for(uint8 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
2013-02-16 16:14:39 -08:00
if(membername[i][0])
{
2013-02-16 16:14:39 -08:00
++MemberCount;
}
}
2013-02-16 16:14:39 -08:00
return MemberCount;
}
uint32 Group::GetHighestLevel()
{
uint32 level = 1;
uint32 i;
2013-02-16 16:14:39 -08:00
for (i = 0; i < MAX_GROUP_MEMBERS; i++)
2013-05-09 10:44:08 -04:00
{
2013-02-16 16:14:39 -08:00
if (members[i])
2013-05-09 10:44:08 -04:00
{
2013-02-16 16:14:39 -08:00
if(members[i]->GetLevel() > level)
level = members[i]->GetLevel();
2013-05-09 10:44:08 -04:00
}
2013-02-16 16:14:39 -08:00
}
return level;
}
2013-02-16 16:14:39 -08:00
uint32 Group::GetLowestLevel()
{
uint32 level = 255;
uint32 i;
2013-02-16 16:14:39 -08:00
for (i = 0; i < MAX_GROUP_MEMBERS; i++)
2013-05-09 10:44:08 -04:00
{
2013-02-16 16:14:39 -08:00
if (members[i])
2013-05-09 10:44:08 -04:00
{
2013-02-16 16:14:39 -08:00
if(members[i]->GetLevel() < level)
level = members[i]->GetLevel();
2013-05-09 10:44:08 -04:00
}
2013-02-16 16:14:39 -08:00
}
return level;
}
void Group::TeleportGroup(Mob* sender, uint32 zoneID, uint16 instance_id, float x, float y, float z, float heading)
{
uint32 i;
2013-05-09 10:44:08 -04:00
for (i = 0; i < MAX_GROUP_MEMBERS; i++)
{
2013-05-04 18:06:58 -07:00
if (members[i] != nullptr && members[i]->IsClient() && members[i] != sender)
2013-05-09 10:44:08 -04:00
{
2013-02-16 16:14:39 -08:00
members[i]->CastToClient()->MovePC(zoneID, instance_id, x, y, z, heading, 0, ZoneSolicited);
}
2013-05-06 13:07:41 -04:00
}
2013-02-16 16:14:39 -08:00
}
bool Group::LearnMembers() {
std::string query = StringFormat("SELECT name FROM group_id WHERE groupid = %lu", (unsigned long)GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
return false;
2013-02-16 16:14:39 -08:00
2019-09-01 22:05:44 -05:00
if (results.RowCount() == 0) {
LogError(
"Error getting group members for group [{}]: [{}]",
(unsigned long) GetID(),
results.ErrorMessage().c_str()
);
return false;
}
int memberIndex = 0;
for(auto row = results.begin(); row != results.end(); ++row) {
if(!row[0])
continue;
members[memberIndex] = nullptr;
strn0cpy(membername[memberIndex], row[0], 64);
memberIndex++;
2013-02-16 16:14:39 -08:00
}
return true;
2013-02-16 16:14:39 -08:00
}
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).
*/
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (membername[i][0] == '\0') {
#if EQDEBUG >= 7
2019-09-01 23:54:24 -05:00
LogDebug("Group [{}]: Verify [{}]: Empty.\n", (unsigned long)GetID(), i);
2013-02-16 16:14:39 -08:00
#endif
2013-05-04 18:06:58 -07:00
members[i] = nullptr;
2013-02-16 16:14:39 -08:00
continue;
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
Mob *them = entity_list.GetMob(membername[i]);
if(them == nullptr && members[i] != nullptr) { //they aren't in zone
2013-02-16 16:14:39 -08:00
#if EQDEBUG >= 6
2019-09-01 23:54:24 -05:00
LogDebug("Member of group [{}] named [{}] has disappeared!!", (unsigned long)GetID(), membername[i]);
2013-02-16 16:14:39 -08:00
#endif
membername[i][0] = '\0';
2013-05-04 18:06:58 -07:00
members[i] = nullptr;
2013-02-16 16:14:39 -08:00
continue;
}
2013-05-06 13:07:41 -04:00
2013-05-04 18:06:58 -07:00
if(them != nullptr && members[i] != them) { //our pointer is out of date... not so good.
2013-02-16 16:14:39 -08:00
#if EQDEBUG >= 5
2019-09-01 23:54:24 -05:00
LogDebug("Member of group [{}] named [{}] had an out of date pointer!!", (unsigned long)GetID(), membername[i]);
2013-02-16 16:14:39 -08:00
#endif
members[i] = them;
continue;
}
#if EQDEBUG >= 8
2019-09-01 23:54:24 -05:00
LogDebug("Member of group [{}] named [{}] is valid", (unsigned long)GetID(), membername[i]);
2013-02-16 16:14:39 -08:00
#endif
}
}
2019-08-11 00:14:02 -05:00
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) {
2013-02-16 16:14:39 -08:00
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
2013-05-04 18:06:58 -07:00
if(members[i] == nullptr)
2013-02-16 16:14:39 -08:00
continue;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(members[i] == sender)
continue;
if(!members[i]->IsClient())
continue;
2013-05-06 13:07:41 -04:00
2019-08-11 00:14:02 -05:00
members[i]->MessageString(type, string_id, message, message2, message3, message4, message5, message6, message7, message8, message9, 0);
2013-02-16 16:14:39 -08:00
}
}
void Client::LeaveGroup() {
Group *g = GetGroup();
2013-05-06 13:07:41 -04:00
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)
{
2013-02-16 16:14:39 -08:00
g->DisbandGroup();
}
2013-02-16 16:14:39 -08:00
else
{
2013-02-16 16:14:39 -08:00
g->DelMember(this);
if (GetMerc() != nullptr && g == GetMerc()->GetGroup() )
{
GetMerc()->RemoveMercFromGroup(GetMerc(), GetMerc()->GetGroup());
}
}
}
else
{
2013-02-16 16:14:39 -08:00
//force things a little
database.SetGroupID(GetCleanName(), 0, CharacterID(), false);
if (GetMerc())
{
database.SetGroupID(GetMerc()->GetCleanName(), 0, CharacterID(), true);
}
2013-02-16 16:14:39 -08:00
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
isgrouped = false;
}
void Group::HealGroup(uint32 heal_amt, Mob* caster, float range)
2013-02-16 16:14:39 -08:00
{
if (!caster)
return;
2014-02-10 12:04:35 -05:00
if (!range)
range = 200;
2014-02-10 12:04:35 -05:00
float distance;
float range2 = range*range;
2013-02-16 16:14:39 -08:00
int numMem = 0;
unsigned int gi = 0;
for(; gi < MAX_GROUP_MEMBERS; gi++)
{
if(members[gi]){
distance = DistanceSquared(caster->GetPosition(), members[gi]->GetPosition());
2014-02-10 12:04:35 -05:00
if(distance <= range2){
numMem += 1;
}
2013-02-16 16:14:39 -08:00
}
}
heal_amt /= numMem;
for(gi = 0; gi < MAX_GROUP_MEMBERS; gi++)
{
if(members[gi]){
distance = DistanceSquared(caster->GetPosition(), members[gi]->GetPosition());
2014-02-10 12:04:35 -05:00
if(distance <= range2){
members[gi]->HealDamage(heal_amt, caster);
members[gi]->SendHPUpdate();
}
2013-02-16 16:14:39 -08:00
}
}
}
void Group::BalanceHP(int32 penalty, float range, Mob* caster, int32 limit)
2013-02-16 16:14:39 -08:00
{
2014-02-10 12:04:35 -05:00
if (!caster)
return;
if (!range)
range = 200;
int dmgtaken = 0, numMem = 0, dmgtaken_tmp = 0;
2014-02-10 12:04:35 -05:00
float distance;
float range2 = range*range;
2013-02-16 16:14:39 -08:00
unsigned int gi = 0;
for(; gi < MAX_GROUP_MEMBERS; gi++)
{
if(members[gi]){
distance = DistanceSquared(caster->GetPosition(), members[gi]->GetPosition());
2014-02-10 12:04:35 -05:00
if(distance <= range2){
dmgtaken_tmp = members[gi]->GetMaxHP() - members[gi]->GetHP();
if (limit && (dmgtaken_tmp > limit))
dmgtaken_tmp = limit;
dmgtaken += (dmgtaken_tmp);
2014-02-10 12:04:35 -05:00
numMem += 1;
}
2013-02-16 16:14:39 -08:00
}
}
dmgtaken += dmgtaken * penalty / 100;
dmgtaken /= numMem;
for(gi = 0; gi < MAX_GROUP_MEMBERS; gi++)
{
if(members[gi]){
distance = DistanceSquared(caster->GetPosition(), members[gi]->GetPosition());
2014-02-10 12:04:35 -05:00
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();
}
2013-02-16 16:14:39 -08:00
}
}
}
}
void Group::BalanceMana(int32 penalty, float range, Mob* caster, int32 limit)
2013-02-16 16:14:39 -08:00
{
2014-02-10 12:04:35 -05:00
if (!caster)
return;
if (!range)
range = 200;
2014-02-10 12:04:35 -05:00
float distance;
float range2 = range*range;
int manataken = 0, numMem = 0, manataken_tmp = 0;
2013-02-16 16:14:39 -08:00
unsigned int gi = 0;
for(; gi < MAX_GROUP_MEMBERS; gi++)
{
if(members[gi] && (members[gi]->GetMaxMana() > 0)){
distance = DistanceSquared(caster->GetPosition(), members[gi]->GetPosition());
2014-02-10 12:04:35 -05:00
if(distance <= range2){
manataken_tmp = members[gi]->GetMaxMana() - members[gi]->GetMana();
if (limit && (manataken_tmp > limit))
manataken_tmp = limit;
manataken += (manataken_tmp);
2014-02-10 12:04:35 -05:00
numMem += 1;
}
2013-02-16 16:14:39 -08:00
}
}
manataken += manataken * penalty / 100;
manataken /= numMem;
if (limit && (manataken > limit))
manataken = limit;
2013-02-16 16:14:39 -08:00
for(gi = 0; gi < MAX_GROUP_MEMBERS; gi++)
{
if(members[gi]){
distance = DistanceSquared(caster->GetPosition(), members[gi]->GetPosition());
2014-02-10 12:04:35 -05:00
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();
}
2013-02-16 16:14:39 -08:00
}
}
}
}
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())
2013-02-16 16:14:39 -08:00
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;
2013-05-04 18:06:58 -07:00
UpdateXTargetMarkedNPC(i+1, nullptr);
2013-02-16 16:14:39 -08:00
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);
2013-05-04 18:06:58 -07:00
UpdateXTargetMarkedNPC(Number, nullptr);
2013-02-16 16:14:39 -08:00
}
if(EntityID)
{
Mob* m = entity_list.GetMob(Target->GetID());
if(m)
m->IsTargeted(1);
}
}
MarkedNPCs[Number - 1] = EntityID;
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_MarkNPC, sizeof(MarkNPC_Struct));
2013-02-16 16:14:39 -08:00
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);
2013-05-06 13:07:41 -04:00
UpdateXTargetMarkedNPC(Number, m);
2013-02-16 16:14:39 -08:00
}
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.
//
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
bool updateDB = false;
if(!NewMainTankName)
return;
Mob *m = entity_list.GetMob(NewMainTankName);
if(!m)
return;
if(MainTankName != NewMainTankName || !toggle)
updateDB = true;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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())
2019-09-01 22:05:44 -05:00
LogError("Unable to set group main tank: [{}]\n", results.ErrorMessage().c_str());
2013-02-16 16:14:39 -08:00
}
}
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.
//
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
bool updateDB = false;
if(!NewMainAssistName)
return;
Mob *m = entity_list.GetMob(NewMainAssistName);
if(!m)
return;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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())
2019-09-01 22:05:44 -05:00
LogError("Unable to set group main assist: [{}]\n", results.ErrorMessage().c_str());
2013-02-16 16:14:39 -08:00
}
}
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.
//
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
bool updateDB = false;
if(!NewPullerName)
return;
Mob *m = entity_list.GetMob(NewPullerName);
if(!m)
return;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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())
2019-09-01 22:05:44 -05:00
LogError("Unable to set group main puller: [{}]\n", results.ErrorMessage().c_str());
2013-02-16 16:14:39 -08:00
}
}
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)
2013-02-16 16:14:39 -08:00
{
if(toggle)
c->Message(Chat::White, "%s is now Main Tank.", MainTankName.c_str());
2013-02-16 16:14:39 -08:00
else
c->Message(Chat::White, "%s is no longer Main Tank.", MainTankName.c_str());
2013-02-16 16:14:39 -08:00
}
else
{
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_GroupRoles, sizeof(GroupRole_Struct));
2013-02-16 16:14:39 -08:00
GroupRole_Struct *grs = (GroupRole_Struct*)outapp->pBuffer;
strn0cpy(grs->Name1, MainTankName.c_str(), sizeof(grs->Name1));
strn0cpy(grs->Name2, GetLeaderName(), sizeof(grs->Name2));
2013-05-06 13:07:41 -04:00
grs->RoleNumber = 1;
2013-02-16 16:14:39 -08:00
grs->Toggle = toggle;
c->QueuePacket(outapp);
safe_delete(outapp);
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
}
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)
2013-02-16 16:14:39 -08:00
{
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_DelegateAbility, sizeof(DelegateAbility_Struct));
2013-02-16 16:14:39 -08:00
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
{
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_GroupRoles, sizeof(GroupRole_Struct));
2013-02-16 16:14:39 -08:00
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)
2013-02-16 16:14:39 -08:00
{
if(toggle)
c->Message(Chat::White, "%s is now Puller.", PullerName.c_str());
2013-02-16 16:14:39 -08:00
else
c->Message(Chat::White, "%s is no longer Puller.", PullerName.c_str());
2013-02-16 16:14:39 -08:00
}
else
{
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_GroupRoles, sizeof(GroupRole_Struct));
2013-02-16 16:14:39 -08:00
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())
2019-09-01 22:05:44 -05:00
LogError("Unable to clear group main tank: [{}]\n", results.ErrorMessage().c_str());
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(!toggle) {
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) {
if(members[i] && members[i]->IsClient())
{
NotifyMainTank(members[i]->CastToClient(), toggle);
2013-05-04 18:06:58 -07:00
members[i]->CastToClient()->UpdateXTargetType(GroupTank, nullptr, "");
members[i]->CastToClient()->UpdateXTargetType(GroupTankTarget, nullptr);
2013-02-16 16:14:39 -08:00
}
}
}
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) {
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_DelegateAbility, sizeof(DelegateAbility_Struct));
2013-02-16 16:14:39 -08:00
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);
2013-05-04 18:06:58 -07:00
members[i]->CastToClient()->UpdateXTargetType(GroupAssist, nullptr, "");
2013-02-16 16:14:39 -08:00
}
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())
2019-09-01 22:05:44 -05:00
LogError("Unable to clear group main assist: [{}]\n", results.ErrorMessage().c_str());
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(!toggle)
{
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if(members[i] && members[i]->IsClient())
{
NotifyMainAssist(members[i]->CastToClient(), toggle);
2013-05-04 18:06:58 -07:00
members[i]->CastToClient()->UpdateXTargetType(GroupAssistTarget, nullptr);
2013-02-16 16:14:39 -08:00
}
}
}
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())
2019-09-01 22:05:44 -05:00
LogError("Unable to clear group main puller: [{}]\n", results.ErrorMessage().c_str());
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(!toggle) {
for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) {
if(members[i] && members[i]->IsClient())
{
NotifyPuller(members[i]->CastToClient(), toggle);
2013-05-04 18:06:58 -07:00
members[i]->CastToClient()->UpdateXTargetType(Puller, nullptr, "");
members[i]->CastToClient()->UpdateXTargetType(PullerTarget, nullptr);
2013-02-16 16:14:39 -08:00
}
}
}
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())
2019-09-01 22:05:44 -05:00
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())
2019-09-01 22:05:44 -05:00
LogError("Unable to clear group mentor: [{}]\n", results.ErrorMessage().c_str());
}
2013-02-16 16:14:39 -08:00
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;
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_SetGroupTarget, sizeof(MarkNPC_Struct));
2013-02-16 16:14:39 -08:00
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);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
}
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())
2019-09-01 22:05:44 -05:00
LogError("Unable to set group mark npc: [{}]\n", results.ErrorMessage().c_str());
2013-02-16 16:14:39 -08:00
}
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;
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_DelegateAbility, sizeof(DelegateAbility_Struct));
2013-02-16 16:14:39 -08:00
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;
2013-05-06 13:07:41 -04:00
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_DelegateAbility, sizeof(DelegateAbility_Struct));
2013-02-16 16:14:39 -08:00
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())
2019-09-01 22:05:44 -05:00
LogError("Unable to clear group marknpc: [{}]\n", results.ErrorMessage().c_str());
2013-02-16 16:14:39 -08:00
}
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.
2016-05-25 16:10:28 -04:00
auto queryBuffer = new char[sizeof(GroupLeadershipAA_Struct) * 2 + 1];
database.DoEscapeString(queryBuffer, (char *)&LeaderAbilities, sizeof(GroupLeadershipAA_Struct));
2013-02-16 16:14:39 -08:00
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())
2019-09-01 22:05:44 -05:00
LogError("Unable to store LeadershipAA: [{}]\n", results.ErrorMessage().c_str());
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
}
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;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(TankTargetID == ID)
TankTargetID = 0;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(PullerTargetID == ID)
PullerTargetID = 0;
for(int i = 0; i < MAX_MARKED_NPCS; ++i)
{
if(MarkedNPCs[i] == ID)
{
MarkedNPCs[i] = 0;
2013-05-04 18:06:58 -07:00
UpdateXTargetMarkedNPC(i + 1, nullptr);
2013-02-16 16:14:39 -08:00
}
}
}
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;
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_MarkNPC, sizeof(MarkNPC_Struct));
2013-02-16 16:14:39 -08:00
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;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
for( int i = 0; i<MAX_GROUP_MEMBERS; i++) {
if(members[i] && !members[i]->qglobal) {
2013-05-06 13:07:41 -04:00
if(members[i]->GetHPRatio() <= hpr)
2013-02-16 16:14:39 -08:00
needHealed++;
if(includePets) {
if(members[i]->GetPet() && members[i]->GetPet()->GetHPRatio() <= hpr) {
needHealed++;
}
}
}
2013-05-06 13:07:41 -04:00
}
2013-02-16 16:14:39 -08:00
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())
2013-02-16 16:14:39 -08:00
return;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
Mob* oldleader = GetLeader();
2013-05-06 13:07:41 -04:00
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct));
2013-02-16 16:14:39 -08:00
GroupJoin_Struct* gu = (GroupJoin_Struct*) outapp->pBuffer;
gu->action = groupActMakeLeader;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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)
2013-02-16 16:14:39 -08:00
members[i]->CastToClient()->SendGroupLeaderChangePacket(newleader->GetName());
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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();
}
}
}
2013-02-16 16:14:39 -08:00
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));
}
2013-02-16 16:14:39 -08:00
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;
}
2013-05-09 10:44:08 -04:00
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);
}
}
}
}
}