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