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