/*
 *  ntscmds.c		
 */

/*
   Command interface routines general to ntserv processes.  This
   is a replacement for the spagetti compile method used previously in
   commands.c .  This file contains the defines, and code for performing
   general commands issued to the server.
   It doesn't contain the PUCK/DOG/INL and general command checking
   routines that used to be grouped together in commands.c
*/

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include "defs.h"
#include "struct.h"
#include "data.h"
#include "gencmds.h"

#if defined (ALLOW_EJECT)
int do_player_eject();
#endif

#if defined (AUTO_INL)
int do_start_inl();
#endif

#if defined (AUTO_PRACTICE)
int do_start_basep();
#endif

#if defined(AUTO_HOCKEY)
int do_start_puck();
#endif

#if defined(AUTO_DOGFIGHT)
int do_start_mars();
#endif

#if defined (TRIPLE_PLANET_MAYHEM)
char *teamNames[9] = {" ", "Federation", "Romulans", " ", "Klingons",
                      " ", " ", " ", "Orions"};
int do_balance();
int do_triple_planet_mayhem();
#endif

const char myname[] = {"GOD"};

int do_sbstats_query();
int do_time_msg();
int do_queue_msg();

#ifdef GENO_COUNT
int do_genos_query();
#endif

int do_client_query();
int do_ping_query();
int do_stats_query();
#ifdef RSA
int bounceRSAClientType();
#endif
int bouncePingStats();
int bounceSessionStats();
int bounceSBStats();

/********* COMMANDS LIST ********
	Note: - The commands *must* be in upper case. All commands are converted
		to upper case before be checked. 
	      - Votes are entered in the Voting list.
	The first field is the command, the second is a description string 
		(up to 50 chars.), lastly is the function to call.
*/

static struct command_handler_2 nts_commands[] =
{
    { "Possible commands are:", C_DESC },
    { "HELP",
	        0,
		"Show this help information",
		do_help },     			/* HELP */
    { "CLIENT",
		C_PLAYER,
		"Show a user's client. Ex: 'CLIENT 0'",
		do_client_query },		/* CLIENT */
    { "PING",
		C_PLAYER,
		"Show client's ping (lag). Ex: 'PING 0'",
		do_ping_query },		/* PING */
    { "STATS",
		C_PLAYER,
		"Show player's stats during t-mode. Ex: 'STATS 0'",
		do_stats_query },		/* STATS */
#ifdef ALLOW_PAUSE
    { "PAUSE",
                0,
                "Pause the game",
                game_pause },
    { "UNPAUSE",
                0,
                "Resume the game",
                game_resume },
#endif
    { "SBSTATS",
                C_PLAYER,
                "Show player's starbase stats. Ex: 'SBSTATS 0'",
                do_sbstats_query },             /* SBSTATS */
						/***** Vanilla commands */
    { "TIME",
		0,
		"Show time left on Genocide timer.",
		do_time_msg },			/* TIME */
    { "QUEUE",
		0,
		"Show how many people are on the queue.",
		do_queue_msg },			/* QUEUE */
#ifdef GENO_COUNT
    { "GENOS",
		C_PLAYER,
		"Show player's winning genocides.  Ex: 'GENOS 0'",
		do_genos_query },                       /* GENOS */
#endif
#ifdef VOTING

