? autohunt_bz2.0.9_v1.0.diff
? autohunt_bz2.0.9_v2.0.diff
? autohunt_bz2.0.9_v3.0.diff
? autohunt_bz2.0.9_v4.0.diff
? diff
? rr.diff
cvs diff: Diffing .
Index: AutoHunt.cxx
===================================================================
RCS file: AutoHunt.cxx
diff -N AutoHunt.cxx
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ AutoHunt.cxx	25 May 2006 10:21:39 -0000
@@ -0,0 +1,987 @@
+/* bzflag
+ * Copyright (c) 1993 - 2006 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.
+ */
+
+// Sets autohunt status for players based on the string
+// contained in the BZDB "autohunt" variable. The format
+// is as follows:
+//
+//   set autohunt MIN<500#4:MAX<50#8:ST?GM:CL<300#9
+//
+//   TOKEN1[<float][#level][:TOKEN2 [:TOKEN3...]]
+//
+//   Parsing Control:
+//     *var:    replace with the string in BZDB variable 'var'
+//   Flag Type Control:
+//     GM:      always hunt a player with the Guided Missile flag
+//     CL?L:    if the local player has Cloak, hunt players with Laser 
+//   Specific Triggers:
+//     TKS:     if a friend has exceeded the teamkill ratio
+//     RATIO:   if an enemy player has more kills then deaths on you, hunt them
+//     LEADER:  hunt the enemy with the best score
+//   Range Control:
+//     RESET:   reset range settings
+//     SET:     set all levels to value
+//     MIN:     set range minimum level and its value
+//     MAX:     set range maximum level and its value
+//   Display Control:
+//     COUNT:   chevron count
+//     SPACE:   chevron space
+//     DELTA:   chevron expansion rate
+//     IALPHA:  chevron inner translucency
+//     OALPHA:  chevron outer translucency
+//     PERIOD:  chevron blink period
+//     IBLINK:  tank square blink threshold
+//     OBLINK:  chevron marker blink threshold
+
+// NOTES:
+// - try using textures instead of chevron patterns
+// - assign AutoHuntMarker objects instead of 'int' levels
+//   (float importance values vs. proximity)
+
+#include "common.h"
+
+// interface header
+#include "AutoHunt.h"
+
+// system headers
+#include <map>
+#include <vector>
+#include <string>
+#include <stdlib.h>
+
+// common headers
+#include "global.h"
+#include "Flag.h"
+#include "AnsiCodes.h"
+#include "TextUtils.h"
+#include "StateDatabase.h"
+
+// local headers
+#include "playing.h"
+#include "World.h"
+#include "Player.h"
+#include "LocalPlayer.h"
+#include "ControlPanel.h"
+
+
+// typedefs / local classes
+class HuntInfo {
+  public:
+    HuntInfo() {}
+    bool active() const { return (coords.size() > 0); }
+    std::map<int, float> coords;  // level, proximity
+};
+typedef std::map<const FlagType*, HuntInfo> FlagHuntInfoMap;
+typedef std::map<const FlagType*, FlagHuntInfoMap> FlagPairMap;
+class HuntRange {
+  public:
+    HuntRange() { reset(); }
+    void reset()
+    {
+      minLevel = 1;
+      maxLevel = 0;
+      minProximity = MAXFLOAT;
+      maxProximity = MAXFLOAT;
+    }
+  public:
+    int minLevel;
+    int maxLevel;
+    float minProximity;
+    float maxProximity;
+};
+
+
+// static data
+static const int LevelCount = 10; // level 0 is a dud
+
+static FlagPairMap FlagPairs;
+static HuntInfo TkHuntInfo;
+static HuntInfo RatioHuntInfo;
+static HuntInfo LeaderHuntInfo; 
+
+static int   ChevronCounts[LevelCount];
+static float ChevronSpaces[LevelCount];
+static float ChevronDeltas[LevelCount];
+static float ChevronInnerAlphas[LevelCount];
+static float ChevronOuterAlphas[LevelCount];
+static float BlinkPeriods[LevelCount];
+static float InnerBlinkThresholds[LevelCount];
+static float OuterBlinkThresholds[LevelCount];
+
+const struct {
+  int chevronCount;
+  float chevronSpace;
+  float chevronDelta;
+  float chevronInnerAlpha;
+  float chevronOuterAlpha;
+  float blinkPeriod;
+  float innerBlinkThreshold;
+  float outerBlinkThreshold;
+} DisplayStyles[LevelCount] = {
+  { 0,  3.0f, 0.0f,  0.9f, 0.4f,  1.00f,  0.0f, 0.0f }, // 0
+  { 1,  1.0f, 0.0f,  0.9f, 0.4f,  0.75f,  0.0f, 0.0f }, // 1
+  { 2,  1.0f, 0.0f,  0.9f, 0.4f,  0.70f,  0.0f, 0.0f }, // 2
+  { 3,  1.0f, 0.0f,  0.9f, 0.4f,  0.65f,  0.0f, 0.0f }, // 3
+  { 2,  3.0f, 2.0f,  0.9f, 0.4f,  0.60f,  0.0f, 0.0f }, // 4
+  { 2,  3.0f, 1.0f,  0.9f, 0.4f,  0.55f,  0.0f, 0.0f }, // 5
+  { 2,  3.0f, 0.0f,  0.9f, 0.4f,  0.50f,  0.0f, 0.0f }, // 6
+  { 3,  3.0f, 2.0f,  0.9f, 0.4f,  0.45f,  0.0f, 0.0f }, // 7
+  { 3,  3.0f, 1.0f,  0.9f, 0.4f,  0.40f,  0.0f, 0.0f }, // 8
+  { 3,  3.0f, 0.0f,  0.9f, 0.4f,  0.35f,  0.0f, 0.0f }  // 9
+};
+
+
+// static functions
+static void resetArrays();
+static std::string getExpandedString(const std::string& varname);
+static void parse(const std::string& msg);
+static void copyToIntArray(int array[LevelCount], const HuntInfo& hi);
+static void copyToFloatArray(float array[LevelCount], const HuntInfo& hi);
+static void huntPlayers();
+static void clearPlayers();
+static void setupHuntInfoRange(HuntInfo&, const HuntRange&);
+static int getHuntInfoLevel(const HuntInfo&, float dist);
+static float distBetweenPlayers(const Player*, const Player*);
+static bool isEnemy(const Player*);
+static bool isBeatingMe(const Player*);
+static bool isTeamKiller(const Player* p, float ratio);
+static Player* findEnemyLeader();
+static void printConfig();
+
+
+/******************************************************************************/
+
+static void resetArrays()
+{
+  for (int i = 0; i < LevelCount; i++) {
+    ChevronCounts[i] = DisplayStyles[i].chevronCount;
+    ChevronSpaces[i] = DisplayStyles[i].chevronSpace;
+    ChevronDeltas[i] = DisplayStyles[i].chevronDelta;
+    ChevronInnerAlphas[i] = DisplayStyles[i].chevronInnerAlpha;
+    ChevronOuterAlphas[i] = DisplayStyles[i].chevronOuterAlpha;
+    BlinkPeriods[i] = DisplayStyles[i].blinkPeriod;
+    InnerBlinkThresholds[i] = DisplayStyles[i].innerBlinkThreshold;
+    OuterBlinkThresholds[i] = DisplayStyles[i].outerBlinkThreshold;
+  }
+
+  return;
+}
+
+
+/******************************************************************************/
+
+static void parse(const std::string& cmd)
+{
+  std::vector<std::string> errors;
+  HuntRange range;
+  HuntInfo ChevronCountHuntInfo;
+  HuntInfo ChevronSpaceHuntInfo;
+  HuntInfo ChevronDeltaHuntInfo;
+  HuntInfo ChevronInnerAlphaHuntInfo;
+  HuntInfo ChevronOuterAlphaHuntInfo;
+  HuntInfo BlinkPeriodHuntInfo;
+  HuntInfo InnerBlinkHuntInfo;
+  HuntInfo OuterBlinkHuntInfo;
+
+  resetArrays();
+  FlagPairs.clear();
+  TkHuntInfo.coords.clear();
+  RatioHuntInfo.coords.clear();
+  LeaderHuntInfo.coords.clear();
+
+  DEBUG3("AutoHunt: %s\n", cmd.c_str());
+  std::vector<std::string> tokens = TextUtils::tokenize(cmd, ":");
+
+  for (int i = 0; i < (int)tokens.size(); i++) {
+    std::string token = TextUtils::toupper(tokens[i]);
+    DEBUG3("  Token: %-10s", token.c_str());
+
+    bool useRange = true;
+    
+    // parse the level
+    int level = 1;
+    const int len = (int)token.size();
+    if (len >= 2) {
+      if (token[len - 2] == '#') {
+        int number = token[len - 1] - '0';
+        if ((number >= 0) && (number <= 9)) {
+          level = number;
+          token.resize(len - 2);
+          useRange = false;
+        } else {
+          errors.push_back(tokens[i]);
+          DEBUG3("Error parsing level\n");
+          continue;
+        }
+      }
+    }
+    if (level <= 0) {
+      continue;
+    }
+
+    // parse the proximity
+    float proximity = MAXFLOAT;
+    std::string::size_type lessThen = token.find_first_of('<');
+    if (lessThen != std::string::npos) {
+      char* end;
+      const char* start = token.c_str() + lessThen + 1;
+      proximity = (float)strtod(start, &end);
+      if (end != start) {
+        token.resize(lessThen);
+        useRange = false;
+      } else {
+        errors.push_back(tokens[i]);
+        DEBUG3("Error parsing proximity\n");
+        continue;
+      }
+    }
+
+    // Display Properties    
+    if (token == "COUNT") {
+      if (useRange) {
+        setupHuntInfoRange(ChevronCountHuntInfo, range);
+        DEBUG3("(count/range)\n");
+      } else {
+        ChevronCountHuntInfo.coords[level] = proximity;
+        DEBUG3("(count/%i/%f)\n", level, proximity);
+      }
+    }
+    else if (token == "SPACE") {
+      if (useRange) {
+        setupHuntInfoRange(ChevronSpaceHuntInfo, range);
+        DEBUG3("(space/range)\n");
+      } else {
+        ChevronSpaceHuntInfo.coords[level] = proximity;
+        DEBUG3("(space/%i/%f)\n", level, proximity);
+      }
+    }
+    else if (token == "DELTA") {
+      if (useRange) {
+        setupHuntInfoRange(ChevronDeltaHuntInfo, range);
+        DEBUG3("(delta/range)\n");
+      } else {
+        ChevronDeltaHuntInfo.coords[level] = proximity;
+        DEBUG3("(delta/%i/%f)\n", level, proximity);
+      }
+    }
+    else if (token == "IALPHA") {
+      if (useRange) {
+        setupHuntInfoRange(ChevronInnerAlphaHuntInfo, range);
+        DEBUG3("(ialpha/range)\n");
+      } else {
+        ChevronInnerAlphaHuntInfo.coords[level] = proximity;
+        DEBUG3("(ialpha/%i/%f)\n", level, proximity);
+      }
+    }
+    else if (token == "OALPHA") {
+      if (useRange) {
+        setupHuntInfoRange(ChevronOuterAlphaHuntInfo, range);
+        DEBUG3("(oalpha/range)\n");
+      } else {
+        ChevronOuterAlphaHuntInfo.coords[level] = proximity;
+        DEBUG3("(oalpha/%i/%f)\n", level, proximity);
+      }
+    }
+    else if (token == "PERIOD") {
+      if (useRange) {
+        setupHuntInfoRange(BlinkPeriodHuntInfo, range);
+        DEBUG3("(period/range)\n");
+      } else {
+        BlinkPeriodHuntInfo.coords[level] = proximity;
+        DEBUG3("(period/%i/%f)\n", level, proximity);
+      }
+    }
+    else if (token == "IBLINK") {
+      if (useRange) {
+        setupHuntInfoRange(InnerBlinkHuntInfo, range);
+        DEBUG3("(iblink/range)\n");
+      } else {
+        InnerBlinkHuntInfo.coords[level] = proximity;
+        DEBUG3("(iblink/%i/%f)\n", level, proximity);
+      }
+    }
+    else if (token == "OBLINK") {
+      if (useRange) {
+        setupHuntInfoRange(OuterBlinkHuntInfo, range);
+        DEBUG3("(oblink/range)\n");
+      } else {
+        OuterBlinkHuntInfo.coords[level] = proximity;
+        DEBUG3("(oblink/%i/%f)\n", level, proximity);
+      }
+    }
+    // Range Control
+    else if (token == "SET") {
+      range.minLevel = 0;
+      range.maxLevel = LevelCount - 1;
+      range.minProximity = proximity;
+      range.maxProximity = proximity;
+      DEBUG3("(range set/%f)\n", proximity);
+    }
+    else if (token == "RESET") {
+      range.reset();
+      DEBUG3("(range reset)\n");
+    }
+    else if (token == "MIN") {
+      range.minLevel = level;
+      range.minProximity = proximity;
+      DEBUG3("(range min/%i/%f)\n", level, proximity);
+    }
+    else if (token == "MAX") {
+      range.maxLevel = level;
+      range.maxProximity = proximity;
+      DEBUG3("(range max/%i/%f)\n", level, proximity);
+    }
+    // Specific trigger types
+    else if (token == "TKS") {
+      if (useRange) {
+        setupHuntInfoRange(TkHuntInfo, range);
+        DEBUG3("(TKs/range)\n");
+      } else {
+        TkHuntInfo.coords[level] = proximity;
+        DEBUG3("(TKs/%i/%f)\n", level, proximity);
+      }
+    }
+    else if (token == "RATIO") {
+      if (useRange) {
+        setupHuntInfoRange(RatioHuntInfo, range);
+        DEBUG3("(ratio/range)\n");
+      } else {
+        RatioHuntInfo.coords[level] = proximity;
+        DEBUG3("(ratio/%i/%f)\n", level, proximity);
+      }
+    }
+    else if (token == "LEADER") {
+      if (useRange) {
+        setupHuntInfoRange(LeaderHuntInfo, range);
+        DEBUG3("(leader/range)\n");
+      } else {
+        LeaderHuntInfo.coords[level] = proximity;
+        DEBUG3("(leader/%i/%f)\n", level, proximity);
+      }
+    }
+    // Flag trigger types
+    else {
+      std::vector<std::string> flags = TextUtils::tokenize(token, "?");
+      if (flags.size() == 1) {
+        // SoloFlags
+        FlagType* ft = Flag::getDescFromAbbreviation(flags[0].c_str());
+        if (ft != Flags::Null) {
+          FlagHuntInfoMap& fhi = FlagPairs[Flags::Null]; // reserved for solos
+          HuntInfo& hi = fhi[ft];
+          if (useRange) {
+            setupHuntInfoRange(hi, range);
+            DEBUG3("(soloFlag/range)\n");
+          } else {
+            hi.coords[level] = proximity;
+            DEBUG3("(soloFlag/%i/%f)\n", level, proximity);
+          }
+        } else {
+          errors.push_back(tokens[i]);
+          DEBUG3("Error parsing solo flag type\n");
+        }
+      }
+      else if (flags.size() == 2) {
+        // FlagPairs
+        FlagType* ft0 = Flag::getDescFromAbbreviation(flags[0].c_str());
+        FlagType* ft1 = Flag::getDescFromAbbreviation(flags[1].c_str());
+        if ((ft0 != Flags::Null) && (ft1 != Flags::Null)) {
+          FlagHuntInfoMap& fhi = FlagPairs[ft0];
+          HuntInfo& hi = fhi[ft1];
+          if (useRange) {
+            setupHuntInfoRange(hi, range);
+            DEBUG3("(flagPair/range)\n");
+          } else {
+            hi.coords[level] = proximity;
+            DEBUG3("(flagPair/%i/%f)\n", level, proximity);
+          }
+        } else {
+          errors.push_back(tokens[i]);
+          DEBUG3("Error parsing flag pair type\n");
+        }
+      }
+      else {
+        errors.push_back(tokens[i]);
+        DEBUG3("Error parsing token type\n");
+      }
+    }
+  }
+
+  if (errors.size() > 0) {
+    std::string parseError = "AutoHunt: ";
+    parseError += cmd;
+    controlPanel->addMessage(parseError, -1);
+    for (int i = 0; i < (int)errors.size(); i++) {
+      std::string error = "  Bad Token: ";
+      error += errors[i];
+      controlPanel->addMessage(error, -1);
+    }
+  }
+
+  // copy the hunt info maps into the arrays  
+  copyToIntArray(ChevronCounts, ChevronCountHuntInfo);
+  copyToFloatArray(ChevronSpaces, ChevronSpaceHuntInfo);
+  copyToFloatArray(ChevronDeltas, ChevronDeltaHuntInfo);
+  copyToFloatArray(ChevronInnerAlphas, ChevronInnerAlphaHuntInfo);
+  copyToFloatArray(ChevronOuterAlphas, ChevronOuterAlphaHuntInfo);
+  copyToFloatArray(BlinkPeriods, BlinkPeriodHuntInfo);
+  copyToFloatArray(InnerBlinkThresholds, InnerBlinkHuntInfo);
+  copyToFloatArray(OuterBlinkThresholds, OuterBlinkHuntInfo);
+
+  return;
+}
+
+
+/******************************************************************************/
+
+static void setupHuntInfoRange(HuntInfo& hi, const HuntRange& hr)
+{
+  const int levelCount = (hr.maxLevel - hr.minLevel);
+  if (levelCount <= 0) {
+    if (hr.minLevel > 0) {
+      hi.coords[hr.minLevel] = hr.minProximity;
+    }
+    return;
+  }
+  const float range = (hr.minProximity - hr.maxProximity);
+  const float delta = range / (float)levelCount;
+  int step = 0;
+  for (int i = hr.minLevel; i <= hr.maxLevel; i++) {
+    if (i > 0) {
+      hi.coords[i] = hr.minProximity - ((float)step * delta);
+    }
+    step++;
+  }
+  return;
+}
+
+
+/******************************************************************************/
+
+static void copyToIntArray(int array[LevelCount], const HuntInfo& hi)
+{
+  std::map<int, float>::const_iterator it;
+  for (it = hi.coords.begin(); it != hi.coords.end(); it++) {
+    const int level = it->first;
+    if ((level >= 0) && (level < LevelCount)) {
+      array[level] = (int)it->second;
+    }
+  }
+}
+
+
+/******************************************************************************/
+
+static void copyToFloatArray(float array[LevelCount], const HuntInfo& hi)
+{
+  std::map<int, float>::const_iterator it;
+  for (it = hi.coords.begin(); it != hi.coords.end(); it++) {
+    const int level = it->first;
+    if ((level >= 0) && (level < LevelCount)) {
+      array[level] = it->second;
+    }
+  }
+}
+
+
+/******************************************************************************/
+
+static void printConfig()
+{
+  if (debugLevel < 3) {
+    return;
+  }
+
+  DEBUG3("AutoHunt Config:\n");
+  DEBUG3("---------------:\n");
+  std::map<int, float>::const_iterator cit;
+  FlagPairMap::const_iterator it;
+  for (it = FlagPairs.begin(); it != FlagPairs.end(); it++) {
+    DEBUG3("  %s\n", (it->first == Flags::Null) ? "NULL" : it->first->flagAbbv);
+    const FlagHuntInfoMap& fhim = it->second;
+    FlagHuntInfoMap::const_iterator fhim_it;
+    for (fhim_it = fhim.begin(); fhim_it != fhim.end(); fhim_it++) {
+      DEBUG3("    %s\n", fhim_it->first->flagAbbv);
+      const HuntInfo& hi = fhim_it->second;
+      for (cit = hi.coords.begin(); cit != hi.coords.end(); cit++) {
+        DEBUG3("      level: %i, dist: %f\n", cit->first, cit->second);
+      }
+    }
+  }
+
+  DEBUG3("Tk:\n");
+  const HuntInfo& thi = TkHuntInfo;
+  for (cit = thi.coords.begin(); cit != thi.coords.end(); cit++) {
+    DEBUG3("  level: %i, dist: %f\n", cit->first, cit->second);
+  }
+
+  DEBUG3("Ratio:\n");
+  const HuntInfo& rhi = RatioHuntInfo;
+  for (cit = rhi.coords.begin(); cit != rhi.coords.end(); cit++) {
+    DEBUG3("  level: %i, dist: %f\n", cit->first, cit->second);
+  }
+
+  DEBUG3("Leader:\n");
+  const HuntInfo& lhi = RatioHuntInfo;
+  for (cit = lhi.coords.begin(); cit != lhi.coords.end(); cit++) {
+    DEBUG3("  level: %i, dist: %f\n", cit->first, cit->second);
+  }
+  DEBUG3("Styles:    count space delta ialpha oalpha period iblink oblink\n");
+  for (int i = 1; i < LevelCount; i++) {
+    DEBUG3("  level %i:   %i  %5.2f %5.2f %5.2f %6.2f %6.2f %6.2f %6.2f\n", i,
+           ChevronCounts[i],
+           ChevronSpaces[i],
+           ChevronDeltas[i],
+           ChevronInnerAlphas[i],
+           ChevronOuterAlphas[i],
+           BlinkPeriods[i],
+           InnerBlinkThresholds[i],
+           OuterBlinkThresholds[i]);
+  }
+
+  return;
+}
+
+
+/******************************************************************************/
+
+static Player* findEnemyLeader()
+{
+  World* world = World::getWorld();
+  if (world == NULL) {
+    return NULL;
+  }
+  Player** players = (Player**)world->getPlayers();
+  if (players == NULL) {
+    return NULL;
+  }
+  const int maxPlayers = world->getCurMaxPlayers();
+
+  int maxScore = -123456789;
+  Player* leader = NULL;
+  for (int i = 0; i < maxPlayers; i++) {
+    Player* p = players[i];
+    if ((p != NULL) && isEnemy(p)) {
+      const int pScore = p->getScore();
+      if (pScore > maxScore) {
+        leader = p;
+        maxScore = pScore;
+      }
+    }
+  }
+
+  return leader;
+}
+
+
+/******************************************************************************/
+
+// FIXME: rabbits?
+
+static bool isEnemy(const Player* p)
+{
+  LocalPlayer* myTank = LocalPlayer::getMyTank();
+  if ((myTank == NULL) || (p == NULL)) {
+    return false;
+  }
+
+  const TeamColor myTeam = myTank->getTeam();
+  if (myTeam == ObserverTeam) {
+    return false;
+  }
+
+  const TeamColor playerTeam = p->getTeam();
+  if (playerTeam == ObserverTeam) {
+    return false;
+  }
+
+  if ((myTeam == RogueTeam) || (myTeam != playerTeam)) {
+    return true;
+  }
+
+  return false;
+}
+
+
+/******************************************************************************/
+
+static bool isBeatingMe(const Player* p)
+{
+  if (p == NULL) {
+    return false;
+  }
+  if (p->getLocalLosses() > p->getLocalWins()) {
+    return true;
+  }
+  return false;
+}
+
+
+/******************************************************************************/
+
+static bool isTeamKiller(const Player* p, float tkRatio)
+{
+  if (tkRatio > 0.0) {
+    if (((p->getWins() > 0) && (p->getTKRatio() > tkRatio)) ||
+        ((p->getWins() == 0) && (p->getTeamKills() >= 3))) {
+      return true;
+    }
+  }
+  return false;
+}
+                            
+
+/******************************************************************************/
+                            
+static void clearPlayers()
+{
+  World* world = World::getWorld();
+  if (world == NULL) {
+    return;
+  }
+  Player** players = (Player**)world->getPlayers();
+  if (players == NULL) {
+    return;
+  }
+  const int maxPlayers = world->getCurMaxPlayers();
+
+  // clear the autohunt status
+  for (int i = 0; i < maxPlayers; i++) {
+    Player* p = players[i];
+    if (p != NULL) {
+      p->setAutoHuntLevel(0);
+    }
+  }
+
+  return;
+}
+
+
+/******************************************************************************/
+
+static float distBetweenPlayers(const Player* p0, const Player* p1)
+{
+  const float* pos0 = p0->getPosition();
+  const float* pos1 = p1->getPosition();
+  const float dx = pos0[0] - pos1[0];
+  const float dy = pos0[1] - pos1[1];
+  const float dz = pos0[2] - pos1[2];
+  return sqrtf((dx * dx) + (dy * dy) + (dz * dz));
+}
+
+
+/******************************************************************************/
+
+static int getHuntInfoLevel(const HuntInfo& hi, float dist)
+{
+  // search backwards, from highest level to lowest level
+  std::map<int, float>::const_reverse_iterator rit;
+  for (rit = hi.coords.rbegin(); rit != hi.coords.rend(); rit++) {
+    if (dist < rit->second) {
+      return rit->first;
+    }
+  }
+  return 0;
+}
+
+
+/******************************************************************************/
+
+static void huntPlayers()
+{
+  World* world = World::getWorld();
+  if (world == NULL) {
+    return;
+  }
+  Player** players = (Player**)world->getPlayers();
+  if (players == NULL) {
+    return;
+  }
+  const int maxPlayers = world->getCurMaxPlayers();
+
+  LocalPlayer* myTank = LocalPlayer::getMyTank();
+  if (myTank == NULL) {
+    return;
+  }
+  const bool observer = (myTank->getTeam() == ObserverTeam);
+  
+  // for detecting team killers
+  const float tkWarnRatio = BZDB.eval("tkwarnratio");
+
+  // setup the paired flag map
+  const FlagType* myFlagType = myTank->getFlag();
+  const FlagHuntInfoMap* pairedMap = NULL;
+  if (myFlagType != Flags::Null) {
+    FlagPairMap::const_iterator fpm_it = FlagPairs.find(myFlagType);
+    if (fpm_it != FlagPairs.end()) {
+      pairedMap = &(fpm_it->second);
+    }
+  }
+
+  // setup the solo flag map
+  const FlagHuntInfoMap& soloMap = FlagPairs[Flags::Null];
+
+  // setup hunt level based on player status
+  for (int i = 0; i < maxPlayers; i++) {
+
+    Player* p = players[i];
+    if (p == NULL) {
+      continue;
+    }
+    const bool myEnemy = isEnemy(p);
+
+    // get the distance
+    const float dist = distBetweenPlayers(myTank, p);
+
+    // flag type
+    FlagType* ft = p->getFlag();
+    if (ft != Flags::Null) {
+      // solo flag type
+      if (myEnemy || observer) {
+        FlagHuntInfoMap::const_iterator sit = soloMap.find(ft);
+        if (sit != soloMap.end()) {
+          const HuntInfo& hi = sit->second;
+          const int level = getHuntInfoLevel(hi, dist);
+          if (level > p->getAutoHuntLevel()) {
+            p->setAutoHuntLevel(level);
+          }
+        }
+      }
+      // paired flag type
+      if (myEnemy && (pairedMap != NULL)) {
+        FlagHuntInfoMap::const_iterator pit = pairedMap->find(ft);
+        if (pit != pairedMap->end()) {
+          const HuntInfo& hi = pit->second;
+          const int level = getHuntInfoLevel(hi, dist);
+          if (level > p->getAutoHuntLevel()) {
+            p->setAutoHuntLevel(level);
+          }
+        }
+      }
+    }
+
+    // player ratio
+    if (myEnemy && RatioHuntInfo.active() && isBeatingMe(p)) {
+      const int level = getHuntInfoLevel(RatioHuntInfo, dist);
+      if (level > p->getAutoHuntLevel()) {
+        p->setAutoHuntLevel(level);
+      }
+    }
+
+    // teamkill ratio
+    if (!myEnemy && TkHuntInfo.active() && isTeamKiller(p, tkWarnRatio)) {
+      const int level = getHuntInfoLevel(TkHuntInfo, dist);
+      if (level > p->getAutoHuntLevel()) {
+        p->setAutoHuntLevel(level);
+      }
+    }
+  }
+
+  // set the autohunt status based on score leadership
+  if (LeaderHuntInfo.active()) {
+    Player* leader = findEnemyLeader();
+    if (leader != NULL) {
+      const float dist = distBetweenPlayers(myTank, leader);
+      const int level = getHuntInfoLevel(LeaderHuntInfo, dist);
+      if (level > leader->getAutoHuntLevel()) {
+        leader->setAutoHuntLevel(level);
+      }
+    }
+  }
+
+  return;
+}
+
+
+/******************************************************************************/
+
+static std::string getExpandedString(const std::string& varname)
+{
+  static int depth = 0;
+  if (depth >= 8) { // recursion protection
+    return "";
+  }
+
+  const std::string init = BZDB.get(varname);
+  if (init.size() == 0) {
+    return "";
+  }
+
+  std::string expanded;
+  const std::vector<std::string> tokens = TextUtils::tokenize(init, ":");
+  for (int i = 0; i < (int)tokens.size(); i++) {
+    std::string token = tokens[i];
+    if (token[0] == '*') {
+      depth++;
+      token = getExpandedString(tokens[i].c_str() + 1);
+      depth--;
+    }
+    expanded += token;
+    if (i < ((int)tokens.size() - 1)) {
+      expanded += ':';
+    }
+  }
+
+  return expanded;
+}
+
+
+/******************************************************************************/
+/******************************************************************************/
+
+void AutoHunt::update()
+{
+  static std::string active = "";
+  
+  std::string current = getExpandedString("autohunt");
+  if ((current.size() == 0) && (active.size() == 0)) {
+    return;
+  }
+
+  clearPlayers();
+
+  if (active != current) {
+    active = current;
+    parse(active);
+    printConfig(); // for debugging
+  }
+
+  huntPlayers();
+
+  return;
+}
+
+
+/******************************************************************************/
+
+const char* AutoHunt::getColorString(int level)
+{
+  switch (level) {
+    case 1: return ANSI_STR_FG_BLACK;
+    case 2: return ANSI_STR_FG_MAGENTA;
+    case 3: return ANSI_STR_FG_BLUE;
+    case 4: return ANSI_STR_FG_GREEN;
+    case 5: return ANSI_STR_FG_YELLOW;
+    case 6: return ANSI_STR_FG_ORANGE;
+    case 7: return ANSI_STR_FG_RED;
+    case 8: return ANSI_STR_FG_WHITE;
+    case 9: return ANSI_STR_FG_WHITE ANSI_STR_UNDERLINE;
+    default: return ANSI_STR_FG_BLACK;
+  }
+}
+
+
+/******************************************************************************/
+
+int AutoHunt::getChevronCount(int level)
+{
+  if ((level >= 0) && (level < LevelCount)) {
+    return ChevronCounts[level];
+  } else {
+    return 0;
+  }
+}
+
+
+/******************************************************************************/
+
+float AutoHunt::getChevronSpace(int level)
+{
+  if ((level >= 0) && (level < LevelCount)) {
+    return ChevronSpaces[level];
+  } else {
+    return 1.0f;
+  }
+}
+
+
+/******************************************************************************/
+
+float AutoHunt::getChevronDelta(int level)
+{
+  if ((level >= 0) && (level < LevelCount)) {
+    return ChevronDeltas[level];
+  } else {
+    return 1.0f;
+  }
+}
+
+
+/******************************************************************************/
+
+float AutoHunt::getChevronInnerAlpha(int level)
+{
+  if ((level >= 0) && (level < LevelCount)) {
+    return ChevronInnerAlphas[level];
+  } else {
+    return 1.0f;
+  }
+}
+
+
+/******************************************************************************/
+
+float AutoHunt::getChevronOuterAlpha(int level)
+{
+  if ((level >= 0) && (level < LevelCount)) {
+    return ChevronOuterAlphas[level];
+  } else {
+    return 1.0f;
+  }
+}
+
+
+/******************************************************************************/
+
+float AutoHunt::getBlinkPeriod(int level)
+{
+  if ((level >= 0) && (level < LevelCount)) {
+    return BlinkPeriods[level];
+  } else {
+    return 0.5f;
+  }
+}
+
+
+/******************************************************************************/
+
+float AutoHunt::getInnerBlinkThreshold(int level)
+{
+  if ((level >= 0) && (level < LevelCount)) {
+    return InnerBlinkThresholds[level];
+  } else {
+    return 0.5f;
+  }
+}
+
+
+/******************************************************************************/
+
+float AutoHunt::getOuterBlinkThreshold(int level)
+{
+  if ((level >= 0) && (level < LevelCount)) {
+    return OuterBlinkThresholds[level];
+  } else {
+    return 0.5f;
+  }
+}
+
+
+/******************************************************************************/
+
+// Local Variables: ***
+// mode:C++ ***
+// tab-width: 8 ***
+// c-basic-offset: 2 ***
+// indent-tabs-mode: t ***
+// End: ***
+// ex: shiftwidth=2 tabstop=8
+
Index: AutoHunt.h
===================================================================
RCS file: AutoHunt.h
diff -N AutoHunt.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ AutoHunt.h	25 May 2006 10:21:39 -0000
@@ -0,0 +1,47 @@
+/* bzflag
+ * Copyright (c) 1993 - 2006 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.
+ */
+
+
+#include "common.h"
+
+// system headers
+#include <string>
+
+
+namespace AutoHunt {
+
+  void update();
+
+  const char* getColorString(int level);
+
+  int getChevronCount(int level);
+  float getChevronSpace(int level);
+  float getChevronDelta(int level);
+  float getChevronInnerAlpha(int level);
+  float getChevronOuterAlpha(int level);
+  float getBlinkPeriod(int level);
+  float getInnerBlinkThreshold(int level); // tank square
+  float getOuterBlinkThreshold(int level); // corner chevrons
+  // NOTE: (blink thresholds)
+  //       0 -> always blink color 
+  //       1 -> always normal color
+}
+
+
+// Local Variables: ***
+// mode:C++ ***
+// tab-width: 8 ***
+// c-basic-offset: 2 ***
+// indent-tabs-mode: t ***
+// End: ***
+// ex: shiftwidth=2 tabstop=8
+
Index: Makefile.am
===================================================================
RCS file: /cvsroot/bzflag/bzflag/src/bzflag/Makefile.am,v
retrieving revision 2.20.2.4
diff -u -r2.20.2.4 Makefile.am
--- Makefile.am	31 Jan 2006 10:29:01 -0000	2.20.2.4
+++ Makefile.am	25 May 2006 10:21:39 -0000
@@ -21,6 +21,8 @@
 	ActionBinding.h			\
 	AudioMenu.cxx			\
 	AudioMenu.h			\
