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