~ubuntu-branches/debian/stretch/assaultcube-data/stretch

« back to all changes in this revision

Viewing changes to source/src/serverbrowser.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Gonéri Le Bouder, Ansgar Burchardt, Gonéri Le Bouder
  • Date: 2010-04-02 23:37:55 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20100402233755-kf74fxwlu634o6vg
Tags: 1.0.4+repack1-1
[ Ansgar Burchardt ]
* debian/control: fix typo in short description

[ Gonéri Le Bouder ]
* Upgrade to 1.0.4
* bump standards-version to 3.8.4
* Add Depends: ${misc:Depends} just to avoid a lintian warning
* Add a debian/source/format file for the same reason

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window management
 
2
 
 
3
#include "pch.h"
 
4
#include "cube.h"
 
5
#ifdef __APPLE__
 
6
#include <pthread.h>
 
7
#endif
 
8
#include "SDL_thread.h"
 
9
 
 
10
extern bool isdedicated;
 
11
 
 
12
struct resolverthread
 
13
{
 
14
    SDL_Thread *thread;
 
15
    const char *query;
 
16
    int starttime;
 
17
};
 
18
 
 
19
struct resolverresult
 
20
{
 
21
    const char *query;
 
22
    ENetAddress address;
 
23
};
 
24
 
 
25
vector<resolverthread> resolverthreads;
 
26
vector<const char *> resolverqueries;
 
27
vector<resolverresult> resolverresults;
 
28
SDL_mutex *resolvermutex;
 
29
SDL_cond *querycond, *resultcond;
 
30
 
 
31
#define RESOLVERTHREADS 1
 
32
#define RESOLVERLIMIT 3000
 
33
 
 
34
int resolverloop(void * data)
 
35
{
 
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())
 
41
        return 0;
 
42
    while(thread == rt->thread)
 
43
    {
 
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);
 
49
 
 
50
        ENetAddress address = { ENET_HOST_ANY, ENET_PORT_ANY };
 
51
        enet_address_set_host(&address, rt->query);
 
52
 
 
53
        SDL_LockMutex(resolvermutex);
 
54
        if(rt->query && thread == rt->thread)
 
55
        {
 
56
            resolverresult &rr = resolverresults.add();
 
57
            rr.query = rt->query;
 
58
            rr.address = address;
 
59
            rt->query = NULL;
 
60
            rt->starttime = 0;
 
61
            SDL_CondSignal(resultcond);
 
62
        }
 
63
        SDL_UnlockMutex(resolvermutex);
 
64
    }
 
65
    return 0;
 
66
}
 
67
 
 
68
void resolverinit()
 
69
{
 
70
    resolvermutex = SDL_CreateMutex();
 
71
    querycond = SDL_CreateCond();
 
72
    resultcond = SDL_CreateCond();
 
73
 
 
74
    SDL_LockMutex(resolvermutex);
 
75
    loopi(RESOLVERTHREADS)
 
76
    {
 
77
        resolverthread &rt = resolverthreads.add();
 
78
        rt.query = NULL;
 
79
        rt.starttime = 0;
 
80
        rt.thread = SDL_CreateThread(resolverloop, &rt);
 
81
    }
 
82
    SDL_UnlockMutex(resolvermutex);
 
83
}
 
84
 
 
85
void resolverstop(resolverthread &rt)
 
86
{
 
87
    SDL_LockMutex(resolvermutex);
 
88
    if(rt.query)
 
89
    {
 
90
#ifndef __APPLE__
 
91
        SDL_KillThread(rt.thread);
 
92
#endif
 
93
        rt.thread = SDL_CreateThread(resolverloop, &rt);
 
94
    }
 
95
    rt.query = NULL;
 
96
    rt.starttime = 0;
 
97
    SDL_UnlockMutex(resolvermutex);
 
98
}
 
99
 
 
100
void resolverclear()
 
101
{
 
102
    if(resolverthreads.empty()) return;
 
103
 
 
104
    SDL_LockMutex(resolvermutex);
 
105
    resolverqueries.setsize(0);
 
106
    resolverresults.setsize(0);
 
107
    loopv(resolverthreads)
 
108
    {
 
109
        resolverthread &rt = resolverthreads[i];
 
110
        resolverstop(rt);
 
111
    }
 
112
    SDL_UnlockMutex(resolvermutex);
 
113
}
 
114
 
 
115
void resolverquery(const char *name)
 
116
{
 
117
    if(resolverthreads.empty()) resolverinit();
 
118
 
 
119
    SDL_LockMutex(resolvermutex);
 
120
    resolverqueries.add(name);
 
121
    SDL_CondSignal(querycond);
 
122
    SDL_UnlockMutex(resolvermutex);
 
123
}
 
124
 
 
125
bool resolvercheck(const char **name, ENetAddress *address)
 
126
{
 
127
    bool resolved = false;
 
128
    SDL_LockMutex(resolvermutex);
 
129
    if(!resolverresults.empty())
 
130
    {
 
131
        resolverresult &rr = resolverresults.pop();
 
132
        *name = rr.query;
 
133
        address->host = rr.address.host;
 
134
        resolved = true;
 
135
    }
 
136
    else loopv(resolverthreads)
 
137
    {
 
138
        resolverthread &rt = resolverthreads[i];
 
139
        if(rt.query && totalmillis - rt.starttime > RESOLVERLIMIT)
 
140
        {
 
141
            resolverstop(rt);
 
142
            *name = rt.query;
 
143
            resolved = true;
 
144
        }
 
145
    }
 
146
    SDL_UnlockMutex(resolvermutex);
 
147
    return resolved;
 
148
}
 
