// server.cpp: little more than enhanced multicaster // runs dedicated or as client coroutine #include "pch.h" #ifdef WIN32 #include #else #include #define _dup dup #define _fileno fileno #endif #include "cube.h" #include "servercontroller.h" #define DEBUGCOND (true) void resetmap(const char *newname, int newmode, int newtime = -1, bool notify = true); void disconnect_client(int n, int reason = -1); int clienthasflag(int cn); bool refillteams(bool now = false, bool notify = true); void changeclientrole(int client, int role, char *pwd = NULL, bool force=false); bool mapavailable(const char *mapname); void getservermap(void); mapstats *getservermapstats(const char *name); servercontroller *svcctrl = NULL; struct log *logger = NULL; struct servercommandline scl; #define valid_flag(f) (f >= 0 && f < 2) #define SERVERMAP_PATH "packages/maps/servermaps/" #define SERVERMAP_PATH_BUILTIN "packages/maps/official/" #define SERVERMAP_PATH_INCOMING "packages/maps/servermaps/incoming/" static const int DEATHMILLIS = 300; enum { GE_NONE = 0, GE_SHOT, GE_EXPLODE, GE_HIT, GE_AKIMBO, GE_RELOAD, GE_SUICIDE, GE_PICKUP }; enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; int mastermode = MM_OPEN; // allows the gamemode macros to work with the server mode #define gamemode smode string smapname, nextmapname; int smode = 0, nextgamemode; 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 info; float dist; }; float dir[3]; }; struct suicideevent { int type; }; struct pickupevent { int type; int ent; }; struct akimboevent { int type; int millis, id; }; struct reloadevent { int type; int millis, id; int gun; }; union gameevent { int type; shotevent shot; explodeevent explode; hitevent hit; suicideevent suicide; pickupevent pickup; akimboevent akimbo; reloadevent reload; }; template 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 clientstate : playerstate { vec o; int state; int lastdeath, lastspawn, lifesequence; int lastshot; projectilestate<8> grenades; int akimbos, akimbomillis; int flagscore, frags, teamkills, deaths, shotdamage, damage; clientstate() : state(CS_DEAD) {} bool isalive(int gamemillis) { return state==CS_ALIVE || (state==CS_DEAD && gamemillis - lastdeath <= DEATHMILLIS); } bool waitexpired(int gamemillis) { int wait = gamemillis - lastshot; loopi(NUMGUNS) if(wait < gunwait[i]) return false; return true; } void reset() { state = CS_DEAD; lifesequence = -1; grenades.reset(); akimbos = 0; akimbomillis = 0; flagscore = frags = teamkills = deaths = shotdamage = damage = 0; respawn(); } void respawn() { playerstate::respawn(); o = vec(-1e10f, -1e10f, -1e10f); lastdeath = 0; lastspawn = -1; lastshot = 0; akimbos = 0; akimbomillis = 0; } }; struct savedscore { string name; uint ip; int frags, flagscore, deaths, teamkills, shotdamage, damage; void save(clientstate &cs) { frags = cs.frags; flagscore = cs.flagscore; deaths = cs.deaths; teamkills = cs.teamkills; shotdamage = cs.shotdamage; damage = cs.damage; } void restore(clientstate &cs) { cs.frags = frags; cs.flagscore = flagscore; cs.deaths = deaths; cs.teamkills = teamkills; cs.shotdamage = shotdamage; cs.damage = damage; } }; static vector scores; struct client // server side version of "dynent" type { int type; int clientnum; ENetPeer *peer; string hostname; string name, team; int ping; int skin; int vote; int role; int connectmillis; bool isauthed; // for passworded servers bool haswelcome; bool timesync; int gameoffset, lastevent, lastvotecall; int demoflags; clientstate state; vector events; vector position, messages; string lastsaytext; int saychars, lastsay, spamcount; int at3_score, at3_lastforce, lastforce; bool at3_dontmove; int spawnindex; int salt; gameevent &addevent() { static gameevent dummy; if(events.length()>100) return dummy; return events.add(); } void mapchange() { vote = VOTE_NEUTRAL; state.reset(); events.setsizenodelete(0); timesync = false; lastevent = 0; at3_lastforce = 0; } void reset() { name[0] = team[0] = demoflags = 0; ping = 9999; skin = 0; position.setsizenodelete(0); messages.setsizenodelete(0); isauthed = haswelcome = false; role = CR_DEFAULT; lastvotecall = 0; lastsaytext[0] = '\0'; saychars = 0; lastforce = 0; spawnindex = -1; mapchange(); } void zap() { type = ST_EMPTY; role = CR_DEFAULT; isauthed = haswelcome = false; } }; vector clients; bool valid_client(int cn) { return clients.inrange(cn) && clients[cn]->type != ST_EMPTY; } struct ban { ENetAddress address; int millis; }; vector bans; struct worldstate { enet_uint32 uses; vector positions, messages; }; vector worldstates; void cleanworldstate(ENetPacket *packet) { loopv(worldstates) { worldstate *ws = worldstates[i]; if(ws->positions.inbuf(packet->data) || ws->messages.inbuf(packet->data)) ws->uses--; else continue; if(!ws->uses) { delete ws; worldstates.remove(i); } break; } } int bsend = 0, brec = 0, laststatus = 0, servmillis = 0, lastfillup = 0; void recordpacket(int chan, void *data, int len); void sendpacket(int n, int chan, ENetPacket *packet, int exclude = -1) { if(n<0) { recordpacket(chan, packet->data, (int)packet->dataLength); loopv(clients) if(i!=exclude && (clients[i]->type!=ST_TCPIP || clients[i]->isauthed)) sendpacket(i, chan, packet); return; } switch(clients[n]->type) { case ST_TCPIP: { enet_peer_send(clients[n]->peer, chan, packet); bsend += (int)packet->dataLength; break; } case ST_LOCAL: localservertoclient(chan, packet->data, (int)packet->dataLength); break; } } static bool reliablemessages = false; bool buildworldstate() { static struct { int posoff, msgoff, msglen; } pkt[MAXCLIENTS]; worldstate &ws = *new worldstate; loopv(clients) { client &c = *clients[i]; if(c.type!=ST_TCPIP || !c.isauthed) continue; if(c.position.empty()) pkt[i].posoff = -1; else { pkt[i].posoff = ws.positions.length(); loopvj(c.position) ws.positions.add(c.position[j]); } if(c.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, c.clientnum); putuint(p, c.messages.length()); ws.messages.addbuf(p); loopvj(c.messages) ws.messages.add(c.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); ucharbuf p = ws.positions.reserve(psize); p.put(ws.positions.getbuf(), psize); ws.positions.addbuf(p); } if(msize) { recordpacket(1, ws.messages.getbuf(), msize); ucharbuf p = ws.messages.reserve(msize); p.put(ws.messages.getbuf(), msize); ws.messages.addbuf(p); } ws.uses = 0; loopv(clients) { client &c = *clients[i]; if(c.type!=ST_TCPIP || !c.isauthed) continue; ENetPacket *packet; if(psize && (pkt[i].posoff<0 || psize-c.position.length()>0)) { packet = enet_packet_create(&ws.positions[pkt[i].posoff<0 ? 0 : pkt[i].posoff+c.position.length()], pkt[i].posoff<0 ? psize : psize-c.position.length(), ENET_PACKET_FLAG_NO_ALLOCATE); sendpacket(c.clientnum, 0, packet); if(!packet->referenceCount) enet_packet_destroy(packet); else { ++ws.uses; packet->freeCallback = cleanworldstate; } } c.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(c.clientnum, 1, packet); if(!packet->referenceCount) enet_packet_destroy(packet); else { ++ws.uses; packet->freeCallback = cleanworldstate; } } c.messages.setsizenodelete(0); } reliablemessages = false; if(!ws.uses) { delete &ws; return false; } else { worldstates.add(&ws); return true; } } int countclients(int type, bool exclude = false) { int num = 0; loopv(clients) if((clients[i]->type!=type)==exclude) num++; return num; } int numclients() { return countclients(ST_EMPTY, true); } int numlocalclients() { return countclients(ST_LOCAL); } int numnonlocalclients() { return countclients(ST_TCPIP); } int numauthedclients() { int num = 0; loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed) num++; return num; } int calcscores(); int freeteam(int pl = -1) { int teamsize[2] = {0, 0}; int teamscore[2] = {0, 0}; int t; int sum = calcscores(); loopv(clients) if(clients[i]->type!=ST_EMPTY && i != pl && clients[i]->isauthed) { t = team_int(clients[i]->team); teamsize[t]++; teamscore[t] += clients[i]->at3_score; } if(teamsize[0] == teamsize[1]) { return sum > 200 ? (teamscore[0] < teamscore[1] ? 0 : 1) : rnd(2); } return teamsize[0] < teamsize[1] ? 0 : 1; } int findcnbyaddress(ENetAddress *address) { loopv(clients) { if(clients[i]->type == ST_TCPIP && clients[i]->peer->address.host == address->host && clients[i]->peer->address.port == address->port) return i; } return -1; } savedscore *findscore(client &c, bool insert) { if(c.type!=ST_TCPIP) return NULL; if(!insert) { loopv(clients) { client &o = *clients[i]; if(o.type!=ST_TCPIP) continue; if(o.clientnum!=c.clientnum && o.peer->address.host==c.peer->address.host && !strcmp(o.name, c.name)) { static savedscore curscore; curscore.save(o.state); return &curscore; } } } loopv(scores) { savedscore &sc = scores[i]; if(!strcmp(sc.name, c.name) && sc.ip==c.peer->address.host) return ≻ } if(!insert) return NULL; savedscore &sc = scores.add(); s_strcpy(sc.name, c.name); sc.ip = c.peer->address.host; return ≻ } struct server_entity // server side version of "entity" type { int type; bool spawned; int spawntime; }; vector sents; bool notgotitems = true; // true when map has changed and waiting for clients to send item int clnumspawn[3], clnumflagspawn[2]; void restoreserverstate(vector &ents) // hack: called from savegame code, only works in SP { loopv(sents) { sents[i].spawned = ents[i].spawned; sents[i].spawntime = 0; } } static int interm = 0, minremain = 0, gamemillis = 0, gamelimit = 0; static bool mapreload = false, autoteam = true, forceintermission = false; string servdesc_current; ENetAddress servdesc_caller; bool custom_servdesc = false; bool isdedicated = false; ENetHost *serverhost = NULL; void process(ENetPacket *packet, int sender, int chan); void welcomepacket(ucharbuf &p, int n, ENetPacket *packet, bool forcedeath = false); void sendwelcome(client *cl, int chan = 1, bool forcedeath = false); void sendf(int cn, int chan, const char *format, ...) { int exclude = -1; bool reliable = false; if(*format=='r') { reliable = true; ++format; } ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, reliable ? ENET_PACKET_FLAG_RELIABLE : 0); ucharbuf p(packet->data, packet->dataLength); va_list args; va_start(args, format); while(*format) switch(*format++) { case 'x': exclude = va_arg(args, int); break; case 'v': { int n = va_arg(args, int); int *v = va_arg(args, int *); loopi(n) putint(p, v[i]); break; } case 'i': { int n = isdigit(*format) ? *format++-'0' : 1; loopi(n) putint(p, va_arg(args, int)); break; } case 's': sendstring(va_arg(args, const char *), p); break; case 'm': { int n = va_arg(args, int); enet_packet_resize(packet, packet->dataLength+n); p.buf = packet->data; p.maxlen += n; p.put(va_arg(args, uchar *), n); break; } } va_end(args); enet_packet_resize(packet, p.length()); sendpacket(cn, chan, packet, exclude); if(packet->referenceCount==0) enet_packet_destroy(packet); } void sendservmsg(const char *msg, int client=-1) { sendf(client, 1, "ris", SV_SERVMSG, msg); } void spawnstate(client *c) { clientstate &gs = c->state; gs.spawnstate(smode); gs.lifesequence++; } void sendspawn(client *c) { clientstate &gs = c->state; spawnstate(c); sendf(c->clientnum, 1, "ri7vv", SV_SPAWNSTATE, gs.lifesequence, gs.health, gs.armour, gs.primary, gs.gunselect, m_arena ? c->spawnindex : -1, NUMGUNS, gs.ammo, NUMGUNS, gs.mag); gs.lastspawn = gamemillis; } // demo struct demofile { string info; string file; uchar *data; int len; }; vector demos; bool demonextmatch = false; FILE *demotmp = NULL; gzFile demorecord = NULL, demoplayback = NULL; bool recordpackets = false; int nextplayback = 0; 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) { if(recordpackets) writedemo(chan, data, len); } char *asctime() { time_t t = time(NULL); char *timestr = ctime(&t); char *trim = timestr + strlen(timestr); while(trim>timestr && isspace(*--trim)) *trim = '\0'; return timestr; } char fmttimestr[15]; // flowtron assumes "%b" is always 3 chars (true at least for DE and EN) char *dmostrftime() { time_t t = time(NULL); struct tm * timeinfo; timeinfo = localtime (&t); strftime(fmttimestr, 15, "%Y%b%d_%H%M", timeinfo); // 14 + "\0" return fmttimestr; } void enddemorecord() { if(!demorecord) return; gzclose(demorecord); recordpackets = false; demorecord = NULL; #ifdef WIN32 demotmp = fopen(path("demos/demorecord", true), "rb"); #endif if(!demotmp) return; fseek(demotmp, 0, SEEK_END); int len = ftell(demotmp); rewind(demotmp); if(demos.length() >= scl.maxdemos) { delete[] demos[0].data; demos.remove(0); } demofile &d = demos.add(); s_sprintf(d.info)("%s: %s, %s, %.2f%s", asctime(), modestr(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); s_sprintf(d.file)("%s_%s_%s", modestr(gamemode, true), behindpath(smapname), dmostrftime()); s_sprintfd(msg)("Demo \"%s\" recorded\nPress F10 to download it from the server..", d.info); sendservmsg(msg); logger->writeline(log::info, "Demo \"%s\" recorded.", d.info); d.data = new uchar[len]; d.len = len; fread(d.data, 1, len, demotmp); fclose(demotmp); demotmp = NULL; if(scl.demopath[0]) { s_sprintf(msg)("%s%s_%s_%s.dmo", scl.demopath, filenametime(), behindpath(smapname), modestr(gamemode, true)); path(msg); FILE *demo = openfile(msg, "wb"); if(demo) { int wlen = (int) fwrite(d.data, 1, d.len, demo); fclose(demo); logger->writeline(log::info, "demo written to file \"%s\" (%d bytes)", msg, wlen); } else { logger->writeline(log::info, "failed to write demo to file \"%s\"", msg); } } } void setupdemorecord() { if(numlocalclients() || !m_mp(gamemode) || gamemode==1) return; #ifdef WIN32 gzFile f = gzopen(path("demos/demorecord", true), "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"); logger->writeline(log::info, "Demo recording started."); demorecord = f; recordpackets = false; 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); memset(hdr.desc, 0, DHDR_DESCCHARS); s_sprintfd(desc)("%s, %s, %s %s", modestr(gamemode, false), behindpath(smapname), asctime(), servdesc_current); if(strlen(desc) > DHDR_DESCCHARS) s_sprintf(desc)("%s, %s, %s %s", modestr(gamemode, true), behindpath(smapname), asctime(), servdesc_current); desc[DHDR_DESCCHARS - 1] = '\0'; strcpy(hdr.desc, desc); gzwrite(demorecord, &hdr, sizeof(demoheader)); ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); ucharbuf p(packet->data, packet->dataLength); welcomepacket(p, -1, packet); writedemo(1, p.buf, p.len); enet_packet_destroy(packet); uchar buf[MAXTRANS]; loopv(clients) { client *ci = clients[i]; if(ci->type==ST_EMPTY) continue; uchar header[16]; ucharbuf q(&buf[sizeof(header)], sizeof(buf)-sizeof(header)); putint(q, SV_INITC2S); sendstring(ci->name, q); sendstring(ci->team, q); putint(q, ci->skin); 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); } static 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)) { if(demos.empty()) sendservmsg("no demos available", cn); else { s_sprintfd(msg)("no demo %d available", num); sendservmsg(msg, cn); } return; } demofile &d = demos[num-1]; ENetPacket *packet = enet_packet_create(NULL, MAXTRANS + d.len, ENET_PACKET_FLAG_RELIABLE); ucharbuf p(packet->data, packet->dataLength); putint(p, SV_SENDDEMO); sendstring(d.file, p); putint(p, d.len); p.put(d.data, d.len); enet_packet_resize(packet, p.length()); sendpacket(cn, 2, packet); if(!packet->referenceCount) enet_packet_destroy(packet); } void enddemoplayback() { if(!demoplayback) return; gzclose(demoplayback); demoplayback = NULL; loopv(clients) sendf(i, 1, "ri3", SV_DEMOPLAYBACK, 0, i); sendservmsg("demo playback finished"); loopv(clients) sendwelcome(clients[i]); } void setupdemoplayback() { demoheader hdr; string msg; msg[0] = '\0'; s_sprintfd(file)("demos/%s.dmo", smapname); path(file); 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 AssaultCube", file, hdr.version=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); } } // struct sflaginfo { int state; int actor_cn; float pos[3]; int lastupdate; int stolentime; } sflaginfos[2]; void putflaginfo(ucharbuf &p, int flag) { sflaginfo &f = sflaginfos[flag]; putint(p, SV_FLAGINFO); putint(p, flag); putint(p, f.state); switch(f.state) { case CTFF_STOLEN: putint(p, f.actor_cn); break; case CTFF_DROPPED: loopi(3) putuint(p, uint(f.pos[i]*DMF)); break; } } void sendflaginfo(int flag = -1, int cn = -1) { ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); ucharbuf p(packet->data, packet->dataLength); if(flag >= 0) putflaginfo(p, flag); else loopi(2) putflaginfo(p, i); enet_packet_resize(packet, p.length()); sendpacket(cn, 1, packet); if(packet->referenceCount==0) enet_packet_destroy(packet); } void flagmessage(int flag, int message, int actor, int cn = -1) { if(message == FM_KTFSCORE) sendf(cn, 1, "riiiii", SV_FLAGMSG, flag, message, actor, (gamemillis - sflaginfos[flag].stolentime) / 1000); else sendf(cn, 1, "riiii", SV_FLAGMSG, flag, message, actor); } void flagaction(int flag, int action, int actor) { if(!valid_flag(flag)) return; sflaginfo &f = sflaginfos[flag]; sflaginfo &of = sflaginfos[team_opposite(flag)]; bool deadactor = valid_client(actor) ? clients[actor]->state.state != CS_ALIVE : true; int score = 0; int message = -1; if(m_ctf || m_htf) { switch(action) { case FA_PICKUP: // ctf: f = enemy team htf: f = own team { if(deadactor || f.state == CTFF_STOLEN) return; int team = team_int(clients[actor]->team); if(m_ctf) team = team_opposite(team); if(team != flag) return; f.state = CTFF_STOLEN; f.actor_cn = actor; message = FM_PICKUP; break; } case FA_LOST: if(actor == -1) actor = f.actor_cn; case FA_DROP: if(f.state!=CTFF_STOLEN || f.actor_cn != actor) return; f.state = CTFF_DROPPED; loopi(3) f.pos[i] = clients[actor]->state.o[i]; message = action == FA_LOST ? FM_LOST : FM_DROP; break; case FA_RETURN: if(f.state!=CTFF_DROPPED || m_htf) return; f.state = CTFF_INBASE; message = FM_RETURN; break; case FA_SCORE: // ctf: f = carried by actor flag, htf: f = hunted flag (run over by actor) if(m_ctf) { if(f.state != CTFF_STOLEN || f.actor_cn != actor || of.state != CTFF_INBASE) return; score = 1; message = FM_SCORE; } else // m_htf { if(f.state != CTFF_DROPPED) return; score = (of.state == CTFF_STOLEN) ? 1 : 0; message = score ? FM_SCORE : FM_SCOREFAIL; if(of.actor_cn == actor) score *= 2; } f.state = CTFF_INBASE; break; case FA_RESET: f.state = CTFF_INBASE; message = FM_RESET; break; } } else if(m_ktf) // f: active flag, of: idle flag { switch(action) { case FA_PICKUP: if(deadactor || f.state != CTFF_INBASE) return; f.state = CTFF_STOLEN; f.actor_cn = actor; f.stolentime = gamemillis; message = FM_PICKUP; break; case FA_SCORE: // f = carried by actor flag if(actor != -1 || f.state != CTFF_STOLEN) return; // no client msg allowed here if(valid_client(f.actor_cn) && clients[f.actor_cn]->state.state == CS_ALIVE) { actor = f.actor_cn; score = 1; message = FM_KTFSCORE; break; } case FA_LOST: if(actor == -1) actor = f.actor_cn; case FA_DROP: if(f.actor_cn != actor || f.state != CTFF_STOLEN) return; case FA_RESET: if(f.state == CTFF_STOLEN) { actor = f.actor_cn; message = FM_LOST; } f.state = CTFF_IDLE; of.state = CTFF_INBASE; sendflaginfo(team_opposite(flag)); break; } } if(score) { clients[actor]->state.flagscore += score; sendf(-1, 1, "riii", SV_FLAGCNT, actor, clients[actor]->state.flagscore); } if(valid_client(actor)) { client &c = *clients[actor]; switch(message) { case FM_PICKUP: logger->writeline(log::info,"[%s] %s stole the flag", c.hostname, c.name); break; case FM_DROP: case FM_LOST: logger->writeline(log::info,"[%s] %s %s the flag", c.hostname, c.name, message == FM_LOST ? "lost" : "dropped"); break; case FM_RETURN: logger->writeline(log::info,"[%s] %s returned the flag", c.hostname, c.name); break; case FM_SCORE: logger->writeline(log::info, "[%s] %s scored with the flag for %s, new score %d", c.hostname, c.name, c.team, c.state.flagscore); break; case FM_KTFSCORE: logger->writeline(log::info, "[%s] %s scored, carrying for %d seconds, new score %d", c.hostname, c.name, (gamemillis - f.stolentime) / 1000, c.state.flagscore); break; case FM_SCOREFAIL: logger->writeline(log::info, "[%s] %s failed to score", c.hostname, c.name); break; default: logger->writeline(log::info, "flagaction %d, actor %d, flag %d, message %d", action, actor, flag, message); break; } } else { switch(message) { case FM_RESET: logger->writeline(log::info,"the server reset the flag for team %s", team_string(flag)); break; default: logger->writeline(log::info, "flagaction %d with invalid actor cn %d, flag %d, message %d", action, actor, flag, message); break; } } f.lastupdate = gamemillis; sendflaginfo(flag); if(message >= 0) flagmessage(flag, message, valid_client(actor) ? actor : -1); } int clienthasflag(int cn) { if(m_flags && valid_client(cn)) { loopi(2) { if(sflaginfos[i].state==CTFF_STOLEN && sflaginfos[i].actor_cn==cn) return i; } } return -1; } void ctfreset() { int idleflag = m_ktf ? rnd(2) : -1; loopi(2) { sflaginfos[i].actor_cn = -1; sflaginfos[i].state = i == idleflag ? CTFF_IDLE : CTFF_INBASE; sflaginfos[i].lastupdate = -1; } } void dropflag(int cn) { int fl = clienthasflag(cn); if(fl >= 0) flagaction(fl, FA_LOST, cn); } void resetflag(int cn) { int fl = clienthasflag(cn); if(fl >= 0) flagaction(fl, FA_RESET, -1); } void htf_forceflag(int flag) { sflaginfo &f = sflaginfos[flag]; int besthealth = 0, numbesthealth = 0; loopv(clients) if(clients[i]->type!=ST_EMPTY) { if(clients[i]->state.state == CS_ALIVE && team_int(clients[i]->team) == flag) { if(clients[i]->state.health == besthealth) numbesthealth++; else { if(clients[i]->state.health > besthealth) { besthealth = clients[i]->state.health; numbesthealth = 1; } } } } if(numbesthealth) { int pick = rnd(numbesthealth); loopv(clients) if(clients[i]->type!=ST_EMPTY) { if(clients[i]->state.state == CS_ALIVE && team_int(clients[i]->team) == flag && --pick < 0) { f.state = CTFF_STOLEN; f.actor_cn = i; sendflaginfo(flag); flagmessage(flag, FM_PICKUP, i); logger->writeline(log::info,"[%s] %s got forced to pickup the flag", clients[i]->hostname, clients[i]->name); break; } } } f.lastupdate = gamemillis; } bool canspawn(client *c, bool connecting = false) { if(m_arena) { if(connecting && numauthedclients()<=2) return true; return false; } return true; } int arenaround = 0; struct twoint { int index, value; }; int cmpscore(const void *a, const void * b) { return clients[*((int *)a)]->at3_score - clients[*((int *)b)]->at3_score; } int cmptwoint(const void *a, const void * b) { return ((struct twoint *)a)->value - ((struct twoint *)b)->value; } ivector tdistrib; vector sdistrib; void distributeteam(int team) { int numsp = team == 100 ? clnumspawn[2] : clnumspawn[team]; if(!numsp) numsp = 30; // no map data yet: make a guess twoint ti; tdistrib.setsize(0); loopv(clients) if(clients[i]->type!=ST_EMPTY) { if(team == 100 || team == team_int(clients[i]->team)) { tdistrib.add(i); clients[i]->at3_score = rand(); } } tdistrib.sort(cmpscore); // random player order sdistrib.setsize(0); loopi(numsp) { ti.index = i; ti.value = rand(); sdistrib.add(ti); } sdistrib.sort(cmptwoint); // random spawn order int x = 0; loopv(tdistrib) { clients[tdistrib[i]]->spawnindex = sdistrib[x++].index; x %= sdistrib.length(); } } void distributespawns() { loopv(clients) if(clients[i]->type!=ST_EMPTY) { clients[i]->spawnindex = -1; } if(m_teammode) { distributeteam(0); distributeteam(1); } else { distributeteam(100); } } void arenacheck() { if(!m_arena || interm || gamemillistype!=ST_EMPTY && clients[i]->isauthed) { clients[i]->state.respawn(); sendspawn(clients[i]); } return; } #ifndef STANDALONE if(m_botmode && clients[0]->type==ST_LOCAL) { bool alive = false, dead = false; loopv(players) if(players[i]) { if(players[i]->state==CS_DEAD) dead = true; else alive = true; } if((dead && !alive) || player1->state==CS_DEAD) { sendf(-1, 1, "ri2", SV_ARENAWIN, player1->state==CS_ALIVE ? getclientnum() : (alive ? -2 : -1)); arenaround = gamemillis+5000; } return; } #endif client *alive = NULL; bool dead = false; int lastdeath = 0; loopv(clients) { client &c = *clients[i]; if(c.type==ST_EMPTY || !c.isauthed) continue; if(c.state.state==CS_ALIVE || (c.state.state==CS_DEAD && c.state.lastspawn>=0)) { if(!alive) alive = &c; else if(!m_teammode || strcmp(alive->team, c.team)) return; } else if(c.state.state==CS_DEAD) { dead = true; lastdeath = max(lastdeath, c.state.lastdeath); } } if(!dead || gamemillis < lastdeath + 500) return; sendf(-1, 1, "ri2", SV_ARENAWIN, alive ? alive->clientnum : -1); arenaround = gamemillis+5000; if(autoteam && m_teammode) refillteams(true); } #define SPAMREPEATINTERVAL 20 // detect doubled lines only if interval < 20 seconds #define SPAMMAXREPEAT 3 // 4th time is SPAM #define SPAMCHARPERMINUTE 220 // good typist #define SPAMCHARINTERVAL 30 // allow 20 seconds typing at maxspeed bool spamdetect(client *cl, char *text) // checks doubled lines and average typing speed { if(cl->type != ST_TCPIP) return false; bool spam = false; int pause = servmillis - cl->lastsay; if(pause < 0 || pause > 90*1000) pause = 90*1000; cl->saychars -= (SPAMCHARPERMINUTE * pause) / (60*1000); cl->saychars += (int)strlen(text); if(cl->saychars < 0) cl->saychars = 0; if(text[0] && !strcmp(text, cl->lastsaytext) && servmillis - cl->lastsay < SPAMREPEATINTERVAL*1000) { spam = ++cl->spamcount > SPAMMAXREPEAT; } else { s_strcpy(cl->lastsaytext, text); cl->spamcount = 0; } cl->lastsay = servmillis; if(cl->saychars > (SPAMCHARPERMINUTE * SPAMCHARINTERVAL) / 60) spam = true; return spam; } void sendteamtext(char *text, int sender) { if(!valid_client(sender) || !clients[sender]->team[0]) return; ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); ucharbuf p(packet->data, packet->dataLength); putint(p, SV_TEAMTEXT); putint(p, sender); sendstring(text, p); enet_packet_resize(packet, p.length()); loopv(clients) if(i!=sender) { if(!strcmp(clients[i]->team, clients[sender]->team) || !m_teammode) // send to everyone in non-team mode sendpacket(i, 1, packet); } if(packet->referenceCount==0) enet_packet_destroy(packet); } void sendvoicecomteam(int sound, int sender) { if(!valid_client(sender) || !clients[sender]->team[0]) return; ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); ucharbuf p(packet->data, packet->dataLength); putint(p, SV_VOICECOMTEAM); putint(p, sender); putint(p, sound); enet_packet_resize(packet, p.length()); loopv(clients) if(i!=sender) { if(!strcmp(clients[i]->team, clients[sender]->team) || !m_teammode) sendpacket(i, 1, packet); } if(packet->referenceCount==0) enet_packet_destroy(packet); } void resetitems() { sents.setsize(0); notgotitems = true; } int spawntime(int type) { int np = numclients(); np = np<3 ? 4 : (np>4 ? 2 : 3); // spawn times are dependent on number of players int sec = 0; switch(type) { case I_CLIPS: case I_AMMO: case I_GRENADE: sec = np*2; break; case I_HEALTH: sec = np*5; break; case I_ARMOUR: sec = 20; break; case I_AKIMBO: sec = 60; break; } return sec*1000; } bool serverpickup(int i, int sender) // server side item pickup, acknowledge first client that gets it { if(!sents.inrange(i)) return false; server_entity &e = sents[i]; if(!e.spawned) return false; if(sender>=0) { client *cl = clients[sender]; if(cl->type==ST_TCPIP) { if(cl->state.state!=CS_ALIVE || !cl->state.canpickup(e.type)) return false; } sendf(-1, 1, "ri3", SV_ITEMACC, i, sender); cl->state.pickup(sents[i].type); } e.spawned = false; e.spawntime = spawntime(e.type); return true; } void checkitemspawns(int diff) { if(!diff) return; loopv(sents) if(sents[i].spawntime) { sents[i].spawntime -= diff; if(sents[i].spawntime<=0) { sents[i].spawntime = 0; sents[i].spawned = true; sendf(-1, 1, "ri2", SV_ITEMSPAWN, i); } } } void serverdamage(client *target, client *actor, int damage, int gun, bool gib, const vec &hitpush = vec(0, 0, 0)) { clientstate &ts = target->state; ts.dodamage(damage); actor->state.damage += damage != 1000 ? damage : 0; sendf(-1, 1, "ri6", gib ? SV_GIBDAMAGE : 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 targethasflag = clienthasflag(target->clientnum); bool tk = false, suic = false; target->state.deaths++; if(target!=actor) { if(!isteam(target->team, actor->team)) actor->state.frags += gib ? 2 : 1; else { actor->state.frags--; actor->state.teamkills++; tk = true; } } else { // suicide actor->state.frags--; suic = true; logger->writeline(log::info, "[%s] %s suicided", actor->hostname, actor->name); } sendf(-1, 1, "ri4", gib ? SV_GIBDIED : SV_DIED, target->clientnum, actor->clientnum, actor->state.frags); if((suic || tk) && (m_htf || m_ktf) && targethasflag >= 0) { actor->state.flagscore--; sendf(-1, 1, "riii", SV_FLAGCNT, actor->clientnum, actor->state.flagscore); } target->position.setsizenodelete(0); ts.state = CS_DEAD; ts.lastdeath = gamemillis; if(!suic) logger->writeline(log::info, "[%s] %s %s%s %s", actor->hostname, actor->name, gib ? "gibbed" : "fragged", tk ? " his teammate" : "", target->name); if(m_flags && targethasflag >= 0) { if(m_ctf) flagaction(targethasflag, tk ? FA_RESET : FA_LOST, -1); else if(m_htf) flagaction(targethasflag, FA_LOST, -1); else // ktf || tktf flagaction(targethasflag, FA_RESET, -1); } // don't issue respawn yet until DEATHMILLIS has elapsed // ts.respawn(); if(actor->state.frags < scl.banthreshold) { ban b = { actor->peer->address, servmillis+20*60*1000 }; bans.add(b); disconnect_client(actor->clientnum, DISC_AUTOBAN); } else if(actor->state.frags < scl.kickthreshold) disconnect_client(actor->clientnum, DISC_AUTOKICK); } } #include "serverevents.h" struct configset { string mapname; int mode; int time; bool vote; int minplayer; int maxplayer; int skiplines; }; vector configsets; int curcfgset = -1; char *loadcfgfile(char *cfg, const char *name, int *len) { if(name && name[0]) { s_strcpy(cfg, name); path(cfg); } char *buf = loadfile(cfg, len); if(!buf) { if(name && logger) logger->writeline(log::info,"could not read config file '%s'", name); return NULL; } char *p = buf; while((p = strstr(p, "//")) != NULL) // remove comments while(p[0] != '\n' && p[0] != '\0') p++[0] = ' '; if('\r' != '\n') // this is not a joke! { p = buf; while((p = strchr(p, '\r')) != NULL) p++[0] = ' '; } p = buf; while((p = strchr(p, '\n')) != NULL) p++[0] = 0; return buf; } #define CONFIG_MAXPAR 6 void readscfg(const char *name) { static string cfgfilename; static int cfgfilesize; const char *sep = ": \t"; configset c; char *p, *l; int i, len, par[CONFIG_MAXPAR]; if(!name && getfilesize(cfgfilename) == cfgfilesize) return; configsets.setsize(0); char *buf = loadcfgfile(cfgfilename, name, &len); cfgfilesize = len; if(!buf) return; p = buf; if(scl.verbose) logger->writeline(log::info,"reading map rotation '%s'", cfgfilename); while(p < buf + len) { l = p; p += strlen(p) + 1; l = strtok(l, sep); if(l) { s_strcpy(c.mapname, l); par[3] = par[4] = par[5] = 0; // default values for(i = 0; i < CONFIG_MAXPAR; i++) { if((l = strtok(NULL, sep)) != NULL) par[i] = atoi(l); else break; } if(i > 2) { c.mode = par[0]; c.time = par[1]; c.vote = par[2] > 0; c.minplayer = par[3]; c.maxplayer = par[4]; c.skiplines = par[5]; configsets.add(c); if(scl.verbose) logger->writeline(log::info," %s, %s, %d minutes, vote:%d, minplayer:%d, maxplayer:%d, skiplines:%d", c.mapname, modestr(c.mode, false), c.time, c.vote, c.minplayer, c.maxplayer, c.skiplines); } } } delete[] buf; logger->writeline(log::info,"read %d map rotation entries from '%s'", configsets.length(), cfgfilename); } struct iprange { enet_uint32 lr, ur; }; int cmpiprange(const void *a, const void * b) { if(((struct iprange *)a)->lr < ((struct iprange *)b)->lr) return -1; if(((struct iprange *)a)->lr > ((struct iprange *)b)->lr) return 1; return 0; } int cmpipmatch(const void *a, const void * b) { return - (((struct iprange *)a)->lr < ((struct iprange *)b)->lr) + (((struct iprange *)a)->lr > ((struct iprange *)b)->ur); } enet_uint32 atoip(const char *s) { unsigned int d[3], res; if(sscanf(s, "%u.%u.%u.%u", &res, d, d + 1, d + 2) != 4) return 0; loopi(3) { if(d[i] > 255) return 0; res = (res << 8) + d[i]; } return res; } const char *iptoa(enet_uint32 ip) { static string s[2]; static int buf = 0; buf = (buf + 1) % 2; s_sprintf(s[buf])("%d.%d.%d.%d", (ip >> 24) & 255, (ip >> 16) & 255, (ip >> 8) & 255, ip & 255); return s[buf]; } const char *iprtoa(struct iprange *ipr) { static string s[2]; static int buf = 0; buf = (buf + 1) % 2; if(ipr->lr == ipr->ur) s_strcpy(s[buf], iptoa(ipr->lr)); else s_sprintf(s[buf])("%s-%s", iptoa(ipr->lr), iptoa(ipr->ur)); return s[buf]; } vector blacklist; void readblacklist(const char *name) { static string blfilename; static int blfilesize; char *p, *l; iprange ir; int m, len; if(!name && getfilesize(blfilename) == blfilesize) return; blacklist.setsize(0); char *buf = loadcfgfile(blfilename, name, &len); blfilesize = len; if(!buf) return; p = buf; if(scl.verbose) logger->writeline(log::info,"reading blacklist '%s'", blfilename); while(p < buf + len) { l = p; p += strlen(p) + 1; if(!(ir.lr = ir.ur = atoip(l))) continue; if(strchr(l, '-')) { ir.ur = atoip(strchr(l, '-') + 1); if(!ir.ur || ir.lr > ir.ur) continue; } else if(strchr(l, '/')) { m = atoi(strchr(l, '/') + 1); if(m > 0 && m < 33) { m = (1 << (32 - m)) - 1; ir.lr &= ~m; ir.ur |= m; } else continue; } blacklist.add(ir); } delete[] buf; blacklist.sort(cmpiprange); int orglength = blacklist.length(); loopv(blacklist) { if(!i) continue; if(blacklist[i].ur <= blacklist[i - 1].ur) { if(scl.verbose) { if(blacklist[i].lr == blacklist[i - 1].lr && blacklist[i].ur == blacklist[i - 1].ur) logger->writeline(log::info," blacklist entry %s got dropped (double entry)", iprtoa(&blacklist[i])); else logger->writeline(log::info," blacklist entry %s got dropped (already covered by %s)", iprtoa(&blacklist[i]), iprtoa(&blacklist[i - 1])); } blacklist.remove(i--); continue; } if(blacklist[i].lr <= blacklist[i - 1].ur) { if(scl.verbose) logger->writeline(log::info," blacklist entries %s and %s are joined due to overlap", iprtoa(&blacklist[i - 1]), iprtoa(&blacklist[i])); blacklist[i - 1].ur = blacklist[i].ur; blacklist.remove(i--); continue; } } if(scl.verbose) { loopv(blacklist) logger->writeline(log::info," %s", iprtoa(&blacklist[i])); } logger->writeline(log::info,"read %d (%d) blacklist entries from '%s'", blacklist.length(), orglength, blfilename); } bool checkblacklist(enet_uint32 ip) // ip: network byte order { iprange t; t.lr = ntohl(ip); // blacklist uses host byte order t.ur = 0; return blacklist.search(&t, cmpipmatch) != NULL; } struct pwddetail { string pwd; int line; bool denyadmin; // true: connect only }; vector adminpwds; #define ADMINPWD_MAXPAR 1 void readpwdfile(const char *name) { static string pwdfilename; static int pwdfilesize; const char *sep = " \t"; pwddetail c; char *p, *l; int i, len, line, par[ADMINPWD_MAXPAR]; if(!name && getfilesize(pwdfilename) == pwdfilesize) return; adminpwds.setsize(0); if(scl.adminpasswd[0]) { s_strcpy(c.pwd, scl.adminpasswd); c.line = 0; // commandline is 'line 0' c.denyadmin = false; adminpwds.add(c); } char *buf = loadcfgfile(pwdfilename, name, &len); pwdfilesize = len; if(!buf) return; p = buf; line = 1; if(scl.verbose) logger->writeline(log::info,"reading admin passwords '%s'", pwdfilename); while(p < buf + len) { l = p; p += strlen(p) + 1; l = strtok(l, sep); if(l) { s_strcpy(c.pwd, l); par[0] = 0; // default values for(i = 0; i < ADMINPWD_MAXPAR; i++) { if((l = strtok(NULL, sep)) != NULL) par[i] = atoi(l); else break; } //if(i > 0) { c.line = line; c.denyadmin = par[0] > 0; adminpwds.add(c); if(scl.verbose) logger->writeline(log::info,"line%4d: %s %d", c.line, c.pwd, c.denyadmin ? 1 : 0); } } line++; } delete[] buf; logger->writeline(log::info,"read %d admin passwords from '%s'", adminpwds.length() - (scl.adminpasswd[0] > 0), pwdfilename); } bool checkadmin(const char *name, const char *pwd, int salt, pwddetail *detail = NULL) { bool found = false; loopv(adminpwds) { if(!strcmp(genpwdhash(name, adminpwds[i].pwd, salt), pwd)) { if(detail) *detail = adminpwds[i]; found = true; break; } } return found; } bool updatedescallowed(void) { return scl.servdesc_pre[0] || scl.servdesc_suf[0]; } void updatesdesc(const char *newdesc, ENetAddress *caller = NULL) { if(!newdesc || !newdesc[0] || !updatedescallowed()) { s_strcpy(servdesc_current, scl.servdesc_full); custom_servdesc = false; } else { s_sprintf(servdesc_current)("%s%s%s", scl.servdesc_pre, newdesc, scl.servdesc_suf); custom_servdesc = true; if(caller) servdesc_caller = *caller; } } void resetvotes(int result) { loopv(clients) { clients[i]->vote = VOTE_NEUTRAL; if(result == VOTE_YES) clients[i]->lastvotecall = 0; // flowtron: successful votes and mapchange reset the timer } } void forceteam(int client, int team, bool respawn, bool notify = false) { if(!valid_client(client) || team < 0 || team > 1) return; if(clients[client]->lastforce && (servmillis - clients[client]->lastforce) < 2000) return; sendf(client, 1, "riii", SV_FORCETEAM, team, (respawn ? 1 : 0) | (respawn && !notify ? 2 : 0)); clients[client]->lastforce = servmillis; if(notify) sendf(-1, 1, "riii", SV_FORCENOTIFY, client, team); } int calcscores() // skill eval { int fp12 = (m_ctf || m_htf) ? 55 : 33; int fp3 = (m_ctf || m_htf) ? 25 : 15; int sum = 0; loopv(clients) if(clients[i]->type!=ST_EMPTY) { clientstate &cs = clients[i]->state; sum += clients[i]->at3_score = (cs.frags * 100) / (cs.deaths ? cs.deaths : 1) + (cs.flagscore < 3 ? fp12 * cs.flagscore : 2 * fp12 + fp3 * (cs.flagscore - 2)); } return sum; } ivector shuffle; void shuffleteams(bool respawn = true) { int numplayers = numclients(); int team, sums = calcscores(); if(gamemillis < 2 * 60 *1000) { // random int teamsize[2] = {0, 0}; loopv(clients) if(clients[i]->type!=ST_EMPTY) { sums += rnd(1000); team = sums & 1; if(teamsize[team] >= numplayers/2) team = team_opposite(team); forceteam(i, team, respawn); teamsize[team]++; sums >>= 1; } } else { // skill sorted shuffle.setsize(0); sums /= 4 * numplayers + 2; team = rnd(2); loopv(clients) if(clients[i]->type!=ST_EMPTY) { clients[i]->at3_score += rnd(sums | 1); shuffle.add(i); } shuffle.sort(cmpscore); loopi(shuffle.length()) { forceteam(shuffle[i], team, respawn); team = !team; } } } bool refillteams(bool now, bool notify) // force only minimal amounts of players { static int lasttime_eventeams = 0; int teamsize[2] = {0, 0}, teamscore[2] = {0, 0}, moveable[2] = {0, 0}; bool switched = false; calcscores(); loopv(clients) if(clients[i]->type!=ST_EMPTY) // playerlist stocktaking { client *c = clients[i]; c->at3_dontmove = true; if(c->isauthed) { int t = 0; if(!strcmp(c->team, "CLA") || t++ || !strcmp(c->team, "RVSF")) // need exact teams here { teamsize[t]++; teamscore[t] += c->at3_score; if(clienthasflag(i) < 0) { c->at3_dontmove = false; moveable[t]++; if(c->lastforce && (servmillis - c->lastforce) < 3000) return false; // possible unanswered forceteam commands } } } } int bigteam = teamsize[1] > teamsize[0]; int allplayers = teamsize[0] + teamsize[1]; int diffnum = teamsize[bigteam] - teamsize[!bigteam]; int diffscore = teamscore[bigteam] - teamscore[!bigteam]; if(lasttime_eventeams > gamemillis) lasttime_eventeams = 0; if(diffnum > 1) { if(now || gamemillis - lasttime_eventeams > 8000 + allplayers * 1000 || diffnum > 2 + allplayers / 10) { // time to even out teams loopv(clients) if(clients[i]->type!=ST_EMPTY && team_int(clients[i]->team) != bigteam) clients[i]->at3_dontmove = true; // dont move small team players while(diffnum > 1 && moveable[bigteam] > 0) { // pick best fitting cn string atlog, buf; // debug logging - will be removed int pick = -1; int bestfit = 1000000000; int targetscore = diffscore / (diffnum & ~1); s_sprintf(atlog)("at-target: %d, ", targetscore); loopv(clients) if(clients[i]->type!=ST_EMPTY && !clients[i]->at3_dontmove) // try all still movable players { int fit = targetscore - clients[i]->at3_score; if(fit < 0 ) fit = -(fit * 15) / 10; // avoid too good players int forcedelay = clients[i]->at3_lastforce ? (1000 - (gamemillis - clients[i]->at3_lastforce) / (5 * 60)) : 0; if(forcedelay > 0) fit += (fit * forcedelay) / 600; // avoid lately forced players if(fit < bestfit + fit * rnd(100) / 400) // search 'almost' best fit { bestfit = fit; pick = i; } s_sprintf(buf)("%d:%d ", i, fit); s_strcat(atlog, buf); } if(pick < 0) break; // should really never happen // move picked player forceteam(pick, !bigteam, true, notify); diffnum -= 2; diffscore -= 2 * clients[pick]->at3_score; moveable[bigteam]--; clients[pick]->at3_dontmove = true; clients[pick]->at3_lastforce = gamemillis; // try not to force this player again for the next 5 minutes switched = true; s_sprintf(buf)(" pick:%d", pick); s_strcat(atlog, buf); logger->writeline(log::info,"%s", atlog); } } } if(diffnum < 2) lasttime_eventeams = gamemillis; return switched; } void resetmap(const char *newname, int newmode, int newtime, bool notify) { if(m_demo) enddemoplayback(); else enddemorecord(); if(custom_servdesc && findcnbyaddress(&servdesc_caller) < 0) { updatesdesc(NULL); if(notify) { sendservmsg("server description reset to default"); logger->writeline(log::info, "server description reset to '%s'", servdesc_current); } } bool lastteammode = m_teammode; smode = newmode; s_strcpy(smapname, newname); if(isdedicated && smapname[0]) getservermap(); minremain = newtime >= 0 ? newtime : (m_teammode ? 15 : 10); gamemillis = 0; gamelimit = minremain*60000; mapreload = false; interm = 0; if(!laststatus) laststatus = servmillis-61*1000; lastfillup = servmillis; resetvotes(VOTE_YES); // flowtron: VOTE_YES => reset lastvotecall too resetitems(); mapstats *ms = getservermapstats(smapname); loopi(3) clnumspawn[i] = ms ? ms->spawns[i] : 0; loopi(2) clnumflagspawn[i] = ms ? ms->flags[i] : 0; if(ms) { entity e; loopi(ms->hdr.numents) { e.type = ms->enttypes[i]; e.transformtype(smode); server_entity se = { e.type, false, 0 }; sents.add(se); if(e.fitsmode(smode)) sents[i].spawned = true; } notgotitems = false; } scores.setsize(0); ctfreset(); if(notify) { // change map sendf(-1, 1, "risii", SV_MAPCHANGE, smapname, smode, mapavailable(smapname) ? 1 : 0); if(smode>1 || (smode==0 && numnonlocalclients()>0)) sendf(-1, 1, "ri2", SV_TIMEUP, minremain); } if(newname[0]) { logger->writeline(log::info, "\nGame start: %s on %s, %d players, %d minutes remaining, mastermode %d, (itemlist %spreloaded, 'getmap' %sprepared)", modestr(smode), smapname, numclients(), minremain, mastermode, ms ? "" : "not ", mapavailable(smapname) ? "" : "not "); } arenaround = 0; if(m_arena) { distributespawns(); } if(notify) { // shuffle if previous mode wasn't a team-mode if(m_teammode) { if(!lastteammode) shuffleteams(false); else if(autoteam) refillteams(true, false); } // send spawns loopv(clients) if(clients[i]->type!=ST_EMPTY) { client *c = clients[i]; c->mapchange(); if(m_mp(smode)) sendspawn(c); } } if(m_demo) setupdemoplayback(); else if((demonextmatch || scl.demoeverymatch) && *newname && numnonlocalclients() > 0) { demonextmatch = false; setupdemorecord(); } if(notify && m_ktf) sendflaginfo(); nextmapname[0] = '\0'; forceintermission = false; } int nextcfgset(bool notify = true, bool nochange = false) // load next maprotation set { int n = numclients(); int csl = configsets.length(); int ccs = curcfgset; if(ccs >= 0 && ccs < csl) ccs += configsets[ccs].skiplines; configset *c = NULL; loopi(csl) { ccs++; if(ccs >= csl || ccs < 0) ccs = 0; c = &configsets[ccs]; if(n >= c->minplayer && (!c->maxplayer || n <= c->maxplayer)) { if(getservermapstats(c->mapname)) break; else logger->writeline(log::info, "maprot error: map '%s' not found", c->mapname); } } if(!nochange) { curcfgset = ccs; resetmap(c->mapname, c->mode, c->time, notify); } return ccs; } bool isbanned(int cn) { if(!valid_client(cn)) return false; client &c = *clients[cn]; if(c.type==ST_LOCAL) return false; loopv(bans) { ban &b = bans[i]; if(b.millis < servmillis) { bans.remove(i--); } if(b.address.host == c.peer->address.host) { return true; } } return checkblacklist(c.peer->address.host); } int serveroperator() { loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->role > CR_DEFAULT) return i; return -1; } void sendserveropinfo(int receiver) { int op = serveroperator(); sendf(receiver, 1, "riii", SV_SERVOPINFO, op, op >= 0 ? clients[op]->role : -1); } #include "serveractions.h" struct voteinfo { int owner, callmillis, result; serveraction *action; voteinfo() : owner(0), callmillis(0), result(VOTE_NEUTRAL), action(NULL) {} void end(int result) { resetvotes(result); sendf(-1, 1, "ri2", SV_VOTERESULT, result); if(result == VOTE_YES && action) { //if(demorecord) enddemorecord(); action->perform(); } this->result = result; } bool isvalid() { return valid_client(owner) && action != NULL && action->isvalid(); } bool isalive() { return servmillis < callmillis+40*1000; } void evaluate(bool forceend = false) { if(result!=VOTE_NEUTRAL) return; // block double action if(action && !action->isvalid()) end(VOTE_NO); int stats[VOTE_NUM] = {0}; int adminvote = VOTE_NEUTRAL; loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->connectmillis < callmillis) { stats[clients[i]->vote]++; if(clients[i]->role==CR_ADMIN) adminvote = clients[i]->vote; }; bool admin = clients[owner]->role==CR_ADMIN || (!isdedicated && clients[owner]->type==ST_LOCAL); int total = stats[VOTE_NO]+stats[VOTE_YES]+stats[VOTE_NEUTRAL]; const float requiredcount = 0.51f; if(stats[VOTE_YES]/(float)total > requiredcount || admin || adminvote == VOTE_YES) end(VOTE_YES); else if(forceend || stats[VOTE_NO]/(float)total > requiredcount || stats[VOTE_NO] >= stats[VOTE_YES]+stats[VOTE_NEUTRAL] || adminvote == VOTE_NO) end(VOTE_NO); else return; } }; static voteinfo *curvote = NULL; bool vote(int sender, int vote, ENetPacket *msg) // true if the vote was placed successfully { if(!curvote || !valid_client(sender) || vote < VOTE_YES || vote > VOTE_NO) return false; if(clients[sender]->vote != VOTE_NEUTRAL) { sendf(sender, 1, "ri2", SV_CALLVOTEERR, VOTEE_MUL); return false; } else { sendpacket(-1, 1, msg, sender); clients[sender]->vote = vote; curvote->evaluate(); return true; } } void callvotesuc(voteinfo *v) { if(!v->isvalid()) return; DELETEP(curvote); curvote = v; clients[v->owner]->lastvotecall = servmillis; sendf(v->owner, 1, "ri", SV_CALLVOTESUC); logger->writeline(log::info, "[%s] client %s called a vote: %s", clients[v->owner]->hostname, clients[v->owner]->name, v->action->desc ? v->action->desc : "[unknown]"); } void callvoteerr(voteinfo *v, int error) { if(!valid_client(v->owner)) return; sendf(v->owner, 1, "ri2", SV_CALLVOTEERR, error); logger->writeline(log::info, "[%s] client %s failed to call a vote: %s (%s)", clients[v->owner]->hostname, clients[v->owner]->name, v->action->desc ? v->action->desc : "[unknown]", voteerrorstr(error)); } bool callvote(voteinfo *v, ENetPacket *msg) // true if a regular vote was called { int area = isdedicated ? EE_DED_SERV : EE_LOCAL_SERV; int error = -1; if(!v || !v->isvalid()) error = VOTEE_INVALID; else if(v->action->role > clients[v->owner]->role) error = VOTEE_PERMISSION; else if(!(area & v->action->area)) error = VOTEE_AREA; else if(curvote && curvote->result==VOTE_NEUTRAL) error = VOTEE_CUR; else if(clients[v->owner]->role == CR_DEFAULT && v->action->isdisabled()) error = VOTEE_DISABLED; else if(clients[v->owner]->lastvotecall && servmillis - clients[v->owner]->lastvotecall < 60*1000 && clients[v->owner]->role != CR_ADMIN && numclients()>1) error = VOTEE_MAX; if(error>=0) { callvoteerr(v, error); return false; } else { sendpacket(-1, 1, msg, v->owner); callvotesuc(v); return true; } } void changeclientrole(int client, int role, char *pwd, bool force) { pwddetail pd; if(!isdedicated || !valid_client(client)) return; pd.line = -1; if(force || role == CR_DEFAULT || (role == CR_ADMIN && pwd && pwd[0] && checkadmin(clients[client]->name, pwd, clients[client]->salt, &pd) && !pd.denyadmin)) { if(role == clients[client]->role) return; if(role > CR_DEFAULT) { loopv(clients) clients[i]->role = CR_DEFAULT; } clients[client]->role = role; sendserveropinfo(-1); if(pd.line > -1) logger->writeline(log::info,"[%s] player %s used admin password in line %d", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", pd.line); logger->writeline(log::info,"[%s] set role of player %s to %s", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", role == CR_ADMIN ? "admin" : "normal player"); // flowtron : connecting players haven't got a name yet (connectadmin) } else if(pwd && pwd[0]) disconnect_client(client, DISC_SOPLOGINFAIL); // avoid brute-force if(curvote) curvote->evaluate(); } const char *disc_reason(int reason) { static const char *disc_reasons[] = { "normal", "end of packet", "client num", "kicked by server operator", "banned by server operator", "tag type", "connection refused due to ban", "wrong password", "failed admin login", "server FULL - maxclients", "server mastermode is \"private\"", "auto kick - did your score drop below the threshold?", "auto ban - did your score drop below the threshold?", "duplicate connection" }; return reason >= 0 && (size_t)reason < sizeof(disc_reasons)/sizeof(disc_reasons[0]) ? disc_reasons[reason] : "unknown"; } void disconnect_client(int n, int reason) { if(!clients.inrange(n) || clients[n]->type!=ST_TCPIP) return; dropflag(n); client &c = *clients[n]; const char *scoresaved = ""; if(c.haswelcome) { savedscore *sc = findscore(c, true); if(sc) { sc->save(c.state); scoresaved = " (score saved)"; } } if(reason>=0) logger->writeline(log::info, "[%s] disconnecting client %s (%s) (cn %d)%s", c.hostname, c.name, disc_reason(reason), n, scoresaved); else logger->writeline(log::info, "[%s] disconnected client %s (cn %d)%s", c.hostname, c.name, n, scoresaved); c.peer->data = (void *)-1; if(reason>=0) enet_peer_disconnect(c.peer, reason); clients[n]->zap(); sendf(-1, 1, "rii", SV_CDIS, n); if(curvote) curvote->evaluate(); } void sendwhois(int sender, int cn) { if(!valid_client(sender) || !valid_client(cn)) return; if(clients[cn]->type == ST_TCPIP && clients[cn]->peer) { uint ip = clients[cn]->peer->address.host; if(clients[sender]->role != CR_ADMIN) ip &= 0xFFFF; // only admin gets full IP sendf(sender, 1, "ri3", SV_WHOISINFO, cn, ip); } } // sending of maps between clients string copyname; int copysize, copymapsize, copycfgsize, copycfgsizegz; uchar *copydata = NULL; bool mapavailable(const char *mapname) { return copydata && !strcmp(copyname, behindpath(mapname)); } bool sendmapserv(int n, string mapname, int mapsize, int cfgsize, int cfgsizegz, uchar *data) { string name; FILE *fp; bool written = false; if(!mapname[0] || mapsize <= 0 || mapsize + cfgsizegz > MAXMAPSENDSIZE || cfgsize > MAXCFGFILESIZE) return false; s_strcpy(copyname, mapname); copymapsize = mapsize; copycfgsize = cfgsize; copycfgsizegz = cfgsizegz; copysize = mapsize + cfgsizegz; DELETEA(copydata); copydata = new uchar[copysize]; memcpy(copydata, data, copysize); s_sprintf(name)(SERVERMAP_PATH_INCOMING "%s.cgz", behindpath(copyname)); path(name); fp = fopen(name, "wb"); if(fp) { fwrite(copydata, 1, copymapsize, fp); fclose(fp); s_sprintf(name)(SERVERMAP_PATH_INCOMING "%s.cfg", behindpath(copyname)); path(name); fp = fopen(name, "wb"); if(fp) { uchar *rawcfg = new uchar[copycfgsize]; uLongf rawsize = copycfgsize; if(uncompress(rawcfg, &rawsize, copydata + copymapsize, copycfgsizegz) == Z_OK && rawsize - copycfgsize == 0) fwrite(rawcfg, 1, copycfgsize, fp); fclose(fp); DELETEA(rawcfg); written = true; } } return written; } ENetPacket *getmapserv(int n) { if(!mapavailable(smapname)) return NULL; ENetPacket *packet = enet_packet_create(NULL, MAXTRANS + copysize, ENET_PACKET_FLAG_RELIABLE); ucharbuf p(packet->data, packet->dataLength); putint(p, SV_RECVMAP); sendstring(copyname, p); putint(p, copymapsize); putint(p, copycfgsize); putint(p, copycfgsizegz); p.put(copydata, copysize); enet_packet_resize(packet, p.length()); return packet; } // provide maps by the server mapstats *getservermapstats(const char *mapname) { const char *name = behindpath(mapname); s_sprintfd(filename)(SERVERMAP_PATH "%s.cgz", name); path(filename); bool found = fileexists(filename, "r"); if(!found) { s_sprintf(filename)(SERVERMAP_PATH_INCOMING "%s.cgz", name); path(filename); found = fileexists(filename, "r"); if(!found) { s_sprintf(filename)(SERVERMAP_PATH_BUILTIN "%s.cgz", name); path(filename); found = fileexists(filename, "r"); } } return found ? loadmapstats(filename) : NULL; } #define GZBUFSIZE ((MAXCFGFILESIZE * 11) / 10) void getservermap(void) { static uchar *gzbuf = NULL; string cgzname, cfgname; int cgzsize, cfgsize, cfgsizegz; const char *name = behindpath(smapname); // no paths allowed here if(!gzbuf) gzbuf = new uchar[GZBUFSIZE]; if(!gzbuf) return; if(!strcmp(name, behindpath(copyname))) return; s_sprintf(cgzname)(SERVERMAP_PATH "%s.cgz", name); path(cgzname); if(fileexists(cgzname, "r")) { s_sprintf(cfgname)(SERVERMAP_PATH "%s.cfg", name); } else { s_sprintf(cgzname)(SERVERMAP_PATH_INCOMING "%s.cgz", name); path(cgzname); s_sprintf(cfgname)(SERVERMAP_PATH_INCOMING "%s.cfg", name); } path(cfgname); uchar *cgzdata = (uchar *)loadfile(cgzname, &cgzsize); uchar *cfgdata = (uchar *)loadfile(cfgname, &cfgsize); if(cgzdata && (!cfgdata || cfgsize < MAXCFGFILESIZE)) { uLongf gzbufsize = GZBUFSIZE; if(!cfgdata || compress2(gzbuf, &gzbufsize, cfgdata, cfgsize, 9) != Z_OK) { cfgsize = 0; gzbufsize = 0; } cfgsizegz = (int) gzbufsize; if(cgzsize + cfgsizegz < MAXMAPSENDSIZE) { s_strcpy(copyname, name); copymapsize = cgzsize; copycfgsize = cfgsize; copycfgsizegz = cfgsizegz; copysize = cgzsize + cfgsizegz; DELETEA(copydata); copydata = new uchar[copysize]; memcpy(copydata, cgzdata, cgzsize); memcpy(copydata + cgzsize, gzbuf, cfgsizegz); logger->writeline(log::info,"loaded map %s, %d + %d(%d) bytes.", cgzname, cgzsize, cfgsize, cfgsizegz); } } DELETEA(cgzdata); DELETEA(cfgdata); } void sendresume(client &c, bool broadcast) { sendf(broadcast ? -1 : c.clientnum, 1, "rxi2i9vvi", broadcast ? c.clientnum : -1, SV_RESUME, c.clientnum, c.state.state, c.state.lifesequence, c.state.primary, c.state.gunselect, c.state.flagscore, c.state.frags, c.state.deaths, c.state.health, c.state.armour, NUMGUNS, c.state.ammo, NUMGUNS, c.state.mag, -1); } void sendinits2c(client &c) { sendf(c.clientnum, 1, "ri5", SV_INITS2C, c.clientnum, PROTOCOL_VERSION, c.salt, scl.serverpassword[0] ? 1 : 0); } void welcomepacket(ucharbuf &p, int n, ENetPacket *packet, bool forcedeath) { #define CHECKSPACE(n) \ { \ int space = (n); \ if(p.remaining() < space) \ { \ enet_packet_resize(packet, packet->dataLength + max(MAXTRANS, space - p.remaining())); \ p.buf = packet->data; \ p.maxlen = (int)packet->dataLength; \ } \ } if(!smapname[0] && configsets.length()) nextcfgset(false); client *c = valid_client(n) ? clients[n] : NULL; int numcl = numclients(); putint(p, SV_WELCOME); putint(p, smapname[0] && !m_demo ? numcl : -1); if(smapname[0] && !m_demo) { putint(p, SV_MAPCHANGE); sendstring(smapname, p); putint(p, smode); putint(p, mapavailable(smapname) ? 1 : 0); if(smode>1 || (smode==0 && numnonlocalclients()>0)) { putint(p, SV_TIMEUP); putint(p, minremain); } if(numcl>1) { putint(p, SV_ITEMLIST); loopv(sents) if(sents[i].spawned) { putint(p, i); putint(p, sents[i].type); CHECKSPACE(256); } putint(p, -1); } if(m_flags) { CHECKSPACE(256); loopi(2) putflaginfo(p, i); } } if(c && c->type == ST_TCPIP && serveroperator() != -1) sendserveropinfo(n); if(numcl>1) { putint(p, SV_FORCETEAM); putint(p, freeteam(n)); putint(p, 0); } if(c) c->lastforce = servmillis; bool restored = false; if(c) { if(c->type==ST_TCPIP) { savedscore *sc = findscore(*c, false); if(sc) { sc->restore(c->state); restored = true; } } CHECKSPACE(256); if(!canspawn(c, true) || forcedeath) { putint(p, SV_FORCEDEATH); putint(p, n); sendf(-1, 1, "ri2x", SV_FORCEDEATH, n, n); } else { clientstate &gs = c->state; spawnstate(c); putint(p, SV_SPAWNSTATE); putint(p, gs.lifesequence); putint(p, gs.health); putint(p, gs.armour); putint(p, gs.primary); putint(p, gs.gunselect); putint(p, -1); loopi(NUMGUNS) putint(p, gs.ammo[i]); loopi(NUMGUNS) putint(p, gs.mag[i]); gs.lastspawn = gamemillis; } } if(clients.length()>1 || restored) { putint(p, SV_RESUME); loopv(clients) { client &c = *clients[i]; if(c.type!=ST_TCPIP || (c.clientnum==n && !restored)) continue; CHECKSPACE(256); putint(p, c.clientnum); putint(p, c.state.state); putint(p, c.state.lifesequence); putint(p, c.state.primary); putint(p, c.state.gunselect); putint(p, c.state.flagscore); putint(p, c.state.frags); putint(p, c.state.deaths); putint(p, c.state.health); putint(p, c.state.armour); loopi(NUMGUNS) putint(p, c.state.ammo[i]); loopi(NUMGUNS) putint(p, c.state.mag[i]); } putint(p, -1); } putint(p, SV_AUTOTEAM); putint(p, autoteam ? 1 : 0); if(scl.motd[0]) { CHECKSPACE(5+2*(int)strlen(scl.motd)+1); putint(p, SV_TEXT); sendstring(scl.motd, p); } #undef CHECKSPACE } void sendwelcome(client *cl, int chan, bool forcedeath) { ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); ucharbuf p(packet->data, packet->dataLength); welcomepacket(p, cl->clientnum, packet, forcedeath); enet_packet_resize(packet, p.length()); sendpacket(cl->clientnum, chan, packet); if(!packet->referenceCount) enet_packet_destroy(packet); cl->haswelcome = true; } int checktype(int type, client *cl) { if(cl && cl->type==ST_LOCAL) return type; // only allow edit messages in coop-edit mode static int edittypes[] = { SV_EDITENT, SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, SV_NEWMAP }; if(cl && smode!=1) loopi(sizeof(edittypes)/sizeof(int)) if(type == edittypes[i]) return -1; // server only messages static int servtypes[] = { SV_INITS2C, SV_MAPRELOAD, SV_SERVMSG, SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_DIED, SV_SPAWNSTATE, SV_FORCEDEATH, SV_ITEMACC, SV_ITEMSPAWN, SV_TIMEUP, SV_CDIS, SV_PONG, SV_RESUME, SV_FLAGINFO, SV_FLAGMSG, SV_FLAGCNT, SV_ARENAWIN, SV_SENDDEMOLIST, SV_SENDDEMO, SV_DEMOPLAYBACK, SV_CLIENT, SV_CALLVOTESUC, SV_CALLVOTEERR, SV_VOTERESULT, SV_WHOISINFO }; if(cl) loopi(sizeof(servtypes)/sizeof(int)) if(type == servtypes[i]) return -1; return type; } // server side processing of updates: does very little and most state is tracked client only // could be extended to move more gameplay to server (at expense of lag) void process(ENetPacket *packet, int sender, int chan) // sender may be -1 { ucharbuf p(packet->data, packet->dataLength); char text[MAXTRANS]; client *cl = sender>=0 ? clients[sender] : NULL; pwddetail pd; int type; if(cl && !cl->isauthed) { int clientrole = CR_DEFAULT; if(chan==0) return; else if(chan!=1 || getint(p)!=SV_CONNECT) disconnect_client(sender, DISC_TAGT); else { getstring(text, p); filtertext(text, text, 0, MAXNAMELEN); if(!text[0]) s_strcpy(text, "unarmed"); s_strncpy(cl->name, text, MAXNAMELEN+1); getstring(text, p); int wantrole = getint(p); cl->state.nextprimary = getint(p); bool banned = isbanned(sender); bool srvfull = numnonlocalclients() > scl.maxclients; bool srvprivate = mastermode == MM_PRIVATE; if(checkadmin(cl->name, text, cl->salt, &pd) && (!pd.denyadmin || (banned && !srvfull && !srvprivate))) // pass admins always through { bool banremoved = false; cl->isauthed = true; if(!pd.denyadmin && wantrole == CR_ADMIN) clientrole = CR_ADMIN; if(banned) { loopv(bans) if(bans[i].address.host == cl->peer->address.host) { banremoved = true; bans.remove(i); break; } // remove admin bans } if(srvfull) { loopv(clients) if(i != sender && clients[i]->type==ST_TCPIP) { disconnect_client(i, DISC_MAXCLIENTS); // disconnect someone else to fit maxclients again break; } } logger->writeline(log::info, "[%s] logged in using the admin password in line %d%s", cl->hostname, pd.line, banremoved ? ", (ban removed)" : ""); } else if(scl.serverpassword[0]) { if(!strcmp(genpwdhash(cl->name, scl.serverpassword, cl->salt), text)) { cl->isauthed = true; logger->writeline(log::info, "[%s] client logged in (using serverpassword)", cl->hostname); } else disconnect_client(sender, DISC_WRONGPW); } else if(srvprivate) disconnect_client(sender, DISC_MASTERMODE); else if(srvfull) disconnect_client(sender, DISC_MAXCLIENTS); else if(banned) disconnect_client(sender, DISC_BANREFUSE); else cl->isauthed = true; } if(!cl->isauthed) return; if(cl->type==ST_TCPIP) { loopv(clients) if(i != sender) { client *dup = clients[i]; if(dup->type==ST_TCPIP && dup->peer->address.host==cl->peer->address.host && dup->peer->address.port==cl->peer->address.port) disconnect_client(i, DISC_DUP); } } sendwelcome(cl); if(findscore(*cl, false)) sendresume(*cl, true); if(clientrole != CR_DEFAULT) changeclientrole(sender, clientrole, NULL, true); } if(packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true; #define QUEUE_MSG { if(cl->type==ST_TCPIP) while(curmsgmessages.add(p.buf[curmsg++]); } #define QUEUE_BUF(size, body) { \ if(cl->type==ST_TCPIP) \ { \ curmsg = p.length(); \ ucharbuf buf = cl->messages.reserve(size); \ { body; } \ cl->messages.addbuf(buf); \ } \ } #define QUEUE_INT(n) QUEUE_BUF(5, putint(buf, n)) #define QUEUE_UINT(n) QUEUE_BUF(4, putuint(buf, n)) #define QUEUE_STR(text) QUEUE_BUF(2*(int)strlen(text)+1, sendstring(text, buf)) #define MSG_PACKET(packet) \ ENetPacket *packet = enet_packet_create(NULL, 16 + p.length() - curmsg, ENET_PACKET_FLAG_RELIABLE); \ ucharbuf buf(packet->data, packet->dataLength); \ putint(buf, SV_CLIENT); \ putint(buf, cl->clientnum); \ putuint(buf, p.length() - curmsg); \ buf.put(&p.buf[curmsg], p.length() - curmsg); \ enet_packet_resize(packet, buf.length()); int curmsg; while((curmsg = p.length()) < p.maxlen) { type = checktype(getint(p), cl); #ifdef _DEBUG if(type!=SV_POS && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_CLIENT) { DEBUGVAR(cl->name); ASSERT(type>=0 && typewriteline(log::info, "[%s] %s says to team %s: '%s'", cl->hostname, cl->name, cl->team, text); sendteamtext(text, sender); } else { logger->writeline(log::info, "[%s] %s says to team %s: '%s', SPAM detected", cl->hostname, cl->name, cl->team, text); sendservmsg("\f3please do not spam", sender); } break; case SV_TEXT: { int mid1 = curmsg, mid2 = p.length(); getstring(text, p); filtertext(text, text); if(!spamdetect(cl, text)) { logger->writeline(log::info, "[%s] %s says: '%s'", cl->hostname, cl->name, text); if(cl->type==ST_TCPIP) while(mid1messages.add(p.buf[mid1++]); QUEUE_STR(text); } else { logger->writeline(log::info, "[%s] %s says: '%s', SPAM detected", cl->hostname, cl->name, text); sendservmsg("\f3please do not spam", sender); } break; } case SV_VOICECOM: getint(p); QUEUE_MSG; break; case SV_VOICECOMTEAM: sendvoicecomteam(getint(p), sender); break; case SV_INITC2S: { QUEUE_MSG; getstring(text, p); filtertext(text, text, 0, MAXNAMELEN); if(!text[0]) s_strcpy(text, "unarmed"); QUEUE_STR(text); if(strcmp(cl->name, text)) logger->writeline(log::info,"[%s] %s changed his name to %s", cl->hostname, cl->name, text); s_strncpy(cl->name, text, MAXNAMELEN+1); getstring(text, p); filtertext(cl->team, text, 0, MAXTEAMLEN); QUEUE_STR(text); cl->skin = getint(p); QUEUE_MSG; break; } case SV_ITEMLIST: { int n; while((n = getint(p))!=-1) { server_entity se = { getint(p), false, 0 }; if(notgotitems) { while(sents.length()<=n) sents.add(se); sents[n].spawned = true; } } notgotitems = false; break; } case SV_SPAWNLIST: { if(getint(p) > 0) { loopi(3) clnumspawn[i] = getint(p); loopi(2) clnumflagspawn[i] = getint(p); } QUEUE_MSG; break; } case SV_ITEMPICKUP: { int n = getint(p); if(!arenaround || arenaround - gamemillis > 2000) { gameevent &pickup = cl->addevent(); pickup.type = GE_PICKUP; pickup.pickup.ent = n; } break; } case SV_WEAPCHANGE: { int gunselect = getint(p); if(gunselect<0 && gunselect>=NUMGUNS) break; cl->state.gunselect = gunselect; QUEUE_MSG; break; } case SV_PRIMARYWEAP: { int nextprimary = getint(p); if(nextprimary<0 && nextprimary>=NUMGUNS) break; cl->state.nextprimary = nextprimary; break; } case SV_CHANGETEAM: if(cl->state.state==CS_ALIVE) { cl->state.state = CS_DEAD; cl->state.respawn(); sendf(-1, 1, "rii", SV_FORCEDEATH, cl->clientnum); } break; case SV_TRYSPAWN: if(cl->state.state!=CS_DEAD || cl->state.lastspawn>=0 || !canspawn(cl)) break; if(cl->state.lastdeath) cl->state.respawn(); sendspawn(cl); break; case SV_SPAWN: { int ls = getint(p), gunselect = getint(p); if((cl->state.state!=CS_ALIVE && cl->state.state!=CS_DEAD) || ls!=cl->state.lifesequence || cl->state.lastspawn<0 || gunselect<0 || gunselect>=NUMGUNS) break; cl->state.lastspawn = -1; cl->state.state = CS_ALIVE; cl->state.gunselect = gunselect; QUEUE_BUF(5*(5 + 2*NUMGUNS), { putint(buf, SV_SPAWN); putint(buf, cl->state.lifesequence); putint(buf, cl->state.health); putint(buf, cl->state.armour); putint(buf, cl->state.gunselect); loopi(NUMGUNS) putint(buf, cl->state.ammo[i]); loopi(NUMGUNS) putint(buf, cl->state.mag[i]); }); break; } case SV_SUICIDE: { gameevent &suicide = cl->addevent(); suicide.type = GE_SUICIDE; break; } case SV_SHOOT: { gameevent &shot = cl->addevent(); shot.type = GE_SHOT; #define seteventmillis(event) \ { \ event.id = getint(p); \ if(!cl->timesync || (cl->events.length()==1 && cl->state.waitexpired(gamemillis))) \ { \ cl->timesync = true; \ cl->gameoffset = gamemillis - event.id; \ event.millis = gamemillis; \ } \ else event.millis = cl->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 = cl->addevent(); hit.type = GE_HIT; hit.hit.target = getint(p); hit.hit.lifesequence = getint(p); hit.hit.info = getint(p); loopk(3) hit.hit.dir[k] = getint(p)/DNF; } break; } case SV_EXPLODE: { gameevent &exp = cl->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 = cl->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_AKIMBO: { gameevent &akimbo = cl->addevent(); akimbo.type = GE_AKIMBO; seteventmillis(akimbo.akimbo); break; } case SV_RELOAD: { gameevent &reload = cl->addevent(); reload.type = GE_RELOAD; seteventmillis(reload.reload); reload.reload.gun = getint(p); break; } case SV_PING: sendf(sender, 1, "ii", SV_PONG, getint(p)); break; case SV_CLIENTPING: { int ping = getint(p); if(cl) cl->ping = ping; QUEUE_MSG; break; } case SV_POS: { int cn = getint(p); if(cn!=sender) { disconnect_client(sender, DISC_CN); #ifndef STANDALONE conoutf("ERROR: invalid client (msg %i)", type); #endif return; } loopi(3) clients[cn]->state.o[i] = getuint(p)/DMF; getuint(p); loopi(5) getint(p); getuint(p); if(cl->type==ST_TCPIP && (cl->state.state==CS_ALIVE || cl->state.state==CS_EDITING)) { cl->position.setsizenodelete(0); while(curmsgposition.add(p.buf[curmsg++]); } break; } case SV_NEXTMAP: { getstring(text, p); filtertext(text, text); int mode = getint(p); if(mapreload || numclients() == 1) resetmap(text, mode); break; } case SV_SENDMAP: { getstring(text, p); filtertext(text, text); int mapsize = getint(p); int cfgsize = getint(p); int cfgsizegz = getint(p); if(p.remaining() < mapsize + cfgsizegz) { p.forceoverread(); break; } if(sendmapserv(sender, text, mapsize, cfgsize, cfgsizegz, &p.buf[p.len])) { logger->writeline(log::info,"[%s] %s sent map %s, %d + %d(%d) bytes written", clients[sender]->hostname, clients[sender]->name, text, mapsize, cfgsize, cfgsizegz); } p.len += mapsize + cfgsizegz; break; } case SV_RECVMAP: { ENetPacket *mappacket = getmapserv(cl->clientnum); if(mappacket) { resetflag(cl->clientnum); // drop ctf flag // save score savedscore *sc = findscore(*cl, true); if(sc) sc->save(cl->state); // resend state properly sendpacket(cl->clientnum, 2, mappacket); cl->mapchange(); sendwelcome(cl, 2, true); } else sendservmsg("no map to get", cl->clientnum); break; } case SV_FLAGACTION: { int action = getint(p); int flag = getint(p); if(!m_flags || flag < 0 || flag > 1 || action < 0 || action > FA_NUM) break; flagaction(flag, action, sender); break; } case SV_SETADMIN: { bool claim = getint(p) != 0; getstring(text, p); changeclientrole(sender, claim ? CR_ADMIN : CR_DEFAULT, text); break; } case SV_CALLVOTE: { voteinfo *vi = new voteinfo; int type = getint(p); switch(type) { case SA_MAP: { getstring(text, p); filtertext(text, text); int mode = getint(p); if(mode==GMODE_DEMO) vi->action = new demoplayaction(text); else vi->action = new mapaction(newstring(text), mode, sender); break; } case SA_KICK: vi->action = new kickaction(getint(p)); break; case SA_BAN: vi->action = new banaction(getint(p)); break; case SA_REMBANS: vi->action = new removebansaction(); break; case SA_MASTERMODE: vi->action = new mastermodeaction(getint(p)); break; case SA_AUTOTEAM: vi->action = new autoteamaction(getint(p) > 0); break; case SA_SHUFFLETEAMS: vi->action = new shuffleteamaction(); break; case SA_FORCETEAM: vi->action = new forceteamaction(getint(p), sender); break; case SA_GIVEADMIN: vi->action = new giveadminaction(getint(p)); break; case SA_RECORDDEMO: vi->action = new recorddemoaction(getint(p)!=0); break; case SA_STOPDEMO: vi->action = new stopdemoaction(); break; case SA_CLEARDEMOS: vi->action = new cleardemosaction(getint(p)); break; case SA_SERVERDESC: getstring(text, p); filtertext(text, text); vi->action = new serverdescaction(newstring(text), sender); break; } vi->owner = sender; vi->callmillis = servmillis; MSG_PACKET(msg); if(!callvote(vi, msg)) delete vi; if(!msg->referenceCount) enet_packet_destroy(msg); break; } case SV_VOTE: { int n = getint(p); MSG_PACKET(msg); vote(sender, n, msg); if(valid_client(sender) && !msg->referenceCount) enet_packet_destroy(msg); // check sender existence first because he might have been disconnected due to a vote break; } case SV_WHOIS: { sendwhois(sender, getint(p)); break; } case SV_LISTDEMOS: listdemos(sender); break; case SV_GETDEMO: senddemo(sender, getint(p)); break; case SV_EXTENSION: { // AC server extensions // // rules: // 1. extensions MUST NOT modify gameplay or the beavior of the game in any way // 2. extensions may ONLY be used to extend or automate server administration tasks // 3. extensions may ONLY operate on the server and must not send any additional data to the connected clients // 4. extensions not adhering to these rules may cause the hosting server being banned from the masterserver // // also note that there is no guarantee that custom extensions will work in future AC versions getstring(text, p, 64); char *ext = text; // extension specifier in the form of OWNER::EXTENSION, see sample below int n = getint(p); // length of data after the specifier if(n > 50) return; // sample if(!strcmp(ext, "driAn::writelog")) { // owner: driAn - root@sprintf.org // extension: writelog - WriteLog v1.0 // description: writes a custom string to the server log // access: requires admin privileges // usage: /serverextension driAn::writelog "your log message here.." getstring(text, p, n); if(valid_client(sender) && clients[sender]->role==CR_ADMIN && logger) logger->writeline(log::info, "%s", text); } // else if() // add other extensions here else for(; n > 0; n--) getint(p); // ignore unknown extensions break; } default: { int size = msgsizelookup(type); if(size==-1) { if(sender>=0) disconnect_client(sender, DISC_TAGT); return; } loopi(size-1) getint(p); QUEUE_MSG; break; } } } if(p.overread() && sender>=0) disconnect_client(sender, DISC_EOP); #ifdef _DEBUG protocoldebug(false); #endif } void localclienttoserver(int chan, ENetPacket *packet) { process(packet, 0, chan); } client &addclient() { client *c = NULL; loopv(clients) if(clients[i]->type==ST_EMPTY) { c = clients[i]; break; } if(!c) { c = new client; c->clientnum = clients.length(); clients.add(c); } c->reset(); return *c; } void checkintermission() { if(minremain>0) { minremain = gamemillis>=gamelimit || forceintermission ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000; sendf(-1, 1, "ri2", SV_TIMEUP, minremain); } if(!interm && minremain<=0) interm = gamemillis+10000; forceintermission = false; } void resetserverifempty() { loopv(clients) if(clients[i]->type!=ST_EMPTY) return; resetmap("", 0, 10, false); mastermode = MM_OPEN; autoteam = true; nextmapname[0] = '\0'; } void sendworldstate() { static enet_uint32 lastsend = 0; if(clients.empty()) return; enet_uint32 curtime = enet_time_get()-lastsend; if(curtime<40) return; bool flush = buildworldstate(); lastsend += curtime - (curtime%40); if(flush) enet_host_flush(serverhost); if(demorecord) recordpackets = true; // enable after 'old' worldstate is sent } void rereadcfgs(void) { static int cfgupdate; if(!cfgupdate || servmillis - cfgupdate > 10 * 60 * 1000) { cfgupdate = servmillis; readscfg(NULL); readblacklist(NULL); readpwdfile(NULL); } } void loggamestatus(const char *reason) { int fragscore[2] = {0, 0}, flagscore[2] = {0, 0}, pnum[2] = {0, 0}, n; string text1, text2; s_sprintf(text1)("%d minutes remaining", minremain); logger->writeline(log::info, "\nGame status: %s on %s, %s, %s%c %s", modestr(gamemode), smapname, reason ? reason : text1, mmfullname(mastermode), custom_servdesc ? ',' : '\0', servdesc_current); logger->writeline(log::info, "cn name %sfrag death %srole host", m_teammode ? "team " : "", m_flags ? "flags " : ""); loopv(clients) { if(clients[i]->type == ST_EMPTY || !clients[i]->name[0]) continue; s_sprintf(text1)("%2d %-16s%c%-4s", clients[i]->clientnum, clients[i]->name, m_teammode ? ' ' : '\0', clients[i]->team); s_sprintf(text2)(" %4d %5d%c%5d", clients[i]->state.frags, clients[i]->state.deaths, m_flags ? ' ' : '\0', clients[i]->state.flagscore); logger->writeline(log::info, "%s%s %-6s %s", text1, text2, clients[i]->role == CR_ADMIN ? "admin" : "normal", clients[i]->hostname); n = team_int(clients[i]->team); flagscore[n] += clients[i]->state.flagscore; fragscore[n] += clients[i]->state.frags; pnum[n] += 1; } if(m_teammode) { loopi(2) logger->writeline(log::info, "Team %4s:%3d players,%5d frags%c%5d flags", team_string(i), pnum[i], fragscore[i], m_flags ? ',' : '\0', flagscore[i]); } logger->writeline(log::info, ""); } void serverslice(uint timeout) // main server update, called from cube main loop in sp, or dedicated server loop { static int msend = 0, mrec = 0, csend = 0, crec = 0, mnum = 0, cnum = 0; #ifdef STANDALONE int nextmillis = (int)enet_time_get(); if(svcctrl) svcctrl->keepalive(); #else int nextmillis = isdedicated ? (int)enet_time_get() : lastmillis; #endif int diff = nextmillis - servmillis; gamemillis += diff; servmillis = nextmillis; if(m_demo) readdemo(); if(minremain>0) { processevents(); checkitemspawns(diff); bool ktfflagingame = false; if(m_flags) loopi(2) { sflaginfo &f = sflaginfos[i]; if(f.state == CTFF_DROPPED && gamemillis-f.lastupdate > (m_ctf ? 30000 : 10000)) flagaction(i, FA_RESET, -1); if(m_htf && f.state == CTFF_INBASE && gamemillis-f.lastupdate > (clnumflagspawn[0] && clnumflagspawn[1] ? 10000 : 1000)) { htf_forceflag(i); } if(m_ktf && f.state == CTFF_STOLEN && gamemillis-f.lastupdate > 15000) { flagaction(i, FA_SCORE, -1); } if(f.state == CTFF_INBASE || f.state == CTFF_STOLEN) ktfflagingame = true; } if(m_ktf && !ktfflagingame) flagaction(rnd(2), FA_RESET, -1); // ktf flag watchdog if(m_arena) arenacheck(); } if(curvote) { if(!curvote->isalive()) curvote->evaluate(true); if(curvote->result!=VOTE_NEUTRAL) DELETEP(curvote); } int nonlocalclients = numnonlocalclients(); if(forceintermission || ((smode>1 || (gamemode==0 && nonlocalclients)) && gamemillis-diff>0 && gamemillis/60000!=(gamemillis-diff)/60000)) checkintermission(); if(interm && gamemillis>interm) { loggamestatus("game finished"); if(demorecord) enddemorecord(); interm = 0; //start next game if(nextmapname[0]) resetmap(nextmapname, nextgamemode); else if(configsets.length()) nextcfgset(); else loopv(clients) if(clients[i]->type!=ST_EMPTY) { sendf(i, 1, "rii", SV_MAPRELOAD, 0); // ask a client to trigger map reload mapreload = true; break; } } resetserverifempty(); if(!isdedicated) return; // below is network only serverms(smode, numclients(), minremain, smapname, servmillis, serverhost->address, &mnum, &msend, &mrec, &cnum, &csend, &crec); if(autoteam && m_teammode && !m_arena && !interm && servmillis - lastfillup > 5000 && refillteams()) lastfillup = servmillis; if(servmillis-laststatus>60*1000) // display bandwidth stats, useful for server ops { laststatus = servmillis; rereadcfgs(); if(nonlocalclients || bsend || brec || (msend || mrec || csend || crec)) { if(nonlocalclients) loggamestatus(NULL); time_t rawtime; struct tm * timeinfo; char buffer [80]; time (&rawtime); timeinfo = localtime(&rawtime); strftime (buffer,80,"%d-%m-%Y %H:%M:%S",timeinfo); logger->writeline(log::info, "Status at %s: %d remote clients, %.1f send, %.1f rec (K/sec)", buffer, nonlocalclients, bsend/60.0f/1024, brec/60.0f/1024); logger->writeline(log::info, "Ping num: %d, %d send, %d rec; CSL num: %d, %d send, %d rec (bytes)", mnum, msend, mrec, cnum, csend, crec); } mnum = msend = mrec = 0; cnum = csend = crec = 0; bsend = brec = 0; } ENetEvent event; bool serviced = false; while(!serviced) { if(enet_host_check_events(serverhost, &event) <= 0) { if(enet_host_service(serverhost, &event, timeout) <= 0) break; serviced = true; } switch(event.type) { case ENET_EVENT_TYPE_CONNECT: { client &c = addclient(); c.type = ST_TCPIP; c.peer = event.peer; c.peer->data = (void *)(size_t)c.clientnum; c.connectmillis = servmillis; c.salt = rand()*((servmillis%1000)+1); char hn[1024]; s_strcpy(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown"); logger->writeline(log::info,"[%s] client connected", c.hostname); sendinits2c(c); break; } case ENET_EVENT_TYPE_RECEIVE: { brec += (int)event.packet->dataLength; int cn = (int)(size_t)event.peer->data; if(valid_client(cn)) process(event.packet, cn, event.channelID); if(event.packet->referenceCount==0) enet_packet_destroy(event.packet); break; } case ENET_EVENT_TYPE_DISCONNECT: { int cn = (int)(size_t)event.peer->data; if(!valid_client(cn)) break; disconnect_client(cn); break; } default: break; } } sendworldstate(); } void cleanupserver() { if(serverhost) enet_host_destroy(serverhost); if(svcctrl) { svcctrl->stop(); DELETEP(svcctrl); } if(logger) DELETEP(logger); } int getpongflags(enet_uint32 ip) { int flags = mastermode << PONGFLAG_MASTERMODE; flags |= scl.serverpassword[0] ? 1 << PONGFLAG_PASSWORD : 0; loopv(bans) if(bans[i].address.host == ip) { flags |= 1 << PONGFLAG_BANNED; break; } flags |= checkblacklist(ip) ? 1 << PONGFLAG_BLACKLIST : 0; return flags; } void extping_namelist(ucharbuf &p) { loopv(clients) { if(clients[i]->type == ST_TCPIP && clients[i]->isauthed) sendstring(clients[i]->name, p); } sendstring("", p); } void extinfo_cnbuf(ucharbuf &p, int cn) { if(cn == -1) // add all available player ids { loopv(clients) if(clients[i]->type != ST_EMPTY) putint(p,clients[i]->clientnum); } else if(valid_client(cn)) // add single player only { putint(p,clients[cn]->clientnum); } } void extinfo_statsbuf(ucharbuf &p, int pid, int bpos, ENetSocket &pongsock, ENetAddress &addr, ENetBuffer &buf, int len, int *csend) { loopv(clients) { if(clients[i]->type == ST_EMPTY) continue; if(pid>-1 && clients[i]->clientnum!=pid) continue; putint(p,EXT_PLAYERSTATS_RESP_STATS); // send player stats following putint(p,clients[i]->clientnum); //add player id putint(p,clients[i]->ping); //Ping sendstring(clients[i]->name,p); //Name sendstring(clients[i]->team,p); //Team putint(p,clients[i]->state.frags); //Frags putint(p,clients[i]->state.flagscore); //Flagscore putint(p,clients[i]->state.deaths); //Death putint(p,clients[i]->state.teamkills); //Teamkills putint(p,clients[i]->state.damage*100/max(clients[i]->state.shotdamage,1)); //Accuracy putint(p,clients[i]->state.health); //Health putint(p,clients[i]->state.armour); //Armour putint(p,clients[i]->state.gunselect); //Gun selected putint(p,clients[i]->role); //Role putint(p,clients[i]->state.state); //State (Alive,Dead,Spawning,Lagged,Editing) uint ip = clients[i]->peer->address.host; // only 3 byte of the ip address (privacy protected) p.put((uchar*)&ip,3); buf.dataLength = len + p.length(); enet_socket_send(pongsock, &addr, &buf, 1); *csend += (int)buf.dataLength; if(pid>-1) break; p.len=bpos; } } void extinfo_teamscorebuf(ucharbuf &p) { putint(p, m_teammode ? EXT_ERROR_NONE : EXT_ERROR); putint(p, gamemode); putint(p, minremain); if(!m_teammode) return; cvector teams; bool addteam; loopv(clients) if(clients[i]->type!=ST_EMPTY) { addteam = true; loopvj(teams) { if(strcmp(clients[i]->team,teams[j])==0 || !clients[i]->team[0]) { addteam = false; break; } } if(addteam) teams.add(clients[i]->team); } loopv(teams) { sendstring(teams[i],p); //team int fragscore = 0; int flagscore = 0; loopvj(clients) if(clients[j]->type!=ST_EMPTY) { if(!(strcmp(clients[j]->team,teams[i])==0)) continue; fragscore += clients[j]->state.frags; flagscore += clients[j]->state.flagscore; } putint(p,fragscore); //add fragscore per team if(m_flags) //when capture mode { putint(p,flagscore); //add flagscore per team } else //all other team modes { putint(p,-1); //flagscore not available } putint(p,-1); } } #ifndef STANDALONE void localdisconnect() { loopv(clients) if(clients[i]->type==ST_LOCAL) clients[i]->zap(); } void localconnect() { client &c = addclient(); c.type = ST_LOCAL; c.role = CR_ADMIN; s_strcpy(c.hostname, "local"); sendinits2c(c); } #endif void initserver(bool dedicated) { srand(time(NULL)); s_strcpy(servdesc_current, scl.servdesc_full); servermsinit(scl.master ? scl.master : AC_MASTER_URI, scl.ip, CUBE_SERVINFO_PORT(scl.serverport), dedicated); string identity; if(scl.logident[0]) s_sprintf(identity)("[%s]", scl.logident); else s_sprintf(identity)("%s[%d]", scl.ip[0] ? scl.ip : "local", scl.serverport); logger = newlogger(identity, scl.syslogfacility); if(dedicated) logger->open(); // log on ded servers only logger->writeline(log::info, "logging local AssaultCube server (version %d, protocol %d/%d) now..", AC_VERSION, PROTOCOL_VERSION, EXT_VERSION); if((isdedicated = dedicated)) { ENetAddress address = { ENET_HOST_ANY, scl.serverport }; if(scl.ip[0] && enet_address_set_host(&address, scl.ip)<0) logger->writeline(log::warning, "server ip not resolved!"); serverhost = enet_host_create(&address, scl.maxclients+1, 0, scl.uprate); if(!serverhost) fatal("could not create server host"); loopi(scl.maxclients) serverhost->peers[i].data = (void *)-1; readscfg(scl.maprot); readpwdfile(scl.pwdfile); readblacklist(scl.blfile); if(scl.verbose) { if(scl.demoeverymatch) logger->writeline(log::info, "recording demo of every game (holding up to %d in memory)", scl.maxdemos); if(scl.demopath[0]) logger->writeline(log::info,"all recorded demos will be written to: \"%s\"", scl.demopath); if(scl.voteperm[0]) logger->writeline(log::info,"vote permission string: \"%s\"", scl.voteperm); logger->writeline(log::info,"server description: \"%s\"", scl.servdesc_full); if(scl.servdesc_pre[0] || scl.servdesc_suf[0]) logger->writeline(log::info,"custom server description: \"%sCUSTOMPART%s\"", scl.servdesc_pre, scl.servdesc_suf); } } resetserverifempty(); if(isdedicated) // do not return, this becomes main loop { #ifdef WIN32 SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); #endif logger->writeline(log::info, "dedicated server started, waiting for clients...\nCtrl-C to exit\n"); atexit(enet_deinitialize); atexit(cleanupserver); enet_time_set(0); for(;;) serverslice(5); } } #ifdef STANDALONE void localservertoclient(int chan, uchar *buf, int len) {} void fatal(const char *s, ...) { cleanupserver(); s_sprintfdlv(msg,s,s); s_sprintfd(out)("AssaultCube fatal error: %s", msg); if(logger) logger->writeline(log::error, "%s", out); else puts(out); exit(EXIT_FAILURE); } int main(int argc, char **argv) { #ifdef WIN32 //atexit((void (__cdecl *)(void))_CrtDumpMemoryLeaks); #ifndef _DEBUG #ifndef __GNUC__ __try { #endif #endif #endif const char *service = NULL; for(int i = 1; iargc = argc; svcctrl->argv = argv; svcctrl->start(); } } if(enet_initialize()<0) fatal("Unable to initialise network module"); initserver(true); return EXIT_SUCCESS; #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__) } __except(stackdumper(0, GetExceptionInformation()), EXCEPTION_CONTINUE_SEARCH) { return 0; } #endif } #endif