Logo Search packages:      
Sourcecode: warzone2100 version File versions

projectile.c

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

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

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

      You should have received a copy of the GNU General Public License
      along with Warzone 2100; if not, write to the Free Software
      Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/***************************************************************************/
/*
 * Projectile functions
 *
 */
/***************************************************************************/
#include <string.h>

#include "lib/framework/frame.h"
#include "lib/framework/trig.h"
#include "lib/framework/math-help.h"

#include "lib/gamelib/gtime.h"
#include "objects.h"
#include "move.h"
#include "action.h"
#include "combat.h"
#include "effects.h"
#include "map.h"
#include "lib/sound/audio_id.h"
#include "lib/sound/audio.h"
#include "anim_id.h"
#include "projectile.h"
#include "visibility.h"
#include "lib/script/script.h"
#include "scripttabs.h"
#include "scriptcb.h"
#include "group.h"
#include "cmddroid.h"
#include "feature.h"
#include "lib/ivis_common/piestate.h"
#include "loop.h"
// FIXME Direct iVis implementation include!
#include "lib/ivis_opengl/piematrix.h"

#include "scores.h"

#include "display3d.h"
#include "display.h"
#include "multiplay.h"
#include "multistat.h"
#include "mapgrid.h"

#define     PROJ_MAX_PITCH                30
#define     ACC_GRAVITY                   1000
#define     DIRECT_PROJ_SPEED       500
#define     CHECK_PROJ_ABOVE_GROUND 1
#define NOMINAL_DAMAGE  5
#define VTOL_HITBOX_MODIFICATOR 100

/** Used for passing data to the checkBurnDamage function */
00071 typedef struct
{
      SWORD   x1, y1;
      SWORD   x2, y2;
      SWORD   rad;
} FIRE_BOX;

// Watermelon:they are from droid.c
/* The range for neighbouring objects */
#define PROJ_NAYBOR_RANGE           (TILE_UNITS*4)

// Watermelon:neighbour global info ripped from droid.c
static PROJ_NAYBOR_INFO asProjNaybors[MAX_NAYBORS];
static UDWORD           numProjNaybors = 0;

static BASE_OBJECT      *CurrentProjNaybors = NULL;
static UDWORD     projnayborTime = 0;

/* The list of projectiles in play */
static PROJECTILE *psProjectileList = NULL;

/* The next projectile to give out in the proj_First / proj_Next methods */
static PROJECTILE *psProjectileNext = NULL;

/***************************************************************************/

// the last unit that did damage - used by script functions
00098 BASE_OBJECT       *g_pProjLastAttacker;

extern BOOL godMode;

/***************************************************************************/

static UDWORD     establishTargetRadius( BASE_OBJECT *psTarget );
static UDWORD     establishTargetHeight( BASE_OBJECT *psTarget );
static void proj_InFlightDirectFunc( PROJECTILE *psObj );
static void proj_InFlightIndirectFunc( PROJECTILE *psObj );
static void proj_ImpactFunc( PROJECTILE *psObj );
static void proj_PostImpactFunc( PROJECTILE *psObj );
static void proj_checkBurnDamage( BASE_OBJECT *apsList, PROJECTILE *psProj,
                                                      FIRE_BOX *pFireBox );
static void proj_Free(PROJECTILE *psObj);

static float objectDamage(BASE_OBJECT *psObj, UDWORD damage, UDWORD weaponClass,UDWORD weaponSubClass, HIT_SIDE impactSide);
static HIT_SIDE getHitSide (PROJECTILE *psObj, BASE_OBJECT *psTarget);

static void projGetNaybors(PROJECTILE *psObj);


/***************************************************************************/
BOOL gfxVisible(PROJECTILE *psObj)
{
      BOOL bVisible = false;

      // Already know it is visible
      if (psObj->bVisible)
      {
            return true;
      }

      // You fired it
      if (psObj->player == selectedPlayer)
      {
            return true;
      }

      // Always see in this mode
      if (godMode)
      {
            return true;
      }

      // You can see the source
      if (psObj->psSource != NULL
       && !psObj->psSource->died
       && psObj->psSource->visible[selectedPlayer])
      {
            bVisible = true;
      }

      // You can see the destination
      if (psObj->psDest != NULL
       && !psObj->psDest->died
       && psObj->psDest->visible[selectedPlayer])
      {
            bVisible = true;
      }

      // Someone elses structure firing at something you can't see
      if (psObj->psSource != NULL
       && !psObj->psSource->died
       && psObj->psSource->type == OBJ_STRUCTURE
       && psObj->psSource->player != selectedPlayer
       && (psObj->psDest == NULL
        || psObj->psDest->died
        || !psObj->psDest->visible[selectedPlayer]))
      {
            bVisible = false;
      }

      // Something you cannot see firing at a structure that isn't yours
      if (psObj->psDest != NULL
       && !psObj->psDest->died
       && psObj->psDest->type == OBJ_STRUCTURE
       && psObj->psDest->player != selectedPlayer
       && (psObj->psSource == NULL
        || !psObj->psSource->visible[selectedPlayer]))
      {
            bVisible = false;
      }

      return bVisible;
}

/***************************************************************************/

BOOL
00188 proj_InitSystem( void )
{
      psProjectileList = NULL;
      psProjectileNext = NULL;

      return true;
}

/***************************************************************************/

// Clean out all projectiles from the system, and properly decrement
// all reference counts.
void
00201 proj_FreeAllProjectiles( void )
{
      PROJECTILE *psCurr = psProjectileList, *psPrev = NULL;

      while (psCurr)
      {
            psPrev = psCurr;
            psCurr = psCurr->psNext;
            proj_Free(psPrev);
      }

      psProjectileList = NULL;
      psProjectileNext = NULL;
}

/***************************************************************************/

BOOL
00219 proj_Shutdown( void )
{
      proj_FreeAllProjectiles();

      return true;
}

/***************************************************************************/

// Free the memory held by a projectile, and decrement its reference counts,
// if any. Do not call directly on a projectile in a list, because then the
// list will be broken!
static void proj_Free(PROJECTILE *psObj)
{
      /* Decrement any reference counts the projectile may have increased */
      setProjectileDamaged(psObj, NULL);
      setProjectileSource(psObj, NULL);
      setProjectileDestination(psObj, NULL);

      free(psObj);
}

/***************************************************************************/

// Reset the first/next methods, and give out the first projectile in the list.
PROJECTILE *
00245 proj_GetFirst( void )
{
      psProjectileNext = psProjectileList;
      return psProjectileList;
}

/***************************************************************************/

// Get the next projectile
PROJECTILE *
00255 proj_GetNext( void )
{
      psProjectileNext = psProjectileNext->psNext;
      return psProjectileNext;
}

/***************************************************************************/

/*
 * Relates the quality of the attacker to the quality of the victim.
 * The value returned satisfies the following inequality: 0.5 <= ret <= 2.0
 */
static float QualityFactor(DROID *psAttacker, DROID *psVictim)
{
      float powerRatio = calcDroidPower(psVictim) / calcDroidPower(psAttacker);
      float pointsRatio = calcDroidPoints(psVictim) / calcDroidPoints(psAttacker);

      CLIP(powerRatio, 0.5, 2.0);
      CLIP(pointsRatio, 0.5, 2.0);

      return (powerRatio + pointsRatio) / 2;
}

// update the kills after a target is damaged/destroyed
static void proj_UpdateKills(PROJECTILE *psObj, float experienceInc)
{
      DROID         *psDroid;
      BASE_OBJECT     *psSensor;

      CHECK_PROJECTILE(psObj);

      if ((psObj->psSource == NULL) ||
            ((psObj->psDest != NULL) && (psObj->psDest->type == OBJ_FEATURE)))
      {
            return;
      }

      // If experienceInc is negative then the target was killed
      if (bMultiPlayer && experienceInc < 0.0f)
      {
            updateMultiStatsKills(psObj->psDest, psObj->psSource->player);
      }

      // Since we are no longer interested if it was killed or not, abs it
      experienceInc = fabs(experienceInc);

      if (psObj->psSource->type == OBJ_DROID)               /* update droid kills */
      {
            psDroid = (DROID *) psObj->psSource;

            // If it is 'droid-on-droid' then modify the experience by the Quality factor
            // Only do this in MP so to not un-balance the campaign
            if (psObj->psDest != NULL
             && psObj->psDest->type == OBJ_DROID
             && bMultiPlayer)
            {
                  // Modify the experience gained by the 'quality factor' of the units
                  experienceInc *= QualityFactor(psDroid, (DROID *) psObj->psDest);
            }

            psDroid->experience += experienceInc;
            cmdDroidUpdateKills(psDroid, experienceInc);

            psSensor = orderStateObj(psDroid, DORDER_FIRESUPPORT);
            if (psSensor
             && psSensor->type == OBJ_DROID)
            {
                ((DROID *) psSensor)->experience += experienceInc;
            }
      }
      else if (psObj->psSource->type == OBJ_STRUCTURE)
      {
            // See if there was a command droid designating this target
            psDroid = cmdDroidGetDesignator(psObj->psSource->player);

            if (psDroid != NULL
             && psDroid->action == DACTION_ATTACK
             && psDroid->psActionTarget[0] == psObj->psDest)
            {
                  psDroid->experience += experienceInc;
            }
      }
}

/***************************************************************************/