149
 
 
150
extern bool isdedicated;
 
151
 
 
152
bool resolverwait(const char *name, ENetAddress *address)
 
153
{
 
154
    if(isdedicated) return enet_address_set_host(address, name) >= 0;
 
155
 
 
156
    if(resolverthreads.empty()) resolverinit();
 
157
 
 
158
    s_sprintfd(text)("resolving %s... (esc to abort)", name);
 
159
    show_out_of_renderloop_progress(0, text);
 
160
 
 
161
    SDL_LockMutex(resolvermutex);
 
162
    resolverqueries.add(name);
 
163
    SDL_CondSignal(querycond);
 
164
    int starttime = SDL_GetTicks(), timeout = 0;
 
165
    bool resolved = false;
 
166
    for(;;)
 
167
    {
 
168
        SDL_CondWaitTimeout(resultcond, resolvermutex, 250);
 
169
        loopv(resolverresults) if(resolverresults[i].query == name)
 
170
        {
 
171
            address->host = resolverresults[i].address.host;
 
172
            resolverresults.remove(i);
 
173
            resolved = true;
 
174
            break;
 
175
        }
 
176
        if(resolved) break;
 
177
 
 
178
        timeout = SDL_GetTicks() - starttime;
 
179
        show_out_of_renderloop_progress(min(float(timeout)/RESOLVERLIMIT, 1.0f), text);
 
180
        SDL_Event event;
 
181
        while(SDL_PollEvent(&event))
 
182
        {
 
183
            if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) timeout = RESOLVERLIMIT + 1;
 
184
        }
 
185
 
 
186
        if(timeout > RESOLVERLIMIT) break;
 
187
    }
 
188
    if(!resolved && timeout > RESOLVERLIMIT)
 
189
    {
 
190
        loopv(resolverthreads)
 
191
        {
 
192
            resolverthread &rt = resolverthreads[i];
 
193
            if(rt.query == name) { resolverstop(rt); break; }
 
194
        }
 
195
    }
 
196
    SDL_UnlockMutex(resolvermutex);
 
197
    return resolved;
 
198
}
 
199
 
 
200
SDL_Thread *connthread = NULL;
 
201
SDL_mutex *connmutex = NULL;
 
202
SDL_cond *conncond = NULL;
 
203
 
 
204
struct connectdata
 
205
{
 
206
    ENetSocket sock;
 
207
    ENetAddress address;
 
208
    int result;
 
209
};
 
210
 
 
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)
 
214
{
 
215
    SDL_LockMutex(connmutex);
 
216
    if(!connthread || SDL_GetThreadID(connthread) != SDL_ThreadID())
 
217
    {
 
218
        SDL_UnlockMutex(connmutex);
 
219
        return 0;
 
220
    }
 
221
    connectdata cd = *(connectdata *)data;
 
222
    SDL_UnlockMutex(connmutex);
 
223
 
 
224
    int result = enet_socket_connect(cd.sock, &cd.address);
 
225
 
 
226
    SDL_LockMutex(connmutex);
 
227
    if(!connthread || SDL_GetThreadID(connthread) != SDL_ThreadID())
 
228
    {
 
229
        enet_socket_destroy(cd.sock);
 
230
        SDL_UnlockMutex(connmutex);
 
231
        return 0;
 
232
    }
 
233
    ((connectdata *)data)->result = result;
 
234
    SDL_CondSignal(conncond);
 
235
    SDL_UnlockMutex(connmutex);
 
236
 
 
237
    return 0;
 
238
}
 
239
 
 
240
#define CONNLIMIT 20000
 
241
 
 
242
int connectwithtimeout(ENetSocket sock, const char *hostname, ENetAddress &address)
 
243
{
 
244
    if(isdedicated)
 
245
    {
 
246
        int result = enet_socket_connect(sock, &address);
 
247
        if(result<0) enet_socket_destroy(sock);
 
248
        return result;
 
249
    }
 
250
 
 
251
    s_sprintfd(text)("connecting to %s... (esc to abort)", hostname);
 
252
    show_out_of_renderloop_progress(0, text);
 
253
 
 
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);
 
259
 
 
260
    int starttime = SDL_GetTicks(), timeout = 0;
 
261
    for(;;)
 
262
    {
 
263
        if(!SDL_CondWaitTimeout(conncond, connmutex, 250))
 
264
        {
 
265
            if(cd.result<0) enet_socket_destroy(sock);
 
266
            break;
 
267
        }
 
268
        timeout = SDL_GetTicks() - starttime;
 
269
        show_out_of_renderloop_progress(min(float(timeout)/CONNLIMIT, 1.0f), text);
 
270
        SDL_Event event;
 
271
        while(SDL_PollEvent(&event))
 
272
        {
 
273
            if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) timeout = CONNLIMIT + 1;
 
274
        }
 
275
        if(timeout > CONNLIMIT) break;
 
