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 00000000..9cfa4c03 Binary files /dev/null and b/packages/misc/igraph/(.png differ diff --git a/packages/misc/igraph/).png b/packages/misc/igraph/).png new file mode 100644 index 00000000..81812973 Binary files /dev/null and b/packages/misc/igraph/).png differ diff --git a/packages/misc/igraph/1.png b/packages/misc/igraph/1.png new file mode 100644 index 00000000..0f1b467d Binary files /dev/null and b/packages/misc/igraph/1.png differ diff --git a/packages/misc/igraph/3.png b/packages/misc/igraph/3.png new file mode 100644 index 00000000..5dd84fe1 Binary files /dev/null and b/packages/misc/igraph/3.png differ 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 00000000..fb0f6382 Binary files /dev/null and b/packages/misc/igraph/blink_1600_100_100_100.png differ diff --git a/packages/misc/igraph/igraphs.txt b/packages/misc/igraph/igraphs.txt new file mode 100644 index 00000000..10d20f18 --- /dev/null +++ b/packages/misc/igraph/igraphs.txt @@ -0,0 +1,6 @@ +igraphs "1" to "9" are hardcoded to be used in menus: + +1: menu icon (indicating a sub-menu) +2: not assigned yet +3: ellipsis (three dots, indicating a sub-menu) +4..9: not assigned yet diff --git a/source/src/command.cpp b/source/src/command.cpp index 52c01425..741e162e 100644 --- a/source/src/command.cpp +++ b/source/src/command.cpp @@ -763,7 +763,6 @@ void resetcomplete() bool nickcomplete(char *s, bool reversedirection) { static int nickcompleteidx; - if(!players.length()) return false; char *cp = strrchr(s, ' '); // find last space cp = cp ? cp + 1 : s; @@ -775,13 +774,18 @@ bool nickcomplete(char *s, bool reversedirection) } vector 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)); +}