1 #include "Fleet.h"
2 
3 #include "Predicates.h"
4 #include "ShipDesign.h"
5 #include "Ship.h"
6 #include "System.h"
7 #include "Pathfinder.h"
8 #include "Universe.h"
9 #include "Enums.h"
10 #include "../util/i18n.h"
11 #include "../util/Logger.h"
12 #include "../util/ScopedTimer.h"
13 #include "../util/AppInterface.h"
14 #include "../Empire/Empire.h"
15 #include "../Empire/EmpireManager.h"
16 #include "../Empire/Supply.h"
17 
18 #include <boost/algorithm/cxx11/all_of.hpp>
19 
20 namespace {
21     const bool ALLOW_ALLIED_SUPPLY = true;
22 
23     const std::set<int> EMPTY_SET;
24     const double MAX_SHIP_SPEED = 500.0;        // max allowed speed of ship movement
25     const double FLEET_MOVEMENT_EPSILON = 0.1;  // how close a fleet needs to be to a system to have arrived in the system
26 
SystemHasNoVisibleStarlanes(int system_id,int empire_id)27     bool SystemHasNoVisibleStarlanes(int system_id, int empire_id)
28     { return !GetPathfinder()->SystemHasVisibleStarlanes(system_id, empire_id); }
29 
MoveFleetWithShips(Fleet & fleet,double x,double y)30     void MoveFleetWithShips(Fleet& fleet, double x, double y){
31         fleet.MoveTo(x, y);
32 
33         for (auto& ship : Objects().find<Ship>(fleet.ShipIDs())) {
34             ship->MoveTo(x, y);
35         }
36     }
37 
InsertFleetWithShips(Fleet & fleet,std::shared_ptr<System> & system)38     void InsertFleetWithShips(Fleet& fleet, std::shared_ptr<System>& system){
39         system->Insert(fleet.shared_from_this());
40 
41         for (auto& ship : Objects().find<Ship>(fleet.ShipIDs())) {
42             system->Insert(ship);
43         }
44     }
45 
46     /** Return \p full_route terminates at \p last_system or before the first
47         system not known to the \p empire_id. */
TruncateRouteToEndAtSystem(const std::list<int> & full_route,int empire_id,int last_system)48     std::list<int> TruncateRouteToEndAtSystem(const std::list<int>& full_route, int empire_id, int last_system) {
49 
50         if (full_route.empty() || (last_system == INVALID_OBJECT_ID))
51             return std::list<int>();
52 
53         auto visible_end_it = full_route.cend();
54         if (last_system != full_route.back()) {
55             visible_end_it = std::find(full_route.begin(), full_route.end(), last_system);
56 
57             // if requested last system not in route, do nothing
58             if (visible_end_it == full_route.end())
59                 return std::list<int>();
60 
61             ++visible_end_it;
62         }
63 
64 #if BOOST_VERSION >= 106000
65     using boost::placeholders::_1;
66 #endif
67 
68         // Remove any extra systems from the route after the apparent destination.
69         // SystemHasNoVisibleStarlanes determines if empire_id knows about the
70         // system and/or its starlanes.  It is enforced on the server in the
71         // visibility calculations that an owning empire knows about a) the
72         // system containing a fleet, b) the starlane on which a fleet is travelling
73         // and c) both systems terminating a starlane on which a fleet is travelling.
74         auto end_it = std::find_if(full_route.begin(), visible_end_it,
75                                    boost::bind(&SystemHasNoVisibleStarlanes, _1, empire_id));
76 
77         std::list<int> truncated_route;
78         std::copy(full_route.begin(), end_it, std::back_inserter(truncated_route));
79 
80         return truncated_route;
81     }
82 }
83 
84 // static(s)
85 const int Fleet::ETA_UNKNOWN =      (1 << 30);
86 const int Fleet::ETA_OUT_OF_RANGE = (1 << 30) - 1;
87 const int Fleet::ETA_NEVER =        (1 << 30) - 2;
88 
Fleet(const std::string & name,double x,double y,int owner)89 Fleet::Fleet(const std::string& name, double x, double y, int owner) :
90     UniverseObject(name, x, y)
91 {
92     UniverseObject::Init();
93     SetOwner(owner);
94 }
95 
Clone(int empire_id) const96 Fleet* Fleet::Clone(int empire_id) const {
97     Visibility vis = GetUniverse().GetObjectVisibilityByEmpire(this->ID(), empire_id);
98 
99     if (!(vis >= VIS_BASIC_VISIBILITY && vis <= VIS_FULL_VISIBILITY))
100         return nullptr;
101 
102     Fleet* retval = new Fleet();
103     retval->Copy(shared_from_this(), empire_id);
104     return retval;
105 }
106 
Copy(std::shared_ptr<const UniverseObject> copied_object,int empire_id)107 void Fleet::Copy(std::shared_ptr<const UniverseObject> copied_object, int empire_id) {
108     if (copied_object.get() == this)
109         return;
110     auto copied_fleet = std::dynamic_pointer_cast<const Fleet>(copied_object);
111     if (!copied_fleet) {
112         ErrorLogger() << "Fleet::Copy passed an object that wasn't a Fleet";
113         return;
114     }
115 
116     int copied_object_id = copied_object->ID();
117     Visibility vis = GetUniverse().GetObjectVisibilityByEmpire(copied_object_id, empire_id);
118     auto visible_specials = GetUniverse().GetObjectVisibleSpecialsByEmpire(copied_object_id, empire_id);
119 
120     UniverseObject::Copy(copied_object, vis, visible_specials);
121 
122     if (vis >= VIS_BASIC_VISIBILITY) {
123         m_ships =                         copied_fleet->VisibleContainedObjectIDs(empire_id);
124 
125         m_next_system = ((EmpireKnownObjects(empire_id).get<System>(copied_fleet->m_next_system))
126                          ? copied_fleet->m_next_system : INVALID_OBJECT_ID);
127         m_prev_system = ((EmpireKnownObjects(empire_id).get<System>(copied_fleet->m_prev_system))
128                          ? copied_fleet->m_prev_system : INVALID_OBJECT_ID);
129         m_arrived_this_turn =             copied_fleet->m_arrived_this_turn;
130         m_arrival_starlane =              copied_fleet->m_arrival_starlane;
131 
132         if (vis >= VIS_PARTIAL_VISIBILITY) {
133             m_aggressive =                copied_fleet->m_aggressive;
134             if (Unowned())
135                 m_name =                  copied_fleet->m_name;
136 
137             // Truncate the travel route to only systems known to empire_id
138             int moving_to = (vis >= VIS_FULL_VISIBILITY
139                              ? (!copied_fleet->m_travel_route.empty()
140                                 ? copied_fleet->m_travel_route.back()
141                                 : INVALID_OBJECT_ID)
142                              : m_next_system);
143 
144             m_travel_route = TruncateRouteToEndAtSystem(copied_fleet->m_travel_route, empire_id, moving_to);
145 
146 
147             if (vis >= VIS_FULL_VISIBILITY)
148                 m_ordered_given_to_empire_id =copied_fleet->m_ordered_given_to_empire_id;
149         }
150     }
151 }
152 
HostileToEmpire(int empire_id) const153 bool Fleet::HostileToEmpire(int empire_id) const
154 {
155     if (OwnedBy(empire_id))
156         return false;
157     return empire_id == ALL_EMPIRES || Unowned() ||
158            Empires().GetDiplomaticStatus(Owner(), empire_id) == DIPLO_WAR;
159 }
160 
ObjectType() const161 UniverseObjectType Fleet::ObjectType() const
162 { return OBJ_FLEET; }
163 
Dump(unsigned short ntabs) const164 std::string Fleet::Dump(unsigned short ntabs) const {
165     std::stringstream os;
166     os << UniverseObject::Dump(ntabs);
167     os << ( m_aggressive ? " agressive" : " passive")
168        << " cur system: " << SystemID()
169        << " moving to: " << FinalDestinationID()
170        << " prev system: " << m_prev_system
171        << " next system: " << m_next_system
172        << " arrival lane: " << m_arrival_starlane
173        << " ships: ";
174     for (auto it = m_ships.begin(); it != m_ships.end();) {
175         int ship_id = *it;
176         ++it;
177         os << ship_id << (it == m_ships.end() ? "" : ", ");
178     }
179     return os.str();
180 }
181 
ContainerObjectID() const182 int Fleet::ContainerObjectID() const
183 { return this->SystemID(); }
184 
ContainedObjectIDs() const185 const std::set<int>& Fleet::ContainedObjectIDs() const
186 { return m_ships; }
187 
Contains(int object_id) const188 bool Fleet::Contains(int object_id) const
189 { return object_id != INVALID_OBJECT_ID && m_ships.count(object_id); }
190 
ContainedBy(int object_id) const191 bool Fleet::ContainedBy(int object_id) const
192 { return object_id != INVALID_OBJECT_ID && this->SystemID() == object_id; }
193 
PublicName(int empire_id) const194 const std::string& Fleet::PublicName(int empire_id) const {
195     // Disclose real fleet name only to fleet owners.
196     if (GetUniverse().AllObjectsVisible() || empire_id == ALL_EMPIRES || OwnedBy(empire_id))
197         return Name();
198     else if (!Unowned())
199         return UserString("FW_FOREIGN_FLEET");
200     else if (Unowned() && HasMonsters())
201         return UserString("MONSTERS");
202     else if (Unowned() && GetVisibility(empire_id) > VIS_NO_VISIBILITY)
203         return UserString("FW_ROGUE_FLEET");
204     else
205         return UserString("OBJ_FLEET");
206 }
207 
MaxShipAgeInTurns() const208 int Fleet::MaxShipAgeInTurns() const {
209     if (m_ships.empty())
210         return INVALID_OBJECT_AGE;
211 
212     bool fleet_is_scrapped = true;
213     int retval = 0;
214     for (const auto& ship : Objects().find<Ship>(m_ships)) {
215         if (!ship || ship->OrderedScrapped())
216             continue;
217         if (ship->AgeInTurns() > retval)
218             retval = ship->AgeInTurns();
219         fleet_is_scrapped = false;
220     }
221 
222     if (fleet_is_scrapped)
223         retval = 0;
224 
225     return retval;
226 }
227 
TravelRoute() const228 const std::list<int>& Fleet::TravelRoute() const
229 { return m_travel_route; }
230 
MovePath(bool flag_blockades) const231 std::list<MovePathNode> Fleet::MovePath(bool flag_blockades /*= false*/) const
232 { return MovePath(TravelRoute(), flag_blockades); }
233 
MovePath(const std::list<int> & route,bool flag_blockades) const234 std::list<MovePathNode> Fleet::MovePath(const std::list<int>& route, bool flag_blockades /*= false*/) const {
235     std::list<MovePathNode> retval;
236 
237     if (route.empty())
238         return retval;                                      // nowhere to go => empty path
239     // if (route.size() == 1) do nothing special.  this fleet is probably on the starlane leading to
240     //                        its final destination.  normal looping to read destination should work fine
241     if (route.size() == 2 && route.front() == route.back())
242         return retval;                                      // nowhere to go => empty path
243     if (this->Speed() < FLEET_MOVEMENT_EPSILON) {
244         retval.emplace_back(this->X(), this->Y(), true, ETA_NEVER,
245                             this->SystemID(),
246                             INVALID_OBJECT_ID,
247                             INVALID_OBJECT_ID,
248                             false);
249         return retval;                                      // can't move => path is just this system with explanatory ETA
250     }
251 
252     float fuel =       Fuel();
253     float max_fuel =   MaxFuel();
254 
255     auto RouteNums = [route]() {
256         std::stringstream ss;
257         for (int waypoint : route)
258             ss << waypoint << " ";
259         return ss.str();
260     };
261     TraceLogger() << "Fleet::MovePath for Fleet " << this->Name() << " (" << this->ID()
262                   << ") fuel: " << fuel << " at sys id: " << this->SystemID() << "  route: "
263                   << RouteNums();
264 
265     // determine all systems where fleet(s) can be resupplied if fuel runs out
266     const Empire* empire = GetEmpire(this->Owner());
267     auto fleet_supplied_systems = GetSupplyManager().FleetSupplyableSystemIDs(this->Owner(), ALLOW_ALLIED_SUPPLY);
268     auto& unobstructed_systems = empire ? empire->SupplyUnobstructedSystems() : EMPTY_SET;
269 
270     // determine if, given fuel available and supplyable systems, fleet will ever be able to move
271     if (fuel < 1.0f &&
272         this->SystemID() != INVALID_OBJECT_ID &&
273         !fleet_supplied_systems.count(this->SystemID()))
274     {
275         retval.emplace_back(this->X(), this->Y(), true, ETA_OUT_OF_RANGE,
276                             this->SystemID(),
277                             INVALID_OBJECT_ID,
278                             INVALID_OBJECT_ID);
279         return retval;      // can't move => path is just this system with explanatory ETA
280     }
281 
282 
283     // get iterator pointing to std::shared_ptr<System> on route that is the first after where this fleet is currently.
284     // if this fleet is in a system, the iterator will point to the system after the current in the route
285     // if this fleet is not in a system, the iterator will point to the first system in the route
286     auto route_it = route.begin();
287     if (*route_it == SystemID())
288         ++route_it;     // first system in route is current system of this fleet.  skip to the next system
289     if (route_it == route.end())
290         return retval;  // current system of this fleet is the *only* system in the route.  path is empty.
291 
292 
293     // get current, previous and next systems of fleet
294     auto cur_system = Objects().get<System>(this->SystemID());          // may be 0
295     auto prev_system = Objects().get<System>(this->PreviousSystemID()); // may be 0 if this fleet is not moving or ordered to move
296     auto next_system = Objects().get<System>(*route_it);                // can't use this->NextSystemID() because this fleet may not be moving and may not have a next system. this might occur when a fleet is in a system, not ordered to move or ordered to move to a system, but a projected fleet move line is being calculated to a different system
297     if (!next_system) {
298         ErrorLogger() << "Fleet::MovePath couldn't get next system with id " << *route_it << " for fleet " << this->Name() << "(" << this->ID() << ")";
299         return retval;
300     }
301 
302     TraceLogger() << "Initial cur system: " << (cur_system ? cur_system->Name() : "(none)") << "(" << (cur_system ? cur_system->ID() : -1) << ")"
303                   << "  prev system: " << (prev_system ? prev_system->Name() : "(none)") << "(" << (prev_system ? prev_system->ID() : -1) << ")"
304                   << "  next system: " << (next_system ? next_system->Name() : "(none)") << "(" << (next_system ? next_system->ID() : -1) << ")";
305 
306 
307     bool is_post_blockade = false;
308     if (cur_system) {
309         //DebugLogger() << "Fleet::MovePath starting in system "<< SystemID();
310         if (flag_blockades) {
311             if (BlockadedAtSystem(cur_system->ID(), next_system->ID())) {
312                 // blockade debug logging
313                 TraceLogger() << "Fleet::MovePath checking blockade from "<< cur_system->ID() << " to "<< next_system->ID();
314                 TraceLogger() << "Fleet::MovePath finds system " << cur_system->Name() << " (" <<cur_system->ID()
315                               << ") blockaded for fleet " << this->Name();
316                 is_post_blockade = true;
317             } else {
318                 // blockade debug logging, but only for the more complex situations
319                 if (next_system->ID() != m_arrival_starlane && !unobstructed_systems.count(cur_system->ID())) {
320                     TraceLogger() << "Fleet::MovePath checking blockade from "<< cur_system->ID() << " to " << next_system->ID();
321                     TraceLogger() << "Fleet::MovePath finds system " << cur_system->Name() << " (" << cur_system->ID()
322                                   << ") NOT blockaded for fleet " << this->Name();
323                 }
324             }
325         }
326     }
327     // place initial position MovePathNode
328     retval.emplace_back(this->X(), this->Y(), false, 0,
329                         (cur_system  ? cur_system->ID()  : INVALID_OBJECT_ID),
330                         (prev_system ? prev_system->ID() : INVALID_OBJECT_ID),
331                         (next_system ? next_system->ID() : INVALID_OBJECT_ID),
332                         false);
333 
334 
335     const int       TOO_LONG =              100;            // limit on turns to simulate.  99 turns max keeps ETA to two digits, making UI work better
336     int             turns_taken =           1;
337     double          turn_dist_remaining =   this->Speed();  // additional distance that can be travelled in current turn of fleet movement being simulated
338     double          cur_x =                 this->X();
339     double          cur_y =                 this->Y();
340     double          next_x =                next_system->X();
341     double          next_y =                next_system->Y();
342 
343     // simulate fleet movement given known speed, starting position, fuel limit and systems on route
344     // need to populate retval with MovePathNodes that indicate the correct position, whether this
345     // fleet will end a turn at the node, the turns it will take to reach the node, and (when applicable)
346     // the current (if at a system), previous and next system IDs at which the fleet will be.  the
347     // previous and next system ids are needed to know what starlane a given node is located on, if any.
348     // nodes at systems don't need previous system ids to be valid, but should have next system ids
349     // valid so that when rendering starlanes using the returned move path, lines departing a system
350     // can be drawn on the correct side of the system icon
351 
352     while (turns_taken < TOO_LONG) {
353         // each loop iteration moves the current position to the next location of interest along the move
354         // path, and then adds a node at that position.
355 
356         if (cur_system)
357             TraceLogger() << "Starting iteration at system " << cur_system->Name() << " (" << cur_system->ID() << ")";
358         else
359             TraceLogger() << "Starting iteration at (" << cur_x << ", " << cur_y << ")";
360 
361         // Make sure that there actually still is a starlane between the two systems
362         // we are between
363         std::shared_ptr<const System> prev_or_cur;
364         if (cur_system) {
365             prev_or_cur = cur_system;
366         } else if (prev_system) {
367             prev_or_cur = prev_system;
368         } else {
369             ErrorLogger() << "Fleet::MovePath: No previous or current system!?";
370         }
371         if (prev_or_cur && !prev_or_cur->HasStarlaneTo(next_system->ID())) {
372             DebugLogger() << "Fleet::MovePath for Fleet " << this->Name() << " (" << this->ID()
373                             << ") No starlane connection between systems " << prev_or_cur->Name() << "(" << prev_or_cur->ID()
374                             << ")  and " << next_system->Name() << "(" << next_system->ID()
375                             << "). Abandoning the rest of the route. Route was: " << RouteNums();
376             return retval;
377         }
378 
379 
380         // check if fuel limits movement or current system refuels passing fleet
381         if (cur_system) {
382             // check if current system has fuel supply available
383             if (fleet_supplied_systems.count(cur_system->ID())) {
384                 // current system has fuel supply.  replenish fleet's supply and don't restrict movement
385                 fuel = max_fuel;
386                 //DebugLogger() << " ... at system with fuel supply.  replenishing and continuing movement";
387 
388             } else {
389                 // current system has no fuel supply.  require fuel to proceed
390                 if (fuel >= 1.0) {
391                     //DebugLogger() << " ... at system without fuel supply.  consuming unit of fuel to proceed";
392                     fuel -= 1.0;
393 
394                 } else {
395                     //DebugLogger() << " ... at system without fuel supply.  have insufficient fuel to continue moving";
396                     turns_taken = ETA_OUT_OF_RANGE;
397                     break;
398                 }
399             }
400         }
401 
402         // find distance to next system along path from current position
403         double dist_to_next_system = std::sqrt((next_x - cur_x)*(next_x - cur_x) + (next_y - cur_y)*(next_y - cur_y));
404         //DebugLogger() << " ... dist to next system: " << dist_to_next_system;
405 
406 
407         // move ship as far as it can go this turn, or to next system, whichever is closer, and deduct
408         // distance travelled from distance travellable this turn
409         if (turn_dist_remaining >= FLEET_MOVEMENT_EPSILON) {
410             double dist_travelled_this_step = std::min(turn_dist_remaining, dist_to_next_system);
411 
412             //DebugLogger() << " ... fleet moving " << dist_travelled_this_step << " this iteration.  dist to next system: " << dist_to_next_system << " and turn_dist_remaining: " << turn_dist_remaining;
413 
414             double x_dist = next_x - cur_x;
415             double y_dist = next_y - cur_y;
416             // dist_to_next_system = std::sqrt(x_dist * x_dist + y_dist * y_dist);  // should already equal this distance, so don't need to recalculate
417             double unit_vec_x = x_dist / dist_to_next_system;
418             double unit_vec_y = y_dist / dist_to_next_system;
419 
420             cur_x += unit_vec_x*dist_travelled_this_step;
421             cur_y += unit_vec_y*dist_travelled_this_step;
422 
423             turn_dist_remaining -= dist_travelled_this_step;
424             dist_to_next_system -= dist_travelled_this_step;
425 
426             // if moved away any distance from a system, are no longer in that system
427             if (cur_system && dist_travelled_this_step >= FLEET_MOVEMENT_EPSILON) {
428                 prev_system = cur_system;
429                 cur_system = nullptr;
430             }
431         }
432 
433         bool end_turn_at_cur_position = false;
434 
435         // check if fleet can move any further this turn
436         if (turn_dist_remaining < FLEET_MOVEMENT_EPSILON) {
437             //DebugLogger() << " ... fleet can't move further this turn.";
438             turn_dist_remaining = 0.0;      // to prevent any possible precision-related errors
439             end_turn_at_cur_position = true;
440         }
441 
442         // check if current position is close enough to next system on route to qualify as at that system.
443         if (dist_to_next_system < FLEET_MOVEMENT_EPSILON) {
444             // close enough to be consider to be at next system.
445             // set current position to be exactly at next system to avoid rounding issues
446             cur_system = next_system;
447             if (!cur_system) {
448                 ErrorLogger() << "Fleet::MovePath got null next system!";
449                 break;
450             }
451             cur_x = cur_system->X();    // update positions to ensure no round-off-errors
452             cur_y = cur_system->Y();
453 
454             TraceLogger() << " ... arrived at system: " << cur_system->Name();
455 
456             bool clear_exit = cur_system->ID() == m_arrival_starlane; //just part of the test for the moment
457             // attempt to get next system on route, to update next system.  if new current
458             // system is the end of the route, abort.
459             ++route_it;
460             if (route_it != route.end()) {
461                 // update next system on route and distance to it from current position
462                 next_system = EmpireKnownObjects(this->Owner()).get<System>(*route_it);
463                 if (next_system) {
464                     TraceLogger() << "Fleet::MovePath checking unrestriced lane travel from Sys("
465                                   <<  cur_system->ID() << ") to Sys(" << (next_system && next_system->ID()) << ")";
466                     clear_exit = clear_exit || next_system->ID() == m_arrival_starlane ||
467                     (empire && empire->PreservedLaneTravel(cur_system->ID(), next_system->ID()));
468                 }
469             }
470             if (flag_blockades && !clear_exit) {
471                 TraceLogger() << "Fleet::MovePath checking blockades at system " << cur_system->Name()
472                               << " (" << cur_system->ID() << ") for fleet " << this->Name()
473                               << " travelling to system " << (*route_it);
474                 if (cur_system && next_system && BlockadedAtSystem(cur_system->ID(), next_system->ID())) {
475                     // blockade debug logging
476                     TraceLogger() << "Fleet::MovePath finds system " << cur_system->Name() << " (" << cur_system->ID()
477                                   << ") blockaded for fleet " << this->Name();
478                     is_post_blockade = true;
479                 } else {
480                     TraceLogger() << "Fleet::MovePath finds system " << cur_system->Name() << " (" << cur_system->ID()
481                                   << ") NOT blockaded for fleet " << this->Name();
482                 }
483             }
484 
485             if (route_it == route.end() || !cur_system)
486                 break;
487 
488             if (!next_system) {
489                 ErrorLogger() << "Fleet::MovePath couldn't get system with id " << *route_it;
490                 break;
491             }
492             next_x = next_system->X();
493             next_y = next_system->Y();
494         }
495 
496         // if new position is an obstructed system, must end turn here
497         // on client side, if have stale info on cur_system it may appear blockaded even if not actually obstructed,
498         // and so will force a stop in that situation
499         if (cur_system && !unobstructed_systems.count(cur_system->ID())) {
500             turn_dist_remaining = 0.0;
501             end_turn_at_cur_position = true;
502         }
503 
504         // if turn done and turns taken is enough, abort simulation
505         if (end_turn_at_cur_position && (turns_taken + 1 >= TOO_LONG)) {
506             // exit loop before placing current node to simplify post-loop processing: now all cases require a post-loop node to be added
507             ++turns_taken;
508             break;
509         }
510 
511         // blockade debug logging
512         TraceLogger() << "Fleet::MovePath for fleet " << this->Name() << " id " << this->ID() << " adding node at sysID "
513                       << (cur_system ? cur_system->ID() : INVALID_OBJECT_ID) << " with post blockade status "
514                       << is_post_blockade << " and ETA " << turns_taken;
515 
516         // add MovePathNode for current position (end of turn position and/or system location)
517         retval.emplace_back(cur_x, cur_y, end_turn_at_cur_position, turns_taken,
518                             (cur_system  ? cur_system->ID()  : INVALID_OBJECT_ID),
519                             (prev_system ? prev_system->ID() : INVALID_OBJECT_ID),
520                             (next_system ? next_system->ID() : INVALID_OBJECT_ID),
521                             is_post_blockade);
522 
523 
524         // if the turn ended at this position, increment the turns taken and
525         // reset the distance remaining to be travelled during the current (now
526         // next) turn for the next loop iteration
527         if (end_turn_at_cur_position) {
528             //DebugLogger() << " ... end of simulated turn " << turns_taken;
529             ++turns_taken;
530             turn_dist_remaining = this->Speed();
531         }
532     }
533 
534 
535     // done looping.  may have exited due to reaching end of path, lack of fuel, or turns taken getting too big
536     if (turns_taken == TOO_LONG)
537         turns_taken = ETA_NEVER;
538     // blockade debug logging
539     TraceLogger() << "Fleet::MovePath for fleet " << this->Name()<<" id "<<this->ID()<<" adding node at sysID "
540                   << (cur_system  ? cur_system->ID()  : INVALID_OBJECT_ID) << " with post blockade status "
541                   << is_post_blockade << " and ETA " << turns_taken;
542 
543     retval.emplace_back(cur_x, cur_y, true, turns_taken,
544                         (cur_system  ? cur_system->ID()  : INVALID_OBJECT_ID),
545                         (prev_system ? prev_system->ID() : INVALID_OBJECT_ID),
546                         (next_system ? next_system->ID() : INVALID_OBJECT_ID),
547                         is_post_blockade);
548     TraceLogger() << "Fleet::MovePath for fleet " << this->Name() << "(" << this->ID() << ") is complete";
549 
550     return retval;
551 }
552 
ETA() const553 std::pair<int, int> Fleet::ETA() const
554 { return ETA(MovePath()); }
555 
ETA(const std::list<MovePathNode> & move_path) const556 std::pair<int, int> Fleet::ETA(const std::list<MovePathNode>& move_path) const {
557     // check that path exists.  if empty, there was no valid route or some other problem prevented pathing
558     if (move_path.empty())
559         return {ETA_UNKNOWN, ETA_UNKNOWN};
560 
561     // check for single node in path.  return the single node's eta as both .first and .second (likely indicates that fleet couldn't move)
562     if (move_path.size() == 1) {
563         const MovePathNode& node = *move_path.begin();
564         return {node.eta, node.eta};
565     }
566 
567     // general case: there is a multi-node path.  return the ETA of the first object node, and the ETA of the last node
568     int last_stop_eta = move_path.rbegin()->eta;
569     int first_stop_eta = last_stop_eta;
570     for (auto it = ++(move_path.begin()); it != move_path.end(); ++it) {
571         const MovePathNode& node = *it;
572         if (node.object_id != INVALID_OBJECT_ID) {
573             first_stop_eta = node.eta;
574             break;
575         }
576     }
577 
578     return {last_stop_eta, first_stop_eta};
579 }
580 
Fuel() const581 float Fleet::Fuel() const {
582     if (NumShips() < 1)
583         return 0.0f;
584 
585     // determine fuel available to fleet (fuel of the ship that has the least fuel in the fleet)
586     float fuel = Meter::LARGE_VALUE;
587     bool is_fleet_scrapped = true;
588 
589     for (auto& ship : Objects().find<const Ship>(m_ships)) {
590         const Meter* meter = ship->UniverseObject::GetMeter(METER_FUEL);
591         if (!meter) {
592             ErrorLogger() << "Fleet::Fuel skipping ship with no fuel meter";
593             continue;
594         }
595         if (!ship->OrderedScrapped()) {
596             fuel = std::min(fuel, meter->Current());
597             is_fleet_scrapped = false;
598         }
599     }
600     if (is_fleet_scrapped) {
601         fuel = 0.0f;
602     }
603     return fuel;
604 }
605 
MaxFuel() const606 float Fleet::MaxFuel() const {
607     if (NumShips() < 1)
608         return 0.0f;
609 
610     // determine the maximum amount of fuel that can be stored by the ship in the fleet that
611     // can store the least amount of fuel
612     float max_fuel = Meter::LARGE_VALUE;
613     bool is_fleet_scrapped = true;
614 
615     for (auto& ship : Objects().find<const Ship>(m_ships)) {
616         const Meter* meter = ship->UniverseObject::GetMeter(METER_MAX_FUEL);
617         if (!meter) {
618             ErrorLogger() << "Fleet::MaxFuel skipping ship with no max fuel meter";
619             continue;
620         }
621         if (!ship->OrderedScrapped()) {
622             max_fuel = std::min(max_fuel, meter->Current());
623             is_fleet_scrapped = false;
624         }
625     }
626     if (is_fleet_scrapped) {
627         max_fuel = 0.0f;
628     }
629     return max_fuel;
630 }
631 
FinalDestinationID() const632 int Fleet::FinalDestinationID() const {
633     if (m_travel_route.empty()) {
634         return INVALID_OBJECT_ID;
635     } else {
636         return m_travel_route.back();
637     }
638 }
639 
640 namespace {
HasXShips(const std::function<bool (const std::shared_ptr<const Ship> &)> & pred,const std::set<int> & ship_ids)641     bool HasXShips(const std::function<bool(const std::shared_ptr<const Ship>&)>& pred,
642                    const std::set<int>& ship_ids)
643     {
644         // Searching for each Ship one at a time is faster than
645         // find(ship_ids), because an early exit avoids searching the
646         // remaining ids.
647         return std::any_of(
648             ship_ids.begin(), ship_ids.end(),
649             [&pred](const int ship_id) {
650                 const auto& ship = Objects().get<const Ship>(ship_id);
651                 if (!ship) {
652                     WarnLogger() << "Object map is missing ship with expected id " << ship_id;
653                     return false;
654                 }
655                 return pred(ship);
656             });
657     }
658 }
659 
HasMonsters() const660 bool Fleet::HasMonsters() const {
661     auto isX = [](const std::shared_ptr<const Ship>& ship){ return ship->IsMonster(); };
662     return HasXShips(isX, m_ships);
663 }
664 
HasArmedShips() const665 bool Fleet::HasArmedShips() const {
666     auto isX = [](const std::shared_ptr<const Ship>& ship){ return ship->IsArmed(); };
667     return HasXShips(isX, m_ships);
668 }
669 
HasFighterShips() const670 bool Fleet::HasFighterShips() const {
671     auto isX = [](const std::shared_ptr<const Ship>& ship){ return ship->HasFighters(); };
672     return HasXShips(isX, m_ships);
673 }
674 
HasColonyShips() const675 bool Fleet::HasColonyShips() const {
676     auto isX = [](const std::shared_ptr<const Ship>& ship) {
677         if (ship->CanColonize())
678             if (const auto design = ship->Design())
679                 if (design->ColonyCapacity() > 0.0f)
680                     return true;
681         return false;
682     };
683     return HasXShips(isX, m_ships);
684 }
685 
HasOutpostShips() const686 bool Fleet::HasOutpostShips() const {
687     auto isX = [](const std::shared_ptr<const Ship>& ship) {
688         if (ship->CanColonize())
689             if (const auto design = ship->Design())
690                 if (design->ColonyCapacity() == 0.0f)
691                     return true;
692         return false;
693     };
694     return HasXShips(isX, m_ships);
695 }
696 
HasTroopShips() const697 bool Fleet::HasTroopShips() const {
698     auto isX = [](const std::shared_ptr<const Ship>& ship){ return ship->HasTroops(); };
699     return HasXShips(isX, m_ships);
700 }
701 
HasShipsOrderedScrapped() const702 bool Fleet::HasShipsOrderedScrapped() const {
703     auto isX = [](const std::shared_ptr<const Ship>& ship){ return ship->OrderedScrapped(); };
704     return HasXShips(isX, m_ships);
705 }
706 
HasShipsWithoutScrapOrders() const707 bool Fleet::HasShipsWithoutScrapOrders() const {
708     auto isX = [](const std::shared_ptr<const Ship>& ship){ return !ship->OrderedScrapped(); };
709     return HasXShips(isX, m_ships);
710 }
711 
ResourceOutput(ResourceType type) const712 float Fleet::ResourceOutput(ResourceType type) const {
713     float output = 0.0f;
714     if (NumShips() < 1)
715         return output;
716     MeterType meter_type = ResourceToMeter(type);
717     if (meter_type == INVALID_METER_TYPE)
718         return output;
719 
720     // determine resource output of each ship in this fleet
721     for (auto& ship : Objects().find<const Ship>(m_ships))
722         output += ship->GetMeter(meter_type)->Current();
723 
724     return output;
725 }
726 
UnknownRoute() const727 bool Fleet::UnknownRoute() const
728 { return m_travel_route.size() == 1 && m_travel_route.front() == INVALID_OBJECT_ID; }
729 
Accept(const UniverseObjectVisitor & visitor) const730 std::shared_ptr<UniverseObject> Fleet::Accept(const UniverseObjectVisitor& visitor) const
731 { return visitor.Visit(std::const_pointer_cast<Fleet>(std::static_pointer_cast<const Fleet>(shared_from_this()))); }
732 
SetRoute(const std::list<int> & route)733 void Fleet::SetRoute(const std::list<int>& route) {
734     if (UnknownRoute())
735         throw std::invalid_argument("Fleet::SetRoute() : Attempted to set an unknown route.");
736 
737     if (m_prev_system != SystemID() && m_prev_system == route.front() && !CanChangeDirectionEnRoute())
738         throw std::invalid_argument("Fleet::SetRoute() : Illegally attempted to change a fleet's direction while it was in transit.");
739 
740     m_travel_route = route;
741 
742     TraceLogger() << "Fleet::SetRoute: " << this->Name() << " (" << this->ID() << ")  input: " << [&]() {
743         std::stringstream ss;
744         for (int id : m_travel_route)
745             if (const auto obj = Objects().get<UniverseObject>(id))
746                 ss << obj->Name() << " (" << id << ")  ";
747         return ss.str();
748     }();
749 
750     if (m_travel_route.size() == 1 && this->SystemID() == m_travel_route.front()) {
751         // Fleet was ordered to move to the system where it is currently,
752         // without an intermediate stop
753         m_travel_route.clear();
754         m_prev_system = m_next_system = INVALID_OBJECT_ID;
755 
756     } else if (!m_travel_route.empty()) {
757         // Fleet was given a route to follow...
758         if (m_prev_system != SystemID() && m_prev_system == m_travel_route.front()) {
759             // Fleet was ordered to return to its previous system directly
760             m_prev_system = m_next_system;
761         } else if (SystemID() == route.front()) {
762             // Fleet was ordered to follow a route that starts at its current system
763             m_prev_system = SystemID();
764         }
765 
766         auto it = m_travel_route.begin();
767         if (m_prev_system == SystemID() && m_travel_route.size() > 1) {
768             m_next_system = *++it;
769         } else {
770             m_next_system = *it;
771         }
772 
773     } else if (m_travel_route.empty() && this->SystemID() != INVALID_OBJECT_ID) {
774         // route is empty and fleet is in a system. no movement needed.
775         // ensure next and previous systems are reset
776         m_prev_system = m_next_system = INVALID_OBJECT_ID;
777 
778     } else {    // m_travel_route.empty() && this->SystemID() == INVALID_OBJECT_ID
779         if (m_next_system != INVALID_OBJECT_ID) {
780             ErrorLogger() << "Fleet::SetRoute fleet " << this->Name() << " has empty route but fleet is not in a system. Resetting route to end at next system: " << m_next_system;
781             m_travel_route.push_back(m_next_system);
782         } else {
783             ErrorLogger() << "Fleet::SetRoute fleet " << this->Name() << " has empty route but fleet is not in a system, and has no next system set.";
784         }
785     }
786 
787     TraceLogger() << "Fleet::SetRoute: " << this->Name() << " (" << this->ID() << ")  final: " << [&]() {
788         std::stringstream ss;
789         for (int id : m_travel_route)
790             if (const auto obj = Objects().get<UniverseObject>(id))
791                 ss << obj->Name() << " (" << id << ")  ";
792         return ss.str();
793     }();
794 
795     StateChangedSignal();
796 }
797 
SetAggressive(bool aggressive)798 void Fleet::SetAggressive(bool aggressive/* = true*/) {
799     if (aggressive == m_aggressive)
800         return;
801     m_aggressive = aggressive;
802     StateChangedSignal();
803 }
804 
AddShips(const std::vector<int> & ship_ids)805 void Fleet::AddShips(const std::vector<int>& ship_ids) {
806     size_t old_ships_size = m_ships.size();
807     std::copy(ship_ids.begin(), ship_ids.end(), std::inserter(m_ships, m_ships.end()));
808     if (old_ships_size != m_ships.size())
809         StateChangedSignal();
810 }
811 
RemoveShips(const std::vector<int> & ship_ids)812 void Fleet::RemoveShips(const std::vector<int>& ship_ids) {
813     size_t old_ships_size = m_ships.size();
814     for (int ship_id : ship_ids)
815         m_ships.erase(ship_id);
816     if (old_ships_size != m_ships.size())
817         StateChangedSignal();
818 }
819 
SetNextAndPreviousSystems(int next,int prev)820 void Fleet::SetNextAndPreviousSystems(int next, int prev) {
821     m_prev_system = prev;
822     m_next_system = next;
823     m_arrival_starlane = prev; // see comment for ArrivalStarlane()
824 }
825 
MovementPhase()826 void Fleet::MovementPhase() {
827     Empire* empire = GetEmpire(Owner());
828     std::set<int> supply_unobstructed_systems;
829     if (empire)
830         supply_unobstructed_systems.insert(empire->SupplyUnobstructedSystems().begin(),
831                                            empire->SupplyUnobstructedSystems().end());
832 
833     auto ships = Objects().find<Ship>(m_ships);
834 
835     // if owner of fleet can resupply ships at the location of this fleet, then
836     // resupply all ships in this fleet
837     if (GetSupplyManager().SystemHasFleetSupply(SystemID(), Owner(), ALLOW_ALLIED_SUPPLY)) {
838         for (auto& ship : ships)
839             ship->Resupply();
840     }
841 
842     auto current_system = Objects().get<System>(SystemID());
843     auto initial_system = current_system;
844     auto move_path = MovePath();
845 
846     if (!move_path.empty()) {
847         DebugLogger() << "Fleet::MovementPhase " << this->Name() << " (" << this->ID()
848                       << ")  route:" << [&]() {
849             std::stringstream ss;
850             for (auto sys_id : this->TravelRoute()) {
851                 auto sys = Objects().get<System>(sys_id);
852                 if (sys)
853                     ss << "  " << sys->Name() << " (" << sys_id << ")";
854                 else
855                     ss << "  (??\?) (" << sys_id << ")";
856             }
857             return ss.str();
858         }()
859                       << "   move path:" << [&]() {
860             std::stringstream ss;
861             for (auto node : move_path) {
862                 auto sys = Objects().get<System>(node.object_id);
863                 if (sys)
864                     ss << "  " << sys->Name() << " (" << node.object_id << ")";
865                 else
866                     ss << "  (-)";
867             }
868             return ss.str();
869         }();
870     } else {
871         // enforce m_next_system and m_prev_system being INVALID_OBJECT_ID when
872         // move path is empty. bug was reported where m_next_system was somehow
873         // left with a system ID in it, which was never reset, and lead to
874         // supply propagation issues
875         m_next_system = m_prev_system = INVALID_OBJECT_ID;
876     }
877 
878     // If the move path cannot lead to the destination,
879     // make the route go as far as it can
880     if (!move_path.empty() && !m_travel_route.empty() &&
881          move_path.back().object_id != m_travel_route.back())
882     {
883         auto shortened_route = TruncateRouteToEndAtSystem(m_travel_route, Owner(), move_path.back().object_id);
884         try {
885             SetRoute(shortened_route);
886         } catch (const std::exception& e) {
887             ErrorLogger() << "Caught exception in Fleet MovementPhase shorentning route: " << e.what();
888         }
889         move_path = MovePath();
890     }
891 
892     auto it = move_path.begin();
893     auto next_it = it;
894     if (next_it != move_path.end())
895         ++next_it;
896 
897 
898     // is the fleet stuck in a system for a whole turn?
899     if (current_system) {
900         // update m_arrival_starlane if no blockade, if needed
901         if (supply_unobstructed_systems.count(SystemID()))
902             m_arrival_starlane = SystemID();// allows departure via any starlane
903 
904         // in a system.  if either:
905         // - there is no system after the current one in the path
906         // - the current and next nodes are the same and are system IDs or actual systems (not empty space)
907         // - this fleet is blockaded from its intended path
908         // then this fleet won't be moving further this turn
909         bool stopped = false;
910 
911         if (next_it == move_path.end()) {
912             // at end of path
913             stopped = true;
914 
915         } else if (it->object_id != INVALID_OBJECT_ID && it->object_id == next_it->object_id) {
916             // arriving at system
917             stopped = true;
918 
919         } else if (m_arrival_starlane != SystemID()) {
920             // blockaded
921             int next_sys_id;
922             if (next_it->object_id != INVALID_OBJECT_ID) {
923                 next_sys_id = next_it->object_id;
924             } else {
925                 next_sys_id = next_it->lane_end_id;
926             }
927             stopped = BlockadedAtSystem(SystemID(), next_sys_id);
928         }
929 
930         if (stopped)
931             return;
932 
933         // record previous system on fleet's path, and the starlane along
934         // which it will arrive at the next system (for blockading purposes)
935         m_arrival_starlane = SystemID();
936         m_prev_system = SystemID();
937 
938         // remove fleet and ships from system they are departing
939         current_system->Remove(ID());
940         SetSystem(INVALID_OBJECT_ID);
941         for (auto& ship : ships) {
942             current_system->Remove(ship->ID());
943             ship->SetSystem(INVALID_OBJECT_ID);
944         }
945     }
946 
947 
948     // if fleet not moving, nothing more to do.
949     if (move_path.empty()) {
950         m_next_system = m_prev_system = INVALID_OBJECT_ID;
951         return;
952     }
953 
954 
955     // move fleet in sequence to MovePathNodes it can reach this turn
956     float fuel_consumed = 0.0f;
957     for (it = move_path.begin(); it != move_path.end(); ++it) {
958         next_it = it;   ++next_it;
959 
960         auto system = Objects().get<System>(it->object_id);
961 
962         // is this system the last node reached this turn?  either it's an end of turn node,
963         // or there are no more nodes after this one on path
964         bool node_is_next_stop = (it->turn_end || next_it == move_path.end());
965 
966 
967         if (system) {
968             // node is a system.  explore system for all owners of this fleet
969             if (empire) {
970                 empire->AddExploredSystem(it->object_id);
971                 empire->RecordPendingLaneUpdate(it->object_id, m_prev_system);  // specifies the lane from it->object_id back to m_prev_system is available
972             }
973 
974             m_prev_system = system->ID();               // passing a system, so update previous system of this fleet
975 
976             // reached a system, so remove it from the route
977             if (m_travel_route.front() == system->ID())
978                 m_travel_route.erase(m_travel_route.begin());
979 
980             bool resupply_here = GetSupplyManager().SystemHasFleetSupply(system->ID(), this->Owner(), ALLOW_ALLIED_SUPPLY);
981 
982             // if this system can provide supplies, reset consumed fuel and refuel ships
983             if (resupply_here) {
984                 //DebugLogger() << " ... node has fuel supply.  consumed fuel for movement reset to 0 and fleet resupplied";
985                 fuel_consumed = 0.0f;
986                 for (auto& ship : ships) {
987                     ship->Resupply();
988                 }
989             }
990 
991 
992             // is system the last node reached this turn?
993             if (node_is_next_stop) {
994                 // fleet ends turn at this node.  insert fleet and ships into system
995                 InsertFleetWithShips(*this, system);
996 
997                 current_system = system;
998 
999                 if (supply_unobstructed_systems.count(SystemID()))
1000                     m_arrival_starlane = SystemID();//allows departure via any starlane
1001 
1002                 // Add current system to the start of any existing route for next turn
1003                 if (!m_travel_route.empty() && m_travel_route.front() != SystemID())
1004                     m_travel_route.push_front(SystemID());
1005 
1006                 break;
1007 
1008             } else {
1009                 // fleet will continue past this system this turn.
1010                 m_arrival_starlane = m_prev_system;
1011                 if (!resupply_here) {
1012                     fuel_consumed += 1.0f;
1013                 }
1014             }
1015 
1016         } else {
1017             // node is not a system.
1018             m_arrival_starlane = m_prev_system;
1019             if (node_is_next_stop) {            // node is not a system, but is it the last node reached this turn?
1020                 MoveFleetWithShips(*this, it->x, it->y);
1021                 break;
1022             }
1023         }
1024     }
1025 
1026 
1027     // update next system
1028     if (!m_travel_route.empty() && next_it != move_path.end() && it != move_path.end()) {
1029         // there is another system later on the path to aim for.  find it
1030         for (; next_it != move_path.end(); ++next_it) {
1031             if (Objects().get<System>(next_it->object_id)) {
1032                 //DebugLogger() << "___ setting m_next_system to " << next_it->object_id;
1033                 m_next_system = next_it->object_id;
1034                 break;
1035             }
1036         }
1037 
1038     } else {
1039         // no more systems on path
1040         m_arrived_this_turn = current_system != initial_system;
1041         m_next_system = m_prev_system = INVALID_OBJECT_ID;
1042     }
1043 
1044 
1045     // consume fuel from ships in fleet
1046     if (fuel_consumed > 0.0f) {
1047         for (auto& ship : ships) {
1048             if (Meter* meter = ship->UniverseObject::GetMeter(METER_FUEL)) {
1049                 meter->AddToCurrent(-fuel_consumed);
1050                 meter->BackPropagate();
1051             }
1052         }
1053     }
1054 }
1055 
ResetTargetMaxUnpairedMeters()1056 void Fleet::ResetTargetMaxUnpairedMeters() {
1057     UniverseObject::ResetTargetMaxUnpairedMeters();
1058 
1059     // give fleets base stealth very high, so that they can (almost?) never be
1060     // seen by empires that don't own them, unless their ships are seen and
1061     // that visibility is propagated to the fleet that contains the ships
1062     if (Meter* stealth = GetMeter(METER_STEALTH)) {
1063         stealth->ResetCurrent();
1064         stealth->AddToCurrent(2000.0f);
1065     }
1066 }
1067 
CalculateRouteTo(int target_system_id)1068 void Fleet::CalculateRouteTo(int target_system_id) {
1069     std::list<int> route;
1070 
1071     //DebugLogger() << "Fleet::CalculateRoute";
1072     if (target_system_id == INVALID_OBJECT_ID) {
1073         try {
1074             SetRoute(route);
1075         } catch (const std::exception& e) {
1076             ErrorLogger() << "Caught exception in Fleet CalculateRouteTo: " << e.what();
1077         }
1078         return;
1079     }
1080 
1081     if (m_prev_system != INVALID_OBJECT_ID && SystemID() == m_prev_system) {
1082         // if we haven't actually left yet, we have to move from whichever system we are at now
1083 
1084         if (!Objects().get<System>(target_system_id)) {
1085             // destination system doesn't exist or doesn't exist in known universe, so can't move to it.  leave route empty.
1086             try {
1087                 SetRoute(route);
1088             } catch (const std::exception& e) {
1089                 ErrorLogger() << "Caught exception in Fleet CalculateRouteTo: " << e.what();
1090             }
1091             return;
1092         }
1093 
1094         std::pair<std::list<int>, double> path;
1095         try {
1096             path = GetPathfinder()->ShortestPath(m_prev_system, target_system_id, this->Owner());
1097         } catch (...) {
1098             DebugLogger() << "Fleet::CalculateRoute couldn't find route to system(s):"
1099                           << " fleet's previous: " << m_prev_system << " or moving to: " << target_system_id;
1100         }
1101         try {
1102             SetRoute(path.first);
1103         } catch (const std::exception& e) {
1104             ErrorLogger() << "Caught exception in Fleet CalculateRouteTo: " << e.what();
1105         }
1106         return;
1107     }
1108 
1109     int dest_system_id = target_system_id;
1110 
1111     // if we're between systems, the shortest route may be through either one
1112     if (this->CanChangeDirectionEnRoute()) {
1113         std::pair<std::list<int>, double> path1;
1114         try {
1115             path1 = GetPathfinder()->ShortestPath(m_next_system, dest_system_id, this->Owner());
1116         } catch (...) {
1117             DebugLogger() << "Fleet::CalculateRoute couldn't find route to system(s):"
1118                           << " fleet's next: " << m_next_system << " or destination: " << dest_system_id;
1119         }
1120         auto& sys_list1 = path1.first;
1121         if (sys_list1.empty()) {
1122             ErrorLogger() << "Fleet::CalculateRoute got empty route from ShortestPath";
1123             return;
1124         }
1125         auto obj = Objects().get(sys_list1.front());
1126         if (!obj) {
1127             ErrorLogger() << "Fleet::CalculateRoute couldn't get path start object with id " << path1.first.front();
1128             return;
1129         }
1130         double dist_x = obj->X() - this->X();
1131         double dist_y = obj->Y() - this->Y();
1132         double dist1 = std::sqrt(dist_x*dist_x + dist_y*dist_y);
1133 
1134         std::pair<std::list<int>, double> path2;
1135         try {
1136             path2 = GetPathfinder()->ShortestPath(m_prev_system, dest_system_id, this->Owner());
1137         } catch (...) {
1138             DebugLogger() << "Fleet::CalculateRoute couldn't find route to system(s):"
1139                           << " fleet's previous: " << m_prev_system << " or destination: " << dest_system_id;
1140         }
1141         auto& sys_list2 = path2.first;
1142         if (sys_list2.empty()) {
1143             ErrorLogger() << "Fleet::CalculateRoute got empty route from ShortestPath";
1144             return;
1145         }
1146         obj = Objects().get(sys_list2.front());
1147         if (!obj) {
1148             ErrorLogger() << "Fleet::CalculateRoute couldn't get path start object with id " << path2.first.front();
1149             return;
1150         }
1151         dist_x = obj->X() - this->X();
1152         dist_y = obj->Y() - this->Y();
1153         double dist2 = std::sqrt(dist_x*dist_x + dist_y*dist_y);
1154 
1155         try {
1156             // pick whichever path is quicker
1157             if (dist1 + path1.second < dist2 + path2.second) {
1158                 SetRoute(path1.first);
1159             } else {
1160                 SetRoute(path2.first);
1161             }
1162         } catch (const std::exception& e) {
1163             ErrorLogger() << "Caught exception in Fleet CalculateRouteTo: " << e.what();
1164         }
1165 
1166     } else {
1167         // Cannot change direction. Must go to the end of the current starlane
1168         std::pair<std::list<int>, double> path;
1169         try {
1170             path = GetPathfinder()->ShortestPath(m_next_system, dest_system_id, this->Owner());
1171         } catch (...) {
1172             DebugLogger() << "Fleet::CalculateRoute couldn't find route to system(s):"
1173                           << " fleet's next: " << m_next_system << " or destination: " << dest_system_id;
1174         }
1175         try {
1176             SetRoute(path.first);
1177         } catch (const std::exception& e) {
1178             ErrorLogger() << "Caught exception in Fleet CalculateRouteTo: " << e.what();
1179         }
1180     }
1181 }
1182 
Blockaded() const1183 bool Fleet::Blockaded() const {
1184     auto system = Objects().get<System>(this->SystemID());
1185 
1186     if (!system)
1187         return false;
1188 
1189     if (m_next_system != INVALID_OBJECT_ID)
1190         return BlockadedAtSystem(SystemID(), m_next_system);
1191 
1192     for (const auto& target_system : system->StarlanesWormholes()) {
1193         if (BlockadedAtSystem(this->SystemID(), target_system.first))
1194             return true;
1195     }
1196 
1197     return false;
1198 }
1199 
BlockadedAtSystem(int start_system_id,int dest_system_id) const1200 bool Fleet::BlockadedAtSystem(int start_system_id, int dest_system_id) const {
1201     /** If a newly arrived fleet joins a non-blockaded fleet of the same empire
1202       * (perhaps should include allies?) already at the system, the newly
1203       * arrived fleet will not be blockaded.  Additionally, since fleets are
1204       * blockade-checked at movement phase and also postcombat, the following
1205       * tests mean that post-compbat, this fleet will be blockaded iff it was
1206       * blockaded pre-combat AND there are armed aggressive enemies surviving in
1207       * system post-combat which can detect this fleet.  Fleets arriving at the
1208       * same time do not blockade each other. Unrestricted lane access (i.e,
1209       * (fleet->ArrivalStarlane() == system->ID()) ) is used as a proxy for
1210       * order of arrival -- if an enemy has unrestricted lane access and you
1211       * don't, they must have arrived before you, or be in cahoots with someone
1212       * who did. */
1213 
1214     if (m_arrival_starlane == start_system_id) {
1215         //DebugLogger() << "Fleet::BlockadedAtSystem fleet " << ID() << " has cleared blockade flag for system (" << start_system_id << ")";
1216         return false;
1217     }
1218     bool not_yet_in_system = SystemID() != start_system_id;
1219 
1220     if (!not_yet_in_system && m_arrival_starlane == dest_system_id)
1221         return false;
1222 
1223     // find which empires have blockading aggressive armed ships in system;
1224     // fleets that just arrived do not blockade by themselves, but may
1225     // reinforce a preexisting blockade, and may possibly contribute to detection
1226     auto current_system = Objects().get<System>(start_system_id);
1227     if (!current_system) {
1228         DebugLogger() << "Fleet::BlockadedAtSystem fleet " << ID() << " considering system (" << start_system_id << ") but can't retrieve system copy";
1229         return false;
1230     }
1231 
1232     auto empire = GetEmpire(this->Owner());
1233     if (empire) {
1234         auto unobstructed_systems = empire->SupplyUnobstructedSystems();
1235         if (unobstructed_systems.count(start_system_id))
1236             return false;
1237         if (empire->PreservedLaneTravel(start_system_id, dest_system_id)) {
1238             return false;
1239         } else {
1240             TraceLogger() << "Fleet::BlockadedAtSystem fleet " << ID() << " considering travel from system (" << start_system_id << ") to system (" << dest_system_id << ")";
1241         }
1242     }
1243 
1244     float lowest_ship_stealth = 99999.9f; // arbitrary large number. actual stealth of ships should be less than this...
1245     for (auto& ship : Objects().find<const Ship>(this->ShipIDs())) {
1246         float ship_stealth = ship->GetMeter(METER_STEALTH)->Current();
1247         if (lowest_ship_stealth > ship_stealth)
1248             lowest_ship_stealth = ship_stealth;
1249     }
1250 
1251     float monster_detection = 0.0f;
1252     auto fleets = Objects().find<const Fleet>(current_system->FleetIDs());
1253     for (auto& fleet : fleets) {
1254         if (!fleet->Unowned())
1255             continue;
1256 
1257         for (auto& ship : Objects().find<const Ship>(fleet->ShipIDs())) {
1258             float cur_detection = ship->GetMeter(METER_DETECTION)->Current();
1259             if (cur_detection >= monster_detection)
1260                 monster_detection = cur_detection;
1261         }
1262     }
1263 
1264     bool can_be_blockaded = false;
1265     for (auto& fleet : fleets) {
1266         if (fleet->NextSystemID() != INVALID_OBJECT_ID) //fleets trying to leave this turn can't blockade pre-combat.
1267             continue;
1268         bool unrestricted = (fleet->m_arrival_starlane == start_system_id);
1269         if  (fleet->Owner() == this->Owner()) {
1270             if (unrestricted)  // perhaps should consider allies
1271                 return false;
1272             continue;
1273         }
1274         if (!unrestricted && !not_yet_in_system)
1275             continue;
1276 
1277         bool can_see;
1278         if (!fleet->Unowned())
1279             can_see = (GetEmpire(fleet->Owner())->GetMeter("METER_DETECTION_STRENGTH")->Current() >= lowest_ship_stealth);
1280         else
1281             can_see = (monster_detection >= lowest_ship_stealth);
1282         if (!can_see)
1283             continue;
1284 
1285         bool at_war = Unowned() || fleet->Unowned() ||
1286                       Empires().GetDiplomaticStatus(this->Owner(), fleet->Owner()) == DIPLO_WAR;
1287         if (!at_war)
1288             continue;
1289         bool aggressive = (fleet->Aggressive() || fleet->Unowned());
1290         if (!aggressive)
1291             continue;
1292         // Newly created ships/monsters are not allowed to block other fleet movement since they have not even
1293         // potentially gone through a combat round at the present location.  Potential sources for such new ships are
1294         // monsters created via Effect and Ships/fleets newly constructed by empires.  We check ship ages not fleet
1295         // ageas since fleets can be created/destroyed as purely organizational matters.  Since these checks are
1296         // pertinent just during those stages of turn processing immediately following turn number advancement,
1297         // whereas the new ships were created just prior to turn advamcenemt, we require age greater than 1.
1298         if (fleet->MaxShipAgeInTurns() <= 1)
1299             continue;
1300         // These are the most costly checks.  Do them last
1301         if (!fleet->HasArmedShips())
1302             continue;
1303 
1304         // don't exit early here, because blockade may yet be thwarted by ownership & presence check above
1305         can_be_blockaded = true;
1306 
1307     }
1308 
1309     return can_be_blockaded;
1310 }
1311 
Speed() const1312 float Fleet::Speed() const {
1313     if (m_ships.empty())
1314         return 0.0f;
1315 
1316     bool fleet_is_scrapped = true;
1317     float retval = MAX_SHIP_SPEED;  // max speed no ship can go faster than
1318     for (const auto& ship : Objects().find<Ship>(m_ships)) {
1319         if (!ship || ship->OrderedScrapped())
1320             continue;
1321         if (ship->Speed() < retval)
1322             retval = ship->Speed();
1323         fleet_is_scrapped = false;
1324     }
1325 
1326     if (fleet_is_scrapped)
1327         retval = 0.0f;
1328 
1329     return retval;
1330 }
1331 
Damage() const1332 float Fleet::Damage() const {
1333     if (m_ships.empty())
1334         return 0.0f;
1335 
1336     bool fleet_is_scrapped = true;
1337     float retval = 0.0f;
1338     for (const auto& ship : Objects().find<Ship>(m_ships)) {
1339         if (!ship || ship->OrderedScrapped())
1340             continue;
1341         if (const auto design = ship->Design())
1342             retval += design->Attack();
1343         fleet_is_scrapped = false;
1344     }
1345 
1346     if (fleet_is_scrapped)
1347         retval = 0.0f;
1348 
1349     return retval;
1350 }
1351 
Structure() const1352 float Fleet::Structure() const {
1353     if (m_ships.empty())
1354         return 0.0f;
1355 
1356     bool fleet_is_scrapped = true;
1357     float retval = 0.0f;
1358     for (const auto& ship : Objects().find<Ship>(m_ships)) {
1359         if (!ship || ship->OrderedScrapped())
1360             continue;
1361         retval += ship->GetMeter(METER_STRUCTURE)->Current();
1362         fleet_is_scrapped = false;
1363     }
1364 
1365     if (fleet_is_scrapped)
1366         retval = 0.0f;
1367 
1368     return retval;
1369 }
1370 
Shields() const1371 float Fleet::Shields() const {
1372     if (m_ships.empty())
1373         return 0.0f;
1374 
1375     bool fleet_is_scrapped = true;
1376     float retval = 0.0f;
1377     for (const auto& ship : Objects().find<Ship>(m_ships)) {
1378         if (!ship || ship->OrderedScrapped())
1379             continue;
1380         retval += ship->GetMeter(METER_SHIELD)->Current();
1381         fleet_is_scrapped = false;
1382     }
1383 
1384     if (fleet_is_scrapped)
1385         retval = 0.0f;
1386 
1387     return retval;
1388 }
1389 
1390 namespace {
IsCombatShip(const Ship & ship)1391     bool IsCombatShip(const Ship& ship)
1392     { return ship.IsArmed() || ship.HasFighters() || ship.CanHaveTroops() || ship.CanBombard(); }
1393 }
1394 
GenerateFleetName()1395 std::string Fleet::GenerateFleetName() {
1396     // TODO: Change returned name based on passed ship designs.  eg. return "colony fleet" if
1397     // ships are colony ships, or "battle fleet" if ships are armed.
1398     if (ID() == INVALID_OBJECT_ID)
1399         return UserString("NEW_FLEET_NAME_NO_NUMBER");
1400 
1401     std::vector<std::shared_ptr<const Ship>> ships;
1402     for (const auto& ship : Objects().find<Ship>(m_ships)) {
1403         if (!ship)
1404             continue;
1405         ships.push_back(ship);
1406     }
1407 
1408     std::string fleet_name_key = UserStringNop("NEW_FLEET_NAME");
1409 
1410     if (boost::algorithm::all_of(ships, [](const auto& ship){ return ship->IsMonster(); }))
1411         fleet_name_key = UserStringNop("NEW_MONSTER_FLEET_NAME");
1412     else if (boost::algorithm::all_of(ships, [](const auto& ship){ return ship->CanColonize(); }))
1413         fleet_name_key = UserStringNop("NEW_COLONY_FLEET_NAME");
1414     else if (boost::algorithm::all_of(ships, [](const auto& ship){ return !IsCombatShip(*ship); }))
1415         fleet_name_key = UserStringNop("NEW_RECON_FLEET_NAME");
1416     else if (boost::algorithm::all_of(ships, [](const auto& ship){ return ship->CanHaveTroops(); }))
1417         fleet_name_key = UserStringNop("NEW_TROOP_FLEET_NAME");
1418     else if (boost::algorithm::all_of(ships, [](const auto& ship){ return ship->CanBombard(); }))
1419         fleet_name_key = UserStringNop("NEW_BOMBARD_FLEET_NAME");
1420     else if (boost::algorithm::all_of(ships, [](const auto& ship){ return IsCombatShip(*ship); }))
1421         fleet_name_key = UserStringNop("NEW_BATTLE_FLEET_NAME");
1422 
1423     return boost::io::str(FlexibleFormat(UserString(fleet_name_key)) % ID());
1424 }
1425 
SetGiveToEmpire(int empire_id)1426 void Fleet::SetGiveToEmpire(int empire_id) {
1427     if (empire_id == m_ordered_given_to_empire_id) return;
1428     m_ordered_given_to_empire_id = empire_id;
1429     StateChangedSignal();
1430 }
1431 
ClearGiveToEmpire()1432 void Fleet::ClearGiveToEmpire()
1433 { SetGiveToEmpire(ALL_EMPIRES); }
1434 
1435