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