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