/*=============================================================================
    Name    : AIShip.c
    Purpose : This AI layer controls where a ship's velocity vector should be
              and what heading the ship should face.  It determines this based
              on commands received (parameters passed), and on where enemy
              ships are, etc.
              Note that it is the responsibility of the AItrack layer below
              to actually cause the ship to follow the desired velocity vector
              and heading.

    Created 6/26/1997 by gshaw
    Copyright Relic Entertainment, Inc.  All rights reserved.
=============================================================================*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "types.h"
#include "vector.h"
#include "matrix.h"
#include "fastmath.h"
#include "debug.h"
#include "spaceobj.h"
#include "physics.h"
#include "aitrack.h"
#include "aiship.h"
#include "statscript.h"
#include "universe.h"
#include "ships.h"
#include "univupdate.h"
#include "collision.h"
#include "blobs.h"
#include "tactics.h"
#include "alliance.h"
#include "randy.h"
#include "ProfileTimers.h"

#ifdef gshaw
#define DEBUG_AISHIP    0
#else
#define DEBUG_AISHIP    0
#endif

#define PAD_AROUND_SMALLEST_SHIP    1

#define VELFACTOR 0

bool isCapitalShipStaticOrBig(ShipStaticInfo *shipstatic);

/*=============================================================================
    AIShip Stats (optional)
=============================================================================*/

#ifdef AISHIP_STATS
AIshipStats aishipStats = { 0,0,0,0,0,0.0f, 0, {0.0f, 0.0f, 0.0f } };
#endif

/*=============================================================================
    Data
=============================================================================*/

bool aishipTempDisableAvoiding = FALSE;

/*=============================================================================
    Private Function declarations:
=============================================================================*/

static void scriptAIShipPrecalculate(char *directory,char *field,void *dataToFillIn);
static void rowSetDetails(char *directory,char *field,void *dataToFillIn);

/*=============================================================================
    Tweakables:
=============================================================================*/

real32 VELOCITY_SCALE_FACTOR = 0.1f;
real32 URGENT_SCALE_FACTOR   = 1.0f;
real32 MISSILE_SCALE_FACTOR  = 1.0f;
real32 MISSILE_MIN_VELOCITY = 500.0f;
real32 MISSILE_USE_VELOCITYPRED_DISTANCE = 250.0f*250.0f;
//real32 MINE_MIN_VELOCITY = 75.0f;
real32 AVOID_OBJ_SCALE_FACTOR = (real32)1e-4;
real32 COMBAT_AVOID_OBJ_SCALE_FACTOR = (real32)1e-5;
real32 PROBE_AVOID_OBJ_SCALE_FACTOR = (real32)1e-5;
real32 AVOID_PANIC_FACTOR = 0.2f;
real32 AVOID_OBJ_PADDING_SCALE = 1.6f;
real32 AVOID_OBJ_PADDING_SCALE_BIG = 2.0f;
real32 AVOID_OBJ_ROW_PADDING_SCALE = 3.0f;
real32 AVOID_OBJ_FORMATION_PADDING_SCALE = 2.55f;

real32 AVOID_OBJ_MSHIP_MILPARADE_PADDING_SCALE = 1.55f;
real32 AVOID_OBJ_R2MSHIP_MILPARADE_PADDING_SCALE = 0.75f;

real32 AVOID_OBJ_OBSCUREDPOINT_SCALE = 1.55f;
real32 AVOID_SPREAD_OUT_DIST = 100.0f;
real32 CAR_TURN_POWER = 0.1f;
real32 FLYSHIP_HEADINGACCURACY = 0.999f;
real32 FLYSHIP_ATTACKACCURACY = 0.999f;
real32 FIGHTER_BANK = 0.99f;
real32 CORVETTE_BANK = 0.707f;
real32 FRIGATE_BANK = 0.5f;
real32 DESTROYER_BANK = 0.35f;
real32 MASSIVESHIP_BANK = 0.17f;
real32 NOSHIP_BANK = 0.01f;
real32 FRIGATE_TURNPITCH = DEG_TO_RAD(30.0f);
real32 DESTROYER_TURNPITCH = DEG_TO_RAD(25.0f);
real32 MASSIVESHIP_TURNPITCH = DEG_TO_RAD(20.0f);
real32 NOSHIP_TURNPITCH = DEG_TO_RAD(0.01f);
real32 FRIGATE_DESCENDPITCH = DEG_TO_RAD(20.0f);
real32 DESTROYER_DESCENDPITCH = DEG_TO_RAD(15.0f);
real32 MASSIVESHIP_DESCENDPITCH = DEG_TO_RAD(10.0f);
real32 NOSHIP_DESCENDPITCH = DEG_TO_RAD(0.01f);

real32 INTERCEPTORBREAK_TOLERANCE = 110.0f;
real32 INTERCEPTORBREAK_MINVELOCITY = -200.0f;
real32 INTERCEPTORKILL_MINVELOCITY = -200.0f;

real32 DESCEND_PITCH4_DIST = 0.25f;
real32 DESCEND_PITCH3_DIST = 0.50f;
real32 DESCEND_PITCH2_DIST = 0.75f;
real32 MIN_DIST_FOR_FANCY_DESCEND = 100.0f;
real32 MIN_ANGLE_FOR_FANCY_DESCEND = 0.577f;      // tan(30)

real32 MIN_DIST_TO_TRACK_HEADING = 20.0f;
real32 MAX_ANGLE_FOR_TRACK_HEADING = 2.747f;   // tan(70)

real32 ROW_GOING_MOSTLY_UPDOWN_ANGLE = 0.707f;
real32 AVOID_ROW_SHIP_BY_SCALE = 4.0f;
real32 AVOID_ROW_SHIP_BY_ADD = 200.0f;
real32 AVOID_ROW_R1MOTHERSHIP_BY = 1000.0f;
real32 ROW_GO_DOWN_INSTEAD_OF_UP_RATIO = 0.3f;

real32 PASS_RIGHT_FACTOR = 1.0f;
real32 USE_PASS_RIGHT_ANGLE = 0.97f;

real32 NEG_AVOID_MIN_VEL = -1.0f;
real32 AVOID_MIN_VEL = 1.0f;

real32 DONTUSEVELOCITYPRED_IFBELOW = 1000.0f;

bool DO_AVOID_OBJS = TRUE;


// pre-calculated constants:
real32 oneOverDESCEND_PITCH4_DIST;
real32 oneOverDESCEND_3_MINUS_4_DIST;
real32 oneOverDESCEND_2_MINUS_3_DIST;

scriptEntry AIShipTweaks[] =
{
    makeEntry(VELOCITY_SCALE_FACTOR,scriptSetReal32CB),
    makeEntry(URGENT_SCALE_FACTOR,scriptSetReal32CB),
    makeEntry(MISSILE_SCALE_FACTOR,scriptSetReal32CB),
    makeEntry(MISSILE_MIN_VELOCITY,scriptSetReal32CB),
    makeEntry(MISSILE_USE_VELOCITYPRED_DISTANCE,scriptSetReal32SqrCB),
    //makeEntry(MINE_MIN_VELOCITY,scriptSetReal32CB),
    makeEntry(AVOID_OBJ_SCALE_FACTOR,scriptSetReal32CB),
    makeEntry(COMBAT_AVOID_OBJ_SCALE_FACTOR,scriptSetReal32CB),
    makeEntry(PROBE_AVOID_OBJ_SCALE_FACTOR,scriptSetReal32CB),
    makeEntry(AVOID_OBJ_PADDING_SCALE,scriptSetReal32CB),
    makeEntry(AVOID_OBJ_PADDING_SCALE_BIG,scriptSetReal32CB),
    makeEntry(AVOID_OBJ_FORMATION_PADDING_SCALE,scriptSetReal32CB),
    makeEntry(AVOID_OBJ_MSHIP_MILPARADE_PADDING_SCALE,scriptSetReal32CB),
    makeEntry(AVOID_OBJ_R2MSHIP_MILPARADE_PADDING_SCALE,scriptSetReal32CB),
    makeEntry(AVOID_OBJ_OBSCUREDPOINT_SCALE,scriptSetReal32CB),
    makeEntry(AVOID_OBJ_ROW_PADDING_SCALE,scriptSetReal32CB),
    makeEntry(AVOID_PANIC_FACTOR,scriptSetReal32CB),
    makeEntry(AVOID_SPREAD_OUT_DIST,scriptSetReal32CB),
    makeEntry(CAR_TURN_POWER,scriptSetReal32CB),
    makeEntry(FLYSHIP_HEADINGACCURACY,scriptSetCosAngCB),
    makeEntry(FLYSHIP_ATTACKACCURACY,scriptSetCosAngCB),
    makeEntry(DO_AVOID_OBJS,scriptSetBool),
    makeEntry(CORVETTE_BANK,scriptSetSinAngCB),
    makeEntry(FRIGATE_BANK,scriptSetSinAngCB),
    makeEntry(DESTROYER_BANK,scriptSetSinAngCB),
    makeEntry(MASSIVESHIP_BANK,scriptSetSinAngCB),
    makeEntry(NOSHIP_BANK,scriptSetSinAngCB),
    makeEntry(FRIGATE_TURNPITCH,scriptSetAngCB),
    makeEntry(DESTROYER_TURNPITCH,scriptSetAngCB),
    makeEntry(MASSIVESHIP_TURNPITCH,scriptSetAngCB),
    makeEntry(NOSHIP_TURNPITCH,scriptSetAngCB),
    makeEntry(FRIGATE_DESCENDPITCH,scriptSetAngCB),
    makeEntry(DESTROYER_DESCENDPITCH,scriptSetAngCB),
    makeEntry(MASSIVESHIP_DESCENDPITCH,scriptSetAngCB),
    makeEntry(NOSHIP_DESCENDPITCH,scriptSetAngCB),
    makeEntry(DESCEND_PITCH4_DIST,scriptSetReal32CB),
    makeEntry(DESCEND_PITCH3_DIST,scriptSetReal32CB),
    makeEntry(DESCEND_PITCH2_DIST,scriptSetReal32CB),
    makeEntry(MIN_DIST_FOR_FANCY_DESCEND,scriptSetReal32CB),
    makeEntry(MIN_ANGLE_FOR_FANCY_DESCEND,scriptSetTanAngCB),
    makeEntry(MIN_DIST_TO_TRACK_HEADING,scriptSetReal32CB),
    makeEntry(MAX_ANGLE_FOR_TRACK_HEADING,scriptSetTanAngCB),
    makeEntry(INTERCEPTORBREAK_TOLERANCE,scriptSetReal32CB),
    makeEntry(INTERCEPTORBREAK_MINVELOCITY,scriptSetReal32CB),
    makeEntry(INTERCEPTORKILL_MINVELOCITY,scriptSetReal32CB),
    makeEntry(ROW_GOING_MOSTLY_UPDOWN_ANGLE,scriptSetCosAngCB),
    makeEntry(AVOID_ROW_SHIP_BY_SCALE,scriptSetReal32CB),
    makeEntry(AVOID_ROW_SHIP_BY_ADD,scriptSetReal32CB),
    makeEntry(AVOID_ROW_R1MOTHERSHIP_BY,scriptSetReal32CB),
    makeEntry(ROW_GO_DOWN_INSTEAD_OF_UP_RATIO,scriptSetReal32CB),
    makeEntry(PASS_RIGHT_FACTOR,scriptSetReal32CB),
    makeEntry(USE_PASS_RIGHT_ANGLE,scriptSetCosAngSqrCB),
    makeEntry(AVOID_MIN_VEL,scriptSetReal32CB),
    makeEntry(DONTUSEVELOCITYPRED_IFBELOW,scriptSetReal32CB),
    { "rowDetails", rowSetDetails, NULL },
    { "AISHIP_OTHER_CALCULATIONS", scriptAIShipPrecalculate, NULL },        // should go last
    endEntry
};

