1 /*
2 * XPilot NG, a multiplayer space war game.
3 *
4 * Copyright (C) 2000-2004 by
5 *
6 * Uoti Urpala <uau@users.sourceforge.net>
7 * Erik Andersson <maximan@users.sourceforge.net>
8 * Kristian S�derblom <kps@users.sourceforge.net>
9 *
10 * Copyright (C) 1991-2001 by
11 *
12 * Bj�rn Stabell <bjoern@xpilot.org>
13 * Ken Ronny Schouten <ken@xpilot.org>
14 * Bert Gijsbers <bert@xpilot.org>
15 * Dick Balaska <dick@xpilot.org>
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30 */
31
32 #include "xpserver.h"
33
34
35 /*
36 * Look if any player's name is exactly 'str',
37 * If not, look if any player's name contains 'str'.
38 * The matching is case insensitive. If there is an
39 * error (no matches or several matches) NULL is returned
40 * and the error code is stored in 'error' if that is not NULL
41 * and a string describing the error is stored in
42 * 'errorstr_p' if that is not NULL.
43 */
Get_player_by_name(const char * str,int * error_p,const char ** errorstr_p)44 player_t *Get_player_by_name(const char *str,
45 int *error_p, const char **errorstr_p)
46 {
47 int i, id;
48 player_t *found_pl = NULL, *pl;
49 size_t len;
50
51 if (str == NULL || (len = strlen(str)) == 0)
52 goto match_none;
53
54 /* Get player by id */
55 id = atoi(str);
56 if (id > 0) {
57 found_pl = Player_by_id(id);
58 if (!found_pl)
59 goto match_none;
60 return found_pl;
61 }
62
63 /* Look for an exact match on player nickname. */
64 for (i = 0; i < NumPlayers; i++) {
65 pl = Player_by_index(i);
66 if (!strcasecmp(pl->name, str))
67 return pl;
68 }
69
70 /* Look if 'str' matches beginning of only one nick. */
71 for (i = 0; i < NumPlayers; i++) {
72 pl = Player_by_index(i);
73
74 if (!strncasecmp(pl->name, str, len)) {
75 if (found_pl)
76 goto match_several;
77 found_pl = pl;
78 continue;
79 }
80 }
81 if (found_pl)
82 return found_pl;
83
84 /*
85 * Check what players' name 'str' is a substring of (case insensitively).
86 */
87 for (i = 0; i < NumPlayers; i++) {
88 int j;
89
90 pl = Player_by_index(i);
91
92 for (j = 0; j < 1 + (int)strlen(pl->name) - (int)len; j++) {
93 if (!strncasecmp(pl->name + j, str, len)) {
94 if (found_pl)
95 goto match_several;
96 found_pl = pl;
97 break;
98 }
99 }
100 }
101 if (found_pl)
102 return found_pl;
103
104 match_none:
105 if (error_p != NULL)
106 *error_p = -1;
107 if (errorstr_p != NULL)
108 *errorstr_p = "Name does not match any player.";
109 return NULL;
110
111 match_several:
112 if (error_p != NULL)
113 *error_p = -2;
114 if (errorstr_p != NULL)
115 *errorstr_p = "Name matches several players.";
116 return NULL;
117 }
118
119
Send_info_about_player(player_t * pl)120 void Send_info_about_player(player_t *pl)
121 {
122 int i;
123
124 for (i = 0; i < spectatorStart + NumSpectators; i++) {
125 player_t *pl_i;
126
127 if (i == NumPlayers) {
128 i = spectatorStart - 1;
129 continue;
130 }
131 pl_i = Player_by_index(i);
132 if (pl_i->conn != NULL) {
133 Send_team(pl_i->conn, pl->id, pl->team);
134 /*if we do either, we do both... but is either necessary?*/
135 updateScores = true;
136 pl->update_score = true;
137 if (pl->home_base != NULL)
138 Send_base(pl_i->conn, pl->id, pl->home_base->ind);
139 }
140 }
141 }
142
143
Set_swapper_state(player_t * pl)144 void Set_swapper_state(player_t *pl)
145 {
146 if (BIT(pl->have, HAS_BALL))
147 Detach_ball(pl, NULL);
148
149 if (BIT(world->rules->mode, LIMITED_LIVES)) {
150 int i;
151
152 for (i = 0; i < NumPlayers; i++) {
153 player_t *pl_i = Player_by_index(i);
154
155 if (!Players_are_teammates(pl, pl_i) &&
156 !Player_is_paused(pl_i)) {
157 /* put team swapping player waiting mode. */
158 /* kps - there used to be a if (pl->mychar == ' ') here,
159 * not sure what that was good for.
160 */
161 Player_set_state(pl, PL_STATE_WAITING);
162 Player_self_destruct(pl, false);
163 pl->pause_count = 0;
164 pl->recovery_count = 0;
165 break;
166 }
167 }
168 }
169 }
170
171
172 #define CMD_RESULT_SUCCESS 0
173 #define CMD_RESULT_ERROR (-1)
174 #define CMD_RESULT_NOT_OPERATOR (-2)
175 #define CMD_RESULT_NO_NAME (-3)
176
177
178 static int Cmd_addr(char *arg, player_t *pl, bool oper, char *msg, size_t size);
179 static int Cmd_advance(char *arg, player_t *pl, bool oper, char *msg, size_t size);
180 static int Cmd_ally(char *arg, player_t *pl, bool oper, char *msg, size_t size);
181 static int Cmd_get(char *arg, player_t *pl, bool oper, char *msg, size_t size);
182 static int Cmd_help(char *arg, player_t *pl, bool oper, char *msg, size_t size);
183 static int Cmd_kick(char *arg, player_t *pl, bool oper, char *msg, size_t size);
184 static int Cmd_lock(char *arg, player_t *pl, bool oper, char *msg, size_t size);
185 static int Cmd_maxturnsps(char *arg, player_t *pl, bool oper, char *msg, size_t size);
186 static int Cmd_mute(char *arg, player_t *pl, bool oper, char *msg, size_t size);
187 static int Cmd_op(char *arg, player_t *pl, bool oper, char *msg, size_t size);
188 static int Cmd_password(char *arg, player_t *pl, bool oper, char *msg, size_t size);
189 static int Cmd_pause(char *arg, player_t *pl, bool oper, char *msg, size_t size);
190 static int Cmd_plinfo(char *arg, player_t *pl, bool oper, char *msg, size_t size);
191 static int Cmd_queue(char *arg, player_t *pl, bool oper, char *msg, size_t size);
192 static int Cmd_reset(char *arg, player_t *pl, bool oper, char *msg, size_t size);
193 static int Cmd_set(char *arg, player_t *pl, bool oper, char *msg, size_t size);
194 static int Cmd_shutdown(char *arg, player_t *pl, bool oper, char *msg, size_t size);
195 static int Cmd_stats(char *arg, player_t *pl, bool oper, char *msg, size_t size);
196 static int Cmd_team(char *arg, player_t *pl, bool oper, char *msg, size_t size);
197 static int Cmd_version(char *arg, player_t *pl, bool oper, char *msg, size_t size);
198
199
200 typedef struct {
201 const char *name;
202 const char *abbrev;
203 const char *help;
204 bool oper_only;
205 int (*cmd)(char *arg, player_t *pl, bool oper, char *msg, size_t size);
206 } Command_info;
207
208
209 /*
210 * A list of all the commands sorted alphabetically.
211 */
212 static Command_info commands[] = {
213 {
214 "addr",
215 "addr",
216 "/addr <player name or ID number>. Show IP-address of player. "
217 "(operator)",
218 true,
219 Cmd_addr
220 },
221 {
222 "advance",
223 "ad",
224 "/advance <name of player in the queue>. "
225 "Move the player to the front of the queue. (operator)",
226 true,
227 Cmd_advance
228 },
229 {
230 "ally",
231 "al",
232 "/ally {invite|cancel|refuse|accept|leave|list} [<player name>]. "
233 "Manages alliances and invitations for them.",
234 false,
235 Cmd_ally
236 },
237 {
238 "get",
239 "g",
240 "/get <option>. Gets a server option.",
241 false,
242 Cmd_get
243 },
244 {
245 "help",
246 "h",
247 "Print command list. /help <command> gives more info.",
248 false,
249 Cmd_help
250 },
251 {
252 "kick",
253 "k",
254 "/kick <player name or ID number>. Remove a player from game. "
255 "(operator)",
256 true,
257 Cmd_kick
258 },
259 {
260 "lock",
261 "l",
262 "Just /lock tells lock status. /lock 1 locks, /lock 0 unlocks. "
263 "(operator)",
264 false, /* checked in the function */
265 Cmd_lock
266 },
267 {
268 "maxturnsps",
269 "maxturns",
270 "/maxturnsps <number> set max amount of turns per second. "
271 "(EXPERIMENTAL FEATURE)",
272 false,
273 Cmd_maxturnsps
274 },
275 {
276 "mute",
277 "m",
278 "Just /mute 1 mutes, /mute 0 unmutes paused players WITHOUT BASE /mute <name> toggles muting of player. "
279 "(operator)",
280 false, /* checked in the function */
281 Cmd_mute
282 },
283 {
284 "op",
285 "o",
286 "/op <command> [player name or ID number]. Operator commands. "
287 "(operator)",
288 true,
289 Cmd_op
290 },
291 {
292 "password",
293 "pas",
294 "/password <string>. If string matches -password option "
295 "gives operator status.",
296 false,
297 Cmd_password
298 },
299 {
300 "pause",
301 "pau",
302 "/pause <player name or ID number>. Pauses player. (operator)",
303 true,
304 Cmd_pause
305 },
306 {
307 "plinfo",
308 "pl",
309 "/plinfo <player name or ID number>. Show misc. player info.",
310 false,
311 Cmd_plinfo
312 },
313 {
314 "queue",
315 "q",
316 "/queue. Show the names of players waiting to enter.",
317 false,
318 Cmd_queue
319 },
320 {
321 "reset",
322 "r",
323 "Just /reset re-starts the round. "
324 "'/reset all' also resets all scores to 0. (operator)",
325 true,
326 Cmd_reset
327 },
328 {
329 "set",
330 "s",
331 "/set <option> <value>. Sets a server option. (operator)",
332 true,
333 Cmd_set
334 },
335 {
336 "shutdown",
337 "shutd",
338 "/shutdown <delay in seconds> [reason]. Shutdown server. "
339 "Use delay <= 0 to cancel. (operator) "
340 "Just /shutdown to query.",
341 false, /* checked in the function */
342 Cmd_shutdown
343 },
344 {
345 "stats",
346 "st",
347 "/stats <player name or ID number>. Show player ranking info.",
348 false,
349 Cmd_stats
350 },
351 {
352 "team",
353 "t",
354 "/team <team number> [name] swaps you to given team. "
355 "Can be used with full teams too.",
356 false,
357 Cmd_team
358 },
359 {
360 "version",
361 "v",
362 "Print server version.",
363 false,
364 Cmd_version
365 },
366 };
367
368
369 /*
370 * cmd parameter has no leading slash.
371 */
Handle_player_command(player_t * pl,char * cmd)372 void Handle_player_command(player_t *pl, char *cmd)
373 {
374 int i, result;
375 char *args, msg[MSG_LEN];
376
377 if (!*cmd) {
378 strlcpy(msg,
379 "No command given. Type /help for help. [*Server reply*]",
380 sizeof(msg));
381 Set_player_message(pl, msg);
382 return;
383 }
384
385 args = strchr(cmd + 1, ' ');
386 if (!args)
387 /* point to end of string. */
388 args = cmd + strlen(cmd);
389 else {
390 /* zero terminate cmd and advance 1 byte. */
391 *args++ = '\0';
392 while (isspace(*args))
393 args++;
394 }
395
396 for (i = 0; i < NELEM(commands); i++) {
397 size_t len1 = strlen(commands[i].abbrev);
398 size_t len2 = strlen(cmd);
399
400 if (!strncasecmp(cmd, commands[i].name, MAX(len1, len2)))
401 break;
402 }
403
404 if (i == NELEM(commands)) {
405 snprintf(msg, sizeof(msg),
406 "Unknown command '%s'. [*Server reply*]", cmd);
407 Set_player_message(pl, msg);
408 return;
409 }
410 #if 0 /* kps - recording related stuff too obscure */
411 else if (!pl->isoperator && (commands[i].operOnly || rplayback && !playback && commands[i].number != PASSWORD_CMD)) {
412 i = NO_CMD;
413 sprintf(msg, "You need operator status to use this command.");
414 }
415 #endif
416
417 msg[0] = '\0';
418 result = (*commands[i].cmd)(args, pl, pl->isoperator, msg, sizeof(msg));
419 switch (result) {
420 case CMD_RESULT_SUCCESS:
421 break;
422
423 case CMD_RESULT_ERROR:
424 if (msg[0] == '\0')
425 strlcpy(msg, "Error.", sizeof(msg));
426 break;
427
428 case CMD_RESULT_NOT_OPERATOR:
429 if (msg[0] == '\0')
430 strlcpy(msg,
431 "You need operator status to use this command.",
432 sizeof(msg));
433 break;
434
435 case CMD_RESULT_NO_NAME:
436 if (msg[0] == '\0')
437 strlcpy(msg,
438 "You must give a player name as an argument.",
439 sizeof(msg));
440 break;
441
442 default:
443 strlcpy(msg, "Bug.", sizeof(msg));
444 break;
445 }
446
447 if (msg[0]) {
448 strlcat(msg, " [*Server reply*]", sizeof(msg));
449 Set_player_message(pl, msg);
450 }
451 }
452
453
454
Cmd_addr(char * arg,player_t * pl,bool oper,char * msg,size_t size)455 static int Cmd_addr(char *arg, player_t *pl, bool oper, char *msg, size_t size)
456 {
457 player_t *pl2 = NULL;
458 const char *errorstr;
459
460 UNUSED_PARAM(pl);
461
462 if (!oper)
463 return CMD_RESULT_NOT_OPERATOR;
464
465 if (!arg || !*arg)
466 return CMD_RESULT_NO_NAME;
467
468 pl2 = Get_player_by_name(arg, NULL, &errorstr);
469 if (pl2) {
470 const char *addr = Player_get_addr(pl2);
471
472 if (addr == NULL)
473 snprintf(msg, size, "Unable to get address for %s.", pl2->name);
474 else
475 snprintf(msg, size, "%s plays from: %s.", pl2->name, addr);
476 } else {
477 strlcpy(msg, errorstr, size);
478 return CMD_RESULT_ERROR;
479 }
480
481 return CMD_RESULT_SUCCESS;
482 }
483
484
485 /*
486 * The queue system from the original server is not replicated
487 * during playback. Therefore interactions with it in the
488 * recording can cause problems (at least different message
489 * lengths in acks from client). It would be possible to work
490 * around this, but not implemented now. Currently queue and advance
491 * commands are disabled during recording.
492 */
Cmd_advance(char * arg,player_t * pl,bool oper,char * msg,size_t size)493 static int Cmd_advance(char *arg, player_t *pl, bool oper,
494 char *msg, size_t size)
495 {
496 int result;
497
498 UNUSED_PARAM(pl);
499
500 if (!oper)
501 return CMD_RESULT_NOT_OPERATOR;
502
503 if (record || playback) {
504 strlcpy(msg, "Command currently disabled during recording for "
505 "technical reasons.", size);
506 return CMD_RESULT_ERROR;
507 }
508
509 if (!arg || !*arg)
510 return CMD_RESULT_NO_NAME;
511
512 result = Queue_advance_player(arg, msg, size);
513
514 if (result < 0)
515 return CMD_RESULT_ERROR;
516
517 return CMD_RESULT_SUCCESS;
518 }
519
520
Cmd_ally(char * arg,player_t * pl,bool oper,char * msg,size_t size)521 static int Cmd_ally(char *arg, player_t *pl, bool oper, char *msg, size_t size)
522 {
523 char *command;
524 int result = CMD_RESULT_SUCCESS;
525 static const char usage[] =
526 "Usage: /ally {invite|cancel|refuse|accept|leave|list} "
527 "[<player name>]";
528 static const char *cmds[] = {
529 "invite",
530 "cancel",
531 "refuse",
532 "accept",
533 "leave",
534 "list",
535 };
536 enum AllyCmds {
537 AllyInvite = 0,
538 AllyCancel = 1,
539 AllyRefuse = 2,
540 AllyAccept = 3,
541 AllyLeave = 4,
542 AllyList = 5,
543 NumAllyCmds = 6
544 };
545 int i, cmd;
546
547 UNUSED_PARAM(pl); UNUSED_PARAM(oper);
548
549 if (!BIT(world->rules->mode, ALLIANCES)) {
550 strlcpy(msg, "Alliances are not allowed.", size);
551 result = CMD_RESULT_ERROR;
552 }
553 else if (!arg || !(command = strtok(arg, " \t"))) {
554 strlcpy(msg, usage, size);
555 result = CMD_RESULT_ERROR;
556 }
557 else {
558 if ((arg = strtok(NULL, "")) != NULL) {
559 while (*arg == ' ')
560 ++arg;
561 }
562 cmd = -1;
563 for (i = 0; i < NumAllyCmds; i++) {
564 if (!strncasecmp(cmds[i], command, strlen(command)))
565 cmd = (cmd == -1) ? i : (-2);
566 }
567 if (cmd < 0) {
568 strlcpy(msg, usage, size);
569 result = CMD_RESULT_ERROR;
570 }
571 else if (arg) {
572 /* a name is specified */
573 const char *errorstr;
574 player_t *pl2 = Get_player_by_name(arg, NULL, &errorstr);
575
576 if (pl2) {
577 if (cmd == AllyInvite)
578 Invite_player(pl, pl2);
579 else if (cmd == AllyRefuse)
580 Refuse_alliance(pl, pl2);
581 else if (cmd == AllyAccept)
582 Accept_alliance(pl, pl2);
583 else {
584 strlcpy(msg, usage, size);
585 result = CMD_RESULT_ERROR;
586 }
587 } else {
588 strlcpy(msg, errorstr, size);
589 result = CMD_RESULT_ERROR;
590 }
591 } else {
592 /* no player name is specified */
593 if (cmd == AllyCancel)
594 Cancel_invitation(pl);
595 else if (cmd == AllyRefuse)
596 Refuse_all_alliances(pl);
597 else if (cmd == AllyAccept)
598 Accept_all_alliances(pl);
599 else if (cmd == AllyLeave)
600 Leave_alliance(pl);
601 else if (cmd == AllyList)
602 Alliance_player_list(pl);
603 else {
604 strlcpy(msg, usage, size);
605 result = CMD_RESULT_ERROR;
606 }
607 }
608 }
609 return result;
610 }
611
Cmd_get(char * arg,player_t * pl,bool oper,char * msg,size_t size)612 static int Cmd_get(char *arg, player_t *pl, bool oper, char *msg, size_t size)
613 {
614 char value[MAX_CHARS];
615 char *valcpy, *name;
616 int i, retval = CMD_RESULT_ERROR;
617
618 UNUSED_PARAM(pl); UNUSED_PARAM(oper);
619
620 if (!arg || !*arg) {
621 strlcpy(msg, "Usage: /get option.", size);
622 return CMD_RESULT_ERROR;
623 }
624
625 valcpy = xp_safe_strdup(arg);
626 name = strtok(valcpy, " \t\r\n");
627 i = Get_option_value(name, value, sizeof(value));
628
629 switch (i) {
630 case 1:
631 snprintf(msg, size, "The value of %s is %s.", name, value);
632 retval = CMD_RESULT_SUCCESS;
633 break;
634 case -2:
635 snprintf(msg, size, "No server option named %s.", name);
636 break;
637 case -3:
638 snprintf(msg, size, "Cannot show the value of this option.");
639 break;
640 case -4:
641 snprintf(msg, size, "No value has been set for option %s.", name);
642 break;
643 default:
644 strlcpy(msg, "Generic error.", size);
645 break;
646 }
647
648 XFREE(valcpy);
649
650 return retval;
651 }
652
653
Cmd_help(char * arg,player_t * pl,bool oper,char * msg,size_t size)654 static int Cmd_help(char *arg, player_t *pl, bool oper, char *msg, size_t size)
655 {
656 int i;
657
658 UNUSED_PARAM(pl); UNUSED_PARAM(oper);
659
660 if (!*arg) {
661 strlcpy(msg, "Commands: ", size);
662 for (i = 0; i < NELEM(commands); i++) {
663 if (!commands[i].oper_only) {
664 strlcat(msg, commands[i].name, size);
665 strlcat(msg, " ", size);
666 }
667 }
668
669 if (pl->isoperator) {
670 strlcat(msg, " [*Server reply*]", size);
671 Set_player_message(pl, msg);
672
673 strlcpy(msg, "Operator commands: ", size);
674 for (i = 0; i < NELEM(commands); i++) {
675 if (commands[i].oper_only) {
676 strlcat(msg, commands[i].name, size);
677 strlcat(msg, " ", size);
678 }
679 }
680 }
681 }
682 else {
683 for (i = 0; i < NELEM(commands); i++) {
684 size_t len1 = strlen(commands[i].abbrev);
685 size_t len2 = strlen(arg);
686
687 if (!strncasecmp(arg, commands[i].name, MAX(len1, len2)))
688 break;
689 }
690 if (i == NELEM(commands))
691 snprintf(msg, size, "No help for nonexistent command '%s'.", arg);
692 else
693 strlcpy(msg, commands[i].help, size);
694 }
695
696 return CMD_RESULT_SUCCESS;
697 }
698
699
Cmd_kick(char * arg,player_t * pl,bool oper,char * msg,size_t size)700 static int Cmd_kick(char *arg, player_t *pl, bool oper, char *msg, size_t size)
701 {
702 player_t *kicked_pl;
703 const char *errorstr;
704
705 if (!oper)
706 return CMD_RESULT_NOT_OPERATOR;
707
708 if (!arg || !*arg)
709 return CMD_RESULT_NO_NAME;
710
711 kicked_pl = Get_player_by_name(arg, NULL, &errorstr);
712 if (kicked_pl) {
713 snprintf(msg, size, "%s kicked %s out! [*Server notice*]",
714 pl->name, kicked_pl->name);
715 if (kicked_pl->conn == NULL)
716 Delete_player(kicked_pl);
717 else
718 Destroy_connection(kicked_pl->conn, "kicked out");
719 Set_message(msg);
720 strlcpy(msg, "", size);
721 return CMD_RESULT_SUCCESS;
722 }
723 strlcpy(msg, errorstr, size);
724
725 return CMD_RESULT_ERROR;
726 }
727
728
Cmd_lock(char * arg,player_t * pl,bool oper,char * msg,size_t size)729 static int Cmd_lock(char *arg, player_t *pl, bool oper, char *msg, size_t size)
730 {
731 bool new_lock;
732
733 if (!arg || !*arg) {
734 snprintf(msg, size, "The game is currently %s.",
735 game_lock ? "locked" : "unlocked");
736 return CMD_RESULT_SUCCESS;
737 }
738
739 if (!oper)
740 return CMD_RESULT_NOT_OPERATOR;
741
742 if (!strcmp(arg, "1"))
743 new_lock = true;
744 else if (!strcmp(arg, "0"))
745 new_lock = false;
746 else {
747 snprintf(msg, size, "Invalid argument '%s'. Specify either 0 or 1.",
748 arg);
749 return CMD_RESULT_ERROR;
750 }
751
752 if (new_lock == game_lock)
753 snprintf(msg, size, "Game is already %s.",
754 game_lock ? "locked" : "unlocked");
755 else {
756 game_lock = new_lock;
757 snprintf(msg, size, " < The game has been %s by %s! >",
758 game_lock ? "locked" : "unlocked",
759 pl->name);
760 Set_message(msg);
761 strlcpy(msg, "", size);
762 }
763
764 return CMD_RESULT_SUCCESS;
765 }
766
767 /* temporary hack */
Cmd_maxturnsps(char * arg,player_t * pl,bool oper,char * msg,size_t size)768 static int Cmd_maxturnsps(char *arg, player_t *pl, bool oper, char *msg, size_t size)
769 {
770 int new_maxturnsps;
771
772 UNUSED_PARAM(oper);
773
774 if (!arg || !*arg) {
775 snprintf(msg, size, "Your current maxturnsps is %d.", pl->maxturnsps);
776 return CMD_RESULT_SUCCESS;
777 }
778
779 new_maxturnsps = atoi(arg);
780 if (new_maxturnsps <= 0) {
781 snprintf(msg, size, "Value of maxturnsps must be > 0.");
782 return CMD_RESULT_ERROR;
783 }
784
785 pl->maxturnsps = new_maxturnsps;
786 Set_player_message_f(pl, "Max number of turns/s is now %d. "
787 "[*Server reply*]", pl->maxturnsps);
788
789 return CMD_RESULT_SUCCESS;
790 }
791
Cmd_mute(char * arg,player_t * pl,bool oper,char * msg,size_t size)792 static int Cmd_mute(char *arg, player_t *pl, bool oper, char *msg, size_t size)
793 {
794 int new_mute;
795 player_t *mutee;
796 const char *errorstr;
797
798 if (!arg || !*arg) {
799 snprintf(msg, size, "Baseless paused players are currently %s.",
800 mute_baseless ? "muted" : "unmuted");
801 return CMD_RESULT_SUCCESS;
802 }
803
804 if (!oper)
805 return CMD_RESULT_NOT_OPERATOR;
806
807 if (!strcmp(arg, "1"))
808 new_mute = true;
809 else if (!strcmp(arg, "0"))
810 new_mute = false;
811 else if ((mutee = Get_player_by_name(arg, NULL, &errorstr)) != NULL) {
812 mutee->muted = mutee->muted ? false : true;
813 snprintf(msg, size, "Player %s is now %s.",
814 mutee->name, mutee->muted ? "muted" : "unmuted");
815 return CMD_RESULT_SUCCESS;
816 } else {
817 strlcpy(msg, errorstr, size);
818 return CMD_RESULT_ERROR;
819 }
820
821 if (new_mute == mute_baseless)
822 snprintf(msg, size, "Already %s.",
823 mute_baseless ? "muted" : "unmuted");
824 else {
825 mute_baseless = new_mute;
826 snprintf(msg, size, " < Baseless paused players have been %s by %s! >",
827 mute_baseless ? "muted" : "unmuted", pl->name);
828 Set_message(msg);
829 strlcpy(msg, "", size);
830 }
831
832 return CMD_RESULT_SUCCESS;
833 }
834
835 /* kps - this one is a bit obscure, maybe clean it up a bit ? */
Cmd_op(char * arg,player_t * pl,bool oper,char * msg,size_t size)836 static int Cmd_op(char *arg, player_t *pl, bool oper, char *msg, size_t size)
837 {
838 player_t *issuer = pl;
839 char *origarg = arg;
840 char *name;
841 int cmd, priv;
842
843 if (!oper)
844 return CMD_RESULT_NOT_OPERATOR;
845
846 if (!arg || (*arg != '+' && *arg != '-')) {
847 snprintf(msg, size, "Usage: /op {+|-}[nlo]+ <player name>");
848 return CMD_RESULT_ERROR;
849 }
850
851 name = strpbrk(arg, " \t");
852 if (name) {
853 const char *errorstr;
854
855 *name++ = '\0';
856 while (isspace(*name))
857 name++;
858
859 pl = Get_player_by_name(name, NULL, &errorstr);
860 if (!pl) {
861 strlcpy(msg, errorstr, size);
862 return CMD_RESULT_ERROR;
863 }
864 }
865
866 priv = 0;
867 cmd = *arg;
868 arg++;
869 while (*arg) {
870 switch (*arg) {
871 case 'n':
872 priv |= PRIV_NOAUTOKICK;
873 break;
874 case 'l':
875 priv |= PRIV_AUTOKICKLAST;
876 break;
877 case 'o':
878 if (cmd == '+')
879 pl->isoperator = true;
880 else
881 pl->isoperator = false;
882 break;
883 default:
884 snprintf(msg, size, "Invalid operator command '%c'.", *arg);
885 return CMD_RESULT_ERROR;
886 }
887 arg++;
888 }
889 if (cmd == '+')
890 pl->privs |= priv;
891 else
892 pl->privs &= ~priv;
893
894 if (pl != issuer) {
895 snprintf(msg, size, "%s executed '/op %s' on you. [*Server notice*]",
896 issuer->name, origarg);
897 Set_player_message(pl, msg);
898 }
899 snprintf(msg, size, "Executed '/op %s' on %s", origarg, pl->name);
900
901 return CMD_RESULT_SUCCESS;
902 }
903
904
Cmd_password(char * arg,player_t * pl,bool oper,char * msg,size_t size)905 static int Cmd_password(char *arg, player_t *pl, bool oper,
906 char *msg, size_t size)
907 {
908 UNUSED_PARAM(oper);
909
910 if (!options.password || !arg || strcmp(arg, options.password)) {
911 strlcpy(msg, "Wrong.", size);
912 if (pl->isoperator && pl->rectype != 2) {
913 NumOperators--;
914 pl->isoperator = false;
915 strlcat(msg, " You lost operator status.", size);
916 }
917 }
918 else {
919 if (!pl->isoperator && pl->rectype != 2) {
920 NumOperators++;
921 pl->isoperator = true;
922 pl->privs |= PRIV_AUTOKICKLAST;
923 }
924 strlcpy(msg, "You got operator status.", size);
925 }
926 return CMD_RESULT_SUCCESS;
927 }
928
929
Cmd_pause(char * arg,player_t * pl,bool oper,char * msg,size_t size)930 static int Cmd_pause(char *arg, player_t *pl, bool oper, char *msg, size_t size)
931 {
932 const char *errorstr;
933 player_t *pl2;
934
935 if (!oper)
936 return CMD_RESULT_NOT_OPERATOR;
937
938 if (!arg || !*arg)
939 return CMD_RESULT_NO_NAME;
940
941 pl2 = Get_player_by_name(arg, NULL, &errorstr);
942 if (!pl2) {
943 strlcpy(msg, errorstr, size);
944 return CMD_RESULT_ERROR;
945 }
946
947 if (pl2->conn != NULL) {
948 if (Player_is_alive(pl2))
949 Kill_player(pl2, false);
950 Pause_player(pl2, true);
951 snprintf(msg, size, "%s was paused by %s. [*Server notice*]",
952 pl2->name, pl->name);
953 Set_message(msg);
954 strlcpy(msg, "", size);
955 } else {
956 snprintf(msg, size, "Robots and tanks can't be paused.");
957 return CMD_RESULT_ERROR;
958 }
959
960 return CMD_RESULT_SUCCESS;
961 }
962
Cmd_plinfo(char * arg,player_t * pl,bool oper,char * msg,size_t size)963 static int Cmd_plinfo(char *arg, player_t *pl, bool oper, char *msg, size_t size)
964 {
965 const char *errorstr;
966 player_t *pl2;
967
968 UNUSED_PARAM(pl); UNUSED_PARAM(oper);
969
970 if (!arg || !*arg)
971 return CMD_RESULT_NO_NAME;
972
973 pl2 = Get_player_by_name(arg, NULL, &errorstr);
974 if (!pl2) {
975 strlcpy(msg, errorstr, size);
976 return CMD_RESULT_ERROR;
977 }
978
979 if (!Player_is_human(pl2)) {
980 snprintf(msg, size, "Robots and tanks don't have player info.");
981 return CMD_RESULT_ERROR;
982 }
983
984 snprintf(msg, size,
985 "%-15s Ver: 0x%x MaxFPS: %d Turnspeed: %.2f Turnres: %.2f "
986 "RTT: %i ms RTT_dev: %i ms",
987 pl2->name, pl2->version,
988 pl2->player_fps, pl2->turnspeed, pl2->turnresistance,
989 (int)((pl2->conn->rtt_smoothed >> 3) * timePerFrame * 1000),
990 (int)((pl2->conn->rtt_dev >> 2) * timePerFrame * 1000));
991
992 return CMD_RESULT_SUCCESS;
993 }
994
Cmd_queue(char * arg,player_t * pl,bool oper,char * msg,size_t size)995 static int Cmd_queue(char *arg, player_t *pl, bool oper, char *msg, size_t size)
996 {
997 int result;
998
999 UNUSED_PARAM(arg); UNUSED_PARAM(pl); UNUSED_PARAM(oper);
1000
1001 if (record || playback) {
1002 strlcpy(msg, "Command currently disabled during recording for "
1003 "technical reasons.", size);
1004 return CMD_RESULT_ERROR;
1005 }
1006
1007 result = Queue_show_list(msg, size);
1008
1009 if (result < 0)
1010 return CMD_RESULT_ERROR;
1011
1012 return CMD_RESULT_SUCCESS;
1013 }
1014
1015
Cmd_reset(char * arg,player_t * pl,bool oper,char * msg,size_t size)1016 static int Cmd_reset(char *arg, player_t *pl, bool oper, char *msg, size_t size)
1017 {
1018 int i;
1019
1020 if (!oper)
1021 return CMD_RESULT_NOT_OPERATOR;
1022
1023 for (i = NumObjs - 1; i >= 0; i--) {
1024 object_t *obj = Obj[i];
1025 obj->life = 0;
1026 Delete_shot(i);
1027
1028 }
1029
1030 if (arg && !strcasecmp(arg, "all")) {
1031 for (i = NumPlayers - 1; i >= 0; i--) {
1032 player_t *pl_i = Player_by_index(i);
1033
1034 if (!Player_is_paused(pl_i))
1035 Rank_set_score(pl_i, 0.0);
1036 }
1037 Reset_all_players();
1038 if (options.gameDuration == -1)
1039 options.gameDuration = 0;
1040 roundsPlayed = 0;
1041
1042 snprintf(msg, size, " < Total reset by %s! >", pl->name);
1043 Set_message(msg);
1044 strlcpy(msg, "", size);
1045
1046 teamcup_game_over();
1047 teamcup_game_start();
1048 }
1049 else {
1050 Reset_all_players();
1051 if (options.gameDuration == -1)
1052 options.gameDuration = 0;
1053
1054 snprintf(msg, size, " < Round reset by %s! >", pl->name);
1055 Set_message(msg);
1056 strlcpy(msg, "", size);
1057 }
1058
1059 return CMD_RESULT_SUCCESS;
1060 }
1061
1062
Cmd_stats(char * arg,player_t * pl,bool oper,char * msg,size_t size)1063 static int Cmd_stats(char *arg, player_t *pl, bool oper, char *msg, size_t size)
1064 {
1065 UNUSED_PARAM(pl); UNUSED_PARAM(oper);
1066
1067 if (!arg || !*arg)
1068 return CMD_RESULT_NO_NAME;
1069
1070 if (!Rank_get_stats(arg, msg, size)) {
1071 snprintf(msg, size, "Player \"%s\" doesn't have ranking stats.", arg);
1072 return CMD_RESULT_ERROR;
1073 }
1074
1075 return CMD_RESULT_SUCCESS;
1076 }
1077
1078
Cmd_team(char * arg,player_t * pl,bool oper,char * msg,size_t size)1079 static int Cmd_team(char *arg, player_t *pl, bool oper, char *msg, size_t size)
1080 {
1081 int i, team, swap_allowed;
1082 char *arg2;
1083
1084 UNUSED_PARAM(oper);
1085
1086 /*
1087 * Assume nothing will be said or done.
1088 */
1089 msg[0] = '\0';
1090 swap_allowed = false;
1091 team = pl->team;
1092
1093 if (!BIT(world->rules->mode, TEAM_PLAY))
1094 snprintf(msg, size, "No team play going on.");
1095 else if (pl->team >= MAX_TEAMS)
1096 snprintf(msg, size, "You do not currently have a team.");
1097 else if (!arg)
1098 snprintf(msg, size, "No team specified.");
1099 else if (!isdigit(*arg))
1100 snprintf(msg, size, "Invalid team specification.");
1101 else {
1102 team = strtoul(arg, &arg2, 0);
1103 if (arg2 && *arg2) {
1104 const char *errorstr;
1105
1106 if (!pl->isoperator) {
1107 snprintf(msg, size,
1108 "You need operator status to swap other players.");
1109 return CMD_RESULT_NOT_OPERATOR;
1110 }
1111 while (isspace(*arg2))
1112 arg2++;
1113 pl = Get_player_by_name(arg2, NULL, &errorstr);
1114 if (!pl) {
1115 strlcpy(msg, errorstr, size);
1116 return CMD_RESULT_ERROR;
1117 }
1118 }
1119
1120 for (i = 0; i < MAX_TEAMS ; i++) {
1121 team_t *t = Team_by_index(i);
1122
1123 /* Can't queue to two teams at once. */
1124 if (t->SwapperId == pl->id)
1125 t->SwapperId = NO_ID;
1126 }
1127
1128 if (game_lock && pl->home_base == NULL)
1129 snprintf(msg, size, "Playing teams are locked.");
1130 else if (team < 0 || team >= MAX_TEAMS)
1131 snprintf(msg, size, "Team %d is not a valid team.", team);
1132 else if (team == pl->team && pl->home_base != NULL)
1133 snprintf(msg, size, "You already are on team %d.", team);
1134 else if (world->teams[team].NumBases == 0)
1135 snprintf(msg, size,
1136 "There are no bases for team %d on this map.", team);
1137 else if (options.reserveRobotTeam && team == options.robotTeam)
1138 snprintf(msg, size,
1139 "You cannot join the robot team on this server.");
1140 else if (pl->rectype == 2)
1141 snprintf(msg, size, "Spectators cannot change team.");
1142 else
1143 swap_allowed = true;
1144 }
1145 if (!swap_allowed)
1146 return CMD_RESULT_ERROR;
1147
1148 if (world->teams[team].NumBases > world->teams[team].NumMembers) {
1149 snprintf(msg, size, "%s has swapped to team %d.", pl->name, team);
1150 Set_message(msg);
1151 if (pl->home_base)
1152 world->teams[pl->team].NumMembers--;
1153 pl->team = team;
1154 world->teams[pl->team].NumMembers++;
1155 Set_swapper_state(pl);
1156 if (pl->home_base == NULL) {
1157 Pick_startpos(pl);
1158 Pause_player(pl, false);
1159 }
1160 else
1161 Pick_startpos(pl);
1162 Send_info_about_player(pl);
1163 strlcpy(msg, "", size);
1164
1165 return CMD_RESULT_SUCCESS;
1166 }
1167
1168 i = world->teams[pl->team].SwapperId;
1169 while (i != -1 && pl->home_base != NULL) {
1170 if ((i = Player_by_id(i)->team) != team)
1171 i = world->teams[i].SwapperId;
1172 else {
1173 /* Found a cycle, now change the teams */
1174 base_t *xbase = pl->home_base, *xbase2;
1175 int xteam = pl->team, xteam2;
1176 player_t *pl2 = pl;
1177
1178 do {
1179 pl2 = Player_by_id(world->teams[xteam].SwapperId);
1180 world->teams[xteam].SwapperId = -1;
1181 xbase2 = pl2->home_base;
1182 xteam2 = pl2->team;
1183 pl2->team = xteam;
1184 pl2->home_base = xbase;
1185 Set_swapper_state(pl2);
1186 Send_info_about_player(pl2);
1187 /* This can send a huge amount of data if several
1188 * players swap. Unfortunately all player data, even
1189 * shipshape, has to be resent to change the team of
1190 * a player. This should probably be changed somehow
1191 * to prevent disturbing other players. */
1192 xbase = xbase2;
1193 xteam = xteam2;
1194 } while (xteam != team);
1195 xteam = pl->team;
1196 pl->team = team;
1197 pl->home_base = xbase;
1198 Set_swapper_state(pl);
1199 Send_info_about_player(pl);
1200 snprintf(msg, size, "Some players swapped teams.");
1201 Set_message(msg);
1202 strlcpy(msg, "", size);
1203 return CMD_RESULT_SUCCESS;
1204 }
1205 }
1206 /* Swap a paused player away from the full team */
1207 for (i = NumPlayers - 1; i >= 0; i--) {
1208 player_t *pl2 = Player_by_index(i);
1209
1210 if (pl2->conn != NULL && Player_is_paused(pl2)
1211 && (pl2->team == team) && pl2->home_base != NULL) {
1212 base_t *temp;
1213
1214 pl2->team = pl->team;
1215 pl->team = team;
1216 temp = pl2->home_base;
1217 pl2->home_base = pl->home_base;
1218 pl->home_base = temp;
1219 Set_swapper_state(pl2);
1220 Set_swapper_state(pl);
1221 Send_info_about_player(pl2);
1222 Send_info_about_player(pl);
1223 snprintf(msg, size, "%s has swapped with paused %s.",
1224 pl->name, pl2->name);
1225 Set_message(msg);
1226 strlcpy(msg, "", size);
1227 return CMD_RESULT_SUCCESS;
1228 }
1229 }
1230 snprintf(msg, size, "You are queued for swap to team %d.", team);
1231 world->teams[team].SwapperId = pl->id;
1232 return CMD_RESULT_SUCCESS;
1233 }
1234
Cmd_set(char * arg,player_t * pl,bool oper,char * msg,size_t size)1235 static int Cmd_set(char *arg, player_t *pl, bool oper, char *msg, size_t size)
1236 {
1237 int i;
1238 char *option, *value;
1239
1240 if (!oper)
1241 return CMD_RESULT_NOT_OPERATOR;
1242
1243 /*
1244 * Second argument of second strtok is " " instead of ""
1245 * which allows setting string options to values that contain spaces.
1246 */
1247 if (!arg
1248 || !(option = strtok(arg, " "))
1249 || !(value = strtok(NULL, ""))) {
1250
1251 snprintf(msg, size, "Usage: /set option value.");
1252 return CMD_RESULT_ERROR;
1253 }
1254
1255 i = Tune_option(option, value);
1256 if (i == 1) {
1257 if (!strcasecmp(option, "password"))
1258 snprintf(msg, size, "Operation successful.");
1259 else {
1260 char val[MAX_CHARS];
1261
1262 Get_option_value(option, val, sizeof(val));
1263 snprintf(msg, size, " < Option %s set to %s by %s. >",
1264 option, val, pl->name);
1265 Set_message(msg);
1266 strlcpy(msg, "", size);
1267
1268 return CMD_RESULT_SUCCESS;
1269 }
1270 }
1271 else if (i == 0)
1272 snprintf(msg, size, "Invalid value.");
1273 else if (i == -1)
1274 snprintf(msg, size, "This option cannot be changed at runtime.");
1275 else if (i == -2)
1276 snprintf(msg, size, "No option named '%s'.", option);
1277 else
1278 snprintf(msg, size, "Error.");
1279
1280 return CMD_RESULT_ERROR;
1281 }
1282
Cmd_shutdown(char * arg,player_t * pl,bool oper,char * msg,size_t size)1283 static int Cmd_shutdown(char *arg, player_t *pl, bool oper,
1284 char *msg, size_t size)
1285 {
1286 int delay;
1287 const char *delaystr, *reason;
1288 bool is_shutting_down = (ShutdownServer == -1 ? false : true);
1289
1290 if (!arg || !*arg) {
1291 if (is_shutting_down)
1292 snprintf(msg, size, "Shutting down in %d seconds: \"%s\"",
1293 ShutdownServer / FPS, ShutdownReason);
1294 else
1295 strlcpy(msg, "The server is not shutting down.", size);
1296 return CMD_RESULT_SUCCESS;
1297 }
1298
1299 if (!oper)
1300 return CMD_RESULT_NOT_OPERATOR;
1301
1302 if (!arg
1303 || !(delaystr = strtok(arg, " "))) {
1304
1305 snprintf(msg, size, "Usage: /shutdown <delay> [reason].");
1306 return CMD_RESULT_ERROR;
1307 }
1308
1309 reason = strtok(NULL, "");
1310 if (reason == NULL)
1311 reason = "";
1312
1313 delay = atoi(delaystr);
1314
1315 if (is_shutting_down || delay > 0) {
1316 Server_shutdown(pl->name, delay, reason);
1317 return CMD_RESULT_SUCCESS;
1318 }
1319 else
1320 /* no need to cancel if not shutting down */
1321 snprintf(msg, size, "The server is not shutting down.");
1322
1323 return CMD_RESULT_ERROR;
1324 }
1325
Cmd_version(char * arg,player_t * pl,bool oper,char * msg,size_t size)1326 static int Cmd_version(char *arg, player_t *pl, bool oper,
1327 char *msg, size_t size)
1328 {
1329 UNUSED_PARAM(arg); UNUSED_PARAM(pl); UNUSED_PARAM(oper);
1330 snprintf(msg, size, "%s version %s.", PACKAGE_NAME, VERSION);
1331 return CMD_RESULT_SUCCESS;
1332 }
1333