276
    }
 
277
 
 
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
 
280
     */
 
281
    connthread = NULL;
 
282
    SDL_UnlockMutex(connmutex);
 
283
 
 
284
    return cd.result;
 
285
}
 
286
 
 
287
vector<serverinfo *> servers;
 
288
ENetSocket pingsock = ENET_SOCKET_NULL;
 
289
int lastinfo = 0;
 
290
 
 
291
char *getservername(int n) { return servers[n]->name; }
 
292
 
 
293
serverinfo *findserverinfo(ENetAddress address)
 
294
{
 
295
    loopv(servers) if(servers[i]->address.host == address.host && servers[i]->port == address.port) return servers[i];
 
296
    return NULL;
 
297
}
 
298
 
 
299
serverinfo *getconnectedserverinfo()
 
300
{
 
301
    extern ENetPeer *curpeer;
 
302
    if(!curpeer) return NULL;
 
303
    return findserverinfo(curpeer->address);
 
304
}
 
305
 
 
306
static serverinfo *newserver(const char *name, uint ip = ENET_HOST_ANY, int port = CUBE_DEFAULT_SERVER_PORT, int weight = 0)
 
307
{
 
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;
 
313
 
 
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)
 
316
    {
 
317
        delete si;
 
318
        return NULL;
 
319
    }
 
320
    si->port = port;
 
321
 
 
322
    servers.insert(0, si);
 
323
 
 
324
    return si;
 
325
}
 
326
 
 
327
void addserver(const char *servername, const char *serverport, const char *weight)
 
328
{
 
329
    int port = atoi(serverport);
 
330
    if(port == 0) port = CUBE_DEFAULT_SERVER_PORT;
 
331
 
 
332
    loopv(servers) if(strcmp(servers[i]->name, servername)==0 && servers[i]->port == port) return;
 
333
 
 
334
    newserver(servername, ENET_HOST_ANY, port, weight ? atoi(weight) : 0);
 
335
}
 
336
 
 
337
VARP(servpingrate, 1000, 5000, 60000);
 
338
VARP(maxservpings, 0, 0, 1000);
 
339
VAR(searchlan, 0, 1, 2);
 
340
 
 
341
#define PINGBUFSIZE 100
 
342
static int pingbuf[PINGBUFSIZE], curpingbuf = 0;
 
343
 
 
344
void pingservers(bool issearch, serverinfo *onlyconnected)
 
345
{
 
346
    if(pingsock == ENET_SOCKET_NULL)
 
347
    {
 
348
        pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
 
349
        if(pingsock == ENET_SOCKET_NULL)
 
350
        {
 
351
            lastinfo = totalmillis;
 
352
            return;
 
353
        }
 
354
        enet_socket_set_option(pingsock, ENET_SOCKOPT_NONBLOCK, 1);
 
355
        enet_socket_set_option(pingsock, ENET_SOCKOPT_BROADCAST, 1);
 
356
    }
 
357
    ENetBuffer buf;
 
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();
 
365
    if(onlyconnected)
 
366
    {
 
367
        serverinfo *si = onlyconnected;
 
368
        //p.len = baselen;
 
369
        if(si->getinfo)
 
370
        {
 
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]);
 
375
        }
 
376
        else putint(p, si->getnames || issearch ? EXTPING_NAMELIST : EXTPING_NOP);
 
377
        buf.data = ping;
 
378
        buf.dataLength = p.length();
 
379
        enet_socket_send(pingsock, &si->address, &buf, 1);
 
380
    }
 
381
    else if(searchlan < 2)
 
382
    {
 
383
        static int lastping = 0;
 
384
        if(lastping >= servers.length()) lastping = 0;
 
385
        loopi(maxservpings ? min(servers.length(), maxservpings) : servers.length())
 
386
        {
 
387
            serverinfo &si = *servers[lastping];
 
388
            if(++lastping >= servers.length()) lastping = 0;
 
389
            if(si.address.host == ENET_HOST_ANY) continue;
 
390
            p.len = baselen;
 
391
            putint(p, si.getnames || issearch ? EXTPING_NAMELIST : EXTPING_NOP);
 
392
            buf.data = ping;
 
393
            buf.dataLength = p.length();
 
394
            enet_socket_send(pingsock, &si.address, &buf, 1);
 
395
        }
 
396
    }
 
397
    if(searchlan && !onlyconnected)
 
398
    {
 
399
        ENetAddress address;
 
400
        address.host = ENET_HOST_BROADCAST;
 
401
        address.port = CUBE_SERVINFO_PORT_LAN;
 
402
        p.len = baselen;
 
403
        putint(p, issearch ? EXTPING_NAMELIST : EXTPING_NOP);
 
404
        buf.data = ping;
 
405
        buf.dataLength = p.length();
 
406
        enet_socket_send(pingsock, &address, &buf, 1);
 
407
    }
 
408
    lastinfo = totalmillis;
 
409
}
 
410
 
 
411
void checkresolver()
 
