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