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