Fix a 64-bit warning. While here, also fix a gcc34 warning.
[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  * + Redistributions of source code must retain the above copyright 
10  *   notice, this list of conditions and the following disclaimer.
11  * + Redistributions in binary form must reproduce the above copyright 
12  *   notice, this list of conditions and the following disclaimer in the 
13  *   documentation and/or other materials provided with the distribution.
14  * + Neither the name of the University of California, San Francisco nor 
15  *   the names of its contributors may be used to endorse or promote 
16  *   products derived from this software without specific prior written 
17  *   permission.
18  * 
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
22  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  *
31  * $OpenBSD: driver.c,v 1.17 2007/04/02 14:55:16 jmc Exp $
32  * $NetBSD: driver.c,v 1.5 1997/10/20 00:37:16 lukem Exp $
33  * $DragonFly: src/games/hunt/huntd/driver.c,v 1.3 2008/11/10 15:28:13 swildner Exp $
34  */
35
36 #include <sys/ioctl.h>
37 #include <sys/stat.h>
38 #include <sys/time.h>
39 #include <sys/socket.h>
40
41 #include <netinet/in.h>
42 #include <arpa/inet.h>
43
44 #include <err.h>
45 #include <errno.h>
46 #include <signal.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <unistd.h>
50 #include <stdio.h>
51 #include <tcpd.h>
52 #include <syslog.h>
53 #include <netdb.h>
54 #include <paths.h>
55 #include <fcntl.h>
56 #include "hunt.h"
57 #include "conf.h"
58 #include "server.h"
59
60 char    *First_arg;             /* pointer to argv[0] */
61 u_int16_t Server_port;
62 int     Server_socket;          /* test socket to answer datagrams */
63 FLAG    should_announce = TRUE; /* true if listening on standard port */
64 u_short sock_port;              /* port # of tcp listen socket */
65 u_short stat_port;              /* port # of statistics tcp socket */
66 in_addr_t Server_addr = INADDR_ANY;     /* address to bind to */
67
68 static  void    clear_scores(void);
69 static  int     havechar(PLAYER *);
70 static  void    init(void);
71         int     main(int, char *[]);
72 static  void    makeboots(void);
73 static  void    send_stats(void);
74 static  void    zap(PLAYER *, FLAG);
75 static  void    announce_game(void);
76 static  void    siginfo(int);
77 static  void    print_stats(FILE *);
78 static  void    handle_wkport(int);
79
80 /*
81  * main:
82  *      The main program.
83  */
84 int
85 main(int ac, char **av)
86 {
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;
99
100         First_arg = av[0];
101
102         config();
103
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);
127                 }
128         }
129         if (optind < ac)
130                 goto erred;
131
132         /* Open syslog: */
133         openlog("huntd", LOG_PID | (conf_logerr && !server? LOG_PERROR : 0),
134                 LOG_DAEMON);
135
136         /* Initialise game parameters: */
137         init();
138
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);
151                         }
152                 } while (nready < 0);
153
154                 if (nready == 0) {
155                         /*
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.
160                          */
161                         do {
162                                 if (conf_simstep && can_moveshots()) {
163                                 /*
164                                  * block for a short time before continuing
165                                  * with explosions, bullets and whatnot
166                                  */
167                                         to = &timeout;
168                                         to->tv_sec =  conf_simstep / 1000000;
169                                         to->tv_usec = conf_simstep % 1000000;
170                                 } else
171                                 /*
172                                  * since there's nothing going on,
173                                  * just block waiting for external activity
174                                  */
175                                         to = NULL;
176                                 
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);
184                                 }
185                         } while (nready < 0);
186                 }
187
188                 /* Remember which descriptors are active: */
189                 Have_inp = read_fds;
190
191                 /* Answer new player connections: */
192                 if (FD_ISSET(Socket, &Have_inp))
193                         answer_first();
194
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)) {
200                                 /*
201                                  * Remove from the spawn list. (fd remains in 
202                                  * read set).
203                                  */
204                                 *sp->prevnext = sp->next;
205                                 if (sp->next)
206                                         sp->next->prevnext = sp->prevnext;
207                                 free(sp);
208
209                                 /* We probably consumed all data. */
210                                 FD_CLR(fd, &Have_inp);
211
212                                 /* Announce game if this is the first spawn. */
213                                 if (first && should_announce)
214                                         announce_game();
215                                 first = FALSE;
216                         } 
217                         sp = spnext;
218                 }
219
220                 /* Process input and move bullets until we've exhausted input */
221                 had_char = TRUE;
222                 while (had_char) {
223
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++;
235
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;
242                                 }
243                         for (pp = Monitor; pp < End_monitor; pp++)
244                                 if (havechar(pp)) {
245                                         mon_execute(pp);
246                                         pp->p_nexec++;
247                                         had_char = TRUE;
248                                 }
249                 }
250
251                 /* Handle a datagram sent to the server socket: */
252                 if (FD_ISSET(Server_socket, &Have_inp))
253                         handle_wkport(Server_socket);
254
255                 /* Answer statistics connections: */
256                 if (FD_ISSET(Status, &Have_inp))
257                         send_stats();
258
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;
264                         }
265                         flush(pp);
266                 }
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;
271                         }
272                         flush(pp);
273                 }
274         } while (Nplayer > 0);
275
276         /* No more players! */
277
278         /* No players yet or a continuous game? */
279         if (first || conf_linger < 0)
280                 goto again;
281
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;
289                 }
290                 read_fds = Fds_mask;
291                 linger.tv_sec = conf_linger;
292                 linger.tv_usec = 0;
293         }
294         if (ret > 0)
295                 /* Someone returned! Resume the game: */
296                 goto again;
297         /* else, it timed out, and the game is really over. */
298
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;
307         }
308
309         /* Get rid of any attached monitors: */
310         for (pp = Monitor; pp < End_monitor; )
311                 zap(pp, FALSE);
312
313         /* Fin: */
314         cleanup(0);
315         exit(0);
316 }
317
318 /*
319  * init:
320  *      Initialize the global parameters.
321  */
322 static void
323 init(void)
324 {
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;
332
333         (void) setsid();
334         if (setpgid(getpid(), getpid()) == -1)
335                 err(1, "setpgid");
336
337         sact.sa_flags = SA_RESTART;
338         sigemptyset(&sact.sa_mask);
339
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");
348
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");
355
356         /* Handle INFO: */
357         sact.sa_handler = siginfo;
358         if (sigaction(SIGINFO, &sact, NULL) == -1)
359                 err(1, "sigaction SIGINFO");
360
361         if (chdir("/") == -1)
362                 warn("chdir");
363         (void) umask(0777);
364
365         /* Initialize statistics socket: */
366         addr.sin_family = AF_INET;
367         addr.sin_addr.s_addr = Server_addr;
368         addr.sin_port = 0;
369
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);
374         }
375         if (listen(Status, 5) == -1) {
376                 logit(LOG_ERR, "listen");
377                 cleanup(1);
378         }
379
380         len = sizeof (struct sockaddr_in);
381         if (getsockname(Status, (struct sockaddr *) &addr, &len) < 0)  {
382                 logit(LOG_ERR, "getsockname");
383                 cleanup(1);
384         }
385         stat_port = ntohs(addr.sin_port);
386
387         /* Initialize main socket: */
388         addr.sin_family = AF_INET;
389         addr.sin_addr.s_addr = Server_addr;
390         addr.sin_port = 0;
391
392         Socket = socket(AF_INET, SOCK_STREAM, 0);
393
394         if (bind(Socket, (struct sockaddr *) &addr, sizeof addr) < 0) {
395                 logit(LOG_ERR, "bind");
396                 cleanup(1);
397         }
398         if (listen(Socket, 5) == -1) {
399                 logit(LOG_ERR, "listen");
400                 cleanup(1);
401         }
402
403         len = sizeof (struct sockaddr_in);
404         if (getsockname(Socket, (struct sockaddr *) &addr, &len) < 0)  {
405                 logit(LOG_ERR, "getsockname");
406                 cleanup(1);
407         }
408         sock_port = ntohs(addr.sin_port);
409
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;
415
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;
423         }
424
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);
436                 }
437         } else {
438                 /* We need to listen on a socket: */
439                 test_port = addr;
440                 test_port.sin_port = htons((u_short) Server_port);
441
442                 Server_socket = socket(AF_INET, SOCK_DGRAM, 0);
443
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");
448
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);
453                 }
454
455                 /* Datagram sockets do not need a listen() call. */
456         }
457
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;
462
463         /* Initialise the random seed: */
464         srandomdev();
465
466         /* Dig the maze: */
467         makemaze();
468
469         /* Create some boots, if needed: */
470         makeboots();
471
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;
481
482         logx(LOG_INFO, "game started");
483 }
484
485 /*
486  * makeboots:
487  *      Put the boots in the maze
488  */
489 static void
490 makeboots(void)
491 {
492         int     x, y;
493         PLAYER  *pp;
494
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;
501         }
502
503         for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
504                 pp->p_flying = -1;
505 }
506
507
508 /*
509  * checkdam:
510  *      Apply damage to the victim from an attacker.
511  *      If the victim dies as a result, give points to 'credit',
512  */
513 void
514 checkdam(PLAYER *victim, PLAYER *attacker, IDENT *credit, int damage,
515     char stype)
516 {
517         const char      *cp;
518         int     y;
519
520         /* Don't do anything if the victim is already in the throes of death */
521         if (victim->p_death[0] != '\0')
522                 return;
523
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;
536                 }
537
538         /* The victim sustains some damage: */
539         victim->p_damage += damage;
540
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;
547         }
548
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;
584         }
585
586         if (credit == NULL) {
587                 const char *blame;
588
589                 /*
590                  * Nobody is taking the credit for the kill.
591                  * Attribute it to either a mine or 'act of God'.
592                  */
593                 switch (stype) {
594                 case MINE:
595                 case GMINE:
596                         blame = "a mine";
597                         break;
598                 default:
599                         blame = "act of God";
600                         break;
601                 }
602
603                 /* Set the death message: */
604                 (void) snprintf(victim->p_death, sizeof victim->p_death, 
605                         "| %s by %s |", cp, blame);
606
607                 /* No further score crediting needed. */
608                 return;
609         }
610
611         /* Set the death message: */
612         (void) snprintf(victim->p_death, sizeof victim->p_death, 
613                 "| %s by %s |", cp, credit->i_name);
614
615         if (victim == attacker) {
616                 /* No use killing yourself. */
617                 credit->i_kills--;
618                 credit->i_bkills++;
619         } 
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++;
625         }
626         else {
627                 /* They killed someone on the same team: */
628                 credit->i_kills--;
629                 credit->i_bkills++;
630         }
631
632         /* Compute the new credited score: */
633         credit->i_score = credit->i_kills / (double) credit->i_entries;
634
635         /* The victim accrues one death: */
636         victim->p_ident->i_deaths++;
637
638         /* Account for 'Stillborn' deaths */
639         if (victim->p_nchar == 0)
640                 victim->p_ident->i_stillb++;
641
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;
648
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);
652
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);
656
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);
661         }
662 }
663
664 /*
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.
669  */
670 static void
671 zap(PLAYER *pp, FLAG was_player)
672 {
673         int     len;
674         BULLET  *bp;
675         PLAYER  *np;
676         int     x, y;
677         int     savefd;
678
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--;
687         }
688
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);
693
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);
700
701         /* Move to bottom left */
702         cgoto(pp, HEIGHT, 0);
703
704         savefd = pp->p_fd;
705
706         if (was_player) {
707                 int     expl_charge;
708                 int     expl_type;
709                 int     ammo_exploding;
710
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;
719                 }
720
721                 /* Explode a random fraction of the player's ammo: */
722                 ammo_exploding = rand_num(pp->p_ammo);
723
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 {
734                         /*
735                          * Figure out the best effective explosion
736                          * type to use, given the amount of charge
737                          */
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;
749                         }
750                         if (btype >= 0)  {
751                                 expl_type = shot_type[btype];
752                                 expl_charge = shot_req[btype];
753                         } else
754                                 expl_type = SLIME;
755                 }
756
757                 if (expl_charge > 0) {
758                         char buf[BUFSIZ];
759
760                         /* Detonate: */
761                         (void) add_shot(expl_type, pp->p_y, pp->p_x, 
762                             pp->p_face, expl_charge, (PLAYER *) NULL, 
763                             TRUE, SPACE);
764
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);
769
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);
790                         }
791                 }
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]);
801                 }
802
803                 /* Any unexploded ammo builds up in the volcano: */
804                 volcano += pp->p_ammo - expl_charge;
805
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);
814
815                         /* Convert volcano charge into lava: */
816                         (void) add_shot(LAVA, y, x, LEFTS, volcano,
817                                 (PLAYER *) NULL, TRUE, SPACE);
818                         volcano = 0;
819
820                         /* Tell eveyone what's happening */
821                         message(ALL_PLAYERS, "Volcano eruption.");
822                 }
823
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);
831
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                                 (PLAYER *) NULL, FALSE, SPACE);
837                 }
838
839                 /* Tell the zapped player's client to shut down. */
840                 sendcom(pp, ENDWIN, ' ');
841                 (void) fclose(pp->p_output);
842
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);
854                 }
855
856                 /* Erase the last player from the display: */
857                 cgoto(ALL_PLAYERS, STAT_PLAY_ROW + 1 + Nplayer, STAT_NAME_COL);
858                 ce(ALL_PLAYERS);
859         }
860         else {
861                 /* Zap a monitor */
862
863                 /* Close the session: */
864                 sendcom(pp, ENDWIN, LAST_PLAYER);
865                 (void) fclose(pp->p_output);
866
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);
875                 }
876
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);
882         }
883
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++;
897         }
898 }
899
900 /*
901  * rand_num:
902  *      Return a random number in a given range.
903  */
904 int
905 rand_num(int range)
906 {
907         if (range == 0)
908                 return 0;
909         return (random() % range);
910 }
911
912 /*
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.
917  */
918 static int
919 havechar(PLAYER *pp)
920 {
921         int ret;
922
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);
931
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;
944                 }
945                 logit(LOG_INFO, "read");
946         }
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;
954         }
955         /* Reset pointer into read buffer */
956         pp->p_ncount = 0;
957         return TRUE;
958 }
959
960 /*
961  * cleanup:
962  *      Exit with the given value, cleaning up any droppings lying around
963  */
964 void
965 cleanup(int eval)
966 {
967         PLAYER  *pp;
968
969         /* Place their cursor in a friendly position: */
970         cgoto(ALL_PLAYERS, HEIGHT, 0);
971
972         /* Send them all the ENDWIN command: */
973         sendcom(ALL_PLAYERS, ENDWIN, LAST_PLAYER);
974
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);
980
981         /* Close the server socket: */
982         (void) close(Socket);
983
984         /* The end: */
985         logx(LOG_INFO, "game over");
986         exit(eval);
987 }
988
989 /*
990  * send_stats:
991  *      Accept a connection to the statistics port, and emit
992  *      the stats.
993  */
994 static void
995 send_stats(void)
996 {
997         FILE    *fp;
998         int     s;
999         struct sockaddr_in      sockstruct;
1000         socklen_t       socklen;
1001         struct request_info ri;
1002         int     flags;
1003
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;
1012         }
1013
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;
1021         }
1022
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);
1027
1028         fp = fdopen(s, "w");
1029         if (fp == NULL) {
1030                 logit(LOG_ERR, "fdopen");
1031                 (void) close(s);
1032                 return;
1033         }
1034
1035         print_stats(fp);
1036
1037         (void) fclose(fp);
1038 }
1039
1040 /*
1041  * print_stats:
1042  *      emit the game statistics
1043  */
1044 void
1045 print_stats(FILE *fp)
1046 {
1047         IDENT   *ip;
1048         PLAYER  *pp;
1049
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 == ' ' ? ' ' : ']'
1057                 );
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);
1064         }
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 == ' ' ? ' ' : ']'
1071                 );
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);
1084         }
1085 }
1086
1087
1088 /*
1089  * Send the game statistics to the controlling tty
1090  */
1091 static void
1092 siginfo(int sig __unused)
1093 {
1094         int tty;
1095         FILE *fp;
1096
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);
1102         }
1103 }
1104
1105 /*
1106  * clear_scores:
1107  *      Clear the Scores list.
1108  */
1109 static void
1110 clear_scores(void)
1111 {
1112         IDENT   *ip, *nextip;
1113
1114         /* Release the list of scores: */
1115         for (ip = Scores; ip != NULL; ip = nextip) {
1116                 nextip = ip->i_next;
1117                 free((char *) ip);
1118         }
1119         Scores = NULL;
1120 }
1121
1122 /*
1123  * announce_game:
1124  *      Publically announce the game
1125  */
1126 static void
1127 announce_game(void)
1128 {
1129
1130         /* TODO: could use system() to do something user-configurable */
1131 }
1132
1133 /*
1134  * Handle a UDP packet sent to the well known port.
1135  */
1136 static void
1137 handle_wkport(int fd)
1138 {
1139         struct sockaddr         fromaddr;
1140         socklen_t               fromlen;
1141         u_int16_t               query;
1142         u_int16_t               response;
1143         struct request_info     ri;
1144
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)
1149         {
1150                 logit(LOG_WARNING, "recvfrom");
1151                 return;
1152         }
1153
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
1163
1164         /* Do we allow access? */
1165         if (hosts_access(&ri) == 0) {
1166                 logx(LOG_INFO, "rejected connection from %s", eval_client(&ri));
1167                 return;
1168         }
1169
1170         query = ntohs(query);
1171
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;
1196         }
1197
1198         response = ntohs(response);
1199         if (sendto(fd, &response, sizeof response, 0,
1200             &fromaddr, sizeof fromaddr) == -1)
1201                 logit(LOG_WARNING, "sendto");
1202 }