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