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