OctaCore/src/game/server.cc

2023 lines
62 KiB
C++

#include "game.hh"
namespace game
{
void parseoptions(vector<const char *> &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"; }
}
extern ENetAddress masteraddress;
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 hitinfo
{
int target;
int lifesequence;
int rays;
float dist;
vec dir;
};
struct shotevent : timedevent
{
int id, atk;
vec from, to;
vector<hitinfo> hits;
void process(clientinfo *ci);
};
struct explodeevent : timedevent
{
int id, atk;
vector<hitinfo> hits;
bool keepable() const { return true; }
void process(clientinfo *ci);
};
struct suicideevent : gameevent
{
void process(clientinfo *ci);
};
struct pickupevent : gameevent
{
int ent;
void process(clientinfo *ci);
};
template <int N>
struct projectilestate
{
int projs[N];
int numprojs;
projectilestate() : numprojs(0) {}
void reset() { numprojs = 0; }
void add(int val)
{
if(numprojs>=N) numprojs = 0;
projs[numprojs++] = val;
}
bool remove(int val)
{
loopi(numprojs) if(projs[i]==val)
{
projs[i] = projs[--numprojs];
return true;
}
return false;
}
};
struct servstate : gamestate
{
vec o;
int state, editstate;
int lastdeath, deadflush, lastspawn, lifesequence;
int lastshot;
projectilestate<8> projs;
int frags, flags, deaths, shotdamage, damage;
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 gamemillis - lastshot >= gunwait;
}
void reset()
{
if(state!=CS_SPECTATOR) state = editstate = CS_DEAD;
maxhealth = 1;
projs.reset();
timeplayed = 0;
effectiveness = 0;
frags = flags = deaths = shotdamage = damage = 0;
lastdeath = 0;
respawn();
}
void respawn()
{
gamestate::respawn();
o = vec(-1e10f, -1e10f, -1e10f);
deadflush = 0;
lastspawn = -1;
lastshot = 0;
}
void reassign()
{
respawn();
projs.reset();
}
};
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<gameevent *> events;
vector<uchar> 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;
}
};
struct ban
{
int time, expire;
uint ip;
};
#define MM_MODE 0xF
#define MM_AUTOAPPROVE 0x1000
#define MM_PRIVSERV (MM_MODE | MM_AUTOAPPROVE)
#define MM_PUBSERV ((1<<MM_OPEN) | (1<<MM_VETO))
#define MM_COOPSERV (MM_AUTOAPPROVE | MM_PUBSERV | (1<<MM_LOCKED))
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;
int mastermode = MM_OPEN, mastermask = MM_PRIVSERV;
stream *mapdata = NULL;
vector<uint> allowedips;
vector<ban> bannedips;
void addban(uint ip, int expire)
{
allowedips.removeobj(ip);
ban b;
b.time = totalmillis;
b.expire = totalmillis + expire;
b.ip = ip;
loopv(bannedips) if(bannedips[i].expire - b.expire > 0) { bannedips.insert(i, b); return; }
bannedips.add(b);
}
vector<clientinfo *> connects, clients;
bool searchmodename(const char *haystack, const char *needle)
{
if(!needle[0]) return true;
do
{
if(needle[0] != '.')
{
haystack = strchr(haystack, needle[0]);
if(!haystack) break;
haystack++;
}
const char *h = haystack, *n = needle+1;
for(; *h && *n; h++)
{
if(*h == *n) n++;
else if(*h != ' ') break;
}
if(!*n) return true;
if(*n == '.') return !*h;
} while(needle[0] != '.');
return false;
}
int genmodemask(vector<char *> &modes)
{
int modemask = 0;
loopv(modes)
{
const char *mode = modes[i];
int op = mode[0];
switch(mode[0])
{
case '*':
modemask |= 1<<NUMGAMEMODES;
loopk(NUMGAMEMODES) if(m_checknot(k+STARTGAMEMODE, M_DEMO|M_EDIT|M_LOCAL)) modemask |= 1<<k;
continue;
case '!':
mode++;
if(mode[0] != '?') break;
case '?':
mode++;
loopk(NUMGAMEMODES) if(searchmodename(gamemodes[k].name, mode))
{
if(op == '!') modemask &= ~(1<<k);
else modemask |= 1<<k;
}
continue;
}
int modenum = INT_MAX;
if(isdigit(mode[0])) modenum = atoi(mode);
else loopk(NUMGAMEMODES) if(searchmodename(gamemodes[k].name, mode)) { modenum = k+STARTGAMEMODE; break; }
if(!m_valid(modenum)) continue;
switch(op)
{
case '!': modemask &= ~(1 << (modenum - STARTGAMEMODE)); break;
default: modemask |= 1 << (modenum - STARTGAMEMODE); break;
}
}
return modemask;
}
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<entity> ments;
vector<server_entity> 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;
}
const char *modename(int n, const char *unknown)
{
if(m_valid(n)) return gamemodes[n - STARTGAMEMODE].name;
return unknown;
}
const char *modeprettyname(int n, const char *unknown)
{
if(m_valid(n)) return gamemodes[n - STARTGAMEMODE].prettyname;
return unknown;
}
const char *mastermodename(int n, const char *unknown)
{
return (n>=MM_START && size_t(n-MM_START)<sizeof(mastermodenames)/sizeof(mastermodenames[0])) ? mastermodenames[n-MM_START] : unknown;
}
const char *privname(int type)
{
return "unknown";
}
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 || isteam(victim->team, actor->team)) 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 void intermission() {}
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
{
if((m_timed && gamemillis>=gamelimit) || !sents.inrange(i) || !sents[i].spawned) return false;
clientinfo *ci = getinfo(sender);
if(!ci) return false;
if(!ci->local && !ci->state.canpickup(sents[i].type))
{
sendf(sender, 1, "ri3", N_ITEMACC, i, -1);
return false;
}
sents[i].spawned = false;
sents[i].spawntime = spawntime(sents[i].type);
sendf(-1, 1, "ri3", N_ITEMACC, i, sender);
ci->state.pickup(sents[i].type);
return true;
}
static teaminfo teaminfos[MAXTEAMS];
void clearteaminfo()
{
loopi(MAXTEAMS) teaminfos[i].reset();
}
clientinfo *choosebestclient(float &bestrank)
{
clientinfo *best = NULL;
bestrank = -1;
loopv(clients)
{
clientinfo *ci = clients[i];
if(ci->state.timeplayed<0) continue;
float rank = ci->state.state!=CS_SPECTATOR ? ci->state.effectiveness/max(ci->state.timeplayed, 1) : -1;
if(!best || rank > bestrank) { best = ci; bestrank = rank; }
}
return best;
}
void autoteam()
{
vector<clientinfo *> team[MAXTEAMS];
float teamrank[MAXTEAMS] = {0};
for(int round = 0, remaining = clients.length(); remaining>=0; round++)
{
int first = round&1, second = (round+1)&1, selected = 0;
while(teamrank[first] <= teamrank[second])
{
float rank;
clientinfo *ci = choosebestclient(rank);
if(!ci) break;
if(smode && smode->hidefrags()) rank = 1;
else if(selected && rank<=0) break;
ci->state.timeplayed = -1;
team[first].add(ci);
if(rank>0) teamrank[first] += rank;
selected++;
if(rank<=0) break;
}
if(!selected) break;
remaining -= selected;
}
loopi(MAXTEAMS) loopvj(team[i])
{
clientinfo *ci = team[i][j];
if(ci->team == 1+i) continue;
ci->team = 1+i;
sendf(-1, 1, "riiii", N_SETTEAM, ci->clientnum, ci->team, -1);
}
}
struct teamrank
{
float rank;
int clients;
teamrank() : rank(0), clients(0) {}
};
int chooseworstteam(clientinfo *exclude = NULL)
{
teamrank teamranks[MAXTEAMS];
loopv(clients)
{
clientinfo *ci = clients[i];
if(ci==exclude || ci->state.state==CS_SPECTATOR || !validteam(ci->team)) continue;
ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
ci->state.lasttimeplayed = lastmillis;
teamrank &ts = teamranks[ci->team-1];
ts.rank += ci->state.effectiveness/max(ci->state.timeplayed, 1);
ts.clients++;
}
teamrank *worst = &teamranks[0];
for(int i = 1; i < MAXTEAMS; i++)
{
teamrank &ts = teamranks[i];
if(smode && smode->hidefrags())
{
if(ts.clients < worst->clients || (ts.clients == worst->clients && ts.rank < worst->rank)) worst = &ts;
}
else if(ts.rank < worst->rank || (ts.rank == worst->rank && ts.clients < worst->clients)) worst = &ts;
}
return 1+int(worst-teamranks);
}
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_TEAMINFO, N_ITEMACC, N_ITEMSPAWN, N_TIMEUP, 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<worldstate> 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<class T>
void sendstate(servstate &gs, T &p)
{
putint(p, gs.lifesequence);
putint(p, gs.health);
putint(p, gs.maxhealth);
putint(p, gs.gunselect);
loopi(NUMGUNS) putint(p, gs.ammo[i]);
}
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, "rii5v", N_SPAWNSTATE, ci->clientnum, gs.lifesequence,
gs.health, gs.maxhealth,
gs.gunselect, NUMGUNS, gs.ammo);
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);
putint(p, ci->team);
putint(p, ci->playermodel);
putint(p, ci->playercolor);
}
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 (m_edit && (clients.length() > 0 || ci->local)) ||
(smapname[0] && (!m_timed || gamemillis < gamelimit || (ci->state.state==CS_SPECTATOR && !ci->local) || numclients(ci->clientnum, true, true, 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(!ci || (m_timed && smapname[0]))
{
putint(p, N_TIMEUP);
putint(p, gamemillis < gamelimit && !interm ? max((gamelimit - gamemillis)/1000, 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(m_teammode)
{
putint(p, N_TEAMINFO);
loopi(MAXTEAMS)
{
teaminfo &t = teaminfos[i];
putint(p, t.frags);
}
}
if(ci)
{
putint(p, N_SETTEAM);
putint(p, ci->clientnum);
putint(p, ci->team);
putint(p, -1);
}
if(ci && m_mp(gamemode) && ci->state.state!=CS_SPECTATOR)
{
if(smode && !smode->canspawn(ci, true))
{
ci->state.state = CS_DEAD;
putint(p, N_FORCEDEATH);
putint(p, ci->clientnum);
sendf(-1, 1, "ri2x", N_FORCEDEATH, ci->clientnum, ci->clientnum);
}
else
{
servstate &gs = ci->state;
spawnstate(ci);
putint(p, N_SPAWNSTATE);
putint(p, ci->clientnum);
sendstate(gs, p);
gs.lastspawn = gamemillis;
}
}
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);
putint(p, oi->state.frags);
putint(p, oi->state.flags);
putint(p, oi->state.deaths);
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, "ri3i7vi", N_RESUME, ci->clientnum, gs.state,
gs.frags, gs.flags, gs.deaths,
gs.lifesequence,
gs.health, gs.maxhealth,
gs.gunselect, NUMGUNS, gs.ammo, -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;
if(m_mp(gamemode) && delayspawn(sents[i].type)) sents[i].spawntime = spawntime(sents[i].type);
else 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 = (m_overtime ? 15 : 10)*60000;
interm = 0;
nextexceeded = 0;
copystring(smapname, s);
loaditems();
loopv(clients)
{
clientinfo *ci = clients[i];
ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
}
if(!m_mp(gamemode)) kicknonlocalclients(DISC_LOCAL);
sendf(-1, 1, "risii", N_MAPCHANGE, smapname, gamemode, 1);
clearteaminfo();
if(m_teammode) autoteam();
smode = NULL;
if(m_timed && smapname[0]) sendf(-1, 1, "ri2", N_TIMEUP, gamemillis < gamelimit && !interm ? max((gamelimit - gamemillis)/1000, 1) : 0);
loopv(clients)
{
clientinfo *ci = clients[i];
ci->mapchange();
ci->state.lasttimeplayed = lastmillis;
if(m_mp(gamemode) && ci->state.state!=CS_SPECTATOR) sendspawn(ci);
}
if(smode) smode->setup();
}
void forcemap(const char *map, int mode)
{
changemap(map, mode);
}
void checkintermission()
{
if(gamemillis >= gamelimit && !interm)
{
sendf(-1, 1, "ri2", N_TIMEUP, 0);
if(smode) smode->intermission();
changegamespeed(100);
interm = gamemillis + 10000;
}
}
void startintermission() { gamelimit = min(gamelimit, gamemillis); checkintermission(); }
void dodamage(clientinfo *target, clientinfo *actor, int damage, int atk, const vec &hitpush = vec(0, 0, 0))
{
servstate &ts = target->state;
ts.dodamage(damage);
if(target!=actor && !isteam(target->team, actor->team)) actor->state.damage += damage;
sendf(-1, 1, "ri5", N_DAMAGE, target->clientnum, actor->clientnum, damage, ts.health);
if(target==actor) target->setpushed();
else if(!hitpush.iszero())
{
ivec v(vec(hitpush).rescale(DNF));
sendf(ts.health<=0 ? -1 : target->ownernum, 1, "ri7", N_HITPUSH, target->clientnum, atk, damage, v.x, v.y, v.z);
target->setpushed();
}
if(ts.health<=0)
{
target->state.deaths++;
int fragvalue = smode ? smode->fragvalue(target, actor) : (target==actor || isteam(target->team, actor->team) ? -1 : 1);
actor->state.frags += fragvalue;
if(fragvalue>0)
{
int friends = 0, enemies = 0; // note: friends also includes the fragger
if(m_teammode) loopv(clients) if(clients[i]->team != actor->team) enemies++; else friends++;
else { friends = 1; enemies = clients.length()-1; }
actor->state.effectiveness += fragvalue*friends/float(max(enemies, 1));
}
teaminfo *t = m_teammode && validteam(actor->team) ? &teaminfos[actor->team-1] : NULL;
if(t) t->frags += fragvalue;
sendf(-1, 1, "ri5", N_DIED, target->clientnum, actor->clientnum, actor->state.frags, t ? t->frags : 0);
target->position.setsize(0);
if(smode) smode->died(target, actor);
ts.state = CS_DEAD;
ts.lastdeath = gamemillis;
ts.deadflush = ts.lastdeath + DEATHMILLIS;
// don't issue respawn yet until DEATHMILLIS has elapsed
// ts.respawn();
}
}
void suicide(clientinfo *ci)
{
servstate &gs = ci->state;
if(gs.state!=CS_ALIVE) return;
int fragvalue = smode ? smode->fragvalue(ci, ci) : -1;
ci->state.frags += fragvalue;
ci->state.deaths++;
teaminfo *t = m_teammode && validteam(ci->team) ? &teaminfos[ci->team-1] : NULL;
if(t) t->frags += fragvalue;
sendf(-1, 1, "ri5", N_DIED, ci->clientnum, ci->clientnum, gs.frags, t ? t->frags : 0);
ci->position.setsize(0);
if(smode) smode->died(ci, NULL);
gs.state = CS_DEAD;
gs.lastdeath = gamemillis;
gs.respawn();
}
void suicideevent::process(clientinfo *ci)
{
suicide(ci);
}
void explodeevent::process(clientinfo *ci)
{
servstate &gs = ci->state;
switch(atk)
{
case ATK_PULSE_SHOOT:
if(!gs.projs.remove(id)) return;
break;
default:
return;
}
sendf(-1, 1, "ri4x", N_EXPLODEFX, ci->clientnum, atk, id, ci->ownernum);
loopv(hits)
{
hitinfo &h = hits[i];
clientinfo *target = getinfo(h.target);
if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.dist<0 || h.dist>attacks[atk].exprad) continue;
bool dup = false;
loopj(i) if(hits[j].target==h.target) { dup = true; break; }
if(dup) continue;
float damage = attacks[atk].damage*(1-h.dist/EXP_DISTSCALE/attacks[atk].exprad);
if(target==ci) damage /= EXP_SELFDAMDIV;
if(damage > 0) dodamage(target, ci, max(int(damage), 1), atk, h.dir);
}
}
void shotevent::process(clientinfo *ci)
{
servstate &gs = ci->state;
int wait = millis - gs.lastshot;
if(!gs.isalive(gamemillis) ||
wait<gs.gunwait ||
!validatk(atk))
return;
int gun = attacks[atk].gun;
if(gs.ammo[gun]<=0 || (attacks[atk].range && from.dist(to) > attacks[atk].range + 1))
return;
gs.ammo[gun] -= attacks[atk].use;
gs.lastshot = millis;
gs.gunwait = attacks[atk].attackdelay;
sendf(-1, 1, "rii9x", N_SHOTFX, ci->clientnum, atk, id,
int(from.x*DMF), int(from.y*DMF), int(from.z*DMF),
int(to.x*DMF), int(to.y*DMF), int(to.z*DMF),
ci->ownernum);
gs.shotdamage += attacks[atk].damage*attacks[atk].rays;
switch(atk)
{
case ATK_PULSE_SHOOT: gs.projs.add(id); break;
default:
{
int totalrays = 0, maxrays = attacks[atk].rays;
loopv(hits)
{
hitinfo &h = hits[i];
clientinfo *target = getinfo(h.target);
if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.rays<1 || h.dist > attacks[atk].range + 1) continue;
totalrays += h.rays;
if(totalrays>maxrays) continue;
int damage = h.rays*attacks[atk].damage;
dodamage(target, ci, damage, atk, h.dir);
}
break;
}
}
}
void pickupevent::process(clientinfo *ci)
{
servstate &gs = ci->state;
if(m_mp(gamemode) && !gs.isalive(gamemillis)) return;
pickup(ent, ci->clientnum);
}
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;
if(!m_timed || gamemillis < gamelimit)
{
processevents();
if(curtime)
{
loopv(sents) if(sents[i].spawntime) // spawn entities when timer reached
{
sents[i].spawntime -= curtime;
if(sents[i].spawntime<=0)
{
sents[i].spawntime = 0;
sents[i].spawned = true;
sendf(-1, 1, "ri2", N_ITEMSPAWN, i);
}
}
}
if(smode) smode->update();
}
}
while(bannedips.length() && bannedips[0].expire-totalmillis <= 0) bannedips.remove(0);
loopv(connects) if(totalmillis-connects[i]->connectmillis>15000) disconnect_client(connects[i]->clientnum, DISC_TIMEOUT);
if(nextexceeded && gamemillis > nextexceeded && (!m_timed || gamemillis < gamelimit))
{
nextexceeded = 0;
loopvrev(clients)
{
clientinfo &c = *clients[i];
if(c.checkexceeded()) disconnect_client(c.clientnum, DISC_MSGERR);
else c.scheduleexceeded();
}
}
if(shouldstep && !gamepaused)
{
if(m_timed && smapname[0] && gamemillis-curtime>0) checkintermission();
if(interm > 0 && gamemillis>interm)
{
interm = -1;
}
}
shouldstep = clients.length() > 0;
}
void forcespectator(clientinfo *ci)
{
if(ci->state.state==CS_ALIVE) suicide(ci);
if(smode) smode->leavegame(ci);
ci->state.state = CS_SPECTATOR;
ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
sendf(-1, 1, "ri3", N_SPECTATOR, ci->clientnum, 1);
}
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; }
};
VAR(modifiedmapspectator, 0, 1, 2);
void checkmaps(int req = -1)
{
if(m_edit || !smapname[0]) return;
vector<crcinfo> crcs;
int total = 0, unsent = 0, invalid = 0;
if(mcrc) crcs.add(crcinfo(mcrc, clients.length() + 1));
loopv(clients)
{
clientinfo *ci = clients[i];
if(ci->state.state==CS_SPECTATOR) continue;
total++;
if(!ci->clientmap[0])
{
if(ci->mapcrc < 0) invalid++;
else if(!ci->mapcrc) unsent++;
}
else
{
crcinfo *match = NULL;
loopvj(crcs) if(crcs[j].crc == ci->mapcrc) { match = &crcs[j]; break; }
if(!match) crcs.add(crcinfo(ci->mapcrc, 1));
else match->matches++;
}
}
if(!mcrc && total - unsent < min(total, 4)) return;
crcs.sort(crcinfo::compare);
string msg;
loopv(clients)
{
clientinfo *ci = clients[i];
if(ci->state.state==CS_SPECTATOR || ci->clientmap[0] || ci->mapcrc >= 0 || (req < 0 && ci->warned)) continue;
formatstring(msg, "%s has modified map \"%s\"", colorname(ci), smapname);
sendf(req, 1, "ris", N_SERVMSG, msg);
if(req < 0) ci->warned = true;
}
if(crcs.length() >= 2) loopv(crcs)
{
crcinfo &info = crcs[i];
if(i || info.matches <= crcs[i+1].matches) loopvj(clients)
{
clientinfo *ci = clients[j];
if(ci->state.state==CS_SPECTATOR || !ci->clientmap[0] || ci->mapcrc != info.crc || (req < 0 && ci->warned)) continue;
formatstring(msg, "%s has modified map \"%s\"", colorname(ci), smapname);
sendf(req, 1, "ris", N_SERVMSG, msg);
if(req < 0) ci->warned = true;
}
}
if(req < 0 && modifiedmapspectator && (mcrc || modifiedmapspectator > 1)) loopv(clients)
{
clientinfo *ci = clients[i];
if(!ci->local && ci->warned && ci->state.state != CS_SPECTATOR) forcespectator(ci);
}
}
bool shouldspectate(clientinfo *ci)
{
return !ci->local && ci->warned && modifiedmapspectator && (mcrc || modifiedmapspectator > 1);
}
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()
{
bannedips.shrink(0);
}
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);
if(!m_mp(gamemode)) return DISC_LOCAL;
sendservinfo(ci);
return DISC_NONE;
}
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 masterconnected()
{
}
void masterdisconnected()
{
}
void processmasterinput(const char *cmd, int cmdlen, const char *args)
{
}
void receivefile(int sender, uchar *data, int len)
{
if(!m_edit || len <= 0 || len > 4*1024*1024) return;
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;
if(mastermode>=MM_LOCKED) ci->state.state = CS_SPECTATOR;
ci->state.lasttimeplayed = lastmillis;
ci->team = m_teammode ? chooseworstteam(ci) : 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(curmsg<p.length()) cm->messages.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<<k)) { n |= p.get()<<16; if(n&0x800000) n |= ~0U<<24; }
pos[k] = n/DMF;
}
loopk(3) p.get();
int mag = p.get(); if(flags&(1<<3)) mag |= p.get()<<8;
int dir = p.get(); dir |= p.get()<<8;
vec vel = vec((dir%360)*RAD, (clamp(dir/360, 0, 180)-90)*RAD).mul(mag/DVELF);
if(flags&(1<<4))
{
p.get(); if(flags&(1<<5)) p.get();
if(flags&(1<<6)) loopk(2) p.get();
}
if(cp)
{
if((!ci->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(curmsg<p.length()) cp->position.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);
ci->state.projs.reset();
}
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_CHECKMAPS:
checkmaps(sender);
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), gunselect = 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 || !validgun(gunselect)) break;
cq->state.lastspawn = -1;
cq->state.state = CS_ALIVE;
cq->state.gunselect = gunselect;
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_SAYTEAM:
{
getstring(text, p);
if(!ci || !cq || (ci->state.state==CS_SPECTATOR && !ci->local) || !m_teammode || !validteam(cq->team)) break;
filtertext(text, text, true, true);
loopv(clients)
{
clientinfo *t = clients[i];
if(t==cq || t->state.state==CS_SPECTATOR || cq->team != t->team) continue;
sendf(t->clientnum, 1, "riis", N_SAYTEAM, cq->clientnum, text);
}
if(isdedicatedserver() && cq) logoutf("%s <%s>: %s", colorname(cq), teamnames[cq->team], 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(i<MAXENTS && (sents.inrange(i) || canspawnitem(type)))
{
server_entity se = { NOTUSED, 0, false };
while(sents.length()<=i) sents.add(se);
sents[i].type = type;
if(canspawn ? !sents[i].spawned : (sents[i].spawned || sents[i].spawntime))
{
sents[i].spawntime = canspawn ? 1 : 0;
sents[i].spawned = false;
}
}
break;
}
case N_EDITVAR:
{
int type = getint(p);
getstring(text, p);
switch(type)
{
case ID_VAR: getint(p); break;
case ID_FVAR: getfloat(p); break;
case ID_SVAR: getstring(text, p);
}
if(ci && ci->state.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_FORCEINTERMISSION:
if(ci->local && !hasnonlocalclients()) startintermission();
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; }
const char *defaultmaster() { return "master.tesseract.gg"; }
int masterport() { return TESSERACT_MASTER_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, m_timed ? max((gamelimit - gamemillis)/1000, 0) : 0);
putint(p, MM_PRIVATE);
if(gamepaused || gamespeed != 100)
{
putint(p, gamepaused ? 1 : 0);
putint(p, gamespeed);
}
sendstring(smapname, p);
sendserverinforeply(p);
}
int protocolversion() { return PROTOCOL_VERSION; }
}