diff --git a/src/Sourcefile b/src/Sourcefile index f3cc487c1..a2e9ee736 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -29,6 +29,7 @@ m_cond.c m_fixed.c m_menu.c m_textinput.c +m_emotes.cpp m_misc.c m_perfstats.c m_queue.c diff --git a/src/d_main.c b/src/d_main.c index fed32eef5..4f75ee787 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -40,6 +40,7 @@ #include "g_game.h" #include "g_input.h" #include "hu_stuff.h" +#include "m_emotes.h" #include "i_sound.h" #include "i_system.h" #include "i_time.h" @@ -2024,6 +2025,8 @@ void D_SRB2Main(void) S_InitMusicDefs(); + M_InitEmotes(); + CONS_Printf("ST_Init(): Init status bar.\n"); ST_Init(); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 8a4e12ebf..b9a11b5d1 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -21,6 +21,7 @@ #include "i_system.h" #include "g_game.h" #include "hu_stuff.h" +#include "m_emotes.h" #include "g_input.h" #include "m_menu.h" #include "r_local.h" @@ -912,6 +913,8 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_keyboardlayout); + CV_RegisterVar(&cv_emotes); + // time attack ghost options are also saved to config CV_RegisterVar(&cv_ghost_besttime); CV_RegisterVar(&cv_ghost_bestlap); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index cd4c60dd8..f052207fb 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -17,6 +17,7 @@ #include "m_menu.h" // gametype_cons_t #include "m_cond.h" // emblems +#include "m_emotes.h" #include "d_clisrv.h" @@ -94,6 +95,9 @@ static textinput_t w_chat; 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}; static huddrawlist_h luahuddrawlist_scores; @@ -1103,6 +1107,7 @@ void HU_Ticker(void) return; hu_tick++; + hu_emoteanim++; hu_tick &= 7; // currently only to blink chat input cursor if (PLAYER1INPUTDOWN(gc_scores)) @@ -1367,7 +1372,7 @@ boolean HU_Responder(event_t *ev) && ev->data1 != gamecontrol[0][gc_talkkey][1])) return false; - M_TextInputHandle(&w_chat, c); + M_TextInputHandleEmotes(&w_chat, c, emote_suggestions, MAXEMOTESUGGESTIONS); if (c == KEY_ENTER) { @@ -1407,6 +1412,8 @@ boolean HU_Responder(event_t *ev) #ifndef NONET +#define HU_DrawEmote(x, y, emote, flags) M_DrawEmote((x), (y), (emote), hu_emoteanim, (flags)) + // Precompile a wordwrapped string to any given width. // This is a muuuch better method than V_WORDWRAP. // again stolen and modified a bit from video.c, don't mind me, will need to rearrange this one day. @@ -1418,6 +1425,8 @@ static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string) size_t slen; char *newstring = Z_StrDup(string); INT32 spacewidth = (vid.width < 640) ? 8 : 4, charwidth = (vid.width < 640) ? 8 : 4; + emote_t *emote = NULL; + int emotelen = 0; slen = strlen(string); x = 0; @@ -1439,7 +1448,12 @@ static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string) c = toupper(c); c -= HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + if ((emote = M_VerifyEmote(string+i, &emotelen))) + { + chw = EMOTEWIDTH; + i += emotelen-1; // Will be incremented on next loop iteration, hence -1 + } + else if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) { chw = spacewidth; lastusablespace = i; @@ -1461,7 +1475,6 @@ static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string) return newstring; } - // 30/7/18: chaty is now the distance at which the lowest point of the chat will be drawn if that makes any sense. INT16 chatx = 13, chaty = 169; // let's use this as our coordinates, shh @@ -1494,6 +1507,8 @@ static void HU_drawMiniChat(void) char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i-1]); size_t j = 0; INT32 linescount = 0; + emote_t *emote = NULL; + int emotelen = 0; while(msg[j]) // iterate through msg { @@ -1518,6 +1533,11 @@ static void HU_drawMiniChat(void) ++j; } + else if ((emote = M_VerifyEmote(msg+j, &emotelen))) + { + dx += EMOTEWIDTH - charwidth; + j += emotelen; + } else { j++; @@ -1563,6 +1583,8 @@ static void HU_drawMiniChat(void) size_t j = 0; char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]); // get the current message, and word wrap it. UINT8 *colormap = NULL; + emote_t *emote = NULL; + int emotelen = 0; while(msg[j]) // iterate through msg { @@ -1589,6 +1611,16 @@ static void HU_drawMiniChat(void) ++j; } + else if ((emote = M_VerifyEmote(msg+j, &emotelen))) + { + if (cv_chatbacktint.value) // on request of wolfy + V_DrawFillConsoleMap(x + dx + 2, y+dy, EMOTEWIDTH, charheight, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT); + + HU_DrawEmote(x+dx+2, y+dy, emote, V_SNAPTOBOTTOM|V_SNAPTOLEFT|transflag); + dx += EMOTEWIDTH - charwidth; + + j += emotelen; + } else { if (cv_chatbacktint.value) // on request of wolfy @@ -1666,6 +1698,9 @@ static void HU_drawChatLog(INT32 offset) INT32 j = 0; char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]); // get the current message, and word wrap it. UINT8 *colormap = NULL; + emote_t *emote = NULL; + int emotelen = 0; + while(msg[j]) // iterate through msg { if (msg[j] < HU_FONTSTART) // don't draw @@ -1687,6 +1722,15 @@ static void HU_drawChatLog(INT32 offset) ++j; } + else if ((emote = M_VerifyEmote(msg+j, &emotelen))) + { + if ((y+dy+2 >= chat_topy) && (y+dy < (chat_bottomy))) + { + HU_DrawEmote(x+dx+2, y+dy, emote, V_SNAPTOBOTTOM|V_SNAPTOLEFT); + dx += EMOTEWIDTH - charwidth; + } + j += emotelen; + } else { if ((y+dy+2 >= chat_topy) && (y+dy < (chat_bottomy))) @@ -1812,7 +1856,7 @@ static void HU_DrawChat(void) typelines = 1; if ((w_chat.cursor == 0 || w_chat.length == 0) && hu_tick < 4) - V_DrawChatCharacter(chatx+2+c+charwidth*w_chat.cursor, y+1, '_'|V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL); + V_DrawChatCharacter(chatx+2+c, y+1, '_'|V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL); if (w_chat.select != w_chat.cursor) { @@ -1823,30 +1867,40 @@ static void HU_DrawChat(void) while (w_chat_buf[i]) { boolean skippedline = false; - if (w_chat.cursor == (i+1)) + emote_t *emote = NULL; + int emotelen = 0; + int drawwidth = charwidth; // if we've drawn emote, selection needs to account for that + + if ((emote = M_VerifyEmote(w_chat_buf+i, &emotelen))) + { + HU_DrawEmote(chatx + c + 2, y-1, emote, V_SNAPTOBOTTOM|V_SNAPTOLEFT|t); + c += EMOTEWIDTH - charwidth; + i += emotelen-1; + drawwidth = EMOTEWIDTH; + } + else if (w_chat_buf[i] >= HU_FONTSTART) //Hurdler: isn't it better like that? + V_DrawChatCharacter(chatx + c + 2, y, w_chat_buf[i] | V_SNAPTOBOTTOM|V_SNAPTOLEFT | t, !cv_allcaps.value, NULL); + + // Draw selection + if (i >= select_start && i < select_end) + V_DrawFill(chatx + c + 2 - (drawwidth-charwidth), y-1, drawwidth, charheight, 103|V_TRANSLUCENT|V_SNAPTOBOTTOM|V_SNAPTOLEFT|t); + + ++i; + + if (w_chat.cursor == i) { INT32 cursorx = (c+charwidth < boxw-charwidth) ? (chatx + 2 + c+charwidth) : (chatx+1); // we may have to go down. INT32 cursory = (cursorx != chatx+1) ? (y) : (y+charheight); if (hu_tick < 4) V_DrawChatCharacter(cursorx, cursory+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL); - if (cursorx == chatx+1 && saylen == i) // a weirdo hack + if (cursorx == chatx+1 && saylen == i-1) // a weirdo hack { typelines += 1; skippedline = true; } } - //Hurdler: isn't it better like that? - if (w_chat_buf[i] >= HU_FONTSTART) - V_DrawChatCharacter(chatx + c + 2, y, w_chat_buf[i] | V_SNAPTOBOTTOM|V_SNAPTOLEFT | t, !cv_allcaps.value, NULL); - - // Draw selection - if (i >= select_start && i < select_end) - V_DrawFill(chatx + c + 2, y-1, charwidth, charheight, 103|V_TRANSLUCENT|V_SNAPTOBOTTOM|V_SNAPTOLEFT|t); - - ++i; - c += charwidth; if (c > boxw-(charwidth*2) && !skippedline) { @@ -1856,6 +1910,36 @@ static void HU_DrawChat(void) } } + if (emote_suggestions[0]) + { + // 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) + { + longest_suggestion_length = max(longest_suggestion_length, strlen(emote_suggestions[i]->name)); + } + +#ifdef NETSPLITSCREEN + if (splitscreen) + { + suggesty -= BASEVIDHEIGHT/2; + if (splitscreen > 1) + suggesty += 16; + } + else +#endif + suggesty -= (cv_kartspeedometer.value ? 16 : 0); + + for (i = 0; i < MAXEMOTESUGGESTIONS && emote_suggestions[i]; ++i) + { + 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); + } + } + // handle /pm list. It's messy, horrible and I don't care. if (strnicmp(w_chat_buf, "/pm", 3) == 0 && vid.width >= 400 && !teamtalk) // 320x200 unsupported kthxbai { diff --git a/src/m_emotes.cpp b/src/m_emotes.cpp new file mode 100644 index 000000000..facc7fb54 --- /dev/null +++ b/src/m_emotes.cpp @@ -0,0 +1,262 @@ +#include +#include +#include +#include + +#include "m_emotes.h" + +extern "C" { +#include "w_wad.h" +#include "z_zone.h" +#include "hu_stuff.h" +#include "v_video.h" +} + +consvar_t cv_emotes = {"emotes", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};; + +static std::map emotes; + +// TODO - Would be really nice to generalize soc-like file parsing and not reimplement it 100 times :))))) +void M_LoadEmotes(UINT16 wadnum) +{ + UINT16 lumpnum = W_CheckNumForNamePwad("EMOTES", wadnum, 0); + + if (lumpnum == INT16_MAX) + return; + + char *lump = static_cast(W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE)); + size_t size = W_LumpLengthPwad(wadnum, lumpnum); + + std::istringstream file(std::string(lump, size)); + std::string line; + + emote_t *emote = nullptr; + std::string emote_name; + + // Just smol lambda to remove all the trailing chars we might not want + auto trim = [](std::string &str) { + str.erase(0, str.find_first_not_of(" \t\n\r\f\v")); + str.erase(str.find_last_not_of(" \t\n\r\f\v")+1); + }; + + int numemotes = 0; + int linenum = 0; + + while (std::getline(file, line)) + { + ++linenum; + + trim(line); + + if (line.empty()) + continue; + + size_t split = line.find('='); + + // Didn't find the =, means we're about to parse `Emote ` + if (split == line.npos) + { + split = line.find(' '); + + if (split == line.npos) + { + CONS_Alert(CONS_WARNING, "EMOTES: Unrecognized line. (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum); + continue; + } + + if (strnicmp(line.c_str(), "emote", 5)) + { + CONS_Alert(CONS_WARNING, "EMOTES: 'Emote' expected. (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum); + continue; + } + + emote_name = line.substr(split+1); + + if (emote_name.find_first_of(" \t\n\r\f\v:") != emote_name.npos) + { + CONS_Alert(CONS_WARNING, "EMOTES: Emote name cannot contain spaces or ':' symbols. (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum); + continue; + } + + if (emote_name.size() > MAXEMOTENAME) + { + CONS_Alert(CONS_WARNING, "EMOTES: Emote name is too long, truncating. (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum); + emote_name = emote_name.substr(0, MAXEMOTENAME); + } + + emote = &emotes[emote_name]; + strlcpy(emote->name, emote_name.c_str(), MAXEMOTENAME); + + emote->timeperframe = 1; + emote->numframes = 0; + std::memset(emote->frames, 0, sizeof(emote->frames)); + + ++numemotes; + } + else if (emote) + { + std::string field = line.substr(0, split); + std::string value = line.substr(split+1); + + trim(field); + trim(value); + + if (stricmp(field.c_str(), "frames") == 0) + { + UINT8 numframes = 0; + + auto copy_frame = [&] { + if (numframes == MAXEMOTEFRAMES) + { + CONS_Alert(CONS_WARNING, "EMOTES: Too many frames. (file %s, line %d)", wadfiles[wadnum]->filename, linenum); + return false; + } + + size_t list_split = value.find(','); + std::string framelumpname = value.substr(0, list_split); + trim(framelumpname); + + if (list_split == value.npos) + value = ""; + else + value = value.substr(list_split+1, value.npos); + + // End of list of frames + if (framelumpname.size() == 0) + return false; + + if (framelumpname.size() > 8) + { + CONS_Alert(CONS_WARNING, "EMOTES: Frame %d name is too long. (file %s, line %d)", numframes, wadfiles[wadnum]->filename, linenum); + framelumpname = framelumpname.substr(0, 8); + } + + std::strncpy(emote->frames[numframes], framelumpname.c_str(), framelumpname.size()+1); + + ++numframes; + + return true; + }; + + while (copy_frame()) {} + + if (numframes == 0) + { + CONS_Alert(CONS_WARNING, "EMOTES: Expected list of frames. (file %s, line %d)", wadfiles[wadnum]->filename, linenum); + } + + emote->numframes = numframes; + } + else if (stricmp(field.c_str(), "timeperframe") == 0) + { + emote->timeperframe = std::stoi(value); + + if (emote->timeperframe <= 0) + { + emote->timeperframe = 1; + CONS_Alert(CONS_WARNING, "EMOTES: Bad value for 'timeperframe'. (file %s, line %d)", wadfiles[wadnum]->filename, linenum); + } + } + else + { + CONS_Alert(CONS_WARNING, "EMOTES: Unrecognized field '%s'. (file %s, line %d)", field.c_str(), wadfiles[wadnum]->filename, linenum); + } + } + else + { + CONS_Alert(CONS_WARNING, "EMOTES: Unrecognized line. (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum); + } + } + + CONS_Printf("Added %d emotes\n", numemotes); +} + +void M_InitEmotes(void) +{ + UINT16 i; + for (i = 0; i < numwadfiles; i++) + M_LoadEmotes(i); +} + +emote_t *M_FindEmote(const char *name, int len, int skip) +{ + if (!cv_emotes.value) + return nullptr; + + char query[MAXEMOTENAME+1] = {0}; + std::strncpy(query, name, std::min(len, MAXEMOTENAME)); + + for (auto &pair: emotes) + { + if (pair.first.rfind(query, 0, std::min(len, MAXEMOTENAME)) != 0) + continue; + + if (skip > 0) + { + --skip; + continue; + } + + return &pair.second; + } + + return nullptr; +} + +emote_t *M_VerifyEmote(const char *name, int *emotelen) +{ + if (*name != ':') + return nullptr; + + if (!cv_emotes.value) + return nullptr; + + const char *p = name+1; // skip ':' + emote_t *match = nullptr; + + while (*p && (p - name) < MAXEMOTENAME) + { + if (*p == ':') + { + std::string checkname = std::string(name+1, (p-name)-1); + auto it = emotes.find(checkname); + + if (it != emotes.end()) + match = &it->second; + + break; + } + + ++p; + } + + if (match && emotelen) + *emotelen = p-name+1; + + return match; +} + +void M_DrawEmote(INT32 x, INT32 y, emote_t *emote, tic_t anim, INT32 flags) +{ + if (emote->numframes == 0) + return; + + const char *lumpname = emote->frames[(anim/emote->timeperframe) % emote->numframes]; + patch_t *emotepatch = (patch_t*)W_CachePatchName(lumpname, PU_CACHE); + + const int CHARHEIGHT = 6; + + fixed_t scale = FRACUNIT; + + x *= FRACUNIT; + y *= FRACUNIT; + + if (emotepatch->width > EMOTEWIDTH) + scale = (FRACUNIT/emotepatch->width)*EMOTEWIDTH; + else if (emotepatch->width < EMOTEWIDTH) + x += (EMOTEWIDTH-emotepatch->width)*FRACUNIT/2; + + y -= (scale*emotepatch->height-CHARHEIGHT*FRACUNIT)/2; + + V_DrawFixedPatch(x, y, scale, flags, emotepatch, NULL); +} diff --git a/src/m_emotes.h b/src/m_emotes.h new file mode 100644 index 000000000..783c6ec17 --- /dev/null +++ b/src/m_emotes.h @@ -0,0 +1,48 @@ +#ifndef __M_EMOTES__ +#define __M_EMOTES__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "doomdef.h" +#include "command.h" + +extern consvar_t cv_emotes; + +#define MAXEMOTENAME 32 +#define MAXEMOTEFRAMES 32 +#define EMOTEWIDTH 6 + +typedef struct emote_s { + char name[MAXEMOTENAME+1]; + char frames[MAXEMOTEFRAMES][9]; + UINT8 numframes; + tic_t timeperframe; +} emote_t; + +// Reads and adds emotes from EMOTES lump, with syntax similar to soc +void M_LoadEmotes(UINT16 wadnum); + +// Load emotes from all base wads +void M_InitEmotes(void); + +// Finds first matching emote, ignoring first few matches +emote_t *M_FindEmote(const char *name, int len, int skips); + +// If first character is not ':', instantly returns null +// Starts from ':' character, goes until it finds ':' or end of string +// If end of string is found, or closing ':' is found but resulting emote name doesn't exist, +// it returns null, otherwise it returns emote and stores into *emotelen how much characters +// needs to be skipped to get past emote name +emote_t *M_VerifyEmote(const char *name, int *emotelen); + +// Draw the emote, anim should be some kind of timer ticking every game tic +// for animated emotes +void M_DrawEmote(INT32 x, INT32 y, emote_t *emote, tic_t anim, INT32 flags); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __M_EMOTES__ diff --git a/src/m_textinput.c b/src/m_textinput.c index dfe9f5a64..353c8122b 100644 --- a/src/m_textinput.c +++ b/src/m_textinput.c @@ -130,6 +130,82 @@ static void M_TextInputPaste(textinput_t *input) M_TextInputAddString(input, paste); } +static void M_TextInputLeft(textinput_t *input, boolean emotes) +{ + // Just do it simple way if possible + if (!emotes || input->cursor < 2 || input->buffer[input->cursor-1] != ':') + { + if (input->cursor != 0) + --input->cursor; + return; + } + + // Otherwise we need to check if we have to skip emote + size_t start = input->cursor-2; + + while (input->buffer[start] != ':') + { + // We reached start of string and didn't find ':' symbol, thats definitely not an emote + // so just do it simple way + if (start == 0) + { + M_TextInputLeft(input, false); + return; + } + + --start; + } + + int emotelen = 0; + if (M_VerifyEmote(input->buffer+start, &emotelen)) + input->cursor -= emotelen; // also skip the : + else + M_TextInputLeft(input, false); // Not a valid emote, do the simple thing +} + +static void M_TextInputRight(textinput_t *input, boolean emotes) +{ + // Just do it simple way if possible + if (!emotes || input->cursor == input->length || input->buffer[input->cursor] != ':') + { + if (input->cursor < input->length) + ++input->cursor; + return; + } + + int emotelen = 0; + if (M_VerifyEmote(input->buffer+input->cursor, &emotelen)) + input->cursor += emotelen; // Also skip the : + else + M_TextInputRight(input, false); // Not a valid emote, do the simple thing +} + +// Check if we're inside a valid emote +static boolean M_TextInputCheckEmote(textinput_t *input) +{ + int start = input->cursor; + + // Definitely not an emote + if (start == 0) + return false; + + // Might be closing :, need to skip it just in case + if (input->buffer[start] == ':') + --start; + + while (input->buffer[start] != ':') + { + // No ':' found, definitely not inside emote + if (start == 0) + return false; + + --start; + } + + // Found what may be emote start, now we can check + return M_VerifyEmote(input->buffer+start, NULL) != NULL; +} + void M_TextInputInit(textinput_t *input, char *buffer, size_t buffer_size) { input->buffer = buffer; @@ -156,7 +232,7 @@ void M_TextInputSetString(textinput_t *input, const char *c) input->cursor = input->select = input->length = strlen(c); } -boolean M_TextInputHandle(textinput_t *input, INT32 key) +static boolean M_TextInputHandleBase(textinput_t *input, INT32 key, boolean emotes) { if (key == KEY_LSHIFT || key == KEY_RSHIFT || key == KEY_LCTRL || key == KEY_RCTRL @@ -241,16 +317,14 @@ boolean M_TextInputHandle(textinput_t *input, INT32 key) if (key == KEY_LEFTARROW) { - if (input->cursor != 0) - --input->cursor; + M_TextInputLeft(input, emotes); if (!shiftdown) input->select = input->cursor; return true; } else if (key == KEY_RIGHTARROW) { - if (input->cursor < input->length) - ++input->cursor; + M_TextInputRight(input, emotes); if (!shiftdown) input->select = input->cursor; return true; @@ -320,6 +394,96 @@ boolean M_TextInputHandle(textinput_t *input, INT32 key) return true; } +// Just an alias, more or less +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) +{ + 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 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 + if (isspace(input->buffer[i])) + break; + + // Found a :, try suggest emotes + 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; + } + } + + // If we suggest emotes, try autocomplete + if (key == '\t' && suggestions[0]) + { + int pos = (input->cursor-emotestart); + boolean autocomplete = true; + + while (autocomplete) + { + // 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) + M_TextInputAddChar(input, c); + } + + // This was the only suggestion, finish autocomplete with a ':' and clear suggestions + if (maxsuggestions == 1 || !suggestions[1]) + { + M_TextInputAddChar(input, ':'); + suggestions[0] = NULL; + } + } + + return ret; +} + void M_DrawTextInputScroll(INT32 x, INT32 y, textinput_t *input, INT32 flags, INT32 MAXINPUTWIDTH) { if (renderisnewtic) diff --git a/src/m_textinput.h b/src/m_textinput.h index 649288ec4..1cc1c5005 100644 --- a/src/m_textinput.h +++ b/src/m_textinput.h @@ -2,6 +2,7 @@ #define __M_TEXTINPUT__ #include "doomtype.h" +#include "m_emotes.h" // M_VerifyEmote, M_FindEmote typedef struct textinput_s { size_t cursor; @@ -22,6 +23,10 @@ void M_TextInputSetString(textinput_t *input, const char *c); 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); + #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); diff --git a/src/p_setup.c b/src/p_setup.c index 118dbfc74..a14580f56 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -34,6 +34,7 @@ #include "r_fps.h" // R_ResetViewInterpolation in level load #include "s_sound.h" +#include "m_emotes.h" #include "st_stuff.h" #include "w_wad.h" #include "z_zone.h" @@ -3337,6 +3338,11 @@ UINT16 P_PartialAddWadFile(const char *wadfilename, boolean local) // S_LoadMusicDefs(wadnum); + // + // look for emotes + // + M_LoadEmotes(wadnum); + // TODO: Experimental SPRTINFO support, test first R_LoadSpriteInfoLumps(wadnum, wadfiles[wadnum]->numlumps);