static void scriptAIShipPrecalculate(char *directory,char *field,void *dataToFillIn)
{
    oneOverDESCEND_PITCH4_DIST = 1.0f / DESCEND_PITCH4_DIST;
    oneOverDESCEND_3_MINUS_4_DIST = 1.0f / (DESCEND_PITCH3_DIST - DESCEND_PITCH4_DIST);
    oneOverDESCEND_2_MINUS_3_DIST = 1.0f / (DESCEND_PITCH2_DIST - DESCEND_PITCH3_DIST);
    dbgAssert(oneOverDESCEND_3_MINUS_4_DIST > 0.0f);
    dbgAssert(oneOverDESCEND_2_MINUS_3_DIST > 0.0f);
    NEG_AVOID_MIN_VEL = -AVOID_MIN_VEL;
}

#ifdef AISHIP_STATS
void aishipStatsInitFunc(Ship *ship)
{
    if (selSelected.numShips > 0 && (ship == selSelected.ShipPtr[0]))
    {
        memset(&aishipStats,0,sizeof(aishipStats));
        aishipStats.isValid = TRUE;
        aishipStats.isDisplayable = TRUE;
    }
}

void aishipStatsCloseFunc()
{
    aishipStats.isValid = FALSE;
}

void aishipStatsPrint(sdword *y)
{
    if (aishipStats.isDisplayable)
    {
        fontPrintf(0,*y += 20,colWhite, "AIShip state:%d walks:%d considered:%d",
                   aishipStats.state, aishipStats.avoidedWalks, aishipStats.avoidedConsidered);

        *y += 20;
        if (aishipStats.passingOnRight)
        fontPrintf(0,*y,colWhite, "AIShip passingonright:(%f %f %f)",
           aishipStats.passingOnRightVec.x,aishipStats.passingOnRightVec.y,aishipStats.passingOnRightVec.z);

        *y += 20;
        if (aishipStats.repulsenum)
        fontPrintf(0,*y,colWhite, "AIShip Repulse %d:(%f %f %f)",
           aishipStats.repulsenum, aishipStats.repulse.x, aishipStats.repulse.y, aishipStats.repulse.z);

        fontPrintf(0,*y += 20,colWhite,"distToColl:%f desiredVel:(%f %f %f)",
                   aishipStats.distanceToCollision,aishipStats.desiredVel.x,aishipStats.desiredVel.y,aishipStats.desiredVel.z);

        fontPrintf(0,*y += 20,colWhite,"desiredHeading:(%f %f %f) actualHeading:(%f %f %f)",
                   aishipStats.desiredHeading.x,aishipStats.desiredHeading.y,aishipStats.desiredHeading.z,aishipStats.actualHeading.x,aishipStats.actualHeading.y,aishipStats.actualHeading.z);
    }
}

#endif

#if 0       // obsolete
void aishipFlyToPoint(Ship *ship,vector *destination,udword aishipflags)
{
    vector desiredVel;
    vector desiredHead;

    vecSub(desiredVel,*destination,ship->posinfo.position);

    vecMultiplyByScalar(desiredVel,VELOCITY_SCALE_FACTOR);

    if (bitTest(aishipflags,AISHIP_PointInDirectionFlying))
    {
        vecCopyAndNormalize(&desiredVel,&desiredHead);
        aitrackHeading(ship,&desiredHead,FLYSHIP_HEADINGACCURACY);
    }
    aitrackVelocityVector(ship,&desiredVel);
}
#endif

/*-----------------------------------------------------------------------------
    Name        : aishipPrecalcInfo
    Description : precalculates information used by the AIShip routines and puts
                  it in the ShipStaticInfo structure.
    Inputs      : shipstatinfo
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void aishipPrecalcInfo(ShipStaticInfo *shipstatinfo)
{
    shipstatinfo->sinbank = 0.0f;
    shipstatinfo->pitchturn = 0.0f;
    shipstatinfo->pitchdescend = 0.0f;

    switch (shipstatinfo->shipclass)
    {
        case CLASS_Fighter:
            shipstatinfo->sinbank = FIGHTER_BANK;
            break;

        case CLASS_Corvette:
            shipstatinfo->sinbank = CORVETTE_BANK;
            break;

        case CLASS_Frigate:
            shipstatinfo->sinbank = FRIGATE_BANK;
            shipstatinfo->pitchturn = FRIGATE_TURNPITCH;
            shipstatinfo->pitchdescend = FRIGATE_DESCENDPITCH;
            break;

        case CLASS_Destroyer:
            shipstatinfo->sinbank = DESTROYER_BANK;
            shipstatinfo->pitchturn = DESTROYER_TURNPITCH;
            shipstatinfo->pitchdescend = DESTROYER_DESCENDPITCH;
            break;

        case CLASS_Carrier:
        case CLASS_HeavyCruiser:
            shipstatinfo->sinbank = MASSIVESHIP_BANK;
            shipstatinfo->pitchturn = MASSIVESHIP_TURNPITCH;
            shipstatinfo->pitchdescend = MASSIVESHIP_DESCENDPITCH;
            break;

        default:
            switch (shipstatinfo->shiptype)
            {
                case SensorArray:
                    shipstatinfo->sinbank = NOSHIP_BANK;
                    shipstatinfo->pitchturn = NOSHIP_TURNPITCH;
                    shipstatinfo->pitchdescend = NOSHIP_DESCENDPITCH;
                    break;
                case CryoTray:
                case P1Mothership:
                case P2Mothership:
                case P3Megaship:
                case FloatingCity:
                case ResearchStation:
                case Ghostship:
                    shipstatinfo->sinbank = MASSIVESHIP_BANK;
                    shipstatinfo->pitchturn = MASSIVESHIP_TURNPITCH;
                    shipstatinfo->pitchdescend = MASSIVESHIP_DESCENDPITCH;
                    break;
                case ResourceController:
                    shipstatinfo->sinbank = FRIGATE_BANK;
                    shipstatinfo->pitchturn = FRIGATE_TURNPITCH;
                    shipstatinfo->pitchdescend = FRIGATE_DESCENDPITCH;
                    break;
            }
            break;
    }
}

/*-----------------------------------------------------------------------------
    Name        : getPitchToDescend
    Description : returns the pitch in radians the ship should be at, given the
                  normalized distance left zdistanceleft, and pitchdescend (maximum
                  pitch change)
    Inputs      : zdistanceleft, pitchdescend
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
real32 getPitchToDescend(real32 zdistanceleft,real32 pitchdescend)
{
    if (zdistanceleft < 0.0f)
    {
        zdistanceleft = -zdistanceleft;

        if (zdistanceleft < 0.0f)
        {
            return 0.0f;
        }
        else if (zdistanceleft < DESCEND_PITCH4_DIST)       // 0 to DESCEND_PITCH4_DIST
        {
            return pitchdescend * zdistanceleft * oneOverDESCEND_PITCH4_DIST;
        }
        else if (zdistanceleft < DESCEND_PITCH3_DIST)       // DESCEND_PITCH4_DIST to DESCEND_PITCH3_DIST
        {
            return pitchdescend * (DESCEND_PITCH3_DIST - zdistanceleft) * oneOverDESCEND_3_MINUS_4_DIST;
        }
        else if (zdistanceleft < DESCEND_PITCH2_DIST)       // DESCEND_PITCH3_DIST to DESCEND_PITCH2_DIST
        {
            return -pitchdescend * (zdistanceleft - DESCEND_PITCH3_DIST) * oneOverDESCEND_2_MINUS_3_DIST;
        }
        else                                                // DESCEND_PITCH2_DIST +
        {
            return -pitchdescend;
        }
    }
    else
    {
        if (zdistanceleft < 0.0f)
        {
            return 0.0f;
        }
        else if (zdistanceleft < DESCEND_PITCH4_DIST)       // 0 to DESCEND_PITCH4_DIST
        {
            return -pitchdescend * zdistanceleft * oneOverDESCEND_PITCH4_DIST;
        }
        else if (zdistanceleft < DESCEND_PITCH3_DIST)       // DESCEND_PITCH4_DIST to DESCEND_PITCH3_DIST
        {
            return -pitchdescend * (DESCEND_PITCH3_DIST - zdistanceleft) * oneOverDESCEND_3_MINUS_4_DIST;
        }
        else if (zdistanceleft < DESCEND_PITCH2_DIST)       // DESCEND_PITCH3_DIST to DESCEND_PITCH2_DIST
        {
            return pitchdescend * (zdistanceleft - DESCEND_PITCH3_DIST) * oneOverDESCEND_2_MINUS_3_DIST;
        }
        else                                                // DESCEND_PITCH2_DIST +
        {
            return pitchdescend;
        }
    }
}

void aishipGetTrajectoryWithVelPrediction(Ship *ship,SpaceObjRotImpTarg *target,real32 bulletspeed,vector *trajectory)
{
    vector distToTargetVec;
    real32 distToTargetMag;
    real32 bullettraveltime;
    vector deltatarget;

    vecSub(distToTargetVec,target->collInfo.collPosition,ship->posinfo.position); // MUST be relative to ship->posinfo.position, because
                                                                                  // later it is modified into the ship's coordinate system
                                                                                  // for gun firing, which is based on ship->posinfo.position
    if (bulletspeed <= DONTUSEVELOCITYPRED_IFBELOW)
    {
        // don't bother with velocity prediction for 0 bulletspeed
        goto settrajectory;
    }

    distToTargetMag = fsqrt(vecMagnitudeSquared(distToTargetVec));

    bullettraveltime = distToTargetMag / bulletspeed;

    vecScalarMultiply(deltatarget,target->posinfo.velocity,bullettraveltime);

    vecAddTo(distToTargetVec,deltatarget);

settrajectory:
    *trajectory = distToTargetVec;
}

void aishipGetTrajectory(Ship *ship,SpaceObjRotImpTarg *target,vector *trajectory)
{
    vecSub(*trajectory,target->collInfo.collPosition,ship->posinfo.position);
}

void aishipGetTrajectoryWithAngleCorrection(Ship *ship,SpaceObjRotImpTarg *target,vector *trajectory)
{
    vector futurepos = ship->posinfo.position;

    futurepos.x += (ship->posinfo.velocity.x * 0.25f);
    futurepos.y += (ship->posinfo.velocity.y * 0.25f);
    futurepos.z += (ship->posinfo.velocity.z * 0.25f);

    vecSub(*trajectory,target->collInfo.collPosition,futurepos);
}

#define STATE_POINT_IN_DIRECTION    1
#define STATE_START_FLYING          2

real32 GetCollSizeInDirection(SpaceObjRotImp *obj,vector dir)
{
    vector up;
    vector right;
    vector heading;
    real32 upcomp,rightcomp,headingcomp;
    StaticCollInfo *collInfo = &obj->staticinfo->staticheader.staticCollInfo;

    matGetVectFromMatrixCol1(up,obj->rotinfo.coordsys);
    matGetVectFromMatrixCol2(right,obj->rotinfo.coordsys);
    matGetVectFromMatrixCol3(heading,obj->rotinfo.coordsys);

    upcomp = abs(vecDotProduct(up,dir));
    rightcomp = abs(vecDotProduct(right,dir));
    headingcomp = abs(vecDotProduct(heading,dir));

    return (upcomp*collInfo->uplength + rightcomp*collInfo->rightlength + headingcomp*collInfo->forwardlength)*0.5f;
}

/*-----------------------------------------------------------------------------
    Name        : rowSetDetails
    Description : sets right of way details for a ship (callback function for statscript)
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
static void rowSetDetails(char *directory,char *field,void *dataToFillIn)
{
    char shipstr[50];
    sdword rowpri;
    ShipType shiptype;
    real32 rowAvoidByVal;

    RemoveCommasFromString(field);

    sscanf(field,"%s %d %f",shipstr,&rowpri,&rowAvoidByVal);

    shiptype = StrToShipType(shipstr);
    if ((shiptype < 0) || (shiptype >= TOTAL_NUM_SHIPS))
    {
        dbgFatalf(DBG_Loc,"Invalid shiptype read at line %s in aiship.script",field);
    }

    rightOfWays[shiptype] = (sbyte)rowpri;
    rowAvoidBy[shiptype] = rowAvoidByVal;
}

/*-----------------------------------------------------------------------------
    Name        : GetDirectionOfTravel
    Description : gets the direction of travel of ship
                  bases direction of travel based on where it's moving to if it's got a move command, otherwise
                  bases direction of travel on velocity of ship
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void GetDirectionOfTravel(Ship *ship,vector *dirOfTravel)
{
    if (!vecIsZero(ship->moveTo))
    {
        vecSub(*dirOfTravel,ship->moveTo,ship->moveFrom);
    }
    else
    {
        *dirOfTravel = ship->posinfo.velocity;
    }
}

/*-----------------------------------------------------------------------------
    Name        : rowRemoveShipFromGettingOutOfWay
    Description : removes getting out of way status from ship
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void rowRemoveShipFromGettingOutOfWay(Ship *ship)
{
    if (!(ship->specialFlags & SPECIAL_rowGettingOutOfWay))
    {
        return;
    }

    bitClear(ship->specialFlags,SPECIAL_rowGettingOutOfWay);
    //bitClear(ship->dontrotateever,2);
    bitClear(ship->dontapplyforceever,2);

    ship->rowGetOutOfWay = NULL;
}

/*-----------------------------------------------------------------------------
    Name        : rowGetOutOfWayShipDiedCB
    Description : callback function notify for when rowGetOutOfWay ship dies
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void rowGetOutOfWayShipDiedCB(Ship *ship)
{
    ;
}

#define ROW_CAN_GET_OUT_OF_WAY      1
#define ROW_CANNOT_GET_OUT_OF_WAY   0
#define ROW_YOU_STILL_AVOID_ME      -1

/*-----------------------------------------------------------------------------
    Name        : rowShipCanGetOutOfWayOfMe
    Description : returns TRUE if the ship can get out of the way of me
    Inputs      : ship, me
    Outputs     :
    Return      : returns TRUE if the ship can get out of the way of me
----------------------------------------------------------------------------*/
sdword rowShipCanGetOutOfWayOfMe(Ship *ship,Ship *me)
{
    CommandToDo *shipcommand = getShipAndItsCommand(&universe.mainCommandLayer,ship);
    CommandToDo *mecommand;

    if (ship->shiptype == Drone)
    {
        return ROW_CANNOT_GET_OUT_OF_WAY;
    }

    if (shipcommand == NULL)
    {                   // ship not doing anything, so it can get out of way
        return ROW_CAN_GET_OUT_OF_WAY;
    }

    mecommand = getShipAndItsCommand(&universe.mainCommandLayer,me);

    if (shipcommand == mecommand)
    {
        return ROW_YOU_STILL_AVOID_ME;       // if ship and me are in the same command, we shouldn't use right of way
    }

    if (shipcommand->ordertype.attributes & COMMAND_IS_PROTECTING)
    {
        return ROW_CANNOT_GET_OUT_OF_WAY;       // ships that are protecting are avoiding stuff anyway, shouldn't use right of way
    }

    if (mecommand && (mecommand->ordertype.attributes & COMMAND_IS_PROTECTING))
    {
        return ROW_CANNOT_GET_OUT_OF_WAY;       // ships that are protecting are avoiding stuff anyway, shouldn't use right of way
    }

    switch (shipcommand->ordertype.order)
    {
        case COMMAND_NULL:
        case COMMAND_HALT:
            return ROW_CAN_GET_OUT_OF_WAY;

        case COMMAND_MILITARYPARADE:
            if (shipcommand->militaryParade->aroundShip == me)
            {
                return ROW_CANNOT_GET_OUT_OF_WAY;       // ships in my parade shouldn't try to avoid me!
            }
            return ROW_CAN_GET_OUT_OF_WAY;

        case COMMAND_MOVE:
        case COMMAND_DOCK:
        case COMMAND_ATTACK:
        case COMMAND_SPECIAL:
            return ROW_CANNOT_GET_OUT_OF_WAY;

        default:
            return ROW_CANNOT_GET_OUT_OF_WAY;
    }
}

