1 #include "System.h"
2 
3 #include "Fleet.h"
4 #include "Ship.h"
5 #include "Planet.h"
6 #include "Building.h"
7 #include "Predicates.h"
8 #include "Universe.h"
9 #include "Enums.h"
10 
11 #include "../Empire/Empire.h"
12 #include "../Empire/EmpireManager.h"
13 
14 #include "../util/i18n.h"
15 #include "../util/Logger.h"
16 #include "../util/OptionsDB.h"
17 #include "../util/AppInterface.h"
18 
System()19 System::System() :
20     m_star(INVALID_STAR_TYPE)
21 {}
22 
System(StarType star,const std::string & name,double x,double y)23 System::System(StarType star, const std::string& name, double x, double y) :
24     UniverseObject(name, x, y),
25     m_star(star)
26 {
27     if (m_star < INVALID_STAR_TYPE || NUM_STAR_TYPES < m_star)
28         m_star = INVALID_STAR_TYPE;
29 
30     m_orbits.assign(SYSTEM_ORBITS, INVALID_OBJECT_ID);
31 
32     UniverseObject::Init();
33 }
34 
System(StarType star,const std::map<int,bool> & lanes_and_holes,const std::string & name,double x,double y)35 System::System(StarType star, const std::map<int, bool>& lanes_and_holes,
36                const std::string& name, double x, double y) :
37     UniverseObject(name, x, y),
38     m_star(star),
39     m_starlanes_wormholes(lanes_and_holes)
40 {
41     if (m_star < INVALID_STAR_TYPE || NUM_STAR_TYPES < m_star)
42         m_star = INVALID_STAR_TYPE;
43 
44     m_orbits.assign(SYSTEM_ORBITS, INVALID_OBJECT_ID);
45 
46     SetSystem(ID());
47 
48     UniverseObject::Init();
49 }
50 
Clone(int empire_id) const51 System* System::Clone(int empire_id) const {
52     Visibility vis = GetUniverse().GetObjectVisibilityByEmpire(this->ID(), empire_id);
53 
54     if (!(vis >= VIS_BASIC_VISIBILITY && vis <= VIS_FULL_VISIBILITY))
55         return nullptr;
56 
57     System* retval = new System();
58     retval->Copy(shared_from_this(), empire_id);
59     return retval;
60 }
61 
Copy(std::shared_ptr<const UniverseObject> copied_object,int empire_id)62 void System::Copy(std::shared_ptr<const UniverseObject> copied_object, int empire_id) {
63     if (copied_object.get() == this)
64         return;
65     std::shared_ptr<const System> copied_system = std::dynamic_pointer_cast<const System>(copied_object);
66     if (!copied_system) {
67         ErrorLogger() << "System::Copy passed an object that wasn't a System";
68         return;
69     }
70 
71     int copied_object_id = copied_object->ID();
72     Visibility vis = GetUniverse().GetObjectVisibilityByEmpire(copied_object_id, empire_id);
73     std::set<std::string> visible_specials = GetUniverse().GetObjectVisibleSpecialsByEmpire(copied_object_id, empire_id);
74 
75     UniverseObject::Copy(copied_object, vis, visible_specials);
76 
77     if (vis >= VIS_BASIC_VISIBILITY) {
78         // add any visible lanes, without removing existing entries
79         std::map<int, bool> visible_lanes_holes = copied_system->VisibleStarlanesWormholes(empire_id);
80         for (const auto& entry : visible_lanes_holes)
81         { this->m_starlanes_wormholes[entry.first] = entry.second; }
82 
83         // copy visible info of visible contained objects
84         this->m_objects = copied_system->VisibleContainedObjectIDs(empire_id);
85 
86         // only copy orbit info for visible planets
87         size_t orbits_size = m_orbits.size();
88         m_orbits.clear();
89         m_orbits.assign(orbits_size, INVALID_OBJECT_ID);
90         for (std::size_t o = 0; o < copied_system->m_orbits.size(); ++o) {
91             int planet_id = copied_system->m_orbits[o];
92             if (m_objects.count(planet_id))
93                 m_orbits[o] = planet_id;
94         }
95 
96         // copy visible contained object per-type info
97         m_planets.clear();
98         for (int planet_id : copied_system->m_planets) {
99             if (m_objects.count(planet_id))
100                 m_planets.insert(planet_id);
101         }
102 
103         m_buildings.clear();
104         for (int building_id : copied_system->m_buildings) {
105             if (m_objects.count(building_id))
106                 m_buildings.insert(building_id);
107         }
108 
109         m_fleets.clear();
110         for (int fleet_id : copied_system->m_fleets) {
111             if (m_objects.count(fleet_id))
112                 m_fleets.insert(fleet_id);
113         }
114 
115         m_ships.clear();
116         for (int ship_id : copied_system->m_ships) {
117             if (m_objects.count(ship_id))
118                 m_ships.insert(ship_id);
119         }
120 
121         m_fields.clear();
122         for (int field_id : copied_system->m_fields) {
123             if (m_objects.count(field_id))
124                 m_fields.insert(field_id);
125         }
126 
127 
128         if (vis >= VIS_PARTIAL_VISIBILITY) {
129             this->m_name =                  copied_system->m_name;
130             this->m_star =                  copied_system->m_star;
131             this->m_last_turn_battle_here = copied_system->m_last_turn_battle_here;
132 
133             // remove any not-visible lanes that were previously known: with
134             // partial vis, they should be seen, but aren't, so are known not
135             // to exist any more
136 
137             // remove previously known lanes that aren't currently visible
138             for (auto entry_it = m_starlanes_wormholes.begin(); entry_it != m_starlanes_wormholes.end();
139                  /* conditional increment in deleting loop */)
140             {
141                 int lane_end_sys_id = entry_it->first;
142                 if (!visible_lanes_holes.count(lane_end_sys_id)) {
143                     entry_it = m_starlanes_wormholes.erase(entry_it);
144                 } else {
145                     ++entry_it;
146                 }
147             }
148         }
149     }
150 }
151 
ObjectType() const152 UniverseObjectType System::ObjectType() const
153 { return OBJ_SYSTEM; }
154 
Dump(unsigned short ntabs) const155 std::string System::Dump(unsigned short ntabs) const {
156     std::stringstream os;
157     os << UniverseObject::Dump(ntabs);
158     os << " star type: " << m_star
159        << "  last combat on turn: " << m_last_turn_battle_here
160        << "  total orbits: " << m_orbits.size();
161 
162     if (m_orbits.size() > 0) {
163         os << "  objects per orbit: ";
164 
165         int orbit_index = 0;
166         for (auto it = m_orbits.begin();
167             it != m_orbits.end();)
168         {
169             os << "[" << orbit_index << "]" << *it;
170             ++it;
171             if (it != m_orbits.end())
172                 os << ", ";
173             ++orbit_index;
174         }
175     }
176 
177     os << "  starlanes: ";
178     for (auto it = m_starlanes_wormholes.begin();
179          it != m_starlanes_wormholes.end();)
180     {
181         int lane_end_id = it->first;
182         ++it;
183         os << lane_end_id << (it == m_starlanes_wormholes.end() ? "" : ", ");
184     }
185 
186     os << "  objects: ";
187     for (auto it = m_objects.begin(); it != m_objects.end();) {
188         int obj_id = *it;
189         ++it;
190         if (obj_id == INVALID_OBJECT_ID)
191             continue;
192         os << obj_id << (it == m_objects.end() ? "" : ", ");
193     }
194     return os.str();
195 }
196 
ApparentName(int empire_id,bool blank_unexplored_and_none) const197 const std::string& System::ApparentName(int empire_id, bool blank_unexplored_and_none/* = false*/) const {
198     static const std::string EMPTY_STRING;
199 
200     if (empire_id == ALL_EMPIRES)
201         return this->PublicName(empire_id);
202 
203     // has the indicated empire ever detected this system?
204     const auto& vtm = GetUniverse().GetObjectVisibilityTurnMapByEmpire(this->ID(), empire_id);
205     if (!vtm.count(VIS_PARTIAL_VISIBILITY)) {
206         if (blank_unexplored_and_none)
207             return EMPTY_STRING;
208 
209         if (m_star == INVALID_STAR_TYPE)
210             return UserString("UNEXPLORED_REGION");
211         else
212             return UserString("UNEXPLORED_SYSTEM");
213     }
214 
215     if (m_star == STAR_NONE) {
216         // determine if there are any planets in the system
217         for (const auto& entry : Objects().ExistingPlanets()) {
218             if (entry.second->SystemID() == this->ID())
219                 return this->PublicName(empire_id);
220         }
221         if (blank_unexplored_and_none) {
222             //DebugLogger() << "System::ApparentName No-Star System (" << ID() << "), returning name "<< EMPTY_STRING;
223             return EMPTY_STRING;
224         }
225         //DebugLogger() << "System::ApparentName No-Star System (" << ID() << "), returning name "<< UserString("EMPTY_SPACE");
226         return UserString("EMPTY_SPACE");
227     }
228 
229     return this->PublicName(empire_id);
230 }
231 
NextOlderStarType() const232 StarType System::NextOlderStarType() const {
233     if (m_star <= INVALID_STAR_TYPE || m_star >= NUM_STAR_TYPES)
234         return INVALID_STAR_TYPE;
235     if (m_star >= STAR_RED)
236         return m_star;              // STAR_RED, STAR_NEUTRON, STAR_BLACK, STAR_NONE
237     return StarType(m_star + 1);    // STAR_BLUE -> STAR_WHITE -> STAR_YELLOW -> STAR_ORANGE -> STAR_RED
238 }
239 
NextYoungerStarType() const240 StarType System::NextYoungerStarType() const {
241     if (m_star <= INVALID_STAR_TYPE || m_star >= NUM_STAR_TYPES)
242         return INVALID_STAR_TYPE;
243     if (m_star > STAR_RED)
244         return m_star;              // STAR_NEUTRON, STAR_BLACK, STAR_NONE
245     if (m_star <= STAR_BLUE)
246         return STAR_BLUE;           // STAR_BLUE
247     return StarType(m_star - 1);    // STAR_BLUE <- STAR_WHITE <- STAR_YELLOW <- STAR_ORANGE <- STAR_RED
248 }
249 
NumStarlanes() const250 int System::NumStarlanes() const {
251     int retval = 0;
252     for (const auto& entry : m_starlanes_wormholes) {
253         if (!entry.second)
254             ++retval;
255     }
256     return retval;
257 }
258 
NumWormholes() const259 int System::NumWormholes() const {
260     int retval = 0;
261     for (const auto& entry : m_starlanes_wormholes) {
262         if (entry.second)
263             ++retval;
264     }
265     return retval;
266 }
267 
HasStarlaneTo(int id) const268 bool System::HasStarlaneTo(int id) const {
269     auto it = m_starlanes_wormholes.find(id);
270     return (it == m_starlanes_wormholes.end() ? false : it->second == false);
271 }
272 
HasWormholeTo(int id) const273 bool System::HasWormholeTo(int id) const {
274     auto it = m_starlanes_wormholes.find(id);
275     return (it == m_starlanes_wormholes.end() ? false : it->second == true);
276 }
277 
Owner() const278 int  System::Owner() const {
279     // Check if all of the owners are the same empire.
280     int first_owner_found(ALL_EMPIRES);
281     for (const auto& planet : Objects().find<Planet>(m_planets)) {
282         if (!planet)
283             continue;
284         const int owner = planet->Owner();
285         if (owner == ALL_EMPIRES)
286             continue;
287         if (first_owner_found == ALL_EMPIRES)
288             first_owner_found = owner;
289         if (first_owner_found != owner)
290             return ALL_EMPIRES;
291     }
292     return first_owner_found;
293 }
294 
ContainedObjectIDs() const295 const std::set<int>& System::ContainedObjectIDs() const
296 { return m_objects; }
297 
Contains(int object_id) const298 bool System::Contains(int object_id) const {
299     if (object_id == INVALID_OBJECT_ID)
300         return false;
301     return m_objects.count(object_id);
302 }
303 
Accept(const UniverseObjectVisitor & visitor) const304 std::shared_ptr<UniverseObject> System::Accept(const UniverseObjectVisitor& visitor) const
305 { return visitor.Visit(std::const_pointer_cast<System>(std::static_pointer_cast<const System>(shared_from_this()))); }
306 
Insert(std::shared_ptr<UniverseObject> obj,int orbit)307 void System::Insert(std::shared_ptr<UniverseObject> obj, int orbit/* = -1*/) {
308     if (!obj) {
309         ErrorLogger() << "System::Insert() : Attempted to place a null object in a System";
310         return;
311     }
312     if (orbit < -1 || orbit >= static_cast<int>(m_orbits.size())) {
313         ErrorLogger() << "System::Insert() : Attempted to place an object in invalid orbit";
314         return;
315     }
316 
317     obj->MoveTo(this->X(), this->Y());
318     obj->SetSystem(this->ID());
319 
320     if (obj->ObjectType() == OBJ_PLANET) {
321         if (orbit == -1) {
322             bool already_in_orbit = false;
323             for (int planet_id : m_orbits) {
324                 if (planet_id == obj->ID()) {
325                     already_in_orbit = true;
326                     break;
327                 }
328             }
329             // if planet already in an orbit, do nothing
330             // if planet not in an orbit, find orbit to put planet in
331             if (!already_in_orbit) {
332                 for (int o = 0; o < static_cast<int>(m_orbits.size()); ++o) {
333                     if (m_orbits[o] == INVALID_OBJECT_ID) {
334                         orbit = o;
335                         m_orbits[orbit] = obj->ID();
336                         break;
337                     }
338                 }
339             }
340         } else {
341             // check if desired orbit is occupied
342             // if yes, it is either already occupied by the inserted object or
343             // by another object. in either case, do nothing.
344             // if not, put object into the orbit, and remove from any other
345             // orbit it currently occupies.
346             if (!OrbitOccupied(orbit)) {
347                 // check if object already in any different orbit
348                 // ... if yes, remove it
349                 for (int o = 0; o < static_cast<int>(m_orbits.size()); ++o) {
350                     if (o != orbit && m_orbits[o] == obj->ID())
351                         m_orbits[o] = INVALID_OBJECT_ID;
352                 }
353                 // put object into desired orbit
354                 m_orbits[orbit] = obj->ID();
355             } else {  // Log as an error, if no current orbit attempt to assign to a free orbit
356                 ErrorLogger() << "System::Insert() Planet " << obj->ID()
357                               << " requested orbit " << orbit
358                               << " in system " << ID()
359                               << ", which is occupied by" << m_orbits[orbit];
360                 const std::set<int>& free_orbits = FreeOrbits();
361                 if (free_orbits.size() > 0 && OrbitOfPlanet(obj->ID()) == -1) {
362                     int new_orbit = *(free_orbits.begin());
363                     m_orbits[new_orbit] = obj->ID();
364                     DebugLogger() << "System::Insert() Planet " << obj->ID()
365                                   << " assigned to orbit " << new_orbit;
366                 }
367             }
368         }
369         //TODO If planet not assigned to an orbit, reject insertion of planet, provide feedback to caller
370     }
371     // if not a planet, don't need to put into an orbit
372 
373     switch (obj->ObjectType()) {
374     case OBJ_SHIP: {
375         m_ships.insert(obj->ID());
376         if (std::shared_ptr<Ship> ship = std::dynamic_pointer_cast<Ship>(obj))
377             ship->SetArrivedOnTurn(CurrentTurn());
378         break;
379     }
380     case OBJ_FLEET: {
381         m_fleets.insert(obj->ID());
382         FleetsInsertedSignal({std::dynamic_pointer_cast<Fleet>(obj)});
383         break;
384     }
385     case OBJ_PLANET:
386         m_planets.insert(obj->ID());
387         break;
388     case OBJ_FIELD:
389         m_fields.insert(obj->ID());
390         break;
391     case OBJ_SYSTEM:
392         ErrorLogger() << "System::Insert inserting a system into another system...??";
393         break;
394     case OBJ_BUILDING:
395         m_buildings.insert(obj->ID());
396         break;
397     default:
398         ErrorLogger() << "System::Insert inserting an unknown object type";
399     }
400     m_objects.insert(obj->ID());
401 
402     StateChangedSignal();
403 }
404 
Remove(int id)405 void System::Remove(int id) {
406     if (id == INVALID_OBJECT_ID)
407         return;
408 
409     bool removed_fleet = false;
410 
411     auto it = m_fleets.find(id);
412     if (it != m_fleets.end()) {
413         m_fleets.erase(it);
414         removed_fleet = true;
415     }
416 
417     it = m_planets.find(id);
418     if (it != m_planets.end()) {
419         m_planets.erase(it);
420         for (int& planet_id : m_orbits)
421             if (planet_id == id)
422                 planet_id = INVALID_OBJECT_ID;
423     }
424 
425     m_ships.erase(id);
426     m_fields.erase(id);
427     m_buildings.erase(id);
428     m_objects.erase(id);
429 
430     if (removed_fleet) {
431         if (auto fleet = Objects().get<Fleet>(id))
432             FleetsRemovedSignal({fleet});
433     }
434     StateChangedSignal();
435 }
436 
SetStarType(StarType type)437 void System::SetStarType(StarType type) {
438     m_star = type;
439     if (m_star <= INVALID_STAR_TYPE || NUM_STAR_TYPES <= m_star)
440         ErrorLogger() << "System::SetStarType set star type to " << type;
441     StateChangedSignal();
442 }
443 
AddStarlane(int id)444 void System::AddStarlane(int id) {
445     if (!HasStarlaneTo(id) && id != this->ID()) {
446         m_starlanes_wormholes[id] = false;
447         StateChangedSignal();
448         TraceLogger() << "Added starlane from system " << this->Name() << " (" << this->ID() << ") system " << id;
449     }
450 }
451 
AddWormhole(int id)452 void System::AddWormhole(int id) {
453     if (!HasWormholeTo(id) && id != this->ID()) {
454         m_starlanes_wormholes[id] = true;
455         StateChangedSignal();
456     }
457 }
458 
RemoveStarlane(int id)459 bool System::RemoveStarlane(int id) {
460     bool retval = false;
461     if ((retval = HasStarlaneTo(id))) {
462         m_starlanes_wormholes.erase(id);
463         StateChangedSignal();
464     }
465     return retval;
466 }
467 
RemoveWormhole(int id)468 bool System::RemoveWormhole(int id) {
469     bool retval = false;
470     if ((retval = HasWormholeTo(id))) {
471         m_starlanes_wormholes.erase(id);
472         StateChangedSignal();
473     }
474     return retval;
475 }
476 
SetLastTurnBattleHere(int turn)477 void System::SetLastTurnBattleHere(int turn)
478 { m_last_turn_battle_here = turn; }
479 
ResetTargetMaxUnpairedMeters()480 void System::ResetTargetMaxUnpairedMeters() {
481     UniverseObject::ResetTargetMaxUnpairedMeters();
482 
483     // give systems base stealth slightly above zero, so that they can't be
484     // seen from a distance without high detection ability
485     if (Meter* stealth = GetMeter(METER_STEALTH)) {
486         stealth->ResetCurrent();
487         //stealth->AddToCurrent(0.01f);
488     }
489 }
490 
OrbitOccupied(int orbit) const491 bool System::OrbitOccupied(int orbit) const {
492     if (orbit < 0 || orbit >= static_cast<int>(m_orbits.size()))
493         return false;
494     return m_orbits[orbit] != INVALID_OBJECT_ID;
495 }
496 
PlanetInOrbit(int orbit) const497 int System::PlanetInOrbit(int orbit) const {
498     if (orbit < 0 || orbit >= static_cast<int>(m_orbits.size()))
499         return INVALID_OBJECT_ID;
500     return m_orbits[orbit];
501 }
502 
OrbitOfPlanet(int object_id) const503 int System::OrbitOfPlanet(int object_id) const {
504     if (object_id == INVALID_OBJECT_ID)
505         return -1;
506     for (int o = 0; o < static_cast<int>(m_orbits.size()); ++o)
507         if (m_orbits[o] == object_id)
508             return o;
509     return -1;
510 }
511 
FreeOrbits() const512 std::set<int> System::FreeOrbits() const {
513     std::set<int> retval;
514     for (int o = 0; o < static_cast<int>(m_orbits.size()); ++o)
515         if (m_orbits[o] == INVALID_OBJECT_ID)
516             retval.insert(o);
517     return retval;
518 }
519 
StarlanesWormholes() const520 const std::map<int, bool>& System::StarlanesWormholes() const
521 { return m_starlanes_wormholes; }
522 
VisibleStarlanesWormholes(int empire_id) const523 std::map<int, bool> System::VisibleStarlanesWormholes(int empire_id) const {
524     if (empire_id == ALL_EMPIRES)
525         return m_starlanes_wormholes;
526 
527     const Universe& universe = GetUniverse();
528     const ObjectMap& objects = universe.Objects();
529 
530 
531     Visibility this_system_vis = universe.GetObjectVisibilityByEmpire(this->ID(), empire_id);
532 
533     //visible starlanes are:
534     //  - those connected to systems with vis >= partial
535     //  - those with visible ships travelling along them
536 
537 
538     // return all starlanes if partially visible or better
539     if (this_system_vis >= VIS_PARTIAL_VISIBILITY)
540         return m_starlanes_wormholes;
541 
542 
543     // compile visible lanes connected to this only basically-visible system
544     std::map<int, bool> retval;
545 
546 
547     // check if any of the adjacent systems are partial or better visible
548     for (const auto& entry : m_starlanes_wormholes) {
549         int lane_end_sys_id = entry.first;
550         if (universe.GetObjectVisibilityByEmpire(lane_end_sys_id, empire_id) >= VIS_PARTIAL_VISIBILITY)
551             retval[lane_end_sys_id] = entry.second;
552     }
553 
554 
555     // early exit check... can't see any more lanes than exist, so don't need to check for more if all lanes are already visible
556     if (retval == m_starlanes_wormholes)
557         return retval;
558 
559 
560     // check if any fleets owned by empire are moving along a starlane connected to this system...
561 
562     // get moving fleets owned by empire
563     std::vector<std::shared_ptr<const Fleet>> moving_empire_fleets;
564     for (auto& object : objects.find(MovingFleetVisitor())) {
565         if (auto fleet = std::dynamic_pointer_cast<const Fleet>(object))
566             if (fleet->OwnedBy(empire_id))
567                 moving_empire_fleets.push_back(fleet);
568     }
569 
570     // add any lanes an owned fleet is moving along that connect to this system
571     for (auto& fleet : moving_empire_fleets) {
572         if (fleet->SystemID() != INVALID_OBJECT_ID) {
573             ErrorLogger() << "System::VisibleStarlanesWormholes somehow got a moving fleet that had a valid system id?";
574             continue;
575         }
576 
577         int prev_sys_id = fleet->PreviousSystemID();
578         int next_sys_id = fleet->NextSystemID();
579 
580         // see if previous or next system is this system, and if so, is other
581         // system on lane along which ship is moving one of this system's
582         // starlanes or wormholes?
583         int other_lane_end_sys_id = INVALID_OBJECT_ID;
584 
585         if (prev_sys_id == this->ID())
586             other_lane_end_sys_id = next_sys_id;
587         else if (next_sys_id == this->ID())
588             other_lane_end_sys_id = prev_sys_id;
589 
590         if (other_lane_end_sys_id != INVALID_OBJECT_ID) {
591             auto lane_it = m_starlanes_wormholes.find(other_lane_end_sys_id);
592             if (lane_it == m_starlanes_wormholes.end()) {
593                 ErrorLogger() << "System::VisibleStarlanesWormholes found an owned fleet moving along a starlane connected to this system that isn't also connected to one of this system's starlane-connected systems...?";
594                 continue;
595             }
596             retval[other_lane_end_sys_id] = lane_it->second;
597         }
598     }
599 
600     return retval;
601 }
602 
SetOverlayTexture(const std::string & texture,double size)603 void System::SetOverlayTexture(const std::string& texture, double size) {
604     m_overlay_texture = texture;
605     m_overlay_size = size;
606     StateChangedSignal();
607 }
608