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