Logo Search packages:      
Sourcecode: warzone2100 version File versions

droid.c

Go to the documentation of this file.
/*
      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
*/
/**
 * @file droid.c
 *
 * Droid method functions.
 *
 */
#include "lib/framework/frame.h"
#include "lib/framework/math-help.h"
#include "lib/framework/strres.h"

#include "lib/gamelib/gtime.h"
#include "lib/gamelib/animobj.h"
#include "lib/ivis_opengl/piematrix.h"
#include "lib/ivis_opengl/screen.h"
#include "lib/script/script.h"
#include "lib/sound/audio.h"
#include "lib/sound/audio_id.h"

#include "objects.h"
#include "loop.h"
#include "visibility.h"
#include "map.h"
#include "drive.h"
#include "droid.h"
#include "hci.h"
#include "game.h"
#include "power.h"
#include "miscimd.h"
#include "effects.h"
#include "feature.h"
#include "action.h"
#include "order.h"
#include "move.h"
#include "anim_id.h"
#include "geometry.h"
#include "display.h"
#include "console.h"
#include "component.h"
#include "function.h"
#include "lighting.h"
#include "gateway.h"
#include "multiplay.h"
#include "formationdef.h"
#include "formation.h"
#include "warcam.h"
#include "display3d.h"
#include "group.h"
#include "text.h"
#include "scripttabs.h"
#include "scriptcb.h"
#include "cmddroid.h"
#include "fpath.h"
#include "mapgrid.h"
#include "projectile.h"
#include "cluster.h"
#include "mission.h"
#include "levels.h"
#include "transporter.h"
#include "selection.h"
#include "difficulty.h"
#include "edit3d.h"
#include "scores.h"
#include "research.h"
#include "combat.h"
#include "scriptfuncs.h"                  //for ThreatInRange()


#define DEFAULT_RECOIL_TIME   (GAME_TICKS_PER_SEC/4)
#define     DROID_DAMAGE_SPREAD     (16 - rand()%32)
#define     DROID_REPAIR_SPREAD     (20 - rand()%40)


/* default droid design template */
extern DROID_TEMPLATE   sDefaultDesignTemplate;

//storage
DROID_TEMPLATE          *apsDroidTemplates[MAX_PLAYERS];

// store the experience of recently recycled droids
UWORD aDroidExperience[MAX_PLAYERS][MAX_RECYCLED_DROIDS];
UDWORD      selectedGroup = UBYTE_MAX;
UDWORD      selectedCommander = UBYTE_MAX;

/** Height the transporter hovers at above the terrain. */
00104 #define TRANSPORTER_HOVER_HEIGHT    10

/** How far round a repair droid looks for a damaged droid. */
00107 #define REPAIR_DIST           (TILE_UNITS * 4)//8)

/* Store for the objects near the droid currently being updated
 * NAYBOR = neighbour - thanks to Keith for a great abreviation
 */
NAYBOR_INFO             asDroidNaybors[MAX_NAYBORS];
UDWORD                        numNaybors=0;

// the structure that was last hit
DROID *psLastDroidHit;

//determines the best IMD to draw for the droid - A TEMP MEASURE!
void  groupConsoleInformOfSelection( UDWORD groupNumber );
void  groupConsoleInformOfCreation( UDWORD groupNumber );
void  groupConsoleInformOfCentering( UDWORD groupNumber );
void  droidUpdateRecoil( DROID *psDroid );


// initialise droid module
BOOL droidInit(void)
{
      memset(aDroidExperience, 0, sizeof(UWORD) * MAX_PLAYERS * MAX_RECYCLED_DROIDS);
      psLastDroidHit = NULL;

      return true;
}


#define UNIT_LOST_DELAY (5*GAME_TICKS_PER_SEC)
/* Deals damage to a droid
 * \param psDroid droid to deal damage to
 * \param damage amount of damage to deal
 * \param weaponClass the class of the weapon that deals the damage
 * \param weaponSubClass the subclass of the weapon that deals the damage
 * \param angle angle of impact (from the damage dealing projectile in relation to this droid)
 * \return > 0 when the dealt damage destroys the droid, < 0 when the droid survives
 *
 * NOTE: This function will damage but _never_ destroy transports when in single player (campaign) mode
 */
float droidDamage(DROID *psDroid, UDWORD damage, UDWORD weaponClass, UDWORD weaponSubClass, HIT_SIDE impactSide)
{
      float       relativeDamage;
      SECONDARY_STATE   state;

      CHECK_DROID(psDroid);

      // VTOLs on the ground take triple damage
      if (isVtolDroid(psDroid) && psDroid->sMove.Status == MOVEINACTIVE)
      {
            damage *= 3;
      }

      relativeDamage = objDamage((BASE_OBJECT *)psDroid, damage, psDroid->originalBody, weaponClass, weaponSubClass, impactSide);

      if (relativeDamage > 0.0f)
      {
            // reset the attack level
            if (secondaryGetState(psDroid, DSO_ATTACK_LEVEL, &state) && state == DSS_ALEV_ATTACKED)
            {
                  secondarySetState(psDroid, DSO_ATTACK_LEVEL, DSS_ALEV_ALWAYS);
            }
            // Now check for auto return on droid's secondary orders (i.e. return on medium/heavy damage)
            secondaryCheckDamageLevel(psDroid);

            // Now check for scripted run-away based on health left
            orderHealthCheck(psDroid);

            CHECK_DROID(psDroid);
      }
      else if (relativeDamage < 0.0f)
      {
            // HACK: Prevent transporters from being destroyed in single player
            if (!bMultiPlayer && psDroid->droidType == DROID_TRANSPORTER)
            {
                  psDroid->body = 1;
                  return 0.0f;
            }

            // Droid destroyed
            debug(LOG_ATTACK, "droidDamage(%d): DESTROYED", psDroid->id);

            // Deal with score increase/decrease and messages to the player
            if( psDroid->player == selectedPlayer)
            {
                  CONPRINTF(ConsoleString,(ConsoleString, _("Unit Lost!")));
                  scoreUpdateVar(WD_UNITS_LOST);
                  audio_QueueTrackMinDelayPos(ID_SOUND_UNIT_DESTROYED,UNIT_LOST_DELAY,
                                                            psDroid->pos.x, psDroid->pos.y, psDroid->pos.z );
            }
            else
            {
                  scoreUpdateVar(WD_UNITS_KILLED);
            }

            // If this is droid is a person and was destroyed by flames,
            // show it nicely by burning him/her to death.
            if (psDroid->droidType == DROID_PERSON && weaponClass == WC_HEAT)
            {
                  droidBurn(psDroid);
            }
            // Otherwise use the default destruction animation
            else
            {
                  debug(LOG_DEATH, "droidDamage: Droid %d beyond repair", (int)psDroid->id);
                  destroyDroid(psDroid);
            }
      }
      
      return relativeDamage;
}

// Check that psVictimDroid is not referred to by any other object in the game
BOOL droidCheckReferences(DROID *psVictimDroid)
{
      int plr, i;

      for (plr = 0; plr < MAX_PLAYERS; plr++)
      {
            STRUCTURE *psStruct;
            DROID *psDroid;

            for (psStruct = apsStructLists[plr]; psStruct != NULL; psStruct = psStruct->psNext)
            {
                  for (i = 0; i < psStruct->numWeaps; i++)
                  {
                        if ((DROID *)psStruct->psTarget[i] == psVictimDroid)
                        {
#ifndef DEBUG
                              debug(LOG_ERROR, "droidCheckReferences: Illegal reference to droid");
#else
                              ASSERT(!"Illegal reference to droid", "Illegal reference to droid from %s line %d",
                                     psStruct->targetFunc[i], psStruct->targetLine[i]);
#endif
                              return false;
                        }
                  }
            }
            for (psDroid = apsDroidLists[plr]; psDroid != NULL; psDroid = psDroid->psNext)
            {
                  if ((DROID *)psDroid->psTarget == psVictimDroid && psVictimDroid != psDroid)
                  {
#ifndef DEBUG
                        debug(LOG_ERROR, "droidCheckReferences: Illegal reference to droid");
#else
                        ASSERT(!"Illegal reference to droid", "Illegal reference to droid from %s line %d",
                               psDroid->targetFunc, psDroid->targetLine);
#endif
                        return false;
                  }
                  for (i = 0; i < psDroid->numWeaps; i++)
                  {
                        if ((DROID *)psDroid->psActionTarget[i] == psVictimDroid && psVictimDroid != psDroid)
                        {
#ifndef DEBUG
                              debug(LOG_ERROR, "droidCheckReferences: Illegal action reference to droid");
#else
                              ASSERT(!"Illegal reference to droid", "Illegal action reference to droid from %s line %d",
                                     psDroid->actionTargetFunc[i], psDroid->actionTargetLine[i]);
#endif
                              return false;
                        }
                  }
            }
      }
      return true;
}

/* droidRelease: release all resources associated with a droid -
 * should only be called by objmem - use vanishDroid preferably
 */
void droidRelease(DROID *psDroid)
{
      DROID *psCurr, *psNext;

      /* remove animation if present */
      if (psDroid->psCurAnim != NULL)
      {
            animObj_Remove(&psDroid->psCurAnim, psDroid->psCurAnim->psAnim->uwID);
            psDroid->psCurAnim = NULL;
      }

      if (psDroid->droidType == DROID_TRANSPORTER)
      {
            if (psDroid->psGroup)
            {
                  //free all droids associated with this Transporter
                  for (psCurr = psDroid->psGroup->psList; psCurr != NULL && psCurr !=
                        psDroid; psCurr = psNext)
                  {
                        psNext = psCurr->psGrpNext;
                        droidRelease(psCurr);
                        free(psCurr);
                  }
            }
      }

      // leave the current formation if any
      if (psDroid->sMove.psFormation)
      {
            formationLeave(psDroid->sMove.psFormation, psDroid);
      }

      // leave the current group if any
      if (psDroid->psGroup)
      {
            grpLeave(psDroid->psGroup, psDroid);
      }

      // remove the object from the grid
      gridRemoveObject((BASE_OBJECT *)psDroid);

      // remove the droid from the cluster systerm
      clustRemoveObject((BASE_OBJECT *)psDroid);
}


// recycle a droid (retain it's experience and some of it's cost)
void recycleDroid(DROID *psDroid)
{
      UDWORD            numKills, minKills;
      SDWORD            i, cost, storeIndex;
      Vector3i position;

      CHECK_DROID(psDroid);

      // store the droids kills
      numKills = psDroid->experience;
      if (numKills > UWORD_MAX)
      {
            numKills = UWORD_MAX;
      }
      minKills = UWORD_MAX;
      storeIndex = 0;
      for(i=0; i<MAX_RECYCLED_DROIDS; i++)
      {
            if (aDroidExperience[psDroid->player][i] < (UWORD)minKills)
            {
                  storeIndex = i;
                  minKills = aDroidExperience[psDroid->player][i];
            }
      }
      aDroidExperience[psDroid->player][storeIndex] = (UWORD)numKills;

      // return part of the cost of the droid
      cost = calcDroidPower(psDroid);
      cost = (cost/2) * psDroid->body / psDroid->originalBody;
      addPower(psDroid->player, (UDWORD)cost);

      // hide the droid
      memset(psDroid->visible, 0, sizeof(psDroid->visible));
      // stop any group moral checks
      if (psDroid->psGroup)
      {
            grpLeave(psDroid->psGroup, psDroid);
      }

      position.x = psDroid->pos.x;                    // Add an effect
      position.z = psDroid->pos.y;
      position.y = psDroid->pos.z;

      vanishDroid(psDroid);

      addEffect(&position,EFFECT_EXPLOSION,EXPLOSION_TYPE_DISCOVERY,false,NULL,false);

      CHECK_DROID(psDroid);
}


void  removeDroidBase(DROID *psDel)
{
      DROID *psCurr, *psNext;
      BOOL  bRet;
      DROID_GROUP *psGroup;
      STRUCTURE   *psStruct;

      CHECK_DROID(psDel);

      //tell the power system its gone
      powerDestroyObject((BASE_OBJECT *)psDel);

      if (isDead((BASE_OBJECT *)psDel))
      {
            // droid has already been killed, quit
            return;
      }

      //ajl, inform others of destruction.
      if (bMultiPlayer
       && !(psDel->player != selectedPlayer && psDel->order == DORDER_RECYCLE))
      {
            ASSERT(droidOnMap(psDel), "Asking other players to destroy droid driving off the map");
            SendDestroyDroid(psDel);
      }


      /* remove animation if present */
      if (psDel->psCurAnim != NULL)
      {
            bRet = animObj_Remove( &psDel->psCurAnim, psDel->psCurAnim->psAnim->uwID );
            ASSERT( bRet == true, "destroyUnit: animObj_Remove failed" );
            psDel->psCurAnim = NULL;
      }

      // leave the current formation if any
      if (psDel->sMove.psFormation)
      {
            formationLeave(psDel->sMove.psFormation, psDel);
            psDel->sMove.psFormation = NULL;
      }

      //kill all the droids inside the transporter
      if (psDel->droidType == DROID_TRANSPORTER)
      {
            if (psDel->psGroup)
            {
                  //free all droids associated with this Transporter
                  for (psCurr = psDel->psGroup->psList; psCurr != NULL && psCurr !=
                        psDel; psCurr = psNext)
                  {
                        psNext = psCurr->psGrpNext;

                        /* add droid to droid list then vanish it - hope this works! - GJ */
                        addDroid(psCurr, apsDroidLists);
                        vanishDroid(psCurr);
                  }
            }
      }

      // check moral
      if (psDel->psGroup && psDel->psGroup->refCount > 1)
      {
            psGroup = psDel->psGroup;
            grpLeave(psDel->psGroup, psDel);
            orderGroupMoralCheck(psGroup);
      }
      else
      {
            orderMoralCheck(psDel->player);
      }

      // leave the current group if any
      if (psDel->psGroup)
      {
            grpLeave(psDel->psGroup, psDel);
            psDel->psGroup = NULL;
      }

      /* Put Deliv. Pts back into world when a command droid dies */
      if (psDel->droidType == DROID_COMMAND)
      {
            for (psStruct = apsStructLists[psDel->player]; psStruct; psStruct=psStruct->psNext)
            {
                  // alexl's stab at a right answer.
                  if (StructIsFactory(psStruct)
                   && psStruct->pFunctionality->factory.psCommander == psDel)
                  {
                        assignFactoryCommandDroid(psStruct, NULL);
                  }
            }
      }

      // Check to see if constructor droid currently trying to find a location to build
      if ((psDel->droidType == DROID_CONSTRUCT || psDel->droidType == DROID_CYBORG_CONSTRUCT)
          && psDel->player == selectedPlayer && psDel->selected)
      {
            // If currently trying to build, kill off the placement
            if (tryingToGetLocation())
            {
                  kill3DBuilding();
            }
      }

      // remove the droid from the grid
      gridRemoveObject((BASE_OBJECT *)psDel);

      // remove the droid from the cluster systerm
      clustRemoveObject((BASE_OBJECT *)psDel);

      if (psDel->player == selectedPlayer)
      {
            intRefreshScreen();
      }

      killDroid(psDel);
}

static void removeDroidFX(DROID *psDel)
{
      Vector3i pos;

      CHECK_DROID(psDel);

      // only display anything if the droid is visible
      if (!psDel->visible[selectedPlayer])
      {
            return;
      }

      if ((psDel->droidType == DROID_PERSON || cyborgDroid(psDel)) && psDel->order != DORDER_RUNBURN)
      {
            /* blow person up into blood and guts */
            compPersonToBits(psDel);
      }

      /* if baba and not running (on fire) then squish */
      if (psDel->droidType == DROID_PERSON
       && psDel->order != DORDER_RUNBURN
       && psDel->visible[selectedPlayer])
      {
            // The babarian has been run over ...
            audio_PlayStaticTrack(psDel->pos.x, psDel->pos.y, ID_SOUND_BARB_SQUISH);
      }
      else if (psDel->visible[selectedPlayer])
      {
            destroyFXDroid(psDel);
            pos.x = psDel->pos.x;
            pos.z = psDel->pos.y;
            pos.y = psDel->pos.z;
            addEffect(&pos,EFFECT_DESTRUCTION,DESTRUCTION_TYPE_DROID,false,NULL,0);
            audio_PlayStaticTrack( psDel->pos.x, psDel->pos.y, ID_SOUND_EXPLOSION );
      }
}

void destroyDroid(DROID *psDel)
{
      if (psDel->lastHitWeapon == WSC_LAS_SAT)        // darken tile if lassat.
      {
            UDWORD width, breadth, mapX, mapY;
            MAPTILE     *psTile;

            mapX = map_coord(psDel->pos.x);
            mapY = map_coord(psDel->pos.y);
            for (width = mapX-1; width <= mapX+1; width++)
            {
                  for (breadth = mapY-1; breadth <= mapY+1; breadth++)
                  {
                        psTile = mapTile(width,breadth);
                        if(TEST_TILE_VISIBLE(selectedPlayer, psTile))
                        {
                              psTile->illumination /= 2;
                              if(psTile->bMaxed && psTile->level > 0) //only do one's already seen
                              {
                                    psTile->level/=2;
                              }

                        }
                  }
            }
      }

      removeDroidFX(psDel);
      removeDroidBase(psDel);
      return;
}

void  vanishDroid(DROID *psDel)
{
      removeDroidBase(psDel);
}

/* Remove a droid from the List so doesn't update or get drawn etc
TAKE CARE with removeDroid() - usually want droidRemove since it deal with cluster and grid code*/
//returns false if the droid wasn't removed - because it died!
BOOL droidRemove(DROID *psDroid, DROID *pList[MAX_PLAYERS])
{
      CHECK_DROID(psDroid);

      driveDroidKilled(psDroid);    // Tell the driver system it's gone.

      //tell the power system its gone
      powerDestroyObject((BASE_OBJECT *)psDroid);

      if (isDead((BASE_OBJECT *) psDroid))
      {
            // droid has already been killed, quit
            return false;
      }

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

      // leave the current group if any - not if its a Transporter droid
      if (psDroid->droidType != DROID_TRANSPORTER && psDroid->psGroup)
      {
            grpLeave(psDroid->psGroup, psDroid);
            psDroid->psGroup = NULL;
      }

      // reset the baseStruct
      setDroidBase(psDroid, NULL);

      // remove the droid from the cluster systerm
      clustRemoveObject((BASE_OBJECT *)psDroid);

      // remove the droid from the grid
      gridRemoveObject((BASE_OBJECT *)psDroid);

      removeDroid(psDroid, pList);

      // tell the power system it is gone
      powerDestroyObject((BASE_OBJECT *)psDroid);

      if (psDroid->player == selectedPlayer)
      {
            intRefreshScreen();
      }

      return true;
}

static void droidFlameFallCallback( ANIM_OBJECT * psObj )
{
      DROID *psDroid;

      ASSERT( psObj != NULL,
            "unitFlameFallCallback: invalid anim object pointer\n" );
      psDroid = (DROID *) psObj->psParent;
      ASSERT( psDroid != NULL,
            "unitFlameFallCallback: invalid Unit pointer\n" );

      psDroid->psCurAnim = NULL;

      debug(LOG_DEATH, "droidFlameFallCallback: Droid %d destroyed", (int)psDroid->id);
      destroyDroid( psDroid );
}

static void droidBurntCallback( ANIM_OBJECT * psObj )
{
      DROID *psDroid;

      ASSERT( psObj != NULL,
            "unitBurntCallback: invalid anim object pointer\n" );
      psDroid = (DROID *) psObj->psParent;
      ASSERT( psDroid != NULL,
            "unitBurntCallback: invalid Unit pointer\n" );

      /* add falling anim */
      psDroid->psCurAnim = animObj_Add((BASE_OBJECT *)psDroid, ID_ANIM_DROIDFLAMEFALL, 0, 1);
      if (!psDroid->psCurAnim)
      {
            debug( LOG_ERROR, "unitBurntCallback: couldn't add fall over anim\n" );
            abort();
            return;
      }

      animObj_SetDoneFunc( psDroid->psCurAnim, droidFlameFallCallback );
}

