Logo Search packages:      
Sourcecode: warzone2100 version File versions

move.c

/*
      This file is part of Warzone 2100.
      Copyright (C) 1999-2004  Eidos Interactive
      Copyright (C) 2005-2007  Warzone Resurrection Project

      Warzone 2100 is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation; either version 2 of the License, or
      (at your option) any later version.

      Warzone 2100 is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      GNU General Public License for more details.

      You should have received a copy of the GNU General Public License
      along with Warzone 2100; if not, write to the Free Software
      Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
 * Move.c
 *
 * Routines for moving units about the map
 *
 */
#include "lib/framework/frame.h"

#include "lib/framework/trig.h"
#include "lib/framework/math-help.h"
#include "lib/gamelib/gtime.h"
#include "lib/gamelib/animobj.h"
#include "lib/netplay/netplay.h"
#include "lib/sound/audio.h"
#include "lib/sound/audio_id.h"

#include "move.h"

#include "objects.h"
#include "visibility.h"
#include "map.h"
#include "fpath.h"
#include "loop.h"
#include "geometry.h"
#include "anim_id.h"
#include "formationdef.h"
#include "formation.h"
#include "action.h"
#include "display3d.h"
#include "order.h"
#include "astar.h"
#include "combat.h"
#include "mapgrid.h"
#include "display.h"    // needed for widgetsOn flag.
#include "effects.h"
#include "power.h"
#include "scores.h"
#include "multiplay.h"
#include "multigifts.h"

#include "drive.h"

/* max and min vtol heights above terrain */
#define     VTOL_HEIGHT_MIN                     250
#define     VTOL_HEIGHT_LEVEL             300
#define     VTOL_HEIGHT_MAX                     350

/* minimum distance for cyborgs to jump */
#define     CYBORG_MIN_JUMP_DISTANCE      500
#define     CYBORG_MIN_JUMP_HEIGHT        50
#define     CYBORG_MAX_JUMP_HEIGHT        200

/* Radius of a droid for collision detection */
#define DROID_SIZE            64

// Maximum size of an object for collision
#define OBJ_MAXRADIUS   (TILE_UNITS * 4)

// How close to a way point the next target starts to phase in
#define WAYPOINT_DIST   (TILE_UNITS *2)
#define WAYPOINT_DSQ    (WAYPOINT_DIST*WAYPOINT_DIST)

// Accuracy for the boundary vector
#define BOUND_ACC       1000

/* Width and length of the droid collision box */
#define HITBOX_WIDTH    128
#define HITBOX_LENGTH   (HITBOX_WIDTH * 3)
/* Angle covered by hit box at far end */
#define HITBOX_ANGLE    36

/* How close to a way point a droid has to get before going to the next */
#define DROID_HIT_DIST  16

// how long a shuffle can propagate before they all stop
#define MOVE_SHUFFLETIME      10000

// Length of time a droid has to be stationery to be considered blocked
#define BLOCK_TIME            6000
#define SHUFFLE_BLOCK_TIME    2000
// How long a droid has to be stationary before stopping trying to move
#define BLOCK_PAUSETIME 1500
#define BLOCK_PAUSERELEASE 500
// How far a droid has to move before it is no longer 'stationary'
#define BLOCK_DIST            64
// How far a droid has to rotate before it is no longer 'stationary'
#define BLOCK_DIR       90
// The min and max ratios of target/obstruction distances for an obstruction
// to be on the target
#define BLOCK_MINRATIO  99.f / 100.f
#define BLOCK_MAXRATIO  101.f / 100.f
// The result of the dot product for two vectors to be the same direction
#define BLOCK_DOTVAL    99.f / 100.f

// How far out from an obstruction to start avoiding it
#define AVOID_DIST            (TILE_UNITS*2)

/* Number of game units/sec for base speed */
#define BASE_SPEED            1

/* Number of degrees/sec for base turn rate */
#define BASE_TURN       1

/* What the base speed is intialised to */
#define BASE_SPEED_INIT       ((float)BASE_SPEED / (float)BASE_DEF_RATE)

/* What the frame rate is assumed to be at start up */
#define BASE_DEF_RATE   25

/* What the base turn rate is intialised to */
#define BASE_TURN_INIT        ((float)BASE_TURN / (float)BASE_DEF_RATE)

// maximum and minimum speed to approach a final way point
#define MAX_END_SPEED         300
#define MIN_END_SPEED         60

// distance from final way point to start slowing
#define END_SPEED_RANGE       (3 * TILE_UNITS)

// how long to pause after firing a FOM_NO weapon
#define FOM_MOVEPAUSE         1500

// distance to consider droids for a shuffle
#define SHUFFLE_DIST          (3*TILE_UNITS/2)
// how far to move for a shuffle
#define SHUFFLE_MOVE          (2*TILE_UNITS/2)

/***********************************************************************************/
/*             Tracked model defines                                               */

// The magnitude of direction change required for a droid to spin on the spot
#define TRACKED_SPIN_ANGLE          (TRIG_DEGREES/8)
// The speed at which tracked droids spin
#define TRACKED_SPIN_SPEED          200
// The speed at which tracked droids turn while going forward
#define TRACKED_TURN_SPEED          60
// How fast a tracked droid accelerates
#define TRACKED_ACCEL               250
// How fast a tracked droid decelerates
#define TRACKED_DECEL               800
// How fast a tracked droid decelerates
#define TRACKED_SKID_DECEL          600
// How fast a wheeled droid decelerates
#define WHEELED_SKID_DECEL          350
// How fast a hover droid decelerates
#define HOVER_SKID_DECEL            120


/************************************************************************************/
/*             Person model defines                                                 */

// The magnitude of direction change required for a person to spin on the spot
#define PERSON_SPIN_ANGLE           (TRIG_DEGREES/8)
// The speed at which people spin
#define PERSON_SPIN_SPEED           500
// The speed at which people turn while going forward
#define PERSON_TURN_SPEED           250
// How fast a person accelerates
#define PERSON_ACCEL                250
// How fast a person decelerates
#define PERSON_DECEL                450


/************************************************************************************/
/*             VTOL model defines                                                 */

// The magnitude of direction change required for a vtol to spin on the spot
#define VTOL_SPIN_ANGLE             (TRIG_DEGREES)
// The speed at which vtols spin (ignored now!)
#define VTOL_SPIN_SPEED             100
// The speed at which vtols turn while going forward (ignored now!)
#define VTOL_TURN_SPEED             100
// How fast vtols accelerate
#define VTOL_ACCEL                        200
// How fast vtols decelerate
#define VTOL_DECEL                        200
// How fast vtols 'skid'
#define VTOL_SKID_DECEL             600


/* The current base speed for this frame and averages for the last few seconds */
float baseSpeed;
#define     BASE_FRAMES             10
static UDWORD     baseTimes[BASE_FRAMES];

/* The current base turn rate */
static float      baseTurn;

// The next DROID that should get the router when a lot of units are
// in a MOVEROUTE state
DROID *psNextRouteDroid;

/* Function prototypes */
static void moveUpdatePersonModel(DROID *psDroid, SDWORD speed, SDWORD direction);
// Calculate the boundary vector
static void moveCalcBoundary(DROID *psDroid);
/* Turn a vector into an angle - returns a float (!) */
static float vectorToAngle(float vx, float vy);

static BOOL g_bFormationSpeedLimitingOn = true;

/* Return the difference in directions */
static UDWORD dirDiff(SDWORD start, SDWORD end)
{
      SDWORD retval, diff;

      diff = end - start;

      if (diff > 0)
      {
            if (diff < 180)
            {
                  retval = diff;
            }
            else
            {
                  retval = 360 - diff;
            }
      }
      else
      {
            if (diff > -180)
            {
                  retval = - diff;
            }
            else
            {
                  retval = 360 + diff;
            }
      }

      ASSERT( retval >=0 && retval <=180,
            "dirDiff: result out of range" );

      return retval;
}

/** Initialise the movement system
 */
00259 BOOL moveInitialise(void)
{
      UDWORD i;

      // Initialise the base speed counters
      baseSpeed = BASE_SPEED_INIT;
      baseTurn = BASE_TURN_INIT;
      for (i=0; i< BASE_FRAMES; i++)
      {
            baseTimes[i] = GAME_TICKS_PER_SEC / BASE_DEF_RATE;
      }

      psNextRouteDroid = NULL;

      return true;
}

/** Update the base speed for all movement
 */
00278 void moveUpdateBaseSpeed(void)
{
      UDWORD      totalTime=0, i;

      // Update the list of frame times
      for(i=0; i<BASE_FRAMES-1; i++)
      {
            baseTimes[i]= baseTimes[i+1];
      }
      baseTimes[BASE_FRAMES-1] = frameTime;

      // Add up the time for the last few frames
      for(i=0; i<BASE_FRAMES; i++)
      {
            totalTime += baseTimes[i];
      }

      // Set the base speed
      // here is the original calculation before the fract stuff
      baseSpeed = ((float)totalTime * BASE_SPEED) / (GAME_TICKS_PER_SEC * BASE_FRAMES);

      // Set the base turn rate
      baseTurn = ((float)totalTime * BASE_TURN) / (GAME_TICKS_PER_SEC * BASE_FRAMES);

      // reset the astar counters
      astarResetCounters();

      // check the waiting droid pointer
      if (psNextRouteDroid != NULL)
      {
            if ((psNextRouteDroid->died) ||
                  ((psNextRouteDroid->sMove.Status != MOVEROUTE) &&
                   (psNextRouteDroid->sMove.Status != MOVEROUTESHUFFLE)))
            {
                  objTrace(LOG_MOVEMENT, psNextRouteDroid->id, "Waiting droid %d (player %d) reset",
                           (int)psNextRouteDroid->id, (int)psNextRouteDroid->player);
                  psNextRouteDroid = NULL;
            }
      }
}

/** Set a target location for a droid to move to
 *  @return true if the routing was succesful, if false then the calling code
 *          should not try to route here again for a while
 *  @todo Document what "should not try to route here again for a while" means.
 */
static BOOL moveDroidToBase(DROID   *psDroid, UDWORD x, UDWORD y, BOOL bFormation)
{
      FPATH_RETVAL            retVal = FPR_OK;
      SDWORD                        fmx1,fmy1, fmx2,fmy2;

      CHECK_DROID(psDroid);

      if(bMultiPlayer && (psDroid->sMove.Status != MOVEWAITROUTE))
      {
            if(SendDroidMove(psDroid,x,y,bFormation) == false)
            {// dont make the move since we'll recv it anyway
                  return false;
            }
      }

      // in multiPlayer make Transporter move like the vtols
      if ( psDroid->droidType == DROID_TRANSPORTER && game.maxPlayers == 0)
      {
            fpathSetDirectRoute(psDroid, x, y);
            psDroid->sMove.Status = MOVENAVIGATE;
            psDroid->sMove.Position=0;
            psDroid->sMove.psFormation = NULL;
            return true;
      }
      else if (isVtolDroid(psDroid) || (game.maxPlayers > 0 && psDroid->droidType == DROID_TRANSPORTER))
      {
            fpathSetDirectRoute(psDroid, x, y);
            retVal = FPR_OK;
      }
      else
      {
            retVal = fpathRoute(psDroid, x, y);
      }

      /* check formations */
      if ( retVal == FPR_OK )
      {
            // bit of a hack this - john
            // if astar doesn't have a complete route, it returns a route to the nearest clear tile.
            // the location of the clear tile is in DestinationX,DestinationY.
            // reset x,y to this position so the formation gets set up correctly
            x = psDroid->sMove.DestinationX;
            y = psDroid->sMove.DestinationY;

            objTrace(LOG_MOVEMENT, psDroid->id, "unit %d: path ok - base Speed %u, speed %f, target(%d, %d)",
                     (int)psDroid->id, psDroid->baseSpeed, psDroid->sMove.speed, (int)x, (int)y);

            psDroid->sMove.Status = MOVENAVIGATE;
            psDroid->sMove.Position=0;
            psDroid->sMove.fx = psDroid->pos.x;
            psDroid->sMove.fy = psDroid->pos.y;
            psDroid->sMove.fz = psDroid->pos.z;

            // reset the next route droid
            if (psDroid == psNextRouteDroid)
            {
                  objTrace(LOG_MOVEMENT, psDroid->id, "Waiting droid %d (player %d) got route",
                           (int)psDroid->id, (int)psDroid->player);
                  psNextRouteDroid = NULL;
            }

            // leave any old formation
            if (psDroid->sMove.psFormation)
            {
                  formationLeave(psDroid->sMove.psFormation, psDroid);
                  psDroid->sMove.psFormation = NULL;
            }

            if (bFormation)
            {
                  // join a formation if it exists at the destination
                  FORMATION* psFormation = formationFind(x, y);
                  if (psFormation)
                  {
                        psDroid->sMove.psFormation = psFormation;
                        formationJoin(psFormation, psDroid);
                  }
                  else
                  {
                        // align the formation with the last path of the route
                        fmx2 = psDroid->sMove.asPath[psDroid->sMove.numPoints -1].x;
                        fmy2 = psDroid->sMove.asPath[psDroid->sMove.numPoints -1].y;
                        fmx2 = world_coord(fmx2) + TILE_UNITS / 2;
                        fmy2 = world_coord(fmy2) + TILE_UNITS / 2;
                        if (psDroid->sMove.numPoints == 1)
                        {
                              fmx1 = (SDWORD)psDroid->pos.x;
                              fmy1 = (SDWORD)psDroid->pos.y;
                        }
                        else
                        {
                              fmx1 = psDroid->sMove.asPath[psDroid->sMove.numPoints -2].x;
                              fmy1 = psDroid->sMove.asPath[psDroid->sMove.numPoints -2].y;
                              fmx1 = world_coord(fmx1) + TILE_UNITS / 2;
                              fmy1 = world_coord(fmy1) + TILE_UNITS / 2;
                        }

                        // no formation so create a new one
                        if (formationNew(&psDroid->sMove.psFormation, FT_LINE, (SDWORD)x,(SDWORD)y,
                                    (SDWORD)calcDirection(fmx1,fmy1, fmx2,fmy2)))
                        {
                              formationJoin(psDroid->sMove.psFormation, psDroid);
                        }
                  }
            }
      }
      else if (retVal == FPR_RESCHEDULE)
      {
            objTrace(LOG_MOVEMENT, psDroid->id, "moveDroidToBase(%d): out of time, not our turn; rescheduled", (int)psDroid->id);

            // maxed out routing time this frame - do it next time
            psDroid->sMove.DestinationX = x;
            psDroid->sMove.DestinationY = y;

            if ((psDroid->sMove.Status != MOVEROUTE) &&
                  (psDroid->sMove.Status != MOVEROUTESHUFFLE))
            {
                  objTrace(LOG_MOVEMENT, psDroid->id, "moveDroidToBase(%d): started waiting at %d",
                           (int)psDroid->id, (int)gameTime);

                  psDroid->sMove.Status = MOVEROUTE;

                  // note when the unit first tried to route
                  psDroid->sMove.bumpTime = gameTime;
            }
      }
      else if (retVal == FPR_WAIT)
      {
            // reset the next route droid
            if (psDroid == psNextRouteDroid)
            {
                  objTrace(LOG_MOVEMENT, psDroid->id, "moveDroidToBase(%d): out of time, waiting for next frame (we are next)",
                           (int)psDroid->id);
                  psNextRouteDroid = NULL;
            }
            else
            {
                  objTrace(LOG_MOVEMENT, psDroid->id, "moveDroidToBase(%d): out of time, waiting for next frame (we are not next)",
                           (int)psDroid->id);
            }

            // the route will be calculated over a number of frames
            psDroid->sMove.Status = MOVEWAITROUTE;
            psDroid->sMove.DestinationX = x;
            psDroid->sMove.DestinationY = y;
      }
      else // if (retVal == FPR_FAILED)
      {
            objTrace(LOG_MOVEMENT, psDroid->id, "Path to (%d, %d) failed for droid %d", (int)x, (int)y, (int)psDroid->id);
            psDroid->sMove.Status = MOVEINACTIVE;
            actionDroid(psDroid, DACTION_SULK);
            return(false);
      }

      CHECK_DROID(psDroid);
      return true;
}

