/* pret.c
*/

/*
 * Pre T entertainment by Nick Slager
 * Based on Newbie Server robot by Jeff Nowakowski
*/


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <pwd.h>
#include <ctype.h>
#include <time.h>
#include "defs.h"
#include "struct.h"
#include "data.h"
#include "proto.h"
#include "alarm.h"
#include "roboshar.h"
#include "pretdefs.h"
#include "planet.h"
#include "util.h"
#include "slotmaint.h"

int debug=0;

/* define the name of the moderation bot - please note that due to the way */
/* messages are handled and the formatting of those messages care must be  */
/* taken to ensure that the bot name does not exceed 5 characters.  If the */
/* desired name is larger than 5 chars the message routines will need to   */
/* have their formatting and contents corrected */
char *roboname = "Kathy";

#define NUMNAMES        20

/* borrowed from daemonII.c until we find a better place */
#define PLAYERFUSE      1

static char    *names[NUMNAMES] =
{"Annihilator", "Banisher", "Blaster",
 "Demolisher", "Destroyer", "Eliminator",
 "Eradicator", "Exiler", "Obliterator",
 "Razer", "Demoralizer", "Smasher",
 "Shredder", "Vanquisher", "Wrecker",
 "Ravager", "Despoiler", "Abolisher",
 "Emasculator", "Decimator"};

static  char    hostname[64];

int target;  /* Terminator's target 7/27/91 TC */
int phrange; /* phaser range 7/31/91 TC */
int trrange; /* tractor range 8/2/91 TC */
int ticks = 0;
int oldmctl;
static int realT = 0;
int team1 = 0;
int team2 = 0;
static struct planet savedplanets[MAXPLANETS];
static int galaxysaved = 0;
time_t savedtime;
time_t now;

static void cleanup(int);
void checkmess();
static void obliterate(int wflag, char kreason, int killRobots, int resetShip);
static void start_a_robot(char *team);
static void stop_a_robot(void);
#ifndef ROBOTS_STAY_IF_PLAYERS_LEAVE
static int is_robots_only(void);
#endif
static int totalRobots(int team);
static void exitRobot(void);
static char * namearg(void);
static int num_players(int *next_team);
static void stop_this_bot(struct player * p, char *why);
static void save_armies(struct player *p);
static void resetPlanets(void);
static void checkPreTVictory();
static int num_humans(int team);
static int num_humans_alive();
static int totalPlayers(int noteam);
static void terminate(int);
static void savegalaxy(void);
static void restoregalaxy(void);
static void save_carried_armies(void);
 
static void
reaper(int sig)
{
    int pid, stat = 0;

    while ((pid = WAIT3(&stat, WNOHANG, 0)) > 0) ;
    HANDLE_SIG(SIGCHLD, reaper);
}

#ifdef PRETSERVER
int
main(argc, argv)
     int             argc;
     char           *argv[];
{
    int pno;
 
#ifndef TREKSERVER
    if (gethostname(hostname, 64) != 0) {
        perror("gethostname");
        exit(1);
    }
#else
    strcpy(hostname, TREKSERVER);
#endif

    srandom(time(NULL));

    getpath();
    (void) SIGNAL(SIGCHLD, reaper);
    openmem(1);
    strcpy(robot_host,REMOTEHOST);
    readsysdefaults();
    alarm_init();
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
    if (!debug)
        SIGNAL(SIGINT, terminate);

    target = -1;                /* no target 7/27/91 TC */
    if ((pno = pickslot(QU_PRET_DMN)) < 0) {
       printf("exiting! due pickslot returning %d\n", pno);
       exit(0);
    }
    me = &players[pno];
    myship = &me->p_ship;
    mystats = &me->p_stats;
    lastm = mctl->mc_current;
    /* At this point we have memory set up.  If we aren't a fleet, we don't
       want to replace any other robots on this team, so we'll check the
       other players and get out if there are any on our team.
       */

    robonameset(me); /* set the robot@nowhere fields */
    enter(4, 0, pno, STARBASE, roboname); /* was BATTLESHIP 8/9/91 TC */

    me->p_pos = -1;                     /* So robot stats don't get saved */
    me->p_flags |= (PFROBOT | PFCLOAK); /* Mark as a robot and hide it */
    
    /* don't appear in the galaxy */
    p_x_y_set(me, -100000, -100000);
    me->p_hostile = 0;
    me->p_swar = 0;
    me->p_war = 0;
    me->p_team = 0;     /* indep */

    oldmctl = mctl->mc_current;

#ifdef nodef
    for (i = 0; i <= oldmctl; i++) {
        check_command(&messages[i]);
    }
#endif

    if (status->tourn)
        realT = 1;
    else
        status->gameup |= GU_PRET;

    me->p_process = getpid();
    p_ups_set(me, 10 / HOWOFTEN);

    /* set up team1 and team2 so we're not always playing rom/fed */
    team1 = (random()%2) ? FED : KLI;
    team2 = (random()%2) ? ROM : ORI;

    me->p_status = POBSERV;              /* Put robot in game */
    if (!status->tourn)
        resetPlanets();

    while ((status->gameup) & GU_GAMEOK) {
        alarm_wait_for();
        checkmess();
        update_sys_defaults();
    }
    cleanup(0);
    return 0;
}