412
{
 
413
    int resolving = 0;
 
414
    loopv(servers)
 
415
    {
 
416
        serverinfo &si = *servers[i];
 
417
        if(si.resolved == serverinfo::RESOLVED) continue;
 
418
        if(si.address.host == ENET_HOST_ANY)
 
419
        {
 
420
            if(si.resolved == serverinfo::UNRESOLVED) { si.resolved = serverinfo::RESOLVING; resolverquery(si.name); }
 
421
            resolving++;
 
422
        }
 
423
    }
 
424
    if(!resolving) return;
 
425
 
 
426
    const char *name = NULL;
 
427
    ENetAddress addr = { ENET_HOST_ANY, ENET_PORT_ANY };
 
428
    while(resolvercheck(&name, &addr))
 
429
    {
 
430
        loopv(servers)
 
431
        {
 
432
            serverinfo &si = *servers[i];
 
433
            if(name == si.name)
 
434
            {
 
435
                si.resolved = serverinfo::RESOLVED;
 
436
                si.address.host = addr.host;
 
437
                addr.host = ENET_HOST_ANY;
 
438
                break;
 
439
            }
 
440
        }
 
441
    }
 
442
}
 
443
 
 
444
#define MAXINFOLINELEN 100  // including color codes
 
445
 
 
446
void checkpings()
 
447
{
 
448
    if(pingsock == ENET_SOCKET_NULL) return;
 
449
    enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE;
 
450
    ENetBuffer buf;
 
451
    ENetAddress addr;
 
452
    static uchar ping[MAXTRANS];
 
453
    static char text[MAXTRANS];
 
454
    buf.data = ping;
 
455
    buf.dataLength = sizeof(ping);
 
456
    while(enet_socket_wait(pingsock, &events, 0) >= 0 && events)
 
457
    {
 
458
        int len = enet_socket_receive(pingsock, &addr, &buf, 1);
 
459
        if(len <= 0) return;
 
460
        serverinfo *si = NULL;
 
461
        loopv(servers) if(addr.host == servers[i]->address.host && addr.port == servers[i]->address.port)
 
462
        {
 
463
            si = servers[i];
 
464
            break;
 
465
        }
 
466
        if(!si && searchlan) si = newserver(NULL, addr.host, CUBE_SERVINFO_TO_SERV_PORT(addr.port));
 
467
        if(!si) continue;
 
468
 
 
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);
 
474
        switch(query)
 
475
        { // cleanup additional query info
 
476
            case EXTPING_SERVERINFO:
 
477
                loopi(2) getint(p);
 
478
                break;
 
479
        }
 
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);
 
485
        getstring(text, p);
 
486
        filtertext(si->map, text, 1);
 
487
        getstring(text, p);
 
488
        filterservdesc(si->sdesc, text);
 
489
        s_strcpy(si->description, si->sdesc);
 
490
        si->maxclients = getint(p);
 
491
        if(p.remaining())
 
492
        {
 
493
            si->pongflags = getint(p);
 
494
            if(p.remaining() && getint(p) == query)
 
495
            {
 
496
                switch(query)
 
497
                {
 
498
                    case EXTPING_NAMELIST:
 
499
                    {
 
500
                        si->playernames.setsizenodelete(0);
 
501
                        ucharbuf q(si->namedata, sizeof(si->namedata));
 
502
                        loopi(si->numplayers)
 
503
                        {
 
504
                            getstring(text, p);
 
505
                            filtertext(text, text, 0);
 
506
                            if(text[0] && !p.overread())
 
507
                            {
 
508
                                si->playernames.add((const char *)si->namedata + q.length());
 
509
                                sendstring(text, q);
 
510
                            }
 
511
                            else break;
 
512
                        }
 
513
                        break;
 
514
                    }
 
515
                    case EXTPING_SERVERINFO:
 
516
                    {
 
517
                        si->infotexts.setsizenodelete(0);
 
518
                        ucharbuf q(si->textdata, sizeof(si->textdata));
 
519
                        getstring(text, p);
 
520
                        si->getinfo = 0;
 
521
                        if(strlen(text) != 2)
 
522
                        {
 
523
                            si->infotexts.add((char *)si->textdata);
 
524
                            sendstring("this server does not provide additional information", q);
 
525
                            break;
 
526
                        }
 
527
                        strcpy(si->lang, text);
 
528
                        while(p.remaining())
 
529
                        {
 
530
                            getstring(text, p);
 
531
                            if(*text && !p.overread())
 
532
                            {
 
533
                                text[MAXINFOLINELEN] = '\0';
 
534
                                cutcolorstring(text, 80);
 
535
                                si->infotexts.add((char *)si->textdata + q.length());
 
536
                                sendstring(strcmp(text, ".") ? text : "", q);
 
537
                            }
 
538
                            else break;
 
539
                        }
 
540
                        break;
 
541
                    }
 
542
                    case EXTPING_MAPROT:
 
543
                    {
 
544
                        si->infotexts.setsizenodelete(0);
 
545
                        ucharbuf q(si->textdata, sizeof(si->textdata));
 
546
                        int n = getint(p);
 
547
                        si->getinfo = 0;
 
548
                        while(p.remaining())
 
549
                        {
 
550
                            getstring(text, p);
 
551
                            if(*text && !p.overread())
 
552
                            {
 
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);
 
557
                            }
 
558
                            else break;
 
559
                        }
 
560
                        break;
 
561
                    }
 
562
                }
 
563
            }
 
564
        }
 
565
        else
 
566
        {
 
567
            si->pongflags = 0;
 
568
        }
 