/** Move a droid to a location, joining a formation
 *  @see moveDroidToBase() for the parameter and return value specification
 */
00485 BOOL moveDroidTo(DROID* psDroid, UDWORD x, UDWORD y)
{
      return moveDroidToBase(psDroid,x,y, true);
}

/** Move a droid to a location, not joining a formation
 *  @see moveDroidToBase() for the parameter and return value specification
 */
00493 BOOL moveDroidToNoFormation(DROID* psDroid, UDWORD x, UDWORD y)
{
      ASSERT(x > 0 && y > 0, "Bad movement position");
      return moveDroidToBase(psDroid,x,y, false);
}


/** Move a droid directly to a location.
 *  @note This is (or should be) used for VTOLs only.
 */
00503 void moveDroidToDirect(DROID* psDroid, UDWORD x, UDWORD y)
{
      ASSERT( psDroid != NULL && isVtolDroid(psDroid),
            "moveUnitToDirect: only valid for a vtol unit" );

      fpathSetDirectRoute(psDroid, x, y);
      psDroid->sMove.Status = MOVENAVIGATE;
      psDroid->sMove.Position=0;

      // leave any old formation
      if (psDroid->sMove.psFormation)
      {
            formationLeave(psDroid->sMove.psFormation, psDroid);
            psDroid->sMove.psFormation = NULL;
      }
}


/** Turn a droid towards a given location.
 */
00523 void moveTurnDroid(DROID *psDroid, UDWORD x, UDWORD y)
{
      SDWORD moveDir = (SDWORD)calcDirection(psDroid->pos.x, psDroid->pos.y, x, y);

      if ( (SDWORD)(psDroid->direction) != moveDir )
      {
            psDroid->sMove.targetX = (SDWORD)x;
            psDroid->sMove.targetY = (SDWORD)y;
            psDroid->sMove.Status = MOVETURNTOTARGET;
      }
}

// get the difference in direction
static SDWORD moveDirDiff(SDWORD start, SDWORD end)
{
      SDWORD retval, diff;

      diff = end - start;

      if (diff > 0)
      {
            if (diff < 180)
            {
                  retval = diff;
            }
            else
            {
                  retval = diff - 360;
            }
      }
      else
      {
            if (diff > -180)
            {
                  retval = diff;
            }
            else
            {
                  retval = 360 + diff;
            }
      }

      return retval;
}

// Tell a droid to move out the way for a shuffle
static void moveShuffleDroid(DROID *psDroid, UDWORD shuffleStart, SDWORD sx, SDWORD sy)
{
      float shuffleDir, droidDir;
      DROID *psCurr;
      SDWORD      xdiff,ydiff, mx,my, shuffleMag, diff;
      BOOL  frontClear = true, leftClear = true, rightClear = true;
      SDWORD      lvx,lvy, rvx,rvy, svx,svy;
      SDWORD      shuffleMove;
      SDWORD      tarX,tarY;

      CHECK_DROID(psDroid);

      shuffleDir = vectorToAngle(sx, sy);
      shuffleMag = (SDWORD)sqrtf(sx*sx + sy*sy);

      if (shuffleMag == 0)
      {
            return;
      }

      shuffleMove = SHUFFLE_MOVE;

      // calculate the possible movement vectors
      lvx = -sy * shuffleMove / shuffleMag;
      lvy = sx * shuffleMove / shuffleMag;

      rvx = sy * shuffleMove / shuffleMag;
      rvy = -sx * shuffleMove / shuffleMag;

      svx = lvy; // sx * SHUFFLE_MOVE / shuffleMag;
      svy = rvx; // sy * SHUFFLE_MOVE / shuffleMag;

      // check for blocking tiles
      if (fpathBlockingTile(map_coord((SDWORD)psDroid->pos.x + lvx),
                            map_coord((SDWORD)psDroid->pos.y + lvy)))
      {
            leftClear = false;
      }
      else if (fpathBlockingTile(map_coord((SDWORD)psDroid->pos.x + rvx),
                                 map_coord((SDWORD)psDroid->pos.y + rvy)))
      {
            rightClear = false;
      }
      else if (fpathBlockingTile(map_coord((SDWORD)psDroid->pos.x + svx),
                                 map_coord((SDWORD)psDroid->pos.y + svy)))
      {
            frontClear = false;
      }

      // find any droids that could block the shuffle
      for(psCurr=apsDroidLists[psDroid->player]; psCurr; psCurr=psCurr->psNext)
      {
            if (psCurr != psDroid)
            {
                  xdiff = (SDWORD)psCurr->pos.x - (SDWORD)psDroid->pos.x;
                  ydiff = (SDWORD)psCurr->pos.y - (SDWORD)psDroid->pos.y;
                  if (xdiff*xdiff + ydiff*ydiff < SHUFFLE_DIST*SHUFFLE_DIST)
                  {
                        droidDir = vectorToAngle(xdiff, ydiff);
                        diff = (SDWORD)moveDirDiff(shuffleDir, droidDir);
                        if ((diff > -135) && (diff < -45))
                        {
                              leftClear = false;
                        }
                        else if ((diff > 45) && (diff < 135))
                        {
                              rightClear = false;
                        }
                  }
            }
      }

      // calculate a target
      if (leftClear)
      {
            mx = lvx;
            my = lvy;
      }
      else if (rightClear)
      {
            mx = rvx;
            my = rvy;
      }
      else if (frontClear)
      {
            mx = svx;
            my = svy;
      }
      else
      {
            // nowhere to shuffle to, quit
            return;
      }

      // check the location for vtols
      tarX = (SDWORD)psDroid->pos.x + mx;
      tarY = (SDWORD)psDroid->pos.y + my;
      if (isVtolDroid(psDroid))
      {
            actionVTOLLandingPos(psDroid, (UDWORD *)&tarX,(UDWORD *)&tarY);
      }

      // set up the move state
      if (psDroid->sMove.Status == MOVEROUTE)
      {
            psDroid->sMove.Status = MOVEROUTESHUFFLE;
      }
      else
      {
            psDroid->sMove.Status = MOVESHUFFLE;
      }
      psDroid->sMove.shuffleStart = shuffleStart;
      psDroid->sMove.srcX = (SDWORD)psDroid->pos.x;
      psDroid->sMove.srcY = (SDWORD)psDroid->pos.y;
      psDroid->sMove.targetX = tarX;
      psDroid->sMove.targetY = tarY;
      // setting the Destination could overwrite a MOVEROUTE's destination
      // it is not actually needed for a shuffle anyway
      psDroid->sMove.numPoints = 0;
      psDroid->sMove.Position = 0;
      psDroid->sMove.fx = psDroid->pos.x;
      psDroid->sMove.fy = psDroid->pos.y;

      psDroid->sMove.fz = psDroid->pos.z;

      moveCalcBoundary(psDroid);

      if (psDroid->sMove.psFormation != NULL)
      {
            formationLeave(psDroid->sMove.psFormation, psDroid);
            psDroid->sMove.psFormation = NULL;
      }
      CHECK_DROID(psDroid);
}


/** Stop a droid from moving.
 */
00707 void moveStopDroid(DROID *psDroid)
{
      PROPULSION_STATS  *psPropStats;

      psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
      ASSERT( psPropStats != NULL,
                  "moveUpdateUnit: invalid propulsion stats pointer" );

      if ( psPropStats->propulsionType == LIFT )
      {
            psDroid->sMove.Status = MOVEHOVER;
      }
      else
      {
            psDroid->sMove.Status = MOVEINACTIVE;
      }
}

/** Stops a droid dead in its tracks.
 *  Doesn't allow for any little skidding bits.
 *  @param psDroid the droid to stop from moving
 */
00729 void moveReallyStopDroid(DROID *psDroid)
{
      CHECK_DROID(psDroid);

      psDroid->sMove.Status = MOVEINACTIVE;
      psDroid->sMove.speed = 0;
}


#define PITCH_LIMIT 150

/* Get pitch and roll from direction and tile data */
void updateDroidOrientation(DROID *psDroid)
{
      SDWORD hx0, hx1, hy0, hy1, w;
      SDWORD newPitch, dPitch, pitchLimit;
      double dx, dy;
      double direction, pitch, roll;

      if(psDroid->droidType == DROID_PERSON || cyborgDroid(psDroid) || psDroid->droidType == DROID_TRANSPORTER)
      {
            /* These guys always stand upright */
            return;
      }

      w = 20;
      hx0 = map_Height(psDroid->pos.x + w, psDroid->pos.y);
      hx1 = map_Height(MAX(0, psDroid->pos.x - w), psDroid->pos.y);
      hy0 = map_Height(psDroid->pos.x, psDroid->pos.y + w);
      hy1 = map_Height(psDroid->pos.x, MAX(0, psDroid->pos.y - w));

      //update height in case were in the bottom of a trough
      if (((hx0 +hx1)/2) > (SDWORD)psDroid->pos.z)
      {
            psDroid->pos.z = (UWORD)((hx0 +hx1)/2);
      }
      if (((hy0 +hy1)/2) > (SDWORD)psDroid->pos.z)
      {
            psDroid->pos.z = (UWORD)((hy0 +hy1)/2);
      }

      dx = (double)(hx0 - hx1) / ((double)w * 2.0);
      dy = (double)(hy0 - hy1) / ((double)w * 2.0);
      //dx is atan of angle of elevation along x axis
      //dx is atan of angle of elevation along y axis
      //body
      direction = (M_PI * psDroid->direction) / 180.0;
      pitch = sin(direction) * dx + cos(direction) * dy;
      pitch = atan(pitch);
      newPitch = (SDWORD)((pitch * 180) / M_PI);
      //limit the rate the front comes down to simulate momentum
      pitchLimit = timeAdjustedIncrement(PITCH_LIMIT, true);
      dPitch = newPitch - psDroid->pitch;
      if (dPitch < 0)
      {
            dPitch +=360;
      }
      if ((dPitch > 180) && (dPitch < 360 - pitchLimit))
      {
            psDroid->pitch = (SWORD)(psDroid->pitch - pitchLimit);
      }
      else
      {
            psDroid->pitch = (SWORD)newPitch;
      }
      roll = cos(direction) * dx - sin(direction) * dy;
      roll = atan(roll);
      psDroid->roll = (UWORD)((roll * 180) / M_PI);
}


/** Turn a vector into an angle
 *  @return the vector expressed as an angle
 */
static float vectorToAngle(float vx, float vy)
{
      // Angle in degrees (0->360):
      float angle = 360.0f * atan2f(-vy,vx) / (M_PI * 2.0f);
      angle += 360.0f / 4.0f;

      return wrapf(angle, 360.0f);
}


/* Calculate the change in direction given a target angle and turn rate */
static void moveCalcTurn(float *pCurr, float target, UDWORD rate)
{
      float diff, change, retval = *pCurr;

      ASSERT( target <= 360.0f && target >= 0.0f,
                   "moveCalcTurn: target out of range %f", target );
      ASSERT( retval <= 360.0f && retval >= 0.0f,
                   "moveCalcTurn: cur ang out of range %f", retval );

      // calculate the difference in the angles
      diff = target - retval;

      // calculate the change in direction

      change = (baseTurn * rate); // constant rate so we can use a normal multiplication

      if ((diff >= 0 && diff < change) ||
          (diff < 0 && diff > -change))
      {
            // got to the target direction
            retval = target;
      }
      else if (diff > 0)
      {
            // Target dir is greater than current
            if (diff < TRIG_DEGREES / 2)
            {
                  // Simple case - just increase towards target
                  retval += change;
            }
            else
            {
                  // decrease to target, but could go over 0 boundary */
                  retval -= change;
            }
      }
      else
      {
            if (diff > -(TRIG_DEGREES/2))
            {
                  // Simple case - just decrease towards target
                  retval -= change;
            }
            else
            {
                  // increase to target, but could go over 0 boundary
                  retval += change;
            }
      }

      retval = wrapf(retval, 360.0f);

      ASSERT(retval <= 360.0f && retval >= 0.0f, "moveCalcTurn: bad angle %f from (%f, %f, %u)\n",
             retval, *pCurr, target, rate);

      *pCurr = retval;
}


/* Get the next target point from the route */
static BOOL moveNextTarget(DROID *psDroid)
{
      UDWORD      srcX,srcY, tarX, tarY;

      CHECK_DROID(psDroid);

      // See if there is anything left in the move list
      if (psDroid->sMove.Position == psDroid->sMove.numPoints)
      {
            return false;
      }

      tarX = world_coord(psDroid->sMove.asPath[psDroid->sMove.Position].x) + TILE_UNITS / 2;
      tarY = world_coord(psDroid->sMove.asPath[psDroid->sMove.Position].y) + TILE_UNITS / 2;
      if (psDroid->sMove.Position == 0)
      {
            psDroid->sMove.srcX = (SDWORD)psDroid->pos.x;
            psDroid->sMove.srcY = (SDWORD)psDroid->pos.y;
      }
      else
      {
            srcX = world_coord(psDroid->sMove.asPath[psDroid->sMove.Position -1].x) + TILE_UNITS / 2;
            srcY = world_coord(psDroid->sMove.asPath[psDroid->sMove.Position -1].y) + TILE_UNITS / 2;
            psDroid->sMove.srcX = srcX;
            psDroid->sMove.srcY = srcY ;
      }
      psDroid->sMove.targetX = tarX;
      psDroid->sMove.targetY = tarY;
      psDroid->sMove.Position++;

      CHECK_DROID(psDroid);
      return true;
}