void checkmess()
{ 
    static int no_bots = 0;
    static int time_in_T = 0;
#ifndef ROBOTS_STAY_IF_PLAYERS_LEAVE
    static int no_humans = 0;
#endif

    me->p_ghostbuster = 0;         /* keep ghostbuster away */
    if (me->p_status != POBSERV){  /*So I'm not alive now...*/
        ERROR(2,("ERROR: %s died??\n", roboname));
        cleanup(0);   /* dead for some unpredicted reason like xsg */
    }

    /* exit if daemon terminates use of shared memory */
    if (forgotten()) exit(1);

    ticks++;

#ifndef ROBOTS_STAY_IF_PLAYERS_LEAVE
    /* End the current game if no humans for 60 seconds. */
    if ((ticks % ROBOCHECK) == 0) {
        if (no_humans >= 60)
            cleanup(0); /* Doesn't return. */

        if (is_robots_only())
            no_humans += ROBOCHECK / PERSEC;
        else
            no_humans = 0;
    }
#endif

    /* Check to see if we should start adding bots again */
    if ((ticks % ROBOCHECK) == 0) {
        if ((no_bots > time_in_T || no_bots >= 300) && realT) {
            if (pret_save_galaxy) {
                messAll(255,roboname,"*** Pre-T Entertainment restarting. T-mode galaxy saved. ***");
                messAll(255,roboname,"*** Galaxy will be restored if T-mode starts again within %d minutes ***", pret_galaxy_lifetime/60);
                if (pret_save_armies)
                    save_carried_armies();
                savegalaxy();
                galaxysaved = 1;
                savedtime = time((time_t *) 0);
            } else {
                messAll(255,roboname,"*** Pre-T Entertainment restarting. ***");
            }
            resetPlanets();
            realT = 0;
            status->gameup |= GU_PRET;
        }

        if (num_humans(0) < 8 && realT) {
            if((no_bots % 60) == 0) {
                messAll(255,roboname,"Pre-T Entertainment will start in %d minutes if T-mode doesn't return...",
                        (((300<time_in_T)?300:time_in_T)-no_bots)/60);
            }
            no_bots += ROBOCHECK / PERSEC;
        }
        else
            no_bots = 0;
    }

    /* check if either side has won */
    if ((ticks % ROBOCHECK) == 0) {
        checkPreTVictory();
    }

    /* Stop a robot. */
    if ((ticks % ROBOEXITWAIT) == 0) {
        if(robot_debug_target != -1) {
            messOne(255, roboname, robot_debug_target,
                    "Total Players: %d  Current bots: %d  Current human players: %d",
                    totalPlayers(1), totalRobots(0), num_humans(0));
        }
        if(totalPlayers(1) > PT_MAX_WITH_ROBOTS) {
            if(robot_debug_target != -1) {
                messOne(255, roboname, robot_debug_target, "Stopping a robot");
                messOne(255, roboname, robot_debug_target, "Current bots: %d  Current human players: %d",
                        totalRobots(0), num_humans(0));
            }
            stop_a_robot();
        }
    }

    /* Start a robot */
    if ((ticks % ROBOCHECK) == 0) {

        if (num_humans_alive() > 0 &&
            totalRobots(0) < PT_ROBOTS &&
            totalPlayers(1) < PT_MAX_WITH_ROBOTS &&
            realT == 0)
        {
            int next_team = 0;
            num_players(&next_team);

            if (robot_debug_target != -1) {
                messOne(255, roboname, robot_debug_target, "Starting a robot");
                messOne(255, roboname, robot_debug_target, "Current bots: %d  Current human players: %d",
                        totalRobots(0), num_humans(0));
            }
            if (next_team == FED)
                start_a_robot("-Tf");
            else if (next_team == ROM)
                start_a_robot("-Tr");
            else if (next_team == ORI)
                start_a_robot("-To");
            else if (next_team == KLI)
                start_a_robot("-Tk");
            else
                fprintf(stderr, "Start_a_robot team select (%d) failed.\n", next_team);
            status->gameup |= GU_PRET;
        }
    }

   /* Reset for real T mode ? */
   if ((ticks % ROBOCHECK) == 0) {
        if(totalRobots(0) == 0 && totalPlayers(0) >= 8) {
            time_in_T += ROBOCHECK / PERSEC;
            if(realT == 0) {
                time_in_T = 0;
                realT = 1;
                status->gameup &= ~GU_BOT_IN_GAME;
                messAll(255,roboname,"Resetting for real T-mode!");
                obliterate(0, TOURNSTART, 0, 1);
                if (pret_save_galaxy) {
                    if (galaxysaved)
                    {
                        now = time((time_t *) 0);
                        if ((now - savedtime) < pret_galaxy_lifetime) {
                            messAll(255,roboname,"Restoring previous T-mode galaxy.");
                            restoregalaxy();
                        } else {
                            messAll(255,roboname,"Saved T-mode galaxy expired - creating new galaxy");
                            resetPlanets();
                        }
                        galaxysaved = 0;
                    }
                    else
                        resetPlanets();
                }
                status->gameup &= ~GU_PRET;
            }
        }
    }

    if ((ticks % SENDINFO) == 0) {
        if (totalRobots(0) > 0) {
            messAll(255,roboname,"              Welcome to the Pre-T Entertainment!");
            messAll(255,roboname," ");
            messAll(255,roboname,"During Pre-T mode you are permitted to bomb and take planets.");
            messAll(255,roboname,"However, rank and your stats will NOT increase during this time.");
            messAll(255,roboname," ");
            messAll(255,roboname,"Your team wins if you're up by at least %d planets.", pret_planets);
            messAll(255,roboname," ");
            messAll(255,roboname,"Real t-mode will start as soon as there are a minimum of 4 human");
            messAll(255,roboname,"players per team.  Until then robots will enter and exit to");
            messAll(255,roboname,"maintain a minimum 4 vs 4 game.");
        }
    }
    while (oldmctl!=mctl->mc_current) {
        oldmctl++;
        if (oldmctl==MAXMESSAGE) oldmctl=0;
        robohelp(me, oldmctl, roboname);
    }
}