    { "The following votes can be used:  (M=Majority, T=Team vote)", C_DESC },
    { "Ejection Votes are recorded for the god to review.", C_DESC },
#if defined(ALLOW_EJECT)
    { "EJECT",
	C_VC_TEAM | C_GLOG | C_PLAYER,
	"To eject a player. Ex: 'EJECT 0'", 
	do_player_eject,				/* EJECT */
	2, 0, 120, 600},
#endif
#if defined(TRIPLE_PLANET_MAYHEM)
    { "TRIPLE",
        C_VC_ALL | C_GLOG,
        "Start triple planet mayhem by vote",
        do_triple_planet_mayhem,
	2, 22, 0},
    { "BALANCE",
        C_VC_ALL | C_GLOG,
        "Request team randomise & balance",
        do_balance,
        4, 23, 0 },
#endif
#if defined(AUTO_INL)
  { "INL",
	C_VC_ALL | C_GLOG,
	"Start game under INL rules.",
	do_start_inl,
	1, 20, 0 },
#endif
#if defined(AUTO_PRACTICE)
  { "PRACTICE",
	C_VC_ALL,
	"Start basepractice by majority vote.",
	do_start_basep,
	1, 20, 0 },
#endif
#if defined(AUTO_HOCKEY)
  { "HOCKEY",
	C_VC_ALL | C_GLOG,
	"Start hockey by majority vote.",
	do_start_puck,
	1, 20, 0 },
#endif
#if defined(AUTO_DOGFIGHT)
  { "DOGFIGHT",
	C_VC_ALL | C_GLOG,
	"Start dogfight tournament by majority vote.",
	do_start_mars,
	1, 20, 0 },
#endif
#endif /* VOTING */

    { NULL }
    };

int
check_command(mess)
     struct message *mess;
{
  return check_2_command(mess, nts_commands, 0);
}

#if defined (ALLOW_EJECT)
do_player_eject(who,player,mflags, sendto)
int who, player, mflags, sendto;
{
    register struct player *j;

    j = &players[player];

    if (j->p_status == PFREE){
	pmessage(sendto, mflags, addr_mess(who,MTEAM), 
		"That player is not in the game");
	return;
     }
    if (j->p_flags & PFROBOT) {
	pmessage(sendto, mflags, addr_mess(players[who].p_team,MTEAM), 
		"You cannot eject a robot, twinks!");
	return;
    }
    if (j->p_team != players[who].p_team){
      pmessage(players[who].p_team, MTEAM, 
	addr_mess(players[who].p_team,MTEAM), 
	"You can only eject your own teammates, twinks!");
      return;
    }

    pmessage(0, MALL, addr_mess(who,MALL), 
	" %2s has been ejected by the players", j->p_mapchars);

    eject_player(j->p_no);
}

eject_player(who)
int who;
{
  struct player* j;

  j = &players[who];
  j->p_ship.s_type = STARBASE;
  j->p_whydead=KQUIT;
  j->p_explode=10;
  j->p_status=PEXPLODE;
  j->p_whodead=me->p_no;
}
#endif /* ALLOW_EJECT */

#if defined(AUTO_PRACTICE)
int do_start_basep(who, mflags, sendto)
int who, mflags,sendto;
{
  if (vfork() == 0) {
      (void) SIGNAL(SIGALRM,SIG_DFL);
      execl(Basep, "basep", 0);
      perror(Basep);
      }
}
#endif

#if defined(AUTO_INL)
int do_start_inl(who, mflags, sendto)
int who, mflags, sendto;
{
  if (vfork() == 0) {
      (void) SIGNAL(SIGALRM,SIG_DFL);
      execl(Inl, "inl", 0);
      perror(Inl);
      }
}
#endif

#if defined(AUTO_HOCKEY)
int do_start_puck(who, mflags, sendto)
int who, mflags, sendto;
{
  if (vfork() == 0) {
      (void) SIGNAL(SIGALRM,SIG_DFL);
      execl(Puck, "puck", 0);
      perror(Puck);
      }
}
#endif

#if defined(AUTO_DOGFIGHT)
int do_start_mars(who, mflags, sendto)
int who, mflags, sendto;
{
  if (vfork() == 0) {
      (void) SIGNAL(SIGALRM,SIG_DFL);
      execl(Mars, "mars", 0);
      perror(Mars);
      }
}
#endif

/*** QUERIES ***/
send_query(which,who,from)
char which;
int  who, from;
{
	pmessage2(who, MINDIV, " ", from, "%c", which);
}

