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