/* bzflag
 * Copyright (c) 1993-2017 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

/* this should be the only header necessary except for headers specific
 * to the class implementation (such as version.h)
 */
#include "CmdLineOptions.h"

// System headers
#include <time.h>

// implementation-specific bzflag headers
#include "version.h"
#include "Team.h"
#include "TextUtils.h"
#include "BZDBCache.h"
#include "BzMaterial.h"
#include "ObstacleMgr.h"

/* FIXME implementation specific header for global that should eventually go away */
#include <vector>
#include <set>

// for -pidfile option
#ifdef _WIN32
#include <process.h>
#else
#include <sys/types.h>
#include <unistd.h>
#endif

// implementation-specific bzfs-specific headers
#include "bzfs.h"
#include "RecordReplay.h"
#include "BZWError.h"
#include "Permissions.h"
#include "EntryZones.h"


// import from TextUtils for convenience
using TextUtils::compare_nocase;

const char *usageString =
"[-a <vel> <rot>] "
"[-adminlagannounce <time/ms>] "
"[-admsg <text>] "
"[-advertise <group,group...>] "
"[-autoTeam] "
"[-b] "
"[-badwords <filename>] "
"[-ban ip{,ip}*] "
"[-banfile <filename>] "
"[-c] "
"[-cache <url prefix>] "
"[-cacheout <filename>] "
"[-conf <filename>] "
"[-cr] "
"[-d] "
"[-density <num>] "
"[-disableBots] "
"[+f {good|<id>}] "
"[-f {bad|<id>}] "
"[-fb] "
"[-filterCallsigns] "
"[-filterChat] "
"[-filterSimple] "
"[-g] "
"[-gndtex <texture name>] "
"[-groupdb <group file>] "
"[-h] "
"[-handicap] "
"[-helpmsg <file> <name>] "
"[-i interface] "
"[-j] "
"[-jitterdrop <num>] "
"[-jitterwarn <time/ms>] "
"[-lagannounce <time/ms>] "
"[-lagdrop <num>] "
"[-lagwarn <time/ms>] "
"[-loadplugin <pluginname,commandline>] "
"[-masterBanURL <URL>] "
"[-maxidle <time/s>] "
"[-mp {<count>|[<count>][,<count>][,<count>][,<count>][,<count>][,<count>]}] "
"[-mps <score>] "
"[-ms <shots>] "
"[-mts <score>] "
"[-noMasterBanlist] "
"[-noradar] "
"[-noTeamKills] "
"[-offa] "
"[-p <port>] "
"[-packetlossdrop <num>] "
"[-packetlosswarn <%>] "
"[-passwd <password>] "
"[-pidfile <filename>] "
"[-poll <variable>=<value>] "
#ifdef PRINTSCORE
"[-printscore] "
#endif
"[-publictitle <server-description>] "
"[-publicaddr <server-hostname>[:<server-port>]] "
"[-publiclist <list-server-url>] "
"[-publickey <key>] "
"[-q] "
"[+r] "
"[-rabbit [score|killer|random]] "
"[-recbuf <Mbytes>] "
"[-recbufonly] "
"[-recdir <dirname>] "
"[-replay] "
"[-reportfile <filename>] "
"[-reportpipe <filename>] "
"[+s <flag-count>] "
"[-s <flag-count>] "
"[-sa] "
"[-sb] "
"[-set <name> <value>] "
"[-setforced <name> <value>] "
"[-sl <id> <num>] "
"[-spamtime <time>] "
"[-spamwarn <warnAmt>] "
"[-speedtol <tolerance>] "
"[-srvmsg <text>] "
"[-st <time>] "
"[-sw <num>] "
"[-synctime] "
"[-synclocation] "
"[-t] "
"[-tftimeout <seconds>] "
"[-time {<seconds>|endTime}] "
"[-timemanual] "
"[-tk] "
"[-tkannounce] "
"[-tkkr <percent>] "
"[-ts [micros]] "
#ifdef HAVE_MINIUPNPC_MINIUPNPC_H
"[-UPnP] "
#endif
"[-userdb <user permissions file>] "
"[-utc] "
"[-vars <filename>] "
"[-version] "
"[-world <filename>] "
"[-worldsize <world size>] "
"[-ws <number of wall sides>] ";

const char *extraUsageString =
"\n"
"BZFS Option Descriptions\n"
"\n"
"\t-a: maximum acceleration settings\n"
"\t-adminlagannounce: lag announcement threshhold time [ms]\n"
"\t-admsg: specify a <msg> which will be broadcast every 15 minutes\n"
"\t-advertise: specify which groups to advertise to (list server)\n"
"\t-autoTeam: automatically assign players to teams when they join\n"
"\t-b: randomly oriented buildings\n"
"\t-badwords: bad-word file\n"
"\t-ban ip{,ip}*: ban players based on ip address\n"
"\t-banfile: specify a file to load and store the banlist in\n"
"\t-c: classic capture-the-flag style game,\n"
"\t-cache: url to get binary formatted world\n"
"\t-cacheout: generate a binary cache file\n"
"\t-conf: configuration file\n"
"\t-cr: capture-the-flag style game with random world\n"
"\t-d: increase debugging level\n"
"\t-density: specify building density for random worlds (default=5)\n"
"\t-disableBots: disallow clients from using autopilot or robots\n"
"\t+f: always have flag <id> available\n"
"\t-f: never randomly generate flag <id>\n"
"\t-fb: allow flags on box buildings\n"
"\t-filterCallsigns: filter callsigns to disallow inappropriate user names\n"
"\t-filterChat: filter chat messages\n"
"\t-filterSimple: perform simple exact matches with the bad word list\n"
"\t-g: serve one game and then exit\n"
"\t-gndtex: specify ground texture\n"
"\t-groupdb: file to read for group permissions\n"
"\t-h: use random building heights\n"
"\t-handicap: give advantage based on relative playing ability\n"
"\t-helpmsg: show the lines in <file> on command /help <name>\n"
"\t-i: listen on <interface>\n"
"\t-j: allow jumping\n"
"\t-jitterdrop: drop player after this many jitter warnings\n"
"\t-jitterwarn: jitter warning threshhold time [ms]\n"
"\t-lagannounce: lag announcement threshhold time [ms]\n"
"\t-lagdrop: drop player after this many lag warnings\n"
"\t-lagwarn: lag warning threshhold time [ms]\n"
"\t-loadplugin: load the specified plugin with the specified commandline\n"
"\t\tstring\n"
"\t-masterBanURL: URL to atempt to get the master ban list from <URL>\n"
"\t-maxidle: idle kick threshhold [s]\n"
"\t-mp: maximum players total or per team\n"
"\t-mps: set player score limit on each game\n"
"\t-ms: maximum simultaneous shots per player\n"
"\t-mts: set team score limit on each game\n"
"\t-noMasterBanlist: has public servers ignore the master ban list\n"
"\t-noradar: disallow the use of radar\n"
"\t-noTeamKills: Players on the same team are immune to each other's shots. Rogue is excepted.\n"
"\t-offa: teamless free-for-all game stye\n"
"\t-p: use alternative port (default=5154)\n"
"\t-packetlossdrop: drop player after this many packetloss warnings\n"
"\t-packetlosswarn: packetloss warning threshold [%]\n"
"\t-passwd: specify a <password> for operator commands\n"
"\t-pidfile: write the process id into <filename> on startup\n"
"\t-poll: configure several aspects of the in-game polling system\n"
#ifdef PRINTSCORE
"\t-printscore: write score to stdout whenever it changes\n"
#endif
"\t-publictitle <server-description>\n"
"\t-publicaddr <effective-server-hostname>[:<effective-server-port>]\n"
"\t-publiclist <list-server-url>\n"
"\t-publickey <key>\n"
"\t-q: don't listen for or respond to pings\n"
"\t+r: all shots ricochet\n"
"\t-rabbit [score|killer|random]: rabbit chase style\n"
"\t-recbuf <Mbytes>: start with a recording buffer of specified megabytes\n"
"\t-recbufonly: disable recording directly to files\n"
"\t-recdir <dirname>: specify the directory for recorded file\n"
"\t-replay: setup the server to replay a previously saved game\n"
"\t-reportfile <filename>: the file to store reports in\n"
"\t-reportpipe <filename>: the program to pipe reports through\n"
"\t+s: always have <num> super flags (default=16)\n"
"\t-s: allow up to <num> super flags (default=16)\n"
"\t-sa: insert antidote superflags\n"
"\t-sb: allow tanks to respawn on buildings\n"
"\t-set <name> <value>: set a BZDB variable's value\n"
"\t-setforced <name> <value>: set a BZDB variable's value (whether it\n"
"\t\texists or not)\n"
"\t-sl: limit flag <id> to <num> shots\n"
"\t-spamtime <time>: make <time> be the required time in seconds between\n"
"\t\tmessages sent that are alike\n"
"\t-spamwarn <warnAmt>: warn a spammer that sends messages before\n"
"\t\tspamtime times out <warnAmt> many times\n"
"\t-speedtol: multiplier of normal speed for auto kick (default=1.25)\n"
"\t\tshould not be less than 1.0\n"
"\t-srvmsg: specify a <msg> to print upon client login\n"
"\t-st: shake bad flags in <time> seconds\n"
"\t-sw: shake bad flags after <num> wins\n"
"\t-synctime: synchronize time of day on all clients\n"
"\t-synclocation: synchronize latitude and longitude on all clients\n"
"\t-t: allow teleporters\n"
"\t-tftimeout: set timeout for team flag zapping (default=30)\n"
"\t-time: set time limit on each game in format of either seconds or ending time in h[h]:[mm:[ss]] format\n"
"\t-timemanual: countdown for timed games is started with /countdown\n"
"\t-tk: player does not die when killing a teammate\n"
"\t-tkannounce: announces team kills to the admin channel\n"
"\t-tkkr: team-kills-to-wins percentage (1-100) for kicking tk-ing players\n"
"\t-ts [micros]: timestamp all console output, [micros] to include\n"
"\t\tmicroseconds\n"
#ifdef HAVE_MINIUPNPC_MINIUPNPC_H
"\t-UPnP: enable UPnP\n"
#endif
"\t-userdb: file to read for user access permissions\n"
"\t-utc: timestamp console output in UTC instead of local time, implies -ts\n"
"\t-vars: file to read for worlds configuration variables\n"
"\t-version: print version and exit\n"
"\t-world: world file to load\n"
"\t-worldsize: numeric value for the size of the world (default=400)\n"
"\t-ws: numeric value for the number off outer walls (default=4)\n"
"\n"
"Poll Variables:  (see -poll)\n"
"\n"
"\tbanTime: number of minutes player should be banned (default=300)\n"
"\tvetoTime: max seconds authorized user has to abort poll (default=20)\n"
"\tvotePercentage: percentage of players required to affirm a poll\n"
"\t\t(default=50.1%)\n"
"\tvoteRepeatTime: minimum seconds required before a player may request\n"
"\t\tanother vote (default=300)\n"
"\tvotesRequired: minimum number of additional votes required to make a\n"
"\t\tvote valid (default=2)\n"
"\tvoteTime: maximum amount of time player has to vote, in seconds\n"
"\t\t(default=60)\n"
"\n";


