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