00341 BOOL proj_SendProjectile(WEAPON *psWeap, BASE_OBJECT *psAttacker, int player, int tarX, int tarY, int tarZ,
                         BASE_OBJECT *psTarget, BOOL bVisible, BOOL bPenetrate, int weapon_slot)
{
      PROJECTILE        *psObj = malloc(sizeof(PROJECTILE));
      SDWORD                  tarHeight, srcHeight, iMinSq;
      SDWORD                  altChange, dx, dy, dz, iVelSq, iVel;
      double          fR, fA, fS, fT, fC;
      Vector3i muzzle;
      SDWORD                  iRadSq, iPitchLow, iPitchHigh, iTemp;
      UDWORD                  heightVariance;
      WEAPON_STATS      *psWeapStats = &asWeaponStats[psWeap->nStat];

      ASSERT( psWeapStats != NULL,
                  "proj_SendProjectile: invalid weapon stats" );

      /* get muzzle offset */
      if (psAttacker == NULL)
      {
            // if there isn't an attacker just start at the target position
            // NB this is for the script function to fire the las sats
            muzzle.x = (SDWORD)tarX;
            muzzle.y = (SDWORD)tarY;
            muzzle.z = (SDWORD)tarZ;
      }
      else if (psAttacker->type == OBJ_DROID && weapon_slot >= 0)
      {
            calcDroidMuzzleLocation( (DROID *) psAttacker, &muzzle, weapon_slot);
            /*update attack runs for VTOL droid's each time a shot is fired*/
            updateVtolAttackRun((DROID *)psAttacker, weapon_slot);
      }
      else if (psAttacker->type == OBJ_STRUCTURE && weapon_slot >= 0)
      {
            calcStructureMuzzleLocation( (STRUCTURE *) psAttacker, &muzzle, weapon_slot);
      }
      else // incase anything wants a projectile
      {
            muzzle.x = psAttacker->pos.x;
            muzzle.y = psAttacker->pos.y;
            muzzle.z = psAttacker->pos.z;
      }

      /* Initialise the structure */
      psObj->type           = OBJ_PROJECTILE;
      psObj->state            = PROJ_INFLIGHT;
      psObj->psWStats         = asWeaponStats + psWeap->nStat;
      psObj->pos.x                  = (UWORD)muzzle.x;
      psObj->pos.y                  = (UWORD)muzzle.y;
      psObj->pos.z                  = (UWORD)muzzle.z;
      psObj->startX           = muzzle.x;
      psObj->startY           = muzzle.y;
      psObj->tarX             = tarX;
      psObj->tarY             = tarY;
      psObj->targetRadius = (psTarget ? establishTargetRadius(psTarget) : 0); // needed to backtrack FX
      psObj->born             = gameTime;
      psObj->player           = (UBYTE)player;
      psObj->bVisible         = false;
      psObj->airTarget  = false;
      psObj->psDamaged  = NULL; // must initialize these to NULL first!
      psObj->psSource         = NULL;
      psObj->psDest           = NULL;
      psObj->died       = 0;
      setProjectileDestination(psObj, psTarget);

      ASSERT(!psTarget || !psTarget->died, "Aiming at dead target!");

      /* If target is a VTOL or higher than ground, it is an air target. */
      if ((psTarget != NULL && psTarget->type == OBJ_DROID && isVtolDroid((DROID*)psTarget))
          || (psTarget == NULL && (SDWORD)tarZ > map_Height(tarX,tarY)))
      {
            psObj->airTarget = true;
      }

      //Watermelon:use the source of the source of psObj :) (psAttacker is a projectile)
      if (bPenetrate && psAttacker)
      {
            // psAttacker is a projectile if bPenetrate
            PROJECTILE *psProj = (PROJECTILE*)psAttacker;

            ASSERT(psProj->type == OBJ_PROJECTILE, "Penetrating but not projectile?");

            if (psProj->psSource && !psProj->psSource->died)
            {
                  setProjectileSource(psObj, psProj->psSource);
            }

            if (psProj->psDest && !psProj->psDest->died)
            {
                  setProjectileDamaged(psObj, psProj->psDest);
            }
            psProj->state = PROJ_IMPACT;
      }
      else
      {
            setProjectileSource(psObj, psAttacker);
      }

      if (psTarget)
      {
            scoreUpdateVar(WD_SHOTS_ON_TARGET);
            heightVariance = 0;
            switch(psTarget->type)
            {
                  case OBJ_DROID:
                  case OBJ_FEATURE:
                        if( ((DROID*)psTarget)->droidType == DROID_PERSON )
                        {
                              heightVariance = rand()%4;
                        }
                        else
                        {
                              heightVariance = rand()%8;
                        }
                        break;

                  case OBJ_STRUCTURE:
                        heightVariance = rand()%8;
                        break;

                  case OBJ_PROJECTILE:
                        ASSERT(!"invalid object type: bullet", "proj_SendProjectile: invalid object type: OBJ_PROJECTILE");
                        break;

                  case OBJ_TARGET:
                        ASSERT(!"invalid object type: target", "proj_SendProjectile: invalid object type: OBJ_TARGET");
                        break;

                  default:
                        ASSERT(!"unknown object type", "proj_SendProjectile: unknown object type");
                        break;
            }
            tarHeight = psTarget->pos.z + heightVariance;
      }
      else
      {
            tarHeight = (SDWORD)tarZ;
            scoreUpdateVar(WD_SHOTS_OFF_TARGET);
      }

      srcHeight               = muzzle.z;
      altChange               = tarHeight - srcHeight;

      psObj->srcHeight  = srcHeight;
      psObj->altChange  = altChange;

      dx = ((SDWORD)psObj->tarX) - muzzle.x;
      dy = ((SDWORD)psObj->tarY) - muzzle.y;
      dz = tarHeight - muzzle.z;

      /* roll never set */
      psObj->roll = 0;

      fR = (double) atan2(dx, dy);
      if ( fR < 0.0 )
      {
            fR += (double) (2 * M_PI);
      }
      psObj->direction = RAD_TO_DEG(fR);


      /* get target distance */
      iRadSq = dx*dx + dy*dy + dz*dz;
      fR = trigIntSqrt( iRadSq );
      iMinSq = (SDWORD)(psWeapStats->minRange * psWeapStats->minRange);

      if ( proj_Direct(psObj->psWStats) ||
             ( !proj_Direct(psWeapStats) && (iRadSq <= iMinSq) ) )
      {
            fR = (double) atan2(dz, fR);
            if ( fR < 0.0 )
            {
                  fR += (double) (2 * M_PI);
            }
            psObj->pitch = (SWORD)( RAD_TO_DEG(fR) );
            psObj->pInFlightFunc = proj_InFlightDirectFunc;
      }
      else
      {
            /* indirect */
            iVelSq = psObj->psWStats->flightSpeed * psObj->psWStats->flightSpeed;

            fA = ACC_GRAVITY * (double)iRadSq / (2 * iVelSq);
            fC = 4 * fA * ((double)dz + fA);
            fS = (double)iRadSq - fC;

            /* target out of range - increase velocity to hit target */
            if ( fS < 0. )
            {
                  /* set optimal pitch */
                  psObj->pitch = PROJ_MAX_PITCH;

                  fS = (double)trigSin(PROJ_MAX_PITCH);
                  fC = (double)trigCos(PROJ_MAX_PITCH);
                  fT = fS / fC;
                  fS = ACC_GRAVITY * (1. + fT * fT);
                  fS = fS / (2 * (fR * fT - (double)dz));
                  {
                        double Tmp = fR * fR;
                        iVel = trigIntSqrt(fS * Tmp);
                  }
            }
            else
            {
                  /* set velocity to stats value */
                  iVel = psObj->psWStats->flightSpeed;

                  /* get floating point square root */
                  fS = trigIntSqrt(fS);

                  fT = (double) atan2(fR + fS, 2 * fA);

                  /* make sure angle positive */
                  if ( fT < 0 )
                  {
                        fT += (double) (2 * M_PI);
                  }
                  iPitchLow = RAD_TO_DEG(fT);

                  fT = (double) atan2(fR-fS, 2*fA);
                  /* make sure angle positive */
                  if ( fT < 0 )
                  {
                        fT += (double) (2 * M_PI);
                  }
                  iPitchHigh = RAD_TO_DEG(fT);

                  /* swap pitches if wrong way round */
                  if ( iPitchLow > iPitchHigh )
                  {
                        iTemp = iPitchHigh;
                        iPitchLow = iPitchHigh;
                        iPitchHigh = iTemp;
                  }

                  /* chooselow pitch unless -ve */
                  if ( iPitchLow > 0 )
                  {
                        psObj->pitch = (SWORD)iPitchLow;
                  }
                  else
                  {
                        psObj->pitch = (SWORD)iPitchHigh;
                  }
            }

            /* if droid set muzzle pitch */
            //Watermelon:fix turret pitch for more turrets
            if (psAttacker != NULL && weapon_slot >= 0)
            {
                  if (psAttacker->type == OBJ_DROID)
                  {
                        ((DROID *) psAttacker)->turretPitch[weapon_slot] = psObj->pitch;
                  }
                  else if (psAttacker->type == OBJ_STRUCTURE)
                  {
                        ((STRUCTURE *) psAttacker)->turretPitch[weapon_slot] = psObj->pitch;
                  }
            }

            psObj->vXY = iVel * trigCos(psObj->pitch);
            psObj->vZ  = iVel * trigSin(psObj->pitch);

            /* set function pointer */
            psObj->pInFlightFunc = proj_InFlightIndirectFunc;
      }

      /* put the projectile object first in the global list */
      psObj->psNext = psProjectileList;
      psProjectileList = psObj;

      /* play firing audio */
      // only play if either object is visible, i know it's a bit of a hack, but it avoids the problem
      // of having to calculate real visibility values for each projectile.
      if ( bVisible || gfxVisible(psObj) )
      {
            // note that the projectile is visible
            psObj->bVisible = true;

            if ( psObj->psWStats->iAudioFireID != NO_SOUND )
            {

            if ( psObj->psSource )
            {
                        /* firing sound emitted from source */
                  audio_PlayObjDynamicTrack( (BASE_OBJECT *) psObj->psSource,
                                                      psObj->psWStats->iAudioFireID, NULL );
                        /* GJ HACK: move howitzer sound with shell */
                        if ( psObj->psWStats->weaponSubClass == WSC_HOWITZERS )
                        {
                        audio_PlayObjDynamicTrack( (BASE_OBJECT *) psObj,
                                                      ID_SOUND_HOWITZ_FLIGHT, NULL );
                        }
            }
                  //don't play the sound for a LasSat in multiPlayer
            else if (!(bMultiPlayer && psWeapStats->weaponSubClass == WSC_LAS_SAT))
                  {
                    audio_PlayObjStaticTrack(psObj, psObj->psWStats->iAudioFireID);
            }
            }
      }

      if ((psAttacker != NULL) && !proj_Direct(psWeapStats))
      {
            //check for Counter Battery Sensor in range of target
            counterBatteryFire(psAttacker, psTarget);
      }

      CHECK_PROJECTILE(psObj);

      return true;
}

/***************************************************************************/