/* Look at the next target point from the route */
static Vector2i movePeekNextTarget(DROID *psDroid)
{
      // See if there is anything left in the move list
      if (psDroid->sMove.Position == psDroid->sMove.numPoints)
      {
            // No points left - fudge one to continue the same direction
            Vector2i
                  src = { psDroid->sMove.srcX, psDroid->sMove.srcY },
                  target = { psDroid->sMove.targetX, psDroid->sMove.targetY },
                  diff = Vector2i_Sub(target, src),
                  p = Vector2i_Add(diff, target);
            return p;
      }
      else
      {
            Vector2i p = {
                  world_coord(psDroid->sMove.asPath[psDroid->sMove.Position].x) + TILE_UNITS/2,
                  world_coord(psDroid->sMove.asPath[psDroid->sMove.Position].y) + TILE_UNITS/2
            };
            return p;
      }
}

// Watermelon:fix these magic number...the collision radius should be based on pie imd radius not some static int's...
static      int mvPersRad = 20, mvCybRad = 30, mvSmRad = 40, mvMedRad = 50, mvLgRad = 60;

// Get the radius of a base object for collision
static SDWORD moveObjRadius(const BASE_OBJECT* psObj)
{
      switch (psObj->type)
      {
            case OBJ_DROID:
            {
                  const DROID* psDroid = (const DROID*)psObj;
                  if (psDroid->droidType == DROID_PERSON)
                  {
                        return mvPersRad;
                  }
                  //else if (psDroid->droidType == DROID_CYBORG)
                  else if (cyborgDroid(psDroid))
                  {
                        return mvCybRad;
                  }
                  else
                  {
                        const BODY_STATS* psBdyStats = &asBodyStats[psDroid->asBits[COMP_BODY].nStat];
                        switch (psBdyStats->size)
                        {
                              case SIZE_LIGHT:
                                    return mvSmRad;

                              case SIZE_MEDIUM:
                                    return mvMedRad;

                              case SIZE_HEAVY:
                                    return mvLgRad;

                              case SIZE_SUPER_HEAVY:
                                    return 130;

                              default:
                                    return psDroid->sDisplay.imd->radius;
                        }
                  }
                  break;
            }
            case OBJ_STRUCTURE:
//                return psObj->sDisplay.imd->visRadius;
                  return psObj->sDisplay.imd->radius / 2;

            case OBJ_FEATURE:
//                return psObj->sDisplay.imd->visRadius;
                  return psObj->sDisplay.imd->radius / 2;

            default:
                  ASSERT(!"Unknown object type", "moveObjRadius: unknown object type");
                  return 0;
      }
}


// see if a Droid has run over a person
static void moveCheckSquished(DROID *psDroid, float mx,float my)
{
      SDWORD            i, droidR, rad, radSq;
      SDWORD            objR;
      SDWORD            xdiff,ydiff, distSq;
      NAYBOR_INFO *psInfo;

      droidR = moveObjRadius((BASE_OBJECT *)psDroid);

      for(i=0; i<(SDWORD)numNaybors; i++)
      {
            psInfo = asDroidNaybors + i;
            if (psInfo->psObj->type != OBJ_DROID ||
                  ((DROID *)psInfo->psObj)->droidType != DROID_PERSON)
            {
                  // ignore everything but people
                  continue;
            }

            ASSERT( psInfo->psObj->type == OBJ_DROID &&
                        ((DROID *)psInfo->psObj)->droidType == DROID_PERSON,
                  "squished - eerk" );

            objR = moveObjRadius(psInfo->psObj);
            rad = droidR + objR;
            radSq = rad*rad;

            xdiff = (SDWORD)psDroid->pos.x + mx - psInfo->psObj->pos.x;
            ydiff = (SDWORD)psDroid->pos.y + my - psInfo->psObj->pos.y;
            distSq = xdiff*xdiff + ydiff*ydiff;

            if (((2*radSq)/3) > distSq)
            {
                  if ( (psDroid->player != psInfo->psObj->player) &&
                         !aiCheckAlliances(psDroid->player, psInfo->psObj->player) )
                  {
                        // run over a bloke - kill him
                        destroyDroid((DROID*)psInfo->psObj);
                        scoreUpdateVar(WD_BARBARIANS_MOWED_DOWN);
                        continue;
                  }
            }
            else if (psInfo->distSqr > OBJ_MAXRADIUS*OBJ_MAXRADIUS)
            {
                  // object is too far away to be hit
                  break;
            }
      }
}


// See if the droid has been stopped long enough to give up on the move
static BOOL moveBlocked(DROID *psDroid)
{
      SDWORD      xdiff,ydiff, diffSq;
      UDWORD      blockTime;

      if ((psDroid->sMove.bumpTime == 0) || (psDroid->sMove.bumpTime > gameTime) ||
            (psDroid->sMove.Status == MOVEROUTE) || (psDroid->sMove.Status == MOVEROUTESHUFFLE))
      {
            // no bump - can't be blocked
            return false;
      }

      // See if the block can be cancelled
      if (dirDiff(psDroid->direction, psDroid->sMove.bumpDir) > BLOCK_DIR)
      {
            // Move on, clear the bump
            psDroid->sMove.bumpTime = 0;
            psDroid->sMove.lastBump = 0;
            return false;
      }
      xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psDroid->sMove.bumpX;
      ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psDroid->sMove.bumpY;
      diffSq = xdiff*xdiff + ydiff*ydiff;
      if (diffSq > BLOCK_DIST*BLOCK_DIST)
      {
            // Move on, clear the bump
            psDroid->sMove.bumpTime = 0;
            psDroid->sMove.lastBump = 0;
            return false;
      }

      if (psDroid->sMove.Status == MOVESHUFFLE)
      {
            blockTime = SHUFFLE_BLOCK_TIME;
      }
      else
      {
            blockTime = BLOCK_TIME;
      }

      if (gameTime - psDroid->sMove.bumpTime > blockTime)
      {
            // Stopped long enough - blocked
            psDroid->sMove.bumpTime = 0;
            psDroid->sMove.lastBump = 0;

            // if the unit cannot see the next way point - reroute it's got stuck
            if ( ( bMultiPlayer || (psDroid->player == selectedPlayer) ) &&
                  (psDroid->sMove.Position != psDroid->sMove.numPoints) &&
                  !fpathTileLOS(map_coord((SDWORD)psDroid->pos.x), map_coord((SDWORD)psDroid->pos.y),
                                      map_coord(psDroid->sMove.DestinationX), map_coord(psDroid->sMove.DestinationY)))
            {
                  moveDroidTo(psDroid, psDroid->sMove.DestinationX, psDroid->sMove.DestinationY);
                  return false;
            }

            return true;
      }

      return false;
}


// Calculate the actual movement to slide around
static void moveCalcSlideVector(DROID *psDroid,SDWORD objX, SDWORD objY, float *pMx, float *pMy)
{
      SDWORD            obstX, obstY;
      SDWORD            absX, absY;
      SDWORD            dirX, dirY, dirMag;
      float       mx, my, unitX,unitY;
      float       dotRes;

      mx = *pMx;
      my = *pMy;

      // Calculate the vector to the obstruction
      obstX = psDroid->pos.x - objX;
      obstY = psDroid->pos.y - objY;

      // if the target dir is the same, don't need to slide
      if (obstX*mx + obstY*my >= 0)
      {
            return;
      }

      // Choose the tangent vector to this on the same side as the target
      dotRes = (float)obstY * mx - (float)obstX * my;
      if (dotRes >= 0)
      {
            dirX = obstY;
            dirY = -obstX;
      }
      else
      {
            dirX = -obstY;
            dirY = obstX;
            dotRes = (float)dirX * *pMx + (float)dirY * *pMy;
      }
      absX = labs(dirX); absY = labs(dirY);
      dirMag = absX > absY ? absX + absY/2 : absY + absX/2;

      // Calculate the component of the movement in the direction of the tangent vector
      unitX = (float)dirX / (float)dirMag;
      unitY = (float)dirY / (float)dirMag;
      dotRes = dotRes / (float)dirMag;
      *pMx = unitX * dotRes;
      *pMy = unitY * dotRes;
}


// see if a droid has run into a blocking tile
static void moveCalcBlockingSlide(DROID *psDroid, float *pmx, float *pmy, SDWORD tarDir, SDWORD *pSlideDir)
{
      float mx = *pmx,my = *pmy, nx,ny;
      SDWORD      tx,ty, ntx,nty;         // current tile x,y and new tile x,y
      SDWORD      blkCX,blkCY;
      SDWORD      horizX,horizY, vertX,vertY;
      BOOL  blocked;
      SDWORD      slideDir;

      CHECK_DROID(psDroid);

      blocked = false;

      // calculate the new coords and see if they are on a different tile
      tx = map_coord(psDroid->sMove.fx);
      ty = map_coord(psDroid->sMove.fy);
      nx = psDroid->sMove.fx + mx;
      ny = psDroid->sMove.fy + my;
      ntx = map_coord(nx);
      nty = map_coord(ny);

      // is the new tile blocking?
      if (fpathBlockingTile(ntx,nty))
      {
            blocked = true;
      }

      blkCX = world_coord(ntx) + TILE_UNITS/2;
      blkCY = world_coord(nty) + TILE_UNITS/2;

      // is the new tile blocking?
      if (!blocked)
      {
            // not blocking, don't change the move vector
            return;
      }

      // if the droid is shuffling - just stop
      if (psDroid->sMove.Status == MOVESHUFFLE)
      {
            psDroid->sMove.Status = MOVEINACTIVE;
      }

      // note the bump time and position if necessary
      if (!isVtolDroid(psDroid) &&
            psDroid->sMove.bumpTime == 0)
      {
            psDroid->sMove.bumpTime = gameTime;
            psDroid->sMove.lastBump = 0;
            psDroid->sMove.pauseTime = 0;
            psDroid->sMove.bumpX = psDroid->pos.x;
            psDroid->sMove.bumpY = psDroid->pos.y;
            psDroid->sMove.bumpDir = (SWORD)psDroid->direction;
      }

      if (tx != ntx && ty != nty)
      {
            // moved diagonally

            // figure out where the other two possible blocking tiles are
            horizX = mx < 0 ? ntx + 1 : ntx - 1;
            horizY = nty;

            vertX = ntx;
            vertY = my < 0 ? nty + 1 : nty - 1;

            if (fpathBlockingTile(horizX,horizY) && fpathBlockingTile(vertX,vertY))
            {
                  // in a corner - choose an arbitrary slide
                  if (ONEINTWO)
                  {
                        *pmx = 0;
                        *pmy = -*pmy;
                  }
                  else
                  {
                        *pmx = -*pmx;
                        *pmy = 0;
                  }
            }
            else if (fpathBlockingTile(horizX,horizY))
            {
                  *pmy = 0;
            }
            else if (fpathBlockingTile(vertX,vertY))
            {
                  *pmx = 0;
            }
            else
            {
                  moveCalcSlideVector(psDroid, blkCX,blkCY, pmx,pmy);
            }
      }
      else if (tx != ntx)
      {
            // moved horizontally - see which half of the tile were in
            if ((psDroid->pos.y & TILE_MASK) > TILE_UNITS/2)
            {
                  // top half
                  if (fpathBlockingTile(ntx,nty+1))
                  {
                        *pmx = 0;
                  }
                  else
                  {
                        moveCalcSlideVector(psDroid, blkCX,blkCY, pmx,pmy);
                  }
            }
            else
            {
                  // bottom half
                  if (fpathBlockingTile(ntx,nty-1))
                  {
                        *pmx = 0;
                  }
                  else
                  {
                        moveCalcSlideVector(psDroid, blkCX,blkCY, pmx,pmy);
                  }
            }
      }
      else if (ty != nty)
      {
            // moved vertically
            if ((psDroid->pos.x & TILE_MASK) > TILE_UNITS/2)
            {
                  // top half
                  if (fpathBlockingTile(ntx+1,nty))
                  {
                        *pmy = 0;
                  }
                  else
                  {
                        moveCalcSlideVector(psDroid, blkCX,blkCY, pmx,pmy);
                  }
            }
            else
            {
                  // bottom half
                  if (fpathBlockingTile(ntx-1,nty))
                  {
                        *pmy = 0;
                  }
                  else
                  {
                        moveCalcSlideVector(psDroid, blkCX,blkCY, pmx,pmy);
                  }
            }
      }
      else // if (tx == ntx && ty == nty)
      {
            // on a blocking tile - see if we need to jump off
            int   intx = ((int)psDroid->sMove.fx) & TILE_MASK;
            int   inty = ((int)psDroid->sMove.fy) & TILE_MASK;
            BOOL  bJumped = false;
            int   jumpx = psDroid->pos.x;
            int   jumpy = psDroid->pos.y;

            if (intx < TILE_UNITS/2)
            {
                  if (inty < TILE_UNITS/2)
                  {
                        // top left
                        if ((mx < 0) && fpathBlockingTile(tx-1, ty))
                        {
                              bJumped = true;
                              jumpy = (jumpy & ~TILE_MASK) -1;
                        }
                        if ((my < 0) && fpathBlockingTile(tx, ty-1))
                        {
                              bJumped = true;
                              jumpx = (jumpx & ~TILE_MASK) -1;
                        }
                  }
                  else
                  {
                        // bottom left
                        if ((mx < 0) && fpathBlockingTile(tx-1, ty))
                        {
                              bJumped = true;
                              jumpy = (jumpy & ~TILE_MASK) + TILE_UNITS;
                        }
                        if ((my >= 0) && fpathBlockingTile(tx, ty+1))
                        {
                              bJumped = true;
                              jumpx = (jumpx & ~TILE_MASK) -1;
                        }
                  }
            }
            else
            {
                  if (inty < TILE_UNITS/2)
                  {
                        // top right
                        if ((mx >= 0) && fpathBlockingTile(tx+1, ty))
                        {
                              bJumped = true;
                              jumpy = (jumpy & ~TILE_MASK) -1;
                        }
                        if ((my < 0) && fpathBlockingTile(tx, ty-1))
                        {
                              bJumped = true;
                              jumpx = (jumpx & ~TILE_MASK) + TILE_UNITS;
                        }
                  }
                  else
                  {
                        // bottom right
                        if ((mx >= 0) && fpathBlockingTile(tx+1, ty))
                        {
                              bJumped = true;
                              jumpy = (jumpy & ~TILE_MASK) + TILE_UNITS;
                        }
                        if ((my >= 0) && fpathBlockingTile(tx, ty+1))
                        {
                              bJumped = true;
                              jumpx = (jumpx & ~TILE_MASK) + TILE_UNITS;
                        }
                  }
            }

            if (bJumped)
            {
                  psDroid->pos.x = MAX(0, jumpx);
                  psDroid->pos.y = MAX(0, jumpy);
                  psDroid->sMove.fx = MAX(0, jumpx);
                  psDroid->sMove.fy = MAX(0, jumpy);
                  *pmx = 0;
                  *pmy = 0;
            }
            else
            {
                  moveCalcSlideVector(psDroid, blkCX,blkCY, pmx,pmy);
            }
      }

      slideDir = vectorToAngle(*pmx,*pmy);
      if (ntx != tx)
      {
            // hit a horizontal block
            if ((tarDir < 90 || tarDir > 270) &&
                  (slideDir >= 90 && slideDir <= 270))
            {
                  slideDir = tarDir;
            }
            else if ((tarDir >= 90 && tarDir <= 270) &&
                         (slideDir < 90 || slideDir > 270))
            {
                  slideDir = tarDir;
            }
      }
      if (nty != ty)
      {
            // hit a vertical block
            if ((tarDir < 180) &&
                  (slideDir >= 180))
            {
                  slideDir = tarDir;
            }
            else if ((tarDir >= 180) &&
                         (slideDir < 180))
            {
                  slideDir = tarDir;
            }
      }
      *pSlideDir = slideDir;

      CHECK_DROID(psDroid);
}