// temp variables for carrying flag options from parse to finalize
static std::vector<std::string> storedFlagDisallows;
static std::vector<std::string> storedFlagCounts;
static std::map<std::string, int> storedFlagLimits;

/* private */

static void printVersion()
{
  std::cout << "BZFlag server " << getAppVersion() << " (protocol " << getProtocolVersion() <<
    ") http://BZFlag.org/\n";
  std::cout << bzfcopyright << std::endl;
  std::cout.flush();
}

static void usage(const char *pname)
{
  printVersion();
  std::cerr << "\nUsage: " << pname << ' ' << usageString << std::endl;
  exit(1);
}

static void extraUsage(const char *pname)
{
  char buffer[64];
  printVersion();
  std::cout << "\nUsage: " << pname << ' ' << usageString << std::endl;
  std::cout << std::endl << extraUsageString << std::endl << "Flag codes:\n";
  for (FlagTypeMap::iterator it = FlagType::getFlagMap().begin(); it != FlagType::getFlagMap().end(); ++it) {
    snprintf(buffer, 64, "\t%2.2s %s\n", (*it->second).flagAbbv.c_str(), (*it->second).flagName.c_str());
    std::cout << buffer;
  }
  exit(0);
}

static void checkArgc(int count, int& i, int argc, const char* option, const char *type = NULL)
{
  if ((i+count) >= argc) {
    if (count > 1) {
      std::cerr << count << " argument(s) expected for " << option << '\n';
    } else if (type != NULL) {
      std::cerr << type << " argument expected for " << option << '\n';
    } else {
      std::cerr << "argument expected for " << option << '\n';
    }
    usage("bzfs");
  }

  i++; // just skip the option argument string
}

static void checkFromWorldFile (const char *option, bool fromWorldFile)
{
  if (fromWorldFile) {
    std::cerr << "option \"" << option << "\" cannot be set within a world file" << '\n';
    usage("bzfs");
  }
}



static bool parsePlayerCount(const char *argv, CmdLineOptions &options)
{
  /* either a single number or 5 or 6 optional numbers separated by
   * 4 or 5 (mandatory) commas.
   */
  const char *scan = argv;
  while (*scan && *scan != ',') scan++;

  if (*scan == ',') {
    // okay, it's the comma separated list.  count commas
    int commaCount = 1;
    while (*++scan) {
      if (*scan == ',') {
	commaCount++;
      }
    }
    if (commaCount != 5) {
      std::cerr << "improper player count list\n";
      return false;
    }

    // no team size limit by default for real teams, set it to max players
    int i;
    for (i = 0; i < CtfTeams ; i++) {
      options.maxTeam[i] = maxRealPlayers;
    }
    // allow 5 Observers by default
    options.maxTeam[ObserverTeam] = 5;

    // now get the new counts

    // number of counts given
    int countCount = 0;
    scan = argv;
    // ctf teams plus observers
    for (i = 0; i < CtfTeams + 1; i++) {
      char *tail;
      long count = strtol(scan, &tail, 10);
      if (tail != scan) {
	// got a number
	countCount++;
	if (count < 0) {
	  options.maxTeam[i] = 0;
	} else {
	  if (count > maxRealPlayers) {
	    if (i == ObserverTeam && count > MaxPlayers)
	      options.maxTeam[i] = MaxPlayers;
	    else
	      options.maxTeam[i] = maxRealPlayers;
	  } else {
	    options.maxTeam[i] = uint8_t(count);
	  }
	}
      } // end if tail != scan
      while (*tail && *tail != ',') tail++;
      scan = tail + 1;
    } // end iteration over teams

    // if no/zero players were specified, allow a bunch of rogues
    bool allZero = true;
    for (i = 0; i < CtfTeams; i++) {
      if (options.maxTeam[i] != 0) {
	allZero = false;
      }
    }
    if (allZero) {
      options.maxTeam[RogueTeam] = MaxPlayers;
    }

    // if all counts explicitly listed then add 'em up and set maxRealPlayers
    // (unless max total players was explicitly set)
    if (countCount >= CtfTeams && maxRealPlayers == MaxPlayers) {
      maxRealPlayers = 0;
      for (i = 0; i < CtfTeams ; i++) {
	maxRealPlayers += options.maxTeam[i];
      }
    }

  } else {
    /* single number was provided instead of comma-separated */
    char *tail;
    long count = strtol(argv, &tail, 10);
    if (argv == tail) {
      std::cerr << "improper player count\n";
      return false;
    }
    if (count < 1) {
      maxRealPlayers = 1;
    } else {
      if (count > MaxPlayers) {
	maxRealPlayers = MaxPlayers;
      } else {
	maxRealPlayers = uint8_t(count);
      }
    }
    // limit max team size to max players
    for (int i = 0; i < CtfTeams ; i++) {
      if (options.maxTeam[i] > maxRealPlayers)
	options.maxTeam[i] = maxRealPlayers;
    }
  } // end check if comm-separated list

  maxPlayers = maxRealPlayers + options.maxTeam[ObserverTeam];
  if (maxPlayers > MaxPlayers) {
    maxPlayers = MaxPlayers;
  }

  return true;
}

