Apply the premade group finder category filter.
An empty search box no longer returns the whole category. Recommended, current expansion, current season, and PvE or PvP now narrow the list, and the difficulty checkboxes cover raid difficulties as well as dungeons.
This commit is contained in:
@@ -543,10 +543,12 @@ void WorldSession::HandleLFGListSearch(WorldPackets::LFGList::LFGListSearch& pac
|
||||
filter.AdvancedFilterMask = packet.AdvancedFilterMask;
|
||||
filter.MinimumRating = packet.MinimumRating;
|
||||
filter.LanguageMask = packet.LanguageMask;
|
||||
filter.ListFilter = packet.Filter;
|
||||
filter.SearcherLevel = GetPlayer()->GetLevel();
|
||||
filter.SearcherClass = GetPlayer()->GetClass();
|
||||
// Not acted on, each for a reason recorded at its declaration: Filter (already consumed client-side before
|
||||
// ResolvedActivityIDs was built), PreferredFilters and FilterByte2, CrossFaction (every listing is already
|
||||
// visible to both factions), FilterByte1 (a client constant) and Guids (no client path fills it).
|
||||
// Not acted on, each for a reason recorded at its declaration: PreferredFilters and FilterByte2,
|
||||
// CrossFaction (every listing is already visible to both factions), FilterByte1 (a client constant)
|
||||
// and Guids (no client path fills it). Filter is applied in LFGListMgr::Matches.
|
||||
|
||||
// Keep this browser subscribed so listings published/edited from now on are pushed live via
|
||||
// SMSG_LFG_LIST_SEARCH_RESULTS_UPDATE instead of the player having to re-search. It records the SAME
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
#include "WorldSession.h" // TransferListingLeadership re-announces the entry to the new owner
|
||||
#include <cctype>
|
||||
#include <utility>
|
||||
//WowCommunity
|
||||
#include "ChallengeModeMgr.h"
|
||||
#include "SharedDefines.h"
|
||||
//WowCommunity
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -732,8 +736,8 @@ float LFGListMgr::GetLeaderDungeonScore(LFGList::Listing const& listing)
|
||||
}
|
||||
|
||||
// C_LFGList.GetActivityInfoTable's difficulty flags, as the 12.1.0.69587 client builds them (0x7FF7CF2B9F50) from
|
||||
// GroupFinderActivity.DifficultyID after remapping 233 -> 16 and 250 -> 17: isNormalActivity == 1, isHeroicActivity == 2,
|
||||
// isMythicActivity == 23, isMythicPlusActivity == 8. Everything else - raid difficulties 14-16 included - is in no band
|
||||
// isMythicActivity == 23, isMythicPlusActivity == 8. Raid difficulties use the same four checkboxes: 14 normal,
|
||||
// 15 heroic, 16 mythic. Anything outside those bands still passes, so an unknown difficulty is not hidden.
|
||||
// and passes, exactly like the Lua, which only rejects an activity that IS in a band the player left unticked.
|
||||
bool LFGListMgr::MatchesDifficultyBand(LFGList::Listing const& listing, uint32 advancedFilterMask)
|
||||
{
|
||||
@@ -753,14 +757,87 @@ bool LFGListMgr::MatchesDifficultyBand(LFGList::Listing const& listing, uint32 a
|
||||
|
||||
switch (difficulty)
|
||||
{
|
||||
case 1: return (advancedFilterMask & ADVANCED_FILTER_DIFFICULTY_NORMAL) != 0;
|
||||
case 2: return (advancedFilterMask & ADVANCED_FILTER_DIFFICULTY_HEROIC) != 0;
|
||||
case 1:
|
||||
case 14: return (advancedFilterMask & ADVANCED_FILTER_DIFFICULTY_NORMAL) != 0;
|
||||
case 2:
|
||||
case 15: return (advancedFilterMask & ADVANCED_FILTER_DIFFICULTY_HEROIC) != 0;
|
||||
case 16:
|
||||
case 23: return (advancedFilterMask & ADVANCED_FILTER_DIFFICULTY_MYTHIC) != 0;
|
||||
case 8: return (advancedFilterMask & ADVANCED_FILTER_DIFFICULTY_MYTHIC_PLUS) != 0;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Enum.LFGListFilter bits for one GroupFinderActivity, from the searching player's level and the record.
|
||||
// The client builds the same mask at RVA 0x24DA960 and keeps a record only when it carries every bit of
|
||||
// CMSG_LFG_LIST_SEARCH.Filter. An empty search box sends no resolved activity ids, and the client does not
|
||||
// re-apply the mask to rows it already received, so this is the only place the category buttons can work.
|
||||
// Recommended is the expansion the player's level sits in. Current expansion content stays recommended from
|
||||
// the previous cap upward, so a level-80 character still sees Midnight. PvE/PvP come from
|
||||
// GroupFinderActivity.Flags (0x2 / 0x4). A keystone activity is current season only when its challenge mode
|
||||
// is in the live season; every other current-expansion activity is current season too.
|
||||
static uint32 ActivityListFilterMask(GroupFinderActivityEntry const* activity, uint32 searcherLevel)
|
||||
{
|
||||
using namespace LFGList;
|
||||
uint32 mask = 0;
|
||||
|
||||
if (activity->Flags & 0x4)
|
||||
mask |= LFG_LIST_FILTER_PVP;
|
||||
if ((activity->Flags & 0x2) || (activity->Flags & 0x4) == 0)
|
||||
mask |= LFG_LIST_FILTER_PVE;
|
||||
|
||||
if (activity->ExpansionID == int32(CURRENT_EXPANSION))
|
||||
mask |= LFG_LIST_FILTER_CURRENT_EXPANSION;
|
||||
|
||||
bool currentSeason = activity->ExpansionID == int32(CURRENT_EXPANSION);
|
||||
if (activity->MapChallengeModeID > 0)
|
||||
{
|
||||
currentSeason = false;
|
||||
for (uint32 challengeModeId : sChallengeModeMgr.GetSeasonMapChallengeModeIds())
|
||||
{
|
||||
if (challengeModeId == uint32(activity->MapChallengeModeID))
|
||||
{
|
||||
currentSeason = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mask |= currentSeason ? LFG_LIST_FILTER_CURRENT_SEASON : LFG_LIST_FILTER_NOT_CURRENT_SEASON;
|
||||
|
||||
bool recommended = false;
|
||||
if (activity->ExpansionID >= 0 && uint32(activity->ExpansionID) < MAX_EXPANSIONS)
|
||||
{
|
||||
uint32 const expansion = uint32(activity->ExpansionID);
|
||||
uint32 const bandMax = GetMaxLevelForExpansion(expansion);
|
||||
uint32 const bandMin = expansion > 0 ? GetMaxLevelForExpansion(expansion - 1) : 0;
|
||||
if (expansion == CURRENT_EXPANSION)
|
||||
recommended = searcherLevel >= bandMin && searcherLevel > 0;
|
||||
else if (bandMax > bandMin && searcherLevel > bandMin && searcherLevel <= bandMax)
|
||||
recommended = true;
|
||||
else if (bandMax > 0 && bandMax == bandMin && searcherLevel == bandMax)
|
||||
recommended = true;
|
||||
}
|
||||
mask |= recommended ? LFG_LIST_FILTER_RECOMMENDED : LFG_LIST_FILTER_NOT_RECOMMENDED;
|
||||
return mask;
|
||||
}
|
||||
|
||||
static bool MatchesListFilter(LFGList::Listing const& listing, LFGList::SearchFilter const& filter)
|
||||
{
|
||||
if (!filter.ListFilter)
|
||||
return true;
|
||||
|
||||
if (listing.Descriptor.ActivityIDs.empty())
|
||||
return false;
|
||||
|
||||
for (uint32 activityId : listing.Descriptor.ActivityIDs)
|
||||
if (GroupFinderActivityEntry const* activity = sGroupFinderActivityStore.LookupEntry(activityId))
|
||||
if ((ActivityListFilterMask(activity, filter.SearcherLevel) & filter.ListFilter) == filter.ListFilter)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// The advanced filter, applied exactly as LFGList.lua (12.1.0.69587) EntryStillSatisfiesFilters re-applies it to a row
|
||||
// the client already holds: role / class needs against GetSearchResultMemberCounts, minimumRating against
|
||||
// leaderOverallDungeonScore, the difficulty band of the listed activity, and the listing's general playstyle. The
|
||||
@@ -859,30 +936,17 @@ bool LFGListMgr::Matches(LFGList::Listing const& listing, LFGList::SearchFilter
|
||||
// Two consequences, and they decide the shape of this predicate:
|
||||
// - ResolvedActivityIds is non-empty ONLY when the player typed something. It is never the lone
|
||||
// carrier of a request, and the union below can never be reached with a wildcard on one side.
|
||||
// - The Filter narrowing therefore does not reach the server at all when the box is empty, and this
|
||||
// is a REAL GAP, stated here rather than smoothed over. mask() is the client's computed
|
||||
// Enum.LFGListFilter mask (RVA 0x24DA960), built from the SEARCHING PLAYER's level against the
|
||||
// activity's suggestion (Recommended 1 / NotRecommended 2), the record's own PvE 4 / PvP 8 /
|
||||
// Timerunning 16 flag bits, an expansion test (CurrentExpansion 32) and membership in a client-side
|
||||
// store (CurrentSeason 64 / NotCurrentSeason 128). The last three are not server state at all, and
|
||||
// the first two need a level/suggestion model this unit's listing does not carry.
|
||||
// The client does NOT make up for it either - that was checked rather than hoped. The UI displays
|
||||
// C_LFGList.GetFilteredSearchResults() (LFGList.lua 12.1.0.69404,
|
||||
// LFGListSearchPanel_UpdateResultList), whose implementation @ RVA 0x24E3A40 walks the received
|
||||
// rows and drops exactly those whose key at row+0x10 is present in a client-side map
|
||||
// (qword_7FF7853F17F0) - what fills that map was not identified. It re-applies neither the filter
|
||||
// mask nor the category nor the activity set. So a search with a Filter and an empty box really does
|
||||
// show the whole category, and closing it needs work outside this unit.
|
||||
// Reproducing the mask PARTIALLY would be worse than not reproducing it: the test is
|
||||
// `(mask & Filter) == Filter`, so a server mask that cannot carry Recommended would fail every
|
||||
// record for the one Filter value actually measured (1, which ResolveCategoryFilters forces for the
|
||||
// dungeon category) and empty the browse pane. Delivering too much is the recoverable error here.
|
||||
// - Filter is a separate test. An empty box leaves ResolvedActivityIds empty, and the client does
|
||||
// not re-apply Enum.LFGListFilter to the rows it is sent, so MatchesListFilter applies that mask
|
||||
// here. The dungeon category forces the Recommended bit; current-expansion activities carry it
|
||||
// from the previous level cap upward, which is why that forced bit does not empty the pane.
|
||||
//
|
||||
// Hence: a request with BOTH halves empty is the wildcard - that is the search box left blank, and the
|
||||
// whole category is the honest answer to it. Anything else has to satisfy at least one half, and each
|
||||
// half only counts when it was actually asked for. That is the fix for Runde 12's second finding: a
|
||||
// request carrying resolved activities and no keywords now filters by them instead of riding in an OR
|
||||
// that an empty keyword set satisfies on its own.
|
||||
// Hence: a request with BOTH halves empty is the wildcard for the search box. The category filter
|
||||
// above still applies. Anything else has to satisfy at least one half, and each half only counts
|
||||
// when it was actually asked for.
|
||||
if (!MatchesListFilter(listing, filter))
|
||||
return false;
|
||||
|
||||
if (!MatchesAdvancedFilter(listing, filter))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -99,6 +99,19 @@ namespace LFGList
|
||||
| ADVANCED_FILTER_GENERAL_PLAYSTYLE_3 | ADVANCED_FILTER_GENERAL_PLAYSTYLE_4,
|
||||
};
|
||||
|
||||
// Enum.LFGListFilter, CMSG_LFG_LIST_SEARCH.Filter. A listing's activity must carry every set bit.
|
||||
enum ListFilter : uint32
|
||||
{
|
||||
LFG_LIST_FILTER_RECOMMENDED = 0x01,
|
||||
LFG_LIST_FILTER_NOT_RECOMMENDED = 0x02,
|
||||
LFG_LIST_FILTER_PVE = 0x04,
|
||||
LFG_LIST_FILTER_PVP = 0x08,
|
||||
LFG_LIST_FILTER_TIMERRUNNING = 0x10,
|
||||
LFG_LIST_FILTER_CURRENT_EXPANSION = 0x20,
|
||||
LFG_LIST_FILTER_CURRENT_SEASON = 0x40,
|
||||
LFG_LIST_FILTER_NOT_CURRENT_SEASON = 0x80,
|
||||
};
|
||||
|
||||
// What a listing's party looks like to a browser: C_LFGList.GetSearchResultMemberCounts' TANK / HEALER / DAMAGER
|
||||
// and per-class counts, taken from the very role bytes the search row carries so the two cannot disagree.
|
||||
struct MemberComposition
|
||||
@@ -130,6 +143,8 @@ namespace LFGList
|
||||
uint32 AdvancedFilterMask = 0; // AdvancedFilterFlag
|
||||
uint32 MinimumRating = 0; // advancedFilter.minimumRating, against the leader's dungeon score
|
||||
uint32 LanguageMask = 0; // one bit per LocaleConstant; 0 = no restriction
|
||||
uint32 ListFilter = 0; // Enum.LFGListFilter; 0 = no restriction
|
||||
uint32 SearcherLevel = 0; // recommended / not-recommended is relative to this level
|
||||
uint8 SearcherClass = 0; // needsMyClass is relative to the player who searches
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user