]> git.xonotic.org Git - xonotic/darkplaces.git/blob - host_cmd.c
(Round 1) Break up host_cmd.c
[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 extern cvar_t sv_adminnick;
35 extern cvar_t sv_status_privacy;
36 extern cvar_t sv_status_show_qcstatus;
37 extern cvar_t sv_namechangetimer;
38 cvar_t rcon_password = {CVAR_CLIENT | CVAR_SERVER | CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"};
39 cvar_t rcon_secure = {CVAR_CLIENT | CVAR_SERVER | CVAR_NQUSERINFOHACK, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"};
40 cvar_t rcon_secure_challengetimeout = {CVAR_CLIENT, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
41 cvar_t rcon_address = {CVAR_CLIENT, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
42 cvar_t team = {CVAR_CLIENT | CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
43 cvar_t skin = {CVAR_CLIENT | CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
44 cvar_t noaim = {CVAR_CLIENT | CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
45 cvar_t r_fixtrans_auto = {CVAR_CLIENT, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"};
46
47 extern cvar_t developer_entityparsing;
48
49 /*
50 ==================
51 Host_Quit_f
52 ==================
53 */
54
55 void Host_Quit_f(cmd_state_t *cmd)
56 {
57         if(host.state == host_shutdown)
58                 Con_Printf("shutting down already!\n");
59         else
60                 host.state = host_shutdown;
61 }
62
63 /*
64 ==================
65 CL_Reconnect_f
66
67 This command causes the client to wait for the signon messages again.
68 This is sent just before a server changes levels
69 ==================
70 */
71 void CL_Reconnect_f(cmd_state_t *cmd)
72 {
73         char temp[128];
74         // if not connected, reconnect to the most recent server
75         if (!cls.netcon)
76         {
77                 // if we have connected to a server recently, the userinfo
78                 // will still contain its IP address, so get the address...
79                 InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
80                 if (temp[0])
81                         CL_EstablishConnection(temp, -1);
82                 else
83                         Con_Printf("Reconnect to what server?  (you have not connected to a server yet)\n");
84                 return;
85         }
86         // if connected, do something based on protocol
87         if (cls.protocol == PROTOCOL_QUAKEWORLD)
88         {
89                 // quakeworld can just re-login
90                 if (cls.qw_downloadmemory)  // don't change when downloading
91                         return;
92
93                 S_StopAllSounds();
94
95                 if (cls.state == ca_connected)
96                 {
97                         Con_Printf("Server is changing level...\n");
98                         MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
99                         MSG_WriteString(&cls.netcon->message, "new");
100                 }
101         }
102         else
103         {
104                 // netquake uses reconnect on level changes (silly)
105                 if (Cmd_Argc(cmd) != 1)
106                 {
107                         Con_Print("reconnect : wait for signon messages again\n");
108                         return;
109                 }
110                 if (!cls.signon)
111                 {
112                         Con_Print("reconnect: no signon, ignoring reconnect\n");
113                         return;
114                 }
115                 cls.signon = 0;         // need new connection messages
116         }
117 }
118
119 /*
120 =====================
121 CL_Connect_f
122
123 User command to connect to server
124 =====================
125 */
126 static void CL_Connect_f(cmd_state_t *cmd)
127 {
128         if (Cmd_Argc(cmd) < 2)
129         {
130                 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
131                 return;
132         }
133         // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
134         if(rcon_secure.integer <= 0)
135                 Cvar_SetQuick(&rcon_password, "");
136         CL_EstablishConnection(Cmd_Argv(cmd, 1), 2);
137 }
138
139
140 //============================================================================
141
142 /*
143 ======================
144 CL_Name_f
145 ======================
146 */
147 cvar_t cl_name = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
148 static void CL_Name_f(cmd_state_t *cmd)
149 {
150         prvm_prog_t *prog = SVVM_prog;
151         int i, j;
152         qboolean valid_colors;
153         const char *newNameSource;
154         char newName[sizeof(host_client->name)];
155
156         if (Cmd_Argc (cmd) == 1)
157         {
158                 if (cmd->source == src_command)
159                 {
160                         Con_Printf("name: %s\n", cl_name.string);
161                 }
162                 return;
163         }
164
165         if (Cmd_Argc (cmd) == 2)
166                 newNameSource = Cmd_Argv(cmd, 1);
167         else
168                 newNameSource = Cmd_Args(cmd);
169
170         strlcpy(newName, newNameSource, sizeof(newName));
171
172         if (cmd->source == src_command)
173         {
174                 Cvar_Set (&cvars_all, "_cl_name", newName);
175                 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
176                 {
177                         Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
178                         Con_Printf("name: %s\n", cl_name.string);
179                 }
180                 return;
181         }
182
183         if (host.realtime < host_client->nametime)
184         {
185                 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
186                 return;
187         }
188
189         host_client->nametime = host.realtime + max(0.0f, sv_namechangetimer.value);
190
191         // point the string back at updateclient->name to keep it safe
192         strlcpy (host_client->name, newName, sizeof (host_client->name));
193
194         for (i = 0, j = 0;host_client->name[i];i++)
195                 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
196                         host_client->name[j++] = host_client->name[i];
197         host_client->name[j] = 0;
198
199         if(host_client->name[0] == 1 || host_client->name[0] == 2)
200         // may interfere with chat area, and will needlessly beep; so let's add a ^7
201         {
202                 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
203                 host_client->name[sizeof(host_client->name) - 1] = 0;
204                 host_client->name[0] = STRING_COLOR_TAG;
205                 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
206         }
207
208         u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
209         if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
210         {
211                 size_t l;
212                 l = strlen(host_client->name);
213                 if(l < sizeof(host_client->name) - 1)
214                 {
215                         // duplicate the color tag to escape it
216                         host_client->name[i] = STRING_COLOR_TAG;
217                         host_client->name[i+1] = 0;
218                         //Con_DPrintf("abuse detected, adding another trailing color tag\n");
219                 }
220                 else
221                 {
222                         // remove the last character to fix the color code
223                         host_client->name[l-1] = 0;
224                         //Con_DPrintf("abuse detected, removing a trailing color tag\n");
225                 }
226         }
227
228         // find the last color tag offset and decide if we need to add a reset tag
229         for (i = 0, j = -1;host_client->name[i];i++)
230         {
231                 if (host_client->name[i] == STRING_COLOR_TAG)
232                 {
233                         if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
234                         {
235                                 j = i;
236                                 // if this happens to be a reset  tag then we don't need one
237                                 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
238                                         j = -1;
239                                 i++;
240                                 continue;
241                         }
242                         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]))
243                         {
244                                 j = i;
245                                 i += 4;
246                                 continue;
247                         }
248                         if (host_client->name[i+1] == STRING_COLOR_TAG)
249                         {
250                                 i++;
251                                 continue;
252                         }
253                 }
254         }
255         // does not end in the default color string, so add it
256         if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
257                 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
258
259         PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
260         if (strcmp(host_client->old_name, host_client->name))
261         {
262                 if (host_client->begun)
263                         SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
264                 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
265                 // send notification to all clients
266                 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
267                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
268                 MSG_WriteString (&sv.reliable_datagram, host_client->name);
269                 SV_WriteNetnameIntoDemo(host_client);
270         }
271 }
272
273 /*
274 ======================
275 CL_Playermodel_f
276 ======================
277 */
278 cvar_t cl_playermodel = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
279 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
280 static void CL_Playermodel_f(cmd_state_t *cmd)
281 {
282         prvm_prog_t *prog = SVVM_prog;
283         int i, j;
284         char newPath[sizeof(host_client->playermodel)];
285
286         if (Cmd_Argc (cmd) == 1)
287         {
288                 if (cmd->source == src_command)
289                 {
290                         Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
291                 }
292                 return;
293         }
294
295         if (Cmd_Argc (cmd) == 2)
296                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
297         else
298                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
299
300         for (i = 0, j = 0;newPath[i];i++)
301                 if (newPath[i] != '\r' && newPath[i] != '\n')
302                         newPath[j++] = newPath[i];
303         newPath[j] = 0;
304
305         if (cmd->source == src_command)
306         {
307                 Cvar_Set (&cvars_all, "_cl_playermodel", newPath);
308                 return;
309         }
310
311         /*
312         if (host.realtime < host_client->nametime)
313         {
314                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
315                 return;
316         }
317
318         host_client->nametime = host.realtime + 5;
319         */
320
321         // point the string back at updateclient->name to keep it safe
322         strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
323         PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
324         if (strcmp(host_client->old_model, host_client->playermodel))
325         {
326                 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
327                 /*// send notification to all clients
328                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
329                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
330                 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
331         }
332 }
333
334 /*
335 ======================
336 CL_Playerskin_f
337 ======================
338 */
339 cvar_t cl_playerskin = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
340 static void CL_Playerskin_f(cmd_state_t *cmd)
341 {
342         prvm_prog_t *prog = SVVM_prog;
343         int i, j;
344         char newPath[sizeof(host_client->playerskin)];
345
346         if (Cmd_Argc (cmd) == 1)
347         {
348                 if (cmd->source == src_command)
349                 {
350                         Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
351                 }
352                 return;
353         }
354
355         if (Cmd_Argc (cmd) == 2)
356                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
357         else
358                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
359
360         for (i = 0, j = 0;newPath[i];i++)
361                 if (newPath[i] != '\r' && newPath[i] != '\n')
362                         newPath[j++] = newPath[i];
363         newPath[j] = 0;
364
365         if (cmd->source == src_command)
366         {
367                 Cvar_Set (&cvars_all, "_cl_playerskin", newPath);
368                 return;
369         }
370
371         /*
372         if (host.realtime < host_client->nametime)
373         {
374                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
375                 return;
376         }
377
378         host_client->nametime = host.realtime + 5;
379         */
380
381         // point the string back at updateclient->name to keep it safe
382         strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
383         PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
384         if (strcmp(host_client->old_skin, host_client->playerskin))
385         {
386                 //if (host_client->begun)
387                 //      SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
388                 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
389                 /*// send notification to all clients
390                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
391                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
392                 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
393         }
394 }
395
396 static void Host_Version_f(cmd_state_t *cmd)
397 {
398         Con_Printf("Version: %s build %s\n", gamename, buildstring);
399 }
400
401 /*
402 ==================
403 CL_Color_f
404 ==================
405 */
406 cvar_t cl_color = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
407 static void CL_Color(cmd_state_t *cmd, int changetop, int changebottom)
408 {
409         prvm_prog_t *prog = SVVM_prog;
410         int top, bottom, playercolor;
411
412         // get top and bottom either from the provided values or the current values
413         // (allows changing only top or bottom, or both at once)
414         top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
415         bottom = changebottom >= 0 ? changebottom : cl_color.integer;
416
417         top &= 15;
418         bottom &= 15;
419         // LadyHavoc: allowing skin colormaps 14 and 15 by commenting this out
420         //if (top > 13)
421         //      top = 13;
422         //if (bottom > 13)
423         //      bottom = 13;
424
425         playercolor = top*16 + bottom;
426
427         if (cmd->source == src_command)
428         {
429                 Cvar_SetValueQuick(&cl_color, playercolor);
430                 return;
431         }
432
433         if (cls.protocol == PROTOCOL_QUAKEWORLD)
434                 return;
435
436         if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
437         {
438                 Con_DPrint("Calling SV_ChangeTeam\n");
439                 prog->globals.fp[OFS_PARM0] = playercolor;
440                 PRVM_serverglobalfloat(time) = sv.time;
441                 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
442                 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
443         }
444         else
445         {
446                 if (host_client->edict)
447                 {
448                         PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
449                         PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
450                 }
451                 host_client->colors = playercolor;
452                 if (host_client->old_colors != host_client->colors)
453                 {
454                         host_client->old_colors = host_client->colors;
455                         // send notification to all clients
456                         MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
457                         MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
458                         MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
459                 }
460         }
461 }
462
463 static void CL_Color_f(cmd_state_t *cmd)
464 {
465         int             top, bottom;
466
467         if (Cmd_Argc(cmd) == 1)
468         {
469                 if (cmd->source == src_command)
470                 {
471                         Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
472                         Con_Print("color <0-15> [0-15]\n");
473                 }
474                 return;
475         }
476
477         if (Cmd_Argc(cmd) == 2)
478                 top = bottom = atoi(Cmd_Argv(cmd, 1));
479         else
480         {
481                 top = atoi(Cmd_Argv(cmd, 1));
482                 bottom = atoi(Cmd_Argv(cmd, 2));
483         }
484         CL_Color(cmd, top, bottom);
485 }
486
487 static void CL_TopColor_f(cmd_state_t *cmd)
488 {
489         if (Cmd_Argc(cmd) == 1)
490         {
491                 if (cmd->source == src_command)
492                 {
493                         Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
494                         Con_Print("topcolor <0-15>\n");
495                 }
496                 return;
497         }
498
499         CL_Color(cmd, atoi(Cmd_Argv(cmd, 1)), -1);
500 }
501
502 static void CL_BottomColor_f(cmd_state_t *cmd)
503 {
504         if (Cmd_Argc(cmd) == 1)
505         {
506                 if (cmd->source == src_command)
507                 {
508                         Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
509                         Con_Print("bottomcolor <0-15>\n");
510                 }
511                 return;
512         }
513
514         CL_Color(cmd, -1, atoi(Cmd_Argv(cmd, 1)));
515 }
516
517 cvar_t cl_rate = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
518 cvar_t cl_rate_burstsize = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate_burstsize", "1024", "internal storage cvar for current rate control burst size (changed by rate_burstsize command)"};
519 static void CL_Rate_f(cmd_state_t *cmd)
520 {
521         int rate;
522
523         if (Cmd_Argc(cmd) != 2)
524         {
525                 if (cmd->source == src_command)
526                 {
527                         Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
528                         Con_Print("rate <bytespersecond>\n");
529                 }
530                 return;
531         }
532
533         rate = atoi(Cmd_Argv(cmd, 1));
534
535         if (cmd->source == src_command)
536         {
537                 Cvar_SetValue (&cvars_all, "_cl_rate", max(NET_MINRATE, rate));
538                 return;
539         }
540
541         host_client->rate = rate;
542 }
543
544 static void CL_Rate_BurstSize_f(cmd_state_t *cmd)
545 {
546         int rate_burstsize;
547
548         if (Cmd_Argc(cmd) != 2)
549         {
550                 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
551                 Con_Print("rate_burstsize <bytes>\n");
552                 return;
553         }
554
555         rate_burstsize = atoi(Cmd_Argv(cmd, 1));
556
557         if (cmd->source == src_command)
558         {
559                 Cvar_SetValue (&cvars_all, "_cl_rate_burstsize", rate_burstsize);
560                 return;
561         }
562
563         host_client->rate_burstsize = rate_burstsize;
564 }
565
566 /*
567 ======================
568 CL_PModel_f
569 LadyHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
570 LadyHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
571 ======================
572 */
573 cvar_t cl_pmodel = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_pmodel", "0", "internal storage cvar for current player model number in nehahra (changed by pmodel command)"};
574 static void CL_PModel_f(cmd_state_t *cmd)
575 {
576         prvm_prog_t *prog = SVVM_prog;
577         int i;
578
579         if (Cmd_Argc (cmd) == 1)
580         {
581                 if (cmd->source == src_command)
582                 {
583                         Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
584                 }
585                 return;
586         }
587         i = atoi(Cmd_Argv(cmd, 1));
588
589         if (cmd->source == src_command)
590         {
591                 if (cl_pmodel.integer == i)
592                         return;
593                 Cvar_SetValue (&cvars_all, "_cl_pmodel", i);
594                 if (cls.state == ca_connected)
595                         Cmd_ForwardToServer_f(cmd);
596                 return;
597         }
598
599         PRVM_serveredictfloat(host_client->edict, pmodel) = i;
600 }
601
602 //===========================================================================
603
604 //===========================================================================
605
606 /*
607 ===============================================================================
608
609 DEMO LOOP CONTROL
610
611 ===============================================================================
612 */
613
614
615 /*
616 ==================
617 CL_Startdemos_f
618 ==================
619 */
620 static void CL_Startdemos_f(cmd_state_t *cmd)
621 {
622         int             i, c;
623
624         if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
625                 return;
626
627         c = Cmd_Argc(cmd) - 1;
628         if (c > MAX_DEMOS)
629         {
630                 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
631                 c = MAX_DEMOS;
632         }
633         Con_DPrintf("%i demo(s) in loop\n", c);
634
635         for (i=1 ; i<c+1 ; i++)
636                 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
637
638         // LadyHavoc: clear the remaining slots
639         for (;i <= MAX_DEMOS;i++)
640                 cls.demos[i-1][0] = 0;
641
642         if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
643         {
644                 cls.demonum = 0;
645                 CL_NextDemo ();
646         }
647         else
648                 cls.demonum = -1;
649 }
650
651
652 /*
653 ==================
654 CL_Demos_f
655
656 Return to looping demos
657 ==================
658 */
659 static void CL_Demos_f(cmd_state_t *cmd)
660 {
661         if (cls.state == ca_dedicated)
662                 return;
663         if (cls.demonum == -1)
664                 cls.demonum = 1;
665         CL_Disconnect_f (cmd);
666         CL_NextDemo ();
667 }
668
669 /*
670 ==================
671 CL_Stopdemo_f
672
673 Return to looping demos
674 ==================
675 */
676 static void CL_Stopdemo_f(cmd_state_t *cmd)
677 {
678         if (!cls.demoplayback)
679                 return;
680         CL_Disconnect ();
681         SV_Shutdown ();
682 }
683
684 static void CL_SendCvar_f(cmd_state_t *cmd)
685 {
686         int             i;
687         cvar_t  *c;
688         const char *cvarname;
689         client_t *old;
690         char vabuf[1024];
691
692         if(Cmd_Argc(cmd) != 2)
693                 return;
694         cvarname = Cmd_Argv(cmd, 1);
695         if (cls.state == ca_connected)
696         {
697                 c = Cvar_FindVar(&cvars_all, cvarname, CVAR_CLIENT | CVAR_SERVER);
698                 // LadyHavoc: if there is no such cvar or if it is private, send a
699                 // reply indicating that it has no value
700                 if(!c || (c->flags & CVAR_PRIVATE))
701                         Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
702                 else
703                         Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
704                 return;
705         }
706         if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
707                 return;
708
709         old = host_client;
710         if (cls.state != ca_dedicated)
711                 i = 1;
712         else
713                 i = 0;
714         for(;i<svs.maxclients;i++)
715                 if(svs.clients[i].active && svs.clients[i].netconnection)
716                 {
717                         host_client = &svs.clients[i];
718                         SV_ClientCommands("sendcvar %s\n", cvarname);
719                 }
720         host_client = old;
721 }
722
723 /*
724 =====================
725 CL_PQRcon_f
726
727 ProQuake rcon support
728 =====================
729 */
730 static void CL_PQRcon_f(cmd_state_t *cmd)
731 {
732         int n;
733         const char *e;
734         lhnetsocket_t *mysocket;
735
736         if (Cmd_Argc(cmd) == 1)
737         {
738                 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
739                 return;
740         }
741
742         if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
743         {
744                 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
745                 return;
746         }
747
748         e = strchr(rcon_password.string, ' ');
749         n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
750
751         if (cls.netcon)
752                 cls.rcon_address = cls.netcon->peeraddress;
753         else
754         {
755                 if (!rcon_address.string[0])
756                 {
757                         Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
758                         return;
759                 }
760                 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
761         }
762         mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
763         if (mysocket)
764         {
765                 sizebuf_t buf;
766                 unsigned char bufdata[64];
767                 buf.data = bufdata;
768                 SZ_Clear(&buf);
769                 MSG_WriteLong(&buf, 0);
770                 MSG_WriteByte(&buf, CCREQ_RCON);
771                 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
772                 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
773                 MSG_WriteString(&buf, Cmd_Args(cmd));
774                 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
775                 NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address);
776                 SZ_Clear(&buf);
777         }
778 }
779
780 //=============================================================================
781
782 // QuakeWorld commands
783
784 /*
785 =====================
786 CL_Rcon_f
787
788   Send the rest of the command line over as
789   an unconnected command.
790 =====================
791 */
792 static void CL_Rcon_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
793 {
794         int i, n;
795         const char *e;
796         lhnetsocket_t *mysocket;
797
798         if (Cmd_Argc(cmd) == 1)
799         {
800                 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
801                 return;
802         }
803
804         if (!rcon_password.string || !rcon_password.string[0])
805         {
806                 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
807                 return;
808         }
809
810         e = strchr(rcon_password.string, ' ');
811         n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
812
813         if (cls.netcon)
814                 cls.rcon_address = cls.netcon->peeraddress;
815         else
816         {
817                 if (!rcon_address.string[0])
818                 {
819                         Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
820                         return;
821                 }
822                 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
823         }
824         mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
825         if (mysocket && Cmd_Args(cmd)[0])
826         {
827                 // simply put together the rcon packet and send it
828                 if(Cmd_Argv(cmd, 0)[0] == 's' || rcon_secure.integer > 1)
829                 {
830                         if(cls.rcon_commands[cls.rcon_ringpos][0])
831                         {
832                                 char s[128];
833                                 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
834                                 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]);
835                                 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
836                                 --cls.rcon_trying;
837                         }
838                         for (i = 0;i < MAX_RCONS;i++)
839                                 if(cls.rcon_commands[i][0])
840                                         if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i]))
841                                                 break;
842                         ++cls.rcon_trying;
843                         if(i >= MAX_RCONS)
844                                 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later
845                         strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(cmd), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
846                         cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address;
847                         cls.rcon_timeout[cls.rcon_ringpos] = host.realtime + rcon_secure_challengetimeout.value;
848                         cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
849                 }
850                 else if(rcon_secure.integer > 0)
851                 {
852                         char buf[1500];
853                         char argbuf[1500];
854                         dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args(cmd));
855                         memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
856                         if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n))
857                         {
858                                 buf[40] = ' ';
859                                 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
860                                 NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address);
861                         }
862                 }
863                 else
864                 {
865                         char buf[1500];
866                         memcpy(buf, "\377\377\377\377", 4);
867                         dpsnprintf(buf+4, sizeof(buf)-4, "rcon %.*s %s",  n, rcon_password.string, Cmd_Args(cmd));
868                         NetConn_WriteString(mysocket, buf, &cls.rcon_address);
869                 }
870         }
871 }
872
873 /*
874 ==================
875 CL_FullServerinfo_f
876
877 Sent by server when serverinfo changes
878 ==================
879 */
880 // TODO: shouldn't this be a cvar instead?
881 static void CL_FullServerinfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
882 {
883         char temp[512];
884         if (Cmd_Argc(cmd) != 2)
885         {
886                 Con_Printf ("usage: fullserverinfo <complete info string>\n");
887                 return;
888         }
889
890         strlcpy (cl.qw_serverinfo, Cmd_Argv(cmd, 1), sizeof(cl.qw_serverinfo));
891         InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
892         cl.qw_teamplay = atoi(temp);
893 }
894
895 /*
896 ==================
897 CL_FullInfo_f
898
899 Allow clients to change userinfo
900 ==================
901 Casey was here :)
902 */
903 static void CL_FullInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
904 {
905         char key[512];
906         char value[512];
907         const char *s;
908
909         if (Cmd_Argc(cmd) != 2)
910         {
911                 Con_Printf ("fullinfo <complete info string>\n");
912                 return;
913         }
914
915         s = Cmd_Argv(cmd, 1);
916         if (*s == '\\')
917                 s++;
918         while (*s)
919         {
920                 size_t len = strcspn(s, "\\");
921                 if (len >= sizeof(key)) {
922                         len = sizeof(key) - 1;
923                 }
924                 strlcpy(key, s, len + 1);
925                 s += len;
926                 if (!*s)
927                 {
928                         Con_Printf ("MISSING VALUE\n");
929                         return;
930                 }
931                 ++s; // Skip over backslash.
932
933                 len = strcspn(s, "\\");
934                 if (len >= sizeof(value)) {
935                         len = sizeof(value) - 1;
936                 }
937                 strlcpy(value, s, len + 1);
938
939                 CL_SetInfo(key, value, false, false, false, false);
940
941                 s += len;
942                 if (!*s)
943                 {
944                         break;
945                 }
946                 ++s; // Skip over backslash.
947         }
948 }
949
950 /*
951 ==================
952 CL_SetInfo_f
953
954 Allow clients to change userinfo
955 ==================
956 */
957 static void CL_SetInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
958 {
959         if (Cmd_Argc(cmd) == 1)
960         {
961                 InfoString_Print(cls.userinfo);
962                 return;
963         }
964         if (Cmd_Argc(cmd) != 3)
965         {
966                 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
967                 return;
968         }
969         CL_SetInfo(Cmd_Argv(cmd, 1), Cmd_Argv(cmd, 2), true, false, false, false);
970 }
971
972 /*
973 ====================
974 CL_Packet_f
975
976 packet <destination> <contents>
977
978 Contents allows \n escape character
979 ====================
980 */
981 static void CL_Packet_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
982 {
983         char send[2048];
984         int i, l;
985         const char *in;
986         char *out;
987         lhnetaddress_t address;
988         lhnetsocket_t *mysocket;
989
990         if (Cmd_Argc(cmd) != 3)
991         {
992                 Con_Printf ("packet <destination> <contents>\n");
993                 return;
994         }
995
996         if (!LHNETADDRESS_FromString (&address, Cmd_Argv(cmd, 1), sv_netport.integer))
997         {
998                 Con_Printf ("Bad address\n");
999                 return;
1000         }
1001
1002         in = Cmd_Argv(cmd, 2);
1003         out = send+4;
1004         send[0] = send[1] = send[2] = send[3] = -1;
1005
1006         l = (int)strlen (in);
1007         for (i=0 ; i<l ; i++)
1008         {
1009                 if (out >= send + sizeof(send) - 1)
1010                         break;
1011                 if (in[i] == '\\' && in[i+1] == 'n')
1012                 {
1013                         *out++ = '\n';
1014                         i++;
1015                 }
1016                 else if (in[i] == '\\' && in[i+1] == '0')
1017                 {
1018                         *out++ = '\0';
1019                         i++;
1020                 }
1021                 else if (in[i] == '\\' && in[i+1] == 't')
1022                 {
1023                         *out++ = '\t';
1024                         i++;
1025                 }
1026                 else if (in[i] == '\\' && in[i+1] == 'r')
1027                 {
1028                         *out++ = '\r';
1029                         i++;
1030                 }
1031                 else if (in[i] == '\\' && in[i+1] == '"')
1032                 {
1033                         *out++ = '\"';
1034                         i++;
1035                 }
1036                 else
1037                         *out++ = in[i];
1038         }
1039
1040         mysocket = NetConn_ChooseClientSocketForAddress(&address);
1041         if (!mysocket)
1042                 mysocket = NetConn_ChooseServerSocketForAddress(&address);
1043         if (mysocket)
1044                 NetConn_Write(mysocket, send, out - send, &address);
1045 }
1046
1047 static void CL_PingPLReport_f(cmd_state_t *cmd)
1048 {
1049         char *errbyte;
1050         int i;
1051         int l = Cmd_Argc(cmd);
1052         if (l > cl.maxclients)
1053                 l = cl.maxclients;
1054         for (i = 0;i < l;i++)
1055         {
1056                 cl.scores[i].qw_ping = atoi(Cmd_Argv(cmd, 1+i*2));
1057                 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(cmd, 1+i*2+1), &errbyte, 0);
1058                 if(errbyte && *errbyte == ',')
1059                         cl.scores[i].qw_movementloss = atoi(errbyte + 1);
1060                 else
1061                         cl.scores[i].qw_movementloss = 0;
1062         }
1063 }
1064
1065 //=============================================================================
1066
1067 /*
1068 ==================
1069 Host_InitCommands
1070 ==================
1071 */
1072 void Host_InitCommands (void)
1073 {
1074         dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
1075
1076         Cvar_RegisterVariable(&cl_name);
1077         Cvar_RegisterVariable(&cl_color);
1078         Cvar_RegisterVariable(&cl_rate);
1079         Cvar_RegisterVariable(&cl_rate_burstsize);
1080         Cvar_RegisterVariable(&cl_pmodel);
1081         Cvar_RegisterVariable(&cl_playermodel);
1082         Cvar_RegisterVariable(&cl_playerskin);
1083         Cvar_RegisterVariable(&rcon_password);
1084         Cvar_RegisterVariable(&rcon_address);
1085         Cvar_RegisterVariable(&rcon_secure);
1086         Cvar_RegisterVariable(&rcon_secure_challengetimeout);
1087         Cvar_RegisterVariable(&r_fixtrans_auto);
1088         Cvar_RegisterVariable(&team);
1089         Cvar_RegisterVariable(&skin);
1090         Cvar_RegisterVariable(&noaim);
1091
1092         // client commands - this includes server commands because the client can host a server, so they must exist
1093         Cmd_AddCommand(CMD_SHARED, "quit", Host_Quit_f, "quit the game");
1094         Cmd_AddCommand(CMD_SHARED, "version", Host_Version_f, "print engine version");
1095
1096         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "name", CL_Name_f, "change your player name");
1097         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "color", CL_Color_f, "change your player shirt and pants colors");
1098         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate", CL_Rate_f, "change your network connection speed");
1099         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate_burstsize", CL_Rate_BurstSize_f, "change your network connection speed");
1100         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "pmodel", CL_PModel_f, "(Nehahra-only) change your player model choice");
1101         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playermodel", CL_Playermodel_f, "change your player model");
1102         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playerskin", CL_Playerskin_f, "change your player skin number");
1103
1104         Cmd_AddCommand(CMD_CLIENT, "connect", CL_Connect_f, "connect to a server by IP address or hostname");
1105         Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "reconnect", CL_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)");
1106         Cmd_AddCommand(CMD_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
1107         Cmd_AddCommand(CMD_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
1108         Cmd_AddCommand(CMD_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
1109         Cmd_AddCommand(CMD_CLIENT, "sendcvar", CL_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
1110         Cmd_AddCommand(CMD_CLIENT, "rcon", CL_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");
1111         Cmd_AddCommand(CMD_CLIENT, "srcon", CL_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");
1112         Cmd_AddCommand(CMD_CLIENT, "pqrcon", CL_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)");
1113         Cmd_AddCommand(CMD_CLIENT, "fullinfo", CL_FullInfo_f, "allows client to modify their userinfo");
1114         Cmd_AddCommand(CMD_CLIENT, "setinfo", CL_SetInfo_f, "modifies your userinfo");
1115         Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "packet", CL_Packet_f, "send a packet to the specified address:port containing a text string");
1116         Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "topcolor", CL_TopColor_f, "QW command to set top color without changing bottom color");
1117         Cmd_AddCommand(CMD_CLIENT, "bottomcolor", CL_BottomColor_f, "QW command to set bottom color without changing top color");
1118         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)");
1119
1120         // commands that are only sent by server to client for execution
1121         Cmd_AddCommand(CMD_CLIENT_FROM_SERVER, "pingplreport", CL_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)");
1122         Cmd_AddCommand(CMD_CLIENT_FROM_SERVER, "fullserverinfo", CL_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
1123 }
1124
1125 void Host_NoOperation_f(cmd_state_t *cmd)
1126 {
1127 }