xref: /openbsd/games/hunt/huntd/driver.c (revision 274d7c50)
1 /*	$OpenBSD: driver.c,v 1.29 2017/01/21 08:22:57 krw Exp $	*/
2 /*	$NetBSD: driver.c,v 1.5 1997/10/20 00:37:16 lukem Exp $	*/
3 /*
4  * Copyright (c) 1983-2003, Regents of the University of California.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are
9  * met:
10  *
11  * + Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  * + Redistributions in binary form must reproduce the above copyright
14  *   notice, this list of conditions and the following disclaimer in the
15  *   documentation and/or other materials provided with the distribution.
16  * + Neither the name of the University of California, San Francisco nor
17  *   the names of its contributors may be used to endorse or promote
18  *   products derived from this software without specific prior written
19  *   permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 #include <sys/stat.h>
35 
36 #include <arpa/inet.h>
37 
38 #include <err.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <netdb.h>
42 #include <paths.h>
43 #include <signal.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <syslog.h>
47 #include <unistd.h>
48 
49 #include "conf.h"
50 #include "hunt.h"
51 #include "server.h"
52 
53 u_int16_t Server_port;
54 int	Server_socket;		/* test socket to answer datagrams */
55 FLAG	should_announce = TRUE;	/* true if listening on standard port */
56 u_short	sock_port;		/* port # of tcp listen socket */
57 u_short	stat_port;		/* port # of statistics tcp socket */
58 in_addr_t Server_addr = INADDR_ANY;	/* address to bind to */
59 
60 static	void	clear_scores(void);
61 static	int	havechar(PLAYER *);
62 static	void	init(int);
63 static	void	makeboots(void);
64 static	void	send_stats(void);
65 static	void	zap(PLAYER *, FLAG);
66 static  void	announce_game(void);
67 static	void	siginfo(int);
68 static	void	print_stats(FILE *);
69 static	void	handle_wkport(int);
70 
71 /*
72  * main:
73  *	The main program.
74  */
75 int
76 main(int ac, char **av)
77 {
78 	PLAYER		*pp;
79 	int		had_char;
80 	static fd_set	read_fds;
81 	static FLAG	first = TRUE;
82 	static FLAG	server = FALSE;
83 	extern int	optind;
84 	extern char	*optarg;
85 	extern char	*__progname;
86 	int		c;
87 	static struct timeval	linger = { 0, 0 };
88 	static struct timeval	timeout = { 0, 0 }, *to;
89 	struct spawn	*sp, *spnext;
90 	int		ret;
91 	int		nready;
92 	int		fd;
93 	int		background = 0;
94 
95 	config();
96 
97 	while ((c = getopt(ac, av, "bsp:a:D:")) != -1) {
98 		switch (c) {
99 		  case 'b':
100 			background = 1;
101 			conf_syslog = 1;
102 			conf_logerr = 0;
103 			break;
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 [-bs] [-a addr] [-D var=value] "
122 			    "[-p port]\n",
123 			    __progname);
124 			return 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(background);
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 	return 0;
314 }
315 
316 /*
317  * init:
318  *	Initialize the global parameters.
319  */
320 static void
321 init(int background)
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 	sact.sa_flags = SA_RESTART;
332 	sigemptyset(&sact.sa_mask);
333 
334 	/* Ignore HUP, QUIT and PIPE: */
335 	sact.sa_handler = SIG_IGN;
336 	if (sigaction(SIGHUP, &sact, NULL) == -1)
337 		err(1, "sigaction SIGHUP");
338 	if (sigaction(SIGQUIT, &sact, NULL) == -1)
339 		err(1, "sigaction SIGQUIT");
340 	if (sigaction(SIGPIPE, &sact, NULL) == -1)
341 		err(1, "sigaction SIGPIPE");
342 
343 	/* Clean up gracefully on INT and TERM: */
344 	sact.sa_handler = cleanup;
345 	if (sigaction(SIGINT, &sact, NULL) == -1)
346 		err(1, "sigaction SIGINT");
347 	if (sigaction(SIGTERM, &sact, NULL) == -1)
348 		err(1, "sigaction SIGTERM");
349 
350 	/* Handle INFO: */
351 	sact.sa_handler = siginfo;
352 	if (sigaction(SIGINFO, &sact, NULL) == -1)
353 		err(1, "sigaction SIGINFO");
354 
355 	if (chdir("/") == -1)
356 		warn("chdir");
357 	(void) umask(0777);
358 
359 	/* Initialize statistics socket: */
360 	addr.sin_family = AF_INET;
361 	addr.sin_addr.s_addr = Server_addr;
362 	addr.sin_port = 0;
363 
364 	Status = socket(AF_INET, SOCK_STREAM, 0);
365 	if (bind(Status, (struct sockaddr *) &addr, sizeof addr) < 0) {
366 		logit(LOG_ERR, "bind");
367 		cleanup(1);
368 	}
369 	if (listen(Status, 5) == -1) {
370 		logit(LOG_ERR, "listen");
371 		cleanup(1);
372 	}
373 
374 	len = sizeof (struct sockaddr_in);
375 	if (getsockname(Status, (struct sockaddr *) &addr, &len) < 0)  {
376 		logit(LOG_ERR, "getsockname");
377 		cleanup(1);
378 	}
379 	stat_port = ntohs(addr.sin_port);
380 
381 	/* Initialize main socket: */
382 	addr.sin_family = AF_INET;
383 	addr.sin_addr.s_addr = Server_addr;
384 	addr.sin_port = 0;
385 
386 	Socket = socket(AF_INET, SOCK_STREAM, 0);
387 
388 	if (bind(Socket, (struct sockaddr *) &addr, sizeof addr) < 0) {
389 		logit(LOG_ERR, "bind");
390 		cleanup(1);
391 	}
392 	if (listen(Socket, 5) == -1) {
393 		logit(LOG_ERR, "listen");
394 		cleanup(1);
395 	}
396 
397 	len = sizeof (struct sockaddr_in);
398 	if (getsockname(Socket, (struct sockaddr *) &addr, &len) < 0)  {
399 		logit(LOG_ERR, "getsockname");
400 		cleanup(1);
401 	}
402 	sock_port = ntohs(addr.sin_port);
403 
404 	/* Initialize minimal select mask */
405 	FD_ZERO(&Fds_mask);
406 	FD_SET(Socket, &Fds_mask);
407 	FD_SET(Status, &Fds_mask);
408 	Num_fds = ((Socket > Status) ? Socket : Status) + 1;
409 
410 	/* Find the port that huntd should run on */
411 	if (Server_port == 0) {
412 		se = getservbyname("hunt", "udp");
413 		if (se != NULL)
414 			Server_port = ntohs(se->s_port);
415 		else
416 			Server_port = HUNT_PORT;
417 	}
418 
419 	/* Check if stdin is a socket: */
420 	len = sizeof (struct sockaddr_in);
421 	if (getsockname(STDIN_FILENO, (struct sockaddr *) &test_port, &len) >= 0
422 	    && test_port.sin_family == AF_INET) {
423 		/* We are probably running from inetd:  don't log to stderr */
424 		Server_socket = STDIN_FILENO;
425 		conf_logerr = 0;
426 		if (test_port.sin_port != htons((u_short) Server_port)) {
427 			/* Private game */
428 			should_announce = FALSE;
429 			Server_port = ntohs(test_port.sin_port);
430 		}
431 	} else {
432 		/* We need to listen on a socket: */
433 		test_port = addr;
434 		test_port.sin_port = htons((u_short) Server_port);
435 
436 		Server_socket = socket(AF_INET, SOCK_DGRAM, 0);
437 
438 		/* Permit multiple huntd's on the same port. */
439 		if (setsockopt(Server_socket, SOL_SOCKET, SO_REUSEPORT, &true,
440 		    sizeof true) < 0)
441 			logit(LOG_ERR, "setsockopt SO_REUSEADDR");
442 
443 		if (bind(Server_socket, (struct sockaddr *) &test_port,
444 		    sizeof test_port) < 0) {
445 			logit(LOG_ERR, "bind port %d", Server_port);
446 			cleanup(1);
447 		}
448 
449 		/* Become a daemon if asked to do so. */
450 		if (background)
451 			daemon(0, 0);
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 	/* Dig the maze: */
462 	makemaze();
463 
464 	/* Create some boots, if needed: */
465 	makeboots();
466 
467 	/* Construct a table of what objects a player can see over: */
468 	for (i = 0; i < NASCII; i++)
469 		See_over[i] = TRUE;
470 	See_over[DOOR] = FALSE;
471 	See_over[WALL1] = FALSE;
472 	See_over[WALL2] = FALSE;
473 	See_over[WALL3] = FALSE;
474 	See_over[WALL4] = FALSE;
475 	See_over[WALL5] = FALSE;
476 
477 	logx(LOG_INFO, "game started");
478 }
479 
480 /*
481  * makeboots:
482  *	Put the boots in the maze
483  */
484 static void
485 makeboots(void)
486 {
487 	int	x, y;
488 	PLAYER	*pp;
489 
490 	if (conf_boots) {
491 		do {
492 			x = rand_num(WIDTH - 1) + 1;
493 			y = rand_num(HEIGHT - 1) + 1;
494 		} while (Maze[y][x] != SPACE);
495 		Maze[y][x] = BOOT_PAIR;
496 	}
497 
498 	for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
499 		pp->p_flying = -1;
500 }
501 
502 
503 /*
504  * checkdam:
505  *	Apply damage to the victim from an attacker.
506  *	If the victim dies as a result, give points to 'credit',
507  */
508 void
509 checkdam(PLAYER *victim, PLAYER *attacker, IDENT *credit, int damage,
510 	char shot_type)
511 {
512 	char	*cp;
513 	int	y;
514 
515 	/* Don't do anything if the victim is already in the throes of death */
516 	if (victim->p_death[0] != '\0')
517 		return;
518 
519 	/* Weaken slime attacks by 0.5 * number of boots the victim has on: */
520 	if (shot_type == SLIME)
521 		switch (victim->p_nboots) {
522 		  default:
523 			break;
524 		  case 1:
525 			damage = (damage + 1) / 2;
526 			break;
527 		  case 2:
528 			if (attacker != NULL)
529 				message(attacker, "He has boots on!");
530 			return;
531 		}
532 
533 	/* The victim sustains some damage: */
534 	victim->p_damage += damage;
535 
536 	/* Check if the victim survives the hit: */
537 	if (victim->p_damage <= victim->p_damcap) {
538 		/* They survive. */
539 		outyx(victim, STAT_DAM_ROW, STAT_VALUE_COL, "%2d",
540 			victim->p_damage);
541 		return;
542 	}
543 
544 	/* Describe how the victim died: */
545 	switch (shot_type) {
546 	  default:
547 		cp = "Killed";
548 		break;
549 	  case FALL:
550 		cp = "Killed on impact";
551 		break;
552 	  case KNIFE:
553 		cp = "Stabbed to death";
554 		victim->p_ammo = 0;		/* No exploding */
555 		break;
556 	  case SHOT:
557 		cp = "Shot to death";
558 		break;
559 	  case GRENADE:
560 	  case SATCHEL:
561 	  case BOMB:
562 		cp = "Bombed";
563 		break;
564 	  case MINE:
565 	  case GMINE:
566 		cp = "Blown apart";
567 		break;
568 	  case SLIME:
569 		cp = "Slimed";
570 		if (credit != NULL)
571 			credit->i_slime++;
572 		break;
573 	  case LAVA:
574 		cp = "Baked";
575 		break;
576 	  case DSHOT:
577 		cp = "Eliminated";
578 		break;
579 	}
580 
581 	if (credit == NULL) {
582 		char *blame;
583 
584 		/*
585 		 * Nobody is taking the credit for the kill.
586 		 * Attribute it to either a mine or 'act of God'.
587 		 */
588 		switch (shot_type) {
589 		case MINE:
590 		case GMINE:
591 			blame = "a mine";
592 			break;
593 		default:
594 			blame = "act of God";
595 			break;
596 		}
597 
598 		/* Set the death message: */
599 		(void) snprintf(victim->p_death, sizeof victim->p_death,
600 			"| %s by %s |", cp, blame);
601 
602 		/* No further score crediting needed. */
603 		return;
604 	}
605 
606 	/* Set the death message: */
607 	(void) snprintf(victim->p_death, sizeof victim->p_death,
608 		"| %s by %s |", cp, credit->i_name);
609 
610 	if (victim == attacker) {
611 		/* No use killing yourself. */
612 		credit->i_kills--;
613 		credit->i_bkills++;
614 	}
615 	else if (victim->p_ident->i_team == ' '
616 	    || victim->p_ident->i_team != credit->i_team) {
617 		/* A cross-team kill: */
618 		credit->i_kills++;
619 		credit->i_gkills++;
620 	}
621 	else {
622 		/* They killed someone on the same team: */
623 		credit->i_kills--;
624 		credit->i_bkills++;
625 	}
626 
627 	/* Compute the new credited score: */
628 	credit->i_score = credit->i_kills / (double) credit->i_entries;
629 
630 	/* The victim accrues one death: */
631 	victim->p_ident->i_deaths++;
632 
633 	/* Account for 'Stillborn' deaths */
634 	if (victim->p_nchar == 0)
635 		victim->p_ident->i_stillb++;
636 
637 	if (attacker) {
638 		/* Give the attacker player a bit more strength */
639 		attacker->p_damcap += conf_killgain;
640 		attacker->p_damage -= conf_killgain;
641 		if (attacker->p_damage < 0)
642 			attacker->p_damage = 0;
643 
644 		/* Tell the attacker his new strength: */
645 		outyx(attacker, STAT_DAM_ROW, STAT_VALUE_COL, "%2d/%2d",
646 			attacker->p_damage, attacker->p_damcap);
647 
648 		/* Tell the attacker his new 'kill count': */
649 		outyx(attacker, STAT_KILL_ROW, STAT_VALUE_COL, "%3d",
650 			(attacker->p_damcap - conf_maxdam) / 2);
651 
652 		/* Update the attacker's score for everyone else */
653 		y = STAT_PLAY_ROW + 1 + (attacker - Player);
654 		outyx(ALL_PLAYERS, y, STAT_NAME_COL,
655 			"%5.2f", attacker->p_ident->i_score);
656 	}
657 }
658 
659 /*
660  * zap:
661  *	Kill off a player and take them out of the game.
662  *	The 'was_player' flag indicates that the player was not
663  *	a monitor and needs extra cleaning up.
664  */
665 static void
666 zap(PLAYER *pp, FLAG was_player)
667 {
668 	int	len;
669 	BULLET	*bp;
670 	PLAYER	*np;
671 	int	x, y;
672 	int	savefd;
673 
674 	if (was_player) {
675 		/* If they died from a shot, clean up shrapnel */
676 		if (pp->p_undershot)
677 			fixshots(pp->p_y, pp->p_x, pp->p_over);
678 		/* Let the player see their last position: */
679 		drawplayer(pp, FALSE);
680 		/* Remove from game: */
681 		Nplayer--;
682 	}
683 
684 	/* Display the cause of death in the centre of the screen: */
685 	len = strlen(pp->p_death);
686 	x = (WIDTH - len) / 2;
687 	outyx(pp, HEIGHT / 2, x, "%s", pp->p_death);
688 
689 	/* Put some horizontal lines around and below the death message: */
690 	memset(pp->p_death + 1, '-', len - 2);
691 	pp->p_death[0] = '+';
692 	pp->p_death[len - 1] = '+';
693 	outyx(pp, HEIGHT / 2 - 1, x, "%s", pp->p_death);
694 	outyx(pp, HEIGHT / 2 + 1, x, "%s", pp->p_death);
695 
696 	/* Move to bottom left */
697 	cgoto(pp, HEIGHT, 0);
698 
699 	savefd = pp->p_fd;
700 
701 	if (was_player) {
702 		int	expl_charge;
703 		int	expl_type;
704 		int	ammo_exploding;
705 
706 		/* Check all the bullets: */
707 		for (bp = Bullets; bp != NULL; bp = bp->b_next) {
708 			if (bp->b_owner == pp)
709 				/* Zapped players can't own bullets: */
710 				bp->b_owner = NULL;
711 			if (bp->b_x == pp->p_x && bp->b_y == pp->p_y)
712 				/* Bullets over the player are now over air: */
713 				bp->b_over = SPACE;
714 		}
715 
716 		/* Explode a random fraction of the player's ammo: */
717 		ammo_exploding = rand_num(pp->p_ammo);
718 
719 		/* Determine the type and amount of detonation: */
720 		expl_charge = rand_num(ammo_exploding + 1);
721 		if (pp->p_ammo == 0)
722 			/* Ignore the no-ammo case: */
723 			expl_charge = 0;
724 		else if (ammo_exploding >= pp->p_ammo - 1) {
725 			/* Maximal explosions always appear as slime: */
726 			expl_charge = pp->p_ammo;
727 			expl_type = SLIME;
728 		} else {
729 			/*
730 			 * Figure out the best effective explosion
731 			 * type to use, given the amount of charge
732 			 */
733 			int btype, stype;
734 			for (btype = MAXBOMB - 1; btype > 0; btype--)
735 				if (expl_charge >= shot_req[btype])
736 					break;
737 			for (stype = MAXSLIME - 1; stype > 0; stype--)
738 				if (expl_charge >= slime_req[stype])
739 					break;
740 			/* Pick the larger of the bomb or slime: */
741 			if (btype >= 0 && stype >= 0) {
742 				if (shot_req[btype] > slime_req[stype])
743 					btype = -1;
744 			}
745 			if (btype >= 0)  {
746 				expl_type = shot_type[btype];
747 				expl_charge = shot_req[btype];
748 			} else
749 				expl_type = SLIME;
750 		}
751 
752 		if (expl_charge > 0) {
753 			char buf[BUFSIZ];
754 
755 			/* Detonate: */
756 			(void) add_shot(expl_type, pp->p_y, pp->p_x,
757 			    pp->p_face, expl_charge, (PLAYER *) NULL,
758 			    TRUE, SPACE);
759 
760 			/* Explain what the explosion is about. */
761 			snprintf(buf, sizeof buf, "%s detonated.",
762 				pp->p_ident->i_name);
763 			message(ALL_PLAYERS, buf);
764 
765 			while (pp->p_nboots-- > 0) {
766 				/* Throw one of the boots away: */
767 				for (np = Boot; np < &Boot[NBOOTS]; np++)
768 					if (np->p_flying < 0)
769 						break;
770 #ifdef DIAGNOSTIC
771 				if (np >= &Boot[NBOOTS])
772 					err(1, "Too many boots");
773 #endif
774 				/* Start the boots from where the player is */
775 				np->p_undershot = FALSE;
776 				np->p_x = pp->p_x;
777 				np->p_y = pp->p_y;
778 				/* Throw for up to 20 steps */
779 				np->p_flying = rand_num(20);
780 				np->p_flyx = 2 * rand_num(6) - 5;
781 				np->p_flyy = 2 * rand_num(6) - 5;
782 				np->p_over = SPACE;
783 				np->p_face = BOOT;
784 				showexpl(np->p_y, np->p_x, BOOT);
785 			}
786 		}
787 		/* No explosion. Leave the player's boots behind. */
788 		else if (pp->p_nboots > 0) {
789 			if (pp->p_nboots == 2)
790 				Maze[pp->p_y][pp->p_x] = BOOT_PAIR;
791 			else
792 				Maze[pp->p_y][pp->p_x] = BOOT;
793 			if (pp->p_undershot)
794 				fixshots(pp->p_y, pp->p_x,
795 					Maze[pp->p_y][pp->p_x]);
796 		}
797 
798 		/* Any unexploded ammo builds up in the volcano: */
799 		volcano += pp->p_ammo - expl_charge;
800 
801 		/* Volcano eruption: */
802 		if (conf_volcano && rand_num(100) < volcano /
803 		    conf_volcano_max) {
804 			/* Erupt near the middle of the map */
805 			do {
806 				x = rand_num(WIDTH / 2) + WIDTH / 4;
807 				y = rand_num(HEIGHT / 2) + HEIGHT / 4;
808 			} while (Maze[y][x] != SPACE);
809 
810 			/* Convert volcano charge into lava: */
811 			(void) add_shot(LAVA, y, x, LEFTS, volcano,
812 				(PLAYER *) NULL, TRUE, SPACE);
813 			volcano = 0;
814 
815 			/* Tell eveyone what's happening */
816 			message(ALL_PLAYERS, "Volcano eruption.");
817 		}
818 
819 		/* Drone: */
820 		if (conf_drone && rand_num(100) < 2) {
821 			/* Find a starting place near the middle of the map: */
822 			do {
823 				x = rand_num(WIDTH / 2) + WIDTH / 4;
824 				y = rand_num(HEIGHT / 2) + HEIGHT / 4;
825 			} while (Maze[y][x] != SPACE);
826 
827 			/* Start the drone going: */
828 			add_shot(DSHOT, y, x, rand_dir(),
829 				shot_req[conf_mindshot +
830 				rand_num(MAXBOMB - conf_mindshot)],
831 				(PLAYER *) NULL, FALSE, SPACE);
832 		}
833 
834 		/* Tell the zapped player's client to shut down. */
835 		sendcom(pp, ENDWIN, ' ');
836 		(void) fclose(pp->p_output);
837 
838 		/* Close up the gap in the Player array: */
839 		End_player--;
840 		if (pp != End_player) {
841 			/* Move the last player into the gap: */
842 			memcpy(pp, End_player, sizeof *pp);
843 			outyx(ALL_PLAYERS,
844 				STAT_PLAY_ROW + 1 + (pp - Player),
845 				STAT_NAME_COL,
846 				"%5.2f%c%-10.10s %c",
847 				pp->p_ident->i_score, stat_char(pp),
848 				pp->p_ident->i_name, pp->p_ident->i_team);
849 		}
850 
851 		/* Erase the last player from the display: */
852 		cgoto(ALL_PLAYERS, STAT_PLAY_ROW + 1 + Nplayer, STAT_NAME_COL);
853 		ce(ALL_PLAYERS);
854 	}
855 	else {
856 		/* Zap a monitor */
857 
858 		/* Close the session: */
859 		sendcom(pp, ENDWIN, LAST_PLAYER);
860 		(void) fclose(pp->p_output);
861 
862 		/* shuffle the monitor table */
863 		End_monitor--;
864 		if (pp != End_monitor) {
865 			memcpy(pp, End_monitor, sizeof *pp);
866 			outyx(ALL_PLAYERS,
867 				STAT_MON_ROW + 1 + (pp - Player), STAT_NAME_COL,
868 				"%5.5s %-10.10s %c", " ",
869 				pp->p_ident->i_name, pp->p_ident->i_team);
870 		}
871 
872 		/* Erase the last monitor in the list */
873 		cgoto(ALL_PLAYERS,
874 			STAT_MON_ROW + 1 + (End_monitor - Monitor),
875 			STAT_NAME_COL);
876 		ce(ALL_PLAYERS);
877 	}
878 
879 	/* Update the file descriptor sets used by select: */
880 	FD_CLR(savefd, &Fds_mask);
881 	if (Num_fds == savefd + 1) {
882 		Num_fds = Socket;
883 		if (Server_socket > Socket)
884 			Num_fds = Server_socket;
885 		for (np = Player; np < End_player; np++)
886 			if (np->p_fd > Num_fds)
887 				Num_fds = np->p_fd;
888 		for (np = Monitor; np < End_monitor; np++)
889 			if (np->p_fd > Num_fds)
890 				Num_fds = np->p_fd;
891 		Num_fds++;
892 	}
893 }
894 
895 /*
896  * rand_num:
897  *	Return a random number in a given range.
898  */
899 int
900 rand_num(int range)
901 {
902 	return (arc4random_uniform(range));
903 }
904 
905 /*
906  * havechar:
907  *	Check to see if we have any characters in the input queue; if
908  *	we do, read them, stash them away, and return TRUE; else return
909  *	FALSE.
910  */
911 static int
912 havechar(PLAYER *pp)
913 {
914 	int ret;
915 
916 	/* Do we already have characters? */
917 	if (pp->p_ncount < pp->p_nchar)
918 		return TRUE;
919 	/* Ignore if nothing to read. */
920 	if (!FD_ISSET(pp->p_fd, &Have_inp))
921 		return FALSE;
922 	/* Remove the player from the read set until we have drained them: */
923 	FD_CLR(pp->p_fd, &Have_inp);
924 
925 	/* Suck their keypresses into a buffer: */
926 check_again:
927 	errno = 0;
928 	ret = read(pp->p_fd, pp->p_cbuf, sizeof pp->p_cbuf);
929 	if (ret == -1) {
930 		if (errno == EINTR)
931 			goto check_again;
932 		if (errno == EAGAIN) {
933 #ifdef DEBUG
934 			warn("Have_inp is wrong for %d", pp->p_fd);
935 #endif
936 			return FALSE;
937 		}
938 		logit(LOG_INFO, "read");
939 	}
940 	if (ret > 0) {
941 		/* Got some data */
942 		pp->p_nchar = ret;
943 	} else {
944 		/* Connection was lost/closed: */
945 		pp->p_cbuf[0] = 'q';
946 		pp->p_nchar = 1;
947 	}
948 	/* Reset pointer into read buffer */
949 	pp->p_ncount = 0;
950 	return TRUE;
951 }
952 
953 /*
954  * cleanup:
955  *	Exit with the given value, cleaning up any droppings lying around
956  */
957 void
958 cleanup(int eval)
959 {
960 	PLAYER	*pp;
961 
962 	/* Place their cursor in a friendly position: */
963 	cgoto(ALL_PLAYERS, HEIGHT, 0);
964 
965 	/* Send them all the ENDWIN command: */
966 	sendcom(ALL_PLAYERS, ENDWIN, LAST_PLAYER);
967 
968 	/* And close their connections: */
969 	for (pp = Player; pp < End_player; pp++)
970 		(void) fclose(pp->p_output);
971 	for (pp = Monitor; pp < End_monitor; pp++)
972 		(void) fclose(pp->p_output);
973 
974 	/* Close the server socket: */
975 	(void) close(Socket);
976 
977 	/* The end: */
978 	logx(LOG_INFO, "game over");
979 	exit(eval);
980 }
981 
982 /*
983  * send_stats:
984  *	Accept a connection to the statistics port, and emit
985  *	the stats.
986  */
987 static void
988 send_stats(void)
989 {
990 	FILE	*fp;
991 	int	s;
992 	struct sockaddr_in	sockstruct;
993 	socklen_t	socklen;
994 
995 	/* Accept a connection to the statistics socket: */
996 	socklen = sizeof sockstruct;
997 	s = accept4(Status, (struct sockaddr *) &sockstruct, &socklen,
998 	    SOCK_NONBLOCK);
999 	if (s < 0) {
1000 		if (errno == EINTR)
1001 			return;
1002 		logx(LOG_ERR, "accept");
1003 		return;
1004 	}
1005 
1006 	fp = fdopen(s, "w");
1007 	if (fp == NULL) {
1008 		logit(LOG_ERR, "fdopen");
1009 		(void) close(s);
1010 		return;
1011 	}
1012 
1013 	print_stats(fp);
1014 
1015 	(void) fclose(fp);
1016 }
1017 
1018 /*
1019  * print_stats:
1020  *	emit the game statistics
1021  */
1022 void
1023 print_stats(FILE *fp)
1024 {
1025 	IDENT	*ip;
1026 	PLAYER  *pp;
1027 
1028 	/* Send the statistics as raw text down the socket: */
1029 	fputs("Name\t\tScore\tDucked\tAbsorb\tFaced\tShot\tRobbed\tMissed\tSlimeK\n", fp);
1030 	for (ip = Scores; ip != NULL; ip = ip->i_next) {
1031 		fprintf(fp, "%s%c%c%c\t", ip->i_name,
1032 			ip->i_team == ' ' ? ' ' : '[',
1033 			ip->i_team,
1034 			ip->i_team == ' ' ? ' ' : ']'
1035 		);
1036 		if (strlen(ip->i_name) + 3 < 8)
1037 			putc('\t', fp);
1038 		fprintf(fp, "%.2f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
1039 			ip->i_score, ip->i_ducked, ip->i_absorbed,
1040 			ip->i_faced, ip->i_shot, ip->i_robbed,
1041 			ip->i_missed, ip->i_slime);
1042 	}
1043 	fputs("\n\nName\t\tEnemy\tFriend\tDeaths\tStill\tSaved\tConnect\n", fp);
1044 	for (ip = Scores; ip != NULL; ip = ip->i_next) {
1045 		fprintf(fp, "%s%c%c%c\t", ip->i_name,
1046 			ip->i_team == ' ' ? ' ' : '[',
1047 			ip->i_team,
1048 			ip->i_team == ' ' ? ' ' : ']'
1049 		);
1050 		if (strlen(ip->i_name) + 3 < 8)
1051 			putc('\t', fp);
1052 		fprintf(fp, "%d\t%d\t%d\t%d\t%d\t",
1053 			ip->i_gkills, ip->i_bkills, ip->i_deaths,
1054 			ip->i_stillb, ip->i_saved);
1055 		for (pp = Player; pp < End_player; pp++)
1056 			if (pp->p_ident == ip)
1057 				putc('p', fp);
1058 		for (pp = Monitor; pp < End_monitor; pp++)
1059 			if (pp->p_ident == ip)
1060 				putc('m', fp);
1061 		putc('\n', fp);
1062 	}
1063 }
1064 
1065 
1066 /*
1067  * Send the game statistics to the controlling tty
1068  */
1069 static void
1070 siginfo(int sig)
1071 {
1072 	int tty;
1073 	FILE *fp;
1074 
1075 	if ((tty = open(_PATH_TTY, O_WRONLY)) >= 0) {
1076 		fp = fdopen(tty, "w");
1077 		print_stats(fp);
1078 		answer_info(fp);
1079 		fclose(fp);
1080 	}
1081 }
1082 
1083 /*
1084  * clear_scores:
1085  *	Clear the Scores list.
1086  */
1087 static void
1088 clear_scores(void)
1089 {
1090 	IDENT	*ip, *nextip;
1091 
1092 	/* Release the list of scores: */
1093 	for (ip = Scores; ip != NULL; ip = nextip) {
1094 		nextip = ip->i_next;
1095 		free((char *) ip);
1096 	}
1097 	Scores = NULL;
1098 }
1099 
1100 /*
1101  * announce_game:
1102  *	Publically announce the game
1103  */
1104 static void
1105 announce_game(void)
1106 {
1107 
1108 	/* TODO: could use system() to do something user-configurable */
1109 }
1110 
1111 /*
1112  * Handle a UDP packet sent to the well known port.
1113  */
1114 static void
1115 handle_wkport(int fd)
1116 {
1117 	struct sockaddr		fromaddr;
1118 	socklen_t		fromlen;
1119 	u_int16_t		query;
1120 	u_int16_t		response;
1121 
1122 	fromlen = sizeof fromaddr;
1123 	if (recvfrom(fd, &query, sizeof query, 0, &fromaddr, &fromlen) == -1)
1124 	{
1125 		logit(LOG_WARNING, "recvfrom");
1126 		return;
1127 	}
1128 
1129 #ifdef DEBUG
1130 	fprintf(stderr, "query %d (%s) from %s:%d\n", query,
1131 		query == C_MESSAGE ? "C_MESSAGE" :
1132 		query == C_SCORES ? "C_SCORES" :
1133 		query == C_PLAYER ? "C_PLAYER" :
1134 		query == C_MONITOR ? "C_MONITOR" : "?",
1135 		inet_ntoa(((struct sockaddr_in *)&fromaddr)->sin_addr),
1136 		ntohs(((struct sockaddr_in *)&fromaddr)->sin_port));
1137 #endif
1138 
1139 	query = ntohs(query);
1140 
1141 	switch (query) {
1142 	  case C_MESSAGE:
1143 		if (Nplayer <= 0)
1144 			/* Don't bother replying if nobody to talk to: */
1145 			return;
1146 		/* Return the number of people playing: */
1147 		response = Nplayer;
1148 		break;
1149 	  case C_SCORES:
1150 		/* Someone wants the statistics port: */
1151 		response = stat_port;
1152 		break;
1153 	  case C_PLAYER:
1154 	  case C_MONITOR:
1155 		/* Someone wants to play or watch: */
1156 		if (query == C_MONITOR && Nplayer <= 0)
1157 			/* Don't bother replying if there's nothing to watch: */
1158 			return;
1159 		/* Otherwise, tell them how to get to the game: */
1160 		response = sock_port;
1161 		break;
1162 	  default:
1163 		logit(LOG_INFO, "unknown udp query %d", query);
1164 		return;
1165 	}
1166 
1167 	response = ntohs(response);
1168 	if (sendto(fd, &response, sizeof response, 0,
1169 	    &fromaddr, sizeof fromaddr) == -1)
1170 		logit(LOG_WARNING, "sendto");
1171 }
1172