xref: /openbsd/games/hunt/huntd/driver.c (revision d167610c)
1*d167610cSschwarze /*	$OpenBSD: driver.c,v 1.30 2020/02/14 19:17:33 schwarze Exp $	*/
23faf6791Sd /*	$NetBSD: driver.c,v 1.5 1997/10/20 00:37:16 lukem Exp $	*/
33faf6791Sd /*
4598075eaSpjanzen  * Copyright (c) 1983-2003, Regents of the University of California.
5598075eaSpjanzen  * All rights reserved.
6598075eaSpjanzen  *
7598075eaSpjanzen  * Redistribution and use in source and binary forms, with or without
8598075eaSpjanzen  * modification, are permitted provided that the following conditions are
9598075eaSpjanzen  * met:
10598075eaSpjanzen  *
11598075eaSpjanzen  * + Redistributions of source code must retain the above copyright
12598075eaSpjanzen  *   notice, this list of conditions and the following disclaimer.
13598075eaSpjanzen  * + Redistributions in binary form must reproduce the above copyright
14598075eaSpjanzen  *   notice, this list of conditions and the following disclaimer in the
15598075eaSpjanzen  *   documentation and/or other materials provided with the distribution.
16598075eaSpjanzen  * + Neither the name of the University of California, San Francisco nor
17598075eaSpjanzen  *   the names of its contributors may be used to endorse or promote
18598075eaSpjanzen  *   products derived from this software without specific prior written
19598075eaSpjanzen  *   permission.
20598075eaSpjanzen  *
21598075eaSpjanzen  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22598075eaSpjanzen  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23598075eaSpjanzen  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24598075eaSpjanzen  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25598075eaSpjanzen  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26598075eaSpjanzen  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27598075eaSpjanzen  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28598075eaSpjanzen  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29598075eaSpjanzen  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30598075eaSpjanzen  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31598075eaSpjanzen  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
323faf6791Sd  */
333faf6791Sd 
343faf6791Sd #include <sys/stat.h>
359ef48543Smestre 
369ef48543Smestre #include <arpa/inet.h>
379ef48543Smestre 
383faf6791Sd #include <err.h>
393faf6791Sd #include <errno.h>
409ef48543Smestre #include <fcntl.h>
419ef48543Smestre #include <netdb.h>
429ef48543Smestre #include <paths.h>
433faf6791Sd #include <signal.h>
443faf6791Sd #include <stdlib.h>
4549d027d2Spjanzen #include <string.h>
46fab1dce0Sd #include <syslog.h>
479ef48543Smestre #include <unistd.h>
489ef48543Smestre 
49fab1dce0Sd #include "conf.h"
509ef48543Smestre #include "hunt.h"
51fab1dce0Sd #include "server.h"
523faf6791Sd 
53ca17d46aSd u_int16_t Server_port;
54fab1dce0Sd int	Server_socket;		/* test socket to answer datagrams */
55fab1dce0Sd FLAG	should_announce = TRUE;	/* true if listening on standard port */
563faf6791Sd u_short	sock_port;		/* port # of tcp listen socket */
573faf6791Sd u_short	stat_port;		/* port # of statistics tcp socket */
58fab1dce0Sd in_addr_t Server_addr = INADDR_ANY;	/* address to bind to */
593faf6791Sd 
60c72b5b24Smillert static	void	clear_scores(void);
61c72b5b24Smillert static	int	havechar(PLAYER *);
62ad952203Smillert static	void	init(int);
63c72b5b24Smillert static	void	makeboots(void);
64c72b5b24Smillert static	void	send_stats(void);
65c72b5b24Smillert static	void	zap(PLAYER *, FLAG);
66c72b5b24Smillert static  void	announce_game(void);
67c72b5b24Smillert static	void	siginfo(int);
68c72b5b24Smillert static	void	print_stats(FILE *);
69c72b5b24Smillert static	void	handle_wkport(int);
703faf6791Sd 
713faf6791Sd /*
723faf6791Sd  * main:
733faf6791Sd  *	The main program.
743faf6791Sd  */
753faf6791Sd int
main(int ac,char ** av)760f16a76cSmestre main(int ac, char **av)
773faf6791Sd {
783faf6791Sd 	PLAYER		*pp;
793faf6791Sd 	int		had_char;
803faf6791Sd 	static fd_set	read_fds;
813faf6791Sd 	static FLAG	first = TRUE;
823faf6791Sd 	static FLAG	server = FALSE;
83466e81dfStb 	extern char	*__progname;
843faf6791Sd 	int		c;
85fab1dce0Sd 	static struct timeval	linger = { 0, 0 };
86c6d62522Sd 	static struct timeval	timeout = { 0, 0 }, *to;
87ca17d46aSd 	struct spawn	*sp, *spnext;
88c6d62522Sd 	int		ret;
89c6d62522Sd 	int		nready;
90ca17d46aSd 	int		fd;
91ad952203Smillert 	int		background = 0;
923faf6791Sd 
93fab1dce0Sd 	config();
94fab1dce0Sd 
95ad952203Smillert 	while ((c = getopt(ac, av, "bsp:a:D:")) != -1) {
963faf6791Sd 		switch (c) {
97ad952203Smillert 		  case 'b':
98ad952203Smillert 			background = 1;
99ad952203Smillert 			conf_syslog = 1;
100ad952203Smillert 			conf_logerr = 0;
101ad952203Smillert 			break;
1023faf6791Sd 		  case 's':
1033faf6791Sd 			server = TRUE;
1043faf6791Sd 			break;
1053faf6791Sd 		  case 'p':
106fab1dce0Sd 			should_announce = FALSE;
107fab1dce0Sd 			Server_port = atoi(optarg);
1083faf6791Sd 			break;
109fab1dce0Sd 		  case 'a':
110ca17d46aSd 			if (!inet_aton(optarg, (struct in_addr *)&Server_addr))
111fab1dce0Sd 				err(1, "bad interface address: %s", optarg);
112fab1dce0Sd 			break;
113ca17d46aSd 		  case 'D':
114ca17d46aSd 			config_arg(optarg);
115ca17d46aSd 			break;
1163faf6791Sd 		  default:
1173faf6791Sd erred:
118eb151060Sjmc 			fprintf(stderr,
119cc710aa9Sschwarze 			    "usage: %s [-bs] [-a addr] [-D var=value] "
120eb151060Sjmc 			    "[-p port]\n",
121466e81dfStb 			    __progname);
1220f16a76cSmestre 			return 2;
1233faf6791Sd 		}
1243faf6791Sd 	}
1253faf6791Sd 	if (optind < ac)
1263faf6791Sd 		goto erred;
1273faf6791Sd 
128fab1dce0Sd 	/* Open syslog: */
129fab1dce0Sd 	openlog("huntd", LOG_PID | (conf_logerr && !server? LOG_PERROR : 0),
130fab1dce0Sd 		LOG_DAEMON);
1313faf6791Sd 
132fab1dce0Sd 	/* Initialise game parameters: */
133ad952203Smillert 	init(background);
1343faf6791Sd 
1353faf6791Sd again:
1363faf6791Sd 	do {
137c6d62522Sd 		/* First, poll to see if we can get input */
138c6d62522Sd 		do {
1393faf6791Sd 			read_fds = Fds_mask;
1403faf6791Sd 			errno = 0;
1411e5650c4Spjanzen 			timerclear(&timeout);
142c6d62522Sd 			nready = select(Num_fds, &read_fds, NULL, NULL,
143c6d62522Sd 			    &timeout);
144c6d62522Sd 			if (nready < 0 && errno != EINTR) {
1453fc386a2Sespie 				logit(LOG_ERR, "select");
146fab1dce0Sd 				cleanup(1);
147fab1dce0Sd 			}
148c6d62522Sd 		} while (nready < 0);
149c6d62522Sd 
150c6d62522Sd 		if (nready == 0) {
151c6d62522Sd 			/*
152c6d62522Sd 			 * Nothing was ready. We do some work now
153c6d62522Sd 			 * to see if the simulation has any pending work
1547a333710Sdavid 			 * to do, and decide if we need to block
155c6d62522Sd 			 * indefinitely or just timeout.
156c6d62522Sd 			 */
1571e5650c4Spjanzen 			do {
158c6d62522Sd 				if (conf_simstep && can_moveshots()) {
159c6d62522Sd 				/*
160c6d62522Sd 				 * block for a short time before continuing
161c6d62522Sd 				 * with explosions, bullets and whatnot
162c6d62522Sd 				 */
163c6d62522Sd 					to = &timeout;
164c6d62522Sd 					to->tv_sec =  conf_simstep / 1000000;
165c6d62522Sd 					to->tv_usec = conf_simstep % 1000000;
166c6d62522Sd 				} else
167c6d62522Sd 				/*
168c6d62522Sd 				 * since there's nothing going on,
169c6d62522Sd 				 * just block waiting for external activity
170c6d62522Sd 				 */
171c6d62522Sd 					to = NULL;
172c6d62522Sd 
173c6d62522Sd 				read_fds = Fds_mask;
1743faf6791Sd 				errno = 0;
175c6d62522Sd 				nready = select(Num_fds, &read_fds, NULL, NULL,
176c6d62522Sd 				    to);
177c6d62522Sd 				if (nready < 0 && errno != EINTR) {
1783fc386a2Sespie 					logit(LOG_ERR, "select");
179c6d62522Sd 					cleanup(1);
180c6d62522Sd 				}
181c6d62522Sd 			} while (nready < 0);
1823faf6791Sd 		}
183fab1dce0Sd 
184fab1dce0Sd 		/* Remember which descriptors are active: */
1853faf6791Sd 		Have_inp = read_fds;
186fab1dce0Sd 
187c6d62522Sd 		/* Answer new player connections: */
188ca17d46aSd 		if (FD_ISSET(Socket, &Have_inp))
189c6d62522Sd 			answer_first();
190fab1dce0Sd 
191c6d62522Sd 		/* Continue answering new player connections: */
192ca17d46aSd 		for (sp = Spawn; sp; ) {
193ca17d46aSd 			spnext = sp->next;
194ca17d46aSd 			fd = sp->fd;
195ca17d46aSd 			if (FD_ISSET(fd, &Have_inp) && answer_next(sp)) {
196ca17d46aSd 				/*
197ca17d46aSd 				 * Remove from the spawn list. (fd remains in
198ca17d46aSd 				 * read set).
199ca17d46aSd 				 */
200ca17d46aSd 				*sp->prevnext = sp->next;
201ca17d46aSd 				if (sp->next)
202ca17d46aSd 					sp->next->prevnext = sp->prevnext;
203ca17d46aSd 				free(sp);
204ca17d46aSd 
205ca17d46aSd 				/* We probably consumed all data. */
206ca17d46aSd 				FD_CLR(fd, &Have_inp);
207ca17d46aSd 
208ca17d46aSd 				/* Announce game if this is the first spawn. */
209c6d62522Sd 				if (first && should_announce)
210c6d62522Sd 					announce_game();
211c6d62522Sd 				first = FALSE;
2123faf6791Sd 			}
213ca17d46aSd 			sp = spnext;
214ca17d46aSd 		}
215fab1dce0Sd 
216fab1dce0Sd 		/* Process input and move bullets until we've exhausted input */
217c6d62522Sd 		had_char = TRUE;
218c6d62522Sd 		while (had_char) {
219c6d62522Sd 
2203faf6791Sd 			moveshots();
2213faf6791Sd 			for (pp = Player; pp < End_player; )
2223faf6791Sd 				if (pp->p_death[0] != '\0')
2233faf6791Sd 					zap(pp, TRUE);
2243faf6791Sd 				else
2253faf6791Sd 					pp++;
2263faf6791Sd 			for (pp = Monitor; pp < End_monitor; )
2273faf6791Sd 				if (pp->p_death[0] != '\0')
2283faf6791Sd 					zap(pp, FALSE);
2293faf6791Sd 				else
2303faf6791Sd 					pp++;
231c6d62522Sd 
232c6d62522Sd 			had_char = FALSE;
233c6d62522Sd 			for (pp = Player; pp < End_player; pp++)
234c6d62522Sd 				if (havechar(pp)) {
235c6d62522Sd 					execute(pp);
236c6d62522Sd 					pp->p_nexec++;
237c6d62522Sd 					had_char = TRUE;
238c6d62522Sd 				}
239c6d62522Sd 			for (pp = Monitor; pp < End_monitor; pp++)
240c6d62522Sd 				if (havechar(pp)) {
241c6d62522Sd 					mon_execute(pp);
242c6d62522Sd 					pp->p_nexec++;
243c6d62522Sd 					had_char = TRUE;
244c6d62522Sd 				}
2453faf6791Sd 		}
246fab1dce0Sd 
247c6d62522Sd 		/* Handle a datagram sent to the server socket: */
248ca17d46aSd 		if (FD_ISSET(Server_socket, &Have_inp))
249c6d62522Sd 			handle_wkport(Server_socket);
250fab1dce0Sd 
251fab1dce0Sd 		/* Answer statistics connections: */
252ca17d46aSd 		if (FD_ISSET(Status, &Have_inp))
2533faf6791Sd 			send_stats();
254fab1dce0Sd 
255fab1dce0Sd 		/* Flush/synchronize all the displays: */
2563faf6791Sd 		for (pp = Player; pp < End_player; pp++) {
257ca17d46aSd 			if (FD_ISSET(pp->p_fd, &read_fds)) {
2583faf6791Sd 				sendcom(pp, READY, pp->p_nexec);
2593faf6791Sd 				pp->p_nexec = 0;
260ca17d46aSd 			}
261fab1dce0Sd 			flush(pp);
2623faf6791Sd 		}
2633faf6791Sd 		for (pp = Monitor; pp < End_monitor; pp++) {
264ca17d46aSd 			if (FD_ISSET(pp->p_fd, &read_fds)) {
2653faf6791Sd 				sendcom(pp, READY, pp->p_nexec);
2663faf6791Sd 				pp->p_nexec = 0;
267ca17d46aSd 			}
268fab1dce0Sd 			flush(pp);
2693faf6791Sd 		}
2703faf6791Sd 	} while (Nplayer > 0);
2713faf6791Sd 
272c6d62522Sd 	/* No more players! */
273c6d62522Sd 
274ca17d46aSd 	/* No players yet or a continuous game? */
275ca17d46aSd 	if (first || conf_linger < 0)
276c6d62522Sd 		goto again;
277c6d62522Sd 
278c6d62522Sd 	/* Wait a short while for one to come back: */
2793faf6791Sd 	read_fds = Fds_mask;
280fab1dce0Sd 	linger.tv_sec = conf_linger;
281c6d62522Sd 	while ((ret = select(Num_fds, &read_fds, NULL, NULL, &linger)) < 0) {
282c6d62522Sd 		if (errno != EINTR) {
2833fc386a2Sespie 			logit(LOG_WARNING, "select");
284c6d62522Sd 			break;
285c6d62522Sd 		}
2865898cabcSpjanzen 		read_fds = Fds_mask;
2871e5650c4Spjanzen 		linger.tv_sec = conf_linger;
2881e5650c4Spjanzen 		linger.tv_usec = 0;
289c6d62522Sd 	}
290c6d62522Sd 	if (ret > 0)
291fab1dce0Sd 		/* Someone returned! Resume the game: */
2923faf6791Sd 		goto again;
293c6d62522Sd 	/* else, it timed out, and the game is really over. */
294fab1dce0Sd 
295c6d62522Sd 	/* If we are an inetd server, we should re-init the map and restart: */
2963faf6791Sd 	if (server) {
2973faf6791Sd 		clear_scores();
2983faf6791Sd 		makemaze();
2993faf6791Sd 		clearwalls();
3003faf6791Sd 		makeboots();
3013faf6791Sd 		first = TRUE;
3023faf6791Sd 		goto again;
3033faf6791Sd 	}
3043faf6791Sd 
305c6d62522Sd 	/* Get rid of any attached monitors: */
3063faf6791Sd 	for (pp = Monitor; pp < End_monitor; )
3073faf6791Sd 		zap(pp, FALSE);
308fab1dce0Sd 
309c6d62522Sd 	/* Fin: */
3103faf6791Sd 	cleanup(0);
3110f16a76cSmestre 	return 0;
3123faf6791Sd }
3133faf6791Sd 
3143faf6791Sd /*
3153faf6791Sd  * init:
3163faf6791Sd  *	Initialize the global parameters.
3173faf6791Sd  */
3183faf6791Sd static void
init(int background)319ad952203Smillert init(int background)
3203faf6791Sd {
3213faf6791Sd 	int	i;
322fab1dce0Sd 	struct sockaddr_in	test_port;
323ca17d46aSd 	int	true = 1;
324ca17d46aSd 	socklen_t	len;
325fab1dce0Sd 	struct sockaddr_in	addr;
326c6d62522Sd 	struct sigaction	sact;
327ca17d46aSd 	struct servent *se;
3283faf6791Sd 
329c6d62522Sd 	sact.sa_flags = SA_RESTART;
330c6d62522Sd 	sigemptyset(&sact.sa_mask);
3313faf6791Sd 
332c6d62522Sd 	/* Ignore HUP, QUIT and PIPE: */
333c6d62522Sd 	sact.sa_handler = SIG_IGN;
334c6d62522Sd 	if (sigaction(SIGHUP, &sact, NULL) == -1)
335c6d62522Sd 		err(1, "sigaction SIGHUP");
336c6d62522Sd 	if (sigaction(SIGQUIT, &sact, NULL) == -1)
337c6d62522Sd 		err(1, "sigaction SIGQUIT");
338c6d62522Sd 	if (sigaction(SIGPIPE, &sact, NULL) == -1)
339c6d62522Sd 		err(1, "sigaction SIGPIPE");
340c6d62522Sd 
341c6d62522Sd 	/* Clean up gracefully on INT and TERM: */
342c6d62522Sd 	sact.sa_handler = cleanup;
343c6d62522Sd 	if (sigaction(SIGINT, &sact, NULL) == -1)
344c6d62522Sd 		err(1, "sigaction SIGINT");
345c6d62522Sd 	if (sigaction(SIGTERM, &sact, NULL) == -1)
346c6d62522Sd 		err(1, "sigaction SIGTERM");
347c6d62522Sd 
348c6d62522Sd 	/* Handle INFO: */
349c6d62522Sd 	sact.sa_handler = siginfo;
350c6d62522Sd 	if (sigaction(SIGINFO, &sact, NULL) == -1)
351c6d62522Sd 		err(1, "sigaction SIGINFO");
352c6d62522Sd 
353c6d62522Sd 	if (chdir("/") == -1)
354c6d62522Sd 		warn("chdir");
355c6d62522Sd 	(void) umask(0777);
3563faf6791Sd 
357fab1dce0Sd 	/* Initialize statistics socket: */
358fab1dce0Sd 	addr.sin_family = AF_INET;
359fab1dce0Sd 	addr.sin_addr.s_addr = Server_addr;
360fab1dce0Sd 	addr.sin_port = 0;
3613faf6791Sd 
362fab1dce0Sd 	Status = socket(AF_INET, SOCK_STREAM, 0);
363fab1dce0Sd 	if (bind(Status, (struct sockaddr *) &addr, sizeof addr) < 0) {
3643fc386a2Sespie 		logit(LOG_ERR, "bind");
3653faf6791Sd 		cleanup(1);
3663faf6791Sd 	}
367c6d62522Sd 	if (listen(Status, 5) == -1) {
3683fc386a2Sespie 		logit(LOG_ERR, "listen");
369c6d62522Sd 		cleanup(1);
370c6d62522Sd 	}
3713faf6791Sd 
372fab1dce0Sd 	len = sizeof (struct sockaddr_in);
373fab1dce0Sd 	if (getsockname(Status, (struct sockaddr *) &addr, &len) < 0)  {
3743fc386a2Sespie 		logit(LOG_ERR, "getsockname");
3753faf6791Sd 		cleanup(1);
3763faf6791Sd 	}
377fab1dce0Sd 	stat_port = ntohs(addr.sin_port);
378fab1dce0Sd 
379fab1dce0Sd 	/* Initialize main socket: */
380fab1dce0Sd 	addr.sin_family = AF_INET;
381fab1dce0Sd 	addr.sin_addr.s_addr = Server_addr;
382fab1dce0Sd 	addr.sin_port = 0;
383fab1dce0Sd 
384fab1dce0Sd 	Socket = socket(AF_INET, SOCK_STREAM, 0);
385ca17d46aSd 
386fab1dce0Sd 	if (bind(Socket, (struct sockaddr *) &addr, sizeof addr) < 0) {
3873fc386a2Sespie 		logit(LOG_ERR, "bind");
388fab1dce0Sd 		cleanup(1);
3893faf6791Sd 	}
390c6d62522Sd 	if (listen(Socket, 5) == -1) {
3913fc386a2Sespie 		logit(LOG_ERR, "listen");
392c6d62522Sd 		cleanup(1);
393c6d62522Sd 	}
3943faf6791Sd 
395fab1dce0Sd 	len = sizeof (struct sockaddr_in);
396fab1dce0Sd 	if (getsockname(Socket, (struct sockaddr *) &addr, &len) < 0)  {
3973fc386a2Sespie 		logit(LOG_ERR, "getsockname");
398fab1dce0Sd 		cleanup(1);
3993faf6791Sd 	}
400fab1dce0Sd 	sock_port = ntohs(addr.sin_port);
4013faf6791Sd 
402fab1dce0Sd 	/* Initialize minimal select mask */
4033faf6791Sd 	FD_ZERO(&Fds_mask);
4043faf6791Sd 	FD_SET(Socket, &Fds_mask);
4053faf6791Sd 	FD_SET(Status, &Fds_mask);
4063faf6791Sd 	Num_fds = ((Socket > Status) ? Socket : Status) + 1;
4073faf6791Sd 
408ca17d46aSd 	/* Find the port that huntd should run on */
409ca17d46aSd 	if (Server_port == 0) {
410ca17d46aSd 		se = getservbyname("hunt", "udp");
411ca17d46aSd 		if (se != NULL)
412ca17d46aSd 			Server_port = ntohs(se->s_port);
413ca17d46aSd 		else
414ca17d46aSd 			Server_port = HUNT_PORT;
415ca17d46aSd 	}
416ca17d46aSd 
417fab1dce0Sd 	/* Check if stdin is a socket: */
418fab1dce0Sd 	len = sizeof (struct sockaddr_in);
419fab1dce0Sd 	if (getsockname(STDIN_FILENO, (struct sockaddr *) &test_port, &len) >= 0
4203faf6791Sd 	    && test_port.sin_family == AF_INET) {
4210c53816aSpjanzen 		/* We are probably running from inetd:  don't log to stderr */
422fab1dce0Sd 		Server_socket = STDIN_FILENO;
4230c53816aSpjanzen 		conf_logerr = 0;
424fab1dce0Sd 		if (test_port.sin_port != htons((u_short) Server_port)) {
425ca17d46aSd 			/* Private game */
426fab1dce0Sd 			should_announce = FALSE;
427fab1dce0Sd 			Server_port = ntohs(test_port.sin_port);
4283faf6791Sd 		}
4293faf6791Sd 	} else {
430fab1dce0Sd 		/* We need to listen on a socket: */
431fab1dce0Sd 		test_port = addr;
432fab1dce0Sd 		test_port.sin_port = htons((u_short) Server_port);
4333faf6791Sd 
434fab1dce0Sd 		Server_socket = socket(AF_INET, SOCK_DGRAM, 0);
435ca17d46aSd 
436ca17d46aSd 		/* Permit multiple huntd's on the same port. */
437ca17d46aSd 		if (setsockopt(Server_socket, SOL_SOCKET, SO_REUSEPORT, &true,
438ca17d46aSd 		    sizeof true) < 0)
4393fc386a2Sespie 			logit(LOG_ERR, "setsockopt SO_REUSEADDR");
440ca17d46aSd 
441fab1dce0Sd 		if (bind(Server_socket, (struct sockaddr *) &test_port,
442fab1dce0Sd 		    sizeof test_port) < 0) {
4433fc386a2Sespie 			logit(LOG_ERR, "bind port %d", Server_port);
444fab1dce0Sd 			cleanup(1);
4453faf6791Sd 		}
446c6d62522Sd 
447ad952203Smillert 		/* Become a daemon if asked to do so. */
448ad952203Smillert 		if (background)
449ad952203Smillert 			daemon(0, 0);
450ad952203Smillert 
451c6d62522Sd 		/* Datagram sockets do not need a listen() call. */
4523faf6791Sd 	}
4533faf6791Sd 
454fab1dce0Sd 	/* We'll handle the broadcast listener in the main loop: */
455fab1dce0Sd 	FD_SET(Server_socket, &Fds_mask);
456fab1dce0Sd 	if (Server_socket + 1 > Num_fds)
457fab1dce0Sd 		Num_fds = Server_socket + 1;
4583faf6791Sd 
459fab1dce0Sd 	/* Dig the maze: */
460fab1dce0Sd 	makemaze();
461fab1dce0Sd 
462fab1dce0Sd 	/* Create some boots, if needed: */
463fab1dce0Sd 	makeboots();
464fab1dce0Sd 
465fab1dce0Sd 	/* Construct a table of what objects a player can see over: */
4663faf6791Sd 	for (i = 0; i < NASCII; i++)
4673faf6791Sd 		See_over[i] = TRUE;
4683faf6791Sd 	See_over[DOOR] = FALSE;
4693faf6791Sd 	See_over[WALL1] = FALSE;
4703faf6791Sd 	See_over[WALL2] = FALSE;
4713faf6791Sd 	See_over[WALL3] = FALSE;
4723faf6791Sd 	See_over[WALL4] = FALSE;
4733faf6791Sd 	See_over[WALL5] = FALSE;
4743faf6791Sd 
475c6d62522Sd 	logx(LOG_INFO, "game started");
4763faf6791Sd }
4773faf6791Sd 
4783faf6791Sd /*
4793faf6791Sd  * makeboots:
4803faf6791Sd  *	Put the boots in the maze
4813faf6791Sd  */
4823faf6791Sd static void
makeboots(void)4830f16a76cSmestre makeboots(void)
4843faf6791Sd {
4853faf6791Sd 	int	x, y;
4863faf6791Sd 	PLAYER	*pp;
4873faf6791Sd 
488fab1dce0Sd 	if (conf_boots) {
4893faf6791Sd 		do {
4903faf6791Sd 			x = rand_num(WIDTH - 1) + 1;
4913faf6791Sd 			y = rand_num(HEIGHT - 1) + 1;
4923faf6791Sd 		} while (Maze[y][x] != SPACE);
4933faf6791Sd 		Maze[y][x] = BOOT_PAIR;
494fab1dce0Sd 	}
495fab1dce0Sd 
4963faf6791Sd 	for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
4973faf6791Sd 		pp->p_flying = -1;
4983faf6791Sd }
4993faf6791Sd 
5003faf6791Sd 
5013faf6791Sd /*
5023faf6791Sd  * checkdam:
503fab1dce0Sd  *	Apply damage to the victim from an attacker.
504fab1dce0Sd  *	If the victim dies as a result, give points to 'credit',
5053faf6791Sd  */
5063faf6791Sd void
checkdam(PLAYER * victim,PLAYER * attacker,IDENT * credit,int damage,char shot_type)5070f16a76cSmestre checkdam(PLAYER *victim, PLAYER *attacker, IDENT *credit, int damage,
5080f16a76cSmestre 	char shot_type)
5093faf6791Sd {
5103faf6791Sd 	char	*cp;
511fab1dce0Sd 	int	y;
5123faf6791Sd 
513fab1dce0Sd 	/* Don't do anything if the victim is already in the throes of death */
514fab1dce0Sd 	if (victim->p_death[0] != '\0')
5153faf6791Sd 		return;
516fab1dce0Sd 
517fab1dce0Sd 	/* Weaken slime attacks by 0.5 * number of boots the victim has on: */
5183faf6791Sd 	if (shot_type == SLIME)
519fab1dce0Sd 		switch (victim->p_nboots) {
5203faf6791Sd 		  default:
5213faf6791Sd 			break;
5223faf6791Sd 		  case 1:
523fab1dce0Sd 			damage = (damage + 1) / 2;
5243faf6791Sd 			break;
5253faf6791Sd 		  case 2:
526fab1dce0Sd 			if (attacker != NULL)
527fab1dce0Sd 				message(attacker, "He has boots on!");
5283faf6791Sd 			return;
5293faf6791Sd 		}
5303faf6791Sd 
531fab1dce0Sd 	/* The victim sustains some damage: */
532fab1dce0Sd 	victim->p_damage += damage;
533fab1dce0Sd 
534fab1dce0Sd 	/* Check if the victim survives the hit: */
535fab1dce0Sd 	if (victim->p_damage <= victim->p_damcap) {
536fab1dce0Sd 		/* They survive. */
537fab1dce0Sd 		outyx(victim, STAT_DAM_ROW, STAT_VALUE_COL, "%2d",
538fab1dce0Sd 			victim->p_damage);
539fab1dce0Sd 		return;
540fab1dce0Sd 	}
541fab1dce0Sd 
542fab1dce0Sd 	/* Describe how the victim died: */
5433faf6791Sd 	switch (shot_type) {
5443faf6791Sd 	  default:
5453faf6791Sd 		cp = "Killed";
5463faf6791Sd 		break;
5473faf6791Sd 	  case FALL:
5483faf6791Sd 		cp = "Killed on impact";
5493faf6791Sd 		break;
5503faf6791Sd 	  case KNIFE:
5513faf6791Sd 		cp = "Stabbed to death";
552fab1dce0Sd 		victim->p_ammo = 0;		/* No exploding */
5533faf6791Sd 		break;
5543faf6791Sd 	  case SHOT:
5553faf6791Sd 		cp = "Shot to death";
5563faf6791Sd 		break;
5573faf6791Sd 	  case GRENADE:
5583faf6791Sd 	  case SATCHEL:
5593faf6791Sd 	  case BOMB:
5603faf6791Sd 		cp = "Bombed";
5613faf6791Sd 		break;
5623faf6791Sd 	  case MINE:
5633faf6791Sd 	  case GMINE:
5643faf6791Sd 		cp = "Blown apart";
5653faf6791Sd 		break;
5663faf6791Sd 	  case SLIME:
5673faf6791Sd 		cp = "Slimed";
5683faf6791Sd 		if (credit != NULL)
5693faf6791Sd 			credit->i_slime++;
5703faf6791Sd 		break;
5713faf6791Sd 	  case LAVA:
5723faf6791Sd 		cp = "Baked";
5733faf6791Sd 		break;
5743faf6791Sd 	  case DSHOT:
5753faf6791Sd 		cp = "Eliminated";
5763faf6791Sd 		break;
5773faf6791Sd 	}
578fab1dce0Sd 
5793faf6791Sd 	if (credit == NULL) {
580fab1dce0Sd 		char *blame;
581fab1dce0Sd 
582fab1dce0Sd 		/*
583fab1dce0Sd 		 * Nobody is taking the credit for the kill.
584fab1dce0Sd 		 * Attribute it to either a mine or 'act of God'.
585fab1dce0Sd 		 */
586fab1dce0Sd 		switch (shot_type) {
587fab1dce0Sd 		case MINE:
588fab1dce0Sd 		case GMINE:
589fab1dce0Sd 			blame = "a mine";
590fab1dce0Sd 			break;
591fab1dce0Sd 		default:
592fab1dce0Sd 			blame = "act of God";
593fab1dce0Sd 			break;
594fab1dce0Sd 		}
595fab1dce0Sd 
596fab1dce0Sd 		/* Set the death message: */
597fab1dce0Sd 		(void) snprintf(victim->p_death, sizeof victim->p_death,
598fab1dce0Sd 			"| %s by %s |", cp, blame);
599fab1dce0Sd 
600fab1dce0Sd 		/* No further score crediting needed. */
6013faf6791Sd 		return;
6023faf6791Sd 	}
6033faf6791Sd 
604fab1dce0Sd 	/* Set the death message: */
605fab1dce0Sd 	(void) snprintf(victim->p_death, sizeof victim->p_death,
606fab1dce0Sd 		"| %s by %s |", cp, credit->i_name);
6073faf6791Sd 
608fab1dce0Sd 	if (victim == attacker) {
609fab1dce0Sd 		/* No use killing yourself. */
6103faf6791Sd 		credit->i_kills--;
6113faf6791Sd 		credit->i_bkills++;
6123faf6791Sd 	}
613fab1dce0Sd 	else if (victim->p_ident->i_team == ' '
614fab1dce0Sd 	    || victim->p_ident->i_team != credit->i_team) {
615fab1dce0Sd 		/* A cross-team kill: */
6163faf6791Sd 		credit->i_kills++;
6173faf6791Sd 		credit->i_gkills++;
6183faf6791Sd 	}
6193faf6791Sd 	else {
620fab1dce0Sd 		/* They killed someone on the same team: */
6213faf6791Sd 		credit->i_kills--;
6223faf6791Sd 		credit->i_bkills++;
6233faf6791Sd 	}
624fab1dce0Sd 
625fab1dce0Sd 	/* Compute the new credited score: */
6263faf6791Sd 	credit->i_score = credit->i_kills / (double) credit->i_entries;
627fab1dce0Sd 
628fab1dce0Sd 	/* The victim accrues one death: */
629fab1dce0Sd 	victim->p_ident->i_deaths++;
630fab1dce0Sd 
631fab1dce0Sd 	/* Account for 'Stillborn' deaths */
632fab1dce0Sd 	if (victim->p_nchar == 0)
633fab1dce0Sd 		victim->p_ident->i_stillb++;
634fab1dce0Sd 
635fab1dce0Sd 	if (attacker) {
636fab1dce0Sd 		/* Give the attacker player a bit more strength */
637fab1dce0Sd 		attacker->p_damcap += conf_killgain;
638fab1dce0Sd 		attacker->p_damage -= conf_killgain;
639fab1dce0Sd 		if (attacker->p_damage < 0)
640fab1dce0Sd 			attacker->p_damage = 0;
641fab1dce0Sd 
64248c40995Spjanzen 		/* Tell the attacker his new strength: */
643fab1dce0Sd 		outyx(attacker, STAT_DAM_ROW, STAT_VALUE_COL, "%2d/%2d",
644fab1dce0Sd 			attacker->p_damage, attacker->p_damcap);
645fab1dce0Sd 
64648c40995Spjanzen 		/* Tell the attacker his new 'kill count': */
647fab1dce0Sd 		outyx(attacker, STAT_KILL_ROW, STAT_VALUE_COL, "%3d",
648fab1dce0Sd 			(attacker->p_damcap - conf_maxdam) / 2);
649fab1dce0Sd 
650fab1dce0Sd 		/* Update the attacker's score for everyone else */
651fab1dce0Sd 		y = STAT_PLAY_ROW + 1 + (attacker - Player);
652fab1dce0Sd 		outyx(ALL_PLAYERS, y, STAT_NAME_COL,
653fab1dce0Sd 			"%5.2f", attacker->p_ident->i_score);
6543faf6791Sd 	}
6553faf6791Sd }
6563faf6791Sd 
6573faf6791Sd /*
6583faf6791Sd  * zap:
659fab1dce0Sd  *	Kill off a player and take them out of the game.
660fab1dce0Sd  *	The 'was_player' flag indicates that the player was not
661fab1dce0Sd  *	a monitor and needs extra cleaning up.
6623faf6791Sd  */
6633faf6791Sd static void
zap(PLAYER * pp,FLAG was_player)6640f16a76cSmestre zap(PLAYER *pp, FLAG was_player)
6653faf6791Sd {
666fab1dce0Sd 	int	len;
6673faf6791Sd 	BULLET	*bp;
6683faf6791Sd 	PLAYER	*np;
6693faf6791Sd 	int	x, y;
6703faf6791Sd 	int	savefd;
6713faf6791Sd 
6723faf6791Sd 	if (was_player) {
673fab1dce0Sd 		/* If they died from a shot, clean up shrapnel */
6743faf6791Sd 		if (pp->p_undershot)
6753faf6791Sd 			fixshots(pp->p_y, pp->p_x, pp->p_over);
676fab1dce0Sd 		/* Let the player see their last position: */
6773faf6791Sd 		drawplayer(pp, FALSE);
678fab1dce0Sd 		/* Remove from game: */
6793faf6791Sd 		Nplayer--;
6803faf6791Sd 	}
6813faf6791Sd 
682fab1dce0Sd 	/* Display the cause of death in the centre of the screen: */
683fab1dce0Sd 	len = strlen(pp->p_death);
6843faf6791Sd 	x = (WIDTH - len) / 2;
685fab1dce0Sd 	outyx(pp, HEIGHT / 2, x, "%s", pp->p_death);
686fab1dce0Sd 
687fab1dce0Sd 	/* Put some horizontal lines around and below the death message: */
688fab1dce0Sd 	memset(pp->p_death + 1, '-', len - 2);
6893faf6791Sd 	pp->p_death[0] = '+';
6903faf6791Sd 	pp->p_death[len - 1] = '+';
691fab1dce0Sd 	outyx(pp, HEIGHT / 2 - 1, x, "%s", pp->p_death);
692fab1dce0Sd 	outyx(pp, HEIGHT / 2 + 1, x, "%s", pp->p_death);
693fab1dce0Sd 
694fab1dce0Sd 	/* Move to bottom left */
6953faf6791Sd 	cgoto(pp, HEIGHT, 0);
6963faf6791Sd 
6973faf6791Sd 	savefd = pp->p_fd;
6983faf6791Sd 
6993faf6791Sd 	if (was_player) {
700fab1dce0Sd 		int	expl_charge;
701fab1dce0Sd 		int	expl_type;
702fab1dce0Sd 		int	ammo_exploding;
703fab1dce0Sd 
704fab1dce0Sd 		/* Check all the bullets: */
7053faf6791Sd 		for (bp = Bullets; bp != NULL; bp = bp->b_next) {
7063faf6791Sd 			if (bp->b_owner == pp)
707fab1dce0Sd 				/* Zapped players can't own bullets: */
7083faf6791Sd 				bp->b_owner = NULL;
7093faf6791Sd 			if (bp->b_x == pp->p_x && bp->b_y == pp->p_y)
710fab1dce0Sd 				/* Bullets over the player are now over air: */
7113faf6791Sd 				bp->b_over = SPACE;
7123faf6791Sd 		}
7133faf6791Sd 
714fab1dce0Sd 		/* Explode a random fraction of the player's ammo: */
715fab1dce0Sd 		ammo_exploding = rand_num(pp->p_ammo);
716fab1dce0Sd 
717fab1dce0Sd 		/* Determine the type and amount of detonation: */
718fab1dce0Sd 		expl_charge = rand_num(ammo_exploding + 1);
7193faf6791Sd 		if (pp->p_ammo == 0)
720fab1dce0Sd 			/* Ignore the no-ammo case: */
721fab1dce0Sd 			expl_charge = 0;
722fab1dce0Sd 		else if (ammo_exploding >= pp->p_ammo - 1) {
723fab1dce0Sd 			/* Maximal explosions always appear as slime: */
724fab1dce0Sd 			expl_charge = pp->p_ammo;
725fab1dce0Sd 			expl_type = SLIME;
726fab1dce0Sd 		} else {
727fab1dce0Sd 			/*
728fab1dce0Sd 			 * Figure out the best effective explosion
729fab1dce0Sd 			 * type to use, given the amount of charge
730fab1dce0Sd 			 */
731fab1dce0Sd 			int btype, stype;
732fab1dce0Sd 			for (btype = MAXBOMB - 1; btype > 0; btype--)
733fab1dce0Sd 				if (expl_charge >= shot_req[btype])
7343faf6791Sd 					break;
735fab1dce0Sd 			for (stype = MAXSLIME - 1; stype > 0; stype--)
736fab1dce0Sd 				if (expl_charge >= slime_req[stype])
7373faf6791Sd 					break;
738fab1dce0Sd 			/* Pick the larger of the bomb or slime: */
739fab1dce0Sd 			if (btype >= 0 && stype >= 0) {
740b38b2a39Sbeck 				if (shot_req[btype] > slime_req[stype])
741fab1dce0Sd 					btype = -1;
7423faf6791Sd 			}
743fab1dce0Sd 			if (btype >= 0)  {
744fab1dce0Sd 				expl_type = shot_type[btype];
745fab1dce0Sd 				expl_charge = shot_req[btype];
746fab1dce0Sd 			} else
747fab1dce0Sd 				expl_type = SLIME;
7483faf6791Sd 		}
749fab1dce0Sd 
750fab1dce0Sd 		if (expl_charge > 0) {
751fab1dce0Sd 			char buf[BUFSIZ];
752fab1dce0Sd 
753fab1dce0Sd 			/* Detonate: */
754fab1dce0Sd 			(void) add_shot(expl_type, pp->p_y, pp->p_x,
755fab1dce0Sd 			    pp->p_face, expl_charge, (PLAYER *) NULL,
756fab1dce0Sd 			    TRUE, SPACE);
757fab1dce0Sd 
758fab1dce0Sd 			/* Explain what the explosion is about. */
759fab1dce0Sd 			snprintf(buf, sizeof buf, "%s detonated.",
7603faf6791Sd 				pp->p_ident->i_name);
761fab1dce0Sd 			message(ALL_PLAYERS, buf);
762fab1dce0Sd 
7633faf6791Sd 			while (pp->p_nboots-- > 0) {
764fab1dce0Sd 				/* Throw one of the boots away: */
7653faf6791Sd 				for (np = Boot; np < &Boot[NBOOTS]; np++)
7663faf6791Sd 					if (np->p_flying < 0)
7673faf6791Sd 						break;
768fab1dce0Sd #ifdef DIAGNOSTIC
7693faf6791Sd 				if (np >= &Boot[NBOOTS])
7703faf6791Sd 					err(1, "Too many boots");
771fab1dce0Sd #endif
772fab1dce0Sd 				/* Start the boots from where the player is */
7733faf6791Sd 				np->p_undershot = FALSE;
7743faf6791Sd 				np->p_x = pp->p_x;
7753faf6791Sd 				np->p_y = pp->p_y;
776fab1dce0Sd 				/* Throw for up to 20 steps */
7773faf6791Sd 				np->p_flying = rand_num(20);
7783faf6791Sd 				np->p_flyx = 2 * rand_num(6) - 5;
7793faf6791Sd 				np->p_flyy = 2 * rand_num(6) - 5;
7803faf6791Sd 				np->p_over = SPACE;
7813faf6791Sd 				np->p_face = BOOT;
7823faf6791Sd 				showexpl(np->p_y, np->p_x, BOOT);
7833faf6791Sd 			}
7843faf6791Sd 		}
785fab1dce0Sd 		/* No explosion. Leave the player's boots behind. */
7863faf6791Sd 		else if (pp->p_nboots > 0) {
7873faf6791Sd 			if (pp->p_nboots == 2)
7883faf6791Sd 				Maze[pp->p_y][pp->p_x] = BOOT_PAIR;
7893faf6791Sd 			else
7903faf6791Sd 				Maze[pp->p_y][pp->p_x] = BOOT;
7913faf6791Sd 			if (pp->p_undershot)
7923faf6791Sd 				fixshots(pp->p_y, pp->p_x,
7933faf6791Sd 					Maze[pp->p_y][pp->p_x]);
7943faf6791Sd 		}
7953faf6791Sd 
796fab1dce0Sd 		/* Any unexploded ammo builds up in the volcano: */
797fab1dce0Sd 		volcano += pp->p_ammo - expl_charge;
798fab1dce0Sd 
799fab1dce0Sd 		/* Volcano eruption: */
800fab1dce0Sd 		if (conf_volcano && rand_num(100) < volcano /
801fab1dce0Sd 		    conf_volcano_max) {
802fab1dce0Sd 			/* Erupt near the middle of the map */
8033faf6791Sd 			do {
8043faf6791Sd 				x = rand_num(WIDTH / 2) + WIDTH / 4;
8053faf6791Sd 				y = rand_num(HEIGHT / 2) + HEIGHT / 4;
8063faf6791Sd 			} while (Maze[y][x] != SPACE);
807fab1dce0Sd 
808fab1dce0Sd 			/* Convert volcano charge into lava: */
8093faf6791Sd 			(void) add_shot(LAVA, y, x, LEFTS, volcano,
8103faf6791Sd 				(PLAYER *) NULL, TRUE, SPACE);
8113faf6791Sd 			volcano = 0;
8123faf6791Sd 
813fab1dce0Sd 			/* Tell eveyone what's happening */
814fab1dce0Sd 			message(ALL_PLAYERS, "Volcano eruption.");
815fab1dce0Sd 		}
816fab1dce0Sd 
817fab1dce0Sd 		/* Drone: */
818fab1dce0Sd 		if (conf_drone && rand_num(100) < 2) {
819fab1dce0Sd 			/* Find a starting place near the middle of the map: */
8203faf6791Sd 			do {
8213faf6791Sd 				x = rand_num(WIDTH / 2) + WIDTH / 4;
8223faf6791Sd 				y = rand_num(HEIGHT / 2) + HEIGHT / 4;
8233faf6791Sd 			} while (Maze[y][x] != SPACE);
824fab1dce0Sd 
825fab1dce0Sd 			/* Start the drone going: */
8263faf6791Sd 			add_shot(DSHOT, y, x, rand_dir(),
827fab1dce0Sd 				shot_req[conf_mindshot +
828fab1dce0Sd 				rand_num(MAXBOMB - conf_mindshot)],
8293faf6791Sd 				(PLAYER *) NULL, FALSE, SPACE);
8303faf6791Sd 		}
8313faf6791Sd 
832fab1dce0Sd 		/* Tell the zapped player's client to shut down. */
833fab1dce0Sd 		sendcom(pp, ENDWIN, ' ');
8343faf6791Sd 		(void) fclose(pp->p_output);
8353faf6791Sd 
836fab1dce0Sd 		/* Close up the gap in the Player array: */
8373faf6791Sd 		End_player--;
8383faf6791Sd 		if (pp != End_player) {
839fab1dce0Sd 			/* Move the last player into the gap: */
840fab1dce0Sd 			memcpy(pp, End_player, sizeof *pp);
841fab1dce0Sd 			outyx(ALL_PLAYERS,
842fab1dce0Sd 				STAT_PLAY_ROW + 1 + (pp - Player),
843fab1dce0Sd 				STAT_NAME_COL,
844fab1dce0Sd 				"%5.2f%c%-10.10s %c",
8453faf6791Sd 				pp->p_ident->i_score, stat_char(pp),
8463faf6791Sd 				pp->p_ident->i_name, pp->p_ident->i_team);
8473faf6791Sd 		}
8483faf6791Sd 
849fab1dce0Sd 		/* Erase the last player from the display: */
850fab1dce0Sd 		cgoto(ALL_PLAYERS, STAT_PLAY_ROW + 1 + Nplayer, STAT_NAME_COL);
851fab1dce0Sd 		ce(ALL_PLAYERS);
8523faf6791Sd 	}
8533faf6791Sd 	else {
854fab1dce0Sd 		/* Zap a monitor */
855fab1dce0Sd 
856fab1dce0Sd 		/* Close the session: */
857fab1dce0Sd 		sendcom(pp, ENDWIN, LAST_PLAYER);
8583faf6791Sd 		(void) fclose(pp->p_output);
8593faf6791Sd 
860fab1dce0Sd 		/* shuffle the monitor table */
8613faf6791Sd 		End_monitor--;
8623faf6791Sd 		if (pp != End_monitor) {
863fab1dce0Sd 			memcpy(pp, End_monitor, sizeof *pp);
864fab1dce0Sd 			outyx(ALL_PLAYERS,
865fab1dce0Sd 				STAT_MON_ROW + 1 + (pp - Player), STAT_NAME_COL,
866fab1dce0Sd 				"%5.5s %-10.10s %c", " ",
8673faf6791Sd 				pp->p_ident->i_name, pp->p_ident->i_team);
8683faf6791Sd 		}
8693faf6791Sd 
870fab1dce0Sd 		/* Erase the last monitor in the list */
871fab1dce0Sd 		cgoto(ALL_PLAYERS,
872fab1dce0Sd 			STAT_MON_ROW + 1 + (End_monitor - Monitor),
873fab1dce0Sd 			STAT_NAME_COL);
874fab1dce0Sd 		ce(ALL_PLAYERS);
8753faf6791Sd 	}
8763faf6791Sd 
877fab1dce0Sd 	/* Update the file descriptor sets used by select: */
8783faf6791Sd 	FD_CLR(savefd, &Fds_mask);
8793faf6791Sd 	if (Num_fds == savefd + 1) {
8803faf6791Sd 		Num_fds = Socket;
881fab1dce0Sd 		if (Server_socket > Socket)
882fab1dce0Sd 			Num_fds = Server_socket;
8833faf6791Sd 		for (np = Player; np < End_player; np++)
8843faf6791Sd 			if (np->p_fd > Num_fds)
8853faf6791Sd 				Num_fds = np->p_fd;
8863faf6791Sd 		for (np = Monitor; np < End_monitor; np++)
8873faf6791Sd 			if (np->p_fd > Num_fds)
8883faf6791Sd 				Num_fds = np->p_fd;
8893faf6791Sd 		Num_fds++;
8903faf6791Sd 	}
8913faf6791Sd }
8923faf6791Sd 
8933faf6791Sd /*
8943faf6791Sd  * rand_num:
8953faf6791Sd  *	Return a random number in a given range.
8963faf6791Sd  */
8973faf6791Sd int
rand_num(int range)8980f16a76cSmestre rand_num(int range)
8993faf6791Sd {
90066e49541Snaddy 	return (arc4random_uniform(range));
9013faf6791Sd }
9023faf6791Sd 
9033faf6791Sd /*
9043faf6791Sd  * havechar:
9053faf6791Sd  *	Check to see if we have any characters in the input queue; if
9063faf6791Sd  *	we do, read them, stash them away, and return TRUE; else return
9073faf6791Sd  *	FALSE.
9083faf6791Sd  */
9093faf6791Sd static int
havechar(PLAYER * pp)9100f16a76cSmestre havechar(PLAYER *pp)
9113faf6791Sd {
912ca17d46aSd 	int ret;
9133faf6791Sd 
914c6d62522Sd 	/* Do we already have characters? */
9153faf6791Sd 	if (pp->p_ncount < pp->p_nchar)
9163faf6791Sd 		return TRUE;
917ca17d46aSd 	/* Ignore if nothing to read. */
9183faf6791Sd 	if (!FD_ISSET(pp->p_fd, &Have_inp))
9193faf6791Sd 		return FALSE;
920c6d62522Sd 	/* Remove the player from the read set until we have drained them: */
9213faf6791Sd 	FD_CLR(pp->p_fd, &Have_inp);
922c6d62522Sd 
923c6d62522Sd 	/* Suck their keypresses into a buffer: */
9243faf6791Sd check_again:
925ca17d46aSd 	errno = 0;
926ca17d46aSd 	ret = read(pp->p_fd, pp->p_cbuf, sizeof pp->p_cbuf);
927ca17d46aSd 	if (ret == -1) {
9283faf6791Sd 		if (errno == EINTR)
9293faf6791Sd 			goto check_again;
930ca17d46aSd 		if (errno == EAGAIN) {
931ca17d46aSd #ifdef DEBUG
932ca17d46aSd 			warn("Have_inp is wrong for %d", pp->p_fd);
933ca17d46aSd #endif
934ca17d46aSd 			return FALSE;
935ca17d46aSd 		}
9363fc386a2Sespie 		logit(LOG_INFO, "read");
937ca17d46aSd 	}
938ca17d46aSd 	if (ret > 0) {
939ca17d46aSd 		/* Got some data */
940ca17d46aSd 		pp->p_nchar = ret;
941ca17d46aSd 	} else {
942ca17d46aSd 		/* Connection was lost/closed: */
9433faf6791Sd 		pp->p_cbuf[0] = 'q';
944c6d62522Sd 		pp->p_nchar = 1;
9453faf6791Sd 	}
946c6d62522Sd 	/* Reset pointer into read buffer */
9473faf6791Sd 	pp->p_ncount = 0;
9483faf6791Sd 	return TRUE;
9493faf6791Sd }
9503faf6791Sd 
9513faf6791Sd /*
9523faf6791Sd  * cleanup:
9533faf6791Sd  *	Exit with the given value, cleaning up any droppings lying around
9543faf6791Sd  */
955fab1dce0Sd void
cleanup(int eval)9560f16a76cSmestre cleanup(int eval)
9573faf6791Sd {
9583faf6791Sd 	PLAYER	*pp;
9593faf6791Sd 
960fab1dce0Sd 	/* Place their cursor in a friendly position: */
961fab1dce0Sd 	cgoto(ALL_PLAYERS, HEIGHT, 0);
9623faf6791Sd 
963fab1dce0Sd 	/* Send them all the ENDWIN command: */
964fab1dce0Sd 	sendcom(ALL_PLAYERS, ENDWIN, LAST_PLAYER);
965fab1dce0Sd 
966fab1dce0Sd 	/* And close their connections: */
967fab1dce0Sd 	for (pp = Player; pp < End_player; pp++)
968fab1dce0Sd 		(void) fclose(pp->p_output);
969fab1dce0Sd 	for (pp = Monitor; pp < End_monitor; pp++)
970fab1dce0Sd 		(void) fclose(pp->p_output);
971fab1dce0Sd 
972fab1dce0Sd 	/* Close the server socket: */
973fab1dce0Sd 	(void) close(Socket);
974fab1dce0Sd 
975fab1dce0Sd 	/* The end: */
976c6d62522Sd 	logx(LOG_INFO, "game over");
9773faf6791Sd 	exit(eval);
9783faf6791Sd }
9793faf6791Sd 
9803faf6791Sd /*
9813faf6791Sd  * send_stats:
982fab1dce0Sd  *	Accept a connection to the statistics port, and emit
983fab1dce0Sd  *	the stats.
9843faf6791Sd  */
9853faf6791Sd static void
send_stats(void)9860f16a76cSmestre send_stats(void)
9873faf6791Sd {
9883faf6791Sd 	FILE	*fp;
9893faf6791Sd 	int	s;
990fab1dce0Sd 	struct sockaddr_in	sockstruct;
991ca17d46aSd 	socklen_t	socklen;
9923faf6791Sd 
993fab1dce0Sd 	/* Accept a connection to the statistics socket: */
9943faf6791Sd 	socklen = sizeof sockstruct;
9955f13b87eSguenther 	s = accept4(Status, (struct sockaddr *) &sockstruct, &socklen,
9965f13b87eSguenther 	    SOCK_NONBLOCK);
9973faf6791Sd 	if (s < 0) {
9983faf6791Sd 		if (errno == EINTR)
9993faf6791Sd 			return;
1000c6d62522Sd 		logx(LOG_ERR, "accept");
10013faf6791Sd 		return;
10023faf6791Sd 	}
1003fab1dce0Sd 
10043faf6791Sd 	fp = fdopen(s, "w");
10053faf6791Sd 	if (fp == NULL) {
10063fc386a2Sespie 		logit(LOG_ERR, "fdopen");
10073faf6791Sd 		(void) close(s);
10083faf6791Sd 		return;
10093faf6791Sd 	}
10103faf6791Sd 
1011c6d62522Sd 	print_stats(fp);
1012c6d62522Sd 
1013c6d62522Sd 	(void) fclose(fp);
1014c6d62522Sd }
1015c6d62522Sd 
1016c6d62522Sd /*
1017c6d62522Sd  * print_stats:
1018c6d62522Sd  *	emit the game statistics
1019c6d62522Sd  */
1020c6d62522Sd void
print_stats(FILE * fp)10210f16a76cSmestre print_stats(FILE *fp)
1022c6d62522Sd {
1023c6d62522Sd 	IDENT	*ip;
1024c6d62522Sd 	PLAYER  *pp;
1025c6d62522Sd 
1026fab1dce0Sd 	/* Send the statistics as raw text down the socket: */
10273faf6791Sd 	fputs("Name\t\tScore\tDucked\tAbsorb\tFaced\tShot\tRobbed\tMissed\tSlimeK\n", fp);
10283faf6791Sd 	for (ip = Scores; ip != NULL; ip = ip->i_next) {
1029fab1dce0Sd 		fprintf(fp, "%s%c%c%c\t", ip->i_name,
1030fab1dce0Sd 			ip->i_team == ' ' ? ' ' : '[',
1031fab1dce0Sd 			ip->i_team,
1032fab1dce0Sd 			ip->i_team == ' ' ? ' ' : ']'
1033fab1dce0Sd 		);
1034fab1dce0Sd 		if (strlen(ip->i_name) + 3 < 8)
10353faf6791Sd 			putc('\t', fp);
10363faf6791Sd 		fprintf(fp, "%.2f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
10373faf6791Sd 			ip->i_score, ip->i_ducked, ip->i_absorbed,
10383faf6791Sd 			ip->i_faced, ip->i_shot, ip->i_robbed,
10393faf6791Sd 			ip->i_missed, ip->i_slime);
10403faf6791Sd 	}
1041c6d62522Sd 	fputs("\n\nName\t\tEnemy\tFriend\tDeaths\tStill\tSaved\tConnect\n", fp);
10423faf6791Sd 	for (ip = Scores; ip != NULL; ip = ip->i_next) {
1043fab1dce0Sd 		fprintf(fp, "%s%c%c%c\t", ip->i_name,
1044fab1dce0Sd 			ip->i_team == ' ' ? ' ' : '[',
1045fab1dce0Sd 			ip->i_team,
1046fab1dce0Sd 			ip->i_team == ' ' ? ' ' : ']'
1047fab1dce0Sd 		);
10483faf6791Sd 		if (strlen(ip->i_name) + 3 < 8)
10493faf6791Sd 			putc('\t', fp);
1050c6d62522Sd 		fprintf(fp, "%d\t%d\t%d\t%d\t%d\t",
10513faf6791Sd 			ip->i_gkills, ip->i_bkills, ip->i_deaths,
10523faf6791Sd 			ip->i_stillb, ip->i_saved);
1053c6d62522Sd 		for (pp = Player; pp < End_player; pp++)
1054c6d62522Sd 			if (pp->p_ident == ip)
1055c6d62522Sd 				putc('p', fp);
1056c6d62522Sd 		for (pp = Monitor; pp < End_monitor; pp++)
1057c6d62522Sd 			if (pp->p_ident == ip)
1058c6d62522Sd 				putc('m', fp);
1059c6d62522Sd 		putc('\n', fp);
1060c6d62522Sd 	}
10613faf6791Sd }
10623faf6791Sd 
1063c6d62522Sd 
1064c6d62522Sd /*
1065c6d62522Sd  * Send the game statistics to the controlling tty
1066c6d62522Sd  */
1067c6d62522Sd static void
siginfo(int sig)10680f16a76cSmestre siginfo(int sig)
1069c6d62522Sd {
1070c6d62522Sd 	int tty;
1071c6d62522Sd 	FILE *fp;
1072c6d62522Sd 
1073c6d62522Sd 	if ((tty = open(_PATH_TTY, O_WRONLY)) >= 0) {
1074c6d62522Sd 		fp = fdopen(tty, "w");
1075c6d62522Sd 		print_stats(fp);
1076c6d62522Sd 		answer_info(fp);
1077c6d62522Sd 		fclose(fp);
1078c6d62522Sd 	}
10793faf6791Sd }
10803faf6791Sd 
10813faf6791Sd /*
10823faf6791Sd  * clear_scores:
1083fab1dce0Sd  *	Clear the Scores list.
10843faf6791Sd  */
10853faf6791Sd static void
clear_scores(void)10860f16a76cSmestre clear_scores(void)
10873faf6791Sd {
10883faf6791Sd 	IDENT	*ip, *nextip;
10893faf6791Sd 
1090fab1dce0Sd 	/* Release the list of scores: */
10913faf6791Sd 	for (ip = Scores; ip != NULL; ip = nextip) {
10923faf6791Sd 		nextip = ip->i_next;
1093c6d62522Sd 		free((char *) ip);
10943faf6791Sd 	}
10953faf6791Sd 	Scores = NULL;
10963faf6791Sd }
1097fab1dce0Sd 
1098fab1dce0Sd /*
1099fab1dce0Sd  * announce_game:
1100fab1dce0Sd  *	Publically announce the game
1101fab1dce0Sd  */
1102fab1dce0Sd static void
announce_game(void)11030f16a76cSmestre announce_game(void)
1104fab1dce0Sd {
1105fab1dce0Sd 
1106ca17d46aSd 	/* TODO: could use system() to do something user-configurable */
1107fab1dce0Sd }
1108c6d62522Sd 
1109c6d62522Sd /*
1110c6d62522Sd  * Handle a UDP packet sent to the well known port.
1111c6d62522Sd  */
1112c6d62522Sd static void
handle_wkport(int fd)11130f16a76cSmestre handle_wkport(int fd)
1114c6d62522Sd {
1115c6d62522Sd 	struct sockaddr		fromaddr;
1116ca17d46aSd 	socklen_t		fromlen;
1117c6d62522Sd 	u_int16_t		query;
1118c6d62522Sd 	u_int16_t		response;
1119c6d62522Sd 
1120c6d62522Sd 	fromlen = sizeof fromaddr;
1121c6d62522Sd 	if (recvfrom(fd, &query, sizeof query, 0, &fromaddr, &fromlen) == -1)
1122c6d62522Sd 	{
11233fc386a2Sespie 		logit(LOG_WARNING, "recvfrom");
1124c6d62522Sd 		return;
1125c6d62522Sd 	}
112648c40995Spjanzen 
1127ca17d46aSd #ifdef DEBUG
1128ca17d46aSd 	fprintf(stderr, "query %d (%s) from %s:%d\n", query,
1129ca17d46aSd 		query == C_MESSAGE ? "C_MESSAGE" :
1130ca17d46aSd 		query == C_SCORES ? "C_SCORES" :
1131ca17d46aSd 		query == C_PLAYER ? "C_PLAYER" :
1132ca17d46aSd 		query == C_MONITOR ? "C_MONITOR" : "?",
1133ca17d46aSd 		inet_ntoa(((struct sockaddr_in *)&fromaddr)->sin_addr),
1134ca17d46aSd 		ntohs(((struct sockaddr_in *)&fromaddr)->sin_port));
1135ca17d46aSd #endif
1136ca17d46aSd 
1137c6d62522Sd 	query = ntohs(query);
1138c6d62522Sd 
1139c6d62522Sd 	switch (query) {
1140c6d62522Sd 	  case C_MESSAGE:
1141c6d62522Sd 		if (Nplayer <= 0)
1142c6d62522Sd 			/* Don't bother replying if nobody to talk to: */
1143c6d62522Sd 			return;
1144c6d62522Sd 		/* Return the number of people playing: */
1145c6d62522Sd 		response = Nplayer;
1146c6d62522Sd 		break;
1147c6d62522Sd 	  case C_SCORES:
1148c6d62522Sd 		/* Someone wants the statistics port: */
1149c6d62522Sd 		response = stat_port;
1150c6d62522Sd 		break;
1151c6d62522Sd 	  case C_PLAYER:
1152c6d62522Sd 	  case C_MONITOR:
1153c6d62522Sd 		/* Someone wants to play or watch: */
1154c6d62522Sd 		if (query == C_MONITOR && Nplayer <= 0)
1155c6d62522Sd 			/* Don't bother replying if there's nothing to watch: */
1156c6d62522Sd 			return;
1157c6d62522Sd 		/* Otherwise, tell them how to get to the game: */
1158c6d62522Sd 		response = sock_port;
1159c6d62522Sd 		break;
1160c6d62522Sd 	  default:
11613fc386a2Sespie 		logit(LOG_INFO, "unknown udp query %d", query);
1162c6d62522Sd 		return;
1163c6d62522Sd 	}
1164c6d62522Sd 
1165c6d62522Sd 	response = ntohs(response);
1166c6d62522Sd 	if (sendto(fd, &response, sizeof response, 0,
1167c6d62522Sd 	    &fromaddr, sizeof fromaddr) == -1)
11683fc386a2Sespie 		logit(LOG_WARNING, "sendto");
1169c6d62522Sd }
1170