#ifndef ROBOTS_STAY_IF_PLAYERS_LEAVE
static int is_robots_only(void)
{
   return !num_humans(0);
}
#endif

static int totalPlayers(int noteam)
{
   int i;
   struct player *j;
   int count = 0;

   for (i = 0, j = players; i < MAXPLAYER; i++, j++) {
        if (j == me) continue;
        if (j->p_status == PFREE)
            continue;
        if (j->p_flags & PFROBOT)
            continue;
        if (j->p_flags & PFOBSERV)
            continue;
        if (!noteam && (j->p_team == ALLTEAM))
            continue;
        count++;
   }
   return count;
}

static int num_humans(int team)
{
   int i;
   struct player *j;
   int count = 0;

   for (i = 0, j = players; i < MAXPLAYER; i++, j++) {
        if (j == me) continue;
        if (j->p_status == PFREE)
            continue;
        if (j->p_flags & PFROBOT)
            continue;
        if (j->p_status == POBSERV)
            continue;
        if(team != 0 && j->p_team != team)
            continue;
        if (!is_robot(j)) {
            /* Found a human. */
            count++;
            if(robot_debug_target != -1 && robot_debug_level >= 2) {
                messOne(255, roboname, robot_debug_target,
                        "%d: Counting %s (%s %s) as a human",
                        i, j->p_mapchars, j->p_login, j->p_full_hostname);
            }
        }
        else {
            if(robot_debug_target != -1 && robot_debug_level >= 2) {
                messOne(255, roboname, robot_debug_target,
                        "%d: NOT Counting %s (%s %s) as a human",
                        i, j->p_mapchars, j->p_login, j->p_full_hostname);
            }
        }
   }
   return count;
}