/* ARGSUSED */
do_client_query(comm,mess,who)
char *comm;
struct message *mess;
int who;
{
#ifdef RSA
	send_query('#', who, mess->m_from); 
#endif
}

/* ARGSUSED */
do_ping_query(comm,mess,who)
char *comm;
struct message *mess;
int who;
{
      send_query('!', who, mess->m_from);
}

/* ARGSUSED */
do_stats_query(comm,mess,who)
char *comm;
struct message *mess;
int who;
{
      send_query('?', who, mess->m_from); 
}

#ifdef RSA
int bounceRSAClientType(from)
int from;
{
        bounce(from,"Client: %s", RSA_client_type);

        return 1;
}
#endif

int
bounceSessionStats(from)
   int from;
{
    float                          sessionBombing, sessionPlanets,
                                   sessionOffense, sessionDefense;
    int                            deltaArmies, deltaPlanets,
                                   deltaKills, deltaLosses,
                                   deltaTicks;
    deltaPlanets = me->p_stats.st_tplanets - startTplanets;
    deltaArmies = me->p_stats.st_tarmsbomb - startTarms;
    deltaKills = me->p_stats.st_tkills - startTkills;
    deltaLosses = me->p_stats.st_tlosses - startTlosses;
    deltaTicks = me->p_stats.st_tticks - startTticks;


    if (deltaTicks == 0) {
        bounce(from,
                "Session stats are only available during t-mode.");
        return 1; /* non t-mode */
    }

    sessionPlanets = (double ) deltaPlanets * status->timeprod /
        ((double) deltaTicks * status->planets);

    sessionBombing = (double) deltaArmies * status->timeprod /
        ((double) deltaTicks * status->armsbomb);

    sessionOffense = (double) deltaKills * status->timeprod /
        ((double) deltaTicks * status->kills);

    sessionDefense = (double) deltaTicks * status->losses /
        (deltaLosses!=0 ? (deltaLosses * status->timeprod) : (status->timeprod));

    bounce(from,
        "%2s stats: %d planets and %d armies. %d wins/%d losses. %5.2f hours.",
        me->p_mapchars,
        deltaPlanets,
        deltaArmies,
        deltaKills,
        deltaLosses,
        (float) deltaTicks/36000.0);
    bounce(from,
        "Ratings: Pla: %5.2f  Bom: %5.2f  Off: %5.2f  Def: %5.2f  Ratio: %4.2f",
        sessionPlanets,
        sessionBombing,
        sessionOffense,
        sessionDefense,
        (float) deltaKills /
        (float) ((deltaLosses == 0) ? 1 : deltaLosses));
    return 1;
}

int
bounceSBStats(from)
    int from;
{
    float                       sessionRatio, sessionKPH, sessionDPH,
                                overallKPH, overallDPH, overallRatio;
    int                         deltaKills, deltaLosses,
                                deltaTicks;

    deltaKills = me->p_stats.st_sbkills - startSBkills;
    deltaLosses = me->p_stats.st_sblosses - startSBlosses;
    deltaTicks = me->p_stats.st_sbticks - startSBticks;
    overallRatio = (float) (me->p_stats.st_sblosses==0) ?
      (float) me->p_stats.st_sbkills :
      (float) me->p_stats.st_sbkills / (float) me->p_stats.st_sblosses;

/*  if (deltaTicks == 0) {
        bounce(from,
        "No SB time yet this session.");
        return 0;
    } */

    if (deltaTicks != 0) {
        sessionKPH = (float) deltaKills * 36000.0 / (float) deltaTicks;
        sessionDPH = (float) deltaLosses * 36000.0 / (float) deltaTicks;
    } else {
        sessionKPH = sessionDPH = 0.0;
    }

    sessionRatio = (float) (deltaLosses==0) ? (float) deltaKills :
        (float) deltaKills / (float) deltaLosses;

    if (me->p_stats.st_sbticks != 0) {
        overallKPH = (float) me->p_stats.st_sbkills * 36000.0 /
                (float) me->p_stats.st_sbticks;
        overallDPH = (float) me->p_stats.st_sblosses * 36000.0 /
                (float) me->p_stats.st_sbticks;
    } else {
        overallKPH = overallDPH = 0.0;
    }

    bounce(from,
      "%2s overall SB stats: %d wins/%d losses. %5.2f hours. Ratio: %5.2f",
      me->p_mapchars,
      me->p_stats.st_sbkills,
      me->p_stats.st_sblosses,
      (float) me->p_stats.st_sbticks/36000.0,
      overallRatio);

    if (deltaTicks)
        bounce(from,
          "%2s session SB stats: %d wins/%d losses. %5.2f hours. Ratio: %5.2f",
          me->p_mapchars,
          deltaKills,
          deltaLosses,
          (float) deltaTicks/36000.0,
          sessionRatio);
        bounce(from,
          "Kills/Hour: %5.2f (%5.2f total), Deaths/Hour: %4.2f (%4.2f total)",
          sessionKPH,
          overallKPH,
          sessionDPH,
          overallDPH);
    return 1;
}


