From 9f4604ec3ebf8df4c1c1059a6a5865c576d77324 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 17 Feb 2017 21:04:48 -0500 Subject: [PATCH 1/4] Rework how XTarget auto haters work This should cause the auto haters to be shared with other toons who might be interested (group/raid) like live. There maybe some bugs since there is a lot of complex interactions here. --- zone/CMakeLists.txt | 2 + zone/attack.cpp | 6 +- zone/client.cpp | 194 +++++++++++++++++++++++++++---------- zone/client.h | 11 +++ zone/client_packet.cpp | 24 +++-- zone/client_process.cpp | 6 ++ zone/entity.cpp | 10 +- zone/groups.cpp | 38 +++++++- zone/groups.h | 8 +- zone/raids.cpp | 36 ++++++- zone/raids.h | 6 ++ zone/worldserver.cpp | 5 + zone/xtargetautohaters.cpp | 112 +++++++++++++++++++++ zone/xtargetautohaters.h | 48 +++++++++ 14 files changed, 435 insertions(+), 71 deletions(-) create mode 100644 zone/xtargetautohaters.cpp create mode 100644 zone/xtargetautohaters.h diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 532cc86cd..7bc96c3db 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -122,6 +122,7 @@ SET(zone_sources water_map_v2.cpp waypoints.cpp worldserver.cpp + xtargetautohaters.cpp zone.cpp zone_config.cpp zonedb.cpp @@ -215,6 +216,7 @@ SET(zone_headers water_map_v1.h water_map_v2.h worldserver.h + xtargetautohaters.h zone.h zone_config.h zonedb.h diff --git a/zone/attack.cpp b/zone/attack.cpp index 826ae713b..b7cd49384 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2432,9 +2432,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b Mob* mypet = this->GetPet(); Mob* myowner = this->GetOwner(); Mob* targetmob = this->GetTarget(); + bool on_hatelist = CheckAggro(other); if(other){ - bool on_hatelist = CheckAggro(other); AddRampage(other); if (on_hatelist) { // odd reason, if you're not on the hate list, subtlety etc don't apply! // Spell Casting Subtlety etc @@ -2510,7 +2510,7 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b hate_list.AddEntToHateList(other, hate, damage, bFrenzy, !iBuffTic); - if(other->IsClient()) + if(other->IsClient() && !on_hatelist) other->CastToClient()->AddAutoXTarget(this); #ifdef BOTS @@ -2549,7 +2549,7 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b if(!owner->GetSpecialAbility(IMMUNE_AGGRO)) { hate_list.AddEntToHateList(owner, 0, 0, false, !iBuffTic); - if(owner->IsClient()) + if(owner->IsClient() && !CheckAggro(owner)) owner->CastToClient()->AddAutoXTarget(this); } } diff --git a/zone/client.cpp b/zone/client.cpp index 1a5c02f1e..a3239eeb1 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -310,6 +310,8 @@ Client::Client(EQStreamInterface* ieqs) } MaxXTargets = 5; XTargetAutoAddHaters = true; + m_autohatermgr.SetOwner(this, nullptr, nullptr); + m_activeautohatermgr = &m_autohatermgr; LoadAccountFlags(); initial_respawn_selection = 0; @@ -4159,6 +4161,18 @@ bool Client::GroupFollow(Client* inviter) { } if (raid->RaidCount() < MAX_RAID_MEMBERS) { + // okay, so we now have a single client (this) joining a group in a raid + // And they're not already in the raid (which is above and doesn't need xtarget shit) + if (!GetXTargetAutoMgr()->empty()) { + raid->GetXTargetAutoMgr()->merge(*GetXTargetAutoMgr()); + GetXTargetAutoMgr()->clear(); + RemoveAutoXTargets(); + } + + SetXTargetAutoMgr(GetXTargetAutoMgr()); + if (!GetXTargetAutoMgr()->empty()) + SetDirtyAutoHaters(); + if (raid->GroupCount(groupToUse) < 6) { raid->SendRaidCreate(this); @@ -4234,7 +4248,9 @@ bool Client::GroupFollow(Client* inviter) { inviter->SendGroupLeaderChangePacket(inviter->GetName()); inviter->SendGroupJoinAcknowledge(); } - + group->GetXTargetAutoMgr()->merge(*inviter->GetXTargetAutoMgr()); + inviter->GetXTargetAutoMgr()->clear(); + inviter->SetXTargetAutoMgr(group->GetXTargetAutoMgr()); } if (!group) @@ -7171,12 +7187,12 @@ void Client::UpdateClientXTarget(Client *c) } } +// IT IS NOT SAFE TO CALL THIS IF IT'S NOT INITIAL AGGRO void Client::AddAutoXTarget(Mob *m, bool send) { - if(!XTargettingAvailable() || !XTargetAutoAddHaters) - return; + m_activeautohatermgr->increment_count(m); - if(IsXTarget(m)) + if (!XTargettingAvailable() || !XTargetAutoAddHaters || IsXTarget(m)) return; for(int i = 0; i < GetMaxXTargets(); ++i) @@ -7195,60 +7211,15 @@ void Client::AddAutoXTarget(Mob *m, bool send) void Client::RemoveXTarget(Mob *m, bool OnlyAutoSlots) { - if (!XTargettingAvailable()) - return; - - bool HadFreeAutoSlotsBefore = false; - - int FreedAutoSlots = 0; - - if (m->GetID() == 0) - return; - + m_activeautohatermgr->decrement_count(m); + // now we may need to clean up our CurrentTargetNPC entries for (int i = 0; i < GetMaxXTargets(); ++i) { - if (OnlyAutoSlots && XTargets[i].Type != Auto) - continue; - - if (XTargets[i].ID == m->GetID()) { - if (XTargets[i].Type == CurrentTargetNPC) - XTargets[i].Type = Auto; - - if (XTargets[i].Type == Auto) - ++FreedAutoSlots; - + if (XTargets[i].Type == CurrentTargetNPC && XTargets[i].ID == m->GetID()) { + XTargets[i].Type = Auto; XTargets[i].ID = 0; XTargets[i].dirty = true; - } else { - if (XTargets[i].Type == Auto && XTargets[i].ID == 0) - HadFreeAutoSlotsBefore = true; } } - - // move shit up! If the removed NPC was in a CurrentTargetNPC slot it becomes Auto - // and we need to potentially fill it - std::queue empty_slots; - for (int i = 0; i < GetMaxXTargets(); ++i) { - if (XTargets[i].Type != Auto) - continue; - - if (XTargets[i].ID == 0) { - empty_slots.push(i); - continue; - } - - if (XTargets[i].ID != 0 && !empty_slots.empty()) { - int temp = empty_slots.front(); - std::swap(XTargets[i], XTargets[temp]); - XTargets[i].dirty = XTargets[temp].dirty = true; - empty_slots.pop(); - empty_slots.push(i); - } - } - // If there are more mobs aggro on us than we had auto-hate slots, add one of those haters into the slot(s) we - // just freed up. - if (!HadFreeAutoSlotsBefore && FreedAutoSlots) - entity_list.RefreshAutoXTargets(this); - SendXTargetUpdates(); } void Client::UpdateXTargetType(XTargetType Type, Mob *m, const char *Name) @@ -7405,6 +7376,123 @@ void Client::ShowXTargets(Client *c) for(int i = 0; i < GetMaxXTargets(); ++i) c->Message(0, "Xtarget Slot: %i, Type: %2i, ID: %4i, Name: %s", i, XTargets[i].Type, XTargets[i].ID, XTargets[i].Name); + auto &list = GetXTargetAutoMgr()->get_list(); + // yeah, I kept having to do something for debugging to tell if managers were the same object or not :P + // so lets use the address as an "ID" + c->Message(0, "XTargetAutoMgr ID %p size %d", GetXTargetAutoMgr(), list.size()); + int count = 0; + for (auto &e : list) { + c->Message(0, "spawn id %d count %d", e.spawn_id, e.count); + count++; + if (count == 20) { // lets not spam too many ... + c->Message(0, " ... "); + break; + } + } +} + +void Client::ProcessXTargetAutoHaters() +{ + if (!XTargettingAvailable()) + return; + + // move shit up! If the removed NPC was in a CurrentTargetNPC slot it becomes Auto + // and we need to potentially fill it + std::queue empty_slots; + for (int i = 0; i < GetMaxXTargets(); ++i) { + if (XTargets[i].Type != Auto) + continue; + + if (XTargets[i].ID != 0 && !GetXTargetAutoMgr()->contains_mob(XTargets[i].ID)) { + XTargets[i].ID = 0; + XTargets[i].dirty = true; + } + + if (XTargets[i].ID == 0) { + empty_slots.push(i); + continue; + } + + if (XTargets[i].ID != 0 && !empty_slots.empty()) { + int temp = empty_slots.front(); + std::swap(XTargets[i], XTargets[temp]); + XTargets[i].dirty = XTargets[temp].dirty = true; + empty_slots.pop(); + empty_slots.push(i); + } + } + // okay, now we need to check if we have any empty slots and if we have aggro + // We make the assumption that if we shuffled the NPCs up that they're still on the aggro + // list in the same order. We could probably do this better and try to calc if + // there are new NPCs for our empty slots on the manager, but ahhh fuck it. + if (!empty_slots.empty() && !GetXTargetAutoMgr()->empty() && XTargetAutoAddHaters) { + auto &haters = GetXTargetAutoMgr()->get_list(); + for (auto &e : haters) { + auto *mob = entity_list.GetMob(e.spawn_id); + if (!IsXTarget(mob)) { + auto slot = empty_slots.front(); + empty_slots.pop(); + XTargets[slot].dirty = true; + XTargets[slot].ID = mob->GetID(); + strn0cpy(XTargets[slot].Name, mob->GetCleanName(), 64); + } + if (empty_slots.empty()) + break; + } + } + m_dirtyautohaters = false; + SendXTargetUpdates(); +} + +// This function is called when a client is added to a group +// Group leader joining isn't handled by this function +void Client::JoinGroupXTargets(Group *g) +{ + if (!g) + return; + + if (!GetXTargetAutoMgr()->empty()) { + g->GetXTargetAutoMgr()->merge(*GetXTargetAutoMgr()); + GetXTargetAutoMgr()->clear(); + RemoveAutoXTargets(); + } + + SetXTargetAutoMgr(g->GetXTargetAutoMgr()); + + if (!GetXTargetAutoMgr()->empty()) + SetDirtyAutoHaters(); +} + +// This function is called when a client leaves a group +void Client::LeaveGroupXTargets(Group *g) +{ + if (!g) + return; + + SetXTargetAutoMgr(nullptr); // this will set it back to our manager + RemoveAutoXTargets(); + entity_list.RefreshAutoXTargets(this); // this will probably break the temporal ordering, but whatever + // We now have a rebuilt, valid auto hater manager, so we need to demerge from the groups + if (!GetXTargetAutoMgr()->empty()) { + GetXTargetAutoMgr()->demerge(*g->GetXTargetAutoMgr()); // this will remove entries where we only had aggro + SetDirtyAutoHaters(); + } +} + +// This function is called when a client leaves a group +void Client::LeaveRaidXTargets(Raid *r) +{ + if (!r) + return; + + SetXTargetAutoMgr(nullptr); // this will set it back to our manager + RemoveAutoXTargets(); + entity_list.RefreshAutoXTargets(this); // this will probably break the temporal ordering, but whatever + // We now have a rebuilt, valid auto hater manager, so we need to demerge from the groups + if (!GetXTargetAutoMgr()->empty()) { + GetXTargetAutoMgr()->demerge(*r->GetXTargetAutoMgr()); // this will remove entries where we only had aggro + SetDirtyAutoHaters(); + } } void Client::SetMaxXTargets(uint8 NewMax) diff --git a/zone/client.h b/zone/client.h index 69fdddffa..ae6c2ef30 100644 --- a/zone/client.h +++ b/zone/client.h @@ -48,6 +48,7 @@ namespace EQEmu #include "../common/inventory_profile.h" #include "../common/guilds.h" //#include "../common/item_data.h" +#include "xtargetautohaters.h" #include "common.h" #include "merc.h" @@ -1122,6 +1123,13 @@ public: void RemoveGroupXTargets(); void RemoveAutoXTargets(); void ShowXTargets(Client *c); + inline XTargetAutoHaters *GetXTargetAutoMgr() { return m_activeautohatermgr; } // will be either raid or group or self + inline void SetXTargetAutoMgr(XTargetAutoHaters *in) { if (in) m_activeautohatermgr = in; else m_activeautohatermgr = &m_autohatermgr; } + inline void SetDirtyAutoHaters() { m_dirtyautohaters = true; } + void ProcessXTargetAutoHaters(); // fixes up our auto haters + void JoinGroupXTargets(Group *g); + void LeaveGroupXTargets(Group *g); + void LeaveRaidXTargets(Raid *r); bool GroupFollow(Client* inviter); inline bool GetRunMode() const { return runmode; } @@ -1546,8 +1554,11 @@ private: uint8 MaxXTargets; bool XTargetAutoAddHaters; + bool m_dirtyautohaters; struct XTarget_Struct XTargets[XTARGET_HARDCAP]; + XTargetAutoHaters m_autohatermgr; + XTargetAutoHaters *m_activeautohatermgr; Timer ItemTickTimer; Timer ItemQuestTimer; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index f2e78adf7..721421b9e 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -575,6 +575,11 @@ void Client::CompleteConnect() } } raid->SendGroupLeadershipAA(this, grpID); // this may get sent an extra time ... + + SetXTargetAutoMgr(raid->GetXTargetAutoMgr()); + if (!GetXTargetAutoMgr()->empty()) + SetDirtyAutoHaters(); + if (raid->IsLocked()) raid->SendRaidLockTo(this); } @@ -1548,8 +1553,8 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) // we purchased a new one while out-of-zone. if (group->IsLeader(this)) group->SendLeadershipAAUpdate(); - } + JoinGroupXTargets(group); group->UpdatePlayer(this); LFG = false; } @@ -10806,7 +10811,8 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) } } } - g->DisbandGroup(); + g->JoinRaidXTarget(r); + g->DisbandGroup(true); r->GroupUpdate(freeGroup); } else{ @@ -10871,7 +10877,8 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) } } } - ig->DisbandGroup(); + ig->JoinRaidXTarget(r, true); + ig->DisbandGroup(true); r->GroupUpdate(groupFree); groupFree = r->GetFreeGroup(); } @@ -10924,10 +10931,11 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) } } } - g->DisbandGroup(); + g->JoinRaidXTarget(r); + g->DisbandGroup(true); r->GroupUpdate(groupFree); } - else + else // target does not have a group { if (ig){ r = new Raid(i); @@ -10981,14 +10989,15 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) r->SendRaidCreate(this); r->SendMakeLeaderPacketTo(r->leadername, this); r->SendBulkRaid(this); + ig->JoinRaidXTarget(r, true); r->AddMember(this); - ig->DisbandGroup(); + ig->DisbandGroup(true); r->GroupUpdate(0); if (r->IsLocked()) { r->SendRaidLockTo(this); } } - else{ + else{ // neither has a group r = new Raid(i); entity_list.AddRaid(r); r->SetRaidDetails(); @@ -14087,6 +14096,7 @@ void Client::Handle_OP_XTargetAutoAddHaters(const EQApplicationPacket *app) } XTargetAutoAddHaters = app->ReadUInt8(0); + SetDirtyAutoHaters(); } void Client::Handle_OP_XTargetOpen(const EQApplicationPacket *app) diff --git a/zone/client_process.cpp b/zone/client_process.cpp index bb2db4777..b97f7b22e 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -681,6 +681,12 @@ bool Client::Process() { Message(0, "Your enemies have forgotten you!"); } + if (client_state == CLIENT_CONNECTED) { + if (m_dirtyautohaters) + ProcessXTargetAutoHaters(); + // aggro meter stuff should live here + } + return ret; } diff --git a/zone/entity.cpp b/zone/entity.cpp index 2ff83980e..9f23f63a4 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -1404,15 +1404,15 @@ void EntityList::RemoveFromTargets(Mob *mob, bool RemoveFromXTargets) if (!m) continue; - m->RemoveFromHateList(mob); - if (RemoveFromXTargets) { - if (m->IsClient()) + if (m->IsClient() && mob->CheckAggro(m)) m->CastToClient()->RemoveXTarget(mob, false); // FadingMemories calls this function passing the client. - else if (mob->IsClient()) + else if (mob->IsClient() && m->CheckAggro(mob)) mob->CastToClient()->RemoveXTarget(m, false); } + + m->RemoveFromHateList(mob); } } @@ -2557,6 +2557,8 @@ void EntityList::RemoveFromHateLists(Mob *mob, bool settoone) it->second->RemoveFromHateList(mob); else it->second->SetHateAmountOnEnt(mob, 1); + if (mob->IsClient()) + mob->CastToClient()->RemoveXTarget(it->second, false); // gotta do book keeping } ++it; } diff --git a/zone/groups.cpp b/zone/groups.cpp index 2162e735e..24d31e74c 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -64,6 +64,8 @@ Group::Group(uint32 gid) MarkedNPCs[i] = 0; NPCMarkerID = 0; + + m_autohatermgr.SetOwner(nullptr, this, nullptr); } //creating a new group @@ -94,6 +96,7 @@ Group::Group(Mob* leader) MarkedNPCs[i] = 0; NPCMarkerID = 0; + m_autohatermgr.SetOwner(nullptr, this, nullptr); } Group::~Group() @@ -346,6 +349,9 @@ bool Group::AddMember(Mob* newmember, const char *NewMemberName, uint32 Characte database.SetGroupID(NewMemberName, GetID(), CharacterID, ismerc); } + if (newmember && newmember->IsClient()) + newmember->CastToClient()->JoinGroupXTargets(this); + safe_delete(outapp); return true; @@ -718,8 +724,10 @@ bool Group::DelMember(Mob* oldmember, bool ignoresender) if (oldmember->GetName() == mentoree_name) ClearGroupMentor(); - if(oldmember->IsClient()) + if(oldmember->IsClient()) { SendMarkedNPCsToMember(oldmember->CastToClient(), true); + oldmember->CastToClient()->LeaveGroupXTargets(this); + } if(GroupCount() < 3) { @@ -872,7 +880,7 @@ uint32 Group::GetTotalGroupDamage(Mob* other) { return total; } -void Group::DisbandGroup() { +void Group::DisbandGroup(bool joinraid) { auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupUpdate_Struct)); GroupUpdate_Struct* gu = (GroupUpdate_Struct*) outapp->pBuffer; @@ -899,6 +907,8 @@ void Group::DisbandGroup() { database.SetGroupID(members[i]->GetCleanName(), 0, members[i]->CastToClient()->CharacterID(), false); members[i]->CastToClient()->QueuePacket(outapp); SendMarkedNPCsToMember(members[i]->CastToClient(), true); + if (!joinraid) + members[i]->CastToClient()->LeaveGroupXTargets(this); } if (members[i]->IsMerc()) @@ -2292,6 +2302,30 @@ void Group::UpdateXTargetMarkedNPC(uint32 Number, Mob *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; diff --git a/zone/groups.h b/zone/groups.h index 3da2aae54..6de364d93 100644 --- a/zone/groups.h +++ b/zone/groups.h @@ -22,6 +22,7 @@ #include "../common/types.h" #include "mob.h" +#include "xtargetautohaters.h" class Client; class EQApplicationPacket; @@ -58,7 +59,7 @@ public: void SendWorldGroup(uint32 zone_id,Mob* zoningmember); bool DelMemberOOZ(const char *Name); bool DelMember(Mob* oldmember,bool ignoresender = false); - void DisbandGroup(); + void DisbandGroup(bool joinraid = false); void GetMemberList(std::list& member_list, bool clear_list = true); void GetClientList(std::list& client_list, bool clear_list = true); #ifdef BOTS @@ -140,6 +141,9 @@ public: void ChangeLeader(Mob* newleader); const char *GetClientNameByIndex(uint8 index); void UpdateXTargetMarkedNPC(uint32 Number, Mob *m); + void SetDirtyAutoHaters(); + inline XTargetAutoHaters *GetXTargetAutoMgr() { return &m_autohatermgr; } + void JoinRaidXTarget(Raid *raid, bool first = false); void SetGroupMentor(int percent, char *name); void ClearGroupMentor(); @@ -168,6 +172,8 @@ private: std::string mentoree_name; Client *mentoree; int mentor_percent; + + XTargetAutoHaters m_autohatermgr; }; #endif diff --git a/zone/raids.cpp b/zone/raids.cpp index ff9e84578..46dd4424e 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -43,6 +43,8 @@ Raid::Raid(uint32 raidID) memset(leadername, 0, 64); locked = false; LootType = 4; + + m_autohatermgr.SetOwner(nullptr, nullptr, this); } Raid::Raid(Client* nLeader) @@ -60,6 +62,8 @@ Raid::Raid(Client* nLeader) strn0cpy(leadername, nLeader->GetName(), 64); locked = false; LootType = 4; + + m_autohatermgr.SetOwner(nullptr, nullptr, this); } Raid::~Raid() @@ -121,6 +125,26 @@ void Raid::AddMember(Client *c, uint32 group, bool rleader, bool groupleader, bo c->SetRaidGrouped(true); SendRaidMOTD(c); + // xtarget shit .......... + if (group == RAID_GROUPLESS) { + if (rleader) { + GetXTargetAutoMgr()->merge(*c->GetXTargetAutoMgr()); + c->GetXTargetAutoMgr()->clear(); + c->SetXTargetAutoMgr(GetXTargetAutoMgr()); + } else { + if (!c->GetXTargetAutoMgr()->empty()) { + GetXTargetAutoMgr()->merge(*c->GetXTargetAutoMgr()); + c->GetXTargetAutoMgr()->clear(); + c->RemoveAutoXTargets(); + } + + c->SetXTargetAutoMgr(GetXTargetAutoMgr()); + + if (!c->GetXTargetAutoMgr()->empty()) + c->SetDirtyAutoHaters(); + } + } + auto pack = new ServerPacket(ServerOP_RaidAdd, sizeof(ServerRaidGeneralAction_Struct)); ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; rga->rid = GetID(); @@ -143,8 +167,10 @@ void Raid::RemoveMember(const char *characterName) LearnMembers(); VerifyRaid(); - if(client) + if(client) { client->SetRaidGrouped(false); + client->LeaveRaidXTargets(this); + } auto pack = new ServerPacket(ServerOP_RaidRemove, sizeof(ServerRaidGeneralAction_Struct)); ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; @@ -1672,3 +1698,11 @@ void Raid::CheckGroupMentor(uint32 group_id, Client *c) group_mentor[group_id].mentoree = c; } +void Raid::SetDirtyAutoHaters() +{ + for (int i = 0; i < MAX_RAID_MEMBERS; ++i) + if (members[i].member) + members[i].member->SetDirtyAutoHaters(); + +} + diff --git a/zone/raids.h b/zone/raids.h index ca5d0ff9b..9e681153c 100644 --- a/zone/raids.h +++ b/zone/raids.h @@ -20,6 +20,7 @@ #include "../common/types.h" #include "groups.h" +#include "xtargetautohaters.h" class Client; class EQApplicationPacket; @@ -230,6 +231,9 @@ public: inline int GetMentorPercent(uint32 group_id) { return group_mentor[group_id].mentor_percent; } inline Client *GetMentoree(uint32 group_id) { return group_mentor[group_id].mentoree; } + void SetDirtyAutoHaters(); + inline XTargetAutoHaters *GetXTargetAutoMgr() { return &m_autohatermgr; } + RaidMember members[MAX_RAID_MEMBERS]; char leadername[64]; protected: @@ -244,6 +248,8 @@ protected: GroupLeadershipAA_Struct group_aa[MAX_RAID_GROUPS]; GroupMentor group_mentor[MAX_RAID_GROUPS]; + + XTargetAutoHaters m_autohatermgr; }; diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 84d4cb399..3251250ce 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -897,6 +897,9 @@ void WorldServer::Process() { Inviter->CastToClient()->SendGroupLeaderChangePacket(Inviter->GetName()); Inviter->CastToClient()->SendGroupJoinAcknowledge(); } + group->GetXTargetAutoMgr()->merge(*Inviter->CastToClient()->GetXTargetAutoMgr()); + Inviter->CastToClient()->GetXTargetAutoMgr()->clear(); + Inviter->CastToClient()->SetXTargetAutoMgr(group->GetXTargetAutoMgr()); } if(!group) @@ -1011,6 +1014,7 @@ void WorldServer::Process() { group->SetGroupMentor(mentor_percent, mentoree_name); } + client->JoinGroupXTargets(group); } else if (client->GetMerc()) { @@ -1109,6 +1113,7 @@ void WorldServer::Process() { r->SendRaidRemoveAll(rga->playername); Client *rem = entity_list.GetClientByName(rga->playername); if(rem){ + rem->LeaveRaidXTargets(r); r->SendRaidDisband(rem); } r->LearnMembers(); diff --git a/zone/xtargetautohaters.cpp b/zone/xtargetautohaters.cpp new file mode 100644 index 000000000..ab010d9db --- /dev/null +++ b/zone/xtargetautohaters.cpp @@ -0,0 +1,112 @@ +#include "xtargetautohaters.h" +#include "mob.h" +#include "client.h" +#include "raids.h" +#include "groups.h" + +#include + +void XTargetAutoHaters::increment_count(Mob *in) +{ + assert(in != nullptr); + auto it = std::find_if(m_haters.begin(), m_haters.end(), + [&in](const HatersCount &c) { return c.spawn_id == in->GetID(); }); + + // we are on the list, we just need to increment the count + if (it != m_haters.end()) { + it->count++; + return; + } + // We are not on the list + HatersCount c; + c.spawn_id = in->GetID(); + c.count = 1; + + m_haters.push_back(c); + // trigger event on owner + if (m_client) + m_client->SetDirtyAutoHaters(); + else if (m_group) + m_group->SetDirtyAutoHaters(); + else if (m_raid) + m_raid->SetDirtyAutoHaters(); +} + +void XTargetAutoHaters::decrement_count(Mob *in) +{ + assert(in != nullptr); + auto it = std::find_if(m_haters.begin(), m_haters.end(), + [&in](const HatersCount &c) { return c.spawn_id == in->GetID(); }); + + // we are not on the list ... shouldn't happen + if (it == m_haters.end()) + return; + it->count--; + if (it->count == 0) { + m_haters.erase(it); + if (m_client) + m_client->SetDirtyAutoHaters(); + else if (m_group) + m_group->SetDirtyAutoHaters(); + else if (m_raid) + m_raid->SetDirtyAutoHaters(); + } +} + +void XTargetAutoHaters::merge(XTargetAutoHaters &other) +{ + bool trigger = false; + for (auto &e : other.m_haters) { + auto it = std::find_if(m_haters.begin(), m_haters.end(), + [&e](const HatersCount &c) { return e.spawn_id == c.spawn_id; }); + if (it != m_haters.end()) { + it->count += e.count; + continue; + } + m_haters.push_back(e); + trigger = true; + } + + if (trigger) { + if (m_client) + m_client->SetDirtyAutoHaters(); + else if (m_group) + m_group->SetDirtyAutoHaters(); + else if (m_raid) + m_raid->SetDirtyAutoHaters(); + } +} + +// demerge this from other. other belongs to group/raid you just left +void XTargetAutoHaters::demerge(XTargetAutoHaters &other) +{ + bool trigger = false; + for (auto &e : m_haters) { + auto it = std::find_if(other.m_haters.begin(), other.m_haters.end(), + [&e](const HatersCount &c) { return e.spawn_id == c.spawn_id; }); + if (it != other.m_haters.end()) { + it->count -= e.count; + if (it->count == 0) { + trigger = true; + other.m_haters.erase(it); + } + } + } + + if (trigger) { + if (other.m_client) + other.m_client->SetDirtyAutoHaters(); + else if (other.m_group) + other.m_group->SetDirtyAutoHaters(); + else if (other.m_raid) + other.m_raid->SetDirtyAutoHaters(); + } +} + +bool XTargetAutoHaters::contains_mob(int spawn_id) +{ + auto it = std::find_if(m_haters.begin(), m_haters.end(), + [spawn_id](const HatersCount &c) { return c.spawn_id == spawn_id; }); + return it != m_haters.end(); +} + diff --git a/zone/xtargetautohaters.h b/zone/xtargetautohaters.h new file mode 100644 index 000000000..fd8ee4f21 --- /dev/null +++ b/zone/xtargetautohaters.h @@ -0,0 +1,48 @@ +#ifndef XTARGETAUTOHATERS_H +#define XTARGETAUTOHATERS_H + +#include + +class Mob; +class Client; +class Group; +class Raid; + +class XTargetAutoHaters { +struct HatersCount { + int spawn_id; + int count; +}; +public: + XTargetAutoHaters() : m_client(nullptr), m_group(nullptr), m_raid(nullptr) {} + XTargetAutoHaters(Client *co, Group *go, Raid *ro) : m_client(co), m_group(go), m_raid(ro) {} + ~XTargetAutoHaters() {} + + void merge(XTargetAutoHaters &other); + void demerge(XTargetAutoHaters &other); + void increment_count(Mob *in); + void decrement_count(Mob *in); + + bool contains_mob(int spawn_id); + + inline const std::vector &get_list() { return m_haters; } + inline void SetOwner(Client *c, Group *g, Raid *r) {m_client = c; m_group = g; m_raid = r; } + inline void clear() { m_haters.clear(); } + inline bool empty() { return m_haters.empty(); } + +private: + /* This will contain all of the mobs that are possible to fill in an autohater + * slot. This keeps track of ALL MOBS for a client or group or raid + * This list needs to be merged when you join group/raid/etc + */ + std::vector m_haters; + + // So this is the object that owns us ... only 1 shouldn't be null + Client *m_client; + Group *m_group; + Raid *m_raid; +}; + + +#endif /* !XTARGETAUTOHATERS_H */ + From 08c2f73e379066d4a2486c223282ba6daf72e6cb Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 18 Feb 2017 22:27:34 -0500 Subject: [PATCH 2/4] Implement aggro meter for RoF2 (RoF wasn't tested) I didn't test RoF, so it's disabled for now (change AggroMeterAvaliable if you want to test) Group member meters probably buggy ... but do later The "lock target" feature isn't working currently either --- common/emu_oplist.h | 3 + common/ruletypes.h | 1 + utils/patches/patch_RoF.conf | 3 + utils/patches/patch_RoF2.conf | 3 + zone/CMakeLists.txt | 2 + zone/aggromanager.cpp | 11 +++ zone/aggromanager.h | 80 +++++++++++++++++ zone/client.cpp | 165 ++++++++++++++++++++++++++++++++++ zone/client.h | 9 ++ zone/client_packet.cpp | 12 +++ zone/client_packet.h | 1 + zone/client_process.cpp | 3 +- zone/hate_list.cpp | 30 ++++++- zone/hate_list.h | 8 +- zone/mob.h | 2 + 15 files changed, 328 insertions(+), 5 deletions(-) create mode 100644 zone/aggromanager.cpp create mode 100644 zone/aggromanager.h diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 6b7c58841..cf6f4ca28 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -25,6 +25,9 @@ N(OP_AdventureRequest), N(OP_AdventureStatsReply), N(OP_AdventureStatsRequest), N(OP_AdventureUpdate), +N(OP_AggroMeterLockTarget), +N(OP_AggroMeterTargetInfo), +N(OP_AggroMeterUpdate), N(OP_AltCurrency), N(OP_AltCurrencyMerchantReply), N(OP_AltCurrencyMerchantRequest), diff --git a/common/ruletypes.h b/common/ruletypes.h index f972034f0..a0bf01496 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -114,6 +114,7 @@ RULE_BOOL(Character, CheckCursorEmptyWhenLooting, true) // If true, a player can RULE_BOOL(Character, MaintainIntoxicationAcrossZones, true) // If true, alcohol effects are maintained across zoning and logging out/in. RULE_BOOL(Character, EnableDiscoveredItems, true) // If enabled, it enables EVENT_DISCOVER_ITEM and also saves character names and timestamps for the first time an item is discovered. RULE_BOOL(Character, EnableXTargetting, true) // Enable Extended Targetting Window, for users with UF and later clients. +RULE_BOOL(Character, EnableAggroMeter, true) // Enable Aggro Meter, for users with RoF and later clients. RULE_BOOL(Character, KeepLevelOverMax, false) // Don't delevel a character that has somehow gone over the level cap RULE_INT(Character, FoodLossPerUpdate, 35) // How much food/water you lose per stamina update RULE_INT(Character, BaseInstrumentSoftCap, 36) // Softcap for instrument mods, 36 commonly referred to as "3.6" as well. diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index aefcd8dbc..ad54b6341 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -359,6 +359,9 @@ OP_OpenContainer=0x654f OP_Marquee=0x288a OP_Fling=0x6b8e OP_CancelSneakHide=0x265f +OP_AggroMeterLockTarget=0x70b7 +OP_AggroMeterTargetInfo=0x18fe +OP_AggroMeterUpdate=0x75aa OP_DzQuit=0x5fc8 OP_DzListTimers=0x67b9 diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 20111e850..9f588d18f 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -360,6 +360,9 @@ OP_ItemRecastDelay=0x15a9 OP_ResetAA=0x1669 OP_Fling=0x6f80 OP_CancelSneakHide=0x0927 +OP_AggroMeterLockTarget=0x1643 +OP_AggroMeterTargetInfo=0x16bc +OP_AggroMeterUpdate=0x1781 # Expeditions OP_DzAddPlayer=0x4701 diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 7bc96c3db..deadd079e 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -4,6 +4,7 @@ SET(zone_sources aa.cpp aa_ability.cpp aggro.cpp + aggromanager.cpp attack.cpp beacon.cpp bonuses.cpp @@ -132,6 +133,7 @@ SET(zone_sources SET(zone_headers aa.h aa_ability.h + aggromanager.h basic_functions.h beacon.h bot.h diff --git a/zone/aggromanager.cpp b/zone/aggromanager.cpp new file mode 100644 index 000000000..8dd532c74 --- /dev/null +++ b/zone/aggromanager.cpp @@ -0,0 +1,11 @@ +#include "aggromanager.h" + +AggroMeter::AggroMeter() : lock_id(0), target_id(0), secondary_id(0), lock_changed(false) +{ + for (int i = 0; i < AT_Max; ++i) { + data[i].type = i; + data[i].pct = 0; + } +} + + diff --git a/zone/aggromanager.h b/zone/aggromanager.h new file mode 100644 index 000000000..b140b07d7 --- /dev/null +++ b/zone/aggromanager.h @@ -0,0 +1,80 @@ +#ifndef AGGROMANAGER_H +#define AGGROMANAGER_H + +#include "../common/types.h" +#include +#include + +class AggroMeter +{ +public: + enum AggroTypes { + AT_Player, + AT_Secondary, + AT_Group1, + AT_Group2, + AT_Group3, + AT_Group4, + AT_Group5, + AT_XTarget1, + AT_XTarget2, + AT_XTarget3, + AT_XTarget4, + AT_XTarget5, + AT_XTarget6, + AT_XTarget7, + AT_XTarget8, + AT_XTarget9, + AT_XTarget10, + AT_XTarget11, + AT_XTarget12, + AT_XTarget13, + AT_XTarget14, + AT_XTarget15, + AT_XTarget16, + AT_XTarget17, + AT_XTarget18, + AT_XTarget19, + AT_XTarget20, + AT_Max + }; + +private: + struct AggroData { + int16 type; + int16 pct; + }; + + AggroData data[AT_Max]; + int lock_id; // we set this + int target_id; // current target or if PC targeted, their Target + // so secondary depends on if we have aggro or not + // When we are the current target, this will be the 2nd person on list + // When we are not tanking, this will be the current tank + int secondary_id; + + // so we need some easy way to detect the client changing but still delaying the packet + bool lock_changed; +public: + AggroMeter(); + ~AggroMeter() {} + + inline void set_lock_id(int in) { lock_id = in; lock_changed = true; } + inline bool update_lock() { bool ret = lock_changed; lock_changed = false; return ret; } + inline void set_target_id(int in) { target_id = in; } + inline void set_secondary_id(int in) { secondary_id = in; } + // returns true when changed + inline bool set_pct(AggroTypes t, int pct) { assert(t >= AT_Player && t < AT_Max); if (data[t].pct == pct) return false; data[t].pct = pct; return true; } + + inline int get_lock_id() const { return lock_id; } + inline int get_target_id() const { return target_id; } + inline int get_secondary_id() const { return secondary_id; } + inline int get_pct(AggroTypes t) const { assert(t >= AT_Player && t < AT_Max); return data[t].pct; } + // the ID of the spawn for player entry depends on lock_id + inline int get_player_aggro_id() const { return lock_id ? lock_id : target_id; } + // fuck it, lets just use a buffer the size of the largest to work with + const inline size_t max_packet_size() const { return sizeof(uint8) + sizeof(uint32) + sizeof(uint8) + (sizeof(uint8) + sizeof(uint16)) * AT_Max; } +}; + + +#endif /* !AGGROMANAGER_H */ diff --git a/zone/client.cpp b/zone/client.cpp index a3239eeb1..9e02195c0 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -154,6 +154,7 @@ Client::Client(EQStreamInterface* ieqs) afk_toggle_timer(250), helm_toggle_timer(250), light_update_timer(600), + aggro_meter_timer(1000), m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number m_ZoneSummonLocation(-2.0f,-2.0f,-2.0f), m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), @@ -8771,3 +8772,167 @@ void Client::CheckRegionTypeChanges() else if (GetPVP()) SetPVP(false, false); } + +void Client::ProcessAggroMeter() +{ + if (!AggroMeterAvailable()) + return; + + // we need to decide if we need to send OP_AggroMeterTargetInfo now + // This packet sends the current lock target ID and the current target ID + // target ID will be either our target or our target of target when we're targeting a PC + bool send_targetinfo = false; + auto cur_tar = GetTarget(); + + // probably should have PVP rules ... + if (cur_tar && cur_tar != this) { + if (cur_tar->IsNPC() && !cur_tar->IsPetOwnerClient() && cur_tar->GetID() != m_aggrometer.get_target_id()) { + m_aggrometer.set_target_id(cur_tar->GetID()); + send_targetinfo = true; + } else if ((cur_tar->IsPetOwnerClient() || cur_tar->IsClient()) && cur_tar->GetTarget() && cur_tar->GetTarget()->GetID() != m_aggrometer.get_target_id()) { + m_aggrometer.set_target_id(cur_tar->GetTarget()->GetID()); + send_targetinfo = true; + } + } else if (m_aggrometer.get_target_id()) { + m_aggrometer.set_target_id(0); + send_targetinfo = true; + } + + if (m_aggrometer.update_lock()) + send_targetinfo = true; + + if (send_targetinfo) { + auto app = new EQApplicationPacket(OP_AggroMeterTargetInfo, sizeof(uint32) * 2); + app->WriteUInt32(m_aggrometer.get_lock_id()); + app->WriteUInt32(m_aggrometer.get_target_id()); + FastQueuePacket(&app); + } + + // we could just calculate how big the packet would need to be ... but it's easier this way :P should be 87 bytes + auto app = new EQApplicationPacket(OP_AggroMeterUpdate, m_aggrometer.max_packet_size()); + + cur_tar = entity_list.GetMob(m_aggrometer.get_target_id()); + + // first we must check the secondary + // TODO: lock target should affect secondary as well + bool send = false; + Mob *secondary = nullptr; + bool has_aggro = false; + if (cur_tar) { + if (cur_tar->GetTarget() == this) {// we got aggro + secondary = cur_tar->GetSecondaryHate(this); + has_aggro = true; + } else { + secondary = cur_tar->GetTarget(); + } + } + + if (secondary && secondary->GetID() != m_aggrometer.get_secondary_id()) { + m_aggrometer.set_secondary_id(secondary->GetID()); + app->WriteUInt8(1); + app->WriteUInt32(m_aggrometer.get_secondary_id()); + send = true; + } else if (!secondary && m_aggrometer.get_secondary_id()) { + m_aggrometer.set_secondary_id(0); + app->WriteUInt8(1); + app->WriteUInt32(0); + send = true; + } else { // might not need to send in this case + app->WriteUInt8(0); + } + + auto count_offset = app->GetWritePosition(); + app->WriteUInt8(0); + + int count = 0; + auto add_entry = [&app, &count, this](AggroMeter::AggroTypes i) { + count++; + app->WriteUInt8(i); + app->WriteUInt16(m_aggrometer.get_pct(i)); + }; + // TODO: Player entry should either be lock or yourself, ignoring lock for now + // player, secondary, and group depend on your target/lock + if (cur_tar) { + if (m_aggrometer.set_pct(AggroMeter::AT_Player, cur_tar->GetHateRatio(cur_tar->GetTarget(), this))) + add_entry(AggroMeter::AT_Player); + + if (m_aggrometer.set_pct(AggroMeter::AT_Secondary, has_aggro ? cur_tar->GetHateRatio(this, secondary) : secondary ? 100 : 0)) + add_entry(AggroMeter::AT_Secondary); + + // fuuuuuuuuuuuuuuuuuuuuuuuucckkkkkkkkkkkkkkk raids + if (IsRaidGrouped()) { + auto raid = GetRaid(); + if (raid) { + auto gid = raid->GetGroup(this); + if (gid < 12) { + int at_id = AggroMeter::AT_Group1; + for (int i = 0; i < MAX_RAID_MEMBERS; ++i) { + if (raid->members[i].member && raid->members[i].member != this && raid->members[i].GroupNumber == gid) { + if (m_aggrometer.set_pct(static_cast(at_id), cur_tar->GetHateRatio(cur_tar->GetTarget(), raid->members[i].member))) + add_entry(static_cast(at_id)); + at_id++; + if (at_id > AggroMeter::AT_Group5) + break; + } + } + } + } + } else if (IsGrouped()) { + auto group = GetGroup(); + if (group) { + int at_id = AggroMeter::AT_Group1; + for (int i = 0; i < MAX_GROUP_MEMBERS; ++i) { + if (group->members[i] && group->members[i] != this) { + if (m_aggrometer.set_pct(static_cast(at_id), cur_tar->GetHateRatio(cur_tar->GetTarget(), group->members[i]))) + add_entry(static_cast(at_id)); + at_id++; + } + } + } + } + } else { // we might need to clear out some data now + if (m_aggrometer.set_pct(AggroMeter::AT_Player, 0)) + add_entry(AggroMeter::AT_Player); + if (m_aggrometer.set_pct(AggroMeter::AT_Secondary, 0)) + add_entry(AggroMeter::AT_Secondary); + if (m_aggrometer.set_pct(AggroMeter::AT_Group1, 0)) + add_entry(AggroMeter::AT_Group1); + if (m_aggrometer.set_pct(AggroMeter::AT_Group2, 0)) + add_entry(AggroMeter::AT_Group2); + if (m_aggrometer.set_pct(AggroMeter::AT_Group3, 0)) + add_entry(AggroMeter::AT_Group3); + if (m_aggrometer.set_pct(AggroMeter::AT_Group4, 0)) + add_entry(AggroMeter::AT_Group5); + if (m_aggrometer.set_pct(AggroMeter::AT_Group5, 0)) + add_entry(AggroMeter::AT_Group5); + } + + // now to go over our xtargets + // if the entry is an NPC it's our hate relative to the NPCs current tank + // if it's a PC, it's their hate relative to our current target + for (int i = 0; i < GetMaxXTargets(); ++i) { + if (XTargets[i].ID) { + auto mob = entity_list.GetMob(XTargets[i].ID); + if (mob) { + int ratio = 0; + if (mob->IsNPC()) + ratio = mob->GetHateRatio(mob->GetTarget(), this); + else if (cur_tar) + ratio = cur_tar->GetHateRatio(cur_tar->GetTarget(), mob); + if (m_aggrometer.set_pct(static_cast(AggroMeter::AT_XTarget1 + i), ratio)) + add_entry(static_cast(AggroMeter::AT_XTarget1 + i)); + } + } + } + + if (send || count) { + app->size = app->GetWritePosition(); // this should be safe, although not recommended + // but this way we can have a smaller buffer created for the packet dispatched to the client w/o resizing this one + app->SetWritePosition(count_offset); + app->WriteUInt8(count); + FastQueuePacket(&app); + } else { + safe_delete(app); + } +} + diff --git a/zone/client.h b/zone/client.h index ae6c2ef30..6dc4d6e38 100644 --- a/zone/client.h +++ b/zone/client.h @@ -49,6 +49,7 @@ namespace EQEmu #include "../common/guilds.h" //#include "../common/item_data.h" #include "xtargetautohaters.h" +#include "aggromanager.h" #include "common.h" #include "merc.h" @@ -1133,6 +1134,11 @@ public: bool GroupFollow(Client* inviter); inline bool GetRunMode() const { return runmode; } + inline bool AggroMeterAvailable() const { return ((m_ClientVersionBit & EQEmu::versions::bit_RoF2AndLater)) && RuleB(Character, EnableAggroMeter); } // RoF untested + inline void SetAggroMeterLock(int in) { m_aggrometer.set_lock_id(in); } + + void ProcessAggroMeter(); // builds packet and sends + void InitializeMercInfo(); bool CheckCanSpawnMerc(uint32 template_id); bool CheckCanHireMerc(Mob* merchant, uint32 template_id); @@ -1470,6 +1476,7 @@ private: Timer afk_toggle_timer; Timer helm_toggle_timer; Timer light_update_timer; + Timer aggro_meter_timer; glm::vec3 m_Proximity; @@ -1560,6 +1567,8 @@ private: XTargetAutoHaters m_autohatermgr; XTargetAutoHaters *m_activeautohatermgr; + AggroMeter m_aggrometer; + Timer ItemTickTimer; Timer ItemQuestTimer; std::map accountflags; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 721421b9e..535e5e98e 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -120,6 +120,7 @@ void MapOpcodes() ConnectedOpcodes[OP_AdventureMerchantSell] = &Client::Handle_OP_AdventureMerchantSell; ConnectedOpcodes[OP_AdventureRequest] = &Client::Handle_OP_AdventureRequest; ConnectedOpcodes[OP_AdventureStatsRequest] = &Client::Handle_OP_AdventureStatsRequest; + ConnectedOpcodes[OP_AggroMeterLockTarget] = &Client::Handle_OP_AggroMeterLockTarget; ConnectedOpcodes[OP_AltCurrencyMerchantRequest] = &Client::Handle_OP_AltCurrencyMerchantRequest; ConnectedOpcodes[OP_AltCurrencyPurchase] = &Client::Handle_OP_AltCurrencyPurchase; ConnectedOpcodes[OP_AltCurrencyReclaim] = &Client::Handle_OP_AltCurrencyReclaim; @@ -2400,6 +2401,17 @@ void Client::Handle_OP_AdventureStatsRequest(const EQApplicationPacket *app) FastQueuePacket(&outapp); } +void Client::Handle_OP_AggroMeterLockTarget(const EQApplicationPacket *app) +{ + if (app->size < sizeof(uint32)) { + Log.Out(Logs::General, Logs::Error, "Handle_OP_AggroMeterLockTarget had a packet that was too small."); + return; + } + + SetAggroMeterLock(app->ReadUInt32(0)); + ProcessAggroMeter(); +} + void Client::Handle_OP_AltCurrencyMerchantRequest(const EQApplicationPacket *app) { VERIFY_PACKET_LENGTH(OP_AltCurrencyMerchantRequest, app, uint32); diff --git a/zone/client_packet.h b/zone/client_packet.h index 2635724dd..76a26e04e 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -32,6 +32,7 @@ void Handle_OP_AdventureMerchantSell(const EQApplicationPacket *app); void Handle_OP_AdventureRequest(const EQApplicationPacket *app); void Handle_OP_AdventureStatsRequest(const EQApplicationPacket *app); + void Handle_OP_AggroMeterLockTarget(const EQApplicationPacket *app); void Handle_OP_AltCurrencyMerchantRequest(const EQApplicationPacket *app); void Handle_OP_AltCurrencyPurchase(const EQApplicationPacket *app); void Handle_OP_AltCurrencyReclaim(const EQApplicationPacket *app); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index b97f7b22e..2bfa8a055 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -684,7 +684,8 @@ bool Client::Process() { if (client_state == CLIENT_CONNECTED) { if (m_dirtyautohaters) ProcessXTargetAutoHaters(); - // aggro meter stuff should live here + if (aggro_meter_timer.Check()) + ProcessAggroMeter(); } return ret; diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index 34519b69f..225ff39ff 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "raids.h" #include "../common/rulesys.h" +#include "../common/data_verification.h" #include "hate_list.h" #include "quest_parser_collection.h" @@ -277,7 +278,24 @@ int HateList::GetSummonedPetCountOnHateList(Mob *hater) { return pet_count; } -Mob *HateList::GetEntWithMostHateOnList(Mob *center) +int HateList::GetHateRatio(Mob *top, Mob *other) +{ + auto other_entry = Find(other); + + if (!other_entry || other_entry->stored_hate_amount < 1) + return 0; + + auto top_entry = Find(top); + + if (!top_entry || top_entry->stored_hate_amount < 1) + return 999; // shouldn't happen if you call it right :P + + return EQEmu::Clamp(static_cast((other_entry->stored_hate_amount * 100) / top_entry->stored_hate_amount), 1, 999); +} + +// skip is used to ignore a certain mob on the list +// Currently used for getting 2nd on list for aggro meter +Mob *HateList::GetEntWithMostHateOnList(Mob *center, Mob *skip) { // hack fix for zone shutdown crashes on some servers if (!zone->IsLoaded()) @@ -310,6 +328,11 @@ Mob *HateList::GetEntWithMostHateOnList(Mob *center) continue; } + if (cur->entity_on_hatelist == skip) { + ++iterator; + continue; + } + auto hateEntryPosition = glm::vec3(cur->entity_on_hatelist->GetX(), cur->entity_on_hatelist->GetY(), cur->entity_on_hatelist->GetZ()); if (center->IsNPC() && center->CastToNPC()->IsUnderwaterOnly() && zone->HasWaterMap()) { if (!zone->watermap->InLiquid(hateEntryPosition)) { @@ -436,6 +459,11 @@ Mob *HateList::GetEntWithMostHateOnList(Mob *center) while (iterator != list.end()) { struct_HateList *cur = (*iterator); + if (cur->entity_on_hatelist == skip) { + ++iterator; + continue; + } + if (center->IsNPC() && center->CastToNPC()->IsUnderwaterOnly() && zone->HasWaterMap()) { if(!zone->watermap->InLiquid(glm::vec3(cur->entity_on_hatelist->GetPosition()))) { skipped_count++; diff --git a/zone/hate_list.h b/zone/hate_list.h index dda8cb21d..f0e2b7618 100644 --- a/zone/hate_list.h +++ b/zone/hate_list.h @@ -41,9 +41,9 @@ public: Mob *GetClosestEntOnHateList(Mob *hater); Mob *GetDamageTopOnHateList(Mob *hater); - Mob *GetEntWithMostHateOnList(Mob *center); + Mob *GetEntWithMostHateOnList(Mob *center, Mob *skip = nullptr); Mob *GetRandomEntOnHateList(); - Mob* GetEntWithMostHateOnList(); + Mob *GetEntWithMostHateOnList(); bool IsEntOnHateList(Mob *mob); bool IsHateListEmpty(); @@ -51,6 +51,7 @@ public: int AreaRampage(Mob *caster, Mob *target, int count, ExtraAttackOptions *opts); int GetSummonedPetCountOnHateList(Mob *hater); + int GetHateRatio(Mob *top, Mob *other); int32 GetEntHateAmount(Mob *ent, bool in_damage = false); @@ -73,4 +74,5 @@ private: Mob *hate_owner; }; -#endif \ No newline at end of file +#endif + diff --git a/zone/mob.h b/zone/mob.h index fe76f949d..42672f046 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -539,7 +539,9 @@ public: void DoubleAggro(Mob *other) { uint32 in_hate = GetHateAmount(other); SetHateAmountOnEnt(other, (in_hate ? in_hate * 2 : 1)); } uint32 GetHateAmount(Mob* tmob, bool is_dam = false) { return hate_list.GetEntHateAmount(tmob,is_dam);} uint32 GetDamageAmount(Mob* tmob) { return hate_list.GetEntHateAmount(tmob, true);} + int GetHateRatio(Mob *first, Mob *with) { return hate_list.GetHateRatio(first, with); } Mob* GetHateTop() { return hate_list.GetEntWithMostHateOnList(this);} + Mob* GetSecondaryHate(Mob *skip) { return hate_list.GetEntWithMostHateOnList(this, skip); } Mob* GetHateDamageTop(Mob* other) { return hate_list.GetDamageTopOnHateList(other);} Mob* GetHateRandom() { return hate_list.GetRandomEntOnHateList();} Mob* GetHateMost() { return hate_list.GetEntWithMostHateOnList();} From f0f5c41c305668862dca717c04fb2392bec79d35 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sun, 19 Feb 2017 21:12:18 -0600 Subject: [PATCH 3/4] Fixed an issue where clients would sell x1000 stacks of items where the price overflows data sizes, the code will now make sure not to sell too many items that go over this data size --- zone/client_packet.cpp | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 535e5e98e..67e085d0d 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -12440,14 +12440,33 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app) return; } - int cost_quantity = mp->quantity; + uint32 cost_quantity = mp->quantity; if (inst->IsCharged()) - int cost_quantity = 1; + uint32 cost_quantity = 1; + + uint32 i; + + if (RuleB(Merchant, UsePriceMod)) { + for (i = 0; i < cost_quantity; i++) { + price = (uint32)((item->Price * i)*(RuleR(Merchant, BuyCostMod))*Client::CalcPriceMod(vendor, true) + 0.5); // need to round up, because client does it automatically when displaying price + if (price > 4000000000) { + cost_quantity = i; + mp->quantity = i; + break; + } + } + } + else { + for (i = 0; i < cost_quantity; i++) { + price = (uint32)((item->Price * i)*(RuleR(Merchant, BuyCostMod)) + 0.5); // need to round up, because client does it automatically when displaying price + if (price > 4000000000) { + cost_quantity = i; + mp->quantity = i; + break; + } + } + } - if (RuleB(Merchant, UsePriceMod)) - price = (int)((item->Price*cost_quantity)*(RuleR(Merchant, BuyCostMod))*Client::CalcPriceMod(vendor, true) + 0.5); // need to round up, because client does it automatically when displaying price - else - price = (int)((item->Price*cost_quantity)*(RuleR(Merchant, BuyCostMod)) + 0.5); AddMoneyToPP(price, false); if (inst->IsStackable() || inst->IsCharged()) From 46b19e8e6f41045b6411c274f59078a502565cc1 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 20 Feb 2017 18:41:17 -0500 Subject: [PATCH 4/4] Disable the aggro meter timer if it's not enabled --- zone/client.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zone/client.cpp b/zone/client.cpp index 9e02195c0..5f3d3ac81 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -8775,8 +8775,10 @@ void Client::CheckRegionTypeChanges() void Client::ProcessAggroMeter() { - if (!AggroMeterAvailable()) + if (!AggroMeterAvailable()) { + aggro_meter_timer.Disable(); return; + } // we need to decide if we need to send OP_AggroMeterTargetInfo now // This packet sends the current lock target ID and the current target ID