/*checks through the target players list of structures and droids to see if any support a counter battery sensor*/ void counterBatteryFire(BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget) { BASE_OBJECT *psViewer; /*if a null target is passed in ignore - this will be the case when a 'miss' projectile is sent - we may have to cater for these at some point*/ // also ignore cases where you attack your own player // Also ignore cases where there are already 1000 missiles heading towards the attacker. if (psTarget == NULL || (psAttacker != NULL && psAttacker->player == psTarget->player) || aiObjectIsProbablyDoomed(psAttacker)) { return; } CHECK_OBJECT(psTarget); for (psViewer = apsSensorList[0]; psViewer; psViewer = psViewer->psNextFunc) { if (aiCheckAlliances(psTarget->player, psViewer->player)) { if ((psViewer->type == OBJ_STRUCTURE && !structCBSensor((STRUCTURE *)psViewer)) || (psViewer->type == OBJ_DROID && !cbSensorDroid((DROID *)psViewer))) { continue; } const int sensorRange = objSensorRange(psViewer); // Check sensor distance from target const int xDiff = psViewer->pos.x - psTarget->pos.x; const int yDiff = psViewer->pos.y - psTarget->pos.y; if (xDiff * xDiff + yDiff * yDiff < sensorRange * sensorRange) { // Inform viewer of target if (psViewer->type == OBJ_DROID) { orderDroidObj((DROID *)psViewer, DORDER_OBSERVE, psAttacker, ModeImmediate); } else if (psViewer->type == OBJ_STRUCTURE) { ((STRUCTURE *)psViewer)->psTarget[0] = psAttacker; } } } } }
/*checks through the target players list of structures and droids to see if any support a counter battery sensor*/ void counterBatteryFire(BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget) { BASE_OBJECT *psViewer; /*if a null target is passed in ignore - this will be the case when a 'miss' projectile is sent - we may have to cater for these at some point*/ // also ignore cases where you attack your own player // Also ignore cases where there are already 1000 missiles heading towards the attacker. if ((psTarget == NULL) || ((psAttacker != NULL) && (psAttacker->player == psTarget->player)) || aiObjectIsProbablyDoomed(psAttacker)) { return; } CHECK_OBJECT(psTarget); gridStartIterate(psTarget->pos.x, psTarget->pos.y, PREVIOUS_DEFAULT_GRID_SEARCH_RADIUS); for (psViewer = gridIterate(); psViewer != NULL; psViewer = gridIterate()) { STRUCTURE *psStruct; DROID *psDroid; SDWORD sensorRange = 0; if (psViewer->player != psTarget->player) { //ignore non target players' objects continue; } if (psViewer->type == OBJ_STRUCTURE) { psStruct = (STRUCTURE *)psViewer; //check if have a sensor of correct type if (structCBSensor(psStruct) || structVTOLCBSensor(psStruct)) { sensorRange = psStruct->pStructureType->pSensor->range; } } else if (psViewer->type == OBJ_DROID) { psDroid = (DROID *)psViewer; //must be a CB sensor if (cbSensorDroid(psDroid)) { sensorRange = asSensorStats[psDroid->asBits[COMP_SENSOR]. nStat].range; } } //check sensor distance from target if (sensorRange) { SDWORD xDiff = (SDWORD)psViewer->pos.x - (SDWORD)psTarget->pos.x; SDWORD yDiff = (SDWORD)psViewer->pos.y - (SDWORD)psTarget->pos.y; if (xDiff*xDiff + yDiff*yDiff < sensorRange * sensorRange) { //inform viewer of target if (psViewer->type == OBJ_DROID) { orderDroidObj((DROID *)psViewer, DORDER_OBSERVE, psAttacker, ModeImmediate); } else if (psViewer->type == OBJ_STRUCTURE) { ((STRUCTURE *)psViewer)->psTarget[0] = psAttacker; } } } } }
/* See if there is a target in range */ BOOL aiChooseTarget(BASE_OBJECT *psObj, BASE_OBJECT **ppsTarget, int weapon_slot, BOOL bUpdateTarget, UWORD *targetOrigin) { BASE_OBJECT *psTarget = NULL; DROID *psCommander; SDWORD curTargetWeight=-1; UWORD tmpOrigin = ORIGIN_UNKNOWN; if (targetOrigin) { *targetOrigin = ORIGIN_UNKNOWN; } /* Get the sensor range */ switch (psObj->type) { case OBJ_DROID: if (((DROID *)psObj)->asWeaps[weapon_slot].nStat == 0) { return false; } if (((DROID *)psObj)->asWeaps[0].nStat == 0 && ((DROID *)psObj)->droidType != DROID_SENSOR) { // Can't attack without a weapon return false; } break; case OBJ_STRUCTURE: if (((STRUCTURE *)psObj)->numWeaps == 0 || ((STRUCTURE *)psObj)->asWeaps[0].nStat == 0) { // Can't attack without a weapon return false; } break; default: break; } /* See if there is a something in range */ if (psObj->type == OBJ_DROID) { BASE_OBJECT *psCurrTarget = ((DROID *)psObj)->psActionTarget[0]; /* find a new target */ int newTargetWeight = aiBestNearestTarget((DROID *)psObj, &psTarget, weapon_slot, NULL); /* Calculate weight of the current target if updating; but take care not to target * ourselves... */ if (bUpdateTarget && psCurrTarget != psObj) { curTargetWeight = targetAttackWeight(psCurrTarget, psObj, weapon_slot); } if (newTargetWeight >= 0 // found a new target && (!bUpdateTarget // choosing a new target, don't care if current one is better || curTargetWeight <= 0 // attacker had no valid target, use new one || newTargetWeight > curTargetWeight + OLD_TARGET_THRESHOLD) // updating and new target is better && validTarget(psObj, psTarget, weapon_slot) && (aiDroidHasRange((DROID *)psObj, psTarget, weapon_slot) || (secondaryGetState((DROID *)psObj, DSO_HALTTYPE) != DSS_HALT_HOLD))) { ASSERT(!isDead(psTarget), "aiChooseTarget: Droid found a dead target!"); *ppsTarget = psTarget; return true; } } else if (psObj->type == OBJ_STRUCTURE) { WEAPON_STATS *psWStats = NULL; int tarDist, longRange = 0; BOOL bCommanderBlock = false; ASSERT(((STRUCTURE *)psObj)->asWeaps[weapon_slot].nStat > 0, "no weapons on structure"); psWStats = ((STRUCTURE *)psObj)->asWeaps[weapon_slot].nStat + asWeaponStats; longRange = proj_GetLongRange(psWStats); // see if there is a target from the command droids psTarget = NULL; psCommander = cmdDroidGetDesignator(psObj->player); if (!proj_Direct(psWStats) && (psCommander != NULL) && aiStructHasRange((STRUCTURE *)psObj, (BASE_OBJECT *)psCommander, weapon_slot)) { // there is a commander that can fire designate for this structure // set bCommanderBlock so that the structure does not fire until the commander // has a target - (slow firing weapons will not be ready to fire otherwise). bCommanderBlock = true; // I do believe this will never happen, check for yourself :-) debug(LOG_NEVER,"Commander %d is good enough for fire designation", psCommander->id); if (psCommander->action == DACTION_ATTACK && psCommander->psActionTarget[0] != NULL && !aiObjectIsProbablyDoomed(psCommander->psActionTarget[0])) { // the commander has a target to fire on if (aiStructHasRange((STRUCTURE *)psObj, psCommander->psActionTarget[0], weapon_slot)) { // target in range - fire on it tmpOrigin = ORIGIN_COMMANDER; psTarget = psCommander->psActionTarget[0]; } else { // target out of range - release the commander block bCommanderBlock = false; } } } // indirect fire structures use sensor towers first tarDist = longRange * longRange; if (psTarget == NULL && !bCommanderBlock && !proj_Direct(psWStats)) { psTarget = aiSearchSensorTargets(psObj, weapon_slot, psWStats, &tmpOrigin); } if (psTarget == NULL && !bCommanderBlock) { BASE_OBJECT *psCurr; gridStartIterate(psObj->pos.x, psObj->pos.y, PREVIOUS_DEFAULT_GRID_SEARCH_RADIUS); psCurr = gridIterate(); while (psCurr != NULL) { /* Check that it is a valid target */ if (psCurr->type != OBJ_FEATURE && !aiObjectIsProbablyDoomed(psCurr) && aiStructHasRange((STRUCTURE *)psObj, psCurr, weapon_slot) && !aiCheckAlliances(psCurr->player, psObj->player) && validTarget(psObj, psCurr, weapon_slot) && psCurr->visible[psObj->player]) { // See if in sensor range and visible int distSq = objPosDiffSq(psCurr->pos, psObj->pos); if (distSq < tarDist || (psTarget && psTarget->type == OBJ_STRUCTURE && ((STRUCTURE *)psTarget)->status != SS_BUILT) || (psTarget && aiObjIsWall(psTarget) && !aiObjIsWall(psCurr))) { tmpOrigin = ORIGIN_VISUAL; psTarget = psCurr; tarDist = distSq; } } psCurr = gridIterate(); } } if (psTarget) { ASSERT(!psTarget->died, "aiChooseTarget: Structure found a dead target!"); if (targetOrigin) { *targetOrigin = tmpOrigin; } *ppsTarget = psTarget; return true; } } return false; }
/* See if there is a target in range for Sensor objects*/ BOOL aiChooseSensorTarget(BASE_OBJECT *psObj, BASE_OBJECT **ppsTarget) { int sensorRange = objSensorRange(psObj); unsigned int radSquared = sensorRange * sensorRange; bool radarDetector = objRadarDetector(psObj); if (!objActiveRadar(psObj) && !radarDetector) { ASSERT(false, "Only to be used for sensor turrets!"); return false; } /* See if there is something in range */ if (radarDetector) { BASE_OBJECT *psCurr, *psTemp = NULL; int tarDist = SDWORD_MAX; gridStartIterate(psObj->pos.x, psObj->pos.y, PREVIOUS_DEFAULT_GRID_SEARCH_RADIUS); psCurr = gridIterate(); while (psCurr != NULL) { if ((psCurr->type == OBJ_STRUCTURE || psCurr->type == OBJ_DROID) && !aiObjectIsProbablyDoomed(psCurr)) { if (!aiCheckAlliances(psCurr->player,psObj->player) && objActiveRadar(psCurr)) { // See if in twice *their* sensor range const int xdiff = psCurr->pos.x - psObj->pos.x; const int ydiff = psCurr->pos.y - psObj->pos.y; const unsigned int distSq = xdiff * xdiff + ydiff * ydiff; const int targetSensor = objSensorRange(psCurr) * 2; const unsigned int sensorSquared = targetSensor * targetSensor; if (distSq < sensorSquared && distSq < tarDist) { psTemp = psCurr; tarDist = distSq; } } } psCurr = gridIterate(); } if (psTemp) { objTrace(psTemp->id, "Targetted by radar detector %d", (int)psObj->id); objTrace(psObj->id, "Targetting radar %d", (int)psTemp->id); ASSERT(!psTemp->died, "aiChooseSensorTarget gave us a dead target"); *ppsTarget = psTemp; return true; } } else if (psObj->type == OBJ_DROID) { BASE_OBJECT *psTarget = NULL; if (aiBestNearestTarget((DROID *)psObj, &psTarget, 0, NULL) >= 0) { /* See if in sensor range */ const int xdiff = psTarget->pos.x - psObj->pos.x; const int ydiff = psTarget->pos.y - psObj->pos.y; const unsigned int distSq = xdiff * xdiff + ydiff * ydiff; // I do believe this will never happen, check for yourself :-) debug(LOG_NEVER, "Sensor droid(%d) found possible target(%d)!!!", psObj->id, psTarget->id); if (distSq < radSquared) { *ppsTarget = psTarget; return true; } } } else // structure { BASE_OBJECT *psCurr, *psTemp = NULL; int tarDist = SDWORD_MAX; gridStartIterate(psObj->pos.x, psObj->pos.y, PREVIOUS_DEFAULT_GRID_SEARCH_RADIUS); psCurr = gridIterate(); while (psCurr != NULL) { // Don't target features or doomed/dead objects if (psCurr->type != OBJ_FEATURE && !aiObjectIsProbablyDoomed(psCurr)) { if (!aiCheckAlliances(psCurr->player,psObj->player) && !aiObjIsWall(psCurr)) { // See if in sensor range and visible const int xdiff = psCurr->pos.x - psObj->pos.x; const int ydiff = psCurr->pos.y - psObj->pos.y; const unsigned int distSq = xdiff * xdiff + ydiff * ydiff; if (distSq < radSquared && psCurr->visible[psObj->player] && distSq < tarDist) { psTemp = psCurr; tarDist = distSq; } } } psCurr = gridIterate(); } if (psTemp) { ASSERT(!psTemp->died, "aiChooseSensorTarget gave us a dead target"); *ppsTarget = psTemp; return true; } } return false; }
// Find the best nearest target for a droid // Returns integer representing target priority, -1 if failed SDWORD aiBestNearestTarget(DROID *psDroid, BASE_OBJECT **ppsObj, int weapon_slot, UWORD *targetOrigin) { SDWORD bestMod = 0,newMod, failure = -1; BASE_OBJECT *psTarget = NULL, *friendlyObj, *bestTarget = NULL, *iter, *targetInQuestion, *tempTarget; BOOL electronic = false; STRUCTURE *targetStructure; WEAPON_EFFECT weaponEffect; UWORD tmpOrigin = ORIGIN_UNKNOWN; // reset origin if (targetOrigin) { *targetOrigin = ORIGIN_UNKNOWN; } //don't bother looking if empty vtol droid if (vtolEmpty(psDroid)) { return failure; } /* Return if have no weapons */ // The ai orders a non-combat droid to patrol = crash without it... if ((psDroid->asWeaps[0].nStat == 0 || psDroid->numWeaps == 0) && psDroid->droidType != DROID_SENSOR) { return failure; } // Check if we have a CB target to begin with if (!proj_Direct(asWeaponStats + psDroid->asWeaps[weapon_slot].nStat)) { WEAPON_STATS *psWStats = psWStats = psDroid->asWeaps[weapon_slot].nStat + asWeaponStats; bestTarget = aiSearchSensorTargets((BASE_OBJECT *)psDroid, weapon_slot, psWStats, &tmpOrigin); bestMod = targetAttackWeight(bestTarget, (BASE_OBJECT *)psDroid, weapon_slot); } weaponEffect = ((WEAPON_STATS *)(asWeaponStats + psDroid->asWeaps[weapon_slot].nStat))->weaponEffect; electronic = electronicDroid(psDroid); // Range was previously 9*TILE_UNITS. Increasing this doesn't seem to help much, though. Not sure why. gridStartIterate(psDroid->pos.x, psDroid->pos.y, psDroid->sensorRange + 6*TILE_UNITS); for (iter = gridIterate(); iter != NULL; iter = gridIterate()) { friendlyObj = NULL; targetInQuestion = iter; /* This is a friendly unit, check if we can reuse its target */ if(aiCheckAlliances(targetInQuestion->player,psDroid->player)) { friendlyObj = targetInQuestion; targetInQuestion = NULL; /* Can we see what it is doing? */ if(friendlyObj->visible[psDroid->player]) { if(friendlyObj->type == OBJ_DROID) { DROID *friendlyDroid = (DROID *)friendlyObj; /* See if friendly droid has a target */ tempTarget = friendlyDroid->psActionTarget[0]; if(tempTarget && !aiObjectIsProbablyDoomed(tempTarget)) { //make sure a weapon droid is targeting it if(friendlyDroid->numWeaps > 0) { // make sure this target wasn't assigned explicitly to this droid if(friendlyDroid->order != DORDER_ATTACK) { // make sure target is near enough if (aiDroidHasRange(psDroid, tempTarget, weapon_slot)) { targetInQuestion = tempTarget; //consider this target } } } } } else if(friendlyObj->type == OBJ_STRUCTURE) { tempTarget = ((STRUCTURE*)friendlyObj)->psTarget[0]; if (tempTarget && !aiObjectIsProbablyDoomed(tempTarget) && aiDroidHasRange(psDroid, tempTarget, weapon_slot)) { targetInQuestion = tempTarget; } } } } if (targetInQuestion != NULL && targetInQuestion != (BASE_OBJECT *)psDroid // in case friendly unit had me as target && (targetInQuestion->type == OBJ_DROID || targetInQuestion->type == OBJ_STRUCTURE || targetInQuestion->type == OBJ_FEATURE) && targetInQuestion->visible[psDroid->player] && !aiCheckAlliances(targetInQuestion->player,psDroid->player) && validTarget((BASE_OBJECT *)psDroid, targetInQuestion, weapon_slot) && aiDroidHasRange(psDroid, targetInQuestion, weapon_slot)) { if (targetInQuestion->type == OBJ_DROID) { // in multiPlayer - don't attack Transporters with EW if (bMultiPlayer) { // if not electronic then valid target if (!electronic || (electronic && ((DROID *)targetInQuestion)->droidType != DROID_TRANSPORTER)) { //only a valid target if NOT a transporter psTarget = targetInQuestion; } } else { psTarget = targetInQuestion; } } else if (targetInQuestion->type == OBJ_STRUCTURE) { STRUCTURE *psStruct = (STRUCTURE *)targetInQuestion; if (electronic) { /* don't want to target structures with resistance of zero if using electronic warfare */ if (validStructResistance((STRUCTURE *)targetInQuestion)) { psTarget = targetInQuestion; } } else if (psStruct->asWeaps[weapon_slot].nStat > 0) { // structure with weapons - go for this psTarget = targetInQuestion; } else if ((psStruct->pStructureType->type != REF_WALL && psStruct->pStructureType->type != REF_WALLCORNER) || driveModeActive() || (bMultiPlayer && !isHumanPlayer(psDroid->player))) { psTarget = targetInQuestion; } } else if (targetInQuestion->type == OBJ_FEATURE && gameTime - psDroid->lastFrustratedTime < FRUSTRATED_TIME && ((FEATURE *)targetInQuestion)->psStats->damageable && !(game.scavengers && psDroid->player == 7)) // hack to avoid scavs blowing up their nice feature walls { psTarget = targetInQuestion; } /* Check if our weapon is most effective against this object */ if(psTarget != NULL && psTarget == targetInQuestion) //was assigned? { newMod = targetAttackWeight(psTarget, (BASE_OBJECT *)psDroid, weapon_slot); /* Remember this one if it's our best target so far */ if( newMod >= 0 && (newMod > bestMod || bestTarget == NULL)) { bestMod = newMod; tmpOrigin = ORIGIN_ALLY; bestTarget = psTarget; } } } } if (bestTarget) { ASSERT(!bestTarget->died, "aiBestNearestTarget: AI gave us a target that is already dead."); targetStructure = visGetBlockingWall((BASE_OBJECT *)psDroid, bestTarget); /* See if target is blocked by a wall; only affects direct weapons */ if (proj_Direct(asWeaponStats + psDroid->asWeaps[weapon_slot].nStat) && targetStructure) { //are we any good against walls? if(asStructStrengthModifier[weaponEffect][targetStructure->pStructureType->strength] >= 100) //can attack atleast with default strength { bestTarget = (BASE_OBJECT *)targetStructure; //attack wall } } if (targetOrigin) { *targetOrigin = tmpOrigin; } *ppsObj = bestTarget; return bestMod; } return failure; }
/* Calculates attack priority for a certain target */ static SDWORD targetAttackWeight(BASE_OBJECT *psTarget, BASE_OBJECT *psAttacker, SDWORD weapon_slot) { SDWORD targetTypeBonus=0, damageRatio=0, attackWeight=0, noTarget=-1; UDWORD weaponSlot; DROID *targetDroid=NULL,*psAttackerDroid=NULL,*psGroupDroid,*psDroid; STRUCTURE *targetStructure=NULL; WEAPON_EFFECT weaponEffect; WEAPON_STATS *attackerWeapon; BOOL bEmpWeap=false,bCmdAttached=false,bTargetingCmd=false; if (psTarget == NULL || psAttacker == NULL || aiObjectIsProbablyDoomed(psTarget)) { return noTarget; } ASSERT(psTarget != psAttacker, "targetAttackWeight: Wanted to evaluate the worth of attacking ourselves..."); targetTypeBonus = 0; //Sensors/ecm droids, non-military structures get lower priority /* Get attacker weapon effect */ if(psAttacker->type == OBJ_DROID) { psAttackerDroid = (DROID *)psAttacker; attackerWeapon = (WEAPON_STATS *)(asWeaponStats + psAttackerDroid->asWeaps[weapon_slot].nStat); //check if this droid is assigned to a commander bCmdAttached = hasCommander(psAttackerDroid); //find out if current target is targeting our commander if(bCmdAttached) { if(psTarget->type == OBJ_DROID) { psDroid = (DROID *)psTarget; //go through all enemy weapon slots for(weaponSlot = 0; !bTargetingCmd && weaponSlot < ((DROID *)psTarget)->numWeaps; weaponSlot++) { //see if this weapon is targeting our commander if (psDroid->psActionTarget[weaponSlot] == (BASE_OBJECT *)psAttackerDroid->psGroup->psCommander) { bTargetingCmd = true; } } } else { if(psTarget->type == OBJ_STRUCTURE) { //go through all enemy weapons for(weaponSlot = 0; !bTargetingCmd && weaponSlot < ((STRUCTURE *)psTarget)->numWeaps; weaponSlot++) { if (((STRUCTURE *)psTarget)->psTarget[weaponSlot] == (BASE_OBJECT *)psAttackerDroid->psGroup->psCommander) { bTargetingCmd = true; } } } } } } else if(psAttacker->type == OBJ_STRUCTURE) { attackerWeapon = ((WEAPON_STATS *)(asWeaponStats + ((STRUCTURE *)psAttacker)->asWeaps[weapon_slot].nStat)); } else /* feature */ { ASSERT(!"invalid attacker object type", "targetAttackWeight: Invalid attacker object type"); return noTarget; } //Get weapon effect weaponEffect = attackerWeapon->weaponEffect; //See if attacker is using an EMP weapon bEmpWeap = (attackerWeapon->weaponSubClass == WSC_EMP); /* Calculate attack weight */ if(psTarget->type == OBJ_DROID) { targetDroid = (DROID *)psTarget; if (targetDroid->died) { debug(LOG_NEVER, "Target droid is dead, skipping invalid droid.\n"); return noTarget; } /* Calculate damage this target suffered */ if (targetDroid->originalBody == 0) // FIXME Somewhere we get 0HP droids from { damageRatio = 0; debug(LOG_ERROR, "targetAttackWeight: 0HP droid detected!"); debug(LOG_ERROR, " Type: %i Name: \"%s\" Owner: %i \"%s\")", targetDroid->droidType, targetDroid->aName, targetDroid->player, getPlayerName(targetDroid->player)); } else { damageRatio = 1 - targetDroid->body / targetDroid->originalBody; } assert(targetDroid->originalBody != 0); // Assert later so we get the info from above /* See if this type of a droid should be prioritized */ switch (targetDroid->droidType) { case DROID_SENSOR: case DROID_ECM: case DROID_PERSON: case DROID_TRANSPORTER: case DROID_DEFAULT: case DROID_ANY: break; case DROID_CYBORG: case DROID_WEAPON: case DROID_CYBORG_SUPER: targetTypeBonus = WEIGHT_WEAPON_DROIDS; break; case DROID_COMMAND: targetTypeBonus = WEIGHT_COMMAND_DROIDS; break; case DROID_CONSTRUCT: case DROID_REPAIR: case DROID_CYBORG_CONSTRUCT: case DROID_CYBORG_REPAIR: targetTypeBonus = WEIGHT_SERVICE_DROIDS; break; } /* Now calculate the overall weight */ attackWeight = asWeaponModifier[weaponEffect][(asPropulsionStats + targetDroid->asBits[COMP_PROPULSION].nStat)->propulsionType] // Our weapon's effect against target + WEIGHT_DIST_TILE_DROID * psAttacker->sensorRange/TILE_UNITS - WEIGHT_DIST_TILE_DROID * map_coord(iHypot(psAttacker->pos.x - targetDroid->pos.x, psAttacker->pos.y - targetDroid->pos.y)) // farer droids are less attractive + WEIGHT_HEALTH_DROID * damageRatio // we prefer damaged droids + targetTypeBonus; // some droid types have higher priority /* If attacking with EMP try to avoid targets that were already "EMPed" */ if(bEmpWeap && (targetDroid->lastHitWeapon == WSC_EMP) && ((gameTime - targetDroid->timeLastHit) < EMP_DISABLE_TIME)) //target still disabled { attackWeight /= EMP_DISABLED_PENALTY_F; } } else if(psTarget->type == OBJ_STRUCTURE) { targetStructure = (STRUCTURE *)psTarget; /* Calculate damage this target suffered */ damageRatio = 1 - targetStructure->body / structureBody(targetStructure); /* See if this type of a structure should be prioritized */ switch(targetStructure->pStructureType->type) { case REF_DEFENSE: targetTypeBonus = WEIGHT_WEAPON_STRUCT; break; case REF_RESOURCE_EXTRACTOR: targetTypeBonus = WEIGHT_DERRICK_STRUCT; break; case REF_FACTORY: case REF_CYBORG_FACTORY: case REF_REPAIR_FACILITY: targetTypeBonus = WEIGHT_MILITARY_STRUCT; break; default: break; } /* Now calculate the overall weight */ attackWeight = asStructStrengthModifier[weaponEffect][targetStructure->pStructureType->strength] // Our weapon's effect against target + WEIGHT_DIST_TILE_STRUCT * psAttacker->sensorRange/TILE_UNITS - WEIGHT_DIST_TILE_STRUCT * map_coord(iHypot(psAttacker->pos.x - targetStructure->pos.x, psAttacker->pos.y - targetStructure->pos.y)) // farer structs are less attractive + WEIGHT_HEALTH_STRUCT * damageRatio // we prefer damaged structures + targetTypeBonus; // some structure types have higher priority /* Go for unfinished structures only if nothing else found (same for non-visible structures) */ if(targetStructure->status != SS_BUILT) //a decoy? { attackWeight /= WEIGHT_STRUCT_NOTBUILT_F; } /* EMP should only attack structures if no enemy droids are around */ if(bEmpWeap) { attackWeight /= EMP_STRUCT_PENALTY_F; } } else //a feature { return 1; } /* We prefer objects we can see and can attack immediately */ if(!visibleObject((BASE_OBJECT *)psAttacker, psTarget, true)) { attackWeight /= WEIGHT_NOT_VISIBLE_F; } /* Commander-related criterias */ if(bCmdAttached) //attached to a commander and don't have a target assigned by some order { ASSERT(psAttackerDroid->psGroup->psCommander != NULL, "Commander is NULL"); //if commander is being targeted by our target, try to defend the commander if(bTargetingCmd) { attackWeight += WEIGHT_CMD_RANK * ( 1 + getDroidLevel(psAttackerDroid->psGroup->psCommander)); } //fire support - go through all droids assigned to the commander for (psGroupDroid = psAttackerDroid->psGroup->psList; psGroupDroid; psGroupDroid = psGroupDroid->psGrpNext) { for(weaponSlot = 0; weaponSlot < psGroupDroid->numWeaps; weaponSlot++) { //see if this droid is currently targeting current target if(psGroupDroid->psTarget == psTarget || psGroupDroid->psActionTarget[weaponSlot] == psTarget) { //we prefer targets that are already targeted and hence will be destroyed faster attackWeight += WEIGHT_CMD_SAME_TARGET; } } } } return attackWeight; }
/** Search the global list of sensors for a possible target for psObj. */ static BASE_OBJECT *aiSearchSensorTargets(BASE_OBJECT *psObj, int weapon_slot, WEAPON_STATS *psWStats, UWORD *targetOrigin) { int longRange = proj_GetLongRange(psWStats); int tarDist = longRange * longRange; BOOL foundCB = false; int minDist = psWStats->minRange * psWStats->minRange; BASE_OBJECT *psSensor, *psTarget = NULL; if (targetOrigin) { *targetOrigin = ORIGIN_UNKNOWN; } for (psSensor = apsSensorList[0]; psSensor; psSensor = psSensor->psNextFunc) { BASE_OBJECT *psTemp = NULL; bool isCB = false; bool isRD = false; if (!aiCheckAlliances(psSensor->player, psObj->player)) { continue; } else if (psSensor->type == OBJ_DROID) { DROID *psDroid = (DROID *)psSensor; ASSERT_OR_RETURN(false, psDroid->droidType == DROID_SENSOR, "A non-sensor droid in a sensor list is non-sense"); psTemp = psDroid->psTarget; isCB = cbSensorDroid(psDroid); isRD = objRadarDetector((BASE_OBJECT *)psDroid); } else if (psSensor->type == OBJ_STRUCTURE) { STRUCTURE *psCStruct = (STRUCTURE *)psSensor; // skip incomplete structures if (psCStruct->status != SS_BUILT) { continue; } psTemp = psCStruct->psTarget[0]; isCB = structCBSensor(psCStruct); isRD = objRadarDetector((BASE_OBJECT *)psCStruct); } if (!psTemp || aiObjectIsProbablyDoomed(psTemp) || !validTarget(psObj, psTemp, 0) || aiCheckAlliances(psTemp->player, psObj->player)) { continue; } if (aiObjHasRange(psObj, psTemp, weapon_slot) && visibleObject(psSensor, psTemp, false)) { int distSq = objPosDiffSq(psTemp->pos, psObj->pos); // Need to be in range, prefer closer targets or CB targets if ((isCB > foundCB || (isCB == foundCB && distSq < tarDist)) && distSq > minDist) { tarDist = distSq; psTarget = psTemp; if (targetOrigin) { *targetOrigin = ORIGIN_SENSOR; } if (isCB) { if (targetOrigin) { *targetOrigin = ORIGIN_CB_SENSOR; } foundCB = true; // got CB target, drop everything and shoot! } else if (isRD) { if (targetOrigin) { *targetOrigin = ORIGIN_RADAR_DETECTOR; } } } } } return psTarget; }
/* Do the AI for a droid */ void aiUpdateDroid(DROID *psDroid) { BASE_OBJECT *psTarget; BOOL lookForTarget,updateTarget; ASSERT(psDroid != NULL, "Invalid droid pointer"); if (!psDroid || isDead((BASE_OBJECT *)psDroid)) { return; } // HACK: we always want to update orders when NOT running a MP game, // and we don't want to update when the droid belongs to another human player if (!myResponsibility(psDroid->player) && bMultiPlayer && isHumanPlayer(psDroid->player)) { return; // we should not order this droid around } lookForTarget = false; updateTarget = false; // look for a target if doing nothing if (orderState(psDroid, DORDER_NONE) || orderState(psDroid, DORDER_GUARD) || orderState(psDroid, DORDER_TEMP_HOLD)) { lookForTarget = true; } // but do not choose another target if doing anything while guarding if (orderState(psDroid, DORDER_GUARD) && (psDroid->action != DACTION_NONE)) { lookForTarget = false; } // except when self-repairing if (psDroid->action == DACTION_DROIDREPAIR && psDroid->psActionTarget[0] == (BASE_OBJECT *)psDroid) { lookForTarget = true; } // don't look for a target if sulking if (psDroid->action == DACTION_SULK) { lookForTarget = false; } /* Only try to update target if already have some target */ if (psDroid->action == DACTION_ATTACK || psDroid->action == DACTION_MOVEFIRE || psDroid->action == DACTION_MOVETOATTACK || psDroid->action == DACTION_ROTATETOATTACK) { updateTarget = true; } if ((orderState(psDroid, DORDER_OBSERVE) || orderState(psDroid, DORDER_ATTACKTARGET)) && psDroid->psTarget && aiObjectIsProbablyDoomed(psDroid->psTarget)) { lookForTarget = true; updateTarget = false; } /* Don't update target if we are sent to attack and reached attack destination (attacking our target) */ if (orderState(psDroid, DORDER_ATTACK) && psDroid->psActionTarget[0] == psDroid->psTarget) { updateTarget = false; } // don't look for a target if there are any queued orders if (psDroid->listSize > 0) { lookForTarget = false; updateTarget = false; } // horrible check to stop droids looking for a target if // they would switch to the guard order in the order update loop if ((psDroid->order == DORDER_NONE) && (psDroid->player == selectedPlayer) && !isVtolDroid(psDroid) && secondaryGetState(psDroid, DSO_HALTTYPE) == DSS_HALT_GUARD) { lookForTarget = false; updateTarget = false; } // don't allow units to start attacking if they will switch to guarding the commander if(hasCommander(psDroid)) { lookForTarget = false; updateTarget = false; } if(bMultiPlayer && isVtolDroid(psDroid) && isHumanPlayer(psDroid->player)) { lookForTarget = false; updateTarget = false; } // do not look for a target if droid is currently under direct control. if(driveModeActive() && (psDroid == driveGetDriven())) { lookForTarget = false; updateTarget = false; } // CB and VTOL CB droids can't autotarget. if (psDroid->droidType == DROID_SENSOR && !standardSensorDroid(psDroid)) { lookForTarget = false; updateTarget = false; } // do not attack if the attack level is wrong if (secondaryGetState(psDroid, DSO_ATTACK_LEVEL) != DSS_ALEV_ALWAYS) { lookForTarget = false; } /* For commanders and non-assigned non-commanders: look for a better target once in a while */ if(!lookForTarget && updateTarget) { if((psDroid->numWeaps > 0) && !hasCommander(psDroid)) //not assigned to commander { if((psDroid->id + gameTime)/TARGET_UPD_SKIP_FRAMES != (psDroid->id + gameTime - deltaGameTime)/TARGET_UPD_SKIP_FRAMES) { unsigned int i; (void)updateAttackTarget((BASE_OBJECT*)psDroid, 0); // this function always has to be called on weapon-slot 0 (even if ->numWeaps == 0) //updates all targets for (i = 1; i < psDroid->numWeaps; ++i) { (void)updateAttackTarget((BASE_OBJECT*)psDroid, i); } } } } /* Null target - see if there is an enemy to attack */ if (lookForTarget && !updateTarget) { if (psDroid->droidType == DROID_SENSOR) { if (aiChooseSensorTarget((BASE_OBJECT *)psDroid, &psTarget)) { orderDroidObj(psDroid, DORDER_OBSERVE, psTarget); } } else { if (aiChooseTarget((BASE_OBJECT *)psDroid, &psTarget, 0, true, NULL)) { orderDroidObj(psDroid, DORDER_ATTACKTARGET, psTarget); } } } }
/* See if there is a target in range for Sensor objects*/ bool aiChooseSensorTarget(BASE_OBJECT *psObj, BASE_OBJECT **ppsTarget) { int sensorRange = objSensorRange(psObj); unsigned int radSquared = sensorRange * sensorRange; bool radarDetector = objRadarDetector(psObj); if (!objActiveRadar(psObj) && !radarDetector) { ASSERT(false, "Only to be used for sensor turrets!"); return false; } /* See if there is something in range */ if (psObj->type == OBJ_DROID) { BASE_OBJECT *psTarget = NULL; if (aiBestNearestTarget((DROID *)psObj, &psTarget, 0) >= 0) { /* See if in sensor range */ const int xdiff = psTarget->pos.x - psObj->pos.x; const int ydiff = psTarget->pos.y - psObj->pos.y; const unsigned int distSq = xdiff * xdiff + ydiff * ydiff; // I do believe this will never happen, check for yourself :-) debug(LOG_NEVER, "Sensor droid(%d) found possible target(%d)!!!", psObj->id, psTarget->id); if (distSq < radSquared) { *ppsTarget = psTarget; return true; } } } else // structure { BASE_OBJECT *psTemp = NULL; int tarDist = SDWORD_MAX; static GridList gridList; // static to avoid allocations. gridList = gridStartIterate(psObj->pos.x, psObj->pos.y, objSensorRange(psObj)); for (GridIterator gi = gridList.begin(); gi != gridList.end(); ++gi) { BASE_OBJECT *psCurr = *gi; // Don't target features or doomed/dead objects if (psCurr->type != OBJ_FEATURE && !psCurr->died && !aiObjectIsProbablyDoomed(psCurr, false)) { if (!aiCheckAlliances(psCurr->player, psObj->player) && !aiObjIsWall(psCurr)) { // See if in sensor range and visible const int xdiff = psCurr->pos.x - psObj->pos.x; const int ydiff = psCurr->pos.y - psObj->pos.y; const unsigned int distSq = xdiff * xdiff + ydiff * ydiff; if (distSq < radSquared && psCurr->visible[psObj->player] == UBYTE_MAX && distSq < tarDist) { psTemp = psCurr; tarDist = distSq; } } } } if (psTemp) { ASSERT(!psTemp->died, "aiChooseSensorTarget gave us a dead target"); *ppsTarget = psTemp; return true; } } return false; }