static int num_humans_alive()
{
   int i;
   struct player *j;
   int count = 0;

   for (i = 0, j = players; i < MAXPLAYER; i++, j++) {
        if (j == me) continue;
        if (j->p_status != PALIVE)
            continue;
        if (j->p_flags & PFROBOT)
            continue;
        if (!is_robot(j)) {
            count++;
        }
   }
   return count;
}

static void stop_a_robot(void)
{
    int i;
    struct player *j;
    int teamToStop;
    char *why = " to make room for a human player.";

    if(robot_debug_target != -1 && robot_debug_level >= 3) {
        messOne(255, roboname, robot_debug_target, "#1(%d): %d  #2(%d): %d",
                team1, num_humans(team1), team2, num_humans(team2));
    }
    if(num_humans(team1) < num_humans(team2))
        teamToStop = team1;
    else
        teamToStop = team2;

    if(robot_debug_target != -1 && robot_debug_level >= 3) {
        messOne(255, roboname, robot_debug_target,
                "Stopping from %d", teamToStop);
    }

    /* remove a robot, first check for robots not carrying */
    for (i = 0, j = players; i < MAXPLAYER; i++, j++) {
        if (j->p_status != PALIVE) continue;
        if (j->p_team != teamToStop) continue;
        if (j->p_armies) continue;
        if (j == me) continue;
        if (is_robot(j)) {
            stop_this_bot(j, why);
            return;
        }
    }

    /* then ignore the risk of them carrying */
    for (i = 0, j = players; i < MAXPLAYER; i++, j++) {
        if (j->p_status != PALIVE) continue;
        if (j->p_team != teamToStop) continue;
        if (j == me) continue;
        if (is_robot(j)) {
            stop_this_bot(j, why);
            return;
        }
    }
}

static int totalRobots(int team)
{
   int i;
   struct player *j;
   int count = 0;

   for (i = 0, j = players; i < MAXPLAYER; i++, j++) {
        if (j->p_status == PFREE)
            continue;
        if (j->p_flags & PFROBOT)
            continue;
        if (j->p_status == POBSERV)
            continue;
        if (j == me) continue;
        if (team != 0 && j->p_team != team)
            continue;

        if (is_robot(j))
            /* Found a robot. */
            count++;
   }
   return count;
}

static void stop_this_bot(struct player *p, char *why)
{
    char msg[16];

    p->p_ship.s_type = STARBASE;
    p->p_whydead=KQUIT;
    p->p_explode=10;
    p->p_status=PEXPLODE;
    p->p_whodead=0;
    sprintf(msg, "%s->ALL", roboname);

    pmessage(0, MALL, msg, 
        "Robot %s (%2s) was ejected%s",
        p->p_name, p->p_mapchars, why);
    if ((p->p_status != POBSERV) && (p->p_armies>0)) save_armies(p);
}

static void save_carried_armies(void)
{
    int i;
    struct player *j;
    for (i = 0, j = players; i < MAXPLAYER; i++, j++) {
        if (j->p_status == PFREE)
            continue;
        if (j->p_flags & PFROBOT)
            continue;
        if (j->p_status == POBSERV)
            continue;
        if (j->p_status == PDEAD)
            continue;
        if (j == me) continue;

        if (j->p_armies > 0) {
            save_armies(j);
            j->p_armies = 0;
        }
    }
}

