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