void AI::Refuel(Ship &ship, Command &command) { if(ship.GetParent() && ship.GetParent()->GetTargetPlanet()) ship.SetTargetPlanet(ship.GetParent()->GetTargetPlanet()); else if(!ship.GetTargetPlanet()) { double closest = numeric_limits<double>::infinity(); for(const StellarObject &object : ship.GetSystem()->Objects()) if(object.GetPlanet() && object.GetPlanet()->HasSpaceport()) { double distance = ship.Position().Distance(object.Position()); if(distance < closest) { ship.SetTargetPlanet(&object); closest = distance; } } } if(ship.GetTargetPlanet()) { MoveToPlanet(ship, command); command |= Command::LAND; } }
void AI::MoveEscort(Ship &ship, Command &command) { const Ship &parent = *ship.GetParent(); bool isStaying = ship.GetPersonality().IsStaying(); // If an escort is out of fuel, they should refuel without waiting for the // "parent" to land (because the parent may not be planning on landing). if(ship.Attributes().Get("fuel capacity") && !ship.JumpsRemaining() && ship.GetSystem()->IsInhabited()) Refuel(ship, command); else if(ship.GetSystem() != parent.GetSystem() && !isStaying) { DistanceMap distance(ship, parent.GetSystem()); const System *system = distance.Route(ship.GetSystem()); ship.SetTargetSystem(system); if(!system || (ship.GetSystem()->IsInhabited() && !system->IsInhabited() && ship.JumpsRemaining() == 1)) Refuel(ship, command); else { PrepareForHyperspace(ship, command); command |= Command::JUMP; } } else if(parent.Commands().Has(Command::LAND) && parent.GetTargetPlanet()) { ship.SetTargetPlanet(parent.GetTargetPlanet()); MoveToPlanet(ship, command); if(parent.IsLanding() || parent.CanLand()) command |= Command::LAND; } else if(parent.Commands().Has(Command::BOARD) && parent.GetTargetShip().get() == &ship) Stop(ship, command); else if(parent.Commands().Has(Command::JUMP) && parent.GetTargetSystem() && !isStaying) { DistanceMap distance(ship, parent.GetTargetSystem()); const System *dest = distance.Route(ship.GetSystem()); ship.SetTargetSystem(dest); if(!dest || (dest != parent.GetTargetSystem() && !dest->IsInhabited() && ship.JumpsRemaining() == 1)) Refuel(ship, command); else { PrepareForHyperspace(ship, command); if(parent.IsEnteringHyperspace() || parent.CheckHyperspace()) command |= Command::JUMP; } } else CircleAround(ship, command, parent); }
void Engine::CalculateStep() { FrameTimer loadTimer; // Clear the list of objects to draw. draw[calcTickTock].Clear(step); radar[calcTickTock].Clear(); if(!player.GetSystem()) return; // Now, all the ships must decide what they are doing next. ai.Step(ships, player); const Ship *flagship = player.Flagship(); bool wasHyperspacing = (flagship && flagship->IsEnteringHyperspace()); // Now, move all the ships. We must finish moving all of them before any of // them fire, or their turrets will be targeting where a given ship was // instead of where it is now. This is also where ships get deleted, and // where they may create explosions if they are dying. for(auto it = ships.begin(); it != ships.end(); ) { int hyperspaceType = (*it)->HyperspaceType(); bool wasHere = (flagship && (*it)->GetSystem() == flagship->GetSystem()); bool wasHyperspacing = (*it)->IsHyperspacing(); // Give the ship the list of effects so that it can draw explosions, // ion sparks, jump drive flashes, etc. if(!(*it)->Move(effects, flotsam)) { // If Move() returns false, it means the ship should be removed from // play. That may be because it was destroyed, because it is an // ordinary ship that has been out of system for long enough to be // "forgotten," or because it is a fighter that just docked with its // mothership. Report it destroyed if that's really what happened: if((*it)->IsDestroyed()) eventQueue.emplace_back(nullptr, *it, ShipEvent::DESTROY); it = ships.erase(it); } else { if(&**it != flagship) { // Did this ship just begin hyperspacing? if(wasHere && !wasHyperspacing && (*it)->IsHyperspacing()) Audio::Play( Audio::Get(hyperspaceType >= 200 ? "jump out" : "hyperdrive out"), (*it)->Position()); // Did this ship just jump into the player's system? if(!wasHere && flagship && (*it)->GetSystem() == flagship->GetSystem()) Audio::Play( Audio::Get(hyperspaceType >= 200 ? "jump in" : "hyperdrive in"), (*it)->Position()); } // Boarding: bool autoPlunder = !(*it)->GetGovernment()->IsPlayer(); shared_ptr<Ship> victim = (*it)->Board(autoPlunder); if(victim) eventQueue.emplace_back(*it, victim, (*it)->GetGovernment()->IsEnemy(victim->GetGovernment()) ? ShipEvent::BOARD : ShipEvent::ASSIST); ++it; } } if(!wasHyperspacing && flagship && flagship->IsEnteringHyperspace()) Audio::Play(Audio::Get(flagship->HyperspaceType() >= 200 ? "jump drive" : "hyperdrive")); if(flagship && player.GetSystem() != flagship->GetSystem()) { // Wormhole travel: if(!wasHyperspacing) for(const auto &it : player.GetSystem()->Objects()) if(it.GetPlanet() && it.GetPlanet()->IsWormhole() && it.GetPlanet()->WormholeDestination(player.GetSystem()) == flagship->GetSystem()) player.Visit(it.GetPlanet()); doFlash = Preferences::Has("Show hyperspace flash"); player.SetSystem(flagship->GetSystem()); EnterSystem(); } // Draw the planets. Point newCenter = center; Point newCenterVelocity; if(flagship) { newCenter = flagship->Position(); newCenterVelocity = flagship->Velocity(); } else doClick = false; draw[calcTickTock].SetCenter(newCenter, newCenterVelocity); radar[calcTickTock].SetCenter(newCenter); for(const StellarObject &object : player.GetSystem()->Objects()) if(object.HasSprite()) { // Don't apply motion blur to very large planets and stars. if(object.Width() >= 280.) draw[calcTickTock].AddUnblurred(object); else draw[calcTickTock].Add(object); double r = max(2., object.Radius() * .03 + .5); radar[calcTickTock].Add(RadarType(object), object.Position(), r, r - 1.); if(object.GetPlanet()) object.GetPlanet()->DeployDefense(ships); Point position = object.Position() - newCenter; if(doClick && object.GetPlanet() && (clickPoint - position).Length() < object.Radius()) { if(&object == player.Flagship()->GetTargetPlanet()) { if(!object.GetPlanet()->CanLand()) Messages::Add("The authorities on " + object.GetPlanet()->Name() + " refuse to let you land."); else { clickCommands |= Command::LAND; Messages::Add("Landing on " + object.GetPlanet()->Name() + "."); } } else player.Flagship()->SetTargetPlanet(&object); } } // Add all neighboring systems to the radar. const System *targetSystem = flagship ? flagship->GetTargetSystem() : nullptr; const vector<const System *> &links = (flagship && flagship->Attributes().Get("jump drive")) ? player.GetSystem()->Neighbors() : player.GetSystem()->Links(); for(const System *system : links) radar[calcTickTock].AddPointer( (system == targetSystem) ? Radar::SPECIAL : Radar::INACTIVE, system->Position() - player.GetSystem()->Position()); // Now that the planets have been drawn, we can draw the asteroids on top // of them. This could be done later, as long as it is done before the // collision detection. asteroids.Step(); asteroids.Draw(draw[calcTickTock], newCenter); // Move existing projectiles. Do this before ships fire, which will create // new projectiles, since those should just stay where they are created for // this turn. This is also where projectiles get deleted, which may also // result in a "die" effect or a sub-munition being created. We could not // move the projectiles before this because some of them are homing and need // to know the current positions of the ships. list<Projectile> newProjectiles; for(auto it = projectiles.begin(); it != projectiles.end(); ) { if(!it->Move(effects)) { it->MakeSubmunitions(newProjectiles); it = projectiles.erase(it); } else ++it; } projectiles.splice(projectiles.end(), newProjectiles); // Move the flotsam, which should be drawn underneath the ships. for(auto it = flotsam.begin(); it != flotsam.end(); ) { if(!it->Move(effects)) { it = flotsam.erase(it); continue; } Ship *collector = nullptr; for(const shared_ptr<Ship> &ship : ships) { if(ship->GetSystem() != player.GetSystem() || ship->CannotAct()) continue; if(ship.get() == it->Source() || ship->Cargo().Free() < it->UnitSize()) continue; const Mask &mask = ship->GetMask(step); if(mask.Contains(it->Position() - ship->Position(), ship->Facing())) { collector = ship.get(); break; } } if(collector) { string name; if(collector->IsYours()) { if(collector->GetParent()) name = "Your ship \"" + collector->Name() + "\" picked up "; else name = "You picked up "; } string commodity; int amount = 0; if(it->OutfitType()) { amount = collector->Cargo().Add(it->OutfitType(), it->Count()); if(!name.empty()) { if(it->OutfitType()->Get("installable") < 0.) commodity = it->OutfitType()->Name(); else Messages::Add(name + Format::Number(amount) + " " + it->OutfitType()->Name() + (amount == 1 ? "." : "s.")); } } else { amount = collector->Cargo().Add(it->CommodityType(), it->Count()); if(!name.empty()) commodity = it->CommodityType(); } if(!commodity.empty()) Messages::Add(name + (amount == 1 ? "a ton" : Format::Number(amount) + " tons") + " of " + Format::LowerCase(commodity) + "."); it = flotsam.erase(it); continue; } // Draw this flotsam. draw[calcTickTock].Add(*it); ++it; } // Keep track of the relative strength of each government in this system. Do // not add more ships to make a winning team even stronger. This is mostly // to avoid having the player get mobbed by pirates, say, if they hang out // in one system for too long. map<const Government *, int64_t> strength; // Now, ships fire new projectiles, which includes launching fighters. If an // anti-missile system is ready to fire, it does not actually fire unless a // missile is detected in range during collision detection, below. vector<Ship *> hasAntiMissile; double clickRange = 50.; const Ship *previousTarget = nullptr; const Ship *clickTarget = nullptr; if(player.Flagship() && player.Flagship()->GetTargetShip()) previousTarget = &*player.Flagship()->GetTargetShip(); bool showFlagship = false; bool hasHostiles = false; for(shared_ptr<Ship> &ship : ships) if(ship->GetSystem() == player.GetSystem()) { strength[ship->GetGovernment()] += ship->Cost(); // Note: if a ship "fires" a fighter, that fighter was already in // existence and under the control of the same AI as the ship, but // its system was null to mark that it was not active. ship->Launch(ships); if(ship->Fire(projectiles, effects)) hasAntiMissile.push_back(ship.get()); int scan = ship->Scan(); if(scan) { shared_ptr<Ship> target = ship->GetTargetShip(); if(target && target->IsTargetable()) eventQueue.emplace_back(ship, target, scan); } // This is a good opportunity to draw all the ships in system. if(!ship->HasSprite()) continue; // Draw the flagship separately, on top of everything else. if(ship.get() != flagship) { AddSprites(*ship); if(ship->IsThrusting()) { for(const auto &it : ship->Attributes().FlareSounds()) if(it.second > 0) Audio::Play(it.first, ship->Position()); } } else showFlagship = true; // Do not show cloaked ships on the radar, except the player's ships. bool isPlayer = ship->GetGovernment()->IsPlayer(); if(ship->Cloaking() == 1. && !isPlayer) continue; if(doClick && &*ship != player.Flagship() && ship->IsTargetable()) { Point position = ship->Position() - newCenter; const Mask &mask = ship->GetMask(step); double range = mask.Range(clickPoint - position, ship->Facing()); if(range <= clickRange) { clickRange = range; clickTarget = ship.get(); player.Flagship()->SetTargetShip(ship); // If we've found an enemy within the click zone, favor // targeting it rather than any other ship. Otherwise, keep // checking for hits because another ship might be an enemy. if(!range && ship->GetGovernment()->IsEnemy()) doClick = false; } } double size = sqrt(ship->Width() + ship->Height()) * .14 + .5; bool isYourTarget = (flagship && ship == flagship->GetTargetShip()); int type = RadarType(*ship); hasHostiles |= (type == Radar::HOSTILE); radar[calcTickTock].Add(isYourTarget ? Radar::SPECIAL : type, ship->Position(), size); } if(flagship && showFlagship) { AddSprites(*flagship); if(flagship->IsThrusting()) { for(const auto &it : flagship->Attributes().FlareSounds()) if(it.second > 0) Audio::Play(it.first); } } if(clickTarget && clickTarget == previousTarget) clickCommands |= Command::BOARD; if(alarmTime) --alarmTime; else if(hasHostiles && !hadHostiles) { if(Preferences::Has("Warning siren")) Audio::Play(Audio::Get("alarm")); alarmTime = 180; hadHostiles = true; } else if(!hasHostiles) hadHostiles = false; // Collision detection: if(grudgeTime) --grudgeTime; for(Projectile &projectile : projectiles) { // The asteroids can collide with projectiles, the same as any other // object. If the asteroid turns out to be closer than the ship, it // shields the ship (unless the projectile has a blast radius). Point hitVelocity; double closestHit = 0.; shared_ptr<Ship> hit; const Government *gov = projectile.GetGovernment(); // If this "projectile" is a ship explosion, it always explodes. if(gov) { closestHit = asteroids.Collide(projectile, step, &hitVelocity); // Projectiles can only collide with ships that are in the current // system and are not landing, and that are hostile to this projectile. for(shared_ptr<Ship> &ship : ships) if(ship->GetSystem() == player.GetSystem() && !ship->IsLanding() && ship->Cloaking() < 1.) { if(ship.get() != projectile.Target() && !gov->IsEnemy(ship->GetGovernment())) continue; // This returns a value of 0 if the projectile has a trigger // radius and the ship is within it. double range = projectile.CheckCollision(*ship, step); if(range < closestHit) { closestHit = range; hit = ship; hitVelocity = ship->Velocity(); } } } if(closestHit < 1.) { // Create the explosion the given distance along the projectile's // motion path for this step. projectile.Explode(effects, closestHit, hitVelocity); // If this projectile has a blast radius, find all ships within its // radius. Otherwise, only one is damaged. if(projectile.HasBlastRadius()) { // Even friendly ships can be hit by the blast. for(shared_ptr<Ship> &ship : ships) if(ship->GetSystem() == player.GetSystem() && ship->Zoom() == 1.) if(projectile.InBlastRadius(*ship, step, closestHit)) { int eventType = ship->TakeDamage(projectile, ship != hit); if(eventType) eventQueue.emplace_back( projectile.GetGovernment(), ship, eventType); } } else if(hit) { int eventType = hit->TakeDamage(projectile); if(eventType) eventQueue.emplace_back( projectile.GetGovernment(), hit, eventType); } if(hit) DoGrudge(hit, projectile.GetGovernment()); } else if(projectile.MissileStrength()) { bool isEnemy = projectile.GetGovernment() && projectile.GetGovernment()->IsEnemy(); radar[calcTickTock].Add( isEnemy ? Radar::SPECIAL : Radar::INACTIVE, projectile.Position(), 1.); // If the projectile did not hit anything, give the anti-missile // systems a chance to shoot it down. for(Ship *ship : hasAntiMissile) if(ship == projectile.Target() || gov->IsEnemy(ship->GetGovernment()) || ship->GetGovernment()->IsEnemy(gov)) if(ship->FireAntiMissile(projectile, effects)) { projectile.Kill(); break; } } else if(projectile.HasBlastRadius()) radar[calcTickTock].Add(Radar::SPECIAL, projectile.Position(), 1.8); // Now, we can draw the projectile. The motion blur should be reduced // depending on how much motion blur is in the sprite itself: double innateVelocity = 2. * projectile.GetWeapon().Velocity(); Point relativeVelocity = projectile.Velocity() - projectile.Unit() * innateVelocity; draw[calcTickTock].AddProjectile(projectile, relativeVelocity, closestHit); } // Finally, draw all the effects, and then move them (because their motion // is not dependent on anything else, and this way we do all the work on // them in a single place. for(auto it = effects.begin(); it != effects.end(); ) { draw[calcTickTock].AddUnblurred(*it); if(!it->Move()) it = effects.erase(it); else ++it; } // Add incoming ships. for(const System::FleetProbability &fleet : player.GetSystem()->Fleets()) if(!Random::Int(fleet.Period())) { const Government *gov = fleet.Get()->GetGovernment(); if(!gov) continue; int64_t enemyStrength = 0; for(const auto &it : strength) if(gov->IsEnemy(it.first)) enemyStrength += it.second; if(enemyStrength && strength[gov] > 2 * enemyStrength) continue; fleet.Get()->Enter(*player.GetSystem(), ships); } if(!Random::Int(36000) && !player.GetSystem()->Links().empty()) { // Loop through all persons once to see if there are any who can enter // this system. int sum = 0; for(const auto &it : GameData::Persons()) sum += it.second.Frequency(player.GetSystem()); if(sum) { // Adjustment factor: special persons will appear once every ten // minutes, but much less frequently if the game only specifies a // few of them. This way, they will become more common as I add // more, without needing to change the 10-minute constant above. sum = Random::Int(sum + 1000); for(const auto &it : GameData::Persons()) { const Person &person = it.second; sum -= person.Frequency(player.GetSystem()); if(sum < 0) { shared_ptr<Ship> ship = person.GetShip(); ship->Recharge(); ship->SetName(it.first); ship->SetGovernment(person.GetGovernment()); ship->SetPersonality(person.GetPersonality()); ship->SetHail(person.GetHail()); Fleet::Enter(*player.GetSystem(), *ship); ships.push_front(ship); break; } } } } // Occasionally have some ship hail you. if(!Random::Int(600) && !ships.empty()) { shared_ptr<Ship> source; unsigned i = Random::Int(ships.size()); for(const shared_ptr<Ship> &it : ships) if(!i--) { source = it; break; } if(source->GetGovernment() && !source->GetGovernment()->IsPlayer() && !source->IsDisabled() && source->Crew()) { string message = source->GetHail(); if(!message.empty() && source->GetSystem() == player.GetSystem()) Messages::Add(source->GetGovernment()->GetName() + " ship \"" + source->Name() + "\": " + message); } } // A mouse click should only be active for a single step. doClick = false; // Keep track of how much of the CPU time we are using. loadSum += loadTimer.Time(); if(++loadCount == 60) { load = loadSum; loadSum = 0.; loadCount = 0; } }
void AI::MoveIndependent(Ship &ship, Command &command) const { if(ship.Position().Length() >= 10000.) { MoveTo(ship, command, Point(), 40., .8); return; } shared_ptr<const Ship> target = ship.GetTargetShip(); if(target && (ship.GetGovernment()->IsEnemy(target->GetGovernment()) || (ship.IsYours() && target == sharedTarget.lock()))) { bool shouldBoard = ship.Cargo().Free() && ship.GetPersonality().Plunders(); bool hasBoarded = Has(ship, target, ShipEvent::BOARD); if(shouldBoard && target->IsDisabled() && !hasBoarded) { if(ship.IsBoarding()) return; MoveTo(ship, command, target->Position(), 40., .8); command |= Command::BOARD; } else Attack(ship, command, *target); return; } else if(target) { bool cargoScan = ship.Attributes().Get("cargo scan"); bool outfitScan = ship.Attributes().Get("outfit scan"); if((!cargoScan || Has(ship, target, ShipEvent::SCAN_CARGO)) && (!outfitScan || Has(ship, target, ShipEvent::SCAN_OUTFITS))) target.reset(); else { CircleAround(ship, command, *target); if(!ship.GetGovernment()->IsPlayer()) command |= Command::SCAN; } return; } // If this ship is moving independently because it has a target, not because // it has no parent, don't let it make travel plans. if(ship.GetParent() && !ship.GetPersonality().IsStaying()) return; if(!ship.GetTargetSystem() && !ship.GetTargetPlanet() && !ship.GetPersonality().IsStaying()) { int jumps = ship.JumpsRemaining(); // Each destination system has an average priority of 10. // If you only have one jump left, landing should be high priority. int planetWeight = jumps ? (1 + 40 / jumps) : 1; vector<int> systemWeights; int totalWeight = 0; const vector<const System *> &links = ship.Attributes().Get("jump drive") ? ship.GetSystem()->Neighbors() : ship.GetSystem()->Links(); if(jumps) { for(const System *link : links) { // Prefer systems in the direction we're facing. Point direction = link->Position() - ship.GetSystem()->Position(); int weight = static_cast<int>( 11. + 10. * ship.Facing().Unit().Dot(direction.Unit())); systemWeights.push_back(weight); totalWeight += weight; } } int systemTotalWeight = totalWeight; // Anywhere you can land that has a port has the same weight. Ships will // not land anywhere without a port. vector<const StellarObject *> planets; for(const StellarObject &object : ship.GetSystem()->Objects()) if(object.GetPlanet() && object.GetPlanet()->HasSpaceport() && object.GetPlanet()->CanLand(ship)) { planets.push_back(&object); totalWeight += planetWeight; } if(!totalWeight) return; int choice = Random::Int(totalWeight); if(choice < systemTotalWeight) { for(unsigned i = 0; i < systemWeights.size(); ++i) { choice -= systemWeights[i]; if(choice < 0) { ship.SetTargetSystem(links[i]); break; } } } else { choice = (choice - systemTotalWeight) / planetWeight; ship.SetTargetPlanet(planets[choice]); } } if(ship.GetTargetSystem()) { PrepareForHyperspace(ship, command); bool mustWait = false; for(const weak_ptr<const Ship> &escort : ship.GetEscorts()) { shared_ptr<const Ship> locked = escort.lock(); mustWait = locked && locked->CanBeCarried(); } if(!mustWait) command |= Command::JUMP; } else if(ship.GetTargetPlanet()) { MoveToPlanet(ship, command); if(!ship.GetPersonality().IsStaying()) command |= Command::LAND; else if(ship.Position().Distance(ship.GetTargetPlanet()->Position()) < 100.) ship.SetTargetPlanet(nullptr); } else if(ship.GetPersonality().IsStaying() && ship.GetSystem()->Objects().size()) { unsigned i = Random::Int(ship.GetSystem()->Objects().size()); ship.SetTargetPlanet(&ship.GetSystem()->Objects()[i]); } }
// Pick a new target for the given ship. shared_ptr<Ship> AI::FindTarget(const Ship &ship, const list<shared_ptr<Ship>> &ships) const { // If this ship has no government, it has no enemies. shared_ptr<Ship> target; const Government *gov = ship.GetGovernment(); if(!gov) return target; bool isPlayerEscort = ship.IsYours(); if(isPlayerEscort) { shared_ptr<Ship> locked = sharedTarget.lock(); if(locked && locked->GetSystem() == ship.GetSystem() && !locked->IsDisabled()) return locked; } // If this ship is not armed, do not make it fight. double minRange = numeric_limits<double>::infinity(); double maxRange = 0.; for(const Armament::Weapon &weapon : ship.Weapons()) if(weapon.GetOutfit() && !weapon.IsAntiMissile()) { minRange = min(minRange, weapon.GetOutfit()->Range()); maxRange = max(maxRange, weapon.GetOutfit()->Range()); } if(!maxRange) return target; shared_ptr<Ship> oldTarget = ship.GetTargetShip(); if(oldTarget && !oldTarget->IsTargetable()) oldTarget.reset(); shared_ptr<Ship> parentTarget; if(ship.GetParent()) parentTarget = ship.GetParent()->GetTargetShip(); if(parentTarget && !parentTarget->IsTargetable()) parentTarget.reset(); // Find the closest enemy ship (if there is one). If this ship is "heroic," // it will attack any ship in system. Otherwise, if all its weapons have a // range higher than 2000, it will engage ships up to 50% beyond its range. // If a ship has short range weapons and is not heroic, it will engage any // ship that is within 3000 of it. const Personality &person = ship.GetPersonality(); double closest = person.IsHeroic() ? numeric_limits<double>::infinity() : (minRange > 1000.) ? maxRange * 1.5 : 4000.; const System *system = ship.GetSystem(); bool isDisabled = false; for(const auto &it : ships) if(it->GetSystem() == system && it->IsTargetable() && gov->IsEnemy(it->GetGovernment())) { if(person.IsNemesis() && !it->GetGovernment()->IsPlayer()) continue; double range = it->Position().Distance(ship.Position()); // Preferentially focus on your previous target or your parent ship's // target if they are nearby. if(it == oldTarget || it == parentTarget) range -= 500.; // If your personality it to disable ships rather than destroy them, // never target disabled ships. if(it->IsDisabled() && !person.Plunders() && (person.Disables() || (!person.IsNemesis() && it != oldTarget))) continue; if(!person.Plunders()) range += 5000. * it->IsDisabled(); else { bool hasBoarded = Has(ship, it, ShipEvent::BOARD); // Don't plunder unless there are no "live" enemies nearby. range += 2000. * (2 * it->IsDisabled() - !hasBoarded); } // Focus on nearly dead ships. range += 500. * (it->Shields() + it->Hull()); if(range < closest) { closest = range; target = it; isDisabled = it->IsDisabled(); } } bool cargoScan = ship.Attributes().Get("cargo scan"); bool outfitScan = ship.Attributes().Get("outfit scan"); if(!target && (cargoScan || outfitScan) && !isPlayerEscort) { closest = numeric_limits<double>::infinity(); for(const auto &it : ships) if(it->GetSystem() == system && it->GetGovernment() != gov && it->IsTargetable()) { if((cargoScan && !Has(ship, it, ShipEvent::SCAN_CARGO)) || (outfitScan && !Has(ship, it, ShipEvent::SCAN_OUTFITS))) { double range = it->Position().Distance(ship.Position()); if(range < closest) { closest = range; target = it; } } } } // Run away if your target is not disabled and you are badly damaged. if(!isDisabled && (ship.GetPersonality().IsFleeing() || (ship.Shields() + ship.Hull() < 1. && !ship.GetPersonality().IsHeroic()))) target.reset(); return target; }