void droidBurn(DROID *psDroid)
{
      BOOL  bRet;

      CHECK_DROID(psDroid);

      if ( psDroid->droidType != DROID_PERSON )
      {
            ASSERT(LOG_ERROR, "unitBurn: can't burn anything except babarians currently!");
            return;
      }

      /* if already burning return else remove currently-attached anim if present */
      if ( psDroid->psCurAnim != NULL )
      {
            /* return if already burning */
            if ( psDroid->psCurAnim->psAnim->uwID == ID_ANIM_DROIDBURN )
            {
                  return;
            }
            else
            {
                  bRet = animObj_Remove( &psDroid->psCurAnim, psDroid->psCurAnim->psAnim->uwID );
                  ASSERT( bRet == true, "unitBurn: animObj_Remove failed" );
                  psDroid->psCurAnim = NULL;
            }
      }

      /* add burning anim */
      psDroid->psCurAnim = animObj_Add( (BASE_OBJECT *) psDroid,
                                                                  ID_ANIM_DROIDBURN, 0, 3 );
      if ( psDroid->psCurAnim == NULL )
      {
            debug( LOG_ERROR, "unitBurn: couldn't add burn anim\n" );
            abort();
            return;
      }

      /* set callback */
      animObj_SetDoneFunc( psDroid->psCurAnim, droidBurntCallback );

      /* add scream */
      debug( LOG_NEVER, "baba burn\n" );

      audio_PlayObjDynamicTrack( psDroid, ID_SOUND_BARB_SCREAM+(rand()%3), NULL );


      /* set droid running */
      orderDroid( psDroid, DORDER_RUNBURN );
}

/* Add a new object to the naybor list */
static void addNaybor(BASE_OBJECT *psObj, UDWORD distSqr)
{
      UDWORD      pos;

      if (numNaybors >= MAX_NAYBORS)
      {
            return;
      }
      
      else if (numNaybors == 0)
      {
            // No objects in the list
            asDroidNaybors[0].psObj = psObj;
            asDroidNaybors[0].distSqr = distSqr;
            numNaybors++;
      }
      else if (distSqr >= asDroidNaybors[numNaybors-1].distSqr)
      {
            // Simple case - this is the most distant object
            asDroidNaybors[numNaybors].psObj = psObj;
            asDroidNaybors[numNaybors].distSqr = distSqr;
            numNaybors++;
      }
      else
      {
            // Move all the objects further away up the list
            for (pos = numNaybors; pos && asDroidNaybors[pos - 1].distSqr > distSqr; pos--)
            {
                  memcpy(asDroidNaybors + pos, asDroidNaybors + (pos - 1), sizeof(NAYBOR_INFO));
            }

            // Insert the object at the correct position
            asDroidNaybors[pos].psObj = psObj;
            asDroidNaybors[pos].distSqr = distSqr;
            numNaybors++;
      }

      ASSERT( numNaybors <= MAX_NAYBORS,
            "addNaybor: numNaybors > MAX_NAYBORS" );
}


static DROID      *CurrentNaybors = NULL;
static UDWORD     nayborTime = 0;

// macro to see if an object is in NAYBOR_RANGE
// used by droidGetNayb
#define IN_NAYBOR_RANGE(psObj) \
      xdiff = dx - (SDWORD)psObj->pos.x; \
      if (xdiff < 0) \
      { \
            xdiff = -xdiff; \
      } \
      if (xdiff > NAYBOR_RANGE) \
      { \
            continue; \
      } \
\
      ydiff = dy - (SDWORD)psObj->pos.y; \
      if (ydiff < 0) \
      { \
            ydiff = -ydiff; \
      } \
      if (ydiff > NAYBOR_RANGE) \
      { \
            continue; \
      } \
\
      distSqr = xdiff*xdiff + ydiff*ydiff; \
      if (distSqr > NAYBOR_RANGE*NAYBOR_RANGE) \
      { \
            continue; \
      } \


/* Find all the objects close to the droid */
void droidGetNaybors(DROID *psDroid)
{
      SDWORD            xdiff, ydiff;
      UDWORD            dx,dy, distSqr;
      BASE_OBJECT *psObj;

      CHECK_DROID(psDroid);

      // Ensure only called max of once per droid per game cycle.
      if(CurrentNaybors == psDroid && nayborTime == gameTime) {
            return;
      }
      CurrentNaybors = psDroid;
      nayborTime = gameTime;

      // reset the naybor array
      numNaybors = 0;
#ifdef DEBUG
      memset(asDroidNaybors, 0xcd, sizeof(asDroidNaybors));
#endif

      // search for naybor objects
      dx = psDroid->pos.x;
      dy = psDroid->pos.y;

      gridStartIterate((SDWORD)dx, (SDWORD)dy);
      for (psObj = gridIterate(); psObj != NULL; psObj = gridIterate())
      {
            if (psObj != (BASE_OBJECT *)psDroid && !psObj->died)
            {
                  IN_NAYBOR_RANGE(psObj);

                  addNaybor(psObj, distSqr);
            }
      }
}

/* The main update routine for all droids */
void droidUpdate(DROID *psDroid)
{
      Vector3i dv;
      UDWORD      percentDamage, emissionInterval;
      BASE_OBJECT *psBeingTargetted = NULL;
      SDWORD      damageToDo;

      CHECK_DROID(psDroid);

      // update the cluster of the droid
      if (psDroid->id % 20 == frameGetFrameNumber() % 20)
      {
            clustUpdateObject((BASE_OBJECT *)psDroid);
      }

      // May need power
      if (droidUsesPower(psDroid))
      {
            if (checkPower(psDroid->player, POWER_PER_CYCLE, false))
            {
                  // Check if this droid is due some power
                  if (getLastPowered((BASE_OBJECT *)psDroid))
                  {
                        // Get some power if necessary
                        if (accruePower((BASE_OBJECT *)psDroid))
                        {
                              updateLastPowered((BASE_OBJECT *)psDroid, psDroid->player);
                        }
                  }
            }
      }

      // ai update droid
      aiUpdateDroid(psDroid);

      // update the droids order
      orderUpdateDroid(psDroid);

      // update the action of the droid
      actionUpdateDroid(psDroid);

      // update the move system
      moveUpdateDroid(psDroid);

      /* Only add smoke if they're visible */
      if((psDroid->visible[selectedPlayer]) && psDroid->droidType != DROID_PERSON)
      {
            // need to clip this value to prevent overflow condition
            percentDamage = 100 - clip(PERCENT(psDroid->body, psDroid->originalBody), 0, 100);
            
            // Is there any damage?
            if(percentDamage>=25)
            {
                  if(percentDamage>=100)
                  {
                        percentDamage = 99;
                  }
                  
                  emissionInterval = CALC_DROID_SMOKE_INTERVAL(percentDamage);
                  
                  if(gameTime > (psDroid->lastEmission + emissionInterval))
                  {
                        dv.x = psDroid->pos.x + DROID_DAMAGE_SPREAD;
                        dv.z = psDroid->pos.y + DROID_DAMAGE_SPREAD;
                        dv.y = psDroid->pos.z;

                        dv.y += (psDroid->sDisplay.imd->max.y * 2);
                        addEffect(&dv,EFFECT_SMOKE,SMOKE_TYPE_DRIFTING_SMALL,false,NULL,0);
                        psDroid->lastEmission = gameTime;
                  }
            }
      }

      processVisibility((BASE_OBJECT*)psDroid);

      // -----------------
      /* Are we a sensor droid or a command droid? */
      if( (psDroid->droidType == DROID_SENSOR) || (psDroid->droidType == DROID_COMMAND) )
      {
            /* If we're attacking or sensing (observing), then... */
            if ((psBeingTargetted = orderStateObj(psDroid, DORDER_ATTACK))
             || (psBeingTargetted = orderStateObj(psDroid, DORDER_OBSERVE)))
            {
                  /* If it's a structure */
                  if(psBeingTargetted->type == OBJ_STRUCTURE)
                  {
                        /* And it's your structure or your droid... */
                        if( (((STRUCTURE*)psBeingTargetted)->player == selectedPlayer)
                              || (psDroid->player == selectedPlayer) )
                        {
                              /* Highlight the structure in question */
                              ((STRUCTURE*)psBeingTargetted)->targetted = 1;
                        }
                  }
                  else if (psBeingTargetted->type == OBJ_DROID)
                  {
                        /* And it's your  your droid... */
                        if( (((DROID*)psBeingTargetted)->player == selectedPlayer)
                              || (psDroid->player == selectedPlayer) )
                        {
                              ((DROID*)psBeingTargetted)->bTargetted = true;

                        }
                  }
                  else if(psBeingTargetted->type == OBJ_FEATURE)
                  {
                        if(psDroid->player == selectedPlayer)
                        {
                              ((FEATURE*)psBeingTargetted)->bTargetted = true;
                        }
                  }

            }
      }
      // -----------------

      /* Update the fire damage data */
      if (psDroid->inFire & IN_FIRE)
      {
            /* Still in a fire, reset the fire flag to see if we get out this turn */
            psDroid->inFire = 0;
      }
      else
      {
            /* The fire flag has not been set so we must be out of the fire */
            if (psDroid->inFire & BURNING)
            {
                  if (psDroid->burnStart + BURN_TIME < gameTime)
                  {
                        // stop burning
                        psDroid->inFire = 0;
                        psDroid->burnStart = 0;
                        psDroid->burnDamage = 0;
                  }
                  else
                  {
                        // do burn damage
                        damageToDo = BURN_DAMAGE * ((SDWORD)gameTime - (SDWORD)psDroid->burnStart) /
                                                GAME_TICKS_PER_SEC;
                        damageToDo -= (SDWORD)psDroid->burnDamage;
                        if (damageToDo > 0)
                        {
                              psDroid->burnDamage += damageToDo;

                              //Watermelon:just assume the burn damage is from FRONT
                              droidDamage(psDroid, damageToDo, WC_HEAT,WSC_FLAME, HIT_SIDE_FRONT);
                        }
                  }
            }
            else if (psDroid->burnStart != 0)
            {
                  // just left the fire
                  psDroid->inFire |= BURNING;
                  psDroid->burnStart = gameTime;
                  psDroid->burnDamage = 0;
            }
      }

      droidUpdateRecoil(psDroid);

      calcDroidIllumination(psDroid);

      // Check the resistance level of the droid
      if (psDroid->id % 50 == frameGetFrameNumber() % 50)
      {
            // Zero resistance means not currently been attacked - ignore these
            if (psDroid->resistance && psDroid->resistance < droidResistance(psDroid))
            {
                  // Increase over time if low
                  psDroid->resistance++;
            }
      }

      CHECK_DROID(psDroid);
}

/* See if a droid is next to a structure */
static BOOL droidNextToStruct(DROID *psDroid, BASE_OBJECT *psStruct)
{
      SDWORD      minX, maxX, maxY, x,y;

      CHECK_DROID(psDroid);

      minX = map_coord(psDroid->pos.x) - 1;
      y = map_coord(psDroid->pos.y) - 1;
      maxX = minX + 2;
      maxY = y + 2;
      if (minX < 0)
      {
            minX = 0;
      }
      if (maxX >= (SDWORD)mapWidth)
      {
            maxX = (SDWORD)mapWidth;
      }
      if (y < 0)
      {
            y = 0;
      }
      if (maxY >= (SDWORD)mapHeight)
      {
            maxY = (SDWORD)mapHeight;
      }
      for(;y <= maxY; y++)
      {
            for(x = minX;x <= maxX; x++)
            {
                  if (TileHasStructure(mapTile((UWORD)x,(UWORD)y)) &&
                        getTileStructure(x,y) == (STRUCTURE *)psStruct)

                  {
                        return true;
                  }
            }
      }

      return false;
}

static BOOL
droidCheckBuildStillInProgress( void *psObj )
{
      DROID *psDroid;

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

      psDroid = (DROID*)psObj;
      CHECK_DROID(psDroid);

      if ( !psDroid->died && psDroid->action == DACTION_BUILD )
      {
            return true;
      }
      else
      {
            return false;
      }
}

static BOOL
droidBuildStartAudioCallback( void *psObj )
{
      DROID *psDroid;

      psDroid = (DROID*)psObj;

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

      if ( psDroid->visible[selectedPlayer] )
      {
            audio_PlayObjDynamicTrack( psDroid, ID_SOUND_CONSTRUCTION_LOOP, droidCheckBuildStillInProgress );
      }

      return true;
}


/* Set up a droid to build a structure - returns true if successful */
BOOL droidStartBuild(DROID *psDroid)
{
      STRUCTURE *psStruct;

      CHECK_DROID(psDroid);

      /* See if we are starting a new structure */
      if ((psDroid->psTarget == NULL) &&
            (psDroid->order == DORDER_BUILD ||
             psDroid->order == DORDER_LINEBUILD))
      {
            STRUCTURE_STATS *psStructStat = (STRUCTURE_STATS *)psDroid->psTarStats;
            STRUCTURE_LIMITS *structLimit = &asStructLimits[psDroid->player][psStructStat - asStructureStats];

            //need to check structLimits have not been exceeded
            if (structLimit->currentQuantity >= structLimit->limit)
            {
                  intBuildFinished(psDroid);
                  return false;
            }
            //ok to build
            psStruct = buildStructure(psStructStat, psDroid->orderX,psDroid->orderY, psDroid->player,false);
            if (!psStruct)
            {
                  intBuildFinished(psDroid);
                  return false;
            }

            if (bMultiPlayer)
            {
                  if(myResponsibility(psDroid->player) )
                  {
                        sendBuildStarted(psStruct, psDroid);
                  }
            }

      }
      else
      {
            /* Check the structure is still there to build (joining a partially built struct) */
            psStruct = (STRUCTURE *)psDroid->psTarget;
            if (!droidNextToStruct(psDroid, (BASE_OBJECT *)psStruct))
            {
                  /* Nope - stop building */
                  debug( LOG_NEVER, "unitStartBuild: not next to structure\n" );
            }
      }

      //check structure not already built
      if (psStruct->status != SS_BUILT)
      {
            psDroid->actionStarted = gameTime;
            psDroid->actionPoints = 0;
            setDroidTarget(psDroid, (BASE_OBJECT *)psStruct);
            setDroidActionTarget(psDroid, (BASE_OBJECT *)psStruct, 0);
      }

      if ( psStruct->visible[selectedPlayer] )
      {
            audio_PlayObjStaticTrackCallback( psDroid, ID_SOUND_CONSTRUCTION_START,
                                                            droidBuildStartAudioCallback );
      }

      CHECK_DROID(psDroid);

      return true;
}

static void droidAddWeldSound( Vector3i iVecEffect )
{
      SDWORD            iAudioID;

      iAudioID = ID_SOUND_CONSTRUCTION_1 + (rand()%4);

      audio_PlayStaticTrack( iVecEffect.x, iVecEffect.z, iAudioID );
}

static void addConstructorEffect(STRUCTURE *psStruct)
{
      UDWORD            widthRange,breadthRange;
      Vector3i temp;

      //FIXME
      if((ONEINTEN) && (psStruct->visible[selectedPlayer]))
      {
            /* This needs fixing - it's an arse effect! */
            widthRange = (psStruct->pStructureType->baseWidth*TILE_UNITS)/4;
            breadthRange = (psStruct->pStructureType->baseBreadth*TILE_UNITS)/4;
            temp.x = psStruct->pos.x+((rand()%(2*widthRange)) - widthRange);
            temp.y = map_TileHeight(map_coord(psStruct->pos.x), map_coord(psStruct->pos.y))+
                                    (psStruct->sDisplay.imd->max.y / 6);
            temp.z = psStruct->pos.y+((rand()%(2*breadthRange)) - breadthRange);
            if(rand()%2)
            {
                  droidAddWeldSound( temp );
            }
      }
}

/* Update a construction droid while it is building
   returns true while building continues */
BOOL droidUpdateBuild(DROID *psDroid)
{
      UDWORD            pointsToAdd, constructPoints;
      STRUCTURE   *psStruct;

      CHECK_DROID(psDroid);

      ASSERT( psDroid->action == DACTION_BUILD,
            "unitUpdateBuild: unit is not building" );
      psStruct = (STRUCTURE *)psDroid->psTarget;
      ASSERT( psStruct->type == OBJ_STRUCTURE,
            "unitUpdateBuild: target is not a structure" );
      ASSERT( psDroid->asBits[COMP_CONSTRUCT].nStat < numConstructStats,
            "unitUpdateBuild: Invalid construct pointer for unit" );

      // First check the structure hasn't been completed by another droid
      if (psStruct->status == SS_BUILT)
      {
            // Update the interface
            intBuildFinished(psDroid);

            debug( LOG_NEVER, "DACTION_BUILD: done\n");
            psDroid->action = DACTION_NONE;

            return false;
      }

    // For now wait until have enough power to build
    if (psStruct->currentPowerAccrued < (SWORD) structPowerToBuild(psStruct))
    {
        psDroid->actionStarted = gameTime;
        return true;
    }

      constructPoints = constructorPoints(asConstructStats + psDroid->
            asBits[COMP_CONSTRUCT].nStat, psDroid->player);

      pointsToAdd = constructPoints * (gameTime - psDroid->actionStarted) /
            GAME_TICKS_PER_SEC;

      psStruct->currentBuildPts = (SWORD) (psStruct->currentBuildPts + pointsToAdd - psDroid->actionPoints);

      //store the amount just added
      psDroid->actionPoints = pointsToAdd;

      //check if structure is built
      if (psStruct->currentBuildPts > (SDWORD)psStruct->pStructureType->buildPoints)
      {
            psStruct->currentBuildPts = (SWORD)psStruct->pStructureType->buildPoints;
            psStruct->status = SS_BUILT;
            buildingComplete(psStruct);

            intBuildFinished(psDroid);

            if((bMultiPlayer) && myResponsibility(psStruct->player))
            {
                  SendBuildFinished(psStruct);
            }


            //only play the sound if selected player
            if (psStruct->player == selectedPlayer
             && (psDroid->order != DORDER_LINEBUILD
              || (map_coord(psDroid->orderX) == map_coord(psDroid->orderX2)
               && map_coord(psDroid->orderY) == map_coord(psDroid->orderY2))))
            {
                  audio_QueueTrackPos( ID_SOUND_STRUCTURE_COMPLETED,
                              psStruct->pos.x, psStruct->pos.y, psStruct->pos.z );
                  intRefreshScreen();           // update any open interface bars.
            }

            /* Not needed, but left for backward compatibility */
            structureCompletedCallback(psStruct->pStructureType);

            /* must reset here before the callback, droid must have DACTION_NONE
                 in order to be able to start a new built task, doubled in actionUpdateDroid() */
            debug( LOG_NEVER, "DACTION_NONE: done\n");
            psDroid->action = DACTION_NONE;

            /* Notify scripts we just finished building a structure, pass builder and what was built */
            psScrCBNewStruct  = psStruct;
            psScrCBNewStructTruck= psDroid;
            eventFireCallbackTrigger((TRIGGER_TYPE)CALL_STRUCTBUILT);

            audio_StopObjTrack( psDroid, ID_SOUND_CONSTRUCTION_LOOP );

            return false;
      }
      else
      {
            addConstructorEffect(psStruct);
      }
      
      return true;
}

BOOL droidStartDemolishing( DROID *psDroid )
{
      STRUCTURE   *psStruct;

      CHECK_DROID(psDroid);

      ASSERT( psDroid->order == DORDER_DEMOLISH,
            "unitStartDemolishing: unit is not demolishing" );
      psStruct = (STRUCTURE *)psDroid->psTarget;
      ASSERT( psStruct->type == OBJ_STRUCTURE,
            "unitStartDemolishing: target is not a structure" );

      psDroid->actionStarted = gameTime;
      psDroid->actionPoints  = 0;

      /* init build points Don't - could be partially demolished*/
      //psStruct->currentBuildPts = psStruct->pStructureType->buildPoints;
      psStruct->status = SS_BEING_DEMOLISHED;

      // Set height scale for demolishing
      //psStruct->heightScale = (float)psStruct->currentBuildPts /
      //    psStruct->pStructureType->buildPoints;

      //if start to demolish a power gen need to inform the derricks
      if (psStruct->pStructureType->type == REF_POWER_GEN)
      {
            releasePowerGen(psStruct);
      }

      CHECK_DROID(psDroid);

      return true;
}

