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