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