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