static char **parseConfFile( const char *file, int &ac)
{
  std::vector<std::string> tokens;
  ac = 0;

  BZWError errorHandler(file);

  std::ifstream confStrm(file);
  if (confStrm.is_open()) {
    char buffer[1024];
    confStrm.getline(buffer,1024);

    if (!confStrm.good()) {
      errorHandler.fatalError(std::string("could not find bzflag configuration file"), 0);
      exit(1);
    }

    confStrm.seekg(0, std::ifstream::beg);
    while (confStrm.good()) {
      confStrm.getline(buffer,1024);
      std::string line = buffer;
      int startPos = line.find_first_not_of("\t \r\n");
      while ((startPos >= 0) && (line.at(startPos) != '#')) {
	int endPos;
	if (line.at(startPos) == '"') {
	  startPos++;
	  endPos = line.find_first_of('"', startPos);
	} else {
	  endPos = line.find_first_of("\t \r\n", startPos+1);
	}
	if (endPos < 0)
	  endPos = line.length();
	tokens.push_back(line.substr(startPos,endPos-startPos));
	startPos = line.find_first_not_of("\t \r\n", endPos+1);
      }
   }
  } else {
    errorHandler.fatalError(std::string("could not find bzflag configuration file"), 0);
    exit(1);
  }

  char **av = new char*[tokens.size()+1];
  av[0] = strdup("bzfs");
  ac = 1;
  for (std::vector<std::string>::iterator it = tokens.begin(); it != tokens.end(); ++it)
    av[ac++] = strdup((*it).c_str());
  return av;
}

static char **parseWorldOptions (const char *file, int &ac)
{
  std::vector<std::string> tokens;
  ac = 0;

  std::ifstream confStrm(file);
  if (confStrm.is_open()) {
    char buffer[1024];
    confStrm.getline(buffer,1024);

    if (!confStrm.good()) {
      std::cerr << "world file not found\n";
      usage("bzfs");
    }

    while (confStrm.good()) {
      std::string line = buffer;
      int startPos = line.find_first_not_of("\t \r\n");
      if (strncasecmp ("options", line.c_str() + startPos, 7) == 0) {
	confStrm.getline(buffer,1024);
	break;
      }
      confStrm.getline(buffer,1024);
    }

    while (confStrm.good()) {
      std::string line = buffer;
      int startPos = line.find_first_not_of("\t \r\n");
      if (strncasecmp ("end", line.c_str() + startPos, 3) == 0) {
	break;
      }

      while ((startPos >= 0) && (line.at(startPos) != '#')) {
	int endPos;
	if (line.at(startPos) == '"') {
	  startPos++;
	  endPos = line.find_first_of('"', startPos);
	} else {
	  endPos = line.find_first_of("\t \r\n", startPos+1);
	}
	if (endPos < 0)
	  endPos = line.length();
	tokens.push_back(line.substr(startPos,endPos-startPos));
	startPos = line.find_first_not_of("\t \r\n", endPos+1);
      }
      confStrm.getline(buffer,1024);
    }
  }

  char **av = new char*[tokens.size()+1];
  av[0] = strdup("bzfs");
  ac = 1;
  for (std::vector<std::string>::iterator it = tokens.begin(); it != tokens.end(); ++it)
    av[ac++] = strdup((*it).c_str());

  return av;
}


static bool accLimitSet = false;
static bool allFlagsOut = false;

