]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - host_cmd.c
fix image loading sRGB conversion
[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_serverfunction(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_serverfunction(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         if (e)
2182         {
2183                 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2184                 if (m && m->loaded && m->Draw)
2185                 {
2186                         PRVM_serveredictfloat(e, frame) = 0;
2187                         cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2188                 }
2189                 else
2190                         Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2191         
2192         }
2193         SV_VM_End();
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         if (e)
2213         {
2214                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2215
2216                 f = atoi(Cmd_Argv(1));
2217                 if (f >= m->numframes)
2218                         f = m->numframes-1;
2219
2220                 PRVM_serveredictfloat(e, frame) = f;
2221         }
2222         SV_VM_End();
2223 }
2224
2225
2226 void PrintFrameName (dp_model_t *m, int frame)
2227 {
2228         if (m->animscenes)
2229                 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2230         else
2231                 Con_Printf("frame %i\n", frame);
2232 }
2233
2234 /*
2235 ==================
2236 Host_Viewnext_f
2237 ==================
2238 */
2239 void Host_Viewnext_f (void)
2240 {
2241         prvm_edict_t    *e;
2242         dp_model_t      *m;
2243
2244         if (!sv.active)
2245                 return;
2246
2247         SV_VM_Begin();
2248         e = FindViewthing ();
2249         SV_VM_End();
2250         if (e)
2251         {
2252                 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2253
2254                 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2255                 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2256                         PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2257
2258                 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2259         }
2260 }
2261
2262 /*
2263 ==================
2264 Host_Viewprev_f
2265 ==================
2266 */
2267 void Host_Viewprev_f (void)
2268 {
2269         prvm_edict_t    *e;
2270         dp_model_t      *m;
2271
2272         if (!sv.active)
2273                 return;
2274
2275         SV_VM_Begin();
2276         e = FindViewthing ();
2277         if (e)
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         SV_VM_End();
2288 }
2289
2290 /*
2291 ===============================================================================
2292
2293 DEMO LOOP CONTROL
2294
2295 ===============================================================================
2296 */
2297
2298
2299 /*
2300 ==================
2301 Host_Startdemos_f
2302 ==================
2303 */
2304 void Host_Startdemos_f (void)
2305 {
2306         int             i, c;
2307
2308         if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2309                 return;
2310
2311         c = Cmd_Argc() - 1;
2312         if (c > MAX_DEMOS)
2313         {
2314                 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2315                 c = MAX_DEMOS;
2316         }
2317         Con_DPrintf("%i demo(s) in loop\n", c);
2318
2319         for (i=1 ; i<c+1 ; i++)
2320                 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2321
2322         // LordHavoc: clear the remaining slots
2323         for (;i <= MAX_DEMOS;i++)
2324                 cls.demos[i-1][0] = 0;
2325
2326         if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2327         {
2328                 cls.demonum = 0;
2329                 CL_NextDemo ();
2330         }
2331         else
2332                 cls.demonum = -1;
2333 }
2334
2335
2336 /*
2337 ==================
2338 Host_Demos_f
2339
2340 Return to looping demos
2341 ==================
2342 */
2343 void Host_Demos_f (void)
2344 {
2345         if (cls.state == ca_dedicated)
2346                 return;
2347         if (cls.demonum == -1)
2348                 cls.demonum = 1;
2349         CL_Disconnect_f ();
2350         CL_NextDemo ();
2351 }
2352
2353 /*
2354 ==================
2355 Host_Stopdemo_f
2356
2357 Return to looping demos
2358 ==================
2359 */
2360 void Host_Stopdemo_f (void)
2361 {
2362         if (!cls.demoplayback)
2363                 return;
2364         CL_Disconnect ();
2365         Host_ShutdownServer ();
2366 }
2367
2368 void Host_SendCvar_f (void)
2369 {
2370         int             i;
2371         cvar_t  *c;
2372         const char *cvarname;
2373         client_t *old;
2374
2375         if(Cmd_Argc() != 2)
2376                 return;
2377         cvarname = Cmd_Argv(1);
2378         if (cls.state == ca_connected)
2379         {
2380                 c = Cvar_FindVar(cvarname);
2381                 // LordHavoc: if there is no such cvar or if it is private, send a
2382                 // reply indicating that it has no value
2383                 if(!c || (c->flags & CVAR_PRIVATE))
2384                         Cmd_ForwardStringToServer(va("sentcvar %s", cvarname));
2385                 else
2386                         Cmd_ForwardStringToServer(va("sentcvar %s \"%s\"", c->name, c->string));
2387                 return;
2388         }
2389         if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2390                 return;
2391
2392         old = host_client;
2393         if (cls.state != ca_dedicated)
2394                 i = 1;
2395         else
2396                 i = 0;
2397         for(;i<svs.maxclients;i++)
2398                 if(svs.clients[i].active && svs.clients[i].netconnection)
2399                 {
2400                         host_client = &svs.clients[i];
2401                         Host_ClientCommands("sendcvar %s\n", cvarname);
2402                 }
2403         host_client = old;
2404 }
2405
2406 static void MaxPlayers_f(void)
2407 {
2408         int n;
2409
2410         if (Cmd_Argc() != 2)
2411         {
2412                 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2413                 return;
2414         }
2415
2416         if (sv.active)
2417         {
2418                 Con_Print("maxplayers can not be changed while a server is running.\n");
2419                 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2420         }
2421
2422         n = atoi(Cmd_Argv(1));
2423         n = bound(1, n, MAX_SCOREBOARD);
2424         Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2425
2426         svs.maxclients_next = n;
2427         if (n == 1)
2428                 Cvar_Set ("deathmatch", "0");
2429         else
2430                 Cvar_Set ("deathmatch", "1");
2431 }
2432
2433 /*
2434 =====================
2435 Host_PQRcon_f
2436
2437 ProQuake rcon support
2438 =====================
2439 */
2440 void Host_PQRcon_f (void)
2441 {
2442         int n;
2443         const char *e;
2444         lhnetaddress_t to;
2445         lhnetsocket_t *mysocket;
2446         char peer_address[64];
2447
2448         if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2449         {
2450                 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2451                 return;
2452         }
2453
2454         e = strchr(rcon_password.string, ' ');
2455         n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2456
2457         if (cls.netcon)
2458         {
2459                 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2460         }
2461         else
2462         {
2463                 if (!rcon_address.string[0])
2464                 {
2465                         Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2466                         return;
2467                 }
2468                 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2469         }
2470         LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2471         mysocket = NetConn_ChooseClientSocketForAddress(&to);
2472         if (mysocket)
2473         {
2474                 SZ_Clear(&net_message);
2475                 MSG_WriteLong (&net_message, 0);
2476                 MSG_WriteByte (&net_message, CCREQ_RCON);
2477                 SZ_Write(&net_message, (const unsigned char*)rcon_password.string, n);
2478                 MSG_WriteByte (&net_message, 0); // terminate the (possibly partial) string
2479                 MSG_WriteString (&net_message, Cmd_Args());
2480                 StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
2481                 NetConn_Write(mysocket, net_message.data, net_message.cursize, &to);
2482                 SZ_Clear (&net_message);
2483         }
2484 }
2485
2486 //=============================================================================
2487
2488 // QuakeWorld commands
2489
2490 /*
2491 =====================
2492 Host_Rcon_f
2493
2494   Send the rest of the command line over as
2495   an unconnected command.
2496 =====================
2497 */
2498 void Host_Rcon_f (void) // credit: taken from QuakeWorld
2499 {
2500         int i, n;
2501         const char *e;
2502         lhnetaddress_t to;
2503         lhnetsocket_t *mysocket;
2504
2505         if (!rcon_password.string || !rcon_password.string[0])
2506         {
2507                 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2508                 return;
2509         }
2510
2511         e = strchr(rcon_password.string, ' ');
2512         n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2513
2514         if (cls.netcon)
2515                 to = cls.netcon->peeraddress;
2516         else
2517         {
2518                 if (!rcon_address.string[0])
2519                 {
2520                         Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2521                         return;
2522                 }
2523                 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2524         }
2525         mysocket = NetConn_ChooseClientSocketForAddress(&to);
2526         if (mysocket && Cmd_Args()[0])
2527         {
2528                 // simply put together the rcon packet and send it
2529                 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2530                 {
2531                         if(cls.rcon_commands[cls.rcon_ringpos][0])
2532                         {
2533                                 char s[128];
2534                                 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2535                                 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]);
2536                                 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2537                                 --cls.rcon_trying;
2538                         }
2539                         for (i = 0;i < MAX_RCONS;i++)
2540                                 if(cls.rcon_commands[i][0])
2541                                         if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2542                                                 break;
2543                         ++cls.rcon_trying;
2544                         if(i >= MAX_RCONS)
2545                                 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2546                         strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2547                         cls.rcon_addresses[cls.rcon_ringpos] = to;
2548                         cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2549                         cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2550                 }
2551                 else if(rcon_secure.integer > 0)
2552                 {
2553                         char buf[1500];
2554                         char argbuf[1500];
2555                         dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2556                         memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2557                         if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
2558                         {
2559                                 buf[40] = ' ';
2560                                 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2561                                 NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
2562                         }
2563                 }
2564                 else
2565                 {
2566                         NetConn_WriteString(mysocket, va("\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2567                 }
2568         }
2569 }
2570
2571 /*
2572 ====================
2573 Host_User_f
2574
2575 user <name or userid>
2576
2577 Dump userdata / masterdata for a user
2578 ====================
2579 */
2580 void Host_User_f (void) // credit: taken from QuakeWorld
2581 {
2582         int             uid;
2583         int             i;
2584
2585         if (Cmd_Argc() != 2)
2586         {
2587                 Con_Printf ("Usage: user <username / userid>\n");
2588                 return;
2589         }
2590
2591         uid = atoi(Cmd_Argv(1));
2592
2593         for (i = 0;i < cl.maxclients;i++)
2594         {
2595                 if (!cl.scores[i].name[0])
2596                         continue;
2597                 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2598                 {
2599                         InfoString_Print(cl.scores[i].qw_userinfo);
2600                         return;
2601                 }
2602         }
2603         Con_Printf ("User not in server.\n");
2604 }
2605
2606 /*
2607 ====================
2608 Host_Users_f
2609
2610 Dump userids for all current players
2611 ====================
2612 */
2613 void Host_Users_f (void) // credit: taken from QuakeWorld
2614 {
2615         int             i;
2616         int             c;
2617
2618         c = 0;
2619         Con_Printf ("userid frags name\n");
2620         Con_Printf ("------ ----- ----\n");
2621         for (i = 0;i < cl.maxclients;i++)
2622         {
2623                 if (cl.scores[i].name[0])
2624                 {
2625                         Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2626                         c++;
2627                 }
2628         }
2629
2630         Con_Printf ("%i total users\n", c);
2631 }
2632
2633 /*
2634 ==================
2635 Host_FullServerinfo_f
2636
2637 Sent by server when serverinfo changes
2638 ==================
2639 */
2640 // TODO: shouldn't this be a cvar instead?
2641 void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2642 {
2643         char temp[512];
2644         if (Cmd_Argc() != 2)
2645         {
2646                 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2647                 return;
2648         }
2649
2650         strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2651         InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2652         cl.qw_teamplay = atoi(temp);
2653 }
2654
2655 /*
2656 ==================
2657 Host_FullInfo_f
2658
2659 Allow clients to change userinfo
2660 ==================
2661 Casey was here :)
2662 */
2663 void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2664 {
2665         char key[512];
2666         char value[512];
2667         char *o;
2668         const char *s;
2669
2670         if (Cmd_Argc() != 2)
2671         {
2672                 Con_Printf ("fullinfo <complete info string>\n");
2673                 return;
2674         }
2675
2676         s = Cmd_Argv(1);
2677         if (*s == '\\')
2678                 s++;
2679         while (*s)
2680         {
2681                 o = key;
2682                 while (*s && *s != '\\')
2683                         *o++ = *s++;
2684                 *o = 0;
2685
2686                 if (!*s)
2687                 {
2688                         Con_Printf ("MISSING VALUE\n");
2689                         return;
2690                 }
2691
2692                 o = value;
2693                 s++;
2694                 while (*s && *s != '\\')
2695                         *o++ = *s++;
2696                 *o = 0;
2697
2698                 if (*s)
2699                         s++;
2700
2701                 CL_SetInfo(key, value, false, false, false, false);
2702         }
2703 }
2704
2705 /*
2706 ==================
2707 CL_SetInfo_f
2708
2709 Allow clients to change userinfo
2710 ==================
2711 */
2712 void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2713 {
2714         if (Cmd_Argc() == 1)
2715         {
2716                 InfoString_Print(cls.userinfo);
2717                 return;
2718         }
2719         if (Cmd_Argc() != 3)
2720         {
2721                 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2722                 return;
2723         }
2724         CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2725 }
2726
2727 /*
2728 ====================
2729 Host_Packet_f
2730
2731 packet <destination> <contents>
2732
2733 Contents allows \n escape character
2734 ====================
2735 */
2736 void Host_Packet_f (void) // credit: taken from QuakeWorld
2737 {
2738         char send[2048];
2739         int i, l;
2740         const char *in;
2741         char *out;
2742         lhnetaddress_t address;
2743         lhnetsocket_t *mysocket;
2744
2745         if (Cmd_Argc() != 3)
2746         {
2747                 Con_Printf ("packet <destination> <contents>\n");
2748                 return;
2749         }
2750
2751         if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2752         {
2753                 Con_Printf ("Bad address\n");
2754                 return;
2755         }
2756
2757         in = Cmd_Argv(2);
2758         out = send+4;
2759         send[0] = send[1] = send[2] = send[3] = -1;
2760
2761         l = (int)strlen (in);
2762         for (i=0 ; i<l ; i++)
2763         {
2764                 if (out >= send + sizeof(send) - 1)
2765                         break;
2766                 if (in[i] == '\\' && in[i+1] == 'n')
2767                 {
2768                         *out++ = '\n';
2769                         i++;
2770                 }
2771                 else if (in[i] == '\\' && in[i+1] == '0')
2772                 {
2773                         *out++ = '\0';
2774                         i++;
2775                 }
2776                 else if (in[i] == '\\' && in[i+1] == 't')
2777                 {
2778                         *out++ = '\t';
2779                         i++;
2780                 }
2781                 else if (in[i] == '\\' && in[i+1] == 'r')
2782                 {
2783                         *out++ = '\r';
2784                         i++;
2785                 }
2786                 else if (in[i] == '\\' && in[i+1] == '"')
2787                 {
2788                         *out++ = '\"';
2789                         i++;
2790                 }
2791                 else
2792                         *out++ = in[i];
2793         }
2794
2795         mysocket = NetConn_ChooseClientSocketForAddress(&address);
2796         if (!mysocket)
2797                 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2798         if (mysocket)
2799                 NetConn_Write(mysocket, send, out - send, &address);
2800 }
2801
2802 /*
2803 ====================
2804 Host_Pings_f
2805
2806 Send back ping and packet loss update for all current players to this player
2807 ====================
2808 */
2809 void Host_Pings_f (void)
2810 {
2811         int             i, j, ping, packetloss, movementloss;
2812         char temp[128];
2813
2814         if (!host_client->netconnection)
2815                 return;
2816
2817         if (sv.protocol != PROTOCOL_QUAKEWORLD)
2818         {
2819                 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2820                 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2821         }
2822         for (i = 0;i < svs.maxclients;i++)
2823         {
2824                 packetloss = 0;
2825                 movementloss = 0;
2826                 if (svs.clients[i].netconnection)
2827                 {
2828                         for (j = 0;j < NETGRAPH_PACKETS;j++)
2829                                 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2830                                         packetloss++;
2831                         for (j = 0;j < NETGRAPH_PACKETS;j++)
2832                                 if (svs.clients[i].movement_count[j] < 0)
2833                                         movementloss++;
2834                 }
2835                 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2836                 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2837                 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2838                 ping = bound(0, ping, 9999);
2839                 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2840                 {
2841                         // send qw_svc_updateping and qw_svc_updatepl messages
2842                         MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2843                         MSG_WriteShort(&host_client->netconnection->message, ping);
2844                         MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2845                         MSG_WriteByte(&host_client->netconnection->message, packetloss);
2846                 }
2847                 else
2848                 {
2849                         // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2850                         if(movementloss)
2851                                 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2852                         else
2853                                 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2854                         MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2855                 }
2856         }
2857         if (sv.protocol != PROTOCOL_QUAKEWORLD)
2858                 MSG_WriteString(&host_client->netconnection->message, "\n");
2859 }
2860
2861 void Host_PingPLReport_f(void)
2862 {
2863         char *errbyte;
2864         int i;
2865         int l = Cmd_Argc();
2866         if (l > cl.maxclients)
2867                 l = cl.maxclients;
2868         for (i = 0;i < l;i++)
2869         {
2870                 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2871                 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2872                 if(errbyte && *errbyte == ',')
2873                         cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2874                 else
2875                         cl.scores[i].qw_movementloss = 0;
2876         }
2877 }
2878
2879 //=============================================================================
2880
2881 /*
2882 ==================
2883 Host_InitCommands
2884 ==================
2885 */
2886 void Host_InitCommands (void)
2887 {
2888         dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
2889
2890         Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
2891         Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
2892         Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2893         Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2894         Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2895         Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2896         Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2897         Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
2898         Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
2899         Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
2900         Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
2901         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)");
2902         Cmd_AddCommand ("version", Host_Version_f, "print engine version");
2903         Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
2904         Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
2905         Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
2906         Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
2907         Cmd_AddCommand_WithClientCommand ("pause", NULL, Host_Pause_f, "pause the game (if the server allows pausing)");
2908         Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
2909         Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
2910         Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
2911         Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
2912
2913         Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
2914         Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
2915         Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
2916
2917         Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
2918         Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
2919         Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
2920         Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
2921
2922         Cvar_RegisterVariable (&cl_name);
2923         Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
2924         Cvar_RegisterVariable (&cl_color);
2925         Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
2926         Cvar_RegisterVariable (&cl_rate);
2927         Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
2928         Cvar_RegisterVariable (&cl_pmodel);
2929         Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
2930
2931         // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
2932         Cvar_RegisterVariable (&cl_playermodel);
2933         Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
2934         Cvar_RegisterVariable (&cl_playerskin);
2935         Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
2936
2937         Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
2938         Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
2939         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)");
2940         Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
2941
2942         Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
2943
2944         Cvar_RegisterVariable (&rcon_password);
2945         Cvar_RegisterVariable (&rcon_address);
2946         Cvar_RegisterVariable (&rcon_secure);
2947         Cvar_RegisterVariable (&rcon_secure_challengetimeout);
2948         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");
2949         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");
2950         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)");
2951         Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
2952         Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
2953         Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
2954         Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
2955         Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
2956         Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
2957         Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
2958         Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
2959
2960         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)");
2961         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)");
2962
2963         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)");
2964         Cvar_RegisterVariable (&r_fixtrans_auto);
2965
2966         Cvar_RegisterVariable (&team);
2967         Cvar_RegisterVariable (&skin);
2968         Cvar_RegisterVariable (&noaim);
2969
2970         Cvar_RegisterVariable(&sv_cheats);
2971         Cvar_RegisterVariable(&sv_adminnick);
2972         Cvar_RegisterVariable(&sv_status_privacy);
2973         Cvar_RegisterVariable(&sv_status_show_qcstatus);
2974         Cvar_RegisterVariable(&sv_namechangetimer);
2975 }
2976
2977 void Host_NoOperation_f(void)
2978 {
2979 }