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