/** ** Enemy units in distance. ** ** @param player Find enemies of this player ** @param type Optional unit type to check if enemy can target this ** @param pos location ** @param range Distance range to look. ** ** @return Number of enemy units. */ int AiEnemyUnitsInDistance(const CPlayer *player, const CUnitType *type, const Vec2i &pos, unsigned range) { // Select all units in range. CUnit *table[UnitMax]; const unsigned int n = Map.Select(pos.x - range, pos.y - range, pos.x + range + (type ? type->TileWidth :0), pos.y + range + (type ? type->TileHeight:0), table); // Find the enemy units which can attack int e = 0; for (unsigned int i = 0; i < n; ++i) { const CUnit *dest = table[i]; // Those can't attack anyway. if (dest->Removed || dest->Variable[INVISIBLE_INDEX].Value || dest->CurrentAction() == UnitActionDie) { continue; } if (!player->IsEnemy(*dest)) { // a friend or neutral continue; } // Unit can attack back? if (!type || CanTarget(dest->Type, type)) { ++e; } } return e; }
void FighterTacticalAI::SelectTargetDirected(Ship* tgt) { Ship* potential_target = tgt; if (!tgt) { // try to target one of the element's objectives // (if it shows up in the contact list) Element* elem = ship->GetElement(); if (elem) { Instruction* objective = elem->GetTargetObjective(); if (objective) { SimObject* obj_sim_obj = objective->GetTarget(); Ship* obj_tgt = 0; if (obj_sim_obj && obj_sim_obj->Type() == SimObject::SIM_SHIP) obj_tgt = (Ship*) obj_sim_obj; if (obj_tgt && ship->FindContact(obj_tgt)) potential_target = obj_tgt; } } } if (!CanTarget(potential_target)) potential_target = 0; ship_ai->SetTarget(potential_target); SelectSecondaryForTarget(potential_target); }
/** ** Choose target on map area. ** ** @param source Unit which want to attack. ** @param x1 X position on map, tile-based. ** @param y1 Y position on map, tile-based. ** @param x2 X position on map, tile-based. ** @param y2 Y position on map, tile-based. ** ** @return Returns ideal target on map tile. */ CUnit *TargetOnMap(const CUnit *source, int x1, int y1, int x2, int y2) { CUnit *table[UnitMax]; CUnit *best = NoUnitP; int n; n = UnitCache.Select(x1, y1, x2, y2, table, UnitMax); for (int i = 0; i < n; ++i) { CUnit *unit = table[i]; if (!unit->IsVisibleAsGoal(source->Player)) { continue; } if (x2 < unit->X || x1 >= unit->X + unit->Type->TileWidth || y2 < unit->Y || y1 >= unit->Y + unit->Type->TileHeight) { continue; } if (!CanTarget(source->Type, unit->Type)) { continue; } // // Choose the best target. // if (!best || best->Type->Priority < unit->Type->Priority) { best = unit; } } return best; }
/** ** Called if a member of Group is Attacked ** ** @param attacker Pointer to attacker unit. ** @param defender Pointer to unit that is being attacked. */ void GroupHelpMe(CUnit *attacker, CUnit &defender) { /* Freandly Fire - typical splash */ if (!attacker || attacker->Player->Index == defender.Player->Index) { return; } DebugPrint("%d: GroupHelpMe %d(%s) attacked at %d,%d\n" _C_ defender.Player->Index _C_ UnitNumber(defender) _C_ defender.Type->Ident.c_str() _C_ defender.tilePos.x _C_ defender.tilePos.y); // // Don't send help to scouts (zeppelin,eye of vision). // if (!defender.Type->CanAttack && defender.Type->UnitType == UnitTypeFly) { return; } if (defender.GroupId) { int mask = 0; CUnitGroup *group; for (int num = 0; num < NUM_GROUPS; ++num) { // Unit belongs to an group, check if brothers in arms can help if (defender.GroupId & (1<<num)) { mask |= (1<<num); group = &Groups[num]; for (int i = 0; i < group->NumUnits; ++i) { CUnit &gunit = *group->Units[i]; if (&defender == &gunit) { continue; } // if brother is idle or attack no-agressive target and // can attack our attacker then ask for help if (gunit.IsAgressive() && (gunit.IsIdle() || !(gunit.CurrentAction() == UnitActionAttack && gunit.CurrentOrder()->HasGoal() && gunit.CurrentOrder()->GetGoal()->IsAgressive())) && CanTarget(gunit.Type, attacker->Type)) { if (gunit.SavedOrder.Action == UnitActionStill) { // FIXME: should rewrite command handling CommandAttack(gunit, gunit.tilePos, NoUnitP, FlushCommands); gunit.SavedOrder = *gunit.Orders[1]; } CommandAttack(gunit, attacker->tilePos, attacker, FlushCommands); } } if (!(defender.GroupId & ~mask)) { return; } } } } }
void TacticalAI::SelectTargetDirected(Ship* tgt) { Ship* potential_target = tgt; // try to target one of the element's objectives // (if it shows up in the contact list) if (!tgt) { Element* elem = ship->GetElement(); if (elem) { Instruction* objective = elem->GetTargetObjective(); if (objective) { SimObject* obj_sim_obj = objective->GetTarget(); Ship* obj_tgt = 0; if (obj_sim_obj && obj_sim_obj->Type() == SimObject::SIM_SHIP) obj_tgt = (Ship*) obj_sim_obj; if (obj_tgt) { ListIter<Contact> contact = ship->ContactList(); while (++contact && !potential_target) { Ship* test = contact->GetShip(); if (obj_tgt == test) { potential_target = test; } } } } } } if (!CanTarget(potential_target)) potential_target = 0; ship_ai->SetTarget(potential_target); if (tgt && tgt == ship_ai->GetTarget()) directed_tgtid = tgt->Identity(); else directed_tgtid = 0; }
/** ** Choose target at pixel map coordinates. ** ** @param source Unit which wants to attack. ** @param x X position on the display map, pixel-based. ** @param y Y position on the display map, pixel-based. ** ** @return Returns ideal target */ global Unit* TargetOnScreenMapPosition(const Unit* source,int x,int y) { Unit* table[UnitMax]; Unit* unit; Unit* best; int tx; int ty; int n; int i; // this code runs upon right button action only so it can affort being a // little inefficient. tx = x / TileSizeX; ty = y / TileSizeY; // ships and flyers could be 2 fields away n = UnitCacheSelect(tx-2,ty-2, tx+2, ty+2, table); best=NoUnitP; for( i=0; i<n; ++i ) { unit=table[i]; // unusable unit ? // if( UnitUnusable(unit) ) can't attack constructions // FIXME: did SelectUnitsOnTile already filter this? // Invisible and not Visible if( unit->Removed || unit->Invisible || !(unit->Visible&(1<<source->Player->Player)) || unit->Orders[0].Action==UnitActionDie ) { continue; } if ( !InsideUnitSprite(table[i], x, y)) { continue; } if( !CanTarget(source->Type,unit->Type) ) { continue; } // // Choose the best target. // if( !best || best->Type->Priority < unit->Type->Priority ) { best=unit; } } return best; }
/** ** Choose target on map tile. ** ** @param source Unit which want to attack. ** @param tx X position on map, tile-based. ** @param ty Y position on map, tile-based. ** ** @return Returns ideal target on map tile. */ global Unit* TargetOnMapTile(const Unit* source,int tx,int ty) { Unit* table[UnitMax]; Unit* unit; Unit* best; const UnitType* type; int n; int i; n=SelectUnitsOnTile(tx,ty,table); best=NoUnitP; for( i=0; i<n; ++i ) { unit=table[i]; // unusable unit ? // if( UnitUnusable(unit) ) can't attack constructions // FIXME: did SelectUnitsOnTile already filter this? // Invisible and not Visible if( unit->Removed || unit->Invisible || !unit->HP || !(unit->Visible&(1<<source->Player->Player)) || unit->Orders[0].Action==UnitActionDie ) { continue; } type=unit->Type; if( tx<unit->X || tx>=unit->X+type->TileWidth || ty<unit->Y || ty>=unit->Y+type->TileHeight ) { continue; } if( !CanTarget(source->Type,unit->Type) ) { continue; } // // Choose the best target. // if( !best || best->Type->Priority<unit->Type->Priority ) { best=unit; } } return best; }
/** ** Choose target on map area. ** ** @param source Unit which want to attack. ** @param pos1 position on map, tile-based. ** @param pos2 position on map, tile-based. ** ** @return Returns ideal target on map tile. */ CUnit *TargetOnMap(const CUnit &source, const Vec2i &pos1, const Vec2i &pos2) { std::vector<CUnit *> table; Select(pos1, pos2, table); CUnit *best = NULL; for (size_t i = 0; i != table.size(); ++i) { CUnit &unit = *table[i]; if (!unit.IsVisibleAsGoal(*source.Player)) { continue; } if (!CanTarget(*source.Type, *unit.Type)) { continue; } // Choose the best target. if (!best || best->Variable[PRIORITY_INDEX].Value < unit.Variable[PRIORITY_INDEX].Value) { best = &unit; } } return best; }
void Compute(CUnit *const dest) { const CPlayer &player = *attacker->Player; if (!dest->IsVisibleAsGoal(player)) { dest->CacheLock = 1; return; } const CUnitType &type = *attacker->Type; const CUnitType &dtype = *dest->Type; // won't be a target... if (!CanTarget(type, dtype)) { // can't be attacked. dest->CacheLock = 1; return; } // Don't attack invulnerable units if (dtype.BoolFlag[INDESTRUCTIBLE_INDEX].value || dest->Variable[UNHOLYARMOR_INDEX].Value) { dest->CacheLock = 1; return; } // Calculate the costs to attack the unit. // Unit with the smallest attack costs will be taken. int cost = 0; int hp_damage_evaluate; if (Damage) { hp_damage_evaluate = CalculateDamage(*attacker, *dest, Damage); } else { hp_damage_evaluate = attacker->Stats->Variables[BASICDAMAGE_INDEX].Value + attacker->Stats->Variables[PIERCINGDAMAGE_INDEX].Value; } if (!player.IsEnemy(*dest)) { // a friend or neutral dest->CacheLock = 1; // Calc a negative cost // The gost is more important when the unit would be killed // by our fire. // It costs (is positive) if hp_damage_evaluate>dest->HP ...) // FIXME : assume that PRIORITY_FACTOR>HEALTH_FACTOR cost = HEALTH_FACTOR * (2 * hp_damage_evaluate - dest->Variable[HP_INDEX].Value) / (dtype.TileWidth * dtype.TileWidth); cost = std::max(cost, 1); cost = -cost; } else { // Priority 0-255 cost += dtype.DefaultStat.Variables[PRIORITY_INDEX].Value * PRIORITY_FACTOR; for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); i++) { if (type.BoolFlag[i].AiPriorityTarget != CONDITION_TRUE) { if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_ONLY) & (dtype.BoolFlag[i].value)) { cost -= AIPRIORITY_BONUS; } else if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_FALSE) & (dtype.BoolFlag[i].value)) { cost += AIPRIORITY_BONUS; } } } // Remaining HP (Health) 0-65535 // Give a boost to unit we can kill in one shoot only // calculate HP which will remain in the enemy unit, after hit int effective_hp = (dest->Variable[HP_INDEX].Value - 2 * hp_damage_evaluate); // Unit we won't kill are evaluated the same // Unit we are sure to kill are all evaluated the same (except PRIORITY) clamp(&effective_hp, -hp_damage_evaluate, 0); // Here, effective_hp vary from -hp_damage_evaluate (unit will be killed) to 0 (unit can't be killed) // => we prefer killing rather than only hitting... cost += -effective_hp * HEALTH_FACTOR; // Unit can attack back. if (CanTarget(dtype, type)) { cost += CANATTACK_BONUS; } // the cost may be divided across multiple cells cost = cost / (dtype.TileWidth * dtype.TileWidth); cost = std::max(cost, 1); // Removed Unit's are in bunkers int d; if (attacker->Removed) { d = attacker->Container->MapDistanceTo(*dest); } else { d = attacker->MapDistanceTo(*dest); } int attackrange = attacker->Stats->Variables[ATTACKRANGE_INDEX].Max; if (d <= attackrange || (d <= range && UnitReachable(*attacker, *dest, attackrange))) { ++enemy_count; } else { dest->CacheLock = 1; } // Attack walls only if we are stuck in them if (dtype.BoolFlag[WALL_INDEX].value && d > 1) { dest->CacheLock = 1; } } // cost map is relative to attacker position const int x = dest->tilePos.x - attacker->tilePos.x + (size / 2); const int y = dest->tilePos.y - attacker->tilePos.y + (size / 2); Assert(x >= 0 && y >= 0); // Mark the good/bad array... for (int yy = 0; yy < dtype.TileHeight; ++yy) { for (int xx = 0; xx < dtype.TileWidth; ++xx) { int pos = (y + yy) * (size / 2) + (x + xx); if (pos >= good->size()) { printf("BUG: RangeTargetFinder::FillBadGood.Compute out of range. "\ "size: %d, pos: %d, " \ "x: %d, xx: %d, y: %d, yy: %d", size, pos, x, xx, y, yy); break; } if (cost < 0) { good->at(pos) -= cost; } else { bad->at(pos) += cost; } } } }
int ComputeCost(CUnit *const dest) const { const CPlayer &player = *attacker->Player; const CUnitType &type = *attacker->Type; const CUnitType &dtype = *dest->Type; const int attackrange = attacker->Stats->Variables[ATTACKRANGE_INDEX].Max; if (!player.IsEnemy(*dest) // a friend or neutral || !dest->IsVisibleAsGoal(player) || !CanTarget(type, dtype)) { return INT_MAX; } // Don't attack invulnerable units if (dtype.BoolFlag[INDESTRUCTIBLE_INDEX].value || dest->Variable[UNHOLYARMOR_INDEX].Value) { return INT_MAX; } // Unit in range ? const int d = attacker->MapDistanceTo(*dest); if (d > attackrange && !UnitReachable(*attacker, *dest, attackrange)) { return INT_MAX; } // Attack walls only if we are stuck in them if (dtype.BoolFlag[WALL_INDEX].value && d > 1) { return INT_MAX; } if (dtype.UnitType == UnitTypeFly && dest->IsAgressive() == false) { return INT_MAX / 2; } // Calculate the costs to attack the unit. // Unit with the smallest attack costs will be taken. int cost = 0; // Priority 0-255 cost -= dtype.DefaultStat.Variables[PRIORITY_INDEX].Value * PRIORITY_FACTOR; // Remaining HP (Health) 0-65535 cost += dest->Variable[HP_INDEX].Value * 100 / dest->Variable[HP_INDEX].Max * HEALTH_FACTOR; if (d <= attackrange && d >= type.MinAttackRange) { cost += d * INRANGE_FACTOR; cost -= INRANGE_BONUS; } else { cost += d * DISTANCE_FACTOR; } for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); i++) { if (type.BoolFlag[i].AiPriorityTarget != CONDITION_TRUE) { if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_ONLY) & (dtype.BoolFlag[i].value)) { cost -= AIPRIORITY_BONUS; } if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_FALSE) & (dtype.BoolFlag[i].value)) { cost += AIPRIORITY_BONUS; } } } // Unit can attack back. if (CanTarget(dtype, type)) { cost -= CANATTACK_BONUS; } return cost; }
/** ** Attack units in distance. ** ** If the unit can attack must be handled by caller. ** Choose the best target, that can be attacked. ** ** @param unit Find in distance for this unit. ** @param range Distance range to look. ** ** @return Unit to be attacked. ** */ CUnit *AttackUnitsInDistance(const CUnit *unit, int range) { CUnit *dest; const CUnitType *type; const CUnitType *dtype; CUnit *table[UnitMax]; int x; int y; int n; int i; int d; int attackrange; int cost; int best_cost; const CPlayer *player; CUnit *best_unit; // if necessary, take possible damage on allied units into account... if (unit->Type->Missile.Missile->Range > 1 && (range + unit->Type->Missile.Missile->Range < 15)) { return FindRangeAttack(unit, range); } // // Select all units in range. // x = unit->X; y = unit->Y; type = unit->Type; n = UnitCache.Select(x - range, y - range, x + range + type->TileWidth, y + range + type->TileHeight, table, UnitMax); if (range > 25 && n > 9) { referenceunit = unit; qsort((void*)table, n, sizeof(CUnit*), &CompareUnitDistance); } best_unit = NoUnitP; best_cost = INT_MAX; player = unit->Player; attackrange = unit->Stats->Variables[ATTACKRANGE_INDEX].Max; // // Find the best unit to attack // for (i = 0; i < n; ++i) { dest = table[i]; if (!dest->IsVisibleAsGoal(unit->Player)) { continue; } if (!player->IsEnemy(dest)) { // a friend or neutral continue; } dtype = dest->Type; if (!CanTarget(type, dtype)) { // can't be attacked. continue; } // // Calculate the costs to attack the unit. // Unit with the smallest attack costs will be taken. // cost = 0; // // Priority 0-255 // cost -= dtype->Priority * PRIORITY_FACTOR; // // Remaining HP (Health) 0-65535 // cost += dest->Variable[HP_INDEX].Value * HEALTH_FACTOR; // // Unit in attack range? // d = MapDistanceBetweenUnits(unit, dest); // Use Circle, not square :) if (d > range) { continue; } if (d <= attackrange && d >= type->MinAttackRange) { cost += d * INRANGE_FACTOR; cost -= INRANGE_BONUS; } else { cost += d * DISTANCE_FACTOR; } // // Unit can attack back. // if (CanTarget(dtype, type)) { cost -= CANATTACK_BONUS; } // // Take this target? // if (cost < best_cost && (d <= attackrange || UnitReachable(unit, dest, attackrange))) { best_unit = dest; best_cost = cost; } } return best_unit; }
/** ** Called if a Unit is Attacked ** ** @param attacker Pointer to attacker unit. ** @param defender Pointer to unit that is being attacked. */ void AiHelpMe(const CUnit *attacker, CUnit &defender) { /* Friendly Fire - typical splash */ if (!attacker || attacker->Player->Index == defender.Player->Index) { //FIXME - try react somehow return; } DebugPrint("%d: %d(%s) attacked at %d,%d\n" _C_ defender.Player->Index _C_ UnitNumber(defender) _C_ defender.Type->Ident.c_str() _C_ defender.tilePos.x _C_ defender.tilePos.y); // Don't send help to scouts (zeppelin,eye of vision). if (!defender.Type->CanAttack && defender.Type->UnitType == UnitTypeFly) { return; } // Summoned unit, don't help if (defender.Summoned) { return; } PlayerAi &pai = *defender.Player->Ai; AiPlayer = &pai; // If unit belongs to an attacking force, check if force members can help. if (defender.GroupId) { AiForce &aiForce = pai.Force[defender.GroupId - 1]; // Unit belongs to an force, check if brothers in arms can help for (unsigned int i = 0; i < aiForce.Units.size(); ++i) { CUnit &aiunit = *aiForce.Units[i]; if (&defender == &aiunit) { continue; } // if brother is idle or attack no-agressive target and // can attack our attacker then ask for help // FIXME ad support for help from Coward type units if (aiunit.IsAgressive() && CanTarget(*aiunit.Type, *attacker->Type) && aiunit.CurrentOrder()->GetGoal() != attacker) { bool shouldAttack = aiunit.IsIdle() && aiunit.Threshold == 0; if (aiunit.CurrentAction() == UnitActionAttack) { const COrder_Attack &orderAttack = *static_cast<COrder_Attack *>(aiunit.CurrentOrder()); const CUnit *oldGoal = orderAttack.GetGoal(); if (oldGoal == NULL || (ThreatCalculate(defender, *attacker) < ThreatCalculate(defender, *oldGoal) && aiunit.MapDistanceTo(defender) <= aiunit.Stats->Variables[ATTACKRANGE_INDEX].Max)) { shouldAttack = true; } } if (shouldAttack) { CommandAttack(aiunit, attacker->tilePos, const_cast<CUnit *>(attacker), FlushCommands); COrder *savedOrder = COrder::NewActionAttack(aiunit, attacker->tilePos); if (aiunit.CanStoreOrder(savedOrder) == false) { delete savedOrder; savedOrder = NULL; } else { aiunit.SavedOrder = savedOrder; } } } } if (!aiForce.Defending && aiForce.State > 0) { DebugPrint("%d: %d(%s) belong to attacking force, don't defend it\n" _C_ defender.Player->Index _C_ UnitNumber(defender) _C_ defender.Type->Ident.c_str()); // unit belongs to an attacking force, // so don't send others force in such case. // FIXME: there may be other attacking the same place force who can help return; } } // Send defending forces, also send attacking forces if they are home/traning. // This is still basic model where we suspect only one base ;( const Vec2i &pos = attacker->tilePos; for (unsigned int i = 0; i < pai.Force.Size(); ++i) { AiForce &aiForce = pai.Force[i]; if (aiForce.Size() > 0 && ((aiForce.Role == AiForceRoleDefend && !aiForce.Attacking) || (aiForce.Role == AiForceRoleAttack && !aiForce.Attacking && !aiForce.State))) { // none attacking aiForce.Defending = true; aiForce.Attack(pos); } } }
bool operator()(const CUnit *unit) const { return unit->IsVisibleAsGoal(*player) && unit->IsEnemy(*player) && CanTarget(unit->Type, type); }
/** ** Select the target for the autocast. ** ** @param caster Unit who would cast the spell. ** @param spell Spell-type pointer. ** ** @return Target* chosen target or Null if spell can't be cast. ** @todo FIXME: should be global (for AI) ??? ** @todo FIXME: write for position target. */ static Target *SelectTargetUnitsOfAutoCast(CUnit &caster, const SpellType &spell) { AutoCastInfo *autocast; // Ai cast should be a lot better. Use autocast if not found. if (caster.Player->AiEnabled && spell.AICast) { autocast = spell.AICast; } else { autocast = spell.AutoCast; } Assert(autocast); const Vec2i &pos = caster.tilePos; int range = autocast->Range; // Select all units aroung the caster std::vector<CUnit *> table; SelectAroundUnit(caster, range, table); // Check generic conditions. FIXME: a better way to do this? if (autocast->Combat != CONDITION_TRUE) { // Check each unit if it is hostile. bool inCombat = false; for (size_t i = 0; i < table.size(); ++i) { const CUnit &target = *table[i]; // Note that CanTarget doesn't take into account (offensive) spells... if (target.IsVisibleAsGoal(*caster.Player) && caster.IsEnemy(target) && (CanTarget(*caster.Type, *target.Type) || CanTarget(*target.Type, *caster.Type))) { inCombat = true; break; } } if ((autocast->Combat == CONDITION_ONLY) ^ (inCombat)) { return NULL; } } switch (spell.Target) { case TargetSelf : if (PassCondition(caster, spell, &caster, pos, spell.Condition) && PassCondition(caster, spell, &caster, pos, autocast->Condition)) { return NewTargetUnit(caster); } return NULL; case TargetPosition: return 0; // Autocast with a position? That's hard // Possibilities: cast reveal-map on a dark region // Cast raise dead on a bunch of corpses. That would rule. // Cast summon until out of mana in the heat of battle. Trivial? // Find a tight group of units and cast area-damage spells. HARD, // but it is a must-have for AI. What about area-heal? case TargetUnit: { // The units are already selected. // Check every unit if it is a possible target int n = 0; for (size_t i = 0; i != table.size(); ++i) { // Check if unit in battle if (autocast->Attacker == CONDITION_ONLY) { if (table[i]->CurrentAction() != UnitActionAttack && table[i]->CurrentAction() != UnitActionAttackGround && table[i]->CurrentAction() != UnitActionSpellCast) { continue; } } if (PassCondition(caster, spell, table[i], pos, spell.Condition) && PassCondition(caster, spell, table[i], pos, autocast->Condition)) { table[n++] = table[i]; } } // Now select the best unit to target. if (n != 0) { // For the best target??? if (autocast->PriorytyVar != ACP_NOVALUE) { std::sort(table.begin(), table.begin() + n, AutoCastPrioritySort(caster, autocast->PriorytyVar, autocast->ReverseSort)); return NewTargetUnit(*table[0]); } else { // Use the old behavior return NewTargetUnit(*table[SyncRand() % n]); } } break; } default: // Something is wrong DebugPrint("Spell is screwed up, unknown target type\n"); Assert(0); return NULL; break; } return NULL; // Can't spell the auto-cast. }
/** ** Attack units in distance. ** ** If the unit can attack must be handled by caller. ** Choose the best target, that can be attacked. ** ** @param unit Find in distance for this unit. ** @param range Distance range to look. ** ** @return Unit to be attacked. ** ** @note This could be improved, for better performance. */ global Unit* AttackUnitsInDistance(const Unit* unit,int range) { const Unit* dest; const UnitType* type; const UnitType* dtype; Unit* table[UnitMax]; int x; int y; int n; int i; int d; int attackrange; int cost; const Player* player; const Unit* best_unit; int best_cost; DebugLevel3Fn("(%d)%s\n" _C_ UnitNumber(unit) _C_ unit->Type->Ident); // if necessary, take possible damage on allied units into account... if (unit->Type->Missile.Missile->Range>1 && (range+unit->Type->Missile.Missile->Range<15)) { return FindRangeAttack(unit,range); } // // Select all units in range. // x=unit->X; y=unit->Y; n=SelectUnits(x-range,y-range,x+range+1,y+range+1,table); best_unit=NoUnitP; best_cost=INT_MAX; player=unit->Player; type=unit->Type; attackrange=unit->Stats->AttackRange; // // Find the best unit to attack // for( i=0; i<n; ++i ) { dest=table[i]; // // unusable unit // // FIXME: did SelectUnits already filter this. if( dest->Removed || dest->Invisible || !unit->HP || !(dest->Visible&(1<<player->Player)) || dest->Orders[0].Action==UnitActionDie ) { continue; } if( !IsEnemy(player,dest) ) { // a friend or neutral continue; } dtype=dest->Type; if( !CanTarget(type,dtype) ) { // can't be attacked. continue; } // // Calculate the costs to attack the unit. // Unit with the smallest attack costs will be taken. // cost=0; // // Priority 0-255 // cost-=dtype->Priority*PRIORITY_FACTOR; // // Remaining HP (Health) 0-65535 // cost+=dest->HP*HEALTH_FACTOR; // // Unit in attack range? // d=MapDistanceBetweenUnits(unit,dest); if( d<type->MinAttackRange ) { // FIXME: we don't support moving away! continue; } if( d<attackrange && d>type->MinAttackRange ) { cost+=d*INRANGE_FACTOR; cost-=INRANGE_BONUS; } else { cost+=d*DISTANCE_FACTOR; } // // Unit can attack back. // if( CanTarget(dtype,type) ) { cost-=CANATTACK_BONUS; } DebugLevel3Fn("%s -> %s\t%08x\n" _C_ type->Ident _C_ dtype->Ident _C_ cost); // // Take this target? // if( cost<best_cost && (d<attackrange || UnitReachable(unit,dest,attackrange)) ) { best_unit=dest; best_cost=cost; } } /* if( best_unit ) { DebugLevel3Fn("Attacking (%d)%s -> %s\n" _C_ UnitNumber(unit) _C_ unit->Type->Ident _C_ best_unit->Type->Ident); } */ // FIXME: No idea how to make this correct, without cast!! return (Unit*)best_unit; }
/** ** Attack units in distance, with large missile ** ** Choose the best target, that can be attacked. It takes into ** account allied unit which could be hit by the missile ** ** @param u Find in distance for this unit. ** @param range Distance range to look. ** ** @return Unit to be attacked. ** ** @note This could be improved, for better performance / better trade. ** @note Limited to attack range smaller than 16. ** @note Will be moved to unit_ai.c soon. */ local Unit* FindRangeAttack(const Unit* u, int range) { int x, y, n, cost,d,effective_hp,enemy_count; int missile_range,attackrange,hp_damage_evaluate; int good[32][32], bad[32][32]; Unit* table[UnitMax]; Unit* dest; const UnitType* dtype; const UnitType* type; const Player* player; int xx, yy; int best_x, best_y, best_cost; int i, sbad, sgood; Unit* best; type = u->Type; player = u->Player; // If catapult, count units near the target... // FIXME : make it configurable // missile_range = type->Missile.Missile->Range + range - 1; attackrange=u->Stats->AttackRange; // Evaluation of possible damage... hp_damage_evaluate=u->Stats->BasicDamage+u->Stats->PiercingDamage; DebugCheck(2 * missile_range + 1 >= 32); x = u->X; y = u->Y; n = SelectUnits(x - missile_range, y - missile_range, x + missile_range + 1, y + missile_range + 1, table); if (!n) { return NoUnitP; } for (y = 0; y < 2 * missile_range + 1; y++) { for (x = 0; x < 2 * missile_range + 1; x++) { good[y][x] = 0; bad[y][x] = 0; } } enemy_count=0; // FILL good/bad... for (i = 0; i < n; i++) { dest = table[i]; dtype = dest->Type; // // unusable unit // // FIXME: did SelectUnits already filter this. if (dest->Removed || dest->Invisible || !u->HP || !(dest->Visible & (1 << player->Player)) || dest->Orders[0].Action == UnitActionDie) { table[i] = 0; continue; } // won't be a target... if (!CanTarget(type, dtype)) { // can't be attacked. table[i] = 0; continue; } if (!IsEnemy(player, dest)) { // a friend or neutral table[i] = 0; // Calc a negative cost // The gost is more important when the unit would be killed // by our fire. // It costs ( is positive ) if hp_damage_evaluate>dest->HP ... ) // FIXME : assume that PRIORITY_FACTOR>HEALTH_FACTOR cost = HEALTH_FACTOR*(2*hp_damage_evaluate-dest->HP) / (dtype->TileWidth * dtype->TileWidth); if (cost < 1) { cost = 1; } cost = (-cost); } else { // // Calculate the costs to attack the unit. // Unit with the smallest attack costs will be taken. // cost = 0; // // Priority 0-255 // cost += dtype->Priority * PRIORITY_FACTOR; // // Remaining HP (Health) 0-65535 // // Give a boost to unit we can kill in one shoot only // // calculate HP which will remain in the enemy unit, after hit // effective_hp=(dest->HP-2*hp_damage_evaluate); // // Unit we won't kill are evaluated the same // if (effective_hp>0){ effective_hp=0; } // // Unit we are sure to kill are all evaluated the same ( except PRIORITY ) // if (effective_hp<(-hp_damage_evaluate)){ effective_hp=(-hp_damage_evaluate); } // // Here, effective_hp vary from -hp_damage_evaluate ( unit will be killed) to 0 ( unit can't be killed ) // => we prefer killing rather than only hitting... // cost += (-effective_hp) * HEALTH_FACTOR; // // Unit can attack back. // if (CanTarget(dtype, type)) { cost += CANATTACK_BONUS; } // // the cost may be divided accros multiple cells // cost=cost / (dtype->TileWidth * dtype->TileWidth); if (cost < 1) { cost = 1; } d=MapDistanceBetweenUnits(u,dest); // FIXME: we don't support moving away! if((d<type->MinAttackRange)||(!UnitReachable(u,dest,attackrange))) { table[i]=0; } else { enemy_count++; } } x = dest->X - u->X + missile_range+1; y = dest->Y - u->Y + missile_range+1; // Mark the good/bad array... for (xx = 0; xx < dtype->TileWidth; xx++) { for (yy = 0; yy < dtype->TileWidth; yy++) { if ((x + xx < 0) || (y + yy < 0) || (x + xx >= 2 * missile_range + 1) || (y + yy >= 2 * missile_range + 1)) { continue; } if (cost < 0) { good[y + yy][x + xx] -= cost; } else { bad[y + yy][x + xx] += cost; } } } } if (!enemy_count) { return NoUnitP; } // Find the best area... // The target which provide the best bad/good ratio is choosen... best_x = -1; best_y = -1; best_cost = -1; best = NoUnitP; for (i = 0; i < n; i++) { if (!table[i]) { continue; } dest = table[i]; dtype = dest->Type; // put in x-y the real point which will be hit... // ( only meaningfull when dtype->TileWidth>1 ) if (u->X<dest->X){ x=dest->X; } else if (u->X>dest->X+dtype->TileWidth-1){ x=dest->X+dtype->TileWidth-1; } else { x=u->X; } if (u->Y<dest->Y){ y=dest->Y; } else if(u->Y>dest->Y+dtype->TileWidth-1){ y=dest->Y+dtype->TileWidth-1; } else { y=u->Y; } // Make x,y relative to u->x... x = x - u->X + missile_range+1; y = y - u->Y + missile_range+1; sbad = 0; sgood = 0; for (yy = -(type->Missile.Missile->Range - 1); yy <= type->Missile.Missile->Range - 1; yy++) { for (xx = -(type->Missile.Missile->Range - 1); xx <= type->Missile.Missile->Range - 1; xx++) { if ((x + xx < 0) || (y + yy < 0) || ((x + xx) >= 2 * missile_range + 1) || ((y + yy) >= 2 * missile_range + 1)) { continue; } sbad += bad[y + yy][x + xx]; sgood += good[y + yy][x + xx]; if ((!yy) && (!xx)) { sbad += bad[y + yy][x + xx]; sgood += good[y + yy][x + xx]; } } } // don't consider small damages... if (sgood < 20) { sgood = 20; } cost = sbad / sgood; if (cost > best_cost) { best_cost = cost; best = dest; } } return best; }
/** ** Attack units in distance, with large missile ** ** Choose the best target, that can be attacked. It takes into ** account allied unit which could be hit by the missile ** ** @param u Find in distance for this unit. ** @param range Distance range to look. ** ** @return Unit to be attacked. ** ** @note This could be improved, for better performance / better trade. ** @note Limited to attack range smaller than 16. ** @note Will be moved to unit_ai.c soon. */ static CUnit *FindRangeAttack(const CUnit *u, int range) { int x; int y; int n; int cost; int d; int effective_hp; int enemy_count; int missile_range; int attackrange; int hp_damage_evaluate; int good[32][32]; int bad[32][32]; CUnit *table[UnitMax]; CUnit *dest; const CUnitType *dtype; const CUnitType *type; const CPlayer *player; int xx; int yy; int best_cost; int i; int sbad; int sgood; CUnit *best; type = u->Type; player = u->Player; // If catapult, count units near the target... // FIXME : make it configurable // missile_range = type->Missile.Missile->Range + range - 1; attackrange = u->Stats->Variables[ATTACKRANGE_INDEX].Max; // Evaluation of possible damage... hp_damage_evaluate = u->Stats->Variables[BASICDAMAGE_INDEX].Value + u->Stats->Variables[PIERCINGDAMAGE_INDEX].Value; Assert(2 * missile_range + 1 < 32); // // If unit is removed, use containers x and y if (u->Removed) { x = u->Container->X; y = u->Container->Y; n = UnitCache.Select(x - missile_range, y - missile_range, x + missile_range + u->Container->Type->TileWidth, y + missile_range + u->Container->Type->TileHeight, table, UnitMax); } else { x = u->X; y = u->Y; n = UnitCache.Select(x - missile_range, y - missile_range, x + missile_range + u->Type->TileWidth, y + missile_range + u->Type->TileHeight, table, UnitMax); } if (!n) { return NoUnitP; } for (y = 0; y < 2 * missile_range + 1; ++y) { for (x = 0; x < 2 * missile_range + 1; ++x) { good[y][x] = 0; bad[y][x] = 0; } } enemy_count = 0; // FILL good/bad... for (i = 0; i < n; ++i) { dest = table[i]; dtype = dest->Type; if (!dest->IsVisibleAsGoal(u->Player)) { table[i] = 0; continue; } // won't be a target... if (!CanTarget(type, dtype)) { // can't be attacked. table[i] = 0; continue; } if (!player->IsEnemy(dest)) { // a friend or neutral table[i] = 0; // Calc a negative cost // The gost is more important when the unit would be killed // by our fire. // It costs (is positive) if hp_damage_evaluate>dest->HP ...) // FIXME : assume that PRIORITY_FACTOR>HEALTH_FACTOR cost = HEALTH_FACTOR * (2 * hp_damage_evaluate - dest->Variable[HP_INDEX].Value) / (dtype->TileWidth * dtype->TileWidth); if (cost < 1) { cost = 1; } cost = (-cost); } else { // // Calculate the costs to attack the unit. // Unit with the smallest attack costs will be taken. // cost = 0; // // Priority 0-255 // cost += dtype->Priority * PRIORITY_FACTOR; // // Remaining HP (Health) 0-65535 // // Give a boost to unit we can kill in one shoot only // // calculate HP which will remain in the enemy unit, after hit // effective_hp = (dest->Variable[HP_INDEX].Value - 2 * hp_damage_evaluate); // // Unit we won't kill are evaluated the same // if (effective_hp > 0) { effective_hp = 0; } // // Unit we are sure to kill are all evaluated the same (except PRIORITY) // if (effective_hp < -hp_damage_evaluate) { effective_hp = -hp_damage_evaluate; } // // Here, effective_hp vary from -hp_damage_evaluate (unit will be killed) to 0 (unit can't be killed) // => we prefer killing rather than only hitting... // cost += -effective_hp * HEALTH_FACTOR; // // Unit can attack back. // if (CanTarget(dtype, type)) { cost += CANATTACK_BONUS; } // // the cost may be divided accros multiple cells // cost = cost / (dtype->TileWidth * dtype->TileWidth); if (cost < 1) { cost = 1; } // // Removed Unit's are in bunkers // if (u->Removed) { d = MapDistanceBetweenUnits(u->Container, dest); } else { d = MapDistanceBetweenUnits(u, dest); } if (d <= attackrange || (d <= range && UnitReachable(u, dest, attackrange))) { ++enemy_count; } else { table[i] = 0; } } x = dest->X - u->X + missile_range + 1; y = dest->Y - u->Y + missile_range + 1; // Mark the good/bad array... for (xx = 0; xx < dtype->TileWidth; ++xx) { for (yy = 0; yy < dtype->TileWidth; ++yy) { if ((x + xx < 0) || (y + yy < 0) || (x + xx >= 2 * missile_range + 1) || (y + yy >= 2 * missile_range + 1)) { continue; } if (cost < 0) { good[y + yy][x + xx] -= cost; } else { bad[y + yy][x + xx] += cost; } } } } if (!enemy_count) { return NoUnitP; } // Find the best area... // The target which provide the best bad/good ratio is choosen... best_cost = -1; best = NoUnitP; for (i = 0; i < n; ++i) { if (!table[i]) { continue; } dest = table[i]; dtype = dest->Type; // put in x-y the real point which will be hit... // (only meaningful when dtype->TileWidth > 1) if (u->X < dest->X) { x = dest->X; } else if (u->X > dest->X + dtype->TileWidth - 1) { x = dest->X + dtype->TileWidth - 1; } else { x = u->X; } if (u->Y < dest->Y) { y = dest->Y; } else if (u->Y > dest->Y + dtype->TileHeight - 1) { y = dest->Y + dtype->TileHeight - 1; } else { y = u->Y; } // Make x,y relative to u->x... x = x - u->X + missile_range + 1; y = y - u->Y + missile_range + 1; sbad = 0; sgood = 0; for (yy = -(type->Missile.Missile->Range - 1); yy <= type->Missile.Missile->Range - 1; ++yy) { for (xx = -(type->Missile.Missile->Range - 1); xx <= type->Missile.Missile->Range - 1; ++xx) { if ((x + xx < 0) || (y + yy < 0) || ((x + xx) >= 2 * missile_range + 1) || ((y + yy) >= 2 * missile_range + 1)) { continue; } sbad += bad[y + yy][x + xx]; sgood += good[y + yy][x + xx]; if (!yy && !xx) { sbad += bad[y + yy][x + xx]; sgood += good[y + yy][x + xx]; } } } // don't consider small damages... if (sgood < 20) { sgood = 20; } cost = sbad / sgood; if (cost > best_cost) { best_cost = cost; best = dest; } } return best; }
bool TacticalAI::ProcessOrders() { if (ship_ai) ship_ai->ClearPatrol(); if (orders && orders->EMCON() > 0) { int desired_emcon = orders->EMCON(); if (ship_ai && (ship_ai->GetThreat() || ship_ai->GetThreatMissile())) desired_emcon = 3; if (ship->GetEMCON() != desired_emcon) ship->SetEMCON(desired_emcon); } if (orders && orders->Action()) { switch (orders->Action()) { case RadioMessage::ATTACK: case RadioMessage::BRACKET: case RadioMessage::IDENTIFY: { bool tgt_ok = false; SimObject* tgt = orders->GetTarget(); if (tgt && tgt->Type() == SimObject::SIM_SHIP) { Ship* tgt_ship = (Ship*) tgt; if (CanTarget(tgt_ship)) { roe = DIRECTED; SelectTargetDirected((Ship*) tgt); ship_ai->SetBracket(orders->Action() == RadioMessage::BRACKET); ship_ai->SetIdentify(orders->Action() == RadioMessage::IDENTIFY); ship_ai->SetNavPoint(0); tgt_ok = true; } } if (!tgt_ok) ClearRadioOrders(); } break; case RadioMessage::ESCORT: case RadioMessage::COVER_ME: { SimObject* tgt = orders->GetTarget(); if (tgt && tgt->Type() == SimObject::SIM_SHIP) { roe = DEFENSIVE; ship_ai->SetWard((Ship*) tgt); ship_ai->SetNavPoint(0); } else { ClearRadioOrders(); } } break; case RadioMessage::WEP_FREE: roe = AGRESSIVE; ship_ai->DropTarget(0.1); break; case RadioMessage::WEP_HOLD: case RadioMessage::FORM_UP: roe = NONE; ship_ai->DropTarget(5); break; case RadioMessage::MOVE_PATROL: roe = SELF_DEFENSIVE; ship_ai->SetPatrol(orders->Location()); ship_ai->SetNavPoint(0); ship_ai->DropTarget(Random(5, 10)); break; case RadioMessage::RTB: case RadioMessage::DOCK_WITH: roe = NONE; ship_ai->DropTarget(10); if (!ship->GetInbound()) { RadioMessage* msg = 0; Ship* controller = ship->GetController(); if (orders->Action() == RadioMessage::DOCK_WITH && orders->GetTarget()) { controller = (Ship*) orders->GetTarget(); } if (!controller) { Element* elem = ship->GetElement(); if (elem && elem->GetCommander()) { Element* cmdr = elem->GetCommander(); controller = cmdr->GetShip(1); } } if (controller && controller->GetHangar() && controller->GetHangar()->CanStow(ship)) { SimRegion* self_rgn = ship->GetRegion(); SimRegion* rtb_rgn = controller->GetRegion(); if (self_rgn == rtb_rgn) { double range = Point(controller->Location() - ship->Location()).length(); if (range < 50e3) { msg = new(__FILE__,__LINE__) RadioMessage(controller, ship, RadioMessage::CALL_INBOUND); RadioTraffic::Transmit(msg); } } } else { ship->ClearRadioOrders(); } ship_ai->SetNavPoint(0); } break; case RadioMessage::QUANTUM_TO: case RadioMessage::FARCAST_TO: roe = NONE; ship_ai->DropTarget(10); break; } action = orders->Action(); return true; } // if we had an action before, this must be a "cancel orders" else if (action) { ClearRadioOrders(); } return false; }
/** ** Work for missile hit. */ void Missile::MissileHit(CUnit *unit) { const MissileType &mtype = *this->Type; if (mtype.ImpactSound.Sound) { PlayMissileSound(*this, mtype.ImpactSound.Sound); } const PixelPos pixelPos = this->position + this->Type->size / 2; // // The impact generates a new missile. // if (mtype.Impact.empty() == false) { for (std::vector<MissileConfig *>::const_iterator it = mtype.Impact.begin(); it != mtype.Impact.end(); ++it) { const MissileConfig &mc = **it; Missile *impact = MakeMissile(*mc.Missile, pixelPos, pixelPos); if (impact && impact->Type->Damage) { impact->SourceUnit = this->SourceUnit; } } } if (mtype.ImpactParticle) { mtype.ImpactParticle->pushPreamble(); mtype.ImpactParticle->pushInteger(pixelPos.x); mtype.ImpactParticle->pushInteger(pixelPos.y); mtype.ImpactParticle->run(); } if (!this->SourceUnit) { // no owner - green-cross ... return; } const Vec2i pos = Map.MapPixelPosToTilePos(pixelPos); if (!Map.Info.IsPointOnMap(pos)) { // FIXME: this should handled by caller? DebugPrint("Missile gone outside of map!\n"); return; // outside the map. } // // Choose correct goal. // if (unit) { if (unit->Destroyed) { return; } if (mtype.Pierce && mtype.PierceOnce) { if (IsPiercedUnit(*this, *unit)) { return; } else { PiercedUnits.insert(this->PiercedUnits.begin(), unit); } } MissileHitsGoal(*this, *unit, 1); return; } if (!mtype.Range) { if (this->TargetUnit && (mtype.FriendlyFire == false || this->TargetUnit->Player->Index != this->SourceUnit->Player->Index)) { // // Missiles without range only hits the goal always. // CUnit &goal = *this->TargetUnit; if (mtype.Pierce && mtype.PierceOnce) { if (IsPiercedUnit(*this, goal)) { return; } else { PiercedUnits.insert(this->PiercedUnits.begin(), &goal); } } if (goal.Destroyed) { this->TargetUnit = NULL; return; } MissileHitsGoal(*this, goal, 1); return; } MissileHitsWall(*this, pos, 1); return; } { // // Hits all units in range. // const Vec2i range(mtype.Range - 1, mtype.Range - 1); std::vector<CUnit *> table; Select(pos - range, pos + range, table); Assert(this->SourceUnit != NULL); for (size_t i = 0; i != table.size(); ++i) { CUnit &goal = *table[i]; // // Can the unit attack this unit-type? // NOTE: perhaps this should be come a property of the missile. // Also check CorrectSphashDamage so land explosions can't hit the air units // if (CanTarget(*this->SourceUnit->Type, *goal.Type) && (mtype.FriendlyFire == false || goal.Player->Index != this->SourceUnit->Player->Index)) { bool shouldHit = true; if (mtype.Pierce && mtype.PierceOnce) { if (IsPiercedUnit(*this, goal)) { shouldHit = false; } else { PiercedUnits.insert(this->PiercedUnits.begin(), &goal); } } if (mtype.CorrectSphashDamage == true) { bool isPositionSpell = false; if (this->TargetUnit == NULL && this->SourceUnit->CurrentAction() == UnitActionSpellCast) { const COrder_SpellCast &order = *static_cast<COrder_SpellCast *>(this->SourceUnit->CurrentOrder()); if (order.GetSpell().Target == TargetPosition) { isPositionSpell = true; } } if (isPositionSpell || this->SourceUnit->CurrentAction() == UnitActionAttackGround) { if (goal.Type->UnitType != this->SourceUnit->Type->UnitType) { shouldHit = false; } } else { if (this->TargetUnit == NULL || goal.Type->UnitType != this->TargetUnit->Type->UnitType) { shouldHit = false; } } } if (shouldHit) { int splash = goal.MapDistanceTo(pos); if (splash) { splash *= mtype.SplashFactor; } else { splash = 1; } MissileHitsGoal(*this, goal, splash); } } } } // Missile hits ground. const Vec2i offset(mtype.Range, mtype.Range); const Vec2i posmin = pos - offset; for (int i = mtype.Range * 2; --i;) { for (int j = mtype.Range * 2; --j;) { const Vec2i posIt(posmin.x + i, posmin.y + j); if (Map.Info.IsPointOnMap(posIt)) { int d = Distance(pos, posIt); d *= mtype.SplashFactor; if (d == 0) { d = 1; } MissileHitsWall(*this, posIt, d); } } } }
/** ** Work for missile hit. ** ** @param missile Missile reaching end-point. */ void MissileHit(Missile *missile) { CUnit *goal; int x; int y; CUnit *table[UnitMax]; int n; int i; int splash; if (missile->Type->ImpactSound.Sound) { PlayMissileSound(missile, missile->Type->ImpactSound.Sound); } x = missile->X + missile->Type->Width / 2; y = missile->Y + missile->Type->Height / 2; // // The impact generates a new missile. // if (missile->Type->ImpactMissile) { MakeMissile(missile->Type->ImpactMissile, x, y, x, y); } if (missile->Type->ImpactParticle) { missile->Type->ImpactParticle->pushPreamble(); missile->Type->ImpactParticle->pushInteger(x); missile->Type->ImpactParticle->pushInteger(y); missile->Type->ImpactParticle->run(); } if (!missile->SourceUnit) { // no owner - green-cross ... return; } x /= TileSizeX; y /= TileSizeY; if (x < 0 || y < 0 || x >= Map.Info.MapWidth || y >= Map.Info.MapHeight) { // FIXME: this should handled by caller? DebugPrint("Missile gone outside of map!\n"); return; // outside the map. } // // Choose correct goal. // if (!missile->Type->Range) { if (missile->TargetUnit) { // // Missiles without range only hits the goal always. // goal = missile->TargetUnit; if (goal->Destroyed) { // Destroyed goal->RefsDecrease(); missile->TargetUnit = NoUnitP; return; } MissileHitsGoal(missile, goal, 1); return; } return; } // // Hits all units in range. // i = missile->Type->Range; n = UnitCache.Select(x - i + 1, y - i + 1, x + i, y + i, table, UnitMax); Assert(missile->SourceUnit != NULL); for (i = 0; i < n; ++i) { goal = table[i]; // // Can the unit attack the this unit-type? // NOTE: perhaps this should be come a property of the missile. // if (CanTarget(missile->SourceUnit->Type, goal->Type)) { splash = MapDistanceToUnit(x, y, goal); if (splash) { splash *= missile->Type->SplashFactor; } else { splash = 1; } MissileHitsGoal(missile, goal, splash); } } }