emotes: change autocompletion behavior to work like console

This commit is contained in:
Indev 2025-11-23 01:31:23 +03:00 committed by NepDisk
parent 3bc79a8f93
commit 4fbe2c31fe
3 changed files with 121 additions and 75 deletions

View file

@ -96,8 +96,7 @@ static boolean headsupactive = false;
boolean hu_showscores; // draw rankings
static char hu_tick;
static tic_t hu_emoteanim = 0;
#define MAXEMOTESUGGESTIONS 8
static emote_t *emote_suggestions[MAXEMOTESUGGESTIONS] = {0};
emote_autocomplete_t emote_autocomplete = {0};
//-------------------------------------------
// misc vars
@ -1194,6 +1193,7 @@ boolean HU_Responder(event_t *ev)
chat_on = true;
w_chat_buf[0] = 0;
M_TextInputInit(&w_chat, w_chat_buf, sizeof w_chat_buf);
memset(&emote_autocomplete, 0, sizeof(emote_autocomplete));
teamtalk = false;
chat_scrollmedown = true;
typelines = 1;
@ -1205,6 +1205,7 @@ boolean HU_Responder(event_t *ev)
chat_on = true;
w_chat_buf[0] = 0;
M_TextInputInit(&w_chat, w_chat_buf, sizeof w_chat_buf);
memset(&emote_autocomplete, 0, sizeof(emote_autocomplete));
teamtalk = G_GametypeHasTeams(); // Don't teamtalk if we don't have teams.
chat_scrollmedown = true;
typelines = 1;
@ -1228,7 +1229,7 @@ boolean HU_Responder(event_t *ev)
&& !G_ControlBoundToKey(0, gc_talkkey, ev->data1, false))
return false;
M_TextInputHandleEmotes(&w_chat, c, emote_suggestions, MAXEMOTESUGGESTIONS);
M_TextInputHandleEmotes(&w_chat, c, &emote_autocomplete);
if (c == KEY_ENTER)
{
@ -1775,17 +1776,37 @@ static void HU_DrawChat(void)
}
}
if (emote_suggestions[0])
if (emote_autocomplete.emotestart != -1 && (emote_autocomplete.complete[0] || (w_chat.cursor - emote_autocomplete.emotestart) > 0))
{
emote_t *suggest;
int skip = 0;
const char *complete;
int complete_len;
if (emote_autocomplete.complete[0])
{
complete = emote_autocomplete.complete;
complete_len = strlen(complete);
}
else
{
complete = &w_chat_buf[emote_autocomplete.emotestart];
complete_len = w_chat.cursor - emote_autocomplete.emotestart;
}
// A bit of copy-paste from /pm code :p
INT32 suggesty = chaty - charheight - 1;
size_t longest_suggestion_length = 0;
for (i = 0; i < MAXEMOTESUGGESTIONS && emote_suggestions[i]; ++i)
while ((suggest = M_FindEmote(complete, complete_len, skip)) != NULL)
{
longest_suggestion_length = max(longest_suggestion_length, strlen(emote_suggestions[i]->name));
longest_suggestion_length = max(longest_suggestion_length, strlen(suggest->name));
++skip;
}
skip = 0;
#ifdef NETSPLITSCREEN
if (splitscreen)
{
@ -1797,11 +1818,19 @@ static void HU_DrawChat(void)
#endif
suggesty -= (cv_kartspeedometer.value ? 16 : 0);
for (i = 0; i < MAXEMOTESUGGESTIONS && emote_suggestions[i]; ++i)
while ((suggest = M_FindEmote(complete, complete_len, skip)) != NULL)
{
V_DrawFillConsoleMap(chatx + boxw + 2, suggesty - (7*i), (longest_suggestion_length+2)*4 + EMOTEWIDTH, 6, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT);
V_DrawSmallString(chatx + boxw + 4 + EMOTEWIDTH, suggesty - (7*i), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, va(":%s:", emote_suggestions[i]->name));
HU_DrawEmote(chatx + boxw + 2, suggesty - i*7, emote_suggestions[i], V_SNAPTOBOTTOM|V_SNAPTOLEFT);
V_DrawFillConsoleMap(chatx + boxw + 2, suggesty - (7*skip), (longest_suggestion_length+2)*4 + EMOTEWIDTH, 6, V_SNAPTOBOTTOM|V_SNAPTOLEFT);
// Highlight currently suggested emote
int hlflag = 0;
if (skip == emote_autocomplete.skip && emote_autocomplete.complete[0])
hlflag = V_YELLOWMAP;
V_DrawSmallString(chatx + boxw + 4 + EMOTEWIDTH, suggesty - (7*skip), hlflag|V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, va(":%s:", suggest->name));
HU_DrawEmote(chatx + boxw + 2, suggesty - skip*7, suggest, V_SNAPTOBOTTOM|V_SNAPTOLEFT);
++skip;
}
}

View file

@ -401,21 +401,13 @@ boolean M_TextInputHandle(textinput_t *input, INT32 key)
return M_TextInputHandleBase(input, key, false);
}
boolean M_TextInputHandleEmotes(textinput_t *input, INT32 key, emote_t *suggestions[], int maxsuggestions)
static int M_TextInputEmoteStart(textinput_t *input)
{
boolean ret = M_TextInputHandleBase(input, key, true);
if (input->cursor == 0)
return -1;
// After this handled key we ended up inside an emote, lets fix that
if (M_TextInputCheckEmote(input))
M_TextInputToWordEnd(input, !shiftdown);
int emotestart = -1;
// Always clear the first entry
suggestions[0] = NULL;
// For autocomplete
int emotestart = 0;
// Try find suggestions for emote names
for (int i = input->cursor-1; i >= 0 && input->cursor-i <= MAXEMOTENAME; --i)
{
// Space can't be part of emote name
@ -426,71 +418,90 @@ boolean M_TextInputHandleEmotes(textinput_t *input, INT32 key, emote_t *suggesti
if (input->buffer[i] == ':')
{
emotestart = i+1;
// ...But only if we typed at least something
if ((int)input->cursor-(i-1) < 3)
break;
for (int skip = 0; skip < maxsuggestions; ++skip)
{
suggestions[skip] = M_FindEmote(input->buffer+i+1, input->cursor-i-1, skip);
// No more suggestions
if (!suggestions[skip])
break;
}
// In any case, we found what we wanted, can exit the loop now
break;
}
}
const boolean single_suggestion = suggestions[0] && (maxsuggestions == 1 || !suggestions[1]);
return emotestart;
}
// If we suggest emotes, try autocomplete
if ((key == '\t' && suggestions[0]) || (key == KEY_ENTER && single_suggestion))
static void M_TextInputCompleteEmote(textinput_t *input, emote_autocomplete_t *autocomplete)
{
if (input->cursor == 0)
return;
// If we're in "autocomplete" state, delete ':' for latest autocompleted emote
if (autocomplete->complete[0])
{
int pos = 0;
const int insertpos = (input->cursor-emotestart); // Only insert after that index (including it)
boolean autocomplete = true;
while (autocomplete)
if (input->buffer[input->cursor-1] == ':')
{
// Check if current character matches for all suggestions
char c = suggestions[0]->name[pos];
// End of string reached
if (!c)
break;
for (int i = 1; i < maxsuggestions && suggestions[i]; ++i)
{
if (suggestions[i]->name[pos] != c)
{
autocomplete = false;
break;
}
}
++pos;
if (autocomplete && pos > insertpos)
M_TextInputAddChar(input, c);
M_TextInputDelChar(input);
}
// This was the only suggestion, finish autocomplete with a ':' and clear suggestions
if (single_suggestion)
autocomplete->skip++;
}
else
{
// Try to find emote to autocomplete
int emotestart = autocomplete->emotestart;
// No emote - no autocomplete
if (emotestart == -1 || (size_t)emotestart == input->cursor)
return;
strlcpy(autocomplete->complete, &input->buffer[emotestart], input->cursor-emotestart+1);
}
emote_t *completed = M_FindEmote(autocomplete->complete, strlen(autocomplete->complete), autocomplete->skip);
// We hit end of list of suggested emotes
if (completed == NULL)
{
autocomplete->skip = 0;
completed = M_FindEmote(autocomplete->complete, strlen(autocomplete->complete), autocomplete->skip);
// NULL again, list was empty to begin with
if (completed == NULL)
{
memcpy(&input->buffer[emotestart], suggestions[0]->name, strlen(suggestions[0]->name));
M_TextInputAddChar(input, ':');
suggestions[0] = NULL;
}
else if (pos > emotestart)
{
// Otherwise make sure beginning of emote matches
memcpy(&input->buffer[emotestart], suggestions[0]->name, pos-emotestart);
// Clear autocompletion state
autocomplete->complete[0] = 0;
return;
}
}
// Erase previously typed emote. Not really efficent but too lazy to count chars and do M_TextInputDel()
while (input->buffer[input->cursor-1] != ':')
{
M_TextInputDelChar(input);
}
M_TextInputAddString(input, completed->name);
M_TextInputAddChar(input, ':');
}
boolean M_TextInputHandleEmotes(textinput_t *input, INT32 key, emote_autocomplete_t *autocomplete)
{
boolean ret = M_TextInputHandleBase(input, key, true);
// After this handled key we ended up inside an emote, lets fix that
if (M_TextInputCheckEmote(input))
M_TextInputToWordEnd(input, !shiftdown);
// Always update it
autocomplete->emotestart = M_TextInputEmoteStart(input);
if (key == '\t' || (autocomplete->complete[0] == 0 && key == KEY_ENTER))
{
M_TextInputCompleteEmote(input, autocomplete);
}
else
{
// Reset state just in case
autocomplete->complete[0] = 0;
autocomplete->skip = 0;
}
return ret;
}

View file

@ -15,6 +15,12 @@ typedef struct textinput_s {
unsigned skull;
} textinput_t;
typedef struct emote_autocomplete_s {
char complete[MAXEMOTENAME+1];
int skip;
int emotestart;
} emote_autocomplete_t;
void M_TextInputInit(textinput_t *input, char *buffer, size_t buffer_size);
void M_TextInputClear(textinput_t *input);
@ -25,7 +31,7 @@ boolean M_TextInputHandle(textinput_t *input, INT32 key);
// Does everything same as M_TextInputHandle, but also handles emotes in input string
// If currently typing something that looks like an existing emote, return suggestions for it
boolean M_TextInputHandleEmotes(textinput_t *input, INT32 key, emote_t *suggestions[], int maxsuggestions);
boolean M_TextInputHandleEmotes(textinput_t *input, INT32 key, emote_autocomplete_t *autocomplete);
#define M_DrawTextInput(x, y, input, flags) M_DrawTextInputScroll((x), (y), (input), (flags), ((input)->buffer_size-1)*8)
void M_DrawTextInputScroll(INT32 x, INT32 y, textinput_t *input, INT32 flags, INT32 MAXINPUTWIDTH);