static void proj_InFlightDirectFunc( PROJECTILE *psObj )
{
      WEAPON_STATS      *psStats;
      SDWORD                  timeSoFar;
      SDWORD                  dx, dy, dz, iX, iY, dist, xdiff, ydiff;
      //Watermelon: make zdiff always positive
      UDWORD                  zdiff;
      SDWORD                  rad;
      Vector3i pos;
      //Watermelon:int i
      UDWORD                  i;
      //Watermelon:2 temp BASE_OBJECT pointer
      BASE_OBJECT       *psNewTarget;
      //Watermelon:Missile or not
      BOOL              bMissile = false;
      //Watermelon:extended 'lifespan' of a projectile
      //no more disappeared projectiles.
      SDWORD                  extendRad;
      //Watermelon:Penetrate or not
      BOOL              bPenetrate;
      WEAPON                  asWeap;

      CHECK_PROJECTILE(psObj);

      psStats = psObj->psWStats;
      ASSERT( psStats != NULL,
            "proj_InFlightDirectFunc: Invalid weapon stats pointer" );

      bPenetrate = psStats->penetrate;
      timeSoFar = gameTime - psObj->born;

    //we want a delay between Las-Sats firing and actually hitting in multiPlayer
            //magic number but that's how long the audio countdown message lasts!
    if ( bMultiPlayer && psStats->weaponSubClass == WSC_LAS_SAT && timeSoFar < 8 * GAME_TICKS_PER_SEC )
      {
            return;
    }

      /* If it's homing and it has a target (not a miss)... */
      if ( psStats->movementModel == MM_HOMINGDIRECT && psObj->psDest )
      {
            dx = (SDWORD)psObj->psDest->pos.x - (SDWORD)psObj->startX;
            dy = (SDWORD)psObj->psDest->pos.y - (SDWORD)psObj->startY;
            dz = (SDWORD)psObj->psDest->pos.z - (SDWORD)psObj->srcHeight;
      }
      else
      {
            dx = (SDWORD)psObj->tarX - (SDWORD)psObj->startX;
            dy = (SDWORD)psObj->tarY - (SDWORD)psObj->startY;
            // LASSAT has no z component.
            if (psStats->weaponSubClass == WSC_LAS_SAT)
            {
                  dz = 0;
            }
            else
            {
                  dz = (SDWORD)(psObj->altChange);
            }
      }

      // ffs
      rad = (SDWORD)sqrtf( dx*dx + dy*dy );
      //Watermelon:extended life span
      extendRad = (SDWORD)(rad * 1.5f);

      if (rad == 0) // Prevent div by zero
      {
            rad = 1;
      }

      dist = timeSoFar * psStats->flightSpeed / GAME_TICKS_PER_SEC;

      iX = psObj->startX + (dist * dx / rad);
      iY = psObj->startY + (dist * dy / rad);

      /* impact if about to go off map else update coordinates */
      if ( worldOnMap( iX, iY ) == false )
      {
            psObj->state = PROJ_IMPACT;
            debug( LOG_NEVER, "**** projectile off map - removed ****\n" );
            return;
      }
      else
      {
            psObj->pos.x = (UWORD)iX;
            psObj->pos.y = (UWORD)iY;
      }
      psObj->pos.z = (UWORD)(psObj->srcHeight) + (UWORD)(dist * dz / rad);

      if ( gfxVisible(psObj) )
      {
            if ( psStats->weaponSubClass == WSC_FLAME )
            {
                  effectGiveAuxVar(PERCENT(dist,rad));
                  pos.x = psObj->pos.x;
                  pos.y = psObj->pos.z-8;
                  pos.z = psObj->pos.y;
                  addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_FLAMETHROWER,false,NULL,0);
            }
            else if ( psStats->weaponSubClass == WSC_COMMAND ||
                  psStats->weaponSubClass == WSC_ELECTRONIC ||
                  psStats->weaponSubClass == WSC_EMP )
            {
                  effectGiveAuxVar((PERCENT(dist,rad)/2));
                  pos.x = psObj->pos.x;
                  pos.y = psObj->pos.z-8;
                  pos.z = psObj->pos.y;
                  addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_LASER,false,NULL,0);
            }
            else if ( psStats->weaponSubClass == WSC_ROCKET ||
                  psStats->weaponSubClass == WSC_MISSILE ||
                  psStats->weaponSubClass == WSC_SLOWROCKET ||
                  psStats->weaponSubClass == WSC_SLOWMISSILE )
            {
                  pos.x = psObj->pos.x;
                  pos.y = psObj->pos.z+8;
                  pos.z = psObj->pos.y;
                  addEffect(&pos,EFFECT_SMOKE,SMOKE_TYPE_TRAIL,false,NULL,0);
            }
      }

      //Watermelon:weapon radius,or the 'real' projectile will never hit a moving target with the changes...
      if ( psStats->weaponSubClass == WSC_ROCKET ||
            psStats->weaponSubClass == WSC_MISSILE ||
            psStats->weaponSubClass == WSC_SLOWROCKET ||
            psStats->weaponSubClass == WSC_SLOWMISSILE )
      {
            bMissile = true;
      }
      else if ( psStats->weaponSubClass == WSC_MGUN ||
                  psStats->weaponSubClass == WSC_COMMAND )
      {
            //Watermelon:extended life span
            extendRad = (SDWORD)(rad * 1.2f);
      }
      else if ( psStats->weaponSubClass == WSC_CANNON ||
                  psStats->weaponSubClass == WSC_BOMB ||
                  psStats->weaponSubClass == WSC_ELECTRONIC ||
                  psStats->weaponSubClass == WSC_EMP ||
                  psStats->weaponSubClass == WSC_FLAME ||
                  psStats->weaponSubClass == WSC_ENERGY ||
                  psStats->weaponSubClass == WSC_GAUSS )
      {
            //Watermelon:extended life span
            extendRad = (SDWORD)(rad * 1.5f);
      }
      else if ( psStats->weaponSubClass == WSC_AAGUN )
      {
            //increased radius 1.414 times
            extendRad = rad;
      }

      for (i = 0;i < numProjNaybors;i++)
      {
            BASE_OBJECT *psTempObj = asProjNaybors[i].psObj;

            CHECK_OBJECT(psTempObj);

            if ( psTempObj == psObj->psDamaged )
            {
                  continue;
            }

            //Watermelon;so a projectile wont collide with another projectile unless it's a counter-missile weapon
            if ( psTempObj->type == OBJ_PROJECTILE && !( bMissile || ((PROJECTILE *)psTempObj)->psWStats->weaponSubClass == WSC_COUNTER ) )
            {
                  continue;
            }

            //Watermelon:ignore oil resource and pickup
            if ( psTempObj->type == OBJ_FEATURE &&
                  ((FEATURE *)psTempObj)->psStats->damageable == 0 )
            {
                  continue;
            }

            if (psTempObj->died)
            {
                  continue;
            }

            if ( psTempObj->player != psObj->player &&
                  ( psTempObj->type == OBJ_DROID ||
                  psTempObj->type == OBJ_STRUCTURE ||
                  psTempObj->type == OBJ_PROJECTILE ||
                  psTempObj->type == OBJ_FEATURE ) &&
                  !aiCheckAlliances(psTempObj->player,psObj->player) )
            {
                  if ( psTempObj->type == OBJ_STRUCTURE || psTempObj->type == OBJ_FEATURE )
                  {
                        //Watermelon:AA weapon shouldnt hit buildings
                        if ( psObj->psWStats->surfaceToAir == SHOOT_IN_AIR )
                        {
                              continue;
                        }

                        xdiff = (SDWORD)psObj->pos.x - (SDWORD)psTempObj->pos.x;
                        ydiff = (SDWORD)psObj->pos.y - (SDWORD)psTempObj->pos.y;
                        zdiff = abs((UDWORD)psObj->pos.z - (UDWORD)psTempObj->pos.z);

                        if ( zdiff < establishTargetHeight(psTempObj) &&
                              (xdiff*xdiff + ydiff*ydiff) < ( (SDWORD)establishTargetRadius(psTempObj) * (SDWORD)establishTargetRadius(psTempObj) ) )
                        {
                              psNewTarget = psTempObj;
                              setProjectileDestination(psObj, psNewTarget);
                              psObj->state = PROJ_IMPACT;
                              return;
                        }
                  }
                  else
                  {
                        if ( psObj->psWStats->surfaceToAir == SHOOT_IN_AIR &&
                              psTempObj->type == OBJ_DROID && !isVtolDroid((DROID *)psTempObj) )
                        {
                              continue;
                        }

                        xdiff = (SDWORD)psObj->pos.x - (SDWORD)psTempObj->pos.x;
                        ydiff = (SDWORD)psObj->pos.y - (SDWORD)psTempObj->pos.y;
                        zdiff = abs((UDWORD)psObj->pos.z - (UDWORD)psTempObj->pos.z);

                        if ( zdiff < establishTargetHeight(psTempObj) &&
                              (xdiff*xdiff + ydiff*ydiff) < (SDWORD)( establishTargetRadius(psTempObj) * establishTargetRadius(psTempObj) ) )
                        {
                              psNewTarget = psTempObj;
                              setProjectileDestination(psObj, psNewTarget);

                              if (bPenetrate)
                              {
                                    SDWORD TargetX, TargetY;

                                    asWeap.nStat = psObj->psWStats - asWeaponStats;
                                    //Watermelon:just assume we damaged the chosen target
                                    setProjectileDamaged(psObj, psNewTarget);

                                    // Determine position to fire a missile at
                                    // (must be at least 0 because we don't use signed integers
                                    //  this shouldn't be larger than the height and width of the map either)
                                    TargetX = psObj->startX + extendRad * dx / rad;
                                    TargetY = psObj->startY + extendRad * dy / rad;
                                    CLIP(TargetX, 0, world_coord(mapWidth - 1));
                                    CLIP(TargetY, 0, world_coord(mapHeight - 1));
                                    proj_SendProjectile( &asWeap, (BASE_OBJECT*)psObj, psObj->player, TargetX, TargetY, psObj->pos.z, NULL, true, bPenetrate, -1 );
                              }
                              else
                              {
                                    psObj->state = PROJ_IMPACT;
                              }
                              return;
                        }
                  }
            }
      }

      /* See if effect has finished */
      if ( psStats->movementModel == MM_HOMINGDIRECT && psObj->psDest )
      {
            xdiff = (SDWORD)psObj->pos.x - (SDWORD)psObj->psDest->pos.x;
            ydiff = (SDWORD)psObj->pos.y - (SDWORD)psObj->psDest->pos.y;
            zdiff = abs((UDWORD)psObj->pos.z - (UDWORD)psObj->psDest->pos.z);

            if ( zdiff < establishTargetHeight(psObj->psDest ) &&
                  (xdiff*xdiff + ydiff*ydiff) < ((SDWORD)psObj->targetRadius * (SDWORD)psObj->targetRadius) )
            {
                  psObj->state = PROJ_IMPACT;
            }
      }
      else if ( dist > extendRad - (SDWORD)psObj->targetRadius )
      {
            psObj->state = PROJ_IMPACT;

            /* It's damage time */
            if (psObj->psDest)
            {
                  xdiff = (SDWORD)psObj->pos.x - (SDWORD)psObj->psDest->pos.x;
                  ydiff = (SDWORD)psObj->pos.y - (SDWORD)psObj->psDest->pos.y;
                  zdiff = abs((UDWORD)psObj->pos.z - (UDWORD)psObj->psDest->pos.z);

                  //Watermelon:'real' hit check even if the projectile is about to 'timeout'
                  if ( zdiff < establishTargetHeight(psObj->psDest) &&
                        (xdiff*xdiff + ydiff*ydiff) < (SDWORD)( establishTargetRadius(psObj->psDest) * establishTargetRadius(psObj->psDest) ) )
                  {
                  }
                  else
                  {
                        //Watermelon:missed.you can now 'dodge' projectile by micro,so cyborgs should be more useful now
                        setProjectileDestination(psObj, NULL);
                  }
            }
      }