static void save_armies(struct player *p)
{
  int i, k;
  char msg[16];
  sprintf (msg, "%s->ALL", roboname);

  k=10*(remap[p->p_team]-1);
  if (k>=0 && k<=30) for (i=0; i<10; i++) {
    if (planets[i+k].pl_owner==p->p_team) {
      planets[i+k].pl_armies += p->p_armies;
      pmessage(0, MALL, msg, "%s's %d arm%s placed on %s",
                     p->p_name, p->p_armies, (p->p_armies == 1) ? "y" : "ies",
                     planets[k+i].pl_name);
      break;
    }
  }
}

static int
num_players(int *next_team)
{
    int i;
    struct player *j;
    int team_count[MAXTEAM+1];

    int c = 0;

    team_count[FED] = 0;
    team_count[KLI] = 0;
    team_count[ROM] = 0;
    team_count[ORI] = 0;

    for (i = 0, j = players; i < MAXPLAYER; i++, j++) {
        if (j->p_status != PFREE && j->p_status != POBSERV && j != me)
            {
                team_count[j->p_team]++;
                c++;
            }
    }

    /* team sanity check */
    if(team_count[FED] != team_count[KLI]) {
      int t = (team_count[FED]>team_count[KLI])?FED:KLI;
      if(team1 != t) {
        team1 = t;
        resetPlanets();
      }
    }
    if(team_count[ROM] != team_count[ORI]) {
      int t = (team_count[ROM]>team_count[ORI])?ROM:ORI;
      if(team2 != t) {
        team2 = t;
        resetPlanets();
      }
    }

    /* Assign which team gets the next robot. */
    if (team_count[team1] > team_count[team2])
        *next_team = team2;
    else
        *next_team = team1;

    return c;
}

static char           *
namearg(void)
{
    int i, k = 0;
    struct player *j;
    char           *name;
    int             namef = 1;

    if (pret_guest) return "Guest";

    while (1) {

        name = names[random() % NUMNAMES];
        k++;

        namef = 0;
        for (i = 0, j = players; i < MAXPLAYER; i++, j++) {
            if (j->p_status != PFREE &&
                strncmp(name, j->p_name, strlen(name) - 1) == 0) {
                namef = 1;
                break;
            }
        }
        if (!namef)
            return name;

        if (k == 50)
            return "guest";
    }
}


static void
start_a_robot(char *team)
{
    int pid;

    pid = fork();
    if (pid == -1) return;
    if (pid == 0) {
        alarm_prevent_inheritance();
        execl(OROBOT, "pretbot",
              team,
              "-h", (strlen(robot_host))?robot_host:hostname,
              "-p", PORT,
              "-n", namearg(),
              "-X", PRE_T_ROBOT_LOGIN,
              "-b", "-O", "-I", "-g",
              "-C", COMFILE, (char *) NULL);
        perror("pretbot'execl");
        _exit(1);
    }
    status->gameup |= GU_BOT_IN_GAME;
}

static void cleanup(int terminate)
{
    struct player *j;
    int i, retry;
    char msg[16];

    sprintf(msg, " because %s is cleaning up.", roboname);

    do {
        /* terminate all robots */
        for (i = 0, j = players; i < MAXPLAYER; i++, j++) {
            if ((j->p_status == PALIVE) && j != me && is_robot(j))
                stop_this_bot(j, msg);
        }

        for (i=0; i<100; i++) {
          usleep(20000);
          me->p_ghostbuster = 0;
        }

        retry=0;
        for (i = 0, j = players; i < MAXPLAYER; i++, j++) {
            if ((j->p_status != PFREE) && j != me && is_robot(j))
                retry++;
        }
    } while (retry);            /* Some robots havn't terminated yet */

    obliterate(0, KOVER, 1, 1);
    status->gameup &= ~GU_PRET;
    if (terminate)
        exitRobot();
}

/* terminate is called as a signal handler and is a wrapper for cleanup() */
static void terminate (int ignored) {
    cleanup (1);
}

