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