void parse(int argc, char **argv, CmdLineOptions &options, bool fromWorldFile)
{
  // prepare flag counts
  int i;

  // InertiaGameStyle maintained just for compatibility
  // Same effect is achieved setting linear/angular Acceleration
  options.gameOptions |= int(InertiaGameStyle);

  // parse command line
  int playerCountArg = 0,playerCountArg2 = 0;
  for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-a") == 0) {
      // momentum settings
      accLimitSet = true;
      checkArgc(2, i, argc, argv[i]);
      options.linearAcceleration = (float)atof(argv[i]);
      options.angularAcceleration = (float)atof(argv[++i]);
      if (options.linearAcceleration < 0.0f)
	options.linearAcceleration = 0.0f;
      if (options.angularAcceleration < 0.0f)
	options.angularAcceleration = 0.0f;
    }
    else if (strcmp(argv[i], "-adminlagannounce") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.adminlagannounce = atoi(argv[i])/1000.0f;
    }
    else if (strcmp(argv[i], "-admsg") == 0) {
      checkArgc(1, i, argc, argv[i]);
      if ((options.advertisemsg != "") || (strlen (argv[i]) == 0)) {
	options.advertisemsg += "\\n";
      }
      options.advertisemsg += argv[i];
    }
    else if (strcmp(argv[i], "-advertise") == 0) {
      checkArgc(1, i, argc, argv[i]);
      if (checkCommaList (argv[i], 2048))
	      std::cerr << "Invalid group list for -advertise" << std::endl;
      else
	options.advertiseGroups = argv[i];
    }
    else if (strcmp(argv[i], "-autoTeam") == 0) {
      options.autoTeam = true;
    }
    else if (strcmp(argv[i], "-b") == 0) {
      // random rotation to boxes in capture-the-flag game
      options.randomBoxes = true;
    }
    else if (strcmp(argv[i], "-badwords") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      options.filterFilename = argv[i];
    }
    else if (strcmp(argv[i], "-ban") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      options.acl.ban(argv[i]);
    }
    else if (strcmp(argv[i], "-banfile") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      options.acl.setBanFile(argv[i]);
      if (!options.acl.load()) {
	std::cerr << "could not load banfile \"" << argv[i] << "\"" << std::endl;
	usage(argv[0]);
      }
    }
    else if (strcmp(argv[i], "-c") == 0) {
      // capture the flag style
      if (options.gameType == RabbitChase) {
	std::cerr << "Capture the flag incompatible with Rabbit Chase" << std::endl;
	std::cerr << "Capture the flag assumed" << std::endl;
      }
      options.gameType = ClassicCTF;
    }
    else if (strcmp(argv[i], "-cache") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      options.cacheURL = argv[i];
    }
    else if (strcmp(argv[i], "-cacheout") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      options.cacheOut = argv[i];
    }
    else if (strcmp(argv[i], "-conf") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      int ac;
      char **av;
      av = parseConfFile(argv[i], ac);
      // Theoretically we could merge the options specified in the conf file after parsing
      // the cmd line options. But for now just override them on the spot
      parse(ac, av, options);
      for (int j = 0; j < ac; j++)
	free(av[j]);
      delete[] av;
      options.numAllowedFlags = 0;
    }
    else if (strcmp(argv[i], "-cr") == 0) {
      // CTF with random world
      options.randomCTF = true;
      // capture the flag style
      if (options.gameType == RabbitChase) {
	std::cerr << "Capture the flag incompatible with Rabbit Chase" << std::endl;
	std::cerr << "Capture the flag assumed" << std::endl;
      }
      options.gameType = ClassicCTF;
    }
    else if (strcmp(argv[i], "-density") == 0) {
      if (i+1 != argc && isdigit(*argv[i+1])) {
	options.citySize = atoi(argv[i+1]);
	i++;
      } else {
	checkArgc(1, i, argc, argv[i], "integer");
      }
    }
    else if (strcmp(argv[i], "-disableBots") == 0) {
      // disallow clients from using autopilot or bots
      BZDB.set(StateDatabase::BZDB_DISABLEBOTS, "true");
    }
    else if (strncmp(argv[i], "-d", 2) == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      // increase debug level - this must be the last
      // option beginning with -d so that -dd, -ddd, etc. work
      const char num = argv[i][2];
      if ((num >= '0') && (num <= '9') && (argv[i][3] == 0)) {
	debugLevel = num - '0';
      }
      else {
	int count = 0;
	const char *scan;
	for (scan = (argv[i] + 1); *scan == 'd'; scan++) {
	  count++;
	}
	if (*scan != '\0') {
	  std::cerr << "ERROR: bad argument [" << argv[i] << "]" << std::endl;
	  usage(argv[0]);
	}
	debugLevel += count;
	// std::cout << "Debug level is now " << debugLevel << "" << std::endl;
      }
    }
    else if (strcmp(argv[i], "-f") == 0) {
      // disallow given flag
      checkArgc(1, i, argc, argv[i]);
      storedFlagDisallows.push_back(argv[i]);
    }
    else if (strcmp(argv[i], "+f") == 0) {
      // add required flag
      checkArgc(1, i, argc, argv[i]);
      storedFlagCounts.push_back(argv[i]);
    }
    else if (strcmp(argv[i], "-fb") == 0) {
      // flags on buildings
      options.flagsOnBuildings = true;
    }
    else if (strcmp(argv[i], "-filterCallsigns") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      options.filterCallsigns = true;
    }
    else if (strcmp(argv[i], "-filterChat") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      options.filterChat = true;
    }
    else if (strcmp(argv[i], "-filterSimple") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      options.filterSimple = true;
    }
    else if (strcmp(argv[i], "-g") == 0) {
      options.oneGameOnly = true;
    }
    else if (strcmp(argv[i], "-gndtex") == 0) {
      checkArgc(1, i, argc, argv[i]);
      BzMaterial material;
      material.setName("GroundMaterial");
      material.setTexture(argv[i]);
      MATERIALMGR.addMaterial(&material);
    }
    else if (strcmp(argv[i], "-groupdb") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      groupsFile = argv[i];
      logDebugMessage(1,"using group file \"%s\"\n", argv[i]);
    }
    else if (strcmp(argv[i], "-h") == 0) {
      options.randomHeights = true;
    }
    else if (strcmp(argv[i], "-help") == 0) {
      extraUsage(argv[0]);
    }
    else if (strcmp(argv[i], "-helpmsg") == 0) {
    checkArgc(2, i, argc, argv[i]);
    std::ifstream helpfile(argv[i]);
    if (!helpfile)
	std::cerr << "warning: helpmsg file \"" << argv[i] << "\" does not exist or can't be read" << std::endl;
    else if (!options.textChunker.parseFile(argv[i], argv[i+1], 50, MessageLen)) {
	    std::cerr << "couldn't read helpmsg file \"" << argv[i] << "\"" << std::endl;
	    usage(argv[0]);
      }
      i++;
    }
    else if (strcmp(argv[i], "-i") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      // use a different interface
      checkArgc(1, i, argc, argv[i]);
      options.pingInterface = argv[i];
    }
    else if (strcmp(argv[i], "-j") == 0) {
      // allow jumping
      options.gameOptions |= int(JumpingGameStyle);
    }
    else if (strcmp(argv[i], "-handicap") == 0) {
      // allow handicap advantage
      options.gameOptions |= int(HandicapGameStyle);
    }
    else if (strcmp(argv[i], "-jitterdrop") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.maxjitterwarn = atoi(argv[i]);
    }
    else if (strcmp(argv[i], "-jitterwarn") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.jitterwarnthresh = atoi(argv[i])/1000.0f;
    }
    else if (strcmp(argv[i], "-lagannounce") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.lagannounce = atoi(argv[i])/1000.0f;
    }
    else if (strcmp(argv[i], "-lagdrop") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.maxlagwarn = atoi(argv[i]);
    }
    else if (strcmp(argv[i], "-lagwarn") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.lagwarnthresh = atoi(argv[i])/1000.0f;
    }
    else if (strcmp(argv[i], "-loadplugin") == 0) {
      checkArgc(1, i, argc, argv[i]);
      std::vector<std::string> a = TextUtils::tokenize(argv[i],std::string(","), 2);
      CmdLineOptions::pluginDef	pDef;
      if (a.size() >= 1)
	pDef.plugin = a[0];
      if (a.size() >= 2)
	pDef.command = a[1];
      if (pDef.plugin.size())
	options.pluginList.push_back(pDef);
    }
    else if (strcmp(argv[i], "-maxidle") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.idlekickthresh = (float) atoi(argv[i]);
    }
    else if (strcmp(argv[i], "-mp") == 0) {
      // set maximum number of players
      checkArgc(1, i, argc, argv[i]);
      if (playerCountArg == 0)
	playerCountArg = i;
      else
	playerCountArg2 = i;
    }
    else if (strcmp(argv[i], "-mps") == 0) {
      // set maximum player score
      checkArgc(1, i, argc, argv[i]);
      options.maxPlayerScore = atoi(argv[i]);
      if (options.maxPlayerScore < 1) {
	std::cerr << "disabling player score limit" << std::endl;
	options.maxPlayerScore = 0;
      }
    }
    else if (strcmp(argv[i], "-ms") == 0) {
      // set maximum number of shots
      checkArgc(1, i, argc, argv[i]);
      int newMaxShots = atoi(argv[i]);
      if (newMaxShots == 0) {
	std::cerr << "WARNING: tanks will not be able to shoot" << std::endl;
	options.maxShots = 0;
      } else if (newMaxShots < 1) {
	std::cerr << "using minimum number of shots of 1" << std::endl;
	options.maxShots = 1;
      } else if (newMaxShots > MaxShots) {
	std::cerr << "using maximum number of shots of " << MaxShots << std::endl;
	options.maxShots = uint16_t(MaxShots);
      }
      else options.maxShots = uint16_t(newMaxShots);
    }
    else if (strcmp(argv[i], "-mts") == 0) {
      // set maximum team score
      checkArgc(1, i, argc, argv[i]);
      options.maxTeamScore = atoi(argv[i]);
      if (options.maxTeamScore < 1) {
	std::cerr << "disabling team score limit" << std::endl;
	options.maxTeamScore = 0;
      }
    }
    else if (strcmp(argv[i],"-noMasterBanlist") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      options.suppressMasterBanList = true;
    }
    else if (strcmp(argv[i],"-noradar") == 0) {
      BZDB.set(StateDatabase::BZDB_RADARLIMIT, "-1.0");
    }
    else if (strcmp(argv[i],"-masterBanURL") == 0) {
      /* if this is the first master ban url, override the default
       * list.  otherwise just keep adding urls.
       */
      checkFromWorldFile(argv[i], fromWorldFile);
      if (!options.masterBanListOverridden) {
	options.masterBanListURL.clear();
	options.masterBanListOverridden = true;
      }
      checkArgc(1, i, argc, argv[i]);
      options.masterBanListURL.push_back(argv[i]);
    }
    else if (strcmp(argv[i], "-noTeamKills") == 0) {
      // disable team killing
      if (options.gameType == OpenFFA)
	std::cerr << "noteamkills check WARNING: -noTeamKills is incompatible with -offa, ignoring" << std::endl;
      else
	options.gameOptions |= int(NoTeamKillsGameStyle);
    }
    else if (strcmp(argv[i], "-p") == 0) {
      // use a different port
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      options.wksPort = atoi(argv[i]);
      if (options.wksPort < 1 || options.wksPort > 65535)
	options.wksPort = ServerPort;
      else
	options.useGivenPort = true;
    }
    else if (strcmp(argv[i], "-packetlossdrop") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.maxpacketlosswarn = atoi(argv[i]);
    }
    else if (strcmp(argv[i], "-packetlosswarn") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.packetlosswarnthresh = atoi(argv[i])/1000.0f;
    }
    else if (strcmp(argv[i], "-passwd") == 0 || strcmp(argv[i], "-password") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      // at least put password someplace that ps won't see
      options.password = argv[i];
      memset(argv[i], 'X', options.password.size());
    }
    else if (strcmp(argv[i], "-pidfile") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      unsigned int pid = 0;
	  checkArgc(1, i, argc, argv[i]);
      FILE *fp = fopen(argv[i], "wt");
#ifndef _WIN32
      pid = getpid();
#else
      pid = _getpid();
#endif
      if (fp) {
	fprintf(fp, "%d", pid);
	fclose(fp);
      }
    }
    else if (strcmp(argv[i], "-poll") == 0) {
      // parse the variety of poll system variables
      checkArgc(1, i, argc, argv[i]);

      std::vector<std::string> args = TextUtils::tokenize(argv[i], std::string("="), 2, true);
      if (args.size() != 2) {
	std::cerr << "expected -poll variable=value" << std::endl;
	usage(argv[0]);
      }

      if (compare_nocase(args[0], "bantime") == 0) {
	options.banTime = (unsigned short int)atoi(args[1].c_str());
      } else if (compare_nocase(args[0], "vetotime") == 0) {
	options.vetoTime = (unsigned short int)atoi(args[1].c_str());
      } else if (compare_nocase(args[0], "votepercentage") == 0) {
	options.votePercentage = (float)atof(args[1].c_str());
      } else if (compare_nocase(args[0], "voterepeattime") == 0) {
	options.voteRepeatTime = (unsigned short int)atoi(args[1].c_str());
      } else if (compare_nocase(args[0], "votesrequired") == 0) {
	options.votesRequired = (unsigned short int)atoi(args[1].c_str());
      } else if (compare_nocase(args[0], "votetime") == 0) {
	options.voteTime = (unsigned short int)atoi(args[1].c_str());
      } else {
	std::cerr << "unknown variable for -poll, skipping";
      }
#ifdef PRINTSCORE
    }
    else if (strcmp(argv[i], "-printscore") == 0) {
      // dump score whenever it changes
      options.printScore = true;
#endif
    }
    else if (strcmp(argv[i], "-publictitle") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.publicizeServer = true;
      options.publicizedTitle = argv[i];
      if (options.publicizedTitle.length() > 127) {
	argv[i][127] = '\0';
	std::cerr << "description too long... truncated" << std::endl;
      }
    }
    else if (strcmp(argv[i], "-publicaddr") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      options.publicizedAddress = argv[i];
      options.publicizeServer = true;
    }
    else if (strcmp(argv[i], "-publiclist") == 0) {
      /* if this is the first -publiclist, override the default list
       * server.  otherwise just keep adding urls.
       */
      checkFromWorldFile(argv[i], fromWorldFile);
      if (!options.listServerOverridden) {
	options.listServerURL.clear();
	options.listServerOverridden = true;
      }
      checkArgc(1, i, argc, argv[i]);
      options.listServerURL.push_back(argv[i]);
    }
    else if (strcmp(argv[i], "-publickey") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      options.publicizeServer = true;
      // at least put publickey someplace that ps won't see
      options.publicizedKey = argv[i];
      memset(argv[i], 'X', options.publicizedKey.size());
      if (options.publicizedKey.length() == 0) {
	options.publicizeServer = false;
	std::cerr << "key too short server will not be made public" << std::endl;
      }
    }
    else if (strcmp(argv[i], "-q") == 0) {
      // don't handle pings
      checkFromWorldFile(argv[i], fromWorldFile);
      handlePings = false;
    }
    else if (strcmp(argv[i], "+r") == 0) {
      // all shots ricochet style
      options.gameOptions |= int(RicochetGameStyle);
    }
    else if (strcmp(argv[i], "-rabbit") == 0) {
      // rabbit chase style
      if (options.gameType == ClassicCTF) {
	std::cerr << "Rabbit Chase incompatible with Capture the flag" << std::endl;
	std::cerr << "Rabbit Chase assumed" << std::endl;
      }
      options.gameType = RabbitChase;
      // default selection style
      options.rabbitSelection = ScoreRabbitSelection;

      // if there are any arguments following, see if they are a
      // rabbit selection styles.
      if (i+1 != argc) {
	if (strcmp(argv[i+1], "score") == 0) {
	  options.rabbitSelection = ScoreRabbitSelection;
	  i++;
	} else if (strcmp(argv[i+1], "killer") == 0) {
	  options.rabbitSelection = KillerRabbitSelection;
	  i++;
	} else if (strcmp(argv[i+1], "random") == 0) {
	  options.rabbitSelection = RandomRabbitSelection;
	  i++;
	}
      }
    }
    else if (strcmp(argv[i], "-recbuf") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      Record::setSize (ServerPlayer, atoi(argv[i]));
      options.startRecording = true;
    }
    else if (strcmp(argv[i], "-recbufonly") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      Record::setAllowFileRecs (false);
    }
    else if (strcmp(argv[i], "-recdir") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      Record::setDirectory (argv[i]);
    }
    else if (strcmp(argv[i], "-replay") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      options.replayServer = true;
    }
    else if (strcmp(argv[i], "-reportfile") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      options.reportFile = argv[i];
    }
    else if (strcmp(argv[i], "-reportpipe") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      options.reportPipe = argv[i];
    }
    else if (strcmp(argv[i], "-tkannounce") == 0) {
      options.tkAnnounce = true;
    }
    else if (strcmp(argv[i], "+s") == 0 || strcmp(argv[i], "-s") == 0) {
      // with +s all flags are required to exist all the time
      allFlagsOut = argv[i][0] == '+' ? true : false;
      // set number of random flags
      if (i+1 < argc && isdigit(argv[i+1][0])) {
	++i;
	if ((options.numExtraFlags = atoi(argv[i])) == 0)
	  options.numExtraFlags = 16;
      } else {
	options.numExtraFlags = 16;
      }
    }
    else if (strcmp(argv[i], "-sa") == 0) {
      // insert antidote flags
      options.gameOptions |= int(AntidoteGameStyle);
    }
    else if (strcmp(argv[i], "-sb") == 0) {
      // respawns on buildings
      options.respawnOnBuildings = true;
    }
    else if (strcmp(argv[i], "-set") == 0) {
      const char *name, *value;
      checkArgc(2, i, argc, argv[i]);
      name = argv[i];
      if (!BZDB.isSet(name)) {
	std::cerr << "Unknown BZDB variable: " << name << std::endl;
	exit (1);
      }
      i++;
      value = argv[i];
      BZDB.set(name, value);
      logDebugMessage(1,"set variable: %s = %s\n", name, BZDB.get(name).c_str());
    }
    else if (strcmp(argv[i], "-setforced") == 0) {
      const char *name, *value;
      checkArgc(2, i, argc, argv[i]);
      name = argv[i];
      i++;
      value = argv[i];
      BZDB.set(name, value);
      logDebugMessage(1,"set variable: %s = %s\n", name, BZDB.get(name).c_str());
    }
    else if (strcmp(argv[i], "-sl") == 0) {
      // shot limits
      checkArgc(2, i, argc, argv[i]);
      i++; // move past the flag descriptor for now (we'll store it in a bit)
      int x = 1;
      if (isdigit(argv[i][0])) {
	x = atoi(argv[i]);
	if (x < 1) {
	  std::cerr << "can only limit to 1 or more shots, changing to 1" << std::endl;
	  x = 1;
	}
      } else {
	std::cerr << "ERROR: invalid shot limit [" << argv[i] << "]" << std::endl;
	usage(argv[0]);
      }
      storedFlagLimits[argv[i-1]] = x;
    }
    else if (strcmp(argv[i], "-spamtime") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.msgTimer = atoi(argv[i]);
      logDebugMessage(1,"using spam time of %d seconds\n", options.msgTimer);
    }
    else if (strcmp(argv[i], "-spamwarn") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.spamWarnMax = atoi(argv[i]);
      logDebugMessage(1,"using spam warn threshold of %d\n", options.spamWarnMax);
    }
    else if (strcmp(argv[i], "-speedtol") == 0) {
      checkArgc(1, i, argc, argv[i]);
      speedTolerance = (float) atof(argv[i]);
      logDebugMessage(1,"using speed autokick tolerance of \"%f\"\n", speedTolerance);
    }
    else if (strcmp(argv[i], "-srvmsg") == 0) {
      checkArgc(1, i, argc, argv[i]);
      if ((options.servermsg != "") || (strlen (argv[i]) == 0)) {
	options.servermsg += "\\n";
      }
      options.servermsg += argv[i];
    }
    else if (strcmp(argv[i], "-st") == 0) {
      // set shake timeout
      checkArgc(1, i, argc, argv[i]);
      float timeout = (float)atof(argv[i]);
      if (timeout < 0.1f) {
	options.shakeTimeout = 1;
	std::cerr << "using minimum shake timeout of " << 0.1f * (float)options.shakeTimeout << std::endl;
      } else if (timeout > 300.0f) {
	options.shakeTimeout = 3000;
	std::cerr << "using maximum shake timeout of " << 0.1f * (float)options.shakeTimeout << std::endl;
      } else {
	options.shakeTimeout = uint16_t(timeout * 10.0f + 0.5f);
      }
      options.gameOptions |= int(ShakableGameStyle);
    }
    else if (strcmp(argv[i], "-sw") == 0) {
      // set shake win count
      checkArgc(1, i, argc, argv[i]);
      int count = atoi(argv[i]);
      if (count < 1) {
	options.shakeWins = 1;
	std::cerr << "using minimum shake win count of " << options.shakeWins << std::endl;
      } else if (count > 20) {
	options.shakeWins = 20;
	std::cerr << "using maximum ttl of " << options.shakeWins << std::endl;
      } else {
	options.shakeWins = uint16_t(count);
      }
      options.gameOptions |= int(ShakableGameStyle);
    }
    else if (strcmp(argv[i], "-synctime") == 0) {
      // client clocks should be synchronized to server clock
      BZDB.set(StateDatabase::BZDB_SYNCTIME, "1.0"); // any positive number
    }
    else if (strcmp(argv[i], "-synclocation") == 0) {
      // client coordinates should be set to server coordinates
      BZDB.set(StateDatabase::BZDB_SYNCLOCATION, "true");
    }
    else if (strcmp(argv[i], "-t") == 0) {
      // allow teleporters
      options.useTeleporters = true;
      if (options.worldFile != "")
	std::cerr << "-t is meaningless when using a custom world, ignoring" << std::endl;
    }
    else if (strcmp(argv[i], "-offa") == 0) {
      // capture the flag style
      if (options.gameType == RabbitChase || options.gameType == ClassicCTF) {
	std::cerr << "Open (Teamless) Free-for-all incompatible with other modes" << std::endl;
	std::cerr << "Open Free-for-all assumed" << std::endl;
      }
      if ((options.gameOptions & int(NoTeamKillsGameStyle)) != 0) {
	std::cerr << "offa check WARNING: -noTeamKills is incompatible with -offa, ignoring" << std::endl;
	options.gameOptions ^= int(NoTeamKillsGameStyle);
      }
      options.gameType = OpenFFA;
    }
    else if (strcmp(argv[i], "-tftimeout") == 0) {
      // use team flag timeout
      checkArgc(1, i, argc, argv[i]);
      options.teamFlagTimeout = atoi(argv[i]);
      if (options.teamFlagTimeout < 0)
	options.teamFlagTimeout = 0;
      logDebugMessage(1,"using team flag timeout of %d seconds\n", options.teamFlagTimeout);
    }
    else if (strcmp(argv[i], "-time") == 0) {
      checkArgc(1, i, argc, argv[i]);
      const std::string timeStr = argv[i];
      // If there is no colon in the time, consider it to be the time limit in seconds
      if (timeStr.find(':') == std::string::npos) {
	options.timeLimit = (float)atof(timeStr.c_str());
      }
      // Otherwise treat it as the end time
      else {
	std::vector<std::string> endTime = TextUtils::tokenize(timeStr, std::string(":"));
	{
	  unsigned int sizer = endTime.size();
	  if (sizer > 3) {
	    std::cerr << "ERROR: too many arguments to -time" << std::endl;
	    usage(argv[0]);
	  }
	  while (sizer != 3) {
	    endTime.push_back("00");
	    ++sizer;
	  }

	  // Force the countdown to start right away
	  gameOver = false;
	  gameStartTime = TimeKeeper::getCurrent();
	  countdownActive = true;
	}
	time_t tnow = time(0);
	struct tm *now = localtime(&tnow);
	unsigned int hour = now->tm_hour, min = now->tm_min, sec = now->tm_sec,
	  cmdHour = atoi(endTime[0].c_str()),
	  cmdMin = atoi(endTime[1].c_str()),
	  cmdSec = atoi(endTime[2].c_str());
	unsigned long secsToday = (hour * 3600) + (min * 60) + sec,
	  secsTill = (cmdHour * 3600) + (cmdMin * 60) + cmdSec;
	if (secsToday > secsTill) { //if the requested time has already past
	  options.timeLimit = (float)((86400 - secsToday) + secsTill); //secs left today + till req. time
	} else {
	  options.timeLimit = (float)(secsTill - secsToday);
	}
      }
      if (options.timeLimit <= 0.0f) {
	// league matches are 30 min
	options.timeLimit = 1800.0f;
      }
      logDebugMessage(1,"using time limit of %d seconds\n", (int)options.timeLimit);
    }
    else if (strcmp(argv[i], "-timemanual") == 0) {
      options.timeManualStart = true;
    }
    else if (strcmp(argv[i], "-tk") == 0) {
      // team killer does not die
      options.teamKillerDies = false;
    }
    else if (strcmp(argv[i], "-tkkr") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.teamKillerKickRatio = atoi(argv[i]);
      if (options.teamKillerKickRatio < 0) {
	options.teamKillerKickRatio = 0;
	std::cerr << "disabling team killer kick ratio";
      }
    }
    else if (strcmp(argv[i], "-ts") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      // timestamp output
      options.timestampLog = true;
      // if there is an argument following, see if it is 'micros'
      if (i+1 != argc) {
	if (strcasecmp(argv[i+1], "micros") == 0) {
	  options.timestampMicros = true;
	  i++;
	}
      }
    }
