driver.c revision 1.25
1/*	$OpenBSD: driver.c,v 1.25 2016/01/07 21:29:31 mestre Exp $	*/
2/*	$NetBSD: driver.c,v 1.5 1997/10/20 00:37:16 lukem Exp $	*/
3/*
4 * Copyright (c) 1983-2003, Regents of the University of California.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are
9 * met:
10 *
11 * + Redistributions of source code must retain the above copyright
12 *   notice, this list of conditions and the following disclaimer.
13 * + Redistributions in binary form must reproduce the above copyright
14 *   notice, this list of conditions and the following disclaimer in the
15 *   documentation and/or other materials provided with the distribution.
16 * + Neither the name of the University of California, San Francisco nor
17 *   the names of its contributors may be used to endorse or promote
18 *   products derived from this software without specific prior written
19 *   permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34#include <sys/stat.h>
35
36#include <arpa/inet.h>
37
38#include <err.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <netdb.h>
42#include <paths.h>
43#include <signal.h>
44#include <stdlib.h>
45#include <string.h>
46#include <syslog.h>
47#include <unistd.h>
48
49#include "conf.h"
50#include "hunt.h"
51#include "server.h"
52
53u_int16_t Server_port;
54int	Server_socket;		/* test socket to answer datagrams */
55FLAG	should_announce = TRUE;	/* true if listening on standard port */
56u_short	sock_port;		/* port # of tcp listen socket */
57u_short	stat_port;		/* port # of statistics tcp socket */
58in_addr_t Server_addr = INADDR_ANY;	/* address to bind to */
59
60static	void	clear_scores(void);
61static	int	havechar(PLAYER *);
62static	void	init(int);
63	int	main(int, char *[]);
64static	void	makeboots(void);
65static	void	send_stats(void);
66static	void	zap(PLAYER *, FLAG);
67static  void	announce_game(void);
68static	void	siginfo(int);
69static	void	print_stats(FILE *);
70static	void	handle_wkport(int);
71
72/*
73 * main:
74 *	The main program.
75 */
76int
77main(ac, av)
78	int	ac;
79	char	**av;
80{
81	PLAYER		*pp;
82	int		had_char;
83	static fd_set	read_fds;
84	static FLAG	first = TRUE;
85	static FLAG	server = FALSE;
86	extern int	optind;
87	extern char	*optarg;
88	extern char	*__progname;
89	int		c;
90	static struct timeval	linger = { 0, 0 };
91	static struct timeval	timeout = { 0, 0 }, *to;
92	struct spawn	*sp, *spnext;
93	int		ret;
94	int		nready;
95	int		fd;
96	int		background = 0;
97
98	config();
99
100	while ((c = getopt(ac, av, "bsp:a:D:")) != -1) {
101		switch (c) {
102		  case 'b':
103			background = 1;
104			conf_syslog = 1;
105			conf_logerr = 0;
106			break;
107		  case 's':
108			server = TRUE;
109			break;
110		  case 'p':
111			should_announce = FALSE;
112			Server_port = atoi(optarg);
113			break;
114		  case 'a':
115			if (!inet_aton(optarg, (struct in_addr *)&Server_addr))
116				err(1, "bad interface address: %s", optarg);
117			break;
118		  case 'D':
119			config_arg(optarg);
120			break;
121		  default:
122erred:
123			fprintf(stderr,
124			    "usage: %s [-bs] [-a addr] [-D var=value] "
125			    "[-p port]\n",
126			    __progname);
127			exit(2);
128		}
129	}
130	if (optind < ac)
131		goto erred;
132
133	/* Open syslog: */
134	openlog("huntd", LOG_PID | (conf_logerr && !server? LOG_PERROR : 0),
135		LOG_DAEMON);
136
137	/* Initialise game parameters: */
138	init(background);
139
140again:
141	do {
142		/* First, poll to see if we can get input */
143		do {
144			read_fds = Fds_mask;
145			errno = 0;
146			timerclear(&timeout);
147			nready = select(Num_fds, &read_fds, NULL, NULL,
148			    &timeout);
149			if (nready < 0 && errno != EINTR) {
150				logit(LOG_ERR, "select");
151				cleanup(1);
152			}
153		} while (nready < 0);
154
155		if (nready == 0) {
156			/*
157			 * Nothing was ready. We do some work now
158			 * to see if the simulation has any pending work
159			 * to do, and decide if we need to block
160			 * indefinitely or just timeout.
161			 */
162			do {
163				if (conf_simstep && can_moveshots()) {
164				/*
165				 * block for a short time before continuing
166				 * with explosions, bullets and whatnot
167				 */
168					to = &timeout;
169					to->tv_sec =  conf_simstep / 1000000;
170					to->tv_usec = conf_simstep % 1000000;
171				} else
172				/*
173				 * since there's nothing going on,
174				 * just block waiting for external activity
175				 */
176					to = NULL;
177
178				read_fds = Fds_mask;
179				errno = 0;
180				nready = select(Num_fds, &read_fds, NULL, NULL,
181				    to);
182				if (nready < 0 && errno != EINTR) {
183					logit(LOG_ERR, "select");
184					cleanup(1);
185				}
186			} while (nready < 0);
187		}
188
189		/* Remember which descriptors are active: */
190		Have_inp = read_fds;
191
192		/* Answer new player connections: */
193		if (FD_ISSET(Socket, &Have_inp))
194			answer_first();
195
196		/* Continue answering new player connections: */
197		for (sp = Spawn; sp; ) {
198			spnext = sp->next;
199			fd = sp->fd;
200			if (FD_ISSET(fd, &Have_inp) && answer_next(sp)) {
201				/*
202				 * Remove from the spawn list. (fd remains in
203				 * read set).
204				 */
205				*sp->prevnext = sp->next;
206				if (sp->next)
207					sp->next->prevnext = sp->prevnext;
208				free(sp);
209
210				/* We probably consumed all data. */
211				FD_CLR(fd, &Have_inp);
212
213				/* Announce game if this is the first spawn. */
214				if (first && should_announce)
215					announce_game();
216				first = FALSE;
217			}
218			sp = spnext;
219		}
220
221		/* Process input and move bullets until we've exhausted input */
222		had_char = TRUE;
223		while (had_char) {
224
225			moveshots();
226			for (pp = Player; pp < End_player; )
227				if (pp->p_death[0] != '\0')
228					zap(pp, TRUE);
229				else
230					pp++;
231			for (pp = Monitor; pp < End_monitor; )
232				if (pp->p_death[0] != '\0')
233					zap(pp, FALSE);
234				else
235					pp++;
236
237			had_char = FALSE;
238			for (pp = Player; pp < End_player; pp++)
239				if (havechar(pp)) {
240					execute(pp);
241					pp->p_nexec++;
242					had_char = TRUE;
243				}
244			for (pp = Monitor; pp < End_monitor; pp++)
245				if (havechar(pp)) {
246					mon_execute(pp);
247					pp->p_nexec++;
248					had_char = TRUE;
249				}
250		}
251
252		/* Handle a datagram sent to the server socket: */
253		if (FD_ISSET(Server_socket, &Have_inp))
254			handle_wkport(Server_socket);
255
256		/* Answer statistics connections: */
257		if (FD_ISSET(Status, &Have_inp))
258			send_stats();
259
260		/* Flush/synchronize all the displays: */
261		for (pp = Player; pp < End_player; pp++) {
262			if (FD_ISSET(pp->p_fd, &read_fds)) {
263				sendcom(pp, READY, pp->p_nexec);
264				pp->p_nexec = 0;
265			}
266			flush(pp);
267		}
268		for (pp = Monitor; pp < End_monitor; pp++) {
269			if (FD_ISSET(pp->p_fd, &read_fds)) {
270				sendcom(pp, READY, pp->p_nexec);
271				pp->p_nexec = 0;
272			}
273			flush(pp);
274		}
275	} while (Nplayer > 0);
276
277	/* No more players! */
278
279	/* No players yet or a continuous game? */
280	if (first || conf_linger < 0)
281		goto again;
282
283	/* Wait a short while for one to come back: */
284	read_fds = Fds_mask;
285	linger.tv_sec = conf_linger;
286	while ((ret = select(Num_fds, &read_fds, NULL, NULL, &linger)) < 0) {
287		if (errno != EINTR) {
288			logit(LOG_WARNING, "select");
289			break;
290		}
291		read_fds = Fds_mask;
292		linger.tv_sec = conf_linger;
293		linger.tv_usec = 0;
294	}
295	if (ret > 0)
296		/* Someone returned! Resume the game: */
297		goto again;
298	/* else, it timed out, and the game is really over. */
299
300	/* If we are an inetd server, we should re-init the map and restart: */
301	if (server) {
302		clear_scores();
303		makemaze();
304		clearwalls();
305		makeboots();
306		first = TRUE;
307		goto again;
308	}
309
310	/* Get rid of any attached monitors: */
311	for (pp = Monitor; pp < End_monitor; )
312		zap(pp, FALSE);
313
314	/* Fin: */
315	cleanup(0);
316	exit(0);
317}
318
319/*
320 * init:
321 *	Initialize the global parameters.
322 */
323static void
324init(int background)
325{
326	int	i;
327	struct sockaddr_in	test_port;
328	int	true = 1;
329	socklen_t	len;
330	struct sockaddr_in	addr;
331	struct sigaction	sact;
332	struct servent *se;
333
334	sact.sa_flags = SA_RESTART;
335	sigemptyset(&sact.sa_mask);
336
337	/* Ignore HUP, QUIT and PIPE: */
338	sact.sa_handler = SIG_IGN;
339	if (sigaction(SIGHUP, &sact, NULL) == -1)
340		err(1, "sigaction SIGHUP");
341	if (sigaction(SIGQUIT, &sact, NULL) == -1)
342		err(1, "sigaction SIGQUIT");
343	if (sigaction(SIGPIPE, &sact, NULL) == -1)
344		err(1, "sigaction SIGPIPE");
345
346	/* Clean up gracefully on INT and TERM: */
347	sact.sa_handler = cleanup;
348	if (sigaction(SIGINT, &sact, NULL) == -1)
349		err(1, "sigaction SIGINT");
350	if (sigaction(SIGTERM, &sact, NULL) == -1)
351		err(1, "sigaction SIGTERM");
352
353	/* Handle INFO: */
354	sact.sa_handler = siginfo;
355	if (sigaction(SIGINFO, &sact, NULL) == -1)
356		err(1, "sigaction SIGINFO");
357
358	if (chdir("/") == -1)
359		warn("chdir");
360	(void) umask(0777);
361
362	/* Initialize statistics socket: */
363	addr.sin_family = AF_INET;
364	addr.sin_addr.s_addr = Server_addr;
365	addr.sin_port = 0;
366
367	Status = socket(AF_INET, SOCK_STREAM, 0);
368	if (bind(Status, (struct sockaddr *) &addr, sizeof addr) < 0) {
369		logit(LOG_ERR, "bind");
370		cleanup(1);
371	}
372	if (listen(Status, 5) == -1) {
373		logit(LOG_ERR, "listen");
374		cleanup(1);
375	}
376
377	len = sizeof (struct sockaddr_in);
378	if (getsockname(Status, (struct sockaddr *) &addr, &len) < 0)  {
379		logit(LOG_ERR, "getsockname");
380		cleanup(1);
381	}
382	stat_port = ntohs(addr.sin_port);
383
384	/* Initialize main socket: */
385	addr.sin_family = AF_INET;
386	addr.sin_addr.s_addr = Server_addr;
387	addr.sin_port = 0;
388
389	Socket = socket(AF_INET, SOCK_STREAM, 0);
390
391	if (bind(Socket, (struct sockaddr *) &addr, sizeof addr) < 0) {
392		logit(LOG_ERR, "bind");
393		cleanup(1);
394	}
395	if (listen(Socket, 5) == -1) {
396		logit(LOG_ERR, "listen");
397		cleanup(1);
398	}
399
400	len = sizeof (struct sockaddr_in);
401	if (getsockname(Socket, (struct sockaddr *) &addr, &len) < 0)  {
402		logit(LOG_ERR, "getsockname");
403		cleanup(1);
404	}
405	sock_port = ntohs(addr.sin_port);
406
407	/* Initialize minimal select mask */
408	FD_ZERO(&Fds_mask);
409	FD_SET(Socket, &Fds_mask);
410	FD_SET(Status, &Fds_mask);
411	Num_fds = ((Socket > Status) ? Socket : Status) + 1;
412
413	/* Find the port that huntd should run on */
414	if (Server_port == 0) {
415		se = getservbyname("hunt", "udp");
416		if (se != NULL)
417			Server_port = ntohs(se->s_port);
418		else
419			Server_port = HUNT_PORT;
420	}
421
422	/* Check if stdin is a socket: */
423	len = sizeof (struct sockaddr_in);
424	if (getsockname(STDIN_FILENO, (struct sockaddr *) &test_port, &len) >= 0
425	    && test_port.sin_family == AF_INET) {
426		/* We are probably running from inetd:  don't log to stderr */
427		Server_socket = STDIN_FILENO;
428		conf_logerr = 0;
429		if (test_port.sin_port != htons((u_short) Server_port)) {
430			/* Private game */
431			should_announce = FALSE;
432			Server_port = ntohs(test_port.sin_port);
433		}
434	} else {
435		/* We need to listen on a socket: */
436		test_port = addr;
437		test_port.sin_port = htons((u_short) Server_port);
438
439		Server_socket = socket(AF_INET, SOCK_DGRAM, 0);
440
441		/* Permit multiple huntd's on the same port. */
442		if (setsockopt(Server_socket, SOL_SOCKET, SO_REUSEPORT, &true,
443		    sizeof true) < 0)
444			logit(LOG_ERR, "setsockopt SO_REUSEADDR");
445
446		if (bind(Server_socket, (struct sockaddr *) &test_port,
447		    sizeof test_port) < 0) {
448			logit(LOG_ERR, "bind port %d", Server_port);
449			cleanup(1);
450		}
451
452		/* Become a daemon if asked to do so. */
453		if (background)
454			daemon(0, 0);
455
456		/* Datagram sockets do not need a listen() call. */
457	}
458
459	/* We'll handle the broadcast listener in the main loop: */
460	FD_SET(Server_socket, &Fds_mask);
461	if (Server_socket + 1 > Num_fds)
462		Num_fds = Server_socket + 1;
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[stype])
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	return (arc4random_uniform(range));
912}
913
914/*
915 * havechar:
916 *	Check to see if we have any characters in the input queue; if
917 *	we do, read them, stash them away, and return TRUE; else return
918 *	FALSE.
919 */
920static int
921havechar(pp)
922	PLAYER	*pp;
923{
924	int ret;
925
926	/* Do we already have characters? */
927	if (pp->p_ncount < pp->p_nchar)
928		return TRUE;
929	/* Ignore if nothing to read. */
930	if (!FD_ISSET(pp->p_fd, &Have_inp))
931		return FALSE;
932	/* Remove the player from the read set until we have drained them: */
933	FD_CLR(pp->p_fd, &Have_inp);
934
935	/* Suck their keypresses into a buffer: */
936check_again:
937	errno = 0;
938	ret = read(pp->p_fd, pp->p_cbuf, sizeof pp->p_cbuf);
939	if (ret == -1) {
940		if (errno == EINTR)
941			goto check_again;
942		if (errno == EAGAIN) {
943#ifdef DEBUG
944			warn("Have_inp is wrong for %d", pp->p_fd);
945#endif
946			return FALSE;
947		}
948		logit(LOG_INFO, "read");
949	}
950	if (ret > 0) {
951		/* Got some data */
952		pp->p_nchar = ret;
953	} else {
954		/* Connection was lost/closed: */
955		pp->p_cbuf[0] = 'q';
956		pp->p_nchar = 1;
957	}
958	/* Reset pointer into read buffer */
959	pp->p_ncount = 0;
960	return TRUE;
961}
962
963/*
964 * cleanup:
965 *	Exit with the given value, cleaning up any droppings lying around
966 */
967void
968cleanup(eval)
969	int	eval;
970{
971	PLAYER	*pp;
972
973	/* Place their cursor in a friendly position: */
974	cgoto(ALL_PLAYERS, HEIGHT, 0);
975
976	/* Send them all the ENDWIN command: */
977	sendcom(ALL_PLAYERS, ENDWIN, LAST_PLAYER);
978
979	/* And close their connections: */
980	for (pp = Player; pp < End_player; pp++)
981		(void) fclose(pp->p_output);
982	for (pp = Monitor; pp < End_monitor; pp++)
983		(void) fclose(pp->p_output);
984
985	/* Close the server socket: */
986	(void) close(Socket);
987
988	/* The end: */
989	logx(LOG_INFO, "game over");
990	exit(eval);
991}
992
993/*
994 * send_stats:
995 *	Accept a connection to the statistics port, and emit
996 *	the stats.
997 */
998static void
999send_stats()
1000{
1001	FILE	*fp;
1002	int	s;
1003	struct sockaddr_in	sockstruct;
1004	socklen_t	socklen;
1005	int	flags;
1006
1007	/* Accept a connection to the statistics socket: */
1008	socklen = sizeof sockstruct;
1009	s = accept(Status, (struct sockaddr *) &sockstruct, &socklen);
1010	if (s < 0) {
1011		if (errno == EINTR)
1012			return;
1013		logx(LOG_ERR, "accept");
1014		return;
1015	}
1016
1017	/* Don't allow the writes to block: */
1018	flags = fcntl(s, F_GETFL, 0);
1019	flags |= O_NDELAY;
1020	(void) fcntl(s, F_SETFL, flags);
1021
1022	fp = fdopen(s, "w");
1023	if (fp == NULL) {
1024		logit(LOG_ERR, "fdopen");
1025		(void) close(s);
1026		return;
1027	}
1028
1029	print_stats(fp);
1030
1031	(void) fclose(fp);
1032}
1033
1034/*
1035 * print_stats:
1036 * 	emit the game statistics
1037 */
1038void
1039print_stats(fp)
1040	FILE *fp;
1041{
1042	IDENT	*ip;
1043	PLAYER  *pp;
1044
1045	/* Send the statistics as raw text down the socket: */
1046	fputs("Name\t\tScore\tDucked\tAbsorb\tFaced\tShot\tRobbed\tMissed\tSlimeK\n", fp);
1047	for (ip = Scores; ip != NULL; ip = ip->i_next) {
1048		fprintf(fp, "%s%c%c%c\t", ip->i_name,
1049			ip->i_team == ' ' ? ' ' : '[',
1050			ip->i_team,
1051			ip->i_team == ' ' ? ' ' : ']'
1052		);
1053		if (strlen(ip->i_name) + 3 < 8)
1054			putc('\t', fp);
1055		fprintf(fp, "%.2f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
1056			ip->i_score, ip->i_ducked, ip->i_absorbed,
1057			ip->i_faced, ip->i_shot, ip->i_robbed,
1058			ip->i_missed, ip->i_slime);
1059	}
1060	fputs("\n\nName\t\tEnemy\tFriend\tDeaths\tStill\tSaved\tConnect\n", fp);
1061	for (ip = Scores; ip != NULL; ip = ip->i_next) {
1062		fprintf(fp, "%s%c%c%c\t", ip->i_name,
1063			ip->i_team == ' ' ? ' ' : '[',
1064			ip->i_team,
1065			ip->i_team == ' ' ? ' ' : ']'
1066		);
1067		if (strlen(ip->i_name) + 3 < 8)
1068			putc('\t', fp);
1069		fprintf(fp, "%d\t%d\t%d\t%d\t%d\t",
1070			ip->i_gkills, ip->i_bkills, ip->i_deaths,
1071			ip->i_stillb, ip->i_saved);
1072		for (pp = Player; pp < End_player; pp++)
1073			if (pp->p_ident == ip)
1074				putc('p', fp);
1075		for (pp = Monitor; pp < End_monitor; pp++)
1076			if (pp->p_ident == ip)
1077				putc('m', fp);
1078		putc('\n', fp);
1079	}
1080}
1081
1082
1083/*
1084 * Send the game statistics to the controlling tty
1085 */
1086static void
1087siginfo(sig)
1088	int sig;
1089{
1090	int tty;
1091	FILE *fp;
1092
1093	if ((tty = open(_PATH_TTY, O_WRONLY)) >= 0) {
1094		fp = fdopen(tty, "w");
1095		print_stats(fp);
1096		answer_info(fp);
1097		fclose(fp);
1098	}
1099}
1100
1101/*
1102 * clear_scores:
1103 *	Clear the Scores list.
1104 */
1105static void
1106clear_scores()
1107{
1108	IDENT	*ip, *nextip;
1109
1110	/* Release the list of scores: */
1111	for (ip = Scores; ip != NULL; ip = nextip) {
1112		nextip = ip->i_next;
1113		free((char *) ip);
1114	}
1115	Scores = NULL;
1116}
1117
1118/*
1119 * announce_game:
1120 *	Publically announce the game
1121 */
1122static void
1123announce_game()
1124{
1125
1126	/* TODO: could use system() to do something user-configurable */
1127}
1128
1129/*
1130 * Handle a UDP packet sent to the well known port.
1131 */
1132static void
1133handle_wkport(fd)
1134	int fd;
1135{
1136	struct sockaddr		fromaddr;
1137	socklen_t		fromlen;
1138	u_int16_t		query;
1139	u_int16_t		response;
1140
1141	fromlen = sizeof fromaddr;
1142	if (recvfrom(fd, &query, sizeof query, 0, &fromaddr, &fromlen) == -1)
1143	{
1144		logit(LOG_WARNING, "recvfrom");
1145		return;
1146	}
1147
1148#ifdef DEBUG
1149	fprintf(stderr, "query %d (%s) from %s:%d\n", query,
1150		query == C_MESSAGE ? "C_MESSAGE" :
1151		query == C_SCORES ? "C_SCORES" :
1152		query == C_PLAYER ? "C_PLAYER" :
1153		query == C_MONITOR ? "C_MONITOR" : "?",
1154		inet_ntoa(((struct sockaddr_in *)&fromaddr)->sin_addr),
1155		ntohs(((struct sockaddr_in *)&fromaddr)->sin_port));
1156#endif
1157
1158	query = ntohs(query);
1159
1160	switch (query) {
1161	  case C_MESSAGE:
1162		if (Nplayer <= 0)
1163			/* Don't bother replying if nobody to talk to: */
1164			return;
1165		/* Return the number of people playing: */
1166		response = Nplayer;
1167		break;
1168	  case C_SCORES:
1169		/* Someone wants the statistics port: */
1170		response = stat_port;
1171		break;
1172	  case C_PLAYER:
1173	  case C_MONITOR:
1174		/* Someone wants to play or watch: */
1175		if (query == C_MONITOR && Nplayer <= 0)
1176			/* Don't bother replying if there's nothing to watch: */
1177			return;
1178		/* Otherwise, tell them how to get to the game: */
1179		response = sock_port;
1180		break;
1181	  default:
1182		logit(LOG_INFO, "unknown udp query %d", query);
1183		return;
1184	}
1185
1186	response = ntohs(response);
1187	if (sendto(fd, &response, sizeof response, 0,
1188	    &fromaddr, sizeof fromaddr) == -1)
1189		logit(LOG_WARNING, "sendto");
1190}
1191