diff src/message.c @ 4:aa38447a4b21

First entry of Paradise Server 2.9 patch 10 Beta
author darius
date Sat, 06 Dec 1997 04:37:03 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/message.c	Sat Dec 06 04:37:03 1997 +0000
@@ -0,0 +1,2513 @@
+/*--------------------------------------------------------------------------
+NETREK II -- Paradise
+
+Permission to use, copy, modify, and distribute this software and its
+documentation, or any derivative works thereof, for any NON-COMMERCIAL
+purpose and without fee is hereby granted, provided that this copyright
+notice appear in all copies.  No representations are made about the
+suitability of this software for any purpose.  This software is provided
+"as is" without express or implied warranty.
+
+    Xtrek Copyright 1986                            Chris Guthrie
+    Netrek (Xtrek II) Copyright 1989                Kevin P. Smith
+                                                    Scott Silvey
+    Paradise II (Netrek II) Copyright 1993          Larry Denys
+                                                    Kurt Olsen
+                                                    Brandon Gillespie
+--------------------------------------------------------------------------*/
+
+/* was control_mess.c, but changed to message.c for a smaller filename (BG) */
+
+/*
+ * Ugh, this code is in the middle of a rewrite.
+ * 
+ * It used to use a tokenizer with a global dictionary to split the input into
+ * words.  The tokenizer accepted abbreviations as long as these were unique.
+ * However, adding a new word to the dictionary would often cause old
+ * abbreviations to be invalidated.
+ * 
+ * I wrote a new parser that was called as each token needed to be extracted.
+ * This used a dictionary that was local to each submenu. This localizes
+ * changes to the menu structure so that effects of adding new commands are
+ * minimized.
+ * 
+ * Some of the file is converted to use this, but not all.  Eventually the
+ * entire module will use the context-sensitive tokenizer.
+ * 
+ */
+
+
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+#include "defs.h"
+#include "data.h"
+#include "struct.h"
+#include "shmem.h"
+
+enum token_names_e
+{
+  HELPTOK = 128,
+  CONTROLTOK,
+  VERSIONTOK,
+  QUEUETOK,
+  LEAGUETOK,
+  PARAMTOK,
+  INFOTOK,
+  OBSERVETOK,
+  CLUECHECKTOK,
+
+  NUKEGAMETOK,
+  FREESLOTTOK,
+  ROBOTTOK,
+  TOURNTOK,
+  NEWGALAXY,
+  SHIPTIMERTOK,
+
+  REFITTOK,
+  PLAYERTOK,
+  EJECTTOK,
+  DIETOK,
+  ARMIESTOK,
+  PLASMATOK,
+  MISSILETOK,
+  PLANETTOK,
+  RANKTOK,
+  MOVETOK,
+
+  PASSWDTOK,
+  RATINGSTOK,
+
+  TIMETOK,
+  CAPTAINTOK,
+  RESTARTTOK,
+  STARTTOK,
+  PASSTOK,
+  TIMEOUTTOK,
+  TEAMNAMETOK,
+  AWAYTOK,
+  HOMETOK,
+  PAUSETOK,
+  CONTINUETOK,
+  MAXPLAYERTOK,
+
+  TEAMTOK,
+
+  INDTOK,			/* these need to be adjacent and in order */
+  FEDTOK,			/* these need to be adjacent and in order */
+  ROMTOK,			/* these need to be adjacent and in order */
+  KLITOK,			/* these need to be adjacent and in order */
+  ORITOK,			/* these need to be adjacent and in order */
+
+  SHIPTOK,
+
+  SCOUTTOK,			/* these need to be adjacent and in order */
+  DESTROYERTOK,			/* these need to be adjacent and in order */
+  CRUISERTOK,			/* these need to be adjacent and in order */
+  BATTLESHIPTOK,		/* these need to be adjacent and in order */
+  ASSAULTTOK,			/* these need to be adjacent and in order */
+  STARBASETOK,			/* these need to be adjacent and in order */
+  ATTTOK,			/* these need to be adjacent and in order */
+  JUMPSHIPTOK,			/* these need to be adjacent and in order */
+  FRIGATETOK,			/* these need to be adjacent and in order */
+  WARBASETOK,			/* these need to be adjacent and in order */
+  LIGHTCRUISERTOK,
+  CARRIERTOK,
+  UTILITYTOK,
+  PATROLTOK,
+
+  PLUSTOK,
+  MINUSTOK,
+  ROYALTOK,
+  QUIETTOK,
+  KILLSTOK,
+  HOSETOK,
+  SUPERTOK,
+  ALLOWTOK,			/* control allow [teams] */
+
+  ERRORTOK = 255
+};
+
+#ifdef GOD_CONTROLS
+static int god_silent = 0;
+#endif
+
+/* static char *con_shipnos = "0123456789abcdefghijklmnopqrstuvwxyz"; */
+
+extern int pmessage2();
+extern int pmessage();
+#ifndef linux
+extern int atoi();
+#endif				/* linux */
+extern int detourneyqueue();
+extern void compute_ratings();
+
+static void respond();
+
+#if 0
+static enum token_names_e tokstring[128];	/* list of tokens */
+static char *thestrings[128];	/* list of corresponding strings */
+static char *therest[128];	/* list of pointers to the original text
+				 * starting with token i */
+static int sizetokstring;
+
+struct token
+{
+  char string[32];
+  int token;
+};
+
+
+
+struct token tstrings[] = {
+  {"0", 0}, {"1", 1}, {"2", 2}, {"3", 3},
+  {"4", 4}, {"5", 5}, {"6", 6}, {"7", 7},
+  {"8", 8}, {"9", 9}, {"a", 10}, {"b", 11},
+  {"c", 12}, {"d", 13}, {"e", 14}, {"f", 15},
+  {"g", 16}, {"h", 17}, {"i", 18}, {"j", 19},
+  {"k", 20}, {"l", 21}, {"m", 22}, {"n", 23},
+  {"o", 24}, {"p", 25}, {"q", 26}, {"r", 27},
+  {"s", 28}, {"t", 29}, {"u", 30}, {"v", 31},
+  {"w", 32}, {"x", 33}, {"y", 34}, {"z", 35},
+  {"help", HELPTOK},
+  {"control", CONTROLTOK},
+  {"con", CONTROLTOK},		/* popular abbrev */
+  {"version", VERSIONTOK},
+  {"tq", QUEUETOK},		/* popular abbrev */
+  {"queue", QUEUETOK},
+  {"league", LEAGUETOK},
+  {"params", PARAMTOK},
+  {"observe", OBSERVETOK},
+  {"info", INFOTOK},
+  {"cluecheck", CLUECHECKTOK},
+
+  {"shiptimers", SHIPTIMERTOK},
+
+  {"refit", REFITTOK},
+  {"player", PLAYERTOK},
+  {"pl", PLAYERTOK},		/* popular abbrev */
+  {"planet", PLANETTOK},
+  {"eject", EJECTTOK},
+  {"die", DIETOK},
+  {"rank", RANKTOK},
+  {"royalty", ROYALTOK},
+  {"move", MOVETOK},
+  {"armies", ARMIESTOK},
+  {"plasma", PLASMATOK},
+  {"missile", MISSILETOK},
+  {"team", TEAMTOK},
+  {"ship", SHIPTOK},
+  {"quiet", QUIETTOK},
+  {"kills", KILLSTOK},
+  {"hose", HOSETOK},
+  {"super", SUPERTOK},
+  {"allow", ALLOWTOK},
+  {"+", PLUSTOK},
+  {"-", MINUSTOK},
+
+  {"robot", ROBOTTOK},
+  {"nukegame", NUKEGAMETOK},
+  {"freeslot", FREESLOTTOK},
+  {"tourn", TOURNTOK},
+  {"galaxy", NEWGALAXY},
+
+  /* player commands */
+  {"password", PASSWDTOK},
+  {"ratings", RATINGSTOK},
+
+  /* league commands */
+  {"time", TIMETOK},
+  {"captain", CAPTAINTOK},
+  {"restart", RESTARTTOK},
+  {"start", STARTTOK},
+  {"pass", PASSTOK},
+  {"timeout", TIMEOUTTOK},
+  {"teamname", TEAMNAMETOK},
+  {"away", AWAYTOK},
+  {"home", HOMETOK},
+  {"pause", PAUSETOK},
+  {"continue", CONTINUETOK},
+  {"maxplayers", MAXPLAYERTOK},
+
+  {"independent", INDTOK},
+  {"federation", FEDTOK}, {"romulan", ROMTOK},
+  {"klingon", KLITOK}, {"orion", ORITOK},
+
+  {"sc", SCOUTTOK}, {"dd", DESTROYERTOK},
+  {"ca", CRUISERTOK}, {"bb", BATTLESHIPTOK},
+  {"as", ASSAULTTOK}, {"sb", STARBASETOK},
+  {"at", ATTTOK}, {"js", JUMPSHIPTOK},
+  {"fr", FRIGATETOK}, {"wb", WARBASETOK},
+  {"cl", LIGHTCRUISERTOK}, {"cv", CARRIERTOK},
+  {"ut", UTILITYTOK}, {"pt", PATROLTOK},
+
+  {"", ERRORTOK}		/* I'm last. Keep it that way. */
+};
+
+static void
+dotokenize(input)
+  char *input;
+{
+  char *temp0, *temp1;
+  int done = 0;
+  static char internal[120];
+
+  sizetokstring = 0;
+
+  /* Convert string to lowercase. */
+
+  temp0 = strcpy(internal, input);
+  while (*temp0 != '\0')
+  {
+    if (isupper(*temp0))
+      *temp0 = tolower(*temp0);
+    temp0++;
+  }
+
+
+  temp0 = internal;
+
+  while (done == 0)
+  {
+    struct token *tptr;
+    int wordlen;
+    int potentialtok, ambiguous;
+
+    /* Eat leading spaces. */
+
+    while (*temp0 == ' ')
+      temp0++;
+
+    /* Find the next word and chop the rest of the string away. */
+
+    temp1 = temp0;
+    therest[sizetokstring] = input + (temp1 - internal);
+    while (*temp1 != '\0' && *temp1 != ' ')
+      temp1++;
+    if (*temp1 != '\0')
+      *temp1++ = '\0';
+    wordlen = strlen(temp0);
+
+    /* Go find out what they've typed. */
+
+    tptr = tstrings;
+    potentialtok = ERRORTOK;
+    ambiguous = 0;
+    while (tptr->token != ERRORTOK)
+    {
+      if (0 == strncmp(temp0, tptr->string, wordlen))
+      {
+	if (strlen(tptr->string) == wordlen)
+	{
+	  ambiguous = 0;
+	  potentialtok = tptr->token;
+	  break;		/* exact match */
+	}
+	if (potentialtok != ERRORTOK)
+	{
+	  ambiguous = 1;	/* this isn't the only match */
+	}
+	potentialtok = tptr->token;
+      }
+      tptr++;
+    }
+
+    thestrings[sizetokstring] = temp0;
+    tokstring[sizetokstring++] = ambiguous ? ERRORTOK : potentialtok;
+    temp0 = temp1;
+
+    /* If *temp0 == 0 then we're done. */
+
+    if (*temp0 == '\0')
+      done = 1;
+  }
+  thestrings[sizetokstring] = 0;
+}
+#endif
+
+/**********************************************************************/
+
+/* New parsing method. */
+
+struct control_cmd
+{
+  char *literal;		/* the command they should type */
+  enum token_names_e tok;	/* what the parser should return */
+  char *doc;			/* documentation to print for a help command */
+};
+
+/*
+ * Scans the string cmd for the first whitespace delimited string. Tries to
+ * match this string in a case-insensitive manner against the list in legals.
+ * Returns the appropriate token.  Alters the char* pointed to by after to be
+ * the beginning of the next whitespace-delimited string.
+ * 
+ * cmd:	unmodified legals:	unmodified after:	MODIFIED
+ * 
+ */
+
+enum token_names_e
+next_token(cmd, legals, after)
+  char *cmd;
+  struct control_cmd *legals;
+  char **after;
+{
+  char buf[80];			/* space for the token */
+  char *s;
+  int i;
+  int ambiguous = 0;
+  enum token_names_e potentialtok = ERRORTOK;
+
+  while (*cmd && isspace(*cmd))
+    cmd++;
+
+  if (!*cmd)
+    return ERRORTOK;
+
+  for (s = buf; *cmd && !isspace(*cmd); s++, cmd++)
+    *s = *cmd;
+  *s = 0;
+
+  while (*cmd && isspace(*cmd))
+    cmd++;
+
+  if (after)
+    *after = cmd;		/* so they can find the next token */
+
+  for (i = 0; legals[i].literal; i++)
+  {
+    int wordlen = strlen(buf);
+    if (0 == strncasecmp(buf, legals[i].literal, wordlen))
+    {
+      if (strlen(legals[i].literal) == wordlen)
+      {
+	ambiguous = 0;
+	potentialtok = legals[i].tok;
+	break;			/* exact match */
+      }
+      if (potentialtok != ERRORTOK)
+      {
+	ambiguous = 1;		/* this isn't the only match */
+	return ERRORTOK;
+      }
+      potentialtok = legals[i].tok;
+    }
+  }
+
+  return potentialtok;
+}
+
+int
+match_token(cmd, token, after)
+  char *cmd;
+  char *token;
+  char **after;
+{
+  struct control_cmd legals[2];
+  legals[0].literal = token;
+  legals[0].tok = HELPTOK;	/* pick any token but ERRORTOK */
+  legals[1].literal = 0;
+  return HELPTOK == next_token(cmd, legals, after);
+}
+
+/*
+ * Get a player slot number. Returns -1 on failure, slot number on success.
+ * Slot number is guaranteed to be <MAXPLAYER.
+ */
+
+int 
+get_slotnum(cmd, after)
+  char *cmd;
+  char **after;
+{
+  int rval;
+  while (*cmd && isspace(*cmd))
+    cmd++;
+
+  if (!*cmd)
+    return -1;			/* no token */
+
+  if (cmd[1] && !isspace(cmd[1]))
+    return -1;			/* token too long */
+
+  if (*cmd >= '0' && *cmd <= '9')
+    rval = *cmd - '0';
+  else if (*cmd >= 'a' && *cmd <= 'z')
+    rval = *cmd - 'a' + 10;
+  else if (*cmd >= 'A' && *cmd <= 'Z')
+    rval = *cmd - 'A' + 10;
+  else
+    return -1;
+
+  if (rval >= MAXPLAYER)
+    return -1;			/* there aren't that many players */
+
+  if (after)
+  {
+    /* scan to next token */
+    cmd++;
+    while (*cmd && isspace(*cmd))
+      cmd++;
+    *after = cmd;
+  }
+
+  return rval;
+}
+
+void 
+bad_slotnum(msg)
+  char *msg;
+{
+  char buf[256];
+  sprintf(buf, "`%s' requires player slot number", msg);
+  respond(buf, 1);
+}
+
+
+/*
+ * Get a single token. Returns 0 on failure, 1 on success. Token is returned
+ * in dst.
+ */
+
+int 
+get_one_token(cmd, dst, dstsize, after)
+  char *cmd;
+  char *dst;			/* destination */
+  int dstsize;
+  char **after;
+{
+  while (*cmd && isspace(*cmd))
+    cmd++;
+
+  if (!*cmd)
+    return 0;			/* no token */
+
+  while (dstsize > 1 && *cmd && !isspace(*cmd))
+  {
+    *(dst++) = *(cmd++);
+    dstsize--;
+  }
+  *dst = 0;
+
+  if (after)
+  {
+    /* scan to next token */
+    while (*cmd && isspace(*cmd))
+      cmd++;
+    *after = cmd;
+  }
+
+  return 1;
+}
+
+/*
+ * Get an integer Integer is returned in dst. Returns 0 on failure without
+ * modifying dst.
+ */
+
+int 
+get_int(cmd, dst, after)
+  char *cmd;
+  int *dst;
+  char **after;
+{
+  int rval, offset;
+
+  if (1 != sscanf(cmd, " %i%n", &rval, &offset))
+    return 0;
+
+  cmd += offset;
+  if (*cmd && !isspace(*cmd))
+    return 0;			/* token wasn't all digits */
+
+  *dst = rval;
+
+  if (after)
+  {
+    /* scan to next token */
+    while (*cmd && isspace(*cmd))
+      cmd++;
+    *after = cmd;
+  }
+
+  return 1;
+}
+
+/*
+ * Get a double Double is returned in dst. Returns 0 on failure without
+ * modifying dst.
+ */
+
+int 
+get_double(cmd, dst, after)
+  char *cmd;
+  double *dst;
+  char **after;
+{
+  double rval;
+  int offset;
+
+  if (1 != sscanf(cmd, " %lg%n", &rval, &offset))
+    return 0;
+
+  cmd += offset;
+  if (*cmd && !isspace(*cmd))
+    return 0;			/* token wasn't all digits */
+
+  *dst = rval;
+
+  if (after)
+  {
+    /* scan to next token */
+    while (*cmd && isspace(*cmd))
+      cmd++;
+    *after = cmd;
+  }
+
+  return 1;
+}
+
+int 
+get_teamid(cmd, team, after)
+  char *cmd;
+  int *team;
+  char **after;
+{
+  int i, j;
+
+  while (*cmd && isspace(*cmd))
+  {
+    cmd++;
+  }
+  if (cmd[3] && !isspace(cmd[3]))
+    return 0;			/* too long */
+
+  *team = NOBODY;
+
+  for (i = -1; i < NUMTEAM; i++)
+  {
+    j = idx_to_mask(i);
+    if (0 == strncasecmp(cmd, teams[j].shortname, 3))
+    {
+      *team = i;
+      cmd += 3;
+      break;
+    }
+  }
+
+  if (after)
+  {
+    /* scan to next token */
+    while (*cmd && isspace(*cmd))
+      cmd++;
+    *after = cmd;
+  }
+
+  return i < NUMTEAM;
+}
+
+int 
+get_shipid(cmd, shipn, after)
+  char *cmd;
+  int *shipn;
+  char **after;
+{
+  int i;
+
+  while (*cmd && isspace(*cmd))
+  {
+    cmd++;
+  }
+  *shipn = -1;
+
+  for (i = 0; i < NUM_TYPES; i++)
+  {
+    struct ship *ship = &shipvals[i];
+    int len;
+    len = strlen(ship->s_name);
+    if (0 == strncasecmp(cmd, ship->s_name, len) &&
+	(cmd[len] == 0 || isspace(cmd[len])))
+    {
+      *shipn = i;
+      cmd += len;
+      break;
+    }
+    else if (tolower(cmd[0]) == tolower(ship->s_desig1) &&
+	     tolower(cmd[1]) == tolower(ship->s_desig2) &&
+	     (cmd[2] == 0 || isspace(cmd[2])))
+    {
+      *shipn = i;
+      cmd += 2;
+      break;
+    }
+  }
+
+  if (after)
+  {
+    /* scan to next token */
+    while (*cmd && isspace(*cmd))
+      cmd++;
+    *after = cmd;
+  }
+
+  return i < NUM_TYPES;
+}
+
+/* writes a comma-separated list of help strings into the message window */
+
+void
+respond_with_help_string(legals)
+  struct control_cmd *legals;
+{
+  int i;
+  char buf[65];			/* leave space for the message prefix */
+
+  strcpy(buf, "Available commands: ");
+  for (i = 0; legals[i].literal; i++)
+  {
+    if (!(legals[i].doc && legals[i].doc[0]))
+      continue;
+    if (strlen(buf) + 3 + strlen(legals[i].doc) > sizeof(buf))
+    {
+      respond(buf, 0);
+      strcpy(buf, "    ");
+      if (!buf[0])
+      {				/* one of the help strings was just too long */
+	respond("ACK! programmer error: help string too long", 0);
+	return;
+      }
+      i--;			/* retry */
+      continue;
+    }
+    strcat(buf, legals[i].doc);
+    if (legals[i + 1].literal)
+      strcat(buf, ", ");
+  }
+  if (buf[0])
+    respond(buf, 0);
+}
+
+/**********************************************************************/
+
+static void
+respond(msg, type)
+  char *msg;
+  int type;
+{
+  if (type == 1)
+    warning(msg);
+  else
+    pmessage2(msg, me->p_no, MINDIV, MCONTROL, 255);
+}
+
+#ifdef GOD_CONTROLS
+
+/*
+ * Here we handle the controls on players. If you add something, make sure
+ * you place it in the help. Thanks, have a nice day.
+ */
+
+static int
+parse_control_player(cmd)
+  char *cmd;
+{
+  char buf[120];
+  int pnum;
+  struct player *victim;
+  int godliness = me->p_stats.st_royal - GODLIKE + 1;
+  char *arg;
+  static struct control_cmd available_cmds[] = {
+    {"help", HELPTOK, 0},
+    {"die", DIETOK, "die"},
+    {"eject", EJECTTOK, "eject"},
+    {"armies", ARMIESTOK, "armies [%d=5]"},
+    {"plasma", PLASMATOK, "plasma [%d]"},
+    {"missiles", MISSILETOK, "missiles [%d=max]"},
+    {"team", TEAMTOK, "team <teamstr>"},
+    {"ship", SHIPTOK, "ship <shiptype>"},
+    {"rank", RANKTOK, "rank (+|-|%d)"},
+    {"royal", ROYALTOK, "royal (+|-|%d)"},
+    {"kills", KILLSTOK, "kills (+|-|%d)"},
+    {"hose", HOSETOK, "hose"},
+    {"move", MOVETOK, "move %d %d"},
+    {0}
+  };
+
+  pnum = get_slotnum(cmd, &cmd);
+  if (pnum < 0)
+  {
+    bad_slotnum("control player");
+    return 0;
+  }
+  victim = &players[pnum];
+
+  if (victim->p_status == PFREE)
+  {
+    respond("Slot is not alive.", 1);
+    return 1;
+  }
+
+  /*
+   * These would probably work better as pointers to functions instead of a
+   * giant switch, but what the hell, I'm lazy. Maybe I'll change it later.
+   */
+
+  switch (next_token(cmd, available_cmds, &arg))
+  {
+   case DIETOK:
+    victim->p_ship.s_type = STARBASE;
+    victim->p_whydead = KPROVIDENCE;
+    victim->p_explode = 10;
+    victim->p_status = PEXPLODE;
+    victim->p_whodead = 0;
+    if (!god_silent)
+    {
+      sprintf(buf, "%s (%2s) was utterly obliterated by %s (%2s).",
+	      victim->p_name, twoletters(victim),
+	      me->p_name, twoletters(me));
+      pmessage(buf, 0, MALL, MCONTROL);
+    }
+    return 1;
+
+   case EJECTTOK:
+    victim->p_ship.s_type = STARBASE;
+    victim->p_whydead = KQUIT;
+    victim->p_explode = 10;
+    victim->p_status = PEXPLODE;
+    victim->p_whodead = 0;
+    if (!god_silent)
+    {
+      sprintf(buf,
+	      "%s (%2s) has been ejected from the game by %s (%2s).",
+	      victim->p_name, twoletters(victim),
+	      me->p_name, twoletters(me));
+      pmessage(buf, 0, MALL, MCONTROL);
+    }
+    return 1;
+
+   case ARMIESTOK:
+    {
+      int armies = 5;
+      if (*arg && !get_int(arg, &armies, (char **) 0))
+      {
+	respond("optional arg to `control player <slotnum> armies` must be integer", 0);
+	return 0;
+      }
+      victim->p_armies += armies;
+      if (!god_silent)
+      {
+	sprintf(buf, "%s (%2s) has been given %d armies by %s (%2s).",
+		victim->p_name, twoletters(victim), armies,
+		me->p_name, twoletters(me));
+	pmessage(buf, 0, MALL, MCONTROL);
+      }
+      return 1;
+    }
+
+   case PLASMATOK:
+    {
+      int yes = 1;
+      if (*arg && !get_int(arg, &yes, (char **) 0))
+      {
+	respond("optional arg to `control player <slotnum> plasma` must be integer", 0);
+	return 0;
+      }
+
+      if (yes)
+	victim->p_ship.s_nflags |= SFNPLASMAARMED;
+      else
+	victim->p_ship.s_nflags &= ~SFNPLASMAARMED;
+
+      if (!god_silent)
+      {
+	sprintf(buf, "%s (%2s) has been %s plasma torps by %s (%2s).",
+		victim->p_name, twoletters(victim),
+		yes ? "given" : "denied",
+		me->p_name, twoletters(me));
+	pmessage(buf, 0, MALL, MCONTROL);
+      }
+      return 1;
+    }
+
+   case MISSILETOK:
+    {
+      int yes = shipvals[victim->p_ship.s_type].s_missilestored;
+
+      if (*arg && !get_int(arg, &yes, (char **) 0))
+      {
+	respond("optional arg to `control player <slotnum> missile` must be integer", 0);
+	return 0;
+      }
+
+      if (yes)
+      {
+	victim->p_ship.s_nflags |= SFNHASMISSILE;
+	victim->p_ship.s_missilestored = yes;
+      }
+      else
+      {
+	victim->p_ship.s_nflags &= ~SFNHASMISSILE;
+      }
+
+      if (!god_silent)
+      {
+	sprintf(buf, "%s (%2s) has been %s %d missiles by %s (%2s).",
+		victim->p_name, twoletters(victim),
+		yes ? "given" : "denied",
+		yes, me->p_name, twoletters(me));
+	pmessage(buf, 0, MALL, MCONTROL);
+      }
+      return 1;
+    }
+
+   case TEAMTOK:
+    {
+      int team;
+
+      if (!get_teamid(arg, &team, (char **) 0))
+      {
+	respond("available teams: FED ORI ROM KLI IND", 0);
+	return 0;
+      }
+      team = idx_to_mask(team);
+
+      victim->p_hostile |= victim->p_team;
+      victim->p_team = team;
+      victim->p_hostile &= ~team;
+      victim->p_swar &= ~team;
+      sprintf(buf, "%s (%2s) has been changed to a %s by %s (%2s).",
+	      victim->p_name, twoletters(victim), teams[team].nickname,
+	      me->p_name, twoletters(me));
+      if (!god_silent)
+	pmessage(buf, 0, MALL, MCONTROL);
+      return 1;
+    }
+
+   case SHIPTOK:
+    {
+      int ship;
+      if (!get_shipid(arg, &ship, (char **) 0))
+      {
+	respond("available ships: SC DD CA AS BB SB AT JS FR WB CL CV SUPER", 0);
+	return 0;
+      }
+#if 0
+      if (tokstring[4] == SUPERTOK)
+      {
+	victim->p_ship.s_maxshield = 750;
+	victim->p_shield = 750;
+	victim->p_ship.s_maxdamage = 750;
+	victim->p_ship.s_maxegntemp = 5000;
+	sprintf(buf, "%s (%2s) has been supercharged by %s (%2s).",
+		victim->p_name, twoletters(victim),
+		me->p_name, twoletters(me));
+	if (!god_silent)
+	  pmessage(buf, 0, MALL, MCONTROL);
+	return 1;
+      }
+#endif
+      /* If others are docked, then kick them off */
+      if (allows_docking(victim->p_ship))
+      {
+	int i;
+	for (i = 0; i < victim->p_ship.s_numports; i++)
+	{
+	  base_undock(victim, i);
+	}
+      }
+      get_ship_for_player(victim, ship);
+      switch_special_weapon();
+      victim->p_flags &= ~PFENG;
+      sprintf(buf, "%s (%2s) has been changed to a %c%c by %s (%2s).",
+	      victim->p_name, twoletters(victim),
+	      victim->p_ship.s_desig1, victim->p_ship.s_desig2,
+	      me->p_name, twoletters(me));
+      if (!god_silent)
+	pmessage(buf, 0, MALL, MCONTROL);
+      return 1;
+    }
+
+   case RANKTOK:
+    {
+      int rank = victim->p_stats.st_rank;
+
+      if (match_token(arg, "+", (char **) 0))
+	rank++;
+      else if (match_token(arg, "-", (char **) 0))
+	rank--;
+      else if (!get_int(arg, &rank, (char **) 0))
+      {
+	respond("Try: control player %d rank [%d]+-", 0);
+	return 0;
+      }
+
+      if (rank < 0)
+	rank = 0;
+      if (rank >= NUMRANKS)
+	rank = NUMRANKS - 1;
+
+      victim->p_stats.st_rank = rank;
+      sprintf(buf, "%s (%2s) has been given a rank of %s by %s (%2s).",
+	      victim->p_name, twoletters(victim),
+	      ranks[victim->p_stats.st_rank].name,
+	      me->p_name, twoletters(me));
+      if (!god_silent)
+	pmessage(buf, 0, MALL, MCONTROL);
+      return 1;
+    }
+
+   case ROYALTOK:
+    {
+      int rank = victim->p_stats.st_royal;
+
+      if (match_token(arg, "+", (char **) 0))
+	rank++;
+      else if (match_token(arg, "-", (char **) 0))
+	rank--;
+      else if (!get_int(arg, &rank, (char **) 0))
+      {
+	respond("Try: control player %d royal [%d]+-", 0);
+	return 0;
+      }
+
+      if (rank < 0)
+	rank = 0;
+      if (rank >= NUMROYALRANKS)
+	rank = NUMROYALRANKS - 1;
+
+      if (rank >= GODLIKE && godliness < 2)
+      {
+	respond("You aren't powerful enough to grant godlike royalty.",
+		1);
+	return 1;
+      }
+      victim->p_stats.st_royal = rank;
+      sprintf(buf, "%s (%2s) has been given a rank of %s by %s (%2s).",
+	      victim->p_name, twoletters(victim),
+	      royal[victim->p_stats.st_royal].name,
+	      me->p_name, twoletters(me));
+      if (!god_silent)
+	pmessage(buf, 0, MALL, MCONTROL);
+      return 1;
+    }
+
+   case KILLSTOK:
+    {
+      double kills = victim->p_kills;
+
+      if (match_token(arg, "+", (char **) 0))
+	kills += 1;
+      else if (match_token(arg, "-", (char **) 0))
+      {
+	kills -= 1;
+	if (kills < 0)
+	  kills = 0;
+      }
+      else if (!get_double(arg, &kills, (char **) 0))
+      {
+	respond("Try: control player %d kills [%f]+-", 0);
+	return 0;
+      }
+
+      victim->p_kills = kills;
+      sprintf(buf, "%s (%2s) has been given %f kills by %s (%2s).",
+	      victim->p_name, twoletters(victim),
+	      kills, me->p_name, twoletters(me));
+      if (!god_silent)
+	pmessage(buf, 0, MALL, MCONTROL);
+      return 1;
+    }
+
+   case HOSETOK:
+    victim->p_shield = 0;
+    victim->p_damage = victim->p_ship.s_maxdamage / 2;
+    sprintf(buf, "%s (%2s) has been hosed by %s (%2s).",
+	    victim->p_name, twoletters(victim),
+	    me->p_name, twoletters(me));
+    if (!god_silent)
+      pmessage(buf, 0, MALL, MCONTROL);
+    return 1;
+
+   case MOVETOK:
+    {
+      int x, y;
+      char *s;
+      if (!(get_int(arg, &x, &s) && get_int(s, &y, (char **) 0)))
+      {
+	respond("Try: control player %d move %d %d", 0);
+	return 0;
+      }
+
+      if (x <= 0 || y <= 0 || x >= 200000 || y >= 200000)
+      {
+	respond("You want to move him where?", 0);
+	return 0;
+      }
+      victim->p_x = x;
+      victim->p_y = y;
+      sprintf(buf, "%s (%2s) has been moved to %d %d by %s (%2s).",
+	      victim->p_name, twoletters(victim),
+	      x, y,
+	      me->p_name, twoletters(me));
+      if (!god_silent)
+	pmessage(buf, 0, MALL, MCONTROL);
+      return 1;
+    }
+
+   case HELPTOK:		/* fall through */
+   default:
+#if 1
+    respond_with_help_string(available_cmds);
+#else
+    respond("player controls: die, eject, armies [%d], plasma [%d],", 0);
+    respond("player controls: missile [%d], team [team], ship [ship],", 0);
+    respond("player controls: rank [%d]+-, royal [%d]+-, kills [%d]+-", 0);
+    respond("player controls: hose, move %d %d", 0);
+#endif
+    return 0;
+  }
+}
+
+static int
+parse_control(str)
+  char *str;
+{
+  char buf[120];
+  struct player *victim;
+  int i;
+  int godliness = me->p_stats.st_royal - GODLIKE + 1;
+
+  static struct control_cmd available_cmds[] = {
+    {"help", HELPTOK, 0},
+    {"freeslot", FREESLOTTOK, "freeslot %p"},
+    {"player", PLAYERTOK, "player ..."},
+    {"robot", ROBOTTOK, "robot [args]"},
+    {"quiet", QUIETTOK, "quiet"},
+    {"nukegame", NUKEGAMETOK, "nukegame"},
+    {"restart", RESTARTTOK, "restart"},
+    {"galaxy", NEWGALAXY, "galaxy"},
+    {"shiptimer", SHIPTIMERTOK, "shiptimer (teamstr|shipstr)*"},
+    {"allow", ALLOWTOK, "allow [teams]"},
+    {0}
+  };
+  char *nexttoken;
+
+  if (godliness <= 0)
+  {
+#if 0
+    respond("Those commands are only available to server gods");
+#endif
+    return 0;			/* "fail" silently.  Don't advertise divine
+				 * powers to peasants. */
+  }
+
+
+  switch (next_token(str, available_cmds, &nexttoken))
+  {
+   case FREESLOTTOK:
+    {
+      int slot = get_slotnum(nexttoken, (char **) 0);
+      if (slot < 0)
+      {
+	respond("\"control freeslot\" requires slot number.", 0);
+	return 1;
+      }
+      victim = &players[slot];
+      if (victim->p_ntspid)
+	kill(victim->p_ntspid, SIGHUP);
+
+      victim->p_status = PFREE;
+      victim->p_ntspid = 0;
+
+      if (!god_silent)
+      {
+	sprintf(buf, "Player slot %s has been freed by %s (%2s).",
+		nexttoken, me->p_name, twoletters(me));
+	pmessage(buf, 0, MALL, MCONTROL);
+      }
+    }
+    return 1;
+
+   case ALLOWTOK:
+    {
+      int newlock = 0;
+      int team;
+      char *s;
+      if (0 == *nexttoken)
+      {
+	newlock = ALLTEAM;
+      }
+      else
+      {
+	for (s = nexttoken; get_teamid(s, &team, &s);)
+	{
+	  newlock |= idx_to_mask(team);
+	}
+	if (*s)
+	{
+	  respond("Usage: control allow [fed] [rom] [kli] [ori]", 0);
+	  return 1;
+	}
+      }
+
+      status2->nontteamlock = newlock;
+      strcpy(buf, "Allowed teams now set to:");
+      if (status2->nontteamlock == ALLTEAM)
+      {
+	strcat(buf, " <all teams>");
+      }
+      else
+      {
+	if (status2->nontteamlock & FED)
+	  strcat(buf, " fed");
+	if (status2->nontteamlock & ROM)
+	  strcat(buf, " rom");
+	if (status2->nontteamlock & KLI)
+	  strcat(buf, " kli");
+	if (status2->nontteamlock & ORI)
+	  strcat(buf, " ori");
+      }
+      respond(buf, 0);
+    }
+    return 1;
+   case PLAYERTOK:
+    return parse_control_player(nexttoken);
+
+   case ROBOTTOK:
+    {
+      int pid;
+      char *s;
+
+      pid = fork();
+      if (pid == 0)
+      {
+	char *argv[40];
+	argv[0] = build_path(ROBOT);
+
+	s = nexttoken;
+	for (i = 1; 1; i++)
+	{
+	  int size = 80;
+	  argv[i] = malloc(size);
+	  if (!get_one_token(s, argv[i], size, &s))
+	    break;
+	  realloc(argv[i], strlen(argv[i]) + 1);
+	}
+	free(argv[i]);
+	argv[i] = 0;
+
+	execvp(argv[0], argv);
+	fprintf(stderr, "Ack! Unable to exec %s\n", argv[0]);
+	exit(1);
+      }
+      else if (pid < 0)
+      {
+	respond("Unable to fork robot", 0);
+      }
+      else
+      {
+	sprintf(buf, "Robot forked (pid %d) with arguments %s",
+		pid, nexttoken);
+	respond(buf, 1);
+      }
+    }
+    return 1;
+
+   case QUIETTOK:
+    if (godliness < 2)
+    {
+      respond("No sneaking allowed", 0);
+      return 1;
+    }
+    sprintf(buf, "Switching to %s mode.", god_silent ? "loud" : "quiet");
+    respond(buf, 0);
+    god_silent = !god_silent;
+    return 1;
+   case NUKEGAMETOK:
+    warning("Nuking game.  Have a nice day.");
+    if (!god_silent)
+    {
+      sprintf(buf, "The game has been nuked by %s (%2s).",
+	      me->p_name, twoletters(me));
+      pmessage(buf, 0, MALL, MCONTROL);
+    }
+    kill(status->nukegame, 15);
+    return 1;
+   case RESTARTTOK:
+    warning("Attempting daemon restart.");
+    startdaemon(
+#ifdef LEAGUE_SUPPORT
+		status2->league
+#else
+		0
+#endif
+		,1);
+    return 1;
+   case NEWGALAXY:
+    explode_everyone(KPROVIDENCE, 0);
+    if (!god_silent)
+    {
+      sprintf(buf, "The galaxy has been reset by %s (%2s).",
+	      me->p_name, twoletters(me));
+      pmessage(buf, 0, MALL, MCONTROL);
+    }
+    status2->newgalaxy = 1;
+    warning("Creating new galaxy");
+    return 1;
+   case SHIPTIMERTOK:
+    {
+      int teammask = 0;
+      int shipmask = 0;
+      int i, j;
+      char *s = nexttoken;
+      while (1)
+      {
+	if (get_shipid(s, &j, &s))
+	  shipmask |= 1 << j;
+	else if (get_teamid(s, &j, &s))
+	  teammask |= idx_to_mask(j);
+	else if (*s)
+	{
+	  respond("Usage:", 0);
+	  respond("control shiptimers (fed|rom|kli|ori)* (sc|dd|ca|bb|as|sb|at|js|fr|wb)*", 0);
+	  respond("  resets the ship timers.", 0);
+	  return 0;
+	}
+	else
+	  break;
+      }
+      for (i = 0; i < NUMTEAM; i++)
+      {
+	int teammask = idx_to_mask(i);
+	if (teammask && !(teammask & (1 << i)))
+	  continue;
+
+	for (j = 0; j < NUM_TYPES; j++)
+	{
+	  if (shipmask && !(shipmask & (1 << j)))
+	    continue;
+
+	  if (teams[teammask].s_turns[j])
+	  {
+	    sprintf(buf, "%s %s reset", teams[teammask].name, shipvals[j].s_name);
+	    respond(buf, 0);
+	    teams[teammask].s_turns[j] = 0;
+	  }
+	}
+      }
+    }
+    return 1;
+   case HELPTOK:		/* fall through */
+   default:
+#if 1
+    respond_with_help_string(available_cmds);
+#else
+    respond("Available controls: player, quiet, nukegame, freeslot,", 0);
+    respond("  galaxy, restart, shiptimer", 0);
+#endif
+    return 0;
+  }
+}
+#endif
+
+/*
+ */
+
+static int
+parse_info(cmd)
+  char *cmd;
+{
+  char buf[120];
+  char *nexttoken;
+
+  static struct control_cmd available_cmds[] = {
+    {"help", HELPTOK, 0},
+    {"shiptimer", SHIPTIMERTOK, "shiptimer (teamstr|shipstr)*"},
+    {0}
+  };
+
+  switch (next_token(cmd, available_cmds, &nexttoken))
+  {
+   case SHIPTIMERTOK:
+    {
+      int race = 0;
+      int i, j;
+      int anydead = 0;
+#if 0
+      if (me->p_stats.st_royal < 2)
+	race = me->p_team;
+#endif
+      for (i = 0; i < NUMTEAM; i++)
+      {
+	int teammask = idx_to_mask(i);
+	if (race && !(race & (1 << i)))
+	  continue;
+	for (j = 0; j < NUM_TYPES; j++)
+	{
+	  if (teams[teammask].s_turns[j])
+	  {
+	    sprintf(buf, "%s %s: %d minutes", teams[teammask].name,
+		    shipvals[j].s_name, teams[teammask].s_turns[j]);
+	    anydead = 1;
+	    respond(buf, 0);
+	  }
+	}
+      }
+      if (!anydead)
+	respond("All ships are available", 0);
+    }
+    return 1;
+   case HELPTOK:
+   default:
+    respond("Available subcommands: shiptimer", 0);
+    return 1;
+  }
+}
+
+#define crypt(a, b) (a)
+
+static int
+parse_player(cmd)
+  char *cmd;
+{
+  static int passver = 0;
+  char buf[80];
+#if 0
+  static char newpass[16];
+#endif
+  char *nexttoken;
+
+  static struct control_cmd available_cmds[] = {
+    {"help", HELPTOK, 0},
+    {"password", PASSWDTOK, "password %s"},
+    {"passwd", PASSWDTOK, 0},
+    {"ratings", RATINGSTOK, "ratings"},
+    {"rank", RANKTOK, "rank"},
+    {0}
+  };
+
+  if (0 == *cmd)
+  {
+    if (passver)
+      respond("Password change cancelled.", 0);
+
+    passver = 0;
+    return 1;
+  }
+
+  switch (next_token(cmd, available_cmds, &nexttoken))
+  {
+   case PASSWDTOK:
+    {
+      static char newpass[16];
+      if (me->p_pos < 0)
+      {				/* guest login */
+	respond("You don't have a password!", 0);
+      }
+      else if (*nexttoken == 0)
+      {
+	respond("\"player password\" requires new password as argument.", 0);
+	respond("  example: \"player password lh4ern\"", 0);
+      }
+      else if (!passver)
+      {
+	strcpy(newpass, crypt(nexttoken, me->p_name));
+	respond("Repeat \"player password\" command to verify new password.", 0);
+	respond(" or send \"player\" (no arguments) to cancel.", 0);
+	passver = 1;
+      }
+      else
+      {
+	char *paths;
+	int fd;
+	if (!strcmp(newpass, crypt(nexttoken, me->p_name)))
+	{
+
+	  /* perhaps it'd be better to put this part in */
+	  /* a different place */
+	  paths = build_path(PLAYERFILE);
+	  fd = open(paths, O_WRONLY, 0644);
+	  if (fd >= 0)
+	  {
+	    lseek(fd, 16 + me->p_pos * sizeof(struct statentry), 0);
+	    write(fd, newpass, 16);
+	    close(fd);
+	    respond("Password changed.", 0);
+	  }
+	  else
+	  {
+	    respond("open() of playerfile failed, password not changed.", 0);
+	  }
+	}
+	else
+	  respond("Passwords did not match, password unchanged.", 0);
+	passver = 0;
+      }
+    }
+    return 1;
+   case RATINGSTOK:		/* print your ratings */
+    {
+      struct rating r;
+      compute_ratings(me, &r);
+
+      sprintf(buf, "Bomb:%5.2f   Plnts:%5.2f   Rsrcs:%5.2f    Dshs:%5.2f   Offns:%5.2f", r.bombrat, r.planetrat, r.resrat, r.dooshrat, r.offrat);
+      respond(buf, 0);
+
+      sprintf(buf, "  JS:%5.2f      SB:%5.2f      WB:%5.2f   Ratio:%5.2f", r.jsrat, r.sbrat, r.wbrat, r.ratio);
+      respond(buf, 0);
+
+      sprintf(buf, "Overall Ratings:  Battle:%5.2f   Strat:%5.2f   Spec. Ship:%5.2f", r.battle, r.strategy, r.special);
+      respond(buf, 0);
+    }
+    return 1;
+
+   case RANKTOK:		/* print the requirements for the next rank */
+    {
+      int rank;
+      rank = me->p_stats.st_rank;
+      strcpy(buf, "Your current rank is ");
+      strcat(buf, ranks[rank].name);
+      respond(buf, 0);
+      if (rank == NUMRANKS - 1)
+	return 1;
+
+      sprintf(buf, "To make the next rank (%s) you need:",
+	      ranks[rank + 1].name);
+      respond(buf, 0);
+
+      sprintf(buf, "    Genocides: %d   DI: %.2f   Battle: %.2f",
+	      ranks[rank + 1].genocides, ranks[rank + 1].di,
+	      ranks[rank + 1].battle);
+      respond(buf, 0);
+
+      sprintf(buf, "    Strategy: %.2f   Spec. Ships: %.2f",
+	      ranks[rank + 1].strategy, ranks[rank + 1].specship);
+      respond(buf, 0);
+    }
+    return 1;
+
+   case HELPTOK:
+   default:
+    respond_with_help_string(available_cmds);
+    return 1;
+  }
+}
+
+/*
+ */
+
+#ifdef LEAGUE_SUPPORT
+
+static void
+umpire_speak(msg)
+  char *msg;
+{
+  pmessage(msg, -1, MALL, UMPIRE);
+}
+
+static void
+talk_about_team(team, type)
+  struct league_team *team;
+  char *type;
+{
+  char buf[120];
+  struct player *captain;
+
+  if (team->captain >= 0)
+    captain = &players[team->captain];
+  else
+    captain = 0;
+
+  sprintf(buf, "The %s team is named `%s'.", type, team->name);
+  respond(buf, 0);
+
+  if (captain)
+    sprintf(buf, "  %s (%s) is their captain.", captain->p_name,
+	    twoletters(captain));
+  else
+    strcpy(buf, "  They have not chosen a captain yet.");
+  respond(buf, 0);
+
+  if (team->index >= 0)
+  {
+    sprintf(buf, "  They have chosen the %s",
+	    teams[idx_to_mask(team->index)].name);
+  }
+  else
+  {
+    strcpy(buf, "  They have not chosen an empire yet.");
+  }
+  respond(buf, 0);
+}
+
+static int
+team_really_ready(team)
+  struct league_team *team;
+{
+  if (team->index < 0)
+  {
+    respond("You haven't chosen an empire", 1);
+    return 0;
+  }
+  if (team->name[0] == 0)
+  {
+    respond("You haven't chosen a name", 1);
+    return 0;
+  }
+  return 1;
+}
+
+void
+trydefect(victim, dest, destname, from, fromname, actor)
+  struct player *victim;
+  enum HomeAway dest;
+  char *destname;
+  enum HomeAway from;
+  char *fromname;
+  struct player *actor;
+{
+  char buf[120];
+  struct league_team *fromteam =
+  (from == AWAY) ? &status2->away : &status2->home;
+
+  if (victim->p_status == PFREE)
+  {
+    respond("Uh, he's not in the game.", 1);
+    return;
+  }
+
+  if (victim->p_homeaway == dest)
+  {
+    sprintf(buf, "%s already belong to the %s team",
+	    actor == victim ? "You" : "They", destname);
+    respond(buf, 1);
+    return;
+  }
+  if (actor->p_homeaway != from)
+  {
+    sprintf(buf, "You don't belong to the %s team.  You can't kick him off.",
+	    fromname);
+    respond(buf, 1);
+    return;
+  }
+  if (fromteam->captain == actor->p_no)
+  {
+    if (victim == actor)
+    {
+      if (status2->league > 1 || status2->home.ready || status2->away.ready)
+      {
+	respond("You can't defect in the middle of the game.  You're the captain!", 1);
+	return;
+      }
+      sprintf(buf, "%s (%s), the captain of the %s team, has defected!",
+	      victim->p_name, twoletters(victim), fromname);
+      umpire_speak(buf);
+    }
+    else
+    {
+      sprintf(buf, "%s (%s) has kicked %s (%s) off his team.",
+	      actor->p_name, twoletters(actor),
+	      victim->p_name, twoletters(victim));
+      umpire_speak(buf);
+    }
+  }
+  else
+  {
+    if (victim == actor)
+    {
+      if (status2->league > 1 || status2->home.ready || status2->away.ready)
+      {
+	respond("Only the captain can kick you off now.", 1);
+	return;
+      }
+      sprintf(buf, "%s (%s) has defected to the %s team!",
+	      victim->p_name, twoletters(victim),
+	      destname);
+      umpire_speak(buf);
+    }
+    else
+    {
+      respond("Only the captain can kick other people off the team.", 1);
+      return;
+    }
+  }
+  victim->p_homeaway = dest;
+  victim->p_status = PEXPLODE;
+  victim->p_whydead = KPROVIDENCE;
+  victim->p_explode = 1;
+}
+
+static int
+parse_league(subcommand)
+  char *subcommand;
+{
+  struct league_team *myteam, *otherteam;
+  char *teamtype;
+  char buf[120];
+  int i;
+  int iscaptain;
+  char *nexttoken;
+  static char captain_only[] = "That command is reserved for team captains.";
+  static struct control_cmd available_cmds[] = {
+    {"help", HELPTOK, 0},
+    {"captain", CAPTAINTOK, "captain [%d]"},
+    {"time", TIMETOK, "time [%d %d]"},
+    {"pass", PASSTOK, "pass"},
+    {"start", STARTTOK, "start [%d]"},
+    {"restart", RESTARTTOK, "restart [%d]"},
+#if 0
+    {"timeout", TIMEOUTTOK, "timeout [%d]"},
+#endif
+    {"teamname", TEAMNAMETOK, "teamname %s"},
+    {"information", INFOTOK, "information"},
+#if 0
+    /* damn, these should be initialized from the current race list */
+    {"federation", FEDTOK, "fed"},
+    {"romulan", ROMTOK, "rom"},
+    {"klingon", KLITOK, "kli"},
+    {"orion", ORITOK, "ori"},
+#endif
+
+    {"away", AWAYTOK, "away [%d]"},
+    {"home", HOMETOK, "home [%d]"},
+    {"newgalaxy", NEWGALAXY, "newgalaxy [%d]"},
+    {"pause", PAUSETOK, "pause"},
+    {"continue", CONTINUETOK, "continue"},
+    {"maxplayer", MAXPLAYERTOK, "maxplayer [%d]"},
+    {"freeslot", FREESLOTTOK, "freeslot %d"},
+    {0}
+  };
+
+  switch (me->p_homeaway)
+  {
+   case HOME:
+    myteam = &status2->home;
+    otherteam = &status2->away;
+    teamtype = "home";
+    break;
+   case AWAY:
+    myteam = &status2->away;
+    otherteam = &status2->home;
+    teamtype = "away";
+    break;
+   default:
+    respond("WHOA! internal error.  You aren't on a team!", 0);
+    respond("I'm afraid I'm going to have to kill you", 0);
+    me->p_status = PEXPLODE;
+    me->p_explode = 1;
+    return 0;
+  }
+
+  iscaptain = (myteam->captain == me->p_no);
+
+  /********************/
+
+  if (get_teamid(subcommand, &i, (char **) 0))
+  {
+    if (!iscaptain)
+    {
+      respond(captain_only, 1);
+      return 1;
+    }
+    if (status2->league != 1)
+    {
+      respond("The game has started.  You can't change your mind now.", 1);
+      return 1;
+    }
+    if ((myteam->ready || otherteam->ready) && myteam->index >= 0)
+    {
+      respond("One of the teams is ready.  You can't change your mind now.", 1);
+      return 1;
+    }
+    if (otherteam->index >= 0)
+    {
+      if (i == otherteam->index)
+      {
+	respond("The other team has already chosen that empire", 1);
+	return 1;
+      }
+    }
+    else
+    {
+      if (me->p_homeaway == HOME)
+      {
+	if (!status2->awaypassed)
+	{
+	  respond("Away team gets first choice of empire.", 1);
+	  return 1;
+	}
+      }
+      else /* away */ if (myteam->index >= 0 && 0 == status2->awaypassed)
+      {
+	respond("Give the other team a chance to choose a side, will ya?", 1);
+	return 1;
+      }
+      else if (status2->awaypassed == 1)
+      {
+	respond("You passed the choice of empire.  You have to wait for their choice.", 1);
+	return 1;
+      }
+    }
+
+    if (i == myteam->index)
+    {
+      respond("That already IS your empire.", 1);
+      return 1;
+    }
+    if (i < 0)
+    {
+#if 0
+      sprintf(buf, "The %s team no longer wishes the %s for their empire.",
+	      teamtype, teams[idx_to_mask(myteam->index)].name);
+#else
+      respond("You can't change your mind without a reset.  Ask for one", 1);
+      return 1;
+#endif
+    }
+    else
+      sprintf(buf, "The %s team has chosen the %s for their empire.",
+	      teamtype, teams[idx_to_mask(i)].name);
+    umpire_speak(buf);
+
+    myteam->index = i;
+
+    return 1;
+  }
+  else
+    switch (next_token(subcommand, available_cmds, &nexttoken))
+    {
+     default:
+     case HELPTOK:		/********************/
+      if (iscaptain)
+      {
+	respond_with_help_string(available_cmds);
+      }
+      else
+      {
+	respond("Available commands: captain [ %d ], time, information, maxplayer.", 0);
+      }
+      return 1;
+
+     case CAPTAINTOK:		/********************/
+      {
+	int j;
+	i = !get_int(nexttoken, &j, (char **) 0) || j;
+      }
+      if (i)
+      {
+	if (myteam->captain < 0 ||
+	    players[myteam->captain].p_status != PALIVE ||
+	    players[myteam->captain].p_team != me->p_team)
+	{
+	  if (myteam->captain >= 0)
+	  {
+	    /*
+	     * safety valve in case the person is ghostbusted or on another
+	     * team
+	     */
+	    sprintf(buf, "%s has been overthrown as captain of the %s team",
+		    players[myteam->captain].p_name, teamtype);
+	    umpire_speak(buf);
+	  }
+	  respond("OK.  *POOF* you're a captain!", 1);
+	  sprintf(buf, "%s (%s) is now fearless leader of the %s team!",
+		  me->p_name, twoletters(me), teamtype);
+	  umpire_speak(buf);
+	  myteam->captain = me->p_no;
+	}
+	else if (iscaptain)
+	{
+	  respond("Listen, silly.  You already are captain.  No point in rubbing it in", 1);
+	}
+	else
+	{
+	  /* if myteam->captain were <0, we wouldn't get here */
+	  struct player *capn = &players[myteam->captain];
+	  sprintf(buf, "Sorry, %s (%s) is already captain of your team.",
+		  capn->p_name, twoletters(capn));
+	  respond(buf, 1);
+	}
+      }
+      else
+      {
+	if (iscaptain)
+	{
+	  respond("Wimp.  We didn't want you for captain anyway.", 1);
+	  sprintf(buf, "%s (%s) has chickened out.",
+		  me->p_name, twoletters(me));
+	  umpire_speak(buf);
+	  sprintf(buf, "Who now will lead the %s team?", teamtype);
+	  umpire_speak(buf);
+	  myteam->captain = -1;
+	}
+	else
+	{
+	  respond("You can't quit being a captain.  You weren't one in the first place.", 1);
+	}
+      }
+      return 1;
+
+     case TIMETOK:		/********************/
+      if (0 == *nexttoken)
+      {
+	switch (status2->league)
+	{
+	 case 2:
+	  sprintf(buf, "%d seconds left in pre-tourney warm-up.",
+		  status2->leagueticksleft / SECONDS(1));
+	  break;
+	 case 3:
+	  sprintf(buf, "%d minutes left in regulation play.",
+		  status2->leagueticksleft / MINUTES(1));
+	  break;
+	 case 4:
+	  sprintf(buf, "%d minutes left in overtime.",
+		  status2->leagueticksleft / MINUTES(1));
+	  break;
+	 default:
+	  sprintf(buf, "game is configured for %d minutes (%d overtime).",
+	      configvals->regulation_minutes, configvals->overtime_minutes);
+	  break;
+	}
+	respond(buf, 0);
+	return 1;
+      }
+      else if (!iscaptain)
+      {
+	respond(captain_only, 1);
+	return 1;
+      }
+      else if (status2->league != 1)
+      {
+	respond("You can only adjust the time parameters during the configuration phase", 1);
+	return 1;
+      }
+      else if (otherteam->ready)
+      {
+	respond("The other team is ready to start.  You can't change the game params NOW.", 1);
+	return 1;
+      }
+      else if (!get_int(nexttoken, &myteam->desired.regulation, &nexttoken)
+	     || !get_int(nexttoken, &myteam->desired.overtime, (char **) 0))
+      {
+	respond("Usage: time [ %d %d ]", 1);
+	return 1;
+      }
+
+      if (status2->home.desired.regulation == status2->away.desired.regulation
+	&& status2->home.desired.overtime == status2->away.desired.overtime)
+      {
+	configvals->regulation_minutes = status2->home.desired.regulation;
+	configvals->overtime_minutes = status2->home.desired.overtime;
+	sprintf(buf, "The captains have agreed to a %d minute game (%d overtime).", configvals->regulation_minutes, configvals->overtime_minutes);
+	umpire_speak(buf);
+      }
+      else
+      {
+	sprintf(buf, "The %s team wishes a game of %d minutes (%d overtime)",
+	    teamtype, myteam->desired.regulation, myteam->desired.overtime);
+	umpire_speak(buf);
+      }
+
+      return 1;
+
+     case PASSTOK:		/********************/
+      if (status2->league != 1)
+      {
+	respond("The time for that is long past.", 1);
+	return 1;
+      }
+      if (!iscaptain)
+      {
+	respond(captain_only, 1);
+	return 1;
+      }
+      if (me->p_homeaway == AWAY && status2->awaypassed)
+      {
+	respond("You already passed the choice of empire.", 1);
+	return 1;
+      }
+      else if (me->p_homeaway == HOME && status2->awaypassed == 0)
+      {
+	respond("You can't possibly pass the choice of empire.  You don't HAVE it!", 1);
+	return 1;
+      }
+      else if (status2->awaypassed > 1)
+      {
+	respond("You both passed already, so get on with it.  (indecisive wishy-washy cretins)", 1);
+	return 1;
+      }
+      status2->awaypassed++;
+
+      sprintf(buf, "The %s team has passed the choice of empire", teamtype);
+      umpire_speak(buf);
+
+      if (status2->awaypassed > 1)
+      {
+	umpire_speak("Computer choosing randomly for both teams");
+	if (status2->home.index < 0)
+	{
+	  status2->home.index = lrand48() % ((status2->away.index < 0) ? 4 : 3);
+	  if (status2->away.index >= 0 &&
+	      status2->home.index >= status2->away.index)
+	    status2->home.index++;
+	}
+	if (status2->away.index < 0)
+	{
+	  status2->away.index = lrand48() % 3;
+	  if (status2->away.index >= status2->home.index)
+	    status2->away.index++;
+	}
+      }
+      return 1;
+
+     case STARTTOK:		/********************/
+      if (status2->league != 1)
+      {
+	respond("The game has already started.", 1);
+	return 1;
+      }
+      if (!iscaptain)
+      {
+	respond(captain_only, 1);
+	return 1;
+      }
+      if (get_int(nexttoken, &myteam->ready, (char **) 0) && !myteam->ready)
+      {
+	sprintf(buf, "The %s team is not ready.", teamtype);
+	umpire_speak(buf);
+	return 0;
+      }
+      myteam->ready = 1;
+      if (!team_really_ready(myteam))
+      {
+	respond("Your team is not really ready.  You need a name and an empire.", 0);
+	myteam->ready = 0;
+	return 0;
+      }
+
+      if (otherteam->ready && !team_really_ready(otherteam))
+      {
+	otherteam->ready = 0;
+	sprintf(buf, "The %s team was ready but the other wasn't.", teamtype);
+      }
+      else
+      {
+	sprintf(buf, "The %s team is ready to start now.", teamtype);
+      }
+      umpire_speak(buf);
+
+
+      if (otherteam->ready)
+      {
+	/* shit! we're good to go! */
+
+	umpire_speak("Both sides are ready.  Let the carnage begin!");
+	umpire_speak("Everybody dies.  T-mode starts in 1 minute.");
+	status2->league = 2;
+	status2->leagueticksleft = 60 * TICKSPERSEC;
+
+	/* version */
+
+	explode_everyone(KTOURNSTART, 0);
+      }
+      return 1;
+
+     case RESTARTTOK:		/********************/
+      if (status2->league != 1)
+      {
+	respond("The game has started.  You can't change your mind now.", 1);
+	return 1;
+      }
+
+      if (!iscaptain)
+      {
+	respond(captain_only, 1);
+	return 1;
+      }
+
+      myteam->desired.restart = !get_int(nexttoken, &i, (char **) 0) || i;
+
+      sprintf(buf, myteam->desired.restart ?
+	      "%s (%s) would like to restart team selection." :
+	      "%s (%s) is satisfied with the teams.",
+	      me->p_name, twoletters(me));
+      umpire_speak(buf);
+
+      if (status2->home.desired.restart && status2->away.desired.restart)
+      {
+	umpire_speak("Both captains have agreed to restart team selection.");
+
+	status2->awaypassed = 0;
+	status2->home.index = status2->away.index = -1;
+
+	status2->home.ready = status2->away.ready = 0;
+
+	status2->home.desired.restart = status2->away.desired.restart = 0;
+      }
+
+      return 1;
+
+     case TIMEOUTTOK:
+      respond("NYI", 0);
+      return 1;
+
+     case TEAMNAMETOK:		/********************/
+      if (status2->league != 1)
+      {
+	respond("The game has started.  You can't change your mind now.", 1);
+	return 1;
+      }
+      if (!iscaptain)
+      {
+	respond(captain_only, 1);
+	return 1;
+      }
+      if (0 == *nexttoken)
+      {
+	respond("What do you want to call your team?\n", 1);
+	return 1;
+      }
+      strncpy(myteam->name, nexttoken, sizeof(myteam->name));
+      myteam->name[sizeof(myteam->name) - 1] = 0;
+
+      sprintf(buf, "Henceforth let the %s team be known as `%s'!",
+	      teamtype, myteam->name);
+      umpire_speak(buf);
+
+      return 1;
+
+     case INFOTOK:		/********************/
+      sprintf(buf, "The game will last for %d minutes (%d overtime)",
+	      configvals->regulation_minutes,
+	      configvals->overtime_minutes);
+      respond(buf, 0);
+      sprintf(buf, "Teams are limited to %d players on the field at once",
+	      configvals->playersperteam);
+      respond(buf, 0);
+      sprintf(buf, "You are on the %s team.", teamtype);
+      respond(buf, 0);
+      talk_about_team(&status2->home, "home");
+      talk_about_team(&status2->away, "away");
+
+      if (status2->awaypassed > 1)
+	umpire_speak("Both teams passed empire choice.  Computer assigned.");
+      else if (status2->awaypassed)
+	umpire_speak("Away has passed empire choice to the Home team");
+      else
+	umpire_speak("Away chooses empire first");
+
+      return 1;
+
+
+     case AWAYTOK:		/********************/
+      {
+	struct player *victim;
+	if (!*nexttoken)
+	  victim = me;
+	else
+	{
+	  int idx = get_slotnum(nexttoken, (char **) 0);
+	  if (idx < 0)
+	  {
+	    respond("`league away' requires a valid slot number", 0);
+	    return 1;
+	  }
+	  else
+	  {
+	    victim = &players[idx];
+	  }
+	}
+	trydefect(victim, AWAY, "away", HOME, "home", me);
+      }
+      return 1;
+
+     case HOMETOK:		/********************/
+      {
+	struct player *victim;
+	if (!*nexttoken)
+	  victim = me;
+	else
+	{
+	  int idx = get_slotnum(nexttoken, (char **) 0);
+	  if (idx < 0)
+	  {
+	    respond("`league away' requires a valid slot number", 0);
+	    return 1;
+	  }
+	  else
+	  {
+	    victim = &players[idx];
+	  }
+	}
+	trydefect(victim, HOME, "home", AWAY, "away", me);
+      }
+
+      return 1;
+     case NEWGALAXY:		/********************/
+
+      if (status2->league != 1)
+      {
+	respond("The game has started.  You can't change your mind now.", 1);
+	return 1;
+      }
+
+      if (myteam->ready || otherteam->ready)
+      {
+	respond("You can't reset the galaxy now.  We're almost ready!", 1);
+	return 1;
+      }
+      if (!iscaptain)
+      {
+	respond(captain_only, 1);
+	return 1;
+      }
+
+      {
+	int j;
+	myteam->desired.galaxyreset =
+	  !get_int(nexttoken, &j, (char **) 0) || j;
+      }
+
+      if (myteam->desired.galaxyreset)
+      {
+	sprintf(buf, "%s (%s) is dissatisfied with the galaxy",
+		me->p_name, twoletters(me));
+      }
+      else
+      {
+	sprintf(buf, "%s (%s) thinks the galaxy is just fine, thank you.",
+		me->p_name, twoletters(me));
+      }
+      umpire_speak(buf);
+
+      if (status2->home.desired.galaxyreset &&
+	  status2->away.desired.galaxyreset)
+      {
+	umpire_speak("Both captains have agreed that the galaxy sucks.");
+	status2->newgalaxy = 1;
+	warning("Creating new galaxy");
+
+	status2->home.desired.galaxyreset = status2->away.desired.galaxyreset = 0;
+
+	status2->awaypassed = 0;
+	status2->home.index = status2->away.index = -1;
+	status2->home.ready = status2->away.ready = 0;
+	status2->home.desired.restart = status2->away.desired.restart = 0;
+      }
+
+      return 1;
+
+     case PAUSETOK:		/********************/
+      if (!iscaptain)
+      {
+	respond(captain_only, 1);
+	return 1;
+      }
+
+      myteam->desirepause = 1;
+
+      if (status2->home.desirepause && status2->away.desirepause)
+      {
+	/* well, it's unanimous! */
+	status2->paused = SECONDS(10);
+	umpire_speak("The game has been paused");
+      }
+      else
+      {
+	sprintf(buf, "The %s team wishes to PAUSE the game.", teamtype);
+	umpire_speak(buf);
+      }
+
+      status2->pausemsgfuse = 0;
+
+      return 1;
+
+     case CONTINUETOK:		/********************/
+      if (!iscaptain)
+      {
+	respond(captain_only, 1);
+	return 0;
+      }
+
+      myteam->desirepause = 0;
+
+      sprintf(buf, "The %s team wishes to CONTINUE.", teamtype);
+      umpire_speak(buf);
+
+      status2->pausemsgfuse = 0;
+
+      return 1;
+
+     case MAXPLAYERTOK:
+      {
+	int mp;
+	if (!get_int(nexttoken, &mp, (char **) 0))
+	{
+	  sprintf(buf, "The game is currently configured for a maximum of %d players on each", configvals->playersperteam);
+	  respond(buf, 0);
+	  respond("team.", 0);
+	  if (configvals->playersperteam != myteam->desired.maxplayers)
+	  {
+	    sprintf(buf, "You, however, want it to be %d.",
+		    myteam->desired.maxplayers);
+	    respond(buf, 0);
+	  }
+	}
+	else
+	{
+	  if (!iscaptain)
+	  {
+	    respond(captain_only, 1);
+	    return 1;
+	  }
+
+	  if (mp < 1)
+	  {
+	    respond("That's a stupid value for players-per-team.", 1);
+	    return 1;
+	  }
+
+	  myteam->desired.maxplayers = mp;
+	  if (status2->away.desired.maxplayers
+	      == status2->home.desired.maxplayers)
+	  {
+	    configvals->playersperteam = mp;
+	    sprintf(buf, "Captains have agreed to limit players per team to %d", mp);
+	    umpire_speak(buf);
+	  }
+	  else
+	  {
+	    sprintf(buf, "The %s team would like to limit the number of ",
+		    teamtype);
+	    umpire_speak(buf);
+	    sprintf(buf, "players per team to %d", mp);
+	    umpire_speak(buf);
+	  }
+	}
+      }
+      return 1;
+     case FREESLOTTOK:
+      {
+	int slotnum;
+	struct player *victim;
+	slotnum = get_slotnum(nexttoken, (char **) 0);
+	if (slotnum < 0)
+	{
+	  respond("freeslot requires slot number", 1);
+	  return 0;
+	}
+
+	victim = &players[slotnum];
+	if (victim->p_ntspid)
+	  kill(victim->p_ntspid, SIGHUP);
+
+	victim->p_status = PFREE;
+	victim->p_ntspid = 0;
+
+	sprintf(buf, "Player slot %s has been freed by %s (%2s).",
+		nexttoken, me->p_name, twoletters(me));
+	umpire_speak(buf);
+      }
+      return 1;
+    }
+}
+#endif
+/*
+ */
+
+
+
+/*
+ * Parse command messages.
+ */
+
+int
+parse_command_mess(str, who)
+  char *str;
+  unsigned char who;
+{
+  char buf[120];
+  char *nexttoken;
+  int godlike = me->p_stats.st_royal - GODLIKE + 1;
+
+  static struct control_cmd available_cmds[] = {
+    {"help", HELPTOK, 0},
+    {"control", CONTROLTOK, "control ..."},
+    {"league", LEAGUETOK, "league ..."},
+    {"version", VERSIONTOK, "version"},
+    {"player", PLAYERTOK, "player ..."},
+    {"queue", QUEUETOK, "queue"}, {"tq", QUEUETOK, 0},
+    {"information", INFOTOK, "information ..."},
+    {"observe", OBSERVETOK, "observe"},
+    {"parameters", PARAMTOK, "parameters"}, {"params", PARAMTOK, 0},
+    {"cluecheck", CLUECHECKTOK, "cluecheck [%s]"},
+    {0}
+  };
+
+  if (godlike < 0)
+    godlike = 0;
+
+
+
+#if 0
+  /* so we won't damage the original string */
+  strcpy(buf, str);
+
+  dotokenize(buf);
+
+  if (sizetokstring == 0)	/* nothing?  ok, then let them send it :) */
+    return 0;
+#endif
+
+  switch (next_token(str, available_cmds, &nexttoken))
+  {
+   case HELPTOK:
+    sprintf(buf, "Available commands: %s%s%s",
+	    godlike ? "control ..., " : "",
+#ifdef LEAGUE_SUPPORT
+	    status2->league ? "league ..., " :
+#endif
+	    "",
+	    "version, player ..., queue,");
+    respond(buf, 0);
+    respond("   information ..., observe [%d], parameters, cluecheck [%s], help", 0);
+    return 1;
+   case CONTROLTOK:
+#ifdef GOD_CONTROLS
+#ifdef LEAGUE_SUPPORT
+    if (status2->league)
+    {
+      respond("God controls are disabled during league play.", 1);
+    }
+    else
+#endif
+      return parse_control(nexttoken);
+#else
+    respond("This ntserv is not compiled with god controls.", 1);
+    return 1;
+#endif
+
+   case VERSIONTOK:
+    sprintf(buf, "NetrekII (Paradise), %s", PARAVERS);
+    respond(buf, 0);
+    return 1;
+
+   case PLAYERTOK:
+    return parse_player(nexttoken);
+
+   case QUEUETOK:
+    if (me->p_status == PTQUEUE)
+    {
+      detourneyqueue();
+    }
+    else if (status->tourn)
+      respond("Dude, what are you waiting for!?  It's T-mode.  GO FIGHT!", 1);
+    else if (!(me->p_flags & PFGREEN))
+      respond("Can not enter the tourney queue while at alert", 1);
+#ifndef AEDILE
+    else if (me->p_damage != 0 || me->p_shield < me->p_ship.s_maxshield)
+      respond("Can not enter the tourney queue while damaged", 1);
+#endif
+    else if (me->p_armies > 0)
+      respond("Can not take armies into the tourney queue", 1);
+    else
+    {
+      /*
+       * well, stick them on the queue.  They will be awoken when T mode
+       * arrives
+       */
+
+      evaporate(me);
+
+      /* need code to blab about this */
+      me->p_status = PTQUEUE;
+      sprintf(buf, "%s has entered the tournament queue to wait for T-mode", me->p_name);
+      pmessage(buf, -1, MALL, "GOD->ALL");
+    }
+    return 1;
+
+   case LEAGUETOK:
+#ifdef LEAGUE_SUPPORT
+    if (status2->league)
+    {
+      return parse_league(nexttoken);
+    }
+    else
+    {
+      respond("League commands are disabled during non-league play.", 1);
+      return 1;
+    }
+#else
+    respond("Server is not compiled with league support.", 1);
+    respond("Edit config.h, recompile, install, and nuke the game.", 1);
+    return 1;
+#endif
+
+   case INFOTOK:
+    return parse_info(nexttoken);
+
+   case OBSERVETOK:
+    {
+      int i;
+      i = !get_int(nexttoken, &i, (char **) 0) || i;
+
+      if (i)
+      {
+	if (me->p_observer && me->p_status == POBSERVE)
+	{
+	  respond("You are already an observer.", 1);
+	}
+	else
+	{
+	  if (!(me->p_flags & PFGREEN))
+	    respond("Can not become an observer while at alert", 1);
+	  else if (me->p_damage != 0 || me->p_shield < me->p_ship.s_maxshield)
+	    respond("Can not become an observer while damaged", 1);
+	  else if (me->p_armies > 0)
+	    respond("Can not become an observer while carrying armies", 1);
+	  else
+	  {
+	    evaporate(me);
+	    me->p_status = POBSERVE;
+	  }
+	  me->p_observer = 1;
+	}
+      }
+      else
+      {
+	if (me->p_observer && me->p_status == POBSERVE)
+	{
+	  me->p_observer = 0;
+#if 1
+#ifdef LEAGUE_SUPPORT
+	  if (!(status2->league && status->tourn))
+#endif
+	    evaporate(me);
+#else
+	  respond("You may now self-destruct and reenter the game as a player", 0);
+	  respond("(assuming there's room for you).", 0);
+#endif
+	}
+      }
+    }
+    return 1;
+
+   case PARAMTOK:
+    warning("Transmitting new game parameters");
+    updateGameparams();
+    return 1;
+   case CLUECHECKTOK:
+#if defined(CLUECHECK1) || defined(CLUECHECK2)
+    return accept_cluecheck(nexttoken);
+#else
+    return 0;
+#endif
+   default:
+    return 0;
+  }
+}