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