1
// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window management
8
#include "SDL_thread.h"
10
extern bool isdedicated;
25
vector<resolverthread> resolverthreads;
26
vector<const char *> resolverqueries;
27
vector<resolverresult> resolverresults;
28
SDL_mutex *resolvermutex;
29
SDL_cond *querycond, *resultcond;
31
#define RESOLVERTHREADS 1
32
#define RESOLVERLIMIT 3000
34
int resolverloop(void * data)
36
resolverthread *rt = (resolverthread *)data;
37
SDL_LockMutex(resolvermutex);
38
SDL_Thread *thread = rt->thread;
39
SDL_UnlockMutex(resolvermutex);
40
if(!thread || SDL_GetThreadID(thread) != SDL_ThreadID())
42
while(thread == rt->thread)
44
SDL_LockMutex(resolvermutex);
45
while(resolverqueries.empty()) SDL_CondWait(querycond, resolvermutex);
46
rt->query = resolverqueries.pop();
47
rt->starttime = totalmillis;
48
SDL_UnlockMutex(resolvermutex);
50
ENetAddress address = { ENET_HOST_ANY, ENET_PORT_ANY };
51
enet_address_set_host(&address, rt->query);
53
SDL_LockMutex(resolvermutex);
54
if(rt->query && thread == rt->thread)
56
resolverresult &rr = resolverresults.add();
61
SDL_CondSignal(resultcond);
63
SDL_UnlockMutex(resolvermutex);
70
resolvermutex = SDL_CreateMutex();
71
querycond = SDL_CreateCond();
72
resultcond = SDL_CreateCond();
74
SDL_LockMutex(resolvermutex);
75
loopi(RESOLVERTHREADS)
77
resolverthread &rt = resolverthreads.add();
80
rt.thread = SDL_CreateThread(resolverloop, &rt);
82
SDL_UnlockMutex(resolvermutex);
85
void resolverstop(resolverthread &rt)
87
SDL_LockMutex(resolvermutex);
91
SDL_KillThread(rt.thread);
93
rt.thread = SDL_CreateThread(resolverloop, &rt);
97
SDL_UnlockMutex(resolvermutex);
102
if(resolverthreads.empty()) return;
104
SDL_LockMutex(resolvermutex);
105
resolverqueries.setsize(0);
106
resolverresults.setsize(0);
107
loopv(resolverthreads)
109
resolverthread &rt = resolverthreads[i];
112
SDL_UnlockMutex(resolvermutex);
115
void resolverquery(const char *name)
117
if(resolverthreads.empty()) resolverinit();
119
SDL_LockMutex(resolvermutex);
120
resolverqueries.add(name);
121
SDL_CondSignal(querycond);
122
SDL_UnlockMutex(resolvermutex);
125
bool resolvercheck(const char **name, ENetAddress *address)
127
bool resolved = false;
128
SDL_LockMutex(resolvermutex);
129
if(!resolverresults.empty())
131
resolverresult &rr = resolverresults.pop();
133
address->host = rr.address.host;
136
else loopv(resolverthreads)
138
resolverthread &rt = resolverthreads[i];
139
if(rt.query && totalmillis - rt.starttime > RESOLVERLIMIT)
146
SDL_UnlockMutex(resolvermutex);
150
extern bool isdedicated;
152
bool resolverwait(const char *name, ENetAddress *address)
154
if(isdedicated) return enet_address_set_host(address, name) >= 0;
156
if(resolverthreads.empty()) resolverinit();
158
s_sprintfd(text)("resolving %s... (esc to abort)", name);
159
show_out_of_renderloop_progress(0, text);
161
SDL_LockMutex(resolvermutex);
162
resolverqueries.add(name);
163
SDL_CondSignal(querycond);
164
int starttime = SDL_GetTicks(), timeout = 0;
165
bool resolved = false;
168
SDL_CondWaitTimeout(resultcond, resolvermutex, 250);
169
loopv(resolverresults) if(resolverresults[i].query == name)
171
address->host = resolverresults[i].address.host;
172
resolverresults.remove(i);
178
timeout = SDL_GetTicks() - starttime;
179
show_out_of_renderloop_progress(min(float(timeout)/RESOLVERLIMIT, 1.0f), text);
181
while(SDL_PollEvent(&event))
183
if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) timeout = RESOLVERLIMIT + 1;
186
if(timeout > RESOLVERLIMIT) break;
188
if(!resolved && timeout > RESOLVERLIMIT)
190
loopv(resolverthreads)
192
resolverthread &rt = resolverthreads[i];
193
if(rt.query == name) { resolverstop(rt); break; }
196
SDL_UnlockMutex(resolvermutex);
200
SDL_Thread *connthread = NULL;
201
SDL_mutex *connmutex = NULL;
202
SDL_cond *conncond = NULL;
211
// do this in a thread to prevent timeouts
212
// could set timeouts on sockets, but this is more reliable and gives more control
213
int connectthread(void *data)
215
SDL_LockMutex(connmutex);
216
if(!connthread || SDL_GetThreadID(connthread) != SDL_ThreadID())
218
SDL_UnlockMutex(connmutex);
221
connectdata cd = *(connectdata *)data;
222
SDL_UnlockMutex(connmutex);
224
int result = enet_socket_connect(cd.sock, &cd.address);
226
SDL_LockMutex(connmutex);
227
if(!connthread || SDL_GetThreadID(connthread) != SDL_ThreadID())
229
enet_socket_destroy(cd.sock);
230
SDL_UnlockMutex(connmutex);
233
((connectdata *)data)->result = result;
234
SDL_CondSignal(conncond);
235
SDL_UnlockMutex(connmutex);
240
#define CONNLIMIT 20000
242
int connectwithtimeout(ENetSocket sock, const char *hostname, ENetAddress &address)
246
int result = enet_socket_connect(sock, &address);
247
if(result<0) enet_socket_destroy(sock);
251
s_sprintfd(text)("connecting to %s... (esc to abort)", hostname);
252
show_out_of_renderloop_progress(0, text);
254
if(!connmutex) connmutex = SDL_CreateMutex();
255
if(!conncond) conncond = SDL_CreateCond();
256
SDL_LockMutex(connmutex);
257
connectdata cd = { sock, address, -1 };
258
connthread = SDL_CreateThread(connectthread, &cd);
260
int starttime = SDL_GetTicks(), timeout = 0;
263
if(!SDL_CondWaitTimeout(conncond, connmutex, 250))
265
if(cd.result<0) enet_socket_destroy(sock);
268
timeout = SDL_GetTicks() - starttime;
269
show_out_of_renderloop_progress(min(float(timeout)/CONNLIMIT, 1.0f), text);
271
while(SDL_PollEvent(&event))
273
if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) timeout = CONNLIMIT + 1;
275
if(timeout > CONNLIMIT) break;
278
/* thread will actually timeout eventually if its still trying to connect
279
* so just leave it (and let it destroy socket) instead of causing problems on some platforms by killing it
282
SDL_UnlockMutex(connmutex);
287
vector<serverinfo *> servers;
288
ENetSocket pingsock = ENET_SOCKET_NULL;
291
char *getservername(int n) { return servers[n]->name; }
293
serverinfo *findserverinfo(ENetAddress address)
295
loopv(servers) if(servers[i]->address.host == address.host && servers[i]->port == address.port) return servers[i];
299
serverinfo *getconnectedserverinfo()
301
extern ENetPeer *curpeer;
302
if(!curpeer) return NULL;
303
return findserverinfo(curpeer->address);
306
static serverinfo *newserver(const char *name, uint ip = ENET_HOST_ANY, int port = CUBE_DEFAULT_SERVER_PORT, int weight = 0)
308
serverinfo *si = new serverinfo;
309
si->address.host = ip;
310
si->address.port = CUBE_SERVINFO_PORT(port);
311
si->msweight = weight;
312
if(ip!=ENET_HOST_ANY) si->resolved = serverinfo::RESOLVED;
314
if(name) s_strcpy(si->name, name);
315
else if(ip==ENET_HOST_ANY || enet_address_get_host_ip(&si->address, si->name, sizeof(si->name)) < 0)
322
servers.insert(0, si);
327
void addserver(const char *servername, const char *serverport, const char *weight)
329
int port = atoi(serverport);
330
if(port == 0) port = CUBE_DEFAULT_SERVER_PORT;
332
loopv(servers) if(strcmp(servers[i]->name, servername)==0 && servers[i]->port == port) return;
334
newserver(servername, ENET_HOST_ANY, port, weight ? atoi(weight) : 0);
337
VARP(servpingrate, 1000, 5000, 60000);
338
VARP(maxservpings, 0, 0, 1000);
339
VAR(searchlan, 0, 1, 2);
341
#define PINGBUFSIZE 100
342
static int pingbuf[PINGBUFSIZE], curpingbuf = 0;
344
void pingservers(bool issearch, serverinfo *onlyconnected)
346
if(pingsock == ENET_SOCKET_NULL)
348
pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
349
if(pingsock == ENET_SOCKET_NULL)
351
lastinfo = totalmillis;
354
enet_socket_set_option(pingsock, ENET_SOCKOPT_NONBLOCK, 1);
355
enet_socket_set_option(pingsock, ENET_SOCKOPT_BROADCAST, 1);
358
static uchar ping[MAXTRANS];
359
ucharbuf p(ping, sizeof(ping));
360
curpingbuf = (curpingbuf + 1) % PINGBUFSIZE;
361
pingbuf[(curpingbuf + PINGBUFSIZE / 2) % PINGBUFSIZE] = 0;
362
pingbuf[curpingbuf] = onlyconnected ? 0 : totalmillis;
363
putint(p, curpingbuf + 1); // offset by 1 to avoid extinfo trigger
364
int baselen = p.length();
367
serverinfo *si = onlyconnected;
371
putint(p, EXTPING_SERVERINFO);
372
const char *lang = getalias("LANG");
373
if(!lang || strlen(lang) != 2) lang = "en";
374
loopi(2) putint(p, lang[i]);
376
else putint(p, si->getnames || issearch ? EXTPING_NAMELIST : EXTPING_NOP);
378
buf.dataLength = p.length();
379
enet_socket_send(pingsock, &si->address, &buf, 1);
381
else if(searchlan < 2)
383
static int lastping = 0;
384
if(lastping >= servers.length()) lastping = 0;
385
loopi(maxservpings ? min(servers.length(), maxservpings) : servers.length())
387
serverinfo &si = *servers[lastping];
388
if(++lastping >= servers.length()) lastping = 0;
389
if(si.address.host == ENET_HOST_ANY) continue;
391
putint(p, si.getnames || issearch ? EXTPING_NAMELIST : EXTPING_NOP);
393
buf.dataLength = p.length();
394
enet_socket_send(pingsock, &si.address, &buf, 1);
397
if(searchlan && !onlyconnected)
400
address.host = ENET_HOST_BROADCAST;
401
address.port = CUBE_SERVINFO_PORT_LAN;
403
putint(p, issearch ? EXTPING_NAMELIST : EXTPING_NOP);
405
buf.dataLength = p.length();
406
enet_socket_send(pingsock, &address, &buf, 1);
408
lastinfo = totalmillis;
416
serverinfo &si = *servers[i];
417
if(si.resolved == serverinfo::RESOLVED) continue;
418
if(si.address.host == ENET_HOST_ANY)
420
if(si.resolved == serverinfo::UNRESOLVED) { si.resolved = serverinfo::RESOLVING; resolverquery(si.name); }
424
if(!resolving) return;
426
const char *name = NULL;
427
ENetAddress addr = { ENET_HOST_ANY, ENET_PORT_ANY };
428
while(resolvercheck(&name, &addr))
432
serverinfo &si = *servers[i];
435
si.resolved = serverinfo::RESOLVED;
436
si.address.host = addr.host;
437
addr.host = ENET_HOST_ANY;
444
#define MAXINFOLINELEN 100 // including color codes
448
if(pingsock == ENET_SOCKET_NULL) return;
449
enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE;
452
static uchar ping[MAXTRANS];
453
static char text[MAXTRANS];
455
buf.dataLength = sizeof(ping);
456
while(enet_socket_wait(pingsock, &events, 0) >= 0 && events)
458
int len = enet_socket_receive(pingsock, &addr, &buf, 1);
460
serverinfo *si = NULL;
461
loopv(servers) if(addr.host == servers[i]->address.host && addr.port == servers[i]->address.port)
466
if(!si && searchlan) si = newserver(NULL, addr.host, CUBE_SERVINFO_TO_SERV_PORT(addr.port));
469
ucharbuf p(ping, len);
470
si->lastpingmillis = totalmillis;
471
int pingtm = pingbuf[(getint(p) - 1) % PINGBUFSIZE];
472
if(pingtm) si->ping = totalmillis - pingtm;
473
int query = getint(p);
475
{ // cleanup additional query info
476
case EXTPING_SERVERINFO:
480
si->protocol = getint(p);
481
if(si->protocol!=PROTOCOL_VERSION) si->ping = 9998;
482
si->mode = getint(p);
483
si->numplayers = getint(p);
484
si->minremain = getint(p);
486
filtertext(si->map, text, 1);
488
filterservdesc(si->sdesc, text);
489
s_strcpy(si->description, si->sdesc);
490
si->maxclients = getint(p);
493
si->pongflags = getint(p);
494
if(p.remaining() && getint(p) == query)
498
case EXTPING_NAMELIST:
500
si->playernames.setsizenodelete(0);
501
ucharbuf q(si->namedata, sizeof(si->namedata));
502
loopi(si->numplayers)
505
filtertext(text, text, 0);
506
if(text[0] && !p.overread())
508
si->playernames.add((const char *)si->namedata + q.length());
515
case EXTPING_SERVERINFO:
517
si->infotexts.setsizenodelete(0);
518
ucharbuf q(si->textdata, sizeof(si->textdata));
521
if(strlen(text) != 2)
523
si->infotexts.add((char *)si->textdata);
524
sendstring("this server does not provide additional information", q);
527
strcpy(si->lang, text);
531
if(*text && !p.overread())
533
text[MAXINFOLINELEN] = '\0';
534
cutcolorstring(text, 80);
535
si->infotexts.add((char *)si->textdata + q.length());
536
sendstring(strcmp(text, ".") ? text : "", q);
544
si->infotexts.setsizenodelete(0);
545
ucharbuf q(si->textdata, sizeof(si->textdata));
551
if(*text && !p.overread())
553
text[MAXINFOLINELEN] = '\0';
554
loopi(n) s_strcatf(text, ", %d", getint(p));
555
si->infotexts.add((char *)si->textdata + q.length());
556
sendstring(text , q);
569
if(si->pongflags > 0)
572
int mm = si->pongflags >> PONGFLAG_MASTERMODE;
573
if(si->pongflags & (1 << PONGFLAG_BANNED))
574
sp = "you are banned from this server";
575
if(si->pongflags & (1 << PONGFLAG_BLACKLIST))
576
sp = "you are blacklisted on this server";
577
else if(si->pongflags & (1 << PONGFLAG_PASSWORD))
578
sp = "this server is password-protected";
579
else if(mm) sp = mmfullname(mm);
580
s_sprintf(si->description)("%s \f1(%s)", si->sdesc, sp);
585
enum { SBS_PING = 0, SBS_NUMPL, SBS_MAXPL, SBS_MINREM, SBS_MAP, SBS_MODE, SBS_IP, SBS_DESC, NUMSERVSORT };
587
VARP(serversort, 0, 0, NUMSERVSORT-1);
588
VARP(serversortdir, 0, 0, 1);
589
VARP(showonlygoodservers, 0, 0, 1);
590
VAR(shownamesinbrowser, 0, 0, 1);
591
VARP(showminremain, 0, 0, 1);
592
VARP(serversortpreferofficial, 0, 1, 1);
594
void serversortprepare()
598
serverinfo &si = *servers[i];
599
// basic group weights: used(700) - empty(500) - unusable(200)
600
if(si.protocol != PROTOCOL_VERSION) si.weight += 200;
601
else if(!si.numplayers) si.weight += 500;
605
if(serversortpreferofficial && securemapcheck(si.map, false)) si.weight += 100;
607
si.weight += si.msweight;
611
int sicompare(serverinfo **ap, serverinfo **bp)
613
serverinfo *a = *ap, *b = *bp;
614
int dir = serversortdir ? -1 : 1;
615
if(a->weight > b->weight) return -dir;
616
if(a->weight < b->weight) return dir;
617
enet_uint32 ai = ntohl(a->address.host), bi = ntohl(b->address.host);
618
int ips = ai < bi ? -dir : (ai > bi ? dir : 0); // most steady base sorting
621
case SBS_NUMPL: // player number
622
if(a->numplayers < b->numplayers) return dir;
623
if(a->numplayers > b->numplayers) return -dir;
625
case SBS_MAXPL: // maxplayers
626
if(a->maxclients < b->maxclients) return dir;
627
if(a->maxclients > b->maxclients) return -dir;
629
case SBS_MINREM: // minutes remaining
630
if(a->minremain < b->minremain) return dir;
631
if(a->minremain > b->minremain) return -dir;
633
case SBS_DESC: // description
635
static string ad, bd;
636
filtertext(ad, a->sdesc);
637
filtertext(bd, b->sdesc);
638
if(!ad[0] && bd[0]) return dir;
639
if(ad[0] && !bd[0]) return -dir;
640
int mdir = dir * strcasecmp(ad, bd);
641
if(mdir) return mdir;
649
int mdir = dir * strcasecmp(a->map, b->map);
650
if(mdir) return mdir;
653
case SBS_MODE: // mode
655
const char *am = modestr(a->mode, modeacronyms > 0), *bm = modestr(b->mode, modeacronyms > 0);
656
int mdir = dir * strcasecmp(am, bm);
657
if(mdir) return mdir;
661
if(serversort == SBS_PING || (!a->numplayers && !b->numplayers)) // ping
663
if(a->ping > b->ping) return dir;
664
if(a->ping < b->ping) return -dir;
667
if(a->port > b->port) return dir;
671
void *servmenu = NULL, *searchmenu = NULL, *serverinfomenu = NULL;
672
vector<char *> namelists;
674
string cursearch, cursearchuc;
676
void searchnickname(const char *name)
678
if(!name || !name[0]) return;
679
s_strcpy(cursearch, name);
680
s_strcpy(cursearchuc, name);
681
strtoupper(cursearchuc);
684
COMMAND(searchnickname, ARG_1STR);
686
VAR(showallservers, 0, 1, 1);
688
bool matchplayername(const char *name)
690
static string nameuc;
691
s_strcpy(nameuc, name);
693
return strstr(nameuc, cursearchuc) != NULL;
696
VARP(serverbrowserhideip, 0, 0, 2);
697
VARP(serverbrowserhidefavtag, 0, 1, 2);
698
VAR(showweights, 0, 0, 1);
700
vector<char *> favcats;
701
const char *fc_als[] = { "weight", "tag", "desc", "red", "green", "blue", "alpha", "keys", "ignore" };
702
enum { FC_WEIGHT = 0, FC_TAG, FC_DESC, FC_RED, FC_GREEN, FC_BLUE, FC_ALPHA, FC_KEYS, FC_IGNORE, FC_NUM };
704
VARF(showonlyfavourites, 0, 0, 100,
706
if(showonlyfavourites > favcats.length())
708
conoutf("showonlyfavourites: %d out of range (0..%d)", showonlyfavourites, favcats.length());
709
showonlyfavourites = 0;
713
const char *favcatargname(const char *refdes, int par)
715
static string text[3];
717
if(par < 0 || par >= FC_NUM) return NULL;
719
s_sprintf(text[i])("sbfavourite_%s_%s", refdes, fc_als[par]);
723
void addfavcategory(const char *refdes)
727
if(!refdes) { intret(0); return; }
728
filtertext(text, refdes);
729
if(!text[0]) { intret(0); return; }
730
loopv(favcats) if(!strcmp(favcats[i], text)) { intret(i + 1); return; }
731
favcats.add(newstring(text));
732
bool oldpersist = persistidents;
733
persistidents = false; // only keep changed values
734
loopi(FC_NUM) alx[i] = getalias(favcatargname(text, i)) ? 1 : 0;
737
s_sprintf(val)("%d", *text & (1 << i) ? 90 : 10);
738
if(!alx[i + FC_RED]) alias(favcatargname(text, i + FC_RED), val);
740
s_sprintf(val)("favourites %d", favcats.length());
741
const int defk[] = { FC_WEIGHT, FC_DESC, FC_TAG, FC_KEYS, FC_IGNORE, FC_ALPHA };
742
const char *defv[] = { "0", val, refdes, "", "", "20" };
743
loopi(sizeof(defk)/sizeof(defk[0])) { if(!alx[defk[i]]) alias(favcatargname(text, defk[i]), defv[i]); }
744
persistidents = oldpersist;
745
intret(favcats.length());
750
const char *str = conc(&favcats[0], favcats.length(), true);
755
bool favcatcheckkey(serverinfo &si, const char *key)
756
{ // IP:port, #gamemode ,%mapname, desc
758
if(isdigit(*key)) // IP
760
s_sprintf(text)("%s:%d", si.name, si.port);
761
return !strncmp(text, key, strlen(key));
763
else if(si.address.host != ENET_HOST_ANY && si.ping != 9999) switch(*key)
766
return si.map[0] && si.mode == atoi(key + 1);
768
strtoupper(text, si.map);
769
strtoupper(keyuc, key + 1);
770
return si.map[0] && key[1] && strstr(text, keyuc);
772
return si.ping > atoi(key + 1);
774
return !favcatcheckkey(si, key + 1);
776
return si.map[0] && favcatcheckkey(si, key + 1);
780
s_sprintf(text)("%s \"%s\" %d %d, %d %d %d \"%s\" %d %d", key + 1, si.map, si.mode, si.ping, si.minremain, si.numplayers, si.maxclients, si.name, si.port, si.pongflags);
781
filtertext(text, text, 1);
783
for(const char *p = text; (p = strchr(p, '\"')); p++) cnt++;
784
return cnt == 4 && execute(text);
788
filtertext(text, si.sdesc);
789
return *key && strstr(text, key);
794
const char *favcatcheck(serverinfo &si, const char *ckeys)
796
if(!ckeys) return NULL;
797
static char *nkeys = NULL;
798
const char *sep = " \t\n\r";
799
char *keys = newstring(ckeys), *k = strtok(keys, sep);
802
nkeys = newstring(strlen(ckeys));
806
if(favcatcheckkey(si, k)) res = true;
809
if(*nkeys) strcat(nkeys, " ");
812
k = strtok(NULL, sep);
815
return res ? nkeys : NULL;
818
vector<const char *> favcattags;
820
bool assignserverfavourites()
823
const char *alx[FC_NUM], *sep = " \t\n\r";
824
favcattags.setsizenodelete(0);
826
loopv(servers) { servers[i]->favcat = -1; servers[i]->weight = 0; }
829
loopi(FC_NUM) { alx[i] = getalias(favcatargname(favcats[j], i)); alxn[i] = alx[i] ? atoi(alx[i]) : 0; }
830
favcattags.add(alx[FC_TAG]);
831
bool showonlythiscat = j == showonlyfavourites - 1;
832
char *keys = newstring(alx[FC_KEYS]), *k = strtok(keys, sep);
837
serverinfo &si = *servers[i];
838
if((!alxn[FC_IGNORE] || showonlythiscat) && favcatcheckkey(si, k))
840
si.weight += alxn[FC_WEIGHT];
842
if(si.favcat == -1 || showonlythiscat)
847
if(!si.bgcolor) si.bgcolor = new color;
848
new (si.bgcolor) color(((float)alxn[FC_RED])/100, ((float)alxn[FC_GREEN])/100, ((float)alxn[FC_BLUE])/100, ((float)alxn[FC_ALPHA])/100);
853
k = strtok(NULL, sep);
857
loopv(servers) if(servers[i]->favcat == -1)
859
DELETEA(servers[i]->bgcolor);
864
COMMAND(addfavcategory, ARG_1STR);
865
COMMAND(listfavcats, ARG_NONE);
867
void refreshservers(void *menu, bool init)
869
static int servermenumillis;
870
static bool usedselect = false;
872
static serverinfo *lastselectedserver = NULL;
873
bool issearch = menu == searchmenu;
874
bool isinfo = menu == serverinfomenu;
875
bool isscoreboard = menu == NULL;
877
serverinfo *curserver = getconnectedserverinfo(), *oldsel = NULL;
880
loopi(PINGBUFSIZE) if(pingbuf[i] < totalmillis) pingbuf[i] = 0;
881
if(resolverthreads.empty()) resolverinit();
882
else resolverclear();
883
loopv(servers) resolverquery(servers[i]->name);
884
servermenumillis = totalmillis;
886
if(menu && curserver) oldsel = curserver;
893
if(lastselectedserver)
896
loopv(servers) if(lastselectedserver == servers[i]) { found = true; break; }
897
if(!found) lastselectedserver = NULL;
899
menutitle(menu, "extended server information");
901
static string infotext;
902
static char dummy = '\0';
903
if(lastselectedserver)
905
serverinfo &si = *lastselectedserver;
906
s_sprintf(si.full)("%s:%d %s", si.name, si.port, si.sdesc);
907
menumanual(menu, si.full);
908
menumanual(menu, &dummy);
909
if(si.infotexts.length())
912
loopv(si.infotexts) menumanual(menu, si.infotexts[i]);
916
s_strcpy(infotext, "-- waiting for server response --");
918
if(init || totalmillis - lastinfo >= servpingrate) pingservers(false, lastselectedserver);
922
s_strcpy(infotext, " -- no server selected --");
923
if(*infotext) menumanual(menu, infotext);
926
if((init && issearch) || totalmillis - lastinfo >= (servpingrate * (issearch ? 2 : 1))/(maxservpings ? (servers.length() + maxservpings - 1) / maxservpings : 1))
927
pingservers(issearch, isscoreboard ? curserver : NULL);
928
if(!init && menu)// && servers.inrange(((gmenu *)menu)->menusel))
930
serverinfo *foundserver = NULL;
931
loopv(servers) if(servers[i]->menuline_from == ((gmenu *)menu)->menusel && servers[i]->menuline_to > servers[i]->menuline_from) { foundserver = servers[i]; break; }
934
if((usedselect || ((gmenu *)menu)->menusel > 0)) oldsel = foundserver;
935
lastselectedserver = foundserver;
938
if(isscoreboard) lastselectedserver = curserver;
940
bool showfavtag = (assignserverfavourites() || !serverbrowserhidefavtag) && serverbrowserhidefavtag != 2;
942
servers.sort(sicompare);
945
static const char *titles[NUMSERVSORT] =
947
"%s\fs\f0ping\fr\tplr\tserver%s%s", // 0: ping
948
"%sping\t\fs\f0plr\fr\tserver%s%s", // 1: player number
949
"%sping\tplr\tserver (\fs\f0max players\fr)%s%s", // 2: maxplayers
950
"%sping\tplr\fs\f0\fr\tserver (\fs\f0minutes remaining\fr)%s%s", // 3: minutes remaining
951
"%sping\tplr\tserver (\fs\f0map\fr)%s%s", // 4: map
952
"%sping\tplr\tserver (\fs\f0game mode\fr)%s%s", // 5: mode
953
"%sping\tplr\tserver (\fs\f0IP\fr)%s%s", // 6: IP
954
"%sping\tplr\tserver (\fs\f0description\fr)%s%s" // 7: description
956
bool showmr = showminremain || serversort == SBS_MINREM;
957
s_sprintf(title)(titles[serversort], showfavtag ? "fav\t" : "", issearch ? " search results for \f3" : " (F1: Help)", issearch ? cursearch : "");
958
menutitle(menu, title);
962
bool sbconnectexists = identexists("sbconnect");
965
serverinfo &si = *servers[i];
966
si.menuline_to = si.menuline_from = ((gmenu *)menu)->items.length();
967
if(!showallservers && si.lastpingmillis < servermenumillis) continue; // no pong yet
968
int banned = ((si.pongflags >> PONGFLAG_BANNED) & 1) | ((si.pongflags >> (PONGFLAG_BLACKLIST - 1)) & 2);
969
bool showthisone = !(banned && showonlygoodservers) && !(showonlyfavourites > 0 && si.favcat != showonlyfavourites - 1);
970
bool serverfull = si.numplayers >= si.maxclients;
971
bool needspasswd = (si.pongflags & (1 << PONGFLAG_PASSWORD)) > 0;
972
bool isprivate = (si.pongflags >> PONGFLAG_MASTERMODE) > 0;
973
char basecolor = banned ? '4' : (curserver == servers[i] ? '1' : '5');
974
char plnumcolor = serverfull ? '2' : (needspasswd ? '3' : (isprivate ? '1' : basecolor));
975
if(si.address.host != ENET_HOST_ANY && si.ping != 9999)
977
if(si.protocol!=PROTOCOL_VERSION)
979
if(!showonlygoodservers) s_sprintf(si.full)("%s:%d [%s]", si.name, si.port, si.protocol<0 ? "modded version" : (si.protocol<PROTOCOL_VERSION ? "older protocol" : "newer protocol"));
980
else showthisone = false;
984
filterrichtext(text, si.favcat > -1 ? favcattags[si.favcat] : "");
985
if(showweights) s_strcatf(text, "(%d)", si.weight);
986
s_sprintf(si.full)(showfavtag ? "\fs%s\fr\t" : "", text);
987
s_strcatf(si.full, "\fs\f%c%d\t\fs\f%c%d/%d\fr\t", basecolor, si.ping, plnumcolor, si.numplayers, si.maxclients);
990
s_strcatf(si.full, "%s, %s", si.map, modestr(si.mode, modeacronyms > 0));
991
if(showmr) s_strcatf(si.full, ", (%d)", si.minremain);
993
else s_strcatf(si.full, "empty");
994
s_strcatf(si.full, serverbrowserhideip < 2 ? ": \fs%s%s:%d\fr" : ": ", serverbrowserhideip == 1 ? "\f4" : "", si.name, si.port);
995
s_strcatf(si.full, "\fr %s", si.sdesc);
1000
if(!showonlygoodservers) s_sprintf(si.full)(si.address.host != ENET_HOST_ANY ? "%s:%d [waiting for server response]" : "%s:%d [unknown host]", si.name, si.port);
1001
else showthisone = false;
1003
if(issearch && showthisone)
1006
loopvj(si.playernames) if(matchplayername(si.playernames[j])) { found = true; break; };
1007
if(!found) showthisone = false;
1011
cutcolorstring(si.full, 76); // cut off too long server descriptions
1012
cutcolorstring(si.description, 76);
1015
filtertext(text, si.sdesc);
1016
for(char *p = text; (p = strchr(p, '\"')); *p++ = ' ');
1018
s_sprintf(si.cmd)("sbconnect %s %d %d %d %d %d \"%s\"", si.name, si.port, serverfull ?1:0, needspasswd ?1:0, isprivate ?1:0, banned, text);
1020
else s_sprintf(si.cmd)("connect %s %d", si.name, si.port);
1021
menumanual(menu, si.full, si.cmd, si.bgcolor, si.description);
1022
if(!issearch && servers[i] == oldsel)
1024
((gmenu *)menu)->menusel = ((gmenu *)menu)->items.length() - 1;
1026
si.getnames = shownamesinbrowser ? 1 : 0;
1028
if((shownamesinbrowser && servers[i] == oldsel && si.playernames.length()) || issearch)
1032
loopvj(si.playernames)
1036
if(namelists.length() < ++curnl) namelists.add(newstringbuf());
1037
t = namelists[curnl - 1];
1038
s_strcpy(t, showfavtag ? "\t\t" : "\t");
1040
s_strcatf(t, " \t\fs%s%s\fr", !issearch || matchplayername(si.playernames[j]) ? "" : "\f4" ,si.playernames[j]);
1044
menumanual(menu, t, NULL, NULL, NULL);
1048
if(cur) menumanual(menu, t, NULL, NULL, NULL);
1051
si.menuline_to = ((gmenu *)menu)->items.length();
1053
static string notfoundmsg;
1058
s_sprintf(notfoundmsg)("\t\tpattern \fs\f3%s\fr not found.", cursearch);
1059
menumanual(menu, notfoundmsg, NULL, NULL, NULL);
1062
else if(!((gmenu *)menu)->items.length() && showonlyfavourites && favcats.inrange(showonlyfavourites - 1))
1064
const char *desc = getalias(favcatargname(favcats[showonlyfavourites - 1], FC_DESC));
1065
s_sprintf(notfoundmsg)("no servers in category \f2%s", desc ? desc : favcattags[showonlyfavourites - 1]);
1066
menumanual(menu, notfoundmsg, NULL, NULL, NULL);
1071
bool serverskey(void *menu, int code, bool isdown, int unicode)
1073
const int fk[] = { SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, SDLK_7, SDLK_8, SDLK_9, SDLK_0 };
1074
if(!isdown) return false;
1075
loopi(sizeof(fk)/sizeof(fk[0])) if(code == fk[i] && favcats.inrange(i))
1077
int sel = ((gmenu *)menu)->menusel;
1078
loopvj(servers) if(menu && (servers[j]->menuline_from <= sel && servers[j]->menuline_to > sel))
1080
const char *keyalias = favcatargname(favcats[i], FC_KEYS), *key = getalias(keyalias), *rest = favcatcheck(*servers[j], key), *desc = getalias(favcatargname(favcats[i], FC_DESC));
1081
if(!desc) desc = "";
1083
{ // remove from favourite group
1084
conoutf("removing server \"\fs%s\fr\" from favourites category '\fs%s\fr' (rest '%s')", servers[j]->sdesc, desc, rest);
1085
alias(keyalias, rest);
1088
{ // add IP:port to group
1089
s_sprintfd(text)("%s:%d", servers[j]->name, servers[j]->port);
1092
char *newkey = newstring(key, strlen(text) + 1 + strlen(key));
1093
strcat(newkey, " ");
1094
strcat(newkey, text);
1095
alias(keyalias, newkey);
1098
else alias(keyalias, text);
1099
conoutf("adding server \"\fs%s\fr\" to favourites category '\fs%s\fr' (new '%s')", servers[j]->sdesc, desc, getalias(keyalias));
1107
if(menu) ((gmenu *)menu)->menusel = 0;
1111
serversort = (serversort+NUMSERVSORT-1) % NUMSERVSORT;
1115
serversort = (serversort+1) % NUMSERVSORT;
1119
updatefrommaster(1);
1123
serversortdir = serversortdir ? 0 : 1;
1127
showmenu("serverinfo");
1130
if(menu == searchmenu) return false;
1134
showmenu("serverbrowser help");
1138
shownamesinbrowser = shownamesinbrowser ? 0 : 1;
1142
showmenu("search player");
1146
showmenu("edit favourites");
1150
showonlygoodservers = showonlygoodservers ? 0 : 1;
1154
showminremain = showminremain ? 0 : 1;
1163
servers.deletecontentsp();
1166
VARP(masterupdatefrequency, 1, 60*60, 24*60*60);
1168
void updatefrommaster(int force)
1170
static int lastupdate = 0;
1171
if(!force && lastupdate && totalmillis-lastupdate<masterupdatefrequency*1000) return;
1174
uchar *reply = retrieveservers(buf, sizeof(buf));
1175
if(!*reply || strstr((char *)reply, "<html>") || strstr((char *)reply, "<HTML>")) conoutf("master server not replying");
1178
// preserve currently connected server from deletion
1179
serverinfo *curserver = getconnectedserverinfo();
1180
string curname, curport, curweight;
1183
s_strcpy(curname, curserver->name);
1184
s_sprintf(curport)("%d", curserver->port);
1185
s_sprintf(curweight)("%d", curserver->msweight);
1189
execute((char *)reply);
1191
if(curserver) addserver(curname, curport, curweight);
1192
lastupdate = totalmillis;
1196
COMMAND(addserver, ARG_3STR);
1197
COMMAND(clearservers, ARG_NONE);
1198
COMMAND(updatefrommaster, ARG_1INT);
1200
void writeservercfg()
1202
FILE *f = openfile(path("config/servers.cfg", true), "w");
1204
fprintf(f, "// servers connected to are added here automatically\n");
1207
fprintf(f, "\naddserver %s %d", servers[i]->name, servers[i]->port);
1208
if(servers[i]->msweight) fprintf(f, " %d", servers[i]->msweight);