driver.c revision 1.22
1/*	$OpenBSD: driver.c,v 1.22 2015/09/25 17:50:53 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/ioctl.h>
35#include <sys/stat.h>
36#include <sys/time.h>
37#include <err.h>
38#include <errno.h>
39#include <signal.h>
40#include <stdlib.h>
41#include <string.h>
42#include <unistd.h>
43#include <stdio.h>
44#include <syslog.h>
45#include <netdb.h>
46#include <sys/socket.h>
47#include <netinet/in.h>
48#include <arpa/inet.h>
49#include <paths.h>
50#include <fcntl.h>
51#include "hunt.h"
52#include "conf.h"
53#include "server.h"
54
55char	*First_arg;		/* pointer to argv[0] */
56u_int16_t Server_port;
57int	Server_socket;		/* test socket to answer datagrams */
58FLAG	should_announce = TRUE;	/* true if listening on standard port */
59u_short	sock_port;		/* port # of tcp listen socket */
60u_short	stat_port;		/* port # of statistics tcp socket */
61in_addr_t Server_addr = INADDR_ANY;	/* address to bind to */
62
63static	void	clear_scores(void);
64static	int	havechar(PLAYER *);
65static	void	init(int);
66	int	main(int, char *[]);
67static	void	makeboots(void);
68static	void	send_stats(void);
69static	void	zap(PLAYER *, FLAG);
70static  void	announce_game(void);
71static	void	siginfo(int);
72static	void	print_stats(FILE *);
73static	void	handle_wkport(int);
74
75/*
76 * main:
77 *	The main program.
78 */
79int
80main(ac, av)
81	int	ac;
82	char	**av;
83{
84	PLAYER		*pp;
85	int		had_char;
86	static fd_set	read_fds;
87	static FLAG	first = TRUE;
88	static FLAG	server = FALSE;
89	extern int	optind;
90	extern char	*optarg;
91	int		c;
92	static struct timeval	linger = { 0, 0 };
93	static struct timeval	timeout = { 0, 0 }, *to;
94	struct spawn	*sp, *spnext;
95	int		ret;
96	int		nready;
97	int		fd;
98	int		background = 0;
99
100	First_arg = av[0];
101
102	config();
103
104	while ((c = getopt(ac, av, "bsp:a:D:")) != -1) {
105		switch (c) {
106		  case 'b':
107			background = 1;
108			conf_syslog = 1;
109			conf_logerr = 0;
110			break;
111		  case 's':
112			server = TRUE;
113			break;
114		  case 'p':
115			should_announce = FALSE;
116			Server_port = atoi(optarg);
117			break;
118		  case 'a':
119			if (!inet_aton(optarg, (struct in_addr *)&Server_addr))
120				err(1, "bad interface address: %s", optarg);
121			break;
122		  case 'D':
123			config_arg(optarg);
124			break;
125		  default:
126erred:
127			fprintf(stderr,
128			    "usage: %s [-bs] [-a addr] [-D var=value] "
129			    "[-p port]\n",
130			    av[0]);
131			exit(2);
132		}
133	}
134	if (optind < ac)
135		goto erred;
136
137	/* Open syslog: */
138	openlog("huntd", LOG_PID | (conf_logerr && !server? LOG_PERROR : 0),
139		LOG_DAEMON);
140
141	/* Initialise game parameters: */
142	init(background);
143
144again:
145	do {
146		/* First, poll to see if we can get input */
147		do {
148			read_fds = Fds_mask;
149			errno = 0;
150			timerclear(&timeout);
151			nready = select(Num_fds, &read_fds, NULL, NULL,
152			    &timeout);
153			if (nready < 0 && errno != EINTR) {
154				logit(LOG_ERR, "select");
155				cleanup(1);
156			}
157		} while (nready < 0);
158
159		if (nready == 0) {
160			/*
161			 * Nothing was ready. We do some work now
162			 * to see if the simulation has any pending work
163			 * to do, and decide if we need to block
164			 * indefinitely or just timeout.
165			 */
166			do {
167				if (conf_simstep && can_moveshots()) {
168				/*
169				 * block for a short time before continuing
170				 * with explosions, bullets and whatnot
171				 */
172					to = &timeout;
173					to->tv_sec =  conf_simstep / 1000000;
174					to->tv_usec = conf_simstep % 1000000;
175				} else
176				/*
177				 * since there's nothing going on,
178				 * just block waiting for external activity
179				 */
180					to = NULL;
181
182				read_fds = Fds_mask;
183				errno = 0;
184				nready = select(Num_fds, &read_fds, NULL, NULL,
185				    to);
186				if (nready < 0 && errno != EINTR) {
187					logit(LOG_ERR, "select");
188					cleanup(1);
189				}
190			} while (nready < 0);
191		}
192
193		/* Remember which descriptors are active: */
194		Have_inp = read_fds;
195
196		/* Answer new player connections: */
197		if (FD_ISSET(Socket, &Have_inp))
198			answer_first();
199
200		/* Continue answering new player connections: */
201		for (sp = Spawn; sp; ) {
202			spnext = sp->next;
203			fd = sp->fd;
204			if (FD_ISSET(fd, &Have_inp) && answer_next(sp)) {
205				/*
206				 * Remove from the spawn list. (fd remains in
207				 * read set).
208				 */
209				*sp->prevnext = sp->next;
210				if (sp->next)
211					sp->next->prevnext = sp->prevnext;
212				free(sp);
213
214				/* We probably consumed all data. */
215				FD_CLR(fd, &Have_inp);
216
217				/* Announce game if this is the first spawn. */
218				if (first && should_announce)
219					announce_game();
220				first = FALSE;
221			}
222			sp = spnext;
223		}
224
225		/* Process input and move bullets until we've exhausted input */
226		had_char = TRUE;
227		while (had_char) {
228
229			moveshots();
230			for (pp = Player; pp < End_player; )
231				if (pp->p_death[0] != '\0')
232					zap(pp, TRUE);
233				else
234					pp++;
235			for (pp = Monitor; pp < End_monitor; )
236				if (pp->p_death[0] != '\0')
237					zap(pp, FALSE);
238				else
239					pp++;
240
241			had_char = FALSE;
242			for (pp = Player; pp < End_player; pp++)
243				if (havechar(pp)) {
244					execute(pp);
245					pp->p_nexec++;
246					had_char = TRUE;
247				}
248			for (pp = Monitor; pp < End_monitor; pp++)
249				if (havechar(pp)) {
250					mon_execute(pp);
251					pp->p_nexec++;
252					had_char = TRUE;
253				}
254		}
255
256		/* Handle a datagram sent to the server socket: */
257		if (FD_ISSET(Server_socket, &Have_inp))
258			handle_wkport(Server_socket);
259
260		/* Answer statistics connections: */
261		if (FD_ISSET(Status, &Have_inp))
262			send_stats();
263
264		/* Flush/synchronize all the displays: */
265		for (pp = Player; pp < End_player; 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		for (pp = Monitor; pp < End_monitor; pp++) {
273			if (FD_ISSET(pp->p_fd, &read_fds)) {
274				sendcom(pp, READY, pp->p_nexec);
275				pp->p_nexec = 0;
276			}
277			flush(pp);
278		}
279	} while (Nplayer > 0);
280
281	/* No more players! */
282
283	/* No players yet or a continuous game? */
284	if (first || conf_linger < 0)
285		goto again;
286
287	/* Wait a short while for one to come back: */
288	read_fds = Fds_mask;
289	linger.tv_sec = conf_linger;
290	while ((ret = select(Num_fds, &read_fds, NULL, NULL, &linger)) < 0) {
291		if (errno != EINTR) {
292			logit(LOG_WARNING, "select");
293			break;
294		}
295		read_fds = Fds_mask;
296		linger.tv_sec = conf_linger;
297		linger.tv_usec = 0;
298	}
299	if (ret > 0)
300		/* Someone returned! Resume the game: */
301		goto again;
302	/* else, it timed out, and the game is really over. */
303
304	/* If we are an inetd server, we should re-init the map and restart: */
305	if (server) {
306		clear_scores();
307		makemaze();
308		clearwalls();
309		makeboots();
310		first = TRUE;
311		goto again;
312	}
313
314	/* Get rid of any attached monitors: */
315	for (pp = Monitor; pp < End_monitor; )
316		zap(pp, FALSE);
317
318	/* Fin: */
319	cleanup(0);
320	exit(0);
321}
322
323/*
324 * init:
325 *	Initialize the global parameters.
326 */
327static void
328init(int background)
329{
330	int	i;
331	struct sockaddr_in	test_port;
332	int	true = 1;
333	socklen_t	len;
334	struct sockaddr_in	addr;
335	struct sigaction	sact;
336	struct servent *se;
337
338	sact.sa_flags = SA_RESTART;
339	sigemptyset(&sact.sa_mask);
340
341	/* Ignore HUP, QUIT and PIPE: */
342	sact.sa_handler = SIG_IGN;
343	if (sigaction(SIGHUP, &sact, NULL) == -1)
344		err(1, "sigaction SIGHUP");
345	if (sigaction(SIGQUIT, &sact, NULL) == -1)
346		err(1, "sigaction SIGQUIT");
347	if (sigaction(SIGPIPE, &sact, NULL) == -1)
348		err(1, "sigaction SIGPIPE");
349
350	/* Clean up gracefully on INT and TERM: */
351	sact.sa_handler = cleanup;
352	if (sigaction(SIGINT, &sact, NULL) == -1)
353		err(1, "sigaction SIGINT");
354	if (sigaction(SIGTERM, &sact, NULL) == -1)
355		err(1, "sigaction SIGTERM");
356
357	/* Handle INFO: */
358	sact.sa_handler = siginfo;
359	if (sigaction(SIGINFO, &sact, NULL) == -1)
360		err(1, "sigaction SIGINFO");
361
362	if (chdir("/") == -1)
363		warn("chdir");
364	(void) umask(0777);
365
366	/* Initialize statistics socket: */
367	addr.sin_family = AF_INET;
368	addr.sin_addr.s_addr = Server_addr;
369	addr.sin_port = 0;
370
371	Status = socket(AF_INET, SOCK_STREAM, 0);
372	if (bind(Status, (struct sockaddr *) &addr, sizeof addr) < 0) {
373		logit(LOG_ERR, "bind");
374		cleanup(1);
375	}
376	if (listen(Status, 5) == -1) {
377		logit(LOG_ERR, "listen");
378		cleanup(1);
379	}
380
381	len = sizeof (struct sockaddr_in);
382	if (getsockname(Status, (struct sockaddr *) &addr, &len) < 0)  {
383		logit(LOG_ERR, "getsockname");
384		cleanup(1);
385	}
386	stat_port = ntohs(addr.sin_port);
387
388	/* Initialize main socket: */
389	addr.sin_family = AF_INET;
390	addr.sin_addr.s_addr = Server_addr;
391	addr.sin_port = 0;
392
393	Socket = socket(AF_INET, SOCK_STREAM, 0);
394
395	if (bind(Socket, (struct sockaddr *) &addr, sizeof addr) < 0) {
396		logit(LOG_ERR, "bind");
397		cleanup(1);
398	}
399	if (listen(Socket, 5) == -1) {
400		logit(LOG_ERR, "listen");
401		cleanup(1);
402	}
403
404	len = sizeof (struct sockaddr_in);
405	if (getsockname(Socket, (struct sockaddr *) &addr, &len) < 0)  {
406		logit(LOG_ERR, "getsockname");
407		cleanup(1);
408	}
409	sock_port = ntohs(addr.sin_port);
410
411	/* Initialize minimal select mask */
412	FD_ZERO(&Fds_mask);
413	FD_SET(Socket, &Fds_mask);
414	FD_SET(Status, &Fds_mask);
415	Num_fds = ((Socket > Status) ? Socket : Status) + 1;
416
417	/* Find the port that huntd should run on */
418	if (Server_port == 0) {
419		se = getservbyname("hunt", "udp");
420		if (se != NULL)
421			Server_port = ntohs(se->s_port);
422		else
423			Server_port = HUNT_PORT;
424	}
425
426	/* Check if stdin is a socket: */
427	len = sizeof (struct sockaddr_in);
428	if (getsockname(STDIN_FILENO, (struct sockaddr *) &test_port, &len) >= 0
429	    && test_port.sin_family == AF_INET) {
430		/* We are probably running from inetd:  don't log to stderr */
431		Server_socket = STDIN_FILENO;
432		conf_logerr = 0;
433		if (test_port.sin_port != htons((u_short) Server_port)) {
434			/* Private game */
435			should_announce = FALSE;
436			Server_port = ntohs(test_port.sin_port);
437		}
438	} else {
439		/* We need to listen on a socket: */
440		test_port = addr;
441		test_port.sin_port = htons((u_short) Server_port);
442
443		Server_socket = socket(AF_INET, SOCK_DGRAM, 0);
444
445		/* Permit multiple huntd's on the same port. */
446		if (setsockopt(Server_socket, SOL_SOCKET, SO_REUSEPORT, &true,
447		    sizeof true) < 0)
448			logit(LOG_ERR, "setsockopt SO_REUSEADDR");
449
450		if (bind(Server_socket, (struct sockaddr *) &test_port,
451		    sizeof test_port) < 0) {
452			logit(LOG_ERR, "bind port %d", Server_port);
453			cleanup(1);
454		}
455
456		/* Become a daemon if asked to do so. */
457		if (background)
458			daemon(0, 0);
459
460		/* Datagram sockets do not need a listen() call. */
461	}
462
463	/* We'll handle the broadcast listener in the main loop: */
464	FD_SET(Server_socket, &Fds_mask);
465	if (Server_socket + 1 > Num_fds)
466		Num_fds = Server_socket + 1;
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 */
491static void
492makeboots()
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 */
515void
516checkdam(victim, attacker, credit, damage, shot_type)
517	PLAYER	*victim, *attacker;
518	IDENT	*credit;
519	int	damage;
520	char	shot_type;
521{
522	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 (shot_type == 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 (shot_type) {
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		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 (shot_type) {
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 */
675static void
676zap(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[stype])
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 */
911int
912rand_num(range)
913	int	range;
914{
915	return (arc4random_uniform(range));
916}
917
918/*
919 * havechar:
920 *	Check to see if we have any characters in the input queue; if
921 *	we do, read them, stash them away, and return TRUE; else return
922 *	FALSE.
923 */
924static int
925havechar(pp)
926	PLAYER	*pp;
927{
928	int ret;
929
930	/* Do we already have characters? */
931	if (pp->p_ncount < pp->p_nchar)
932		return TRUE;
933	/* Ignore if nothing to read. */
934	if (!FD_ISSET(pp->p_fd, &Have_inp))
935		return FALSE;
936	/* Remove the player from the read set until we have drained them: */
937	FD_CLR(pp->p_fd, &Have_inp);
938
939	/* Suck their keypresses into a buffer: */
940check_again:
941	errno = 0;
942	ret = read(pp->p_fd, pp->p_cbuf, sizeof pp->p_cbuf);
943	if (ret == -1) {
944		if (errno == EINTR)
945			goto check_again;
946		if (errno == EAGAIN) {
947#ifdef DEBUG
948			warn("Have_inp is wrong for %d", pp->p_fd);
949#endif
950			return FALSE;
951		}
952		logit(LOG_INFO, "read");
953	}
954	if (ret > 0) {
955		/* Got some data */
956		pp->p_nchar = ret;
957	} else {
958		/* Connection was lost/closed: */
959		pp->p_cbuf[0] = 'q';
960		pp->p_nchar = 1;
961	}
962	/* Reset pointer into read buffer */
963	pp->p_ncount = 0;
964	return TRUE;
965}
966
967/*
968 * cleanup:
969 *	Exit with the given value, cleaning up any droppings lying around
970 */
971void
972cleanup(eval)
973	int	eval;
974{
975	PLAYER	*pp;
976
977	/* Place their cursor in a friendly position: */
978	cgoto(ALL_PLAYERS, HEIGHT, 0);
979
980	/* Send them all the ENDWIN command: */
981	sendcom(ALL_PLAYERS, ENDWIN, LAST_PLAYER);
982
983	/* And close their connections: */
984	for (pp = Player; pp < End_player; pp++)
985		(void) fclose(pp->p_output);
986	for (pp = Monitor; pp < End_monitor; pp++)
987		(void) fclose(pp->p_output);
988
989	/* Close the server socket: */
990	(void) close(Socket);
991
992	/* The end: */
993	logx(LOG_INFO, "game over");
994	exit(eval);
995}
996
997/*
998 * send_stats:
999 *	Accept a connection to the statistics port, and emit
1000 *	the stats.
1001 */
1002static void
1003send_stats()
1004{
1005	FILE	*fp;
1006	int	s;
1007	struct sockaddr_in	sockstruct;
1008	socklen_t	socklen;
1009	int	flags;
1010
1011	/* Accept a connection to the statistics socket: */
1012	socklen = sizeof sockstruct;
1013	s = accept(Status, (struct sockaddr *) &sockstruct, &socklen);
1014	if (s < 0) {
1015		if (errno == EINTR)
1016			return;
1017		logx(LOG_ERR, "accept");
1018		return;
1019	}
1020
1021	/* Don't allow the writes to block: */
1022	flags = fcntl(s, F_GETFL, 0);
1023	flags |= O_NDELAY;
1024	(void) fcntl(s, F_SETFL, flags);
1025
1026	fp = fdopen(s, "w");
1027	if (fp == NULL) {
1028		logit(LOG_ERR, "fdopen");
1029		(void) close(s);
1030		return;
1031	}
1032
1033	print_stats(fp);
1034
1035	(void) fclose(fp);
1036}
1037
1038/*
1039 * print_stats:
1040 * 	emit the game statistics
1041 */
1042void
1043print_stats(fp)
1044	FILE *fp;
1045{
1046	IDENT	*ip;
1047	PLAYER  *pp;
1048
1049	/* Send the statistics as raw text down the socket: */
1050	fputs("Name\t\tScore\tDucked\tAbsorb\tFaced\tShot\tRobbed\tMissed\tSlimeK\n", fp);
1051	for (ip = Scores; ip != NULL; ip = ip->i_next) {
1052		fprintf(fp, "%s%c%c%c\t", ip->i_name,
1053			ip->i_team == ' ' ? ' ' : '[',
1054			ip->i_team,
1055			ip->i_team == ' ' ? ' ' : ']'
1056		);
1057		if (strlen(ip->i_name) + 3 < 8)
1058			putc('\t', fp);
1059		fprintf(fp, "%.2f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
1060			ip->i_score, ip->i_ducked, ip->i_absorbed,
1061			ip->i_faced, ip->i_shot, ip->i_robbed,
1062			ip->i_missed, ip->i_slime);
1063	}
1064	fputs("\n\nName\t\tEnemy\tFriend\tDeaths\tStill\tSaved\tConnect\n", fp);
1065	for (ip = Scores; ip != NULL; ip = ip->i_next) {
1066		fprintf(fp, "%s%c%c%c\t", ip->i_name,
1067			ip->i_team == ' ' ? ' ' : '[',
1068			ip->i_team,
1069			ip->i_team == ' ' ? ' ' : ']'
1070		);
1071		if (strlen(ip->i_name) + 3 < 8)
1072			putc('\t', fp);
1073		fprintf(fp, "%d\t%d\t%d\t%d\t%d\t",
1074			ip->i_gkills, ip->i_bkills, ip->i_deaths,
1075			ip->i_stillb, ip->i_saved);
1076		for (pp = Player; pp < End_player; pp++)
1077			if (pp->p_ident == ip)
1078				putc('p', fp);
1079		for (pp = Monitor; pp < End_monitor; pp++)
1080			if (pp->p_ident == ip)
1081				putc('m', fp);
1082		putc('\n', fp);
1083	}
1084}
1085
1086
1087/*
1088 * Send the game statistics to the controlling tty
1089 */
1090static void
1091siginfo(sig)
1092	int sig;
1093{
1094	int tty;
1095	FILE *fp;
1096
1097	if ((tty = open(_PATH_TTY, O_WRONLY)) >= 0) {
1098		fp = fdopen(tty, "w");
1099		print_stats(fp);
1100		answer_info(fp);
1101		fclose(fp);
1102	}
1103}
1104
1105/*
1106 * clear_scores:
1107 *	Clear the Scores list.
1108 */
1109static void
1110clear_scores()
1111{
1112	IDENT	*ip, *nextip;
1113
1114	/* Release the list of scores: */
1115	for (ip = Scores; ip != NULL; ip = nextip) {
1116		nextip = ip->i_next;
1117		free((char *) ip);
1118	}
1119	Scores = NULL;
1120}
1121
1122/*
1123 * announce_game:
1124 *	Publically announce the game
1125 */
1126static void
1127announce_game()
1128{
1129
1130	/* TODO: could use system() to do something user-configurable */
1131}
1132
1133/*
1134 * Handle a UDP packet sent to the well known port.
1135 */
1136static void
1137handle_wkport(fd)
1138	int fd;
1139{
1140	struct sockaddr		fromaddr;
1141	socklen_t		fromlen;
1142	u_int16_t		query;
1143	u_int16_t		response;
1144
1145	fromlen = sizeof fromaddr;
1146	if (recvfrom(fd, &query, sizeof query, 0, &fromaddr, &fromlen) == -1)
1147	{
1148		logit(LOG_WARNING, "recvfrom");
1149		return;
1150	}
1151
1152#ifdef DEBUG
1153	fprintf(stderr, "query %d (%s) from %s:%d\n", query,
1154		query == C_MESSAGE ? "C_MESSAGE" :
1155		query == C_SCORES ? "C_SCORES" :
1156		query == C_PLAYER ? "C_PLAYER" :
1157		query == C_MONITOR ? "C_MONITOR" : "?",
1158		inet_ntoa(((struct sockaddr_in *)&fromaddr)->sin_addr),
1159		ntohs(((struct sockaddr_in *)&fromaddr)->sin_port));
1160#endif
1161
1162	query = ntohs(query);
1163
1164	switch (query) {
1165	  case C_MESSAGE:
1166		if (Nplayer <= 0)
1167			/* Don't bother replying if nobody to talk to: */
1168			return;
1169		/* Return the number of people playing: */
1170		response = Nplayer;
1171		break;
1172	  case C_SCORES:
1173		/* Someone wants the statistics port: */
1174		response = stat_port;
1175		break;
1176	  case C_PLAYER:
1177	  case C_MONITOR:
1178		/* Someone wants to play or watch: */
1179		if (query == C_MONITOR && Nplayer <= 0)
1180			/* Don't bother replying if there's nothing to watch: */
1181			return;
1182		/* Otherwise, tell them how to get to the game: */
1183		response = sock_port;
1184		break;
1185	  default:
1186		logit(LOG_INFO, "unknown udp query %d", query);
1187		return;
1188	}
1189
1190	response = ntohs(response);
1191	if (sendto(fd, &response, sizeof response, 0,
1192	    &fromaddr, sizeof fromaddr) == -1)
1193		logit(LOG_WARNING, "sendto");
1194}
1195