diff --git a/config/menus_multiplayer.cfg b/config/menus_multiplayer.cfg index 7b5d07d7..b130dd7b 100644 --- a/config/menus_multiplayer.cfg +++ b/config/menus_multiplayer.cfg @@ -21,6 +21,7 @@ menuitem "Set mode/map..." "showmenu Gamemode" menuitem "Kick/ban player..." "showmenu kick/ban" menuitem "Adjust teams..." [ showmenu "Team adjustment" ] menuitem "Download a demo..." [ listdemos; showmenu "Download demo" ] +menuitem "Get additional server information" "showmenu [serverinfo]" menuitem "" -1 menuitem " 4Main menu..." [ closemenu In-game; showmenu main ] menuitem " 4Browse servers..." [ closemenu "In-game"; joinservermenu ] @@ -124,6 +125,7 @@ menuitem "F1: Display this help menu" -1 menuitem "F3: Search for players by nickname..." [ showmenu "search player" ] menuitem "F4: Edit favourites..." "showmenu [edit favourites]" menuitem "F5: Update the serverlist from the masterserver" [updatefrommaster 1; closemenu "serverbrowser help"] +menuitem "F9: Get additional server information" "showmenu [serverinfo]" menuitem "" -1 menuitem "Options:" -1 menuitemcheckbox "F6: Reverse the sort order:" "$serversortdir" "serversortdir $arg1" diff --git a/config/serverinfo_en.txt b/config/serverinfo_en.txt new file mode 100644 index 00000000..10eec30c --- /dev/null +++ b/config/serverinfo_en.txt @@ -0,0 +1,21 @@ +// extended serverinfo file +// ======================== +// WARNING: You can NOT use CubeScript in here!! +// +// This text file is sent, whenever a client requests extended server information. +// Comments are removed, also leading and trailing blanks and empty lines. +// To send an intentionally left blank line, use a line with a single '.' +// +// Please use this file to provide contact information, especially for players +// who are innocently affect by an IP range ban. +// You can also use this page to advertize your clan or provide contact +// information for recruiting. +// +// Don't use more than 80 characters per line. You can use colors and TABs. +// The available colors are: 0: green, 1: blue, 2: yellow, 3: red, 4: gray, 5: white, 6: dark brown, 7: dark red +// +// Example: +// BobsInstagibServer \f3play nicely! +// contact me at a@b.c + + diff --git a/docs/commandline.html b/docs/commandline.html index 80384387..4b00288c 100644 --- a/docs/commandline.html +++ b/docs/commandline.html @@ -470,6 +470,17 @@ -Kconfig/nicknameblacklist.cfg + + + -If + + + Sets the serverinfo text filename prefix. The default is + -Iconfig/serverinfo. + The server adds "_en.txt" to the prefix to + complete the filename. + + -oN diff --git a/docs/server.html b/docs/server.html index 83937519..a425cbcc 100644 --- a/docs/server.html +++ b/docs/server.html @@ -197,7 +197,7 @@ Important: do only use standard maps and maps from your packages/maps/servermaps folder!

- Blacklist + IP Blacklist