569
        if(si->pongflags > 0)
 
570
        {
 
571
            const char *sp = "";
 
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);
 
581
        }
 
582
    }
 
583
}
 
584
 
 
585
enum { SBS_PING = 0, SBS_NUMPL, SBS_MAXPL, SBS_MINREM, SBS_MAP, SBS_MODE, SBS_IP, SBS_DESC, NUMSERVSORT };
 
586
 
 
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);
 
593
 
 
594
void serversortprepare()
 
595
{
 
596
    loopv(servers)
 
597
    {
 
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;
 
602
        else
 
603
        {
 
604
            si.weight += 700;
 
605
            if(serversortpreferofficial && securemapcheck(si.map, false)) si.weight += 100;
 
606
        }
 
607
        si.weight += si.msweight;
 
608
    }
 
609
}
 
610
 
 
611
int sicompare(serverinfo **ap, serverinfo **bp)
 
612
{
 
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
 
619
    switch(serversort)
 
620
    {
 
621
        case SBS_NUMPL: // player number
 
622
            if(a->numplayers < b->numplayers) return dir;
 
623
            if(a->numplayers > b->numplayers) return -dir;
 
624
            break;
 
625
        case SBS_MAXPL: // maxplayers
 
626
                if(a->maxclients < b->maxclients) return dir;
 
627
            if(a->maxclients > b->maxclients) return -dir;
 
628
            break;
 
629
        case SBS_MINREM: // minutes remaining
 
630
                if(a->minremain < b->minremain) return dir;
 
631
            if(a->minremain > b->minremain) return -dir;
 
632
            break;
 
633
        case SBS_DESC: // description
 
634
        {
 
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;
 
642
            break;
 
643
        }
 
644
        case SBS_IP: // IP
 
645
            if(ips) return ips;
 
646
            break;
 
647
        case SBS_MAP: // map
 
648
        {
 
649
            int mdir = dir * strcasecmp(a->map, b->map);
 
650
            if(mdir) return mdir;
 
651
            break;
 
652
        }
 
653
        case SBS_MODE: // mode
 
654
        {
 
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;
 
658
            break;
 
659
        }
 
660
    }
 
661
    if(serversort ==  SBS_PING || (!a->numplayers && !b->numplayers)) // ping
 
662
    {
 
663
        if(a->ping > b->ping) return dir;
 
664
        if(a->ping < b->ping) return -dir;
 
665
    }
 
666
    if(ips) return ips;
 
667
    if(a->port > b->port) return dir;
 
668
    else return -dir;
 
669
}
 
670
 
 
671
void *servmenu = NULL, *searchmenu = NULL, *serverinfomenu = NULL;
 
672
vector<char *> namelists;
 
673
 
 
674
string cursearch, cursearchuc;
 
675
 
 
676
void searchnickname(const char *name)
 
677
{
 
678
    if(!name || !name[0]) return;
 
679
    s_strcpy(cursearch, name);
 
680
    s_strcpy(cursearchuc, name);
 
681
    strtoupper(cursearchuc);
 
682
    showmenu("search");
 
683
}
 
684
COMMAND(searchnickname, ARG_1STR);
 
685
 
 
686
VAR(showallservers, 0, 1, 1);
 
687
 
 
688
bool matchplayername(const char *name)
 
689
{
 
690
    static string nameuc;
 
691
    s_strcpy(nameuc, name);
 
692
    strtoupper(nameuc);
 
693
    return strstr(nameuc, cursearchuc) != NULL;
 
694
}
 
695
 
 
696
VARP(serverbrowserhideip, 0, 0, 2);
 
697
VARP(serverbrowserhidefavtag, 0, 1, 2);
 
698
VAR(showweights, 0, 0, 1);
 
699
 
 
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 };
 
703
 
 
704
VARF(showonlyfavourites, 0, 0, 100,
 
705
{
 
706
    if(showonlyfavourites > favcats.length())
 
707
    {
 
708
        conoutf("showonlyfavourites: %d out of range (0..%d)", showonlyfavourites, favcats.length());
 
709
        showonlyfavourites = 0;
 
710
    }
 
711
});
 
712
 
 
713
const char *favcatargname(const char *refdes, int par)
 
714
{
 
715
    static string text[3];
 
716
    static int i = 0;
 
717
    if(par < 0 || par >= FC_NUM) return NULL;
 
718
    i = (i + 1) % 3;
 
719
    s_sprintf(text[i])("sbfavourite_%s_%s", refdes, fc_als[par]);
 
720
    return text[i];
 
721
}
 
722
 
 
723
void addfavcategory(const char *refdes)
 
724
{
 
725
    string text, val;
 
726
    char alx[FC_NUM];
 
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;
 
735
    loopi(3)
 
736
    {
 
737
        s_sprintf(val)("%d", *text & (1 << i) ? 90 : 10);
 
738
        if(!alx[i + FC_RED]) alias(favcatargname(text, i + FC_RED), val);
 
739
    }
 
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());
 
746
}
 
747
 
 
748
void listfavcats()
 
749
{
 
750
    const char *str = conc(&favcats[0], favcats.length(), true);
 
751
    result(str);
 
752
    delete [] str;
 
753
}
 