#ifdef PING
int bouncePingStats(from)
int from;
{
    if(me->p_avrt == -1){
        /* client doesn't support it or server not pinging */
        bounce(from,"No PING stats available for %c%c",
           me->p_mapchars[0], me->p_mapchars[1]);
    }
    else{
        bounce(from,
            "%c%c PING stats: Average: %d ms, Stdv: %d ms, Loss: %d%%/%d%% s->c/c->s",
            me->p_mapchars[0], me->p_mapchars[1],
            me->p_avrt,
            me->p_stdv,
            me->p_pkls_s_c,
            me->p_pkls_c_s);
    }
    return 1;
}
#endif


/* ARGSUSED */
do_sbstats_query(comm,mess,who)
char *comm;
struct message *mess;
int who;
{
      send_query('^', who, mess->m_from);	
}

#ifdef GENO_COUNT
do_genos_query(comm,mess,who)
char *comm;
struct message *mess;
int who;
{
  char *addr;

  addr = addr_mess(mess->m_from,MINDIV);
  pmessage(mess->m_from, MINDIV, addr, "%s has won the game %d times.",
	players[who].p_name,players[who].p_stats.st_genos);
}
#endif


/* ARGSUSED */
do_time_msg(comm,mess)
char *comm;
struct message *mess;
{
  char *teamNames[9] = {" ", "Federation", "Romulans", " ", "Klingons", 
			  " ", " ", " ", "Orions"};
  int who;
  int t;
  char *addr;

  who = mess->m_from;
  addr = addr_mess(who,MINDIV);

  for (t=0;((t<=MAXTEAM)&&(teams[t].s_surrender==0));t++);

  if (t>MAXTEAM) {
    pmessage(who, MINDIV, addr, "No one is considering surrender now.  Go take some planets.");
  } else {
    pmessage(who, MINDIV, addr, "The %s have %d minutes left before they surrender.", teamNames[t],teams[t].s_surrender);
  }
}

/*
 * Give information about the waitqueue status
 */

/* ARGSUSED */
do_queue_msg(comm,mess)
char *comm;
struct message *mess;
{
    int who;
    char *addr;
    int i;

    who = mess->m_from;
    addr = addr_mess(who,MINDIV);

    for (i=0; i < MAXQUEUE; i++) {
        /* Only report open queues that have the report flag set. */
        if (!(queues[i].q_flags & QU_OPEN))   continue;
	if (!(queues[i].q_flags & QU_REPORT)) continue;
      
	if (queues[i].count>0 ) 
  	    pmessage(who, MINDIV, addr, "Approximate %s queue size: %d", 
		     queues[i].q_name, queues[i].count);
	else
	    pmessage(who, MINDIV, addr, "There is no one on the %s queue.",
		     queues[i].q_name);
    }
}