In the blacklist file specified on the command line (default: diff --git a/source/src/main.cpp b/source/src/main.cpp index 3fcd887d..cbeff9e8 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -660,17 +660,18 @@ int main(int argc, char **argv) audiomgr.initsound(); initlog("cfg"); - extern void *scoremenu, *teammenu, *ctfmenu, *servmenu, *searchmenu, *kickmenu, *banmenu, *forceteammenu, *giveadminmenu, *docmenu, *applymenu; - scoremenu = addmenu("score", "frags\tdeath\tratio\tpj\tping\tcn\tname", false, renderscores, NULL, false, true); - teammenu = addmenu("team score", "frags\tdeath\tratio\tpj\tping\tcn\tname", false, renderscores, NULL, false, true); - ctfmenu = addmenu("ctf score", "flags\tfrags\tdeath\tratio\tpj\tping\tcn\tname", false, renderscores, NULL, false, true); - servmenu = addmenu("server", NULL, true, refreshservers, serverskey); - searchmenu = addmenu("search", NULL, true, refreshservers, serverskey); - kickmenu = addmenu("kick player", NULL, true, refreshsopmenu); - banmenu = addmenu("ban player", NULL, true, refreshsopmenu); - forceteammenu = addmenu("force team", NULL, true, refreshsopmenu); - giveadminmenu = addmenu("give admin", NULL, true, refreshsopmenu); - docmenu = addmenu("reference", NULL, true, renderdocmenu); + extern void *scoremenu, *teammenu, *ctfmenu, *servmenu, *searchmenu, *serverinfomenu, *kickmenu, *banmenu, *forceteammenu, *giveadminmenu, *docmenu, *applymenu; + scoremenu = addmenu("score", "frags\tdeath\tratio\tpj\tping\tcn\tname", false, renderscores, NULL, false, true); + teammenu = addmenu("team score", "frags\tdeath\tratio\tpj\tping\tcn\tname", false, renderscores, NULL, false, true); + ctfmenu = addmenu("ctf score", "flags\tfrags\tdeath\tratio\tpj\tping\tcn\tname", false, renderscores, NULL, false, true); + servmenu = addmenu("server", NULL, true, refreshservers, serverskey); + searchmenu = addmenu("search", NULL, true, refreshservers, serverskey); + serverinfomenu = addmenu("serverinfo", NULL, true, refreshservers, NULL); + kickmenu = addmenu("kick player", NULL, true, refreshsopmenu); + banmenu = addmenu("ban player", NULL, true, refreshsopmenu); + forceteammenu = addmenu("force team", NULL, true, refreshsopmenu); + giveadminmenu = addmenu("give admin", NULL, true, refreshsopmenu); + docmenu = addmenu("reference", NULL, true, renderdocmenu); applymenu = addmenu("apply", "apply changes now?", true, refreshapplymenu); exec("config/scontext.cfg"); diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 41cb6257..4a50e2d7 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -159,6 +159,17 @@ void filterservdesc(char *dst, const char *src, int len) *dst = '\0'; } +void cutcolorstring(char *text, int len) +{ // limit string length, ignore color codes + while(*text) + { + if(*text == '\f' && text[1]) text++; + else len--; + if(len < 0) { *text = '\0'; break; } + text++; + } +} + const char *modefullnames[] = { "demo playback", diff --git a/source/src/protocol.h b/source/src/protocol.h index 2a5a6c0c..5a98711e 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -96,7 +96,7 @@ enum { DISC_NONE = 0, DISC_EOP, DISC_CN, DISC_MKICK, DISC_MBAN, DISC_TAGT, DISC_ #define EXT_PLAYERSTATS_RESP_STATS -11 enum { PONGFLAG_PASSWORD = 0, PONGFLAG_BANNED, PONGFLAG_BLACKLIST, PONGFLAG_MASTERMODE = 6, PONGFLAG_NUM }; -enum { EXTPING_NOP = 0, EXTPING_NAMELIST, EXTPING_NUM }; +enum { EXTPING_NOP = 0, EXTPING_NAMELIST, EXTPING_SERVERINFO, EXTPING_MAPROT, EXTPING_NUM }; enum { diff --git a/source/src/protos.h b/source/src/protos.h index fb41a51d..b6b4b388 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -196,17 +196,21 @@ struct serverinfo string sdesc; string description; string cmd; - int mode, numplayers, maxclients, ping, protocol, minremain, resolved, port, lastpingmillis, pongflags, getnames, menuline_from, menuline_to; + int mode, numplayers, maxclients, ping, protocol, minremain, resolved, port, lastpingmillis, pongflags, getnames, getinfo, menuline_from, menuline_to; ENetAddress address; vector playernames; uchar namedata[MAXTRANS]; + vector infotexts; + uchar textdata[MAXTRANS]; + char lang[3]; color *bgcolor; int favcat, msweight, weight; serverinfo() - : mode(0), numplayers(0), maxclients(0), ping(9999), protocol(0), minremain(0), resolved(UNRESOLVED), port(-1), lastpingmillis(0), pongflags(0), getnames(0), bgcolor(NULL), favcat(-1), msweight(0), weight(0) + : mode(0), numplayers(0), maxclients(0), ping(9999), protocol(0), minremain(0), resolved(UNRESOLVED), port(-1), lastpingmillis(0), pongflags(0), getnames(0), getinfo(0), bgcolor(NULL), favcat(-1), msweight(0), weight(0) { name[0] = full[0] = map[0] = sdesc[0] = description[0] = '\0'; + loopi(3) lang[i] = '\0'; } }; @@ -685,6 +689,7 @@ extern void getstring(char *t, ucharbuf &p, int len = MAXTRANS); extern void filtertext(char *dst, const char *src, int whitespace = 1, int len = sizeof(string)-1); extern void filterrichtext(char *dst, const char *src, int len = sizeof(string)-1); extern void filterservdesc(char *dst, const char *src, int len = sizeof(string)-1); +extern void cutcolorstring(char *text, int len); extern void startintermission(); extern void restoreserverstate(vector &ents); extern uchar *retrieveservers(uchar *buf, int buflen); @@ -722,7 +727,7 @@ extern bool logline(int level, const char *msg, ...); struct servercommandline { int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose; - const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *nbfile; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *nbfile, *infopath; bool demoeverymatch, logtimestamp; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; int clfilenesting; @@ -732,6 +737,7 @@ struct servercommandline maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(""), maprot("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), nbfile("config/nicknameblacklist.cfg"), + infopath("config/serverinfo"), demoeverymatch(true), logtimestamp(false), clfilenesting(0) { @@ -744,7 +750,7 @@ struct servercommandline const char *a = arg + 2 + strspn(arg + 2, " "); int ai = atoi(a); switch(arg[1]) - { // todo: egjlqEGHIJOQUYZ + { // todo: egjlqEGHJOQUYZ case 'u': uprate = ai; break; case 'f': if(ai > 0 && ai < 65536) serverport = ai; break; case 'i': ip = a; break; @@ -777,6 +783,7 @@ struct servercommandline case 'X': pwdfile = a; break; case 'B': blfile = a; break; case 'K': nbfile = a; break; + case 'I': infopath = a; break; case 'o': filterrichtext(motd, a); break; case 'n': { diff --git a/source/src/server.cpp b/source/src/server.cpp index ebd38525..123c8261 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -3793,6 +3793,80 @@ void extping_namelist(ucharbuf &p) sendstring("", p); } +#define MAXINFOLINELEN 100 // including color codes + +const char *readserverinfo(const char *lang) +{ + s_sprintfd(fname)("%s_%s.txt", scl.infopath, lang); + path(fname); + int len, n; + char *c, *s, *t, *buf = loadfile(fname, &len); + if(!buf) return NULL; + char *nbuf = new char[len + 2]; + for(t = nbuf, s = strtok(buf, "\n\r"); s; s = strtok(NULL, "\n\r")) + { + c = strstr(s, "//"); + if(c) *c = '\0'; // strip comments + for(n = strlen(s) - 1; n >= 0 && s[n] == ' '; n--) s[n] = '\0'; // strip trailing blanks + filterrichtext(t, s + strspn(s, " "), MAXINFOLINELEN); // skip leading blanks + n = strlen(t); + if(n) t += n + 1; + } + *t = '\0'; + delete[] buf; + if(!*nbuf) DELETEA(nbuf); + return nbuf; +} + +struct serverinfotext { char lang[3]; const char *info; }; +vector serverinfotexts; + +const char *getserverinfo(const char *lang) +{ + if(!islower(lang[0]) || !islower(lang[1])) return NULL; + serverinfotext s; + loopi(3) s.lang[i] = lang[i]; + loopv(serverinfotexts) + { + if(!strcmp(s.lang, serverinfotexts[i].lang) && serverinfotexts[i].info) return serverinfotexts[i].info; + } + s.info = readserverinfo(lang); + serverinfotexts.add(s); + return s.info; +} + +void extping_serverinfo(ucharbuf &pi, ucharbuf &po) +{ + char lang[3]; + lang[0] = tolower(getint(pi)); lang[1] = tolower(getint(pi)); lang[2] = '\0'; + const char *reslang = lang, *buf = getserverinfo(lang); // try client language + if(!buf) buf = getserverinfo(reslang = "en"); // try english + sendstring(buf ? reslang : "", po); + if(buf) + { + for(const char *c = buf; *c && po.remaining() > MAXINFOLINELEN + 10; c += strlen(c) + 1) sendstring(c, po); + sendstring("", po); + } +} + +void extping_maprot(ucharbuf &po) +{ + putint(po, CONFIG_MAXPAR); + string text; + bool abort = false; + loopv(configsets) + { + if(po.remaining() < 100) abort = true; + configset &c = configsets[i]; + filtertext(text, c.mapname, 0); + text[30] = '\0'; + sendstring(abort ? "-- list truncated --" : text, po); + loopi(CONFIG_MAXPAR) putint(po, c.par[i]); + if(abort) break; + } + sendstring("", po); +} + void extinfo_cnbuf(ucharbuf &p, int cn) { if(cn == -1) // add all available player ids @@ -3932,6 +4006,7 @@ void initserver(bool dedicated) readpwdfile(scl.pwdfile); readipblacklist(scl.blfile); nbl.readnickblacklist(scl.nbfile); + getserverinfo("en"); // cache 'en' serverinfo if(scl.demoeverymatch) logline(ACLOG_VERBOSE, "recording demo of every game (holding up to %d in memory)", scl.maxdemos); if(scl.demopath[0]) logline(ACLOG_VERBOSE,"all recorded demos will be written to: \"%s\"", scl.demopath); if(scl.voteperm[0]) logline(ACLOG_VERBOSE,"vote permission string: \"%s\"", scl.voteperm); diff --git a/source/src/serverbrowser.cpp b/source/src/serverbrowser.cpp index a3159d6f..9fb8c5cb 100644 --- a/source/src/serverbrowser.cpp +++ b/source/src/serverbrowser.cpp @@ -341,7 +341,7 @@ VAR(searchlan, 0, 1, 2); #define PINGBUFSIZE 100 static int pingbuf[PINGBUFSIZE], curpingbuf = 0; -void pingservers(bool issearch, bool onlyconnected) +void pingservers(bool issearch, serverinfo *onlyconnected) { if(pingsock == ENET_SOCKET_NULL) { @@ -364,15 +364,19 @@ void pingservers(bool issearch, bool onlyconnected) int baselen = p.length(); if(onlyconnected) { - serverinfo *si = getconnectedserverinfo(); - if(si) + serverinfo *si = onlyconnected; + //p.len = baselen; + if(si->getinfo) { - //p.len = baselen; - putint(p, si->getnames || issearch ? EXTPING_NAMELIST : EXTPING_NOP); - buf.data = ping; - buf.dataLength = p.length(); - enet_socket_send(pingsock, &si->address, &buf, 1); + putint(p, EXTPING_SERVERINFO); + const char *lang = getalias("LANG"); + if(!lang || strlen(lang) != 2) lang = "en"; + loopi(2) putint(p, lang[i]); } + else putint(p, si->getnames || issearch ? EXTPING_NAMELIST : EXTPING_NOP); + buf.data = ping; + buf.dataLength = p.length(); + enet_socket_send(pingsock, &si->address, &buf, 1); } else if(searchlan < 2) { @@ -437,6 +441,8 @@ void checkresolver() } } +#define MAXINFOLINELEN 100 // including color codes + void checkpings() { if(pingsock == ENET_SOCKET_NULL) return; @@ -465,6 +471,12 @@ void checkpings() int pingtm = pingbuf[(getint(p) - 1) % PINGBUFSIZE]; if(pingtm) si->ping = totalmillis - pingtm; int query = getint(p); + switch(query) + { // cleanup additional query info + case EXTPING_SERVERINFO: + loopi(2) getint(p); + break; + } si->protocol = getint(p); if(si->protocol!=PROTOCOL_VERSION) si->ping = 9998; si->mode = getint(p); @@ -500,6 +512,53 @@ void checkpings() } break; } + case EXTPING_SERVERINFO: + { + si->infotexts.setsizenodelete(0); + ucharbuf q(si->textdata, sizeof(si->textdata)); + getstring(text, p); + si->getinfo = 0; + if(strlen(text) != 2) + { + si->infotexts.add((char *)si->textdata); + sendstring("this server does not provide additional information", q); + break; + } + strcpy(si->lang, text); + while(p.remaining()) + { + getstring(text, p); + if(*text && !p.overread()) + { + text[MAXINFOLINELEN] = '\0'; + cutcolorstring(text, 80); + si->infotexts.add((char *)si->textdata + q.length()); + sendstring(strcmp(text, ".") ? text : "", q); + } + else break; + } + break; + } + case EXTPING_MAPROT: + { + si->infotexts.setsizenodelete(0); + ucharbuf q(si->textdata, sizeof(si->textdata)); + int n = getint(p); + si->getinfo = 0; + while(p.remaining()) + { + getstring(text, p); + if(*text && !p.overread()) + { + text[MAXINFOLINELEN] = '\0'; + loopi(n) s_strcatf(text, ", %d", getint(p)); + si->infotexts.add((char *)si->textdata + q.length()); + sendstring(text , q); + } + else break; + } + break; + } } } } @@ -609,7 +668,7 @@ int sicompare(serverinfo **ap, serverinfo **bp) else return -dir; } -void *servmenu = NULL, *searchmenu = NULL; +void *servmenu = NULL, *searchmenu = NULL, *serverinfomenu = NULL; vector namelists; string cursearch, cursearchuc; @@ -810,7 +869,10 @@ void refreshservers(void *menu, bool init) static int servermenumillis; static bool usedselect = false; static string title; + static serverinfo *lastselectedserver = NULL; bool issearch = menu == searchmenu; + bool isinfo = menu == serverinfomenu; + bool isscoreboard = menu == NULL; serverinfo *curserver = getconnectedserverinfo(), *oldsel = NULL; if(init) @@ -826,11 +888,55 @@ void refreshservers(void *menu, bool init) checkresolver(); checkpings(); - if((init && issearch) || totalmillis - lastinfo >= (servpingrate * (issearch ? 2 : 1))/(maxservpings ? (servers.length() + maxservpings - 1) / maxservpings : 1)) pingservers(issearch, menu == NULL); - if(!init && menu && servers.inrange(((gmenu *)menu)->menusel) && (usedselect || ((gmenu *)menu)->menusel > 0)) + if(isinfo) { - loopv(servers) if(servers[i]->menuline_from == ((gmenu *)menu)->menusel && servers[i]->menuline_to > servers[i]->menuline_from) { oldsel = servers[i]; break; } + if(lastselectedserver) + { + bool found = false; + loopv(servers) if(lastselectedserver == servers[i]) { found = true; break; } + if(!found) lastselectedserver = NULL; + } + menutitle(menu, "extended server information"); + menureset(menu); + static string infotext; + static char dummy = '\0'; + if(lastselectedserver) + { + serverinfo &si = *lastselectedserver; + s_sprintf(si.full)("%s:%d %s", si.name, si.port, si.sdesc); + menumanual(menu, si.full); + menumanual(menu, &dummy); + if(si.infotexts.length()) + { + infotext[0] = '\0'; + loopv(si.infotexts) menumanual(menu, si.infotexts[i]); + } + else + { + s_strcpy(infotext, "-- waiting for server response --"); + si.getinfo = 1; + if(init || totalmillis - lastinfo >= servpingrate) pingservers(false, lastselectedserver); + } + } + else + s_strcpy(infotext, " -- no server selected --"); + if(*infotext) menumanual(menu, infotext); + return; } + if((init && issearch) || totalmillis - lastinfo >= (servpingrate * (issearch ? 2 : 1))/(maxservpings ? (servers.length() + maxservpings - 1) / maxservpings : 1)) + pingservers(issearch, isscoreboard ? curserver : NULL); + if(!init && menu)// && servers.inrange(((gmenu *)menu)->menusel)) + { + serverinfo *foundserver = NULL; + loopv(servers) if(servers[i]->menuline_from == ((gmenu *)menu)->menusel && servers[i]->menuline_to > servers[i]->menuline_from) { foundserver = servers[i]; break; } + if(foundserver) + { + if((usedselect || ((gmenu *)menu)->menusel > 0)) oldsel = foundserver; + lastselectedserver = foundserver; + } + } + if(isscoreboard) lastselectedserver = curserver; + bool showfavtag = (assignserverfavourites() || !serverbrowserhidefavtag) && serverbrowserhidefavtag != 2; serversortprepare(); servers.sort(sicompare); @@ -857,6 +963,7 @@ void refreshservers(void *menu, bool init) loopv(servers) { serverinfo &si = *servers[i]; + si.menuline_to = si.menuline_from = ((gmenu *)menu)->items.length(); if(!showallservers && si.lastpingmillis < servermenumillis) continue; // no pong yet int banned = ((si.pongflags >> PONGFLAG_BANNED) & 1) | ((si.pongflags >> (PONGFLAG_BLACKLIST - 1)) & 2); bool showthisone = !(banned && showonlygoodservers) && !(showonlyfavourites > 0 && si.favcat != showonlyfavourites - 1); @@ -865,7 +972,6 @@ void refreshservers(void *menu, bool init) bool isprivate = (si.pongflags >> PONGFLAG_MASTERMODE) > 0; char basecolor = banned ? '4' : (curserver == servers[i] ? '1' : '5'); char plnumcolor = serverfull ? '2' : (needspasswd ? '3' : (isprivate ? '1' : basecolor)); - si.menuline_from = ((gmenu *)menu)->items.length(); if(si.address.host != ENET_HOST_ANY && si.ping != 9999) { if(si.protocol!=PROTOCOL_VERSION) @@ -902,8 +1008,8 @@ void refreshservers(void *menu, bool init) } if(showthisone) { - si.full[94] = 0; // cut off too long server descriptions - si.description[75] = 0; + cutcolorstring(si.full, 76); // cut off too long server descriptions + cutcolorstring(si.description, 76); if(sbconnectexists) { filtertext(text, si.sdesc); @@ -1016,6 +1122,10 @@ bool serverskey(void *menu, int code, bool isdown, int unicode) case SDLK_F6: serversortdir = serversortdir ? 0 : 1; return true; + + case SDLK_F9: + showmenu("serverinfo"); + return true; } if(menu == searchmenu) return false; switch(code) diff --git a/source/src/serverms.cpp b/source/src/serverms.cpp index 60958fcd..da54e95f 100644 --- a/source/src/serverms.cpp +++ b/source/src/serverms.cpp @@ -214,6 +214,20 @@ void serverms(int mode, int numplayers, int minremain, char *smapname, int milli extping_namelist(po); break; } + case EXTPING_SERVERINFO: + { + extern void extping_serverinfo(ucharbuf &pi, ucharbuf &po); + putint(po, query); + extping_serverinfo(pi, po); + break; + } + case EXTPING_MAPROT: + { + extern void extping_maprot(ucharbuf &po); + putint(po, query); + extping_maprot(po); + break; + } case EXTPING_NOP: default: putint(po, EXTPING_NOP);