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