Index: MainWindow.h
===================================================================
--- MainWindow.h	(revision 20911)
+++ MainWindow.h	(working copy)
@@ -67,6 +67,7 @@
 
     void		showWindow(bool = true);
     void		warpMouse();
+    void		warpMouse(int mx, int my);
     void		grabMouse();
     void		enableGrabMouse(bool on);
     bool		isGrabEnabled(void);
Index: BackgroundRenderer.cxx
===================================================================
--- BackgroundRenderer.cxx	(revision 20911)
+++ BackgroundRenderer.cxx	(working copy)
@@ -1217,6 +1217,14 @@
 }
 
 
+static void setupBlackFog(float fogColor[4])
+{
+  static const float black[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+  glGetFloatv(GL_FOG_COLOR, fogColor);
+  glFogfv(GL_FOG_COLOR, black);
+}
+
+
 void BackgroundRenderer::drawGroundReceivers(SceneRenderer& renderer)
 {
   static const int receiverRings = 4;
@@ -1244,6 +1252,10 @@
 
   receiverGState.setState();
 
+  // setup black fog
+  float fogColor[4];
+  setupBlackFog(fogColor);
+
   glPushMatrix();
   int i, j;
   for (int k = 0; k < count; k++) {
@@ -1339,6 +1351,8 @@
     glTranslatef(-pos[0], -pos[1], 0.0f);
   }
   glPopMatrix();
+
+  glFogfv(GL_FOG_COLOR, fogColor);
 }
 
 
@@ -1384,6 +1398,10 @@
   advGState = builder.getState();
   advGState.setState();
 
+  // setup black fog
+  float fogColor[4];
+  setupBlackFog(fogColor);
+
   // lazy way to get texcoords
   if (useTexture) {
     const float repeat = BZDB.eval("groundHighResTexRepeat");
@@ -1512,6 +1530,8 @@
     glDisable(GL_TEXTURE_GEN_S);
     glDisable(GL_TEXTURE_GEN_T);
   }
+
+  glFogfv(GL_FOG_COLOR, fogColor);
 }
 
 
Index: playing.cxx
===================================================================
--- playing.cxx	(revision 20911)
+++ playing.cxx	(working copy)
@@ -171,6 +171,11 @@
 static float FarDeepPlane = FarDeepPlaneDefault;
 static float NearPlane = NearPlaneNormal;
 
+static bool leftButton   = false;
+static bool middleButton = false;
+static bool rightButton  = false;
+
+
 enum BlowedUpReason {
   GotKilledMsg,
   GotShot,
@@ -547,11 +552,94 @@
 static bool		motionFreeze = false;
 #endif
 
-static enum {None, Left, Right, Up, Down} keyboardMovement;
+static enum { None, Left, Right, Up, Down } keyboardMovement;
 static int shiftKeyStatus;
 
-static bool		doKeyCommon(const BzfKeyEvent& key, bool pressed)
+
+static bool getMouseRay(float pos[3], float dir[3])
 {
+  int mx, my;
+  mainWindow->getMousePosition(mx, my);
+  const int wx  = mainWindow->getWidth();
+  const int wy  = mainWindow->getHeight();
+  const int wvy = mainWindow->getViewHeight();
+  printf("getMouseRay: mx=%i my=%i\n", mx, my);  
+  printf("    wx=%i wy=%i wvy=%i\n", wx, wy, wvy);
+
+  const Roaming::RoamingCamera* camera = ROAM.getCamera();
+  if (!camera) {
+    return false;
+  }
+  pos[0] = camera->pos[0];
+  pos[1] = camera->pos[1];
+  pos[2] = camera->pos[2];
+
+  const float rphi   = (float)(camera->phi   * M_PI / 180.0);
+  const float rtheta = (float)(camera->theta * M_PI / 180.0);
+  dir[0] = cosf(rphi) * cosf(rtheta);
+  dir[1] = cosf(rphi) * sinf(rtheta);
+  dir[2] = sinf(rphi);
+
+  const float fov = (float)(camera->zoom * M_PI / 180.0);
+  return true;
+  return fov == 0.0f; /* FIXME */
+}
+
+
+static int findMousePlayer()
+{
+  float pos[3];
+  float dir[3];
+  if (!getMouseRay(pos, dir)) {
+    return -1;
+  }
+  printf("dir = %.3f, %.3f, %.3f\n", dir[0], dir[1], dir[2]);
+  printf("pos = %.3f, %.3f, %.3f\n", pos[0], pos[1], pos[2]);
+
+  const float radius = BZDBCache::tankRadius;
+  const float radiusSqr = (radius * radius);
+
+  float closestDist = MAXFLOAT;
+  int closestIndex = -1;
+
+  for (int i = 0; i < curMaxPlayers; i++) {
+    const Player* p = player[i];
+    if (p) {
+      float ppos[3];
+      const float* rppos = p->getPosition();
+      ppos[0] = rppos[0];
+      ppos[1] = rppos[1];
+      ppos[2] = rppos[2] + (0.5f * BZDBCache::tankHeight);
+      float v[3];
+      v[0] = ppos[0] - pos[0];
+      v[1] = ppos[1] - pos[1];
+      v[2] = ppos[2] - pos[2];
+      const float dot = (v[0] * dir[0]) + (v[1] * dir[1]) + (v[2] * dir[2]);
+      if (dot < -radius) {
+        continue;
+      }
+      float perp[3];
+      perp[0] = v[0] - (dot * v[0]);
+      perp[1] = v[1] - (dot * v[1]);
+      perp[2] = v[2] - (dot * v[2]);
+      const float distSqr = (perp[0] * perp[0])
+                          + (perp[1] * perp[1])
+                          + (perp[2] * perp[2]);
+      if (distSqr <= radiusSqr) {
+        if (dot < closestDist) {
+          closestDist = dot;
+          closestIndex = i;
+        }
+      }
+    }
+  }
+
+  return closestIndex;
+}
+
+
+static bool doKeyCommon(const BzfKeyEvent& key, bool pressed)
+{
   keyboardMovement = None;
   shiftKeyStatus   = key.shift;
   const std::string cmd = KEYMGR.get(key, pressed);
@@ -577,6 +665,7 @@
       return true;
     }
   }
