warzone2100/src/action.cpp

2537 lines
76 KiB
C++

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2013 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 "qtscript.h"
#include "random.h"
#include "research.h"
#include "scriptcb.h"
#include "scripttabs.h"
#include "transporter.h"
#include "mapgrid.h"
/* attack run distance */
#define VTOL_ATTACK_LENGTH 1000
#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
struct DROID_ACTION_DATA
{
DROID_ACTION action;
UDWORD x,y;
//multiple action target info
BASE_OBJECT *psObj;
BASE_STATS *psStats;
};
// 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_UNUSED",
"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_UNUSED2",
"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, 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;
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;
longRange = proj_GetLongRange(psStats, psDroid->player);
rangeSq = longRange * longRange;
/* check max range */
if ( radSq <= rangeSq )
{
/* check min range */
const int minrange = psStats->upgrade[psDroid->player].minRange;
if (radSq >= minrange * minrange || !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, 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;
radSq = dx*dx + dy*dy;
longRange = proj_GetLongRange(psStats, psDroid->player);
rangeSq = longRange * longRange;
/* check max range */
if ( radSq <= rangeSq )
{
/* check min range */
const int minrange = psStats->upgrade[psDroid->player].minRange;
if (radSq >= minrange * minrange || !proj_Direct(psStats))
{
return true;
}
}
return false;
}
// check if a target is inside minimum weapon range
static bool actionInsideMinRange(DROID *psDroid, BASE_OBJECT *psObj, WEAPON_STATS *psStats)
{
SDWORD dx, dy, 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;
radSq = dx*dx + dy*dy;
minRange = psStats->upgrade[psDroid->player].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;
//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;
int32_t rotationError;
int32_t rotationTolerance = 0;
bool onTarget;
int32_t pitchLowerLimit, pitchUpperLimit;
bool bRepair;
if (!psTarget)
{
return false;
}
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;
Vector3i attackerMuzzlePos = psAttacker->pos; // Using for calculating the pitch, but not the direction, in case using the exact direction causes bugs somewhere.
if (psAttacker->type == OBJ_STRUCTURE)
{
STRUCTURE *psStructure = (STRUCTURE *)psAttacker;
int weapon_slot = psWeapon - psStructure->asWeaps; // Should probably be passed weapon_slot instead of psWeapon.
calcStructureMuzzleLocation(psStructure, &attackerMuzzlePos, weapon_slot);
pitchLowerLimit = DEG(psWeapStats->minElevation);
pitchUpperLimit = DEG(psWeapStats->maxElevation);
}
else if (psAttacker->type == OBJ_DROID)
{
DROID *psDroid = (DROID *)psAttacker;
int weapon_slot = psWeapon - psDroid->asWeaps; // Should probably be passed weapon_slot instead of psWeapon.
calcDroidMuzzleLocation(psDroid, &attackerMuzzlePos, weapon_slot);
if (psDroid->droidType == DROID_WEAPON || psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER
|| 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 not repairing or outside minimum range */
if (!bRepair && objPosDiffSq(psAttacker, psTarget) > psWeapStats->upgrade[psAttacker->player].minRange * psWeapStats->upgrade[psAttacker->player].minRange)
{
/* get target distance */
Vector3i delta = psTarget->pos - attackerMuzzlePos;
int32_t dxy = iHypot(delta.x, delta.y);
uint16_t targetPitch = iAtan2(delta.z, dxy);
targetPitch = (uint16_t)clip(angleDelta(targetPitch), pitchLowerLimit, pitchUpperLimit); // Cast wrapping intended.
int pitchError = angleDelta(targetPitch - tPitch);
tPitch += clip(pitchError, -pitchRate, pitchRate); // Addition wrapping intended.
onTarget = onTarget && targetPitch == tPitch;
}
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)
{
CHECK_DROID(psDroid);
ASSERT_OR_RETURN(false, psTarget != NULL, "Target is NULL");
if (!psTarget->visible[psDroid->player])
{
return false;
}
if ((psDroid->numWeaps == 0 || isVtolDroid(psDroid)) && visibleObject(psDroid, psTarget, false))
{
return true;
}
const int compIndex = psDroid->asWeaps[weapon_slot].nStat;
ASSERT_OR_RETURN( false, compIndex < numWeaponStats, "Invalid range referenced for numWeaponStats, %d > %d", compIndex, numWeaponStats);
return (orderState(psDroid, DORDER_FIRESUPPORT) || visibleObject(psDroid, psTarget, false))
&& lineOfFire(psDroid, psTarget, weapon_slot, true);
}
static void actionAddVtolAttackRun( DROID *psDroid )
{
BASE_OBJECT *psTarget;
CHECK_DROID(psDroid);
if ( psDroid->psActionTarget[0] != NULL )
{
psTarget = psDroid->psActionTarget[0];
}
else if (psDroid->order.psObj != NULL )
{
psTarget = psDroid->order.psObj;
}
else
{
return;
}
/* get normal vector from droid to target */
Vector2i delta = removeZ(psTarget->pos - psDroid->pos);
/* get magnitude of normal vector (Pythagorean theorem) */
int dist = std::max(iHypot(delta), 1);
/* add waypoint behind target attack length away*/
Vector2i dest = removeZ(psTarget->pos) + delta * VTOL_ATTACK_LENGTH / dist;
if (!worldOnMap(dest))
{
debug( LOG_NEVER, "*** actionAddVtolAttackRun: run off map! ***" );
}
else
{
moveDroidToDirect(psDroid, dest.x, dest.y);
}
}
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.type == 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 const *psDroid, int x, int y, uint16_t dir, BASE_STATS const *psStats)
{
ASSERT_OR_RETURN(false, psStats != NULL && psDroid != NULL, "Bad stat or droid");
CHECK_DROID(psDroid);
StructureBounds b = getStructureBounds(psStats, Vector2i(x, y), dir);
// do all calculations in half tile units so that
// the droid moves to within half a tile of the target
// NOT ANY MORE - JOHN
Vector2i delta = map_coord(removeZ(psDroid->pos)) - b.map;
return delta.x >= -1 && delta.x <= b.size.x && delta.y >= -1 && delta.y <= b.size.y;
}
// check if a droid is on the foundations of a new building
static bool actionRemoveDroidsFromBuildPos(unsigned player, Vector2i pos, uint16_t dir, BASE_STATS *psStats)
{
ASSERT_OR_RETURN(false, psStats != NULL, "Bad stat");
bool buildPosEmpty = true;
StructureBounds b = getStructureBounds(psStats, pos, dir);
Vector2i structureCentre = world_coord(b.map) + world_coord(b.size)/2;
unsigned structureMaxRadius = iHypot(world_coord(b.size)/2) + 1; // +1 since iHypot rounds down.
static GridList gridList; // static to avoid allocations.
gridList = gridStartIterate(structureCentre.x, structureCentre.y, structureMaxRadius);
for (GridIterator gi = gridList.begin(); gi != gridList.end(); ++gi)
{
DROID *droid = castDroid(*gi);
if (droid == NULL)
{
continue; // Only looking for droids.
}
Vector2i delta = map_coord(removeZ(droid->pos)) - b.map;
if (delta.x < 0 || delta.x >= b.size.x || delta.y < 0 || delta.y >= b.size.y || isFlying(droid))
{
continue; // Droid not under new structure (just near it).
}
buildPosEmpty = false; // Found a droid, have to move it away.
if (!aiCheckAlliances(player, droid->player))
{
continue; // Enemy droids probably don't feel like moving.
}
// TODO If the action code was less convoluted, it would be possible for the droid should drive away instead of just getting moved away.
Vector2i bestDest(0, 0); // Dummy initialisation.
unsigned bestDist = UINT32_MAX;
for (int y = -1; y <= b.size.y; ++y)
for (int x = -1; x <= b.size.x; x += y >= 0 && y < b.size.y? b.size.x + 1 : 1)
{
Vector2i dest = world_coord(b.map + Vector2i(x, y)) + Vector2i(TILE_UNITS, TILE_UNITS)/2;
unsigned dist = iHypot(removeZ(droid->pos) - dest);
if (dist < bestDist && !fpathBlockingTile(map_coord(dest.x), map_coord(dest.y), getPropulsionStats(droid)->propulsionType))
{
bestDest = dest;
bestDist = dist;
}
}
if (bestDist != UINT32_MAX)
{
// Push the droid out of the way.
Vector2i newPos = removeZ(droid->pos) + iSinCosR(iAtan2(bestDest - removeZ(droid->pos)), gameTimeAdjustedIncrement(TILE_UNITS));
droidSetPosition(droid, newPos.x, newPos.y);
}
}
return buildPosEmpty;
}
void actionSanity(DROID *psDroid)
{
// Don't waste ammo unless given a direct attack order.
bool avoidOverkill = psDroid->order.type != DORDER_ATTACK &&
(psDroid->action == DACTION_ATTACK || psDroid->action == DACTION_MOVEFIRE || psDroid->action == DACTION_MOVETOATTACK ||
psDroid->action == DACTION_ROTATETOATTACK || psDroid->action == DACTION_VTOLATTACK);
// clear the target if it has died
for (int i = 0; i < DROID_MAXWEAPS; i++)
{
if (psDroid->psActionTarget[i] && (avoidOverkill? aiObjectIsProbablyDoomed(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.type == DORDER_PATROL || psDroid->order.type == DORDER_CIRCLE) && (!vtolEmpty(psDroid) || (psDroid->secondaryOrder & DSS_ALEV_MASK) == DSS_ALEV_NEVER))
{
// Back to the patrol.
actionDroid(psDroid, DACTION_MOVE, psDroid->order.pos.x, psDroid->order.pos.y);
}
else
{
moveToRearm(psDroid);
}
}
}
}
}
}
}
// Update the action state for a droid
void actionUpdateDroid(DROID *psDroid)
{
BASE_OBJECT *psTarget;
PROPULSION_STATS *psPropStats;
bool (*actionUpdateFunc)(DROID *psDroid) = NULL;
unsigned i;
//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];
ASSERT_OR_RETURN(, psPropStats != NULL, "Invalid propulsion stats pointer");
actionSanity(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 = WSC_NUM_WEAPON_SUBCLASSES;
}
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;
DROID_ORDER_DATA *order = &psDroid->order;
psTarget = order->psObj;
switch (psDroid->action)
{
case DACTION_NONE:
case DACTION_WAITFORREPAIR:
// doing nothing
// see if there's anything to shoot.
if (psDroid->numWeaps > 0 && !isVtolDroid(psDroid)
&& (order->type == DORDER_NONE || order->type == DORDER_HOLD || order->type == 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) >= 0)
{
if (secondaryGetState(psDroid, DSO_ATTACK_LEVEL) == DSS_ALEV_ALWAYS)
{
psDroid->action = DACTION_ATTACK;
setDroidActionTarget(psDroid, psTemp, 0);
}
}
}
}
}
break;
case DACTION_WAITDURINGREPAIR:
// Check that repair facility still exists
if (!order->psObj)
{
psDroid->action = DACTION_NONE;
break;
}
// move back to the repair facility if necessary
if (DROID_STOPPED(psDroid) &&
!actionReachedBuildPos(psDroid,
order->psObj->pos.x, order->psObj->pos.y, ((STRUCTURE*)order->psObj)->rot.direction,
((STRUCTURE*)order->psObj)->pStructureType ) )
{
moveDroidToNoFormation(psDroid, order->psObj->pos.x, order->psObj->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);
triggerEvent(TRIGGER_START_LEVEL);
}
}
}
break;
case DACTION_MOVE:
case DACTION_RETURNTOPOS:
case DACTION_FIRESUPPORT_RETREAT:
// moving to a location
if (DROID_STOPPED(psDroid))
{
bool notify = psDroid->action == DACTION_MOVE;
// Got to destination
psDroid->action = DACTION_NONE;
if (notify)
{
/* notify scripts we have reached the destination
* also triggers when patrolling and reached a waypoint
*/
psScrCBOrder = order->type;
psScrCBOrderDroid = psDroid;
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_DROID_REACH_LOCATION);
psScrCBOrderDroid = NULL;
psScrCBOrder = DORDER_NONE;
triggerEventDroidIdle(psDroid);
}
}
//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
&& aiBestNearestTarget(psDroid, &psTemp, i) >= 0)
{
if (secondaryGetState(psDroid, DSO_ATTACK_LEVEL) == DSS_ALEV_ALWAYS)
{
psDroid->action = DACTION_MOVEFIRE;
setDroidActionTarget(psDroid, psTemp, i);
}
}
}
}
}
break;
case DACTION_TRANSPORTIN:
case DACTION_TRANSPORTOUT:
actionUpdateTransporter( psDroid );
break;
case DACTION_MOVEFIRE:
// check if vtol is armed
if (vtolEmpty(psDroid))
{
moveToRearm(psDroid);
}
// If droid stopped, it can no longer be in DACTION_MOVEFIRE
if (DROID_STOPPED(psDroid))
{
psDroid->action = DACTION_NONE;
break;
}
// loop through weapons and look for target for each weapon
bHasTarget = false;
for (i = 0; i < psDroid->numWeaps; ++i)
{
// Does this weapon have a target?
if (psDroid->psActionTarget[i] != NULL)
{
// Is target worth shooting yet?
if (aiObjectIsProbablyDoomed(psDroid->psActionTarget[i]))
{
setDroidActionTarget(psDroid, NULL, i);
}
// Is target from our team now? (Electronic Warfare)
else if (electronicDroid(psDroid) && psDroid->player == psDroid->psActionTarget[i]->player)
{
setDroidActionTarget(psDroid, NULL, i);
}
// I have a target!
else
{
bHasTarget = true;
}
}
// This weapon doesn't have a target
else
{
// Can we find a good target for the weapon?
BASE_OBJECT *psTemp;
if (aiBestNearestTarget(psDroid, &psTemp, i) >= 0) // assuming aiBestNearestTarget checks for electronic warfare
{
bHasTarget = true;
setDroidActionTarget(psDroid, psTemp, i); // this updates psDroid->psActionTarget[i] to != NULL
}
}
// If we have a target for the weapon: is it visible?
if (psDroid->psActionTarget[i] != NULL
&& visibleObject(psDroid, psDroid->psActionTarget[i], false))
{
hasVisibleTarget = true; // droid have a visible target to shoot
targetVisibile[i] = true;// it is at least visible for this weapon
}
}
// if there is at least one target
if (bHasTarget)
{
// loop through weapons
for (i = 0; i < psDroid->numWeaps; ++i)
{
// has weapon a target? is target valid?
if (psDroid->psActionTarget[i] != NULL && validTarget(psDroid, psDroid->psActionTarget[i], i))
{
// is target visible and weapon is not a Nullweapon?
if (targetVisibile[i] && nonNullWeapon[i]) //to fix a AA-weapon attack ground unit exploit
{
BASE_OBJECT* psActionTarget = psDroid->psActionTarget[i];
// is the turret aligned with the target?
if (actionTargetTurret(psDroid, psActionTarget, &psDroid->asWeaps[i]))
{
// In range - fire !!!
combFire(&psDroid->asWeaps[i], psDroid, psActionTarget, i);
}
}
}
}
// Droid don't have a visible target and it is not in pursue mode
if (!hasVisibleTarget && secondaryGetState(psDroid, DSO_ATTACK_LEVEL) != DSS_ALEV_ALWAYS)
{
// Target lost
psDroid->action = DACTION_MOVE;
}
}
// it don't have a target, change to DACTION_MOVE
else
{
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:
case DACTION_ROTATETOATTACK:
ASSERT_OR_RETURN( , psDroid->psActionTarget[0] != NULL, "target is NULL while attacking");
if (psDroid->action == DACTION_ROTATETOATTACK)
{
if (psDroid->sMove.Status == MOVETURNTOTARGET)
{
moveTurnDroid(psDroid, psDroid->psActionTarget[0]->pos.x, psDroid->psActionTarget[0]->pos.y);
break; // Still turning.
}
psDroid->action = DACTION_ATTACK;
}
//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 ((order->type == DORDER_ATTACK || order->type == DORDER_ATTACKTARGET) &&
psDroid->psActionTarget[i] != psDroid->psActionTarget[0] &&
validTarget(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(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 (psDroid->psActionTarget[i])
{
psActionTarget = psDroid->psActionTarget[i];
}
else
{
psActionTarget = psDroid->psActionTarget[0];
}
if (nonNullWeapon[i]
&& actionVisibleTarget(psDroid, psActionTarget, i)
&& actionInRange(psDroid, psActionTarget, i))
{
WEAPON_STATS* const psWeapStats = &asWeaponStats[psDroid->asWeaps[i].nStat];
bHasTarget = true;
if (validTarget(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(psDroid, psActionTarget, &psDroid->asWeaps[i]))
{
/* In range - fire !!! */
combFire(&psDroid->asWeaps[i], 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 ((!isVtolDroid(psDroid)
&& (psTarget = orderStateObj(psDroid, DORDER_FIRESUPPORT))
&& psTarget->type == OBJ_STRUCTURE)
|| order->type == DORDER_NONE
|| order->type == DORDER_HOLD
|| order->type == 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(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(psDroid, psDroid->psActionTarget[0], 0) )
{
moveToRearm(psDroid);
break;
}
for(i = 0;i <psDroid->numWeaps;i++)
{
if (nonNullWeapon[i]
&& validTarget(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(psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[i]))
{
// In range - fire !!!
combFire(&psDroid->asWeaps[i], psDroid,
psDroid->psActionTarget[0], i);
}
}
else
{
actionTargetTurret(psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[i]);
}
}
}
}
}
/* circle around target if hovering and not cyborg */
Vector2i attackRunDelta = psDroid->pos - psDroid->sMove.destination;
if (DROID_STOPPED(psDroid) || attackRunDelta*attackRunDelta < TILE_UNITS*TILE_UNITS)
{
actionAddVtolAttackRun( psDroid );
}
else
{
// if the vtol is close to the target, go around again
const Vector2i diff = removeZ(psDroid->pos - psDroid->psActionTarget[0]->pos);
const int rangeSq = diff*diff;
if (rangeSq < VTOL_ATTACK_TARDIST*VTOL_ATTACK_TARDIST)
{
// don't do another attack run if already moving away from the target
const Vector2i diff = psDroid->sMove.destination - removeZ(psDroid->psActionTarget[0]->pos);
if (diff*diff < 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 > psWeapStats->upgrade[psDroid->player].maxRange * psWeapStats->upgrade[psDroid->player].maxRange)
{
// don't do another attack run if already heading for the target
const Vector2i diff = psDroid->sMove.destination - removeZ(psDroid->psActionTarget[0]->pos);
if (diff*diff > 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(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(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(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)
{
chaseBloke = true;
}
if (actionInAttackRange(psDroid, psDroid->psActionTarget[0], i)
&& !chaseBloke)
{
/* init vtol attack runs count if necessary */
if ( psPropStats->propulsionType == PROPULSION_TYPE_LIFT )
{
psDroid->action = DACTION_VTOLATTACK;
}
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], 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(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 (actionInsideMinRange(psDroid, psDroid->psActionTarget[0], psWeapStats))
{
if ( proj_Direct( psWeapStats ) )
{
SDWORD pbx, pby;
// try and extend the range
actionCalcPullBackPoint(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 // too far away
{
// 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)
{
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_MOVETOBUILD:
if (!order->psStats)
{
psDroid->action = DACTION_NONE;
break;
}
// moving to a location to build a structure
if (actionReachedBuildPos(psDroid, order->pos.x, order->pos.y, order->direction, order->psStats))
{
bool buildPosEmpty = actionRemoveDroidsFromBuildPos(psDroid->player, order->pos, order->direction, order->psStats);
if (!buildPosEmpty)
{
break;
}
bool helpBuild = false;
// Got to destination - start building
STRUCTURE_STATS* const psStructStats = order->psStats;
uint16_t dir = order->direction;
moveStopDroid(psDroid);
objTrace(psDroid->id, "Halted in our tracks - at construction site");
if (order->type == DORDER_BUILD && order->psObj == NULL)
{
// Starting a new structure
const Vector2i pos(order->pos.x, order->pos.y);
//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(order->pos.x), map_coord(order->pos.y))))
{
// structure on the build location - see if it is the same type
STRUCTURE* const psStruct = getTileStructure(map_coord(order->pos.x), map_coord(order->pos.y));
if (psStruct->pStructureType == order->psStats ||
(order->psStats->type == REF_WALL && psStruct->pStructureType->type == REF_WALLCORNER))
{
// same type - do a help build
syncDebug("Reached build target: do-help");
setDroidTarget(psDroid, psStruct);
helpBuild = true;
}
else if ((psStruct->pStructureType->type == REF_WALL ||
psStruct->pStructureType->type == REF_WALLCORNER) &&
(order->psStats->type == REF_DEFENSE ||
order->psStats->type == REF_GATE))
{
// building a gun tower or gate over a wall - OK
if (droidStartBuild(psDroid))
{
syncDebug("Reached build target: tower");
psDroid->action = DACTION_BUILD;
}
}
else
{
syncDebug("Reached build target: already-structure");
objTrace(psDroid->id, "DACTION_MOVETOBUILD: tile has structure already");
cancelBuild(psDroid);
}
}
else if (!validLocation(order->psStats, pos, dir, psDroid->player, false))
{
syncDebug("Reached build target: invalid");
objTrace(psDroid->id, "DACTION_MOVETOBUILD: !validLocation");
cancelBuild(psDroid);
}
else if (droidStartBuild(psDroid))
{
syncDebug("Reached build target: build");
psDroid->action = DACTION_BUILD;
psDroid->actionStarted = gameTime;
psDroid->actionPoints = 0;
}
}
else if ((order->type == DORDER_LINEBUILD || order->type==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(order->pos.x), map_coord(order->pos.y));
syncDebug("Reached build target: wall");
if (order->psObj == 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(order->pos.x), map_coord(order->pos.y));
if (psStruct->pStructureType == order->psStats)
{
// same type - do a help build
setDroidTarget(psDroid, psStruct);
helpBuild = true;
}
else if ((psStruct->pStructureType->type == REF_WALL || psStruct->pStructureType->type == REF_WALLCORNER) &&
(order->psStats->type == REF_DEFENSE || order->psStats->type == REF_GATE))
{
// building a gun tower over a wall - OK
if (droidStartBuild(psDroid))
{
objTrace(psDroid->id, "DACTION_MOVETOBUILD: start building");
psDroid->action = DACTION_BUILD;
}
}
else
{
objTrace(psDroid->id, "DACTION_MOVETOBUILD: line build hit building");
cancelBuild(psDroid);
}
}
else
{
objTrace(psDroid->id, "DACTION_MOVETOBUILD: blocked line build");
cancelBuild(psDroid);
}
}
else if (droidStartBuild(psDroid))
{
psDroid->action = DACTION_BUILD;
intBuildStarted(psDroid);
}
}
else
{
syncDebug("Reached build target: planned-help");
helpBuild = true;
}
if (helpBuild)
{
// continuing a partially built structure (order = helpBuild)
if (droidStartBuild(psDroid))
{
objTrace(psDroid->id, "DACTION_MOVETOBUILD: starting help build");
psDroid->action = DACTION_BUILD;
intBuildStarted(psDroid);
}
}
}
else if (DROID_STOPPED(psDroid))
{
objTrace(psDroid->id, "DACTION_MOVETOBUILD: Starting to drive toward construction site - move status was %d", (int)psDroid->sMove.Status);
moveDroidToNoFormation(psDroid, psDroid->actionPos.x, psDroid->actionPos.y);
}
break;
case DACTION_BUILD:
if (!order->psStats)
{
objTrace(psDroid->id, "No target stats for build order - resetting");
psDroid->action = DACTION_NONE;
break;
}
if (DROID_STOPPED(psDroid) &&
!actionReachedBuildPos(psDroid, order->pos.x, order->pos.y, order->direction, order->psStats))
{
objTrace(psDroid->id, "DACTION_BUILD: Starting to drive toward construction site");
moveDroidToNoFormation(psDroid, order->pos.x, order->pos.y);
}
else if (!DROID_STOPPED(psDroid) &&
psDroid->sMove.Status != MOVETURNTOTARGET &&
psDroid->sMove.Status != MOVESHUFFLE &&
actionReachedBuildPos(psDroid, order->pos.x, order->pos.y, order->direction, order->psStats))
{
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(psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[0]);
}
break;
case DACTION_MOVETODEMOLISH:
case DACTION_MOVETOREPAIR:
case DACTION_MOVETORESTORE:
if (!order->psStats)
{
psDroid->action = DACTION_NONE;
break;
}
// see if the droid is at the edge of what it is moving to
if (actionReachedBuildPos(psDroid, psDroid->actionPos.x, psDroid->actionPos.y, ((STRUCTURE *)psDroid->psActionTarget[0])->rot.direction, order->psStats))
{
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_MOVETORESTORE:
psDroid->action = droidStartRestore(psDroid) ? DACTION_RESTORE : DACTION_NONE;
break;
default:
break;
}
}
else if (DROID_STOPPED(psDroid))
{
moveDroidToNoFormation(psDroid, psDroid->actionPos.x, psDroid->actionPos.y);
}
break;
case DACTION_DEMOLISH:
case DACTION_REPAIR:
case DACTION_RESTORE:
if (!order->psStats)
{
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_RESTORE:
// DACTION_MOVETORESTORE;
actionUpdateFunc = droidUpdateRestore;
break;
default:
break;
}
// now do the action update
if (DROID_STOPPED(psDroid) && !actionReachedBuildPos(psDroid, psDroid->actionPos.x, psDroid->actionPos.y, ((STRUCTURE *)psDroid->psActionTarget[0])->rot.direction, order->psStats))
{
if (order->type != DORDER_HOLD)
{
objTrace(psDroid->id, "Secondary order: Go to construction site");
moveDroidToNoFormation(psDroid, psDroid->actionPos.x, psDroid->actionPos.y);
}
else
{
psDroid->action = DACTION_NONE;
}
}
else if (!DROID_STOPPED(psDroid) &&
psDroid->sMove.Status != MOVETURNTOTARGET &&
psDroid->sMove.Status != MOVESHUFFLE &&
actionReachedBuildPos(psDroid, psDroid->actionPos.x, psDroid->actionPos.y, ((STRUCTURE *)psDroid->psActionTarget[0])->rot.direction, order->psStats))
{
objTrace(psDroid->id, "Stopped - reached build position");
moveStopDroid(psDroid);
}
else if ( actionUpdateFunc(psDroid) )
{
//use 0 for non-combat(only 1 'weapon')
actionTargetTurret(psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[0]);
}
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, ((STRUCTURE *)psDroid->psActionTarget[0])->rot.direction, ((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_OBSERVE:
// align the turret
actionTargetTurret(psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[0]);
if (order->type != DORDER_HOLD && !cbSensorDroid(psDroid))
{
// 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(psDroid, psDroid->psActionTarget[0], false)
|| xdiff * xdiff + ydiff * ydiff >= rangeSq)
{
psDroid->action = DACTION_MOVETOOBSERVE;
moveDroidTo(psDroid, psDroid->psActionTarget[0]->pos.x, psDroid->psActionTarget[0]->pos.y);
}
}
break;
case DACTION_MOVETOOBSERVE:
// align the turret
actionTargetTurret(psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[0]);
if (visibleObject(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 (!order->psObj)
{
psDroid->action = DACTION_NONE;
return;
}
//can be either a droid or a structure now - AB 7/10/98
ASSERT_OR_RETURN(, (order->psObj->type == OBJ_DROID || order->psObj->type == OBJ_STRUCTURE)
&& aiCheckAlliances(order->psObj->player, psDroid->player), "DACTION_FIRESUPPORT: incorrect target type" );
//don't move VTOL's
// also don't move closer to sensor towers
if (!isVtolDroid(psDroid) &&
(order->psObj->type != OBJ_STRUCTURE))
{
Vector2i diff = removeZ(psDroid->pos - order->psObj->pos);
int rangeSq = asWeaponStats[psDroid->asWeaps[0].nStat].upgrade[psDroid->player].maxRange / 2; // move close to sensor
rangeSq = rangeSq * rangeSq;
if (diff*diff < rangeSq)
{
if (!DROID_STOPPED(psDroid))
{
moveStopDroid(psDroid);
}
}
else
{
if (!DROID_STOPPED(psDroid))
{
diff = removeZ(order->psObj->pos) - psDroid->sMove.destination;
}
if (DROID_STOPPED(psDroid) || diff*diff > rangeSq)
{
// move in range
moveDroidTo(psDroid, order->psObj->pos.x,order->psObj->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, psDroid->actionStarted + ACTION_DESTRUCT_TIME);
}
}
break;
case DACTION_MOVETODROIDREPAIR:
{
// moving to repair a droid
if (!psDroid->psActionTarget[0])
{
psDroid->action = DACTION_NONE;
return;
}
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(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->actionPos = psDroid->psActionTarget[0]->pos;
moveDroidTo(psDroid, psDroid->actionPos.x, psDroid->actionPos.y);
}
break;
}
case DACTION_DROIDREPAIR:
{
int xdiff, ydiff;
// If not doing self-repair (psActionTarget[0] is repair target)
if (psDroid->psActionTarget[0] != psDroid)
{
actionTargetTurret(psDroid, psDroid->psActionTarget[0], &psDroid->asWeaps[0]);
}
// Just self-repairing.
// See if there's anything to shoot.
else if (psDroid->numWeaps > 0 && !isVtolDroid(psDroid)
&& (order->type == DORDER_NONE || order->type == DORDER_HOLD || order->type == 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
&& secondaryGetState(psDroid, DSO_ATTACK_LEVEL) == DSS_ALEV_ALWAYS
&& aiBestNearestTarget(psDroid, &psTemp, i) >= 0 && 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 (order->type == DORDER_REPAIR)
{
// damaged droid has moved off - follow if we're not holding position!
psDroid->actionPos = psDroid->psActionTarget[0]->pos;
psDroid->action = DACTION_MOVETODROIDREPAIR;
moveDroidTo(psDroid, psDroid->actionPos.x, psDroid->actionPos.y);
}
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;
if (!vtolHappy(psDroid)) // Droid has cleared the rearm pad without getting rearmed. One way this can happen if a rearming pad was built under the VTOL while it was waiting for a pad.
{
moveToRearm(psDroid); // Rearm somewhere else instead.
}
}
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(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, psStruct, 0);
}
psDroid->action = DACTION_WAITFORREARM;
}
if (DROID_STOPPED(psDroid) ||
(psDroid->action == DACTION_WAITFORREARM))
{
Vector2i pos = removeZ(psDroid->psActionTarget[0]->pos);
if (!actionVTOLLandingPos(psDroid, &pos))
{
// 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, pos.x, pos.y);
}
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(psDroid, 0);
}
}
else
{
for (i = 0;i < psDroid->numWeaps;i++)
{
if (psDroid->asWeaps[i].rot.direction != 0 || psDroid->asWeaps[i].rot.pitch != 0)
{
actionAlignTurret(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);
Vector2i pos;
BASE_OBJECT *psTarget;
//added MinRangeResult;
UBYTE i;
CHECK_DROID(psDroid);
psDroid->actionStarted = gameTime;
syncDebugDroid(psDroid, '-');
syncDebug("%d does %s", psDroid->id, getDroidActionName(psAction->action));
DROID_ORDER_DATA *order = &psDroid->order;
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->actionPos = Vector2i(0, 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 || psDroid->droidType == DROID_SUPERTRANSPORTER) ||
(psAction->psObj == 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 || ((DROID *)psAction->psObj)->droidType == DROID_SUPERTRANSPORTER))
{
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->actionPos = psDroid->pos;
setDroidActionTarget(psDroid, psAction->psObj, 0);
if (!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(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->actionPos = psAction->psObj->pos;
psDroid->actionStarted = gameTime;
setDroidActionTarget(psDroid, psAction->psObj, 0);
pos = removeZ(psDroid->psActionTarget[0]->pos);
if (!actionVTOLLandingPos(psDroid, &pos))
{
// totally bunged up - give up
orderDroid(psDroid, DORDER_RTB, ModeImmediate);
break;
}
moveDroidToDirect(psDroid, pos.x, pos.y);
break;
case DACTION_CLEARREARMPAD:
debug( LOG_NEVER, "Unit %d clearing rearm pad", psDroid->id);
psDroid->action = DACTION_CLEARREARMPAD;
setDroidActionTarget(psDroid, psAction->psObj, 0);
pos = removeZ(psDroid->psActionTarget[0]->pos);
if (!actionVTOLLandingPos(psDroid, &pos))
{
// totally bunged up - give up
orderDroid(psDroid, DORDER_RTB, ModeImmediate);
break;
}
moveDroidToDirect(psDroid, pos.x, pos.y);
break;
case DACTION_MOVE:
case DACTION_TRANSPORTIN:
case DACTION_TRANSPORTOUT:
case DACTION_RETURNTOPOS:
case DACTION_FIRESUPPORT_RETREAT:
psDroid->action = psAction->action;
psDroid->actionPos.x = psAction->x;
psDroid->actionPos.y = psAction->y;
psDroid->actionStarted = gameTime;
setDroidActionTarget(psDroid, psAction->psObj, 0);
moveDroidTo(psDroid, psAction->x, psAction->y);
break;
case DACTION_BUILD:
if (!order->psStats)
{
psDroid->action = DACTION_NONE;
break;
}
ASSERT(order->type == DORDER_BUILD || order->type == DORDER_HELPBUILD ||
order->type == 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->actionPos.x = psAction->x;
psDroid->actionPos.y = psAction->y;
moveDroidToNoFormation(psDroid, psDroid->actionPos.x, psDroid->actionPos.y);
break;
case DACTION_DEMOLISH:
ASSERT(order->type == DORDER_DEMOLISH,
"cannot start demolish action without a demolish order");
psDroid->action = DACTION_MOVETODEMOLISH;
psDroid->actionPos.x = psAction->x;
psDroid->actionPos.y = psAction->y;
ASSERT((order->psObj != NULL) && (order->psObj->type == OBJ_STRUCTURE),
"invalid target for demolish order" );
order->psStats = ((STRUCTURE *)order->psObj)->pStructureType;
setDroidActionTarget(psDroid, psAction->psObj, 0);
moveDroidTo(psDroid, psAction->x, psAction->y);
break;
case DACTION_REPAIR:
psDroid->action = DACTION_MOVETOREPAIR;
psDroid->actionPos.x = psAction->x;
psDroid->actionPos.y = 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" );
order->psStats = ((STRUCTURE *)psDroid->psActionTarget[0])->pStructureType;
if (order->type != DORDER_HOLD)
{
psDroid->action = DACTION_MOVETOREPAIR;
moveDroidTo(psDroid, psAction->x, psAction->y);
}
break;
case DACTION_OBSERVE:
setDroidActionTarget(psDroid, psAction->psObj, 0);
psDroid->actionPos.x = psDroid->pos.x;
psDroid->actionPos.y = psDroid->pos.y;
if (order->type != DORDER_HOLD)
{
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) && order->psObj->type != OBJ_STRUCTURE)
{
moveDroidTo(psDroid, order->psObj->pos.x, order->psObj->pos.y); // movetotarget.
}
break;
case DACTION_SULK:
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->actionPos.x = psAction->x;
psDroid->actionPos.y = 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->actionPos.x = psAction->x;
psDroid->actionPos.y = 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:
psDroid->action = DACTION_MOVETODROIDREPAIR;
psDroid->actionPos.x = psAction->x;
psDroid->actionPos.y = psAction->y;
setDroidActionTarget(psDroid, psAction->psObj, 0);
//initialise the action points
psDroid->actionPoints = 0;
psDroid->actionStarted = gameTime;
if (order->type != DORDER_HOLD)
{
psDroid->action = DACTION_MOVETODROIDREPAIR;
moveDroidTo(psDroid, psAction->x, psAction->y);
}
break;
case DACTION_RESTORE:
ASSERT( order->type == DORDER_RESTORE,
"cannot start restore action without a restore order" );
psDroid->action = DACTION_MOVETORESTORE;
psDroid->actionPos.x = psAction->x;
psDroid->actionPos.y = psAction->y;
ASSERT( (order->psObj != NULL) && (order->psObj->type == OBJ_STRUCTURE),
"invalid target for restore order" );
order->psStats = ((STRUCTURE *)order->psObj)->pStructureType;
setDroidActionTarget(psDroid, psAction->psObj, 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 actionDroid(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 actionDroid(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 actionDroid(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;
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.type == DORDER_NONE)
{
// no order set - use the rearm order to ensure the unit goes back
// to the landing pad
orderDroidObj(psDroid, DORDER_REARM, psStruct, ModeImmediate);
}
else
{
actionDroid(psDroid, DACTION_MOVETOREARM, psStruct);
}
}
else
{
//return to base un-armed
orderDroid(psDroid, DORDER_RTB, ModeImmediate);
}
}
// 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(DROID const *psDroid, Vector2i *p)
{
int startX, startY;
DROID* psCurr;
bool foundTile;
Vector2i xyCoords;
CHECK_DROID(psDroid);
/* Initial box dimensions and set iteration count to zero */
startX = map_coord(p->x);
startY = map_coord(p->y);
// set blocking flags for all the other droids
for(psCurr=apsDroidLists[psDroid->player]; psCurr; psCurr = psCurr->psNext)
{
Vector2i t;
if (DROID_STOPPED(psCurr))
{
t = map_coord(removeZ(psCurr->pos));
}
else
{
t = map_coord(psCurr->sMove.destination);
}
if (psCurr != psDroid)
{
if (tileOnMap(t))
{
mapTile(t)->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);
p->x = world_coord(xyCoords.x) + TILE_UNITS / 2;
p->y = world_coord(xyCoords.y) + TILE_UNITS / 2;
}
// clear blocking flags for all the other droids
for(psCurr=apsDroidLists[psDroid->player]; psCurr; psCurr = psCurr->psNext)
{
Vector2i t;
if (DROID_STOPPED(psCurr))
{
t = map_coord(removeZ(psCurr->pos));
}
else
{
t = map_coord(psCurr->sMove.destination);
}
if (tileOnMap(t))
{
mapTile(t)->tileInfoBits &= ~BITS_FPATHBLOCK;
}
}
return foundTile;
}