BOOL droidUpdateDemolishing( DROID *psDroid )
{
      STRUCTURE   *psStruct;
      UDWORD            pointsToAdd, constructPoints;

      CHECK_DROID(psDroid);

      ASSERT( psDroid->action == DACTION_DEMOLISH,
            "unitUpdateDemolishing: unit is not demolishing" );
      psStruct = (STRUCTURE *)psDroid->psTarget;
      ASSERT( psStruct->type == OBJ_STRUCTURE,
            "unitUpdateDemolishing: target is not a structure" );

      //constructPoints = (asConstructStats + psDroid->asBits[COMP_CONSTRUCT].nStat)->
      //    constructPoints;
      constructPoints = constructorPoints(asConstructStats + psDroid->
            asBits[COMP_CONSTRUCT].nStat, psDroid->player);

      pointsToAdd = constructPoints * (gameTime - psDroid->actionStarted) /
            GAME_TICKS_PER_SEC;

      psStruct->currentBuildPts = (SWORD)(psStruct->currentBuildPts - pointsToAdd - psDroid->actionPoints);

      //psStruct->heightScale = (float)psStruct->currentBuildPts / psStruct->pStructureType->buildPoints;

      //store the amount just subtracted
      psDroid->actionPoints = pointsToAdd;

      /* check if structure is demolished */
      if ( psStruct->currentBuildPts <= 0 )
      {

            if(bMultiPlayer)
            {
                  SendDemolishFinished(psStruct,psDroid);
            }


            if(psStruct->pStructureType->type == REF_POWER_GEN)
            {
            //if had module attached - the base must have been completely built
            if (psStruct->pFunctionality->powerGenerator.capacity)
            {
                //so add the power required to build the base struct
                addPower(psStruct->player, psStruct->pStructureType->powerToBuild);
            }
            //add the currentAccruedPower since this may or may not be all required
            addPower(psStruct->player, psStruct->currentPowerAccrued);
            }
            else
            {
            //if it had a module attached, need to add the power for the base struct as well
            if (StructIsFactory(psStruct))
            {
                if (psStruct->pFunctionality->factory.capacity)
                {
                    //add half power for base struct
                    addPower(psStruct->player, psStruct->pStructureType->
                        powerToBuild / 2);
                    //if large factory - add half power for one upgrade
                    if (psStruct->pFunctionality->factory.capacity > SIZE_MEDIUM)
                    {
                        addPower(psStruct->player, structPowerToBuild(psStruct) / 2);
                    }
                }
            }
            else if (psStruct->pStructureType->type == REF_RESEARCH)
            {
                if (psStruct->pFunctionality->researchFacility.capacity)
                {
                    //add half power for base struct
                    addPower(psStruct->player, psStruct->pStructureType->powerToBuild / 2);
                }
            }
            //add currentAccrued for the current layer of the structure
            addPower(psStruct->player, psStruct->currentPowerAccrued / 2);
        }
            /* remove structure and foundation */
            removeStruct( psStruct, true );

            /* reset target stats*/
          psDroid->psTarStats = NULL;

            return false;
      }
    else
    {
            addConstructorEffect(psStruct);
      }

      CHECK_DROID(psDroid);

      return true;
}

/* Set up a droid to clear a wrecked building feature - returns true if successful */
BOOL droidStartClearing( DROID *psDroid )
{
      FEATURE                 *psFeature;

      CHECK_DROID(psDroid);

      ASSERT( psDroid->order == DORDER_CLEARWRECK,
            "unitStartClearing: unit is not clearing wreckage" );
      psFeature = (FEATURE *)psDroid->psTarget;
      ASSERT( psFeature->type == OBJ_FEATURE,
            "unitStartClearing: target is not a feature" );
      ASSERT( psFeature->psStats->subType == FEAT_BUILD_WRECK,
            "unitStartClearing: feature is not a wrecked building" );

      psDroid->actionStarted = gameTime;
      psDroid->actionPoints  = 0;

      CHECK_DROID(psDroid);

      return true;
}

/* Update a construction droid while it is clearing
   returns true while continues */
BOOL droidUpdateClearing( DROID *psDroid )
{
      FEATURE           *psFeature;
      UDWORD            pointsToAdd, constructPoints;

      CHECK_DROID(psDroid);

      ASSERT( psDroid->action == DACTION_CLEARWRECK,
            "unitUpdateClearing: unit is not clearing wreckage" );
      psFeature = (FEATURE *)psDroid->psTarget;
      ASSERT( psFeature->type == OBJ_FEATURE,
            "unitStartClearing: target is not a feature" );
      ASSERT( psFeature->psStats->subType == FEAT_BUILD_WRECK,
            "unitStartClearing: feature is not a wrecked building" );

      if (psFeature->body > 0)
      {
            constructPoints = constructorPoints(asConstructStats + psDroid->
                  asBits[COMP_CONSTRUCT].nStat, psDroid->player);

            pointsToAdd = constructPoints * (gameTime - psDroid->actionStarted) /
                  GAME_TICKS_PER_SEC;

            psFeature->body -= (pointsToAdd - psDroid->actionPoints);

            //store the amount just subtracted
            psDroid->actionPoints = pointsToAdd;
      }

      /* check if structure is demolished */
      if ( psFeature->body <= 0 )
      {
            /* remove feature */
            removeFeature(psFeature);

            /* reset target stats */
            psDroid->psTarStats = NULL;

            CHECK_DROID(psDroid);

            return false;
      }

      CHECK_DROID(psDroid);

      return true;
}

BOOL droidStartRepair( DROID *psDroid )
{
      STRUCTURE   *psStruct;

      CHECK_DROID(psDroid);

      psStruct = (STRUCTURE *)psDroid->psActionTarget[0];
      ASSERT( psStruct->type == OBJ_STRUCTURE,
            "unitStartRepair: target is not a structure" );

      psDroid->actionStarted = gameTime;
      psDroid->actionPoints  = 0;

      CHECK_DROID(psDroid);

      return true;
}


/*Start a Repair Droid working on a damaged droid*/
BOOL droidStartDroidRepair( DROID *psDroid )
{
      DROID *psDroidToRepair;

      CHECK_DROID(psDroid);

      psDroidToRepair = (DROID *)psDroid->psActionTarget[0];
      ASSERT( psDroidToRepair->type == OBJ_DROID,
            "unitStartUnitRepair: target is not a unit" );

      psDroid->actionStarted = gameTime;
      psDroid->actionPoints  = 0;

      CHECK_DROID(psDroid);

      return true;
}

/*checks a droids current body points to see if need to self repair*/
void droidSelfRepair(DROID *psDroid)
{
      CHECK_DROID(psDroid);

      if (!isVtolDroid(psDroid))
      {
            if (psDroid->body < psDroid->originalBody)
            {
                  if (psDroid->asBits[COMP_REPAIRUNIT].nStat != 0)
                  {
                        psDroid->action = DACTION_DROIDREPAIR;
                        setDroidActionTarget(psDroid, (BASE_OBJECT *)psDroid, 0);
                        psDroid->actionStarted = gameTime;
                        psDroid->actionPoints  = 0;
                  }
            }
      }

      CHECK_DROID(psDroid);
}


/*Start a EW weapon droid working on a low resistance structure*/
BOOL droidStartRestore( DROID *psDroid )
{
      STRUCTURE   *psStruct;

      CHECK_DROID(psDroid);

      ASSERT( psDroid->order == DORDER_RESTORE,
            "unitStartRestore: unit is not restoring" );
      psStruct = (STRUCTURE *)psDroid->psTarget;
      ASSERT( psStruct->type == OBJ_STRUCTURE,
            "unitStartRestore: target is not a structure" );

      psDroid->actionStarted = gameTime;
      psDroid->actionPoints  = 0;

      CHECK_DROID(psDroid);

      return true;
}

/*continue restoring a structure*/
BOOL droidUpdateRestore( DROID *psDroid )
{
      STRUCTURE         *psStruct;
      UDWORD                  pointsToAdd, restorePoints;
      WEAPON_STATS      *psStats;

      CHECK_DROID(psDroid);

      ASSERT( psDroid->action == DACTION_RESTORE,
            "unitUpdateRestore: unit is not restoring" );
      psStruct = (STRUCTURE *)psDroid->psTarget;
      ASSERT( psStruct->type == OBJ_STRUCTURE,
            "unitUpdateRestore: target is not a structure" );
      ASSERT( psStruct->pStructureType->resistance != 0,
            "unitUpdateRestore: invalid structure for EW" );

      ASSERT( psDroid->asWeaps[0].nStat > 0,
            "unitUpdateRestore: droid doean't have any weapons" );

      psStats = asWeaponStats + psDroid->asWeaps[0].nStat;

      ASSERT( psStats->weaponSubClass == WSC_ELECTRONIC,
            "unitUpdateRestore: unit's weapon is not EW" );

      restorePoints = calcDamage(weaponDamage(psStats, psDroid->player),
            psStats->weaponEffect,(BASE_OBJECT *)psStruct);

      pointsToAdd = restorePoints * (gameTime - psDroid->actionStarted) /
            GAME_TICKS_PER_SEC;

      psStruct->resistance = (SWORD)(psStruct->resistance + (pointsToAdd - psDroid->actionPoints));

      //store the amount just added
      psDroid->actionPoints = pointsToAdd;

      CHECK_DROID(psDroid);

      /* check if structure is restored */
      if (psStruct->resistance < (SDWORD)structureResistance(psStruct->
            pStructureType, psStruct->player))
      {
            return true;
      }
      else
      {
            addConsoleMessage(_("Structure Restored") ,DEFAULT_JUSTIFY,SYSTEM_MESSAGE);
            psStruct->resistance = (UWORD)structureResistance(psStruct->pStructureType,
                  psStruct->player);
            return false;
      }
}

/* Code to have the droid's weapon assembly rock back upon firing */
void  droidUpdateRecoil( DROID *psDroid )
{
      UDWORD      percent;
      UDWORD      recoil;
      float fraction;
      //Watermelon:added multiple weapon update
      UBYTE i = 0;
      UBYTE num_weapons = 0;

      CHECK_DROID(psDroid);

      if (psDroid->numWeaps > 1)
      {
            for(i = 0;i < psDroid->numWeaps;i++)
            {
                  if (psDroid->asWeaps[i].nStat != 0)
                  {
                        num_weapons += (1 << (i+1));
                  }
            }
      }
      else
      {
            if (psDroid->asWeaps[0].nStat == 0)
            {
                  return;
            }
            num_weapons = 2;
      }

      for (i = 0; i < psDroid->numWeaps; i++)
      {
            if ( (num_weapons & (1 << (i+1))) )
            {
                  /* Check it's actually got a weapon */
                  if(psDroid->asWeaps[i].nStat == 0)
                  {
                        continue;
                  }

                  /* We have a weapon */
                  if(gameTime > (psDroid->asWeaps[i].lastFired + DEFAULT_RECOIL_TIME) )
                  {
                        /* Recoil effect is over */
                        psDroid->asWeaps[i].recoilValue = 0;
                        continue;
                  }

                  /* Where should the assembly be? */
                  percent = PERCENT((gameTime-psDroid->asWeaps[i].lastFired),DEFAULT_RECOIL_TIME);

                  /* Outward journey */
                  if(percent >= 50)
                  {
                        recoil = (100 - percent)/5;
                  }
                  /* Return journey */
                  else
                  {
                        recoil = percent/5;
                  }

                  fraction =
                        (float)asWeaponStats[psDroid->asWeaps[i].nStat].recoilValue / 100.f;

                  recoil = (float)recoil * fraction;

                  /* Put it into the weapon data */
                  psDroid->asWeaps[i].recoilValue = recoil;
            }
      }
      CHECK_DROID(psDroid);
}


BOOL droidUpdateRepair( DROID *psDroid )
{
      STRUCTURE   *psStruct;
      UDWORD            iPointsToAdd, iRepairPoints;

      CHECK_DROID(psDroid);

      ASSERT( psDroid->action == DACTION_REPAIR,
            "unitUpdateRepair: unit does not have repair order" );
      psStruct = (STRUCTURE *)psDroid->psActionTarget[0];
      ASSERT( psStruct->type == OBJ_STRUCTURE,
            "unitUpdateRepair: target is not a structure" );

      iRepairPoints = constructorPoints(asConstructStats + psDroid->
            asBits[COMP_CONSTRUCT].nStat, psDroid->player);

      iPointsToAdd = iRepairPoints * (gameTime - psDroid->actionStarted) /
            GAME_TICKS_PER_SEC;

      /* add points to structure */
      psStruct->body = (UWORD)(psStruct->body  + (iPointsToAdd - psDroid->actionPoints));

      /* store the amount just added */
      psDroid->actionPoints = iPointsToAdd;

      /* if not finished repair return true else complete repair and return false */
      if ( psStruct->body < structureBody(psStruct))
      {
            return true;
      }
      else
      {
            psStruct->body = (UWORD)structureBody(psStruct);
            return false;
      }
}

/*Updates a Repair Droid working on a damaged droid*/
BOOL droidUpdateDroidRepair(DROID *psRepairDroid)
{
      DROID       *psDroidToRepair;
      UDWORD            iPointsToAdd, iRepairPoints, powerCost;
      Vector3i iVecEffect;

      CHECK_DROID(psRepairDroid);

      ASSERT( psRepairDroid->action == DACTION_DROIDREPAIR,
            "unitUpdateUnitRepair: unit does not have unit repair order" );
      ASSERT( psRepairDroid->asBits[COMP_REPAIRUNIT].nStat != 0,
            "unitUpdateUnitRepair: unit does not have a repair turret" );

      psDroidToRepair = (DROID *)psRepairDroid->psActionTarget[0];
      ASSERT( psDroidToRepair->type == OBJ_DROID,
            "unitUpdateUnitRepair: target is not a unit" );

    //the amount of power accrued limits how much of the work can be done
    //self-repair doesn't cost power
    if (psRepairDroid == psDroidToRepair)
    {
        powerCost = 0;
    }
    else
    {
        //check if enough power to do any
        powerCost = powerReqForDroidRepair(psDroidToRepair);
        if (powerCost > psDroidToRepair->powerAccrued)
        {
            powerCost = (powerCost - psDroidToRepair->powerAccrued) / POWER_FACTOR;
        }
        else
        {
            powerCost = 0;
        }

    }

    //if the power cost is 0 (due to rounding) then do for free!
    if (powerCost)
    {
        if (!psDroidToRepair->powerAccrued)
        {
            //reset the actionStarted time and actionPoints added so the correct
            //amount of points are added when there is more power
                  psRepairDroid->actionStarted = gameTime;
            //init so repair points to add won't be huge when start up again
            psRepairDroid->actionPoints = 0;
            return true;
        }
    }

      iRepairPoints = repairPoints(asRepairStats + psRepairDroid->
            asBits[COMP_REPAIRUNIT].nStat, psRepairDroid->player);

      //if self repair then add repair points depending on the time delay for the stat
      if (psRepairDroid == psDroidToRepair)
      {
            iPointsToAdd = iRepairPoints * (gameTime - psRepairDroid->actionStarted) /
                  (asRepairStats + psRepairDroid->asBits[COMP_REPAIRUNIT].nStat)->time;
      }
      else
      {
            iPointsToAdd = iRepairPoints * (gameTime - psRepairDroid->actionStarted) /
                  GAME_TICKS_PER_SEC;
      }

    iPointsToAdd -= psRepairDroid->actionPoints;

    if (iPointsToAdd)
    {
        //just add the points if the power cost is negligable
        //if these points would make the droid healthy again then just add
        if (!powerCost || (psDroidToRepair->body + iPointsToAdd >= psDroidToRepair->originalBody))
        {
            //anothe HACK but sorts out all the rounding errors when values get small
            psDroidToRepair->body += iPointsToAdd;
            psRepairDroid->actionPoints += iPointsToAdd;
        }
        else
        {
            //see if we have enough power to do this amount of repair
            powerCost = iPointsToAdd * repairPowerPoint(psDroidToRepair);
            if (powerCost <= psDroidToRepair->powerAccrued)
            {
                psDroidToRepair->body += iPointsToAdd;
                psRepairDroid->actionPoints += iPointsToAdd;
                //subtract the power cost for these points
                psDroidToRepair->powerAccrued -= powerCost;
            }
            else
            {
                /*reset the actionStarted time and actionPoints added so the correct
                amount of points are added when there is more power*/
                psRepairDroid->actionStarted = gameTime;
                psRepairDroid->actionPoints = 0;
            }
        }
    }

      /* add plasma repair effect whilst being repaired */
      if ((ONEINFIVE) && (psDroidToRepair->visible[selectedPlayer]))
      {
            iVecEffect.x = psDroidToRepair->pos.x + DROID_REPAIR_SPREAD;
            iVecEffect.y = psDroidToRepair->pos.z + rand()%8;;
            iVecEffect.z = psDroidToRepair->pos.y + DROID_REPAIR_SPREAD;
            effectGiveAuxVar(90+rand()%20);
            addEffect(&iVecEffect,EFFECT_EXPLOSION,EXPLOSION_TYPE_LASER,false,NULL,0);
            droidAddWeldSound( iVecEffect );
      }

      CHECK_DROID(psRepairDroid);

      /* if not finished repair return true else complete repair and return false */
      if (psDroidToRepair->body < psDroidToRepair->originalBody)
      {
            return true;
      }
      else
      {
        //reset the power accrued
        psDroidToRepair->powerAccrued = 0;
            psDroidToRepair->body = psDroidToRepair->originalBody;
            return false;
      }
}

