parent
929b1ee8e9
commit
00ab36390d
10 changed files with 698 additions and 54 deletions
|
|
@ -37,6 +37,7 @@ m_menu.c
|
|||
m_textinput.c
|
||||
m_memcpy.c
|
||||
m_misc.cpp
|
||||
m_emotes.cpp
|
||||
m_perfstats.c
|
||||
m_random.c
|
||||
m_queue.c
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
#include "f_finale.h"
|
||||
#include "g_game.h"
|
||||
#include "hu_stuff.h"
|
||||
#include "m_emotes.h"
|
||||
#include "i_sound.h"
|
||||
#include "i_system.h"
|
||||
#include "i_time.h"
|
||||
|
|
@ -1799,6 +1800,8 @@ void D_SRB2Main(void)
|
|||
|
||||
S_InitMusicDefs();
|
||||
|
||||
M_InitEmotes();
|
||||
|
||||
CONS_Printf("ST_Init(): Init status bar.\n");
|
||||
ST_Init();
|
||||
CON_SetLoadingProgress(LOADED_STINIT);
|
||||
|
|
|
|||
|
|
@ -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 "p_mobj.h"
|
||||
|
|
@ -1104,6 +1105,8 @@ void D_RegisterClientCommands(void)
|
|||
// HUD
|
||||
CV_RegisterVar(&cv_itemfinder);
|
||||
|
||||
CV_RegisterVar(&cv_emotes);
|
||||
|
||||
// time attack ghost options are also saved to config
|
||||
CV_RegisterVar(&cv_ghost_besttime);
|
||||
CV_RegisterVar(&cv_ghost_bestlap);
|
||||
|
|
|
|||
115
src/hu_stuff.c
115
src/hu_stuff.c
|
|
@ -19,6 +19,7 @@
|
|||
#include "m_menu.h" // gametype_cons_t
|
||||
#include "m_cond.h" // emblems
|
||||
#include "m_misc.h" // word jumping
|
||||
#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};
|
||||
|
||||
//-------------------------------------------
|
||||
// misc vars
|
||||
|
|
@ -983,6 +987,7 @@ void HU_Ticker(void)
|
|||
return;
|
||||
|
||||
hu_tick++;
|
||||
hu_emoteanim++;
|
||||
hu_tick &= 7; // currently only to blink chat input cursor
|
||||
|
||||
if (G_PlayerInputDown(0, gc_scores, false))
|
||||
|
|
@ -1227,7 +1232,7 @@ boolean HU_Responder(event_t *ev)
|
|||
&& !G_ControlBoundToKey(0, gc_talkkey, ev->data1, false))
|
||||
return false;
|
||||
|
||||
M_TextInputHandle(&w_chat, c);
|
||||
M_TextInputHandleEmotes(&w_chat, c, emote_suggestions, MAXEMOTESUGGESTIONS);
|
||||
|
||||
if (c == KEY_ENTER)
|
||||
{
|
||||
|
|
@ -1269,6 +1274,8 @@ boolean HU_Responder(event_t *ev)
|
|||
// HEADS UP DRAWING
|
||||
//======================================================================
|
||||
|
||||
#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.
|
||||
|
|
@ -1280,6 +1287,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;
|
||||
|
|
@ -1301,7 +1310,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 || !fontv[HU_FONT].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 || !fontv[HU_FONT].font[c])
|
||||
{
|
||||
chw = spacewidth;
|
||||
lastusablespace = i;
|
||||
|
|
@ -1323,7 +1337,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
|
||||
|
|
@ -1356,6 +1369,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
|
||||
{
|
||||
|
|
@ -1380,6 +1395,11 @@ static void HU_drawMiniChat(void)
|
|||
|
||||
++j;
|
||||
}
|
||||
else if ((emote = M_VerifyEmote(msg+j, &emotelen)))
|
||||
{
|
||||
dx += EMOTEWIDTH - charwidth;
|
||||
j += emotelen;
|
||||
}
|
||||
else
|
||||
{
|
||||
j++;
|
||||
|
|
@ -1428,6 +1448,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
|
||||
{
|
||||
|
|
@ -1454,6 +1476,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
|
||||
|
|
@ -1534,6 +1566,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
|
||||
|
|
@ -1555,6 +1590,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)))
|
||||
|
|
@ -1681,7 +1725,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_menucaps.value, NULL);
|
||||
V_DrawChatCharacter(chatx+2+c, y+1, '_'|V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_menucaps.value, NULL);
|
||||
|
||||
if (w_chat.select != w_chat.cursor)
|
||||
{
|
||||
|
|
@ -1692,29 +1736,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_menucaps.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, true, 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_menucaps.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)
|
||||
{
|
||||
|
|
@ -1724,6 +1779,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
|
||||
{
|
||||
|
|
|
|||
262
src/m_emotes.cpp
Normal file
262
src/m_emotes.cpp
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
#include <map>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
|
||||
#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 = CVAR_INIT ("emotes", "On", CV_SAVE, CV_OnOff, NULL);
|
||||
|
||||
static std::map<std::string, emote_t> 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<char*>(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 <name>`
|
||||
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);
|
||||
}
|
||||
48
src/m_emotes.h
Normal file
48
src/m_emotes.h
Normal file
|
|
@ -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__
|
||||
19
src/m_menu.c
19
src/m_menu.c
|
|
@ -2229,23 +2229,6 @@ void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines)
|
|||
V_DrawFill(x+5, y+5, width*8+6, boxlines*8+6, 159);
|
||||
}
|
||||
|
||||
static void M_DrawTextInput(INT32 x, INT32 y, INT32 flags, textinput_t *input)
|
||||
{
|
||||
// draw text cursor for name
|
||||
if (skullAnimCounter < 4) // blink cursor
|
||||
V_DrawCharacter(x+V_SubStringWidth(input->buffer, input->cursor, flags|V_ALLOWLOWERCASE), y+3, '_'|(flags & ~(V_FLIP|V_PARAMMASK)), false);
|
||||
|
||||
// draw selection
|
||||
if (input->select != input->cursor)
|
||||
{
|
||||
size_t start = min(input->select, input->cursor);
|
||||
size_t end = max(input->select, input->cursor);
|
||||
size_t len = end - start;
|
||||
INT32 startx = V_SubStringWidth(input->buffer, start, flags|V_ALLOWLOWERCASE);
|
||||
V_DrawFill(x+startx, y, V_SubStringWidth(input->buffer+start, len, V_ALLOWLOWERCASE), 8, (flags & ~(V_ALPHAMASK|V_PARAMMASK))|103|V_TRANSLUCENT);
|
||||
}
|
||||
}
|
||||
|
||||
// horizontally centered text
|
||||
static void M_CentreText(INT32 y, const char *string)
|
||||
{
|
||||
|
|
@ -2353,7 +2336,7 @@ static void M_DrawRightString(menuitem_t *item, INT16 x, INT16 y, INT32 vflags,
|
|||
M_DrawTextBox(x + xofs, y + yofs, w, 1);
|
||||
V_DrawString(x + xofs + 8, y + yofs + 8, vflags|V_ALLOWLOWERCASE, cv->string);
|
||||
if (selected)
|
||||
M_DrawTextInput(x + xofs + 8, y + yofs + 8, vflags|V_ALLOWLOWERCASE, &menuinput);
|
||||
M_DrawTextInput(x + xofs + 8, y + yofs + 8, &menuinput, vflags|V_ALLOWLOWERCASE);
|
||||
}
|
||||
else if (item->status & IT_SLIDER)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#include "m_textinput.h"
|
||||
#include "m_menu.h" // MAXSTRINGLENGTH
|
||||
#include "v_video.h"
|
||||
#include "r_main.h" // renderisnewtic
|
||||
#include "i_system.h"
|
||||
#include "keys.h"
|
||||
#include "console.h"
|
||||
|
|
@ -118,11 +121,98 @@ static void M_TextInputToWordBegin(textinput_t *input, boolean move_sel)
|
|||
if (move_sel) input->select = input->cursor;
|
||||
}
|
||||
|
||||
static void M_TextInputPaste(textinput_t *input)
|
||||
{
|
||||
const char *paste = I_ClipboardPaste();
|
||||
if (input->select != input->cursor)
|
||||
M_TextInputDelSelection(input);
|
||||
if (paste != NULL)
|
||||
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;
|
||||
input->buffer_size = buffer_size;
|
||||
|
||||
input->skull = 0;
|
||||
|
||||
M_TextInputClear(input);
|
||||
}
|
||||
|
||||
|
|
@ -131,6 +221,8 @@ void M_TextInputClear(textinput_t *input)
|
|||
input->cursor = 0;
|
||||
input->select = 0;
|
||||
input->length = 0;
|
||||
|
||||
input->buffer[0] = 0;
|
||||
}
|
||||
|
||||
void M_TextInputSetString(textinput_t *input, const char *c)
|
||||
|
|
@ -140,13 +232,19 @@ 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
|
||||
|| key == KEY_LALT || key == KEY_RALT)
|
||||
|| key == KEY_LCTRL || key == KEY_RCTRL
|
||||
|| key == KEY_LALT || key == KEY_RALT)
|
||||
return false;
|
||||
|
||||
if (shiftdown && key == KEY_INS)
|
||||
{
|
||||
M_TextInputPaste(input);
|
||||
return true;
|
||||
}
|
||||
|
||||
//if ((cv_keyboardlayout.value != 3 && ctrldown) || (cv_keyboardlayout.value == 3 && ctrldown && !altdown))
|
||||
if (ctrldown)
|
||||
{
|
||||
|
|
@ -169,11 +267,7 @@ boolean M_TextInputHandle(textinput_t *input, INT32 key)
|
|||
}
|
||||
else if (key == 'v' || key == 'V')
|
||||
{
|
||||
const char *paste = I_ClipboardPaste();
|
||||
if (input->select != input->cursor)
|
||||
M_TextInputDelSelection(input);
|
||||
if (paste != NULL)
|
||||
M_TextInputAddString(input, paste);
|
||||
M_TextInputPaste(input);
|
||||
return true;
|
||||
}
|
||||
else if (key == 'w' || key == 'W')
|
||||
|
|
@ -224,16 +318,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;
|
||||
|
|
@ -280,29 +372,181 @@ boolean M_TextInputHandle(textinput_t *input, INT32 key)
|
|||
if (key >= KEY_KEYPAD7 && key <= KEY_KPADDEL)
|
||||
{
|
||||
char keypad_translation[] = {'7','8','9','-',
|
||||
'4','5','6','+',
|
||||
'1','2','3',
|
||||
'0','.'};
|
||||
'4','5','6','+',
|
||||
'1','2','3',
|
||||
'0','.'};
|
||||
|
||||
key = keypad_translation[key - KEY_KEYPAD7];
|
||||
key = keypad_translation[key - KEY_KEYPAD7];
|
||||
}
|
||||
else if (key == KEY_KPADSLASH)
|
||||
key = '/';
|
||||
|
||||
// same capslock code as hu_stuff.c's HU_responder. Check there for details.
|
||||
key = CON_ShiftChar(key);
|
||||
key = /*cv_keyboardlayout.value == 3 ? CON_ShitAndAltGrChar(key) : */CON_ShiftChar(key);
|
||||
|
||||
// enter a char into the command prompt
|
||||
if (key < 32 || key > 127)
|
||||
return false;
|
||||
|
||||
// add key to cmd line here
|
||||
if (key >= 'A' && key <= 'Z' && !(shiftdown ^ capslock)) //this is only really necessary for dedicated servers
|
||||
key = key + 'a' - 'A';
|
||||
|
||||
if (input->select != input->cursor)
|
||||
M_TextInputDelSelection(input);
|
||||
M_TextInputAddChar(input, 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)
|
||||
input->skull++;
|
||||
input->skull %= 8;
|
||||
|
||||
char nametodraw[MAXSTRINGLENGTH*2+1] = {0};
|
||||
|
||||
size_t drawstart = 0;
|
||||
size_t drawend = 0; // Only used for selection
|
||||
|
||||
INT32 skullx = x;
|
||||
|
||||
while (V_SubStringWidth(input->buffer+drawstart, input->cursor-drawstart, V_ALLOWLOWERCASE) > MAXINPUTWIDTH)
|
||||
++drawstart;
|
||||
|
||||
size_t drawlength = V_SubStringLengthToFit(input->buffer+drawstart, MAXINPUTWIDTH+8, V_ALLOWLOWERCASE)+1;
|
||||
drawend = drawstart + drawlength;
|
||||
|
||||
memcpy(nametodraw, input->buffer+drawstart, drawlength);
|
||||
|
||||
if (input->length)
|
||||
skullx += V_SubStringWidth(nametodraw, input->cursor-drawstart, V_ALLOWLOWERCASE);
|
||||
|
||||
V_DrawString(x, y, V_ALLOWLOWERCASE|flags, nametodraw);
|
||||
|
||||
// draw text cursor for name
|
||||
if (input->skull < 4) // blink cursor
|
||||
V_DrawCharacter(skullx, y+3, '_'|flags, false);
|
||||
|
||||
// draw selection
|
||||
if (input->select != input->cursor)
|
||||
{
|
||||
size_t start = min(input->select, input->cursor);
|
||||
size_t end = max(input->select, input->cursor);
|
||||
|
||||
INT32 startx = 0;
|
||||
INT32 width = 0;
|
||||
|
||||
// I couldn't figure out one formula so here's bunch of separate cases
|
||||
if (start < drawstart && end > drawend) // Selection covers whole visible portion of demo name
|
||||
{
|
||||
startx = -2;
|
||||
width = V_StringWidth(nametodraw, V_ALLOWLOWERCASE)+4;
|
||||
}
|
||||
else if (start < drawstart) // Only left side of selection is off visible part
|
||||
{
|
||||
startx = -2;
|
||||
size_t len = (end - start) - (drawstart - start);
|
||||
width = V_SubStringWidth(nametodraw, len, V_ALLOWLOWERCASE)+2;
|
||||
}
|
||||
else if (end > drawend) // Only right side of selection is off visible part
|
||||
{
|
||||
startx = V_SubStringWidth(nametodraw, start-drawstart, V_ALLOWLOWERCASE);
|
||||
width = V_StringWidth(nametodraw+(start-drawstart), V_ALLOWLOWERCASE)+2;
|
||||
}
|
||||
else // All selection is on visible part
|
||||
{
|
||||
startx = V_SubStringWidth(nametodraw, start-drawstart, V_ALLOWLOWERCASE);
|
||||
width = V_SubStringWidth(nametodraw+(start-drawstart), end-start, V_ALLOWLOWERCASE);
|
||||
}
|
||||
|
||||
V_DrawFill(x+startx, y, width, 8, 103|V_TRANSLUCENT|flags);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -10,6 +11,8 @@ typedef struct textinput_s {
|
|||
|
||||
char *buffer;
|
||||
size_t buffer_size;
|
||||
|
||||
unsigned skull;
|
||||
} textinput_t;
|
||||
|
||||
void M_TextInputInit(textinput_t *input, char *buffer, size_t buffer_size);
|
||||
|
|
@ -20,4 +23,11 @@ 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);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -38,6 +38,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"
|
||||
|
|
@ -9578,6 +9579,10 @@ UINT16 P_PartialAddWadFile(const char *wadfilename, wadcompat_t compat)
|
|||
//
|
||||
R_LoadSpriteInfoLumps(wadnum, numlumps);
|
||||
|
||||
// look for emotes
|
||||
//
|
||||
M_LoadEmotes(wadnum);
|
||||
|
||||
refreshdirmenu &= ~REFRESHDIR_GAMEDATA; // Under usual circumstances we'd wait for REFRESHDIR_ flags to disappear the next frame, but this one's a bit too dangerous for that...
|
||||
partadd_stage = 0;
|
||||
return wadnum;
|
||||
|
|
|
|||
Loading…
Reference in a new issue