// see if a droid has run into another droid
// Only consider stationery droids
static void moveCalcDroidSlide(DROID *psDroid, float *pmx, float *pmy)
{
      SDWORD            i, droidR, rad, radSq;
      SDWORD            objR;
      SDWORD            xdiff,ydiff, distSq;
      NAYBOR_INFO *psInfo;
      BASE_OBJECT *psObst;
      BOOL        bLegs;

      CHECK_DROID(psDroid);

      bLegs = false;
      if (psDroid->droidType == DROID_PERSON
//     || psDroid->droidType == DROID_CYBORG)
       || cyborgDroid(psDroid))
      {
            bLegs = true;
      }

      droidR = moveObjRadius((BASE_OBJECT *)psDroid);
      psObst = NULL;
      for(i=0; i<(SDWORD)numNaybors; i++)
      {
            psInfo = asDroidNaybors + i;
            if (psInfo->psObj->type == OBJ_DROID)
            {
                  if ( ((DROID *)psInfo->psObj)->droidType == DROID_TRANSPORTER )
                  {
                        // ignore transporters
                        continue;
                  }

                  if (bLegs
                   && ((DROID *)psInfo->psObj)->droidType != DROID_PERSON
//                 && ((DROID *)psInfo->psObj)->droidType != DROID_CYBORG)
                   && !cyborgDroid((DROID *)psInfo->psObj))
                  {
                        // cyborgs/people only avoid other cyborgs/people
                        continue;
                  }
                  if (!bLegs &&
                        (((DROID *)psInfo->psObj)->droidType == DROID_PERSON))
                  {
                        // everything else doesn't avoid people
                        continue;
                  }
            }
            else
            {
                  // ignore anything that isn't a droid
                  continue;
            }

            objR = moveObjRadius(psInfo->psObj);
            rad = droidR + objR;
            radSq = rad*rad;

            xdiff = psDroid->sMove.fx + *pmx - psInfo->psObj->pos.x;
            ydiff = psDroid->sMove.fy + *pmy - psInfo->psObj->pos.y;
            distSq = xdiff * xdiff + ydiff * ydiff;
            if ((float)xdiff * *pmx + (float)ydiff * *pmy >= 0)
            {
                  // object behind
                  continue;
            }

            if (radSq > distSq)
            {
                  if (psObst != NULL || !aiCheckAlliances(psInfo->psObj->player, psDroid->player))
                  {
                        // hit more than one droid - stop
                        *pmx = (float)0;
                        *pmy = (float)0;
                        psObst = NULL;
                        break;
                  }
                  else
                  {
//                      if (((DROID *)psInfo->psObj)->sMove.Status == MOVEINACTIVE)
                        psObst = psInfo->psObj;

                        // note the bump time and position if necessary
                        if (psDroid->sMove.bumpTime == 0)
                        {
                              psDroid->sMove.bumpTime = gameTime;
                              psDroid->sMove.lastBump = 0;
                              psDroid->sMove.pauseTime = 0;
                              psDroid->sMove.bumpX = psDroid->pos.x;
                              psDroid->sMove.bumpY = psDroid->pos.y;
                              psDroid->sMove.bumpDir = (SWORD)psDroid->direction;
                        }
                        else
                        {
                              psDroid->sMove.lastBump = (UWORD)(gameTime - psDroid->sMove.bumpTime);
                        }

                        // tell inactive droids to get out the way
                        if ((psObst->type == OBJ_DROID) &&
                              aiCheckAlliances(psObst->player, psDroid->player) &&
                              ((((DROID *)psObst)->sMove.Status == MOVEINACTIVE) ||
                               (((DROID *)psObst)->sMove.Status == MOVEROUTE)) )
                        {
                              if (psDroid->sMove.Status == MOVESHUFFLE)
                              {
                                    moveShuffleDroid( (DROID *)psObst, psDroid->sMove.shuffleStart,
                                          psDroid->sMove.targetX - (SDWORD)psDroid->pos.x,
                                          psDroid->sMove.targetY - (SDWORD)psDroid->pos.y);
                              }
                              else
                              {
                                    moveShuffleDroid( (DROID *)psObst, gameTime,
                                          psDroid->sMove.targetX - (SDWORD)psDroid->pos.x,
                                          psDroid->sMove.targetY - (SDWORD)psDroid->pos.y);
                              }
                        }
                  }
            }
            else if (psInfo->distSqr > OBJ_MAXRADIUS*OBJ_MAXRADIUS)
            {
                  // object is too far away to be hit
                  break;
            }
      }

      if (psObst != NULL)
      {
            // Try to slide round it
            moveCalcSlideVector(psDroid, (SDWORD)psObst->pos.x,(SDWORD)psObst->pos.y, pmx,pmy);
      }
      CHECK_DROID(psDroid);
}

// get an obstacle avoidance vector
static void moveGetObstacleVector(DROID *psDroid, float *pX, float *pY)
{
      SDWORD                        i, xdiff, ydiff, absx, absy, dist;
      BASE_OBJECT             *psObj;
      SDWORD                        numObst = 0, distTot = 0;
      float                   dirX = 0, dirY = 0;
      float                   omag, ox, oy, ratio;
      float                   avoidX, avoidY;
      SDWORD                        mapX, mapY, tx, ty, td;
      PROPULSION_STATS  *psPropStats;

      psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
      ASSERT( psPropStats != NULL,
                  "moveUpdateUnit: invalid propulsion stats pointer" );

      droidGetNaybors(psDroid);

      // scan the neighbours for obstacles
      for(i = 0; i < (SDWORD)numNaybors; i++)
      {
            psObj = asDroidNaybors[i].psObj;
            if (psObj->type != OBJ_DROID ||
                  asDroidNaybors[i].distSqr >= AVOID_DIST*AVOID_DIST)
            {
                  // object too far away to worry about
                  continue;
            }

            // vtol droids only avoid each other and don't affect ground droids
            if ( (isVtolDroid(psDroid) && (psObj->type != OBJ_DROID || !isVtolDroid((DROID *)psObj))) ||
                   (!isVtolDroid(psDroid) && psObj->type == OBJ_DROID && isVtolDroid((DROID *)psObj)) )
            {
                  continue;
            }

            if (((DROID *)psObj)->droidType == DROID_TRANSPORTER ||
                  (((DROID *)psObj)->droidType == DROID_PERSON &&
                   psObj->player != psDroid->player))
            {
                  // don't avoid people on the other side - run over them
                  continue;
            }
            xdiff = (SDWORD)psObj->pos.x - (SDWORD)psDroid->pos.x;
            ydiff = (SDWORD)psObj->pos.y - (SDWORD)psDroid->pos.y;
            if ((float)xdiff * *pX + (float)ydiff * *pY < 0)
            {
                  // object behind
                  continue;
            }

            absx = labs(xdiff);
            absy = labs(ydiff);
            dist = absx > absy ? absx + absy/2 : absx/2 + absy;

            if (dist != 0)
            {
                  dirX += (float)xdiff / (float)(dist * dist);
                  dirY += (float)ydiff / (float)(dist * dist);
                  distTot += dist*dist;
                  numObst += 1;
            }
            else
            {
                  dirX += xdiff;
                  dirY += ydiff;
                  numObst += 1;
            }
      }

      // now scan for blocking tiles
      mapX = map_coord(psDroid->pos.x);
      mapY = map_coord(psDroid->pos.y);
      for(ydiff=-2; ydiff<=2; ydiff++)
      {
            for(xdiff=-2; xdiff<=2; xdiff++)
            {
                  if ((float)xdiff * *pX + (float)ydiff * *pY <= 0)
                  {
                        // object behind
                        continue;
                  }
                  if (fpathBlockingTile(mapX+xdiff, mapY+ydiff))
                  {
                        tx = world_coord(xdiff);
                        ty = world_coord(ydiff);
                        td = tx*tx + ty*ty;
                        if (td < AVOID_DIST*AVOID_DIST)
                        {
                              absx = labs(tx);
                              absy = labs(ty);
                              dist = absx > absy ? absx + absy/2 : absx/2 + absy;

                              if (dist != 0)
                              {
                                    dirX += (float)tx / (float)(dist * dist);
                                    dirY += (float)ty / (float)(dist * dist);
                                    distTot += dist*dist;
                                    numObst += 1;
                              }
                        }
                  }
            }
      }

      if (numObst > 0)
      {
            distTot /= numObst;

            // Create the avoid vector
            if (dirX == 0 && dirY == 0)
            {
                  avoidX = 0;
                  avoidY = 0;
                  distTot = AVOID_DIST*AVOID_DIST;
            }
            else
            {
                  omag = sqrtf(dirX*dirX + dirY*dirY);
                  ox = dirX / omag;
                  oy = dirY / omag;
                  if (*pX * oy + *pY * -ox < 0)
                  {
//                      debug( LOG_NEVER, "First perp\n");
                        avoidX = -oy;
                        avoidY = ox;
                  }
                  else
                  {
//                      debug( LOG_NEVER, "Second perp\n");
                        avoidX = oy;
                        avoidY = -ox;
                  }
            }

            // combine the avoid vector and the target vector
            ratio = (float)distTot / (float)(AVOID_DIST * AVOID_DIST);
            if (ratio > 1)
            {
                  ratio = 1;
            }

            *pX = *pX * ratio + avoidX * (1.f - ratio);
            *pY = *pY * ratio + avoidY * (1.f - ratio);
      }
      ASSERT(isfinite(*pX) && isfinite(*pY), "moveGetObstacleVector: bad float");
}


/*!
 * Get a direction for a droid to avoid obstacles etc.
 * \param psDroid Which droid to examine
 * \return The normalised direction vector
 */
static Vector2f moveGetDirection(DROID *psDroid)
{
      // Current position and destination direction
      Vector2f
            src = { psDroid->pos.x, psDroid->pos.y },
            dest = { 0.0f, 0.0f };
      // Current waypoint
      Vector2f
            target = { psDroid->sMove.targetX, psDroid->sMove.targetY },
            delta = Vector2f_Sub(target, src);
      float magnitude = Vector2f_ScalarP(delta, delta);

      // Dont fade in the next target point if we are at finished or too far away from the current
      if (psDroid->sMove.Position == psDroid->sMove.numPoints || magnitude > WAYPOINT_DSQ)
      {
            dest = Vector2f_Normalise(delta);
      }
      // We are in reach of the current waypoint and have further points to go
      else
      {
            // Next waypoint
            Vector2f
                  nextTarget = Vector2i_To2f( movePeekNextTarget(psDroid) ),
                  nextDelta = Vector2f_Sub(nextTarget, src);
            float nextMagnitude = Vector2f_ScalarP(nextDelta, nextDelta);

            // We are already there
            if (magnitude == 0.0f && nextMagnitude == 0.0f)
            {
                  Vector2f zero = {0.0f, 0.0f};
                  return zero; // We are practically standing on our only waypoint
            }
            // We are passing the current waypoint, so directly head over to the next
            else if (magnitude == 0.0f)
            {
                  dest = Vector2f_Normalise(nextDelta);
            }
            // We are passing the next waypoint, so for now don't interpolate it
            else if (nextMagnitude == 0.0f)
            {
                  dest = Vector2f_Normalise(delta);
            }
            // Interpolate with the next target
            else
            {
                  // Interpolate the vectors based on how close to them we are
                  delta = Vector2f_Mult(delta, magnitude);
                  nextDelta = Vector2f_Mult(nextDelta, WAYPOINT_DSQ - magnitude);

                  dest = Vector2f_Normalise( Vector2f_Add(delta, nextDelta) );
            }
      }

      ASSERT(isfinite(dest.x) && isfinite(dest.y), "moveGetDirection: bad float, early check");

      // Transporters don't need to avoid obstacles, but everyone else should
      if ( psDroid->droidType != DROID_TRANSPORTER )
      {
            moveGetObstacleVector(psDroid, &dest.x, &dest.y);
      }

      ASSERT(isfinite(dest.x) && isfinite(dest.y), "moveGetDirection: bad float");

      return dest;
}


// Calculate the boundary vector
static void moveCalcBoundary(DROID *psDroid)
{
      Vector2i
            src = { psDroid->sMove.srcX, psDroid->sMove.srcY },
            target = { psDroid->sMove.targetX, psDroid->sMove.targetY },
            prev, next, nextTarget;
      SDWORD      absX, absY;
      SDWORD      prevMag, nextMag;
      SDWORD      sumX, sumY;

      // No points left - simple case for the bound vector
      if (psDroid->sMove.Position == psDroid->sMove.numPoints)
      {
            psDroid->sMove.boundX = src.x - target.x;
            psDroid->sMove.boundY = src.y - target.y;
            return;
      }

      // Calculate the vector back along the current path
      prev = Vector2i_Sub(src, target),
      absX = abs(prev.x);
      absY = abs(prev.y);
      prevMag = (absX > absY) ? (absX + absY/2) : (absY + absX/2);
//    prevMag = Vector2i_Length(prev);

      // Calculate the vector to the next target
      nextTarget = movePeekNextTarget(psDroid);

      next = Vector2i_Sub(nextTarget, target);
      absX = abs(next.x);
      absY = abs(next.y);
      nextMag = (absX > absY) ? (absX + absY/2) : (absY + absX/2);
//    nextMag = Vector2i_Length(next);

      if (prevMag != 0 && nextMag == 0)
      {
            // don't bother mixing the boundaries - cos there isn't a next vector anyway
            psDroid->sMove.boundX = src.x - target.x;
            psDroid->sMove.boundY = src.y - target.y;
            return;
      }
      else if (prevMag == 0 || nextMag == 0)
      {
            psDroid->sMove.boundX = 0;
            psDroid->sMove.boundY = 0;
            return;
      }

      // Calculate the vector between the two
      sumX = (prev.x * BOUND_ACC)/prevMag + (next.x * BOUND_ACC)/nextMag;
      sumY = (prev.y * BOUND_ACC)/prevMag + (next.y * BOUND_ACC)/nextMag;

      // Rotate by 90 degrees one way and see if it is the same side as the src vector
      // if not rotate 90 the other.
      if (prev.y*sumY - prev.y*sumX < 0)
      {
            psDroid->sMove.boundX = -sumY;
            psDroid->sMove.boundY = sumX;
      }
      else
      {
            psDroid->sMove.boundX = sumY;
            psDroid->sMove.boundY = -sumX;
      }

      debug( LOG_NEVER, "new boundary: droid %d boundary (%d,%d)\n",
                  psDroid->id, psDroid->sMove.boundX, psDroid->sMove.boundY);
}


