warzone2100/src/action.c

3029 lines
88 KiB
C

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2010 Warzone 2100 Project
Warzone 2100 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Warzone 2100 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file action.c
*
* Functions for setting the action of a droid.
*
*/
#include "lib/framework/frame.h"
#include "lib/script/script.h"
#include "lib/sound/audio.h"
#include "lib/sound/audio_id.h"
#include "lib/netplay/netplay.h"
#include "action.h"
#include "combat.h"
#include "geometry.h"
#include "intdisplay.h"
#include "mission.h"
#include "projectile.h"
#include "random.h"
#include "research.h"
#include "scriptcb.h"
#include "scripttabs.h"
#include "transporter.h"
/* attack run distance */
#define VTOL_ATTACK_LENGTH 1000
#define VTOL_ATTACK_WIDTH 200
#define VTOL_ATTACK_TARDIST 400
// turret rotation limit
#define VTOL_TURRET_LIMIT DEG(45)
#define VTOL_TURRET_LIMIT_BOMB DEG(60)
#define VTOL_ATTACK_AUDIO_DELAY (3*GAME_TICKS_PER_SEC)
/** Time to pause before a droid blows up. */
#define ACTION_DESTRUCT_TIME 2000
/** Droids heavier than this rotate and pitch more slowly. */
#define HEAVY_WEAPON_WEIGHT 50000
#define ACTION_TURRET_ROTATION_RATE 45
#define REPAIR_PITCH_LOWER 30
#define REPAIR_PITCH_UPPER -15
/* How many tiles to pull back. */
#define PULL_BACK_DIST 10
// data required for any action
typedef struct _droid_action_data
{
DROID_ACTION action;
UDWORD x,y;
//multiple action target info
BASE_OBJECT *psObj;
BASE_STATS *psStats;
} DROID_ACTION_DATA;
// Check if a droid has stopped moving
#define DROID_STOPPED(psDroid) \
(psDroid->sMove.Status == MOVEINACTIVE || psDroid->sMove.Status == MOVEHOVER || \
psDroid->sMove.Status == MOVESHUFFLE)
/** Radius for search when looking for VTOL landing position */
static const int vtolLandingRadius = 23;
/**
* @typedef tileMatchFunction
*
* @brief pointer to a 'tile search function', used by spiralSearch()
*
* @param x,y are the coordinates that should be inspected.
*
* @param data a pointer to state data, allows the search function to retain
* state in between calls and can be used as a means of returning
* its result to the caller of spiralSearch().
*
* @return true when the search has finished, false when the search should
* continue.
*/
typedef bool (*tileMatchFunction)(int x, int y, void* matchState);
const char* getDroidActionName(DROID_ACTION action)
{
static const char* name[] =
{
"DACTION_NONE", // not doing anything
"DACTION_MOVE", // 1 moving to a location
"DACTION_BUILD", // building a structure
"DACTION_BUILD_FOUNDATION", // 3 building a foundation for a structure
"DACTION_DEMOLISH", // demolishing a structure
"DACTION_REPAIR", // 5 repairing a structure
"DACTION_ATTACK", // attacking something
"DACTION_OBSERVE", // 7 observing something
"DACTION_FIRESUPPORT", // attacking something visible by a sensor droid
"DACTION_SULK", // 9 refuse to do anything aggressive for a fixed time
"DACTION_DESTRUCT", // self destruct
"DACTION_TRANSPORTOUT", // 11 move transporter offworld
"DACTION_TRANSPORTWAITTOFLYIN", // wait for timer to move reinforcements in
"DACTION_TRANSPORTIN", // 13 move transporter onworld
"DACTION_DROIDREPAIR", // repairing a droid
"DACTION_RESTORE", // 15 restore resistance points of a structure
"DACTION_CLEARWRECK", // clearing building wreckage
"DACTION_MOVEFIRE", // 17
"DACTION_MOVETOBUILD", // moving to a new building location
"DACTION_MOVETODEMOLISH", // 19 moving to a new demolition location
"DACTION_MOVETOREPAIR", // moving to a new repair location
"DACTION_BUILDWANDER", // 21 moving around while building
"DACTION_FOUNDATION_WANDER", // moving around while building the foundation
"DACTION_MOVETOATTACK", // 23 moving to a target to attack
"DACTION_ROTATETOATTACK", // rotating to a target to attack
"DACTION_MOVETOOBSERVE", // 25 moving to be able to see a target
"DACTION_WAITFORREPAIR", // waiting to be repaired by a facility
"DACTION_MOVETOREPAIRPOINT", // 27 move to repair facility repair point
"DACTION_WAITDURINGREPAIR", // waiting to be repaired by a facility
"DACTION_MOVETODROIDREPAIR", // 29 moving to a new location next to droid to be repaired
"DACTION_MOVETORESTORE", // moving to a low resistance structure
"DACTION_MOVETOCLEAR", // 31 moving to a building wreck location
"DACTION_MOVETOREARM", // (32)moving to a rearming pad - VTOLS
"DACTION_WAITFORREARM", // (33)waiting for rearm - VTOLS
"DACTION_MOVETOREARMPOINT", // (34)move to rearm point - VTOLS - this actually moves them onto the pad
"DACTION_WAITDURINGREARM", // (35)waiting during rearm process- VTOLS
"DACTION_VTOLATTACK", // (36) a VTOL droid doing attack runs
"DACTION_CLEARREARMPAD", // (37) a VTOL droid being told to get off a rearm pad
"DACTION_RETURNTOPOS", // (38) used by scout/patrol order when returning to route
"DACTION_FIRESUPPORT_RETREAT", // (39) used by firesupport order when sensor retreats
"ACTION UNKNOWN",
"DACTION_CIRCLE" // (41) circling while engaging
};
ASSERT_OR_RETURN(NULL, action < sizeof(name) / sizeof(name[0]), "DROID_ACTION out of range: %u", action);
return name[action];
}
/* Check if a target is at correct range to attack */
static BOOL actionInAttackRange(DROID *psDroid, BASE_OBJECT *psObj, int weapon_slot)
{
SDWORD dx, dy, dz, radSq, rangeSq, longRange;
WEAPON_STATS *psStats;
int compIndex;
CHECK_DROID(psDroid);
if (psDroid->asWeaps[0].nStat == 0)
{
return false;
}
dx = (SDWORD)psDroid->pos.x - (SDWORD)psObj->pos.x;
dy = (SDWORD)psDroid->pos.y - (SDWORD)psObj->pos.y;
dz = (SDWORD)psDroid->pos.z - (SDWORD)psObj->pos.z;
radSq = dx*dx + dy*dy;
compIndex = psDroid->asWeaps[weapon_slot].nStat;
ASSERT_OR_RETURN( false, compIndex < numWeaponStats, "Invalid range referenced for numWeaponStats, %d > %d", compIndex, numWeaponStats);
psStats = asWeaponStats + compIndex;
if (psDroid->order == DORDER_ATTACKTARGET
&& secondaryGetState(psDroid, DSO_HALTTYPE) == DSS_HALT_HOLD)
{
longRange = proj_GetLongRange(psStats);
rangeSq = longRange * longRange;
}
else
{
switch (psDroid->secondaryOrder & DSS_ARANGE_MASK)
{
case DSS_ARANGE_DEFAULT:
//if (psStats->shortHit > psStats->longHit)
if (weaponShortHit(psStats, psDroid->player) > weaponLongHit(psStats, psDroid->player))
{
rangeSq = psStats->shortRange * psStats->shortRange;
}
else
{
longRange = proj_GetLongRange(psStats);
rangeSq = longRange * longRange;
}
break;
case DSS_ARANGE_SHORT:
rangeSq = psStats->shortRange * psStats->shortRange;
break;
case DSS_ARANGE_LONG:
longRange = proj_GetLongRange(psStats);
rangeSq = longRange * longRange;
break;
default:
ASSERT(!"unknown attackrange order", "unknown attack range order");
longRange = proj_GetLongRange(psStats);
rangeSq = longRange * longRange;
break;
}
}
/* check max range */
if ( radSq <= rangeSq )
{
/* check min range */
rangeSq = psStats->minRange * psStats->minRange;
if ( radSq >= rangeSq || !proj_Direct( psStats ) )
{
return true;
}
}
return false;
}
// check if a target is within weapon range
BOOL actionInRange(DROID *psDroid, BASE_OBJECT *psObj, int weapon_slot)
{
SDWORD dx, dy, dz, radSq, rangeSq, longRange;
WEAPON_STATS *psStats;
int compIndex;
CHECK_DROID(psDroid);
if (psDroid->asWeaps[0].nStat == 0)
{
return false;
}
compIndex = psDroid->asWeaps[weapon_slot].nStat;
ASSERT_OR_RETURN( false, compIndex < numWeaponStats, "Invalid range referenced for numWeaponStats, %d > %d", compIndex, numWeaponStats);
psStats = asWeaponStats + compIndex;
dx = (SDWORD)psDroid->pos.x - (SDWORD)psObj->pos.x;
dy = (SDWORD)psDroid->pos.y - (SDWORD)psObj->pos.y;
dz = (SDWORD)psDroid->pos.z - (SDWORD)psObj->pos.z;
radSq = dx*dx + dy*dy;
longRange = proj_GetLongRange(psStats);
rangeSq = longRange * longRange;
/* check max range */
if ( radSq <= rangeSq )
{
/* check min range */
rangeSq = psStats->minRange * psStats->minRange;
if ( radSq >= rangeSq || !proj_Direct( psStats ) )
{
return true;
}
}
return false;
}
// check if a target is inside minimum weapon range
BOOL actionInsideMinRange(DROID *psDroid, BASE_OBJECT *psObj, WEAPON_STATS *psStats)
{
SDWORD dx, dy, dz, radSq, rangeSq, minRange;
CHECK_DROID(psDroid);
CHECK_OBJECT(psObj);
if (!psStats)
{
psStats = getWeaponStats(psDroid, 0);
}
/* if I am a multi-turret droid */
if (psDroid->asWeaps[0].nStat == 0)
{
return false;
}
dx = (SDWORD)psDroid->pos.x - (SDWORD)psObj->pos.x;
dy = (SDWORD)psDroid->pos.y - (SDWORD)psObj->pos.y;
dz = (SDWORD)psDroid->pos.z - (SDWORD)psObj->pos.z;
radSq = dx*dx + dy*dy;
minRange = (SDWORD)psStats->minRange;
rangeSq = minRange * minRange;
// check min range
if ( radSq <= rangeSq )
{
return true;
}
return false;
}
// Realign turret
void actionAlignTurret(BASE_OBJECT *psObj, int weapon_slot)
{
int32_t rotation;
uint16_t nearest = 0;
uint16_t tRot;
uint16_t tPitch;
//default turret rotation 0
tRot = 0;
//get the maximum rotation this frame
rotation = gameTimeAdjustedIncrement(DEG(ACTION_TURRET_ROTATION_RATE));
switch (psObj->type)
{
case OBJ_DROID:
tRot = ((DROID *)psObj)->asWeaps[weapon_slot].rot.direction;
tPitch = ((DROID *)psObj)->asWeaps[weapon_slot].rot.pitch;
break;
case OBJ_STRUCTURE:
tRot = ((STRUCTURE *)psObj)->asWeaps[weapon_slot].rot.direction;
tPitch = ((STRUCTURE *)psObj)->asWeaps[weapon_slot].rot.pitch;
// now find the nearest 90 degree angle
nearest = (uint16_t)((tRot + DEG(45)) / DEG(90) * DEG(90)); // Cast wrapping indended.
break;
default:
ASSERT(!"invalid object type", "invalid object type");
return;
}
tRot += clip(angleDelta(nearest - tRot), -rotation, rotation); // Addition wrapping intended.
// align the turret pitch
tPitch += clip(angleDelta(0 - tPitch), -rotation/2, rotation/2); // Addition wrapping intended.
switch (psObj->type)
{
case OBJ_DROID:
((DROID *)psObj)->asWeaps[weapon_slot].rot.direction = tRot;
((DROID *)psObj)->asWeaps[weapon_slot].rot.pitch = tPitch;
break;
case OBJ_STRUCTURE:
((STRUCTURE *)psObj)->asWeaps[weapon_slot].rot.direction = tRot;
((STRUCTURE *)psObj)->asWeaps[weapon_slot].rot.pitch = tPitch;
break;
default:
ASSERT(!"invalid object type", "invalid object type");
return;
}
}
/* returns true if on target */
BOOL actionTargetTurret(BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget, WEAPON *psWeapon)
{
WEAPON_STATS *psWeapStats = asWeaponStats + psWeapon->nStat;
uint16_t tRotation, tPitch;
int32_t rotRate, pitchRate;
uint16_t targetRotation, targetPitch;
int32_t pitchError;
int32_t rotationError, dx, dy, dz;
int32_t rotationTolerance = 0;
bool onTarget;
int32_t dxy;
int32_t pitchLowerLimit, pitchUpperLimit;
bool bInvert;
bool bRepair;
if (!psTarget)
{
return false;
}
/* check whether turret position inverted vertically on body */
bInvert = psAttacker->type == OBJ_DROID && !cyborgDroid((DROID *)psAttacker) && isVtolDroid((DROID *)psAttacker);
bRepair = psAttacker->type == OBJ_DROID && ((DROID *)psAttacker)->droidType == DROID_REPAIR;
// these are constants now and can be set up at the start of the function
rotRate = DEG(ACTION_TURRET_ROTATION_RATE) * 4;
pitchRate = DEG(ACTION_TURRET_ROTATION_RATE) * 2;
// extra heavy weapons on some structures need to rotate and pitch more slowly
if (psWeapStats->weight > HEAVY_WEAPON_WEIGHT && !bRepair)
{
UDWORD excess = DEG(100) * (psWeapStats->weight - HEAVY_WEAPON_WEIGHT) / psWeapStats->weight;
rotRate = DEG(ACTION_TURRET_ROTATION_RATE) * 2 - excess;
pitchRate = rotRate / 2;
}
tRotation = psWeapon->rot.direction;
tPitch = psWeapon->rot.pitch;
//set the pitch limits based on the weapon stats of the attacker
pitchLowerLimit = pitchUpperLimit = 0;
if (psAttacker->type == OBJ_STRUCTURE)
{
pitchLowerLimit = DEG(psWeapStats->minElevation);
pitchUpperLimit = DEG(psWeapStats->maxElevation);
}
else if (psAttacker->type == OBJ_DROID)
{
DROID *psDroid = (DROID *)psAttacker;
if (psDroid->droidType == DROID_WEAPON || psDroid->droidType == DROID_TRANSPORTER
|| psDroid->droidType == DROID_COMMAND || psDroid->droidType == DROID_CYBORG
|| psDroid->droidType == DROID_CYBORG_SUPER)
{
pitchLowerLimit = DEG(psWeapStats->minElevation);
pitchUpperLimit = DEG(psWeapStats->maxElevation);
}
else if ( psDroid->droidType == DROID_REPAIR )
{
pitchLowerLimit = DEG(REPAIR_PITCH_LOWER);
pitchUpperLimit = DEG(REPAIR_PITCH_UPPER);
}
}
//get the maximum rotation this frame
rotRate = gameTimeAdjustedIncrement(rotRate);
rotRate = MAX(rotRate, DEG(1));
pitchRate = gameTimeAdjustedIncrement(pitchRate);
pitchRate = MAX(pitchRate, DEG(1));
//and point the turret at target
targetRotation = calcDirection(psAttacker->pos.x, psAttacker->pos.y, psTarget->pos.x, psTarget->pos.y);
//restrict rotationerror to =/- 180 degrees
rotationError = angleDelta(targetRotation - (tRotation + psAttacker->rot.direction));
tRotation += clip(rotationError, -rotRate, rotRate); // Addition wrapping intentional.
if (psAttacker->type == OBJ_DROID && isVtolDroid((DROID *)psAttacker))
{
// limit the rotation for vtols
int32_t limit = VTOL_TURRET_LIMIT;
if (psWeapStats->weaponSubClass == WSC_BOMB || psWeapStats->weaponSubClass == WSC_EMP)
{
limit = 0; // Don't turn bombs.
rotationTolerance = VTOL_TURRET_LIMIT_BOMB;
}
tRotation = (uint16_t)clip(angleDelta(tRotation), -limit, limit); // Cast wrapping intentional.
}
onTarget = abs(angleDelta(targetRotation - (tRotation + psAttacker->rot.direction))) <= rotationTolerance;
/* set muzzle pitch if direct fire */
if (!bRepair && (proj_Direct(psWeapStats) || ((psAttacker->type == OBJ_DROID)
&& !proj_Direct(psWeapStats)
&& actionInsideMinRange((DROID *)psAttacker, psTarget, psWeapStats))))
{
dx = psTarget->pos.x - psAttacker->pos.x;
dy = psTarget->pos.y - psAttacker->pos.y;
dz = psTarget->pos.z - psAttacker->pos.z;
/* get target distance */
dxy = iHypot(dx, dy);
targetPitch = iAtan2(dz, dxy);
/* invert calculations for bottom-mounted weapons (i.e. for vtols) */
//if (bInvert) { why do anything here? }
pitchError = angleDelta(targetPitch - tPitch);
tPitch += clip(pitchError, -pitchRate, pitchRate); // Addition wrapping intended.
tPitch = (uint16_t)clip(angleDelta(tPitch), pitchLowerLimit, pitchUpperLimit); // Cast wrapping intended.
onTarget = onTarget && targetPitch == tPitch;
/* re-invert result for bottom-mounted weapons (i.e. for vtols) */
//if (bInvert) { why do anything here? }
}
psWeapon->rot.direction = tRotation;
psWeapon->rot.pitch = tPitch;
return onTarget;
}
// return whether a droid can see a target to fire on it
BOOL actionVisibleTarget(DROID *psDroid, BASE_OBJECT *psTarget, int weapon_slot)
{
WEAPON_STATS *psStats;
int compIndex;
CHECK_DROID(psDroid);
ASSERT_OR_RETURN(false, psTarget != NULL, "Target is NULL");
if (psDroid->numWeaps == 0)
{
if ( visibleObject((BASE_OBJECT*)psDroid, psTarget, false) )
{
return true;
}
}
if (isVtolDroid(psDroid))
{
if ( visibleObject((BASE_OBJECT*)psDroid, psTarget, false) )
{
return true;
}
return false;
}
compIndex = psDroid->asWeaps[weapon_slot].nStat;
ASSERT_OR_RETURN( false, compIndex < numWeaponStats, "Invalid range referenced for numWeaponStats, %d > %d", compIndex, numWeaponStats);
psStats = asWeaponStats + compIndex;
if (proj_Direct(psStats))
{
if (visibleObject((BASE_OBJECT*)psDroid, psTarget, true))
{
return true;
}
}
else
{
// indirect can only attack things they can see unless attacking
// through a sensor droid - see DORDER_FIRESUPPORT
if (orderState(psDroid, DORDER_FIRESUPPORT))
{
if (psTarget->visible[psDroid->player])
{
return true;
}
}
else
{
if (visibleObject((BASE_OBJECT*)psDroid, psTarget, false))
{
return true;
}
}
}
return false;
}
static void actionAddVtolAttackRun( DROID *psDroid )
{
SDWORD deltaX, deltaY, iA, iX, iY;
BASE_OBJECT *psTarget;
#if 0
SDWORD iVx, iVy;
#endif
CHECK_DROID(psDroid);
if ( psDroid->psActionTarget[0] != NULL )
{
psTarget = psDroid->psActionTarget[0];
}
else if ( psDroid->psTarget != NULL )
{
psTarget = psDroid->psTarget;
}
else
{
return;
}
/* get normal vector from droid to target */
deltaX = psTarget->pos.x - psDroid->pos.x;
deltaY = psTarget->pos.y - psDroid->pos.y;
/* get magnitude of normal vector (Pythagorean theorem) */
iA = iHypot(deltaX, deltaY);
#if 0
/* get left perpendicular to normal vector:
* swap normal vector elements and negate y:
* scale to attack ellipse width
*/
iVx = deltaY * VTOL_ATTACK_WIDTH / iA;
iVy = -deltaX * VTOL_ATTACK_WIDTH / iA;
/* add waypoint left perpendicular to target*/
iX = psTarget->pos.x + iVx;
iY = psTarget->pos.y + iVy;
#endif
/* add waypoint behind target attack length away*/
if (iA != 0)
{
iX = psTarget->pos.x + (deltaX * VTOL_ATTACK_LENGTH / iA);
iY = psTarget->pos.y + (deltaY * VTOL_ATTACK_LENGTH / iA);
}
else
{
// We should only ever get here if both deltaX and deltaY
// are zero (look at the above pythagoeran theorem).
// This code is here to prevent a divide by zero error
//
// The next values are valid because if both deltas are zero
// then iA will be zero as well resulting in:
// (deltaXY * VTOL_ATTACK_LENGTH / iA) = (0 * VTOL_ATTACK_LENGTH / 0) = (0 / 0) = 0
iX = psTarget->pos.x;
iY = psTarget->pos.y;
}
if (iX <= 0
|| iY<=0
|| iX > world_coord(GetWidthOfMap())
|| iY > world_coord(GetHeightOfMap()))
{
debug( LOG_NEVER, "*** actionAddVtolAttackRun: run off map! ***" );
}
else
{
moveDroidToDirect( psDroid, iX, iY );
}
}
static void actionUpdateVtolAttack( DROID *psDroid )
{
WEAPON_STATS *psWeapStats[DROID_MAXWEAPS] = { NULL, NULL, NULL };
UBYTE i;
CHECK_DROID(psDroid);
/* don't do attack runs whilst returning to base */
if ( psDroid->order == DORDER_RTB )
{
return;
}
/* if I am a multi-turret droid */
if (psDroid->numWeaps > 1)
{
for(i = 0;i < psDroid->numWeaps;i++)
{
if (psDroid->asWeaps[i].nStat != 0)
{
psWeapStats[i] = asWeaponStats + psDroid->asWeaps[i].nStat;
ASSERT(psWeapStats != NULL, "invalid weapon stats pointer");
break;
}
}
}
else
{
if (psDroid->asWeaps[0].nStat > 0)
{
psWeapStats[0] = asWeaponStats + psDroid->asWeaps[0].nStat;
ASSERT(psWeapStats != NULL, "invalid weapon stats pointer");
}
}
/* order back to base after fixed number of attack runs */
if ( psWeapStats[0] != NULL )
{
if (vtolEmpty(psDroid))
{
moveToRearm(psDroid);
return;
}
}
/* circle around target if hovering and not cyborg */
if (psDroid->sMove.Status == MOVEHOVER && !cyborgDroid(psDroid))
{
actionAddVtolAttackRun( psDroid );
}
}
static void actionUpdateTransporter( DROID *psDroid )
{
CHECK_DROID(psDroid);
//check if transporter has arrived
if (updateTransporter(psDroid))
{
// Got to destination
psDroid->action = DACTION_NONE;
return;
}
}
// calculate a position for units to pull back to if they
// need to increase the range between them and a target
static void actionCalcPullBackPoint(BASE_OBJECT *psObj, BASE_OBJECT *psTarget, SDWORD *px, SDWORD *py)
{
SDWORD xdiff,ydiff, len;
// get the vector from the target to the object
xdiff = (SDWORD)psObj->pos.x - (SDWORD)psTarget->pos.x;
ydiff = (SDWORD)psObj->pos.y - (SDWORD)psTarget->pos.y;
len = iHypot(xdiff, ydiff);
if (len == 0)
{
xdiff = TILE_UNITS;
ydiff = TILE_UNITS;
}
else
{
xdiff = (xdiff * TILE_UNITS) / len;
ydiff = (ydiff * TILE_UNITS) / len;
}
// create the position
*px = (SDWORD)psObj->pos.x + xdiff * PULL_BACK_DIST;
*py = (SDWORD)psObj->pos.y + ydiff * PULL_BACK_DIST;
// make sure coordinates stay inside of the map
clip_world_offmap(px, py);
}
// check whether a droid is in the neighboring tile to a build position
BOOL actionReachedBuildPos(DROID *psDroid, SDWORD x, SDWORD y, BASE_STATS *psStats)
{
SDWORD width, breadth, tx,ty, dx,dy;
ASSERT_OR_RETURN(false, psStats != NULL && psDroid != NULL, "Bad stat or droid");
CHECK_DROID(psDroid);
// do all calculations in half tile units so that
// the droid moves to within half a tile of the target
// NOT ANY MORE - JOHN
dx = map_coord(psDroid->pos.x);
dy = map_coord(psDroid->pos.y);
if (StatIsStructure(psStats))
{
width = ((STRUCTURE_STATS *)psStats)->baseWidth;
breadth = ((STRUCTURE_STATS *)psStats)->baseBreadth;
}
else
{
width = ((FEATURE_STATS *)psStats)->baseWidth;
breadth = ((FEATURE_STATS *)psStats)->baseBreadth;
}
tx = map_coord(x);
ty = map_coord(y);
// move the x,y to the top left of the structure
tx -= width/2;
ty -= breadth/2;
if ( (dx == (tx -1)) || (dx == (tx + width)) )
{
// droid could be at either the left or the right
if ( (dy >= (ty -1)) && (dy <= (ty + breadth)) )
{
return true;
}
}
else if ( (dy == (ty -1)) || (dy == (ty + breadth)) )
{
// droid could be at either the top or the bottom
if ( (dx >= (tx -1)) && (dx <= (tx + width)) )
{
return true;
}
}
return false;
}
// check if a droid is on the foundations of a new building
static BOOL actionDroidOnBuildPos(DROID *psDroid, SDWORD x, SDWORD y, BASE_STATS *psStats)
{
SDWORD width, breadth, tx,ty, dx,dy;
CHECK_DROID(psDroid);
ASSERT_OR_RETURN(false, psStats != NULL, "Bad stat");
dx = map_coord(psDroid->pos.x);
dy = map_coord(psDroid->pos.y);
if (StatIsStructure(psStats))
{
width = ((STRUCTURE_STATS *)psStats)->baseWidth;
breadth = ((STRUCTURE_STATS *)psStats)->baseBreadth;
}
else
{
width = ((FEATURE_STATS *)psStats)->baseWidth;
breadth = ((FEATURE_STATS *)psStats)->baseBreadth;
}
tx = map_coord(x) - (width / 2);
ty = map_coord(y) - (breadth / 2);
if (dx >= tx
&& dx < tx + width
&& dy >= ty
&& dy < ty + breadth)
{
return true;
}
return false;
}
// return the position of a players home base
// MAY return an invalid (0, 0) position on MP maps!
static void actionHomeBasePos(SDWORD player, SDWORD *px, SDWORD *py)
{
STRUCTURE *psStruct;
ASSERT_OR_RETURN(, player >= 0 && player < MAX_PLAYERS, "Invalid player number %d", (int)player);
for(psStruct = apsStructLists[player]; psStruct; psStruct=psStruct->psNext)
{
if (psStruct->pStructureType->type == REF_HQ)
{
*px = (SDWORD)psStruct->pos.x;
*py = (SDWORD)psStruct->pos.y;
return;
}
}
*px = getLandingX(player);
*py = getLandingY(player);
}
// Update the action state for a droid
void actionUpdateDroid(DROID *psDroid)
{
BASE_OBJECT *psTarget;
PROPULSION_STATS *psPropStats;
BOOL (*actionUpdateFunc)(DROID *psDroid) = NULL;
signed int i;
unsigned int j;
//this is a bit field
bool nonNullWeapon[DROID_MAXWEAPS] = { false };
BASE_OBJECT *psTargets[DROID_MAXWEAPS];
bool hasVisibleTarget = false;
bool targetVisibile[DROID_MAXWEAPS] = { false };
bool bHasTarget;
CHECK_DROID(psDroid);
psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
ASSERT_OR_RETURN(, psPropStats != NULL, "Invalid propulsion stats pointer");
// clear the target if it has died
for (i = 0; i < DROID_MAXWEAPS; i++)
{
if (psDroid->psActionTarget[i] && psDroid->psActionTarget[i]->died)
{
setDroidActionTarget(psDroid, NULL, i);
if (i == 0)
{
if (psDroid->action != DACTION_MOVEFIRE &&
psDroid->action != DACTION_TRANSPORTIN &&
psDroid->action != DACTION_TRANSPORTOUT)
{
psDroid->action = DACTION_NONE;
// if VTOL - return to rearm pad if not patrolling
if (isVtolDroid(psDroid))
{
if (psDroid->order == DORDER_PATROL)
{
// Back to the patrol.
actionDroidLoc(psDroid, DACTION_MOVE, psDroid->orderX,psDroid->orderY);
}
else
{
moveToRearm(psDroid);
}
}
}
}
}
}
//if the droid has been attacked by an EMP weapon, it is temporarily disabled
if (psDroid->lastHitWeapon == WSC_EMP)
{
if (gameTime - psDroid->timeLastHit > EMP_DISABLE_TIME)
{
//the actionStarted time needs to be adjusted
psDroid->actionStarted += (gameTime - psDroid->timeLastHit);
//reset the lastHit parameters
psDroid->timeLastHit = 0;
psDroid->lastHitWeapon = UDWORD_MAX;
}
else
{
//get out without updating
return;
}
}
for (i = 0; i < psDroid->numWeaps; ++i)
{
if (psDroid->asWeaps[i].nStat > 0)
{
nonNullWeapon[i] = true;
}
}
// HACK: Apparently we can't deal with a droid that only has NULL weapons ?
// FIXME: Find out whether this is really necessary
if (psDroid->numWeaps <= 1)
nonNullWeapon[0] = true;
psTarget = psDroid->psTarget;
switch (psDroid->action)
{
case DACTION_NONE:
case DACTION_WAITFORREPAIR:
// doing nothing
// see if there's anything to shoot.
if (psDroid->numWeaps > 0 && !isVtolDroid(psDroid)
&& (psDroid->order == DORDER_NONE || psDroid->order == DORDER_TEMP_HOLD || psDroid->order == DORDER_RTR))
{
for (i = 0;i < psDroid->numWeaps;i++)
{
if (nonNullWeapon[i])
{
BASE_OBJECT *psTemp = NULL;
WEAPON_STATS* const psWeapStats = &asWeaponStats[psDroid->asWeaps[i].nStat];
if (psDroid->asWeaps[i].nStat > 0
&& psWeapStats->rotate
&& aiBestNearestTarget(psDroid, &psTemp, i, NULL) >= 0)
{
if (secondaryGetState(psDroid, DSO_ATTACK_LEVEL) != DSS_ALEV_ALWAYS)
{
psTemp = NULL;
}
else
{
psDroid->action = DACTION_ATTACK;
}
}
setDroidActionTarget(psDroid, psTemp, 0);
}
}
}
// Still not doing anything? See if we need to self repair.
if ((psDroid->action == DACTION_NONE || psDroid->action == DACTION_WAITFORREPAIR)
&& selfRepairEnabled(psDroid->player))
{
droidSelfRepair(psDroid);
}
break;
case DACTION_WAITDURINGREPAIR:
// Check that repair facility still exists
if (!psDroid->psTarget)
{
psDroid->action = DACTION_NONE;
break;
}
// move back to the repair facility if necessary
if (DROID_STOPPED(psDroid) &&
!actionReachedBuildPos(psDroid,
(SDWORD)psDroid->psTarget->pos.x,(SDWORD)psDroid->psTarget->pos.y,
(BASE_STATS *)((STRUCTURE*)psDroid->psTarget)->pStructureType ) )
{
moveDroidToNoFormation(psDroid, psDroid->psTarget->pos.x, psDroid->psTarget->pos.y);
}
break;
case DACTION_TRANSPORTWAITTOFLYIN:
//if we're moving droids to safety and currently waiting to fly back in, see if time is up
if (psDroid->player == selectedPlayer && getDroidsToSafetyFlag())
{
if ((SDWORD)(mission.ETA - (gameTime - missionGetReinforcementTime())) <= 0)
{
UDWORD droidX, droidY;
if (!droidRemove(psDroid, mission.apsDroidLists))
{
ASSERT_OR_RETURN( , false, "Unable to remove transporter from mission list" );
}
addDroid(psDroid, apsDroidLists);
//set the x/y up since they were set to INVALID_XY when moved offWorld
missionGetTransporterExit(selectedPlayer, &droidX, &droidY);
psDroid->pos.x = droidX;
psDroid->pos.y = droidY;
//fly Transporter back to get some more droids
orderDroidLoc( psDroid, DORDER_TRANSPORTIN,
getLandingX(selectedPlayer), getLandingY(selectedPlayer), ModeImmediate);
}
else
{
/*if we're currently moving units to safety and waiting to fly
back in - check there is something to fly back for!*/
if (!missionDroidsRemaining(selectedPlayer))
{
//the script can call startMission for this callback for offworld missions
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_START_NEXT_LEVEL);
}
}
}
break;
case DACTION_MOVE:
// moving to a location
if (DROID_STOPPED(psDroid))
{
// Got to destination
psDroid->action = DACTION_NONE;
/* notify scripts we have reached the destination
* also triggers when patrolling and reached a waypoint
*/
psScrCBOrder = psDroid->order;
psScrCBOrderDroid = psDroid;
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_DROID_REACH_LOCATION);
psScrCBOrderDroid = NULL;
psScrCBOrder = DORDER_NONE;
}
//added multiple weapon check
else if (psDroid->numWeaps > 0)
{
for(i = 0;i < psDroid->numWeaps;i++)
{
if (nonNullWeapon[i])
{
BASE_OBJECT *psTemp = NULL;
//I moved psWeapStats flag update there
WEAPON_STATS* const psWeapStats = &asWeaponStats[psDroid->asWeaps[i].nStat];
if (!isVtolDroid(psDroid)
&& psDroid->asWeaps[i].nStat > 0
&& psWeapStats->rotate
&& psWeapStats->fireOnMove != FOM_NO
&& aiBestNearestTarget(psDroid, &psTemp, i, NULL) >= 0)
{
if (secondaryGetState(psDroid, DSO_ATTACK_LEVEL) == DSS_ALEV_ALWAYS)
{
psDroid->action = DACTION_MOVEFIRE;
}
}
setDroidActionTarget(psDroid, psTemp, 0);
}
}
}
break;
case DACTION_RETURNTOPOS:
case DACTION_FIRESUPPORT_RETREAT:
if (DROID_STOPPED(psDroid))
{
// Got to destination
psDroid->action = DACTION_NONE;
}
break;
case DACTION_TRANSPORTIN:
case DACTION_TRANSPORTOUT:
actionUpdateTransporter( psDroid );
break;
case DACTION_MOVEFIRE:
//check if vtol that its armed
if (vtolEmpty(psDroid))
{
moveToRearm(psDroid);
}
bHasTarget = false;
//loop through weapons and look for target for each weapon
for (i = 0; i < psDroid->numWeaps; ++i)
{
if (psDroid->psActionTarget[i] != NULL && aiObjectIsProbablyDoomed(psDroid->psActionTarget[i]))
{
setDroidActionTarget(psDroid, NULL, i); // Target not worth shooting at anymore.
}
if (psDroid->psActionTarget[i] == NULL)
{
BASE_OBJECT *psTemp;
if (aiBestNearestTarget(psDroid, &psTemp, i, NULL) >= 0)
{
bHasTarget = true;
setDroidActionTarget(psDroid, psTemp, i);
}
}
if (psDroid->psActionTarget[i]
&& visibleObject((BASE_OBJECT*)psDroid, psDroid->psActionTarget[i], false))
{
hasVisibleTarget = true;
targetVisibile[i] = true;
}
}
for (j = 0;j < psDroid->numWeaps;j++)
{
//vtResult uses psActionTarget[0] for now since it's the first target
if (psDroid->psActionTarget[j] != NULL &&
validTarget((BASE_OBJECT *)psDroid, psDroid->psActionTarget[j], j))
{
// firing on something while moving
if (DROID_STOPPED(psDroid))
{
// Got to desitination
psDroid->action = DACTION_NONE;
break;
}
else if (psDroid->psActionTarget[j] == NULL
|| !validTarget((BASE_OBJECT *)psDroid, psDroid->psActionTarget[j], j)
|| (secondaryGetState(psDroid, DSO_ATTACK_LEVEL) != DSS_ALEV_ALWAYS))
{
if (j == (psDroid->numWeaps - 1) && !bHasTarget)
{
// Target lost
psDroid->action = DACTION_MOVE;
}
else
{
continue;
}
//if Vtol - return to rearm pad
/*if (isVtolDroid(psDroid))
{
moveToRearm(psDroid);
}*/
}
//check the target hasn't become one the same player ID - eg Electronic Warfare
else if (electronicDroid(psDroid) &&
(psDroid->player == psDroid->psActionTarget[j]->player))
{
setDroidActionTarget(psDroid, NULL, i);
psDroid->action = DACTION_NONE;
}
else
{
if (!hasVisibleTarget
&& !bHasTarget
&& j == (psDroid->numWeaps - 1))
{
// lost the target
psDroid->action = DACTION_MOVE;
for (i = 0; i < psDroid->numWeaps;i++)
{
setDroidActionTarget(psDroid, NULL, i);
}
}
if (targetVisibile[j])
{
bHasTarget = true;
//to fix a AA-weapon attack ground unit exploit
if (nonNullWeapon[j])
{
BASE_OBJECT* psActionTarget = psDroid->psActionTarget[j];
if (!psActionTarget)
{
if (targetVisibile[0])
{
psActionTarget = psDroid->psActionTarget[0];
}
}
if (psActionTarget && validTarget((BASE_OBJECT *)psDroid, psActionTarget, j)
&& actionTargetTurret((BASE_OBJECT*)psDroid, psActionTarget, &psDroid->asWeaps[j]))
{
// In range - fire !!!
combFire(&psDroid->asWeaps[j], (BASE_OBJECT *)psDroid, psActionTarget, j);
}
}
}
}
}
}
/* Extra bit of paranoia, in case none of the weapons have a target */
bHasTarget = false;
for (i = 0; i < psDroid->numWeaps; i++)
{
if (psDroid->psActionTarget[i] != NULL)
{
bHasTarget = true;
break;
}
}
if (!bHasTarget)
{
psDroid->action = DACTION_MOVE;
}
//check its a VTOL unit since adding Transporter's into multiPlayer
/* check vtol attack runs */
if (isVtolDroid(psDroid))
{
actionUpdateVtolAttack( psDroid );
}
break;
case DACTION_ATTACK:
ASSERT_OR_RETURN( , psDroid->psActionTarget[0] != NULL, "target is NULL while attacking");
//check the target hasn't become one the same player ID - Electronic Warfare
if ((electronicDroid(psDroid) && (psDroid->player == psDroid->psActionTarget[0]->player)))
{
for (i = 0;i < psDroid->numWeaps;i++)
{
setDroidActionTarget(psDroid, NULL, i);
}
psDroid->action = DACTION_NONE;
break;
}
bHasTarget = false;
for (i = 0;i < psDroid->numWeaps;i++)
{
BASE_OBJECT* psActionTarget;
if (i > 0)
{
// If we're ordered to shoot something, and we can, shoot it
if ((psDroid->order == DORDER_ATTACK || psDroid->order == DORDER_ATTACKTARGET) &&
psDroid->psActionTarget[i] != psDroid->psActionTarget[0] &&
validTarget((BASE_OBJECT *)psDroid, psDroid->psActionTarget[0], i) &&
actionInRange(psDroid, psDroid->psActionTarget[0], i))
{
setDroidActionTarget(psDroid, psDroid->psActionTarget[0], i);
}
// If we still don't have a target, try to find one
else
{
if (psDroid->psActionTarget[i] == NULL &&
aiChooseTarget((BASE_OBJECT*)psDroid, &psTargets[i], i, false, NULL)) // Can probably just use psTarget instead of psTargets[i], and delete the psTargets variable.
{
setDroidActionTarget(psDroid, psTargets[i], i);
}
}
}
// If main turret lost its target, but we're not ordered to attack any specific
// target, try to find a new one.
else if (psDroid->psActionTarget[0] == NULL &&
psDroid->order != DORDER_ATTACK &&
aiChooseTarget((BASE_OBJECT*)psDroid, &psTargets[i], i, false, NULL))
{
// FIXME What is this code path for?
// FIXME If psDroid->psActionTarget[0] == NULL, then actionVisibleTarget(psDroid, psActionTarget, i) crashes.
// FIXME And if aiChooseTarget above fails, psActionTarget stays NULL.
// FIXME So, assuming the game can't crash, psDroid->psActionTarget[0] is never NULL.
debug(LOG_NEVER, "Can this happen?");
setDroidActionTarget(psDroid, psTargets[i], i);
}
if (psDroid->psActionTarget[i])
{
psActionTarget = psDroid->psActionTarget[i];
}
else
{
psActionTarget = psDroid->psActionTarget[0];
}
if (nonNullWeapon[i]
&& actionVisibleTarget(psDroid, psActionTarget, i)
&& actionInRange(psDroid, psActionTarget, i)
&& (psDroid->order == DORDER_ATTACK
|| psDroid->order == DORDER_ATTACKTARGET
|| !aiObjectIsProbablyDoomed(psActionTarget)))
{
WEAPON_STATS* const psWeapStats = &asWeaponStats[psDroid->asWeaps[i].nStat];
bHasTarget = true;
if (validTarget((BASE_OBJECT *)psDroid, psActionTarget, i))
{
int dirDiff = 0;
if (!psWeapStats->rotate)
{
// no rotating turret - need to check aligned with target
const uint16_t targetDir = calcDirection(psDroid->pos.x, psDroid->pos.y, psActionTarget->pos.x, psActionTarget->pos.y);
dirDiff = abs(angleDelta(targetDir - psDroid->rot.direction));
}
if (dirDiff > FIXED_TURRET_DIR)
{
if (i > 0)
{
if (psDroid->psActionTarget[i] != psDroid->psActionTarget[0])
{
// Nope, can't shoot this, try something else next time
setDroidActionTarget(psDroid, NULL, i);
}
}
else if (psDroid->sMove.Status != MOVESHUFFLE)
{
psDroid->action = DACTION_ROTATETOATTACK;
moveTurnDroid(psDroid, psActionTarget->pos.x, psActionTarget->pos.y);
}
}
else if (!psWeapStats->rotate ||
actionTargetTurret((BASE_OBJECT*)psDroid, psActionTarget, &psDroid->asWeaps[i]))
{
/* In range - fire !!! */
combFire(&psDroid->asWeaps[i], (BASE_OBJECT *)psDroid, psActionTarget, i);
}
}
else if (i > 0)
{
// Nope, can't shoot this, try something else next time
setDroidActionTarget(psDroid, NULL, i);
}
}
else if (i > 0)
{
// Nope, can't shoot this, try something else next time
setDroidActionTarget(psDroid, NULL, i);
}
}
if (!bHasTarget)
{
if (((psDroid->order == DORDER_ATTACKTARGET
|| psDroid->order == DORDER_FIRESUPPORT)
&& secondaryGetState(psDroid, DSO_HALTTYPE) == DSS_HALT_HOLD)
|| (!isVtolDroid(psDroid)
&& (psTarget = orderStateObj(psDroid, DORDER_FIRESUPPORT))
&& psTarget->type == OBJ_STRUCTURE)
|| (psDroid->order == DORDER_NONE) || (psDroid->order == DORDER_TEMP_HOLD)
|| (psDroid->order == DORDER_RTR))
{
// don't move if on hold or firesupport for a sensor tower
// also don't move if we're holding position or waiting for repair
psDroid->action = DACTION_NONE; // holding, cancel the order.
}
else
{
psDroid->action = DACTION_MOVETOATTACK; // out of range - chase it
}
}
break;
case DACTION_VTOLATTACK:
{
WEAPON_STATS* psWeapStats = NULL;
//uses vtResult
if (psDroid->psActionTarget[0] != NULL &&
validTarget((BASE_OBJECT *)psDroid, psDroid->psActionTarget[0], 0))
{
//check if vtol that its armed
if ( (vtolEmpty(psDroid)) ||
(psDroid->psActionTarget[0] == NULL) ||
//check the target hasn't become one the same player ID - Electronic Warfare
(electronicDroid(psDroid) && (psDroid->player == psDroid->psActionTarget[0]->player)) ||
!validTarget((BASE_OBJECT *)psDroid, psDroid->psActionTarget[0], 0) )
{
moveToRearm(psDroid);
break;
}
for(i = 0;i <psDroid->numWeaps;i++)
{
if (nonNullWeapon[i]
&& validTarget((BASE_OBJECT *)psDroid, psDroid->psActionTarget[0], i))
{
//I moved psWeapStats flag update there
psWeapStats = &asWeaponStats[psDroid->asWeaps[i].nStat];
if (actionVisibleTarget(psDroid, psDroid->psActionTarget[0], i))
{
if ( actionInRange(psDroid, psDroid->psActionTarget[0], i) )
{
if ( psDroid->player == selectedPlayer )
{
audio_QueueTrackMinDelay( ID_SOUND_COMMENCING_ATTACK_RUN2,
VTOL_ATTACK_AUDIO_DELAY );
}
if (actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[i]))
{
// In range - fire !!!
combFire(&psDroid->asWeaps[i], (BASE_OBJECT *)psDroid,
psDroid->psActionTarget[0], i);
}
}
else
{
actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[i]);
}
}
}
}
}
/* else
{
// lost the target
psDroid->action = DACTION_MOVETOATTACK;
moveDroidTo(psDroid, psDroid->psActionTarget->pos.x, psDroid->psActionTarget->pos.y);
}*/
/* check vtol attack runs */
// actionUpdateVtolAttack( psDroid );
/* circle around target if hovering and not cyborg */
if (DROID_STOPPED(psDroid))
{
actionAddVtolAttackRun( psDroid );
}
else
{
// if the vtol is close to the target, go around again
const int xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psDroid->psActionTarget[0]->pos.x;
const int ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psDroid->psActionTarget[0]->pos.y;
const int rangeSq = xdiff * xdiff + ydiff * ydiff;
if (rangeSq < (VTOL_ATTACK_TARDIST*VTOL_ATTACK_TARDIST))
{
// don't do another attack run if already moving away from the target
const int xdiff = (SDWORD)psDroid->sMove.DestinationX - (SDWORD)psDroid->psActionTarget[0]->pos.x;
const int ydiff = (SDWORD)psDroid->sMove.DestinationY - (SDWORD)psDroid->psActionTarget[0]->pos.y;
if ((xdiff*xdiff + ydiff*ydiff) < (VTOL_ATTACK_TARDIST*VTOL_ATTACK_TARDIST))
{
actionAddVtolAttackRun( psDroid );
}
}
// in case psWeapStats is still NULL
else if (psWeapStats)
{ // if the vtol is far enough away head for the target again
if (rangeSq > (SDWORD)(psWeapStats->longRange * psWeapStats->longRange))
{
// don't do another attack run if already heading for the target
const int xdiff = (SDWORD)psDroid->sMove.DestinationX - (SDWORD)psDroid->psActionTarget[0]->pos.x;
const int ydiff = (SDWORD)psDroid->sMove.DestinationY - (SDWORD)psDroid->psActionTarget[0]->pos.y;
if ((xdiff*xdiff + ydiff*ydiff) > (VTOL_ATTACK_TARDIST*VTOL_ATTACK_TARDIST))
{
moveDroidToDirect(psDroid, psDroid->psActionTarget[0]->pos.x,psDroid->psActionTarget[0]->pos.y);
}
}
}
}
break;
}
case DACTION_MOVETOATTACK:
// send vtols back to rearm
if (isVtolDroid(psDroid) &&
vtolEmpty(psDroid))
{
moveToRearm(psDroid);
break;
}
ASSERT_OR_RETURN( , psDroid->psActionTarget[0] != NULL, "action update move to attack target is NULL");
//check the target hasn't become one the same player ID - Electronic Warfare
if ((electronicDroid(psDroid) && (psDroid->player == psDroid->psActionTarget[0]->player)) ||
!validTarget((BASE_OBJECT *)psDroid, psDroid->psActionTarget[0], 0) )
{
for (i = 0;i < psDroid->numWeaps;i++)
{
setDroidActionTarget(psDroid, NULL, i);
}
psDroid->action = DACTION_NONE;
}
else
{
if (actionVisibleTarget(psDroid, psDroid->psActionTarget[0], 0))
{
for(i = 0;i < psDroid->numWeaps;i++)
{
if (nonNullWeapon[i]
&& validTarget((BASE_OBJECT *)psDroid, psDroid->psActionTarget[0], i)
&& actionVisibleTarget(psDroid, psDroid->psActionTarget[0], i))
{
bool chaseBloke = false;
WEAPON_STATS* const psWeapStats = &asWeaponStats[psDroid->asWeaps[i].nStat];
if (psWeapStats->rotate)
{
actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[i]);
}
if (!isVtolDroid(psDroid) &&
psDroid->psActionTarget[0]->type == OBJ_DROID &&
((DROID *)psDroid->psActionTarget[0])->droidType == DROID_PERSON &&
psWeapStats->fireOnMove != FOM_NO)
{
chaseBloke = true;
}
if (actionInAttackRange(psDroid, psDroid->psActionTarget[0], i)
&& !chaseBloke)
{
/* Stop the droid moving any closer */
// ASSERT( psDroid->pos.x != 0 && psDroid->pos.y != 0,
// "moveUpdateUnit: Unit at (0,0)" );
/* init vtol attack runs count if necessary */
if ( psPropStats->propulsionType == PROPULSION_TYPE_LIFT )
{
psDroid->action = DACTION_VTOLATTACK;
//actionAddVtolAttackRun( psDroid );
//actionUpdateVtolAttack( psDroid );
}
else
{
moveStopDroid(psDroid);
if (psWeapStats->rotate)
{
psDroid->action = DACTION_ATTACK;
}
else
{
psDroid->action = DACTION_ROTATETOATTACK;
moveTurnDroid(psDroid, psDroid->psActionTarget[0]->pos.x,psDroid->psActionTarget[0]->pos.y);
}
}
}
else if ( actionInRange(psDroid, psDroid->psActionTarget[0], i) )
{
// fire while closing range
combFire(&psDroid->asWeaps[i], (BASE_OBJECT *)psDroid, psDroid->psActionTarget[0], i);
}
}
}
}
else
{
for(i = 0;i < psDroid->numWeaps;i++)
{
if ((psDroid->asWeaps[i].rot.direction != 0) ||
(psDroid->asWeaps[i].rot.pitch != 0))
{
actionAlignTurret((BASE_OBJECT *)psDroid, i);
}
}
}
if (DROID_STOPPED(psDroid) && psDroid->action != DACTION_ATTACK)
{
/* Stopped moving but haven't reached the target - possibly move again */
//'hack' to make the droid to check the primary turrent instead of all
WEAPON_STATS* const psWeapStats = &asWeaponStats[psDroid->asWeaps[0].nStat];
if (psDroid->order == DORDER_ATTACKTARGET
&& secondaryGetState(psDroid, DSO_HALTTYPE) == DSS_HALT_HOLD)
{
psDroid->action = DACTION_NONE; // on hold, give up.
}
else if (actionInsideMinRange(psDroid, psDroid->psActionTarget[0], psWeapStats))
{
if ( proj_Direct( psWeapStats ) )
{
SDWORD pbx, pby;
// try and extend the range
actionCalcPullBackPoint((BASE_OBJECT *)psDroid, psDroid->psActionTarget[0], &pbx,&pby);
moveDroidTo(psDroid, (UDWORD)pbx, (UDWORD)pby);
}
else
{
if (psWeapStats->rotate)
{
psDroid->action = DACTION_ATTACK;
}
else
{
psDroid->action = DACTION_ROTATETOATTACK;
moveTurnDroid( psDroid, psDroid->psActionTarget[0]->pos.x,
psDroid->psActionTarget[0]->pos.y);
}
}
}
else
{
// try to close the range
moveDroidTo(psDroid, psDroid->psActionTarget[0]->pos.x, psDroid->psActionTarget[0]->pos.y);
}
}
}
break;
case DACTION_SULK:
// unable to route to target ... don't do anything aggressive until time is up
// we need to do something defensive at this point ???
//hmmm, hope this doesn't cause any problems!
if (gameTime > psDroid->actionStarted)
{
// debug( LOG_NEVER, "sulk over.. %p", psDroid );
psDroid->action = DACTION_NONE; // Sulking is over lets get back to the action ... is this all I need to do to get it back into the action?
}
break;
case DACTION_ROTATETOATTACK:
// if (DROID_STOPPED(psDroid))
if (psDroid->sMove.Status != MOVETURNTOTARGET)
{
psDroid->action = DACTION_ATTACK;
}
break;
case DACTION_MOVETOBUILD:
if (!psDroid->psTarStats)
{
psDroid->action = DACTION_NONE;
break;
}
// moving to a location to build a structure
if (actionReachedBuildPos(psDroid,
(SDWORD)psDroid->orderX,(SDWORD)psDroid->orderY, psDroid->psTarStats) &&
!actionDroidOnBuildPos(psDroid,
(SDWORD)psDroid->orderX,(SDWORD)psDroid->orderY, psDroid->psTarStats))
{
bool helpBuild = false;
// Got to destination - start building
STRUCTURE_STATS* const psStructStats = (STRUCTURE_STATS*)psDroid->psTarStats;
uint16_t dir = psDroid->orderDirection;
moveStopDroid(psDroid);
objTrace(psDroid->id, "Halted in our tracks - at construction site");
if (psDroid->order == DORDER_BUILD && psDroid->psTarget == NULL)
{
// Starting a new structure
// calculate the top left of the structure
const UDWORD tlx = psDroid->orderX - getStructureStatsWidth(psStructStats, dir) * TILE_UNITS/2;
const UDWORD tly = psDroid->orderY - getStructureStatsBreadth(psStructStats, dir) * TILE_UNITS/2;
//need to check if something has already started building here?
//unless its a module!
if (IsStatExpansionModule(psStructStats))
{
syncDebug("Reached build target: module");
debug( LOG_NEVER, "DACTION_MOVETOBUILD: setUpBuildModule");
setUpBuildModule(psDroid);
}
else if (TileHasStructure(mapTile(map_coord(psDroid->orderX), map_coord(psDroid->orderY))))
{
// structure on the build location - see if it is the same type
STRUCTURE* const psStruct = getTileStructure(map_coord(psDroid->orderX), map_coord(psDroid->orderY));
if (psStruct->pStructureType == (STRUCTURE_STATS *)psDroid->psTarStats)
{
// same type - do a help build
syncDebug("Reached build target: do-help");
setDroidTarget(psDroid, (BASE_OBJECT *)psStruct);
helpBuild = true;
}
else if ((psStruct->pStructureType->type == REF_WALL ||
psStruct->pStructureType->type == REF_WALLCORNER) &&
((STRUCTURE_STATS *)psDroid->psTarStats)->type == REF_DEFENSE)
{
// building a gun tower over a wall - OK
if (droidStartBuild(psDroid))
{
syncDebug("Reached build target: tower");
debug( LOG_NEVER, "DACTION_MOVETOBUILD: start foundation");
psDroid->action = DACTION_BUILD;
}
else
{
syncDebug("Reached build target: wall-in-way");
psDroid->action = DACTION_NONE;
}
}
else
{
syncDebug("Reached build target: already-structure");
objTrace(psDroid->id, "DACTION_MOVETOBUILD: tile has structure already");
cancelBuild(psDroid);
}
}
else if (!validLocation((BASE_STATS*)psDroid->psTarStats,
map_coord(tlx),
map_coord(tly), dir,
psDroid->player,
false))
{
syncDebug("Reached build target: invalid");
objTrace(psDroid->id, "DACTION_MOVETOBUILD: !validLocation");
cancelBuild(psDroid);
}
else
{
syncDebug("Reached build target: build");
psDroid->action = DACTION_BUILD_FOUNDATION;
psDroid->actionStarted = gameTime;
psDroid->actionPoints = 0;
}
}
else if ((psDroid->order == DORDER_LINEBUILD || psDroid->order==DORDER_BUILD)
&& (psStructStats->type == REF_WALL || psStructStats->type == REF_WALLCORNER || psStructStats->type == REF_GATE ||
psStructStats->type == REF_DEFENSE || psStructStats->type == REF_REARM_PAD))
{
// building a wall.
MAPTILE* const psTile = mapTile(map_coord(psDroid->orderX), map_coord(psDroid->orderY));
syncDebug("Reached build target: wall");
if (psDroid->psTarget == NULL
&& (TileHasStructure(psTile)
|| TileHasFeature(psTile)))
{
if (TileHasStructure(psTile))
{
// structure on the build location - see if it is the same type
STRUCTURE* const psStruct = getTileStructure(map_coord(psDroid->orderX), map_coord(psDroid->orderY));
if (psStruct->pStructureType == (STRUCTURE_STATS *)psDroid->psTarStats)
{
// same type - do a help build
setDroidTarget(psDroid, (BASE_OBJECT *)psStruct);
helpBuild = true;
}
else if ((psStruct->pStructureType->type == REF_WALL || psStruct->pStructureType->type == REF_WALLCORNER) &&
((STRUCTURE_STATS *)psDroid->psTarStats)->type == REF_DEFENSE)
{
// building a gun tower over a wall - OK
if (droidStartBuild(psDroid))
{
debug( LOG_NEVER, "DACTION_MOVETOBUILD: start foundation");
psDroid->action = DACTION_BUILD;
}
else
{
psDroid->action = DACTION_NONE;
}
}
else
{
psDroid->action = DACTION_NONE;
}
}
else
{
psDroid->action = DACTION_NONE;
}
}
else if (droidStartBuild(psDroid))
{
psDroid->action = DACTION_BUILD;
intBuildStarted(psDroid);
}
else
{
psDroid->action = DACTION_NONE;
}
}
else
{
syncDebug("Reached build target: planned-help");
helpBuild = true;
}
if (helpBuild)
{
// continuing a partially built structure (order = helpBuild)
if (droidStartBuild(psDroid))
{
debug( LOG_NEVER, "DACTION_MOVETOBUILD: starting help build");
psDroid->action = DACTION_BUILD;
intBuildStarted(psDroid);
}
else
{
debug( LOG_NEVER, "DACTION_MOVETOBUILD: giving up (3)");
psDroid->action = DACTION_NONE;
}
}
}
else if (DROID_STOPPED(psDroid))
{
if (actionDroidOnBuildPos(psDroid,
(SDWORD)psDroid->orderX,(SDWORD)psDroid->orderY, psDroid->psTarStats))
{
SDWORD pbx = 0, pby = 0;
actionHomeBasePos(psDroid->player, &pbx, &pby);
if (pbx == 0 || pby == 0)
{
objTrace(psDroid->id, "DACTION_MOVETOBUILD: No HQ, cannot move in that direction");
psDroid->action = DACTION_NONE;
break;
}
objTrace(psDroid->id, "DACTION_MOVETOBUILD: Starting to drive inside construction site");
moveDroidToNoFormation(psDroid, (UDWORD)pbx,(UDWORD)pby);
}
else
{
objTrace(psDroid->id, "DACTION_MOVETOBUILD: Starting to drive toward construction site - move status was %d", (int)psDroid->sMove.Status);
moveDroidToNoFormation(psDroid, psDroid->actionX,psDroid->actionY);
}
}
break;
case DACTION_BUILD:
if (!psDroid->psTarStats)
{
psDroid->action = DACTION_NONE;
break;
}
if (DROID_STOPPED(psDroid) &&
!actionReachedBuildPos(psDroid,
(SDWORD)psDroid->orderX,(SDWORD)psDroid->orderY, psDroid->psTarStats))
{
objTrace(psDroid->id, "DACTION_BUILD: Starting to drive toward construction site");
moveDroidToNoFormation(psDroid, psDroid->orderX, psDroid->orderY);
}
else if (!DROID_STOPPED(psDroid) &&
psDroid->sMove.Status != MOVETURNTOTARGET &&
psDroid->sMove.Status != MOVESHUFFLE &&
actionReachedBuildPos(psDroid,
(SDWORD)psDroid->orderX,(SDWORD)psDroid->orderY, psDroid->psTarStats))
{
objTrace(psDroid->id, "DACTION_BUILD: Stopped - at construction site");
moveStopDroid(psDroid);
}
if (psDroid->action == DACTION_SULK)
{
objTrace(psDroid->id, "Failed to go to objective, aborting build action");
psDroid->action = DACTION_NONE;
break;
}
if (droidUpdateBuild(psDroid))
{
actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[0]);
}
break;
case DACTION_MOVETODEMOLISH:
case DACTION_MOVETOREPAIR:
case DACTION_MOVETOCLEAR:
case DACTION_MOVETORESTORE:
if (!psDroid->psTarStats)
{
psDroid->action = DACTION_NONE;
break;
}
// see if the droid is at the edge of what it is moving to
if (actionReachedBuildPos(psDroid,
(SDWORD)psDroid->actionX,(SDWORD)psDroid->actionY, psDroid->psTarStats) &&
!actionDroidOnBuildPos(psDroid,
(SDWORD)psDroid->actionX,(SDWORD)psDroid->actionY, psDroid->psTarStats))
{
moveStopDroid(psDroid);
// got to the edge - start doing whatever it was meant to do
switch (psDroid->action)
{
case DACTION_MOVETODEMOLISH:
psDroid->action = droidStartDemolishing(psDroid) ? DACTION_DEMOLISH : DACTION_NONE;
break;
case DACTION_MOVETOREPAIR:
psDroid->action = droidStartRepair(psDroid) ? DACTION_REPAIR : DACTION_NONE;
break;
case DACTION_MOVETOCLEAR:
psDroid->action = droidStartClearing(psDroid) ? DACTION_CLEARWRECK : DACTION_NONE;
break;
case DACTION_MOVETORESTORE:
psDroid->action = droidStartRestore(psDroid) ? DACTION_RESTORE : DACTION_NONE;
break;
default:
break;
}
}
else if (DROID_STOPPED(psDroid))
{
if (actionDroidOnBuildPos(psDroid,
(SDWORD)psDroid->actionX,(SDWORD)psDroid->actionY, psDroid->psTarStats))
{
SDWORD pbx = 0, pby = 0;
actionHomeBasePos(psDroid->player, &pbx, &pby);
if (pbx == 0 || pby == 0)
{
debug(LOG_NEVER, "No HQ - cannot move in that direction.");
psDroid->action = DACTION_NONE;
break;
}
moveDroidToNoFormation(psDroid, (UDWORD)pbx,(UDWORD)pby);
}
else
{
moveDroidToNoFormation(psDroid, psDroid->actionX,psDroid->actionY);
}
}
break;
case DACTION_DEMOLISH:
case DACTION_REPAIR:
case DACTION_CLEARWRECK:
case DACTION_RESTORE:
if (!psDroid->psTarStats)
{
psDroid->action = DACTION_NONE;
break;
}
// set up for the specific action
switch (psDroid->action)
{
case DACTION_DEMOLISH:
// DACTION_MOVETODEMOLISH;
actionUpdateFunc = droidUpdateDemolishing;
break;
case DACTION_REPAIR:
// DACTION_MOVETOREPAIR;
actionUpdateFunc = droidUpdateRepair;
break;
case DACTION_CLEARWRECK:
// DACTION_MOVETOCLEAR;
actionUpdateFunc = droidUpdateClearing;
break;
case DACTION_RESTORE:
// DACTION_MOVETORESTORE;
actionUpdateFunc = droidUpdateRestore;
break;
default:
break;
}
// now do the action update
if (DROID_STOPPED(psDroid) &&
!actionReachedBuildPos(psDroid,
(SDWORD)psDroid->actionX,(SDWORD)psDroid->actionY, psDroid->psTarStats))
{
if (secondaryGetState(psDroid, DSO_HALTTYPE) != DSS_HALT_HOLD ||
(psDroid->order != DORDER_NONE && psDroid->order != DORDER_TEMP_HOLD))
{
objTrace(psDroid->id, "Secondary order: Go to construction site");
moveDroidToNoFormation(psDroid, psDroid->actionX,psDroid->actionY);
}
else
{
psDroid->action = DACTION_NONE;
}
}
else if (!DROID_STOPPED(psDroid) &&
psDroid->sMove.Status != MOVETURNTOTARGET &&
psDroid->sMove.Status != MOVESHUFFLE &&
actionReachedBuildPos(psDroid,
(SDWORD)psDroid->actionX,(SDWORD)psDroid->actionY, psDroid->psTarStats))
{
objTrace(psDroid->id, "Stopped - reached build position");
moveStopDroid(psDroid);
}
else if ( actionUpdateFunc(psDroid) )
{
//use 0 for non-combat(only 1 'weapon')
actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[0]);
}
else if (psDroid->action == DACTION_CLEARWRECK)
{
//see if there is any other wreckage in the area
FEATURE* const psNextWreck = checkForWreckage(psDroid);
psDroid->action = DACTION_NONE;
if (psNextWreck)
{
orderDroidObj(psDroid, DORDER_CLEARWRECK, (BASE_OBJECT *)psNextWreck, ModeImmediate);
}
}
else
{
psDroid->action = DACTION_NONE;
}
break;
case DACTION_MOVETOREARMPOINT:
/* moving to rearm pad */
if (DROID_STOPPED(psDroid))
{
psDroid->action = DACTION_WAITDURINGREARM;
}
break;
case DACTION_MOVETOREPAIRPOINT:
/* moving from front to rear of repair facility or rearm pad */
if (actionReachedBuildPos(psDroid, psDroid->psActionTarget[0]->pos.x,psDroid->psActionTarget[0]->pos.y,
(BASE_STATS *)((STRUCTURE *)psDroid->psActionTarget[0])->pStructureType))
{
objTrace(psDroid->id, "Arrived at repair point - waiting for our turn");
moveStopDroid(psDroid);
psDroid->action = DACTION_WAITDURINGREPAIR;
}
else if (DROID_STOPPED(psDroid))
{
moveDroidToNoFormation(psDroid, psDroid->psActionTarget[0]->pos.x,
psDroid->psActionTarget[0]->pos.y);
}
break;
case DACTION_BUILD_FOUNDATION:
if (!psDroid->psTarStats)
{
psDroid->action = DACTION_NONE;
break;
}
//building a structure's foundation - flattening the ground for now
{
MAPTILE* const psTile = mapTile(map_coord(psDroid->orderX), map_coord(psDroid->orderY));
STRUCTURE_STATS* const psStructStats = (STRUCTURE_STATS*)psDroid->psTarStats;
uint16_t dir = psDroid->orderDirection;
const UDWORD tlx = psDroid->orderX - getStructureStatsWidth(psStructStats, dir) * TILE_UNITS/2;
const UDWORD tly = psDroid->orderY - getStructureStatsBreadth(psStructStats, dir) * TILE_UNITS/2;
if ((psDroid->psTarget == NULL) &&
(TileHasStructure(psTile) ||
TileHasFeature(psTile)))
{
if (TileHasStructure(psTile))
{
// structure on the build location - see if it is the same type
STRUCTURE* const psStruct = getTileStructure(map_coord(psDroid->orderX), map_coord(psDroid->orderY));
if (psStruct->pStructureType == (STRUCTURE_STATS *)psDroid->psTarStats)
{
// same type - do a help build
setDroidTarget(psDroid, (BASE_OBJECT *)psStruct);
}
else
{
psDroid->action = DACTION_NONE;
}
}
else if (!validLocation((BASE_STATS*)psDroid->psTarStats,
map_coord(tlx),
map_coord(tly), dir,
psDroid->player,
false))
{
psDroid->action = DACTION_NONE;
}
}
//ready to start building the structure
if ( psDroid->action != DACTION_NONE &&
droidStartBuild(psDroid))
{
debug( LOG_NEVER, "DACTION_BUILD_FOUNDATION: start build");
psDroid->action = DACTION_BUILD;
}
else
{
debug( LOG_NEVER, "DACTION_BUILD_FOUNDATION: giving up");
psDroid->action = DACTION_NONE;
}
}
break;
case DACTION_OBSERVE:
// align the turret
actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[0]);
// WSS shouldn't get a free pass to hit anything on map
if ((cbSensorDroid(psDroid) && asSensorStats[psDroid->asBits[COMP_SENSOR].nStat].type != SUPER_SENSOR)
|| objRadarDetector((BASE_OBJECT *)psDroid))
{
// don't move to the target, just make sure it is visible
// Anyone commenting this out will get a knee capping from John.
// You have been warned!!
psDroid->psActionTarget[0]->visible[psDroid->player] = UBYTE_MAX;
}
else
{
// make sure the target is within sensor range
const int xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psDroid->psActionTarget[0]->pos.x;
const int ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psDroid->psActionTarget[0]->pos.y;
int rangeSq = droidSensorRange(psDroid);
rangeSq = rangeSq * rangeSq;
if (!visibleObject((BASE_OBJECT *)psDroid, psDroid->psActionTarget[0], false)
|| xdiff * xdiff + ydiff * ydiff >= rangeSq)
{
if (secondaryGetState(psDroid, DSO_HALTTYPE) == DSS_HALT_HOLD &&
(psDroid->order == DORDER_NONE || psDroid->order == DORDER_TEMP_HOLD))
{
psDroid->action = DACTION_NONE;
}
else
{
psDroid->action = DACTION_MOVETOOBSERVE;
moveDroidTo(psDroid, psDroid->psActionTarget[0]->pos.x, psDroid->psActionTarget[0]->pos.y);
}
}
}
break;
case DACTION_MOVETOOBSERVE:
// align the turret
actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[0]);
if (visibleObject((BASE_OBJECT *)psDroid, psDroid->psActionTarget[0], false))
{
// make sure the target is within sensor range
const int xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psDroid->psActionTarget[0]->pos.x;
const int ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psDroid->psActionTarget[0]->pos.y;
int rangeSq = droidSensorRange(psDroid);
rangeSq = rangeSq * rangeSq;
if ((xdiff*xdiff + ydiff*ydiff < rangeSq) &&
!DROID_STOPPED(psDroid))
{
psDroid->action = DACTION_OBSERVE;
moveStopDroid(psDroid);
}
}
if (DROID_STOPPED(psDroid) && psDroid->action == DACTION_MOVETOOBSERVE)
{
moveDroidTo(psDroid, psDroid->psActionTarget[0]->pos.x, psDroid->psActionTarget[0]->pos.y);
}
break;
case DACTION_FIRESUPPORT:
if (!psDroid->psTarget)
{
psDroid->action = DACTION_NONE;
return;
}
//can be either a droid or a structure now - AB 7/10/98
ASSERT_OR_RETURN(, (psDroid->psTarget->type == OBJ_DROID || psDroid->psTarget->type == OBJ_STRUCTURE)
&& (psDroid->psTarget->player == psDroid->player), "DACTION_FIRESUPPORT: incorrect target type" );
//don't move VTOL's
// also don't move closer to sensor towers
if (!isVtolDroid(psDroid) &&
(psDroid->psTarget->type != OBJ_STRUCTURE))
{
//move droids to within short range of the sensor now!!!!
int xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psDroid->psTarget->pos.x;
int ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psDroid->psTarget->pos.y;
int rangeSq = asWeaponStats[psDroid->asWeaps[0].nStat].shortRange;
rangeSq = rangeSq * rangeSq;
if (xdiff*xdiff + ydiff*ydiff < rangeSq)
{
if (!DROID_STOPPED(psDroid))
{
moveStopDroid(psDroid);
}
}
else
{
if (!DROID_STOPPED(psDroid))
{
xdiff = (SDWORD)psDroid->psTarget->pos.x - (SDWORD)psDroid->sMove.DestinationX;
ydiff = (SDWORD)psDroid->psTarget->pos.y - (SDWORD)psDroid->sMove.DestinationY;
}
if (DROID_STOPPED(psDroid) ||
(xdiff*xdiff + ydiff*ydiff > rangeSq))
{
if (secondaryGetState(psDroid, DSO_HALTTYPE) == DSS_HALT_HOLD)
{
// droid on hold, don't allow moves.
psDroid->action = DACTION_NONE;
}
else
{
// move in range
moveDroidTo(psDroid, psDroid->psTarget->pos.x,psDroid->psTarget->pos.y);
}
}
}
}
//}
break;
case DACTION_DESTRUCT:
if ((psDroid->actionStarted + ACTION_DESTRUCT_TIME) < gameTime)
{
if ( psDroid->droidType == DROID_PERSON )
{
droidBurn(psDroid);
}
else
{
debug(LOG_DEATH, "Droid %d destructed", (int)psDroid->id);
destroyDroid(psDroid);
}
}
break;
case DACTION_MOVETODROIDREPAIR:
{
// moving to repair a droid
const int xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psDroid->psActionTarget[0]->pos.x;
const int ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psDroid->psActionTarget[0]->pos.y;
if ( xdiff*xdiff + ydiff*ydiff < REPAIR_RANGE )
{
// Got to destination - start repair
//rotate turret to point at droid being repaired
//use 0 for repair droid
actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[0]);
if (droidStartDroidRepair(psDroid))
{
psDroid->action = DACTION_DROIDREPAIR;
}
else
{
psDroid->action = DACTION_NONE;
}
}
if (DROID_STOPPED(psDroid))
{
// Couldn't reach destination - try and find a new one
psDroid->actionX = psDroid->psActionTarget[0]->pos.x;
psDroid->actionY = psDroid->psActionTarget[0]->pos.y;
moveDroidTo(psDroid, psDroid->actionX, psDroid->actionY);
}
break;
}
case DACTION_DROIDREPAIR:
{
int xdiff, ydiff;
// If not doing self-repair (psActionTarget[0] is repair target)
if (psDroid->psActionTarget[0] != (BASE_OBJECT *)psDroid)
{
actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[0]);
}
// Just self-repairing.
// See if there's anything to shoot.
else if (psDroid->numWeaps > 0 && !isVtolDroid(psDroid)
&& (psDroid->order == DORDER_NONE || psDroid->order == DORDER_TEMP_HOLD || psDroid->order == DORDER_RTR))
{
for (i = 0;i < psDroid->numWeaps;i++)
{
if (nonNullWeapon[i])
{
BASE_OBJECT *psTemp = NULL;
WEAPON_STATS* const psWeapStats = &asWeaponStats[psDroid->asWeaps[i].nStat];
if (psDroid->asWeaps[i].nStat > 0
&& psWeapStats->rotate
&& aiBestNearestTarget(psDroid, &psTemp, i, NULL) >= 0)
{
if (secondaryGetState(psDroid, DSO_ATTACK_LEVEL) != DSS_ALEV_ALWAYS)
{
psTemp = NULL;
}
}
if (psTemp)
{
psDroid->action = DACTION_ATTACK;
setDroidActionTarget(psDroid, psTemp, 0);
break;
}
}
}
}
if (psDroid->action != DACTION_DROIDREPAIR)
{
break; // action has changed
}
//check still next to the damaged droid
xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psDroid->psActionTarget[0]->pos.x;
ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psDroid->psActionTarget[0]->pos.y;
if (xdiff * xdiff + ydiff * ydiff > REPAIR_RANGE)
{
if (secondaryGetState(psDroid, DSO_HALTTYPE) != DSS_HALT_HOLD || psDroid->order == DORDER_REPAIR)
{
/*once started - don't allow the Repair droid to follow the
damaged droid for too long*/
/*if (psDroid->actionPoints)
{
if (gameTime - psDroid->actionStarted > KEEP_TRYING_REPAIR)
{
addConsoleMessage("Repair Droid has given up!",DEFAULT_JUSTIFY);
psDroid->action = DACTION_NONE;
break;
}
}*/
// damaged droid has moved off - follow if we're not holding position!
psDroid->actionX = psDroid->psActionTarget[0]->pos.x;
psDroid->actionY = psDroid->psActionTarget[0]->pos.y;
psDroid->action = DACTION_MOVETODROIDREPAIR;
moveDroidTo(psDroid, psDroid->actionX, psDroid->actionY);
}
else
{
psDroid->action = DACTION_NONE;
}
}
else
{
if (!droidUpdateDroidRepair(psDroid))
{
psDroid->action = DACTION_NONE;
//if the order is RTR then resubmit order so that the unit will go to repair facility point
if (orderState(psDroid,DORDER_RTR))
{
orderDroid(psDroid, DORDER_RTR, ModeImmediate);
}
}
else
{
// don't let the target for a repair shuffle
if (((DROID *)psDroid->psActionTarget[0])->sMove.Status == MOVESHUFFLE)
{
moveStopDroid((DROID *)psDroid->psActionTarget[0]);
}
}
}
break;
}
case DACTION_WAITFORREARM:
// wait here for the rearm pad to ask the vtol to move
if (psDroid->psActionTarget[0] == NULL)
{
// rearm pad destroyed - move to another
moveToRearm(psDroid);
break;
}
if (DROID_STOPPED(psDroid) && vtolHappy(psDroid))
{
// don't actually need to rearm so just sit next to the rearm pad
psDroid->action = DACTION_NONE;
}
break;
case DACTION_CLEARREARMPAD:
if (DROID_STOPPED(psDroid))
{
psDroid->action = DACTION_NONE;
}
break;
case DACTION_WAITDURINGREARM:
// this gets cleared by the rearm pad
break;
case DACTION_MOVETOREARM:
if (psDroid->psActionTarget[0] == NULL)
{
// base destroyed - find another
moveToRearm(psDroid);
break;
}
if (visibleObject((BASE_OBJECT *)psDroid, psDroid->psActionTarget[0], false))
{
STRUCTURE* const psStruct = findNearestReArmPad(psDroid, (STRUCTURE *)psDroid->psActionTarget[0], true);
// got close to the rearm pad - now find a clear one
objTrace(psDroid->id, "Seen rearm pad - searching for available one");
if (psStruct != NULL)
{
// found a clear landing pad - go for it
objTrace(psDroid->id, "Found clear rearm pad");
setDroidActionTarget(psDroid, (BASE_OBJECT *)psStruct, 0);
}
psDroid->action = DACTION_WAITFORREARM;
}
if (DROID_STOPPED(psDroid) ||
(psDroid->action == DACTION_WAITFORREARM))
{
UDWORD droidX = psDroid->psActionTarget[0]->pos.x;
UDWORD droidY = psDroid->psActionTarget[0]->pos.y;
if (!actionVTOLLandingPos(psDroid, &droidX, &droidY))
{
// totally bunged up - give up
objTrace(psDroid->id, "Couldn't find a clear tile near rearm pad - returning to base");
orderDroid(psDroid, DORDER_RTB, ModeImmediate);
break;
}
moveDroidToDirect(psDroid, droidX,droidY);
}
break;
default:
ASSERT(!"unknown action", "unknown action");
break;
}
if (psDroid->action != DACTION_MOVEFIRE &&
psDroid->action != DACTION_ATTACK &&
psDroid->action != DACTION_MOVETOATTACK &&
psDroid->action != DACTION_MOVETODROIDREPAIR &&
psDroid->action != DACTION_DROIDREPAIR &&
psDroid->action != DACTION_BUILD &&
psDroid->action != DACTION_OBSERVE &&
psDroid->action != DACTION_MOVETOOBSERVE)
{
//use 0 for all non-combat droid types
if (psDroid->numWeaps == 0)
{
if (psDroid->asWeaps[0].rot.direction != 0 || psDroid->asWeaps[0].rot.pitch != 0)
{
actionAlignTurret((BASE_OBJECT *)psDroid, 0);
}
}
else
{
for (i = 0;i < psDroid->numWeaps;i++)
{
if (psDroid->asWeaps[i].rot.direction != 0 || psDroid->asWeaps[i].rot.pitch != 0)
{
actionAlignTurret((BASE_OBJECT *)psDroid, i);
}
}
}
}
CHECK_DROID(psDroid);
}
/* Overall action function that is called by the specific action functions */
static void actionDroidBase(DROID *psDroid, DROID_ACTION_DATA *psAction)
{
SDWORD pbx,pby;
WEAPON_STATS *psWeapStats = getWeaponStats(psDroid, 0);
UDWORD droidX,droidY;
BASE_OBJECT *psTarget;
//added MinRangeResult;
UBYTE i;
CHECK_DROID(psDroid);
psDroid->actionStarted = gameTime;
syncDebugDroid(psDroid, '-');
syncDebug("%d does %s", psDroid->id, getDroidActionName(psAction->action));
switch (psAction->action)
{
case DACTION_NONE:
// Clear up what ever the droid was doing before if necessary
if (!DROID_STOPPED(psDroid))
{
moveStopDroid(psDroid);
}
psDroid->action = DACTION_NONE;
psDroid->actionX = 0;
psDroid->actionY = 0;
psDroid->actionStarted = 0;
psDroid->actionPoints = 0;
if (psDroid->numWeaps > 0)
{
for (i = 0;i < psDroid->numWeaps;i++)
{
setDroidActionTarget(psDroid, NULL, i);
}
}
else
{
setDroidActionTarget(psDroid, NULL, 0);
}
break;
case DACTION_TRANSPORTWAITTOFLYIN:
psDroid->action = DACTION_TRANSPORTWAITTOFLYIN;
break;
case DACTION_ATTACK:
// can't attack without a weapon
// or yourself
if ((psDroid->asWeaps[0].nStat == 0) ||
(psDroid->droidType == DROID_TRANSPORTER) ||
(psAction->psObj == (BASE_OBJECT *)psDroid))
{
break;
}
//check electronic droids only attack structures - not so anymore!
if (electronicDroid(psDroid))
{
//check for low or zero resistance - just zero resistance!
if (psAction->psObj->type == OBJ_STRUCTURE
&& !validStructResistance((STRUCTURE *)psAction->psObj))
{
//structure is low resistance already so don't attack
psDroid->action = DACTION_NONE;
break;
}
//in multiPlayer cannot electronically attack a tranporter
if (bMultiPlayer
&& psAction->psObj->type == OBJ_DROID
&& ((DROID *)psAction->psObj)->droidType == DROID_TRANSPORTER)
{
psDroid->action = DACTION_NONE;
break;
}
}
// note the droid's current pos so that scout & patrol orders know how far the
// droid has gone during an attack
// slightly strange place to store this I know, but I didn't want to add any more to the droid
psDroid->actionX = psDroid->pos.x;
psDroid->actionY = psDroid->pos.y;
setDroidActionTarget(psDroid, psAction->psObj, 0);
if (((psDroid->order == DORDER_ATTACKTARGET
|| psDroid->order == DORDER_NONE
|| psDroid->order == DORDER_TEMP_HOLD
|| psDroid->order == DORDER_FIRESUPPORT)
&& secondaryGetState(psDroid, DSO_HALTTYPE) == DSS_HALT_HOLD)
|| (!isVtolDroid(psDroid)
&& (psTarget = orderStateObj(psDroid, DORDER_FIRESUPPORT))
&& psTarget->type == OBJ_STRUCTURE))
{
psDroid->action = DACTION_ATTACK; // holding, try attack straightaway
}
else if (actionInsideMinRange(psDroid, psAction->psObj, psWeapStats))
{
if ( !proj_Direct( psWeapStats ) )
{
if (psWeapStats->rotate)
{
psDroid->action = DACTION_ATTACK;
}
else
{
psDroid->action = DACTION_ROTATETOATTACK;
moveTurnDroid(psDroid, psDroid->psActionTarget[0]->pos.x, psDroid->psActionTarget[0]->pos.y);
}
}
else
{
/* direct fire - try and extend the range */
psDroid->action = DACTION_MOVETOATTACK;
actionCalcPullBackPoint((BASE_OBJECT *)psDroid, psAction->psObj, &pbx,&pby);
turnOffMultiMsg(true);
moveDroidTo(psDroid, (UDWORD)pbx, (UDWORD)pby);
turnOffMultiMsg(false);
}
}
else
{
psDroid->action = DACTION_MOVETOATTACK;
turnOffMultiMsg(true);
moveDroidTo(psDroid, psAction->psObj->pos.x, psAction->psObj->pos.y);
turnOffMultiMsg(false);
}
break;
case DACTION_MOVETOREARM:
psDroid->action = DACTION_MOVETOREARM;
psDroid->actionX = psAction->psObj->pos.x;
psDroid->actionY = psAction->psObj->pos.y;
psDroid->actionStarted = gameTime;
setDroidActionTarget(psDroid, psAction->psObj, 0);
droidX = psDroid->psActionTarget[0]->pos.x;
droidY = psDroid->psActionTarget[0]->pos.y;
if (!actionVTOLLandingPos(psDroid, &droidX, &droidY))
{
// totally bunged up - give up
orderDroid(psDroid, DORDER_RTB, ModeImmediate);
break;
}
moveDroidToDirect(psDroid, droidX, droidY);
break;
case DACTION_CLEARREARMPAD:
debug( LOG_NEVER, "Unit %d clearing rearm pad", psDroid->id);
psDroid->action = DACTION_CLEARREARMPAD;
setDroidActionTarget(psDroid, psAction->psObj, 0);
droidX = psDroid->psActionTarget[0]->pos.x;
droidY = psDroid->psActionTarget[0]->pos.y;
if (!actionVTOLLandingPos(psDroid, &droidX, &droidY))
{
// totally bunged up - give up
orderDroid(psDroid, DORDER_RTB, ModeImmediate);
break;
}
moveDroidToDirect(psDroid, droidX, droidY);
break;
case DACTION_MOVE:
case DACTION_TRANSPORTIN:
case DACTION_TRANSPORTOUT:
case DACTION_RETURNTOPOS:
case DACTION_FIRESUPPORT_RETREAT:
psDroid->action = psAction->action;
psDroid->actionX = psAction->x;
psDroid->actionY = psAction->y;
psDroid->actionStarted = gameTime;
setDroidActionTarget(psDroid, psAction->psObj, 0);
moveDroidTo(psDroid, psAction->x, psAction->y);
break;
case DACTION_BUILD:
if (!psDroid->psTarStats)
{
psDroid->action = DACTION_NONE;
break;
}
ASSERT(psDroid->order == DORDER_BUILD || psDroid->order == DORDER_HELPBUILD ||
psDroid->order == DORDER_LINEBUILD,
"cannot start build action without a build order");
ASSERT_OR_RETURN( , psAction->x > 0 && psAction->y > 0, "Bad build order position");
psDroid->action = DACTION_MOVETOBUILD;
psDroid->actionX = psAction->x;
psDroid->actionY = psAction->y;
if (actionDroidOnBuildPos(psDroid, psDroid->orderX, psDroid->orderY, psDroid->psTarStats))
{
actionHomeBasePos(psDroid->player, &pbx,&pby);
if (pbx == 0 || pby == 0)
{
objTrace(psDroid->id, "Setting DACTION_BUILD: No HQ, cannot move in that direction");
psDroid->action = DACTION_NONE;
break;
}
moveDroidToNoFormation(psDroid, (UDWORD)pbx,(UDWORD)pby);
}
else
{
moveDroidToNoFormation(psDroid, psDroid->actionX,psDroid->actionY);
}
break;
case DACTION_DEMOLISH:
ASSERT(psDroid->order == DORDER_DEMOLISH,
"cannot start demolish action without a demolish order");
psDroid->action = DACTION_MOVETODEMOLISH;
psDroid->actionX = psAction->x;
psDroid->actionY = psAction->y;
ASSERT((psDroid->psTarget != NULL) && (psDroid->psTarget->type == OBJ_STRUCTURE),
"invalid target for demolish order" );
psDroid->psTarStats = (BASE_STATS *)((STRUCTURE *)psDroid->psTarget)->pStructureType;
setDroidActionTarget(psDroid, psAction->psObj, 0);
moveDroidTo(psDroid, psAction->x, psAction->y);
break;
case DACTION_REPAIR:
//ASSERT( psDroid->order == DORDER_REPAIR,
// "actionDroidBase: cannot start repair action without a repair order" );
psDroid->action = DACTION_MOVETOREPAIR;
psDroid->actionX = psAction->x;
psDroid->actionY = psAction->y;
//this needs setting so that automatic repair works
setDroidActionTarget(psDroid, psAction->psObj, 0);
ASSERT((psDroid->psActionTarget[0] != NULL) && (psDroid->psActionTarget[0]->type == OBJ_STRUCTURE),
"invalid target for demolish order" );
psDroid->psTarStats = (BASE_STATS *)((STRUCTURE *)psDroid->psActionTarget[0])->pStructureType;
if (secondaryGetState(psDroid, DSO_HALTTYPE) == DSS_HALT_HOLD &&
(psDroid->order == DORDER_NONE || psDroid->order == DORDER_TEMP_HOLD))
{
psDroid->action = DACTION_REPAIR;
}
else
{
psDroid->action = DACTION_MOVETOREPAIR;
moveDroidTo(psDroid, psAction->x, psAction->y);
}
break;
case DACTION_OBSERVE:
setDroidActionTarget(psDroid, psAction->psObj, 0);
psDroid->actionX = psDroid->pos.x;
psDroid->actionY = psDroid->pos.y;
if ((secondaryGetState(psDroid, DSO_HALTTYPE) == DSS_HALT_HOLD &&
(psDroid->order == DORDER_NONE || psDroid->order == DORDER_TEMP_HOLD)) ||
cbSensorDroid(psDroid) || objRadarDetector((BASE_OBJECT *)psDroid))
{
psDroid->action = DACTION_OBSERVE;
}
else
{
psDroid->action = DACTION_MOVETOOBSERVE;
moveDroidTo(psDroid, psDroid->psActionTarget[0]->pos.x, psDroid->psActionTarget[0]->pos.y);
}
break;
case DACTION_FIRESUPPORT:
psDroid->action = DACTION_FIRESUPPORT;
if(!isVtolDroid(psDroid) &&
(secondaryGetState(psDroid, DSO_HALTTYPE) != DSS_HALT_HOLD) && // check hold
(psDroid->psTarget->type != OBJ_STRUCTURE))
{
moveDroidTo(psDroid, psDroid->psTarget->pos.x, psDroid->psTarget->pos.y); // movetotarget.
}
break;
case DACTION_SULK:
// debug( LOG_NEVER, "Go with sulk ... %p", psDroid );
psDroid->action = DACTION_SULK;
// hmmm, hope this doesn't cause any problems!
psDroid->actionStarted = gameTime + MIN_SULK_TIME + (gameRand(MAX_SULK_TIME - MIN_SULK_TIME));
break;
case DACTION_DESTRUCT:
psDroid->action = DACTION_DESTRUCT;
psDroid->actionStarted = gameTime;
break;
case DACTION_WAITFORREPAIR:
psDroid->action = DACTION_WAITFORREPAIR;
// set the time so we can tell whether the start the self repair or not
psDroid->actionStarted = gameTime;
break;
case DACTION_MOVETOREPAIRPOINT:
psDroid->action = psAction->action;
psDroid->actionX = psAction->x;
psDroid->actionY = psAction->y;
psDroid->actionStarted = gameTime;
setDroidActionTarget(psDroid, psAction->psObj, 0);
moveDroidToNoFormation( psDroid, psAction->x, psAction->y );
break;
case DACTION_WAITDURINGREPAIR:
psDroid->action = DACTION_WAITDURINGREPAIR;
break;
case DACTION_MOVETOREARMPOINT:
debug( LOG_NEVER, "Unit %d moving to rearm point", psDroid->id);
psDroid->action = psAction->action;
psDroid->actionX = psAction->x;
psDroid->actionY = psAction->y;
psDroid->actionStarted = gameTime;
setDroidActionTarget(psDroid, psAction->psObj, 0);
moveDroidToDirect( psDroid, psAction->x, psAction->y );
// make sure there arn't any other VTOLs on the rearm pad
ensureRearmPadClear((STRUCTURE *)psAction->psObj, psDroid);
break;
case DACTION_DROIDREPAIR:
// ASSERT( psDroid->order == DORDER_DROIDREPAIR,
// "actionDroidBase: cannot start droid repair action without a repair order" );
psDroid->action = DACTION_MOVETODROIDREPAIR;
psDroid->actionX = psAction->x;
psDroid->actionY = psAction->y;
setDroidActionTarget(psDroid, psAction->psObj, 0);
//initialise the action points
psDroid->actionPoints = 0;
psDroid->actionStarted = gameTime;
if (secondaryGetState(psDroid, DSO_HALTTYPE) == DSS_HALT_HOLD &&
(psDroid->order == DORDER_NONE || psDroid->order == DORDER_TEMP_HOLD))
{
psDroid->action = DACTION_DROIDREPAIR;
}
else
{
psDroid->action = DACTION_MOVETODROIDREPAIR;
moveDroidTo(psDroid, psAction->x, psAction->y);
}
break;
case DACTION_RESTORE:
ASSERT( psDroid->order == DORDER_RESTORE,
"cannot start restore action without a restore order" );
psDroid->action = DACTION_MOVETORESTORE;
psDroid->actionX = psAction->x;
psDroid->actionY = psAction->y;
ASSERT( (psDroid->psTarget != NULL) && (psDroid->psTarget->type == OBJ_STRUCTURE),
"invalid target for restore order" );
psDroid->psTarStats = (BASE_STATS *)((STRUCTURE *)psDroid->psTarget)->pStructureType;
setDroidActionTarget(psDroid, psAction->psObj, 0);
moveDroidTo(psDroid, psAction->x, psAction->y);
break;
case DACTION_CLEARWRECK:
ASSERT( psDroid->order == DORDER_CLEARWRECK,
"cannot start clear action without a clear order" );
psDroid->action = DACTION_MOVETOCLEAR;
psDroid->actionX = psAction->x;
psDroid->actionY = psAction->y;
ASSERT( (psDroid->psTarget != NULL) && (psDroid->psTarget->type == OBJ_FEATURE),
"invalid target for demolish order" );
psDroid->psTarStats = (BASE_STATS *)((FEATURE *)psDroid->psTarget)->psStats;
setDroidActionTarget(psDroid, psDroid->psTarget, 0);
moveDroidTo(psDroid, psAction->x, psAction->y);
break;
default:
ASSERT(!"unknown action", "actionUnitBase: unknown action");
break;
}
syncDebugDroid(psDroid, '+');
CHECK_DROID(psDroid);
}
/* Give a droid an action */
void actionDroid(DROID *psDroid, DROID_ACTION action)
{
DROID_ACTION_DATA sAction;
memset(&sAction, 0, sizeof(DROID_ACTION_DATA));
sAction.action = action;
actionDroidBase(psDroid, &sAction);
}
/* Give a droid an action with a location target */
void actionDroidLoc(DROID *psDroid, DROID_ACTION action, UDWORD x, UDWORD y)
{
DROID_ACTION_DATA sAction;
memset(&sAction, 0, sizeof(DROID_ACTION_DATA));
sAction.action = action;
sAction.x = x;
sAction.y = y;
actionDroidBase(psDroid, &sAction);
}
/* Give a droid an action with an object target */
void actionDroidObj(DROID *psDroid, DROID_ACTION action, BASE_OBJECT *psObj)
{
DROID_ACTION_DATA sAction;
memset(&sAction, 0, sizeof(DROID_ACTION_DATA));
sAction.action = action;
sAction.psObj = psObj;
sAction.x = psObj->pos.x;
sAction.y = psObj->pos.y;
actionDroidBase(psDroid, &sAction);
}
/* Give a droid an action with an object target and a location */
void actionDroidObjLoc(DROID *psDroid, DROID_ACTION action,
BASE_OBJECT *psObj, UDWORD x, UDWORD y)
{
DROID_ACTION_DATA sAction;
memset(&sAction, 0, sizeof(DROID_ACTION_DATA));
sAction.action = action;
sAction.psObj = psObj;
sAction.x = x;
sAction.y = y;
actionDroidBase(psDroid, &sAction);
}
/*send the vtol droid back to the nearest rearming pad - if one otherwise
return to base*/
// IF YOU CHANGE THE ORDER/ACTION RESULTS TELL ALEXL!!!! && recvvtolrearm
void moveToRearm(DROID *psDroid)
{
STRUCTURE *psStruct;
UBYTE chosen=0;
CHECK_DROID(psDroid);
if (!isVtolDroid(psDroid))
{
return;
}
//if droid is already returning - ignore
if (vtolRearming(psDroid))
{
return;
}
//get the droid to fly back to a ReArming Pad
// don't worry about finding a clear one for the minute
psStruct = findNearestReArmPad(psDroid, psDroid->psBaseStruct, false);
if (psStruct)
{
// note a base rearm pad if the vtol doesn't have one
if (psDroid->psBaseStruct == NULL)
{
setDroidBase(psDroid, psStruct);
}
//return to re-arming pad
if (psDroid->order == DORDER_NONE)
{
// no order set - use the rearm order to ensure the unit goes back
// to the landing pad
orderDroidObj(psDroid, DORDER_REARM, (BASE_OBJECT *)psStruct, ModeImmediate);
chosen=1;
}
else
{
actionDroidObj(psDroid, DACTION_MOVETOREARM, (BASE_OBJECT *)psStruct);
chosen=2;
}
}
else
{
//return to base un-armed
orderDroid(psDroid, DORDER_RTB, ModeImmediate);
chosen =3;
}
}
// whether a tile is suitable for a vtol to land on
static BOOL vtolLandingTile(SDWORD x, SDWORD y)
{
MAPTILE *psTile;
if (x < 0 || x >= (SDWORD)mapWidth ||
y < 0 || y >= (SDWORD)mapHeight)
{
return false;
}
psTile = mapTile(x,y);
if ((psTile->tileInfoBits & BITS_FPATHBLOCK) ||
(TileIsOccupied(psTile)) ||
(terrainType(psTile) == TER_CLIFFFACE) ||
(terrainType(psTile) == TER_WATER))
{
return false;
}
return true;
}
/**
* Performs a space-filling spiral-like search from startX,startY up to (and
* including) radius. For each tile, the search function is called; if it
* returns 'true', the search will finish immediately.
*
* @param startX,startY starting x and y coordinates
*
* @param max_radius radius to examine. Search will finish when @c max_radius is exceeded.
*
* @param match searchFunction to use; described in typedef
* \param matchState state for the search function
* \return true if finished because the searchFunction requested termination,
* false if the radius limit was reached
*/
static bool spiralSearch(int startX, int startY, int max_radius, tileMatchFunction match, void* matchState)
{
int radius; // radius counter
// test center tile
if (match(startX, startY, matchState))
{
return true;
}
// test for each radius, from 1 to max_radius (inclusive)
for (radius = 1; radius <= max_radius; ++radius)
{
// choose tiles that are between radius and radius+1 away from center
// distances are squared
const int min_distance = radius * radius;
const int max_distance = min_distance + 2 * radius;
// X offset from startX
int dx;
// dx starts with 1, to visiting tiles on same row or col as start twice
for (dx = 1; dx <= max_radius; dx++)
{
// Y offset from startY
int dy;
for (dy = 0; dy <= max_radius; dy++)
{
// Current distance, squared
const int distance = dx * dx + dy * dy;
// Ignore tiles outside of the current circle
if (distance < min_distance || distance > max_distance)
{
continue;
}
// call search function for each of the 4 quadrants of the circle
if (match(startX + dx, startY + dy, matchState)
|| match(startX - dx, startY - dy, matchState)
|| match(startX + dy, startY - dx, matchState)
|| match(startX - dy, startY + dx, matchState))
{
return true;
}
}
}
}
return false;
}
/**
* an internal tileMatchFunction that checks if x and y are coordinates of a
* valid landing place.
*
* @param matchState a pointer to a Vector2i where these coordintates should be stored
*
* @return true if coordinates are a valid landing tile, false if not.
*/
static bool vtolLandingTileSearchFunction(int x, int y, void* matchState)
{
Vector2i* const xyCoords = (Vector2i*)matchState;
if (vtolLandingTile(x, y))
{
xyCoords->x = x;
xyCoords->y = y;
return true;
}
return false;
}
// choose a landing position for a VTOL when it goes to rearm
bool actionVTOLLandingPos(const DROID* psDroid, UDWORD* px, UDWORD* py)
{
int startX, startY, tx, ty;
DROID* psCurr;
bool foundTile;
Vector2i xyCoords;
CHECK_DROID(psDroid);
/* Initial box dimensions and set iteration count to zero */
startX = map_coord(*px);
startY = map_coord(*py);
// set blocking flags for all the other droids
for(psCurr=apsDroidLists[psDroid->player]; psCurr; psCurr = psCurr->psNext)
{
if (DROID_STOPPED(psCurr))
{
tx = map_coord(psCurr->pos.x);
ty = map_coord(psCurr->pos.y);
}
else
{
tx = map_coord(psCurr->sMove.DestinationX);
ty = map_coord(psCurr->sMove.DestinationY);
}
if (psCurr != psDroid)
{
if (tileOnMap(tx, ty))
{
mapTile(tx,ty)->tileInfoBits |= BITS_FPATHBLOCK;
}
}
}
// search for landing tile; will stop when found or radius exceeded
foundTile = spiralSearch(startX, startY, vtolLandingRadius,
vtolLandingTileSearchFunction, &xyCoords);
if (foundTile)
{
debug( LOG_NEVER, "Unit %d landing pos (%d,%d)",
psDroid->id, xyCoords.x, xyCoords.y);
*px = world_coord(xyCoords.x) + TILE_UNITS / 2;
*py = world_coord(xyCoords.y) + TILE_UNITS / 2;
}
// clear blocking flags for all the other droids
for(psCurr=apsDroidLists[psDroid->player]; psCurr; psCurr = psCurr->psNext)
{
if (DROID_STOPPED(psCurr))
{
tx = map_coord(psCurr->pos.x);
ty = map_coord(psCurr->pos.y);
}
else
{
tx = map_coord(psCurr->sMove.DestinationX);
ty = map_coord(psCurr->sMove.DestinationY);
}
if (tileOnMap(tx, ty))
{
mapTile(tx,ty)->tileInfoBits &= ~BITS_FPATHBLOCK;
}
}
return foundTile;
}