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