1 /*
2 sv_ccmds.c
3
4 (description)
5
6 Copyright (C) 1996-1997 Id Software, Inc.
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
17 See the GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to:
21
22 Free Software Foundation, Inc.
23 59 Temple Place - Suite 330
24 Boston, MA 02111-1307, USA
25
26 */
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #ifdef HAVE_STRING_H
32 # include <string.h>
33 #endif
34 #ifdef HAVE_STRINGS_H
35 # include <strings.h>
36 #endif
37
38 #include <stdlib.h>
39 #include <ctype.h>
40
41 #include "QF/cmd.h"
42 #include "QF/console.h"
43 #include "QF/cvar.h"
44 #include "QF/dstring.h"
45 #include "QF/msg.h"
46 #include "QF/qargs.h"
47 #include "QF/qendian.h"
48 #include "QF/quakefs.h"
49 #include "QF/sys.h"
50 #include "QF/va.h"
51
52 #include "qw/bothdefs.h"
53 #include "compat.h"
54 #include "server.h"
55 #include "sv_demo.h"
56 #include "sv_progs.h"
57 #include "sv_qtv.h"
58 #include "sv_recorder.h"
59
60 qboolean sv_allow_cheats;
61
62 int fp_messages = 4, fp_persecond = 4, fp_secondsdead = 10;
63 char fp_msg[255] = { 0 };
64 cvar_t *sv_leetnickmatch;
65
66 static qboolean
match_char(char a,char b)67 match_char (char a, char b)
68 {
69 a = tolower ((byte) sys_char_map[(byte) a]);
70 b = tolower ((byte) sys_char_map[(byte) b]);
71
72 if (a == b || (sv_leetnickmatch->int_val
73 && ( (a == '1' && b == 'i') || (a == 'i' && b == '1')
74 || (a == '1' && b == 'l') || (a == 'l' && b == '1')
75 || (a == '3' && b == 'e') || (a == 'e' && b == '3')
76 || (a == '4' && b == 'a') || (a == 'a' && b == '4')
77 || (a == '5' && b == 'g') || (a == 'g' && b == '5')
78 || (a == '5' && b == 'r') || (a == 'r' && b == '5')
79 || (a == '7' && b == 't') || (a == 't' && b == '7')
80 || (a == '9' && b == 'p') || (a == 'p' && b == '9')
81 || (a == '0' && b == 'o') || (a == 'o' && b == '0')
82 || (a == '$' && b == 's') || (a == 's' && b == '$'))))
83 return true;
84 return false;
85 }
86
87 /*
88 FIXME: this function and it's callers are getting progressively
89 uglier as more features are added :)
90 */
91 static client_t *
SV_Match_User(const char * substr)92 SV_Match_User (const char *substr)
93 {
94 int i, j, uid;
95 int count = 0;
96 char *str;
97 client_t *cl;
98 client_t *match = 0;
99
100 if (!substr || !*substr)
101 return 0;
102 uid = strtol (substr, &str, 10);
103
104 if (!*str) {
105 for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
106 if (cl->state < cs_zombie)
107 continue;
108 if (cl->userid == uid) {
109 SV_Printf ("User %04d matches with name: %s\n",
110 cl->userid, cl->name);
111 return cl;
112 }
113 }
114 SV_Printf ("No such uid: %d\n", uid);
115 return 0;
116 }
117
118 for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
119 if (cl->state < cs_zombie)
120 continue;
121 for (str = cl->name; *str && !match_char (*str, substr[0]); str++)
122 ;
123 while (*str) {
124 for (j = 0; substr[j] && str[j]; j++)
125 if (!match_char (substr[j], str[j]))
126 break;
127 if (!substr[j]) { // found a match;
128 match = cl;
129 count++;
130 SV_Printf ("User %04d matches with name: %s\n",
131 cl->userid, cl->name);
132 break;
133 }
134 str++;
135 }
136 }
137 if (count != 1) {
138 SV_Printf ("No unique matches, ignoring command!\n");
139 return 0;
140 }
141 return match;
142 }
143
144 /*
145 OPERATOR CONSOLE ONLY COMMANDS
146
147 These commands can be entered only from stdin or by a remote operator
148 datagram
149 */
150
151 /*
152 SV_SetMaster_f
153
154 Make a master server current
155 */
156 static void
SV_SetMaster_f(void)157 SV_SetMaster_f (void)
158 {
159 char data[2];
160 int i;
161
162 memset (&master_adr, 0, sizeof (master_adr));
163
164 for (i = 1; i < Cmd_Argc (); i++) {
165 if (i > MAX_MASTERS) {
166 SV_Printf ("Too many masters specified. Using only the first %d\n",
167 MAX_MASTERS);
168 break;
169 }
170 if (!strcmp (Cmd_Argv (i), "none")
171 || !NET_StringToAdr (Cmd_Argv (i), &master_adr[i - 1])) {
172 SV_Printf ("Setting nomaster mode.\n");
173 return;
174 }
175 if (master_adr[i - 1].port == 0)
176 master_adr[i - 1].port = BigShort (27000);
177
178 SV_Printf ("Master server at %s\n",
179 NET_AdrToString (master_adr[i - 1]));
180
181 SV_Printf ("Sending a ping.\n");
182
183 data[0] = A2A_PING;
184 data[1] = 0;
185 Netchan_SendPacket (2, data, master_adr[i - 1]);
186 }
187
188 svs.last_heartbeat = -99999;
189 }
190
191 #define RESTART_CLSTUFF \
192 "wait;wait;wait;wait;wait;"\
193 "wait;wait;wait;wait;wait;"\
194 "wait;wait;wait;reconnect\n"
195
196 static void
SV_Restart_f(void)197 SV_Restart_f (void)
198 {
199 client_t *client;
200 int j;
201
202 SZ_Clear (net_message->message);
203
204 MSG_WriteByte (net_message->message, svc_print);
205 MSG_WriteByte (net_message->message, PRINT_HIGH);
206 MSG_WriteString (net_message->message,
207 "\x9d\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
208 "\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
209 "\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
210 "\x9e\x9e\x9f\n"
211 " Server \xf2\xe5\xf3\xf4\xe1\xf2\xf4 engaged\xae\xae\xae\n"
212 "\x9d\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
213 "\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
214 "\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
215 "\x9e\x9e\x9f\n\n");
216 MSG_WriteByte (net_message->message, svc_stufftext);
217 MSG_WriteString (net_message->message, RESTART_CLSTUFF);
218 MSG_WriteByte (net_message->message, svc_disconnect);
219
220 for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) {
221 if (client->state >= cs_spawned)
222 Netchan_Transmit (&client->netchan, net_message->message->cursize,
223 net_message->message->data);
224 }
225 Sys_Printf ("Shutting down: server restart, shell must relaunch server\n");
226 SV_Shutdown ();
227 // Error code 2 on exit, indication shell must restart the server
228 exit (2);
229 }
230
231 static void
SV_Quit_f(void)232 SV_Quit_f (void)
233 {
234 SV_FinalMessage ("server shutdown\n");
235 SV_Printf ("Shutting down.\n");
236 Sys_Quit ();
237 }
238
239 static void
SV_Fraglogfile_f(void)240 SV_Fraglogfile_f (void)
241 {
242 dstring_t *name;
243
244 if (sv_fraglogfile) {
245 SV_Printf ("Frag file logging off.\n");
246 Qclose (sv_fraglogfile);
247 sv_fraglogfile = NULL;
248 return;
249 }
250 name = dstring_new ();
251 if (!QFS_NextFilename (name,
252 va ("%s/frag_", qfs_gamedir->dir.def), ".log")) {
253 SV_Printf ("Can't open any logfiles.\n");
254 sv_fraglogfile = NULL;
255 } else {
256 SV_Printf ("Logging frags to %s.\n", name->str);
257 sv_fraglogfile = QFS_WOpen (name->str, 0);
258 }
259 dstring_delete (name);
260 }
261
262 /*
263 SV_SetPlayer
264
265 Sets host_client and sv_player to the player with idnum Cmd_Argv (1)
266 */
267 static qboolean
SV_SetPlayer(void)268 SV_SetPlayer (void)
269 {
270 client_t *cl;
271 int i, idnum;
272
273 idnum = atoi (Cmd_Argv (1));
274
275 for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
276 if (cl->state < cs_zombie)
277 continue;
278 if (cl->userid == idnum) {
279 host_client = cl;
280 sv_player = host_client->edict;
281 return true;
282 }
283 }
284 SV_Printf ("Userid %i is not on the server\n", idnum);
285 return false;
286 }
287
288 /*
289 SV_God_f
290
291 Sets client to godmode
292 */
293 static void
SV_God_f(void)294 SV_God_f (void)
295 {
296 if (!sv_allow_cheats) {
297 SV_Printf
298 ("You must run the server with -cheats to enable this command.\n");
299 return;
300 }
301
302 if (!SV_SetPlayer ())
303 return;
304
305 SVfloat (sv_player, flags) = (int) SVfloat (sv_player, flags) ^ FL_GODMODE;
306 if (!((int) SVfloat (sv_player, flags) & FL_GODMODE))
307 SV_ClientPrintf (1, host_client, PRINT_HIGH, "godmode OFF\n");
308 else
309 SV_ClientPrintf (1, host_client, PRINT_HIGH, "godmode ON\n");
310 }
311
312 static void
SV_Noclip_f(void)313 SV_Noclip_f (void)
314 {
315 if (!sv_allow_cheats) {
316 SV_Printf
317 ("You must run the server with -cheats to enable this command.\n");
318 return;
319 }
320
321 if (!SV_SetPlayer ())
322 return;
323
324 if (SVfloat (sv_player, movetype) != MOVETYPE_NOCLIP) {
325 SVfloat (sv_player, movetype) = MOVETYPE_NOCLIP;
326 SV_ClientPrintf (1, host_client, PRINT_HIGH, "noclip ON\n");
327 } else {
328 SVfloat (sv_player, movetype) = MOVETYPE_WALK;
329 SV_ClientPrintf (1, host_client, PRINT_HIGH, "noclip OFF\n");
330 }
331 }
332
333 static void
SV_Give_f(void)334 SV_Give_f (void)
335 {
336 const char *t;
337 int v;
338
339 if (!sv_allow_cheats) {
340 SV_Printf
341 ("You must run the server with -cheats to enable this command.\n");
342 return;
343 }
344
345 if (!SV_SetPlayer ())
346 return;
347
348 t = Cmd_Argv (2);
349 v = atoi (Cmd_Argv (3));
350
351 switch (t[0]) {
352 case '2':
353 case '3':
354 case '4':
355 case '5':
356 case '6':
357 case '7':
358 case '8':
359 case '9':
360 SVfloat (sv_player, items) =
361 (int) SVfloat (sv_player, items) | IT_SHOTGUN << (t[0] - '2');
362 break;
363
364 case 's':
365 SVfloat (sv_player, ammo_shells) = v;
366 break;
367 case 'n':
368 SVfloat (sv_player, ammo_nails) = v;
369 break;
370 case 'r':
371 SVfloat (sv_player, ammo_rockets) = v;
372 break;
373 case 'h':
374 SVfloat (sv_player, health) = v;
375 break;
376 case 'c':
377 SVfloat (sv_player, ammo_cells) = v;
378 break;
379 }
380 }
381
382 // Use this to keep track of current level --KB
383 static dstring_t *curlevel;
384
385 const char *
SV_Current_Map(void)386 SV_Current_Map (void)
387 {
388 return curlevel ? curlevel->str : "";
389 }
390
391 static const char *
nice_time(float time)392 nice_time (float time)
393 {
394 int t = time + 0.5;
395
396 #if 0 //FIXME ditch or cvar?
397 if (t < 60) {
398 return va ("%ds", t);
399 } else if (t < 600) {
400 return va ("%dm%02ds", t / 60, t % 60);
401 } else if (t < 3600) {
402 return va ("%dm", t / 60);
403 } else if (t < 36000) {
404 t /= 60;
405 return va ("%dh%02dm", t / 60, t % 60);
406 } else if (t < 86400) {
407 return va ("%dh", t / 3600);
408 } else {
409 t /= 3600;
410 return va ("%dd%02dh", t / 24, t % 24);
411 }
412 #endif
413 if (t < 60) {
414 return va ("%ds", t);
415 } else if (t < 3600) {
416 return va ("%dm%02ds", t / 60, t % 60);
417 } else if (t < 86400) {
418 return va ("%dh%02dm%02ds", t / 3600, (t / 60) % 60, t % 60);
419 } else {
420 return va ("%dd%02dh%02dm%02ds",
421 t / 86400, (t / 3600) % 24, (t / 60) % 60, t % 60);
422 }
423 }
424
425 /*
426 SV_Map_f
427
428 handle a
429 map <mapname>
430 command from the console or progs.
431 */
432 static void
SV_Map_f(void)433 SV_Map_f (void)
434 {
435 const char *level;
436 char *expanded;
437 QFile *f;
438
439 if (!curlevel)
440 curlevel = dstring_newstr ();
441
442 if (Cmd_Argc () > 2) {
443 SV_Printf ("map <levelname> : continue game on a new level\n");
444 return;
445 }
446 if (Cmd_Argc () == 1) {
447 SV_Printf ("map is %s \"%s\" (%s)\n", curlevel->str,
448 PR_GetString (&sv_pr_state, SVstring (sv.edicts, message)),
449 nice_time (sv.time));
450 return;
451 }
452 level = Cmd_Argv (1);
453
454 // check to make sure the level exists
455 expanded = nva ("maps/%s.bsp", level);
456 QFS_FOpenFile (expanded, &f);
457 if (!f) {
458 SV_Printf ("Can't find %s\n", expanded);
459 free (expanded);
460 return;
461 }
462 Qclose (f);
463 free (expanded);
464
465 if (sv.recording_demo)
466 SV_Stop (0);
467
468 SV_qtvChanging ();
469 SV_BroadcastCommand ("changing\n");
470 SV_SendMessagesToAll ();
471
472 dstring_copystr (curlevel, level);
473 SV_SpawnServer (level);
474
475 SV_qtvReconnect ();
476 SV_BroadcastCommand ("reconnect\n");
477 }
478
479 /*
480 SV_Kick_f
481
482 Kick a user off of the server
483 */
484 static void
SV_Kick_f(void)485 SV_Kick_f (void)
486 {
487 client_t *cl;
488 int argc = Cmd_Argc ();
489 const char *reason;
490
491 if (argc < 2) {
492 SV_Printf ("usage: kick <name/userid>\n");
493 return;
494 }
495 if (!(cl = SV_Match_User (Cmd_Argv (1))))
496 return;
497
498 // print directly, because the dropped client won't get the
499 // SV_BroadcastPrintf message
500 if (argc > 2) {
501 reason = Cmd_Args (2);
502 SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked: %s\n", cl->name,
503 reason);
504 SV_ClientPrintf (1, cl, PRINT_HIGH,
505 "You were kicked from the game: %s\n", reason);
506 } else {
507 SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked\n", cl->name);
508 SV_ClientPrintf (1, cl, PRINT_HIGH, "You were kicked from the game\n");
509 }
510 SV_DropClient (cl);
511 }
512
513 void
SV_Status_f(void)514 SV_Status_f (void)
515 {
516 int i;
517 client_t *cl;
518 float cpu, avg, pak, demo = 0;
519 const char *s;
520
521
522 cpu = (svs.stats.latched_active + svs.stats.latched_idle);
523 if (cpu) {
524 demo = 100 * svs.stats.latched_demo / cpu;
525 cpu = 100 * svs.stats.latched_active / cpu;
526 }
527 avg = 1000 * svs.stats.latched_active / STATFRAMES;
528 pak = (float) svs.stats.latched_packets / STATFRAMES;
529
530 SV_Printf ("net address : %s\n", NET_AdrToString (net_local_adr));
531 SV_Printf ("uptime : %s\n", nice_time (Sys_DoubleTime ()));
532 SV_Printf ("cpu utilization : %3i%% (%3i%%)\n", (int) cpu, (int)demo);
533 SV_Printf ("avg response time: %i ms\n", (int) avg);
534 SV_Printf ("packets/frame : %5.2f\n", pak);
535
536 // min fps lat drp
537 if (sv_redirected != RD_NONE && sv_redirected != RD_MOD) {
538 // most remote clients are 40 columns
539 // 0123456789012345678901234567890123456789
540 SV_Printf ("name userid frags\n");
541 SV_Printf (" address rate ping drop\n");
542 SV_Printf (" ---------------- ---- ---- -----\n");
543 for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
544 if (!cl->state)
545 continue;
546
547 SV_Printf ("%-16.16s ", cl->name);
548
549 SV_Printf ("%6i %5i", cl->userid, (int) SVfloat (cl->edict, frags));
550 if (cl->spectator)
551 SV_Printf (" (s)\n");
552 else
553 SV_Printf ("\n");
554
555 s = NET_BaseAdrToString (cl->netchan.remote_address);
556 SV_Printf (" %-16.16s", s);
557 if (cl->state == cs_connected) {
558 SV_Printf ("CONNECTING\n");
559 continue;
560 }
561 if (cl->state == cs_zombie) {
562 SV_Printf ("ZOMBIE\n");
563 continue;
564 }
565 if (cl->state == cs_server) {
566 SV_Printf ("SERVER %d\n", cl->ping);
567 continue;
568 }
569 SV_Printf ("%4i %4i %5.2f\n",
570 (int) (1000 * cl->netchan.frame_rate),
571 (int) SV_CalcPing (cl),
572 100.0 * cl->netchan.drop_count /
573 cl->netchan.incoming_sequence);
574 }
575 } else {
576 SV_Printf ("frags userid address name rate ping "
577 "drop qport\n");
578 SV_Printf ("----- ------ --------------- --------------- ---- ---- "
579 "----- -----\n");
580 for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
581 if (!cl->state)
582 continue;
583 SV_Printf ("%5i %6i ", (int) SVfloat (cl->edict, frags),
584 cl->userid);
585
586 s = NET_BaseAdrToString (cl->netchan.remote_address);
587
588 SV_Printf ("%-15.15s ", s);
589
590 SV_Printf ("%-15.15s ", cl->name);
591
592 if (cl->state == cs_connected) {
593 SV_Printf ("CONNECTING\n");
594 continue;
595 }
596 if (cl->state == cs_zombie) {
597 SV_Printf ("ZOMBIE\n");
598 continue;
599 }
600 if (cl->state == cs_server) {
601 SV_Printf ("SERVER %d\n", cl->ping);
602 continue;
603 }
604 SV_Printf ("%4i %4i %3.1f %4i",
605 (int) (1000 * cl->netchan.frame_rate),
606 (int) SV_CalcPing (cl),
607 100.0 * cl->netchan.drop_count /
608 cl->netchan.incoming_sequence, cl->netchan.qport);
609 if (cl->spectator)
610 SV_Printf (" (s)\n");
611 else
612 SV_Printf ("\n");
613 }
614 }
615 SV_Printf ("\n");
616 }
617
618 #define MAXPENALTY 10.0
619
620 static void
SV_Punish(int mode)621 SV_Punish (int mode)
622 {
623 int i;
624 double mins = 0.5;
625 qboolean all = false, done = false;
626 client_t *cl = 0;
627 dstring_t *text = dstring_new();
628 const char *cmd = 0;
629 const char *cmd_do = 0;
630 //FIXME const char *cmd_undo = 0;
631 int field_offs = 0;
632
633 switch (mode) {
634 case 0:
635 cmd = "cuff";
636 cmd_do = "cuffed";
637 //FIXME cmd_undo = "un-cuffed";
638 field_offs = field_offset (client_t, cuff_time);
639 break;
640 case 1:
641 cmd = "mute";
642 cmd_do = "muted";
643 //FIXME cmd_undo = "can speak";
644 field_offs = field_offset (client_t, lockedtill);
645 break;
646 }
647
648 if (Cmd_Argc () != 2 && Cmd_Argc () != 3) {
649 SV_Printf ("usage: %s <name/userid/ALL> [minutes]\n"
650 " (default = 0.5, 0 = cancel cuff).\n", cmd);
651 return;
652 }
653
654 if (strequal (Cmd_Argv (1), "ALL")) {
655 all = true;
656 } else {
657 cl = SV_Match_User (Cmd_Argv (1));
658 }
659 if (!all && !cl)
660 return;
661 if (Cmd_Argc () == 3) {
662 mins = atof (Cmd_Argv (2));
663 if (mins < 0.0 || mins > MAXPENALTY)
664 mins = MAXPENALTY;
665 }
666 if (cl) {
667 *(double *)((char *)cl + field_offs) = realtime + mins * 60.0;
668 if (mins) {
669 dsprintf (text, "You are %s for %.1f minutes\n\n"
670 "reconnecting won't help...\n", cmd_do, mins);
671 MSG_ReliableWrite_Begin (&cl->backbuf, svc_centerprint,
672 2 + strlen (text->str));
673 MSG_ReliableWrite_String (&cl->backbuf, text->str);
674 }
675 }
676 if (all) {
677 for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
678 if (cl->state < cs_zombie)
679 continue;
680 *(double *)((char *)cl + field_offs) = realtime + mins * 60.0;
681 done = true;
682 if (mins) {
683 dsprintf (text, "You are %s for %.1f minutes\n\n"
684 "reconnecting won't help...\n", cmd_do, mins);
685 MSG_ReliableWrite_Begin (&cl->backbuf, svc_centerprint,
686 2 + strlen (text->str));
687 MSG_ReliableWrite_String (&cl->backbuf, text->str);
688 }
689 }
690 }
691 if (done) {
692 if (mins)
693 SV_BroadcastPrintf (PRINT_HIGH, "%s %s for %.1f minutes.\n",
694 all? "All Users" : cl->name, cmd_do, mins);
695 else
696 SV_BroadcastPrintf (PRINT_HIGH, "%s %s.\n",
697 all? "All Users" : cl->name, cmd_do);
698 }
699 dstring_delete (text);
700 }
701
702 static void
SV_Cuff_f(void)703 SV_Cuff_f (void)
704 {
705 SV_Punish (0);
706 }
707
708
709 static void
SV_Mute_f(void)710 SV_Mute_f (void)
711 {
712 SV_Punish (1);
713 }
714
715 static void
SV_Ban_f(void)716 SV_Ban_f (void)
717 {
718 double mins = 30.0, m;
719 client_t *cl;
720 char *e;
721 const char *a, *reason;
722 int argc = Cmd_Argc (), argr = 2;
723
724 if (argc < 2) {
725 SV_Printf ("usage: ban <name/userid> [minutes] [reason]\n"
726 " (default = 30, 0 = permanent).\n");
727 return;
728 }
729 if (!(cl = SV_Match_User (Cmd_Argv (1))))
730 return;
731 if (argc >= 3) {
732 a = Cmd_Argv (2);
733 m = strtod (a, &e);
734 if (e != a) {
735 argr++;
736 mins = m;
737 if (mins < 0.0 || mins > 1000000.0) // bout 2 yrs
738 mins = 0.0;
739 }
740 }
741 if (argc > argr) {
742 reason = Cmd_Args (argr);
743 SV_BroadcastPrintf (PRINT_HIGH, "Admin Banned user %s %s: %s\n",
744 cl->name, mins ? va ("for %.1f minutes", mins)
745 : "permanently", reason);
746 } else {
747 SV_BroadcastPrintf (PRINT_HIGH, "Admin Banned user %s %s\n",
748 cl->name, mins ? va ("for %.1f minutes", mins)
749 : "permanently");
750 }
751 SV_DropClient (cl);
752 Cmd_ExecuteString (va ("addip %s %f",
753 NET_BaseAdrToString (cl->netchan.remote_address),
754 mins), src_command);
755 }
756
757 static void
SV_Match_f(void)758 SV_Match_f (void)
759 {
760 if (Cmd_Argc () != 2) {
761 SV_Printf ("usage: match <name/userid>\n");
762 return;
763 }
764
765 if (!SV_Match_User (Cmd_Argv (1)))
766 SV_Printf ("No unique uid/name matched\n");
767 }
768
769
770 static void
SV_ConSay(const char * prefix,client_t * client)771 SV_ConSay (const char *prefix, client_t *client)
772 {
773 char *p;
774 dstring_t *text;
775 int j;
776
777 if (Cmd_Argc () < 2)
778 return;
779
780 p = Hunk_TempAlloc (strlen (Cmd_Args (1)) + 1);
781 strcpy (p, Cmd_Args (1));
782 if (*p == '"') {
783 p++;
784 p[strlen (p) - 1] = 0;
785 }
786 text = dstring_new ();
787 dstring_copystr (text, prefix);
788 dstring_appendstr (text, "\x8d "); // arrow
789 j = strlen (text->str);
790 dstring_appendstr (text, p);
791 SV_Printf ("%s\n", text->str);
792 while (text->str[j])
793 text->str[j++] |= 0x80; // non-bold text
794
795 if (client) {
796 if (client->state >= cs_zombie) {
797 SV_ClientPrintf (1, client, PRINT_CHAT, "\n"); // bell
798 SV_ClientPrintf (1, client, PRINT_HIGH, "%s\n", text->str);
799 SV_ClientPrintf (1, client, PRINT_CHAT, "%s", ""); // bell
800 }
801 } else {
802 for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) {
803 if (client->state < cs_zombie)
804 continue;
805 SV_ClientPrintf (0, client, PRINT_HIGH, "%s\n", text->str);
806 if (*prefix != 'I') // beep, except for Info says
807 SV_ClientPrintf (0, client, PRINT_CHAT, "%s", "");
808 }
809 if (sv.recorders) {
810 sizebuf_t *dbuf;
811 dbuf = SVR_WriteBegin (dem_all, 0, strlen (text->str) + 7);
812 MSG_WriteByte (dbuf, svc_print);
813 MSG_WriteByte (dbuf, PRINT_HIGH);
814 MSG_WriteString (dbuf, va ("%s\n", text->str));
815 MSG_WriteByte (dbuf, svc_print);
816 MSG_WriteByte (dbuf, PRINT_CHAT);
817 MSG_WriteString (dbuf, "");
818 }
819 }
820 }
821
822 static void
SV_Tell_f(void)823 SV_Tell_f (void)
824 {
825 client_t *cl;
826
827 if (Cmd_Argc () < 3) {
828 SV_Printf ("usage: tell <name/userid> <text...>\n");
829 return;
830 }
831 if (!(cl = SV_Match_User (Cmd_Argv (1))))
832 return;
833
834 if (rcon_from_user)
835 SV_ConSay ("[\xd0\xd2\xc9\xd6\xc1\xd4\xc5] Admin", cl);
836 else
837 SV_ConSay ("[\xd0\xd2\xc9\xd6\xc1\xd4\xc5] Console", cl);
838 }
839
840 static void
SV_ConSay_f(void)841 SV_ConSay_f (void)
842 {
843 if (rcon_from_user)
844 SV_ConSay ("Admin", 0);
845 else
846 SV_ConSay ("Console", 0);
847 }
848
849 static void
SV_ConSay_Info_f(void)850 SV_ConSay_Info_f (void)
851 {
852 SV_ConSay ("Info", 0);
853 }
854
855 static void
SV_Heartbeat_f(void)856 SV_Heartbeat_f (void)
857 {
858 svs.last_heartbeat = -9999;
859 }
860
861 static void
SV_SendServerInfoChange(const char * key,const char * value)862 SV_SendServerInfoChange (const char *key, const char *value)
863 {
864 if (!sv.state)
865 return;
866
867 MSG_WriteByte (&sv.reliable_datagram, svc_serverinfo);
868 MSG_WriteString (&sv.reliable_datagram, key);
869 MSG_WriteString (&sv.reliable_datagram, value);
870 }
871
872 void
Cvar_Info(cvar_t * var)873 Cvar_Info (cvar_t *var)
874 {
875 if (var->flags & CVAR_SERVERINFO) {
876 Info_SetValueForKey (svs.info, var->name, var->string,
877 (!sv_highchars || !sv_highchars->int_val));
878
879 SV_SendServerInfoChange (var->name, var->string);
880 }
881 }
882
883 /*
884 SV_Serverinfo_f
885
886 Examine or change the serverinfo string
887 */
888 static void
SV_Serverinfo_f(void)889 SV_Serverinfo_f (void)
890 {
891 cvar_t *var;
892 const char *key;
893 const char *value;
894
895 switch (Cmd_Argc ()) {
896 case 1:
897 SV_Printf ("Server info settings:\n");
898 Info_Print (svs.info);
899 return;
900 case 2:
901 key = Cmd_Argv (1);
902 value = Info_ValueForKey (svs.info, key);
903 if (Info_Key (svs.info, key))
904 SV_Printf ("Server info %s: \"%s\"\n", key, value);
905 else
906 SV_Printf ("No such key %s\n", Cmd_Argv(1));
907 return;
908 case 3:
909 key = Cmd_Argv (1);
910 value = Cmd_Argv (2);
911 break;
912 default:
913 SV_Printf ("usage: serverinfo [ <key> <value> ]\n");
914 return;
915 }
916
917 if (Cmd_Argv (1)[0] == '*') {
918 SV_Printf ("Star variables cannot be changed.\n");
919 return;
920 }
921
922 // if this is a cvar, change it too
923 var = Cvar_FindVar (key);
924 if (var && (var->flags & CVAR_SERVERINFO)) {
925 Cvar_Set (var, value);
926 } else {
927 Info_SetValueForKey (svs.info, key, value, !sv_highchars->int_val);
928 SV_SendServerInfoChange (key, value);
929 }
930 }
931
932 void
SV_SetLocalinfo(const char * key,const char * value)933 SV_SetLocalinfo (const char *key, const char *value)
934 {
935 char *oldvalue = 0;
936
937 if (sv_funcs.LocalinfoChanged)
938 oldvalue = strdup (Info_ValueForKey (localinfo, key));
939
940 if (*value)
941 Info_SetValueForKey (localinfo, key, value, !sv_highchars->int_val);
942 else
943 Info_RemoveKey (localinfo, key);
944
945 if (sv_funcs.LocalinfoChanged) {
946 *sv_globals.time = sv.time;
947 *sv_globals.self = 0;
948 PR_PushFrame (&sv_pr_state);
949 PR_RESET_PARAMS (&sv_pr_state);
950 P_STRING (&sv_pr_state, 0) = PR_SetTempString (&sv_pr_state, key);
951 P_STRING (&sv_pr_state, 1) = PR_SetTempString (&sv_pr_state, oldvalue);
952 P_STRING (&sv_pr_state, 2) = PR_SetTempString (&sv_pr_state, value);
953 PR_ExecuteProgram (&sv_pr_state, sv_funcs.LocalinfoChanged);
954 PR_PopFrame (&sv_pr_state);
955 }
956
957 if (oldvalue)
958 free (oldvalue);
959 }
960
961 /*
962 SV_Serverinfo_f
963
964 Examine or change the serverinfo string
965 */
966 static void
SV_Localinfo_f(void)967 SV_Localinfo_f (void)
968 {
969 const char *key;
970 const char *value;
971
972 switch (Cmd_Argc ()) {
973 case 1:
974 SV_Printf ("Local info settings:\n");
975 Info_Print (localinfo);
976 return;
977 case 2:
978 key = Cmd_Argv (1);
979 value = Info_ValueForKey (localinfo, key);
980 if (Info_Key (localinfo, key))
981 SV_Printf ("Localinfo %s: \"%s\"\n", key, value);
982 else
983 SV_Printf ("No such key %s\n", Cmd_Argv(1));
984 return;
985 case 3:
986 key = Cmd_Argv (1);
987 value = Cmd_Argv (2);
988 break;
989 default:
990 SV_Printf ("usage: localinfo [ <key> <value> ]\n");
991 return;
992 }
993
994 if (key[0] == '*') {
995 SV_Printf ("Star variables cannot be changed.\n");
996 return;
997 }
998 SV_SetLocalinfo (key, value);
999 }
1000
1001 /*
1002 SV_User_f
1003
1004 Examine a users info strings
1005 */
1006 static void
SV_User_f(void)1007 SV_User_f (void)
1008 {
1009 if (Cmd_Argc () != 2) {
1010 SV_Printf ("Usage: user <userid>\n");
1011 return;
1012 }
1013
1014 if (!SV_SetPlayer ())
1015 return;
1016
1017 Info_Print (host_client->userinfo);
1018 }
1019
1020 /*
1021 SV_Gamedir
1022
1023 Sets the fake *gamedir to a different directory.
1024 */
1025 static void
SV_Gamedir(void)1026 SV_Gamedir (void)
1027 {
1028 const char *dir;
1029
1030 if (Cmd_Argc () == 1) {
1031 SV_Printf ("Current *gamedir: %s\n",
1032 Info_ValueForKey (svs.info, "*gamedir"));
1033 return;
1034 }
1035
1036 if (Cmd_Argc () != 2) {
1037 SV_Printf ("Usage: sv_gamedir <newgamedir>\n");
1038 return;
1039 }
1040
1041 dir = Cmd_Argv (1);
1042
1043 if (strstr (dir, "..") || strstr (dir, "/")
1044 || strstr (dir, "\\") || strstr (dir, ":")) {
1045 SV_Printf ("*Gamedir should be a single filename, not a path\n");
1046 return;
1047 }
1048
1049 Info_SetValueForStarKey (svs.info, "*gamedir", dir,
1050 !sv_highchars->int_val);
1051 }
1052
1053 /*
1054 SV_Floodport_f
1055
1056 Sets the gamedir and path to a different directory.
1057 */
1058 static void
SV_Floodprot_f(void)1059 SV_Floodprot_f (void)
1060 {
1061 int arg1, arg2, arg3;
1062
1063 if (Cmd_Argc () == 1) {
1064 if (fp_messages) {
1065 SV_Printf
1066 ("Current floodprot settings: \nAfter %d msgs per %d seconds, "
1067 "silence for %d seconds\n",
1068 fp_messages, fp_persecond, fp_secondsdead);
1069 return;
1070 } else
1071 SV_Printf ("No floodprots enabled.\n");
1072 }
1073
1074 if (Cmd_Argc () != 4) {
1075 SV_Printf
1076 ("Usage: floodprot <# of messages> <per # of seconds> <seconds to "
1077 "silence>\n");
1078 SV_Printf
1079 ("Use floodprotmsg to set a custom message to say to the "
1080 "flooder.\n");
1081 return;
1082 }
1083
1084 arg1 = atoi (Cmd_Argv (1));
1085 arg2 = atoi (Cmd_Argv (2));
1086 arg3 = atoi (Cmd_Argv (3));
1087
1088 if (arg1 <= 0 || arg2 <= 0 || arg3 <= 0) {
1089 SV_Printf ("All values must be positive numbers\n");
1090 return;
1091 }
1092
1093 if (arg1 > 10) {
1094 SV_Printf ("Can track up to only 10 messages.\n");
1095 return;
1096 }
1097
1098 fp_messages = arg1;
1099 fp_persecond = arg2;
1100 fp_secondsdead = arg3;
1101 }
1102
1103 static void
SV_Floodprotmsg_f(void)1104 SV_Floodprotmsg_f (void)
1105 {
1106 if (Cmd_Argc () == 1) {
1107 SV_Printf ("Current msg: %s\n", fp_msg);
1108 return;
1109 } else if (Cmd_Argc () != 2) {
1110 SV_Printf ("Usage: floodprotmsg \"<message>\"\n");
1111 return;
1112 }
1113 snprintf (fp_msg, sizeof (fp_msg), "%s", Cmd_Argv (1));
1114 }
1115
1116 static void
SV_Snap(int uid)1117 SV_Snap (int uid)
1118 {
1119 client_t *cl;
1120 int i;
1121
1122 for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
1123 if (cl->state < cs_zombie)
1124 continue;
1125 if (cl->userid == uid)
1126 break;
1127 }
1128 if (i >= MAX_CLIENTS) {
1129 SV_Printf ("userid not found\n");
1130 return;
1131 }
1132
1133 if (!cl->uploadfn)
1134 cl->uploadfn = dstring_new ();
1135
1136 if (!QFS_NextFilename (cl->uploadfn,
1137 va ("%s/snap/%d-", qfs_gamedir->dir.def, uid),
1138 ".pcx")) {
1139 SV_Printf ("Snap: Couldn't create a file, clean some out.\n");
1140 dstring_delete (cl->uploadfn);
1141 cl->uploadfn = 0;
1142 return;
1143 }
1144
1145 memcpy (&cl->snap_from, &net_from, sizeof (net_from));
1146 if (sv_redirected != RD_NONE)
1147 cl->remote_snap = true;
1148 else
1149 cl->remote_snap = false;
1150
1151 MSG_ReliableWrite_Begin (&cl->backbuf, svc_stufftext, 24);
1152 MSG_ReliableWrite_String (&cl->backbuf, "cmd snap\n");
1153 SV_Printf ("Requesting snap from user %d...\n", uid);
1154 }
1155
1156 static void
SV_Snap_f(void)1157 SV_Snap_f (void)
1158 {
1159 int uid;
1160
1161 if (Cmd_Argc () != 2) {
1162 SV_Printf ("Usage: snap <userid>\n");
1163 return;
1164 }
1165
1166 uid = atoi (Cmd_Argv (1));
1167
1168 SV_Snap (uid);
1169 }
1170
1171 static void
SV_SnapAll_f(void)1172 SV_SnapAll_f (void)
1173 {
1174 client_t *cl;
1175 int i;
1176
1177 for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
1178 if (cl->state < cs_connected || cl->spectator)
1179 continue;
1180 SV_Snap (cl->userid);
1181 }
1182 }
1183
1184 void
SV_InitOperatorCommands(void)1185 SV_InitOperatorCommands (void)
1186 {
1187 if (COM_CheckParm ("-cheats")) {
1188 sv_allow_cheats = true;
1189 Info_SetValueForStarKey (svs.info, "*cheats", "ON", 0);
1190 }
1191
1192 Cmd_AddCommand ("fraglogfile", SV_Fraglogfile_f, "Enables logging of kills "
1193 "to frag_##.log");
1194
1195 Cmd_AddCommand ("snap", SV_Snap_f, "Take a screenshot of userid");
1196 Cmd_AddCommand ("snapall", SV_SnapAll_f, "Take a screenshot of all users");
1197 Cmd_AddCommand ("kick", SV_Kick_f, "Remove a user from the server (kick "
1198 "userid)");
1199 Cmd_AddCommand ("status", SV_Status_f, "Report information on the current "
1200 "connected clients and the server - displays userids");
1201
1202 Cmd_AddCommand ("map", SV_Map_f, "Change to a new map (map mapname)");
1203 Cmd_AddCommand ("setmaster", SV_SetMaster_f, "Lists the server with up to "
1204 "eight masters.\n"
1205 "When a server is listed with a master, the master is "
1206 "aware of the server's IP address and port and it is added "
1207 "to the\n"
1208 "list of current servers connected to a master. A "
1209 "heartbeat is sent to the master from the server to "
1210 "indicated that the\n"
1211 "server is still running and alive.\n\n"
1212 "Examples:\n"
1213 "setmaster 192.246.40.12:27002\n"
1214 "setmaster 192.246.40.12:27002 192.246.40.12:27004");
1215 Cmd_AddCommand ("heartbeat", SV_Heartbeat_f, "Force a heartbeat to be sent "
1216 "to the master server.\n"
1217 "A heartbeat tells the Master the server's IP address and "
1218 "that it is still alive.");
1219 Cmd_AddCommand ("restart", SV_Restart_f, "Restart the server (with shell "
1220 "support)");
1221 Cmd_AddCommand ("quit", SV_Quit_f, "Shut down the server");
1222 Cmd_AddCommand ("god", SV_God_f, "Toggle god cheat to userid (god userid) "
1223 "Requires cheats are enabled");
1224 Cmd_AddCommand ("give", SV_Give_f, "Give userid items, or health.\n"
1225 "Items: 1 Axe, 2 Shotgun, 3 Double-Barrelled Shotgun, 4 "
1226 "Nailgun, 5 Super Nailgun, 6 Grenade Launcher, 7 Rocket "
1227 "Launcher,\n"
1228 "8 ThunderBolt, C Cells, H Health, N Nails, R Rockets, S "
1229 "Shells. Requires cheats to be enabled. (give userid item "
1230 "amount)");
1231 Cmd_AddCommand ("noclip", SV_Noclip_f, "Toggle no clipping cheat for "
1232 "userid. Requires cheats to be enabled. (noclip userid)");
1233 Cmd_AddCommand ("serverinfo", SV_Serverinfo_f, "Reports or sets "
1234 "information about server.\n"
1235 "The information stored in this space is broadcast on the "
1236 "network to all players.\n"
1237 "Values:\n"
1238 "dq - Drop Quad Damage when a player dies.\n"
1239 "dr - Drop Ring of Shadows when a player dies.\n"
1240 "rj - Sets the multiplier rate for splash damage kick.\n"
1241 "needpass - Displays the passwords enabled on the server.\n"
1242 "watervis - Toggle the use of r_watervis by OpenGL "
1243 "clients.\n"
1244 "Note: Keys with (*) in front cannot be changed. Maximum "
1245 "key size cannot exceed 64-bytes.\n"
1246 "Maximum size for all keys cannot exceed 512-bytes.\n"
1247 "(serverinfo key value)");
1248 Cmd_AddCommand ("localinfo", SV_Localinfo_f, "Shows or sets localinfo "
1249 "variables.\n"
1250 "Useful for mod programmers who need to allow the admin to "
1251 "change settings.\n"
1252 "This is an alternative storage space to the serverinfo "
1253 "space for mod variables.\n"
1254 "The variables stored in this space are not broadcast on "
1255 "the network.\n"
1256 "This space also has a 32-kilobyte limit which is much "
1257 "greater then the 512-byte limit on the serverinfo space.\n"
1258 "Special Keys: (current map) (next map) - Using this "
1259 "combination will allow the creation of a custom map cycle "
1260 "without editing code.\n\n"
1261 "Example:\n"
1262 "localinfo dm2 dm4\n"
1263 "localinfo dm4 dm6\n"
1264 "localinfo dm6 dm2\n"
1265 "(localinfo key value)");
1266 Cmd_AddCommand ("user", SV_User_f, "Report information about the user "
1267 "(user userid)");
1268 Cmd_AddCommand ("sv_gamedir", SV_Gamedir, "Displays or determines the "
1269 "value of the serverinfo *gamedir variable.\n"
1270 "Note: Useful when the physical gamedir directory has a "
1271 "different name than the widely accepted gamedir "
1272 "directory.\n"
1273 "Example:\n"
1274 "gamedir tf2_5; sv_gamedir fortress\n"
1275 "gamedir ctf4_2; sv_gamedir ctf\n"
1276 "(sv_gamedir dirname)");
1277 Cmd_AddCommand ("floodprot", SV_Floodprot_f, "Sets the options for flood "
1278 "protection.\n"
1279 "Default: 4 4 10\n"
1280 "(floodprot (number of messages) (number of seconds) "
1281 "(silence time in seconds))");
1282 Cmd_AddCommand ("floodprotmsg", SV_Floodprotmsg_f, "Sets the message "
1283 "displayed after flood protection is invoked (floodprotmsg "
1284 "message)");
1285 Cmd_AddCommand ("maplist", Con_Maplist_f, "List all maps on the server");
1286 Cmd_AddCommand ("say", SV_ConSay_f, "Say something to everyone on the "
1287 "server. Will show up as the name 'Console' (or 'Admin') "
1288 "in game");
1289 Cmd_AddCommand ("sayinfo", SV_ConSay_Info_f, "Say something to everyone on "
1290 "the server. Will show up as the name 'Info' in game");
1291 Cmd_AddCommand ("tell", SV_Tell_f, "Say something to a specific user on "
1292 "the server. Will show up as the name 'Console' (or "
1293 "'Admin') in game");
1294 Cmd_AddCommand ("ban", SV_Ban_f, "ban a player for a specified time");
1295 Cmd_AddCommand ("cuff", SV_Cuff_f, "\"hand-cuff\" a player for a "
1296 "specified time");
1297 Cmd_AddCommand ("mute", SV_Mute_f, "silience a player for a specified "
1298 "time");
1299 Cmd_AddCommand ("match", SV_Match_f, "matches nicks as ban/cuff/mute "
1300 "commands do, so you can check safely");
1301
1302 // poor description
1303 sv_leetnickmatch = Cvar_Get ("sv_3133735_7h4n_7h0u", "1", CVAR_NONE, NULL,
1304 "Match '1' as 'i' and such in nicks");
1305 }
1306