+	AutoHunt.cxx			\
+	AutoHunt.h			\
 	AutoPilot.cxx			\
 	AutoPilot.h			\
 	BackgroundRenderer.cxx		\
Index: Player.cxx
===================================================================
RCS file: /cvsroot/bzflag/bzflag/src/bzflag/Player.cxx,v
retrieving revision 2.29.2.5
diff -u -r2.29.2.5 Player.cxx
--- Player.cxx	29 Mar 2006 23:18:02 -0000	2.29.2.5
+++ Player.cxx	25 May 2006 10:21:40 -0000
@@ -48,6 +48,7 @@
   lastObstacle(NULL),
   notResponding(false),
   hunted(false),
+  autoHuntLevel(0),
   id(_id),
   admin(false),
   registered(false),
Index: Player.h
===================================================================
RCS file: /cvsroot/bzflag/bzflag/src/bzflag/Player.h,v
retrieving revision 2.10.2.2
diff -u -r2.10.2.2 Player.h
--- Player.h	29 Mar 2006 23:18:03 -0000	2.10.2.2
+++ Player.h	25 May 2006 10:21:41 -0000
@@ -134,6 +134,8 @@
   void		resetNotResponding();
   bool		isHunted() const;
   void		setHunted(bool _hunted);
+  int		getAutoHuntLevel() const;
+  void		setAutoHuntLevel(int level);
   bool		isAutoPilot() const;
   void		setAutoPilot(bool = true);
   bool		isAdmin() const;
