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