]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - host_cmd.c
Patch by graphitemaster to support column number enhanced lno format.
[xonotic/darkplaces.git] / host_cmd.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22 #include "sv_demo.h"
23 #include "image.h"
24
25 #include "utf8lib.h"
26
27 // for secure rcon authentication
28 #include "hmac.h"
29 #include "mdfour.h"
30 #include <time.h>
31
32 int current_skill;
33 cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
34 cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
35 cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
36 cvar_t sv_status_show_qcstatus = {CVAR_SAVE, "sv_status_show_qcstatus", "0", "show the 'qcstatus' field in status replies, not the 'frags' field. Turn this on if your mod uses this field, and the 'frags' field on the other hand has no meaningful value."};
37 cvar_t sv_namechangetimer = {CVAR_SAVE, "sv_namechangetimer", "5", "how often to allow name changes, in seconds (prevents people from using animated names and other tricks"};
38 cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"};
39 cvar_t rcon_secure = {CVAR_NQUSERINFOHACK, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"};
40 cvar_t rcon_secure_challengetimeout = {0, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
41 cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
42 cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
43 cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
44 cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
45 cvar_t r_fixtrans_auto = {0, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"};
46 qboolean allowcheats = false;
47
48 extern qboolean host_shuttingdown;
49 extern cvar_t developer_entityparsing;
50
51 /*
52 ==================
53 Host_Quit_f
54 ==================
55 */
56
57 void Host_Quit_f (void)
58 {
59         if(host_shuttingdown)
60                 Con_Printf("shutting down already!\n");
61         else
62                 Sys_Quit (0);
63 }
64
65 /*
66 ==================
67 Host_Status_f
68 ==================
69 */
70 static void Host_Status_f (void)
71 {
72         prvm_prog_t *prog = SVVM_prog;
73         char qcstatus[256];
74         client_t *client;
75         int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
76         void (*print) (const char *fmt, ...);
77         char ip[48]; // can contain a full length v6 address with [] and a port
78         int frags;
79         char vabuf[1024];
80
81         if (cmd_source == src_command)
82         {
83                 // if running a client, try to send over network so the client's status report parser will see the report
84                 if (cls.state == ca_connected)
85                 {
86                         Cmd_ForwardToServer ();
87                         return;
88                 }
89                 print = Con_Printf;
90         }
91         else
92                 print = SV_ClientPrintf;
93
94         if (!sv.active)
95                 return;
96
97         in = 0;
98         if (Cmd_Argc() == 2)
99         {
100                 if (strcmp(Cmd_Argv(1), "1") == 0)
101                         in = 1;
102                 else if (strcmp(Cmd_Argv(1), "2") == 0)
103                         in = 2;
104         }
105
106         for (players = 0, i = 0;i < svs.maxclients;i++)
107                 if (svs.clients[i].active)
108                         players++;
109         print ("host:     %s\n", Cvar_VariableString ("hostname"));
110         print ("version:  %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
111         print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
112         print ("map:      %s\n", sv.name);
113         print ("timing:   %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
114         print ("players:  %i active (%i max)\n\n", players, svs.maxclients);
115
116         if (in == 1)
117                 print ("^2IP                                             %%pl ping  time   frags  no   name\n");
118         else if (in == 2)
119                 print ("^5IP                                              no   name\n");
120
121         for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
122         {
123                 if (!client->active)
124                         continue;
125
126                 ++k;
127
128                 if (in == 0 || in == 1)
129                 {
130                         seconds = (int)(realtime - client->connecttime);
131                         minutes = seconds / 60;
132                         if (minutes)
133                         {
134                                 seconds -= (minutes * 60);
135                                 hours = minutes / 60;
136                                 if (hours)
137                                         minutes -= (hours * 60);
138                         }
139                         else
140                                 hours = 0;
141                         
142                         packetloss = 0;
143                         if (client->netconnection)
144                                 for (j = 0;j < NETGRAPH_PACKETS;j++)
145                                         if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
146                                                 packetloss++;
147                         packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
148                         ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
149                 }
150
151                 if(sv_status_privacy.integer && cmd_source != src_command)
152                         strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
153                 else
154                         strlcpy(ip, (client->netconnection && client->netconnection->address) ? client->netconnection->address : "botclient", 48);
155
156                 frags = client->frags;
157
158                 if(sv_status_show_qcstatus.integer)
159                 {
160                         prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
161                         const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
162                         if(str && *str)
163                         {
164                                 char *p;
165                                 const char *q;
166                                 p = qcstatus;
167                                 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
168                                         if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
169                                                 *p++ = *q;
170                                 *p = 0;
171                                 if(*qcstatus)
172                                         frags = atoi(qcstatus);
173                         }
174                 }
175                 
176                 if (in == 0) // default layout
177                 {
178                         if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
179                         {
180                                 // LordHavoc: this is very touchy because we must maintain ProQuake compatible status output
181                                 print ("#%-2u %-16.16s  %3i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
182                                 print ("   %s\n", ip);
183                         }
184                         else
185                         {
186                                 // LordHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
187                                 print ("#%-3u %-16.16s %4i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
188                                 print ("   %s\n", ip);
189                         }
190                 }
191                 else if (in == 1) // extended layout
192                 {
193                         print ("%s%-47s %2i %4i %2i:%02i:%02i %4i  #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, packetloss, ping, hours, minutes, seconds, frags, i+1, client->name);
194                 }
195                 else if (in == 2) // reduced layout
196                 {
197                         print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
198                 }
199         }
200 }
201
202
203 /*
204 ==================
205 Host_God_f
206
207 Sets client to godmode
208 ==================
209 */
210 static void Host_God_f (void)
211 {
212         prvm_prog_t *prog = SVVM_prog;
213         if (!allowcheats)
214         {
215                 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
216                 return;
217         }
218
219         PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
220         if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
221                 SV_ClientPrint("godmode OFF\n");
222         else
223                 SV_ClientPrint("godmode ON\n");
224 }
225
226 static void Host_Notarget_f (void)
227 {
228         prvm_prog_t *prog = SVVM_prog;
229         if (!allowcheats)
230         {
231                 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
232                 return;
233         }
234
235         PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
236         if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
237                 SV_ClientPrint("notarget OFF\n");
238         else
239                 SV_ClientPrint("notarget ON\n");
240 }
241
242 qboolean noclip_anglehack;
243
244 static void Host_Noclip_f (void)
245 {
246         prvm_prog_t *prog = SVVM_prog;
247         if (!allowcheats)
248         {
249                 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
250                 return;
251         }
252
253         if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
254         {
255                 noclip_anglehack = true;
256                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
257                 SV_ClientPrint("noclip ON\n");
258         }
259         else
260         {
261                 noclip_anglehack = false;
262                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
263                 SV_ClientPrint("noclip OFF\n");
264         }
265 }
266
267 /*
268 ==================
269 Host_Fly_f
270
271 Sets client to flymode
272 ==================
273 */
274 static void Host_Fly_f (void)
275 {
276         prvm_prog_t *prog = SVVM_prog;
277         if (!allowcheats)
278         {
279                 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
280                 return;
281         }
282
283         if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
284         {
285                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
286                 SV_ClientPrint("flymode ON\n");
287         }
288         else
289         {
290                 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
291                 SV_ClientPrint("flymode OFF\n");
292         }
293 }
294
295
296 /*
297 ==================
298 Host_Ping_f
299
300 ==================
301 */
302 void Host_Pings_f (void); // called by Host_Ping_f
303 static void Host_Ping_f (void)
304 {
305         int i;
306         client_t *client;
307         void (*print) (const char *fmt, ...);
308
309         if (cmd_source == src_command)
310         {
311                 // if running a client, try to send over network so the client's ping report parser will see the report
312                 if (cls.state == ca_connected)
313                 {
314                         Cmd_ForwardToServer ();
315                         return;
316                 }
317                 print = Con_Printf;
318         }
319         else
320                 print = SV_ClientPrintf;
321
322         if (!sv.active)
323                 return;
324
325         print("Client ping times:\n");
326         for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
327         {
328                 if (!client->active)
329                         continue;
330                 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
331         }
332
333         // now call the Pings command also, which will send a report that contains packet loss for the scoreboard (as well as a simpler ping report)
334         // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console)
335         //Host_Pings_f();
336 }
337
338 /*
339 ===============================================================================
340
341 SERVER TRANSITIONS
342
343 ===============================================================================
344 */
345
346 /*
347 ======================
348 Host_Map_f
349
350 handle a
351 map <servername>
352 command from the console.  Active clients are kicked off.
353 ======================
354 */
355 static void Host_Map_f (void)
356 {
357         char level[MAX_QPATH];
358
359         if (Cmd_Argc() != 2)
360         {
361                 Con_Print("map <levelname> : start a new game (kicks off all players)\n");
362                 return;
363         }
364
365         // GAME_DELUXEQUAKE - clear warpmark (used by QC)
366         if (gamemode == GAME_DELUXEQUAKE)
367                 Cvar_Set("warpmark", "");
368
369         cls.demonum = -1;               // stop demo loop in case this fails
370
371         CL_Disconnect ();
372         Host_ShutdownServer();
373
374         if(svs.maxclients != svs.maxclients_next)
375         {
376                 svs.maxclients = svs.maxclients_next;
377                 if (svs.clients)
378                         Mem_Free(svs.clients);
379                 svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
380         }
381
382 #ifdef CONFIG_MENU
383         // remove menu
384         if (key_dest == key_menu || key_dest == key_menu_grabbed)
385                 MR_ToggleMenu(0);
386 #endif
387         key_dest = key_game;
388
389         svs.serverflags = 0;                    // haven't completed an episode yet
390         allowcheats = sv_cheats.integer != 0;
391         strlcpy(level, Cmd_Argv(1), sizeof(level));
392         SV_SpawnServer(level);
393         if (sv.active && cls.state == ca_disconnected)
394                 CL_EstablishConnection("local:1", -2);
395 }
396
397 /*
398 ==================
399 Host_Changelevel_f
400
401 Goes to a new map, taking all clients along
402 ==================
403 */
404 static void Host_Changelevel_f (void)
405 {
406         char level[MAX_QPATH];
407
408         if (Cmd_Argc() != 2)
409         {
410                 Con_Print("changelevel <levelname> : continue game on a new level\n");
411                 return;
412         }
413         // HACKHACKHACK
414         if (!sv.active) {
415                 Host_Map_f();
416                 return;
417         }
418
419 #ifdef CONFIG_MENU
420         // remove menu
421         if (key_dest == key_menu || key_dest == key_menu_grabbed)
422                 MR_ToggleMenu(0);
423 #endif
424         key_dest = key_game;
425
426         SV_SaveSpawnparms ();
427         allowcheats = sv_cheats.integer != 0;
428         strlcpy(level, Cmd_Argv(1), sizeof(level));
429         SV_SpawnServer(level);
430         if (sv.active && cls.state == ca_disconnected)
431                 CL_EstablishConnection("local:1", -2);
432 }
433
434 /*
435 ==================
436 Host_Restart_f
437
438 Restarts the current server for a dead player
439 ==================
440 */
441 static void Host_Restart_f (void)
442 {
443         char mapname[MAX_QPATH];
444
445         if (Cmd_Argc() != 1)
446         {
447                 Con_Print("restart : restart current level\n");
448                 return;
449         }
450         if (!sv.active)
451         {
452                 Con_Print("Only the server may restart\n");
453                 return;
454         }
455
456 #ifdef CONFIG_MENU
457         // remove menu
458         if (key_dest == key_menu || key_dest == key_menu_grabbed)
459                 MR_ToggleMenu(0);
460 #endif
461         key_dest = key_game;
462
463         allowcheats = sv_cheats.integer != 0;
464         strlcpy(mapname, sv.name, sizeof(mapname));
465         SV_SpawnServer(mapname);
466         if (sv.active && cls.state == ca_disconnected)
467                 CL_EstablishConnection("local:1", -2);
468 }
469
470 /*
471 ==================
472 Host_Reconnect_f
473
474 This command causes the client to wait for the signon messages again.
475 This is sent just before a server changes levels
476 ==================
477 */
478 void Host_Reconnect_f (void)
479 {
480         char temp[128];
481         // if not connected, reconnect to the most recent server
482         if (!cls.netcon)
483         {
484                 // if we have connected to a server recently, the userinfo
485                 // will still contain its IP address, so get the address...
486                 InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
487                 if (temp[0])
488                         CL_EstablishConnection(temp, -1);
489                 else
490                         Con_Printf("Reconnect to what server?  (you have not connected to a server yet)\n");
491                 return;
492         }
493         // if connected, do something based on protocol
494         if (cls.protocol == PROTOCOL_QUAKEWORLD)
495         {
496                 // quakeworld can just re-login
497                 if (cls.qw_downloadmemory)  // don't change when downloading
498                         return;
499
500                 S_StopAllSounds();
501
502                 if (cls.state == ca_connected && cls.signon < SIGNONS)
503                 {
504                         Con_Printf("reconnecting...\n");
505                         MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
506                         MSG_WriteString(&cls.netcon->message, "new");
507                 }
508         }
509         else
510         {
511                 // netquake uses reconnect on level changes (silly)
512                 if (Cmd_Argc() != 1)
513                 {
514                         Con_Print("reconnect : wait for signon messages again\n");
515                         return;
516                 }
517                 if (!cls.signon)
518                 {
519                         Con_Print("reconnect: no signon, ignoring reconnect\n");
520                         return;
521                 }
522                 cls.signon = 0;         // need new connection messages
523         }
524 }
525
526 /*
527 =====================
528 Host_Connect_f
529
530 User command to connect to server
531 =====================
532 */
533 static void Host_Connect_f (void)
534 {
535         if (Cmd_Argc() < 2)
536         {
537                 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
538                 return;
539         }
540         // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
541         if(rcon_secure.integer <= 0)
542                 Cvar_SetQuick(&rcon_password, "");
543         CL_EstablishConnection(Cmd_Argv(1), 2);
544 }
545
546
547 /*
548 ===============================================================================
549
550 LOAD / SAVE GAME
551
552 ===============================================================================
553 */
554
555 #define SAVEGAME_VERSION        5
556
557 void Host_Savegame_to(prvm_prog_t *prog, const char *name)
558 {
559         qfile_t *f;
560         int             i, k, l, numbuffers, lightstyles = 64;
561         char    comment[SAVEGAME_COMMENT_LENGTH+1];
562         char    line[MAX_INPUTLINE];
563         qboolean isserver;
564         char    *s;
565
566         // first we have to figure out if this can be saved in 64 lightstyles
567         // (for Quake compatibility)
568         for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
569                 if (sv.lightstyles[i][0])
570                         lightstyles = i+1;
571
572         isserver = prog == SVVM_prog;
573
574         Con_Printf("Saving game to %s...\n", name);
575         f = FS_OpenRealFile(name, "wb", false);
576         if (!f)
577         {
578                 Con_Print("ERROR: couldn't open.\n");
579                 return;
580         }
581
582         FS_Printf(f, "%i\n", SAVEGAME_VERSION);
583
584         memset(comment, 0, sizeof(comment));
585         if(isserver)
586                 dpsnprintf(comment, sizeof(comment), "%-21.21s kills:%3i/%3i", PRVM_GetString(prog, PRVM_serveredictstring(prog->edicts, message)), (int)PRVM_serverglobalfloat(killed_monsters), (int)PRVM_serverglobalfloat(total_monsters));
587         else
588                 dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", prog->name);
589         // convert space to _ to make stdio happy
590         // LordHavoc: convert control characters to _ as well
591         for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
592                 if (ISWHITESPACEORCONTROL(comment[i]))
593                         comment[i] = '_';
594         comment[SAVEGAME_COMMENT_LENGTH] = '\0';
595
596         FS_Printf(f, "%s\n", comment);
597         if(isserver)
598         {
599                 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
600                         FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
601                 FS_Printf(f, "%d\n", current_skill);
602                 FS_Printf(f, "%s\n", sv.name);
603                 FS_Printf(f, "%f\n",sv.time);
604         }
605         else
606         {
607                 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
608                         FS_Printf(f, "(dummy)\n");
609                 FS_Printf(f, "%d\n", 0);
610                 FS_Printf(f, "%s\n", "(dummy)");
611                 FS_Printf(f, "%f\n", realtime);
612         }
613
614         // write the light styles
615         for (i=0 ; i<lightstyles ; i++)
616         {
617                 if (isserver && sv.lightstyles[i][0])
618                         FS_Printf(f, "%s\n", sv.lightstyles[i]);
619                 else
620                         FS_Print(f,"m\n");
621         }
622
623         PRVM_ED_WriteGlobals (prog, f);
624         for (i=0 ; i<prog->num_edicts ; i++)
625         {
626                 FS_Printf(f,"// edict %d\n", i);
627                 //Con_Printf("edict %d...\n", i);
628                 PRVM_ED_Write (prog, f, PRVM_EDICT_NUM(i));
629         }
630
631 #if 1
632         FS_Printf(f,"/*\n");
633         FS_Printf(f,"// DarkPlaces extended savegame\n");
634         // darkplaces extension - extra lightstyles, support for color lightstyles
635         for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
636                 if (isserver && sv.lightstyles[i][0])
637                         FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
638
639         // darkplaces extension - model precaches
640         for (i=1 ; i<MAX_MODELS ; i++)
641                 if (sv.model_precache[i][0])
642                         FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
643
644         // darkplaces extension - sound precaches
645         for (i=1 ; i<MAX_SOUNDS ; i++)
646                 if (sv.sound_precache[i][0])
647                         FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
648
649         // darkplaces extension - save buffers
650         numbuffers = Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
651         for (i = 0; i < numbuffers; i++)
652         {
653                 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
654                 if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
655                 {
656                         FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS);
657                         for(k = 0; k < stringbuffer->num_strings; k++)
658                         {
659                                 if (!stringbuffer->strings[k])
660                                         continue;
661                                 // Parse the string a bit to turn special characters
662                                 // (like newline, specifically) into escape codes
663                                 s = stringbuffer->strings[k];
664                                 for (l = 0;l < (int)sizeof(line) - 2 && *s;)
665                                 {       
666                                         if (*s == '\n')
667                                         {
668                                                 line[l++] = '\\';
669                                                 line[l++] = 'n';
670                                         }
671                                         else if (*s == '\r')
672                                         {
673                                                 line[l++] = '\\';
674                                                 line[l++] = 'r';
675                                         }
676                                         else if (*s == '\\')
677                                         {
678                                                 line[l++] = '\\';
679                                                 line[l++] = '\\';
680                                         }
681                                         else if (*s == '"')
682                                         {
683                                                 line[l++] = '\\';
684                                                 line[l++] = '"';
685                                         }
686                                         else
687                                                 line[l++] = *s;
688                                         s++;
689                                 }
690                                 line[l] = '\0';
691                                 FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
692                         }
693                 }
694         }
695         FS_Printf(f,"*/\n");
696 #endif
697
698         FS_Close (f);
699         Con_Print("done.\n");
700 }
701
702 /*
703 ===============
704 Host_Savegame_f
705 ===============
706 */
707 static void Host_Savegame_f (void)
708 {
709         prvm_prog_t *prog = SVVM_prog;
710         char    name[MAX_QPATH];
711         qboolean deadflag = false;
712
713         if (!sv.active)
714         {
715                 Con_Print("Can't save - no server running.\n");
716                 return;
717         }
718
719         deadflag = cl.islocalgame && svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag);
720
721         if (cl.islocalgame)
722         {
723                 // singleplayer checks
724                 if (cl.intermission)
725                 {
726                         Con_Print("Can't save in intermission.\n");
727                         return;
728                 }
729
730                 if (deadflag)
731                 {
732                         Con_Print("Can't savegame with a dead player\n");
733                         return;
734                 }
735         }
736         else
737                 Con_Print("Warning: saving a multiplayer game may have strange results when restored (to properly resume, all players must join in the same player slots and then the game can be reloaded).\n");
738
739         if (Cmd_Argc() != 2)
740         {
741                 Con_Print("save <savename> : save a game\n");
742                 return;
743         }
744
745         if (strstr(Cmd_Argv(1), ".."))
746         {
747                 Con_Print("Relative pathnames are not allowed.\n");
748                 return;
749         }
750
751         strlcpy (name, Cmd_Argv(1), sizeof (name));
752         FS_DefaultExtension (name, ".sav", sizeof (name));
753
754         Host_Savegame_to(prog, name);
755 }
756
757
758 /*
759 ===============
760 Host_Loadgame_f
761 ===============
762 */
763
764 prvm_stringbuffer_t *BufStr_FindCreateReplace (prvm_prog_t *prog, int bufindex, int flags, char *format);
765 void BufStr_Set(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer, int strindex, const char *str);
766 void BufStr_Del(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer);
767 void BufStr_Flush(prvm_prog_t *prog);
768
769 static void Host_Loadgame_f (void)
770 {
771         prvm_prog_t *prog = SVVM_prog;
772         char filename[MAX_QPATH];
773         char mapname[MAX_QPATH];
774         float time;
775         const char *start;
776         const char *end;
777         const char *t;
778         char *text;
779         prvm_edict_t *ent;
780         int i, k, numbuffers;
781         int entnum;
782         int version;
783         float spawn_parms[NUM_SPAWN_PARMS];
784         prvm_stringbuffer_t *stringbuffer;
785
786         if (Cmd_Argc() != 2)
787         {
788                 Con_Print("load <savename> : load a game\n");
789                 return;
790         }
791
792         strlcpy (filename, Cmd_Argv(1), sizeof(filename));
793         FS_DefaultExtension (filename, ".sav", sizeof (filename));
794
795         Con_Printf("Loading game from %s...\n", filename);
796
797         // stop playing demos
798         if (cls.demoplayback)
799                 CL_Disconnect ();
800
801 #ifdef CONFIG_MENU
802         // remove menu
803         if (key_dest == key_menu || key_dest == key_menu_grabbed)
804                 MR_ToggleMenu(0);
805 #endif
806         key_dest = key_game;
807
808         cls.demonum = -1;               // stop demo loop in case this fails
809
810         t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
811         if (!text)
812         {
813                 Con_Print("ERROR: couldn't open.\n");
814                 return;
815         }
816
817         if(developer_entityparsing.integer)
818                 Con_Printf("Host_Loadgame_f: loading version\n");
819
820         // version
821         COM_ParseToken_Simple(&t, false, false, true);
822         version = atoi(com_token);
823         if (version != SAVEGAME_VERSION)
824         {
825                 Mem_Free(text);
826                 Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
827                 return;
828         }
829
830         if(developer_entityparsing.integer)
831                 Con_Printf("Host_Loadgame_f: loading description\n");
832
833         // description
834         COM_ParseToken_Simple(&t, false, false, true);
835
836         for (i = 0;i < NUM_SPAWN_PARMS;i++)
837         {
838                 COM_ParseToken_Simple(&t, false, false, true);
839                 spawn_parms[i] = atof(com_token);
840         }
841         // skill
842         COM_ParseToken_Simple(&t, false, false, true);
843 // this silliness is so we can load 1.06 save files, which have float skill values
844         current_skill = (int)(atof(com_token) + 0.5);
845         Cvar_SetValue ("skill", (float)current_skill);
846
847         if(developer_entityparsing.integer)
848                 Con_Printf("Host_Loadgame_f: loading mapname\n");
849
850         // mapname
851         COM_ParseToken_Simple(&t, false, false, true);
852         strlcpy (mapname, com_token, sizeof(mapname));
853
854         if(developer_entityparsing.integer)
855                 Con_Printf("Host_Loadgame_f: loading time\n");
856
857         // time
858         COM_ParseToken_Simple(&t, false, false, true);
859         time = atof(com_token);
860
861         allowcheats = sv_cheats.integer != 0;
862
863         if(developer_entityparsing.integer)
864                 Con_Printf("Host_Loadgame_f: spawning server\n");
865
866         SV_SpawnServer (mapname);
867         if (!sv.active)
868         {
869                 Mem_Free(text);
870                 Con_Print("Couldn't load map\n");
871                 return;
872         }
873         sv.paused = true;               // pause until all clients connect
874         sv.loadgame = true;
875
876         if(developer_entityparsing.integer)
877                 Con_Printf("Host_Loadgame_f: loading light styles\n");
878
879 // load the light styles
880
881         // -1 is the globals
882         entnum = -1;
883
884         for (i = 0;i < MAX_LIGHTSTYLES;i++)
885         {
886                 // light style
887                 start = t;
888                 COM_ParseToken_Simple(&t, false, false, true);
889                 // if this is a 64 lightstyle savegame produced by Quake, stop now
890                 // we have to check this because darkplaces may save more than 64
891                 if (com_token[0] == '{')
892                 {
893                         t = start;
894                         break;
895                 }
896                 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
897         }
898
899         if(developer_entityparsing.integer)
900                 Con_Printf("Host_Loadgame_f: skipping until globals\n");
901
902         // now skip everything before the first opening brace
903         // (this is for forward compatibility, so that older versions (at
904         // least ones with this fix) can load savegames with extra data before the
905         // first brace, as might be produced by a later engine version)
906         for (;;)
907         {
908                 start = t;
909                 if (!COM_ParseToken_Simple(&t, false, false, true))
910                         break;
911                 if (com_token[0] == '{')
912                 {
913                         t = start;
914                         break;
915                 }
916         }
917
918         // unlink all entities
919         World_UnlinkAll(&sv.world);
920
921 // load the edicts out of the savegame file
922         end = t;
923         for (;;)
924         {
925                 start = t;
926                 while (COM_ParseToken_Simple(&t, false, false, true))
927                         if (!strcmp(com_token, "}"))
928                                 break;
929                 if (!COM_ParseToken_Simple(&start, false, false, true))
930                 {
931                         // end of file
932                         break;
933                 }
934                 if (strcmp(com_token,"{"))
935                 {
936                         Mem_Free(text);
937                         Host_Error ("First token isn't a brace");
938                 }
939
940                 if (entnum == -1)
941                 {
942                         if(developer_entityparsing.integer)
943                                 Con_Printf("Host_Loadgame_f: loading globals\n");
944
945                         // parse the global vars
946                         PRVM_ED_ParseGlobals (prog, start);
947
948                         // restore the autocvar globals
949                         Cvar_UpdateAllAutoCvars();
950                 }
951                 else
952                 {
953                         // parse an edict
954                         if (entnum >= MAX_EDICTS)
955                         {
956                                 Mem_Free(text);
957                                 Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
958                         }
959                         while (entnum >= prog->max_edicts)
960                                 PRVM_MEM_IncreaseEdicts(prog);
961                         ent = PRVM_EDICT_NUM(entnum);
962                         memset(ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
963                         ent->priv.server->free = false;
964
965                         if(developer_entityparsing.integer)
966                                 Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum);
967
968                         PRVM_ED_ParseEdict (prog, start, ent);
969
970                         // link it into the bsp tree
971                         if (!ent->priv.server->free)
972                                 SV_LinkEdict(ent);
973                 }
974
975                 end = t;
976                 entnum++;
977         }
978
979         prog->num_edicts = entnum;
980         sv.time = time;
981
982         for (i = 0;i < NUM_SPAWN_PARMS;i++)
983                 svs.clients[0].spawn_parms[i] = spawn_parms[i];
984
985         if(developer_entityparsing.integer)
986                 Con_Printf("Host_Loadgame_f: skipping until extended data\n");
987
988         // read extended data if present
989         // the extended data is stored inside a /* */ comment block, which the
990         // parser intentionally skips, so we have to check for it manually here
991         if(end)
992         {
993                 while (*end == '\r' || *end == '\n')
994                         end++;
995                 if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
996                 {
997                         if(developer_entityparsing.integer)
998                                 Con_Printf("Host_Loadgame_f: loading extended data\n");
999
1000                         Con_Printf("Loading extended DarkPlaces savegame\n");
1001                         t = end + 2;
1002                         memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
1003                         memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
1004                         memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
1005                         BufStr_Flush(prog);
1006
1007                         while (COM_ParseToken_Simple(&t, false, false, true))
1008                         {
1009                                 if (!strcmp(com_token, "sv.lightstyles"))
1010                                 {
1011                                         COM_ParseToken_Simple(&t, false, false, true);
1012                                         i = atoi(com_token);
1013                                         COM_ParseToken_Simple(&t, false, false, true);
1014                                         if (i >= 0 && i < MAX_LIGHTSTYLES)
1015                                                 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
1016                                         else
1017                                                 Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
1018                                 }
1019                                 else if (!strcmp(com_token, "sv.model_precache"))
1020                                 {
1021                                         COM_ParseToken_Simple(&t, false, false, true);
1022                                         i = atoi(com_token);
1023                                         COM_ParseToken_Simple(&t, false, false, true);
1024                                         if (i >= 0 && i < MAX_MODELS)
1025                                         {
1026                                                 strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
1027                                                 sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
1028                                         }
1029                                         else
1030                                                 Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
1031                                 }
1032                                 else if (!strcmp(com_token, "sv.sound_precache"))
1033                                 {
1034                                         COM_ParseToken_Simple(&t, false, false, true);
1035                                         i = atoi(com_token);
1036                                         COM_ParseToken_Simple(&t, false, false, true);
1037                                         if (i >= 0 && i < MAX_SOUNDS)
1038                                                 strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
1039                                         else
1040                                                 Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
1041                                 }
1042                                 else if (!strcmp(com_token, "sv.buffer"))
1043                                 {
1044                                         if (COM_ParseToken_Simple(&t, false, false, true))
1045                                         {
1046                                                 i = atoi(com_token);
1047                                                 if (i >= 0)
1048                                                 {
1049                                                         k = STRINGBUFFER_SAVED;
1050                                                         if (COM_ParseToken_Simple(&t, false, false, true))
1051                                                                 k |= atoi(com_token);
1052                                                         if (!BufStr_FindCreateReplace(prog, i, k, "string"))
1053                                                                 Con_Printf("failed to create stringbuffer %i\n", i);
1054                                                 }
1055                                                 else
1056                                                         Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token);
1057                                         }
1058                                         else
1059                                                 Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n");
1060                                 }
1061                                 else if (!strcmp(com_token, "sv.bufstr"))
1062                                 {
1063                                         if (!COM_ParseToken_Simple(&t, false, false, true))
1064                                                 Con_Printf("unexpected end of line when parsing sv.bufstr\n");
1065                                         else
1066                                         {
1067                                                 i = atoi(com_token);
1068                                                 stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string");
1069                                                 if (stringbuffer)
1070                                                 {
1071                                                         if (COM_ParseToken_Simple(&t, false, false, true))
1072                                                         {
1073                                                                 k = atoi(com_token);
1074                                                                 if (COM_ParseToken_Simple(&t, false, false, true))
1075                                                                         BufStr_Set(prog, stringbuffer, k, com_token);
1076                                                                 else
1077                                                                         Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n");
1078                                                         }
1079                                                         else
1080                                                                 Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n");
1081                                                 }
1082                                                 else
1083                                                         Con_Printf("failed to create stringbuffer %i \"%s\"\n", i, com_token);
1084                                         }
1085                                 }       
1086                                 // skip any trailing text or unrecognized commands
1087                                 while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n"))
1088                                         ;
1089                         }
1090                 }
1091         }
1092         Mem_Free(text);
1093
1094         // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace)
1095         numbuffers = Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
1096         for (i = 0; i < numbuffers; i++)
1097         {
1098                 if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) )
1099                         if (stringbuffer->flags & STRINGBUFFER_TEMP)
1100                                 BufStr_Del(prog, stringbuffer);
1101         }
1102
1103         if(developer_entityparsing.integer)
1104                 Con_Printf("Host_Loadgame_f: finished\n");
1105
1106         // make sure we're connected to loopback
1107         if (sv.active && cls.state == ca_disconnected)
1108                 CL_EstablishConnection("local:1", -2);
1109 }
1110
1111 //============================================================================
1112
1113 /*
1114 ======================
1115 Host_Name_f
1116 ======================
1117 */
1118 cvar_t cl_name = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
1119 static void Host_Name_f (void)
1120 {
1121         prvm_prog_t *prog = SVVM_prog;
1122         int i, j;
1123         qboolean valid_colors;
1124         const char *newNameSource;
1125         char newName[sizeof(host_client->name)];
1126
1127         if (Cmd_Argc () == 1)
1128         {
1129                 Con_Printf("name: %s\n", cl_name.string);
1130                 return;
1131         }
1132
1133         if (Cmd_Argc () == 2)
1134                 newNameSource = Cmd_Argv(1);
1135         else
1136                 newNameSource = Cmd_Args();
1137
1138         strlcpy(newName, newNameSource, sizeof(newName));
1139
1140         if (cmd_source == src_command)
1141         {
1142                 Cvar_Set ("_cl_name", newName);
1143                 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
1144                 {
1145                         Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
1146                         Con_Printf("name: %s\n", cl_name.string);
1147                 }
1148                 return;
1149         }
1150
1151         if (realtime < host_client->nametime)
1152         {
1153                 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
1154                 return;
1155         }
1156
1157         host_client->nametime = realtime + max(0.0f, sv_namechangetimer.value);
1158
1159         // point the string back at updateclient->name to keep it safe
1160         strlcpy (host_client->name, newName, sizeof (host_client->name));
1161
1162         for (i = 0, j = 0;host_client->name[i];i++)
1163                 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
1164                         host_client->name[j++] = host_client->name[i];
1165         host_client->name[j] = 0;
1166
1167         if(host_client->name[0] == 1 || host_client->name[0] == 2)
1168         // may interfere with chat area, and will needlessly beep; so let's add a ^7
1169         {
1170                 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
1171                 host_client->name[sizeof(host_client->name) - 1] = 0;
1172                 host_client->name[0] = STRING_COLOR_TAG;
1173                 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
1174         }
1175
1176         u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1177         if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1178         {
1179                 size_t l;
1180                 l = strlen(host_client->name);
1181                 if(l < sizeof(host_client->name) - 1)
1182                 {
1183                         // duplicate the color tag to escape it
1184                         host_client->name[i] = STRING_COLOR_TAG;
1185                         host_client->name[i+1] = 0;
1186                         //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1187                 }
1188                 else
1189                 {
1190                         // remove the last character to fix the color code
1191                         host_client->name[l-1] = 0;
1192                         //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1193                 }
1194         }
1195
1196         // find the last color tag offset and decide if we need to add a reset tag
1197         for (i = 0, j = -1;host_client->name[i];i++)
1198         {
1199                 if (host_client->name[i] == STRING_COLOR_TAG)
1200                 {
1201                         if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1202                         {
1203                                 j = i;
1204                                 // if this happens to be a reset  tag then we don't need one
1205                                 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1206                                         j = -1;
1207                                 i++;
1208                                 continue;
1209                         }
1210                         if (host_client->name[i+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(host_client->name[i+2]) && isxdigit(host_client->name[i+3]) && isxdigit(host_client->name[i+4]))
1211                         {
1212                                 j = i;
1213                                 i += 4;
1214                                 continue;
1215                         }
1216                         if (host_client->name[i+1] == STRING_COLOR_TAG)
1217                         {
1218                                 i++;
1219                                 continue;
1220                         }
1221                 }
1222         }
1223         // does not end in the default color string, so add it
1224         if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1225                 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1226
1227         PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1228         if (strcmp(host_client->old_name, host_client->name))
1229         {
1230                 if (host_client->begun)
1231                         SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1232                 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1233                 // send notification to all clients
1234                 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1235                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1236                 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1237                 SV_WriteNetnameIntoDemo(host_client);
1238         }
1239 }
1240
1241 /*
1242 ======================
1243 Host_Playermodel_f
1244 ======================
1245 */
1246 cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
1247 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
1248 static void Host_Playermodel_f (void)
1249 {
1250         prvm_prog_t *prog = SVVM_prog;
1251         int i, j;
1252         char newPath[sizeof(host_client->playermodel)];
1253
1254         if (Cmd_Argc () == 1)
1255         {
1256                 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
1257                 return;
1258         }
1259
1260         if (Cmd_Argc () == 2)
1261                 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1262         else
1263                 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1264
1265         for (i = 0, j = 0;newPath[i];i++)
1266                 if (newPath[i] != '\r' && newPath[i] != '\n')
1267                         newPath[j++] = newPath[i];
1268         newPath[j] = 0;
1269
1270         if (cmd_source == src_command)
1271         {
1272                 Cvar_Set ("_cl_playermodel", newPath);
1273                 return;
1274         }
1275
1276         /*
1277         if (realtime < host_client->nametime)
1278         {
1279                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1280                 return;
1281         }
1282
1283         host_client->nametime = realtime + 5;
1284         */
1285
1286         // point the string back at updateclient->name to keep it safe
1287         strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1288         PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1289         if (strcmp(host_client->old_model, host_client->playermodel))
1290         {
1291                 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1292                 /*// send notification to all clients
1293                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1294                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1295                 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1296         }
1297 }
1298
1299 /*
1300 ======================
1301 Host_Playerskin_f
1302 ======================
1303 */
1304 cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
1305 static void Host_Playerskin_f (void)
1306 {
1307         prvm_prog_t *prog = SVVM_prog;
1308         int i, j;
1309         char newPath[sizeof(host_client->playerskin)];
1310
1311         if (Cmd_Argc () == 1)
1312         {
1313                 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
1314                 return;
1315         }
1316
1317         if (Cmd_Argc () == 2)
1318                 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1319         else
1320                 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1321
1322         for (i = 0, j = 0;newPath[i];i++)
1323                 if (newPath[i] != '\r' && newPath[i] != '\n')
1324                         newPath[j++] = newPath[i];
1325         newPath[j] = 0;
1326
1327         if (cmd_source == src_command)
1328         {
1329                 Cvar_Set ("_cl_playerskin", newPath);
1330                 return;
1331         }
1332
1333         /*
1334         if (realtime < host_client->nametime)
1335         {
1336                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1337                 return;
1338         }
1339
1340         host_client->nametime = realtime + 5;
1341         */
1342
1343         // point the string back at updateclient->name to keep it safe
1344         strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1345         PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1346         if (strcmp(host_client->old_skin, host_client->playerskin))
1347         {
1348                 //if (host_client->begun)
1349                 //      SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1350                 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1351                 /*// send notification to all clients
1352                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1353                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1354                 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1355         }
1356 }
1357
1358 static void Host_Version_f (void)
1359 {
1360         Con_Printf("Version: %s build %s\n", gamename, buildstring);
1361 }
1362
1363 static void Host_Say(qboolean teamonly)
1364 {
1365         prvm_prog_t *prog = SVVM_prog;
1366         client_t *save;
1367         int j, quoted;
1368         const char *p1;
1369         char *p2;
1370         // LordHavoc: long say messages
1371         char text[1024];
1372         qboolean fromServer = false;
1373
1374         if (cmd_source == src_command)
1375         {
1376                 if (cls.state == ca_dedicated)
1377                 {
1378                         fromServer = true;
1379                         teamonly = false;
1380                 }
1381                 else
1382                 {
1383                         Cmd_ForwardToServer ();
1384                         return;
1385                 }
1386         }
1387
1388         if (Cmd_Argc () < 2)
1389                 return;
1390
1391         if (!teamplay.integer)
1392                 teamonly = false;
1393
1394         p1 = Cmd_Args();
1395         quoted = false;
1396         if (*p1 == '\"')
1397         {
1398                 quoted = true;
1399                 p1++;
1400         }
1401         // note this uses the chat prefix \001
1402         if (!fromServer && !teamonly)
1403                 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
1404         else if (!fromServer && teamonly)
1405                 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
1406         else if(*(sv_adminnick.string))
1407                 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
1408         else
1409                 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
1410         p2 = text + strlen(text);
1411         while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
1412         {
1413                 if (p2[-1] == '\"' && quoted)
1414                         quoted = false;
1415                 p2[-1] = 0;
1416                 p2--;
1417         }
1418         strlcat(text, "\n", sizeof(text));
1419
1420         // note: save is not a valid edict if fromServer is true
1421         save = host_client;
1422         for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
1423                 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
1424                         SV_ClientPrint(text);
1425         host_client = save;
1426
1427         if (cls.state == ca_dedicated)
1428                 Con_Print(&text[1]);
1429 }
1430
1431
1432 static void Host_Say_f(void)
1433 {
1434         Host_Say(false);
1435 }
1436
1437
1438 static void Host_Say_Team_f(void)
1439 {
1440         Host_Say(true);
1441 }
1442
1443
1444 static void Host_Tell_f(void)
1445 {
1446         const char *playername_start = NULL;
1447         size_t playername_length = 0;
1448         int playernumber = 0;
1449         client_t *save;
1450         int j;
1451         const char *p1, *p2;
1452         char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
1453         qboolean fromServer = false;
1454
1455         if (cmd_source == src_command)
1456         {
1457                 if (cls.state == ca_dedicated)
1458                         fromServer = true;
1459                 else
1460                 {
1461                         Cmd_ForwardToServer ();
1462                         return;
1463                 }
1464         }
1465
1466         if (Cmd_Argc () < 2)
1467                 return;
1468
1469         // note this uses the chat prefix \001
1470         if (!fromServer)
1471                 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1472         else if(*(sv_adminnick.string))
1473                 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1474         else
1475                 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1476
1477         p1 = Cmd_Args();
1478         p2 = p1 + strlen(p1);
1479         // remove the target name
1480         while (p1 < p2 && *p1 == ' ')
1481                 p1++;
1482         if(*p1 == '#')
1483         {
1484                 ++p1;
1485                 while (p1 < p2 && *p1 == ' ')
1486                         p1++;
1487                 while (p1 < p2 && isdigit(*p1))
1488                 {
1489                         playernumber = playernumber * 10 + (*p1 - '0');
1490                         p1++;
1491                 }
1492                 --playernumber;
1493         }
1494         else if(*p1 == '"')
1495         {
1496                 ++p1;
1497                 playername_start = p1;
1498                 while (p1 < p2 && *p1 != '"')
1499                         p1++;
1500                 playername_length = p1 - playername_start;
1501                 if(p1 < p2)
1502                         p1++;
1503         }
1504         else
1505         {
1506                 playername_start = p1;
1507                 while (p1 < p2 && *p1 != ' ')
1508                         p1++;
1509                 playername_length = p1 - playername_start;
1510         }
1511         while (p1 < p2 && *p1 == ' ')
1512                 p1++;
1513         if(playername_start)
1514         {
1515                 // set playernumber to the right client
1516                 char namebuf[128];
1517                 if(playername_length >= sizeof(namebuf))
1518                 {
1519                         if (fromServer)
1520                                 Con_Print("Host_Tell: too long player name/ID\n");
1521                         else
1522                                 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1523                         return;
1524                 }
1525                 memcpy(namebuf, playername_start, playername_length);
1526                 namebuf[playername_length] = 0;
1527                 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1528                 {
1529                         if (!svs.clients[playernumber].active)
1530                                 continue;
1531                         if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1532                                 break;
1533                 }
1534         }
1535         if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1536         {
1537                 if (fromServer)
1538                         Con_Print("Host_Tell: invalid player name/ID\n");
1539                 else
1540                         SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1541                 return;
1542         }
1543         // remove trailing newlines
1544         while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1545                 p2--;
1546         // remove quotes if present
1547         if (*p1 == '"')
1548         {
1549                 p1++;
1550                 if (p2[-1] == '"')
1551                         p2--;
1552                 else if (fromServer)
1553                         Con_Print("Host_Tell: missing end quote\n");
1554                 else
1555                         SV_ClientPrint("Host_Tell: missing end quote\n");
1556         }
1557         while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1558                 p2--;
1559         if(p1 == p2)
1560                 return; // empty say
1561         for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1562                 text[j++] = *p1++;
1563         text[j++] = '\n';
1564         text[j++] = 0;
1565
1566         save = host_client;
1567         host_client = svs.clients + playernumber;
1568         SV_ClientPrint(text);
1569         host_client = save;
1570 }
1571
1572
1573 /*
1574 ==================
1575 Host_Color_f
1576 ==================
1577 */
1578 cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1579 static void Host_Color(int changetop, int changebottom)
1580 {
1581         prvm_prog_t *prog = SVVM_prog;
1582         int top, bottom, playercolor;
1583
1584         // get top and bottom either from the provided values or the current values
1585         // (allows changing only top or bottom, or both at once)
1586         top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1587         bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1588
1589         top &= 15;
1590         bottom &= 15;
1591         // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out
1592         //if (top > 13)
1593         //      top = 13;
1594         //if (bottom > 13)
1595         //      bottom = 13;
1596
1597         playercolor = top*16 + bottom;
1598
1599         if (cmd_source == src_command)
1600         {
1601                 Cvar_SetValueQuick(&cl_color, playercolor);
1602                 return;
1603         }
1604
1605         if (cls.protocol == PROTOCOL_QUAKEWORLD)
1606                 return;
1607
1608         if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1609         {
1610                 Con_DPrint("Calling SV_ChangeTeam\n");
1611                 prog->globals.fp[OFS_PARM0] = playercolor;
1612                 PRVM_serverglobalfloat(time) = sv.time;
1613                 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1614                 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1615         }
1616         else
1617         {
1618                 if (host_client->edict)
1619                 {
1620                         PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1621                         PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1622                 }
1623                 host_client->colors = playercolor;
1624                 if (host_client->old_colors != host_client->colors)
1625                 {
1626                         host_client->old_colors = host_client->colors;
1627                         // send notification to all clients
1628                         MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1629                         MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1630                         MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1631                 }
1632         }
1633 }
1634
1635 static void Host_Color_f(void)
1636 {
1637         int             top, bottom;
1638
1639         if (Cmd_Argc() == 1)
1640         {
1641                 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1642                 Con_Print("color <0-15> [0-15]\n");
1643                 return;
1644         }
1645
1646         if (Cmd_Argc() == 2)
1647                 top = bottom = atoi(Cmd_Argv(1));
1648         else
1649         {
1650                 top = atoi(Cmd_Argv(1));
1651                 bottom = atoi(Cmd_Argv(2));
1652         }
1653         Host_Color(top, bottom);
1654 }
1655
1656 static void Host_TopColor_f(void)
1657 {
1658         if (Cmd_Argc() == 1)
1659         {
1660                 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1661                 Con_Print("topcolor <0-15>\n");
1662                 return;
1663         }
1664
1665         Host_Color(atoi(Cmd_Argv(1)), -1);
1666 }
1667
1668 static void Host_BottomColor_f(void)
1669 {
1670         if (Cmd_Argc() == 1)
1671         {
1672                 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1673                 Con_Print("bottomcolor <0-15>\n");
1674                 return;
1675         }
1676
1677         Host_Color(-1, atoi(Cmd_Argv(1)));
1678 }
1679
1680 cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1681 cvar_t cl_rate_burstsize = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate_burstsize", "1024", "internal storage cvar for current rate control burst size (changed by rate_burstsize command)"};
1682 static void Host_Rate_f(void)
1683 {
1684         int rate;
1685
1686         if (Cmd_Argc() != 2)
1687         {
1688                 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1689                 Con_Print("rate <bytespersecond>\n");
1690                 return;
1691         }
1692
1693         rate = atoi(Cmd_Argv(1));
1694
1695         if (cmd_source == src_command)
1696         {
1697                 Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
1698                 return;
1699         }
1700
1701         host_client->rate = rate;
1702 }
1703 static void Host_Rate_BurstSize_f(void)
1704 {
1705         int rate_burstsize;
1706
1707         if (Cmd_Argc() != 2)
1708         {
1709                 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
1710                 Con_Print("rate_burstsize <bytes>\n");
1711                 return;
1712         }
1713
1714         rate_burstsize = atoi(Cmd_Argv(1));
1715
1716         if (cmd_source == src_command)
1717         {
1718                 Cvar_SetValue ("_cl_rate_burstsize", rate_burstsize);
1719                 return;
1720         }
1721
1722         host_client->rate_burstsize = rate_burstsize;
1723 }
1724
1725 /*
1726 ==================
1727 Host_Kill_f
1728 ==================
1729 */
1730 static void Host_Kill_f (void)
1731 {
1732         prvm_prog_t *prog = SVVM_prog;
1733         if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
1734         {
1735                 SV_ClientPrint("Can't suicide -- already dead!\n");
1736                 return;
1737         }
1738
1739         PRVM_serverglobalfloat(time) = sv.time;
1740         PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1741         prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
1742 }
1743
1744
1745 /*
1746 ==================
1747 Host_Pause_f
1748 ==================
1749 */
1750 static void Host_Pause_f (void)
1751 {
1752         void (*print) (const char *fmt, ...);
1753         if (cmd_source == src_command)
1754         {
1755                 // if running a client, try to send over network so the pause is handled by the server
1756                 if (cls.state == ca_connected)
1757                 {
1758                         Cmd_ForwardToServer ();
1759                         return;
1760                 }
1761                 print = Con_Printf;
1762         }
1763         else
1764                 print = SV_ClientPrintf;
1765
1766         if (!pausable.integer)
1767         {
1768                 if (cmd_source == src_client)
1769                 {
1770                         if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
1771                         {
1772                                 print("Pause not allowed.\n");
1773                                 return;
1774                         }
1775                 }
1776         }
1777         
1778         sv.paused ^= 1;
1779         SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1780         // send notification to all clients
1781         MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1782         MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1783 }
1784
1785 /*
1786 ======================
1787 Host_PModel_f
1788 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1789 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1790 ======================
1791 */
1792 cvar_t cl_pmodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_pmodel", "0", "internal storage cvar for current player model number in nehahra (changed by pmodel command)"};
1793 static void Host_PModel_f (void)
1794 {
1795         prvm_prog_t *prog = SVVM_prog;
1796         int i;
1797
1798         if (Cmd_Argc () == 1)
1799         {
1800                 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1801                 return;
1802         }
1803         i = atoi(Cmd_Argv(1));
1804
1805         if (cmd_source == src_command)
1806         {
1807                 if (cl_pmodel.integer == i)
1808                         return;
1809                 Cvar_SetValue ("_cl_pmodel", i);
1810                 if (cls.state == ca_connected)
1811                         Cmd_ForwardToServer ();
1812                 return;
1813         }
1814
1815         PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1816 }
1817
1818 //===========================================================================
1819
1820
1821 /*
1822 ==================
1823 Host_PreSpawn_f
1824 ==================
1825 */
1826 static void Host_PreSpawn_f (void)
1827 {
1828         if (host_client->prespawned)
1829         {
1830                 Con_Print("prespawn not valid -- already prespawned\n");
1831                 return;
1832         }
1833         host_client->prespawned = true;
1834
1835         if (host_client->netconnection)
1836         {
1837                 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1838                 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1839                 MSG_WriteByte (&host_client->netconnection->message, 2);
1840                 host_client->sendsignon = 0;            // enable unlimited sends again
1841         }
1842
1843         // reset the name change timer because the client will send name soon
1844         host_client->nametime = 0;
1845 }
1846
1847 /*
1848 ==================
1849 Host_Spawn_f
1850 ==================
1851 */
1852 static void Host_Spawn_f (void)
1853 {
1854         prvm_prog_t *prog = SVVM_prog;
1855         int i;
1856         client_t *client;
1857         int stats[MAX_CL_STATS];
1858
1859         if (!host_client->prespawned)
1860         {
1861                 Con_Print("Spawn not valid -- not yet prespawned\n");
1862                 return;
1863         }
1864         if (host_client->spawned)
1865         {
1866                 Con_Print("Spawn not valid -- already spawned\n");
1867                 return;
1868         }
1869         host_client->spawned = true;
1870
1871         // reset name change timer again because they might want to change name
1872         // again in the first 5 seconds after connecting
1873         host_client->nametime = 0;
1874
1875         // LordHavoc: moved this above the QC calls at FrikaC's request
1876         // LordHavoc: commented this out
1877         //if (host_client->netconnection)
1878         //      SZ_Clear (&host_client->netconnection->message);
1879
1880         // run the entrance script
1881         if (sv.loadgame)
1882         {
1883                 // loaded games are fully initialized already
1884                 if (PRVM_serverfunction(RestoreGame))
1885                 {
1886                         Con_DPrint("Calling RestoreGame\n");
1887                         PRVM_serverglobalfloat(time) = sv.time;
1888                         PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1889                         prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1890                 }
1891         }
1892         else
1893         {
1894                 //Con_Printf("Host_Spawn_f: host_client->edict->netname = %s, host_client->edict->netname = %s, host_client->name = %s\n", PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), host_client->name);
1895
1896                 // copy spawn parms out of the client_t
1897                 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1898                         (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1899
1900                 // call the spawn function
1901                 host_client->clientconnectcalled = true;
1902                 PRVM_serverglobalfloat(time) = sv.time;
1903                 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1904                 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1905
1906                 if (cls.state == ca_dedicated)
1907                         Con_Printf("%s connected\n", host_client->name);
1908
1909                 PRVM_serverglobalfloat(time) = sv.time;
1910                 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1911         }
1912
1913         if (!host_client->netconnection)
1914                 return;
1915
1916         // send time of update
1917         MSG_WriteByte (&host_client->netconnection->message, svc_time);
1918         MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1919
1920         // send all current names, colors, and frag counts
1921         for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1922         {
1923                 if (!client->active)
1924                         continue;
1925                 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1926                 MSG_WriteByte (&host_client->netconnection->message, i);
1927                 MSG_WriteString (&host_client->netconnection->message, client->name);
1928                 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1929                 MSG_WriteByte (&host_client->netconnection->message, i);
1930                 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1931                 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1932                 MSG_WriteByte (&host_client->netconnection->message, i);
1933                 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1934         }
1935
1936         // send all current light styles
1937         for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1938         {
1939                 if (sv.lightstyles[i][0])
1940                 {
1941                         MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1942                         MSG_WriteByte (&host_client->netconnection->message, (char)i);
1943                         MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1944                 }
1945         }
1946
1947         // send some stats
1948         MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1949         MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1950         MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1951
1952         MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1953         MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1954         MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1955
1956         MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1957         MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1958         MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1959
1960         MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1961         MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1962         MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1963
1964         // send a fixangle
1965         // Never send a roll angle, because savegames can catch the server
1966         // in a state where it is expecting the client to correct the angle
1967         // and it won't happen if the game was just loaded, so you wind up
1968         // with a permanent head tilt
1969         if (sv.loadgame)
1970         {
1971                 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1972                 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1973                 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1974                 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1975         }
1976         else
1977         {
1978                 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1979                 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1980                 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
1981                 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1982         }
1983
1984         SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
1985
1986         MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1987         MSG_WriteByte (&host_client->netconnection->message, 3);
1988 }
1989
1990 /*
1991 ==================
1992 Host_Begin_f
1993 ==================
1994 */
1995 static void Host_Begin_f (void)
1996 {
1997         if (!host_client->spawned)
1998         {
1999                 Con_Print("Begin not valid -- not yet spawned\n");
2000                 return;
2001         }
2002         if (host_client->begun)
2003         {
2004                 Con_Print("Begin not valid -- already begun\n");
2005                 return;
2006         }
2007         host_client->begun = true;
2008
2009         // LordHavoc: note: this code also exists in SV_DropClient
2010         if (sv.loadgame)
2011         {
2012                 int i;
2013                 for (i = 0;i < svs.maxclients;i++)
2014                         if (svs.clients[i].active && !svs.clients[i].spawned)
2015                                 break;
2016                 if (i == svs.maxclients)
2017                 {
2018                         Con_Printf("Loaded game, everyone rejoined - unpausing\n");
2019                         sv.paused = sv.loadgame = false; // we're basically done with loading now
2020                 }
2021         }
2022 }
2023
2024 //===========================================================================
2025
2026
2027 /*
2028 ==================
2029 Host_Kick_f
2030
2031 Kicks a user off of the server
2032 ==================
2033 */
2034 static void Host_Kick_f (void)
2035 {
2036         const char *who;
2037         const char *message = NULL;
2038         client_t *save;
2039         int i;
2040         qboolean byNumber = false;
2041
2042         if (!sv.active)
2043                 return;
2044
2045         save = host_client;
2046
2047         if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
2048         {
2049                 i = (int)(atof(Cmd_Argv(2)) - 1);
2050                 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
2051                         return;
2052                 byNumber = true;
2053         }
2054         else
2055         {
2056                 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
2057                 {
2058                         if (!host_client->active)
2059                                 continue;
2060                         if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
2061                                 break;
2062                 }
2063         }
2064
2065         if (i < svs.maxclients)
2066         {
2067                 if (cmd_source == src_command)
2068                 {
2069                         if (cls.state == ca_dedicated)
2070                                 who = "Console";
2071                         else
2072                                 who = cl_name.string;
2073                 }
2074                 else
2075                         who = save->name;
2076
2077                 // can't kick yourself!
2078                 if (host_client == save)
2079                         return;
2080
2081                 if (Cmd_Argc() > 2)
2082                 {
2083                         message = Cmd_Args();
2084                         COM_ParseToken_Simple(&message, false, false, true);
2085                         if (byNumber)
2086                         {
2087                                 message++;                                                      // skip the #
2088                                 while (*message == ' ')                         // skip white space
2089                                         message++;
2090                                 message += strlen(Cmd_Argv(2)); // skip the number
2091                         }
2092                         while (*message && *message == ' ')
2093                                 message++;
2094                 }
2095                 if (message)
2096                         SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2097                 else
2098                         SV_ClientPrintf("Kicked by %s\n", who);
2099                 SV_DropClient (false); // kicked
2100         }
2101
2102         host_client = save;
2103 }
2104
2105 /*
2106 ===============================================================================
2107
2108 DEBUGGING TOOLS
2109
2110 ===============================================================================
2111 */
2112
2113 /*
2114 ==================
2115 Host_Give_f
2116 ==================
2117 */
2118 static void Host_Give_f (void)
2119 {
2120         prvm_prog_t *prog = SVVM_prog;
2121         const char *t;
2122         int v;
2123
2124         if (!allowcheats)
2125         {
2126                 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2127                 return;
2128         }
2129
2130         t = Cmd_Argv(1);
2131         v = atoi (Cmd_Argv(2));
2132
2133         switch (t[0])
2134         {
2135         case '0':
2136         case '1':
2137         case '2':
2138         case '3':
2139         case '4':
2140         case '5':
2141         case '6':
2142         case '7':
2143         case '8':
2144         case '9':
2145                 // MED 01/04/97 added hipnotic give stuff
2146                 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
2147                 {
2148                         if (t[0] == '6')
2149                         {
2150                                 if (t[1] == 'a')
2151                                         PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2152                                 else
2153                                         PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2154                         }
2155                         else if (t[0] == '9')
2156                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2157                         else if (t[0] == '0')
2158                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2159                         else if (t[0] >= '2')
2160                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2161                 }
2162                 else
2163                 {
2164                         if (t[0] >= '2')
2165                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2166                 }
2167                 break;
2168
2169         case 's':
2170                 if (gamemode == GAME_ROGUE)
2171                         PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2172
2173                 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2174                 break;
2175         case 'n':
2176                 if (gamemode == GAME_ROGUE)
2177                 {
2178                         PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2179                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2180                                 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2181                 }
2182                 else
2183                 {
2184                         PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2185                 }
2186                 break;
2187         case 'l':
2188                 if (gamemode == GAME_ROGUE)
2189                 {
2190                         PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2191                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2192                                 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2193                 }
2194                 break;
2195         case 'r':
2196                 if (gamemode == GAME_ROGUE)
2197                 {
2198                         PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2199                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2200                                 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2201                 }
2202                 else
2203                 {
2204                         PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2205                 }
2206                 break;
2207         case 'm':
2208                 if (gamemode == GAME_ROGUE)
2209                 {
2210                         PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2211                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2212                                 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2213                 }
2214                 break;
2215         case 'h':
2216                 PRVM_serveredictfloat(host_client->edict, health) = v;
2217                 break;
2218         case 'c':
2219                 if (gamemode == GAME_ROGUE)
2220                 {
2221                         PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2222                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2223                                 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2224                 }
2225                 else
2226                 {
2227                         PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2228                 }
2229                 break;
2230         case 'p':
2231                 if (gamemode == GAME_ROGUE)
2232                 {
2233                         PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2234                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2235                                 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2236                 }
2237                 break;
2238         }
2239 }
2240
2241 static prvm_edict_t     *FindViewthing(prvm_prog_t *prog)
2242 {
2243         int             i;
2244         prvm_edict_t    *e;
2245
2246         for (i=0 ; i<prog->num_edicts ; i++)
2247         {
2248                 e = PRVM_EDICT_NUM(i);
2249                 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2250                         return e;
2251         }
2252         Con_Print("No viewthing on map\n");
2253         return NULL;
2254 }
2255
2256 /*
2257 ==================
2258 Host_Viewmodel_f
2259 ==================
2260 */
2261 static void Host_Viewmodel_f (void)
2262 {
2263         prvm_prog_t *prog = SVVM_prog;
2264         prvm_edict_t    *e;
2265         dp_model_t      *m;
2266
2267         if (!sv.active)
2268                 return;
2269
2270         e = FindViewthing(prog);
2271         if (e)
2272         {
2273                 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2274                 if (m && m->loaded && m->Draw)
2275                 {
2276                         PRVM_serveredictfloat(e, frame) = 0;
2277                         cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2278                 }
2279                 else
2280                         Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2281         }
2282 }
2283
2284 /*
2285 ==================
2286 Host_Viewframe_f
2287 ==================
2288 */
2289 static void Host_Viewframe_f (void)
2290 {
2291         prvm_prog_t *prog = SVVM_prog;
2292         prvm_edict_t    *e;
2293         int             f;
2294         dp_model_t      *m;
2295
2296         if (!sv.active)
2297                 return;
2298
2299         e = FindViewthing(prog);
2300         if (e)
2301         {
2302                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2303
2304                 f = atoi(Cmd_Argv(1));
2305                 if (f >= m->numframes)
2306                         f = m->numframes-1;
2307
2308                 PRVM_serveredictfloat(e, frame) = f;
2309         }
2310 }
2311
2312
2313 static void PrintFrameName (dp_model_t *m, int frame)
2314 {
2315         if (m->animscenes)
2316                 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2317         else
2318                 Con_Printf("frame %i\n", frame);
2319 }
2320
2321 /*
2322 ==================
2323 Host_Viewnext_f
2324 ==================
2325 */
2326 static void Host_Viewnext_f (void)
2327 {
2328         prvm_prog_t *prog = SVVM_prog;
2329         prvm_edict_t    *e;
2330         dp_model_t      *m;
2331
2332         if (!sv.active)
2333                 return;
2334
2335         e = FindViewthing(prog);
2336         if (e)
2337         {
2338                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2339
2340                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2341                 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2342                         PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2343
2344                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2345         }
2346 }
2347
2348 /*
2349 ==================
2350 Host_Viewprev_f
2351 ==================
2352 */
2353 static void Host_Viewprev_f (void)
2354 {
2355         prvm_prog_t *prog = SVVM_prog;
2356         prvm_edict_t    *e;
2357         dp_model_t      *m;
2358
2359         if (!sv.active)
2360                 return;
2361
2362         e = FindViewthing(prog);
2363         if (e)
2364         {
2365                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2366
2367                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2368                 if (PRVM_serveredictfloat(e, frame) < 0)
2369                         PRVM_serveredictfloat(e, frame) = 0;
2370
2371                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2372         }
2373 }
2374
2375 /*
2376 ===============================================================================
2377
2378 DEMO LOOP CONTROL
2379
2380 ===============================================================================
2381 */
2382
2383
2384 /*
2385 ==================
2386 Host_Startdemos_f
2387 ==================
2388 */
2389 static void Host_Startdemos_f (void)
2390 {
2391         int             i, c;
2392
2393         if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2394                 return;
2395
2396         c = Cmd_Argc() - 1;
2397         if (c > MAX_DEMOS)
2398         {
2399                 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2400                 c = MAX_DEMOS;
2401         }
2402         Con_DPrintf("%i demo(s) in loop\n", c);
2403
2404         for (i=1 ; i<c+1 ; i++)
2405                 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2406
2407         // LordHavoc: clear the remaining slots
2408         for (;i <= MAX_DEMOS;i++)
2409                 cls.demos[i-1][0] = 0;
2410
2411         if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2412         {
2413                 cls.demonum = 0;
2414                 CL_NextDemo ();
2415         }
2416         else
2417                 cls.demonum = -1;
2418 }
2419
2420
2421 /*
2422 ==================
2423 Host_Demos_f
2424
2425 Return to looping demos
2426 ==================
2427 */
2428 static void Host_Demos_f (void)
2429 {
2430         if (cls.state == ca_dedicated)
2431                 return;
2432         if (cls.demonum == -1)
2433                 cls.demonum = 1;
2434         CL_Disconnect_f ();
2435         CL_NextDemo ();
2436 }
2437
2438 /*
2439 ==================
2440 Host_Stopdemo_f
2441
2442 Return to looping demos
2443 ==================
2444 */
2445 static void Host_Stopdemo_f (void)
2446 {
2447         if (!cls.demoplayback)
2448                 return;
2449         CL_Disconnect ();
2450         Host_ShutdownServer ();
2451 }
2452
2453 static void Host_SendCvar_f (void)
2454 {
2455         int             i;
2456         cvar_t  *c;
2457         const char *cvarname;
2458         client_t *old;
2459         char vabuf[1024];
2460
2461         if(Cmd_Argc() != 2)
2462                 return;
2463         cvarname = Cmd_Argv(1);
2464         if (cls.state == ca_connected)
2465         {
2466                 c = Cvar_FindVar(cvarname);
2467                 // LordHavoc: if there is no such cvar or if it is private, send a
2468                 // reply indicating that it has no value
2469                 if(!c || (c->flags & CVAR_PRIVATE))
2470                         Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2471                 else
2472                         Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2473                 return;
2474         }
2475         if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2476                 return;
2477
2478         old = host_client;
2479         if (cls.state != ca_dedicated)
2480                 i = 1;
2481         else
2482                 i = 0;
2483         for(;i<svs.maxclients;i++)
2484                 if(svs.clients[i].active && svs.clients[i].netconnection)
2485                 {
2486                         host_client = &svs.clients[i];
2487                         Host_ClientCommands("sendcvar %s\n", cvarname);
2488                 }
2489         host_client = old;
2490 }
2491
2492 static void MaxPlayers_f(void)
2493 {
2494         int n;
2495
2496         if (Cmd_Argc() != 2)
2497         {
2498                 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2499                 return;
2500         }
2501
2502         if (sv.active)
2503         {
2504                 Con_Print("maxplayers can not be changed while a server is running.\n");
2505                 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2506         }
2507
2508         n = atoi(Cmd_Argv(1));
2509         n = bound(1, n, MAX_SCOREBOARD);
2510         Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2511
2512         svs.maxclients_next = n;
2513         if (n == 1)
2514                 Cvar_Set ("deathmatch", "0");
2515         else
2516                 Cvar_Set ("deathmatch", "1");
2517 }
2518
2519 /*
2520 =====================
2521 Host_PQRcon_f
2522
2523 ProQuake rcon support
2524 =====================
2525 */
2526 static void Host_PQRcon_f (void)
2527 {
2528         int n;
2529         const char *e;
2530         lhnetaddress_t to;
2531         lhnetsocket_t *mysocket;
2532         char peer_address[64];
2533
2534         if (Cmd_Argc() == 1)
2535         {
2536                 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2537                 return;
2538         }
2539
2540         if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2541         {
2542                 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2543                 return;
2544         }
2545
2546         e = strchr(rcon_password.string, ' ');
2547         n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2548
2549         if (cls.netcon)
2550         {
2551                 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2552         }
2553         else
2554         {
2555                 if (!rcon_address.string[0])
2556                 {
2557                         Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2558                         return;
2559                 }
2560                 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2561         }
2562         LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2563         mysocket = NetConn_ChooseClientSocketForAddress(&to);
2564         if (mysocket)
2565         {
2566                 sizebuf_t buf;
2567                 unsigned char bufdata[64];
2568                 buf.data = bufdata;
2569                 SZ_Clear(&buf);
2570                 MSG_WriteLong(&buf, 0);
2571                 MSG_WriteByte(&buf, CCREQ_RCON);
2572                 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2573                 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2574                 MSG_WriteString(&buf, Cmd_Args());
2575                 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2576                 NetConn_Write(mysocket, buf.data, buf.cursize, &to);
2577                 SZ_Clear(&buf);
2578         }
2579 }
2580
2581 //=============================================================================
2582
2583 // QuakeWorld commands
2584
2585 /*
2586 =====================
2587 Host_Rcon_f
2588
2589   Send the rest of the command line over as
2590   an unconnected command.
2591 =====================
2592 */
2593 static void Host_Rcon_f (void) // credit: taken from QuakeWorld
2594 {
2595         int i, n;
2596         const char *e;
2597         lhnetaddress_t to;
2598         lhnetsocket_t *mysocket;
2599         char vabuf[1024];
2600
2601         if (Cmd_Argc() == 1)
2602         {
2603                 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2604                 return;
2605         }
2606
2607         if (!rcon_password.string || !rcon_password.string[0])
2608         {
2609                 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2610                 return;
2611         }
2612
2613         e = strchr(rcon_password.string, ' ');
2614         n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2615
2616         if (cls.netcon)
2617                 to = cls.netcon->peeraddress;
2618         else
2619         {
2620                 if (!rcon_address.string[0])
2621                 {
2622                         Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2623                         return;
2624                 }
2625                 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2626         }
2627         mysocket = NetConn_ChooseClientSocketForAddress(&to);
2628         if (mysocket && Cmd_Args()[0])
2629         {
2630                 // simply put together the rcon packet and send it
2631                 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2632                 {
2633                         if(cls.rcon_commands[cls.rcon_ringpos][0])
2634                         {
2635                                 char s[128];
2636                                 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2637                                 Con_Printf("rcon to %s (for command %s) failed: too many buffered commands (possibly increase MAX_RCONS)\n", s, cls.rcon_commands[cls.rcon_ringpos]);
2638                                 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2639                                 --cls.rcon_trying;
2640                         }
2641                         for (i = 0;i < MAX_RCONS;i++)
2642                                 if(cls.rcon_commands[i][0])
2643                                         if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2644                                                 break;
2645                         ++cls.rcon_trying;
2646                         if(i >= MAX_RCONS)
2647                                 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2648                         strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2649                         cls.rcon_addresses[cls.rcon_ringpos] = to;
2650                         cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2651                         cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2652                 }
2653                 else if(rcon_secure.integer > 0)
2654                 {
2655                         char buf[1500];
2656                         char argbuf[1500];
2657                         dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2658                         memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2659                         if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
2660                         {
2661                                 buf[40] = ' ';
2662                                 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2663                                 NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
2664                         }
2665                 }
2666                 else
2667                 {
2668                         NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2669                 }
2670         }
2671 }
2672
2673 /*
2674 ====================
2675 Host_User_f
2676
2677 user <name or userid>
2678
2679 Dump userdata / masterdata for a user
2680 ====================
2681 */
2682 static void Host_User_f (void) // credit: taken from QuakeWorld
2683 {
2684         int             uid;
2685         int             i;
2686
2687         if (Cmd_Argc() != 2)
2688         {
2689                 Con_Printf ("Usage: user <username / userid>\n");
2690                 return;
2691         }
2692
2693         uid = atoi(Cmd_Argv(1));
2694
2695         for (i = 0;i < cl.maxclients;i++)
2696         {
2697                 if (!cl.scores[i].name[0])
2698                         continue;
2699                 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2700                 {
2701                         InfoString_Print(cl.scores[i].qw_userinfo);
2702                         return;
2703                 }
2704         }
2705         Con_Printf ("User not in server.\n");
2706 }
2707
2708 /*
2709 ====================
2710 Host_Users_f
2711
2712 Dump userids for all current players
2713 ====================
2714 */
2715 static void Host_Users_f (void) // credit: taken from QuakeWorld
2716 {
2717         int             i;
2718         int             c;
2719
2720         c = 0;
2721         Con_Printf ("userid frags name\n");
2722         Con_Printf ("------ ----- ----\n");
2723         for (i = 0;i < cl.maxclients;i++)
2724         {
2725                 if (cl.scores[i].name[0])
2726                 {
2727                         Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2728                         c++;
2729                 }
2730         }
2731
2732         Con_Printf ("%i total users\n", c);
2733 }
2734
2735 /*
2736 ==================
2737 Host_FullServerinfo_f
2738
2739 Sent by server when serverinfo changes
2740 ==================
2741 */
2742 // TODO: shouldn't this be a cvar instead?
2743 static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2744 {
2745         char temp[512];
2746         if (Cmd_Argc() != 2)
2747         {
2748                 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2749                 return;
2750         }
2751
2752         strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2753         InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2754         cl.qw_teamplay = atoi(temp);
2755 }
2756
2757 /*
2758 ==================
2759 Host_FullInfo_f
2760
2761 Allow clients to change userinfo
2762 ==================
2763 Casey was here :)
2764 */
2765 static void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2766 {
2767         char key[512];
2768         char value[512];
2769         char *o;
2770         const char *s;
2771
2772         if (Cmd_Argc() != 2)
2773         {
2774                 Con_Printf ("fullinfo <complete info string>\n");
2775                 return;
2776         }
2777
2778         s = Cmd_Argv(1);
2779         if (*s == '\\')
2780                 s++;
2781         while (*s)
2782         {
2783                 o = key;
2784                 while (*s && *s != '\\')
2785                         *o++ = *s++;
2786                 *o = 0;
2787
2788                 if (!*s)
2789                 {
2790                         Con_Printf ("MISSING VALUE\n");
2791                         return;
2792                 }
2793
2794                 o = value;
2795                 s++;
2796                 while (*s && *s != '\\')
2797                         *o++ = *s++;
2798                 *o = 0;
2799
2800                 if (*s)
2801                         s++;
2802
2803                 CL_SetInfo(key, value, false, false, false, false);
2804         }
2805 }
2806
2807 /*
2808 ==================
2809 CL_SetInfo_f
2810
2811 Allow clients to change userinfo
2812 ==================
2813 */
2814 static void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2815 {
2816         if (Cmd_Argc() == 1)
2817         {
2818                 InfoString_Print(cls.userinfo);
2819                 return;
2820         }
2821         if (Cmd_Argc() != 3)
2822         {
2823                 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2824                 return;
2825         }
2826         CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2827 }
2828
2829 /*
2830 ====================
2831 Host_Packet_f
2832
2833 packet <destination> <contents>
2834
2835 Contents allows \n escape character
2836 ====================
2837 */
2838 static void Host_Packet_f (void) // credit: taken from QuakeWorld
2839 {
2840         char send[2048];
2841         int i, l;
2842         const char *in;
2843         char *out;
2844         lhnetaddress_t address;
2845         lhnetsocket_t *mysocket;
2846
2847         if (Cmd_Argc() != 3)
2848         {
2849                 Con_Printf ("packet <destination> <contents>\n");
2850                 return;
2851         }
2852
2853         if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2854         {
2855                 Con_Printf ("Bad address\n");
2856                 return;
2857         }
2858
2859         in = Cmd_Argv(2);
2860         out = send+4;
2861         send[0] = send[1] = send[2] = send[3] = -1;
2862
2863         l = (int)strlen (in);
2864         for (i=0 ; i<l ; i++)
2865         {
2866                 if (out >= send + sizeof(send) - 1)
2867                         break;
2868                 if (in[i] == '\\' && in[i+1] == 'n')
2869                 {
2870                         *out++ = '\n';
2871                         i++;
2872                 }
2873                 else if (in[i] == '\\' && in[i+1] == '0')
2874                 {
2875                         *out++ = '\0';
2876                         i++;
2877                 }
2878                 else if (in[i] == '\\' && in[i+1] == 't')
2879                 {
2880                         *out++ = '\t';
2881                         i++;
2882                 }
2883                 else if (in[i] == '\\' && in[i+1] == 'r')
2884                 {
2885                         *out++ = '\r';
2886                         i++;
2887                 }
2888                 else if (in[i] == '\\' && in[i+1] == '"')
2889                 {
2890                         *out++ = '\"';
2891                         i++;
2892                 }
2893                 else
2894                         *out++ = in[i];
2895         }
2896
2897         mysocket = NetConn_ChooseClientSocketForAddress(&address);
2898         if (!mysocket)
2899                 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2900         if (mysocket)
2901                 NetConn_Write(mysocket, send, out - send, &address);
2902 }
2903
2904 /*
2905 ====================
2906 Host_Pings_f
2907
2908 Send back ping and packet loss update for all current players to this player
2909 ====================
2910 */
2911 void Host_Pings_f (void)
2912 {
2913         int             i, j, ping, packetloss, movementloss;
2914         char temp[128];
2915
2916         if (!host_client->netconnection)
2917                 return;
2918
2919         if (sv.protocol != PROTOCOL_QUAKEWORLD)
2920         {
2921                 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2922                 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2923         }
2924         for (i = 0;i < svs.maxclients;i++)
2925         {
2926                 packetloss = 0;
2927                 movementloss = 0;
2928                 if (svs.clients[i].netconnection)
2929                 {
2930                         for (j = 0;j < NETGRAPH_PACKETS;j++)
2931                                 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2932                                         packetloss++;
2933                         for (j = 0;j < NETGRAPH_PACKETS;j++)
2934                                 if (svs.clients[i].movement_count[j] < 0)
2935                                         movementloss++;
2936                 }
2937                 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2938                 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2939                 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2940                 ping = bound(0, ping, 9999);
2941                 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2942                 {
2943                         // send qw_svc_updateping and qw_svc_updatepl messages
2944                         MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2945                         MSG_WriteShort(&host_client->netconnection->message, ping);
2946                         MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2947                         MSG_WriteByte(&host_client->netconnection->message, packetloss);
2948                 }
2949                 else
2950                 {
2951                         // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2952                         if(movementloss)
2953                                 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2954                         else
2955                                 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2956                         MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2957                 }
2958         }
2959         if (sv.protocol != PROTOCOL_QUAKEWORLD)
2960                 MSG_WriteString(&host_client->netconnection->message, "\n");
2961 }
2962
2963 static void Host_PingPLReport_f(void)
2964 {
2965         char *errbyte;
2966         int i;
2967         int l = Cmd_Argc();
2968         if (l > cl.maxclients)
2969                 l = cl.maxclients;
2970         for (i = 0;i < l;i++)
2971         {
2972                 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2973                 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2974                 if(errbyte && *errbyte == ',')
2975                         cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2976                 else
2977                         cl.scores[i].qw_movementloss = 0;
2978         }
2979 }
2980
2981 //=============================================================================
2982
2983 /*
2984 ==================
2985 Host_InitCommands
2986 ==================
2987 */
2988 void Host_InitCommands (void)
2989 {
2990         dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
2991
2992         Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
2993         Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
2994         Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2995         Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2996         Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2997         Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2998         Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2999         Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
3000         Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
3001         Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
3002         Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
3003         Cmd_AddCommand ("reconnect", Host_Reconnect_f, "reconnect to the last server you were on, or resets a quakeworld connection (do not use if currently playing on a netquake server)");
3004         Cmd_AddCommand ("version", Host_Version_f, "print engine version");
3005         Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
3006         Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
3007         Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
3008         Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
3009         Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)");
3010         Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
3011         Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
3012         Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
3013         Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
3014
3015         Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
3016         Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
3017         Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
3018
3019         Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
3020         Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
3021         Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
3022         Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
3023
3024         Cvar_RegisterVariable (&cl_name);
3025         Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
3026         Cvar_RegisterVariable (&cl_color);
3027         Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
3028         Cvar_RegisterVariable (&cl_rate);
3029         Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
3030         Cvar_RegisterVariable (&cl_rate_burstsize);
3031         Cmd_AddCommand_WithClientCommand ("rate_burstsize", Host_Rate_BurstSize_f, Host_Rate_BurstSize_f, "change your network connection speed");
3032         Cvar_RegisterVariable (&cl_pmodel);
3033         Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
3034
3035         // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
3036         Cvar_RegisterVariable (&cl_playermodel);
3037         Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
3038         Cvar_RegisterVariable (&cl_playerskin);
3039         Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
3040
3041         Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
3042         Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
3043         Cmd_AddCommand_WithClientCommand ("begin", NULL, Host_Begin_f, "signon 3 (client asks server to start sending entities, and will go to signon 4 (playing) when the first entity update is received)");
3044         Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
3045
3046         Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
3047
3048         Cvar_RegisterVariable (&rcon_password);
3049         Cvar_RegisterVariable (&rcon_address);
3050         Cvar_RegisterVariable (&rcon_secure);
3051         Cvar_RegisterVariable (&rcon_secure_challengetimeout);
3052         Cmd_AddCommand ("rcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); note: if rcon_secure is set, client and server clocks must be synced e.g. via NTP");
3053         Cmd_AddCommand ("srcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); this always works as if rcon_secure is set; note: client and server clocks must be synced e.g. via NTP");
3054         Cmd_AddCommand ("pqrcon", Host_PQRcon_f, "sends a command to a proquake server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)");
3055         Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
3056         Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
3057         Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
3058         Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
3059         Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
3060         Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
3061         Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
3062         Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
3063
3064         Cmd_AddCommand_WithClientCommand ("pings", NULL, Host_Pings_f, "command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers)");
3065         Cmd_AddCommand ("pingplreport", Host_PingPLReport_f, "command sent by server containing client ping and packet loss values for scoreboard, triggered by pings command from client (not used by QW servers)");
3066
3067         Cmd_AddCommand ("fixtrans", Image_FixTransparentPixels_f, "change alpha-zero pixels in an image file to sensible values, and write out a new TGA (warning: SLOW)");
3068         Cvar_RegisterVariable (&r_fixtrans_auto);
3069
3070         Cvar_RegisterVariable (&team);
3071         Cvar_RegisterVariable (&skin);
3072         Cvar_RegisterVariable (&noaim);
3073
3074         Cvar_RegisterVariable(&sv_cheats);
3075         Cvar_RegisterVariable(&sv_adminnick);
3076         Cvar_RegisterVariable(&sv_status_privacy);
3077         Cvar_RegisterVariable(&sv_status_show_qcstatus);
3078         Cvar_RegisterVariable(&sv_namechangetimer);
3079 }
3080
3081 void Host_NoOperation_f(void)
3082 {
3083 }