add animated inlined images for console text

Images are stored in packages/misc/igraph/*.png and loaded during game
start. Filenames consist of the image shorthand (mnemonic) and optional
frame times, separated by "_". Keep in mind, media file names may only
contain letters, digits and "_-.()".

All texts displayed in the game console are scanned for mnemonics prefixed
with ":". All found matches are replaced by the image.

The images are displayed as squares with width and height the same as the
regular font height. To create animations, put several square images side
by side into the file. For example, a 128x32 image is interpreted as four
frames. Frame duration is encoded in the filename, so, for example the file
"nop_1000_200_100.png" shows the first frame for 1000 milliseconds, the
second frame for 200 msec and the third (as the rest) for 100 msec.

Igraphs "1" to "9" are hardcoded and can be manually encoded in menu texts.
Use the new escape code "\i" plus the number of the image: "\i\1".."\i\9".
Other images can be used in menus as well, but the codes have to be fetched
by getigraph.

new commands:

  updateigraphs

    Checks for new files in packages/misc/igraph. Only necessary, if new
    files are added during the game - for example, by mod package download.

  getigraph <mnemonic>

    Returns an encoded string to display an image outside the console, or
    an empty string, if the mnemonic was not found.

new parameters:

  igraphsize 80..300 (default 120)

    scales the images (in percent)

  igraphsizehardcoded 80..160 (default 106)

    scales the hardcoded images "1".."9" (in percent)

  igraphanimate 0..1 (default 1)

    if 0, only the first frame of every image is displayed

  igraphdefaultframetime 5..2000 (default 200)

    used for images with multiple frames but no frame duration specified
    in the filename

  hideigraphs 0..1 (default 0)

    if 1, no new images will be shown in the console

DOCREF updateigraphs getigraph igraphsizehardcoded
DOCREF igraphsize igraphanimate igraphdefaultframetime hideigraphs
This commit is contained in:
ac-stef
2017-05-28 23:29:06 +02:00
parent 4e77b91c0e
commit 5e20a2343b
13 changed files with 206 additions and 21 deletions
+166 -13
View File
@@ -34,8 +34,7 @@ void newfont(char *name, char *tex, int *defaultw, int *defaulth, int *offsetx,
fontdef = f;
}
extern GLenum texformat(int bpp);
COMMANDN(font, newfont, "ssiiiiii");
void fontchar(int *x, int *y, int *w, int *h)
{
@@ -47,6 +46,7 @@ void fontchar(int *x, int *y, int *w, int *h)
c.w = *w ? *w : fontdef->defaultw;
c.h = *h ? *h : fontdef->defaulth;
}
COMMAND(fontchar, "iiii");
void fontskip(int *n)
{
@@ -54,9 +54,6 @@ void fontskip(int *n)
fontdef->skip = *n;
}
COMMANDN(font, newfont, "ssiiiiii");
COMMAND(fontchar, "iiii");
COMMAND(fontskip, "i");
bool setfont(const char *name)
@@ -66,7 +63,8 @@ bool setfont(const char *name)
curfont = f;
return true;
}
COMMANDF(setfont, "s", (char *name) { setfont(name); });
COMMAND(setfont, "s");
COMMANDF(curfont, "", () { result(curfont && curfont->name ? curfont->name : ""); });
font *getfont(const char *name)
@@ -127,6 +125,36 @@ static int draw_char(int c, int x, int y)
return 0;
}
vector<threeint> igraphbatch;
void queue_igraph(int c, int x, int y)
{
threeint v = { c, x, y };
igraphbatch.add(v);
}
VARP(igraphsize, 80, 120, 300);
VARP(igraphsizehardcoded, 80, 106, 160);
VARP(igraphanimate, 0, 1, 1);
void render_igraphs()
{
igraphbatch.sort(cmpintasc);
int last = 0, w[2] = { (FONTH * igraphsize) / 100, (FONTH * igraphsizehardcoded) / 100 }, offs[2] = { (FONTH - w[0]) / 2, (FONTH - w[1]) / 2 }, ishc = 0;
igraph *ig = NULL;
glColor4ub(255, 255, 255, 255);
loopv(igraphbatch)
{
if(last != igraphbatch[i].key) ig = getusedigraph((last = igraphbatch[i].key)), ishc = last > 0 && last < 10 ? 1 : 0;
if(ig && ig->tex)
{
int &x = igraphbatch[i].val1, &y = igraphbatch[i].val2, fpos = ((totalmillis + x * x + VIRTW * y) % ig->frames.last() + ig->frames.last()) % ig->frames.last(), frame = 0;
float fw = min(1.0f, float(ig->tex->ys) / ig->tex->xs);
if(igraphanimate) loopvj(ig->frames) if(ig->frames[j] < fpos) frame++;
quad(ig->tex->id, x + offs[ishc], y + offs[ishc], w[ishc], (frame % max(1, ig->tex->xs / ig->tex->ys)) * fw, 0, fw, 1);
}
}
}
//stack[sp] is current color index
static void text_color(char c, char *stack, int size, int &sp, bvec color, int a)
@@ -153,8 +181,7 @@ static void text_color(char c, char *stack, int size, int &sp, bvec color, int a
case '7': color = bvec( 153, 51, 51 ); break; // dark red: dead admin
case '8': color = bvec( 192, 64, 192 ); break; // magenta
case '9': color = bvec( 255, 102, 0 ); break; // orange
//extendeded color palette
//case 'a': case 'A':color = bvec( 0xFF, 0xCC, 0xCC); break; // some lowercase seem to have special meaning like 'b' (flashing text) so not yet using them
case 'A':color = bvec( 0xff, 0xb7, 0xb7); break; // red set
case 'B':color = bvec( 0xCC, 0x33, 0x33); break; //
case 'C':color = bvec( 0x66, 0x33, 0x33); break; //
@@ -187,8 +214,6 @@ static void text_color(char c, char *stack, int size, int &sp, bvec color, int a
case 'Y':color = bvec( 0xC7, 0xD1, 0xE2); break; // lt gray
case 'Z':color = bvec( 0x32, 0x32, 0x32); break; // dark gray
// white (provided color): everything else
//default: color = bvec( 255, 255, 255 ); break;
}
int b = (int) (sinf(lastmillis / 200.0f) * 115.0f);
b = stack[sp] > 0 ? 100 : min(iabs(b), 100);
@@ -243,6 +268,7 @@ void text_endcolumns()
else if(c==' ') { x += curfont->defaultw; TEXTWHITE(i) }\
else if(c=='\n') { TEXTLINE(i) x = 0; y += FONTH; }\
else if(c=='\f') { if(str[i+1]) { i++; TEXTCOLOR(i) }}\
else if(c=='\1') { if(str[i+1]) { i++; TEXTIGRAPH(i) }}\
else if(c=='\a') { if(str[i+1]) { i++; }}\
else if(curfont->chars.inrange(c-curfont->skip))\
{\
@@ -253,10 +279,12 @@ void text_endcolumns()
for(; str[i+1]; i++)\
{\
int c = str[i+1];\
bool isig = c == '\1';\
if(c=='\f') { if(str[i+2]) i++; continue; }\
if(i-j > 16) break;\
if(!curfont->chars.inrange(c-curfont->skip)) break;\
int cw = curfont->chars[c-curfont->skip].w + 1;\
if(!isig && !curfont->chars.inrange(c-curfont->skip)) break;\
int cw = isig ? FONTH + 1 : curfont->chars[c-curfont->skip].w + 1;\
if(isig && str[i + 2]) i++;\
if(w + cw >= maxwidth) break;\
w += cw;\
}\
@@ -275,6 +303,7 @@ void text_endcolumns()
TEXTINDEX(j)\
int c = str[j];\
if(c=='\f') { if(str[j+1]) { j++; TEXTCOLOR(j) }}\
else if(c=='\1') { if(str[j+1]) { j++; TEXTIGRAPH(j) }}\
else { TEXTCHAR(j) }\
}
@@ -285,6 +314,7 @@ int text_visible(const char *str, int hitx, int hity, int maxwidth)
#define TEXTWHITE(idx) if(y+FONTH > hity && x >= hitx) return idx;
#define TEXTLINE(idx) if(y+FONTH > hity) return idx;
#define TEXTCOLOR(idx)
#define TEXTIGRAPH(idx) x += FONTH; TEXTWHITE(idx)
#define TEXTCHAR(idx) x += curfont->chars[c-curfont->skip].w+1; TEXTWHITE(idx)
#define TEXTWORD TEXTWORDSKELETON
TEXTSKELETON
@@ -293,6 +323,7 @@ int text_visible(const char *str, int hitx, int hity, int maxwidth)
#undef TEXTWHITE
#undef TEXTLINE
#undef TEXTCOLOR
#undef TEXTIGRAPH
#undef TEXTCHAR
#undef TEXTWORD
return i;
@@ -306,6 +337,7 @@ void text_pos(const char *str, int cursor, int &cx, int &cy, int maxwidth)
#define TEXTWHITE(idx)
#define TEXTLINE(idx)
#define TEXTCOLOR(idx)
#define TEXTIGRAPH(idx) x += FONTH;
#define TEXTCHAR(idx) x += curfont->chars[c-curfont->skip].w + 1;
#define TEXTWORD TEXTWORDSKELETON if(i >= cursor) break;
cx = INT_MIN;
@@ -317,6 +349,7 @@ void text_pos(const char *str, int cursor, int &cx, int &cy, int maxwidth)
#undef TEXTWHITE
#undef TEXTLINE
#undef TEXTCOLOR
#undef TEXTIGRAPH
#undef TEXTCHAR
#undef TEXTWORD
}
@@ -328,6 +361,7 @@ void text_bounds(const char *str, int &width, int &height, int maxwidth)
#define TEXTWHITE(idx)
#define TEXTLINE(idx) if(x > width) width = x;
#define TEXTCOLOR(idx)
#define TEXTIGRAPH(idx) x += FONTH + 1;
#define TEXTCHAR(idx) x += curfont->chars[c-curfont->skip].w + 1;
#define TEXTWORD x += w + 1;
width = 0;
@@ -339,6 +373,7 @@ void text_bounds(const char *str, int &width, int &height, int maxwidth)
#undef TEXTWHITE
#undef TEXTLINE
#undef TEXTCOLOR
#undef TEXTIGRAPH
#undef TEXTCHAR
#undef TEXTWORD
}
@@ -350,12 +385,14 @@ void draw_text(const char *str, int left, int top, int r, int g, int b, int a, i
#define TEXTWHITE(idx)
#define TEXTLINE(idx)
#define TEXTCOLOR(idx) text_color(str[idx], colorstack, sizeof(colorstack), colorpos, color, a);
#define TEXTCHAR(idx) x += draw_char(c, left+x, top+y)+1;
#define TEXTIGRAPH(idx) queue_igraph((uchar)str[idx], left+x, top+y), x += FONTH + 1;
#define TEXTCHAR(idx) x += draw_char(c, left+x, top+y) + 1;
#define TEXTWORD TEXTWORDSKELETON
char colorstack[10];
bvec color(r, g, b);
int colorpos = 0, cx = INT_MIN, cy = 0, cc = ' ';
colorstack[0] = 'c'; //indicate user color
igraphbatch.setsize(0);
glBlendFunc(GL_SRC_ALPHA, curfont->tex->bpp==32 ? GL_ONE_MINUS_SRC_ALPHA : GL_ONE);
glBindTexture(GL_TEXTURE_2D, curfont->tex->id);
glBegin(GL_QUADS);
@@ -369,11 +406,13 @@ void draw_text(const char *str, int left, int top, int r, int g, int b, int a, i
int cw = curfont->chars.inrange(cc-33) ? curfont->chars[cc-33].w + 1 : curfont->defaultw;
rendercursor(left+cx, top+cy, cw);
}
render_igraphs();
#undef TEXTINDEX
#undef TEXTTAB
#undef TEXTWHITE
#undef TEXTLINE
#undef TEXTCOLOR
#undef TEXTIGRAPH
#undef TEXTCHAR
#undef TEXTWORD
}
@@ -393,9 +432,123 @@ void cutcolorstring(char *text, int maxlen)
while(*text)
{
if(*text == '\f' && text[1]) text++;
else if(*text == '\1' && text[1]) text++, len += FONTH + 1;
else if(*text == '\t') len = TABALIGN(len);
else len += curfont->chars.inrange(*text - curfont->skip) ? curfont->chars[*text - curfont->skip].w : curfont->defaultw;
if(len > maxlen) { *text = '\0'; break; }
text++;
}
}
// animated inlined graphics
VAR(igraphdefaultframetime, 5, 200, 2000);
VARP(hideigraphs, 0, 0, 1);
#define IGRAPHPATH "packages" PATHDIVS "misc" PATHDIVS "igraph" PATHDIVS
hashtable<const char *, igraph> igraphs; // keyed by shorthand
hashtable<const char *, char> igraphsi; // known filenames
vector<igraph *> usedigraphs; // char codes ('\1' + n)
void addigraph(char *fname)
{
char *b, *s = newstring(fname), *mnem = strtok_r(s, "_", &b), *r;
if(mnem && *mnem && !igraphs.access(mnem))
{ // new mnem
defformatstring(filename)("%s%s.png", IGRAPHPATH, fname);
Texture *tex = textureload(filename);
if(tex != notexture && tex->xs && tex->ys)
{
igraph &ig = igraphs[newstring(mnem)];
ig.fname = fname;
ig.tex = tex;
ig.used = !mnem[1] && *mnem >= '1' && *mnem <= '9' ? *mnem - '0' : 0;
if(ig.used && usedigraphs.inrange(ig.used)) usedigraphs[ig.used] = &ig; // hardcode "1".."9"
while((r = strtok_r(NULL, "_", &b))) ig.frames.add(max(5, (int)ATOI(r)));
if(ig.frames.empty()) ig.frames.add(igraphdefaultframetime);
while(ig.frames.length() < tex->xs / tex->ys) ig.frames.add(ig.frames.last());
loopv(ig.frames) if(i) ig.frames[i] += ig.frames[i - 1];
#ifdef _DEBUG
clientlogf(" loaded igraph \"%s\", short \"%s\", %dx%d, %d frame%s", fname, mnem, tex->xs, tex->ys, ig.frames.length(), ig.frames.length() > 1 ? "s" : "");
#endif
}
}
igraphsi[fname] = 0;
delstring(s);
}
void updateigraphs() // read all filenames and parse new ones (also preloads the textures)
{
vector<char *> files;
while(usedigraphs.length() <= (int)'\n') usedigraphs.add(NULL); // allocate hardcoded slots and skip slot 10 ('\n')
listfiles(IGRAPHPATH, "png", files);
loopvrev(files)
{
if(igraphsi.access(files[i])) delstring(files[i]);
else addigraph(files[i]);
}
}
COMMAND(updateigraphs, "");
igraph *getusedigraph(int i)
{
if(usedigraphs.inrange(i)) return usedigraphs[i];
return NULL;
}
int getigraph(const char *mnem)
{
igraph *ig = igraphs.access(mnem);
if(ig)
{
if(!ig->used && usedigraphs.length() < 255)
{
ig->used = usedigraphs.length();
usedigraphs.add(ig);
}
return ig->used;
}
return 0;
}
void _getigraph(char *s)
{
uchar res[3] = { '\1', (uchar)getigraph(s), '\0' };
result(res[1] ? (char*)res : "");
}
COMMANDN(getigraph, _getigraph, "s");
void encodeigraphs(char *d, const char *s, int len) // find known igraphs in a string and substitute with rendercodes, only used for console messages
{
if(hideigraphs) copystring(d, s, len);
else
{
len--;
string mnem;
loopi(len)
{
if(*s == ':' && (!i || isspace(s[-1])) && i < len - 1)
{
int l = strcspn(s + 1, " \t\n."), u;
if(l && l < MAXSTRLEN)
{
copystring(mnem, s + 1, l + 1);
if((u = getigraph(mnem)))
{ // found one: encode
*d++ = '\1';
*d++ = u;
s += l + 1;
i++;
continue;
}
}
}
*d++ = *s++;
}
*d = '\0';
}
}
void enumigraphs(vector<const char *> &igs, const char *s, int len)
{
enumeratek(igraphs, const char *, mnem, if(!strncasecmp(mnem, s, len)) igs.add(mnem));
}