/* load the Droid stats for the components from the Access database */
BOOL loadDroidTemplates(const char *pDroidData, UDWORD bufferSize)
{
      unsigned int NumDroids = numCR(pDroidData, bufferSize), line;
      BOOL bDefaultTemplateFound = false;

      /* init default template */
      memset( &sDefaultDesignTemplate, 0, sizeof(DROID_TEMPLATE) );

      for (line = 0; line < NumDroids; line++)
      {
            char componentName[MAX_STR_LENGTH];
            unsigned int player;
            int cnt;
            DROID_TEMPLATE *pDroidDesign = malloc(sizeof(DROID_TEMPLATE));
            if (pDroidDesign == NULL)
            {
                  debug(LOG_ERROR, "loadDroidTemplates: pDroidDesign: Out of memory");
                  return false;
            }
            memset(pDroidDesign, 0, sizeof(DROID_TEMPLATE));

            //read the data into the storage - the data is delimited using comma's
            sscanf(pDroidData, "%[^','],%d,%n", componentName, &pDroidDesign->multiPlayerID, &cnt);
            pDroidData += cnt;

            // Store unique name in pName
            pDroidDesign->pName = strdup(componentName);

            // Store translated name in aName
            if (!getDroidResourceName(componentName))
            {
                  free(pDroidDesign->pName);
                  free(pDroidDesign);
                  return false;
            }
            sstrcpy(pDroidDesign->aName, componentName);

            //read in Body Name
            sscanf(pDroidData, "%[^','],%n", componentName, &cnt);
            pDroidData += cnt;

            //get the Body stats pointer
            if (!strcmp(componentName,"0"))
            {
                  pDroidDesign->asParts[COMP_BODY] = NULL_COMP;
            }
            else
            {
                  COMP_BASE_STATS *pStats = (COMP_BASE_STATS *)asBodyStats;
                  const size_t size = sizeof(BODY_STATS);
                  unsigned int inc = 0;
                  BOOL found = false;

                  if (!getResourceName(componentName))
                  {
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }

                  for (inc = 0; inc < numBodyStats; inc++)
                  {
                        //compare the names
                        if (!strcmp(componentName, pStats->pName))
                        {
                              pDroidDesign->asParts[COMP_BODY] = inc;
                              found = true;
                              break;
                        }
                        pStats = ((COMP_BASE_STATS*)((UBYTE*)pStats + size));
                  }
                  if (!found)
                  {
                        debug( LOG_ERROR, "Body component not found for droid %s", getTemplateName(pDroidDesign) );
                        abort();
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }
            }

            //read in Brain Name
            sscanf(pDroidData, "%[^','],%n", componentName, &cnt);
                        pDroidData += cnt;

            //get the Brain stats pointer
            if (!strcmp(componentName,"0"))
            {
                  pDroidDesign->asParts[COMP_BRAIN] = NULL_COMP;
            }
            else
            {
                  COMP_BASE_STATS *pStats = (COMP_BASE_STATS *)asBrainStats;
                  const size_t size = sizeof(BRAIN_STATS);
                  unsigned int inc = 0;
                  BOOL found = false;

                  if (!getResourceName(componentName))
                  {
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }

                  for (inc=0; inc < numBrainStats; inc++)
                  {
                        //compare the names
                        if (!strcmp(componentName, pStats->pName))
                        {
                              pDroidDesign->asParts[COMP_BRAIN] = inc;
                              found = true;
                              break;
                        }
                        pStats = ((COMP_BASE_STATS*)((UBYTE*)pStats + size));
                  }
                  if (!found)
                  {
                        debug( LOG_ERROR, "Brain component not found for droid %s", getTemplateName(pDroidDesign) );
                        abort();
//                      DBERROR(("Brain component not found for droid %s", pDroidDesign->pName));
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }
            }

            //read in Construct Name
            sscanf(pDroidData, "%[^','],%n", componentName, &cnt);
                        pDroidData += cnt;

            //get the Construct stats pointer
            if (!strcmp(componentName,"0"))
            {
                  pDroidDesign->asParts[COMP_CONSTRUCT] = NULL_COMP;
            }
            else
            {
                  COMP_BASE_STATS *pStats = (COMP_BASE_STATS *)asConstructStats;
                  const size_t size = sizeof(CONSTRUCT_STATS);
                  unsigned int inc = 0;
                  BOOL found = false;

                  if (!getResourceName(componentName))
                  {
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }
                  for (inc=0; inc < numConstructStats; inc++)
                  {
                        //compare the names
                        if (!strcmp(componentName, pStats->pName))
                        {
                              pDroidDesign->asParts[COMP_CONSTRUCT] = inc;
                              found = true;
                              break;
                        }
                        pStats = ((COMP_BASE_STATS*)((UBYTE*)pStats + size));
                  }
                  if (!found)
                  {
                        debug( LOG_ERROR, "Construct component not found for droid %s", getTemplateName(pDroidDesign) );
                        abort();
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }
            }

            //read in Ecm Name
            sscanf(pDroidData, "%[^','],%n", componentName, &cnt);
                        pDroidData += cnt;

            //get the Ecm stats pointer
            if (!strcmp(componentName,"0"))
            {
                  pDroidDesign->asParts[COMP_ECM] = NULL_COMP;
            }
            else
            {
                  COMP_BASE_STATS *pStats = (COMP_BASE_STATS *)asECMStats;
                  const size_t size = sizeof(ECM_STATS);
                  unsigned int inc = 0;
                  BOOL found = false;

                  if (!getResourceName(componentName))
                  {
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }
                  for (inc=0; inc < numECMStats; inc++)
                  {
                        //compare the names
                        if (!strcmp(componentName, pStats->pName))
                        {
                              pDroidDesign->asParts[COMP_ECM] = inc;
                              found = true;
                              break;
                        }
                        pStats = ((COMP_BASE_STATS*)((UBYTE*)pStats + size));
                  }
                  if (!found)
                  {
                        debug( LOG_ERROR, "ECM component not found for droid %s", getTemplateName(pDroidDesign) );
                        abort();
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }
            }

            //read in player id - Access decides the order -crap hey?
            sscanf(pDroidData, "%d,%n", &player,&cnt);
            pDroidData += cnt;

            if (getTemplateFromUniqueName(pDroidDesign->pName, player))
            {
                  debug( LOG_ERROR, "Duplicate template %s", pDroidDesign->pName );
                  abort();
                  free(pDroidDesign->pName);
                  free(pDroidDesign);
                  return false;
            }

            //read in Propulsion Name
            sscanf(pDroidData, "%[^','],%n", componentName, &cnt);
            pDroidData += cnt;

            //get the Propulsion stats pointer
            if (!strcmp(componentName,"0"))
            {
                  pDroidDesign->asParts[COMP_PROPULSION] = NULL_COMP;
            }
            else
            {
                  COMP_BASE_STATS *pStats = (COMP_BASE_STATS *)asPropulsionStats;
                  const size_t size = sizeof(PROPULSION_STATS);
                  unsigned int inc = 0;
                  BOOL found = false;

                  if (!getResourceName(componentName))
                  {
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }
                  for (inc=0; inc < numPropulsionStats; inc++)
                  {
                        //compare the names
                        if (!strcmp(componentName, pStats->pName))
                        {
                              pDroidDesign->asParts[COMP_PROPULSION] = inc;
                              found = true;
                              break;
                        }
                        pStats = ((COMP_BASE_STATS*)((UBYTE*)pStats + size));
                  }
                  if (!found)
                  {
                        debug( LOG_ERROR, "Propulsion component not found for droid %s", getTemplateName(pDroidDesign) );
                        abort();
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }
            }

            //read in Repair Name
            sscanf(pDroidData, "%[^','],%n", componentName, &cnt);
            pDroidData += cnt;

            //get the Repair stats pointer
            if (!strcmp(componentName,"0"))
            {
                  pDroidDesign->asParts[COMP_REPAIRUNIT] = NULL_COMP;
            }
            else
            {
                  COMP_BASE_STATS *pStats = (COMP_BASE_STATS *)asRepairStats;
                  const size_t size = sizeof(REPAIR_STATS);
                  unsigned int inc = 0;
                  BOOL found = false;

                  if (!getResourceName(componentName))
                  {
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }
                  for (inc=0; inc < numRepairStats; inc++)
                  {
                        //compare the names
                        if (!strcmp(componentName, pStats->pName))
                        {
                              pDroidDesign->asParts[COMP_REPAIRUNIT] = inc;
                              found = true;
                              break;
                        }
                        pStats = ((COMP_BASE_STATS*)((UBYTE*)pStats + size));
                  }
                  if (!found)
                  {
                        debug( LOG_ERROR, "Repair component not found for droid %s", getTemplateName(pDroidDesign) );
                        abort();
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }
            }

            //read in droid type - only interested if set to PERSON
            sscanf(pDroidData, "%[^','],%n", componentName, &cnt);
            pDroidData += cnt;

            if (!strcmp(componentName, "PERSON"))
            {
                  pDroidDesign->droidType = DROID_PERSON;
            }
            else if (!strcmp(componentName, "CYBORG"))
            {
                  pDroidDesign->droidType = DROID_CYBORG;
            }
            else if (!strcmp(componentName, "CYBORG_SUPER"))
            {
                  pDroidDesign->droidType = DROID_CYBORG_SUPER;
            }
            else if (!strcmp(componentName, "CYBORG_CONSTRUCT"))
            {
                  pDroidDesign->droidType = DROID_CYBORG_CONSTRUCT;
            }
            else if (!strcmp(componentName, "CYBORG_REPAIR"))
            {
                  pDroidDesign->droidType = DROID_CYBORG_REPAIR;
            }
            else if (!strcmp(componentName, "TRANSPORTER"))
            {
                  pDroidDesign->droidType = DROID_TRANSPORTER;
            }
            else if (!strcmp(componentName, "ZNULLDROID"))
            {
                  pDroidDesign->droidType = DROID_DEFAULT;
                  bDefaultTemplateFound = true;
            }

            //read in Sensor Name
            sscanf(pDroidData, "%[^','],%n", componentName, &cnt);
                        pDroidData += cnt;

            //get the Sensor stats pointer
            if (!strcmp(componentName,"0"))
            {
                  pDroidDesign->asParts[COMP_SENSOR] = NULL_COMP;
            }
            else
            {
                  COMP_BASE_STATS *pStats = (COMP_BASE_STATS *)asSensorStats;
                  const size_t size = sizeof(SENSOR_STATS);
                  unsigned int inc = 0;
                  BOOL found = false;

                  if (!getResourceName(componentName))
                  {
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }

                  for (inc=0; inc < numSensorStats; inc++)
                  {
                        //compare the names
                        if (!strcmp(componentName, pStats->pName))
                        {
                              pDroidDesign->asParts[COMP_SENSOR] = inc;
                              found = true;
                              break;
                        }
                        pStats = ((COMP_BASE_STATS*)((UBYTE*)pStats + size));
                  }
                  if (!found)
                  {
                        debug( LOG_ERROR, "Sensor not found for droid Template: %s", pDroidDesign->aName );
                        abort();
                        free(pDroidDesign->pName);
                        free(pDroidDesign);
                        return false;
                  }
            }

            //read in totals
            sscanf(pDroidData,"%d", &pDroidDesign->numWeaps);
            //check that not allocating more weapons than allowed
            if (pDroidDesign->numWeaps > DROID_MAXWEAPS)
            {
                  debug( LOG_ERROR, "Too many weapons have been allocated for droid Template: %s", pDroidDesign->aName );
                  abort();
                  free(pDroidDesign->pName);
                  free(pDroidDesign);
                  return false;
            }


            pDroidDesign->ref = REF_TEMPLATE_START + line;
/*    Loaded in from the database now AB 29/10/98
                  pDroidDesign->multiPlayerID = line;             // another unique number, just for multiplayer stuff.
*/
            /* store global default design if found else
            * store in the appropriate array
            */
            if ( pDroidDesign->droidType == DROID_DEFAULT )
            {
                  // NOTE: sDefaultDesignTemplate.pName takes ownership
                  //       of the memory allocated to pDroidDesign->pName
                  //       here. Which is good because pDroidDesign leaves
                  //       scope here anyway.
                  memcpy( &sDefaultDesignTemplate, pDroidDesign, sizeof(DROID_TEMPLATE) );
                  free(pDroidDesign);
            }
            else
            {
                  pDroidDesign->psNext = apsDroidTemplates[player];
                  apsDroidTemplates[player] = pDroidDesign;
            }

            //increment the pointer to the start of the next record
            pDroidData = strchr(pDroidData,'\n') + 1;
      }

      if ( bDefaultTemplateFound == false )
      {
            debug( LOG_ERROR, "loadUnitTemplates: default template not found\n" );
            abort();
            return false;
      }

      return true;
}

/*initialise the template build and power points */
void initTemplatePoints(void)
{
      UDWORD                  player;
      DROID_TEMPLATE    *pDroidDesign;

      for (player=0; player < MAX_PLAYERS; player++)
      {
            for(pDroidDesign = apsDroidTemplates[player]; pDroidDesign != NULL;
                  pDroidDesign = pDroidDesign->psNext)
            {
                  //calculate the total build points
                  pDroidDesign->buildPoints = calcTemplateBuild(pDroidDesign);
                  //calc the total power points
                  pDroidDesign->powerPoints = calcTemplatePower(pDroidDesign);
            }
      }
}


// return whether a droid is IDF
BOOL idfDroid(DROID *psDroid)
{
    //add Cyborgs
      //if (psDroid->droidType != DROID_WEAPON)
    if (!(psDroid->droidType == DROID_WEAPON || psDroid->droidType == DROID_CYBORG ||
        psDroid->droidType == DROID_CYBORG_SUPER))
      {
            return false;
      }

      if (proj_Direct(psDroid->asWeaps[0].nStat + asWeaponStats))
      {
            return false;
      }

      return true;
}

// return whether a template is for an IDF droid
BOOL templateIsIDF(DROID_TEMPLATE *psTemplate)
{
    //add Cyborgs
    if (!(psTemplate->droidType == DROID_WEAPON || psTemplate->droidType == DROID_CYBORG ||
        psTemplate->droidType == DROID_CYBORG_SUPER))
      {
            return false;
      }

      if (proj_Direct(psTemplate->asWeaps[0] + asWeaponStats))
      {
            return false;
      }

      return true;
}

/* Return the type of a droid */
DROID_TYPE droidType(DROID *psDroid)
{
      return psDroid->droidType;
}


/* Return the type of a droid from it's template */
DROID_TYPE droidTemplateType(DROID_TEMPLATE *psTemplate)
{
      DROID_TYPE  type;

      if (psTemplate->droidType == DROID_PERSON)
      {
            type = DROID_PERSON;
      }
      else if (psTemplate->droidType == DROID_CYBORG)
      {
            type = DROID_CYBORG;
      }
      else if (psTemplate->droidType == DROID_CYBORG_SUPER)
      {
            type = DROID_CYBORG_SUPER;
      }
      else if (psTemplate->droidType == DROID_CYBORG_CONSTRUCT)
      {
            type = DROID_CYBORG_CONSTRUCT;
      }
      else if (psTemplate->droidType == DROID_CYBORG_REPAIR)
      {
            type = DROID_CYBORG_REPAIR;
      }
      else if (psTemplate->droidType == DROID_TRANSPORTER)
      {
            type = DROID_TRANSPORTER;
      }
      else if (psTemplate->asParts[COMP_BRAIN] != 0)
      {
            type = DROID_COMMAND;
      }
      else if ((asSensorStats + psTemplate->asParts[COMP_SENSOR])->location == LOC_TURRET)
      {
            type = DROID_SENSOR;
      }
      else if ((asECMStats + psTemplate->asParts[COMP_ECM])->location == LOC_TURRET)
      {
            type = DROID_ECM;
      }
      else if (psTemplate->asParts[COMP_CONSTRUCT] != 0)
      {
            type = DROID_CONSTRUCT;
      }
      else if ((asRepairStats + psTemplate->asParts[COMP_REPAIRUNIT])->location == LOC_TURRET)
      {
            type = DROID_REPAIR;
      }
      else if ( psTemplate->asWeaps[0] != 0 )
      {
            type = DROID_WEAPON;
      }
      /* Watermelon:with more than weapon is still a DROID_WEAPON */
      else if ( psTemplate->numWeaps > 1)
      {
            type = DROID_WEAPON;
      }
      else
      {
            type = DROID_DEFAULT;
      }

      return type;
}

//Load the weapons assigned to Droids in the Access database
BOOL loadDroidWeapons(const char *pWeaponData, UDWORD bufferSize)
{
      unsigned int NumWeapons = numCR(pWeaponData, bufferSize);
      unsigned int SkippedWeaponCount = 0;
      unsigned int line = 0;

      ASSERT(NumWeapons > 0, "template without weapons");

      for (line = 0; line < NumWeapons; line++)
      {
            unsigned int player;
            char WeaponName[DROID_MAXWEAPS][MAX_STR_LENGTH] = {{'\0'}},
                  TemplateName[MAX_STR_LENGTH] = {'\0'};

            //read the data into the storage - the data is delimeted using comma's
            sscanf(pWeaponData, "%[^','],%[^','],%[^','],%[^','],%d",
                  TemplateName, WeaponName[0], WeaponName[1], WeaponName[2], &player);

            if (player < MAX_PLAYERS)
            {
                  unsigned int j;

                  DROID_TEMPLATE *pTemplate = getTemplateFromUniqueName(TemplateName, player);

                  /* if Template not found - try default design */
                  if (!pTemplate)
                  {
                        if (strcmp(TemplateName, sDefaultDesignTemplate.pName) == 0)
                        {
                              pTemplate = &sDefaultDesignTemplate;
                        }
                        else
                        {
                              debug( LOG_ERROR, "Unable to find Template - %s", TemplateName );
                              abort();
                              return false;
                        }
                  }

                  ASSERT(pTemplate->numWeaps <= DROID_MAXWEAPS, "stack corruption unavoidable");

                  for (j = 0; j < pTemplate->numWeaps; j++)
                  {
                        int incWpn;

                        if (!getResourceName(WeaponName[j]))
                        {
                              return false;
                        }

                        incWpn = getCompFromName(COMP_WEAPON, WeaponName[j]);

                        //if weapon not found - error
                        if (incWpn == -1)
                        {
                              debug( LOG_ERROR, "Unable to find Weapon %s for template %s",
                                    WeaponName[j], TemplateName );
                              abort();
                              return false;
                        }
                        else
                        {
                              //Weapon found, alloc this to the current Template
                              pTemplate->asWeaps[pTemplate->storeCount] = incWpn;

                              //check not allocating more than allowed
                              if (pTemplate->storeCount > pTemplate->numWeaps)
                              {
                                    debug( LOG_ERROR, "Trying to allocate more weapons than allowed for Template - %s", TemplateName );
                                    abort();
                                    return false;
                              }
                              //check valid weapon/propulsion
                              if (!checkValidWeaponForProp(pTemplate))
                              {
                                    debug( LOG_ERROR, "Weapon is invalid for air propulsion for template %s", pTemplate->aName );
                                    abort();

                                    return false;
                              }

                              pTemplate->storeCount++;
                        }
                  }
            }
            else
            {
                  SkippedWeaponCount++;
            }

            //increment the pointer to the start of the next record
            pWeaponData = strchr(pWeaponData, '\n') + 1;
      }

      if (SkippedWeaponCount > 0)
      {
            debug( LOG_ERROR, "Illegal player number in %d droid weapons", SkippedWeaponCount );
            abort();
      }

      return true;
}

//free the storage for the droid templates
BOOL droidTemplateShutDown(void)
{
      unsigned int player;

      for (player = 0; player < MAX_PLAYERS; player++)
      {
            DROID_TEMPLATE *pTemplate, *pNext;

            for (pTemplate = apsDroidTemplates[player]; pTemplate != NULL; pTemplate = pNext)
            {
                  pNext = pTemplate->psNext;
                  ASSERT(sDefaultDesignTemplate.pName != pTemplate->pName, "We'll soon be getting a double free()!!!");
                  if (pTemplate->pName != sDefaultDesignTemplate.pName)
                  {
                        free(pTemplate->pName);
                  }
                  free(pTemplate);
            }
            apsDroidTemplates[player] = NULL;
      }
      free(sDefaultDesignTemplate.pName);
      sDefaultDesignTemplate.pName = NULL;

      return true;
}

/* Calculate the weight of a droid from it's template */
UDWORD calcDroidWeight(DROID_TEMPLATE *psTemplate)
{
      UDWORD weight, i;

      /* Get the basic component weight */
      weight =
            (asBodyStats + psTemplate->asParts[COMP_BODY])->weight +
            (asBrainStats + psTemplate->asParts[COMP_BRAIN])->weight +
            //(asPropulsionStats + psTemplate->asParts[COMP_PROPULSION])->weight +
            (asSensorStats + psTemplate->asParts[COMP_SENSOR])->weight +
            (asECMStats + psTemplate->asParts[COMP_ECM])->weight +
            (asRepairStats + psTemplate->asParts[COMP_REPAIRUNIT])->weight +
            (asConstructStats + psTemplate->asParts[COMP_CONSTRUCT])->weight;

      /* propulsion weight is a percentage of the body weight */
      weight += (((asPropulsionStats + psTemplate->asParts[COMP_PROPULSION])->weight *
            (asBodyStats + psTemplate->asParts[COMP_BODY])->weight) / 100);

      /* Add the weapon weight */
      for(i=0; i<psTemplate->numWeaps; i++)
      {
            weight += (asWeaponStats + psTemplate->asWeaps[i])->weight;
      }

      return weight;
}

/* Calculate the body points of a droid from it's template */
UDWORD calcTemplateBody(DROID_TEMPLATE *psTemplate, UBYTE player)
{
      UDWORD body, i;

      if (psTemplate == NULL)
      {
            return 0;
      }
      /* Get the basic component body points */
      body =
            (asBodyStats + psTemplate->asParts[COMP_BODY])->body +
            (asBrainStats + psTemplate->asParts[COMP_BRAIN])->body +
            (asSensorStats + psTemplate->asParts[COMP_SENSOR])->body +
            (asECMStats + psTemplate->asParts[COMP_ECM])->body +
            (asRepairStats + psTemplate->asParts[COMP_REPAIRUNIT])->body +
            (asConstructStats + psTemplate->asParts[COMP_CONSTRUCT])->body;

      /* propulsion body points are a percentage of the bodys' body points */
      body += (((asPropulsionStats + psTemplate->asParts[COMP_PROPULSION])->body *
            (asBodyStats + psTemplate->asParts[COMP_BODY])->body) / 100);

      /* Add the weapon body points */
      for(i=0; i<psTemplate->numWeaps; i++)
      {
            body += (asWeaponStats + psTemplate->asWeaps[i])->body;
      }

      //add on any upgrade value that may need to be applied
      body += (body * asBodyUpgrade[player]->body / 100);
      return body;
}

/* Calculate the base body points of a droid without upgrades*/
UDWORD calcDroidBaseBody(DROID *psDroid)
{
      //Watermelon:re-enabled i;
      UDWORD      body, i;

      if (psDroid == NULL)
      {
            return 0;
      }
      /* Get the basic component body points */
      body =
            (asBodyStats + psDroid->asBits[COMP_BODY].nStat)->body +
            (asBrainStats + psDroid->asBits[COMP_BRAIN].nStat)->body +
            (asSensorStats + psDroid->asBits[COMP_SENSOR].nStat)->body +
            (asECMStats + psDroid->asBits[COMP_ECM].nStat)->body +
            (asRepairStats + psDroid->asBits[COMP_REPAIRUNIT].nStat)->body +
            (asConstructStats + psDroid->asBits[COMP_CONSTRUCT].nStat)->body;

      /* propulsion body points are a percentage of the bodys' body points */
      body += (((asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat)->body *
            (asBodyStats + psDroid->asBits[COMP_BODY].nStat)->body) / 100);

      /* Add the weapon body points */
      for(i=0; i<psDroid->numWeaps; i++)
      {
            if (psDroid->asWeaps[i].nStat > 0)
            {
                  //body += (asWeaponStats + psDroid->asWeaps[i].nStat)->body;
                  body += (asWeaponStats + psDroid->asWeaps[i].nStat)->body;
            }
      }

      return body;
}