/*-----------------------------------------------------------------------------
    Name        : rowOriginalPointIsClear
    Description : returns TRUE if original point is clear
                  (right-of-way ship we're getting out way of is not near it and
                   right-of-way ship is not heading in our direction)
    Inputs      :
    Outputs     :
    Return      : returns TRUE if original point is clear
----------------------------------------------------------------------------*/
bool rowOriginalPointIsClear(Ship *ship)
{
    real32 avoidcollsize;
    real32 avoidcollpad;
    vector destToObj;
    real32 destToObjMag2;
    Ship *avoidme;

    vector meToShip;
    vector meDirOfTravel;

    dbgAssert(ship->specialFlags & SPECIAL_rowGettingOutOfWay);

    avoidme = ship->rowGetOutOfWay;
    if (avoidme == NULL)
    {
        return TRUE;
    }

    GetDirectionOfTravel(avoidme,&meDirOfTravel);

    vecSub(meToShip,ship->collInfo.collPosition,avoidme->collInfo.collPosition);

    if (vecDotProduct(meToShip,meDirOfTravel) > 0.0f)
    {
        // right-of-way ship (avoidme) is still heading in our direction, must wait till it is not
        return FALSE;
    }

    avoidcollsize = avoidme->staticinfo->staticheader.staticCollInfo.avoidcollspheresize;
    avoidcollpad = avoidme->staticinfo->staticheader.staticCollInfo.avoidcollspherepad;

    vecSub(destToObj,avoidme->collInfo.collPosition,ship->rowOriginalPoint);
    destToObjMag2 = vecMagnitudeSquared(destToObj);

    if (destToObjMag2 < (avoidcollsize*avoidcollsize))
    {
        return FALSE;
    }

    // later check all ships in nearest blob too?

    return TRUE;
}

#define rowHANG_AROUND_TILL_SHIP_OUT_OF_WAY     1
#define rowFLY_BACK_TO_ORIGINAL_POINT           2

