1 // server.cpp: little more than enhanced multicaster
2 // runs dedicated or as client coroutine
3
4 #include "cube.h"
5
6 #define DEBUGCOND (true)
7
8 #include "server.h"
9 #include "servercontroller.h"
10 #include "serverfiles.h"
11 // 2011feb05:ft: quitproc
12 #include "signal.h"
13 // config
14 servercontroller *svcctrl = NULL;
15 servercommandline scl;
16 servermaprot maprot;
17 serveripblacklist ipblacklist;
18 servernickblacklist nickblacklist;
19 serverforbiddenlist forbiddenlist;
20 serverpasswords passwords;
21 serverinfofile infofiles;
22 killmessagesfile killmsgs;
23
24 // server state
25 bool isdedicated = false;
26 ENetHost *serverhost = NULL;
27
28 int nextstatus = 0, servmillis = 0, lastfillup = 0;
29
30 vector<client *> clients;
31 vector<worldstate *> worldstates;
32 vector<savedscore> savedscores;
33 vector<ban> bans;
34 vector<demofile> demofiles;
35
36 int mastermode = MM_OPEN;
37 static bool autoteam = true;
38 int matchteamsize = 0;
39
40 long int incoming_size = 0;
41
42 static bool forceintermission = false;
43
44 string servdesc_current;
45 ENetAddress servdesc_caller;
46 bool custom_servdesc = false;
47
48 // current game
49 string smapname, nextmapname;
50 int smode = 0, nextgamemode;
51 int interm = 0;
52 static int minremain = 0, gamemillis = 0, gamelimit = 0, /*lmsitemtype = 0,*/ nextsendscore = 0;
53 mapstats smapstats;
54 vector<server_entity> sents;
55 char *maplayout = NULL, *testlayout = NULL;
56 int maplayout_factor, testlayout_factor, maplayoutssize;
57 servermapbuffer mapbuffer;
58
59 // cmod
60 char *global_name;
61 int totalclients = 0;
62 int cn2boot;
63 int servertime = 0, serverlagged = 0;
64
valid_client(int cn)65 bool valid_client(int cn)
66 {
67 return clients.inrange(cn) && clients[cn]->type != ST_EMPTY;
68 }
69
cleanworldstate(ENetPacket * packet)70 void cleanworldstate(ENetPacket *packet)
71 {
72 loopv(worldstates)
73 {
74 worldstate *ws = worldstates[i];
75 if(ws->positions.inbuf(packet->data) || ws->messages.inbuf(packet->data)) ws->uses--;
76 else continue;
77 if(!ws->uses)
78 {
79 delete ws;
80 worldstates.remove(i);
81 }
82 break;
83 }
84 }
85
sendpacket(int n,int chan,ENetPacket * packet,int exclude,bool demopacket)86 void sendpacket(int n, int chan, ENetPacket *packet, int exclude, bool demopacket)
87 {
88 if(n<0)
89 {
90 recordpacket(chan, packet->data, (int)packet->dataLength);
91 loopv(clients) if(i!=exclude && (clients[i]->type!=ST_TCPIP || clients[i]->isauthed)) sendpacket(i, chan, packet, -1, demopacket);
92 return;
93 }
94 switch(clients[n]->type)
95 {
96 case ST_TCPIP:
97 {
98 enet_peer_send(clients[n]->peer, chan, packet);
99 break;
100 }
101
102 case ST_LOCAL:
103 localservertoclient(chan, packet->data, (int)packet->dataLength, demopacket);
104 break;
105 }
106 }
107
108 static bool reliablemessages = false;
109
buildworldstate()110 bool buildworldstate()
111 {
112 static struct { int posoff, poslen, msgoff, msglen; } pkt[MAXCLIENTS];
113 worldstate &ws = *new worldstate;
114 loopv(clients)
115 {
116 client &c = *clients[i];
117 if(c.type!=ST_TCPIP || !c.isauthed) continue;
118 c.overflow = 0;
119 if(c.position.empty()) pkt[i].posoff = -1;
120 else
121 {
122 pkt[i].posoff = ws.positions.length();
123 ws.positions.put(c.position.getbuf(), c.position.length());
124 pkt[i].poslen = ws.positions.length() - pkt[i].posoff;
125 c.position.setsize(0);
126 }
127 if(c.messages.empty()) pkt[i].msgoff = -1;
128 else
129 {
130 pkt[i].msgoff = ws.messages.length();
131 putint(ws.messages, SV_CLIENT);
132 putint(ws.messages, c.clientnum);
133 putuint(ws.messages, c.messages.length());
134 ws.messages.put(c.messages.getbuf(), c.messages.length());
135 pkt[i].msglen = ws.messages.length() - pkt[i].msgoff;
136 c.messages.setsize(0);
137 }
138 }
139 int psize = ws.positions.length(), msize = ws.messages.length();
140 if(psize)
141 {
142 recordpacket(0, ws.positions.getbuf(), psize);
143 ucharbuf p = ws.positions.reserve(psize);
144 p.put(ws.positions.getbuf(), psize);
145 ws.positions.addbuf(p);
146 }
147 if(msize)
148 {
149 recordpacket(1, ws.messages.getbuf(), msize);
150 ucharbuf p = ws.messages.reserve(msize);
151 p.put(ws.messages.getbuf(), msize);
152 ws.messages.addbuf(p);
153 }
154 ws.uses = 0;
155 loopv(clients)
156 {
157 client &c = *clients[i];
158 if(c.type!=ST_TCPIP || !c.isauthed) continue;
159 ENetPacket *packet;
160 if(psize && (pkt[i].posoff<0 || psize-pkt[i].poslen>0))
161 {
162 packet = enet_packet_create(&ws.positions[pkt[i].posoff<0 ? 0 : pkt[i].posoff+pkt[i].poslen],
163 pkt[i].posoff<0 ? psize : psize-pkt[i].poslen,
164 ENET_PACKET_FLAG_NO_ALLOCATE);
165 sendpacket(c.clientnum, 0, packet);
166 if(!packet->referenceCount) enet_packet_destroy(packet);
167 else { ++ws.uses; packet->freeCallback = cleanworldstate; }
168 }
169
170 if(msize && (pkt[i].msgoff<0 || msize-pkt[i].msglen>0))
171 {
172 packet = enet_packet_create(&ws.messages[pkt[i].msgoff<0 ? 0 : pkt[i].msgoff+pkt[i].msglen],
173 pkt[i].msgoff<0 ? msize : msize-pkt[i].msglen,
174 (reliablemessages ? ENET_PACKET_FLAG_RELIABLE : 0) | ENET_PACKET_FLAG_NO_ALLOCATE);
175 sendpacket(c.clientnum, 1, packet);
176 if(!packet->referenceCount) enet_packet_destroy(packet);
177 else { ++ws.uses; packet->freeCallback = cleanworldstate; }
178 }
179 }
180 reliablemessages = false;
181 if(!ws.uses)
182 {
183 delete &ws;
184 return false;
185 }
186 else
187 {
188 worldstates.add(&ws);
189 return true;
190 }
191 }
192
countclients(int type,bool exclude=false)193 int countclients(int type, bool exclude = false)
194 {
195 int num = 0;
196 loopv(clients) if((clients[i]->type!=type)==exclude) num++;
197 return num;
198 }
199
numclients()200 int numclients() { return countclients(ST_EMPTY, true); }
numlocalclients()201 int numlocalclients() { return countclients(ST_LOCAL); }
numnonlocalclients()202 int numnonlocalclients() { return countclients(ST_TCPIP); }
203
numauthedclients()204 int numauthedclients()
205 {
206 int num = 0;
207 loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed) num++;
208 return num;
209 }
210
numactiveclients()211 int numactiveclients()
212 {
213 int num = 0;
214 loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed && clients[i]->isonrightmap && team_isactive(clients[i]->team)) num++;
215 return num;
216 }
217
numteamclients(int exclude=-1)218 int *numteamclients(int exclude = -1)
219 {
220 static int num[TEAM_NUM];
221 loopi(TEAM_NUM) num[i] = 0;
222 loopv(clients) if(i != exclude && clients[i]->type!=ST_EMPTY && clients[i]->isauthed && clients[i]->isonrightmap && team_isvalid(clients[i]->team)) num[clients[i]->team]++;
223 return num;
224 }
225
sendservermode(bool send=true)226 int sendservermode(bool send = true)
227 {
228 int sm = (autoteam ? AT_ENABLED : AT_DISABLED) | ((mastermode & MM_MASK) << 2) | (matchteamsize << 4);
229 if(send) sendf(-1, 1, "ri2", SV_SERVERMODE, sm);
230 return sm;
231 }
232
changematchteamsize(int newteamsize)233 void changematchteamsize(int newteamsize)
234 {
235 if(newteamsize < 0) return;
236 if(matchteamsize != newteamsize)
237 {
238 matchteamsize = newteamsize;
239 sendservermode();
240 }
241 if(mastermode == MM_MATCH && matchteamsize && m_teammode)
242 {
243 int size[2] = { 0 };
244 loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed && clients[i]->isonrightmap)
245 {
246 if(team_isactive(clients[i]->team))
247 {
248 if(++size[clients[i]->team] > matchteamsize) updateclientteam(i, team_tospec(clients[i]->team), FTR_SILENTFORCE);
249 }
250 }
251 }
252 }
253
changemastermode(int newmode)254 void changemastermode(int newmode)
255 {
256 if(mastermode != newmode)
257 {
258 mastermode = newmode;
259 senddisconnectedscores(-1);
260 if(mastermode != MM_MATCH)
261 {
262 loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed)
263 {
264 if(clients[i]->team == TEAM_CLA_SPECT || clients[i]->team == TEAM_RVSF_SPECT) updateclientteam(i, TEAM_SPECT, FTR_SILENTFORCE);
265 }
266 }
267 else if(matchteamsize) changematchteamsize(matchteamsize);
268 sendservermode();
269 }
270 }
271
findcnbyaddress(ENetAddress * address)272 int findcnbyaddress(ENetAddress *address)
273 {
274 loopv(clients)
275 {
276 if(clients[i]->type == ST_TCPIP && clients[i]->peer->address.host == address->host && clients[i]->peer->address.port == address->port)
277 return i;
278 }
279 return -1;
280 }
281
findscore(client & c,bool insert)282 savedscore *findscore(client &c, bool insert)
283 {
284 if(c.type!=ST_TCPIP) return NULL;
285 enet_uint32 mask = ENET_HOST_TO_NET_32(mastermode == MM_MATCH ? 0xFFFF0000 : 0xFFFFFFFF); // in match mode, reconnecting from /16 subnet is allowed
286 if(!insert)
287 {
288 loopv(clients)
289 {
290 client &o = *clients[i];
291 if(o.type!=ST_TCPIP || !o.isauthed) continue;
292 if(o.clientnum!=c.clientnum && o.peer->address.host==c.peer->address.host && !strcmp(o.name, c.name))
293 {
294 static savedscore curscore;
295 curscore.save(o.state, o.team);
296 return &curscore;
297 }
298 }
299 }
300 loopv(savedscores)
301 {
302 savedscore &sc = savedscores[i];
303 if(!strcmp(sc.name, c.name) && (sc.ip & mask) == (c.peer->address.host & mask)) return ≻
304 }
305 if(!insert) return NULL;
306 savedscore &sc = savedscores.add();
307 copystring(sc.name, c.name);
308 sc.ip = c.peer->address.host;
309 return ≻
310 }
311
restoreserverstate(vector<entity> & ents)312 void restoreserverstate(vector<entity> &ents) // hack: called from savegame code, only works in SP
313 {
314 loopv(sents)
315 {
316 sents[i].spawned = ents[i].spawned;
317 sents[i].spawntime = 0;
318 }
319 }
320
sendf(int cn,int chan,const char * format,...)321 void sendf(int cn, int chan, const char *format, ...)
322 {
323 int exclude = -1;
324 bool reliable = false;
325 if(*format=='r') { reliable = true; ++format; }
326 packetbuf p(MAXTRANS, reliable ? ENET_PACKET_FLAG_RELIABLE : 0);
327 va_list args;
328 va_start(args, format);
329 while(*format) switch(*format++)
330 {
331 case 'x':
332 exclude = va_arg(args, int);
333 break;
334
335 case 'v':
336 {
337 int n = va_arg(args, int);
338 int *v = va_arg(args, int *);
339 loopi(n) putint(p, v[i]);
340 break;
341 }
342
343 case 'i':
344 {
345 int n = isdigit(*format) ? *format++-'0' : 1;
346 loopi(n) putint(p, va_arg(args, int));
347 break;
348 }
349 case 's': sendstring(va_arg(args, const char *), p); break;
350 case 'm':
351 {
352 int n = va_arg(args, int);
353 p.put(va_arg(args, uchar *), n);
354 break;
355 }
356 }
357 va_end(args);
358 sendpacket(cn, chan, p.finalize(), exclude);
359 }
360
sendextras()361 void sendextras()
362 {
363 if ( gamemillis < nextsendscore ) return;
364 int count = 0, list[MAXCLIENTS];
365 loopv(clients)
366 {
367 client &c = *clients[i];
368 if ( c.type!=ST_TCPIP || !c.isauthed || !(c.md.updated && c.md.upmillis < gamemillis) ) continue;
369 if ( c.md.combosend )
370 {
371 sendf(c.clientnum, 1, "ri2", SV_HUDEXTRAS, min(c.md.combo,c.md.combofrags)-1 + HE_COMBO);
372 c.md.combosend = false;
373 }
374 if ( c.md.dpt )
375 {
376 list[count] = i;
377 count++;
378 }
379 }
380 nextsendscore = gamemillis + 160; // about 4 cicles
381 if ( !count ) return;
382
383 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
384 putint(p, SV_POINTS);
385 putint(p,count);
386 int *v = list;
387 loopi(count)
388 {
389 client &c = *clients[*v];
390 putint(p,c.clientnum); putint(p,c.md.dpt); c.md.updated = false; c.md.upmillis = c.md.dpt = 0;
391 v++;
392 }
393
394 sendpacket(-1, 1, p.finalize());
395 }
396
sendservmsg(const char * msg,int cn=-1)397 void sendservmsg(const char *msg, int cn = -1)
398 {
399 sendf(cn, 1, "ris", SV_SERVMSG, msg);
400 }
401
sendspawn(client * c)402 void sendspawn(client *c)
403 {
404 if(team_isspect(c->team)) return;
405 clientstate &gs = c->state;
406 gs.respawn();
407 gs.spawnstate(smode);
408 gs.lifesequence++;
409 sendf(c->clientnum, 1, "ri7vv", SV_SPAWNSTATE, gs.lifesequence,
410 gs.health, gs.armour,
411 gs.primary, gs.gunselect, m_arena ? c->spawnindex : -1,
412 NUMGUNS, gs.ammo, NUMGUNS, gs.mag);
413 gs.lastspawn = gamemillis;
414 }
415
416 // demo
417 stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL;
418 bool recordpackets = false;
419 int nextplayback = 0;
420
writedemo(int chan,void * data,int len)421 void writedemo(int chan, void *data, int len)
422 {
423 if(!demorecord) return;
424 int stamp[3] = { gamemillis, chan, len };
425 lilswap(stamp, 3);
426 demorecord->write(stamp, sizeof(stamp));
427 demorecord->write(data, len);
428 }
429
recordpacket(int chan,void * data,int len)430 void recordpacket(int chan, void *data, int len)
431 {
432 if(recordpackets) writedemo(chan, data, len);
433 }
434
recordpacket(int chan,ENetPacket * packet)435 void recordpacket(int chan, ENetPacket *packet)
436 {
437 if(recordpackets) writedemo(chan, packet->data, (int)packet->dataLength);
438 }
439
440 #ifdef STANDALONE
currentserver(int i)441 const char *currentserver(int i)
442 {
443 static string curSRVinfo;
444 string r;
445 r[0] = '\0';
446 switch(i)
447 {
448 case 1: { copystring(r, scl.ip[0] ? scl.ip : "local"); break; } // IP
449 case 2: { copystring(r, scl.logident[0] ? scl.logident : "local"); break; } // HOST
450 case 3: { formatstring(r)("%d", scl.serverport); break; } // PORT
451 // the following are used by a client, a server will simply return empty strings for them
452 case 4:
453 case 5:
454 case 6:
455 case 7:
456 case 8:
457 {
458 break;
459 }
460 default:
461 {
462 formatstring(r)("%s %d", scl.ip[0] ? scl.ip : "local", scl.serverport);
463 break;
464 }
465 }
466 copystring(curSRVinfo, r);
467 return curSRVinfo;
468 }
469 #endif
470
471 // these are actually the values used by the client, the server ones are in "scl".
472 string demofilenameformat = DEFDEMOFILEFMT;
473 string demotimestampformat = DEFDEMOTIMEFMT;
474 int demotimelocal = 0;
475
476 #ifdef STANDALONE
477 #define DEMOFORMAT scl.demofilenameformat
478 #define DEMOTSFORMAT scl.demotimestampformat
479 #else
480 #define DEMOFORMAT demofilenameformat
481 #define DEMOTSFORMAT demotimestampformat
482 #endif
483
getDemoFilename(int gmode,int mplay,int mdrop,int tstamp,char * srvmap)484 const char *getDemoFilename(int gmode, int mplay, int mdrop, int tstamp, char *srvmap)
485 {
486 // we use the following internal mapping of formatchars:
487 // %g : gamemode (int) %G : gamemode (chr) %F : gamemode (full)
488 // %m : minutes remaining %M : minutes played
489 // %s : seconds remaining %S : seconds played
490 // %h : IP of server %H : hostname of server
491 // %n : mapName
492 // %w : timestamp "when"
493 static string dmofn;
494 copystring(dmofn, "");
495
496 int cc = 0;
497 int mc = strlen(DEMOFORMAT);
498
499 while(cc<mc)
500 {
501 switch(DEMOFORMAT[cc])
502 {
503 case '%':
504 {
505 if(cc<(mc-1))
506 {
507 string cfspp;
508 switch(DEMOFORMAT[cc+1])
509 {
510 case 'F': formatstring(cfspp)("%s", fullmodestr(gmode)); break;
511 case 'g': formatstring(cfspp)("%d", gmode); break;
512 case 'G': formatstring(cfspp)("%s", acronymmodestr(gmode)); break;
513 case 'h': formatstring(cfspp)("%s", currentserver(1)); break; // client/server have different implementations
514 case 'H': formatstring(cfspp)("%s", currentserver(2)); break; // client/server have different implementations
515 case 'm': formatstring(cfspp)("%d", mdrop/60); break;
516 case 'M': formatstring(cfspp)("%d", mplay/60); break;
517 case 'n': formatstring(cfspp)("%s", srvmap); break;
518 case 's': formatstring(cfspp)("%d", mdrop); break;
519 case 'S': formatstring(cfspp)("%d", mplay); break;
520 case 'w':
521 {
522 time_t t = tstamp;
523 struct tm * timeinfo;
524 timeinfo = demotimelocal ? localtime(&t) : gmtime (&t);
525 strftime(cfspp, sizeof(string) - 1, DEMOTSFORMAT, timeinfo);
526 break;
527 }
528 default: logline(ACLOG_INFO, "bad formatstring: demonameformat @ %d", cc); cc-=1; break; // don't drop the bad char
529 }
530 concatstring(dmofn, cfspp);
531 }
532 else
533 {
534 logline(ACLOG_INFO, "trailing %%-sign in demonameformat");
535 }
536 cc+=1;
537 break;
538 }
539 default:
540 {
541 defformatstring(fsbuf)("%s%c", dmofn, DEMOFORMAT[cc]);
542 copystring(dmofn, fsbuf);
543 break;
544 }
545 }
546 cc+=1;
547 }
548 return dmofn;
549 }
550 #undef DEMOFORMAT
551 #undef DEMOTSFORMAT
552
enddemorecord()553 void enddemorecord()
554 {
555 if(!demorecord) return;
556
557 delete demorecord;
558 recordpackets = false;
559 demorecord = NULL;
560
561 if(!demotmp) return;
562
563 if(gamemillis < DEMO_MINTIME)
564 {
565 delete demotmp;
566 demotmp = NULL;
567 logline(ACLOG_INFO, "Demo discarded.");
568 return;
569 }
570
571 int len = demotmp->size();
572 demotmp->seek(0, SEEK_SET);
573 if(demofiles.length() >= scl.maxdemos)
574 {
575 delete[] demofiles[0].data;
576 demofiles.remove(0);
577 }
578 int mr = gamemillis >= gamelimit ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000;
579 demofile &d = demofiles.add();
580
581 //2010oct10:ft: suggests : formatstring(d.info)("%s, %s, %.2f%s", modestr(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); // the datetime bit is pretty useless in the servmesg, no?!
582 formatstring(d.info)("%s: %s, %s, %.2f%s", asctime(), modestr(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB");
583 if(mr) { concatformatstring(d.info, ", %d mr", mr); concatformatstring(d.file, "_%dmr", mr); }
584 defformatstring(msg)("Demo \"%s\" recorded\nPress F10 to download it from the server..", d.info);
585 sendservmsg(msg);
586 logline(ACLOG_INFO, "Demo \"%s\" recorded.", d.info);
587
588 // 2011feb05:ft: previously these two static formatstrings were used ..
589 //formatstring(d.file)("%s_%s_%s", timestring(), behindpath(smapname), modestr(gamemode, true)); // 20100522_10.08.48_ac_mines_DM.dmo
590 //formatstring(d.file)("%s_%s_%s", modestr(gamemode, true), behindpath(smapname), timestring( true, "%Y.%m.%d_%H%M")); // DM_ac_mines.2010.05.22_1008.dmo
591 // .. now we use client-side parseable fileattribs
592 int mPLAY = gamemillis >= gamelimit ? gamelimit/1000 : gamemillis/1000;
593 int mDROP = gamemillis >= gamelimit ? 0 : (gamelimit - gamemillis)/1000;
594 int iTIME = time(NULL);
595 const char *mTIME = numtime();
596 const char *sMAPN = behindpath(smapname);
597 string iMAPN;
598 copystring(iMAPN, sMAPN);
599 formatstring(d.file)( "%d:%d:%d:%s:%s", gamemode, mPLAY, mDROP, mTIME, iMAPN);
600
601 d.data = new uchar[len];
602 d.len = len;
603 demotmp->read(d.data, len);
604 delete demotmp;
605 demotmp = NULL;
606 if(scl.demopath[0])
607 {
608 formatstring(msg)("%s%s.dmo", scl.demopath, getDemoFilename(gamemode, mPLAY, mDROP, iTIME, iMAPN)); //d.file);
609 path(msg);
610 stream *demo = openfile(msg, "wb");
611 if(demo)
612 {
613 int wlen = (int) demo->write(d.data, d.len);
614 delete demo;
615 logline(ACLOG_INFO, "demo written to file \"%s\" (%d bytes)", msg, wlen);
616 }
617 else
618 {
619 logline(ACLOG_INFO, "failed to write demo to file \"%s\"", msg);
620 }
621 }
622 }
623
setupdemorecord()624 void setupdemorecord()
625 {
626 if(numlocalclients() || !m_mp(gamemode) || gamemode == GMODE_COOPEDIT) return;
627
628 defformatstring(demotmppath)("demos/demorecord_%s_%d", scl.ip[0] ? scl.ip : "local", scl.serverport);
629 demotmp = opentempfile(demotmppath, "w+b");
630 if(!demotmp) return;
631
632 stream *f = opengzfile(NULL, "wb", demotmp);
633 if(!f)
634 {
635 delete demotmp;
636 demotmp = NULL;
637 return;
638 }
639
640 sendservmsg("recording demo");
641 logline(ACLOG_INFO, "Demo recording started.");
642
643 demorecord = f;
644 recordpackets = false;
645
646 demoheader hdr;
647 memcpy(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic));
648 hdr.version = DEMO_VERSION;
649 hdr.protocol = SERVER_PROTOCOL_VERSION;
650 lilswap(&hdr.version, 1);
651 lilswap(&hdr.protocol, 1);
652 memset(hdr.desc, 0, DHDR_DESCCHARS);
653 defformatstring(desc)("%s, %s, %s %s", modestr(gamemode, false), behindpath(smapname), asctime(), servdesc_current);
654 if(strlen(desc) > DHDR_DESCCHARS)
655 formatstring(desc)("%s, %s, %s %s", modestr(gamemode, true), behindpath(smapname), asctime(), servdesc_current);
656 desc[DHDR_DESCCHARS - 1] = '\0';
657 strcpy(hdr.desc, desc);
658 memset(hdr.plist, 0, DHDR_PLISTCHARS);
659 const char *bl = "";
660 loopv(clients)
661 {
662 client *ci = clients[i];
663 if(ci->type==ST_EMPTY) continue;
664 if(strlen(hdr.plist) + strlen(ci->name) < DHDR_PLISTCHARS - 2) { strcat(hdr.plist, bl); strcat(hdr.plist, ci->name); }
665 bl = " ";
666 }
667 demorecord->write(&hdr, sizeof(demoheader));
668
669 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
670 welcomepacket(p, -1);
671 writedemo(1, p.buf, p.len);
672 }
673
listdemos(int cn)674 void listdemos(int cn)
675 {
676 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
677 putint(p, SV_SENDDEMOLIST);
678 putint(p, demofiles.length());
679 loopv(demofiles) sendstring(demofiles[i].info, p);
680 sendpacket(cn, 1, p.finalize());
681 }
682
cleardemos(int n)683 static void cleardemos(int n)
684 {
685 if(!n)
686 {
687 loopv(demofiles) delete[] demofiles[i].data;
688 demofiles.shrink(0);
689 sendservmsg("cleared all demos");
690 }
691 else if(demofiles.inrange(n-1))
692 {
693 delete[] demofiles[n-1].data;
694 demofiles.remove(n-1);
695 defformatstring(msg)("cleared demo %d", n);
696 sendservmsg(msg);
697 }
698 }
699
700 bool sending_demo = false;
701
senddemo(int cn,int num)702 void senddemo(int cn, int num)
703 {
704 client *cl = cn>=0 ? clients[cn] : NULL;
705 bool is_admin = (cl && cl->role == CR_ADMIN);
706 if(scl.demo_interm && (!interm || totalclients > 2) && !is_admin)
707 {
708 sendservmsg("\f3sorry, but this server only sends demos at intermission.\n wait for the end of this game, please", cn);
709 return;
710 }
711 if(!num) num = demofiles.length();
712 if(!demofiles.inrange(num-1))
713 {
714 if(demofiles.empty()) sendservmsg("no demos available", cn);
715 else
716 {
717 defformatstring(msg)("no demo %d available", num);
718 sendservmsg(msg, cn);
719 }
720 return;
721 }
722 demofile &d = demofiles[num-1];
723 loopv(d.clientssent) if(d.clientssent[i].ip == cl->peer->address.host && d.clientssent[i].clientnum == cl->clientnum)
724 {
725 sendservmsg("\f3Sorry, you have already downloaded this demo.", cl->clientnum);
726 return;
727 }
728 clientidentity &ci = d.clientssent.add();
729 ci.ip = cl->peer->address.host;
730 ci.clientnum = cl->clientnum;
731
732 if (interm) sending_demo = true;
733 packetbuf p(MAXTRANS + d.len, ENET_PACKET_FLAG_RELIABLE);
734 putint(p, SV_SENDDEMO);
735 sendstring(d.file, p);
736 putint(p, d.len);
737 p.put(d.data, d.len);
738 sendpacket(cn, 2, p.finalize());
739 }
740
741 int demoprotocol;
742 bool watchingdemo = false;
743
enddemoplayback()744 void enddemoplayback()
745 {
746 if(!demoplayback) return;
747 delete demoplayback;
748 demoplayback = NULL;
749 watchingdemo = false;
750
751 loopv(clients) sendf(i, 1, "risi", SV_DEMOPLAYBACK, "", i);
752
753 sendservmsg("demo playback finished");
754
755 loopv(clients) sendwelcome(clients[i]);
756 }
757
setupdemoplayback()758 void setupdemoplayback()
759 {
760 demoheader hdr;
761 string msg;
762 msg[0] = '\0';
763 defformatstring(file)("demos/%s.dmo", smapname);
764 path(file);
765 demoplayback = opengzfile(file, "rb");
766 if(!demoplayback) formatstring(msg)("could not read demo \"%s\"", file);
767 else if(demoplayback->read(&hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)))
768 formatstring(msg)("\"%s\" is not a demo file", file);
769 else
770 {
771 lilswap(&hdr.version, 1);
772 lilswap(&hdr.protocol, 1);
773 if(hdr.version!=DEMO_VERSION) formatstring(msg)("demo \"%s\" requires an %s version of AssaultCube", file, hdr.version<DEMO_VERSION ? "older" : "newer");
774 else if(hdr.protocol != PROTOCOL_VERSION && !(hdr.protocol < 0 && hdr.protocol == -PROTOCOL_VERSION) && hdr.protocol != 1132) formatstring(msg)("demo \"%s\" requires an %s version of AssaultCube", file, hdr.protocol<PROTOCOL_VERSION ? "older" : "newer");
775 else if(hdr.protocol == 1132) sendservmsg("WARNING: using experimental compatibility mode for older demo protocol, expect breakage");
776 demoprotocol = hdr.protocol;
777 }
778 if(msg[0])
779 {
780 if(demoplayback) { delete demoplayback; demoplayback = NULL; }
781 sendservmsg(msg);
782 return;
783 }
784
785 formatstring(msg)("playing demo \"%s\"", file);
786 sendservmsg(msg);
787 sendf(-1, 1, "risi", SV_DEMOPLAYBACK, smapname, -1);
788 watchingdemo = true;
789
790 if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
791 {
792 enddemoplayback();
793 return;
794 }
795 lilswap(&nextplayback, 1);
796 }
797
readdemo()798 void readdemo()
799 {
800 if(!demoplayback) return;
801 while(gamemillis>=nextplayback)
802 {
803 int chan, len;
804 if(demoplayback->read(&chan, sizeof(chan))!=sizeof(chan) ||
805 demoplayback->read(&len, sizeof(len))!=sizeof(len))
806 {
807 enddemoplayback();
808 return;
809 }
810 lilswap(&chan, 1);
811 lilswap(&len, 1);
812 ENetPacket *packet = enet_packet_create(NULL, len, 0);
813 if(!packet || demoplayback->read(packet->data, len)!=len)
814 {
815 if(packet) enet_packet_destroy(packet);
816 enddemoplayback();
817 return;
818 }
819 sendpacket(-1, chan, packet, -1, true);
820 if(!packet->referenceCount) enet_packet_destroy(packet);
821 if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
822 {
823 enddemoplayback();
824 return;
825 }
826 lilswap(&nextplayback, 1);
827 }
828 }
829
830 struct sflaginfo
831 {
832 int state;
833 int actor_cn;
834 float pos[3];
835 int lastupdate;
836 int stolentime;
837 short x, y; // flag entity location
838
sflaginfosflaginfo839 sflaginfo() { actor_cn = -1; }
840 } sflaginfos[2];
841
putflaginfo(packetbuf & p,int flag)842 void putflaginfo(packetbuf &p, int flag)
843 {
844 sflaginfo &f = sflaginfos[flag];
845 putint(p, SV_FLAGINFO);
846 putint(p, flag);
847 putint(p, f.state);
848 switch(f.state)
849 {
850 case CTFF_STOLEN:
851 putint(p, f.actor_cn);
852 break;
853 case CTFF_DROPPED:
854 loopi(3) putuint(p, (int)(f.pos[i]*DMF));
855 break;
856 }
857 }
858
send_item_list(packetbuf & p)859 inline void send_item_list(packetbuf &p)
860 {
861 putint(p, SV_ITEMLIST);
862 loopv(sents) if(sents[i].spawned) putint(p, i);
863 putint(p, -1);
864 if(m_flags) loopi(2) putflaginfo(p, i);
865 }
866
867 #include "serverchecks.h"
868
flagdistance(sflaginfo & f,int cn)869 bool flagdistance(sflaginfo &f, int cn)
870 {
871 if(!valid_client(cn) || m_demo) return false;
872 client &c = *clients[cn];
873 vec v(-1, -1, c.state.o.z);
874 switch(f.state)
875 {
876 case CTFF_INBASE:
877 v.x = f.x; v.y = f.y;
878 break;
879 case CTFF_DROPPED:
880 v.x = f.pos[0]; v.y = f.pos[1];
881 break;
882 }
883 bool lagging = (c.ping > 1000 || c.spj > 100);
884 if(v.x < 0 && !lagging) return true;
885 float dist = c.state.o.dist(v);
886 int pdist = check_pdist(&c,dist);
887 if(pdist)
888 {
889 c.farpickups++;
890 logline(ACLOG_INFO, "[%s] %s %s the %s flag at distance %.2f (%d)",
891 c.hostname, c.name, (pdist==2?"tried to touch":"touched"), team_string(&f == sflaginfos + 1), dist, c.farpickups);
892 if (pdist==2) return false;
893 }
894 return lagging ? false : true; // today I found a lag hacker :: Brahma, 19-oct-2010... lets test it a bit
895 }
896
sendflaginfo(int flag=-1,int cn=-1)897 void sendflaginfo(int flag = -1, int cn = -1)
898 {
899 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
900 if(flag >= 0) putflaginfo(p, flag);
901 else loopi(2) putflaginfo(p, i);
902 sendpacket(cn, 1, p.finalize());
903 }
904
flagmessage(int flag,int message,int actor,int cn=-1)905 void flagmessage(int flag, int message, int actor, int cn = -1)
906 {
907 if(message == FM_KTFSCORE)
908 sendf(cn, 1, "riiiii", SV_FLAGMSG, flag, message, actor, (gamemillis - sflaginfos[flag].stolentime) / 1000);
909 else
910 sendf(cn, 1, "riiii", SV_FLAGMSG, flag, message, actor);
911 }
912
flagaction(int flag,int action,int actor)913 void flagaction(int flag, int action, int actor)
914 {
915 if(!valid_flag(flag)) return;
916 sflaginfo &f = sflaginfos[flag];
917 sflaginfo &of = sflaginfos[team_opposite(flag)];
918 bool deadactor = valid_client(actor) ? clients[actor]->state.state != CS_ALIVE || team_isspect(clients[actor]->team): true;
919 int abort = 0;
920 int score = 0;
921 int message = -1;
922
923 if(m_ctf || m_htf)
924 {
925 switch(action)
926 {
927 case FA_PICKUP: // ctf: f = enemy team htf: f = own team
928 case FA_STEAL:
929 {
930 if(deadactor || f.state != (action == FA_STEAL ? CTFF_INBASE : CTFF_DROPPED) || !flagdistance(f, actor)) { abort = 10; break; }
931 int team = team_base(clients[actor]->team);
932 if(m_ctf) team = team_opposite(team);
933 if(team != flag) { abort = 11; break; }
934 f.state = CTFF_STOLEN;
935 f.actor_cn = actor;
936 message = FM_PICKUP;
937 break;
938 }
939 case FA_LOST:
940 if(actor == -1) actor = f.actor_cn;
941 case FA_DROP:
942 if(f.state!=CTFF_STOLEN || f.actor_cn != actor) { abort = 12; break; }
943 f.state = CTFF_DROPPED;
944 loopi(3) f.pos[i] = clients[actor]->state.o[i];
945 message = action == FA_LOST ? FM_LOST : FM_DROP;
946 break;
947 case FA_RETURN:
948 if(f.state!=CTFF_DROPPED || m_htf) { abort = 13; break; }
949 f.state = CTFF_INBASE;
950 message = FM_RETURN;
951 break;
952 case FA_SCORE: // ctf: f = carried by actor flag, htf: f = hunted flag (run over by actor)
953 if(m_ctf)
954 {
955 if(f.state != CTFF_STOLEN || f.actor_cn != actor || of.state != CTFF_INBASE || !flagdistance(of, actor)) { abort = 14; break; }
956 score = 1;
957 message = FM_SCORE;
958 }
959 else // m_htf
960 {
961 if(f.state != CTFF_DROPPED || !flagdistance(f, actor)) { abort = 15; break; }
962 score = (of.state == CTFF_STOLEN) ? 1 : 0;
963 message = score ? FM_SCORE : FM_SCOREFAIL;
964 if(of.actor_cn == actor) score *= 2;
965 }
966 f.state = CTFF_INBASE;
967 break;
968
969 case FA_RESET:
970 f.state = CTFF_INBASE;
971 message = FM_RESET;
972 break;
973 }
974 }
975 else if(m_ktf) // f: active flag, of: idle flag
976 {
977 switch(action)
978 {
979 case FA_STEAL:
980 if(deadactor || f.state != CTFF_INBASE || !flagdistance(f, actor)) { abort = 20; break; }
981 f.state = CTFF_STOLEN;
982 f.actor_cn = actor;
983 f.stolentime = gamemillis;
984 message = FM_PICKUP;
985 break;
986 case FA_SCORE: // f = carried by actor flag
987 if(actor != -1 || f.state != CTFF_STOLEN) { abort = 21; break; } // no client msg allowed here
988 if(valid_client(f.actor_cn) && clients[f.actor_cn]->state.state == CS_ALIVE && !team_isspect(clients[f.actor_cn]->team))
989 {
990 actor = f.actor_cn;
991 score = 1;
992 message = FM_KTFSCORE;
993 break;
994 }
995 case FA_LOST:
996 if(actor == -1) actor = f.actor_cn;
997 case FA_DROP:
998 if(f.actor_cn != actor || f.state != CTFF_STOLEN) { abort = 22; break; }
999 case FA_RESET:
1000 if(f.state == CTFF_STOLEN)
1001 {
1002 actor = f.actor_cn;
1003 message = FM_LOST;
1004 }
1005 f.state = CTFF_IDLE;
1006 of.state = CTFF_INBASE;
1007 sendflaginfo(team_opposite(flag));
1008 break;
1009 }
1010 }
1011 if(abort)
1012 {
1013 logline(ACLOG_DEBUG,"aborting flagaction(flag %d, action %d, actor %d), reason %d, resending flag states", flag, action, actor, abort); // FIXME: remove this logline after some time - it will only show a few bad ping effects
1014 sendflaginfo();
1015 return;
1016 }
1017 if(score)
1018 {
1019 client *c = clients[actor];
1020 c->state.flagscore += score;
1021 sendf(-1, 1, "riii", SV_FLAGCNT, actor, c->state.flagscore);
1022 if (m_teammode) computeteamwork(c->team, c->clientnum); /** WIP */
1023 }
1024 if(valid_client(actor))
1025 {
1026 client &c = *clients[actor];
1027 switch(message)
1028 {
1029 case FM_PICKUP:
1030 logline(ACLOG_INFO,"[%s] %s %s the flag", c.hostname, c.name, action == FA_STEAL ? "stole" : "picked up");
1031 break;
1032 case FM_DROP:
1033 case FM_LOST:
1034 logline(ACLOG_INFO,"[%s] %s %s the flag", c.hostname, c.name, message == FM_LOST ? "lost" : "dropped");
1035 break;
1036 case FM_RETURN:
1037 logline(ACLOG_INFO,"[%s] %s returned the flag", c.hostname, c.name);
1038 break;
1039 case FM_SCORE:
1040 if(m_htf)
1041 logline(ACLOG_INFO, "[%s] %s hunted the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore);
1042 else
1043 logline(ACLOG_INFO, "[%s] %s scored with the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore);
1044 break;
1045 case FM_KTFSCORE:
1046 logline(ACLOG_INFO, "[%s] %s scored, carrying for %d seconds, new score %d", c.hostname, c.name, (gamemillis - f.stolentime) / 1000, c.state.flagscore);
1047 break;
1048 case FM_SCOREFAIL:
1049 logline(ACLOG_INFO, "[%s] %s failed to score", c.hostname, c.name);
1050 break;
1051 default:
1052 logline(ACLOG_INFO, "flagaction %d, actor %d, flag %d, message %d", action, actor, flag, message);
1053 break;
1054 }
1055 flagpoints (&c, message);
1056 }
1057 else
1058 {
1059 switch(message)
1060 {
1061 case FM_RESET:
1062 logline(ACLOG_INFO,"the server reset the flag for team %s", team_string(flag));
1063 break;
1064 default:
1065 logline(ACLOG_INFO, "flagaction %d with invalid actor cn %d, flag %d, message %d", action, actor, flag, message);
1066 break;
1067 }
1068 }
1069
1070 f.lastupdate = gamemillis;
1071 sendflaginfo(flag);
1072 if(message >= 0)
1073 flagmessage(flag, message, valid_client(actor) ? actor : -1);
1074 }
1075
clienthasflag(int cn)1076 int clienthasflag(int cn)
1077 {
1078 if(m_flags && valid_client(cn))
1079 {
1080 loopi(2) { if(sflaginfos[i].state==CTFF_STOLEN && sflaginfos[i].actor_cn==cn) return i; }
1081 }
1082 return -1;
1083 }
1084
ctfreset()1085 void ctfreset()
1086 {
1087 int idleflag = m_ktf ? rnd(2) : -1;
1088 loopi(2)
1089 {
1090 sflaginfos[i].actor_cn = -1;
1091 sflaginfos[i].state = i == idleflag ? CTFF_IDLE : CTFF_INBASE;
1092 sflaginfos[i].lastupdate = -1;
1093 }
1094 }
1095
sdropflag(int cn)1096 void sdropflag(int cn)
1097 {
1098 int fl = clienthasflag(cn);
1099 if(fl >= 0) flagaction(fl, FA_LOST, cn);
1100 }
1101
resetflag(int cn)1102 void resetflag(int cn)
1103 {
1104 int fl = clienthasflag(cn);
1105 if(fl >= 0) flagaction(fl, FA_RESET, -1);
1106 }
1107
htf_forceflag(int flag)1108 void htf_forceflag(int flag)
1109 {
1110 sflaginfo &f = sflaginfos[flag];
1111 int besthealth = 0;
1112 vector<int> clientnumbers;
1113 loopv(clients) if(clients[i]->type!=ST_EMPTY)
1114 {
1115 if(clients[i]->state.state == CS_ALIVE && team_base(clients[i]->team) == flag)
1116 {
1117 if(clients[i]->state.health == besthealth)
1118 clientnumbers.add(i);
1119 else
1120 {
1121 if(clients[i]->state.health > besthealth)
1122 {
1123 besthealth = clients[i]->state.health;
1124 clientnumbers.shrink(0);
1125 clientnumbers.add(i);
1126 }
1127 }
1128 }
1129 }
1130
1131 if(clientnumbers.length())
1132 {
1133 int pick = rnd(clientnumbers.length());
1134 client *cl = clients[clientnumbers[pick]];
1135 f.state = CTFF_STOLEN;
1136 f.actor_cn = cl->clientnum;
1137 sendflaginfo(flag);
1138 flagmessage(flag, FM_PICKUP, cl->clientnum);
1139 logline(ACLOG_INFO,"[%s] %s got forced to pickup the flag", cl->hostname, cl->name);
1140 }
1141 f.lastupdate = gamemillis;
1142 }
1143
1144 int arenaround = 0, arenaroundstartmillis = 0;
1145
1146 struct twoint { int index, value; };
cmpscore(const int * a,const int * b)1147 int cmpscore(const int *a, const int *b) { return clients[*a]->at3_score - clients[*b]->at3_score; }
cmptwoint(const struct twoint * a,const struct twoint * b)1148 int cmptwoint(const struct twoint *a, const struct twoint *b) { return a->value - b->value; }
1149 vector<int> tdistrib;
1150 vector<twoint> sdistrib;
1151
distributeteam(int team)1152 void distributeteam(int team)
1153 {
1154 int numsp = team == 100 ? smapstats.spawns[2] : smapstats.spawns[team];
1155 if(!numsp) numsp = 30; // no spawns: try to distribute anyway
1156 twoint ti;
1157 tdistrib.shrink(0);
1158 loopv(clients) if(clients[i]->type!=ST_EMPTY)
1159 {
1160 if(team == 100 || team == clients[i]->team)
1161 {
1162 tdistrib.add(i);
1163 clients[i]->at3_score = rnd(0x1000000);
1164 }
1165 }
1166 tdistrib.sort(cmpscore); // random player order
1167 sdistrib.shrink(0);
1168 loopi(numsp)
1169 {
1170 ti.index = i;
1171 ti.value = rnd(0x1000000);
1172 sdistrib.add(ti);
1173 }
1174 sdistrib.sort(cmptwoint); // random spawn order
1175 int x = 0;
1176 loopv(tdistrib)
1177 {
1178 clients[tdistrib[i]]->spawnindex = sdistrib[x++].index;
1179 x %= sdistrib.length();
1180 }
1181 }
1182
distributespawns()1183 void distributespawns()
1184 {
1185 loopv(clients) if(clients[i]->type!=ST_EMPTY)
1186 {
1187 clients[i]->spawnindex = -1;
1188 }
1189 if(m_teammode)
1190 {
1191 distributeteam(0);
1192 distributeteam(1);
1193 }
1194 else
1195 {
1196 distributeteam(100);
1197 }
1198 }
1199
1200 bool items_blocked = false;
free_items(int n)1201 bool free_items(int n)
1202 {
1203 client *c = clients[n];
1204 int waitspawn = min(c->ping,200) + c->state.spawn; // flowtron to Brahma: can't this be removed now? (re: rev. 5270)
1205 return !items_blocked && (waitspawn < gamemillis);
1206 }
1207
1208 void checkitemspawns(int);
1209
arenacheck()1210 void arenacheck()
1211 {
1212 if(!m_arena || interm || gamemillis<arenaround || !numactiveclients()) return;
1213
1214 if(arenaround)
1215 { // start new arena round
1216 arenaround = 0;
1217 arenaroundstartmillis = gamemillis;
1218 distributespawns();
1219 checkitemspawns(60*1000); // the server will respawn all items now
1220 loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed)
1221 {
1222 if(clients[i]->isonrightmap && team_isactive(clients[i]->team))
1223 sendspawn(clients[i]);
1224 }
1225 items_blocked = false;
1226 return;
1227 }
1228
1229 #ifndef STANDALONE
1230 if(m_botmode && clients[0]->type==ST_LOCAL)
1231 {
1232 int enemies = 0, alive_enemies = 0;
1233 playerent *alive = NULL;
1234 loopv(players) if(players[i] && (!m_teammode || players[i]->team == team_opposite(player1->team)))
1235 {
1236 enemies++;
1237 if(players[i]->state == CS_ALIVE)
1238 {
1239 alive = players[i];
1240 alive_enemies++;
1241 }
1242 }
1243 if(player1->state != CS_DEAD) alive = player1;
1244 if(enemies && (!alive_enemies || player1->state == CS_DEAD))
1245 {
1246 sendf(-1, 1, "ri2", SV_ARENAWIN, m_teammode ? (alive ? alive->clientnum : -1) : (alive && alive->type == ENT_BOT ? -2 : player1->state == CS_ALIVE ? player1->clientnum : -1));
1247 arenaround = gamemillis+5000;
1248 }
1249 return;
1250 }
1251 #endif
1252 client *alive = NULL;
1253 bool dead = false;
1254 int lastdeath = 0;
1255 loopv(clients)
1256 {
1257 client &c = *clients[i];
1258 if(c.type==ST_EMPTY || !c.isauthed || !c.isonrightmap || team_isspect(c.team)) continue; /// TODO: simplify the team/state sysmtem, it is not smart to have SPECTATE in both, for example
1259 if (c.state.lastspawn < 0 && (c.state.state==CS_DEAD || c.state.state==CS_SPECTATE))
1260 {
1261 dead = true;
1262 lastdeath = max(lastdeath, c.state.lastdeath);
1263 }
1264 else if(c.state.state==CS_ALIVE)
1265 {
1266 if(!alive) alive = &c;
1267 else if(!m_teammode || alive->team != c.team) return;
1268 }
1269 }
1270
1271 if(!dead || gamemillis < lastdeath + 500) return;
1272 items_blocked = true;
1273 sendf(-1, 1, "ri2", SV_ARENAWIN, alive ? alive->clientnum : -1);
1274 arenaround = gamemillis+5000;
1275 if(autoteam && m_teammode) refillteams(true);
1276 }
1277
1278 #define SPAMREPEATINTERVAL 20 // detect doubled lines only if interval < 20 seconds
1279 #define SPAMMAXREPEAT 3 // 4th time is SPAM
1280 #define SPAMCHARPERMINUTE 220 // good typist
1281 #define SPAMCHARINTERVAL 30 // allow 20 seconds typing at maxspeed
1282
spamdetect(client * cl,char * text)1283 bool spamdetect(client *cl, char *text) // checks doubled lines and average typing speed
1284 {
1285 if(cl->type != ST_TCPIP || cl->role == CR_ADMIN) return false;
1286 bool spam = false;
1287 int pause = servmillis - cl->lastsay;
1288 if(pause < 0 || pause > 90*1000) pause = 90*1000;
1289 cl->saychars -= (SPAMCHARPERMINUTE * pause) / (60*1000);
1290 cl->saychars += (int)strlen(text);
1291 if(cl->saychars < 0) cl->saychars = 0;
1292 if(text[0] && !strcmp(text, cl->lastsaytext) && servmillis - cl->lastsay < SPAMREPEATINTERVAL*1000)
1293 {
1294 spam = ++cl->spamcount > SPAMMAXREPEAT;
1295 }
1296 else
1297 {
1298 copystring(cl->lastsaytext, text);
1299 cl->spamcount = 0;
1300 }
1301 cl->lastsay = servmillis;
1302 if(cl->saychars > (SPAMCHARPERMINUTE * SPAMCHARINTERVAL) / 60)
1303 spam = true;
1304 return spam;
1305 }
1306
1307 // chat message distribution matrix:
1308 //
1309 // /------------------------ common chat C c C c c C C c C
1310 // |/----------------------- RVSF chat T
1311 // ||/---------------------- CLA chat T
1312 // |||/--------------------- spect chat t t t T t T
1313 // |||| | | | | | | | | |
1314 // |||| | | | | | | | | | C: normal chat
1315 // |||| team modes: chat goes to | | | | | | | | | T: team chat
1316 // XX --> RVSF players >-----/ | | | | | | | | c: normal chat in all mastermodes except 'match'
1317 // XX X --> RVSF spect players >-------/ | | | | | | | t: all chat in mastermode 'match', otherwise only team chat
1318 // X X --> CLA players >---------/ | | | | | |
1319 // X XX --> CLA spect players >-----------/ | | | | |
1320 // X X --> SPECTATORs >-------------/ | | | |
1321 // XXXX --> SPECTATORs (admin) >---------------/ | | |
1322 // ffa modes: | | |
1323 // X --> any player (ffa mode) >-----------------/ | |
1324 // X X --> any spectator (ffa mode) >-------------------/ |
1325 // X X --> admin spectator >---------------------/
1326
1327 // purpose:
1328 // a) give spects a possibility to chat without annoying the players (even in ffa),
1329 // b) no hidden messages from spects to active teams,
1330 // c) no spect talk to players during 'match'
1331
sendteamtext(char * text,int sender,int msgtype)1332 void sendteamtext(char *text, int sender, int msgtype)
1333 {
1334 if(!valid_client(sender) || clients[sender]->team == TEAM_VOID) return;
1335 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1336 putint(p, msgtype);
1337 putint(p, sender);
1338 sendstring(text, p);
1339 ENetPacket *packet = p.finalize();
1340 recordpacket(1, packet);
1341 int &st = clients[sender]->team;
1342 loopv(clients) if(i!=sender)
1343 {
1344 int &rt = clients[i]->team;
1345 if((rt == TEAM_SPECT && clients[i]->role == CR_ADMIN) || // spect-admin reads all
1346 (team_isactive(st) && st == team_group(rt)) || // player to own team + own spects
1347 (team_isspect(st) && team_isspect(rt))) // spectator to other spectators
1348 sendpacket(i, 1, packet);
1349 }
1350 }
1351
sendvoicecomteam(int sound,int sender)1352 void sendvoicecomteam(int sound, int sender)
1353 {
1354 if(!valid_client(sender) || clients[sender]->team == TEAM_VOID) return;
1355 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1356 putint(p, SV_VOICECOMTEAM);
1357 putint(p, sender);
1358 putint(p, sound);
1359 ENetPacket *packet = p.finalize();
1360 loopv(clients) if(i!=sender)
1361 {
1362 if(clients[i]->team == clients[sender]->team || !m_teammode)
1363 sendpacket(i, 1, packet);
1364 }
1365 }
1366
numplayers()1367 int numplayers()
1368 {
1369 int count = 1;
1370 #ifdef STANDALONE
1371 count = numclients();
1372 #else
1373 if(m_botmode)
1374 {
1375 extern vector<botent *> bots;
1376 loopv(bots) if(bots[i]) count++;
1377 }
1378 if(m_demo)
1379 {
1380 count = numclients();
1381 }
1382 #endif
1383 return count;
1384 }
1385
spawntime(int type)1386 int spawntime(int type)
1387 {
1388 int np = numplayers();
1389 np = np<3 ? 4 : (np>4 ? 2 : 3); // Some spawn times are dependent on the number of players.
1390 int sec = 0;
1391 switch(type)
1392 {
1393 // Please update ./ac_website/htdocs/docs/introduction.html if these times change.
1394 case I_CLIPS:
1395 case I_AMMO: sec = np*2; break;
1396 case I_GRENADE: sec = np + 5; break;
1397 case I_HEALTH: sec = np*5; break;
1398 case I_HELMET:
1399 case I_ARMOUR: sec = 25; break;
1400 case I_AKIMBO: sec = 60; break;
1401 }
1402 return sec*1000;
1403 }
1404
serverpickup(int i,int sender)1405 bool serverpickup(int i, int sender) // server side item pickup, acknowledge first client that gets it
1406 {
1407 const char *hn = sender >= 0 && clients[sender]->type == ST_TCPIP ? clients[sender]->hostname : NULL;
1408 if(!sents.inrange(i))
1409 {
1410 if(hn && !m_coop) logline(ACLOG_INFO, "[%s] tried to pick up entity #%d - doesn't exist on this map", hn, i);
1411 return false;
1412 }
1413 server_entity &e = sents[i];
1414 if(!e.spawned)
1415 {
1416 if(!e.legalpickup && hn && !m_demo) logline(ACLOG_INFO, "[%s] tried to pick up entity #%d (%s) - can't be picked up in this gamemode or at all", hn, i, entnames[e.type]);
1417 return false;
1418 }
1419 if(sender>=0)
1420 {
1421 client *cl = clients[sender];
1422 if(cl->type==ST_TCPIP)
1423 {
1424 if( cl->state.state!=CS_ALIVE || !cl->state.canpickup(e.type) || ( m_arena && !free_items(sender) ) ) return false;
1425 vec v(e.x, e.y, cl->state.o.z);
1426 float dist = cl->state.o.dist(v);
1427 int pdist = check_pdist(cl,dist);
1428 if (pdist)
1429 {
1430 cl->farpickups++;
1431 if (!m_demo) logline(ACLOG_INFO, "[%s] %s %s up entity #%d (%s), distance %.2f (%d)",
1432 cl->hostname, cl->name, (pdist==2?"tried to pick":"picked"), i, entnames[e.type], dist, cl->farpickups);
1433 if (pdist==2) return false;
1434 }
1435 }
1436 sendf(-1, 1, "ri3", SV_ITEMACC, i, sender);
1437 cl->state.pickup(sents[i].type);
1438 if (m_lss && sents[i].type == I_GRENADE) cl->state.pickup(sents[i].type); // get two nades at lss
1439 }
1440 e.spawned = false;
1441 if(!m_lms) e.spawntime = spawntime(e.type);
1442 return true;
1443 }
1444
checkitemspawns(int diff)1445 void checkitemspawns(int diff)
1446 {
1447 if(!diff) return;
1448 loopv(sents) if(sents[i].spawntime)
1449 {
1450 sents[i].spawntime -= diff;
1451 if(sents[i].spawntime<=0)
1452 {
1453 sents[i].spawntime = 0;
1454 sents[i].spawned = true;
1455 sendf(-1, 1, "ri2", SV_ITEMSPAWN, i);
1456 }
1457 }
1458 }
1459
serverdamage(client * target,client * actor,int damage,int gun,bool gib,const vec & hitpush=vec (0,0,0))1460 void serverdamage(client *target, client *actor, int damage, int gun, bool gib, const vec &hitpush = vec(0, 0, 0))
1461 {
1462 if (!m_demo && !m_coop && !validdamage(target, actor, damage, gun, gib)) return;
1463 if ( m_arena && gun == GUN_GRENADE && arenaroundstartmillis + 2000 > gamemillis && target != actor ) return;
1464 clientstate &ts = target->state;
1465 ts.dodamage(damage, gun);
1466 if(damage < INT_MAX)
1467 {
1468 actor->state.damage += damage;
1469 sendf(-1, 1, "ri7", gib ? SV_GIBDAMAGE : SV_DAMAGE, target->clientnum, actor->clientnum, gun, damage, ts.armour, ts.health);
1470 if(target!=actor)
1471 {
1472 checkcombo (target, actor, damage, gun);
1473 if(!hitpush.iszero())
1474 {
1475 vec v(hitpush);
1476 if(!v.iszero()) v.normalize();
1477 sendf(target->clientnum, 1, "ri6", SV_HITPUSH, gun, damage,
1478 int(v.x*DNF), int(v.y*DNF), int(v.z*DNF));
1479 }
1480 }
1481 }
1482 if(ts.health<=0)
1483 {
1484 int targethasflag = clienthasflag(target->clientnum);
1485 bool tk = false, suic = false;
1486 target->state.deaths++;
1487 checkfrag(target, actor, gun, gib);
1488 if(target!=actor)
1489 {
1490 if(!isteam(target->team, actor->team)) actor->state.frags += gib && gun != GUN_GRENADE && gun != GUN_SHOTGUN ? 2 : 1;
1491 else
1492 {
1493 actor->state.frags--;
1494 actor->state.teamkills++;
1495 tk = true;
1496 }
1497 }
1498 else
1499 { // suicide
1500 actor->state.frags--;
1501 suic = true;
1502 logline(ACLOG_INFO, "[%s] %s suicided", actor->hostname, actor->name);
1503 }
1504 sendf(-1, 1, "ri5", gib ? SV_GIBDIED : SV_DIED, target->clientnum, actor->clientnum, actor->state.frags, gun);
1505 if((suic || tk) && (m_htf || m_ktf) && targethasflag >= 0)
1506 {
1507 actor->state.flagscore--;
1508 sendf(-1, 1, "riii", SV_FLAGCNT, actor->clientnum, actor->state.flagscore);
1509 }
1510 target->position.setsize(0);
1511 ts.state = CS_DEAD;
1512 ts.lastdeath = gamemillis;
1513 if(!suic) logline(ACLOG_INFO, "[%s] %s %s%s %s", actor->hostname, actor->name, killmessage(gun, gib), tk ? " their teammate" : "", target->name);
1514 if(m_flags && targethasflag >= 0)
1515 {
1516 if(m_ctf)
1517 flagaction(targethasflag, tk ? FA_RESET : FA_LOST, -1);
1518 else if(m_htf)
1519 flagaction(targethasflag, FA_LOST, -1);
1520 else // ktf || tktf
1521 flagaction(targethasflag, FA_RESET, -1);
1522 }
1523 // don't issue respawn yet until DEATHMILLIS has elapsed
1524 // ts.respawn();
1525
1526 if(isdedicated && actor->type == ST_TCPIP && tk)
1527 {
1528 if( actor->state.frags < scl.banthreshold ||
1529 /** teamkilling more than 6 (defaults), more than 2 per minute and less than 4 frags per tk */
1530 ( actor->state.teamkills >= -scl.banthreshold &&
1531 actor->state.teamkills * 30 * 1000 > gamemillis &&
1532 actor->state.frags < 4 * actor->state.teamkills ) )
1533 {
1534 addban(actor, DISC_AUTOBAN);
1535 }
1536 else if( actor->state.frags < scl.kickthreshold ||
1537 /** teamkilling more than 5 (defaults), more than 1 tk per minute and less than 4 frags per tk */
1538 ( actor->state.teamkills >= -scl.kickthreshold &&
1539 actor->state.teamkills * 60 * 1000 > gamemillis &&
1540 actor->state.frags < 4 * actor->state.teamkills ) ) disconnect_client(actor->clientnum, DISC_AUTOKICK);
1541 }
1542 } else if ( target!=actor && isteam(target->team, actor->team) ) check_ffire (target, actor, damage); // friendly fire counter
1543 }
1544
1545 #include "serverevents.h"
1546
updatedescallowed(void)1547 bool updatedescallowed(void) { return scl.servdesc_pre[0] || scl.servdesc_suf[0]; }
1548
updatesdesc(const char * newdesc,ENetAddress * caller=NULL)1549 void updatesdesc(const char *newdesc, ENetAddress *caller = NULL)
1550 {
1551 if(!newdesc || !newdesc[0] || !updatedescallowed())
1552 {
1553 copystring(servdesc_current, scl.servdesc_full);
1554 custom_servdesc = false;
1555 }
1556 else
1557 {
1558 formatstring(servdesc_current)("%s%s%s", scl.servdesc_pre, newdesc, scl.servdesc_suf);
1559 custom_servdesc = true;
1560 if(caller) servdesc_caller = *caller;
1561 }
1562 }
1563
canspawn(client * c)1564 int canspawn(client *c) // beware: canspawn() doesn't check m_arena!
1565 {
1566 if(!c || c->type == ST_EMPTY || !c->isauthed || !team_isvalid(c->team) ||
1567 (c->type == ST_TCPIP && (c->state.lastdeath > 0 ? gamemillis - c->state.lastdeath : servmillis - c->connectmillis) < (m_arena ? 0 : (m_flags ? 5000 : 2000))) ||
1568 (servmillis - c->connectmillis < 1000 + c->state.reconnections * 2000 &&
1569 gamemillis > 10000 && totalclients > 3 && !team_isspect(c->team))) return SP_OK_NUM; // equivalent to SP_DENY
1570 if(!c->isonrightmap) return SP_WRONGMAP;
1571 if(mastermode == MM_MATCH && matchteamsize)
1572 {
1573 if(c->team == TEAM_SPECT || (team_isspect(c->team) && !m_teammode)) return SP_SPECT;
1574 if(c->team == TEAM_CLA_SPECT || c->team == TEAM_RVSF_SPECT)
1575 {
1576 if(numteamclients()[team_base(c->team)] >= matchteamsize) return SP_SPECT;
1577 else return SP_REFILLMATCH;
1578 }
1579 }
1580 return SP_OK;
1581 }
1582
1583 /** FIXME: this function is unnecessarily complicated */
updateclientteam(int cln,int newteam,int ftr)1584 bool updateclientteam(int cln, int newteam, int ftr)
1585 {
1586 if(!valid_client(cln)) return false;
1587 if(!team_isvalid(newteam) && newteam != TEAM_ANYACTIVE) newteam = TEAM_SPECT;
1588 client &cl = *clients[cln];
1589 if(cl.team == newteam && ftr != FTR_AUTOTEAM) return true; // no change
1590 int *teamsizes = numteamclients(cln);
1591 if( mastermode == MM_OPEN && cl.state.forced && ftr == FTR_PLAYERWISH &&
1592 newteam < TEAM_SPECT && team_base(cl.team) != team_base(newteam) ) return false; // no free will changes to forced people
1593 if(newteam == TEAM_ANYACTIVE) // when spawning from spect
1594 {
1595 if(mastermode == MM_MATCH && cl.team < TEAM_SPECT)
1596 {
1597 newteam = team_base(cl.team);
1598 }
1599 else
1600 {
1601 if(autoteam && teamsizes[TEAM_CLA] != teamsizes[TEAM_RVSF]) newteam = teamsizes[TEAM_CLA] < teamsizes[TEAM_RVSF] ? TEAM_CLA : TEAM_RVSF;
1602 else
1603 { // join weaker team
1604 int teamscore[2] = {0, 0}, sum = calcscores();
1605 loopv(clients) if(clients[i]->type!=ST_EMPTY && i != cln && clients[i]->isauthed && clients[i]->team != TEAM_SPECT)
1606 {
1607 teamscore[team_base(clients[i]->team)] += clients[i]->at3_score;
1608 }
1609 newteam = sum > 200 ? (teamscore[TEAM_CLA] < teamscore[TEAM_RVSF] ? TEAM_CLA : TEAM_RVSF) : rnd(2);
1610 }
1611 }
1612 }
1613 if(ftr == FTR_PLAYERWISH)
1614 {
1615 if(mastermode == MM_MATCH && matchteamsize && m_teammode)
1616 {
1617 if(newteam != TEAM_SPECT && (team_base(newteam) != team_base(cl.team) || !m_teammode)) return false; // no switching sides in match mode when teamsize is set
1618 }
1619 if(team_isactive(newteam))
1620 {
1621 if(!m_teammode && cl.state.state == CS_ALIVE) return false; // no comments
1622 if(mastermode == MM_MATCH)
1623 {
1624 if(m_teammode && matchteamsize && teamsizes[newteam] >= matchteamsize) return false; // ensure maximum team size
1625 }
1626 else
1627 {
1628 if(m_teammode && autoteam && teamsizes[newteam] > teamsizes[team_opposite(newteam)]) return false; // don't switch to an already bigger team
1629 }
1630 }
1631 else if(mastermode != MM_MATCH || !m_teammode) newteam = TEAM_SPECT; // only match mode (team) has more than one spect team
1632 }
1633 if(cl.team == newteam && ftr != FTR_AUTOTEAM) return true; // no change
1634 if(cl.team != newteam) sdropflag(cl.clientnum);
1635 if(ftr != FTR_INFO && (team_isspect(newteam) || (team_isactive(newteam) && team_isactive(cl.team)))) forcedeath(&cl);
1636 sendf(-1, 1, "riii", SV_SETTEAM, cln, newteam | ((ftr == FTR_SILENTFORCE ? FTR_INFO : ftr) << 4));
1637 if(ftr != FTR_INFO && !team_isspect(newteam) && team_isspect(cl.team)) sendspawn(&cl);
1638 if (team_isspect(newteam)) cl.state.state = CS_SPECTATE;
1639 cl.team = newteam;
1640 return true;
1641 }
1642
calcscores()1643 int calcscores() // skill eval
1644 {
1645 int sum = 0;
1646 loopv(clients) if(clients[i]->type!=ST_EMPTY)
1647 {
1648 clientstate &cs = clients[i]->state;
1649 sum += clients[i]->at3_score = cs.points > 0 ? ufSqrt((float)cs.points) : -ufSqrt((float)-cs.points);
1650 }
1651 return sum;
1652 }
1653
1654 vector<int> shuffle;
1655
shuffleteams(int ftr=FTR_AUTOTEAM)1656 void shuffleteams(int ftr = FTR_AUTOTEAM)
1657 {
1658 int numplayers = numclients();
1659 int team, sums = calcscores();
1660 if(gamemillis < 2 * 60 *1000)
1661 { // random
1662 int teamsize[2] = {0, 0};
1663 loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isonrightmap && !team_isspect(clients[i]->team)) // only shuffle active players
1664 {
1665 sums += rnd(1000);
1666 team = sums & 1;
1667 if(teamsize[team] >= numplayers/2) team = team_opposite(team);
1668 updateclientteam(i, team, ftr);
1669 teamsize[team]++;
1670 sums >>= 1;
1671 }
1672 }
1673 else
1674 { // skill sorted
1675 shuffle.shrink(0);
1676 sums /= 4 * numplayers + 2;
1677 team = rnd(2);
1678 loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isonrightmap && !team_isspect(clients[i]->team))
1679 {
1680 clients[i]->at3_score += rnd(sums | 1);
1681 shuffle.add(i);
1682 }
1683 shuffle.sort(cmpscore);
1684 loopi(shuffle.length())
1685 {
1686 updateclientteam(shuffle[i], team, ftr);
1687 team = !team;
1688 }
1689 }
1690 }
1691
balanceteams(int ftr)1692 bool balanceteams(int ftr) // pro vs noobs never more
1693 {
1694 if(mastermode != MM_OPEN || totalclients < 3 ) return true;
1695 int tsize[2] = {0, 0}, tscore[2] = {0, 0};
1696 int totalscore = 0, nplayers = 0;
1697 int flagmult = (m_ctf ? 50 : (m_htf ? 25 : 12));
1698
1699 loopv(clients) if(clients[i]->type!=ST_EMPTY)
1700 {
1701 client *c = clients[i];
1702 if(c->isauthed && team_isactive(c->team))
1703 {
1704 int time = servmillis - c->connectmillis + 5000;
1705 if ( time > gamemillis ) time = gamemillis + 5000;
1706 tsize[c->team]++;
1707 // effective score per minute, thanks to wtfthisgame for the nice idea
1708 // in a normal game, normal players will do 500 points in 10 minutes
1709 c->eff_score = c->state.points * 60 * 1000 / time + c->state.points / 6 + c->state.flagscore * flagmult;
1710 tscore[c->team] += c->eff_score;
1711 nplayers++;
1712 totalscore += c->state.points;
1713 }
1714 }
1715
1716 int h = 0, l = 1;
1717 if ( tscore[1] > tscore[0] ) { h = 1; l = 0; }
1718 if ( 2 * tscore[h] < 3 * tscore[l] || totalscore < nplayers * 100 ) return true;
1719 if ( tscore[h] > 3 * tscore[l] && tscore[h] > 150 * nplayers )
1720 {
1721 // sendf(-1, 1, "ri2", SV_SERVERMODE, sendservermode(false) | AT_SHUFFLE);
1722 shuffleteams();
1723 return true;
1724 }
1725
1726 float diffscore = tscore[h] - tscore[l];
1727
1728 int besth = 0, hid = -1;
1729 int bestdiff = 0, bestpair[2] = {-1, -1};
1730 if ( tsize[h] - tsize[l] > 0 ) // the h team has more players, so we will force only one player
1731 {
1732 loopv(clients) if( clients[i]->type!=ST_EMPTY )
1733 {
1734 client *c = clients[i]; // loop for h
1735 // client from the h team, not forced in this game, and without the flag
1736 if( c->isauthed && c->team == h && !c->state.forced && clienthasflag(i) < 0 )
1737 {
1738 // do not exchange in the way that weaker team becomes the stronger or the change is less than 20% effective
1739 if ( 2 * c->eff_score <= diffscore && 10 * c->eff_score >= diffscore && c->eff_score > besth )
1740 {
1741 besth = c->eff_score;
1742 hid = i;
1743 }
1744 }
1745 }
1746 if ( hid >= 0 )
1747 {
1748 updateclientteam(hid, l, ftr);
1749 clients[hid]->at3_lastforce = gamemillis;
1750 clients[hid]->state.forced = true;
1751 return true;
1752 }
1753 } else { // the h score team has less or the same player number, so, lets exchange
1754 loopv(clients) if(clients[i]->type!=ST_EMPTY)
1755 {
1756 client *c = clients[i]; // loop for h
1757 if( c->isauthed && c->team == h && !c->state.forced && clienthasflag(i) < 0 )
1758 {
1759 loopvj(clients) if(clients[j]->type!=ST_EMPTY && j != i )
1760 {
1761 client *cj = clients[j]; // loop for l
1762 if( cj->isauthed && cj->team == l && !cj->state.forced && clienthasflag(j) < 0 )
1763 {
1764 int pairdiff = 2 * (c->eff_score - cj->eff_score);
1765 if ( pairdiff <= diffscore && 5 * pairdiff >= diffscore && pairdiff > bestdiff )
1766 {
1767 bestdiff = pairdiff;
1768 bestpair[h] = i;
1769 bestpair[l] = j;
1770 }
1771 }
1772 }
1773 }
1774 }
1775 if ( bestpair[h] >= 0 && bestpair[l] >= 0 )
1776 {
1777 updateclientteam(bestpair[h], l, ftr);
1778 updateclientteam(bestpair[l], h, ftr);
1779 clients[bestpair[h]]->at3_lastforce = clients[bestpair[l]]->at3_lastforce = gamemillis;
1780 clients[bestpair[h]]->state.forced = clients[bestpair[l]]->state.forced = true;
1781 return true;
1782 }
1783 }
1784 return false;
1785 }
1786
1787 int lastbalance = 0, waitbalance = 2 * 60 * 1000;
1788
refillteams(bool now,int ftr)1789 bool refillteams(bool now, int ftr) // force only minimal amounts of players
1790 {
1791 if(mastermode == MM_MATCH) return false;
1792 static int lasttime_eventeams = 0;
1793 int teamsize[2] = {0, 0}, teamscore[2] = {0, 0}, moveable[2] = {0, 0};
1794 bool switched = false;
1795
1796 calcscores();
1797 loopv(clients) if(clients[i]->type!=ST_EMPTY) // playerlist stocktaking
1798 {
1799 client *c = clients[i];
1800 c->at3_dontmove = true;
1801 if(c->isauthed)
1802 {
1803 if(team_isactive(c->team)) // only active players count
1804 {
1805 teamsize[c->team]++;
1806 teamscore[c->team] += c->at3_score;
1807 if(clienthasflag(i) < 0)
1808 {
1809 c->at3_dontmove = false;
1810 moveable[c->team]++;
1811 }
1812 }
1813 }
1814 }
1815 int bigteam = teamsize[1] > teamsize[0];
1816 int allplayers = teamsize[0] + teamsize[1];
1817 int diffnum = teamsize[bigteam] - teamsize[!bigteam];
1818 int diffscore = teamscore[bigteam] - teamscore[!bigteam];
1819 if(lasttime_eventeams > gamemillis) lasttime_eventeams = 0;
1820 if(diffnum > 1)
1821 {
1822 if(now || gamemillis - lasttime_eventeams > 8000 + allplayers * 1000 || diffnum > 2 + allplayers / 10)
1823 {
1824 // time to even out teams
1825 loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->team != bigteam) clients[i]->at3_dontmove = true; // dont move small team players
1826 while(diffnum > 1 && moveable[bigteam] > 0)
1827 {
1828 // pick best fitting cn
1829 int pick = -1;
1830 int bestfit = 1000000000;
1831 int targetscore = diffscore / (diffnum & ~1);
1832 loopv(clients) if(clients[i]->type!=ST_EMPTY && !clients[i]->at3_dontmove) // try all still movable players
1833 {
1834 int fit = targetscore - clients[i]->at3_score;
1835 if(fit < 0 ) fit = -(fit * 15) / 10; // avoid too good players
1836 int forcedelay = clients[i]->at3_lastforce ? (1000 - (gamemillis - clients[i]->at3_lastforce) / (5 * 60)) : 0;
1837 if(forcedelay > 0) fit += (fit * forcedelay) / 600; // avoid lately forced players
1838 if(fit < bestfit + fit * rnd(100) / 400) // search 'almost' best fit
1839 {
1840 bestfit = fit;
1841 pick = i;
1842 }
1843 }
1844 if(pick < 0) break; // should really never happen
1845 // move picked player
1846 clients[pick]->at3_dontmove = true;
1847 moveable[bigteam]--;
1848 if(updateclientteam(pick, !bigteam, ftr))
1849 {
1850 diffnum -= 2;
1851 diffscore -= 2 * clients[pick]->at3_score;
1852 clients[pick]->at3_lastforce = gamemillis; // try not to force this player again for the next 5 minutes
1853 switched = true;
1854 }
1855 }
1856 }
1857 }
1858 if(diffnum < 2)
1859 {
1860 if ( ( gamemillis - lastbalance ) > waitbalance && ( gamelimit - gamemillis ) > 4*60*1000 )
1861 {
1862 if ( balanceteams (ftr) )
1863 {
1864 waitbalance = 2 * 60 * 1000 + gamemillis / 3;
1865 switched = true;
1866 }
1867 else waitbalance = 20 * 1000;
1868 lastbalance = gamemillis;
1869 }
1870 else if ( lastbalance > gamemillis )
1871 {
1872 lastbalance = 0;
1873 waitbalance = 2 * 60 * 1000;
1874 }
1875 lasttime_eventeams = gamemillis;
1876 }
1877 return switched;
1878 }
1879
resetserver(const char * newname,int newmode,int newtime)1880 void resetserver(const char *newname, int newmode, int newtime)
1881 {
1882 if(m_demo) enddemoplayback();
1883 else enddemorecord();
1884
1885 smode = newmode;
1886 copystring(smapname, newname);
1887
1888 minremain = newtime > 0 ? newtime : defaultgamelimit(newmode);
1889 gamemillis = 0;
1890 gamelimit = minremain*60000;
1891 arenaround = arenaroundstartmillis = 0;
1892 memset(&smapstats, 0, sizeof(smapstats));
1893
1894 interm = nextsendscore = 0;
1895 lastfillup = servmillis;
1896 sents.shrink(0);
1897 if(mastermode == MM_PRIVATE)
1898 {
1899 loopv(savedscores) savedscores[i].valid = false;
1900 }
1901 else savedscores.shrink(0);
1902 ctfreset();
1903
1904 nextmapname[0] = '\0';
1905 forceintermission = false;
1906 }
1907
startdemoplayback(const char * newname)1908 void startdemoplayback(const char *newname)
1909 {
1910 if(isdedicated) return;
1911 resetserver(newname, GMODE_DEMO, -1);
1912 setupdemoplayback();
1913 }
1914
startgame(const char * newname,int newmode,int newtime,bool notify)1915 void startgame(const char *newname, int newmode, int newtime, bool notify)
1916 {
1917 if(!newname || !*newname || (newmode == GMODE_DEMO && isdedicated)) fatal("startgame() abused");
1918 if(newmode == GMODE_DEMO)
1919 {
1920 startdemoplayback(newname);
1921 }
1922 else
1923 {
1924 bool lastteammode = m_teammode;
1925 resetserver(newname, newmode, newtime); // beware: may clear *newname
1926
1927 if(custom_servdesc && findcnbyaddress(&servdesc_caller) < 0)
1928 {
1929 updatesdesc(NULL);
1930 if(notify)
1931 {
1932 sendservmsg("server description reset to default");
1933 logline(ACLOG_INFO, "server description reset to '%s'", servdesc_current);
1934 }
1935 }
1936
1937 int maploc = MAP_VOID;
1938 mapstats *ms = getservermapstats(smapname, isdedicated, &maploc);
1939 mapbuffer.clear();
1940 if(isdedicated && distributablemap(maploc)) mapbuffer.load();
1941 if(ms)
1942 {
1943 smapstats = *ms;
1944 loopi(2)
1945 {
1946 sflaginfo &f = sflaginfos[i];
1947 if(smapstats.flags[i] == 1) // don't check flag positions, if there is more than one flag per team
1948 {
1949 short *fe = smapstats.entposs + smapstats.flagents[i] * 3;
1950 f.x = *fe;
1951 fe++;
1952 f.y = *fe;
1953 }
1954 else f.x = f.y = -1;
1955 }
1956 if (smapstats.flags[0] == 1 && smapstats.flags[1] == 1)
1957 {
1958 sflaginfo &f0 = sflaginfos[0], &f1 = sflaginfos[1];
1959 FlagFlag = pow2(f0.x - f1.x) + pow2(f0.y - f1.y);
1960 coverdist = FlagFlag > 6 * COVERDIST ? COVERDIST : FlagFlag / 6;
1961 }
1962 entity e;
1963 loopi(smapstats.hdr.numents)
1964 {
1965 e.type = smapstats.enttypes[i];
1966 e.transformtype(smode);
1967 server_entity se = { e.type, false, false, false, 0, smapstats.entposs[i * 3], smapstats.entposs[i * 3 + 1]};
1968 sents.add(se);
1969 if(e.fitsmode(smode)) sents[i].spawned = sents[i].legalpickup = true;
1970 }
1971 mapbuffer.setrevision();
1972 logline(ACLOG_INFO, "Map height density information for %s: H = %.2f V = %d, A = %d and MA = %d", smapname, Mheight, Mvolume, Marea, Mopen);
1973 items_blocked = false;
1974 }
1975 else if(isdedicated) sendservmsg("\f3server error: map not found - please start another map or send this map to the server");
1976 if(notify)
1977 {
1978 // change map
1979 sendf(-1, 1, "risiii", SV_MAPCHANGE, smapname, smode, mapbuffer.available(), mapbuffer.revision);
1980 if(smode>1 || (smode==0 && numnonlocalclients()>0)) sendf(-1, 1, "ri3", SV_TIMEUP, gamemillis, gamelimit);
1981 }
1982 packetbuf q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1983 send_item_list(q); // always send the item list when a game starts
1984 sendpacket(-1, 1, q.finalize());
1985 defformatstring(gsmsg)("Game start: %s on %s, %d players, %d minutes, mastermode %d, ", modestr(smode), smapname, numclients(), minremain, mastermode);
1986 if(mastermode == MM_MATCH) concatformatstring(gsmsg, "teamsize %d, ", matchteamsize);
1987 if(ms) concatformatstring(gsmsg, "(map rev %d/%d, %s, 'getmap' %sprepared)", smapstats.hdr.maprevision, smapstats.cgzsize, maplocstr[maploc], mapbuffer.available() ? "" : "not ");
1988 else concatformatstring(gsmsg, "error: failed to preload map (map: %s)", maplocstr[maploc]);
1989 logline(ACLOG_INFO, "\n%s", gsmsg);
1990 if(m_arena) distributespawns();
1991 if(notify)
1992 {
1993 // shuffle if previous mode wasn't a team-mode
1994 if(m_teammode)
1995 {
1996 if(!lastteammode)
1997 shuffleteams(FTR_INFO);
1998 else if(autoteam)
1999 refillteams(true, FTR_INFO);
2000 }
2001 // prepare spawns; players will spawn, once they've loaded the correct map
2002 loopv(clients) if(clients[i]->type!=ST_EMPTY)
2003 {
2004 client *c = clients[i];
2005 c->mapchange();
2006 forcedeath(c);
2007 }
2008 }
2009 if(numnonlocalclients() > 0) setupdemorecord();
2010 if(notify && m_ktf) sendflaginfo();
2011 if(notify) senddisconnectedscores(-1);
2012 }
2013 }
2014
2015 struct gbaninfo
2016 {
2017 enet_uint32 ip, mask;
2018 };
2019
2020 vector<gbaninfo> gbans;
2021
cleargbans()2022 void cleargbans()
2023 {
2024 gbans.shrink(0);
2025 }
2026
checkgban(uint ip)2027 bool checkgban(uint ip)
2028 {
2029 loopv(gbans) if((ip & gbans[i].mask) == gbans[i].ip) return true;
2030 return false;
2031 }
2032
addgban(const char * name)2033 void addgban(const char *name)
2034 {
2035 union { uchar b[sizeof(enet_uint32)]; enet_uint32 i; } ip, mask;
2036 ip.i = 0;
2037 mask.i = 0;
2038 loopi(4)
2039 {
2040 char *end = NULL;
2041 int n = strtol(name, &end, 10);
2042 if(!end) break;
2043 if(end > name) { ip.b[i] = n; mask.b[i] = 0xFF; }
2044 name = end;
2045 while(*name && *name++ != '.');
2046 }
2047 gbaninfo &ban = gbans.add();
2048 ban.ip = ip.i;
2049 ban.mask = mask.i;
2050
2051 loopvrev(clients)
2052 {
2053 client &c = *clients[i];
2054 if(c.type!=ST_TCPIP) continue;
2055 if(checkgban(c.peer->address.host)) disconnect_client(c.clientnum, DISC_BANREFUSE);
2056 }
2057 }
2058
addban(client * cl,int reason,int type)2059 inline void addban(client *cl, int reason, int type)
2060 {
2061 if(!cl) return;
2062 ban b = { cl->peer->address, servmillis+scl.ban_time, type };
2063 bans.add(b);
2064 disconnect_client(cl->clientnum, reason);
2065 }
2066
getbantype(int cn)2067 int getbantype(int cn)
2068 {
2069 if(!valid_client(cn)) return BAN_NONE;
2070 client &c = *clients[cn];
2071 if(c.type==ST_LOCAL) return BAN_NONE;
2072 if(checkgban(c.peer->address.host)) return BAN_MASTER;
2073 if(ipblacklist.check(c.peer->address.host)) return BAN_BLACKLIST;
2074 loopv(bans)
2075 {
2076 ban &b = bans[i];
2077 if(b.millis < servmillis) { bans.remove(i--); }
2078 if(b.address.host == c.peer->address.host) return b.type;
2079 }
2080 return BAN_NONE;
2081 }
2082
serveroperator()2083 int serveroperator()
2084 {
2085 loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->role > CR_DEFAULT) return i;
2086 return -1;
2087 }
2088
sendserveropinfo(int receiver)2089 void sendserveropinfo(int receiver)
2090 {
2091 int op = serveroperator();
2092 sendf(receiver, 1, "riii", SV_SERVOPINFO, op, op >= 0 ? clients[op]->role : -1);
2093 }
2094
2095 #include "serveractions.h"
2096
2097 struct voteinfo
2098 {
2099 int boot;
2100 int owner, callmillis, result, num1, num2, type;
2101 char text[MAXTRANS];
2102 serveraction *action;
2103 bool gonext;
2104 enet_uint32 host;
2105
voteinfovoteinfo2106 voteinfo() : boot(0), owner(0), callmillis(0), result(VOTE_NEUTRAL), action(NULL), gonext(false), host(0) {}
~voteinfovoteinfo2107 ~voteinfo() { delete action; }
2108
endvoteinfo2109 void end(int result)
2110 {
2111 if(action && !action->isvalid()) result = VOTE_NO; // don't perform() invalid votes
2112 sendf(-1, 1, "ri2", SV_VOTERESULT, result);
2113 this->result = result;
2114 if(result == VOTE_YES)
2115 {
2116 if(valid_client(owner)) clients[owner]->lastvotecall = 0;
2117 if(action) action->perform();
2118 }
2119 loopv(clients) clients[i]->vote = VOTE_NEUTRAL;
2120 }
2121
isvalidvoteinfo2122 bool isvalid() { return valid_client(owner) && action != NULL && action->isvalid(); }
isalivevoteinfo2123 bool isalive() { return servmillis - callmillis < 30*1000; }
2124
evaluatevoteinfo2125 void evaluate(bool forceend = false)
2126 {
2127 if(result!=VOTE_NEUTRAL) return; // block double action
2128 if(action && !action->isvalid()) end(VOTE_NO);
2129 int stats[VOTE_NUM] = {0};
2130 int adminvote = VOTE_NEUTRAL;
2131 loopv(clients)
2132 if(clients[i]->type!=ST_EMPTY /*&& clients[i]->connectmillis < callmillis*/) // new connected people will vote now
2133 {
2134 stats[clients[i]->vote]++;
2135 if(clients[i]->role==CR_ADMIN) adminvote = clients[i]->vote;
2136 };
2137
2138 bool admin = clients[owner]->role==CR_ADMIN || (!isdedicated && clients[owner]->type==ST_LOCAL);
2139 int total = stats[VOTE_NO]+stats[VOTE_YES]+stats[VOTE_NEUTRAL];
2140 const float requiredcount = 0.51f;
2141 bool min_time = servmillis - callmillis > 10*1000;
2142 #define yes_condition ((min_time && stats[VOTE_YES] - stats[VOTE_NO] > 0.34f*total && totalclients > 4) || stats[VOTE_YES] > requiredcount*total)
2143 #define no_condition (forceend || !valid_client(owner) || stats[VOTE_NO] >= stats[VOTE_YES]+stats[VOTE_NEUTRAL] || adminvote == VOTE_NO)
2144 #define boot_condition (!boot || (boot && valid_client(num1) && clients[num1]->peer->address.host == host))
2145 if( (yes_condition || admin || adminvote == VOTE_YES) && boot_condition ) end(VOTE_YES);
2146 else if( no_condition || (min_time && !boot_condition)) end(VOTE_NO);
2147 else return;
2148 #undef boot_condition
2149 #undef no_condition
2150 #undef yes_condition
2151 }
2152 };
2153
2154 static voteinfo *curvote = NULL;
2155
svote(int sender,int vote,ENetPacket * msg)2156 bool svote(int sender, int vote, ENetPacket *msg) // true if the vote was placed successfully
2157 {
2158 if(!curvote || !valid_client(sender) || vote < VOTE_YES || vote > VOTE_NO) return false;
2159 if(clients[sender]->vote != VOTE_NEUTRAL)
2160 {
2161 sendf(sender, 1, "ri2", SV_CALLVOTEERR, VOTEE_MUL);
2162 return false;
2163 }
2164 else
2165 {
2166 sendpacket(-1, 1, msg, sender);
2167
2168 clients[sender]->vote = vote;
2169 logline(ACLOG_DEBUG,"[%s] client %s voted %s", clients[sender]->hostname, clients[sender]->name, vote == VOTE_NO ? "no" : "yes");
2170 curvote->evaluate();
2171 return true;
2172 }
2173 }
2174
scallvotesuc(voteinfo * v)2175 void scallvotesuc(voteinfo *v)
2176 {
2177 if(!v->isvalid()) return;
2178 DELETEP(curvote);
2179 curvote = v;
2180 clients[v->owner]->lastvotecall = servmillis;
2181 clients[v->owner]->nvotes--; // successful votes do not count as abuse
2182 sendf(v->owner, 1, "ri", SV_CALLVOTESUC);
2183 logline(ACLOG_INFO, "[%s] client %s called a vote: %s", clients[v->owner]->hostname, clients[v->owner]->name, v->action && v->action->desc ? v->action->desc : "[unknown]");
2184 }
2185
scallvoteerr(voteinfo * v,int error)2186 void scallvoteerr(voteinfo *v, int error)
2187 {
2188 if(!valid_client(v->owner)) return;
2189 sendf(v->owner, 1, "ri2", SV_CALLVOTEERR, error);
2190 logline(ACLOG_INFO, "[%s] client %s failed to call a vote: %s (%s)", clients[v->owner]->hostname, clients[v->owner]->name, v->action && v->action->desc ? v->action->desc : "[unknown]", voteerrorstr(error));
2191 }
2192
2193 bool map_queued = false;
2194 void callvotepacket (int, voteinfo *);
2195
scallvote(voteinfo * v,ENetPacket * msg)2196 bool scallvote(voteinfo *v, ENetPacket *msg) // true if a regular vote was called
2197 {
2198 if (!v) return false;
2199 int area = isdedicated ? EE_DED_SERV : EE_LOCAL_SERV;
2200 int error = -1;
2201 client *c = clients[v->owner], *b = ( v->boot && valid_client(cn2boot) ? clients[cn2boot] : NULL );
2202 v->host = v->boot && b ? b->peer->address.host : 0;
2203
2204 int time = servmillis - c->lastvotecall;
2205 if ( c->nvotes > 0 && time > 4*60*1000 ) c->nvotes -= time/(4*60*1000);
2206 if ( c->nvotes < 0 || c->role == CR_ADMIN ) c->nvotes = 0;
2207 c->nvotes++;
2208
2209 if( !v || !v->isvalid() || (v->boot && (!b || cn2boot == v->owner) ) ) error = VOTEE_INVALID;
2210 else if( v->action->role > c->role ) error = VOTEE_PERMISSION;
2211 else if( !(area & v->action->area) ) error = VOTEE_AREA;
2212 else if( curvote && curvote->result==VOTE_NEUTRAL ) error = VOTEE_CUR;
2213 else if( v->type == SA_MAP && v->num1 >= GMODE_NUM && map_queued ) error = VOTEE_NEXT;
2214 else if( c->role == CR_DEFAULT && v->action->isdisabled() ) error = VOTEE_DISABLED;
2215 else if( (c->lastvotecall && servmillis - c->lastvotecall < 60*1000 && c->role != CR_ADMIN && numclients()>1) || c->nvotes > 3 ) error = VOTEE_MAX;
2216 else if( ( ( v->boot == 1 && c->role < roleconf('w') ) || ( v->boot == 2 && c->role < roleconf('X') ) )
2217 && !is_lagging(b) && !b->mute && b->spamcount < 2 )
2218 {
2219 /** not same team, with low ratio, not lagging, and not spamming... so, why to kick? */
2220 if ( !isteam(c->team, b->team) && b->state.frags < ( b->state.deaths > 0 ? b->state.deaths : 1 ) * 3 ) error = VOTEE_WEAK;
2221 /** same team, with low tk, not lagging, and not spamming... so, why to kick? */
2222 else if ( isteam(c->team, b->team) && b->state.teamkills < c->state.teamkills ) error = VOTEE_WEAK;
2223 }
2224
2225 if(error>=0)
2226 {
2227 scallvoteerr(v, error);
2228 return false;
2229 }
2230 else
2231 {
2232 if ( v->type == SA_MAP && v->num1 >= GMODE_NUM ) map_queued = true;
2233 if (!v->gonext) sendpacket(-1, 1, msg, v->owner); // FIXME in fact, all votes should go to the server, registered, and then go back to the clients
2234 else callvotepacket (-1, v); // also, no vote should exclude the caller... these would provide many code advantages/facilities
2235 scallvotesuc(v); // but we cannot change the vote system now for compatibility issues... so, TODO
2236 return true;
2237 }
2238 }
2239
callvotepacket(int cn,voteinfo * v=curvote)2240 void callvotepacket (int cn, voteinfo *v = curvote)
2241 { // FIXME, it would be far smart if the msg buffer from SV_CALLVOTE was simply saved
2242 int n_yes = 0, n_no = 0;
2243 loopv(clients) if(clients[i]->type!=ST_EMPTY)
2244 {
2245 if ( clients[i]->vote == VOTE_YES ) n_yes++;
2246 else if ( clients[i]->vote == VOTE_NO ) n_no++;
2247 }
2248
2249 packetbuf q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2250 putint(q, SV_CALLVOTE);
2251 putint(q, -1);
2252 putint(q, v->owner);
2253 putint(q, n_yes);
2254 putint(q, n_no);
2255 putint(q, v->type);
2256 switch(v->type)
2257 {
2258 case SA_KICK:
2259 case SA_BAN:
2260 putint(q, v->num1);
2261 sendstring(v->text, q);
2262 break;
2263 case SA_MAP:
2264 sendstring(v->text, q);
2265 putint(q, v->num1);
2266 putint(q, v->num2);
2267 break;
2268 case SA_SERVERDESC:
2269 sendstring(v->text, q);
2270 break;
2271 case SA_STOPDEMO:
2272 // compatibility
2273 break;
2274 case SA_REMBANS:
2275 case SA_SHUFFLETEAMS:
2276 break;
2277 case SA_FORCETEAM:
2278 putint(q, v->num1);
2279 putint(q, v->num2);
2280 break;
2281 default:
2282 putint(q, v->num1);
2283 break;
2284 }
2285 sendpacket(cn, 1, q.finalize());
2286 }
2287
2288 // TODO: use AUTH code
changeclientrole(int client,int role,char * pwd,bool force)2289 void changeclientrole(int client, int role, char *pwd, bool force)
2290 {
2291 pwddetail pd;
2292 if(!isdedicated || !valid_client(client)) return;
2293 pd.line = -1;
2294 if(force || role == CR_DEFAULT || (role == CR_ADMIN && pwd && pwd[0] && passwords.check(clients[client]->name, pwd, clients[client]->salt, &pd) && !pd.denyadmin))
2295 {
2296 if(role == clients[client]->role) return;
2297 if(role > CR_DEFAULT)
2298 {
2299 loopv(clients) clients[i]->role = CR_DEFAULT;
2300 }
2301 clients[client]->role = role;
2302 sendserveropinfo(-1);
2303 if(pd.line > -1)
2304 logline(ACLOG_INFO,"[%s] player %s used admin password in line %d", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", pd.line);
2305 logline(ACLOG_INFO,"[%s] set role of player %s to %s", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", role == CR_ADMIN ? "admin" : "normal player"); // flowtron : connecting players haven't got a name yet (connectadmin)
2306 if(role > CR_DEFAULT) sendiplist(client);
2307 }
2308 else if(pwd && pwd[0]) disconnect_client(client, DISC_SOPLOGINFAIL); // avoid brute-force
2309 if(curvote) curvote->evaluate();
2310 }
2311
senddisconnectedscores(int cn)2312 void senddisconnectedscores(int cn)
2313 {
2314 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2315 putint(p, SV_DISCSCORES);
2316 if(mastermode == MM_MATCH)
2317 {
2318 loopv(savedscores)
2319 {
2320 savedscore &sc = savedscores[i];
2321 if(sc.valid)
2322 {
2323 putint(p, sc.team);
2324 sendstring(sc.name, p);
2325 putint(p, sc.flagscore);
2326 putint(p, sc.frags);
2327 putint(p, sc.deaths);
2328 putint(p, sc.points);
2329 }
2330 }
2331 }
2332 putint(p, -1);
2333 sendpacket(cn, 1, p.finalize());
2334 }
2335
disc_reason(int reason)2336 const char *disc_reason(int reason)
2337 {
2338 static const char *disc_reasons[] = { "normal", "error - end of packet", "error - client num", "vote-kicked from the server", "vote-banned from the server", "error - tag type", "connection refused - you have been banned from this server", "incorrect password", "unsuccessful administrator login", "the server is FULL - try again later", "servers mastermode is \"private\" - wait until the servers mastermode is \"open\"", "auto-kick - your score dropped below the servers threshold", "auto-ban - your score dropped below the servers threshold", "duplicate connection", "inappropriate nickname", "error - packet flood", "auto-kick - excess spam detected", "auto-kick - inactivity detected", "auto-kick - team killing detected", "auto-kick - abnormal client behavior detected" };
2339 return reason >= 0 && (size_t)reason < sizeof(disc_reasons)/sizeof(disc_reasons[0]) ? disc_reasons[reason] : "unknown";
2340 }
2341
disconnect_client(int n,int reason)2342 void disconnect_client(int n, int reason)
2343 {
2344 if(!clients.inrange(n) || clients[n]->type!=ST_TCPIP) return;
2345 sdropflag(n);
2346 client &c = *clients[n];
2347 c.state.lastdisc = servmillis;
2348 const char *scoresaved = "";
2349 if(c.haswelcome)
2350 {
2351 savedscore *sc = findscore(c, true);
2352 if(sc)
2353 {
2354 sc->save(c.state, c.team);
2355 scoresaved = ", score saved";
2356 }
2357 }
2358 int sp = (servmillis - c.connectmillis) / 1000;
2359 if(reason>=0) logline(ACLOG_INFO, "[%s] disconnecting client %s (%s) cn %d, %d seconds played%s", c.hostname, c.name, disc_reason(reason), n, sp, scoresaved);
2360 else logline(ACLOG_INFO, "[%s] disconnected client %s cn %d, %d seconds played%s", c.hostname, c.name, n, sp, scoresaved);
2361 totalclients--;
2362 c.peer->data = (void *)-1;
2363 if(reason>=0) enet_peer_disconnect(c.peer, reason);
2364 clients[n]->zap();
2365 sendf(-1, 1, "rii", SV_CDIS, n);
2366 if(curvote) curvote->evaluate();
2367 if(*scoresaved && mastermode == MM_MATCH) senddisconnectedscores(-1);
2368 }
2369
2370 // for AUTH: WIP
2371
findauth(uint id)2372 client *findauth(uint id)
2373 {
2374 loopv(clients) if(clients[i]->authreq == id) return clients[i];
2375 return NULL;
2376 }
2377
authfailed(uint id)2378 void authfailed(uint id)
2379 {
2380 client *cl = findauth(id);
2381 if(!cl) return;
2382 cl->authreq = 0;
2383 }
2384
authsucceeded(uint id)2385 void authsucceeded(uint id)
2386 {
2387 client *cl = findauth(id);
2388 if(!cl) return;
2389 cl->authreq = 0;
2390 logline(ACLOG_INFO, "player authenticated: %s", cl->name);
2391 defformatstring(auth4u)("player authenticated: %s", cl->name);
2392 sendf(-1, 1, "ris", SV_SERVMSG, auth4u);
2393 //setmaster(cl, true, "", ci->authname);//TODO? compare to sauerbraten
2394 }
2395
authchallenged(uint id,const char * val)2396 void authchallenged(uint id, const char *val)
2397 {
2398 client *cl = findauth(id);
2399 if(!cl) return;
2400 sendf(cl->clientnum, 1, "risis", SV_AUTHCHAL, "", id, val);
2401 }
2402
2403 uint nextauthreq = 0;
2404
tryauth(client * cl,const char * user)2405 void tryauth(client *cl, const char *user)
2406 {
2407 extern bool requestmasterf(const char *fmt, ...);
2408 if(!nextauthreq) nextauthreq = 1;
2409 cl->authreq = nextauthreq++;
2410 filtertext(cl->authname, user, false, 100);
2411 if(!requestmasterf("reqauth %u %s\n", cl->authreq, cl->authname))
2412 {
2413 cl->authreq = 0;
2414 sendf(cl->clientnum, 1, "ris", SV_SERVMSG, "not connected to authentication server");
2415 }
2416 }
2417
answerchallenge(client * cl,uint id,char * val)2418 void answerchallenge(client *cl, uint id, char *val)
2419 {
2420 if(cl->authreq != id) return;
2421 extern bool requestmasterf(const char *fmt, ...);
2422 for(char *s = val; *s; s++)
2423 {
2424 if(!isxdigit(*s)) { *s = '\0'; break; }
2425 }
2426 if(!requestmasterf("confauth %u %s\n", id, val))
2427 {
2428 cl->authreq = 0;
2429 sendf(cl->clientnum, 1, "ris", SV_SERVMSG, "not connected to authentication server");
2430 }
2431 }
2432
2433 // :for AUTH
2434
sendiplist(int receiver,int cn)2435 void sendiplist(int receiver, int cn)
2436 {
2437 if(!valid_client(receiver)) return;
2438 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2439 putint(p, SV_IPLIST);
2440 loopv(clients) if(valid_client(i) && clients[i]->type == ST_TCPIP && i != receiver
2441 && (clients[i]->clientnum == cn || cn == -1))
2442 {
2443 putint(p, i);
2444 putint(p, isbigendian() ? endianswap(clients[i]->peer->address.host) : clients[i]->peer->address.host);
2445 }
2446 putint(p, -1);
2447 sendpacket(receiver, 1, p.finalize());
2448 }
2449
sendresume(client & c,bool broadcast)2450 void sendresume(client &c, bool broadcast)
2451 {
2452 sendf(broadcast ? -1 : c.clientnum, 1, "ri3i9ivvi", SV_RESUME,
2453 c.clientnum,
2454 c.state.state,
2455 c.state.lifesequence,
2456 c.state.primary,
2457 c.state.gunselect,
2458 c.state.flagscore,
2459 c.state.frags,
2460 c.state.deaths,
2461 c.state.health,
2462 c.state.armour,
2463 c.state.points,
2464 c.state.teamkills,
2465 NUMGUNS, c.state.ammo,
2466 NUMGUNS, c.state.mag,
2467 -1);
2468 }
2469
restorescore(client & c)2470 bool restorescore(client &c)
2471 {
2472 //if(ci->local) return false;
2473 savedscore *sc = findscore(c, false);
2474 if(sc && sc->valid)
2475 {
2476 sc->restore(c.state);
2477 sc->valid = false;
2478 if ( c.connectmillis - c.state.lastdisc < 5000 ) c.state.reconnections++;
2479 else if ( c.state.reconnections ) c.state.reconnections--;
2480 return true;
2481 }
2482 return false;
2483 }
2484
sendservinfo(client & c)2485 void sendservinfo(client &c)
2486 {
2487 sendf(c.clientnum, 1, "ri5", SV_SERVINFO, c.clientnum, isdedicated ? SERVER_PROTOCOL_VERSION : PROTOCOL_VERSION, c.salt, scl.serverpassword[0] ? 1 : 0);
2488 }
2489
putinitclient(client & c,packetbuf & p)2490 void putinitclient(client &c, packetbuf &p)
2491 {
2492 putint(p, SV_INITCLIENT);
2493 putint(p, c.clientnum);
2494 sendstring(c.name, p);
2495 putint(p, c.skin[TEAM_CLA]);
2496 putint(p, c.skin[TEAM_RVSF]);
2497 putint(p, c.team);
2498 enet_uint32 ip = 0;
2499 if(c.type == ST_TCPIP) ip = c.peer->address.host & 0xFFFFFF;
2500 putint(p, isbigendian() ? endianswap(ip) : ip);
2501 }
2502
sendinitclient(client & c)2503 void sendinitclient(client &c)
2504 {
2505 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2506 putinitclient(c, p);
2507 sendpacket(-1, 1, p.finalize(), c.clientnum);
2508 }
2509
welcomeinitclient(packetbuf & p,int exclude=-1)2510 void welcomeinitclient(packetbuf &p, int exclude = -1)
2511 {
2512 loopv(clients)
2513 {
2514 client &c = *clients[i];
2515 if(c.type!=ST_TCPIP || !c.isauthed || c.clientnum == exclude) continue;
2516 putinitclient(c, p);
2517 }
2518 }
2519
welcomepacket(packetbuf & p,int n)2520 void welcomepacket(packetbuf &p, int n)
2521 {
2522 if(!smapname[0]) maprot.next(false);
2523
2524 client *c = valid_client(n) ? clients[n] : NULL;
2525 int numcl = numclients();
2526
2527 putint(p, SV_WELCOME);
2528 putint(p, smapname[0] && !m_demo ? numcl : -1);
2529 if(smapname[0] && !m_demo)
2530 {
2531 putint(p, SV_MAPCHANGE);
2532 sendstring(smapname, p);
2533 putint(p, smode);
2534 putint(p, mapbuffer.available());
2535 putint(p, mapbuffer.revision);
2536 if(smode>1 || (smode==0 && numnonlocalclients()>0))
2537 {
2538 putint(p, SV_TIMEUP);
2539 putint(p, (gamemillis>=gamelimit || forceintermission) ? gamelimit : gamemillis);
2540 putint(p, gamelimit);
2541 //putint(p, minremain*60);
2542 }
2543 send_item_list(p); // this includes the flags
2544 }
2545 savedscore *sc = NULL;
2546 if(c)
2547 {
2548 if(c->type == ST_TCPIP && serveroperator() != -1) sendserveropinfo(n);
2549 c->team = mastermode == MM_MATCH && sc ? team_tospec(sc->team) : TEAM_SPECT;
2550 putint(p, SV_SETTEAM);
2551 putint(p, n);
2552 putint(p, c->team | (FTR_INFO << 4));
2553
2554 putint(p, SV_FORCEDEATH);
2555 putint(p, n);
2556 sendf(-1, 1, "ri2x", SV_FORCEDEATH, n, n);
2557 }
2558 if(!c || clients.length()>1)
2559 {
2560 putint(p, SV_RESUME);
2561 loopv(clients)
2562 {
2563 client &c = *clients[i];
2564 if(c.type!=ST_TCPIP || c.clientnum==n) continue;
2565 putint(p, c.clientnum);
2566 putint(p, c.state.state);
2567 putint(p, c.state.lifesequence);
2568 putint(p, c.state.primary);
2569 putint(p, c.state.gunselect);
2570 putint(p, c.state.flagscore);
2571 putint(p, c.state.frags);
2572 putint(p, c.state.deaths);
2573 putint(p, c.state.health);
2574 putint(p, c.state.armour);
2575 putint(p, c.state.points);
2576 putint(p, c.state.teamkills);
2577 loopi(NUMGUNS) putint(p, c.state.ammo[i]);
2578 loopi(NUMGUNS) putint(p, c.state.mag[i]);
2579 }
2580 putint(p, -1);
2581 welcomeinitclient(p, n);
2582 }
2583 putint(p, SV_SERVERMODE);
2584 putint(p, sendservermode(false));
2585 const char *motd = scl.motd[0] ? scl.motd : infofiles.getmotd(c ? c->lang : "");
2586 if(motd)
2587 {
2588 putint(p, SV_TEXT);
2589 sendstring(motd, p);
2590 }
2591 }
2592
sendwelcome(client * cl,int chan)2593 void sendwelcome(client *cl, int chan)
2594 {
2595 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2596 welcomepacket(p, cl->clientnum);
2597 sendpacket(cl->clientnum, chan, p.finalize());
2598 cl->haswelcome = true;
2599 }
2600
forcedeath(client * cl)2601 void forcedeath(client *cl)
2602 {
2603 sdropflag(cl->clientnum);
2604 cl->state.state = CS_DEAD;
2605 cl->state.respawn();
2606 sendf(-1, 1, "rii", SV_FORCEDEATH, cl->clientnum);
2607 }
2608
checktype(int type,client * cl)2609 int checktype(int type, client *cl)
2610 {
2611 if(cl && cl->type==ST_LOCAL) return type;
2612 if(type < 0 || type >= SV_NUM) return -1;
2613 // server only messages
2614 static int servtypes[] = { SV_SERVINFO, SV_WELCOME, SV_INITCLIENT, SV_POSN, SV_CDIS, SV_GIBDIED, SV_DIED,
2615 SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_AUTHREQ, SV_AUTHCHAL,
2616 SV_SPAWNSTATE, SV_SPAWNDENY, SV_FORCEDEATH, SV_RESUME,
2617 SV_DISCSCORES, SV_TIMEUP, SV_ITEMACC, SV_MAPCHANGE, SV_ITEMSPAWN, SV_PONG,
2618 SV_SERVMSG, SV_ITEMLIST, SV_FLAGINFO, SV_FLAGMSG, SV_FLAGCNT,
2619 SV_ARENAWIN, SV_SERVOPINFO,
2620 SV_CALLVOTESUC, SV_CALLVOTEERR, SV_VOTERESULT,
2621 SV_SETTEAM, SV_TEAMDENY, SV_SERVERMODE, SV_IPLIST,
2622 SV_SENDDEMOLIST, SV_SENDDEMO, SV_DEMOPLAYBACK,
2623 SV_CLIENT, SV_HUDEXTRAS, SV_POINTS };
2624 // only allow edit messages in coop-edit mode
2625 static int edittypes[] = { SV_EDITENT, SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, SV_NEWMAP };
2626 if(cl)
2627 {
2628 loopi(sizeof(servtypes)/sizeof(int)) if(type == servtypes[i]) return -1;
2629 loopi(sizeof(edittypes)/sizeof(int)) if(type == edittypes[i]) return smode==GMODE_COOPEDIT ? type : -1;
2630 if(++cl->overflow >= 200) return -2;
2631 }
2632 return type;
2633 }
2634
2635 // server side processing of updates: does very little and most state is tracked client only
2636 // could be extended to move more gameplay to server (at expense of lag)
2637
process(ENetPacket * packet,int sender,int chan)2638 void process(ENetPacket *packet, int sender, int chan)
2639 {
2640 ucharbuf p(packet->data, packet->dataLength);
2641 char text[MAXTRANS];
2642 client *cl = sender>=0 ? clients[sender] : NULL;
2643 pwddetail pd;
2644 int type;
2645
2646 if(cl && !cl->isauthed)
2647 {
2648 int clientrole = CR_DEFAULT;
2649
2650 if(chan==0) return;
2651 else if(chan!=1 || getint(p)!=SV_CONNECT) disconnect_client(sender, DISC_TAGT);
2652 else
2653 {
2654 cl->acversion = getint(p);
2655 cl->acbuildtype = getint(p);
2656 defformatstring(tags)(", AC: %d|%x", cl->acversion, cl->acbuildtype);
2657 getstring(text, p);
2658 filtertext(text, text, 0, MAXNAMELEN);
2659 if(!text[0]) copystring(text, "unarmed");
2660 copystring(cl->name, text, MAXNAMELEN+1);
2661 getstring(text, p);
2662 copystring(cl->pwd, text);
2663 getstring(text, p);
2664 filterlang(cl->lang, text);
2665 int wantrole = getint(p);
2666 cl->state.nextprimary = getint(p);
2667 loopi(2) cl->skin[i] = getint(p);
2668 int bantype = getbantype(sender);
2669 bool banned = bantype > BAN_NONE;
2670 bool srvfull = numnonlocalclients() > scl.maxclients;
2671 bool srvprivate = mastermode == MM_PRIVATE || mastermode == MM_MATCH;
2672 bool matchreconnect = mastermode == MM_MATCH && findscore(*cl, false);
2673 int bl = 0, wl = nickblacklist.checkwhitelist(*cl);
2674 if(wl == NWL_PASS) concatstring(tags, ", nickname whitelist match");
2675 if(wl == NWL_UNLISTED) bl = nickblacklist.checkblacklist(cl->name);
2676 if(matchreconnect && !banned)
2677 { // former player reconnecting to a server in match mode
2678 cl->isauthed = true;
2679 logline(ACLOG_INFO, "[%s] %s logged in (reconnect to match)%s", cl->hostname, cl->name, tags);
2680 }
2681 else if(wl == NWL_IPFAIL || wl == NWL_PWDFAIL)
2682 { // nickname matches whitelist, but IP is not in the required range or PWD doesn't match
2683 logline(ACLOG_INFO, "[%s] '%s' matches nickname whitelist: wrong %s%s", cl->hostname, cl->name, wl == NWL_IPFAIL ? "IP" : "PWD", tags);
2684 disconnect_client(sender, DISC_BADNICK);
2685 }
2686 else if(bl > 0)
2687 { // nickname matches blacklist
2688 logline(ACLOG_INFO, "[%s] '%s' matches nickname blacklist line %d%s", cl->hostname, cl->name, bl, tags);
2689 disconnect_client(sender, DISC_BADNICK);
2690 }
2691 else if(passwords.check(cl->name, cl->pwd, cl->salt, &pd, (cl->type==ST_TCPIP ? cl->peer->address.host : 0)) && (!pd.denyadmin || (banned && !srvfull && !srvprivate)) && bantype != BAN_MASTER) // pass admins always through
2692 { // admin (or deban) password match
2693 cl->isauthed = true;
2694 if(!pd.denyadmin && wantrole == CR_ADMIN) clientrole = CR_ADMIN;
2695 if(bantype == BAN_VOTE)
2696 {
2697 loopv(bans) if(bans[i].address.host == cl->peer->address.host) { bans.remove(i); concatstring(tags, ", ban removed"); break; } // remove admin bans
2698 }
2699 if(srvfull)
2700 {
2701 loopv(clients) if(i != sender && clients[i]->type==ST_TCPIP)
2702 {
2703 disconnect_client(i, DISC_MAXCLIENTS); // disconnect someone else to fit maxclients again
2704 break;
2705 }
2706 }
2707 logline(ACLOG_INFO, "[%s] %s logged in using the admin password in line %d%s", cl->hostname, cl->name, pd.line, tags);
2708 }
2709 else if(scl.serverpassword[0] && !(srvprivate || srvfull || banned))
2710 { // server password required
2711 if(!strcmp(genpwdhash(cl->name, scl.serverpassword, cl->salt), cl->pwd))
2712 {
2713 cl->isauthed = true;
2714 logline(ACLOG_INFO, "[%s] %s client logged in (using serverpassword)%s", cl->hostname, cl->name, tags);
2715 }
2716 else disconnect_client(sender, DISC_WRONGPW);
2717 }
2718 else if(srvprivate) disconnect_client(sender, DISC_MASTERMODE);
2719 else if(srvfull) disconnect_client(sender, DISC_MAXCLIENTS);
2720 else if(banned) disconnect_client(sender, DISC_BANREFUSE);
2721 else
2722 {
2723 cl->isauthed = true;
2724 logline(ACLOG_INFO, "[%s] %s logged in (default)%s", cl->hostname, cl->name, tags);
2725 }
2726 }
2727 if(!cl->isauthed) return;
2728
2729 if(cl->type==ST_TCPIP)
2730 {
2731 loopv(clients) if(i != sender)
2732 {
2733 client *dup = clients[i];
2734 if(dup->type==ST_TCPIP && dup->peer->address.host==cl->peer->address.host && dup->peer->address.port==cl->peer->address.port)
2735 disconnect_client(i, DISC_DUP);
2736 }
2737 }
2738
2739 sendwelcome(cl);
2740 if(restorescore(*cl)) { sendresume(*cl, true); senddisconnectedscores(-1); }
2741 else if(cl->type==ST_TCPIP) senddisconnectedscores(sender);
2742 sendinitclient(*cl);
2743 if(clientrole != CR_DEFAULT) changeclientrole(sender, clientrole, NULL, true);
2744 if( curvote && curvote->result == VOTE_NEUTRAL ) callvotepacket (cl->clientnum);
2745
2746 // send full IPs to admins
2747 loopv(clients)
2748 {
2749 if(clients[i] && clients[i]->clientnum != cl->clientnum && (clients[i]->role == CR_ADMIN || clients[i]->type == ST_LOCAL))
2750 sendiplist(clients[i]->clientnum, cl->clientnum);
2751 }
2752 }
2753
2754 if(!cl) { logline(ACLOG_ERROR, "<NULL> client in process()"); return; } // should never happen anyway
2755
2756 if(packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true;
2757
2758 #define QUEUE_MSG { if(cl->type==ST_TCPIP) while(curmsg<p.length()) cl->messages.add(p.buf[curmsg++]); }
2759 #define QUEUE_BUF(body) \
2760 { \
2761 if(cl->type==ST_TCPIP) \
2762 { \
2763 curmsg = p.length(); \
2764 { body; } \
2765 } \
2766 }
2767 #define QUEUE_INT(n) QUEUE_BUF(putint(cl->messages, n))
2768 #define QUEUE_UINT(n) QUEUE_BUF(putuint(cl->messages, n))
2769 #define QUEUE_STR(text) QUEUE_BUF(sendstring(text, cl->messages))
2770 #define MSG_PACKET(packet) \
2771 packetbuf buf(16 + p.length() - curmsg, ENET_PACKET_FLAG_RELIABLE); \
2772 putint(buf, SV_CLIENT); \
2773 putint(buf, cl->clientnum); \
2774 putuint(buf, p.length() - curmsg); \
2775 buf.put(&p.buf[curmsg], p.length() - curmsg); \
2776 ENetPacket *packet = buf.finalize();
2777
2778 int curmsg;
2779 while((curmsg = p.length()) < p.maxlen)
2780 {
2781 type = checktype(getint(p), cl);
2782
2783 #ifdef _DEBUG
2784 if(type!=SV_POS && type!=SV_POSC && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_CLIENT)
2785 {
2786 DEBUGVAR(cl->name);
2787 ASSERT(type>=0 && type<SV_NUM);
2788 DEBUGVAR(messagenames[type]);
2789 protocoldebug(true);
2790 }
2791 else protocoldebug(false);
2792 #endif
2793
2794 type = checkmessage(cl,type);
2795 switch(type)
2796 {
2797 case SV_TEAMTEXTME:
2798 case SV_TEAMTEXT:
2799 getstring(text, p);
2800 filtertext(text, text);
2801 trimtrailingwhitespace(text);
2802 if(*text)
2803 {
2804 bool canspeech = forbiddenlist.canspeech(text);
2805 if(!spamdetect(cl, text) && canspeech) // team chat
2806 {
2807 logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s'", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text);
2808 sendteamtext(text, sender, type);
2809 }
2810 else
2811 {
2812 logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s', %s", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "",
2813 cl->name, team_string(cl->team), text, canspeech ? "SPAM detected" : "Forbidden speech");
2814 if (canspeech)
2815 {
2816 sendservmsg("\f3Please do not spam; your message was not delivered.", sender);
2817 if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_ABUSE);
2818 }
2819 else
2820 {
2821 sendservmsg("\f3Watch your language! Your message was not delivered.", sender);
2822 kick_abuser(cl->clientnum, cl->badmillis, cl->badspeech, 3);
2823 }
2824 }
2825 }
2826 break;
2827
2828 case SV_TEXTME:
2829 case SV_TEXT:
2830 {
2831 int mid1 = curmsg, mid2 = p.length();
2832 getstring(text, p);
2833 filtertext(text, text);
2834 trimtrailingwhitespace(text);
2835 if(*text)
2836 {
2837 bool canspeech = forbiddenlist.canspeech(text);
2838 if(!spamdetect(cl, text) && canspeech)
2839 {
2840 if(mastermode != MM_MATCH || !matchteamsize || team_isactive(cl->team) || (cl->team == TEAM_SPECT && cl->role == CR_ADMIN)) // common chat
2841 {
2842 logline(ACLOG_INFO, "[%s] %s%s says: '%s'", cl->hostname, type == SV_TEXTME ? "(me) " : "", cl->name, text);
2843 if(cl->type==ST_TCPIP) while(mid1<mid2) cl->messages.add(p.buf[mid1++]);
2844 QUEUE_STR(text);
2845 }
2846 else // spect chat
2847 {
2848 logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s'", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text);
2849 sendteamtext(text, sender, type == SV_TEXTME ? SV_TEAMTEXTME : SV_TEAMTEXT);
2850 }
2851 }
2852 else
2853 {
2854 logline(ACLOG_INFO, "[%s] %s%s says: '%s', %s", cl->hostname, type == SV_TEXTME ? "(me) " : "",
2855 cl->name, text, canspeech ? "SPAM detected" : "Forbidden speech");
2856 if (canspeech)
2857 {
2858 sendservmsg("\f3Please do not spam; your message was not delivered.", sender);
2859 if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_ABUSE);
2860 }
2861 else
2862 {
2863 sendservmsg("\f3Watch your language! Your message was not delivered.", sender);
2864 kick_abuser(cl->clientnum, cl->badmillis, cl->badspeech, 3);
2865 }
2866 }
2867 }
2868 break;
2869 }
2870
2871 case SV_TEXTPRIVATE:
2872 {
2873 int targ = getint(p);
2874 getstring(text, p);
2875 filtertext(text, text);
2876 trimtrailingwhitespace(text);
2877
2878 if(!valid_client(targ)) break;
2879 client *target = clients[targ];
2880
2881 if(*text)
2882 {
2883 bool canspeech = forbiddenlist.canspeech(text);
2884 if(!spamdetect(cl, text) && canspeech)
2885 {
2886 bool allowed = !(mastermode == MM_MATCH && cl->team != target->team) && cl->role >= roleconf('t');
2887 logline(ACLOG_INFO, "[%s] %s says to %s: '%s' (%s)", cl->hostname, cl->name, target->name, text, allowed ? "allowed":"disallowed");
2888 if(allowed) sendf(target->clientnum, 1, "riis", SV_TEXTPRIVATE, cl->clientnum, text);
2889 }
2890 else
2891 {
2892 logline(ACLOG_INFO, "[%s] %s says to %s: '%s', %s", cl->hostname, cl->name, target->name, text, canspeech ? "SPAM detected" : "Forbidden speech");
2893 if (canspeech)
2894 {
2895 sendservmsg("\f3Please do not spam; your message was not delivered.", sender);
2896 if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_ABUSE);
2897 }
2898 else
2899 {
2900 sendservmsg("\f3Watch your language! Your message was not delivered.", sender);
2901 kick_abuser(cl->clientnum, cl->badmillis, cl->badspeech, 3);
2902 }
2903 }
2904 }
2905 }
2906 break;
2907
2908 case SV_VOICECOM:
2909 case SV_VOICECOMTEAM:
2910 {
2911 int s = getint(p);
2912 /* spam filter */
2913 if ( servmillis > cl->mute ) // client is not muted
2914 {
2915 if( s < S_AFFIRMATIVE || s >= S_NULL ) cl->mute = servmillis + 10000; // vc is invalid
2916 else if ( cl->lastvc + 4000 < servmillis ) { if ( cl->spam > 0 ) cl->spam -= (servmillis - cl->lastvc) / 4000; } // no vc in the last 4 seconds
2917 else cl->spam++; // the guy is spamming
2918 if ( cl->spam < 0 ) cl->spam = 0;
2919 cl->lastvc = servmillis; // register
2920 if ( cl->spam > 4 ) { cl->mute = servmillis + 10000; break; } // 5 vcs in less than 20 seconds... shut up please
2921 if ( m_teammode ) checkteamplay(s,sender); // finally here we check the teamplay comm
2922 if ( type == SV_VOICECOM ) { QUEUE_MSG; }
2923 else sendvoicecomteam(s, sender);
2924 }
2925 }
2926 break;
2927
2928 case SV_MAPIDENT:
2929 {
2930 int gzs = getint(p);
2931 int rev = getint(p);
2932 if(!isdedicated || (smapstats.cgzsize == gzs && smapstats.hdr.maprevision == rev))
2933 { // here any game really starts for a client: spawn, if it's a new game - don't spawn if the game was already running
2934 cl->isonrightmap = true;
2935 int sp = canspawn(cl);
2936 sendf(sender, 1, "rii", SV_SPAWNDENY, sp);
2937 cl->spawnperm = sp;
2938 if(cl->loggedwrongmap) logline(ACLOG_INFO, "[%s] %s is now on the right map: revision %d/%d", cl->hostname, cl->name, rev, gzs);
2939 bool spawn = false;
2940 if(team_isspect(cl->team))
2941 {
2942 if(numclients() < 2 && !m_demo && mastermode != MM_MATCH) // spawn on empty servers
2943 {
2944 spawn = updateclientteam(cl->clientnum, TEAM_ANYACTIVE, FTR_INFO);
2945 }
2946 }
2947 else
2948 {
2949 if((cl->freshgame || numclients() < 2) && !m_demo) spawn = true;
2950 }
2951 cl->freshgame = false;
2952 if(spawn) sendspawn(cl);
2953
2954 }
2955 else
2956 {
2957 forcedeath(cl);
2958 logline(ACLOG_INFO, "[%s] %s is on the wrong map: revision %d/%d", cl->hostname, cl->name, rev, gzs);
2959 cl->loggedwrongmap = true;
2960 sendf(sender, 1, "rii", SV_SPAWNDENY, SP_WRONGMAP);
2961 }
2962 QUEUE_MSG;
2963 break;
2964 }
2965
2966 case SV_ITEMPICKUP:
2967 {
2968 int n = getint(p);
2969 if(!arenaround || arenaround - gamemillis > 2000)
2970 {
2971 gameevent &pickup = cl->addevent();
2972 pickup.type = GE_PICKUP;
2973 pickup.pickup.ent = n;
2974 }
2975 break;
2976 }
2977
2978 case SV_WEAPCHANGE:
2979 {
2980 int gunselect = getint(p);
2981 if(gunselect<0 || gunselect>=NUMGUNS || gunselect == GUN_CPISTOL) break;
2982 if(!m_demo && !m_coop) checkweapon(type,gunselect);
2983 cl->state.gunselect = gunselect;
2984 QUEUE_MSG;
2985 break;
2986 }
2987
2988 case SV_PRIMARYWEAP:
2989 {
2990 int nextprimary = getint(p);
2991 if(nextprimary<0 && nextprimary>=NUMGUNS) break;
2992 cl->state.nextprimary = nextprimary;
2993 break;
2994 }
2995
2996 case SV_SWITCHNAME:
2997 {
2998 QUEUE_MSG;
2999 getstring(text, p);
3000 filtertext(text, text, 0, MAXNAMELEN);
3001 if(!text[0]) copystring(text, "unarmed");
3002 QUEUE_STR(text);
3003 bool namechanged = strcmp(cl->name, text) != 0;
3004 if(namechanged) logline(ACLOG_INFO,"[%s] %s changed name to %s", cl->hostname, cl->name, text);
3005 copystring(cl->name, text, MAXNAMELEN+1);
3006 if(namechanged)
3007 {
3008 // very simple spam detection (possible FIXME: centralize spam detection)
3009 if(servmillis - cl->lastprofileupdate < 1000)
3010 {
3011 ++cl->fastprofileupdates;
3012 if(cl->fastprofileupdates == 3) sendservmsg("\f3Please do not spam");
3013 if(cl->fastprofileupdates >= 5) { disconnect_client(sender, DISC_ABUSE); break; }
3014 }
3015 else if(servmillis - cl->lastprofileupdate > 10000) cl->fastprofileupdates = 0;
3016 cl->lastprofileupdate = servmillis;
3017
3018 switch(nickblacklist.checkwhitelist(*cl))
3019 {
3020 case NWL_PWDFAIL:
3021 case NWL_IPFAIL:
3022 logline(ACLOG_INFO, "[%s] '%s' matches nickname whitelist: wrong IP/PWD", cl->hostname, cl->name);
3023 disconnect_client(sender, DISC_BADNICK);
3024 break;
3025
3026 case NWL_UNLISTED:
3027 {
3028 int l = nickblacklist.checkblacklist(cl->name);
3029 if(l >= 0)
3030 {
3031 logline(ACLOG_INFO, "[%s] '%s' matches nickname blacklist line %d", cl->hostname, cl->name, l);
3032 disconnect_client(sender, DISC_BADNICK);
3033 }
3034 break;
3035 }
3036 }
3037 }
3038 break;
3039 }
3040
3041 case SV_SWITCHTEAM:
3042 {
3043 int t = getint(p);
3044 if(!updateclientteam(sender, team_isvalid(t) ? t : TEAM_SPECT, FTR_PLAYERWISH)) sendf(sender, 1, "rii", SV_TEAMDENY, t);
3045 break;
3046 }
3047
3048 case SV_SWITCHSKIN:
3049 {
3050 loopi(2) cl->skin[i] = getint(p);
3051 QUEUE_MSG;
3052
3053 if(servmillis - cl->lastprofileupdate < 1000)
3054 {
3055 ++cl->fastprofileupdates;
3056 if(cl->fastprofileupdates == 3) sendservmsg("\f3Please do not spam");
3057 if(cl->fastprofileupdates >= 5) disconnect_client(sender, DISC_ABUSE);
3058 }
3059 else if(servmillis - cl->lastprofileupdate > 10000) cl->fastprofileupdates = 0;
3060 cl->lastprofileupdate = servmillis;
3061 break;
3062 }
3063
3064 case SV_TRYSPAWN:
3065 {
3066 int sp = canspawn(cl);
3067 if(team_isspect(cl->team) && sp < SP_OK_NUM)
3068 {
3069 updateclientteam(sender, TEAM_ANYACTIVE, FTR_PLAYERWISH);
3070 sp = canspawn(cl);
3071 }
3072 if( !m_arena && sp < SP_OK_NUM && gamemillis > cl->state.lastspawn + 1000 ) sendspawn(cl);
3073 break;
3074 }
3075
3076 case SV_SPAWN:
3077 {
3078 int ls = getint(p), gunselect = getint(p);
3079 if((cl->state.state!=CS_ALIVE && cl->state.state!=CS_DEAD && cl->state.state!=CS_SPECTATE) ||
3080 ls!=cl->state.lifesequence || cl->state.lastspawn<0 || gunselect<0 || gunselect>=NUMGUNS || gunselect == GUN_CPISTOL) break;
3081 cl->state.lastspawn = -1;
3082 cl->state.spawn = gamemillis;
3083 cl->upspawnp = false;
3084 cl->state.state = CS_ALIVE;
3085 cl->state.gunselect = gunselect;
3086 QUEUE_BUF(
3087 {
3088 putint(cl->messages, SV_SPAWN);
3089 putint(cl->messages, cl->state.lifesequence);
3090 putint(cl->messages, cl->state.health);
3091 putint(cl->messages, cl->state.armour);
3092 putint(cl->messages, cl->state.gunselect);
3093 loopi(NUMGUNS) putint(cl->messages, cl->state.ammo[i]);
3094 loopi(NUMGUNS) putint(cl->messages, cl->state.mag[i]);
3095 });
3096 break;
3097 }
3098
3099 case SV_SUICIDE:
3100 {
3101 gameevent &suicide = cl->addevent();
3102 suicide.type = GE_SUICIDE;
3103 break;
3104 }
3105
3106 case SV_SHOOT:
3107 {
3108 gameevent &shot = cl->addevent();
3109 shot.type = GE_SHOT;
3110 #define seteventmillis(event) \
3111 { \
3112 event.id = getint(p); \
3113 if(!cl->timesync || (cl->events.length()==1 && cl->state.waitexpired(gamemillis))) \
3114 { \
3115 cl->timesync = true; \
3116 cl->gameoffset = gamemillis - event.id; \
3117 event.millis = gamemillis; \
3118 } \
3119 else event.millis = cl->gameoffset + event.id; \
3120 }
3121 seteventmillis(shot.shot);
3122 shot.shot.gun = getint(p);
3123 loopk(3) { shot.shot.from[k] = cl->state.o.v[k] + ( k == 2 ? (((cl->f>>7)&1)?2.2f:4.2f) : 0); }
3124 loopk(3) { float v = getint(p)/DMF; shot.shot.to[k] = ((k<2 && v<0.0f)?0.0f:v); }
3125 int hits = getint(p);
3126 int tcn = -1;
3127 loopk(hits)
3128 {
3129 gameevent &hit = cl->addevent();
3130 hit.type = GE_HIT;
3131 tcn = hit.hit.target = getint(p);
3132 hit.hit.lifesequence = getint(p);
3133 hit.hit.info = getint(p);
3134 loopk(3) hit.hit.dir[k] = getint(p)/DNF;
3135 }
3136 if(!m_demo && !m_coop) checkshoot(sender, shot, hits, tcn);
3137 break;
3138 }
3139
3140 case SV_EXPLODE: // Brahma says: FIXME handle explosion by location and deal damage from server
3141 {
3142 gameevent &exp = cl->addevent();
3143 exp.type = GE_EXPLODE;
3144 seteventmillis(exp.explode);
3145 exp.explode.gun = getint(p);
3146 exp.explode.id = getint(p);
3147 int hits = getint(p);
3148 loopk(hits)
3149 {
3150 gameevent &hit = cl->addevent();
3151 hit.type = GE_HIT;
3152 hit.hit.target = getint(p);
3153 hit.hit.lifesequence = getint(p);
3154 hit.hit.dist = getint(p)/DMF;
3155 loopk(3) hit.hit.dir[k] = getint(p)/DNF;
3156 }
3157 break;
3158 }
3159
3160 case SV_AKIMBO:
3161 {
3162 gameevent &akimbo = cl->addevent();
3163 akimbo.type = GE_AKIMBO;
3164 seteventmillis(akimbo.akimbo);
3165 break;
3166 }
3167
3168 case SV_RELOAD:
3169 {
3170 gameevent &reload = cl->addevent();
3171 reload.type = GE_RELOAD;
3172 seteventmillis(reload.reload);
3173 reload.reload.gun = getint(p);
3174 break;
3175 }
3176
3177 // for AUTH:
3178
3179 case SV_AUTHTRY:
3180 {
3181 string desc, name;
3182 getstring(desc, p, sizeof(desc)); // unused for now
3183 getstring(name, p, sizeof(name));
3184 if(!desc[0]) tryauth(cl, name);
3185 break;
3186 }
3187
3188 case SV_AUTHANS:
3189 {
3190 string desc, ans;
3191 getstring(desc, p, sizeof(desc)); // unused for now
3192 uint id = (uint)getint(p);
3193 getstring(ans, p, sizeof(ans));
3194 if(!desc[0]) answerchallenge(cl, id, ans);
3195 break;
3196 }
3197
3198
3199 case SV_AUTHT:
3200 {
3201 /* int n = getint(p);
3202 loopi(n) getint(p);*/
3203 // if (cl) disconnect_client(cl->clientnum, DISC_TAGT); // remove this in the future, when auth is complete
3204 break;
3205 }
3206 // :for AUTH
3207
3208 case SV_PING:
3209 sendf(sender, 1, "ii", SV_PONG, getint(p));
3210 break;
3211
3212 case SV_CLIENTPING:
3213 {
3214 int ping = getint(p);
3215 if(cl) cl->ping = cl->ping == 9999 ? ping : (cl->ping * 4 + ping) / 5;
3216 QUEUE_MSG;
3217 break;
3218 }
3219
3220 case SV_POS:
3221 {
3222 int cn = getint(p);
3223 if(cn!=sender)
3224 {
3225 disconnect_client(sender, DISC_CN);
3226 #ifndef STANDALONE
3227 conoutf("ERROR: invalid client (msg %i)", type);
3228 #endif
3229 return;
3230 }
3231 loopi(3) cl->state.o[i] = getuint(p)/DMF;
3232 cl->y = getuint(p);
3233 cl->p = getint(p);
3234 cl->g = getuint(p);
3235 loopi(4) if ( (cl->g >> i) & 1 ) getint(p);
3236 cl->f = getuint(p);
3237 if(!cl->isonrightmap) break;
3238 if(cl->type==ST_TCPIP && (cl->state.state==CS_ALIVE || cl->state.state==CS_EDITING))
3239 {
3240 cl->position.setsize(0);
3241 while(curmsg<p.length()) cl->position.add(p.buf[curmsg++]);
3242 }
3243 if(!m_demo && !m_coop) checkmove(cl);
3244 break;
3245 }
3246
3247 case SV_POSC:
3248 {
3249 bitbuf<ucharbuf> q(p);
3250 int cn = q.getbits(5);
3251 if(cn!=sender)
3252 {
3253 disconnect_client(sender, DISC_CN);
3254 #ifndef STANDALONE
3255 conoutf("ERROR: invalid client (msg %i)", type);
3256 #endif
3257 return;
3258 }
3259 int usefactor = q.getbits(2) + 7;
3260 int xt = q.getbits(usefactor + 4);
3261 int yt = q.getbits(usefactor + 4);
3262 cl->y = (q.getbits(9)*360)/512;
3263 cl->p = ((q.getbits(8)-128)*90)/127;
3264 if(!q.getbits(1)) q.getbits(6);
3265 if(!q.getbits(1)) q.getbits(4 + 4 + 4);
3266 cl->f = q.getbits(8);
3267 int negz = q.getbits(1);
3268 int zfull = q.getbits(1);
3269 int s = q.rembits();
3270 if(s < 3) s += 8;
3271 if(zfull) s = 11;
3272 int zt = q.getbits(s);
3273 if(negz) zt = -zt;
3274 int g1 = q.getbits(1); // scoping
3275 int g2 = q.getbits(1); // shooting
3276 cl->g = (g1<<4) | (g2<<5);
3277
3278 if(!cl->isonrightmap || p.remaining() || p.overread()) { p.flags = 0; break; }
3279 if(((cl->f >> 6) & 1) != (cl->state.lifesequence & 1) || usefactor != (smapstats.hdr.sfactor < 7 ? 7 : smapstats.hdr.sfactor)) break;
3280 cl->state.o[0] = xt / DMF;
3281 cl->state.o[1] = yt / DMF;
3282 cl->state.o[2] = zt / DMF;
3283 if(cl->type==ST_TCPIP && (cl->state.state==CS_ALIVE || cl->state.state==CS_EDITING))
3284 {
3285 cl->position.setsize(0);
3286 while(curmsg<p.length()) cl->position.add(p.buf[curmsg++]);
3287 }
3288 if(!m_demo && !m_coop) checkmove(cl);
3289 break;
3290 }
3291
3292 case SV_SENDMAP:
3293 {
3294 getstring(text, p);
3295 filtertext(text, text);
3296 const char *sentmap = behindpath(text), *reject = NULL;
3297 int mapsize = getint(p);
3298 int cfgsize = getint(p);
3299 int cfgsizegz = getint(p);
3300 int revision = getint(p);
3301 if(p.remaining() < mapsize + cfgsizegz || MAXMAPSENDSIZE < mapsize + cfgsizegz)
3302 {
3303 p.forceoverread();
3304 break;
3305 }
3306 int mp = findmappath(sentmap);
3307 if(readonlymap(mp))
3308 {
3309 reject = "map is ro";
3310 defformatstring(msg)("\f3map upload rejected: map %s is readonly", sentmap);
3311 sendservmsg(msg, sender);
3312 }
3313 else if( scl.incoming_limit && ( scl.incoming_limit << 20 ) < incoming_size + mapsize + cfgsizegz )
3314 {
3315 reject = "server incoming reached its limits";
3316 sendservmsg("\f3server does not support more incomings: limit reached", sender);
3317 }
3318 else if(mp == MAP_NOTFOUND && strchr(scl.mapperm, 'C') && cl->role < CR_ADMIN)
3319 {
3320 reject = "no permission for initial upload";
3321 sendservmsg("\f3initial map upload rejected: you need to be admin", sender);
3322 }
3323 else if(mp == MAP_TEMP && revision >= mapbuffer.revision && !strchr(scl.mapperm, 'u') && cl->role < CR_ADMIN) // default: only admins can update maps
3324 {
3325 reject = "no permission to update";
3326 sendservmsg("\f3map update rejected: you need to be admin", sender);
3327 }
3328 else if(mp == MAP_TEMP && revision < mapbuffer.revision && !strchr(scl.mapperm, 'r') && cl->role < CR_ADMIN) // default: only admins can revert maps to older revisions
3329 {
3330 reject = "no permission to revert revision";
3331 sendservmsg("\f3map revert to older revision rejected: you need to be admin to upload an older map", sender);
3332 }
3333 else
3334 {
3335 if(mapbuffer.sendmap(sentmap, mapsize, cfgsize, cfgsizegz, &p.buf[p.len]))
3336 {
3337 incoming_size += mapsize + cfgsizegz;
3338 logline(ACLOG_INFO,"[%s] %s sent map %s, rev %d, %d + %d(%d) bytes written",
3339 clients[sender]->hostname, clients[sender]->name, sentmap, revision, mapsize, cfgsize, cfgsizegz);
3340 defformatstring(msg)("%s (%d) up%sed map %s, rev %d%s", clients[sender]->name, sender, mp == MAP_NOTFOUND ? "load": "dat", sentmap, revision,
3341 /*strcmp(sentmap, behindpath(smapname)) || smode == GMODE_COOPEDIT ? "" :*/ "\f3 (restart game to use new map version)");
3342 sendservmsg(msg);
3343 }
3344 else
3345 {
3346 reject = "write failed (no 'incoming'?)";
3347 sendservmsg("\f3map upload failed", sender);
3348 }
3349 }
3350 if (reject)
3351 {
3352 logline(ACLOG_INFO,"[%s] %s sent map %s rev %d, rejected: %s",
3353 clients[sender]->hostname, clients[sender]->name, sentmap, revision, reject);
3354 }
3355 p.len += mapsize + cfgsizegz;
3356 break;
3357 }
3358
3359 case SV_RECVMAP:
3360 {
3361 if(mapbuffer.available())
3362 {
3363 resetflag(cl->clientnum); // drop ctf flag
3364 savedscore *sc = findscore(*cl, true); // save score
3365 if(sc) sc->save(cl->state, cl->team);
3366 mapbuffer.sendmap(cl, 2);
3367 cl->mapchange(true);
3368 sendwelcome(cl, 2); // resend state properly
3369 }
3370 else sendservmsg("no map to get", cl->clientnum);
3371 break;
3372 }
3373
3374 case SV_REMOVEMAP:
3375 {
3376 getstring(text, p);
3377 filtertext(text, text);
3378 string filename;
3379 const char *rmmap = behindpath(text), *reject = NULL;
3380 int mp = findmappath(rmmap);
3381 int reqrole = strchr(scl.mapperm, 'D') ? CR_ADMIN : (strchr(scl.mapperm, 'd') ? CR_DEFAULT : CR_ADMIN + 100);
3382 if(cl->role < reqrole) reject = "no permission";
3383 else if(readonlymap(mp)) reject = "map is readonly";
3384 else if(mp == MAP_NOTFOUND) reject = "map not found";
3385 else
3386 {
3387 formatstring(filename)(SERVERMAP_PATH_INCOMING "%s.cgz", rmmap);
3388 remove(filename);
3389 formatstring(filename)(SERVERMAP_PATH_INCOMING "%s.cfg", rmmap);
3390 remove(filename);
3391 defformatstring(msg)("map '%s' deleted", rmmap);
3392 sendservmsg(msg, sender);
3393 logline(ACLOG_INFO,"[%s] deleted map %s", clients[sender]->hostname, rmmap);
3394 }
3395 if (reject)
3396 {
3397 logline(ACLOG_INFO,"[%s] deleting map %s failed: %s", clients[sender]->hostname, rmmap, reject);
3398 defformatstring(msg)("\f3can't delete map '%s', %s", rmmap, reject);
3399 sendservmsg(msg, sender);
3400 }
3401 break;
3402 }
3403
3404 case SV_FLAGACTION:
3405 {
3406 int action = getint(p);
3407 int flag = getint(p);
3408 if(!m_flags || flag < 0 || flag > 1 || action < 0 || action > FA_NUM) break;
3409 flagaction(flag, action, sender);
3410 break;
3411 }
3412
3413 case SV_SETADMIN:
3414 {
3415 bool claim = getint(p) != 0;
3416 if(claim)
3417 {
3418 getstring(text, p);
3419 changeclientrole(sender, CR_ADMIN, text);
3420 } else changeclientrole(sender, CR_DEFAULT);
3421 break;
3422 }
3423
3424 case SV_CALLVOTE:
3425 {
3426 voteinfo *vi = new voteinfo;
3427 vi->boot = 0;
3428 vi->type = getint(p);
3429 switch(vi->type)
3430 {
3431 case SA_MAP:
3432 {
3433 getstring(text, p);
3434 filtertext(text, text);
3435 int mode = getint(p), time = getint(p);
3436 if(time <= 0) time = -1;
3437 time = min(time, 60);
3438 vi->gonext = text[0]=='+' && text[1]=='1';
3439 if (vi->gonext)
3440 {
3441 int ccs = mode ? maprot.next(false,false) : maprot.get_next();
3442 configset *c = maprot.get(ccs);
3443 if(c)
3444 {
3445 strcpy(vi->text,c->mapname);
3446 mode = vi->num1 = c->mode;
3447 }
3448 else fatal("unable to get next map in maprot");
3449 }
3450 else
3451 {
3452 strncpy(vi->text,text,MAXTRANS-1);
3453 vi->num1 = mode;
3454 vi->num2 = time;
3455 }
3456 int qmode = (mode >= GMODE_NUM ? mode - GMODE_NUM : mode);
3457 if(mode==GMODE_DEMO) vi->action = new demoplayaction(newstring(text));
3458 else
3459 {
3460 char *vmap = newstring(vi->text ? behindpath(vi->text) : "");
3461 vi->action = new mapaction(vmap, qmode, time, sender, qmode!=mode);
3462 }
3463 break;
3464 }
3465 case SA_KICK:
3466 {
3467 vi->num1 = cn2boot = getint(p);
3468 getstring(text, p);
3469 strncpy(vi->text,text,128);
3470 filtertext(text, text);
3471 trimtrailingwhitespace(text);
3472 vi->action = new kickaction(cn2boot, newstring(text, 128));
3473 vi->boot = 1;
3474 break;
3475 }
3476 case SA_BAN:
3477 {
3478 vi->num1 = cn2boot = getint(p);
3479 getstring(text, p);
3480 strncpy(vi->text,text,128);
3481 filtertext(text, text);
3482 trimtrailingwhitespace(text);
3483 vi->action = new banaction(cn2boot, newstring(text, 128));
3484 vi->boot = 2;
3485 break;
3486 }
3487 case SA_REMBANS:
3488 vi->action = new removebansaction();
3489 break;
3490 case SA_MASTERMODE:
3491 vi->action = new mastermodeaction(vi->num1 = getint(p));
3492 break;
3493 case SA_AUTOTEAM:
3494 vi->action = new autoteamaction((vi->num1 = getint(p)) > 0);
3495 break;
3496 case SA_SHUFFLETEAMS:
3497 vi->action = new shuffleteamaction();
3498 break;
3499 case SA_FORCETEAM:
3500 vi->num1 = getint(p);
3501 vi->num2 = getint(p);
3502 vi->action = new forceteamaction(vi->num1, sender, vi->num2);
3503 break;
3504 case SA_GIVEADMIN:
3505 vi->action = new giveadminaction(vi->num1 = getint(p));
3506 break;
3507 case SA_RECORDDEMO:
3508 vi->action = new recorddemoaction((vi->num1 = getint(p))!=0);
3509 break;
3510 case SA_STOPDEMO:
3511 // compatibility
3512 break;
3513 case SA_CLEARDEMOS:
3514 vi->action = new cleardemosaction(vi->num1 = getint(p));
3515 break;
3516 case SA_SERVERDESC:
3517 getstring(text, p);
3518 strncpy(vi->text,text,MAXTRANS-1);
3519 filtertext(text, text);
3520 vi->action = new serverdescaction(newstring(text), sender);
3521 break;
3522 }
3523 vi->owner = sender;
3524 vi->callmillis = servmillis;
3525 MSG_PACKET(msg);
3526 if(!scallvote(vi, msg)) delete vi;
3527 break;
3528 }
3529
3530 case SV_VOTE:
3531 {
3532 int n = getint(p);
3533 MSG_PACKET(msg);
3534 ++msg->referenceCount; // need to increase reference count in case a vote disconnects a player after packet is queued to prevent double-freeing by packetbuf
3535 svote(sender, n, msg);
3536 --msg->referenceCount;
3537 break;
3538 }
3539
3540 case SV_LISTDEMOS:
3541 listdemos(sender);
3542 break;
3543
3544 case SV_GETDEMO:
3545 senddemo(sender, getint(p));
3546 break;
3547
3548 case SV_EXTENSION:
3549 {
3550 // AC server extensions
3551 //
3552 // rules:
3553 // 1. extensions MUST NOT modify gameplay or the behavior of the game in any way
3554 // 2. extensions may ONLY be used to extend or automate server administration tasks
3555 // 3. extensions may ONLY operate on the server and must not send any additional data to the connected clients
3556 // 4. extensions not adhering to these rules may cause the hosting server being banned from the masterserver
3557 //
3558 // also note that there is no guarantee that custom extensions will work in future AC versions
3559
3560
3561 getstring(text, p, 64);
3562 char *ext = text; // extension specifier in the form of OWNER::EXTENSION, see sample below
3563 int n = getint(p); // length of data after the specifier
3564 if(n < 0 || n > 50) return;
3565
3566 // sample
3567 if(!strcmp(ext, "driAn::writelog"))
3568 {
3569 // owner: driAn - root@sprintf.org
3570 // extension: writelog - WriteLog v1.0
3571 // description: writes a custom string to the server log
3572 // access: requires admin privileges
3573 // usage: /serverextension driAn::writelog "your log message here.."
3574 // note: There is a 49 character limit. The server will ignore messages with 50+ characters.
3575
3576 getstring(text, p, n);
3577 if(valid_client(sender) && clients[sender]->role==CR_ADMIN)
3578 {
3579 logline(ACLOG_INFO, "[%s] %s writes to log: %s", cl->hostname, cl->name, text);
3580 sendservmsg("your message has been logged", sender);
3581 }
3582 }
3583 else if(!strcmp(ext, "set::teamsize"))
3584 {
3585 // intermediate solution to set the teamsize (will be voteable)
3586
3587 getstring(text, p, n);
3588 if(valid_client(sender) && clients[sender]->role==CR_ADMIN && mastermode == MM_MATCH)
3589 {
3590 changematchteamsize(atoi(text));
3591 defformatstring(msg)("match team size set to %d", matchteamsize);
3592 sendservmsg(msg, -1);
3593 }
3594 }
3595 // else if()
3596
3597 // add other extensions here
3598
3599 else for(; n > 0; n--) getint(p); // ignore unknown extensions
3600
3601 break;
3602 }
3603
3604 case -1:
3605 disconnect_client(sender, DISC_TAGT);
3606 return;
3607
3608 case -2:
3609 disconnect_client(sender, DISC_OVERFLOW);
3610 return;
3611
3612 default:
3613 {
3614 int size = msgsizelookup(type);
3615 if(size<=0) { if(sender>=0) disconnect_client(sender, DISC_TAGT); return; }
3616 loopi(size-1) getint(p);
3617 QUEUE_MSG;
3618 break;
3619 }
3620 }
3621 }
3622
3623 if(p.overread() && sender>=0) disconnect_client(sender, DISC_EOP);
3624
3625 #ifdef _DEBUG
3626 protocoldebug(false);
3627 #endif
3628 }
3629
localclienttoserver(int chan,ENetPacket * packet)3630 void localclienttoserver(int chan, ENetPacket *packet)
3631 {
3632 process(packet, 0, chan);
3633 }
3634
addclient()3635 client &addclient()
3636 {
3637 client *c = NULL;
3638 loopv(clients) if(clients[i]->type==ST_EMPTY) { c = clients[i]; break; }
3639 if(!c)
3640 {
3641 c = new client;
3642 c->clientnum = clients.length();
3643 clients.add(c);
3644 }
3645 c->reset();
3646 return *c;
3647 }
3648
checkintermission()3649 void checkintermission()
3650 {
3651 if(minremain>0)
3652 {
3653 minremain = (gamemillis>=gamelimit || forceintermission) ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000;
3654 sendf(-1, 1, "ri3", SV_TIMEUP, (gamemillis>=gamelimit || forceintermission) ? gamelimit : gamemillis, gamelimit);
3655 }
3656 if(!interm && minremain<=0) interm = gamemillis+10000;
3657 forceintermission = false;
3658 }
3659
resetserverifempty()3660 void resetserverifempty()
3661 {
3662 loopv(clients) if(clients[i]->type!=ST_EMPTY) return;
3663 resetserver("", 0, 10);
3664 matchteamsize = 0;
3665 autoteam = true;
3666 changemastermode(MM_OPEN);
3667 nextmapname[0] = '\0';
3668 }
3669
sendworldstate()3670 void sendworldstate()
3671 {
3672 static enet_uint32 lastsend = 0;
3673 if(clients.empty()) return;
3674 enet_uint32 curtime = enet_time_get()-lastsend;
3675 if(curtime<40) return;
3676 bool flush = buildworldstate();
3677 lastsend += curtime - (curtime%40);
3678 if(flush) enet_host_flush(serverhost);
3679 if(demorecord) recordpackets = true; // enable after 'old' worldstate is sent
3680 }
3681
rereadcfgs(void)3682 void rereadcfgs(void)
3683 {
3684 maprot.read();
3685 ipblacklist.read();
3686 nickblacklist.read();
3687 forbiddenlist.read();
3688 passwords.read();
3689 killmsgs.read();
3690 }
3691
loggamestatus(const char * reason)3692 void loggamestatus(const char *reason)
3693 {
3694 int fragscore[2] = {0, 0}, flagscore[2] = {0, 0}, pnum[2] = {0, 0};
3695 string text;
3696 formatstring(text)("%d minutes remaining", minremain);
3697 logline(ACLOG_INFO, "");
3698 logline(ACLOG_INFO, "Game status: %s on %s, %s, %s, %d clients%c %s",
3699 modestr(gamemode), smapname, reason ? reason : text, mmfullname(mastermode), totalclients, custom_servdesc ? ',' : '\0', servdesc_current);
3700 if(!scl.loggamestatus) return;
3701 logline(ACLOG_INFO, "cn name %s%s score frag death %sping role host", m_teammode ? "team " : "", m_flags ? "flag " : "", m_teammode ? "tk " : "");
3702 loopv(clients)
3703 {
3704 client &c = *clients[i];
3705 if(c.type == ST_EMPTY || !c.name[0]) continue;
3706 formatstring(text)("%2d %-16s ", c.clientnum, c.name); // cn name
3707 if(m_teammode) concatformatstring(text, "%-4s ", team_string(c.team, true)); // teamname (abbreviated)
3708 if(m_flags) concatformatstring(text, "%4d ", c.state.flagscore); // flag
3709 concatformatstring(text, "%6d ", c.state.points); // score
3710 concatformatstring(text, "%4d %5d", c.state.frags, c.state.deaths); // frag death
3711 if(m_teammode) concatformatstring(text, " %2d", c.state.teamkills); // tk
3712 logline(ACLOG_INFO, "%s%5d %s %s", text, c.ping, c.role == CR_ADMIN ? "admin " : "normal", c.hostname);
3713 if(c.team != TEAM_SPECT)
3714 {
3715 int t = team_base(c.team);
3716 flagscore[t] += c.state.flagscore;
3717 fragscore[t] += c.state.frags;
3718 pnum[t] += 1;
3719 }
3720 }
3721 if(mastermode == MM_MATCH)
3722 {
3723 loopv(savedscores)
3724 {
3725 savedscore &sc = savedscores[i];
3726 if(sc.valid)
3727 {
3728 formatstring(text)(m_teammode ? "%-4s " : "", team_string(sc.team, true));
3729 if(m_flags) concatformatstring(text, "%4d ", sc.flagscore);
3730 logline(ACLOG_INFO, " %-16s %s%4d %5d%s - disconnected", sc.name, text, sc.frags, sc.deaths, m_teammode ? " -" : "");
3731 if(sc.team != TEAM_SPECT)
3732 {
3733 int t = team_base(sc.team);
3734 flagscore[t] += sc.flagscore;
3735 fragscore[t] += sc.frags;
3736 pnum[t] += 1;
3737 }
3738 }
3739 }
3740 }
3741 if(m_teammode)
3742 {
3743 loopi(2) logline(ACLOG_INFO, "Team %4s:%3d players,%5d frags%c%5d flags", team_string(i), pnum[i], fragscore[i], m_flags ? ',' : '\0', flagscore[i]);
3744 }
3745 logline(ACLOG_INFO, "");
3746 }
3747
3748 static unsigned char chokelog[MAXCLIENTS + 1] = { 0 };
3749
linequalitystats(int elapsed)3750 void linequalitystats(int elapsed)
3751 {
3752 static unsigned int chokes[MAXCLIENTS + 1] = { 0 }, spent[MAXCLIENTS + 1] = { 0 }, chokes_raw[MAXCLIENTS + 1] = { 0 }, spent_raw[MAXCLIENTS + 1] = { 0 };
3753 if(elapsed)
3754 { // collect data
3755 int c1 = 0, c2 = 0, r1 = 0, numc = 0;
3756 loopv(clients)
3757 {
3758 client &c = *clients[i];
3759 if(c.type != ST_TCPIP) continue;
3760 numc++;
3761 enet_uint32 &rtt = c.peer->lastRoundTripTime, &throttle = c.peer->packetThrottle;
3762 if(rtt < c.bottomRTT + c.bottomRTT / 3)
3763 {
3764 if(servmillis - c.connectmillis < 5000)
3765 c.bottomRTT = rtt;
3766 else
3767 c.bottomRTT = (c.bottomRTT * 15 + rtt) / 16; // simple IIR
3768 }
3769 if(throttle < 22) c1++;
3770 if(throttle < 11) c2++;
3771 if(rtt > c.bottomRTT * 2 && rtt - c.bottomRTT > 300) r1++;
3772 }
3773 spent_raw[numc] += elapsed;
3774 int t = numc < 7 ? numc : (numc + 1) / 2 + 3;
3775 chokes_raw[numc] += ((c1 >= t ? c1 + c2 : 0) + (r1 >= t ? r1 : 0)) * elapsed;
3776 }
3777 else
3778 { // calculate compressed statistics
3779 defformatstring(msg)("Uplink quality [ ");
3780 int ncs = 0;
3781 loopj(scl.maxclients)
3782 {
3783 int i = j + 1;
3784 int nc = chokes_raw[i] / 1000 / i;
3785 chokes[i] += nc;
3786 ncs += nc;
3787 spent[i] += spent_raw[i] / 1000;
3788 chokes_raw[i] = spent_raw[i] = 0;
3789 int s = 0, c = 0;
3790 if(spent[i])
3791 {
3792 frexp((double)spent[i] / 30, &s);
3793 if(s < 0) s = 0;
3794 if(s > 15) s = 15;
3795 if(chokes[i])
3796 {
3797 frexp(((double)chokes[i]) / spent[i], &c);
3798 c = 15 + c;
3799 if(c < 0) c = 0;
3800 if(c > 15) c = 15;
3801 }
3802 }
3803 chokelog[i] = (s << 4) + c;
3804 concatformatstring(msg, "%02X ", chokelog[i]);
3805 }
3806 logline(ACLOG_DEBUG, "%s] +%d", msg, ncs);
3807 }
3808 }
3809
serverslice(uint timeout)3810 void serverslice(uint timeout) // main server update, called from cube main loop in sp, or dedicated server loop
3811 {
3812 static int msend = 0, mrec = 0, csend = 0, crec = 0, mnum = 0, cnum = 0;
3813 #ifdef STANDALONE
3814 int nextmillis = (int)enet_time_get();
3815 if(svcctrl) svcctrl->keepalive();
3816 #else
3817 int nextmillis = isdedicated ? (int)enet_time_get() : lastmillis;
3818 #endif
3819 int diff = nextmillis - servmillis;
3820 gamemillis += diff;
3821 servmillis = nextmillis;
3822 servertime = ((diff + 3 * servertime)>>2);
3823 if (servertime > 40) serverlagged = servmillis;
3824
3825 #ifndef STANDALONE
3826 if(m_demo)
3827 {
3828 readdemo();
3829 extern void silenttimeupdate(int milliscur, int millismax);
3830 silenttimeupdate(gamemillis, gametimemaximum);
3831 }
3832 #endif
3833
3834 if(minremain>0)
3835 {
3836 processevents();
3837 checkitemspawns(diff);
3838 bool ktfflagingame = false;
3839 if(m_flags) loopi(2)
3840 {
3841 sflaginfo &f = sflaginfos[i];
3842 if(f.state == CTFF_DROPPED && gamemillis-f.lastupdate > (m_ctf ? 30000 : 10000)) flagaction(i, FA_RESET, -1);
3843 if(m_htf && f.state == CTFF_INBASE && gamemillis-f.lastupdate > (smapstats.hasflags ? 10000 : 1000))
3844 {
3845 htf_forceflag(i);
3846 }
3847 if(m_ktf && f.state == CTFF_STOLEN && gamemillis-f.lastupdate > 15000)
3848 {
3849 flagaction(i, FA_SCORE, -1);
3850 }
3851 if(f.state == CTFF_INBASE || f.state == CTFF_STOLEN) ktfflagingame = true;
3852 }
3853 if(m_ktf && !ktfflagingame) flagaction(rnd(2), FA_RESET, -1); // ktf flag watchdog
3854 if(m_arena) arenacheck();
3855 // if(m_lms) lmscheck();
3856 sendextras();
3857 if ( scl.afk_limit && mastermode == MM_OPEN && next_afk_check < servmillis && gamemillis > 20 * 1000 ) check_afk();
3858 }
3859
3860 if(curvote)
3861 {
3862 if(!curvote->isalive()) curvote->evaluate(true);
3863 if(curvote->result!=VOTE_NEUTRAL) DELETEP(curvote);
3864 }
3865
3866 int nonlocalclients = numnonlocalclients();
3867
3868 if(forceintermission || ((smode>1 || (gamemode==0 && nonlocalclients)) && gamemillis-diff>0 && gamemillis/60000!=(gamemillis-diff)/60000))
3869 checkintermission();
3870 if(m_demo && !demoplayback) maprot.restart();
3871 else if(interm && ( (scl.demo_interm && sending_demo) ? gamemillis>(interm<<1) : gamemillis>interm ) )
3872 {
3873 sending_demo = false;
3874 loggamestatus("game finished");
3875 if(demorecord) enddemorecord();
3876 interm = nextsendscore = 0;
3877
3878 //start next game
3879 if(nextmapname[0]) startgame(nextmapname, nextgamemode);
3880 else maprot.next();
3881 nextmapname[0] = '\0';
3882 map_queued = false;
3883 }
3884
3885 resetserverifempty();
3886
3887 if(!isdedicated) return; // below is network only
3888
3889 serverms(smode, numclients(), minremain, smapname, servmillis, serverhost->address, &mnum, &msend, &mrec, &cnum, &csend, &crec, SERVER_PROTOCOL_VERSION);
3890
3891 if(autoteam && m_teammode && !m_arena && !interm && servmillis - lastfillup > 5000 && refillteams()) lastfillup = servmillis;
3892
3893 static unsigned int lastThrottleEpoch = 0;
3894 if(serverhost->bandwidthThrottleEpoch != lastThrottleEpoch)
3895 {
3896 if(lastThrottleEpoch) linequalitystats(serverhost->bandwidthThrottleEpoch - lastThrottleEpoch);
3897 lastThrottleEpoch = serverhost->bandwidthThrottleEpoch;
3898 }
3899
3900 if(servmillis>nextstatus) // display bandwidth stats, useful for server ops
3901 {
3902 nextstatus = servmillis + 60 * 1000;
3903 rereadcfgs();
3904 if(nonlocalclients || serverhost->totalSentData || serverhost->totalReceivedData)
3905 {
3906 if(nonlocalclients) loggamestatus(NULL);
3907 logline(ACLOG_INFO, "Status at %s: %d remote clients, %.1f send, %.1f rec (K/sec);"
3908 " Ping: #%d|%d|%d; CSL: #%d|%d|%d (bytes)",
3909 timestring(true, "%d-%m-%Y %H:%M:%S"), nonlocalclients, serverhost->totalSentData/60.0f/1024, serverhost->totalReceivedData/60.0f/1024,
3910 mnum, msend, mrec, cnum, csend, crec);
3911 mnum = msend = mrec = cnum = csend = crec = 0;
3912 linequalitystats(0);
3913 }
3914 serverhost->totalSentData = serverhost->totalReceivedData = 0;
3915 }
3916
3917 ENetEvent event;
3918 bool serviced = false;
3919 while(!serviced)
3920 {
3921 if(enet_host_check_events(serverhost, &event) <= 0)
3922 {
3923 if(enet_host_service(serverhost, &event, timeout) <= 0) break;
3924 serviced = true;
3925 }
3926 switch(event.type)
3927 {
3928 case ENET_EVENT_TYPE_CONNECT:
3929 {
3930 client &c = addclient();
3931 c.type = ST_TCPIP;
3932 c.peer = event.peer;
3933 c.peer->data = (void *)(size_t)c.clientnum;
3934 c.connectmillis = servmillis;
3935 c.state.state = CS_SPECTATE;
3936 c.salt = rnd(0x1000000)*((servmillis%1000)+1);
3937 char hn[1024];
3938 copystring(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown");
3939 logline(ACLOG_INFO,"[%s] client connected", c.hostname);
3940 sendservinfo(c);
3941 totalclients++;
3942 break;
3943 }
3944
3945 case ENET_EVENT_TYPE_RECEIVE:
3946 {
3947 int cn = (int)(size_t)event.peer->data;
3948 if(valid_client(cn)) process(event.packet, cn, event.channelID);
3949 if(event.packet->referenceCount==0) enet_packet_destroy(event.packet);
3950 break;
3951 }
3952
3953 case ENET_EVENT_TYPE_DISCONNECT:
3954 {
3955 int cn = (int)(size_t)event.peer->data;
3956 if(!valid_client(cn)) break;
3957 disconnect_client(cn);
3958 break;
3959 }
3960
3961 default:
3962 break;
3963 }
3964 }
3965 sendworldstate();
3966 }
3967
cleanupserver()3968 void cleanupserver()
3969 {
3970 if(serverhost) { enet_host_destroy(serverhost); serverhost = NULL; }
3971 if(svcctrl)
3972 {
3973 svcctrl->stop();
3974 DELETEP(svcctrl);
3975 }
3976 exitlogging();
3977 }
3978
getpongflags(enet_uint32 ip)3979 int getpongflags(enet_uint32 ip)
3980 {
3981 int flags = mastermode << PONGFLAG_MASTERMODE;
3982 flags |= scl.serverpassword[0] ? 1 << PONGFLAG_PASSWORD : 0;
3983 loopv(bans) if(bans[i].address.host == ip) { flags |= 1 << PONGFLAG_BANNED; break; }
3984 flags |= ipblacklist.check(ip) ? 1 << PONGFLAG_BLACKLIST : 0;
3985 return flags;
3986 }
3987
extping_namelist(ucharbuf & p)3988 void extping_namelist(ucharbuf &p)
3989 {
3990 loopv(clients)
3991 {
3992 if(clients[i]->type == ST_TCPIP && clients[i]->isauthed) sendstring(clients[i]->name, p);
3993 }
3994 sendstring("", p);
3995 }
3996
extping_serverinfo(ucharbuf & pi,ucharbuf & po)3997 void extping_serverinfo(ucharbuf &pi, ucharbuf &po)
3998 {
3999 char lang[3];
4000 lang[0] = tolower(getint(pi)); lang[1] = tolower(getint(pi)); lang[2] = '\0';
4001 const char *reslang = lang, *buf = infofiles.getinfo(lang); // try client language
4002 if(!buf) buf = infofiles.getinfo(reslang = "en"); // try english
4003 sendstring(buf ? reslang : "", po);
4004 if(buf)
4005 {
4006 for(const char *c = buf; *c && po.remaining() > MAXINFOLINELEN + 10; c += strlen(c) + 1) sendstring(c, po);
4007 sendstring("", po);
4008 }
4009 }
4010
extping_maprot(ucharbuf & po)4011 void extping_maprot(ucharbuf &po)
4012 {
4013 putint(po, CONFIG_MAXPAR);
4014 string text;
4015 bool abort = false;
4016 loopv(maprot.configsets)
4017 {
4018 if(po.remaining() < 100) abort = true;
4019 configset &c = maprot.configsets[i];
4020 filtertext(text, c.mapname, 0);
4021 text[30] = '\0';
4022 sendstring(abort ? "-- list truncated --" : text, po);
4023 loopi(CONFIG_MAXPAR) putint(po, c.par[i]);
4024 if(abort) break;
4025 }
4026 sendstring("", po);
4027 }
4028
extping_uplinkstats(ucharbuf & po)4029 void extping_uplinkstats(ucharbuf &po)
4030 {
4031 if(scl.maxclients > 3)
4032 po.put(chokelog + 4, scl.maxclients - 3); // send logs for 4..n used slots
4033 }
4034
extinfo_cnbuf(ucharbuf & p,int cn)4035 void extinfo_cnbuf(ucharbuf &p, int cn)
4036 {
4037 if(cn == -1) // add all available player ids
4038 {
4039 loopv(clients) if(clients[i]->type != ST_EMPTY)
4040 putint(p,clients[i]->clientnum);
4041 }
4042 else if(valid_client(cn)) // add single player only
4043 {
4044 putint(p,clients[cn]->clientnum);
4045 }
4046 }
4047
extinfo_statsbuf(ucharbuf & p,int pid,int bpos,ENetSocket & pongsock,ENetAddress & addr,ENetBuffer & buf,int len,int * csend)4048 void extinfo_statsbuf(ucharbuf &p, int pid, int bpos, ENetSocket &pongsock, ENetAddress &addr, ENetBuffer &buf, int len, int *csend)
4049 {
4050 loopv(clients)
4051 {
4052 if(clients[i]->type != ST_TCPIP) continue;
4053 if(pid>-1 && clients[i]->clientnum!=pid) continue;
4054
4055 bool ismatch = mastermode == MM_MATCH;
4056 putint(p,EXT_PLAYERSTATS_RESP_STATS); // send player stats following
4057 putint(p,clients[i]->clientnum); //add player id
4058 putint(p,clients[i]->ping); //Ping
4059 sendstring(clients[i]->name,p); //Name
4060 sendstring(team_string(clients[i]->team),p); //Team
4061 // "team_string(clients[i]->team)" sometimes return NULL according to RK, causing the server to crash. WTF ?
4062 putint(p,clients[i]->state.frags); //Frags
4063 putint(p,clients[i]->state.flagscore); //Flagscore
4064 putint(p,clients[i]->state.deaths); //Death
4065 putint(p,clients[i]->state.teamkills); //Teamkills
4066 putint(p,ismatch ? 0 : clients[i]->state.damage*100/max(clients[i]->state.shotdamage,1)); //Accuracy
4067 putint(p,ismatch ? 0 : clients[i]->state.health); //Health
4068 putint(p,ismatch ? 0 : clients[i]->state.armour); //Armour
4069 putint(p,ismatch ? 0 : clients[i]->state.gunselect); //Gun selected
4070 putint(p,clients[i]->role); //Role
4071 putint(p,clients[i]->state.state); //State (Alive,Dead,Spawning,Lagged,Editing)
4072 uint ip = clients[i]->peer->address.host; // only 3 byte of the ip address (privacy protected)
4073 p.put((uchar*)&ip,3);
4074
4075 buf.dataLength = len + p.length();
4076 enet_socket_send(pongsock, &addr, &buf, 1);
4077 *csend += (int)buf.dataLength;
4078
4079 if(pid>-1) break;
4080 p.len=bpos;
4081 }
4082 }
4083
extinfo_teamscorebuf(ucharbuf & p)4084 void extinfo_teamscorebuf(ucharbuf &p)
4085 {
4086 putint(p, m_teammode ? EXT_ERROR_NONE : EXT_ERROR);
4087 putint(p, gamemode);
4088 putint(p, minremain); // possible TODO: use gamemillis, gamelimit here too?
4089 if(!m_teammode) return;
4090
4091 int teamsizes[TEAM_NUM] = { 0 }, fragscores[TEAM_NUM] = { 0 }, flagscores[TEAM_NUM] = { 0 };
4092 loopv(clients) if(clients[i]->type!=ST_EMPTY && team_isvalid(clients[i]->team))
4093 {
4094 teamsizes[clients[i]->team] += 1;
4095 fragscores[clients[i]->team] += clients[i]->state.frags;
4096 flagscores[clients[i]->team] += clients[i]->state.flagscore;
4097 }
4098
4099 loopi(TEAM_NUM) if(teamsizes[i])
4100 {
4101 sendstring(team_string(i), p); // team name
4102 putint(p, fragscores[i]); // add fragscore per team
4103 putint(p, m_flags ? flagscores[i] : -1); // add flagscore per team
4104 putint(p, -1); // ?
4105 }
4106 }
4107
4108
4109 #ifndef STANDALONE
localdisconnect()4110 void localdisconnect()
4111 {
4112 loopv(clients) if(clients[i]->type==ST_LOCAL) clients[i]->zap();
4113 }
4114
localconnect()4115 void localconnect()
4116 {
4117 modprotocol = false;
4118 servstate.reset();
4119 client &c = addclient();
4120 c.type = ST_LOCAL;
4121 c.role = CR_ADMIN;
4122 copystring(c.hostname, "local");
4123 sendservinfo(c);
4124 }
4125 #endif
4126
processmasterinput(const char * cmd,int cmdlen,const char * args)4127 void processmasterinput(const char *cmd, int cmdlen, const char *args)
4128 {
4129 // AUTH WiP
4130 uint id;
4131 string val;
4132 if(sscanf(cmd, "failauth %u", &id) == 1) authfailed(id);
4133 else if(sscanf(cmd, "succauth %u", &id) == 1) authsucceeded(id);
4134 else if(sscanf(cmd, "chalauth %u %s", &id, val) == 2) authchallenged(id, val);
4135 else if(!strncmp(cmd, "cleargbans", cmdlen)) cleargbans();
4136 else if(sscanf(cmd, "addgban %s", val) == 1) addgban(val);
4137 }
4138
4139 string server_name = "unarmed server";
4140
quitproc(int param)4141 void quitproc(int param)
4142 {
4143 // this triggers any "atexit"-calls:
4144 exit(param == 2 ? EXIT_SUCCESS : EXIT_FAILURE); // 3 is the only reply on Win32 apparently, SIGINT == 2 == Ctrl-C
4145 }
4146
initserver(bool dedicated,int argc,char ** argv)4147 void initserver(bool dedicated, int argc, char **argv)
4148 {
4149 const char *service = NULL;
4150
4151 for(int i = 1; i<argc; i++)
4152 {
4153 if(!scl.checkarg(argv[i]))
4154 {
4155 char *a = &argv[i][2];
4156 if(!scl.checkarg(argv[i]) && argv[i][0]=='-') switch(argv[i][1])
4157 {
4158 case '-': break;
4159 case 'S': service = a; break;
4160 default: break; /*printf("WARNING: unknown commandline option\n");*/ // less warnings - 2011feb05:ft: who disabled this - I think this should be on - more warnings == more clarity
4161 }
4162 else if (strncmp(argv[i], "assaultcube://", 13)) printf("WARNING: unknown commandline argument\n");
4163 }
4164 }
4165
4166 if(service && !svcctrl)
4167 {
4168 #ifdef WIN32
4169 svcctrl = new winservice(service);
4170 #endif
4171 if(svcctrl)
4172 {
4173 svcctrl->argc = argc; svcctrl->argv = argv;
4174 svcctrl->start();
4175 }
4176 }
4177
4178 if ( strlen(scl.servdesc_full) ) global_name = scl.servdesc_full;
4179 else global_name = server_name;
4180
4181 smapname[0] = '\0';
4182
4183 string identity;
4184 if(scl.logident[0]) filtertext(identity, scl.logident, 0);
4185 else formatstring(identity)("%s#%d", scl.ip[0] ? scl.ip : "local", scl.serverport);
4186 int conthres = scl.verbose > 1 ? ACLOG_DEBUG : (scl.verbose ? ACLOG_VERBOSE : ACLOG_INFO);
4187 if(dedicated && !initlogging(identity, scl.syslogfacility, conthres, scl.filethres, scl.syslogthres, scl.logtimestamp))
4188 printf("WARNING: logging not started!\n");
4189 logline(ACLOG_INFO, "logging local AssaultCube server (version %d, protocol %d/%d) now..", AC_VERSION, SERVER_PROTOCOL_VERSION, EXT_VERSION);
4190
4191 copystring(servdesc_current, scl.servdesc_full);
4192 servermsinit(scl.master ? scl.master : AC_MASTER_URI, scl.ip, CUBE_SERVINFO_PORT(scl.serverport), dedicated);
4193
4194 if((isdedicated = dedicated))
4195 {
4196 ENetAddress address = { ENET_HOST_ANY, (enet_uint16)scl.serverport };
4197 if(scl.ip[0] && enet_address_set_host(&address, scl.ip)<0) logline(ACLOG_WARNING, "server ip not resolved!");
4198 serverhost = enet_host_create(&address, scl.maxclients+1, 3, 0, scl.uprate);
4199 if(!serverhost) fatal("could not create server host");
4200 loopi(scl.maxclients) serverhost->peers[i].data = (void *)-1;
4201
4202 maprot.init(scl.maprot);
4203 maprot.next(false, true); // ensure minimum maprot length of '1'
4204 passwords.init(scl.pwdfile, scl.adminpasswd);
4205 ipblacklist.init(scl.blfile);
4206 nickblacklist.init(scl.nbfile);
4207 forbiddenlist.init(scl.forbidden);
4208 killmsgs.init(scl.killmessages);
4209 infofiles.init(scl.infopath, scl.motdpath);
4210 infofiles.getinfo("en"); // cache 'en' serverinfo
4211 logline(ACLOG_VERBOSE, "holding up to %d recorded demos in memory", scl.maxdemos);
4212 if(scl.demopath[0]) logline(ACLOG_VERBOSE,"all recorded demos will be written to: \"%s\"", scl.demopath);
4213 if(scl.voteperm[0]) logline(ACLOG_VERBOSE,"vote permission string: \"%s\"", scl.voteperm);
4214 if(scl.mapperm[0]) logline(ACLOG_VERBOSE,"map permission string: \"%s\"", scl.mapperm);
4215 logline(ACLOG_VERBOSE,"server description: \"%s\"", scl.servdesc_full);
4216 if(scl.servdesc_pre[0] || scl.servdesc_suf[0]) logline(ACLOG_VERBOSE,"custom server description: \"%sCUSTOMPART%s\"", scl.servdesc_pre, scl.servdesc_suf);
4217 logline(ACLOG_VERBOSE,"maxclients: %d, kick threshold: %d, ban threshold: %d", scl.maxclients, scl.kickthreshold, scl.banthreshold);
4218 if(scl.master) logline(ACLOG_VERBOSE,"master server URL: \"%s\"", scl.master);
4219 if(scl.serverpassword[0]) logline(ACLOG_VERBOSE,"server password: \"%s\"", hiddenpwd(scl.serverpassword));
4220 #ifdef ACAC
4221 logline(ACLOG_INFO, "anticheat: enabled");
4222 #else
4223 logline(ACLOG_INFO, "anticheat: disabled");
4224 #endif
4225 }
4226
4227 resetserverifempty();
4228
4229 if(isdedicated) // do not return, this becomes main loop
4230 {
4231 #ifdef WIN32
4232 SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
4233 #endif
4234 // kill -2 / Ctrl-C - see http://msdn.microsoft.com/en-us/library/xdkz3x12%28v=VS.100%29.aspx (or VS-2008?) for caveat (seems not to pertain to AC - 2011feb05:ft)
4235 if (signal(SIGINT, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGINT!");
4236 // kill -15 / probably process-manager on Win32 *shrug*
4237 if (signal(SIGTERM, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGTERM!");
4238 #ifndef WIN32
4239 // kill -1
4240 if (signal(SIGHUP, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGHUP!");
4241 // kill -9 is uncatchable - http://en.wikipedia.org/wiki/SIGKILL
4242 //if (signal(SIGKILL, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGKILL!");
4243 #endif
4244 logline(ACLOG_INFO, "dedicated server started, waiting for clients...");
4245 logline(ACLOG_INFO, "Ctrl-C to exit"); // this will now actually call the atexit-hooks below - thanks to SIGINT hooked above - noticed and signal-code-docs found by SKB:2011feb05:ft:
4246 atexit(enet_deinitialize);
4247 atexit(cleanupserver);
4248 enet_time_set(0);
4249 for(;;) serverslice(5);
4250 }
4251 }
4252
4253 #ifdef STANDALONE
4254
localservertoclient(int chan,uchar * buf,int len,bool demo)4255 void localservertoclient(int chan, uchar *buf, int len, bool demo) {}
fatal(const char * s,...)4256 void fatal(const char *s, ...)
4257 {
4258 defvformatstring(msg,s,s);
4259 defformatstring(out)("AssaultCube fatal error: %s", msg);
4260 if (logline(ACLOG_ERROR, "%s", out));
4261 else puts(out);
4262 cleanupserver();
4263 exit(EXIT_FAILURE);
4264 }
4265
main(int argc,char ** argv)4266 int main(int argc, char **argv)
4267 {
4268 #ifdef WIN32
4269 //atexit((void (__cdecl *)(void))_CrtDumpMemoryLeaks);
4270 #ifndef _DEBUG
4271 #ifndef __GNUC__
4272 __try {
4273 #endif
4274 #endif
4275 #endif
4276
4277 for(int i = 1; i<argc; i++)
4278 {
4279 if (!strncmp(argv[i],"--wizard",8)) return wizardmain(argc, argv);
4280 }
4281
4282 if(enet_initialize()<0) fatal("Unable to initialise network module");
4283 initserver(true, argc, argv);
4284 return EXIT_SUCCESS;
4285
4286 #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__)
4287 } __except(stackdumper(0, GetExceptionInformation()), EXCEPTION_CONTINUE_SEARCH) { return 0; }
4288 #endif
4289 }
4290 #endif
4291
4292