// Check if a droid has got to a way point
static BOOL moveReachedWayPoint(DROID *psDroid)
{
      SDWORD      droidX,droidY, iRange;

      // Calculate the vector to the droid
      droidX = (SDWORD)psDroid->pos.x - psDroid->sMove.targetX;
      droidY = (SDWORD)psDroid->pos.y - psDroid->sMove.targetY;

      // see if this is a formation end point
      if (psDroid->droidType == DROID_TRANSPORTER ||
            (psDroid->sMove.psFormation &&
             formationMember(psDroid->sMove.psFormation, psDroid)) ||
             (isVtolDroid(psDroid) && (psDroid->sMove.numPoints == psDroid->sMove.Position)) )
//                                         && (psDroid->action != DACTION_VTOLATTACK)) )
      {
            if ( psDroid->droidType == DROID_TRANSPORTER )
            {

                  iRange = TILE_UNITS/4;

            }
            else
            {
                  iRange = TILE_UNITS/4;
            }

            if (droidX*droidX + droidY*droidY < iRange*iRange)
            {
                  return true;
            }
      }
      else
      {
            // if the dot product is -ve the droid has got past the way point
            // but only move onto the next way point if we can see the previous one
            // (this helps units that have got nudged off course).
            if ((psDroid->sMove.boundX * droidX + psDroid->sMove.boundY * droidY <= 0) &&
                  fpathTileLOS(map_coord(psDroid->pos.x), map_coord(psDroid->pos.y), map_coord(psDroid->sMove.targetX), map_coord(psDroid->sMove.targetY)))
            {
                  objTrace(LOG_MOVEMENT, psDroid->id, "Next waypoint: droid %d bound (%d,%d) target (%d,%d)",
                           (int)psDroid->id, (int)psDroid->sMove.boundX, (int)psDroid->sMove.boundY, (int)droidX, (int)droidY);

                  return true;
            }
      }

      return false;
}

void moveToggleFormationSpeedLimiting( void )
{
      g_bFormationSpeedLimitingOn = !g_bFormationSpeedLimitingOn;
}

void moveSetFormationSpeedLimiting( BOOL bVal )
{
      g_bFormationSpeedLimitingOn = bVal;
}

BOOL moveFormationSpeedLimitingOn( void )
{
      return g_bFormationSpeedLimitingOn;
}

#define MAX_SPEED_PITCH  60

/** Calculate the new speed for a droid based on factors like damage and pitch.
 *  @todo Remove hack for steep slopes not properly marked as blocking on some maps.
 */
01921 SDWORD moveCalcDroidSpeed(DROID *psDroid)
{
      UDWORD                  mapX,mapY, damLevel;
      SDWORD                  speed, pitch;
      WEAPON_STATS      *psWStats;

      CHECK_DROID(psDroid);

      mapX = map_coord(psDroid->pos.x);
      mapY = map_coord(psDroid->pos.y);
      speed = (SDWORD) calcDroidSpeed(psDroid->baseSpeed, terrainType(mapTile(mapX,mapY)),
                                            psDroid->asBits[COMP_PROPULSION].nStat,
                                            getDroidEffectiveLevel(psDroid));

      pitch = psDroid->pitch;
      if (pitch > MAX_SPEED_PITCH)
      {
            pitch = MAX_SPEED_PITCH;
      }
      else if (pitch < - MAX_SPEED_PITCH)
      {
            pitch = -MAX_SPEED_PITCH;
      }
      // now offset the speed for the slope of the droid
      speed = (MAX_SPEED_PITCH - pitch) * speed / MAX_SPEED_PITCH;
      if (speed <= 0)
      {
            // Very nasty hack to deal with buggy maps, where some cliffs are
            // not properly marked as being cliffs, but too steep to drive over.
            // This confuses the heck out of the path-finding code! - Per
            speed = 10;
      }

      // slow down damaged droids
      damLevel = PERCENT(psDroid->body, psDroid->originalBody);
      if (damLevel < HEAVY_DAMAGE_LEVEL)
      {
            speed = 2 * speed / 3;
      }

      // stop droids that have just fired a no fire while moving weapon
      if (psDroid->numWeaps > 0)
      {
            if (psDroid->asWeaps[0].nStat > 0 && psDroid->asWeaps[0].lastFired + FOM_MOVEPAUSE > gameTime)
            {
                  psWStats = asWeaponStats + psDroid->asWeaps[0].nStat;
                  if (psWStats->fireOnMove == FOM_NO)
                  {
                        speed = 0;
                  }
            }
      }

      /* adjust speed for formation */
      if(!isVtolDroid(psDroid) &&
            moveFormationSpeedLimitingOn() && psDroid->sMove.psFormation)
      {
            SDWORD FrmSpeed = (SDWORD)psDroid->sMove.psFormation->iSpeed;

            if ( speed > FrmSpeed )
            {
                  speed = FrmSpeed;
            }
      }

      // slow down shuffling VTOLs
      if (isVtolDroid(psDroid) &&
            (psDroid->sMove.Status == MOVESHUFFLE) &&
            (speed > MIN_END_SPEED))
      {
            speed = MIN_END_SPEED;
      }

      CHECK_DROID(psDroid);

      return speed;
}

/** Determine whether a droid has stopped moving.
 *  @return true if the droid doesn't move, false if it's moving.
 */
static BOOL moveDroidStopped(DROID* psDroid, SDWORD speed)
{
      if ((psDroid->sMove.Status == MOVEINACTIVE
        || psDroid->sMove.Status == MOVEROUTE)
       && speed == 0
       && psDroid->sMove.speed == 0)
      {
            return true;
      }
      else
      {
            return false;
      }
}

static void moveUpdateDroidDirection( DROID *psDroid, SDWORD *pSpeed, SDWORD direction,
            SDWORD iSpinAngle, SDWORD iSpinSpeed, SDWORD iTurnSpeed, float *pDroidDir,
            float *pfSpeed ) // direction is target-direction
{
      float       adiff;
      float       temp;

      *pfSpeed = *pSpeed;
      *pDroidDir = psDroid->direction;

      // don't move if in MOVEPAUSE state
      if (psDroid->sMove.Status == MOVEPAUSE)
      {
            return;
      }

      temp = *pDroidDir;
      adiff = fabsf(direction - *pDroidDir);
      if (adiff > TRIG_DEGREES/2)
      {
            adiff = TRIG_DEGREES - adiff;
      }
      if (adiff > iSpinAngle)
      {
            // large change in direction, spin on the spot
            moveCalcTurn(&temp, direction, iSpinSpeed);
            *pSpeed = 0;
      }
      else
      {
            // small change in direction, turn while moving
            moveCalcTurn(&temp, direction, iTurnSpeed);
      }

      *pDroidDir = temp;
}


// Calculate current speed perpendicular to droids direction
static float moveCalcPerpSpeed( DROID *psDroid, float iDroidDir, SDWORD iSkidDecel )
{
      float       adiff;
      float       perpSpeed;

      adiff = fabsf(iDroidDir - psDroid->sMove.moveDir);
      perpSpeed = psDroid->sMove.speed * trigSin(adiff);

      // decelerate the perpendicular speed
      perpSpeed -= (iSkidDecel * baseSpeed);
      if (perpSpeed < 0)
      {
            perpSpeed = 0;
      }

      return perpSpeed;
}


static void moveCombineNormalAndPerpSpeeds( DROID *psDroid, float fNormalSpeed,
            float fPerpSpeed, float iDroidDir )
{
      float       finalDir, adiff;
      float       finalSpeed;

      /* set current direction */
      psDroid->direction = iDroidDir;

      /* set normal speed and direction if perpendicular speed is zero */
      if (fPerpSpeed == 0)
      {
            psDroid->sMove.speed = fNormalSpeed;
            psDroid->sMove.moveDir = iDroidDir;
            return;
      }

      finalSpeed = sqrtf(fNormalSpeed * fNormalSpeed + fPerpSpeed * fPerpSpeed);

      // calculate the angle between the droid facing and movement direction
      finalDir = trigInvCos(fNormalSpeed / finalSpeed);

      // choose the finalDir on the same side as the old movement direction
      adiff = fabsf(iDroidDir - psDroid->sMove.moveDir);
      if (adiff < TRIG_DEGREES/2)
      {
            if (iDroidDir > psDroid->sMove.moveDir)
            {
                  finalDir = iDroidDir - finalDir;
            }
            else
            {
                  finalDir = iDroidDir + finalDir;
            }
      }
      else
      {
            if (iDroidDir > psDroid->sMove.moveDir)
            {
                  finalDir = iDroidDir + finalDir;
                  if (finalDir >= TRIG_DEGREES)
                  {
                        finalDir -= TRIG_DEGREES;
                  }
            }
            else
            {
                  finalDir = iDroidDir - finalDir;
                  if (finalDir < 0)
                  {
                        finalDir += TRIG_DEGREES;
                  }
            }
      }

      psDroid->sMove.moveDir = finalDir;
      psDroid->sMove.speed = finalSpeed;
}


// Calculate the current speed in the droids normal direction
static float moveCalcNormalSpeed( DROID *psDroid, float fSpeed, float iDroidDir,
            SDWORD iAccel, SDWORD iDecel )
{
      float       adiff;
      float       normalSpeed;

      adiff = fabsf(iDroidDir - psDroid->sMove.moveDir);
      normalSpeed = psDroid->sMove.speed * trigCos(adiff);

      if (normalSpeed < fSpeed)
      {
            // accelerate
            normalSpeed += (iAccel * baseSpeed);
            if (normalSpeed > fSpeed)
            {
                  normalSpeed = fSpeed;
            }
      }
      else
      {
            // decelerate
            normalSpeed -= (iDecel * baseSpeed);
            if (normalSpeed < fSpeed)
            {
                  normalSpeed = fSpeed;
            }
      }

      return normalSpeed;
}


static void moveGetDroidPosDiffs( DROID *psDroid, float *pDX, float *pDY )
{
      float move;

      move = (float)psDroid->sMove.speed * (float)baseSpeed;

      *pDX = move * trigSin(psDroid->sMove.moveDir);
      *pDY = move * trigCos(psDroid->sMove.moveDir);
}

// see if the droid is close to the final way point
static void moveCheckFinalWaypoint( DROID *psDroid, SDWORD *pSpeed )
{
      SDWORD            xdiff,ydiff, distSq;
      SDWORD            minEndSpeed = psDroid->baseSpeed/3;

      if (minEndSpeed > MIN_END_SPEED)
      {
            minEndSpeed = MIN_END_SPEED;
      }

      // don't do this for VTOLs doing attack runs
      if (isVtolDroid(psDroid) && (psDroid->action == DACTION_VTOLATTACK))
      {
            return;
      }

      if (*pSpeed > minEndSpeed &&
            (psDroid->sMove.Status != MOVESHUFFLE) &&
            psDroid->sMove.Position == psDroid->sMove.numPoints)
      {
            xdiff = (SDWORD)psDroid->pos.x - psDroid->sMove.targetX;
            ydiff = (SDWORD)psDroid->pos.y - psDroid->sMove.targetY;
            distSq = xdiff*xdiff + ydiff*ydiff;
            if (distSq < END_SPEED_RANGE*END_SPEED_RANGE)
            {
                  *pSpeed = (MAX_END_SPEED-minEndSpeed) * distSq
                                    / (END_SPEED_RANGE*END_SPEED_RANGE) + minEndSpeed;
            }
      }
}

static void moveUpdateDroidPos( DROID *psDroid, float dx, float dy )
{
      SDWORD      iX = 0, iY = 0;

      CHECK_DROID(psDroid);

      if (psDroid->sMove.Status == MOVEPAUSE)
      {
            // don't actually move if the move is paused
            return;
      }

      psDroid->sMove.fx += dx;
      psDroid->sMove.fy += dy;

      iX = psDroid->sMove.fx;
      iY = psDroid->sMove.fy;

      /* impact if about to go off map else update coordinates */
      if ( worldOnMap( iX, iY ) == false )
      {
            /* transporter going off-world will trigger next map, and is ok */
            ASSERT(psDroid->droidType == DROID_TRANSPORTER, "droid trying to move off the map!")
            if (psDroid->droidType != DROID_TRANSPORTER)
            {
                  /* dreadful last-ditch crash-avoiding hack - sort this! - GJ */
                  destroyDroid( psDroid );
                  return;
            }
      }
      else
      {
            psDroid->pos.x = (UWORD)iX;
            psDroid->pos.y = (UWORD)iY;
      }

      // lovely hack to keep transporters just on the map
      // two weeks to go and the hacks just get better !!!
      if (psDroid->droidType == DROID_TRANSPORTER)
      {
            if (psDroid->pos.x == 0)
            {
                  psDroid->pos.x = 1;
            }
            if (psDroid->pos.y == 0)
            {
                  psDroid->pos.y = 1;
            }
      }
      CHECK_DROID(psDroid);
}