/* Calculate the base speed of a droid from it's template */
UDWORD calcDroidBaseSpeed(DROID_TEMPLATE *psTemplate, UDWORD weight, UBYTE player)
{
      UDWORD      speed;
      //Watermelon:engine output bonus? 150%
      float eoBonus = 1.5f;

      if (psTemplate->droidType == DROID_CYBORG ||
            psTemplate->droidType == DROID_CYBORG_SUPER ||
        psTemplate->droidType == DROID_CYBORG_CONSTRUCT ||
        psTemplate->droidType == DROID_CYBORG_REPAIR)
      {
            speed = (asPropulsionTypes[(asPropulsionStats + psTemplate->
            asParts[COMP_PROPULSION])->propulsionType].powerRatioMult *
            bodyPower(asBodyStats + psTemplate->asParts[COMP_BODY],
            player, CYBORG_BODY_UPGRADE)) / weight;
      }
      else
      {
            speed = (asPropulsionTypes[(asPropulsionStats + psTemplate->
            asParts[COMP_PROPULSION])->propulsionType].powerRatioMult *
            bodyPower(asBodyStats + psTemplate->asParts[COMP_BODY],
                  player, DROID_BODY_UPGRADE)) / weight;
      }

      // reduce the speed of medium/heavy VTOLs
      if (asPropulsionStats[psTemplate->asParts[COMP_PROPULSION]].propulsionType == LIFT)
      {
            if ((asBodyStats + psTemplate->asParts[COMP_BODY])->size == SIZE_HEAVY)
            {
                  speed /= 4;
            }
            else if ((asBodyStats + psTemplate->asParts[COMP_BODY])->size == SIZE_MEDIUM)
            {
                  speed = 3 * speed / 4;
            }
      }

      // Wateremelon:applies the engine output bonus if output > weight
      if ( (asBodyStats + psTemplate->asParts[COMP_BODY])->powerOutput > weight )
      {
            speed *= eoBonus;
      }

      return speed;
}


/* Calculate the speed of a droid over a terrain */
UDWORD calcDroidSpeed(UDWORD baseSpeed, UDWORD terrainType, UDWORD propIndex, UDWORD level)
{
      PROPULSION_STATS  *propulsion = asPropulsionStats + propIndex;
      UDWORD                        speed;

    speed  = baseSpeed;
    // Factor in terrain
    speed *= getSpeedFactor(terrainType, propulsion->propulsionType);
    speed /= 100; 
    // Factor in experience
    speed *= (100 + EXP_SPEED_BONUS * level);
    speed /= 100;

      // Need to ensure doesn't go over the max speed possible for this propulsion
      if (speed > propulsion->maxSpeed)
      {
            speed = propulsion->maxSpeed;
      }
      
      return speed;
}

/* Calculate the points required to build the template - used to calculate time*/
UDWORD calcTemplateBuild(DROID_TEMPLATE *psTemplate)
{
      UDWORD      build, i;

      build = (asBodyStats + psTemplate->asParts[COMP_BODY])->buildPoints +
      (asBrainStats + psTemplate->asParts[COMP_BRAIN])->buildPoints +
      (asSensorStats + psTemplate->asParts[COMP_SENSOR])->buildPoints +
      (asECMStats + psTemplate->asParts[COMP_ECM])->buildPoints +
      (asRepairStats + psTemplate->asParts[COMP_REPAIRUNIT])->buildPoints +
      (asConstructStats + psTemplate->asParts[COMP_CONSTRUCT])->buildPoints;

      /* propulsion build points are a percentage of the bodys' build points */
      build += (((asPropulsionStats + psTemplate->asParts[COMP_PROPULSION])->buildPoints *
            (asBodyStats + psTemplate->asParts[COMP_BODY])->buildPoints) / 100);

      //add weapon power
      for(i=0; i<psTemplate->numWeaps; i++)
      {
            ASSERT( psTemplate->asWeaps[i]<numWeaponStats,
                  //"Invalid Template weapon for %s", psTemplate->pName );
                  "Invalid Template weapon for %s", getTemplateName(psTemplate) );
            build += (asWeaponStats + psTemplate->asWeaps[i])->buildPoints;
      }

      return build;
}


/* Calculate the power points required to build/maintain a template */
UDWORD      calcTemplatePower(DROID_TEMPLATE *psTemplate)
{
      UDWORD power, i;

      //get the component power
      power = (asBodyStats + psTemplate->asParts[COMP_BODY])->buildPower;
      power += (asBrainStats + psTemplate->asParts[COMP_BRAIN])->buildPower;
      power += (asSensorStats + psTemplate->asParts[COMP_SENSOR])->buildPower;
      power += (asECMStats + psTemplate->asParts[COMP_ECM])->buildPower;
      power += (asRepairStats + psTemplate->asParts[COMP_REPAIRUNIT])->buildPower;
      power += (asConstructStats + psTemplate->asParts[COMP_CONSTRUCT])->buildPower;

      /* propulsion power points are a percentage of the bodys' power points */
      power += (((asPropulsionStats + psTemplate->asParts[COMP_PROPULSION])->buildPower *
            (asBodyStats + psTemplate->asParts[COMP_BODY])->buildPower) / 100);

      //add weapon power
      for(i=0; i<psTemplate->numWeaps; i++)
      {
            power += (asWeaponStats + psTemplate->asWeaps[i])->buildPower;
      }

      return power;
}


/* Calculate the power points required to build/maintain a droid */
UDWORD      calcDroidPower(DROID *psDroid)
{
      //Watermelon:re-enabled i
      UDWORD      power, i;

      //get the component power
      power = (asBodyStats + psDroid->asBits[COMP_BODY].nStat)->buildPower +
      (asBrainStats + psDroid->asBits[COMP_BRAIN].nStat)->buildPower +
      //(asPropulsionStats + psDroid->asBits[COMP_PROPULSION])->buildPower +
      (asSensorStats + psDroid->asBits[COMP_SENSOR].nStat)->buildPower +
      (asECMStats + psDroid->asBits[COMP_ECM].nStat)->buildPower +
      (asRepairStats + psDroid->asBits[COMP_REPAIRUNIT].nStat)->buildPower +
      (asConstructStats + psDroid->asBits[COMP_CONSTRUCT].nStat)->buildPower;

      /* propulsion power points are a percentage of the bodys' power points */
      power += (((asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat)->buildPower *
            (asBodyStats + psDroid->asBits[COMP_BODY].nStat)->buildPower) / 100);

      //add weapon power
      for(i=0; i<psDroid->numWeaps; i++)
      {
            if (psDroid->asWeaps[i].nStat > 0)
            {
                  //power += (asWeaponStats + psDroid->asWeaps[i].nStat)->buildPower;
                  power += (asWeaponStats + psDroid->asWeaps[i].nStat)->buildPower;
            }
      }

      return power;
}

UDWORD calcDroidPoints(DROID *psDroid)
{
      int points, i;

      points  = getBodyStats(psDroid)->buildPoints;
      points += getBrainStats(psDroid)->buildPoints;
      points += getPropulsionStats(psDroid)->buildPoints;
      points += getSensorStats(psDroid)->buildPoints;
      points += getECMStats(psDroid)->buildPoints;
      points += getRepairStats(psDroid)->buildPoints;
      points += getConstructStats(psDroid)->buildPoints;

      for (i = 0; i < psDroid->numWeaps; i++)
      {
            points += (asWeaponStats + psDroid->asWeaps[i].nStat)->buildPoints;
      }

      return points;
}

//Builds an instance of a Droid - the x/y passed in are in world coords.
DROID* buildDroid(DROID_TEMPLATE *pTemplate, UDWORD x, UDWORD y, UDWORD player,
                          BOOL onMission)
{
      DROID             *psDroid;
      DROID_GROUP       *psGrp;
      UDWORD                  inc;
      UDWORD                  numKills;
      SDWORD                  i, experienceLoc;
      HIT_SIDE          impact_side;

      // Don't use this assertion in single player, since droids can finish building while on an away mission
      ASSERT(!bMultiPlayer || worldOnMap(x,y), "the build locations are not on the map");

      //allocate memory
      psDroid = createDroid(player);
      if (psDroid == NULL)
      {
            debug(LOG_NEVER, "unit build: unable to create\n");
            ASSERT(!"out of memory", "Cannot get the memory for the unit");
            return NULL;
      }

      //fill in other details

      droidSetName(psDroid, pTemplate->aName);

      // Set the droids type
      psDroid->droidType = droidTemplateType(pTemplate);

      psDroid->pos.x = (UWORD)x;
      psDroid->pos.y = (UWORD)y;

      //don't worry if not on homebase cos not being drawn yet
      if (!onMission)
      {
            //set droid height
            psDroid->pos.z = map_Height(psDroid->pos.x, psDroid->pos.y);
      }

      psDroid->cluster = 0;
      psDroid->psGroup = NULL;
      psDroid->psGrpNext = NULL;
      if ( (psDroid->droidType == DROID_TRANSPORTER) ||
             (psDroid->droidType == DROID_COMMAND) )
      {
            if (!grpCreate(&psGrp))
            {
                  debug(LOG_NEVER, "unit build: unable to create group\n");
                  ASSERT(!"unable to create group", "Can't create unit because can't create group");
                  free(psDroid);
                  return NULL;
            }
            grpJoin(psGrp, psDroid);
      }

      psDroid->order = DORDER_NONE;
      psDroid->orderX = 0;
      psDroid->orderY = 0;
      psDroid->orderX2 = 0;
      psDroid->orderY2 = 0;
      psDroid->secondaryOrder = DSS_ARANGE_DEFAULT | DSS_REPLEV_NEVER | DSS_ALEV_ALWAYS | DSS_HALT_GUARD;
      psDroid->action = DACTION_NONE;
      psDroid->actionX = 0;
      psDroid->actionY = 0;
      psDroid->psTarStats = NULL;
      psDroid->psTarget = NULL;

      for(i = 0;i < DROID_MAXWEAPS;i++)
      {
            psDroid->psActionTarget[i] = NULL;
            psDroid->asWeaps[i].lastFired = 0;
            psDroid->asWeaps[i].nStat = 0;
            psDroid->asWeaps[i].ammo = 0;
            psDroid->asWeaps[i].recoilValue = 0;
            psDroid->asWeaps[i].hitPoints = 0;
      }

      psDroid->listSize = 0;
      memset(psDroid->asOrderList, 0, sizeof(ORDER_LIST)*ORDER_LIST_MAX);

      psDroid->iAudioID = NO_SOUND;
      psDroid->lastSync = 0;

      psDroid->psCurAnim = NULL;
      psDroid->group = UBYTE_MAX;
      psDroid->psBaseStruct = NULL;

      // find the highest stored experience
      if ((psDroid->droidType != DROID_CONSTRUCT) &&
        (psDroid->droidType != DROID_CYBORG_CONSTRUCT) &&
            (psDroid->droidType != DROID_REPAIR) &&
        (psDroid->droidType != DROID_CYBORG_REPAIR) &&
            (psDroid->droidType != DROID_TRANSPORTER))
      {
            numKills = 0;
            experienceLoc = 0;
            for(i=0; i<MAX_RECYCLED_DROIDS; i++)
            {
                  if (aDroidExperience[player][i] > numKills)
                  {
                        numKills = aDroidExperience[player][i];
                        experienceLoc = i;
                  }
            }
            aDroidExperience[player][experienceLoc] = 0;
            psDroid->experience = (UWORD)numKills;
      }
      else
      {
            psDroid->experience = 0;
      }

      droidSetBits(pTemplate,psDroid);

      //calculate the droids total weight
      psDroid->weight = calcDroidWeight(pTemplate);

      // Initialise the movement stuff
      psDroid->baseSpeed = calcDroidBaseSpeed(pTemplate, psDroid->weight, (UBYTE)player);

      initDroidMovement(psDroid);

      psDroid->direction = 0;
      psDroid->pitch =  0;
      psDroid->roll = 0;
      //Watermelon:initialize all weapon turrent rotation pitch info
      for(i = 0;i < DROID_MAXWEAPS; i++)
      {
            psDroid->turretRotation[i] = 0;
            psDroid->turretPitch[i] = 0;
      }
      psDroid->selected = false;
      psDroid->lastEmission = 0;
            // ffs AM
      psDroid->bTargetted = false;
      psDroid->timeLastHit = UDWORD_MAX;
      psDroid->lastHitWeapon = UDWORD_MAX;      // no such weapon

      // it was never drawn before
      psDroid->sDisplay.frameNumber = 0;

      //allocate 'easy-access' data!
      psDroid->sensorRange = sensorRange((asSensorStats + pTemplate->asParts
            [COMP_SENSOR]), (UBYTE)player);
      psDroid->sensorPower = sensorPower((asSensorStats + pTemplate->asParts
            [COMP_SENSOR]), (UBYTE)player);
      psDroid->ECMMod = ecmPower((asECMStats + pTemplate->asParts[COMP_ECM]),
            (UBYTE) player);
      psDroid->body = calcTemplateBody(pTemplate, (UBYTE)player);
      psDroid->originalBody = psDroid->body;

      if (cyborgDroid(psDroid))
      {
            for (inc = 0; inc < NUM_WEAPON_CLASS; inc++)
            {
                  for (impact_side = 0;impact_side < NUM_HIT_SIDES;impact_side=impact_side+1)
                  {
                        psDroid->armour[impact_side][inc] = bodyArmour(asBodyStats + pTemplate->
                        asParts[COMP_BODY], (UBYTE)player, CYBORG_BODY_UPGRADE, (WEAPON_CLASS)inc, impact_side);
                  }
            }
      }
      else
      {
            for (inc = 0; inc < NUM_WEAPON_CLASS; inc++)
            {
                  for (impact_side = 0;impact_side < NUM_HIT_SIDES;impact_side=impact_side+1)
                  {
                        psDroid->armour[impact_side][inc] = bodyArmour(asBodyStats + pTemplate->
                              asParts[COMP_BODY], (UBYTE)player, DROID_BODY_UPGRADE, (WEAPON_CLASS)inc, impact_side);
                  }
            }
      }

      //init the resistance to indicate no EW performed on this droid
      psDroid->resistance = ACTION_START_TIME;

      memset(psDroid->visible, 0, sizeof(psDroid->visible));
      psDroid->visible[psDroid->player] = UBYTE_MAX;
      psDroid->died = 0;
      psDroid->inFire = false;
      psDroid->burnStart = 0;
      psDroid->burnDamage = 0;
      psDroid->sDisplay.screenX = OFF_SCREEN;
      psDroid->sDisplay.screenY = OFF_SCREEN;
      psDroid->sDisplay.screenR = 0;

      /* Set droid's initial illumination */
      psDroid->illumination = UBYTE_MAX;
      psDroid->sDisplay.imd = BODY_IMD(psDroid, psDroid->player);

      //don't worry if not on homebase cos not being drawn yet
      if (!onMission)
      {
            /* People always stand upright */
            if(psDroid->droidType != DROID_PERSON)
            {
                  updateDroidOrientation(psDroid);
            }
            visTilesUpdate((BASE_OBJECT *)psDroid);
            gridAddObject((BASE_OBJECT *)psDroid);
            clustNewDroid(psDroid);
      }

      // ajl. droid will be created, so inform others
      if(bMultiPlayer)
      {
            if (SendDroid(pTemplate,  x,  y,  (UBYTE)player, psDroid->id) == false)
            {
                  return NULL;
            }
      }

      /* transporter-specific stuff */
      if (psDroid->droidType == DROID_TRANSPORTER)
      {
            //add transporter launch button if selected player and not a reinforcable situation
            if ( player == selectedPlayer && !missionCanReEnforce())
            {
                  (void)intAddTransporterLaunch(psDroid);
            }

            //set droid height to be above the terrain
            psDroid->pos.z += TRANSPORTER_HOVER_HEIGHT;

            /* reset halt secondary order from guard to hold */
            secondarySetState( psDroid, DSO_HALTTYPE, DSS_HALT_HOLD );
      }

      if(player == selectedPlayer)
      {
            scoreUpdateVar(WD_UNITS_BUILT);
      }

      return psDroid;
}

//initialises the droid movement model
void initDroidMovement(DROID *psDroid)
{
      memset(&psDroid->sMove, 0, sizeof(MOVE_CONTROL));

      psDroid->sMove.fx = psDroid->pos.x;
      psDroid->sMove.fy = psDroid->pos.y;
      psDroid->sMove.fz = psDroid->pos.z;
      psDroid->sMove.speed = 0.0f;
      psDroid->sMove.moveDir = 0.0f;
}

// Set the asBits in a DROID structure given it's template.
void droidSetBits(DROID_TEMPLATE *pTemplate,DROID *psDroid)
{
      UDWORD                                    inc;

      psDroid->droidType = droidTemplateType(pTemplate);

      psDroid->direction = 0;
      psDroid->pitch =  0;
      psDroid->roll = 0;
      psDroid->numWeaps = pTemplate->numWeaps;
      for (inc = 0;inc < psDroid->numWeaps;inc++)
      {
            psDroid->turretRotation[inc] = 0;
            psDroid->turretPitch[inc] = 0;
      }

      psDroid->body = calcTemplateBody(pTemplate, psDroid->player);
      psDroid->originalBody = psDroid->body;

      //create the droids weapons
      if (pTemplate->numWeaps > 0)
      {

            for (inc=0; inc < pTemplate->numWeaps; inc++)
            {
                  psDroid->asWeaps[inc].lastFired=0;
                  psDroid->asWeaps[inc].nStat = pTemplate->asWeaps[inc];
                  psDroid->asWeaps[inc].hitPoints = (asWeaponStats + psDroid->
                        asWeaps[inc].nStat)->hitPoints;
                  psDroid->asWeaps[inc].recoilValue = 0;
                  psDroid->asWeaps[inc].ammo = (asWeaponStats + psDroid->
                        asWeaps[inc].nStat)->numRounds;
            }
      }
      else
      {
            // no weapon (could be a construction droid for example)
            // this is also used to check if a droid has a weapon, so zero it
            psDroid->asWeaps[0].nStat = 0;
      }
      //allocate the components hit points
      psDroid->asBits[COMP_BODY].nStat = (UBYTE)pTemplate->asParts[COMP_BODY];

      // ajl - changed this to init brains for all droids (crashed)
      psDroid->asBits[COMP_BRAIN].nStat = 0;

      // This is done by the Command droid stuff - John.
      // Not any more - John.
      psDroid->asBits[COMP_BRAIN].nStat = pTemplate->asParts[COMP_BRAIN];
      psDroid->asBits[COMP_PROPULSION].nStat = pTemplate->asParts[COMP_PROPULSION];
      psDroid->asBits[COMP_SENSOR].nStat = pTemplate->asParts[COMP_SENSOR];
      psDroid->asBits[COMP_ECM].nStat = pTemplate->asParts[COMP_ECM];
      psDroid->asBits[COMP_REPAIRUNIT].nStat = pTemplate->asParts[COMP_REPAIRUNIT];
      psDroid->asBits[COMP_CONSTRUCT].nStat = pTemplate->asParts[COMP_CONSTRUCT];
}


// Sets the parts array in a template given a droid.
static void templateSetParts(const DROID *psDroid, DROID_TEMPLATE *psTemplate)
{
      UDWORD inc;
      psTemplate->numWeaps = 0;

      psTemplate->droidType = psDroid->droidType;

    //Watermelon:can only have DROID_MAXWEAPS weapon now
      for (inc = 0; inc < DROID_MAXWEAPS; inc++)
      {
            //Watermelon:this should fix the NULL weapon stats for empty weaponslots
            psTemplate->asWeaps[inc] = 0;
            if (psDroid->asWeaps[inc].nStat > 0)
            {
                  psTemplate->numWeaps += 1;
                  psTemplate->asWeaps[inc] = psDroid->asWeaps[inc].nStat;
            }
      }

      psTemplate->asParts[COMP_BODY] = psDroid->asBits[COMP_BODY].nStat;

      psTemplate->asParts[COMP_BRAIN] = psDroid->asBits[COMP_BRAIN].nStat;

      psTemplate->asParts[COMP_PROPULSION] = psDroid->asBits[COMP_PROPULSION].nStat;

      psTemplate->asParts[COMP_SENSOR] = psDroid->asBits[COMP_SENSOR].nStat;

      psTemplate->asParts[COMP_ECM] = psDroid->asBits[COMP_ECM].nStat;

      psTemplate->asParts[COMP_REPAIRUNIT] = psDroid->asBits[COMP_REPAIRUNIT].nStat;

      psTemplate->asParts[COMP_CONSTRUCT] = psDroid->asBits[COMP_CONSTRUCT].nStat;
}


