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,14 +300,23 @@ void CServerBrowser::Filter()
} }
} }
if(!Filtered && g_Config.m_BrFilterString[0] != 0) if(!Filtered && g_Config.m_BrFilterString[0] != '\0')
{ {
int MatchFound = 0; int MatchFound = 0;
m_ppServerlist[i]->m_Info.m_QuickSearchHit = 0; m_ppServerlist[i]->m_Info.m_QuickSearchHit = 0;
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))))
{
if(aFilterStr[0] == '\0')
{
continue;
}
// match against server name // match against server name
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrFilterString)) if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, aFilterStr))
{ {
MatchFound = 1; MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME; m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME;
@ -316,8 +325,8 @@ void CServerBrowser::Filter()
// match against players // match against players
for(p = 0; p < minimum(m_ppServerlist[i]->m_Info.m_NumClients, (int)MAX_CLIENTS); p++) 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) || 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, g_Config.m_BrFilterString)) str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan, aFilterStr))
{ {
MatchFound = 1; MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_PLAYER; m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_PLAYER;
@ -326,40 +335,49 @@ void CServerBrowser::Filter()
} }
// match against map // match against map
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrFilterString)) if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, aFilterStr))
{ {
MatchFound = 1; MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_MAPNAME; m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_MAPNAME;
} }
}
if(!MatchFound) if(!MatchFound)
Filtered = 1; Filtered = 1;
} }
if(!Filtered && g_Config.m_BrExcludeString[0] != 0) if(!Filtered && g_Config.m_BrExcludeString[0] != '\0')
{ {
int MatchFound = 0; 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))))
{
if(aExcludeStr[0] == '\0')
{
continue;
}
// match against server name // match against server name
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrExcludeString)) if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, aExcludeStr))
{ {
MatchFound = 1; Filtered = 1;
break;
} }
// match against map // match against map
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrExcludeString)) if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, aExcludeStr))
{ {
MatchFound = 1; Filtered = 1;
break;
} }
// match against gametype // match against gametype
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrExcludeString)) if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, aExcludeStr))
{ {
MatchFound = 1;
}
if(MatchFound)
Filtered = 1; Filtered = 1;
break;
}
}
} }
} }

View file

@ -141,6 +141,8 @@ public:
NUM_NETWORKS, NUM_NETWORKS,
}; };
static constexpr const char *SEARCH_EXCLUDE_TOKEN = ";";
virtual void Refresh(int Type) = 0; virtual void Refresh(int Type) = 0;
virtual bool IsGettingServerlist() const = 0; virtual bool IsGettingServerlist() const = 0;
virtual bool IsRefreshing() 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(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(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(BrFilterString, br_filter_string, 128, "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(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(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(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") 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) else if(ID == COL_NAME)
{ {
float FontSize = 12.0f; float FontSize = 12.0f;
bool Printed = false;
if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_SERVERNAME)) if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_SERVERNAME))
{ {
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))))
{
if(aFilterStr[0] == '\0')
{
continue;
}
// highlight the parts that matches // highlight the parts that matches
const char *pStr = str_utf8_find_nocase(pItem->m_aName, g_Config.m_BrFilterString); const char *pStr = str_utf8_find_nocase(pItem->m_aName, aFilterStr);
if(pStr) 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)); 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); 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); 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); 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); 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); UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
} }
else if(ID == COL_MAP) else if(ID == COL_MAP)
@ -382,23 +393,29 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
} }
float FontSize = 12.0f; float FontSize = 12.0f;
bool Printed = false;
if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_MAPNAME)) if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_MAPNAME))
{
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))))
{ {
// highlight the parts that matches // highlight the parts that matches
const char *pStr = str_utf8_find_nocase(pItem->m_aMap, g_Config.m_BrFilterString); const char *pStr = str_utf8_find_nocase(pItem->m_aMap, aFilterStr);
if(pStr) 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)); 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); 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); 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); 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); 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); UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
} }
else if(ID == COL_PLAYERS) 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); 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; Cursor.m_LineWidth = Name.w;
const char *pName = pSelectedServer->m_aClients[i].m_aName; const char *pName = pSelectedServer->m_aClients[i].m_aName;
bool Printed = false;
if(g_Config.m_BrFilterString[0]) if(g_Config.m_BrFilterString[0])
{
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))))
{ {
// highlight the parts that matches // highlight the parts that matches
const char *pFilteredStr = str_utf8_find_nocase(pName, g_Config.m_BrFilterString); const char *pFilteredStr = str_utf8_find_nocase(pName, aFilterStr);
if(pFilteredStr) if(pFilteredStr)
{ {
TextRender()->TextEx(&Cursor, pName, (int)(pFilteredStr - pName)); TextRender()->TextEx(&Cursor, pName, (int)(pFilteredStr - pName));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f); TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredStr, str_length(g_Config.m_BrFilterString)); TextRender()->TextEx(&Cursor, pFilteredStr, str_length(aFilterStr));
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredStr + str_length(g_Config.m_BrFilterString), -1); 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); TextRender()->TextEx(&Cursor, pName, -1);
// clan // clan
TextRender()->SetCursor(&Cursor, Clan.x, Clan.y + (Clan.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); 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; Cursor.m_LineWidth = Clan.w;
const char *pClan = pSelectedServer->m_aClients[i].m_aClan; const char *pClan = pSelectedServer->m_aClients[i].m_aClan;
Printed = false;
if(g_Config.m_BrFilterString[0]) if(g_Config.m_BrFilterString[0])
{
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))))
{ {
// highlight the parts that matches // highlight the parts that matches
const char *pFilteredString = str_utf8_find_nocase(pClan, g_Config.m_BrFilterString); const char *pFilteredString = str_utf8_find_nocase(pClan, aFilterStr);
if(pFilteredString) if(pFilteredString)
{ {
TextRender()->TextEx(&Cursor, pClan, (int)(pFilteredString - pClan)); TextRender()->TextEx(&Cursor, pClan, (int)(pFilteredString - pClan));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f); TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredString, str_length(g_Config.m_BrFilterString)); TextRender()->TextEx(&Cursor, pFilteredString, str_length(aFilterStr));
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredString + str_length(g_Config.m_BrFilterString), -1); 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); TextRender()->TextEx(&Cursor, pClan, -1);
// flag // flag