1 // server.h
2 
3 #define gamemode smode   // allows the gamemode macros to work with the server mode
4 
5 #define SERVER_PROTOCOL_VERSION    (PROTOCOL_VERSION)    // server without any gameplay modification
6 //#define SERVER_PROTOCOL_VERSION   (-PROTOCOL_VERSION)  // server with gameplay modification but compatible to vanilla client (using /modconnect)
7 //#define SERVER_PROTOCOL_VERSION  (PROTOCOL_VERSION)    // server with incompatible protocol (change PROTOCOL_VERSION in file protocol.h to a negative number!)
8 
9 #define valid_flag(f) (f >= 0 && f < 2)
10 
11 enum { GE_NONE = 0, GE_SHOT, GE_EXPLODE, GE_HIT, GE_AKIMBO, GE_RELOAD, GE_SUICIDE, GE_PICKUP };
12 enum { ST_EMPTY, ST_LOCAL, ST_TCPIP };
13 
14 extern int smode, servmillis;
15 
16 struct shotevent
17 {
18     int type;
19     int millis, id;
20     int gun;
21     float from[3], to[3];
22 };
23 
24 struct explodeevent
25 {
26     int type;
27     int millis, id;
28     int gun;
29 };
30 
31 struct hitevent
32 {
33     int type;
34     int target;
35     int lifesequence;
36     union
37     {
38         int info;
39         float dist;
40     };
41     float dir[3];
42 };
43 
44 struct suicideevent
45 {
46     int type;
47 };
48 
49 struct pickupevent
50 {
51     int type;
52     int ent;
53 };
54 
55 struct akimboevent
56 {
57     int type;
58     int millis, id;
59 };
60 
61 struct reloadevent
62 {
63     int type;
64     int millis, id;
65     int gun;
66 };
67 
68 union gameevent
69 {
70     int type;
71     shotevent shot;
72     explodeevent explode;
73     hitevent hit;
74     suicideevent suicide;
75     pickupevent pickup;
76     akimboevent akimbo;
77     reloadevent reload;
78 };
79 
80 template <int N>
81 struct projectilestate
82 {
83     int projs[N];
84     int numprojs;
85 
projectilestateprojectilestate86     projectilestate() : numprojs(0) {}
87 
resetprojectilestate88     void reset() { numprojs = 0; }
89 
addprojectilestate90     void add(int val)
91     {
92         if(numprojs>=N) numprojs = 0;
93         projs[numprojs++] = val;
94     }
95 
removeprojectilestate96     bool remove(int val)
97     {
98         loopi(numprojs) if(projs[i]==val)
99         {
100             projs[i] = projs[--numprojs];
101             return true;
102         }
103         return false;
104     }
105 };
106 
107 static const int DEATHMILLIS = 300;
108 
109 struct clientstate : playerstate
110 {
111     vec o;
112     int state;
113     int lastdeath, lastspawn, spawn, lifesequence;
114     bool forced;
115     int lastshot;
116     projectilestate<8> grenades;
117     int akimbomillis;
118     bool scoped;
119     int flagscore, frags, teamkills, deaths, shotdamage, damage, points, events, lastdisc, reconnections;
120 
clientstateclientstate121     clientstate() : state(CS_DEAD) {}
122 
isaliveclientstate123     bool isalive(int gamemillis)
124     {
125         return state==CS_ALIVE || (state==CS_DEAD && gamemillis - lastdeath <= DEATHMILLIS);
126     }
127 
waitexpiredclientstate128     bool waitexpired(int gamemillis)
129     {
130         int wait = gamemillis - lastshot;
131         loopi(NUMGUNS) if(wait < gunwait[i]) return false;
132         return true;
133     }
134 
resetclientstate135     void reset()
136     {
137         state = CS_DEAD;
138         lifesequence = -1;
139         grenades.reset();
140         akimbomillis = 0;
141         scoped = forced = false;
142         flagscore = frags = teamkills = deaths = shotdamage = damage = points = events = lastdisc = reconnections = 0;
143         respawn();
144     }
145 
respawnclientstate146     void respawn()
147     {
148         playerstate::respawn();
149         o = vec(-1e10f, -1e10f, -1e10f);
150         lastdeath = 0;
151         lastspawn = -1;
152         spawn = 0;
153         lastshot = 0;
154         akimbomillis = 0;
155         scoped = false;
156     }
157 };
158 
159 struct savedscore
160 {
161     string name;
162     uint ip;
163     int frags, flagscore, deaths, teamkills, shotdamage, damage, team, points, events, lastdisc, reconnections;
164     bool valid, forced;
165 
resetsavedscore166     void reset()
167     {
168         // to avoid 2 connections with the same score... this can disrupt some laggers that eventually produces 2 connections (but it is rare)
169         frags = flagscore = deaths = teamkills = shotdamage = damage = points = events = lastdisc = reconnections = 0;
170     }
171 
savesavedscore172     void save(clientstate &cs, int t)
173     {
174         frags = cs.frags;
175         flagscore = cs.flagscore;
176         deaths = cs.deaths;
177         teamkills = cs.teamkills;
178         shotdamage = cs.shotdamage;
179         damage = cs.damage;
180         points = cs.points;
181         forced = cs.forced;
182         events = cs.events;
183         lastdisc = cs.lastdisc;
184         reconnections = cs.reconnections;
185         team = t;
186         valid = true;
187     }
188 
restoresavedscore189     void restore(clientstate &cs)
190     {
191         cs.frags = frags;
192         cs.flagscore = flagscore;
193         cs.deaths = deaths;
194         cs.teamkills = teamkills;
195         cs.shotdamage = shotdamage;
196         cs.damage = damage;
197         cs.points = points;
198         cs.forced = forced;
199         cs.events = events;
200         cs.lastdisc = lastdisc;
201         cs.reconnections = reconnections;
202         reset();
203     }
204 };
205 
206 struct medals
207 {
208     int dpt, lasthit, lastgun, ncovers, nhs;
209     int combohits, combo, combofrags, combotime, combodamage, ncombos;
210     int ask, askmillis, linked, linkmillis, linkreason, upmillis, flagmillis;
211     int totalhits, totalshots;
212     bool updated, combosend;
213     vec pos, flagpos;
resetmedals214     void reset()
215     {
216         dpt = lasthit = lastgun = ncovers = nhs = 0;
217         combohits = combo = combofrags = combotime = combodamage = ncombos = 0;
218         askmillis = linkmillis = upmillis = flagmillis = 0;
219         linkreason = linked = ask = -1;
220         totalhits = totalshots = 0;
221         updated = combosend = false;
222         pos = flagpos = vec(-1e10f, -1e10f, -1e10f);
223     }
224 };
225 
226 struct client                   // server side version of "dynent" type
227 {
228     int type;
229     int clientnum;
230     ENetPeer *peer;
231     string hostname;
232     string name;
233     int team;
234     char lang[3];
235     int ping;
236     int skin[2];
237     int vote;
238     int role;
239     int connectmillis, lmillis, ldt, spj;
240     int mute, spam, lastvc; // server side voice comm spam control
241     int acversion, acbuildtype;
242     bool isauthed; // for passworded servers
243     bool haswelcome;
244     bool isonrightmap, loggedwrongmap, freshgame;
245     bool timesync;
246     int overflow;
247     int gameoffset, lastevent, lastvotecall;
248     int lastprofileupdate, fastprofileupdates;
249     int demoflags;
250     clientstate state;
251     vector<gameevent> events;
252     vector<uchar> position, messages;
253     string lastsaytext;
254     int saychars, lastsay, spamcount, badspeech, badmillis;
255     int at3_score, at3_lastforce, eff_score;
256     bool at3_dontmove;
257     int spawnindex;
258     int spawnperm, spawnpermsent;
259     int salt;
260     string pwd;
261     uint authreq; // for AUTH
262     string authname; // for AUTH
263     int mapcollisions, farpickups;
264     enet_uint32 bottomRTT;
265     medals md;
266     bool upspawnp;
267     int lag;
268     vec spawnp;
269     int nvotes;
270     int input, inputmillis;
271     int ffire, wn, f, g, t, y, p;
272     int yb, pb, oy, op, lda, ldda, fam;
273     int nt[10], np, lp, ls, lsm, ld, nd, nlt, lem, led;
274     vec cp[10], dp[10], d0, lv, lt, le;
275     float dda, tr, sda;
276     int ps, ph, tcn, bdt, pws;
277     float pr;
278     int yls, pls, tls;
279     int bs, bt, blg, bp;
280 
addeventclient281     gameevent &addevent()
282     {
283         static gameevent dummy;
284         if(events.length()>100) return dummy;
285         return events.add();
286     }
287 
288     void mapchange(bool getmap = false)
289     {
290         state.reset();
291         events.setsize(0);
292         overflow = 0;
293         timesync = false;
294         isonrightmap = m_coop;
295         spawnperm = SP_WRONGMAP;
296         spawnpermsent = servmillis;
297         if(!getmap)
298         {
299             loggedwrongmap = false;
300             freshgame = true;         // permission to spawn at once
301         }
302         lastevent = 0;
303         at3_lastforce = eff_score = 0;
304         mapcollisions = farpickups = 0;
305         md.reset();
306         upspawnp = false;
307         lag = 0;
308         spawnp = vec(-1e10f, -1e10f, -1e10f);
309         lmillis = ldt = spj = 0;
310         ffire = 0;
311         f = g = y = p = t = 0;
312         yb = pb = oy = op = lda = ldda = fam = 0;
313         np = lp = ls = lsm = ld = nd = nlt = lem = led = 0;
314         d0 = lv = lt = le = vec(0,0,0);
315         loopi(10) { cp[i] = dp[i] = vec(0,0,0); nt[i] = 0; }
316         dda = tr = sda = 0;
317         ps = ph = bdt = pws = 0;
318         tcn = -1;
319         pr = 0.0f;
320         yls = pls = tls = 0;
321     }
322 
resetclient323     void reset()
324     {
325         name[0] = pwd[0] = demoflags = 0;
326         bottomRTT = ping = 9999;
327         team = TEAM_SPECT;
328         state.state = CS_SPECTATE;
329         loopi(2) skin[i] = 0;
330         position.setsize(0);
331         messages.setsize(0);
332         isauthed = haswelcome = false;
333         role = CR_DEFAULT;
334         lastvotecall = 0;
335         lastprofileupdate = fastprofileupdates = 0;
336         vote = VOTE_NEUTRAL;
337         lastsaytext[0] = '\0';
338         saychars = 0;
339         spawnindex = -1;
340         authreq = 0; // for AUTH
341         mapchange();
342         freshgame = false;         // don't spawn into running games
343         mute = spam = lastvc = badspeech = badmillis = nvotes = 0;
344         input = inputmillis = 0;
345         wn = -1;
346         bs = bt = blg = bp = 0;
347     }
348 
zapclient349     void zap()
350     {
351         type = ST_EMPTY;
352         role = CR_DEFAULT;
353         isauthed = haswelcome = false;
354     }
355 };
356 
357 struct ban
358 {
359     ENetAddress address;
360     int millis, type;
361 };
362 
363 struct worldstate
364 {
365     enet_uint32 uses;
366     vector<uchar> positions, messages;
367 };
368 
369 struct server_entity            // server side version of "entity" type
370 {
371     int type;
372     bool spawned, legalpickup, twice;
373     int spawntime;
374     short x, y;
375 };
376 
377 struct clientidentity
378 {
379     uint ip;
380     int clientnum;
381 };
382 
383 struct demofile
384 {
385     string info;
386     string file;
387     uchar *data;
388     int len;
389     vector<clientidentity> clientssent;
390 };
391 
392 void startgame(const char *newname, int newmode, int newtime = -1, bool notify = true);
393 void disconnect_client(int n, int reason = -1);
394 void sendiplist(int receiver, int cn = -1);
395 int clienthasflag(int cn);
396 bool refillteams(bool now = false, int ftr = FTR_AUTOTEAM);
397 void changeclientrole(int client, int role, char *pwd = NULL, bool force=false);
398 mapstats *getservermapstats(const char *mapname, bool getlayout = false, int *maploc = NULL);
399 int findmappath(const char *mapname, char *filename = NULL);
400 int calcscores();
401 void recordpacket(int chan, void *data, int len);
402 void senddisconnectedscores(int cn);
403 void process(ENetPacket *packet, int sender, int chan);
404 void welcomepacket(packetbuf &p, int n);
405 void sendwelcome(client *cl, int chan = 1);
406 void sendpacket(int n, int chan, ENetPacket *packet, int exclude = -1, bool demopacket = false);
407 int numclients();
408 bool updateclientteam(int cln, int newteam, int ftr);
409 void forcedeath(client *cl);
410 void sendf(int cn, int chan, const char *format, ...);
411 
412 extern bool isdedicated;
413 extern string smapname;
414 extern mapstats smapstats;
415 extern char *maplayout;
416 
417 const char *messagenames[SV_NUM] =
418 {
419     "SV_SERVINFO", "SV_WELCOME", "SV_INITCLIENT", "SV_POS", "SV_POSC", "SV_POSN", "SV_TEXT", "SV_TEAMTEXT", "SV_TEXTME", "SV_TEAMTEXTME", "SV_TEXTPRIVATE",
420     "SV_SOUND", "SV_VOICECOM", "SV_VOICECOMTEAM", "SV_CDIS",
421     "SV_SHOOT", "SV_EXPLODE", "SV_SUICIDE", "SV_AKIMBO", "SV_RELOAD", "SV_AUTHT", "SV_AUTHREQ", "SV_AUTHTRY", "SV_AUTHANS", "SV_AUTHCHAL",
422     "SV_GIBDIED", "SV_DIED", "SV_GIBDAMAGE", "SV_DAMAGE", "SV_HITPUSH", "SV_SHOTFX", "SV_THROWNADE",
423     "SV_TRYSPAWN", "SV_SPAWNSTATE", "SV_SPAWN", "SV_SPAWNDENY", "SV_FORCEDEATH", "SV_RESUME",
424     "SV_DISCSCORES", "SV_TIMEUP", "SV_EDITENT", "SV_ITEMACC",
425     "SV_MAPCHANGE", "SV_ITEMSPAWN", "SV_ITEMPICKUP",
426     "SV_PING", "SV_PONG", "SV_CLIENTPING", "SV_GAMEMODE",
427     "SV_EDITMODE", "SV_EDITH", "SV_EDITT", "SV_EDITS", "SV_EDITD", "SV_EDITE", "SV_NEWMAP",
428     "SV_SENDMAP", "SV_RECVMAP", "SV_REMOVEMAP",
429     "SV_SERVMSG", "SV_ITEMLIST", "SV_WEAPCHANGE", "SV_PRIMARYWEAP",
430     "SV_FLAGACTION", "SV_FLAGINFO", "SV_FLAGMSG", "SV_FLAGCNT",
431     "SV_ARENAWIN",
432     "SV_SETADMIN", "SV_SERVOPINFO",
433     "SV_CALLVOTE", "SV_CALLVOTESUC", "SV_CALLVOTEERR", "SV_VOTE", "SV_VOTERESULT",
434     "SV_SETTEAM", "SV_TEAMDENY", "SV_SERVERMODE",
435     "SV_IPLIST",
436     "SV_LISTDEMOS", "SV_SENDDEMOLIST", "SV_GETDEMO", "SV_SENDDEMO", "SV_DEMOPLAYBACK",
437     "SV_CONNECT",
438     "SV_SWITCHNAME", "SV_SWITCHSKIN", "SV_SWITCHTEAM",
439     "SV_CLIENT",
440     "SV_EXTENSION",
441     "SV_MAPIDENT", "SV_HUDEXTRAS", "SV_POINTS"
442 };
443 
444 const char *entnames[MAXENTTYPES] =
445 {
446     "none?",
447     "light", "playerstart", "pistol", "ammobox","grenades",
448     "health", "helmet", "armour", "akimbo",
449     "mapmodel", "trigger", "ladder", "ctf-flag", "sound", "clip", "plclip"
450 };
451 
452 // see entity.h:61: struct itemstat { int add, start, max, sound; };
453 // Please update ./ac_website/htdocs/docs/introduction.html if these figures change.
454 itemstat ammostats[NUMGUNS] =
455 {
456     {  1,  1,   1,  S_ITEMAMMO  },   // knife dummy
457     { 20, 60, 100,  S_ITEMAMMO  },   // pistol
458     { 15, 30,  30,  S_ITEMAMMO  },   // carbine
459     { 14, 28,  21,  S_ITEMAMMO  },   // shotgun
460     { 60, 90,  90,  S_ITEMAMMO  },   // subgun
461     { 10, 20,  15,  S_ITEMAMMO  },   // sniper
462     { 40, 60,  60,  S_ITEMAMMO  },   // assault
463     { 30, 45,  75,  S_ITEMAMMO  },   // cpistol
464     {  1,  0,   3,  S_ITEMAMMO  },   // grenade
465     {100,  0, 100,  S_ITEMAKIMBO}    // akimbo
466 };
467 
468 itemstat powerupstats[I_ARMOUR-I_HEALTH+1] =
469 {
470     {33, 0, 100, S_ITEMHEALTH}, // 0 health
471     {25, 0, 100, S_ITEMHELMET}, // 1 helmet
472     {50, 0, 100, S_ITEMARMOUR}, // 2 armour
473 };
474 
475 guninfo guns[NUMGUNS] =
476 {
477     // Please update ./ac_website/htdocs/docs/introduction.html if these figures change.
478     //mKR: mdl_kick_rot && mKB: mdl_kick_back
479     //reI: recoilincrease && reB: recoilbase && reM: maxrecoil && reF: recoilbackfade
480     //pFX: pushfactor
481     //modelname                   reload       attackdelay      piercing     part     recoil       mKR       reI          reM           pFX
482     //              sound                reloadtime        damage    projspeed  spread     magsize     mKB        reB             reF           isauto
483     { "knife",      S_KNIFE,      S_NULL,     0,      500,    50, 100,     0,   0,  1,    1,   1,    0,  0,   0,   0,      0,      0,   1,      false },
484     { "pistol",     S_PISTOL,     S_RPISTOL,  1400,   160,    18,   0,     0,   0, 53,   10,   10,   6,  5,   6,  35,     58,     125,  1,      false },
485     { "carbine",    S_CARBINE,    S_RCARBINE, 1800,   720,    60,  40,     0,   0, 10,   60,   10,   4,  4,  10,  60,     60,     150,  1,      false },
486     { "shotgun",    S_SHOTGUN,    S_RSHOTGUN, 2400,   880,    1,    0,     0,   0,  1,   35,    7,   9,  9,  10, 140,    140,    125,   1,      false },   // CAUTION dmg only sane for server!
487     { "subgun",     S_SUBGUN,     S_RSUBGUN,  1650,   80,     16,   0,     0,   0, 45,   15,   30,   1,  2,   5,  25,     50,     188,  1,      true  },
488     { "sniper",     S_SNIPER,     S_RSNIPER,  1950,   1500,   82,  25,     0,   0, 50,   50,    5,   4,  4,  10,  85,     85,     100,  1,      false },
489     { "assault",    S_ASSAULT,    S_RASSAULT, 2000,   120,    22,   0,     0,   0, 18,   30,   20,   0,  2,   3,  25,     50,     115,  1,      true  },
490     { "cpistol",    S_PISTOL,     S_RPISTOL,  1400,   120,    19,   0,     0,   0, 35,   10,   15,   6,  5,   6,  35,     50,     125,  1,      false },   // temporary
491     { "grenade",    S_NULL,       S_NULL,     1000,   650,    200,  0,    20,  6,  1,    1,   1,    3,   1,    0,   0,      0,      0,   3,      false },
492     { "pistol",     S_PISTOL,     S_RAKIMBO,  1400,   80,     19,   0,     0,   0, 50,   10,   20,   6,  5,   4,  15,     25,     115,  1,      true  },
493 };
494 
495 const char *teamnames[TEAM_NUM+1] = {"CLA", "RVSF", "CLA-SPECT", "RVSF-SPECT", "SPECTATOR", "void"};
496 const char *teamnames_s[TEAM_NUM+1] = {"CLA", "RVSF", "CSPC", "RSPC", "SPEC", "void"};
497 
498 // for both client and server
499 // default messages are hardcoded !
500 char killmessages[2][NUMGUNS][MAXKILLMSGLEN] = {{ "", "busted", "picked off", "peppered", "sprayed", "punctured", "shredded", "busted", "", "busted" }, { "slashed", "", "", "splattered", "", "headshot", "", "", "gibbed", "" }};
501