/*
fills the list with Templates that can be manufactured
in the Factory - based on size. There is a limit on how many can be manufactured
at any one time. Pass back the number available.
*/
UDWORD fillTemplateList(DROID_TEMPLATE **ppList, STRUCTURE *psFactory, UDWORD limit)
{
      DROID_TEMPLATE    *psCurr;
      UDWORD                  count = 0;
      UDWORD                  iCapacity = psFactory->pFunctionality->factory.capacity;

      /* Add the templates to the list*/
      for (psCurr = apsDroidTemplates[psFactory->player]; psCurr != NULL;
            psCurr = psCurr->psNext)
      {
            //must add Command Droid if currently in production
            if (!getProductionQuantity(psFactory, psCurr))
            {
                  //can only have (MAX_CMDDROIDS) in the world at any one time
                  if (psCurr->droidType == DROID_COMMAND)
                  {
                        if (checkProductionForCommand(psFactory->player) +
                              checkCommandExist(psFactory->player) >= (MAX_CMDDROIDS))
                        {
                              continue;
                        }
                  }
            }

            if (!validTemplateForFactory(psCurr, psFactory))
            {
                  continue;
            }

            //check the factory can cope with this sized body
            if (!((asBodyStats + psCurr->asParts[COMP_BODY])->size > iCapacity) )
            {
            //cyborg templates are available when the body has been research
            //-same for Transporter in multiPlayer
                  if ( psCurr->droidType == DROID_CYBORG ||
                         psCurr->droidType == DROID_CYBORG_SUPER ||
                 psCurr->droidType == DROID_CYBORG_CONSTRUCT ||
                 psCurr->droidType == DROID_CYBORG_REPAIR ||
                 psCurr->droidType == DROID_TRANSPORTER)
                  {
                        if ( apCompLists[psFactory->player][COMP_BODY]
                               [psCurr->asParts[COMP_BODY]] != AVAILABLE )
                        {
                              //ignore if not research yet
                              continue;
                        }
                  }
                  *ppList++ = psCurr;
                  count++;
                  //once reached the limit, stop adding any more to the list
                  if (count == limit)
                  {
                        return count;
                  }
            }
      }
      return count;
}

/* Make all the droids for a certain player a member of a specific group */
void assignDroidsToGroup(UDWORD     playerNumber, UDWORD groupNumber)
{
DROID *psDroid;
BOOL  bAtLeastOne = false;
FLAG_POSITION     *psFlagPos;

      if(groupNumber<UBYTE_MAX)
      {
            /* Run through all the droids */
            for(psDroid = apsDroidLists[playerNumber]; psDroid!=NULL; psDroid = psDroid->psNext)
            {
                  /* Clear out the old ones */
                  if(psDroid->group == groupNumber)
                  {
                        psDroid->group = UBYTE_MAX;
                  }

                  /* Only assign the currently selected ones */
                  if(psDroid->selected)
                  {
                        /* Set them to the right group - they can only be a member of one group */
                        psDroid->group = (UBYTE)groupNumber;
                        bAtLeastOne = true;
                  }
            }
      }
      if(bAtLeastOne)
      {
            setSelectedGroup(groupNumber);
            //clear the Deliv Point if one
            for (psFlagPos = apsFlagPosLists[selectedPlayer]; psFlagPos;
            psFlagPos = psFlagPos->psNext)
            {
                  psFlagPos->selected = false;
            }
            groupConsoleInformOfCreation(groupNumber);
            secondarySetAverageGroupState(selectedPlayer, groupNumber);
      }
}


BOOL activateGroupAndMove(UDWORD playerNumber, UDWORD groupNumber)
{
      DROID *psDroid,*psCentreDroid = NULL;
      BOOL selected = false;
      FLAG_POSITION     *psFlagPos;

      if (groupNumber < UBYTE_MAX)
      {
            for(psDroid = apsDroidLists[playerNumber]; psDroid!=NULL; psDroid = psDroid->psNext)
            {
                  /* Wipe out the ones in the wrong group */
                  if (psDroid->selected && psDroid->group != groupNumber)
                  {
                        DeSelectDroid(psDroid);
                  }
                  /* Get the right ones */
                  if(psDroid->group == groupNumber)
                  {
                        SelectDroid(psDroid);
                        psCentreDroid = psDroid;
                  }
            }

            /* There was at least one in the group */
            if (psCentreDroid)
            {
                  //clear the Deliv Point if one
                  for (psFlagPos = apsFlagPosLists[selectedPlayer]; psFlagPos;
                  psFlagPos = psFlagPos->psNext)
                  {
                        psFlagPos->selected = false;
                  }

                  selected = true;
                  if (!driveModeActive())
                  {
                        if (getWarCamStatus())
                        {
                              camToggleStatus();                   // messy - fix this
                              processWarCam(); //odd, but necessary
                              camToggleStatus();                        // messy - FIXME
                        }
                        else
                        {
                              /* Centre display on him if warcam isn't active */
                              setViewPos(map_coord(psCentreDroid->pos.x), map_coord(psCentreDroid->pos.y), true);
                        }
                  }
            }
      }

      if(selected)
      {
            setSelectedGroup(groupNumber);
            groupConsoleInformOfCentering(groupNumber);
      }
      else
      {
            setSelectedGroup(UBYTE_MAX);
      }

      return selected;
}

BOOL activateGroup(UDWORD playerNumber, UDWORD groupNumber)
{
      DROID *psDroid;
      BOOL selected = false;
      FLAG_POSITION     *psFlagPos;

      if (groupNumber < UBYTE_MAX)
      {
            for (psDroid = apsDroidLists[playerNumber]; psDroid; psDroid = psDroid->psNext)
            {
                  /* Wipe out the ones in the wrong group */
                  if (psDroid->selected && psDroid->group != groupNumber)
                  {
                        DeSelectDroid(psDroid);
                  }
                  /* Get the right ones */
                  if (psDroid->group == groupNumber)
                  {
                        SelectDroid(psDroid);
                        selected = true;
                  }
            }
      }

      if (selected)
      {
            setSelectedGroup(groupNumber);
            //clear the Deliv Point if one
            for (psFlagPos = apsFlagPosLists[selectedPlayer]; psFlagPos;
            psFlagPos = psFlagPos->psNext)
            {
                  psFlagPos->selected = false;
            }
            groupConsoleInformOfSelection(groupNumber);
      }
      else
      {
            setSelectedGroup(UBYTE_MAX);
      }
      return selected;
}

void  groupConsoleInformOfSelection( UDWORD groupNumber )
{
      // ffs am
      char groupInfo[255];
      unsigned int num_selected = selNumSelected(selectedPlayer);

      ssprintf(groupInfo, ngettext("Group %u selected - %u Unit", "Group %u selected - %u Units", num_selected), groupNumber, num_selected);
      addConsoleMessage(groupInfo,RIGHT_JUSTIFY,SYSTEM_MESSAGE);

}

void  groupConsoleInformOfCreation( UDWORD groupNumber )
{
      char groupInfo[255];
      
      if (!getWarCamStatus())
      {
            unsigned int num_selected = selNumSelected(selectedPlayer);

            ssprintf(groupInfo, ngettext("%u unit assigned to Group %u", "%u units assigned to Group %u", num_selected), num_selected, groupNumber);
            addConsoleMessage(groupInfo,RIGHT_JUSTIFY,SYSTEM_MESSAGE);
      }

}

void  groupConsoleInformOfCentering( UDWORD groupNumber )
{
      char  groupInfo[255];
      unsigned int num_selected = selNumSelected(selectedPlayer);

      if(!getWarCamStatus())
      {
            ssprintf(groupInfo, ngettext("Centered on Group %u - %u Unit", "Centered on Group %u - %u Units", num_selected), groupNumber, num_selected);
      }
      else
      {
            ssprintf(groupInfo, ngettext("Aligning with Group %u - %u Unit", "Aligning with Group %u - %u Units", num_selected), groupNumber, num_selected);
      }
      
      addConsoleMessage(groupInfo,RIGHT_JUSTIFY,SYSTEM_MESSAGE);
}



UDWORD      getSelectedGroup( void )
{
      return(selectedGroup);
}

void  setSelectedGroup(UDWORD groupNumber)
{
      selectedGroup = groupNumber;
      selectedCommander = UBYTE_MAX;
}

UDWORD      getSelectedCommander( void )
{
      return(selectedCommander);
}

void  setSelectedCommander(UDWORD commander)
{
      selectedGroup = UBYTE_MAX;
      selectedCommander = commander;
}

/**
 * calculate muzzle tip location in 3d world
 */
03466 BOOL calcDroidMuzzleLocation(DROID *psDroid, Vector3i *muzzle, int weapon_slot)
{
      Vector3i barrel;
      iIMDShape *psShape, *psWeapon, *psWeaponMount;

      CHECK_DROID(psDroid);

      psShape = BODY_IMD(psDroid,psDroid->player);

      psWeapon      = (asWeaponStats[psDroid->asWeaps[weapon_slot].nStat]).pIMD;
      psWeaponMount = (asWeaponStats[psDroid->asWeaps[weapon_slot].nStat]).pMountGraphic;
      if(psShape && psShape->nconnectors)
      {
            pie_MatBegin();

            pie_TRANSLATE(psDroid->pos.x,-(SDWORD)psDroid->pos.z,psDroid->pos.y);
            //matrix = the center of droid
            pie_MatRotY( DEG( (SDWORD)psDroid->direction ) );
            pie_MatRotX( DEG( psDroid->pitch ) );
            pie_MatRotZ( DEG( -(SDWORD)psDroid->roll ) );
            pie_TRANSLATE( psShape->connectors[weapon_slot].x, -psShape->connectors[weapon_slot].z,
                                -psShape->connectors[weapon_slot].y);//note y and z flipped

            //matrix = the gun and turret mount on the body
            pie_MatRotY(DEG((SDWORD)psDroid->turretRotation[weapon_slot]));//+ve anticlockwise
            pie_MatRotX(DEG(psDroid->turretPitch[weapon_slot]));//+ve up
            pie_MatRotZ(DEG(0));
            //matrix = the muzzle mount on turret
            if( psWeapon && psWeapon->nconnectors )
            {
                  barrel.x = psWeapon->connectors->x;
                  barrel.y = -psWeapon->connectors->y;
                  barrel.z = -psWeapon->connectors->z;
            }
            else
            {
                  barrel.x = 0;
                  barrel.y = 0;
                  barrel.z = 0;
            }

            pie_RotateTranslate3iv(&barrel, muzzle);
            muzzle->z = -muzzle->z;

            pie_MatEnd();
      }
      else
      {
            muzzle->x = psDroid->pos.x;
            muzzle->y = psDroid->pos.y;
            muzzle->z = psDroid->pos.z+32;
      }

      CHECK_DROID(psDroid);
  return true;
}


/*!
 * Gets a template from its name
 * relies on the name being unique (or it will return the first one it finds!)
 * \param pName Template name
 * \param player Player number
 * \pre pName has to be the unique, untranslated name!
 * \pre player < MAX_PLAYERS
 */
03532 DROID_TEMPLATE * getTemplateFromUniqueName(const char *pName, unsigned int player)
{
      DROID_TEMPLATE *psCurr;

      for (psCurr = apsDroidTemplates[player]; psCurr != NULL; psCurr = psCurr->psNext)
      {
            if (strcmp(psCurr->pName, pName) == 0)
            {
                  return psCurr;
            }
      }

      return NULL;
}


/*!
 * Gets a template from its name
 * relies on the name being unique (or it will return the first one it finds!)
 * \param pName Template name
 * \pre pName has to be the unique, untranslated name!
 * \post pName will be translated via getDroidResourceName()!
 * \warning IF YOU USE THIS FUNCTION - NOTE THAT selectedPlayer's TEMPLATES ARE NOT USED!!!!
 */
03556 DROID_TEMPLATE * getTemplateFromTranslatedNameNoPlayer(char *pName)
{
      unsigned int player;

      /*all droid and template names are now stored as the translated
      name regardless of RESOURCE_NAMES and STORE_RESOURCE_ID! - AB 26/06/98*/
      getDroidResourceName(pName);

      for (player = 0; player < MAX_PLAYERS; player++)
      {
            DROID_TEMPLATE *psCurr;
            // OK so we want selectedPlayer's CYBORG templates since they cannot be edited
            // and we don't want to duplicate them for the sake of it! (ha!)
            // don't use selectedPlayer's templates if not multiplayer
            // this was written for use in the scripts and we don't want the scripts to use
            // selectedPlayer's templates because we cannot guarentee they will exist!
            for (psCurr = apsDroidTemplates[player]; psCurr != NULL; psCurr = psCurr->psNext)
            {
                  if (strcmp(psCurr->aName, pName) == 0)
                  {
                        //if template is selectedPlayers' it must be a CYBORG or we ignore it
                        if (!bMultiPlayer
                         && player == selectedPlayer
                         && !(psCurr->droidType == DROID_CYBORG
                           || psCurr->droidType == DROID_CYBORG_SUPER
                           || psCurr->droidType == DROID_CYBORG_CONSTRUCT
                           || psCurr->droidType == DROID_CYBORG_REPAIR))
                        {
                              //ignore
                              continue;
                        }
                        return psCurr;
                  }
            }
      }
      return NULL;
}

/*getTemplatefFromSinglePlayerID gets template for unique ID  searching one players list */
DROID_TEMPLATE* getTemplateFromSinglePlayerID(UDWORD multiPlayerID, UDWORD player)
{
      DROID_TEMPLATE    *pDroidDesign;

      for(pDroidDesign = apsDroidTemplates[player]; pDroidDesign != NULL; pDroidDesign = pDroidDesign->psNext)
      {
            if (pDroidDesign->multiPlayerID == multiPlayerID)
            {
                  return pDroidDesign;
            }
      }
      return NULL;
}

/*getTemplatefFromMultiPlayerID gets template for unique ID  searching all lists */
DROID_TEMPLATE* getTemplateFromMultiPlayerID(UDWORD multiPlayerID)
{
      UDWORD                  player;
      DROID_TEMPLATE    *pDroidDesign;

      for (player=0; player < MAX_PLAYERS; player++)
      {
            pDroidDesign = getTemplateFromSinglePlayerID(multiPlayerID, player);
            if (pDroidDesign != NULL)
            {
                  return pDroidDesign;
            }
      }
      return NULL;
}

// finds a droid for the player and sets it to be the current selected droid
BOOL selectDroidByID(UDWORD id, UDWORD player)
{
      DROID *psCurr;

      //look through the list of droids for the player and find the matching id
      for (psCurr = apsDroidLists[player]; psCurr; psCurr = psCurr->psNext)
      {
            if (psCurr->id == id)
            {
                  break;
            }
      }
      
      if (psCurr)
      {
            clearSelection();
            SelectDroid(psCurr);
            return true;
      }
      
      return false;
}

struct rankMap
{
      unsigned int kills;          // required minimum amount of kills to reach this rank
      unsigned int commanderKills; // required minimum amount of kills for a commander (or sensor) to reach this rank
      const char*  name;           // name of this rank
};

static const struct rankMap arrRank[] =
{
      {0,   0,    N_("Rookie")},
      {4,   16,   NP_("rank", "Green")},
      {8,   32,   N_("Trained")},
      {16,  64,   N_("Regular")},
      {32,  128,  N_("Professional")},
      {64,  256,  N_("Veteran")},
      {128, 512,  N_("Elite")},
      {256, 1024, N_("Special")},
      {512, 2048, N_("Hero")}
};

UDWORD      getDroidLevel(DROID *psDroid)
{
      static const unsigned int lastRank = sizeof(arrRank) / sizeof(struct rankMap);
      bool isCommander = (psDroid->droidType == DROID_COMMAND ||
                          psDroid->droidType == DROID_SENSOR) ? true : false;
      unsigned int numKills = psDroid->experience;
      unsigned int i;

      // Commanders don't need as much kills for ranks in multiplayer
      if (isCommander && cmdGetDroidMultiExpBoost())
      {
            numKills *= 2;
      }

      // Search through the array of ranks until one is found
      // which requires more kills than the droid has.
      // Then fall back to the previous rank.
      for (i = 1; i != lastRank; ++i)
      {
            unsigned int requiredKills = isCommander ? arrRank[i].commanderKills : arrRank[i].kills;
            if (numKills < requiredKills)
            {
                  return i - 1;
            }
      }

      // If the criteria of the last rank are met, then select the last one
      return lastRank - 1;
}

UDWORD getDroidEffectiveLevel(DROID *psDroid)
{
      UDWORD level = getDroidLevel(psDroid);
      UDWORD cmdLevel = 0;

      // get commander level
      if(hasCommander(psDroid))
      {
            cmdLevel = cmdGetCommanderLevel(psDroid);

            // Commanders boost unit's effectiveness just by being assigned to it
            if(bMultiPlayer)
            {
                  cmdLevel++;
            }
      }

      return MAX(level, cmdLevel);
}


const char *getDroidNameForRank(UDWORD rank)
{
      ASSERT( rank < (sizeof(arrRank) / sizeof(struct rankMap)),
              "getDroidNameForRank: given rank number (%d) out of bounds, we only have %zu ranks\n", rank, (sizeof(arrRank) / sizeof(struct rankMap)) );

      return PE_("rank", arrRank[rank].name);
}

const char *getDroidLevelName(DROID *psDroid)
{
      return(getDroidNameForRank(getDroidLevel(psDroid)));
}

UDWORD      getNumDroidsForLevel(UDWORD   level)
{
      DROID *psDroid;
      UDWORD      count;

      for(psDroid = apsDroidLists[selectedPlayer],count = 0;
            psDroid; psDroid = psDroid->psNext)
      {
            if (getDroidLevel(psDroid) == level)
            {
                  count++;
            }
      }

      return count;
}

// Get the name of a droid from it's DROID structure.
//
char *droidGetName(DROID *psDroid)
{
      return psDroid->aName;
}

//
// Set the name of a droid in it's DROID structure.
//
// - only possible on the PC where you can adjust the names,
//
void droidSetName(DROID *psDroid,const char *pName)
{
      sstrcpy(psDroid->aName, pName);
}



// ////////////////////////////////////////////////////////////////////////////
// returns true when no droid on x,y square.
BOOL noDroid(UDWORD x, UDWORD y)
{
      UDWORD i;
      DROID *pD;
      // check each droid list
      for(i=0;i<MAX_PLAYERS;i++)
      {
            for(pD = apsDroidLists[i]; pD ; pD= pD->psNext)
            {
                  if (map_coord(pD->pos.x) == x)
                  {
                        if (map_coord(pD->pos.y) == y)
                        {
                              return false;
                        }
                  }
            }
      }
      return true;
}

// ////////////////////////////////////////////////////////////////////////////
// returns true when one droid on x,y square.
static BOOL oneDroid(UDWORD x, UDWORD y)
{
      UDWORD i;
      BOOL bFound = false;
      DROID *pD;
      // check each droid list
      for(i=0;i<MAX_PLAYERS;i++)
      {
            for(pD = apsDroidLists[i]; pD ; pD= pD->psNext)
            {
                  if (map_coord(pD->pos.x) == x)
                  {
                        if (map_coord(pD->pos.y) == y)
                        {
                              if (bFound)
                              {
                                    return false;
                              }
                              else
                              {
                                    bFound = true;//first droid on this square so continue
                              }
                        }
                  }
            }
      }
      return true;
}

// ////////////////////////////////////////////////////////////////////////////
// returns true if it's a sensible place to put that droid.
BOOL sensiblePlace(SDWORD x, SDWORD y)
{
      UDWORD count=0;

      // not too near the edges.
      if((x < TOO_NEAR_EDGE) || (x > (SDWORD)(mapWidth - TOO_NEAR_EDGE)))
            return false;
      if((y < TOO_NEAR_EDGE) || (y > (SDWORD)(mapHeight - TOO_NEAR_EDGE)))
            return false;

    //check no features there
      if(TileHasFeature(mapTile(x,y)))
      {
            return false;
      }

      // not on a blocking tile.
      if( fpathBlockingTile(x,y) )
            return false;

      // shouldn't next to more than one blocking tile, to avoid windy paths.
      if( fpathBlockingTile(x-1 ,y-1) )
            count++;
      if( fpathBlockingTile(x,y-1) )
            count++;
      if( fpathBlockingTile(x+1,y-1) )
            count++;
      if( fpathBlockingTile(x-1,y) )
            count++;
      if( fpathBlockingTile(x+1,y) )
            count++;
      if( fpathBlockingTile(x-1,y+1) )
            count++;
      if( fpathBlockingTile(x,y+1) )
            count++;
      if( fpathBlockingTile(x+1,y+1) )
            count++;

      if(count > 1)
            return false;

      return true;
}


// ------------------------------------------------------------------------------------
BOOL  normalPAT(UDWORD x, UDWORD y)
{
      if(sensiblePlace(x,y) && noDroid(x,y))
      {
            return(true);
      }
      else
      {
            return(false);
      }
}
// ------------------------------------------------------------------------------------
// Should stop things being placed in inaccessible areas?
BOOL  zonedPAT(UDWORD x, UDWORD y)
{
      if (sensiblePlace(x,y) && noDroid(x,y) && gwZoneReachable(gwGetZone(x,y)))
      {
            return(true);
      }
      else
      {
            return(false);
      }
}