/*-----------------------------------------------------------------------------
    Name        : rowFlyShipOutOfWay
    Description : flys ship out of way
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void rowFlyShipOutOfWay(Ship *ship)
{
    dbgAssert(ship->specialFlags & SPECIAL_rowGettingOutOfWay);

    //bitClear(ship->dontrotateever,2);
    bitClear(ship->dontapplyforceever,2);

    switch (ship->rowState)
    {
        case 0:     // fly to rowOutOfWayPoint
            if (ship->rowGetOutOfWay == NULL)
            {
                ship->rowState = rowFLY_BACK_TO_ORIGINAL_POINT;
                break;
            }
            if (rowOriginalPointIsClear(ship))
            {
                ship->rowState = rowFLY_BACK_TO_ORIGINAL_POINT;
                break;
            }
            if (MoveReachedDestinationVariable(ship,&ship->rowOutOfWayPoint,50.0f))
            {
                ship->rowState = rowHANG_AROUND_TILL_SHIP_OUT_OF_WAY;
                break;
            }
            aishipFlyToPointAvoidingObjs(ship,&ship->rowOutOfWayPoint,0,0.0f);
            break;

        case rowHANG_AROUND_TILL_SHIP_OUT_OF_WAY:
            if (ship->rowGetOutOfWay == NULL)
            {
                ship->rowState = rowFLY_BACK_TO_ORIGINAL_POINT;
                break;
            }
            if (rowOriginalPointIsClear(ship))
            {
                ship->rowState = rowFLY_BACK_TO_ORIGINAL_POINT;
                break;
            }
            // just hover around here
            aishipFlyToPointAvoidingObjs(ship,NULL,0,0.0f);
            break;

        case rowFLY_BACK_TO_ORIGINAL_POINT:
            if (MoveReachedDestinationVariable(ship,&ship->rowOriginalPoint,50.0f))
            {
                rowRemoveShipFromGettingOutOfWay(ship);     // we're done!
                return;
            }
            if (aishipFlyToPointAvoidingObjs(ship,&ship->rowOriginalPoint,AISHIP_DontFlyToObscuredPoints|AISHIP_ReturnImmedIfPointObscured,0.0f) & AISHIP_FLY_OBJECT_IN_WAY)
            {
                rowRemoveShipFromGettingOutOfWay(ship);     // we're done!
                return;
            }
            break;

        default:
            dbgAssert(FALSE);
            break;
    }

    //bitSet(ship->dontrotateever,2);
    bitSet(ship->dontapplyforceever,2);
}

/*-----------------------------------------------------------------------------
    Name        : rowSignalShipToGetOutOfWayOfMe
    Description : tells ship to get out of the way of me
    Inputs      : ship, me
                  me is the right of way ship (get out of way of me)
                  ship is avoiding ship
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void rowSignalShipToGetOutOfWayOfMe(Ship *ship,Ship *me,vector *meToShip,vector *meDirOfTravel)
{
    vector delta;

    ship->specialFlags |= SPECIAL_rowGettingOutOfWay;
    //bitSet(ship->dontrotateever,2);
    bitSet(ship->dontapplyforceever,2);

    ship->rowState = 0;
    ship->rowGetOutOfWay = me;
    ship->rowOriginalPoint = ship->posinfo.position;

    // calculate outOfWayPoint;

    if ((me->shiprace == R1) && (me->shiptype == Mothership))
    {
        // special case solution for R1 Mothership
        // under no circumstances do we want ships going up or down to avoid the R1 Mothership!

        vector meToShipNorm = *meToShip;
        vector meDirOfTravelNorm = *meDirOfTravel;
        vector directionOfParting;
        vector up = { 0.0f, 0.0f, 1.0f };
        vector test;

        meToShipNorm.z = 0.0f;
        meDirOfTravelNorm.z = 0.0f;
        vecNormalize(&meToShipNorm);
        vecNormalize(&meDirOfTravelNorm);

        // find direction ships should part in (should be on x-y plane and perpendicular to direction of movement of mothership)
        vecCrossProduct(directionOfParting,meDirOfTravelNorm,up);

        // now find out if we should go in directionOfParting or -directionOfParting
        vecCrossProduct(test,meDirOfTravelNorm,meToShipNorm);
        if (test.z > 0.0f)
        {
            vecNegate(directionOfParting);
        }

        vecScalarMultiply(delta,directionOfParting,AVOID_ROW_R1MOTHERSHIP_BY);
    }
    else
    {
        vector meDirOfTravelNorm = *meDirOfTravel;
        vector meToShipNorm;
        real32 avoidby = rowAvoidBy[me->shiptype];

        if (avoidby == 0.0f) avoidby = me->staticinfo->staticheader.staticCollInfo.avoidcollspheresize*AVOID_ROW_SHIP_BY_SCALE + AVOID_ROW_SHIP_BY_ADD;

        vecNormalize(&meDirOfTravelNorm);

        if ((meDirOfTravelNorm.z > ROW_GOING_MOSTLY_UPDOWN_ANGLE) || (meDirOfTravelNorm.z < -ROW_GOING_MOSTLY_UPDOWN_ANGLE))
        {
            // ship we are trying to avoid is going mainly up/down, so we should move on x-y plane.
            vector directionOfParting;
            vector up = { 0.0f, 0.0f, 1.0f };
            vector test;

            meToShipNorm = *meToShip;
            meDirOfTravelNorm = *meDirOfTravel;

            meToShipNorm.z = 0.0f;
            meDirOfTravelNorm.z = 0.0f;
            vecNormalize(&meToShipNorm);
            vecNormalize(&meDirOfTravelNorm);

            // find direction ships should part in (should be on x-y plane and perpendicular to direction of movement of me)
            vecCrossProduct(directionOfParting,meDirOfTravelNorm,up);

            // now find out if we should go in directionOfParting or -directionOfParting
            vecCrossProduct(test,meDirOfTravelNorm,meToShipNorm);
            if (test.z > 0.0f)
            {
                vecNegate(directionOfParting);
            }

            vecScalarMultiply(delta,directionOfParting,avoidby);
        }
        else
        {
            // move up or down to get out of way

            if ((-meToShip->z) > (avoidby*ROW_GO_DOWN_INSTEAD_OF_UP_RATIO)) // if me is above ship by this let's go down
            {
                // go down
                vecSet(delta,0.0f,0.0f,-avoidby);
            }
            else
            {
                // go up
                vecSet(delta,0.0f,0.0f,avoidby);
            }
        }
    }

    vecAdd(ship->rowOutOfWayPoint,ship->posinfo.position,delta);
}

/*-----------------------------------------------------------------------------
    Name        : rowSignalLeaderInFormationToGetOutOfWayOfMe
    Description : tells other ships in ship's formation to get out of the way of me
    Inputs      : ship, me
                  me is the right of way ship (get out of way of me)
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void rowSignalLeaderInFormationToGetOutOfWayOfMe(Ship *ship,Ship *me,vector *meToShip,vector *meDirOfTravel)
{
    SelectCommand *selection = ship->formationcommand->selection;
    //sdword numShips;
    //sdword i;
    Ship *shipi;

    dbgAssert(selection);
    dbgAssert(selection->numShips > 0);
    //numShips = selection->numShips;

    shipi = selection->ShipPtr[0];
    if (!(shipi->specialFlags & SPECIAL_rowGettingOutOfWay))    // not already getting out of way
    {
        rowSignalShipToGetOutOfWayOfMe(shipi,me,meToShip,meDirOfTravel);
    }

#if 0
    for (i=0;i<numShips;i++)
    {
        shipi = selection->ShipPtr[i];
        if (shipi != ship)
        {
            if (!(shipi->specialFlags & SPECIAL_rowGettingOutOfWay))    // not already getting out of way
            {
                rowSignalShipToGetOutOfWayOfMe(shipi,me,meToShip,meDirOfTravel);
            }
        }
    }
#endif
}

udword aishipFlyToPointAvoidingObjsFunc(Ship *ship,vector *destination,udword aishipflags,real32 limitvel,vector *withVel)
{
    vector desiredVel;
    vector desiredHead;
    vector targetVec;
    sdword objindex;
    SpaceObjRotImp *avoidobj = NULL;
    ShipStaticInfo *shipstaticinfo = (ShipStaticInfo *)ship->staticinfo;

    blob *thisBlob = NULL;
    SelectAnyCommand *targetsAvoid = NULL;

    vector repulse;
    vector repulsenorm;
    real32 repulsedist;
    real32 repulsemag2;
#if VELFACTOR
    real32 velfactor;
#endif
    real32 collidedist;
    real32 distanceToCollision;

    real32 testExactOpposite;
    real32 shipcollsize,shipcollpad,shiprowcollpad;
    real32 avoidcollsize = 0,avoidcollpad = 0;

    real32 maxavoidcollsize,maxavoidcollpad;

    real32 maxdistconsider;
    real32 temp;

    real32 zdistanceleft;
    real32 pitchtodescend;

    udword returnflag = 0;

#ifndef HW_Release
    udword considered = 0;
#endif

    sdword pass;
    sdword bottom,top;
    sdword numTargets;

    real32 distcheck;
    real32 maxpossibledist;

    real32 obscureaddcollsize = 0;

    real32 destToObjDist;
    vector destToObjNorm;

    bool rowEnabled;

    CommandToDo *shipcommand = ship->command;

    real32 useAVOID_OBJ_SCALE_FACTOR = AVOID_OBJ_SCALE_FACTOR;

    SpaceObjRotImpTarg *takeOutInMyWay = NULL;

    if (ship->dontapplyforceever)
    {
        return 0;     // no point wasting our time
    }

    if (ship->staticinfo->staticheader.immobile)
    {
        return 0;     // no point wasting our time
    }

    PTSTART(2);

    if (isStrikeCraft(ship->shiptype))
    {
        if ((shipcommand) && (shipcommand->ordertype.order == COMMAND_ATTACK))
        {
            useAVOID_OBJ_SCALE_FACTOR = COMBAT_AVOID_OBJ_SCALE_FACTOR;
        }
    }
    else if (ship->shiptype == Probe)
    {
        if (((ProbeSpec *)ship->ShipSpecifics)->HaveMoved)
        {
            useAVOID_OBJ_SCALE_FACTOR = PROBE_AVOID_OBJ_SCALE_FACTOR;
        }
    }

    aishipStatsInit(ship);

    aishipStatsState(ship->aistate);

    switch (ship->aistate)
    {
/*=============================================================================
    State 0
=============================================================================*/
        case 0:
            if (shipstaticinfo->pitchdescend != 0.0f)
            {
                if (destination)
                {
                    real32 xdistanceleft = (destination->x - ship->posinfo.position.x);
                    real32 ydistanceleft = (destination->y - ship->posinfo.position.y);
                    real32 r;
                    zdistanceleft = abs(destination->z - ship->posinfo.position.z);
                    r = fsqrt(xdistanceleft*xdistanceleft + ydistanceleft*ydistanceleft);
                    if ((zdistanceleft < MIN_DIST_FOR_FANCY_DESCEND) ||
                        (zdistanceleft < (r*MIN_ANGLE_FOR_FANCY_DESCEND)))
                    {
                        ship->aidescend = 0.0f;                     // set to 0 to indicate no fancy descend
                    }
                    else
                    {
                        ship->aidescend = 1.0f / zdistanceleft;     // used to normalize distance left to go in descend

                        if ((r < MIN_DIST_TO_TRACK_HEADING) ||
                            (zdistanceleft > (r*MAX_ANGLE_FOR_TRACK_HEADING)))
                        {
                            ship->aidescend = -ship->aidescend;     // -ve aidescend indicates don't track heading as well
                        }
                    }
                }
                else
                {
                    ship->aidescend = 0.0f;                         // set to 0 to indicate no fancy descend
                }
            }
            ship->aistate = STATE_POINT_IN_DIRECTION;
            // deliberately fall through to STATE_POINT_IN_DIRECTION

        case STATE_POINT_IN_DIRECTION:
            if (bitTest(aishipflags,(AISHIP_FirstPointInDirectionFlying+AISHIP_CarTurn)))
            {
        #if DEBUG_AISHIP
                 dbgMessagef("\nOrienting ship for flight");
        #endif
                dbgAssert(destination);     // must have destination for AISHIP_FirstPointInDirectionFlying or AISHIP_CarTurn to have any meaning
                vecSub(targetVec,*destination,ship->posinfo.position);

                vecNormalize(&targetVec);

                if (shipstaticinfo->pitchdescend == 0.0f)
                {
                    if (aitrackHeadingWithBankFlags(ship,&targetVec,0.96f,shipstaticinfo->sinbank,AITRACKHEADING_IGNOREUPVEC))
                    {
                        ship->aistate = STATE_START_FLYING;
                        aishipStatsClose();
                        PTEND(2);
                        return 0;
                    }
                }
                else
                {
                    if ((ship->aidescend < 0.0f) || (aitrackHeadingWithBankPitchFlags(ship,&targetVec,0.96f,shipstaticinfo->sinbank,0.0f,shipstaticinfo->pitchturn,AITRACKHEADING_IGNOREUPVEC)))
                    {
                        ship->aistate = STATE_START_FLYING;
                        aishipStatsClose();
                        PTEND(2);
                        return 0;
                    }
                }

                if (bitTest(aishipflags,AISHIP_CarTurn) && (!isCapitalShipStaticOrBig(shipstaticinfo)))
                {
                    physApplyForceToObj((SpaceObj *)ship,ship->nonstatvars.thruststrength[TRANS_FORWARD]*CAR_TURN_POWER,TRANS_FORWARD);
                }
                else
                {
                    aitrackSteadyShipDriftOnly(ship);
                }
            }
            else
            {
                ship->aistate = STATE_START_FLYING;
                goto startflying;
            }
            aishipStatsClose();
            PTEND(2);
            return 0;

/*=============================================================================
    State START_FLYING
=============================================================================*/
startflying:
        case STATE_START_FLYING:

            if (destination)
            {
                vecSub(targetVec,*destination,ship->posinfo.position);

                if ((aishipflags & AISHIP_FastAsPossible) == 0)
                {
                    vecMultiplyByScalar(targetVec,VELOCITY_SCALE_FACTOR);
                }
                // else assumes URGENT_SCALE_FACTOR is 1!
            }
            else
            {
                vecZeroVector(targetVec);
            }

            if ((limitvel == 0.0f) && ship->formationcommand && (ship == shipcommand->selection->ShipPtr[0]))
            {
                // ship is leader of formation, it may potentially travel slower so other ships can maintain formation

                if ((shipcommand->formation.formationtype != SPHERE_FORMATION) ||
                    (!((shipcommand->ordertype.attributes & COMMAND_IS_PROTECTING) || (shipcommand->formation.enders)))
                   )
                {
                    limitvel = shipcommand->formation.travelvel;
                }
            }

            if (limitvel < 0.0f)
            {
                vecCapMinVector(&targetVec,-limitvel);
            }

            if (withVel != NULL)
            {
                vecAddTo(targetVec,*withVel);
            }

            if (limitvel > 0.0f)
            {
                vecCapVector(&targetVec,limitvel);
            }
            else
            {
                real32 velMult = tacticsGetShipsMaxVelocity(ship);
                vecCapVector(&targetVec,velMult);
            }

        #if DEBUG_AISHIP
            dbgMessagef("\nFlying to target vector: (%f %f %f)",targetVec.x,targetVec.y,targetVec.z);
        #endif

            desiredVel = targetVec;

            if (aishipTempDisableAvoiding)
            {
                goto noavoid;
            }