#ifdef TRIPLE_PLANET_MAYHEM
/*
**  16-Jul-1994 James Cameron
**
**  Balances teams according to player statistics.
**  Intended for use with Triple Planet Mayhem
*/

/*
**  Tell all that player will be moved
*/
static void
moveallmsg ( int p_no, int ours, int theirs )
{
    struct player *k = &players[p_no];
    pmessage(0, MALL, addr_mess(myname, MALL),
       "Balance: %16s (%c%c) is to join the %s",
       k->p_name, teamlet[k->p_team], shipnos[p_no], teamNames[ours] );
}

/*
**  Move a player to the specified team, if they are not yet there.
**  Make them peaceful with the new team, and hostile/at war with the
**  other team.
*/
static void 
move(int p_no, int ours, int theirs)
{
  struct player *k = &players[p_no];
  int queue;
    
  if ( k->p_team != ours ) {
    pmessage(k->p_no, MINDIV, addr_mess(k->p_no,MINDIV),
	     "%s: please SWAP SIDES to the %s", k->p_name, teamNames[ours] );
  }
  else {
    pmessage(k->p_no, MINDIV, addr_mess(k->p_no,MINDIV),
	     "%s: please remain with the %s", k->p_name, teamNames[ours] );
  }
  
  printf("Balance: %16s (%s) is to join the %s\n", 
	 k->p_name, k->p_mapchars, teamNames[ours]);
  
  /* cope with a balance during INL pre-game, if we don't shift players who
     are on the QU_HOME or QU_AWAY queues then the queue masks will force
     them to join the team they were on anyway. */
  queue = ( ours == FED ) ? QU_HOME : QU_AWAY;
  if (k->w_queue != QU_PICKUP && k->w_queue != queue) {
    queues[k->w_queue].free_slots++;
    k->w_queue = queue;
    queues[k->w_queue].free_slots--;
  }  
  
  k->p_hostile |= theirs;
  k->p_swar    |= theirs;
  k->p_hostile &= ~ours;
  k->p_swar    &= ~ours;
  k->p_war      = (k->p_hostile | k->p_swar);
  k->p_team     =  ours;
  sprintf(k->p_mapchars, "%c%c", teamlet[k->p_team], shipnos[p_no]);
  sprintf(k->p_longname, "%s (%s)", k->p_name, k->p_mapchars);
  
  k->p_status = PEXPLODE;
  k->p_whydead = KPROVIDENCE; /* should be KTOURNSTART? */
  if (k->p_ship.s_type = STARBASE)
    k->p_explode = 2 * SBEXPVIEWS;
  else
    k->p_explode = 10;
  k->p_ntorp = 0;
  k->p_nplasmatorp = 0;
  k->p_hostile = (FED | ROM | ORI | KLI);
  k->p_war     = (k->p_hostile | k->p_swar);
}

/*
**  Return two team masks corresponding to the teams of the first two
**  teams found in the player list.
*/
static void sides ( int *one, int *two )
{
    struct player *k;
    int i;
    int unseen;

    unseen = (FED | ROM | ORI | KLI);
    *one = 0;
    *two = 0;
    k = &players[0];
    for(i=0;i<MAXPLAYER;i++)
    {
        if ( (k->p_status != PFREE) && (!(k->p_flags & PFROBOT)))
        {
            if ( ( unseen & k->p_team ) != 0 )
            {
                if ( *one == 0 )
                {
                    *one = k->p_team;
                    unseen &= ~k->p_team;
                    k++;
                    continue;
                }
                *two = k->p_team;
                return;
            }
        }
        k++;
    }
}

/*
**  Calculate a player value
*/
static int value ( struct player *k )
{
    return
    (int)
    (
        (float)
        (
            bombingRating(k) +
            planetRating(k) +
            offenseRating(k)
        ) * 100.0
    );
}