// ------------------------------------------------------------------------------------
BOOL  pickATileGen(UDWORD *x, UDWORD *y, UBYTE numIterations,
                               BOOL (*function)(UDWORD x, UDWORD y))
{
      SDWORD      i,j;
      SDWORD      startX,endX,startY,endY;
      UDWORD      passes;


      ASSERT( *x<mapWidth,"x coordinate is off-map for pickATileGen" );
      ASSERT( *y<mapHeight,"y coordinate is off-map for pickATileGen" );

      /* Exit if they're fine! */
      if(sensiblePlace(*x,*y) && noDroid(*x,*y))
      {
            return(true);
      }

      /* Initial box dimensions and set iteration count to zero */
      startX = endX = *x;     startY = endY = *y;     passes = 0;

      /* Keep going until we get a tile or we exceed distance */
      while(passes<numIterations)
      {
            /* Process whole box */
            for(i = startX; i <= endX; i++)
            {
                  for(j = startY; j<= endY; j++)
                  {
                        /* Test only perimeter as internal tested previous iteration */
                        if(i==startX || i==endX || j==startY || j==endY)
                        {
                              /* Good enough? */
                              if(function(i,j))
                              {
                                    /* Set exit conditions and get out NOW */
                                    *x = i;     *y = j;
                                    return(true);
                              }
                        }
                  }
            }
            /* Expand the box out in all directions - off map handled by tileAcceptable */
            startX--; startY--;     endX++;     endY++;     passes++;
      }
      /* If we got this far, then we failed - passed in values will be unchanged */
      return(false);

}

//same as orig, but with threat check
BOOL  pickATileGenThreat(UDWORD *x, UDWORD *y, UBYTE numIterations, SDWORD threatRange,
                               SDWORD player, BOOL (*function)(UDWORD x, UDWORD y))
{
SDWORD      i,j;
SDWORD      startX,endX,startY,endY;
UDWORD      passes;


      ASSERT( *x<mapWidth,"x coordinate is off-map for pickATileGen" );
      ASSERT( *y<mapHeight,"y coordinate is off-map for pickATileGen" );

      if(function(*x,*y) && ((threatRange <=0) || (!ThreatInRange(player, threatRange, *x, *y, false))))    //TODO: vtol check really not needed?
      {
            return(true);
      }

      /* Initial box dimensions and set iteration count to zero */
      startX = endX = *x;     startY = endY = *y;     passes = 0;

      /* Keep going until we get a tile or we exceed distance */
      while(passes<numIterations)
      {
            /* Process whole box */
            for(i = startX; i <= endX; i++)
            {
                  for(j = startY; j<= endY; j++)
                  {
                        /* Test only perimeter as internal tested previous iteration */
                        if(i==startX || i==endX || j==startY || j==endY)
                        {
                              /* Good enough? */
                              if(function(i,j) && ((threatRange <=0) || (!ThreatInRange(player, threatRange, world_coord(i), world_coord(j), false))))            //TODO: vtols check really not needed?
                              {
                                    /* Set exit conditions and get out NOW */
                                    *x = i;     *y = j;
                                    return true;
                              }
                        }
                  }
            }
            /* Expand the box out in all directions - off map handled by tileAcceptable */
            startX--; startY--;     endX++;     endY++;     passes++;
      }
      /* If we got this far, then we failed - passed in values will be unchanged */
      return false;

}

// ------------------------------------------------------------------------------------
/* Improved pickATile - Replaces truly scary existing one. */
/* AM 22 - 10 - 98 */
BOOL  pickATile(UDWORD *x, UDWORD *y, UBYTE numIterations)
{
      SDWORD      i,j;
      SDWORD      startX,endX,startY,endY;
      UDWORD      passes;

      ASSERT( *x<mapWidth,"x coordinate is off-map for pickATile" );
      ASSERT( *y<mapHeight,"y coordinate is off-map for pickATile" );

      /* Exit if they're fine! */
      if(sensiblePlace(*x,*y) && noDroid(*x,*y))
      {
            return(true);
      }

      /* Initial box dimensions and set iteration count to zero */
      startX = endX = *x;     startY = endY = *y;     passes = 0;

      /* Keep going until we get a tile or we exceed distance */
      while(passes<numIterations)
      {
            /* Process whole box */
            for(i = startX; i <= endX; i++)
            {
                  for(j = startY; j<= endY; j++)
                  {
                        /* Test only perimeter as internal tested previous iteration */
                        if(i==startX || i==endX || j==startY || j==endY)
                        {
                              /* Good enough? */
                              if(sensiblePlace(i,j) && noDroid(i,j))
                              {
                                    /* Set exit conditions and get out NOW */
                                    *x = i;     *y = j;
                                    return(true);
                              }
                        }
                  }
            }
            /* Expand the box out in all directions - off map handled by tileAcceptable */
            startX--; startY--;     endX++;     endY++;     passes++;
      }
      /* If we got this far, then we failed - passed in values will be unchanged */
      return(false);
}

// pickHalfATile just like improved pickATile but with Double Density Droid Placement
PICKTILE pickHalfATile(UDWORD *x, UDWORD *y, UBYTE numIterations)
{
      SDWORD      i,j;
      SDWORD      startX,endX,startY,endY;
      UDWORD      passes;

      /*
            Why was this written - I wrote pickATileGen to take a function
            pointer for what qualified as a valid tile - could use that.
            I'm not going to change it in case I'm missing the point */
      if (pickATileGen(x, y, numIterations,zonedPAT))
      {
            return FREE_TILE;
      }

      /* Exit if they're fine! */
      if (sensiblePlace(*x, *y) && oneDroid(*x, *y))
      {
            return HALF_FREE_TILE;
      }

      /* Initial box dimensions and set iteration count to zero */
      startX = endX = *x;     startY = endY = *y;     passes = 0;



      /* Keep going until we get a tile or we exceed distance */
      while(passes<numIterations)
      {
            /* Process whole box */
            for(i = startX; i <= endX; i++)
            {
                  for(j = startY; j<= endY; j++)
                  {
                        /* Test only perimeter as internal tested previous iteration */
                        if(i==startX || i==endX || j==startY || j==endY)
                        {
                              /* Good enough? */
                              if(sensiblePlace(i,j) && oneDroid(i,j))
                              {
                                    /* Set exit conditions and get out NOW */
                                    *x = i;     *y = j;
                                    return HALF_FREE_TILE;
                              }
                        }
                  }
            }
            /* Expand the box out in all directions - off map handled by tileAcceptable */
            startX--; startY--;     endX++;     endY++;     passes++;
      }
      /* If we got this far, then we failed - passed in values will be unchanged */
      return NO_FREE_TILE;
}

/* Looks through the players list of droids to see if any of them are
building the specified structure - returns true if finds one*/
BOOL checkDroidsBuilding(STRUCTURE *psStructure)
{
      DROID                   *psDroid;

      for (psDroid = apsDroidLists[psStructure->player]; psDroid != NULL; psDroid =
            psDroid->psNext)
      {
            //check DORDER_BUILD, HELP_BUILD is handled the same
            BASE_OBJECT * const psStruct = orderStateObj(psDroid, DORDER_BUILD);
            if ((STRUCTURE*)psStruct == psStructure)
            {
                  return true;
            }
      }
      return false;
}

/* Looks through the players list of droids to see if any of them are
demolishing the specified structure - returns true if finds one*/
BOOL checkDroidsDemolishing(STRUCTURE *psStructure)
{
      DROID                   *psDroid;

      for (psDroid = apsDroidLists[psStructure->player]; psDroid != NULL; psDroid =
            psDroid->psNext)
      {
            //check DORDER_DEMOLISH
            BASE_OBJECT * const psStruct = orderStateObj(psDroid, DORDER_DEMOLISH);
            if ((STRUCTURE*)psStruct == psStructure)
            {
                  return true;
            }
      }
      return false;
}


/* checks the structure for type and capacity and **NOT orders the droid*** to build
a module if it can - returns true if order is set */
BOOL buildModule(STRUCTURE *psStruct)
{
      BOOL  order = false;
      UDWORD      i = 0;

      ASSERT(psStruct != NULL, "buildModule: Invalid structure pointer");

      switch (psStruct->pStructureType->type)
      {
            case REF_POWER_GEN:
                  //check room for one more!
                  ASSERT(psStruct->pFunctionality, "buildModule: Functionality missing for power!");
                  if (psStruct->pFunctionality->powerGenerator.capacity < NUM_POWER_MODULES)
                  {
                        i = powerModuleStat;
                        order = true;
                  }
                  break;
            case REF_FACTORY:
            case REF_VTOL_FACTORY:
                  //check room for one more!
                  ASSERT(psStruct->pFunctionality, "buildModule: Functionality missing for factory!");
                  if (psStruct->pFunctionality->factory.capacity < NUM_FACTORY_MODULES)
                  {
                        i = factoryModuleStat;
                        order = true;
                  }
                  break;
            case REF_RESEARCH:
                  //check room for one more!
                  ASSERT(psStruct->pFunctionality, "buildModule: Functionality missing for research!");
                  if (psStruct->pFunctionality->researchFacility.capacity < NUM_RESEARCH_MODULES)
                  {
                        i = researchModuleStat;
                        order = true;
                  }
                  break;
            default:
                  //no other structures can have modules attached
                  break;
      }

      if (order)
      {
            // Check availability of Module
            if (!((i < numStructureStats) &&
                  (apStructTypeLists[psStruct->player][i] == AVAILABLE)))
            {
                  order = false;
            }
      }

      return order;
}

/*Deals with building a module - checking if any droid is currently doing this
 - if so, helping to build the current one*/
void setUpBuildModule(DROID *psDroid)
{
      UDWORD            tileX, tileY;
      STRUCTURE   *psStruct;

      tileX = map_coord(psDroid->orderX);
      tileY = map_coord(psDroid->orderY);

      //check not another Truck started
      psStruct = getTileStructure(tileX,tileY);
      if (psStruct)
      {
            if (checkDroidsBuilding(psStruct))
            {
                  //set up the help build scenario
                  psDroid->order = DORDER_HELPBUILD;
                  setDroidTarget(psDroid, (BASE_OBJECT *)psStruct);
                  if (droidStartBuild(psDroid))
                  {
                        psDroid->action = DACTION_BUILD;
                        intBuildStarted(psDroid);
                  }
                  else
                  {
                        psDroid->action = DACTION_NONE;
                  }
            }
            else
            {
                  if(buildModule(psStruct))
                  {
                        //no other droids building so just start it off
                        if (droidStartBuild(psDroid))
                        {
                              psDroid->action = DACTION_BUILD;
                              intBuildStarted(psDroid);
                        }
                        else
                        {
                              psDroid->action = DACTION_NONE;
                        }
                  }
                  else
                  {
                        psDroid->action = DACTION_NONE;
                  }
            }
      }
      else
      {
            //we've got a problem if it didn't find a structure
            psDroid->action = DACTION_NONE;
      }
}

const char *getDroidName(const DROID *psDroid)
{
      DROID_TEMPLATE sTemplate;

      templateSetParts(psDroid, &sTemplate);

      return getTemplateName(&sTemplate);
}



/*return the name to display for the interface - we don't know if this is
a string ID or something the user types in*/
const char* getTemplateName(const DROID_TEMPLATE *psTemplate)
{
      const char *pNameID = psTemplate->aName;
      UDWORD id;

      /*see if the name has a resource associated with it by trying to get
      the ID for the string*/
      if (strresGetIDNum(psStringRes, pNameID, &id))
      {
            //get the string from the id
            const char *pName = strresGetString(psStringRes, id);
            if (pName)
            {
                  return pName;
            }
      }
      //if haven't found a resource, return the name passed in
      return pNameID;
}

/* Just returns true if the droid's present body points aren't as high as the original*/
BOOL  droidIsDamaged(DROID *psDroid)
{
      if(psDroid->body < psDroid->originalBody)
      {
            return(true);
      }
      else
      {
            return(false);
      }
}


BOOL getDroidResourceName(char *pName)
{
      UDWORD id;

      //see if the name has a resource associated with it by trying to get the ID for the string
      if (!strresGetIDNum(psStringRes, pName, &id))
      {
            debug( LOG_ERROR, "Unable to find string resource for %s", pName );
            abort();
            return false;
      }
      //get the string from the id
      strcpy(pName, strresGetString(psStringRes, id));

      return true;
}


/*checks to see if an electronic warfare weapon is attached to the droid*/
BOOL electronicDroid(DROID *psDroid)
{
      DROID *psCurr;

      CHECK_DROID(psDroid);

      //Watermelon:use slot 0 for now
      //if (psDroid->numWeaps && asWeaponStats[psDroid->asWeaps[0].nStat].
    if (psDroid->numWeaps > 0 && asWeaponStats[psDroid->asWeaps[0].nStat].
            weaponSubClass == WSC_ELECTRONIC)
      {
            return true;
      }

      if (psDroid->droidType == DROID_COMMAND && psDroid->psGroup)
      {
            // if a commander has EW units attached it is electronic
            for (psCurr = psDroid->psGroup->psList; psCurr; psCurr = psCurr->psGrpNext)
            {
                  if (electronicDroid(psCurr))
                  {
                        return true;
                  }
            }
      }

      return false;
}

/*checks to see if the droid is currently being repaired by another*/
BOOL droidUnderRepair(DROID *psDroid)
{
      DROID       *psCurr;

      CHECK_DROID(psDroid);

      //droid must be damaged
      if (droidIsDamaged(psDroid))
      {
            //look thru the list of players droids to see if any are repairing this droid
            for (psCurr = apsDroidLists[psDroid->player]; psCurr != NULL; psCurr = psCurr->psNext)
            {
                  //if (psCurr->droidType == DROID_REPAIR && psCurr->action ==
            if ((psCurr->droidType == DROID_REPAIR || psCurr->droidType ==
                DROID_CYBORG_REPAIR) && psCurr->action ==
                        DACTION_DROIDREPAIR && psCurr->psTarget == (BASE_OBJECT *)psDroid)
                  {
                        return true;
                  }
            }
      }
      return false;
}

//count how many Command Droids exist in the world at any one moment
UBYTE checkCommandExist(UBYTE player)
{
      DROID *psDroid;
      UBYTE quantity = 0;

      for (psDroid = apsDroidLists[player]; psDroid != NULL; psDroid = psDroid->psNext)
      {
            if (psDroid->droidType == DROID_COMMAND)
            {
                  quantity++;
            }
      }
      return quantity;
}

//access functions for vtols
BOOL isVtolDroid(const DROID* psDroid)
{
      return asPropulsionStats[psDroid->asBits[COMP_PROPULSION].nStat].propulsionType == LIFT
          && psDroid->droidType != DROID_TRANSPORTER;
}

/*returns true if a VTOL Weapon Droid which has completed all runs*/
BOOL vtolEmpty(DROID *psDroid)
{
      UBYTE i;
      UBYTE numVtolWeaps = 0;
      UBYTE emptyWeaps = 0;
      BOOL  bEmpty = true;

      CHECK_DROID(psDroid);

      if (!isVtolDroid(psDroid))
      {
            return false;
      }
      if (psDroid->droidType != DROID_WEAPON)
      {
            return false;
      }

      if (psDroid->numWeaps > 0)
      {
            for (i = 0;i < psDroid->numWeaps;i++)
            {
                  if (asWeaponStats[psDroid->asWeaps[i].nStat].vtolAttackRuns > 0)
                  {
                        numVtolWeaps += (1 << (1 + i));
                        if (psDroid->sMove.iAttackRuns[i] >= getNumAttackRuns(psDroid, i))
                        {
                              emptyWeaps += (1 << (1 + i));
                        }
                  }
            }
      }

      for (i = 0;i < psDroid->numWeaps;i++)
      {
            if ((numVtolWeaps & (1 << (1 + i))) && !(emptyWeaps & (1 << (1 + i))))
            {
                  bEmpty = false;
                  break;
            }
      }
      return bEmpty;
}

// true if a vtol is waiting to be rearmed by a particular rearm pad
BOOL vtolReadyToRearm(DROID *psDroid, STRUCTURE *psStruct)
{
      BASE_OBJECT *psRearmPad;

      CHECK_DROID(psDroid);

      if (!isVtolDroid(psDroid)
       || psDroid->action != DACTION_WAITFORREARM)
      {
            return false;
      }

      // If a unit has been ordered to rearm make sure it goes to the correct base
      psRearmPad = orderStateObj(psDroid, DORDER_REARM);
      if (psRearmPad
       && (STRUCTURE*)psRearmPad != psStruct
       && !vtolOnRearmPad((STRUCTURE*)psRearmPad, psDroid))
      {
            // target rearm pad is clear - let it go there
            return false;
      }

      if (vtolHappy(psDroid) &&
            vtolOnRearmPad(psStruct, psDroid))
      {
            // there is a vtol on the pad and this vtol is already rearmed
            // don't bother shifting the other vtol off
            return false;
      }

      if ((psDroid->psActionTarget[0] != NULL) &&
            (psDroid->psActionTarget[0]->cluster != psStruct->cluster))
      {
            // vtol is rearming at a different base
            return false;
      }

      return true;
}

// true if a vtol droid currently returning to be rearmed
BOOL vtolRearming(DROID *psDroid)
{
      CHECK_DROID(psDroid);

      if (!isVtolDroid(psDroid))
      {
            return false;
      }
      if (psDroid->droidType != DROID_WEAPON)
      {
            return false;
      }

      if (psDroid->action == DACTION_MOVETOREARM ||
            psDroid->action == DACTION_WAITFORREARM ||
        psDroid->action == DACTION_MOVETOREARMPOINT ||
            psDroid->action == DACTION_WAITDURINGREARM)
      {
            return true;
      }

      return false;
}

// true if a droid is currently attacking
BOOL droidAttacking(DROID *psDroid)
{
      CHECK_DROID(psDroid);

    //what about cyborgs?
    if (!(psDroid->droidType == DROID_WEAPON || psDroid->droidType == DROID_CYBORG ||
        psDroid->droidType == DROID_CYBORG_SUPER))
      {
            return false;
      }

      if (psDroid->action == DACTION_ATTACK ||
            psDroid->action == DACTION_MOVETOATTACK ||
            psDroid->action == DACTION_ROTATETOATTACK ||
            psDroid->action == DACTION_VTOLATTACK ||
            psDroid->action == DACTION_MOVEFIRE)
      {
            return true;
      }

      return false;
}

// see if there are any other vtols attacking the same target
// but still rearming
BOOL allVtolsRearmed(DROID *psDroid)
{
      DROID *psCurr;
      BOOL  stillRearming;

      CHECK_DROID(psDroid);

      // ignore all non vtols
      if (!isVtolDroid(psDroid))
      {
            return true;
      }

      stillRearming = false;
      for (psCurr=apsDroidLists[psDroid->player]; psCurr; psCurr=psCurr->psNext)
      {
            if (vtolRearming(psCurr) &&
                  psCurr->order == psDroid->order &&
                  psCurr->psTarget == psDroid->psTarget)
            {
                  stillRearming = true;
                  break;
            }
      }

      return !stillRearming;
}


/*returns a count of the base number of attack runs for the weapon attached to the droid*/
//Watermelon:adds int weapon_slot
UWORD   getNumAttackRuns(DROID *psDroid, int weapon_slot)
{
    UWORD   numAttackRuns;

    ASSERT(isVtolDroid(psDroid), "not a VTOL Droid");

    /*if weapon attached to the droid is a salvo weapon, then number of shots that
    can be fired = vtolAttackRuns*numRounds */
    if (asWeaponStats[psDroid->asWeaps[weapon_slot].nStat].reloadTime)
    {
        numAttackRuns = (UWORD)(asWeaponStats[psDroid->asWeaps[weapon_slot].nStat].numRounds *
            asWeaponStats[psDroid->asWeaps[weapon_slot].nStat].vtolAttackRuns);
    }
    else
    {
        numAttackRuns = asWeaponStats[psDroid->asWeaps[weapon_slot].nStat].vtolAttackRuns;
    }

    return numAttackRuns;
}

/*Checks a vtol for being fully armed and fully repaired to see if ready to
leave reArm pad */
BOOL vtolHappy(const DROID* psDroid)
{
      unsigned int i;

      CHECK_DROID(psDroid);

      ASSERT(isVtolDroid(psDroid), "not a VTOL droid");

      if (psDroid->body < psDroid->originalBody)
      {
            // VTOLs with less health than their original aren't happy
            return false;
      }

      if (psDroid->droidType != DROID_WEAPON)
      {
            // Not an armed droid, so don't check the (non-existent) weapons
            return true;
      }

      /* NOTE: Previous code (r5410) returned false if a droid had no weapon,
       *       which IMO isn't correct, but might be expected behaviour. I'm
       *       also not sure if weapon droids (see the above droidType check)
       *       can even have zero weapons. -- Giel
       */
      ASSERT(psDroid->numWeaps > 0, "VTOL weapon droid without weapons found!");

      //check full complement of ammo
      for (i = 0; i < psDroid->numWeaps; ++i)
      {
            if (asWeaponStats[psDroid->asWeaps[i].nStat].vtolAttackRuns > 0
             && psDroid->sMove.iAttackRuns[i] != 0)
            {
                  return false;
            }
      }

      return true;
}

