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