#ifndef HW_Release
            if (!DO_AVOID_OBJS)
            {
                goto noavoid;
            }
#endif

            if (ship->dockingship != NULL)       // don't avoid any objects if docking
            {
                goto noavoid;
            }

            //don't avoid our target if we are in kamikaze mode
            if(bitTest(ship->specialFlags,SPECIAL_Kamikaze))
            {
                goto noavoid;
            }

#ifdef COLLISION_CHECK_STATS
            shipsavoidingstuff++;
#endif

            shipcollsize = shipstaticinfo->staticheader.staticCollInfo.avoidcollspheresize;
            shipcollpad = shipstaticinfo->staticheader.staticCollInfo.avoidcollspherepad;

            rowEnabled = FALSE;

            if (shipstaticinfo->staticheader.rightOfWay > 0)    // if ship has any right of way
            {
                shiprowcollpad = shipcollsize * AVOID_OBJ_ROW_PADDING_SCALE;
                rowEnabled = TRUE;
            }
            else
                shiprowcollpad = shipcollpad;

            thisBlob = ship->collMyBlob;
            if (thisBlob == NULL)
            {
                dbgMessagef("\nWarning: flags %x ship %s %s flying with no blob",ship->flags,ShipRaceToStr(ship->shiprace),ShipTypeToStr(ship->shiptype));
                goto noavoid;
            }

            pass = 0;

