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