+
   std::string cmdDrive = cmd;
   if (cmdDrive.empty()) {
     // Check for driving keys
@@ -594,7 +683,7 @@
     keyboardMovement = Down;
   }
 
-  if (myTank)
+  if (myTank) {
     switch (keyboardMovement) {
       case Left:
 	myTank->setKey(BzfKeyEvent::Left, pressed);
@@ -611,9 +700,18 @@
       case None:
 	break;
     }
+  }
+
   if (!cmd.empty()) {
-    if (cmd=="fire")
+    if (cmd == "fire") {
       fireButton = pressed;
+      if (myTank && (myTank->getTeam() == ObserverTeam)) {
+        const int targetID = findMousePlayer();
+        if (targetID >= 0) {
+          ROAM.changeTarget(Roaming::explicitSet, targetID);
+        }
+      }
+    }
     roamButton = pressed;
     if (keyboardMovement == None) {
       std::string result = CMDMGR.run(cmd);
@@ -753,7 +851,14 @@
   }
 }
 
+
 static void doKey(const BzfKeyEvent& key, bool pressed) {
+  switch (key.button) {
+    case BzfKeyEvent::LeftMouse:   { leftButton = pressed;   break; }
+    case BzfKeyEvent::RightMouse:  { rightButton = pressed;  break; }
+    case BzfKeyEvent::MiddleMouse: { middleButton = pressed; break; }
+  }
+    
   if (myTank) {
     const std::string cmd = KEYMGR.get(key, pressed);
     if (cmd == "jump") {
@@ -3254,9 +3359,11 @@
     msg = nboUnpackString(msg, value, valueLen);
     value[valueLen] = '\0';
 
-    BZDB.set(name, value);
-    BZDB.setPersistent(name, false);
-    BZDB.setPermission(name, StateDatabase::Locked);
+    if (strcmp(name, "fpsLimit") != 0) {
+      BZDB.set(name, value);
+      BZDB.setPersistent(name, false);
+      BZDB.setPermission(name, StateDatabase::Locked);
+    }
   }
   return msg;
 }
@@ -5679,6 +5786,13 @@
 {
   static Roaming::RoamingCamera prevDeltaCamera;
   static bool inited = false;
+  static int prevMouseBits = 0;
+  const int leftMouseBit   = (1 << 0);
+  const int rightMouseBit  = (1 << 1);
+  const int middleMouseBit = (1 << 2);
+  int currMouseBits = (leftButton   ? leftMouseBit   : 0) |
+                      (rightButton  ? rightMouseBit  : 0) |
+                      (middleButton ? middleMouseBit : 0);
 
   if (!inited) {
     memset(&prevDeltaCamera, 0, sizeof(Roaming::RoamingCamera));
@@ -5688,28 +5802,73 @@
   Roaming::RoamingCamera deltaCamera;
   memset(&deltaCamera, 0, sizeof(Roaming::RoamingCamera));
 
+  const bool mouseWarp = (currMouseBits != prevMouseBits) &&
+                         ((currMouseBits == 0) || (prevMouseBits == 0));
+
+  if (mouseWarp) {
+    mainWindow->warpMouse();
+    memset(&prevDeltaCamera, 0, sizeof(Roaming::RoamingCamera));
+  }
+
   // move roaming camera
   if (myTank) {
-    bool control = ((shiftKeyStatus & BzfKeyEvent::ControlKey) != 0);
-    bool alt     = ((shiftKeyStatus & BzfKeyEvent::AltKey) != 0);
-    bool shift   = ((shiftKeyStatus & BzfKeyEvent::ShiftKey) != 0);
-    if (display->hasGetKeyMode()) {
-      display->getModState (shift, control, alt);
+    if (currMouseBits != 0) {
+      int mx, my;
+      if (mouseWarp) {
+        mx = my = 0;
+      } else {
+        mainWindow->getMousePosition(mx, my);
+      }
+//      const int w = mainWindow->getWidth();
+//      const int h = mainWindow->getViewHeight();
+      const float sx = float(mx * abs(mx)) * 0.0025f;// / float(w);
+      const float sy = float(my * abs(my)) * 0.0025f;// / float(h);
+      
+//      printf("setupRoamingCamera: %i %i  %.3f %.3f\n", mx, my, sx, sy);
+
+      if (leftButton) {
+        if (middleButton) {
+        deltaCamera.pos[2] = -10.0f * sy;
+        } else {
+          deltaCamera.pos[0] = -10.0f * sy;
+        }
+        if (rightButton) {
+          deltaCamera.pos[1] = -10.0f * sx;
+        } else {
+          deltaCamera.theta  = -1.0f * sx;
+        }
+      }
+      else if (rightButton) {
+        deltaCamera.phi    = -1.0f * sy;
+        deltaCamera.theta  = -1.0f * sx;
+      }
+      else if (middleButton) {
+        deltaCamera.pos[2] = -10.0f * sy;
+        deltaCamera.pos[1] = -10.0f * sx;
+      }
     }
-    if (!control && !shift) {
-      deltaCamera.pos[0] = (float)(4 * myTank->getSpeed()) * BZDBCache::tankSpeed;
+    else {
+      bool control = ((shiftKeyStatus & BzfKeyEvent::ControlKey) != 0);
+      bool alt     = ((shiftKeyStatus & BzfKeyEvent::AltKey) != 0);
+      bool shift   = ((shiftKeyStatus & BzfKeyEvent::ShiftKey) != 0);
+      if (display->hasGetKeyMode()) {
+        display->getModState (shift, control, alt);
+      }
+      if (!control && !shift) {
+        deltaCamera.pos[0] = (float)(4 * myTank->getSpeed()) * BZDBCache::tankSpeed;
+      }
+      if (alt) {
+        deltaCamera.pos[1] = (float)(4 * myTank->getRotation()) * BZDBCache::tankSpeed;
+      } else {
+        deltaCamera.theta  = ROAM.getZoom() * (float)myTank->getRotation();
+      }
+      if (control) {
+        deltaCamera.phi    = -2.0f * ROAM.getZoom() / 3.0f * (float)myTank->getSpeed();
+      }
+      if (shift) {
+        deltaCamera.pos[2] = (float)(-4 * myTank->getSpeed()) * BZDBCache::tankSpeed;
+      }
     }
-    if (alt) {
-      deltaCamera.pos[1] = (float)(4 * myTank->getRotation()) * BZDBCache::tankSpeed;
-    } else {
-      deltaCamera.theta  = ROAM.getZoom() * (float)myTank->getRotation();
-    }
-    if (control) {
-      deltaCamera.phi    = -2.0f * ROAM.getZoom() / 3.0f * (float)myTank->getSpeed();
-    }
-    if (shift) {
-      deltaCamera.pos[2] = (float)(-4 * myTank->getSpeed()) * BZDBCache::tankSpeed;
-    }
   }
 
   // adjust for slow keyboard
@@ -5723,8 +5882,8 @@
     deltaCamera.pos[0] = (at * deltaCamera.pos[0]) + (bt * prevDeltaCamera.pos[0]);
     deltaCamera.pos[1] = (at * deltaCamera.pos[1]) + (bt * prevDeltaCamera.pos[1]);
     deltaCamera.pos[2] = (at * deltaCamera.pos[2]) + (bt * prevDeltaCamera.pos[2]);
-    deltaCamera.theta  = (at * deltaCamera.theta) + (bt * prevDeltaCamera.theta);
-    deltaCamera.phi    = (at * deltaCamera.phi) + (bt * prevDeltaCamera.phi);
+    deltaCamera.theta  = (at * deltaCamera.theta)  + (bt * prevDeltaCamera.theta);
+    deltaCamera.phi    = (at * deltaCamera.phi)    + (bt * prevDeltaCamera.phi);
   }
 
   deltaCamera.zoom = roamDZoom;
@@ -5734,6 +5893,8 @@
   // copy the old delta values
   memcpy(&prevDeltaCamera, &deltaCamera, sizeof(Roaming::RoamingCamera));
 
+  prevMouseBits = currMouseBits;
+
   return;
 }
 
Index: MainWindow.cxx
===================================================================
--- MainWindow.cxx	(revision 20911)
+++ MainWindow.cxx	(working copy)
@@ -84,6 +84,11 @@
   window->warpMouse((width >> 1) + xOrigin, y);
 }
 
+void			MainWindow::warpMouse(int mx, int my)
+{
+  window->warpMouse(mx, my);
+}
+
 void			MainWindow::getMousePosition(int& mx, int& my) const
 {
   window->getMouse(mx, my);
