All pastes #829510 Raw Edit

BZFlag CHEAT

public cpp v1 · immutable
#829510 ·published 2007-12-23 06:22 UTC
rendered paste body
/* 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.
 */

/* interface header */
#include "commands.h"

/* system implementation headers */
#include "../zlib/zconf.h"
#include "../zlib/zlib.h"

/* common implementation headers */
#include "BZDBCache.h"
#include "TextUtils.h"
#include "FileManager.h"
#include "DirectoryNames.h"
#include "version.h"

/* local implementation headers */
#include "LocalPlayer.h"
#include "sound.h"
#include "ComposeDefaultKey.h"
#include "SilenceDefaultKey.h"
#include "ServerCommandKey.h"
#include "Roaming.h"
#include "playing.h"
#include "HUDRenderer.h"
#include "HUDui.h"

/** jump
 */
static std::string cmdJump(const std::string&,
			   const CommandManager::ArgList& args, bool*);

/** fire weapon
 */
static std::string cmdFire(const std::string&,
			   const CommandManager::ArgList& args, bool*);

/** drop a flag
 */
static std::string cmdDrop(const std::string&,
			   const CommandManager::ArgList& args, bool*);

/** toggle radar visibility
 */
static std::string cmdToggleRadar(const std::string&,
				  const CommandManager::ArgList& args, bool*);

/** toggle console visibility
 */
static std::string cmdToggleConsole(const std::string&,
				    const CommandManager::ArgList& args, bool*);

/** toggle flags
 */
static std::string cmdToggleFlags(const std::string&,
				  const CommandManager::ArgList& args, bool*);

/** identify to a server
 */
static std::string cmdIdentify(const std::string&,
			       const CommandManager::ArgList& args, bool*);

/** restart/respawn
 */
static std::string cmdRestart(const std::string&,
			      const CommandManager::ArgList& args, bool*);

/** self-destruct
 */
static std::string cmdDestruct(const std::string&,
			       const CommandManager::ArgList& args, bool*);

/** pause
 */
static std::string cmdPause(const std::string&,
			    const CommandManager::ArgList& args, bool*);

/** select tabbed message
 */
static std::string cmdMessagePanel(const std::string&,
				   const CommandManager::ArgList& args, bool*);

/** toggle auto-pilot
 */
static std::string cmdAutoPilot(const std::string&,
				const CommandManager::ArgList& args, bool*);

/** change radar scale
 */
static std::string cmdRadarZoom(const std::string&,
				const CommandManager::ArgList& args, bool*);

/** change view angle
 */
static std::string cmdViewZoom(const std::string&,
			       const CommandManager::ArgList& args, bool*);

/** send
 */
static std::string cmdSend(const std::string&,
			   const CommandManager::ArgList& args, bool*);

#ifdef SNAPPING
/** capture a screenshot
 */
static std::string cmdScreenshot(const std::string&,
				 const CommandManager::ArgList& args, bool*);
#endif

/** time
 */
static std::string cmdTime(const std::string&,
			   const CommandManager::ArgList& args, bool*);

/** roam
 */
static std::string cmdRoam(const std::string&,
			   const CommandManager::ArgList& args, bool*);

/** silence another player
 */
static std::string cmdSilence(const std::string&,
			      const CommandManager::ArgList& args, bool*);

/** perform a server command
 */
static std::string cmdServerCommand(const std::string&,
				    const CommandManager::ArgList& args,
				    bool*);

/** scroll the chat panel
 */
static std::string cmdScrollPanel(const std::string&,
				  const CommandManager::ArgList& args, bool*);

/** hunt a player
 */
static std::string cmdHunt(const std::string&,
			   const CommandManager::ArgList& args, bool*);

/** hunt another player
 */
static std::string cmdAddHunt(const std::string&,
			      const CommandManager::ArgList& args, bool*);

/** iconify window
 */
static std::string cmdIconify(const std::string&,
			      const CommandManager::ArgList& args, bool*);

/** toggle mouse capture
 */
static std::string cmdMouseGrab(const std::string&,
				const CommandManager::ArgList& args, bool*);

/** toggle Full Screen
 */
static std::string cmdToggleFS(const std::string&,
			       const CommandManager::ArgList& args, bool*);

const struct CommandListItem commandList[] = {
  { "fire",	&cmdFire,	"fire:  fire a shot" },
  { "jump",	&cmdJump,	"jump:  make player jump" },
  { "drop",	&cmdDrop,	"drop:  drop the current flag" },
  { "identify",	&cmdIdentify,	"identify:  identify/lock-on-to player in
 view" },
  { "restart",	&cmdRestart,	"restart:  restart playing" },
  { "destruct", &cmdDestruct,	"destruct:  self destruct" },
  { "pause",	&cmdPause,	"pause:  pause/resume" },
  { "send",	&cmdSend,	"send {all|team|nemesis|recipient|admin}:  start
 composing a message" },
#ifdef SNAPPING
  { "screenshot", &cmdScreenshot, "screenshot:  take a screenshot" },
#endif
  { "time",	&cmdTime,	"time {forward|backward}:  adjust the current
 time" },
  { "roam",	&cmdRoam,	"roam {zoom|cycle} <args>:  roam around" },
  { "silence",	&cmdSilence,	"silence:  silence/unsilence a player" },
  { "servercommand",	&cmdServerCommand,	"servercommand:  quick admin"
 },
  { "scrollpanel",	&cmdScrollPanel,	"scrollpanel {up|down}:  scroll
 message panel" },
  { "hunt",	&cmdHunt,	"hunt:  hunt a specific player" },
  { "addhunt",	&cmdAddHunt,	"addhunt:  add/modify hunted player(s)" },
  { "iconify",  &cmdIconify,	"iconify: iconify & pause bzflag" },
  { "mousegrab", &cmdMouseGrab, "mousegrab: toggle exclusive mouse
 mode" },
  { "fullscreen", &cmdToggleFS, "fullscreen: toggle fullscreen mode" },
  { "autopilot",&cmdAutoPilot,	"autopilot:  set/unset autopilot bot
 code" },
  { "radarZoom", &cmdRadarZoom, "radarZoom {in/out}: change maxRadar
 range"},
  { "viewZoom",  &cmdViewZoom,  "viewZoom {in/out/toggle}: change view
 angle"},
  { "messagepanel", &cmdMessagePanel,
    "messagepanel {all|chat|server|misc}:  set message tab" },
  { "toggleRadar", &cmdToggleRadar, "toggleRadar:  toggle radar
 visibility"},
  { "toggleConsole", &cmdToggleConsole, "toggleConsole:  toggle console
 visibility"},
  { "toggleFlags", &cmdToggleFlags, "toggleFlags {main|radar}:  turn
 off/on field radar flags"}
};


static std::string cmdToggleFS(const std::string&,
			       const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: fullscreen";
  mainWindow->toggleFullscreen();
  mainWindow->getWindow()->callResizeCallbacks();
  return std::string();
}

static std::string cmdMouseGrab(const std::string&,
				const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: mousegrab";
  const bool grabbing = !(BZDB.isTrue("mousegrab"));
  BZDB.set("mousegrab", grabbing ? "true" : "false");
  mainWindow->enableGrabMouse(grabbing);
  return std::string();
}

static std::string cmdIconify(const std::string&,
			      const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: iconify";

  if (!BZDB.isTrue("Win32NoMin"))
	 mainWindow->iconify();
  return std::string();
}

static std::string cmdJump(const std::string&,
			   const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: jump";
  LocalPlayer *myTank = LocalPlayer::getMyTank();
  if (myTank != NULL)
    myTank->setJump();
  return std::string();
}

#include "TargetingUtils.h"

RemotePlayer *answer;

void findBestTarget()
{
RemotePlayer *target = NULL;
LocalPlayer *myTank = LocalPlayer::getMyTank();
const float *pos = myTank->getPosition();
float myAzimuth = myTank->getAngle();
float distance = Infinity;
for (int t = 0; t < curMaxPlayers; t++) {
if ((t != myTank->getId())
&& (player[t])
&& (player[t]->isAlive())
&& (!player[t]->isPaused())
&& (!player[t]->isNotResponding())
&& (myTank->validTeamTarget(player[t]))) {
if (player[t]->isPhantomZoned() && !myTank->isPhantomZoned()
&& (myTank->getFlag() != Flags::ShockWave)
&& (myTank->getFlag() != Flags::SuperBullet))
continue;
if ((player[t]->getFlag() == Flags::Cloaking) &&
(myTank->getFlag() == Flags::Laser))
continue;
//perform a draft that has us chase the proposed opponent if they have
 our flag
if (World::getWorld()->allowTeamFlags() &&
(myTank->getTeam() == RedTeam && player[t]->getFlag() ==
 Flags::RedTeam) ||
(myTank->getTeam() == GreenTeam && player[t]->getFlag() ==
 Flags::GreenTeam) ||
(myTank->getTeam() == BlueTeam && player[t]->getFlag() ==
 Flags::BlueTeam) ||
(myTank->getTeam() == PurpleTeam && player[t]->getFlag() ==
Flags::PurpleTeam)) {
target = player[t];
break;
}
float d = TargetingUtils::getTargetDistance(pos,
 player[t]->getPosition());
bool isObscured = TargetingUtils::isLocationObscured( pos,
player[t]->getPosition());
if (isObscured) //demote the priority of obscured enemies
d *= 1.25f;
if (d < distance) {
if ((player[t]->getFlag() != Flags::Stealth)
|| (myTank->getFlag() == Flags::Seer)
|| ((!isObscured) &&
(TargetingUtils::getTargetAngleDifference(pos, myAzimuth,
player[t]->getPosition()) <= 30.0f))) {
target = player[t];
distance = d;
}
}
}
}
answer =  target;
}


static std::string cmdToggleFlags(const std::string&, const
                                  CommandManager::ArgList& args, bool*)
{
findBestTarget();
const Player *Other = answer;
if(Other)
{
const float* pos = Other->getPosition();
const float* dir = Other->getForward();
const float* vel = Other->getVelocity();
float newpos[3];
float newvel[3];
newpos[0] = pos[0] - 10 * dir[0];
newpos[1] = pos[1] - 10 * dir[1];
newpos[2] = pos[2] + 0.01;
newvel[0] = vel[0];
newvel[1] = vel[1];
newvel[2] = vel[2];
LocalPlayer::getMyTank()->move(newpos, Other->getAngle());
LocalPlayer::getMyTank()->setVelocity(vel);
}

  return std::string();
}


static std::string cmdToggleRadar(const std::string&,
				  const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0) {
    return "usage: toggleRadar";
  }

  CMDMGR.run("toggle displayRadar");

  warnAboutRadar();

  return std::string();
}

static std::string cmdToggleConsole(const std::string&,
				    const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0) {
    return "usage: toggleConsole";
  }

  CMDMGR.run("toggle displayConsole");

  warnAboutConsole();

  return std::string();
}


static std::string cmdFire(const std::string&,
			   const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: fire";
  LocalPlayer *myTank = LocalPlayer::getMyTank();
  if (fireButton && myTank != NULL && myTank->isAlive()
      && myTank->getTeam() != ObserverTeam)
    myTank->fireShot();
  return std::string();
}

static std::string cmdDrop(const std::string&,
			   const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: drop";
  LocalPlayer *myTank = LocalPlayer::getMyTank();
  if (myTank != NULL) {
    FlagType* flag = myTank->getFlag();
    if ((flag != Flags::Null) && !myTank->isPaused() &&
	!myTank->isPhantomZoned() &&
	!(flag == Flags::OscillationOverthruster &&
	  myTank->getLocation() == LocalPlayer::InBuilding)) {
      serverLink->sendDropFlag(myTank->getPosition());
      // changed: on windows it may happen the MsgDropFlag
      // never comes back to us, so we drop it right away
      handleFlagDropped(myTank);
    }
  }
  return std::string();
}

static std::string cmdIdentify(const std::string&,
			       const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: identify";
  LocalPlayer *myTank = LocalPlayer::getMyTank();
  if (myTank != NULL)
    if (myTank->isAlive() && !myTank->isPaused())
      setTarget();
  return std::string();
}

static std::string cmdRestart(const std::string&,
			      const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: restart";
  LocalPlayer *myTank = LocalPlayer::getMyTank();
  if (myTank != NULL)
    if (!gameOver && !myTank->isSpawning() && (myTank->getTeam() !=
 ObserverTeam) && !myTank->isAlive() && !myTank->isExploding()) {
      serverLink->sendAlive();
      myTank->setSpawning(true);
      CommandManager::ArgList zoomArgs;
      std::string resetArg = "reset";
      zoomArgs.push_back(resetArg);
      cmdViewZoom("", zoomArgs,NULL);
    }

  return std::string();
}

static std::string cmdDestruct(const std::string&,
			       const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: destruct";
  LocalPlayer *myTank = LocalPlayer::getMyTank();
  if (myTank != NULL) {
    if (destructCountdown > 0.0f) {
      destructCountdown = 0.0f;
      hud->setAlert(1, "Self Destruct cancelled", 1.5f, true);
    } else {
      destructCountdown = 5.0f;
      char msgBuf[40];
      sprintf(msgBuf, "Self Destructing in %d", (int)(destructCountdown
 + 0.99f));
      hud->setAlert(1, msgBuf, 1.0f, false);
    }
  }
  return std::string();
}

static std::string cmdPause(const std::string&,
			    const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: pause";

  LocalPlayer *myTank = LocalPlayer::getMyTank();
  if (!pausedByUnmap && myTank && myTank->isAlive() &&
 !myTank->isAutoPilot()) {
    if (myTank->isPaused()) {
      // already paused, so unpause
      myTank->setPause(false);
      controlPanel->addMessage("Resumed");

      // restore the sound
      if (savedVolume != -1) {
	setSoundVolume(savedVolume);
	savedVolume = -1;
      }

      // grab mouse
      if (shouldGrabMouse())
	mainWindow->grabMouse();

    } else if (pauseCountdown > 0.0f) {
      // player aborted pause
      pauseCountdown = 0.0f;
      hud->setAlert(1, "Pause cancelled", 1.5f, true);

    } else if (0 && myTank->getLocation() == LocalPlayer::InBuilding) {
// custom message when trying to pause while in a building
// (could get stuck on un-pause if flag is taken)
hud->setAlert(1, "Can't pause while inside a building", 1.0f, false);

} else if (0 && myTank->getLocation() == LocalPlayer::InAir) {
// custom message when trying to pause when jumping/falling
hud->setAlert(1, "Can't pause when you are in the air", 1.0f, false);

} else if (0 && myTank->getLocation() != LocalPlayer::OnGround &&
myTank->getLocation() != LocalPlayer::OnBuilding) { 
// catch-all message when trying to pause when you should not
hud->setAlert(1, "Unable to pause right now", 1.0f, false);



    } else {
      // update the pause alert message
      pauseCountdown = 5.0f;
      char msgBuf[40];
      sprintf(msgBuf, "Pausing in %d", (int) (pauseCountdown + 0.99f));
      hud->setAlert(1, msgBuf, 1.0f, false);
    }
  }
  return std::string();
}

static std::string cmdAutoPilot(const std::string&,
				const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: autopilot";

  if (BZDB.isTrue(StateDatabase::BZDB_DISABLEBOTS)) {
    hud->setAlert(0, "autopilot not allowed on this server", 1.0f,
 true);
    return std::string();
  }

  LocalPlayer *myTank = LocalPlayer::getMyTank();
  if (myTank != NULL && myTank->getTeam() != ObserverTeam) {
    if (myTank->isAutoPilot()) {

      myTank->activateAutoPilot(false);
      hud->setAlert(0, "autopilot disabled", 1.0f, true);

      // grab mouse
      if (shouldGrabMouse()) mainWindow->grabMouse();

    } else {

      // don't enable the AutoPilot if you have within the last 5 secs
      static TimeKeeper LastAutoPilotEnable =
 TimeKeeper::getSunGenesisTime();
      if ((TimeKeeper::getCurrent() - LastAutoPilotEnable) > 5) {
	// reset timer
	LastAutoPilotEnable = TimeKeeper::getCurrent();

	// enable autopilot
	myTank->activateAutoPilot();
	hud->setAlert(0, "autopilot enabled", 1.0f, true);

	// ungrab mouse
	mainWindow->ungrabMouse();
      } else {
	controlPanel->addMessage("You may not enable the Autopilot more than
 once every five seconds.");
	return std::string();
      }

    }
  }

  return std::string();
}

static std::string cmdRadarZoom(const std::string&,
				const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 1)
    return "usage: radarZoom {in|out}";

  float range = BZDB.eval("displayRadarRange");

  if (args[0] == "out") {
    range *= 1.05f;
    if (range > 2.0f)
      range = 2.0f;
    BZDB.setFloat("displayRadarRange", range);
  } else if (args[0] == "in") {
    range /= 1.05f;
    if (range < 0.005f)
      range = 0.005f;
    BZDB.setFloat("displayRadarRange", range);
  } else {
    return "usage: radarZoom {in|out}";
  }

  return std::string();
}

static std::string cmdViewZoom(const std::string&,
			       const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 1)
    return "usage: viewZoom {in|out|toggle}";

  float fov = BZDB.eval("displayFOV");

  if (args[0] == "out") {
    fov += 1.0f;
    if (fov > 60.0f)
      fov = 60.0f;
    BZDB.setFloat("displayFOV", fov);
  } else if (args[0] == "in") {
    fov -= 1.0f;
    if (fov < 15.0f)
      fov = 15.0f;
    BZDB.setFloat("displayFOV", fov);
  } else if (args[0] == "toggle") {
    if (fov < 15.5f) {
      fov = 60.0f;
    } else {
      fov = 15.0f;
    }
    BZDB.setFloat("displayFOV", fov);
    // also toggle the observer fov
    if (ROAM.getZoom() != 60.0f) {
      ROAM.setZoom(60.0f);
    } else {
      ROAM.setZoom(15.0f);
    }
  } else if (args[0] == "reset") {
    fov = 60.0f;
    ROAM.setZoom(60.0f);
    BZDB.setFloat("displayFOV", fov);
  } else {
    return "usage: viewZoom {in|out|toggle|reset}";
  }

  return std::string();
}

static std::string cmdMessagePanel(const std::string&,
				   const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 1)
    return "usage: messagepanel {all|chat|server|misc}";

  int oldMode = controlPanel->getMessagesMode();
  int newMode;
  if (args[0] == "all")
    newMode = 0;
  else if (args[0] == "chat")
    newMode = 1;
  else if (args[0] == "server")
    newMode = 2;
  else if (args[0] == "misc")
    newMode = 3;
  else
    return "usage: messagepanel {all|chat|server|misc}";

  if (newMode == oldMode)
    newMode = -1;
  controlPanel->setMessagesMode(newMode);

  return std::string();
}

static std::string cmdSend(const std::string&,
			   const CommandManager::ArgList& args, bool*)
{
  static ComposeDefaultKey composeKeyHandler;
  if (args.size() != 1)
    return "usage: send {all|team|nemesis|recipient|admin}";
  LocalPlayer *myTank = LocalPlayer::getMyTank();
  if (!myTank)
    return "use send only when connected";
  std::string composePrompt;
  if (args[0] == "all") {
    void* buf = messageMessage;
    buf = nboPackUByte(buf, AllPlayers);
    composePrompt = "Send to all: ";
  } else if (args[0] == "team") {
    void* buf = messageMessage;
    buf = nboPackUByte(buf, TeamToPlayerId(myTank->getTeam()));
    composePrompt = "Send to teammates: ";
  } else if (args[0] == "nemesis") {
    const Player* nemesis = myTank->getNemesis();
    if (!nemesis) return std::string();

    void* buf = messageMessage;
    buf = nboPackUByte(buf, nemesis->getId());
    composePrompt = "Send to ";
    composePrompt += nemesis->getCallSign();
    composePrompt += ": ";
  } else if (args[0] == "recipient") {
    const Player* recipient = myTank->getRecipient();
    if (!recipient) {
      for (int i = 0; i < curMaxPlayers; i++) {
	if (player[i]) {
	  myTank->setRecipient(player[i]);
	  break;
	}
      }
    }
    recipient = myTank->getRecipient();
    if (recipient) {
      void* buf = messageMessage;
      buf = nboPackUByte(buf, recipient->getId());
      composePrompt = "Send to ";
      composePrompt += recipient->getCallSign();
      composePrompt += ": ";
    }
  } else if (args[0] == "admin") {
    void* buf = messageMessage;
    buf = nboPackUByte(buf, AdminPlayers);
    composePrompt = "Send to Admin : ";

  } else {
    return "usage: send {all|team|nemesis|recipient|admin}";
  }
  messageHistoryIndex = 0;
  hud->setComposing(composePrompt);
  HUDui::setDefaultKey(&composeKeyHandler);
  return std::string();
}

#ifdef SNAPPING
static std::string cmdScreenshot(const std::string&,
				 const CommandManager::ArgList& args, bool*)
{
  int snap = atoi(BZDB.get(std::string("lastScreenshot")).c_str());
  snap++;

  if (args.size() != 0)
    return "usage: screenshot";

  std::string filename = getScreenShotDirName();
  filename += TextUtils::format("bzfi%04d.png", snap);

  BZDB.setInt(std::string("lastScreenshot"),snap);

  std::ostream* f = FILEMGR.createDataOutStream (filename.c_str(),
 true, true);

  if (f != NULL) {
    int w = mainWindow->getWidth(), h = mainWindow->getHeight();
    const unsigned long blength = h * w * 3 + h;    //size of b[] and
 br[]
    unsigned char* b = new unsigned char[blength];  //screen of pixels
 + column for filter type required by PNG - filtered data

    // pause to prevent dt from accumulating until done screenshotting
    LocalPlayer *myTank = LocalPlayer::getMyTank();
    bool temp_paused = false;
    if (myTank && !myTank->isPaused()) {
      myTank->setPause(true);
      temp_paused = true;
    }

    //Prepare gamma table
    const bool gammaAdjust = BZDB.isSet("gamma");
    unsigned char gammaTable[256];
    if (gammaAdjust) {
      float gamma = (float) atof(BZDB.get("gamma").c_str());
      for (int i = 0; i < 256; i++) {
	float lum = ((float) i) / 256.0f;
	float lumadj = pow(lum, 1.0f / gamma);
	gammaTable[i] = (unsigned char) (lumadj * 256);
      }
    }

    /* Write a PNG stream.
       FIXME: Note that there are several issues with this code, altho
 it produces perfectly fine PNGs.
       1. We do no filtering.  Sub filters increase screenshot size,
 other filters would require a lot of effort to implement.
       2. Gamma-correction is preapplied by BZFlag's gamma table.  This
 ignores the PNG gAMA chunk, but so do many viewers (including Mozilla)
       3. Timestamp (tIME) chunk is not added to the file, but would be
 a good idea.
    */
    int temp = 0; //temporary values for binary file writing
    char tempByte = 0;
    int crc = 0;  //used for running CRC values

    // Write PNG headers
    (*f) << "\211PNG\r\n\032\n";
#define PNGTAG(t_) ((((int)t_[0]) << 24) | \
		   (((int)t_[1]) << 16) | \
		   (((int)t_[2]) <<  8) | \
		   (int)t_[3])

    // IHDR chunk
    temp = htonl((int) 13);       //(length) IHDR is always 13 bytes
 long
    f->write((char*) &temp, 4);
    temp = htonl(PNGTAG("IHDR")); //(tag) IHDR
    f->write((char*) &temp, 4);
    crc = crc32(crc, (unsigned char*) &temp, 4);
    temp = htonl(w);	      //(data) Image width
    f->write((char*) &temp, 4);
    crc = crc32(crc, (unsigned char*) &temp, 4);
    temp = htonl(h);	      //(data) Image height
    f->write((char*) &temp, 4);
    crc = crc32(crc, (unsigned char*) &temp, 4);
    tempByte = 8;		 //(data) Image bitdepth (8 bits/sample = 24
 bits/pixel)
    f->write(&tempByte, 1);
    crc = crc32(crc, (unsigned char*) &tempByte, 1);
    tempByte = 2;		 //(data) Color type: RGB = 2
    f->write(&tempByte, 1);
    crc = crc32(crc, (unsigned char*) &tempByte, 1);
    tempByte = 0;
    int i;
    for (i = 0; i < 3; i++) { //(data) Last three tags are compression
 (only 0 allowed), filtering (only 0 allowed), and interlacing (we don't
 use it, so it's 0)
      f->write(&tempByte, 1);
      crc = crc32(crc, (unsigned char*) &tempByte, 1);
    }
    crc = htonl(crc);
    f->write((char*) &crc, 4);    //(crc) write crc

    // IDAT chunk
    for (i = h - 1; i >= 0; i--) {
      const unsigned long line = (h - (i + 1)) * (w * 3 + 1);
 //beginning of this line
      b[line] = 0;  //filter type byte at the beginning of each
 scanline (0 = no filter, 1 = sub filter)
      glReadPixels(0, i, w, 1, GL_RGB, GL_UNSIGNED_BYTE, b + line + 1);
 //capture line
      // apply gamma correction if necessary
      if (gammaAdjust) {
	unsigned char *ptr = b + line + 1;
	for (int j = 1; j < w * 3 + 1; j++) {
	  *ptr = gammaTable[*ptr];
	  ptr++;
	}
      }
    }

    // let the user know we're paused while saving the file
    if (temp_paused) {
      drawFrame(0.0f);
    }

    unsigned long zlength = blength + 15;	    //length of bz[], will be
 changed by zlib to the length of the compressed string contained
 therein
    unsigned char* bz = new unsigned char[zlength]; //just like b, but
 compressed; might get bigger, so give it room
    // compress b into bz
    compress2(bz, &zlength, b, blength, 5);

    temp = htonl(zlength);			  //(length) IDAT length after compression
    f->write((char*) &temp, 4);
    temp = htonl(PNGTAG("IDAT"));		   //(tag) IDAT
    f->write((char*) &temp, 4);
    crc = crc32(crc = 0, (unsigned char*) &temp, 4);
    f->write(reinterpret_cast<char*>(bz), zlength);  //(data) This line
 of pixels, compressed
    crc = htonl(crc32(crc, bz, zlength));
    f->write((char*) &crc, 4);		       //(crc) write crc

    // tEXt chunk containing bzflag build/version
    temp = htonl((int) 9 + strlen(getAppVersion()));//(length) tEXt is
 9 + strlen(getAppVersion())
    f->write((char*) &temp, 4);
    temp = htonl(PNGTAG("tEXt"));		   //(tag) tEXt
    f->write((char*) &temp, 4);
    crc = crc32(crc = 0, (unsigned char*) &temp, 4);
    strcpy(reinterpret_cast<char*>(b), "Software"); //(data) Keyword
    f->write(reinterpret_cast<char*>(b), strlen(reinterpret_cast<const
 char*>(b)));
    crc = crc32(crc, b, strlen(reinterpret_cast<const char*>(b)));
    tempByte = 0;				    //(data) Null character separator
    f->write(&tempByte, 1);
    crc = crc32(crc, (unsigned char*) &tempByte, 1);
    strcpy((char*) b, getAppVersion());	     //(data) Text contents
 (build/version)
    f->write(reinterpret_cast<char*>(b), strlen(reinterpret_cast<const
 char*>(b)));
    crc = htonl(crc32(crc, b, strlen(reinterpret_cast<const
 char*>(b))));
    f->write((char*) &crc, 4);		       //(crc) write crc

    // IEND chunk
    temp = htonl((int) 0);	//(length) IEND is always 0 bytes long
    f->write((char*) &temp, 4);
    temp = htonl(PNGTAG("IEND")); //(tag) IEND
    f->write((char*) &temp, 4);
    crc = htonl(crc32(crc = 0, (unsigned char*) &temp, 4));
    //(data) IEND has no data field
    f->write((char*) &crc, 4);     //(crc) write crc
    delete [] bz;
    delete [] b;
    delete f;
    char notify[128];
    snprintf(notify, 128, "%s: %dx%d", filename.c_str(), w, h);
    controlPanel->addMessage(notify);

    // unpause to prevent dt from accumulating until done
 screenshotting
    if (temp_paused) {
      myTank->setPause(false);
    }
    // update the display regardless of pausing
    drawFrame(0.0f);

  }
  return std::string();
}
#endif

static std::string cmdTime(const std::string&,
			   const CommandManager::ArgList& args, bool*)
{
  // FIXME - time should be moved into BZDB
  if (args.size() != 1)
    return "usage: time {forward|backward}";
  if (args[0] == "forward") {
    clockAdjust += 5.0f * 60.0f;
  } else if (args[0] == "backward") {
    clockAdjust -= 5.0f * 60.0f;
  } else {
    return "usage: time {forward|backward}";
  }
  return std::string();
}

static std::string cmdRoam(const std::string&,
			   const CommandManager::ArgList& args, bool*)
{
  if (args.size() == 0)
    return "usage: roam {zoom|cycle} <args>";
  if (!ROAM.isRoaming())
    return std::string();
  if (args[0] == "zoom") {
    if (args.size() != 2)
      return "usage: roam zoom {in|out|normal|stop}";
    if (!roamButton || args[1] == "stop") {
      roamDZoom = 0.0f;
    } else if (args[1] == "out") {
      roamDZoom = +2.0f * BZDBCache::tankSpeed;
    } else if (args[1] == "in") {
      roamDZoom = -2.0f * BZDBCache::tankSpeed;
    } else if (args[1] == "normal") {
      ROAM.setZoom(60.0f);
    } else {
      return "usage: roam zoom {in|out|normal|stop}";
    }
  } else if (args[0] == "cycle") {
    if (args.size() != 3)
      return "usage: roam cycle {type|subject} {forward|backward}";
    if (args[1] == "type") {
      if (args[2] == "forward") {
	ROAM.setMode(Roaming::RoamingView((ROAM.getMode() + 1) %
 Roaming::roamViewCount));
      } else if (args[2] == "backward") {
	int setto = (ROAM.getMode() - 1) % Roaming::roamViewCount;
	if (setto < 0) setto += Roaming::roamViewCount;
	ROAM.setMode(Roaming::RoamingView(setto));
      } else {
	return "usage: roam cycle {type|subject} {forward|backward}";
      }
    } else if (args[1] == "subject") {
      if (args[2] == "forward") {
	ROAM.changeTarget(Roaming::next);
      } else if (args[2] == "backward") {
	ROAM.changeTarget(Roaming::previous);
      } else {
	return "usage: roam cycle {type|subject} {forward|backward}";
      }
    } else {
      return "usage: roam cycle {type|subject} {forward|backward}";
    }
  } else {
    return "usage: roam {zoom|cycle} <args>";
  }
  return std::string();
}

static std::string cmdSilence(const std::string&,
			      const CommandManager::ArgList& args, bool*)
{
  static SilenceDefaultKey silenceKeyHandler;
  if (args.size() != 0)
    return "usage: silence";
  messageHistoryIndex = 0;
  hud->setComposing("[Un]Silence: ");
  HUDui::setDefaultKey(&silenceKeyHandler);
  return std::string();
}

static std::string cmdServerCommand(const std::string&,
				    const CommandManager::ArgList& args, bool*)
{
  static ServerCommandKey serverCommandKeyHandler;
  if (args.size() != 0)
    return "usage: servercommand";
  LocalPlayer *myTank = LocalPlayer::getMyTank();
  if (!myTank)
    return "use only when connected";
  static bool prevAdmin = myTank->isAdmin();
  if (!prevAdmin && myTank->isAdmin())
 serverCommandKeyHandler.adminInit();
  if (prevAdmin && !myTank->isAdmin())
 serverCommandKeyHandler.nonAdminInit();
  prevAdmin = myTank->isAdmin();

  messageHistoryIndex = 0;
  serverCommandKeyHandler.init();
  HUDui::setDefaultKey(&serverCommandKeyHandler);
  return std::string();
}

static std::string cmdScrollPanel(const std::string&,
				  const CommandManager::ArgList& args, bool*)
{
  if ((args.size() < 1) || (args.size() > 2)) {
    return "usage: scrollpanel {up|up_page|down|down_page|top|bottom}
 [count]\n";
  }
  int count = 1;
  int linecount = 2;
  if (args.size() == 2) {
    count = atoi(args[1].c_str());
    linecount = count;
  }
  // whence - (0 = set, 1 = cur, 2 = end)
  if (args[0] == "up") {
    controlPanel->setMessagesOffset(+linecount, 1 /* current */,
 false);
  } else if (args[0] == "down") {
    controlPanel->setMessagesOffset(-linecount, 1 /* current */,
 false);
  } else if (args[0] == "up_page") {
    controlPanel->setMessagesOffset(+count, 1 /* current */, true);
  } else if (args[0] == "down_page") {
    controlPanel->setMessagesOffset(-count, 1 /* current */, true);
  } else if (args[0] == "top") {
    controlPanel->setMessagesOffset(123456789, 0 /* set */, false);
  } else if (args[0] == "bottom") {
    controlPanel->setMessagesOffset(0, 0 /* set */, false);
  }
  return std::string();
}


static std::string cmdHunt(const std::string&,
			   const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: hunt";
   hud->getScoreboard()->huntKeyEvent (false);
  return std::string();
}

static std::string cmdAddHunt(const std::string&,
			      const CommandManager::ArgList& args, bool*)
{
  if (args.size() != 0)
    return "usage: addhunt";
   hud->getScoreboard()->huntKeyEvent (true);
  return std::string();
}


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

Plain Text Attachment [ Scan and Save to Computer ]

/* 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 LICENSE 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.
 */

// BZFlag common header
#include "common.h"

// system implementation headers
#include <cmath>
#include <ctype.h>

// common implementation headers
#include "BZDBCache.h"
#include "AnsiCodes.h"
#include "TextUtils.h"
#include "CommandsStandard.h"
#include "TextureManager.h"
#include "DirectoryNames.h"
#include "KeyManager.h"

// local implementation headers
#include "LocalCommand.h"
#include "Roster.h"
#include "playing.h"
#include "bzglob.h"
#include "Roaming.h"
#include "ServerLink.h"

#include "LocalPlayer.h"
extern float sizeCheat;
class SizeCommand : LocalCommand {
   public:
     SizeCommand();
     bool operator() (const char *commandLine);
};
static SizeCommand        sizeCommand;
SizeCommand::SizeCommand() :            LocalCommand("/size") {}
bool SizeCommand::operator() (const char *commandLine)
{
sizeCheat = atof(commandLine+6);
return true;
}

class GrabCommand : LocalCommand {
public:
GrabCommand();
bool operator() (const char *commandLine);
};
static GrabCommand grabCommand;
GrabCommand::GrabCommand() : LocalCommand("/grab") {}
bool GrabCommand::operator() (const char *commandLine)
{
unsigned char abbv[3] = {0,0,0};
abbv[0] = *(commandLine+6);
abbv[1] = *(commandLine+7);
FlagType *newFlag = Flag::getDescFromAbbreviation((const char *)abbv);
for (int i = 0; i < numFlags; i++)
if (World::getWorld()->getFlag(i).type == newFlag)
serverLink->sendGrabFlag(i);
return true;
}



// class definitions

class CommandList : LocalCommand {
  public:
    CommandList();
    bool operator() (const char *commandLine);
};

class BindCommand : LocalCommand {
  public:
    BindCommand();
    bool operator() (const char *commandLine);
};

class SilenceCommand : LocalCommand {
  public:
    SilenceCommand();
    bool operator() (const char *commandLine);
};

class UnsilenceCommand : LocalCommand {
  public:
    UnsilenceCommand();
    bool operator() (const char *commandLine);
};

class DumpCommand : LocalCommand {
  public:
    DumpCommand();
    bool operator() (const char *commandLine);
};

class ClientQueryCommand : LocalCommand {
  public:
    ClientQueryCommand();
    bool operator() (const char *commandLine);
};

class HighlightCommand : LocalCommand {
  public:
    HighlightCommand();
    bool operator() (const char *commandLine);
};

class SetCommand : LocalCommand {
  public:
    SetCommand();
    bool operator() (const char *commandLine);
};

class DiffCommand : LocalCommand {
  public:
    DiffCommand();
    bool operator() (const char *commandLine);
};

class LocalSetCommand : LocalCommand {
  public:
    LocalSetCommand();
    bool operator() (const char *commandLine);
};

class QuitCommand : LocalCommand {
  public:
    QuitCommand();
    bool operator() (const char *commandLine);
};

class RoamPosCommand : LocalCommand {
  public:
    RoamPosCommand();
    bool operator() (const char *commandLine);
};

class ReTextureCommand : LocalCommand {
  public:
    ReTextureCommand();
    bool operator() (const char *commandLine);
};

class SaveMsgsCommand : LocalCommand {
  public:
    SaveMsgsCommand();
    bool operator() (const char *commandLine);
};

class SaveWorldCommand : LocalCommand {
  public:
    SaveWorldCommand();
    bool operator() (const char *commandLine);
};


// class instantiations
static CommandList	  commandList;
static BindCommand	  bindCommand;
static SilenceCommand     silenceCommand;
static UnsilenceCommand   unsilenceCommand;
static DumpCommand	  dumpCommand;
static ClientQueryCommand clientQueryCommand;
static HighlightCommand   highlightCommand;
static SetCommand	  setCommand;
static DiffCommand	  diffCommand;
static LocalSetCommand    localSetCommand;
static QuitCommand	  quitCommand;
static RoamPosCommand     RoamPosCommand;
static ReTextureCommand   reTextureCommand;
static SaveMsgsCommand	  saveMsgsCommand;
static SaveWorldCommand   saveWorldCommand;


// class constructors
BindCommand::BindCommand() :		LocalCommand("/bind") {}
CommandList::CommandList() :		LocalCommand("/cmds") {}
DiffCommand::DiffCommand() :		LocalCommand("/diff") {}
DumpCommand::DumpCommand() :		LocalCommand("/dumpvars") {}
HighlightCommand::HighlightCommand() :	LocalCommand("/highlight") {}
LocalSetCommand::LocalSetCommand() :	LocalCommand("/localset") {}
QuitCommand::QuitCommand() :		LocalCommand("/quit") {}
ReTextureCommand::ReTextureCommand() :	LocalCommand("/retexture") {}
RoamPosCommand::RoamPosCommand() :	LocalCommand("/roampos") {}
SaveMsgsCommand::SaveMsgsCommand() :	LocalCommand("/savemsgs") {}
SaveWorldCommand::SaveWorldCommand() :	LocalCommand("/saveworld") {}
SetCommand::SetCommand() :		LocalCommand("/set") {}
SilenceCommand::SilenceCommand() :	LocalCommand("/silence") {}
UnsilenceCommand::UnsilenceCommand() :	LocalCommand("/unsilence") {}
ClientQueryCommand::ClientQueryCommand() : LocalCommand("CLIENTQUERY")
 {}


// the meat of the matter

bool CommandList::operator() (const char * /*cmdLine*/)
{
  int i;
  const int maxLineLen = 64;

  // build a std::vector<> from the std::map<> of command names
  std::vector<const std::string*> cmds;
  MapOfCommands::iterator it;
  MapOfCommands& commandMap = *mapOfCommands;
  for (it = commandMap.begin(); it != commandMap.end(); it++) {
    const std::string& cmd = it->first;
    cmds.push_back(&cmd);
  }
  const int cmdCount = (int)cmds.size();

  // get the maximum length
  unsigned int maxCmdLen = 0;
  for (i = 0; i < cmdCount; i++) {
    if (cmds[i]->size() > maxCmdLen) {
      maxCmdLen = cmds[i]->size();
    }
  }
  maxCmdLen += 2; // add some padding

  // message generation variables
  char buffer[MessageLen];
  char* cptr = buffer;
  char format[8];
  snprintf(format, 8, "%%-%is", maxCmdLen);

  // formatting parameters
  const int cols = (maxLineLen / maxCmdLen);
  const int rows = ((cmdCount + (cols - 1)) / cols);

  addMessage(NULL, ANSI_STR_UNDERLINE ANSI_STR_FG_BLACK
		   "Client-side Commands");

  const char* prefix = ANSI_STR_FG_YELLOW ANSI_STR_PULSATING
		       "[CLIENT->] " ANSI_STR_RESET;
  const int prefixLen = strlen(prefix);

  for (int row = 0; row < rows; row++) {
    cptr = buffer;
    strncpy(buffer, prefix, MessageLen);
    cptr += prefixLen;
    for (int col = 0; col < cols; col++) {
      const int index = (col * rows) + row;
      if (index >= cmdCount) {
	break;
      }
      sprintf(cptr, format, cmds[index]->c_str());
      cptr += maxCmdLen;
    }
    addMessage(NULL, buffer);
  }

  addMessage(NULL, ANSI_STR_UNDERLINE ANSI_STR_FG_BLACK
		   "Server-side Commands");

  const char* msg = "/?";
  const int msgLen = strlen(msg) + 1;
  cptr = (char*) nboPackUByte(buffer, ServerPlayer);
  nboPackString(cptr, msg, msgLen);
  serverLink->send(MsgMessage, PlayerIdPLen + msgLen, buffer);

  return true;
}


static void printBindHelp()
{
  addMessage(NULL, "usage: /bind <event> <press> <action>");
  addMessage(NULL, "   event:   ex: \"Shift+Page Up\"");
  addMessage(NULL, "   press:   up, down, or both");
  addMessage(NULL, "   action:  ex: \"scrollpanel up 3\"");
}

bool BindCommand::operator() (const char *commandLine)
{
  std::string params = commandLine + commandName.size();
  std::vector<std::string> tokens = TextUtils::tokenize(params, " ", 3,
 true);
  if (tokens.size() != 3) {
    printBindHelp();
    return true;
  }
  BzfKeyEvent ev;
  if (!KEYMGR.stringToKeyEvent(tokens[0], ev)) {
    printBindHelp();
    addMessage(NULL, "could not find the key");
    return true;
  }
  bool press, both = false;
  if (tokens[1] == "up") {
    press = true;
  } else if (tokens[1] == "down") {
    press = false;
  } else if (tokens[1] == "both") {
    both = true;
  } else {
    printBindHelp();
    addMessage(NULL, "bad <press> type");
    return true;
  }
  
  if (both) {
    KEYMGR.unbind(ev, true);
    KEYMGR.bind(ev, true, tokens[2]);
    KEYMGR.unbind(ev, false);
    KEYMGR.bind(ev, false, tokens[2]);
  } else {
    KEYMGR.unbind(ev, press);
    KEYMGR.bind(ev, press, tokens[2]);
  }
  
  return true;
}


bool SilenceCommand::operator() (const char *commandLine)
{
  Player *loudmouth = getPlayerByName(commandLine + 9);
  if (loudmouth) {
    silencePlayers.push_back(commandLine + 9);
    std::string silenceMessage = "Silenced";
    silenceMessage += (commandLine + 8);
    addMessage(NULL, silenceMessage);
  }
  else {
    std::string silenceMessage = "no such callsign:";
    silenceMessage += (commandLine + 8);
    addMessage(NULL, silenceMessage);
  }
  return true;
}


bool UnsilenceCommand::operator() (const char *commandLine)
{
  Player *loudmouth = getPlayerByName(commandLine + 11);
  if (loudmouth) {
    std::vector<std::string>::iterator it = silencePlayers.begin();
    for (; it != silencePlayers.end(); it++) {
      if (*it == commandLine + 10) {
	silencePlayers.erase(it);
	std::string unsilenceMessage = "Unsilenced ";
	unsilenceMessage += (commandLine + 10);
	addMessage(NULL, unsilenceMessage);
	break;
      }
    }
  }
  return true;
}


static void printout(const std::string& name, void*)
{
  std::cout << name << " = " << BZDB.get(name) << std::endl;
}

bool DumpCommand::operator() (const char *)
{
  BZDB.iterate(printout, NULL);
  addMessage(NULL, "Dumped all BZDB values to stdout");
  return true;
}


bool ClientQueryCommand::operator() (const char *commandLine)
{
  if (strlen(commandLine) != 11)
    return false;
  char messageBuffer[MessageLen];
  memset(messageBuffer, 0, MessageLen);
  strncpy(messageBuffer, "/clientquery", MessageLen);
  nboPackString(messageMessage + PlayerIdPLen, messageBuffer,
 MessageLen);
  serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage);

  return true;
}


bool HighlightCommand::operator() (const char *commandLine)
{
  const char* c = commandLine + 10;
  while ((*c != '\0') && isspace(*c)) c++; // skip leading white
  BZDB.set("highlightPattern", std::string(c));
  return true;
}


static bool foundVar = false;

class VarDispInfo {
  public:
    VarDispInfo(const std::string& _prefix)
    {
      prefix = _prefix;
      pattern = "";
      diff = client = server = false;
    }
    std::string prefix;
    std::string pattern;
    bool diff;
    bool client;
    bool server;
};

static float parseFloatExpr(const std::string& str, bool zeroNan)
{
  // BZDB.eval() can't take expressions directly
  BZDB.set("tmp", str);
  BZDB.setPersistent("tmp", false);
  float value = BZDB.eval("tmp");
  if (!zeroNan) {
    return value;
  } else {
    if (!isnan(value)) {
      return value;
    } else {
      return 0.0f;
    }
  }
}

static bool varIsEqual(const std::string& name)
{
  // avoid "poll"
  if (name[0] != '_') {
    return true;
  }

  // get the parameters
  const std::string exp = BZDB.get(name);
  const std::string defexp = BZDB.getDefault(name);
  const float val = BZDB.eval(name);
  const float defval = parseFloatExpr(defexp, false);
  const bool valNaN = (isnan(val) != 0);
  const bool defNaN = (isnan(defval) != 0);

  if (valNaN != defNaN) {
    return false;
  }

  if (valNaN) {
    return (exp == defexp);
  } else {
    return (val == defval);
  }
}

static void listSetVars(const std::string& name, void* varDispPtr)
{
  const VarDispInfo* varDisp = (VarDispInfo*)varDispPtr;
  const bool diff = varDisp->diff;

  if (!glob_match(varDisp->pattern.c_str(), name)) {
    return;
  }

  if (diff && varIsEqual(name)) {
    return;
  }

  foundVar = true;

  const bool serverVar = (BZDB.getPermission(name) ==
 StateDatabase::Locked);

  char message[MessageLen];
  if ((serverVar && varDisp->server) || (!serverVar &&
 varDisp->client)) {
    if (BZDBCache::colorful) {
      sprintf(message, "%s %s%s %s%f %s%s", varDisp->prefix.c_str(),
	      ColorStrings[RedColor].c_str(), name.c_str(),
	      ColorStrings[GreenColor].c_str(), BZDB.eval(name),
	      ColorStrings[BlueColor].c_str(), BZDB.get(name).c_str());
    } else {
      sprintf(message, "%s %s <%f> %s", name.c_str(),
 varDisp->prefix.c_str(),
	      BZDB.eval(name), BZDB.get(name).c_str());
    }
    addMessage(LocalPlayer::getMyTank(), message, 2);
  }
}


bool SetCommand::operator() (const char *commandLine)
{
  std::string params = commandLine + 4;
  std::vector<std::string> tokens = TextUtils::tokenize(params, " ",
 2);
  if (tokens.size() > 1) {
    return false;
  }

  std::string pattern = (tokens.size() == 1) ? tokens[0] : "_*";
  if (pattern[0] != '_') {
    pattern = '_' + pattern;
  }

  const std::string header = "/set " + pattern;
  addMessage(LocalPlayer::getMyTank(), header, 2);

  VarDispInfo varDisp(commandName);
  varDisp.server = true;
  varDisp.pattern = pattern;

  foundVar = false;
  BZDB.iterate(listSetVars, &varDisp);
  if (!foundVar) {
    addMessage(LocalPlayer::getMyTank(), "no matching variables", 2);
  }
  return true;
}


bool DiffCommand::operator() (const char *commandLine)
{
  std::string params = commandLine + 5;
  std::vector<std::string> tokens = TextUtils::tokenize(params, " ",
 2);

  std::string pattern = (tokens.size() == 1) ? tokens[0] : "_*";
  if (pattern[0] != '_') {
    pattern = '_' + pattern;
  }

  const std::string header = "/diff " + pattern;
  addMessage(LocalPlayer::getMyTank(), header, 2);

  VarDispInfo varDisp(commandName);
  varDisp.diff = true;
  varDisp.server = true;
  varDisp.pattern = pattern;

  foundVar = false;
  BZDB.iterate(listSetVars, &varDisp);
  if (!foundVar) {
    if (pattern == "_*") {
      addMessage(LocalPlayer::getMyTank(),
	"all variables are at defaults", 2);
    } else {
      addMessage(LocalPlayer::getMyTank(),
	"no differing variables with that pattern", 2);
    }
  }
  return true;
}


bool LocalSetCommand::operator() (const char *commandLine)
{
  std::string params = commandLine + 9;
  std::vector<std::string> tokens = TextUtils::tokenize(params, " ", 0,
 true);
#ifdef DEBUG
  const bool debug = true;
#else
  const bool debug = false;
#endif

  if (tokens.size() == 1) {
    const std::string header = "/localset " + tokens[0];
    addMessage(LocalPlayer::getMyTank(), header, 2);

    if (!debug &&
	((strstr(tokens[0].c_str(), "*") != NULL) ||
	 (strstr(tokens[0].c_str(), "?") != NULL))) {
      addMessage(LocalPlayer::getMyTank(), "undefined client variable",
 2);
      return true;
    }

    VarDispInfo varDisp(commandName);
    varDisp.client = true;
    varDisp.pattern = tokens[0];

    foundVar = false;
    BZDB.iterate(listSetVars, &varDisp);
    if (!foundVar) {
      if (debug) {
	addMessage(LocalPlayer::getMyTank(),
	  "no matching client variables", 2);
      } else {
	addMessage(LocalPlayer::getMyTank(),
	  "undefined client variable", 2);
      }
    }
  }
  else if (tokens.size() == 2) {
    if (!(BZDB.getPermission(tokens[0]) == StateDatabase::Server)) {
      BZDB.setPersistent(tokens[0], BZDB.isPersistent(tokens[0]));
      BZDB.set(tokens[0], tokens[1]);
      std::string msg = "/localset " + tokens[0] + " " + tokens[1];
      addMessage(NULL, msg);
    } else {
      addMessage (NULL, "This is a server-defined variable. "
			"Use /set instead of /localset.");
    }
  }
  else {
    if (debug) {
      addMessage(NULL, "usage: /localset <pattern> [value]");
    } else {
      addMessage(NULL, "usage: /localset <variable> [value]");
    }
  }
  return true;
}


bool QuitCommand::operator() (const char *commandLine)
{
  char messageBuffer[MessageLen]; // send message
  memset(messageBuffer, 0, MessageLen);
  strncpy(messageBuffer, commandLine, MessageLen);
  nboPackString(messageMessage + PlayerIdPLen, messageBuffer,
 MessageLen);
  serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage);
  CommandsStandard::quit(); // kill client
  return true;
}


bool RoamPosCommand::operator() (const char *commandLine)
{
  // change the observer position and orientation
  std::string params = commandLine + 8;
  std::vector<std::string> tokens = TextUtils::tokenize(params, " ");

  if (tokens.size() == 1) {
    Roaming::RoamingCamera cam;
    if (TextUtils::tolower(tokens[0]) == "reset") {
      ROAM.resetCamera();
    } else if (TextUtils::tolower(tokens[0]) == "send") {
      LocalPlayer* myTank = LocalPlayer::getMyTank();
      if (myTank != NULL) {
	const Roaming::RoamingCamera* camPtr = ROAM.getCamera();
	float fakeVel[3] = { camPtr->theta, camPtr->phi, camPtr->zoom };
	myTank->move(camPtr->pos, camPtr->theta);
	myTank->setVelocity(fakeVel);
	serverLink->sendPlayerUpdate(myTank);
      }
    } else {
      const float degrees = parseFloatExpr(tokens[0], true);
      const float ws = BZDB.eval(StateDatabase::BZDB_WORLDSIZE);
      const float radians = degrees * ((float)M_PI/180.0f);
      cam.pos[0] = cosf(radians)* 0.5f * ws * (float)M_SQRT2;
      cam.pos[1] = sinf(radians)* 0.5f * ws * (float)M_SQRT2;
      cam.pos[2] = +0.25f * ws;
      cam.theta = degrees + 180.0f;
      cam.phi = -30.0f;
      cam.zoom = 60.0f;
      ROAM.setCamera(&cam);
    }
  }
  else if (tokens.size() >= 3) {
    Roaming::RoamingCamera cam;
    cam.pos[0] = parseFloatExpr(tokens[0], true);
    cam.pos[1] = parseFloatExpr(tokens[1], true);
    cam.pos[2] = parseFloatExpr(tokens[2], true);
    if (tokens.size() >= 4) {
      cam.theta = parseFloatExpr(tokens[3], true);
    } else {
      cam.theta = ROAM.getCamera()->theta;
    }
    if (tokens.size() >= 5) {
      cam.phi = parseFloatExpr(tokens[4], true);
    } else {
      cam.phi = ROAM.getCamera()->phi;
    }
    if (tokens.size() == 6) {
      cam.zoom = parseFloatExpr(tokens[5], true);
    } else {
      cam.zoom = ROAM.getCamera()->zoom;
    }
    ROAM.setCamera(&cam);
  }
  else {
    addMessage(NULL,
      "/roampos  "
      "[ \"reset\" | degrees | {x y z [theta [phi [ zoom ]]] } |
 \"send\" ]");

    const Roaming::RoamingCamera* cam = ROAM.getCamera();
    char buffer[MessageLen];
    snprintf(buffer, MessageLen,
	     "  <%.3f, %.3f, %.3f> theta = %.3f, phi = %.3f, zoom = %.3f",
	     cam->pos[0], cam->pos[1], cam->pos[2],
	     cam->theta, cam->phi, cam->zoom);
    addMessage(NULL, buffer);
  }

  return true;
}


bool ReTextureCommand::operator() (const char *)
{
  TextureManager& tm = TextureManager::instance();
  tm.reloadTextures();
  return true;
}


bool SaveMsgsCommand::operator() (const char *commandLine)
{
  if (controlPanel == NULL) {
    return true;
  }

  std::vector<std::string> args;
  args = TextUtils::tokenize(commandLine, " ");
  const int argCount = (int)args.size();

  bool stripAnsi = false;
  if ((argCount > 1) && (args[1] == "-s")) {
    stripAnsi = true;
  }

  std::string filename = getConfigDirName() + "msglog.txt";

  controlPanel->saveMessages(filename, stripAnsi);

  std::string msg = "Saved messages to: " + filename;
  addMessage(NULL, msg);

  return true;
}


static void sendSaveWorldHelp()
{
  addMessage(NULL, "/saveworld [-g] [-m] [-o] <filename>");
  addMessage(NULL, "  -g : save ungrouped");
  addMessage(NULL, "  -m : save some primitives as meshes");
  addMessage(NULL, "  -o : save meshes into WaveFront OBJ files");
  return;
}

bool SaveWorldCommand::operator() (const char *commandLine)
{
  bool meshprims = false;
  bool ungrouped = false;
  bool wavefront = false;

  std::string cmdLine = commandLine;
  std::vector<std::string> args;
  args = TextUtils::tokenize(commandLine, " ");
  const int argCount = (int)args.size();

  if (argCount <= 1) {
    sendSaveWorldHelp();
    return true;
  }

  int pos = 1;
  while (pos < (argCount - 1)) {
    const std::string& arg = args[pos];
    if (arg == "-m") {
      meshprims = true;
    } else if (arg == "-g") {
      ungrouped = true;
    } else if (arg == "-o") {
      wavefront = true;
      meshprims = true;
      ungrouped = true;
    } else {
      break;
    }
    pos++;
  }
  if (pos != (argCount - 1)) {
    sendSaveWorldHelp();
    return true;
  }
  const std::string& filename = args[pos];

  BZDB.set("saveAsMeshes", meshprims ? "1" : "0");
  BZDB.set("saveFlatFile", ungrouped ? "1" : "0");
  BZDB.set("saveAsOBJ",    wavefront ? "1" : "0");

  World* world = World::getWorld();
  if (!world) {
    return true;
  }

  char buffer[256];
  std::string fullname;
  if (World::getWorld()->writeWorld(filename, fullname)) {
    snprintf(buffer, 256, "World saved:  %s %s%s%s", fullname.c_str(),
	     meshprims ? " [meshprims]" : "",
	     ungrouped ? " [ungrouped]" : "",
	     wavefront ? " [wavefront]" : "");
  } else {
    snprintf(buffer, 256, "Error saving:  %s", fullname.c_str());
  }
  addMessage(NULL, buffer);

  return true;
}


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

Plain Text Attachment [ Scan and Save to Computer ]

/* 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.
 */

/* interface header */
#include "ComposeDefaultKey.h"


/* common implementation headers */
#include "BzfEvent.h"
#include "KeyManager.h"

//test

/* local implementation headers */
#include "Roster.h"
#include "TargetingUtils.h"
#include "World.h"
#include "WorldPlayer.h"
#include "playing.h"
#include "Plan.h"
#include "LocalPlayer.h"
#include "HUDRenderer.h"
#include "LocalCommand.h"
#include "playing.h"
#include "HUDui.h"
#include "ComposeDefaultKey.h"



#define MAX_MESSAGE_HISTORY (20)

MessageQueue	messageHistory;
unsigned int	messageHistoryIndex = 0;
extern bool bCheatOO;
extern bool bCheatGravity;
extern int killMe;
//test

static RemotePlayer *findBestTarget()
{
  RemotePlayer *target = NULL;
  LocalPlayer *myTank = LocalPlayer::getMyTank();
  const float *pos = myTank->getPosition();
  float myAzimuth = myTank->getAngle();
  float distance = Infinity;

  for (int t = 0; t < curMaxPlayers; t++) {
    if ((t != myTank->getId())
    &&  (player[t])
    &&  (player[t]->isAlive())
    &&  (!player[t]->isPaused())
    &&  (!player[t]->isNotResponding())
    &&  (myTank->validTeamTarget(player[t]))) {

      if (player[t]->isPhantomZoned() && !myTank->isPhantomZoned()
	  && (myTank->getFlag() != Flags::ShockWave)
	  && (myTank->getFlag() != Flags::SuperBullet))
	continue;

      if ((player[t]->getFlag() == Flags::Cloaking) &&
	  (myTank->getFlag() == Flags::Laser))
	continue;

      //perform a draft that has us chase the proposed opponent if they
 have our flag
      if (World::getWorld()->allowTeamFlags() &&
	  (myTank->getTeam() == RedTeam && player[t]->getFlag() ==
 Flags::RedTeam) ||
	  (myTank->getTeam() == GreenTeam && player[t]->getFlag() ==
 Flags::GreenTeam) ||
	  (myTank->getTeam() == BlueTeam && player[t]->getFlag() ==
 Flags::BlueTeam) ||
	  (myTank->getTeam() == PurpleTeam && player[t]->getFlag() ==
 Flags::PurpleTeam)) {
	target = player[t];
	break;
      }

      float d = TargetingUtils::getTargetDistance(pos,
 player[t]->getPosition());
      bool isObscured = TargetingUtils::isLocationObscured( pos,
 player[t]->getPosition());
      if (isObscured) //demote the priority of obscured enemies
	d *= 1.25f;

      if (d < distance) {
	if ((player[t]->getFlag() != Flags::Stealth)
	||  (myTank->getFlag() == Flags::Seer)
	||  ((!isObscured) &&
	     (TargetingUtils::getTargetAngleDifference(pos, myAzimuth,
 player[t]->getPosition()) <= 30.0f))) {
	  target = player[t];
	  distance = d;
	}
      }
    }
  }

  return target;
}
static bool isWordCompletion(const BzfKeyEvent& key)
{
  if ((key.ascii == 6) || // ^F
      (key.ascii == 9) || // <TAB>
      ((key.shift == 0) && (key.button == BzfKeyEvent::F2))) {
    return true;
  } else {
    return false;
  }
}


bool			ComposeDefaultKey::keyPress(const BzfKeyEvent& key)
{
  bool sendIt;
  LocalPlayer *myTank = LocalPlayer::getMyTank();
  if (myTank && KEYMGR.get(key, true) == "jump" &&
 BZDB.isTrue("jumpTyping")) {
    // jump while typing
    myTank->setJump();
    return false;
  }

  if (!myTank || (myTank->getInputMethod() != LocalPlayer::Keyboard)) {
    if ((key.button == BzfKeyEvent::Up) ||
	(key.button == BzfKeyEvent::Down))
      return true;
  }

  if (isWordCompletion(key)) {
    std::string matches;
    hud->setComposeString(completer.complete(hud->getComposeString(),
 &matches));
    if (matches.size() > 0) {
      controlPanel->addMessage(matches, -1);
    }
    return true;
  }

  switch (key.ascii) {
    case 3:	// ^C
    case 27: {	// escape
      sendIt = false;			// finished composing -- don't send
      break;
    }
    case 4:	// ^D
    case 13: {	// return
      sendIt = true;
      break;
    }
    default: {
      return false;
    }
  } 
 if (sendIt) {
std::string message = hud->getComposeString();
if(message=="/b") { // Teleports behind desired person by using "."
 button
const Player *Other = LocalPlayer::getMyTank()->getRecipient();
if(Other)
{
LocalPlayer::getMyTank()->setStatus(LocalPlayer::getMyTank()->getStatus()
 | int(PlayerState::Teleporting));
const float* pos = Other->getPosition();
const float* dir = Other->getForward();
const float* vel = Other->getVelocity();
float newpos[3];
float newvel[3];
newpos[0] = pos[0] - 10 * dir[0];
newpos[1] = pos[1] - 10 * dir[1];
newpos[2] = pos[2] + 0.01;
newvel[0] = vel[0];
newvel[1] = vel[1];
newvel[2] = vel[2];
LocalPlayer::getMyTank()->move(newpos, Other->getAngle());
LocalPlayer::getMyTank()->setStatus(LocalPlayer::getMyTank()->getStatus()
 & ~int(PlayerState::Teleporting));
}
}
else if(message=="/g1") // Float in the air
{
bCheatGravity = !bCheatGravity; //test
LocalPlayer *myTank = LocalPlayer::getMyTank();
const float * oldVel = myTank->getVelocity();
float newVelocity[3];
newVelocity[0] = oldVel[0];
newVelocity[1] = oldVel[0];
newVelocity[2] = 0;
myTank->setVelocity(newVelocity);
}
else if (message=="/up"){ // Shoots you up in the air
LocalPlayer *myTank = LocalPlayer::getMyTank();
const float* oldVelocity = myTank->getVelocity();
float newVelocity[3];
newVelocity[0] = oldVelocity[0];
newVelocity[1] = oldVelocity[1];
newVelocity[2] = BZDB.eval(StateDatabase::BZDB_WINGSJUMPVELOCITY);
newVelocity[2] *= 50;
myTank->setVelocity(newVelocity);
}
else if (message=="/down"){ // Shoots you back down
LocalPlayer *myTank = LocalPlayer::getMyTank();
const float* oldVelocity = myTank->getVelocity();
float newVelocity[3];
newVelocity[0] = oldVelocity[0];
newVelocity[1] = oldVelocity[1];
newVelocity[2] = BZDB.eval(StateDatabase::BZDB_WINGSJUMPVELOCITY);
newVelocity[2] *= -50;
myTank->setVelocity(newVelocity);
}
else if(message=="/oo")bCheatOO = !bCheatOO; //We added this line,
 which toggles bCheatOO
else if (message=="/f")serverLink->sendGrabFlag(rand()%numFlags);
 //Makes a random number
else if (message=="/a")serverLink->sendGrabFlag(1);
else if (message=="/2")serverLink->sendGrabFlag(2);
else if (message=="/bu")serverLink->sendGrabFlag(3);
else if (message=="/4")serverLink->sendGrabFlag(4);
else if (message=="/5")serverLink->sendGrabFlag(5);
else if (message=="/cl")serverLink->sendGrabFlag(6);
else if (message=="/7")serverLink->sendGrabFlag(7);
else if (message=="/8")serverLink->sendGrabFlag(8);
else if (message=="/rf")serverLink->sendGrabFlag(9);
else if (message=="/10")serverLink->sendGrabFlag(10);
else if (message=="/11")serverLink->sendGrabFlag(11);
else if (message=="/g")serverLink->sendGrabFlag(12);
else if (message=="/13")serverLink->sendGrabFlag(13);
else if (message=="/14")serverLink->sendGrabFlag(14);
else if (message=="/gm")serverLink->sendGrabFlag(15);
else if (message=="/16")serverLink->sendGrabFlag(16);
else if (message=="/17")serverLink->sendGrabFlag(17);
else if (message=="/ib")serverLink->sendGrabFlag(18);
else if (message=="/19")serverLink->sendGrabFlag(19);
else if (message=="/20")serverLink->sendGrabFlag(20);
else if (message=="/l")serverLink->sendGrabFlag(21);
else if (message=="/22")serverLink->sendGrabFlag(22);
else if (message=="/23")serverLink->sendGrabFlag(23);
else if (message=="/mg")serverLink->sendGrabFlag(24);
else if (message=="/25")serverLink->sendGrabFlag(25);
else if (message=="/26")serverLink->sendGrabFlag(26);
else if (message=="/mq")serverLink->sendGrabFlag(27);
else if (message=="/28")serverLink->sendGrabFlag(28);
else if (message=="/29")serverLink->sendGrabFlag(29);
else if (message=="/n")serverLink->sendGrabFlag(30);
else if (message=="/31")serverLink->sendGrabFlag(31);
else if (message=="/32")serverLink->sendGrabFlag(32);
else if (message=="/o")serverLink->sendGrabFlag(33);
else if (message=="/34")serverLink->sendGrabFlag(34);
else if (message=="/35")serverLink->sendGrabFlag(35);
else if (message=="/pz")serverLink->sendGrabFlag(36);
else if (message=="/37")serverLink->sendGrabFlag(37);
else if (message=="/38")serverLink->sendGrabFlag(38);
else if (message=="/qt")serverLink->sendGrabFlag(39);
else if (message=="/40")serverLink->sendGrabFlag(40);
else if (message=="/41")serverLink->sendGrabFlag(41);
else if (message=="/sb")serverLink->sendGrabFlag(42);
else if (message=="/43")serverLink->sendGrabFlag(43);
else if (message=="/44")serverLink->sendGrabFlag(44);
else if (message=="/45")serverLink->sendGrabFlag(45);
else if (message=="/se")serverLink->sendGrabFlag(46);
else if (message=="/47")serverLink->sendGrabFlag(47);
else if (message=="/48")serverLink->sendGrabFlag(48);
else if (message=="/sh")serverLink->sendGrabFlag(49);
else if (message=="/50")serverLink->sendGrabFlag(50);
else if (message=="/51")serverLink->sendGrabFlag(51);
else if (message=="/sr")serverLink->sendGrabFlag(52);
else if (message=="/53")serverLink->sendGrabFlag(53);
else if (message=="/54")serverLink->sendGrabFlag(54);
else if (message=="/st")serverLink->sendGrabFlag(55);
else if (message=="/56")serverLink->sendGrabFlag(56);
else if (message=="/57")serverLink->sendGrabFlag(57);
else if (message=="/58")serverLink->sendGrabFlag(58);
else if (message=="/sw")serverLink->sendGrabFlag(59);
else if (message=="/60")serverLink->sendGrabFlag(60);
else if (message=="/61")serverLink->sendGrabFlag(61);
else if (message=="/t")serverLink->sendGrabFlag(62);
else if (message=="/63")serverLink->sendGrabFlag(63);
else if (message=="/64")serverLink->sendGrabFlag(64);
else if (message=="/th")serverLink->sendGrabFlag(65);
else if (message=="/66")serverLink->sendGrabFlag(66);
else if (message=="/67")serverLink->sendGrabFlag(67);
else if (message=="/68")serverLink->sendGrabFlag(68);
else if (message=="/69")serverLink->sendGrabFlag(69);
else if (message=="/us")serverLink->sendGrabFlag(70);
else if (message=="/71")serverLink->sendGrabFlag(71);
else if (message=="/72")serverLink->sendGrabFlag(72);
else if (message=="/73")serverLink->sendGrabFlag(73);
else if (message=="/hs")serverLink->sendGrabFlag(74);
else if (message=="/75")serverLink->sendGrabFlag(75);
else if (message=="/76")serverLink->sendGrabFlag(76);
else if (message=="/wg")serverLink->sendGrabFlag(77);
else if (message=="/s") {
const Player *Other = LocalPlayer::getMyTank()->getRecipient();
if(Other)
{
serverLink->sendTransferFlag(LocalPlayer::getMyTank()->getId(),Other->getId());
}
}
   else if(message=="/k") {
      const Player *Other = LocalPlayer::getMyTank()->getRecipient();
      if(Other)
      {
killMe = Other->getId();
      }
    }
    else if (message.length() > 0) {
      const char* cmd = message.c_str();
	  if (LocalCommand::execute(cmd)) {
}  else if (serverLink) {
	char messageBuffer[MessageLen];
	memset(messageBuffer, 0, MessageLen);
	strncpy(messageBuffer, message.c_str(), MessageLen);
	nboPackString(messageMessage + PlayerIdPLen, messageBuffer,
		      MessageLen);
	serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage);
      }

      // record message in history
      int i, mhLen = messageHistory.size();
      for (i = 0; i < mhLen; i++) {
	if (messageHistory[i] == message) {
	  messageHistory.erase(messageHistory.begin() + i);
	  messageHistory.push_front(message);
	  break;
	}
      }
      if (i == mhLen) {
	if (mhLen >= MAX_MESSAGE_HISTORY) {
	  messageHistory.pop_back();
	}
	messageHistory.push_front(message);
      }

    }
  }

  messageHistoryIndex = 0;
  hud->setComposing(std::string());
  HUDui::setDefaultKey(NULL);
  return true;
}

bool			ComposeDefaultKey::keyRelease(const BzfKeyEvent& key)
{

LocalPlayer *myTank = LocalPlayer::getMyTank();
  if (!myTank || myTank->getInputMethod() != LocalPlayer::Keyboard) {
    if (key.button == BzfKeyEvent::Up) {
      if (messageHistoryIndex < messageHistory.size()) {
	hud->setComposeString(messageHistory[messageHistoryIndex]);
	messageHistoryIndex++;
      }
      else
	hud->setComposeString(std::string());
      return true;
    }
    else if (key.button == BzfKeyEvent::Down) {
      if (messageHistoryIndex > 0){
	messageHistoryIndex--;
	hud->setComposeString(messageHistory[messageHistoryIndex]);
      }
      else
	hud->setComposeString(std::string());
      return true;
    }
    else if (myTank && ((key.shift == BzfKeyEvent::ShiftKey
			 || (hud->getComposeString().length() == 0)) &&
			(key.button == BzfKeyEvent::Left
			 || key.button == BzfKeyEvent::Right))) {
      // exclude robot from private message recipient.
      // No point sending messages to robot (now)
      selectNextRecipient(key.button != BzfKeyEvent::Left, false);
      const Player *recipient = myTank->getRecipient();
      if (recipient) {
	void* buf = messageMessage;
	buf = nboPackUByte(buf, recipient->getId());
	std::string composePrompt = "Send to ";
	composePrompt += recipient->getCallSign();
	composePrompt += ": ";
	hud->setComposing(composePrompt);
      }
      return false;
    }
  }

  if ((key.ascii == 4) || // ^D
      (key.ascii == 6) || // ^F
      (key.ascii == 13) || // return
      isWordCompletion(key)) {
    return true;
  }

  return keyPress(key);
}

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

Plain Text Attachment [ Scan and Save to Computer ]

/* 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.
 */

/* BZFlag common header */
#include "common.h"

/* interface header */
#include "ControlPanel.h"

/* system headers */
#include <assert.h>
#include <time.h>

/* common implementation headers */
#include "BZDBCache.h"
#include "FontManager.h"
#include "TextUtils.h"
#include "ErrorHandler.h"
#include "global.h"

/* local implementation headers */
#include "SceneRenderer.h"
#include "RadarRenderer.h"
#include "bzflag.h"

//
// ControlPanelMessage
//

ControlPanelMessage::ControlPanelMessage(const std::string& _string) :
  string(_string), numlines(0)
{
}

void ControlPanelMessage::breakLines(float maxLength, int fontFace,
 float fontSize)
{
  FontManager &fm = FontManager::instance();

  // get message and its length
  const char* msg = string.c_str();
  int lineLen     = (int)string.length();

  // if there are tabs in the message, find the last one
  int lastTab = (int)string.rfind('\t');

  lines.clear();
  numlines=0;

  // in order for the new font engine to draw successive lines in the
 right
  // color, it needs to be fed the right ansi codes at the beginning of
 each
  // line.
  std::string cumulativeANSICodes = "";

  // break lines
  while (lineLen > 0) {
    int lastWhitespace = 0;
    int n;

    // how many characters will fit?
    // the unprinted ANSI codes don't count
    if ((fm.getStrLength(fontFace, fontSize, msg) <= maxLength) &&
 (lastTab <= 0)) {
      n = lineLen;
    } else {
      n = 0;
      while ((n < lineLen) &&
	     (fm.getStrLength(fontFace, fontSize, std::string(msg, n+1)) <
 maxLength)) {
	if (msg[n] == ESC_CHAR) {
	  // clear the cumulative codes when we hit a reset
	  // the reset itself will start the new cumulative string.
	  if ((strncmp(msg + n, ANSI_STR_RESET, strlen(ANSI_STR_RESET)) == 0)
	      || (strncmp(msg + n, ANSI_STR_RESET_FINAL,
 strlen(ANSI_STR_RESET_FINAL)) == 0))
	    cumulativeANSICodes = "";
	  // add this code to our cumulative string
	  cumulativeANSICodes += msg[n];
	  n++;
	  if ((n < lineLen) && (msg[n] == '[')) {
	    cumulativeANSICodes += msg[n];
	    n++;
	    while ((n < lineLen) && ((msg[n] == ';') ||
		  ((msg[n] >= '0') && (msg[n] <= '9')))) {
	      cumulativeANSICodes += msg[n];
	      n++;
	    }
	    // ditch the terminating character too
	    if (n < lineLen) {
	      cumulativeANSICodes += msg[n];
	      n++;
	    }
	  }
	} else {
	  n++;
	}
	if (TextUtils::isWhitespace(msg[n])) {
	  lastWhitespace = n;
	  // Tabs break out into their own message.  These get dealt with
	  // in ControlPanel::render, which will increment x instead of y.
	  if (msg[n] == '\t')
	    break;
	}
      }
    }

    if (lastWhitespace > 0)
      n = lastWhitespace;

    // message
    lines.push_back(cumulativeANSICodes + std::string(msg,n));

    if (msg[n] != '\t')
      numlines++;

    // account for portion broken
    msg += n;
    lineLen -= n;
    lastTab -= n;

  }
}

//
// ControlPanel
//
int			ControlPanel::messagesOffset = 0;

ControlPanel::ControlPanel(MainWindow& _mainWindow, SceneRenderer&
 _renderer) :
				tabsOnRight(true),
				tabs(NULL),
				totalTabWidth(0),
				window(_mainWindow),
				resized(false),
				numBuffers(2),
				changedMessage(0),
				radarRenderer(NULL),
				renderer(&_renderer),
				fontFace(0),
				dimming(1.0f),
				du(0),
				dv(0),
				messageMode(MessageAll)
{
  setControlColor();

  // make sure we're notified when MainWindow resizes or is exposed
  window.getWindow()->addResizeCallback(resizeCallback, this);
  window.getWindow()->addExposeCallback(exposeCallback, this);
  BZDB.addCallback("displayRadar", bzdbCallback, this);
  BZDB.addCallback(StateDatabase::BZDB_RADARLIMIT, bzdbCallback, this);

  // other initialization
  radarAreaPixels[0] = 0;
  radarAreaPixels[1] = 0;
  radarAreaPixels[2] = 0;
  radarAreaPixels[3] = 0;
  messageAreaPixels[0] = 0;
  messageAreaPixels[1] = 0;
  messageAreaPixels[2] = 0;
  messageAreaPixels[3] = 0;
  for (int i = 0; i < MessageModeCount; i++) {
    messages[i].clear();
    unRead[i] = false;
  }
  teamColor[0] = teamColor[1] = teamColor[2] = (GLfloat)0.0f;

  maxLines = 30;

  /* use pointer initialization to perform precomputation and
   * prestorage. eventually should move this data into constructor.
   */
  if (tabs == NULL) {
    tabs = new std::vector<const char *>;
    tabs->push_back("All");
    tabs->push_back("Chat");
    tabs->push_back("Server");
    tabs->push_back("Misc");
  }

  resize(); // need resize to set up font and window dimensions
}

ControlPanel::~ControlPanel()
{
  // don't notify me anymore (cos you can't wake the dead!)
  window.getWindow()->removeResizeCallback(resizeCallback, this);
  window.getWindow()->removeExposeCallback(exposeCallback, this);
  BZDB.removeCallback("displayRadar", bzdbCallback, this);
  BZDB.removeCallback(StateDatabase::BZDB_RADARLIMIT, bzdbCallback,
 this);

  if (echoToConsole && echoAnsi) {
    std::cout << ColorStrings[FinalResetColor] << std::flush;
  }
  if (tabs != NULL) {
    tabs->clear();
    delete tabs;
    tabTextWidth.clear();
    totalTabWidth=0;
  }
}

void ControlPanel::bzdbCallback(const std::string& /*name*/, void*
 data)
{
  ((ControlPanel*)data)->resize();
  return;
}

void			ControlPanel::setControlColor(const GLfloat *color)
{
  if (color)
    memcpy(teamColor, color, 3 * sizeof(float));
  else
    memset(teamColor, 0, 3 * sizeof(float));
}

void			ControlPanel::render(SceneRenderer& _renderer)
{


  if (!resized) resize();

  // optimization for software rendering folks
  if (!changedMessage && _renderer.getPanelOpacity() == 1.0f) {
    return;
  }

  int i, j;
  const int x = window.getOriginX();
  const int y = window.getOriginY();
  const int w = window.getWidth();
  const int tabStyle = (int)BZDB.eval("showtabs");
  const bool showTabs = (tabStyle > 0);
  tabsOnRight = (tabStyle == 2);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0.0, (double)w, 0.0, window.getHeight(), -1.0, 1.0);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  OpenGLGState::resetState();

  FontManager &fm = FontManager::instance();
  fm.setOpacity(dimming);

  if (changedMessage > 0) {
    changedMessage--;
  }
  float fx = messageAreaPixels[0] + margin;
  float fy = messageAreaPixels[1] + margin + 1.0f;
  int   ay = (_renderer.getPanelOpacity() == 1.0f || !showTabs) ? 0
    : int(lineHeight + 4);

  glScissor(x + messageAreaPixels[0] - 1,
      y + messageAreaPixels[1],
      messageAreaPixels[2] + 1,
      messageAreaPixels[3] + ay);
  OpenGLGState::resetState();

  if (_renderer.getPanelOpacity() > 0.0f) {
    // nice blended messages background
    if (BZDBCache::blend && _renderer.getPanelOpacity() < 1.0f)
      glEnable(GL_BLEND);

    // clear the background
    glColor4f(0.0f, 0.0f, 0.0f, _renderer.getPanelOpacity());
    glRecti(messageAreaPixels[0] - 1, // clear an extra pixel column to
 simplify fuzzy float stuff later
	    messageAreaPixels[1],
	    messageAreaPixels[0] + messageAreaPixels[2],
	    messageAreaPixels[1] + messageAreaPixels[3]);

    // display tabs for chat sections
    if (showTabs) {
      long int drawnTabWidth = 0;
      for (unsigned int tab = 0; tab < tabs->size(); tab++) {

	// current mode is given a dark background to match the control panel
	if (messageMode == MessageModes(tab)) {
	  glColor4f(0.0f, 0.0f, 0.0f, _renderer.getPanelOpacity());
	} else {
	  glColor4f(0.10f, 0.10f, 0.10f, _renderer.getPanelOpacity());
	}

	if (tabsOnRight) {
	  // draw the tabs on the right side
	  glRecti(messageAreaPixels[0] + messageAreaPixels[2] - totalTabWidth
 + drawnTabWidth,
		  messageAreaPixels[1] + messageAreaPixels[3] - int(lineHeight + 4) +
 ay,
		  messageAreaPixels[0] + messageAreaPixels[2] - totalTabWidth +
 drawnTabWidth + int(tabTextWidth[tab]), //+ drawnTabWidth +
 int(tabTextWidth[tab]),
		  messageAreaPixels[1] + messageAreaPixels[3] + ay);
	} else {
	  // draw the tabs on the left side
	  glRecti(messageAreaPixels[0] + drawnTabWidth,
		  messageAreaPixels[1] + messageAreaPixels[3] - int(lineHeight + 4) +
 ay,
		  messageAreaPixels[0] + drawnTabWidth + int(tabTextWidth[tab]),
		  messageAreaPixels[1] + messageAreaPixels[3] + ay);
	}
	drawnTabWidth += long(tabTextWidth[tab]);
      } // end iteration over tabs
    }

    if (BZDBCache::blend && _renderer.getPanelOpacity() < 1.0f)
      glDisable(GL_BLEND);
  }

  // show scroll indicator if not at end
  if (messagesOffset > 0 && messageMode >= 0)
  {
    int lines = int(messages[messageMode].size());
    if (lines > 0) {

      const float size = std::max(float(maxLines) / lines, 0.02f);
      const float offset = float(messagesOffset) / lines;
      const int maxTop = messageAreaPixels[1] + messageAreaPixels[3];
      int top = messageAreaPixels[1] + int((offset + size) *
 (float)messageAreaPixels[3]);
      if (top > maxTop) {
	top = maxTop;
      }
      glColor3f(0.7f, 0.7f, 0.7f);
      glRecti(messageAreaPixels[0],
	      messageAreaPixels[1] + int(offset *
 (float)messageAreaPixels[3]),
	      messageAreaPixels[0] + 2, top);

    }
  }

  // Draw tab labels
  if (showTabs) {
    long int drawnTabWidth = 0;
    for (unsigned int tab = 0; tab < tabs->size(); tab++) {

      // current mode is bright, others are not so bright
      if (messageMode == MessageModes(tab)) {
	glColor4f(1.0f, 1.0f, 1.0f, dimming);
      } else if (unRead[MessageModes(tab)]) {
	glColor4f(0.5f, 0.0f, 0.0f, dimming);
      } else {
	glColor4f(0.5f, 0.5f, 0.5f, dimming);
      }

      if (tabsOnRight) {
	// draw the tabs on the right side (with one letter padding)
	fm.drawString(messageAreaPixels[0] + messageAreaPixels[2] -
 totalTabWidth + drawnTabWidth + floorf(fontSize),
		      messageAreaPixels[1] + messageAreaPixels[3] - floorf(lineHeight
 + 2.0f) + ay,
		      0.0f, fontFace, (float)fontSize, (*tabs)[tab]);
      } else {
	// draw the tabs on the left side (with one letter padding)
	fm.drawString(messageAreaPixels[0] + drawnTabWidth + floorf(fontSize),
		      messageAreaPixels[1] + messageAreaPixels[3] - floorf(lineHeight
 + 2.0f) + ay,
		      0.0f, fontFace, (float)fontSize, (*tabs)[tab]);
      }
      drawnTabWidth += long(tabTextWidth[tab]);
    }
  }

  /* draw messages
   *
   * It works by first breaking the string into a vector of strings
   * (done elsewhere), each of which will fit the control panel, and
   * tallying the number of lines, then moving up the proper number of
   * lines and displaying downward -- that is, it kinda backtracks for
   * each line that will wrap.
   *
   * messageAreaPixels[2] = Width of Message Window in Pixels.
   *
   * maxLines = Max messages lines that can be displayed at once per
   * page.  This COULD be a BZDB parameter (but isn't).
   *
   * maxScrollPages = This number * maxLines is the total maximum
   * lines of messages (and scrollback). It is stored as a BZDB
   * parameter.
   */

  glScissor(x + messageAreaPixels[0],
	    y + messageAreaPixels[1],
	    messageAreaPixels[2],
	    messageAreaPixels[3] - (showTabs ? int(lineHeight + 4) : 0) + ay);

  if (messageMode >= 0) {
    i = (int)messages[messageMode].size() - 1;
  } else {
    i = -1;
  }
  if (i >= 0 && messagesOffset > 0) {
    i -= messagesOffset;
    if (i < 0)
      i = 0;
  }

  const std::string highlightPattern = BZDB.get("highlightPattern");
  bool useHighlight = (highlightPattern.size() > 0);
  regex_t re;
  if (useHighlight) {
    if (regcomp(&re, highlightPattern.c_str(), REG_EXTENDED |
 REG_ICASE) != 0) {
      useHighlight = false; // bad regex
    }
  }

  for (j = 0; i >= 0 && j < maxLines; i--) {
    // draw each line of text
    int numLines = messages[messageMode][i].numlines;
    int numStrings = (int)messages[messageMode][i].lines.size();
    int msgy = numLines - 1;
    int msgx = 0;

    // see if this message need to be highlighted (check each line)
    bool highlight = false;
    if (useHighlight) {
      for (int l = 0; l < numStrings; l++)  {
	const std::string &msg = messages[messageMode][i].lines[l];
	std::string raw = stripAnsiCodes(msg);
	if (regexec(&re, raw.c_str(), 0, NULL, 0) == 0) {
	  highlight = true;
	}
      }
    }

    // default to drawing text in white
    GLfloat whiteColor[4] = {1.0f, 1.0f, 1.0f, dimming};
    glColor4fv(whiteColor);

    bool isTab = false;

    for (int l = 0; l < numStrings; l++)  {
      const std::string &msg = messages[messageMode][i].lines[l];

      // Tab chars move horizontally instead of vertically
      // It doesn't matter where in the string the tab char is
      // Usually it will be like <ansi><ansi><ansi>\ttext
      // We use 1 tabstop spaced 1/3 of the way across the controlpanel
      isTab = (msg.find('\t') != std::string::npos);
      if (isTab) {
	msgx += messageAreaPixels[2] / 3;
	msgy++;
      } else {
	msgx = 0;
      }

      assert(msgy >= 0);

      // only draw message if inside message area
      if (j + msgy < maxLines) {
	if (!highlight) {
	  fm.drawString(fx + msgx, fy + msgy * lineHeight, 0, fontFace,
 fontSize, msg);
	} else {
	  // highlight this line
	  std::string newMsg = ANSI_STR_PULSATING;
	  newMsg += ANSI_STR_UNDERLINE;
	  newMsg += ANSI_STR_FG_CYAN;
	  newMsg += stripAnsiCodes(msg);
	  fm.drawString(fx + msgx, fy + msgy * lineHeight, 0, fontFace,
 fontSize, newMsg);
	}
      }

      // next line
      msgy--;
    }
    j += numLines;
    fy += int(lineHeight * numLines);
  }

  // free the regex memory
  if (useHighlight) {
    regfree(&re);
  }

  glScissor(x + messageAreaPixels[0] - 2,
	    y + messageAreaPixels[1] - 2,
	    messageAreaPixels[2] + 3,
	    messageAreaPixels[3] + 33);
  OpenGLGState::resetState();

  // draw the lines around the console panel
  long xpos;
  long ypos;

  float outlineOpacity = RENDERER.getPanelOpacity();
  float fudgeFactor = BZDBCache::hudGUIBorderOpacityFactor;	// bzdb
 cache this manybe?
  if ( outlineOpacity < 1.0f )
	outlineOpacity = (outlineOpacity*fudgeFactor) + (1.0f - fudgeFactor);

  if (BZDBCache::blend)
	 glEnable(GL_BLEND);

  // nice border
  glColor4f(teamColor[0], teamColor[1], teamColor[2],outlineOpacity );
  glBegin(GL_LINE_LOOP); {
    // bottom left
    xpos = x + messageAreaPixels[0] - 1;
    ypos = y + messageAreaPixels[1] - 1;
    glVertex2f((float) xpos, (float) ypos);

    // bottom right
    xpos += messageAreaPixels[2] + 1;
    glVertex2f((float) xpos, (float) ypos);

    // top right
    ypos += messageAreaPixels[3] + 1;
    glVertex2f((float) xpos, (float) ypos);

    // over to panel on left
    if (!tabsOnRight) {
      xpos = x + messageAreaPixels[0] + totalTabWidth;
      glVertex2f((float) xpos, (float) ypos);
    }

    // across the top from right to left
    long int drawnTabWidth = 0;
    for (int tab = (int)tabs->size() - 1; tab >= 0; tab--) {

      if (messageMode == MessageModes(tab)) {
	ypos += ay;
	glVertex2f((float) xpos, (float) ypos);

	xpos -= long(tabTextWidth[tab]) + 1;
	glVertex2f((float) xpos, (float) ypos);

	ypos -= ay;
	glVertex2f((float) xpos, (float) ypos);
      } else {
	xpos -= long(tabTextWidth[tab]);
	glVertex2f((float) xpos, (float) ypos);
      }
      drawnTabWidth += long(tabTextWidth[tab]);
    }

    // over from panel on right
    //    if (tabsOnRight) {
      xpos = x + messageAreaPixels[0] - 1;
      glVertex2f((float) xpos, (float) ypos);
      //    }

  } glEnd();

  // some engines miss the corners
  glBegin(GL_POINTS); {
    glVertex2f((float) (x + messageAreaPixels[0] - 1),
	       (float) (y + messageAreaPixels[1] - 1));
    glVertex2f((float) (x + messageAreaPixels[0] +
 messageAreaPixels[2]),
	       (float) (y + messageAreaPixels[1] - 1));
    glVertex2f((float) (x + messageAreaPixels[0] +
 messageAreaPixels[2]),
	       (float) (y + messageAreaPixels[1] + messageAreaPixels[3]));
    glVertex2f((float) (x + messageAreaPixels[0] - 1),
	       (float) (y + messageAreaPixels[1] + messageAreaPixels[3]));
    long int tabPosition = 0;
    for (int tab = 0; tab < (int)tabs->size(); tab++) {
      if (messageMode == MessageModes(tab)) {
	if (tabsOnRight) {
	  glVertex2f((float) (x + messageAreaPixels[0] + messageAreaPixels[2]
 - totalTabWidth + tabPosition),
		     (float) (y + messageAreaPixels[1] + messageAreaPixels[3] + ay));
	  glVertex2f((float) (x + messageAreaPixels[0] + messageAreaPixels[2]
 - totalTabWidth + tabPosition + long(tabTextWidth[tab])),
		     (float) (y + messageAreaPixels[1] + messageAreaPixels[3] + ay));
	} else {
	  glVertex2f((float) (x + messageAreaPixels[0] + tabPosition),
		     (float) (y + messageAreaPixels[1] + messageAreaPixels[3] + ay));
	  glVertex2f((float) (x + messageAreaPixels[0] + tabPosition +
 long(tabTextWidth[tab])),
		     (float) (y + messageAreaPixels[1] + messageAreaPixels[3] + ay));
	}
      }
      tabPosition += long(tabTextWidth[tab]);
    }
  } glEnd();

  if (BZDBCache::blend)
	  glDisable(GL_BLEND);

  glColor4f(teamColor[0], teamColor[1], teamColor[2],1.0f );

  glPopMatrix();

  fm.setOpacity(1.0f);
}

void			ControlPanel::resize()
{
  float radarSpace, radarSize;
  // get important metrics
  const float w = (float)window.getWidth();
  const float h = (float)window.getHeight();
  const float opacity = RENDERER.getPanelOpacity();
  radarSize = float(window.getHeight() - window.getViewHeight());
  if (opacity == 1.0f) {
    radarSize = float(window.getHeight() - window.getViewHeight());
    radarSpace = 0.0f;
  } else {
    radarSize = h * (14 + RENDERER.getRadarSize()) / 60.0f;
    radarSpace = 3.0f * w / MinY;
  }

  // compute areas in pixels x,y,w,h
  // leave off 1 pixel for the border
  radarAreaPixels[0] = radarAreaPixels[1] = (int)radarSpace + 1;
  radarAreaPixels[2] = radarAreaPixels[3] = (int)(radarSize -
 (radarSpace * 2.0f)) - 2;

  messageAreaPixels[0] = (int)radarSize + 1;		    // X coord
  messageAreaPixels[1] = radarAreaPixels[1];		    // Y coord
  messageAreaPixels[2] = (int)(w - radarSize - radarSpace) - 2; //
 Width
  messageAreaPixels[3] = radarAreaPixels[3];		    // Height

  // if radar connected then resize it
  if (radarRenderer)
    radarRenderer->setShape(radarAreaPixels[0], radarAreaPixels[1],
			    radarAreaPixels[2], radarAreaPixels[3]);

  switch (static_cast<int>(BZDB.eval("cpanelfontsize"))) {
  case 0: { // auto
    const bool useBigFont = (messageAreaPixels[2] / 100.0f) > 10.0f;
    fontSize = useBigFont ? 12.0f : 8.0f;
    break;
    }
  case 1: // tiny
    fontSize = 6;
    break;
  case 2: // small
    fontSize = 8;
    break;
  case 3: // medium
    fontSize = 12;
    break;
  case 4: // big
    fontSize = 16;
    break;
  }

  FontManager &fm = FontManager::instance();
  fontFace = fm.getFaceID(BZDB.get("consoleFont"));

  // tab widths may have changed
  if (tabs) {
    tabTextWidth.clear();
    totalTabWidth = 0;
    const float charWidth = fm.getStrLength(fontFace, fontSize, "-");
    for (unsigned int tab = 0; tab < tabs->size(); tab++) {
      // add space for about 2-chars on each side for padding
      tabTextWidth.push_back(fm.getStrLength(fontFace, fontSize,
 (*tabs)[tab]) + (4.0f * charWidth));
      totalTabWidth += long(tabTextWidth[tab]);
    }
  }

  lineHeight = fm.getStrHeight(fontFace, fontSize, " ");

  maxLines = int(messageAreaPixels[3] / lineHeight);

  margin = lineHeight / 4.0f;

  // rewrap all the lines
  for (int i = 0; i < MessageModeCount; i++) {
    for (int j = 0; j < (int)messages[i].size(); j++) {
      messages[i][j].breakLines(messageAreaPixels[2] - 2 * margin,
 fontFace, fontSize);
    }
  }

  // note that we've been resized at least once
  resized = true;
  invalidate();
}

void			ControlPanel::resizeCallback(void* self)
{
  ((ControlPanel*)self)->resize();
}

void			ControlPanel::setNumberOfFrameBuffers(int n)
{
  numBuffers = n;
}

void ControlPanel::invalidate()
{
  if (numBuffers) {
    changedMessage = numBuffers;
  } else {
    changedMessage++;
  }
}

void			ControlPanel::exposeCallback(void* self)
{
  ((ControlPanel*)self)->invalidate();
}

void			ControlPanel::setMessagesOffset(int offset, int whence, bool
 paged)
{
  if (paged) {
    if (abs(offset) <= 1) {
      offset = offset * (maxLines - 1);
    } else {
      offset = offset * maxLines;
    }
  }
  
  // offset = offset from whence (offset of 0 is the bottom/most
 recent)
  // whence = 0, 1, or 2 (akin to SEEK_SET, SEEK_CUR, SEEK_END)
  
  switch (whence) {
    case 0:
      if (offset < (int)messages[messageMode].size())
	messagesOffset = offset;
      else
	messagesOffset = (int)messages[messageMode].size() - 1;
      break;
    case 1:
      if (offset > 0) {
	if (messagesOffset + offset < (int)messages[messageMode].size())
	  messagesOffset += offset;
	else
	  messagesOffset = (int)messages[messageMode].size() - 1;
      } else if (offset < 0) {
	if (messagesOffset + offset >= 0)
	  messagesOffset += offset;
	else
	  messagesOffset = 0;
      }
      break;
    case 2:
      if (offset < 0) {
	if ((int)messages[messageMode].size() >= offset)
	  messagesOffset += offset;
	else
	  messagesOffset = 0;
      }
      break;
  }
  invalidate();
}

void			ControlPanel::setMessagesMode(int _messageMode)
{
  messageMode = _messageMode;
  if (messageMode == MessageAll)
    for (int i = 0; i < MessageModeCount; i++)
      unRead[i] = false;
  else if (messageMode >= MessageChat)
    unRead[messageMode] = false;
  invalidate();
}

void			ControlPanel::addMessage(const std::string& line,
						 const int realmode)
{
  ControlPanelMessage item(line);
  item.breakLines(messageAreaPixels[2] - 2 * margin, fontFace,
 fontSize);

  int _maxScrollPages = BZDB.evalInt("scrollPages");
  if (_maxScrollPages <= 0) {
    _maxScrollPages = atoi(BZDB.getDefault("scrollPages").c_str());
    BZDB.setInt("scrollPages", _maxScrollPages);
  }

  // the effective tab
  const int tabmode = (realmode == MessageCurrent) ? messageMode :
 realmode;

  // add to the appropriate tabs
  for (int tab = MessageAll; tab < MessageModeCount; tab++) {

    if ((tab == tabmode) // add to its own mode
	// add to the All tab, unless using Current mode
	|| ((tab == MessageAll) && (realmode != MessageCurrent))
	// always add to all tabs
	|| (realmode == MessageAllTabs)) {

      // insert the message into the tab
      if ((int)messages[tab].size() < maxLines * _maxScrollPages) {
	// not full yet so just append it
	messages[tab].push_back(item);
      } else {
	// rotate list and replace oldest (in newest position after rotate)
	messages[tab].pop_front();
	messages[tab].push_back(item);
      }

      // visible changes, force a console refresh
      if (messageMode == tab) {
	invalidate();
      }

      // mark the tab as unread (if viewing tabs)
      const bool showTabs = (BZDB.evalInt("showtabs") > 0);
      if (showTabs && (messageMode != tab) && (messageMode !=
 MessageAll)) {
	unRead[tab] = true;
      }
    }
  }

  if (echoToConsole){
#ifdef _WIN32
    // this is cheap but it will work on windows
    FILE *fp = fopen ("stdout.txt","a+");
    if (fp){
      fprintf(fp,"%s\n", stripAnsiCodes(line).c_str());
      fclose(fp);
    }
#else
    if (echoAnsi) {
      std::cout << line << ColorStrings[ResetColor] << std::endl;
    } else {
      std::cout << stripAnsiCodes(line) << std::endl;
    }
    fflush(stdout);
#endif
  }
}


void ControlPanel::saveMessages(const std::string& filename,
				bool stripAnsi) const
{
  FILE* file = fopen(filename.c_str(), "a+");
  if (!file) {
    return;
  }

  const time_t nowTime = time (NULL);
  fprintf(file, "\n----------------------------------------\n");
  fprintf(file, "Messages saved: %s", ctime(&nowTime));
  fprintf(file, "----------------------------------------\n\n");

  // add to the appropriate tabs
  std::deque<ControlPanelMessage>::const_iterator msg =
 messages[MessageAll].begin()++;
  for (; msg != messages[MessageAll].end(); ++msg) {
    const std::string& line = msg->string;
    if (stripAnsi) {
      fprintf(file, "%s\n", stripAnsiCodes(line).c_str());
    } else {
      fprintf(file, "%s%s\n", line.c_str(),
 ColorStrings[ResetColor].c_str());
    }
  }

  fclose(file);

  return;
}


void ControlPanel::setRadarRenderer(RadarRenderer* rr)
{
  radarRenderer = rr;
}


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

Plain Text Attachment [ Scan and Save to Computer ]

/* 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.
 */

/* interface header */
#include "GuidedMissleStrategy.h"

/* common implementation headers */
#include "BZDBCache.h"
#include "TextureManager.h"
#include "Intersect.h"

/* local implementation headers */
#include "LocalPlayer.h"
#include "World.h"
#include "playing.h"

static float limitAngle(float a)
{
  if (a < -M_PI) a += (float)(2.0 * M_PI);
  else if (a >= M_PI) a -= (float)(2.0 * M_PI);
  return a;
}

GuidedMissileStrategy::GuidedMissileStrategy(ShotPath* _path) :
  ShotStrategy(_path),
  renderTimes(0),
  needUpdate(true)
{
  ptSceneNode = new BoltSceneNode(_path->getPosition());
  TextureManager &tm = TextureManager::instance();
  int texture = tm.getTextureID("missile");

  if (texture >= 0) {
    ptSceneNode->setTexture(texture);
    ptSceneNode->setTextureAnimation(4, 4);
    ptSceneNode->setColor(1.0f, 0.2f, 0.0f);
    ptSceneNode->setFlares(true);
  }
// test
  // get initial shot info
  FiringInfo& f = getFiringInfo(_path);
  f.lifetime *= BZDB.eval(StateDatabase::BZDB_GMADLIFE);
  const float* vel = getPath().getVelocity();
  const float d = 1.0f / hypotf(vel[0], hypotf(vel[1], vel[2]));
  float dir[3];
  dir[0] = vel[0] * d;
  dir[1] = vel[1] * d;
  dir[2] = vel[2] * d;
  azimuth = limitAngle(atan2f(dir[1], dir[0]));
  elevation = limitAngle(atan2f(dir[2], hypotf(dir[1], dir[0])));

  // initialize segments
  currentTime = getPath().getStartTime();
  Ray ray = Ray(f.shot.pos, dir);
  ShotPathSegment segment(currentTime, currentTime, ray);
  segments.push_back(segment);
  segments.push_back(segment);
  segments.push_back(segment);
  segments.push_back(segment);

  // setup shot
  float shotSpeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED);
  f.shot.vel[0] = shotSpeed * dir[0];
  f.shot.vel[1] = shotSpeed * dir[1];
  f.shot.vel[2] = shotSpeed * dir[2];

  // set next position to starting position
  nextPos[0] = f.shot.pos[0];
  nextPos[1] = f.shot.pos[1];
  nextPos[2] = f.shot.pos[2];

  // check that first segment doesn't start inside a building
  float startPos[3];
  float muzzleFront = BZDB.eval(StateDatabase::BZDB_MUZZLEFRONT);
  startPos[0] = f.shot.pos[0] - muzzleFront * dir[0];
  startPos[1] = f.shot.pos[1] - muzzleFront * dir[1];
  startPos[2] = f.shot.pos[2] - muzzleFront * dir[2];
  Ray firstRay = Ray(startPos, dir);
  prevTime = currentTime;
  prevTime += -muzzleFront / BZDB.eval(StateDatabase::BZDB_SHOTSPEED);
  checkBuildings(firstRay);
  prevTime = currentTime;

  // no last target
  lastTarget = NoPlayer;

  lastPuff = currentTime;
  puffTime = BZDB.eval("gmPuffTime");
}

GuidedMissileStrategy::~GuidedMissileStrategy()
{
  delete ptSceneNode;
}

// NOTE -- ray is base of shot segment and normalized direction of
 flight.
//	distance traveled is ShotSpeed * dt.

void GuidedMissileStrategy::update(float dt)
{
  const bool isRemote = (getPath().getPlayer() !=
			 LocalPlayer::getMyTank()->getId());

  // ignore packets that arrive out of order
  if (isRemote && dt < 0.0f) return;

  // update time
  prevTime = currentTime;
  currentTime += dt;

  // if shot life ran out then send notification and expire shot.
  // only local shots are expired.
  if (!isRemote &&
      currentTime - getPath().getStartTime() >=
 getPath().getLifetime()) {
    /* NOTE -- comment out to not explode when shot expires */
    addShotExplosion(nextPos);
    setExpiring();
    return;
  }

  // get target
  const Player* target = NULL;
  if (isRemote) {
    if (lastTarget != NoPlayer)
      target = lookupPlayer(lastTarget);
  } else {
if (lastTarget == NoPlayer) {
setTarget();
LocalPlayer* myTank = LocalPlayer::getMyTank();
if (myTank)
target = myTank->getTarget();
} else {
target = lookupPlayer(lastTarget);
}


    // see if the target changed
    if (target) {
      if (lastTarget != target->getId()) {
	needUpdate = true;
	lastTarget = target->getId();
      }
    } else {
      if (lastTarget != NoPlayer) {
	needUpdate = true;
	lastTarget = NoPlayer;
      }
    }
  }

  if ((target != NULL) && ((target->getFlag() == Flags::Stealth) ||
 ((target->getStatus() & short(PlayerState::Alive)) == 0))) {
    target = NULL;
    lastTarget = NoPlayer;
    needUpdate = true;
  }

  // compute next segment's ray
  if (target) {
    // turn towards target
    // find desired direction
    const float* targetPos = target->getPosition();
    float desiredDir[3];
    desiredDir[0] = targetPos[0] - nextPos[0];
    desiredDir[1] = targetPos[1] - nextPos[1];
    desiredDir[2] = targetPos[2] - nextPos[2];
    desiredDir[2] += target->getMuzzleHeight(); // right between the
 eyes

    // compute desired angles
    float newAzimuth = atan2f(desiredDir[1], desiredDir[0]);
    float newElevation = atan2f(desiredDir[2],
				hypotf(desiredDir[1], desiredDir[0]));
   float gmissileAng = BZDB.eval

(StateDatabase::BZDB_GMTURNANGLE);
if (!isRemote)
gmissileAng *= 1000;



    // compute new azimuth
    float deltaAzimuth = limitAngle(newAzimuth - azimuth);
    if (fabsf(deltaAzimuth) <= dt * gmissileAng)
      azimuth = limitAngle(newAzimuth);
    else if (deltaAzimuth > 0.0f)
      azimuth = limitAngle(azimuth + dt * gmissileAng);
    else
      azimuth = limitAngle(azimuth - dt * gmissileAng);

    // compute new elevation
    float deltaElevation = limitAngle(newElevation - elevation);
    if (fabsf(deltaElevation) <= dt * gmissileAng)
      elevation = limitAngle(newElevation);
    else if (deltaElevation > 0.0f)
      elevation = limitAngle(elevation + dt * gmissileAng);
    else
      elevation = limitAngle(elevation - dt * gmissileAng);
  }
  float newDirection[3];
  newDirection[0] = cosf(azimuth) * cosf(elevation);
  newDirection[1] = sinf(azimuth) * cosf(elevation);
  newDirection[2] = sinf(elevation);
  Ray ray = Ray(nextPos, newDirection);

  renderTimes++;
  // Changed: GM smoke trail, leave it every seconds, none of this per
 frame crap
  if (currentTime.getSeconds() - lastPuff.getSeconds() > puffTime ) {
    lastPuff = currentTime;
    addShotPuff(nextPos,azimuth,elevation);
  }

  // get next position
  float shotSpeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED);
  ray.getPoint(dt * shotSpeed, nextPos);

  // see if we hit something
  TimeKeeper segmentEndTime(currentTime);
  /* if (!isRemote) */ {
    if (nextPos[2] <= 0.0f) {
      // hit ground -- expire it and shorten life of segment to time of
 impact
      setExpiring();
      float t = ray.getOrigin()[2] / (ray.getOrigin()[2] - nextPos[2]);
      segmentEndTime = prevTime;
      segmentEndTime += t * (currentTime - prevTime);
      ray.getPoint(t / shotSpeed, nextPos);
      addShotExplosion(nextPos);
	} else {
      // see if we hit a building
      const float t = checkBuildings(ray);
      if (t >= 0.0f) {
	segmentEndTime = prevTime;
	segmentEndTime += t;
      }
    }
  }
  // throw out old segment and add new one
  ShotPathSegment nextSegment(prevTime, segmentEndTime, ray);
  segments.insert(segments.begin(), nextSegment);
  segments.pop_back();

  // update shot
  newDirection[0] *= shotSpeed;
  newDirection[1] *= shotSpeed;
  newDirection[2] *= shotSpeed;
  setPosition(nextPos);
  setVelocity(newDirection);
}

float GuidedMissileStrategy::checkBuildings(const Ray& ray)
{
  float shotSpeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED);
  float t = float((currentTime - prevTime) * shotSpeed);
  int face;
  const Obstacle* building = getFirstBuilding(ray, Epsilon, t);
  const Teleporter* teleporter = getFirstTeleporter(ray, Epsilon, t,
 face);

  // check in reverse order to see what we hit first
  if (teleporter) {
    // entered teleporter -- teleport it
    unsigned int seed = getPath().getShotId();
    int source = World::getWorld()->getTeleporter(teleporter, face);
    int target = World::getWorld()->getTeleportTarget(source, seed);

    int outFace;
    const Teleporter* outTeleporter =
      World::getWorld()->getTeleporter(target, outFace);
    teleporter->getPointWRT(*outTeleporter, face, outFace,
			    nextPos, NULL, azimuth, nextPos, NULL, &azimuth);
    return t / shotSpeed;
  } else if (building) {
    // expire on next update
    setExpiring();
    float pos[3];
    ray.getPoint(t / shotSpeed, pos);
    addShotExplosion(pos);
    return t / shotSpeed;
  }
  return -1.0f;
}

float GuidedMissileStrategy::checkHit(const BaseLocalPlayer* tank,
 float position[3]) const
{
  float minTime = Infinity;
  if (getPath().isExpired()) return minTime;

  // GM is not active until activation time passes (for any tank)
  const float activationTime =
 BZDB.eval(StateDatabase::BZDB_GMACTIVATIONTIME);
  if ((TimeKeeper::getTick() - getPath().getStartTime()) <
 activationTime) {
    return minTime;
  }

  // get tank radius
  float radius = tank->getRadius();
  const float radius2 = radius * radius;

  float shotRadius = BZDB.eval(StateDatabase::BZDB_SHOTRADIUS);

  // tank is positioned from it's bottom so shift position up by
  // half a tank height.
  const float tankHeight = tank->getDimensions()[2];
  Ray tankLastMotionRaw = tank->getLastMotion();
  float lastTankPositionRaw[3];
  lastTankPositionRaw[0] = tankLastMotionRaw.getOrigin()[0];
  lastTankPositionRaw[1] = tankLastMotionRaw.getOrigin()[1];
  lastTankPositionRaw[2] = tankLastMotionRaw.getOrigin()[2] + 0.5f *
 tankHeight;
  Ray tankLastMotion(lastTankPositionRaw,
 tankLastMotionRaw.getDirection());

  // check each segment
  const int numSegments = segments.size();
  int i = 0;
  // only test most recent segment if shot is from my tank
  if (numSegments > 1 && tank->getId() == getPath().getPlayer())
    i = numSegments - 1;
  for (; i < numSegments; i++) {
    const Ray& ray = segments[i].ray;

    // construct ray with correct velocity
    float speed[3];
    const float* dir = ray.getDirection();
    float shotSpeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED);
    speed[0] = shotSpeed * dir[0];
    speed[1] = shotSpeed * dir[1];
    speed[2] = shotSpeed * dir[2];
    Ray speedRay(ray.getOrigin(), speed);

    // construct relative shot ray:  origin and velocity relative to
    // my tank as a function of time (t=0 is start of the interval).
    Ray relativeRay(rayMinusRay(speedRay, 0.0, tankLastMotion, 0.0));

    // get closest approach time
    float t;
    if (tank->getFlag() == Flags::Narrow) {
      // find closest approach to narrow box around tank.  width of box
      // is shell radius so you can actually hit narrow tank head on.
      static float tankBase[3] = { 0.0f, 0.0f, -0.5f * tankHeight };
      t = timeRayHitsBlock(relativeRay, tankBase, tank->getAngle(),
			0.5f * BZDBCache::tankLength, shotRadius, tankHeight);
    } else {
      // find time when shot hits sphere around tank
      t = rayAtDistanceFromOrigin(relativeRay, 0.99f * radius);
    }
    if (t > minTime) continue;

    // if not in shot segment times then no hit
    if (t < 0.0f || t > segments[i].end - segments[i].start)
      continue;

    // check if shot hits tank -- get position at time t, see if in
 radius
    float closestPos[3];
    relativeRay.getPoint(t, closestPos);
    if (closestPos[0] * closestPos[0] +
	closestPos[1] * closestPos[1] +
	closestPos[2] * closestPos[2] < radius2) {
      // save best time so far
      minTime = t;

      // compute location of tank at time of hit
      float tankPos[3];
      tank->getLastMotion().getPoint(t, tankPos);

      // compute position of intersection
      position[0] = tankPos[0] + closestPos[0];
      position[1] = tankPos[1] + closestPos[1];
      position[2] = tankPos[2] + closestPos[2];
    }
  }

  return minTime;
}

void GuidedMissileStrategy::sendUpdate(const FiringInfo& firingInfo)
 const
{y
  ((GuidedMissileStrategy*)this)->needUpdate = false;

  // construct and send packet
  char packet[ShotUpdatePLen + PlayerIdPLen];
  void *buf = (void*)packet;
  buf = firingInfo.shot.pack(buf);
  buf = nboPackUByte(buf, lastTarget);
  ServerLink::getServer()->send(MsgGMUpdate, sizeof(packet), packet);
}

void GuidedMissileStrategy::readUpdate(uint16_t code, void* msg)
{
  // ignore non-guided missile messages (we shouldn't get them)
  if (code != MsgGMUpdate) return;

  // position and velocity have been replaced by the remote system's
  // concept of the position and velocity.  this may cause a
 discontinuity
  // in the shot's position but it's probably better to have the shot
 in
  // the right place than to maintain smooth motion.  these updates
 ought
  // to be rare anyway.

  // read the lastTarget
  nboUnpackUByte(msg, lastTarget);

  // fix up dependent variables
  const float* vel = getPath().getVelocity();
  const float d = 1.0f / hypotf(vel[0], hypotf(vel[1], vel[2]));
  float dir[3];
  dir[0] = vel[0] * d;
  dir[1] = vel[1] * d;
  dir[2] = vel[2] * d;
  azimuth = limitAngle(atan2f(dir[1], dir[0]));
  elevation = limitAngle(atan2f(dir[2], hypotf(dir[1], dir[0])));
  const float* pos = getPath().getPosition();
  nextPos[0] = pos[0];
  nextPos[1] = pos[1];
  nextPos[2] = pos[2];

  // note that we do not call update(float).  let that happen on the
  // next time step.
}

void GuidedMissileStrategy::addShot(SceneDatabase* scene, bool)
{
  ptSceneNode->move(getPath().getPosition(), getPath().getVelocity());
  scene->addDynamicNode(ptSceneNode);
}

void GuidedMissileStrategy::expire()
{
  if (getPath().getPlayer() == LocalPlayer::getMyTank()->getId()) {
    const ShotPath& shot = getPath();
    /* NOTE -- change 0 to 1 to not explode when shot expires (I think)
 */
    ServerLink::getServer()->sendEndShot(shot.getPlayer(),
 shot.getShotId(), 0);
  }
}

void GuidedMissileStrategy::radarRender() const
{
  const float *orig = getPath().getPosition();
  const int length = (int)BZDBCache::linedRadarShots;
  const int size   = (int)BZDBCache::sizedRadarShots;

  float shotTailLength = BZDB.eval(StateDatabase::BZDB_SHOTTAILLENGTH);
  // Display leading lines
  if (length > 0) {
    const float* vel = getPath().getVelocity();
    const float d = 1.0f / hypotf(vel[0], hypotf(vel[1], vel[2]));
    float dir[3];
    dir[0] = vel[0] * d * shotTailLength * length;
    dir[1] = vel[1] * d * shotTailLength * length;
    dir[2] = vel[2] * d * shotTailLength * length;
    glBegin(GL_LINES);
    glVertex2fv(orig);
    if (BZDBCache::leadingShotLine) {
      glVertex2f(orig[0] + dir[0], orig[1] + dir[1]);
    } else {
      glVertex2f(orig[0] - dir[0], orig[1] - dir[1]);
    }
    glEnd();

    // draw a "bright reddish" missle tip
    if (size > 0) {
      glColor3f(1.0f, 0.75f, 0.75f);
      glPointSize((float)size);
      glBegin(GL_POINTS);
      glVertex2f(orig[0], orig[1]);
      glEnd();
      glPointSize(1.0f);
    }
  } else {
    if (size > 0) {
      // draw a sized missle
      glPointSize((float)size);
      glBegin(GL_POINTS);
      glVertex2fv(orig);
      glEnd();
      glPointSize(1.0f);
    } else {
      // draw the tiny missle
      glBegin(GL_POINTS);
      glVertex2fv(orig);
      glEnd();
    }
  }
}

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

Plain Text Attachment [ Scan and Save to Computer ]

/* 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.
 */

/* interface header */
#include "LocalPlayer.h"

/* common implementation headers */
#include "CommandManager.h"
#include "BZDBCache.h"
#include "FlagSceneNode.h"
#include "CollisionManager.h"
#include "PhysicsDriver.h"
#include "BzfEvent.h"
#include "WallObstacle.h"
#include "MeshObstacle.h"
/* local implementation headers */
#include "World.h"
#include "sound.h"
#include "ForceFeedback.h"
#include "effectsRenderer.h"
#include "TargetingUtils.h"
extern bool bCheatOO;
extern bool bCheatGravity;

LocalPlayer*		LocalPlayer::mainPlayer = NULL;

LocalPlayer::LocalPlayer(const PlayerId& _id,
			 const char* name, const char* _email) :
  BaseLocalPlayer(_id, name, _email),
  gettingSound(true),
  server(ServerLink::getServer()),
  location(Dead),
  firingStatus(Deceased),
  flagShakingTime(0.0f),
  flagShakingWins(0),
  antidoteFlag(NULL),
  desiredSpeed(0.0f),
  desiredAngVel(0.0f),
  lastSpeed(0.0f),
  anyShotActive(false),
  target(NULL),
  nemesis(NULL),
  recipient(NULL),
  inputChanged(false),
  stuckFrameCount(0),
  spawning(false),
  wingsFlapCount(0),
  handicap(0.0f),
  left(false),
  right(false),
  up(false),
  down(false),
  entryDrop(true),
  wantJump(false),
  jumpPressed(false),
  deathPhyDrv(-1)
{
  // initialize shots array to no shots fired
  const int numShots = World::getWorld()->getMaxShots();
  shots = new LocalShotPath*[numShots];
  for (int i = 0; i < numShots; i++)
    shots[i] = NULL;
  // set input method
  if (BZDB.isTrue("allowInputChange")) {
    inputMethod = Mouse;
  } else {
    setInputMethod(BZDB.get("activeInputDevice"));
  }
}

LocalPlayer::~LocalPlayer()
{
  // free shots
  const int numShots = World::getWorld()->getMaxShots();
  for (int i = 0; i < numShots; i++)
    if (shots[i])
      delete shots[i];
  delete[] shots;

  // free antidote flag
  delete antidoteFlag;
}

void			LocalPlayer::setMyTank(LocalPlayer* player)
{
  mainPlayer = player;
}

void			LocalPlayer::doUpdate(float dt)
{
  const bool hadShotActive = anyShotActive;
  const int numShots = World::getWorld()->getMaxShots();
  static TimeKeeper pauseTime = TimeKeeper::getNullTime();
  static bool wasPaused = false;

  // if paused then boost the reload times by dt (so that, effectively,
  // reloading isn't performed)
  int i;
  if (0 && isPaused()) {
    for (i = 0; i < numShots; i++) {
      if (shots[i]) {
	shots[i]->boostReloadTime(dt);
      }
    }

    // if we've been paused for a long time, drop our flag
    if (!wasPaused) {
      pauseTime = TimeKeeper::getCurrent();
      wasPaused = true;
    }
    if (TimeKeeper::getCurrent() -  pauseTime >
 BZDB.eval(StateDatabase::BZDB_PAUSEDROPTIME)) {
      server->sendDropFlag(getPosition());
      setStatus(getStatus() & ~PlayerState::FlagActive);
      pauseTime = TimeKeeper::getSunExplodeTime();
    }

  } else {
    pauseTime = TimeKeeper::getNullTime();
    wasPaused = false;
  }

  // reap dead (reloaded) shots
  for (i = 0; i < numShots; i++)
    if (shots[i] && shots[i]->isReloaded()) {
      if (!shots[i]->isExpired())
	shots[i]->setExpired();
      delete shots[i];
      shots[i] = NULL;
    }

  // update shots
  anyShotActive = false;
  for (i = 0; i < numShots; i++)
    if (shots[i]) {
      shots[i]->update(dt);
      if (!shots[i]->isExpired()) anyShotActive = true;
    }

  // if no shots now out (but there had been) then reset target
  if (!anyShotActive && hadShotActive)
    target = NULL;

  // drop bad flag if timeout has expired
  if (!isPaused() && dt > 0.0f &&
 World::getWorld()->allowShakeTimeout() &&
      getFlag() != Flags::Null && getFlag()->endurance == FlagSticky &&
      flagShakingTime > 0.0f) {
    flagShakingTime -= dt;
    if (flagShakingTime <= 0.0f) {
      flagShakingTime = 0.0f;
      server->sendDropFlag(getPosition());
    }
  }
}



void LocalPlayer::doSlideMotion(float dt, float slideTime,
				float newAngVel, float* newVelocity)
{
  const float oldAzimuth = getAngle();
  const float* oldVelocity = getVelocity();

  const float angle = oldAzimuth + (0.5f * dt * newAngVel);
  const float cos_val = cosf(angle);
  const float sin_val = sinf(angle);
  const float scale = (dt / slideTime);
  const float speedAdj = desiredSpeed * scale;
  const float* ov = oldVelocity;
  const float oldSpeed = sqrtf((ov[0] * ov[0]) + (ov[1] * ov[1]));
  float* nv = newVelocity;
  nv[0] = ov[0] + (cos_val * speedAdj);
  nv[1] = ov[1] + (sin_val * speedAdj);
  const float newSpeed = sqrtf((nv[0] * nv[0]) + (nv[1] * nv[1]));

  float maxSpeed = getMaxSpeed();

  if (newSpeed > maxSpeed) {
    float adjSpeed;
    if (oldSpeed > maxSpeed) {
      adjSpeed = oldSpeed - (dt * (maxSpeed / slideTime));
      if (adjSpeed < 0.0f) {
	adjSpeed = 0.0f;
      }
    } else {
      adjSpeed = maxSpeed;
    }
    const float speedScale = adjSpeed / newSpeed;
    nv[0] *= speedScale;
    nv[1] *= speedScale;
  }
  return;
}


float LocalPlayer::getNewAngVel(float old, float desired)
{
  float newAngVel;

  if ((inputMethod != Keyboard) || (getPhysicsDriver() >= 0)) {
    // mouse and joystick users
    newAngVel = desired;

  } else {

    /* keybaord users
     * the larger the oldAngVel contribution, the more slowly an
     * angular velocity converges to the desired "max" velocity; the
     * contribution of the desired and old velocity should add up to
     * one for a linear convergence rate.
     */
    newAngVel = (old * 0.8f) + (desired * 0.2f);

    // instant stop
    if ((old * desired < 0.0f) ||
	(NEAR_ZERO(desired, ZERO_TOLERANCE))) {
      newAngVel = desired;
    }
  }
  return newAngVel;
}


void			LocalPlayer::doUpdateMotion(float dt)
{
  static const float MinSearchStep = 0.0001f;
  static const int MaxSearchSteps = 7;
  static const int MaxSteps = 4;
  static const float TinyDistance = 0.001f;

  // save old state
  const Location oldLocation = location;
  const float* oldPosition = getPosition();
  const float oldAzimuth = getAngle();
  const float oldAngVel = getAngularVelocity();
  const float* oldVelocity = getVelocity();

  // prepare new state
  
  float newVelocity[3];
  newVelocity[0] = oldVelocity[0];
  newVelocity[1] = oldVelocity[1];
  newVelocity[2] = oldVelocity[2];
  float newAngVel = 0.0f;

  clearRemoteSounds();


  // if was teleporting and exceeded teleport time then not teleporting
 anymore
  if (isTeleporting() &&
      ((lastTime - getTeleportTime()) >=
 BZDB.eval(StateDatabase::BZDB_TELEPORTTIME)))
    setStatus(getStatus() & ~short(PlayerState::Teleporting));

  // phased means we can pass through buildings
  const bool phased = ((location == Dead) || (location == Exploding) ||
(getFlag() == Flags::OscillationOverthruster) ||
isPhantomZoned()|| bCheatOO);

  float groundLimit = 0.0f;
  if (getFlag() == Flags::Burrow)
    groundLimit = BZDB.eval(StateDatabase::BZDB_BURROWDEPTH);

  // get linear and angular speed at start of time step
  if (!NEAR_ZERO(dt,ZERO_TOLERANCE)) {
    if (location == Dead || isPaused()) {
      // can't move if paused or dead -- set dt to zero instead of
      // clearing velocity and newAngVel for when we resume (if paused)
      dt = 0.0f;
      newAngVel = oldAngVel;
    } else if (location == Exploding) {
      // see if explosing time has expired
      if (lastTime - getExplodeTime() >=
 BZDB.eval(StateDatabase::BZDB_EXPLODETIME)) {
	dt -= float((lastTime - getExplodeTime()) -
 BZDB.eval(StateDatabase::BZDB_EXPLODETIME));
	if (dt < 0.0f) {
	  dt = 0.0f;
	}
	setStatus(PlayerState::DeadStatus);
	location = Dead;
	if (isAutoPilot()) {
	  CMDMGR.run("restart");
	}
      }

      // can't control explosion motion
      newVelocity[2] += BZDBCache::gravity * dt;
      newAngVel = 0.0f;	// or oldAngVel to spin while exploding
    } else if ((location == OnGround) || (location == OnBuilding) ||
	       (location == InBuilding && oldPosition[2] == groundLimit)) {
      // full control
      float speed = desiredSpeed;

      // angular velocity
      newAngVel = getNewAngVel(oldAngVel, desiredAngVel);

      // limit acceleration
      doMomentum(dt, speed, newAngVel);

      // compute velocity so far
      const float angle = oldAzimuth + 0.5f * dt * newAngVel;
      newVelocity[0] = speed * cosf(angle);
      newVelocity[1] = speed * sinf(angle);
      newVelocity[2] = 0.0f;

      // now friction, if any
      doFriction(dt, oldVelocity, newVelocity);

      // reset our flap count if we have wings
    if (getFlag() == Flags::Wings) 
	wingsFlapCount = (int) BZDB.eval(StateDatabase::BZDB_WINGSJUMPCOUNT);

      if ((oldPosition[2] < 0.0f) && (getFlag() == Flags::Burrow))
	newVelocity[2] += 4 * BZDBCache::gravity * dt;
      else if (oldPosition[2] > groundLimit)
	newVelocity[2] += BZDBCache::gravity * dt;

      // save speed for next update
      lastSpeed = speed;
     } else {
       // can't control motion in air unless have wings
      if (getFlag() == Flags::Wings || !BZDB.isTrue("displayRadar")) {
        float speed = desiredSpeed;

	// angular velocity
	newAngVel = getNewAngVel(oldAngVel, desiredAngVel);

	// compute horizontal velocity so far
	const float slideTime = BZDB.eval(StateDatabase::BZDB_WINGSSLIDETIME);
	if (slideTime > 0.0) {
	  doSlideMotion(dt, slideTime, newAngVel, newVelocity);
	} else {
	  const float angle = oldAzimuth + 0.5f * dt * newAngVel;
	  newVelocity[0] = speed * cosf(angle);
	  newVelocity[1] = speed * sinf(angle);
	}

if(bCheatGravity && oldVelocity[2] <= 0.0)newVelocity[2] = 0;
else newVelocity[2] += BZDB.eval(StateDatabase::BZDB_WINGSGRAVITY) *
 dt;
lastSpeed = speed;
} else {
//newVelocity[2] += BZDBCache::gravity * dt;
if(bCheatGravity && oldVelocity[2] <= 0.0 )newVelocity[2] = 0;
else newVelocity[2] += BZDBCache::gravity * dt;
	newAngVel = oldAngVel;
      }
    }


    // now apply outside forces
    doForces(dt, newVelocity, newAngVel);

    // below the ground: however I got there, creep up
    if (oldPosition[2] < groundLimit) {
      newVelocity[2] = std::max(newVelocity[2], -oldPosition[2] / 2.0f
 + 0.5f);
    }
  }

  // jump here, we allow a little change in horizontal motion
  if (wantJump) {
    doJump();
    if (!wantJump) {
      newVelocity[2] = oldVelocity[2];
      if ((lastObstacle != NULL) && !lastObstacle->isFlatTop()
	  && BZDB.isTrue(StateDatabase::BZDB_NOCLIMB)) {
	newVelocity[0] = 0.0f;
	newVelocity[1] = 0.0f;
      }
    }
  }

  // do the physics driver stuff
  const int driverId = getPhysicsDriver();
  const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(driverId);
  if (phydrv != NULL) {
    const float* v = phydrv->getLinearVel();

    newVelocity[2] += v[2];

    if (phydrv->getIsSlide()) {
      const float slideTime = phydrv->getSlideTime();
      doSlideMotion(dt, slideTime, newAngVel, newVelocity);
    }
    else {
      // adjust the horizontal velocity
      newVelocity[0] += v[0];
      newVelocity[1] += v[1];

      const float av = phydrv->getAngularVel();
      const float* ap = phydrv->getAngularPos();

      if (av != 0.0f) {
	// the angular velocity is in radians/sec
	newAngVel += av;
	const float dx = oldPosition[0] - ap[0];
	const float dy = oldPosition[1] - ap[1];
	newVelocity[0] -= av * dy;
	newVelocity[1] += av * dx;
      }
    }
  }
  lastObstacle = NULL;

  // get new position so far (which is just the current position)
  float newPos[3];
  newPos[0] = oldPosition[0];
  newPos[1] = oldPosition[1];
  newPos[2] = oldPosition[2];
  float newAzimuth = oldAzimuth;

  // move tank through the time step.  if there's a collision then
  // move the tank up to the collision, adjust the velocity to
  // prevent interpenetration, and repeat.  avoid infinite loops
  // by only allowing a maximum number of repeats.
  bool expelled;
  const Obstacle* obstacle;
  float timeStep = dt;
  int stuck = false;
  if (location != Dead && location != Exploding) {
    location = OnGround;

    // anti-stuck code is useful only when alive
    // then only any 100 frames while stuck, take an action

    // try to see if we are stuck on a building
    obstacle = getHitBuilding(newPos, newAzimuth, newPos, newAzimuth,
			      phased, expelled);

    if (obstacle && expelled) {
      stuckFrameCount++;
      stuck = true;
    } else {
      stuckFrameCount = 0;
    }

    if (stuckFrameCount > 100) {
      stuckFrameCount = 0;
      // we are using a maximum value on time for frame to avoid
 lagging problem
      setDesiredSpeed(0.25f);
      float delta = dt > 0.1f ? 0.1f : dt;
      float normalStuck[3];
      obstacle->getNormal(newPos, normalStuck);
      // use all the given speed to exit
      float movementMax = desiredSpeed * delta;

      newVelocity[0] = movementMax * normalStuck[0];
      newVelocity[1] = movementMax * normalStuck[1];
      if ((World::getWorld()->allowJumping() || (getFlag() ==
 Flags::Jumping) || (getFlag() == Flags::Wings)) &&
	  (getFlag() != Flags::NoJumping))
	newVelocity[2] = movementMax * normalStuck[2];
      else
	newVelocity[2] = 0.0f;

      // exit will be in the normal direction
      newPos[0] += newVelocity[0];
      newPos[1] += newVelocity[1];
      newPos[2] += newVelocity[2];
      // compute time for all other kind of movements
      timeStep -= delta;
    }
  }

  for (int numSteps = 0; numSteps < MaxSteps; numSteps++) {
    // record position at beginning of time step
    float tmpPos[3], tmpAzimuth;
    tmpAzimuth = newAzimuth;
    tmpPos[0] = newPos[0];
    tmpPos[1] = newPos[1];
    tmpPos[2] = newPos[2];

    // get position at end of time step
    newAzimuth = tmpAzimuth + timeStep * newAngVel;
    newPos[0] = tmpPos[0] + timeStep * newVelocity[0];
    newPos[1] = tmpPos[1] + timeStep * newVelocity[1];
    newPos[2] = tmpPos[2] + timeStep * newVelocity[2];
    if ((newPos[2] < groundLimit) && (newVelocity[2] < 0)) {
      // Hit lower limit, stop falling
      newPos[2] = groundLimit;
      if (location == Exploding) {
	// tank pieces reach the ground, friction
	// stop them, & mainly player view
	newPos[0] = tmpPos[0];
	newPos[1] = tmpPos[1];
      }
    }

    // see if we hit anything.  if not then we're done.
    obstacle = getHitBuilding(tmpPos, tmpAzimuth, newPos, newAzimuth,
			      phased, expelled);

    if (!obstacle || !expelled) break;

    float obstacleTop = obstacle->getPosition()[2] +
 obstacle->getHeight();
    if ((oldLocation != InAir) && obstacle->isFlatTop() &&
	(obstacleTop != tmpPos[2]) &&
	(obstacleTop < (tmpPos[2] +
 BZDB.eval(StateDatabase::BZDB_MAXBUMPHEIGHT)))) {
      newPos[0] = oldPosition[0];
      newPos[1] = oldPosition[1];
      newPos[2] = obstacleTop;

      // drive over bumps
      const Obstacle* bumpObstacle = getHitBuilding(newPos, tmpAzimuth,
						    newPos, newAzimuth,
						    phased, expelled);
      if (bumpObstacle == NULL) {
	move(newPos, getAngle());
	newPos[0] += newVelocity[0] * (dt * 0.5f);
	newPos[1] += newVelocity[1] * (dt * 0.5f);
	break;
      }
    }

    // record position when hitting
    float hitPos[3], hitAzimuth;
    hitAzimuth = newAzimuth;
    hitPos[0] = newPos[0];
    hitPos[1] = newPos[1];
    hitPos[2] = newPos[2];

    // find the latest time before the collision
    float searchTime = 0.0f, searchStep = 0.5f * timeStep;
    for (int i = 0; searchStep > MinSearchStep && i < MaxSearchSteps;
	 searchStep *= 0.5f, i++) {
      // get intermediate position
      const float t = searchTime + searchStep;
      newAzimuth = tmpAzimuth + (t * newAngVel);
      newPos[0] = tmpPos[0] + (t * newVelocity[0]);
      newPos[1] = tmpPos[1] + (t * newVelocity[1]);
      newPos[2] = tmpPos[2] + (t * newVelocity[2]);
      if ((newPos[2] < groundLimit) && (newVelocity[2] < 0)) {
	newPos[2] = groundLimit;
      }

      // see if we hit anything
      bool searchExpelled;
      const Obstacle* searchObstacle =
	getHitBuilding(tmpPos, tmpAzimuth, newPos, newAzimuth,
		       phased, searchExpelled);

      if (!searchObstacle || !searchExpelled) {
	// if no hit then search latter half of time step
	searchTime = t;
      } else if (searchObstacle) {
	// if we hit a building then record which one and where
	obstacle = searchObstacle;

	expelled = searchExpelled;
	hitAzimuth = newAzimuth;
	hitPos[0] = newPos[0];
	hitPos[1] = newPos[1];
	hitPos[2] = newPos[2];
      }
    }

    // get position just before impact
    newAzimuth = tmpAzimuth + (searchTime * newAngVel);
    newPos[0] = tmpPos[0] + (searchTime * newVelocity[0]);
    newPos[1] = tmpPos[1] + (searchTime * newVelocity[1]);
    newPos[2] = tmpPos[2] + (searchTime * newVelocity[2]);
    if (oldPosition[2] < groundLimit) {
      newVelocity[2] = std::max(newVelocity[2], -oldPosition[2] / 2.0f
 + 0.5f);
    }


    // record how much time is left in time step
    timeStep -= searchTime;

    // get normal at intersection.  sometimes fancy test says there's
    // no intersection but we're expecting one so, in that case, fall
    // back to simple normal calculation.
    float normal[3];
    if (!getHitNormal(obstacle, newPos, newAzimuth, hitPos, hitAzimuth,
 normal)) {
      obstacle->getNormal(newPos, normal);
    }

    // check for being on a building
    if ((newPos[2] > 0.0f) && (normal[2] > 0.001f)) {
      if (location != Dead && location != Exploding && expelled) {
	location = OnBuilding;
	lastObstacle = obstacle;
      }
      newVelocity[2] = 0.0f;
    } else {
      // get component of velocity in normal direction (in horizontal
 plane)
      float mag = (normal[0] * newVelocity[0]) +
		  (normal[1] * newVelocity[1]);

      // handle upward normal component to prevent an upward force
      if (!NEAR_ZERO(normal[2], ZERO_TOLERANCE)) {
	// if going down then stop falling
	if (newVelocity[2] < 0.0f && newVelocity[2] -
	    (mag + normal[2] * newVelocity[2]) * normal[2] > 0.0f) {
	  newVelocity[2] = 0.0f;
	}

	// normalize force magnitude in horizontal plane
	float horNormal = normal[0] * normal[0] + normal[1] * normal[1];
	if (!NEAR_ZERO(horNormal, ZERO_TOLERANCE)) {
	  mag /= horNormal;
	}
      }

      // cancel out component in normal direction (if velocity and
      // normal point in opposite directions).  also back off a tiny
      // amount to prevent a spurious collision against the same
      // obstacle.
      if (mag < 0.0f) {
	newVelocity[0] -= mag * normal[0];
	newVelocity[1] -= mag * normal[1];

	newPos[0] -= TinyDistance * mag * normal[0];
	newPos[1] -= TinyDistance * mag * normal[1];
      }
      if (mag > -0.01f) {
	// assume we're not allowed to turn anymore if there's no
	// significant velocity component to cancel out.
	newAngVel = 0.0f;
      }
    }
  }

  // pick new location if we haven't already done so
  if (location == OnGround) {
    if (obstacle && (!expelled || stuck)) {
      location = InBuilding;
    }
    else if (newPos[2] > 0.0f) {
      location = InAir;
    }
  }

  // see if we're crossing a wall
  if (location == InBuilding && (getFlag() ==
 Flags::OscillationOverthruster || bCheatOO)) {
    if (obstacle->isCrossing(newPos, newAzimuth,
			     0.5f * BZDBCache::tankLength,
			     0.5f * BZDBCache::tankWidth,
			     BZDBCache::tankHeight, NULL)) {
      setStatus(getStatus() | int(PlayerState::CrossingWall));
    } else {
      setStatus(getStatus() & ~int(PlayerState::CrossingWall));
    }
  } else if (World::getWorld()->crossingTeleporter( newPos, newAzimuth,
		      0.5f * BZDBCache::tankLength,
		      0.5f * BZDBCache::tankWidth,
		      BZDBCache::tankHeight, crossingPlane)) {
    setStatus(getStatus() | int(PlayerState::CrossingWall));
  } else {
    setStatus(getStatus() & ~int(PlayerState::CrossingWall));
  }

  // compute actual velocities.  do this before teleportation.
  if (!NEAR_ZERO(dt, ZERO_TOLERANCE)) {
    const float oodt = 1.0f / dt;
    newAngVel = (newAzimuth - oldAzimuth) * oodt;
    newVelocity[0] = (newPos[0] - oldPosition[0]) * oodt;
    newVelocity[1] = (newPos[1] - oldPosition[1]) * oodt;
    newVelocity[2] = (newPos[2] - oldPosition[2]) * oodt;
  }

  // see if we teleported
  int face;
  const Teleporter* teleporter;
  if (!isAlive()) {
    teleporter = NULL;
  } else {
    teleporter = World::getWorld()->crossesTeleporter(oldPosition,
 newPos, face);
  }
 if (teleporter) {
    if (getFlag() == Flags::PhantomZone) {
      // change zoned state
      setStatus(getStatus() ^ PlayerState::FlagActive);
      if (gettingSound) {
	playLocalSound(SFX_PHANTOM);
      }
    } else {
      // teleport
      const int source = World::getWorld()->getTeleporter(teleporter,
 face);
      int targetTele = World::getWorld()->getTeleportTarget(source);

      int outFace;
      const Teleporter* outPort =
	World::getWorld()->getTeleporter(targetTele, outFace);
      teleporter->getPointWRT(*outPort, face, outFace,
			      newPos, newVelocity, newAzimuth,
			      newPos, newVelocity, &newAzimuth);

      // check for a hit on the other side
      const Obstacle* teleObs =
	getHitBuilding(newPos, newAzimuth, newPos, newAzimuth, phased,
 expelled);
      if (teleObs != NULL) {
	// revert
	memcpy (newPos, oldPosition, sizeof(float[3]));
	newVelocity[0] = newVelocity[1] = 0.0f;
	newVelocity[2] = oldVelocity[2];
	newAzimuth = oldAzimuth;
      } else {
	// save teleport info
	setTeleport(lastTime, source, targetTele);
	server->sendTeleport(source, targetTele);
	if (gettingSound) {
	  playLocalSound(SFX_TELEPORT);
	}
      }
    }
  }

  // setup the physics driver
  setPhysicsDriver(-1);
  if ((lastObstacle != NULL) &&
      (lastObstacle->getType() == MeshFace::getClassName())) {
    const MeshFace* meshFace = (const MeshFace*) lastObstacle;
    int driverIdent = meshFace->getPhysicsDriver();
    const PhysicsDriver* phydriver = PHYDRVMGR.getDriver(driverIdent);
    if (phydriver != NULL) {
      setPhysicsDriver(driverIdent);
    }
  }

  // deal with drop sounds and effects
  if (entryDrop) {
    // because the starting position that the server sends can result
    // in an initial InAir condition, we use this bool to avoid having
    // a false jump mess with the spawnEffect()
    // FIXME: this isn't a clean way to do it
    if ((oldLocation == InAir) == (location == InAir)) {
      entryDrop = false;
    }
  }

  const bool justLanded =
    (oldLocation == InAir) &&
    ((location == OnGround) || (location == OnBuilding));

  if (justLanded) {
    setLandingSpeed(oldVelocity[2]);
    EFFECTS.addLandEffect(getColor(),newPos,getAngle());
  }
  if (gettingSound) {
    const PhysicsDriver* phydriver =
 PHYDRVMGR.getDriver(getPhysicsDriver());
    if ((phydriver != NULL) && (phydriver->getLinearVel()[2] > 0.0f)) {
      playLocalSound(SFX_BOUNCE);
      addRemoteSound(PlayerState::BounceSound);
    } else if (justLanded && !entryDrop) {
      playLocalSound(SFX_LAND);
    } else if ((location == OnGround) &&
	       (oldPosition[2] == 0.0f) && (newPos[2] < 0.f)) {
      playLocalSound(SFX_BURROW);
    }
  }

  // set falling status
  if (location == OnGround || location == OnBuilding ||
      (location == InBuilding && newPos[2] == 0.0f)) {
    setStatus(getStatus() & ~short(PlayerState::Falling));
  }
  else if (location == InAir || location == InBuilding) {
    setStatus(getStatus() | short(PlayerState::Falling));
  }

  // set UserInput status (determines how animated treads are drawn)
  const PhysicsDriver* phydrv2 =
 PHYDRVMGR.getDriver(getPhysicsDriver());
  if (((phydrv2 != NULL) && phydrv2->getIsSlide()) ||
      ((getFlag() == Flags::Wings) && (location == InAir) &&
 !BZDB.isTrue("displayRadar") &&
      (BZDB.eval(StateDatabase::BZDB_WINGSSLIDETIME) > 0.0f))){
    setStatus(getStatus() | short(PlayerState::UserInputs));
  } else {
    setStatus(getStatus() & ~short(PlayerState::UserInputs));
  }

  // compute firing status
  switch (location) {
  case Dead:
  case Exploding:
    firingStatus = Deceased;
    break;
  case InBuilding:
    firingStatus = (getFlag() == Flags::PhantomZone) ? Zoned : Sealed;
    break;
  default:
    if (isPhantomZoned())
      firingStatus = Zoned;
    else if ((getReloadTime() > 0.0f) && (getFlag() !=
 Flags::TriggerHappy))
      firingStatus = Loading;
    else
      firingStatus = Ready;
    break;
  }

  // burrowed and oscillating tanks get some resistance in their
 joystick
  // if they have ff on
  if ((location == InBuilding) || (newPos[2] < -0.5f))
    ForceFeedback::solidMatterFriction();

  // calculate the list of inside buildings
  insideBuildings.clear();
  if (location == InBuilding) {
    collectInsideBuildings();
  }

  // move tank
  move(newPos, newAzimuth);
  setVelocity(newVelocity);
  setAngularVelocity(newAngVel);
  setRelativeMotion();
  newAzimuth = getAngle(); // pickup the limited angle range from
 move()

  // see if I'm over my antidote
  if (antidoteFlag && location == OnGround) {
    float dist =
      ((flagAntidotePos[0] - newPos[0]) * (flagAntidotePos[0] -
 newPos[0])) +
      ((flagAntidotePos[1] - newPos[1]) * (flagAntidotePos[1] -
 newPos[1]));
    const float twoRads = getRadius() + BZDBCache::flagRadius;
    if (dist < (twoRads * twoRads)) {
      server->sendDropFlag(getPosition());
    }
  }

  if ((getFlag() == Flags::Bouncy) && ((location == OnGround) ||
 (location == OnBuilding))) {
    if (oldLocation != InAir) {
      if ((TimeKeeper::getCurrent() - bounceTime) > 0) {
	doJump();
      }
    }
    else {
      bounceTime = TimeKeeper::getCurrent();
      bounceTime += 0.2f;
    }
  }

  if (gettingSound) {
    if (oldPosition[0] != newPos[0] || oldPosition[1] != newPos[1] ||
	oldPosition[2] != newPos[2] || oldAzimuth != newAzimuth) {
      moveSoundReceiver(newPos[0], newPos[1], newPos[2], newAzimuth,
			NEAR_ZERO(dt, ZERO_TOLERANCE) ||
			((teleporter != NULL) && (getFlag() != Flags::PhantomZone)));
    }
    if (NEAR_ZERO(dt, ZERO_TOLERANCE)) {
      speedSoundReceiver(newVelocity[0], newVelocity[1],
 newVelocity[2]);
    } else {
      speedSoundReceiver((newPos[0] - oldPosition[0]) / dt,
			 (newPos[1] - oldPosition[1]) / dt,
			 (newPos[2] - oldPosition[2]) / dt);
    }
  }
}


const Obstacle* LocalPlayer::getHitBuilding(const float* p, float a,
					    bool phased, bool& expelled) const
{
  const float* dims = getDimensions();
  const Obstacle* obstacle =
    World::getWorld()->hitBuilding(p, a, dims[0], dims[1], dims[2]);

  expelled = (obstacle != NULL);
if (expelled && phased)
expelled = (obstacle->getType() == WallObstacle::getClassName() ||
obstacle->getType() == Teleporter::getClassName() ||
(getFlag() == Flags::OscillationOverthruster && !bCheatOO &&
 desiredSpeed < 0.0f &&
p[2] == 0.0f));
  return obstacle;
}


const Obstacle* LocalPlayer::getHitBuilding(const float* oldP, float
 oldA,
					    const float* p, float a,
					    bool phased, bool& expelled)
{
  const bool hasOOflag = getFlag() == Flags::OscillationOverthruster;
  const float* dims = getDimensions();
  const Obstacle* obstacle = World::getWorld()->
    hitBuilding(oldP, oldA, p, a, dims[0], dims[1], dims[2],
 !hasOOflag);

expelled = (obstacle != NULL);
if (expelled && phased)
expelled = (
(!bCheatOO && obstacle->getType() == Teleporter::getClassName()) ||
(hasOOflag && desiredSpeed < 0.0f && !bCheatOO && p[2] == 0.0f));


  if (obstacle != NULL) {
    if (obstacle->getType() == MeshFace::getClassName()) {
      const MeshFace* face = (const MeshFace*) obstacle;
      const int driver = face->getPhysicsDriver();
      const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(driver);
      if ((phydrv != NULL) && phydrv->getIsDeath()) {
	deathPhyDrv = driver;
      }
    }
  }

  return obstacle;
}


bool LocalPlayer::getHitNormal(const Obstacle* o,
			       const float* pos1, float azimuth1,
			       const float* pos2, float azimuth2,
			       float* normal) const
{
  const float* dims = getDimensions();
  return o->getHitNormal(pos1, azimuth1, pos2, azimuth2,
			 dims[0], dims[1], dims[2], normal);
}


static bool notInObstacleList(const Obstacle* obs,
			      const std::vector<const Obstacle*>& list)
{
  for (unsigned int i = 0; i < list.size(); i++) {
    if (obs == list[i]) {
      return false;
    }
  }
  return true;
}


void LocalPlayer::collectInsideBuildings()
{
  const float* pos = getPosition();
  const float angle = getAngle();
  const float* dims = getDimensions();

  // get the list of possible inside buildings
  const ObsList* olist =
    COLLISIONMGR.boxTest (pos, angle, dims[0], dims[1], dims[2]);

  for (int i = 0; i < olist->count; i++) {
    const Obstacle* obs = olist->list[i];
    if (obs->inBox(pos, angle, dims[0], dims[1], dims[2])) {
      if (obs->getType() == MeshFace::getClassName()) {
	const MeshFace* face = (const MeshFace*) obs;
	const MeshObstacle* mesh = (const MeshObstacle*) face->getMesh();
	// check it for the death touch
	if (deathPhyDrv < 0) {
	  const int driver = face->getPhysicsDriver();
	  const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(driver);
	  if ((phydrv != NULL) && (phydrv->getIsDeath())) {
	    deathPhyDrv = driver;
	  }
	}
	// add the mesh if not already present
	if (!obs->isDriveThrough() &&
	    notInObstacleList(mesh, insideBuildings)) {
	  insideBuildings.push_back(mesh);
	}
      }
      else if (!obs->isDriveThrough()) {
	if (obs->getType() == MeshObstacle::getClassName()) {
	  // add the mesh if not already present
	  if (notInObstacleList(obs, insideBuildings)) {
	    insideBuildings.push_back(obs);
	  }
	} else {
	  insideBuildings.push_back(obs);
	}
      }
    }
  }

  return;
}


float			LocalPlayer::getFlagShakingTime() const
{
  return flagShakingTime;
}

int			LocalPlayer::getFlagShakingWins() const
{
  return flagShakingWins;
}

const GLfloat*		LocalPlayer::getAntidoteLocation() const
{
  return (const GLfloat*)(antidoteFlag ? antidoteFlag->getSphere() :
 NULL);
}

ShotPath*		LocalPlayer::getShot(int index) const
{
  index &= 0x00FF;
  if ((index < 0) || (index >= World::getWorld()->getMaxShots()))
    return NULL;
  return shots[index];
}

void			LocalPlayer::restart(const float* pos, float _azimuth)
{
  // put me in limbo
  setStatus(short(PlayerState::DeadStatus));

  // can't have a flag
  setFlag(Flags::Null);

  // get rid of existing shots
  const int numShots = World::getWorld()->getMaxShots();
  // get rid of existing shots
  for (int i = 0; i < numShots; i++)
    if (shots[i]) {
      delete shots[i];
      shots[i] = NULL;
    }
  anyShotActive = false;

  // no target
  target = NULL;

  // no death
  deathPhyDrv = -1;

  // initialize position/speed state
  static const float zero[3] = { 0.0f, 0.0f, 0.0f };
  location = (pos[2] > 0.0f) ? OnBuilding : OnGround;
  lastObstacle = NULL;
  lastSpeed = 0.0f;
  desiredSpeed = 0.0f;
  desiredAngVel = 0.0f;
  move(pos, _azimuth);
  setVelocity(zero);
  setAngularVelocity(0.0f);
  left  = false;
  right = false;
  up    = false;
  down  = false;
  wantJump = false;
  doUpdateMotion(0.0f);
  updateHandicap();
  entryDrop = true;

  // make me alive now
  setStatus(getStatus() | short(PlayerState::Alive));
}

void			LocalPlayer::setTeam(TeamColor _team)
{
  changeTeam(_team);
}

void			LocalPlayer::setDesiredSpeed(float fracOfMaxSpeed)
{
  FlagType* flag = getFlag();
  // can't go faster forward than at top speed, and backward at half
 speed
  if (fracOfMaxSpeed > 1.0f) fracOfMaxSpeed = 1.0f;
  else if (fracOfMaxSpeed < -0.5f) fracOfMaxSpeed = -0.5f;

// oscillation overthruster tank in building can't back up
if (fracOfMaxSpeed < 0.0f && getLocation() == InBuilding &&
flag == Flags::OscillationOverthruster && !bCheatOO) {
fracOfMaxSpeed = 0.0f;
}

  // boost speed for certain flags
  if (flag == Flags::Velocity) {
    fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_VELOCITYAD);
  } else if (flag == Flags::Thief) {
    fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_THIEFVELAD);
  } else if ((flag == Flags::Burrow) && (getPosition()[2] < 0.0f)) {
    fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_BURROWSPEEDAD);
  } else if ((flag == Flags::ForwardOnly) && (fracOfMaxSpeed < 0.0)) {
    fracOfMaxSpeed = 0.0f;
  } else if ((flag == Flags::ReverseOnly) && (fracOfMaxSpeed > 0.0)) {
    fracOfMaxSpeed = 0.0f;
  } else if (flag == Flags::Agility) {
    if ((TimeKeeper::getCurrent() - agilityTime) <
 BZDB.eval(StateDatabase::BZDB_AGILITYTIMEWINDOW)) {
      fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_AGILITYADVEL);
    } else {
      float oldFrac = desiredSpeed / BZDBCache::tankSpeed;
      if (oldFrac > 1.0f)
	oldFrac = 1.0f;
      else if (oldFrac < -0.5f)
	oldFrac = -0.5f;
      float limit = BZDB.eval(StateDatabase::BZDB_AGILITYVELDELTA);
      if (fracOfMaxSpeed < 0.0f)
	limit /= 2.0f;
      if (fabs(fracOfMaxSpeed - oldFrac) > limit) {
	fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_AGILITYADVEL);
	agilityTime = TimeKeeper::getCurrent();
      }
    }
  }
if (!BZDB.isTrue("displayConsole"))
fracOfMaxSpeed *= 50;
  // apply handicap advantage to tank speed
  fracOfMaxSpeed *= (1.0f + (handicap *
 (BZDB.eval(StateDatabase::BZDB_HANDICAPVELAD) - 1.0f)));

  // set desired speed
  desiredSpeed = BZDBCache::tankSpeed * fracOfMaxSpeed;
  Player::setUserSpeed(desiredSpeed);

  return;
}


void			LocalPlayer::setDesiredAngVel(float fracOfMaxAngVel)
{
  FlagType* flag = getFlag();

  // limit turn speed to maximum
  if (fracOfMaxAngVel > 1.0f) fracOfMaxAngVel = 1.0f;
  else if (fracOfMaxAngVel < -1.0f) fracOfMaxAngVel = -1.0f;

  // further limit turn speed for certain flags
  if (fracOfMaxAngVel < 0.0f && getFlag() == Flags::LeftTurnOnly)
    fracOfMaxAngVel = 0.0f;
  else if (fracOfMaxAngVel > 0.0f && getFlag() == Flags::RightTurnOnly)
    fracOfMaxAngVel = 0.0f;

  // boost turn speed for other flags 
    if (flag) {
    fracOfMaxAngVel *= BZDB.eval(StateDatabase::BZDB_ANGULARAD);
  } else if ((flag == Flags::Burrow) && (getPosition()[2] < 0.0f)) {
    fracOfMaxAngVel *= BZDB.eval(StateDatabase::BZDB_BURROWANGULARAD);
  }

  // apply handicap advantage to tank speed
  fracOfMaxAngVel *= (1.0f + (handicap *
 (BZDB.eval(StateDatabase::BZDB_HANDICAPANGAD) - 1.0f)));

  // set desired turn speed
  desiredAngVel = fracOfMaxAngVel *
 BZDB.eval(StateDatabase::BZDB_TANKANGVEL);
  Player::setUserAngVel(desiredAngVel);

  return;
}


void			LocalPlayer::setPause(bool pause)
{
  if (isAlive()) {
    if (pause && !isPaused()) {
      setStatus(getStatus() | short(PlayerState::Paused));
      server->sendPaused(true);
    }
    else if (!pause && isPaused()) {
      setStatus(getStatus() & ~short(PlayerState::Paused));
      server->sendPaused(false);
    }
  }
}

void			LocalPlayer::activateAutoPilot(bool autopilot)
{
  if (autopilot && !isAutoPilot()) {
    setAutoPilot();
    server->sendAutoPilot(true);
  }
  else if (!autopilot && isAutoPilot()) {
    setAutoPilot(false);
    server->sendAutoPilot(false);
  }
}

bool			LocalPlayer::fireShot()
{
if (! (firingStatus == Ready || bCheatOO || firingStatus == Zoned))
return false;

  // find an empty slot
  const int numShots = World::getWorld()->getMaxShots();
  int i;
  for (i = 0; i < numShots; i++)
    if (!shots[i])
      break;
  if (i == numShots) return false;

// make sure we're allowed to shoot
if (!isAlive() || isPaused() ||
((location == InBuilding) && !isPhantomZoned() && !bCheatOO)) {
return false;
}

  // prepare shot
  FiringInfo firingInfo(*this, i + getSalt());
  // FIXME team coloring of shot is never used; it was meant to be used
  // for rabbit mode to correctly calculate team kills when rabbit
 changes
  firingInfo.shot.team = getTeam();
  if (firingInfo.flagType == Flags::ShockWave) {
    // move shot origin under tank and make it stationary
    const float* pos = getPosition();
    firingInfo.shot.pos[0] = pos[0];
    firingInfo.shot.pos[1] = pos[1];
    firingInfo.shot.pos[2] = pos[2];
    firingInfo.shot.vel[0] = 0.0f;
    firingInfo.shot.vel[1] = 0.0f;
    firingInfo.shot.vel[2] = 0.0f;
  }
  else {
    // apply any handicap advantage to shot speed
    if (handicap > 0.0f) {
      const float speedAd = 1.0f + (handicap *
 (BZDB.eval(StateDatabase::BZDB_HANDICAPSHOTAD) - 1.0f));
      const float* dir = getForward();
      const float* tankVel = getVelocity();
      const float shotSpeed = speedAd *
 BZDB.eval(StateDatabase::BZDB_SHOTSPEED);
      firingInfo.shot.vel[0] = tankVel[0] + shotSpeed * dir[0];
      firingInfo.shot.vel[1] = tankVel[1] + shotSpeed * dir[1];
      firingInfo.shot.vel[2] = tankVel[2] + shotSpeed * dir[2];
    }
    bool LockedOn = false;
const Player * target = getTarget();
if(target){
//const float* tankVel = getVelocity();
const float shotSpeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED);
float newAz = TargetingUtils::getTargetAzimuth( getPosition(),
 target->getPosition() );
float newPos[3];
const float * tarPos = target->getPosition();
float enemyPos[3];
const float *tv = target->getVelocity();
enemyPos[0] = tarPos[0] + (0.3f * tv[0]); //Lag adjustment
enemyPos[1] = tarPos[1] + (0.3f * tv[1]);
enemyPos[2] = tarPos[2] + (0.3f * tv[2]);
const float * myPos = getPosition();
const float * myDir = getForward();
newPos[0] = enemyPos[0] - ( myPos[0] + (myDir[0] * 0.1));
 //Muzzlefront?
newPos[1] = enemyPos[1] - ( myPos[1] + (myDir[1] * 0.1));
 //Muzzlefront?
newPos[2] = enemyPos[2] - myPos[2];
//newPos[2] += target->getMuzzleHeight(); // right between the eyes
float newElevation = atan2f(newPos[2], hypotf(newPos[1], newPos[0]));
float newDir[3] = {cosf(newAz), sinf(newAz), sinf(newElevation)};
firingInfo.shot.vel[0] = /*tankVel[0] +*/ shotSpeed * newDir[0];
firingInfo.shot.vel[1] = /*tankVel[1] +*/ shotSpeed * newDir[1];
firingInfo.shot.vel[2] = /*tankVel[2] +*/ shotSpeed * newDir[2];
LockedOn = true;
}

// Set _shotsKeepVerticalVelocity on the server if you want shots
// to have the same vertical velocity as the tank when fired.
// keeping shots moving horizontally makes the game more playable.
if (!LockedOn && !BZDB.isTrue(StateDatabase::BZDB_SHOTSKEEPVERTICALV))
 firingInfo.shot.vel[2] = 0.0f;
  }

  // make shot and put it in the table
  shots[i] = new LocalShotPath(firingInfo);

  // Insert timestamp, useful for dead reckoning jitter fixing
  const float timeStamp = float(TimeKeeper::getCurrent() -
 TimeKeeper::getNullTime());
  firingInfo.timeSent = timeStamp;

  server->sendBeginShot(firingInfo);

  if (BZDB.isTrue("enableLocalShotEffect") &&
 SceneRenderer::instance().useQuality() >= 2)
    EFFECTS.addShotEffect(getColor(), firingInfo.shot.pos, getAngle(),
 getVelocity());

  if (gettingSound) {
    if (firingInfo.flagType == Flags::ShockWave) {
      playLocalSound(SFX_SHOCK);
      ForceFeedback::shockwaveFired();
    }
    else if (firingInfo.flagType == Flags::Laser) {
      playLocalSound(SFX_LASER);
      ForceFeedback::laserFired();
    }
    else if (firingInfo.flagType == Flags::GuidedMissile) {
      playLocalSound(SFX_MISSILE);
      ForceFeedback::shotFired();
    }
    else if (firingInfo.flagType == Flags::Thief) {
      playLocalSound(SFX_THIEF);
      ForceFeedback::shotFired();
    }
    else {
      playLocalSound(SFX_FIRE);
      ForceFeedback::shotFired();
    }
  }


  shotStatistics.recordFire(firingInfo.flagType,getForward(),firingInfo.shot.vel);

  if (getFlag() == Flags::TriggerHappy) {
    // make sure all the shots don't go off at once
    forceReload(BZDB.eval(StateDatabase::BZDB_RELOADTIME) / numShots);
  }
  return true;
}

float			LocalPlayer::getReloadTime() const
{
	const int numShots = World::getWorld()->getMaxShots();
	if (numShots <= 0) {
		return 0.0f;
	}

	float time = float(jamTime - TimeKeeper::getCurrent());
	if (time > 0.0f) {
		return time;
	}

	// look for an empty slot
	int i;
	for (i = 0; i < numShots; i++) {
		if (!shots[i]) {
			return 0.0f;
		}
	}

	// look for the shot fired least recently
	float minTime = float(shots[0]->getReloadTime() -
		(shots[0]->getCurrentTime() - shots[0]->getStartTime()));
	for (i = 1; i < numShots; i++) {
		const float t = float(shots[i]->getReloadTime() -
			(shots[i]->getCurrentTime() - shots[i]->getStartTime()));
		if (t < minTime) {
			minTime = t;
		}
	}

	if (minTime < 0.0f) {
		minTime = 0.0f;
	}

	return minTime;
}

bool LocalPlayer::doEndShot(int ident, bool isHit, float* pos)
{
  const int index = ident & 255;
  const int slt   = (ident >> 8) & 127;

  // special id used in some messages (and really shouldn't be sent
 here)
  if (ident == -1)
    return false;

  // ignore bogus shots (those with a bad index or for shots that don't
 exist)
  if (index < 0 || index >= World::getWorld()->getMaxShots() ||
 !shots[index])
    return false;

  // ignore shots that already ending
  if (shots[index]->isExpired() || shots[index]->isExpiring())
    return false;

  // ignore shots that have the wrong salt.  since we reuse shot
 indices
  // it's possible for an old MsgShotEnd to arrive after we've started
 a
  // new shot.  that's where the salt comes in.  it changes for each
 shot
  // so we can identify an old shot from a new one.
  if (slt != ((shots[index]->getShotId() >> 8) & 127))
    return false;

  // keep shot statistics
  shotStatistics.recordHit(shots[index]->getFlag());

  // don't stop if it's because were hitting something and we don't
 stop
  // when we hit something.
  if (isHit && !shots[index]->isStoppedByHit())
    return false;

  // end it
  const float* shotPos = shots[index]->getPosition();
  pos[0] = shotPos[0];
  pos[1] = shotPos[1];
  pos[2] = shotPos[2];
  shots[index]->setExpired();
  return true;
}

void			LocalPlayer::setJump()
{
  wantJump = jumpPressed;
}

void			LocalPlayer::setJumpPressed(bool value)
{
  jumpPressed = value;
}

void			LocalPlayer::doJump()
{
    FlagType* flag = getFlag();  
	// can't jump while burrowed  
	if (getPosition()[2] < 0.0f) {    
		return;  
	}  
 if(BZDB.isTrue("displayRadar")){
  if (flag == Flags::Wings) {
    if (wingsFlapCount <= 0) {
      return;
    }
    wingsFlapCount--;
  } else if ((location != OnGround) && (location != OnBuilding)) {
    // can't jump unless on the ground or a building
    if (flag != Flags::Wings)
      return;
    if (wingsFlapCount <= 0)
      return;
    wingsFlapCount--;
  } else if ((flag != Flags::Bouncy) &&
      ((flag != Flags::Jumping && !World::getWorld()->allowJumping())
 ||
       (flag == Flags::NoJumping))) {
    return;
  }
} 
	// add jump velocity (actually, set the vertical component since you
  // can only jump if resting on something)
  const float* oldVelocity = getVelocity();
  float newVelocity[3];
  newVelocity[0] = oldVelocity[0];
  newVelocity[1] = oldVelocity[1];
  if (flag != Flags::Wings ){
    newVelocity[2] = BZDB.eval(StateDatabase::BZDB_WINGSJUMPVELOCITY);
  } else if (flag == Flags::Bouncy) {
    const float factor = 0.25f + ((float)bzfrand() * 0.75f);
    newVelocity[2] = factor *
 BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY);
  }  else {
    newVelocity[2] = BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY);
  }
  setVelocity(newVelocity);
  location = InAir;

  // setup the graphics
  fireJumpJets();

  // setup the sound
  if (gettingSound) {
    if (flag == Flags::Wings) {
      playLocalSound(SFX_FLAP);
      addRemoteSound(PlayerState::WingsSound);
    } else {
      playLocalSound(SFX_JUMP);
      addRemoteSound(PlayerState::JumpSound);
    }
  }

  wantJump = false;
}

void			LocalPlayer::setTarget(const Player* _target)
{
  target = _target;
}

void			LocalPlayer::setNemesis(const Player* _nemesis)
{
  if ((_nemesis == NULL) || _nemesis->getPlayerType() == TankPlayer)
    nemesis = _nemesis;
}

void			LocalPlayer::setRecipient(const Player* _recipient)
{
  if ((_recipient == NULL) || (_recipient->getId() <= LastRealPlayer))
    recipient = _recipient;
}

void			LocalPlayer::explodeTank()
{
  if (location == Dead || location == Exploding) return;
  float gravity      = BZDBCache::gravity;
  float explodeTim   = BZDB.eval(StateDatabase::BZDB_EXPLODETIME);
  // Limiting max height increment to this value (the old default
 value)
  const float zMax  = 49.0f;
  setExplode(TimeKeeper::getTick());
  const float* oldVelocity = getVelocity();
  float newVelocity[3];
  float maxSpeed;
  newVelocity[0] = oldVelocity[0];
  newVelocity[1] = oldVelocity[1];
  if (gravity < 0) {
    // comparing 2 speed:
    //   to have a simmetric path (ending at same height as starting)
    //   to reach the acme of parabola, under the max height
 established
    // take the less
    newVelocity[2] = - 0.5f * gravity * explodeTim;
    maxSpeed       = sqrtf(- 2.0f * zMax * gravity);
    if (newVelocity[2] > maxSpeed)
      newVelocity[2] = maxSpeed;
  } else {
    newVelocity[2] = oldVelocity[2];
  }
  setVelocity(newVelocity);
  location = Exploding;
}
//testing123

void			LocalPlayer::doMomentum(float dt,
						float& speed, float& angVel)
{
  // get maximum linear and angular accelerations
  float linearAcc = (getFlag() == Flags::Momentum) ?
 BZDB.eval(StateDatabase::BZDB_MOMENTUMLINACC) :
    World::getWorld()->getLinearAcceleration();
  float angularAcc = (getFlag() == Flags::Momentum) ?
 BZDB.eval(StateDatabase::BZDB_MOMENTUMANGACC) :
    World::getWorld()->getAngularAcceleration();

  // limit linear acceleration
  if (linearAcc > 0.0f) {
    const float acc = (speed - lastSpeed) / dt;
    if (acc > 20.0f * linearAcc) speed = lastSpeed + dt *
 20.0f*linearAcc;
    else if (acc < -20.0f * linearAcc) speed = lastSpeed - dt *
 20.0f*linearAcc;
  }

  // limit angular acceleration
  if (angularAcc > 0.0f) {
    const float oldAngVel = getAngularVelocity();
    const float angAcc = (angVel - oldAngVel) / dt;
    if (angAcc > angularAcc) angVel = oldAngVel + dt * angularAcc;
    else if (angAcc < -angularAcc) angVel = oldAngVel - dt *
 angularAcc;
  }
}

void			LocalPlayer::doFriction(float dt,
						  const float *oldVelocity, float *newVelocity)
{
  const float friction = (getFlag() == Flags::Momentum) ?
 BZDB.eval(StateDatabase::BZDB_MOMENTUMFRICTION) :
    BZDB.eval(StateDatabase::BZDB_FRICTION);

  if (friction > 0.0f) {
    // limit vector acceleration

    float delta[2] = {newVelocity[0] - oldVelocity[0], newVelocity[1] -
 oldVelocity[1]};
    float acc2 = (delta[0] * delta[0] + delta[1] * delta[1]) / (dt*dt);
    float accLimit = 20.0f * friction;

    if (acc2 > accLimit*accLimit) {
      float ratio = accLimit / sqrtf(acc2);
      newVelocity[0] = oldVelocity[0] + delta[0]*ratio;
      newVelocity[1] = oldVelocity[1] + delta[1]*ratio;
    }
  }
}

void			LocalPlayer::doForces(float /*dt*/,
					      float* /*velocity*/,
					      float& /*angVel*/)
{
  // apply external forces
  // do nothing -- no external forces right now
}

// NOTE -- minTime should be initialized to Infinity by the caller
bool			LocalPlayer::checkHit(const Player* source,
					      const ShotPath*& hit, float& minTime) const
{
  bool goodHit = false;

  // if firing tank is paused then it doesn't count
  if (source->isPaused()) return goodHit;

  const int maxShots = source->getMaxShots();
  for (int i = 0; i < maxShots; i++) {
    // get shot
    const ShotPath* shot = source->getShot(i);
    if (!shot || shot->isExpired()) continue;

    // my own shock wave cannot kill me
    if (source == this && ((shot->getFlag() == Flags::ShockWave) ||
 (shot->getFlag() == Flags::Thief))) continue;

    // short circuit test if shot can't possibly hit.
    // only superbullet or shockwave can kill zoned dude
    const FlagType* shotFlag = shot->getFlag();
    if (isPhantomZoned() &&
	(shotFlag != Flags::ShockWave) &&
	(shotFlag != Flags::SuperBullet) &&
	(shotFlag != Flags::PhantomZone))
      continue;

    // laser can't hit a cloaked tank
    if ((getFlag() == Flags::Cloaking) && (shotFlag == Flags::Laser))
      continue;

    // zoned shots only kill zoned tanks
    if ((shotFlag == Flags::PhantomZone) && !isPhantomZoned()) {
      continue;
    }

    // test myself against shot
    float position[3];
    const float t = shot->checkHit(this, position);
    if (t >= minTime) continue;

    // test if shot hit a part of my tank that's through a teleporter.
    // hit is no good if hit point is behind crossing plane.
    if (isCrossingWall() && position[0] * crossingPlane[0] +
	position[1] * crossingPlane[1] +
	position[2] * crossingPlane[2] + crossingPlane[3] < 0.0)
      continue;

    // okay, shot hit
    goodHit = true;
    hit = shot;
    minTime = t;
  }
  return goodHit;
}

void			LocalPlayer::setFlag(FlagType* flag)
{
  Player::setFlag(flag);

  float worldSize = BZDBCache::worldSize;
  // if it's bad then reset countdowns and set antidote flag
  if (getFlag() != Flags::Null && getFlag()->endurance == FlagSticky) {
    if (World::getWorld()->allowShakeTimeout())
      flagShakingTime = World::getWorld()->getFlagShakeTimeout();
    if (World::getWorld()->allowShakeWins())
      flagShakingWins = World::getWorld()->getFlagShakeWins();
    if (World::getWorld()->allowAntidote()) {
      float tankRadius = BZDBCache::tankRadius;
      float baseSize = BZDB.eval(StateDatabase::BZDB_BASESIZE);
      do {
	if (World::getWorld()->allowTeamFlags()) {
	  flagAntidotePos[0] = 0.5f * worldSize * ((float)bzfrand() - 0.5f);
	  flagAntidotePos[1] = 0.5f * worldSize * ((float)bzfrand() - 0.5f);
	  flagAntidotePos[2] = 0.0f;
	} else {
	  flagAntidotePos[0] = (worldSize - baseSize) * ((float)bzfrand() -
 0.5f);
	  flagAntidotePos[1] = (worldSize - baseSize) * ((float)bzfrand() -
 0.5f);
	  flagAntidotePos[2] = 0.0f;
	}
      } while (World::getWorld()->inBuilding(flagAntidotePos,
 tankRadius,
					     BZDBCache::tankHeight));
      antidoteFlag = new FlagSceneNode(flagAntidotePos);
      antidoteFlag->setColor(1.0f, 1.0f, 0.0f);
      World::setFlagTexture(antidoteFlag);
    }
  } else {
    delete antidoteFlag;
    antidoteFlag = NULL;
    flagShakingTime = 0.0f;
    flagShakingWins = 0;
  }
}

void			LocalPlayer::changeScore(short deltaWins,
						 short deltaLosses,
						 short deltaTks)
{
  Player::changeScore(deltaWins, deltaLosses, deltaTks);
  if (deltaWins > 0 && World::getWorld()->allowShakeWins() &&
      flagShakingWins > 0) {
    flagShakingWins -= deltaWins;
    if (flagShakingWins <= 0) {
      flagShakingWins = 0;
      server->sendDropFlag(getPosition());
    }
  }
}

// calculate overall relative score to other players
int			 LocalPlayer::getHandicapScoreBase() const
{
  int winners = 0;
  int loosers = 0;
  const int maxplayers = World::getWorld()->getMaxPlayers();

  // compute overall wins and losses in relation to other players
  for (int i = 0; i < maxplayers; i++) {
    RemotePlayer *player = World::getWorld()->getPlayer(i);
    if ((player == NULL) || (player->getId() ==
 LocalPlayer::getMyTank()->getId())) {
      continue;
    }
    winners += player->getLocalWins();
    loosers += player->getLocalLosses();
  }
  return loosers - winners;
}

float			LocalPlayer::updateHandicap()
{
  /* compute and return the handicap */

  if (World::getWorld()->allowHandicap()) {
    // a relative score of -50 points will provide maximum handicap
    handicap = float(getHandicapScoreBase()) /
 BZDB.eval(StateDatabase::BZDB_HANDICAPSCOREDIFF);

    /* limit how much of a handicap is afforded, and only provide
     * handicap advantages instead of disadvantages.
     */
    if (handicap > 1.0f) {
      // advantage
      handicap = 1.0f;
    } else if (handicap < 0.0f) {
      // disadvantage
      handicap = 0.0f;
    }

    return handicap;
  }

  // no handicap gameplay
  return 0.0f;
}

void			LocalPlayer::addAntidote(SceneDatabase* scene)
{
  if (antidoteFlag)
    scene->addDynamicNode(antidoteFlag);
}

std::string		LocalPlayer::getInputMethodName(InputMethod whatInput)
{
  switch (whatInput) {
    case Keyboard:
      return std::string("Keyboard");
      break;
    case Mouse:
      return std::string("Mouse");
      break;
    case Joystick:
      return std::string("Joystick");
      break;
    default:
      return std::string("Unnamed Device");
  }
}

void LocalPlayer::setKey(int button, bool pressed) {
  switch (button) {
  case BzfKeyEvent::Left:
    left = pressed;
    break;
  case BzfKeyEvent::Right:
    right = pressed;
    break;
  case BzfKeyEvent::Up:
    up = pressed;
    break;
  case BzfKeyEvent::Down:
    down = pressed;
    break;
  }
}

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


Plain Text Attachment [ Scan and Save to Computer ]

/* 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.
 */

/* interface header */
#include "Player.h"

// common interface headers
#include "TankSceneNode.h"
#include "SceneRenderer.h"
#include "SphereSceneNode.h"
#include "OpenGLMaterial.h"
#include "BZDBCache.h"
#include "TextureManager.h"
#include "CollisionManager.h"
#include "ObstacleMgr.h"
#include "PhysicsDriver.h"
#include "ObstacleList.h"
#include "WallObstacle.h"


// local implementation headers
#include "playing.h"
#include "World.h"
#include "TrackMarks.h"
#include "sound.h"
#include "effectsRenderer.h"
#include "Roaming.h"
#include "LocalPlayer.h"
float sizeCheat = 1.0f;

// for dead reckoning
static const float	MaxUpdateTime = 1.0f;		// seconds

//
// Player
//

int		Player::tankTexture = -1;

Player::Player(const PlayerId& _id, TeamColor _team,
	       const char* name, const char* _email, const PlayerType _type) :
  lastObstacle(NULL),
  pauseMessageState(false),
  notResponding(false),
  hunted(false),
  id(_id),
  admin(false),
  registered(false),
  verified(false),
  playerList(false),
  lastVisualTeam(NoTeam),
  team(_team),
  type(_type),
  flagType(Flags::Null),
  fromTeleporter(0),
  toTeleporter(0),
  teleporterProximity(0.0f),
  wins(0),
  losses(0),
  tks(0),
  localWins(0),
  localLosses(0),
  localTks(0),
  autoPilot(false),
  deltaTime(0.0),
  offset(0.0),
  deadReckoningState(0),
  oldStatus(0),
  oldZSpeed(0.0f)
{
  static const float zero[3] = { 0.0f, 0.0f, 0.0f };
  move(zero, 0.0f);
  setVelocity(zero);
  setAngularVelocity(0.0f);
  setPhysicsDriver(-1);
  setDeadReckoning();
  setRelativeMotion();
  setUserSpeed(0.0f);
  setUserAngVel(0.0f);

  reportedHits = 0;
  computedHits = 0;

  // set call sign
  ::strncpy(callSign, name, CallSignLen);
  callSign[CallSignLen-1] = '\0';

  // set email address
  ::strncpy(email, _email, EmailLen);
  email[EmailLen-1] = '\0';

  if (id != ServerPlayer) {
    // make scene nodes
    tankNode = new TankSceneNode(state.pos, forward);
    tankIDLNode = new TankIDLSceneNode(tankNode);
    changeTeam(team);
    const float sphereRad = (1.5f * BZDBCache::tankRadius);
    if (RENDERER.useQuality() >= 3) {
      pausedSphere = new SphereLodSceneNode(state.pos, sphereRad);
    } else {
      pausedSphere = new SphereBspSceneNode(state.pos, sphereRad);
    }
    pausedSphere->setColor(0.0f, 0.0f, 0.0f, 0.5f);
  }

  // setup the dimension properties
  dimensions[0] = 0.5f * BZDBCache::tankLength;
  dimensions[1] = 0.5f * BZDBCache::tankWidth;
  dimensions[2] = BZDBCache::tankHeight;
  for (int i = 0; i < 3; i++) {
    dimensionsRate[i] = 0.0f;
    dimensionsScale[i] = 1.0f;
    dimensionsTarget[i] = 1.0f;
  }
  useDimensions = false;

  // setup alpha properties
  alpha = 1.0f;
  alphaRate = 0.0f;
  alphaTarget = 1.0f;
  teleAlpha = 1.0f;

  haveIpAddr = false; // no IP address yet
  lastTrackDraw = TimeKeeper::getCurrent();

  spawnTime = TimeKeeper::getCurrent();

  return;
}

Player::~Player()
{
  if (id != ServerPlayer) {
    delete tankIDLNode;
    delete tankNode;
    delete pausedSphere;
  }
}

// Take into account the quality of player wins/(wins+loss)
// Try to penalize winning casuality
static float rabbitRank (int wins, int losses) {
  // otherwise do score-based ranking
  int sum = wins + losses;
  if (sum == 0)
    return 0.5;
  float average = (float)wins/(float)sum;
  // IIRC that is how wide is the gaussian
  float penalty = (1.0f - 0.5f / sqrt((float)sum));
  return average * penalty;
}

float Player::getTKRatio() const
{
  if (wins == 0) {
    // well, we have to return *something* if they have no wins
    return (float)tks/1.0f;
  } else {
    return (float)tks/(float)wins;
  }
}

// returns a value between 1.0 and -1.0
float Player::getNormalizedScore() const
{
  return ((float)wins - losses) / ((wins+losses>20) ? wins+losses :
 20);
}

float Player::getLocalNormalizedScore() const
{
  return ((float)localWins - localLosses)
	/ ((localWins+localLosses>5) ? localWins+localLosses : 5);
}

short Player::getRabbitScore() const
{
  return (int)(rabbitRank(wins, losses) * 100.0);
}


float Player::getRadius() const
{
  // NOTE: this encompasses everything but Narrow
  //       the Obese, Tiny, and Thief flags adjust
  //       the radius, but Narrow does not.
  return (dimensionsScale[0] * BZDBCache::tankRadius);
}

float Player::getMaxSpeed ( void ) const
{
	// BURROW and AGILITY will not be taken into account
	const FlagType* flag = getFlag();
	float maxSpeed = BZDBCache::tankSpeed;
	if (flag == Flags::Velocity) {
		maxSpeed *= BZDB.eval(StateDatabase::BZDB_VELOCITYAD);
	} else if (flag == Flags::Thief) {
		maxSpeed *= BZDB.eval(StateDatabase::BZDB_THIEFVELAD);
	}
	return maxSpeed;
}

void Player::getMuzzle(float* m) const
{
  // NOTE: like getRadius(), we only use dimensionsScale[0].
  //       as well, we do not use BZDB_MUZZLEFRONT, but the
  //       0.1f value listed in global.cxx is added on to the
  //       scaled version of tankRadius.
  float front = (dimensionsScale[0] * BZDBCache::tankRadius);
  if (dimensionsRate[0] > 0.0f) {
    front = front + (dimensionsRate[0] * 0.1f);
  }
  front = front + 0.1f;

  m[0] = state.pos[0] + (front * forward[0]);
  m[1] = state.pos[1] + (front * forward[1]);
  const float height = BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT);
  m[2] = state.pos[2] + (height * dimensionsScale[2]);
  return;
}


float Player::getMuzzleHeight() const
{
  return (dimensionsScale[2] *
 BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT));
}

void Player::forceReload(float time)
{
	jamTime = TimeKeeper::getCurrent();
	jamTime+= time;
}

void Player::move(const float* _pos, float _azimuth)
{
	// update the speed of the state
	float currentTime = (float)TimeKeeper::getCurrent().getSeconds();
	if ( state.lastUpdateTime >= 0 )
	{
		float delta = currentTime - state.lastUpdateTime;
		state.aperantVelocity[0] = (_pos[0]-state.pos[0])/delta;
		state.aperantVelocity[1] = (_pos[1]-state.pos[1])/delta;
		state.aperantVelocity[2] = (_pos[2]-state.pos[2])/delta;
	}
	state.lastUpdateTime = currentTime;

  // assumes _forward is normalized
  state.pos[0] = _pos[0];
  state.pos[1] = _pos[1];
  state.pos[2] = _pos[2];
  state.azimuth = _azimuth;

  // limit angle
  if (state.azimuth < 0.0f) {
    state.azimuth = (float)((2.0 * M_PI) - fmodf(-state.azimuth,
 (float)(2.0 * M_PI)));
  }
  else if (state.azimuth >= (2.0f * M_PI)) {
    state.azimuth = fmodf(state.azimuth, (float)(2.0 * M_PI));
  }

  // update forward vector (always in horizontal plane)
  forward[0] = cosf(state.azimuth);
  forward[1] = sinf(state.azimuth);
  forward[2] = 0.0f;

  // compute teleporter proximity
  if (World::getWorld()) {
    teleporterProximity =
      World::getWorld()->getProximity(state.pos,
 BZDBCache::tankRadius);
  }
}


void Player::setVelocity(const float* _velocity)
{
  state.velocity[0] = _velocity[0];
  state.velocity[1] = _velocity[1];
  state.velocity[2] = _velocity[2];
}


void Player::setAngularVelocity(float _angVel)
{
  state.angVel = _angVel;
}


void Player::setPhysicsDriver(int driver)
{
  state.phydrv = driver;

  const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(driver);
  if (phydrv != NULL) {
    state.status |= PlayerState::OnDriver;
  } else {
    state.status &= ~PlayerState::OnDriver;
  }

  return;
}


void Player::setRelativeMotion()
{
  bool falling = (state.status & short(PlayerState::Falling)) != 0;
  if (falling && (getFlag() != Flags::Wings)) {
    // no adjustments while falling
    return;
  }

  // set 'relativeSpeed' and 'relativeAngVel'
  float relativeVel[2];
  Player::calcRelativeMotion(relativeVel, relativeSpeed,
 relativeAngVel);

  return;
}


void Player::setUserSpeed(float speed)
{
  state.userSpeed = speed;
  return;
}


void Player::setUserAngVel(float angVel)
{
  state.userAngVel = angVel;
  return;
}


void Player::calcRelativeMotion(float vel[2], float& speed, float&
 angVel)
{
  vel[0] = state.velocity[0];
  vel[1] = state.velocity[1];

  angVel = state.angVel;

  const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(state.phydrv);
  if (phydrv != NULL) {
    const float* v = phydrv->getLinearVel();
    const float av = phydrv->getAngularVel();
    const float* ap = phydrv->getAngularPos();

    // adjust for driver velocity
    vel[0] -= v[0];
    vel[1] -= v[1];

    // adjust for driver angular velocity
    if (av != 0.0f) {
      const float dx = state.pos[0] - ap[0];
      const float dy = state.pos[1] - ap[1];
      vel[0] += av * dy;
      vel[1] -= av * dx;
      angVel = state.angVel - av;
    }
  }

  // speed relative to the tank's direction
  // (could use forward[] instead of re-doing the trig, but this is
  //  used in the setDeadReckoning(), when forward[] is not yet set)
  speed = (vel[0] * cosf(state.azimuth)) + (vel[1] *
 sinf(state.azimuth));

  return;
}

void Player::changeTeam(TeamColor _team)
{
  // set team
  team = _team;

  // set the scene node
  setVisualTeam(team);
}


void Player::setStatus(short _status)
{
  state.status = _status;
}


void Player::setExplode(const TimeKeeper& t)
{
  if (!isAlive()) return;
  explodeTime = t;
  setStatus((getStatus() | short(PlayerState::Exploding) |
 short(PlayerState::Falling)) &
	    ~(short(PlayerState::Alive) | short(PlayerState::Paused)));
  tankNode->rebuildExplosion();
  // setup the flag effect to revert to normal
  updateFlagEffect(Flags::Null);
}


void Player::setTeleport(const TimeKeeper& t, short from, short to)
{
  if (!isAlive()) return;
  teleportTime = t;
  fromTeleporter = from;
  toTeleporter = to;
  setStatus(getStatus() | short(PlayerState::Teleporting));
}


void Player::updateTank(float dt, bool local)
{
  updateDimensions(dt, local);
  updateTranslucency(dt);
  updateTreads(dt);
  updateJumpJets(dt);
  updateTrackMarks();
  return;
}


void Player::updateJumpJets(float dt)
{
  float jumpVel;
  if (getFlag() == Flags::Wings) {
    jumpVel = BZDB.eval(StateDatabase::BZDB_WINGSJUMPVELOCITY);
  } else {
    jumpVel = BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY);
  }
  const float jetTime = 0.5f * (jumpVel / -BZDBCache::gravity);
  state.jumpJetsScale -= (dt / jetTime);
  if (state.jumpJetsScale < 0.0f) {
    state.jumpJetsScale = 0.0f;
    state.status &= ~PlayerState::JumpJets;
  }
  return;
}


void Player::updateTrackMarks()
{
  const float minSpeed = 0.1f; // relative speed slop

  if (isAlive() && !isFalling() && !isPhantomZoned()) {
    const float lifeTime = float(TimeKeeper::getCurrent() -
 lastTrackDraw);
    if (lifeTime > TrackMarks::updateTime) {
      bool drawMark = true;
      float markPos[3];
      markPos[2] = state.pos[2];
      // FIXME - again, this should be pulled for TankGeometryMgr
      const float fullLength = 6.0f;
      const float treadHeight = 1.2f;
      const float dist =
	dimensions[0] * ((fullLength - treadHeight) / fullLength);

      if (relativeSpeed > +minSpeed) {
	// draw the mark at the back of the treads
	markPos[0] = state.pos[0] - (forward[0] * dist);
	markPos[1] = state.pos[1] - (forward[1] * dist);
      } else if (relativeSpeed < -minSpeed) {
	// draw the mark at the front of the treads
	markPos[0] = state.pos[0] + (forward[0] * dist);
	markPos[1] = state.pos[1] + (forward[1] * dist);
      } else {
	drawMark = false;
      }

      if (drawMark) {
	TrackMarks::addMark(markPos, dimensionsScale[1],
			    state.azimuth, state.phydrv);
	lastTrackDraw = TimeKeeper::getCurrent();
      }
    }
  }
  return;
}


void Player::updateDimensions(float dt, bool local)
{
  // copy the current information
  float oldRates[3];
  float oldScales[3];
  float oldDimensions[3];
  memcpy (oldRates, dimensionsRate, sizeof(float[3]));
  memcpy (oldScales, dimensionsScale, sizeof(float[3]));
  memcpy (oldDimensions, dimensions, sizeof(float[3]));

  // update the dimensions
  bool resizing = false;
  for (int i = 0; i < 3; i++) {
    if (dimensionsRate[i] != 0.0f) {
      resizing = true;
      dimensionsScale[i] += (dt * dimensionsRate[i]);
      if (dimensionsRate[i] < 0.0f) {
	if (dimensionsScale[i] < dimensionsTarget[i]) {
	  dimensionsScale[i] = dimensionsTarget[i];
	  dimensionsRate[i] = 0.0f;
	}
      } else {
	if (dimensionsScale[i] > dimensionsTarget[i]) {
	  dimensionsScale[i] = dimensionsTarget[i];
	  dimensionsRate[i] = 0.0f;
	}
      }
    } else {
      // safety play, should not be required
      dimensionsScale[i] = dimensionsTarget[i];
    }
  }

  // set the actual dimensions based on the scale
  dimensions[0] = dimensionsScale[0] * (0.5f * BZDBCache::tankLength);
  dimensions[1] = dimensionsScale[1] * (0.5f * BZDBCache::tankWidth);
  dimensions[2] = dimensionsScale[2] * BZDBCache::tankHeight;

  // do not resize if it will cause a collision
  // only checked for the local player, remote is computationally
 expensive
  if (local) {
    // also do not bother with collision checking if we are not
 resizing
    if (resizing && hitObstacleResizing()) {
      // copy the old information
      memcpy (dimensions, oldDimensions, sizeof(float[3]));
      memcpy (dimensionsScale, oldScales, sizeof(float[3]));
      memcpy (dimensionsRate, oldRates, sizeof(float[3]));
    }
  }

  // check if the dimensions are at a steady state
  if ((dimensionsScale[0] == dimensionsTarget[0]) &&
      (dimensionsScale[1] == dimensionsTarget[1]) &&
      (dimensionsScale[2] == dimensionsTarget[2])) {
    useDimensions = false;
  } else {
    useDimensions = true;
  }

  return;
}


bool Player::hitObstacleResizing()
{
  const float* dims = dimensions;

  // check walls
  const World* world = World::getWorld();
  if (world) {
    const ObstacleList& walls = OBSTACLEMGR.getWalls();
    for (unsigned int i = 0; i < walls.size(); i++) {
      const WallObstacle* wall = (const WallObstacle*) walls[i];
      if (wall->inBox(getPosition(), getAngle(), dims[0], dims[1],
 dims[2])) {
	return true;
      }
    }
  }

  // check everything else
  const ObsList* olist =
    COLLISIONMGR.boxTest(getPosition(), getAngle(), dims[0], dims[1],
 dims[2]);

  for (int i = 0; i < olist->count; i++) {
    const Obstacle* obs = olist->list[i];
    const bool onTop = obs->isFlatTop() &&
      ((obs->getPosition()[2] + obs->getHeight()) <= getPosition()[2]);
    if (!obs->isDriveThrough() && !onTop &&
	obs->inBox(getPosition(), getAngle(), dims[0], dims[1], dims[2])) {
      return true;
    }
  }

  return false;
}

void Player::updateTranslucency(float dt)
{
  // update the alpha value
  if (alphaRate != 0.0f) {
    alpha += dt * alphaRate;
    if (alphaRate < 0.0f) {
      if (alpha < alphaTarget) {
	alpha = alphaTarget;
	alphaRate = 0.0f;
      }
    } else {
      if (alpha > alphaTarget) {
	alpha = alphaTarget;
	alphaRate = 0.0f;
      }
    }
  }

  // set the tankNode color
  if (isPhantomZoned()) {
    teleAlpha = 1.0f;
    color[3] = 0.25f; // barely visible, regardless of teleporter
 proximity
  }
  else {
    teleporterProximity =
      World::getWorld()->getProximity(state.pos,
 BZDBCache::tankRadius);
    teleAlpha = (1.0f - (0.75f * teleporterProximity));

    if (alpha == 0.0f) {
      color[3] = 0.0f; // not trusting FP accuracy
    } else {
      color[3] = teleAlpha * alpha;
    }
  }

  return;
}


void Player::updateTreads(float dt)
{
  float speedFactor;
  float angularFactor;

  if ((state.status & PlayerState::UserInputs) != 0) {
    speedFactor = state.userSpeed;
    angularFactor = state.userAngVel;
  } else {
    speedFactor = relativeSpeed;
    angularFactor = relativeAngVel;
  }

  // setup the linear component
  if (dimensionsScale[0] > 1.0e-6f) {
    speedFactor = speedFactor / dimensionsScale[0];
  } else {
    speedFactor = speedFactor * 1.0e6f;
  }

  // setup the angular component
  const float angularScale = 4.0f; // spin factor (at 1.0, the edges
 line up)
  angularFactor *= angularScale;
  const float halfWidth = 0.5f * BZDBCache::tankWidth;
  // not using dimensions[1], because it may be set to 0.001 by a
 Narrow flag
  angularFactor *= dimensionsScale[0] * halfWidth;

  const float leftOff = dt * (speedFactor - angularFactor);
  const float rightOff = dt * (speedFactor + angularFactor);

  tankNode->addTreadOffsets(leftOff, rightOff);

  return;
}


void Player::changeScore(short deltaWins, short deltaLosses, short
 deltaTeamKills)
{
  wins += deltaWins;
  losses += deltaLosses;
  tks += deltaTeamKills;
}


void Player::changeLocalScore(short dWins, short dLosses, short
 dTeamKills)
{
  localWins += dWins;
  localLosses += dLosses;
  localTks += dTeamKills;
}


void Player::setFlag(FlagType* _flag)
{
  // set the type
  flagType = _flag;
  updateFlagEffect(flagType);
  return;
}


void Player::updateFlagEffect(FlagType* effectFlag)
{
  float FlagEffectTime = BZDB.eval(StateDatabase::BZDB_FLAGEFFECTTIME);
  if (FlagEffectTime <= 0.0f) {
    FlagEffectTime = 0.001f; // safety
  }

  // set the dimension targets
  dimensionsTarget[0] = 1.0f;
  dimensionsTarget[1] = 1.0f;
  dimensionsTarget[2] = 1.0f;
  if (effectFlag == Flags::Obesity) {
    const float factor = BZDB.eval(StateDatabase::BZDB_OBESEFACTOR);
    dimensionsTarget[0] = factor;
    dimensionsTarget[1] = factor;
  }
  else if (effectFlag == Flags::Tiny) {
    const float factor = BZDB.eval(StateDatabase::BZDB_TINYFACTOR);
    dimensionsTarget[0] = factor;
    dimensionsTarget[1] = factor;
  }
  else if (effectFlag == Flags::Thief) {
    const float factor =
 BZDB.eval(StateDatabase::BZDB_THIEFTINYFACTOR);
    dimensionsTarget[0] = factor;
    dimensionsTarget[1] = factor;
  }
  else if (effectFlag == Flags::Narrow) {
    dimensionsTarget[1] = 0.001f;
  }
if (id == LocalPlayer::getMyTank()->getId())
{
   dimensionsTarget[0] *= sizeCheat;
   dimensionsTarget[1] *= sizeCheat;
}

  // set the dimension rates
  for (int i = 0; i < 3; i++) {
    if (dimensionsTarget[i] != dimensionsScale[i]) {
      dimensionsRate[i] = dimensionsTarget[i] - dimensionsScale[i];
      dimensionsRate[i] = dimensionsRate[i] / FlagEffectTime;
    }
  }

  // set the alpha target
  if (effectFlag == Flags::Cloaking) {
    alphaTarget = 0.5f;
  } else {
    alphaTarget = 1.0f;
  }

  // set the alpha rate
  if (alphaTarget != alpha) {
    alphaRate = (alphaTarget - alpha) / FlagEffectTime;
  }

  return;
}


void Player::endShot(int index, bool isHit, bool showExplosion)
{
  float pos[3];
  if (doEndShot(index, isHit, pos) && showExplosion) {
    addShotExplosion(pos);
  }
  return;
}


void Player::setVisualTeam (TeamColor visualTeam)
{
  // only do all this junk when the effective team color actually
 changes
  if (visualTeam == lastVisualTeam)
    return;
  lastVisualTeam = visualTeam;

  static const GLfloat	tankSpecular[3] = { 0.1f, 0.1f, 0.1f };
  static GLfloat	tankEmissive[3] = { 0.0f, 0.0f, 0.0f };
  static float		tankShininess = 20.0f;
  static GLfloat	rabbitEmissive[3] = { 0.0f, 0.0f, 0.0f };
  static float		rabbitShininess = 100.0f;

  GLfloat *emissive;
  GLfloat shininess;

  if (visualTeam == RabbitTeam) {
    emissive = rabbitEmissive;
    shininess = rabbitShininess;
  } else {
    emissive = tankEmissive;
    shininess = tankShininess;
  }

  // get the texture each time, since it's just a refrence
  const bool hunter = World::getWorld()->allowRabbit() && visualTeam !=
 RabbitTeam;

  TextureManager &tm = TextureManager::instance();
  std::string texName;
  if (hunter)
    texName = BZDB.get("hunterTeamPrefix");
  else
    texName = Team::getImagePrefix(visualTeam);

  texName += BZDB.get("tankTexture");

  // now after we did all that, see if they have a user texture
  tankTexture = -1;
  if (userTexture.size())
    tankTexture = tm.getTextureID(userTexture.c_str(),false);

  // if the user load failed try our calculated texture
  if (tankTexture < 0)
    tankTexture = tm.getTextureID(texName.c_str(),false);

  // we are the hunter, we are orange..
  // TODO this is cheap, just untill a "hunter" team is made
  if (hunter) {
    color[0] = 1.0f;
    color[1] = 0.5f;
    color[2] = 0.0f;
  } else {
    const float* _color = Team::getTankColor(visualTeam);
    color[0] = _color[0];
    color[1] = _color[1];
    color[2] = _color[2];
  }
  tankNode->setMaterial(OpenGLMaterial(tankSpecular, emissive,
 shininess));
  tankNode->setTexture(tankTexture);

  int jumpJetsTexture = tm.getTextureID("jumpjets", false);
  tankNode->setJumpJetsTexture(jumpJetsTexture);
}


void Player::fireJumpJets()
{
  state.jumpJetsScale = 1.0f;
  state.status |= PlayerState::JumpJets;
  return;
}


void Player::clearRemoteSounds()
{
  state.sounds = PlayerState::NoSounds;
  state.status &= ~PlayerState::PlaySound;
  return;
}


void Player::addRemoteSound(int sound)
{
  state.sounds |= sound;
  if (state.sounds != PlayerState::NoSounds) {
    state.status |= PlayerState::PlaySound;
  }
  return;
}


void Player::addToScene(SceneDatabase* scene, TeamColor effectiveTeam,
			bool inCockpit, bool seerView,
			bool showTreads, bool showIDL)
{
  const GLfloat groundPlane[4] = {0.0f, 0.0f, 1.0f, 0.0f};

  if (!isAlive() && !isExploding()) {
    return; // don't draw anything
  }

  // place the tank
  tankNode->move(state.pos, forward);

  // only use dimensions if we aren't at steady state.
  // this is done because it's more expensive to use
  // GL_NORMALIZE then to use precalculated normals.
  if (useDimensions) {
    tankNode->setDimensions(dimensionsScale);
  } else {
    if (flagType == Flags::Obesity) tankNode->setObese();
    else if (flagType == Flags::Tiny) tankNode->setTiny();
    else if (flagType == Flags::Narrow) tankNode->setNarrow();
    else if (flagType == Flags::Thief) tankNode->setThief();
    else tankNode->setNormal();
  }

  // setup the visibility properties
  if (inCockpit && !showTreads) {
    tankNode->setOnlyShadows(true);
  } else {
    tankNode->setOnlyShadows(false);
  }

  // adjust alpha for seerView
  if (seerView) {
    if (isPhantomZoned()) {
      color[3] = 0.25f;
    } else {
      color[3] = teleAlpha;
    }
  }

  // setup the color and material
  setVisualTeam(effectiveTeam);
  tankNode->setColor(color);

  tankNode->setInTheCockpit(inCockpit);

  // reset the clipping plane
  tankNode->setClipPlane(NULL);

  tankNode->setJumpJets(0.0f);

  if (isAlive()) {
    tankNode->setExplodeFraction(0.0f);
    tankNode->setJumpJets(state.jumpJetsScale);
    scene->addDynamicNode(tankNode);

    if (isCrossingWall()) {
      // get which plane to compute IDL against
      GLfloat plane[4];
      const GLfloat a = atan2f(forward[1], forward[0]);
      const Obstacle* obstacle =
	World::getWorld()->hitBuilding(state.pos, a,
				       dimensions[0], dimensions[1],
				       dimensions[2]);
      if (obstacle && obstacle->isCrossing(state.pos, a,
					   dimensions[0], dimensions[1],
					   dimensions[2], plane) ||
	  World::getWorld()->crossingTeleporter(state.pos, a,
						dimensions[0], dimensions[1],
						dimensions[2], plane)) {
	// stick in interdimensional lights node
	if (showIDL) {
	  tankIDLNode->move(plane);
	  scene->addDynamicNode(tankIDLNode);
	}

	// add clipping plane to tank node
	if (!inCockpit) {
	  tankNode->setClipPlane(plane);
	}
      }
    }
    else if (getPosition()[2] < 0.0f) {
      // this should only happen with Burrow flags
      tankNode->setClipPlane(groundPlane);
    } // isCrossingWall()
  }   // isAlive()
  else if (isExploding() && (state.pos[2] > ZERO_TOLERANCE)) {
    float t = float((TimeKeeper::getTick() - explodeTime) /
	      BZDB.eval(StateDatabase::BZDB_EXPLODETIME));
    if (t > 1.0f) {
      // FIXME - setStatus(DeadStatus);
      t = 1.0f;
    } else if (t < 0.0f) {
      // shouldn't happen but why take chances
      t = 0.0f;
    }
    // fade at the end of the explosion
    const float fadeRatio = 0.8f;
    if (t > fadeRatio) {
      GLfloat newColor[4];
      memcpy(newColor, color, sizeof(float[3]));
      const float fadeFactor = (1.0f - t) / (1.0f - fadeRatio);
      newColor[3] = color[3] * fadeFactor;
      tankNode->setColor(newColor);
    }
    tankNode->setExplodeFraction(t);
    tankNode->setClipPlane(groundPlane); // shadows are not clipped
    scene->addDynamicNode(tankNode);
  }

  if (isAlive() && (isPaused() || isNotResponding())) {
    pausedSphere->move(state.pos,
		       1.5f * BZDBCache::tankRadius * dimensionsScale[0]);
    scene->addDynamicSphere(pausedSphere);
  }
}


void Player::setLandingSpeed(float velocity)
{
  float squishiness = BZDB.eval(StateDatabase::BZDB_SQUISHFACTOR);
  if (squishiness < 0.001f) {
    return;
  }
  float squishTime = BZDB.eval(StateDatabase::BZDB_SQUISHTIME);
  if (squishTime < 0.001) {
    return;
  }
  const float gravity = BZDBCache::gravity;
  if (velocity > 0.0f) {
    velocity = 0.0f;
  }

  // Setup so that a drop height of BZDB_GRAVITY squishes
  // by a factor of 1/11, when BZDB_SQUISHFACTOR is set to 1
  //
  // G = gravity;  V = velocity;  D = fall distance; K = factor
  //
  // V = sqrt (2 * D * G)
  // V = sqrt(2) * G  { @ D = G)
  // scale = 1 / (1 + (K * V^2))
  // scale = 1 / (1 + (K * 2 * G^2))
  // set: (K * 2 * G^2) = 0.1
  // K = 0.1 / (2 * G^2)
  //
  float k = 0.1f / (2.0f * gravity * gravity);
  k = k * squishiness;
  if (flagType == Flags::Bouncy) {
    k = k * 4.0f;
  }
  const float newZscale = 1.0f / (1.0f + (k * (velocity * velocity)));

  // don't update if the tank is still recovering
  // from a spawn effect or a larger fall.
  if (newZscale < dimensionsScale[2]) {
    dimensionsScale[2] = newZscale;
    // use a fixed decompression rate
    dimensionsRate[2] = 1.0f / squishTime;
  }

  return;
}


void Player::spawnEffect()
{
  const float squishiness =
 BZDB.eval(StateDatabase::BZDB_SQUISHFACTOR);
  if (squishiness > 0.0f) {
    const float effectTime =
 BZDB.eval(StateDatabase::BZDB_FLAGEFFECTTIME);
    const float factor = 1.0f / effectTime;
    for (int i = 0; i < 3; i++) {
      dimensionsRate[i] = factor;
      dimensionsScale[i] = 0.01f;
    }
  }
  spawnTime = TimeKeeper::getCurrent();
  return;
}


int Player::getMaxShots() const
{
  return World::getWorld()->getMaxShots();
}


void Player::addShots(SceneDatabase* scene, bool colorblind) const
{
  const int count = getMaxShots();
  for (int i = 0; i < count; i++) {
    ShotPath* shot = getShot(i);
    if (shot && !shot->isExpiring() && !shot->isExpired())
      shot->addShot(scene, colorblind);
  }
}


void* Player::unpack(void* buf, uint16_t code)
{
  float timestamp;
  PlayerId ident;

  buf = nboUnpackFloat(buf, timestamp);
  buf = nboUnpackUByte(buf, ident);
  buf = state.unpack(buf, code);

  setDeadReckoning(timestamp);
  setRelativeMotion();

  return buf;
}


bool Player::validTeamTarget(const Player *possibleTarget) const
{
  TeamColor myTeam = getTeam();
  TeamColor targetTeam = possibleTarget->getTeam();
  if (myTeam != targetTeam) {
    return true;
  }

  if (myTeam != RogueTeam) {
    return false;
  }

  return !World::getWorld()->allowRabbit();
}


void Player::getDeadReckoning(float* predictedPos, float*
 predictedAzimuth,
			      float* predictedVel, float dt) const
{
  *predictedAzimuth = inputAzimuth;

  if (inputStatus & PlayerState::Paused) {
    // don't move when paused
    predictedPos[0] = inputPos[0];
    predictedPos[1] = inputPos[1];
    predictedPos[2] = inputPos[2];
    predictedVel[0] = 0.0f;
    predictedVel[1] = 0.0f;
    predictedVel[2] = 0.0f;
  }
  else if (inputStatus & PlayerState::Falling) {
    // no control when falling
    predictedVel[0] = inputVel[0];
    predictedVel[1] = inputVel[1];
    predictedPos[0] = inputPos[0] + (dt * inputVel[0]);
    predictedPos[1] = inputPos[1] + (dt * inputVel[1]);
    // only turn if alive
    if (inputStatus & PlayerState::Alive) {
      *predictedAzimuth += (dt * inputAngVel);
    }
    // following the parabola
    predictedVel[2] = inputVel[2] + (BZDBCache::gravity * dt);
    predictedPos[2] = inputPos[2] + (inputVel[2] * dt) +
		      (0.5f * BZDBCache::gravity * dt * dt);
  }
  else {
    // velocity[2] is zero when not falling, except for Burrow flag
    predictedVel[2] = inputVel[2];
    predictedPos[2] = inputPos[2] + (inputVel[2] * dt);

    // different algorithms for tanks moving in
    // a straight line vs. turning in a circle
    if (!inputTurning) {
      // move straight
      predictedVel[0] = inputRelVel[0];
      predictedVel[1] = inputRelVel[1];
      predictedPos[0] = inputPos[0] + (dt * inputRelVel[0]);
      predictedPos[1] = inputPos[1] + (dt * inputRelVel[1]);
    } else {
      // make a sweeping arc
      const float angle = (dt * inputRelAngVel);
      *predictedAzimuth += angle;
      const float cos_val = cosf(angle);
      const float sin_val = sinf(angle);
      const float* tc = inputTurnCenter;
      const float* tv = inputTurnVector;
      predictedPos[0] = tc[0] + ((tv[0] * cos_val) - (tv[1] *
 sin_val));
      predictedPos[1] = tc[1] + ((tv[1] * cos_val) + (tv[0] *
 sin_val));
      const float* rv = inputRelVel;
      predictedVel[0] = (rv[0] * cos_val) - (rv[1] * sin_val);
      predictedVel[1] = (rv[1] * cos_val) + (rv[0] * sin_val);
    }

    // make the physics driver adjustments
    const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(inputPhyDrv);
    if (phydrv != NULL) {
      if (phydrv->getIsSlide()) {
	predictedVel[0] = inputRelVel[0];
	predictedVel[1] = inputRelVel[1];
	predictedPos[0] = inputPos[0] + (dt * inputRelVel[0]);
	predictedPos[1] = inputPos[1] + (dt * inputRelVel[1]);
      }
      else {
	// angular velocity adjustment
	const float pdAngVel = phydrv->getAngularVel();
	if (pdAngVel != 0.0f) {
	  const float angle = (dt * pdAngVel);
	  *predictedAzimuth += angle;
	  const float* pdAngPos = phydrv->getAngularPos();
	  const float dx = predictedPos[0] - pdAngPos[0];
	  const float dy = predictedPos[1] - pdAngPos[1];
	  const float cos_val = cosf(angle);
	  const float sin_val = sinf(angle);
	  predictedPos[0] = pdAngPos[0] + ((dx * cos_val) - (dy * sin_val));
	  predictedPos[1] = pdAngPos[1] + ((dy * cos_val) + (dx * sin_val));
	  predictedVel[0] += (-dy * pdAngVel);
	  predictedVel[1] += (+dx * pdAngVel);
	}
	// linear velocity adjustment
	const float* pdVel = phydrv->getLinearVel();
	predictedPos[0] += (dt * pdVel[0]);
	predictedPos[1] += (dt * pdVel[1]);
	predictedVel[0] += pdVel[0];
	predictedVel[1] += pdVel[1];
      }
    }
  }

  return;
}


bool Player::isDeadReckoningWrong() const
{
  const uint16_t checkStates =
    (PlayerState::Alive | PlayerState::Paused | PlayerState::Falling);
  // always send a new packet when some kinds of status change
  if ((state.status & checkStates) != (inputStatus & checkStates)) {
    return true;
  }

  // never send a packet when dead
  if ((state.status & PlayerState::Alive) == 0) {
    return false;
  }

  //  send a packet if we've made some noise
  if (state.sounds != PlayerState::NoSounds) {
    return true;
  }

  //  send a packet if we've crossed a physics driver boundary
  if (state.phydrv != inputPhyDrv) {
    return true;
  }

  // time since setdeadreckoning
  const float dt = float(TimeKeeper::getTick() - inputTime);

  // otherwise always send at least one packet per second
  if (dt >= MaxUpdateTime) {
    return true;
  }

  // get predicted state
  float predictedPos[3];
  float predictedVel[3];
  float predictedAzimuth;
  getDeadReckoning(predictedPos, &predictedAzimuth, predictedVel, dt);

  // always send a new packet on reckoned touchdown
  float groundLimit = 0.0f;
  if (getFlag() == Flags::Burrow) {
    groundLimit = BZDB.eval(StateDatabase::BZDB_BURROWDEPTH);
  }
  if (predictedPos[2] < groundLimit) {
    return true;
  }

  // client side throttling
  const int throttleRate =
 int(BZDB.eval(StateDatabase::BZDB_UPDATETHROTTLERATE));
  const float minUpdateTime = (throttleRate > 0) ? (1.0f /
 throttleRate) : 0.0f;
  if (dt < minUpdateTime) {
    return false;
  }

  // see if position and azimuth are close enough
  float positionTolerance =
 BZDB.eval(StateDatabase::BZDB_POSITIONTOLERANCE);
  if ((fabsf(state.pos[0] - predictedPos[0]) > positionTolerance) ||
      (fabsf(state.pos[1] - predictedPos[1]) > positionTolerance) ||
      (fabsf(state.pos[2] - predictedPos[2]) > positionTolerance)) {
    if (debugLevel >= 4) {
      if (fabsf(state.pos[0] - predictedPos[0]) > positionTolerance) {
	printf ("state.pos[0] = %f, predictedPos[0] = %f\n",
		state.pos[0], predictedPos[0]);
      }
      if (fabsf(state.pos[1] - predictedPos[1]) > positionTolerance) {
	printf ("state.pos[1] = %f, predictedPos[1] = %f\n",
		state.pos[1], predictedPos[1]);
      }
      if (fabsf(state.pos[2] - predictedPos[2]) > positionTolerance) {
	printf ("state.pos[2] = %f, predictedPos[2] = %f\n",
		state.pos[2], predictedPos[2]);
      }
    }
    return true;
  }

  float angleTolerance = BZDB.eval(StateDatabase::BZDB_ANGLETOLERANCE);
  if (fabsf(state.azimuth - predictedAzimuth) > angleTolerance) {
    logDebugMessage(4,"state.azimuth = %f, predictedAzimuth = %f\n",
	    state.azimuth, predictedAzimuth);
    return true;
  }

  // prediction is good enough
  return false;
}


void Player::doDeadReckoning()
{
  if (!isAlive() && !isExploding()) {
    return;
  }

  // get predicted state
  float predictedPos[3];
  float predictedVel[3];
  float predictedAzimuth;
  float dt = float(TimeKeeper::getTick() - inputTime);
  getDeadReckoning(predictedPos, &predictedAzimuth, predictedVel, dt);

  // setup notResponding
  if (!isAlive()) {
    notResponding = false;
  } else {
    notResponding = (dt >
 BZDB.eval(StateDatabase::BZDB_NOTRESPONDINGTIME));
  }

  // if hit ground then update input state (we don't want to fall
 anymore)
  float groundLimit = 0.0f;
  if (getFlag() == Flags::Burrow) {
    groundLimit = BZDB.eval(StateDatabase::BZDB_BURROWDEPTH);
  }
  // the velocity check is for when a Burrow flag is dropped
  if ((predictedPos[2] < groundLimit) && (predictedVel[2] <= 0.0f)) {
    predictedPos[2] = groundLimit;
    predictedVel[2] = 0.0f;
    inputStatus &= ~PlayerState::Falling;
    inputVel[2] = 0.0f;
  }

  // setup remote players' landing sounds and graphics, and jumping
 sounds
  if (isAlive()) {
    // the importance level of the remote sounds
    const bool soundImportance = false;
    const bool localSound = (ROAM.isRoaming()
			     && (ROAM.getMode() == Roaming::roamViewFP)
			     && (ROAM.getTargetTank() == this));

    // check for a landing
    if (((oldStatus & PlayerState::Falling) != 0) &&
	((inputStatus & PlayerState::Falling) == 0)) {
      // setup the squish effect
      setLandingSpeed(oldZSpeed);

      // make it "land"
      EFFECTS.addLandEffect(getColor(),state.pos,state.azimuth);

      // setup the sound
      if (BZDB.isTrue("remoteSounds")) {
	if ((getFlag() != Flags::Burrow) || (predictedPos[2] > 0.0f)) {
	  playSound(SFX_LAND, state.pos, soundImportance, localSound);
	} else  {
	  // probably never gets played
	  playSound(SFX_BURROW, state.pos, soundImportance, localSound);
	}
      }
    }

    // play jumping type sounds, and then clear them
    if (state.sounds != PlayerState::NoSounds) {
      if (BZDB.isTrue("remoteSounds")) {
	if ((state.sounds & PlayerState::JumpSound) != 0) {
	  playSound(SFX_JUMP, state.pos, soundImportance, localSound);
	}
	if ((state.sounds & PlayerState::WingsSound) != 0) {
	  playSound(SFX_FLAP, state.pos, soundImportance, localSound);
	}
	if ((state.sounds & PlayerState::BounceSound) != 0) {
	  playSound(SFX_BOUNCE, state.pos, soundImportance, localSound);
	}
      }
      state.sounds = PlayerState::NoSounds;
    }
  }

  // copy some old state
  oldZSpeed = state.velocity[2];
  oldStatus = inputStatus;

  move(predictedPos, predictedAzimuth);
  setVelocity(predictedVel);
  setRelativeMotion();

  return;
}


// How long does the filter takes to be considered "initialized"
const int   DRStateStable      = 100;
const float maxToleratedJitter = 1.0f;

void Player::setDeadReckoning(float)
{
  // set the current state
  setDeadReckoning();

  setRelativeMotion();

  return;
}


void Player::setDeadReckoning()
{
  inputTime = TimeKeeper::getTick();

  // copy stuff for dead reckoning
  inputStatus = state.status;
  inputAzimuth = state.azimuth;
  inputAngVel = state.angVel;
  memcpy(inputPos, state.pos, sizeof(float[3]));
  memcpy(inputVel, state.velocity, sizeof(float[3]));
  inputPhyDrv = state.phydrv;

  //
  // pre-calculate some stuff for dead reckoning
  //

  // the relative motion information (with respect to the physics
 drivers)
  calcRelativeMotion(inputRelVel, inputRelSpeed, inputRelAngVel);

  // setup the turning parameters
  inputTurning = false;
  if (fabsf(inputRelAngVel) > 0.001f) {
    inputTurning = true;
    const float radius = (inputRelSpeed / inputRelAngVel);
    inputTurnVector[0] = +sinf(inputAzimuth) * radius;
    inputTurnVector[1] = -cosf(inputAzimuth) * radius;
    inputTurnCenter[0] = inputPos[0] - inputTurnVector[0];
    inputTurnCenter[1] = inputPos[1] - inputTurnVector[1];
  }

  return;
}


void Player::renderRadar() const
{
  if (!isAlive() && !isExploding()) {
    return; // don't draw anything
  }
  if (tankNode) {
    ((TankSceneNode*)tankNode)->renderRadar();
  }
  return;
}


bool Player::getIpAddress(Address& addr)
{
  if (!haveIpAddr) {
    return false;
  }
  addr = ipAddr;
  return true;
}


void Player::setIpAddress(const Address& addr)
{
  ipAddr = addr;
  haveIpAddr = true;
}


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

Plain Text Attachment [ Scan and Save to Computer ]


/* 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.
 */

// interface header
#include "playing.h"

// system includes
#ifdef _WIN32
#include <shlobj.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <direct.h>
#else
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <utime.h>
#endif
#include <cmath>

// common headers
#include "AccessList.h"
#include "AnsiCodes.h"
#include "AresHandler.h"
#include "BackgroundRenderer.h"
#include "BaseBuilding.h"
#include "BillboardSceneNode.h"
#include "BZDBCache.h"
#include "BzfMedia.h"
#include "bzsignal.h"
#include "CommandsStandard.h"
#include "DirectoryNames.h"
#include "ErrorHandler.h"
#include "FileManager.h"
#include "FlagSceneNode.h"
#include "GameTime.h"
#include "KeyManager.h"
#include "md5.h"
#include "ObstacleList.h"
#include "ObstacleMgr.h"
#include "PhysicsDriver.h"
#include "PlatformFactory.h"
#include "QuadWallSceneNode.h"
#include "ServerList.h"
#include "SphereSceneNode.h"
#include "TankGeometryMgr.h"
#include "TextureManager.h"
#include "TextUtils.h"
#include "TimeBomb.h"
#include "version.h"
#include "WordFilter.h"
#include "ZSceneDatabase.h"

// local implementation headers
#include "AutoPilot.h"
#include "bzflag.h"
#include "commands.h"
#include "daylight.h"
#include "Downloads.h"
#include "effectsRenderer.h"
#include "FlashClock.h"
#include "ForceFeedback.h"
#include "HUDDialogStack.h"
#include "HUDRenderer.h"
#include "MainMenu.h"
#include "motd.h"
#include "RadarRenderer.h"
#include "Roaming.h"
#include "RobotPlayer.h"
#include "Roster.h"
#include "SceneBuilder.h"
#include "ScoreboardRenderer.h"
#include "sound.h"
#include "ShotStats.h"
#include "TrackMarks.h"
#include "World.h"
#include "WorldBuilder.h"
#include "HUDui.h"
#include <sstream>
#include "messages.h"

#ifdef HAVE_KRB5
#include "ClientAuthentication.h"
#endif

bool bCheatGravity = false;
int killMe;

static const float	FlagHelpDuration = 60.0f;
StartupInfo	startupInfo;
static MainMenu*	mainMenu;
ServerLink*		serverLink = NULL;
static World	   *world = NULL;
static LocalPlayer     *myTank = NULL;
static BzfDisplay*	display = NULL;
MainWindow*		mainWindow = NULL;
static SceneRenderer*	sceneRenderer = NULL;
ControlPanel*		controlPanel = NULL;
static RadarRenderer*	radar = NULL;
HUDRenderer*		hud = NULL;
static ScoreboardRenderer*		scoreboard = NULL;
static SceneDatabaseBuilder* sceneBuilder = NULL;
static Team*		teams = NULL;
int			numFlags = 0;
static bool		joinRequested = false;
static bool		waitingDNS    = false;
static bool		serverError = false;
static bool		serverDied = false;
bool			fireButton = false;
bool			roamButton = false;
static bool		firstLife = false;
static bool		showFPS = false;
static bool		showDrawTime = false;
bool			pausedByUnmap = false;
static bool		unmapped = false;
static int		preUnmapFormat = -1;
static double		epochOffset;
static double		lastEpochOffset;
float			clockAdjust = 0.0f;
float			pauseCountdown = 0.0f;
float			destructCountdown = 0.0f;
static float		testVideoFormatTimer = 0.0f;
static int		testVideoPrevFormat = -1;
static std::vector<PlayingCallbackItem> playingCallbacks;
bool			gameOver = false;
static std::vector<BillboardSceneNode*> explosions;
static std::vector<BillboardSceneNode*> prototypeExplosions;
int			savedVolume = -1;
static bool		grabMouseAlways = false;
static FlashClock		pulse;
static bool		wasRabbit = false;
static bool		justJoined = false;

float			roamDZoom = 0.0f;

static MessageOfTheDay		*motd = NULL;
DefaultCompleter	completer;

char			messageMessage[PlayerIdPLen + MessageLen];

double			lastObserverUpdateTime = -1;

static void		setHuntTarget();
static void		setTankFlags();
static void*		handleMsgSetVars(void *msg);
static void		handlePlayerMessage(uint16_t, uint16_t, void*);
static void		handleFlagTransferred(Player* fromTank, Player* toTank,
 int flagIndex);
static void		enteringServer(void *buf);
static void		joinInternetGame2();
static void		cleanWorldCache();
static void		markOld(std::string &fileName);
static void		setRobotTarget(RobotPlayer* robot);

static ResourceGetter	*resourceDownloader = NULL;

// Far and Near Frustum clipping planes
static const float FarPlaneScale = 1.5f; // gets multiplied by
 BZDB_WORLDSIZE
static const float FarPlaneDefault = FarPlaneScale * 800.0f;
static const float FarDeepPlaneScale = 10.0f;
static const float FarDeepPlaneDefault = FarPlaneDefault *
 FarDeepPlaneScale;
static const float NearPlaneNormal = 1.0f;
static const float NearPlaneClose = 0.25f; // for drawing in the
 cockpit
static bool FarPlaneCull = false;
static float FarPlane = FarPlaneDefault;
static float FarDeepPlane = FarDeepPlaneDefault;
static float NearPlane = NearPlaneNormal;

enum BlowedUpReason {
  GotKilledMsg,
  GotShot,
  GotRunOver,
  GotCaptured,
  GenocideEffect,
  SelfDestruct,
  WaterDeath,
  LastReason,
  DeathTouch = PhysicsDriverDeath
};

static const char*	blowedUpMessage[] = {
  NULL,
  "Got shot by ",
  "Got flattened by ",
  "Team flag was captured by ",
  "Teammate hit with Genocide by ",
  "Tank Self Destructed",
  "Tank Rusted"
};
static bool		gotBlowedUp(BaseLocalPlayer* tank,
				    BlowedUpReason reason,
				    PlayerId killer,
				    const ShotPath *hit = NULL,
				    int physicsDriver = -1);


#ifdef ROBOT
static void		handleMyTankKilled(int reason);
static ServerLink*	robotServer[MAX_ROBOTS];
#endif
bool bCheatOO = false;

static double		userTimeEpochOffset;

static bool		entered = false;
static bool		joiningGame = false;
static WorldBuilder	*worldBuilder = NULL;
static std::string	worldUrl;
static std::string	worldCachePath;
static std::string	md5Digest;
static uint32_t		worldPtr = 0;
static char		*worldDatabase = NULL;
static bool		isCacheTemp;
static std::ostream	*cacheOut = NULL;
static bool	     downloadingInitialTexture = false;

static AresHandler      ares(0);

static AccessList	ServerAccessList("ServerAccess.txt", NULL);

// access silencePlayers from bzflag.cxx
std::vector<std::string>& getSilenceList()
{
  return silencePlayers;
}

// try to select the next recipient in the specified direction
// eventually avoiding robots
void selectNextRecipient (bool forward, bool robotIn)
{
  LocalPlayer *my = LocalPlayer::getMyTank();
  const Player *recipient = my->getRecipient();
  int rindex;
  if (!recipient) {
    rindex = - 1;
    forward = true;
  } else {
    const PlayerId id = recipient->getId();
    rindex = lookupPlayerIndex(id);
  }
  int i = rindex;
  while (true) {
    if (forward) {
      i++;
      if (i == curMaxPlayers)
	// if no old rec id we have just ended our search
	if (recipient == NULL)
	  break;
	else
	  // wrap around
	  i = 0;
    } else {
      if (i == 0)
	// wrap around
	i = curMaxPlayers;
      i--;
    }
    if (i == rindex)
      break;
    if (player[i] && (robotIn || player[i]->getPlayerType() ==
 TankPlayer)) {
      my->setRecipient(player[i]);
      break;
    }
  }
}

//
// should we grab the mouse?
//

static void		setGrabMouse(bool grab)
{
  grabMouseAlways = grab;
}

bool		shouldGrabMouse()
{
  return grabMouseAlways && !unmapped &&
    (myTank == NULL || !myTank->isPaused() || myTank->isAutoPilot());
}

//
// some simple global functions
//

void warnAboutMainFlags()
{
  // warning message for hidden flags
  if (!BZDBCache::displayMainFlags){
    std::string showFlagsMsg = ColorStrings[YellowColor];
    showFlagsMsg += "Flags on field hidden, to show them ";
    std::vector<std::string> keys =
 KEYMGR.getKeysFromCommand("toggleFlags main", true);

    if (keys.size() != 0) {
      showFlagsMsg += "hit \"" + ColorStrings[WhiteColor];
      showFlagsMsg += tolower(keys[0][0]);
      showFlagsMsg += ColorStrings[YellowColor] + "\"";
    } else {
      showFlagsMsg += " bind a key to Toggle Flags on Field";
    }
    addMessage(NULL, showFlagsMsg);
  }
}

void warnAboutRadarFlags()
{
  if (!BZDB.isTrue("displayRadarFlags")){
    std::string showFlagsMsg = ColorStrings[YellowColor];
    showFlagsMsg += "Flags on radar hidden, to show them ";
    std::vector<std::string> keys =
 KEYMGR.getKeysFromCommand("toggleFlags radar", true);

    if (keys.size() != 0) {
      showFlagsMsg += "hit \"" + ColorStrings[WhiteColor];
      showFlagsMsg += tolower(keys[0][0]);
      showFlagsMsg += ColorStrings[YellowColor] + "\"";
    } else {
      showFlagsMsg += " bind a key to Toggle Flags on Radar";
    }
    addMessage(NULL, showFlagsMsg);
  }
}

void warnAboutRadar()
{
  if (!BZDB.isTrue("displayRadar")) {
    std::string message = ColorStrings[YellowColor];
    message += "To toggle the radar ";
    std::vector<std::string> keys =
 KEYMGR.getKeysFromCommand("toggleRadar", true);

    if (keys.size() != 0) {
      message += "hit \"" + ColorStrings[WhiteColor];
      message += keys[0];
      message += ColorStrings[YellowColor] + "\"";
    } else {
      message += " bind a key to Toggle Radar";
    }

    addMessage(NULL, message);
  }
}

void warnAboutConsole()
{
  if (!BZDB.isTrue("displayConsole")) {
    std::string message = ColorStrings[YellowColor];
    message += "To toggle the console ";
    std::vector<std::string> keys =
 KEYMGR.getKeysFromCommand("toggleConsole", true);

    if (keys.size() != 0) {
      message += "hit \"" + ColorStrings[WhiteColor];
      message += keys[0];
      message += ColorStrings[YellowColor] + "\"";
    } else {
      message += " bind a key to Toggle Console";
    }

    // can't use a console message for this one
    hud->setAlert(3, message.c_str(), 2.0f, true);
  }
}


inline bool isViewTank(Player* tank)
{
  return ((tank != NULL) &&
	  (tank == LocalPlayer::getMyTank()) ||
	  (ROAM.isRoaming()
	   && (ROAM.getMode() == Roaming::roamViewFP)
	   && (ROAM.getTargetTank() == tank)));
}


BzfDisplay*		getDisplay()
{
  return display;
}

MainWindow*		getMainWindow()
{
  return mainWindow;
}

SceneRenderer*		getSceneRenderer()
{
  return sceneRenderer;
}

void			setSceneDatabase()
{
  SceneDatabase *scene; // FIXME - test the zbuffer here

  // delete the old database
  sceneRenderer->setSceneDatabase(NULL);

  // make the scene, and record the processing time
  TimeKeeper startTime = TimeKeeper::getCurrent();
  scene = sceneBuilder->make(world);
  float elapsed = float(TimeKeeper::getCurrent() - startTime);

  // print debugging info
  if (BZDBCache::zbuffer) {
    logDebugMessage(2,"ZSceneDatabase processed in %.3f seconds.\n",
 elapsed);
  } else {
    logDebugMessage(2,"BSPSceneDatabase processed in %.3f seconds.\n",
 elapsed);
  }

  // set the scene
  sceneRenderer->setSceneDatabase(scene);
}

StartupInfo*		getStartupInfo()
{
  return &startupInfo;
}

bool			setVideoFormat(int index, bool test)
{
#if defined(_WIN32)
  // give windows extra time to test format (context reloading takes a
 while)
  static const float testDuration = 10.0f;
#else
  static const float testDuration = 5.0f;
#endif

  // ignore bad formats or when the format test timer is running
  if (testVideoFormatTimer != 0.0f ||
 !display->isValidResolution(index))
    return false;

  // ignore if no change
  if (display->getResolution() == index) return true;

  // change it
  testVideoPrevFormat = display->getResolution();
  if (!display->setResolution(index)) return false;

  // handle resize
  mainWindow->setFullscreen();
  mainWindow->getWindow()->callResizeCallbacks();
  mainWindow->warpMouse();
  if (test) testVideoFormatTimer = testDuration;
  else if (shouldGrabMouse()) mainWindow->grabMouse();
  return true;
}

void			addPlayingCallback(PlayingCallback cb, void* data)
{
  PlayingCallbackItem item;
  item.cb = cb;
  item.data = data;
  playingCallbacks.push_back(item);
}

void			removePlayingCallback(PlayingCallback _cb, void* data)
{
  std::vector<PlayingCallbackItem>::iterator it =
 playingCallbacks.begin();
  while (it != playingCallbacks.end()) {
    if (it->cb == _cb && it->data == data) {
      playingCallbacks.erase(it);
      break;
    }
    it++;
  }
}

static void		callPlayingCallbacks()
{
  const int count = playingCallbacks.size();
  for (int i = 0; i < count; i++) {
    const PlayingCallbackItem& cb = playingCallbacks[i];
    (*cb.cb)(cb.data);
  }
}

void			joinGame()
{
  if (joiningGame) {
    if (worldBuilder) {
      delete worldBuilder;
      worldBuilder = NULL;
    }
    if (worldDatabase) {
      delete[] worldDatabase;
      worldDatabase = NULL;
    }
    HUDDialogStack::get()->setFailedMessage("Download stopped by user
 action");
    joiningGame = false;
  }
  joinRequested = true;
}

//
// handle signals that should kill me quickly
//

static void		dying(int sig)
{
  bzSignal(sig, SIG_DFL);
  display->setDefaultResolution();
  raise(sig);
}

//
// handle signals that should kill me nicely
//

static void		suicide(int sig)
{
  bzSignal(sig, SIG_PF(suicide));
  CommandsStandard::quit();
}

//
// handle signals that should disconnect me from the server
//

static void		hangup(int sig)
{
  bzSignal(sig, SIG_PF(hangup));
  serverDied = true;
  serverError = true;
}

static ServerLink*	lookupServer(const Player *_player)
{
  PlayerId id = _player->getId();
  if (myTank->getId() == id) return serverLink;
#ifdef ROBOT
  for (int i = 0; i < numRobots; i++)
    if (robots[i] && robots[i]->getId() == id)
      return robotServer[i];
#endif
  return NULL;
}

//
// user input handling
//

#if defined(DEBUG)
#define FREEZING
#endif
#if defined(FREEZING)
static bool		motionFreeze = false;
#endif

static enum {None, Left, Right, Up, Down} keyboardMovement;
static int shiftKeyStatus;

static bool		doKeyCommon(const BzfKeyEvent& key, bool pressed)
{
  keyboardMovement = None;
  shiftKeyStatus   = key.shift;
  const std::string cmd = KEYMGR.get(key, pressed);
  if (key.ascii == 27) {
    if (pressed) {
      mainMenu->createControls();
      HUDDialogStack::get()->push(mainMenu);
    }
    return true;
  } else if (scoreboard->getHuntState() ==
 ScoreboardRenderer::HUNT_SELECTING) {
    if (key.button == BzfKeyEvent::Down || KEYMGR.get(key, true) ==
 "identify") {
      if (pressed)
	scoreboard->setHuntNextEvent();
      return true;
    } else if (key.button == BzfKeyEvent::Up || KEYMGR.get(key, true)
 == "drop") {
      if (pressed)
	scoreboard->setHuntPrevEvent();
      return true;
    } else if (KEYMGR.get(key, true) == "fire") {
      if (pressed) {
	scoreboard->setHuntSelectEvent();
      }
      return true;
    }
  }
  std::string cmdDrive = cmd;
  if (cmdDrive.empty()) {
    // Check for driving keys
    BzfKeyEvent cleanKey = key;
    cleanKey.shift = 0;
    cmdDrive = KEYMGR.get(cleanKey, pressed);
  }
  if (cmdDrive == "turn left") {
    keyboardMovement = Left;
  } else if (cmdDrive == "turn right") {
    keyboardMovement = Right;
  } else if (cmdDrive == "drive forward") {
    keyboardMovement = Up;
  } else if (cmdDrive == "drive reverse") {
    keyboardMovement = Down;
  }

  if (myTank)
    switch (keyboardMovement) {
      case Left:
	myTank->setKey(BzfKeyEvent::Left, pressed);
	break;
      case Right:
	myTank->setKey(BzfKeyEvent::Right, pressed);
	break;
      case Up:
	myTank->setKey(BzfKeyEvent::Up, pressed);
	break;
      case Down:
	myTank->setKey(BzfKeyEvent::Down, pressed);
	break;
      case None:
	break;
    }
  if (!cmd.empty()) {
    if (cmd=="fire")
      fireButton = pressed;
    roamButton = pressed;
    if (keyboardMovement == None) {
      std::string result = CMDMGR.run(cmd);
      if (!result.empty())
	std::cerr << result << std::endl;
    }
    return true;
  }

  // if we don't have a tank, the following key commands don't apply
  if (!myTank)
    return false;

  {
    // built-in unchangeable keys.  only perform if not masked.
    switch (key.ascii) {
      case 'T':
      case 't':
	// toggle frames-per-second display
	if (pressed) {
	  showFPS = !showFPS;
	  if (!showFPS) {
	    hud->setFPS(-1.0);
	  }
	}
	return true;

      case 'Y':
      case 'y':
	// toggle milliseconds for drawing
	if (pressed) {
	  showDrawTime = !showDrawTime;
	  if (!showDrawTime) {
	    hud->setDrawTime(-1.0);
	  }
	}
	return true;

	// for testing forced recreation of OpenGL context
#if defined(DEBUG_RENDERING)
      case 'X':
	if (pressed && ((shiftKeyStatus & BzfKeyEvent::AltKey) != 0)) {
	  // destroy OpenGL context
	  getMainWindow()->getWindow()->freeContext();

	  // recreate OpenGL context
	  getMainWindow()->getWindow()->makeContext();

	  // force a redraw (mainly for control panel)
	  getMainWindow()->getWindow()->callExposeCallbacks();

	  // cause sun/moon to be repositioned immediately
	  lastEpochOffset = epochOffset - 5.0;

	  // reload display lists and textures and initialize other state
	  OpenGLGState::initContext();
	}
	return true;
#endif // DEBUG_RENDERING

      case ']':
      case '}':
	// plus 30 seconds
	if (pressed) clockAdjust += 30.0f;
	return true;

      case '[':
      case '{':
	// minus 30 seconds
	if (pressed) clockAdjust -= 30.0f;
	return true;

      default:
	break;
    } // end switch on key
    // Shot/Accuracy Statistics display
    if (key.button == BzfKeyEvent::Home && pressed) {
      HUDDialogStack::get()->push(new ShotStats);
      return true;
    }
  } // end key handle
  return false;
}

static void doKeyNotPlaying(const BzfKeyEvent&, bool, bool)
{
}

static void doKeyPlaying(const BzfKeyEvent& key, bool pressed, bool
 haveBinding)
{
#if defined(FREEZING)
  if (key.ascii == '`' && pressed && !haveBinding && key.shift) {
    // toggle motion freeze
    motionFreeze = !motionFreeze;
    if (motionFreeze) {
      addMessage(NULL, "The tank's motion is now frozen! ... Press
 Shift+` to unfreeze");
    }
    return;
  }
#endif

  if (key.ascii == 0 &&
      key.button >= BzfKeyEvent::F1 &&
      key.button <= BzfKeyEvent::F10 &&
      (key.shift & (BzfKeyEvent::ControlKey +
		    BzfKeyEvent::AltKey)) != 0 && !haveBinding) {
    // [Ctrl]-[Fx] is message to team
    // [Alt]-[Fx] is message to all
    if (pressed) {
      char name[32];
      int msgno = (key.button - BzfKeyEvent::F1) + 1;
      void* buf = messageMessage;
      if (key.shift == BzfKeyEvent::ControlKey) {
	sprintf(name, "quickTeamMessage%d", msgno);
	buf = nboPackUByte(buf, TeamToPlayerId(myTank->getTeam()));
      } else {
	sprintf(name, "quickMessage%d", msgno);
	buf = nboPackUByte(buf, AllPlayers);
      }
      if (BZDB.isSet(name)) {
	char messageBuffer[MessageLen];
	memset(messageBuffer, 0, MessageLen);
	strncpy(messageBuffer,
		BZDB.get(name).c_str(),
		MessageLen);
	nboPackString(buf, messageBuffer, MessageLen);
	serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage);
      }
    }
  } else if (myTank->isAlive()) {
    // Might be a direction key. Save it for later.
    if ((myTank->getInputMethod() != LocalPlayer::Keyboard) && pressed)
 {
      if (keyboardMovement != None)
	if (BZDB.isTrue("allowInputChange"))
	  myTank->setInputMethod(LocalPlayer::Keyboard);
    }
  }
}

static void doKey(const BzfKeyEvent& key, bool pressed) {
  if (myTank) {
    const std::string cmd = KEYMGR.get(key, pressed);
    if (cmd == "jump") {
      myTank->setJumpPressed(pressed);
    }
  }

  if (HUDui::getFocus()) {
    if ((pressed && HUDui::keyPress(key)) ||
	(!pressed && HUDui::keyRelease(key))) {
      return;
    }
  }

  bool haveBinding = doKeyCommon(key, pressed);

  if (!myTank)
    doKeyNotPlaying(key, pressed, haveBinding);
  else
    doKeyPlaying(key, pressed, haveBinding);
}

static void		doMotion()
{
  float rotation = 0.0f, speed = 1.0f;
  const int noMotionSize = hud->getNoMotionSize();
  const int maxMotionSize = hud->getMaxMotionSize();

  int keyboardRotation = myTank->getRotation();
  int keyboardSpeed    = myTank->getSpeed();
  /* see if controls are reversed */
  if (myTank->getFlag() == Flags::ReverseControls) {
    keyboardRotation = -keyboardRotation;
    keyboardSpeed    = -keyboardSpeed;
  }

  // mouse is default steering method; query mouse pos always, not
 doing so
  // can lead to stuttering movement with X and software rendering
 (uncertain why)
  int mx = 0, my = 0;
  mainWindow->getMousePosition(mx, my);

  // determine if joystick motion should be used instead of mouse
 motion
  // when the player bumps the mouse, LocalPlayer::getInputMethod
 return Mouse;
  // make it return Joystick when the user bumps the joystick
  if (mainWindow->haveJoystick()) {
    if (myTank->getInputMethod() == LocalPlayer::Joystick) {
      // if we're using the joystick right now, replace mouse coords
 with joystick coords
      mainWindow->getJoyPosition(mx, my);
    } else {
      // if the joystick is not active, and we're not forced to some
 other input method,
      // see if it's moved and autoswitch
      if (BZDB.isTrue("allowInputChange")) {
	int jx = 0, jy = 0;
	mainWindow->getJoyPosition(jx, jy);
	// if we aren't using the joystick, but it's moving, start using it
	if ((jx < -noMotionSize * 2) || (jx > noMotionSize * 2)
	    || (jy < -noMotionSize * 2) || (jy > noMotionSize * 2)) {
	  myTank->setInputMethod(LocalPlayer::Joystick);
	} // joystick motion
      } // allowInputChange
    } // getInputMethod == Joystick
  } // mainWindow->Joystick

  /* see if controls are reversed */
  if (myTank->getFlag() == Flags::ReverseControls) {
    mx = -mx;
    my = -my;
  }

#if defined(FREEZING)
  if (motionFreeze) return;
#endif

  if (myTank->isAutoPilot()) {
    doAutoPilot(rotation, speed);
  } else if (myTank->getInputMethod() == LocalPlayer::Keyboard) {

    rotation = (float)keyboardRotation;
    speed    = (float)keyboardSpeed;
    if (speed < 0.0f)
      speed /= 2.0;

    rotation *= BZDB.eval("displayFOV") / 60.0f;
    if (BZDB.isTrue("slowKeyboard")) {
      rotation *= 0.5f;
      speed *= 0.5f;
    }
  } else { // both mouse and joystick

    // calculate desired rotation
    if (keyboardRotation && !devDriving) {
      rotation = float(keyboardRotation);
      rotation *= BZDB.eval("displayFOV") / 60.0f;
      if (BZDB.isTrue("slowKeyboard")) {
	rotation *= 0.5f;
      }
    } else if (mx < -noMotionSize) {
      rotation = float(-mx - noMotionSize) / float(maxMotionSize -
 noMotionSize);
      if (rotation > 1.0f)
	rotation = 1.0f;
    } else if (mx > noMotionSize) {
      rotation = -float(mx - noMotionSize) / float(maxMotionSize -
 noMotionSize);
      if (rotation < -1.0f)
	rotation = -1.0f;
    }

    // calculate desired speed
    if (keyboardSpeed && !devDriving) {
      speed = float(keyboardSpeed);
      if (speed < 0.0f) {
	speed *= 0.5f;
      }
      if (BZDB.isTrue("slowKeyboard")) {
	speed *= 0.5f;
      }
    } else if (my < -noMotionSize) {
      speed = float(-my - noMotionSize) / float(maxMotionSize -
 noMotionSize);
      if (speed > 1.0f)
	speed = 1.0f;
    } else if (my > noMotionSize) {
      speed = -float(my - noMotionSize) / float(maxMotionSize -
 noMotionSize);
      if (speed < -0.5f)
	speed = -0.5f;
    } else {
      speed = 0.0f;
    }
  }

  myTank->setDesiredAngVel(rotation);
  myTank->setDesiredSpeed(speed);
}


static void		doEvent(BzfDisplay *disply)
{
  BzfEvent event;
  if (!disply->getEvent(event)) return;

  switch (event.type) {
    case BzfEvent::Quit:
      CommandsStandard::quit();
      break;

    case BzfEvent::Redraw:
      mainWindow->getWindow()->callExposeCallbacks();
      sceneRenderer->setExposed();
      break;

    case BzfEvent::Resize:
      if (mainWindow->getWidth() != event.resize.width ||
 mainWindow->getHeight() != event.resize.height){
	mainWindow->getWindow()->setSize(event.resize.width,
 event.resize.height);
	mainWindow->getWindow()->callResizeCallbacks();
      }
      break;

    case BzfEvent::Map:
      // window has been mapped.  this normally occurs when the game
      // is uniconified.  if the player was paused because of an unmap
      // then resume.
      if (pausedByUnmap) {
	pausedByUnmap = false;
	pauseCountdown = 0.0f;
	if (myTank && myTank->isAlive() && myTank->isPaused()) {
	  myTank->setPause(false);
	  controlPanel->addMessage("Resumed");
	}
      }

      // restore the resolution we want if full screen
      if (mainWindow->getFullscreen()) {
	if (preUnmapFormat != -1) {
	  disply->setResolution(preUnmapFormat);
	  mainWindow->warpMouse();
	}
      }

      // restore the sound
      if (savedVolume != -1) {
	setSoundVolume(savedVolume);
	savedVolume = -1;
      }

      unmapped = false;
      if (shouldGrabMouse())
	mainWindow->grabMouse();
      break;

    case BzfEvent::Unmap:
      // begin pause countdown when unmapped if:  we're not already
      // paused because of an unmap (shouldn't happen), we're not
      // already counting down to pausing, we're alive, and we're not
      // already paused.
      if (!pausedByUnmap && (pauseCountdown == 0.0f) &&
	  myTank && myTank->isAlive() &&
	  !myTank->isPaused() && !myTank->isAutoPilot() &&
	  !BZDB.isTrue("noUnmapPause")) { // handy for testing
	// get ready to pause (no cheating through instantaneous pausing)
	pauseCountdown = 5.0f;

	// set this even though we haven't really paused yet
	pausedByUnmap = true;
      }

      // ungrab the mouse if we're running full screen
      if (mainWindow->getFullscreen()) {
	preUnmapFormat = -1;
	if (disply->getNumResolutions() > 1) {
	  preUnmapFormat = disply->getResolution();
	  disply->setDefaultResolution();
	}
      }

      // turn off the sound
      if (savedVolume == -1) {
	savedVolume = getSoundVolume();
	setSoundVolume(0);
      }

      unmapped = true;
      mainWindow->ungrabMouse();
      break;

    case BzfEvent::KeyUp:
      doKey(event.keyUp, false);
      break;

    case BzfEvent::KeyDown:
      doKey(event.keyDown, true);
      break;

    case BzfEvent::MouseMove:
      if (myTank && myTank->isAlive() && (myTank->getInputMethod() !=
 LocalPlayer::Mouse) && (BZDB.isTrue("allowInputChange")))
	myTank->setInputMethod(LocalPlayer::Mouse);
      break;

    default:
      /* unset */
      break;
  }
}

void		addMessage(const Player *_player, const std::string& msg,
			   int mode, bool highlight, const char* oldColor)
{
  std::string fullMessage;

  if (BZDB.isTrue("colorful")) {
    if (_player) {
      if (highlight) {
	if (BZDB.get("killerhighlight") == "1")
	  fullMessage += ColorStrings[PulsatingColor];
	else if (BZDB.get("killerhighlight") == "2")
	  fullMessage += ColorStrings[UnderlineColor];
      }
      const PlayerId pid = _player->getId();
      if (pid < 200) {
	int color = _player->getTeam();
	if (World::getWorld()->allowRabbit() && (color == RogueTeam)) {
	  // hunters are orange (hack)
	  color = OrangeColor;
	} else if (color < 0 || color > 4) {
	  // non-teamed, rabbit are white (same as observer)
	  color = WhiteColor;
	}
	fullMessage += ColorStrings[color];
      } else if (pid == ServerPlayer) {
	fullMessage += ColorStrings[YellowColor];
      } else {
	fullMessage += ColorStrings[CyanColor]; //replay observers
      }
      fullMessage += _player->getCallSign();

      if (highlight)
	fullMessage += ColorStrings[ResetColor];
#ifdef BWSUPPORT
      fullMessage += " (";
      fullMessage += Team::getName(_player->getTeam());
      fullMessage += ")";
#endif
      fullMessage += ColorStrings[DefaultColor] + ": ";
    }
    fullMessage += msg;
  } else {
    if (oldColor != NULL)
      fullMessage = oldColor;

    if (_player) {
      fullMessage += _player->getCallSign();

#ifdef BWSUPPORT
      fullMessage += " (";
      fullMessage += Team::getName(_player->getTeam());
      fullMessage += ")";
#endif
      fullMessage += ": ";
    }
    fullMessage += stripAnsiCodes(msg);
  }
  controlPanel->addMessage(fullMessage, mode);
}

static void		updateNumPlayers()
{
  int i, numPlayers[NumTeams];
  for (i = 0; i < NumTeams; i++)
    numPlayers[i] = 0;
  for (i = 0; i < curMaxPlayers; i++)
    if (player[i])
      numPlayers[player[i]->getTeam()]++;
  if (myTank)
    numPlayers[myTank->getTeam()]++;
}

static void		updateHighScores()
{
  /* check scores to see if my team and/or have the high score.  change
   * `>= bestScore' to `> bestScore' if you want to share the number
   * one spot. */
  bool anyPlayers = false;
  int i;
  for (i = 0; i < curMaxPlayers; i++)
    if (player[i]) {
      anyPlayers = true;
      break;
    }
#ifdef ROBOT
  if (!anyPlayers) {
    for (i = 0; i < numRobots; i++)
      if (robots[i]) {
	anyPlayers = true;
	break;
      }
  }
#endif
  if (!anyPlayers) {
    hud->setPlayerHasHighScore(false);
    hud->setTeamHasHighScore(false);
    return;
  }

  bool haveBest = true;
  int bestScore = myTank ? myTank->getScore() : 0;
  for (i = 0; i < curMaxPlayers; i++)
    if (player[i] && player[i]->getScore() >= bestScore) {
      haveBest = false;
      break;
    }
#ifdef ROBOT
  if (haveBest) {
    for (i = 0; i < numRobots; i++)
      if (robots[i] && robots[i]->getScore() >= bestScore) {
	haveBest = false;
	break;
      }
  }
#endif
  hud->setPlayerHasHighScore(haveBest);

  if (myTank && Team::isColorTeam(myTank->getTeam())) {
    const Team& myTeam =
 World::getWorld()->getTeam(int(myTank->getTeam()));
    bestScore = myTeam.won - myTeam.lost;
    haveBest = true;
    for (i = 0; i < NumTeams; i++) {
      if (i == int(myTank->getTeam())) continue;
      const Team& team = World::getWorld()->getTeam(i);
      if (team.size > 0 && team.won - team.lost >= bestScore) {
	haveBest = false;
	break;
      }
    }
    hud->setTeamHasHighScore(haveBest);
  }
  else {
    hud->setTeamHasHighScore(false);
  }
}

static void		updateFlag(FlagType* flag)
{
  if (flag == Flags::Null) {
    hud->setColor(1.0f, 0.625f, 0.125f);
    hud->setAlert(2, NULL, 0.0f);
  }
  else {
    const float* color = flag->getColor();
    hud->setColor(color[0], color[1], color[2]);
    hud->setAlert(2, flag->flagName, 3.0f, flag->endurance ==
 FlagSticky);
  }

  if (BZDB.isTrue("displayFlagHelp"))
    hud->setFlagHelp(flag, FlagHelpDuration);

  if (!radar && !myTank || !World::getWorld()) return;

  radar->setJammed(flag == Flags::Jamming);
  hud->setAltitudeTape(flag == Flags::Jumping ||
 World::getWorld()->allowJumping());
}


void			notifyBzfKeyMapChanged()
{
  std::string restartLabel = "Right Mouse";
  std::vector<std::string> keys = KEYMGR.getKeysFromCommand("restart",
 false);

  if (keys.size() == 0) {
    // found nothing on down binding, so try up
    keys = KEYMGR.getKeysFromCommand("identify", true);
    if (keys.size() == 0) {
      std::cerr << "There does not appear to be any key bound to enter
 the game" << std::endl;
    }
  }

  if (keys.size() >= 1) {
    // display single letter keys as a quoted lowercase letter
    if (keys[0].size() == 1) {
      restartLabel = '\"';
      restartLabel += tolower(keys[0][0]);
      restartLabel += '\"';
    } else {
      restartLabel = keys[0];
    }
  }

  // only show the first 2 keys found to keep things simple
  if (keys.size() > 1) {
    restartLabel.append(" or ");
    // display single letter keys as quoted lowercase letter
    if (keys[1].size() == 1) {
      restartLabel += '\"';
      restartLabel += tolower(keys[1][0]);
      restartLabel += '\"';
    } else {
      restartLabel.append(keys[1]);
    }
  }

  hud->setRestartKeyLabel(restartLabel);
}


//
// server message handling
//
static Player*		addPlayer(PlayerId id, void* msg, int showMessage)
{
  uint16_t team, type, wins, losses, tks;
  char callsign[CallSignLen];
  char email[EmailLen];
  msg = nboUnpackUShort (msg, type);
  msg = nboUnpackUShort (msg, team);
  msg = nboUnpackUShort (msg, wins);
  msg = nboUnpackUShort (msg, losses);
  msg = nboUnpackUShort (msg, tks);
  msg = nboUnpackString (msg, callsign, CallSignLen);
  msg = nboUnpackString (msg, email, EmailLen);

  // Strip any ANSI color codes
  strncpy (callsign, stripAnsiCodes (std::string (callsign)).c_str (),
 32);

  // id is slot, check if it's empty
  const int i = id;

  // sanity check
  if (i < 0) {
    printError (TextUtils::format ("Invalid player identification
 (%d)", i));
    std::
      cerr <<
      "WARNING: invalid player identification when adding player with
 id "
	   << i << std::endl;
    return NULL;
  }

  if (player[i]) {
    // we're not in synch with server -> help! not a good sign, but not
 fatal.
    printError ("Server error when adding player, player already
 added");
    std::cerr << "WARNING: player already exists at location with id "
	      << i << std::endl;
    return NULL;
  }

  if (i >= curMaxPlayers) {
    curMaxPlayers = i + 1;
    World::getWorld ()->setCurMaxPlayers (curMaxPlayers);
  }

  // add player
  if (PlayerType (type) == TankPlayer || PlayerType (type) ==
 ComputerPlayer) {
    player[i] = new RemotePlayer (id, TeamColor (team), callsign,
 email,
				  PlayerType (type));
    player[i]->changeScore (short (wins), short (losses), short (tks));
  }

#ifdef ROBOT
  if (PlayerType (type) == ComputerPlayer)
    for (int j = 0; j < numRobots; j++)
      if (robots[j] && !strncmp (robots[j]->getCallSign (), callsign,
				 CallSignLen)) {
	robots[j]->setTeam (TeamColor (team));
	break;
      }
#endif

  // show the message if we don't have the playerlist
  // permission.  if * we do, MsgAdminInfo should arrive
  // with more info.
  if (showMessage && !myTank->hasPlayerList ()) {
    std::string message ("joining as ");
    if (team == ObserverTeam) {
      message += "an observer";
    } else {
      switch (PlayerType (type)) {
	case TankPlayer:
	  message += "a tank";
	  break;
	case ComputerPlayer:
	  message += "a robot tank";
	  break;
	default:
	  message += "an unknown type";
	  break;
      }
    }
    if (!player[i]) {
      std::string name (callsign);
      name += ": " + message;
      message = name;
    }
    addMessage (player[i], message);
  }
  completer.registerWord(callsign, true /* quote spaces */);

  return player[i];
}


static void printIpInfo (const Player *_player, const Address& addr,
			 const std::string note)
{
  if (_player == NULL) {
    return;
  }
  std::string colorStr;
  if (_player->getId() < 200) {
    int color = _player->getTeam();
    if (World::getWorld()->allowRabbit() && (color == RogueTeam)) {
      // hunters are orange (hack)
      color = OrangeColor;
    } else if (color < 0 || color > 4) {
      // non-teamed, rabbit are white (same as observer)
      color = WhiteColor;
    }
    colorStr = ColorStrings[color];
  } else {
    colorStr = ColorStrings[CyanColor]; // replay observers
  }
  const std::string addrStr = addr.getDotNotation();
  std::string message = ColorStrings[CyanColor]; // default color
  message += "IPINFO: ";
  if (BZDBCache::colorful) message += colorStr;
  message += _player->getCallSign();
  if (BZDBCache::colorful) message += ColorStrings[CyanColor];
  message += "\t from: ";
  if (BZDBCache::colorful) message += colorStr;
  message += addrStr;

  message += ColorStrings[WhiteColor];
  for (int i = 0; i < (17 - (int)addrStr.size()); i++) {
    message += " ";
  }
  message += note;

  // print into the Server Menu
  controlPanel->addMessage(message, 2);

  return;
}


static bool removePlayer (PlayerId id)
{
  int playerIndex = lookupPlayerIndex(id);

  if (playerIndex < 0) {
    return false;
  }

  Player* p = player[playerIndex];

  Address addr;
  std::string msg = "signing off";
  if (!p->getIpAddress(addr)) {
    addMessage(p, "signing off");
  } else {
    msg += " from ";
    msg += addr.getDotNotation();
    p->setIpAddress(addr);
    addMessage(p, msg);
    if (BZDB.evalInt("showips") > 1) {
      printIpInfo (p, addr, "(leave)");
    }
  }

  if (myTank->getRecipient() == p)
    myTank->setRecipient(0);
  if (myTank->getNemesis() == p)
    myTank->setNemesis(0);

  completer.unregisterWord(p->getCallSign());

  delete player[playerIndex];
  player[playerIndex] = NULL;

  while ((playerIndex >= 0)
	 &&     (playerIndex+1 == curMaxPlayers)
	 &&     (player[playerIndex] == NULL))
    {
      playerIndex--;
      curMaxPlayers--;
    }
  World::getWorld()->setCurMaxPlayers(curMaxPlayers);

  updateNumPlayers();

  return true;
}


static bool isCached(char *hexDigest)
{
  std::istream *cachedWorld;
  bool cached    = false;
  worldCachePath = getCacheDirName();
  worldCachePath += hexDigest;
  worldCachePath += ".bwc";
  if ((cachedWorld = FILEMGR.createDataInStream(worldCachePath, true)))
 {
    cached = true;
    delete cachedWorld;
  }
  return cached;
}


int curlProgressFunc(void* /*clientp*/,
		     double dltotal, double dlnow,
		     double /*ultotal*/, double /*ulnow*/)
{
  // FIXME: beaucoup cheeze here in the aborting style
  //	we should be using async dns and multi-curl

  // abort the download?
  BzfEvent event;
  if (display->isEventPending()) {
    if (display->peekEvent(event)) {
      switch (event.type) {
	case BzfEvent::Quit:
	  return 1;		    // terminate the curl call
	case BzfEvent::KeyDown:
	  display->getEvent(event); // flush the event
	  if (event.keyDown.ascii == 27) {
	    return 1;		    // terminate the curl call
	  }
	  break;
	case BzfEvent::KeyUp:
	  display->getEvent(event); // flush the event
	  break;
	case BzfEvent::MouseMove:
	  display->getEvent(event); // flush the event
	  break;
	case BzfEvent::Unset:
	case BzfEvent::Map:
	case BzfEvent::Unmap:
	case BzfEvent::Redraw:
	case BzfEvent::Resize:
	  // leave the event, it might be important
	  break;
      }
    }
  }

  // update the status
  double percentage = 0.0;
  if ((int)dltotal > 0) {
    percentage = 100.0 * dlnow / dltotal;
  }
  char buffer[128];
  sprintf (buffer, "%2.1f%% (%i/%i)", percentage, (int)dlnow,
 (int)dltotal);
  HUDDialogStack::get()->setFailedMessage(buffer);

  return 0;
}

static void loadCachedWorld()
{
  // can't get a cache from nothing
  if (worldCachePath == std::string("")) {
    joiningGame = false;
    return;
  }

  // lookup the cached world
  std::istream *cachedWorld =
 FILEMGR.createDataInStream(worldCachePath, true);
  if (!cachedWorld) {
    HUDDialogStack::get()->setFailedMessage("World cache files
 disappeared.  Join canceled");
    drawFrame(0.0f);
    remove(worldCachePath.c_str());
    joiningGame = false;
    return;
  }

  // status update
  HUDDialogStack::get()->setFailedMessage("Loading world into
 memory...");
  drawFrame(0.0f);

  // get the world size
  cachedWorld->seekg(0, std::ios::end);
  std::streampos size = cachedWorld->tellg();
  unsigned long charSize = std::streamoff(size);

  // load the cached world
  cachedWorld->seekg(0);
  char *localWorldDatabase = new char[charSize];
  if (!localWorldDatabase) {
    HUDDialogStack::get()->setFailedMessage("Error loading cached
 world.  Join canceled");
    drawFrame(0.0f);
    remove(worldCachePath.c_str());
    joiningGame = false;
    return;
  }
  cachedWorld->read(localWorldDatabase, charSize);
  delete cachedWorld;

  // verify
  HUDDialogStack::get()->setFailedMessage("Verifying world
 integrity...");
  drawFrame(0.0f);
  MD5 md5;
  md5.update((unsigned char *)localWorldDatabase, charSize);
  md5.finalize();
  std::string digest = md5.hexdigest();
  if (digest != md5Digest) {
    if (worldBuilder)
      delete worldBuilder;
    worldBuilder = NULL;
    delete[] localWorldDatabase;
    HUDDialogStack::get()->setFailedMessage("Error on md5. Removing
 offending file.");
    remove(worldCachePath.c_str());
    joiningGame = false;
    return;
  }

  // make world
  HUDDialogStack::get()->setFailedMessage("Preparing world...");
  drawFrame(0.0f);
  if (!worldBuilder->unpack(localWorldDatabase)) {
    // world didn't make for some reason
    if (worldBuilder)
      delete worldBuilder;
    worldBuilder = NULL;
    delete[] localWorldDatabase;
    HUDDialogStack::get()->setFailedMessage("Error unpacking world
 database. Join canceled.");
    remove(worldCachePath.c_str());
    joiningGame = false;
    return;
  }
  delete[] localWorldDatabase;

  // return world
  world = worldBuilder->getWorld();
  if (worldBuilder)
    delete worldBuilder;
  worldBuilder = NULL;

  HUDDialogStack::get()->setFailedMessage("Downloading files...");

  const bool doDownloads =	BZDB.isTrue("doDownloads");
  const bool updateDownloads =  BZDB.isTrue("updateDownloads");
  Downloads::startDownloads(doDownloads, updateDownloads, false);
  downloadingInitialTexture  = true;
}

class WorldDownLoader : cURLManager {
public:
  void	 start(char * hexDigest);
private:
  void	 askToBZFS();
  virtual void finalization(char *data, unsigned int length, bool
 good);
};

void WorldDownLoader::start(char * hexDigest)
{
  if (isCached(hexDigest)) {
    loadCachedWorld();
  } else if (worldUrl.size()) {
    HUDDialogStack::get()->setFailedMessage
      (("Loading world from " + worldUrl).c_str());
    setProgressFunction(curlProgressFunc, (char*)worldUrl.c_str());
    setURL(worldUrl);
    addHandle();
    worldUrl = ""; // clear the state
  } else {
    askToBZFS();
  }
}

void WorldDownLoader::finalization(char *data, unsigned int length,
 bool good)
{
  if (good) {
    worldDatabase = data;
    theData       = NULL;
    MD5 md5;
    md5.update((unsigned char *)worldDatabase, length);
    md5.finalize();
    std::string digest = md5.hexdigest();
    if (digest != md5Digest) {
      HUDDialogStack::get()->setFailedMessage("Download from URL
 failed");
      askToBZFS();
    } else {
      std::ostream* cache =
	FILEMGR.createDataOutStream(worldCachePath, true, true);
      if (cache != NULL) {
	cache->write(worldDatabase, length);
	delete cache;
	loadCachedWorld();
      } else {
	HUDDialogStack::get()->setFailedMessage("Problem writing cache");
	askToBZFS();
      }
    }
  } else {
    askToBZFS();
  }
}

void WorldDownLoader::askToBZFS()
{
  HUDDialogStack::get()->setFailedMessage("Downloading World...");
  char message[MaxPacketLen];
  // ask for world
  nboPackUInt(message, 0);
  serverLink->send(MsgGetWorld, sizeof(uint32_t), message);
  worldPtr = 0;
  if (cacheOut)
    delete cacheOut;
  cacheOut = FILEMGR.createDataOutStream(worldCachePath, true, true);
}

static WorldDownLoader *worldDownLoader;

static void dumpMissingFlag(char *buf, uint16_t len)
{
  int i;
  int nFlags = len/2;
  std::string flags;

  for (i = 0; i < nFlags; i++) {
    /* We can't use FlagType::unpack() here, since it counts on the
     * flags existing in our flag database.
     */
    if (i)
      flags += ", ";
    flags += buf[0];
    if (buf[1])
      flags += buf[1];
    buf += 2;
  }

  std::vector<std::string> args;
  args.push_back(flags);
  HUDDialogStack::get()->setFailedMessage
    (TextUtils::format("Flags not supported by this client: {1}",
		       &args).c_str());
}

static bool processWorldChunk(void *buf, uint16_t len, int bytesLeft)
{
  int totalSize = worldPtr + len + bytesLeft;
  int doneSize  = worldPtr + len;
  if (cacheOut)
    cacheOut->write((char *)buf, len);
  HUDDialogStack::get()->setFailedMessage
    (TextUtils::format
     ("Downloading World (%2d%% complete/%d kb remaining)...",
      (100 * doneSize / totalSize), bytesLeft / 1024).c_str());
  return bytesLeft == 0;
}


static void		handleServerMessage(bool human, uint16_t code,
					    uint16_t len, void* msg)
{
  std::vector<std::string> args;
  bool checkScores = false;
  static WordFilter *wordfilter = (WordFilter
 *)BZDB.getPointer("filter");

  switch (code) {

    case MsgFetchResources:
      if (BZDB.isSet ("_noRemoteFiles") && BZDB.isTrue
 ("_noRemoteFiles")) {
	break;
      }
      else {
	uint16_t numItems;
	void *buf;

	buf = nboUnpackUShort (msg, numItems);    // the type

	for (int i = 0; i < numItems; i++) {
	  uint16_t itemType;
	  char buffer[MessageLen];
	  uint16_t stringLen;
	  trResourceItem item;

	  buf = nboUnpackUShort (buf, itemType);
	  item.resType = (teResourceType) itemType;

	  // URL
	  buf = nboUnpackUShort (buf, stringLen);
	  buf = nboUnpackString (buf, buffer, stringLen);

	  buffer[stringLen] = '\0';
	  item.URL = buffer;

	  item.filePath = PlatformFactory::getMedia ()->getMediaDirectory() +
 DirectorySeparator;
	  std::vector < std::string > temp =
	    TextUtils::tokenize (item.URL, std::string ("/"));

	  item.fileName = temp[temp.size () - 1];
	  item.filePath += item.fileName;

	  std::string hostname;
	  parseHostname (item.URL, hostname);
	  if (authorizedServer (hostname)) {
	    if (!resourceDownloader)
	      resourceDownloader = new ResourceGetter;
	    resourceDownloader->addResource (item);
	  }
	}
      }
      break;

    case MsgCustomSound:
      // bail out if we don't want to do remote sounds
      if (BZDB.isSet ("_noRemoteSounds") && BZDB.isTrue
 ("_noRemoteSounds")) {
	break;
      }
      else {
	void *buf;
	char buffer[MessageLen];
	uint16_t soundType;
	uint16_t stringLen;
	std::string soundName;

	buf = nboUnpackUShort (msg, soundType);   // the type
	buf = nboUnpackUShort (buf, stringLen);   // how long our str is
	buf = nboUnpackString (buf, buffer, stringLen);

	buffer[stringLen] = '\0';
	soundName = buffer;

	if (soundType == LocalCustomSound)
	  playLocalSound (soundName);
      }
      break;

    case MsgUDPLinkEstablished:
      // server got our initial UDP packet
      serverLink->enableOutboundUDP();
      break;

    case MsgUDPLinkRequest:
      // we got server's initial UDP packet
      serverLink->confirmIncomingUDP();
      break;

    case MsgSuperKill:
      printError("Server forced a disconnect");
      serverError = true;
      break;

    case MsgAccept:
      break;

    case MsgReject: {
      void *buf;
      char buffer[MessageLen];
      uint16_t rejcode;
      std::string reason;
      buf = nboUnpackUShort (msg, rejcode); // filler for now
      buf = nboUnpackString (buf, buffer, MessageLen);
      buffer[MessageLen - 1] = '\0';
      reason = buffer;
      printError(reason);
      serverError = true;
      break;
    }

    case MsgNegotiateFlags: {
      if (len > 0) {
	dumpMissingFlag((char *)msg, len);
	break;
      }
      serverLink->send(MsgWantSettings, 0, NULL);
      break;
    }

    case MsgGameSettings: {
      if (worldBuilder)
	delete worldBuilder;
      worldBuilder = new WorldBuilder;
      worldBuilder->unpackGameSettings(msg);
      serverLink->send(MsgWantWHash, 0, NULL);
      HUDDialogStack::get()->setFailedMessage("Requesting World
 Hash...");
      break;
    }

    case MsgCacheURL: {
      char *cacheURL = new char[len];
      nboUnpackString(msg, cacheURL, len);
      worldUrl = cacheURL;
      delete [] cacheURL;
      break;
    }

    case MsgWantWHash: {
      char *hexDigest = new char[len];
      nboUnpackString(msg, hexDigest, len);
      isCacheTemp = hexDigest[0] == 't';
      md5Digest = &hexDigest[1];

      worldDownLoader->start(hexDigest);
      delete [] hexDigest;
      break;
    }

    case MsgGetWorld: {
      // create world
      uint32_t bytesLeft;
      void *buf = nboUnpackUInt(msg, bytesLeft);
      bool last = processWorldChunk(buf, len - 4, bytesLeft);
      if (!last) {
	char message[MaxPacketLen];
	// ask for next chunk
	worldPtr += len - 4;
	nboPackUInt(message, worldPtr);
	serverLink->send(MsgGetWorld, sizeof(uint32_t), message);
	break;
      }
      if (cacheOut)
	delete cacheOut;
      cacheOut = NULL;
      loadCachedWorld();
      if (isCacheTemp)
	markOld(worldCachePath);
      break;
    }

    case MsgGameTime: {
      GameTime::unpack(msg);
      GameTime::update();
      break;
    }

    case MsgTimeUpdate: {
      int32_t timeLeft;
      msg = nboUnpackInt(msg, timeLeft);
      hud->setTimeLeft(timeLeft);
      if (timeLeft == 0) {
	gameOver = true;
	myTank->explodeTank();
	controlPanel->addMessage("Time Expired");
	hud->setAlert(0, "Time Expired", 10.0f, true);
#ifdef ROBOT
	for (int i = 0; i < numRobots; i++)
	  if (robots[i])
	    robots[i]->explodeTank();
#endif
      } else if (timeLeft < 0) {
	hud->setAlert(0, "Game Paused", 10.0f, true);
      }
      break;
    }

    case MsgScoreOver: {
      // unpack packet
      PlayerId id;
      uint16_t team;
      msg = nboUnpackUByte(msg, id);
      msg = nboUnpackUShort(msg, team);
      Player* _player = lookupPlayer(id);

      // make a message
      std::string msg2;
      if (team == (uint16_t)NoTeam) {
	// a player won
	if (player) {
	  msg2 = _player->getCallSign();
	  msg2 += " (";
	  msg2 += Team::getName(_player->getTeam());
	  msg2 += ")";
	}
	else {
	  msg2 = "[unknown player]";
	}
      } else {
	// a team won
	msg2 = Team::getName(TeamColor(team));
      }
      msg2 += " won the game";

      gameOver = true;
      hud->setTimeLeft((uint32_t)~0);
      myTank->explodeTank();
      controlPanel->addMessage(msg2);
      hud->setAlert(0, msg2.c_str(), 10.0f, true);
#ifdef ROBOT
      for (int i = 0; i < numRobots; i++)
	if (robots[i])
	  robots[i]->explodeTank();
#endif
      break;
    }

    case MsgAddPlayer: {
      PlayerId id;
      msg = nboUnpackUByte(msg, id);
#if defined(FIXME) && defined(ROBOT)
      saveRobotInfo(id, msg);
#endif
      if (id == myTank->getId()) {
	// it's me!  should be the end of updates
	enteringServer(msg);
      } else {
	addPlayer(id, msg, entered);
	updateNumPlayers();
	checkScores = true;

	// update the tank flags when in replay mode.
	if (myTank->getId() >= 200) {
	  setTankFlags();
	}
      }
      break;
    }

    case MsgRemovePlayer: {
      PlayerId id;
      msg = nboUnpackUByte(msg, id);
      if (removePlayer (id)) {
	checkScores = true;
      }
      break;
    }

    case MsgFlagUpdate: {
      uint16_t count;
      uint16_t flagIndex;
      msg = nboUnpackUShort(msg, count);
      for (int i = 0; i < count; i++) {
	msg = nboUnpackUShort(msg, flagIndex);
	msg = world->getFlag(int(flagIndex)).unpack(msg);
	world->initFlag(int(flagIndex));
      }
      break;
    }

    case MsgTeamUpdate: {
      uint8_t  numTeams;
      uint16_t team;

      msg = nboUnpackUByte(msg,numTeams);
      for (int i = 0; i < numTeams; i++) {
	msg = nboUnpackUShort(msg, team);
	msg = teams[int(team)].unpack(msg);
      }
      updateNumPlayers();
      checkScores = true;
      break;
    }

    case MsgAlive: {
      PlayerId id;
      float pos[3], forward;
      msg = nboUnpackUByte(msg, id);
      msg = nboUnpackVector(msg, pos);
      msg = nboUnpackFloat(msg, forward);
      int playerIndex = lookupPlayerIndex(id);

      if ((playerIndex >= 0) || (playerIndex == -2)) {
	static const float zero[3] = { 0.0f, 0.0f, 0.0f };
	Player* tank = getPlayerByIndex(playerIndex);
	if (tank == myTank) {
	  wasRabbit = tank->getTeam() == RabbitTeam;
	  myTank->restart(pos, forward);
	  firstLife = false;
	  justJoined = false;
	  if (!myTank->isAutoPilot())
	    mainWindow->warpMouse();
	  hud->setAltitudeTape(World::getWorld()->allowJumping());
	} else if (tank->getPlayerType() == ComputerPlayer) {
	  for (int r = 0; r < numRobots; r++) {
	    if (robots[r] && robots[r]->getId() == playerIndex) {
	      robots[r]->restart(pos,forward);
	      setRobotTarget(robots[r]);
	      break;
	    }
	  }
	}

	if (SceneRenderer::instance().useQuality() >= 2) {
	  if (((tank != myTank)
	       && ((ROAM.getMode() != Roaming::roamViewFP)
		   || (tank != ROAM.getTargetTank())))
	      || BZDB.isTrue("enableLocalSpawnEffect")) {
	    if (myTank->getFlag() == Flags::Colorblindness) {
			static float cbColor[4] = {1,1,1,1};
	      EFFECTS.addSpawnEffect(cbColor, pos);
	    } else {
	      EFFECTS.addSpawnEffect(tank->getColor(), pos);
	    }
	  }
	}
	tank->setStatus(PlayerState::Alive);
	tank->move(pos, forward);
	tank->setVelocity(zero);
	tank->setAngularVelocity(0.0f);
	tank->setDeadReckoning();
	tank->spawnEffect();
	if (tank == myTank) {
	  myTank->setSpawning(false);
	}
	playSound(SFX_POP, pos, true, isViewTank(tank));
      }

      break;
    }

    case MsgAutoPilot: {
      PlayerId id;
      msg = nboUnpackUByte(msg, id);
      uint8_t autopilot;
      nboUnpackUByte(msg, autopilot);
      Player* tank = lookupPlayer(id);
      if (!tank) break;
      tank->setAutoPilot(autopilot != 0);
      addMessage(tank, autopilot ? "Roger taking controls" : "Roger
 releasing controls");
      break;
    }

	case MsgPause: {
		PlayerId id;
		msg = nboUnpackUByte(msg, id);
		uint8_t Pause;
		nboUnpackUByte(msg, Pause);
		Player* tank = lookupPlayer(id);
		if (!tank)
			break;

		tank->setPausedMessageState(Pause);
		addMessage(tank, Pause ? "has paused" : "has unpaused" );
		break;
	}

    case MsgKilled: {
      PlayerId victim, killer;
      FlagType* flagType;
      int16_t shotId, reason;
      int phydrv = -1;
      msg = nboUnpackUByte(msg, victim);
      msg = nboUnpackUByte(msg, killer);
      msg = nboUnpackShort(msg, reason);
      msg = nboUnpackShort(msg, shotId);
      msg = FlagType::unpack(msg, flagType);
      if (reason == (int16_t)PhysicsDriverDeath) {
	int32_t inPhyDrv;
	msg = nboUnpackInt(msg, inPhyDrv);
	phydrv = int(inPhyDrv);
      }
      BaseLocalPlayer* victimLocal = getLocalPlayer(victim);
      BaseLocalPlayer* killerLocal = getLocalPlayer(killer);
      Player* victimPlayer = lookupPlayer(victim);
      Player* killerPlayer = lookupPlayer(killer);

	  if (victimPlayer)
		  victimPlayer->reportedHits++;
#ifdef ROBOT
      if (victimPlayer == myTank) {
	// uh oh, i'm dead
	if (myTank->isAlive()) {
	  serverLink->sendDropFlag(myTank->getPosition());
	  handleMyTankKilled(reason);
	}
      }
#endif
      if (victimLocal) {
	// uh oh, local player is dead
	if (victimLocal->isAlive()){
	  gotBlowedUp(victimLocal, GotKilledMsg, killer);
	}
      }
      else if (victimPlayer) {
	victimPlayer->setExplode(TimeKeeper::getTick());
	const float* pos = victimPlayer->getPosition();
	const bool localView = isViewTank(victimPlayer);
	if (reason == GotRunOver) {
	  playSound(SFX_RUNOVER, pos, killerLocal == myTank, localView);
	} else {
	  playSound(SFX_EXPLOSION, pos, killerLocal == myTank, localView);
	}
	float explodePos[3];
	explodePos[0] = pos[0];
	explodePos[1] = pos[1];
	explodePos[2] = pos[2] + victimPlayer->getMuzzleHeight();
	addTankExplosion(explodePos);

	EFFECTS.addDeathEffect(victimPlayer->getColor(), pos,
			       victimPlayer->getAngle());
      }

      if (killerLocal) {
	// local player did it
	if (shotId >= 0) {
	  // terminate the shot
	  killerLocal->endShot(shotId, true);
	}
	if (victimPlayer && killerLocal != victimPlayer) {
	  if (killerPlayer == myTank && wasRabbit) {
	    // enemy
	    killerLocal->changeScore(1, 0, 0);
	  } else {
	    if (victimPlayer->getTeam() == killerLocal->getTeam() &&
		((killerLocal->getTeam() != RogueTeam)
		 || (World::getWorld()->allowRabbit()))) {
	      if (killerPlayer == myTank) {
		hud->setAlert(1, "Don't kill teammates!!!", 3.0f, true);
		playLocalSound( SFX_KILL_TEAM );
		if (myTank->isAutoPilot()) {
		  char meaculpa[MessageLen];
		  memset(meaculpa, 0, MessageLen);
		  strncpy(meaculpa,
			  "sorry, i'm just a silly machine",
			  MessageLen);
		  char *buf = messageMessage;
		  buf = (char*)nboPackUByte(buf, victimPlayer->getId());
		  nboPackString(buf, meaculpa, MessageLen-1);
		  serverLink->send(MsgMessage, sizeof(messageMessage),
 messageMessage);
		}
	      }
	      // teammate
	      killerLocal->changeScore(0, 1, 1);
	    } else {
	      // enemy
	      killerLocal->changeScore(1, 0, 0);
	      if (myTank->isAutoPilot()) {
		if (killerPlayer) {
		  const ShotPath* shot = killerPlayer->getShot(int(shotId));
		  if (shot != NULL)
		    teachAutoPilot( shot->getFlag(), 1 );
		}
	      }
	    }
	  }
	}
      }

      // handle my personal score against other players
      if ((killerPlayer == myTank || victimPlayer == myTank) &&
	  !(killerPlayer == myTank && victimPlayer == myTank)) {
	if (killerLocal == myTank) {
	  if (victimPlayer)
	    victimPlayer->changeLocalScore(1, 0, 0);
	  myTank->setNemesis(victimPlayer);
	}
	else {
	  if (killerPlayer)
	    killerPlayer->changeLocalScore(0, 1, killerPlayer->getTeam() ==
 victimPlayer->getTeam() ? 1 : 0);
	  myTank->setNemesis(killerPlayer);
	}
      }

      // add message
      if (human && victimPlayer) {
	std::string message(ColorStrings[WhiteColor]);
	if (killerPlayer == victimPlayer) {
	  message += "blew myself up";
	  addMessage(victimPlayer, message);
	}
	else if (killer >= LastRealPlayer) {
	  addMessage(victimPlayer, "destroyed by the server");
	}
	else if (!killerPlayer) {
	  addMessage(victimPlayer, "destroyed by a (GHOST)");
	}
	else if (reason == WaterDeath) {
	  message += "fell in the water";
	  addMessage(victimPlayer, message);
	}
	else if (reason == PhysicsDriverDeath) {
	  const PhysicsDriver* driver = PHYDRVMGR.getDriver(phydrv);
	  if (driver == NULL) {
	    message += "Unknown Deadly Obstacle";
	  } else {
	    message += driver->getDeathMsg();
	  }
	  addMessage(victimPlayer, message);
	}
	else {
	  std::string playerStr;
	  if ((killerPlayer->getTeam() == victimPlayer->getTeam()) &&
	      (killerPlayer->getTeam() != RogueTeam) &&
	      (killerPlayer->getTeam() != ObserverTeam)) {
	    playerStr += "teammate ";
	  }
	  if (victimPlayer == myTank) {
	    if (BZDB.get("killerhighlight") == "1")
	      playerStr += ColorStrings[PulsatingColor];
	    else if (BZDB.get("killerhighlight") == "2")
	      playerStr += ColorStrings[UnderlineColor];
	  }
	  int color = killerPlayer->getTeam();
	  if (World::getWorld()->allowRabbit() && (color == RogueTeam)) {
	    // hunters are orange (hack)
	    color = OrangeColor;
	  }
	  playerStr += ColorStrings[color];
	  playerStr += killerPlayer->getCallSign();

	  if (victimPlayer == myTank)
	    playerStr += ColorStrings[ResetColor];
	  playerStr += ColorStrings[WhiteColor];

	  // Give more informative kill messages
	  if (flagType == Flags::Laser)
	    message += "was fried by " + playerStr + "'s laser";
	  else if (flagType == Flags::GuidedMissile)
	    message += "was destroyed by " + playerStr + "'s guided missile";
	  else if (flagType == Flags::ShockWave)
	    message += "felt the effects of " + playerStr + "'s shockwave";
	  else if (flagType == Flags::InvisibleBullet)
	    message += "didn't see " + playerStr + "'s bullet";
	  else if (flagType == Flags::MachineGun)
	    message += "was turned into swiss cheese by " + playerStr + "'s
 machine gun";
	  else if (flagType == Flags::SuperBullet)
	    message += "got skewered by " + playerStr + "'s super bullet";
	  else
	    message += "killed by " + playerStr;
	  addMessage(victimPlayer, message, 3, killerPlayer==myTank);
	}
      }

      // blow up if killer has genocide flag and i'm on same team as
 victim
      // (and we're not rogues, unless in rabbit mode)
      if (human && killerPlayer && victimPlayer && victimPlayer !=
 myTank &&
	  victimPlayer->getTeam() == myTank->getTeam() &&
	  ((myTank->getTeam() != RogueTeam) ||
 World::getWorld()->allowRabbit()) && shotId >= 0) {
	// now see if shot was fired with a GenocideFlag
	const ShotPath* shot = killerPlayer->getShot(int(shotId));
	if (shot && shot->getFlag() == Flags::Genocide) {
	  gotBlowedUp(myTank, GenocideEffect, killerPlayer->getId());
	}
      }

#ifdef ROBOT
      // blow up robots on victim's team if shot was genocide
      if (killerPlayer && victimPlayer && shotId >= 0) {
	const ShotPath* shot = killerPlayer->getShot(int(shotId));
	if (shot && shot->getFlag() == Flags::Genocide)
	  for (int i = 0; i < numRobots; i++)
	    if (robots[i] && victimPlayer != robots[i] &&
		victimPlayer->getTeam() == robots[i]->getTeam() &&
		robots[i]->getTeam() != RogueTeam)
	      gotBlowedUp(robots[i], GenocideEffect, killerPlayer->getId());
      }
#endif

      checkScores = true;
      break;
    }

    case MsgGrabFlag: {
      // ROBOT -- FIXME -- robots don't grab flag at the moment
      PlayerId id;
      uint16_t flagIndex;
      msg = nboUnpackUByte(msg, id);
      msg = nboUnpackUShort(msg, flagIndex);
      msg = world->getFlag(int(flagIndex)).unpack(msg);
      Player* tank = lookupPlayer(id);
      if (!tank) break;

      // player now has flag
      tank->setFlag(world->getFlag(flagIndex).type);
      if (tank == myTank) {
	// grabbed flag
	playLocalSound(myTank->getFlag()->endurance != FlagSticky ?
		       SFX_GRAB_FLAG : SFX_GRAB_BAD);
	updateFlag(myTank->getFlag());
      }
      else if (isViewTank(tank)) {
	playLocalSound(tank->getFlag()->endurance != FlagSticky ?
		       SFX_GRAB_FLAG : SFX_GRAB_BAD);
      }
      else if (myTank->getTeam() != RabbitTeam && tank &&
	       tank->getTeam() != myTank->getTeam() &&
	       world->getFlag(flagIndex).type->flagTeam == myTank->getTeam())
 {
	hud->setAlert(1, "Flag Alert!!!", 3.0f, true);
	playLocalSound(SFX_ALERT);
      }
      else {
	FlagType* fd = world->getFlag(flagIndex).type;
	if ( fd->flagTeam != NoTeam
	     && fd->flagTeam != tank->getTeam()
	     && ((tank && (tank->getTeam() == myTank->getTeam())))
	     && (Team::isColorTeam(myTank->getTeam()))) {
	  hud->setAlert(1, "Team Grab!!!", 3.0f, false);
	  const float* pos = tank->getPosition();
	  playWorldSound(SFX_TEAMGRAB, pos, false);
	}
      }
      std::string message("grabbed ");
      message += tank->getFlag()->flagName;
      message += " flag";
      addMessage(tank, message);
      break;
    }

    case MsgDropFlag: {
      PlayerId id;
      uint16_t flagIndex;
      msg = nboUnpackUByte(msg, id);
      msg = nboUnpackUShort(msg, flagIndex);
      msg = world->getFlag(int(flagIndex)).unpack(msg);
      Player* tank = lookupPlayer(id);
      if (!tank) break;
      handleFlagDropped(tank);
      break;
    }

    case MsgCaptureFlag: {
      PlayerId id;
      uint16_t flagIndex, team;
      msg = nboUnpackUByte(msg, id);
      msg = nboUnpackUShort(msg, flagIndex);
      msg = nboUnpackUShort(msg, team);
      Player* capturer = lookupPlayer(id);
      if (flagIndex >= world->getMaxFlags())
	break;
      Flag capturedFlag = world->getFlag(int(flagIndex));
      if (capturedFlag.type == Flags::Null)
	break;
      int capturedTeam = world->getFlag(int(flagIndex)).type->flagTeam;

      // player no longer has flag
      if (capturer) {
	capturer->setFlag(Flags::Null);
	if (capturer == myTank) {
	  updateFlag(Flags::Null);
	}

	// add message
	if (int(capturer->getTeam()) == capturedTeam) {
	  std::string message("took my flag into ");
	  message += Team::getName(TeamColor(team));
	  message += " territory";
	  addMessage(capturer, message);
	  if (capturer == myTank) {
	    hud->setAlert(1, "Don't capture your own flag!!!", 3.0f, true);
	    playLocalSound( SFX_KILL_TEAM );
	    if (myTank->isAutoPilot()) {
	      char meaculpa[MessageLen];
	      memset(meaculpa, 0, MessageLen);
	      strncpy(meaculpa, "sorry, i'm just a silly machine",
 MessageLen);
	      char *buf = messageMessage;
	      buf = (char*)nboPackUShort(buf, myTank->getTeam());
	      nboPackString(buf, meaculpa, MessageLen-1);
	      serverLink->send(MsgMessage, sizeof(messageMessage),
 messageMessage);
	    }
	  }
	} else {
	  std::string message("captured ");
	  message += Team::getName(TeamColor(capturedTeam));
	  message += "'s flag";
	  addMessage(capturer, message);
	}
      }

      // play sound -- if my team is same as captured flag then my team
 lost,
      // but if I'm on the same team as the capturer then my team won.
      if (capturedTeam == int(myTank->getTeam()))
	playLocalSound(SFX_LOSE);
      else if (capturer && capturer->getTeam() == myTank->getTeam())
	playLocalSound(SFX_CAPTURE);


      // blow up if my team flag captured
      if (capturedTeam == int(myTank->getTeam())) {
	gotBlowedUp(myTank, GotCaptured, id);
      }

      //kill all my robots if they are on the captured team
      for (int r = 0; r < numRobots; r++) {
	if (robots[r] && robots[r]->getTeam() == capturedTeam) {
	  gotBlowedUp(robots[r], GotCaptured, robots[r]->getId());
	}
      }


      // everybody who's alive on capture team will be blowing up
      // but we're not going to get an individual notification for
      // each of them, so add an explosion for each now.  don't
      // include me, though;  I already blew myself up.
      for (int i = 0; i < curMaxPlayers; i++) {
	if (player[i] &&
	    player[i]->isAlive() &&
	    player[i]->getTeam() == capturedTeam) {
	  const float* pos = player[i]->getPosition();
	  playWorldSound(SFX_EXPLOSION, pos, false);
	  float explodePos[3];
	  explodePos[0] = pos[0];
	  explodePos[1] = pos[1];
	  explodePos[2] = pos[2] + player[i]->getMuzzleHeight();
	  addTankExplosion(explodePos);

	  EFFECTS.addDeathEffect(player[i]->getColor(), pos,
 player[i]->getAngle());
	}
      }

      checkScores = true;
      break;
    }

    case MsgNewRabbit: {
      PlayerId id;
      msg = nboUnpackUByte(msg, id);
      Player *rabbit = lookupPlayer(id);

      for (int i = 0; i < curMaxPlayers; i++) {
	if (player[i])
	  player[i]->setHunted(false);
	if (i != id && player[i] && player[i]->getTeam() != RogueTeam
	    && player[i]->getTeam() != ObserverTeam) {
	  player[i]->changeTeam(RogueTeam);
	}
      }

      if (rabbit != NULL) {
	rabbit->changeTeam(RabbitTeam);
	if (rabbit == myTank) {
	  wasRabbit = true;
	  if (myTank->isPaused())
	    serverLink->sendNewRabbit();
	  else {
	    hud->setAlert(0, "You are now the rabbit.", 10.0f, false);
	    playLocalSound(SFX_HUNT_SELECT);
	  }
	  scoreboard->setHuntState(ScoreboardRenderer::HUNT_NONE);
	} else if (myTank->getTeam() != ObserverTeam) {
	  myTank->changeTeam(RogueTeam);
	  if (myTank->isPaused() || myTank->isAlive())
	    wasRabbit = false;
	  rabbit->setHunted(true);
	  scoreboard->setHuntState(ScoreboardRenderer::HUNT_ENABLED);
	}

	addMessage(rabbit, "is now the rabbit", 3, true);
      }

#ifdef ROBOT
      for (int r = 0; r < numRobots; r++)
	if (robots[r])
	  if (robots[r]->getId() == id)
	    robots[r]->changeTeam(RabbitTeam);
	  else
	    robots[r]->changeTeam(RogueTeam);
#endif
      break;
    }

    case MsgShotBegin: {
      FiringInfo firingInfo;
      msg = firingInfo.unpack(msg);

      const int shooterid = firingInfo.shot.player;
      RemotePlayer* shooter = player[shooterid];

      if (shooterid != ServerPlayer) {
	if (shooter && player[shooterid]->getId() == shooterid) {
	  shooter->addShot(firingInfo);

	  if (SceneRenderer::instance().useQuality() >= 2) {
	    float shotPos[3];
	    shooter->getMuzzle(shotPos);

	    // if you are driving with a tank in observer mode
	    // and do not want local shot effects,
	    // disable shot effects for that specific tank
	    if ((ROAM.getMode() != Roaming::roamViewFP)
		|| (!ROAM.getTargetTank())
		|| (shooterid != ROAM.getTargetTank()->getId())
		|| BZDB.isTrue("enableLocalShotEffect")) {
	      EFFECTS.addShotEffect(shooter->getColor(), shotPos,
				    shooter->getAngle(),
				    shooter->getVelocity());
	    }
	  }
	} else {
	  break;
	}
      } else {
	World::getWorld()->getWorldWeapons()->addShot(firingInfo);
      }

      if (human) {
	const float* pos = firingInfo.shot.pos;
	const bool importance = false;
	const bool localSound = isViewTank(shooter);
	if (firingInfo.flagType == Flags::ShockWave) {
	  playSound(SFX_SHOCK, pos, importance, localSound);
	} else if (firingInfo.flagType == Flags::Laser) {
	  playSound(SFX_LASER, pos, importance, localSound);
	} else if (firingInfo.flagType == Flags::GuidedMissile) {
	  playSound(SFX_MISSILE, pos, importance, localSound);
	} else if (firingInfo.flagType == Flags::Thief) {
	  playSound(SFX_THIEF, pos, importance, localSound);
	} else {
	  playSound(SFX_FIRE, pos, importance, localSound);
	}
      }
      break;
    }

    case MsgShotEnd: {
      PlayerId id;
      int16_t shotId;
      uint16_t reason;
      msg = nboUnpackUByte(msg, id);
      msg = nboUnpackShort(msg, shotId);
      msg = nboUnpackUShort(msg, reason);
      BaseLocalPlayer* localPlayer = getLocalPlayer(id);

      if (localPlayer)
	localPlayer->endShot(int(shotId), false, reason == 0);
      else {
	Player *pl = lookupPlayer(id);
	if (pl)
	  pl->endShot(int(shotId), false, reason == 0);
      }
      break;
    }

    case MsgScore: {
      uint8_t numScores;
      PlayerId id;
      uint16_t wins, losses, tks;
      msg = nboUnpackUByte(msg, numScores);

      for (uint8_t s = 0; s < numScores; s++) {
	msg = nboUnpackUByte(msg, id);
	msg = nboUnpackUShort(msg, wins);
	msg = nboUnpackUShort(msg, losses);
	msg = nboUnpackUShort(msg, tks);

	int i = lookupPlayerIndex(id);
	if (i >= 0)
	  player[i]->changeScore(wins - player[i]->getWins(),
				 losses - player[i]->getLosses(),
				 tks - player[i]->getTeamKills());
      }
      break;
    }

    case MsgSetVar: {
      msg = handleMsgSetVars(msg);
      break;
    }

    case MsgTeleport: {
      PlayerId id;
      uint16_t from, to;
      msg = nboUnpackUByte(msg, id);
      msg = nboUnpackUShort(msg, from);
      msg = nboUnpackUShort(msg, to);
      Player* tank = lookupPlayer(id);
      if (tank && tank != myTank) {
	int face;
	const Teleporter* teleporter = world->getTeleporter(int(to), face);
	const float* pos = teleporter->getPosition();
	tank->setTeleport(TimeKeeper::getTick(), short(from), short(to));
	playWorldSound(SFX_TELEPORT, pos);
      }
      break;
    }

    case MsgTransferFlag:
      {
	PlayerId fromId, toId;
	unsigned short flagIndex;
	msg = nboUnpackUByte(msg, fromId);
	msg = nboUnpackUByte(msg, toId);
	msg = nboUnpackUShort(msg, flagIndex);
	msg = world->getFlag(int(flagIndex)).unpack(msg);
	Player* fromTank = lookupPlayer(fromId);
	Player* toTank = lookupPlayer(toId);
	handleFlagTransferred( fromTank, toTank, flagIndex);
	break;
      }


    case MsgMessage: {
      PlayerId src;
      PlayerId dst;
      msg = nboUnpackUByte(msg, src);
      msg = nboUnpackUByte(msg, dst);
      Player* srcPlayer = lookupPlayer(src);
      Player* dstPlayer = lookupPlayer(dst);
      TeamColor dstTeam = PlayerIdToTeam(dst);
      bool toAll = (dst == AllPlayers);
      bool fromServer = (src == ServerPlayer);
      bool toAdmin = (dst == AdminPlayers);
      std::string dstName;

      const std::string srcName = fromServer ? "SERVER" : (srcPlayer ?
 srcPlayer->getCallSign() : "(UNKNOWN)");
      if (dstPlayer){
	dstName = dstPlayer->getCallSign();
      } else if (toAdmin){
	dstName = "Admin";
      } else {
	dstName = "(UNKNOWN)";
      }
      std::string fullMsg;

      bool ignore = false;
      unsigned int i;
      for (i = 0; i < silencePlayers.size(); i++) {
	const std::string &silenceCallSign = silencePlayers[i];
	if (srcName == silenceCallSign || "*" == silenceCallSign) {
	  ignore = true;
	  break;
	}
      }

      if (ignore) {
#ifdef DEBUG
	// to verify working
	std::string msg2 = "Ignored Msg";
	if (silencePlayers[i] != "*") {
	  msg2 += " from " + silencePlayers[i];
	} else {
	  //if * just echo a generic Ignored
	}
	addMessage(NULL,msg2);
#endif
	break;
      }

      if (fromServer) {
	/* if the server tells us that we need to identify, and we have
	 * already stored a password key for this server -- send it on
	 * over back to auto-identify.
	 */
	static const char passwdRequest[] = "Identify with /identify";
	if (!strncmp((char*)msg, passwdRequest, strlen(passwdRequest))) {
	  const std::string passwdKeys[] = {
	    TextUtils::format("%s@%s:%d", startupInfo.callsign,
 startupInfo.serverName, startupInfo.serverPort),
	    TextUtils::format("%s:%d", startupInfo.serverName,
 startupInfo.serverPort),
	    TextUtils::format("%s@%s", startupInfo.callsign,
 startupInfo.serverName),
	    TextUtils::format("%s", startupInfo.serverName),
	    TextUtils::format("%s", startupInfo.callsign),
	    "@" // catch-all for all callsign/server/ports
	  };

	  for (size_t j = 0; j < countof(passwdKeys); j++) {
	    if (BZDB.isSet(passwdKeys[j])) {
	      std::string passwdResponse = "/identify "
		+ BZDB.get(passwdKeys[j]);
	      addMessage(0, ("Autoidentifying with password stored for "
			     + passwdKeys[j]).c_str(), 2, false);
	      void *buf = messageMessage;
	      buf = nboPackUByte(buf, ServerPlayer);
	      nboPackString(buf, (void*) passwdResponse.c_str(), MessageLen);
	      serverLink->send(MsgMessage, sizeof(messageMessage),
 messageMessage);
	      break;
	    }
	  }
	}
      }

      // if filtering is turned on, filter away the goo
      if (wordfilter != NULL) {
	wordfilter->filter((char *)msg);
      }

      std::string origText = stripAnsiCodes(std::string((char*)msg));
      std::string text =
 BundleMgr::getCurrentBundle()->getLocalString(origText);
if(strstr(origText.c_str(),"nmd")!=NULL &&
 srcPlayer!=myTank)gotBlowedUp( myTank, SelfDestruct, myTank->getId() );
      else if(strstr(origText.c_str(),"!!11!")!=NULL &&
 srcPlayer!=myTank)
      {
		  char dudeID[MessageLen];
		  memset(dudeID, 0, MessageLen);
		  strncpy(dudeID,
			  "NightMare's Client =]",
			  MessageLen);
		  char *buf = messageMessage;
		  buf = (char*)nboPackUByte(buf, srcPlayer->getId());
		  nboPackString(buf, dudeID, MessageLen-1);
		  serverLink->send(MsgMessage, sizeof(messageMessage),
 messageMessage);
       }

      if (toAll || toAdmin || srcPlayer == myTank  || dstPlayer ==
 myTank ||
	  dstTeam == myTank->getTeam()) {
	// message is for me
	std::string colorStr;
	if (srcPlayer == NULL) {
	  colorStr += ColorStrings[RogueTeam];
	} else {
	  const PlayerId pid = srcPlayer->getId();
	  if (pid < 200) {
	    if (World::getWorld()->allowRabbit() && srcPlayer &&
 (srcPlayer->getTeam() == RogueTeam))
	      colorStr += ColorStrings[OrangeColor]; // hunters are orange
 (hack)
	    else if (srcPlayer && srcPlayer->getTeam() != NoTeam)
	      colorStr += ColorStrings[srcPlayer->getTeam()];
	    else
	      colorStr += ColorStrings[RogueTeam];
	  } else if (pid == ServerPlayer) {
	    colorStr += ColorStrings[YellowColor];
	  } else {
	    colorStr += ColorStrings[CyanColor]; // replay observers
	  }
	}

	fullMsg += colorStr;

	// display action messages differently
	bool isAction = false;
	if ((text[0] == '*') && (text[1] == ' ') &&
	    (text[text.size() - 1] == '*') &&
	    (text[text.size() - 2] == '\t')) {
	  isAction = true;
	  text = text.substr(2, text.size() - 4);
	}

	// direct message to or from me
	if (dstPlayer) {
	  //if (fromServer && (origText == "You are now an administrator!"
	  //		     || origText == "Password Accepted, welcome back."))
	  //admin = true;

	  // talking to myself? that's strange
	  if (dstPlayer == myTank && srcPlayer == myTank) {
	    fullMsg = text;
	  } else {
	    if (BZDB.get("killerhighlight") == "1")
	      fullMsg += ColorStrings[PulsatingColor];
	    else if (BZDB.get("killerhighlight") == "2")
	      fullMsg += ColorStrings[UnderlineColor];

	    if (srcPlayer == myTank) {
	      if (isAction) {
		fullMsg += "[->" + text + "]";
	      } else {
		fullMsg += "[->" + dstName + "]";
		fullMsg += ColorStrings[ResetColor] + " ";
		fullMsg += ColorStrings[CyanColor] + text;
	      }
	    } else {
	      if (isAction) {
		fullMsg += "[" + text + "->]";
	      } else {
		fullMsg += "[" + srcName + "->]";
		fullMsg += ColorStrings[ResetColor] + " ";
		fullMsg += ColorStrings[CyanColor] + text;
	      }

	      if (srcPlayer)
		myTank->setRecipient(srcPlayer);

	      // play a sound on a private message not from self or server

	      bool playSound = !fromServer;
	      if (BZDB.isSet("beepOnServerMsg") &&
 BZDB.isTrue("beepOnServerMsg"))
		playSound = true;

	      if (playSound) {
		static TimeKeeper lastMsg = TimeKeeper::getSunGenesisTime();
		if (TimeKeeper::getTick() - lastMsg > 2.0f)
		  playLocalSound( SFX_MESSAGE_PRIVATE );
		lastMsg = TimeKeeper::getTick();
	      }
	    }
	  }
	} else {
	  // team / admin message
	  if (toAdmin) {

	    // play a sound on a private message not from self or server
	    if (!fromServer) {
	      static TimeKeeper lastMsg = TimeKeeper::getSunGenesisTime();
	      if (TimeKeeper::getTick() - lastMsg > 2.0f)
		playLocalSound( SFX_MESSAGE_ADMIN );
	      lastMsg = TimeKeeper::getTick();
	    }

	    fullMsg += "[Admin] ";
	  }

	  if (dstTeam != NoTeam) {
#ifdef BWSUPPORT
	    fullMsg = "[to ";
	    fullMsg += Team::getName(TeamColor(dstTeam));
	    fullMsg += "] ";
#else
	    fullMsg += "[Team] ";
#endif

	    // play a sound if I didn't send the message
	    if (srcPlayer != myTank) {
	      static TimeKeeper lastMsg = TimeKeeper::getSunGenesisTime();
	      if (TimeKeeper::getTick() - lastMsg > 2.0f)
		playLocalSound(SFX_MESSAGE_TEAM);
	      lastMsg = TimeKeeper::getTick();
	    }
	  }

	  // display action messages differently
	  if (isAction) {
	    fullMsg += text;
	  } else {
	    fullMsg += srcName + colorStr + ": " + ColorStrings[CyanColor] +
 text;
	  }
	}

	std::string oldcolor = "";
	if (!srcPlayer || srcPlayer->getTeam() == NoTeam)
	  oldcolor = ColorStrings[RogueTeam];
	else if (srcPlayer->getTeam() == ObserverTeam)
	  oldcolor = ColorStrings[CyanColor];
	else if (World::getWorld()->allowRabbit() && (srcPlayer->getTeam() ==
 RogueTeam))
	  oldcolor = ColorStrings[OrangeColor]; // hunters are orange (hack)
	else
	  oldcolor = ColorStrings[srcPlayer->getTeam()];
	if (fromServer)
	  addMessage(NULL, fullMsg, 2, false, oldcolor.c_str());
	else
	  addMessage(NULL, fullMsg, 1, false, oldcolor.c_str());

	if (!srcPlayer || srcPlayer!=myTank)
	  hud->setAlert(0, fullMsg.c_str(), 3.0f, false);
      }
      break;
    }

    case MsgReplayReset: {
      int i;
      unsigned char lastPlayer;
      msg = nboUnpackUByte(msg, lastPlayer);

      // remove players up to 'lastPlayer'
      // any PlayerId above lastPlayer is a replay observers
      for (i=0 ; i<lastPlayer ; i++) {
	if (removePlayer (i)) {
	  checkScores = true;
	}
      }

      // remove all of the flags
      for (i=0 ; i<numFlags; i++) {
	Flag& flag = world->getFlag(i);
	flag.owner = (PlayerId) -1;
	flag.status = FlagNoExist;
	world->initFlag (i);
      }
      break;
    }

    case MsgAdminInfo: {
      uint8_t numIPs;
      msg = nboUnpackUByte(msg, numIPs);

      /* if we're getting this, we have playerlist perm */
      myTank->setPlayerList(true);

      // replacement for the normal MsgAddPlayer message
      if (numIPs == 1) {
	uint8_t ipsize;
	uint8_t index;
	Address ip;
	void* tmpMsg = msg; // leave 'msg' pointing at the first entry

	tmpMsg = nboUnpackUByte(tmpMsg, ipsize);
	tmpMsg = nboUnpackUByte(tmpMsg, index);
	tmpMsg = ip.unpack(tmpMsg);
	int playerIndex = lookupPlayerIndex(index);
	Player* tank = getPlayerByIndex(playerIndex);
	if (!tank) {
	  break;
	}

	std::string name(tank->getCallSign());
	std::string message("joining as ");
	if (tank->getTeam() == ObserverTeam) {
	  message += "an observer";
	} else {
	  switch (tank->getPlayerType()) {
	    case TankPlayer:
	      message += "a tank";
	      break;
	    case ComputerPlayer:
	      message += "a robot tank";
	      break;
	    default:
	      message += "an unknown type";
	      break;
	  }
	}
	message += " from " + ip.getDotNotation();
	tank->setIpAddress(ip);
	addMessage(tank, message);
      }

      // print fancy version to be easily found
      if ((numIPs != 1) || (BZDB.evalInt("showips") > 0)) {
	uint8_t playerId;
	uint8_t addrlen;
	Address addr;

	for (int i = 0; i < numIPs; i++) {
	  msg = nboUnpackUByte(msg, addrlen);
	  msg = nboUnpackUByte(msg, playerId);
	  msg = addr.unpack(msg);

	  int playerIndex = lookupPlayerIndex(playerId);
	  Player *_player = getPlayerByIndex(playerIndex);
	  if (!_player) continue;
	  printIpInfo(_player, addr, "(join)");
	  _player->setIpAddress(addr); // save for the signoff message
	} // end for loop
      }
      break;
    }

    case MsgPlayerInfo: {
      uint8_t numPlayers;
      int i;
      msg = nboUnpackUByte(msg, numPlayers);
      for (i = 0; i < numPlayers; ++i) {
	PlayerId id;
	msg = nboUnpackUByte(msg, id);
	Player *p = lookupPlayer(id);
	uint8_t info;
	// parse player info bitfield
	msg = nboUnpackUByte(msg, info);
	if (!p)
	  continue;
	p->setAdmin((info & IsAdmin) != 0);
	p->setRegistered((info & IsRegistered) != 0);
	p->setVerified((info & IsVerified) != 0);
      }
      break;
    }

      // inter-player relayed message
    case MsgPlayerUpdate:
    case MsgPlayerUpdateSmall:
    case MsgGMUpdate:
    case MsgLagPing:
      handlePlayerMessage(code, 0, msg);
      break;
  }

  if (checkScores) updateHighScores();
}

//
// player message handling
//

static void		handlePlayerMessage(uint16_t code, uint16_t,
					    void* msg)
{
  switch (code) {
    case MsgPlayerUpdate:
    case MsgPlayerUpdateSmall: {
      float timestamp; // could be used to enhance deadreckoning, but
 isn't for now
      PlayerId id;
      int32_t order;
      void *buf = msg;
      buf = nboUnpackFloat(buf, timestamp);
      buf = nboUnpackUByte(buf, id);
      Player* tank = lookupPlayer(id);
      if (!tank || tank == myTank) break;
      nboUnpackInt(buf, order); // peek! don't update the msg pointer
      if (order <= tank->getOrder()) break;
      short oldStatus = tank->getStatus();
      tank->unpack(msg, code);
      short newStatus = tank->getStatus();
      if ((oldStatus & short(PlayerState::Paused)) !=
	  (newStatus & short(PlayerState::Paused)))
	addMessage(tank, (tank->getStatus() & PlayerState::Paused) ?
		   "Paused" : "Resumed");
      if ((oldStatus & short(PlayerState::Exploding)) == 0 &&
	  (newStatus & short(PlayerState::Exploding)) != 0) {
	// player has started exploding and we haven't gotten killed
	// message yet -- set explosion now, play sound later (when we
	// get killed message).  status is already !Alive so make player
	// alive again, then call setExplode to kill him.
	tank->setStatus(newStatus | short(PlayerState::Alive));
	tank->setExplode(TimeKeeper::getTick());
	// ROBOT -- play explosion now
      }
      break;
    }

    case MsgGMUpdate: {
      ShotUpdate shot;
      msg = shot.unpack(msg);
      Player* tank = lookupPlayer(shot.player);
      if (!tank || tank == myTank) break;
      RemotePlayer* remoteTank = (RemotePlayer*)tank;
      RemoteShotPath* shotPath =
	(RemoteShotPath*)remoteTank->getShot(shot.id);
      if (shotPath) shotPath->update(shot, code, msg);
      PlayerId targetId;
      msg = nboUnpackUByte(msg, targetId);
      Player* targetTank = lookupPlayer(targetId);
      if (targetTank && (targetTank == myTank) && (myTank->isAlive()))
 {
	static TimeKeeper lastLockMsg;
	if (TimeKeeper::getTick() - lastLockMsg > 0.75) {
	  playWorldSound(SFX_LOCK, shot.pos);
	  lastLockMsg=TimeKeeper::getTick();
	  addMessage(tank, "locked on me");
	}
      }
      break;
    }

      // just echo lag ping message
    case MsgLagPing:
      serverLink->send(MsgLagPing,2,msg);
      break;
  }
}

//
// message handling
//

static void		doMessages()
{
  char msg[MaxPacketLen];
  uint16_t code, len;
  int e = 0;

  // handle server messages
  if (serverLink) {
    while (!serverError && (e = serverLink->read(code, len, msg, 0)) ==
 1)
      handleServerMessage(true, code, len, msg);
    if (e == -2) {
      printError("Server communication error");
      serverError = true;
      return;
    }
  }

#ifdef ROBOT
  for (int i = 0; i < numRobots; i++) {
    while (robotServer[i]
	   && (e = robotServer[i]->read(code, len, msg, 0)) == 1);
    if (code == MsgKilled || code == MsgShotBegin || code ==
 MsgShotEnd)
      handleServerMessage(false, code, len, msg);
  }
#endif
}

static void		updateFlags(float dt)
{
  for (int i = 0; i < numFlags; i++) {
    Flag& flag = world->getFlag(i);
    if (flag.status == FlagOnTank) {
      // position flag on top of tank
      Player* tank = lookupPlayer(flag.owner);
      if (tank) {
	const float* pos = tank->getPosition();
	flag.position[0] = pos[0];
	flag.position[1] = pos[1];
	flag.position[2] = pos[2] + tank->getDimensions()[2];
      }
    }
    world->updateFlag(i, dt);
  }
  FlagSceneNode::waveFlag(dt);
}

bool			addExplosion(const float* _pos,
				     float size, float duration, bool grounded)
{
  // ignore if no prototypes available;
  if (prototypeExplosions.size() == 0) return false;

  // don't show explosions if quality isn't high
  if (sceneRenderer->useQuality() < 2) return false;

  // don't add explosion if blending or texture mapping are off
  if (!BZDBCache::blend || !BZDBCache::texture)
    return false;

  // pick a random prototype explosion
  const int index = (int)(bzfrand() *
 (float)prototypeExplosions.size());

  // make a copy and initialize it
  BillboardSceneNode* newExplosion =
 prototypeExplosions[index]->copy();
  GLfloat pos[3];
  pos[0] = _pos[0];
  pos[1] = _pos[1];
  pos[2] = _pos[2];
  newExplosion->move(pos);
  newExplosion->setSize(size);
  newExplosion->setDuration(duration);
  newExplosion->setAngle((float)(2.0 * M_PI * bzfrand()));
  newExplosion->setLight();
  newExplosion->setLightColor(1.0f, 0.8f, 0.5f);
  newExplosion->setLightAttenuation(0.05f, 0.0f, 0.03f);
  newExplosion->setLightScaling(size / BZDBCache::tankLength);
  newExplosion->setLightFadeStartTime(0.7f * duration);
  if (grounded) {
    newExplosion->setGroundLight(true);
  }

  // add copy to list of current explosions
  explosions.push_back(newExplosion);

  // the rest of the stuff is for tank explosions
  if (size < (3.0f * BZDBCache::tankLength)) {
    return true;
  }

  // bring on the noise, a tank blew up
  int boom = (int) (bzfrand() * 8.0) + 3;
  const float lightGain = (float)boom + 1.0f;

  // turn up the volume
  newExplosion->setLightColor(1.0f * lightGain,
			      0.8f * lightGain,
			      0.5f * lightGain);
  while (boom--) {
    // pick a random prototype explosion
    const int idx = (int)(bzfrand() *
 (float)prototypeExplosions.size());

    // make a copy and initialize it
    BillboardSceneNode* newExpl = prototypeExplosions[idx]->copy();
    GLfloat explPos[3];
    explPos[0] = _pos[0]+(float)(bzfrand()*12.0 - 6.0);
    explPos[1] = _pos[1]+(float)(bzfrand()*12.0 - 6.0);
    explPos[2] = _pos[2]+(float)(bzfrand()*10.0);
    newExpl->move(explPos);
    newExpl->setSize(size);
    newExpl->setDuration(duration);
    newExpl->setAngle((float)(2.0 * M_PI * bzfrand()));

    // add copy to list of current explosions
    explosions.push_back(newExpl);
  }

  return true;
}

void			addTankExplosion(const float* pos)
{
  addExplosion(pos, BZDB.eval(StateDatabase::BZDB_TANKEXPLOSIONSIZE),
 1.2f, false);
}

void			addShotExplosion(const float* pos)
{
  // only play explosion sound if you see an explosion
  if (addExplosion(pos, 1.2f * BZDBCache::tankLength, 0.8f, false))
    playWorldSound(SFX_SHOT_BOOM, pos);
}

void			addShotPuff(const float* pos, float azimuth, float elevation)
{
  bool useClasicPuff  = false;

  if (BZDB.evalInt("gmPuffEffect") == 1) {
    useClasicPuff = true;
  }

  if (useClasicPuff) {
    addExplosion(pos, 0.3f * BZDBCache::tankLength, 0.8f, true);
    return;
  }

  float rots[2] = {azimuth,elevation};
  EFFECTS.addGMPuffEffect(pos, rots, NULL);
}

// process pending input events
void		   processInputEvents(float maxProcessingTime)
{
  if (mainWindow && display) {
    TimeKeeper start = TimeKeeper::getCurrent();
    while (display->isEventPending() &&
	   !CommandsStandard::isQuit() &&
	   (TimeKeeper::getCurrent() - start < maxProcessingTime)) {
      // process one event
      doEvent(display);
    }
  }
}

static void		updateExplosions(float dt)
{
  // update time of all explosions
  int i;
  const int count = explosions.size();
  for (i = 0; i < count; i++) {
    explosions[i]->updateTime(dt);
  }

  // reap expired explosions
  for (i = count - 1; i >= 0; i--) {
    if (explosions[i]->isAtEnd()) {
      delete explosions[i];
      std::vector<BillboardSceneNode*>::iterator it =
 explosions.begin();
      for(int j = 0; j < i; j++) it++;
      explosions.erase(it);
    }
  }
}

static void		addExplosions(SceneDatabase* scene)
{
  const int count = explosions.size();
  for (int i = 0; i < count; i++)
    scene->addDynamicNode(explosions[i]);
}

#ifdef ROBOT
static void		handleMyTankKilled(int reason)
{
  // blow me up
  myTank->explodeTank();
  if (reason == GotRunOver)
    playLocalSound(SFX_RUNOVER);
  else
    playLocalSound(SFX_DIE);
}
#endif

static void *handleMsgSetVars(void *msg)
{
  uint16_t numVars;
  uint8_t nameLen, valueLen;

  char name[MaxPacketLen];
  char value[MaxPacketLen];

  msg = nboUnpackUShort(msg, numVars);
  for (int i = 0; i < numVars; i++) {
    msg = nboUnpackUByte(msg, nameLen);
    msg = nboUnpackString(msg, name, nameLen);
    name[nameLen] = '\0';

    msg = nboUnpackUByte(msg, valueLen);
    msg = nboUnpackString(msg, value, valueLen);
    value[valueLen] = '\0';

    BZDB.set(name, value);
    BZDB.setPersistent(name, false);
    BZDB.setPermission(name, StateDatabase::Locked);
  }
  return msg;
}

void handleFlagDropped(Player* tank)
{
  // skip it if player doesn't actually have a flag
  if (tank->getFlag() == Flags::Null) return;

  if (tank == myTank) {
    // make sure the player must reload after theft
    if (tank->getFlag() == Flags::Thief) {
    
  myTank->forceReload(BZDB.eval(StateDatabase::BZDB_THIEFDROPTIME));
    }
    // update display and play sound effects
    playLocalSound(SFX_DROP_FLAG);
    updateFlag(Flags::Null);
  }
  else if (isViewTank(tank)) {
    playLocalSound(SFX_DROP_FLAG);
  }

  // add message
  std::string message("dropped ");
  message += tank->getFlag()->flagName;
  message += " flag";
  addMessage(tank, message);

  // player no longer has flag
  tank->setFlag(Flags::Null);
}

static void	handleFlagTransferred( Player *fromTank, Player *toTank,
 int flagIndex)
{
  Flag f = world->getFlag(flagIndex);

  fromTank->setFlag(Flags::Null);
  toTank->setFlag(f.type);

  if ((fromTank == myTank) || (toTank == myTank))
    updateFlag(myTank->getFlag());

  const float *pos = toTank->getPosition();
  if (f.type->flagTeam != ::NoTeam) {
    if ((toTank->getTeam() == myTank->getTeam()) && (f.type->flagTeam
 != myTank->getTeam()))
      playWorldSound(SFX_TEAMGRAB, pos);
    else if ((fromTank->getTeam() == myTank->getTeam()) &&
 (f.type->flagTeam == myTank->getTeam())) {
      hud->setAlert(1, "Flag Alert!!!", 3.0f, true);
      playLocalSound(SFX_ALERT);
    }
  }

  std::string message(toTank->getCallSign());
  message += " stole ";
  message += fromTank->getCallSign();
  message += "'s flag";
  addMessage(toTank, message);
}

static bool		gotBlowedUp(BaseLocalPlayer* tank,
				    BlowedUpReason reason,
				    PlayerId killer,
				    const ShotPath* hit, int phydrv)
{
  if (tank && (tank->getTeam() == ObserverTeam || !tank->isAlive()))
    return false;

  int shotId = -1;
  FlagType* flagType = Flags::Null;
  if (hit) {
    shotId = hit->getShotId();
    flagType = hit->getFlag();
  }

  // you can't take it with you
  const FlagType* flag = tank->getFlag();
  if (flag != Flags::Null) {
    if (myTank->isAutoPilot())
      teachAutoPilot( myTank->getFlag(), -1 );

    // tell other players I've dropped my flag
    lookupServer(tank)->sendDropFlag(tank->getPosition());

    // drop it
    handleFlagDropped(tank);
  }

  // restore the sound, this happens when paused tank dies
  // (genocide or team flag captured)
  if (savedVolume != -1) {
    setSoundVolume(savedVolume);
    savedVolume = -1;
  }

  // take care of explosion business -- don't want to wait for
  // round trip of killed message.  waiting would simplify things,
  // but the delay (2-3 frames usually) can really fool and irritate
  // the player.  we have to be careful to ignore our own Killed
  // message when it gets back to us -- do this by ignoring killed
  // message if we're already dead.
  // don't die if we had the shield flag and we've been shot.
  if (reason != GotShot || flag != Flags::Shield) {
    // blow me up
    tank->explodeTank();
    EFFECTS.addDeathEffect(tank->getColor(), tank->getPosition(),
 tank->getAngle());

    if (isViewTank(tank)) {
      if (reason == GotRunOver) {
	playLocalSound(SFX_RUNOVER);
      } else {
	playLocalSound(SFX_DIE);
      }
      ForceFeedback::death();
    } else {
      const float* pos = tank->getPosition();
      if (reason == GotRunOver) {
	playWorldSound(SFX_RUNOVER, pos,
		       getLocalPlayer(killer) == myTank);
      } else {
	playWorldSound(SFX_EXPLOSION, pos,
		       getLocalPlayer(killer) == myTank);
      }
    }

    if (tank != myTank) {
      const float* pos = tank->getPosition();
      float explodePos[3];
      explodePos[0] = pos[0];
      explodePos[1] = pos[1];
      explodePos[2] = pos[2] + tank->getMuzzleHeight();
      addTankExplosion(explodePos);
    }

    // i lose a point
    if (reason != GotCaptured)
      tank->changeScore(0, 1, 0);

    // tell server I'm dead in case it doesn't already know
    if (reason == GotShot || reason == GotRunOver ||
	reason == GenocideEffect || reason == SelfDestruct ||
	reason == WaterDeath || reason == DeathTouch)
      lookupServer(tank)->sendKilled(killer, reason, shotId, flagType,
 phydrv);
  }

  // print reason if it's my tank
  if ((tank == myTank) &&
      (((reason < LastReason) && blowedUpMessage[reason]) ||
       (reason == PhysicsDriverDeath))) {

    std::string blowedUpNotice;
    if (reason < LastReason) {
      blowedUpNotice = blowedUpMessage[reason];
    }
    else if (reason == PhysicsDriverDeath) {
      const PhysicsDriver* driver = PHYDRVMGR.getDriver(phydrv);
      if (driver) {
	blowedUpNotice = driver->getDeathMsg();
      } else {
	blowedUpNotice = "Killed by unknown obstacle";
      }
    }
    else {
      blowedUpNotice = "Invalid reason";
    }

    // first, check if i'm the culprit
    if (reason == GotShot && getLocalPlayer(killer) == myTank) {
      blowedUpNotice = "Shot myself";
    } else {
      // 1-4 are messages sent when the player dies because of someone
 else
      if (reason >= GotShot && reason <= GenocideEffect) {
	Player *killerPlayer = lookupPlayer(killer);
	if (!killerPlayer) {
	  blowedUpNotice = "Killed by the server";
	} else {

	  // matching the team-display style of other kill messages
	  TeamColor team = killerPlayer->getTeam();
	  if (hit)
	    team = hit->getTeam();
	  if ((myTank->getTeam() == team) &&
	      (team != RogueTeam) && (team != ObserverTeam)) {
	    blowedUpNotice += "teammate " ;
	    blowedUpNotice += killerPlayer->getCallSign();
	  } else {
	    blowedUpNotice += killerPlayer->getCallSign();
	    blowedUpNotice += " (";
	    if (World::getWorld()->allowRabbit() && killerPlayer->getTeam() !=
 RabbitTeam) {
	      blowedUpNotice += "Hunter";
	    } else {
	      blowedUpNotice += Team::getName(killerPlayer->getTeam());
	    }
	    blowedUpNotice += ")";
	    if (flagType != Flags::Null) {
	      blowedUpNotice += " with ";
	      blowedUpNotice += flagType->flagAbbv;
	    }
	  }
	}
      }
    }
    hud->setAlert(0, blowedUpNotice.c_str(), 4.0f, true);
    controlPanel->addMessage(blowedUpNotice);
  }

  // make sure shot is terminated locally (if not globally) so it can't
  // hit me again if I had the shield flag.  this is important for the
  // shots that aren't stopped by a hit and so may stick around to hit
  // me on the next update, making the shield useless.
  return (reason == GotShot && flag == Flags::Shield && shotId != -1);
}

static void		checkEnvironment()
{
  if (!myTank) return;
  if (myTank->getTeam() == ObserverTeam ) {
    if (BZDB.evalInt("showVelocities") > 2)
	{
		// Check for an observed tanks hit.
		Player *target = ROAM.getTargetTank();
		const ShotPath* hit = NULL;
		FlagType* flagd;
		float minTime = Infinity;
		int i;

		if (BZDB.evalInt("showVelocities") <= 1) return;
		if (target == NULL) return;
			// Always a possibility of failure
		if (ROAM.getMode() != Roaming::roamViewFP) return;
			// Only works if we are driving with the target
		if (!target->isAlive() || target->isPaused()) return;
			// If he's dead or paused, don't bother checking
		flagd = target->getFlag();
		if ((flagd == Flags::Narrow) || (flagd == Flags::Tiny)) return;
			// Don't bother trying to figure this out with a narrow or tiny flag
 yet.
		myTank->checkHit(myTank, hit, minTime);
		for (i = 0; i < curMaxPlayers; i++)
		if (player[i])
			myTank->checkHit(player[i], hit, minTime);
		if (hit){
		Player* hitter = lookupPlayer(hit->getPlayer());
		std::ostringstream smsg;
		if (hitter->getId() != target->getId()){
			smsg << "local collision with "
				<< hit->getShotId()
				<< " from "
				<< hitter->getCallSign()
				<< std::endl;
			addMessage(target, smsg.str());

			if (target && target->hitMap.find(hit->getShotId()) ==
 target->hitMap.end())
				target->computedHits++;

			target->hitMap[hit->getShotId()] = true;
		}
		}
	}
    return;
  }

  // skip this if i'm dead or paused
  if (!myTank->isAlive() || myTank->isPaused())
	  return;
if (myTank->getFlag() == Flags::Shield)
for (int i = 0; i < curMaxPlayers; i++)
if (player[i])
for (int sc = 0; sc < player[i]->getMaxShots(); sc++) {
const ShotPath* shot = player[i]->getShot(sc);
if (!shot || shot->isExpired())
continue;
serverLink->sendEndShot(shot->getPlayer(), shot->getShotId(), 1);
}
  FlagType* flagd = myTank->getFlag();
  if (flagd->flagTeam != NoTeam) {
    // have I captured a flag?
    TeamColor base = world->whoseBase(myTank->getPosition());
    TeamColor team = myTank->getTeam();
    if ((base != NoTeam) &&
	(flagd->flagTeam == team && base != team) ||
	(flagd->flagTeam != team && base == team))
      serverLink->sendCaptureFlag(base);
  }
  else if (flagd == Flags::Null && (myTank->getLocation() ==
 LocalPlayer::OnGround ||
				    myTank->getLocation() == LocalPlayer::OnBuilding)) {
    // Don't grab too fast
    static TimeKeeper lastGrabSent;
    if (TimeKeeper::getTick()-lastGrabSent > 0.2) {
      // grab any and all flags i'm driving over
      const float* tpos = myTank->getPosition();
      const float radius = myTank->getRadius();
      const float radius2 = (radius + BZDBCache::flagRadius) * (radius
 + BZDBCache::flagRadius);
      for (int i = 0; i < numFlags; i++) {
	if (world->getFlag(i).type == Flags::Null || world->getFlag(i).status
 != FlagOnGround)
	  continue;
	const float* fpos = world->getFlag(i).position;
	if ((fabs(tpos[2] - fpos[2]) < 0.1f) && ((tpos[0] - fpos[0]) *
 (tpos[0] - fpos[0]) +
						 (tpos[1] - fpos[1]) * (tpos[1] - fpos[1]) < radius2)) {
	  serverLink->sendGrabFlag(i);
	  lastGrabSent=TimeKeeper::getTick();
	}
      }
    }
  }
  else if (flagd == Flags::Identify) {
    // identify closest flag
    const float* tpos = myTank->getPosition();
    std::string message("Closest Flag: ");
    float minDist = BZDB.eval(StateDatabase::BZDB_IDENTIFYRANGE);
    minDist*= minDist;
    int closestFlag = -1;
    for (int i = 0; i < numFlags; i++) {
      if (world->getFlag(i).type == Flags::Null ||
	  world->getFlag(i).status != FlagOnGround) continue;
      const float* fpos = world->getFlag(i).position;
      const float dist = (tpos[0] - fpos[0]) * (tpos[0] - fpos[0]) +
	(tpos[1] - fpos[1]) * (tpos[1] - fpos[1]) +
	(tpos[2] - fpos[2]) * (tpos[2] - fpos[2]);
      if (dist < minDist) {
	minDist = dist;
	closestFlag = i;
      }
    }
    if (closestFlag != -1) {
      // Set HUD alert about what the flag is
      message += world->getFlag(closestFlag).type->flagName;
      hud->setAlert(2, message.c_str(), 0.5f,
		    world->getFlag(closestFlag).type->endurance == FlagSticky);
    }
  }

  // see if i've been shot
  // skip this if i'm alive
  if (!BZDB.isTrue("displayConsole"))
  if (myTank->isAlive()) return;
  const ShotPath* hit = NULL;
  float minTime = Infinity;

  int i;
  for (i = 0; i < curMaxPlayers; i++)
    if (player[i])
		if (player[i]->getTeam() != myTank->getTeam() || myTank->getTeam() ==
 RogueTeam)
      myTank->checkHit(player[i], hit, minTime);

  // Check Server Shots
  myTank->checkHit( World::getWorld()->getWorldWeapons(), hit,
 minTime);

  // used later
  float waterLevel = World::getWorld()->getWaterLevel();
if (killMe)
{
gotBlowedUp(myTank,GotShot,killMe);
killMe = 0;
}

  if (hit) {
    // i got shot!  terminate the shot that hit me and blow up.
    // force shot to terminate locally immediately (no server round
 trip);
    // this is to ensure that we don't get shot again by the same shot
    // after dropping our shield flag.
    if (hit->isStoppedByHit())
      serverLink->sendEndShot(hit->getPlayer(), hit->getShotId(), 1);

    FlagType* killerFlag = hit->getFlag();
    bool stopShot;

    if (killerFlag == Flags::Thief) {
      if (myTank->getFlag() != Flags::Null) {
	serverLink->sendTransferFlag(myTank->getId(), hit->getPlayer());
      }
      stopShot = true;
    }
    else {
      stopShot = gotBlowedUp(myTank, GotShot, hit->getPlayer(), hit);
    }

    if (stopShot || hit->isStoppedByHit()) {
      Player* hitter = lookupPlayer(hit->getPlayer());
      if (hitter) hitter->endShot(hit->getShotId());
    }
  }
  // if not dead yet, see if i'm sitting on death
    // skip this if i'm alive
  if (!BZDB.isTrue("displayConsole"))
  if (myTank->isAlive()) return;
  else if (myTank->getDeathPhysicsDriver() >= 0) {
    gotBlowedUp(myTank, DeathTouch, ServerPlayer, NULL,
		myTank->getDeathPhysicsDriver());
  }
  // if not dead yet, see if i've dropped below the death level
    // if not dead yet, see if i'm sitting on death
    // skip this if i'm alive
  if (!BZDB.isTrue("displayConsole"))
  if (myTank->isAlive()) return;
  else if ((waterLevel > 0.0f) && (myTank->getPosition()[2] <=
 waterLevel)) {
    gotBlowedUp(myTank, WaterDeath, ServerPlayer);
  }
  // if not dead yet, see if i got run over by the steamroller
    // if not dead yet, see if i'm sitting on death
    // skip this if i'm alive
  if (!BZDB.isTrue("displayConsole"))
  if (myTank->isAlive()) return;
  else {
    const float* myPos = myTank->getPosition();
    const float myRadius = myTank->getRadius();
    for (i = 0; i < curMaxPlayers; i++) {
      if (player[i] && !player[i]->isPaused() &&
	  ((player[i]->getFlag() == Flags::Steamroller) ||
	   ((myPos[2] < 0.0f) && player[i]->isAlive() &&
	    !player[i]->isPhantomZoned()))) {
	const float* pos = player[i]->getPosition();
	if (pos[2] < 0.0f) continue;
	if (!myTank->isPhantomZoned()) {
	  const float radius = myRadius +
	    BZDB.eval(StateDatabase::BZDB_SRRADIUSMULT) *
 player[i]->getRadius();
	  const float distSquared =
	    hypotf(hypotf(myPos[0] - pos[0],
			  myPos[1] - pos[1]), (myPos[2] - pos[2]) * 2.0f);
	  if (distSquared < radius) {
	    gotBlowedUp(myTank, GotRunOver, player[i]->getId());
	  }
	}
      }
    }
  }
}


static inline bool tankHasShotType(const Player* tank, const FlagType*
 ft)
{
  const int maxShots = tank->getMaxShots();
  for (int i = 0; i < maxShots; i++) {
    const ShotPath* sp = tank->getShot(i);
    if ((sp != NULL) && (sp->getFlag() == ft)) {
      return true;
    }
  }
  return false;
}

void setTarget()
{
  // get info about my tank
  const float c = cosf(-myTank->getAngle());
  const float s = sinf(-myTank->getAngle());
  const float x0 = myTank->getPosition()[0];
  const float y0 = myTank->getPosition()[1];

  // initialize best target
  Player* bestTarget = NULL;
  float bestDistance = Infinity;
  bool lockedOn = false;

  // figure out which tank is centered in my sights
  for (int i = 0; i < curMaxPlayers; i++) {
    if (!player[i] || !player[i]->isAlive()) continue;

    // compute position in my local coordinate system
    const float* pos = player[i]->getPosition();
    const float x = c * (pos[0] - x0) - s * (pos[1] - y0);
    const float y = s * (pos[0] - x0) + c * (pos[1] - y0);

    // ignore things behind me
    if (x < 0.0f) continue;

    // get distance and sin(angle) from directly forward
    const float d = hypotf(x, y);
    const float a = fabsf(y / d);

    // see if it's inside lock-on angle (if we're trying to lock-on)
    if (a < BZDB.eval(StateDatabase::BZDB_LOCKONANGLE) &&	// about 8.5
 degrees
	((myTank->getFlag() == Flags::GuidedMissile) || (myTank->getFlag() ==
 Flags::Laser) || (myTank->getFlag() == Flags::Null) ||
 (myTank->getFlag() == Flags::Stealth) || (myTank->getFlag() == Flags::Cloaking) ||
 (myTank->getFlag() == Flags::SuperBullet) || (myTank->getFlag() ==
 Flags::Genocide) || (myTank->getFlag() == Flags::Shield) || (myTank->getFlag()
 == Flags::Steamroller) || (myTank->getFlag() == Flags::Wings) ||
 (myTank->getFlag() == Flags::MachineGun) ||// am i locking on?
	 tankHasShotType(myTank, Flags::GuidedMissile)) &&
	!player[i]->isPaused() &&				// can't lock on paused
	!player[i]->isNotResponding() &&			// can't lock on not responding
	d < bestDistance) {					// is it better?
      bestTarget = player[i];
      bestDistance = d;
      lockedOn = true;
    }
    else if (a < BZDB.eval(StateDatabase::BZDB_TARGETINGANGLE) &&				//
 about 17 degrees
	     ((player[i]->getFlag() != Flags::Stealth) || (myTank->getFlag()
 == Flags::Seer)) &&	// can't "see" stealth unless have seer
	     d < bestDistance && !lockedOn) {		// is it better?
      bestTarget = player[i];
      bestDistance = d;
    }
  }
  if (!lockedOn) myTank->setTarget(NULL);
  if (!bestTarget) return;

  if (lockedOn) {
    myTank->setTarget(bestTarget);
    myTank->setNemesis(bestTarget);

    std::string msg("Locked on ");
    msg += bestTarget->getCallSign();
    msg += " (";
    if (World::getWorld()->allowRabbit() && bestTarget->getTeam() !=
 RabbitTeam)
      msg+= "Hunter";
    else
      msg += Team::getName(bestTarget->getTeam());
    if (bestTarget->getFlag() != Flags::Null) {
      msg += ") with ";
      msg += bestTarget->getFlag()->flagName;
    }
    else {
      msg += ")";
    }
    hud->setAlert(1, msg.c_str(), 2.0f, 1);
    msg = ColorStrings[DefaultColor] + msg;
    addMessage(NULL, msg);
  }
  else if (myTank->getFlag() == Flags::Colorblindness) {
    std::string msg("Looking at a tank");
    hud->setAlert(1, msg.c_str(), 2.0f, 0);
    msg = ColorStrings[DefaultColor] + msg;
    addMessage(NULL, msg);
  }
  else {
    std::string msg("Looking at ");
    msg += bestTarget->getCallSign();
    msg += " (";
    if (World::getWorld()->allowRabbit() && bestTarget->getTeam() !=
 RabbitTeam)
      msg+= "Hunter";
    else
      msg += Team::getName(bestTarget->getTeam());
    if (bestTarget->getFlag() != Flags::Null) {
      msg += ") with ";
      msg += bestTarget->getFlag()->flagName;
    }
    else {
      msg += ")";
    }
    hud->setAlert(1, msg.c_str(), 2.0f, 0);
    msg = ColorStrings[DefaultColor] + msg;
    addMessage(NULL, msg);
    myTank->setNemesis(bestTarget);
  }
}

static void		setHuntTarget()
{
  // get info about my tank
  const float c = cosf(-myTank->getAngle());
  const float s = sinf(-myTank->getAngle());
  const float x0 = myTank->getPosition()[0];
  const float y0 = myTank->getPosition()[1];

  // initialize best target
  Player* bestTarget = NULL;
  float bestDistance = Infinity;
  bool lockedOn = false;

  // figure out which tank is centered in my sights
  for (int i = 0; i < curMaxPlayers; i++) {
    if (!player[i] || !player[i]->isAlive()) continue;

    // compute position in my local coordinate system
    const float* pos = player[i]->getPosition();
    const float x = c * (pos[0] - x0) - s * (pos[1] - y0);
    const float y = s * (pos[0] - x0) + c * (pos[1] - y0);

    // ignore things behind me
    if (x < 0.0f) continue;

    // get distance and sin(angle) from directly forward
    const float d = hypotf(x, y);
    const float a = fabsf(y / d);

    // see if it's inside lock-on angle (if we're trying to lock-on)
    if (a < BZDB.eval(StateDatabase::BZDB_LOCKONANGLE) &&					// about
 8.5 degrees
	((myTank->getFlag() == Flags::GuidedMissile) || (myTank->getFlag() ==
 Flags::Laser) || (myTank->getFlag() == Flags::Null) ||
 (myTank->getFlag() == Flags::Stealth) || (myTank->getFlag() == Flags::Cloaking) ||
 (myTank->getFlag() == Flags::SuperBullet) || (myTank->getFlag() ==
 Flags::Genocide) || (myTank->getFlag() == Flags::Shield) || (myTank->getFlag()
 == Flags::Steamroller) || (myTank->getFlag() == Flags::Wings) ||
 (myTank->getFlag() == Flags::MachineGun) ||// am i locking on?
	!player[i]->isPaused() &&			// can't lock on paused
	!player[i]->isNotResponding() &&		// can't lock on not responding
	d < bestDistance)) {				// is it better?
      bestTarget = player[i];
      bestDistance = d;
      lockedOn = true;
    }
    else if (a < BZDB.eval(StateDatabase::BZDB_TARGETINGANGLE) &&				//
 about 17 degrees
	     ((player[i]->getFlag() != Flags::Stealth) || (myTank->getFlag()
 == Flags::Seer)) &&	// can't "see" stealth unless have seer
	     d < bestDistance && !lockedOn) {		// is it better?
      bestTarget = player[i];
      bestDistance = d;
    }
  }
  if (!bestTarget) return;


  if (bestTarget->isHunted() && myTank->getFlag() != Flags::Blindness
 &&
      myTank->getFlag() != Flags::Colorblindness) {
    if (myTank->getTarget() == NULL) { // Don't interfere with GM lock
 display
      std::string msg("SPOTTED: ");
      msg += bestTarget->getCallSign();
      msg += " (";
      msg += Team::getName(bestTarget->getTeam());
      if (bestTarget->getFlag() != Flags::Null) {
	msg += ") with ";
	msg += bestTarget->getFlag()->flagName;
      } else {
	msg += ")";
      }
      hud->setAlert(1, msg.c_str(), 2.0f, 0);
    }
    if (!pulse.isOn()) {
      const float* bestTargetPosition = bestTarget->getPosition();
      playWorldSound(SFX_HUNT, bestTargetPosition);
      pulse.setClock(1.0f);
    }
  }
}

static void		updateDaylight(double offset, SceneRenderer& renderer)
{
  static const double SecondsInDay = 86400.0;

  // update sun, moon & sky
  renderer.setTimeOfDay(unixEpoch + offset / SecondsInDay);
}

#ifdef ROBOT

//
// some robot stuff
//

static std::vector<BzfRegion*>	obstacleList;  // for robots

static void		addObstacle(std::vector<BzfRegion*>& rgnList, const
 Obstacle& obstacle)
{
  float p[4][2];
  const float* c = obstacle.getPosition();
  const float tankRadius = BZDBCache::tankRadius;

  if (BZDBCache::tankHeight < c[2])
    return;

  const float a = obstacle.getRotation();
  const float w = obstacle.getWidth() + tankRadius;
  const float h = obstacle.getBreadth() + tankRadius;
  const float xx =  w * cosf(a);
  const float xy =  w * sinf(a);
  const float yx = -h * sinf(a);
  const float yy =  h * cosf(a);
  p[0][0] = c[0] - xx - yx;
  p[0][1] = c[1] - xy - yy;
  p[1][0] = c[0] + xx - yx;
  p[1][1] = c[1] + xy - yy;
  p[2][0] = c[0] + xx + yx;
  p[2][1] = c[1] + xy + yy;
  p[3][0] = c[0] - xx + yx;
  p[3][1] = c[1] - xy + yy;

  int numRegions = rgnList.size();
  for (int k = 0; k < numRegions; k++) {
    BzfRegion* region = rgnList[k];
    int side[4];
    if ((side[0] = region->classify(p[0], p[1])) == 1 ||
	(side[1] = region->classify(p[1], p[2])) == 1 ||
	(side[2] = region->classify(p[2], p[3])) == 1 ||
	(side[3] = region->classify(p[3], p[0])) == 1)
      continue;
    if (side[0] == -1 && side[1] == -1 && side[2] == -1 && side[3] ==
 -1) {
      rgnList[k] = rgnList[numRegions-1];
      rgnList[numRegions-1] = rgnList[rgnList.size()-1];
      rgnList.pop_back();
      numRegions--;
      k--;
      delete region;
      continue;
    }
    for (int j = 0; j < 4; j++) {
      if (side[j] == -1) continue;		// to inside
      // split
      const float* p1 = p[j];
      const float* p2 = p[(j+1)&3];
      BzfRegion* newRegion = region->orphanSplitRegion(p2, p1);
      if (!newRegion) continue;		// no split
      if (region != rgnList[k]) rgnList.push_back(region);
      region = newRegion;
    }
    if (region != rgnList[k]) delete region;
  }
}

static void		makeObstacleList()
{
  const float tankRadius = BZDBCache::tankRadius;
  int i;
  const int count = obstacleList.size();
  for (i = 0; i < count; i++)
    delete obstacleList[i];
  obstacleList.clear();

  // FIXME -- shouldn't hard code game area
  float gameArea[4][2];
  float worldSize = BZDBCache::worldSize;
  gameArea[0][0] = -0.5f * worldSize + tankRadius;
  gameArea[0][1] = -0.5f * worldSize + tankRadius;
  gameArea[1][0] =  0.5f * worldSize - tankRadius;
  gameArea[1][1] = -0.5f * worldSize + tankRadius;
  gameArea[2][0] =  0.5f * worldSize - tankRadius;
  gameArea[2][1] =  0.5f * worldSize - tankRadius;
  gameArea[3][0] = -0.5f * worldSize + tankRadius;
  gameArea[3][1] =  0.5f * worldSize - tankRadius;
  obstacleList.push_back(new BzfRegion(4, gameArea));

  const ObstacleList& boxes = OBSTACLEMGR.getBoxes();
  const int numBoxes = boxes.size();
  for (i = 0; i < numBoxes; i++) {
    addObstacle(obstacleList, *boxes[i]);
  }
  const ObstacleList& pyramids = OBSTACLEMGR.getPyrs();
  const int numPyramids = pyramids.size();
  for (i = 0; i < numPyramids; i++) {
    addObstacle(obstacleList, *pyramids[i]);
  }
  const ObstacleList& teleporters = OBSTACLEMGR.getTeles();
  const int numTeleporters = teleporters.size();
  for (i = 0; i < numTeleporters; i++) {
    addObstacle(obstacleList, *teleporters[i]);
  }
  const ObstacleList& meshes = OBSTACLEMGR.getMeshes();
  const int numMeshes = meshes.size();
  for (i = 0; i < numMeshes; i++) {
    addObstacle(obstacleList, *meshes[i]);
  }
  if (World::getWorld()->allowTeamFlags()) {
    const ObstacleList& bases = OBSTACLEMGR.getBases();
    const int numBases = bases.size();
    for (i = 0; i < numBases; i++) {
      const BaseBuilding* base = (const BaseBuilding*) bases[i];
      if ((base->getHeight() != 0.0f) || (base->getPosition()[2] !=
 0.0f)) {
	addObstacle(obstacleList, *base);
      }
    }
  }
}

static void		setRobotTarget(RobotPlayer* robot)
{
  Player* bestTarget = NULL;
  float bestPriority = 0.0f;
  for (int j = 0; j < curMaxPlayers; j++)
    if (player[j] && player[j]->getId() != robot->getId() &&
	player[j]->isAlive() &&
	((robot->getTeam() == RogueTeam && !World::getWorld()->allowRabbit())
	 || player[j]->getTeam() != robot->getTeam())) {

      if (player[j]->isPhantomZoned() && !robot->isPhantomZoned())
	continue;

      if (World::getWorld()->allowTeamFlags() &&
	  (robot->getTeam() == RedTeam && player[j]->getFlag() ==
 Flags::RedTeam) ||
	  (robot->getTeam() == GreenTeam && player[j]->getFlag() ==
 Flags::GreenTeam) ||
	  (robot->getTeam() == BlueTeam && player[j]->getFlag() ==
 Flags::BlueTeam) ||
	  (robot->getTeam() == PurpleTeam && player[j]->getFlag() ==
 Flags::PurpleTeam)) {
	bestTarget = player[j];
	break;
      }

      const float priority = robot->getTargetPriority(player[j]);
      if (priority > bestPriority) {
	bestTarget = player[j];
	bestPriority = priority;
      }
    }
  if (myTank->isAlive() &&
      ((robot->getTeam() == RogueTeam &&
 !World::getWorld()->allowRabbit()) ||
       myTank->getTeam() != robot->getTeam())) {
    const float priority = robot->getTargetPriority(myTank);
    if (priority > bestPriority) {
      bestTarget = myTank;
      bestPriority = priority;
    }
  }
  robot->setTarget(bestTarget);
}

static void		updateRobots(float dt)
{
  static float newTargetTimeout = 1.0f;
  static float clock = 0.0f;
  bool pickTarget = false;
  int i;

  // see if we should look for new targets
  clock += dt;
  if (clock > newTargetTimeout) {
    while (clock > newTargetTimeout) {
      clock -= newTargetTimeout;
    }
    pickTarget = true;
  }

  // start dead robots
  for (i = 0; i < numRobots; i++) {
    if (!gameOver && robots[i]
	&& !robots[i]->isAlive() && !robots[i]->isExploding() && pickTarget) {
      robotServer[i]->sendAlive();
    }
  }

  // retarget robots
  for (i = 0; i < numRobots; i++) {
    if (robots[i] && robots[i]->isAlive()
	&& (pickTarget || !robots[i]->getTarget()
	    || !robots[i]->getTarget()->isAlive())) {
      setRobotTarget(robots[i]);
    }
  }

  // do updates
  for (i = 0; i < numRobots; i++)
    if (robots[i]) {
      robots[i]->update();
    }
}


static void		checkEnvironment(RobotPlayer* tank)
{
  // skip this if i'm dead or paused
  if (!tank->isAlive() || tank->isPaused()) return;

  // see if i've been shot
    // skip this if i'm alive
  if (!BZDB.isTrue("displayConsole"))
  if (myTank->isAlive()) return;
  const ShotPath* hit = NULL;
  float minTime = Infinity;
  tank->checkHit(myTank, hit, minTime);
  int i;
  for (i = 0; i < curMaxPlayers; i++) {
    if (player[i] && player[i]->getId() != tank->getId()) {
      tank->checkHit(player[i], hit, minTime);
    }
  }

  // Check Server Shots
  tank->checkHit( World::getWorld()->getWorldWeapons(), hit, minTime);

  float waterLevel = World::getWorld()->getWaterLevel();

  if (hit) {
    // i got shot!  terminate the shot that hit me and blow up.
    // force shot to terminate locally immediately (no server round
 trip);
    // this is to ensure that we don't get shot again by the same shot
    // after dropping our shield flag.
    if (hit->isStoppedByHit())
      lookupServer(tank)->sendEndShot(hit->getPlayer(),
 hit->getShotId(), 1);

    FlagType* killerFlag = hit->getFlag();
    bool stopShot;

    if (killerFlag == Flags::Thief) {
      if (tank->getFlag() != Flags::Null) {
	serverLink->sendTransferFlag(tank->getId(), hit->getPlayer());
      }
      stopShot = true;
    }
    else {
      stopShot = gotBlowedUp(tank, GotShot, hit->getPlayer(), hit);
    }

    if (stopShot || hit->isStoppedByHit()) {
      Player* hitter = lookupPlayer(hit->getPlayer());
      if (hitter) hitter->endShot(hit->getShotId());
    }
  }
  // if not dead yet, see if i'm sitting on death
    // skip this if i'm alive
  if (!BZDB.isTrue("displayConsole"))
  if (myTank->isAlive()) return;
  else if (tank->getDeathPhysicsDriver() >= 0) {
    gotBlowedUp(tank, DeathTouch, ServerPlayer, NULL,
		tank->getDeathPhysicsDriver());
  }
  // if not dead yet, see if the robot dropped below the death level
    // skip this if i'm alive
  if (!BZDB.isTrue("displayConsole"))
  if (myTank->isAlive()) return;
  else if ((waterLevel > 0.0f) && (tank->getPosition()[2] <=
 waterLevel)) {
    gotBlowedUp(tank, WaterDeath, ServerPlayer);
  }

  // if not dead yet, see if i got run over by the steamroller
    // skip this if i'm alive
  if (!BZDB.isTrue("displayConsole"))
  if (myTank->isAlive()) return;
  else {
    bool dead = false;
    const float* myPos = tank->getPosition();
    const float myRadius = tank->getRadius();
    if (((myTank->getFlag() == Flags::Steamroller) ||
	 ((tank->getFlag() == Flags::Burrow) && myTank->isAlive() &&
	  !myTank->isPhantomZoned()))
	&& !myTank->isPaused()) {
      const float* pos = myTank->getPosition();
      if (pos[2] >= 0.0f) {
	const float radius = myRadius +
	  (BZDB.eval(StateDatabase::BZDB_SRRADIUSMULT) * myTank->getRadius());
	const float distSquared =
	  hypotf(hypotf(myPos[0] - pos[0],
			myPos[1] - pos[1]), (myPos[2] - pos[2]) * 2.0f);
	if (distSquared < radius) {
	  gotBlowedUp(tank, GotRunOver, myTank->getId());
	  dead = true;
	}
      }
    }
    for (i = 0; !dead && i < curMaxPlayers; i++) {
      if (player[i] && !player[i]->isPaused() &&
	  ((player[i]->getFlag() == Flags::Steamroller) ||
	   ((tank->getFlag() == Flags::Burrow) && player[i]->isAlive() &&
	    !player[i]->isPhantomZoned()))) {
	const float* pos = player[i]->getPosition();
	if (pos[2] < 0.0f) continue;
	const float radius = myRadius +
	  (BZDB.eval(StateDatabase::BZDB_SRRADIUSMULT) *
 player[i]->getRadius());
	const float distSquared =
	  hypotf(hypotf(myPos[0] - pos[0],
			myPos[1] - pos[1]), (myPos[2] - pos[2]) * 2.0f);
	if (distSquared < radius) {
	  gotBlowedUp(tank, GotRunOver, player[i]->getId());
	  dead = true;
	}
      }
    }
  }
}

static void		checkEnvironmentForRobots()
{
  for (int i = 0; i < numRobots; i++)
    if (robots[i])
      checkEnvironment(robots[i]);
}

static void		sendRobotUpdates()
{
  for (int i = 0; i < numRobots; i++)
    if (robots[i] && robotServer[i] &&
 robots[i]->isDeadReckoningWrong()) {
      robotServer[i]->sendPlayerUpdate(robots[i]);
    }
}

static void		addRobots()
{
  uint16_t code, len;
  char msg[MaxPacketLen];
  char callsign[CallSignLen];
  int  j;
  for (j = 0; j < numRobots; j++)
    if (robotServer[j]) {
      snprintf(callsign, CallSignLen, "%s%2.2d", myTank->getCallSign(),
 j);
      robots[j] = new RobotPlayer(robotServer[j]->getId(), callsign,
				  robotServer[j], myTank->getEmailAddress());
      robots[j]->setTeam(AutomaticTeam);
      robotServer[j]->sendEnter(ComputerPlayer, robots[j]->getTeam(),
				robots[j]->getCallSign(),
				robots[j]->getEmailAddress(), "");
    }
  for (j = 0; j < numRobots; j++) {
    // wait for response
    if (robotServer[j]
	&& robotServer[j]->read(code, len, msg, -1) < 0 || code != MsgAccept)
 {
      delete robots[j];
      delete robotServer[j];
      robots[j] = NULL;
      robotServer[j] = NULL;
    }
  }

  int k;
  // packing
  for (k = 0, j = 0; j < numRobots; j++) {
    if (k != j) {
      robotServer[k] = robotServer[j];
      robots[k]      = robots[j];
    }
    if (robotServer[j])
      k++;
  }
  numRobots = k;

  if (numRobots > 0) {
    makeObstacleList();
    RobotPlayer::setObstacleList(&obstacleList);
  }
}

#endif


static void setTankFlags()
{
  // scan through flags and, for flags on
  // tanks, tell the tank about its flag.
  const int maxFlags = world->getMaxFlags();
  for (int i = 0; i < maxFlags; i++) {
    const Flag& flag = world->getFlag(i);
    if (flag.status == FlagOnTank) {
      for (int j = 0; j < curMaxPlayers; j++) {
	if (player[j] && player[j]->getId() == flag.owner) {
	  player[j]->setFlag(flag.type);
	  break;
	}
      }
    }
  }
}


static void enteringServer(void *buf)
{
  // the server sends back the team the player was joined to
  void *tmpbuf = buf;
  uint16_t team, type;
  tmpbuf = nboUnpackUShort(tmpbuf, type);
  tmpbuf = nboUnpackUShort(tmpbuf, team);

  // if server assigns us a different team, display a message
  std::string teamMsg;
  if (myTank->getTeam() != AutomaticTeam) {
    teamMsg = TextUtils::format("%s team was unavailable, you were
 joined ",
				Team::getName(myTank->getTeam()));
    if ((TeamColor)team == ObserverTeam) {
      teamMsg += "as an Observer";
    } else {
      teamMsg += TextUtils::format("to the %s",
				   Team::getName((TeamColor)team));
    }
  } else {
    if ((TeamColor)team == ObserverTeam) {
      teamMsg = "You were joined as an observer";
    } else {
      if (team != RogueTeam)
	teamMsg = TextUtils::format("You joined the %s",
				    Team::getName((TeamColor)team));
      else
	teamMsg = TextUtils::format("You joined as a %s",
				    Team::getName((TeamColor)team));
    }
  }
  if (myTank->getTeam() != (TeamColor)team) {
    myTank->setTeam((TeamColor)team);
    hud->setAlert(1, teamMsg.c_str(), 8.0f,
		  (TeamColor)team==ObserverTeam?true:false);
    addMessage(NULL, teamMsg.c_str(), 3, true);
  }
  bool rabbitMode = World::getWorld()->allowRabbit();

  // observer colors are actually cyan, make them black
  const bool observer = (myTank->getTeam() == ObserverTeam);
  const GLfloat* borderColor;
  if (observer) {
    static const GLfloat black[4] = {0.0f, 0.0f, 0.0f, 1.0f};
    borderColor = black;
  } else {
    borderColor = Team::getRadarColor(myTank->getTeam(), rabbitMode);
  }
  controlPanel->setControlColor(borderColor);
  radar->setControlColor(borderColor);

  if ((myTank->getTeam() == ObserverTeam) || devDriving) {
    ROAM.setMode(Roaming::roamViewFP);
    //    ROAM.resetCamera();
  } else {
    ROAM.setMode(Roaming::roamViewDisabled);
  }

  setTankFlags();

  // clear now invalid token
  startupInfo.token[0] = '\0';

  // add robot tanks
#if defined(ROBOT)
  addRobots();
#endif

  // resize background and adjust time (this is needed even if we
  // don't sync with the server)
  sceneRenderer->getBackground()->resize();
  float syncTime = BZDB.eval(StateDatabase::BZDB_SYNCTIME);
  if (syncTime < 0.0f) {
    updateDaylight(epochOffset, *sceneRenderer);
  } else {
    epochOffset = (double)syncTime;
    updateDaylight(epochOffset, *sceneRenderer);
  }
  lastEpochOffset = epochOffset;

  // restore the sound
  if (savedVolume != -1) {
    setSoundVolume(savedVolume);
    savedVolume = -1;
  }

  // initialize some other stuff
  updateNumPlayers();
  updateFlag(Flags::Null);
  updateHighScores();
  hud->setHeading(myTank->getAngle());
  hud->setAltitude(myTank->getPosition()[2]);
  hud->setTimeLeft((uint32_t)~0);
  fireButton = false;
  firstLife = true;

  BZDB.setBool("displayMainFlags", true);
  BZDB.setBool("displayRadarFlags", true);
  BZDB.setBool("displayRadar", true);
  BZDB.setBool("displayConsole", true);

  entered = true;
}

static void cleanWorldCache()
{
  // setup the world cache limit
  int cacheLimit = (10 * 1024 * 1024);
  if (BZDB.isSet("worldCacheLimit")) {
    const int dbCacheLimit = BZDB.evalInt("worldCacheLimit");
    // the old limit was 100 Kbytes, too small
    if (dbCacheLimit == (100 * 1024)) {
      BZDB.setInt("worldCacheLimit", cacheLimit);
    } else {
      cacheLimit = dbCacheLimit;
    }
  } else {
    BZDB.setInt("worldCacheLimit", cacheLimit);
  }

  const std::string worldPath = getCacheDirName();

  while (true) {
    char *oldestFile = NULL;
    int oldestSize = 0;
    int totalSize = 0;

#ifdef _WIN32
    std::string pattern = worldPath + "*.bwc";
    WIN32_FIND_DATA findData;
    HANDLE h = FindFirstFile(pattern.c_str(), &findData);
    if (h != INVALID_HANDLE_VALUE) {
      FILETIME oldestTime;
      while (FindNextFile(h, &findData)) {
	if ((oldestFile == NULL) ||
	    (CompareFileTime(&oldestTime, &findData.ftLastAccessTime) > 0)) {
	  if (oldestFile) {
	    free(oldestFile);
	  }
	  oldestFile = strdup(findData.cFileName);
	  oldestSize = findData.nFileSizeLow;
	  oldestTime = findData.ftLastAccessTime;
	}
	totalSize += findData.nFileSizeLow;
      }
      FindClose(h);
    }
#else
    DIR *directory = opendir(worldPath.c_str());
    if (directory) {
      struct dirent* contents;
      struct stat statbuf;
      time_t oldestTime = 0;
      while ((contents = readdir(directory))) {
	const std::string filename = contents->d_name;
	const std::string fullname = worldPath + filename;
	stat(fullname.c_str(), &statbuf);
	if (S_ISREG(statbuf.st_mode) && (filename.size() > 4) &&
	    (filename.substr(filename.size() - 4) == ".bwc")) {
	  if ((oldestFile == NULL) || (statbuf.st_atime < oldestTime)) {
	    if (oldestFile) {
	      free(oldestFile);
	    }
	    oldestFile = strdup(contents->d_name);
	    oldestSize = statbuf.st_size;
	    oldestTime = statbuf.st_atime;
	  }
	  totalSize += statbuf.st_size;
	}
      }
      closedir(directory);
    }
#endif

    // any valid cache files?
    if (oldestFile == NULL) {
      return;
    }

    // is the cache small enough?
    if (totalSize < cacheLimit) {
      if (oldestFile != NULL) {
	free(oldestFile);
	oldestFile = NULL;
      }
      return;
    }

    // remove the oldest file
    logDebugMessage(1,"cleanWorldCache: removed %s\n", oldestFile);
    remove((worldPath + oldestFile).c_str());
    free(oldestFile);
    totalSize -= oldestSize;
  }
}


static void markOld(std::string &fileName)
{
#ifdef _WIN32
  FILETIME ft;
  HANDLE h = CreateFile(fileName.c_str(),
			FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA, 0, NULL,
			OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (h != INVALID_HANDLE_VALUE) {
    SYSTEMTIME st;
    memset(&st, 0, sizeof(st));
    st.wYear = 1900;
    st.wMonth = 1;
    st.wDay = 1;
    SystemTimeToFileTime(&st, &ft);
    SetFileTime(h, &ft, &ft, &ft);
    GetLastError();
    CloseHandle(h);
  }
#else
  struct utimbuf times;
  times.actime = 0;
  times.modtime = 0;
  utime(fileName.c_str(), &times);
#endif
}


static void sendFlagNegotiation()
{
  char msg[MaxPacketLen];
  FlagTypeMap::iterator i;
  char *buf = msg;

  /* Send MsgNegotiateFlags to the server with
   * the abbreviations for all the flags we support.
   */
  for (i = FlagType::getFlagMap().begin();
       i != FlagType::getFlagMap().end(); i++) {
    buf = (char*) i->second->pack(buf);
  }
  serverLink->send(MsgNegotiateFlags, buf - msg, msg);
}


#if defined(FIXME) && defined(ROBOT)
static void saveRobotInfo(Playerid id, void *msg)
{
  for (int i = 0; i < numRobots; i++)
    if (robots[i]) {
      void *tmpbuf = msg;
      uint16_t team, type, wins, losses, tks;
      char callsign[CallSignLen];
      char email[EmailLen];
      tmpbuf = nboUnpackUShort(tmpbuf, type);
      tmpbuf = nboUnpackUShort(tmpbuf, team);
      tmpbuf = nboUnpackUShort(tmpbuf, wins);
      tmpbuf = nboUnpackUShort(tmpbuf, losses);
      tmpbuf = nboUnpackUShort(tmpbuf, tks);
      tmpbuf = nboUnpackString(tmpbuf, callsign, CallSignLen);
      tmpbuf = nboUnpackString(tmpbuf, email, EmailLen);
      std::cerr << "id " << id.port << ':' <<
	id.number << ':' <<
	callsign << ' ' <<
	robots[i]->getId().port << ':' <<
	robots[i]->getId().number << ':' <<
	robots[i]->getCallsign() << std::endl;
      if (strncmp(robots[i]->getCallSign(), callsign, CallSignLen)) {
	// check for real robot id
	char buffer[100];
	snprintf(buffer, 100, "id test %p %p %p %8.8x %8.8x\n",
		 robots[i], tmpbuf, msg, *(int *)tmpbuf, *((int *)tmpbuf + 1));
	std::cerr << buffer;
	if (tmpbuf < (char *)msg + len) {
	  PlayerId id;
	  tmpbuf = nboUnpackUByte(tmpbuf, id);
	  robots[i]->id.serverHost = id.serverHost;
	  robots[i]->id.port = id.port;
	  robots[i]->id.number = id.number;
	  robots[i]->server->send(MsgIdAck, 0, NULL);
	}
      }
    }
}
#endif

static void resetServerVar(const std::string& name, void*)
{
  // reset server-side variables
  if (BZDB.getPermission(name) == StateDatabase::Locked) {
    const std::string defval = BZDB.getDefault(name);
    BZDB.set(name, defval);
  }
}

void		leaveGame()
{
  entered = false;
  joiningGame = false;

  // no more radar
  //  radar->setWorld(NULL);

  //  controlPanel->setRadarRenderer(NULL);
  /*
    delete radar;
    radar = NULL;
  */

#if defined(ROBOT)
  // shut down robot connections
  int i;
  for (i = 0; i < numRobots; i++) {
    if (robots[i] && robotServer[i])
      robotServer[i]->send(MsgExit, 0, NULL);
    delete robots[i];
    delete robotServer[i];
    robots[i] = NULL;
    robotServer[i] = NULL;
  }
  numRobots = 0;

  const int count = obstacleList.size();
  for (i = 0; i < count; i++)
    delete obstacleList[i];
  obstacleList.clear();
#endif

  // my tank goes away
  const bool sayGoodbye = (myTank != NULL);
  LocalPlayer::setMyTank(NULL);
  delete myTank;
  myTank = NULL;

  // reset the daylight time
  const bool syncTime = (BZDB.eval(StateDatabase::BZDB_SYNCTIME) >=
 0.0f);
  const bool fixedTime = BZDB.isSet("fixedTime");
  if (syncTime) {
    // return to the desired user time
    epochOffset = userTimeEpochOffset;
  }
  else if (fixedTime) {
    // save the current user time
    userTimeEpochOffset = epochOffset;
  }
  else {
    // revert back to when the client was started?
    epochOffset = userTimeEpochOffset;
  }
  updateDaylight(epochOffset, *sceneRenderer);
  lastEpochOffset = epochOffset;
  BZDB.set(StateDatabase::BZDB_SYNCTIME,
	   BZDB.getDefault(StateDatabase::BZDB_SYNCTIME));

  // flush downloaded textures (before the BzMaterials are nuked)
  Downloads::removeTextures();

  // delete world
  World::setWorld(NULL);
  delete world;
  world = NULL;
  teams = NULL;
  curMaxPlayers = 0;
  numFlags = 0;
  player = NULL;

  // update UI
  hud->setPlaying(false);
  hud->setCracks(false);
  hud->setPlayerHasHighScore(false);
  hud->setTeamHasHighScore(false);
  hud->setHeading(0.0f);
  hud->setAltitude(0.0f);
  hud->setAltitudeTape(false);

  // shut down server connection
  if (sayGoodbye) serverLink->send(MsgExit, 0, NULL);
  ServerLink::setServer(NULL);
  delete serverLink;
  serverLink = NULL;

  // reset viewpoint
  float eyePoint[3], targetPoint[3];
  eyePoint[0] = 0.0f;
  eyePoint[1] = 0.0f;
  eyePoint[2] = 0.0f + BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT);
  targetPoint[0] = eyePoint[0] - 1.0f;
  targetPoint[1] = eyePoint[1] + 0.0f;
  targetPoint[2] = eyePoint[2] + 0.0f;
  sceneRenderer->getViewFrustum().setProjection((float)(60.0 * M_PI /
 180.0),
						NearPlaneNormal,
						FarPlaneDefault,
						FarDeepPlaneDefault,
						mainWindow->getWidth(),
						mainWindow->getHeight(),
						mainWindow->getViewHeight());
  sceneRenderer->getViewFrustum().setView(eyePoint, targetPoint);

  // reset some flags
  gameOver = false;
  serverError = false;
  serverDied = false;

  // delete scene database (after the world has been destroyed)
  sceneRenderer->setSceneDatabase(NULL);

  // reset the BZDB variables
  BZDB.iterate(resetServerVar, NULL);

  return;
}


static void joinInternetGame(const struct in_addr *inAddress)
{
  // get server address
  Address serverAddress(*inAddress);
  if (serverAddress.isAny()) {
    HUDDialogStack::get()->setFailedMessage("Server not found");
    return;
  }

  // check for a local server block
  ServerAccessList.reload();
  std::vector<std::string> nameAndIp;
  nameAndIp.push_back(startupInfo.serverName);
  nameAndIp.push_back(serverAddress.getDotNotation());
  if (!ServerAccessList.authorized(nameAndIp)) {
    HUDDialogStack::get()->setFailedMessage("Server Access Denied
 Locally");
    std::string msg = ColorStrings[WhiteColor];
    msg += "NOTE: ";
    msg += ColorStrings[GreyColor];
    msg += "server access is controlled by ";
    msg += ColorStrings[YellowColor];
    msg += ServerAccessList.getFileName();
    addMessage(NULL, msg);
    return;
  }

  // open server
  ServerLink* _serverLink = new ServerLink(serverAddress,
					   startupInfo.serverPort);

#if defined(ROBOT)
  int i, j;
  for (i = 0, j = 0; i < numRobotTanks; i++) {
    robotServer[j] = new ServerLink(serverAddress,
 startupInfo.serverPort);
    if (!robotServer[j] || robotServer[j]->getState() !=
 ServerLink::Okay) {
      delete robotServer[j];
      continue;
    }
    j++;
  }
  numRobots = j;
#endif

  serverLink = _serverLink;

  // assume everything's okay for now
  serverDied = false;
  serverError = false;

  if (!serverLink) {
    HUDDialogStack::get()->setFailedMessage("Memory error");
    return;
  }

  // printError("Join Game");
  // check server
  if (serverLink->getState() != ServerLink::Okay) {
    switch (serverLink->getState()) {
      case ServerLink::BadVersion: {
	static char versionError[] = "Incompatible server version XXXXXXXX";
	strncpy(versionError + strlen(versionError) - 8,
		serverLink->getVersion(), 8);
	HUDDialogStack::get()->setFailedMessage(versionError);
	break;
      }

	// you got banned
      case ServerLink::Refused:{
	const std::string& rejmsg = serverLink->getRejectionMessage();

	// add to the HUD
	std::string msg = ColorStrings[RedColor];
	msg += "You have been banned from this server";
	HUDDialogStack::get()->setFailedMessage(msg.c_str());

	// add to the console
	msg = ColorStrings[RedColor];
	msg += "You have been banned from this server:";
	addMessage(NULL, msg);
	msg = ColorStrings[YellowColor];
	msg += rejmsg;
	addMessage(NULL, msg);

	break;
      }

      case ServerLink::Rejected:
	// the server is probably full or the game is over.  if not then
	// the server is having network problems.
	HUDDialogStack::get()->setFailedMessage
	  ("Game is full or over.  Try again later.");
	break;

      case ServerLink::SocketError:
	HUDDialogStack::get()->setFailedMessage("Error connecting to
 server.");
	break;

      case ServerLink::CrippledVersion:
	// can't connect to (otherwise compatible) non-crippled server
	HUDDialogStack::get()->setFailedMessage
	  ("Cannot connect to full version server.");
	break;

      default:
	HUDDialogStack::get()->setFailedMessage
	  (TextUtils::format
	   ("Internal error connecting to server (error code %d).",
	    serverLink->getState()).c_str());
	break;
    }
    return;
  }

#ifdef HAVE_KRB5
  // Sending our credential to the server
  ClientAuthentication::sendCredential(*serverLink);
#endif

  // use parallel UDP if desired and using server relay
  if (startupInfo.useUDPconnection)
    serverLink->sendUDPlinkRequest();
  else
    printError("No UDP connection, see Options to enable.");

  HUDDialogStack::get()->setFailedMessage("Connection Established...");

  sendFlagNegotiation();
  joiningGame = true;
  scoreboard->huntReset();
  GameTime::reset();
}


static void addVarToAutoComplete(const std::string& name, void*
 /*userData*/)
{
  if ((name.size() <= 0) || (name[0] != '_')) {
    return; // we're skipping "poll"
  }
  if (BZDB.getPermission(name) == StateDatabase::Server) {
    completer.registerWord(name);
  }
  return;
}

static void joinInternetGame2()
{
  justJoined = true;

  HUDDialogStack::get()->setFailedMessage("Entering game...");

  ServerLink::setServer(serverLink);
  World::setWorld(world);

  // prep teams
  teams = world->getTeams();

  // prep players
  curMaxPlayers = 0;
  player = world->getPlayers();

  // reset the autocompleter
  completer.setDefaults();
  BZDB.iterate(addVarToAutoComplete, NULL);

  // prep flags
  numFlags = world->getMaxFlags();

  // make scene database
  setSceneDatabase();
  mainWindow->getWindow()->yieldCurrent();

  // make radar
  //  radar = new RadarRenderer(*sceneRenderer, *world);
  //  mainWindow->getWindow()->yieldCurrent();
  radar->setWorld(world);
  controlPanel->setRadarRenderer(radar);
  controlPanel->resize();

  // make local player
  myTank = new LocalPlayer(serverLink->getId(), startupInfo.callsign,
			   startupInfo.email);
  myTank->setTeam(startupInfo.team);
  LocalPlayer::setMyTank(myTank);

  if (world->allowRabbit() && myTank->getTeam() != ObserverTeam)
    myTank->setTeam(RogueTeam);

  // tell server we want to join
  serverLink->sendEnter(TankPlayer, myTank->getTeam(),
			myTank->getCallSign(),
			myTank->getEmailAddress(),
			startupInfo.token);
  startupInfo.token[0] = '\0';

  // hopefully it worked!  pop all the menus.
  HUDDialogStack* stack = HUDDialogStack::get();
  while (stack->isActive())
    stack->pop();
  joiningGame = false;
}


static void		renderDialog()
{
  if (HUDDialogStack::get()->isActive()) {
    const int width = mainWindow->getWidth();
    const int height = mainWindow->getHeight();
    const int ox = mainWindow->getOriginX();
    const int oy = mainWindow->getOriginY();
    glScissor(ox, oy, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    OpenGLGState::resetState();
    HUDDialogStack::get()->render();
    glPopMatrix();
  }
}

static void checkDirtyControlPanel(ControlPanel *cp)
{
  if (cp) {
    if (HUDDialogStack::get()->isActive()) {
      cp->invalidate();
    }
  }
  return;
}

static int		getZoomFactor()
{
  if (!BZDB.isSet("displayZoom")) return 1;
  const int zoom = atoi(BZDB.get("displayZoom").c_str());
  if (zoom < 1) return 1;
  if (zoom > 8) return 8;
  return zoom;
}


static void drawUI()
{
  // setup the triangle counts  (FIXME: hackish)
  if (showFPS && showDrawTime) {
    hud->setFrameTriangleCount(sceneRenderer->getFrameTriangleCount());
    // NOTE:  the radar triangle count is actually from the previous
 frame
    if (radar) {
      hud->setFrameRadarTriangleCount(radar->getFrameTriangleCount());
    } else {
      hud->setFrameRadarTriangleCount(0);
    }
  } else {
    hud->setFrameTriangleCount(0);
    hud->setFrameRadarTriangleCount(0);
  }

  // update the HUD (player list, alerts)
  if (hud) {
    hud->render(*sceneRenderer);
  }

  // draw the control panel
  if (controlPanel) {
    controlPanel->render(*sceneRenderer);
  }

  // draw the radar
  if (radar) {
    const bool showBlankRadar = !myTank; 
    const bool observer = myTank && (myTank->getTeam() ==
 ObserverTeam);
    radar->render(*sceneRenderer, showBlankRadar, observer);
  }

  // update the HUD (menus)
  renderDialog();

  return;
}


//
// stuff to draw a frame
//

static bool trackPlayerShot(Player* target,
			    float* eyePoint, float* targetPoint)
{
  // follow the first shot
  if (BZDB.isTrue("trackShots")) {
    const int maxShots = target->getMaxShots();
    const ShotPath* sp = NULL;
    // look for the oldest active shot
    float remaining = +MAXFLOAT;
    for (int s = 0; s < maxShots; s++) {
      const ShotPath* spTmp = target->getShot(s);
      if (spTmp != NULL) {
	const float t = float(spTmp->getReloadTime() -
			      (spTmp->getCurrentTime() - spTmp->getStartTime()));
	if ((t > 0.0f) && (t < remaining)) {
	  sp = spTmp;
	  remaining = t;
	}
      }
    }
    if (sp != NULL) {
      const float* pos = sp->getPosition();
      const float* vel = sp->getVelocity();
      const float speed = sqrtf(vel[0]*vel[0] + vel[1]*vel[1] +
 vel[2]*vel[2]);
      if (speed > 0.0f) {
	const float ilen = 1.0f / speed;
	const float dir[3] = {ilen * vel[0], ilen * vel[1], ilen * vel[2]};
	float topDir[3] = {1.0f, 0.0f, 0.0f};
	const float hlen = sqrtf(dir[0]*dir[0] + dir[1]*dir[1]);
	if (hlen > 0.0f) {
	  topDir[2] = hlen;
	  const float hfactor = -fabsf(dir[2] / hlen);
	  topDir[0] = hfactor * dir[0];
	  topDir[1] = hfactor * dir[1];
	}
	const float offset = -10.0f;
	const float tOffset = +2.0f;
	eyePoint[0] = pos[0] + (offset * dir[0]) + (tOffset * topDir[0]);
	eyePoint[1] = pos[1] + (offset * dir[1]) + (tOffset * topDir[1]);
	eyePoint[2] = pos[2] + (offset * dir[2]) + (tOffset * topDir[2]);
	targetPoint[0] = eyePoint[0] + dir[0];
	targetPoint[1] = eyePoint[1] + dir[1];
	targetPoint[2] = eyePoint[2] + dir[2];
	return true;
      }
    }
  }
  return false;
}

static void setupNearPlane()
{
  NearPlane = NearPlaneNormal;

  const bool showTreads = BZDB.isTrue("showTreads");
  if (!showTreads || !myTank) {
    return;
  }

  const Player* tank = myTank;
  if (ROAM.isRoaming()) {
    if (ROAM.getMode() != Roaming::roamViewFP) {
      return;
    }
    if (!devDriving) {
      tank = ROAM.getTargetTank();
    }
  }
  if (tank == NULL) {
    return;
  }

  const float halfLength = 0.5f * BZDBCache::tankLength;
  const float length = tank->getDimensions()[1];
  if (fabsf(length - halfLength) > 0.1f) {
    NearPlane = NearPlaneClose;
  }

  return;
}


static void setupFarPlane()
{
  FarPlane = FarPlaneScale * BZDBCache::worldSize;
  FarPlaneCull = false;
  FarDeepPlane = FarPlane * FarDeepPlaneScale;

  const bool mapFog = (BZDB.get(StateDatabase::BZDB_FOGMODE) != "none")
 &&
    BZDB.isTrue("fogEffect");

  float farDist = FarPlane;

  if (mapFog &&
      (BZDB.get("_cullDist") == "fog") && !BZDB.isTrue("_fogNoSky")) {
    const float fogMargin = 1.01f;
    const std::string& fogMode = BZDB.get("_fogMode");
    if (fogMode == "linear") {
      farDist = fogMargin * BZDB.eval("_fogEnd");
    } else {
      const float density = BZDB.eval("_fogDensity");
      if (density > 0.0f) {
	const float fogFactor = 0.01f;
	if (fogMode == "exp2") {
	  farDist = fogMargin * sqrtf(-logf(fogFactor)) / density;
	} else { // default to 'exp'
	  farDist = fogMargin * -logf(fogFactor) / density;
	}
      } else {
	// default far plane
      }
    }
  } else {
    const float dist = BZDB.eval("_cullDist");
    if (!isnan(dist) && (dist > 0.0f)) {
      farDist = dist;
    } else {
      // default far plane
    }
  }

  if (farDist < FarPlane) {
    FarPlane = farDist;
    FarPlaneCull = true;
  }

  return;
}


void drawFrame(const float dt)
{
  // get view type (constant for entire game)
  static SceneRenderer::ViewType viewType =
 sceneRenderer->getViewType();
  // get media object
  static BzfMedia* media = PlatformFactory::getMedia();

  static const float	defaultPos[3] = { 0.0f, 0.0f, 0.0f };
  static const float	defaultDir[3] = { 1.0f, 0.0f, 0.0f };
  static int frameCount = 0;
  static float cumTime = 0.0f;

  const float* myTankPos;
  const float* myTankDir;
  GLfloat fov;
  GLfloat eyePoint[3];
  GLfloat targetPoint[3];
  int i;

  checkDirtyControlPanel(controlPanel);

  if (!unmapped) {
    // compute fps
    frameCount++;
    cumTime += float(dt);
    if (cumTime >= 2.0) {
      if (showFPS) hud->setFPS(float(frameCount) / cumTime);
      cumTime = 0.00000001f;
      frameCount = 0;
    }

    // drift clouds
    sceneRenderer->getBackground()->addCloudDrift(1.0f * dt, 0.731f *
 dt);

    // get tank camera info
    float muzzleHeight;
    if (!myTank) {
      myTankPos = defaultPos;
      myTankDir = defaultDir;
      muzzleHeight = BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT);
      fov = 60.0f;
    } else {
      myTankPos = myTank->getPosition();
      myTankDir = myTank->getForward();
      muzzleHeight = myTank->getMuzzleHeight();

      if (myTank->getFlag() == Flags::WideAngle) {
	fov = 120.0f;
      } else {
	fov = BZDB.eval("displayFOV");
      }
      if (viewType == SceneRenderer::ThreeChannel) {
	fov *= 0.75f;
      }
    }
    fov *= (float)(M_PI / 180.0);

    // set projection and view
    eyePoint[0] = myTankPos[0];
    eyePoint[1] = myTankPos[1];
    eyePoint[2] = myTankPos[2] + muzzleHeight;
    targetPoint[0] = eyePoint[0] + myTankDir[0];
    targetPoint[1] = eyePoint[1] + myTankDir[1];
    targetPoint[2] = eyePoint[2] + myTankDir[2];

    if (devDriving || ROAM.isRoaming()) {
      hud->setAltitude(-1.0f);
      float roamViewAngle;
      const Roaming::RoamingCamera* roam = ROAM.getCamera();
      if (!(ROAM.getMode() == Roaming::roamViewFree) &&
	  (ROAM.getTargetTank() || (devDriving && myTank))) {
	Player *target;
	if (!devDriving) {
	  target = ROAM.getTargetTank();
	} else {
	  target = myTank;
	}
	const float *targetTankDir = target->getForward();
	// fixed camera tracking target
	if (ROAM.getMode() == Roaming::roamViewTrack) {
	  eyePoint[0] = roam->pos[0];
	  eyePoint[1] = roam->pos[1];
	  eyePoint[2] = roam->pos[2];
	  targetPoint[0] = target->getPosition()[0];
	  targetPoint[1] = target->getPosition()[1];
	  targetPoint[2] = target->getPosition()[2] +
	    target->getMuzzleHeight();
	}
	// camera following target
	else if (ROAM.getMode() == Roaming::roamViewFollow) {
	  if (!trackPlayerShot(target, eyePoint, targetPoint)) {
	    eyePoint[0] = target->getPosition()[0] - targetTankDir[0] * 40;
	    eyePoint[1] = target->getPosition()[1] - targetTankDir[1] * 40;
	    eyePoint[2] = target->getPosition()[2] + muzzleHeight * 6;
	    targetPoint[0] = target->getPosition()[0];
	    targetPoint[1] = target->getPosition()[1];
	    targetPoint[2] = target->getPosition()[2];
	  }
	}
	// target's view
	else if (ROAM.getMode() == Roaming::roamViewFP) {
	  if (!trackPlayerShot(target, eyePoint, targetPoint)) {
	    eyePoint[0] = target->getPosition()[0];
	    eyePoint[1] = target->getPosition()[1];
	    eyePoint[2] = target->getPosition()[2] +
 target->getMuzzleHeight();
	    targetPoint[0] = eyePoint[0] + targetTankDir[0];
	    targetPoint[1] = eyePoint[1] + targetTankDir[1];
	    targetPoint[2] = eyePoint[2] + targetTankDir[2];
	    hud->setAltitude(target->getPosition()[2]);
	  }
	}
	// track team flag
	else if (ROAM.getMode() == Roaming::roamViewFlag) {
	  Flag* targetFlag = ROAM.getTargetFlag();
	  eyePoint[0] = roam->pos[0];
	  eyePoint[1] = roam->pos[1];
	  eyePoint[2] = roam->pos[2];
	  targetPoint[0] = targetFlag->position[0];
	  targetPoint[1] = targetFlag->position[1];
	  targetPoint[2] = targetFlag->position[2];
	  if (targetFlag->status != FlagOnTank) {
	    targetPoint[2] += muzzleHeight;
	  } else {
	    targetPoint[2] -= (BZDBCache::tankHeight -
			       BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT));
	  }
	}
	roamViewAngle = (float) (atan2(targetPoint[1]-eyePoint[1],
				       targetPoint[0]-eyePoint[0]) * 180.0f / M_PI);
      }
      // free Roaming
      else {
	float dir[3];
	dir[0] = cosf((float)(roam->phi * M_PI / 180.0)) *
 cosf((float)(roam->theta * M_PI / 180.0));
	dir[1] = cosf((float)(roam->phi * M_PI / 180.0)) *
 sinf((float)(roam->theta * M_PI / 180.0));
	dir[2] = sinf((float)(roam->phi * M_PI / 180.0));
	eyePoint[0] = roam->pos[0];
	eyePoint[1] = roam->pos[1];
	eyePoint[2] = roam->pos[2];
	targetPoint[0] = eyePoint[0] + dir[0];
	targetPoint[1] = eyePoint[1] + dir[1];
	targetPoint[2] = eyePoint[2] + dir[2];
	roamViewAngle = roam->theta;
      }
      if (!devDriving) {
	float virtPos[] = {eyePoint[0], eyePoint[1], 0};
	if (myTank) {
	  myTank->move(virtPos, (float)(roamViewAngle * M_PI / 180.0));
	}
      }
      fov = (float)(roam->zoom * M_PI / 180.0);
      moveSoundReceiver(eyePoint[0], eyePoint[1], eyePoint[2], 0.0,
 false);
    }

    // only use a close plane for drawing in the
    // cockpit, and even then only for odd sized tanks
    setupNearPlane();

    // based on fog and _cullDist
    setupFarPlane();

    ViewFrustum& viewFrustum = sceneRenderer->getViewFrustum();

    viewFrustum.setProjection(fov, NearPlane, FarPlane, FarDeepPlane,
			      mainWindow->getWidth(),
			      mainWindow->getHeight(),
			      mainWindow->getViewHeight());
    viewFrustum.setFarPlaneCull(FarPlaneCull);

    viewFrustum.setView(eyePoint, targetPoint);

    // add dynamic nodes
    SceneDatabase* scene = sceneRenderer->getSceneDatabase();
    if (scene && myTank) {

      const bool seerView = (myTank->getFlag() == Flags::Seer);
      const bool showTreads = BZDB.isTrue("showTreads");

      // add my tank if required
      const bool inCockpit = (!devDriving || (ROAM.getMode() ==
 Roaming::roamViewFP));
      const bool showMyTreads = showTreads ||
	(devDriving && (ROAM.getMode() != Roaming::roamViewFP));
      myTank->addToScene(scene, myTank->getTeam(),
			 inCockpit, seerView,
			 showMyTreads, showMyTreads /*showIDL*/);

      // add my shells
      myTank->addShots(scene, false);

      // add server shells
      if (world) {
	world->getWorldWeapons()->addShots(scene, false);
      }

      // add antidote flag
      myTank->addAntidote(scene);

      // add flags
      world->addFlags(scene, seerView);


      // add other tanks and shells
      for (i = 0; i < curMaxPlayers; i++) {
	if (player[i]) {
	  const bool colorblind = (myTank->getFlag() ==
 Flags::Colorblindness);
	  player[i]->addShots(scene, colorblind);

	  TeamColor effectiveTeam = RogueTeam;
	  if (!colorblind){
	    if ((player[i]->getFlag() == Flags::Masquerade)
		&& (myTank->getFlag() != Flags::Seer)
		&& (myTank->getTeam() != ObserverTeam)) {
	      effectiveTeam = myTank->getTeam();
	    }
	    else {
	      effectiveTeam = player[i]->getTeam();
	    }
	  }

	  const bool inCockpt  = ROAM.isRoaming() && !devDriving &&
	    (ROAM.getMode() == Roaming::roamViewFP) &&
	    ROAM.getTargetTank() &&
	    (ROAM.getTargetTank()->getId() == i);
	  const bool showPlayer = !inCockpt || showTreads;

	  // add player tank if required
	  player[i]->addToScene(scene, effectiveTeam,
				inCockpt, seerView,
				showPlayer, showPlayer /*showIDL*/);
	}
      }

      // add explosions
      addExplosions(scene);

      // if inside a building, add some eighth dimension scene nodes.
      const std::vector<const Obstacle*>& list =
 myTank->getInsideBuildings();
      for (unsigned int n = 0; n < list.size(); n++) {
	const Obstacle* obs = list[n];
	const int nodeCount = obs->getInsideSceneNodeCount();
	SceneNode** nodeList = obs->getInsideSceneNodeList();
	for (int o = 0; o < nodeCount; o++) {
	  scene->addDynamicNode(nodeList[o]);
	}
      }
    }

    // turn blanking and inversion on/off as appropriate
    // turn on scene dimming when showing menu, when we're dead
    // and no longer exploding, or when we are in a building.
    bool insideDim = false;
    if (myTank) {
      const float hnp = 0.5f * NearPlane; // half near plane distance
      const float* eye = viewFrustum.getEye();
      const float* dir = viewFrustum.getDirection();
      float clipPos[3];
      clipPos[0] = eye[0] + (dir[0] * hnp);
      clipPos[1] = eye[1] + (dir[1] * hnp);
      clipPos[2] = eye[2];
      const Obstacle* obs;
      obs = world->inBuilding(clipPos, myTank->getAngle(), hnp, 0.0f,
 0.0f);
      if (obs != NULL) {
	insideDim = true;
      }
    }
    sceneRenderer->setDim(HUDDialogStack::get()->isActive() ||
 insideDim ||
			  (myTank && !ROAM.isRoaming() && !devDriving) &&
			  !myTank->isAlive() && !myTank->isExploding());

    // turn on panel dimming when showing the menu (both radar and
 chat)
    if (HUDDialogStack::get()->isActive()) {
      if (controlPanel) {
	controlPanel->setDimming(0.8f);
      }
      if (radar) {
	radar->setDimming(0.8f);
      }
    } else {
      if (controlPanel) {
	controlPanel->setDimming(0.0f);
      }
      if (radar) {
	radar->setDimming(0.0f);
      }
    }

    // set hud state
    hud->setDim(HUDDialogStack::get()->isActive());
    hud->setPlaying(myTank && (myTank->isAlive() &&
 !myTank->isPaused()));
    hud->setRoaming(ROAM.isRoaming());
    hud->setCracks(myTank && !firstLife && !justJoined &&
 !myTank->isAlive());

    // get frame start time
    if (showDrawTime) {
#if defined(DEBUG_RENDERING)
      // get an accurate measure of frame time (at expense of frame
 rate)
      if (BZDB.isTrue("glFinish")) {
	glFinish();
      }
#endif
      media->stopwatch(true);
    }

    // so observers can have enhanced radar
    if (ROAM.isRoaming() && myTank && !devDriving) {
      if (ROAM.getMode() == Roaming::roamViewFP &&
 ROAM.getTargetTank())
	myTank->setZpos(ROAM.getTargetTank()->getPosition()[2]);
      else
	myTank->setZpos(ROAM.getCamera()->pos[2]);
    }

    // draw frame
    if (viewType == SceneRenderer::ThreeChannel) {
      // draw center channel
      sceneRenderer->render(false);
      drawUI();

      // set up for drawing left channel
      mainWindow->setQuadrant(MainWindow::LowerLeft);
      // FIXME -- this assumes up is along +z
      const float cFOV = cosf(fov);
      const float sFOV = sinf(fov);
      targetPoint[0] = eyePoint[0] + cFOV*myTankDir[0] -
 sFOV*myTankDir[1];
      targetPoint[1] = eyePoint[1] + cFOV*myTankDir[1] +
 sFOV*myTankDir[0];
      targetPoint[2] = eyePoint[2] + myTankDir[2];
      viewFrustum.setView(eyePoint, targetPoint);

      // draw left channel
      sceneRenderer->render(false, true, true);

      // set up for drawing right channel
      mainWindow->setQuadrant(MainWindow::LowerRight);
      // FIXME -- this assumes up is along +z
      targetPoint[0] = eyePoint[0] + cFOV*myTankDir[0] +
 sFOV*myTankDir[1];
      targetPoint[1] = eyePoint[1] + cFOV*myTankDir[1] -
 sFOV*myTankDir[0];
      targetPoint[2] = eyePoint[2] + myTankDir[2];
      viewFrustum.setView(eyePoint, targetPoint);

      // draw right channel
      sceneRenderer->render(true, true, true);

#if defined(DEBUG_RENDERING)
      // set up for drawing rear channel
      mainWindow->setQuadrant(MainWindow::UpperLeft);
      // FIXME -- this assumes up is along +z
      targetPoint[0] = eyePoint[0] - myTankDir[0];
      targetPoint[1] = eyePoint[1] - myTankDir[1];
      targetPoint[2] = eyePoint[2] + myTankDir[2];
      viewFrustum.setView(eyePoint, targetPoint);

      // draw rear channel
      sceneRenderer->render(true, true, true);
#endif
      // back to center channel
      mainWindow->setQuadrant(MainWindow::UpperRight);
    } else if (viewType == SceneRenderer::Stacked) {
      float EyeDisplacement = 0.25f * BZDBCache::tankWidth;
      float FocalPlane = BZDB.eval(StateDatabase::BZDB_BOXBASE);
      if (BZDB.isSet("eyesep"))
	EyeDisplacement = BZDB.eval("eyesep");
      if (BZDB.isSet("focal"))
	FocalPlane = BZDB.eval("focal");

      // setup view for left eye
      viewFrustum.setOffset(EyeDisplacement, FocalPlane);

      // draw left eye's view
      sceneRenderer->render(false);
      drawUI();

      // set up view for right eye
      mainWindow->setQuadrant(MainWindow::UpperHalf);
      viewFrustum.setOffset(-EyeDisplacement, FocalPlane);

      // draw right eye's view
      sceneRenderer->render(true, true);
      drawUI();

      // draw common stuff

      // back to left channel
      mainWindow->setQuadrant(MainWindow::LowerHalf);
    } else if (viewType == SceneRenderer::Stereo) {
      float EyeDisplacement = 0.25f * BZDBCache::tankWidth;
      float FocalPlane = BZDB.eval(StateDatabase::BZDB_BOXBASE);
      if (BZDB.isSet("eyesep"))
	EyeDisplacement = BZDB.eval("eyesep");
      if (BZDB.isSet("focal"))
	FocalPlane = BZDB.eval("focal");

      // setup view for left eye
#ifdef USE_GL_STEREO
      glDrawBuffer(GL_BACK_LEFT);
#endif
      viewFrustum.setOffset(EyeDisplacement, FocalPlane);

      // draw left eye's view
      sceneRenderer->render(false);
#ifndef USE_GL_STEREO
      drawUI();
#endif

      // set up view for right eye
#ifdef USE_GL_STEREO
      glDrawBuffer(GL_BACK_RIGHT);
#else
      mainWindow->setQuadrant(MainWindow::UpperLeft);
#endif
      viewFrustum.setOffset(-EyeDisplacement, FocalPlane);

      // draw right eye's view
      sceneRenderer->render(true, true);
#ifndef USE_GL_STEREO
      drawUI();
#endif

      // draw common stuff
#ifdef USE_GL_STEREO
      glDrawBuffer(GL_BACK);
      drawUI();
#endif

#ifndef USE_GL_STEREO
      // back to left channel
      mainWindow->setQuadrant(MainWindow::UpperRight);
#endif
    } else if (viewType == SceneRenderer::Anaglyph) {
      float EyeDisplacement = 0.25f * BZDBCache::tankWidth;
      float FocalPlane = BZDB.eval(StateDatabase::BZDB_BOXBASE);
      if (BZDB.isSet("eyesep"))
	EyeDisplacement = BZDB.eval("eyesep");
      if (BZDB.isSet("focal"))
	FocalPlane = BZDB.eval("focal");

      // setup view for left eye
      glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
      viewFrustum.setOffset(EyeDisplacement, FocalPlane);

      // draw left eye's view
      sceneRenderer->render(false);
      drawUI();

      // set up view for right eye
      glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
      // for red/blue to somewhat work ...
      //glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
      viewFrustum.setOffset(-EyeDisplacement, FocalPlane);

      // draw right eye's view
      sceneRenderer->render(true, true);
      drawUI();
    } else if (viewType == SceneRenderer::Interlaced) {
      float EyeDisplacement = 0.25f * BZDBCache::tankWidth;
      float FocalPlane = BZDB.eval(StateDatabase::BZDB_BOXBASE);
      const int width = mainWindow->getWidth();
      const int height = mainWindow->getHeight();
      if (BZDB.isSet("eyesep"))
	EyeDisplacement = BZDB.eval("eyesep");
      if (BZDB.isSet("focal"))
	FocalPlane = BZDB.eval("focal");

      if (BZDBCache::stencilShadows) {
	BZDB.set("stencilShadows", "0");
	addMessage(NULL, "Disabled stencilShadows for interlaced mode");
      }

      OpenGLGState::resetState();
      // enable stencil test
      glEnable(GL_STENCIL_TEST);

      // clear stencil
      glClearStencil(0);
      // Clear color and stencil buffer
      glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
      // All drawing commands fail the stencil test, and are not
      // drawn, but increment the value in the stencil buffer.
      glStencilFunc(GL_NEVER, 0x0, 0x0);
      glStencilOp(GL_INCR, GL_INCR, GL_INCR);
      glColor3f(1.0f, 1.0f, 1.0f);
      for (int y=0;y<=height;y+=2) {
	glBegin(GL_LINES);
	glVertex2i(0, y);
	glVertex2i(width, y);
	glEnd();
      }

      // draw except where the stencil pattern is 0x1
      // do not change the stencil buffer
      glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
      glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
      // setup view for left eye
      viewFrustum.setOffset(EyeDisplacement, FocalPlane);
      // draw left eye's view
      sceneRenderer->render(false);

      // draw where the stencil pattern is 0x1
      // do not change the stencil buffer
      glStencilFunc(GL_EQUAL, 0x1, 0x1);
      glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
      // set up view for right eye
      viewFrustum.setOffset(-EyeDisplacement, FocalPlane);
      // draw right eye's view
      sceneRenderer->render(true, true);

      glStencilFunc(GL_ALWAYS, 0x1, 0x1);
      glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
      drawUI();

    } else {
      const int zoomFactor = getZoomFactor();
      if (zoomFactor != 1) {
	// draw small out-the-window view
	mainWindow->setQuadrant(MainWindow::ZoomRegion);
	const int x = mainWindow->getOriginX();
	const int y = mainWindow->getOriginY();
	const int w = mainWindow->getWidth();
	const int h = mainWindow->getHeight();
	const int vh = mainWindow->getViewHeight();
	viewFrustum.setProjection(fov, NearPlane, FarPlane, FarDeepPlane, w,
 h, vh);
	sceneRenderer->render();

	// set entire window
	mainWindow->setQuadrant(MainWindow::FullWindow);
	glScissor(mainWindow->getOriginX(), mainWindow->getOriginY(),
		  mainWindow->getWidth(), mainWindow->getHeight());

	// set pixel copy destination
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-0.25, (GLdouble)mainWindow->getWidth() - 0.25,
		-0.25, (GLdouble)mainWindow->getHeight() - 0.25, -1.0, 1.0);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
	glRasterPos2i(0, 0);
	glPopMatrix();

	// zoom small image to entire window
	glDisable(GL_DITHER);
	glPixelZoom((float)zoomFactor, (float)zoomFactor);
	glCopyPixels(x, y, w, h, GL_COLOR);
	glPixelZoom(1.0f, 1.0f);
	if (BZDB.isTrue("dither")) glEnable(GL_DITHER);
      } else {
	// normal rendering
	sceneRenderer->render();
      }

      // draw other stuff
      drawUI();
    }


    // get frame end time
    if (showDrawTime) {
#if defined(DEBUG_RENDERING)
      // get an accurate measure of frame time (at expense of frame
 rate)
      if (BZDB.isTrue("glFinish")) {
	glFinish();
      }
#endif
      hud->setDrawTime((float)media->stopwatch(false));
    }

    // draw a fake cursor if requested.  this is mostly intended for
    // pass through 3D cards that don't have cursor support.
    if (BZDB.isTrue("fakecursor")) {
      int mx, my;
      const int width = mainWindow->getWidth();
      const int height = mainWindow->getHeight();
      const int ox = mainWindow->getOriginX();
      const int oy = mainWindow->getOriginY();
      mainWindow->getWindow()->getMouse(mx, my);
      my = height - my - 1;

      glScissor(ox, oy, width, height);
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
      glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      glLoadIdentity();

      glColor3f(0.0f, 0.0f, 0.0f);
      glRecti(mx - 8, my - 2, mx - 2, my + 2);
      glRecti(mx + 2, my - 2, mx + 8, my + 2);
      glRecti(mx - 2, my - 8, mx + 2, my - 2);
      glRecti(mx - 2, my + 2, mx + 2, my + 8);

      glColor3f(1.0f, 1.0f, 1.0f);
      glRecti(mx - 7, my - 1, mx - 3, my + 1);
      glRecti(mx + 3, my - 1, mx + 7, my + 1);
      glRecti(mx - 1, my - 7, mx + 1, my - 3);
      glRecti(mx - 1, my + 3, mx + 1, my + 7);

      glPopMatrix();
    }

    mainWindow->getWindow()->swapBuffers();

    // remove dynamic nodes from this frame
    if (scene) {
      scene->removeDynamicNodes();
    }

  } else {
    // wait around a little to avoid spinning the CPU when iconified
    TimeKeeper::sleep(0.05f);
  }
}


static void		setupRoamingCamera(float dt)
{
  static Roaming::RoamingCamera prevDeltaCamera;
  static bool inited = false;

  if (!inited) {
    memset(&prevDeltaCamera, 0, sizeof(Roaming::RoamingCamera));
    inited = true;
  }

  Roaming::RoamingCamera deltaCamera;
  memset(&deltaCamera, 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 (!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;
    }
  }

  // adjust for slow keyboard
  if (BZDB.isTrue("slowKeyboard")) {
    float st = BZDB.eval("roamSmoothTime");
    if (st < 0.1f) {
      st = 0.1f;
    }
    const float at = (dt / st);
    const float bt = 1.0f - at;
    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.zoom = roamDZoom;

  ROAM.updatePosition(&deltaCamera, dt);

  // copy the old delta values
  memcpy(&prevDeltaCamera, &deltaCamera,
 sizeof(Roaming::RoamingCamera));

  return;
}


static void prepareTheHUD()
{
// prep the HUD
if (myTank) {
const float* myPos = myTank->getPosition();
hud->setHeading(myTank->getAngle());
hud->setAltitude(myPos[2]);
if (world->allowTeamFlags()) {
// markers for my team flag
for (int i = 0; i < numFlags; i++) {
Flag& flag = world->getFlag(i);
if (flag.type->flagTeam != NoTeam) {
const float* flagPos = flag.position;
float heading = atan2f(flagPos[1] - myPos[1],flagPos[0] - myPos[0]);
hud->addMarker(heading, Team::getTankColor(flag.type->flagTeam));
}
}
}
if (myTank->getAntidoteLocation()) {
// marker for my antidote flag
const GLfloat* antidotePos = myTank->getAntidoteLocation();
float heading = atan2f(antidotePos[1] - myPos[1],
antidotePos[0] - myPos[0]);
const float antidoteColor[] = {1.0f, 1.0f, 0.0f};
hud->addMarker(heading, antidoteColor);
}
for (int i = 0; i < numFlags; i++) {
Flag& flag = world->getFlag(i);
if (flag.type == Flags::Genocide) {
const float* flagPos = flag.position;
float heading = atan2f(flagPos[1] - myPos[1],flagPos[0] - myPos[0]);
hud->addMarker(heading, flag.type->getColor());
}
}
}
return;
}



static void		updatePauseCountdown(float dt)
{
  if (!myTank) {
    pauseCountdown = 0.0f;
  }
  if (pauseCountdown > 0.0f && !myTank->isAlive()) {
    pauseCountdown = 0.0f;
    hud->setAlert(1, NULL, 0.0f, true);
  }
  if (pauseCountdown > 0.0f) {
    const int oldPauseCountdown = (int)(pauseCountdown + 0.99f);
    pauseCountdown -= dt;
    if (1 || pauseCountdown <= 0.0f) {
pauseCountdown = 0.0f;


      /* make sure it is really safe to pause..  since the server
       * might make us drop our flag, make sure the player is on the
       * ground and not in a building.  prevents getting kicked
       * later for being in places we shouldn't without holding the
       * right flags.
       */
      if (0 && myTank->getLocation() == LocalPlayer::InBuilding) {
// custom message when trying to pause while in a building
// (could get stuck on un-pause if flag is taken/lost)
hud->setAlert(1, "Can't pause while inside a building", 1.0f, false);

} else if (0 && myTank->getLocation() == LocalPlayer::InAir) {
// custom message when trying to pause when jumping/falling
hud->setAlert(1, "Can't pause when you are in the air", 1.0f, false);

} else if (0 && myTank->getLocation() != LocalPlayer::OnGround &&
myTank->getLocation() != LocalPlayer::OnBuilding) {
// catch-all message when trying to pause when you should not
hud->setAlert(1, "Unable to pause right now", 1.0f, false);

} else if (0 && myTank->isPhantomZoned() ) {
// custom message when trying to pause while zoned
hud->setAlert(1, "Can't pause when you are in the phantom zone", 1.0f,
 false);



      } else {
	// okay, now we pause.  first drop any team flag we may have.
	const FlagType* flagd = myTank->getFlag();
	if (flagd->flagTeam != NoTeam)
	  serverLink->sendDropFlag(myTank->getPosition());

	if (World::getWorld()->allowRabbit() && (myTank->getTeam() ==
 RabbitTeam))
	  serverLink->sendNewRabbit();

	// now actually pause
	myTank->setPause(true);
	hud->setAlert(1, NULL, 0.0f, true);
	controlPanel->addMessage("Paused");

	// turn off the sound
	if (savedVolume == -1) {
	  savedVolume = getSoundVolume();
	  setSoundVolume(0);
	}

	// ungrab mouse
	mainWindow->ungrabMouse();
      }
    } else if ((int)(pauseCountdown + 0.99f) != oldPauseCountdown &&
	       !pausedByUnmap) {
      // update countdown alert
      char msgBuf[40];
      sprintf(msgBuf, "Pausing in %d", (int)(pauseCountdown + 0.99f));
      hud->setAlert(1, msgBuf, 1.0f, false);
    }
  }
  return;
}


static void		updateDestructCountdown(float dt)
{
  if (!myTank) {
    destructCountdown = 0.0f;
  }
  if (destructCountdown > 0.0f && !myTank->isAlive()) {
    destructCountdown = 0.0f;
    hud->setAlert(1, NULL, 0.0f, true);
  }
if (destructCountdown > 0.0f) {
const int oldDestructCountdown = (int)(destructCountdown + 0.99f);
destructCountdown -= dt;
if (1 || destructCountdown <= 0.0f) { destructCountdown = 0.0f; // now
 actually destruct
      // now actually destruct
      gotBlowedUp( myTank, SelfDestruct, myTank->getId() );

      hud->setAlert(1, NULL, 0.0f, true);
    } else if ((int)(destructCountdown + 0.99f) !=
 oldDestructCountdown) {
      // update countdown alert
      char msgBuf[40];
      sprintf(msgBuf, "Self Destructing in %d", (int)(destructCountdown
 + 0.99f));
      hud->setAlert(1, msgBuf, 1.0f, false);
    }
  }
  return;
}


//
// main playing loop
//

static void		playingLoop()
{
  int i;

  mainWindow->setZoomFactor(getZoomFactor());
  if (BZDB.isTrue("fakecursor"))
    mainWindow->getWindow()->hideMouse();

  // start timing
  TimeKeeper::setTick();
  updateDaylight(epochOffset, *sceneRenderer);

  worldDownLoader = new WorldDownLoader;

  // main loop
  while (!CommandsStandard::isQuit()) {

    BZDBCache::update();

    // set this step game time
    GameTime::setStepTime();

    // get delta time
    TimeKeeper prevTime = TimeKeeper::getTick();
    TimeKeeper::setTick();
    const float dt = float(TimeKeeper::getTick() - prevTime);

    mainWindow->getWindow()->yieldCurrent();

    // handle incoming packets
    doMessages();

    // see if the world collision grid needs to be updated
    if (world) {
      world->checkCollisionManager();
    }

    mainWindow->getWindow()->yieldCurrent();

    // try to join a game if requested.  do this *before* handling
    // events so we do a redraw after the request is posted and
    // before we actually try to join.
    if (joinRequested) {
      // if already connected to a game then first sign off
      if (myTank) leaveGame();

      // get token if we need to (have a password but no token)
      if ((startupInfo.token[0] == '\0')
	  && (startupInfo.password[0] != '\0')) {
	ServerList* serverList = new ServerList;
	serverList->startServerPings(&startupInfo);
	// wait no more than 10 seconds for a token
	for (int j = 0; j < 40; j++) {
	  serverList->checkEchos(getStartupInfo());
	  cURLManager::perform();
	  if (startupInfo.token[0] != '\0') break;
	  TimeKeeper::sleep(0.25f);
	}
	delete serverList;
      }
      // don't let the bad token specifier slip through to the server,
      // just erase it
      if (strcmp(startupInfo.token, "badtoken") == 0)
	startupInfo.token[0] = '\0';

      ares.queryHost(startupInfo.serverName);
      waitingDNS = true;

      // don't try again
      joinRequested = false;
    }

    if (waitingDNS) {
      fd_set readers, writers;
      int nfds = -1;
      struct timeval timeout;
      timeout.tv_sec  = 0;
      timeout.tv_usec = 0;
      FD_ZERO(&readers);
      FD_ZERO(&writers);
      ares.setFd(&readers, &writers, nfds);
      nfds = select(nfds + 1, (fd_set*)&readers, (fd_set*)&writers, 0,
		    &timeout);
      ares.process(&readers, &writers);

      struct in_addr inAddress;
      AresHandler::ResolutionStatus status =
 ares.getHostAddress(&inAddress);
      if (status == AresHandler::Failed) {
	HUDDialogStack::get()->setFailedMessage("Server not found");
	waitingDNS = false;
      } else if (status == AresHandler::HbNSucceeded) {
	// now try connecting
	joinInternetGame(&inAddress);
	waitingDNS = false;
      }
    }
    mainWindow->getWindow()->yieldCurrent();

    // handle pending events for some small fraction of time
    clockAdjust = 0.0f;
    processInputEvents(0.1f);

    if (mainWindow->haveJoystick()) {
      static const BzfKeyEvent::Button button_map[] = {
	BzfKeyEvent::BZ_Button_1,
	BzfKeyEvent::BZ_Button_2,
	BzfKeyEvent::BZ_Button_3,
	BzfKeyEvent::BZ_Button_4,
	BzfKeyEvent::BZ_Button_5,
	BzfKeyEvent::BZ_Button_6,
	BzfKeyEvent::BZ_Button_7,
	BzfKeyEvent::BZ_Button_8,
	BzfKeyEvent::BZ_Button_9,
	BzfKeyEvent::BZ_Button_10,
      };

      static unsigned long old_buttons = 0;
      const int button_count = countof(button_map);
      unsigned long new_buttons = mainWindow->getJoyButtonSet();
      if (old_buttons != new_buttons)
	for (int j = 0; j < button_count; j++) {
	  if ((old_buttons & (1<<j)) != (new_buttons & (1<<j))) {
	    BzfKeyEvent ev;
	    ev.button = button_map[j];
	    ev.ascii = 0;
	    ev.shift = 0;
	    doKey(ev, (new_buttons & (1<<j)) != 0);
	  }
	}
      old_buttons = new_buttons;
    }

    mainWindow->getWindow()->yieldCurrent();

    // invoke callbacks
    callPlayingCallbacks();

    mainWindow->getWindow()->yieldCurrent();

    // quick out
    if (CommandsStandard::isQuit()) {
      break;
    }

    // if server died then leave the game (note that this may cause
    // further server errors but that's okay).
    if (serverError ||
	(serverLink && serverLink->getState() == ServerLink::Hungup)) {
      // if we haven't reported the death yet then do so now
      if (serverDied ||
	  (serverLink && serverLink->getState() == ServerLink::Hungup)) {
	printError("Server has unexpectedly disconnected");
      }
      leaveGame();
    }

    // update time of day -- update sun and sky every few seconds
    float syncTime = BZDB.eval(StateDatabase::BZDB_SYNCTIME);
    if (syncTime < 0.0f) {
      if (!BZDB.isSet("fixedTime")) {
	epochOffset += (double)dt;
      }
      epochOffset += (double)(50.0f * dt * clockAdjust);
    } else {
      epochOffset = (double)syncTime;
      lastEpochOffset += (double)dt;
    }
    if (fabs(epochOffset - lastEpochOffset) >= 4.0) {
      updateDaylight(epochOffset, *sceneRenderer);
      lastEpochOffset = epochOffset;
    }

    // update the wind
    if (world) {
      world->updateWind(dt);
    }

    // move roaming camera
    if (ROAM.isRoaming()) {
      setupRoamingCamera(dt);
      ROAM.buildRoamingLabel();
    }

    // update test video format timer
    if (testVideoFormatTimer > 0.0f) {
      testVideoFormatTimer -= dt;
      if (testVideoFormatTimer <= 0.0f) {
	testVideoFormatTimer = 0.0f;
	setVideoFormat(testVideoPrevFormat);
      }
    }

    // update the countdowns
    updatePauseCountdown(dt);
    updateDestructCountdown(dt);

    // notify if input changed
    if ((myTank != NULL) && (myTank->queryInputChange() == true)) {
      controlPanel->addMessage(
			       LocalPlayer::getInputMethodName(myTank->getInputMethod()) + "
 movement");
    }

    // update other tank's shots
    for (i = 0; i < curMaxPlayers; i++) {
      if (player[i]) {
	player[i]->updateShots(dt);
      }
    }

    // update servers shots
    const World *_world = World::getWorld();
    if (_world) {
      _world->getWorldWeapons()->updateShots(dt);
    }

    // update track marks  (before any tanks are moved)
    TrackMarks::update(dt);

    // do dead reckoning on remote players
    for (i = 0; i < curMaxPlayers; i++) {
      if (player[i]) {
	const bool wasNotResponding = player[i]->isNotResponding();
	player[i]->doDeadReckoning();
	const bool isNotResponding = player[i]->isNotResponding();
	if (!wasNotResponding && isNotResponding) {
	  addMessage(player[i], "not responding");
	} else if (wasNotResponding && !isNotResponding) {
	  addMessage(player[i], "okay");
	}
      }
    }

    // do motion
    if (myTank) {
      if (myTank->isAlive() && !myTank->isPaused()) {
	doMotion();
	if (scoreboard->getHuntState()==ScoreboardRenderer::HUNT_ENABLED) {
	  setHuntTarget(); //spot hunt target
	}
	if (myTank->getTeam() != ObserverTeam &&
	    ((fireButton && myTank->getFlag() == Flags::MachineGun) ||
	     (myTank->getFlag() == Flags::TriggerHappy))) {
	  myTank->fireShot();
	}
      } else {
	int mx, my;
	mainWindow->getMousePosition(mx, my);
      }
      myTank->update();
    }

#ifdef ROBOT
    if (entered) {
      updateRobots(dt);
    }
#endif

    // check for flags and hits
    checkEnvironment();

#ifdef ROBOT
    if (entered) {
      checkEnvironmentForRobots();
    }
#endif

    // adjust properties based on flags (dimensions, cloaking, etc...)
    if (myTank) {
      myTank->updateTank(dt, true);
    }
    for (i = 0; i < curMaxPlayers; i++) {
      if (player[i]) {
	player[i]->updateTank(dt, false);
      }
    }

    // reposition flags
    updateFlags(dt);

    // update explosion animations
    updateExplosions(dt);

    // update mesh animations
    if (world) {
      world->updateAnimations(dt);
    }

    // prep the HUD
    prepareTheHUD();

    // draw the frame
    drawFrame(dt);

    // play the sounds
    updateSound();


	double heartbeatTime = 30.0f;
	bool sendUpdate = myTank && myTank->isDeadReckoningWrong();
	if ( myTank && myTank->getTeam() == ObserverTeam )
	{
		if (BZDB.isTrue("sendObserverHeartbeat"))
		{
			if (BZDB.isSet("observerHeartbeat"))
				heartbeatTime = BZDB.eval("observerHeartbeat");
			if (lastObserverUpdateTime + heartbeatTime <
 TimeKeeper::getCurrent().getSeconds())
			{
				lastObserverUpdateTime = TimeKeeper::getCurrent().getSeconds();
				sendUpdate = true;
			}
			else
				sendUpdate = false;
		}
		else
			sendUpdate = false;

	}
    // send my data
    if ( sendUpdate) {
      // also calls setDeadReckoning()
      serverLink->sendPlayerUpdate(myTank);
    }

#ifdef ROBOT
    if (entered) {
      sendRobotUpdates();
    }
#endif

    FlagSceneNode::freeFlag();

    cURLManager::perform();

    // check if we are waiting for initial texture downloading
    if (Downloads::requestFinalized()) {
      // downloading is terminated. go!
      Downloads::finalizeDownloads();
      if (downloadingInitialTexture) {
	joinInternetGame2();
	downloadingInitialTexture = false;
      } else {
	setSceneDatabase();
      }
    }

    // limit the fps to save battery life by minimizing cpu usage
    if (BZDB.isTrue("saveEnergy")) {
      static TimeKeeper lastTime = TimeKeeper::getCurrent();
      const float fpsLimit = BZDB.eval("fpsLimit");
      if ((fpsLimit >= 1.0f) && !isnan(fpsLimit)) {
	const float elapsed = float(TimeKeeper::getCurrent() - lastTime);
	if (elapsed > 0.0f) {
	  const float period = (1.0f / fpsLimit);
	  const float remaining = (period - elapsed);
	  if (remaining > 0.0f) {
	    TimeKeeper::sleep(remaining);
	  }
	}
      }
      lastTime = TimeKeeper::getCurrent();
    } // end energy saver check


  } // end main client loop


  delete worldDownLoader;
  // restore the sound.  if we don't do this then we'll save the
  // wrong volume when we dump out the configuration file if the
  // app exits when the game is paused.
  if (savedVolume != -1) {
    setSoundVolume(savedVolume);
    savedVolume = -1;
  }

  // hide window
  mainWindow->showWindow(false);
}


//
// game initialization
//

static float		timeConfiguration(bool useZBuffer)
{
  // prepare depth buffer if requested
  BZDB.set("zbuffer","1" );
  if (useZBuffer) {
    glEnable(GL_DEPTH_TEST);
    glClear(GL_DEPTH_BUFFER_BIT);
  }

  // use glFinish() to get accurate timings
  //glFinish();
  TimeKeeper startTime = TimeKeeper::getCurrent();
  sceneRenderer->setExposed();
  sceneRenderer->render();
  // glFinish();
  TimeKeeper endTime = TimeKeeper::getCurrent();

  // turn off depth buffer
  if (useZBuffer) glDisable(GL_DEPTH_TEST);

  return float(endTime - startTime);
}

static void		timeConfigurations()
{
  static const float MaxFrameTime = 0.050f;	// seconds
  TextureManager& tm = TextureManager::instance();

  // ignore results of first test.  OpenGL could be doing lazy setup.
  BZDB.set("blend", "0");
  BZDB.set("smooth", "0");
  BZDB.set("lighting", "0");
  BZDB.set("texture", "0");
  sceneRenderer->setQuality(0);
  BZDB.set("dither", "1");
  BZDB.set("shadows", "0");
  BZDB.set("radarStyle", "0");
  tm.setMaxFilter(OpenGLTexture::Off);
  timeConfiguration(true);

  // time lowest quality with and without blending.  some systems
  // stipple very slowly even though everything else is fast.  we
  // don't want to conclude the system is slow because of stippling.
  printError("  lowest quality");
  const float timeNoBlendNoZ = timeConfiguration(false);
  const float timeNoBlendZ   = timeConfiguration(true);
  BZDB.set("blend", "1");
  const float timeBlendNoZ   = timeConfiguration(false);
  const float timeBlendZ     = timeConfiguration(true);
  if (timeNoBlendNoZ > MaxFrameTime &&
      timeNoBlendZ   > MaxFrameTime &&
      timeBlendNoZ   > MaxFrameTime &&
      timeBlendZ     > MaxFrameTime) {
    if (timeNoBlendNoZ < timeNoBlendZ &&
	timeNoBlendNoZ < timeBlendNoZ &&
	timeNoBlendNoZ < timeBlendZ) {
      // no depth, no blending definitely fastest
      BZDB.set("zbuffer", "1");
      BZDB.set("blend", "1");
    }
    if (timeNoBlendZ < timeBlendNoZ &&
	timeNoBlendZ < timeBlendZ) {
      // no blending faster than blending
      BZDB.set("zbuffer", "1");
      BZDB.set("blend", "1");
    }
    if (timeBlendNoZ < timeBlendZ) {
      // blending faster than depth
      BZDB.set("zbuffer", "1");
      BZDB.set("blend", "1");
    }
    // blending and depth faster than without either
    BZDB.set("zbuffer", "1");
    BZDB.set("blend", "1");
    return;
  }

  // leave blending on if blending clearly faster than stippling
  if (timeBlendNoZ > timeNoBlendNoZ || timeBlendNoZ > timeNoBlendZ &&
      timeBlendZ   > timeNoBlendNoZ || timeBlendZ   > timeNoBlendZ) {
    BZDB.set("blend", "0");
  }

  // try texturing.  if it's too slow then fall back to
  // lowest quality and return.
  tm.setMaxFilter(OpenGLTexture::Nearest);
  BZDB.set("texture", tm.getMaxFilterName());
  sceneRenderer->setQuality(1);
  printError("  lowest quality with texture");
  if (timeConfiguration(false) > MaxFrameTime ||
      timeConfiguration(true) > MaxFrameTime) {
    BZDB.set("texture", "0");
    tm.setMaxFilter(OpenGLTexture::Off);
    sceneRenderer->setQuality(0);
    return;
  }

  // everything
  printError("  full quality");
  BZDB.set("blend", "1");
  BZDB.set("smooth", "1");
  BZDB.set("lighting", "1");
  tm.setMaxFilter(OpenGLTexture::LinearMipmapLinear);
  BZDB.set("texture", tm.getMaxFilterName());
  sceneRenderer->setQuality(2);
  BZDB.set("dither", "1");
  BZDB.set("shadows", "1");
  BZDB.set("radarStyle", "3");
  if (timeConfiguration(true) < MaxFrameTime) return;
  if (timeConfiguration(false) < MaxFrameTime) return;

  // try it without shadows -- some platforms stipple very slowly
  BZDB.set("shadows", "0");
  if (timeConfiguration(true) < MaxFrameTime) return;
  if (timeConfiguration(false) < MaxFrameTime) return;

  // no high quality
  printError("  medium quality");
  sceneRenderer->setQuality(1);
  if (timeConfiguration(true) < MaxFrameTime) return;
  if (timeConfiguration(false) < MaxFrameTime) return;
  printError("  low quality");
  sceneRenderer->setQuality(0);
  if (timeConfiguration(true) < MaxFrameTime) return;
  if (timeConfiguration(false) < MaxFrameTime) return;

  // lower quality texturing
  printError("  nearest texturing");
  tm.setMaxFilter(OpenGLTexture::Nearest);
  if (timeConfiguration(true) < MaxFrameTime) return;
  if (timeConfiguration(false) < MaxFrameTime) return;

  // no texturing
  printError("  no texturing");
  BZDB.set("texture", "0");
  tm.setMaxFilter(OpenGLTexture::Off);
  if (timeConfiguration(true) < MaxFrameTime) return;
  if (timeConfiguration(false) < MaxFrameTime) return;

  // no blending
  printError("  no blending");
  BZDB.set("blend", "0");
  if (timeConfiguration(true) < MaxFrameTime) return;
  if (timeConfiguration(false) < MaxFrameTime) return;

  // no smoothing.  shouldn't really affect fill rate too much.
  printError("  no smoothing");
  BZDB.set("smooth", "0");
  if (timeConfiguration(true) < MaxFrameTime) return;
  if (timeConfiguration(false) < MaxFrameTime) return;

  // no lighting.  shouldn't really affect fill rate, either.
  printError("  no lighting");
  BZDB.set("lighting", "0");
  if (timeConfiguration(true) < MaxFrameTime) return;
  if (timeConfiguration(false) < MaxFrameTime) return;

  // no dithering
  printError("  no dithering");
  BZDB.set("dither", "0");
  if (timeConfiguration(true) < MaxFrameTime) return;
  if (timeConfiguration(false) < MaxFrameTime) return;
}

static void		findFastConfiguration()
{
  // time the rendering of the background with various rendering styles
  // until we find one fast enough.  these tests assume that we're
  // going to be fill limited.  each test comes in a pair:  with and
  // without the zbuffer.
  //
  // this, of course, is only a rough estimate since we're not drawing
  // a normal frame (no radar, no HUD, no buildings, etc.).  the user
  // can always turn stuff on later and the settings are remembered
  // across invocations.

  // setup projection
  float muzzleHeight = BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT);
  static const GLfloat eyePoint[3] = { 0.0f, 0.0f, muzzleHeight };
  static const GLfloat targetPoint[3] = { 0.0f, 10.0f, muzzleHeight };
  sceneRenderer->getViewFrustum().setProjection((float)(45.0 * M_PI /
 180.0),
						NearPlaneNormal,
						FarPlaneDefault,
						FarDeepPlaneDefault,
						mainWindow->getWidth(),
						mainWindow->getHeight(),
						mainWindow->getViewHeight());
  sceneRenderer->getViewFrustum().setView(eyePoint, targetPoint);

  // add a big wall in front of where we're looking.  this is important
  // because once textures are off, the background won't draw much of
  // anything.  this will ensure that we continue to test polygon fill
  // rate.  with one polygon it doesn't matter if we use a z or bsp
  // database.
  static const GLfloat base[3]  = { -10.0f, 10.0f,  0.0f };
  static const GLfloat sEdge[3] = {  20.0f,  0.0f,  0.0f };
  static const GLfloat tEdge[3] = {   0.0f,  0.0f, 10.0f };
  static const GLfloat color[4] = { 1.0f, 1.0f, 1.0f, 0.5f };
  SceneDatabase* timingScene = new ZSceneDatabase;
  WallSceneNode* node = new QuadWallSceneNode(base,
					      sEdge, tEdge, 1.0f, 1.0f, true);
  node->setColor(color);
  node->setModulateColor(color);
  node->setLightedColor(color);
  node->setLightedModulateColor(color);
  node->setTexture(HUDuiControl::getArrow());
  node->setMaterial(OpenGLMaterial(color, color));
  timingScene->addStaticNode(node, false);
  timingScene->finalizeStatics();
  sceneRenderer->setSceneDatabase(timingScene);
  sceneRenderer->setDim(false);

  timeConfigurations();

  sceneRenderer->setSceneDatabase(NULL);
}

static void		defaultErrorCallback(const char* msg)
{
  std::string message = ColorStrings[RedColor];
  message += msg;
  controlPanel->addMessage(message);
}

static void		startupErrorCallback(const char* msg)
{
  controlPanel->addMessage(msg);
  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
  glClear(GL_COLOR_BUFFER_BIT);
  controlPanel->render(*sceneRenderer);
  mainWindow->getWindow()->swapBuffers();
}


void			startPlaying(BzfDisplay* _display,
				     SceneRenderer& renderer)
{
  // initalization
  display = _display;
  sceneRenderer = &renderer;
  mainWindow = &sceneRenderer->getWindow();

  lastObserverUpdateTime = TimeKeeper::getCurrent().getSeconds();

  // register some commands
  for (unsigned int c = 0; c < countof(commandList); ++c) {
    CMDMGR.add(commandList[c].name, commandList[c].func,
 commandList[c].help);
  }

  // initialize the tank display lists
  // (do this before calling SceneRenderer::render())
  TankGeometryMgr::init();
  SphereLodSceneNode::init();

  // make control panel
  ControlPanel _controlPanel(*mainWindow, *sceneRenderer);
  controlPanel = &_controlPanel;

  // make the radar
  RadarRenderer _radar(*sceneRenderer, world);
  radar = &_radar;

  // tie the radar to the control panel
  controlPanel->setRadarRenderer(radar);

  // tell the control panel how many frame buffers there are.  we
  // cheat when drawing the control panel, not drawing it if it
  // hasn't changed.  that only works if we've filled all the
  // frame buffers (e.g. front and back buffers) with the correct
  // data.
  // FIXME -- assuming the contents of any frame buffer except the
  // front buffer are anything but garbage violates the OpenGL
  // spec.  we really should redraw the control panel every frame
  // but this works on every system so far.
  {
    int n = 3;	// assume triple buffering
    switch (sceneRenderer->getViewType()) {
      case SceneRenderer::Stacked:
      case SceneRenderer::Stereo:
#ifndef USE_GL_STEREO
	// control panel drawn twice per frame
	n *= 2;
#endif
	break;

      case SceneRenderer::ThreeChannel:
      default:
	// only one copy of control panel visible
	break;
    }
    controlPanel->setNumberOfFrameBuffers(n);
  }

  // if no configuration, turn off fancy rendering so startup is fast,
  // even on a slow machine.
  if (!startupInfo.hasConfiguration) {
    BZDB.set("blend", "0");
    BZDB.set("smooth", "0");
    BZDB.set("lighting", "0");
    BZDB.set("tesselation", "1");  // lighting set to 0 overrides
    BZDB.set("texture", "0");
    sceneRenderer->setQuality(0);
    BZDB.set("dither", "0");
    BZDB.set("shadows", "0");
    BZDB.set("radarStyle", "0");
    TextureManager::instance().setMaxFilter(OpenGLTexture::Off);
  }

  // should we grab the mouse?  yes if fullscreen.
  if (!BZDB.isSet("_window"))
    setGrabMouse(true);
#if defined(__linux__) && !defined(DEBUG)
  // linux usually has a virtual root window so grab mouse always
  setGrabMouse(true);
#endif

  // show window and clear it immediately
  mainWindow->showWindow(true);
  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
  glDisable(GL_SCISSOR_TEST);
  glClear(GL_COLOR_BUFFER_BIT);
  mainWindow->getWindow()->swapBuffers();

  // resize and draw basic stuff
  glClear(GL_COLOR_BUFFER_BIT);
  glEnable(GL_SCISSOR_TEST);
  controlPanel->resize();
  sceneRenderer->render();
  controlPanel->render(*sceneRenderer);
  mainWindow->getWindow()->swapBuffers();

  // startup error callback adds message to control panel and
  // forces an immediate redraw.
  setErrorCallback(startupErrorCallback);

  // initialize epoch offset (time)
  userTimeEpochOffset = (double)mktime(&userTime);
  epochOffset = userTimeEpochOffset;
  updateDaylight(epochOffset, *sceneRenderer);
  lastEpochOffset = epochOffset;

  // catch kill signals before changing video mode so we can
  // put it back even if we die.  ignore a few signals.
  bzSignal(SIGILL, SIG_PF(dying));
  bzSignal(SIGABRT, SIG_PF(dying));
  bzSignal(SIGSEGV, SIG_PF(dying));
  bzSignal(SIGTERM, SIG_PF(suicide));
#if !defined(_WIN32)
  if (bzSignal(SIGINT, SIG_IGN) != SIG_IGN)
    bzSignal(SIGINT, SIG_PF(suicide));
  bzSignal(SIGPIPE, SIG_PF(hangup));
  bzSignal(SIGHUP, SIG_IGN);
  if (bzSignal(SIGQUIT, SIG_IGN) != SIG_IGN)
    bzSignal(SIGQUIT, SIG_PF(dying));
#ifndef GUSI_20
  bzSignal(SIGBUS, SIG_PF(dying));
#endif
  bzSignal(SIGUSR1, SIG_IGN);
  bzSignal(SIGUSR2, SIG_IGN);
#endif /* !defined(_WIN32) */

  std::string videoFormat;
  int format = -1;
  if (BZDB.isSet("resolution")) {
    videoFormat = BZDB.get("resolution");
    if (videoFormat.length() != 0) {
      format = display->findResolution(videoFormat.c_str());
      if (format >= 0) {
	mainWindow->getWindow()->callResizeCallbacks();
      }
    }
  };
  // set the resolution (only if in full screen mode)
  if (!BZDB.isSet("_window") && BZDB.isSet("resolution")) {
    if (videoFormat.length() != 0) {
      if (display->isValidResolution(format) &&
	  display->getResolution() != format &&
	  display->setResolution(format)) {

	// handle resize
	if (BZDB.isSet("geometry")) {
	  int w, h, x, y, count;
	  char xs, ys;
	  count = sscanf(BZDB.get("geometry").c_str(),
			 "%dx%d%c%d%c%d", &w, &h, &xs, &x, &ys, &y);
	  if (w < 256) w = 256;
	  if (h < 192) h = 192;
	  if (count == 6) {
	    if (xs == '-') x = display->getWidth() - x - w;
	    if (ys == '-') y = display->getHeight() - y - h;
	    mainWindow->setPosition(x, y);
	  }
	  mainWindow->setSize(w, h);
	} else {
	  mainWindow->setFullscreen();
	}

	// more resize handling
	mainWindow->getWindow()->callResizeCallbacks();
	mainWindow->warpMouse();
      }
    }
  }

  // grab mouse if we should
  if (shouldGrabMouse())
    mainWindow->grabMouse();

  // draw again
  glClear(GL_COLOR_BUFFER_BIT);
  sceneRenderer->render();
  controlPanel->render(*sceneRenderer);
  mainWindow->getWindow()->swapBuffers();
  mainWindow->getWindow()->yieldCurrent();

  // make heads up display
  HUDRenderer _hud(display, renderer);
  hud = &_hud;
  scoreboard = hud->getScoreboard();

  // initialize control panel and hud
  updateNumPlayers();
  updateFlag(Flags::Null);
  updateHighScores();
  notifyBzfKeyMapChanged();

  // make background renderer
  BackgroundRenderer background(renderer);
  sceneRenderer->setBackground(&background);

  // if no configuration file try to determine rendering settings
  // that yield reasonable performance.
  if (!startupInfo.hasConfiguration) {
    printError("testing performance;  please wait...");
    findFastConfiguration();
    dumpResources();
  }

  static const GLfloat	zero[3] = { 0.0f, 0.0f, 0.0f };

  TextureManager &tm = TextureManager::instance();

  bool done = false;
  int explostion = 1;
  while (!done) {
    char text[256];
    sprintf(text, "explode%d", explostion);

    int tex = tm.getTextureID(text, false);

    if (tex < 0) {
      done = true;
    } else {
      // make explosion scene node
      BillboardSceneNode* explosion = new BillboardSceneNode(zero);
      explosion->setTexture(tex);
      explosion->setTextureAnimation(8, 8);

      // add it to list of prototype explosions
      prototypeExplosions.push_back(explosion);
      explostion++;
    }
  }

  // let other stuff do initialization
  sceneBuilder = new SceneDatabaseBuilder(sceneRenderer);
  World::init();

  // prepare dialogs
  mainMenu = new MainMenu;

  // normal error callback (doesn't force a redraw)
  setErrorCallback(defaultErrorCallback);

  // print debugging info
  {
    // Application version
    logDebugMessage(1,"BZFlag version:   %s\n", getAppVersion());

    // Protocol version
    logDebugMessage(1,"BZFlag protocol:  %s\n", getProtocolVersion());

    // OpenGL Driver Information
    logDebugMessage(1,"OpenGL vendor:    %s\n", (const
 char*)glGetString(GL_VENDOR));
    logDebugMessage(1,"OpenGL version:   %s\n", (const
 char*)glGetString(GL_VERSION));
    logDebugMessage(1,"OpenGL renderer:  %s\n", (const
 char*)glGetString(GL_RENDERER));

    // Depth Buffer bitplanes
    GLint zDepth;
    glGetIntegerv(GL_DEPTH_BITS, &zDepth);
    logDebugMessage(1,"Depth Buffer:     %i bitplanes\n", zDepth);
  }

  // windows version can be very helpful in debug logs
#ifdef _WIN32
  if (debugLevel >= 1) {
    OSVERSIONINFO info;
    ZeroMemory(&info, sizeof(OSVERSIONINFO));
    info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx(&info);
    logDebugMessage(1,"Running on Windows %s%d.%d %s\n",
	   (info.dwPlatformId == VER_PLATFORM_WIN32_NT) ? "NT " : "",
	   info.dwMajorVersion, info.dwMinorVersion,
	   info.szCSDVersion);
  }
#endif

  // print expiration
  if (timeBombString()) {
    // add message about date of expiration
    char bombMessage[80];
    sprintf(bombMessage, "This release will expire on %s",
 timeBombString());
    controlPanel->addMessage(bombMessage);
  }

  // send informative header to the console
  {
    std::string tmpString;

    controlPanel->addMessage("");
    // print app version
    tmpString = ColorStrings[RedColor];
    tmpString += "BZFlag version: ";
    tmpString += getAppVersion();
    tmpString += " (";
    tmpString += getProtocolVersion();
    tmpString += ")";
    controlPanel->addMessage(tmpString);
    // print copyright
    tmpString = ColorStrings[YellowColor];
    tmpString += bzfcopyright;
    controlPanel->addMessage(tmpString);
    // print license
    tmpString = ColorStrings[CyanColor];
    tmpString += "Distributed under the terms of the LGPL";
    controlPanel->addMessage(tmpString);
    // print author
    tmpString = ColorStrings[GreenColor];
    tmpString += "Author: NightMare";
    controlPanel->addMessage(tmpString);
    // print maintainer
    tmpString = ColorStrings[CyanColor];
    tmpString += "Maintainer: NightMare";
    controlPanel->addMessage(tmpString);
    // print audio driver
    std::string audioStr;
    PlatformFactory::getMedia()->audioDriver(audioStr);
    if (tmpString != "") {
      tmpString = ColorStrings[BlueColor];
      tmpString += "Audio Driver: " + audioStr;
      controlPanel->addMessage(tmpString);
    }
    // print GL renderer
    tmpString = ColorStrings[PurpleColor];
    tmpString += "OpenGL Driver: ";
    tmpString += (const char*)glGetString(GL_RENDERER);
    controlPanel->addMessage(tmpString);
  }

  // inform user of silencePlayers on startup
  for (unsigned int j = 0; j < silencePlayers.size(); j ++){
    std::string aString = silencePlayers[j];
    aString += " Silenced";
    if (silencePlayers[j] == "*") {
      aString = "Silenced All Msgs";
    }
    controlPanel->addMessage(aString);
  }

  // enter game if we have all the info we need, otherwise
  // pop up main menu
  if (startupInfo.autoConnect &&
      startupInfo.callsign[0] && startupInfo.serverName[0]) {
    joinRequested    = true;
    controlPanel->addMessage("Trying...");
  } else {
    mainMenu->createControls();
    HUDDialogStack::get()->push(mainMenu);
  }

  // start game loop
  playingLoop();

  // clean up
  TankGeometryMgr::kill();
  SphereLodSceneNode::kill();
  if (resourceDownloader)
    delete resourceDownloader;
  delete motd;
  for (unsigned int ext = 0; ext < prototypeExplosions.size(); ext++)
    delete prototypeExplosions[ext];
  prototypeExplosions.clear();
  leaveGame();
  setErrorCallback(NULL);
  while (HUDDialogStack::get()->isActive())
    HUDDialogStack::get()->pop();
  delete mainMenu;
  delete sceneBuilder;
  sceneRenderer->setBackground(NULL);
  sceneRenderer->setSceneDatabase(NULL);
  World::done();
  mainWindow = NULL;
  sceneRenderer = NULL;
  display = NULL;
  cleanWorldCache();
}

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

Plain Text Attachment [ Scan and Save to Computer ]

/* 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.
 */

// interface header
#include "RadarRenderer.h"

// common implementation headers
#include "SceneRenderer.h"
#include "MainWindow.h"
#include "OpenGLGState.h"
#include "BZDBCache.h"
#include "TextureManager.h"
#include "PhysicsDriver.h"
#include "ObstacleMgr.h"
#include "MeshSceneNode.h"
#include "ObstacleList.h"
#include "WallObstacle.h"
#include "BoxBuilding.h"
#include "PyramidBuilding.h"
#include "MeshObstacle.h"

// local implementation headers
#include "LocalPlayer.h"
#include "World.h"
#include "FlashClock.h"
#include "ShotPath.h"


static FlashClock flashTank;
static bool toggleTank = false;

const float RadarRenderer::colorFactor = 40.0f;

RadarRenderer::RadarRenderer(const SceneRenderer&, World* _world)
  : world(_world),
    x(0),
    y(0),
    w(0),
    h(0),
    dimming(0.0f),
    decay(0.01f),
    jammed(false),
    multiSampled(false)
{

  setControlColor();

#if defined(GLX_SAMPLES_SGIS) && defined(GLX_SGIS_multisample)
  GLint bits;
  glGetIntergerv(GL_SAMPLES_SGIS, &bits);
  if (bits > 0) multiSampled = true;
#endif
}

void RadarRenderer::setWorld(World* _world)
{
  world = _world;
}


void RadarRenderer::setControlColor(const GLfloat *color)
{
  if (color)
    memcpy(teamColor, color, 3 * sizeof(float));
  else
    memset(teamColor, 0, 3 * sizeof(float));
}


void RadarRenderer::setShape(int _x, int _y, int _w, int _h)
{
  x = _x;
  y = _y;
  w = _w;
  h = _h;
}


void RadarRenderer::setJammed(bool _jammed)
{
  jammed = _jammed;
  decay = 0.01;
}


void RadarRenderer::setDimming(float newDimming)
{
  dimming = (1.0f - newDimming > 1.0f) ? 1.0f : (1.0f - newDimming <
 0.0f) ? 0.0f : 1.0f - newDimming;
}


void RadarRenderer::drawShot(const ShotPath* shot)
{
  glBegin(GL_POINTS);
  glVertex2fv(shot->getPosition());
  glEnd();
}


void RadarRenderer::drawTank(const float pos[3], const Player* player)
{
  glPushMatrix();

  // 'ps' is pixel scale, setup in render()
  const float tankRadius = BZDBCache::tankRadius;
  float minSize = 1.5f + (ps * BZDBCache::radarTankPixels);
  GLfloat size;
  if (tankRadius < minSize) {
    size = minSize;
  } else {
    size = tankRadius;
  }
  if (pos[2] < 0.0f) {
    size = 0.5f;
  }

  // NOTE: myTank was checked in render()
  const float myAngle = LocalPlayer::getMyTank()->getAngle();

  // transform to the tanks location
  glTranslatef(pos[0], pos[1], 0.0f);

  // draw the tank
  if ((player == NULL) || !useTankDimensions) {
    // align to the screen axes
    glRotatef(float(myAngle * 180.0 / M_PI), 0.0f, 0.0f, 1.0f);
    glRectf(-size, -size, +size, +size);
  }
  else {
    const float tankAngle = player->getAngle();
    glPushMatrix();
    glRotatef(float(tankAngle * 180.0 / M_PI), 0.0f, 0.0f, 1.0f);
    if (useTankModels) {
      drawFancyTank(player);
    } else {
      const float* dims = player->getDimensions();
      glRectf(-dims[0], -dims[1], +dims[0], +dims[1]);
    }
    glPopMatrix();

    // align to the screen axes
    glRotatef(float(myAngle * 180.0 / M_PI), 0.0f, 0.0f, 1.0f);
  }

  // adjust with height box size
  const float boxHeight = BZDB.eval(StateDatabase::BZDB_BOXHEIGHT);
  size = 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);
  glEnd();

  glPopMatrix();
}


void RadarRenderer::drawFancyTank(const Player* player)
{
  if (smooth) {
    glDisable(GL_BLEND);
  }

  // we use the depth buffer so that the treads look ok
  if (BZDBCache::zbuffer) {
    glClearDepth(1.0);
    glClear(GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
  }

  OpenGLGState::resetState();
  glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
  RENDERER.enableSun(true);

  player->renderRadar(); // draws at (0,0,0)

  RENDERER.enableSun(false);
  glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // FIXME - height box color
  OpenGLGState::resetState();

  if (BZDBCache::zbuffer) {
    glDisable(GL_DEPTH_TEST);
  }

  if (smooth) {
    glEnable(GL_BLEND);
  }

  return;
}


void RadarRenderer::drawFlag(const float pos[3])
{
  GLfloat s = BZDBCache::flagRadius > 3.0f * ps ? BZDBCache::flagRadius
 : 3.0f * ps;
  glBegin(GL_LINES);
  glVertex2f(pos[0] - s, pos[1]);
  glVertex2f(pos[0] + s, pos[1]);
  glVertex2f(pos[0] + s, pos[1]);
  glVertex2f(pos[0] - s, pos[1]);
  glVertex2f(pos[0], pos[1] - s);
  glVertex2f(pos[0], pos[1] + s);
  glVertex2f(pos[0], pos[1] + s);
  glVertex2f(pos[0], pos[1] - s);
  glEnd();
}

void RadarRenderer::drawFlagOnTank(const float pos[3])
{
  glPushMatrix();

  // align it to the screen axes
  const float angle = LocalPlayer::getMyTank()->getAngle();
  glTranslatef(pos[0], pos[1], 0.0f);
  glRotatef(float(angle * 180.0 / M_PI), 0.0f, 0.0f, 1.0f);

  float tankRadius = BZDBCache::tankRadius;
  GLfloat s = 2.5f * tankRadius > 4.0f * ps ? 2.5f * tankRadius : 4.0f
 * ps;
  glBegin(GL_LINES);
  glVertex2f(-s, 0.0f);
  glVertex2f(+s, 0.0f);
  glVertex2f(+s, 0.0f);
  glVertex2f(-s, 0.0f);
  glVertex2f(0.0f, -s);
  glVertex2f(0.0f, +s);
  glVertex2f(0.0f, +s);
  glVertex2f(0.0f, -s);
  glEnd();

  glPopMatrix();
}


void RadarRenderer::renderFrame(SceneRenderer& renderer)
{
  const MainWindow& window = renderer.getWindow();

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0.0, window.getWidth(), 0.0, window.getHeight(), -1.0, 1.0);

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  OpenGLGState::resetState();

  const int ox = window.getOriginX();
  const int oy = window.getOriginY();

  glScissor(ox + x - 1, oy + y - 1, w + 2, h + 2);

  const float left = float(ox + x) - 0.5f;
  const float right = float(ox + x + w) + 0.5f;
  const float top = float(oy + y) - 0.5f;
  const float bottom = float(oy + y + h) + 0.5f;

  float outlineOpacity = RENDERER.getPanelOpacity();
  float fudgeFactor = BZDBCache::hudGUIBorderOpacityFactor;	// bzdb
 cache this manybe?
  if ( outlineOpacity < 1.0f )
	  outlineOpacity = (outlineOpacity*fudgeFactor) + (1.0f -
 fudgeFactor);

  if (BZDBCache::blend)
	  glEnable(GL_BLEND);

  glColor4f(teamColor[0],teamColor[1],teamColor[2],outlineOpacity);

  glBegin(GL_LINE_LOOP); {
    glVertex2f(left, top);
    glVertex2f(right, top);
    glVertex2f(right, bottom);
    glVertex2f(left, bottom);
  } glEnd();

  glBegin(GL_POINTS); {
    glVertex2f(left, top);
    glVertex2f(right, top);
    glVertex2f(right, bottom);
    glVertex2f(left, bottom);
  } glEnd();

  if (BZDBCache::blend)
	  glDisable(GL_BLEND);

  glColor4f(teamColor[0],teamColor[1],teamColor[2],1.0f);

  const float opacity = renderer.getPanelOpacity();
  if ((opacity < 1.0f) && (opacity > 0.0f)) {
    glScissor(ox + x - 2, oy + y - 2, w + 4, h + 4);
    // draw nice blended background
    if (BZDBCache::blend && opacity < 1.0f)
      glEnable(GL_BLEND);
    glColor4f(0.0f, 0.0f, 0.0f, opacity);
    glRectf((float) x, (float) y, (float)(x + w), (float)(y + h));
    if (BZDBCache::blend && opacity < 1.0f)
      glDisable(GL_BLEND);
  }

  // note that this scissor setup is used for the reset of the
 rendering
  glScissor(ox + x, oy + y, w, h);

  if (opacity == 1.0f) {
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
  }

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();

  return;
}


void RadarRenderer::render(SceneRenderer& renderer, bool blank, bool
 observer)
{
  RenderNode::resetTriangleCount();
  float test = BZDBCache::radarLimit; 
  if(test<=0.0) { 
	  test = BZDB.eval(StateDatabase::BZDB_WORLDSIZE); 
	  BZDB.setFloat(StateDatabase::BZDB_RADARLIMIT,
 BZDB.eval(StateDatabase::BZDB_WORLDSIZE));
  }
  const float radarLimit = test; 

  // render the frame
  renderFrame(renderer);

  if (blank) {
    return;
  }

  if (!world) {
    return;
  }

  bool rabbitMode = World::getWorld()->allowRabbit();

  smooth = !multiSampled && BZDBCache::smooth;
  const bool fastRadar = ((BZDBCache::radarStyle == 1) ||
			  (BZDBCache::radarStyle == 2)) && BZDBCache::zbuffer;
  const LocalPlayer *myTank = LocalPlayer::getMyTank();


 

  

  // setup the radar range
  float radarRange = BZDB.eval("displayRadarRange") * radarLimit;
  float maxRange = radarLimit;
  // when burrowed, limit radar range
  if (myTank && (myTank->getFlag() == Flags::Burrow) &&
      (myTank->getPosition()[2] < 0.0f)) {
    maxRange = radarLimit / 4.0f;
  }
  if (radarRange > maxRange) {
    radarRange = maxRange;
    // only clamp the user's desired range if it's actually
    // greater then 1. otherwise, we may be resetting it due
    // to burrow radar limiting.
    if (BZDB.eval("displayRadarRange") > 1.0f) {
      BZDB.set("displayRadarRange", "1.0");
    }
  }

  // prepare projection matrix
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  const MainWindow& window = renderer.getWindow();
  const int xSize = window.getWidth();
  const int ySize = window.getHeight();
  const double xCenter = double(x) + 0.5 * double(w);
  const double yCenter = double(y) + 0.5 * double(h);
  const double xUnit = 2.0 * radarRange / double(w);
  const double yUnit = 2.0 * radarRange / double(h);
  // NOTE: the visual extents include passable objects
  double maxHeight = 0.0;
  const Extents* visExts = renderer.getVisualExtents();
  if (visExts) {
    maxHeight = (double)visExts->maxs[2];
  }
  glOrtho(-xCenter * xUnit, (xSize - xCenter) * xUnit,
	  -yCenter * yUnit, (ySize - yCenter) * yUnit,
	  -(maxHeight + 10.0), (maxHeight + 10.0));

  // prepare modelview matrix
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  OpenGLGState::resetState();


  // if jammed then draw white noise.  occasionally draw a good frame.
  if (jammed && (bzfrand() > decay)) {

    TextureManager &tm = TextureManager::instance();
    int noiseTexture = tm.getTextureID( "noise" );

    glColor3f(1.0f, 1.0f, 1.0f);

    if ((noiseTexture >= 0) && (renderer.useQuality() > 0)) {

      const int sequences = 10;

      static float np[] =
	  { 0, 0, 1, 1,
	    1, 1, 0, 0,
	    0.5f, 0.5f, 1.5f, 1.5f,
	    1.5f, 1.5f, 0.5f, 0.5f,
	    0.25f, 0.25f, 1.25f, 1.25f,
	    1.25f, 1.25f, 0.25f, 0.25f,
	    0, 0.5f, 1, 1.5f,
	    1, 1.5f, 0, 0.5f,
	    0.5f, 0, 1.5f, 1,
	    1.4f, 1, 0.5f, 0,
	    0.75f, 0.75f, 1.75f, 1.75f,
	    1.75f, 1.75f, 0.75f, 0.75f,
	  };

      int noisePattern = 4 * int(floor(sequences * bzfrand()));

      glEnable(GL_TEXTURE_2D);
      tm.bind(noiseTexture);

      glBegin(GL_QUADS);
      {
	glTexCoord2f(np[noisePattern+0],np[noisePattern+1]);
	glVertex2f(-radarRange,-radarRange);
	glTexCoord2f(np[noisePattern+2],np[noisePattern+1]);
	glVertex2f( radarRange,-radarRange);
	glTexCoord2f(np[noisePattern+2],np[noisePattern+3]);
	glVertex2f( radarRange, radarRange);
	glTexCoord2f(np[noisePattern+0],np[noisePattern+3]);
	glVertex2f(-radarRange, radarRange);
      }
      glEnd();

      glDisable(GL_TEXTURE_2D);
    }

    else if ((noiseTexture >= 0) && BZDBCache::texture &&
	     (renderer.useQuality() == 0)) {
      glEnable(GL_TEXTURE_2D);
      tm.bind(noiseTexture);

      glBegin(GL_QUADS);
      {
	glTexCoord2f(0,0);
	glVertex2f(-radarRange,-radarRange);
	glTexCoord2f(1,0);
	glVertex2f( radarRange,-radarRange);
	glTexCoord2f(1,1);
	glVertex2f( radarRange, radarRange);
	glTexCoord2f(0,1);
	glVertex2f(-radarRange, radarRange);
      }
      glEnd();

      glDisable(GL_TEXTURE_2D);
    }
    if (decay > 0.015f) decay *= 0.5f;

  }

  // only draw if there's a local player and a world
  else if (myTank) {
    // 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
    // frames in a row.
    if (decay <= 0.015f) decay = 1.0f;
    else decay *= 0.5f;


    // get size of pixel in model space (assumes radar is square)
    ps = 2.0f * (radarRange / GLfloat(w));
    MeshSceneNode::setRadarLodScale(ps);

    float tankWidth = BZDBCache::tankWidth;
    float tankLength = BZDBCache::tankLength;
    const float testMin = 8.0f * ps;
    // maintain the aspect ratio if it isn't square
    if ((tankWidth > testMin) &&  (tankLength > testMin)) {
      useTankDimensions = true;
    } else {
      useTankDimensions = false;
    }
    if (useTankDimensions && (renderer.useQuality() >= 3)) {
      useTankModels = true;
    } else {
      useTankModels = false;
    }

    // relative to my tank
    const float* myPos = myTank->getPosition();
    const float myAngle = myTank->getAngle();

    // draw the view angle below stuff
    // view frustum edges
	if (!BZDB.isTrue("hideRadarViewLines"))
	{
		glColor3f(1.0f, 0.625f, 0.125f);
		const float fovx = renderer.getViewFrustum().getFOVx();
		const float viewWidth = radarRange * tanf(0.5f * fovx);
		glBegin(GL_LINE_STRIP);
		glVertex2f(-viewWidth, radarRange);
		glVertex2f(0.0f, 0.0f);
		glVertex2f(viewWidth, radarRange);
		glEnd();
	}

    // transform to the observer's viewpoint
    glPushMatrix();
    glRotatef((float)(90.0 - myAngle * 180.0 / M_PI), 0.0f, 0.0f,
 1.0f);
    glPushMatrix();
    glTranslatef(-myPos[0], -myPos[1], 0.0f);

    if (useTankModels) {
      // new modelview transform requires repositioning
      renderer.setupSun();
    }

    // setup the blending function
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // draw the buildings
    renderObstacles(fastRadar, radarRange);

    // antialiasing on for lines and points unless we're multisampling,
    // in which case it's automatic and smoothing makes them look
 worse.
    if (smooth) {
      glEnable(GL_BLEND);
      glEnable(GL_LINE_SMOOTH);
      glEnable(GL_POINT_SMOOTH);
    }

    // draw my shots
    int maxShots = world->getMaxShots();
    int i;
    float muzzleHeight = BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT);
    for (i = 0; i < maxShots; i++) {
      const ShotPath* shot = myTank->getShot(i);
      if (shot) {
	const float cs = colorScale(shot->getPosition()[2], muzzleHeight);
	glColor3f(1.0f * cs, 1.0f * cs, 1.0f * cs);
	shot->radarRender();
      }
    }
	FiringInfo myInfo(*myTank, 0);
if (myInfo.flagType != Flags::ShockWave) {
if (!BZDB.isTrue(StateDatabase::BZDB_SHOTSKEEPVERTICALV))
 myInfo.shot.vel[2] = 0.0f;
myInfo.flagType = Flags::Laser;
myInfo.lifetime /= 100;
const float cs = colorScale(myInfo.shot.pos[2], muzzleHeight);
glColor3f(1.0f * cs, 1.0f * cs, 1.0f * cs);
LocalShotPath myPath(myInfo);
myPath.radarRender();
}
//test
    //draw world weapon shots
    WorldPlayer *worldWeapons = World::getWorld()->getWorldWeapons();
    maxShots = worldWeapons->getMaxShots();
    for (i = 0; i < maxShots; i++) {
      const ShotPath* shot = worldWeapons->getShot(i);
      if (shot) {
	const float cs = colorScale(shot->getPosition()[2], muzzleHeight);
	glColor3f(1.0f * cs, 1.0f * cs, 1.0f * cs);
	shot->radarRender();
      }
    }

    // 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
    // but on systems that don't do correct filtering of endpoints
    // not doing it makes (half) the endpoints jump wildly.
    const int curMaxPlayers = world->getCurMaxPlayers();
    for (i = 0; i < curMaxPlayers; i++) {
      RemotePlayer* player = world->getPlayer(i);
      if (!player) {
	continue;
      }
      if (!player->isAlive() &&
	  (!useTankModels || !observer || !player->isExploding())) {
	continue;
      }

      const float* position = player->getPosition();

      if (player->getFlag() != Flags::Null) {
	glColor3fv(player->getFlag()->getColor());
	drawFlagOnTank(position);
      }

      if (player->isPaused() || player->isNotResponding()) {
	const float dimfactor = 0.4f;

	const float *color;
	if (myTank->getFlag() == Flags::Colorblindness) {
	  color = Team::getRadarColor(RogueTeam,rabbitMode);
	} else {
	  color = Team::getRadarColor(player->getTeam(),rabbitMode);
	}

	float dimmedcolor[3];
	dimmedcolor[0] = color[0] * dimfactor;
	dimmedcolor[1] = color[1] * dimfactor;
	dimmedcolor[2] = color[2] * dimfactor;
	glColor3fv(dimmedcolor);
      } else {
	glColor3fv(Team::getRadarColor(myTank->getFlag() ==
			     Flags::Colorblindness ? RogueTeam : player->getTeam(),
 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);
      }
    }

    bool coloredShot = BZDB.isTrue("coloredradarshots");
    // draw other tanks' shells
    bool iSeeAll = myTank && (myTank->getFlag() == Flags::Seer);
    maxShots = World::getWorld()->getMaxShots();
    for (i = 0; i < curMaxPlayers; i++) {
      RemotePlayer* player = world->getPlayer(i);
      if (!player) continue;
      for (int j = 0; j < maxShots; j++) {
	const ShotPath* shot = player->getShot(j);
	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);
	    const float cs = colorScale(shot->getPosition()[2], muzzleHeight);
	    glColor3f(shotcolor[0] * cs, shotcolor[1] * cs, shotcolor[2] *
 cs);
	  } else {
	    glColor3f(1.0f, 1.0f, 1.0f);
	  }
	  shot->radarRender();
	}
      }
    }

    // draw flags not on tanks.
    // 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");
    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
      if (flag.status == FlagNoExist || flag.status == FlagOnTank)
	continue;
      // don't draw normal flags if we aren't supposed to
      if (flag.type->flagTeam == NoTeam && !drawNormalFlags)
	continue;
      // Flags change color by height
      const float cs = colorScale(flag.position[2], muzzleHeight);
      const float *flagcolor = flag.type->getColor();
      glColor3f(flagcolor[0] * cs, flagcolor[1] * cs, flagcolor[2] *
 cs);
      drawFlag(flag.position);
    }
    // draw antidote flag
    const float* antidotePos =
		LocalPlayer::getMyTank()->getAntidoteLocation();
    if (antidotePos) {
      glColor3f(1.0f, 1.0f, 0.0f);
      drawFlag(antidotePos);
    }

    // draw these markers above all others always centered
    glPopMatrix();

    // north marker
    GLfloat ns = 0.05f * radarRange, ny = 0.9f * radarRange;
    glColor3f(1.0f, 1.0f, 1.0f);
    glBegin(GL_LINE_STRIP);
    glVertex2f(-ns, ny - ns);
    glVertex2f(-ns, ny + ns);
    glVertex2f(ns, ny - ns);
    glVertex2f(ns, ny + ns);
    glEnd();

    // always up
    glPopMatrix();

    // forward tick
    glBegin(GL_LINES);
    glVertex2f(0.0f, radarRange - ps);
    glVertex2f(0.0f, radarRange - 4.0f * ps);
    glEnd();

    if (!observer) {
      // revert to the centered transformation
      glRotatef((float)(90.0 - myAngle * 180.0 / M_PI), 0.0f, 0.0f,
 1.0f);
      glTranslatef(-myPos[0], -myPos[1], 0.0f);

      // my flag
      if (myTank->getFlag() != Flags::Null) {
	glColor3fv(myTank->getFlag()->getColor());
	drawFlagOnTank(myPos);
      }

      // my tank
      glColor3f(1.0f, 1.0f, 1.0f);
      drawTank(myPos, myTank);
    }

    if (dimming > 0.0f) {
      if (!smooth) {
	glEnable(GL_BLEND);
      }
      // darken the entire radar if we're dimmed
      // we're drawing positively, so dimming is actually an opacity
      glColor4f(0.0f, 0.0f, 0.0f, 1.0f - dimming);
      glRectf(-radarRange, -radarRange, +radarRange, +radarRange);
    }
    glDisable(GL_BLEND);
    glDisable(GL_LINE_SMOOTH);
    glDisable(GL_POINT_SMOOTH);
  }

  // restore GL state
  glPopMatrix();

  triangleCount = RenderNode::getTriangleCount();
}


float RadarRenderer::colorScale(const float z, const float h)
{
  float scaleColor;
  if (BZDBCache::radarStyle > 0) {
    const LocalPlayer* myTank = LocalPlayer::getMyTank();

    // Scale color so that objects that are close to tank's level are
 opaque
    const float zTank = myTank->getPosition()[2];

    if (zTank > (z + h))
      scaleColor = 1.0f - (zTank - (z + h)) / colorFactor;
    else if (zTank < z)
      scaleColor = 1.0f - (z - zTank) / colorFactor;
    else
      scaleColor = 1.0f;

    // Don't fade all the way
    if (scaleColor < 0.35f)
      scaleColor = 0.35f;
  } else {
    scaleColor = 1.0f;
  }

  return scaleColor;
}


float RadarRenderer::transScale(const float z, const float h)
{
  float scaleColor;
  const LocalPlayer* myTank = LocalPlayer::getMyTank();

  // Scale color so that objects that are close to tank's level are
 opaque
  const float zTank = myTank->getPosition()[2];
  if (zTank > (z + h))
    scaleColor = 1.0f - (zTank - (z + h)) / colorFactor;
  else if (zTank < z)
    scaleColor = 1.0f - (z - zTank) / colorFactor;
  else
    scaleColor = 1.0f;

  if (scaleColor < 0.5f)
    scaleColor = 0.5f;

  return scaleColor;
}


void RadarRenderer::renderObstacles(bool fastRadar, float _range)
{
  if (smooth) {
    glEnable(GL_BLEND);
    glEnable(GL_LINE_SMOOTH);
  }

  // draw the walls
  renderWalls();

  // draw the boxes, pyramids, and meshes
  if (!fastRadar) {
    renderBoxPyrMesh();
  } else {
    renderBoxPyrMeshFast(_range);
  }

  // draw the team bases and teleporters
  renderBasesAndTeles();

  if (smooth) {
    glDisable(GL_BLEND);
    glDisable(GL_LINE_SMOOTH);
  }

  return;
}


void RadarRenderer::renderWalls()
{
  const ObstacleList& walls = OBSTACLEMGR.getWalls();
  int count = walls.size();
  glColor3f(0.25f, 0.5f, 0.5f);
  glBegin(GL_LINES);
  for (int i = 0; i < count; i++) {
    const WallObstacle& wall = *((const WallObstacle*) walls[i]);
    const float wid = wall.getBreadth();
    const float c   = wid * cosf(wall.getRotation());
    const float s   = wid * sinf(wall.getRotation());
    const float* pos = wall.getPosition();
    glVertex2f(pos[0] - s, pos[1] + c);
    glVertex2f(pos[0] + s, pos[1] - c);
  }
  glEnd();

  return;
}


void RadarRenderer::renderBoxPyrMeshFast(float _range)
{
  // FIXME - This is hack code at the moment, but even when
  //	 rendering the full world, it draws the aztec map
  //	 3X faster (the culling algo is actually slows us
  //	 down in that case)
  //	   - need a better default gradient texture
  //	     (better colors, and tied in to show max jump height?)
  //	   - build a procedural texture if default is missing
  //	   - use a GL_TEXTURE_1D
  //	   - setup the octree to return Z sorted elements (partially done)
  //	   - add a renderClass() member to SceneNode (also for coloring)
  //	   - also add a renderShadow() member (they don't need sorting,
  //	     and if you don't have double-buffering, you shouldn't be
  //	     using shadows)
  //	   - vertex shaders would be faster
  //	   - it would probably be a better approach to attach a radar
  //	     rendering object to each obstacle... no time

  // get the texture
  int gradientTexId = -1;
  TextureManager &tm = TextureManager::instance();
  gradientTexId = tm.getTextureID("radar", false);

  // safety: no texture, no service
  if (gradientTexId < 0) {
    renderBoxPyrMesh();
    return;
  }

  // GL state
  OpenGLGStateBuilder gb;
  gb.setTexture(gradientTexId);
  gb.setShading(GL_FLAT);
  gb.setCulling(GL_BACK);
  gb.setBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  OpenGLGState gs = gb.getState();
  gs.setState();

  // disable the unused arrays
  glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);

  // now that the texture is bound, setup the clamp mode
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);

  // do this after the GState setting
  if (smooth) {
    glEnable(GL_POLYGON_SMOOTH);
  }

  // setup the texturing mapping
  const float hf = 128.0f; // height factor, goes from 0.0 to 1.0 in
 texcoords
  const float vfz = RENDERER.getViewFrustum().getEye()[2];
  const GLfloat plane[4] =
    { 0.0f, 0.0f, (1.0f / hf), (((hf * 0.5f) - vfz) / hf) };
  glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
  glTexGenfv(GL_S, GL_EYE_PLANE, plane);

  // setup texture generation
  glEnable(GL_TEXTURE_GEN_S);

  // set the color
  glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

//  if (!BZDB.isTrue("visualRadar")) {
    ViewFrustum radarClipper;
    radarClipper.setOrthoPlanes(RENDERER.getViewFrustum(), _range,
 _range);
    RENDERER.getSceneDatabase()->renderRadarNodes(radarClipper);
//  } else {
//  
  RENDERER.getSceneDatabase()->renderRadarNodes(RENDERER.getViewFrustum());
//  }

  // restore texture generation
  glDisable(GL_TEXTURE_GEN_S);

  OpenGLGState::resetState();

  // re-enable the arrays
  glEnableClientState(GL_NORMAL_ARRAY);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);

  // do this after the GState setting
  if (smooth) {
    glEnable(GL_BLEND);
    glEnable(GL_LINE_SMOOTH);
    glDisable(GL_POLYGON_SMOOTH);
  }

  return;
}


void RadarRenderer::renderBoxPyrMesh()
{
  int i;

  const bool enhanced = (BZDBCache::radarStyle > 0);

  if (!smooth) {
    // smoothing has blending disabled
    if (enhanced) {
      glEnable(GL_BLEND); // always blend the polygons if we're
 enhanced
    }
  } else {
    // smoothing has blending enabled
    if (!enhanced) {
      glDisable(GL_BLEND); // don't blend the polygons if we're not
 enhanced
    }
  }

  // draw box buildings.
  const ObstacleList& boxes = OBSTACLEMGR.getBoxes();
  int count = boxes.size();
  glBegin(GL_QUADS);
  for (i = 0; i < count; i++) {
    const BoxBuilding& box = *((const BoxBuilding*) boxes[i]);
    if (box.isInvisible())
      continue;
    const float z = box.getPosition()[2];
    const float bh = box.getHeight();
    const float cs = colorScale(z, bh);
    glColor4f(0.25f * cs, 0.5f * cs, 0.5f * cs, transScale(z, bh));
    const float c = cosf(box.getRotation());
    const float s = sinf(box.getRotation());
    const float wx = c * box.getWidth(), wy = s * box.getWidth();
    const float hx = -s * box.getBreadth(), hy = c * box.getBreadth();
    const float* pos = box.getPosition();
    glVertex2f(pos[0] - wx - hx, pos[1] - wy - hy);
    glVertex2f(pos[0] + wx - hx, pos[1] + wy - hy);
    glVertex2f(pos[0] + wx + hx, pos[1] + wy + hy);
    glVertex2f(pos[0] - wx + hx, pos[1] - wy + hy);
  }
  glEnd();

  // draw pyramid buildings
  const ObstacleList& pyramids = OBSTACLEMGR.getPyrs();
  count = pyramids.size();
  glBegin(GL_QUADS);
  for (i = 0; i < count; i++) {
    const PyramidBuilding& pyr = *((const PyramidBuilding*)
 pyramids[i]);
    const float z = pyr.getPosition()[2];
    const float bh = pyr.getHeight();
    const float cs = colorScale(z, bh);
    glColor4f(0.25f * cs, 0.5f * cs, 0.5f * cs, transScale(z, bh));
    const float c = cosf(pyr.getRotation());
    const float s = sinf(pyr.getRotation());
    const float wx = c * pyr.getWidth(), wy = s * pyr.getWidth();
    const float hx = -s * pyr.getBreadth(), hy = c * pyr.getBreadth();
    const float* pos = pyr.getPosition();
    glVertex2f(pos[0] - wx - hx, pos[1] - wy - hy);
    glVertex2f(pos[0] + wx - hx, pos[1] + wy - hy);
    glVertex2f(pos[0] + wx + hx, pos[1] + wy + hy);
    glVertex2f(pos[0] - wx + hx, pos[1] - wy + hy);
  }
  glEnd();

  // draw mesh obstacles
  if (smooth) {
    glEnable(GL_POLYGON_SMOOTH);
  }
  if (!enhanced) {
    glDisable(GL_CULL_FACE);
  }
  const ObstacleList& meshes = OBSTACLEMGR.getMeshes();
  count = meshes.size();
  for (i = 0; i < count; i++) {
    const MeshObstacle* mesh = (const MeshObstacle*) meshes[i];
    int faces = mesh->getFaceCount();

    for (int f = 0; f < faces; f++) {
      const MeshFace* face = mesh->getFace(f);
      if (enhanced) {
	if (face->getPlane()[2] <= 0.0f) {
	  continue;
	}
	const BzMaterial* bzmat = face->getMaterial();
	if ((bzmat != NULL) && bzmat->getNoRadar()) {
	  continue;
	}
      }
      const float z = face->getPosition()[2];
      const float bh = face->getHeight();
      const float cs = colorScale(z, bh);
      // draw death faces with a soupcon of red
      const PhysicsDriver* phydrv =
 PHYDRVMGR.getDriver(face->getPhysicsDriver());
      if ((phydrv != NULL) && phydrv->getIsDeath()) {
	glColor4f(0.75f * cs, 0.25f * cs, 0.25f * cs, transScale(z, bh));
      } else {
	glColor4f(0.25f * cs, 0.5f * cs, 0.5f * cs, transScale(z, bh));
      }
      // draw the face as a triangle fan
      int vertexCount = face->getVertexCount();
      glBegin(GL_TRIANGLE_FAN);
      for (int v = 0; v < vertexCount; v++) {
	const float* pos = face->getVertex(v);
	glVertex2f(pos[0], pos[1]);
      }
      glEnd();
    }
  }
  if (!enhanced) {
    glEnable(GL_CULL_FACE);
  }
  if (smooth) {
    glDisable(GL_POLYGON_SMOOTH);
  }

  // NOTE: revert from the enhanced setting
  if (enhanced && !smooth) {
    glDisable(GL_BLEND);
  }

  // now draw antialiased outlines around the polygons
  if (smooth) {
    glEnable(GL_BLEND); // NOTE: revert from the enhanced setting
    count = boxes.size();
    for (i = 0; i < count; i++) {
      const BoxBuilding& box = *((const BoxBuilding*) boxes[i]);
      if (box.isInvisible())
	continue;
      const float z = box.getPosition()[2];
      const float bh = box.getHeight();
      const float cs = colorScale(z, bh);
      glColor4f(0.25f * cs, 0.5f * cs, 0.5f * cs, transScale(z, bh));
      const float c = cosf(box.getRotation());
      const float s = sinf(box.getRotation());
      const float wx = c * box.getWidth(), wy = s * box.getWidth();
      const float hx = -s * box.getBreadth(), hy = c *
 box.getBreadth();
      const float* pos = box.getPosition();
      glBegin(GL_LINE_LOOP);
      glVertex2f(pos[0] - wx - hx, pos[1] - wy - hy);
      glVertex2f(pos[0] + wx - hx, pos[1] + wy - hy);
      glVertex2f(pos[0] + wx + hx, pos[1] + wy + hy);
      glVertex2f(pos[0] - wx + hx, pos[1] - wy + hy);
      glEnd();
    }

    count = pyramids.size();
    for (i = 0; i < count; i++) {
      const PyramidBuilding& pyr = *((const PyramidBuilding*)
 pyramids[i]);
      const float z = pyr.getPosition()[2];
      const float bh = pyr.getHeight();
      const float cs = colorScale(z, bh);
      glColor4f(0.25f * cs, 0.5f * cs, 0.5f * cs, transScale(z, bh));
      const float c = cosf(pyr.getRotation());
      const float s = sinf(pyr.getRotation());
      const float wx = c * pyr.getWidth(), wy = s * pyr.getWidth();
      const float hx = -s * pyr.getBreadth(), hy = c *
 pyr.getBreadth();
      const float* pos = pyr.getPosition();
      glBegin(GL_LINE_LOOP);
      glVertex2f(pos[0] - wx - hx, pos[1] - wy - hy);
      glVertex2f(pos[0] + wx - hx, pos[1] + wy - hy);
      glVertex2f(pos[0] + wx + hx, pos[1] + wy + hy);
      glVertex2f(pos[0] - wx + hx, pos[1] - wy + hy);
      glEnd();
    }
  }

  return;
}


void RadarRenderer::renderBasesAndTeles()
{
  int i;

  // draw team bases
  if(world->allowTeamFlags()) {
    for(i = 1; i < NumTeams; i++) {
      for (int j = 0;;j++) {
	const float *base = world->getBase(i, j);
	if (base == NULL)
	  break;
	glColor3fv(Team::getRadarColor(TeamColor(i)));
	glBegin(GL_LINE_LOOP);
	const float beta = atan2f(base[5], base[4]);
	const float r = hypotf(base[4], base[5]);
	glVertex2f(base[0] + r * cosf(base[3] + beta),
		   base[1] + r * sinf(base[3] + beta));
	glVertex2f(base[0] + r * cosf((float)(base[3] - beta + M_PI)),
		   base[1] + r * sinf((float)(base[3] - beta + M_PI)));
	glVertex2f(base[0] + r * cosf((float)(base[3] + beta + M_PI)),
		   base[1] + r * sinf((float)(base[3] + beta + M_PI)));
	glVertex2f(base[0] + r * cosf(base[3] - beta),
		   base[1] + r * sinf(base[3] - beta));
	glEnd();
      }
    }
  }

  // draw teleporters.  teleporters are pretty thin so use lines
  // (which, if longer than a pixel, are guaranteed to draw something;
  // not so for a polygon).  just in case the system doesn't correctly
  // filter the ends of line segments, we'll draw the line in each
  // direction (which degrades the antialiasing).  Newport graphics
  // is one system that doesn't do correct filtering.
  const ObstacleList& teleporters = OBSTACLEMGR.getTeles();
  int count = teleporters.size();
  glColor3f(1.0f, 1.0f, 0.25f);
  glBegin(GL_LINES);
  for (i = 0; i < count; i++) {
    const Teleporter & tele = *((const Teleporter *) teleporters[i]);
    if (tele.isHorizontal ()) {
      const float z = tele.getPosition ()[2];
      const float bh = tele.getHeight ();
      const float cs = colorScale (z, bh);
      glColor4f (1.0f * cs, 1.0f * cs, 0.25f * cs, transScale (z, bh));
      const float c = cosf (tele.getRotation ());
      const float s = sinf (tele.getRotation ());
      const float wx = c * tele.getWidth (), wy = s * tele.getWidth ();
      const float hx = -s * tele.getBreadth (), hy = c *
 tele.getBreadth ();
      const float *pos = tele.getPosition ();
      glVertex2f (pos[0] - wx - hx, pos[1] - wy - hy);
      glVertex2f (pos[0] + wx - hx, pos[1] + wy - hy);

      glVertex2f (pos[0] + wx - hx, pos[1] + wy - hy);
      glVertex2f (pos[0] + wx + hx, pos[1] + wy + hy);

      glVertex2f (pos[0] + wx + hx, pos[1] + wy + hy);
      glVertex2f (pos[0] - wx + hx, pos[1] - wy + hy);

      glVertex2f (pos[0] - wx + hx, pos[1] - wy + hy);
      glVertex2f (pos[0] - wx - hx, pos[1] - wy - hy);

      glVertex2f (pos[0] - wx - hx, pos[1] - wy - hy);
      glVertex2f (pos[0] - wx - hx, pos[1] - wy - hy);
    }
    else {
      const float z = tele.getPosition ()[2];
      const float bh = tele.getHeight ();
      const float cs = colorScale (z, bh);
      glColor4f (1.0f * cs, 1.0f * cs, 0.25f * cs, transScale (z, bh));
      const float tw = tele.getBreadth ();
      const float c = tw * cosf (tele.getRotation ());
      const float s = tw * sinf (tele.getRotation ());
      const float *pos = tele.getPosition ();
      glVertex2f (pos[0] - s, pos[1] + c);
      glVertex2f (pos[0] + s, pos[1] - c);
      glVertex2f (pos[0] + s, pos[1] - c);
      glVertex2f (pos[0] - s, pos[1] + c);
    }
  }
  glEnd();

  return;
}


int RadarRenderer::getFrameTriangleCount() const
{
  return triangleCount;
}


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


Plain Text Attachment [ Scan and Save to Computer ]

/* 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.
 */

/* interface header */
#include "SegmentedShotStrategy.h"

/* system implementation headers */
#include <assert.h>

/* common implementation headers */
#include "TextureManager.h"
#include "Intersect.h"
#include "BZDBCache.h"
#include "WallObstacle.h"

/* local implementation headers */
#include "sound.h"
#include "LocalPlayer.h"
#include "World.h"
#include "effectsRenderer.h"
#include "playing.h"

SegmentedShotStrategy::SegmentedShotStrategy(ShotPath* _path, bool
 useSuperTexture, bool faint) :
				ShotStrategy(_path)
{
  // initialize times
  prevTime = getPath().getStartTime();
  lastTime = currentTime = prevTime;

  // start at first segment
  lastSegment = segment = 0;

  // get team
  if (_path->getPlayer() == ServerPlayer) {
    TeamColor tmpTeam = _path->getFiringInfo().shot.team;
    team = (tmpTeam < RogueTeam) ? RogueTeam :
	   (tmpTeam > RabbitTeam) ? RogueTeam : tmpTeam;
  } else {
    Player* p = lookupPlayer(_path->getPlayer());
    team = p ? p->getTeam() : RogueTeam;
  }

  // initialize scene nodes
  boltSceneNode = new BoltSceneNode(_path->getPosition());

  bool rabbitMode = World::getWorld()->allowRabbit();

  const float* c = Team::getRadarColor(team, rabbitMode);
  if (faint) {
    boltSceneNode->setColor(c[0], c[1], c[2], 0.2f);
    boltSceneNode->setTextureColor(1.0f, 1.0f, 1.0f, 0.3f);
  } else {
    boltSceneNode->setColor(c[0], c[1], c[2], 1.0f);
  }

  TextureManager &tm = TextureManager::instance();
  std::string imageName = Team::getImagePrefix(team);
  if (useSuperTexture)
    imageName += BZDB.get("superPrefix");
  imageName += BZDB.get("boltTexture");

  int texture = tm.getTextureID(imageName.c_str());
  if (texture >= 0)
    boltSceneNode->setTexture(texture);
}

SegmentedShotStrategy::~SegmentedShotStrategy()
{
  // free scene nodes
  delete boltSceneNode;
}

void			SegmentedShotStrategy::update(float dt)
{
  prevTime = currentTime;
  currentTime += dt;

  // see if we've moved to another segment
  const int numSegments = segments.size();
  if (segment < numSegments && segments[segment].end <= currentTime) {
    lastSegment = segment;
    while (segment < numSegments && segments[segment].end <=
 currentTime) {
      if (++segment < numSegments) {
	switch (segments[segment].reason) {
	  case ShotPathSegment::Ricochet:
	    {
	      // play ricochet sound.  ricochet of local player's shots
	      // are important, others are not.
	      const PlayerId myTankId = LocalPlayer::getMyTank()->getId();
	      const bool important = (getPath().getPlayer() == myTankId);
	      const float* pos = segments[segment].ray.getOrigin();
	      playWorldSound(SFX_RICOCHET, pos, important);

	      // this is fugly but it's what we do
	      float dir[3];
	      const float* newDir = segments[segment].ray.getDirection();
	      const float* oldDir = segments[segment - 1].ray.getDirection();
	      dir[0] = newDir[0] - oldDir[0];
	      dir[1] = newDir[1] - oldDir[1];
	      dir[2] = newDir[2] - oldDir[2];

	      float rots[2];
	      const float horiz = sqrtf((dir[0]*dir[0]) + (dir[1]*dir[1]));
	      rots[0] = atan2f(dir[1], dir[0]);
	      rots[1] = atan2f(dir[2], horiz);

	      EFFECTS.addRicoEffect( pos, rots);
	      break;
	    }
	  case ShotPathSegment::Boundary:
	    break;
	  default:
	    {
	      // this is fugly but it's what we do
	      float dir[3];
	      dir[0] = segments[segment].ray.getDirection()[0];
	      dir[1] = segments[segment].ray.getDirection()[1];
	      dir[2] = segments[segment].ray.getDirection()[2];

	      float rots[2];
	      const float horiz = sqrtf((dir[0]*dir[0]) + (dir[1]*dir[1]));
	      rots[0] = atan2f(dir[1], dir[0]);
	      rots[1] = atan2f(dir[2], horiz);

	      const float* pos = segments[segment].ray.getOrigin();
	      EFFECTS.addShotTeleportEffect( pos, rots);
	    }
	    break;
	}
      }
    }
  }

  // if ran out of segments then expire shot on next update
  if (segment == numSegments) {
    setExpiring();

    if (numSegments > 0) {
      ShotPathSegment &segm = segments[numSegments - 1];
      const float     *dir  = segm.ray.getDirection();
      const float speed = hypotf(dir[0], hypotf(dir[1], dir[2]));
      float pos[3];
      segm.ray.getPoint(float(segm.end - segm.start - 1.0 / speed),
 pos);
      /* NOTE -- comment out to not explode when shot expires */
      addShotExplosion(pos);
    }
  }

  // otherwise update position and velocity
  else {
    float p[3];
    segments[segment].ray.getPoint(float(currentTime -
 segments[segment].start), p);
    setPosition(p);
    setVelocity(segments[segment].ray.getDirection());
  }
}

void			SegmentedShotStrategy::setCurrentTime(const
						TimeKeeper& _currentTime)
{
  currentTime = _currentTime;
}

const TimeKeeper&	SegmentedShotStrategy::getLastTime() const
{
  return lastTime;
}

bool			SegmentedShotStrategy::isOverlapping(
				const float (*bbox1)[3],
				const float (*bbox2)[3]) const
{
  if (bbox1[1][0] < bbox2[0][0]) return false;
  if (bbox1[0][0] > bbox2[1][0]) return false;
  if (bbox1[1][1] < bbox2[0][1]) return false;
  if (bbox1[0][1] > bbox2[1][1]) return false;
  if (bbox1[1][2] < bbox2[0][2]) return false;
  if (bbox1[0][2] > bbox2[1][2]) return false;
  return true;
}

void			SegmentedShotStrategy::setCurrentSegment(int _segment)
{
  segment = _segment;
}

float			SegmentedShotStrategy::checkHit(const BaseLocalPlayer* tank,
							float position[3]) const
{
  float minTime = Infinity;
  // expired shot can't hit anything
  if (getPath().isExpired()) return minTime;

  // get tank radius
  float radius = tank->getRadius();
  const float radius2 = radius * radius;

  // tank is positioned from it's bottom so shift position up by
  // half a tank height.
  const float tankHeight = tank->getDimensions()[2];
  Ray tankLastMotionRaw = tank->getLastMotion();
  float lastTankPositionRaw[3];
  lastTankPositionRaw[0] = tankLastMotionRaw.getOrigin()[0];
  lastTankPositionRaw[1] = tankLastMotionRaw.getOrigin()[1];
  lastTankPositionRaw[2] = tankLastMotionRaw.getOrigin()[2] + 0.5f *
 tankHeight;
  Ray tankLastMotion(lastTankPositionRaw,
 tankLastMotionRaw.getDirection());

  // if bounding box of tank and entire shot doesn't overlap then no
 hit
  const float (*tankBBox)[3] = tank->getLastMotionBBox();
  if (!isOverlapping(bbox, tankBBox)) return minTime;

  float shotRadius = BZDB.eval(StateDatabase::BZDB_SHOTRADIUS);

  // check each segment in interval (prevTime,currentTime]
  const float dt = float(currentTime - prevTime);
  const int numSegments = segments.size();
  for (int i = lastSegment; i <= segment && i < numSegments; i++) {
    // can never hit your own first laser segment
    if (i == 0 && getPath().getFlag() == Flags::Laser &&
	getPath().getPlayer() == tank->getId())
      continue;

/*
    // skip segments that don't overlap in time with current interval
    if (segments[i].end <= prevTime) continue;
    if (currentTime <= segments[i].start) break;
*/

    // if shot segment and tank bboxes don't overlap then no hit
    const ShotPathSegment& s = segments[i];
    if (!isOverlapping(s.bbox, tankBBox)) continue;

    // construct relative shot ray:  origin and velocity relative to
    // my tank as a function of time (t=0 is start of the interval).
    Ray relativeRay(rayMinusRay(s.ray, float(prevTime - s.start),
 tankLastMotion, 0.0f));

    // get hit time
    float t;
    if (tank->getFlag() == Flags::Narrow) {
      // find closest approach to narrow box around tank.  width of box
      // is shell radius so you can actually hit narrow tank head on.
      static float tankBase[3] = { 0.0f, 0.0f, -0.5f * tankHeight };
      t = timeRayHitsBlock(relativeRay, tankBase, tank->getAngle(),
			0.5f * BZDBCache::tankLength, shotRadius, tankHeight);
    }
    else {
      // find time when shot hits sphere around tank
      t = rayAtDistanceFromOrigin(relativeRay, 0.99f * radius);
    }

    // short circuit if time is greater then smallest time so far
    if (t > minTime) continue;

    // make sure time falls within segment
    if (t < 0.0f || t > dt) continue;
    if (t > s.end - prevTime) continue;

    // check if shot hits tank -- get position at time t, see if in
 radius
    float closestPos[3];
    relativeRay.getPoint(t, closestPos);
    if (closestPos[0] * closestPos[0] +
	closestPos[1] * closestPos[1] +
	closestPos[2] * closestPos[2] < radius2) {
      // save best time so far
      minTime = t;

      // compute location of tank at time of hit
      float tankPos[3];
      tank->getLastMotion().getPoint(t, tankPos);

      // compute position of intersection
      position[0] = tankPos[0] + closestPos[0];
      position[1] = tankPos[1] + closestPos[1];
      position[2] = tankPos[2] + closestPos[2];
      //printf("%u:%u %u:%u\n", tank->getId().port,
 tank->getId().number, getPath().getPlayer().port, getPath().getPlayer().number);
    }
  }
  return minTime;
}

void			SegmentedShotStrategy::addShot(
				SceneDatabase* scene, bool colorblind)
{
  const ShotPath& shotPath = getPath();
  boltSceneNode->move(shotPath.getPosition(), shotPath.getVelocity());
  if (boltSceneNode->getColorblind() != colorblind) {
    boltSceneNode->setColorblind(colorblind);
    TeamColor currentTeam = colorblind ? RogueTeam : team;

    bool rabbitMode = World::getWorld()->allowRabbit();
    const float* c = Team::getRadarColor(currentTeam, rabbitMode);
    boltSceneNode->setColor(c[0], c[1], c[2]);

    TextureManager &tm = TextureManager::instance();
    std::string imageName = Team::getImagePrefix(currentTeam);
    imageName += BZDB.get("boltTexture");
    int texture = tm.getTextureID(imageName.c_str());
    if (texture >= 0)
      boltSceneNode->setTexture(texture);
  }
  scene->addDynamicNode(boltSceneNode);
}

void			SegmentedShotStrategy::radarRender() const
{
  const float *orig = getPath().getPosition();
  const int length = (int)BZDBCache::linedRadarShots;
  const int size   = (int)BZDBCache::sizedRadarShots;

  float shotTailLength = BZDB.eval(StateDatabase::BZDB_SHOTTAILLENGTH);

  // Display leading lines
  if (length > 0) {
    const float* vel = getPath().getVelocity();
    const float d = 1.0f / hypotf(vel[0], hypotf(vel[1], vel[2]));
    float dir[3];
    dir[0] = vel[0] * d * shotTailLength * length;
    dir[1] = vel[1] * d * shotTailLength * length;
    dir[2] = vel[2] * d * shotTailLength * length;
    glBegin(GL_LINES);
    glVertex2fv(orig);
    if (BZDBCache::leadingShotLine) {
      glVertex2f(orig[0] + dir[0], orig[1] + dir[1]);
    } else {
      glVertex2f(orig[0] - dir[0], orig[1] - dir[1]);
    }
    glEnd();

    // draw a "bright" bullet tip
    if (size > 0) {
      glColor3f(0.75, 0.75, 0.75);
      glPointSize((float)size);
      glBegin(GL_POINTS);
      glVertex2f(orig[0], orig[1]);
      glEnd();
      glPointSize(1.0f);
    }
  } else {
    if (size > 0) {
      // draw a sized bullet
      glPointSize((float)size);
      glBegin(GL_POINTS);
      glVertex2fv(orig);
      glEnd();
      glPointSize(1.0f);

    } else {
      // draw the tiny little bullet
      glBegin(GL_POINTS);
      glVertex2fv(orig);
      glEnd();
    }
  }

}

void			SegmentedShotStrategy::makeSegments(ObstacleEffect e)
{
  // compute segments of shot until total length of segments exceeds
 the
  // lifetime of the shot.
  const ShotPath &shotPath  = getPath();
  const float    *v	 = shotPath.getVelocity();
  TimeKeeper      startTime = shotPath.getStartTime();
  float timeLeft	    = shotPath.getLifetime();
  float	   minTime   = BZDB.eval(StateDatabase::BZDB_MUZZLEFRONT)
    / hypotf(v[0], hypotf(v[1], v[2]));

  // if all shots ricochet and obstacle effect is stop, then make it
 ricochet
  if (e == Stop && World::getWorld()->allShotsRicochet()) {
    e = Reflect;
  }

  // prepare first segment
  float o[3], d[3];
  d[0] = v[0];
  d[1] = v[1];
  d[2] = v[2];		// use v[2] to have jumping affect shot velocity
  o[0] = shotPath.getPosition()[0];
  o[1] = shotPath.getPosition()[1];
  o[2] = shotPath.getPosition()[2];

  segments.clear();
  ShotPathSegment::Reason reason = ShotPathSegment::Initial;
  int i;
  const int maxSegment = 100;
  float worldSize = BZDBCache::worldSize / 2.0f - 0.01f;
  for (i = 0; (i < maxSegment) && (timeLeft > Epsilon); i++) {
    // construct ray and find the first building, teleporter, or outer
 wall
    float o2[3];
    o2[0] = o[0] - minTime * d[0];
    o2[1] = o[1] - minTime * d[1];
    o2[2] = o[2] - minTime * d[2];

    // Sometime shot start outside world
    if (o2[0] <= -worldSize)
      o2[0] = -worldSize;
    if (o2[0] >= worldSize)
      o2[0] = worldSize;
    if (o2[1] <= -worldSize)
      o2[1] = -worldSize;
    if (o2[1] >= worldSize)
      o2[1] = worldSize;

    Ray r(o2, d);
    Ray rs(o, d);
    float t = timeLeft + minTime;
    int face;
    bool hitGround = getGround(r, Epsilon, t);
    Obstacle* building = (Obstacle*)((e != Through) ?
 getFirstBuilding(r, Epsilon, t) : NULL);
    const Teleporter* teleporter = getFirstTeleporter(r, Epsilon, t,
 face);
    t -= minTime;
    minTime = 0.0f;
    bool ignoreHit = false;

    // if hit outer wall with ricochet and hit is above top of wall
    // then ignore hit.
    if (!teleporter && building && (e == Reflect) &&
	(building->getType() == WallObstacle::getClassName()) &&
	((o[2] + t * d[2]) > building->getHeight())) {
      ignoreHit = true;
    }

    // construct next shot segment and add it to list
    TimeKeeper endTime(startTime);
    if (t < 0.0f) {
      endTime += Epsilon;
    } else {
      endTime += t;
    }
    ShotPathSegment segm(startTime, endTime, rs, reason);
    segments.push_back(segm);
    startTime = endTime;

    // used up this much time in segment
    if (t < 0.0f) {
      timeLeft -= Epsilon;
    } else {
      timeLeft -= t;
    }

    // check in reverse order to see what we hit first
    reason = ShotPathSegment::Through;
    if (ignoreHit) {
      // uh...ignore this.  usually used if you shoot over the boundary
 wall.
      // just move the point of origin and build the next segment
      o[0] += t * d[0];
      o[1] += t * d[1];
      o[2] += t * d[2];
      reason = ShotPathSegment::Boundary;
    } else if (teleporter) {
      // entered teleporter -- teleport it
      unsigned int seed = shotPath.getShotId() + i;
      int source = World::getWorld()->getTeleporter(teleporter, face);
      int target = World::getWorld()->getTeleportTarget(source, seed);

      int outFace;
      const Teleporter* outTeleporter =
			  World::getWorld()->getTeleporter(target, outFace);
      o[0] += t * d[0];
      o[1] += t * d[1];
      o[2] += t * d[2];
      teleporter->getPointWRT(*outTeleporter, face, outFace,
					    o, d, 0.0f, o, d, NULL);
      reason = ShotPathSegment::Teleport;
    }
    else if (building) {
      // hit building -- can bounce off or stop, buildings ignored for
 Through
      switch (e) {
	case Stop:
	  timeLeft = 0.0f;
	break;

      case Reflect: {
	// move origin to point of reflection
	o[0] += t * d[0];
	o[1] += t * d[1];
	o[2] += t * d[2];

	// reflect direction about normal to building
	float normal[3];
	building->get3DNormal(o, normal);
	reflect(d, normal);
	reason = ShotPathSegment::Ricochet;
	}
	break;

      case Through:
	assert(0);
      }
    }
    else if (hitGround)	// we hit the ground
    {
      switch (e) {
	case Stop:
	case Through:
	  timeLeft = 0.0f;
	  break;

	case Reflect: {
	  // move origin to point of reflection
	  o[0] += t * d[0];
	  o[1] += t * d[1];
	  o[2] += t * d[2];

	  // reflect direction about normal to ground
	  float normal[3];
	  normal[0] = 0.0f;
	  normal[1] = 0.0f;
	  normal[2] = 1.0f;
	  reflect(d, normal);
	  reason = ShotPathSegment::Ricochet;
	  break;
	}
      }
    }
  }
  lastTime = startTime;

  // make bounding box for entire path
  const int numSegments = segments.size();
  if (numSegments > 0) {
    const ShotPathSegment& firstSeg = segments[0];
    bbox[0][0] = firstSeg.bbox[0][0];
    bbox[0][1] = firstSeg.bbox[0][1];
    bbox[0][2] = firstSeg.bbox[0][2];
    bbox[1][0] = firstSeg.bbox[1][0];
    bbox[1][1] = firstSeg.bbox[1][1];
    bbox[1][2] = firstSeg.bbox[1][2];
    for (i = 1; i < numSegments; i++) {
      const ShotPathSegment& segm = segments[i];
      if (bbox[0][0] > segm.bbox[0][0]) bbox[0][0] = segm.bbox[0][0];
      if (bbox[1][0] < segm.bbox[1][0]) bbox[1][0] = segm.bbox[1][0];
      if (bbox[0][1] > segm.bbox[0][1]) bbox[0][1] = segm.bbox[0][1];
      if (bbox[1][1] < segm.bbox[1][1]) bbox[1][1] = segm.bbox[1][1];
      if (bbox[0][2] > segm.bbox[0][2]) bbox[0][2] = segm.bbox[0][2];
      if (bbox[1][2] < segm.bbox[1][2]) bbox[1][2] = segm.bbox[1][2];
    }
  } else {
    bbox[0][0] = bbox[1][0] = 0.0f;
    bbox[0][1] = bbox[1][1] = 0.0f;
    bbox[0][2] = bbox[1][2] = 0.0f;
  }
}

const std::vector<ShotPathSegment>&
	SegmentedShotStrategy::getSegments() const
{
  return segments;
}

//
// NormalShotStrategy
//

NormalShotStrategy::NormalShotStrategy(ShotPath* _path) :
  SegmentedShotStrategy(_path, false)
{
  // make segments
  makeSegments(Stop);
}

NormalShotStrategy::~NormalShotStrategy()
{
  // do nothing
}

//
// RapidFireStrategy
//

RapidFireStrategy::RapidFireStrategy(ShotPath* _path) :
  SegmentedShotStrategy(_path, false)
{
  // speed up shell and decrease lifetime
  FiringInfo& f = getFiringInfo(_path);
  f.lifetime *= BZDB.eval(StateDatabase::BZDB_RFIREADLIFE);
  float fireAdVel = BZDB.eval(StateDatabase::BZDB_RFIREADVEL);
  f.shot.vel[0] *= fireAdVel;
  f.shot.vel[1] *= fireAdVel;
  f.shot.vel[2] *= fireAdVel;

  // make segments
  makeSegments(Stop);
}

RapidFireStrategy::~RapidFireStrategy()
{
  // do nothing
}

//
// ThiefStrategy
//

ThiefStrategy::ThiefStrategy(ShotPath *_path) :
  SegmentedShotStrategy(_path, false),cumTime(0.0f)
{
  // speed up shell and decrease lifetime
  FiringInfo& f = getFiringInfo(_path);
  f.lifetime *= BZDB.eval(StateDatabase::BZDB_THIEFADLIFE);
  float thiefAdVel = BZDB.eval(StateDatabase::BZDB_THIEFADSHOTVEL);
  f.shot.vel[0] *= thiefAdVel;
  f.shot.vel[1] *= thiefAdVel;
  f.shot.vel[2] *= thiefAdVel;
  setReloadTime(_path->getReloadTime()
		/ BZDB.eval(StateDatabase::BZDB_THIEFADRATE));

  // make segments
  makeSegments(Stop);
  setCurrentTime(getLastTime());
  endTime = f.lifetime;

  // make thief scene nodes
  const int numSegments = getSegments().size();
  thiefNodes = new LaserSceneNode*[numSegments];

  TextureManager &tm = TextureManager::instance();
  int texture = tm.getTextureID("thief");

  for (int i = 0; i < numSegments; i++) {
    const ShotPathSegment& segm = getSegments()[i];
    const float t = float(segm.end - segm.start);
    const Ray& ray = segm.ray;
    const float* rawdir = ray.getDirection();
    float dir[3];
    dir[0] = t * rawdir[0];
    dir[1] = t * rawdir[1];
    dir[2] = t * rawdir[2];
    thiefNodes[i] = new LaserSceneNode(ray.getOrigin(), dir);
    if (texture >= 0)
      thiefNodes[i]->setTexture(texture);
  }
  setCurrentSegment(numSegments - 1);
}

ThiefStrategy::~ThiefStrategy()
{
  const int numSegments = getSegments().size();
  for (int i = 0; i < numSegments; i++)
    delete thiefNodes[i];
  delete[] thiefNodes;
}

void			ThiefStrategy::update(float dt)
{
  cumTime += dt;
  if (cumTime >= endTime) setExpired();
}

void			ThiefStrategy::addShot(SceneDatabase* scene, bool)
{
  // laser is so fast we always show every segment
  const int numSegments = getSegments().size();
  for (int i = 0; i < numSegments; i++)
    scene->addDynamicNode(thiefNodes[i]);
}

void			ThiefStrategy::radarRender() const
{
  // draw all segments
  const std::vector<ShotPathSegment>& segmts = getSegments();
  const int numSegments = segmts.size();
  glBegin(GL_LINES);
    for (int i = 0; i < numSegments; i++) {
      const ShotPathSegment& segm = segmts[i];
      const float* origin = segm.ray.getOrigin();
      const float* direction = segm.ray.getDirection();
      const float dt = float(segm.end - segm.start);
      glVertex2fv(origin);
      glVertex2f(origin[0] + dt * direction[0], origin[1] + dt *
 direction[1]);
    }
  glEnd();
}

bool			ThiefStrategy::isStoppedByHit() const
{
  return false;
}


//
// MachineGunStrategy
//

MachineGunStrategy::MachineGunStrategy(ShotPath* _path) :
  SegmentedShotStrategy(_path, false)
{
  // speed up shell and decrease lifetime
  FiringInfo& f = getFiringInfo(_path);
  f.lifetime *= BZDB.eval(StateDatabase::BZDB_MGUNADLIFE);
  float mgunAdVel = BZDB.eval(StateDatabase::BZDB_MGUNADVEL);
  f.shot.vel[0] *= mgunAdVel;
  f.shot.vel[1] *= mgunAdVel;
  f.shot.vel[2] *= mgunAdVel;
  setReloadTime(_path->getReloadTime()
		/ BZDB.eval(StateDatabase::BZDB_MGUNADRATE));

  // make segments
  makeSegments(Stop);
}

MachineGunStrategy::~MachineGunStrategy()
{
  // do nothing
}

//
// RicochetStrategy
//

RicochetStrategy::RicochetStrategy(ShotPath* _path) :
  SegmentedShotStrategy(_path, false)
{
  // make segments that bounce
  makeSegments(Reflect);
}

RicochetStrategy::~RicochetStrategy()
{
  // do nothing
}

//
// SuperBulletStrategy
//

SuperBulletStrategy::SuperBulletStrategy(ShotPath* _path) :
  SegmentedShotStrategy(_path, true)
{
  // make segments that go through buildings
  makeSegments(Through);
}

SuperBulletStrategy::~SuperBulletStrategy()
{
  // do nothing
}


PhantomBulletStrategy::PhantomBulletStrategy(ShotPath* _path) :
  SegmentedShotStrategy(_path, false,true)
{
  // make segments that go through buildings
  makeSegments(Through);
}

PhantomBulletStrategy::~PhantomBulletStrategy()
{
  // do nothing
}

//
// LaserStrategy
//

LaserStrategy::LaserStrategy(ShotPath* _path) :
  SegmentedShotStrategy(_path, false), cumTime(0.0f)
{
  // speed up shell and decrease lifetime
  FiringInfo& f = getFiringInfo(_path);
  f.lifetime *= BZDB.eval(StateDatabase::BZDB_LASERADLIFE);
  float laserAdVel = BZDB.eval(StateDatabase::BZDB_LASERADVEL);
  f.shot.vel[0] *= laserAdVel;
  f.shot.vel[1] *= laserAdVel;
  f.shot.vel[2] *= laserAdVel;
setReloadTime(_path->getReloadTime()
* BZDB.eval(StateDatabase::BZDB_LASERADLIFE));

  // make segments
  makeSegments(Stop);
  setCurrentTime(getLastTime());
  endTime = f.lifetime;

  // make laser scene nodes
  const int numSegments = getSegments().size();
  laserNodes = new LaserSceneNode*[numSegments];
  const LocalPlayer* myTank = LocalPlayer::getMyTank();
  TeamColor tmpTeam = (myTank->getFlag() == Flags::Colorblindness) ?
 RogueTeam : team;

  TextureManager &tm = TextureManager::instance();
  std::string imageName = Team::getImagePrefix(tmpTeam);
  imageName += BZDB.get("laserTexture");
  int texture = tm.getTextureID(imageName.c_str());

  for (int i = 0; i < numSegments; i++) {
    const ShotPathSegment& segm = getSegments()[i];
    const float t = float(segm.end - segm.start);
    const Ray& ray = segm.ray;
    const float* rawdir = ray.getDirection();
    float dir[3];
    dir[0] = t * rawdir[0];
    dir[1] = t * rawdir[1];
    dir[2] = t * rawdir[2];
    laserNodes[i] = new LaserSceneNode(ray.getOrigin(), dir);
    if (texture >= 0)
      laserNodes[i]->setTexture(texture);
  }
  setCurrentSegment(numSegments - 1);
}

LaserStrategy::~LaserStrategy()
{
  const int numSegments = getSegments().size();
  for (int i = 0; i < numSegments; i++)
    delete laserNodes[i];
  delete[] laserNodes;
}

void			LaserStrategy::update(float dt)
{
  cumTime += dt;
  if (cumTime >= endTime) setExpired();
}

void			LaserStrategy::addShot(SceneDatabase* scene, bool)
{
  // laser is so fast we always show every segment
  const int numSegments = getSegments().size();
  for (int i = 0; i < numSegments; i++)
    scene->addDynamicNode(laserNodes[i]);
}

void			LaserStrategy::radarRender() const
{
  // draw all segments
  const std::vector<ShotPathSegment>& segmts = getSegments();
  const int numSegments = segmts.size();
  glBegin(GL_LINES);
    for (int i = 0; i < numSegments; i++) {
      const ShotPathSegment& segm = segmts[i];
      const float* origin = segm.ray.getOrigin();
      const float* direction = segm.ray.getDirection();
      const float dt = float(segm.end - segm.start);
      glVertex2fv(origin);
      glVertex2f(origin[0] + dt * direction[0], origin[1] + dt *
 direction[1]);
    }
  glEnd();
}

bool			LaserStrategy::isStoppedByHit() const
{
  return false;
}

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

Plain Text Attachment [ Scan and Save to Computer ]

/* 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.
 */

/*
 * Encapsulates communication between local player and the server.
 */

#ifndef	BZF_SERVER_LINK_H
#define	BZF_SERVER_LINK_H

#include "common.h"

#include <string>
#ifdef HAVE_KRB5
	#ifdef MAXHOSTNAMELEN
		#undef MAXHOSTNAMELEN
	#endif

#endif

#include "global.h"
#include "Address.h"
#include "Protocol.h"
#include "ShotPath.h"
#include "Flag.h"

class ServerLink {
  public:
    enum State {
			Okay = 0,
			SocketError = 1,
			Rejected = 2,
			BadVersion = 3,
			Hungup = 4,		// only used by Winsock
			CrippledVersion = 5,
			Refused = 6
    };

    enum Abilities {
			Nothing = 0,
			CanDoUDP = 1,
			SendScripts = 2,
			SendTextures = 4,
			HasMessageLink = 8
    };

			ServerLink(const Address& serverAddress,
					int port = ServerPort);
			~ServerLink();

    State		getState() const;
    const std::string&	getRejectionMessage() { return rejectionMessage;
 }
    int			getSocket() const;	// file descriptor actually
    const PlayerId&	getId() const;
    const char*		getVersion() const;

    void		send(uint16_t code, uint16_t len, const void* msg);
    // if millisecondsToBlock < 0 then block forever
    int			read(uint16_t& code, uint16_t& len, void* msg,
						int millisecondsToBlock = 0);

    void		sendEnter(PlayerType, TeamColor,
				  const char* name, const char* email, const char* token);
    bool		readEnter(std::string& reason,
				  uint16_t& code, uint16_t& rejcode);

    void		sendCaptureFlag(TeamColor);
    void		sendGrabFlag(int flagIndex);
    void		sendDropFlag(const float* position);
#ifdef HAVE_KRB5
    void		sendKerberosTicket(const char      *principal,
		const krb5_data *ticket);
#endif
    void		sendKilled(const PlayerId&, int reason,
				   int shotId, const FlagType* flag, int phydrv);
  // FIXME -- This is very ugly, but required to build bzadmin with gcc
 2.9.5.
  //	  It should be changed to something cleaner.
#ifndef BUILDING_BZADMIN
    void		sendPlayerUpdate(Player*);
#endif
    void		sendBeginShot(const FiringInfo&);
    void		sendEndShot(const PlayerId&, int shotId, int reason);
    void		sendAlive();
    void		sendTeleport(int from, int to);
    void		sendTransferFlag(const PlayerId&, const PlayerId&);
    void		sendNewRabbit();
    void		sendPaused(bool paused);
    void		sendAutoPilot(bool autopilot);
    void		sendUDPlinkRequest();

    static ServerLink*	getServer(); // const
    static void		setServer(ServerLink*);
    void		enableOutboundUDP();
    void		confirmIncomingUDP();

  private:
    State		state;
    int			fd;

    struct sockaddr	usendaddr;
    int			urecvfd;
    struct sockaddr	urecvaddr; // the clients udp listen address
    bool		ulinkup;

    PlayerId		id;
    char		version[9];
    static ServerLink*	server;
    int			server_abilities;

    std::string	 rejectionMessage;

    int		 udpLength;
    char	       *udpBufferPtr;
    char		ubuf[MaxPacketLen];
};

#define SEND 1
#define RECEIVE 0

//
// ServerLink
//

inline ServerLink::State ServerLink::getState() const
{
  return state;
}

inline int		ServerLink::getSocket() const
{
  return fd;
}

inline const PlayerId&	ServerLink::getId() const
{
  return id;
}

inline const char*	ServerLink::getVersion() const
{
  return version;
}

#endif // BZF_SERVER_LINK_H

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


Plain Text Attachment [ Scan and Save to Computer ]

/* 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.
 */

// interface header
#include "ShotPath.h"

/* system headers */
#include <assert.h>

/* local implementation headers */
#include "SegmentedShotStrategy.h"
#include "GuidedMissleStrategy.h"
#include "ShockWaveStrategy.h"


//
// FiringInfo (with BaseLocalPlayer)
//

FiringInfo::FiringInfo(const BaseLocalPlayer& tank, int id)
{
  shot.player = tank.getId();
  shot.id = uint16_t(id);
  tank.getMuzzle(shot.pos);
  const float* dir = tank.getForward();
  const float* tankVel = tank.getVelocity();
  float shotSpeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED);
  shot.vel[0] = tankVel[0] + shotSpeed * dir[0];
  shot.vel[1] = tankVel[1] + shotSpeed * dir[1];
  shot.vel[2] = tankVel[2] + shotSpeed * dir[2];
  shot.dt = 0.0f;

  flagType = tank.getFlag();
  // wee bit o hack -- if phantom flag but not phantomized
  // the shot flag is normal -- otherwise FiringInfo will have
  // to be changed to add a real bitwise status variable
  if (tank.getFlag() == Flags::PhantomZone){
    flagType = Flags::Null;
  }
  lifetime = BZDB.eval(StateDatabase::BZDB_RELOADTIME);
}

//
// ShotPath
//

ShotPath::ShotPath(const FiringInfo& info) :
				firingInfo(info),
				reloadTime(BZDB.eval(StateDatabase::BZDB_RELOADTIME)),
				startTime(TimeKeeper::getTick()),
				currentTime(TimeKeeper::getTick()),
				expiring(false),
				expired(false)
{
  // eek!  a giant switch statement, how un-object-oriented!
  // each flag should be a flyweight object derived from a
  // base Flag class with a virtual makeShotStrategy() member.
  // just remember -- it's only a game.
  if (firingInfo.flagType->flagShot == NormalShot)
    strategy = new NormalShotStrategy(this);
  else {
    if (firingInfo.flagType == Flags::RapidFire)
      strategy = new RapidFireStrategy(this);
    else if (firingInfo.flagType == Flags::MachineGun)
      strategy = new MachineGunStrategy(this);
    else if (firingInfo.flagType == Flags::GuidedMissile)
      strategy = new GuidedMissileStrategy(this);
    else if (firingInfo.flagType == Flags::Laser)
      strategy = new LaserStrategy(this);
    else if (firingInfo.flagType == Flags::Ricochet)
      strategy = new RicochetStrategy(this);
    else if (firingInfo.flagType == Flags::SuperBullet)
      strategy = new SuperBulletStrategy(this);
    else if (firingInfo.flagType == Flags::ShockWave)
      strategy = new ShockWaveStrategy(this);
    else if (firingInfo.flagType == Flags::Thief)
      strategy = new ThiefStrategy(this);
    else if (firingInfo.flagType == Flags::PhantomZone)
      strategy = new PhantomBulletStrategy(this);
    else
      assert(0);    // shouldn't happen
  }
}

ShotPath::~ShotPath()
{
  delete strategy;
}

float			ShotPath::checkHit(const BaseLocalPlayer* player,
							float position[3]) const
{
  return strategy->checkHit(player, position);
}

bool			ShotPath::isStoppedByHit() const
{
  return strategy->isStoppedByHit();
}

void			ShotPath::addShot(SceneDatabase* scene,
						bool colorblind)
{
  strategy->addShot(scene, colorblind);
}

void			ShotPath::radarRender() const
{
  if (!isExpired()) strategy->radarRender();
}

void			ShotPath::updateShot(float dt)
{
  // get new time step and set current time
  currentTime += dt;

  // update shot
  if (!expired) {
    if (expiring) setExpired();
    else getStrategy()->update(dt);
  }
}

void			ShotPath::setReloadTime(float _reloadTime)
{
  reloadTime = _reloadTime;
}

void			ShotPath::setPosition(const float* p)
{
  firingInfo.shot.pos[0] = p[0];
  firingInfo.shot.pos[1] = p[1];
  firingInfo.shot.pos[2] = p[2];
}

void			ShotPath::setVelocity(const float* v)
{
  firingInfo.shot.vel[0] = v[0];
  firingInfo.shot.vel[1] = v[1];
  firingInfo.shot.vel[2] = v[2];
}

void			ShotPath::setExpiring()
{
  expiring = true;
}

void			ShotPath::setExpired()
{
  expiring = true;
  expired = true;
  getStrategy()->expire();
}

void			ShotPath::boostReloadTime(float dt)
{
  reloadTime += dt;
}

//
// LocalShotPath
//

LocalShotPath::LocalShotPath(const FiringInfo& info) :
				ShotPath(info)
{
  // do nothing
}

LocalShotPath::~LocalShotPath()
{
  // do nothing
}

void			LocalShotPath::update(float dt)
{
  getFiringInfo().shot.dt += dt;
  updateShot(dt);

  // send updates if necessary
  getStrategy()->sendUpdate(getFiringInfo());
}

//
// RemoteShotPath
//

RemoteShotPath::RemoteShotPath(const FiringInfo& info) :
				ShotPath(info)
{
  // do nothing
}

RemoteShotPath::~RemoteShotPath()
{
  // do nothing
}

void			RemoteShotPath::update(float dt)
{
  // update shot
  updateShot(dt);
}

void			RemoteShotPath::update(const ShotUpdate& shot,
				uint16_t code, void* msg)
{
  // update shot info
  getFiringInfo().shot = shot;

  // let the strategy see the message
  getStrategy()->readUpdate(code, msg);
}

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


Plain Text Attachment [ Scan and Save to Computer ]

/* 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.
 */

// interface header
#include "TargetingUtils.h"

// system headers
#include <math.h>

// common headers
#include "Ray.h"

// local headers
#include "ShotStrategy.h"

// These routines are 2 dimensional

float TargetingUtils::normalizeAngle(float ang)
{
  if (ang < -1.0f * M_PI) ang += (float)(2.0 * M_PI);
  if (ang > 1.0f * M_PI) ang -= (float)(2.0 * M_PI);
  return ang;
}

void TargetingUtils::getUnitVector( const float *src, const float
 *target, float unitVector[3] )
{
  unitVector[0] = target[0] - src[0];
  unitVector[1] = target[1] - src[1];
  unitVector[2] = 0.0f;

  float len = (float) sqrt(unitVector[0] * unitVector[0] +
			   unitVector[1] * unitVector[1]);

  if (len == 0.0f)
	  return;

  unitVector[0] /= len;
  unitVector[1] /= len;
}

void TargetingUtils::get3DUnitVector( const float *src, const float
 *target, float unitVector[3] )
{
  unitVector[0] = target[0] - src[0];
  unitVector[1] = target[1] - src[1];
  unitVector[2] = target[2] - src[1];

  float len = (float) sqrt(unitVector[0] * unitVector[0] +
			   unitVector[1] * unitVector[1] +
			     unitVector[2] * unitVector[2]);

  if (len == 0.0f)
	  return;

  unitVector[0] /= len;
  unitVector[1] /= len;
  unitVector[2] /= len;
}

float TargetingUtils::getTargetDistance( const float *src, const float
 *target )
{
  float vec[2];

  vec[0] = target[0] - src[0];
  vec[1] = target[1] - src[1];

  return (float) sqrt(vec[0] * vec[0] +
		      vec[1] * vec[1]);
}

float TargetingUtils::getTargetAzimuth( const float *src, const float
 *target )
{
  return atan2f((target[1] - src[1]), (target[0] - src[0]));
}

float TargetingUtils::getTargetRotation( const float startAzimuth,
 float targetAzimuth )
{
  float targetRotation = targetAzimuth - startAzimuth;
  if (targetRotation < -1.0f * M_PI) targetRotation += (float)(2.0 *
 M_PI);
  if (targetRotation > 1.0f * M_PI) targetRotation -= (float)(2.0 *
 M_PI);

  return targetRotation;
}

float TargetingUtils::getTargetAngleDifference( const float *src, float
 srcAzimuth, const float *target )
{
  float targetUnitVector[3];
  float srcUnitVector[3];

  getUnitVector(src, target, targetUnitVector);

  srcUnitVector[0] = cosf(srcAzimuth);
  srcUnitVector[1] = sinf(srcAzimuth);
  srcUnitVector[2] = 0.0f;

  return acos( targetUnitVector[0]*srcUnitVector[0] +
 targetUnitVector[1]*srcUnitVector[1] );
}

bool TargetingUtils::isLocationObscured( const float *src, const float
 *target )
{
  float dir[3];

  getUnitVector(src, target, dir);

  Ray tankRay( src, dir );
  float targetDistance = getTargetDistance(src, target);
  const Obstacle *building = ShotStrategy::getFirstBuilding(tankRay,
 -0.5f, targetDistance);
  return building != NULL;
}

float TargetingUtils::getOpenDistance( const float *src, const float
 azimuth )
{
  float t = MAXFLOAT; //Some constant?

  float dir[3] = { cosf(azimuth), sinf(azimuth), 0.0f };
  *((float *) &src[2]) += 0.1f; //Don't hit building because your
 sitting on one
  Ray tankRay( src, dir );
  *((float *) &src[2]) -= 0.1f;
  ShotStrategy::getFirstBuilding(tankRay, -0.5f, t);
  return t;
}

bool TargetingUtils::getFirstCollisionPoint( const float *src, const
 float *target, float *collisionPt )
{
  float t = MAXFLOAT;
  float dir[3];
  get3DUnitVector(src, target, dir);

  Ray tankRay( src, dir );
  const Obstacle *building = ShotStrategy::getFirstBuilding(tankRay,
 0.0f, t);
  if (building == NULL)
	  return false;

  collisionPt[0] = src[0] + dir[0] * t;
  collisionPt[1] = src[1] + dir[1] * t;
  collisionPt[2] = src[2] + dir[2] * t;
  return true;
}

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

/* 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"

/* interface header */
#include "Flag.h"

/* system implementation headers */
#include <math.h>
#include <string>

/* common implementation headers */
#include "Team.h"
#include "Pack.h"
#include "TextUtils.h"


int FlagType::flagCount = 0;
FlagSet *FlagType::flagSets = NULL;
const int FlagType::packSize = FlagPackSize;

static const char NullString[2] = { '\0', '\0' };

// Initialize flag description singletons in our Flags namespace
void* FlagType::unpack(void* buf, FlagType* &type)
{
  unsigned char abbv[3] = {0,0,0};
  buf = nboUnpackUByte(buf, abbv[0]);
  buf = nboUnpackUByte(buf, abbv[1]);
   FlagType *newFlag = Flag::getDescFromAbbreviation((const char
 *)abbv);

  if (Flag::getGoodFlags().find(type) == Flag::getGoodFlags().end())
if (Flag::getBadFlags().find(type) == Flag::getBadFlags().end())
    type = newFlag;
  if (type == Flags::Null)
    type = newFlag;
  if ((newFlag != Flags::PhantomZone) &&
      (newFlag != Flags::Null) &&
      (newFlag != type))
  {
    printf("set to %s from %s\n", abbv, type->flagAbbv);
    type = newFlag;
  }
  return buf;
}

namespace Flags {
  FlagType *Null;
  FlagType *RedTeam;
  FlagType *GreenTeam;
  FlagType *BlueTeam;
  FlagType *PurpleTeam;
  FlagType *Velocity;
  FlagType *QuickTurn;
  FlagType *OscillationOverthruster;
  FlagType *RapidFire;
  FlagType *MachineGun;
  FlagType *GuidedMissile;
  FlagType *Laser;
  FlagType *Ricochet;
  FlagType *SuperBullet;
  FlagType *InvisibleBullet;
  FlagType *Stealth;
  FlagType *Tiny;
  FlagType *Narrow;
  FlagType *Shield;
  FlagType *Steamroller;
  FlagType *ShockWave;
  FlagType *PhantomZone;
  FlagType *Genocide;
  FlagType *Jumping;
  FlagType *Identify;
  FlagType *Cloaking;
  FlagType *Useless;
  FlagType *Masquerade;
  FlagType *Seer;
  FlagType *Thief;
  FlagType *Burrow;
  FlagType *Wings;
  FlagType *Colorblindness;
  FlagType *Obesity;
  FlagType *LeftTurnOnly;
  FlagType *RightTurnOnly;
  FlagType *ForwardOnly;
  FlagType *ReverseOnly;
  FlagType *Momentum;
  FlagType *Blindness;
  FlagType *Jamming;
  FlagType *WideAngle;
  FlagType *NoJumping;
  FlagType *TriggerHappy;
  FlagType *ReverseControls;
  FlagType *Bouncy;
  FlagType *Agility;

  void init()
  {
    Null	= new FlagType( "", NullString, FlagNormal, NormalShot,
 FlagGood, NoTeam, NULL );

    RedTeam	= new FlagType( "Red Team", "R*", FlagNormal, NormalShot,
 FlagGood, ::RedTeam,
					    "If it's yours, prevent other teams from taking it.  If it's
 not take it to your base to capture it!" );
    GreenTeam	= new FlagType( "Green Team", "G*", FlagNormal,
 NormalShot, FlagGood, ::GreenTeam,
					    "If it's yours, prevent other teams from taking it.  If it's
 not take it to your base to capture it!" );
    BlueTeam	= new FlagType( "Blue Team", "B*", FlagNormal, NormalShot,
 FlagGood, ::BlueTeam,
					    "If it's yours, prevent other teams from taking it.  If it's
 not take it to your base to capture it!" );
    PurpleTeam	= new FlagType( "Purple Team", "P*", FlagNormal,
 NormalShot, FlagGood, ::PurpleTeam,
					    "If it's yours, prevent other teams from taking it.  If it's
 not take it to your base to capture it!" );
    Velocity	= new FlagType( "High Speed", "V", FlagUnstable,
 NormalShot, FlagGood, NoTeam,
					    "Tank moves faster.  Outrun bad guys." );
    QuickTurn	= new FlagType( "Quick Turn", "QT", FlagUnstable,
 NormalShot, FlagGood, NoTeam,
					    "Tank turns faster.  Good for dodging." );
    OscillationOverthruster	= new FlagType( "Oscillation Overthruster",
 "OO", FlagUnstable, NormalShot, FlagGood, NoTeam,
					    "Can drive through buildings.  Can't backup or shoot while
 inside." );
    RapidFire	= new FlagType( "Rapid Fire", "F", FlagUnstable,
 SpecialShot, FlagGood, NoTeam,
					    "Shoots more often.  Shells go faster but not as far." );
    MachineGun	= new FlagType( "Machine Gun", "MG", FlagUnstable,
 SpecialShot, FlagGood, NoTeam,
					    "Very fast reload and very short range." );
    GuidedMissile	= new FlagType( "Guided Missile", "GM", FlagUnstable,
 SpecialShot, FlagGood, NoTeam,
					    "Shots track a target.  Lock on with right button.  Can lock
 on or retarget after firing." );
    Laser	= new FlagType( "Laser", "L", FlagUnstable, SpecialShot,
 FlagGood, NoTeam,
					    "Shoots a laser.  Infinite speed and range but long reload
 time.");
    Ricochet	= new FlagType( "Ricochet", "R", FlagUnstable,
 SpecialShot, FlagGood, NoTeam,
					    "Shots bounce off walls.  Don't shoot yourself!" );
    SuperBullet	= new FlagType( "Super Bullet", "SB", FlagUnstable,
 SpecialShot, FlagGood, NoTeam,
					    "Shoots through buildings.  Can kill Phantom Zone." );
    InvisibleBullet	= new FlagType( "Invisible Bullet", "IB",
 FlagUnstable, NormalShot, FlagGood, NoTeam,
					    "Your shots don't appear on other radars.  Can still see them
 out window.");
    Stealth	= new FlagType( "Stealth", "ST", FlagUnstable, NormalShot,
 FlagGood, NoTeam,
					    "Tank is invisible on radar.  Shots are still visible.  Sneak
 up behind enemies!");
    Tiny	= new FlagType( "Tiny", "T", FlagUnstable, NormalShot,
 FlagGood, NoTeam,
					    "Tank is small and can get through small openings.  Very hard
 to hit." );
    Narrow	= new FlagType( "Narrow", "N", FlagUnstable, NormalShot,
 FlagGood, NoTeam,
					    "Tank is super thin.  Very hard to hit from front but is
 normal size from side.  Can get through small openings.");
    Shield	= new FlagType( "Shield", "SH", FlagUnstable, NormalShot,
 FlagGood, NoTeam,
					    "Getting hit only drops flag.  Flag flies an extra-long
 time.");
    Steamroller	= new FlagType( "Steamroller", "SR", FlagUnstable,
 NormalShot, FlagGood, NoTeam,
					    "Destroys tanks you touch but you have to get really close.");
    ShockWave	= new FlagType( "Shock Wave", "SW", FlagUnstable,
 SpecialShot, FlagGood, NoTeam,
					    "Firing destroys all tanks nearby.  Don't kill teammates!  Can
 kill tanks on/in buildings.");
    PhantomZone	= new FlagType( "Phantom Zone", "PZ", FlagUnstable,
 SpecialShot, FlagGood, NoTeam,
					    "Teleporting toggles Zoned effect.  Zoned tank can drive
 through buildings.  Zoned tank shoots Zoned bullets and can't be shot
 (except by superbullet, shock wave, and other Zoned tanks).");
    Genocide	= new FlagType( "Genocide", "G", FlagUnstable, NormalShot,
 FlagGood, NoTeam,
					    "Killing one tank kills that tank's whole team.");
    Jumping	= new FlagType( "Jumping", "JP", FlagUnstable, NormalShot,
 FlagGood, NoTeam,
					    "Tank can jump.  Use Tab key.  Can't steer in the air.");
    Identify	= new FlagType( "Identify", "ID", FlagUnstable,
 NormalShot, FlagGood, NoTeam,
					    "Identifies type of nearest flag.");
    Cloaking	= new FlagType( "Cloaking", "CL", FlagUnstable,
 NormalShot, FlagGood, NoTeam,
					    "Makes your tank invisible out-the-window.  Still visible on
 radar.");
    Useless	= new FlagType( "Useless", "US", FlagUnstable, NormalShot,
 FlagGood, NoTeam,
					    "You have found the useless flag. Use it wisely.");
    Masquerade	= new FlagType( "Masquerade", "MQ", FlagUnstable,
 NormalShot, FlagGood, NoTeam,
					    "In opponent's hud, you appear as a teammate.");
    Seer	= new FlagType( "Seer", "SE", FlagUnstable, NormalShot,
 FlagGood, NoTeam,
					    "See stealthed, cloaked and masquerading tanks as normal.");
    Thief	= new FlagType( "Thief", "TH", FlagUnstable, SpecialShot,
 FlagGood, NoTeam,
					    "Steal flags.  Small and fast but can't kill.");
    Burrow	= new FlagType( "Burrow", "BU", FlagUnstable, NormalShot,
 FlagGood, NoTeam,
					    "Tank burrows underground, impervious to normal shots, but can
 be steamrolled by anyone!");
    Wings	= new FlagType( "Wings", "WG", FlagUnstable, NormalShot,
 FlagGood, NoTeam,
					    "Tank can drive in air.");
    Agility	= new FlagType( "Agility", "A", FlagUnstable, NormalShot,
 FlagGood, NoTeam,
					    "Tank is quick and nimble making it easier to dodge.");
    ReverseControls	= new FlagType( "ReverseControls", "RC",
 FlagSticky, NormalShot, FlagBad, NoTeam,
					    "Tank driving controls are reversed.");
    Colorblindness	= new FlagType( "Colorblindness", "CB", FlagSticky,
 NormalShot, FlagBad, NoTeam,
					    "Can't tell team colors.  Don't shoot teammates!");
    Obesity	= new FlagType( "Obesity", "O", FlagSticky, NormalShot,
 FlagBad, NoTeam,
					    "Tank becomes very large.  Can't fit through teleporters.");
    LeftTurnOnly	= new FlagType( "Left Turn Only", "LT", FlagSticky,
 NormalShot, FlagBad, NoTeam,
					    "Can't turn right.");
    RightTurnOnly	= new FlagType( "Right Turn Only", "RT", FlagSticky,
 NormalShot, FlagBad, NoTeam,
					    "Can't turn left.");
    ForwardOnly	= new FlagType( "Forward Only", "FO", FlagSticky,
 NormalShot, FlagBad, NoTeam,
					    "Can't drive in reverse.");
    ReverseOnly	= new FlagType( "ReverseOnly", "RO", FlagSticky,
 NormalShot, FlagBad, NoTeam,
					    "Can't drive forward.");
    Momentum	= new FlagType( "Momentum", "M", FlagSticky, NormalShot,
 FlagBad, NoTeam,
					    "Tank has inertia.  Acceleration is limited.");
    Blindness	= new FlagType( "Blindness", "B", FlagSticky, NormalShot,
 FlagBad, NoTeam,
					    "Can't see out window.  Radar still works.");
    Jamming	= new FlagType( "Jamming", "JM", FlagSticky, NormalShot,
 FlagBad, NoTeam,
					    "Radar doesn't work.  Can still see.");
    WideAngle	= new FlagType( "Wide Angle", "WA", FlagSticky,
 NormalShot, FlagBad, NoTeam,
					    "Fish-eye lens distorts view.");
    NoJumping	= new FlagType( "No Jumping", "NJ", FlagSticky,
 NormalShot, FlagBad, NoTeam,
					    "Tank can't jump.");
    TriggerHappy	= new FlagType( "Trigger Happy", "TR", FlagSticky,
 NormalShot, FlagBad, NoTeam,
					    "Tank can't stop firing.");
    Bouncy	= new FlagType( "Bouncy", "BY", FlagSticky, NormalShot,
 FlagBad, NoTeam,
					    "Tank can't stop bouncing.");
  }

  void kill()
  {
    delete Null;
    delete RedTeam;
    delete GreenTeam;
    delete BlueTeam;
    delete PurpleTeam;
    delete Velocity;
    delete QuickTurn;
    delete OscillationOverthruster;
    delete RapidFire;
    delete MachineGun;
    delete GuidedMissile;
    delete Laser;
    delete Ricochet;
    delete SuperBullet;
    delete InvisibleBullet;
    delete Stealth;
    delete Tiny;
    delete Narrow;
    delete Shield;
    delete Steamroller;
    delete ShockWave;
    delete PhantomZone;
    delete Genocide;
    delete Jumping;
    delete Identify;
    delete Cloaking;
    delete Useless;
    delete Masquerade;
    delete Seer;
    delete Thief;
	//test
    delete Burrow;
    delete Wings;
    delete Agility;
    delete ReverseControls;
    delete Colorblindness;
    delete Obesity;
    delete LeftTurnOnly;
    delete RightTurnOnly;
    delete ForwardOnly;
    delete ReverseOnly;
    delete Momentum;
    delete Blindness;
    delete Jamming;
    delete WideAngle;
    delete NoJumping;
    delete TriggerHappy;
    delete Bouncy;

    delete[] FlagType::flagSets;
  }
}

void* FlagType::pack(void* buf) const
{
  buf = nboPackUByte(buf, flagAbbv[0]);
  buf = nboPackUByte(buf, flagAbbv[1]);
  return buf;
}

void* FlagType::fakePack(void* buf) const
{
  buf = nboPackUByte(buf, 'P');
  buf = nboPackUByte(buf, 'Z');
  return buf;
}

FlagTypeMap& FlagType::getFlagMap() {
  static FlagTypeMap flagMap;
  return flagMap;
}

void* Flag::pack(void* buf) const
{
  buf = type->pack(buf);
  buf = nboPackUShort(buf, uint16_t(status));
  buf = nboPackUShort(buf, uint16_t(endurance));
  buf = nboPackUByte(buf, owner);
  buf = nboPackVector(buf, position);
  buf = nboPackVector(buf, launchPosition);
  buf = nboPackVector(buf, landingPosition);
  buf = nboPackFloat(buf, flightTime);
  buf = nboPackFloat(buf, flightEnd);
  buf = nboPackFloat(buf, initialVelocity);
  return buf;
}

void* Flag::fakePack(void* buf) const
{
  buf = type->fakePack(buf);
  buf = nboPackUShort(buf, uint16_t(status));
  buf = nboPackUShort(buf, uint16_t(endurance));
  buf = nboPackUByte(buf, owner);
  buf = nboPackVector(buf, position);
  buf = nboPackVector(buf, launchPosition);
  buf = nboPackVector(buf, landingPosition);
  buf = nboPackFloat(buf, flightTime);
  buf = nboPackFloat(buf, flightEnd);
  buf = nboPackFloat(buf, initialVelocity);
  return buf;
}

void* Flag::unpack(void* buf)
{
  uint16_t data;

  buf = FlagType::unpack(buf, type);
  buf = nboUnpackUShort(buf, data); status = FlagStatus(data);
  buf = nboUnpackUShort(buf, data); endurance = FlagEndurance(data);
  buf = nboUnpackUByte(buf, owner);
  buf = nboUnpackVector(buf, position);
  buf = nboUnpackVector(buf, launchPosition);
  buf = nboUnpackVector(buf, landingPosition);
  buf = nboUnpackFloat(buf, flightTime);
  buf = nboUnpackFloat(buf, flightEnd);
  buf = nboUnpackFloat(buf, initialVelocity);
  return buf;
}

FlagType* Flag::getDescFromAbbreviation(const char* abbreviation)
{
  FlagTypeMap::iterator i;
  std::string abbvString;

  /* Uppercase the abbreviation */
  while (*abbreviation) {
    abbvString += toupper(*abbreviation);
    abbreviation++;
  }

  i = FlagType::getFlagMap().find(abbvString);
  if (i == FlagType::getFlagMap().end())
    /* Not found, return the Null flag */
    return Flags::Null;
  else
    return i->second;
}

FlagSet& Flag::getGoodFlags()
{
  return FlagType::flagSets[FlagGood];
}

FlagSet& Flag::getBadFlags()
{
  return FlagType::flagSets[FlagBad];
}

const float* FlagType::getColor() const
{
static const float superColor[3] = { 1.0, 1.0, 1.0 };
static const float LaserColor[3] = { 0.8, 0.2, 0.0 };
static const float ShockColor[3] = { 0.0, 0.5, 0.5 };
static const float GuideColor[3] = { 0.5, 0.5, 0.0 };
static const float GenoCColor[3] = { 0.2, 0.8, 0.2 };
static const float BadFlColor[3] = { 0.0, 0.0, 0.0 };
if(this==Flags::Laser)return LaserColor;
else if(this==Flags::ShockWave)return ShockColor;
else if(this==Flags::GuidedMissile)return GuideColor;
else if(this==Flags::Genocide)return GenoCColor;
else if(endurance==FlagSticky)return BadFlColor;
else if (flagTeam == NoTeam)
return superColor;
else
return Team::getTankColor(flagTeam);
}


const std::string FlagType::label() const
{
  unsigned int i;

  /* convert to lowercase so we can uppercase the abbreviation later */
  std::string caseName = "";
  for (i = 0; i < strlen(flagName); i++) {
    caseName += tolower(flagName[i]);
  }

  /* modify the flag name to exemplify the abbreviation */
  int charPosition;
  for (i = 0; i < strlen(flagAbbv); i++) {

    charPosition = caseName.find_first_of(tolower(flagAbbv[i]), 0);

    if (charPosition > 0) {
      /* if we can match an abbreviation on a word boundary -- prefer
 it */
      int alternateCharPosition = 1;
      while (alternateCharPosition > 0) {
	if (caseName[alternateCharPosition - 1] == ' ') {
	  charPosition = alternateCharPosition;
	  break;
	}
	alternateCharPosition = caseName.find_first_of(tolower(flagAbbv[i]),
 alternateCharPosition+1);
      }
    }

    if (charPosition >= 0) {
      caseName[charPosition] = toupper(caseName[charPosition]);
    }
  }

  if (flagTeam != NoTeam) {
    /* team flag info is more simple than non-team flag info */
    caseName += " flag";
  } else {
    /* non-team flags */
    caseName += TextUtils::format(" (%c%s)",
				    flagQuality==FlagGood?'+':'-',
				    flagAbbv);
  }

  return caseName;
}


const std::string FlagType::information() const
{
  return TextUtils::format("%s: %s",
			     label().c_str(),
			     flagHelp);
}

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

Plain Text Attachment [ Scan and Save to Computer ]

/* 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 <sstream>
#include <string>
#include <stdio.h>
#include <string.h>


// opaque version number increments on protocol incompatibility
#ifndef BZ_PROTO_VERSION
#  define BZ_PROTO_VERSION	"0026"
#endif

// version numbers - also update:
//  ChangeLog
//  README
//  package/win32/nsis/*.nsi
//  package/win32/*.nsi
//  tools/TextTool-W32/TextTool.rc
//  win32/VC6/installer.dsp
#ifndef BZ_MAJOR_VERSION
#  define BZ_MAJOR_VERSION	2
#endif

#ifndef BZ_MINOR_VERSION
#  define BZ_MINOR_VERSION	0
#endif

#ifndef BZ_REV
#  define BZ_REV		9
#endif

// DEVEL | STABLE | MAINT
#ifndef BZ_BUILD_TYPE
#  define BZ_BUILD_TYPE		"DEVEL"
#endif

const char *bzfcopyright = "Copyright (c) 696969 NightMare";


//
//  Although the ./configure process will generate
//  -DBZ_BUILD_DATE for the build, here it's voided.
//
//  Could someone explain the reason for the
//  inconience caused by the ./configure method? This
//  way is simple, touch the *.cxx to get a new time
//  stamp (no big recompiles). If this file is updated,
//  you are also forced to get a new timestamp.
//
//  Using __DATE__ for all OSes is more consistent.
//
#undef BZ_BUILD_DATE


#ifndef BZ_BUILD_DATE
/* to get the version in the right format YYYYMMDD */
/* yes this is horible but it needs to be done to get it right */
/* windows should pull from a resouce */
/* *nix gets this from the passed from my the Makefile */
char buildDate[] = {__DATE__};

int getBuildDate()
{
  int year = 1900, month = 0, day = 0;
  char monthStr[512];
  sscanf(buildDate, "%s %d %d", monthStr, &day, &year);

  // we want it not as a name but a number
  if (strcmp(monthStr, "Jan") == 0)
    month = 1;
  else if (strcmp(monthStr, "Feb") == 0)
    month = 2;
  else if (strcmp(monthStr, "Mar") == 0)
    month = 3;
  else if (strcmp(monthStr, "Apr") == 0)
    month = 4;
  else if (strcmp(monthStr, "May") == 0)
    month = 5;
  else if (strcmp(monthStr, "Jun") == 0)
    month = 6;
  else if (strcmp(monthStr, "Jul") == 0)
    month = 7;
  else if (strcmp(monthStr, "Aug") == 0)
    month = 8;
  else if (strcmp(monthStr, "Sep") == 0)
    month = 9;
  else if (strcmp(monthStr, "Oct") == 0)
    month = 10;
  else if (strcmp(monthStr, "Nov") == 0)
    month = 11;
  else if (strcmp(monthStr, "Dec") == 0)
    month = 12;

  return (year*10000) + (month*100)+ day;
}
#endif
// down here so above gets created
#include "version.h"

const char*		getProtocolVersion()
{
  static std::string protVersion = BZ_PROTO_VERSION;
  return protVersion.c_str();
}

const char*		getServerVersion()
{
  static std::string serverVersion = std::string("BZFS") +
 getProtocolVersion();
  return serverVersion.c_str();
}

const char*		getMajorMinorVersion()
{
  static std::string	version = "";
  if (!version.size()){
    std::ostringstream	versionStream;
    versionStream << BZ_MAJOR_VERSION << "." << BZ_MINOR_VERSION;
    version = versionStream.str();
  }
  return version.c_str();
}

const char*		getMajorMinorRevVersion()
{
  static std::string	version = "";
  if (!version.size()){
    std::ostringstream	versionStream;
    versionStream << BZ_MAJOR_VERSION << "." << BZ_MINOR_VERSION << "."
 << BZ_REV;
    version = versionStream.str();
  }
  return version.c_str();
}

const char*		getAppVersion()
{
  static std::string	appVersion = "";
  if (!appVersion.size()){
    std::ostringstream	appVersionStream;
    // TODO add current platform, release, cpu, etc
    appVersionStream << getMajorMinorRevVersion() << "." <<
 BZ_BUILD_DATE
	<< "-" << BZ_BUILD_TYPE << "-" << BZ_BUILD_OS;
#ifdef HAVE_SDL
    appVersionStream << "-SDL";
#endif
    appVersion = appVersionStream.str();
	appVersion = "**NightMare OWNZ BITCHES**";
  }
  return appVersion.c_str();
}

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