/* This file is part of Warzone 2100. Copyright (C) 1999-2004 Eidos Interactive Copyright (C) 2005-2007 Warzone Resurrection Project Warzone 2100 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Warzone 2100 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Warzone 2100; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Action.c * * Functions for setting the action of a droid * */ #include "lib/framework/frame.h" #include "objects.h" #include "action.h" #include "map.h" #include "combat.h" #include "geometry.h" #include "lib/gamelib/gtime.h" #include "lib/ivis_common/ivisdef.h" #include "visibility.h" #include "projectile.h" #include "order.h" #include "hci.h" #include "transporter.h" #include "console.h" #include "research.h" #include "drive.h" #include "mission.h" #include "audio_id.h" #include "multiplay.h" #include "formation.h" #include "intdisplay.h" #include "fpath.h" #include "lib/script/script.h" #include "scripttabs.h" /* attack run distance */ #define VTOL_ATTACK_LENGTH 1000 #define VTOL_ATTACK_WIDTH 200 #define VTOL_ATTACK_TARDIST 400 #define VTOL_ATTACK_RETURNDIST 700 // turret rotation limits #define VTOL_TURRET_RLIMIT 315 #define VTOL_TURRET_LLIMIT 45 // time to pause before a droid blows up #define ACTION_DESTRUCT_TIME 2000 //#define PITCH_UPPER_LIMIT 60 //#define PITCH_LOWER_LIMIT -15 #define ACTION_TURRET_ROTATION_RATE 180 #define REPAIR_PITCH_LOWER 30 #define REPAIR_PITCH_UPPER -15 //how long to follow a damaged droid around before giving up if don't get near #define KEEP_TRYING_REPAIR 10000 //how far away the repair droid can be from the damaged droid to function #define REPAIR_RANGE (TILE_UNITS * TILE_UNITS * 4) //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; BASE_OBJECT *psObj; BASE_STATS *psStats; } DROID_ACTION_DATA; // Check if a droid has stoped moving #define DROID_STOPPED(psDroid) \ (psDroid->sMove.Status == MOVEINACTIVE || psDroid->sMove.Status == MOVEHOVER || \ psDroid->sMove.Status == MOVESHUFFLE) // choose a landing position for a VTOL when it goes to rearm BOOL actionVTOLLandingPos(DROID *psDroid, UDWORD *px, UDWORD *py); /* Check if a target is at correct range to attack */ BOOL actionInAttackRange(DROID *psDroid, BASE_OBJECT *psObj) { SDWORD dx, dy, dz, radSq, rangeSq, longRange; SECONDARY_STATE state; WEAPON_STATS *psStats; //if (psDroid->numWeaps == 0) if (psDroid->asWeaps[0].nStat == 0) { return FALSE; } psStats = asWeaponStats + psDroid->asWeaps[0].nStat; dx = (SDWORD)psDroid->x - (SDWORD)psObj->x; dy = (SDWORD)psDroid->y - (SDWORD)psObj->y; dz = (SDWORD)psDroid->z - (SDWORD)psObj->z; radSq = dx*dx + dy*dy; if ((psDroid->order == DORDER_ATTACKTARGET) && secondaryGetState(psDroid, DSO_HALTTYPE, &state) && (state == DSS_HALT_HOLD)) { longRange = proj_GetLongRange(psStats, dz); 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, dz); rangeSq = longRange * longRange; } break; case DSS_ARANGE_SHORT: rangeSq = psStats->shortRange * psStats->shortRange; break; case DSS_ARANGE_LONG: longRange = proj_GetLongRange(psStats, dz); rangeSq = longRange * longRange; break; default: ASSERT( FALSE, "actionInAttackRange: unknown attack range order" ); longRange = proj_GetLongRange(psStats, dz); 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; } else { return FALSE; } } return FALSE; } // check if a target is within weapon range BOOL actionInRange(DROID *psDroid, BASE_OBJECT *psObj) { SDWORD dx, dy, dz, radSq, rangeSq, longRange; WEAPON_STATS *psStats; //if (psDroid->numWeaps == 0) if (psDroid->asWeaps[0].nStat == 0) { return FALSE; } psStats = asWeaponStats + psDroid->asWeaps[0].nStat; dx = (SDWORD)psDroid->x - (SDWORD)psObj->x; dy = (SDWORD)psDroid->y - (SDWORD)psObj->y; dz = (SDWORD)psDroid->z - (SDWORD)psObj->z; radSq = dx*dx + dy*dy; longRange = proj_GetLongRange(psStats, dz); 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; } else { return FALSE; } } return FALSE; } // check if a target is inside minimum weapon range BOOL actionInsideMinRange(DROID *psDroid, BASE_OBJECT *psObj) { SDWORD dx, dy, dz, radSq, rangeSq, minRange; WEAPON_STATS *psStats; //if (psDroid->numWeaps == 0) if (psDroid->asWeaps[0].nStat == 0) { return FALSE; } psStats = asWeaponStats + psDroid->asWeaps[0].nStat; dx = (SDWORD)psDroid->x - (SDWORD)psObj->x; dy = (SDWORD)psDroid->y - (SDWORD)psObj->y; dz = (SDWORD)psDroid->z - (SDWORD)psObj->z; radSq = dx*dx + dy*dy; minRange = (SDWORD)psStats->minRange; rangeSq = minRange * minRange; // check min range if ( radSq <= rangeSq ) { return TRUE; } return FALSE; } /* Find a new location by a structure when building */ /*static BOOL actionNewBuildPos(DROID *psDroid, UDWORD *pX, UDWORD *pY) { //STRUCTURE_STATS *psStructStats; BASE_STATS *psStats; // get the structure stats switch (psDroid->action) { case DACTION_MOVETOBUILD: case DACTION_FOUNDATION_WANDER: if ( psDroid->order == DORDER_BUILD || psDroid->order == DORDER_LINEBUILD ) { //psStructStats = (STRUCTURE_STATS *)psDroid->psTarStats; psStats = psDroid->psTarStats; } else { ASSERT( psDroid->order == DORDER_HELPBUILD, "actionNewBuildPos: invalid order" ); //psStructStats = ((STRUCTURE *)psDroid->psTarget)->pStructureType; psStats = (BASE_STATS *)((STRUCTURE *)psDroid->psTarget)->pStructureType; } break; case DACTION_BUILD: case DACTION_BUILDWANDER: case DACTION_MOVETODEMOLISH: //case DACTION_MOVETOREPAIR: case DACTION_MOVETORESTORE: //psStructStats = ((STRUCTURE *)psDroid->psTarget)->pStructureType; psStats = (BASE_STATS *)((STRUCTURE *)psDroid->psTarget)->pStructureType; break; case DACTION_MOVETOREPAIR: case DACTION_MOVETOREARM: psStats = (BASE_STATS *)((STRUCTURE *)psDroid->psActionTarget)->pStructureType; break; case DACTION_MOVETOCLEAR: psStats = (BASE_STATS *)((FEATURE *)psDroid->psTarget)->psStats; break; default: ASSERT( FALSE, "actionNewBuildPos: invalid action" ); return FALSE; break; } //ASSERT( PTRVALID(psStructStats, sizeof(STRUCTURE_STATS)), // "actionNewBuildPos: invalid structure stats pointer" ); // find a new destination //if (getDroidDestination(psStructStats, psDroid->orderX, psDroid->orderY,pX,pY)) if (psDroid->action == DACTION_MOVETOREARM OR psDroid->action == DACTION_MOVETOREPAIR) { //use action target if (getDroidDestination(psStats, psDroid->actionX, psDroid->actionY, pX, pY)) { return TRUE; } } else { //use order target if (getDroidDestination(psStats, psDroid->orderX, psDroid->orderY, pX, pY)) { return TRUE; } } return FALSE; }*/ // Realign turret void actionAlignTurret(BASE_OBJECT *psObj) { UDWORD rotation; UWORD tRot, tPitch, nearest = 0; //get the maximum rotation this frame //rotation = (psDroid->turretRotRate * frameTime) / (4 * GAME_TICKS_PER_SEC); rotation = (ACTION_TURRET_ROTATION_RATE * frameTime) / (4 * GAME_TICKS_PER_SEC); if (rotation == 0) { rotation = 1; } switch (psObj->type) { case OBJ_DROID: tRot = ((DROID *)psObj)->turretRotation; tPitch = ((DROID *)psObj)->turretPitch; break; case OBJ_STRUCTURE: tRot = ((STRUCTURE *)psObj)->turretRotation; tPitch = ((STRUCTURE *)psObj)->turretPitch; // now find the nearest 90 degree angle nearest = (UWORD)(((tRot + 45) / 90) * 90); tRot = (UWORD)(((tRot + 360) - nearest) % 360); break; default: ASSERT( FALSE, "actionAlignTurret: invalid object type" ); return; break; } // DBP1(("rotrate=%d framTime=%d rotation=%d\n",psDroid->turretRotRate,frameTime,rotation)); //DBP1(("droid=%x turret=%x\n",psDroid,psDroid->turretRotation)); DBP1(("unit=%x turret=%x\n",psDroid,ACTION_TURRET_ROTATION_RATE)); if (rotation > 180)//crop to 180 degrees, no point in turning more than all the way round { rotation = 180; } if (tRot < 180)// +ve angle 0 - 179 degrees { if (tRot > rotation) { tRot = (UWORD)(tRot - rotation); } else { tRot = 0; } } else //angle greater than 180 rotate in opposite direction { if (tRot < (360 - rotation)) { tRot = (UWORD)(tRot + rotation); } else { tRot = 0; } } tRot %= 360; // align the turret pitch if (tPitch < 180)// +ve angle 0 - 179 degrees { if (tPitch > rotation/2) { tPitch = (UWORD)(tPitch - rotation/2); } else { tPitch = 0; } } else // -ve angle rotate in opposite direction { if (tPitch < (360 -(SDWORD)rotation/2)) { tPitch = (UWORD)(tPitch + rotation/2); } else { tPitch = 0; } } tPitch %= 360; switch (psObj->type) { case OBJ_DROID: ((DROID *)psObj)->turretRotation = tRot; ((DROID *)psObj)->turretPitch = tPitch; break; case OBJ_STRUCTURE: // now adjust back to the nearest 90 degree angle tRot = (UWORD)((tRot + nearest) % 360); ((STRUCTURE *)psObj)->turretRotation = tRot; ((STRUCTURE *)psObj)->turretPitch = tPitch; break; default: ASSERT( FALSE, "actionAlignTurret: invalid object type" ); return; break; } } #define HEAVY_WEAPON_WEIGHT 50000 /* returns true if on target */ //BOOL actionTargetTurret(BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget, UDWORD *pRotation, // UDWORD *pPitch, SDWORD rotRate, SDWORD pitchRate, BOOL bDirectFire, BOOL bInvert) //BOOL actionTargetTurret(BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget, UWORD *pRotation, // UWORD *pPitch, SWORD rotRate, SWORD pitchRate, BOOL bDirectFire, BOOL bInvert) BOOL actionTargetTurret(BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget, UWORD *pRotation, UWORD *pPitch, WEAPON_STATS *psWeapStats, BOOL bInvert) { SWORD tRotation, tPitch, rotRate, pitchRate; SDWORD targetRotation,targetPitch; SDWORD pitchError; SDWORD rotationError, dx, dy, dz; BOOL onTarget = FALSE; FRACT fR; SDWORD pitchLowerLimit, pitchUpperLimit; DROID *psDroid = NULL; // iVector muzzle; //these are constants now and can be set up at the start of the function rotRate = ACTION_TURRET_ROTATION_RATE; pitchRate = (SWORD)(ACTION_TURRET_ROTATION_RATE/2); //added for 22/07/99 upgrade - AB if (psWeapStats) { //extra heavy weapons on some structures need to rotate and pitch more slowly if (psWeapStats->weight > HEAVY_WEAPON_WEIGHT) { rotRate = (SWORD)(ACTION_TURRET_ROTATION_RATE/2 - (100 * (psWeapStats->weight - HEAVY_WEAPON_WEIGHT) / psWeapStats->weight)); pitchRate = (SWORD) (rotRate / 2); } } tRotation = *pRotation; tPitch = *pPitch; //set the pitch limits based on the weapon stats of the attacker pitchLowerLimit = pitchUpperLimit = 0; if (psAttacker->type == OBJ_STRUCTURE) { pitchLowerLimit = asWeaponStats[((STRUCTURE *)psAttacker)->asWeaps[0]. nStat].minElevation; pitchUpperLimit = asWeaponStats[((STRUCTURE *)psAttacker)->asWeaps[0]. nStat].maxElevation; } else if (psAttacker->type == OBJ_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 = asWeaponStats[psDroid->asWeaps[0]. nStat].minElevation; pitchUpperLimit = asWeaponStats[psDroid->asWeaps[0]. nStat].maxElevation; } else if ( psDroid->droidType == DROID_REPAIR ) { pitchLowerLimit = REPAIR_PITCH_LOWER; pitchUpperLimit = REPAIR_PITCH_UPPER; } } //get the maximum rotation this frame rotRate = (SWORD)(rotRate * frameTime / GAME_TICKS_PER_SEC); if (rotRate > 180)//crop to 180 degrees, no point in turning more than all the way round { rotRate = 180; } if (rotRate <= 0) { rotRate = 1; } pitchRate = (SWORD)(pitchRate * frameTime / GAME_TICKS_PER_SEC); if (pitchRate > 180)//crop to 180 degrees, no point in turning more than all the way round { pitchRate = 180; } if (pitchRate <= 0) { pitchRate = 1; } /* if ( (psAttacker->type == OBJ_STRUCTURE) && (((STRUCTURE *)psAttacker)->pStructureType->type == REF_DEFENSE) && (asWeaponStats[((STRUCTURE *)psAttacker)->asWeaps[0].nStat].surfaceToAir == SHOOT_IN_AIR) ) { rotRate = 180; pitchRate = 180; }*/ //and point the turret at target targetRotation = calcDirection(psAttacker->x, psAttacker->y, psTarget->x, psTarget->y); //DBP1(("att: rotrate=%d framTime=%d rotation=%d target=%d startrot=%d ",psAttacker->turretRotRate,frameTime,rotation,targetRotation,tRotation)); DBP1(("att: rotrate=%d framTime=%d rotation=%d target=%d startrot=%d ", ACTION_TURRET_ROTATION_RATE,frameTime,rotation,targetRotation,tRotation)); rotationError = targetRotation - (tRotation + psAttacker->direction); //restrict rotationerror to =/- 180 degrees while (rotationError > 180) { rotationError -= 360; } while (rotationError < -180) { rotationError += 360; } if (-rotationError > (SDWORD)rotRate) { // subtract rotation if (tRotation < rotRate) { tRotation = (SWORD)(tRotation + 360 - rotRate); } else { tRotation = (SWORD)(tRotation - rotRate); } } else if (rotationError > (SDWORD)rotRate) { // add rotation tRotation = (SWORD)(tRotation + rotRate); tRotation %= 360; } else //roughly there so lock on and fire { if ((SDWORD)psAttacker->direction > targetRotation) { tRotation = (SWORD)(targetRotation + 360 - psAttacker->direction); } else { tRotation = (SWORD)(targetRotation - psAttacker->direction); } DBP1(("locked on target...\n")); onTarget = TRUE; } tRotation %= 360; if ((psAttacker->type == OBJ_DROID) && vtolDroid((DROID *)psAttacker)) { // limit the rotation for vtols if ((tRotation <= 180) && (tRotation > VTOL_TURRET_LLIMIT)) { tRotation = VTOL_TURRET_LLIMIT; } else if ((tRotation > 180) && (tRotation < VTOL_TURRET_RLIMIT)) { tRotation = VTOL_TURRET_RLIMIT; } } /* set muzzle pitch if direct fire */ // if ( asWeaponStats[psAttacker->asWeaps->nStat].direct == TRUE ) if ( psWeapStats != NULL && ( proj_Direct( psWeapStats ) || ( (psAttacker->type == OBJ_DROID) && !proj_Direct( psWeapStats ) && actionInsideMinRange(psDroid, psDroid->psActionTarget) )) ) { // difference between muzzle position and droid origin is unlikely to affect aiming // particularly as target origin is used // calcDroidMuzzleLocation( psAttacker, &muzzle); dx = psTarget->x - psAttacker->x;//muzzle.x; dy = psTarget->y - psAttacker->y;//muzzle.y; // dz = map_Height(psTarget->x, psTarget->y) - psAttacker->z;//muzzle.z; dz = psTarget->z - psAttacker->z;//muzzle.z; /* get target distance */ fR = trigIntSqrt( dx*dx + dy*dy ); targetPitch = (SDWORD)( RAD_TO_DEG(atan2(dz, fR))); //tPitch = tPitch; if (tPitch > 180) { tPitch -=360; } /* invert calculations for bottom-mounted weapons (i.e. for vtols) */ if ( bInvert ) { tPitch = (SWORD)(-tPitch); targetPitch = -targetPitch; } pitchError = targetPitch - tPitch; if (pitchError < -pitchRate) { // move down tPitch = (SWORD)(tPitch - pitchRate); onTarget = FALSE; } else if (pitchError > pitchRate) { // add rotation tPitch = (SWORD)(tPitch + pitchRate); onTarget = FALSE; } else //roughly there so lock on and fire { tPitch = (SWORD)targetPitch; } /* re-invert result for bottom-mounted weapons (i.e. for vtols) */ if ( bInvert ) { tPitch = (SWORD)-tPitch; } if (tPitch < pitchLowerLimit) { // move down tPitch = (SWORD)pitchLowerLimit; onTarget = FALSE; } else if (tPitch > pitchUpperLimit) { // add rotation tPitch = (SWORD)pitchUpperLimit; onTarget = FALSE; } if (tPitch < 0) { tPitch += 360; } } *pRotation = tRotation; *pPitch = tPitch; DBP1(("endrot=%d\n",tRotation)); return onTarget; } // return whether a droid can see a target to fire on it BOOL actionVisibleTarget(DROID *psDroid, BASE_OBJECT *psTarget) { WEAPON_STATS *psStats; //if (psDroid->numWeaps == 0) if ((psDroid->asWeaps[0].nStat == 0) || vtolDroid(psDroid)) { return visibleObject((BASE_OBJECT*)psDroid, psTarget); } psStats = asWeaponStats + psDroid->asWeaps[0].nStat; if (proj_Direct(psStats)) { if (visibleObjWallBlock((BASE_OBJECT*)psDroid, psTarget)) { 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)) { return TRUE; } } } return FALSE; } void actionAddVtolAttackRun( DROID *psDroid ) { FRACT_D fA; SDWORD deltaX, deltaY, iA, iX, iY; BASE_OBJECT *psTarget; #if 0 SDWORD iVx, iVy; #endif if ( psDroid->psActionTarget != NULL ) { psTarget = psDroid->psActionTarget; } else if ( psDroid->psTarget != NULL ) { psTarget = psDroid->psTarget; } else { return; } /* get normal vector from droid to target */ deltaX = psTarget->x - psDroid->x; deltaY = psTarget->y - psDroid->y; /* get magnitude of normal vector (Pythagorean theorem) */ fA = trigIntSqrt( deltaX*deltaX + deltaY*deltaY ); iA = MAKEINT(fA); #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->x + iVx; iY = psTarget->y + iVy; // orderAddWayPoint( psDroid, iX, iY ); #endif /* add waypoint behind target attack length away*/ if (iA != 0) { iX = psTarget->x + (deltaX * VTOL_ATTACK_LENGTH / iA); iY = psTarget->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->x; iY = psTarget->y; } // orderAddWayPoint( psDroid, iX, iY ); if ( iX<=0 || iY<=0 || iX>(SDWORD)(GetWidthOfMap()<(SDWORD)(GetHeightOfMap()<sMove.iAttackRuns++; } void actionUpdateVtolAttack( DROID *psDroid ) { WEAPON_STATS *psWeapStats = NULL; /* don't do attack runs whilst returning to base */ if ( psDroid->order == DORDER_RTB ) { return; } //if (psDroid->numWeaps > 0) if (psDroid->asWeaps[0].nStat > 0) { psWeapStats = asWeaponStats + psDroid->asWeaps[0].nStat; ASSERT( PTRVALID(psWeapStats, sizeof(WEAPON_STATS)), "actionUpdateVtolAttack: invalid weapon stats pointer" ); } /* order back to base after fixed number of attack runs */ if ( psWeapStats != NULL ) { //if ( psDroid->sMove.iAttackRuns >= psWeapStats->vtolAttackRuns ) if (vtolEmpty(psDroid)) { moveToRearm(psDroid); return; } } /* circle around target if hovering and not cyborg */ if ( psDroid->sMove.Status == MOVEHOVER && //psDroid->droidType != DROID_CYBORG ) !cyborgDroid(psDroid)) { actionAddVtolAttackRun( psDroid ); } } void actionUpdateTransporter( DROID *psDroid ) { //check if transporter has arrived if (updateTransporter(psDroid)) { // Got to destination psDroid->action = DACTION_NONE; return; } //check the target hasn't become one the same player ID - Electronic Warfare if (psDroid->psActionTarget != NULL AND psDroid->player == psDroid->psActionTarget->player) { psDroid->psActionTarget = NULL; } /* check for weapon */ //if ( psDroid->numWeaps > 0 ) if (psDroid->asWeaps[0].nStat > 0) { if (psDroid->psActionTarget == NULL) { aiNearestTarget(psDroid, &psDroid->psActionTarget); } if ( psDroid->psActionTarget != NULL ) { if ( visibleObject((BASE_OBJECT*)psDroid, psDroid->psActionTarget) ) { /*if (actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), psDroid->turretRotRate, (SWORD)(psDroid->turretRotRate/2), //asWeaponStats[psDroid->asWeaps->nStat].direct)) proj_Direct(&asWeaponStats[psDroid->asWeaps->nStat]), TRUE))*/ if (actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), &asWeaponStats[psDroid->asWeaps->nStat], TRUE)) { // In range - fire !!! combFire(psDroid->asWeaps, (BASE_OBJECT *)psDroid, psDroid->psActionTarget); } } else { // lost the target psDroid->psActionTarget = NULL; } } } } // calculate a position for units to pull back to if they // need to increase the range between them and a target 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->x - (SDWORD)psTarget->x; ydiff = (SDWORD)psObj->y - (SDWORD)psTarget->y; len = (SDWORD)iSQRT(xdiff*xdiff + ydiff*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->x + xdiff * PULL_BACK_DIST; *py = (SDWORD)psObj->y + ydiff * PULL_BACK_DIST; } // 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; // 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 = (SDWORD)(psDroid->x >> (TILE_SHIFT)); dy = (SDWORD)(psDroid->y >> (TILE_SHIFT)); 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 = x >> (TILE_SHIFT); ty = y >> (TILE_SHIFT); // 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 BOOL actionDroidOnBuildPos(DROID *psDroid, SDWORD x, SDWORD y, BASE_STATS *psStats) { SDWORD width, breadth, tx,ty, dx,dy; dx = (SDWORD)(psDroid->x >> (TILE_SHIFT)); dy = (SDWORD)(psDroid->y >> (TILE_SHIFT)); 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 = (x >> (TILE_SHIFT)) - (width/2); ty = (y >> (TILE_SHIFT)) - (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 void actionHomeBasePos(SDWORD player, SDWORD *px, SDWORD *py) { STRUCTURE *psStruct; ASSERT( player >= 0 && player < MAX_PLAYERS, "actionHomeBasePos: invalide player number" ); for(psStruct = apsStructLists[player]; psStruct; psStruct=psStruct->psNext) { if (psStruct->pStructureType->type == REF_HQ) { *px = (SDWORD)psStruct->x; *py = (SDWORD)psStruct->y; return; } } *px = getLandingX(player); *py = getLandingY(player); } // tell the action system of a potential location for walls blocking routing BOOL actionRouteBlockingPos(DROID *psDroid, SDWORD tx, SDWORD ty) { SDWORD i,j; MAPTILE *psTile; STRUCTURE *psWall; if (vtolDroid(psDroid) || ((psDroid->order != DORDER_MOVE) && (psDroid->order != DORDER_SCOUT))) { return FALSE; } // see if there is a wall to attack around the location psWall = NULL; for(i= tx -1; i <= tx + 1; i++) { for(j= ty -1; j <= ty + 1; j++) { if (tileOnMap(i,j)) { psTile = mapTile(i,j); if (psTile->tileInfoBits & BITS_WALL) { psWall = getTileStructure((UDWORD)i,(UDWORD)j); if (psWall->player != psDroid->player) { goto done; } else { psWall = NULL; } } } } } done: if (psWall != NULL) { if (psDroid->order == DORDER_MOVE) { psDroid->order = DORDER_MOVE_ATTACKWALL; } else if (psDroid->order == DORDER_SCOUT) { psDroid->order = DORDER_SCOUT_ATTACKWALL; } psDroid->psTarget = (BASE_OBJECT *)psWall; return TRUE; } return FALSE; } #define VTOL_ATTACK_AUDIO_DELAY (3*GAME_TICKS_PER_SEC) // Update the action state for a droid void actionUpdateDroid(DROID *psDroid) { // UDWORD structX,structY; UDWORD droidX,droidY; UDWORD tlx,tly; STRUCTURE *psStruct; STRUCTURE_STATS *psStructStats; BASE_OBJECT *psTarget = psDroid->psTarget;//, *psObj; WEAPON_STATS *psWeapStats; SDWORD targetDir, dirDiff, pbx,pby; SDWORD xdiff,ydiff, rangeSq; SECONDARY_STATE state; PROPULSION_STATS *psPropStats; BOOL bChaseBloke, bInvert; FEATURE *psNextWreck; BOOL (*actionUpdateFunc)(DROID *psDroid) = NULL; SDWORD moveAction; BOOL bDoHelpBuild; MAPTILE *psTile; psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat; ASSERT( PTRVALID(psPropStats, sizeof(PROPULSION_STATS)), "actionUpdateUnit: invalid propulsion stats pointer" ); ASSERT( psDroid->turretRotation < 360, "turretRotation out of range" ); ASSERT( psDroid->direction < 360, "unit direction out of range" ); /* check whether turret inverted for actionTargetTurret */ //if ( psDroid->droidType != DROID_CYBORG && if ( !cyborgDroid(psDroid) && psPropStats->propulsionType == LIFT ) { bInvert = TRUE; } else { bInvert = FALSE; } // clear the target if it has died if (psDroid->psActionTarget && psDroid->psActionTarget->died) { psDroid->psActionTarget = NULL; if ( (psDroid->action != DACTION_MOVEFIRE) && (psDroid->action != DACTION_TRANSPORTIN) && (psDroid->action != DACTION_TRANSPORTOUT) ) { psDroid->action = DACTION_NONE; //if Vtol - return to rearm pad if (vtolDroid(psDroid)) { 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; } } //if (psDroid->numWeaps > 0) if (psDroid->asWeaps[0].nStat > 0) { psWeapStats = asWeaponStats + psDroid->asWeaps[0].nStat; } else { psWeapStats = NULL; } switch (psDroid->action) { case DACTION_NONE: case DACTION_WAITFORREPAIR: // doing nothing //since not doing anything, see if need to self repair if (selfRepairEnabled(psDroid->player)) { //wait for 1 second to give the repair facility a chance to do the repair work if (gameTime - psDroid->actionStarted > GAME_TICKS_PER_SEC) { droidSelfRepair(psDroid); } } break; case DACTION_WAITDURINGREPAIR: // don't want to be in a formation for this move if (psDroid->sMove.psFormation != NULL) { formationLeave(psDroid->sMove.psFormation, (BASE_OBJECT *)psDroid); psDroid->sMove.psFormation = NULL; } // move back to the repair facility if necessary if (DROID_STOPPED(psDroid) && !actionReachedBuildPos(psDroid, (SDWORD)psDroid->psTarget->x,(SDWORD)psDroid->psTarget->y, (BASE_STATS *)((STRUCTURE*)psDroid->psTarget)->pStructureType ) ) { moveDroidToNoFormation(psDroid, psDroid->psTarget->x, psDroid->psTarget->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 AND getDroidsToSafetyFlag()) { if ((SDWORD)(mission.ETA - (gameTime - missionGetReinforcementTime())) <= 0) { if (!droidRemove(psDroid, mission.apsDroidLists)) { ASSERT( FALSE, "actionUpdate: 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, (UWORD*)&droidX, (UWORD*)&droidY); psDroid->x = (UWORD)droidX; psDroid->y = (UWORD)droidY; //fly Transporter back to get some more droids orderDroidLoc( psDroid, DORDER_TRANSPORTIN, getLandingX(selectedPlayer), getLandingY(selectedPlayer)); } 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; //if vtol and offworld and empty - 'magic' it back home! /* alternatively - lets not - John. if (vtolEmpty(psDroid) AND missionIsOffworld()) { //check has reached LZ xdiff = (SDWORD)psDroid->x - (SDWORD)psDroid->actionX; ydiff = (SDWORD)psDroid->y - (SDWORD)psDroid->actionY; if (xdiff*xdiff + ydiff*ydiff <= TILE_UNITS*TILE_UNITS) { //magic! - take droid out of list and add to one back at home base if (droidRemove(psDroid, apsDroidLists)) { //only add to mission lists if successfully removed from current droid lists addDroid(psDroid, mission.apsDroidLists); //make sure fully armed etc by time back at home mendVtol(psDroid); addConsoleMessage("VTOL MAGIC!",DEFAULT_JUSTIFY); } //else the droid should be dead! } else { //re-order the droid back to the LZ orderDroidLoc(psDroid, DORDER_MOVE, getLandingX(psDroid->player), getLandingY(psDroid->player)); } }*/ } /* else if(secondaryGetState(psDroid, DSO_HALTTYPE, &state) && (state == DSS_HALT_HOLD)) { psDroid->action = DACTION_NONE; // hold is set, stop moving. moveStopDroid(psDroid); }*/ /* else if ((psDroid->order == DORDER_SCOUT) && aiNearestTarget(psDroid, &psObj)) { if (psDroid->numWeaps > 0) { orderDroidObj(psDroid, DORDER_ATTACK, psObj); } else { orderDroid(psDroid, DORDER_STOP); } }*/ //else if (psDroid->numWeaps > 0 && else if (!vtolDroid(psDroid) && psDroid->asWeaps[0].nStat > 0 && psWeapStats->rotate && psWeapStats->fireOnMove != FOM_NO && aiNearestTarget(psDroid, &psDroid->psActionTarget)) { if (secondaryGetState(psDroid, DSO_ATTACK_LEVEL, &state)) { if (state == DSS_ALEV_ALWAYS) { psDroid->action = DACTION_MOVEFIRE; } } else { psDroid->action = DACTION_MOVEFIRE; } } break; case DACTION_RETURNTOPOS: case DACTION_FIRESUPPORT_RETREAT: if (DROID_STOPPED(psDroid)) { // Got to destination psDroid->action = DACTION_NONE; } /* else if(secondaryGetState(psDroid, DSO_HALTTYPE, &state) && (state == DSS_HALT_HOLD)) { psDroid->action = DACTION_NONE; // hold is set, stop moving. moveStopDroid(psDroid); }*/ break; case DACTION_TRANSPORTIN: case DACTION_TRANSPORTOUT: actionUpdateTransporter( psDroid ); break; case DACTION_MOVEFIRE: //check if vtol that its armed if (vtolEmpty(psDroid)) { moveToRearm(psDroid); } // firing on something while moving if (DROID_STOPPED(psDroid)) { // Got to desitination psDroid->action = DACTION_NONE; } else if ((psDroid->psActionTarget == NULL) || !validTarget((BASE_OBJECT *)psDroid, psDroid->psActionTarget) || (secondaryGetState(psDroid, DSO_ATTACK_LEVEL, &state) && (state != DSS_ALEV_ALWAYS))) { // Target lost psDroid->action = DACTION_MOVE; //if Vtol - return to rearm pad /*if (vtolDroid(psDroid)) { moveToRearm(psDroid); }*/ } //check the target hasn't become one the same player ID - Electronic Warfare else if (electronicDroid(psDroid) && (psDroid->player == psDroid->psActionTarget->player)) { psDroid->psActionTarget = NULL; psDroid->action = DACTION_NONE; } else { if (visibleObject((BASE_OBJECT*)psDroid, psDroid->psActionTarget)) { /*if (actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), psDroid->turretRotRate, (SWORD)(psDroid->turretRotRate/2), //asWeaponStats[psDroid->asWeaps->nStat].direct)) proj_Direct(&asWeaponStats[psDroid->asWeaps->nStat]), bInvert))*/ if (actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), &asWeaponStats[psDroid->asWeaps->nStat], bInvert)) { // In range - fire !!! combFire(psDroid->asWeaps, (BASE_OBJECT *)psDroid, psDroid->psActionTarget); } } else { // lost the target psDroid->action = DACTION_MOVE; psDroid->psActionTarget = NULL; } } //check its a VTOL unit since adding Transporter's into multiPlayer /* check vtol attack runs */ //if ( psPropStats->propulsionType == LIFT ) if (vtolDroid(psDroid)) { actionUpdateVtolAttack( psDroid ); } break; case DACTION_ATTACK: ASSERT( psDroid->psActionTarget != NULL, "actionUpdateUnit: target is NULL while attacking" ); // don't wan't formations for this one if (psDroid->sMove.psFormation) { formationLeave(psDroid->sMove.psFormation, (BASE_OBJECT *)psDroid); psDroid->sMove.psFormation = NULL; } //check the target hasn't become one the same player ID - Electronic Warfare if ((electronicDroid(psDroid) && (psDroid->player == psDroid->psActionTarget->player)) || !validTarget((BASE_OBJECT *)psDroid, psDroid->psActionTarget))// || // (secondaryGetState(psDroid, DSO_ATTACK_LEVEL, &state) && (state != DSS_ALEV_ALWAYS))) { psDroid->psActionTarget = NULL; psDroid->action = DACTION_NONE; } else if (actionVisibleTarget(psDroid, psDroid->psActionTarget) && actionInAttackRange(psDroid, psDroid->psActionTarget)) { if (!psWeapStats->rotate) { // no rotating turret - need to check aligned with target targetDir = (SDWORD)calcDirection( psDroid->x,psDroid->y, psDroid->psActionTarget->x,psDroid->psActionTarget->y); dirDiff = labs(targetDir - (SDWORD)psDroid->direction); } else { dirDiff = 0; } if (dirDiff > FIXED_TURRET_DIR) { if (psDroid->sMove.Status != MOVESHUFFLE) { psDroid->action = DACTION_ROTATETOATTACK; moveTurnDroid(psDroid, psDroid->psActionTarget->x,psDroid->psActionTarget->y); } } else if (!psWeapStats->rotate || /*actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), psDroid->turretRotRate, (SWORD)(psDroid->turretRotRate/2), //asWeaponStats[psDroid->asWeaps->nStat].direct)) proj_Direct(&asWeaponStats[psDroid->asWeaps->nStat]), bInvert))*/ actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), &asWeaponStats[psDroid->asWeaps->nStat], bInvert)) { /* In range - fire !!! */ combFire(psDroid->asWeaps, (BASE_OBJECT *)psDroid, psDroid->psActionTarget); } } else { if ( ( (psDroid->order == DORDER_ATTACKTARGET || psDroid->order == DORDER_FIRESUPPORT) && secondaryGetState(psDroid, DSO_HALTTYPE, &state) && (state == DSS_HALT_HOLD) ) || ( !vtolDroid(psDroid) && orderStateObj(psDroid, DORDER_FIRESUPPORT, &psTarget) && (psTarget->type == OBJ_STRUCTURE) ) ) { // don't move if on hold or firesupport for a sensor tower psDroid->action = DACTION_NONE; // holding, cancel the order. } else { psDroid->action = DACTION_MOVETOATTACK; // out of range - chase it } } break; case DACTION_VTOLATTACK: //check if vtol that its armed if ( (vtolEmpty(psDroid)) || (psDroid->psActionTarget == NULL) || //check the target hasn't become one the same player ID - Electronic Warfare (electronicDroid(psDroid) && (psDroid->player == psDroid->psActionTarget->player)) || !validTarget((BASE_OBJECT *)psDroid, psDroid->psActionTarget) )// || // (secondaryGetState(psDroid, DSO_ATTACK_LEVEL, &state) && (state != DSS_ALEV_ALWAYS))) { moveToRearm(psDroid); break; } if (actionVisibleTarget(psDroid, psDroid->psActionTarget)) { if (actionInRange(psDroid, psDroid->psActionTarget)) { if ( psDroid->player == selectedPlayer ) { audio_QueueTrackMinDelay( ID_SOUND_COMMENCING_ATTACK_RUN2, VTOL_ATTACK_AUDIO_DELAY ); } if (actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), &asWeaponStats[psDroid->asWeaps->nStat], bInvert)) { // In range - fire !!! combFire(psDroid->asWeaps, (BASE_OBJECT *)psDroid, psDroid->psActionTarget); } } else { actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), &asWeaponStats[psDroid->asWeaps->nStat], bInvert); } } /* else { // lost the target psDroid->action = DACTION_MOVETOATTACK; moveDroidTo(psDroid, psDroid->psActionTarget->x, psDroid->psActionTarget->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 xdiff = (SDWORD)psDroid->x - (SDWORD)psDroid->psActionTarget->x; ydiff = (SDWORD)psDroid->y - (SDWORD)psDroid->psActionTarget->y; 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 xdiff = (SDWORD)psDroid->sMove.DestinationX - (SDWORD)psDroid->psActionTarget->x; ydiff = (SDWORD)psDroid->sMove.DestinationY - (SDWORD)psDroid->psActionTarget->y; if ((xdiff*xdiff + ydiff*ydiff) < (VTOL_ATTACK_TARDIST*VTOL_ATTACK_TARDIST)) { actionAddVtolAttackRun( psDroid ); } } // if the vtol is far enough away head for the target again // else if (rangeSq > (VTOL_ATTACK_RETURNDIST*VTOL_ATTACK_RETURNDIST)) else if (rangeSq > (SDWORD)(psWeapStats->longRange*psWeapStats->longRange)) { // don't do another attack run if already heading for the target xdiff = (SDWORD)psDroid->sMove.DestinationX - (SDWORD)psDroid->psActionTarget->x; ydiff = (SDWORD)psDroid->sMove.DestinationY - (SDWORD)psDroid->psActionTarget->y; if ((xdiff*xdiff + ydiff*ydiff) > (VTOL_ATTACK_TARDIST*VTOL_ATTACK_TARDIST)) { moveDroidToDirect(psDroid, psDroid->psActionTarget->x,psDroid->psActionTarget->y); } } } break; case DACTION_MOVETOATTACK: DBP1(("MOVETOATTACK - %x\n",psDroid)); // don't wan't formations for this one if (psDroid->sMove.psFormation) { formationLeave(psDroid->sMove.psFormation, (BASE_OBJECT *)psDroid); psDroid->sMove.psFormation = NULL; } // send vtols back to rearm if (vtolDroid(psDroid) && vtolEmpty(psDroid)) { moveToRearm(psDroid); break; } //check the target hasn't become one the same player ID - Electronic Warfare if ((electronicDroid(psDroid) && (psDroid->player == psDroid->psActionTarget->player)) || !validTarget((BASE_OBJECT *)psDroid, psDroid->psActionTarget) )// || // (secondaryGetState(psDroid, DSO_ATTACK_LEVEL, &state) && (state != DSS_ALEV_ALWAYS))) { psDroid->psActionTarget = NULL; psDroid->action = DACTION_NONE; } else { if (actionVisibleTarget(psDroid, psDroid->psActionTarget)) { if (psWeapStats->rotate) { /*actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), psDroid->turretRotRate, (SWORD)(psDroid->turretRotRate/2), //asWeaponStats[psDroid->asWeaps->nStat].direct); proj_Direct(&asWeaponStats[psDroid->asWeaps->nStat]), bInvert);*/ actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), &asWeaponStats[psDroid->asWeaps->nStat], bInvert); } bChaseBloke = FALSE; if (!vtolDroid(psDroid) && psDroid->psActionTarget->type == OBJ_DROID && ((DROID *)psDroid->psActionTarget)->droidType == DROID_PERSON && psWeapStats->fireOnMove != FOM_NO) { bChaseBloke = TRUE; } if (actionInAttackRange(psDroid, psDroid->psActionTarget) && !bChaseBloke) { /* Stop the droid moving any closer */ // ASSERT( psDroid->x != 0 && psDroid->y != 0, // "moveUpdateUnit: Unit at (0,0)" ); /* init vtol attack runs count if necessary */ if ( psPropStats->propulsionType == 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->x,psDroid->psActionTarget->y); } } } else if (actionInRange(psDroid, psDroid->psActionTarget)) { // fire while closing range combFire(psDroid->asWeaps, (BASE_OBJECT *)psDroid, psDroid->psActionTarget); } } else { if ((psDroid->turretRotation!=0) || (psDroid->turretPitch!=0)) { actionAlignTurret((BASE_OBJECT *)psDroid); } } if (DROID_STOPPED(psDroid) && psDroid->action != DACTION_ATTACK) { /* Stopped moving but haven't reached the target - possibly move again */ if ((psDroid->order == DORDER_ATTACKTARGET) && secondaryGetState(psDroid, DSO_HALTTYPE, &state) && (state == DSS_HALT_HOLD)) { psDroid->action = DACTION_NONE; // on hold, give up. } else if (actionInsideMinRange(psDroid, psDroid->psActionTarget)) { if ( proj_Direct( psWeapStats ) ) { // try and extend the range actionCalcPullBackPoint((BASE_OBJECT *)psDroid, psDroid->psActionTarget, &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->x, psDroid->psActionTarget->y); } } } else { // try to close the range moveDroidTo(psDroid, psDroid->psActionTarget->x, psDroid->psActionTarget->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 ??? //if (gameTime>psDroid->actionHeight) // actionHeight is used here for the ending time for this action //hmmm, hope this doesn't cause any problems! if (gameTime > psDroid->actionStarted) { // debug( LOG_NEVER, "sulk over.. %p\n", 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: // The droid cannot be in a formation if (psDroid->sMove.psFormation != NULL) { formationLeave(psDroid->sMove.psFormation, (BASE_OBJECT *)psDroid); psDroid->sMove.psFormation = NULL; } // 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)) { moveStopDroid(psDroid); /* xdiff = (SDWORD)psDroid->x - (SDWORD)psDroid->actionX; ydiff = (SDWORD)psDroid->y - (SDWORD)psDroid->actionY; if ( xdiff*xdiff + ydiff*ydiff >= TILE_UNITS*TILE_UNITS ) { DBP2(("DACTION_MOVETOBUILD: new location\n")); // Couldn't reach destination - try and find a new one if (actionNewBuildPos(psDroid, &droidX,&droidY)) { psDroid->actionX = droidX; psDroid->actionY = droidY; moveDroidTo(psDroid, droidX,droidY); } else { DBP2(("DACTION_MOVETOBUILD: giving up\n")); psDroid->action = DACTION_NONE; } } else*/ { bDoHelpBuild = FALSE; // Got to destination - start building psStructStats = (STRUCTURE_STATS*)psDroid->psTarStats; if (psDroid->order == DORDER_BUILD && (psDroid->psTarget == NULL)) // && ( // psStructStats->type != REF_WALL || // psStructStats->type != REF_WALLCORNER)) //psDroid->order == DORDER_LINEBUILD) { // Starting a new structure // calculate the top left of the structure tlx = (SDWORD)psDroid->orderX - (SDWORD)(psStructStats->baseWidth * TILE_UNITS)/2; tly = (SDWORD)psDroid->orderY - (SDWORD)(psStructStats->baseBreadth * TILE_UNITS)/2; // tlx = tlx >> TILE_UNITS; // tly = tly >> TILE_UNITS; //need to check if something has already started building here? //unless its a module! if (IsStatExpansionModule(psStructStats)) { DBP2(("DACTION_MOVETOBUILD: setUpBuildModule\n")); setUpBuildModule(psDroid); } else if ( TILE_HAS_STRUCTURE(mapTile(psDroid->orderX >> TILE_SHIFT, psDroid->orderY >> TILE_SHIFT)) ) { // structure on the build location - see if it is the same type psStruct = getTileStructure(psDroid->orderX >> TILE_SHIFT, psDroid->orderY >> TILE_SHIFT); if (psStruct->pStructureType == (STRUCTURE_STATS *)psDroid->psTarStats) { // same type - do a help build psDroid->psTarget = (BASE_OBJECT *)psStruct; bDoHelpBuild = 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)) { DBP2(("DACTION_MOVETOBUILD: start foundation\n")); psDroid->action = DACTION_BUILD; } else { psDroid->action = DACTION_NONE; } } else { psDroid->action = DACTION_NONE; } } else if (!validLocation((BASE_STATS*)psDroid->psTarStats, // psDroid->orderX >> TILE_SHIFT, psDroid->orderY >> TILE_SHIFT)) tlx >> TILE_SHIFT, tly >> TILE_SHIFT, psDroid->player, FALSE)) { DBP2(("DACTION_MOVETOBUILD: !validLocation\n")); psDroid->action = DACTION_NONE; } else if (droidStartFoundation(psDroid)) { DBP2(("DACTION_MOVETOBUILD: start foundation\n")); psDroid->action = DACTION_BUILD_FOUNDATION; } else { DBP2(("DACTION_MOVETOBUILD: giving up (2)\n")); psDroid->action = DACTION_NONE; } } else if( (psDroid->order == DORDER_LINEBUILD||psDroid->order==DORDER_BUILD) &&(psStructStats->type == REF_WALL || psStructStats->type == REF_WALLCORNER || psStructStats->type == REF_DEFENSE)) { // building a wall. psTile = mapTile(psDroid->orderX>>TILE_SHIFT, psDroid->orderY>>TILE_SHIFT); if ((psDroid->psTarget == NULL) && (TILE_HAS_STRUCTURE(psTile) || TILE_HAS_FEATURE(psTile))) { if (TILE_HAS_STRUCTURE(psTile)) { // structure on the build location - see if it is the same type psStruct = getTileStructure(psDroid->orderX >> TILE_SHIFT, psDroid->orderY >> TILE_SHIFT); if (psStruct->pStructureType == (STRUCTURE_STATS *)psDroid->psTarStats) { // same type - do a help build psDroid->psTarget = (BASE_OBJECT *)psStruct; bDoHelpBuild = 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)) { DBP2(("DACTION_MOVETOBUILD: start foundation\n")); 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 { bDoHelpBuild = TRUE; } if (bDoHelpBuild) { // continuing a partially built structure (order = helpBuild) if (droidStartBuild(psDroid)) { DBP2(("DACTION_MOVETOBUILD: starting help build\n")); psDroid->action = DACTION_BUILD; intBuildStarted(psDroid); } else { DBP2(("DACTION_MOVETOBUILD: giving up (3)\n")); psDroid->action = DACTION_NONE; } } } } else if (DROID_STOPPED(psDroid)) { if (actionDroidOnBuildPos(psDroid, (SDWORD)psDroid->orderX,(SDWORD)psDroid->orderY, psDroid->psTarStats)) { actionHomeBasePos(psDroid->player, &pbx,&pby); moveDroidToNoFormation(psDroid, (UDWORD)pbx,(UDWORD)pby); } else { moveDroidToNoFormation(psDroid, psDroid->actionX,psDroid->actionY); } } break; case DACTION_BUILD: // The droid cannot be in a formation if (psDroid->sMove.psFormation != NULL) { formationLeave(psDroid->sMove.psFormation, (BASE_OBJECT *)psDroid); psDroid->sMove.psFormation = NULL; } if (DROID_STOPPED(psDroid) && !actionReachedBuildPos(psDroid, (SDWORD)psDroid->orderX,(SDWORD)psDroid->orderY, psDroid->psTarStats)) { // psDroid->action = DACTION_MOVETOBUILD; moveDroidToNoFormation(psDroid, psDroid->actionX,psDroid->actionY); } else if (!DROID_STOPPED(psDroid) && psDroid->sMove.Status != MOVETURNTOTARGET && psDroid->sMove.Status != MOVESHUFFLE && actionReachedBuildPos(psDroid, (SDWORD)psDroid->orderX,(SDWORD)psDroid->orderY, psDroid->psTarStats)) { moveStopDroid(psDroid); } if (droidUpdateBuild(psDroid)) { /* if ( (psDroid->psTarget) && (psDroid->sMove.Status != MOVESHUFFLE) ) { moveTurnDroid(psDroid,psDroid->psTarget->x,psDroid->psTarget->y); }*/ actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), NULL,FALSE); } else { DBP2(("DACTION_BUILD: done\n")); psDroid->action = DACTION_NONE; } break; case DACTION_MOVETODEMOLISH: case DACTION_MOVETOREPAIR: case DACTION_MOVETOCLEAR: case DACTION_MOVETORESTORE: { // The droid cannot be in a formation if (psDroid->sMove.psFormation != NULL) { formationLeave(psDroid->sMove.psFormation, (BASE_OBJECT *)psDroid); psDroid->sMove.psFormation = NULL; } // 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)) { actionHomeBasePos(psDroid->player, &pbx,&pby); 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: // set up for the specific action switch (psDroid->action) { case DACTION_DEMOLISH: moveAction = DACTION_MOVETODEMOLISH; actionUpdateFunc = droidUpdateDemolishing; break; case DACTION_REPAIR: moveAction = DACTION_MOVETOREPAIR; actionUpdateFunc = droidUpdateRepair; break; case DACTION_CLEARWRECK: moveAction = DACTION_MOVETOCLEAR; actionUpdateFunc = droidUpdateClearing; break; case DACTION_RESTORE: moveAction = 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)) { moveDroidToNoFormation(psDroid, psDroid->actionX,psDroid->actionY); } else if (!DROID_STOPPED(psDroid) && psDroid->sMove.Status != MOVETURNTOTARGET && psDroid->sMove.Status != MOVESHUFFLE && actionReachedBuildPos(psDroid, (SDWORD)psDroid->actionX,(SDWORD)psDroid->actionY, psDroid->psTarStats)) { moveStopDroid(psDroid); } else if ( actionUpdateFunc(psDroid) ) { actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), NULL,FALSE); } else if (psDroid->action == DACTION_CLEARWRECK) { //see if there is any other wreckage in the area psDroid->action = DACTION_NONE; psNextWreck = checkForWreckage(psDroid); if (psNextWreck) { orderDroidObj(psDroid, DORDER_CLEARWRECK, (BASE_OBJECT *)psNextWreck); } } 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: // don't want to be in a formation for this move if (psDroid->sMove.psFormation != NULL) { formationLeave(psDroid->sMove.psFormation, (BASE_OBJECT *)psDroid); psDroid->sMove.psFormation = NULL; } /* moving from front to rear of repair facility or rearm pad */ /* xdiff = (SDWORD)psDroid->x - ((SDWORD)psDroid->psActionTarget->x + TILE_UNITS); ydiff = (SDWORD)psDroid->y - (SDWORD)psDroid->psActionTarget->y; if (xdiff*xdiff + ydiff*ydiff < (TILE_UNITS/2)*(TILE_UNITS/2))*/ if (actionReachedBuildPos(psDroid, psDroid->psActionTarget->x,psDroid->psActionTarget->y, (BASE_STATS *)((STRUCTURE *)psDroid->psActionTarget)->pStructureType)) { moveStopDroid(psDroid); psDroid->action = DACTION_WAITDURINGREPAIR; } else if (DROID_STOPPED(psDroid)) { moveDroidToNoFormation(psDroid, psDroid->psActionTarget->x, psDroid->psActionTarget->y); } break; case DACTION_BUILD_FOUNDATION: //building a structure's foundation - flattening the ground for now if (droidUpdateFoundation(psDroid)) { //the droid moves around the site in UpdateFoundation now //randomly move the droid around the construction site /*if ((rand() % 50) < 1) { DBP2(("DACTION_BUILD_FOUNDATION: start wander\n")); psStructStats = (STRUCTURE_STATS *)psDroid->psTarStats; if (getDroidDestination(psStructStats, psDroid->orderX, psDroid->orderY, &droidX, &droidY)) { psDroid->action = DACTION_FOUNDATION_WANDER; psDroid->actionX = droidX; psDroid->actionY = droidY; moveDroidTo(psDroid, droidX,droidY); } }*/ } else { psTile = mapTile(psDroid->orderX>>TILE_SHIFT, psDroid->orderY>>TILE_SHIFT); psStructStats = (STRUCTURE_STATS*)psDroid->psTarStats; tlx = (SDWORD)psDroid->orderX - (SDWORD)(psStructStats->baseWidth * TILE_UNITS)/2; tly = (SDWORD)psDroid->orderY - (SDWORD)(psStructStats->baseBreadth * TILE_UNITS)/2; if ((psDroid->psTarget == NULL) && (TILE_HAS_STRUCTURE(psTile) || TILE_HAS_FEATURE(psTile))) { if (TILE_HAS_STRUCTURE(psTile)) { // structure on the build location - see if it is the same type psStruct = getTileStructure(psDroid->orderX >> TILE_SHIFT, psDroid->orderY >> TILE_SHIFT); if (psStruct->pStructureType == (STRUCTURE_STATS *)psDroid->psTarStats) { // same type - do a help build psDroid->psTarget = (BASE_OBJECT *)psStruct; } else { psDroid->action = DACTION_NONE; } } else if (!validLocation((BASE_STATS*)psDroid->psTarStats, tlx >> TILE_SHIFT, tly >> TILE_SHIFT, psDroid->player, FALSE)) { psDroid->action = DACTION_NONE; } } //ready to start building the structure if ( psDroid->action != DACTION_NONE && droidStartBuild(psDroid)) { DBP2(("DACTION_BUILD_FOUNDATION: start build\n")); psDroid->action = DACTION_BUILD; //add one to current quantity for this player - done in droidStartBuild() //asStructLimits[psDroid->player][(STRUCTURE_STATS *)psDroid-> //psTarStats - asStructureStats].currentQuantity++; } else { DBP2(("DACTION_BUILD_FOUNDATION: giving up\n")); psDroid->action = DACTION_NONE; } } break; case DACTION_OBSERVE: // align the turret actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), NULL,FALSE); if (cbSensorDroid(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->visible[psDroid->player] = UBYTE_MAX; } else { // make sure the target is within sensor range xdiff = (SDWORD)psDroid->x - (SDWORD)psDroid->psActionTarget->x; ydiff = (SDWORD)psDroid->y - (SDWORD)psDroid->psActionTarget->y; //if change this back - change in MOVETOOBSERVE as well //rangeSq = 2 * (SDWORD)psDroid->sensorRange / 3; rangeSq = (SDWORD)psDroid->sensorRange; rangeSq = rangeSq * rangeSq; if (!visibleObject((BASE_OBJECT *)psDroid, psDroid->psActionTarget) || xdiff*xdiff + ydiff*ydiff >= rangeSq) { /* if (secondaryGetState(psDroid, DSO_HALTTYPE, &state) && (state == DSS_HALT_HOLD)) { psDroid->action = DACTION_NONE; // holding, don't move. } else*/ { psDroid->action = DACTION_MOVETOOBSERVE; moveDroidTo(psDroid, psDroid->psActionTarget->x,psDroid->psActionTarget->y); } } } break; case DACTION_MOVETOOBSERVE: // align the turret actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), NULL,FALSE); if (visibleObject((BASE_OBJECT *)psDroid, psDroid->psActionTarget)) { // make sure the target is within sensor range xdiff = (SDWORD)psDroid->x - (SDWORD)psDroid->psActionTarget->x; ydiff = (SDWORD)psDroid->y - (SDWORD)psDroid->psActionTarget->y; //if change this back - change in OBSERVE as well //rangeSq = 2 * (SDWORD)psDroid->sensorRange / 3; rangeSq = (SDWORD)psDroid->sensorRange; 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) { /* if (secondaryGetState(psDroid, DSO_HALTTYPE, &state) && (state == DSS_HALT_HOLD)) { psDroid->action = DACTION_NONE; // on hold, don't go any further. } else*/ { moveDroidTo(psDroid, psDroid->psActionTarget->x,psDroid->psActionTarget->y); } } break; case DACTION_FIRESUPPORT: //can be either a droid or a structure now - AB 7/10/98 ASSERT( (psDroid->psTarget->type == OBJ_DROID OR psDroid->psTarget->type == OBJ_STRUCTURE) && (psDroid->psTarget->player == psDroid->player), "DACTION_FIRESUPPORT: incorrect target type" ); /* if (orderState(((DROID *)psDroid->psTarget), DORDER_OBSERVE)) { // move to attack psDroid->action = DACTION_MOVETOFSUPP_ATTACK; psDroid->psActionTarget = psDroid->psTarget->psTarget; moveDroidTo(psDroid, psDroid->psActionTarget->x, psDroid->psActionTarget->y); } else {*/ //Move droids attached to structures and droids now...AB 13/10/98 //move (indirect weapon)droids attached to a sensor //if (psDroid->psTarget->type == OBJ_DROID) { //don't move VTOL's // also don't move closer to sensor towers if (!vtolDroid(psDroid) && (psDroid->psTarget->type != OBJ_STRUCTURE)) { //move droids to within short range of the sensor now!!!! xdiff = (SDWORD)psDroid->x - (SDWORD)psDroid->psTarget->x; ydiff = (SDWORD)psDroid->y - (SDWORD)psDroid->psTarget->y; // make sure the weapon droid is within 2/3 weapon range of the sensor //rangeSq = 2 * proj_GetLongRange(asWeaponStats + psDroid->asWeaps[0].nStat, 0) / 3; 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->x - (SDWORD)psDroid->sMove.DestinationX; ydiff = (SDWORD)psDroid->psTarget->y - (SDWORD)psDroid->sMove.DestinationY; } if (DROID_STOPPED(psDroid) || (xdiff*xdiff + ydiff*ydiff > rangeSq)) { if (secondaryGetState(psDroid, DSO_HALTTYPE, &state) && ( state == DSS_HALT_HOLD)) { // droid on hold, don't allow moves. psDroid->action = DACTION_NONE; } else { // move in range moveDroidTo(psDroid, psDroid->psTarget->x,psDroid->psTarget->y); } } } } } break; case DACTION_DESTRUCT: if ((psDroid->actionStarted + ACTION_DESTRUCT_TIME) < gameTime) { if ( psDroid->droidType == DROID_PERSON ) { droidBurn(psDroid); } else { destroyDroid(psDroid); } } break; case DACTION_MOVETODROIDREPAIR: // moving to repair a droid xdiff = (SDWORD)psDroid->x - (SDWORD)psDroid->psActionTarget->x; ydiff = (SDWORD)psDroid->y - (SDWORD)psDroid->psActionTarget->y; if ( xdiff*xdiff + ydiff*ydiff < REPAIR_RANGE ) { // Got to destination - start repair //rotate turret to point at droid being repaired /*if (actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), psDroid->turretRotRate, (SWORD)(psDroid->turretRotRate/2), FALSE, FALSE))*/ if (actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), NULL, FALSE)) { 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 /*droidX = psDroid->psTarget->x >> TILE_SHIFT; droidY = psDroid->psTarget->y >> TILE_SHIFT; if (!pickATile(&droidX, &droidY, LOOK_NEXT_TO_DROID)) { //couldn't get next to the droid, so stop trying psDroid->action = DACTION_NONE; } psDroid->actionX = droidX << TILE_SHIFT; psDroid->actionY = droidY << TILE_SHIFT;*/ psDroid->actionX = psDroid->psActionTarget->x; psDroid->actionY = psDroid->psActionTarget->y; moveDroidTo(psDroid, psDroid->actionX, psDroid->actionY); } break; case DACTION_DROIDREPAIR: /*actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), psDroid->turretRotRate, (SWORD)(psDroid->turretRotRate/2), FALSE, FALSE);*/ //if not doing self-repair if (psDroid->psActionTarget != (BASE_OBJECT *)psDroid) { actionTargetTurret((BASE_OBJECT*)psDroid, psDroid->psActionTarget, &(psDroid->turretRotation), &(psDroid->turretPitch), NULL, FALSE); } //check still next to the damaged droid xdiff = (SDWORD)psDroid->x - (SDWORD)psDroid->psActionTarget->x; ydiff = (SDWORD)psDroid->y - (SDWORD)psDroid->psActionTarget->y; if ( xdiff*xdiff + ydiff*ydiff > REPAIR_RANGE ) { /*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! psDroid->actionX = psDroid->psActionTarget->x; psDroid->actionY = psDroid->psActionTarget->y; psDroid->action = DACTION_MOVETODROIDREPAIR; moveDroidTo(psDroid, psDroid->actionX, psDroid->actionY); } 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); } } else { // don't let the target for a repair shuffle if (((DROID *)psDroid->psActionTarget)->sMove.Status == MOVESHUFFLE) { moveStopDroid((DROID *)psDroid->psActionTarget); } } } break; case DACTION_WAITFORREARM: // wait here for the rearm pad to ask the vtol to move if (psDroid->psActionTarget == 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 == NULL) { // base destroyed - find another moveToRearm(psDroid); break; } if (visibleObject((BASE_OBJECT *)psDroid, psDroid->psActionTarget)) { // got close to the rearm pad - now find a clear one DBP4(("Unit %d: seen rearm pad\n", psDroid->id)); psStruct = findNearestReArmPad(psDroid, (STRUCTURE *)psDroid->psActionTarget, TRUE); if (psStruct != NULL) { // found a clear landing pad - go for it DBP4(("Found clear rearm pad\n")); psDroid->psActionTarget = (BASE_OBJECT *)psStruct; } psDroid->action = DACTION_WAITFORREARM; } if (DROID_STOPPED(psDroid) || (psDroid->action == DACTION_WAITFORREARM)) { droidX = psDroid->psActionTarget->x; droidY = psDroid->psActionTarget->y; if (!actionVTOLLandingPos(psDroid, &droidX, &droidY)) { // totally bunged up - give up debug( LOG_NEVER, "DACTION_MOVETOREARM: couldn't find a clear tile near rearm pad - RTB\n" ); orderDroid(psDroid, DORDER_RTB); break; } moveDroidToDirect(psDroid, droidX,droidY); } break; default: ASSERT( FALSE, "actionUpdateUnit: 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) { if ((psDroid->turretRotation!=0) || (psDroid->turretPitch !=0)) { actionAlignTurret((BASE_OBJECT *)psDroid); } } } /* Overall action function that is called by the specific action functions */ static void actionDroidBase(DROID *psDroid, DROID_ACTION_DATA *psAction) { SECONDARY_STATE state; SDWORD pbx,pby; WEAPON_STATS *psWeapStats; UDWORD droidX,droidY; BASE_OBJECT *psTarget; ASSERT( PTRVALID(psDroid, sizeof(DROID)), "actionUnitBase: Invalid Unit pointer" ); ASSERT( psDroid->type == OBJ_DROID, "actionUnitBase: Unit pointer does not reference a unit" ); switch (psAction->action) { case DACTION_NONE: // Clear up what ever the droid was doing before if necessary // if(!driveModeActive() || !psDroid->selected) { if (!DROID_STOPPED(psDroid)) { moveStopDroid(psDroid); } psDroid->action = DACTION_NONE; psDroid->actionX = 0; psDroid->actionY = 0; psDroid->actionStarted = 0; psDroid->actionPoints = 0; //psDroid->actionHeight = 0; psDroid->powerAccrued = 0; psDroid->psActionTarget = NULL; // } //else { // if(psDroid->player == 0) // { // DBPRINTF(("DACTION_NONE %p\n",psDroid); // } // } 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)) { return; } //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 AND ( // (((STRUCTURE *)psAction->psObj)->pStructureType->resistance == 0))) /* OR (((STRUCTURE *)psAction->psObj)->resistance < (SDWORD)structureResistance(((STRUCTURE *)psAction->psObj)-> pStructureType, psAction->psObj->player))))*/ //psObj)->resistance < (SDWORD)((STRUCTURE *)psAction-> //psObj)->pStructureType->resistance))) !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 AND psAction->psObj->type == OBJ_DROID AND ((DROID *)psAction->psObj)->droidType == DROID_TRANSPORTER) { psDroid->action = DACTION_NONE; break; } } // psDroid->actionX = psAction->psObj->x; // psDroid->actionY = psAction->psObj->y; // 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->x; psDroid->actionY = psDroid->y; psDroid->psActionTarget = psAction->psObj; if ( ( (psDroid->order == DORDER_ATTACKTARGET || psDroid->order == DORDER_FIRESUPPORT) && secondaryGetState(psDroid, DSO_HALTTYPE, &state) && (state == DSS_HALT_HOLD)) || ( !vtolDroid(psDroid) && orderStateObj(psDroid, DORDER_FIRESUPPORT, &psTarget) && (psTarget->type == OBJ_STRUCTURE) ) ) { psDroid->action = DACTION_ATTACK; // holding, try attack straightaway psDroid->psActionTarget = psAction->psObj; } else if (actionInsideMinRange(psDroid, psAction->psObj)) { psWeapStats = &asWeaponStats[psDroid->asWeaps[0].nStat]; if ( !proj_Direct( psWeapStats ) ) { if (psWeapStats->rotate) { psDroid->action = DACTION_ATTACK; } else { psDroid->action = DACTION_ROTATETOATTACK; moveTurnDroid( psDroid, psDroid->psActionTarget->x, psDroid->psActionTarget->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->x, psAction->psObj->y); turnOffMultiMsg(FALSE); } break; case DACTION_MOVETOREARM: psDroid->action = DACTION_MOVETOREARM; psDroid->actionX = psAction->psObj->x; psDroid->actionY = psAction->psObj->y; psDroid->actionStarted = gameTime; psDroid->psActionTarget = psAction->psObj; droidX = psDroid->psActionTarget->x; droidY = psDroid->psActionTarget->y; if (!actionVTOLLandingPos(psDroid, &droidX, &droidY)) { // totally bunged up - give up orderDroid(psDroid, DORDER_RTB); break; } moveDroidToDirect(psDroid, droidX, droidY); break; case DACTION_CLEARREARMPAD: DBP4(("Unit %d clearing rearm pad\n", psDroid->id)); psDroid->action = DACTION_CLEARREARMPAD; psDroid->psActionTarget = psAction->psObj; droidX = psDroid->psActionTarget->x; droidY = psDroid->psActionTarget->y; if (!actionVTOLLandingPos(psDroid, &droidX, &droidY)) { // totally bunged up - give up orderDroid(psDroid, DORDER_RTB); 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; psDroid->psActionTarget = psAction->psObj; moveDroidTo(psDroid, psAction->x, psAction->y); break; case DACTION_BUILD: ASSERT( psDroid->order == DORDER_BUILD || psDroid->order == DORDER_HELPBUILD || psDroid->order == DORDER_LINEBUILD, "actionUnitBase: cannot start build action without a build order" ); psDroid->action = DACTION_MOVETOBUILD; psDroid->actionX = psAction->x; psDroid->actionY = psAction->y; if (actionDroidOnBuildPos(psDroid, (SDWORD)psDroid->orderX,(SDWORD)psDroid->orderY, psDroid->psTarStats)) { actionHomeBasePos(psDroid->player, &pbx,&pby); moveDroidToNoFormation(psDroid, (UDWORD)pbx,(UDWORD)pby); } else { moveDroidToNoFormation(psDroid, psDroid->actionX,psDroid->actionY); } break; case DACTION_DEMOLISH: ASSERT( psDroid->order == DORDER_DEMOLISH, "actionUnitBase: 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), "actionUnitBase: invalid target for demolish order" ); psDroid->psTarStats = (BASE_STATS *)((STRUCTURE *)psDroid->psTarget)->pStructureType; psDroid->psActionTarget = psAction->psObj; 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 psDroid->psActionTarget = psAction->psObj; ASSERT( (psDroid->psActionTarget != NULL) && (psDroid->psActionTarget->type == OBJ_STRUCTURE), "actionUnitBase: invalid target for demolish order" ); psDroid->psTarStats = (BASE_STATS *)((STRUCTURE *)psDroid->psActionTarget)->pStructureType; moveDroidTo(psDroid, psAction->x, psAction->y); break; case DACTION_OBSERVE: psDroid->psActionTarget = psAction->psObj; psDroid->actionX = psDroid->x; psDroid->actionY = psDroid->y; if (//(secondaryGetState(psDroid, DSO_HALTTYPE, &state) && (state == DSS_HALT_HOLD)) || cbSensorDroid(psDroid)) { psDroid->action = DACTION_OBSERVE; } else { psDroid->action = DACTION_MOVETOOBSERVE; moveDroidTo(psDroid, psDroid->psActionTarget->x, psDroid->psActionTarget->y); } break; case DACTION_FIRESUPPORT: psDroid->action = DACTION_FIRESUPPORT; if(!vtolDroid(psDroid) && !(secondaryGetState(psDroid, DSO_HALTTYPE, &state) && (state == DSS_HALT_HOLD)) && // check hold (psDroid->psTarget->type != OBJ_STRUCTURE)) { moveDroidTo(psDroid, psDroid->psTarget->x, psDroid->psTarget->y); // movetotarget. } break; case DACTION_SULK: // debug( LOG_NEVER, "Go with sulk ... %p\n", psDroid ); psDroid->action = DACTION_SULK; //hmmm, hope this doesn't cause any problems! //psDroid->actionStarted = gameTime; // what is action started used for ? Certainly not used here! //psDroid->actionHeight = (UWORD)(gameTime+MIN_SULK_TIME+(rand()%( // MAX_SULK_TIME-MIN_SULK_TIME))); // actionHeight is used here for the ending time for this action psDroid->actionStarted = gameTime+MIN_SULK_TIME+(rand()%( 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; psDroid->psActionTarget = psAction->psObj; moveDroidToNoFormation( psDroid, psAction->x, psAction->y ); break; case DACTION_WAITDURINGREPAIR: psDroid->action = DACTION_WAITDURINGREPAIR; break; case DACTION_MOVETOREARMPOINT: DBP4(("Unit %d moving to rearm point\n", psDroid->id)); psDroid->action = psAction->action; psDroid->actionX = psAction->x; psDroid->actionY = psAction->y; psDroid->actionStarted = gameTime; psDroid->psActionTarget = psAction->psObj; 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; psDroid->psActionTarget = psAction->psObj; //initialise the action points psDroid->actionPoints = 0; moveDroidTo(psDroid, psAction->x, psAction->y); break; case DACTION_RESTORE: ASSERT( psDroid->order == DORDER_RESTORE, "actionUnitBase: 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), "actionUnitBase: invalid target for restore order" ); psDroid->psTarStats = (BASE_STATS *)((STRUCTURE *)psDroid->psTarget)->pStructureType; psDroid->psActionTarget = psAction->psObj; moveDroidTo(psDroid, psAction->x, psAction->y); break; case DACTION_CLEARWRECK: ASSERT( psDroid->order == DORDER_CLEARWRECK, "actionUnitBase: 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), "actionUnitBase: invalid target for demolish order" ); psDroid->psTarStats = (BASE_STATS *)((FEATURE *)psDroid->psTarget)->psStats; psDroid->psActionTarget = psDroid->psTarget; moveDroidTo(psDroid, psAction->x, psAction->y); break; default: ASSERT( FALSE, "actionUnitBase: unknown action" ); break; } } /* 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->x; sAction.y = psObj->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; // IF YOU CHANGE THE ORDER/ACTION RESULTS TELL ALEXL!!!! && recvvtolrearm if (!vtolDroid(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) { psDroid->psBaseStruct = 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); chosen=1; } else { actionDroidObj(psDroid, DACTION_MOVETOREARM, (BASE_OBJECT *)psStruct); chosen=2; } } else { //return to base un-armed orderDroid( psDroid, DORDER_RTB ); chosen =3; } if(bMultiPlayer) { sendVtolRearm(psDroid,psStruct,chosen); } } // whether a tile is suitable for a vtol to land on 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) || (TILE_OCCUPIED(psTile)) || (TERRAIN_TYPE(psTile) == TER_CLIFFFACE) || (TERRAIN_TYPE(psTile) == TER_WATER)) { return FALSE; } return TRUE; } // choose a landing position for a VTOL when it goes to rearm BOOL actionVTOLLandingPos(DROID *psDroid, UDWORD *px, UDWORD *py) { SDWORD i,j; SDWORD startX,endX,startY,endY, tx,ty; UDWORD passes; DROID *psCurr; BOOL result; // ASSERT( (psDroid->psActionTarget != NULL), // "actionVTOLLandingPos: no rearm pad set for the VTOL" ); /* Initial box dimensions and set iteration count to zero */ // startX = endX = (SDWORD)psDroid->psActionTarget->x >> TILE_SHIFT; // startY = endY = (SDWORD)psDroid->psActionTarget->y >> TILE_SHIFT; startX = endX = (SDWORD)*px >> TILE_SHIFT; startY = endY = (SDWORD)*py >> TILE_SHIFT; passes = 0; // set blocking flags for all the other droids for(psCurr=apsDroidLists[psDroid->player]; psCurr; psCurr = psCurr->psNext) { if (DROID_STOPPED(psCurr)) { tx = psCurr->x >> TILE_SHIFT; ty = psCurr->y >> TILE_SHIFT; } else { tx = psCurr->sMove.DestinationX >> TILE_SHIFT; ty = psCurr->sMove.DestinationY >> TILE_SHIFT; } if (psCurr != psDroid) { if (tileOnMap(tx, ty)) { mapTile(tx,ty)->tileInfoBits |= BITS_FPATHBLOCK; } } } /* Keep going until we get a tile or we exceed distance */ result = FALSE; while(passes<20) { /* Process whole box */ for(i = startX; i <= endX; i++) { for(j = startY; j<= endY; j++) { /* Test only perimeter as internal tested previous iteration */ if(i==startX OR i==endX OR j==startY OR j==endY) { /* Good enough? */ if(vtolLandingTile(i,j)) { /* Set exit conditions and get out NOW */ DBP4(("Unit %d landing pos (%d,%d)\n",psDroid->id, i,j)); *px = (i << TILE_SHIFT) + TILE_UNITS/2; *py = (j << TILE_SHIFT) + TILE_UNITS/2; result = TRUE; goto exit; } } } } /* Expand the box out in all directions - off map handled by tileAcceptable */ startX--; startY--; endX++; endY++; passes++; } exit: // clear blocking flags for all the other droids for(psCurr=apsDroidLists[psDroid->player]; psCurr; psCurr = psCurr->psNext) { if (DROID_STOPPED(psCurr)) { tx = psCurr->x >> TILE_SHIFT; ty = psCurr->y >> TILE_SHIFT; } else { tx = psCurr->sMove.DestinationX >> TILE_SHIFT; ty = psCurr->sMove.DestinationY >> TILE_SHIFT; } if (tileOnMap(tx, ty)) { mapTile(tx,ty)->tileInfoBits &= ~BITS_FPATHBLOCK; } } return result; }