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