2020-04-16 18:28:40 +00:00
|
|
|
#include "game.hh"
|
2020-04-15 16:39:17 +00:00
|
|
|
|
|
|
|
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 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)
|
|
|
|
{
|
2020-04-17 18:19:42 +00:00
|
|
|
return true;
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2020-04-16 21:18:05 +00:00
|
|
|
string name;
|
2020-04-15 16:39:17 +00:00
|
|
|
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;
|
2020-04-16 21:18:05 +00:00
|
|
|
ENetPacket *getmap, *clipboard;
|
2020-04-15 16:39:17 +00:00
|
|
|
int lastclipboard, needclipboard;
|
|
|
|
|
2020-04-16 21:18:05 +00:00
|
|
|
clientinfo() : getmap(NULL), clipboard(NULL) { reset(); }
|
|
|
|
~clientinfo() { events.deletecontents(); cleanclipboard(); }
|
2020-04-15 16:39:17 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2020-04-16 21:18:05 +00:00
|
|
|
vector<clientinfo *> connects, clients;
|
2020-04-15 16:39:17 +00:00
|
|
|
|
|
|
|
void *newclientinfo() { return new clientinfo; }
|
|
|
|
void deleteclientinfo(void *ci) { delete (clientinfo *)ci; }
|
|
|
|
|
|
|
|
clientinfo *getinfo(int n)
|
|
|
|
{
|
|
|
|
if(n < MAXCLIENTS) return (clientinfo *)getclientinfo(n);
|
2020-04-16 21:18:05 +00:00
|
|
|
return NULL;
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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];
|
2020-04-16 21:18:05 +00:00
|
|
|
if(ci->clientnum!=exclude && (!nospec || ci->state.state!=CS_SPECTATOR || (priv && ci->local))) n++;
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
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;
|
2020-04-16 21:18:05 +00:00
|
|
|
if(name[0] && !duplicatename(ci, name)) return name;
|
2020-04-15 16:39:17 +00:00
|
|
|
static string cname[3];
|
|
|
|
static int cidx = 0;
|
|
|
|
cidx = (cidx+1)%3;
|
2020-04-16 21:18:05 +00:00
|
|
|
formatstring(cname[cidx], "%s \fs\f5(%d)\fr", name, ci->clientnum);
|
2020-04-15 16:39:17 +00:00
|
|
|
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)
|
|
|
|
{
|
2020-04-17 18:19:42 +00:00
|
|
|
if(victim==actor) return -1;
|
2020-04-15 16:39:17 +00:00
|
|
|
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
|
|
|
|
{
|
2020-04-17 18:19:42 +00:00
|
|
|
return false;
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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; }
|
|
|
|
|
2020-04-16 21:18:05 +00:00
|
|
|
static struct msgfilter
|
2020-04-15 16:39:17 +00:00
|
|
|
{
|
2020-04-16 21:18:05 +00:00
|
|
|
uchar msgmask[NUMMSG];
|
2020-04-15 16:39:17 +00:00
|
|
|
|
2020-04-16 21:18:05 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-04-15 16:39:17 +00:00
|
|
|
|
2020-04-16 21:18:05 +00:00
|
|
|
uchar operator[](int msg) const { return msg >= 0 && msg < NUMMSG ? msgmask[msg] : 0; }
|
2020-04-17 18:19:42 +00:00
|
|
|
} 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);
|
2020-04-15 16:39:17 +00:00
|
|
|
|
2020-04-16 21:18:05 +00:00
|
|
|
int checktype(int type, clientinfo *ci)
|
2020-04-15 16:39:17 +00:00
|
|
|
{
|
2020-04-16 21:18:05 +00:00
|
|
|
if(ci)
|
2020-04-15 16:39:17 +00:00
|
|
|
{
|
2020-04-16 21:18:05 +00:00
|
|
|
return type;
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
2020-04-16 21:18:05 +00:00
|
|
|
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;
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
|
2020-04-16 21:18:05 +00:00
|
|
|
struct worldstate
|
2020-04-15 16:39:17 +00:00
|
|
|
{
|
2020-04-16 21:18:05 +00:00
|
|
|
int uses, len;
|
|
|
|
uchar *data;
|
2020-04-15 16:39:17 +00:00
|
|
|
|
2020-04-16 21:18:05 +00:00
|
|
|
worldstate() : uses(0), len(0), data(NULL) {}
|
2020-04-15 16:39:17 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2020-04-16 21:18:05 +00:00
|
|
|
if(ci.position.empty() || !hasnonlocalclients()) return;
|
2020-04-15 16:39:17 +00:00
|
|
|
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)
|
|
|
|
{
|
2020-04-16 21:18:05 +00:00
|
|
|
if(clients.empty() || !hasnonlocalclients()) return false;
|
2020-04-15 16:39:17 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-04-17 18:19:42 +00:00
|
|
|
sendf(ci->ownernum, 1, "rii4", N_SPAWNSTATE, ci->clientnum, gs.lifesequence,
|
|
|
|
1, 1);
|
2020-04-15 16:39:17 +00:00
|
|
|
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)
|
|
|
|
{
|
2020-04-16 21:18:05 +00:00
|
|
|
putint(p, N_INITCLIENT);
|
|
|
|
putint(p, ci->clientnum);
|
|
|
|
sendstring(ci->name, p);
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2020-04-17 18:19:42 +00:00
|
|
|
return true;
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2020-04-17 18:19:42 +00:00
|
|
|
sendf(-1, 1, "ri3ii", N_RESUME, ci->clientnum, gs.state,
|
2020-04-15 16:39:17 +00:00
|
|
|
gs.lifesequence,
|
2020-04-17 18:19:42 +00:00
|
|
|
-1);
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2020-04-17 18:19:42 +00:00
|
|
|
sents[i].spawned = true;
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
notgotitems = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void changemap(const char *s, int mode)
|
|
|
|
{
|
|
|
|
pausegame(false);
|
|
|
|
changegamespeed(100);
|
|
|
|
if(smode) smode->cleanup();
|
|
|
|
|
|
|
|
gamemode = mode;
|
|
|
|
gamemillis = 0;
|
2020-04-17 18:19:42 +00:00
|
|
|
gamelimit = 600000;
|
2020-04-15 16:39:17 +00:00
|
|
|
interm = 0;
|
|
|
|
nextexceeded = 0;
|
|
|
|
copystring(smapname, s);
|
|
|
|
loaditems();
|
|
|
|
loopv(clients)
|
|
|
|
{
|
|
|
|
clientinfo *ci = clients[i];
|
|
|
|
ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
|
|
|
|
}
|
|
|
|
|
2020-04-17 18:19:42 +00:00
|
|
|
kicknonlocalclients(DISC_LOCAL);
|
2020-04-15 16:39:17 +00:00
|
|
|
|
|
|
|
sendf(-1, 1, "risii", N_MAPCHANGE, smapname, gamemode, 1);
|
|
|
|
|
2020-04-16 18:43:52 +00:00
|
|
|
smode = NULL;
|
2020-04-15 16:39:17 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2020-04-17 18:19:42 +00:00
|
|
|
processevents();
|
|
|
|
if(smode) smode->update();
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
loopv(connects) if(totalmillis-connects[i]->connectmillis>15000) disconnect_client(connects[i]->clientnum, DISC_TIMEOUT);
|
|
|
|
|
2020-04-17 18:19:42 +00:00
|
|
|
if(nextexceeded && gamemillis > nextexceeded)
|
2020-04-15 16:39:17 +00:00
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
2020-04-17 18:19:42 +00:00
|
|
|
return false;
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2020-04-16 21:18:05 +00:00
|
|
|
sendf(ci->clientnum, 1, "ri4", N_SERVINFO, ci->clientnum, PROTOCOL_VERSION, ci->sessionid);
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-04-17 18:19:42 +00:00
|
|
|
return DISC_LOCAL;
|
2020-04-15 16:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
clientinfo *ci = getinfo(sender);
|
2020-04-16 21:18:05 +00:00
|
|
|
if(ci->state.state==CS_SPECTATOR && !ci->local) return;
|
2020-04-15 16:39:17 +00:00
|
|
|
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;
|
|
|
|
|
2020-04-17 18:19:42 +00:00
|
|
|
ci->team = 0;
|
2020-04-15 16:39:17 +00:00
|
|
|
|
|
|
|
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);
|
2020-04-16 21:18:05 +00:00
|
|
|
connected(ci);
|
2020-04-15 16:39:17 +00:00
|
|
|
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;
|
2020-04-16 21:18:05 +00:00
|
|
|
#define QUEUE_MSG { if(cm && (!cm->local || hasnonlocalclients())) while(curmsg<p.length()) cm->messages.add(p.buf[curmsg++]); }
|
2020-04-15 16:39:17 +00:00
|
|
|
#define QUEUE_BUF(body) { \
|
2020-04-16 21:18:05 +00:00
|
|
|
if(cm && (!cm->local || hasnonlocalclients())) \
|
2020-04-15 16:39:17 +00:00
|
|
|
{ \
|
|
|
|
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)
|
|
|
|
{
|
2020-04-16 21:18:05 +00:00
|
|
|
if((!ci->local || hasnonlocalclients()) && (cp->state.state==CS_ALIVE || cp->state.state==CS_EDITING))
|
2020-04-15 16:39:17 +00:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
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:
|
|
|
|
{
|
2020-04-17 18:19:42 +00:00
|
|
|
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;
|
2020-04-15 16:39:17 +00:00
|
|
|
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(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_NEWMAP:
|
|
|
|
{
|
|
|
|
int size = getint(p);
|
2020-04-16 21:18:05 +00:00
|
|
|
if(!ci->local && ci->state.state==CS_SPECTATOR) break;
|
2020-04-15 16:39:17 +00:00
|
|
|
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);
|
2020-04-17 18:19:42 +00:00
|
|
|
putint(p, 0);
|
|
|
|
putint(p, 0);
|
2020-04-15 16:39:17 +00:00
|
|
|
if(gamepaused || gamespeed != 100)
|
|
|
|
{
|
|
|
|
putint(p, gamepaused ? 1 : 0);
|
|
|
|
putint(p, gamespeed);
|
|
|
|
}
|
|
|
|
sendstring(smapname, p);
|
|
|
|
sendserverinforeply(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
int protocolversion() { return PROTOCOL_VERSION; }
|
|
|
|
}
|
|
|
|
|