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