]> git.xonotic.org Git - xonotic/darkplaces.git/blob - host_cmd.c
Change prefix of cmds in host_cmd.c to match the side of the engine they belong
[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 SV_Status_f
67 ==================
68 */
69 static void SV_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 SV_God_f
205
206 Sets client to godmode
207 ==================
208 */
209 static void SV_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 SV_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 SV_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 SV_Fly_f
254
255 Sets client to flymode
256 ==================
257 */
258 static void SV_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 SV_Ping_f
278
279 ==================
280 */
281 static void SV_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 SV_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 SV_Map_f
348
349 handle a
350 map <servername>
351 command from the console.  Active clients are kicked off.
352 ======================
353 */
354 static void SV_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 SV_Changelevel_f
398
399 Goes to a new map, taking all clients along
400 ==================
401 */
402 static void SV_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                 SV_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 SV_Restart_f
434
435 Restarts the current server for a dead player
436 ==================
437 */
438 static void SV_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 CL_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 CL_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 CL_Connect_f
525
526 User command to connect to server
527 =====================
528 */
529 static void CL_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 SV_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 SV_Savegame_f
701 ===============
702 */
703 static void SV_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         SV_Savegame_to(prog, name);
751 }
752
753
754 /*
755 ===============
756 SV_Loadgame_f
757 ===============
758 */
759
760 static void SV_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("SV_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("SV_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("SV_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("SV_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("SV_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("SV_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("SV_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("SV_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("SV_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("SV_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("SV_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("SV_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 CL_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 CL_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 CL_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 CL_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 CL_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 CL_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 SV_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 static void SV_Say_f(cmd_state_t *cmd)
1430 {
1431         SV_Say(cmd, false);
1432 }
1433
1434 static void SV_Say_Team_f(cmd_state_t *cmd)
1435 {
1436         SV_Say(cmd, true);
1437 }
1438
1439 static void SV_Tell_f(cmd_state_t *cmd)
1440 {
1441         const char *playername_start = NULL;
1442         size_t playername_length = 0;
1443         int playernumber = 0;
1444         client_t *save;
1445         int j;
1446         const char *p1, *p2;
1447         char text[MAX_INPUTLINE]; // LadyHavoc: FIXME: temporary buffer overflow fix (was 64)
1448         qboolean fromServer = false;
1449
1450         if (cmd->source == src_command)
1451         {
1452                 if (cls.state == ca_dedicated)
1453                         fromServer = true;
1454                 else
1455                 {
1456                         Cmd_ForwardToServer_f(cmd);
1457                         return;
1458                 }
1459         }
1460
1461         if (Cmd_Argc (cmd) < 2)
1462                 return;
1463
1464         // note this uses the chat prefix \001
1465         if (!fromServer)
1466                 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1467         else if(*(sv_adminnick.string))
1468                 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1469         else
1470                 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1471
1472         p1 = Cmd_Args(cmd);
1473         p2 = p1 + strlen(p1);
1474         // remove the target name
1475         while (p1 < p2 && *p1 == ' ')
1476                 p1++;
1477         if(*p1 == '#')
1478         {
1479                 ++p1;
1480                 while (p1 < p2 && *p1 == ' ')
1481                         p1++;
1482                 while (p1 < p2 && isdigit(*p1))
1483                 {
1484                         playernumber = playernumber * 10 + (*p1 - '0');
1485                         p1++;
1486                 }
1487                 --playernumber;
1488         }
1489         else if(*p1 == '"')
1490         {
1491                 ++p1;
1492                 playername_start = p1;
1493                 while (p1 < p2 && *p1 != '"')
1494                         p1++;
1495                 playername_length = p1 - playername_start;
1496                 if(p1 < p2)
1497                         p1++;
1498         }
1499         else
1500         {
1501                 playername_start = p1;
1502                 while (p1 < p2 && *p1 != ' ')
1503                         p1++;
1504                 playername_length = p1 - playername_start;
1505         }
1506         while (p1 < p2 && *p1 == ' ')
1507                 p1++;
1508         if(playername_start)
1509         {
1510                 // set playernumber to the right client
1511                 char namebuf[128];
1512                 if(playername_length >= sizeof(namebuf))
1513                 {
1514                         if (fromServer)
1515                                 Con_Print("Host_Tell: too long player name/ID\n");
1516                         else
1517                                 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1518                         return;
1519                 }
1520                 memcpy(namebuf, playername_start, playername_length);
1521                 namebuf[playername_length] = 0;
1522                 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1523                 {
1524                         if (!svs.clients[playernumber].active)
1525                                 continue;
1526                         if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1527                                 break;
1528                 }
1529         }
1530         if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1531         {
1532                 if (fromServer)
1533                         Con_Print("Host_Tell: invalid player name/ID\n");
1534                 else
1535                         SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1536                 return;
1537         }
1538         // remove trailing newlines
1539         while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1540                 p2--;
1541         // remove quotes if present
1542         if (*p1 == '"')
1543         {
1544                 p1++;
1545                 if (p2[-1] == '"')
1546                         p2--;
1547                 else if (fromServer)
1548                         Con_Print("Host_Tell: missing end quote\n");
1549                 else
1550                         SV_ClientPrint("Host_Tell: missing end quote\n");
1551         }
1552         while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1553                 p2--;
1554         if(p1 == p2)
1555                 return; // empty say
1556         for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1557                 text[j++] = *p1++;
1558         text[j++] = '\n';
1559         text[j++] = 0;
1560
1561         save = host_client;
1562         host_client = svs.clients + playernumber;
1563         SV_ClientPrint(text);
1564         host_client = save;
1565 }
1566
1567 /*
1568 ==================
1569 CL_Color_f
1570 ==================
1571 */
1572 cvar_t cl_color = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1573 static void CL_Color(cmd_state_t *cmd, int changetop, int changebottom)
1574 {
1575         prvm_prog_t *prog = SVVM_prog;
1576         int top, bottom, playercolor;
1577
1578         // get top and bottom either from the provided values or the current values
1579         // (allows changing only top or bottom, or both at once)
1580         top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1581         bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1582
1583         top &= 15;
1584         bottom &= 15;
1585         // LadyHavoc: allowing skin colormaps 14 and 15 by commenting this out
1586         //if (top > 13)
1587         //      top = 13;
1588         //if (bottom > 13)
1589         //      bottom = 13;
1590
1591         playercolor = top*16 + bottom;
1592
1593         if (cmd->source == src_command)
1594         {
1595                 Cvar_SetValueQuick(&cl_color, playercolor);
1596                 return;
1597         }
1598
1599         if (cls.protocol == PROTOCOL_QUAKEWORLD)
1600                 return;
1601
1602         if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1603         {
1604                 Con_DPrint("Calling SV_ChangeTeam\n");
1605                 prog->globals.fp[OFS_PARM0] = playercolor;
1606                 PRVM_serverglobalfloat(time) = sv.time;
1607                 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1608                 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1609         }
1610         else
1611         {
1612                 if (host_client->edict)
1613                 {
1614                         PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1615                         PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1616                 }
1617                 host_client->colors = playercolor;
1618                 if (host_client->old_colors != host_client->colors)
1619                 {
1620                         host_client->old_colors = host_client->colors;
1621                         // send notification to all clients
1622                         MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1623                         MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1624                         MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1625                 }
1626         }
1627 }
1628
1629 static void CL_Color_f(cmd_state_t *cmd)
1630 {
1631         int             top, bottom;
1632
1633         if (Cmd_Argc(cmd) == 1)
1634         {
1635                 if (cmd->source == src_command)
1636                 {
1637                         Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1638                         Con_Print("color <0-15> [0-15]\n");
1639                 }
1640                 return;
1641         }
1642
1643         if (Cmd_Argc(cmd) == 2)
1644                 top = bottom = atoi(Cmd_Argv(cmd, 1));
1645         else
1646         {
1647                 top = atoi(Cmd_Argv(cmd, 1));
1648                 bottom = atoi(Cmd_Argv(cmd, 2));
1649         }
1650         CL_Color(cmd, top, bottom);
1651 }
1652
1653 static void CL_TopColor_f(cmd_state_t *cmd)
1654 {
1655         if (Cmd_Argc(cmd) == 1)
1656         {
1657                 if (cmd->source == src_command)
1658                 {
1659                         Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1660                         Con_Print("topcolor <0-15>\n");
1661                 }
1662                 return;
1663         }
1664
1665         CL_Color(cmd, atoi(Cmd_Argv(cmd, 1)), -1);
1666 }
1667
1668 static void CL_BottomColor_f(cmd_state_t *cmd)
1669 {
1670         if (Cmd_Argc(cmd) == 1)
1671         {
1672                 if (cmd->source == src_command)
1673                 {
1674                         Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1675                         Con_Print("bottomcolor <0-15>\n");
1676                 }
1677                 return;
1678         }
1679
1680         CL_Color(cmd, -1, atoi(Cmd_Argv(cmd, 1)));
1681 }
1682
1683 cvar_t cl_rate = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1684 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)"};
1685 static void CL_Rate_f(cmd_state_t *cmd)
1686 {
1687         int rate;
1688
1689         if (Cmd_Argc(cmd) != 2)
1690         {
1691                 if (cmd->source == src_command)
1692                 {
1693                         Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1694                         Con_Print("rate <bytespersecond>\n");
1695                 }
1696                 return;
1697         }
1698
1699         rate = atoi(Cmd_Argv(cmd, 1));
1700
1701         if (cmd->source == src_command)
1702         {
1703                 Cvar_SetValue (&cvars_all, "_cl_rate", max(NET_MINRATE, rate));
1704                 return;
1705         }
1706
1707         host_client->rate = rate;
1708 }
1709
1710 static void CL_Rate_BurstSize_f(cmd_state_t *cmd)
1711 {
1712         int rate_burstsize;
1713
1714         if (Cmd_Argc(cmd) != 2)
1715         {
1716                 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
1717                 Con_Print("rate_burstsize <bytes>\n");
1718                 return;
1719         }
1720
1721         rate_burstsize = atoi(Cmd_Argv(cmd, 1));
1722
1723         if (cmd->source == src_command)
1724         {
1725                 Cvar_SetValue (&cvars_all, "_cl_rate_burstsize", rate_burstsize);
1726                 return;
1727         }
1728
1729         host_client->rate_burstsize = rate_burstsize;
1730 }
1731
1732 /*
1733 ==================
1734 SV_Kill_f
1735 ==================
1736 */
1737 static void SV_Kill_f(cmd_state_t *cmd)
1738 {
1739         prvm_prog_t *prog = SVVM_prog;
1740         if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
1741         {
1742                 SV_ClientPrint("Can't suicide -- already dead!\n");
1743                 return;
1744         }
1745
1746         PRVM_serverglobalfloat(time) = sv.time;
1747         PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1748         prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
1749 }
1750
1751
1752 /*
1753 ==================
1754 SV_Pause_f
1755 ==================
1756 */
1757 static void SV_Pause_f(cmd_state_t *cmd)
1758 {
1759         void (*print) (const char *fmt, ...);
1760         if (cmd->source == src_command)
1761         {
1762                 // if running a client, try to send over network so the pause is handled by the server
1763                 if (cls.state == ca_connected)
1764                 {
1765                         Cmd_ForwardToServer_f(cmd);
1766                         return;
1767                 }
1768                 print = Con_Printf;
1769         }
1770         else
1771                 print = SV_ClientPrintf;
1772
1773         if (!pausable.integer)
1774         {
1775                 if (cmd->source == src_client)
1776                 {
1777                         if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
1778                         {
1779                                 print("Pause not allowed.\n");
1780                                 return;
1781                         }
1782                 }
1783         }
1784         
1785         sv.paused ^= 1;
1786         if (cmd->source != src_command)
1787                 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1788         else if(*(sv_adminnick.string))
1789                 SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un");
1790         else
1791                 SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un");
1792         // send notification to all clients
1793         MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1794         MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1795 }
1796
1797 /*
1798 ======================
1799 CL_PModel_f
1800 LadyHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1801 LadyHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1802 ======================
1803 */
1804 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)"};
1805 static void CL_PModel_f(cmd_state_t *cmd)
1806 {
1807         prvm_prog_t *prog = SVVM_prog;
1808         int i;
1809
1810         if (Cmd_Argc (cmd) == 1)
1811         {
1812                 if (cmd->source == src_command)
1813                 {
1814                         Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1815                 }
1816                 return;
1817         }
1818         i = atoi(Cmd_Argv(cmd, 1));
1819
1820         if (cmd->source == src_command)
1821         {
1822                 if (cl_pmodel.integer == i)
1823                         return;
1824                 Cvar_SetValue (&cvars_all, "_cl_pmodel", i);
1825                 if (cls.state == ca_connected)
1826                         Cmd_ForwardToServer_f(cmd);
1827                 return;
1828         }
1829
1830         PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1831 }
1832
1833 //===========================================================================
1834
1835
1836 /*
1837 ==================
1838 SV_PreSpawn_f
1839 ==================
1840 */
1841 static void SV_PreSpawn_f(cmd_state_t *cmd)
1842 {
1843         if (host_client->prespawned)
1844         {
1845                 Con_Print("prespawn not valid -- already prespawned\n");
1846                 return;
1847         }
1848         host_client->prespawned = true;
1849
1850         if (host_client->netconnection)
1851         {
1852                 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1853                 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1854                 MSG_WriteByte (&host_client->netconnection->message, 2);
1855                 host_client->sendsignon = 0;            // enable unlimited sends again
1856         }
1857
1858         // reset the name change timer because the client will send name soon
1859         host_client->nametime = 0;
1860 }
1861
1862 /*
1863 ==================
1864 SV_Spawn_f
1865 ==================
1866 */
1867 static void SV_Spawn_f(cmd_state_t *cmd)
1868 {
1869         prvm_prog_t *prog = SVVM_prog;
1870         int i;
1871         client_t *client;
1872         int stats[MAX_CL_STATS];
1873
1874         if (!host_client->prespawned)
1875         {
1876                 Con_Print("Spawn not valid -- not yet prespawned\n");
1877                 return;
1878         }
1879         if (host_client->spawned)
1880         {
1881                 Con_Print("Spawn not valid -- already spawned\n");
1882                 return;
1883         }
1884         host_client->spawned = true;
1885
1886         // reset name change timer again because they might want to change name
1887         // again in the first 5 seconds after connecting
1888         host_client->nametime = 0;
1889
1890         // LadyHavoc: moved this above the QC calls at FrikaC's request
1891         // LadyHavoc: commented this out
1892         //if (host_client->netconnection)
1893         //      SZ_Clear (&host_client->netconnection->message);
1894
1895         // run the entrance script
1896         if (sv.loadgame)
1897         {
1898                 // loaded games are fully initialized already
1899                 if (PRVM_serverfunction(RestoreGame))
1900                 {
1901                         Con_DPrint("Calling RestoreGame\n");
1902                         PRVM_serverglobalfloat(time) = sv.time;
1903                         PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1904                         prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1905                 }
1906         }
1907         else
1908         {
1909                 //Con_Printf("SV_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);
1910
1911                 // copy spawn parms out of the client_t
1912                 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1913                         (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1914
1915                 // call the spawn function
1916                 host_client->clientconnectcalled = true;
1917                 PRVM_serverglobalfloat(time) = sv.time;
1918                 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1919                 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1920
1921                 if (cls.state == ca_dedicated)
1922                         Con_Printf("%s connected\n", host_client->name);
1923
1924                 PRVM_serverglobalfloat(time) = sv.time;
1925                 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1926         }
1927
1928         if (!host_client->netconnection)
1929                 return;
1930
1931         // send time of update
1932         MSG_WriteByte (&host_client->netconnection->message, svc_time);
1933         MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1934
1935         // send all current names, colors, and frag counts
1936         for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1937         {
1938                 if (!client->active)
1939                         continue;
1940                 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1941                 MSG_WriteByte (&host_client->netconnection->message, i);
1942                 MSG_WriteString (&host_client->netconnection->message, client->name);
1943                 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1944                 MSG_WriteByte (&host_client->netconnection->message, i);
1945                 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1946                 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1947                 MSG_WriteByte (&host_client->netconnection->message, i);
1948                 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1949         }
1950
1951         // send all current light styles
1952         for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1953         {
1954                 if (sv.lightstyles[i][0])
1955                 {
1956                         MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1957                         MSG_WriteByte (&host_client->netconnection->message, (char)i);
1958                         MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1959                 }
1960         }
1961
1962         // send some stats
1963         MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1964         MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1965         MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1966
1967         MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1968         MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1969         MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1970
1971         MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1972         MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1973         MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1974
1975         MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1976         MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1977         MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1978
1979         // send a fixangle
1980         // Never send a roll angle, because savegames can catch the server
1981         // in a state where it is expecting the client to correct the angle
1982         // and it won't happen if the game was just loaded, so you wind up
1983         // with a permanent head tilt
1984         if (sv.loadgame)
1985         {
1986                 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1987                 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1988                 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1989                 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1990         }
1991         else
1992         {
1993                 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1994                 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1995                 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
1996                 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1997         }
1998
1999         SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
2000
2001         MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
2002         MSG_WriteByte (&host_client->netconnection->message, 3);
2003 }
2004
2005 /*
2006 ==================
2007 SV_Begin_f
2008 ==================
2009 */
2010 static void SV_Begin_f(cmd_state_t *cmd)
2011 {
2012         if (!host_client->spawned)
2013         {
2014                 Con_Print("Begin not valid -- not yet spawned\n");
2015                 return;
2016         }
2017         if (host_client->begun)
2018         {
2019                 Con_Print("Begin not valid -- already begun\n");
2020                 return;
2021         }
2022         host_client->begun = true;
2023
2024         // LadyHavoc: note: this code also exists in SV_DropClient
2025         if (sv.loadgame)
2026         {
2027                 int i;
2028                 for (i = 0;i < svs.maxclients;i++)
2029                         if (svs.clients[i].active && !svs.clients[i].spawned)
2030                                 break;
2031                 if (i == svs.maxclients)
2032                 {
2033                         Con_Printf("Loaded game, everyone rejoined - unpausing\n");
2034                         sv.paused = sv.loadgame = false; // we're basically done with loading now
2035                 }
2036         }
2037 }
2038
2039 //===========================================================================
2040
2041
2042 /*
2043 ==================
2044 SV_Kick_f
2045
2046 Kicks a user off of the server
2047 ==================
2048 */
2049 static void SV_Kick_f(cmd_state_t *cmd)
2050 {
2051         const char *who;
2052         const char *message = NULL;
2053         client_t *save;
2054         int i;
2055         qboolean byNumber = false;
2056
2057         if (!sv.active)
2058                 return;
2059
2060         save = host_client;
2061
2062         if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
2063         {
2064                 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
2065                 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
2066                         return;
2067                 byNumber = true;
2068         }
2069         else
2070         {
2071                 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
2072                 {
2073                         if (!host_client->active)
2074                                 continue;
2075                         if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
2076                                 break;
2077                 }
2078         }
2079
2080         if (i < svs.maxclients)
2081         {
2082                 if (cmd->source == src_command)
2083                 {
2084                         if (cls.state == ca_dedicated)
2085                                 who = "Console";
2086                         else
2087                                 who = cl_name.string;
2088                 }
2089                 else
2090                         who = save->name;
2091
2092                 // can't kick yourself!
2093                 if (host_client == save)
2094                         return;
2095
2096                 if (Cmd_Argc(cmd) > 2)
2097                 {
2098                         message = Cmd_Args(cmd);
2099                         COM_ParseToken_Simple(&message, false, false, true);
2100                         if (byNumber)
2101                         {
2102                                 message++;                                                      // skip the #
2103                                 while (*message == ' ')                         // skip white space
2104                                         message++;
2105                                 message += strlen(Cmd_Argv(cmd, 2));    // skip the number
2106                         }
2107                         while (*message && *message == ' ')
2108                                 message++;
2109                 }
2110                 if (message)
2111                         SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2112                 else
2113                         SV_ClientPrintf("Kicked by %s\n", who);
2114                 SV_DropClient (false); // kicked
2115         }
2116
2117         host_client = save;
2118 }
2119
2120 /*
2121 ===============================================================================
2122
2123 DEBUGGING TOOLS
2124
2125 ===============================================================================
2126 */
2127
2128 /*
2129 ==================
2130 SV_Give_f
2131 ==================
2132 */
2133 static void SV_Give_f(cmd_state_t *cmd)
2134 {
2135         prvm_prog_t *prog = SVVM_prog;
2136         const char *t;
2137         int v;
2138
2139         t = Cmd_Argv(cmd, 1);
2140         v = atoi (Cmd_Argv(cmd, 2));
2141
2142         switch (t[0])
2143         {
2144         case '0':
2145         case '1':
2146         case '2':
2147         case '3':
2148         case '4':
2149         case '5':
2150         case '6':
2151         case '7':
2152         case '8':
2153         case '9':
2154                 // MED 01/04/97 added hipnotic give stuff
2155                 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
2156                 {
2157                         if (t[0] == '6')
2158                         {
2159                                 if (t[1] == 'a')
2160                                         PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2161                                 else
2162                                         PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2163                         }
2164                         else if (t[0] == '9')
2165                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2166                         else if (t[0] == '0')
2167                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2168                         else if (t[0] >= '2')
2169                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2170                 }
2171                 else
2172                 {
2173                         if (t[0] >= '2')
2174                                 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2175                 }
2176                 break;
2177
2178         case 's':
2179                 if (gamemode == GAME_ROGUE)
2180                         PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2181
2182                 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2183                 break;
2184         case 'n':
2185                 if (gamemode == GAME_ROGUE)
2186                 {
2187                         PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2188                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2189                                 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2190                 }
2191                 else
2192                 {
2193                         PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2194                 }
2195                 break;
2196         case 'l':
2197                 if (gamemode == GAME_ROGUE)
2198                 {
2199                         PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2200                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2201                                 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2202                 }
2203                 break;
2204         case 'r':
2205                 if (gamemode == GAME_ROGUE)
2206                 {
2207                         PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2208                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2209                                 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2210                 }
2211                 else
2212                 {
2213                         PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2214                 }
2215                 break;
2216         case 'm':
2217                 if (gamemode == GAME_ROGUE)
2218                 {
2219                         PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2220                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2221                                 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2222                 }
2223                 break;
2224         case 'h':
2225                 PRVM_serveredictfloat(host_client->edict, health) = v;
2226                 break;
2227         case 'c':
2228                 if (gamemode == GAME_ROGUE)
2229                 {
2230                         PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2231                         if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2232                                 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2233                 }
2234                 else
2235                 {
2236                         PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2237                 }
2238                 break;
2239         case 'p':
2240                 if (gamemode == GAME_ROGUE)
2241                 {
2242                         PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2243                         if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2244                                 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2245                 }
2246                 break;
2247         }
2248 }
2249
2250 static prvm_edict_t     *FindViewthing(prvm_prog_t *prog)
2251 {
2252         int             i;
2253         prvm_edict_t    *e;
2254
2255         for (i=0 ; i<prog->num_edicts ; i++)
2256         {
2257                 e = PRVM_EDICT_NUM(i);
2258                 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2259                         return e;
2260         }
2261         Con_Print("No viewthing on map\n");
2262         return NULL;
2263 }
2264
2265 /*
2266 ==================
2267 SV_Viewmodel_f
2268 ==================
2269 */
2270 static void SV_Viewmodel_f(cmd_state_t *cmd)
2271 {
2272         prvm_prog_t *prog = SVVM_prog;
2273         prvm_edict_t    *e;
2274         dp_model_t      *m;
2275
2276         if (!sv.active)
2277                 return;
2278
2279         e = FindViewthing(prog);
2280         if (e)
2281         {
2282                 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
2283                 if (m && m->loaded && m->Draw)
2284                 {
2285                         PRVM_serveredictfloat(e, frame) = 0;
2286                         cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2287                 }
2288                 else
2289                         Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
2290         }
2291 }
2292
2293 /*
2294 ==================
2295 SV_Viewframe_f
2296 ==================
2297 */
2298 static void SV_Viewframe_f(cmd_state_t *cmd)
2299 {
2300         prvm_prog_t *prog = SVVM_prog;
2301         prvm_edict_t    *e;
2302         int             f;
2303         dp_model_t      *m;
2304
2305         if (!sv.active)
2306                 return;
2307
2308         e = FindViewthing(prog);
2309         if (e)
2310         {
2311                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2312
2313                 f = atoi(Cmd_Argv(cmd, 1));
2314                 if (f >= m->numframes)
2315                         f = m->numframes-1;
2316
2317                 PRVM_serveredictfloat(e, frame) = f;
2318         }
2319 }
2320
2321
2322 static void PrintFrameName (dp_model_t *m, int frame)
2323 {
2324         if (m->animscenes)
2325                 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2326         else
2327                 Con_Printf("frame %i\n", frame);
2328 }
2329
2330 /*
2331 ==================
2332 SV_Viewnext_f
2333 ==================
2334 */
2335 static void SV_Viewnext_f(cmd_state_t *cmd)
2336 {
2337         prvm_prog_t *prog = SVVM_prog;
2338         prvm_edict_t    *e;
2339         dp_model_t      *m;
2340
2341         if (!sv.active)
2342                 return;
2343
2344         e = FindViewthing(prog);
2345         if (e)
2346         {
2347                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2348
2349                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2350                 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2351                         PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2352
2353                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2354         }
2355 }
2356
2357 /*
2358 ==================
2359 SV_Viewprev_f
2360 ==================
2361 */
2362 static void SV_Viewprev_f(cmd_state_t *cmd)
2363 {
2364         prvm_prog_t *prog = SVVM_prog;
2365         prvm_edict_t    *e;
2366         dp_model_t      *m;
2367
2368         if (!sv.active)
2369                 return;
2370
2371         e = FindViewthing(prog);
2372         if (e)
2373         {
2374                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2375
2376                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2377                 if (PRVM_serveredictfloat(e, frame) < 0)
2378                         PRVM_serveredictfloat(e, frame) = 0;
2379
2380                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2381         }
2382 }
2383
2384 /*
2385 ===============================================================================
2386
2387 DEMO LOOP CONTROL
2388
2389 ===============================================================================
2390 */
2391
2392
2393 /*
2394 ==================
2395 CL_Startdemos_f
2396 ==================
2397 */
2398 static void CL_Startdemos_f(cmd_state_t *cmd)
2399 {
2400         int             i, c;
2401
2402         if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2403                 return;
2404
2405         c = Cmd_Argc(cmd) - 1;
2406         if (c > MAX_DEMOS)
2407         {
2408                 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2409                 c = MAX_DEMOS;
2410         }
2411         Con_DPrintf("%i demo(s) in loop\n", c);
2412
2413         for (i=1 ; i<c+1 ; i++)
2414                 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
2415
2416         // LadyHavoc: clear the remaining slots
2417         for (;i <= MAX_DEMOS;i++)
2418                 cls.demos[i-1][0] = 0;
2419
2420         if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2421         {
2422                 cls.demonum = 0;
2423                 CL_NextDemo ();
2424         }
2425         else
2426                 cls.demonum = -1;
2427 }
2428
2429
2430 /*
2431 ==================
2432 CL_Demos_f
2433
2434 Return to looping demos
2435 ==================
2436 */
2437 static void CL_Demos_f(cmd_state_t *cmd)
2438 {
2439         if (cls.state == ca_dedicated)
2440                 return;
2441         if (cls.demonum == -1)
2442                 cls.demonum = 1;
2443         CL_Disconnect_f (cmd);
2444         CL_NextDemo ();
2445 }
2446
2447 /*
2448 ==================
2449 CL_Stopdemo_f
2450
2451 Return to looping demos
2452 ==================
2453 */
2454 static void CL_Stopdemo_f(cmd_state_t *cmd)
2455 {
2456         if (!cls.demoplayback)
2457                 return;
2458         CL_Disconnect ();
2459         Host_ShutdownServer ();
2460 }
2461
2462 static void CL_SendCvar_f(cmd_state_t *cmd)
2463 {
2464         int             i;
2465         cvar_t  *c;
2466         const char *cvarname;
2467         client_t *old;
2468         char vabuf[1024];
2469
2470         if(Cmd_Argc(cmd) != 2)
2471                 return;
2472         cvarname = Cmd_Argv(cmd, 1);
2473         if (cls.state == ca_connected)
2474         {
2475                 c = Cvar_FindVar(&cvars_all, cvarname, CVAR_CLIENT | CVAR_SERVER);
2476                 // LadyHavoc: if there is no such cvar or if it is private, send a
2477                 // reply indicating that it has no value
2478                 if(!c || (c->flags & CVAR_PRIVATE))
2479                         Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2480                 else
2481                         Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2482                 return;
2483         }
2484         if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2485                 return;
2486
2487         old = host_client;
2488         if (cls.state != ca_dedicated)
2489                 i = 1;
2490         else
2491                 i = 0;
2492         for(;i<svs.maxclients;i++)
2493                 if(svs.clients[i].active && svs.clients[i].netconnection)
2494                 {
2495                         host_client = &svs.clients[i];
2496                         Host_ClientCommands("sendcvar %s\n", cvarname);
2497                 }
2498         host_client = old;
2499 }
2500
2501 static void SV_MaxPlayers_f(cmd_state_t *cmd)
2502 {
2503         int n;
2504
2505         if (Cmd_Argc(cmd) != 2)
2506         {
2507                 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2508                 return;
2509         }
2510
2511         if (sv.active)
2512         {
2513                 Con_Print("maxplayers can not be changed while a server is running.\n");
2514                 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2515         }
2516
2517         n = atoi(Cmd_Argv(cmd, 1));
2518         n = bound(1, n, MAX_SCOREBOARD);
2519         Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2520
2521         svs.maxclients_next = n;
2522         if (n == 1)
2523                 Cvar_Set (&cvars_all, "deathmatch", "0");
2524         else
2525                 Cvar_Set (&cvars_all, "deathmatch", "1");
2526 }
2527
2528 /*
2529 =====================
2530 CL_PQRcon_f
2531
2532 ProQuake rcon support
2533 =====================
2534 */
2535 static void CL_PQRcon_f(cmd_state_t *cmd)
2536 {
2537         int n;
2538         const char *e;
2539         lhnetsocket_t *mysocket;
2540
2541         if (Cmd_Argc(cmd) == 1)
2542         {
2543                 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
2544                 return;
2545         }
2546
2547         if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2548         {
2549                 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2550                 return;
2551         }
2552
2553         e = strchr(rcon_password.string, ' ');
2554         n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2555
2556         if (cls.netcon)
2557                 cls.rcon_address = cls.netcon->peeraddress;
2558         else
2559         {
2560                 if (!rcon_address.string[0])
2561                 {
2562                         Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2563                         return;
2564                 }
2565                 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
2566         }
2567         mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
2568         if (mysocket)
2569         {
2570                 sizebuf_t buf;
2571                 unsigned char bufdata[64];
2572                 buf.data = bufdata;
2573                 SZ_Clear(&buf);
2574                 MSG_WriteLong(&buf, 0);
2575                 MSG_WriteByte(&buf, CCREQ_RCON);
2576                 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2577                 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2578                 MSG_WriteString(&buf, Cmd_Args(cmd));
2579                 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2580                 NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address);
2581                 SZ_Clear(&buf);
2582         }
2583 }
2584
2585 //=============================================================================
2586
2587 // QuakeWorld commands
2588
2589 /*
2590 =====================
2591 CL_Rcon_f
2592
2593   Send the rest of the command line over as
2594   an unconnected command.
2595 =====================
2596 */
2597 static void CL_Rcon_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2598 {
2599         int i, n;
2600         const char *e;
2601         lhnetsocket_t *mysocket;
2602
2603         if (Cmd_Argc(cmd) == 1)
2604         {
2605                 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
2606                 return;
2607         }
2608
2609         if (!rcon_password.string || !rcon_password.string[0])
2610         {
2611                 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2612                 return;
2613         }
2614
2615         e = strchr(rcon_password.string, ' ');
2616         n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2617
2618         if (cls.netcon)
2619                 cls.rcon_address = cls.netcon->peeraddress;
2620         else
2621         {
2622                 if (!rcon_address.string[0])
2623                 {
2624                         Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2625                         return;
2626                 }
2627                 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
2628         }
2629         mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
2630         if (mysocket && Cmd_Args(cmd)[0])
2631         {
2632                 // simply put together the rcon packet and send it
2633                 if(Cmd_Argv(cmd, 0)[0] == 's' || rcon_secure.integer > 1)
2634                 {
2635                         if(cls.rcon_commands[cls.rcon_ringpos][0])
2636                         {
2637                                 char s[128];
2638                                 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2639                                 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]);
2640                                 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2641                                 --cls.rcon_trying;
2642                         }
2643                         for (i = 0;i < MAX_RCONS;i++)
2644                                 if(cls.rcon_commands[i][0])
2645                                         if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i]))
2646                                                 break;
2647                         ++cls.rcon_trying;
2648                         if(i >= MAX_RCONS)
2649                                 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later
2650                         strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(cmd), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2651                         cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address;
2652                         cls.rcon_timeout[cls.rcon_ringpos] = host.realtime + rcon_secure_challengetimeout.value;
2653                         cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2654                 }
2655                 else if(rcon_secure.integer > 0)
2656                 {
2657                         char buf[1500];
2658                         char argbuf[1500];
2659                         dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args(cmd));
2660                         memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2661                         if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n))
2662                         {
2663                                 buf[40] = ' ';
2664                                 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2665                                 NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address);
2666                         }
2667                 }
2668                 else
2669                 {
2670                         char buf[1500];
2671                         memcpy(buf, "\377\377\377\377", 4);
2672                         dpsnprintf(buf+4, sizeof(buf)-4, "rcon %.*s %s",  n, rcon_password.string, Cmd_Args(cmd));
2673                         NetConn_WriteString(mysocket, buf, &cls.rcon_address);
2674                 }
2675         }
2676 }
2677
2678 /*
2679 ====================
2680 SV_User_f
2681
2682 user <name or userid>
2683
2684 Dump userdata / masterdata for a user
2685 ====================
2686 */
2687 static void SV_User_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2688 {
2689         int             uid;
2690         int             i;
2691
2692         if (Cmd_Argc(cmd) != 2)
2693         {
2694                 Con_Printf ("Usage: user <username / userid>\n");
2695                 return;
2696         }
2697
2698         uid = atoi(Cmd_Argv(cmd, 1));
2699
2700         for (i = 0;i < cl.maxclients;i++)
2701         {
2702                 if (!cl.scores[i].name[0])
2703                         continue;
2704                 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(cmd, 1)))
2705                 {
2706                         InfoString_Print(cl.scores[i].qw_userinfo);
2707                         return;
2708                 }
2709         }
2710         Con_Printf ("User not in server.\n");
2711 }
2712
2713 /*
2714 ====================
2715 SV_Users_f
2716
2717 Dump userids for all current players
2718 ====================
2719 */
2720 static void SV_Users_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2721 {
2722         int             i;
2723         int             c;
2724
2725         c = 0;
2726         Con_Printf ("userid frags name\n");
2727         Con_Printf ("------ ----- ----\n");
2728         for (i = 0;i < cl.maxclients;i++)
2729         {
2730                 if (cl.scores[i].name[0])
2731                 {
2732                         Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2733                         c++;
2734                 }
2735         }
2736
2737         Con_Printf ("%i total users\n", c);
2738 }
2739
2740 /*
2741 ==================
2742 CL_FullServerinfo_f
2743
2744 Sent by server when serverinfo changes
2745 ==================
2746 */
2747 // TODO: shouldn't this be a cvar instead?
2748 static void CL_FullServerinfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2749 {
2750         char temp[512];
2751         if (Cmd_Argc(cmd) != 2)
2752         {
2753                 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2754                 return;
2755         }
2756
2757         strlcpy (cl.qw_serverinfo, Cmd_Argv(cmd, 1), sizeof(cl.qw_serverinfo));
2758         InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2759         cl.qw_teamplay = atoi(temp);
2760 }
2761
2762 /*
2763 ==================
2764 CL_FullInfo_f
2765
2766 Allow clients to change userinfo
2767 ==================
2768 Casey was here :)
2769 */
2770 static void CL_FullInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2771 {
2772         char key[512];
2773         char value[512];
2774         const char *s;
2775
2776         if (Cmd_Argc(cmd) != 2)
2777         {
2778                 Con_Printf ("fullinfo <complete info string>\n");
2779                 return;
2780         }
2781
2782         s = Cmd_Argv(cmd, 1);
2783         if (*s == '\\')
2784                 s++;
2785         while (*s)
2786         {
2787                 size_t len = strcspn(s, "\\");
2788                 if (len >= sizeof(key)) {
2789                         len = sizeof(key) - 1;
2790                 }
2791                 strlcpy(key, s, len + 1);
2792                 s += len;
2793                 if (!*s)
2794                 {
2795                         Con_Printf ("MISSING VALUE\n");
2796                         return;
2797                 }
2798                 ++s; // Skip over backslash.
2799
2800                 len = strcspn(s, "\\");
2801                 if (len >= sizeof(value)) {
2802                         len = sizeof(value) - 1;
2803                 }
2804                 strlcpy(value, s, len + 1);
2805
2806                 CL_SetInfo(key, value, false, false, false, false);
2807
2808                 s += len;
2809                 if (!*s)
2810                 {
2811                         break;
2812                 }
2813                 ++s; // Skip over backslash.
2814         }
2815 }
2816
2817 /*
2818 ==================
2819 CL_SetInfo_f
2820
2821 Allow clients to change userinfo
2822 ==================
2823 */
2824 static void CL_SetInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2825 {
2826         if (Cmd_Argc(cmd) == 1)
2827         {
2828                 InfoString_Print(cls.userinfo);
2829                 return;
2830         }
2831         if (Cmd_Argc(cmd) != 3)
2832         {
2833                 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2834                 return;
2835         }
2836         CL_SetInfo(Cmd_Argv(cmd, 1), Cmd_Argv(cmd, 2), true, false, false, false);
2837 }
2838
2839 /*
2840 ====================
2841 CL_Packet_f
2842
2843 packet <destination> <contents>
2844
2845 Contents allows \n escape character
2846 ====================
2847 */
2848 static void CL_Packet_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
2849 {
2850         char send[2048];
2851         int i, l;
2852         const char *in;
2853         char *out;
2854         lhnetaddress_t address;
2855         lhnetsocket_t *mysocket;
2856
2857         if (Cmd_Argc(cmd) != 3)
2858         {
2859                 Con_Printf ("packet <destination> <contents>\n");
2860                 return;
2861         }
2862
2863         if (!LHNETADDRESS_FromString (&address, Cmd_Argv(cmd, 1), sv_netport.integer))
2864         {
2865                 Con_Printf ("Bad address\n");
2866                 return;
2867         }
2868
2869         in = Cmd_Argv(cmd, 2);
2870         out = send+4;
2871         send[0] = send[1] = send[2] = send[3] = -1;
2872
2873         l = (int)strlen (in);
2874         for (i=0 ; i<l ; i++)
2875         {
2876                 if (out >= send + sizeof(send) - 1)
2877                         break;
2878                 if (in[i] == '\\' && in[i+1] == 'n')
2879                 {
2880                         *out++ = '\n';
2881                         i++;
2882                 }
2883                 else if (in[i] == '\\' && in[i+1] == '0')
2884                 {
2885                         *out++ = '\0';
2886                         i++;
2887                 }
2888                 else if (in[i] == '\\' && in[i+1] == 't')
2889                 {
2890                         *out++ = '\t';
2891                         i++;
2892                 }
2893                 else if (in[i] == '\\' && in[i+1] == 'r')
2894                 {
2895                         *out++ = '\r';
2896                         i++;
2897                 }
2898                 else if (in[i] == '\\' && in[i+1] == '"')
2899                 {
2900                         *out++ = '\"';
2901                         i++;
2902                 }
2903                 else
2904                         *out++ = in[i];
2905         }
2906
2907         mysocket = NetConn_ChooseClientSocketForAddress(&address);
2908         if (!mysocket)
2909                 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2910         if (mysocket)
2911                 NetConn_Write(mysocket, send, out - send, &address);
2912 }
2913
2914 /*
2915 ====================
2916 SV_Pings_f
2917
2918 Send back ping and packet loss update for all current players to this player
2919 ====================
2920 */
2921 static void SV_Pings_f(cmd_state_t *cmd)
2922 {
2923         int             i, j, ping, packetloss, movementloss;
2924         char temp[128];
2925
2926         if (!host_client->netconnection)
2927                 return;
2928
2929         if (sv.protocol != PROTOCOL_QUAKEWORLD)
2930         {
2931                 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2932                 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2933         }
2934         for (i = 0;i < svs.maxclients;i++)
2935         {
2936                 packetloss = 0;
2937                 movementloss = 0;
2938                 if (svs.clients[i].netconnection)
2939                 {
2940                         for (j = 0;j < NETGRAPH_PACKETS;j++)
2941                                 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2942                                         packetloss++;
2943                         for (j = 0;j < NETGRAPH_PACKETS;j++)
2944                                 if (svs.clients[i].movement_count[j] < 0)
2945                                         movementloss++;
2946                 }
2947                 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2948                 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2949                 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2950                 ping = bound(0, ping, 9999);
2951                 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2952                 {
2953                         // send qw_svc_updateping and qw_svc_updatepl messages
2954                         MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2955                         MSG_WriteShort(&host_client->netconnection->message, ping);
2956                         MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2957                         MSG_WriteByte(&host_client->netconnection->message, packetloss);
2958                 }
2959                 else
2960                 {
2961                         // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2962                         if(movementloss)
2963                                 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2964                         else
2965                                 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2966                         MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2967                 }
2968         }
2969         if (sv.protocol != PROTOCOL_QUAKEWORLD)
2970                 MSG_WriteString(&host_client->netconnection->message, "\n");
2971 }
2972
2973 static void CL_PingPLReport_f(cmd_state_t *cmd)
2974 {
2975         char *errbyte;
2976         int i;
2977         int l = Cmd_Argc(cmd);
2978         if (l > cl.maxclients)
2979                 l = cl.maxclients;
2980         for (i = 0;i < l;i++)
2981         {
2982                 cl.scores[i].qw_ping = atoi(Cmd_Argv(cmd, 1+i*2));
2983                 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(cmd, 1+i*2+1), &errbyte, 0);
2984                 if(errbyte && *errbyte == ',')
2985                         cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2986                 else
2987                         cl.scores[i].qw_movementloss = 0;
2988         }
2989 }
2990
2991 //=============================================================================
2992
2993 /*
2994 ==================
2995 Host_InitCommands
2996 ==================
2997 */
2998 void Host_InitCommands (void)
2999 {
3000         dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
3001
3002         Cvar_RegisterVariable(&cl_name);
3003         Cvar_RegisterVariable(&cl_color);
3004         Cvar_RegisterVariable(&cl_rate);
3005         Cvar_RegisterVariable(&cl_rate_burstsize);
3006         Cvar_RegisterVariable(&cl_pmodel);
3007         Cvar_RegisterVariable(&cl_playermodel);
3008         Cvar_RegisterVariable(&cl_playerskin);
3009         Cvar_RegisterVariable(&rcon_password);
3010         Cvar_RegisterVariable(&rcon_address);
3011         Cvar_RegisterVariable(&rcon_secure);
3012         Cvar_RegisterVariable(&rcon_secure_challengetimeout);
3013         Cvar_RegisterVariable(&r_fixtrans_auto);
3014         Cvar_RegisterVariable(&team);
3015         Cvar_RegisterVariable(&skin);
3016         Cvar_RegisterVariable(&noaim);
3017         Cvar_RegisterVariable(&sv_cheats);
3018         Cvar_RegisterCallback(&sv_cheats, SV_DisableCheats_c);
3019         Cvar_RegisterVariable(&sv_adminnick);
3020         Cvar_RegisterVariable(&sv_status_privacy);
3021         Cvar_RegisterVariable(&sv_status_show_qcstatus);
3022         Cvar_RegisterVariable(&sv_namechangetimer);
3023
3024         // client commands - this includes server commands because the client can host a server, so they must exist
3025         Cmd_AddCommand(CMD_SHARED, "quit", Host_Quit_f, "quit the game");
3026         Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
3027         Cmd_AddCommand(CMD_SHARED | CMD_INITWAIT, "map", SV_Map_f, "kick everyone off the server and start a new level");
3028         Cmd_AddCommand(CMD_SHARED, "restart", SV_Restart_f, "restart current level");
3029         Cmd_AddCommand(CMD_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
3030         Cmd_AddCommand(CMD_SHARED, "version", Host_Version_f, "print engine version");
3031         Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
3032         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
3033         Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
3034         Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
3035         Cmd_AddCommand(CMD_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
3036         Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
3037         Cmd_AddCommand(CMD_SHARED | CMD_INITWAIT, "load", SV_Loadgame_f, "load a saved game file");
3038         Cmd_AddCommand(CMD_SHARED, "save", SV_Savegame_f, "save the game to a file");
3039         Cmd_AddCommand(CMD_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
3040         Cmd_AddCommand(CMD_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
3041         Cmd_AddCommand(CMD_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
3042         Cmd_AddCommand(CMD_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
3043         Cmd_AddCommand(CMD_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
3044         Cmd_AddCommand(CMD_SHARED, "user", SV_User_f, "prints additional information about a player number or name on the scoreboard");
3045         Cmd_AddCommand(CMD_SHARED, "users", SV_Users_f, "prints additional information about all players on the scoreboard");
3046
3047         // 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)
3048         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
3049         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "spawn", SV_Spawn_f, "internal use - signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
3050         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "begin", SV_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)");
3051         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "pings", SV_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)");
3052
3053         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
3054         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
3055         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
3056         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
3057         Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
3058         Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
3059         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "name", CL_Name_f, "change your player name");
3060         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "color", CL_Color_f, "change your player shirt and pants colors");
3061         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate", CL_Rate_f, "change your network connection speed");
3062         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate_burstsize", CL_Rate_BurstSize_f, "change your network connection speed");
3063         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "pmodel", CL_PModel_f, "(Nehahra-only) change your player model choice");
3064         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playermodel", CL_Playermodel_f, "change your player model");
3065         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playerskin", CL_Playerskin_f, "change your player skin number");
3066
3067         Cmd_AddCommand(CMD_CLIENT, "connect", CL_Connect_f, "connect to a server by IP address or hostname");
3068         Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "reconnect", CL_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)");
3069         Cmd_AddCommand(CMD_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
3070         Cmd_AddCommand(CMD_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
3071         Cmd_AddCommand(CMD_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
3072         Cmd_AddCommand(CMD_CLIENT, "sendcvar", CL_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
3073         Cmd_AddCommand(CMD_CLIENT, "rcon", CL_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");
3074         Cmd_AddCommand(CMD_CLIENT, "srcon", CL_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");
3075         Cmd_AddCommand(CMD_CLIENT, "pqrcon", CL_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)");
3076         Cmd_AddCommand(CMD_CLIENT, "fullinfo", CL_FullInfo_f, "allows client to modify their userinfo");
3077         Cmd_AddCommand(CMD_CLIENT, "setinfo", CL_SetInfo_f, "modifies your userinfo");
3078         Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "packet", CL_Packet_f, "send a packet to the specified address:port containing a text string");
3079         Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "topcolor", CL_TopColor_f, "QW command to set top color without changing bottom color");
3080         Cmd_AddCommand(CMD_CLIENT, "bottomcolor", CL_BottomColor_f, "QW command to set bottom color without changing top color");
3081         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)");
3082
3083         // commands that are only sent by server to client for execution
3084         Cmd_AddCommand(CMD_CLIENT_FROM_SERVER, "pingplreport", CL_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)");
3085         Cmd_AddCommand(CMD_CLIENT_FROM_SERVER, "fullserverinfo", CL_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
3086 }
3087
3088 void Host_NoOperation_f(cmd_state_t *cmd)
3089 {
3090 }