/*
**  Balance the teams
**
**  Uses an exhaustive algorithm (I'm exhausted!) to find the best combination
**  of the current players that balances the teams in terms of statistics.
**  The algorithm will support only the number of players that fits into the
**  number of bits in an int.
**
**  If there are multiple "best" combinations, then the combination
**  involving the least number of team swaps will be chosen.
*/
int do_balance(void)
{
    int i, j;                   /* miscellaneous counters    */
    int records;                /* number of players in game */
    int one;                    /* team number one mask      */
    int two;                    /* team number two mask      */

    struct player *k;           /* pointer to current player */

    struct item
    {
        int p_no;               /* player number             */
        int p_value;            /* calculated player value   */
        int p_team;             /* team player on previously */
    } list[MAXPLAYER];          /* working array             */

    struct
    {
        int combination;        /* combination number        */
        int value;              /* team balance difference   */
        int one;                /* team one total value      */
        int two;                /* team two total value      */
        int swaps;              /* number of swaps involved  */
    } best;                     /* best team combination     */

    /* which teams are playing?  give up if only one found */
    sides ( &one, &two );
    if ( two == 0 )
    {
        pmessage ( 0, MALL, addr_mess(myname,MALL),
          "Can't balance only one team!" );
        pmessage ( 0, MALL, addr_mess(myname,MALL),
          "Please could somebody move to another team, then all vote again?" );
        return;
    }

    /* initialise best to worst case */
    best.combination = -1;
    best.value = 1<<30;
    best.one = 0;
    best.two = 0;
    best.swaps = 1<<30;

    /* reset working array */
    for(i=0;i<MAXPLAYER;i++)
    {
        list[i].p_no    = 0;
        list[i].p_value = 0;
    }

    /* insert players in working array */
    records = 0;
    k = &players[0];
    for(i=0;i<MAXPLAYER;i++)
    {
        if ( (k->p_status != PFREE) && (!(k->p_flags & PFROBOT)))
        {
            list[records].p_no    = k->p_no;
            list[records].p_value = value ( k );
            list[records].p_team  = k->p_team;
            records++;
        }
        k++;
    }

    /* randomise the working array; may cause different team mixes */
    for(i=0;i<records;i++)
    {
        int a, b;
        struct item swapper;

        a = random() % records;
        b = random() % records;

        swapper = list[a];
        list[a] = list[b];
        list[b] = swapper;
    }

    /* loop for every _possible_ combination to find the best */
    for(i=0;i<(1<<records);i++)
    {
        int difference;         /* difference in team total       */
        int value_a, value_b;   /* total stats per team           */
        int count_a, count_b;   /* total count of players on team */
        int swaps;              /* number of swaps involved       */

        /* if this a shadow combination already considered, ignore it */
        /* if ( ( i ^ ( ( 1<<records ) - 1 ) ) < i ) continue; */
        /* disabled - it will interfere with swap minimisation goal */

        /* is this combination an equal number of players each side? */
        count_a = 0;
        count_b = 0;

        for(j=0;j<records;j++)
            if((1<<j)&i)
                count_a++;
            else
                count_b++;

        /* skip this combination if teams are significantly unequal */
        if ( abs ( count_a - count_b ) > 1 ) continue;

        /* reset team total for attempt */
        value_a = 0;
        value_b = 0;

        /* calculate team total stats */
        for(j=0;j<records;j++)
            if((1<<j)&i)
                value_a += list[j].p_value;
            else
                value_b += list[j].p_value;

        /* calculate number of swaps this combination produces */
        swaps = 0;
        for(j=0;j<records;j++)
            if((1<<j)&i)
            {
                if ( list[j].p_team != one ) swaps++;
	    }
            else
	    {
                if ( list[j].p_team != two ) swaps++;
	    }

        /* calculate difference in team total stats */
        difference = abs ( value_a - value_b );

        /* if this combo is better than the previous one we had,
           or the combo is the same and the number of swaps is lower...  */
        if ( ( difference < best.value )
            || ( ( difference == best.value )
                && ( swaps < best.swaps ) ) )
        {
            /* remember it */
            best.value = difference;
            best.combination = i;
            best.one = value_a;
            best.two = value_b;
            best.swaps = swaps;
        }
    }

    /* announce movements intended */
    for(j=0;j<records;j++)
        if ( (1<<j)&best.combination )
            moveallmsg ( list[j].p_no, one, two );

    for(j=0;j<records;j++)
        if ( !((1<<j)&best.combination) )
            moveallmsg ( list[j].p_no, two, one );

    /* move players to their teams */
    for(j=0;j<records;j++)
        if ( (1<<j)&best.combination )
            move ( list[j].p_no, one, two );
        else
            move ( list[j].p_no, two, one );

    /* advise all of resultant team mix difference */
    pmessage ( 0, MALL, addr_mess(myname,MALL),
        "The %s total rating will be %.2f",
        teamNames[one],
        (float) ( best.one / 100.0 ) );

    pmessage ( 0, MALL, addr_mess(myname,MALL),
        "The %s total rating will be %.2f",
        teamNames[two],
        (float) ( best.two / 100.0 ) );

}

