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