root/games/hunt/huntd/answer.c
/*      $NetBSD: answer.c,v 1.24 2021/05/02 12:50:45 rillig Exp $       */
/*
 * Copyright (c) 1983-2003, Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * + Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * + Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * + Neither the name of the University of California, San Francisco nor
 *   the names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior written
 *   permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: answer.c,v 1.24 2021/05/02 12:50:45 rillig Exp $");
#endif /* not lint */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include "hunt.h"

#define SCOREDECAY      15

static char Ttyname[WIRE_NAMELEN];

static IDENT *get_ident(uint32_t, uint32_t, const char *, char);
static void stmonitor(PLAYER *);
static void stplayer(PLAYER *, int);

bool
answer(void)
{
        PLAYER *pp;
        int newsock;
        static uint32_t mode;
        static char name[WIRE_NAMELEN];
        static char team;
        static int32_t enter_status;
        static socklen_t socklen;
        static uint32_t machine;
        static uint32_t uid;
        static struct sockaddr_storage newaddr;
        char *cp1, *cp2;
        int flags;
        uint32_t version;
        int i;

        socklen = sizeof(newaddr);
        newsock = accept(huntsock, (struct sockaddr *)&newaddr, &socklen);
        if (newsock < 0) {
                if (errno == EINTR)
                        return false;
                complain(LOG_ERR, "accept");
                cleanup(1);
        }

        /*
         * XXX this is pretty bollocks
         */
        switch (newaddr.ss_family) {
            case AF_INET:
                machine = ((struct sockaddr_in *)&newaddr)->sin_addr.s_addr;
                machine = ntohl(machine);
                break;
            case AF_INET6:
                {
                        struct sockaddr_in6 *sin6;

                        sin6 = (struct sockaddr_in6 *)&newaddr;
                        assert(sizeof(sin6->sin6_addr.s6_addr) >
                               sizeof(machine));
                        memcpy(&machine, sin6->sin6_addr.s6_addr,
                               sizeof(machine));
                }
                break;
            case AF_UNIX:
                machine = gethostid();
                break;
            default:
                machine = 0; /* ? */
                break;
        }

        version = htonl((uint32_t) HUNT_VERSION);
        (void) write(newsock, &version, sizeof(version));
        (void) read(newsock, &uid, sizeof(uid));
        uid = ntohl(uid);
        (void) read(newsock, name, sizeof(name));
        (void) read(newsock, &team, 1);
        (void) read(newsock, &enter_status, sizeof(enter_status));
        enter_status = ntohl(enter_status);
        (void) read(newsock, Ttyname, sizeof(Ttyname));
        (void) read(newsock, &mode, sizeof(mode));
        mode = ntohl(mode);

        /*
         * Ensure null termination.
         */
        name[sizeof(name)-1] = '\0';
        Ttyname[sizeof(Ttyname)-1] = '\0';

        /*
         * Turn off blocking I/O, so a slow or dead terminal won't stop
         * the game.  All subsequent reads check how many bytes they read.
         */
        flags = fcntl(newsock, F_GETFL, 0);
        flags |= O_NDELAY;
        (void) fcntl(newsock, F_SETFL, flags);

        /*
         * Make sure the name contains only printable characters
         * since we use control characters for cursor control
         * between driver and player processes
         */
        for (cp1 = cp2 = name; *cp1 != '\0'; cp1++)
                if (isprint((unsigned char)*cp1) || *cp1 == ' ')
                        *cp2++ = *cp1;
        *cp2 = '\0';

        if (mode == C_MESSAGE) {
                char    buf[BUFSIZ + 1];
                int     n;

                if (team == ' ')
                        (void) snprintf(buf, sizeof(buf), "%s: ", name);
                else
                        (void) snprintf(buf, sizeof(buf), "%s[%c]: ", name,
                                        team);
                n = strlen(buf);
                for (pp = Player; pp < End_player; pp++) {
                        cgoto(pp, HEIGHT, 0);
                        outstr(pp, buf, n);
                }
                while ((n = read(newsock, buf, BUFSIZ)) > 0)
                        for (pp = Player; pp < End_player; pp++)
                                outstr(pp, buf, n);
                for (pp = Player; pp < End_player; pp++) {
                        ce(pp);
                        sendcom(pp, REFRESH);
                        sendcom(pp, READY, 0);
                        (void) fflush(pp->p_output);
                }
                (void) close(newsock);
                return false;
        }
        else
#ifdef MONITOR
        if (mode == C_MONITOR)
                if (End_monitor < &Monitor[MAXMON]) {
                        pp = End_monitor++;
                        i = pp - Monitor + MAXPL + 3;
                } else {
                        socklen = 0;
                        (void) write(newsock, &socklen,
                                sizeof socklen);
                        (void) close(newsock);
                        return false;
                }
        else
#endif
                if (End_player < &Player[MAXPL]) {
                        pp = End_player++;
                        i = pp - Player + 3;
                } else {
                        socklen = 0;
                        (void) write(newsock, &socklen,
                                sizeof socklen);
                        (void) close(newsock);
                        return false;
                }

#ifdef MONITOR
        if (mode == C_MONITOR && team == ' ')
                team = '*';
#endif
        pp->p_ident = get_ident(machine, uid, name, team);
        pp->p_output = fdopen(newsock, "w");
        pp->p_death[0] = '\0';
        pp->p_fd = newsock;
        fdset[i].fd = newsock;
        fdset[i].events = POLLIN;

        pp->p_y = 0;
        pp->p_x = 0;

#ifdef MONITOR
        if (mode == C_MONITOR)
                stmonitor(pp);
        else
#endif
                stplayer(pp, enter_status);
        return true;
}

