mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-12 09:31:30 +00:00
* Implement spell AI pulling, fix throw stone * more pull tweaks * holding check at start of ai process * fully implement ^pull logic to always return, can still be overidden by ^attack * Rewrite ^pull logic and handling. **MORE** Add ^setassistee command to set who your bots will assist. Bots will always assist you first before anyone else. If the rule Bots, AllowCrossGroupRaidAssist is enabled bots will assist the group or raid main assists. Rewrites logic in handling of pull and returning to ensure bots make it back to their location. * Move HateLine to a better ID * cleanup ST_Self logic in CastChecks * Removed unused BotSpellTypeRequiresLoS * Move fizzle message to define * add timer checks to Idle/Engaged/Pursue CastCheck to early terminate * Add back !IsBotNonSpellFighter() check to the different CastCheck * Correct IsValidSpellRange * Implement AAs and harmtouch/layonhands to ^cast --- fix IsValidSpellRange * Add PetDamageShields and PetResistBuffs to IsPetBotSpellType() * Add priorities to HateLine inserts for db update * Remove SpellTypeRequiresCastChecks * Add bot check to DetermineSpellTargets for IsIllusionSpell * merge with previous * Correct bot checks for ST_GroupClientAndPet * Remove misc target_type checks * Add lull/aelull to ^cast * Add more checks for CommandedSubTypes::AETarget * remove unneeded checks on IsValidSpellTypeBySpellID * add to aelull * rewrite GetCorrectSpellType * Add IsBlockedBuff to CastChecks * Add spellid option to ^cast to allow casting of a specific spell by ID * ^cast adjustments for spellid casts * Add missing alert round for ranged attacks * More castcheck improvements * CanUseBotSpell for ^cast * remove ht/loh from attack ai * remove SetCombatRoundForAlerts that triggered every engagement * Add RangedAttackImmunity checks before trying to ranged attack * move bot backstab to mob * fix MinStatusToBypassCreateLimit * more backstab to mob cleanup * add bot checks to tryheadshot / tryassassinate * adjust version number for bots * add back m_mob_check_moving_timer, necessary? * add sanity checks for classattacks * Get rid of Bots:BotGroupXP and change logic to support Bots:SameRaidGroupForXP Bots won't do anything if not in the same group so this should more accurately control only when in the same raid group. * add "confirm" check to ^delete * Update bot.cpp * Remove `id` from bot_settings, correct types * Implement blocked_buffs and blocked_pet_buffs * more blocked buff tweaks * add beneficial check to ^blockedbuffs * command grammar * missing ) * Move getnames for categories and settings to mob, rename hptomed/manatomed * add GetBotSpellCategoryIDByShortName and CopyBotBlockedPetBuffs, update ^defaultsettings command * cls cleanup * Allow bots to clear HasProjectIllusion flag * Add PercentChanceToCastGroupCure * Implmenet PetCures, add some missing types for defaults/chance to cast * Change GetRaidByBotName to GetRaidByBot * Typo on PetBuffs implement * Change GetSpellListSpellType to GetParentSpellType * missing from GetChanceToCastBySpellType * Fix performance in IsValidSpellRange by flipping HasProjectIllusion * merge with prev * merge with cls cleanup * Reorder IsTargetAlreadyReceivingSpell/CheckSpellLevelRestriction/IsBlockedBuff * Combine GatherGroupSpellTargets and GatherSpellTargets * Cleanup IsTargetAlreadyReceivingSpell * Fix ^petsettype to account for usable levels of spells and remove hardcoded level limits. * Remove Bot_AICheckCloseBeneficialSpells and use AttemptCloseBeneficialSpells for better performance * remove default hold for resist buffa * move IsValidSpellRange further down castchecks * raid optimizations * correct name checking to match players * more name checks and add proper soft deletes to bots * organize some checks in IsImmuneToBotSpell * Fix GetRaidByBotName and GetRaidByBot checks to not loop unnecessarily * Move GatherSpellTargets to mob * Change GetPrioritizedBotSpellsBySpellType to vector Some slipped through in "organize some checks in IsImmuneToBotSpell" * Move GatherSpellTargets and Raid to stored variables. Missing some in "organize some checks in IsImmuneToBotSpell" * comment out precheck, delays, thresholds, etc logging missed some in "organize some checks in IsImmuneToBotSpell" * Missing IsInGroupOrRaid cleanup * Implement AIBot_spells_by_type to reduce looping when searching for spells * Add _tempSpellType as placeholder for any future passthru * todo * Move bot_list from std::list to std::unordered_map like other entities * Fix missing raid assignment for GetStoredRaid in IsInGroupOrRaid * TempPet owned by bots that get the kill will now give exp like a client would * Remove unnecessary checks in bot process (closescanmoving timer, verify raid, send hp/mana/end packet * Fix client spell commands from saving the wrong setting * Cleanup ^copysettings command and add new commands * Add pet option to ^taunt No longer has toggle, required on/off option and an optional "pet" option to control pets' taunting state * Allow pet types to ^cast, prevent failure spam, add cure check * more raid optimizations, should be final. 10 clients, 710 bots, 10 raids, ~250 pets sits around 3.5% CPU idle * Move spell range check to proper location * Implement ^discipline * remove ^aggressive/^defensive * remove this for a separate PR * cleanup * Add BotGroupSay method * todo list * Add missing bot_blocked_buffs to schema * Remove plural on ^spelltypeidsand ^spelltypenames * Move spelltype names, spell subtypes, category names and setting names to maps. * move los checks to mob.cpp * Bot CampAll fix * Bots special_attacks.cpp fix * Add zero check for bot spawn limits If the spawn limit rule is set to 0 and spawn limit is set by bucket, if no class buckets are set, it defaults to the rule of 0 and renders the player unable to spawn bots. This adds a check where if the rule and class bucket are 0, it will check for the spawn limit bucket * Add HasSkill checks to bot special abilities (kick/bash/etc) * code cleanup 1 * code cleanup 2 * code cleanup 3 * code cleanup 4 * fix ^cast wirh commanded types * Remove bcspells, fix helper_send_usage_required_bots * linux build fix * remove completed todo * Allow inventory give to specific ID slots * Update TODO * Correct slot ranges for inventorygive * Add zone specific spawn limits and zone specific forced spawn limits * remove bd. from update queries where it doesn't exist * Rename _spellSettings to m_bot_spell_settings * Add IsPetOwnerOfClientBot(), add Lua and Perl methods * Make botOwnerCharacterID snakecase * Throw bot_camp_timer behind Bots:Enabled rule * Move various Bot<>Checks logging to BotSpellChecks * Remove from LogCategoryName * Consolidate IsInGroupOrRaid * Consolidate GatherSpellTargets * Add missing Bot Spell Type Checks to log * Add GetParentSpellType when checking spelltypes for idle, engaged, pursue CastChecks. * Consolidate AttemptForcedCastSpell * Consolidate SetBotBlockedBuff/SetBotBlockedPetBuff * Add list option to ^spellpriority commands. * Move client functions to client_bot * Move mob functions to mob_bot * Move bot spdat functions to spdat_bot * Move SendCommandHelpWindow to SendBotCommandHelpWindow and simplify * Change char_id to character_id for bot_settings * update todo * Fix typo on merge conflict * Cleanup command format changes, remove hardcoded class IDs in examples. * Set #illusionblock for players to guide access * Move client commands for bot spells from gm commands to existing bot commands * Fix alignment issues * More alignment fixes * More cleanup 1 * More cleanup 2 * Fix BotMeditate to med at proper percentages * Correct GetStopMeleeLevel checks for some buff checks * Add back hpmanaend update to bot raid, force timer update to prevent spamming * Remove log * Cleanup ranged and ammo calculations - Adds throwing check for match * Add check in distance calculations to stay at range if set even if no ammo or ranged * Move melee distance calculations to better function * Add GetBuffTargets helper * Missing p_item, s_item in CombatRangeInput * Linux test? * Reduce GetCorrectBotSpellType branching slightly This is still an ugly ass function but my brain is melted * Line fixes * Make bot pets only do half damage in pvp * Add bot pet pvp damage to tune * Add bot pet check for AIYellForHelp * Add bots to UseSpellImpliedTargeting * Move toggleranged, togglehelm and illusionblock to new help window. Add actionable support * Add bot and bot pet checks to various spells, auras and targeting checks that were missing. * update todo * New lines * Correct DoLosChecks * Remove Log TestDebug * Remove _Struct from struct declarations * Add bot check to IsAttackAllowed for GetUltimateOwner to skip entity list where possible * Wrap SaveBotSettings in Bots Enabled check * Remove comment * Wrap bot setting loading for clients in bots enabled rule * Cleanup BlockedBuffs logic in SpellOnTarget * Rename BotSpells_Struct/BotSpells_Struct_wIndex * Rename spawn/create status bypass rules, fix return for spawn limit * Remove unnecessary return in CanBuffStack, cleanup * Enable recastdelay support for clients * Remove unused variables * Rename _assistee to bot_assistee * hardcode BotCommandHelpWindow colors * todo * Fix ^cast summoncorpse * todo * Reimplement secondary colors to BotSendCommandHelpWindow * Give ^copysettings/^defaultsettings more options, cleanup. * Cleanup some commands * Add comment to CheckLosCheat/CheckLosCheatExempt * Make struct BotSpellSettings snake case * Allow duplicate casts of same spell on target for heals and cures * Add default delay to cures * Remove unused methods * Implement missing ^spellresistlimits/^resistlimits command * Move functions out of mob.h and cleanup * Return for GetRawBotList This checks offline bots too * Rename BotGroupSay to RaidGroupSay * Prevent bots from forming their own group if a bot that is a group leader is removed from the raid * Linux fix? * IsPetOwner fixes * Add remove option to list for ^blockedbuffs / ^blockedpetbuffs * Implement ^spellannouncecasts to toggle announcing casts of spell types * Remove rule Bots:BardsAnnounceCasts * Update bot.h * Remove unused no_pets option from GatherSpellTargets * Move ^attack response back to normal chat window (other) * Set lower limit of spell delays to 100 rather than 1 * Correct pet checks on GetUltimateSpell functions * Add rules (Bots, AICastSpellTypeDelay, Bots, AICastSpellTypeHeldDelay) to prevent spamming of failed spell type AI casts * Correct pet buff type logic to catch DS/Resists with other spell effects in them * Fix defaults for clients * Add more logic for necros/shaman for default heal thresholds due to lich and canni * Rename SpellHold, SpellDelay, SpellMinThreshold, SpellMaxThreshold, SpellRecastDelay to fit SpellType style naming * Use GetTempSpellType() for announce check in RaidGroupSay * Make all spell shortnames plural where applicable * Update bot.cpp * Bots:BotsUseLiveBlockedMessage filter to spell failure * Move GetSpellTargetList to only get called when necessary to reduce overhead * formatting * Formatting * Simplify case SE_Illusion and SE_IllusionCopy for GetIllusionBlock * Clean up InterruptSpell * Cleanup IsBot() checks for DetermineSpellTargets->ST_GroupClientAndPet * Cleanup range/aoe_range check in SpellFinished * Cleanup DetermineSpellTargets->ST_GroupNoPets * Cleanup DetermineSpellTargets->ST_Self for bot summon corpse * Cleanup DetermineSpellTargets->ST_Pet * Cleanup bot logic in TryBackstab * Cleanup IsAttackAllowed checks for bots and their pets * Cleanup StopMoving for bots * Cleanup CanThisClassTripleAttack * Fix casting for GetIllusionBlock checks * Formatting * Fix DetermineSpellTargets for group spells (this also wasn't properly checking the rule Character:EnableTGB in master) * Cleanup spelltarget grabbing logic, consolidate group heals in to GetNumberNeedingHealedInGroup * Throw added client los pet checks behind LoS cheat rule for bots * CLeanup give_exp on npc death logic and ensure client pets always pass. * Undo unintended rename from previous refactor * Remove pointless Bots, SameRaidGroupForXP rule * Revision to 0690783a9d1e99005d6bee0824597ea920e26df9 --------- Co-authored-by: Akkadius <akkadius1@gmail.com>
2691 lines
66 KiB
C++
2691 lines
66 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 "worldserver.h"
|
|
#include "string_ids.h"
|
|
#include "../common/events/player_event_logs.h"
|
|
#include "../common/repositories/group_id_repository.h"
|
|
#include "../common/repositories/group_leaders_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 member_name 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, bool share)
|
|
{
|
|
// Return early if no money to split.
|
|
if (!copper && !silver && !gold && !platinum) {
|
|
return;
|
|
}
|
|
|
|
// splitter can not be nullptr
|
|
if (!splitter) {
|
|
return;
|
|
}
|
|
|
|
// find number of clients in group and check if splitter is in group
|
|
|
|
uint8 member_count = 0;
|
|
bool splitter_in_group = false;
|
|
|
|
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 (members[i]->CastToClient() == splitter) {
|
|
splitter_in_group = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return if no group members found.
|
|
if (!member_count) {
|
|
return;
|
|
}
|
|
|
|
// Splitter must be in group
|
|
if (!splitter_in_group) {
|
|
return;
|
|
}
|
|
|
|
uint8 random_member = zone->random.Int(0, member_count - 1);
|
|
|
|
// Calculate split and remainder for each coin type
|
|
uint32 copper_split = copper / member_count;
|
|
uint32 copper_remainder = copper % member_count;
|
|
uint32 silver_split = silver / member_count;
|
|
uint32 silver_remainder = silver % member_count;
|
|
uint32 gold_split = gold / member_count;
|
|
uint32 gold_remainder = gold % member_count;
|
|
uint32 platinum_split = platinum / member_count;
|
|
uint32 platinum_remainder = platinum % member_count;
|
|
|
|
// Loop through the group members to split the coins.
|
|
for (const auto &m: members) {
|
|
if (m && m->IsClient()) {
|
|
Client *member_client = m->CastToClient();
|
|
|
|
uint32 receive_copper = copper_split;
|
|
uint32 receive_silver = silver_split;
|
|
uint32 receive_gold = gold_split;
|
|
uint32 receive_platinum = platinum_split;
|
|
|
|
// if /split is used then splitter gets the remainder + split.
|
|
// if /autosplit is used then random players in the group will get the remainder + split.
|
|
if(share ? member_client == splitter : member_client == members[random_member]) {
|
|
receive_copper += copper_remainder;
|
|
receive_silver += silver_remainder;
|
|
receive_gold += gold_remainder;
|
|
receive_platinum += platinum_remainder;
|
|
}
|
|
|
|
// the group member other than the character doing the /split only gets this message "(splitter) shares the money with the group"
|
|
if (share && member_client != splitter) {
|
|
member_client->MessageString(
|
|
YOU_RECEIVE_AS_SPLIT,
|
|
SHARE_MONEY,
|
|
splitter->GetCleanName()
|
|
);
|
|
}
|
|
|
|
// Check if there are any coins to add to the player's purse.
|
|
if (receive_copper || receive_silver || receive_gold || receive_platinum) {
|
|
member_client->AddMoneyToPP(receive_copper, receive_silver, receive_gold, receive_platinum, true);
|
|
member_client->MessageString(
|
|
Chat::MoneySplit,
|
|
YOU_RECEIVE_AS_SPLIT,
|
|
Strings::Money(receive_platinum, receive_gold, receive_silver, receive_copper).c_str()
|
|
);
|
|
}
|
|
|
|
// If logging of player money transactions is enabled, record the transaction.
|
|
if (player_event_logs.IsEventEnabled(PlayerEvent::SPLIT_MONEY)) {
|
|
auto e = PlayerEvent::SplitMoneyEvent{
|
|
.copper = receive_copper,
|
|
.silver = receive_silver,
|
|
.gold = receive_gold,
|
|
.platinum = receive_platinum,
|
|
.player_money_balance = member_client->GetCarriedMoney(),
|
|
};
|
|
RecordPlayerEventLogWithClient(member_client, PlayerEvent::SPLIT_MONEY, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Group::AddMember(Mob* new_member, std::string new_member_name, uint32 character_id, bool is_merc)
|
|
{
|
|
bool in_zone = 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 character_id of the new member, if they are out of zone.
|
|
if (!new_member && new_member_name.empty()) {
|
|
return false;
|
|
}
|
|
|
|
if (GroupCount() >= MAX_GROUP_MEMBERS) { //Sanity check for merging groups together.
|
|
return false;
|
|
}
|
|
|
|
if (!new_member) {
|
|
in_zone = false;
|
|
} else {
|
|
new_member_name = new_member->GetCleanName();
|
|
|
|
if (new_member->IsClient()) {
|
|
character_id = new_member->CastToClient()->CharacterID();
|
|
}
|
|
|
|
if (new_member->IsMerc()) {
|
|
Client* o = new_member->CastToMerc()->GetMercenaryOwner();
|
|
if (o) {
|
|
character_id = o->CastToClient()->CharacterID();
|
|
}
|
|
|
|
is_merc = true;
|
|
}
|
|
}
|
|
|
|
// See if they are already in the group
|
|
for (const auto& m : membername) {
|
|
if (Strings::EqualFold(m, new_member_name)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Put them in the group
|
|
for (int slot_id = 0; slot_id < MAX_GROUP_MEMBERS; ++slot_id) {
|
|
if (membername[slot_id][0] == '\0') {
|
|
if (in_zone) {
|
|
members[slot_id] = new_member;
|
|
}
|
|
|
|
strcpy(membername[slot_id], new_member_name.c_str());
|
|
MemberRoles[slot_id] = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int x = 1;
|
|
|
|
//build the template join packet
|
|
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct));
|
|
|
|
auto gj = (GroupJoin_Struct*) outapp->pBuffer;
|
|
|
|
strcpy(gj->membername, new_member_name.c_str());
|
|
|
|
gj->action = groupActJoin;
|
|
gj->leader_aas = LeaderAbilities;
|
|
|
|
for (int slot_id = 0; slot_id < MAX_GROUP_MEMBERS; slot_id++) {
|
|
if (members[slot_id] && members[slot_id] != new_member) {
|
|
//fill in group join & send it
|
|
strcpy(gj->yourname, members[slot_id]->GetCleanName());
|
|
if (members[slot_id]->IsClient()) {
|
|
members[slot_id]->CastToClient()->QueuePacket(outapp);
|
|
|
|
//put new member into existing group members' list(s)
|
|
strcpy(
|
|
members[slot_id]->CastToClient()->GetPP().groupMembers[GroupCount() - 1],
|
|
new_member_name.c_str()
|
|
);
|
|
}
|
|
|
|
//put existing group member(s) into the new member's list
|
|
if (in_zone && new_member && new_member->IsClient()) {
|
|
if (IsLeader(members[slot_id])) {
|
|
strcpy(new_member->CastToClient()->GetPP().groupMembers[0], members[slot_id]->GetCleanName());
|
|
} else {
|
|
strcpy(new_member->CastToClient()->GetPP().groupMembers[x], members[slot_id]->GetCleanName());
|
|
++x;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (in_zone && new_member) {
|
|
//put new member in his own list.
|
|
new_member->SetGrouped(true);
|
|
|
|
if (new_member->IsClient()) {
|
|
strcpy(new_member->CastToClient()->GetPP().groupMembers[x], new_member_name.c_str());
|
|
|
|
new_member->CastToClient()->Save();
|
|
|
|
AddToGroup(new_member);
|
|
|
|
SendMarkedNPCsToMember(new_member->CastToClient());
|
|
|
|
NotifyMainTank(new_member->CastToClient(), 1);
|
|
NotifyMainAssist(new_member->CastToClient(), 1);
|
|
NotifyPuller(new_member->CastToClient(), 1);
|
|
}
|
|
|
|
if (new_member->IsMerc()) {
|
|
Client* o = new_member->CastToMerc()->GetMercenaryOwner();
|
|
if (o) {
|
|
AddToGroup(new_member);
|
|
}
|
|
}
|
|
|
|
Group* g = new_member->CastToClient()->GetGroup();
|
|
if (g) {
|
|
g->SendHPManaEndPacketsTo(new_member);
|
|
g->SendHPPacketsFrom(new_member);
|
|
}
|
|
|
|
} else {
|
|
AddToGroup(
|
|
AddToGroupRequest{
|
|
.mob = nullptr,
|
|
.member_name = new_member_name,
|
|
.character_id = character_id,
|
|
}
|
|
);
|
|
}
|
|
|
|
if (new_member && new_member->IsClient()) {
|
|
new_member->CastToClient()->JoinGroupXTargets(this);
|
|
}
|
|
|
|
safe_delete(outapp);
|
|
|
|
if (RuleB(Bots, Enabled)) {
|
|
Bot::UpdateGroupCastingRoles(this);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Group::AddMember(const std::string& new_member_name)
|
|
{
|
|
// This method should be called when both the new member and the group leader are in a different zone to this one.
|
|
for (const auto& m : membername) {
|
|
if (Strings::EqualFold(m, new_member_name)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) {
|
|
if (membername[i][0] == '\0') {
|
|
strcpy(membername[i], new_member_name.c_str());
|
|
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) {
|
|
|
|
if (!removemob) {
|
|
return;
|
|
}
|
|
|
|
if (removemob == GetLeader()) {
|
|
SetLeader(nullptr);
|
|
}
|
|
|
|
//should NOT clear the name, it is used for world communication.
|
|
for (auto & m : members) {
|
|
if (m) {
|
|
if (m->IsBot() && m->CastToBot()->GetBotOwner() && m->CastToBot()->GetBotOwner() == removemob) {
|
|
m = nullptr;
|
|
}
|
|
else if (m == removemob) {
|
|
m = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (removemob->IsClient() && HasRole(removemob, RoleAssist)) {
|
|
SetGroupAssistTarget(nullptr);
|
|
}
|
|
|
|
if (removemob->IsClient() && HasRole(removemob, RoleTank)) {
|
|
SetGroupTankTarget(nullptr);
|
|
}
|
|
|
|
if (removemob->IsClient() && HasRole(removemob, RolePuller)) {
|
|
SetGroupPullerTarget(nullptr);
|
|
}
|
|
|
|
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().c_str(), 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().empty())
|
|
{
|
|
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())
|
|
{
|
|
RemoveFromGroup(oldmember);
|
|
}
|
|
|
|
if(oldmember->IsMerc())
|
|
{
|
|
Client* owner = oldmember->CastToMerc()->GetMercenaryOwner();
|
|
if(owner)
|
|
{
|
|
RemoveFromGroup(oldmember);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
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)
|
|
{
|
|
membername[i][0] = '\0';
|
|
continue;
|
|
}
|
|
|
|
if (members[i]->IsClient())
|
|
{
|
|
if(IsLeader(members[i]))
|
|
{
|
|
Leader = members[i]->CastToClient();
|
|
}
|
|
|
|
strcpy(gu->yourname, members[i]->GetCleanName());
|
|
RemoveFromGroup(members[i]);
|
|
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()->GetMercenaryOwner();
|
|
if(owner)
|
|
{
|
|
RemoveFromGroup(members[i]);
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
std::list<uint32> Group::GetRawBotList()
|
|
{
|
|
std::list<uint32> bot_list;
|
|
|
|
const auto& l = GroupIdRepository::GetWhere(
|
|
database,
|
|
fmt::format(
|
|
"`group_id` = {}",
|
|
GetID()
|
|
)
|
|
);
|
|
|
|
for (const auto& e : l) {
|
|
if (e.bot_id) {
|
|
bot_list.push_back(e.bot_id);
|
|
}
|
|
}
|
|
|
|
return bot_list;
|
|
}
|
|
|
|
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() {
|
|
|
|
const auto& l = GroupIdRepository::GetWhere(
|
|
database,
|
|
fmt::format(
|
|
"`group_id` = {}",
|
|
GetID()
|
|
)
|
|
);
|
|
|
|
if (l.empty()) {
|
|
LogError(
|
|
"Error getting group members for group [{}]",
|
|
GetID()
|
|
);
|
|
}
|
|
|
|
for (int i = 0; i < MAX_GROUP_MEMBERS; ++i) {
|
|
members[i] = nullptr;
|
|
memset(membername[i], 0, 64);
|
|
MemberRoles[i] = 0;
|
|
}
|
|
|
|
int member_index = 0;
|
|
for (const auto& e : l) {
|
|
if (member_index >= MAX_GROUP_MEMBERS) {
|
|
LogError(
|
|
"Too many members in group [{}]",
|
|
GetID()
|
|
);
|
|
break;
|
|
}
|
|
|
|
if (e.name.empty()) {
|
|
members[member_index] = nullptr;
|
|
membername[member_index][0] = '\0';
|
|
} else {
|
|
members[member_index] = nullptr;
|
|
strn0cpy(membername[member_index], e.name.c_str(), 64);
|
|
}
|
|
|
|
++member_index;
|
|
}
|
|
|
|
VerifyGroup();
|
|
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 && members[i]) { //they aren't in zone
|
|
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 (RuleB(Bots, Enabled)) {
|
|
std::list<uint32> sbl = g->GetRawBotList();
|
|
|
|
for (auto botID : sbl) {
|
|
auto b = entity_list.GetBotByBotID(botID);
|
|
|
|
if (b) {
|
|
if (b->GetBotOwnerCharacterID() == CharacterID()) {
|
|
MemberCount -= 1;
|
|
}
|
|
}
|
|
else {
|
|
if (database.botdb.GetOwnerID(botID) == CharacterID()) {
|
|
MemberCount -= 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MemberCount < 3) {
|
|
g->DisbandGroup();
|
|
}
|
|
else {
|
|
g->DelMember(this);
|
|
|
|
if (GetMerc() != nullptr && g == GetMerc()->GetGroup()) {
|
|
GetMerc()->RemoveMercFromGroup(GetMerc(), GetMerc()->GetGroup());
|
|
}
|
|
|
|
if (RuleB(Bots, Enabled)) {
|
|
g->RemoveClientsBots(this);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
//force things a little
|
|
Group::RemoveFromGroup(this);
|
|
|
|
if (GetMerc()) {
|
|
Group::RemoveFromGroup(GetMerc());
|
|
}
|
|
}
|
|
|
|
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().c_str(), 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().c_str(), 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().c_str(), 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()
|
|
{
|
|
const uint32 group_id = GetID();
|
|
|
|
if (!group_id) {
|
|
return;
|
|
}
|
|
|
|
// 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.
|
|
|
|
std::string aa((char*) &LeaderAbilities, sizeof(GroupLeadershipAA_Struct));
|
|
|
|
if (!GroupLeadersRepository::UpdateLeadershipAA(database, aa, group_id)) {
|
|
LogError("Unable to store GroupLeadershipAA for group_id: [{}]", group_id);
|
|
}
|
|
}
|
|
|
|
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 include_pets) {
|
|
int8 need_healed = 0;
|
|
|
|
for( int i = 0; i<MAX_GROUP_MEMBERS; i++) {
|
|
if(members[i] && !members[i]->qglobal) {
|
|
|
|
if(members[i]->GetHPRatio() <= hpr)
|
|
need_healed++;
|
|
|
|
if(include_pets) {
|
|
if(members[i]->GetPet() && members[i]->GetPet()->GetHPRatio() <= hpr) {
|
|
need_healed++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return need_healed;
|
|
}
|
|
|
|
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)MainAssistName.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;
|
|
}
|
|
|
|
std::string Group::GetLeaderName() {
|
|
return database.GetGroupLeaderName(GetID());
|
|
}
|
|
|
|
void Group::RemoveFromGroup(Mob* m)
|
|
{
|
|
uint32 bot_id = 0;
|
|
uint32 character_id = 0;
|
|
uint32 merc_id = 0;
|
|
|
|
if (m->IsBot()) {
|
|
bot_id = m->CastToBot()->GetBotID();
|
|
} else if (m->IsClient()) {
|
|
character_id = m->CastToClient()->CharacterID();
|
|
} else if (m->IsMerc()) {
|
|
merc_id = m->CastToMerc()->GetMercenaryID();
|
|
}
|
|
|
|
GroupIdRepository::DeleteWhere(
|
|
database,
|
|
fmt::format(
|
|
"`character_id` = {} AND `bot_id` = {} AND `merc_id` = {}",
|
|
character_id,
|
|
bot_id,
|
|
merc_id
|
|
)
|
|
);
|
|
}
|
|
|
|
void Group::AddToGroup(Mob* m)
|
|
{
|
|
AddToGroup(
|
|
AddToGroupRequest{
|
|
.mob = m
|
|
}
|
|
);
|
|
}
|
|
|
|
// Handles database-side, should eventually be consolidated to handle memory-based group stuff as well
|
|
void Group::AddToGroup(AddToGroupRequest r)
|
|
{
|
|
uint32 bot_id = 0;
|
|
uint32 character_id = r.character_id;
|
|
uint32 merc_id = 0;
|
|
std::string name = r.member_name;
|
|
|
|
if (r.mob) {
|
|
if (r.mob->IsBot()) {
|
|
bot_id = r.mob->CastToBot()->GetBotID();
|
|
} else if (r.mob->IsClient()) {
|
|
character_id = r.mob->CastToClient()->CharacterID();
|
|
} else if (r.mob->IsMerc()) {
|
|
merc_id = r.mob->CastToMerc()->GetMercenaryID();
|
|
}
|
|
|
|
name = r.mob->GetCleanName();
|
|
}
|
|
|
|
GroupIdRepository::ReplaceOne(
|
|
database,
|
|
GroupIdRepository::GroupId{
|
|
.group_id = GetID(),
|
|
.name = name,
|
|
.character_id = character_id,
|
|
.bot_id = bot_id,
|
|
.merc_id = merc_id
|
|
}
|
|
);
|
|
}
|
|
|
|
void Group::RemoveClientsBots(Client* c) {
|
|
std::list<uint32> sbl = GetRawBotList();
|
|
|
|
for (auto botID : sbl) {
|
|
auto b = entity_list.GetBotByBotID(botID);
|
|
|
|
if (b) {
|
|
if (b->GetBotOwnerCharacterID() == c->CharacterID()) {
|
|
b->RemoveBotFromGroup(b, this);
|
|
}
|
|
}
|
|
else {
|
|
if (database.botdb.GetOwnerID(botID) == c->CharacterID()) {
|
|
auto botName = database.botdb.GetBotNameByID(botID);
|
|
|
|
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
|
if (membername[i] == botName) {
|
|
members[i] = nullptr;
|
|
membername[i][0] = '\0';
|
|
memset(membername[i], 0, 64);
|
|
MemberRoles[i] = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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, botName.c_str());
|
|
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, botName.c_str());
|
|
strcpy(gu->yourname, botName.c_str());
|
|
|
|
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 (membername[i] != botName.c_str()) {
|
|
strcpy(gu->yourname, members[i]->GetCleanName());
|
|
|
|
if (members[i]->IsClient()) {
|
|
members[i]->CastToClient()->QueuePacket(outapp);
|
|
}
|
|
}
|
|
}
|
|
|
|
safe_delete(outapp);
|
|
|
|
DelMemberOOZ(botName.c_str());
|
|
|
|
GroupIdRepository::DeleteWhere(
|
|
database,
|
|
fmt::format(
|
|
"`bot_id` = {}",
|
|
botID
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|