#ifdef HAVE_MINIUPNPC_MINIUPNPC_H
    else if (strcmp(argv[i], "-UPnP") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      // timestamp output
      options.UPnP = true;
    }
#endif
    else if (strcmp(argv[i], "-utc") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      // timestamp output
      options.timestampLog = true;
      options.timestampUTC = true;
    }
    else if (strcmp(argv[i], "-userdb") == 0) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      userDatabaseFile = argv[i];
      logDebugMessage(1,"using userDB file \"%s\"\n", argv[i]);
    }
    else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "-version") == 0) {
      printVersion();
      exit(0);
    }
    else if (strcmp(argv[i], "-vars") == 0) {
      checkArgc(1, i, argc, argv[i]);
      options.bzdbVars = argv[i];
    }
    else if ((strcmp(argv[i], "-w") == 0) ||
	       (strcmp(argv[i], "-world") == 0)) {
      checkFromWorldFile(argv[i], fromWorldFile);
      checkArgc(1, i, argc, argv[i]);
      options.worldFile = argv[i];
      int ac;
      char **av = parseWorldOptions(argv[i], ac);
      parse(ac, av, options, true); // true - from a world file

      for (int j = 0; j < ac; j++)
	free(av[j]);
      delete[] av;

      options.numAllowedFlags = 0; // FIXME - Huh, does a reset?

      if (options.useTeleporters)
	std::cerr << "-t is meaningless when using a custom world, ignoring" << std::endl;
    }
    else if (strcmp(argv[i], "-worldsize") == 0) {
      checkArgc(1, i, argc, argv[i]);
      BZDB.set(StateDatabase::BZDB_WORLDSIZE, TextUtils::format("%d",atoi(argv[i])*2));
      logDebugMessage(1,"using world size of \"%f\"\n", (float)BZDBCache::worldSize);
    }
    else if (strcmp(argv[i], "-ws") == 0) {
      checkArgc(1, i, argc, argv[i]);
      int sides = atoi(argv[i]);
      if (sides > 2)
	options.wallSides = sides;
    }
    else {
      std::cerr << "bad argument \"" << argv[i] << "\"" << std::endl;
      usage(argv[0]);
    }
  }

  // get player counts.  done after other arguments because we need
  // to ignore counts for rogues if rogues aren't allowed.
  if ((playerCountArg > 0) && !parsePlayerCount(argv[playerCountArg], options)) {
    usage(argv[0]);
  }
  if ((playerCountArg2 > 0)
      && !parsePlayerCount(argv[playerCountArg2], options)) {
    usage(argv[0]);
  }

  return;
}


