5879: Support multiple values to search/exclude (fixes #4119) r=Robyt3 a=def-

![screenshot-20220925@143701](https://user-images.githubusercontent.com/2335377/192143966-bd91670c-bcdd-45ea-90aa-189a9fce0975.png)
## Checklist

- [x] Tested the change ingame
- [x] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test (especially base/) or added coverage to integration test
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: def <dennis@felsin9.de>
This commit is contained in:
bors[bot] 2022-10-07 16:58:04 +00:00 committed by GitHub
commit c5401b8607
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 133 additions and 84 deletions

View file

@ -300,66 +300,84 @@ void CServerBrowser::Filter()
}
}
if(!Filtered && g_Config.m_BrFilterString[0] != 0)
if(!Filtered && g_Config.m_BrFilterString[0] != '\0')
{
int MatchFound = 0;
m_ppServerlist[i]->m_Info.m_QuickSearchHit = 0;
// match against server name
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrFilterString))
const char *pStr = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME;
}
if(aFilterStr[0] == '\0')
{
continue;
}
// match against players
for(p = 0; p < minimum(m_ppServerlist[i]->m_Info.m_NumClients, (int)MAX_CLIENTS); p++)
{
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, g_Config.m_BrFilterString) ||
str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan, g_Config.m_BrFilterString))
// match against server name
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, aFilterStr))
{
MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_PLAYER;
break;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME;
}
}
// match against map
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrFilterString))
{
MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_MAPNAME;
// match against players
for(p = 0; p < minimum(m_ppServerlist[i]->m_Info.m_NumClients, (int)MAX_CLIENTS); p++)
{
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, aFilterStr) ||
str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan, aFilterStr))
{
MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_PLAYER;
break;
}
}
// match against map
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, aFilterStr))
{
MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_MAPNAME;
}
}
if(!MatchFound)
Filtered = 1;
}
if(!Filtered && g_Config.m_BrExcludeString[0] != 0)
if(!Filtered && g_Config.m_BrExcludeString[0] != '\0')
{
int MatchFound = 0;
// match against server name
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrExcludeString))
const char *pStr = g_Config.m_BrExcludeString;
char aExcludeStr[sizeof(g_Config.m_BrExcludeString)];
while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aExcludeStr, sizeof(aExcludeStr))))
{
MatchFound = 1;
}
if(aExcludeStr[0] == '\0')
{
continue;
}
// match against map
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrExcludeString))
{
MatchFound = 1;
}
// match against server name
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, aExcludeStr))
{
Filtered = 1;
break;
}
// match against gametype
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrExcludeString))
{
MatchFound = 1;
}
// match against map
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, aExcludeStr))
{
Filtered = 1;
break;
}
if(MatchFound)
Filtered = 1;
// match against gametype
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, aExcludeStr))
{
Filtered = 1;
break;
}
}
}
}

View file

@ -141,6 +141,8 @@ public:
NUM_NETWORKS,
};
static constexpr const char *SEARCH_EXCLUDE_TOKEN = ";";
virtual void Refresh(int Type) = 0;
virtual bool IsGettingServerlist() const = 0;
virtual bool IsRefreshing() const = 0;

View file

