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