games: Massive style(9) cleanup commit. Reduces differences to NetBSD.
[dragonfly.git] / games / hunt / huntd / driver.c
blob371ed7f4dd5cf39382c5c9a521ae1793715ba58f
1 /*-
2 * Copyright (c) 1983-2003, Regents of the University of California.
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. 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 * 3. 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.
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.
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.3 2008/11/10 15:28:13 swildner Exp $
36 #include <sys/ioctl.h>
37 #include <sys/stat.h>
38 #include <sys/time.h>
39 #include <sys/socket.h>
41 #include <netinet/in.h>
42 #include <arpa/inet.h>
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"
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 */
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);
81 * main:
82 * The main program.
84 int
85 main(int ac, char **av)
87 PLAYER *pp;
88 int had_char;
89 static fd_set read_fds;
90 static FLAG first = TRUE;
91 static FLAG server = FALSE;
92 int c;
93 static struct timeval linger = { 0, 0 };
94 static struct timeval timeout = { 0, 0 }, *to;
95 struct spawn *sp, *spnext;
96 int ret;
97 int nready;
98 int fd;
100 First_arg = av[0];
102 config();
104 while ((c = getopt(ac, av, "sp:a:D:")) != -1) {
105 switch (c) {
106 case 's':
107 server = TRUE;
108 break;
109 case 'p':
110 should_announce = FALSE;
111 Server_port = atoi(optarg);
112 break;
113 case 'a':
114 if (!inet_aton(optarg, (struct in_addr *)&Server_addr))
115 err(1, "bad interface address: %s", optarg);
116 break;
117 case 'D':
118 config_arg(optarg);
119 break;
120 default:
121 erred:
122 fprintf(stderr,
123 "usage: %s [-s] [-a addr] [-Dvar=value ...] "
124 "[-p port]\n",
125 av[0]);
126 exit(2);
129 if (optind < ac)
130 goto erred;
132 /* Open syslog: */
133 openlog("huntd", LOG_PID | (conf_logerr && !server? LOG_PERROR : 0),
134 LOG_DAEMON);
136 /* Initialise game parameters: */
137 init();
139 again:
140 do {
141 /* First, poll to see if we can get input */
142 do {
143 read_fds = Fds_mask;
144 errno = 0;
145 timerclear(&timeout);
146 nready = select(Num_fds, &read_fds, NULL, NULL,
147 &timeout);
148 if (nready < 0 && errno != EINTR) {
149 logit(LOG_ERR, "select");
150 cleanup(1);
152 } while (nready < 0);
154 if (nready == 0) {
156 * Nothing was ready. We do some work now
157 * to see if the simulation has any pending work
158 * to do, and decide if we need to block
159 * indefinitely or just timeout.
161 do {
162 if (conf_simstep && can_moveshots()) {
164 * block for a short time before continuing
165 * with explosions, bullets and whatnot
167 to = &timeout;
168 to->tv_sec = conf_simstep / 1000000;
169 to->tv_usec = conf_simstep % 1000000;
170 } else
172 * since there's nothing going on,
173 * just block waiting for external activity
175 to = NULL;
177 read_fds = Fds_mask;
178 errno = 0;
179 nready = select(Num_fds, &read_fds, NULL, NULL,
180 to);
181 if (nready < 0 && errno != EINTR) {
182 logit(LOG_ERR, "select");
183 cleanup(1);
185 } while (nready < 0);
188 /* Remember which descriptors are active: */
189 Have_inp = read_fds;
191 /* Answer new player connections: */
192 if (FD_ISSET(Socket, &Have_inp))
193 answer_first();
195 /* Continue answering new player connections: */
196 for (sp = Spawn; sp; ) {
197 spnext = sp->next;
198 fd = sp->fd;
199 if (FD_ISSET(fd, &Have_inp) && answer_next(sp)) {
201 * Remove from the spawn list. (fd remains in
202 * read set).
204 *sp->prevnext = sp->next;
205 if (sp->next)
206 sp->next->prevnext = sp->prevnext;
207 free(sp);
209 /* We probably consumed all data. */
210 FD_CLR(fd, &Have_inp);
212 /* Announce game if this is the first spawn. */
213 if (first && should_announce)
214 announce_game();
215 first = FALSE;
217 sp = spnext;
220 /* Process input and move bullets until we've exhausted input */
221 had_char = TRUE;
222 while (had_char) {
224 moveshots();
225 for (pp = Player; pp < End_player; )
226 if (pp->p_death[0] != '\0')
227 zap(pp, TRUE);
228 else
229 pp++;
230 for (pp = Monitor; pp < End_monitor; )
231 if (pp->p_death[0] != '\0')
232 zap(pp, FALSE);
233 else
234 pp++;
236 had_char = FALSE;
237 for (pp = Player; pp < End_player; pp++)
238 if (havechar(pp)) {
239 execute(pp);
240 pp->p_nexec++;
241 had_char = TRUE;
243 for (pp = Monitor; pp < End_monitor; pp++)
244 if (havechar(pp)) {
245 mon_execute(pp);
246 pp->p_nexec++;
247 had_char = TRUE;
251 /* Handle a datagram sent to the server socket: */
252 if (FD_ISSET(Server_socket, &Have_inp))
253 handle_wkport(Server_socket);
255 /* Answer statistics connections: */
256 if (FD_ISSET(Status, &Have_inp))
257 send_stats();
259 /* Flush/synchronize all the displays: */
260 for (pp = Player; pp < End_player; pp++) {
261 if (FD_ISSET(pp->p_fd, &read_fds)) {
262 sendcom(pp, READY, pp->p_nexec);
263 pp->p_nexec = 0;
265 flush(pp);
267 for (pp = Monitor; pp < End_monitor; pp++) {
268 if (FD_ISSET(pp->p_fd, &read_fds)) {
269 sendcom(pp, READY, pp->p_nexec);
270 pp->p_nexec = 0;
272 flush(pp);
274 } while (Nplayer > 0);
276 /* No more players! */
278 /* No players yet or a continuous game? */
279 if (first || conf_linger < 0)
280 goto again;
282 /* Wait a short while for one to come back: */
283 read_fds = Fds_mask;
284 linger.tv_sec = conf_linger;
285 while ((ret = select(Num_fds, &read_fds, NULL, NULL, &linger)) < 0) {
286 if (errno != EINTR) {
287 logit(LOG_WARNING, "select");
288 break;
290 read_fds = Fds_mask;
291 linger.tv_sec = conf_linger;
292 linger.tv_usec = 0;
294 if (ret > 0)
295 /* Someone returned! Resume the game: */
296 goto again;
297 /* else, it timed out, and the game is really over. */
299 /* If we are an inetd server, we should re-init the map and restart: */
300 if (server) {
301 clear_scores();
302 makemaze();
303 clearwalls();
304 makeboots();
305 first = TRUE;
306 goto again;
309 /* Get rid of any attached monitors: */
310 for (pp = Monitor; pp < End_monitor; )
311 zap(pp, FALSE);
313 /* Fin: */
314 cleanup(0);
315 exit(0);
319 * init:
320 * Initialize the global parameters.
322 static void
323 init(void)
325 int i;
326 struct sockaddr_in test_port;
327 int true = 1;
328 socklen_t len;
329 struct sockaddr_in addr;
330 struct sigaction sact;
331 struct servent *se;
333 (void) setsid();
334 if (setpgid(getpid(), getpid()) == -1)
335 err(1, "setpgid");
337 sact.sa_flags = SA_RESTART;
338 sigemptyset(&sact.sa_mask);
340 /* Ignore HUP, QUIT and PIPE: */
341 sact.sa_handler = SIG_IGN;
342 if (sigaction(SIGHUP, &sact, NULL) == -1)
343 err(1, "sigaction SIGHUP");
344 if (sigaction(SIGQUIT, &sact, NULL) == -1)
345 err(1, "sigaction SIGQUIT");
346 if (sigaction(SIGPIPE, &sact, NULL) == -1)
347 err(1, "sigaction SIGPIPE");
349 /* Clean up gracefully on INT and TERM: */
350 sact.sa_handler = cleanup;
351 if (sigaction(SIGINT, &sact, NULL) == -1)
352 err(1, "sigaction SIGINT");
353 if (sigaction(SIGTERM, &sact, NULL) == -1)
354 err(1, "sigaction SIGTERM");
356 /* Handle INFO: */
357 sact.sa_handler = siginfo;
358 if (sigaction(SIGINFO, &sact, NULL) == -1)
359 err(1, "sigaction SIGINFO");
361 if (chdir("/") == -1)
362 warn("chdir");
363 (void) umask(0777);
365 /* Initialize statistics socket: */
366 addr.sin_family = AF_INET;
367 addr.sin_addr.s_addr = Server_addr;
368 addr.sin_port = 0;
370 Status = socket(AF_INET, SOCK_STREAM, 0);
371 if (bind(Status, (struct sockaddr *) &addr, sizeof addr) < 0) {
372 logit(LOG_ERR, "bind");
373 cleanup(1);
375 if (listen(Status, 5) == -1) {
376 logit(LOG_ERR, "listen");
377 cleanup(1);
380 len = sizeof (struct sockaddr_in);
381 if (getsockname(Status, (struct sockaddr *) &addr, &len) < 0) {
382 logit(LOG_ERR, "getsockname");
383 cleanup(1);
385 stat_port = ntohs(addr.sin_port);
387 /* Initialize main socket: */
388 addr.sin_family = AF_INET;
389 addr.sin_addr.s_addr = Server_addr;
390 addr.sin_port = 0;
392 Socket = socket(AF_INET, SOCK_STREAM, 0);
394 if (bind(Socket, (struct sockaddr *) &addr, sizeof addr) < 0) {
395 logit(LOG_ERR, "bind");
396 cleanup(1);
398 if (listen(Socket, 5) == -1) {
399 logit(LOG_ERR, "listen");
400 cleanup(1);
403 len = sizeof (struct sockaddr_in);
404 if (getsockname(Socket, (struct sockaddr *) &addr, &len) < 0) {
405 logit(LOG_ERR, "getsockname");
406 cleanup(1);
408 sock_port = ntohs(addr.sin_port);
410 /* Initialize minimal select mask */
411 FD_ZERO(&Fds_mask);
412 FD_SET(Socket, &Fds_mask);
413 FD_SET(Status, &Fds_mask);
414 Num_fds = ((Socket > Status) ? Socket : Status) + 1;
416 /* Find the port that huntd should run on */
417 if (Server_port == 0) {
418 se = getservbyname("hunt", "udp");
419 if (se != NULL)
420 Server_port = ntohs(se->s_port);
421 else
422 Server_port = HUNT_PORT;
425 /* Check if stdin is a socket: */
426 len = sizeof (struct sockaddr_in);
427 if (getsockname(STDIN_FILENO, (struct sockaddr *) &test_port, &len) >= 0
428 && test_port.sin_family == AF_INET) {
429 /* We are probably running from inetd: don't log to stderr */
430 Server_socket = STDIN_FILENO;
431 conf_logerr = 0;
432 if (test_port.sin_port != htons((u_short) Server_port)) {
433 /* Private game */
434 should_announce = FALSE;
435 Server_port = ntohs(test_port.sin_port);
437 } else {
438 /* We need to listen on a socket: */
439 test_port = addr;
440 test_port.sin_port = htons((u_short) Server_port);
442 Server_socket = socket(AF_INET, SOCK_DGRAM, 0);
444 /* Permit multiple huntd's on the same port. */
445 if (setsockopt(Server_socket, SOL_SOCKET, SO_REUSEPORT, &true,
446 sizeof true) < 0)
447 logit(LOG_ERR, "setsockopt SO_REUSEADDR");
449 if (bind(Server_socket, (struct sockaddr *) &test_port,
450 sizeof test_port) < 0) {
451 logit(LOG_ERR, "bind port %d", Server_port);
452 cleanup(1);
455 /* Datagram sockets do not need a listen() call. */
458 /* We'll handle the broadcast listener in the main loop: */
459 FD_SET(Server_socket, &Fds_mask);
460 if (Server_socket + 1 > Num_fds)
461 Num_fds = Server_socket + 1;
463 /* Initialise the random seed: */
464 srandomdev();
466 /* Dig the maze: */
467 makemaze();
469 /* Create some boots, if needed: */
470 makeboots();
472 /* Construct a table of what objects a player can see over: */
473 for (i = 0; i < NASCII; i++)
474 See_over[i] = TRUE;
475 See_over[DOOR] = FALSE;
476 See_over[WALL1] = FALSE;
477 See_over[WALL2] = FALSE;
478 See_over[WALL3] = FALSE;
479 See_over[WALL4] = FALSE;
480 See_over[WALL5] = FALSE;
482 logx(LOG_INFO, "game started");
486 * makeboots:
487 * Put the boots in the maze
489 static void
490 makeboots(void)
492 int x, y;
493 PLAYER *pp;
495 if (conf_boots) {
496 do {
497 x = rand_num(WIDTH - 1) + 1;
498 y = rand_num(HEIGHT - 1) + 1;
499 } while (Maze[y][x] != SPACE);
500 Maze[y][x] = BOOT_PAIR;
503 for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
504 pp->p_flying = -1;
509 * checkdam:
510 * Apply damage to the victim from an attacker.
511 * If the victim dies as a result, give points to 'credit',
513 void
514 checkdam(PLAYER *victim, PLAYER *attacker, IDENT *credit, int damage,
515 char stype)
517 const char *cp;
518 int y;
520 /* Don't do anything if the victim is already in the throes of death */
521 if (victim->p_death[0] != '\0')
522 return;
524 /* Weaken slime attacks by 0.5 * number of boots the victim has on: */
525 if (stype == SLIME)
526 switch (victim->p_nboots) {
527 default:
528 break;
529 case 1:
530 damage = (damage + 1) / 2;
531 break;
532 case 2:
533 if (attacker != NULL)
534 message(attacker, "He has boots on!");
535 return;
538 /* The victim sustains some damage: */
539 victim->p_damage += damage;
541 /* Check if the victim survives the hit: */
542 if (victim->p_damage <= victim->p_damcap) {
543 /* They survive. */
544 outyx(victim, STAT_DAM_ROW, STAT_VALUE_COL, "%2d",
545 victim->p_damage);
546 return;
549 /* Describe how the victim died: */
550 switch (stype) {
551 default:
552 cp = "Killed";
553 break;
554 case FALL:
555 cp = "Killed on impact";
556 break;
557 case KNIFE:
558 cp = "Stabbed to death";
559 victim->p_ammo = 0; /* No exploding */
560 break;
561 case SHOT:
562 cp = "Shot to death";
563 break;
564 case GRENADE:
565 case SATCHEL:
566 case BOMB:
567 cp = "Bombed";
568 break;
569 case MINE:
570 case GMINE:
571 cp = "Blown apart";
572 break;
573 case SLIME:
574 cp = "Slimed";
575 if (credit != NULL)
576 credit->i_slime++;
577 break;
578 case LAVA:
579 cp = "Baked";
580 break;
581 case DSHOT:
582 cp = "Eliminated";
583 break;
586 if (credit == NULL) {
587 const char *blame;
590 * Nobody is taking the credit for the kill.
591 * Attribute it to either a mine or 'act of God'.
593 switch (stype) {
594 case MINE:
595 case GMINE:
596 blame = "a mine";
597 break;
598 default:
599 blame = "act of God";
600 break;
603 /* Set the death message: */
604 (void) snprintf(victim->p_death, sizeof victim->p_death,
605 "| %s by %s |", cp, blame);
607 /* No further score crediting needed. */
608 return;
611 /* Set the death message: */
612 (void) snprintf(victim->p_death, sizeof victim->p_death,
613 "| %s by %s |", cp, credit->i_name);
615 if (victim == attacker) {
616 /* No use killing yourself. */
617 credit->i_kills--;
618 credit->i_bkills++;
620 else if (victim->p_ident->i_team == ' '
621 || victim->p_ident->i_team != credit->i_team) {
622 /* A cross-team kill: */
623 credit->i_kills++;
624 credit->i_gkills++;
626 else {
627 /* They killed someone on the same team: */
628 credit->i_kills--;
629 credit->i_bkills++;
632 /* Compute the new credited score: */
633 credit->i_score = credit->i_kills / (double) credit->i_entries;
635 /* The victim accrues one death: */
636 victim->p_ident->i_deaths++;
638 /* Account for 'Stillborn' deaths */
639 if (victim->p_nchar == 0)
640 victim->p_ident->i_stillb++;
642 if (attacker) {
643 /* Give the attacker player a bit more strength */
644 attacker->p_damcap += conf_killgain;
645 attacker->p_damage -= conf_killgain;
646 if (attacker->p_damage < 0)
647 attacker->p_damage = 0;
649 /* Tell the attacker his new strength: */
650 outyx(attacker, STAT_DAM_ROW, STAT_VALUE_COL, "%2d/%2d",
651 attacker->p_damage, attacker->p_damcap);
653 /* Tell the attacker his new 'kill count': */
654 outyx(attacker, STAT_KILL_ROW, STAT_VALUE_COL, "%3d",
655 (attacker->p_damcap - conf_maxdam) / 2);
657 /* Update the attacker's score for everyone else */
658 y = STAT_PLAY_ROW + 1 + (attacker - Player);
659 outyx(ALL_PLAYERS, y, STAT_NAME_COL,
660 "%5.2f", attacker->p_ident->i_score);
665 * zap:
666 * Kill off a player and take them out of the game.
667 * The 'was_player' flag indicates that the player was not
668 * a monitor and needs extra cleaning up.
670 static void
671 zap(PLAYER *pp, FLAG was_player)
673 int len;
674 BULLET *bp;
675 PLAYER *np;
676 int x, y;
677 int savefd;
679 if (was_player) {
680 /* If they died from a shot, clean up shrapnel */
681 if (pp->p_undershot)
682 fixshots(pp->p_y, pp->p_x, pp->p_over);
683 /* Let the player see their last position: */
684 drawplayer(pp, FALSE);
685 /* Remove from game: */
686 Nplayer--;
689 /* Display the cause of death in the centre of the screen: */
690 len = strlen(pp->p_death);
691 x = (WIDTH - len) / 2;
692 outyx(pp, HEIGHT / 2, x, "%s", pp->p_death);
694 /* Put some horizontal lines around and below the death message: */
695 memset(pp->p_death + 1, '-', len - 2);
696 pp->p_death[0] = '+';
697 pp->p_death[len - 1] = '+';
698 outyx(pp, HEIGHT / 2 - 1, x, "%s", pp->p_death);
699 outyx(pp, HEIGHT / 2 + 1, x, "%s", pp->p_death);
701 /* Move to bottom left */
702 cgoto(pp, HEIGHT, 0);
704 savefd = pp->p_fd;
706 if (was_player) {
707 int expl_charge;
708 int expl_type;
709 int ammo_exploding;
711 /* Check all the bullets: */
712 for (bp = Bullets; bp != NULL; bp = bp->b_next) {
713 if (bp->b_owner == pp)
714 /* Zapped players can't own bullets: */
715 bp->b_owner = NULL;
716 if (bp->b_x == pp->p_x && bp->b_y == pp->p_y)
717 /* Bullets over the player are now over air: */
718 bp->b_over = SPACE;
721 /* Explode a random fraction of the player's ammo: */
722 ammo_exploding = rand_num(pp->p_ammo);
724 /* Determine the type and amount of detonation: */
725 expl_charge = rand_num(ammo_exploding + 1);
726 if (pp->p_ammo == 0)
727 /* Ignore the no-ammo case: */
728 expl_charge = expl_type = 0;
729 else if (ammo_exploding >= pp->p_ammo - 1) {
730 /* Maximal explosions always appear as slime: */
731 expl_charge = pp->p_ammo;
732 expl_type = SLIME;
733 } else {
735 * Figure out the best effective explosion
736 * type to use, given the amount of charge
738 int btype, stype;
739 for (btype = MAXBOMB - 1; btype > 0; btype--)
740 if (expl_charge >= shot_req[btype])
741 break;
742 for (stype = MAXSLIME - 1; stype > 0; stype--)
743 if (expl_charge >= slime_req[stype])
744 break;
745 /* Pick the larger of the bomb or slime: */
746 if (btype >= 0 && stype >= 0) {
747 if (shot_req[btype] > slime_req[btype])
748 btype = -1;
750 if (btype >= 0) {
751 expl_type = shot_type[btype];
752 expl_charge = shot_req[btype];
753 } else
754 expl_type = SLIME;
757 if (expl_charge > 0) {
758 char buf[BUFSIZ];
760 /* Detonate: */
761 (void) add_shot(expl_type, pp->p_y, pp->p_x,
762 pp->p_face, expl_charge, NULL,
763 TRUE, SPACE);
765 /* Explain what the explosion is about. */
766 snprintf(buf, sizeof buf, "%s detonated.",
767 pp->p_ident->i_name);
768 message(ALL_PLAYERS, buf);
770 while (pp->p_nboots-- > 0) {
771 /* Throw one of the boots away: */
772 for (np = Boot; np < &Boot[NBOOTS]; np++)
773 if (np->p_flying < 0)
774 break;
775 #ifdef DIAGNOSTIC
776 if (np >= &Boot[NBOOTS])
777 err(1, "Too many boots");
778 #endif
779 /* Start the boots from where the player is */
780 np->p_undershot = FALSE;
781 np->p_x = pp->p_x;
782 np->p_y = pp->p_y;
783 /* Throw for up to 20 steps */
784 np->p_flying = rand_num(20);
785 np->p_flyx = 2 * rand_num(6) - 5;
786 np->p_flyy = 2 * rand_num(6) - 5;
787 np->p_over = SPACE;
788 np->p_face = BOOT;
789 showexpl(np->p_y, np->p_x, BOOT);
792 /* No explosion. Leave the player's boots behind. */
793 else if (pp->p_nboots > 0) {
794 if (pp->p_nboots == 2)
795 Maze[pp->p_y][pp->p_x] = BOOT_PAIR;
796 else
797 Maze[pp->p_y][pp->p_x] = BOOT;
798 if (pp->p_undershot)
799 fixshots(pp->p_y, pp->p_x,
800 Maze[pp->p_y][pp->p_x]);
803 /* Any unexploded ammo builds up in the volcano: */
804 volcano += pp->p_ammo - expl_charge;
806 /* Volcano eruption: */
807 if (conf_volcano && rand_num(100) < volcano /
808 conf_volcano_max) {
809 /* Erupt near the middle of the map */
810 do {
811 x = rand_num(WIDTH / 2) + WIDTH / 4;
812 y = rand_num(HEIGHT / 2) + HEIGHT / 4;
813 } while (Maze[y][x] != SPACE);
815 /* Convert volcano charge into lava: */
816 (void) add_shot(LAVA, y, x, LEFTS, volcano,
817 NULL, TRUE, SPACE);
818 volcano = 0;
820 /* Tell eveyone what's happening */
821 message(ALL_PLAYERS, "Volcano eruption.");
824 /* Drone: */
825 if (conf_drone && rand_num(100) < 2) {
826 /* Find a starting place near the middle of the map: */
827 do {
828 x = rand_num(WIDTH / 2) + WIDTH / 4;
829 y = rand_num(HEIGHT / 2) + HEIGHT / 4;
830 } while (Maze[y][x] != SPACE);
832 /* Start the drone going: */
833 add_shot(DSHOT, y, x, rand_dir(),
834 shot_req[conf_mindshot +
835 rand_num(MAXBOMB - conf_mindshot)],
836 NULL, FALSE, SPACE);
839 /* Tell the zapped player's client to shut down. */
840 sendcom(pp, ENDWIN, ' ');
841 (void) fclose(pp->p_output);
843 /* Close up the gap in the Player array: */
844 End_player--;
845 if (pp != End_player) {
846 /* Move the last player into the gap: */
847 memcpy(pp, End_player, sizeof *pp);
848 outyx(ALL_PLAYERS,
849 STAT_PLAY_ROW + 1 + (pp - Player),
850 STAT_NAME_COL,
851 "%5.2f%c%-10.10s %c",
852 pp->p_ident->i_score, stat_char(pp),
853 pp->p_ident->i_name, pp->p_ident->i_team);
856 /* Erase the last player from the display: */
857 cgoto(ALL_PLAYERS, STAT_PLAY_ROW + 1 + Nplayer, STAT_NAME_COL);
858 ce(ALL_PLAYERS);
860 else {
861 /* Zap a monitor */
863 /* Close the session: */
864 sendcom(pp, ENDWIN, LAST_PLAYER);
865 (void) fclose(pp->p_output);
867 /* shuffle the monitor table */
868 End_monitor--;
869 if (pp != End_monitor) {
870 memcpy(pp, End_monitor, sizeof *pp);
871 outyx(ALL_PLAYERS,
872 STAT_MON_ROW + 1 + (pp - Player), STAT_NAME_COL,
873 "%5.5s %-10.10s %c", " ",
874 pp->p_ident->i_name, pp->p_ident->i_team);
877 /* Erase the last monitor in the list */
878 cgoto(ALL_PLAYERS,
879 STAT_MON_ROW + 1 + (End_monitor - Monitor),
880 STAT_NAME_COL);
881 ce(ALL_PLAYERS);
884 /* Update the file descriptor sets used by select: */
885 FD_CLR(savefd, &Fds_mask);
886 if (Num_fds == savefd + 1) {
887 Num_fds = Socket;
888 if (Server_socket > Socket)
889 Num_fds = Server_socket;
890 for (np = Player; np < End_player; np++)
891 if (np->p_fd > Num_fds)
892 Num_fds = np->p_fd;
893 for (np = Monitor; np < End_monitor; np++)
894 if (np->p_fd > Num_fds)
895 Num_fds = np->p_fd;
896 Num_fds++;
901 * rand_num:
902 * Return a random number in a given range.
905 rand_num(int range)
907 if (range == 0)
908 return 0;
909 return (random() % range);
913 * havechar:
914 * Check to see if we have any characters in the input queue; if
915 * we do, read them, stash them away, and return TRUE; else return
916 * FALSE.
918 static int
919 havechar(PLAYER *pp)
921 int ret;
923 /* Do we already have characters? */
924 if (pp->p_ncount < pp->p_nchar)
925 return TRUE;
926 /* Ignore if nothing to read. */
927 if (!FD_ISSET(pp->p_fd, &Have_inp))
928 return FALSE;
929 /* Remove the player from the read set until we have drained them: */
930 FD_CLR(pp->p_fd, &Have_inp);
932 /* Suck their keypresses into a buffer: */
933 check_again:
934 errno = 0;
935 ret = read(pp->p_fd, pp->p_cbuf, sizeof pp->p_cbuf);
936 if (ret == -1) {
937 if (errno == EINTR)
938 goto check_again;
939 if (errno == EAGAIN) {
940 #ifdef DEBUG
941 warn("Have_inp is wrong for %d", pp->p_fd);
942 #endif
943 return FALSE;
945 logit(LOG_INFO, "read");
947 if (ret > 0) {
948 /* Got some data */
949 pp->p_nchar = ret;
950 } else {
951 /* Connection was lost/closed: */
952 pp->p_cbuf[0] = 'q';
953 pp->p_nchar = 1;
955 /* Reset pointer into read buffer */
956 pp->p_ncount = 0;
957 return TRUE;
961 * cleanup:
962 * Exit with the given value, cleaning up any droppings lying around
964 void
965 cleanup(int eval)
967 PLAYER *pp;
969 /* Place their cursor in a friendly position: */
970 cgoto(ALL_PLAYERS, HEIGHT, 0);
972 /* Send them all the ENDWIN command: */
973 sendcom(ALL_PLAYERS, ENDWIN, LAST_PLAYER);
975 /* And close their connections: */
976 for (pp = Player; pp < End_player; pp++)
977 (void) fclose(pp->p_output);
978 for (pp = Monitor; pp < End_monitor; pp++)
979 (void) fclose(pp->p_output);
981 /* Close the server socket: */
982 (void) close(Socket);
984 /* The end: */
985 logx(LOG_INFO, "game over");
986 exit(eval);
990 * send_stats:
991 * Accept a connection to the statistics port, and emit
992 * the stats.
994 static void
995 send_stats(void)
997 FILE *fp;
998 int s;
999 struct sockaddr_in sockstruct;
1000 socklen_t socklen;
1001 struct request_info ri;
1002 int flags;
1004 /* Accept a connection to the statistics socket: */
1005 socklen = sizeof sockstruct;
1006 s = accept(Status, (struct sockaddr *) &sockstruct, &socklen);
1007 if (s < 0) {
1008 if (errno == EINTR)
1009 return;
1010 logx(LOG_ERR, "accept");
1011 return;
1014 /* Check for access permissions: */
1015 request_init(&ri, RQ_DAEMON, "huntd", RQ_FILE, s, 0);
1016 fromhost(&ri);
1017 if (hosts_access(&ri) == 0) {
1018 logx(LOG_INFO, "rejected connection from %s", eval_client(&ri));
1019 close(s);
1020 return;
1023 /* Don't allow the writes to block: */
1024 flags = fcntl(s, F_GETFL, 0);
1025 flags |= O_NDELAY;
1026 (void) fcntl(s, F_SETFL, flags);
1028 fp = fdopen(s, "w");
1029 if (fp == NULL) {
1030 logit(LOG_ERR, "fdopen");
1031 (void) close(s);
1032 return;
1035 print_stats(fp);
1037 (void) fclose(fp);
1041 * print_stats:
1042 * emit the game statistics
1044 void
1045 print_stats(FILE *fp)
1047 IDENT *ip;
1048 PLAYER *pp;
1050 /* Send the statistics as raw text down the socket: */
1051 fputs("Name\t\tScore\tDucked\tAbsorb\tFaced\tShot\tRobbed\tMissed\tSlimeK\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 == ' ' ? ' ' : ']'
1058 if (strlen(ip->i_name) + 3 < 8)
1059 putc('\t', fp);
1060 fprintf(fp, "%.2f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
1061 ip->i_score, ip->i_ducked, ip->i_absorbed,
1062 ip->i_faced, ip->i_shot, ip->i_robbed,
1063 ip->i_missed, ip->i_slime);
1065 fputs("\n\nName\t\tEnemy\tFriend\tDeaths\tStill\tSaved\tConnect\n", fp);
1066 for (ip = Scores; ip != NULL; ip = ip->i_next) {
1067 fprintf(fp, "%s%c%c%c\t", ip->i_name,
1068 ip->i_team == ' ' ? ' ' : '[',
1069 ip->i_team,
1070 ip->i_team == ' ' ? ' ' : ']'
1072 if (strlen(ip->i_name) + 3 < 8)
1073 putc('\t', fp);
1074 fprintf(fp, "%d\t%d\t%d\t%d\t%d\t",
1075 ip->i_gkills, ip->i_bkills, ip->i_deaths,
1076 ip->i_stillb, ip->i_saved);
1077 for (pp = Player; pp < End_player; pp++)
1078 if (pp->p_ident == ip)
1079 putc('p', fp);
1080 for (pp = Monitor; pp < End_monitor; pp++)
1081 if (pp->p_ident == ip)
1082 putc('m', fp);
1083 putc('\n', fp);
1089 * Send the game statistics to the controlling tty
1091 static void
1092 siginfo(int sig __unused)
1094 int tty;
1095 FILE *fp;
1097 if ((tty = open(_PATH_TTY, O_WRONLY)) >= 0) {
1098 fp = fdopen(tty, "w");
1099 print_stats(fp);
1100 answer_info(fp);
1101 fclose(fp);
1106 * clear_scores:
1107 * Clear the Scores list.
1109 static void
1110 clear_scores(void)
1112 IDENT *ip, *nextip;
1114 /* Release the list of scores: */
1115 for (ip = Scores; ip != NULL; ip = nextip) {
1116 nextip = ip->i_next;
1117 free((char *) ip);
1119 Scores = NULL;
1123 * announce_game:
1124 * Publically announce the game
1126 static void
1127 announce_game(void)
1130 /* TODO: could use system() to do something user-configurable */
1134 * Handle a UDP packet sent to the well known port.
1136 static void
1137 handle_wkport(int fd)
1139 struct sockaddr fromaddr;
1140 socklen_t fromlen;
1141 u_int16_t query;
1142 u_int16_t response;
1143 struct request_info ri;
1145 request_init(&ri, RQ_DAEMON, "huntd", RQ_FILE, fd, 0);
1146 fromhost(&ri);
1147 fromlen = sizeof fromaddr;
1148 if (recvfrom(fd, &query, sizeof query, 0, &fromaddr, &fromlen) == -1)
1150 logit(LOG_WARNING, "recvfrom");
1151 return;
1154 #ifdef DEBUG
1155 fprintf(stderr, "query %d (%s) from %s:%d\n", query,
1156 query == C_MESSAGE ? "C_MESSAGE" :
1157 query == C_SCORES ? "C_SCORES" :
1158 query == C_PLAYER ? "C_PLAYER" :
1159 query == C_MONITOR ? "C_MONITOR" : "?",
1160 inet_ntoa(((struct sockaddr_in *)&fromaddr)->sin_addr),
1161 ntohs(((struct sockaddr_in *)&fromaddr)->sin_port));
1162 #endif
1164 /* Do we allow access? */
1165 if (hosts_access(&ri) == 0) {
1166 logx(LOG_INFO, "rejected connection from %s", eval_client(&ri));
1167 return;
1170 query = ntohs(query);
1172 switch (query) {
1173 case C_MESSAGE:
1174 if (Nplayer <= 0)
1175 /* Don't bother replying if nobody to talk to: */
1176 return;
1177 /* Return the number of people playing: */
1178 response = Nplayer;
1179 break;
1180 case C_SCORES:
1181 /* Someone wants the statistics port: */
1182 response = stat_port;
1183 break;
1184 case C_PLAYER:
1185 case C_MONITOR:
1186 /* Someone wants to play or watch: */
1187 if (query == C_MONITOR && Nplayer <= 0)
1188 /* Don't bother replying if there's nothing to watch: */
1189 return;
1190 /* Otherwise, tell them how to get to the game: */
1191 response = sock_port;
1192 break;
1193 default:
1194 logit(LOG_INFO, "unknown udp query %d", query);
1195 return;
1198 response = ntohs(response);
1199 if (sendto(fd, &response, sizeof response, 0,
1200 &fromaddr, sizeof fromaddr) == -1)
1201 logit(LOG_WARNING, "sendto");