1 #include "game.h"
2 namespace client
3 {
4 	bool sendinfo = false, isready = false, remote = false,
5 		demoplayback = false, needsmap = false, gettingmap = false, sendcrc = false;
6 	int lastping = 0, sessionid = 0;
7     string connectpass = "";
8 	vector<mapvote> mapvotes;
9 
vote(gameent * d,const char * text,int mode,int muts)10 	void vote(gameent *d, const char *text, int mode, int muts)
11 	{
12 		mapvote *m = NULL;
13 		loopv(mapvotes) if(mapvotes[i].player == d) { m = &mapvotes[i]; break; }
14 		if(!m) m = &mapvotes.add();
15 		m->player = d;
16 		copystring(m->map, text);
17 		m->mode = mode;
18 		m->muts = muts;
19 		SEARCHBINDCACHE(votekey)("showgui vote", 0);
20 		SEARCHBINDCACHE(gamekey)("showgui game", 0);
21 		conoutft(CON_MESG, "\fc%s suggests: \fs\fw%s on %s, press \fs\fc%s\fS to vote or \fs\fc%s\fS to select your own", game::colorname(d), server::gamename(mode, muts), text, votekey, gamekey);
22 	}
getvotes(int vote)23     void getvotes(int vote)
24     {
25     	if(!vote) intret(mapvotes.length());
26     	else
27     	{
28 			mkstring(text);
29 			if(mapvotes.inrange(--vote)) formatstring(text)("%d %d %d %s", mapvotes[vote].player->clientnum, mapvotes[vote].mode, mapvotes[vote].muts, mapvotes[vote].map);
30 			result(text);
31     	}
32     }
33     ICOMMAND(getvote, "i", (int *num), getvotes(*num));
34 
35     int lastauth = 0;
36     string authname = "", authkey = "";
37 
setauthkey(const char * name,const char * key)38     void setauthkey(const char *name, const char *key)
39     {
40         copystring(authname, name);
41         copystring(authkey, key);
42     }
43     ICOMMAND(authkey, "ss", (char *name, char *key), setauthkey(name, key));
44 
45 	// collect c2s messages conveniently
46 	vector<uchar> messages;
47     bool messagereliable = false;
48 
49 	VARP(colourchat, 0, 1, 1);
50 	VARP(showlaptimes, 0, 2, 3); // 0 = off, 1 = only player, 2 = +humans, 3 = +bots
51 	SVARP(serversort, "");
52 
53 	ICOMMAND(mastermode, "i", (int *val), addmsg(SV_MASTERMODE, "ri", *val));
54 	ICOMMAND(getname, "", (), result(game::player1->name));
55 	ICOMMAND(getteam, "", (), result(teamtype[game::player1->team].name));
56     ICOMMAND(getteamicon, "", (), result(hud::teamtex(game::player1->team)));
57 
getname()58     const char *getname() { return game::player1->name; }
59 
switchname(const char * name)60 	void switchname(const char *name)
61 	{
62 		if(name[0])
63 		{
64 			string text; filtertext(text, name);
65 			copystring(game::player1->name, text, MAXNAMELEN);
66 			addmsg(SV_SWITCHNAME, "rs", game::player1->name);
67 		}
68 		else conoutft(CON_MESG, "\fgyour name is: %s", *game::player1->name ? game::colorname(game::player1) : "<not set>");
69 	}
70 	ICOMMAND(name, "s", (char *s), switchname(s));
71 
teamname(const char * team)72 	int teamname(const char *team)
73 	{
74 		if(m_fight(game::gamemode) && m_team(game::gamemode, game::mutators))
75 		{
76 			if(team[0])
77 			{
78 				int t = atoi(team);
79 
80 				loopi(numteams(game::gamemode, game::mutators))
81 				{
82 					if((t && t == i+TEAM_FIRST) || !strcasecmp(teamtype[i+TEAM_FIRST].name, team))
83 					{
84 						return i+TEAM_FIRST;
85 					}
86 				}
87 			}
88 			return TEAM_FIRST;
89 		}
90 		return TEAM_NEUTRAL;
91 	}
92 
switchteam(const char * team)93 	void switchteam(const char *team)
94 	{
95 		if(team[0])
96 		{
97 			if(m_fight(game::gamemode) && m_team(game::gamemode, game::mutators) && game::player1->state != CS_SPECTATOR && game::player1->state != CS_EDITING)
98 			{
99 				int t = teamname(team);
100 				if(t != game::player1->team)
101 				{
102 					if(game::player1->team != t) hud::lastteam = 0;
103 					game::player1->team = t;
104 					addmsg(SV_SWITCHTEAM, "ri", game::player1->team);
105 				}
106 			}
107 			else conoutft(CON_MESG, "\frcan only change teams when actually playing in team games");
108 		}
109 		else conoutft(CON_MESG, "\fs\fgyour team is:\fS \fs%s%s\fS", teamtype[game::player1->team].chat, teamtype[game::player1->team].name);
110 	}
111 	ICOMMAND(team, "s", (char *s), switchteam(s));
112 
numchannels()113 	int numchannels() { return 3; }
114 
writeclientinfo(stream * f)115 	void writeclientinfo(stream *f)
116 	{
117 		f->printf("name \"%s\"\n\n", game::player1->name);
118 	}
119 
connectattempt(const char * name,int port,int qport,const char * password,const ENetAddress & address)120     void connectattempt(const char *name, int port, int qport, const char *password, const ENetAddress &address)
121     {
122         copystring(connectpass, password);
123     }
124 
connectfail()125     void connectfail()
126     {
127         memset(connectpass, 0, sizeof(connectpass));
128     }
129 
gameconnect(bool _remote)130 	void gameconnect(bool _remote)
131 	{
132 		remote = _remote;
133 		if(editmode) toggleedit();
134 	}
135 
gamedisconnect(int clean)136 	void gamedisconnect(int clean)
137 	{
138 		if(editmode) toggleedit();
139 		gettingmap = needsmap = remote = isready = sendinfo = false;
140         sessionid = 0;
141 		messages.setsize(0);
142 		mapvotes.setsize(0);
143         messagereliable = false;
144 		projs::remove(game::player1);
145         removetrackedparticles(game::player1);
146 		removetrackedsounds(game::player1);
147 		game::player1->clientnum = -1;
148 		game::player1->privilege = PRIV_NONE;
149 		game::gamemode = game::mutators = -1;
150 		loopv(game::players) if(game::players[i]) game::clientdisconnected(i);
151 		emptymap(0, true, NULL, true);
152 		smartmusic(true, false);
153 		enumerate(*idents, ident, id, {
154 			if(id.flags&IDF_CLIENT) // reset vars
155 			{
156 				switch(id.type)
157 				{
158 					case ID_VAR:
159 					{
160 						setvar(id.name, id.def.i, true);
161 						break;
162 					}
163 					case ID_FVAR:
164 					{
165 						setfvar(id.name, id.def.f, true);
166 						break;
167 					}
168 					case ID_SVAR:
169 					{
170 						setsvar(id.name, *id.def.s ? id.def.s : "", true);
171 						break;
172 					}
173 					default: break;
174 				}
175 			}
176 		});
177 		if(clean)
178 		{
179 			game::clientmap[0] = '\0';
180 		}
181 	}
182 
allowedittoggle(bool edit)183 	bool allowedittoggle(bool edit)
184 	{
185 		bool allow = edit || m_edit(game::gamemode); // && game::player1->state == CS_ALIVE);
186 		if(!allow) conoutft(CON_MESG, "\fryou must be in edit mode to start editing");
187 		return allow;
188 	}
189 
edittoggled(bool edit)190 	void edittoggled(bool edit)
191 	{
192 		game::player1->editspawn(lastmillis, m_weapon(game::gamemode, game::mutators), m_health(game::gamemode, game::mutators), m_arena(game::gamemode, game::mutators), spawngrenades >= (m_insta(game::gamemode, game::mutators) ? 2 : 1));
193 		game::player1->state = edit ? CS_EDITING : CS_ALIVE;
194 		game::player1->resetinterp();
195 		game::resetstate();
196 		physics::entinmap(game::player1, true); // find spawn closest to current floating pos
197 		projs::remove(game::player1);
198 		if(m_edit(game::gamemode)) addmsg(SV_EDITMODE, "ri", edit ? 1 : 0);
199 		entities::edittoggled(edit);
200 	}
201 
getclientname(int cn)202     const char *getclientname(int cn)
203     {
204         gameent *d = game::getclient(cn);
205         return d ? d->name : "";
206     }
207     ICOMMAND(getclientname, "i", (int *cn), result(getclientname(*cn)));
208 
getclientteam(int cn)209     int getclientteam(int cn)
210     {
211         gameent *d = game::getclient(cn);
212         return d ? d->team : -1;
213     }
214     ICOMMAND(getclientteam, "i", (int *cn), intret(getclientteam(*cn)));
215 
isspectator(int cn)216     bool isspectator(int cn)
217     {
218         gameent *d = game::getclient(cn);
219         return d->state==CS_SPECTATOR;
220     }
221     ICOMMAND(isspectator, "i", (int *cn), intret(isspectator(*cn) ? 1 : 0));
222 
isai(int cn,int type)223     bool isai(int cn, int type)
224     {
225         gameent *d = game::getclient(cn);
226         int aitype = type > 0 && type < AI_MAX ? type : AI_BOT;
227         return d->aitype==aitype;
228     }
229     ICOMMAND(isai, "ii", (int *cn, int *type), intret(isai(*cn, *type) ? 1 : 0));
230 
parseplayer(const char * arg)231     int parseplayer(const char *arg)
232     {
233         char *end;
234         int n = strtol(arg, &end, 10);
235         if(*arg && !*end)
236         {
237             if(n!=game::player1->clientnum && !game::players.inrange(n)) return -1;
238             return n;
239         }
240         // try case sensitive first
241         loopv(game::players) if(game::players[i])
242         {
243             gameent *o = game::players[i];
244             if(!strcmp(arg, o->name)) return o->clientnum;
245         }
246         // nothing found, try case insensitive
247         loopv(game::players) if(game::players[i])
248         {
249             gameent *o = game::players[i];
250             if(!strcasecmp(arg, o->name)) return o->clientnum;
251         }
252         return -1;
253     }
254     ICOMMAND(getclientnum, "s", (char *name), intret(name[0] ? parseplayer(name) : game::player1->clientnum));
255 
listclients(bool local)256     void listclients(bool local)
257     {
258         vector<char> buf;
259         string cn;
260         int numclients = 0;
261         if(local)
262         {
263             formatstring(cn)("%d", game::player1->clientnum);
264             buf.put(cn, strlen(cn));
265             numclients++;
266         }
267         loopv(game::players) if(game::players[i])
268         {
269             formatstring(cn)("%d", game::players[i]->clientnum);
270             if(numclients++) buf.add(' ');
271             buf.put(cn, strlen(cn));
272         }
273         buf.add('\0');
274         result(buf.getbuf());
275     }
276     ICOMMAND(listclients, "i", (int *local), listclients(*local!=0));
277 
clearbans()278 	void clearbans()
279 	{
280 		addmsg(SV_CLEARBANS, "r");
281 	}
282 	ICOMMAND(clearbans, "", (char *s), clearbans());
283 
kick(const char * arg)284 	void kick(const char *arg)
285 	{
286 		int i = parseplayer(arg);
287 		if(i>=0 && i!=game::player1->clientnum) addmsg(SV_KICK, "ri", i);
288 	}
289 	ICOMMAND(kick, "s", (char *s), kick(s));
290 
setteam(const char * arg1,const char * arg2)291 	void setteam(const char *arg1, const char *arg2)
292 	{
293 		if(m_fight(game::gamemode) && m_team(game::gamemode, game::mutators))
294 		{
295 			int i = parseplayer(arg1);
296 			if(i>=0 && i!=game::player1->clientnum)
297 			{
298 				int t = teamname(arg2);
299 				if(t) addmsg(SV_SETTEAM, "ri2", i, t);
300 			}
301 		}
302 		else conoutft(CON_MESG, "\frcan only change teams in team games");
303 	}
304 	ICOMMAND(setteam, "ss", (char *who, char *team), setteam(who, team));
305 
hashpwd(const char * pwd)306     void hashpwd(const char *pwd)
307     {
308         if(game::player1->clientnum<0) return;
309         string hash;
310         server::hashpassword(game::player1->clientnum, sessionid, pwd, hash);
311         result(hash);
312     }
313     COMMAND(hashpwd, "s");
314 
setmaster(const char * arg)315     void setmaster(const char *arg)
316     {
317         if(!arg[0]) return;
318         int val = 1;
319         mkstring(hash);
320         if(!arg[1] && isdigit(arg[0])) val = atoi(arg);
321         else server::hashpassword(game::player1->clientnum, sessionid, arg, hash);
322         addmsg(SV_SETMASTER, "ris", val, hash);
323     }
324 	COMMAND(setmaster, "s");
325 
tryauth()326     void tryauth()
327     {
328         if(!authname[0]) return;
329         lastauth = lastmillis;
330         addmsg(SV_AUTHTRY, "rs", authname);
331     }
332 	ICOMMAND(auth, "", (), tryauth());
333 
togglespectator(int val,const char * who)334     void togglespectator(int val, const char *who)
335 	{
336         int i = who[0] ? parseplayer(who) : game::player1->clientnum;
337 		if(i>=0) addmsg(SV_SPECTATOR, "rii", i, val);
338 	}
339 	ICOMMAND(spectator, "is", (int *val, char *who), togglespectator(*val, who));
340 
addmsg(int type,const char * fmt,...)341 	void addmsg(int type, const char *fmt, ...)
342 	{
343 		static uchar buf[MAXTRANS];
344 		ucharbuf p(buf, MAXTRANS);
345 		putint(p, type);
346 		int numi = 1, nums = 0;
347 		bool reliable = false;
348 		if(fmt)
349 		{
350 			va_list args;
351 			va_start(args, fmt);
352 			while(*fmt) switch(*fmt++)
353 			{
354 				case 'r': reliable = true; break;
355 				case 'v':
356 				{
357 					int n = va_arg(args, int);
358 					int *v = va_arg(args, int *);
359 					loopi(n) putint(p, v[i]);
360 					numi += n;
361 					break;
362 				}
363 				case 'i':
364 				{
365 					int n = isdigit(*fmt) ? *fmt++-'0' : 1;
366 					loopi(n) putint(p, va_arg(args, int));
367 					numi += n;
368 					break;
369 				}
370                 case 'f':
371                 {
372                     int n = isdigit(*fmt) ? *fmt++-'0' : 1;
373                     loopi(n) putfloat(p, (float)va_arg(args, double));
374                     numi += n;
375                     break;
376                 }
377 				case 's': sendstring(va_arg(args, const char *), p); nums++; break;
378 			}
379 			va_end(args);
380 		}
381 		int num = nums?0:numi, msgsize = msgsizelookup(type);
382         if(msgsize && num!=msgsize) { defformatstring(s)("inconsistent msg size for %d (%d != %d)", type, num, msgsize); fatal(s); }
383         if(reliable) messagereliable = true;
384         messages.put(buf, p.length());
385 	}
386 
saytext(gameent * d,int flags,char * text)387 	void saytext(gameent *d, int flags, char *text)
388 	{
389 		if(!colourchat) filtertext(text, text);
390 		mkstring(s);
391 		bool team = flags&SAY_TEAM;
392 		defformatstring(m)("%s", game::colorname(d));
393 		if(team)
394 		{
395 			defformatstring(t)(" (\fs%s%s\fS)", teamtype[d->team].chat, teamtype[d->team].name);
396 			concatstring(m, t);
397 		}
398 		if(flags&SAY_ACTION) formatstring(s)("\fm* \fs%s\fS \fs\fm%s\fS", m, text);
399 		else formatstring(s)("\fa<\fs\fw%s\fS> \fs\fw%s\fS", m, text);
400 
401 		if(d->state != CS_SPECTATOR)
402 		{
403 			defformatstring(ds)("<sub>%s", s);
404 			part_textcopy(d->abovehead(), ds, PART_TEXT, game::aboveheadfade, 0xFFFFFF, 2, 1, -10, 0, d);
405 		}
406 
407 		conoutft(CON_CHAT, "%s", s);
408 		playsound(S_CHAT, camera1->o, camera1, SND_FORCED);
409 	}
410 
toserver(int flags,char * text)411 	void toserver(int flags, char *text)
412 	{
413 		if(ready())
414 		{
415 			saytext(game::player1, flags, text);
416 			addmsg(SV_TEXT, "ri2s", game::player1->clientnum, flags, text);
417 		}
418 	}
419 	ICOMMAND(say, "C", (char *s), toserver(SAY_NONE, s));
420 	ICOMMAND(me, "C", (char *s), toserver(SAY_ACTION, s));
421 	ICOMMAND(sayteam, "C", (char *s), toserver(SAY_TEAM, s));
422 	ICOMMAND(meteam, "C", (char *s), toserver(SAY_ACTION|SAY_TEAM, s));
423 
parsecommand(gameent * d,const char * cmd,const char * arg)424 	void parsecommand(gameent *d, const char *cmd, const char *arg)
425 	{
426 		ident *id = idents->access(cmd);
427 		if(id && id->flags&IDF_CLIENT)
428 		{
429 			string val;
430 			val[0] = 0;
431 			switch(id->type)
432 			{
433 #if 0 // these shouldn't get here
434 				case ID_COMMAND:
435 				case ID_COMMAND:
436 				{
437 					string s;
438 					formatstring(s)("%s %s", cmd, arg);
439 					char *ret = executeret(s);
440 					if(ret)
441 					{
442 						conoutft(CON_MESG, "\fg%s: %s", cmd, ret);
443 						delete[] ret;
444 					}
445 					return;
446 				}
447 #endif
448 				case ID_VAR:
449 				{
450 					int ret = atoi(arg);
451 					*id->storage.i = ret;
452 					id->changed();
453 					formatstring(val)(id->flags&IDF_HEX ? (id->maxval==0xFFFFFF ? "0x%.6X" : "0x%X") : "%d", *id->storage.i);
454 					break;
455 				}
456 				case ID_FVAR:
457 				{
458 					float ret = atof(arg);
459 					*id->storage.f = ret;
460 					id->changed();
461 					formatstring(val)("%s", floatstr(*id->storage.f));
462 					break;
463 				}
464 				case ID_SVAR:
465 				{
466 					delete[] *id->storage.s;
467 					*id->storage.s = newstring(arg);
468 					id->changed();
469 					formatstring(val)("%s", *id->storage.s);
470 					break;
471 				}
472 				default: return;
473 			}
474 			if(d || verbose >= 2)
475 				conoutft(d != game::player1 ? CON_INFO : CON_MESG, "\fg%s set %s to %s", d ? game::colorname(d) : "the server", cmd, val);
476 		}
477 		else conoutft(d != game::player1 ? CON_INFO : CON_MESG, "\fr%s sent unknown command: %s", d ? game::colorname(d) : "the server", cmd);
478 	}
479 
sendcmd(int nargs,const char * cmd,const char * arg)480 	bool sendcmd(int nargs, const char *cmd, const char *arg)
481 	{
482 		if(ready())
483 		{
484 			addmsg(SV_COMMAND, "ri2ss", game::player1->clientnum, nargs, cmd, arg);
485 			return true;
486 		}
487 		else
488 		{
489 			defformatstring(scmd)("sv_%s", cmd);
490 			if(server::servcmd(nargs, scmd, arg))
491 			{
492 				parsecommand(NULL, cmd, arg);
493 				return true;
494 			}
495 		}
496 		return false;
497 	}
498 
changemapserv(char * name,int gamemode,int mutators,bool temp)499 	void changemapserv(char *name, int gamemode, int mutators, bool temp)
500 	{
501 		if(editmode) toggleedit();
502 		game::gamemode = gamemode; game::mutators = mutators;
503 		server::modecheck(&game::gamemode, &game::mutators);
504 		game::nextmode = game::gamemode; game::nextmuts = game::mutators;
505 		game::minremain = -1;
506 		game::maptime = 0;
507 		mapvotes.setsize(0);
508 		if(editmode && !allowedittoggle(editmode)) toggleedit();
509 		if(m_demo(gamemode)) return;
510 		needsmap = false;
511 		if(!name || !*name || !load_world(name, temp))
512 		{
513 			emptymap(0, true, NULL);
514 			setnames(name, MAP_BFGZ);
515 			needsmap = true;
516 		}
517 		if(editmode) edittoggled(editmode);
518 		if(m_stf(gamemode)) stf::setupflags();
519         else if(m_ctf(gamemode)) ctf::setupflags();
520 	}
521 
receivefile(uchar * data,int len)522 	void receivefile(uchar *data, int len)
523 	{
524 		ucharbuf p(data, len);
525 		int type = getint(p);
526 		data += p.length();
527 		len -= p.length();
528 		switch(type)
529 		{
530 			case SV_SENDDEMO:
531 			{
532 				defformatstring(fname)("%d.dmo", lastmillis);
533 				stream *demo = openfile(fname, "wb");
534 				if(!demo) return;
535 				conoutft(CON_MESG, "received demo \"%s\"", fname);
536 				demo->write(data, len);
537                 delete demo;
538 				break;
539 			}
540 
541 			case SV_SENDMAPCONFIG:
542 			case SV_SENDMAPSHOT:
543 			case SV_SENDMAPFILE:
544 			{
545 				const char *reqmap = mapname, *reqext = "xxx";
546 				if(type == SV_SENDMAPCONFIG) reqext = "cfg";
547 				else if(type == SV_SENDMAPSHOT) reqext = "png";
548 				else if(type == SV_SENDMAPFILE) reqext = "bgz";
549 				if(!reqmap || !*reqmap) reqmap = "maps/untitled";
550 				defformatstring(reqfile)(strstr(reqmap, "temp/")==reqmap || strstr(reqmap, "temp\\")==reqmap ? "%s" : "temp/%s", reqmap);
551 				defformatstring(reqfext)("%s.%s", reqfile, reqext);
552 				stream *f = openfile(reqfext, "wb");
553 				if(!f)
554 				{
555 					conoutf("\frfailed to open map file: %s", reqfext);
556 					return;
557 				}
558 				gettingmap = true;
559 				f->write(data, len);
560                 delete f;
561 				break;
562 			}
563 		}
564 		return;
565 	}
566 	ICOMMAND(getmap, "", (), addmsg(SV_GETMAP, "r"));
567 
stopdemo()568 	void stopdemo()
569 	{
570 		if(remote) addmsg(SV_STOPDEMO, "r");
571 		else server::stopdemo();
572 	}
573 	ICOMMAND(stopdemo, "", (), stopdemo());
574 
recorddemo(int val)575     void recorddemo(int val)
576 	{
577         addmsg(SV_RECORDDEMO, "ri", val);
578 	}
579 	ICOMMAND(recorddemo, "i", (int *val), recorddemo(*val));
580 
cleardemos(int val)581 	void cleardemos(int val)
582 	{
583         addmsg(SV_CLEARDEMOS, "ri", val);
584 	}
585 	ICOMMAND(cleardemos, "i", (int *val), cleardemos(*val));
586 
getdemo(int i)587     void getdemo(int i)
588 	{
589 		if(i<=0) conoutf("getting demo...");
590 		else conoutf("getting demo %d...", i);
591 		addmsg(SV_GETDEMO, "ri", i);
592 	}
593 	ICOMMAND(getdemo, "i", (int *val), getdemo(*val));
594 
listdemos()595 	void listdemos()
596 	{
597 		conoutf("listing demos...");
598 		addmsg(SV_LISTDEMOS, "r");
599 	}
600 	ICOMMAND(listdemos, "", (), listdemos());
601 
changemap(const char * name)602 	void changemap(const char *name) // request map change, server may ignore
603 	{
604         int nextmode = game::nextmode, nextmuts = game::nextmuts; // in case stopdemo clobbers these
605         if(!remote) stopdemo();
606         if(!connected())
607         {
608         	server::changemap(name, nextmode, nextmuts);
609         	localconnect(true);
610         }
611         else
612         {
613 			string reqfile;
614 			copystring(reqfile, !strncasecmp(name, "temp/", 5) || !strncasecmp(name, "temp\\", 5) ? name+5 : name);
615 			addmsg(SV_MAPVOTE, "rsi2", reqfile, nextmode, nextmuts);
616         }
617 	}
618 	ICOMMAND(map, "s", (char *s), changemap(s));
619 
sendmap()620 	void sendmap()
621 	{
622 		conoutf("sending map...");
623 		const char *reqmap = mapname;
624 		if(!reqmap || !*reqmap) reqmap = "maps/untitled";
625 		bool edit = m_edit(game::gamemode);
626 		defformatstring(reqfile)("%s%s", edit ? "temp/" : "", reqmap);
627 		loopi(3)
628 		{
629 			string reqfext;
630 			switch(i)
631 			{
632 				case 2: formatstring(reqfext)("%s.cfg", reqfile); break;
633 				case 1: formatstring(reqfext)("%s.png", reqfile); break;
634 				case 0: default:
635 					formatstring(reqfext)("%s.bgz", reqfile);
636 					if(edit)
637 					{
638 						save_world(reqfile, edit, true);
639 						setnames(reqmap, MAP_BFGZ);
640 					}
641 					break;
642 			}
643 			stream *f = openfile(reqfext, "rb");
644 			if(f)
645 			{
646 				conoutf("\fgtransmitting file: %s", reqfext);
647 				sendfile(-1, 2, f, "ri", SV_SENDMAPFILE+i);
648                 delete f;
649 			}
650 			else conoutf("\frfailed to open map file: %s", reqfext);
651 		}
652 	}
653 	ICOMMAND(sendmap, "", (), sendmap());
654 
gotoplayer(const char * arg)655 	void gotoplayer(const char *arg)
656 	{
657 		if(game::player1->state!=CS_SPECTATOR && game::player1->state!=CS_EDITING) return;
658 		int i = parseplayer(arg);
659 		if(i>=0 && i!=game::player1->clientnum)
660 		{
661 			gameent *d = game::getclient(i);
662 			if(!d) return;
663 			game::player1->o = d->o;
664 			vec dir;
665 			vecfromyawpitch(game::player1->yaw, game::player1->pitch, 1, 0, dir);
666 			game::player1->o.add(dir.mul(-32));
667             game::player1->resetinterp();
668 		}
669 	}
670 	ICOMMAND(goto, "s", (char *s), gotoplayer(s));
671 
ready()672 	bool ready() { return connected(false) && isready && game::maptime > 0; }
673 	ICOMMAND(ready, "", (), intret(ready()));
674 
state()675 	int state() { return game::player1->state; }
otherclients()676 	int otherclients()
677 	{
678 		int n = 0; // ai don't count
679 		loopv(game::players) if(game::players[i] && game::players[i]->aitype < 0) n++;
680 		return n;
681 	}
682 
editvar(ident * id,bool local)683 	void editvar(ident *id, bool local)
684 	{
685         if(id && id->flags&IDF_WORLD && local && m_edit(game::gamemode))
686         {
687         	switch(id->type)
688         	{
689         		case ID_VAR:
690 					addmsg(SV_EDITVAR, "risi", id->type, id->name, *id->storage.i);
691 					break;
692         		case ID_FVAR:
693 					addmsg(SV_EDITVAR, "risf", id->type, id->name, *id->storage.f);
694 					break;
695         		case ID_SVAR:
696 					addmsg(SV_EDITVAR, "riss", id->type, id->name, *id->storage.s);
697 					break;
698         		case ID_ALIAS:
699 					addmsg(SV_EDITVAR, "riss", id->type, id->name, id->action);
700 					break;
701 				default: break;
702         	}
703         }
704 	}
705 
edittrigger(const selinfo & sel,int op,int arg1,int arg2,int arg3)706 	void edittrigger(const selinfo &sel, int op, int arg1, int arg2, int arg3)
707 	{
708         if(m_edit(game::gamemode)) switch(op)
709 		{
710 			case EDIT_FLIP:
711 			case EDIT_COPY:
712 			case EDIT_PASTE:
713 			case EDIT_DELCUBE:
714 			{
715 				addmsg(SV_EDITF + op, "ri9i4",
716 					sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
717 					sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner);
718 				break;
719 			}
720 			case EDIT_MAT:
721 			case EDIT_ROTATE:
722 			{
723 				addmsg(SV_EDITF + op, "ri9i5",
724 					sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
725 					sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
726 					arg1);
727 				break;
728 			}
729 			case EDIT_FACE:
730 			case EDIT_TEX:
731 			case EDIT_REPLACE:
732 			{
733 				addmsg(SV_EDITF + op, "ri9i6",
734 					sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
735 					sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
736 					arg1, arg2);
737 				break;
738 			}
739             case EDIT_REMIP:
740             {
741                 addmsg(SV_EDITF + op, "r");
742                 break;
743             }
744 		}
745 	}
746 
sendintro()747     void sendintro()
748     {
749 		packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
750         putint(p, SV_CONNECT);
751         sendstring(game::player1->name, p);
752         mkstring(hash);
753         if(connectpass[0])
754         {
755             server::hashpassword(game::player1->clientnum, sessionid, connectpass, hash);
756             memset(connectpass, 0, sizeof(connectpass));
757         }
758         sendstring(hash, p);
759         sendclientpacket(p.finalize(), 1);
760     }
761 
updateposition(gameent * d)762 	void updateposition(gameent *d)
763 	{
764         if((d->state==CS_ALIVE || d->state==CS_EDITING) && (!d->ai || !d->ai->suspended))
765 		{
766 			// send position updates separately so as to not stall out aiming
767 			packetbuf q(100);
768 			putint(q, SV_POS);
769 			putint(q, d->clientnum);
770 			putuint(q, (int)(d->o.x*DMF));			  // quantize coordinates to 1/4th of a cube, between 1 and 3 bytes
771 			putuint(q, (int)(d->o.y*DMF));
772 			putuint(q, (int)((d->o.z - d->height)*DMF));
773 			putuint(q, (int)d->yaw);
774 			putint(q, (int)d->pitch);
775 			putint(q, (int)d->roll);
776 			putint(q, (int)(d->vel.x*DVELF));		  // quantize to itself, almost always 1 byte
777 			putint(q, (int)(d->vel.y*DVELF));
778 			putint(q, (int)(d->vel.z*DVELF));
779             putuint(q, d->physstate | (d->falling.x || d->falling.y ? 0x20 : 0) | (d->falling.z ? 0x10 : 0));
780             if(d->falling.x || d->falling.y)
781             {
782                 putint(q, (int)(d->falling.x*DVELF));      // quantize to itself, almost always 1 byte
783                 putint(q, (int)(d->falling.y*DVELF));
784             }
785             if(d->falling.z) putint(q, (int)(d->falling.z*DVELF));
786 			// pack rest in almost always 1 byte: strafe:2, move:2, crouching: 1, aimyaw/aimpitch: 1
787 			uint flags = (d->strafe&3) | ((d->move&3)<<2) |
788 				((d->action[AC_CROUCH] ? 1 : 0)<<4) | ((FWV(impulsestyle) && (d->turnside || ((d->action[AC_IMPULSE] && (d->move || d->strafe)))) ? 1 : 0)<<6) | ((d->conopen ? 1 : 0)<<7) |
789 					((int)d->aimyaw!=(int)d->yaw || (int)d->aimpitch!=(int)d->pitch ? 0x20 : 0);
790 			putuint(q, flags);
791             if(flags&0x20)
792             {
793                 putuint(q, (int)d->aimyaw);
794                 putint(q, (int)d->aimpitch);
795             }
796 			sendclientpacket(q.finalize(), 0);
797 		}
798 	}
799 
sendmessages(gameent * d)800     void sendmessages(gameent *d)
801     {
802 		packetbuf p(MAXTRANS);
803 		if(sendcrc)
804 		{
805 			p.reliable();
806 			sendcrc = false;
807 			putint(p, SV_MAPCRC);
808 			sendstring(game::clientmap, p);
809 			putint(p, game::clientmap[0] ? getmapcrc() : 0);
810 		}
811 		if(sendinfo && !needsmap)
812 		{
813             p.reliable();
814 			putint(p, SV_GAMEINFO);
815 			putint(p, game::numplayers);
816 			entities::putitems(p);
817 			putint(p, -1);
818 			if(m_stf(game::gamemode)) stf::sendflags(p);
819             else if(m_ctf(game::gamemode)) ctf::sendflags(p);
820 			sendinfo = false;
821 		}
822         if(messages.length())
823         {
824             p.put(messages.getbuf(), messages.length());
825             messages.setsizenodelete(0);
826             if(messagereliable) p.reliable();
827             messagereliable = false;
828         }
829 		if(lastmillis-lastping>250)
830 		{
831 			putint(p, SV_PING);
832 			putint(p, lastmillis);
833 			lastping = lastmillis;
834 		}
835 
836         sendclientpacket(p.finalize(), 1);
837 	}
838 
c2sinfo()839     void c2sinfo() // send update to the server
840     {
841         static int lastupdate = -1000;
842         if(totalmillis-lastupdate < 40) return;    // don't update faster than 25fps
843         lastupdate = totalmillis;
844         updateposition(game::player1);
845         loopv(game::players) if(game::players[i] && game::players[i]->ai) updateposition(game::players[i]);
846         sendmessages(game::player1);
847         flushclient();
848     }
849 
parsestate(gameent * d,ucharbuf & p,bool resume=false)850     void parsestate(gameent *d, ucharbuf &p, bool resume = false)
851     {
852         if(!d) { static gameent dummy; d = &dummy; }
853 		if(d == game::player1 || d->ai) getint(p);
854 		else d->state = getint(p);
855 		d->frags = getint(p);
856         d->health = getint(p);
857         d->cptime = getint(p);
858         if(resume && (d == game::player1 || d->ai))
859         {
860         	d->weapreset(false);
861             getint(p);
862             loopi(WEAP_MAX) getint(p);
863         }
864         else
865         {
866         	d->weapreset(true);
867             d->lastweap = d->weapselect = getint(p);
868             if(m_arena(game::gamemode, game::mutators)) d->arenaweap = d->weapselect;
869             loopi(WEAP_MAX) d->ammo[i] = getint(p);
870         }
871     }
872 
updatepos(gameent * d)873 	void updatepos(gameent *d)
874 	{
875 		// update the position of other clients in the game in our world
876 		// don't care if he's in the scenery or other players,
877 		// just don't overlap with our client
878 
879 		const float r = game::player1->radius+d->radius;
880 		const float dx = game::player1->o.x-d->o.x;
881 		const float dy = game::player1->o.y-d->o.y;
882 		const float dz = game::player1->o.z-d->o.z;
883 		const float rz = game::player1->aboveeye+game::player1->height;
884 		const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz);
885 		if(fx<r && fy<r && fz<rz && d->state!=CS_SPECTATOR && d->state!=CS_WAITING && d->state!=CS_DEAD)
886 		{
887 			if(fx<fy) d->o.y += dy<0 ? r-fy : -(r-fy);  // push aside
888 			else	  d->o.x += dx<0 ? r-fx : -(r-fx);
889 		}
890 		int lagtime = lastmillis-d->lastupdate;
891 		if(lagtime)
892 		{
893             if(d->lastupdate) d->plag = (d->plag*5+lagtime)/6;
894 			d->lastupdate = lastmillis;
895 		}
896 	}
897 
parsepositions(ucharbuf & p)898 	void parsepositions(ucharbuf &p)
899 	{
900 		int type;
901 		while(p.remaining()) switch(type = getint(p))
902 		{
903 			case SV_POS:						// position of another client
904 			{
905 				int lcn = getint(p);
906 				vec o, vel, falling;
907 				float yaw, pitch, roll, aimyaw, aimpitch;
908 				int physstate, f;
909 				o.x = getuint(p)/DMF;
910 				o.y = getuint(p)/DMF;
911 				o.z = getuint(p)/DMF;
912 				aimyaw = yaw = (float)getuint(p);
913 				aimpitch = pitch = (float)getint(p);
914 				roll = (float)getint(p);
915 				vel.x = getint(p)/DVELF;
916 				vel.y = getint(p)/DVELF;
917 				vel.z = getint(p)/DVELF;
918 				physstate = getuint(p);
919                 falling = vec(0, 0, 0);
920                 if(physstate&0x20)
921                 {
922                     falling.x = getint(p)/DVELF;
923                     falling.y = getint(p)/DVELF;
924                 }
925                 if(physstate&0x10) falling.z = getint(p)/DVELF;
926 				f = getuint(p);
927                 if(f&0x20)
928                 {
929                     aimyaw = (float)getuint(p);
930                     aimpitch = (float)getint(p);
931                 }
932 				gameent *d = game::getclient(lcn);
933                 if(!d || d==game::player1 || d->ai) continue;
934                 float oldyaw = d->yaw, oldpitch = d->pitch, oldaimyaw = d->aimyaw, oldaimpitch = d->aimpitch;
935 				d->action[AC_IMPULSE] = FWV(impulsestyle) && f&0x40 ? true : false;
936 				d->conopen = f&0x80 ? true : false;
937 				d->yaw = yaw;
938 				d->pitch = pitch;
939 				d->roll = roll;
940 				d->aimyaw = aimyaw;
941 				d->aimpitch = aimpitch;
942 				d->strafe = (f&3)==3 ? -1 : f&3; f >>= 2;
943 				d->move = (f&3)==3 ? -1 : f&3; f >>= 2;
944 				bool crouch = d->action[AC_CROUCH];
945 				d->action[AC_CROUCH] = f&1 ? true : false;
946 				if(crouch != d->action[AC_CROUCH]) d->actiontime[AC_CROUCH] = lastmillis;
947                 vec oldpos(d->o);
948                 //if(game::allowmove(d))
949                 //{
950                     d->o = o;
951                     d->o.z += d->height;
952                     d->vel = vel;
953                     d->falling = falling;
954                     d->physstate = physstate & 0x0F;
955                     physics::updatephysstate(d);
956                     updatepos(d);
957                 //}
958                 if(d->state==CS_DEAD || d->state==CS_WAITING)
959                 {
960                     d->resetinterp();
961                     d->smoothmillis = 0;
962                 }
963                 else if(physics::smoothmove && d->smoothmillis>=0 && oldpos.dist(d->o) < physics::smoothdist)
964                 {
965                     d->newpos = d->o;
966                     d->newpos.z -= d->height;
967                     d->newyaw = d->yaw;
968                     d->newpitch = d->pitch;
969                     d->newaimyaw = d->aimyaw;
970                     d->newaimpitch = d->aimpitch;
971 
972                     d->o = oldpos;
973                     d->yaw = oldyaw;
974                     d->pitch = oldpitch;
975                     d->aimyaw = oldaimyaw;
976                     d->aimpitch = oldaimpitch;
977 
978                     oldpos.z -= d->height;
979                     (d->deltapos = oldpos).sub(d->newpos);
980 
981                     d->deltayaw = oldyaw - d->newyaw;
982                     if(d->deltayaw > 180) d->deltayaw -= 360;
983                     else if(d->deltayaw < -180) d->deltayaw += 360;
984                     d->deltapitch = oldpitch - d->newpitch;
985 
986                     d->deltaaimyaw = oldaimyaw - d->newaimyaw;
987                     if(d->deltaaimyaw > 180) d->deltaaimyaw -= 360;
988                     else if(d->deltaaimyaw < -180) d->deltaaimyaw += 360;
989                     d->deltaaimpitch = oldaimpitch - d->newaimpitch;
990 
991                     d->smoothmillis = lastmillis;
992                 }
993                 else d->smoothmillis = 0;
994 				break;
995 			}
996 
997 			default:
998 				neterr("type");
999 				return;
1000 		}
1001 	}
1002 
parsemessages(int cn,gameent * d,ucharbuf & p)1003 	void parsemessages(int cn, gameent *d, ucharbuf &p)
1004 	{
1005 		static char text[MAXTRANS];
1006 		int type = -1, prevtype = -1;
1007 		bool mapchanged = false;
1008 
1009 		while(p.remaining())
1010 		{
1011 			prevtype = type;
1012 			type = getint(p);
1013 			if(verbose > 5) conoutf("[client] msg: %d, prev: %d", type, prevtype);
1014 			switch(type)
1015 			{
1016 				case SV_SERVERINIT:					// welcome messsage from the server
1017 				{
1018 					int mycn = getint(p), gver = getint(p);
1019 					if(gver!=GAMEVERSION)
1020 					{
1021 						conoutft(CON_MESG, "\fryou are using a different game version (you: %d, server: %d)", GAMEVERSION, gver);
1022 						disconnect();
1023 						return;
1024 					}
1025                     sessionid = getint(p);
1026 					game::player1->clientnum = mycn;
1027                     if(getint(p)) conoutft(CON_MESG, "\frthe server is password protected");
1028                     else if(verbose) conoutf("\fathe server welcomes us, yay");
1029                     sendintro();
1030 					break;
1031 				}
1032                 case SV_WELCOME: isready = true; break;
1033 
1034 				case SV_CLIENT:
1035 				{
1036 					int lcn = getint(p), len = getuint(p);
1037 					ucharbuf q = p.subbuf(len);
1038 					gameent *t = game::getclient(lcn);
1039 					parsemessages(lcn, t, q);
1040 					break;
1041 				}
1042 
1043 				case SV_PHYS: // simple phys events
1044 				{
1045 					int lcn = getint(p), st = getint(p);
1046 					gameent *t = game::getclient(lcn);
1047 					if(t && t != game::player1 && !t->ai) switch(st)
1048 					{
1049 						case SPHY_JUMP:
1050 						{
1051 							playsound(S_JUMP, t->o, t); regularshape(PART_SMOKE, int(t->radius), 0x111111, 21, 20, 100, t->feetpos(), 1, 1, -10, 0, 10.f);
1052 							t->actiontime[AC_JUMP] = lastmillis;
1053 							break;
1054 						}
1055 						case SPHY_IMPULSE:
1056 						{
1057 							playsound(S_IMPULSE, t->o, t); game::impulseeffect(t, true);
1058 							break;
1059 						}
1060 						case SPHY_POWER:
1061 						{
1062 							t->setweapstate(t->weapselect, WEAP_S_POWER, 0, lastmillis);
1063 							break;
1064 						}
1065 						case SPHY_EXTINGUISH:
1066 						{
1067 							if(issound(t->fschan)) removesound(t->fschan);
1068 							t->fschan = -1; t->lastfire = 0;
1069 							playsound(S_EXTINGUISH, t->o, t, 0, 128);
1070 							break;
1071 						}
1072 						default: break;
1073 					}
1074 					break;
1075 				}
1076 
1077 				case SV_ANNOUNCE:
1078 				{
1079 					int snd = getint(p), targ = getint(p);
1080 					getstring(text, p);
1081 					if(targ >= 0 && text[0]) game::announce(snd, targ, NULL, "%s", text);
1082 					else game::announce(snd, -1, NULL, "");
1083 					break;
1084 				}
1085 
1086 				case SV_TEXT:
1087 				{
1088 					int tcn = getint(p);
1089 					gameent *t = game::getclient(tcn);
1090 					int flags = getint(p);
1091 					getstring(text, p);
1092 					if(!t) break;
1093 					saytext(t, flags, text);
1094 					break;
1095 				}
1096 
1097 				case SV_COMMAND:
1098 				{
1099 					int lcn = getint(p);
1100 					gameent *f = game::getclient(lcn);
1101 					string cmd;
1102 					getstring(cmd, p);
1103 					getstring(text, p);
1104 					parsecommand(f ? f : NULL, cmd, text);
1105 					break;
1106 				}
1107 
1108 				case SV_EXECLINK:
1109 				{
1110 					int tcn = getint(p), index = getint(p);
1111 					gameent *t = game::getclient(tcn);
1112 					if(!t || !d || (t->clientnum != d->clientnum && t->ownernum != d->clientnum) || t == game::player1 || t->ai) break;
1113 					entities::execlink(t, index, false);
1114 					break;
1115 				}
1116 
1117 				case SV_MAPCHANGE:
1118 				{
1119 					int hasmap = getint(p);
1120 					if(hasmap) getstring(text, p);
1121 					int getit = getint(p), mode = getint(p), muts = getint(p);
1122 					changemapserv(hasmap && getit != 1 ? text : NULL, mode, muts, getit == 2);
1123 					mapchanged = true;
1124 					if(needsmap && multiplayer(false))
1125 					{
1126 						switch(getit)
1127 						{
1128 							case 0:
1129 							{
1130 								conoutf("\fcserver requested map change to %s, and we need it, so asking for it", hasmap ? text : "<temp>");
1131 								addmsg(SV_GETMAP, "r");
1132 								break;
1133 							}
1134 							case 2:
1135 							{
1136 								conoutf("\fcseem to have failed to get map to %s, try /getmap", hasmap ? text : "<temp>");
1137 								needsmap = false; // we failed sir
1138 								break;
1139 							}
1140 						}
1141 					}
1142 					else needsmap = false; // assume empty map
1143 					break;
1144 				}
1145 
1146 				case SV_GAMEINFO:
1147 				{
1148 					int n;
1149 					while((n = getint(p)) != -1) entities::setspawn(n, getint(p));
1150 					sendinfo = false;
1151 					break;
1152 				}
1153 
1154 				case SV_NEWGAME: // server requests next game
1155 				{
1156 					hud::sb.showscores(false);
1157 					if(!menuactive()) showgui("game");
1158 					break;
1159 				}
1160 
1161 				case SV_SWITCHNAME:
1162 					getstring(text, p);
1163 					if(!d) break;
1164 					filtertext(text, text, true, MAXNAMELEN);
1165 					if(!text[0]) copystring(text, "unnamed");
1166 					if(strcmp(d->name, text))
1167 					{
1168 						string oldname, newname;
1169 						copystring(oldname, game::colorname(d, NULL, "", false));
1170 						copystring(newname, game::colorname(d, text));
1171 						if(game::showplayerinfo)
1172 							conoutft(game::showplayerinfo > 1 ? int(CON_EVENT) : int(CON_MESG), "\fm%s is now known as %s", oldname, newname);
1173 					}
1174 					copystring(d->name, text, MAXNAMELEN+1);
1175 					break;
1176 
1177 				case SV_CLIENTINIT: // another client either connected or changed name/team
1178 				{
1179 					int cn = getint(p);
1180 					gameent *d = game::newclient(cn);
1181 					if(!d)
1182 					{
1183 						getstring(text, p);
1184 						getint(p);
1185 						break;
1186 					}
1187 					getstring(text, p);
1188 					filtertext(text, text, true, MAXNAMELEN);
1189 					if(!text[0]) copystring(text, "unnamed");
1190 					if(d->name[0])		  // already connected
1191 					{
1192 						if(strcmp(d->name, text))
1193 						{
1194 							string oldname, newname;
1195 							copystring(oldname, game::colorname(d, NULL, "", false));
1196 							copystring(newname, game::colorname(d, text));
1197 							if(game::showplayerinfo)
1198 								conoutft(game::showplayerinfo > 1 ? int(CON_EVENT) : int(CON_MESG), "\fm%s is now known as %s", oldname, newname);
1199 						}
1200 					}
1201 					else					// new client
1202 					{
1203 						if(game::showplayerinfo)
1204 							conoutft(game::showplayerinfo > 1 ? int(CON_EVENT) : int(CON_MESG), "\fg%s has joined the game", game::colorname(d, text, "", false));
1205 						loopv(game::players)	// clear copies since new player doesn't have them
1206 							if(game::players[i]) freeeditinfo(game::players[i]->edit);
1207 						freeeditinfo(localedit);
1208 					}
1209 					copystring(d->name, text, MAXNAMELEN+1);
1210 					int team = clamp(getint(p), int(TEAM_NEUTRAL), int(TEAM_ENEMY));
1211 					if(d == game::player1 && d->team != team) hud::lastteam = 0;
1212 					d->team = team;
1213 					break;
1214 				}
1215 
1216 				case SV_DISCONNECT:
1217 					game::clientdisconnected(getint(p));
1218 					break;
1219 
1220 				case SV_SPAWN:
1221 				{
1222 					int lcn = getint(p);
1223 					gameent *f = game::newclient(lcn);
1224 					if(f && f != game::player1 && !f->ai)
1225 					{
1226 						f->respawn(lastmillis, m_health(game::gamemode, game::mutators));
1227 						parsestate(f, p);
1228 						playsound(S_RESPAWN, f->o, f);
1229 						game::spawneffect(PART_FIREBALL, vec(f->o).sub(vec(0, 0, f->height/2.f)), teamtype[f->team].colour, int(f->radius*2));
1230 					}
1231 					else parsestate(NULL, p);
1232 					break;
1233 				}
1234 
1235 				case SV_ARENAWEAP:
1236 				{
1237 					hud::sb.showscores(false);
1238 					if(!menuactive()) showgui("arena");
1239 					break;
1240 				}
1241 
1242 				case SV_SPAWNSTATE:
1243 				{
1244 					int lcn = getint(p), ent = getint(p);
1245 					gameent *f = game::newclient(lcn);
1246                     if(!f)
1247                     {
1248                         parsestate(NULL, p);
1249                         break;
1250                     }
1251 					if(f == game::player1 && editmode) toggleedit();
1252 					f->respawn(lastmillis, m_health(game::gamemode, game::mutators));
1253 					parsestate(f, p);
1254 					f->state = CS_ALIVE;
1255 					if(f == game::player1 || f->ai)
1256 					{
1257 						addmsg(SV_SPAWN, "ri", f->clientnum);
1258 						entities::spawnplayer(f, ent, true);
1259 						playsound(S_RESPAWN, f->o, f);
1260 						game::spawneffect(PART_FIREBALL, vec(f->o).sub(vec(0, 0, f->height/2.f)), teamtype[f->team].colour, int(f->radius*2));
1261 					}
1262 					ai::spawned(f, ent);
1263 					if(f == game::player1) game::resetcamera();
1264 					break;
1265 				}
1266 
1267 				case SV_SHOTFX:
1268 				{
1269 					int scn = getint(p), weap = getint(p), flags = getint(p), power = getint(p);
1270 					vec from;
1271 					loopk(3) from[k] = getint(p)/DMF;
1272 					int ls = getint(p);
1273 					vector<vec> locs;
1274 					loopj(ls)
1275 					{
1276 						vec &to = locs.add();
1277 						loopk(3) to[k] = getint(p)/DMF;
1278 					}
1279 					gameent *s = game::getclient(scn);
1280 					if(!s || !isweap(weap) || s == game::player1 || s->ai) break;
1281 					s->setweapstate(weap, WEAP_S_SHOOT, weaptype[weap].adelay[flags&HIT_ALT ? 1 : 0], lastmillis);
1282 					s->weapshot[weap] = weaptype[weap].sub[flags&HIT_ALT ? 1 : 0];
1283 					s->totalshots += int(weaptype[weap].damage[flags&HIT_ALT ? 1 : 0]*damagescale)*weaptype[weap].rays[flags&HIT_ALT ? 1 : 0];
1284 					projs::shootv(weap, flags, power, from, locs, s, false);
1285 					break;
1286 				}
1287 
1288 				case SV_DAMAGE:
1289 				{
1290 					int tcn = getint(p),
1291 						acn = getint(p),
1292 						weap = getint(p),
1293 						flags = getint(p),
1294 						damage = getint(p),
1295 						health = getint(p);
1296 					vec dir;
1297 					loopk(3) dir[k] = getint(p)/DNF;
1298 					dir.normalize();
1299 					gameent *target = game::getclient(tcn), *actor = game::getclient(acn);
1300 					if(!target || !actor) break;
1301 					game::damaged(weap, flags, damage, health, target, actor, lastmillis, dir);
1302 					break;
1303 				}
1304 
1305 				case SV_RELOAD:
1306 				{
1307 					int trg = getint(p), weap = getint(p), amt = getint(p), ammo = getint(p);
1308 					gameent *target = game::getclient(trg);
1309 					if(!target || !isweap(weap)) break;
1310 					weapons::weapreload(target, weap, amt, ammo, false);
1311 					break;
1312 				}
1313 
1314 				case SV_REGEN:
1315 				{
1316 					int trg = getint(p), heal = getint(p), amt = getint(p);
1317 					gameent *f = game::getclient(trg);
1318 					if(!f) break;
1319 					if(!amt)
1320 					{
1321 						f->impulse[IM_METER] = 0;
1322 						if(issound(f->fschan)) removesound(f->fschan);
1323 						f->fschan = -1; f->lastfire = 0;
1324 					}
1325 					else if(amt > 0 && (!f->lastregen || lastmillis-f->lastregen >= 500)) playsound(S_REGEN, f->o, f);
1326 					f->health = heal; f->lastregen = lastmillis;
1327 					break;
1328 				}
1329 
1330 				case SV_DIED:
1331 				{
1332 					int vcn = getint(p), acn = getint(p), frags = getint(p), style = getint(p), weap = getint(p), flags = getint(p), damage = getint(p);
1333 					gameent *victim = game::getclient(vcn), *actor = game::getclient(acn);
1334 					if(!actor || !victim) break;
1335 					actor->frags = frags;
1336 					game::killed(weap, flags, damage, victim, actor, style);
1337 					victim->lastdeath = lastmillis;
1338 					victim->weapreset(true);
1339 					break;
1340 				}
1341 
1342 				case SV_POINTS:
1343 				{
1344 					int acn = getint(p), add = getint(p), points = getint(p);
1345 					gameent *actor = game::getclient(acn);
1346 					if(!actor) break;
1347 					actor->lastpoints = add;
1348 					actor->points = points;
1349 					break;
1350 				}
1351 
1352 				case SV_DROP:
1353 				{
1354 					int trg = getint(p), weap = getint(p), ds = getint(p);
1355 					gameent *target = game::getclient(trg);
1356 					bool local = target && (target == game::player1 || target->ai);
1357 					if(ds) loopj(ds)
1358 					{
1359 						int gs = getint(p), drop = getint(p);
1360 						if(target) projs::drop(target, gs, drop, local);
1361 					}
1362 					if(isweap(weap) && target)
1363 					{
1364 						target->weapswitch(weap, lastmillis);
1365 						if(issound(target->wschan)) removesound(target->wschan);
1366 						playsound(S_SWITCH, target->o, target, 0, -1, -1, -1, &target->wschan);
1367 					}
1368 					break;
1369 				}
1370 
1371 				case SV_WEAPSELECT:
1372 				{
1373 					int trg = getint(p), weap = getint(p);
1374 					gameent *target = game::getclient(trg);
1375 					if(!target || !isweap(weap)) break;
1376 					weapons::weapselect(target, weap, false);
1377 					break;
1378 				}
1379 
1380 				case SV_RESUME:
1381 				{
1382 					for(;;)
1383 					{
1384 						int lcn = getint(p);
1385 						if(p.overread() || lcn < 0) break;
1386 						gameent *f = game::newclient(lcn);
1387 						if(f && f!=game::player1 && !f->ai) f->respawn(0, m_health(game::gamemode, game::mutators));
1388 						parsestate(f, p, true);
1389 					}
1390 					break;
1391 				}
1392 
1393 				case SV_ITEMSPAWN:
1394 				{
1395 					int ent = getint(p);
1396 					if(!entities::ents.inrange(ent)) break;
1397 					entities::setspawn(ent, 1);
1398 					playsound(S_ITEMSPAWN, entities::ents[ent]->o);
1399 					int sweap = m_weapon(game::gamemode, game::mutators), attr = entities::ents[ent]->type == WEAPON ? w_attr(game::gamemode, entities::ents[ent]->attrs[0], sweap) : entities::ents[ent]->attrs[0],
1400 						colour = entities::ents[ent]->type == WEAPON ? weaptype[attr].colour : 0xFFFFFF;
1401 					if(entities::showentdescs)
1402 					{
1403 						vec pos = vec(entities::ents[ent]->o).add(vec(0, 0, 4));
1404 						const char *texname = entities::showentdescs >= 2 ? hud::itemtex(entities::ents[ent]->type, attr) : NULL;
1405 						if(texname && *texname) part_icon(pos, textureload(texname, 3), 2, 1, -10, 0, game::aboveheadfade, colour);
1406 						else
1407 						{
1408 							const char *item = entities::entinfo(entities::ents[ent]->type, entities::ents[ent]->attrs, false);
1409 							if(item && *item)
1410 							{
1411 								defformatstring(ds)("<emphasis>%s (%d)", item, entities::ents[ent]->type);
1412 								part_textcopy(pos, ds, PART_TEXT, game::aboveheadfade, colour, 2, 1, -10);
1413 							}
1414 						}
1415 					}
1416 					game::spawneffect(PART_FIREBALL, entities::ents[ent]->o, entities::ents[ent]->type == WEAPON ? colour : 0x6666FF, enttype[entities::ents[ent]->type].radius, 5);
1417 					break;
1418 				}
1419 
1420 				case SV_TRIGGER:
1421 				{
1422 					int ent = getint(p), st = getint(p);
1423 					entities::setspawn(ent, st);
1424 					break;
1425 				}
1426 
1427 				case SV_ITEMACC:
1428 				{ // uses a specific drop so the client knows what to replace
1429 					int lcn = getint(p), ent = getint(p), spawn = getint(p), weap = getint(p), drop = getint(p);
1430 					gameent *target = game::getclient(lcn);
1431 					if(!target) break;
1432 					if(entities::ents.inrange(ent) && enttype[entities::ents[ent]->type].usetype == EU_ITEM)
1433 						entities::useeffects(target, ent, spawn, weap, drop);
1434 					break;
1435 				}
1436 
1437 				case SV_EDITVAR:
1438 				{
1439 					int t = getint(p);
1440 					bool commit = true;
1441 					getstring(text, p);
1442 					ident *id = idents->access(text);
1443 					if(!d || !id || !(id->flags&IDF_WORLD) || id->type != t) commit = false;
1444 					switch(t)
1445 					{
1446 						case ID_VAR:
1447 						{
1448 							int val = getint(p);
1449 							if(commit)
1450 							{
1451 								if(val > id->maxval) val = id->maxval;
1452 								else if(val < id->minval) val = id->minval;
1453 								setvar(text, val, true);
1454                                 defformatstring(str)(id->flags&IDF_HEX ? (id->maxval==0xFFFFFF ? "0x%.6X" : "0x%X") : "%d", *id->storage.i);
1455 								conoutft(CON_MESG, "\fg%s set worldvar %s to %s", game::colorname(d), id->name, str);
1456 							}
1457 							break;
1458 						}
1459 						case ID_FVAR:
1460 						{
1461 							float val = getfloat(p);
1462 							if(commit)
1463 							{
1464 								if(val > id->maxvalf) val = id->maxvalf;
1465 								else if(val < id->minvalf) val = id->minvalf;
1466 								setfvar(text, val, true);
1467 								conoutft(CON_MESG, "\fg%s set worldvar %s to %s", game::colorname(d), id->name, floatstr(*id->storage.f));
1468 							}
1469 							break;
1470 						}
1471 						case ID_SVAR:
1472 						{
1473 							string val;
1474 							getstring(val, p);
1475 							if(commit)
1476 							{
1477 								setsvar(text, val, true);
1478 								conoutft(CON_MESG, "\fg%s set worldvar %s to %s", game::colorname(d), id->name, *id->storage.s);
1479 							}
1480 							break;
1481 						}
1482 						case ID_ALIAS:
1483 						{
1484 							string val;
1485 							getstring(val, p);
1486 							if(commit || !id) // set aliases anyway
1487 							{
1488 								worldalias(text, val);
1489 								conoutft(CON_MESG, "\fg%s set worldalias %s to %s", game::colorname(d), text, val);
1490 							}
1491 							break;
1492 						}
1493 						default: break;
1494 					}
1495 					break;
1496 				}
1497 
1498 				case SV_EDITF:			  // coop editing messages
1499 				case SV_EDITT:
1500 				case SV_EDITM:
1501 				case SV_FLIP:
1502 				case SV_COPY:
1503 				case SV_PASTE:
1504 				case SV_ROTATE:
1505 				case SV_REPLACE:
1506 				case SV_DELCUBE:
1507 				{
1508 					if(!d) return;
1509 					selinfo s;
1510 					s.o.x = getint(p); s.o.y = getint(p); s.o.z = getint(p);
1511 					s.s.x = getint(p); s.s.y = getint(p); s.s.z = getint(p);
1512 					s.grid = getint(p); s.orient = getint(p);
1513 					s.cx = getint(p); s.cxs = getint(p); s.cy = getint(p), s.cys = getint(p);
1514 					s.corner = getint(p);
1515 					int dir, mode, tex, newtex, mat, allfaces;
1516 					ivec moveo;
1517 					switch(type)
1518 					{
1519 						case SV_EDITF: dir = getint(p); mode = getint(p); mpeditface(dir, mode, s, false); break;
1520 						case SV_EDITT: tex = getint(p); allfaces = getint(p); mpedittex(tex, allfaces, s, false); break;
1521 						case SV_EDITM: mat = getint(p); mpeditmat(mat, s, false); break;
1522 						case SV_FLIP: mpflip(s, false); break;
1523 						case SV_COPY: if(d) mpcopy(d->edit, s, false); break;
1524 						case SV_PASTE: if(d) mppaste(d->edit, s, false); break;
1525 						case SV_ROTATE: dir = getint(p); mprotate(dir, s, false); break;
1526 						case SV_REPLACE: tex = getint(p); newtex = getint(p); mpreplacetex(tex, newtex, s, false); break;
1527 						case SV_DELCUBE: mpdelcube(s, false); break;
1528 					}
1529 					break;
1530 				}
1531 				case SV_REMIP:
1532 				{
1533 					if(!d) return;
1534 					conoutft(CON_MESG, "%s remipped", game::colorname(d));
1535 					mpremip(false);
1536 					break;
1537 				}
1538 				case SV_EDITENT:			// coop edit of ent
1539 				{
1540 					if(!d) return;
1541 					int i = getint(p);
1542 					float x = getint(p)/DMF, y = getint(p)/DMF, z = getint(p)/DMF;
1543 					int type = getint(p), numattrs = getint(p);
1544 					static vector<int> attrs; attrs.setsizenodelete(0);
1545 					loopk(numattrs) attrs.add(getint(p));
1546 					mpeditent(i, vec(x, y, z), type, attrs, false);
1547 					entities::setspawn(i, 0);
1548 					entities::clearentcache();
1549 					break;
1550 				}
1551 
1552 				case SV_EDITLINK:
1553 				{
1554 					if(!d) return;
1555 					int b = getint(p), index = getint(p), node = getint(p);
1556 					entities::linkents(index, node, b!=0, false, false);
1557 				}
1558 
1559 				case SV_PONG:
1560 					addmsg(SV_CLIENTPING, "i", game::player1->ping = (game::player1->ping*5+lastmillis-getint(p))/6);
1561 					break;
1562 
1563 				case SV_CLIENTPING:
1564 					if(!d) return;
1565 					d->ping = getint(p);
1566 					break;
1567 
1568 				case SV_TIMEUP:
1569 					game::timeupdate(getint(p));
1570 					break;
1571 
1572 				case SV_SERVMSG:
1573 				{
1574 					int lev = getint(p);
1575 					getstring(text, p);
1576 					conoutft(lev >= 0 && lev < CON_MAX ? lev : CON_INFO, "%s", text);
1577 					break;
1578 				}
1579 
1580 				case SV_SENDDEMOLIST:
1581 				{
1582 					int demos = getint(p);
1583 					if(!demos) conoutf("\frno demos available");
1584 					else loopi(demos)
1585 					{
1586 						getstring(text, p);
1587 						conoutf("\fa%d. %s", i+1, text);
1588 					}
1589 					break;
1590 				}
1591 
1592 				case SV_DEMOPLAYBACK:
1593 				{
1594 					int on = getint(p);
1595 					if(on) game::player1->state = CS_SPECTATOR;
1596 					else
1597                     {
1598                         loopv(game::players) if(game::players[i]) game::clientdisconnected(i);
1599                     }
1600 					demoplayback = on!=0;
1601                     game::player1->clientnum = getint(p);
1602 					break;
1603 				}
1604 
1605 				case SV_CURRENTMASTER:
1606 				{
1607 					int mn = getint(p), priv = getint(p);
1608 					if(mn >= 0)
1609 					{
1610 						gameent *m = game::getclient(mn);
1611 						if(m) m->privilege = priv;
1612 					}
1613 					break;
1614 				}
1615 
1616 				case SV_EDITMODE:
1617 				{
1618 					int val = getint(p);
1619 					if(!d) break;
1620 					if(val) d->state = CS_EDITING;
1621 					else
1622 					{
1623 						d->state = CS_ALIVE;
1624 						d->editspawn(lastmillis, m_weapon(game::gamemode, game::mutators), m_health(game::gamemode, game::mutators), m_arena(game::gamemode, game::mutators), spawngrenades >= (m_insta(game::gamemode, game::mutators) ? 2 : 1));
1625 					}
1626 					d->resetinterp();
1627 					projs::remove(d);
1628 					break;
1629 				}
1630 
1631 				case SV_SPECTATOR:
1632 				{
1633 					int sn = getint(p), val = getint(p);
1634 					gameent *s = game::newclient(sn);
1635 					if(!s) break;
1636 					if(val)
1637 					{
1638 						if(s == game::player1 && editmode) toggleedit();
1639 						s->state = CS_SPECTATOR;
1640 						s->checkpoint = -1;
1641 						s->cpmillis = 0;
1642 					}
1643 					else if(s->state == CS_SPECTATOR)
1644 					{
1645 						s->state = CS_WAITING;
1646 						s->checkpoint = -1;
1647 						s->cpmillis = 0;
1648 						if(s != game::player1 && !s->ai) s->resetinterp();
1649 					}
1650 					break;
1651 				}
1652 
1653 				case SV_WAITING:
1654 				{
1655 					int sn = getint(p);
1656 					gameent *s = game::newclient(sn);
1657 					if(!s) break;
1658 					if(s == game::player1)
1659 					{
1660 						if(editmode) toggleedit();
1661 						hud::sb.showscores(false);
1662 					}
1663 					else if(!s->ai) s->resetinterp();
1664 					if(s->state == CS_ALIVE) s->lastdeath = lastmillis; // so spawndelay shows properly
1665 					s->state = CS_WAITING;
1666 					s->weapreset(true);
1667 					break;
1668 				}
1669 
1670 				case SV_SETTEAM:
1671 				{
1672 					int wn = getint(p), tn = getint(p);
1673 					gameent *w = game::getclient(wn);
1674 					if(!w) return;
1675 					if(w == game::player1 && w->team != tn) hud::lastteam = 0;
1676 					w->team = tn;
1677 					break;
1678 				}
1679 
1680 				case SV_FLAGINFO:
1681 				{
1682 					int flag = getint(p), converted = getint(p),
1683 							owner = getint(p), enemy = getint(p);
1684 					if(m_stf(game::gamemode)) stf::updateflag(flag, owner, enemy, converted);
1685 					break;
1686 				}
1687 
1688 				case SV_FLAGS:
1689 				{
1690 					int numflags = getint(p);
1691 					loopi(numflags)
1692 					{
1693 						int converted = getint(p), owner = getint(p), enemy = getint(p);
1694 						stf::st.initflag(i, owner, enemy, converted);
1695 					}
1696 					break;
1697 				}
1698 
1699 				case SV_MAPVOTE:
1700 				{
1701 					int vn = getint(p);
1702 					gameent *v = game::getclient(vn);
1703 					getstring(text, p);
1704 					filtertext(text, text);
1705 					int reqmode = getint(p), reqmuts = getint(p);
1706 					if(!v) break;
1707 					vote(v, text, reqmode, reqmuts);
1708 					break;
1709 				}
1710 
1711 				case SV_CHECKPOINT:
1712 				{
1713 					int tn = getint(p), laptime = getint(p), besttime = getint(p);
1714 					gameent *t = game::getclient(tn);
1715 					if(!t) break;
1716 					t->cplast = laptime;
1717 					t->cptime = besttime;
1718 					t->cpmillis = t->impulse[IM_METER] = 0;
1719 					if(showlaptimes > (t != game::player1 ? (t->aitype >= 0 ? 2 : 1) : 0))
1720 					{
1721 						defformatstring(best)("%s", hud::sb.timetostr(besttime));
1722 						conoutft(t != game::player1 ? CON_INFO : CON_SELF, "%s lap time: \fs\fg%s\fS (best: \fs\fy%s\fS)", game::colorname(t), hud::sb.timetostr(laptime), best);
1723 					}
1724 				}
1725 
1726 				case SV_SCORE:
1727 				{
1728 					int team = getint(p), total = getint(p);
1729 					if(m_stf(game::gamemode)) stf::setscore(team, total);
1730 					else if(m_ctf(game::gamemode)) ctf::setscore(team, total);
1731 					break;
1732 				}
1733 
1734 				case SV_INITFLAGS:
1735 				{
1736 					ctf::parseflags(p, m_ctf(game::gamemode));
1737 					break;
1738 				}
1739 
1740 				case SV_DROPFLAG:
1741 				{
1742 					int ocn = getint(p), flag = getint(p);
1743 					vec droploc;
1744 					loopk(3) droploc[k] = getint(p)/DMF;
1745 					gameent *o = game::newclient(ocn);
1746 					if(o && m_ctf(game::gamemode)) ctf::dropflag(o, flag, droploc);
1747 					break;
1748 				}
1749 
1750 				case SV_SCOREFLAG:
1751 				{
1752 					int ocn = getint(p), relayflag = getint(p), goalflag = getint(p), score = getint(p);
1753 					gameent *o = game::newclient(ocn);
1754 					if(o && m_ctf(game::gamemode)) ctf::scoreflag(o, relayflag, goalflag, score);
1755 					break;
1756 				}
1757 
1758 				case SV_RETURNFLAG:
1759 				{
1760 					int ocn = getint(p), flag = getint(p);
1761 					gameent *o = game::newclient(ocn);
1762 					if(o && m_ctf(game::gamemode)) ctf::returnflag(o, flag);
1763 					break;
1764 				}
1765 
1766 				case SV_TAKEFLAG:
1767 				{
1768 					int ocn = getint(p), flag = getint(p);
1769 					gameent *o = game::newclient(ocn);
1770 					if(o && m_ctf(game::gamemode)) ctf::takeflag(o, flag);
1771 					break;
1772 				}
1773 
1774 				case SV_RESETFLAG:
1775 				{
1776 					int flag = getint(p);
1777 					if(m_ctf(game::gamemode)) ctf::resetflag(flag);
1778 					break;
1779 				}
1780 
1781 				case SV_GETMAP:
1782 				{
1783 					conoutf("\fcserver has requested we send the map..");
1784 					if(!needsmap && !gettingmap) sendmap();
1785 					else
1786 					{
1787 						if(!gettingmap) conoutf("\frwe don't have the map though, so asking for it instead");
1788 						else conoutf("\frbut we're in the process of getting it");
1789 							addmsg(SV_GETMAP, "r");
1790 					}
1791 					break;
1792 				}
1793 
1794 				case SV_SENDMAP:
1795 				{
1796 					conoutf("\fcmap data has been uploaded");
1797 					if(needsmap && !gettingmap)
1798 					{
1799 						conoutf("\frwe want the map too, so asking for it");
1800 						addmsg(SV_GETMAP, "r");
1801 					}
1802 					break;
1803 				}
1804 
1805 				case SV_NEWMAP:
1806 				{
1807 					int size = getint(p);
1808 					if(size>=0) emptymap(size, true);
1809 					else enlargemap(true);
1810 					mapvotes.setsize(0);
1811 					needsmap = false;
1812 					if(d && d!=game::player1)
1813 					{
1814 						int newsize = 0;
1815 						while(1<<newsize < getworldsize()) newsize++;
1816 						conoutft(CON_MESG, size>=0 ? "%s started a new map of size %d" : "%s enlarged the map to size %d", game::colorname(d), newsize);
1817 					}
1818 					break;
1819 				}
1820 
1821 				case SV_INITAI:
1822 				{
1823 					int bn = getint(p), on = getint(p), at = getint(p), et = getint(p), sk = clamp(getint(p), 1, 101);
1824 					getstring(text, p);
1825 					int tm = getint(p);
1826 					gameent *b = game::newclient(bn);
1827 					if(!b) break;
1828 					ai::init(b, at, et, on, sk, bn, text, tm);
1829 					break;
1830 				}
1831 
1832 				case SV_AUTHCHAL:
1833 				{
1834 					uint id = (uint)getint(p);
1835 					getstring(text, p);
1836 					conoutft(CON_MESG, "server is challenging authentication details..");
1837 					if(lastauth && lastmillis-lastauth < 60*1000 && authname[0])
1838 					{
1839 						vector<char> buf;
1840                         answerchallenge(authkey, text, buf);
1841 						addmsg(SV_AUTHANS, "ris", id, buf.getbuf());
1842 					}
1843 					break;
1844 				}
1845 
1846 				default:
1847 				{
1848 					neterr("type");
1849 					return;
1850 				}
1851 			}
1852 		}
1853 	}
1854 
serverstat(serverinfo * a)1855 	int serverstat(serverinfo *a)
1856 	{
1857 		if(a->attr.length() > 4 && a->numplayers >= a->attr[4])
1858 		{
1859 			return SSTAT_FULL;
1860 		}
1861 		else if(a->attr.length() > 5) switch(a->attr[5])
1862 		{
1863 			case MM_LOCKED:
1864 			{
1865 				return SSTAT_LOCKED;
1866 			}
1867 			case MM_PRIVATE:
1868             case MM_PASSWORD:
1869 			{
1870 				return SSTAT_PRIVATE;
1871 			}
1872 			default:
1873 			{
1874 				return SSTAT_OPEN;
1875 			}
1876 		}
1877 		return SSTAT_UNKNOWN;
1878 	}
1879 
resetserversort()1880 	void resetserversort()
1881 	{
1882 		defformatstring(u)("serversort [%d %d %d]", SINFO_STATUS, SINFO_PLAYERS, SINFO_PING);
1883 		execute(u);
1884 	}
1885 	ICOMMAND(serversortreset, "", (), resetserversort());
1886 
servercompare(serverinfo * a,serverinfo * b)1887 	int servercompare(serverinfo *a, serverinfo *b)
1888 	{
1889 		if(!serversort || !*serversort) resetserversort();
1890 		int ac = a->address.host == ENET_HOST_ANY || a->ping >= 999 || a->attr.empty() || a->attr[0] != GAMEVERSION ? -1 : 0,
1891 			bc = b->address.host == ENET_HOST_ANY || b->ping >= 999 || b->attr.empty() || b->attr[0] != GAMEVERSION ? -1 : 0;
1892 		if(!ac)
1893 		{
1894 			if(!strcmp(a->sdesc, servermaster)) ac = 3;
1895 			else if(!strcmp(a->name, "localhost")) ac = 2;
1896 			else ac = 1;
1897 		}
1898 
1899 		if(!bc)
1900 		{
1901 			if(!strcmp(b->sdesc, servermaster)) bc = 3;
1902 			else if(!strcmp(b->name, "localhost")) bc = 2;
1903 			else bc = 1;
1904 		}
1905 		if(ac > bc) return -1;
1906 		if(ac < bc) return 1;
1907 
1908 		#define retcp(c) if(c) { return c; }
1909 		#define retsw(c,d,e) \
1910 			if(c != d) \
1911 			{ \
1912 				if(e) { return c > d ? 1 : -1; } \
1913 				else { return c < d ? 1 : -1; } \
1914 			}
1915 
1916 		int len = execute("listlen $serversort");
1917 
1918 		loopi(len)
1919 		{
1920 			defformatstring(s)("at $serversort %d", i);
1921 
1922 			int style = execute(s);
1923 			serverinfo *aa = a, *ab = b;
1924 
1925 			if(style < 0)
1926 			{
1927 				style = 0-style;
1928 				aa = b;
1929 				ab = a;
1930 			}
1931 
1932 			switch (style)
1933 			{
1934 				case SINFO_STATUS:
1935 				{
1936 					retsw(serverstat(aa), serverstat(ab), true);
1937 					break;
1938 				}
1939 				case SINFO_DESC:
1940 				{
1941 					retcp(strcmp(aa->sdesc, ab->sdesc));
1942 					break;
1943 				}
1944 				case SINFO_PING:
1945 				{
1946 					retsw(aa->ping, ab->ping, true);
1947 					break;
1948 				}
1949 				case SINFO_PLAYERS:
1950 				{
1951 					retsw(aa->numplayers, ab->numplayers, false);
1952 					break;
1953 				}
1954 				case SINFO_MAXCLIENTS:
1955 				{
1956 					if(aa->attr.length() > 4) ac = aa->attr[4];
1957 					else ac = 0;
1958 
1959 					if(ab->attr.length() > 4) bc = ab->attr[4];
1960 					else bc = 0;
1961 
1962 					retsw(ac, bc, false);
1963 					break;
1964 				}
1965 				case SINFO_GAME:
1966 				{
1967 					if(aa->attr.length() > 1) ac = aa->attr[1];
1968 					else ac = 0;
1969 
1970 					if(ab->attr.length() > 1) bc = ab->attr[1];
1971 					else bc = 0;
1972 
1973 					retsw(ac, bc, true);
1974 
1975 					if(aa->attr.length() > 2) ac = aa->attr[2];
1976 					else ac = 0;
1977 
1978 					if(ab->attr.length() > 2) bc = ab->attr[2];
1979 					else bc = 0;
1980 
1981 					retsw(ac, bc, true);
1982 					break;
1983 				}
1984 				case SINFO_MAP:
1985 				{
1986 					retcp(strcmp(aa->map, ab->map));
1987 					break;
1988 				}
1989 				case SINFO_TIME:
1990 				{
1991 					if(aa->attr.length() > 3) ac = aa->attr[3];
1992 					else ac = 0;
1993 
1994 					if(ab->attr.length() > 3) bc = ab->attr[3];
1995 					else bc = 0;
1996 
1997 					retsw(ac, bc, false);
1998 					break;
1999 				}
2000 				default:
2001 					break;
2002 			}
2003 		}
2004 		return strcmp(a->name, b->name);
2005 	}
2006 
parsepacketclient(int chan,packetbuf & p)2007 	void parsepacketclient(int chan, packetbuf &p)	// processes any updates from the server
2008 	{
2009 		switch(chan)
2010 		{
2011 			case 0:
2012 				parsepositions(p);
2013 				break;
2014 
2015 			case 1:
2016 				parsemessages(-1, NULL, p);
2017 				break;
2018 
2019 			case 2:
2020 				receivefile(p.buf, p.maxlen);
2021 				break;
2022 		}
2023 	}
2024 
serverstartcolumn(guient * g,int i)2025     void serverstartcolumn(guient *g, int i)
2026     {
2027 		g->pushlist();
2028 		if(g->buttonf("%s ", 0xFFFFFF, NULL, true, i ? serverinfotypes[i] : "") & GUI_UP)
2029 		{
2030 			mkstring(st);
2031 			bool invert = false;
2032 			int len = execute("listlen $serversort");
2033 			loopk(len)
2034 			{
2035 				defformatstring(s)("at $serversort %d", k);
2036 
2037 				int n = execute(s);
2038 				if(abs(n) != i)
2039 				{
2040 					defformatstring(t)("%s%d", st[0] ? " " : "", n);
2041 					formatstring(st)("%s%s", st[0] ? st : "", t);
2042 				}
2043 				else if(!k) invert = true;
2044 			}
2045 			defformatstring(u)("serversort [%d%s%s]",
2046 				invert ? 0-i : i, st[0] ? " " : "", st[0] ? st : "");
2047 			execute(u);
2048 		}
2049 
2050 		g->mergehits(true);
2051     }
2052 
serverendcolumn(guient * g,int i)2053     void serverendcolumn(guient *g, int i)
2054     {
2055         g->mergehits(false);
2056         g->poplist();
2057     }
2058 
serverentry(guient * g,int i,serverinfo * si)2059     bool serverentry(guient *g, int i, serverinfo *si)
2060     {
2061 		mkstring(text);
2062 		int status = serverstat(si), colour = serverstatus[status].colour;
2063 		if(status == SSTAT_OPEN && !strcmp(si->sdesc, servermaster)) colour |= 0x222222;
2064 		switch(i)
2065 		{
2066 			case SINFO_STATUS:
2067 			{
2068 				if(g->button("", colour, serverstatus[status].icon) & GUI_UP)
2069 					return true;
2070 				break;
2071 			}
2072 			case SINFO_DESC:
2073 			{
2074 				copystring(text, si->sdesc, 24);
2075 				if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true;
2076 				break;
2077 			}
2078 			case SINFO_PING:
2079 			{
2080 				formatstring(text)("%d", si->ping);
2081 				if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true;
2082 				break;
2083 			}
2084 			case SINFO_PLAYERS:
2085 			{
2086 				formatstring(text)("%d", si->numplayers);
2087 				if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true;
2088 				break;
2089 			}
2090 			case SINFO_MAXCLIENTS:
2091 			{
2092 				if(si->attr.length() > 4 && si->attr[4] >= 0) formatstring(text)("%d", si->attr[4]);
2093 				if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true;
2094 				break;
2095 			}
2096 			case SINFO_GAME:
2097 			{
2098 				if(si->attr.length() > 1) formatstring(text)("%s", server::gamename(si->attr[1], si->attr[2]));
2099 				if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true;
2100 				break;
2101 			}
2102 			case SINFO_MAP:
2103 			{
2104 				copystring(text, si->map, 18);
2105 				if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true;
2106 				break;
2107 			}
2108 			case SINFO_TIME:
2109 			{
2110 				if(si->attr.length() > 3 && si->attr[3] >= 0)
2111 					formatstring(text)("%d %s", si->attr[3], si->attr[3] == 1 ? "min" : "mins");
2112 				if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true;
2113 				break;
2114 			}
2115 			default:
2116 				break;
2117 		}
2118 		return false;
2119     }
2120 
serverbrowser(guient * g)2121 	int serverbrowser(guient *g)
2122 	{
2123 		if(servers.empty())
2124 		{
2125 			g->pushlist();
2126 			g->text("No servers, press UPDATE to see some..", 0xFFFFFF);
2127 			g->poplist();
2128 			return -1;
2129 		}
2130 		loopv(servers) if(!strcmp(servers[i]->sdesc, servermaster))
2131 		{
2132 			if(servers[i]->attr[0] > GAMEVERSION)
2133 			{
2134 				g->pushlist();
2135 				g->textf("\fs\fgNEW VERSION RELEASED!\fS Please visit \fs\fb%s\fS for more information.", 0xFFFFFF, "info", ENG_URL);
2136 				g->poplist();
2137 			}
2138 			break;
2139 		}
2140 		int n = -1;
2141 		for(int start = 0; start < servers.length();)
2142 		{
2143 			if(start > 0) g->tab();
2144 			int end = servers.length();
2145 			g->pushlist();
2146 			loopi(SINFO_MAX)
2147 			{
2148 				serverstartcolumn(g, i);
2149 				for(int j = start; j < end; j++)
2150 				{
2151 					if(!i && g->shouldtab()) { end = j; break; }
2152 					serverinfo *si = servers[j];
2153 					if(si->ping < 999 && si->attr.length() && si->attr[0] == GAMEVERSION)
2154 					{
2155 						if(serverentry(g, i, si)) n = j;
2156 					}
2157 				}
2158 				serverendcolumn(g, i);
2159 			}
2160 			g->poplist();
2161 			start = end;
2162 		}
2163 		return n;
2164 	}
2165 }
2166