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