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