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