From 5e20a2343b315855d94b006391fab5c17016d1aa Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sun, 28 May 2017 23:29:06 +0200 Subject: [PATCH] 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 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 --- config/menus_settings.cfg | 2 +- packages/misc/igraph/(.png | Bin 0 -> 420 bytes packages/misc/igraph/).png | Bin 0 -> 431 bytes packages/misc/igraph/1.png | Bin 0 -> 226 bytes packages/misc/igraph/3.png | Bin 0 -> 122 bytes .../misc/igraph/blink_1600_100_100_100.png | Bin 0 -> 528 bytes packages/misc/igraph/igraphs.txt | 6 + source/src/command.cpp | 14 +- source/src/console.h | 3 +- source/src/main.cpp | 4 +- source/src/protocol.cpp | 6 + source/src/protos.h | 13 ++ source/src/rendertext.cpp | 179 ++++++++++++++++-- 13 files changed, 206 insertions(+), 21 deletions(-) create mode 100644 packages/misc/igraph/(.png create mode 100644 packages/misc/igraph/).png create mode 100644 packages/misc/igraph/1.png create mode 100644 packages/misc/igraph/3.png create mode 100644 packages/misc/igraph/blink_1600_100_100_100.png create mode 100644 packages/misc/igraph/igraphs.txt diff --git a/config/menus_settings.cfg b/config/menus_settings.cfg index ffef345b..41451d80 100644 --- a/config/menus_settings.cfg +++ b/config/menus_settings.cfg @@ -789,7 +789,7 @@ menuinit [ ] if (|| (strlen $activezips) (strlen $inactivezips)) [ ] [ menuitem "--- no mod packages available ---" -1 ] if (strlen $activezips) [ menuitem "\f3Deactivate all mod packages" zipmodclear ] - menuitem "\f3Refresh textures (has no effect on already loaded models)" resetgl + menuitem "\f3Refresh textures (has no effect on already loaded models)" [ resetgl ; updateigraphs ] pop activezips inactivezips ecurmenu ] diff --git a/packages/misc/igraph/(.png b/packages/misc/igraph/(.png new file mode 100644 index 0000000000000000000000000000000000000000..9cfa4c03d5977f0419be61db513f58493465e7c4 GIT binary patch literal 420 zcmV;V0bBlwP)kdg0004NNklM}=2LAg$nq?2+5gqW*~6`FO7kq0J-gC2kbVgmBx zy7>qaC8UI_a&2Ar1E~^{Q8hY=dPrH^xaK57>XD3Zcb8X!bbMx9adJdTkdg0004YNklV+K428cpfk>s@^d=mH4Lu46p$9-+wkPN{kPs*61yI>kQ4_-Jg1=^xP#SpI#2!D} zKdd+X9U#=Lrf_LeawI02@ZZ?}MN z%oLnVV)_%LPRYoR#?g1w=VVSvpQ_PG{eWFGlOrbu_5?u{Y<)6(<|))X5S! zR5f#t-bp+M18lV64j=$xN>ZhJXVxeSphv5L!3cD8VjB#M(;uVK{Dh%c`4Oq%taV1->#R>vI&|5>-sv`Q zB?9ZH>He3*I`Hv-2R{9L1QwLFg+J)Wij^z^@9H0@s$HUXx&ifhueGvkP1}5!r&fkH z<#E3|%H<$e=s9mK*SW14ZPz*DSw)qk+9nj!3!at9S3hZ2KdQHQ)^9LaZZO(zFkHKi Z_aAjsu-nn8Jx~Au002ovPDHLkV1grV###UX literal 0 HcmV?d00001 diff --git a/packages/misc/igraph/1.png b/packages/misc/igraph/1.png new file mode 100644 index 0000000000000000000000000000000000000000..0f1b467d7e279a721f8d507b056406aa60113e44 GIT binary patch literal 226 zcmV<803H8{P)+BfyJ^w4aWsL`6S#%*qasmE~&1OkD;Pe1_J1CiuKkp%#l4^fDy_`c3% z%~$cIMR9SV`Qe(n7+YSs&edW5pvy zm$x>u5YyV4W-1}qR(_0^RWnE(I)07*qoM6N<$f_gz-G5`Po literal 0 HcmV?d00001 diff --git a/packages/misc/igraph/3.png b/packages/misc/igraph/3.png new file mode 100644 index 0000000000000000000000000000000000000000..5dd84fe133242082cfa3fc4aa18ef51d85fe7eeb GIT binary patch literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4Uj-D=#As)w*6C_v{Cy4Yk9b}SN z_@$mlP{~y?(dob7s|h@+I;UE)Uet>`7gRpwk|_~zn!!yc(RGf-(TxUNZLACo7bAsu UqN6m_fQB)6y85}Sb4q9e0D(m!fB*mh literal 0 HcmV?d00001 diff --git a/packages/misc/igraph/blink_1600_100_100_100.png b/packages/misc/igraph/blink_1600_100_100_100.png new file mode 100644 index 0000000000000000000000000000000000000000..fb0f6382d1cf7bac1a44e4bf345a31ff2e495551 GIT binary patch literal 528 zcmV+r0`L8aP)!#!Q zIi4A3Y=b8m0CS#rV?{;9iZ`B^H=Z6)1pfhGy5t!l6{@t6ByCiwK!|5uV!NJU4dR1Z zpafVVe$^v>5lc|A2>CvO@4)wvDI=$-JA1#ny&zXccJB`{lJ~qP(=}2VhP^>bq-(N8 z`}L#wM(&j;Ls}n#TZ62TG9@2%@Tc)Wy(Ds_q;PbR0|Hpvnu|u`WZnVBlBA7m+bw7t zKuW@L2L$*-YV#)J!hS^i^^L9pd)s2@iX6K9uqbSKXR=2Mrl_z-Q#23Apa1a)?0q1} z503$sZcF53#nCWNxl_r^Mw5n6X?M`&heUACr`(cEo`1qEba|tl6yI+0#!5Gsg)`J^ z@7iQv5aI`}0@;^Q$&Ft^^`tfs$2(3e$G2DX#yRmF%R4^D51{`)pRv5-ovQQT2wrEr z;yspkyxMG!T4xjL#|7Ao*I2$&O4TVD;g7Me6p!T{@M48ut>pQ#w_A9@3&irl?YJ+S z!?)w%yfazOgOzQ_IzcoaFL|)s6wROdNOtl=={X$B&){Nm23NB)xSXEqdj1EIX-%7~ SGt#^O0000 matchingnames; + vector matchingigs; loopv(players) if(players[i] && !strncasecmp(players[i]->name, cp, nickcompletesize)) matchingnames.add(i); // find all matching player names first - if(matchingnames.length()) + if(nickcompletesize > 0 && *cp == ':') enumigraphs(matchingigs, cp + 1, nickcompletesize - 1); // find all matching igraphs + int totalmatches = matchingnames.length() + matchingigs.length(); + if(totalmatches) { - nickcompleteidx += reversedirection ? matchingnames.length() - 1 : 1; - nickcompleteidx %= matchingnames.length(); - const char *fillin = players[matchingnames[nickcompleteidx]]->name; + nickcompleteidx += reversedirection ? totalmatches - 1 : 1; + nickcompleteidx %= totalmatches; + bool isig = nickcompleteidx >= matchingnames.length(); + const char *fillin = isig ? matchingigs[nickcompleteidx - matchingnames.length()] : players[matchingnames[nickcompleteidx]]->name; if(*fillin == '/' && cp == s) *cp++ = ' '; + if(isig) cp++; *cp = '\0'; concatstring(s, fillin); return true; diff --git a/source/src/console.h b/source/src/console.h index ec900411..eda109ad 100644 --- a/source/src/console.h +++ b/source/src/console.h @@ -12,7 +12,8 @@ template struct consolebuffer LINE cl; cl.line = conlines.length()>maxlines ? conlines.pop().line : newstringbuf(""); // constrain the buffer size cl.millis = millis; // for how long to keep line on screen - copystring(cl.line, sf); + extern void encodeigraphs(char *d, const char *s, int len); + encodeigraphs(cl.line, sf, MAXSTRLEN); return conlines.insert(0, cl); } diff --git a/source/src/main.cpp b/source/src/main.cpp index 57a5a8e1..5835da0a 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -152,9 +152,10 @@ void writeinitcfg() f->printf("stencilbits %d\n", stencilbits); f->printf("fsaa %d\n", fsaa); f->printf("vsync %d\n", vsync); - extern int audio, soundchannels; + extern int audio, soundchannels, igraphdefaultframetime; f->printf("audio %d\n", audio > 0 ? 1 : 0); f->printf("soundchannels %d\n", soundchannels); + f->printf("igraphdefaultframetime %d\n", igraphdefaultframetime); if(lang && *lang) f->printf("lang %s\n", lang); extern void writezipmodconfig(stream *f); writezipmodconfig(f); @@ -1171,6 +1172,7 @@ int main(int argc, char **argv) if(!notexture) fatal("could not find core textures (hint: run AssaultCube from the parent of the bin directory)"); initlog("console"); + updateigraphs(); autostartscripts("_veryfirst_"); // Main font file, all other font files execute from here. if(!execfile("config/font.cfg")) fatal("cannot find default font definitions"); diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index ff5468ab..002bac13 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -287,6 +287,11 @@ char *filtertext(char *dst, const char *src, int flags, int len) } pass = true; } + if(c == '\1') + { // always filter encoded igraphs + if(src[1]) ++src; + continue; + } if(mapname && !isalnum(c) && !strchr("_-./\\", c)) continue; if(isspace(c)) { @@ -326,6 +331,7 @@ void filterrichtext(char *dst, const char *src, int len) { case '\0': --src; continue; case 'f': c = '\f'; break; + case 'i': c = '\1'; break; case 'a': c = '\a'; break; case 't': c = '\t'; break; case 'n': c = '\n'; break; diff --git a/source/src/protos.h b/source/src/protos.h index 4fa24cae..13254667 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -448,6 +448,19 @@ extern void scaletmu(int n, int rgbscale, int alphascale = 0); extern void colortmu(int n, float r = 0, float g = 0, float b = 0, float a = 0); extern void setuptmu(int n, const char *rgbfunc = NULL, const char *alphafunc = NULL); +struct igraph +{ + const char *fname; + struct Texture *tex; + vector frames; + int used; +}; +extern void updateigraphs(); +extern igraph *getusedigraph(int i); +extern int getigraph(const char *mnem); +extern void encodeigraphs(char *d, const char *s, int len); +extern void enumigraphs(vector &igs, const char *s, int len); + // renderhud enum { diff --git a/source/src/rendertext.cpp b/source/src/rendertext.cpp index c487a7e6..6f095bd1 100644 --- a/source/src/rendertext.cpp +++ b/source/src/rendertext.cpp @@ -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 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 igraphs; // keyed by shorthand +hashtable igraphsi; // known filenames +vector 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 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 &igs, const char *s, int len) +{ + enumeratek(igraphs, const char *, mnem, if(!strncasecmp(mnem, s, len)) igs.add(mnem)); +}