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