xref: /dragonfly/games/hunt/huntd/driver.c (revision d9f85b33)
1 /*-
2  * Copyright (c) 1983-2003, Regents of the University of California.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. Neither the name of the University of California, San Francisco nor
15  *    the names of its contributors may be used to endorse or promote
16  *    products derived from this software without specific prior written
17  *    permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  *
31  * $OpenBSD: driver.c,v 1.17 2007/04/02 14:55:16 jmc Exp $
32  * $NetBSD: driver.c,v 1.5 1997/10/20 00:37:16 lukem Exp $
33  */
34 
35 #include <sys/ioctl.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include <sys/socket.h>
39 
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
42 
43 #include <err.h>
44 #include <errno.h>
45 #include <signal.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include <stdio.h>
50 #include <tcpd.h>
51 #include <syslog.h>
52 #include <netdb.h>
53 #include <paths.h>
54 #include <fcntl.h>
55 #include "hunt.h"
56 #include "conf.h"
57 #include "server.h"
58 
59 char	*First_arg;		/* pointer to argv[0] */
60 u_int16_t Server_port;
61 int	Server_socket;		/* test socket to answer datagrams */
62 FLAG	should_announce = TRUE;	/* true if listening on standard port */
63 u_short	sock_port;		/* port # of tcp listen socket */
64 u_short	stat_port;		/* port # of statistics tcp socket */
65 in_addr_t Server_addr = INADDR_ANY;	/* address to bind to */
66 
67 static	void	clear_scores(void);
68 static	int	havechar(PLAYER *);
69 static	void	init(void);
70 static	void	makeboots(void);
71 static	void	send_stats(void);
72 static	void	zap(PLAYER *, FLAG);
73 static  void	announce_game(void);
74 static	void	siginfo(int);
75 static	void	print_stats(FILE *);
76 static	void	handle_wkport(int);
77 
78 /*
79  * main:
80  *	The main program.
81  */
82 int
main(int ac,char ** av)83 main(int ac, 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 	int		c;
91 	static struct timeval	linger = { 0, 0 };
92 	static struct timeval	timeout = { 0, 0 }, *to;
93 	struct spawn	*sp, *spnext;
94 	int		ret;
95 	int		nready;
96 	int		fd;
97 
98 	First_arg = av[0];
99 
100 	config();
101 
102 	while ((c = getopt(ac, av, "sp:a:D:")) != -1) {
103 		switch (c) {
104 		  case 's':
105 			server = TRUE;
106 			break;
107 		  case 'p':
108 			should_announce = FALSE;
109 			Server_port = atoi(optarg);
110 			break;
111 		  case 'a':
112 			if (!inet_aton(optarg, (struct in_addr *)&Server_addr))
113 				err(1, "bad interface address: %s", optarg);
114 			break;
115 		  case 'D':
116 			config_arg(optarg);
117 			break;
118 		  default:
119 erred:
120 			fprintf(stderr,
121 			    "usage: %s [-s] [-a addr] [-Dvar=value ...] "
122 			    "[-p port]\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 
137 again:
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  */
320 static void
init(void)321 init(void)
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 	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 	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  */
487 static void
makeboots(void)488 makeboots(void)
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  */
511 void
checkdam(PLAYER * victim,PLAYER * attacker,IDENT * credit,int damage,char stype)512 checkdam(PLAYER *victim, PLAYER *attacker, IDENT *credit, int damage,
513     char stype)
514 {
515 	const char	*cp;
516 	int	y;
517 
518 	/* Don't do anything if the victim is already in the throes of death */
519 	if (victim->p_death[0] != '\0')
520 		return;
521 
522 	/* Weaken slime attacks by 0.5 * number of boots the victim has on: */
523 	if (stype == SLIME)
524 		switch (victim->p_nboots) {
525 		  default:
526 			break;
527 		  case 1:
528 			damage = (damage + 1) / 2;
529 			break;
530 		  case 2:
531 			if (attacker != NULL)
532 				message(attacker, "He has boots on!");
533 			return;
534 		}
535 
536 	/* The victim sustains some damage: */
537 	victim->p_damage += damage;
538 
539 	/* Check if the victim survives the hit: */
540 	if (victim->p_damage <= victim->p_damcap) {
541 		/* They survive. */
542 		outyx(victim, STAT_DAM_ROW, STAT_VALUE_COL, "%2d",
543 			victim->p_damage);
544 		return;
545 	}
546 
547 	/* Describe how the victim died: */
548 	switch (stype) {
549 	  default:
550 		cp = "Killed";
551 		break;
552 	  case FALL:
553 		cp = "Killed on impact";
554 		break;
555 	  case KNIFE:
556 		cp = "Stabbed to death";
557 		victim->p_ammo = 0;		/* No exploding */
558 		break;
559 	  case SHOT:
560 		cp = "Shot to death";
561 		break;
562 	  case GRENADE:
563 	  case SATCHEL:
564 	  case BOMB:
565 		cp = "Bombed";
566 		break;
567 	  case MINE:
568 	  case GMINE:
569 		cp = "Blown apart";
570 		break;
571 	  case SLIME:
572 		cp = "Slimed";
573 		if (credit != NULL)
574 			credit->i_slime++;
575 		break;
576 	  case LAVA:
577 		cp = "Baked";
578 		break;
579 	  case DSHOT:
580 		cp = "Eliminated";
581 		break;
582 	}
583 
584 	if (credit == NULL) {
585 		const char *blame;
586 
587 		/*
588 		 * Nobody is taking the credit for the kill.
589 		 * Attribute it to either a mine or 'act of God'.
590 		 */
591 		switch (stype) {
592 		case MINE:
593 		case GMINE:
594 			blame = "a mine";
595 			break;
596 		default:
597 			blame = "act of God";
598 			break;
599 		}
600 
601 		/* Set the death message: */
602 		snprintf(victim->p_death, sizeof victim->p_death,
603 			"| %s by %s |", cp, blame);
604 
605 		/* No further score crediting needed. */
606 		return;
607 	}
608 
609 	/* Set the death message: */
610 	snprintf(victim->p_death, sizeof victim->p_death,
611 		"| %s by %s |", cp, credit->i_name);
612 
613 	if (victim == attacker) {
614 		/* No use killing yourself. */
615 		credit->i_kills--;
616 		credit->i_bkills++;
617 	}
618 	else if (victim->p_ident->i_team == ' '
619 	    || victim->p_ident->i_team != credit->i_team) {
620 		/* A cross-team kill: */
621 		credit->i_kills++;
622 		credit->i_gkills++;
623 	}
624 	else {
625 		/* They killed someone on the same team: */
626 		credit->i_kills--;
627 		credit->i_bkills++;
628 	}
629 
630 	/* Compute the new credited score: */
631 	credit->i_score = credit->i_kills / (double) credit->i_entries;
632 
633 	/* The victim accrues one death: */
634 	victim->p_ident->i_deaths++;
635 
636 	/* Account for 'Stillborn' deaths */
637 	if (victim->p_nchar == 0)
638 		victim->p_ident->i_stillb++;
639 
640 	if (attacker) {
641 		/* Give the attacker player a bit more strength */
642 		attacker->p_damcap += conf_killgain;
643 		attacker->p_damage -= conf_killgain;
644 		if (attacker->p_damage < 0)
645 			attacker->p_damage = 0;
646 
647 		/* Tell the attacker his new strength: */
648 		outyx(attacker, STAT_DAM_ROW, STAT_VALUE_COL, "%2d/%2d",
649 			attacker->p_damage, attacker->p_damcap);
650 
651 		/* Tell the attacker his new 'kill count': */
652 		outyx(attacker, STAT_KILL_ROW, STAT_VALUE_COL, "%3d",
653 			(attacker->p_damcap - conf_maxdam) / 2);
654 
655 		/* Update the attacker's score for everyone else */
656 		y = STAT_PLAY_ROW + 1 + (attacker - Player);
657 		outyx(ALL_PLAYERS, y, STAT_NAME_COL,
658 			"%5.2f", attacker->p_ident->i_score);
659 	}
660 }
661 
662 /*
663  * zap:
664  *	Kill off a player and take them out of the game.
665  *	The 'was_player' flag indicates that the player was not
666  *	a monitor and needs extra cleaning up.
667  */
668 static void
zap(PLAYER * pp,FLAG was_player)669 zap(PLAYER *pp, FLAG was_player)
670 {
671 	int	len;
672 	BULLET	*bp;
673 	PLAYER	*np;
674 	int	x, y;
675 	int	savefd;
676 
677 	if (was_player) {
678 		/* If they died from a shot, clean up shrapnel */
679 		if (pp->p_undershot)
680 			fixshots(pp->p_y, pp->p_x, pp->p_over);
681 		/* Let the player see their last position: */
682 		drawplayer(pp, FALSE);
683 		/* Remove from game: */
684 		Nplayer--;
685 	}
686 
687 	/* Display the cause of death in the centre of the screen: */
688 	len = strlen(pp->p_death);
689 	x = (WIDTH - len) / 2;
690 	outyx(pp, HEIGHT / 2, x, "%s", pp->p_death);
691 
692 	/* Put some horizontal lines around and below the death message: */
693 	memset(pp->p_death + 1, '-', len - 2);
694 	pp->p_death[0] = '+';
695 	pp->p_death[len - 1] = '+';
696 	outyx(pp, HEIGHT / 2 - 1, x, "%s", pp->p_death);
697 	outyx(pp, HEIGHT / 2 + 1, x, "%s", pp->p_death);
698 
699 	/* Move to bottom left */
700 	cgoto(pp, HEIGHT, 0);
701 
702 	savefd = pp->p_fd;
703 
704 	if (was_player) {
705 		int	expl_charge;
706 		int	expl_type;
707 		int	ammo_exploding;
708 
709 		/* Check all the bullets: */
710 		for (bp = Bullets; bp != NULL; bp = bp->b_next) {
711 			if (bp->b_owner == pp)
712 				/* Zapped players can't own bullets: */
713 				bp->b_owner = NULL;
714 			if (bp->b_x == pp->p_x && bp->b_y == pp->p_y)
715 				/* Bullets over the player are now over air: */
716 				bp->b_over = SPACE;
717 		}
718 
719 		/* Explode a random fraction of the player's ammo: */
720 		ammo_exploding = rand_num(pp->p_ammo);
721 
722 		/* Determine the type and amount of detonation: */
723 		expl_charge = rand_num(ammo_exploding + 1);
724 		if (pp->p_ammo == 0)
725 			/* Ignore the no-ammo case: */
726 			expl_charge = expl_type = 0;
727 		else if (ammo_exploding >= pp->p_ammo - 1) {
728 			/* Maximal explosions always appear as slime: */
729 			expl_charge = pp->p_ammo;
730 			expl_type = SLIME;
731 		} else {
732 			/*
733 			 * Figure out the best effective explosion
734 			 * type to use, given the amount of charge
735 			 */
736 			int btype, stype;
737 			for (btype = MAXBOMB - 1; btype > 0; btype--)
738 				if (expl_charge >= shot_req[btype])
739 					break;
740 			for (stype = MAXSLIME - 1; stype > 0; stype--)
741 				if (expl_charge >= slime_req[stype])
742 					break;
743 			/* Pick the larger of the bomb or slime: */
744 			if (btype >= 0 && stype >= 0) {
745 				if (shot_req[btype] > slime_req[btype])
746 					btype = -1;
747 			}
748 			if (btype >= 0)  {
749 				expl_type = shot_type[btype];
750 				expl_charge = shot_req[btype];
751 			} else
752 				expl_type = SLIME;
753 		}
754 
755 		if (expl_charge > 0) {
756 			char buf[BUFSIZ];
757 
758 			/* Detonate: */
759 			add_shot(expl_type, pp->p_y, pp->p_x,
760 			    pp->p_face, expl_charge, NULL,
761 			    TRUE, SPACE);
762 
763 			/* Explain what the explosion is about. */
764 			snprintf(buf, sizeof buf, "%s detonated.",
765 				pp->p_ident->i_name);
766 			message(ALL_PLAYERS, buf);
767 
768 			while (pp->p_nboots-- > 0) {
769 				/* Throw one of the boots away: */
770 				for (np = Boot; np < &Boot[NBOOTS]; np++)
771 					if (np->p_flying < 0)
772 						break;
773 #ifdef DIAGNOSTIC
774 				if (np >= &Boot[NBOOTS])
775 					err(1, "Too many boots");
776 #endif
777 				/* Start the boots from where the player is */
778 				np->p_undershot = FALSE;
779 				np->p_x = pp->p_x;
780 				np->p_y = pp->p_y;
781 				/* Throw for up to 20 steps */
782 				np->p_flying = rand_num(20);
783 				np->p_flyx = 2 * rand_num(6) - 5;
784 				np->p_flyy = 2 * rand_num(6) - 5;
785 				np->p_over = SPACE;
786 				np->p_face = BOOT;
787 				showexpl(np->p_y, np->p_x, BOOT);
788 			}
789 		}
790 		/* No explosion. Leave the player's boots behind. */
791 		else if (pp->p_nboots > 0) {
792 			if (pp->p_nboots == 2)
793 				Maze[pp->p_y][pp->p_x] = BOOT_PAIR;
794 			else
795 				Maze[pp->p_y][pp->p_x] = BOOT;
796 			if (pp->p_undershot)
797 				fixshots(pp->p_y, pp->p_x,
798 					Maze[pp->p_y][pp->p_x]);
799 		}
800 
801 		/* Any unexploded ammo builds up in the volcano: */
802 		volcano += pp->p_ammo - expl_charge;
803 
804 		/* Volcano eruption: */
805 		if (conf_volcano && rand_num(100) < volcano /
806 		    conf_volcano_max) {
807 			/* Erupt near the middle of the map */
808 			do {
809 				x = rand_num(WIDTH / 2) + WIDTH / 4;
810 				y = rand_num(HEIGHT / 2) + HEIGHT / 4;
811 			} while (Maze[y][x] != SPACE);
812 
813 			/* Convert volcano charge into lava: */
814 			add_shot(LAVA, y, x, LEFTS, volcano,
815 				NULL, TRUE, SPACE);
816 			volcano = 0;
817 
818 			/* Tell eveyone what's happening */
819 			message(ALL_PLAYERS, "Volcano eruption.");
820 		}
821 
822 		/* Drone: */
823 		if (conf_drone && rand_num(100) < 2) {
824 			/* Find a starting place near the middle of the map: */
825 			do {
826 				x = rand_num(WIDTH / 2) + WIDTH / 4;
827 				y = rand_num(HEIGHT / 2) + HEIGHT / 4;
828 			} while (Maze[y][x] != SPACE);
829 
830 			/* Start the drone going: */
831 			add_shot(DSHOT, y, x, rand_dir(),
832 				shot_req[conf_mindshot +
833 				rand_num(MAXBOMB - conf_mindshot)],
834 				NULL, FALSE, SPACE);
835 		}
836 
837 		/* Tell the zapped player's client to shut down. */
838 		sendcom(pp, ENDWIN, ' ');
839 		fclose(pp->p_output);
840 
841 		/* Close up the gap in the Player array: */
842 		End_player--;
843 		if (pp != End_player) {
844 			/* Move the last player into the gap: */
845 			memcpy(pp, End_player, sizeof *pp);
846 			outyx(ALL_PLAYERS,
847 				STAT_PLAY_ROW + 1 + (pp - Player),
848 				STAT_NAME_COL,
849 				"%5.2f%c%-10.10s %c",
850 				pp->p_ident->i_score, stat_char(pp),
851 				pp->p_ident->i_name, pp->p_ident->i_team);
852 		}
853 
854 		/* Erase the last player from the display: */
855 		cgoto(ALL_PLAYERS, STAT_PLAY_ROW + 1 + Nplayer, STAT_NAME_COL);
856 		ce(ALL_PLAYERS);
857 	}
858 	else {
859 		/* Zap a monitor */
860 
861 		/* Close the session: */
862 		sendcom(pp, ENDWIN, LAST_PLAYER);
863 		fclose(pp->p_output);
864 
865 		/* shuffle the monitor table */
866 		End_monitor--;
867 		if (pp != End_monitor) {
868 			memcpy(pp, End_monitor, sizeof *pp);
869 			outyx(ALL_PLAYERS,
870 				STAT_MON_ROW + 1 + (pp - Player), STAT_NAME_COL,
871 				"%5.5s %-10.10s %c", " ",
872 				pp->p_ident->i_name, pp->p_ident->i_team);
873 		}
874 
875 		/* Erase the last monitor in the list */
876 		cgoto(ALL_PLAYERS,
877 			STAT_MON_ROW + 1 + (End_monitor - Monitor),
878 			STAT_NAME_COL);
879 		ce(ALL_PLAYERS);
880 	}
881 
882 	/* Update the file descriptor sets used by select: */
883 	FD_CLR(savefd, &Fds_mask);
884 	if (Num_fds == savefd + 1) {
885 		Num_fds = Socket;
886 		if (Server_socket > Socket)
887 			Num_fds = Server_socket;
888 		for (np = Player; np < End_player; np++)
889 			if (np->p_fd > Num_fds)
890 				Num_fds = np->p_fd;
891 		for (np = Monitor; np < End_monitor; np++)
892 			if (np->p_fd > Num_fds)
893 				Num_fds = np->p_fd;
894 		Num_fds++;
895 	}
896 }
897 
898 /*
899  * rand_num:
900  *	Return a random number in a given range.
901  */
902 int
rand_num(int range)903 rand_num(int range)
904 {
905 	if (range == 0)
906 		return 0;
907 	return (random() % range);
908 }
909 
910 /*
911  * havechar:
912  *	Check to see if we have any characters in the input queue; if
913  *	we do, read them, stash them away, and return TRUE; else return
914  *	FALSE.
915  */
916 static int
havechar(PLAYER * pp)917 havechar(PLAYER *pp)
918 {
919 	int ret;
920 
921 	/* Do we already have characters? */
922 	if (pp->p_ncount < pp->p_nchar)
923 		return TRUE;
924 	/* Ignore if nothing to read. */
925 	if (!FD_ISSET(pp->p_fd, &Have_inp))
926 		return FALSE;
927 	/* Remove the player from the read set until we have drained them: */
928 	FD_CLR(pp->p_fd, &Have_inp);
929 
930 	/* Suck their keypresses into a buffer: */
931 check_again:
932 	errno = 0;
933 	ret = read(pp->p_fd, pp->p_cbuf, sizeof pp->p_cbuf);
934 	if (ret == -1) {
935 		if (errno == EINTR)
936 			goto check_again;
937 		if (errno == EAGAIN) {
938 #ifdef DEBUG
939 			warn("Have_inp is wrong for %d", pp->p_fd);
940 #endif
941 			return FALSE;
942 		}
943 		logit(LOG_INFO, "read");
944 	}
945 	if (ret > 0) {
946 		/* Got some data */
947 		pp->p_nchar = ret;
948 	} else {
949 		/* Connection was lost/closed: */
950 		pp->p_cbuf[0] = 'q';
951 		pp->p_nchar = 1;
952 	}
953 	/* Reset pointer into read buffer */
954 	pp->p_ncount = 0;
955 	return TRUE;
956 }
957 
958 /*
959  * cleanup:
960  *	Exit with the given value, cleaning up any droppings lying around
961  */
962 void
cleanup(int eval)963 cleanup(int eval)
964 {
965 	PLAYER	*pp;
966 
967 	/* Place their cursor in a friendly position: */
968 	cgoto(ALL_PLAYERS, HEIGHT, 0);
969 
970 	/* Send them all the ENDWIN command: */
971 	sendcom(ALL_PLAYERS, ENDWIN, LAST_PLAYER);
972 
973 	/* And close their connections: */
974 	for (pp = Player; pp < End_player; pp++)
975 		fclose(pp->p_output);
976 	for (pp = Monitor; pp < End_monitor; pp++)
977 		fclose(pp->p_output);
978 
979 	/* Close the server socket: */
980 	close(Socket);
981 
982 	/* The end: */
983 	logx(LOG_INFO, "game over");
984 	exit(eval);
985 }
986 
987 /*
988  * send_stats:
989  *	Accept a connection to the statistics port, and emit
990  *	the stats.
991  */
992 static void
send_stats(void)993 send_stats(void)
994 {
995 	FILE	*fp;
996 	int	s;
997 	struct sockaddr_in	sockstruct;
998 	socklen_t	socklen;
999 	struct request_info ri;
1000 	int	flags;
1001 
1002 	/* Accept a connection to the statistics socket: */
1003 	socklen = sizeof sockstruct;
1004 	s = accept(Status, (struct sockaddr *) &sockstruct, &socklen);
1005 	if (s < 0) {
1006 		if (errno == EINTR)
1007 			return;
1008 		logx(LOG_ERR, "accept");
1009 		return;
1010 	}
1011 
1012 	/* Check for access permissions: */
1013 	request_init(&ri, RQ_DAEMON, "huntd", RQ_FILE, s, 0);
1014 	fromhost(&ri);
1015 	if (hosts_access(&ri) == 0) {
1016 		logx(LOG_INFO, "rejected connection from %s", eval_client(&ri));
1017 		close(s);
1018 		return;
1019 	}
1020 
1021 	/* Don't allow the writes to block: */
1022 	flags = fcntl(s, F_GETFL, 0);
1023 	flags |= O_NDELAY;
1024 	fcntl(s, F_SETFL, flags);
1025 
1026 	fp = fdopen(s, "w");
1027 	if (fp == NULL) {
1028 		logit(LOG_ERR, "fdopen");
1029 		close(s);
1030 		return;
1031 	}
1032 
1033 	print_stats(fp);
1034 
1035 	fclose(fp);
1036 }
1037 
1038 /*
1039  * print_stats:
1040  * 	emit the game statistics
1041  */
1042 void
print_stats(FILE * fp)1043 print_stats(FILE *fp)
1044 {
1045 	IDENT	*ip;
1046 	PLAYER  *pp;
1047 
1048 	/* Send the statistics as raw text down the socket: */
1049 	fputs("Name\t\tScore\tDucked\tAbsorb\tFaced\tShot\tRobbed\tMissed\tSlimeK\n", fp);
1050 	for (ip = Scores; ip != NULL; ip = ip->i_next) {
1051 		fprintf(fp, "%s%c%c%c\t", ip->i_name,
1052 			ip->i_team == ' ' ? ' ' : '[',
1053 			ip->i_team,
1054 			ip->i_team == ' ' ? ' ' : ']'
1055 		);
1056 		if (strlen(ip->i_name) + 3 < 8)
1057 			putc('\t', fp);
1058 		fprintf(fp, "%.2f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
1059 			ip->i_score, ip->i_ducked, ip->i_absorbed,
1060 			ip->i_faced, ip->i_shot, ip->i_robbed,
1061 			ip->i_missed, ip->i_slime);
1062 	}
1063 	fputs("\n\nName\t\tEnemy\tFriend\tDeaths\tStill\tSaved\tConnect\n", fp);
1064 	for (ip = Scores; ip != NULL; ip = ip->i_next) {
1065 		fprintf(fp, "%s%c%c%c\t", ip->i_name,
1066 			ip->i_team == ' ' ? ' ' : '[',
1067 			ip->i_team,
1068 			ip->i_team == ' ' ? ' ' : ']'
1069 		);
1070 		if (strlen(ip->i_name) + 3 < 8)
1071 			putc('\t', fp);
1072 		fprintf(fp, "%d\t%d\t%d\t%d\t%d\t",
1073 			ip->i_gkills, ip->i_bkills, ip->i_deaths,
1074 			ip->i_stillb, ip->i_saved);
1075 		for (pp = Player; pp < End_player; pp++)
1076 			if (pp->p_ident == ip)
1077 				putc('p', fp);
1078 		for (pp = Monitor; pp < End_monitor; pp++)
1079 			if (pp->p_ident == ip)
1080 				putc('m', fp);
1081 		putc('\n', fp);
1082 	}
1083 }
1084 
1085 
1086 /*
1087  * Send the game statistics to the controlling tty
1088  */
1089 static void
siginfo(int sig __unused)1090 siginfo(int sig __unused)
1091 {
1092 	int tty;
1093 	FILE *fp;
1094 
1095 	if ((tty = open(_PATH_TTY, O_WRONLY)) >= 0) {
1096 		fp = fdopen(tty, "w");
1097 		print_stats(fp);
1098 		answer_info(fp);
1099 		fclose(fp);
1100 	}
1101 }
1102 
1103 /*
1104  * clear_scores:
1105  *	Clear the Scores list.
1106  */
1107 static void
clear_scores(void)1108 clear_scores(void)
1109 {
1110 	IDENT	*ip, *nextip;
1111 
1112 	/* Release the list of scores: */
1113 	for (ip = Scores; ip != NULL; ip = nextip) {
1114 		nextip = ip->i_next;
1115 		free((char *) ip);
1116 	}
1117 	Scores = NULL;
1118 }
1119 
1120 /*
1121  * announce_game:
1122  *	Publically announce the game
1123  */
1124 static void
announce_game(void)1125 announce_game(void)
1126 {
1127 
1128 	/* TODO: could use system() to do something user-configurable */
1129 }
1130 
1131 /*
1132  * Handle a UDP packet sent to the well known port.
1133  */
1134 static void
handle_wkport(int fd)1135 handle_wkport(int fd)
1136 {
1137 	struct sockaddr		fromaddr;
1138 	socklen_t		fromlen;
1139 	u_int16_t		query;
1140 	u_int16_t		response;
1141 	struct request_info	ri;
1142 
1143 	request_init(&ri, RQ_DAEMON, "huntd", RQ_FILE, fd, 0);
1144 	fromhost(&ri);
1145 	fromlen = sizeof fromaddr;
1146 	if (recvfrom(fd, &query, sizeof query, 0, &fromaddr, &fromlen) == -1)
1147 	{
1148 		logit(LOG_WARNING, "recvfrom");
1149 		return;
1150 	}
1151 
1152 #ifdef DEBUG
1153 	fprintf(stderr, "query %d (%s) from %s:%d\n", query,
1154 		query == C_MESSAGE ? "C_MESSAGE" :
1155 		query == C_SCORES ? "C_SCORES" :
1156 		query == C_PLAYER ? "C_PLAYER" :
1157 		query == C_MONITOR ? "C_MONITOR" : "?",
1158 		inet_ntoa(((struct sockaddr_in *)&fromaddr)->sin_addr),
1159 		ntohs(((struct sockaddr_in *)&fromaddr)->sin_port));
1160 #endif
1161 
1162 	/* Do we allow access? */
1163 	if (hosts_access(&ri) == 0) {
1164 		logx(LOG_INFO, "rejected connection from %s", eval_client(&ri));
1165 		return;
1166 	}
1167 
1168 	query = ntohs(query);
1169 
1170 	switch (query) {
1171 	  case C_MESSAGE:
1172 		if (Nplayer <= 0)
1173 			/* Don't bother replying if nobody to talk to: */
1174 			return;
1175 		/* Return the number of people playing: */
1176 		response = Nplayer;
1177 		break;
1178 	  case C_SCORES:
1179 		/* Someone wants the statistics port: */
1180 		response = stat_port;
1181 		break;
1182 	  case C_PLAYER:
1183 	  case C_MONITOR:
1184 		/* Someone wants to play or watch: */
1185 		if (query == C_MONITOR && Nplayer <= 0)
1186 			/* Don't bother replying if there's nothing to watch: */
1187 			return;
1188 		/* Otherwise, tell them how to get to the game: */
1189 		response = sock_port;
1190 		break;
1191 	  default:
1192 		logit(LOG_INFO, "unknown udp query %d", query);
1193 		return;
1194 	}
1195 
1196 	response = ntohs(response);
1197 	if (sendto(fd, &response, sizeof response, 0,
1198 	    &fromaddr, sizeof fromaddr) == -1)
1199 		logit(LOG_WARNING, "sendto");
1200 }
1201