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