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