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.3 2008/11/10 15:28:13 swildner 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);
85 main(int ac
, char **av
)
89 static fd_set read_fds
;
90 static FLAG first
= TRUE
;
91 static FLAG server
= FALSE
;
93 static struct timeval linger
= { 0, 0 };
94 static struct timeval timeout
= { 0, 0 }, *to
;
95 struct spawn
*sp
, *spnext
;
104 while ((c
= getopt(ac
, av
, "sp:a:D:")) != -1) {
110 should_announce
= FALSE
;
111 Server_port
= atoi(optarg
);
114 if (!inet_aton(optarg
, (struct in_addr
*)&Server_addr
))
115 err(1, "bad interface address: %s", optarg
);
123 "usage: %s [-s] [-a addr] [-Dvar=value ...] "
133 openlog("huntd", LOG_PID
| (conf_logerr
&& !server
? LOG_PERROR
: 0),
136 /* Initialise game parameters: */
141 /* First, poll to see if we can get input */
145 timerclear(&timeout
);
146 nready
= select(Num_fds
, &read_fds
, NULL
, NULL
,
148 if (nready
< 0 && errno
!= EINTR
) {
149 logit(LOG_ERR
, "select");
152 } while (nready
< 0);
156 * Nothing was ready. We do some work now
157 * to see if the simulation has any pending work
158 * to do, and decide if we need to block
159 * indefinitely or just timeout.
162 if (conf_simstep
&& can_moveshots()) {
164 * block for a short time before continuing
165 * with explosions, bullets and whatnot
168 to
->tv_sec
= conf_simstep
/ 1000000;
169 to
->tv_usec
= conf_simstep
% 1000000;
172 * since there's nothing going on,
173 * just block waiting for external activity
179 nready
= select(Num_fds
, &read_fds
, NULL
, NULL
,
181 if (nready
< 0 && errno
!= EINTR
) {
182 logit(LOG_ERR
, "select");
185 } while (nready
< 0);
188 /* Remember which descriptors are active: */
191 /* Answer new player connections: */
192 if (FD_ISSET(Socket
, &Have_inp
))
195 /* Continue answering new player connections: */
196 for (sp
= Spawn
; sp
; ) {
199 if (FD_ISSET(fd
, &Have_inp
) && answer_next(sp
)) {
201 * Remove from the spawn list. (fd remains in
204 *sp
->prevnext
= sp
->next
;
206 sp
->next
->prevnext
= sp
->prevnext
;
209 /* We probably consumed all data. */
210 FD_CLR(fd
, &Have_inp
);
212 /* Announce game if this is the first spawn. */
213 if (first
&& should_announce
)
220 /* Process input and move bullets until we've exhausted input */
225 for (pp
= Player
; pp
< End_player
; )
226 if (pp
->p_death
[0] != '\0')
230 for (pp
= Monitor
; pp
< End_monitor
; )
231 if (pp
->p_death
[0] != '\0')
237 for (pp
= Player
; pp
< End_player
; pp
++)
243 for (pp
= Monitor
; pp
< End_monitor
; pp
++)
251 /* Handle a datagram sent to the server socket: */
252 if (FD_ISSET(Server_socket
, &Have_inp
))
253 handle_wkport(Server_socket
);
255 /* Answer statistics connections: */
256 if (FD_ISSET(Status
, &Have_inp
))
259 /* Flush/synchronize all the displays: */
260 for (pp
= Player
; pp
< End_player
; pp
++) {
261 if (FD_ISSET(pp
->p_fd
, &read_fds
)) {
262 sendcom(pp
, READY
, pp
->p_nexec
);
267 for (pp
= Monitor
; pp
< End_monitor
; pp
++) {
268 if (FD_ISSET(pp
->p_fd
, &read_fds
)) {
269 sendcom(pp
, READY
, pp
->p_nexec
);
274 } while (Nplayer
> 0);
276 /* No more players! */
278 /* No players yet or a continuous game? */
279 if (first
|| conf_linger
< 0)
282 /* Wait a short while for one to come back: */
284 linger
.tv_sec
= conf_linger
;
285 while ((ret
= select(Num_fds
, &read_fds
, NULL
, NULL
, &linger
)) < 0) {
286 if (errno
!= EINTR
) {
287 logit(LOG_WARNING
, "select");
291 linger
.tv_sec
= conf_linger
;
295 /* Someone returned! Resume the game: */
297 /* else, it timed out, and the game is really over. */
299 /* If we are an inetd server, we should re-init the map and restart: */
309 /* Get rid of any attached monitors: */
310 for (pp
= Monitor
; pp
< End_monitor
; )
320 * Initialize the global parameters.
326 struct sockaddr_in test_port
;
329 struct sockaddr_in addr
;
330 struct sigaction sact
;
334 if (setpgid(getpid(), getpid()) == -1)
337 sact
.sa_flags
= SA_RESTART
;
338 sigemptyset(&sact
.sa_mask
);
340 /* Ignore HUP, QUIT and PIPE: */
341 sact
.sa_handler
= SIG_IGN
;
342 if (sigaction(SIGHUP
, &sact
, NULL
) == -1)
343 err(1, "sigaction SIGHUP");
344 if (sigaction(SIGQUIT
, &sact
, NULL
) == -1)
345 err(1, "sigaction SIGQUIT");
346 if (sigaction(SIGPIPE
, &sact
, NULL
) == -1)
347 err(1, "sigaction SIGPIPE");
349 /* Clean up gracefully on INT and TERM: */
350 sact
.sa_handler
= cleanup
;
351 if (sigaction(SIGINT
, &sact
, NULL
) == -1)
352 err(1, "sigaction SIGINT");
353 if (sigaction(SIGTERM
, &sact
, NULL
) == -1)
354 err(1, "sigaction SIGTERM");
357 sact
.sa_handler
= siginfo
;
358 if (sigaction(SIGINFO
, &sact
, NULL
) == -1)
359 err(1, "sigaction SIGINFO");
361 if (chdir("/") == -1)
365 /* Initialize statistics socket: */
366 addr
.sin_family
= AF_INET
;
367 addr
.sin_addr
.s_addr
= Server_addr
;
370 Status
= socket(AF_INET
, SOCK_STREAM
, 0);
371 if (bind(Status
, (struct sockaddr
*) &addr
, sizeof addr
) < 0) {
372 logit(LOG_ERR
, "bind");
375 if (listen(Status
, 5) == -1) {
376 logit(LOG_ERR
, "listen");
380 len
= sizeof (struct sockaddr_in
);
381 if (getsockname(Status
, (struct sockaddr
*) &addr
, &len
) < 0) {
382 logit(LOG_ERR
, "getsockname");
385 stat_port
= ntohs(addr
.sin_port
);
387 /* Initialize main socket: */
388 addr
.sin_family
= AF_INET
;
389 addr
.sin_addr
.s_addr
= Server_addr
;
392 Socket
= socket(AF_INET
, SOCK_STREAM
, 0);
394 if (bind(Socket
, (struct sockaddr
*) &addr
, sizeof addr
) < 0) {
395 logit(LOG_ERR
, "bind");
398 if (listen(Socket
, 5) == -1) {
399 logit(LOG_ERR
, "listen");
403 len
= sizeof (struct sockaddr_in
);
404 if (getsockname(Socket
, (struct sockaddr
*) &addr
, &len
) < 0) {
405 logit(LOG_ERR
, "getsockname");
408 sock_port
= ntohs(addr
.sin_port
);
410 /* Initialize minimal select mask */
412 FD_SET(Socket
, &Fds_mask
);
413 FD_SET(Status
, &Fds_mask
);
414 Num_fds
= ((Socket
> Status
) ? Socket
: Status
) + 1;
416 /* Find the port that huntd should run on */
417 if (Server_port
== 0) {
418 se
= getservbyname("hunt", "udp");
420 Server_port
= ntohs(se
->s_port
);
422 Server_port
= HUNT_PORT
;
425 /* Check if stdin is a socket: */
426 len
= sizeof (struct sockaddr_in
);
427 if (getsockname(STDIN_FILENO
, (struct sockaddr
*) &test_port
, &len
) >= 0
428 && test_port
.sin_family
== AF_INET
) {
429 /* We are probably running from inetd: don't log to stderr */
430 Server_socket
= STDIN_FILENO
;
432 if (test_port
.sin_port
!= htons((u_short
) Server_port
)) {
434 should_announce
= FALSE
;
435 Server_port
= ntohs(test_port
.sin_port
);
438 /* We need to listen on a socket: */
440 test_port
.sin_port
= htons((u_short
) Server_port
);
442 Server_socket
= socket(AF_INET
, SOCK_DGRAM
, 0);
444 /* Permit multiple huntd's on the same port. */
445 if (setsockopt(Server_socket
, SOL_SOCKET
, SO_REUSEPORT
, &true,
447 logit(LOG_ERR
, "setsockopt SO_REUSEADDR");
449 if (bind(Server_socket
, (struct sockaddr
*) &test_port
,
450 sizeof test_port
) < 0) {
451 logit(LOG_ERR
, "bind port %d", Server_port
);
455 /* Datagram sockets do not need a listen() call. */
458 /* We'll handle the broadcast listener in the main loop: */
459 FD_SET(Server_socket
, &Fds_mask
);
460 if (Server_socket
+ 1 > Num_fds
)
461 Num_fds
= Server_socket
+ 1;
463 /* Initialise the random seed: */
469 /* Create some boots, if needed: */
472 /* Construct a table of what objects a player can see over: */
473 for (i
= 0; i
< NASCII
; i
++)
475 See_over
[DOOR
] = FALSE
;
476 See_over
[WALL1
] = FALSE
;
477 See_over
[WALL2
] = FALSE
;
478 See_over
[WALL3
] = FALSE
;
479 See_over
[WALL4
] = FALSE
;
480 See_over
[WALL5
] = FALSE
;
482 logx(LOG_INFO
, "game started");
487 * Put the boots in the maze
497 x
= rand_num(WIDTH
- 1) + 1;
498 y
= rand_num(HEIGHT
- 1) + 1;
499 } while (Maze
[y
][x
] != SPACE
);
500 Maze
[y
][x
] = BOOT_PAIR
;
503 for (pp
= Boot
; pp
< &Boot
[NBOOTS
]; pp
++)
510 * Apply damage to the victim from an attacker.
511 * If the victim dies as a result, give points to 'credit',
514 checkdam(PLAYER
*victim
, PLAYER
*attacker
, IDENT
*credit
, int damage
,
520 /* Don't do anything if the victim is already in the throes of death */
521 if (victim
->p_death
[0] != '\0')
524 /* Weaken slime attacks by 0.5 * number of boots the victim has on: */
526 switch (victim
->p_nboots
) {
530 damage
= (damage
+ 1) / 2;
533 if (attacker
!= NULL
)
534 message(attacker
, "He has boots on!");
538 /* The victim sustains some damage: */
539 victim
->p_damage
+= damage
;
541 /* Check if the victim survives the hit: */
542 if (victim
->p_damage
<= victim
->p_damcap
) {
544 outyx(victim
, STAT_DAM_ROW
, STAT_VALUE_COL
, "%2d",
549 /* Describe how the victim died: */
555 cp
= "Killed on impact";
558 cp
= "Stabbed to death";
559 victim
->p_ammo
= 0; /* No exploding */
562 cp
= "Shot to death";
586 if (credit
== NULL
) {
590 * Nobody is taking the credit for the kill.
591 * Attribute it to either a mine or 'act of God'.
599 blame
= "act of God";
603 /* Set the death message: */
604 (void) snprintf(victim
->p_death
, sizeof victim
->p_death
,
605 "| %s by %s |", cp
, blame
);
607 /* No further score crediting needed. */
611 /* Set the death message: */
612 (void) snprintf(victim
->p_death
, sizeof victim
->p_death
,
613 "| %s by %s |", cp
, credit
->i_name
);
615 if (victim
== attacker
) {
616 /* No use killing yourself. */
620 else if (victim
->p_ident
->i_team
== ' '
621 || victim
->p_ident
->i_team
!= credit
->i_team
) {
622 /* A cross-team kill: */
627 /* They killed someone on the same team: */
632 /* Compute the new credited score: */
633 credit
->i_score
= credit
->i_kills
/ (double) credit
->i_entries
;
635 /* The victim accrues one death: */
636 victim
->p_ident
->i_deaths
++;
638 /* Account for 'Stillborn' deaths */
639 if (victim
->p_nchar
== 0)
640 victim
->p_ident
->i_stillb
++;
643 /* Give the attacker player a bit more strength */
644 attacker
->p_damcap
+= conf_killgain
;
645 attacker
->p_damage
-= conf_killgain
;
646 if (attacker
->p_damage
< 0)
647 attacker
->p_damage
= 0;
649 /* Tell the attacker his new strength: */
650 outyx(attacker
, STAT_DAM_ROW
, STAT_VALUE_COL
, "%2d/%2d",
651 attacker
->p_damage
, attacker
->p_damcap
);
653 /* Tell the attacker his new 'kill count': */
654 outyx(attacker
, STAT_KILL_ROW
, STAT_VALUE_COL
, "%3d",
655 (attacker
->p_damcap
- conf_maxdam
) / 2);
657 /* Update the attacker's score for everyone else */
658 y
= STAT_PLAY_ROW
+ 1 + (attacker
- Player
);
659 outyx(ALL_PLAYERS
, y
, STAT_NAME_COL
,
660 "%5.2f", attacker
->p_ident
->i_score
);
666 * Kill off a player and take them out of the game.
667 * The 'was_player' flag indicates that the player was not
668 * a monitor and needs extra cleaning up.
671 zap(PLAYER
*pp
, FLAG was_player
)
680 /* If they died from a shot, clean up shrapnel */
682 fixshots(pp
->p_y
, pp
->p_x
, pp
->p_over
);
683 /* Let the player see their last position: */
684 drawplayer(pp
, FALSE
);
685 /* Remove from game: */
689 /* Display the cause of death in the centre of the screen: */
690 len
= strlen(pp
->p_death
);
691 x
= (WIDTH
- len
) / 2;
692 outyx(pp
, HEIGHT
/ 2, x
, "%s", pp
->p_death
);
694 /* Put some horizontal lines around and below the death message: */
695 memset(pp
->p_death
+ 1, '-', len
- 2);
696 pp
->p_death
[0] = '+';
697 pp
->p_death
[len
- 1] = '+';
698 outyx(pp
, HEIGHT
/ 2 - 1, x
, "%s", pp
->p_death
);
699 outyx(pp
, HEIGHT
/ 2 + 1, x
, "%s", pp
->p_death
);
701 /* Move to bottom left */
702 cgoto(pp
, HEIGHT
, 0);
711 /* Check all the bullets: */
712 for (bp
= Bullets
; bp
!= NULL
; bp
= bp
->b_next
) {
713 if (bp
->b_owner
== pp
)
714 /* Zapped players can't own bullets: */
716 if (bp
->b_x
== pp
->p_x
&& bp
->b_y
== pp
->p_y
)
717 /* Bullets over the player are now over air: */
721 /* Explode a random fraction of the player's ammo: */
722 ammo_exploding
= rand_num(pp
->p_ammo
);
724 /* Determine the type and amount of detonation: */
725 expl_charge
= rand_num(ammo_exploding
+ 1);
727 /* Ignore the no-ammo case: */
728 expl_charge
= expl_type
= 0;
729 else if (ammo_exploding
>= pp
->p_ammo
- 1) {
730 /* Maximal explosions always appear as slime: */
731 expl_charge
= pp
->p_ammo
;
735 * Figure out the best effective explosion
736 * type to use, given the amount of charge
739 for (btype
= MAXBOMB
- 1; btype
> 0; btype
--)
740 if (expl_charge
>= shot_req
[btype
])
742 for (stype
= MAXSLIME
- 1; stype
> 0; stype
--)
743 if (expl_charge
>= slime_req
[stype
])
745 /* Pick the larger of the bomb or slime: */
746 if (btype
>= 0 && stype
>= 0) {
747 if (shot_req
[btype
] > slime_req
[btype
])
751 expl_type
= shot_type
[btype
];
752 expl_charge
= shot_req
[btype
];
757 if (expl_charge
> 0) {
761 (void) add_shot(expl_type
, pp
->p_y
, pp
->p_x
,
762 pp
->p_face
, expl_charge
, (PLAYER
*) NULL
,
765 /* Explain what the explosion is about. */
766 snprintf(buf
, sizeof buf
, "%s detonated.",
767 pp
->p_ident
->i_name
);
768 message(ALL_PLAYERS
, buf
);
770 while (pp
->p_nboots
-- > 0) {
771 /* Throw one of the boots away: */
772 for (np
= Boot
; np
< &Boot
[NBOOTS
]; np
++)
773 if (np
->p_flying
< 0)
776 if (np
>= &Boot
[NBOOTS
])
777 err(1, "Too many boots");
779 /* Start the boots from where the player is */
780 np
->p_undershot
= FALSE
;
783 /* Throw for up to 20 steps */
784 np
->p_flying
= rand_num(20);
785 np
->p_flyx
= 2 * rand_num(6) - 5;
786 np
->p_flyy
= 2 * rand_num(6) - 5;
789 showexpl(np
->p_y
, np
->p_x
, BOOT
);
792 /* No explosion. Leave the player's boots behind. */
793 else if (pp
->p_nboots
> 0) {
794 if (pp
->p_nboots
== 2)
795 Maze
[pp
->p_y
][pp
->p_x
] = BOOT_PAIR
;
797 Maze
[pp
->p_y
][pp
->p_x
] = BOOT
;
799 fixshots(pp
->p_y
, pp
->p_x
,
800 Maze
[pp
->p_y
][pp
->p_x
]);
803 /* Any unexploded ammo builds up in the volcano: */
804 volcano
+= pp
->p_ammo
- expl_charge
;
806 /* Volcano eruption: */
807 if (conf_volcano
&& rand_num(100) < volcano
/
809 /* Erupt near the middle of the map */
811 x
= rand_num(WIDTH
/ 2) + WIDTH
/ 4;
812 y
= rand_num(HEIGHT
/ 2) + HEIGHT
/ 4;
813 } while (Maze
[y
][x
] != SPACE
);
815 /* Convert volcano charge into lava: */
816 (void) add_shot(LAVA
, y
, x
, LEFTS
, volcano
,
817 (PLAYER
*) NULL
, TRUE
, SPACE
);
820 /* Tell eveyone what's happening */
821 message(ALL_PLAYERS
, "Volcano eruption.");
825 if (conf_drone
&& rand_num(100) < 2) {
826 /* Find a starting place near the middle of the map: */
828 x
= rand_num(WIDTH
/ 2) + WIDTH
/ 4;
829 y
= rand_num(HEIGHT
/ 2) + HEIGHT
/ 4;
830 } while (Maze
[y
][x
] != SPACE
);
832 /* Start the drone going: */
833 add_shot(DSHOT
, y
, x
, rand_dir(),
834 shot_req
[conf_mindshot
+
835 rand_num(MAXBOMB
- conf_mindshot
)],
836 (PLAYER
*) NULL
, FALSE
, SPACE
);
839 /* Tell the zapped player's client to shut down. */
840 sendcom(pp
, ENDWIN
, ' ');
841 (void) fclose(pp
->p_output
);
843 /* Close up the gap in the Player array: */
845 if (pp
!= End_player
) {
846 /* Move the last player into the gap: */
847 memcpy(pp
, End_player
, sizeof *pp
);
849 STAT_PLAY_ROW
+ 1 + (pp
- Player
),
851 "%5.2f%c%-10.10s %c",
852 pp
->p_ident
->i_score
, stat_char(pp
),
853 pp
->p_ident
->i_name
, pp
->p_ident
->i_team
);
856 /* Erase the last player from the display: */
857 cgoto(ALL_PLAYERS
, STAT_PLAY_ROW
+ 1 + Nplayer
, STAT_NAME_COL
);
863 /* Close the session: */
864 sendcom(pp
, ENDWIN
, LAST_PLAYER
);
865 (void) fclose(pp
->p_output
);
867 /* shuffle the monitor table */
869 if (pp
!= End_monitor
) {
870 memcpy(pp
, End_monitor
, sizeof *pp
);
872 STAT_MON_ROW
+ 1 + (pp
- Player
), STAT_NAME_COL
,
873 "%5.5s %-10.10s %c", " ",
874 pp
->p_ident
->i_name
, pp
->p_ident
->i_team
);
877 /* Erase the last monitor in the list */
879 STAT_MON_ROW
+ 1 + (End_monitor
- Monitor
),
884 /* Update the file descriptor sets used by select: */
885 FD_CLR(savefd
, &Fds_mask
);
886 if (Num_fds
== savefd
+ 1) {
888 if (Server_socket
> Socket
)
889 Num_fds
= Server_socket
;
890 for (np
= Player
; np
< End_player
; np
++)
891 if (np
->p_fd
> Num_fds
)
893 for (np
= Monitor
; np
< End_monitor
; np
++)
894 if (np
->p_fd
> Num_fds
)
902 * Return a random number in a given range.
909 return (random() % range
);
914 * Check to see if we have any characters in the input queue; if
915 * we do, read them, stash them away, and return TRUE; else return
923 /* Do we already have characters? */
924 if (pp
->p_ncount
< pp
->p_nchar
)
926 /* Ignore if nothing to read. */
927 if (!FD_ISSET(pp
->p_fd
, &Have_inp
))
929 /* Remove the player from the read set until we have drained them: */
930 FD_CLR(pp
->p_fd
, &Have_inp
);
932 /* Suck their keypresses into a buffer: */
935 ret
= read(pp
->p_fd
, pp
->p_cbuf
, sizeof pp
->p_cbuf
);
939 if (errno
== EAGAIN
) {
941 warn("Have_inp is wrong for %d", pp
->p_fd
);
945 logit(LOG_INFO
, "read");
951 /* Connection was lost/closed: */
955 /* Reset pointer into read buffer */
962 * Exit with the given value, cleaning up any droppings lying around
969 /* Place their cursor in a friendly position: */
970 cgoto(ALL_PLAYERS
, HEIGHT
, 0);
972 /* Send them all the ENDWIN command: */
973 sendcom(ALL_PLAYERS
, ENDWIN
, LAST_PLAYER
);
975 /* And close their connections: */
976 for (pp
= Player
; pp
< End_player
; pp
++)
977 (void) fclose(pp
->p_output
);
978 for (pp
= Monitor
; pp
< End_monitor
; pp
++)
979 (void) fclose(pp
->p_output
);
981 /* Close the server socket: */
982 (void) close(Socket
);
985 logx(LOG_INFO
, "game over");
991 * Accept a connection to the statistics port, and emit
999 struct sockaddr_in sockstruct
;
1001 struct request_info ri
;
1004 /* Accept a connection to the statistics socket: */
1005 socklen
= sizeof sockstruct
;
1006 s
= accept(Status
, (struct sockaddr
*) &sockstruct
, &socklen
);
1010 logx(LOG_ERR
, "accept");
1014 /* Check for access permissions: */
1015 request_init(&ri
, RQ_DAEMON
, "huntd", RQ_FILE
, s
, 0);
1017 if (hosts_access(&ri
) == 0) {
1018 logx(LOG_INFO
, "rejected connection from %s", eval_client(&ri
));
1023 /* Don't allow the writes to block: */
1024 flags
= fcntl(s
, F_GETFL
, 0);
1026 (void) fcntl(s
, F_SETFL
, flags
);
1028 fp
= fdopen(s
, "w");
1030 logit(LOG_ERR
, "fdopen");
1042 * emit the game statistics
1045 print_stats(FILE *fp
)
1050 /* Send the statistics as raw text down the socket: */
1051 fputs("Name\t\tScore\tDucked\tAbsorb\tFaced\tShot\tRobbed\tMissed\tSlimeK\n", fp
);
1052 for (ip
= Scores
; ip
!= NULL
; ip
= ip
->i_next
) {
1053 fprintf(fp
, "%s%c%c%c\t", ip
->i_name
,
1054 ip
->i_team
== ' ' ? ' ' : '[',
1056 ip
->i_team
== ' ' ? ' ' : ']'
1058 if (strlen(ip
->i_name
) + 3 < 8)
1060 fprintf(fp
, "%.2f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
1061 ip
->i_score
, ip
->i_ducked
, ip
->i_absorbed
,
1062 ip
->i_faced
, ip
->i_shot
, ip
->i_robbed
,
1063 ip
->i_missed
, ip
->i_slime
);
1065 fputs("\n\nName\t\tEnemy\tFriend\tDeaths\tStill\tSaved\tConnect\n", fp
);
1066 for (ip
= Scores
; ip
!= NULL
; ip
= ip
->i_next
) {
1067 fprintf(fp
, "%s%c%c%c\t", ip
->i_name
,
1068 ip
->i_team
== ' ' ? ' ' : '[',
1070 ip
->i_team
== ' ' ? ' ' : ']'
1072 if (strlen(ip
->i_name
) + 3 < 8)
1074 fprintf(fp
, "%d\t%d\t%d\t%d\t%d\t",
1075 ip
->i_gkills
, ip
->i_bkills
, ip
->i_deaths
,
1076 ip
->i_stillb
, ip
->i_saved
);
1077 for (pp
= Player
; pp
< End_player
; pp
++)
1078 if (pp
->p_ident
== ip
)
1080 for (pp
= Monitor
; pp
< End_monitor
; pp
++)
1081 if (pp
->p_ident
== ip
)
1089 * Send the game statistics to the controlling tty
1092 siginfo(int sig __unused
)
1097 if ((tty
= open(_PATH_TTY
, O_WRONLY
)) >= 0) {
1098 fp
= fdopen(tty
, "w");
1107 * Clear the Scores list.
1114 /* Release the list of scores: */
1115 for (ip
= Scores
; ip
!= NULL
; ip
= nextip
) {
1116 nextip
= ip
->i_next
;
1124 * Publically announce the game
1130 /* TODO: could use system() to do something user-configurable */
1134 * Handle a UDP packet sent to the well known port.
1137 handle_wkport(int fd
)
1139 struct sockaddr fromaddr
;
1143 struct request_info ri
;
1145 request_init(&ri
, RQ_DAEMON
, "huntd", RQ_FILE
, fd
, 0);
1147 fromlen
= sizeof fromaddr
;
1148 if (recvfrom(fd
, &query
, sizeof query
, 0, &fromaddr
, &fromlen
) == -1)
1150 logit(LOG_WARNING
, "recvfrom");
1155 fprintf(stderr
, "query %d (%s) from %s:%d\n", query
,
1156 query
== C_MESSAGE
? "C_MESSAGE" :
1157 query
== C_SCORES
? "C_SCORES" :
1158 query
== C_PLAYER
? "C_PLAYER" :
1159 query
== C_MONITOR
? "C_MONITOR" : "?",
1160 inet_ntoa(((struct sockaddr_in
*)&fromaddr
)->sin_addr
),
1161 ntohs(((struct sockaddr_in
*)&fromaddr
)->sin_port
));
1164 /* Do we allow access? */
1165 if (hosts_access(&ri
) == 0) {
1166 logx(LOG_INFO
, "rejected connection from %s", eval_client(&ri
));
1170 query
= ntohs(query
);
1175 /* Don't bother replying if nobody to talk to: */
1177 /* Return the number of people playing: */
1181 /* Someone wants the statistics port: */
1182 response
= stat_port
;
1186 /* Someone wants to play or watch: */
1187 if (query
== C_MONITOR
&& Nplayer
<= 0)
1188 /* Don't bother replying if there's nothing to watch: */
1190 /* Otherwise, tell them how to get to the game: */
1191 response
= sock_port
;
1194 logit(LOG_INFO
, "unknown udp query %d", query
);
1198 response
= ntohs(response
);
1199 if (sendto(fd
, &response
, sizeof response
, 0,
1200 &fromaddr
, sizeof fromaddr
) == -1)
1201 logit(LOG_WARNING
, "sendto");