Skip to content

Commit c1672df

Browse files
committed
feat(network): implement LFG List / Group Finder (premade groups)
1 parent 2b9c361 commit c1672df

12 files changed

Lines changed: 2041 additions & 12 deletions

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* This file is part of the ArgusCore Project. See AUTHORS file for Copyright information
3+
*
4+
* This program is free software; you can redistribute it and/or modify it
5+
* under the terms of the GNU General Public License as published by the
6+
* Free Software Foundation; either version 2 of the License, or (at your
7+
* option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12+
* more details.
13+
*
14+
* You should have received a copy of the GNU General Public License along
15+
* with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
#include "LFGList.h"
19+
#include "LFGListMgr.h"
20+
#include "Containers.h"
21+
#include "Group.h"
22+
#include "GroupFinderPackets.h"
23+
#include "ObjectAccessor.h"
24+
#include "ObjectMgr.h"
25+
#include "Player.h"
26+
#include "World.h"
27+
28+
LFGListEntry::LFGListApplicationEntry::LFGListApplicationEntry(ObjectGuid::LowType playerGuid, LFGListEntry* owner)
29+
{
30+
ID = sLFGListMgr->GenerateApplicationId();
31+
ApplicationTime = uint32(GameTime::GetGameTime());
32+
PlayerGuid = playerGuid;
33+
Timeout = ApplicationTime + LFG_LIST_APPLY_FOR_GROUP_TIMEOUT;
34+
ApplicationStatus = LFGListApplicationStatus::None;
35+
Listed = true;
36+
m_Owner = owner;
37+
Status = LFGListStatus::None;
38+
RoleMask = 0;
39+
}
40+
41+
Player* LFGListEntry::LFGListApplicationEntry::GetPlayer() const
42+
{
43+
return ObjectAccessor::FindPlayerByLowGUID(PlayerGuid);
44+
}
45+
46+
void LFGListEntry::LFGListApplicationEntry::ResetTimeout()
47+
{
48+
Timeout = uint32(GameTime::GetGameTime()) + (ApplicationStatus == LFGListApplicationStatus::Invited ? LFG_LIST_INVITE_TO_GROUP_TIMEOUT : LFG_LIST_APPLY_FOR_GROUP_TIMEOUT);
49+
}
50+
51+
bool LFGListEntry::LFGListApplicationEntry::Update(uint32 /*diff*/)
52+
{
53+
return Timeout > uint32(GameTime::GetGameTime());
54+
}
55+
56+
LFGListEntry::LFGListEntry() : GroupFinderActivityData(nullptr), ApplicationGroup(nullptr), HonorLevel(0), QuestID(0), ItemLevel(0), AutoAccept(false)
57+
{
58+
CreationTime = uint32(GameTime::GetGameTime());
59+
Timeout = CreationTime + LFG_LIST_GROUP_TIMEOUT;
60+
}
61+
62+
bool LFGListEntry::IsApplied(ObjectGuid::LowType guid) const
63+
{
64+
return GetApplicantByPlayerGUID(guid) != nullptr;
65+
}
66+
67+
bool LFGListEntry::IsApplied(Player* player) const
68+
{
69+
return IsApplied(player->GetGUID().GetCounter());
70+
}
71+
72+
LFGListEntry::LFGListApplicationEntry* LFGListEntry::GetApplicant(uint32 applicationId)
73+
{
74+
return Trinity::Containers::MapGetValuePtr(ApplicationsContainer, applicationId);
75+
}
76+
77+
LFGListEntry::LFGListApplicationEntry* LFGListEntry::GetApplicantByPlayerGUID(ObjectGuid::LowType guid)
78+
{
79+
for (auto& application : ApplicationsContainer)
80+
if (application.second.PlayerGuid == guid)
81+
return &application.second;
82+
83+
return nullptr;
84+
}
85+
86+
void LFGListEntry::BroadcastApplicantUpdate(LFGListApplicationEntry const* applicant)
87+
{
88+
if (!ApplicationGroup)
89+
return;
90+
91+
WorldPackets::GroupFinder::LfgListApplicationUpdate update;
92+
update.ApplicationTicket.RequesterGuid = ApplicationGroup->GetGUID();
93+
update.ApplicationTicket.Id = GetID();
94+
update.ApplicationTicket.Type = WorldPackets::LFG::RideType::LfgListApplication;
95+
update.ApplicationTicket.Time = CreationTime;
96+
97+
WorldPackets::GroupFinder::ApplicantInfo info;
98+
info.ApplicantTicket.RequesterGuid = ObjectGuid::Create<HighGuid::Player>(applicant->PlayerGuid);
99+
info.ApplicantTicket.Id = applicant->ID;
100+
info.ApplicantTicket.Type = WorldPackets::LFG::RideType::LfgListApplicant;
101+
info.ApplicantTicket.Time = applicant->ApplicationTime;
102+
103+
info.ApplicantPartyLeader = ObjectGuid::Create<HighGuid::Player>(applicant->PlayerGuid);
104+
info.ApplicationStatus = AsUnderlyingType(applicant->ApplicationStatus);
105+
info.Comment = applicant->Comment;
106+
info.Listed = applicant->Listed;
107+
108+
if (Player* player = applicant->GetPlayer(); player && applicant->Listed)
109+
{
110+
WorldPackets::GroupFinder::ApplicantMember member;
111+
member.PlayerGUID = player->GetGUID();
112+
member.VirtualRealmAddress = GetVirtualRealmAddress();
113+
member.Level = player->GetLevel();
114+
member.HonorLevel = player->GetHonorLevel();
115+
member.ItemLevel = sLFGListMgr->GetPlayerItemLevelForActivity(GroupFinderActivityData, player);
116+
member.PossibleRoleMask = applicant->RoleMask;
117+
member.SelectedRoleMask = 0;
118+
119+
info.Member.emplace_back(member);
120+
}
121+
122+
update.Applicants.emplace_back(info);
123+
124+
ApplicationGroup->BroadcastPacket(update.Write(), false);
125+
}
126+
127+
void LFGListEntry::ResetTimeout()
128+
{
129+
Timeout = uint32(GameTime::GetGameTime()) + LFG_LIST_GROUP_TIMEOUT;
130+
sLFGListMgr->SendLFGListStatusUpdate(this);
131+
}
132+
133+
uint32 LFGListEntry::GetID() const
134+
{
135+
return ApplicationGroup ? ApplicationGroup->GetGUID().GetCounter() : 0;
136+
}
137+
138+
bool LFGListEntry::Update(uint32 diff)
139+
{
140+
for (auto itr = ApplicationsContainer.begin(); itr != ApplicationsContainer.end();)
141+
{
142+
if (!itr->second.Update(diff))
143+
{
144+
sLFGListMgr->ChangeApplicantStatus(&itr->second, LFGListApplicationStatus::Timeout);
145+
itr = ApplicationsContainer.begin();
146+
}
147+
else
148+
++itr;
149+
}
150+
151+
return Timeout > uint32(GameTime::GetGameTime());
152+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* This file is part of the ArgusCore Project. See AUTHORS file for Copyright information
3+
*
4+
* This program is free software; you can redistribute it and/or modify it
5+
* under the terms of the GNU General Public License as published by the
6+
* Free Software Foundation; either version 2 of the License, or (at your
7+
* option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12+
* more details.
13+
*
14+
* You should have received a copy of the GNU General Public License along
15+
* with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
// Group Finder ("LFG List" / premade groups) data model. Ported from the shape confirmed by
19+
// 3/4 references (DestinyCore, LegionCore-7.3.5, LegionCore-7.3.5V2 - AshamaneCore lacks this
20+
// feature entirely), translated to ArgusCore's own idioms rather than copied verbatim:
21+
// - Application ticket ids are a plain incrementing uint32 (LFGListMgr::GenerateApplicationId),
22+
// not DestinyCore's ObjectGuid::Create<HighGuid::LFGObject> - ArgusCore's own
23+
// WorldPackets::LFG::RideTicket::Id is already just a uint32 (LFGPacketsCommon.h), so the
24+
// heavier structured-GUID machinery isn't needed here.
25+
// See ARGUSCORE_FIXES.md for the full investigation (DB2 data layer verification, RideType
26+
// enum extension, etc.).
27+
28+
#pragma once
29+
30+
#include "Common.h"
31+
#include "ObjectGuid.h"
32+
#include <map>
33+
#include <string>
34+
35+
struct GroupFinderActivityEntry;
36+
class Player;
37+
class Group;
38+
39+
static uint16 const LFG_LIST_APPLY_FOR_GROUP_TIMEOUT = 300;
40+
static uint8 const LFG_LIST_INVITE_TO_GROUP_TIMEOUT = 60;
41+
static uint16 const LFG_LIST_GROUP_TIMEOUT = 30 * MINUTE;
42+
static uint8 const LFG_LIST_MAX_APPLICATIONS = 5;
43+
44+
enum class LFGListApplicationStatus : uint8
45+
{
46+
None = 0,
47+
Applied = 1,
48+
Invited = 2,
49+
Failed = 3,
50+
Cancelled = 4,
51+
Declined = 5,
52+
DeclinedFull = 6,
53+
DeclinedDelisted = 7,
54+
Timeout = 8,
55+
InviteDeclined = 9,
56+
InviteAccepted = 10,
57+
};
58+
59+
enum LFGListActivityCategory
60+
{
61+
LFG_LIST_ACTIVITY_CATEGORY_ZONE = 1,
62+
LFG_LIST_ACTIVITY_CATEGORY_DUNGEON = 2,
63+
LFG_LIST_ACTIVITY_CATEGORY_RAID = 3,
64+
LFG_LIST_ACTIVITY_CATEGORY_ARENA = 4,
65+
LFG_LIST_ACTIVITY_CATEGORY_SCENARIO = 5,
66+
LFG_LIST_ACTIVITY_CATEGORY_CUSTOM = 6,
67+
LFG_LIST_ACTIVITY_CATEGORY_ARENA_SKIRMISH = 7,
68+
LFG_LIST_ACTIVITY_CATEGORY_BATTLEGROUNDS = 8,
69+
LFG_LIST_ACTIVITY_CATEGORY_RATED_BATTLEGROUNDS = 9,
70+
LFG_LIST_ACTIVITY_CATEGORY_OUTDOOR_PVP = 10
71+
};
72+
73+
// Status codes sent to the client to explain why a join/apply/invite request did or didn't
74+
// succeed. 2/4-corroborated (DestinyCore, LegionCore-7.3.5/V2 agree on the ones checked).
75+
enum class LFGListStatus : uint8
76+
{
77+
None = 0,
78+
Joined = 6,
79+
80+
LFG_LIST_STATUS_ERR_LFG_LIST_GROUP_FULL = 30,
81+
LFG_LIST_STATUS_ERR_LFG_LIST_NO_LFG_LIST_OBJECT = 32,
82+
LFG_LIST_STATUS_ERR_LFG_LIST_NO_SLOTS_PLAYER = 33,
83+
LFG_LIST_STATUS_ERR_LFG_LIST_MISMATCHED_SLOTS = 34,
84+
LFG_LIST_STATUS_ERR_LFG_LIST_MISMATCHED_SLOTS_LOCAL_XREALM = 54,
85+
LFG_LIST_STATUS_ERR_LFG_LIST_PARTY_PLAYERS_FROM_DIFFERENT_REALMS = 35,
86+
LFG_LIST_STATUS_ERR_LFG_LIST_MEMBERS_NOT_PRESENT = 36,
87+
LFG_LIST_STATUS_ERR_LFG_LIST_GET_INFO_TIMEOUT = 37,
88+
LFG_LIST_STATUS_ERR_LFG_LIST_INVALID_SLOT = 38,
89+
LFG_LIST_STATUS_ERR_LFG_LIST_DESERTER_PLAYER = 39,
90+
LFG_LIST_STATUS_ERR_LFG_LIST_DESERTER_PARTY = 40,
91+
LFG_LIST_STATUS_ERR_LFG_LIST_RANDOM_COOLDOWN_PLAYER = 41,
92+
LFG_LIST_STATUS_ERR_LFG_LIST_RANDOM_COOLDOWN_PARTY = 42,
93+
LFG_LIST_STATUS_ERR_LFG_LIST_TOO_MANY_MEMBERS = 43,
94+
LFG_LIST_STATUS_LFG_LIST_LIST_ENTRY_EXPIRED_TIMEOUT = 44,
95+
LFG_LIST_STATUS_ERR_LFG_LIST_ROLE_CHECK_FAILED = 45,
96+
LFG_LIST_STATUS_ERR_LFG_LIST_TOO_FEW_MEMBERS = 51,
97+
LFG_LIST_STATUS_ERR_LFG_LIST_REASON_TOO_MANY_LFG_LIST = 52,
98+
LFG_LIST_STATUS_APPLICATION_STATUS_UPDATED = 59,
99+
LFG_LIST_STATUS_ERR_ALREADY_USING_LFG_LIST_LIST = 62,
100+
LFG_LIST_STATUS_RESTRICTED_ACCOUNT_TRIAL = 65,
101+
};
102+
103+
// One active Group Finder listing. Keyed in LFGListMgr by the leader's group GUID counter.
104+
struct LFGListEntry
105+
{
106+
struct LFGListApplicationEntry
107+
{
108+
LFGListApplicationEntry(ObjectGuid::LowType playerGuid, LFGListEntry* owner);
109+
110+
LFGListEntry* m_Owner;
111+
ObjectGuid::LowType PlayerGuid;
112+
uint32 ID;
113+
uint32 ApplicationTime;
114+
uint32 Timeout;
115+
LFGListStatus Status;
116+
std::string Comment;
117+
LFGListApplicationStatus ApplicationStatus;
118+
uint8 RoleMask;
119+
bool Listed;
120+
121+
Player* GetPlayer() const;
122+
void ResetTimeout();
123+
bool Update(uint32 diff);
124+
};
125+
126+
LFGListEntry();
127+
128+
bool IsApplied(ObjectGuid::LowType guid) const;
129+
bool IsApplied(Player* player) const;
130+
131+
void BroadcastApplicantUpdate(LFGListApplicationEntry const* applicant);
132+
133+
// Keyed by the application's own generated ticket id (LFGListApplicationEntry::ID, matches
134+
// WorldPackets::LFG::RideTicket::Id sent to the client) - confirmed via the reference's own
135+
// insertion call (ApplicationsContainer.insert({application.ID, application})), not by
136+
// player GUID. GetApplicantByPlayerGUID does a linear scan for the player-guid lookup case.
137+
LFGListApplicationEntry* GetApplicant(uint32 applicationId);
138+
LFGListApplicationEntry* GetApplicantByPlayerGUID(ObjectGuid::LowType guid);
139+
140+
bool Update(uint32 diff);
141+
void ResetTimeout();
142+
143+
uint32 GetID() const;
144+
145+
std::map<uint32 /*applicationId*/, LFGListApplicationEntry> ApplicationsContainer;
146+
GroupFinderActivityEntry const* GroupFinderActivityData;
147+
Group* ApplicationGroup;
148+
149+
uint32 Timeout;
150+
uint32 CreationTime;
151+
uint32 HonorLevel;
152+
uint32 QuestID;
153+
float ItemLevel;
154+
std::string GroupName;
155+
std::string Comment;
156+
std::string VoiceChat;
157+
bool AutoAccept;
158+
bool PrivateGroup = false;
159+
};

0 commit comments

Comments
 (0)