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