/* a pre-t victory is when one team is up by pret_planets planets */
static void checkPreTVictory() {
    int f, r, k, o, i;
    int winner = -1;

    /* fix a potential issue of invalid resets during a t-mode game */
    if (status->tourn) return;

    /* don't interfere with a real game */
    if(totalRobots(0) == 0) return;

    f = r = k = o = 0;
    for(i=0;i<40;++i) {
       if(planets[i].pl_owner == FED) f++; 
       if(planets[i].pl_owner == ROM) r++; 
       if(planets[i].pl_owner == KLI) k++; 
       if(planets[i].pl_owner == ORI) o++; 
    }
    if(f>=10+pret_planets) winner = FED;
    if(r>=10+pret_planets) winner = ROM;
    if(k>=10+pret_planets) winner = KLI;
    if(o>=10+pret_planets) winner = ORI;

    if(winner > 0) {
        messAll(255,roboname,
                "The %s have won this round of pre-T entertainment!",
                team_name(winner));
        /* wait for conquer parade to complete */
        while ((status->gameup & GU_CONQUER)) {
          usleep(20000);
          me->p_ghostbuster = 0;
        }
        obliterate(0, KWINNER, 0, 1);
        resetPlanets();
        galaxysaved = 0;
    }
}

static void resetPlanets(void) {
    int i;
    int owner;
    for(i=0;i<40;i++) {
        switch(i/10) {
            case 0:
                owner = FED;
                break;
            case 1:
                owner = ROM;
                break;
            case 2:
                owner = KLI;
                break;
            default:
                owner = ORI;
                break;
        }
        if(planets[i].pl_armies < 3) 
            planets[i].pl_armies += (random() % 3) + 2;
        if(planets[i].pl_armies > 7) 
            planets[i].pl_armies = 8 - (random() % 3);
        if(owner != team1 && owner != team2)
            planets[i].pl_armies = 30;
        planets[i].pl_owner = owner;
    }
    pl_reset();
}

static void exitRobot(void)
{
    if (me != NULL && me->p_team != ALLTEAM) {
        if (target >= 0) {
            messAll(255,roboname, "I'll be back.");
        }
        else {
            messAll(255,roboname,"#");
            messAll(255,roboname,"#  %s is tired.  "
                    "Pre-T Entertainment is over for now", roboname);
            messAll(255,roboname,"#");
        }
    }

    freeslot(me);
    exit(0);
}


static void obliterate(int wflag, char kreason, int killRobots, int resetShip)
{
    /* 0 = do nothing to war status, 1= make war with all, 2= make
    peace with all */
    struct player *j;

    /* clear torps and plasmas out */
    MZERO(torps, sizeof(struct torp) * MAXPLAYER * (MAXTORP + MAXPLASMA));
    for (j = firstPlayer; j<=lastPlayer; j++) {
        if (j->p_status == PFREE)
            continue;
        if (j->p_status == POBSERV)
            continue;
        if ((j->p_flags & PFROBOT) && killRobots == 0)
            continue;
        if (j == me) continue;
        if ((kreason == TOURNSTART) && (j->p_ship.s_type == STARBASE))
        {
#ifdef LTD_STATS
            if (ltd_offense_rating(j) < sb_minimal_offense)
#else
            if (offenseRating(j) < sb_minimal_offense)
#endif
            {
                j->p_status = PEXPLODE;
                j->p_whydead = TOURNSTART;
                j->p_whodead = me->p_no;
                j->p_explode = 2 * SBEXPVIEWS / PLAYERFUSE;
            }
        }
        if (resetShip)
        {
            j->p_kills = 0;
            if (j->p_ship.s_type != STARBASE)
                j->p_ship.s_plasmacost = -1;
        }
        j->p_ntorp = 0;
        j->p_nplasmatorp = 0;
        j->p_armies = 0;
        p_heal(j);
        if (wflag == 1)
            j->p_hostile = (FED | ROM | ORI | KLI);         /* angry */
        else if (wflag == 2)
            j->p_hostile = 0;       /* otherwise make all peaceful */
        j->p_war = (j->p_swar | j->p_hostile);
    }
}

static void savegalaxy(void)
{
    MCOPY(planets, savedplanets, sizeof(struct planet) * MAXPLANETS);
}

static void restoregalaxy(void)
{
    MCOPY(savedplanets, planets, sizeof(struct planet) * MAXPLANETS);
}

#endif  /* PRETSERVER */


#ifndef PRETSERVER
int
main(argc, argv)
     int             argc;
     char           *argv[];
{
    printf("You don't have PRETSERVER option on.\n");
    return 0;
}

#endif /* PRETSERVER */