#ifdef MONITOR
static void
stmonitor(PLAYER *pp)
{
        int line;
        PLAYER *npp;

        memcpy(pp->p_maze, Maze, sizeof Maze);

        drawmaze(pp);

        (void) snprintf(Buf, sizeof(Buf), "%5.5s%c%-10.10s %c", " ",
                stat_char(pp),
                pp->p_ident->i_name, pp->p_ident->i_team);
        line = STAT_MON_ROW + 1 + (pp - Monitor);
        for (npp = Player; npp < End_player; npp++) {
                cgoto(npp, line, STAT_NAME_COL);
                outstr(npp, Buf, STAT_NAME_LEN);
        }
        for (npp = Monitor; npp < End_monitor; npp++) {
                cgoto(npp, line, STAT_NAME_COL);
                outstr(npp, Buf, STAT_NAME_LEN);
        }

        sendcom(pp, REFRESH);
        sendcom(pp, READY, 0);
        (void) fflush(pp->p_output);
}
#endif

static void
stplayer(PLAYER *newpp, int enter_status)
{
        int x, y;
        PLAYER *pp;

        Nplayer++;

        for (y = 0; y < UBOUND; y++)
                for (x = 0; x < WIDTH; x++)
                        newpp->p_maze[y][x] = Maze[y][x];
        for (     ; y < DBOUND; y++) {
                for (x = 0; x < LBOUND; x++)
                        newpp->p_maze[y][x] = Maze[y][x];
                for (     ; x < RBOUND; x++)
                        newpp->p_maze[y][x] = SPACE;
                for (     ; x < WIDTH;  x++)
                        newpp->p_maze[y][x] = Maze[y][x];
        }
        for (     ; y < HEIGHT; y++)
                for (x = 0; x < WIDTH; x++)
                        newpp->p_maze[y][x] = Maze[y][x];

        do {
                x = rand_num(WIDTH - 1) + 1;
                y = rand_num(HEIGHT - 1) + 1;
        } while (Maze[y][x] != SPACE);
        newpp->p_over = SPACE;
        newpp->p_x = x;
        newpp->p_y = y;
        newpp->p_undershot = false;

#ifdef FLY
        if (enter_status == Q_FLY) {
                newpp->p_flying = rand_num(20);
                newpp->p_flyx = 2 * rand_num(6) - 5;
                newpp->p_flyy = 2 * rand_num(6) - 5;
                newpp->p_face = FLYER;
        }
        else
#endif
        {
                newpp->p_flying = -1;
                newpp->p_face = rand_dir();
        }
        newpp->p_damage = 0;
        newpp->p_damcap = MAXDAM;
        newpp->p_nchar = 0;
        newpp->p_ncount = 0;
        newpp->p_nexec = 0;
        newpp->p_ammo = ISHOTS;
#ifdef BOOTS
        newpp->p_nboots = 0;
#endif
        if (enter_status == Q_SCAN) {
                newpp->p_scan = SCANLEN;
                newpp->p_cloak = 0;
        }
        else {
                newpp->p_scan = 0;
                newpp->p_cloak = CLOAKLEN;
        }
        newpp->p_ncshot = 0;

        do {
                x = rand_num(WIDTH - 1) + 1;
                y = rand_num(HEIGHT - 1) + 1;
        } while (Maze[y][x] != SPACE);
        Maze[y][x] = GMINE;
#ifdef MONITOR
        for (pp = Monitor; pp < End_monitor; pp++)
                check(pp, y, x);
#endif

        do {
                x = rand_num(WIDTH - 1) + 1;
                y = rand_num(HEIGHT - 1) + 1;
        } while (Maze[y][x] != SPACE);
        Maze[y][x] = MINE;
#ifdef MONITOR
        for (pp = Monitor; pp < End_monitor; pp++)
                check(pp, y, x);
#endif

        (void) snprintf(Buf, sizeof(Buf), "%5.2f%c%-10.10s %c",
                newpp->p_ident->i_score,
                stat_char(newpp), newpp->p_ident->i_name,
                newpp->p_ident->i_team);
        y = STAT_PLAY_ROW + 1 + (newpp - Player);
        for (pp = Player; pp < End_player; pp++) {
                if (pp != newpp) {
                        char    smallbuf[16];

                        pp->p_ammo += NSHOTS;
                        newpp->p_ammo += NSHOTS;
                        cgoto(pp, y, STAT_NAME_COL);
                        outstr(pp, Buf, STAT_NAME_LEN);
                        (void) snprintf(smallbuf, sizeof(smallbuf),
                                        "%3d", pp->p_ammo);
                        cgoto(pp, STAT_AMMO_ROW, STAT_VALUE_COL);
                        outstr(pp, smallbuf, 3);
                }
        }
#ifdef MONITOR
        for (pp = Monitor; pp < End_monitor; pp++) {
                cgoto(pp, y, STAT_NAME_COL);
                outstr(pp, Buf, STAT_NAME_LEN);
        }
#endif

        drawmaze(newpp);
        drawplayer(newpp, true);
        look(newpp);
#ifdef FLY
        if (enter_status == Q_FLY)
                /* Make sure that the position you enter in will be erased */
                showexpl(newpp->p_y, newpp->p_x, FLYER);
#endif
        sendcom(newpp, REFRESH);
        sendcom(newpp, READY, 0);
        (void) fflush(newpp->p_output);
}

