rendered paste body#ifdef WIN32#include <io.h>#else#include <unistd.h>#define _dup dup#define _fileno fileno#endifstruct fpsserver : igameserver{ struct server_entity // server side version of "entity" type { int type; int spawntime; char spawned; }; static const int DEATHMILLIS = 300; enum { GE_NONE = 0, GE_SHOT, GE_EXPLODE, GE_HIT, GE_SUICIDE, GE_PICKUP }; struct shotevent { int type; int millis, id; int gun; float from[3], to[3]; }; struct explodeevent { int type; int millis, id; int gun; }; struct hitevent { int type; int target; int lifesequence; union { int rays; float dist; }; float dir[3]; }; struct suicideevent { int type; }; struct pickupevent { int type; int ent; }; union gameevent { int type; shotevent shot; explodeevent explode; hitevent hit; suicideevent suicide; pickupevent pickup; }; template <int N> struct projectilestate { int projs[N]; int numprojs; projectilestate() : numprojs(0) {} void reset() { numprojs = 0; } void add(int val) { if(numprojs>=N) numprojs = 0; projs[numprojs++] = val; } bool remove(int val) { loopi(numprojs) if(projs[i]==val) { projs[i] = projs[--numprojs]; return true; } return false; } }; struct gamestate : fpsstate { vec o; int state; int lastdeath, lastspawn, lifesequence; int lastshot; projectilestate<8> rockets, grenades; int frags; int lasttimeplayed, timeplayed; float effectiveness; gamestate() : state(CS_DEAD) {} bool isalive(int gamemillis) { return state==CS_ALIVE || (state==CS_DEAD && gamemillis - lastdeath <= DEATHMILLIS); } bool waitexpired(int gamemillis) { return gamemillis - lastshot >= gunwait; } void reset() { if(state!=CS_SPECTATOR) state = CS_DEAD; lifesequence = 0; maxhealth = 100; rockets.reset(); grenades.reset(); timeplayed = 0; effectiveness = 0; frags = 0; respawn(); } void respawn() { fpsstate::respawn(); o = vec(-1e10f, -1e10f, -1e10f); lastdeath = 0; lastspawn = -1; lastshot = 0; } }; struct savedscore { uint ip; string name; int maxhealth, frags; int timeplayed; float effectiveness; void save(gamestate &gs) { maxhealth = gs.maxhealth; frags = gs.frags; timeplayed = gs.timeplayed; effectiveness = gs.effectiveness; } void restore(gamestate &gs) { gs.maxhealth = maxhealth; gs.frags = frags; gs.timeplayed = timeplayed; gs.effectiveness = effectiveness; } }; struct clientinfo { int clientnum; string name, team, mapvote; int modevote; int privilege; bool spectator, local, timesync, wantsmaster; int gameoffset, lastevent; gamestate state; vector<gameevent> events; vector<uchar> position, messages; vector<clientinfo *> targets; clientinfo() { reset(); } gameevent &addevent() { static gameevent dummy; if(state.state==CS_SPECTATOR || events.length()>100) return dummy; return events.add(); } void mapchange() { mapvote[0] = 0; state.reset(); events.setsizenodelete(0); targets.setsizenodelete(0); timesync = false; lastevent = 0; } void reset() { name[0] = team[0] = 0; privilege = PRIV_NONE; spectator = local = wantsmaster = false; position.setsizenodelete(0); messages.setsizenodelete(0); mapchange(); } }; struct worldstate { int uses; vector<uchar> positions, messages; }; struct ban { int time; uint ip; }; #define MM_MODE 0xF #define MM_AUTOAPPROVE 0x1000 #define MM_DEFAULT (MM_MODE | MM_AUTOAPPROVE) enum { MM_OPEN = 0, MM_VETO, MM_LOCKED, MM_PRIVATE }; bool notgotitems, notgotbases; // true when map has changed and waiting for clients to send item int gamemode; int gamemillis, gamelimit; string serverdesc; string smapname; int lastmillis, totalmillis, curtime; int interm, minremain; bool mapreload; enet_uint32 lastsend; int mastermode, mastermask; int currentmaster; bool masterupdate; string masterpass; FILE *mapdata; vector<ban> bannedips; vector<clientinfo *> clients; vector<worldstate *> worldstates; bool reliablemessages; struct demofile { string info; uchar *data; int len; }; #define MAXDEMOS 5 vector<demofile> demos; bool demonextmatch; FILE *demotmp; gzFile demorecord, demoplayback; int nextplayback; struct servmode { fpsserver &sv; servmode(fpsserver &sv) : sv(sv) {} virtual ~servmode() {} virtual void entergame(clientinfo *ci) {} virtual void leavegame(clientinfo *ci, bool disconnecting = false) {} virtual void moved(clientinfo *ci, const vec &oldpos, const vec &newpos) {} virtual bool canspawn(clientinfo *ci, bool connecting = false) { return true; } virtual void spawned(clientinfo *ci) {} virtual int fragvalue(clientinfo *victim, clientinfo *actor) { int gamemode = sv.gamemode; if(victim==actor || isteam(victim->team, actor->team)) return -1; return 1; } virtual void died(clientinfo *victim, clientinfo *actor) {} virtual void changeteam(clientinfo *ci, const char *oldteam, const char *newteam) {} virtual void initclient(clientinfo *ci, ucharbuf &p, bool connecting) {} virtual void update() {} virtual void reset(bool empty) {} virtual void intermission() {} }; struct arenaservmode : servmode { int arenaround; arenaservmode(fpsserver &sv) : servmode(sv), arenaround(0) {} bool canspawn(clientinfo *ci, bool connecting = false) { if(connecting && sv.nonspectators(ci->clientnum)<=1) return true; return false; } void reset(bool empty) { arenaround = 0; } void update() { if(sv.interm || sv.gamemillis<arenaround || !sv.nonspectators()) return; if(arenaround) { arenaround = 0; loopv(sv.clients) if(sv.clients[i]->state.state==CS_DEAD || sv.clients[i]->state.state==CS_ALIVE) { sv.clients[i]->state.respawn(); sv.sendspawn(sv.clients[i]); } return; } int gamemode = sv.gamemode; clientinfo *alive = NULL; bool dead = false; loopv(sv.clients) { clientinfo *ci = sv.clients[i]; if(ci->state.state==CS_ALIVE || (ci->state.state==CS_DEAD && ci->state.lastspawn>=0)) { if(!alive) alive = ci; else if(!m_teammode || strcmp(alive->team, ci->team)) return; } else if(ci->state.state==CS_DEAD) dead = true; } if(!dead) return; sendf(-1, 1, "ri2", SV_ARENAWIN, !alive ? -1 : alive->clientnum); arenaround = sv.gamemillis+5000; } }; #define CAPTURESERV 1 #include "capture.h" #undef CAPTURESERV #define ASSASSINSERV 1 #include "assassin.h" #undef ASSASSINSERV arenaservmode arenamode; captureservmode capturemode; assassinservmode assassinmode; servmode *smode; 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) { serverdesc[0] = '\0'; masterpass[0] = '\0'; } void *newinfo() { return new clientinfo; } void deleteinfo(void *ci) { delete (clientinfo *)ci; } vector<server_entity> sents; vector<savedscore> scores; static const char *modestr(int n) { static const char *modenames[] = { "slowmo SP", "slowmo DMSP", "demo", "SP", "DMSP", "ffa/default", "coopedit", "ffa/duel", "teamplay", "instagib", "instagib team", "efficiency", "efficiency team", "insta arena", "insta clan arena", "tactics arena", "tactics clan arena", "capture", "insta capture", "regen capture", "assassin", "insta assassin" }; return (n>=-5 && size_t(n+5)<sizeof(modenames)/sizeof(modenames[0])) ? modenames[n+5] : "unknown"; } void sendservmsg(const char *s) { sendf(-1, 1, "ris", SV_SERVMSG, s); } void resetitems() { sents.setsize(0); //cps.reset(); } int spawntime(int type) { if(m_classicsp) return INT_MAX; int np = nonspectators(); np = np<3 ? 4 : (np>4 ? 2 : 3); // spawn times are dependent on number of players int sec = 0; switch(type) { case I_SHELLS: case I_BULLETS: case I_ROCKETS: case I_ROUNDS: case I_GRENADES: case I_CARTRIDGES: sec = np*4; break; case I_HEALTH: sec = np*5; break; case I_GREENARMOUR: case I_YELLOWARMOUR: sec = 20; break; case I_BOOST: case I_QUAD: sec = 40+rnd(40); break; } return sec*1000; } bool pickup(int i, int sender) // server side item pickup, acknowledge first client that gets it { if(minremain<=0 || !sents.inrange(i) || !sents[i].spawned) return false; clientinfo *ci = (clientinfo *)getinfo(sender); if(!ci || (!ci->local && !ci->state.canpickup(sents[i].type))) return false; sents[i].spawned = false; sents[i].spawntime = spawntime(sents[i].type); sendf(-1, 1, "ri3", SV_ITEMACC, i, sender); ci->state.pickup(sents[i].type); return true; } void vote(char *map, int reqmode, int sender) { clientinfo *ci = (clientinfo *)getinfo(sender); if(!ci || (ci->state.state==CS_SPECTATOR && !ci->privilege)) return; s_strcpy(ci->mapvote, map); ci->modevote = reqmode; if(!ci->mapvote[0]) return; if(ci->local || mapreload || (ci->privilege && mastermode>=MM_VETO)) { if(demorecord) enddemorecord(); if(!ci->local && !mapreload) { s_sprintfd(msg)("%s forced %s on map %s", privname(ci->privilege), modestr(reqmode), map); sendservmsg(msg); } sendf(-1, 1, "risi", SV_MAPCHANGE, ci->mapvote, ci->modevote); changemap(ci->mapvote, ci->modevote); } else { s_sprintfd(msg)("%s suggests %s on map %s (select map to vote)", colorname(ci), modestr(reqmode), map); sendservmsg(msg); checkvotes(); } } clientinfo *choosebestclient(float &bestrank) { clientinfo *best = NULL; bestrank = -1; loopv(clients) { clientinfo *ci = clients[i]; if(ci->state.timeplayed<0) continue; float rank = ci->state.effectiveness/max(ci->state.timeplayed, 1); if(!best || rank > bestrank) { best = ci; bestrank = rank; } } return best; } void autoteam() { static const char *teamnames[2] = {"good", "evil"}; vector<clientinfo *> team[2]; float teamrank[2] = {0, 0}; for(int round = 0, remaining = clients.length(); remaining>=0; round++) { int first = round&1, second = (round+1)&1, selected = 0; while(teamrank[first] <= teamrank[second]) { float rank; clientinfo *ci = choosebestclient(rank); if(!ci) break; if(m_capture) rank = 1; else if(selected && rank<=0) break; ci->state.timeplayed = -1; team[first].add(ci); teamrank[first] += rank; selected++; if(rank<=0) break; } if(!selected) break; remaining -= selected; } loopi(sizeof(team)/sizeof(team[0])) { loopvj(team[i]) { clientinfo *ci = team[i][j]; if(!strcmp(ci->team, teamnames[i])) continue; s_strncpy(ci->team, teamnames[i], MAXTEAMLEN+1); sendf(-1, 1, "riis", SV_SETTEAM, ci->clientnum, teamnames[i]); } } } struct teamscore { const char *name; float rank; int clients; teamscore(const char *name) : name(name), rank(0), clients(0) {} }; const char *chooseworstteam(const char *suggest) { teamscore teamscores[2] = { teamscore("good"), teamscore("evil") }; const int numteams = sizeof(teamscores)/sizeof(teamscores[0]); loopv(clients) { clientinfo *ci = clients[i]; if(!ci->team[0]) continue; ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; ci->state.lasttimeplayed = lastmillis; loopj(numteams) if(!strcmp(ci->team, teamscores[j].name)) { teamscore &ts = teamscores[j]; ts.rank += ci->state.effectiveness/max(ci->state.timeplayed, 1); ts.clients++; break; } } teamscore *worst = &teamscores[numteams-1]; loopi(numteams-1) { teamscore &ts = teamscores[i]; if(m_capture) { if(ts.clients < worst->clients || (ts.clients == worst->clients && ts.rank < worst->rank)) worst = &ts; } else if(ts.rank < worst->rank || (ts.rank == worst->rank && ts.clients < worst->clients)) worst = &ts; } return worst->name; } void writedemo(int chan, void *data, int len) { if(!demorecord) return; int stamp[3] = { gamemillis, chan, len }; endianswap(stamp, sizeof(int), 3); gzwrite(demorecord, stamp, sizeof(stamp)); gzwrite(demorecord, data, len); } void recordpacket(int chan, void *data, int len) { writedemo(chan, data, len); } void enddemorecord() { if(!demorecord) return; gzclose(demorecord); demorecord = NULL;#ifdef WIN32 demotmp = fopen("demorecord", "rb");#endif if(!demotmp) return; fseek(demotmp, 0, SEEK_END); int len = ftell(demotmp); rewind(demotmp); if(demos.length()>=MAXDEMOS) { delete[] demos[0].data; demos.remove(0); } demofile &d = demos.add(); time_t t = time(NULL); char *timestr = ctime(&t), *trim = timestr + strlen(timestr); while(trim>timestr && isspace(*--trim)) *trim = '\0'; 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"); s_sprintfd(msg)("demo \"%s\" recorded", d.info); sendservmsg(msg); d.data = new uchar[len]; d.len = len; fread(d.data, 1, len, demotmp); fclose(demotmp); demotmp = NULL; } void setupdemorecord() { if(haslocalclients() || !m_mp(gamemode) || gamemode==1) return;#ifdef WIN32 gzFile f = gzopen("demorecord", "wb9"); if(!f) return;#else demotmp = tmpfile(); if(!demotmp) return; setvbuf(demotmp, NULL, _IONBF, 0); gzFile f = gzdopen(_dup(_fileno(demotmp)), "wb9"); if(!f) { fclose(demotmp); demotmp = NULL; return; }#endif sendservmsg("recording demo"); demorecord = f; demoheader hdr; memcpy(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)); hdr.version = DEMO_VERSION; hdr.protocol = PROTOCOL_VERSION; endianswap(&hdr.version, sizeof(int), 1); endianswap(&hdr.protocol, sizeof(int), 1); gzwrite(demorecord, &hdr, sizeof(demoheader)); uchar buf[MAXTRANS]; ucharbuf p(buf, sizeof(buf)); welcomepacket(p, -1); writedemo(1, buf, p.len); loopv(clients) { clientinfo *ci = clients[i]; uchar header[16]; ucharbuf q(&buf[sizeof(header)], sizeof(buf)-sizeof(header)); putint(q, SV_INITC2S); sendstring(ci->name, q); sendstring(ci->team, q); ucharbuf h(header, sizeof(header)); putint(h, SV_CLIENT); putint(h, ci->clientnum); putuint(h, q.len); memcpy(&buf[sizeof(header)-h.len], header, h.len); writedemo(1, &buf[sizeof(header)-h.len], h.len+q.len); } } void listdemos(int cn) { ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); if(!packet) return; ucharbuf p(packet->data, packet->dataLength); putint(p, SV_SENDDEMOLIST); putint(p, demos.length()); loopv(demos) sendstring(demos[i].info, p); enet_packet_resize(packet, p.length()); sendpacket(cn, 1, packet); if(!packet->referenceCount) enet_packet_destroy(packet); } void cleardemos(int n) { if(!n) { loopv(demos) delete[] demos[i].data; demos.setsize(0); sendservmsg("cleared all demos"); } else if(demos.inrange(n-1)) { delete[] demos[n-1].data; demos.remove(n-1); s_sprintfd(msg)("cleared demo %d", n); sendservmsg(msg); } } void senddemo(int cn, int num) { if(!num) num = demos.length(); if(!demos.inrange(num-1)) return; demofile &d = demos[num-1]; sendf(cn, 2, "rim", SV_SENDDEMO, d.len, d.data); } void setupdemoplayback() { demoheader hdr; string msg; msg[0] = '\0'; s_sprintfd(file)("%s.dmo", smapname); demoplayback = opengzfile(file, "rb9"); if(!demoplayback) s_sprintf(msg)("could not read demo \"%s\"", file); else if(gzread(demoplayback, &hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic))) s_sprintf(msg)("\"%s\" is not a demo file", file); else { endianswap(&hdr.version, sizeof(int), 1); endianswap(&hdr.protocol, sizeof(int), 1); if(hdr.version!=DEMO_VERSION) s_sprintf(msg)("demo \"%s\" requires an %s version of Sauerbraten", file, hdr.version<DEMO_VERSION ? "older" : "newer"); else if(hdr.protocol!=PROTOCOL_VERSION) s_sprintf(msg)("demo \"%s\" requires an %s version of Sauerbraten", file, hdr.protocol<PROTOCOL_VERSION ? "older" : "newer"); } if(msg[0]) { if(demoplayback) { gzclose(demoplayback); demoplayback = NULL; } sendservmsg(msg); return; } s_sprintf(msg)("playing demo \"%s\"", file); sendservmsg(msg); sendf(-1, 1, "rii", SV_DEMOPLAYBACK, 1); if(gzread(demoplayback, &nextplayback, sizeof(nextplayback))!=sizeof(nextplayback)) { enddemoplayback(); return; } endianswap(&nextplayback, sizeof(nextplayback), 1); } void enddemoplayback() { if(!demoplayback) return; gzclose(demoplayback); demoplayback = NULL; sendf(-1, 1, "rii", SV_DEMOPLAYBACK, 0); sendservmsg("demo playback finished"); loopv(clients) { ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); ucharbuf p(packet->data, packet->dataLength); welcomepacket(p, clients[i]->clientnum); enet_packet_resize(packet, p.length()); sendpacket(clients[i]->clientnum, 1, packet); if(!packet->referenceCount) enet_packet_destroy(packet); } } void readdemo() { if(!demoplayback) return; while(gamemillis>=nextplayback) { int chan, len; if(gzread(demoplayback, &chan, sizeof(chan))!=sizeof(chan) || gzread(demoplayback, &len, sizeof(len))!=sizeof(len)) { enddemoplayback(); return; } endianswap(&chan, sizeof(chan), 1); endianswap(&len, sizeof(len), 1); ENetPacket *packet = enet_packet_create(NULL, len, 0); if(!packet || gzread(demoplayback, packet->data, len)!=len) { if(packet) enet_packet_destroy(packet); enddemoplayback(); return; } sendpacket(-1, chan, packet); if(!packet->referenceCount) enet_packet_destroy(packet); if(gzread(demoplayback, &nextplayback, sizeof(nextplayback))!=sizeof(nextplayback)) { enddemoplayback(); return; } endianswap(&nextplayback, sizeof(nextplayback), 1); } } void changemap(const char *s, int mode) { if(m_demo) enddemoplayback(); else enddemorecord(); mapreload = false; gamemode = mode; gamemillis = 0; minremain = m_teammode ? 15 : 10; gamelimit = minremain*60000; interm = 0; s_strcpy(smapname, s); resetitems(); notgotitems = true; scores.setsize(0); loopv(clients) { clientinfo *ci = clients[i]; ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; } if(m_teammode) autoteam(); if(m_arena) smode = &arenamode; else if(m_capture) smode = &capturemode; else if(m_assassin) smode = &assassinmode; else smode = NULL; if(smode) smode->reset(false); if(gamemode>1 || (gamemode==0 && hasnonlocalclients())) sendf(-1, 1, "ri2", SV_TIMEUP, minremain); loopv(clients) { clientinfo *ci = clients[i]; ci->mapchange(); ci->state.lasttimeplayed = lastmillis; if(m_mp(gamemode) && ci->state.state!=CS_SPECTATOR) sendspawn(ci); } if(m_demo) setupdemoplayback(); else if(demonextmatch) { demonextmatch = false; setupdemorecord(); } } savedscore &findscore(clientinfo *ci, bool insert) { uint ip = getclientip(ci->clientnum); if(!ip) return *(savedscore *)0; if(!insert) loopv(clients) { clientinfo *oi = clients[i]; if(oi->clientnum != ci->clientnum && getclientip(oi->clientnum) == ip && !strcmp(oi->name, ci->name)) { oi->state.timeplayed += lastmillis - oi->state.lasttimeplayed; oi->state.lasttimeplayed = lastmillis; static savedscore curscore; curscore.save(oi->state); return curscore; } } loopv(scores) { savedscore &sc = scores[i]; if(sc.ip == ip && !strcmp(sc.name, ci->name)) return sc; } if(!insert) return *(savedscore *)0; savedscore &sc = scores.add(); sc.ip = ip; s_strcpy(sc.name, ci->name); return sc; } void savescore(clientinfo *ci) { savedscore &sc = findscore(ci, true); if(&sc) sc.save(ci->state); } struct votecount { char *map; int mode, count; votecount() {} votecount(char *s, int n) : map(s), mode(n), count(0) {} }; void checkvotes(bool force = false) { vector<votecount> votes; int maxvotes = 0; loopv(clients) { clientinfo *oi = clients[i]; if(oi->state.state==CS_SPECTATOR && !oi->privilege) continue; maxvotes++; if(!oi->mapvote[0]) continue; votecount *vc = NULL; loopvj(votes) if(!strcmp(oi->mapvote, votes[j].map) && oi->modevote==votes[j].mode) { vc = &votes[j]; break; } if(!vc) vc = &votes.add(votecount(oi->mapvote, oi->modevote)); vc->count++; } votecount *best = NULL; loopv(votes) if(!best || votes[i].count > best->count || (votes[i].count == best->count && rnd(2))) best = &votes[i]; if(force || (best && best->count > maxvotes/2)) { if(demorecord) enddemorecord(); if(best && (best->count > (force ? 1 : maxvotes/2))) { sendservmsg(force ? "vote passed by default" : "vote passed by majority"); sendf(-1, 1, "risi", SV_MAPCHANGE, best->map, best->mode); changemap(best->map, best->mode); } else { mapreload = true; if(clients.length()) sendf(-1, 1, "ri", SV_MAPRELOAD); } } } int nonspectators(int exclude = -1) { int n = 0; loopv(clients) if(i!=exclude && clients[i]->state.state!=CS_SPECTATOR) n++; return n; } int checktype(int type, clientinfo *ci) { if(ci && ci->local) return type;#if 0 // other message types can get sent by accident if a master forces spectator on someone, so disabling this case for now and checking for spectator state in message handlers // spectators can only connect and talk static int spectypes[] = { SV_INITC2S, SV_POS, SV_TEXT, SV_PING, SV_CLIENTPING, SV_GETMAP, SV_SETMASTER }; if(ci && ci->state.state==CS_SPECTATOR && !ci->privilege) { loopi(sizeof(spectypes)/sizeof(int)) if(type == spectypes[i]) return type; return -1; }#endif // only allow edit messages in coop-edit mode if(type>=SV_EDITENT && type<=SV_GETMAP && gamemode!=1) return -1; // server only messages 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 }; if(ci) loopi(sizeof(servtypes)/sizeof(int)) if(type == servtypes[i]) return -1; return type; } static void freecallback(ENetPacket *packet) { extern igameserver *sv; ((fpsserver *)sv)->cleanworldstate(packet); } void cleanworldstate(ENetPacket *packet) { loopv(worldstates) { worldstate *ws = worldstates[i]; if(packet->data >= ws->positions.getbuf() && packet->data <= &ws->positions.last()) ws->uses--; else if(packet->data >= ws->messages.getbuf() && packet->data <= &ws->messages.last()) ws->uses--; else continue; if(!ws->uses) { delete ws; worldstates.remove(i); } break; } } bool buildworldstate() { static struct { int posoff, msgoff, msglen; } pkt[MAXCLIENTS]; worldstate &ws = *new worldstate; loopv(clients) { clientinfo &ci = *clients[i]; if(ci.position.empty()) pkt[i].posoff = -1; else { pkt[i].posoff = ws.positions.length(); loopvj(ci.position) ws.positions.add(ci.position[j]); } if(ci.messages.empty()) pkt[i].msgoff = -1; else { pkt[i].msgoff = ws.messages.length(); ucharbuf p = ws.messages.reserve(16); putint(p, SV_CLIENT); putint(p, ci.clientnum); putuint(p, ci.messages.length()); ws.messages.addbuf(p); loopvj(ci.messages) ws.messages.add(ci.messages[j]); pkt[i].msglen = ws.messages.length() - pkt[i].msgoff; } } int psize = ws.positions.length(), msize = ws.messages.length(); if(psize) recordpacket(0, ws.positions.getbuf(), psize); if(msize) recordpacket(1, ws.messages.getbuf(), msize); loopi(psize) { uchar c = ws.positions[i]; ws.positions.add(c); } loopi(msize) { uchar c = ws.messages[i]; ws.messages.add(c); } ws.uses = 0; loopv(clients) { clientinfo &ci = *clients[i]; ENetPacket *packet; if(psize && (pkt[i].posoff<0 || psize-ci.position.length()>0)) { packet = enet_packet_create(&ws.positions[pkt[i].posoff<0 ? 0 : pkt[i].posoff+ci.position.length()], pkt[i].posoff<0 ? psize : psize-ci.position.length(), ENET_PACKET_FLAG_NO_ALLOCATE); sendpacket(ci.clientnum, 0, packet); if(!packet->referenceCount) enet_packet_destroy(packet); else { ++ws.uses; packet->freeCallback = freecallback; } } ci.position.setsizenodelete(0); if(msize && (pkt[i].msgoff<0 || msize-pkt[i].msglen>0)) { packet = enet_packet_create(&ws.messages[pkt[i].msgoff<0 ? 0 : pkt[i].msgoff+pkt[i].msglen], pkt[i].msgoff<0 ? msize : msize-pkt[i].msglen, (reliablemessages ? ENET_PACKET_FLAG_RELIABLE : 0) | ENET_PACKET_FLAG_NO_ALLOCATE); sendpacket(ci.clientnum, 1, packet); if(!packet->referenceCount) enet_packet_destroy(packet); else { ++ws.uses; packet->freeCallback = freecallback; } } ci.messages.setsizenodelete(0); } reliablemessages = false; if(!ws.uses) { delete &ws; return false; } else { worldstates.add(&ws); return true; } } bool sendpackets() { if(clients.empty()) return false; enet_uint32 curtime = enet_time_get()-lastsend; if(curtime<33) return false; bool flush = buildworldstate(); lastsend += curtime - (curtime%33); return flush; } void parsepacket(int sender, int chan, bool reliable, ucharbuf &p) // has to parse exactly each byte of the packet { if(sender<0) return; if(chan==2) { receivefile(sender, p.buf, p.maxlen); return; } if(reliable) reliablemessages = true; char text[MAXTRANS]; int cn = -1, type; clientinfo *ci = sender>=0 ? (clientinfo *)getinfo(sender) : NULL; #define QUEUE_MSG { if(!ci->local) while(curmsg<p.length()) ci->messages.add(p.buf[curmsg++]); } #define QUEUE_INT(n) { if(!ci->local) { curmsg = p.length(); ucharbuf buf = ci->messages.reserve(5); putint(buf, n); ci->messages.addbuf(buf); } } #define QUEUE_UINT(n) { if(!ci->local) { curmsg = p.length(); ucharbuf buf = ci->messages.reserve(4); putuint(buf, n); ci->messages.addbuf(buf); } } #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); } } int curmsg; while((curmsg = p.length()) < p.maxlen) switch(type = checktype(getint(p), ci)) { case SV_POS: { cn = getint(p); if(cn<0 || cn>=getnumclients() || cn!=sender) { disconnect_client(sender, DISC_CN); return; } vec oldpos(ci->state.o); loopi(3) ci->state.o[i] = getuint(p)/DMF; getuint(p); loopi(5) getint(p); int physstate = getuint(p); if(physstate&0x20) loopi(2) getint(p); if(physstate&0x10) getint(p); if(!ci->local && (ci->state.state==CS_ALIVE || ci->state.state==CS_EDITING)) { ci->position.setsizenodelete(0); while(curmsg<p.length()) ci->position.add(p.buf[curmsg++]); } uint f = getuint(p); if(!ci->local && (ci->state.state==CS_ALIVE || ci->state.state==CS_EDITING)) { f &= 0xF; if(ci->state.armourtype==A_GREEN && ci->state.armour>0) f |= 1<<4; if(ci->state.armourtype==A_YELLOW && ci->state.armour>0) f |= 1<<5; if(ci->state.quadmillis) f |= 1<<6; if(ci->state.maxhealth>100) f |= ((ci->state.maxhealth-100)/itemstats[I_BOOST-I_SHELLS].add)<<7; curmsg = p.length(); ucharbuf buf = ci->position.reserve(4); putuint(buf, f); ci->position.addbuf(buf); } if(smode && ci->state.state==CS_ALIVE) smode->moved(ci, oldpos, ci->state.o); break; } case SV_EDITMODE: { int val = getint(p); if(ci->state.state!=(val ? CS_ALIVE : CS_EDITING) || (!ci->local && gamemode!=1)) break; if(smode) { if(val) smode->leavegame(ci); else smode->entergame(ci); } ci->state.state = val ? CS_EDITING : CS_ALIVE; if(val) { ci->events.setsizenodelete(0); ci->state.rockets.reset(); ci->state.grenades.reset(); } QUEUE_MSG; break; } case SV_TRYSPAWN: if(ci->state.state!=CS_DEAD || ci->state.lastspawn>=0 || (smode && !smode->canspawn(ci))) break; if(ci->state.lastdeath) ci->state.respawn(); sendspawn(ci); break; case SV_GUNSELECT: { int gunselect = getint(p); if(ci->state.state!=CS_ALIVE) break; ci->state.gunselect = gunselect; QUEUE_MSG; break; } case SV_SPAWN: { int ls = getint(p), gunselect = getint(p); if((ci->state.state!=CS_ALIVE && ci->state.state!=CS_DEAD) || ls!=ci->state.lifesequence || ci->state.lastspawn<0) break; ci->state.lastspawn = -1; ci->state.state = CS_ALIVE; ci->state.gunselect = gunselect; if(smode) smode->spawned(ci); QUEUE_MSG; break; } case SV_SUICIDE: { gameevent &suicide = ci->addevent(); suicide.type = GE_SUICIDE; break; } case SV_SHOOT: { gameevent &shot = ci->addevent(); shot.type = GE_SHOT; #define seteventmillis(event) \ { \ event.id = getint(p); \ if(!ci->timesync || (ci->events.length()==1 && ci->state.waitexpired(gamemillis))) \ { \ ci->timesync = true; \ ci->gameoffset = gamemillis - event.id; \ event.millis = gamemillis; \ } \ else event.millis = ci->gameoffset + event.id; \ } seteventmillis(shot.shot); shot.shot.gun = getint(p); loopk(3) shot.shot.from[k] = getint(p)/DMF; loopk(3) shot.shot.to[k] = getint(p)/DMF; int hits = getint(p); loopk(hits) { gameevent &hit = ci->addevent(); hit.type = GE_HIT; hit.hit.target = getint(p); hit.hit.lifesequence = getint(p); hit.hit.rays = getint(p); loopk(3) hit.hit.dir[k] = getint(p)/DNF; } break; } case SV_EXPLODE: { gameevent &exp = ci->addevent(); exp.type = GE_EXPLODE; seteventmillis(exp.explode); exp.explode.gun = getint(p); exp.explode.id = getint(p); int hits = getint(p); loopk(hits) { gameevent &hit = ci->addevent(); hit.type = GE_HIT; hit.hit.target = getint(p); hit.hit.lifesequence = getint(p); hit.hit.dist = getint(p)/DMF; loopk(3) hit.hit.dir[k] = getint(p)/DNF; } break; } case SV_ITEMPICKUP: { int n = getint(p); gameevent &pickup = ci->addevent(); pickup.type = GE_PICKUP; pickup.pickup.ent = n; break; } case SV_TEXT: //QUEUE_MSG; //getstring(text, p); //filtertext(text, text); //QUEUE_STR(text); if(text=="#info") { sendservmsg("#INFO!"); } break; case SV_INITC2S: { QUEUE_MSG; bool connected = !ci->name[0]; getstring(text, p); filtertext(text, text, false, MAXNAMELEN); if(!text[0]) s_strcpy(text, "unnamed"); QUEUE_STR(text); s_strncpy(ci->name, text, MAXNAMELEN+1); if(!ci->local && connected) { savedscore &sc = findscore(ci, false); if(&sc) { sc.restore(ci->state); sendf(-1, 1, "ri8", SV_RESUME, sender, ci->state.state, ci->state.lifesequence, ci->state.gunselect, sc.maxhealth, sc.frags, -1); } } getstring(text, p); filtertext(text, text, false, MAXTEAMLEN); if(!ci->local && connected && m_teammode) { const char *worst = chooseworstteam(text); if(worst) { s_strcpy(text, worst); sendf(sender, 1, "riis", SV_SETTEAM, sender, worst); QUEUE_STR(worst); } else QUEUE_STR(text); } else QUEUE_STR(text); if(smode && ci->state.state==CS_ALIVE && strcmp(ci->team, text)) smode->changeteam(ci, ci->team, text); s_strncpy(ci->team, text, MAXTEAMLEN+1); QUEUE_MSG; break; } case SV_MAPVOTE: case SV_MAPCHANGE: { getstring(text, p); filtertext(text, text); int reqmode = getint(p); if(type!=SV_MAPVOTE && !mapreload) break; if(!ci->local && !m_mp(reqmode)) reqmode = 0; vote(text, reqmode, sender); break; } case SV_ITEMLIST: { if(ci->state.state==CS_SPECTATOR || !notgotitems) { while(getint(p)!=-1) getint(p); break; } int n; while((n = getint(p))!=-1) { server_entity se = { getint(p), false, 0 }; while(sents.length()<=n) sents.add(se); if(gamemode>=0 && (sents[n].type==I_QUAD || sents[n].type==I_BOOST)) sents[n].spawntime = spawntime(sents[n].type); else sents[n].spawned = true; } notgotitems = false; break; } case SV_TEAMSCORE: getstring(text, p); getint(p); QUEUE_MSG; break; case SV_BASEINFO: getint(p); getstring(text, p); getstring(text, p); getint(p); QUEUE_MSG; break; case SV_BASES: if(ci->state.state!=CS_SPECTATOR && smode==&capturemode) capturemode.parsebases(p); break; case SV_REPAMMO: if(ci->state.state!=CS_SPECTATOR && smode==&capturemode) capturemode.replenishammo(ci); break; case SV_PING: sendf(sender, 1, "i2", SV_PONG, getint(p)); break; case SV_CLIENTPING: getint(p); QUEUE_MSG; break; case SV_MASTERMODE: { int mm = getint(p); if(ci->privilege && mm>=MM_OPEN && mm<=MM_PRIVATE) { if(ci->privilege>=PRIV_ADMIN || (mastermask&(1<<mm))) { mastermode = mm; s_sprintfd(s)("mastermode is now %d", mastermode); sendservmsg(s); } else { s_sprintfd(s)("mastermode %d is disabled on this server", mm); sendf(sender, 1, "ris", SV_SERVMSG, s); } } break; } case SV_CLEARBANS: { if(ci->privilege) { bannedips.setsize(0); sendservmsg("cleared all bans"); } break; } case SV_KICK: { int victim = getint(p); if(ci->privilege && victim>=0 && victim<getnumclients() && ci->clientnum!=victim && getinfo(victim)) { ban &b = bannedips.add(); b.time = totalmillis; b.ip = getclientip(victim); disconnect_client(victim, DISC_KICK); } break; } case SV_SPECTATOR: { int spectator = getint(p), val = getint(p); if(!ci->privilege && (ci->state.state==CS_SPECTATOR || spectator!=sender)) break; clientinfo *spinfo = (clientinfo *)getinfo(spectator); if(!spinfo) break; sendf(-1, 1, "ri3", SV_SPECTATOR, spectator, val); if(spinfo->state.state!=CS_SPECTATOR && val) { if(smode) smode->leavegame(spinfo); spinfo->state.state = CS_SPECTATOR; } else if(spinfo->state.state==CS_SPECTATOR && !val) { spinfo->state.state = CS_DEAD; spinfo->state.respawn(); if(!smode || smode->canspawn(spinfo)) sendspawn(spinfo); } break; } case SV_SETTEAM: { int who = getint(p); getstring(text, p); filtertext(text, text, false, MAXTEAMLEN); if(!ci->privilege || who<0 || who>=getnumclients()) break; clientinfo *wi = (clientinfo *)getinfo(who); if(!wi) break; if(smode && wi->state.state==CS_ALIVE && strcmp(wi->team, text)) smode->changeteam(wi, wi->team, text); s_strncpy(wi->team, text, MAXTEAMLEN+1); sendf(sender, 1, "riis", SV_SETTEAM, who, text); QUEUE_INT(SV_SETTEAM); QUEUE_INT(who); QUEUE_STR(text); break; } case SV_FORCEINTERMISSION: if(m_sp) startintermission(); break; case SV_RECORDDEMO: { int val = getint(p); if(ci->privilege<PRIV_ADMIN) break; demonextmatch = val!=0; s_sprintfd(msg)("demo recording is %s for next match", demonextmatch ? "enabled" : "disabled"); sendservmsg(msg); break; } case SV_STOPDEMO: { if(!ci->local && ci->privilege<PRIV_ADMIN) break; if(m_demo) enddemoplayback(); else enddemorecord(); break; } case SV_CLEARDEMOS: { int demo = getint(p); if(ci->privilege<PRIV_ADMIN) break; cleardemos(demo); break; } case SV_LISTDEMOS: if(ci->state.state==CS_SPECTATOR) break; listdemos(sender); break; case SV_GETDEMO: { int n = getint(p); if(ci->state.state==CS_SPECTATOR) break; senddemo(sender, n); break; } case SV_GETMAP: if(mapdata) { sendf(sender, 1, "ris", SV_SERVMSG, "server sending map..."); sendfile(sender, 2, mapdata, "ri", SV_SENDMAP); } else sendf(sender, 1, "ris", SV_SERVMSG, "no map to send"); break; case SV_NEWMAP: { int size = getint(p); if(ci->state.state==CS_SPECTATOR) break; if(size>=0) { smapname[0] = '\0'; resetitems(); notgotitems = false; if(smode) smode->reset(true); } QUEUE_MSG; break; } case SV_SETMASTER: { int val = getint(p); getstring(text, p); setmaster(ci, val!=0, text); // don't broadcast the master password break; } case SV_APPROVEMASTER: { int mn = getint(p); if(mastermask&MM_AUTOAPPROVE || ci->state.state==CS_SPECTATOR) break; clientinfo *candidate = (clientinfo *)getinfo(mn); if(!candidate || !candidate->wantsmaster || mn==sender || getclientip(mn)==getclientip(sender)) break; setmaster(candidate, true, "", true); break; } default: { int size = msgsizelookup(type); if(size==-1) { disconnect_client(sender, DISC_TAGT); return; } if(size>0) loopi(size-1) getint(p); if(ci && ci->state.state!=CS_SPECTATOR) QUEUE_MSG; break; } } } int welcomepacket(ucharbuf &p, int n) { clientinfo *ci = (clientinfo *)getinfo(n); int hasmap = (gamemode==1 && clients.length()>1) || (smapname[0] && (minremain>0 || (ci && ci->state.state==CS_SPECTATOR) || nonspectators(n))); putint(p, SV_INITS2C); putint(p, n); putint(p, PROTOCOL_VERSION); putint(p, hasmap); if(hasmap) { putint(p, SV_MAPCHANGE); sendstring(smapname, p); putint(p, gamemode); if(!ci || gamemode>1 || (gamemode==0 && hasnonlocalclients())) { putint(p, SV_TIMEUP); putint(p, minremain); } putint(p, SV_ITEMLIST); loopv(sents) if(sents[i].spawned) { putint(p, i); putint(p, sents[i].type); } putint(p, -1); } if(ci && (m_demo || m_mp(gamemode)) && ci->state.state!=CS_SPECTATOR) { if(smode && !smode->canspawn(ci, true)) { ci->state.state = CS_DEAD; putint(p, SV_FORCEDEATH); putint(p, n); sendf(-1, 1, "ri2x", SV_FORCEDEATH, n, n); } else { gamestate &gs = ci->state; spawnstate(ci); putint(p, SV_SPAWNSTATE); putint(p, gs.lifesequence); putint(p, gs.health); putint(p, gs.maxhealth); putint(p, gs.armour); putint(p, gs.armourtype); putint(p, gs.gunselect); loopi(GUN_PISTOL-GUN_SG+1) putint(p, gs.ammo[GUN_SG+i]); gs.lastspawn = gamemillis; } } if(ci && ci->state.state==CS_SPECTATOR) { putint(p, SV_SPECTATOR); putint(p, n); putint(p, 1); sendf(-1, 1, "ri3x", SV_SPECTATOR, n, 1, n); } if(clients.length()>1) { putint(p, SV_RESUME); loopv(clients) { clientinfo *oi = clients[i]; if(oi->clientnum==n) continue; putint(p, oi->clientnum); putint(p, oi->state.state); putint(p, oi->state.lifesequence); putint(p, oi->state.gunselect); putint(p, oi->state.maxhealth); putint(p, oi->state.frags); } putint(p, -1); } if(smode) smode->initclient(ci, p, true); return 1; } void checkintermission() { if(minremain>0) { minremain = gamemillis>=gamelimit ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000; sendf(-1, 1, "ri2", SV_TIMEUP, minremain); if(!minremain && smode) smode->intermission(); } if(!interm && minremain<=0) interm = gamemillis+10000; } void startintermission() { gamelimit = min(gamelimit, gamemillis); checkintermission(); } void clearevent(clientinfo *ci) { int n = 1; while(n<ci->events.length() && ci->events[n].type==GE_HIT) n++; ci->events.remove(0, n); } void spawnstate(clientinfo *ci) { gamestate &gs = ci->state; gs.spawnstate(gamemode); gs.lifesequence++; } void sendspawn(clientinfo *ci) { gamestate &gs = ci->state; spawnstate(ci); sendf(ci->clientnum, 1, "ri7v", SV_SPAWNSTATE, gs.lifesequence, gs.health, gs.maxhealth, gs.armour, gs.armourtype, gs.gunselect, GUN_PISTOL-GUN_SG+1, &gs.ammo[GUN_SG]); gs.lastspawn = gamemillis; } void dodamage(clientinfo *target, clientinfo *actor, int damage, int gun, const vec &hitpush = vec(0, 0, 0)) { gamestate &ts = target->state; ts.dodamage(damage); sendf(-1, 1, "ri6", SV_DAMAGE, target->clientnum, actor->clientnum, damage, ts.armour, ts.health); if(target!=actor && !hitpush.iszero()) { vec v(hitpush); if(!v.iszero()) v.normalize(); sendf(target->clientnum, 1, "ri6", SV_HITPUSH, gun, damage, int(v.x*DNF), int(v.y*DNF), int(v.z*DNF)); } if(ts.health<=0) { int fragvalue = smode ? smode->fragvalue(target, actor) : (target==actor || isteam(target->team, actor->team) ? -1 : 1); actor->state.frags += fragvalue; if(fragvalue>0) { int friends = 0, enemies = 0; // note: friends also includes the fragger if(m_teammode) loopv(clients) if(strcmp(clients[i]->team, actor->team)) enemies++; else friends++; else { friends = 1; enemies = clients.length()-1; } actor->state.effectiveness += fragvalue*friends/float(max(enemies, 1)); } sendf(-1, 1, "ri4", SV_DIED, target->clientnum, actor->clientnum, actor->state.frags); target->position.setsizenodelete(0); if(smode) smode->died(target, actor); ts.state = CS_DEAD; ts.lastdeath = gamemillis; // don't issue respawn yet until DEATHMILLIS has elapsed // ts.respawn(); } } void processevent(clientinfo *ci, suicideevent &e) { gamestate &gs = ci->state; if(gs.state!=CS_ALIVE) return; ci->state.frags += smode ? smode->fragvalue(ci, ci) : -1; sendf(-1, 1, "ri4", SV_DIED, ci->clientnum, ci->clientnum, gs.frags); ci->position.setsizenodelete(0); if(smode) smode->died(ci, NULL); gs.state = CS_DEAD; gs.respawn(); } void processevent(clientinfo *ci, explodeevent &e) { gamestate &gs = ci->state; switch(e.gun) { case GUN_RL: if(!gs.rockets.remove(e.id)) return; break; case GUN_GL: if(!gs.grenades.remove(e.id)) return; break; default: return; } for(int i = 1; i<ci->events.length() && ci->events[i].type==GE_HIT; i++) { hitevent &h = ci->events[i].hit; clientinfo *target = (clientinfo *)getinfo(h.target); if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.dist<0 || h.dist>RL_DAMRAD) continue; int j = 1; for(j = 1; j<i; j++) if(ci->events[j].hit.target==h.target) break; if(j<i) continue; int damage = guns[e.gun].damage; if(gs.quadmillis) damage *= 4; damage = int(damage*(1-h.dist/RL_DISTSCALE/RL_DAMRAD)); if(e.gun==GUN_RL && target==ci) damage /= RL_SELFDAMDIV; dodamage(target, ci, damage, e.gun, h.dir); } } void processevent(clientinfo *ci, shotevent &e) { gamestate &gs = ci->state; int wait = e.millis - gs.lastshot; if(!gs.isalive(gamemillis) || wait<gs.gunwait || e.gun<GUN_FIST || e.gun>GUN_PISTOL || gs.ammo[e.gun]<=0) return; if(e.gun!=GUN_FIST) gs.ammo[e.gun]--; gs.lastshot = e.millis; gs.gunwait = guns[e.gun].attackdelay; sendf(-1, 1, "ri9x", SV_SHOTFX, ci->clientnum, e.gun, int(e.from[0]*DMF), int(e.from[1]*DMF), int(e.from[2]*DMF), int(e.to[0]*DMF), int(e.to[1]*DMF), int(e.to[2]*DMF), ci->clientnum); switch(e.gun) { case GUN_RL: gs.rockets.add(e.id); break; case GUN_GL: gs.grenades.add(e.id); break; default: { int totalrays = 0, maxrays = e.gun==GUN_SG ? SGRAYS : 1; for(int i = 1; i<ci->events.length() && ci->events[i].type==GE_HIT; i++) { hitevent &h = ci->events[i].hit; clientinfo *target = (clientinfo *)getinfo(h.target); if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.rays<1) continue; totalrays += h.rays; if(totalrays>maxrays) continue; int damage = h.rays*guns[e.gun].damage; if(gs.quadmillis) damage *= 4; dodamage(target, ci, damage, e.gun, h.dir); } break; } } } void processevent(clientinfo *ci, pickupevent &e) { gamestate &gs = ci->state; if(m_mp(gamemode) && !gs.isalive(gamemillis)) return; pickup(e.ent, ci->clientnum); } void processevents() { loopv(clients) { clientinfo *ci = clients[i]; if(curtime>0 && ci->state.quadmillis) ci->state.quadmillis = max(ci->state.quadmillis-curtime, 0); while(ci->events.length()) { gameevent &e = ci->events[0]; if(e.type<GE_SUICIDE) { if(e.shot.millis>gamemillis) break; if(e.shot.millis<ci->lastevent) { clearevent(ci); continue; } ci->lastevent = e.shot.millis; } switch(e.type) { case GE_SHOT: processevent(ci, e.shot); break; case GE_EXPLODE: processevent(ci, e.explode); break; // untimed events case GE_SUICIDE: processevent(ci, e.suicide); break; case GE_PICKUP: processevent(ci, e.pickup); break; } clearevent(ci); } } } void serverupdate(int _lastmillis, int _totalmillis) { curtime = _lastmillis - lastmillis; gamemillis += curtime; lastmillis = _lastmillis; totalmillis = _totalmillis; if(m_demo) readdemo(); else if(minremain>0) { processevents(); if(curtime) loopv(sents) if(sents[i].spawntime) // spawn entities when timer reached { int oldtime = sents[i].spawntime; sents[i].spawntime -= curtime; if(sents[i].spawntime<=0) { sents[i].spawntime = 0; sents[i].spawned = true; sendf(-1, 1, "ri2", SV_ITEMSPAWN, i); } else if(sents[i].spawntime<=10000 && oldtime>10000 && (sents[i].type==I_QUAD || sents[i].type==I_BOOST)) { sendf(-1, 1, "ri2", SV_ANNOUNCE, sents[i].type); } } if(smode) smode->update(); } while(bannedips.length() && bannedips[0].time-totalmillis>4*60*60000) bannedips.remove(0); if(masterupdate) { clientinfo *m = currentmaster>=0 ? (clientinfo *)getinfo(currentmaster) : NULL; sendf(-1, 1, "ri3", SV_CURRENTMASTER, currentmaster, m ? m->privilege : 0); masterupdate = false; } if((gamemode>1 || (gamemode==0 && hasnonlocalclients())) && gamemillis-curtime>0 && gamemillis/60000!=(gamemillis-curtime)/60000) checkintermission(); if(interm && gamemillis>interm) { if(demorecord) enddemorecord(); interm = 0; checkvotes(true); } } bool serveroption(char *arg) { if(arg[0]=='-') switch(arg[1]) { case 'n': s_strcpy(serverdesc, &arg[2]); return true; case 'p': s_strcpy(masterpass, &arg[2]); return true; case 'o': if(atoi(&arg[2])) mastermask = (1<<MM_OPEN) | (1<<MM_VETO); return true; } return false; } void serverinit() { smapname[0] = '\0'; resetitems(); } const char *privname(int type) { switch(type) { case PRIV_ADMIN: return "admin"; case PRIV_MASTER: return "master"; default: return "unknown"; } } void setmaster(clientinfo *ci, bool val, const char *pass = "", bool approved = false) { if(approved && (!val || !ci->wantsmaster)) return; const char *name = ""; if(val) { if(ci->privilege) { if(!masterpass[0] || !pass[0]==(ci->privilege!=PRIV_ADMIN)) return; } else if(ci->state.state==CS_SPECTATOR && (!masterpass[0] || strcmp(masterpass, pass))) return; loopv(clients) if(ci!=clients[i] && clients[i]->privilege) { if(masterpass[0] && !strcmp(masterpass, pass)) clients[i]->privilege = PRIV_NONE; else return; } if(masterpass[0] && !strcmp(masterpass, pass)) ci->privilege = PRIV_ADMIN; else if(!approved && !(mastermask&MM_AUTOAPPROVE) && !ci->privilege) { ci->wantsmaster = true; s_sprintfd(msg)("%s wants master. Type \"/approve %d\" to approve.", colorname(ci), ci->clientnum); sendservmsg(msg); return; } else ci->privilege = PRIV_MASTER; name = privname(ci->privilege); } else { if(!ci->privilege) return; name = privname(ci->privilege); ci->privilege = 0; } mastermode = MM_OPEN; s_sprintfd(msg)("%s %s %s", colorname(ci), val ? (approved ? "approved for" : "claimed") : "relinquished", name); sendservmsg(msg); currentmaster = val ? ci->clientnum : -1; masterupdate = true; loopv(clients) clients[i]->wantsmaster = false; } void localconnect(int n) { clientinfo *ci = (clientinfo *)getinfo(n); ci->clientnum = n; ci->local = true; clients.add(ci); } void localdisconnect(int n) { clientinfo *ci = (clientinfo *)getinfo(n); if(smode) smode->leavegame(ci, true); clients.removeobj(ci); } int clientconnect(int n, uint ip) { clientinfo *ci = (clientinfo *)getinfo(n); ci->clientnum = n; clients.add(ci); loopv(bannedips) if(bannedips[i].ip==ip) return DISC_IPBAN; if(mastermode>=MM_PRIVATE) return DISC_PRIVATE; if(mastermode>=MM_LOCKED) ci->state.state = CS_SPECTATOR; if(currentmaster>=0) masterupdate = true; ci->state.lasttimeplayed = lastmillis; return DISC_NONE; } void clientdisconnect(int n) { clientinfo *ci = (clientinfo *)getinfo(n); if(ci->privilege) setmaster(ci, false); if(smode) smode->leavegame(ci, true); ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; savescore(ci); sendf(-1, 1, "ri2", SV_CDIS, n); clients.removeobj(ci); if(clients.empty()) bannedips.setsize(0); // bans clear when server empties else checkvotes(); } const char *servername() { return "sauerbratenserver"; } int serverinfoport() { return SAUERBRATEN_SERVINFO_PORT; } int serverport() { return SAUERBRATEN_SERVER_PORT; } const char *getdefaultmaster() { return "sauerbraten.org/masterserver/"; } void serverinforeply(ucharbuf &p) { putint(p, clients.length()); putint(p, 5); // number of attrs following putint(p, PROTOCOL_VERSION); // a // generic attributes, passed back below putint(p, gamemode); // b putint(p, minremain); // c putint(p, maxclients); putint(p, mastermode); sendstring(smapname, p); sendstring(serverdesc, p); } bool servercompatible(char *name, char *sdec, char *map, int ping, const vector<int> &attr, int np) { return attr.length() && attr[0]==PROTOCOL_VERSION; } void serverinfostr(char *buf, const char *name, const char *sdesc, const char *map, int ping, const vector<int> &attr, int np) { if(attr[0]!=PROTOCOL_VERSION) s_sprintf(buf)("[%s protocol] %s", attr[0]<PROTOCOL_VERSION ? "older" : "newer", name); else { string numcl; if(attr.length()>=4) s_sprintf(numcl)("%d/%d", np, attr[3]); else s_sprintf(numcl)("%d", np); if(attr.length()>=5) switch(attr[4]) { case MM_LOCKED: s_strcat(numcl, " L"); break; case MM_PRIVATE: s_strcat(numcl, " P"); break; } s_sprintf(buf)("%d\t%s\t%s, %s: %s %s", ping, numcl, map[0] ? map : "[unknown]", modestr(attr[1]), name, sdesc); } } void receivefile(int sender, uchar *data, int len) { if(gamemode != 1 || len > 1024*1024) return; clientinfo *ci = (clientinfo *)getinfo(sender); if(ci->state.state==CS_SPECTATOR && !ci->privilege) return; if(mapdata) { fclose(mapdata); mapdata = NULL; } if(!len) return; mapdata = tmpfile(); if(!mapdata) return; fwrite(data, 1, len, mapdata); s_sprintfd(msg)("[%s uploaded map to server, \"/getmap\" to receive it]", colorname(ci)); sendservmsg(msg); } bool duplicatename(clientinfo *ci, char *name) { if(!name) name = ci->name; loopv(clients) if(clients[i]!=ci && !strcmp(name, clients[i]->name)) return true; return false; } char *colorname(clientinfo *ci, char *name = NULL) { if(!name) name = ci->name; if(name[0] && !duplicatename(ci, name)) return name; static string cname; s_sprintf(cname)("%s \fs\f5(%d)\fr", name, ci->clientnum); return cname; } };