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