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:
+166
-13
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user