int do_triple_planet_mayhem(void)
{
    int i;

    struct player* j;

    /* balance the teams */
    do_balance();

    /* move all planets off the galaxy */
    for (i=0; i<MAXPLANETS; i++)
    {
        planets[i].pl_flags = 0;
        planets[i].pl_owner = 0;
        planets[i].pl_x = -10000;
        planets[i].pl_y = -10000;
        planets[i].pl_info = 0;
        planets[i].pl_armies = 0;
        strcpy ( planets[i].pl_name, "" );
    }

    /* disable Klingon and Orion teams; stop people from joining them */
    planets[20].pl_couptime = 999999; /* no Klingons */
    planets[30].pl_couptime = 999999; /* no Orions */

    /* initialise earth */
    i = 0;
    planets[i].pl_flags |= FED | PLHOME | PLCORE | PLAGRI | PLFUEL | PLREPAIR;
    planets[i].pl_x = 40000;
    planets[i].pl_y = 65000;
    planets[i].pl_armies = 40;
    planets[i].pl_info = FED;
    planets[i].pl_owner = FED;
    strcpy ( planets[i].pl_name, "Earth" );

    /* initialise romulus */
    i = 10;
    planets[i].pl_flags |= ROM | PLHOME | PLCORE | PLAGRI | PLFUEL | PLREPAIR;
    planets[i].pl_x = 40000;
    planets[i].pl_y = 35000;
    planets[i].pl_armies = 40;
    planets[i].pl_info = ROM;
    planets[i].pl_owner = ROM;
    strcpy ( planets[i].pl_name, "Romulus" );

    /* initialise indi */
    i = 18;
    planets[i].pl_flags |= PLFUEL | PLREPAIR;
    planets[i].pl_flags &= ~PLAGRI;
    planets[i].pl_x = 15980;
    planets[i].pl_y = 50000;
    planets[i].pl_armies = 4;
    planets[i].pl_info &= ~ALLTEAM;
    strcpy ( planets[i].pl_name, "Indi" );

    /* fix all planet name lengths */
    for (i=0; i<MAXPLANETS; i++)
    {
        planets[i].pl_namelen = strlen(planets[i].pl_name);
    }

    /* advise players */
    {
        char *list[] =
        {
	    "Galaxy reset for triple planet mayhem!",
	    "Rule 1: they take Indi, they win,",
	    "Rule 2: they take your home planet, they win,",
	    "Rule 3: you can't bomb Indi,",
	    "Rule 4: you may bomb their home planet, and;",
	    "Rule 5: all planets are FUEL & REPAIR, home planets are AGRI.",
	    ""
	};

	for ( i=0; strlen(list[i])!=0; i++ )
            pmessage ( 0, MALL, addr_mess(myname,MALL), list[i] );
    }
}
#endif /* TRIPLE_PLANET_MAYHEM */