#if CHECK_PROJ_ABOVE_GROUND
      /* check not trying to travel through terrain - if so count as a miss */
      if ( mapObjIsAboveGround( (BASE_OBJECT *) psObj ) == false )
      {
            psObj->state = PROJ_IMPACT;
            /* miss registered if NULL target */
            setProjectileDestination(psObj, NULL);
            return;
      }
#endif

      /* add smoke trail to indirect weapons firing directly */
      if( !proj_Direct( psStats ) && gfxVisible(psObj))
      {
            pos.x = psObj->pos.x;
            pos.y = psObj->pos.z+8;
            pos.z = psObj->pos.y;
            addEffect(&pos,EFFECT_SMOKE,SMOKE_TYPE_TRAIL,false,NULL,0);
      }
}

/***************************************************************************/

static void proj_InFlightIndirectFunc( PROJECTILE *psObj )
{
      WEAPON_STATS      *psStats;
      SDWORD                  iTime, iRad, iDist, dx, dy, dz, iX, iY;
      Vector3i pos;
      float             fVVert;
      BOOL              bOver = false;
      BASE_OBJECT       *psNewTarget;
      UDWORD                  i;
      SDWORD                  xdiff,ydiff,extendRad;
      UDWORD                  zdiff;
      BOOL              bPenetrate;
      WEAPON                  asWeap;

      CHECK_PROJECTILE(psObj);

      psStats = psObj->psWStats;
      bPenetrate = psStats->penetrate;
      ASSERT( psStats != NULL,
            "proj_InFlightIndirectFunc: Invalid weapon stats pointer" );

      iTime = gameTime - psObj->born;

      dx = (SDWORD)psObj->tarX-(SDWORD)psObj->startX;
      dy = (SDWORD)psObj->tarY-(SDWORD)psObj->startY;

      // ffs
      iRad = (SDWORD)sqrtf( dx*dx + dy*dy );

      iDist = iTime * psObj->vXY / GAME_TICKS_PER_SEC;

      iX = psObj->startX + (iDist * dx / iRad);
      iY = psObj->startY + (iDist * dy / iRad);

      /* impact if about to go off map else update coordinates */
      if ( !worldOnMap( iX, iY ) )
      {
            psObj->state = PROJ_IMPACT;
            debug( LOG_NEVER, "**** projectile off map - removed ****\n" );
            return;
      }
      else
      {
            psObj->pos.x = (UWORD)iX;
            psObj->pos.y = (UWORD)iY;
      }

      dz = (psObj->vZ - (iTime*ACC_GRAVITY/GAME_TICKS_PER_SEC/2)) *
                        iTime / GAME_TICKS_PER_SEC;
      psObj->pos.z = (UWORD)(psObj->srcHeight + dz);

      fVVert = psObj->vZ - (iTime * ACC_GRAVITY / GAME_TICKS_PER_SEC);
      psObj->pitch = (SWORD)( RAD_TO_DEG(atan2(fVVert, psObj->vXY)) );

      //Watermelon:extended life span for artillery projectile
      extendRad = (SDWORD)(iRad * 1.2f);

      if( gfxVisible(psObj) )
      {
            switch(psStats->weaponSubClass)
            {
                  case WSC_MGUN:
                  case WSC_CANNON:
                  case WSC_MORTARS:
                  case WSC_ENERGY:
                  case WSC_GAUSS:
                  case WSC_HOWITZERS:
                  case WSC_AAGUN:
                  case WSC_LAS_SAT:
                  case WSC_BOMB:
                  case WSC_COUNTER:
                  case NUM_WEAPON_SUBCLASS:
                  case INVALID_SUBCLASS:
                        break;
                  case WSC_FLAME:
                        effectGiveAuxVar(PERCENT(iDist,iRad));
                        pos.x = psObj->pos.x;
                        pos.y = psObj->pos.z-8;
                        pos.z = psObj->pos.y;
                        addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_FLAMETHROWER,false,NULL,0);
                        break;
                  case WSC_COMMAND:
                  case WSC_ELECTRONIC:
                  case WSC_EMP:
                        effectGiveAuxVar((PERCENT(iDist,iRad)/2));
                        pos.x = psObj->pos.x;
                        pos.y = psObj->pos.z-8;
                        pos.z = psObj->pos.y;
                        addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_LASER,false,NULL,0);
                        break;
                  case WSC_ROCKET:
                  case WSC_MISSILE:
                  case WSC_SLOWROCKET:
                  case WSC_SLOWMISSILE:
                        pos.x = psObj->pos.x;
                        pos.y = psObj->pos.z+8;
                        pos.z = psObj->pos.y;
                        addEffect(&pos,EFFECT_SMOKE,SMOKE_TYPE_TRAIL,false,NULL,0);
                        break;
            }
      }

      //Watermelon:test test
      for (i = 0;i < numProjNaybors;i++)
      {
            BASE_OBJECT *psTempObj = asProjNaybors[i].psObj;

            CHECK_OBJECT(psTempObj);

            //Watermelon;dont collide with any other projectiles
            if ( psTempObj->type == OBJ_PROJECTILE )
            {
                  continue;
            }

            //Watermelon:ignore oil resource and pickup
            if ( psTempObj->type == OBJ_FEATURE &&
                  ((FEATURE *)psTempObj)->psStats->damageable == 0 )
            {
                  continue;
            }

            if (psTempObj->died)
            {
                  continue;
            }

            if (psTempObj->player != psObj->player &&
                  (psTempObj->type == OBJ_DROID ||
                  psTempObj->type == OBJ_STRUCTURE ||
                  psTempObj->type == OBJ_PROJECTILE ||
                  psTempObj->type == OBJ_FEATURE) &&
                  !aiCheckAlliances(psTempObj->player,psObj->player))
            {
                  if ( psTempObj->type == OBJ_STRUCTURE || psTempObj->type == OBJ_FEATURE )
                  {
                        xdiff = (SDWORD)psObj->pos.x - (SDWORD)psTempObj->pos.x;
                        ydiff = (SDWORD)psObj->pos.y - (SDWORD)psTempObj->pos.y;
                        zdiff = abs((UDWORD)psObj->pos.z - (UDWORD)psTempObj->pos.z);

                        if ( zdiff < establishTargetHeight(psTempObj) &&
                              (xdiff*xdiff + ydiff*ydiff) < ( (SDWORD)establishTargetRadius(psTempObj) * (SDWORD)establishTargetRadius(psTempObj) ) )
                        {
                              psNewTarget = psTempObj;
                              setProjectileDestination(psObj, psNewTarget);
                              psObj->state = PROJ_IMPACT;
                              return;
                        }
                  }
                  else
                  {
                        xdiff = (SDWORD)psObj->pos.x - (SDWORD)psTempObj->pos.x;
                        ydiff = (SDWORD)psObj->pos.y - (SDWORD)psTempObj->pos.y;
                        zdiff = abs((UDWORD)psObj->pos.z - (UDWORD)psTempObj->pos.z);

                        if ( zdiff < establishTargetHeight(psTempObj) &&
                              (UDWORD)(xdiff*xdiff + ydiff*ydiff) < ( (SDWORD)establishTargetRadius(psTempObj) * (SDWORD)establishTargetRadius(psTempObj) ) )
                        {
                              psNewTarget = psTempObj;
                              setProjectileDestination(psObj, psNewTarget);

                              if (bPenetrate)
                              {
                                    SDWORD TargetX, TargetY;

                                    asWeap.nStat = psObj->psWStats - asWeaponStats;
                                    //Watermelon:just assume we damaged the chosen target
                                    setProjectileDamaged(psObj, psNewTarget);

                                    // Determine position to fire a missile at
                                    // (must be at least 0 because we don't use signed integers
                                    //  this shouldn't be larger than the height and width of the map either)
                                    TargetX = MAX(psObj->startX + extendRad * dx / iRad, 0);
                                    TargetX = MIN(TargetX, world_coord(mapWidth - 1));
                                    TargetY = MAX(psObj->startY + extendRad * dy / iRad, 0);
                                    TargetY = MIN(TargetY, world_coord(mapHeight - 1));
                                    proj_SendProjectile( &asWeap, (BASE_OBJECT*)psObj, psObj->player, TargetX, TargetY, psObj->pos.z, NULL, true, bPenetrate, -1 );
                              }
                              else
                              {
                                    psObj->state = PROJ_IMPACT;
                              }
                              return;
                        }
                  }
            }
      }

      /* See if effect has finished */
      if ( iDist > (extendRad - (SDWORD)psObj->targetRadius) )
      {
            psObj->state = PROJ_IMPACT;

            pos.x = psObj->pos.x;
            pos.z = psObj->pos.y;
            pos.y = map_Height(pos.x,pos.z) + 8;

            /* It's damage time */
            //Watermelon:'real' check
            if ( psObj->psDest )
            {
                  psObj->pos.z = (UWORD)(pos.y + 8); // bring up the impact explosion

                  xdiff = (SDWORD)psObj->pos.x - (SDWORD)psObj->psDest->pos.x;
                  ydiff = (SDWORD)psObj->pos.y - (SDWORD)psObj->psDest->pos.y;
                  zdiff = abs((UDWORD)psObj->pos.z - (UDWORD)psObj->psDest->pos.z);

                  if ( zdiff < establishTargetHeight(psObj->psDest) &&
                        (xdiff*xdiff + ydiff*ydiff) < (SDWORD)( establishTargetRadius(psObj->psDest) * establishTargetRadius(psObj->psDest) ) )
                  {
                        psObj->state = PROJ_IMPACT;
                  }
                  else
                  {
                        setProjectileDestination(psObj, NULL);
                  }
            }
            bOver = true;
      }