/* Update a tracked droids position and speed given target values */
static void moveUpdateGroundModel(DROID *psDroid, SDWORD speed, SDWORD direction)
{
      float                   fPerpSpeed, fNormalSpeed, dx, dy, fSpeed, bx,by;
      float                   iDroidDir;
      SDWORD                        slideDir;
      PROPULSION_STATS  *psPropStats;
      SDWORD                        spinSpeed, turnSpeed, skidDecel;
      // constants for the different propulsion types
      static SDWORD           hvrSkid = HOVER_SKID_DECEL;
      static SDWORD           whlSkid = WHEELED_SKID_DECEL;
      static SDWORD           trkSkid = TRACKED_SKID_DECEL;
      static float            hvrTurn = 3.f / 4.f;          //0.75f;
      static float            whlTurn = 1.f / 1.f;          //1.0f;
      static float            trkTurn = 1.f / 1.f;          //1.0f;

      CHECK_DROID(psDroid);

      psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
      switch (psPropStats->propulsionType)
      {
      case HOVER:
            spinSpeed = psDroid->baseSpeed * hvrTurn;
            turnSpeed = psDroid->baseSpeed / 3 * hvrTurn;
            skidDecel = hvrSkid;//HOVER_SKID_DECEL;
            break;
      case WHEELED:
            spinSpeed = psDroid->baseSpeed * hvrTurn;
            turnSpeed = psDroid->baseSpeed / 3 * whlTurn;
            skidDecel = whlSkid;//WHEELED_SKID_DECEL;
            break;
      case TRACKED:
      default:
            spinSpeed = psDroid->baseSpeed * hvrTurn;
            turnSpeed = psDroid->baseSpeed / 3 * trkTurn;
            skidDecel = trkSkid;//TRACKED_SKID_DECEL;
            break;
      }

      // nothing to do if the droid is stopped
      if ( moveDroidStopped( psDroid, speed ) == true )
      {
            return;
      }

      // update the naybors list
      droidGetNaybors(psDroid);

      moveCheckFinalWaypoint( psDroid, &speed );

      moveUpdateDroidDirection( psDroid, &speed, direction, TRACKED_SPIN_ANGLE,
                        spinSpeed, turnSpeed, &iDroidDir, &fSpeed );

      fNormalSpeed = moveCalcNormalSpeed(psDroid, fSpeed, iDroidDir, TRACKED_ACCEL, TRACKED_DECEL);
      fPerpSpeed   = moveCalcPerpSpeed( psDroid, iDroidDir, skidDecel );

      moveCombineNormalAndPerpSpeeds(psDroid, fNormalSpeed, fPerpSpeed, iDroidDir);
      moveGetDroidPosDiffs( psDroid, &dx, &dy );

      moveCheckSquished(psDroid, dx,dy);
      moveCalcDroidSlide(psDroid, &dx,&dy);
      bx = dx;
      by = dy;
      moveCalcBlockingSlide(psDroid, &bx,&by, direction, &slideDir);
      if (bx != dx || by != dy)
      {
            moveUpdateDroidDirection( psDroid, &speed, slideDir, TRACKED_SPIN_ANGLE,
                              psDroid->baseSpeed, psDroid->baseSpeed/3, &iDroidDir, &fSpeed );
            psDroid->direction = iDroidDir;
      }

      moveUpdateDroidPos( psDroid, bx, by );

      //set the droid height here so other routines can use it
      psDroid->pos.z = map_Height(psDroid->pos.x, psDroid->pos.y);//jps 21july96
      updateDroidOrientation(psDroid);
}

/* Update a persons position and speed given target values */
static void moveUpdatePersonModel(DROID *psDroid, SDWORD speed, SDWORD direction)
{
      float             fPerpSpeed, fNormalSpeed, dx, dy, fSpeed;
      float             iDroidDir;
      SDWORD                  slideDir;
      BOOL              bRet;

      CHECK_DROID(psDroid);

      // nothing to do if the droid is stopped
      if ( moveDroidStopped( psDroid, speed ) == true )
      {
            if ( psDroid->droidType == DROID_PERSON &&
                   psDroid->order != DORDER_RUNBURN &&
                   (psDroid->action == DACTION_ATTACK ||
                    psDroid->action == DACTION_ROTATETOATTACK) )
            {
                  /* remove previous anim */
                  if ( psDroid->psCurAnim != NULL &&
                         psDroid->psCurAnim->psAnim->uwID != ID_ANIM_DROIDFIRE )
                  {
                        bRet = animObj_Remove( &psDroid->psCurAnim, psDroid->psCurAnim->psAnim->uwID );
                        ASSERT( bRet == true, "moveUpdatePersonModel: animObj_Remove failed" );
                        psDroid->psCurAnim = NULL;
                  }

                  /* add firing anim */
                  if ( psDroid->psCurAnim == NULL )
                  {
                        psDroid->psCurAnim = animObj_Add( psDroid, ID_ANIM_DROIDFIRE, 0, 0 );
                  }
                  else
                  {
                        psDroid->psCurAnim->bVisible = true;
                  }

                  return;
            }

            /* don't show move animations if inactive */
            if ( psDroid->psCurAnim != NULL )
            {
                  psDroid->psCurAnim->bVisible = false;
            }
            return;
      }

      // update the naybors list
      droidGetNaybors(psDroid);

      moveUpdateDroidDirection( psDroid, &speed, direction, PERSON_SPIN_ANGLE,
                        PERSON_SPIN_SPEED, PERSON_TURN_SPEED, &iDroidDir, &fSpeed );

      fNormalSpeed = moveCalcNormalSpeed( psDroid, fSpeed, iDroidDir,
                                                            PERSON_ACCEL, PERSON_DECEL );
      /* people don't skid at the moment so set zero perpendicular speed */
      fPerpSpeed = 0;

      moveCombineNormalAndPerpSpeeds( psDroid, fNormalSpeed,
                                                            fPerpSpeed, iDroidDir );

      moveGetDroidPosDiffs( psDroid, &dx, &dy );

      moveCalcDroidSlide(psDroid, &dx,&dy);
      moveCalcBlockingSlide(psDroid, &dx,&dy, direction, &slideDir);

      moveUpdateDroidPos( psDroid, dx, dy );

      //set the droid height here so other routines can use it
      psDroid->pos.z = map_Height(psDroid->pos.x, psDroid->pos.y);//jps 21july96

      psDroid->sMove.fz = psDroid->pos.z;


      /* update anim if moving and not on fire */
      if ( psDroid->droidType == DROID_PERSON && speed != 0 &&
             psDroid->order != DORDER_RUNBURN )
      {
            /* remove previous anim */
            if ( psDroid->psCurAnim != NULL &&
                   (psDroid->psCurAnim->psAnim->uwID != ID_ANIM_DROIDRUN ||
                    psDroid->psCurAnim->psAnim->uwID != ID_ANIM_DROIDRUN)   )
            {
                  bRet = animObj_Remove( &psDroid->psCurAnim, psDroid->psCurAnim->psAnim->uwID );
                  ASSERT( bRet == true, "moveUpdatePersonModel: animObj_Remove failed" );
                  psDroid->psCurAnim = NULL;
            }

            /* if no anim currently attached, get one */
            if ( psDroid->psCurAnim == NULL )
            {
                  // Only add the animation if the droid is on screen, saves memory and time.
                  if(clipXY(psDroid->pos.x,psDroid->pos.y)) {
                        debug( LOG_NEVER, "Added person run anim\n" );
                        psDroid->psCurAnim = animObj_Add( (BASE_OBJECT *) psDroid,
                                                                              ID_ANIM_DROIDRUN, 0, 0 );
                  }
            } else {
                  // If the droid went off screen then remove the animation, saves memory and time.
                  if(!clipXY(psDroid->pos.x,psDroid->pos.y)) {
                        bRet = animObj_Remove( &psDroid->psCurAnim, psDroid->psCurAnim->psAnim->uwID );
                        ASSERT( bRet == true, "moveUpdatePersonModel : animObj_Remove failed" );
                        psDroid->psCurAnim = NULL;
                        debug( LOG_NEVER, "Removed person run anim\n" );
                  }
            }
      }

      /* show anim */
      if ( psDroid->psCurAnim != NULL )
      {
            psDroid->psCurAnim->bVisible = true;
      }
      CHECK_DROID(psDroid);
}

#define     VTOL_VERTICAL_SPEED           ((((SDWORD)psDroid->baseSpeed / 4) > 60) ? ((SDWORD)psDroid->baseSpeed / 4) : 60)

/* primitive 'bang-bang' vtol height controller */
static void moveAdjustVtolHeight( DROID * psDroid, UDWORD iMapHeight )
{
      UDWORD      iMinHeight, iMaxHeight, iLevelHeight;

      if ( psDroid->droidType == DROID_TRANSPORTER )
      {
            iMinHeight   = 2*VTOL_HEIGHT_MIN;
            iLevelHeight = 2*VTOL_HEIGHT_LEVEL;
            iMaxHeight   = 2*VTOL_HEIGHT_MAX;
      }
      else
      {
            iMinHeight   = VTOL_HEIGHT_MIN;
            iLevelHeight = VTOL_HEIGHT_LEVEL;
            iMaxHeight   = VTOL_HEIGHT_MAX;
      }

      if ( psDroid->pos.z >= (iMapHeight+iMaxHeight) )
      {
            psDroid->sMove.iVertSpeed = (SWORD)-VTOL_VERTICAL_SPEED;
      }
      else if ( psDroid->pos.z < (iMapHeight+iMinHeight) )
      {
            psDroid->sMove.iVertSpeed = (SWORD)VTOL_VERTICAL_SPEED;
      }
      else if ( (psDroid->pos.z < iLevelHeight) &&
                    (psDroid->sMove.iVertSpeed < 0 )    )
      {
            psDroid->sMove.iVertSpeed = 0;
      }
      else if ( (psDroid->pos.z > iLevelHeight) &&
                    (psDroid->sMove.iVertSpeed > 0 )    )
      {
            psDroid->sMove.iVertSpeed = 0;
      }
}

// set a vtol to be hovering in the air
void moveMakeVtolHover( DROID *psDroid )
{
      psDroid->sMove.Status = MOVEHOVER;
      psDroid->pos.z = (UWORD)(map_Height(psDroid->pos.x,psDroid->pos.y) + VTOL_HEIGHT_LEVEL);
}

static void moveUpdateVtolModel(DROID *psDroid, SDWORD speed, SDWORD direction)
{
      float fPerpSpeed, fNormalSpeed, dx, dy, fSpeed;
      float iDroidDir;
      SDWORD      iMapZ, iRoll, slideDir, iSpinSpeed, iTurnSpeed;
      float fDZ, fDroidZ, fMapZ;

      CHECK_DROID(psDroid);

      // nothing to do if the droid is stopped
      if ( moveDroidStopped(  psDroid, speed ) == true )
      {
            return;
      }

      // update the naybors list
      droidGetNaybors(psDroid);

      moveCheckFinalWaypoint( psDroid, &speed );

      if ( psDroid->droidType == DROID_TRANSPORTER )
      {
            moveUpdateDroidDirection( psDroid, &speed, direction, VTOL_SPIN_ANGLE,
                              VTOL_SPIN_SPEED, VTOL_TURN_SPEED, &iDroidDir, &fSpeed );
      }
      else
      {
            iSpinSpeed = (psDroid->baseSpeed/2 > VTOL_SPIN_SPEED) ? psDroid->baseSpeed/2 : VTOL_SPIN_SPEED;
            iTurnSpeed = (psDroid->baseSpeed/8 > VTOL_TURN_SPEED) ? psDroid->baseSpeed/8 : VTOL_TURN_SPEED;
            moveUpdateDroidDirection( psDroid, &speed, direction, VTOL_SPIN_ANGLE,
                        iSpinSpeed, iTurnSpeed, &iDroidDir, &fSpeed );
      }

      fNormalSpeed = moveCalcNormalSpeed( psDroid, fSpeed, iDroidDir,
                                                            VTOL_ACCEL, VTOL_DECEL );
      fPerpSpeed   = moveCalcPerpSpeed( psDroid, iDroidDir, VTOL_SKID_DECEL );

      moveCombineNormalAndPerpSpeeds( psDroid, fNormalSpeed,
                                                            fPerpSpeed, iDroidDir );

      moveGetDroidPosDiffs( psDroid, &dx, &dy );

      /* set slide blocking tile for map edge */
      if ( psDroid->droidType != DROID_TRANSPORTER )
      {
            fpathBlockingTile = fpathLiftSlideBlockingTile;
            moveCalcBlockingSlide( psDroid, &dx, &dy, direction, &slideDir );
            fpathBlockingTile = fpathGroundBlockingTile;
      }

      moveUpdateDroidPos( psDroid, dx, dy );

      /* update vtol orientation */
      iRoll = (psDroid->sMove.moveDir - psDroid->direction) / 3;
      if ( iRoll < 0 )
      {
            iRoll += 360;
      }
      psDroid->roll = (UWORD) iRoll;

      iMapZ = map_Height(psDroid->pos.x, psDroid->pos.y);

      /* do vertical movement */

      fDZ = timeAdjustedIncrement(psDroid->sMove.iVertSpeed, true);
      fDroidZ = psDroid->sMove.fz;
      fMapZ = (float) map_Height(psDroid->pos.x, psDroid->pos.y);
      if ( fDroidZ+fDZ < 0 )
      {
            psDroid->sMove.fz = 0;
      }
      else if ( fDroidZ+fDZ < fMapZ )
      {
            psDroid->sMove.fz = fMapZ;
      }
      else
      {
            psDroid->sMove.fz = psDroid->sMove.fz + fDZ;
      }
      psDroid->pos.z = (UWORD)psDroid->sMove.fz;


      moveAdjustVtolHeight( psDroid, iMapZ );
}

#define CYBORG_VERTICAL_SPEED ((SDWORD)psDroid->baseSpeed/2)

static void
moveCyborgLaunchAnimDone( ANIM_OBJECT *psObj )
{
      DROID *psDroid = (DROID*)psObj->psParent;

      ASSERT( psDroid != NULL,
                  "moveCyborgLaunchAnimDone: invalid cyborg pointer" );

      /* raise cyborg a little bit so flying - terrible hack - GJ */
      psDroid->pos.z++;
      psDroid->sMove.iVertSpeed = (SWORD)CYBORG_VERTICAL_SPEED;

      psDroid->psCurAnim = NULL;
}

static void
moveCyborgTouchDownAnimDone( ANIM_OBJECT *psObj )
{
      DROID *psDroid = (DROID*)psObj->psParent;

      ASSERT( psDroid != NULL,
                  "moveCyborgTouchDownAnimDone: invalid cyborg pointer" );

      psDroid->psCurAnim = NULL;
      psDroid->pos.z = map_Height( psDroid->pos.x, psDroid->pos.y );
}


static void moveUpdateJumpCyborgModel(DROID *psDroid, SDWORD speed, SDWORD direction)
{
      float fPerpSpeed, fNormalSpeed, dx, dy, fSpeed;
      float iDroidDir;

      // nothing to do if the droid is stopped
      if ( moveDroidStopped(  psDroid, speed ) == true )
      {
            return;
      }

      // update the naybors list
      droidGetNaybors(psDroid);

      moveUpdateDroidDirection( psDroid, &speed, direction, VTOL_SPIN_ANGLE,
                        psDroid->baseSpeed, psDroid->baseSpeed/3, &iDroidDir, &fSpeed );

      fNormalSpeed = moveCalcNormalSpeed( psDroid, fSpeed, iDroidDir,
                                                            VTOL_ACCEL, VTOL_DECEL );
      fPerpSpeed   = 0;
      moveCombineNormalAndPerpSpeeds( psDroid, fNormalSpeed,
                                                            fPerpSpeed, iDroidDir );

      moveGetDroidPosDiffs( psDroid, &dx, &dy );
      moveUpdateDroidPos( psDroid, dx, dy );
}