static int getZoneTeamFlagCount(FlagType* flagType, EntryZones& entryZones,
				 const std::set<FlagType*>& forbidden)
{
  if (forbidden.find(flagType) != forbidden.end()) {
    return 0;
  }
  // tally zone team flags
  int count = 0;
  const ZoneList& zl = entryZones.getZoneList();
  for (int z = 0; z < (int)zl.size(); z++) {
    const ZoneFlagMap& zfm = zl[z].getZoneFlagMap();
    ZoneFlagMap::const_iterator zfmIt;
    for (zfmIt = zfm.begin(); zfmIt != zfm.end(); ++zfmIt) {
      if (zfmIt->first == flagType) {
	count += zfmIt->second;
      }
    }
  }
  return count;
}


static int addZoneTeamFlags(int startIndex,
			    FlagType* flagType, EntryZones& entryZones,
			    const std::set<FlagType*>& forbidden)
{
  if (forbidden.find(flagType) != forbidden.end()) {
    return startIndex;
  }
  // add zone team flags
  const ZoneList& zl = entryZones.getZoneList();
  for (int z = 0; z < (int)zl.size(); z++) {
    const ZoneFlagMap& zfm = zl[z].getZoneFlagMap();
    ZoneFlagMap::const_iterator zfmIt;
    for (zfmIt = zfm.begin(); zfmIt != zfm.end(); ++zfmIt) {
      if (zfmIt->first == flagType) {
	const int count = zfmIt->second;
	for (int c = 0; c < count; c++) {
	  entryZones.addZoneFlag(z, startIndex);
	  FlagInfo::get(startIndex++)->setRequiredFlag(flagType);
	}
      }
    }
  }
  return startIndex;
}


