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