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