/*checks if the droid is a VTOL droid and updates the attack runs as required*/
void updateVtolAttackRun(DROID *psDroid , int weapon_slot)
{
    if (isVtolDroid(psDroid))
    {
            if (psDroid->numWeaps > 0)
            {
                  if (asWeaponStats[psDroid->asWeaps[weapon_slot].nStat].vtolAttackRuns > 0)
                  {
                        psDroid->sMove.iAttackRuns[weapon_slot]++;
                        //quick check doesn't go over limit
                        ASSERT( psDroid->sMove.iAttackRuns[weapon_slot] < UWORD_MAX, "updateVtolAttackRun: too many attack runs" );
                  }
            }
    }
}

/*this mends the VTOL when it has been returned to home base whilst on an
offworld mission*/
void mendVtol(DROID *psDroid)
{
      UBYTE i;
      ASSERT( vtolEmpty(psDroid), "mendVtol: droid is not an empty weapon VTOL!" );

      CHECK_DROID(psDroid);

      /* set rearm value to no runs made */
      for (i = 0;i < psDroid->numWeaps;i++)
      {
            psDroid->sMove.iAttackRuns[i] = 0;
            //reset ammo and lastTimeFired
            psDroid->asWeaps[i].ammo = asWeaponStats[psDroid->
                  asWeaps[i].nStat].numRounds;
            psDroid->asWeaps[i].lastFired = 0;
      }
      /* set droid points to max */
      psDroid->body = psDroid->originalBody;

      CHECK_DROID(psDroid);
}

//assign rearmPad to the VTOL
void assignVTOLPad(DROID *psNewDroid, STRUCTURE *psReArmPad)
{
    ASSERT(isVtolDroid(psNewDroid), "not a vtol droid");
    ASSERT( psReArmPad->type == OBJ_STRUCTURE &&
                  psReArmPad->pStructureType->type == REF_REARM_PAD,
        "assignVTOLPad: not a ReArm Pad" );

      setDroidBase(psNewDroid, psReArmPad);
}

/*compares the droid sensor type with the droid weapon type to see if the
FIRE_SUPPORT order can be assigned*/
BOOL droidSensorDroidWeapon(BASE_OBJECT *psObj, DROID *psDroid)
{
      SENSOR_STATS      *psStats = NULL;

      //first check if the object is a droid or a structure
      if ( (psObj->type != OBJ_DROID) &&
             (psObj->type != OBJ_STRUCTURE) )
      {
            return false;
      }
      //check same player
      if (psObj->player != psDroid->player)
      {
            return false;
      }
      //check obj is a sensor droid/structure
      switch (psObj->type)
      {
      case OBJ_DROID:
            if (((DROID *)psObj)->droidType != DROID_SENSOR &&
                  ((DROID *)psObj)->droidType != DROID_COMMAND)
            {
                  return false;
            }
            psStats = asSensorStats + ((DROID *)psObj)->asBits[COMP_SENSOR].nStat;
            break;
      case OBJ_STRUCTURE:
            psStats = ((STRUCTURE *)psObj)->pStructureType->pSensor;
            if ((psStats == NULL) ||
                  (psStats->location != LOC_TURRET))
            {
                  return false;
            }
            break;
      default:
            break;
      }

      //check droid is a weapon droid - or Cyborg!!
    if (!(psDroid->droidType == DROID_WEAPON || psDroid->droidType ==
        DROID_CYBORG || psDroid->droidType == DROID_CYBORG_SUPER))
      {
            return false;
      }

      //finally check the right droid/sensor combination
      // check vtol droid with commander
      if ((isVtolDroid(psDroid) || !proj_Direct(asWeaponStats + psDroid->asWeaps[0].nStat)) &&
            ((DROID *)psObj)->droidType == DROID_COMMAND)
      {
            return true;
      }

      //check vtol droid with vtol sensor
    if (isVtolDroid(psDroid) && psDroid->asWeaps[0].nStat > 0)
      {
            if (psStats->type == VTOL_INTERCEPT_SENSOR ||
                  psStats->type == VTOL_CB_SENSOR ||
            psStats->type == SUPER_SENSOR)
            {
                  return true;
            }
            return false;
      }

      //check indirect weapon droid with standard/cb sensor
    /*Super Sensor works as any type*/
      if (!proj_Direct(asWeaponStats + psDroid->asWeaps[0].nStat))
      {
            if (psStats->type == STANDARD_SENSOR ||
                  psStats->type == INDIRECT_CB_SENSOR ||
            psStats->type == SUPER_SENSOR)
            {
                  return true;
            }
            return false;
      }
      return false;
}

// return whether a droid has a CB sensor on it
BOOL cbSensorDroid(DROID *psDroid)
{
      if (psDroid->droidType != DROID_SENSOR)
      {
            return false;
      }

    /*Super Sensor works as any type*/
      if (asSensorStats[psDroid->asBits[COMP_SENSOR].nStat].type ==
            VTOL_CB_SENSOR ||
            asSensorStats[psDroid->asBits[COMP_SENSOR].nStat].type ==
            INDIRECT_CB_SENSOR ||
            asSensorStats[psDroid->asBits[COMP_SENSOR].nStat].type ==
            SUPER_SENSOR)
      {
            return true;
      }

      return false;
}

// ////////////////////////////////////////////////////////////////////////////
// give a droid from one player to another - used in Electronic Warfare and multiplayer
//returns the droid created - for single player
DROID * giftSingleDroid(DROID *psD, UDWORD to)
{
      DROID_TEMPLATE    sTemplate;
      UWORD       x, y, numKills, i;
      float       direction;
      DROID       *psNewDroid, *psCurr;
      STRUCTURE   *psStruct;
      UDWORD            body, armourK[NUM_HIT_SIDES], armourH[NUM_HIT_SIDES];
      HIT_SIDE    impact_side;

      CHECK_DROID(psD);

      if(psD->player == to)
      {
            return psD;
      }

      // FIXME: why completely separate code paths for multiplayer and single player?? - Per

      if (bMultiPlayer)
      {
            // reset order
            orderDroid(psD, DORDER_STOP);

            if (droidRemove(psD, apsDroidLists))            // remove droid from one list
            {
                  if (!isHumanPlayer(psD->player))
                  {
                        droidSetName(psD, "Enemy Unit");
                  }

                  // if successfully removed the droid from the players list add it to new player's list
                  psD->selected     = false;
                  psD->player = to;       // move droid

                  addDroid(psD, apsDroidLists); // add to other list.

                  // the new player may have different default sensor/ecm/repair components
                  if ((asSensorStats + psD->asBits[COMP_SENSOR].nStat)->location == LOC_DEFAULT)
                  {
                        if (psD->asBits[COMP_SENSOR].nStat != aDefaultSensor[psD->player])
                        {
                              psD->asBits[COMP_SENSOR].nStat = (UBYTE)aDefaultSensor[psD->player];
                        }
                  }
                  if ((asECMStats + psD->asBits[COMP_ECM].nStat)->location == LOC_DEFAULT)
                  {
                        if (psD->asBits[COMP_ECM].nStat != aDefaultECM[psD->player])
                        {
                              psD->asBits[COMP_ECM].nStat = (UBYTE)aDefaultECM[psD->player];
                        }
                  }
                  if ((asRepairStats + psD->asBits[COMP_REPAIRUNIT].nStat)->location == LOC_DEFAULT)
                  {
                        if (psD->asBits[COMP_REPAIRUNIT].nStat != aDefaultRepair[psD->player])
                        {
                              psD->asBits[COMP_REPAIRUNIT].nStat = (UBYTE)aDefaultRepair[psD->player];
                        }
                  }
              }
            // add back into cluster system
            clustNewDroid(psD);

            // Update visibility
            visTilesUpdate((BASE_OBJECT*)psD);

            // add back into the grid system
            gridAddObject((BASE_OBJECT *)psD);

            // check through the 'to' players list of droids to see if any are targetting it
            for (psCurr = apsDroidLists[to]; psCurr != NULL; psCurr = psCurr->psNext)
            {
                  if (psCurr->psTarget == (BASE_OBJECT *)psD || psCurr->psActionTarget[0] == (BASE_OBJECT *)psD)
                  {
                        orderDroid(psCurr, DORDER_STOP);
                  }
                  // check through order list
                  for (i = 0; i < psCurr->listSize; i++)
                  {
                        if (psCurr->asOrderList[i].psOrderTarget == (BASE_OBJECT *)psD)
                        {
                              removeDroidOrderTarget(psCurr, i);
                              // move the rest of the list down
                              memmove(&psCurr->asOrderList[i], &psCurr->asOrderList[i] + 1, (psCurr->listSize - i) * sizeof(ORDER_LIST));
                              // adjust list size
                              psCurr->listSize -= 1;
                              // initialise the empty last slot
                              memset(psCurr->asOrderList + psCurr->listSize, 0, sizeof(ORDER_LIST));
                        }
                  }
            }
            // check through the 'to' players list of structures to see if any are targetting it
            for (psStruct = apsStructLists[to]; psStruct != NULL; psStruct = psStruct->psNext)
            {
                  if (psStruct->psTarget[0] == (BASE_OBJECT *)psD)
                  {
                        psStruct->psTarget[0] = NULL;
                  }
            }

            // skirmish callback!
            psScrCBDroidTaken = psD;
            eventFireCallbackTrigger((TRIGGER_TYPE)CALL_UNITTAKEOVER);
            psScrCBDroidTaken = NULL;

            return NULL;
      }
      else
      {
            // got to destroy the droid and build another since there are too many complications re order/action!

            // create a template based on the droid
            templateSetParts(psD, &sTemplate);

            // copy the name across
            sstrcpy(sTemplate.aName, psD->aName);

            x = psD->pos.x;
            y = psD->pos.y;
            body = psD->body;
            for (impact_side = 0;impact_side < NUM_HIT_SIDES;impact_side=impact_side+1)
            {
                  armourK[impact_side] = psD->armour[impact_side][WC_KINETIC];
                  armourH[impact_side] = psD->armour[impact_side][WC_HEAT];
            }
            numKills = psD->experience;
            direction = psD->direction;
            // only play the sound if unit being taken over is selectedPlayer's but not going to the selectedPlayer
            if (psD->player == selectedPlayer && to != selectedPlayer)
            {
                  scoreUpdateVar(WD_UNITS_LOST);
                  audio_QueueTrackPos( ID_SOUND_NEXUS_UNIT_ABSORBED, x, y, psD->pos.z );
            }
            // make the old droid vanish
            vanishDroid(psD);
            // create a new droid
            psNewDroid = buildDroid(&sTemplate, x, y, to, false);
            ASSERT(psNewDroid != NULL, "giftSingleUnit: unable to build a unit" );
            if (psNewDroid)
            {
                  addDroid(psNewDroid, apsDroidLists);
                  psNewDroid->body = body;
                  for (impact_side = 0;impact_side < NUM_HIT_SIDES;impact_side=impact_side+1)
                  {
                        psNewDroid->armour[impact_side][WC_KINETIC] = armourK[impact_side];
                        psNewDroid->armour[impact_side][WC_HEAT] = armourH[impact_side];
                  }
                  psNewDroid->experience = numKills;
                  psNewDroid->direction = direction;
                  if (!(psNewDroid->droidType == DROID_PERSON || cyborgDroid(psNewDroid) || psNewDroid->droidType == DROID_TRANSPORTER))
                  {
                        updateDroidOrientation(psNewDroid);
                  }
            }
            return psNewDroid;
      }
}

/*calculates the electronic resistance of a droid based on its experience level*/
SWORD   droidResistance(DROID *psDroid)
{
    SWORD   resistance;

      CHECK_DROID(psDroid);

    resistance = (SWORD)(psDroid->experience * DROID_RESISTANCE_FACTOR);

    //ensure base minimum in MP before the upgrade effect
    if (bMultiPlayer)
    {
        //ensure resistance is a base minimum
        if (resistance < DROID_RESISTANCE_FACTOR)
        {
            resistance = DROID_RESISTANCE_FACTOR;
        }
    }

    //structure resistance upgrades are passed on to droids
    resistance = (SWORD)(resistance  + resistance * (
        asStructureUpgrade[psDroid->player].resistance/100));

    //ensure resistance is a base minimum
    if (resistance < DROID_RESISTANCE_FACTOR)
    {
        resistance = DROID_RESISTANCE_FACTOR;
    }

    return resistance;
}

/*this is called to check the weapon is 'allowed'. Check if VTOL, the weapon is
direct fire. Also check numVTOLattackRuns for the weapon is not zero - return
true if valid weapon*/
/* Watermelon:this will be buggy if the droid being checked has both AA weapon and non-AA weapon
Cannot think of a solution without adding additional return value atm.
*/
BOOL checkValidWeaponForProp(DROID_TEMPLATE *psTemplate)
{
      PROPULSION_STATS  *psPropStats;
      BOOL              bValid = true;

      //check propulsion stat for vtol
      psPropStats = asPropulsionStats + psTemplate->asParts[COMP_PROPULSION];
      ASSERT( psPropStats != NULL,
            "checkValidWeaponForProp: invalid propulsion stats pointer" );
      if (asPropulsionTypes[psPropStats->propulsionType].travel == AIR)
      {
            //check weapon stat for indirect
            if (!proj_Direct(asWeaponStats + psTemplate->asWeaps[0])
                || !asWeaponStats[psTemplate->asWeaps[0]].vtolAttackRuns)
            {
                  bValid = false;
            }
      }
      else
      {
            if ( asWeaponStats[psTemplate->asWeaps[0]].vtolAttackRuns )
            {
                  bValid = false;
            }
      }

      //also checks that there is only a weapon attached and no other system component
      if (psTemplate->numWeaps == 0)
      {
            bValid = false;
      }
      if (psTemplate->asParts[COMP_BRAIN] != 0
          && asWeaponStats[psTemplate->asWeaps[0]].weaponSubClass != WSC_COMMAND)
      {
            assert(false);
            bValid = false;
      }

      return bValid;
}

/*called when a Template is deleted in the Design screen*/
void deleteTemplateFromProduction(DROID_TEMPLATE *psTemplate, UBYTE player)
{
    STRUCTURE   *psStruct;
    UDWORD      inc, i;
      STRUCTURE   *psList;

    //see if any factory is currently using the template
      for (i=0; i<2; i++)
      {
            psList = NULL;
            switch (i)
            {
            case 0:
                  psList = apsStructLists[player];
                  break;
            case 1:
                  psList = mission.apsStructLists[player];
                  break;
            }
            for (psStruct = psList; psStruct != NULL; psStruct = psStruct->psNext)
            {
                  if (StructIsFactory(psStruct))
                  {
                        FACTORY             *psFactory = &psStruct->pFunctionality->factory;
                        DROID_TEMPLATE      *psNextTemplate = NULL;

                        //if template belongs to the production player - check thru the production list (if struct is busy)
                        if (player == productionPlayer && psFactory->psSubject)
                        {
                              for (inc = 0; inc < MAX_PROD_RUN; inc++)
                              {
                                    if (asProductionRun[psFactory->psAssemblyPoint->factoryType][
                                    psFactory->psAssemblyPoint->factoryInc][inc].psTemplate == psTemplate)
                                    {
                                          //if this is the template currently being worked on
                                          if (psTemplate == (DROID_TEMPLATE *)psFactory->psSubject)
                                          {
                                                //set the quantity to 1 and then use factoryProdAdjust to subtract it
                                                asProductionRun[psFactory->psAssemblyPoint->factoryType][
                                                psFactory->psAssemblyPoint->factoryInc][inc].quantity = 1;
                                                factoryProdAdjust(psStruct, psTemplate, false);
                                                //init the factory production
                                                psFactory->psSubject = NULL;
                                                //check to see if anything left to produce
                                                psNextTemplate = factoryProdUpdate(psStruct, NULL);
                                                //power is returned by factoryProdAdjust()
                                                if (psNextTemplate)
                                                {
                                                      structSetManufacture(psStruct, psNextTemplate,psFactory->quantity);
                                                }
                                                else
                                                {
                                                      //nothing more to manufacture - reset the Subject and Tab on HCI Form
                                                      intManufactureFinished(psStruct);
                                                      //power is returned by factoryProdAdjust()
                                                }
                                          }
                                          else
                                          {
                                                //just need to initialise this production run
                                                asProductionRun[psFactory->psAssemblyPoint->factoryType][
                                                psFactory->psAssemblyPoint->factoryInc][inc].psTemplate = NULL;
                                                asProductionRun[psFactory->psAssemblyPoint->factoryType][
                                                psFactory->psAssemblyPoint->factoryInc][inc].quantity = 0;
                                                asProductionRun[psFactory->psAssemblyPoint->factoryType][
                                                psFactory->psAssemblyPoint->factoryInc][inc].built = 0;
                                          }
                                    }
                              }
                        }
                        else
                        {
                              //not the production player, so check not being built in the factory for the template player
                              if (psFactory->psSubject == (BASE_STATS *)psTemplate)
                              {
                                    //clear the factories subject and quantity
                                    psFactory->psSubject = NULL;
                                    psFactory->quantity = 0;
                                    //return any accrued power
                                    if (psFactory->powerAccrued)
                                    {
                                          addPower(psStruct->player, psFactory->powerAccrued);
                                    }
                                    //tell the interface
                                    intManufactureFinished(psStruct);
                              }
                        }
                  }
            }
      }
}


// Select a droid and do any necessary housekeeping.
//
void SelectDroid(DROID *psDroid)
{
      psDroid->selected = true;
      intRefreshScreen();
}


// De-select a droid and do any necessary housekeeping.
//
void DeSelectDroid(DROID *psDroid)
{
      psDroid->selected = false;
      intRefreshScreen();
}

/*calculate the power cost to repair a droid*/
UWORD powerReqForDroidRepair(DROID *psDroid)
{
    UWORD   powerReq;//powerPercent;

    powerReq = (UWORD)(repairPowerPoint(psDroid) * (psDroid->originalBody - psDroid->body));

    return powerReq;
}

/*power cost for One repair point*/
UWORD repairPowerPoint(DROID *psDroid)
{
    return (UWORD)(((POWER_FACTOR * calcDroidPower(psDroid)) / psDroid->originalBody) *
        REPAIR_POWER_FACTOR);
}

/** Callback function for stopped audio tracks
 *  Sets the droid's current track id to NO_SOUND
 *  \return true on success, false on failure
 */
05155 BOOL droidAudioTrackStopped( void *psObj )
{
      DROID *psDroid;

      psDroid = (DROID*)psObj;
      if (psDroid == NULL)
      {
            debug( LOG_ERROR, "droidAudioTrackStopped: droid pointer invalid\n" );
            return false;
      }

      if ( psDroid->type != OBJ_DROID || psDroid->died )
      {
            return false;
      }

      psDroid->iAudioID = NO_SOUND;
      return true;
}


/*returns true if droid type is one of the Cyborg types*/
BOOL cyborgDroid(const DROID* psDroid)
{
    return (psDroid->droidType == DROID_CYBORG
         || psDroid->droidType == DROID_CYBORG_CONSTRUCT
       || psDroid->droidType == DROID_CYBORG_REPAIR
       || psDroid->droidType == DROID_CYBORG_SUPER);
}

05185 BOOL droidOnMap(const DROID *psDroid)
{
      if (psDroid->died == NOT_CURRENT_LIST || psDroid->droidType == DROID_TRANSPORTER
          || psDroid->sMove.fx == INVALID_XY || psDroid->pos.x == INVALID_XY || missionIsOffworld()
          || mapHeight == 0)
      {
            // Off world or on a transport or is a transport or in mission list, or on a mission, or no map - ignore
            return true;
      }
      return (worldOnMap(psDroid->sMove.fx, psDroid->sMove.fy)
              && worldOnMap(psDroid->pos.x, psDroid->pos.y));
}

/** Teleport a droid to a new position on the map */
05199 void droidSetPosition(DROID *psDroid, int x, int y)
{
      psDroid->pos.x = x;
      psDroid->pos.y = y;
      psDroid->pos.z = map_Height(psDroid->pos.x, psDroid->pos.y);
      initDroidMovement(psDroid);
      visTilesUpdate((BASE_OBJECT *)psDroid);
}

Generated by  Doxygen 1.6.0   Back to index