passagain:
            if (pass == 0)
            {
                targetsAvoid = thisBlob->blobSmallTargets;
                dbgAssert(targetsAvoid);
                numTargets = targetsAvoid->numTargets;
                if (numTargets == 0)
                {
                    goto nextpass;
                }
                maxavoidcollsize = thisBlob->blobMaxSTargetCollSphereSize;
                maxavoidcollpad = maxavoidcollsize * AVOID_OBJ_PADDING_SCALE;
                maxpossibledist = shiprowcollpad + maxavoidcollpad;

                // binary search targetindex such that
                // (ship->collOptimizeDist - avoidobj->collOptimizeDist) <= maxpossibledist
                bottom = 0;
                top = numTargets-1;
                while (top >= bottom)
                {
                    objindex = (bottom+top)>>1;
                    if ((ship->collOptimizeDist - targetsAvoid->TargetPtr[objindex]->collOptimizeDist) > maxpossibledist)
                    {
                        // targetindex is too small
                        bottom = objindex+1;
                    }
                    else
                    {
                        top = objindex-1;
                    }
                }

                dbgAssert(objindex >= 0);
                dbgAssert(objindex < numTargets);
            }
            else
            {
                dbgAssert(pass == 1);
                targetsAvoid = thisBlob->blobBigTargets;
                dbgAssert(targetsAvoid);
                numTargets = targetsAvoid->numTargets;
                if (numTargets == 0)
                {
                    goto nextpass;
                }
                maxavoidcollsize = thisBlob->blobMaxBTargetCollSphereSize;
                maxavoidcollpad = maxavoidcollsize * ((maxavoidcollsize > AISHIP_BIGSHIP) ? AVOID_OBJ_PADDING_SCALE_BIG : AVOID_OBJ_PADDING_SCALE);
                maxpossibledist = shiprowcollpad + maxavoidcollpad;
                objindex = 0;
            }

            while (objindex < numTargets)
            {
                avoidobj = (SpaceObjRotImp *) targetsAvoid->TargetPtr[objindex];

#ifdef COLLISION_CHECK_STATS
                shipsavoidedwalks++;
#endif
                aishipStatsAvoidedWalks();

                if ((distcheck = (avoidobj->collOptimizeDist - ship->collOptimizeDist)) > maxpossibledist)
                {
                    goto nextpass;
                }

                // use more trivial rejections?

                avoidcollpad = avoidobj->staticinfo->staticheader.staticCollInfo.avoidcollspherepad;

                maxdistconsider = avoidcollpad + shiprowcollpad;

                if (abs(distcheck) > maxdistconsider)
                {
                    goto nextnode;
                }

                vecSub(repulse,ship->collInfo.collPosition,avoidobj->collInfo.collPosition);

#ifdef COLLISION_CHECK_STATS
                shipsavoidedchecks++;
#endif

                if (!rowEnabled)
                {
                    goto norowcheck;
                }

                if (!isBetweenInclusive(repulse.x,-maxdistconsider,maxdistconsider))
                {
                    goto nextnode;
                }

                if (!isBetweenInclusive(repulse.y,-maxdistconsider,maxdistconsider))
                {
                    goto nextnode;
                }

                if (!isBetweenInclusive(repulse.z,-maxdistconsider,maxdistconsider))
                {
                    goto nextnode;
                }

                if (shipstaticinfo->staticheader.rightOfWay > avoidobj->staticinfo->staticheader.rightOfWay)
                {
                    // We have right of way:
                    bool dontBotherAvoiding = TRUE;

                    if (avoidobj->flags & SOF_Hide)
                    {
                        goto nextnode;
                    }

                    if (avoidobj->objtype == OBJ_ShipType)
                    {
#define avoidship ((Ship *)avoidobj)
                        // signal ship avoidobj to get out of way here (assuming it's static and our ship)
                        if (!(avoidship->specialFlags & SPECIAL_rowGettingOutOfWay))    // not already getting out of way
                        {
                            if (allianceIsShipAlly(avoidship,ship->playerowner))
                            {
                                sdword canGetOutOfWay = rowShipCanGetOutOfWayOfMe(avoidship,ship);      // ship is me
                                if (canGetOutOfWay == ROW_CAN_GET_OUT_OF_WAY)
                                {
                                    vector meToShip;
                                    vector meDirOfTravel;

                                    GetDirectionOfTravel(ship,&meDirOfTravel);

                                    vecSub(meToShip,avoidship->collInfo.collPosition,ship->collInfo.collPosition);

                                    if (vecDotProduct(meToShip,meDirOfTravel) > 0.0f)
                                    {
                                        if (avoidship->formationcommand)
                                        {
                                            rowSignalLeaderInFormationToGetOutOfWayOfMe(avoidship,ship,&meToShip,&meDirOfTravel);
                                        }
                                        else
                                        {
                                            rowSignalShipToGetOutOfWayOfMe(avoidship,ship,&meToShip,&meDirOfTravel);
                                        }
                                    }
                                }
                                else if (canGetOutOfWay == ROW_YOU_STILL_AVOID_ME)
                                {
                                    dontBotherAvoiding = FALSE;
                                }
                            }
                            else if (ship->specialFlags2 & SPECIAL_2_DontPlowThroughEnemyShips)
                            {
                                dontBotherAvoiding = FALSE;
                            }
                        }
#undef avoidship
                    }
                    else if (avoidobj->objtype == OBJ_AsteroidType)
                    {
                        bool takingItOut = FALSE;

                        // later tell ship to blow up asteroid in way?
                        if (isCapitalShipStatic(shipstaticinfo) && (ship->shiptype != Carrier) && (ship->shiptype != Mothership))
                        {
                            if ((shipcommand == NULL) ||
                                   ((shipcommand->ordertype.order != COMMAND_ATTACK) &&
                                   ((shipcommand->ordertype.attributes & (COMMAND_IS_ATTACKINGANDMOVING|COMMAND_IS_PASSIVEATTACKING)) == 0))
                                )
                            {
                                // ship isn't devoting its guns to anywhere else, so let's take a potshot at the asteroid in our way:
                                if (ship->gunInfo && (ship->gunInfo->numGuns > 0) && (avoidobj->flags & SOF_Targetable))
                                {
                                    vector meToShip;
                                    vector meDirOfTravel;

                                    GetDirectionOfTravel(ship,&meDirOfTravel);

                                    vecSub(meToShip,avoidobj->collInfo.collPosition,ship->collInfo.collPosition);

                                    if ((!takeOutInMyWay) && (vecDotProduct(meToShip,meDirOfTravel) > 0.0f))
                                    {
                                        takingItOut = TRUE;
                                        takeOutInMyWay = (SpaceObjRotImpTarg *)avoidobj;
                                    }
                                }
                            }
                        }

                        if (!takingItOut)
                        {
                            // perhaps we should be avoiding it if we're not taking it out?
                            sbyte asteroidrow = avoidobj->staticinfo->staticheader.rightOfWay;
                            if (((Asteroid *)avoidobj)->asteroidtype == Asteroid3)
                                asteroidrow++;      // give +1 row to asteroid3 since it's pretty big

                            if (shipstaticinfo->staticheader.rightOfWay <= asteroidrow)
                            {
                                dontBotherAvoiding = FALSE;     // we should avoid it after all
                            }
                        }
                    }

                    if (dontBotherAvoiding)
                    {
                        goto nextnode;
                    }
                }

                maxdistconsider = avoidcollpad + shipcollpad;

                if (abs(distcheck) > maxdistconsider)
                {
                    goto nextnode;
                }
norowcheck:

                if (!isBetweenInclusive(repulse.x,-maxdistconsider,maxdistconsider))
                {
                    goto nextnode;
                }

                if (!isBetweenInclusive(repulse.y,-maxdistconsider,maxdistconsider))
                {
                    goto nextnode;
                }

                if (!isBetweenInclusive(repulse.z,-maxdistconsider,maxdistconsider))
                {
                    goto nextnode;
                }


                if ((avoidobj->flags & (SOF_Hide|SOF_Impactable|SOF_NotBumpable)) != (SOF_Impactable))
                {
                    goto nextnode;      // object must be impactable, bumpable
                }

                if (avoidobj == (SpaceObjRotImp *) ship)
                {
                    goto nextnode;  // cannot collide with itself
                }

                avoidcollsize = avoidobj->staticinfo->staticheader.staticCollInfo.avoidcollspheresize;

                if (avoidobj->objtype == OBJ_ShipType)
                {
#define avoidship ((Ship *)avoidobj)

                    if (shipcommand)
                    {
                        if (avoidship->command == shipcommand)
                        {
                            // ship and avoidobj are in the same formation, so we should effectively lower
                            // AVOID_OBJ_PADDING_SCALE to AVOID_OBJ_FORMATION_PADDING_SCALE

                            avoidcollpad = avoidcollsize * AVOID_OBJ_FORMATION_PADDING_SCALE;
                            shipcollpad = shipcollsize * AVOID_OBJ_FORMATION_PADDING_SCALE;
                            goto getmaxdist;
                        }

                        if ( (shipcommand->ordertype.order == COMMAND_MILITARYPARADE) && (shipcommand->militaryParade->aroundShip == avoidship))
                        {
                            if (avoidship->shiptype == Mothership)
                            {
                                avoidcollpad = avoidcollsize * ((avoidship->shiprace == R2) ? AVOID_OBJ_R2MSHIP_MILPARADE_PADDING_SCALE : AVOID_OBJ_MSHIP_MILPARADE_PADDING_SCALE);
                                shipcollpad = shipcollsize * AVOID_OBJ_FORMATION_PADDING_SCALE;
                            }
                            else
                            {
                                avoidcollpad = avoidcollsize * AVOID_OBJ_FORMATION_PADDING_SCALE;
                                shipcollpad = shipcollsize * AVOID_OBJ_FORMATION_PADDING_SCALE;
                            }
                       getmaxdist:
                            maxdistconsider = avoidcollpad + shipcollpad;

                            if (!isBetweenInclusive(repulse.x,-maxdistconsider,maxdistconsider))
                            {
                                goto nextnode;
                            }

                            if (!isBetweenInclusive(repulse.y,-maxdistconsider,maxdistconsider))
                            {
                                goto nextnode;
                            }

                            if (!isBetweenInclusive(repulse.z,-maxdistconsider,maxdistconsider))
                            {
                                goto nextnode;
                            }
                        }
                    }

                    if (((Ship *)avoidobj)->dockingship == ship)
                    {
                        goto nextnode;  // don't check if docking with ship
                    }

                    if((ship->flags & SOF_Slaveable) && (((Ship *) avoidobj)->flags & SOF_Slaveable))
                    {   //ships are slaveable
                        // two slaved ships SHOULD NOT try to avoid eachother
                        if(ship->slaveinfo->Master == ((Ship *) avoidobj)->slaveinfo->Master)
                        {   //if slaved to same thing, don't avoid
                            goto nextnode;
                        }
                    }
#undef avoidship
                }

                if(ship->specialFlags & SPECIAL_IsASalvager)
                {
                    if(((SalCapCorvetteSpec *)ship->ShipSpecifics)->salvageState == SAL_WL_FLYTOCONEORIGIN)
                    {
                        //don't want salvager in this state to avoid...
                        goto noavoid;
                    }
                    if( ((SalCapCorvetteSpec *)ship->ShipSpecifics)->target == (SpaceObjRotImpTargGuidanceShipDerelict *)avoidobj)
                    {
                        //avoid target?
                        if(((SalCapCorvetteSpec *)ship->ShipSpecifics)->noAvoid)
                        {
                            //want to avoid target
                            goto nextnode;
                        }
                    }
                    else if(avoidobj->objtype == OBJ_ShipType)
                    {
                        if( ((Ship *)avoidobj)->specialFlags & SPECIAL_IsASalvager)
                        {
                            if (((SalCapCorvetteSpec *)((Ship *)avoidobj)->ShipSpecifics)->target)
                            if( ((SalCapCorvetteSpec *)((Ship *)avoidobj)->ShipSpecifics)->target == ((SalCapCorvetteSpec *)ship->ShipSpecifics)->target)
                            {
                                //don't avoid other salcap corvettes
                                goto noavoid;
                            }
                        }
                    }
                }

                if(avoidobj->flags & SOF_Clamped)
                {
                    if(((Ship*)((Ship *)avoidobj)->clampInfo->host) == ship)
                    {
                        goto nextnode;
                    }
                }

                if (bitTest(avoidobj->flags,SOF_Cloaked))    //add cloakfield owner check here!
                {           //if object being considered is cloaked
                    if(avoidobj->objtype == OBJ_ShipType)
                    {
                        if(!proximityCanPlayerSeeShip(ship->playerowner,(Ship *)avoidobj))
                        {
                            goto nextnode;      //don't avoid it, but ignore it
                        }
                    }
                    else
                    {
                        goto nextnode;      //don't avoid it, but ignore it
                    }
                }

                if (shipcommand && shipcommand->ordertype.order == COMMAND_COLLECTRESOURCE && shipcommand->collect.resource == ((Resource *)avoidobj))
                {
                    goto nextnode;      // don't avoid asteroids you're harvesting
                }

                repulsemag2 = vecMagnitudeSquared(repulse);
                repulsedist = fsqrt(repulsemag2);

                if (repulsedist > maxdistconsider)
                {
                    goto nextnode;
                }

#ifndef HW_Release
                considered++;
#endif
                aishipStatsAvoidedConsidered();

                if (repulsedist < 0.1f)         // special case
                {
                    repulse.x = 0.1f;
                    repulsedist = 0.1f;
                    repulsemag2 = 0.01f;
                }

                vecScalarDivide(repulsenorm,repulse,repulsedist,temp);

                // very close, so figure out collidedist based on collision cubes
                collidedist = GetCollSizeInDirection((SpaceObjRotImp *)ship,repulsenorm) + GetCollSizeInDirection(avoidobj,repulsenorm);
#if VELFACTOR
                velfactor = -vecDotProduct(ship->posinfo.velocity,repulsenorm);
#endif
                distanceToCollision = repulsedist - collidedist;
                if (distanceToCollision < 20.0f)
                {
                    distanceToCollision = 20.0f;
                }
                repulse = repulsenorm;

                aishipStatsDistanceToCollision(distanceToCollision);

                if (avoidobj->objtype == OBJ_ShipType)
                {
                    if ((((Ship *)avoidobj)->shiptype == Mothership) && (((Ship *)avoidobj)->shiprace == R1))
                    {
                        repulse.z = 0.0f;
                        vecNormalize(&repulse);
                    }
                }

                distanceToCollision *= useAVOID_OBJ_SCALE_FACTOR;
                vecDivideByScalar(repulse,distanceToCollision,temp);

#if VELFACTOR
                if (velfactor > 1)
                {
                    velfactor *= AVOID_PANIC_FACTOR;
                    vecMultiplyByScalar(repulse,velfactor);
                }
#endif
//#define DEST_OBSCURED_BY_OBJ_PADDING    50.0f

                if (bitTest(aishipflags,AISHIP_DontFlyToObscuredPoints))
                {
                    vector destToObj;
                    real32 destToObjMag2;
                    real32 obscurecollsize;

                    if (avoidobj->objtype == OBJ_ShipType)
                    {
                        if (bitTest(aishipflags,AISHIP_IgnoreFormationObscuredPoints))
                        {
                            if (ship->formationcommand == ((Ship *)avoidobj)->formationcommand)
                            {
                                goto skipobscurecheck;
                            }
                        }

                        if (bitTest(aishipflags,AISHIP_IgnoreDockWithObscuredPoints))
                        {
                            if (ship->dockvars.dockship == ((Ship *)avoidobj))
                            {
                                goto skipobscurecheck;
                            }
                        }
                    }

                    obscurecollsize = avoidcollsize*AVOID_OBJ_OBSCUREDPOINT_SCALE + obscureaddcollsize; // + DEST_OBSCURED_BY_OBJ_PADDING;

                    obscureaddcollsize += AVOID_SPREAD_OUT_DIST;

                    // test if trying to move to a point that is being obscured by avoidobj
                    if (destination)
                    {
                        vecSub(destToObj,avoidobj->collInfo.collPosition,*destination);
                    }
                    else
                    {
                        // no destination provided, so use current ship position
                        vecSub(destToObj,avoidobj->collInfo.collPosition,ship->posinfo.position);
                    }

                    destToObjMag2 = vecMagnitudeSquared(destToObj);

                    if (destToObjMag2 < (obscurecollsize*obscurecollsize))
                    {
                        if (avoidobj->staticinfo->staticheader.staticCollInfo.avoidcollmodifier == 0.0f)
                        {
                            goto skipobscurecollsizedirectioncheck;
                        }

                        destToObjDist = fsqrt(destToObjMag2);
                        vecScalarDivide(destToObjNorm,destToObj,destToObjDist,temp);

                        if (destToObjDist < (GetCollSizeInDirection(avoidobj,destToObjNorm)))
                        {
skipobscurecollsizedirectioncheck:
                            if (bitTest(aishipflags,AISHIP_ReturnImmedIfPointObscured))
                            {
                                aishipStatsClose();
                                PTEND(2);
                                return AISHIP_FLY_OBJECT_IN_WAY;
                            }
                            else
                            {
                                returnflag |= AISHIP_FLY_OBJECT_IN_WAY;
                            }
                        }
                    }
skipobscurecheck:;
                }

                // test if repulsion vector is opposite of desired velocity by USE_PASS_RIGHT_ANGLE
                // so that we can add pass on right to repulsion vector so objects
                // will be able to go around each other
                testExactOpposite = vecDotProduct(repulsenorm,targetVec);
                if (testExactOpposite < 0)
                {
                    real32 targetVecMag2 = vecMagnitudeSquared(targetVec);
                    if (targetVecMag2 >= (20.0f*20.0f))     // must be at least 20m away before we consider passing on right (avoid jitter)
                    {
                        if ((testExactOpposite*testExactOpposite) >= (targetVecMag2 * USE_PASS_RIGHT_ANGLE))
                        {
                            vector passright;
                            vector upvector = { 0.0f,0.0f,1.0f };
                            real32 passrightmag;

                            vecCrossProduct(passright,targetVec,upvector);
                            vecNormalize(&passright);

                            passrightmag = repulsedist * PASS_RIGHT_FACTOR;
                            vecMultiplyByScalar(passright,passrightmag);

                            aishipStatsPassingOnRight(passright);

                            vecAddTo(repulse,passright);
                        }
                    }
                }

    #if DEBUG_AISHIP
                dbgMessagef("\nAVOID %x: (%f %f %f)",avoidobj,repulse.x,repulse.y,repulse.z);
    #endif
                aishipStatsRepulse(repulse);

                vecAddTo(desiredVel,repulse);

        nextnode:
                objindex++;
            }

        nextpass:
            pass++;
            if (pass < 2)
            {
                goto passagain;
            }


#ifndef HW_Release
            //dbgMessagef("\nAVOID considered: %d",considered);
#endif

