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