#if CHECK_PROJ_ABOVE_GROUND
      /* check not trying to travel through terrain - if so count as a miss */
      if ( mapObjIsAboveGround( (BASE_OBJECT *) psObj ) == false )
      {
            psObj->state = PROJ_IMPACT;
            /* miss registered if NULL target */
            setProjectileDestination(psObj, NULL);
            bOver = true;
      }
#endif

      /* Add smoke particle at projectile location (in world coords) */
      /* Add a trail graphic */
      /* If it's indirect and not a flamethrower - add a smoke trail! */
      if ( psStats->weaponSubClass != WSC_FLAME &&
            psStats->weaponSubClass != WSC_ENERGY &&
            psStats->weaponSubClass != WSC_COMMAND &&
            psStats->weaponSubClass != WSC_ELECTRONIC &&
            psStats->weaponSubClass != WSC_EMP &&
            !bOver )
      {
            if(gfxVisible(psObj))
            {
                  pos.x = psObj->pos.x;
                  pos.y = psObj->pos.z+4;
                  pos.z = psObj->pos.y;
                  addEffect(&pos,EFFECT_SMOKE,SMOKE_TYPE_TRAIL,false,NULL,0);
            }
      }
}

/***************************************************************************/

static void proj_ImpactFunc( PROJECTILE *psObj )
{
      WEAPON_STATS      *psStats;
      SDWORD                  i, iAudioImpactID;
      DROID             *psCurrD, *psNextD;
      STRUCTURE         *psCurrS, *psNextS;
      FEATURE                 *psCurrF, *psNextF;
      UDWORD                  dice;
      SDWORD                  tarX0,tarY0, tarX1,tarY1;
      SDWORD                  radCubed, xDiff,yDiff;
      float             relativeDamage;
      Vector3i position,scatter;
      UDWORD                  damage;     //optimisation - were all being calculated twice on PC
      //Watermelon: tarZ0,tarZ1,zDiff for AA AOE weapons;
      SDWORD                  tarZ0,tarZ1,zDiff;
      // EvilGuru: Data about the effect to be shown
      EFFECT_TYPE     facing;
      iIMDShape       *imd;
      HIT_SIDE    impactSide = HIT_SIDE_FRONT;

      CHECK_PROJECTILE(psObj);

      psStats = psObj->psWStats;
      ASSERT( psStats != NULL,
            "proj_ImpactFunc: Invalid weapon stats pointer" );

      // Get if we are facing or not
      facing = (psStats->facePlayer) ? EXPLOSION_TYPE_SPECIFIED : EXPLOSION_TYPE_NOT_FACING;

      // note the attacker if any
      g_pProjLastAttacker = psObj->psSource;

      /* play impact audio */
      if (gfxVisible(psObj))
      {
            if (psStats->iAudioImpactID == NO_SOUND)
            {
                  /* play richochet if MG */
                  if (psObj->psDest != NULL && psObj->psWStats->weaponSubClass == WSC_MGUN
                   && ONEINTHREE)
                  {
                        iAudioImpactID = ID_SOUND_RICOCHET_1 + (rand() % 3);
                        audio_PlayStaticTrack(psObj->psDest->pos.x, psObj->psDest->pos.y, iAudioImpactID);
                  }
            }
            else
            {
                  if (psObj->psDest == NULL)
                  {
                        audio_PlayStaticTrack(psObj->tarX, psObj->tarY, psStats->iAudioImpactID);
                  }
                  else
                  {
                        audio_PlayStaticTrack(psObj->psDest->pos.x, psObj->psDest->pos.y, psStats->iAudioImpactID);
                  }
            }

            /* Shouldn't need to do this check but the stats aren't all at a value yet... */ // FIXME
            if (psStats->incenRadius && psStats->incenTime)
            {
                  position.x = psObj->tarX;
                  position.z = psObj->tarY;
                  position.y = map_Height(position.x, position.z);
                  effectGiveAuxVar(psStats->incenRadius);
                  effectGiveAuxVarSec(psStats->incenTime);
                  addEffect(&position, EFFECT_FIRE, FIRE_TYPE_LOCALISED, false, NULL, 0);
            }

            // may want to add both a fire effect and the las sat effect
            if (psStats->weaponSubClass == WSC_LAS_SAT)
            {
                  position.x = psObj->tarX;
                  position.z = psObj->tarY;
                  position.y = map_Height(position.x, position.z);
                  addEffect(&position, EFFECT_SAT_LASER, SAT_LASER_STANDARD, false, NULL, 0);
                  if (clipXY(psObj->tarX, psObj->tarY))
                  {
                        shakeStart();
                  }
            }
      }

      // Set the effects position and radius
      position.x = psObj->pos.x;
      position.z = psObj->pos.y;
      position.y = psObj->pos.z;//map_Height(psObj->pos.x, psObj->pos.y) + 24;
      scatter.x = psStats->radius;
      scatter.y = 0;
      scatter.z = psStats->radius;

      // If the projectile missed its target (or the target died)
      if (psObj->psDest == NULL)
      {
            if (gfxVisible(psObj))
            {
                  // The graphic to show depends on if we hit water or not
                  if (terrainType(mapTile(map_coord(psObj->pos.x), map_coord(psObj->pos.y))) == TER_WATER)
                  {
                        imd = psStats->pWaterHitGraphic;
                  }
                  // We did not hit water, the regular miss graphic will do the trick
                  else
                  {
                        imd = psStats->pTargetMissGraphic;
                  }

                  addMultiEffect(&position, &scatter, EFFECT_EXPLOSION, facing, true, imd, psStats->numExplosions, psStats->lightWorld, psStats->effectSize);

                  // If the target was a VTOL hit in the air add smoke
                  if (psObj->airTarget
                   && (psStats->surfaceToAir & SHOOT_IN_AIR)
                   && !(psStats->surfaceToAir & SHOOT_ON_GROUND))
                  {
                        addMultiEffect(&position, &scatter, EFFECT_SMOKE, SMOKE_TYPE_DRIFTING, false, NULL, 3, 0, 0);
                  }
            }
      }
      // The projectile hit its intended target
      else
      {
            CHECK_OBJECT(psObj->psDest);

            if (psObj->psDest->type == OBJ_FEATURE
             && ((FEATURE *)psObj->psDest)->psStats->damageable == 0)
            {
                  debug(LOG_NEVER, "proj_ImpactFunc: trying to damage non-damageable target,projectile removed");
                  psObj->died = gameTime;
                  return;
            }

            if (gfxVisible(psObj))
            {
                  // If we hit a VTOL with an AA gun use the miss graphic and add some smoke
                  if (psObj->airTarget
                   && (psStats->surfaceToAir & SHOOT_IN_AIR)
                   && !(psStats->surfaceToAir & SHOOT_ON_GROUND)
                   && psStats->weaponSubClass == WSC_AAGUN)
                  {
                        imd = psStats->pTargetMissGraphic;
                        addMultiEffect(&position, &scatter, EFFECT_SMOKE, SMOKE_TYPE_DRIFTING, false, NULL, 3, 0, 0);
                  }
                  // Otherwise we just hit it plain and simple
                  else
                  {
                        imd = psStats->pTargetHitGraphic;
                  }

                  addMultiEffect(&position, &scatter, EFFECT_EXPLOSION, facing, true, imd, psStats->numExplosions, psStats->lightWorld, psStats->effectSize);
            }

            // Check for electronic warfare damage where we know the subclass and source
            if (proj_Direct(psStats)
             && psStats->weaponSubClass == WSC_ELECTRONIC
             && psObj->psSource)
            {
                  // If we did enough `damage' to capture the target
                  if (electronicDamage(psObj->psDest,
                                       calcDamage(weaponDamage(psStats, psObj->player), psStats->weaponEffect, psObj->psDest),
                                       psObj->player))
                  {
                        switch (psObj->psSource->type)
                        {
                              case OBJ_DROID:
                                    ((DROID *) psObj->psSource)->order = DORDER_NONE;
                                    actionDroid((DROID *) (psObj->psSource), DACTION_NONE);
                                    break;

                              case OBJ_STRUCTURE:
                                    ((STRUCTURE *) psObj->psSource)->psTarget[0] = NULL;
                                    break;

                              // This is only here to prevent the compiler from producing
                              // warnings for unhandled enumeration values
                              default:
                                    break;
                        }
                  }
            }
            // Else it is just a regular weapon (direct or indirect)
            else
            {
                  // Calculate the damage the weapon does to its target
                  damage = calcDamage(weaponDamage(psStats, psObj->player), psStats->weaponEffect, psObj->psDest);

                  // If we are in a multi-player game and the attacker is our responsibility
                  if (bMultiPlayer && psObj->psSource && myResponsibility(psObj->psSource->player))
                  {
                        updateMultiStatsDamage(psObj->psSource->player, psObj->psDest->player, damage);
                  }

                  debug(LOG_NEVER, "Damage to object %d, player %d\n",
                        psObj->psDest->id, psObj->psDest->player);

                  // If the target is a droid work out the side of it we hit
                  if (psObj->psDest->type == OBJ_DROID)
                  {
                        // For indirect weapons (e.g. artillery) just assume the side as HIT_SIDE_TOP
                        impactSide = proj_Direct(psStats) ? getHitSide(psObj, psObj->psDest) : HIT_SIDE_TOP;
                  }

                  // Damage the object
                  relativeDamage = objectDamage(psObj->psDest,damage , psStats->weaponClass,psStats->weaponSubClass, impactSide);

                  proj_UpdateKills(psObj, relativeDamage);

                  if (relativeDamage >= 0)      // So long as the target wasn't killed
                  {
                        setProjectileDamaged(psObj, psObj->psDest);
                  }
            }
      }

      // If the projectile does no splash damage and does not set fire to things
      if ((psStats->radius == 0) && (psStats->incenTime == 0) )
      {
            psObj->died = gameTime;
            return;
      }

      if (psStats->radius != 0)
      {
            /* An area effect bullet */
            psObj->state = PROJ_POSTIMPACT;

            /* Note when it exploded for the explosion effect */
            psObj->born = gameTime;

            /* Work out the bounding box for the blast radius */
            tarX0 = (SDWORD)psObj->pos.x - (SDWORD)psStats->radius;
            tarY0 = (SDWORD)psObj->pos.y - (SDWORD)psStats->radius;
            tarX1 = (SDWORD)psObj->pos.x + (SDWORD)psStats->radius;
            tarY1 = (SDWORD)psObj->pos.y + (SDWORD)psStats->radius;
            /* Watermelon:height bounding box  for airborne units*/
            tarZ0 = (SDWORD)psObj->pos.z - (SDWORD)psStats->radius;
            tarZ1 = (SDWORD)psObj->pos.z + (SDWORD)psStats->radius;

            /* Store the radius cubed */
            radCubed = psStats->radius * psStats->radius * psStats->radius;

            for (i = 0; i < MAX_PLAYERS; i++)
            {
                  for (psCurrD = apsDroidLists[i]; psCurrD; psCurrD = psNextD)
                  {
                        /* have to store the next pointer as psCurrD could be destroyed */
                        psNextD = psCurrD->psNext;

                        /* see if psCurrD is hit (don't hit main target twice) */
                        if (((BASE_OBJECT *)psCurrD != psObj->psDest) &&
                              ((SDWORD)psCurrD->pos.x >= tarX0) &&
                              ((SDWORD)psCurrD->pos.x <= tarX1) &&
                              ((SDWORD)psCurrD->pos.y >= tarY0) &&
                              ((SDWORD)psCurrD->pos.y <= tarY1) &&
                              ((SDWORD)psCurrD->pos.z >= tarZ0) &&
                              ((SDWORD)psCurrD->pos.z <= tarZ1))
                        {
                              /* Within the bounding box, now check the radius */
                              xDiff = psCurrD->pos.x - psObj->pos.x;
                              yDiff = psCurrD->pos.y - psObj->pos.y;
                              zDiff = psCurrD->pos.z - psObj->pos.z;
                              if ((xDiff*xDiff + yDiff*yDiff + zDiff*zDiff) <= radCubed)
                              {
                                    HIT_ROLL(dice);
                                    if (dice < weaponRadiusHit(psStats, psObj->player))
                                    {
                                          debug(LOG_NEVER, "Damage to object %d, player %d\n",
                                                      psCurrD->id, psCurrD->player);

                                          damage = calcDamage(
                                                            weaponRadDamage(psStats, psObj->player),
                                                            psStats->weaponEffect, (BASE_OBJECT *)psCurrD);
                                          if (bMultiPlayer)
                                          {
                                                if (psObj->psSource && myResponsibility(psObj->psSource->player))
                                                {
                                                      updateMultiStatsDamage(psObj->psSource->player, psCurrD->player, damage);
                                                }
                                                turnOffMultiMsg(true);
                                          }

                                          //Watermelon:uses a slightly different check for angle,
                                          // since fragment of a project is from the explosion spot not from the projectile start position
                                          impactSide = getHitSide(psObj, (BASE_OBJECT *)psCurrD);

                                          relativeDamage = droidDamage(psCurrD, damage, psStats->weaponClass, psStats->weaponSubClass, impactSide);

                                          turnOffMultiMsg(false); // multiplay msgs back on.

                                          proj_UpdateKills(psObj, relativeDamage);
                                    }
                              }
                        }
                  }
                  if (!psObj->airTarget)
                  {
                        for (psCurrS = apsStructLists[i]; psCurrS; psCurrS = psNextS)
                        {
                              /* have to store the next pointer as psCurrD could be destroyed */
                              psNextS = psCurrS->psNext;

                              /* see if psCurrS is hit (don't hit main target twice) */
                              if (((BASE_OBJECT *)psCurrS != psObj->psDest) &&
                                    ((SDWORD)psCurrS->pos.x >= tarX0) &&
                                    ((SDWORD)psCurrS->pos.x <= tarX1) &&
                                    ((SDWORD)psCurrS->pos.y >= tarY0) &&
                                    ((SDWORD)psCurrS->pos.y <= tarY1))
                              {
                                    /* Within the bounding box, now check the radius */
                                    xDiff = psCurrS->pos.x - psObj->pos.x;
                                    yDiff = psCurrS->pos.y - psObj->pos.y;
                                    if ((xDiff*xDiff + yDiff*yDiff) <= radCubed)
                                    {
                                          HIT_ROLL(dice);
                                          if (dice < weaponRadiusHit(psStats, psObj->player))
                                          {
                                                damage = calcDamage(weaponRadDamage(psStats, psObj->player),
                                                                    psStats->weaponEffect,
                                                                    (BASE_OBJECT *)psCurrS);

                                                if (bMultiPlayer)
                                                {
                                                      if (psObj->psSource && myResponsibility(psObj->psSource->player))
                                                      {
                                                            updateMultiStatsDamage(psObj->psSource->player, psCurrS->player,damage);
                                                      }
                                                }

                                                //Watermelon:uses a slightly different check for angle,
                                                // since fragment of a project is from the explosion spot not from the projectile start position
                                                impactSide = getHitSide(psObj, (BASE_OBJECT *)psCurrS);

                                                relativeDamage = structureDamage(psCurrS,
                                                                                damage,
                                                                                psStats->weaponClass,
                                                                                psStats->weaponSubClass, impactSide);

                                                proj_UpdateKills(psObj, relativeDamage);
                                          }
                                    }
                              }
                              // Missed by old method, but maybe in landed within the building's footprint(baseplate)
                              else if(ptInStructure(psCurrS,psObj->pos.x, psObj->pos.y) && (BASE_OBJECT*)psCurrS != psObj->psDest)
                              {
                                    damage = NOMINAL_DAMAGE;

                                    if(bMultiPlayer)
                                    {
                                          if(psObj->psSource && myResponsibility(psObj->psSource->player))
                                          {
                                                updateMultiStatsDamage(psObj->psSource->player, psCurrS->player,damage);
                                          }
                                    }

                                    relativeDamage = structureDamage(psCurrS,
                                                                    damage,
                                                                    psStats->weaponClass,
                                                                    psStats->weaponSubClass, impactSide);

                                    proj_UpdateKills(psObj, relativeDamage);
                              }
                        }
                  }
            }

            for (psCurrF = apsFeatureLists[0]; psCurrF; psCurrF = psNextF)
            {
                  /* have to store the next pointer as psCurrD could be destroyed */
                  psNextF = psCurrF->psNext;

                  //ignore features that are not damageable
                  if(!psCurrF->psStats->damageable)
                  {
                        continue;
                  }
                  /* see if psCurrS is hit (don't hit main target twice) */
                  if (((BASE_OBJECT *)psCurrF != psObj->psDest) &&
                        ((SDWORD)psCurrF->pos.x >= tarX0) &&
                        ((SDWORD)psCurrF->pos.x <= tarX1) &&
                        ((SDWORD)psCurrF->pos.y >= tarY0) &&
                        ((SDWORD)psCurrF->pos.y <= tarY1))
                  {
                        /* Within the bounding box, now check the radius */
                        xDiff = psCurrF->pos.x - psObj->pos.x;
                        yDiff = psCurrF->pos.y - psObj->pos.y;
                        if ((xDiff*xDiff + yDiff*yDiff) <= radCubed)
                        {
                              HIT_ROLL(dice);
                              if (dice < weaponRadiusHit(psStats, psObj->player))
                              {
                                    debug(LOG_NEVER, "Damage to object %d, player %d\n",
                                                psCurrF->id, psCurrF->player);

                                    // Watermelon:uses a slightly different check for angle,
                                    // since fragment of a project is from the explosion spot not from the projectile start position
                                    impactSide = getHitSide(psObj, (BASE_OBJECT *)psCurrF);

                                    relativeDamage = featureDamage(psCurrF,
                                                                  calcDamage(weaponRadDamage(psStats, psObj->player),
                                                                             psStats->weaponEffect,
                                                                             (BASE_OBJECT *)psCurrF),
                                                                  psStats->weaponClass,
                                                                  psStats->weaponSubClass, impactSide);

                                    proj_UpdateKills(psObj, relativeDamage);
                              }
                        }
                  }
            }
      }

      if (psStats->incenTime != 0)
      {
            /* Incendiary round */
            /* Incendiary damage gets done in the bullet update routine */
            /* Just note when the incendiary started burning            */
            psObj->state = PROJ_POSTIMPACT;
            psObj->born = gameTime;
      }
      /* Something was blown up */
}

