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