1 /* 2 * Copyright (c) 1983-2003, Regents of the University of California. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * + Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * + Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * + Neither the name of the University of California, San Francisco nor 15 * the names of its contributors may be used to endorse or promote 16 * products derived from this software without specific prior written 17 * permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 * 31 * $OpenBSD: driver.c,v 1.17 2007/04/02 14:55:16 jmc Exp $ 32 * $NetBSD: driver.c,v 1.5 1997/10/20 00:37:16 lukem Exp $ 33 * $DragonFly: src/games/hunt/huntd/driver.c,v 1.3 2008/11/10 15:28:13 swildner Exp $ 34 */ 35 36 #include <sys/ioctl.h> 37 #include <sys/stat.h> 38 #include <sys/time.h> 39 #include <sys/socket.h> 40 41 #include <netinet/in.h> 42 #include <arpa/inet.h> 43 44 #include <err.h> 45 #include <errno.h> 46 #include <signal.h> 47 #include <stdlib.h> 48 #include <string.h> 49 #include <unistd.h> 50 #include <stdio.h> 51 #include <tcpd.h> 52 #include <syslog.h> 53 #include <netdb.h> 54 #include <paths.h> 55 #include <fcntl.h> 56 #include "hunt.h" 57 #include "conf.h" 58 #include "server.h" 59 60 char *First_arg; /* pointer to argv[0] */ 61 u_int16_t Server_port; 62 int Server_socket; /* test socket to answer datagrams */ 63 FLAG should_announce = TRUE; /* true if listening on standard port */ 64 u_short sock_port; /* port # of tcp listen socket */ 65 u_short stat_port; /* port # of statistics tcp socket */ 66 in_addr_t Server_addr = INADDR_ANY; /* address to bind to */ 67 68 static void clear_scores(void); 69 static int havechar(PLAYER *); 70 static void init(void); 71 int main(int, char *[]); 72 static void makeboots(void); 73 static void send_stats(void); 74 static void zap(PLAYER *, FLAG); 75 static void announce_game(void); 76 static void siginfo(int); 77 static void print_stats(FILE *); 78 static void handle_wkport(int); 79 80 /* 81 * main: 82 * The main program. 83 */ 84 int 85 main(int ac, char **av) 86 { 87 PLAYER *pp; 88 int had_char; 89 static fd_set read_fds; 90 static FLAG first = TRUE; 91 static FLAG server = FALSE; 92 int c; 93 static struct timeval linger = { 0, 0 }; 94 static struct timeval timeout = { 0, 0 }, *to; 95 struct spawn *sp, *spnext; 96 int ret; 97 int nready; 98 int fd; 99 100 First_arg = av[0]; 101 102 config(); 103 104 while ((c = getopt(ac, av, "sp:a:D:")) != -1) { 105 switch (c) { 106 case 's': 107 server = TRUE; 108 break; 109 case 'p': 110 should_announce = FALSE; 111 Server_port = atoi(optarg); 112 break; 113 case 'a': 114 if (!inet_aton(optarg, (struct in_addr *)&Server_addr)) 115 err(1, "bad interface address: %s", optarg); 116 break; 117 case 'D': 118 config_arg(optarg); 119 break; 120 default: 121 erred: 122 fprintf(stderr, 123 "usage: %s [-s] [-a addr] [-Dvar=value ...] " 124 "[-p port]\n", 125 av[0]); 126 exit(2); 127 } 128 } 129 if (optind < ac) 130 goto erred; 131 132 /* Open syslog: */ 133 openlog("huntd", LOG_PID | (conf_logerr && !server? LOG_PERROR : 0), 134 LOG_DAEMON); 135 136 /* Initialise game parameters: */ 137 init(); 138 139 again: 140 do { 141 /* First, poll to see if we can get input */ 142 do { 143 read_fds = Fds_mask; 144 errno = 0; 145 timerclear(&timeout); 146 nready = select(Num_fds, &read_fds, NULL, NULL, 147 &timeout); 148 if (nready < 0 && errno != EINTR) { 149 logit(LOG_ERR, "select"); 150 cleanup(1); 151 } 152 } while (nready < 0); 153 154 if (nready == 0) { 155 /* 156 * Nothing was ready. We do some work now 157 * to see if the simulation has any pending work 158 * to do, and decide if we need to block 159 * indefinitely or just timeout. 160 */ 161 do { 162 if (conf_simstep && can_moveshots()) { 163 /* 164 * block for a short time before continuing 165 * with explosions, bullets and whatnot 166 */ 167 to = &timeout; 168 to->tv_sec = conf_simstep / 1000000; 169 to->tv_usec = conf_simstep % 1000000; 170 } else 171 /* 172 * since there's nothing going on, 173 * just block waiting for external activity 174 */ 175 to = NULL; 176 177 read_fds = Fds_mask; 178 errno = 0; 179 nready = select(Num_fds, &read_fds, NULL, NULL, 180 to); 181 if (nready < 0 && errno != EINTR) { 182 logit(LOG_ERR, "select"); 183 cleanup(1); 184 } 185 } while (nready < 0); 186 } 187 188 /* Remember which descriptors are active: */ 189 Have_inp = read_fds; 190 191 /* Answer new player connections: */ 192 if (FD_ISSET(Socket, &Have_inp)) 193 answer_first(); 194 195 /* Continue answering new player connections: */ 196 for (sp = Spawn; sp; ) { 197 spnext = sp->next; 198 fd = sp->fd; 199 if (FD_ISSET(fd, &Have_inp) && answer_next(sp)) { 200 /* 201 * Remove from the spawn list. (fd remains in 202 * read set). 203 */ 204 *sp->prevnext = sp->next; 205 if (sp->next) 206 sp->next->prevnext = sp->prevnext; 207 free(sp); 208 209 /* We probably consumed all data. */ 210 FD_CLR(fd, &Have_inp); 211 212 /* Announce game if this is the first spawn. */ 213 if (first && should_announce) 214 announce_game(); 215 first = FALSE; 216 } 217 sp = spnext; 218 } 219 220 /* Process input and move bullets until we've exhausted input */ 221 had_char = TRUE; 222 while (had_char) { 223 224 moveshots(); 225 for (pp = Player; pp < End_player; ) 226 if (pp->p_death[0] != '\0') 227 zap(pp, TRUE); 228 else 229 pp++; 230 for (pp = Monitor; pp < End_monitor; ) 231 if (pp->p_death[0] != '\0') 232 zap(pp, FALSE); 233 else 234 pp++; 235 236 had_char = FALSE; 237 for (pp = Player; pp < End_player; pp++) 238 if (havechar(pp)) { 239 execute(pp); 240 pp->p_nexec++; 241 had_char = TRUE; 242 } 243 for (pp = Monitor; pp < End_monitor; pp++) 244 if (havechar(pp)) { 245 mon_execute(pp); 246 pp->p_nexec++; 247 had_char = TRUE; 248 } 249 } 250 251 /* Handle a datagram sent to the server socket: */ 252 if (FD_ISSET(Server_socket, &Have_inp)) 253 handle_wkport(Server_socket); 254 255 /* Answer statistics connections: */ 256 if (FD_ISSET(Status, &Have_inp)) 257 send_stats(); 258 259 /* Flush/synchronize all the displays: */ 260 for (pp = Player; pp < End_player; pp++) { 261 if (FD_ISSET(pp->p_fd, &read_fds)) { 262 sendcom(pp, READY, pp->p_nexec); 263 pp->p_nexec = 0; 264 } 265 flush(pp); 266 } 267 for (pp = Monitor; pp < End_monitor; pp++) { 268 if (FD_ISSET(pp->p_fd, &read_fds)) { 269 sendcom(pp, READY, pp->p_nexec); 270 pp->p_nexec = 0; 271 } 272 flush(pp); 273 } 274 } while (Nplayer > 0); 275 276 /* No more players! */ 277 278 /* No players yet or a continuous game? */ 279 if (first || conf_linger < 0) 280 goto again; 281 282 /* Wait a short while for one to come back: */ 283 read_fds = Fds_mask; 284 linger.tv_sec = conf_linger; 285 while ((ret = select(Num_fds, &read_fds, NULL, NULL, &linger)) < 0) { 286 if (errno != EINTR) { 287 logit(LOG_WARNING, "select"); 288 break; 289 } 290 read_fds = Fds_mask; 291 linger.tv_sec = conf_linger; 292 linger.tv_usec = 0; 293 } 294 if (ret > 0) 295 /* Someone returned! Resume the game: */ 296 goto again; 297 /* else, it timed out, and the game is really over. */ 298 299 /* If we are an inetd server, we should re-init the map and restart: */ 300 if (server) { 301 clear_scores(); 302 makemaze(); 303 clearwalls(); 304 makeboots(); 305 first = TRUE; 306 goto again; 307 } 308 309 /* Get rid of any attached monitors: */ 310 for (pp = Monitor; pp < End_monitor; ) 311 zap(pp, FALSE); 312 313 /* Fin: */ 314 cleanup(0); 315 exit(0); 316 } 317 318 /* 319 * init: 320 * Initialize the global parameters. 321 */ 322 static void 323 init(void) 324 { 325 int i; 326 struct sockaddr_in test_port; 327 int true = 1; 328 socklen_t len; 329 struct sockaddr_in addr; 330 struct sigaction sact; 331 struct servent *se; 332 333 (void) setsid(); 334 if (setpgid(getpid(), getpid()) == -1) 335 err(1, "setpgid"); 336 337 sact.sa_flags = SA_RESTART; 338 sigemptyset(&sact.sa_mask); 339 340 /* Ignore HUP, QUIT and PIPE: */ 341 sact.sa_handler = SIG_IGN; 342 if (sigaction(SIGHUP, &sact, NULL) == -1) 343 err(1, "sigaction SIGHUP"); 344 if (sigaction(SIGQUIT, &sact, NULL) == -1) 345 err(1, "sigaction SIGQUIT"); 346 if (sigaction(SIGPIPE, &sact, NULL) == -1) 347 err(1, "sigaction SIGPIPE"); 348 349 /* Clean up gracefully on INT and TERM: */ 350 sact.sa_handler = cleanup; 351 if (sigaction(SIGINT, &sact, NULL) == -1) 352 err(1, "sigaction SIGINT"); 353 if (sigaction(SIGTERM, &sact, NULL) == -1) 354 err(1, "sigaction SIGTERM"); 355 356 /* Handle INFO: */ 357 sact.sa_handler = siginfo; 358 if (sigaction(SIGINFO, &sact, NULL) == -1) 359 err(1, "sigaction SIGINFO"); 360 361 if (chdir("/") == -1) 362 warn("chdir"); 363 (void) umask(0777); 364 365 /* Initialize statistics socket: */ 366 addr.sin_family = AF_INET; 367 addr.sin_addr.s_addr = Server_addr; 368 addr.sin_port = 0; 369 370 Status = socket(AF_INET, SOCK_STREAM, 0); 371 if (bind(Status, (struct sockaddr *) &addr, sizeof addr) < 0) { 372 logit(LOG_ERR, "bind"); 373 cleanup(1); 374 } 375 if (listen(Status, 5) == -1) { 376 logit(LOG_ERR, "listen"); 377 cleanup(1); 378 } 379 380 len = sizeof (struct sockaddr_in); 381 if (getsockname(Status, (struct sockaddr *) &addr, &len) < 0) { 382 logit(LOG_ERR, "getsockname"); 383 cleanup(1); 384 } 385 stat_port = ntohs(addr.sin_port); 386 387 /* Initialize main socket: */ 388 addr.sin_family = AF_INET; 389 addr.sin_addr.s_addr = Server_addr; 390 addr.sin_port = 0; 391 392 Socket = socket(AF_INET, SOCK_STREAM, 0); 393 394 if (bind(Socket, (struct sockaddr *) &addr, sizeof addr) < 0) { 395 logit(LOG_ERR, "bind"); 396 cleanup(1); 397 } 398 if (listen(Socket, 5) == -1) { 399 logit(LOG_ERR, "listen"); 400 cleanup(1); 401 } 402 403 len = sizeof (struct sockaddr_in); 404 if (getsockname(Socket, (struct sockaddr *) &addr, &len) < 0) { 405 logit(LOG_ERR, "getsockname"); 406 cleanup(1); 407 } 408 sock_port = ntohs(addr.sin_port); 409 410 /* Initialize minimal select mask */ 411 FD_ZERO(&Fds_mask); 412 FD_SET(Socket, &Fds_mask); 413 FD_SET(Status, &Fds_mask); 414 Num_fds = ((Socket > Status) ? Socket : Status) + 1; 415 416 /* Find the port that huntd should run on */ 417 if (Server_port == 0) { 418 se = getservbyname("hunt", "udp"); 419 if (se != NULL) 420 Server_port = ntohs(se->s_port); 421 else 422 Server_port = HUNT_PORT; 423 } 424 425 /* Check if stdin is a socket: */ 426 len = sizeof (struct sockaddr_in); 427 if (getsockname(STDIN_FILENO, (struct sockaddr *) &test_port, &len) >= 0 428 && test_port.sin_family == AF_INET) { 429 /* We are probably running from inetd: don't log to stderr */ 430 Server_socket = STDIN_FILENO; 431 conf_logerr = 0; 432 if (test_port.sin_port != htons((u_short) Server_port)) { 433 /* Private game */ 434 should_announce = FALSE; 435 Server_port = ntohs(test_port.sin_port); 436 } 437 } else { 438 /* We need to listen on a socket: */ 439 test_port = addr; 440 test_port.sin_port = htons((u_short) Server_port); 441 442 Server_socket = socket(AF_INET, SOCK_DGRAM, 0); 443 444 /* Permit multiple huntd's on the same port. */ 445 if (setsockopt(Server_socket, SOL_SOCKET, SO_REUSEPORT, &true, 446 sizeof true) < 0) 447 logit(LOG_ERR, "setsockopt SO_REUSEADDR"); 448 449 if (bind(Server_socket, (struct sockaddr *) &test_port, 450 sizeof test_port) < 0) { 451 logit(LOG_ERR, "bind port %d", Server_port); 452 cleanup(1); 453 } 454 455 /* Datagram sockets do not need a listen() call. */ 456 } 457 458 /* We'll handle the broadcast listener in the main loop: */ 459 FD_SET(Server_socket, &Fds_mask); 460 if (Server_socket + 1 > Num_fds) 461 Num_fds = Server_socket + 1; 462 463 /* Initialise the random seed: */ 464 srandomdev(); 465 466 /* Dig the maze: */ 467 makemaze(); 468 469 /* Create some boots, if needed: */ 470 makeboots(); 471 472 /* Construct a table of what objects a player can see over: */ 473 for (i = 0; i < NASCII; i++) 474 See_over[i] = TRUE; 475 See_over[DOOR] = FALSE; 476 See_over[WALL1] = FALSE; 477 See_over[WALL2] = FALSE; 478 See_over[WALL3] = FALSE; 479 See_over[WALL4] = FALSE; 480 See_over[WALL5] = FALSE; 481 482 logx(LOG_INFO, "game started"); 483 } 484 485 /* 486 * makeboots: 487 * Put the boots in the maze 488 */ 489 static void 490 makeboots(void) 491 { 492 int x, y; 493 PLAYER *pp; 494 495 if (conf_boots) { 496 do { 497 x = rand_num(WIDTH - 1) + 1; 498 y = rand_num(HEIGHT - 1) + 1; 499 } while (Maze[y][x] != SPACE); 500 Maze[y][x] = BOOT_PAIR; 501 } 502 503 for (pp = Boot; pp < &Boot[NBOOTS]; pp++) 504 pp->p_flying = -1; 505 } 506 507 508 /* 509 * checkdam: 510 * Apply damage to the victim from an attacker. 511 * If the victim dies as a result, give points to 'credit', 512 */ 513 void 514 checkdam(PLAYER *victim, PLAYER *attacker, IDENT *credit, int damage, 515 char stype) 516 { 517 const char *cp; 518 int y; 519 520 /* Don't do anything if the victim is already in the throes of death */ 521 if (victim->p_death[0] != '\0') 522 return; 523 524 /* Weaken slime attacks by 0.5 * number of boots the victim has on: */ 525 if (stype == SLIME) 526 switch (victim->p_nboots) { 527 default: 528 break; 529 case 1: 530 damage = (damage + 1) / 2; 531 break; 532 case 2: 533 if (attacker != NULL) 534 message(attacker, "He has boots on!"); 535 return; 536 } 537 538 /* The victim sustains some damage: */ 539 victim->p_damage += damage; 540 541 /* Check if the victim survives the hit: */ 542 if (victim->p_damage <= victim->p_damcap) { 543 /* They survive. */ 544 outyx(victim, STAT_DAM_ROW, STAT_VALUE_COL, "%2d", 545 victim->p_damage); 546 return; 547 } 548 549 /* Describe how the victim died: */ 550 switch (stype) { 551 default: 552 cp = "Killed"; 553 break; 554 case FALL: 555 cp = "Killed on impact"; 556 break; 557 case KNIFE: 558 cp = "Stabbed to death"; 559 victim->p_ammo = 0; /* No exploding */ 560 break; 561 case SHOT: 562 cp = "Shot to death"; 563 break; 564 case GRENADE: 565 case SATCHEL: 566 case BOMB: 567 cp = "Bombed"; 568 break; 569 case MINE: 570 case GMINE: 571 cp = "Blown apart"; 572 break; 573 case SLIME: 574 cp = "Slimed"; 575 if (credit != NULL) 576 credit->i_slime++; 577 break; 578 case LAVA: 579 cp = "Baked"; 580 break; 581 case DSHOT: 582 cp = "Eliminated"; 583 break; 584 } 585 586 if (credit == NULL) { 587 const char *blame; 588 589 /* 590 * Nobody is taking the credit for the kill. 591 * Attribute it to either a mine or 'act of God'. 592 */ 593 switch (stype) { 594 case MINE: 595 case GMINE: 596 blame = "a mine"; 597 break; 598 default: 599 blame = "act of God"; 600 break; 601 } 602 603 /* Set the death message: */ 604 (void) snprintf(victim->p_death, sizeof victim->p_death, 605 "| %s by %s |", cp, blame); 606 607 /* No further score crediting needed. */ 608 return; 609 } 610 611 /* Set the death message: */ 612 (void) snprintf(victim->p_death, sizeof victim->p_death, 613 "| %s by %s |", cp, credit->i_name); 614 615 if (victim == attacker) { 616 /* No use killing yourself. */ 617 credit->i_kills--; 618 credit->i_bkills++; 619 } 620 else if (victim->p_ident->i_team == ' ' 621 || victim->p_ident->i_team != credit->i_team) { 622 /* A cross-team kill: */ 623 credit->i_kills++; 624 credit->i_gkills++; 625 } 626 else { 627 /* They killed someone on the same team: */ 628 credit->i_kills--; 629 credit->i_bkills++; 630 } 631 632 /* Compute the new credited score: */ 633 credit->i_score = credit->i_kills / (double) credit->i_entries; 634 635 /* The victim accrues one death: */ 636 victim->p_ident->i_deaths++; 637 638 /* Account for 'Stillborn' deaths */ 639 if (victim->p_nchar == 0) 640 victim->p_ident->i_stillb++; 641 642 if (attacker) { 643 /* Give the attacker player a bit more strength */ 644 attacker->p_damcap += conf_killgain; 645 attacker->p_damage -= conf_killgain; 646 if (attacker->p_damage < 0) 647 attacker->p_damage = 0; 648 649 /* Tell the attacker his new strength: */ 650 outyx(attacker, STAT_DAM_ROW, STAT_VALUE_COL, "%2d/%2d", 651 attacker->p_damage, attacker->p_damcap); 652 653 /* Tell the attacker his new 'kill count': */ 654 outyx(attacker, STAT_KILL_ROW, STAT_VALUE_COL, "%3d", 655 (attacker->p_damcap - conf_maxdam) / 2); 656 657 /* Update the attacker's score for everyone else */ 658 y = STAT_PLAY_ROW + 1 + (attacker - Player); 659 outyx(ALL_PLAYERS, y, STAT_NAME_COL, 660 "%5.2f", attacker->p_ident->i_score); 661 } 662 } 663 664 /* 665 * zap: 666 * Kill off a player and take them out of the game. 667 * The 'was_player' flag indicates that the player was not 668 * a monitor and needs extra cleaning up. 669 */ 670 static void 671 zap(PLAYER *pp, FLAG was_player) 672 { 673 int len; 674 BULLET *bp; 675 PLAYER *np; 676 int x, y; 677 int savefd; 678 679 if (was_player) { 680 /* If they died from a shot, clean up shrapnel */ 681 if (pp->p_undershot) 682 fixshots(pp->p_y, pp->p_x, pp->p_over); 683 /* Let the player see their last position: */ 684 drawplayer(pp, FALSE); 685 /* Remove from game: */ 686 Nplayer--; 687 } 688 689 /* Display the cause of death in the centre of the screen: */ 690 len = strlen(pp->p_death); 691 x = (WIDTH - len) / 2; 692 outyx(pp, HEIGHT / 2, x, "%s", pp->p_death); 693 694 /* Put some horizontal lines around and below the death message: */ 695 memset(pp->p_death + 1, '-', len - 2); 696 pp->p_death[0] = '+'; 697 pp->p_death[len - 1] = '+'; 698 outyx(pp, HEIGHT / 2 - 1, x, "%s", pp->p_death); 699 outyx(pp, HEIGHT / 2 + 1, x, "%s", pp->p_death); 700 701 /* Move to bottom left */ 702 cgoto(pp, HEIGHT, 0); 703 704 savefd = pp->p_fd; 705 706 if (was_player) { 707 int expl_charge; 708 int expl_type; 709 int ammo_exploding; 710 711 /* Check all the bullets: */ 712 for (bp = Bullets; bp != NULL; bp = bp->b_next) { 713 if (bp->b_owner == pp) 714 /* Zapped players can't own bullets: */ 715 bp->b_owner = NULL; 716 if (bp->b_x == pp->p_x && bp->b_y == pp->p_y) 717 /* Bullets over the player are now over air: */ 718 bp->b_over = SPACE; 719 } 720 721 /* Explode a random fraction of the player's ammo: */ 722 ammo_exploding = rand_num(pp->p_ammo); 723 724 /* Determine the type and amount of detonation: */ 725 expl_charge = rand_num(ammo_exploding + 1); 726 if (pp->p_ammo == 0) 727 /* Ignore the no-ammo case: */ 728 expl_charge = expl_type = 0; 729 else if (ammo_exploding >= pp->p_ammo - 1) { 730 /* Maximal explosions always appear as slime: */ 731 expl_charge = pp->p_ammo; 732 expl_type = SLIME; 733 } else { 734 /* 735 * Figure out the best effective explosion 736 * type to use, given the amount of charge 737 */ 738 int btype, stype; 739 for (btype = MAXBOMB - 1; btype > 0; btype--) 740 if (expl_charge >= shot_req[btype]) 741 break; 742 for (stype = MAXSLIME - 1; stype > 0; stype--) 743 if (expl_charge >= slime_req[stype]) 744 break; 745 /* Pick the larger of the bomb or slime: */ 746 if (btype >= 0 && stype >= 0) { 747 if (shot_req[btype] > slime_req[btype]) 748 btype = -1; 749 } 750 if (btype >= 0) { 751 expl_type = shot_type[btype]; 752 expl_charge = shot_req[btype]; 753 } else 754 expl_type = SLIME; 755 } 756 757 if (expl_charge > 0) { 758 char buf[BUFSIZ]; 759 760 /* Detonate: */ 761 (void) add_shot(expl_type, pp->p_y, pp->p_x, 762 pp->p_face, expl_charge, (PLAYER *) NULL, 763 TRUE, SPACE); 764 765 /* Explain what the explosion is about. */ 766 snprintf(buf, sizeof buf, "%s detonated.", 767 pp->p_ident->i_name); 768 message(ALL_PLAYERS, buf); 769 770 while (pp->p_nboots-- > 0) { 771 /* Throw one of the boots away: */ 772 for (np = Boot; np < &Boot[NBOOTS]; np++) 773 if (np->p_flying < 0) 774 break; 775 #ifdef DIAGNOSTIC 776 if (np >= &Boot[NBOOTS]) 777 err(1, "Too many boots"); 778 #endif 779 /* Start the boots from where the player is */ 780 np->p_undershot = FALSE; 781 np->p_x = pp->p_x; 782 np->p_y = pp->p_y; 783 /* Throw for up to 20 steps */ 784 np->p_flying = rand_num(20); 785 np->p_flyx = 2 * rand_num(6) - 5; 786 np->p_flyy = 2 * rand_num(6) - 5; 787 np->p_over = SPACE; 788 np->p_face = BOOT; 789 showexpl(np->p_y, np->p_x, BOOT); 790 } 791 } 792 /* No explosion. Leave the player's boots behind. */ 793 else if (pp->p_nboots > 0) { 794 if (pp->p_nboots == 2) 795 Maze[pp->p_y][pp->p_x] = BOOT_PAIR; 796 else 797 Maze[pp->p_y][pp->p_x] = BOOT; 798 if (pp->p_undershot) 799 fixshots(pp->p_y, pp->p_x, 800 Maze[pp->p_y][pp->p_x]); 801 } 802 803 /* Any unexploded ammo builds up in the volcano: */ 804 volcano += pp->p_ammo - expl_charge; 805 806 /* Volcano eruption: */ 807 if (conf_volcano && rand_num(100) < volcano / 808 conf_volcano_max) { 809 /* Erupt near the middle of the map */ 810 do { 811 x = rand_num(WIDTH / 2) + WIDTH / 4; 812 y = rand_num(HEIGHT / 2) + HEIGHT / 4; 813 } while (Maze[y][x] != SPACE); 814 815 /* Convert volcano charge into lava: */ 816 (void) add_shot(LAVA, y, x, LEFTS, volcano, 817 (PLAYER *) NULL, TRUE, SPACE); 818 volcano = 0; 819 820 /* Tell eveyone what's happening */ 821 message(ALL_PLAYERS, "Volcano eruption."); 822 } 823 824 /* Drone: */ 825 if (conf_drone && rand_num(100) < 2) { 826 /* Find a starting place near the middle of the map: */ 827 do { 828 x = rand_num(WIDTH / 2) + WIDTH / 4; 829 y = rand_num(HEIGHT / 2) + HEIGHT / 4; 830 } while (Maze[y][x] != SPACE); 831 832 /* Start the drone going: */ 833 add_shot(DSHOT, y, x, rand_dir(), 834 shot_req[conf_mindshot + 835 rand_num(MAXBOMB - conf_mindshot)], 836 (PLAYER *) NULL, FALSE, SPACE); 837 } 838 839 /* Tell the zapped player's client to shut down. */ 840 sendcom(pp, ENDWIN, ' '); 841 (void) fclose(pp->p_output); 842 843 /* Close up the gap in the Player array: */ 844 End_player--; 845 if (pp != End_player) { 846 /* Move the last player into the gap: */ 847 memcpy(pp, End_player, sizeof *pp); 848 outyx(ALL_PLAYERS, 849 STAT_PLAY_ROW + 1 + (pp - Player), 850 STAT_NAME_COL, 851 "%5.2f%c%-10.10s %c", 852 pp->p_ident->i_score, stat_char(pp), 853 pp->p_ident->i_name, pp->p_ident->i_team); 854 } 855 856 /* Erase the last player from the display: */ 857 cgoto(ALL_PLAYERS, STAT_PLAY_ROW + 1 + Nplayer, STAT_NAME_COL); 858 ce(ALL_PLAYERS); 859 } 860 else { 861 /* Zap a monitor */ 862 863 /* Close the session: */ 864 sendcom(pp, ENDWIN, LAST_PLAYER); 865 (void) fclose(pp->p_output); 866 867 /* shuffle the monitor table */ 868 End_monitor--; 869 if (pp != End_monitor) { 870 memcpy(pp, End_monitor, sizeof *pp); 871 outyx(ALL_PLAYERS, 872 STAT_MON_ROW + 1 + (pp - Player), STAT_NAME_COL, 873 "%5.5s %-10.10s %c", " ", 874 pp->p_ident->i_name, pp->p_ident->i_team); 875 } 876 877 /* Erase the last monitor in the list */ 878 cgoto(ALL_PLAYERS, 879 STAT_MON_ROW + 1 + (End_monitor - Monitor), 880 STAT_NAME_COL); 881 ce(ALL_PLAYERS); 882 } 883 884 /* Update the file descriptor sets used by select: */ 885 FD_CLR(savefd, &Fds_mask); 886 if (Num_fds == savefd + 1) { 887 Num_fds = Socket; 888 if (Server_socket > Socket) 889 Num_fds = Server_socket; 890 for (np = Player; np < End_player; np++) 891 if (np->p_fd > Num_fds) 892 Num_fds = np->p_fd; 893 for (np = Monitor; np < End_monitor; np++) 894 if (np->p_fd > Num_fds) 895 Num_fds = np->p_fd; 896 Num_fds++; 897 } 898 } 899 900 /* 901 * rand_num: 902 * Return a random number in a given range. 903 */ 904 int 905 rand_num(int range) 906 { 907 if (range == 0) 908 return 0; 909 return (random() % range); 910 } 911 912 /* 913 * havechar: 914 * Check to see if we have any characters in the input queue; if 915 * we do, read them, stash them away, and return TRUE; else return 916 * FALSE. 917 */ 918 static int 919 havechar(PLAYER *pp) 920 { 921 int ret; 922 923 /* Do we already have characters? */ 924 if (pp->p_ncount < pp->p_nchar) 925 return TRUE; 926 /* Ignore if nothing to read. */ 927 if (!FD_ISSET(pp->p_fd, &Have_inp)) 928 return FALSE; 929 /* Remove the player from the read set until we have drained them: */ 930 FD_CLR(pp->p_fd, &Have_inp); 931 932 /* Suck their keypresses into a buffer: */ 933 check_again: 934 errno = 0; 935 ret = read(pp->p_fd, pp->p_cbuf, sizeof pp->p_cbuf); 936 if (ret == -1) { 937 if (errno == EINTR) 938 goto check_again; 939 if (errno == EAGAIN) { 940 #ifdef DEBUG 941 warn("Have_inp is wrong for %d", pp->p_fd); 942 #endif 943 return FALSE; 944 } 945 logit(LOG_INFO, "read"); 946 } 947 if (ret > 0) { 948 /* Got some data */ 949 pp->p_nchar = ret; 950 } else { 951 /* Connection was lost/closed: */ 952 pp->p_cbuf[0] = 'q'; 953 pp->p_nchar = 1; 954 } 955 /* Reset pointer into read buffer */ 956 pp->p_ncount = 0; 957 return TRUE; 958 } 959 960 /* 961 * cleanup: 962 * Exit with the given value, cleaning up any droppings lying around 963 */ 964 void 965 cleanup(int eval) 966 { 967 PLAYER *pp; 968 969 /* Place their cursor in a friendly position: */ 970 cgoto(ALL_PLAYERS, HEIGHT, 0); 971 972 /* Send them all the ENDWIN command: */ 973 sendcom(ALL_PLAYERS, ENDWIN, LAST_PLAYER); 974 975 /* And close their connections: */ 976 for (pp = Player; pp < End_player; pp++) 977 (void) fclose(pp->p_output); 978 for (pp = Monitor; pp < End_monitor; pp++) 979 (void) fclose(pp->p_output); 980 981 /* Close the server socket: */ 982 (void) close(Socket); 983 984 /* The end: */ 985 logx(LOG_INFO, "game over"); 986 exit(eval); 987 } 988 989 /* 990 * send_stats: 991 * Accept a connection to the statistics port, and emit 992 * the stats. 993 */ 994 static void 995 send_stats(void) 996 { 997 FILE *fp; 998 int s; 999 struct sockaddr_in sockstruct; 1000 socklen_t socklen; 1001 struct request_info ri; 1002 int flags; 1003 1004 /* Accept a connection to the statistics socket: */ 1005 socklen = sizeof sockstruct; 1006 s = accept(Status, (struct sockaddr *) &sockstruct, &socklen); 1007 if (s < 0) { 1008 if (errno == EINTR) 1009 return; 1010 logx(LOG_ERR, "accept"); 1011 return; 1012 } 1013 1014 /* Check for access permissions: */ 1015 request_init(&ri, RQ_DAEMON, "huntd", RQ_FILE, s, 0); 1016 fromhost(&ri); 1017 if (hosts_access(&ri) == 0) { 1018 logx(LOG_INFO, "rejected connection from %s", eval_client(&ri)); 1019 close(s); 1020 return; 1021 } 1022 1023 /* Don't allow the writes to block: */ 1024 flags = fcntl(s, F_GETFL, 0); 1025 flags |= O_NDELAY; 1026 (void) fcntl(s, F_SETFL, flags); 1027 1028 fp = fdopen(s, "w"); 1029 if (fp == NULL) { 1030 logit(LOG_ERR, "fdopen"); 1031 (void) close(s); 1032 return; 1033 } 1034 1035 print_stats(fp); 1036 1037 (void) fclose(fp); 1038 } 1039 1040 /* 1041 * print_stats: 1042 * emit the game statistics 1043 */ 1044 void 1045 print_stats(FILE *fp) 1046 { 1047 IDENT *ip; 1048 PLAYER *pp; 1049 1050 /* Send the statistics as raw text down the socket: */ 1051 fputs("Name\t\tScore\tDucked\tAbsorb\tFaced\tShot\tRobbed\tMissed\tSlimeK\n", fp); 1052 for (ip = Scores; ip != NULL; ip = ip->i_next) { 1053 fprintf(fp, "%s%c%c%c\t", ip->i_name, 1054 ip->i_team == ' ' ? ' ' : '[', 1055 ip->i_team, 1056 ip->i_team == ' ' ? ' ' : ']' 1057 ); 1058 if (strlen(ip->i_name) + 3 < 8) 1059 putc('\t', fp); 1060 fprintf(fp, "%.2f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", 1061 ip->i_score, ip->i_ducked, ip->i_absorbed, 1062 ip->i_faced, ip->i_shot, ip->i_robbed, 1063 ip->i_missed, ip->i_slime); 1064 } 1065 fputs("\n\nName\t\tEnemy\tFriend\tDeaths\tStill\tSaved\tConnect\n", fp); 1066 for (ip = Scores; ip != NULL; ip = ip->i_next) { 1067 fprintf(fp, "%s%c%c%c\t", ip->i_name, 1068 ip->i_team == ' ' ? ' ' : '[', 1069 ip->i_team, 1070 ip->i_team == ' ' ? ' ' : ']' 1071 ); 1072 if (strlen(ip->i_name) + 3 < 8) 1073 putc('\t', fp); 1074 fprintf(fp, "%d\t%d\t%d\t%d\t%d\t", 1075 ip->i_gkills, ip->i_bkills, ip->i_deaths, 1076 ip->i_stillb, ip->i_saved); 1077 for (pp = Player; pp < End_player; pp++) 1078 if (pp->p_ident == ip) 1079 putc('p', fp); 1080 for (pp = Monitor; pp < End_monitor; pp++) 1081 if (pp->p_ident == ip) 1082 putc('m', fp); 1083 putc('\n', fp); 1084 } 1085 } 1086 1087 1088 /* 1089 * Send the game statistics to the controlling tty 1090 */ 1091 static void 1092 siginfo(int sig __unused) 1093 { 1094 int tty; 1095 FILE *fp; 1096 1097 if ((tty = open(_PATH_TTY, O_WRONLY)) >= 0) { 1098 fp = fdopen(tty, "w"); 1099 print_stats(fp); 1100 answer_info(fp); 1101 fclose(fp); 1102 } 1103 } 1104 1105 /* 1106 * clear_scores: 1107 * Clear the Scores list. 1108 */ 1109 static void 1110 clear_scores(void) 1111 { 1112 IDENT *ip, *nextip; 1113 1114 /* Release the list of scores: */ 1115 for (ip = Scores; ip != NULL; ip = nextip) { 1116 nextip = ip->i_next; 1117 free((char *) ip); 1118 } 1119 Scores = NULL; 1120 } 1121 1122 /* 1123 * announce_game: 1124 * Publically announce the game 1125 */ 1126 static void 1127 announce_game(void) 1128 { 1129 1130 /* TODO: could use system() to do something user-configurable */ 1131 } 1132 1133 /* 1134 * Handle a UDP packet sent to the well known port. 1135 */ 1136 static void 1137 handle_wkport(int fd) 1138 { 1139 struct sockaddr fromaddr; 1140 socklen_t fromlen; 1141 u_int16_t query; 1142 u_int16_t response; 1143 struct request_info ri; 1144 1145 request_init(&ri, RQ_DAEMON, "huntd", RQ_FILE, fd, 0); 1146 fromhost(&ri); 1147 fromlen = sizeof fromaddr; 1148 if (recvfrom(fd, &query, sizeof query, 0, &fromaddr, &fromlen) == -1) 1149 { 1150 logit(LOG_WARNING, "recvfrom"); 1151 return; 1152 } 1153 1154 #ifdef DEBUG 1155 fprintf(stderr, "query %d (%s) from %s:%d\n", query, 1156 query == C_MESSAGE ? "C_MESSAGE" : 1157 query == C_SCORES ? "C_SCORES" : 1158 query == C_PLAYER ? "C_PLAYER" : 1159 query == C_MONITOR ? "C_MONITOR" : "?", 1160 inet_ntoa(((struct sockaddr_in *)&fromaddr)->sin_addr), 1161 ntohs(((struct sockaddr_in *)&fromaddr)->sin_port)); 1162 #endif 1163 1164 /* Do we allow access? */ 1165 if (hosts_access(&ri) == 0) { 1166 logx(LOG_INFO, "rejected connection from %s", eval_client(&ri)); 1167 return; 1168 } 1169 1170 query = ntohs(query); 1171 1172 switch (query) { 1173 case C_MESSAGE: 1174 if (Nplayer <= 0) 1175 /* Don't bother replying if nobody to talk to: */ 1176 return; 1177 /* Return the number of people playing: */ 1178 response = Nplayer; 1179 break; 1180 case C_SCORES: 1181 /* Someone wants the statistics port: */ 1182 response = stat_port; 1183 break; 1184 case C_PLAYER: 1185 case C_MONITOR: 1186 /* Someone wants to play or watch: */ 1187 if (query == C_MONITOR && Nplayer <= 0) 1188 /* Don't bother replying if there's nothing to watch: */ 1189 return; 1190 /* Otherwise, tell them how to get to the game: */ 1191 response = sock_port; 1192 break; 1193 default: 1194 logit(LOG_INFO, "unknown udp query %d", query); 1195 return; 1196 } 1197 1198 response = ntohs(response); 1199 if (sendto(fd, &response, sizeof response, 0, 1200 &fromaddr, sizeof fromaddr) == -1) 1201 logit(LOG_WARNING, "sendto"); 1202 } 1203