/***************************************************************************/

static void proj_PostImpactFunc( PROJECTILE *psObj )
{
      WEAPON_STATS      *psStats;
      SDWORD                  i, age;
      FIRE_BOX          flame;

      CHECK_PROJECTILE(psObj);

      psStats = psObj->psWStats;
      ASSERT( psStats != NULL,
            "proj_PostImpactFunc: Invalid weapon stats pointer" );

      age = (SDWORD)gameTime - (SDWORD)psObj->born;

      /* Time to finish postimpact effect? */
      if (age > (SDWORD)psStats->radiusLife && age > (SDWORD)psStats->incenTime)
      {
            psObj->died = gameTime;
            return;
      }

      /* Burning effect */
      if (psStats->incenTime > 0)
      {
            /* See if anything is in the fire and burn it */

            /* Calculate the fire's bounding box */
            flame.x1 = (SWORD)(psObj->pos.x - psStats->incenRadius);
            flame.y1 = (SWORD)(psObj->pos.y - psStats->incenRadius);
            flame.x2 = (SWORD)(psObj->pos.x + psStats->incenRadius);
            flame.y2 = (SWORD)(psObj->pos.y + psStats->incenRadius);
            flame.rad = (SWORD)(psStats->incenRadius*psStats->incenRadius);

            for (i=0; i<MAX_PLAYERS; i++)
            {
                  /* Don't damage your own droids - unrealistic, but better */
                  if(i!=psObj->player)
                  {
                        proj_checkBurnDamage((BASE_OBJECT*)apsDroidLists[i], psObj, &flame);
                        proj_checkBurnDamage((BASE_OBJECT*)apsStructLists[i], psObj, &flame);
                  }
            }
      }
}

/***************************************************************************/

static void proj_Update(PROJECTILE *psObj)
{
      CHECK_PROJECTILE(psObj);

      /* See if any of the stored objects have died
       * since the projectile was created
       */
      if (psObj->psSource && psObj->psSource->died)
      {
            setProjectileSource(psObj, NULL);
      }
      if (psObj->psDest && psObj->psDest->died)
      {
            setProjectileDestination(psObj, NULL);
      }
      if (psObj->psDamaged && psObj->psDamaged->died)
      {
            setProjectileDamaged(psObj, NULL);
      }

      // This extra check fixes a crash in cam2, mission1
      if (worldOnMap(psObj->pos.x, psObj->pos.y) == false)
      {
            psObj->died = true;
            return;
      }

      projGetNaybors((PROJECTILE *)psObj);

      switch (psObj->state)
      {
            case PROJ_INFLIGHT:
                  (psObj->pInFlightFunc) ( psObj );
                  break;

            case PROJ_IMPACT:
                  proj_ImpactFunc( psObj );
                  break;

            case PROJ_POSTIMPACT:
                  proj_PostImpactFunc( psObj );
                  break;
      }
}

