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