754
 
 
755
bool favcatcheckkey(serverinfo &si, const char *key)
 
756
{ // IP:port, #gamemode ,%mapname, desc
 
757
    string text, keyuc;
 
758
    if(isdigit(*key)) // IP
 
759
    {
 
760
        s_sprintf(text)("%s:%d", si.name, si.port);
 
761
        return !strncmp(text, key, strlen(key));
 
762
    }
 
763
    else if(si.address.host != ENET_HOST_ANY && si.ping != 9999) switch(*key)
 
764
    {
 
765
        case '#':
 
766
            return si.map[0] && si.mode == atoi(key + 1);
 
767
        case '%':
 
768
            strtoupper(text, si.map);
 
769
            strtoupper(keyuc, key + 1);
 
770
            return si.map[0] && key[1] && strstr(text, keyuc);
 
771
        case '>':
 
772
            return si.ping > atoi(key + 1);
 
773
        case '!':
 
774
            return !favcatcheckkey(si, key + 1);
 
775
        case '+':
 
776
            return si.map[0] && favcatcheckkey(si, key + 1);
 
777
        case '$':
 
778
            if(key[1])
 
779
            {
 
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);
 
782
                int cnt = 0;
 
783
                for(const char *p = text; (p = strchr(p, '\"')); p++) cnt++;
 
784
                return cnt == 4 && execute(text);
 
785
            }
 
786
            break;
 
787
        default:
 
788
            filtertext(text, si.sdesc);
 
789
            return *key && strstr(text, key);
 
790
    }
 
791
    return false;
 
792
}
 
793
 
 
794
const char *favcatcheck(serverinfo &si, const char *ckeys)
 
795
{
 
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);
 
800
    bool res = false;
 
801
    DELETEA(nkeys);
 
802
    nkeys = newstring(strlen(ckeys));
 
803
    nkeys[0] = '\0';
 
804
    while(k)
 
805
    {
 
806
        if(favcatcheckkey(si, k)) res = true;
 
807
        else
 
808
        {
 
809
            if(*nkeys) strcat(nkeys, " ");
 
810
            strcat(nkeys, k);
 
811
        }
 
812
        k = strtok(NULL, sep);
 
813
    }
 
814
    delete[] keys;
 
815
    return res ? nkeys : NULL;
 
816
}
 
817
 
 
818
vector<const char *> favcattags;
 
819
 
 
820
bool assignserverfavourites()
 
821
{
 
822
    int alxn[FC_NUM];
 
823
    const char *alx[FC_NUM], *sep = " \t\n\r";
 
824
    favcattags.setsizenodelete(0);
 
825
    bool res = false;
 
826
    loopv(servers) { servers[i]->favcat = -1; servers[i]->weight = 0; }
 
827
    loopvj(favcats)
 
828
    {
 
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);
 
833
        while(k)
 
834
        {
 
835
            loopv(servers)
 
836
            {
 
837
                serverinfo &si = *servers[i];
 
838
                if((!alxn[FC_IGNORE] || showonlythiscat) && favcatcheckkey(si, k))
 
839
                {
 
840
                    si.weight += alxn[FC_WEIGHT];
 
841
                    res = true;
 
842
                    if(si.favcat == -1 || showonlythiscat)
 
843
                    {
 
844
                        si.favcat = j;
 
845
                        if(alxn[FC_ALPHA])
 
846
                        {
 
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);
 
849
                        }
 
850
                    }
 
851
                }
 
852
            }
 
853
            k = strtok(NULL, sep);
 
854
        }
 
855
        delete[] keys;
 
856
    }
 
857
    loopv(servers) if(servers[i]->favcat == -1)
 
858
    {
 
859
        DELETEA(servers[i]->bgcolor);
 
860
    }
 
861
    return res;
 
862
}
 
863
 
 
864
COMMAND(addfavcategory, ARG_1STR);
 
865
COMMAND(listfavcats, ARG_NONE);
 
866
 
 
867
void refreshservers(void *menu, bool init)
 
868
{
 
869
    static int servermenumillis;
 
870
    static bool usedselect = false;
 
871
    static string title;
 
872
    static serverinfo *lastselectedserver = NULL;
 
873
    bool issearch = menu == searchmenu;
 
874
    bool isinfo = menu == serverinfomenu;
 
875
    bool isscoreboard = menu == NULL;
 
876
 
 
877
    serverinfo *curserver = getconnectedserverinfo(), *oldsel = NULL;
 
878
    if(init)
 
879
    {
 
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;
 
885
        usedselect = false;
 
886
        if(menu && curserver) oldsel = curserver;
 
887
    }
 
888
 
 
889
    checkresolver();
 
890
    checkpings();
 
891
    if(isinfo)
 
892
    {
 
893
        if(lastselectedserver)
 
894
        {
 
895
            bool found = false;
 
896
            loopv(servers) if(lastselectedserver == servers[i]) { found = true; break; }
 
897
            if(!found) lastselectedserver = NULL;
 
898
        }
 
899
        menutitle(menu, "extended server information");
 
900
        menureset(menu);
 
901
        static string infotext;
 
902
        static char dummy = '\0';
 
903
        if(lastselectedserver)
 
904
        {
 
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())
 
910
            {
 
911
                infotext[0] = '\0';
 
912
                loopv(si.infotexts) menumanual(menu, si.infotexts[i]);
 
913
            }
 
914
            else
 
915
            {
 
916
                s_strcpy(infotext, "-- waiting for server response --");
 
917
                si.getinfo = 1;
 
918
                if(init || totalmillis - lastinfo >= servpingrate) pingservers(false, lastselectedserver);
 
919
            }
 
920
        }
 
921
        else
 
922
            s_strcpy(infotext, "  -- no server selected --");
 
923
        if(*infotext) menumanual(menu, infotext);
 
924
        return;
 
925
    }
 
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))
 
