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