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