static void
moveUpdateCyborgModel( DROID *psDroid, SDWORD moveSpeed, SDWORD moveDir, UBYTE oldStatus )
{
      PROPULSION_STATS  *psPropStats;
      BASE_OBJECT             *psObj = (BASE_OBJECT *) psDroid;
      UDWORD                        iMapZ = map_Height(psDroid->pos.x, psDroid->pos.y);
      SDWORD                        iDist, iDx, iDy, iDz, iDroidZ;
      BOOL              bRet;

      CHECK_DROID(psDroid);

      // nothing to do if the droid is stopped
      if ( moveDroidStopped( psDroid, moveSpeed ) == true )
      {
            if ( psDroid->psCurAnim != NULL )
            {
                  if ( animObj_Remove( &psDroid->psCurAnim, psDroid->psCurAnim->uwID ) == false )
                  {
                        debug( LOG_NEVER, "moveUpdateCyborgModel: couldn't remove walk anim\n" );
                  }
                  psDroid->psCurAnim = NULL;
            }

            return;
      }

      psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
      ASSERT( psPropStats != NULL,
                  "moveUpdateCyborgModel: invalid propulsion stats pointer" );

      /* do vertical movement */
      if ( psPropStats->propulsionType == JUMP )
      {
            iDz = timeAdjustedIncrement(psDroid->sMove.iVertSpeed, true);
            iDroidZ = (SDWORD) psDroid->pos.z;

            if ( iDroidZ+iDz < (SDWORD) iMapZ )
            {
                  psDroid->sMove.iVertSpeed = 0;
                  psDroid->pos.z = (UWORD)iMapZ;
            }
            else
            {
                  psDroid->pos.z = (UWORD)(psDroid->pos.z + iDz);
            }

            if ( (psDroid->pos.z >= (iMapZ+CYBORG_MAX_JUMP_HEIGHT)) &&
                   (psDroid->sMove.iVertSpeed > 0)                        )
            {
                  psDroid->sMove.iVertSpeed = (SWORD)-CYBORG_VERTICAL_SPEED;
            }


            psDroid->sMove.fz = psDroid->pos.z;

      }

      /* calculate move distance */
      iDx = psDroid->sMove.DestinationX - psDroid->pos.x;
      iDy = psDroid->sMove.DestinationY - psDroid->pos.y;
      iDz = psDroid->pos.z - iMapZ;
      iDist = trigIntSqrt(iDx * iDx + iDy * iDy);

      /* set jumping cyborg walking short distances */
      if ( (psPropStats->propulsionType != JUMP) ||
             ((psDroid->sMove.iVertSpeed == 0)      &&
              (iDist < CYBORG_MIN_JUMP_DISTANCE))       )
      {
            if ( psDroid->psCurAnim == NULL )
            {
                  // Only add the animation if the droid is on screen, saves memory and time.
                  if(clipXY(psDroid->pos.x,psDroid->pos.y))
                  {
                        if ( psDroid->droidType == DROID_CYBORG_SUPER )
                        {
                              psDroid->psCurAnim = animObj_Add( psObj, ID_ANIM_SUPERCYBORG_RUN, 0, 0 );
                        }
                        else if (cyborgDroid(psDroid))
                        {
                              psDroid->psCurAnim = animObj_Add( psObj, ID_ANIM_CYBORG_RUN, 0, 0 );
                        }
                  }
            } else {
                  // If the droid went off screen then remove the animation, saves memory and time.
                  if(!clipXY(psDroid->pos.x,psDroid->pos.y))
                  {
                        bRet = animObj_Remove( &psDroid->psCurAnim, psDroid->psCurAnim->psAnim->uwID );
                        ASSERT( bRet == true, "moveUpdateCyborgModel : animObj_Remove failed" );
                        psDroid->psCurAnim = NULL;
                  }
            }

            /* use baba person movement */
            moveUpdatePersonModel(psDroid,moveSpeed,moveDir);
      }
      else
      {
            /* jumping cyborg: remove walking animation if present */
            if ( psDroid->psCurAnim != NULL )
            {
                  if ( (psDroid->psCurAnim->uwID == ID_ANIM_CYBORG_RUN ||
                          psDroid->psCurAnim->uwID == ID_ANIM_SUPERCYBORG_RUN ||
                          psDroid->psCurAnim->uwID == ID_ANIM_CYBORG_PACK_RUN) &&
                         (animObj_Remove( &psDroid->psCurAnim, psDroid->psCurAnim->uwID ) == false) )
                  {
                        debug( LOG_NEVER, "moveUpdateCyborgModel: couldn't remove walk anim\n" );
                  }
            }

            /* add jumping or landing anim */
            if ( (oldStatus == MOVEPOINTTOPOINT) &&
                          (psDroid->sMove.Status == MOVEINACTIVE) )
            {
                  psDroid->psCurAnim = animObj_Add( psObj, ID_ANIM_CYBORG_PACK_LAND, 0, 1 );
                  animObj_SetDoneFunc( psDroid->psCurAnim, moveCyborgTouchDownAnimDone );
            }
            else if ( psDroid->sMove.Status == MOVEPOINTTOPOINT )
            {
                  if ( psDroid->pos.z == iMapZ )
                  {
                        if ( psDroid->sMove.iVertSpeed == 0 )
                        {
                              psDroid->psCurAnim = animObj_Add( psObj, ID_ANIM_CYBORG_PACK_JUMP, 0, 1 );
                              animObj_SetDoneFunc( psDroid->psCurAnim, moveCyborgLaunchAnimDone );
                        }
                        else
                        {
                              psDroid->psCurAnim = animObj_Add( psObj, ID_ANIM_CYBORG_PACK_LAND, 0, 1 );
                              animObj_SetDoneFunc( psDroid->psCurAnim, moveCyborgTouchDownAnimDone );
                        }
                  }
                  else
                  {
                        moveUpdateJumpCyborgModel( psDroid, moveSpeed, moveDir );
                  }
            }
      }

      psDroid->pitch = 0;
      psDroid->roll  = 0;
}

static BOOL moveDescending( DROID *psDroid, UDWORD iMapHeight )
{
      if ( psDroid->pos.z > iMapHeight )
      {
            /* descending */
            psDroid->sMove.iVertSpeed = (SWORD)-VTOL_VERTICAL_SPEED;
            psDroid->sMove.speed = 0;

            /* return true to show still descending */
            return true;
      }
      else
      {
            /* on floor - stop */
            psDroid->sMove.iVertSpeed = 0;

            /* conform to terrain */
            updateDroidOrientation(psDroid);

            /* return false to show stopped descending */
            return false;
      }
}


BOOL moveCheckDroidMovingAndVisible( void *psObj )
{
      DROID *psDroid = (DROID*)psObj;

      if ( psDroid == NULL )
      {
            return false;
      }

      /* check for dead, not moving or invisible to player */
      if ( psDroid->died || moveDroidStopped( psDroid, 0 ) ||
             (psDroid->droidType == DROID_TRANSPORTER && psDroid->order == DORDER_NONE) ||
             !(psDroid->visible[selectedPlayer] || godMode)                                )
      {
            psDroid->iAudioID = NO_SOUND;
            return false;
      }

      return true;
}


static void movePlayDroidMoveAudio( DROID *psDroid )
{
      SDWORD                        iAudioID = NO_SOUND;
      PROPULSION_TYPES  *psPropType;
      UBYTE                   iPropType = 0;

      ASSERT( psDroid != NULL,
            "movePlayUnitMoveAudio: unit pointer invalid\n" );

      if ( (psDroid != NULL) &&
             (psDroid->visible[selectedPlayer] || godMode) )
      {
            iPropType = asPropulsionStats[(psDroid)->asBits[COMP_PROPULSION].nStat].propulsionType;
            psPropType = &asPropulsionTypes[iPropType];

            /* play specific wheeled and transporter or stats-specified noises */
            if ( iPropType == WHEELED && psDroid->droidType != DROID_CONSTRUCT )
            {
                  iAudioID = ID_SOUND_TREAD;
            }
            else if (psDroid->droidType == DROID_TRANSPORTER)
            {
                  iAudioID = ID_SOUND_BLIMP_FLIGHT;
            }
            else if (iPropType == LEGGED && cyborgDroid(psDroid))
            {
                  iAudioID = ID_SOUND_CYBORG_MOVE;
            }
            else
            {
                  iAudioID = psPropType->moveID;
            }

            if ( iAudioID != NO_SOUND )
            {
                  if ( audio_PlayObjDynamicTrack( psDroid, iAudioID,
                              moveCheckDroidMovingAndVisible ) )
                  {
                        psDroid->iAudioID = iAudioID;
                  }
            }
      }
}


static BOOL moveDroidStartCallback( void *psObj )
{
      DROID *psDroid = (DROID*)psObj;

      if ( psDroid == NULL )
      {
            return false;
      }

      movePlayDroidMoveAudio( psDroid );

      return true;
}


static void movePlayAudio( DROID *psDroid, BOOL bStarted, BOOL bStoppedBefore, SDWORD iMoveSpeed )
{
      UBYTE                   propType;
      PROPULSION_STATS  *psPropStats;
      PROPULSION_TYPES  *psPropType;
      BOOL                    bStoppedNow;
      SDWORD                        iAudioID = NO_SOUND;
      AUDIO_CALLBACK          pAudioCallback = NULL;

      /* get prop stats */
      psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
      ASSERT( psPropStats != NULL,
                  "moveUpdateUnit: invalid propulsion stats pointer" );
      propType = psPropStats->propulsionType;
      psPropType = &asPropulsionTypes[propType];

      /* get current droid motion status */
      bStoppedNow = moveDroidStopped( psDroid, iMoveSpeed );

      if ( bStarted )
      {
            /* play start audio */
            if ((propType == WHEELED && psDroid->droidType != DROID_CONSTRUCT)
                || psPropType->startID == NO_SOUND)
            {
                  movePlayDroidMoveAudio( psDroid );
                  return;
            }
            else if ( psDroid->droidType == DROID_TRANSPORTER )
            {
                  iAudioID = ID_SOUND_BLIMP_TAKE_OFF;
            }
            else
            {
                  iAudioID = psPropType->startID;
            }

            pAudioCallback = moveDroidStartCallback;
      }
      else if ( !bStoppedBefore && bStoppedNow &&
                        (psPropType->shutDownID != NO_SOUND) )
      {
            /* play stop audio */
            if ( psDroid->droidType == DROID_TRANSPORTER )
            {
                  iAudioID = ID_SOUND_BLIMP_LAND;
            }
            else if ( propType != WHEELED || psDroid->droidType == DROID_CONSTRUCT )
            {
                  iAudioID = psPropType->shutDownID;
            }
      }
      else if (!bStoppedBefore && !bStoppedNow && psDroid->iAudioID == NO_SOUND)
      {
            /* play move audio */
            movePlayDroidMoveAudio( psDroid );
            return;
      }

      if ( (iAudioID != NO_SOUND) &&
             (psDroid->visible[selectedPlayer] || godMode) )
      {
            if ( audio_PlayObjDynamicTrack( psDroid, iAudioID,
                        pAudioCallback ) )
            {
                  psDroid->iAudioID = iAudioID;
            }
      }
}


// called when a droid moves to a new tile.
// use to pick up oil, etc..
static void checkLocalFeatures(DROID *psDroid)
{
      SDWORD                  i;
      BASE_OBJECT       *psObj;

      // only do for players droids.
      if(psDroid->player != selectedPlayer)
      {
            return;
      }

      droidGetNaybors(psDroid);// update naybor list.

      // scan the neighbours
      for(i=0; i<(SDWORD)numNaybors; i++)
      {
#define DROIDDIST (((TILE_UNITS*3)/2) * ((TILE_UNITS*3)/2))
            psObj = asDroidNaybors[i].psObj;
            if (   psObj->type != OBJ_FEATURE
                  || ((FEATURE *)psObj)->psStats->subType != FEAT_OIL_DRUM
                  || asDroidNaybors[i].distSqr >= DROIDDIST )
            {
                  // object too far away to worry about
                  continue;
            }


            if(bMultiPlayer && (psObj->player == ANYPLAYER))
            {
                  giftPower(ANYPLAYER,selectedPlayer,true);             // give power and tell everyone.
                  addOilDrum(1);
            }
            else

            {
                  addPower(selectedPlayer,OILDRUM_POWER);
            }
            removeFeature((FEATURE*)psObj);                                         // remove artifact+ send multiplay info.

      }
}


