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 * 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
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 $
35 #include <sys/ioctl.h>
38 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
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 */
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);
83 main(int ac
, char **av
)
87 static fd_set read_fds
;
88 static FLAG first
= TRUE
;
89 static FLAG server
= FALSE
;
91 static struct timeval linger
= { 0, 0 };
92 static struct timeval timeout
= { 0, 0 }, *to
;
93 struct spawn
*sp
, *spnext
;
102 while ((c
= getopt(ac
, av
, "sp:a:D:")) != -1) {
108 should_announce
= FALSE
;
109 Server_port
= atoi(optarg
);
112 if (!inet_aton(optarg
, (struct in_addr
*)&Server_addr
))
113 err(1, "bad interface address: %s", optarg
);
121 "usage: %s [-s] [-a addr] [-Dvar=value ...] "
131 openlog("huntd", LOG_PID
| (conf_logerr
&& !server
? LOG_PERROR
: 0),
134 /* Initialise game parameters: */
139 /* First, poll to see if we can get input */
143 timerclear(&timeout
);
144 nready
= select(Num_fds
, &read_fds
, NULL
, NULL
,
146 if (nready
< 0 && errno
!= EINTR
) {
147 logit(LOG_ERR
, "select");
150 } while (nready
< 0);
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.
160 if (conf_simstep
&& can_moveshots()) {
162 * block for a short time before continuing
163 * with explosions, bullets and whatnot
166 to
->tv_sec
= conf_simstep
/ 1000000;
167 to
->tv_usec
= conf_simstep
% 1000000;
170 * since there's nothing going on,
171 * just block waiting for external activity
177 nready
= select(Num_fds
, &read_fds
, NULL
, NULL
,
179 if (nready
< 0 && errno
!= EINTR
) {
180 logit(LOG_ERR
, "select");
183 } while (nready
< 0);
186 /* Remember which descriptors are active: */
189 /* Answer new player connections: */
190 if (FD_ISSET(Socket
, &Have_inp
))
193 /* Continue answering new player connections: */
194 for (sp
= Spawn
; sp
; ) {
197 if (FD_ISSET(fd
, &Have_inp
) && answer_next(sp
)) {
199 * Remove from the spawn list. (fd remains in
202 *sp
->prevnext
= sp
->next
;
204 sp
->next
->prevnext
= sp
->prevnext
;
207 /* We probably consumed all data. */
208 FD_CLR(fd
, &Have_inp
);
210 /* Announce game if this is the first spawn. */
211 if (first
&& should_announce
)
218 /* Process input and move bullets until we've exhausted input */
223 for (pp
= Player
; pp
< End_player
; )
224 if (pp
->p_death
[0] != '\0')
228 for (pp
= Monitor
; pp
< End_monitor
; )
229 if (pp
->p_death
[0] != '\0')
235 for (pp
= Player
; pp
< End_player
; pp
++)
241 for (pp
= Monitor
; pp
< End_monitor
; pp
++)
249 /* Handle a datagram sent to the server socket: */
250 if (FD_ISSET(Server_socket
, &Have_inp
))
251 handle_wkport(Server_socket
);
253 /* Answer statistics connections: */
254 if (FD_ISSET(Status
, &Have_inp
))
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
);
265 for (pp
= Monitor
; pp
< End_monitor
; pp
++) {
266 if (FD_ISSET(pp
->p_fd
, &read_fds
)) {
267 sendcom(pp
, READY
, pp
->p_nexec
);
272 } while (Nplayer
> 0);
274 /* No more players! */
276 /* No players yet or a continuous game? */
277 if (first
|| conf_linger
< 0)
280 /* Wait a short while for one to come back: */
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");
289 linger
.tv_sec
= conf_linger
;
293 /* Someone returned! Resume the game: */
295 /* else, it timed out, and the game is really over. */
297 /* If we are an inetd server, we should re-init the map and restart: */
307 /* Get rid of any attached monitors: */
308 for (pp
= Monitor
; pp
< End_monitor
; )
318 * Initialize the global parameters.
324 struct sockaddr_in test_port
;
327 struct sockaddr_in addr
;
328 struct sigaction sact
;
332 if (setpgid(getpid(), getpid()) == -1)
335 sact
.sa_flags
= SA_RESTART
;
336 sigemptyset(&sact
.sa_mask
);
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");
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");
355 sact
.sa_handler
= siginfo
;
356 if (sigaction(SIGINFO
, &sact
, NULL
) == -1)
357 err(1, "sigaction SIGINFO");
359 if (chdir("/") == -1)
363 /* Initialize statistics socket: */
364 addr
.sin_family
= AF_INET
;
365 addr
.sin_addr
.s_addr
= Server_addr
;
368 Status
= socket(AF_INET
, SOCK_STREAM
, 0);
369 if (bind(Status
, (struct sockaddr
*) &addr
, sizeof addr
) < 0) {
370 logit(LOG_ERR
, "bind");
373 if (listen(Status
, 5) == -1) {
374 logit(LOG_ERR
, "listen");
378 len
= sizeof (struct sockaddr_in
);
379 if (getsockname(Status
, (struct sockaddr
*) &addr
, &len
) < 0) {
380 logit(LOG_ERR
, "getsockname");
383 stat_port
= ntohs(addr
.sin_port
);
385 /* Initialize main socket: */
386 addr
.sin_family
= AF_INET
;
387 addr
.sin_addr
.s_addr
= Server_addr
;
390 Socket
= socket(AF_INET
, SOCK_STREAM
, 0);
392 if (bind(Socket
, (struct sockaddr
*) &addr
, sizeof addr
) < 0) {
393 logit(LOG_ERR
, "bind");
396 if (listen(Socket
, 5) == -1) {
397 logit(LOG_ERR
, "listen");
401 len
= sizeof (struct sockaddr_in
);
402 if (getsockname(Socket
, (struct sockaddr
*) &addr
, &len
) < 0) {
403 logit(LOG_ERR
, "getsockname");
406 sock_port
= ntohs(addr
.sin_port
);
408 /* Initialize minimal select mask */
410 FD_SET(Socket
, &Fds_mask
);
411 FD_SET(Status
, &Fds_mask
);
412 Num_fds
= ((Socket
> Status
) ? Socket
: Status
) + 1;
414 /* Find the port that huntd should run on */
415 if (Server_port
== 0) {
416 se
= getservbyname("hunt", "udp");
418 Server_port
= ntohs(se
->s_port
);
420 Server_port
= HUNT_PORT
;
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
;
430 if (test_port
.sin_port
!= htons((u_short
) Server_port
)) {
432 should_announce
= FALSE
;
433 Server_port
= ntohs(test_port
.sin_port
);
436 /* We need to listen on a socket: */
438 test_port
.sin_port
= htons((u_short
) Server_port
);
440 Server_socket
= socket(AF_INET
, SOCK_DGRAM
, 0);
442 /* Permit multiple huntd's on the same port. */
443 if (setsockopt(Server_socket
, SOL_SOCKET
, SO_REUSEPORT
, &true,
445 logit(LOG_ERR
, "setsockopt SO_REUSEADDR");
447 if (bind(Server_socket
, (struct sockaddr
*) &test_port
,
448 sizeof test_port
) < 0) {
449 logit(LOG_ERR
, "bind port %d", Server_port
);
453 /* Datagram sockets do not need a listen() call. */
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;
461 /* Initialise the random seed: */
467 /* Create some boots, if needed: */
470 /* Construct a table of what objects a player can see over: */
471 for (i
= 0; i
< NASCII
; i
++)
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
;
480 logx(LOG_INFO
, "game started");
485 * Put the boots in the maze
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
;
501 for (pp
= Boot
; pp
< &Boot
[NBOOTS
]; pp
++)
508 * Apply damage to the victim from an attacker.
509 * If the victim dies as a result, give points to 'credit',
512 checkdam(PLAYER
*victim
, PLAYER
*attacker
, IDENT
*credit
, int damage
,
518 /* Don't do anything if the victim is already in the throes of death */
519 if (victim
->p_death
[0] != '\0')
522 /* Weaken slime attacks by 0.5 * number of boots the victim has on: */
524 switch (victim
->p_nboots
) {
528 damage
= (damage
+ 1) / 2;
531 if (attacker
!= NULL
)
532 message(attacker
, "He has boots on!");
536 /* The victim sustains some damage: */
537 victim
->p_damage
+= damage
;
539 /* Check if the victim survives the hit: */
540 if (victim
->p_damage
<= victim
->p_damcap
) {
542 outyx(victim
, STAT_DAM_ROW
, STAT_VALUE_COL
, "%2d",
547 /* Describe how the victim died: */
553 cp
= "Killed on impact";
556 cp
= "Stabbed to death";
557 victim
->p_ammo
= 0; /* No exploding */
560 cp
= "Shot to death";
584 if (credit
== NULL
) {
588 * Nobody is taking the credit for the kill.
589 * Attribute it to either a mine or 'act of God'.
597 blame
= "act of God";
601 /* Set the death message: */
602 snprintf(victim
->p_death
, sizeof victim
->p_death
,
603 "| %s by %s |", cp
, blame
);
605 /* No further score crediting needed. */
609 /* Set the death message: */
610 snprintf(victim
->p_death
, sizeof victim
->p_death
,
611 "| %s by %s |", cp
, credit
->i_name
);
613 if (victim
== attacker
) {
614 /* No use killing yourself. */
618 else if (victim
->p_ident
->i_team
== ' '
619 || victim
->p_ident
->i_team
!= credit
->i_team
) {
620 /* A cross-team kill: */
625 /* They killed someone on the same team: */
630 /* Compute the new credited score: */
631 credit
->i_score
= credit
->i_kills
/ (double) credit
->i_entries
;
633 /* The victim accrues one death: */
634 victim
->p_ident
->i_deaths
++;
636 /* Account for 'Stillborn' deaths */
637 if (victim
->p_nchar
== 0)
638 victim
->p_ident
->i_stillb
++;
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;
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
);
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);
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
);
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.
669 zap(PLAYER
*pp
, FLAG was_player
)
678 /* If they died from a shot, clean up shrapnel */
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: */
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
);
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
);
699 /* Move to bottom left */
700 cgoto(pp
, HEIGHT
, 0);
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: */
714 if (bp
->b_x
== pp
->p_x
&& bp
->b_y
== pp
->p_y
)
715 /* Bullets over the player are now over air: */
719 /* Explode a random fraction of the player's ammo: */
720 ammo_exploding
= rand_num(pp
->p_ammo
);
722 /* Determine the type and amount of detonation: */
723 expl_charge
= rand_num(ammo_exploding
+ 1);
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
;
733 * Figure out the best effective explosion
734 * type to use, given the amount of charge
737 for (btype
= MAXBOMB
- 1; btype
> 0; btype
--)
738 if (expl_charge
>= shot_req
[btype
])
740 for (stype
= MAXSLIME
- 1; stype
> 0; stype
--)
741 if (expl_charge
>= slime_req
[stype
])
743 /* Pick the larger of the bomb or slime: */
744 if (btype
>= 0 && stype
>= 0) {
745 if (shot_req
[btype
] > slime_req
[btype
])
749 expl_type
= shot_type
[btype
];
750 expl_charge
= shot_req
[btype
];
755 if (expl_charge
> 0) {
759 add_shot(expl_type
, pp
->p_y
, pp
->p_x
,
760 pp
->p_face
, expl_charge
, NULL
,
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
);
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)
774 if (np
>= &Boot
[NBOOTS
])
775 err(1, "Too many boots");
777 /* Start the boots from where the player is */
778 np
->p_undershot
= FALSE
;
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;
787 showexpl(np
->p_y
, np
->p_x
, BOOT
);
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
;
795 Maze
[pp
->p_y
][pp
->p_x
] = BOOT
;
797 fixshots(pp
->p_y
, pp
->p_x
,
798 Maze
[pp
->p_y
][pp
->p_x
]);
801 /* Any unexploded ammo builds up in the volcano: */
802 volcano
+= pp
->p_ammo
- expl_charge
;
804 /* Volcano eruption: */
805 if (conf_volcano
&& rand_num(100) < volcano
/
807 /* Erupt near the middle of the map */
809 x
= rand_num(WIDTH
/ 2) + WIDTH
/ 4;
810 y
= rand_num(HEIGHT
/ 2) + HEIGHT
/ 4;
811 } while (Maze
[y
][x
] != SPACE
);
813 /* Convert volcano charge into lava: */
814 add_shot(LAVA
, y
, x
, LEFTS
, volcano
,
818 /* Tell eveyone what's happening */
819 message(ALL_PLAYERS
, "Volcano eruption.");
823 if (conf_drone
&& rand_num(100) < 2) {
824 /* Find a starting place near the middle of the map: */
826 x
= rand_num(WIDTH
/ 2) + WIDTH
/ 4;
827 y
= rand_num(HEIGHT
/ 2) + HEIGHT
/ 4;
828 } while (Maze
[y
][x
] != SPACE
);
830 /* Start the drone going: */
831 add_shot(DSHOT
, y
, x
, rand_dir(),
832 shot_req
[conf_mindshot
+
833 rand_num(MAXBOMB
- conf_mindshot
)],
837 /* Tell the zapped player's client to shut down. */
838 sendcom(pp
, ENDWIN
, ' ');
839 fclose(pp
->p_output
);
841 /* Close up the gap in the Player array: */
843 if (pp
!= End_player
) {
844 /* Move the last player into the gap: */
845 memcpy(pp
, End_player
, sizeof *pp
);
847 STAT_PLAY_ROW
+ 1 + (pp
- Player
),
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
);
854 /* Erase the last player from the display: */
855 cgoto(ALL_PLAYERS
, STAT_PLAY_ROW
+ 1 + Nplayer
, STAT_NAME_COL
);
861 /* Close the session: */
862 sendcom(pp
, ENDWIN
, LAST_PLAYER
);
863 fclose(pp
->p_output
);
865 /* shuffle the monitor table */
867 if (pp
!= End_monitor
) {
868 memcpy(pp
, End_monitor
, sizeof *pp
);
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
);
875 /* Erase the last monitor in the list */
877 STAT_MON_ROW
+ 1 + (End_monitor
- Monitor
),
882 /* Update the file descriptor sets used by select: */
883 FD_CLR(savefd
, &Fds_mask
);
884 if (Num_fds
== savefd
+ 1) {
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
)
891 for (np
= Monitor
; np
< End_monitor
; np
++)
892 if (np
->p_fd
> Num_fds
)
900 * Return a random number in a given range.
907 return (random() % range
);
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
921 /* Do we already have characters? */
922 if (pp
->p_ncount
< pp
->p_nchar
)
924 /* Ignore if nothing to read. */
925 if (!FD_ISSET(pp
->p_fd
, &Have_inp
))
927 /* Remove the player from the read set until we have drained them: */
928 FD_CLR(pp
->p_fd
, &Have_inp
);
930 /* Suck their keypresses into a buffer: */
933 ret
= read(pp
->p_fd
, pp
->p_cbuf
, sizeof pp
->p_cbuf
);
937 if (errno
== EAGAIN
) {
939 warn("Have_inp is wrong for %d", pp
->p_fd
);
943 logit(LOG_INFO
, "read");
949 /* Connection was lost/closed: */
953 /* Reset pointer into read buffer */
960 * Exit with the given value, cleaning up any droppings lying around
967 /* Place their cursor in a friendly position: */
968 cgoto(ALL_PLAYERS
, HEIGHT
, 0);
970 /* Send them all the ENDWIN command: */
971 sendcom(ALL_PLAYERS
, ENDWIN
, LAST_PLAYER
);
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
);
979 /* Close the server socket: */
983 logx(LOG_INFO
, "game over");
989 * Accept a connection to the statistics port, and emit
997 struct sockaddr_in sockstruct
;
999 struct request_info ri
;
1002 /* Accept a connection to the statistics socket: */
1003 socklen
= sizeof sockstruct
;
1004 s
= accept(Status
, (struct sockaddr
*) &sockstruct
, &socklen
);
1008 logx(LOG_ERR
, "accept");
1012 /* Check for access permissions: */
1013 request_init(&ri
, RQ_DAEMON
, "huntd", RQ_FILE
, s
, 0);
1015 if (hosts_access(&ri
) == 0) {
1016 logx(LOG_INFO
, "rejected connection from %s", eval_client(&ri
));
1021 /* Don't allow the writes to block: */
1022 flags
= fcntl(s
, F_GETFL
, 0);
1024 fcntl(s
, F_SETFL
, flags
);
1026 fp
= fdopen(s
, "w");
1028 logit(LOG_ERR
, "fdopen");
1040 * emit the game statistics
1043 print_stats(FILE *fp
)
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
== ' ' ? ' ' : '[',
1054 ip
->i_team
== ' ' ? ' ' : ']'
1056 if (strlen(ip
->i_name
) + 3 < 8)
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
);
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
== ' ' ? ' ' : '[',
1068 ip
->i_team
== ' ' ? ' ' : ']'
1070 if (strlen(ip
->i_name
) + 3 < 8)
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
)
1078 for (pp
= Monitor
; pp
< End_monitor
; pp
++)
1079 if (pp
->p_ident
== ip
)
1087 * Send the game statistics to the controlling tty
1090 siginfo(int sig __unused
)
1095 if ((tty
= open(_PATH_TTY
, O_WRONLY
)) >= 0) {
1096 fp
= fdopen(tty
, "w");
1105 * Clear the Scores list.
1112 /* Release the list of scores: */
1113 for (ip
= Scores
; ip
!= NULL
; ip
= nextip
) {
1114 nextip
= ip
->i_next
;
1122 * Publically announce the game
1128 /* TODO: could use system() to do something user-configurable */
1132 * Handle a UDP packet sent to the well known port.
1135 handle_wkport(int fd
)
1137 struct sockaddr fromaddr
;
1141 struct request_info ri
;
1143 request_init(&ri
, RQ_DAEMON
, "huntd", RQ_FILE
, fd
, 0);
1145 fromlen
= sizeof fromaddr
;
1146 if (recvfrom(fd
, &query
, sizeof query
, 0, &fromaddr
, &fromlen
) == -1)
1148 logit(LOG_WARNING
, "recvfrom");
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
));
1162 /* Do we allow access? */
1163 if (hosts_access(&ri
) == 0) {
1164 logx(LOG_INFO
, "rejected connection from %s", eval_client(&ri
));
1168 query
= ntohs(query
);
1173 /* Don't bother replying if nobody to talk to: */
1175 /* Return the number of people playing: */
1179 /* Someone wants the statistics port: */
1180 response
= stat_port
;
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: */
1188 /* Otherwise, tell them how to get to the game: */
1189 response
= sock_port
;
1192 logit(LOG_INFO
, "unknown udp query %d", query
);
1196 response
= ntohs(response
);
1197 if (sendto(fd
, &response
, sizeof response
, 0,
1198 &fromaddr
, sizeof fromaddr
) == -1)
1199 logit(LOG_WARNING
, "sendto");