noavoid:

        #if DEBUG_AISHIP
            dbgMessagef("\nFinal Desired Velocity: (%f %f %f)",desiredVel.x,desiredVel.y,desiredVel.z);
        #endif

            aishipStatsDesiredVel(desiredVel);

            if ((aishipflags & (AISHIP_StopIfVelocityTooLow|AISHIP_DontFlyToObscuredPoints)) &&
                isBetweenExclusive(desiredVel.x,NEG_AVOID_MIN_VEL,AVOID_MIN_VEL) &&
                isBetweenExclusive(desiredVel.y,NEG_AVOID_MIN_VEL,AVOID_MIN_VEL) &&
                isBetweenExclusive(desiredVel.z,NEG_AVOID_MIN_VEL,AVOID_MIN_VEL))
            {
                if (bitTest(aishipflags,AISHIP_ReturnImmedIfPointObscured))
                {
                    aishipStatsClose();
                    PTEND(2);
                    return AISHIP_FLY_OBJECT_IN_WAY;
                }
                else
                {
                    returnflag |= AISHIP_FLY_OBJECT_IN_WAY;
                }
                vecZeroVector(desiredVel);
            }
            else if (bitTest(aishipflags,AISHIP_PointInDirectionFlying))
            {
                if (shipstaticinfo->pitchdescend != 0.0f)
                {
                    // calculate normalize distance left
                    if ((destination) && (ship->aidescend != 0.0f))
                    {
                        zdistanceleft = (destination->z - ship->posinfo.position.z) * abs(ship->aidescend);
                        pitchtodescend = getPitchToDescend(zdistanceleft,shipstaticinfo->pitchdescend);
#if DEBUG_AISHIP
                        dbgMessagef("\nzdistanceleft: %f pitchtodescend: %f",zdistanceleft,pitchtodescend);
#endif
                    }
                    else
                    {
                        pitchtodescend = 0.0f;
                    }
                    // use desiredVel for heading, because the heading does not have to be noramlized for this function

                    if ((destination) && (ship->aidescend < 0.0f))
                    {
                        matGetVectFromMatrixCol3(desiredHead,ship->rotinfo.coordsys);
                        aitrackHeadingWithBankPitch(ship,&desiredHead,FLYSHIP_HEADINGACCURACY,shipstaticinfo->sinbank,pitchtodescend,shipstaticinfo->pitchturn);
                    }
                    else
                        aitrackHeadingWithBankPitch(ship,&desiredVel,FLYSHIP_HEADINGACCURACY,shipstaticinfo->sinbank,pitchtodescend,shipstaticinfo->pitchturn);
                }
                else
                {
                    vecCopyAndNormalize(&desiredVel,&desiredHead);
                    aitrackHeadingWithBank(ship,&desiredHead,FLYSHIP_HEADINGACCURACY,shipstaticinfo->sinbank);
                }
            }

            if (takeOutInMyWay)
                attackSimple(ship,takeOutInMyWay);

            aitrackVelocityVector(ship,&desiredVel);

            aishipStatsClose();
            PTEND(2);
            return returnflag;

/*=============================================================================
    Unknown State:
=============================================================================*/

        default:
            dbgAssert(FALSE);
            aishipStatsClose();
            PTEND(2);
            return 0;
    }
}

void aishipFlyMissileToTarget(Missile *missile,SpaceObjRotImpTarg *target)
{
    MissileStaticInfo *missilestaticinfo = (MissileStaticInfo *)missile->staticinfo;
    //vector desiredHead;
    real32 missilevel;
    real32 missiletraveltime;
    vector distToTargetVec;
    real32 distToTargetMagSqr, distToTargetMag;
    vector deltatarget;

    vecSub(distToTargetVec,target->collInfo.collPosition,missile->posinfo.position);

    distToTargetMagSqr = vecMagnitudeSquared(distToTargetVec);

    if (distToTargetMagSqr > MISSILE_USE_VELOCITYPRED_DISTANCE)
    {
        goto gottargettrajectory;
    }

    missilevel = fsqrt(vecMagnitudeSquared(missile->posinfo.velocity));

    if (missilevel < 200.0f)
    {
        goto gottargettrajectory;
    }

    distToTargetMag = fsqrt(distToTargetMagSqr);

    missiletraveltime = distToTargetMag / missilevel;

    vecScalarMultiply(deltatarget,target->posinfo.velocity,missiletraveltime);

    vecAddTo(distToTargetVec,deltatarget);

gottargettrajectory:
    //vecMultiplyByScalar(distToTargetVec,MISSILE_SCALE_FACTOR);

    vecCapMinMaxVector(&distToTargetVec,missilestaticinfo->staticheader.maxvelocity,missilestaticinfo->staticheader.maxvelocity);

    //vecCopyAndNormalize(&desiredVel,&desiredHead);
    //aitrackHeadingWithFlags(missile,&desiredHead,FLYSHIP_ATTACKACCURACY,AITRACKHEADING_DONTROLL+AITRACKHEADING_IGNOREUPVEC);
    aitrackVelocityVectorGuidance((SpaceObjRotImpTargGuidance *)missile,&distToTargetVec);
}


/*-----------------------------------------------------------------------------
    Name        : shouldMissileLoseTarget
    Description : checks certain criteria and sees if missile should lose its target
    Inputs      : missile
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
bool shouldMissileLoseTarget(Missile *missile)
{
    real32 rannum;
    real32 prob;
    vector roughDist;
    ObjType objtype = missile->target->objtype;

    if(objtype == OBJ_ShipType)
    {
        //if captial class we can't miss unless we're stupid
        if(isCapitalShip(((Ship *)missile->target)))
            return FALSE;

        vecSub(roughDist,missile->target->posinfo.position,missile->posinfo.position);

        if(isBetweenInclusive(roughDist.x,-tweakMinDistBeforeTargetLoss,tweakMinDistBeforeTargetLoss) &&
            isBetweenInclusive(roughDist.y,-tweakMinDistBeforeTargetLoss,tweakMinDistBeforeTargetLoss) &&
            isBetweenInclusive(roughDist.z,-tweakMinDistBeforeTargetLoss,tweakMinDistBeforeTargetLoss))
        {
            //target is within range to be considered for target loss
            missile->haveCheckedForLoss++;   //indicate check

         prob = 0.0f;

            prob = tweakRandomNess[((Ship *)missile->target)->tacticstype];
            if( ((Ship *)missile->target)->isDodging == TRUE)
            {
                prob+= tweakBoostIfDodging;
            }
            if( ((Ship *)missile->target)->flightman != FLIGHTMAN_NULL)
            {
                prob+=tweakBoostIfFlightMan;
            }

            rannum = frandombetween(0.0,1.0f);

            if(rannum < prob)
            {
                //satisfied: Target aquisition lost!
                return TRUE;
            }
        }
    }
    else if ((objtype == OBJ_DustType) || (objtype == OBJ_NebulaType))
    {
        vecSub(roughDist,missile->target->posinfo.position,missile->posinfo.position);

        if(isBetweenInclusive(roughDist.x,-200.0f,200.0f) &&
            isBetweenInclusive(roughDist.y,-200.0f,200.0f) &&
            isBetweenInclusive(roughDist.z,-200.0f,200.0f))
        {
            return TRUE;
        }
    }
    return FALSE;
}

/*-----------------------------------------------------------------------------
    Name        : aishipGuideMissile
    Description : flys missile
    Inputs      : missile
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
bool aishipGuideMissile(Missile *missile)
{
    SpaceObjRotImpTarg *target = missile->target;
    vector heading;

    if (missile->flags & SOF_Dead)
    {
        return FALSE;
    }

    if (target == NULL)
    {
        ;   //let missile just keep flying
    }
    else
    {
        aishipFlyMissileToTarget(missile,target);
        if(missile->target->flags & SOF_Cloaked)
        {
            missile->target = NULL;
        }
        else if(missile->haveCheckedForLoss < tweakNumTimesCheck)
        {
            if((universe.univUpdateCounter & tweakCheckMissileTargetLossRate) == (universe.univUpdateCounter & missile->missileID.missileNumber))
            {
                if(shouldMissileLoseTarget(missile))
                {
                    missile->target = NULL;
                }
            }
        }
    }

    heading = missile->posinfo.velocity;
    if ((heading.x != 0.0f) || (heading.y != 0.0f) || (heading.z != 0.0f))
    {
        vecNormalize(&heading);
        matCreateCoordSysFromHeading(&missile->rotinfo.coordsys,&heading);
        vecZeroVector(missile->rotinfo.rotspeed);
        vecZeroVector(missile->rotinfo.torque);
    }

    missile->posinfo.isMoving = TRUE;

    missile->timelived += universe.phystimeelapsed;
    if (missile->timelived > missile->totallifetime)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

bool aishipFlyMineToTarget(Missile *mine,SpaceObjRotImpTarg *target)
{
    MissileStaticInfo *minestaticinfo = (MissileStaticInfo *)mine->staticinfo;
    vector desiredVel;
    //vector desiredHead;
    real32 mine_rangesqr;
    real32 max_velocity;
    real32 max_velocitySQR;
    if(mine->FORCE_DROPPED)
    {
        mine_rangesqr = minestaticinfo->MINE_RANGESQR_FORCED;
        max_velocity = minestaticinfo->maxvelocity_FORCED;
        max_velocitySQR = minestaticinfo->maxvelocity_FORCED*minestaticinfo->maxvelocity_FORCED;
    }
    else
    {
        mine_rangesqr = minestaticinfo->MINE_RANGESQR;
        max_velocity = minestaticinfo->staticheader.maxvelocity;
        max_velocitySQR =minestaticinfo->staticheader.maxvelocity*minestaticinfo->staticheader.maxvelocity;
    }

    vecSub(desiredVel,target->collInfo.collPosition,mine->posinfo.position);
    //if(vecMagnitudeSquared(desiredVel) > mine_rangesqr)
    if(!MoveReachedDestinationVariable((Ship *)mine,&target->collInfo.collPosition,minestaticinfo->MINE_RANGESQR_FORCED))

    {
        //target flew out of range.
        mine->target = NULL;
        return(FALSE);
    }
    else if(mine->target != NULL)
    {
        if(mine->target->flags & SOF_Cloaked || mine->target->flags &SOF_Disabled)
        {
            mine->target = NULL;
            return FALSE;
        }
    }
    //checks targets speed and only goes after it if it can
    //have a chance of reaching it
    if(vecDotProduct(desiredVel,target->posinfo.velocity) > 0 )
    {
        //target is flying away from mine...check velocity now
        if(vecMagnitudeSquared(target->posinfo.velocity) > max_velocitySQR)
        {
            //target is flying faster than mine can go so don't follow it!
            mine->target = NULL;
            return(FALSE);
        }
    }

    if ((target->objtype != -1) && (target->collMyBlob))
    {
        // we've got a "real" target here
        if (mine->collMyBlob != target->collMyBlob)
        {
            if (mine->collMyBlob)
            {
                // let's take it out of our blob
                bobRemoveMineFromSpecificBlob(mine->collMyBlob,mine);
            }

            // put it in targets blob
            collAddSpaceObjToSpecificBlob(target->collMyBlob,(SpaceObj *)mine);
        }
    }

    vecNormalizeToLength(&desiredVel,max_velocity);
    //vecCapMinMaxVector(&desiredVel,MINE_MIN_VELOCITY,max_velocity);

    //vecCopyAndNormalize(&desiredVel,&desiredHead);

    // TAKE out track mines with right vector to spin them - save cpu
    //matGetVectFromMatrixCol1(desiredHead,mine->rotinfo.coordsys);       //track mines right vector always...spins then.
    //aitrackHeadingWithFlags(mine,&desiredHead,FLYSHIP_ATTACKACCURACY,AITRACKHEADING_IGNOREUPVEC);

    aitrackVelocityVectorGuidance((SpaceObjRotImpTargGuidance *)mine,&desiredVel);
    return(TRUE);
}

bool aishipslowminewithFriction(Missile *mine, real32 fric)
{
    if(isBetweenInclusive(mine->posinfo.velocity.x,-1.0f,1.0f) &&
        isBetweenInclusive(mine->posinfo.velocity.y,-1.0f,1.0f) &&
        isBetweenInclusive(mine->posinfo.velocity.z,-1.0f,1.0f))
    {
        mine->posinfo.velocity.x = 0.0f;
        mine->posinfo.velocity.y = 0.0f;
        mine->posinfo.velocity.z = 0.0f;
        mine->rotinfo.rotspeed.x = 0.0f;
        mine->rotinfo.rotspeed.y = 0.0f;
        mine->rotinfo.rotspeed.z = 0.0f;
        return(TRUE);
    }
    else
    {
        mine->posinfo.velocity.x *= fric;
        mine->posinfo.velocity.y *= fric;
        mine->posinfo.velocity.z *= fric;
        mine->rotinfo.rotspeed.x *= fric;
        mine->rotinfo.rotspeed.y *= fric;
        mine->rotinfo.rotspeed.z *= fric;
    }
    return(FALSE);
}


#define blobSearchRight  1
#define blobSearchLeft  2

SpaceObjRotImpTarg *baseObj;
blob *baseBlob;
sdword blobDirection;
sdword blobIndex;
extern BlobProperties collBlobProperties;


//SEARCH Right...then
//SEARCH Left
Ship *getShipNearObjTok(SpaceObjRotImpTarg *obj, real32 range)
{

    Ship *blobShip;
    vector difvec;
    real32 dif;

    if(obj != NULL)
    {
        //starting
        if(obj->collMyBlob == NULL)
            return NULL;

        blobDirection = blobSearchRight;
        baseBlob = obj->collMyBlob;
        baseObj = obj;
        blobIndex = 0;
    }
    //continue or starting...doesn't matter
newBlob:
    for(;blobIndex < baseBlob->blobShips->numShips;blobIndex++)
    {
        //scan blob list
        blobShip = baseBlob->blobShips->ShipPtr[blobIndex];
        if(MoveReachedDestinationVariable((Ship *)blobShip,&baseObj->collInfo.collPosition,range))
        {
            //found ship within range
            blobIndex++;
            return blobShip;
        }
    }

    //blob expired...no more ships
    if(blobDirection == blobSearchRight)
    {
needanotherblob:
        if(baseBlob->node.next == NULL)
        {
            //done going right...go left
            //first reset blob though
startleft:
            blobDirection = blobSearchLeft;
            baseBlob=baseObj->collMyBlob;
            goto leftsearch;
        }

        baseBlob = (blob *)listGetStructOfNode(baseBlob->node.next);
        blobIndex = 0;
        //check if in range

        //this blob is further than normal blob
        //dif = baseBlob->sqrtSortDistance-baseObj->collMyBlob->sqrtSortDistance;
        //dif = dif + baseObj->collMyBlob->radius - collBlobProperties.bobBiggestRadius;
        dif = (baseBlob->sqrtSortDistance-collBlobProperties.bobBiggestRadius)-
               (baseObj->collMyBlob->sqrtSortDistance+baseObj->collOptimizeDist);
        if(dif > range)
        {
            //blob list exhausted to right
            goto startleft;
        }

        //blob is possible
        vecSub(difvec,baseObj->collInfo.collPosition,baseBlob->centre);
        if(!MoveReachedDestinationVariable((Ship *)baseObj,&baseBlob->centre,range+collBlobProperties.bobBiggestRadius))
        {
            //blob edge not in range...consider another
            goto needanotherblob;
        }
        //good
        goto newBlob;

    }
    else
    {
leftsearch:
        if(baseBlob->node.prev == NULL)
        {
            //done going left..return NULL
            return NULL;
        }

        //get next left blob
        baseBlob = (blob *)listGetStructOfNode(baseBlob->node.prev);

        //consider if in range
                //this blob is further than normal blob
        //dif = baseObj->collMyBlob->sqrtSortDistance - baseBlob->sqrtSortDistance;
        //dif = dif + collBlobProperties.bobBiggestRadius - baseObj->collMyBlob->radius;
        dif = (baseObj->collOptimizeDist+baseObj->collMyBlob->sqrtSortDistance)-
               (baseBlob->sqrtSortDistance+collBlobProperties.bobBiggestRadius);
        if(dif > range)
        {
            //blob list exhausted to right
            return NULL;
        }

        //blob is possible
        vecSub(difvec,baseObj->collInfo.collPosition,baseBlob->centre);
        if(!MoveReachedDestinationVariable((Ship *)baseObj,&baseBlob->centre,range+baseBlob->radius))
        {
            //blob edge not in range...consider another
            goto leftsearch;
        }
        //good
        goto newBlob;
    }
    return NULL;
}

Ship *aishipmineaquiretarget(Missile *mine)
{
    blob *MyBlob;
    SelectCommand *shipselection;
    sdword shipindex = 0;
    Ship *ship;
    Ship *ShiptoRecieveShitPounding = NULL;
    //real32 distanceSqr;
    //vector diff;
    real32 mine_range;

    MissileStaticInfo *minestaticinfo = (MissileStaticInfo *)mine->staticinfo;

    MyBlob = mine->collMyBlob;
    shipselection = MyBlob->blobShips;
    if(mine->FORCE_DROPPED)
    {
        mine_range = minestaticinfo->mineRangeForced;
    }
    else
    {
        mine_range = minestaticinfo->mineRange;
    }

    ship = getShipNearObjTok((SpaceObjRotImpTarg *)mine,mine_range);

    while (ship != NULL)
    {
        //ship = shipselection->ShipPtr[shipindex];

        //ship = (Ship *)listGetStructOfNode(shipnode);

        if ( (ship->playerowner == mine->playerowner) ||
             (allianceIsShipAlly(ship,mine->playerowner)) )
        {   //don't target are ships....
            goto nextnode;
        }
        if(ship->flags & SOF_Cloaked)
        {
            if(!proximityCanPlayerSeeShip(mine->playerowner,ship))
            {
                goto nextnode;
            }
        }
        if(ship->flags & SOF_Disabled)
        {
            goto nextnode;
        }
        //here we decide not to attack allies as well :)

        //add more rejections to speed it up

        /*
        vecSub(diff,ship->posinfo.position,mine->posinfo.position);
        distanceSqr = diff.x*diff.x + diff.y*diff.y + diff.z*diff.z;

        if(distanceSqr < minestaticinfo->MINE_RANGESQR)
        */
        {
            if(ShiptoRecieveShitPounding == NULL)
            {
                ShiptoRecieveShitPounding = ship;
            }
            else if(ship->staticinfo->staticheader.mass > ShiptoRecieveShitPounding->staticinfo->staticheader.mass)
            {
                ShiptoRecieveShitPounding = ship;
            }
            else if (ship->staticinfo->staticheader.mass == ShiptoRecieveShitPounding->staticinfo->staticheader.mass)
            {   //if masses are the same
                if(randombetween(1,2) & 1)
                {   //randomly decide one over the other (that way, a group of fighters will
                   ShiptoRecieveShitPounding = ship;   // be attacked equally)
                }
            }
        }

    nextnode:
        //shipindex++;
        ship = getShipNearObjTok(NULL, mine_range);
    }
    return(ShiptoRecieveShitPounding);
}

