4826:  Improvement of the automatic completion of player names in chat r=def- a=C0D3D3V

This should fix #659 or just improve the order of player names if you use automatic completion. 

So far it was so that first at the beginning of the name was searched for the input and then there was a second iteration through the alphabetically sorted list with names where the input does not appear at the beginning.

Now it is so that the position of the input in the name is used as an evaluation criterion to determine the position in the suggestion list. If the input is found at the beginning of the name, the score is 0 and the name is suggested first. If the input is found at position 6 in the name, the score is 6 and the name appears later in the suggestion list. If two names have the same score, the alphabetical sorting remains.


## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [x] Considered possible null pointers and out of bounds array indexing
- [x] Changed no physics that affect existing maps
- [x] 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: c0d3d3v <c0d3d3v@mag-keinen-spam.de>
This commit is contained in:
bors[bot] 2022-03-16 15:32:49 +00:00 committed by GitHub
commit c712b756ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 41 deletions

View file

@ -323,6 +323,32 @@ bool CChat::OnInput(IInput::CEvent Event)
str_truncate(m_aCompletionBuffer, sizeof(m_aCompletionBuffer), m_Input.GetString() + m_PlaceholderOffset, m_PlaceholderLength);
}
if(!m_CompletionUsed && m_aCompletionBuffer[0] != '/')
{
// Create the completion list of player names through which the player can iterate
const char *PlayerName, *FoundInput;
m_PlayerCompletionListLength = 0;
for(auto &PlayerInfo : m_pClient->m_Snap.m_paInfoByName)
{
if(PlayerInfo)
{
PlayerName = m_pClient->m_aClients[PlayerInfo->m_ClientID].m_aName;
FoundInput = str_utf8_find_nocase(PlayerName, m_aCompletionBuffer);
if(FoundInput != 0)
{
m_aPlayerCompletionList[m_PlayerCompletionListLength].ClientID = PlayerInfo->m_ClientID;
// The score for suggesting a player name is determined by the distance of the search input to the beginning of the player name
m_aPlayerCompletionList[m_PlayerCompletionListLength].Score = (int)(FoundInput - PlayerName);
m_PlayerCompletionListLength++;
}
}
}
std::stable_sort(m_aPlayerCompletionList, m_aPlayerCompletionList + m_PlayerCompletionListLength,
[](const CRateablePlayer &p1, const CRateablePlayer &p2) -> bool {
return p1.Score < p2.Score;
});
}
if(m_aCompletionBuffer[0] == '/')
{
CCommand *pCompletionCommand = 0;
@ -395,50 +421,34 @@ bool CChat::OnInput(IInput::CEvent Event)
{
// find next possible name
const char *pCompletionString = 0;
if(m_ReverseTAB && m_CompletionUsed)
m_CompletionChosen--;
else if(!m_ReverseTAB)
m_CompletionChosen++;
m_CompletionChosen = (m_CompletionChosen + 2 * MAX_CLIENTS) % (2 * MAX_CLIENTS);
m_CompletionUsed = true;
for(int i = 0; i < 2 * MAX_CLIENTS; ++i)
if(m_PlayerCompletionListLength > 0)
{
int SearchType;
int Index;
if(m_ReverseTAB)
// We do this in a loop, if a player left the game during the repeated pressing of Tab, they are skipped
CGameClient::CClientData *CompletionClientData;
for(int i = 0; i < m_PlayerCompletionListLength; ++i)
{
SearchType = ((m_CompletionChosen - i + 2 * MAX_CLIENTS) % (2 * MAX_CLIENTS)) / MAX_CLIENTS;
Index = (m_CompletionChosen - i + MAX_CLIENTS) % MAX_CLIENTS;
}
else
{
SearchType = ((m_CompletionChosen + i) % (2 * MAX_CLIENTS)) / MAX_CLIENTS;
Index = (m_CompletionChosen + i) % MAX_CLIENTS;
}
if(m_ReverseTAB && m_CompletionUsed)
{
m_CompletionChosen--;
}
else if(!m_ReverseTAB)
{
m_CompletionChosen++;
}
if(m_CompletionChosen < 0)
{
m_CompletionChosen += m_PlayerCompletionListLength;
}
m_CompletionChosen %= m_PlayerCompletionListLength;
m_CompletionUsed = true;
if(!m_pClient->m_Snap.m_paInfoByName[Index])
continue;
CompletionClientData = &m_pClient->m_aClients[m_aPlayerCompletionList[m_CompletionChosen].ClientID];
if(!CompletionClientData->m_Active)
{
continue;
}
int Index2 = m_pClient->m_Snap.m_paInfoByName[Index]->m_ClientID;
bool Found = false;
if(SearchType == 1)
{
if(str_utf8_comp_nocase_num(m_pClient->m_aClients[Index2].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer)) &&
str_utf8_find_nocase(m_pClient->m_aClients[Index2].m_aName, m_aCompletionBuffer))
Found = true;
}
else if(!str_utf8_comp_nocase_num(m_pClient->m_aClients[Index2].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer)))
Found = true;
if(Found)
{
pCompletionString = m_pClient->m_aClients[Index2].m_aName;
m_CompletionChosen = Index + SearchType * MAX_CLIENTS;
pCompletionString = CompletionClientData->m_aName;
break;
}
}

View file

@ -4,9 +4,9 @@
#define GAME_CLIENT_COMPONENTS_CHAT_H
#include <engine/shared/config.h>
#include <engine/shared/ringbuffer.h>
#include <game/client/component.h>
#include <game/client/lineinput.h>
#include <game/client/skin.h>
class CChat : public CComponent
@ -80,6 +80,13 @@ class CChat : public CComponent
char m_aCompletionBuffer[256];
int m_PlaceholderOffset;
int m_PlaceholderLength;
struct CRateablePlayer
{
int ClientID;
int Score;
};
CRateablePlayer m_aPlayerCompletionList[MAX_CLIENTS];
int m_PlayerCompletionListLength;
struct CCommand
{