@@ -215,6 +217,7 @@
   // data not communicated with other players
   bool			notResponding;
   bool			hunted;
+  int			autoHuntLevel;
 
   // credentials
   PlayerId		id;
@@ -535,6 +538,16 @@
   hunted = _hunted;
 }
 
+inline int		Player::getAutoHuntLevel() const
+{
+  return autoHuntLevel;
+}
+
+inline void		Player::setAutoHuntLevel(int level)
+{
+  autoHuntLevel = level;
+}
+
 inline bool		Player::isAdmin() const
 {
   return admin;
Index: RadarRenderer.cxx
===================================================================
RCS file: /cvsroot/bzflag/bzflag/src/bzflag/RadarRenderer.cxx,v
retrieving revision 2.26.2.1
diff -u -r2.26.2.1 RadarRenderer.cxx
--- RadarRenderer.cxx	4 Jan 2006 00:50:35 -0000	2.26.2.1
+++ RadarRenderer.cxx	25 May 2006 10:21:42 -0000
@@ -27,17 +27,15 @@
 #include "BoxBuilding.h"
 #include "PyramidBuilding.h"
 #include "MeshObstacle.h"
+#include "TimeKeeper.h"
 
 // local implementation headers
 #include "LocalPlayer.h"
 #include "World.h"
-#include "FlashClock.h"
+#include "AutoHunt.h"
 #include "ShotPath.h"
 
 
-static FlashClock flashTank;
-static bool toggleTank = false;
-
 const float RadarRenderer::colorFactor = 40.0f;
 
 RadarRenderer::RadarRenderer(const SceneRenderer&, World* _world)
@@ -49,6 +47,7 @@
     dimming(0.0f),
     decay(0.01f),
     jammed(false),
+    colorblind(false),
     multiSampled(false)
 {
 
@@ -106,10 +105,12 @@
 }
 
 
-void RadarRenderer::drawTank(const float pos[3], const Player* player)
+void RadarRenderer::drawTank(const Player* player, bool allowFancy)
 {
   glPushMatrix();
 
+  const float* pos = player->getPosition();  
+
   // 'ps' is pixel scale, setup in render()
   const float tankRadius = BZDBCache::tankRadius;
   float minSize = 1.5f + (ps * BZDBCache::radarTankPixels);
@@ -130,7 +131,7 @@
   glTranslatef(pos[0], pos[1], 0.0f);
 
   // draw the tank
-  if ((player == NULL) || !useTankDimensions) {
+  if (!allowFancy || !useTankDimensions) {
     // align to the screen axes
     glRotatef(float(myAngle * 180.0 / M_PI), 0.0f, 0.0f, 1.0f);
     glRectf(-size, -size, +size, +size);
@@ -153,17 +154,22 @@
 
   // adjust with height box size
   const float boxHeight = BZDB.eval(StateDatabase::BZDB_BOXHEIGHT);
-  size = size * (1.0f + (0.5f * (pos[2] / boxHeight)));
+  const float hbSize = size * (1.0f + (0.5f * (pos[2] / boxHeight)));
 
   // draw the height box
   glBegin(GL_LINE_STRIP);
-  glVertex2f(-size, 0.0f);
-  glVertex2f(0.0f, -size);
-  glVertex2f(+size, 0.0f);
-  glVertex2f(0.0f, +size);
-  glVertex2f(-size, 0.0f);
+  glVertex2f(-hbSize, 0.0f);
+  glVertex2f(0.0f, -hbSize);
+  glVertex2f(+hbSize, 0.0f);
+  glVertex2f(0.0f, +hbSize);
+  glVertex2f(-hbSize, 0.0f);
   glEnd();
 
+  // draw the AutoHunt indicator
+  if (!colorblind) {
+    drawHuntLevel(player, size, hbSize);
+  }
+
   glPopMatrix();
 }
 
@@ -203,6 +209,97 @@
 }
 
 
+void RadarRenderer::drawHuntLevel(const Player* player,
+                                  float tankSize, float heightBoxSize)
+{
+  const int huntLevel = player->getAutoHuntLevel();
+  const int chevrons = AutoHunt::getChevronCount(huntLevel);
+  if (chevrons < 1) {
+    return;
+  }
+
+  // setup the color
+  float color[4];
+  const double diffTime = TimeKeeper::getTick() - TimeKeeper::getStartTime();
+  const float period = AutoHunt::getBlinkPeriod(huntLevel);
+  const float thresh = AutoHunt::getOuterBlinkThreshold(huntLevel);
+  const bool blink = fmodf((float)diffTime, period) < (period * thresh);
+  if (blink) {
+    color[0] = 0.5f;
+    color[1] = 0.5f;
+    color[2] = 0.5f;
+  } else {
+    color[0] = 1.0f;
+    color[1] = 1.0f;
+    color[2] = 1.0f;
+  }
+  // translucency range
+  const float innerAlpha = AutoHunt::getChevronInnerAlpha(huntLevel);
+  const float outerAlpha = AutoHunt::getChevronOuterAlpha(huntLevel);
+  color[3] = innerAlpha;
+  const float alphaRange = innerAlpha - outerAlpha;
+  float alphaDelta = 0.0f;
+  if (chevrons > 1) {
+    alphaDelta = alphaRange / (float)(chevrons - 1);
+  }
+  
+  // setup the geometry
+  float s = (float)M_SQRT1_2 * heightBoxSize;
+  const float spacing = ps * AutoHunt::getChevronSpace(huntLevel);
+  const float check1 = spacing * 2.0f;
+  if (s < check1) {
+    s = check1;
+  }
+  const float check2 = tankSize + spacing;
+  if (s < check2) {
+    s = check2;
+  }
+  const float gap = 0.4f;
+  float c = s * (1.0f - gap);
+
+  const float cDelta = spacing * AutoHunt::getChevronDelta(huntLevel);
+  
+  if (!smooth) {
+    glEnable(GL_BLEND);
+  }
+
+  // draw the chevrons
+  for (int i = 0; i < chevrons; i++) {
+    glColor4fv(color);
+    glBegin(GL_LINE_STRIP);
+    glVertex2f(+s, +c);
+    glVertex2f(+s, +s);
+    glVertex2f(+c, +s);
+    glEnd();
+    glBegin(GL_LINE_STRIP);
+    glVertex2f(-s, -c);
+    glVertex2f(-s, -s);
+    glVertex2f(-c, -s);
+    glEnd();
+    glBegin(GL_LINE_STRIP);
+    glVertex2f(-c, +s);
+    glVertex2f(-s, +s);
+    glVertex2f(-s, +c);
+    glEnd();
+    glBegin(GL_LINE_STRIP);
+    glVertex2f(+c, -s);
+    glVertex2f(+s, -s);
+    glVertex2f(+s, -c);
+    glEnd();
+    // setup for the next pass
+    s += spacing;
+    c += cDelta;
+    color[3] -= alphaDelta;
+  }
+
+  if (!smooth) {
+    glDisable(GL_BLEND);
+  }
+
+  return;
+}
+
+
 void RadarRenderer::drawFlag(const float pos[3])
 {
   GLfloat s = BZDBCache::flagRadius > 3.0f * ps ? BZDBCache::flagRadius : 3.0f * ps;
@@ -328,6 +425,26 @@
 }
 
 
+static bool checkDrawFlags()
+{
+  if (!BZDB.isTrue("autoFlagDisplay")) {
+    return BZDB.isTrue("displayRadarFlags");
+  }
+  const LocalPlayer *myTank = LocalPlayer::getMyTank();
+  if (!myTank) {
+    return BZDB.isTrue("displayRadarFlags");
+  }
+  // pick the automatic mode
+  const FlagType* ft = myTank->getFlag();
+  if ((ft == Flags::Null)       ||
+      (ft == Flags::Identify)   ||
+      (ft->flagQuality == FlagBad)) { 
+    return true;
+  }
+  return false;
+}
+
+
 void RadarRenderer::render(SceneRenderer& renderer, bool blank, bool observer)
 {
   RenderNode::resetTriangleCount();
@@ -476,6 +593,9 @@
 
   // only draw if there's a local player and a world
   else if (myTank) {
+
+    colorblind = (myTank->getFlag() == Flags::Colorblindness);
+
     // if decay is sufficiently small then boost it so it's more
     // likely a jammed radar will get a few good frames closely
     // spaced in time.  value of 1 guarantees at least two good
@@ -568,6 +688,9 @@
       }
     }
 
+    // used for blinking tanks
+    const double diffTime = TimeKeeper::getTick() - TimeKeeper::getStartTime();
+
     // draw other tanks (and any flags on them)
     // note about flag drawing.  each line segment is drawn twice
     // (once in each direction);  this degrades the antialiasing
@@ -599,10 +722,10 @@
 	const float dimfactor = 0.4f;
 
 	const float *color;
-	if (myTank->getFlag() == Flags::Colorblindness) {
-	  color = Team::getRadarColor(RogueTeam,rabbitMode);
+	if (colorblind) {
+	  color = Team::getRadarColor(RogueTeam, rabbitMode);
 	} else {
-	  color = Team::getRadarColor(player->getTeam(),rabbitMode);
+	  color = Team::getRadarColor(player->getTeam(), rabbitMode);
 	}
 
 	float dimmedcolor[3];
@@ -611,29 +734,34 @@
 	dimmedcolor[2] = color[2] * dimfactor;
 	glColor3fv(dimmedcolor);
       } else {
-	glColor3fv(Team::getRadarColor(myTank->getFlag() ==
-			     Flags::Colorblindness ? RogueTeam : player->getTeam(), rabbitMode));
+        const TeamColor team = colorblind ? RogueTeam : player->getTeam();
+	glColor3fv(Team::getRadarColor(team, rabbitMode));
       }
+
       // If this tank is hunted flash it on the radar
-      if (player->isHunted() && myTank->getFlag() != Flags::Colorblindness) {
-	if (flashTank.isOn()) {
-	  if (!toggleTank) {
-	    float flashcolor[3];
-	    flashcolor[0] = 0.0f;
-	    flashcolor[1] = 0.8f;
-	    flashcolor[2] = 0.9f;
-	    glColor3fv(flashcolor);
-	  }
-	} else {
-	  toggleTank = !toggleTank;
-	  flashTank.setClock(0.2f);
-	}
-      }
-      if (!observer) {
-	drawTank(position, NULL);
-      } else {
-	drawTank(position, player);
-      }
+      int huntLevel = player->isHunted() ? 9 : player->getAutoHuntLevel();  
+      if ((huntLevel > 0) && !colorblind) {
+        float period = 0.40f;
+        float thresh = 0.25f;
+        if (!player->isHunted()) {
+          period = AutoHunt::getBlinkPeriod(huntLevel);
+          thresh = AutoHunt::getInnerBlinkThreshold(huntLevel);
+        }
+        const bool blink = fmodf((float)diffTime, period) < (period * thresh);
+        if (blink) {
+          const float greenBlinkColor[3] = {1.0f, 0.8f, 1.0f};
+          const float normalBlinkColor[3] = {0.0f, 0.8f, 0.9f};
+          if (player->getTeam() == GreenTeam) {
+            glColor3fv(greenBlinkColor);
+          } else {
+            glColor3fv(normalBlinkColor);
+          }
+        } else {
+          glColor3fv(Team::getRadarColor(player->getTeam(), rabbitMode));
+        }
+      } 
+
+      drawTank(player, observer);
     }
 
     bool coloredShot = BZDB.isTrue("coloredradarshots");
@@ -648,10 +776,11 @@
 	if (shot && (shot->getFlag() != Flags::InvisibleBullet || iSeeAll)) {
 	  const float *shotcolor;
 	  if (coloredShot) {
-	    if (myTank->getFlag() == Flags::Colorblindness)
-	      shotcolor = Team::getRadarColor(RogueTeam,rabbitMode);
-	    else
-	      shotcolor = Team::getRadarColor(player->getTeam(),rabbitMode);
+	    if (colorblind) {
+	      shotcolor = Team::getRadarColor(RogueTeam, rabbitMode);
+	    } else {
+	      shotcolor = Team::getRadarColor(player->getTeam(), rabbitMode);
+            }
 	    const float cs = colorScale(shot->getPosition()[2], muzzleHeight);
 	    glColor3f(shotcolor[0] * cs, shotcolor[1] * cs, shotcolor[2] * cs);
 	  } else {
@@ -666,7 +795,7 @@
     // draw them in reverse order so that the team flags
     // (which come first), are drawn on top of the normal flags.
     const int maxFlags = world->getMaxFlags();
-    const bool drawNormalFlags = BZDB.isTrue("displayRadarFlags");
+    const bool drawNormalFlags = checkDrawFlags();
     for (i = (maxFlags - 1); i >= 0; i--) {
       const Flag& flag = world->getFlag(i);
       // don't draw flags that don't exist or are on a tank
@@ -724,7 +853,7 @@
 
       // my tank
       glColor3f(1.0f, 1.0f, 1.0f);
-      drawTank(myPos, myTank);
+      drawTank(myTank, true);
     }
 
     if (dimming > 0.0f) {
Index: RadarRenderer.h
===================================================================
RCS file: /cvsroot/bzflag/bzflag/src/bzflag/RadarRenderer.h,v
retrieving revision 2.4.2.1
diff -u -r2.4.2.1 RadarRenderer.h
--- RadarRenderer.h	4 Jan 2006 00:50:35 -0000	2.4.2.1
+++ RadarRenderer.h	25 May 2006 10:21:42 -0000
@@ -26,6 +26,8 @@
 class SceneRenderer;
 class World;
 class ShotPath;
+class Player;
+
 
 class RadarRenderer {
   public:
@@ -62,9 +64,10 @@
     RadarRenderer&	operator=(const RadarRenderer&);
 
     void		drawShot(const ShotPath*);
-    void		drawTank(const float pos[3],
-				 const class Player* player);
-    void		drawFancyTank(const class Player* player);
+    void		drawTank(const Player* player, bool allowFancy);
+    void		drawFancyTank(const Player* player);
+    void		drawHuntLevel(const Player* player,
+                                      float tankSize, float heightBoxSize);
     void		drawFlag(const float pos[3]);
     void		drawFlagOnTank(const float pos[3]);
 
@@ -82,6 +85,7 @@
     GLfloat		teamColor[3];
     bool		smooth;
     bool		jammed;
+    bool		colorblind;
     bool		multiSampled;
     bool		useTankModels;
     bool		useTankDimensions;
Index: ScoreboardRenderer.cxx
===================================================================
RCS file: /cvsroot/bzflag/bzflag/src/bzflag/ScoreboardRenderer.cxx,v
retrieving revision 2.27.2.3
diff -u -r2.27.2.3 ScoreboardRenderer.cxx
--- ScoreboardRenderer.cxx	5 Apr 2006 23:13:30 -0000	2.27.2.3
+++ ScoreboardRenderer.cxx	25 May 2006 10:21:43 -0000
@@ -26,6 +26,7 @@
 
 /* local implementation headers */
 #include "LocalPlayer.h"
+#include "AutoHunt.h"
 #include "World.h"
 #include "sound.h"
 
@@ -39,9 +40,18 @@
 std::string		ScoreboardRenderer::teamCountSpacingLabel("888");
 std::string		ScoreboardRenderer::playerLabel("Player");
 
-// NOTE: order of sort labels must match SORT_ consts
-const char *ScoreboardRenderer::sortLabels[] = {
-  "[Score]", "[Normalized Score]", "[Callsign]", "[Team Kills]", "[TK ratio]", "[Team]", "[1on1]", NULL};
+// NOTE: order of sort labels must match Sort enums
+const char* ScoreboardRenderer::sortLabels[SortTypeCount + 1] = {
+  "[Score]",
+  "[Normalized Score]",
+  "[Callsign]",
+  "[Team Kills]",
+  "[TK ratio]",
+  "[Team]",
+  "[1on1]",
+  "[HuntLevel]",
+  NULL
+};
 int ScoreboardRenderer::sortMode = 0;
 bool ScoreboardRenderer::alwaysShowTeamScore = 0;
 
@@ -60,7 +70,7 @@
   messageColor[0] = 1.0f;
   messageColor[1] = 1.0f;
   messageColor[2] = 1.0f;
-  sortMode = BZDB.getIntClamped("scoreboardSort", 0, SORT_MAXNUM);
+  sortMode = BZDB.getIntClamped("scoreboardSort", 0, SortTypeCount - 1);
   alwaysShowTeamScore = (BZDB.getIntClamped("alwaysShowTeamScore", 0, 1) != 0);
 }
 
@@ -86,7 +96,7 @@
 }
 
 
-const char  **ScoreboardRenderer::getSortLabels ()
+const char** ScoreboardRenderer::getSortLabels ()
 {
   return sortLabels;
 }
@@ -418,7 +428,7 @@
   hudColor3fv(messageColor);
 
   std::string psLabel = bdl->getLocalString(playerLabel);
-  if (sortMode != SORT_SCORE){
+  if (sortMode != SortScore){
     psLabel += "  ";
     psLabel += sortLabels[sortMode];
   }
@@ -639,11 +649,11 @@
     playerInfo += "[auto]";
 
 #if DEBUG_SHOWRATIOS
-  if (sortMode == SORT_NORMALIZED)
+  if (sortMode == SortNormalized)
     stringAppendNormalized (&playerInfo, player->getNormalizedScore());
-  else if (sortMode == SORT_MYRATIO)
+  else if (sortMode == SortMyRatio)
     stringAppendNormalized (&playerInfo, player->getLocalNormalizedScore());
-  else if (sortMode == SORT_TKRATIO)
+  else if (sortMode == SortTkRatio)
     stringAppendNormalized (&playerInfo, player->getTKRatio());
 #endif
 
@@ -671,13 +681,14 @@
     huntStr += "Hunt->";
     fm.drawString(xs - huntedArrowWidth, y, 0, minorFontFace, minorFontSize,
 		  huntStr.c_str());
-  } else if (huntCursor && !huntAddMode) {
-    std::string huntStr = ColorStrings[WhiteColor];
-    huntStr += ColorStrings[PulsatingColor];
-    huntStr += "->";
-    fm.drawString(xs - huntArrowWidth, y, 0, minorFontFace, minorFontSize,
+  }
+  else if (player->getAutoHuntLevel()) {
+    std::string huntStr = AutoHunt::getColorString(player->getAutoHuntLevel());
+    huntStr += "Auto->";
+    fm.drawString(xs - huntedArrowWidth, y, 0, minorFontFace, minorFontSize,
 		  huntStr.c_str());
   }
+
   if (huntCursor && huntAddMode) {
     std::string huntStr = ColorStrings[WhiteColor];
     huntStr += ColorStrings[PulsatingColor];
@@ -685,20 +696,29 @@
     fm.drawString(xs - huntPlusesWidth, y, 0, minorFontFace, minorFontSize,
 		  huntStr.c_str());
   }
+
+  if (huntCursor && !huntAddMode) {
+    std::string huntStr = ColorStrings[WhiteColor];
+    huntStr += ColorStrings[PulsatingColor];
+    huntStr += "->";
+    fm.drawString(xs - huntArrowWidth, y, 0, minorFontFace, minorFontSize,
+		  huntStr.c_str());
+  }
 }
 
 
 // get current 'leader' (NULL if no player)
-Player*   ScoreboardRenderer::getLeader(std::string *label) {
+Player* ScoreboardRenderer::getLeader(std::string *label)
+{
   int sortType=sortMode;
 
-  if (sortMode==SORT_CALLSIGN || sortMode==SORT_MYRATIO || sortMode==SORT_TEAM){
-    sortType = SORT_SCORE;
+  if (sortMode==SortCallsign || sortMode==SortMyRatio || sortMode==SortTeam){
+    sortType = SortScore;
   }
   if (label != NULL){
-    if (sortMode==SORT_TKS)
+    if (sortMode==SortTKs)
       *label = "TK Leader ";
-    else if (sortMode==SORT_TKRATIO)
+    else if (sortMode==SortTkRatio)
       *label = "TK Ratio Leader ";
     else
       *label = "Leader ";
@@ -784,41 +804,48 @@
     for (i=0; i<numPlayers; i++){
       p = sorter[i].player;
       switch (sortType){
-	case SORT_TKS:
+	case SortTKs:
 	  sorter[i].i1 = p->getTeamKills();
 	  sorter[i].i2 = 0 - (int)(p->getNormalizedScore() * 100000);
 	  break;
-	case SORT_TKRATIO:
+	case SortTkRatio:
 	  sorter[i].i1 = (int)(p->getTKRatio() * 1000);
 	  sorter[i].i2 = 0 - (int)(p->getNormalizedScore() * 100000);
 	  break;
-	case SORT_TEAM:
+	case SortTeam:
 	  sorter[i].i1 = p->getTeam();
 	  sorter[i].i2 = (int)(p->getNormalizedScore() * 100000);
 	  break;
-	case SORT_MYRATIO:
+	case SortMyRatio:
 	  if (p == myTank)
 	    sorter[i].i1 = -100001;
 	  else
 	    sorter[i].i1 = 0 - (int)(p->getLocalNormalizedScore() * 100000);
 	  sorter[i].i2 = (int)(p->getNormalizedScore() * 100000);
 	  break;
-	case SORT_NORMALIZED:
+	case SortNormalized:
 	  sorter[i].i1 = (int)(p->getNormalizedScore() * 100000);
 	  sorter[i].i2 = 0;
 	  break;
-	case SORT_CALLSIGN:
+	case SortCallsign:
 	  sorter[i].cp = p->getCallSign();
 	  break;
-	default:
+	case SortHuntLevel: {
+	  const int huntLevel = p->getAutoHuntLevel();
+	  sorter[i].i1 = p->isHunted() ? 10 : huntLevel;
+	  sorter[i].i2 = p->getScore();
+	  break;
+        }
+	default: {
 	  if (world->allowRabbit())
 	    sorter[i].i1 = p->getRabbitScore();
 	  else
 	    sorter[i].i1 = p->getScore();
 	  sorter[i].i2 = 0;
+        }
       }
     }
-    if (sortType == SORT_CALLSIGN)
+    if (sortType == SortCallsign)
       qsort (sorter, numPlayers, sizeof(sortEntry), sortCompareCp);
     else
       qsort (sorter, numPlayers, sizeof(sortEntry), sortCompareI2);
Index: ScoreboardRenderer.h
===================================================================
RCS file: /cvsroot/bzflag/bzflag/src/bzflag/ScoreboardRenderer.h,v
retrieving revision 2.12.2.2
diff -u -r2.12.2.2 ScoreboardRenderer.h
--- ScoreboardRenderer.h	4 Apr 2006 21:44:33 -0000	2.12.2.2
+++ ScoreboardRenderer.h	25 May 2006 10:21:43 -0000
@@ -57,35 +57,40 @@
   static void    setSort (int _sortby);
   static int     getSort ();
   static const char **getSortLabels();
-  static const int SORT_SCORE = 0;
-  static const int SORT_NORMALIZED = 1;
-  static const int SORT_CALLSIGN = 2;
-  static const int SORT_TKS = 3;
-  static const int SORT_TKRATIO = 4;
-  static const int SORT_TEAM = 5;
-  static const int SORT_MYRATIO = 6;
-  static const int SORT_MAXNUM = SORT_MYRATIO;
+  enum SortingTypes {
+    SortScore = 0,
+    SortNormalized,
+    SortCallsign,
+    SortTKs,
+    SortTkRatio,
+    SortTeam,
+    SortMyRatio,
+    SortHuntLevel,
+    SortTypeCount
+  };
 
 protected:
-  void		hudColor3fv(const GLfloat*);
-  void		renderTeamScores (float y, float x, float dy);
-  void		renderScoreboard(void);
-  void          renderCtfFlags (void);
-  void		drawPlayerScore(const Player*,
-		    float x1, float x2, float x3, float xs, float y, int emailLen, bool huntInd);
-  static const char *sortLabels[SORT_MAXNUM+2];
+  void hudColor3fv(const GLfloat*);
+  void renderTeamScores (float y, float x, float dy);
+  void renderScoreboard(void);
+  void renderCtfFlags (void);
+  void drawPlayerScore(const Player*,
+		       float x1, float x2, float x3, float xs, float y,
+                       int emailLen, bool huntInd);
+  static const char* sortLabels[SortTypeCount + 1];
   static int  sortMode;
   static bool alwaysShowTeamScore;
   void   stringAppendNormalized (std::string *s, float n);
 
 private:
-  void		setMinorFontSize(float height);
-  void		setLabelsFontSize(float height);
-  static int	teamScoreCompare(const void* _a, const void* _b);
-  static int  sortCompareCp(const void* _a, const void* _b);
-  static int  sortCompareI2(const void* _a, const void* _b);
+  void setMinorFontSize(float height);
+  void setLabelsFontSize(float height);
+  void exitSelectState (void);
+
+  static int teamScoreCompare(const void* _a, const void* _b);
+  static int sortCompareCp(const void* _a, const void* _b);
+  static int sortCompareI2(const void* _a, const void* _b);
   static Player** newSortedList (int sortType, bool obsLast, int *_numPlayers=NULL);
-  void	  exitSelectState (void);
 
 private:
   float winX;
@@ -94,35 +99,36 @@
   float winHeight;
 
   GLfloat	messageColor[3];
-  int		  minorFontFace;
+  int		minorFontFace;
   float		minorFontSize;
-  int		  labelsFontFace;
+  int		labelsFontFace;
   float		labelsFontSize;
 
-  bool		dim;
-  float		scoreLabelWidth;
-  float		killsLabelWidth;
-  float		teamScoreLabelWidth;
-  float		teamCountLabelWidth;
-  float		huntArrowWidth;
-  float		huntPlusesWidth;
-  float		huntedArrowWidth;
-  float		tkWarnRatio;
-
-  static std::string	scoreSpacingLabel;
-  static std::string	scoreLabel;
-  static std::string	killSpacingLabel;
-  static std::string	killLabel;
-  static std::string	teamScoreSpacingLabel;
-  static std::string	playerLabel;
-  static std::string	teamCountSpacingLabel;
-  bool		huntIndicator;
-  int			huntPosition;
-  bool		huntSelectEvent;
-  int		huntPositionEvent;
-  int     huntState;
-  bool    huntAddMode;    // valid only if state == SELECTING
-  int     numHunted;
+  bool	dim;
+  float	scoreLabelWidth;
+  float	killsLabelWidth;
+  float	teamScoreLabelWidth;
+  float	teamCountLabelWidth;
+  float	huntArrowWidth;
+  float	huntPlusesWidth;
+  float	huntedArrowWidth;
+  float	tkWarnRatio;
+
+  static std::string scoreSpacingLabel;
+  static std::string scoreLabel;
+  static std::string killSpacingLabel;
+  static std::string killLabel;
+  static std::string teamScoreSpacingLabel;
+  static std::string playerLabel;
+  static std::string teamCountSpacingLabel;
+
+  bool	huntIndicator;
+  int	huntPosition;
+  bool	huntSelectEvent;
+  int	huntPositionEvent;
+  int	huntState;
+  bool	huntAddMode;    // valid only if state == SELECTING
+  int	numHunted;
 };
 
 
Index: defaultBZDB.cxx
===================================================================
RCS file: /cvsroot/bzflag/bzflag/src/bzflag/defaultBZDB.cxx,v
retrieving revision 2.14.2.7
diff -u -r2.14.2.7 defaultBZDB.cxx
--- defaultBZDB.cxx	4 Apr 2006 21:44:33 -0000	2.14.2.7
+++ defaultBZDB.cxx	25 May 2006 10:21:43 -0000
@@ -100,6 +100,8 @@
   { "stencilShadows",		"0",			true,	StateDatabase::ReadWrite,	NULL },
   { "shadowAlpha",		"0.5",			true,	StateDatabase::ReadWrite,	NULL },
   { "aniso",			"1",			true,	StateDatabase::ReadWrite,	NULL },
+  { "autohunt",			"",			true,	StateDatabase::ReadWrite,	NULL },
+  { "autoFlagDisplay",		"0",			true,	StateDatabase::ReadWrite,	NULL },
 
   // hidden graphics rendering params
   { "useDrawInfo",		"1",			true,	StateDatabase::ReadWrite,	NULL },
Index: playing.cxx
===================================================================
RCS file: /cvsroot/bzflag/bzflag/src/bzflag/playing.cxx,v
retrieving revision 2.146.2.24
diff -u -r2.146.2.24 playing.cxx
--- playing.cxx	6 May 2006 20:37:27 -0000	2.146.2.24
+++ playing.cxx	25 May 2006 10:21:48 -0000
@@ -33,6 +33,7 @@
 #include "AccessList.h"
 #include "AnsiCodes.h"
 #include "AresHandler.h"
+#include "AutoHunt.h"
 #include "BackgroundRenderer.h"
 #include "BaseBuilding.h"
 #include "BillboardSceneNode.h"
@@ -6051,6 +6052,9 @@
     if (world) {
       world->updateAnimations(dt);
     }
+    
+    // update AutoHunt
+    AutoHunt::update();
 
     // prep the HUD
     prepareTheHUD();