/*
 * rand_dir:
 *      Return a random direction
 */
int
rand_dir(void)
{
        switch (rand_num(4)) {
          case 0:
                return LEFTS;
          case 1:
                return RIGHT;
          case 2:
                return BELOW;
          case 3:
                return ABOVE;
        }
        /* NOTREACHED */
        return(-1);
}

/*
 * get_ident:
 *      Get the score structure of a player
 */
static IDENT *
get_ident(uint32_t machine, uint32_t uid, const char *name, char team)
{
        IDENT *ip;
        static IDENT punt;

        for (ip = Scores; ip != NULL; ip = ip->i_next)
                if (ip->i_machine == machine
                &&  ip->i_uid == uid
                &&  ip->i_team == team
                &&  strncmp(ip->i_name, name, WIRE_NAMELEN) == 0)
                        break;

        if (ip != NULL) {
                if (ip->i_entries < SCOREDECAY)
                        ip->i_entries++;
                else
                        ip->i_kills = (ip->i_kills * (SCOREDECAY - 1))
                                / SCOREDECAY;
                ip->i_score = ip->i_kills / (double) ip->i_entries;
        }
        else {
                ip = malloc(sizeof(*ip));
                if (ip == NULL) {
                        /* Fourth down, time to punt */
                        ip = &punt;
                }
                ip->i_machine = machine;
                ip->i_team = team;
                ip->i_uid = uid;
                strncpy(ip->i_name, name, sizeof(ip->i_name));
                ip->i_kills = 0;
                ip->i_entries = 1;
                ip->i_score = 0;
                ip->i_absorbed = 0;
                ip->i_faced = 0;
                ip->i_shot = 0;
                ip->i_robbed = 0;
                ip->i_slime = 0;
                ip->i_missed = 0;
                ip->i_ducked = 0;
                ip->i_gkills = ip->i_bkills = ip->i_deaths = 0;
                ip->i_stillb = ip->i_saved = 0;
                ip->i_next = Scores;
                Scores = ip;
        }

        return ip;
}