#include <sys/stat.h>
#include <arpa/inet.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <paths.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "conf.h"
#include "hunt.h"
#include "server.h"
u_int16_t Server_port;
int Server_socket;
FLAG should_announce = TRUE;
u_short sock_port;
u_short stat_port;
in_addr_t Server_addr = INADDR_ANY;
static void clear_scores(void);
static int havechar(PLAYER *);
static void init(int);
static void makeboots(void);
static void send_stats(void);
static void zap(PLAYER *, FLAG);
static void announce_game(void);
static void siginfo(int);
static void print_stats(FILE *);
static void handle_wkport(int);
int
main(int ac, char **av)
{
PLAYER *pp;
int had_char;
static fd_set read_fds;
static FLAG first = TRUE;
static FLAG server = FALSE;
extern char *__progname;
int c;
static struct timeval linger = { 0, 0 };
static struct timeval timeout = { 0, 0 }, *to;
struct spawn *sp, *spnext;
int ret;
int nready;
int fd;
int background = 0;
config();
while ((c = getopt(ac, av, "bsp:a:D:")) != -1) {
switch (c) {
case 'b':
background = 1;
conf_syslog = 1;
conf_logerr = 0;
break;
case 's':
server = TRUE;
break;
case 'p':
should_announce = FALSE;
Server_port = atoi(optarg);
break;
case 'a':
if (inet_pton(AF_INET, optarg,
(struct in_addr *)&Server_addr) != 1)
err(1, "bad interface address: %s", optarg);
break;
case 'D':
config_arg(optarg);
break;
default:
erred:
fprintf(stderr,
"usage: %s [-bs] [-a addr] [-D var=value] "
"[-p port]\n",
__progname);
return 2;
}
}
if (optind < ac)
goto erred;
openlog("huntd", LOG_PID | (conf_logerr && !server? LOG_PERROR : 0),
LOG_DAEMON);
init(background);
again:
do {
do {
read_fds = Fds_mask;
errno = 0;
timerclear(&timeout);
nready = select(Num_fds, &read_fds, NULL, NULL,
&timeout);
if (nready < 0 && errno != EINTR) {
logit(LOG_ERR, "select");
cleanup(1);
}
} while (nready < 0);
if (nready == 0) {
do {
if (conf_simstep && can_moveshots()) {
to = &timeout;
to->tv_sec = conf_simstep / 1000000;
to->tv_usec = conf_simstep % 1000000;
} else
to = NULL;
read_fds = Fds_mask;
errno = 0;
nready = select(Num_fds, &read_fds, NULL, NULL,
to);
if (nready < 0 && errno != EINTR) {
logit(LOG_ERR, "select");
cleanup(1);
}
} while (nready < 0);
}
Have_inp = read_fds;
if (FD_ISSET(Socket, &Have_inp))
answer_first();
for (sp = Spawn; sp; ) {
spnext = sp->next;
fd = sp->fd;
if (FD_ISSET(fd, &Have_inp) && answer_next(sp)) {
*sp->prevnext = sp->next;
if (sp->next)
sp->next->prevnext = sp->prevnext;
free(sp);
FD_CLR(fd, &Have_inp);
if (first && should_announce)
announce_game();
first = FALSE;
}
sp = spnext;
}
had_char = TRUE;
while (had_char) {
moveshots();
for (pp = Player; pp < End_player; )
if (pp->p_death[0] != '\0')
zap(pp, TRUE);
else
pp++;
for (pp = Monitor; pp < End_monitor; )
if (pp->p_death[0] != '\0')
zap(pp, FALSE);
else
pp++;
had_char = FALSE;
for (pp = Player; pp < End_player; pp++)
if (havechar(pp)) {
execute(pp);
pp->p_nexec++;
had_char = TRUE;
}
for (pp = Monitor; pp < End_monitor; pp++)
if (havechar(pp)) {
mon_execute(pp);
pp->p_nexec++;
had_char = TRUE;
}
}
if (FD_ISSET(Server_socket, &Have_inp))
handle_wkport(Server_socket);
if (FD_ISSET(Status, &Have_inp))
send_stats();
for (pp = Player; pp < End_player; pp++) {
if (FD_ISSET(pp->p_fd, &read_fds)) {
sendcom(pp, READY, pp->p_nexec);
pp->p_nexec = 0;
}
flush(pp);
}
for (pp = Monitor; pp < End_monitor; pp++) {
if (FD_ISSET(pp->p_fd, &read_fds)) {
sendcom(pp, READY, pp->p_nexec);
pp->p_nexec = 0;
}
flush(pp);
}
} while (Nplayer > 0);
if (first || conf_linger < 0)
goto again;
read_fds = Fds_mask;
linger.tv_sec = conf_linger;
while ((ret = select(Num_fds, &read_fds, NULL, NULL, &linger)) < 0) {
if (errno != EINTR) {
logit(LOG_WARNING, "select");
break;
}
read_fds = Fds_mask;
linger.tv_sec = conf_linger;
linger.tv_usec = 0;
}
if (ret > 0)
goto again;
if (server) {
clear_scores();
makemaze();
clearwalls();
makeboots();
first = TRUE;
goto again;
}
for (pp = Monitor; pp < End_monitor; )
zap(pp, FALSE);
cleanup(0);
return 0;
}
static void
init(int background)
{
int i;
struct sockaddr_in test_port;
int true = 1;
socklen_t len;
struct sockaddr_in addr;
struct sigaction sact;
struct servent *se;
sact.sa_flags = SA_RESTART;
sigemptyset(&sact.sa_mask);
sact.sa_handler = SIG_IGN;
if (sigaction(SIGHUP, &sact, NULL) == -1)
err(1, "sigaction SIGHUP");
if (sigaction(SIGQUIT, &sact, NULL) == -1)
err(1, "sigaction SIGQUIT");
if (sigaction(SIGPIPE, &sact, NULL) == -1)
err(1, "sigaction SIGPIPE");
sact.sa_handler = cleanup;
if (sigaction(SIGINT, &sact, NULL) == -1)
err(1, "sigaction SIGINT");
if (sigaction(SIGTERM, &sact, NULL) == -1)
err(1, "sigaction SIGTERM");
sact.sa_handler = siginfo;
if (sigaction(SIGINFO, &sact, NULL) == -1)
err(1, "sigaction SIGINFO");
if (chdir("/") == -1)
warn("chdir");
(void) umask(0777);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = Server_addr;
addr.sin_port = 0;
Status = socket(AF_INET, SOCK_STREAM, 0);
if (bind(Status, (struct sockaddr *) &addr, sizeof addr) < 0) {
logit(LOG_ERR, "bind");
cleanup(1);
}
if (listen(Status, 5) == -1) {
logit(LOG_ERR, "listen");
cleanup(1);
}
len = sizeof (struct sockaddr_in);
if (getsockname(Status, (struct sockaddr *) &addr, &len) < 0) {
logit(LOG_ERR, "getsockname");
cleanup(1);
}
stat_port = ntohs(addr.sin_port);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = Server_addr;
addr.sin_port = 0;
Socket = socket(AF_INET, SOCK_STREAM, 0);
if (bind(Socket, (struct sockaddr *) &addr, sizeof addr) < 0) {
logit(LOG_ERR, "bind");
cleanup(1);
}
if (listen(Socket, 5) == -1) {
logit(LOG_ERR, "listen");
cleanup(1);
}
len = sizeof (struct sockaddr_in);
if (getsockname(Socket, (struct sockaddr *) &addr, &len) < 0) {
logit(LOG_ERR, "getsockname");
cleanup(1);
}
sock_port = ntohs(addr.sin_port);
FD_ZERO(&Fds_mask);
FD_SET(Socket, &Fds_mask);
FD_SET(Status, &Fds_mask);
Num_fds = ((Socket > Status) ? Socket : Status) + 1;
if (Server_port == 0) {
se = getservbyname("hunt", "udp");
if (se != NULL)
Server_port = ntohs(se->s_port);
else
Server_port = HUNT_PORT;
}
len = sizeof (struct sockaddr_in);
if (getsockname(STDIN_FILENO, (struct sockaddr *) &test_port, &len) >= 0
&& test_port.sin_family == AF_INET) {
Server_socket = STDIN_FILENO;
conf_logerr = 0;
if (test_port.sin_port != htons((u_short) Server_port)) {
should_announce = FALSE;
Server_port = ntohs(test_port.sin_port);
}
} else {
test_port = addr;
test_port.sin_port = htons((u_short) Server_port);
Server_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (setsockopt(Server_socket, SOL_SOCKET, SO_REUSEPORT, &true,
sizeof true) < 0)
logit(LOG_ERR, "setsockopt SO_REUSEADDR");
if (bind(Server_socket, (struct sockaddr *) &test_port,
sizeof test_port) < 0) {
logit(LOG_ERR, "bind port %d", Server_port);
cleanup(1);
}
if (background)
daemon(0, 0);
}
FD_SET(Server_socket, &Fds_mask);
if (Server_socket + 1 > Num_fds)
Num_fds = Server_socket + 1;
makemaze();
makeboots();
for (i = 0; i < NASCII; i++)
See_over[i] = TRUE;
See_over[DOOR] = FALSE;
See_over[WALL1] = FALSE;
See_over[WALL2] = FALSE;
See_over[WALL3] = FALSE;
See_over[WALL4] = FALSE;
See_over[WALL5] = FALSE;
logx(LOG_INFO, "game started");
}
static void
makeboots(void)
{
int x, y;
PLAYER *pp;
if (conf_boots) {
do {
x = rand_num(WIDTH - 1) + 1;
y = rand_num(HEIGHT - 1) + 1;
} while (Maze[y][x] != SPACE);
Maze[y][x] = BOOT_PAIR;
}
for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
pp->p_flying = -1;
}
void
checkdam(PLAYER *victim, PLAYER *attacker, IDENT *credit, int damage,
char shot_type)
{
char *cp;
int y;
if (victim->p_death[0] != '\0')
return;
if (shot_type == SLIME)
switch (victim->p_nboots) {
default:
break;
case 1:
damage = (damage + 1) / 2;
break;
case 2:
if (attacker != NULL)
message(attacker, "He has boots on!");
return;
}
victim->p_damage += damage;
if (victim->p_damage <= victim->p_damcap) {
outyx(victim, STAT_DAM_ROW, STAT_VALUE_COL, "%2d",
victim->p_damage);
return;
}
switch (shot_type) {
default:
cp = "Killed";
break;
case FALL:
cp = "Killed on impact";
break;
case KNIFE:
cp = "Stabbed to death";
victim->p_ammo = 0;
break;
case SHOT:
cp = "Shot to death";
break;
case GRENADE:
case SATCHEL:
case BOMB:
cp = "Bombed";
break;
case MINE:
case GMINE:
cp = "Blown apart";
break;
case SLIME:
cp = "Slimed";
if (credit != NULL)
credit->i_slime++;
break;
case LAVA:
cp = "Baked";
break;
case DSHOT:
cp = "Eliminated";
break;
}
if (credit == NULL) {
char *blame;
switch (shot_type) {
case MINE:
case GMINE:
blame = "a mine";
break;
default:
blame = "act of God";
break;
}
(void) snprintf(victim->p_death, sizeof victim->p_death,
"| %s by %s |", cp, blame);
return;
}
(void) snprintf(victim->p_death, sizeof victim->p_death,
"| %s by %s |", cp, credit->i_name);
if (victim == attacker) {
credit->i_kills--;
credit->i_bkills++;
}
else if (victim->p_ident->i_team == ' '
|| victim->p_ident->i_team != credit->i_team) {
credit->i_kills++;
credit->i_gkills++;
}
else {
credit->i_kills--;
credit->i_bkills++;
}
credit->i_score = credit->i_kills / (double) credit->i_entries;
victim->p_ident->i_deaths++;
if (victim->p_nchar == 0)
victim->p_ident->i_stillb++;
if (attacker) {
attacker->p_damcap += conf_killgain;
attacker->p_damage -= conf_killgain;
if (attacker->p_damage < 0)
attacker->p_damage = 0;
outyx(attacker, STAT_DAM_ROW, STAT_VALUE_COL, "%2d/%2d",
attacker->p_damage, attacker->p_damcap);
outyx(attacker, STAT_KILL_ROW, STAT_VALUE_COL, "%3d",
(attacker->p_damcap - conf_maxdam) / 2);
y = STAT_PLAY_ROW + 1 + (attacker - Player);
outyx(ALL_PLAYERS, y, STAT_NAME_COL,
"%5.2f", attacker->p_ident->i_score);
}
}
static void
zap(PLAYER *pp, FLAG was_player)
{
int len;
BULLET *bp;
PLAYER *np;
int x, y;
int savefd;
if (was_player) {
if (pp->p_undershot)
fixshots(pp->p_y, pp->p_x, pp->p_over);
drawplayer(pp, FALSE);
Nplayer--;
}
len = strlen(pp->p_death);
x = (WIDTH - len) / 2;
outyx(pp, HEIGHT / 2, x, "%s", pp->p_death);
memset(pp->p_death + 1, '-', len - 2);
pp->p_death[0] = '+';
pp->p_death[len - 1] = '+';
outyx(pp, HEIGHT / 2 - 1, x, "%s", pp->p_death);
outyx(pp, HEIGHT / 2 + 1, x, "%s", pp->p_death);
cgoto(pp, HEIGHT, 0);
savefd = pp->p_fd;
if (was_player) {
int expl_charge;
int expl_type;
int ammo_exploding;
for (bp = Bullets; bp != NULL; bp = bp->b_next) {
if (bp->b_owner == pp)
bp->b_owner = NULL;
if (bp->b_x == pp->p_x && bp->b_y == pp->p_y)
bp->b_over = SPACE;
}
ammo_exploding = rand_num(pp->p_ammo);
expl_charge = rand_num(ammo_exploding + 1);
if (pp->p_ammo == 0)
expl_charge = 0;
else if (ammo_exploding >= pp->p_ammo - 1) {
expl_charge = pp->p_ammo;
expl_type = SLIME;
} else {
int btype, stype;
for (btype = MAXBOMB - 1; btype > 0; btype--)
if (expl_charge >= shot_req[btype])
break;
for (stype = MAXSLIME - 1; stype > 0; stype--)
if (expl_charge >= slime_req[stype])
break;
if (btype >= 0 && stype >= 0) {
if (shot_req[btype] > slime_req[stype])
btype = -1;
}
if (btype >= 0) {
expl_type = shot_type[btype];
expl_charge = shot_req[btype];
} else
expl_type = SLIME;
}
if (expl_charge > 0) {
char buf[BUFSIZ];
(void) add_shot(expl_type, pp->p_y, pp->p_x,
pp->p_face, expl_charge, (PLAYER *) NULL,
TRUE, SPACE);
snprintf(buf, sizeof buf, "%s detonated.",
pp->p_ident->i_name);
message(ALL_PLAYERS, buf);
while (pp->p_nboots-- > 0) {
for (np = Boot; np < &Boot[NBOOTS]; np++)
if (np->p_flying < 0)
break;
#ifdef DIAGNOSTIC
if (np >= &Boot[NBOOTS])
err(1, "Too many boots");
#endif
np->p_undershot = FALSE;
np->p_x = pp->p_x;
np->p_y = pp->p_y;
np->p_flying = rand_num(20);
np->p_flyx = 2 * rand_num(6) - 5;
np->p_flyy = 2 * rand_num(6) - 5;
np->p_over = SPACE;
np->p_face = BOOT;
showexpl(np->p_y, np->p_x, BOOT);
}
}
else if (pp->p_nboots > 0) {
if (pp->p_nboots == 2)
Maze[pp->p_y][pp->p_x] = BOOT_PAIR;
else
Maze[pp->p_y][pp->p_x] = BOOT;
if (pp->p_undershot)
fixshots(pp->p_y, pp->p_x,
Maze[pp->p_y][pp->p_x]);
}
volcano += pp->p_ammo - expl_charge;
if (conf_volcano && rand_num(100) < volcano /
conf_volcano_max) {
do {
x = rand_num(WIDTH / 2) + WIDTH / 4;
y = rand_num(HEIGHT / 2) + HEIGHT / 4;
} while (Maze[y][x] != SPACE);
(void) add_shot(LAVA, y, x, LEFTS, volcano,
(PLAYER *) NULL, TRUE, SPACE);
volcano = 0;
message(ALL_PLAYERS, "Volcano eruption.");
}
if (conf_drone && rand_num(100) < 2) {
do {
x = rand_num(WIDTH / 2) + WIDTH / 4;
y = rand_num(HEIGHT / 2) + HEIGHT / 4;
} while (Maze[y][x] != SPACE);
add_shot(DSHOT, y, x, rand_dir(),
shot_req[conf_mindshot +
rand_num(MAXBOMB - conf_mindshot)],
(PLAYER *) NULL, FALSE, SPACE);
}
sendcom(pp, ENDWIN, ' ');
(void) fclose(pp->p_output);
End_player--;
if (pp != End_player) {
memcpy(pp, End_player, sizeof *pp);
outyx(ALL_PLAYERS,
STAT_PLAY_ROW + 1 + (pp - Player),
STAT_NAME_COL,
"%5.2f%c%-10.10s %c",
pp->p_ident->i_score, stat_char(pp),
pp->p_ident->i_name, pp->p_ident->i_team);
}
cgoto(ALL_PLAYERS, STAT_PLAY_ROW + 1 + Nplayer, STAT_NAME_COL);
ce(ALL_PLAYERS);
}
else {
sendcom(pp, ENDWIN, LAST_PLAYER);
(void) fclose(pp->p_output);
End_monitor--;
if (pp != End_monitor) {
memcpy(pp, End_monitor, sizeof *pp);
outyx(ALL_PLAYERS,
STAT_MON_ROW + 1 + (pp - Player), STAT_NAME_COL,
"%5.5s %-10.10s %c", " ",
pp->p_ident->i_name, pp->p_ident->i_team);
}
cgoto(ALL_PLAYERS,
STAT_MON_ROW + 1 + (End_monitor - Monitor),
STAT_NAME_COL);
ce(ALL_PLAYERS);
}
FD_CLR(savefd, &Fds_mask);
if (Num_fds == savefd + 1) {
Num_fds = Socket;
if (Server_socket > Socket)
Num_fds = Server_socket;
for (np = Player; np < End_player; np++)
if (np->p_fd > Num_fds)
Num_fds = np->p_fd;
for (np = Monitor; np < End_monitor; np++)
if (np->p_fd > Num_fds)
Num_fds = np->p_fd;
Num_fds++;
}
}
int
rand_num(int range)
{
return (arc4random_uniform(range));
}
static int
havechar(PLAYER *pp)
{
int ret;
if (pp->p_ncount < pp->p_nchar)
return TRUE;
if (!FD_ISSET(pp->p_fd, &Have_inp))
return FALSE;
FD_CLR(pp->p_fd, &Have_inp);
check_again:
errno = 0;
ret = read(pp->p_fd, pp->p_cbuf, sizeof pp->p_cbuf);
if (ret == -1) {
if (errno == EINTR)
goto check_again;
if (errno == EAGAIN) {
#ifdef DEBUG
warn("Have_inp is wrong for %d", pp->p_fd);
#endif
return FALSE;
}
logit(LOG_INFO, "read");
}
if (ret > 0) {
pp->p_nchar = ret;
} else {
pp->p_cbuf[0] = 'q';
pp->p_nchar = 1;
}
pp->p_ncount = 0;
return TRUE;
}
void
cleanup(int eval)
{
PLAYER *pp;
cgoto(ALL_PLAYERS, HEIGHT, 0);
sendcom(ALL_PLAYERS, ENDWIN, LAST_PLAYER);
for (pp = Player; pp < End_player; pp++)
(void) fclose(pp->p_output);
for (pp = Monitor; pp < End_monitor; pp++)
(void) fclose(pp->p_output);
(void) close(Socket);
logx(LOG_INFO, "game over");
exit(eval);
}
static void
send_stats(void)
{
FILE *fp;
int s;
struct sockaddr_in sockstruct;
socklen_t socklen;
socklen = sizeof sockstruct;
s = accept4(Status, (struct sockaddr *) &sockstruct, &socklen,
SOCK_NONBLOCK);
if (s < 0) {
if (errno == EINTR)
return;
logx(LOG_ERR, "accept");
return;
}
fp = fdopen(s, "w");
if (fp == NULL) {
logit(LOG_ERR, "fdopen");
(void) close(s);
return;
}
print_stats(fp);
(void) fclose(fp);
}
void
print_stats(FILE *fp)
{
IDENT *ip;
PLAYER *pp;
fputs("Name\t\tScore\tDucked\tAbsorb\tFaced\tShot\tRobbed\tMissed\tSlimeK\n", fp);
for (ip = Scores; ip != NULL; ip = ip->i_next) {
fprintf(fp, "%s%c%c%c\t", ip->i_name,
ip->i_team == ' ' ? ' ' : '[',
ip->i_team,
ip->i_team == ' ' ? ' ' : ']'
);
if (strlen(ip->i_name) + 3 < 8)
putc('\t', fp);
fprintf(fp, "%.2f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
ip->i_score, ip->i_ducked, ip->i_absorbed,
ip->i_faced, ip->i_shot, ip->i_robbed,
ip->i_missed, ip->i_slime);
}
fputs("\n\nName\t\tEnemy\tFriend\tDeaths\tStill\tSaved\tConnect\n", fp);
for (ip = Scores; ip != NULL; ip = ip->i_next) {
fprintf(fp, "%s%c%c%c\t", ip->i_name,
ip->i_team == ' ' ? ' ' : '[',
ip->i_team,
ip->i_team == ' ' ? ' ' : ']'
);
if (strlen(ip->i_name) + 3 < 8)
putc('\t', fp);
fprintf(fp, "%d\t%d\t%d\t%d\t%d\t",
ip->i_gkills, ip->i_bkills, ip->i_deaths,
ip->i_stillb, ip->i_saved);
for (pp = Player; pp < End_player; pp++)
if (pp->p_ident == ip)
putc('p', fp);
for (pp = Monitor; pp < End_monitor; pp++)
if (pp->p_ident == ip)
putc('m', fp);
putc('\n', fp);
}
}
static void
siginfo(int sig)
{
int tty;
FILE *fp;
if ((tty = open(_PATH_TTY, O_WRONLY)) >= 0) {
fp = fdopen(tty, "w");
print_stats(fp);
answer_info(fp);
fclose(fp);
}
}
static void
clear_scores(void)
{
IDENT *ip, *nextip;
for (ip = Scores; ip != NULL; ip = nextip) {
nextip = ip->i_next;
free((char *) ip);
}
Scores = NULL;
}
static void
announce_game(void)
{
}
static void
handle_wkport(int fd)
{
struct sockaddr fromaddr;
socklen_t fromlen;
u_int16_t query;
u_int16_t response;
fromlen = sizeof fromaddr;
if (recvfrom(fd, &query, sizeof query, 0, &fromaddr, &fromlen) == -1)
{
logit(LOG_WARNING, "recvfrom");
return;
}
#ifdef DEBUG
fprintf(stderr, "query %d (%s) from %s:%d\n", query,
query == C_MESSAGE ? "C_MESSAGE" :
query == C_SCORES ? "C_SCORES" :
query == C_PLAYER ? "C_PLAYER" :
query == C_MONITOR ? "C_MONITOR" : "?",
inet_ntoa(((struct sockaddr_in *)&fromaddr)->sin_addr),
ntohs(((struct sockaddr_in *)&fromaddr)->sin_port));
#endif
query = ntohs(query);
switch (query) {
case C_MESSAGE:
if (Nplayer <= 0)
return;
response = Nplayer;
break;
case C_SCORES:
response = stat_port;
break;
case C_PLAYER:
case C_MONITOR:
if (query == C_MONITOR && Nplayer <= 0)
return;
response = sock_port;
break;
default:
logit(LOG_INFO, "unknown udp query %d", query);
return;
}
response = ntohs(response);
if (sendto(fd, &response, sizeof response, 0,
&fromaddr, sizeof fromaddr) == -1)
logit(LOG_WARNING, "sendto");
}