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