/***************************************************************************/

// iterate through all projectiles and update their status
01738 void proj_UpdateAll()
{
      PROJECTILE  *psObj, *psPrev;

      for (psObj = psProjectileList; psObj != NULL; psObj = psObj->psNext)
      {
            proj_Update( psObj );
      }

      // Now delete any dead projectiles
      psObj = psProjectileList;

      // is the first node dead?
      while (psObj && psObj == psProjectileList && psObj->died)
      {
            psProjectileList = psObj->psNext;
            proj_Free(psObj);
            psObj = psProjectileList;
      }

      // first object is now either NULL or not dead, so we have time to set this below
      psPrev = NULL;

      // are any in the list dead?
      while (psObj)
      {
            if (psObj->died)
            {
                  psPrev->psNext = psObj->psNext;
                  proj_Free(psObj);
                  psObj = psPrev->psNext;
            } else {
                  psPrev = psObj;
                  psObj = psObj->psNext;
            }
      }
}

/***************************************************************************/

static void proj_checkBurnDamage( BASE_OBJECT *apsList, PROJECTILE *psProj, FIRE_BOX *pFireBox )
{
      BASE_OBJECT       *psCurr, *psNext;
      SDWORD                  xDiff,yDiff;
      WEAPON_STATS      *psStats;
      UDWORD                  damageSoFar;
      SDWORD                  damageToDo;
      float             relativeDamage;

      CHECK_PROJECTILE(psProj);

      // note the attacker if any
      g_pProjLastAttacker = psProj->psSource;

      psStats = psProj->psWStats;
      for (psCurr = apsList; psCurr; psCurr = psNext)
      {
            /* have to store the next pointer as psCurr could be destroyed */
            psNext = psCurr->psNext;

            if ((psCurr->type == OBJ_DROID) &&
                  isVtolDroid((DROID*)psCurr) &&
                  ((DROID *)psCurr)->sMove.Status != MOVEINACTIVE)
            {
                  // can't set flying vtols on fire
                  continue;
            }

            /* see if psCurr is hit (don't hit main target twice) */
            if (((SDWORD)psCurr->pos.x >= pFireBox->x1) &&
                  ((SDWORD)psCurr->pos.x <= pFireBox->x2) &&
                  ((SDWORD)psCurr->pos.y >= pFireBox->y1) &&
                  ((SDWORD)psCurr->pos.y <= pFireBox->y2))
            {
                  /* Within the bounding box, now check the radius */
                  xDiff = psCurr->pos.x - psProj->pos.x;
                  yDiff = psCurr->pos.y - psProj->pos.y;
                  if ((xDiff*xDiff + yDiff*yDiff) <= pFireBox->rad)
                  {
                        /* The object is in the fire */
                        psCurr->inFire |= IN_FIRE;

                        if ( (psCurr->burnStart == 0) ||
                               (psCurr->inFire & BURNING) )
                        {
                              /* This is the first turn the object is in the fire */
                              psCurr->burnStart = gameTime;
                              psCurr->burnDamage = 0;
                        }
                        else
                        {
                              /* Calculate how much damage should have
                                 been done up till now */
                              damageSoFar = (gameTime - psCurr->burnStart)
                                                  //* psStats->incenDamage
                                                  * weaponIncenDamage(psStats,psProj->player)
                                                  / GAME_TICKS_PER_SEC;
                              damageToDo = (SDWORD)damageSoFar
                                                 - (SDWORD)psCurr->burnDamage;
                              if (damageToDo > 0)
                              {
                                    debug(LOG_NEVER, "Burn damage of %d to object %d, player %d\n",
                                                damageToDo, psCurr->id, psCurr->player);

                                    //Watermelon:just assume the burn damage is from FRONT
                                    relativeDamage = objectDamage(psCurr, damageToDo, psStats->weaponClass,psStats->weaponSubClass, 0);

                                    psCurr->burnDamage += damageToDo;

                                    proj_UpdateKills(psProj, relativeDamage);
                              }
                              /* The damage could be negative if the object
                                 is being burn't by another fire
                                 with a higher burn damage */
                        }
                  }
            }
      }
}

/***************************************************************************/

// return whether a weapon is direct or indirect
01861 bool proj_Direct(const WEAPON_STATS* psStats)
{
      ASSERT(psStats != NULL, "proj_Direct: called with NULL weapon!");
      if (!psStats)
      {
            return true; // arbitrary value in no-debug case
      }
      ASSERT(psStats->movementModel < NUM_MOVEMENT_MODEL, "proj_Direct: invalid weapon stat");

      switch (psStats->movementModel)
      {
      case MM_DIRECT:
      case MM_HOMINGDIRECT:
      case MM_ERRATICDIRECT:
      case MM_SWEEP:
            return true;
            break;
      case MM_INDIRECT:
      case MM_HOMINGINDIRECT:
            return false;
            break;
      case NUM_MOVEMENT_MODEL:
      case INVALID_MOVEMENT:
            break; // error checking in assert above; this is for no-debug case
      }

      return true; // just to satisfy compiler
}

/***************************************************************************/

// return the maximum range for a weapon
01893 SDWORD proj_GetLongRange(const WEAPON_STATS* psStats)
{
      return psStats->longRange;
}


/***************************************************************************/
static UDWORD     establishTargetRadius(BASE_OBJECT *psTarget)
{
      UDWORD            radius;
      STRUCTURE   *psStructure;
      FEATURE           *psFeat;

      CHECK_OBJECT(psTarget);
      radius = 0;

      switch(psTarget->type)
      {
            case OBJ_DROID:
                  switch(((DROID *)psTarget)->droidType)
                  {
                        case DROID_WEAPON:
                        case DROID_SENSOR:
                        case DROID_ECM:
                        case DROID_CONSTRUCT:
                        case DROID_COMMAND:
                        case DROID_REPAIR:
                        case DROID_PERSON:
                        case DROID_CYBORG:
                        case DROID_CYBORG_CONSTRUCT:
                        case DROID_CYBORG_REPAIR:
                        case DROID_CYBORG_SUPER:
                              //Watermelon:'hitbox' size is now based on imd size
                              radius = abs(psTarget->sDisplay.imd->radius) * 2;
                              break;
                        case DROID_DEFAULT:
                        case DROID_TRANSPORTER:
                        default:
                              radius = TILE_UNITS/4;  // how will we arrive at this?
                  }
                  break;
            case OBJ_STRUCTURE:
                  psStructure = (STRUCTURE*)psTarget;
                  radius = (MAX(psStructure->pStructureType->baseBreadth, psStructure->pStructureType->baseWidth) * TILE_UNITS) / 2;
                  break;
            case OBJ_FEATURE:
//                radius = TILE_UNITS/4;  // how will we arrive at this?
                  psFeat = (FEATURE *)psTarget;
                  radius = (MAX(psFeat->psStats->baseBreadth,psFeat->psStats->baseWidth) * TILE_UNITS) / 2;
                  break;
            case OBJ_PROJECTILE:
                  //Watermelon 1/2 radius of a droid?
                  radius = TILE_UNITS/8;
            default:
                  break;
      }

      return(radius);
}
/***************************************************************************/

/*the damage depends on the weapon effect and the target propulsion type or
structure strength*/
UDWORD      calcDamage(UDWORD baseDamage, WEAPON_EFFECT weaponEffect, BASE_OBJECT *psTarget)
{
      UDWORD      damage;

      if (psTarget->type == OBJ_STRUCTURE)
      {
            damage = baseDamage * asStructStrengthModifier[weaponEffect][((
                  STRUCTURE *)psTarget)->pStructureType->strength] / 100;
      }
      else if (psTarget->type == OBJ_DROID)
      {
            damage = baseDamage * asWeaponModifier[weaponEffect][(
                  asPropulsionStats + ((DROID *)psTarget)->asBits[COMP_PROPULSION].
                  nStat)->propulsionType] / 100;
      }
      // Default value
      else
      {
            damage = baseDamage;
      }
      
    // A little fail safe!
    if (damage == 0 && baseDamage != 0)
    {
        damage = 1;
    }

      return damage;
}

/*
 * A quick explanation about hown this function works:
 *  - It returns an integer between 0 and 100 (see note for exceptions);
 *  - this represents the amount of damage inflicted on the droid by the weapon
 *    in relation to its original health.
 *  - e.g. If 100 points of (*actual*) damage were done to a unit who started
 *    off (when first produced) with 400 points then .25 would be returned.
 *  - If the actual damage done to a unit is greater than its remaining points
 *    then the actual damage is clipped: so if we did 200 actual points of
 *    damage to a cyborg with 150 points left the actual damage would be taken
 *    as 150.
 *  - Should sufficient damage be done to destroy/kill a unit then the value is
 *    multiplied by -1, resulting in a negative number.
 */
static float objectDamage(BASE_OBJECT *psObj, UDWORD damage, UDWORD weaponClass,UDWORD weaponSubClass, HIT_SIDE impactSide)
{
      switch (psObj->type)
      {
            case OBJ_DROID:
                  return droidDamage((DROID *)psObj, damage, weaponClass,weaponSubClass, impactSide);
                  break;

            case OBJ_STRUCTURE:
                  return structureDamage((STRUCTURE *)psObj, damage, weaponClass, weaponSubClass, impactSide);
                  break;

            case OBJ_FEATURE:
                  return featureDamage((FEATURE *)psObj, damage, weaponClass, weaponSubClass, impactSide);
                  break;

            case OBJ_PROJECTILE:
                  ASSERT(!"invalid object type: bullet", "objectDamage: invalid object type: OBJ_PROJECTILE");
                  break;

            case OBJ_TARGET:
                  ASSERT(!"invalid object type: target", "objectDamage: invalid object type: OBJ_TARGET");
                  break;

            default:
                  ASSERT(!"unknown object type", "objectDamage: unknown object type");
      }

      return 0;
}

/**
 * This function will calculate which side of the droid psTarget the projectile
 * psObj hit. Although it is possible to extract the target from psObj it is
 * only the `direct' target of the projectile. Since impact sides also apply for
 * any splash damage a projectile might do the exact target is needed.
 */
