xref: /dragonfly/games/hunt/huntd/driver.c (revision f746689a)
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, 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 				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 				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 }
1203