@ -44,8 +44,8 @@ MACRO_CONFIG_STR(ClAssetParticles, cl_asset_particles, 50, "default", CFGFLAG_SA
MACRO_CONFIG_STR(ClAssetHud, cl_asset_hud, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset for HUD")
MACRO_CONFIG_STR(ClAssetExtras, cl_asset_extras, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset for the game graphics that do not come from Teeworlds")
MACRO_CONFIG_STR(BrFilterString, br_filter_string, 25, "Novice", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Server browser filtering string")
MACRO_CONFIG_STR(BrExcludeString, br_exclude_string, 25, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Server browser exclusion string")
MACRO_CONFIG_STR(BrFilterString, br_filter_string, 128, "Novice", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Server browser filtering string")
MACRO_CONFIG_STR(BrExcludeString, br_exclude_string, 128, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Server browser exclusion string")
MACRO_CONFIG_INT(BrFilterFull, br_filter_full, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out full server in browser")
MACRO_CONFIG_INT(BrFilterEmpty, br_filter_empty, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out empty server in browser")
MACRO_CONFIG_INT(BrFilterSpectators, br_filter_spectators, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out spectators from player numbers")

View file

@ -347,23 +347,34 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
else if(ID == COL_NAME)
{
float FontSize = 12.0f;
bool Printed = false;
if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_SERVERNAME))
{
// highlight the parts that matches
const char *pStr = str_utf8_find_nocase(pItem->m_aName, g_Config.m_BrFilterString);
if(pStr)
const char *pStrToken = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStrToken = str_next_token(pStrToken, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 0), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aName));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(g_Config.m_BrFilterString), &pItem->m_pUIElement->Rect(gs_OffsetColName + 0)->m_Cursor);
TextRender()->TextColor(1, 1, 1, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 2), &Button, pStr + str_length(g_Config.m_BrFilterString), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColName + 1)->m_Cursor);
if(aFilterStr[0] == '\0')
{
continue;
}
// highlight the parts that matches
const char *pStr = str_utf8_find_nocase(pItem->m_aName, aFilterStr);
if(pStr)
{
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 0), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aName));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(aFilterStr), &pItem->m_pUIElement->Rect(gs_OffsetColName + 0)->m_Cursor);
TextRender()->TextColor(1, 1, 1, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 2), &Button, pStr + str_length(aFilterStr), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColName + 1)->m_Cursor);
Printed = true;
break;
}
}
else
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
}
else
if(!Printed)
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
}
else if(ID == COL_MAP)
@ -382,23 +393,29 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
}
float FontSize = 12.0f;
bool Printed = false;
if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_MAPNAME))
{
// highlight the parts that matches
const char *pStr = str_utf8_find_nocase(pItem->m_aMap, g_Config.m_BrFilterString);
if(pStr)
const char *pStrToken = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStrToken = str_next_token(pStrToken, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 0), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aMap));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(g_Config.m_BrFilterString), &pItem->m_pUIElement->Rect(gs_OffsetColMap + 0)->m_Cursor);
TextRender()->TextColor(1, 1, 1, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 2), &Button, pStr + str_length(g_Config.m_BrFilterString), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 1)->m_Cursor);
// highlight the parts that matches
const char *pStr = str_utf8_find_nocase(pItem->m_aMap, aFilterStr);
if(pStr)
{
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 0), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aMap));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(aFilterStr), &pItem->m_pUIElement->Rect(gs_OffsetColMap + 0)->m_Cursor);
TextRender()->TextColor(1, 1, 1, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 2), &Button, pStr + str_length(aFilterStr), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 1)->m_Cursor);
Printed = true;
break;
}
}
else
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
}
else
if(!Printed)
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
}
else if(ID == COL_PLAYERS)
@ -1172,44 +1189,56 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
TextRender()->SetCursor(&Cursor, Name.x, Name.y + (Name.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Name.w;
const char *pName = pSelectedServer->m_aClients[i].m_aName;
bool Printed = false;
if(g_Config.m_BrFilterString[0])
{
// highlight the parts that matches
const char *pFilteredStr = str_utf8_find_nocase(pName, g_Config.m_BrFilterString);
if(pFilteredStr)
const char *pStr = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
TextRender()->TextEx(&Cursor, pName, (int)(pFilteredStr - pName));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredStr, str_length(g_Config.m_BrFilterString));
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredStr + str_length(g_Config.m_BrFilterString), -1);
// highlight the parts that matches
const char *pFilteredStr = str_utf8_find_nocase(pName, aFilterStr);
if(pFilteredStr)
{
TextRender()->TextEx(&Cursor, pName, (int)(pFilteredStr - pName));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredStr, str_length(aFilterStr));
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredStr + str_length(aFilterStr), -1);
Printed = true;
break;
}
}
else
TextRender()->TextEx(&Cursor, pName, -1);
}
else
if(!Printed)
TextRender()->TextEx(&Cursor, pName, -1);
// clan
TextRender()->SetCursor(&Cursor, Clan.x, Clan.y + (Clan.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Clan.w;
const char *pClan = pSelectedServer->m_aClients[i].m_aClan;
Printed = false;
if(g_Config.m_BrFilterString[0])
{
// highlight the parts that matches
const char *pFilteredString = str_utf8_find_nocase(pClan, g_Config.m_BrFilterString);
if(pFilteredString)
const char *pStr = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
TextRender()->TextEx(&Cursor, pClan, (int)(pFilteredString - pClan));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredString, str_length(g_Config.m_BrFilterString));
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredString + str_length(g_Config.m_BrFilterString), -1);
// highlight the parts that matches
const char *pFilteredString = str_utf8_find_nocase(pClan, aFilterStr);
if(pFilteredString)
{
TextRender()->TextEx(&Cursor, pClan, (int)(pFilteredString - pClan));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredString, str_length(aFilterStr));
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredString + str_length(aFilterStr), -1);
Printed = true;
break;
}
}
else
TextRender()->TextEx(&Cursor, pClan, -1);
}
else
if(!Printed)
TextRender()->TextEx(&Cursor, pClan, -1);
// flag