// If it is time, it orders a search for a target
bool mine_do_search(Missile *mine)
{
    if((universe.univUpdateCounter & MINE_DO_SEARCH_MASK) == (mine->missileID.missileNumber & MINE_DO_SEARCH_MASK))
    {   //only do a check ever MINE_DO_SEARCH_MASKth frame...much quicker!
        if(mine->target == NULL)
        {
            mine->target = (SpaceObjRotImpTarg *)aishipmineaquiretarget(mine);
        }
        if(mine->target != NULL)
        {
            return(TRUE);
        }
    }
    return(FALSE);
}

/*-----------------------------------------------------------------------------
    Name        : aishipGuideMine
    Description : controls mine
    Inputs      : missile of type mine
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
bool aishipGuideMine(Missile *mine)
{
    //Ship *ship;
    MissileStaticInfo *minestaticinfo = (MissileStaticInfo *)mine->staticinfo;
    vector dist;
    real32 distsqr;
    MinelayerCorvetteStatics *minelayercorvettestatics;

    if (mine->flags & SOF_Dead)
    {
        return FALSE;
    }

    dbgAssert(mine->missileType == MISSILE_Mine);

    if (mine->target)
    {
        switch (mine->target->objtype)
        {
            case OBJ_MissileType:
            case OBJ_AsteroidType:
            case OBJ_NebulaType:
            case OBJ_DustType:
                mine->target = NULL;
        }
    }

    switch(mine->mineAIState)
    {
    case MINE_DROP_ATTACK:                      //Fix exit path of mines here
        mine->mineAIState = MINE_ATTACK_RUN;
        //this is the state where mines are being dropped during an attack
        break;
    case MINE_DROP_FORMATION:     //starting of wall formation
        if(mine->owner != NULL)
        {
            vecSub(dist,mine->posinfo.position,mine->owner->posinfo.position);
            minelayercorvettestatics = (MinelayerCorvetteStatics *)((ShipStaticInfo *)mine->owner->staticinfo)->custstatinfo;
            distsqr = vecMagnitudeSquared(dist);
            if(distsqr > minelayercorvettestatics->MineClearDistanceSQR)
            {
                mine->mineAIState = DO_WALL_FORMATION;
            }
        }
        else
        {
            mine->mineAIState = DO_WALL_FORMATION;
        }
        break;
    case DO_WALL_FORMATION:
        if(mine_do_search(mine))        //always search for enemy after launching is cleared
        {
            mine->mineAIState = MINE_ATTACK_RUN;
        }
        if(!MoveReachedDestinationVariable((Ship *)mine,&mine->formation_position,10.0f))
        {
            SpaceObjRotImpTarg dest_faker;
            dest_faker.objtype = -1;
            dest_faker.collInfo.collPosition = mine->formation_position;
            vecSet(dest_faker.posinfo.velocity, 0.0f,0.0f,0.0f);
            dest_faker.flags = 0;
            aishipFlyMineToTarget(mine,&dest_faker);
        }
        else
        {
            //we've reached the destination so Slow down...
            aishipslowminewithFriction(mine, minestaticinfo->MINE_STOP_FRICTION);
        }

        break;
    case MINE_SEARCH_AND_STOP:
        aishipslowminewithFriction(mine, minestaticinfo->MINE_STOP_FRICTION);
        if(mine_do_search(mine))        //always search for enemy after launching is cleared
        {
            mine->mineAIState = MINE_ATTACK_RUN;
        }
        break;
    case MINE_ATTACK_RUN:
        if(mine->target == NULL)
        {   //Lost target...so stop, reform wall and search for new target
            //check if too far from formation location
            if(mine->formationinfo == NULL)
            {
                mine->mineAIState = MINE_SEARCH_AND_STOP;
            }
            else
            {
                mine->mineAIState = DO_WALL_FORMATION;
            }
                break;
        }
        else
        {
            if(!aishipFlyMineToTarget(mine,mine->target))
            {
                //lost target, so reform wall
                mine->mineAIState = DO_WALL_FORMATION;
            }
        }
        break;
    default:
        dbgAssert(FALSE);
        break;
    }

    mine->posinfo.isMoving = TRUE;

    // all mines, including force dropped ones, now expire
    mine->timelived += universe.phystimeelapsed;
    if (mine->timelived > mine->totallifetime)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }

    return FALSE;
}

