313 lines
7.2 KiB
C++
313 lines
7.2 KiB
C++
#include <map>
|
|
#include <list>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
#include "m_emotes.h"
|
|
#include "i_time.h" // I_GetTime
|
|
|
|
extern "C" {
|
|
#include "w_wad.h"
|
|
#include "z_zone.h"
|
|
#include "hu_stuff.h"
|
|
#include "v_video.h"
|
|
#include "command.h"
|
|
}
|
|
|
|
consvar_t cv_emotes = CVAR_INIT ("emotes", "On", CV_SAVE, CV_OnOff, NULL);
|
|
static void Command_ListEmotes_f(void);
|
|
|
|
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.resize(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 (fasticmp(field.c_str(), "frames"))
|
|
{
|
|
UINT8 numframes = 0;
|
|
|
|
auto copy_frame = [&] {
|
|
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;
|
|
|
|
// Only print this warning if we actually got new frame
|
|
if (numframes == MAXEMOTEFRAMES)
|
|
{
|
|
CONS_Alert(CONS_WARNING, "EMOTES: Too many frames. (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum);
|
|
return false;
|
|
}
|
|
|
|
if (framelumpname.size() > 8)
|
|
{
|
|
CONS_Alert(CONS_WARNING, "EMOTES: Frame %d name is too long. (file %s, line %d)\n", numframes, wadfiles[wadnum]->filename, linenum);
|
|
framelumpname.resize(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)\n", wadfiles[wadnum]->filename, linenum);
|
|
}
|
|
|
|
emote->numframes = numframes;
|
|
}
|
|
else if (fasticmp(field.c_str(), "timeperframe"))
|
|
{
|
|
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)\n", wadfiles[wadnum]->filename, linenum);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CONS_Alert(CONS_WARNING, "EMOTES: Unrecognized field '%s'. (file %s, line %d)\n", field.c_str(), wadfiles[wadnum]->filename, linenum);
|
|
}
|
|
|
|
// Cache frames for speedy use later.
|
|
for (size_t i = 0; i < MAXEMOTEFRAMES; i++)
|
|
{
|
|
emote->frames_cached[i] = (patch_t *)W_CachePatchName(emote->frames[i], PU_CACHE);
|
|
}
|
|
|
|
}
|
|
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);
|
|
|
|
COM_AddCommand("listemotes", Command_ListEmotes_f);
|
|
}
|
|
|
|
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 (strcasestr(pair.first.c_str(), query) == NULL)
|
|
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_DrawScaledEmote(fixed_t x, fixed_t y, fixed_t scale, emote_t *emote, INT32 flags)
|
|
{
|
|
if (emote->numframes == 0)
|
|
return;
|
|
|
|
//const char *lumpname = emote->frames[(I_GetTime()/emote->timeperframe) % emote->numframes];
|
|
INT32 emoteframe = (I_GetTime()/emote->timeperframe) % emote->numframes;
|
|
patch_t *emotepatch = emote->frames_cached[emoteframe];
|
|
|
|
if (!emotepatch)
|
|
{
|
|
CONS_Printf("Emote Patch is NULL?\n");
|
|
return;
|
|
}
|
|
|
|
const int CHARHEIGHT = 6;
|
|
|
|
if (emotepatch->width > EMOTEWIDTH)
|
|
scale = FixedMul(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);
|
|
}
|
|
|
|
static void Command_ListEmotes_f(void)
|
|
{
|
|
const int EMOTES_PER_PAGE = 24;
|
|
const int NUMPAGES = std::max<int>(emotes.size()/EMOTES_PER_PAGE, 1);
|
|
int page = 1;
|
|
|
|
if (COM_Argc() > 1)
|
|
page = std::atoi(COM_Argv(1));
|
|
|
|
if (page <= 0 || page > NUMPAGES)
|
|
{
|
|
if (NUMPAGES > 1)
|
|
CONS_Printf("Enter page number between 1 and %d\n", NUMPAGES);
|
|
else
|
|
CONS_Printf("Bad page number, try using this command without arguments\n");
|
|
return;
|
|
}
|
|
|
|
int i = 0;
|
|
for (auto &pair: emotes)
|
|
{
|
|
++i;
|
|
|
|
if (i <= (page-1)*EMOTES_PER_PAGE)
|
|
continue;
|
|
if (i > page*EMOTES_PER_PAGE)
|
|
break;
|
|
|
|
CONS_Printf("%s - :%s:\n", pair.first.c_str(), pair.first.c_str());
|
|
}
|
|
|
|
CONS_Printf("Page %d/%d\n", page, NUMPAGES);
|
|
}
|