2 * Copyright (c) 1983-2003, Regents of the University of California.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * + Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * + 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 * + 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
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.
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 * $DragonFly: src/games/hunt/huntd/driver.c,v 1.1 2008/09/02 21:50:21 dillon Exp $
36 #include <sys/ioctl.h>
39 #include <sys/socket.h>
41 #include <netinet/in.h>
42 #include <arpa/inet.h>
60 char *First_arg; /* pointer to argv[0] */
61 u_int16_t Server_port;
62 int Server_socket; /* test socket to answer datagrams */
63 FLAG should_announce = TRUE; /* true if listening on standard port */
64 u_short sock_port; /* port # of tcp listen socket */
65 u_short stat_port; /* port # of statistics tcp socket */
66 in_addr_t Server_addr = INADDR_ANY; /* address to bind to */
68 static void clear_scores(void);
69 static int havechar(PLAYER *);
70 static void init(void);
71 int main(int, char *[]);
72 static void makeboots(void);
73 static void send_stats(void);
74 static void zap(PLAYER *, FLAG);
75 static void announce_game(void);
76 static void siginfo(int);
77 static void print_stats(FILE *);
78 static void handle_wkport(int);
91 static fd_set read_fds;
92 static FLAG first = TRUE;
93 static FLAG server = FALSE;
95 static struct timeval linger = { 0, 0 };
96 static struct timeval timeout = { 0, 0 }, *to;
97 struct spawn *sp, *spnext;
106 while ((c = getopt(ac, av, "sp:a:D:")) != -1) {
112 should_announce = FALSE;
113 Server_port = atoi(optarg);
116 if (!inet_aton(optarg, (struct in_addr *)&Server_addr))
117 err(1, "bad interface address: %s", optarg);
125 "usage: %s [-s] [-a addr] [-Dvar=value ...] "
135 openlog("huntd", LOG_PID | (conf_logerr && !server? LOG_PERROR : 0),
138 /* Initialise game parameters: */
143 /* First, poll to see if we can get input */
147 timerclear(&timeout);
148 nready = select(Num_fds, &read_fds, NULL, NULL,
150 if (nready < 0 && errno != EINTR) {
151 logit(LOG_ERR, "select");
154 } while (nready < 0);
158 * Nothing was ready. We do some work now
159 * to see if the simulation has any pending work
160 * to do, and decide if we need to block
161 * indefinitely or just timeout.
164 if (conf_simstep && can_moveshots()) {
166 * block for a short time before continuing
167 * with explosions, bullets and whatnot
170 to->tv_sec = conf_simstep / 1000000;
171 to->tv_usec = conf_simstep % 1000000;
174 * since there's nothing going on,
175 * just block waiting for external activity
181 nready = select(Num_fds, &read_fds, NULL, NULL,
183 if (nready < 0 && errno != EINTR) {
184 logit(LOG_ERR, "select");
187 } while (nready < 0);
190 /* Remember which descriptors are active: */
193 /* Answer new player connections: */
194 if (FD_ISSET(Socket, &Have_inp))
197 /* Continue answering new player connections: */
198 for (sp = Spawn; sp; ) {
201 if (FD_ISSET(fd, &Have_inp) && answer_next(sp)) {
203 * Remove from the spawn list. (fd remains in
206 *sp->prevnext = sp->next;
208 sp->next->prevnext = sp->prevnext;
211 /* We probably consumed all data. */
212 FD_CLR(fd, &Have_inp);
214 /* Announce game if this is the first spawn. */
215 if (first && should_announce)
222 /* Process input and move bullets until we've exhausted input */
227 for (pp = Player; pp < End_player; )
228 if (pp->p_death[0] != '\0')
232 for (pp = Monitor; pp < End_monitor; )
233 if (pp->p_death[0] != '\0')
239 for (pp = Player; pp < End_player; pp++)
245 for (pp = Monitor; pp < End_monitor; pp++)
253 /* Handle a datagram sent to the server socket: */
254 if (FD_ISSET(Server_socket, &Have_inp))
255 handle_wkport(Server_socket);
257 /* Answer statistics connections: */
258 if (FD_ISSET(Status, &Have_inp))
261 /* Flush/synchronize all the displays: */
262 for (pp = Player; pp < End_player; pp++) {
263 if (FD_ISSET(pp->p_fd, &read_fds)) {
264 sendcom(pp, READY, pp->p_nexec);
269 for (pp = Monitor; pp < End_monitor; pp++) {
270 if (FD_ISSET(pp->p_fd, &read_fds)) {
271 sendcom(pp, READY, pp->p_nexec);
276 } while (Nplayer > 0);
278 /* No more players! */
280 /* No players yet or a continuous game? */
281 if (first || conf_linger < 0)
284 /* Wait a short while for one to come back: */
286 linger.tv_sec = conf_linger;
287 while ((ret = select(Num_fds, &read_fds, NULL, NULL, &linger)) < 0) {
288 if (errno != EINTR) {
289 logit(LOG_WARNING, "select");
293 linger.tv_sec = conf_linger;
297 /* Someone returned! Resume the game: */
299 /* else, it timed out, and the game is really over. */
301 /* If we are an inetd server, we should re-init the map and restart: */
311 /* Get rid of any attached monitors: */
312 for (pp = Monitor; pp < End_monitor; )
322 * Initialize the global parameters.
328 struct sockaddr_in test_port;
331 struct sockaddr_in addr;
332 struct sigaction sact;
336 if (setpgid(getpid(), getpid()) == -1)
339 sact.sa_flags = SA_RESTART;
340 sigemptyset(&sact.sa_mask);
342 /* Ignore HUP, QUIT and PIPE: */
343 sact.sa_handler = SIG_IGN;
344 if (sigaction(SIGHUP, &sact, NULL) == -1)
345 err(1, "sigaction SIGHUP");
346 if (sigaction(SIGQUIT, &sact, NULL) == -1)
347 err(1, "sigaction SIGQUIT");
348 if (sigaction(SIGPIPE, &sact, NULL) == -1)
349 err(1, "sigaction SIGPIPE");
351 /* Clean up gracefully on INT and TERM: */
352 sact.sa_handler = cleanup;
353 if (sigaction(SIGINT, &sact, NULL) == -1)
354 err(1, "sigaction SIGINT");
355 if (sigaction(SIGTERM, &sact, NULL) == -1)
356 err(1, "sigaction SIGTERM");
359 sact.sa_handler = siginfo;
360 if (sigaction(SIGINFO, &sact, NULL) == -1)
361 err(1, "sigaction SIGINFO");
363 if (chdir("/") == -1)
367 /* Initialize statistics socket: */
368 addr.sin_family = AF_INET;
369 addr.sin_addr.s_addr = Server_addr;
372 Status = socket(AF_INET, SOCK_STREAM, 0);
373 if (bind(Status, (struct sockaddr *) &addr, sizeof addr) < 0) {
374 logit(LOG_ERR, "bind");
377 if (listen(Status, 5) == -1) {
378 logit(LOG_ERR, "listen");
382 len = sizeof (struct sockaddr_in);
383 if (getsockname(Status, (struct sockaddr *) &addr, &len) < 0) {
384 logit(LOG_ERR, "getsockname");
387 stat_port = ntohs(addr.sin_port);
389 /* Initialize main socket: */
390 addr.sin_family = AF_INET;
391 addr.sin_addr.s_addr = Server_addr;
394 Socket = socket(AF_INET, SOCK_STREAM, 0);
396 if (bind(Socket, (struct sockaddr *) &addr, sizeof addr) < 0) {
397 logit(LOG_ERR, "bind");
400 if (listen(Socket, 5) == -1) {
401 logit(LOG_ERR, "listen");
405 len = sizeof (struct sockaddr_in);
406 if (getsockname(Socket, (struct sockaddr *) &addr, &len) < 0) {
407 logit(LOG_ERR, "getsockname");
410 sock_port = ntohs(addr.sin_port);
412 /* Initialize minimal select mask */
414 FD_SET(Socket, &Fds_mask);
415 FD_SET(Status, &Fds_mask);
416 Num_fds = ((Socket > Status) ? Socket : Status) + 1;
418 /* Find the port that huntd should run on */
419 if (Server_port == 0) {
420 se = getservbyname("hunt", "udp");
422 Server_port = ntohs(se->s_port);
424 Server_port = HUNT_PORT;
427 /* Check if stdin is a socket: */
428 len = sizeof (struct sockaddr_in);
429 if (getsockname(STDIN_FILENO, (struct sockaddr *) &test_port, &len) >= 0
430 && test_port.sin_family == AF_INET) {
431 /* We are probably running from inetd: don't log to stderr */
432 Server_socket = STDIN_FILENO;
434 if (test_port.sin_port != htons((u_short) Server_port)) {
436 should_announce = FALSE;
437 Server_port = ntohs(test_port.sin_port);
440 /* We need to listen on a socket: */
442 test_port.sin_port = htons((u_short) Server_port);
444 Server_socket = socket(AF_INET, SOCK_DGRAM, 0);
446 /* Permit multiple huntd's on the same port. */
447 if (setsockopt(Server_socket, SOL_SOCKET, SO_REUSEPORT, &true,
449 logit(LOG_ERR, "setsockopt SO_REUSEADDR");
451 if (bind(Server_socket, (struct sockaddr *) &test_port,
452 sizeof test_port) < 0) {
453 logit(LOG_ERR, "bind port %d", Server_port);
457 /* Datagram sockets do not need a listen() call. */
460 /* We'll handle the broadcast listener in the main loop: */
461 FD_SET(Server_socket, &Fds_mask);
462 if (Server_socket + 1 > Num_fds)
463 Num_fds = Server_socket + 1;
465 /* Initialise the random seed: */
471 /* Create some boots, if needed: */
474 /* Construct a table of what objects a player can see over: */
475 for (i = 0; i < NASCII; i++)
477 See_over[DOOR] = FALSE;
478 See_over[WALL1] = FALSE;
479 See_over[WALL2] = FALSE;
480 See_over[WALL3] = FALSE;
481 See_over[WALL4] = FALSE;
482 See_over[WALL5] = FALSE;
484 logx(LOG_INFO, "game started");
489 * Put the boots in the maze
499 x = rand_num(WIDTH - 1) + 1;
500 y = rand_num(HEIGHT - 1) + 1;
501 } while (Maze[y][x] != SPACE);
502 Maze[y][x] = BOOT_PAIR;
505 for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
512 * Apply damage to the victim from an attacker.
513 * If the victim dies as a result, give points to 'credit',
516 checkdam(victim, attacker, credit, damage, stype)
517 PLAYER *victim, *attacker;
525 /* Don't do anything if the victim is already in the throes of death */
526 if (victim->p_death[0] != '\0')
529 /* Weaken slime attacks by 0.5 * number of boots the victim has on: */
531 switch (victim->p_nboots) {
535 damage = (damage + 1) / 2;
538 if (attacker != NULL)
539 message(attacker, "He has boots on!");
543 /* The victim sustains some damage: */
544 victim->p_damage += damage;
546 /* Check if the victim survives the hit: */
547 if (victim->p_damage <= victim->p_damcap) {
549 outyx(victim, STAT_DAM_ROW, STAT_VALUE_COL, "%2d",
554 /* Describe how the victim died: */
560 cp = "Killed on impact";
563 cp = "Stabbed to death";
564 victim->p_ammo = 0; /* No exploding */
567 cp = "Shot to death";
591 if (credit == NULL) {
595 * Nobody is taking the credit for the kill.
596 * Attribute it to either a mine or 'act of God'.
604 blame = "act of God";
608 /* Set the death message: */
609 (void) snprintf(victim->p_death, sizeof victim->p_death,
610 "| %s by %s |", cp, blame);
612 /* No further score crediting needed. */
616 /* Set the death message: */
617 (void) snprintf(victim->p_death, sizeof victim->p_death,
618 "| %s by %s |", cp, credit->i_name);
620 if (victim == attacker) {
621 /* No use killing yourself. */
625 else if (victim->p_ident->i_team == ' '
626 || victim->p_ident->i_team != credit->i_team) {
627 /* A cross-team kill: */
632 /* They killed someone on the same team: */
637 /* Compute the new credited score: */
638 credit->i_score = credit->i_kills / (double) credit->i_entries;
640 /* The victim accrues one death: */
641 victim->p_ident->i_deaths++;
643 /* Account for 'Stillborn' deaths */
644 if (victim->p_nchar == 0)
645 victim->p_ident->i_stillb++;
648 /* Give the attacker player a bit more strength */
649 attacker->p_damcap += conf_killgain;
650 attacker->p_damage -= conf_killgain;
651 if (attacker->p_damage < 0)
652 attacker->p_damage = 0;
654 /* Tell the attacker his new strength: */
655 outyx(attacker, STAT_DAM_ROW, STAT_VALUE_COL, "%2d/%2d",
656 attacker->p_damage, attacker->p_damcap);
658 /* Tell the attacker his new 'kill count': */
659 outyx(attacker, STAT_KILL_ROW, STAT_VALUE_COL, "%3d",
660 (attacker->p_damcap - conf_maxdam) / 2);
662 /* Update the attacker's score for everyone else */
663 y = STAT_PLAY_ROW + 1 + (attacker - Player);
664 outyx(ALL_PLAYERS, y, STAT_NAME_COL,
665 "%5.2f", attacker->p_ident->i_score);
671 * Kill off a player and take them out of the game.
672 * The 'was_player' flag indicates that the player was not
673 * a monitor and needs extra cleaning up.
687 /* If they died from a shot, clean up shrapnel */
689 fixshots(pp->p_y, pp->p_x, pp->p_over);
690 /* Let the player see their last position: */
691 drawplayer(pp, FALSE);
692 /* Remove from game: */
696 /* Display the cause of death in the centre of the screen: */
697 len = strlen(pp->p_death);
698 x = (WIDTH - len) / 2;
699 outyx(pp, HEIGHT / 2, x, "%s", pp->p_death);
701 /* Put some horizontal lines around and below the death message: */
702 memset(pp->p_death + 1, '-', len - 2);
703 pp->p_death[0] = '+';
704 pp->p_death[len - 1] = '+';
705 outyx(pp, HEIGHT / 2 - 1, x, "%s", pp->p_death);
706 outyx(pp, HEIGHT / 2 + 1, x, "%s", pp->p_death);
708 /* Move to bottom left */
709 cgoto(pp, HEIGHT, 0);
718 /* Check all the bullets: */
719 for (bp = Bullets; bp != NULL; bp = bp->b_next) {
720 if (bp->b_owner == pp)
721 /* Zapped players can't own bullets: */
723 if (bp->b_x == pp->p_x && bp->b_y == pp->p_y)
724 /* Bullets over the player are now over air: */
728 /* Explode a random fraction of the player's ammo: */
729 ammo_exploding = rand_num(pp->p_ammo);
731 /* Determine the type and amount of detonation: */
732 expl_charge = rand_num(ammo_exploding + 1);
734 /* Ignore the no-ammo case: */
736 else if (ammo_exploding >= pp->p_ammo - 1) {
737 /* Maximal explosions always appear as slime: */
738 expl_charge = pp->p_ammo;
742 * Figure out the best effective explosion
743 * type to use, given the amount of charge
746 for (btype = MAXBOMB - 1; btype > 0; btype--)
747 if (expl_charge >= shot_req[btype])
749 for (stype = MAXSLIME - 1; stype > 0; stype--)
750 if (expl_charge >= slime_req[stype])
752 /* Pick the larger of the bomb or slime: */
753 if (btype >= 0 && stype >= 0) {
754 if (shot_req[btype] > slime_req[btype])
758 expl_type = shot_type[btype];
759 expl_charge = shot_req[btype];
764 if (expl_charge > 0) {
768 (void) add_shot(expl_type, pp->p_y, pp->p_x,
769 pp->p_face, expl_charge, (PLAYER *) NULL,
772 /* Explain what the explosion is about. */
773 snprintf(buf, sizeof buf, "%s detonated.",
774 pp->p_ident->i_name);
775 message(ALL_PLAYERS, buf);
777 while (pp->p_nboots-- > 0) {
778 /* Throw one of the boots away: */
779 for (np = Boot; np < &Boot[NBOOTS]; np++)
780 if (np->p_flying < 0)
783 if (np >= &Boot[NBOOTS])
784 err(1, "Too many boots");
786 /* Start the boots from where the player is */
787 np->p_undershot = FALSE;
790 /* Throw for up to 20 steps */
791 np->p_flying = rand_num(20);
792 np->p_flyx = 2 * rand_num(6) - 5;
793 np->p_flyy = 2 * rand_num(6) - 5;
796 showexpl(np->p_y, np->p_x, BOOT);
799 /* No explosion. Leave the player's boots behind. */
800 else if (pp->p_nboots > 0) {
801 if (pp->p_nboots == 2)
802 Maze[pp->p_y][pp->p_x] = BOOT_PAIR;
804 Maze[pp->p_y][pp->p_x] = BOOT;
806 fixshots(pp->p_y, pp->p_x,
807 Maze[pp->p_y][pp->p_x]);
810 /* Any unexploded ammo builds up in the volcano: */
811 volcano += pp->p_ammo - expl_charge;
813 /* Volcano eruption: */
814 if (conf_volcano && rand_num(100) < volcano /
816 /* Erupt near the middle of the map */
818 x = rand_num(WIDTH / 2) + WIDTH / 4;
819 y = rand_num(HEIGHT / 2) + HEIGHT / 4;
820 } while (Maze[y][x] != SPACE);
822 /* Convert volcano charge into lava: */
823 (void) add_shot(LAVA, y, x, LEFTS, volcano,
824 (PLAYER *) NULL, TRUE, SPACE);
827 /* Tell eveyone what's happening */
828 message(ALL_PLAYERS, "Volcano eruption.");
832 if (conf_drone && rand_num(100) < 2) {
833 /* Find a starting place near the middle of the map: */
835 x = rand_num(WIDTH / 2) + WIDTH / 4;
836 y = rand_num(HEIGHT / 2) + HEIGHT / 4;
837 } while (Maze[y][x] != SPACE);
839 /* Start the drone going: */
840 add_shot(DSHOT, y, x, rand_dir(),
841 shot_req[conf_mindshot +
842 rand_num(MAXBOMB - conf_mindshot)],
843 (PLAYER *) NULL, FALSE, SPACE);
846 /* Tell the zapped player's client to shut down. */
847 sendcom(pp, ENDWIN, ' ');
848 (void) fclose(pp->p_output);
850 /* Close up the gap in the Player array: */
852 if (pp != End_player) {
853 /* Move the last player into the gap: */
854 memcpy(pp, End_player, sizeof *pp);
856 STAT_PLAY_ROW + 1 + (pp - Player),
858 "%5.2f%c%-10.10s %c",
859 pp->p_ident->i_score, stat_char(pp),
860 pp->p_ident->i_name, pp->p_ident->i_team);
863 /* Erase the last player from the display: */
864 cgoto(ALL_PLAYERS, STAT_PLAY_ROW + 1 + Nplayer, STAT_NAME_COL);
870 /* Close the session: */
871 sendcom(pp, ENDWIN, LAST_PLAYER);
872 (void) fclose(pp->p_output);
874 /* shuffle the monitor table */
876 if (pp != End_monitor) {
877 memcpy(pp, End_monitor, sizeof *pp);
879 STAT_MON_ROW + 1 + (pp - Player), STAT_NAME_COL,
880 "%5.5s %-10.10s %c", " ",
881 pp->p_ident->i_name, pp->p_ident->i_team);
884 /* Erase the last monitor in the list */
886 STAT_MON_ROW + 1 + (End_monitor - Monitor),
891 /* Update the file descriptor sets used by select: */
892 FD_CLR(savefd, &Fds_mask);
893 if (Num_fds == savefd + 1) {
895 if (Server_socket > Socket)
896 Num_fds = Server_socket;
897 for (np = Player; np < End_player; np++)
898 if (np->p_fd > Num_fds)
900 for (np = Monitor; np < End_monitor; np++)
901 if (np->p_fd > Num_fds)
909 * Return a random number in a given range.
917 return (random() % range);
922 * Check to see if we have any characters in the input queue; if
923 * we do, read them, stash them away, and return TRUE; else return
932 /* Do we already have characters? */
933 if (pp->p_ncount < pp->p_nchar)
935 /* Ignore if nothing to read. */
936 if (!FD_ISSET(pp->p_fd, &Have_inp))
938 /* Remove the player from the read set until we have drained them: */
939 FD_CLR(pp->p_fd, &Have_inp);
941 /* Suck their keypresses into a buffer: */
944 ret = read(pp->p_fd, pp->p_cbuf, sizeof pp->p_cbuf);
948 if (errno == EAGAIN) {
950 warn("Have_inp is wrong for %d", pp->p_fd);
954 logit(LOG_INFO, "read");
960 /* Connection was lost/closed: */
964 /* Reset pointer into read buffer */
971 * Exit with the given value, cleaning up any droppings lying around
979 /* Place their cursor in a friendly position: */
980 cgoto(ALL_PLAYERS, HEIGHT, 0);
982 /* Send them all the ENDWIN command: */
983 sendcom(ALL_PLAYERS, ENDWIN, LAST_PLAYER);
985 /* And close their connections: */
986 for (pp = Player; pp < End_player; pp++)
987 (void) fclose(pp->p_output);
988 for (pp = Monitor; pp < End_monitor; pp++)
989 (void) fclose(pp->p_output);
991 /* Close the server socket: */
992 (void) close(Socket);
995 logx(LOG_INFO, "game over");
1001 * Accept a connection to the statistics port, and emit
1009 struct sockaddr_in sockstruct;
1011 struct request_info ri;
1014 /* Accept a connection to the statistics socket: */
1015 socklen = sizeof sockstruct;
1016 s = accept(Status, (struct sockaddr *) &sockstruct, &socklen);
1020 logx(LOG_ERR, "accept");
1024 /* Check for access permissions: */
1025 request_init(&ri, RQ_DAEMON, "huntd", RQ_FILE, s, 0);
1027 if (hosts_access(&ri) == 0) {
1028 logx(LOG_INFO, "rejected connection from %s", eval_client(&ri));
1033 /* Don't allow the writes to block: */
1034 flags = fcntl(s, F_GETFL, 0);
1036 (void) fcntl(s, F_SETFL, flags);
1038 fp = fdopen(s, "w");
1040 logit(LOG_ERR, "fdopen");
1052 * emit the game statistics
1061 /* Send the statistics as raw text down the socket: */
1062 fputs("Name\t\tScore\tDucked\tAbsorb\tFaced\tShot\tRobbed\tMissed\tSlimeK\n", fp);
1063 for (ip = Scores; ip != NULL; ip = ip->i_next) {
1064 fprintf(fp, "%s%c%c%c\t", ip->i_name,
1065 ip->i_team == ' ' ? ' ' : '[',
1067 ip->i_team == ' ' ? ' ' : ']'
1069 if (strlen(ip->i_name) + 3 < 8)
1071 fprintf(fp, "%.2f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
1072 ip->i_score, ip->i_ducked, ip->i_absorbed,
1073 ip->i_faced, ip->i_shot, ip->i_robbed,
1074 ip->i_missed, ip->i_slime);
1076 fputs("\n\nName\t\tEnemy\tFriend\tDeaths\tStill\tSaved\tConnect\n", fp);
1077 for (ip = Scores; ip != NULL; ip = ip->i_next) {
1078 fprintf(fp, "%s%c%c%c\t", ip->i_name,
1079 ip->i_team == ' ' ? ' ' : '[',
1081 ip->i_team == ' ' ? ' ' : ']'
1083 if (strlen(ip->i_name) + 3 < 8)
1085 fprintf(fp, "%d\t%d\t%d\t%d\t%d\t",
1086 ip->i_gkills, ip->i_bkills, ip->i_deaths,
1087 ip->i_stillb, ip->i_saved);
1088 for (pp = Player; pp < End_player; pp++)
1089 if (pp->p_ident == ip)
1091 for (pp = Monitor; pp < End_monitor; pp++)
1092 if (pp->p_ident == ip)
1100 * Send the game statistics to the controlling tty
1103 siginfo(int sig __unused)
1108 if ((tty = open(_PATH_TTY, O_WRONLY)) >= 0) {
1109 fp = fdopen(tty, "w");
1118 * Clear the Scores list.
1125 /* Release the list of scores: */
1126 for (ip = Scores; ip != NULL; ip = nextip) {
1127 nextip = ip->i_next;
1135 * Publically announce the game
1141 /* TODO: could use system() to do something user-configurable */
1145 * Handle a UDP packet sent to the well known port.
1151 struct sockaddr fromaddr;
1155 struct request_info ri;
1157 request_init(&ri, RQ_DAEMON, "huntd", RQ_FILE, fd, 0);
1159 fromlen = sizeof fromaddr;
1160 if (recvfrom(fd, &query, sizeof query, 0, &fromaddr, &fromlen) == -1)
1162 logit(LOG_WARNING, "recvfrom");
1167 fprintf(stderr, "query %d (%s) from %s:%d\n", query,
1168 query == C_MESSAGE ? "C_MESSAGE" :
1169 query == C_SCORES ? "C_SCORES" :
1170 query == C_PLAYER ? "C_PLAYER" :
1171 query == C_MONITOR ? "C_MONITOR" : "?",
1172 inet_ntoa(((struct sockaddr_in *)&fromaddr)->sin_addr),
1173 ntohs(((struct sockaddr_in *)&fromaddr)->sin_port));
1176 /* Do we allow access? */
1177 if (hosts_access(&ri) == 0) {
1178 logx(LOG_INFO, "rejected connection from %s", eval_client(&ri));
1182 query = ntohs(query);
1187 /* Don't bother replying if nobody to talk to: */
1189 /* Return the number of people playing: */
1193 /* Someone wants the statistics port: */
1194 response = stat_port;
1198 /* Someone wants to play or watch: */
1199 if (query == C_MONITOR && Nplayer <= 0)
1200 /* Don't bother replying if there's nothing to watch: */
1202 /* Otherwise, tell them how to get to the game: */
1203 response = sock_port;
1206 logit(LOG_INFO, "unknown udp query %d", query);
1210 response = ntohs(response);
1211 if (sendto(fd, &response, sizeof response, 0,
1212 &fromaddr, sizeof fromaddr) == -1)
1213 logit(LOG_WARNING, "sendto");