929
    {
 
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; }
 
932
        if(foundserver)
 
933
        {
 
934
            if((usedselect || ((gmenu *)menu)->menusel > 0)) oldsel = foundserver;
 
935
            lastselectedserver = foundserver;
 
936
        }
 
937
    }
 
938
    if(isscoreboard) lastselectedserver = curserver;
 
939
 
 
940
    bool showfavtag = (assignserverfavourites() || !serverbrowserhidefavtag) && serverbrowserhidefavtag != 2;
 
941
    serversortprepare();
 
942
    servers.sort(sicompare);
 
943
    if(menu)
 
944
    {
 
945
        static const char *titles[NUMSERVSORT] =
 
946
        {
 
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
 
955
        };
 
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);
 
959
        menureset(menu);
 
960
        string text;
 
961
        int curnl = 0;
 
962
        bool sbconnectexists = identexists("sbconnect");
 
963
        loopv(servers)
 
964
        {
 
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)
 
976
            {
 
977
                if(si.protocol!=PROTOCOL_VERSION)
 
978
                {
 
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;
 
981
                }
 
982
                else
 
983
                {
 
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);
 
988
                    if(si.map[0])
 
989
                    {
 
990
                        s_strcatf(si.full, "%s, %s", si.map, modestr(si.mode, modeacronyms > 0));
 
991
                        if(showmr) s_strcatf(si.full, ", (%d)", si.minremain);
 
992
                    }
 
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);
 
996
                }
 
997
            }
 
998
            else
 
999
            {
 
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;
 
1002
            }
 
1003
            if(issearch && showthisone)
 
1004
            {
 
1005
                bool found = false;
 
1006
                loopvj(si.playernames) if(matchplayername(si.playernames[j])) { found = true; break; };
 
1007
                if(!found) showthisone = false;
 
1008
            }
 
1009
            if(showthisone)
 
1010
            {
 
1011
                cutcolorstring(si.full, 76); // cut off too long server descriptions
 
1012
                cutcolorstring(si.description, 76);
 
1013
                if(sbconnectexists)
 
1014
                {
 
1015
                    filtertext(text, si.sdesc);
 
1016
                    for(char *p = text; (p = strchr(p, '\"')); *p++ = ' ');
 
1017
                    text[30] = '\0';
 
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);
 
1019
                }
 
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)
 
1023
                {
 
1024
                    ((gmenu *)menu)->menusel = ((gmenu *)menu)->items.length() - 1;
 
1025
                    usedselect = true;
 
1026
                    si.getnames = shownamesinbrowser ? 1 : 0;
 
1027
                }
 
1028
                if((shownamesinbrowser && servers[i] == oldsel && si.playernames.length()) || issearch)
 
1029
                {
 
1030
                    int cur = 0;
 
1031
                    char *t = NULL;
 
1032
                    loopvj(si.playernames)
 
1033
                    {
 
1034
                        if(cur == 0)
 
1035
                        {
 
1036
                            if(namelists.length() < ++curnl) namelists.add(newstringbuf());
 
1037
                            t = namelists[curnl - 1];
 
1038
                            s_strcpy(t, showfavtag ? "\t\t" : "\t");
 
1039
                        }
 
1040
                        s_strcatf(t, " \t\fs%s%s\fr", !issearch || matchplayername(si.playernames[j]) ? "" : "\f4" ,si.playernames[j]);
 
1041
                        cur++;
 
1042
                        if(cur == 4)
 
1043
                        {
 
1044
                            menumanual(menu, t, NULL, NULL, NULL);
 
1045
                            cur = 0;
 
1046
                        }
 
1047
                    }
 
1048
                    if(cur) menumanual(menu, t, NULL, NULL, NULL);
 
1049
                }
 
1050
            }
 
1051
            si.menuline_to = ((gmenu *)menu)->items.length();
 
1052
        }
 
1053
        static string notfoundmsg;
 
1054
        if(issearch)
 
1055
        {
 
1056
            if(curnl == 0)
 
1057
            {
 
1058
                s_sprintf(notfoundmsg)("\t\tpattern \fs\f3%s\fr not found.", cursearch);
 
1059
                menumanual(menu, notfoundmsg, NULL, NULL, NULL);
 
1060
            }
 
1061
        }
 
1062
        else if(!((gmenu *)menu)->items.length() && showonlyfavourites && favcats.inrange(showonlyfavourites - 1))
 
1063
        {
 
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);
 
1067
        }
 
1068
    }
 
1069
}
 
1070
 
 
1071
bool serverskey(void *menu, int code, bool isdown, int unicode)
 
1072
{
 
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))
 