static HIT_SIDE getHitSide(PROJECTILE *psObj, BASE_OBJECT *psTarget)
{
      int deltaX, deltaY;
      int impactAngle;

      // If we hit the top of the droid
      if (psObj->altChange > 300)
      {
            return HIT_SIDE_TOP;
      }
      // If the height difference between us and the target is > 50
      else if (psObj->pos.z < (psTarget->pos.z - 50))
      {
            return HIT_SIDE_BOTTOM;
      }
      // We hit an actual `side'
      else
      {
            deltaX = psObj->startX - psTarget->pos.x;
            deltaY = psObj->startY - psTarget->pos.y;

            /*
             * Work out the impact angle. It is easiest to understand if you
             * model the target droid as a circle, divided up into 360 pieces.
             */
            impactAngle = abs(psTarget->direction - (180 * atan2f(deltaX, deltaY) / M_PI));

            impactAngle = wrap(impactAngle, 360);

            // Use the impact angle to work out the side hit
            // Right
            if (impactAngle > 45 && impactAngle < 135)
                  return HIT_SIDE_RIGHT;
            // Rear
            else if (impactAngle >= 135 && impactAngle <= 225)
                  return HIT_SIDE_REAR;
            // Left
            else if (impactAngle > 225 && impactAngle < 315)
                  return HIT_SIDE_LEFT;
            // Front - default
            else //if (impactAngle <= 45 || impactAngle >= 315)
                  return HIT_SIDE_FRONT;
      }
}

/* Returns true if an object has just been hit by an electronic warfare weapon*/
static BOOL justBeenHitByEW(BASE_OBJECT *psObj)
{
DROID       *psDroid;
FEATURE           *psFeature;
STRUCTURE   *psStructure;

      if(gamePaused())
      {
            return(false);    // Don't shake when paused...!
      }

      switch(psObj->type)
      {
            case OBJ_DROID:
                  psDroid = (DROID*)psObj;
                  if ((gameTime - psDroid->timeLastHit) < ELEC_DAMAGE_DURATION
                   && psDroid->lastHitWeapon == WSC_ELECTRONIC)
                  {
                        return(true);
                  }
                  break;

            case OBJ_FEATURE:
                  psFeature = (FEATURE*)psObj;
                  if ((gameTime - psFeature->timeLastHit) < ELEC_DAMAGE_DURATION)
                  {
                        return(true);
                  }
                  break;

            case OBJ_STRUCTURE:
                  psStructure = (STRUCTURE*)psObj;
                  if ((gameTime - psStructure->timeLastHit) < ELEC_DAMAGE_DURATION
                   && psStructure->lastHitWeapon == WSC_ELECTRONIC)
                  {
                        return true;
                  }
                  break;

            case OBJ_PROJECTILE:
                  ASSERT(!"invalid object type: bullet", "justBeenHitByEW: invalid object type: OBJ_PROJECTILE");
                  abort();
                  break;

            case OBJ_TARGET:
                  ASSERT(!"invalid object type: target", "justBeenHitByEW: invalid object type: OBJ_TARGET");
                  abort();
                  break;

            default:
                  ASSERT(!"unknown object type", "justBeenHitByEW: unknown object type");
                  abort();
      }

      return false;
}

void  objectShimmy(BASE_OBJECT *psObj)
{
      if(justBeenHitByEW(psObj))
      {
            iV_MatrixRotateX(SKY_SHIMMY);
            iV_MatrixRotateY(SKY_SHIMMY);
            iV_MatrixRotateZ(SKY_SHIMMY);
            if(psObj->type == OBJ_DROID)
            {
                  iV_TRANSLATE(1-rand()%3,0,1-rand()%3);
            }
      }
}

// Watermelon:addProjNaybor ripped from droid.c
/* Add a new object to the projectile naybor list */
static void addProjNaybor(BASE_OBJECT *psObj, UDWORD distSqr)
{
      UDWORD      pos;

      if (numProjNaybors >= MAX_NAYBORS)
      {
//          DBPRINTF(("Naybor list maxed out for id %d\n", psObj->id));
            return;
      }
      else if (numProjNaybors == 0)
      {
            // No objects in the list
            asProjNaybors[0].psObj = psObj;
            asProjNaybors[0].distSqr = distSqr;
            numProjNaybors++;
      }
      else if (distSqr >= asProjNaybors[numProjNaybors-1].distSqr)
      {
            // Simple case - this is the most distant object
            asProjNaybors[numProjNaybors].psObj = psObj;
            asProjNaybors[numProjNaybors].distSqr = distSqr;
            numProjNaybors++;
      }
      else
      {
            // Move all the objects further away up the list
            pos = numProjNaybors;
            while (pos > 0 && asProjNaybors[pos - 1].distSqr > distSqr)
            {
                  memcpy(asProjNaybors + pos, asProjNaybors + (pos - 1), sizeof(PROJ_NAYBOR_INFO));
                  pos --;
            }

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

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

//Watermelon: projGetNaybors ripped from droid.c
/* Find all the objects close to the projectile */
static void projGetNaybors(PROJECTILE *psObj)
{
      SDWORD            xdiff, ydiff;
      UDWORD            dx,dy, distSqr;
      //Watermelon:renamed to psTempObj from psObj
      BASE_OBJECT *psTempObj;

      CHECK_PROJECTILE(psObj);

// Ensure only called max of once per droid per game cycle.
      if(CurrentProjNaybors == (BASE_OBJECT *)psObj && projnayborTime == gameTime) {
            return;
      }
      CurrentProjNaybors = (BASE_OBJECT *)psObj;
      projnayborTime = gameTime;

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

      // search for naybor objects
      dx = ((BASE_OBJECT *)psObj)->pos.x;
      dy = ((BASE_OBJECT *)psObj)->pos.y;

      gridStartIterate((SDWORD)dx, (SDWORD)dy);
      for (psTempObj = gridIterate(); psTempObj != NULL; psTempObj = gridIterate())
      {
            if (psTempObj != (BASE_OBJECT *)psObj && !psTempObj->died)
            {
                  // see if an object is in NAYBOR_RANGE
                  xdiff = dx - (SDWORD)psTempObj->pos.x;
                  if (xdiff < 0)
                  {
                        xdiff = -xdiff;
                  }
                  if (xdiff > PROJ_NAYBOR_RANGE)
                  {
                        continue;
                  }

                  ydiff = dy - (SDWORD)psTempObj->pos.y;
                  if (ydiff < 0)
                  {
                        ydiff = -ydiff;
                  }
                  if (ydiff > PROJ_NAYBOR_RANGE)
                  {
                        continue;
                  }

                  distSqr = xdiff*xdiff + ydiff*ydiff;
                  if (distSqr > PROJ_NAYBOR_RANGE*PROJ_NAYBOR_RANGE)
                  {
                        continue;
                  }

                  addProjNaybor(psTempObj, distSqr);
            }
      }
}

static UDWORD     establishTargetHeight(BASE_OBJECT *psTarget)
{
      UDWORD            height;
      UDWORD            utilityHeight = 0, yMax = 0, yMin = 0; // Temporaries for addition of utility's height to total height
      DROID       *psDroid;
      STRUCTURE_STATS         *psStructureStats;

      if (psTarget == NULL)
      {
            return 0;
      }
      CHECK_OBJECT(psTarget);

      switch(psTarget->type)
      {
            case OBJ_DROID:
                  psDroid = (DROID*)psTarget;
                  height = asBodyStats[psDroid->asBits[COMP_BODY].nStat].pIMD->max.y - asBodyStats[psDroid->asBits[COMP_BODY].nStat].pIMD->min.y;

                  // Don't do this for Barbarian Propulsions as they don't possess a turret (and thus have pIMD == NULL)
                  if (!strcmp(asPropulsionStats[psDroid->asBits[COMP_PROPULSION].nStat].pName, "BaBaProp") )
                  {
                        return height;
                  }

                  // Commanders don't have pIMD either
                  if (psDroid->droidType == DROID_COMMAND)
                        return height;

                  // VTOL's don't have pIMD either it seems...
                  if (isVtolDroid(psDroid))
                  {
                        return (height + VTOL_HITBOX_MODIFICATOR);
                  }

                  switch(psDroid->droidType)
                  {
                        case DROID_WEAPON:
                              if ( psDroid->numWeaps > 0 )
                              {
                                    yMax = (asWeaponStats[psDroid->asWeaps[0].nStat]).pIMD->max.y;
                                    yMin = (asWeaponStats[psDroid->asWeaps[0].nStat]).pIMD->min.y;
                              }
                              break;

                        case DROID_SENSOR:
                              yMax = (asSensorStats[psDroid->asBits[COMP_SENSOR].nStat]).pIMD->max.y;
                              yMin = (asSensorStats[psDroid->asBits[COMP_SENSOR].nStat]).pIMD->min.y;
                              break;

                        case DROID_ECM:
                              yMax = (asECMStats[psDroid->asBits[COMP_ECM].nStat]).pIMD->max.y;
                              yMin = (asECMStats[psDroid->asBits[COMP_ECM].nStat]).pIMD->min.y;
                              break;

                        case DROID_CONSTRUCT:
                              yMax = (asConstructStats[psDroid->asBits[COMP_CONSTRUCT].nStat]).pIMD->max.y;
                              yMin = (asConstructStats[psDroid->asBits[COMP_CONSTRUCT].nStat]).pIMD->min.y;
                              break;

                        case DROID_REPAIR:
                              yMax = (asRepairStats[psDroid->asBits[COMP_REPAIRUNIT].nStat]).pIMD->max.y;
                              yMin = (asRepairStats[psDroid->asBits[COMP_REPAIRUNIT].nStat]).pIMD->min.y;
                              break;

                        case DROID_PERSON:
                              //TODO:add person 'state'checks here(stand, knee, crouch, prone etc)
                        case DROID_CYBORG:
                        case DROID_CYBORG_CONSTRUCT:
                        case DROID_CYBORG_REPAIR:
                        case DROID_CYBORG_SUPER:
                        case DROID_DEFAULT:
                        case DROID_TRANSPORTER:
                        default:
                              break;
                  }

                  utilityHeight = (yMax + yMin)/2;
                  height += utilityHeight;

                  return height;

            case OBJ_STRUCTURE:
                  psStructureStats = ((STRUCTURE *)psTarget)->pStructureType;
                  return (psStructureStats->pIMD->max.y + psStructureStats->pIMD->min.y) / 2;
            case OBJ_FEATURE:
                  // Just use imd ymax+ymin
                  return (psTarget->sDisplay.imd->max.y + psTarget->sDisplay.imd->min.y) / 2;
            case OBJ_PROJECTILE:
                  // 16 for bullet
                  return 16;
            default:
                  return 0;
      }
}

Generated by  Doxygen 1.6.0   Back to index