Initial sauer
[SauerbratenRemote.git] / src / fpsgame / fpsserver.h
blob646a771e6831e81d78bf3441b48330844c6edd37
1 #ifdef WIN32
2 #include <io.h>
3 #else
4 #include <unistd.h>
5 #define _dup dup
6 #define _fileno fileno
7 #endif
9 struct fpsserver : igameserver
11 struct server_entity // server side version of "entity" type
13 int type;
14 int spawntime;
15 char spawned;
18 static const int DEATHMILLIS = 300;
20 enum { GE_NONE = 0, GE_SHOT, GE_EXPLODE, GE_HIT, GE_SUICIDE, GE_PICKUP };
22 struct shotevent
24 int type;
25 int millis, id;
26 int gun;
27 float from[3], to[3];
30 struct explodeevent
32 int type;
33 int millis, id;
34 int gun;
37 struct hitevent
39 int type;
40 int target;
41 int lifesequence;
42 union
44 int rays;
45 float dist;
47 float dir[3];
50 struct suicideevent
52 int type;
55 struct pickupevent
57 int type;
58 int ent;
61 union gameevent
63 int type;
64 shotevent shot;
65 explodeevent explode;
66 hitevent hit;
67 suicideevent suicide;
68 pickupevent pickup;
71 template <int N>
72 struct projectilestate
74 int projs[N];
75 int numprojs;
77 projectilestate() : numprojs(0) {}
79 void reset() { numprojs = 0; }
81 void add(int val)
83 if(numprojs>=N) numprojs = 0;
84 projs[numprojs++] = val;
87 bool remove(int val)
89 loopi(numprojs) if(projs[i]==val)
91 projs[i] = projs[--numprojs];
92 return true;
94 return false;
98 struct gamestate : fpsstate
100 vec o;
101 int state;
102 int lastdeath, lastspawn, lifesequence;
103 int lastshot;
104 projectilestate<8> rockets, grenades;
105 int frags;
106 int lasttimeplayed, timeplayed;
107 float effectiveness;
109 gamestate() : state(CS_DEAD) {}
111 bool isalive(int gamemillis)
113 return state==CS_ALIVE || (state==CS_DEAD && gamemillis - lastdeath <= DEATHMILLIS);
116 bool waitexpired(int gamemillis)
118 return gamemillis - lastshot >= gunwait;
121 void reset()
123 if(state!=CS_SPECTATOR) state = CS_DEAD;
124 lifesequence = 0;
125 maxhealth = 100;
126 rockets.reset();
127 grenades.reset();
129 timeplayed = 0;
130 effectiveness = 0;
131 frags = 0;
133 respawn();
136 void respawn()
138 fpsstate::respawn();
139 o = vec(-1e10f, -1e10f, -1e10f);
140 lastdeath = 0;
141 lastspawn = -1;
142 lastshot = 0;
146 struct savedscore
148 uint ip;
149 string name;
150 int maxhealth, frags;
151 int timeplayed;
152 float effectiveness;
154 void save(gamestate &gs)
156 maxhealth = gs.maxhealth;
157 frags = gs.frags;
158 timeplayed = gs.timeplayed;
159 effectiveness = gs.effectiveness;
162 void restore(gamestate &gs)
164 gs.maxhealth = maxhealth;
165 gs.frags = frags;
166 gs.timeplayed = timeplayed;
167 gs.effectiveness = effectiveness;
171 struct clientinfo
173 int clientnum;
174 string name, team, mapvote;
175 int modevote;
176 int privilege;
177 bool spectator, local, timesync, wantsmaster;
178 int gameoffset, lastevent;
179 gamestate state;
180 vector<gameevent> events;
181 vector<uchar> position, messages;
182 vector<clientinfo *> targets;
184 clientinfo() { reset(); }
186 gameevent &addevent()
188 static gameevent dummy;
189 if(events.length()>100) return dummy;
190 return events.add();
193 void mapchange()
195 mapvote[0] = 0;
196 state.reset();
197 events.setsizenodelete(0);
198 targets.setsizenodelete(0);
199 timesync = false;
200 lastevent = 0;
203 void reset()
205 name[0] = team[0] = 0;
206 privilege = PRIV_NONE;
207 spectator = local = wantsmaster = false;
208 position.setsizenodelete(0);
209 messages.setsizenodelete(0);
210 mapchange();
214 struct worldstate
216 int uses;
217 vector<uchar> positions, messages;
220 struct ban
222 int time;
223 uint ip;
226 #define MM_MODE 0xF
227 #define MM_AUTOAPPROVE 0x1000
228 #define MM_DEFAULT (MM_MODE | MM_AUTOAPPROVE)
230 enum { MM_OPEN = 0, MM_VETO, MM_LOCKED, MM_PRIVATE };
232 bool notgotitems, notgotbases; // true when map has changed and waiting for clients to send item
233 int gamemode;
234 int gamemillis, gamelimit;
236 string serverdesc;
237 string smapname;
238 int lastmillis, totalmillis, curtime;
239 int interm, minremain;
240 bool mapreload;
241 enet_uint32 lastsend;
242 int mastermode, mastermask;
243 int currentmaster;
244 bool masterupdate;
245 string masterpass;
246 FILE *mapdata;
248 vector<ban> bannedips;
249 vector<clientinfo *> clients;
250 vector<worldstate *> worldstates;
251 bool reliablemessages;
253 struct demofile
255 string info;
256 uchar *data;
257 int len;
260 #define MAXDEMOS 5
261 vector<demofile> demos;
263 bool demonextmatch;
264 FILE *demotmp;
265 gzFile demorecord, demoplayback;
266 int nextplayback;
268 struct servmode
270 fpsserver &sv;
272 servmode(fpsserver &sv) : sv(sv) {}
273 virtual ~servmode() {}
275 virtual void entergame(clientinfo *ci) {}
276 virtual void leavegame(clientinfo *ci, bool disconnecting = false) {}
278 virtual void moved(clientinfo *ci, const vec &oldpos, const vec &newpos) {}
279 virtual bool canspawn(clientinfo *ci, bool connecting = false) { return true; }
280 virtual void spawned(clientinfo *ci) {}
281 virtual int fragvalue(clientinfo *victim, clientinfo *actor)
283 int gamemode = sv.gamemode;
284 if(victim==actor || isteam(victim->team, actor->team)) return -1;
285 return 1;
287 virtual void died(clientinfo *victim, clientinfo *actor) {}
288 virtual void changeteam(clientinfo *ci, const char *oldteam, const char *newteam) {}
289 virtual void initclient(clientinfo *ci, ucharbuf &p, bool connecting) {}
290 virtual void update() {}
291 virtual void reset(bool empty) {}
292 virtual void intermission() {}
295 struct arenaservmode : servmode
297 int arenaround;
299 arenaservmode(fpsserver &sv) : servmode(sv), arenaround(0) {}
301 bool canspawn(clientinfo *ci, bool connecting = false)
303 if(connecting && sv.nonspectators(ci->clientnum)<=1) return true;
304 return false;
307 void reset(bool empty)
309 arenaround = 0;
312 void update()
314 if(sv.interm || sv.gamemillis<arenaround || !sv.nonspectators()) return;
316 if(arenaround)
318 arenaround = 0;
319 loopv(sv.clients) if(sv.clients[i]->state.state==CS_DEAD || sv.clients[i]->state.state==CS_ALIVE)
321 sv.clients[i]->state.respawn();
322 sv.sendspawn(sv.clients[i]);
324 return;
327 int gamemode = sv.gamemode;
328 clientinfo *alive = NULL;
329 bool dead = false;
330 loopv(sv.clients)
332 clientinfo *ci = sv.clients[i];
333 if(ci->state.state==CS_ALIVE || (ci->state.state==CS_DEAD && ci->state.lastspawn>=0))
335 if(!alive) alive = ci;
336 else if(!m_teammode || strcmp(alive->team, ci->team)) return;
338 else if(ci->state.state==CS_DEAD) dead = true;
340 if(!dead) return;
341 sendf(-1, 1, "ri2", SV_ARENAWIN, !alive ? -1 : alive->clientnum);
342 arenaround = sv.gamemillis+5000;
346 #define CAPTURESERV 1
347 #include "capture.h"
348 #undef CAPTURESERV
350 #define ASSASSINSERV 1
351 #include "assassin.h"
352 #undef ASSASSINSERV
354 arenaservmode arenamode;
355 captureservmode capturemode;
356 assassinservmode assassinmode;
357 servmode *smode;
359 fpsserver() : notgotitems(true), notgotbases(false), gamemode(0), interm(0), minremain(0), mapreload(false), lastsend(0), mastermode(MM_OPEN), mastermask(MM_DEFAULT), currentmaster(-1), masterupdate(false), mapdata(NULL), reliablemessages(false), demonextmatch(false), demotmp(NULL), demorecord(NULL), demoplayback(NULL), nextplayback(0), arenamode(*this), capturemode(*this), assassinmode(*this), smode(NULL)
361 serverdesc[0] = '\0';
362 masterpass[0] = '\0';
365 void *newinfo() { return new clientinfo; }
366 void deleteinfo(void *ci) { delete (clientinfo *)ci; }
368 vector<server_entity> sents;
369 vector<savedscore> scores;
371 static const char *modestr(int n)
373 static const char *modenames[] =
375 "slowmo SP", "slowmo DMSP", "demo", "SP", "DMSP", "ffa/default", "coopedit", "ffa/duel", "teamplay",
376 "instagib", "instagib team", "efficiency", "efficiency team",
377 "insta arena", "insta clan arena", "tactics arena", "tactics clan arena",
378 "capture", "insta capture", "regen capture", "assassin", "insta assassin"
380 return (n>=-5 && size_t(n+5)<sizeof(modenames)/sizeof(modenames[0])) ? modenames[n+5] : "unknown";
383 void sendservmsg(const char *s) { sendf(-1, 1, "ris", SV_SERVMSG, s); }
385 void resetitems()
387 sents.setsize(0);
388 //cps.reset();
391 int spawntime(int type)
393 if(m_classicsp) return INT_MAX;
394 int np = nonspectators();
395 np = np<3 ? 4 : (np>4 ? 2 : 3); // spawn times are dependent on number of players
396 int sec = 0;
397 switch(type)
399 case I_SHELLS:
400 case I_BULLETS:
401 case I_ROCKETS:
402 case I_ROUNDS:
403 case I_GRENADES:
404 case I_CARTRIDGES: sec = np*4; break;
405 case I_HEALTH: sec = np*5; break;
406 case I_GREENARMOUR:
407 case I_YELLOWARMOUR: sec = 20; break;
408 case I_BOOST:
409 case I_QUAD: sec = 40+rnd(40); break;
411 return sec*1000;
414 bool pickup(int i, int sender) // server side item pickup, acknowledge first client that gets it
416 if(minremain<=0 || !sents.inrange(i) || !sents[i].spawned) return false;
417 clientinfo *ci = (clientinfo *)getinfo(sender);
418 if(!ci || (!ci->local && !ci->state.canpickup(sents[i].type))) return false;
419 sents[i].spawned = false;
420 sents[i].spawntime = spawntime(sents[i].type);
421 sendf(-1, 1, "ri3", SV_ITEMACC, i, sender);
422 ci->state.pickup(sents[i].type);
423 return true;
426 void vote(char *map, int reqmode, int sender)
428 clientinfo *ci = (clientinfo *)getinfo(sender);
429 if(!ci || ci->state.state==CS_SPECTATOR && !ci->privilege) return;
430 s_strcpy(ci->mapvote, map);
431 ci->modevote = reqmode;
432 if(!ci->mapvote[0]) return;
433 if(ci->local || mapreload || (ci->privilege && mastermode>=MM_VETO))
435 if(demorecord) enddemorecord();
436 if(!ci->local && !mapreload)
438 s_sprintfd(msg)("%s forced %s on map %s", privname(ci->privilege), modestr(reqmode), map);
439 sendservmsg(msg);
441 sendf(-1, 1, "risi", SV_MAPCHANGE, ci->mapvote, ci->modevote);
442 changemap(ci->mapvote, ci->modevote);
444 else
446 s_sprintfd(msg)("%s suggests %s on map %s (select map to vote)", colorname(ci), modestr(reqmode), map);
447 sendservmsg(msg);
448 checkvotes();
452 clientinfo *choosebestclient(float &bestrank)
454 clientinfo *best = NULL;
455 bestrank = -1;
456 loopv(clients)
458 clientinfo *ci = clients[i];
459 if(ci->state.timeplayed<0) continue;
460 float rank = ci->state.effectiveness/max(ci->state.timeplayed, 1);
461 if(!best || rank > bestrank) { best = ci; bestrank = rank; }
463 return best;
466 void autoteam()
468 static const char *teamnames[2] = {"good", "evil"};
469 vector<clientinfo *> team[2];
470 float teamrank[2] = {0, 0};
471 for(int round = 0, remaining = clients.length(); remaining>=0; round++)
473 int first = round&1, second = (round+1)&1, selected = 0;
474 while(teamrank[first] <= teamrank[second])
476 float rank;
477 clientinfo *ci = choosebestclient(rank);
478 if(!ci) break;
479 if(m_capture) rank = 1;
480 else if(selected && rank<=0) break;
481 ci->state.timeplayed = -1;
482 team[first].add(ci);
483 teamrank[first] += rank;
484 selected++;
485 if(rank<=0) break;
487 if(!selected) break;
488 remaining -= selected;
490 loopi(sizeof(team)/sizeof(team[0]))
492 loopvj(team[i])
494 clientinfo *ci = team[i][j];
495 if(!strcmp(ci->team, teamnames[i])) continue;
496 s_strncpy(ci->team, teamnames[i], MAXTEAMLEN+1);
497 sendf(-1, 1, "riis", SV_SETTEAM, ci->clientnum, teamnames[i]);
502 struct teamscore
504 const char *name;
505 float rank;
506 int clients;
508 teamscore(const char *name) : name(name), rank(0), clients(0) {}
511 const char *chooseworstteam(const char *suggest)
513 teamscore teamscores[2] = { teamscore("good"), teamscore("evil") };
514 const int numteams = sizeof(teamscores)/sizeof(teamscores[0]);
515 loopv(clients)
517 clientinfo *ci = clients[i];
518 if(!ci->team[0]) continue;
519 ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
520 ci->state.lasttimeplayed = lastmillis;
522 loopj(numteams) if(!strcmp(ci->team, teamscores[j].name))
524 teamscore &ts = teamscores[j];
525 ts.rank += ci->state.effectiveness/max(ci->state.timeplayed, 1);
526 ts.clients++;
527 break;
530 teamscore *worst = &teamscores[numteams-1];
531 loopi(numteams-1)
533 teamscore &ts = teamscores[i];
534 if(m_capture)
536 if(ts.clients < worst->clients || (ts.clients == worst->clients && ts.rank < worst->rank)) worst = &ts;
538 else if(ts.rank < worst->rank || (ts.rank == worst->rank && ts.clients < worst->clients)) worst = &ts;
540 return worst->name;
543 void writedemo(int chan, void *data, int len)
545 if(!demorecord) return;
546 int stamp[3] = { gamemillis, chan, len };
547 endianswap(stamp, sizeof(int), 3);
548 gzwrite(demorecord, stamp, sizeof(stamp));
549 gzwrite(demorecord, data, len);
552 void recordpacket(int chan, void *data, int len)
554 writedemo(chan, data, len);
557 void enddemorecord()
559 if(!demorecord) return;
561 gzclose(demorecord);
562 demorecord = NULL;
564 #ifdef WIN32
565 demotmp = fopen("demorecord", "rb");
566 #endif
567 if(!demotmp) return;
569 fseek(demotmp, 0, SEEK_END);
570 int len = ftell(demotmp);
571 rewind(demotmp);
572 if(demos.length()>=MAXDEMOS)
574 delete[] demos[0].data;
575 demos.remove(0);
577 demofile &d = demos.add();
578 time_t t = time(NULL);
579 char *timestr = ctime(&t), *trim = timestr + strlen(timestr);
580 while(trim>timestr && isspace(*--trim)) *trim = '\0';
581 s_sprintf(d.info)("%s: %s, %s, %.2f%s", timestr, modestr(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB");
582 s_sprintfd(msg)("demo \"%s\" recorded", d.info);
583 sendservmsg(msg);
584 d.data = new uchar[len];
585 d.len = len;
586 fread(d.data, 1, len, demotmp);
587 fclose(demotmp);
588 demotmp = NULL;
591 void setupdemorecord()
593 if(haslocalclients() || !m_mp(gamemode) || gamemode==1) return;
595 #ifdef WIN32
596 gzFile f = gzopen("demorecord", "wb9");
597 if(!f) return;
598 #else
599 demotmp = tmpfile();
600 if(!demotmp) return;
601 setvbuf(demotmp, NULL, _IONBF, 0);
603 gzFile f = gzdopen(_dup(_fileno(demotmp)), "wb9");
604 if(!f)
606 fclose(demotmp);
607 demotmp = NULL;
608 return;
610 #endif
612 sendservmsg("recording demo");
614 demorecord = f;
616 demoheader hdr;
617 memcpy(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic));
618 hdr.version = DEMO_VERSION;
619 hdr.protocol = PROTOCOL_VERSION;
620 endianswap(&hdr.version, sizeof(int), 1);
621 endianswap(&hdr.protocol, sizeof(int), 1);
622 gzwrite(demorecord, &hdr, sizeof(demoheader));
624 uchar buf[MAXTRANS];
625 ucharbuf p(buf, sizeof(buf));
626 welcomepacket(p, -1);
627 writedemo(1, buf, p.len);
629 loopv(clients)
631 clientinfo *ci = clients[i];
632 uchar header[16];
633 ucharbuf q(&buf[sizeof(header)], sizeof(buf)-sizeof(header));
634 putint(q, SV_INITC2S);
635 sendstring(ci->name, q);
636 sendstring(ci->team, q);
638 ucharbuf h(header, sizeof(header));
639 putint(h, SV_CLIENT);
640 putint(h, ci->clientnum);
641 putuint(h, q.len);
643 memcpy(&buf[sizeof(header)-h.len], header, h.len);
645 writedemo(1, &buf[sizeof(header)-h.len], h.len+q.len);
649 void listdemos(int cn)
651 ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
652 if(!packet) return;
653 ucharbuf p(packet->data, packet->dataLength);
654 putint(p, SV_SENDDEMOLIST);
655 putint(p, demos.length());
656 loopv(demos) sendstring(demos[i].info, p);
657 enet_packet_resize(packet, p.length());
658 sendpacket(cn, 1, packet);
659 if(!packet->referenceCount) enet_packet_destroy(packet);
662 void cleardemos(int n)
664 if(!n)
666 loopv(demos) delete[] demos[i].data;
667 demos.setsize(0);
668 sendservmsg("cleared all demos");
670 else if(demos.inrange(n-1))
672 delete[] demos[n-1].data;
673 demos.remove(n-1);
674 s_sprintfd(msg)("cleared demo %d", n);
675 sendservmsg(msg);
679 void senddemo(int cn, int num)
681 if(!num) num = demos.length();
682 if(!demos.inrange(num-1)) return;
683 demofile &d = demos[num-1];
684 sendf(cn, 2, "rim", SV_SENDDEMO, d.len, d.data);
687 void setupdemoplayback()
689 demoheader hdr;
690 string msg;
691 msg[0] = '\0';
692 s_sprintfd(file)("%s.dmo", smapname);
693 demoplayback = opengzfile(file, "rb9");
694 if(!demoplayback) s_sprintf(msg)("could not read demo \"%s\"", file);
695 else if(gzread(demoplayback, &hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)))
696 s_sprintf(msg)("\"%s\" is not a demo file", file);
697 else
699 endianswap(&hdr.version, sizeof(int), 1);
700 endianswap(&hdr.protocol, sizeof(int), 1);
701 if(hdr.version!=DEMO_VERSION) s_sprintf(msg)("demo \"%s\" requires an %s version of Sauerbraten", file, hdr.version<DEMO_VERSION ? "older" : "newer");
702 else if(hdr.protocol!=PROTOCOL_VERSION) s_sprintf(msg)("demo \"%s\" requires an %s version of Sauerbraten", file, hdr.protocol<PROTOCOL_VERSION ? "older" : "newer");
704 if(msg[0])
706 if(demoplayback) { gzclose(demoplayback); demoplayback = NULL; }
707 sendservmsg(msg);
708 return;
711 s_sprintf(msg)("playing demo \"%s\"", file);
712 sendservmsg(msg);
714 sendf(-1, 1, "rii", SV_DEMOPLAYBACK, 1);
716 if(gzread(demoplayback, &nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
718 enddemoplayback();
719 return;
721 endianswap(&nextplayback, sizeof(nextplayback), 1);
724 void enddemoplayback()
726 if(!demoplayback) return;
727 gzclose(demoplayback);
728 demoplayback = NULL;
730 sendf(-1, 1, "rii", SV_DEMOPLAYBACK, 0);
732 sendservmsg("demo playback finished");
734 loopv(clients)
736 ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
737 ucharbuf p(packet->data, packet->dataLength);
738 welcomepacket(p, clients[i]->clientnum);
739 enet_packet_resize(packet, p.length());
740 sendpacket(clients[i]->clientnum, 1, packet);
741 if(!packet->referenceCount) enet_packet_destroy(packet);
745 void readdemo()
747 if(!demoplayback) return;
748 while(gamemillis>=nextplayback)
750 int chan, len;
751 if(gzread(demoplayback, &chan, sizeof(chan))!=sizeof(chan) ||
752 gzread(demoplayback, &len, sizeof(len))!=sizeof(len))
754 enddemoplayback();
755 return;
757 endianswap(&chan, sizeof(chan), 1);
758 endianswap(&len, sizeof(len), 1);
759 ENetPacket *packet = enet_packet_create(NULL, len, 0);
760 if(!packet || gzread(demoplayback, packet->data, len)!=len)
762 if(packet) enet_packet_destroy(packet);
763 enddemoplayback();
764 return;
766 sendpacket(-1, chan, packet);
767 if(!packet->referenceCount) enet_packet_destroy(packet);
768 if(gzread(demoplayback, &nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
770 enddemoplayback();
771 return;
773 endianswap(&nextplayback, sizeof(nextplayback), 1);
777 void changemap(const char *s, int mode)
779 if(m_demo) enddemoplayback();
780 else enddemorecord();
782 mapreload = false;
783 gamemode = mode;
784 gamemillis = 0;
785 minremain = m_teammode ? 15 : 10;
786 gamelimit = minremain*60000;
787 interm = 0;
788 s_strcpy(smapname, s);
789 resetitems();
790 notgotitems = true;
791 scores.setsize(0);
792 loopv(clients)
794 clientinfo *ci = clients[i];
795 ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
797 if(m_teammode) autoteam();
799 if(m_arena) smode = &arenamode;
800 else if(m_capture) smode = &capturemode;
801 else if(m_assassin) smode = &assassinmode;
802 else smode = NULL;
803 if(smode) smode->reset(false);
805 if(gamemode>1 || (gamemode==0 && hasnonlocalclients())) sendf(-1, 1, "ri2", SV_TIMEUP, minremain);
806 loopv(clients)
808 clientinfo *ci = clients[i];
809 ci->mapchange();
810 ci->state.lasttimeplayed = lastmillis;
811 if(m_mp(gamemode) && ci->state.state!=CS_SPECTATOR) sendspawn(ci);
814 if(m_demo) setupdemoplayback();
815 else if(demonextmatch)
817 demonextmatch = false;
818 setupdemorecord();
822 savedscore &findscore(clientinfo *ci, bool insert)
824 uint ip = getclientip(ci->clientnum);
825 if(!ip) return *(savedscore *)0;
826 if(!insert) loopv(clients)
828 clientinfo *oi = clients[i];
829 if(oi->clientnum != ci->clientnum && getclientip(oi->clientnum) == ip && !strcmp(oi->name, ci->name))
831 oi->state.timeplayed += lastmillis - oi->state.lasttimeplayed;
832 oi->state.lasttimeplayed = lastmillis;
833 static savedscore curscore;
834 curscore.save(oi->state);
835 return curscore;
838 loopv(scores)
840 savedscore &sc = scores[i];
841 if(sc.ip == ip && !strcmp(sc.name, ci->name)) return sc;
843 if(!insert) return *(savedscore *)0;
844 savedscore &sc = scores.add();
845 sc.ip = ip;
846 s_strcpy(sc.name, ci->name);
847 return sc;
850 void savescore(clientinfo *ci)
852 savedscore &sc = findscore(ci, true);
853 if(&sc) sc.save(ci->state);
856 struct votecount
858 char *map;
859 int mode, count;
860 votecount() {}
861 votecount(char *s, int n) : map(s), mode(n), count(0) {}
864 void checkvotes(bool force = false)
866 vector<votecount> votes;
867 int maxvotes = 0;
868 loopv(clients)
870 clientinfo *oi = clients[i];
871 if(oi->state.state==CS_SPECTATOR && !oi->privilege) continue;
872 maxvotes++;
873 if(!oi->mapvote[0]) continue;
874 votecount *vc = NULL;
875 loopvj(votes) if(!strcmp(oi->mapvote, votes[j].map) && oi->modevote==votes[j].mode)
877 vc = &votes[j];
878 break;
880 if(!vc) vc = &votes.add(votecount(oi->mapvote, oi->modevote));
881 vc->count++;
883 votecount *best = NULL;
884 loopv(votes) if(!best || votes[i].count > best->count || (votes[i].count == best->count && rnd(2))) best = &votes[i];
885 if(force || (best && best->count > maxvotes/2))
887 if(demorecord) enddemorecord();
888 if(best && (best->count > (force ? 1 : maxvotes/2)))
890 sendservmsg(force ? "vote passed by default" : "vote passed by majority");
891 sendf(-1, 1, "risi", SV_MAPCHANGE, best->map, best->mode);
892 changemap(best->map, best->mode);
894 else
896 mapreload = true;
897 if(clients.length()) sendf(-1, 1, "ri", SV_MAPRELOAD);
902 int nonspectators(int exclude = -1)
904 int n = 0;
905 loopv(clients) if(i!=exclude && clients[i]->state.state!=CS_SPECTATOR) n++;
906 return n;
909 int checktype(int type, clientinfo *ci)
911 if(ci && ci->local) return type;
912 // spectators can only connect and talk
913 static int spectypes[] = { SV_INITC2S, SV_POS, SV_TEXT, SV_PING, SV_CLIENTPING, SV_GETMAP, SV_SETMASTER };
914 if(ci && ci->state.state==CS_SPECTATOR && !ci->privilege)
916 loopi(sizeof(spectypes)/sizeof(int)) if(type == spectypes[i]) return type;
917 return -1;
919 // only allow edit messages in coop-edit mode
920 if(type>=SV_EDITENT && type<=SV_GETMAP && gamemode!=1) return -1;
921 // server only messages
922 static int servtypes[] = { SV_INITS2C, SV_MAPRELOAD, SV_SERVMSG, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_DIED, SV_SPAWNSTATE, SV_FORCEDEATH, SV_ARENAWIN, SV_ITEMACC, SV_ITEMSPAWN, SV_TIMEUP, SV_CDIS, SV_CURRENTMASTER, SV_PONG, SV_RESUME, SV_TEAMSCORE, SV_BASEINFO, SV_BASEREGEN, SV_ANNOUNCE, SV_CLEARTARGETS, SV_CLEARHUNTERS, SV_ADDTARGET, SV_REMOVETARGET, SV_ADDHUNTER, SV_REMOVEHUNTER, SV_SENDDEMOLIST, SV_SENDDEMO, SV_DEMOPLAYBACK, SV_SENDMAP, SV_CLIENT };
923 if(ci) loopi(sizeof(servtypes)/sizeof(int)) if(type == servtypes[i]) return -1;
924 return type;
927 static void freecallback(ENetPacket *packet)
929 extern igameserver *sv;
930 ((fpsserver *)sv)->cleanworldstate(packet);
933 void cleanworldstate(ENetPacket *packet)
935 loopv(worldstates)
937 worldstate *ws = worldstates[i];
938 if(packet->data >= ws->positions.getbuf() && packet->data <= &ws->positions.last()) ws->uses--;
939 else if(packet->data >= ws->messages.getbuf() && packet->data <= &ws->messages.last()) ws->uses--;
940 else continue;
941 if(!ws->uses)
943 delete ws;
944 worldstates.remove(i);
946 break;
950 bool buildworldstate()
952 static struct { int posoff, msgoff, msglen; } pkt[MAXCLIENTS];
953 worldstate &ws = *new worldstate;
954 loopv(clients)
956 clientinfo &ci = *clients[i];
957 if(ci.position.empty()) pkt[i].posoff = -1;
958 else
960 pkt[i].posoff = ws.positions.length();
961 loopvj(ci.position) ws.positions.add(ci.position[j]);
963 if(ci.messages.empty()) pkt[i].msgoff = -1;
964 else
966 pkt[i].msgoff = ws.messages.length();
967 ucharbuf p = ws.messages.reserve(16);
968 putint(p, SV_CLIENT);
969 putint(p, ci.clientnum);
970 putuint(p, ci.messages.length());
971 ws.messages.addbuf(p);
972 loopvj(ci.messages) ws.messages.add(ci.messages[j]);
973 pkt[i].msglen = ws.messages.length() - pkt[i].msgoff;
976 int psize = ws.positions.length(), msize = ws.messages.length();
977 if(psize) recordpacket(0, ws.positions.getbuf(), psize);
978 if(msize) recordpacket(1, ws.messages.getbuf(), msize);
979 loopi(psize) { uchar c = ws.positions[i]; ws.positions.add(c); }
980 loopi(msize) { uchar c = ws.messages[i]; ws.messages.add(c); }
981 ws.uses = 0;
982 loopv(clients)
984 clientinfo &ci = *clients[i];
985 ENetPacket *packet;
986 if(psize && (pkt[i].posoff<0 || psize-ci.position.length()>0))
988 packet = enet_packet_create(&ws.positions[pkt[i].posoff<0 ? 0 : pkt[i].posoff+ci.position.length()],
989 pkt[i].posoff<0 ? psize : psize-ci.position.length(),
990 ENET_PACKET_FLAG_NO_ALLOCATE);
991 sendpacket(ci.clientnum, 0, packet);
992 if(!packet->referenceCount) enet_packet_destroy(packet);
993 else { ++ws.uses; packet->freeCallback = freecallback; }
995 ci.position.setsizenodelete(0);
997 if(msize && (pkt[i].msgoff<0 || msize-pkt[i].msglen>0))
999 packet = enet_packet_create(&ws.messages[pkt[i].msgoff<0 ? 0 : pkt[i].msgoff+pkt[i].msglen],
1000 pkt[i].msgoff<0 ? msize : msize-pkt[i].msglen,
1001 (reliablemessages ? ENET_PACKET_FLAG_RELIABLE : 0) | ENET_PACKET_FLAG_NO_ALLOCATE);
1002 sendpacket(ci.clientnum, 1, packet);
1003 if(!packet->referenceCount) enet_packet_destroy(packet);
1004 else { ++ws.uses; packet->freeCallback = freecallback; }
1006 ci.messages.setsizenodelete(0);
1008 reliablemessages = false;
1009 if(!ws.uses)
1011 delete &ws;
1012 return false;
1014 else
1016 worldstates.add(&ws);
1017 return true;
1021 bool sendpackets()
1023 if(clients.empty()) return false;
1024 enet_uint32 curtime = enet_time_get()-lastsend;
1025 if(curtime<33) return false;
1026 bool flush = buildworldstate();
1027 lastsend += curtime - (curtime%33);
1028 return flush;
1031 void parsepacket(int sender, int chan, bool reliable, ucharbuf &p) // has to parse exactly each byte of the packet
1033 if(sender<0) return;
1034 if(chan==2)
1036 receivefile(sender, p.buf, p.maxlen);
1037 return;
1039 if(reliable) reliablemessages = true;
1040 char text[MAXTRANS];
1041 int cn = -1, type;
1042 clientinfo *ci = sender>=0 ? (clientinfo *)getinfo(sender) : NULL;
1043 #define QUEUE_MSG { if(!ci->local) while(curmsg<p.length()) ci->messages.add(p.buf[curmsg++]); }
1044 #define QUEUE_INT(n) { if(!ci->local) { curmsg = p.length(); ucharbuf buf = ci->messages.reserve(5); putint(buf, n); ci->messages.addbuf(buf); } }
1045 #define QUEUE_UINT(n) { if(!ci->local) { curmsg = p.length(); ucharbuf buf = ci->messages.reserve(4); putuint(buf, n); ci->messages.addbuf(buf); } }
1046 #define QUEUE_STR(text) { if(!ci->local) { curmsg = p.length(); ucharbuf buf = ci->messages.reserve(2*strlen(text)+1); sendstring(text, buf); ci->messages.addbuf(buf); } }
1047 int curmsg;
1048 while((curmsg = p.length()) < p.maxlen) switch(type = checktype(getint(p), ci))
1050 case SV_POS:
1052 cn = getint(p);
1053 if(cn<0 || cn>=getnumclients() || cn!=sender)
1055 disconnect_client(sender, DISC_CN);
1056 return;
1058 vec oldpos(ci->state.o);
1059 loopi(3) ci->state.o[i] = getuint(p)/DMF;
1060 getuint(p);
1061 loopi(5) getint(p);
1062 int physstate = getuint(p);
1063 if(physstate&0x20) loopi(2) getint(p);
1064 if(physstate&0x10) getint(p);
1065 if(!ci->local && (ci->state.state==CS_ALIVE || ci->state.state==CS_EDITING))
1067 ci->position.setsizenodelete(0);
1068 while(curmsg<p.length()) ci->position.add(p.buf[curmsg++]);
1070 uint f = getuint(p);
1071 if(!ci->local && (ci->state.state==CS_ALIVE || ci->state.state==CS_EDITING))
1073 f &= 0xF;
1074 if(ci->state.armourtype==A_GREEN && ci->state.armour>0) f |= 1<<4;
1075 if(ci->state.armourtype==A_YELLOW && ci->state.armour>0) f |= 1<<5;
1076 if(ci->state.quadmillis) f |= 1<<6;
1077 if(ci->state.maxhealth>100) f |= ((ci->state.maxhealth-100)/itemstats[I_BOOST-I_SHELLS].add)<<7;
1078 curmsg = p.length();
1079 ucharbuf buf = ci->position.reserve(4);
1080 putuint(buf, f);
1081 ci->position.addbuf(buf);
1083 if(smode && ci->state.state==CS_ALIVE) smode->moved(ci, oldpos, ci->state.o);
1084 break;
1087 case SV_EDITMODE:
1089 int val = getint(p);
1090 if(ci->state.state!=(val ? CS_ALIVE : CS_EDITING) || (!ci->local && gamemode!=1)) break;
1091 if(smode)
1093 if(val) smode->leavegame(ci);
1094 else smode->entergame(ci);
1096 ci->state.state = val ? CS_EDITING : CS_ALIVE;
1097 if(val)
1099 ci->events.setsizenodelete(0);
1100 ci->state.rockets.reset();
1101 ci->state.grenades.reset();
1103 QUEUE_MSG;
1104 break;
1107 case SV_TRYSPAWN:
1108 if(ci->state.state!=CS_DEAD || ci->state.lastspawn>=0 || (smode && !smode->canspawn(ci))) break;
1109 if(ci->state.lastdeath) ci->state.respawn();
1110 sendspawn(ci);
1111 break;
1113 case SV_GUNSELECT:
1115 int gunselect = getint(p);
1116 ci->state.gunselect = gunselect;
1117 QUEUE_MSG;
1118 break;
1121 case SV_SPAWN:
1123 int ls = getint(p), gunselect = getint(p);
1124 if((ci->state.state!=CS_ALIVE && ci->state.state!=CS_DEAD) || ls!=ci->state.lifesequence || ci->state.lastspawn<0) break;
1125 ci->state.lastspawn = -1;
1126 ci->state.state = CS_ALIVE;
1127 ci->state.gunselect = gunselect;
1128 if(smode) smode->spawned(ci);
1129 QUEUE_MSG;
1130 break;
1133 case SV_SUICIDE:
1135 gameevent &suicide = ci->addevent();
1136 suicide.type = GE_SUICIDE;
1137 break;
1140 case SV_SHOOT:
1142 gameevent &shot = ci->addevent();
1143 shot.type = GE_SHOT;
1144 #define seteventmillis(event) \
1146 event.id = getint(p); \
1147 if(!ci->timesync || (ci->events.length()==1 && ci->state.waitexpired(gamemillis))) \
1149 ci->timesync = true; \
1150 ci->gameoffset = gamemillis - event.id; \
1151 event.millis = gamemillis; \
1153 else event.millis = ci->gameoffset + event.id; \
1155 seteventmillis(shot.shot);
1156 shot.shot.gun = getint(p);
1157 loopk(3) shot.shot.from[k] = getint(p)/DMF;
1158 loopk(3) shot.shot.to[k] = getint(p)/DMF;
1159 int hits = getint(p);
1160 loopk(hits)
1162 gameevent &hit = ci->addevent();
1163 hit.type = GE_HIT;
1164 hit.hit.target = getint(p);
1165 hit.hit.lifesequence = getint(p);
1166 hit.hit.rays = getint(p);
1167 loopk(3) hit.hit.dir[k] = getint(p)/DNF;
1169 break;
1172 case SV_EXPLODE:
1174 gameevent &exp = ci->addevent();
1175 exp.type = GE_EXPLODE;
1176 seteventmillis(exp.explode);
1177 exp.explode.gun = getint(p);
1178 exp.explode.id = getint(p);
1179 int hits = getint(p);
1180 loopk(hits)
1182 gameevent &hit = ci->addevent();
1183 hit.type = GE_HIT;
1184 hit.hit.target = getint(p);
1185 hit.hit.lifesequence = getint(p);
1186 hit.hit.dist = getint(p)/DMF;
1187 loopk(3) hit.hit.dir[k] = getint(p)/DNF;
1189 break;
1192 case SV_ITEMPICKUP:
1194 int n = getint(p);
1195 gameevent &pickup = ci->addevent();
1196 pickup.type = GE_PICKUP;
1197 pickup.pickup.ent = n;
1198 break;
1201 case SV_TEXT:
1202 QUEUE_MSG;
1203 getstring(text, p);
1204 filtertext(text, text);
1205 QUEUE_STR(text);
1206 break;
1208 case SV_INITC2S:
1210 QUEUE_MSG;
1211 bool connected = !ci->name[0];
1212 getstring(text, p);
1213 filtertext(text, text, false, MAXNAMELEN);
1214 if(!text[0]) s_strcpy(text, "unnamed");
1215 QUEUE_STR(text);
1216 s_strncpy(ci->name, text, MAXNAMELEN+1);
1217 if(!ci->local && connected)
1219 savedscore &sc = findscore(ci, false);
1220 if(&sc)
1222 sc.restore(ci->state);
1223 sendf(-1, 1, "ri8", SV_RESUME, sender, ci->state.state, ci->state.lifesequence, ci->state.gunselect, sc.maxhealth, sc.frags, -1);
1226 getstring(text, p);
1227 filtertext(text, text, false, MAXTEAMLEN);
1228 if(!ci->local && connected && m_teammode)
1230 const char *worst = chooseworstteam(text);
1231 if(worst)
1233 s_strcpy(text, worst);
1234 sendf(sender, 1, "riis", SV_SETTEAM, sender, worst);
1235 QUEUE_STR(worst);
1237 else QUEUE_STR(text);
1239 else QUEUE_STR(text);
1240 if(smode && ci->state.state==CS_ALIVE && strcmp(ci->team, text)) smode->changeteam(ci, ci->team, text);
1241 s_strncpy(ci->team, text, MAXTEAMLEN+1);
1242 QUEUE_MSG;
1243 break;
1246 case SV_MAPVOTE:
1247 case SV_MAPCHANGE:
1249 getstring(text, p);
1250 filtertext(text, text);
1251 int reqmode = getint(p);
1252 if(type!=SV_MAPVOTE && !mapreload) break;
1253 if(!ci->local && !m_mp(reqmode)) reqmode = 0;
1254 vote(text, reqmode, sender);
1255 break;
1258 case SV_ITEMLIST:
1260 int n;
1261 while((n = getint(p))!=-1)
1263 server_entity se = { getint(p), false, 0 };
1264 if(notgotitems)
1266 while(sents.length()<=n) sents.add(se);
1267 if(gamemode>=0 && (sents[n].type==I_QUAD || sents[n].type==I_BOOST)) sents[n].spawntime = spawntime(sents[n].type);
1268 else sents[n].spawned = true;
1271 notgotitems = false;
1272 break;
1275 case SV_TEAMSCORE:
1276 getstring(text, p);
1277 getint(p);
1278 QUEUE_MSG;
1279 break;
1281 case SV_BASEINFO:
1282 getint(p);
1283 getstring(text, p);
1284 getstring(text, p);
1285 getint(p);
1286 QUEUE_MSG;
1287 break;
1289 case SV_BASES:
1290 if(smode==&capturemode) capturemode.parsebases(p);
1291 break;
1293 case SV_REPAMMO:
1294 if(smode==&capturemode) capturemode.replenishammo(ci);
1295 break;
1297 case SV_PING:
1298 sendf(sender, 1, "i2", SV_PONG, getint(p));
1299 break;
1301 case SV_MASTERMODE:
1303 int mm = getint(p);
1304 if(ci->privilege && mm>=MM_OPEN && mm<=MM_PRIVATE)
1306 if(ci->privilege>=PRIV_ADMIN || (mastermask&(1<<mm)))
1308 mastermode = mm;
1309 s_sprintfd(s)("mastermode is now %d", mastermode);
1310 sendservmsg(s);
1312 else
1314 s_sprintfd(s)("mastermode %d is disabled on this server", mm);
1315 sendf(sender, 1, "ris", SV_SERVMSG, s);
1318 break;
1321 case SV_CLEARBANS:
1323 if(ci->privilege)
1325 bannedips.setsize(0);
1326 sendservmsg("cleared all bans");
1328 break;
1331 case SV_KICK:
1333 int victim = getint(p);
1334 if(ci->privilege && victim>=0 && victim<getnumclients() && ci->clientnum!=victim && getinfo(victim))
1336 ban &b = bannedips.add();
1337 b.time = totalmillis;
1338 b.ip = getclientip(victim);
1339 disconnect_client(victim, DISC_KICK);
1341 break;
1344 case SV_SPECTATOR:
1346 int spectator = getint(p), val = getint(p);
1347 if(!ci->privilege && spectator!=sender) break;
1348 clientinfo *spinfo = (clientinfo *)getinfo(spectator);
1349 if(!spinfo) break;
1351 sendf(-1, 1, "ri3", SV_SPECTATOR, spectator, val);
1353 if(spinfo->state.state!=CS_SPECTATOR && val)
1355 if(smode) smode->leavegame(spinfo);
1356 spinfo->state.state = CS_SPECTATOR;
1358 else if(spinfo->state.state==CS_SPECTATOR && !val)
1360 spinfo->state.state = CS_DEAD;
1361 spinfo->state.respawn();
1362 if(!smode || smode->canspawn(spinfo)) sendspawn(spinfo);
1364 break;
1367 case SV_SETTEAM:
1369 int who = getint(p);
1370 getstring(text, p);
1371 filtertext(text, text, false, MAXTEAMLEN);
1372 if(!ci->privilege || who<0 || who>=getnumclients()) break;
1373 clientinfo *wi = (clientinfo *)getinfo(who);
1374 if(!wi) break;
1375 if(smode && wi->state.state==CS_ALIVE && strcmp(wi->team, text)) smode->changeteam(wi, wi->team, text);
1376 s_strncpy(wi->team, text, MAXTEAMLEN+1);
1377 sendf(sender, 1, "riis", SV_SETTEAM, who, text);
1378 QUEUE_INT(SV_SETTEAM);
1379 QUEUE_INT(who);
1380 QUEUE_STR(text);
1381 break;
1384 case SV_FORCEINTERMISSION:
1385 if(m_sp) startintermission();
1386 break;
1388 case SV_RECORDDEMO:
1390 int val = getint(p);
1391 if(ci->privilege<PRIV_ADMIN) break;
1392 demonextmatch = val!=0;
1393 s_sprintfd(msg)("demo recording is %s for next match", demonextmatch ? "enabled" : "disabled");
1394 sendservmsg(msg);
1395 break;
1398 case SV_STOPDEMO:
1400 if(!ci->local && ci->privilege<PRIV_ADMIN) break;
1401 if(m_demo) enddemoplayback();
1402 else enddemorecord();
1403 break;
1406 case SV_CLEARDEMOS:
1408 int demo = getint(p);
1409 if(ci->privilege<PRIV_ADMIN) break;
1410 cleardemos(demo);
1411 break;
1414 case SV_LISTDEMOS:
1415 listdemos(sender);
1416 break;
1418 case SV_GETDEMO:
1419 senddemo(sender, getint(p));
1420 break;
1422 case SV_GETMAP:
1423 if(mapdata)
1425 sendf(sender, 1, "ris", SV_SERVMSG, "server sending map...");
1426 sendfile(sender, 2, mapdata, "ri", SV_SENDMAP);
1428 else sendf(sender, 1, "ris", SV_SERVMSG, "no map to send");
1429 break;
1431 case SV_NEWMAP:
1433 int size = getint(p);
1434 if(size>=0)
1436 smapname[0] = '\0';
1437 resetitems();
1438 notgotitems = false;
1439 if(smode) smode->reset(true);
1441 QUEUE_MSG;
1442 break;
1445 case SV_SETMASTER:
1447 int val = getint(p);
1448 getstring(text, p);
1449 setmaster(ci, val!=0, text);
1450 // don't broadcast the master password
1451 break;
1454 case SV_APPROVEMASTER:
1456 int mn = getint(p);
1457 if(mastermask&MM_AUTOAPPROVE) break;
1458 clientinfo *candidate = (clientinfo *)getinfo(mn);
1459 if(!candidate || !candidate->wantsmaster || mn==sender) break;// || getclientip(mn)==getclientip(sender)) break;
1460 setmaster(candidate, true, "", true);
1461 break;
1464 default:
1466 int size = msgsizelookup(type);
1467 if(size==-1) { disconnect_client(sender, DISC_TAGT); return; }
1468 if(size>0) loopi(size-1) getint(p);
1469 if(ci) QUEUE_MSG;
1470 break;
1475 int welcomepacket(ucharbuf &p, int n)
1477 clientinfo *ci = (clientinfo *)getinfo(n);
1478 int hasmap = (gamemode==1 && clients.length()>1) || (smapname[0] && (minremain>0 || (ci && ci->state.state==CS_SPECTATOR) || nonspectators(n)));
1479 putint(p, SV_INITS2C);
1480 putint(p, n);
1481 putint(p, PROTOCOL_VERSION);
1482 putint(p, hasmap);
1483 if(hasmap)
1485 putint(p, SV_MAPCHANGE);
1486 sendstring(smapname, p);
1487 putint(p, gamemode);
1488 if(!ci || gamemode>1 || (gamemode==0 && hasnonlocalclients()))
1490 putint(p, SV_TIMEUP);
1491 putint(p, minremain);
1493 putint(p, SV_ITEMLIST);
1494 loopv(sents) if(sents[i].spawned)
1496 putint(p, i);
1497 putint(p, sents[i].type);
1499 putint(p, -1);
1501 if(ci && (m_demo || m_mp(gamemode)) && ci->state.state!=CS_SPECTATOR)
1503 if(smode && !smode->canspawn(ci, true))
1505 ci->state.state = CS_DEAD;
1506 putint(p, SV_FORCEDEATH);
1507 putint(p, n);
1508 sendf(-1, 1, "ri2x", SV_FORCEDEATH, n, n);
1510 else
1512 gamestate &gs = ci->state;
1513 spawnstate(ci);
1514 putint(p, SV_SPAWNSTATE);
1515 putint(p, gs.lifesequence);
1516 putint(p, gs.health);
1517 putint(p, gs.maxhealth);
1518 putint(p, gs.armour);
1519 putint(p, gs.armourtype);
1520 putint(p, gs.gunselect);
1521 loopi(GUN_PISTOL-GUN_SG+1) putint(p, gs.ammo[GUN_SG+i]);
1522 gs.lastspawn = gamemillis;
1525 if(ci && ci->state.state==CS_SPECTATOR)
1527 putint(p, SV_SPECTATOR);
1528 putint(p, n);
1529 putint(p, 1);
1530 sendf(-1, 1, "ri3x", SV_SPECTATOR, n, 1, n);
1532 if(clients.length()>1)
1534 putint(p, SV_RESUME);
1535 loopv(clients)
1537 clientinfo *oi = clients[i];
1538 if(oi->clientnum==n) continue;
1539 putint(p, oi->clientnum);
1540 putint(p, oi->state.state);
1541 putint(p, oi->state.lifesequence);
1542 putint(p, oi->state.gunselect);
1543 putint(p, oi->state.maxhealth);
1544 putint(p, oi->state.frags);
1546 putint(p, -1);
1548 if(smode) smode->initclient(ci, p, true);
1549 return 1;
1552 void checkintermission()
1554 if(minremain>0)
1556 minremain = gamemillis>=gamelimit ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000;
1557 sendf(-1, 1, "ri2", SV_TIMEUP, minremain);
1558 if(!minremain && smode) smode->intermission();
1560 if(!interm && minremain<=0) interm = gamemillis+10000;
1563 void startintermission() { gamelimit = min(gamelimit, gamemillis); checkintermission(); }
1565 void clearevent(clientinfo *ci)
1567 int n = 1;
1568 while(n<ci->events.length() && ci->events[n].type==GE_HIT) n++;
1569 ci->events.remove(0, n);
1572 void spawnstate(clientinfo *ci)
1574 gamestate &gs = ci->state;
1575 gs.spawnstate(gamemode);
1576 gs.lifesequence++;
1579 void sendspawn(clientinfo *ci)
1581 gamestate &gs = ci->state;
1582 spawnstate(ci);
1583 sendf(ci->clientnum, 1, "ri7v", SV_SPAWNSTATE, gs.lifesequence,
1584 gs.health, gs.maxhealth,
1585 gs.armour, gs.armourtype,
1586 gs.gunselect, GUN_PISTOL-GUN_SG+1, &gs.ammo[GUN_SG]);
1587 gs.lastspawn = gamemillis;
1590 void dodamage(clientinfo *target, clientinfo *actor, int damage, int gun, const vec &hitpush = vec(0, 0, 0))
1592 gamestate &ts = target->state;
1593 ts.dodamage(damage);
1594 sendf(-1, 1, "ri6", SV_DAMAGE, target->clientnum, actor->clientnum, damage, ts.armour, ts.health);
1595 if(target!=actor && !hitpush.iszero())
1597 vec v(hitpush);
1598 if(!v.iszero()) v.normalize();
1599 sendf(target->clientnum, 1, "ri6", SV_HITPUSH, gun, damage,
1600 int(v.x*DNF), int(v.y*DNF), int(v.z*DNF));
1602 if(ts.health<=0)
1604 int fragvalue = smode ? smode->fragvalue(target, actor) : (target==actor || isteam(target->team, actor->team) ? -1 : 1);
1605 actor->state.frags += fragvalue;
1606 if(fragvalue>0)
1608 int friends = 0, enemies = 0; // note: friends also includes the fragger
1609 if(m_teammode) loopv(clients) if(strcmp(clients[i]->team, actor->team)) enemies++; else friends++;
1610 else { friends = 1; enemies = clients.length()-1; }
1611 actor->state.effectiveness += fragvalue*friends/float(max(enemies, 1));
1613 sendf(-1, 1, "ri4", SV_DIED, target->clientnum, actor->clientnum, actor->state.frags);
1614 target->position.setsizenodelete(0);
1615 if(smode) smode->died(target, actor);
1616 ts.state = CS_DEAD;
1617 ts.lastdeath = gamemillis;
1618 // don't issue respawn yet until DEATHMILLIS has elapsed
1619 // ts.respawn();
1623 void processevent(clientinfo *ci, suicideevent &e)
1625 gamestate &gs = ci->state;
1626 if(gs.state!=CS_ALIVE) return;
1627 ci->state.frags += smode ? smode->fragvalue(ci, ci) : -1;
1628 sendf(-1, 1, "ri4", SV_DIED, ci->clientnum, ci->clientnum, gs.frags);
1629 ci->position.setsizenodelete(0);
1630 if(smode) smode->died(ci, NULL);
1631 gs.state = CS_DEAD;
1632 gs.respawn();
1635 void processevent(clientinfo *ci, explodeevent &e)
1637 gamestate &gs = ci->state;
1638 switch(e.gun)
1640 case GUN_RL:
1641 if(!gs.rockets.remove(e.id)) return;
1642 break;
1644 case GUN_GL:
1645 if(!gs.grenades.remove(e.id)) return;
1646 break;
1648 default:
1649 return;
1651 for(int i = 1; i<ci->events.length() && ci->events[i].type==GE_HIT; i++)
1653 hitevent &h = ci->events[i].hit;
1654 clientinfo *target = (clientinfo *)getinfo(h.target);
1655 if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.dist<0 || h.dist>RL_DAMRAD) continue;
1657 int j = 1;
1658 for(j = 1; j<i; j++) if(ci->events[j].hit.target==h.target) break;
1659 if(j<i) continue;
1661 int damage = guns[e.gun].damage;
1662 if(gs.quadmillis) damage *= 4;
1663 damage = int(damage*(1-h.dist/RL_DISTSCALE/RL_DAMRAD));
1664 if(e.gun==GUN_RL && target==ci) damage /= RL_SELFDAMDIV;
1665 dodamage(target, ci, damage, e.gun, h.dir);
1669 void processevent(clientinfo *ci, shotevent &e)
1671 gamestate &gs = ci->state;
1672 int wait = e.millis - gs.lastshot;
1673 if(!gs.isalive(gamemillis) ||
1674 wait<gs.gunwait ||
1675 e.gun<GUN_FIST || e.gun>GUN_PISTOL ||
1676 gs.ammo[e.gun]<=0)
1677 return;
1678 if(e.gun!=GUN_FIST) gs.ammo[e.gun]--;
1679 gs.lastshot = e.millis;
1680 gs.gunwait = guns[e.gun].attackdelay;
1681 sendf(-1, 1, "ri9x", SV_SHOTFX, ci->clientnum, e.gun,
1682 int(e.from[0]*DMF), int(e.from[1]*DMF), int(e.from[2]*DMF),
1683 int(e.to[0]*DMF), int(e.to[1]*DMF), int(e.to[2]*DMF),
1684 ci->clientnum);
1685 switch(e.gun)
1687 case GUN_RL: gs.rockets.add(e.id); break;
1688 case GUN_GL: gs.grenades.add(e.id); break;
1689 default:
1691 int totalrays = 0, maxrays = e.gun==GUN_SG ? SGRAYS : 1;
1692 for(int i = 1; i<ci->events.length() && ci->events[i].type==GE_HIT; i++)
1694 hitevent &h = ci->events[i].hit;
1695 clientinfo *target = (clientinfo *)getinfo(h.target);
1696 if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.rays<1) continue;
1698 totalrays += h.rays;
1699 if(totalrays>maxrays) continue;
1700 int damage = h.rays*guns[e.gun].damage;
1701 if(gs.quadmillis) damage *= 4;
1702 dodamage(target, ci, damage, e.gun, h.dir);
1704 break;
1709 void processevent(clientinfo *ci, pickupevent &e)
1711 gamestate &gs = ci->state;
1712 if(m_mp(gamemode) && !gs.isalive(gamemillis)) return;
1713 pickup(e.ent, ci->clientnum);
1716 void processevents()
1718 loopv(clients)
1720 clientinfo *ci = clients[i];
1721 if(curtime>0 && ci->state.quadmillis) ci->state.quadmillis = max(ci->state.quadmillis-curtime, 0);
1722 while(ci->events.length())
1724 gameevent &e = ci->events[0];
1725 if(e.type<GE_SUICIDE)
1727 if(e.shot.millis>gamemillis) break;
1728 if(e.shot.millis<ci->lastevent) { clearevent(ci); continue; }
1729 ci->lastevent = e.shot.millis;
1731 switch(e.type)
1733 case GE_SHOT: processevent(ci, e.shot); break;
1734 case GE_EXPLODE: processevent(ci, e.explode); break;
1735 // untimed events
1736 case GE_SUICIDE: processevent(ci, e.suicide); break;
1737 case GE_PICKUP: processevent(ci, e.pickup); break;
1739 clearevent(ci);
1744 void serverupdate(int _lastmillis, int _totalmillis)
1746 curtime = _lastmillis - lastmillis;
1747 gamemillis += curtime;
1748 lastmillis = _lastmillis;
1749 totalmillis = _totalmillis;
1751 if(m_demo) readdemo();
1752 else if(minremain>0)
1754 processevents();
1755 if(curtime) loopv(sents) if(sents[i].spawntime) // spawn entities when timer reached
1757 int oldtime = sents[i].spawntime;
1758 sents[i].spawntime -= curtime;
1759 if(sents[i].spawntime<=0)
1761 sents[i].spawntime = 0;
1762 sents[i].spawned = true;
1763 sendf(-1, 1, "ri2", SV_ITEMSPAWN, i);
1765 else if(sents[i].spawntime<=10000 && oldtime>10000 && (sents[i].type==I_QUAD || sents[i].type==I_BOOST))
1767 sendf(-1, 1, "ri2", SV_ANNOUNCE, sents[i].type);
1770 if(smode) smode->update();
1773 while(bannedips.length() && bannedips[0].time-totalmillis>4*60*60000) bannedips.remove(0);
1775 if(masterupdate)
1777 clientinfo *m = currentmaster>=0 ? (clientinfo *)getinfo(currentmaster) : NULL;
1778 sendf(-1, 1, "ri3", SV_CURRENTMASTER, currentmaster, m ? m->privilege : 0);
1779 masterupdate = false;
1782 if((gamemode>1 || (gamemode==0 && hasnonlocalclients())) && gamemillis-curtime>0 && gamemillis/60000!=(gamemillis-curtime)/60000) checkintermission();
1783 if(interm && gamemillis>interm)
1785 if(demorecord) enddemorecord();
1786 interm = 0;
1787 checkvotes(true);
1791 bool serveroption(char *arg)
1793 if(arg[0]=='-') switch(arg[1])
1795 case 'n': s_strcpy(serverdesc, &arg[2]); return true;
1796 case 'p': s_strcpy(masterpass, &arg[2]); return true;
1797 case 'o': if(atoi(&arg[2])) mastermask = (1<<MM_OPEN) | (1<<MM_VETO); return true;
1799 return false;
1802 void serverinit()
1804 smapname[0] = '\0';
1805 resetitems();
1808 const char *privname(int type)
1810 switch(type)
1812 case PRIV_ADMIN: return "admin";
1813 case PRIV_MASTER: return "master";
1814 default: return "unknown";
1818 void setmaster(clientinfo *ci, bool val, const char *pass = "", bool approved = false)
1820 if(approved && (!val || !ci->wantsmaster)) return;
1821 const char *name = "";
1822 if(val)
1824 if(ci->privilege)
1826 if(!masterpass[0] || !pass[0]==(ci->privilege!=PRIV_ADMIN)) return;
1828 else if(ci->state.state==CS_SPECTATOR && (!masterpass[0] || strcmp(masterpass, pass))) return;
1829 loopv(clients) if(ci!=clients[i] && clients[i]->privilege)
1831 if(masterpass[0] && !strcmp(masterpass, pass)) clients[i]->privilege = PRIV_NONE;
1832 else return;
1834 if(masterpass[0] && !strcmp(masterpass, pass)) ci->privilege = PRIV_ADMIN;
1835 else if(!approved && !(mastermask&MM_AUTOAPPROVE) && !ci->privilege)
1837 ci->wantsmaster = true;
1838 s_sprintfd(msg)("%s wants master. Type \"/approve %d\" to approve.", colorname(ci), ci->clientnum);
1839 sendservmsg(msg);
1840 return;
1842 else ci->privilege = PRIV_MASTER;
1843 name = privname(ci->privilege);
1845 else
1847 if(!ci->privilege) return;
1848 name = privname(ci->privilege);
1849 ci->privilege = 0;
1851 mastermode = MM_OPEN;
1852 s_sprintfd(msg)("%s %s %s", colorname(ci), val ? (approved ? "approved for" : "claimed") : "relinquished", name);
1853 sendservmsg(msg);
1854 currentmaster = val ? ci->clientnum : -1;
1855 masterupdate = true;
1856 loopv(clients) clients[i]->wantsmaster = false;
1859 void localconnect(int n)
1861 clientinfo *ci = (clientinfo *)getinfo(n);
1862 ci->clientnum = n;
1863 ci->local = true;
1864 clients.add(ci);
1867 void localdisconnect(int n)
1869 clientinfo *ci = (clientinfo *)getinfo(n);
1870 if(smode) smode->leavegame(ci, true);
1871 clients.removeobj(ci);
1874 int clientconnect(int n, uint ip)
1876 clientinfo *ci = (clientinfo *)getinfo(n);
1877 ci->clientnum = n;
1878 clients.add(ci);
1879 loopv(bannedips) if(bannedips[i].ip==ip) return DISC_IPBAN;
1880 if(mastermode>=MM_PRIVATE) return DISC_PRIVATE;
1881 if(mastermode>=MM_LOCKED) ci->state.state = CS_SPECTATOR;
1882 if(currentmaster>=0) masterupdate = true;
1883 ci->state.lasttimeplayed = lastmillis;
1884 return DISC_NONE;
1887 void clientdisconnect(int n)
1889 clientinfo *ci = (clientinfo *)getinfo(n);
1890 if(ci->privilege) setmaster(ci, false);
1891 if(smode) smode->leavegame(ci, true);
1892 ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
1893 savescore(ci);
1894 sendf(-1, 1, "ri2", SV_CDIS, n);
1895 clients.removeobj(ci);
1896 if(clients.empty()) bannedips.setsize(0); // bans clear when server empties
1897 else checkvotes();
1900 const char *servername() { return "sauerbratenserver"; }
1901 int serverinfoport() { return SAUERBRATEN_SERVINFO_PORT; }
1902 int serverport() { return SAUERBRATEN_SERVER_PORT; }
1903 const char *getdefaultmaster() { return "sauerbraten.org/masterserver/"; }
1905 void serverinforeply(ucharbuf &p)
1907 putint(p, clients.length());
1908 putint(p, 5); // number of attrs following
1909 putint(p, PROTOCOL_VERSION); // a // generic attributes, passed back below
1910 putint(p, gamemode); // b
1911 putint(p, minremain); // c
1912 putint(p, maxclients);
1913 putint(p, mastermode);
1914 sendstring(smapname, p);
1915 sendstring(serverdesc, p);
1918 bool servercompatible(char *name, char *sdec, char *map, int ping, const vector<int> &attr, int np)
1920 return attr.length() && attr[0]==PROTOCOL_VERSION;
1923 void serverinfostr(char *buf, const char *name, const char *sdesc, const char *map, int ping, const vector<int> &attr, int np)
1925 if(attr[0]!=PROTOCOL_VERSION) s_sprintf(buf)("[%s protocol] %s", attr[0]<PROTOCOL_VERSION ? "older" : "newer", name);
1926 else
1928 string numcl;
1929 if(attr.length()>=4) s_sprintf(numcl)("%d/%d", np, attr[3]);
1930 else s_sprintf(numcl)("%d", np);
1931 if(attr.length()>=5) switch(attr[4])
1933 case MM_LOCKED: s_strcat(numcl, " L"); break;
1934 case MM_PRIVATE: s_strcat(numcl, " P"); break;
1937 s_sprintf(buf)("%d\t%s\t%s, %s: %s %s", ping, numcl, map[0] ? map : "[unknown]", modestr(attr[1]), name, sdesc);
1941 void receivefile(int sender, uchar *data, int len)
1943 if(gamemode != 1 || len > 1024*1024) return;
1944 clientinfo *ci = (clientinfo *)getinfo(sender);
1945 if(ci->state.state==CS_SPECTATOR && !ci->privilege) return;
1946 if(mapdata) { fclose(mapdata); mapdata = NULL; }
1947 if(!len) return;
1948 mapdata = tmpfile();
1949 if(!mapdata) return;
1950 fwrite(data, 1, len, mapdata);
1951 s_sprintfd(msg)("[%s uploaded map to server, \"/getmap\" to receive it]", colorname(ci));
1952 sendservmsg(msg);
1955 bool duplicatename(clientinfo *ci, char *name)
1957 if(!name) name = ci->name;
1958 loopv(clients) if(clients[i]!=ci && !strcmp(name, clients[i]->name)) return true;
1959 return false;
1962 char *colorname(clientinfo *ci, char *name = NULL)
1964 if(!name) name = ci->name;
1965 if(name[0] && !duplicatename(ci, name)) return name;
1966 static string cname;
1967 s_sprintf(cname)("%s \fs\f5(%d)\fr", name, ci->clientnum);
1968 return cname;