// return the state for an order for all the units selected // if there are multiple states then don't return a state static SECONDARY_STATE GetSecondaryStates(SECONDARY_ORDER sec) { SECONDARY_STATE state, currState; bool bFirst; state = (SECONDARY_STATE)0; bFirst = true; if (psSelectedFactory) { if (getFactoryState(psSelectedFactory, sec, &currState)) { state = currState; } } else //droids { for (unsigned i = 0; i < SelectedDroids.size(); ++i) { currState = secondaryGetState(SelectedDroids[i], sec, ModeQueue); if (bFirst) { state = currState; bFirst = false; } else if (state != currState) { state = (SECONDARY_STATE)0; } } } return state; }
/* 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 */ 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; }
/* 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; } lookForTarget = false; updateTarget = false; // look for a target if doing nothing if (orderState(psDroid, DORDER_NONE) || orderState(psDroid, DORDER_GUARD) || orderState(psDroid, DORDER_HOLD)) { lookForTarget = true; } // but do not choose another target if doing anything while guarding // exception for sensors, to allow re-targetting when target is doomed if (orderState(psDroid, DORDER_GUARD) && psDroid->action != DACTION_NONE && psDroid->droidType != DROID_SENSOR) { lookForTarget = false; } // 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->order.psObj && psDroid->order.psObj->died) { 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->order.psObj) { updateTarget = false; } // don't look for a target if there are any queued orders if (psDroid->listSize > 0) { 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 && psDroid->numWeaps > 0 && !hasCommander(psDroid) && (psDroid->id + gameTime) / TARGET_UPD_SKIP_FRAMES != (psDroid->id + gameTime - deltaGameTime) / TARGET_UPD_SKIP_FRAMES) { for (int i = 0; i < psDroid->numWeaps; ++i) { 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)) { actionDroid(psDroid, DACTION_OBSERVE, psTarget); } } else { if (aiChooseTarget((BASE_OBJECT *)psDroid, &psTarget, 0, true, NULL)) { actionDroid(psDroid, DACTION_ATTACK, psTarget); } } } }