#include "game.hh" namespace game { void parseoptions(vector &args) { loopv(args) #ifndef STANDALONE if(!game::clientoption(args[i])) #endif if(!server::serveroption(args[i])) conoutf(CON_ERROR, "unknown command-line option: %s", args[i]); } const char *gameident() { return "Tesseract"; } } namespace server { struct server_entity // server side version of "entity" type { int type; int spawntime; bool spawned; }; static const int DEATHMILLIS = 300; struct clientinfo; struct gameevent { virtual ~gameevent() {} virtual bool flush(clientinfo *ci, int fmillis); virtual void process(clientinfo *ci) {} virtual bool keepable() const { return false; } }; struct timedevent : gameevent { int millis; bool flush(clientinfo *ci, int fmillis); }; struct servstate : gamestate { vec o; int state, editstate; int lastdeath, deadflush, lastspawn, lifesequence; int lasttimeplayed, timeplayed; float effectiveness; servstate() : state(CS_DEAD), editstate(CS_DEAD), lifesequence(0) {} bool isalive(int gamemillis) { return state==CS_ALIVE || (state==CS_DEAD && gamemillis - lastdeath <= DEATHMILLIS); } bool waitexpired(int gamemillis) { return true; } void reset() { if(state!=CS_SPECTATOR) state = editstate = CS_DEAD; timeplayed = 0; effectiveness = 0; lastdeath = 0; respawn(); } void respawn() { gamestate::respawn(); o = vec(-1e10f, -1e10f, -1e10f); deadflush = 0; lastspawn = -1; } void reassign() { respawn(); } }; extern int gamemillis, nextexceeded; struct clientinfo { int clientnum, ownernum, connectmillis, sessionid, overflow; string name; int team, playermodel, playercolor; bool connected, local, timesync; int gameoffset, lastevent, pushed, exceeded; servstate state; vector events; vector position, messages; uchar *wsdata; int wslen; int ping, aireinit; string clientmap; int mapcrc; bool warned, gameclip; ENetPacket *getmap, *clipboard; int lastclipboard, needclipboard; clientinfo() : getmap(NULL), clipboard(NULL) { reset(); } ~clientinfo() { events.deletecontents(); cleanclipboard(); } void addevent(gameevent *e) { if(state.state==CS_SPECTATOR || events.length()>100) delete e; else events.add(e); } enum { PUSHMILLIS = 3000 }; int calcpushrange() { ENetPeer *peer = getclientpeer(ownernum); return PUSHMILLIS + (peer ? peer->roundTripTime + peer->roundTripTimeVariance : ENET_PEER_DEFAULT_ROUND_TRIP_TIME); } bool checkpushed(int millis, int range) { return millis >= pushed - range && millis <= pushed + range; } void scheduleexceeded() { if(state.state!=CS_ALIVE || !exceeded) return; int range = calcpushrange(); if(!nextexceeded || exceeded + range < nextexceeded) nextexceeded = exceeded + range; } void setexceeded() { if(state.state==CS_ALIVE && !exceeded && !checkpushed(gamemillis, calcpushrange())) exceeded = gamemillis; scheduleexceeded(); } void setpushed() { pushed = max(pushed, gamemillis); if(exceeded && checkpushed(exceeded, calcpushrange())) exceeded = 0; } bool checkexceeded() { return state.state==CS_ALIVE && exceeded && gamemillis > exceeded + calcpushrange(); } void mapchange() { state.reset(); events.deletecontents(); overflow = 0; timesync = false; lastevent = 0; exceeded = 0; pushed = 0; clientmap[0] = '\0'; mapcrc = 0; warned = false; gameclip = false; } void reassign() { state.reassign(); events.deletecontents(); timesync = false; lastevent = 0; } void cleanclipboard(bool fullclean = true) { if(clipboard) { if(--clipboard->referenceCount <= 0) enet_packet_destroy(clipboard); clipboard = NULL; } if(fullclean) lastclipboard = 0; } void reset() { name[0] = 0; team = 0; playermodel = -1; playercolor = 0; connected = local = false; position.setsize(0); messages.setsize(0); ping = 0; aireinit = 0; needclipboard = 0; cleanclipboard(); mapchange(); } int geteventmillis(int servmillis, int clientmillis) { if(!timesync || (events.empty() && state.waitexpired(servmillis))) { timesync = true; gameoffset = servmillis - clientmillis; return servmillis; } else return gameoffset + clientmillis; } }; bool notgotitems = true; // true when map has changed and waiting for clients to send item int gamemode = 0; int gamemillis = 0, gamelimit = 0, nextexceeded = 0, gamespeed = 100; bool gamepaused = false, shouldstep = true; string smapname = ""; int interm = 0; enet_uint32 lastsend = 0; stream *mapdata = NULL; vector connects, clients; void *newclientinfo() { return new clientinfo; } void deleteclientinfo(void *ci) { delete (clientinfo *)ci; } clientinfo *getinfo(int n) { if(n < MAXCLIENTS) return (clientinfo *)getclientinfo(n); return NULL; } uint mcrc = 0; vector ments; vector sents; int msgsizelookup(int msg) { static int sizetable[NUMMSG] = { -1 }; if(sizetable[0] < 0) { memset(sizetable, -1, sizeof(sizetable)); for(const int *p = msgsizes; *p >= 0; p += 2) sizetable[p[0]] = p[1]; } return msg >= 0 && msg < NUMMSG ? sizetable[msg] : -1; } void sendservmsg(const char *s) { sendf(-1, 1, "ris", N_SERVMSG, s); } void sendservmsgf(const char *fmt, ...) PRINTFARGS(1, 2); void sendservmsgf(const char *fmt, ...) { defvformatstring(s, fmt, fmt); sendf(-1, 1, "ris", N_SERVMSG, s); } void resetitems() { mcrc = 0; ments.setsize(0); sents.setsize(0); //cps.reset(); } bool serveroption(const char *arg) { return false; } void serverinit() { smapname[0] = '\0'; resetitems(); } int numclients(int exclude = -1, bool nospec = true, bool noai = true, bool priv = false) { int n = 0; loopv(clients) { clientinfo *ci = clients[i]; if(ci->clientnum!=exclude && (!nospec || ci->state.state!=CS_SPECTATOR || (priv && ci->local))) n++; } return n; } bool duplicatename(clientinfo *ci, const char *name) { if(!name) name = ci->name; loopv(clients) if(clients[i]!=ci && !strcmp(name, clients[i]->name)) return true; return false; } const char *colorname(clientinfo *ci, const char *name = NULL) { if(!name) name = ci->name; if(name[0] && !duplicatename(ci, name)) return name; static string cname[3]; static int cidx = 0; cidx = (cidx+1)%3; formatstring(cname[cidx], "%s \fs\f5(%d)\fr", name, ci->clientnum); return cname[cidx]; } struct servmode { virtual ~servmode() {} virtual void entergame(clientinfo *ci) {} virtual void leavegame(clientinfo *ci, bool disconnecting = false) {} virtual void moved(clientinfo *ci, const vec &oldpos, bool oldclip, const vec &newpos, bool newclip) {} virtual bool canspawn(clientinfo *ci, bool connecting = false) { return true; } virtual void spawned(clientinfo *ci) {} virtual int fragvalue(clientinfo *victim, clientinfo *actor) { if(victim==actor) return -1; return 1; } virtual void died(clientinfo *victim, clientinfo *actor) {} virtual bool canchangeteam(clientinfo *ci, int oldteam, int newteam) { return true; } virtual void changeteam(clientinfo *ci, int oldteam, int newteam) {} virtual void initclient(clientinfo *ci, packetbuf &p, bool connecting) {} virtual void update() {} virtual void cleanup() {} virtual void setup() {} virtual void newmap() {} virtual bool hidefrags() { return false; } }; servmode *smode = NULL; bool canspawnitem(int type) { return validitem(type); } int spawntime(int type) { int np = numclients(-1, true, false); np = np<3 ? 4 : (np>4 ? 2 : 3); // spawn times are dependent on number of players int sec = 0; switch(type) { } return sec*1000; } bool delayspawn(int type) { switch(type) { default: return false; } } bool pickup(int i, int sender) // server side item pickup, acknowledge first client that gets it { return false; } int welcomepacket(packetbuf &p, clientinfo *ci); void sendwelcome(clientinfo *ci); void pausegame(bool val, clientinfo *ci = NULL) { if(gamepaused==val) return; gamepaused = val; sendf(-1, 1, "riii", N_PAUSEGAME, gamepaused ? 1 : 0, ci ? ci->clientnum : -1); } void checkpausegame() { } void forcepaused(bool paused) { pausegame(paused); } bool ispaused() { return gamepaused; } void changegamespeed(int val, clientinfo *ci = NULL) { val = clamp(val, 10, 1000); if(gamespeed==val) return; gamespeed = val; sendf(-1, 1, "riii", N_GAMESPEED, gamespeed, ci ? ci->clientnum : -1); } void forcegamespeed(int speed) { changegamespeed(speed); } int scaletime(int t) { return t*gamespeed; } static struct msgfilter { uchar msgmask[NUMMSG]; msgfilter(int msg, ...) { memset(msgmask, 0, sizeof(msgmask)); va_list msgs; va_start(msgs, msg); for(uchar val = 1; msg < NUMMSG; msg = va_arg(msgs, int)) { if(msg < 0) val = uchar(-msg); else msgmask[msg] = val; } va_end(msgs); } uchar operator[](int msg) const { return msg >= 0 && msg < NUMMSG ? msgmask[msg] : 0; } } msgfilter(-1, N_CONNECT, N_SERVINFO, N_INITCLIENT, N_WELCOME, N_MAPCHANGE, N_SERVMSG, N_DAMAGE, N_HITPUSH, N_SHOTFX, N_EXPLODEFX, N_DIED, N_SPAWNSTATE, N_FORCEDEATH, N_CDIS, N_PONG, N_RESUME, N_SENDMAP, N_CLIENT, -2, N_CALCLIGHT, N_REMIP, N_NEWMAP, N_GETMAP, N_SENDMAP, N_CLIPBOARD, -3, N_EDITENT, N_EDITF, N_EDITT, N_EDITM, N_FLIP, N_COPY, N_PASTE, N_ROTATE, N_REPLACE, N_DELCUBE, N_EDITVAR, N_EDITVSLOT, N_UNDO, N_REDO, -4, N_POS, NUMMSG); int checktype(int type, clientinfo *ci) { if(ci) { return type; } switch(msgfilter[type]) { // server-only messages case 1: return ci ? -1 : type; // only allowed in coop-edit case 2: if(m_edit) break; return -1; // only allowed in coop-edit, no overflow check case 3: return m_edit ? type : -1; // no overflow check case 4: return type; } if(ci && ++ci->overflow >= 200) return -2; return type; } struct worldstate { int uses, len; uchar *data; worldstate() : uses(0), len(0), data(NULL) {} void setup(int n) { len = n; data = new uchar[n]; } void cleanup() { DELETEA(data); len = 0; } bool contains(const uchar *p) const { return p >= data && p < &data[len]; } }; vector worldstates; bool reliablemessages = false; void cleanworldstate(ENetPacket *packet) { loopv(worldstates) { worldstate &ws = worldstates[i]; if(!ws.contains(packet->data)) continue; ws.uses--; if(ws.uses <= 0) { ws.cleanup(); worldstates.removeunordered(i); } break; } } void flushclientposition(clientinfo &ci) { if(ci.position.empty() || !hasnonlocalclients()) return; packetbuf p(ci.position.length(), 0); p.put(ci.position.getbuf(), ci.position.length()); ci.position.setsize(0); sendpacket(-1, 0, p.finalize(), ci.ownernum); } static void sendpositions(worldstate &ws, ucharbuf &wsbuf) { if(wsbuf.empty()) return; int wslen = wsbuf.length(); wsbuf.put(wsbuf.buf, wslen); loopv(clients) { clientinfo &ci = *clients[i]; uchar *data = wsbuf.buf; int size = wslen; if(ci.wsdata >= wsbuf.buf) { data = ci.wsdata + ci.wslen; size -= ci.wslen; } if(size <= 0) continue; ENetPacket *packet = enet_packet_create(data, size, ENET_PACKET_FLAG_NO_ALLOCATE); sendpacket(ci.clientnum, 0, packet); if(packet->referenceCount) { ws.uses++; packet->freeCallback = cleanworldstate; } else enet_packet_destroy(packet); } wsbuf.offset(wsbuf.length()); } static inline void addposition(worldstate &ws, ucharbuf &wsbuf, int mtu, clientinfo &bi, clientinfo &ci) { if(bi.position.empty()) return; if(wsbuf.length() + bi.position.length() > mtu) sendpositions(ws, wsbuf); int offset = wsbuf.length(); wsbuf.put(bi.position.getbuf(), bi.position.length()); bi.position.setsize(0); int len = wsbuf.length() - offset; if(ci.wsdata < wsbuf.buf) { ci.wsdata = &wsbuf.buf[offset]; ci.wslen = len; } else ci.wslen += len; } static void sendmessages(worldstate &ws, ucharbuf &wsbuf) { if(wsbuf.empty()) return; int wslen = wsbuf.length(); wsbuf.put(wsbuf.buf, wslen); loopv(clients) { clientinfo &ci = *clients[i]; uchar *data = wsbuf.buf; int size = wslen; if(ci.wsdata >= wsbuf.buf) { data = ci.wsdata + ci.wslen; size -= ci.wslen; } if(size <= 0) continue; ENetPacket *packet = enet_packet_create(data, size, (reliablemessages ? ENET_PACKET_FLAG_RELIABLE : 0) | ENET_PACKET_FLAG_NO_ALLOCATE); sendpacket(ci.clientnum, 1, packet); if(packet->referenceCount) { ws.uses++; packet->freeCallback = cleanworldstate; } else enet_packet_destroy(packet); } wsbuf.offset(wsbuf.length()); } static inline void addmessages(worldstate &ws, ucharbuf &wsbuf, int mtu, clientinfo &bi, clientinfo &ci) { if(bi.messages.empty()) return; if(wsbuf.length() + 10 + bi.messages.length() > mtu) sendmessages(ws, wsbuf); int offset = wsbuf.length(); putint(wsbuf, N_CLIENT); putint(wsbuf, bi.clientnum); putuint(wsbuf, bi.messages.length()); wsbuf.put(bi.messages.getbuf(), bi.messages.length()); bi.messages.setsize(0); int len = wsbuf.length() - offset; if(ci.wsdata < wsbuf.buf) { ci.wsdata = &wsbuf.buf[offset]; ci.wslen = len; } else ci.wslen += len; } bool buildworldstate() { int wsmax = 0; loopv(clients) { clientinfo &ci = *clients[i]; ci.overflow = 0; ci.wsdata = NULL; wsmax += ci.position.length(); if(ci.messages.length()) wsmax += 10 + ci.messages.length(); } if(wsmax <= 0) { reliablemessages = false; return false; } worldstate &ws = worldstates.add(); ws.setup(2*wsmax); int mtu = getservermtu() - 100; if(mtu <= 0) mtu = ws.len; ucharbuf wsbuf(ws.data, ws.len); loopv(clients) { clientinfo &ci = *clients[i]; addposition(ws, wsbuf, mtu, ci, ci); } sendpositions(ws, wsbuf); loopv(clients) { clientinfo &ci = *clients[i]; addmessages(ws, wsbuf, mtu, ci, ci); } sendmessages(ws, wsbuf); reliablemessages = false; if(ws.uses) return true; ws.cleanup(); worldstates.drop(); return false; } bool sendpackets(bool force) { if(clients.empty() || !hasnonlocalclients()) return false; enet_uint32 curtime = enet_time_get()-lastsend; if(curtime<40 && !force) return false; bool flush = buildworldstate(); lastsend += curtime - (curtime%40); return flush; } template void sendstate(servstate &gs, T &p) { putint(p, gs.lifesequence); } void spawnstate(clientinfo *ci) { servstate &gs = ci->state; gs.spawnstate(gamemode); gs.lifesequence = (gs.lifesequence + 1)&0x7F; } void sendspawn(clientinfo *ci) { servstate &gs = ci->state; spawnstate(ci); sendf(ci->ownernum, 1, "rii4", N_SPAWNSTATE, ci->clientnum, gs.lifesequence, 1, 1); gs.lastspawn = gamemillis; } void sendwelcome(clientinfo *ci) { packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); int chan = welcomepacket(p, ci); sendpacket(ci->clientnum, chan, p.finalize()); } void putinitclient(clientinfo *ci, packetbuf &p) { putint(p, N_INITCLIENT); putint(p, ci->clientnum); sendstring(ci->name, p); } void welcomeinitclient(packetbuf &p, int exclude = -1) { loopv(clients) { clientinfo *ci = clients[i]; if(!ci->connected || ci->clientnum == exclude) continue; putinitclient(ci, p); } } bool hasmap(clientinfo *ci) { return true; } int welcomepacket(packetbuf &p, clientinfo *ci) { putint(p, N_WELCOME); putint(p, N_MAPCHANGE); sendstring(smapname, p); putint(p, gamemode); putint(p, notgotitems ? 1 : 0); if(!notgotitems) { putint(p, N_ITEMLIST); loopv(sents) if(sents[i].spawned) { putint(p, i); putint(p, sents[i].type); } putint(p, -1); } if(gamepaused) { putint(p, N_PAUSEGAME); putint(p, 1); putint(p, -1); } if(gamespeed != 100) { putint(p, N_GAMESPEED); putint(p, gamespeed); putint(p, -1); } if(ci) { putint(p, N_SETTEAM); putint(p, ci->clientnum); putint(p, ci->team); putint(p, -1); } if(ci && ci->state.state==CS_SPECTATOR) { putint(p, N_SPECTATOR); putint(p, ci->clientnum); putint(p, 1); sendf(-1, 1, "ri3x", N_SPECTATOR, ci->clientnum, 1, ci->clientnum); } if(!ci || clients.length()>1) { putint(p, N_RESUME); loopv(clients) { clientinfo *oi = clients[i]; if(ci && oi->clientnum==ci->clientnum) continue; putint(p, oi->clientnum); putint(p, oi->state.state); sendstate(oi->state, p); } putint(p, -1); welcomeinitclient(p, ci ? ci->clientnum : -1); } if(smode) smode->initclient(ci, p, true); return 1; } void sendresume(clientinfo *ci) { servstate &gs = ci->state; sendf(-1, 1, "ri3ii", N_RESUME, ci->clientnum, gs.state, gs.lifesequence, -1); } void sendinitclient(clientinfo *ci) { packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); putinitclient(ci, p); sendpacket(-1, 1, p.finalize(), ci->clientnum); } void loaditems() { resetitems(); notgotitems = true; if(m_edit || !loadents(smapname, ments, &mcrc)) return; loopv(ments) if(canspawnitem(ments[i].type)) { server_entity se = { NOTUSED, 0, false }; while(sents.length()<=i) sents.add(se); sents[i].type = ments[i].type; sents[i].spawned = true; } notgotitems = false; } void changemap(const char *s, int mode) { pausegame(false); changegamespeed(100); if(smode) smode->cleanup(); gamemode = mode; gamemillis = 0; gamelimit = 600000; interm = 0; nextexceeded = 0; copystring(smapname, s); loaditems(); loopv(clients) { clientinfo *ci = clients[i]; ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; } kicknonlocalclients(DISC_LOCAL); sendf(-1, 1, "risii", N_MAPCHANGE, smapname, gamemode, 1); smode = NULL; loopv(clients) { clientinfo *ci = clients[i]; ci->mapchange(); ci->state.lasttimeplayed = lastmillis; } if(smode) smode->setup(); } void forcemap(const char *map, int mode) { changemap(map, mode); } bool gameevent::flush(clientinfo *ci, int fmillis) { process(ci); return true; } bool timedevent::flush(clientinfo *ci, int fmillis) { if(millis > fmillis) return false; else if(millis >= ci->lastevent) { ci->lastevent = millis; process(ci); } return true; } void clearevent(clientinfo *ci) { delete ci->events.remove(0); } void flushevents(clientinfo *ci, int millis) { while(ci->events.length()) { gameevent *ev = ci->events[0]; if(ev->flush(ci, millis)) clearevent(ci); else break; } } void processevents() { loopv(clients) { clientinfo *ci = clients[i]; flushevents(ci, gamemillis); } } void cleartimedevents(clientinfo *ci) { int keep = 0; loopv(ci->events) { if(ci->events[i]->keepable()) { if(keep < i) { for(int j = keep; j < i; j++) delete ci->events[j]; ci->events.remove(keep, i - keep); i = keep; } keep = i+1; continue; } } while(ci->events.length() > keep) delete ci->events.pop(); ci->timesync = false; } void serverupdate() { if(shouldstep && !gamepaused) { gamemillis += curtime; processevents(); if(smode) smode->update(); } loopv(connects) if(totalmillis-connects[i]->connectmillis>15000) disconnect_client(connects[i]->clientnum, DISC_TIMEOUT); if(nextexceeded && gamemillis > nextexceeded) { nextexceeded = 0; loopvrev(clients) { clientinfo &c = *clients[i]; if(c.checkexceeded()) disconnect_client(c.clientnum, DISC_MSGERR); else c.scheduleexceeded(); } } if(shouldstep && !gamepaused) { if(interm > 0 && gamemillis>interm) { interm = -1; } } shouldstep = clients.length() > 0; } struct crcinfo { int crc, matches; crcinfo() {} crcinfo(int crc, int matches) : crc(crc), matches(matches) {} static bool compare(const crcinfo &x, const crcinfo &y) { return x.matches > y.matches; } }; void checkmaps(int req = -1) { } bool shouldspectate(clientinfo *ci) { return false; } void unspectate(clientinfo *ci) { if(shouldspectate(ci)) return; ci->state.state = CS_DEAD; ci->state.respawn(); ci->state.lasttimeplayed = lastmillis; sendf(-1, 1, "ri3", N_SPECTATOR, ci->clientnum, 0); if(ci->clientmap[0] || ci->mapcrc) checkmaps(); } void sendservinfo(clientinfo *ci) { sendf(ci->clientnum, 1, "ri4", N_SERVINFO, ci->clientnum, PROTOCOL_VERSION, ci->sessionid); } void noclients() { } void localconnect(int n) { clientinfo *ci = getinfo(n); ci->clientnum = ci->ownernum = n; ci->connectmillis = totalmillis; ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF; ci->local = true; connects.add(ci); sendservinfo(ci); } void localdisconnect(int n) { clientdisconnect(n); } int clientconnect(int n, uint ip) { clientinfo *ci = getinfo(n); ci->clientnum = ci->ownernum = n; ci->connectmillis = totalmillis; ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF; connects.add(ci); return DISC_LOCAL; } void clientdisconnect(int n) { clientinfo *ci = getinfo(n); if(ci->connected) { if(smode) smode->leavegame(ci, true); ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; sendf(-1, 1, "ri2", N_CDIS, n); clients.removeobj(ci); if(!numclients(-1, false, true)) noclients(); // bans clear when server empties if(ci->local) checkpausegame(); } else connects.removeobj(ci); } int reserveclients() { return 3; } bool allowbroadcast(int n) { clientinfo *ci = getinfo(n); return ci && ci->connected; } void receivefile(int sender, uchar *data, int len) { clientinfo *ci = getinfo(sender); if(ci->state.state==CS_SPECTATOR && !ci->local) return; if(mapdata) DELETEP(mapdata); mapdata = opentempfile("mapdata", "w+b"); if(!mapdata) { sendf(sender, 1, "ris", N_SERVMSG, "failed to open temporary file for map"); return; } mapdata->write(data, len); sendservmsgf("[%s sent a map to server, \"/getmap\" to receive it]", colorname(ci)); } void sendclipboard(clientinfo *ci) { if(!ci->lastclipboard || !ci->clipboard) return; bool flushed = false; loopv(clients) { clientinfo &e = *clients[i]; if(e.clientnum != ci->clientnum && e.needclipboard - ci->lastclipboard >= 0) { if(!flushed) { flushserver(true); flushed = true; } sendpacket(e.clientnum, 1, ci->clipboard); } } } void connected(clientinfo *ci) { shouldstep = true; connects.removeobj(ci); clients.add(ci); ci->connected = true; ci->needclipboard = totalmillis ? totalmillis : 1; ci->state.lasttimeplayed = lastmillis; ci->team = 0; sendwelcome(ci); sendinitclient(ci); } void parsepacket(int sender, int chan, packetbuf &p) // has to parse exactly each byte of the packet { if(sender<0 || p.packet->flags&ENET_PACKET_FLAG_UNSEQUENCED || chan > 2) return; char text[MAXTRANS]; int type; clientinfo *ci = sender>=0 ? getinfo(sender) : NULL, *cq = ci, *cm = ci; if(ci && !ci->connected) { if(chan==0) return; else if(chan!=1) { disconnect_client(sender, DISC_MSGERR); return; } else while(p.length() < p.maxlen) switch(checktype(getint(p), ci)) { case N_CONNECT: { getstring(text, p); filtertext(text, text, false, false, MAXNAMELEN); if(!text[0]) copystring(text, "unnamed"); copystring(ci->name, text, MAXNAMELEN+1); ci->playermodel = getint(p); ci->playercolor = getint(p); connected(ci); break; } case N_PING: getint(p); break; default: disconnect_client(sender, DISC_MSGERR); return; } return; } else if(chan==2) { receivefile(sender, p.buf, p.maxlen); return; } if(p.packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true; #define QUEUE_AI clientinfo *cm = cq; #define QUEUE_MSG { if(cm && (!cm->local || hasnonlocalclients())) while(curmsgmessages.add(p.buf[curmsg++]); } #define QUEUE_BUF(body) { \ if(cm && (!cm->local || hasnonlocalclients())) \ { \ curmsg = p.length(); \ { body; } \ } \ } #define QUEUE_INT(n) QUEUE_BUF(putint(cm->messages, n)) #define QUEUE_UINT(n) QUEUE_BUF(putuint(cm->messages, n)) #define QUEUE_STR(text) QUEUE_BUF(sendstring(text, cm->messages)) int curmsg; while((curmsg = p.length()) < p.maxlen) switch(type = checktype(getint(p), ci)) { case N_POS: { int pcn = getuint(p); p.get(); uint flags = getuint(p); clientinfo *cp = getinfo(pcn); if(cp && pcn != sender && cp->ownernum != sender) cp = NULL; vec pos; loopk(3) { int n = p.get(); n |= p.get()<<8; if(flags&(1<local || hasnonlocalclients()) && (cp->state.state==CS_ALIVE || cp->state.state==CS_EDITING)) { if(!ci->local && !m_edit && max(vel.magnitude2(), (float)fabs(vel.z)) >= 180) cp->setexceeded(); cp->position.setsize(0); while(curmsgposition.add(p.buf[curmsg++]); } if(smode && cp->state.state==CS_ALIVE) smode->moved(cp, cp->state.o, cp->gameclip, pos, (flags&0x80)!=0); cp->state.o = pos; cp->gameclip = (flags&0x80)!=0; } break; } case N_EDITMODE: { int val = getint(p); if(!ci->local && !m_edit) break; if(val ? ci->state.state!=CS_ALIVE && ci->state.state!=CS_DEAD : ci->state.state!=CS_EDITING) break; if(smode) { if(val) smode->leavegame(ci); else smode->entergame(ci); } if(val) { ci->state.editstate = ci->state.state; ci->state.state = CS_EDITING; ci->events.setsize(0); } else ci->state.state = ci->state.editstate; QUEUE_MSG; break; } case N_MAPCRC: { getstring(text, p); int crc = getint(p); if(!ci) break; if(strcmp(text, smapname)) { if(ci->clientmap[0]) { ci->clientmap[0] = '\0'; ci->mapcrc = 0; } else if(ci->mapcrc > 0) ci->mapcrc = 0; break; } copystring(ci->clientmap, text); ci->mapcrc = text[0] ? crc : 1; checkmaps(); if(cq && cq != ci && cq->ownernum != ci->clientnum) cq = NULL; break; } case N_TRYSPAWN: if(!ci || !cq || cq->state.state!=CS_DEAD || cq->state.lastspawn>=0 || (smode && !smode->canspawn(cq))) break; if(!ci->clientmap[0] && !ci->mapcrc) { ci->mapcrc = -1; checkmaps(); if(ci == cq) { if(ci->state.state != CS_DEAD) break; } else if(cq->ownernum != ci->clientnum) { cq = NULL; break; } } if(cq->state.deadflush) { flushevents(cq, cq->state.deadflush); cq->state.respawn(); } cleartimedevents(cq); sendspawn(cq); break; case N_SPAWN: { int ls = getint(p); if(!cq || (cq->state.state!=CS_ALIVE && cq->state.state!=CS_DEAD && cq->state.state!=CS_EDITING) || ls!=cq->state.lifesequence || cq->state.lastspawn<0) break; cq->state.lastspawn = -1; cq->state.state = CS_ALIVE; cq->exceeded = 0; if(smode) smode->spawned(cq); QUEUE_AI; QUEUE_BUF({ putint(cm->messages, N_SPAWN); sendstate(cq->state, cm->messages); }); break; } case N_TEXT: { QUEUE_AI; QUEUE_MSG; getstring(text, p); filtertext(text, text, true, true); QUEUE_STR(text); if(isdedicatedserver() && cq) logoutf("%s: %s", colorname(cq), text); break; } case N_SWITCHNAME: { QUEUE_MSG; getstring(text, p); filtertext(ci->name, text, false, false, MAXNAMELEN); if(!ci->name[0]) copystring(ci->name, "unnamed"); QUEUE_STR(ci->name); break; } case N_EDITENT: { int i = getint(p); loopk(3) getint(p); int type = getint(p); loopk(5) getint(p); if(!ci || ci->state.state==CS_SPECTATOR) break; QUEUE_MSG; bool canspawn = canspawnitem(type); if(istate.state!=CS_SPECTATOR) QUEUE_MSG; break; } case N_PING: sendf(sender, 1, "i2", N_PONG, getint(p)); break; case N_CLIENTPING: { int ping = getint(p); if(ci) { ci->ping = ping; } QUEUE_MSG; break; } case N_NEWMAP: { int size = getint(p); if(!ci->local && ci->state.state==CS_SPECTATOR) break; if(size>=0) { smapname[0] = '\0'; resetitems(); notgotitems = false; if(smode) smode->newmap(); } QUEUE_MSG; break; } case N_PAUSEGAME: { int val = getint(p); pausegame(val > 0, ci); break; } case N_GAMESPEED: { int val = getint(p); changegamespeed(val, ci); break; } case N_COPY: ci->cleanclipboard(); ci->lastclipboard = totalmillis ? totalmillis : 1; goto genericmsg; case N_PASTE: if(ci->state.state!=CS_SPECTATOR) sendclipboard(ci); goto genericmsg; case N_CLIPBOARD: { int unpacklen = getint(p), packlen = getint(p); ci->cleanclipboard(false); if(ci->state.state==CS_SPECTATOR) { if(packlen > 0) p.subbuf(packlen); break; } if(packlen <= 0 || packlen > (1<<16) || unpacklen <= 0) { if(packlen > 0) p.subbuf(packlen); packlen = unpacklen = 0; } packetbuf q(32 + packlen, ENET_PACKET_FLAG_RELIABLE); putint(q, N_CLIPBOARD); putint(q, ci->clientnum); putint(q, unpacklen); putint(q, packlen); if(packlen > 0) p.get(q.subbuf(packlen).buf, packlen); ci->clipboard = q.finalize(); ci->clipboard->referenceCount++; break; } case N_EDITT: case N_REPLACE: case N_EDITVSLOT: { int size = server::msgsizelookup(type); if(size<=0) { disconnect_client(sender, DISC_MSGERR); return; } loopi(size-1) getint(p); if(p.remaining() < 2) { disconnect_client(sender, DISC_MSGERR); return; } int extra = lilswap(*(const ushort *)p.pad(2)); if(p.remaining() < extra) { disconnect_client(sender, DISC_MSGERR); return; } p.pad(extra); if(ci && ci->state.state!=CS_SPECTATOR) QUEUE_MSG; break; } case N_UNDO: case N_REDO: { int unpacklen = getint(p), packlen = getint(p); if(!ci || ci->state.state==CS_SPECTATOR || packlen <= 0 || packlen > (1<<16) || unpacklen <= 0) { if(packlen > 0) p.subbuf(packlen); break; } if(p.remaining() < packlen) { disconnect_client(sender, DISC_MSGERR); return; } packetbuf q(32 + packlen, ENET_PACKET_FLAG_RELIABLE); putint(q, type); putint(q, ci->clientnum); putint(q, unpacklen); putint(q, packlen); if(packlen > 0) p.get(q.subbuf(packlen).buf, packlen); sendpacket(-1, 1, q.finalize(), ci->clientnum); break; } case N_SERVCMD: getstring(text, p); break; case -1: disconnect_client(sender, DISC_MSGERR); return; case -2: disconnect_client(sender, DISC_OVERFLOW); return; default: genericmsg: { int size = server::msgsizelookup(type); if(size<=0) { disconnect_client(sender, DISC_MSGERR); return; } loopi(size-1) getint(p); if(ci) switch(msgfilter[type]) { case 2: case 3: if(ci->state.state != CS_SPECTATOR) QUEUE_MSG; break; default: if(cq && (ci != cq || ci->state.state!=CS_SPECTATOR)) { QUEUE_AI; QUEUE_MSG; } break; } break; } } } int laninfoport() { return TESSERACT_LANINFO_PORT; } int serverport() { return TESSERACT_SERVER_PORT; } int numchannels() { return 3; } void serverinforeply(ucharbuf &req, ucharbuf &p) { putint(p, PROTOCOL_VERSION); putint(p, numclients(-1, false, true)); putint(p, maxclients); putint(p, gamepaused || gamespeed != 100 ? 5 : 3); // number of attrs following putint(p, gamemode); putint(p, 0); putint(p, 0); if(gamepaused || gamespeed != 100) { putint(p, gamepaused ? 1 : 0); putint(p, gamespeed); } sendstring(smapname, p); sendserverinforeply(p); } int protocolversion() { return PROTOCOL_VERSION; } }