/* Frame update for the movement of a tracked droid */
void moveUpdateDroid(DROID *psDroid)
{
      float                   tangle;           // thats DROID angle and TARGET angle - not some bizzare pun :-)
                                                      // doesn't matter - they're still shit names...! :-)
      SDWORD                        fx, fy;
      UDWORD                        oldx, oldy, iZ;
      UBYTE                   oldStatus = psDroid->sMove.Status;
      SDWORD                        moveSpeed;
      float                   moveDir;
      PROPULSION_STATS  *psPropStats;
      Vector3i pos;
      BOOL                    bStarted = false, bStopped;
      Vector2f                target;

      CHECK_DROID(psDroid);

      psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
      ASSERT( psPropStats != NULL,
                  "moveUpdateUnit: invalid propulsion stats pointer" );

//    if(driveModeActive()) {
//          driveUpdateDroid(psDroid);
//    }

      // If the droid has been attacked by an EMP weapon, it is temporarily disabled
      if (psDroid->lastHitWeapon == WSC_EMP)
      {
            if (gameTime - psDroid->timeLastHit < EMP_DISABLE_TIME)
            {
                  // Get out without updating
                  return;
            }
      }

      /* save current motion status of droid */
      bStopped = moveDroidStopped( psDroid, 0 );

      fpathSetBlockingTile( psPropStats->propulsionType );

      moveSpeed = 0;
      moveDir = psDroid->direction;

      /* get droid height */
      iZ = map_Height(psDroid->pos.x, psDroid->pos.y);

      switch (psDroid->sMove.Status)
      {
      case MOVEINACTIVE:
            if ( (psDroid->droidType == DROID_PERSON) &&
                   (psDroid->psCurAnim != NULL) &&
                   (psDroid->psCurAnim->psAnim->uwID == ID_ANIM_DROIDRUN) )
            {
                  psDroid->psCurAnim->bVisible = false;
            }
            break;
      case MOVEROUTE:
      case MOVEROUTESHUFFLE:
      case MOVESHUFFLE:
            // deal with both waiting for a route (MOVEROUTE) and the droid shuffle (MOVESHUFFLE)
            // here because droids waiting for a route need to shuffle out of the way (MOVEROUTESHUFFLE)
            // of those that have already got a route

            if ((psDroid->sMove.Status == MOVEROUTE) ||
                  (psDroid->sMove.Status == MOVEROUTESHUFFLE))
            {
                  // see if this droid started waiting for a route before the previous one
                  // and note it to be the next droid to route.
                  // selectedPlayer always gets precidence in single player
                  if (psNextRouteDroid == NULL)
                  {
                        objTrace(LOG_MOVEMENT, psDroid->id, "Waiting droid set to %d (player %d) started at %d now %d (none waiting)",
                                 (int)psDroid->id, (int)psDroid->player, (int)psDroid->sMove.bumpTime, (int)gameTime);
                        psNextRouteDroid = psDroid;
                  }

                  else if (bMultiPlayer &&
                               (psNextRouteDroid->sMove.bumpTime > psDroid->sMove.bumpTime))
                  {
                        objTrace(LOG_MOVEMENT, psDroid->id, "Waiting droid set to %d (player %d) started at %d now %d (mulitplayer)",
                                 (int)psDroid->id, (int)psDroid->player, (int)psDroid->sMove.bumpTime, (int)gameTime);
                        psNextRouteDroid = psDroid;
                  }

                  else if ( (psDroid->player == selectedPlayer) &&
                                ( (psNextRouteDroid->player != selectedPlayer) ||
                                    (psNextRouteDroid->sMove.bumpTime > psDroid->sMove.bumpTime) ) )
                  {
                        objTrace(LOG_MOVEMENT, psDroid->id, "Waiting droid set to %d (player %d) started at %d now %d (selectedPlayer)",
                                 (int)psDroid->id, (int)psDroid->player, (int)psDroid->sMove.bumpTime, (int)gameTime);
                        psNextRouteDroid = psDroid;
                  }
                  else if ( (psDroid->player != selectedPlayer) &&
                                (psNextRouteDroid->player != selectedPlayer) &&
                                (psNextRouteDroid->sMove.bumpTime > psDroid->sMove.bumpTime) )
                  {
                        objTrace(LOG_MOVEMENT, psDroid->id, "Waiting droid set to %d (player %d) started at %d now %d (non selectedPlayer)",
                                 (int)psDroid->id, (int)psDroid->player, (int)psDroid->sMove.bumpTime, (int)gameTime);
                        psNextRouteDroid = psDroid;
                  }
            }

            if ((psDroid->sMove.Status == MOVEROUTE) ||
                  (psDroid->sMove.Status == MOVEROUTESHUFFLE))
            {
                  psDroid->sMove.fx = psDroid->pos.x;
                  psDroid->sMove.fy = psDroid->pos.y;
                  psDroid->sMove.fz = psDroid->pos.z;

                  turnOffMultiMsg(true);
                  moveDroidTo(psDroid, psDroid->sMove.DestinationX,psDroid->sMove.DestinationY);
                  fpathSetBlockingTile( psPropStats->propulsionType );
                  turnOffMultiMsg(false);
            }
            else if ((psDroid->sMove.Status == MOVESHUFFLE) ||
                         (psDroid->sMove.Status == MOVEROUTESHUFFLE))
            {
                  if (moveReachedWayPoint(psDroid) ||
                        ((psDroid->sMove.shuffleStart + MOVE_SHUFFLETIME) < gameTime))
                  {
                        if ( psDroid->sMove.Status == MOVEROUTESHUFFLE )
                        {
                              psDroid->sMove.Status = MOVEROUTE;
                        }
                        else if ( psPropStats->propulsionType == LIFT )
                        {
                              psDroid->sMove.Status = MOVEHOVER;
                        }
                        else
                        {
                              psDroid->sMove.Status = MOVEINACTIVE;
                        }
                  }
                  else
                  {
                        // Calculate a target vector
                        Vector2f target = moveGetDirection(psDroid);

                        // Turn the droid if necessary
                        tangle = vectorToAngle(target.x, target.y);

                        if ( psDroid->droidType == DROID_TRANSPORTER )
                        {
                              debug( LOG_NEVER, "a) dir %g,%g (%g)\n", target.x, target.y, tangle );
                        }

                        moveSpeed = moveCalcDroidSpeed(psDroid);
                        moveDir = tangle;
                        ASSERT(moveDir >= 0 && moveDir <= 360, "Illegal movement direction");
                  }
            }

            break;
      case MOVEWAITROUTE:
            moveDroidTo(psDroid, psDroid->sMove.DestinationX,psDroid->sMove.DestinationY);
            fpathSetBlockingTile( psPropStats->propulsionType );
            break;
      case MOVENAVIGATE:
            // Get the next control point
            if (!moveNextTarget(psDroid))
            {
                  // No more waypoints - finish
                  if ( psPropStats->propulsionType == LIFT )
                  {
                        psDroid->sMove.Status = MOVEHOVER;
                  }
                  else
                  {
                        psDroid->sMove.Status = MOVEINACTIVE;
                  }
                  break;
            }

            // Calculate the direction vector
//          psDroid->direction = calcDirection(psDroid->pos.x,psDroid->pos.y, tarX,tarY);
            psDroid->sMove.fx = psDroid->pos.x;
            psDroid->sMove.fy = psDroid->pos.y;
            psDroid->sMove.fz = psDroid->pos.z;

            moveCalcBoundary(psDroid);

            if (isVtolDroid(psDroid))
            {
                  psDroid->pitch = 0;
            }

            psDroid->sMove.Status = MOVEPOINTTOPOINT;
            psDroid->sMove.bumpTime = 0;

            /* save started status for movePlayAudio */
            if (psDroid->sMove.speed == 0)
            {
                  bStarted = true;
            }

            break;
      case MOVEPOINTTOPOINT:
      case MOVEPAUSE:
            // moving between two way points

            // See if the target point has been reached
            if (moveReachedWayPoint(psDroid))
            {
                  // Got there - move onto the next waypoint
                  if (!moveNextTarget(psDroid))
                  {
                        // No more waypoints - finish
//                      psDroid->sMove.Status = MOVEINACTIVE;
                        if ( psPropStats->propulsionType == LIFT )
                        {
                              psDroid->sMove.Status = MOVEHOVER;
                        }
                        else
                        {
                              psDroid->sMove.Status = MOVETURN;
                        }
                        break;
                  }
                  moveCalcBoundary(psDroid);
            }

            if (psDroid->sMove.psFormation &&
                  psDroid->sMove.Position == psDroid->sMove.numPoints)
            {
                  if (isVtolDroid(psDroid))
                  {
                        // vtols have to use the ground blocking tile when they are going to land
                        fpathBlockingTile = fpathGroundBlockingTile;
                  }

                  if (formationGetPos(psDroid->sMove.psFormation, psDroid, &fx,&fy,true))
                  {
                        psDroid->sMove.targetX = fx;
                        psDroid->sMove.targetY = fy;
                        moveCalcBoundary(psDroid);
                  }
            }

            // Calculate a target vector
            target = moveGetDirection(psDroid);

            // Turn the droid if necessary
            tangle = vectorToAngle(target.x, target.y);

            moveSpeed = moveCalcDroidSpeed(psDroid);

            moveDir = tangle;
            ASSERT(moveDir >= 0 && moveDir <= 360, "Illegal movement direction");

            if ((psDroid->sMove.bumpTime != 0) &&
                  (psDroid->sMove.pauseTime + psDroid->sMove.bumpTime + BLOCK_PAUSETIME < gameTime))
            {
                  if (psDroid->sMove.Status == MOVEPOINTTOPOINT)
                  {
                        psDroid->sMove.Status = MOVEPAUSE;
                  }
                  else
                  {
                        psDroid->sMove.Status = MOVEPOINTTOPOINT;
                  }
                  psDroid->sMove.pauseTime = (UWORD)(gameTime - psDroid->sMove.bumpTime);
            }

            if ((psDroid->sMove.Status == MOVEPAUSE) &&
                  (psDroid->sMove.bumpTime != 0) &&
                  (psDroid->sMove.lastBump > psDroid->sMove.pauseTime) &&
                  (psDroid->sMove.lastBump + psDroid->sMove.bumpTime + BLOCK_PAUSERELEASE < gameTime))
            {
                  psDroid->sMove.Status = MOVEPOINTTOPOINT;
            }

            break;
      case MOVETURN:
            // Turn the droid to it's final facing
            if (psDroid->sMove.psFormation &&
                  psDroid->sMove.psFormation->refCount > 1 &&
                  (SDWORD)psDroid->direction != (SDWORD)psDroid->sMove.psFormation->dir)
            {
                  moveSpeed = 0;
                  moveDir = psDroid->sMove.psFormation->dir;
                  ASSERT(moveDir >= 0 && moveDir <= 360, "Illegal movement direction");
            }
            else
            {
                  if ( psPropStats->propulsionType == LIFT )
                  {
                        psDroid->sMove.Status = MOVEPOINTTOPOINT;
                  }
                  else
                  {
                        psDroid->sMove.Status = MOVEINACTIVE;
                  }
            }
            break;
      case MOVETURNTOTARGET:
            moveSpeed = 0;
            moveDir = calcDirection( psDroid->pos.x, psDroid->pos.y, psDroid->sMove.targetX, psDroid->sMove.targetY );
            if ((int)psDroid->direction == (int)moveDir)
            {
                  if ( psPropStats->propulsionType == LIFT )
                  {
                        psDroid->sMove.Status = MOVEPOINTTOPOINT;
                  }
                  else
                  {
                        psDroid->sMove.Status = MOVEINACTIVE;
                  }
            }
            ASSERT(moveDir >= 0 && moveDir <= 360, "Illegal movement direction");
            break;
      case MOVEHOVER:
            /* change vtols to attack run mode if target found - but not if no ammo*/
            /*if ( psDroid->droidType != DROID_CYBORG && psDroid->psTarget != NULL &&
                  !vtolEmpty(psDroid))
            {
                  psDroid->sMove.Status = MOVEPOINTTOPOINT;
                  break;
            }*/

/*          moveGetDirection(psDroid, &tx,&ty);
            tangle = vectorToAngle(tx,ty);
            moveSpeed = moveCalcDroidSpeed(psDroid);
            moveDir = tangle;*/

            /* descend if no orders or actions or cyborg at target */
/*          if ( (psDroid->droidType == DROID_CYBORG) ||
                   ((psDroid->action == DACTION_NONE) && (psDroid->order == DORDER_NONE)) ||
                   ((psDroid->action == DACTION_NONE) && (psDroid->order == DORDER_TRANSPORTIN)) ||
                   ((psDroid->action == DACTION_NONE) && (psDroid->order == DORDER_GUARD)) ||
                   (psDroid->action == DACTION_MOVE) || (psDroid->action == DACTION_WAITFORREARM) ||
                   (psDroid->action == DACTION_WAITDURINGREARM)  || (psDroid->action == DACTION_FIRESUPPORT))*/
            {
                  if ( moveDescending( psDroid, iZ ) == false )
                  {
                        /* reset move state */
                        psDroid->sMove.Status = MOVEINACTIVE;
                  }
/*                else
                  {
                        UDWORD      landX, landY;

                        // see if the landing position is clear
                        landX = psDroid->pos.x;
                        landY = psDroid->pos.y;
                        actionVTOLLandingPos(psDroid, &landX,&landY);
                        if (map_coord(landX) != map_coord(psDroid->pos.x)
                         && map_coord(landY) != map_coord(psDroid->pos.y))
                        {
                              moveDroidToDirect(psDroid, landX,landY);
                        }
                  }*/
            }
/*          else
            {
                  moveAdjustVtolHeight( psDroid, iZ );
            }*/

            break;

            // Driven around by the player.
      case MOVEDRIVE:
            driveSetDroidMove(psDroid);
            moveSpeed = driveGetMoveSpeed();    //psDroid->sMove.speed;
            moveDir = driveGetMoveDir();        //psDroid->sMove.dir;
            ASSERT(moveDir >= 0 && moveDir <= 360, "Illegal movement direction");
            break;

      default:
            ASSERT( false, "moveUpdateUnit: unknown move state" );
            break;
      }

      ASSERT(moveDir >= 0 && moveDir <= 360, "Illegal movement direction");

      // Update the movement model for the droid
      oldx = psDroid->pos.x;
      oldy = psDroid->pos.y;

      if ( psDroid->droidType == DROID_PERSON )
      {
            moveUpdatePersonModel(psDroid, moveSpeed, moveDir);
      }
      else if (cyborgDroid(psDroid))
      {
            moveUpdateCyborgModel(psDroid, moveSpeed, moveDir, oldStatus);
      }
      else if ( psPropStats->propulsionType == LIFT )
      {
            moveUpdateVtolModel(psDroid, moveSpeed, moveDir);
      }
      else
      {
            moveUpdateGroundModel(psDroid, moveSpeed, moveDir);
      }

      if (map_coord(oldx) != map_coord(psDroid->pos.x)
       || map_coord(oldy) != map_coord(psDroid->pos.y))
      {
            visTilesUpdate((BASE_OBJECT *)psDroid);
            gridMoveDroid(psDroid, (SDWORD)oldx,(SDWORD)oldy);

            // object moved from one tile to next, check to see if droid is near stuff.(oil)
            checkLocalFeatures(psDroid);
      }

      // See if it's got blocked
      if ( (psPropStats->propulsionType != LIFT) && moveBlocked(psDroid) )
      {
            objTrace(LOG_MOVEMENT, psDroid->id, "status: id %d blocked", (int)psDroid->id);
            psDroid->sMove.Status = MOVETURN;
      }

//    // If were in drive mode and the droid is a follower then stop it when it gets within
//    // range of the driver.
//    if(driveIsFollower(psDroid)) {
//          if(DoFollowRangeCheck) {
//                if(driveInDriverRange(psDroid)) {
//                      psDroid->sMove.Status = MOVEINACTIVE;
////                    ClearFollowRangeCheck = true;
//                } else {
//                      AllInRange = false;
//                }
//          }
//    }

      // reset the blocking tile function and current object
      fpathBlockingTile = fpathGroundBlockingTile;

      /* If it's sitting in water then it's got to go with the flow! */
      if (terrainType(mapTile(psDroid->pos.x/TILE_UNITS,psDroid->pos.y/TILE_UNITS)) == TER_WATER)
      {
            updateDroidOrientation(psDroid);
      }

      if( (psDroid->inFire && psDroid->type != DROID_PERSON) && psDroid->visible[selectedPlayer])
      {
            pos.x = psDroid->pos.x + (18-rand()%36);
            pos.z = psDroid->pos.y + (18-rand()%36);
            pos.y = psDroid->pos.z + (psDroid->sDisplay.imd->max.y / 3);
            addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_SMALL,false,NULL,0);
      }

      movePlayAudio( psDroid, bStarted, bStopped, moveSpeed );
      CHECK_DROID(psDroid);
}

Generated by  Doxygen 1.6.0   Back to index