void finalizeParsing(int UNUSED(argc), char **argv,
		     CmdLineOptions &options, EntryZones& entryZones)
{
  if (options.flagsOnBuildings && !(options.gameOptions & JumpingGameStyle)) {
    std::cerr << "flags on boxes requires jumping" << std::endl;
    usage(argv[0]);
  }

  if (options.gameType == RabbitChase) {
    for (int j = RedTeam; j <= PurpleTeam; j++) {
      if (options.maxTeam[j] > 0
	  && options.maxTeam[RogueTeam] != maxRealPlayers)
	std::cout << "only rogues are allowed in Rabbit Chase; zeroing out "
		  << Team::getName((TeamColor) j) << std::endl;
      options.maxTeam[j] = 0;
    }
    options.maxTeam[RabbitTeam] = 1;
    options.maxTeam[HunterTeam] = options.maxTeam[RogueTeam];
  } else {
    options.maxTeam[RabbitTeam] = 0;
    options.maxTeam[HunterTeam] = 0;
  }

  // disallowed flags
  std::vector<std::string>::iterator vsitr;
  for (vsitr = storedFlagDisallows.begin(); vsitr != storedFlagDisallows.end(); ++vsitr) {
    if (strcmp(vsitr->c_str(), "bad") == 0) {
      FlagSet badFlags = Flag::getBadFlags();
      for (FlagSet::iterator it = badFlags.begin(); it != badFlags.end(); ++it)
	options.flagDisallowed[*it] = true;
    } else if (strcmp(vsitr->c_str(), "good") == 0) {
      FlagSet goodFlags = Flag::getGoodFlags();
      for (FlagSet::iterator it = goodFlags.begin(); it != goodFlags.end(); ++it)
	options.flagDisallowed[*it] = true;
    } else {
      FlagType* fDesc = Flag::getDescFromAbbreviation(vsitr->c_str());
      if (fDesc == Flags::Null) {
	std::cerr << "ERROR: invalid flag [" << (*vsitr) << "]" << std::endl;
	usage(argv[0]);
      }
      options.flagDisallowed[fDesc] = true;
    }
  }
  storedFlagDisallows.clear();

  // explicitly added flags
  for (vsitr = storedFlagCounts.begin(); vsitr != storedFlagCounts.end(); ++vsitr) {
    size_t rptBgn = vsitr->find_first_of('{');
    int rptCnt = 1;
    if (rptBgn > 0) {
      rptCnt = atoi(vsitr->substr(rptBgn+1, vsitr->length() - rptBgn).c_str());
      if (rptCnt <= 0)
	rptCnt = 1;
      (*vsitr) = vsitr->substr(0, rptBgn);
    }
    if (strcmp(vsitr->c_str(), "good") == 0) {
      FlagSet goodFlags = Flag::getGoodFlags();
      for (FlagSet::iterator it = goodFlags.begin(); it != goodFlags.end(); ++it)
	options.flagCount[*it] += rptCnt;
    } else if (strcmp(vsitr->c_str(), "bad") == 0) {
      FlagSet badFlags = Flag::getBadFlags();
      for (FlagSet::iterator it = badFlags.begin(); it != badFlags.end(); ++it)
	options.flagCount[*it] += rptCnt;
    } else if (strcmp(vsitr->c_str(), "team") == 0) {
      for (int t = RedTeam; t <= PurpleTeam; t++)
	options.numTeamFlags[t] += rptCnt;
    } else {
      FlagType *fDesc = Flag::getDescFromAbbreviation(vsitr->c_str());
      if (fDesc == Flags::Null) {
	std::cerr << "ERROR: invalid flag [" << (*vsitr) << "]" << std::endl;
	usage(argv[0]);
      } else if (fDesc->flagTeam != NoTeam) {
	options.numTeamFlags[fDesc->flagTeam] += rptCnt;
      } else {
	options.flagCount[fDesc] += rptCnt;
      }
    }
  }
  storedFlagCounts.clear();

  if (!accLimitSet) {
    printf("Note: no acceleration limit has been set.  Players using \"mouse\n"
	   "enhancements\" may cause problems on this server due to very high\n"
	   "acceleration rates which are not handled well by dead reckoning\n"
	   "algorithms.  To eliminate this warning, set the -a switch in your\n"
	   "configuration.  '-a 50 38' is recommended for standard-speed servers.\n"
	   "Higher speed servers will need higher values for -a in order to not\n"
	   "affect gameplay.  '-a 0 0' may be used to shut this message up without\n"
	   "affecting any players, including those using \"mouse enhancements\".\n");
  }


  // do we have any team flags?
  bool hasTeam = false;
  for (int p = RedTeam; p <= PurpleTeam; p++) {
    if (options.maxTeam[p] > 1) {
      hasTeam = true;
      break;
    }
  }

  std::set<FlagType*> forbidden;
  forbidden.insert(Flags::Null);

  // first disallow flags inconsistent with game style
  if (options.gameOptions & JumpingGameStyle) {
    forbidden.insert(Flags::Jumping);
  } else {
    forbidden.insert(Flags::NoJumping);
  }
  if (options.gameOptions & RicochetGameStyle) {
    forbidden.insert(Flags::Ricochet);
  }
  if (OBSTACLEMGR.getTeles().size() == 0) {
    forbidden.insert(Flags::PhantomZone);
  }
  if (!hasTeam) {
    forbidden.insert(Flags::Genocide);
    forbidden.insert(Flags::Colorblindness);
    forbidden.insert(Flags::Masquerade);
  }
  if ((options.gameType == ClassicCTF) == 0) {
    forbidden.insert(Flags::RedTeam);
    forbidden.insert(Flags::GreenTeam);
    forbidden.insert(Flags::BlueTeam);
    forbidden.insert(Flags::PurpleTeam);
  }
  if (options.maxTeam[RedTeam] <= 0) {
    forbidden.insert(Flags::RedTeam);
  }
  if (options.maxTeam[GreenTeam] <= 0) {
    forbidden.insert(Flags::GreenTeam);
  }
  if (options.maxTeam[BlueTeam] <= 0) {
    forbidden.insert(Flags::BlueTeam);
  }
  if (options.maxTeam[PurpleTeam] <= 0) {
    forbidden.insert(Flags::PurpleTeam);
  }

  // void the forbidden flags
  std::set<FlagType*>::const_iterator sit;
  for (sit = forbidden.begin(); sit != forbidden.end(); ++sit) {
    FlagType* ft = *sit;
    options.flagCount[ft] = 0;
    options.flagDisallowed[ft] = true;
  }

  // zone team flag counts
  const int zoneTeamFlagCounts[5] = {
    0, // rogue
    getZoneTeamFlagCount(Flags::RedTeam, entryZones, forbidden),
    getZoneTeamFlagCount(Flags::GreenTeam, entryZones, forbidden),
    getZoneTeamFlagCount(Flags::BlueTeam, entryZones, forbidden),
    getZoneTeamFlagCount(Flags::PurpleTeam, entryZones, forbidden)
  };

  // make sure there is at least one team flag for each active team
  if (options.gameType == ClassicCTF) {
    for (int col = RedTeam; col <= PurpleTeam; col++) {
      if ((options.maxTeam[col] > 0) &&
	  (options.numTeamFlags[col] <= 0) &&
	  (zoneTeamFlagCounts[col] <= 0)) {
	options.numTeamFlags[col] = 1;
      }
    }
  }

  // make table of allowed extra flags
  if (options.numExtraFlags > 0) {
    // now count how many aren't disallowed
    for (FlagTypeMap::iterator it = FlagType::getFlagMap().begin();
	 it != FlagType::getFlagMap().end(); ++it) {
      if (!options.flagDisallowed[it->second]) {
	options.numAllowedFlags++;
      }
    }
    // if none allowed then no extra flags either
    if (options.numAllowedFlags == 0) {
      options.numExtraFlags = 0;
    } else {
      // types of extra flags allowed
      std::vector<FlagType*> allowedFlags;
      allowedFlags.clear();
      for (FlagTypeMap::iterator it = FlagType::getFlagMap().begin();
	   it != FlagType::getFlagMap().end(); ++it) {
	FlagType *fDesc = it->second;
	if ((fDesc == Flags::Null) || (fDesc->flagTeam != ::NoTeam))
	  continue;
	if (!options.flagDisallowed[it->second])
	  allowedFlags.push_back(it->second);
      }
      // Loading allowedFlags vector
      FlagInfo::setAllowed(allowedFlags);
    }
  }

  const ZoneList& zl = entryZones.getZoneList();

  // allocate space for extra flags
  numFlags = options.numExtraFlags;
  // allocate space for team flags
  if (options.gameType & ClassicCTF) {
    for (int col = RedTeam; col <= PurpleTeam; col++) {
      if (options.maxTeam[col] > 0) {
	numFlags += options.numTeamFlags[col];
      }
    }
  }
  // allocate space for normal flags
  for (FlagTypeMap::iterator it = FlagType::getFlagMap().begin();
       it != FlagType::getFlagMap().end(); ++it) {
    numFlags += options.flagCount[it->second];
  }
  // allocate space for zone flags (including teams flags)
  for (int z = 0; z < (int)zl.size(); z++) {
    const CustomZone& zone = zl[z];
    const ZoneFlagMap& zfm = zone.getZoneFlagMap();
    ZoneFlagMap::const_iterator zfmIt;
    for (zfmIt = zfm.begin(); zfmIt != zfm.end(); ++zfmIt) {
      if (forbidden.find(zfmIt->first) == forbidden.end()) {
	numFlags += zfmIt->second;
      }
    }
  }

  FlagInfo::setSize(numFlags);

  // add team flags (ordered)
  int f = 0;
  if (options.gameType == ClassicCTF) {
    if (options.maxTeam[RedTeam] > 0) {
      f = addZoneTeamFlags(f, Flags::RedTeam, entryZones, forbidden);
      for (int n = 0; n < options.numTeamFlags[RedTeam]; n++) {
	FlagInfo::get(f++)->setRequiredFlag(Flags::RedTeam);
      }
    }
    if (options.maxTeam[GreenTeam] > 0) {
      f = addZoneTeamFlags(f, Flags::GreenTeam, entryZones, forbidden);
      for (int n = 0; n < options.numTeamFlags[GreenTeam]; n++) {
	FlagInfo::get(f++)->setRequiredFlag(Flags::GreenTeam);
      }
    }
    if (options.maxTeam[BlueTeam] > 0) {
      f = addZoneTeamFlags(f, Flags::BlueTeam, entryZones, forbidden);
      for (int n = 0; n < options.numTeamFlags[BlueTeam]; n++) {
	FlagInfo::get(f++)->setRequiredFlag(Flags::BlueTeam);
      }
    }
    if (options.maxTeam[PurpleTeam] > 0) {
      f = addZoneTeamFlags(f, Flags::PurpleTeam, entryZones, forbidden);
      for (int n = 0; n < options.numTeamFlags[PurpleTeam]; n++) {
	FlagInfo::get(f++)->setRequiredFlag(Flags::PurpleTeam);
      }
    }
  }

  // super flags?
  if (f < numFlags) {
    options.gameOptions |= int(SuperFlagGameStyle);
  }

  // shot limits
  std::map<std::string, int>::iterator msiitr;
  for (msiitr = storedFlagLimits.begin(); msiitr != storedFlagLimits.end(); ++msiitr) {
    FlagType *fDesc = Flag::getDescFromAbbreviation(msiitr->first.c_str());
    if (fDesc == Flags::Null) {
      std::cerr << "ERROR: invalid flag [" << msiitr->first << "]" << std::endl;
      usage(argv[0]);
    } else {
      // this parameter has already been validated
      options.flagLimit[fDesc] = msiitr->second;
    }
  }
  storedFlagLimits.clear();

  // add normal flags
  for (FlagTypeMap::iterator it2 = FlagType::getFlagMap().begin();
       it2 != FlagType::getFlagMap().end(); ++it2) {
    FlagType *fDesc = it2->second;

    if ((fDesc != Flags::Null) && (fDesc->flagTeam == NoTeam)) {
      for (int j = 0; j < options.flagCount[fDesc]; j++) {
	FlagInfo::get(f++)->setRequiredFlag(fDesc);
      }
    }
  }
  // add zone flags
  for (int z = 0; z < (int)zl.size(); z++) {
    const CustomZone& zone = zl[z];
    const ZoneFlagMap& zfm = zone.getZoneFlagMap();
    ZoneFlagMap::const_iterator zfmIt;
    for (zfmIt = zfm.begin(); zfmIt != zfm.end(); ++zfmIt) {
      FlagType* ft = zfmIt->first;
      if ((ft->flagTeam == ::NoTeam) && // no team flags here
	  (forbidden.find(ft) == forbidden.end())) {
	const int count = zfmIt->second;
	for (int c = 0; c < count; c++) {
	  entryZones.addZoneFlag(z, f);
	  FlagInfo::get(f++)->setRequiredFlag(ft);
	}
      }
    }
  }
  // add extra flags
  for (; f < numFlags; f++) {
    FlagInfo::get(f)->required = allFlagsOut;
  }

  // sum the sources of team flags
  if (options.gameType & ClassicCTF) {
    for (int col = RedTeam; col <= PurpleTeam; col++) {
      options.numTeamFlags[col] += zoneTeamFlagCounts[col];
    }
  }


  // debugging
  logDebugMessage(1,"type: %d\n", options.gameType);
  if (options.gameType == ClassicCTF)
    logDebugMessage(1,"  capture the flag\n");
  if (options.gameType == RabbitChase)
    logDebugMessage(1,"  rabbit chase\n");
  if (options.gameType == OpenFFA)
    logDebugMessage(1,"  open free-for-all\n");
  if (options.gameType == TeamFFA)
    logDebugMessage(1,"  teamed free-for-all\n");

  logDebugMessage(1,"options: %X\n", options.gameOptions);
  if (options.gameOptions & int(SuperFlagGameStyle))
    logDebugMessage(1,"  super flags allowed\n");
  if (options.gameOptions & int(JumpingGameStyle))
    logDebugMessage(1,"  jumping allowed\n");
  if (options.gameOptions & int(RicochetGameStyle))
    logDebugMessage(1,"  all shots ricochet\n");
  if (options.gameOptions & int(ShakableGameStyle))
    logDebugMessage(1,"  shakable bad flags: timeout=%f, wins=%i\n",
    0.1f * float(options.shakeTimeout), options.shakeWins);
  if (options.gameOptions & int(AntidoteGameStyle))
    logDebugMessage(1,"  antidote flags\n");

  return;
}


// simple syntax check of comma-seperated list of group names (returns true if error)
bool checkCommaList (const char *list, int maxlen) {
  int x = strlen (list);
  unsigned char c;
  if (x > maxlen)
    return true;
  if (*list==',' || list[x-1]==',')
    return true;
  while ((c=*list++) != '\0')
    if (c<' ' || c>'z' ||  c=='\'' || c=='"')
      return true;
  return false;
}



// Local Variables: ***
// mode: C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