1076
    {
 
1077
        int sel = ((gmenu *)menu)->menusel;
 
1078
        loopvj(servers) if(menu && (servers[j]->menuline_from <= sel && servers[j]->menuline_to > sel))
 
1079
        {
 
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 = "";
 
1082
            if(rest)
 
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);
 
1086
            }
 
1087
            else
 
1088
            { // add IP:port to group
 
1089
                s_sprintfd(text)("%s:%d", servers[j]->name, servers[j]->port);
 
1090
                if(key && *key)
 
1091
                {
 
1092
                    char *newkey = newstring(key, strlen(text) + 1 + strlen(key));
 
1093
                    strcat(newkey, " ");
 
1094
                    strcat(newkey, text);
 
1095
                    alias(keyalias, newkey);
 
1096
                    delete[] newkey;
 
1097
                }
 
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));
 
1100
            }
 
1101
            return true;
 
1102
        }
 
1103
    }
 
1104
    switch(code)
 
1105
    {
 
1106
        case SDLK_HOME:
 
1107
            if(menu) ((gmenu *)menu)->menusel = 0;
 
1108
            return true;
 
1109
 
 
1110
        case SDLK_LEFT:
 
1111
            serversort = (serversort+NUMSERVSORT-1) % NUMSERVSORT;
 
1112
            return true;
 
1113
 
 
1114
        case SDLK_RIGHT:
 
1115
            serversort = (serversort+1) % NUMSERVSORT;
 
1116
            return true;
 
1117
 
 
1118
        case SDLK_F5:
 
1119
            updatefrommaster(1);
 
1120
            return true;
 
1121
 
 
1122
                case SDLK_F6:
 
1123
                        serversortdir = serversortdir ? 0 : 1;
 
1124
                        return true;
 
1125
 
 
1126
        case SDLK_F9:
 
1127
            showmenu("serverinfo");
 
1128
            return true;
 
1129
    }
 
1130
    if(menu == searchmenu) return false;
 
1131
    switch(code)
 
1132
    {
 
1133
        case SDLK_F1:
 
1134
            showmenu("serverbrowser help");
 
1135
            return true;
 
1136
 
 
1137
                case SDLK_F2:
 
1138
            shownamesinbrowser = shownamesinbrowser ? 0 : 1;
 
1139
                        return true;
 
1140
 
 
1141
        case SDLK_F3:
 
1142
            showmenu("search player");
 
1143
            return true;
 
1144
 
 
1145
        case SDLK_F4:
 
1146
            showmenu("edit favourites");
 
1147
            return true;
 
1148
 
 
1149
                case SDLK_F7:
 
1150
                        showonlygoodservers = showonlygoodservers ? 0 : 1;
 
1151
                        return true;
 
1152
 
 
1153
                case SDLK_F8:
 
1154
                        showminremain = showminremain ? 0 : 1;
 
1155
                        return true;
 
1156
    }
 
1157
    return false;
 
1158
}
 
1159
 
 
1160
void clearservers()
 
1161
{
 
1162
    resolverclear();
 
1163
    servers.deletecontentsp();
 
1164
}
 
1165
 
 
1166
VARP(masterupdatefrequency, 1, 60*60, 24*60*60);
 
1167
 
 
1168
void updatefrommaster(int force)
 
1169
{
 
1170
    static int lastupdate = 0;
 
1171
    if(!force && lastupdate && totalmillis-lastupdate<masterupdatefrequency*1000) return;
 
1172
 
 
1173
    uchar buf[32000];
 
1174
    uchar *reply = retrieveservers(buf, sizeof(buf));
 
1175
    if(!*reply || strstr((char *)reply, "<html>") || strstr((char *)reply, "<HTML>")) conoutf("master server not replying");
 
1176
    else
 
1177
    {
 
1178
        // preserve currently connected server from deletion
 
1179
        serverinfo *curserver = getconnectedserverinfo();
 
1180
        string curname, curport, curweight;
 
1181
        if(curserver)
 
1182
        {
 
1183
            s_strcpy(curname, curserver->name);
 
1184
            s_sprintf(curport)("%d", curserver->port);
 
1185
            s_sprintf(curweight)("%d", curserver->msweight);
 
1186
        }
 
1187
 
 
1188
        clearservers();
 
1189
        execute((char *)reply);
 
1190
 
 
1191
        if(curserver) addserver(curname, curport, curweight);
 
1192
        lastupdate = totalmillis;
 
1193
    }
 
1194
}
 
1195
 
 
1196
COMMAND(addserver, ARG_3STR);
 
1197
COMMAND(clearservers, ARG_NONE);
 
1198
COMMAND(updatefrommaster, ARG_1INT);
 
1199
 
 
1200
void writeservercfg()
 
1201
{
 
1202
    FILE *f = openfile(path("config/servers.cfg", true), "w");
 
1203
    if(!f) return;
 
1204
    fprintf(f, "// servers connected to are added here automatically\n");
 
1205
    loopvrev(servers)
 
1206
    {
 
1207
        fprintf(f, "\naddserver %s %d", servers[i]->name, servers[i]->port);
 
1208
        if(servers[i]->msweight) fprintf(f, " %d", servers[i]->msweight);
 
1209
    }
 
1210
    fprintf(f, "\n");
 
1211
    fclose(f);
 
1212
}