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