1 #include "Planet.h"
2 
3 #include "Building.h"
4 #include "BuildingType.h"
5 #include "Condition.h"
6 #include "Fleet.h"
7 #include "Ship.h"
8 #include "System.h"
9 #include "Predicates.h"
10 #include "Species.h"
11 #include "Universe.h"
12 #include "Enums.h"
13 #include "ValueRef.h"
14 #include "../util/Logger.h"
15 #include "../util/GameRules.h"
16 #include "../util/OptionsDB.h"
17 #include "../util/Random.h"
18 #include "../util/Directories.h"
19 #include "../util/SitRepEntry.h"
20 #include "../util/i18n.h"
21 #include "../Empire/Empire.h"
22 #include "../Empire/EmpireManager.h"
23 
24 
25 namespace {
26     // high tilt is arbitrarily taken to mean 45 degrees or more
27     const float HIGH_TILT_THERESHOLD = 45.0f;
28 
SizeRotationFactor(PlanetSize size)29     float SizeRotationFactor(PlanetSize size) {
30         switch (size) {
31         case SZ_TINY:     return 1.5f;
32         case SZ_SMALL:    return 1.25f;
33         case SZ_MEDIUM:   return 1.0f;
34         case SZ_LARGE:    return 0.75f;
35         case SZ_HUGE:     return 0.5f;
36         case SZ_GASGIANT: return 0.25f;
37         default:          return 1.0f;
38         }
39     }
40 
41     static const std::string EMPTY_STRING;
42 
43     /** @content_tag{CTRL_STAT_SKIP_DEPOP} Do not count PopCenter%s with this tag for SpeciesPlanetsDepoped stat */
44     const std::string TAG_STAT_SKIP_DEPOP = "CTRL_STAT_SKIP_DEPOP";
45 }
46 
47 
48 ////////////////////////////////////////////////////////////
49 // Planet
50 ////////////////////////////////////////////////////////////
Planet()51 Planet::Planet() :
52     m_type(PT_TERRAN),
53     m_original_type(PT_TERRAN),
54     m_size(SZ_MEDIUM)
55 {
56     //DebugLogger() << "Planet::Planet()";
57     // assumes PopCenter and ResourceCenter don't need to be initialized, due to having been re-created
58     // in functional form by deserialization.  Also assumes planet-specific meters don't need to be re-added.
59 }
60 
Planet(PlanetType type,PlanetSize size)61 Planet::Planet(PlanetType type, PlanetSize size) :
62     m_type(type),
63     m_original_type(type),
64     m_size(size),
65     m_initial_orbital_position(RandZeroToOne() * 2 * 3.14159f),
66     m_axial_tilt(RandZeroToOne() * HIGH_TILT_THERESHOLD)
67 {
68     //DebugLogger() << "Planet::Planet(" << type << ", " << size <<")";
69     UniverseObject::Init();
70     PopCenter::Init();
71     ResourceCenter::Init();
72     Planet::Init();
73 
74     const double SPIN_STD_DEV = 0.1;
75     const double REVERSE_SPIN_CHANCE = 0.06;
76     m_rotational_period = RandGaussian(1.0, SPIN_STD_DEV) / SizeRotationFactor(m_size);
77     if (RandZeroToOne() < REVERSE_SPIN_CHANCE)
78         m_rotational_period = -m_rotational_period;
79 }
80 
Clone(int empire_id) const81 Planet* Planet::Clone(int empire_id) const {
82     Visibility vis = GetUniverse().GetObjectVisibilityByEmpire(this->ID(), empire_id);
83 
84     if (!(vis >= VIS_BASIC_VISIBILITY && vis <= VIS_FULL_VISIBILITY))
85         return nullptr;
86 
87     Planet* retval = new Planet();
88     retval->Copy(UniverseObject::shared_from_this(), empire_id);
89     return retval;
90 }
91 
Copy(std::shared_ptr<const UniverseObject> copied_object,int empire_id)92 void Planet::Copy(std::shared_ptr<const UniverseObject> copied_object, int empire_id) {
93     if (copied_object.get() == this)
94         return;
95     auto copied_planet = std::dynamic_pointer_cast<const Planet>(copied_object);
96     if (!copied_planet) {
97         ErrorLogger() << "Planet::Copy passed an object that wasn't a Planet";
98         return;
99     }
100 
101     int copied_object_id = copied_object->ID();
102     Visibility vis = GetUniverse().GetObjectVisibilityByEmpire(copied_object_id, empire_id);
103     auto visible_specials = GetUniverse().GetObjectVisibleSpecialsByEmpire(copied_object_id, empire_id);
104 
105     UniverseObject::Copy(copied_object, vis, visible_specials);
106     PopCenter::Copy(copied_planet, vis);
107     ResourceCenter::Copy(copied_planet, vis);
108 
109     if (vis >= VIS_BASIC_VISIBILITY) {
110         this->m_name =                      copied_planet->m_name;
111 
112         this->m_buildings =                 copied_planet->VisibleContainedObjectIDs(empire_id);
113         this->m_type =                      copied_planet->m_type;
114         this->m_original_type =             copied_planet->m_original_type;
115         this->m_size =                      copied_planet->m_size;
116         this->m_orbital_period =            copied_planet->m_orbital_period;
117         this->m_initial_orbital_position =  copied_planet->m_initial_orbital_position;
118         this->m_rotational_period =         copied_planet->m_rotational_period;
119         this->m_axial_tilt =                copied_planet->m_axial_tilt;
120         this->m_turn_last_conquered =       copied_planet->m_turn_last_conquered;
121         this->m_turn_last_colonized =       copied_planet->m_turn_last_colonized;
122 
123 
124         if (vis >= VIS_PARTIAL_VISIBILITY) {
125             if (vis >= VIS_FULL_VISIBILITY) {
126                 this->m_is_about_to_be_colonized =  copied_planet->m_is_about_to_be_colonized;
127                 this->m_is_about_to_be_invaded   =  copied_planet->m_is_about_to_be_invaded;
128                 this->m_is_about_to_be_bombarded =  copied_planet->m_is_about_to_be_bombarded;
129                 this->m_ordered_given_to_empire_id =copied_planet->m_ordered_given_to_empire_id;
130                 this->m_last_turn_attacked_by_ship= copied_planet->m_last_turn_attacked_by_ship;
131             } else {
132                 // copy system name if at partial visibility, as it won't be copied
133                 // by UniverseObject::Copy unless at full visibility, but players
134                 // should know planet names even if they don't own the planet
135                 GetUniverse().InhibitUniverseObjectSignals(true);
136                 this->Rename(copied_planet->Name());
137                 GetUniverse().InhibitUniverseObjectSignals(false);
138             }
139         }
140     }
141 }
142 
HostileToEmpire(int empire_id) const143 bool Planet::HostileToEmpire(int empire_id) const
144 {
145     if (OwnedBy(empire_id))
146         return false;
147 
148     // Empire owned planets are hostile to ALL_EMPIRES
149     if (empire_id == ALL_EMPIRES)
150         return !Unowned();
151 
152     // Unowned planets are only considered hostile if populated
153     auto pop_meter = GetMeter(METER_TARGET_POPULATION);
154     if (Unowned())
155         return pop_meter && (pop_meter->Current() != 0.0f);
156 
157     // both empires are normal empires
158     return Empires().GetDiplomaticStatus(Owner(), empire_id) == DIPLO_WAR;
159 }
160 
Tags() const161 std::set<std::string> Planet::Tags() const {
162     const Species* species = GetSpecies(SpeciesName());
163     if (!species)
164         return std::set<std::string>();
165     return species->Tags();
166 }
167 
HasTag(const std::string & name) const168 bool Planet::HasTag(const std::string& name) const {
169     const Species* species = GetSpecies(SpeciesName());
170 
171     return species && species->Tags().count(name);
172 }
173 
ObjectType() const174 UniverseObjectType Planet::ObjectType() const
175 { return OBJ_PLANET; }
176 
Dump(unsigned short ntabs) const177 std::string Planet::Dump(unsigned short ntabs) const {
178     std::stringstream os;
179     os << UniverseObject::Dump(ntabs);
180     os << PopCenter::Dump(ntabs);
181     os << ResourceCenter::Dump(ntabs);
182     os << " type: " << m_type
183        << " original type: " << m_original_type
184        << " size: " << m_size
185        << " rot period: " << m_rotational_period
186        << " axis tilt: " << m_axial_tilt
187        << " buildings: ";
188     for (auto it = m_buildings.begin(); it != m_buildings.end();) {
189         int building_id = *it;
190         ++it;
191         os << building_id << (it == m_buildings.end() ? "" : ", ");
192     }
193     if (m_is_about_to_be_colonized)
194         os << " (About to be Colonized)";
195     if (m_is_about_to_be_invaded)
196         os << " (About to be Invaded)";
197 
198     os << " colonized on turn: " << m_turn_last_colonized;
199     os << " conquered on turn: " << m_turn_last_conquered;
200     if (m_is_about_to_be_bombarded)
201         os << " (About to be Bombarded)";
202     if (m_ordered_given_to_empire_id != ALL_EMPIRES)
203         os << " (Ordered to be given to empire with id: " << m_ordered_given_to_empire_id << ")";
204     os << " last attacked on turn: " << m_last_turn_attacked_by_ship;
205 
206     return os.str();
207 }
208 
HabitableSize() const209 int Planet::HabitableSize() const {
210     const auto& gr = GetGameRules();
211     switch (m_size) {
212     case SZ_GASGIANT:   return gr.Get<int>("RULE_HABITABLE_SIZE_GASGIANT");   break;
213     case SZ_HUGE:       return gr.Get<int>("RULE_HABITABLE_SIZE_HUGE");   break;
214     case SZ_LARGE:      return gr.Get<int>("RULE_HABITABLE_SIZE_LARGE");   break;
215     case SZ_MEDIUM:     return gr.Get<int>("RULE_HABITABLE_SIZE_MEDIUM");   break;
216     case SZ_ASTEROIDS:  return gr.Get<int>("RULE_HABITABLE_SIZE_ASTEROIDS");   break;
217     case SZ_SMALL:      return gr.Get<int>("RULE_HABITABLE_SIZE_SMALL");   break;
218     case SZ_TINY:       return gr.Get<int>("RULE_HABITABLE_SIZE_TINY");   break;
219     default:            return 0;   break;
220     }
221 }
222 
Init()223 void Planet::Init() {
224     AddMeter(METER_SUPPLY);
225     AddMeter(METER_MAX_SUPPLY);
226     AddMeter(METER_STOCKPILE);
227     AddMeter(METER_MAX_STOCKPILE);
228     AddMeter(METER_SHIELD);
229     AddMeter(METER_MAX_SHIELD);
230     AddMeter(METER_DEFENSE);
231     AddMeter(METER_MAX_DEFENSE);
232     AddMeter(METER_TROOPS);
233     AddMeter(METER_MAX_TROOPS);
234     AddMeter(METER_DETECTION);
235     AddMeter(METER_REBEL_TROOPS);
236 }
237 
EnvironmentForSpecies(const std::string & species_name) const238 PlanetEnvironment Planet::EnvironmentForSpecies(const std::string& species_name/* = ""*/) const {
239     const Species* species = nullptr;
240     if (species_name.empty()) {
241         const std::string& this_planet_species_name = this->SpeciesName();
242         if (this_planet_species_name.empty())
243             return PE_UNINHABITABLE;
244         species = GetSpecies(this_planet_species_name);
245     } else {
246         species = GetSpecies(species_name);
247     }
248     if (!species) {
249         ErrorLogger() << "Planet::EnvironmentForSpecies couldn't get species with name \"" << species_name << "\"";
250         return PE_UNINHABITABLE;
251     }
252     return species->GetPlanetEnvironment(m_type);
253 }
254 
NextBetterPlanetTypeForSpecies(const std::string & species_name) const255 PlanetType Planet::NextBetterPlanetTypeForSpecies(const std::string& species_name/* = ""*/) const {
256     const Species* species = nullptr;
257     if (species_name.empty()) {
258         const std::string& this_planet_species_name = this->SpeciesName();
259         if (this_planet_species_name.empty())
260             return m_type;
261         species = GetSpecies(this_planet_species_name);
262     } else {
263         species = GetSpecies(species_name);
264     }
265     if (!species) {
266         ErrorLogger() << "Planet::NextBetterPlanetTypeForSpecies couldn't get species with name \"" << species_name << "\"";
267         return m_type;
268     }
269     return species->NextBetterPlanetType(m_type);
270 }
271 
272 namespace {
RingNextPlanetType(PlanetType current_type)273     PlanetType RingNextPlanetType(PlanetType current_type) {
274         PlanetType next(PlanetType(int(current_type)+1));
275         if (next >= PT_ASTEROIDS)
276             next = PT_SWAMP;
277         return next;
278     }
RingPreviousPlanetType(PlanetType current_type)279     PlanetType RingPreviousPlanetType(PlanetType current_type) {
280         PlanetType next(PlanetType(int(current_type)-1));
281         if (next <= INVALID_PLANET_TYPE)
282             next = PT_OCEAN;
283         return next;
284     }
285 }
286 
NextCloserToOriginalPlanetType() const287 PlanetType Planet::NextCloserToOriginalPlanetType() const {
288     if (m_type == INVALID_PLANET_TYPE ||
289         m_type == PT_GASGIANT ||
290         m_type == PT_ASTEROIDS ||
291         m_original_type == INVALID_PLANET_TYPE ||
292         m_original_type == PT_GASGIANT ||
293         m_original_type == PT_ASTEROIDS)
294     { return m_type; }
295 
296     if (m_type == m_original_type)
297         return m_type;
298 
299     PlanetType cur_type = m_type;
300     int cw_steps = 0;
301     while (cur_type != m_original_type) {
302         cw_steps++;
303         cur_type = RingNextPlanetType(cur_type);
304     }
305 
306     cur_type = m_type;
307     int ccw_steps = 0;
308     while (cur_type != m_original_type) {
309         ccw_steps++;
310         cur_type = RingPreviousPlanetType(cur_type);
311     }
312 
313     if (cw_steps <= ccw_steps)
314         return RingNextPlanetType(m_type);
315     return RingPreviousPlanetType(m_type);
316 }
317 
318 namespace {
LoopPlanetTypeIncrement(PlanetType initial_type,int step)319     PlanetType LoopPlanetTypeIncrement(PlanetType initial_type, int step) {
320         // avoid too large steps that would mess up enum arithmatic
321         if (std::abs(step) >= PT_ASTEROIDS) {
322             DebugLogger() << "LoopPlanetTypeIncrement giving too large step: " << step;
323             return initial_type;
324         }
325         // some types can't be terraformed
326         if (initial_type == PT_GASGIANT)
327             return PT_GASGIANT;
328         if (initial_type == PT_ASTEROIDS)
329             return PT_ASTEROIDS;
330         if (initial_type == INVALID_PLANET_TYPE)
331             return INVALID_PLANET_TYPE;
332         if (initial_type == NUM_PLANET_TYPES)
333             return NUM_PLANET_TYPES;
334         // calculate next planet type, accounting for loop arounds
335         PlanetType new_type(PlanetType(initial_type + step));
336         if (new_type >= PT_ASTEROIDS)
337             new_type = PlanetType(new_type - PT_ASTEROIDS);
338         else if (new_type <= INVALID_PLANET_TYPE)
339             new_type = PlanetType(new_type + PT_ASTEROIDS);
340         return new_type;
341     }
342 }
343 
ClockwiseNextPlanetType() const344 PlanetType Planet::ClockwiseNextPlanetType() const
345 { return LoopPlanetTypeIncrement(m_type, 1); }
346 
CounterClockwiseNextPlanetType() const347 PlanetType Planet::CounterClockwiseNextPlanetType() const
348 { return LoopPlanetTypeIncrement(m_type, -1); }
349 
TypeDifference(PlanetType type1,PlanetType type2)350 int Planet::TypeDifference(PlanetType type1, PlanetType type2) {
351     // no distance defined for invalid types
352     if (type1 == INVALID_PLANET_TYPE || type2 == INVALID_PLANET_TYPE)
353         return 0;
354     // if the same, distance is zero
355     if (type1 == type2)
356         return 0;
357     // no distance defined for asteroids or gas giants with anything else
358     if (type1 == PT_ASTEROIDS || type1 == PT_GASGIANT || type2 == PT_ASTEROIDS || type2 == PT_GASGIANT)
359         return 0;
360     // find distance around loop:
361     //
362     //  0  1  2
363     //  8     3
364     //  7     4
365     //    6 5
366     int diff = std::abs(int(type1) - int(type2));
367     // raw_dist -> actual dist
368     //  0 to 4       0 to 4
369     //  5 to 8       4 to 1
370     if (diff > 4)
371         diff = 9 - diff;
372     //std::cout << "typedifference type1: " << int(type1) << "  type2: " << int(type2) << "  diff: " << diff << std::endl;
373     return diff;
374 }
375 
376 namespace {
PlanetSizeIncrement(PlanetSize initial_size,int step)377     PlanetSize PlanetSizeIncrement(PlanetSize initial_size, int step) {
378         // some sizes don't have meaningful increments
379         if (initial_size == SZ_GASGIANT)
380             return SZ_GASGIANT;
381         if (initial_size == SZ_ASTEROIDS)
382             return SZ_ASTEROIDS;
383         if (initial_size == SZ_NOWORLD)
384             return SZ_NOWORLD;
385         if (initial_size == INVALID_PLANET_SIZE)
386             return INVALID_PLANET_SIZE;
387         if (initial_size == NUM_PLANET_SIZES)
388             return NUM_PLANET_SIZES;
389         // calculate next planet size
390         PlanetSize new_type(PlanetSize(initial_size + step));
391         if (new_type >= SZ_HUGE)
392             return SZ_HUGE;
393         if (new_type <= SZ_TINY)
394             return SZ_TINY;
395         return new_type;
396     }
397 }
398 
NextLargerPlanetSize() const399 PlanetSize Planet::NextLargerPlanetSize() const
400 { return PlanetSizeIncrement(m_size, 1); }
401 
NextSmallerPlanetSize() const402 PlanetSize Planet::NextSmallerPlanetSize() const
403 { return PlanetSizeIncrement(m_size, -1); }
404 
OrbitalPeriod() const405 float Planet::OrbitalPeriod() const
406 { return m_orbital_period; }
407 
InitialOrbitalPosition() const408 float Planet::InitialOrbitalPosition() const
409 { return m_initial_orbital_position; }
410 
OrbitalPositionOnTurn(int turn) const411 float Planet::OrbitalPositionOnTurn(int turn) const
412 { return m_initial_orbital_position + OrbitalPeriod() * 2.0 * 3.1415926 / 4 * turn; }
413 
RotationalPeriod() const414 float Planet::RotationalPeriod() const
415 { return m_rotational_period; }
416 
AxialTilt() const417 float Planet::AxialTilt() const
418 { return m_axial_tilt; }
419 
Accept(const UniverseObjectVisitor & visitor) const420 std::shared_ptr<UniverseObject> Planet::Accept(const UniverseObjectVisitor& visitor) const
421 { return visitor.Visit(std::const_pointer_cast<Planet>(std::static_pointer_cast<const Planet>(UniverseObject::shared_from_this()))); }
422 
GetMeter(MeterType type)423 Meter* Planet::GetMeter(MeterType type)
424 { return UniverseObject::GetMeter(type); }
425 
GetMeter(MeterType type) const426 const Meter* Planet::GetMeter(MeterType type) const
427 { return UniverseObject::GetMeter(type); }
428 
CardinalSuffix() const429 std::string Planet::CardinalSuffix() const {
430     std::string retval = "";
431     // Early return for invalid ID
432     if (ID() == INVALID_OBJECT_ID) {
433         WarnLogger() << "Planet " << Name() << " has invalid ID";
434         return retval;
435     }
436 
437     auto cur_system = Objects().get<System>(SystemID());
438     // Early return for no system
439     if (!cur_system) {
440         ErrorLogger() << "Planet " << Name() << "(" << ID()
441                       << ") not assigned to a system";
442         return retval;
443     }
444 
445     // Early return for unknown orbit
446     if (cur_system->OrbitOfPlanet(ID()) < 0) {
447         WarnLogger() << "Planet " << Name() << "(" << ID() << ") "
448                      << "has no current orbit";
449         retval.append(RomanNumber(1));
450         return retval;
451     }
452 
453     int num_planets_lteq = 0;  // number of planets at this orbit or smaller
454     int num_planets_total = 0;
455     bool prior_current_planet = true;
456 
457     for (int sys_orbit : cur_system->PlanetIDsByOrbit()) {
458         if (sys_orbit == INVALID_OBJECT_ID)
459             continue;
460 
461         // all other planets are in further orbits
462         if (sys_orbit == ID()) {
463             prior_current_planet = false;
464             ++num_planets_total;
465             ++num_planets_lteq;
466             continue;
467         }
468 
469         PlanetType other_planet_type = Objects().get<Planet>(sys_orbit)->Type();
470         if (other_planet_type == INVALID_PLANET_TYPE)
471             continue;
472 
473         // only increment suffix for non-asteroid planets
474         if (Type() != PT_ASTEROIDS) {
475             if (other_planet_type != PT_ASTEROIDS) {
476                 ++num_planets_total;
477                 if (prior_current_planet)
478                     ++num_planets_lteq;
479             }
480         } else {
481             // unless the planet being named is an asteroid
482             // then only increment suffix for asteroid planets
483             if (other_planet_type == PT_ASTEROIDS) {
484                 ++num_planets_total;
485                 if (prior_current_planet)
486                     ++num_planets_lteq;
487             }
488         }
489     }
490 
491     // Planets are grouped into asteroids, and non-asteroids
492     if (Type() != PT_ASTEROIDS) {
493         retval.append(RomanNumber(num_planets_lteq));
494     } else {
495         // Asteroids receive a localized prefix
496         retval.append(UserString("NEW_ASTEROIDS_SUFFIX"));
497         // If no other asteroids in this system, do not append an ordinal
498         if (num_planets_total > 1)
499             retval.append(" " + RomanNumber(num_planets_lteq));
500     }
501     return retval;
502 }
503 
ContainerObjectID() const504 int Planet::ContainerObjectID() const
505 { return this->SystemID(); }
506 
ContainedObjectIDs() const507 const std::set<int>& Planet::ContainedObjectIDs() const
508 { return m_buildings; }
509 
Contains(int object_id) const510 bool Planet::Contains(int object_id) const
511 { return object_id != INVALID_OBJECT_ID && m_buildings.count(object_id); }
512 
ContainedBy(int object_id) const513 bool Planet::ContainedBy(int object_id) const
514 { return object_id != INVALID_OBJECT_ID && this->SystemID() == object_id; }
515 
AvailableFoci() const516 std::vector<std::string> Planet::AvailableFoci() const {
517     std::vector<std::string> retval;
518     auto this_planet = std::dynamic_pointer_cast<const Planet>(UniverseObject::shared_from_this());
519     if (!this_planet)
520         return retval;
521     ScriptingContext context(this_planet);
522     if (const auto* species = GetSpecies(this_planet->SpeciesName())) {
523         for (const auto& focus_type : species->Foci()) {
524             if (const auto* location = focus_type.Location()) {
525                 if (location->Eval(context, this_planet))
526                     retval.push_back(focus_type.Name());
527             }
528         }
529     }
530 
531     return retval;
532 }
533 
FocusIcon(const std::string & focus_name) const534 const std::string& Planet::FocusIcon(const std::string& focus_name) const {
535     if (const Species* species = GetSpecies(this->SpeciesName())) {
536         for (const FocusType& focus_type : species->Foci()) {
537             if (focus_type.Name() == focus_name)
538                 return focus_type.Graphic();
539         }
540     }
541     return EMPTY_STRING;
542 }
543 
SetType(PlanetType type)544 void Planet::SetType(PlanetType type) {
545     if (type <= INVALID_PLANET_TYPE)
546         type = PT_SWAMP;
547     if (NUM_PLANET_TYPES <= type)
548         type = PT_GASGIANT;
549     m_type = type;
550     StateChangedSignal();
551 }
552 
SetOriginalType(PlanetType type)553 void Planet::SetOriginalType(PlanetType type) {
554     if (type <= INVALID_PLANET_TYPE)
555         type = PT_SWAMP;
556     if (NUM_PLANET_TYPES <= type)
557         type = PT_GASGIANT;
558     m_original_type = type;
559     StateChangedSignal();
560 }
561 
SetSize(PlanetSize size)562 void Planet::SetSize(PlanetSize size) {
563     if (size <= SZ_NOWORLD)
564         size = SZ_TINY;
565     if (NUM_PLANET_SIZES <= size)
566         size = SZ_GASGIANT;
567     m_size = size;
568     StateChangedSignal();
569 }
570 
SetRotationalPeriod(float days)571 void Planet::SetRotationalPeriod(float days)
572 { m_rotational_period = days; }
573 
SetHighAxialTilt()574 void Planet::SetHighAxialTilt() {
575     const double MAX_TILT = 90.0;
576     m_axial_tilt = HIGH_TILT_THERESHOLD + RandZeroToOne() * (MAX_TILT - HIGH_TILT_THERESHOLD);
577 }
578 
AddBuilding(int building_id)579 void Planet::AddBuilding(int building_id) {
580     size_t buildings_size = m_buildings.size();
581     m_buildings.insert(building_id);
582     if (buildings_size != m_buildings.size())
583         StateChangedSignal();
584     // expect calling code to set building's planet
585 }
586 
RemoveBuilding(int building_id)587 bool Planet::RemoveBuilding(int building_id) {
588     if (m_buildings.count(building_id)) {
589         m_buildings.erase(building_id);
590         StateChangedSignal();
591         return true;
592     }
593     return false;
594 }
595 
Reset()596 void Planet::Reset() {
597     PopCenter::Reset();
598     ResourceCenter::Reset();
599 
600     GetMeter(METER_SUPPLY)->Reset();
601     GetMeter(METER_MAX_SUPPLY)->Reset();
602     GetMeter(METER_STOCKPILE)->Reset();
603     GetMeter(METER_MAX_STOCKPILE)->Reset();
604     GetMeter(METER_SHIELD)->Reset();
605     GetMeter(METER_MAX_SHIELD)->Reset();
606     GetMeter(METER_DEFENSE)->Reset();
607     GetMeter(METER_MAX_DEFENSE)->Reset();
608     GetMeter(METER_DETECTION)->Reset();
609     GetMeter(METER_REBEL_TROOPS)->Reset();
610 
611     if (m_is_about_to_be_colonized && !OwnedBy(ALL_EMPIRES)) {
612         for (const auto& building : Objects().find<Building>(m_buildings)) {
613             if (!building)
614                 continue;
615             building->Reset();
616         }
617     }
618 
619     //m_turn_last_colonized left unchanged
620     //m_turn_last_conquered left unchanged
621     m_is_about_to_be_colonized = false;
622     m_is_about_to_be_invaded = false;
623     m_is_about_to_be_bombarded = false;
624     SetOwner(ALL_EMPIRES);
625 }
626 
Depopulate()627 void Planet::Depopulate() {
628     PopCenter::Depopulate();
629 
630     GetMeter(METER_INDUSTRY)->Reset();
631     GetMeter(METER_RESEARCH)->Reset();
632     GetMeter(METER_TRADE)->Reset();
633     GetMeter(METER_CONSTRUCTION)->Reset();
634 
635     ClearFocus();
636 }
637 
Conquer(int conquerer)638 void Planet::Conquer(int conquerer) {
639     m_turn_last_conquered = CurrentTurn();
640 
641     // deal with things on production queue located at this planet
642     Empire::ConquerProductionQueueItemsAtLocation(ID(), conquerer);
643 
644     // deal with UniverseObjects (eg. buildings) located on this planet
645     for (auto& building : Objects().find<Building>(m_buildings)) {
646         const BuildingType* type = GetBuildingType(building->BuildingTypeName());
647 
648         // determine what to do with building of this type...
649         const CaptureResult cap_result = type->GetCaptureResult(building->Owner(), conquerer, this->ID(), false);
650 
651         if (cap_result == CR_CAPTURE) {
652             // replace ownership
653             building->SetOwner(conquerer);
654         } else if (cap_result == CR_DESTROY) {
655             // destroy object
656             //DebugLogger() << "Planet::Conquer destroying object: " << building->Name();
657             this->RemoveBuilding(building->ID());
658             if (auto system = Objects().get<System>(this->SystemID()))
659                 system->Remove(building->ID());
660             GetUniverse().Destroy(building->ID());
661         } else if (cap_result == CR_RETAIN) {
662             // do nothing
663         }
664     }
665 
666     // replace ownership
667     SetOwner(conquerer);
668 
669     GetMeter(METER_SUPPLY)->SetCurrent(0.0f);
670     GetMeter(METER_SUPPLY)->BackPropagate();
671     GetMeter(METER_STOCKPILE)->SetCurrent(0.0f);
672     GetMeter(METER_STOCKPILE)->BackPropagate();
673     GetMeter(METER_INDUSTRY)->SetCurrent(0.0f);
674     GetMeter(METER_INDUSTRY)->BackPropagate();
675     GetMeter(METER_RESEARCH)->SetCurrent(0.0f);
676     GetMeter(METER_RESEARCH)->BackPropagate();
677     GetMeter(METER_TRADE)->SetCurrent(0.0f);
678     GetMeter(METER_TRADE)->BackPropagate();
679     GetMeter(METER_CONSTRUCTION)->SetCurrent(0.0f);
680     GetMeter(METER_CONSTRUCTION)->BackPropagate();
681     GetMeter(METER_DEFENSE)->SetCurrent(0.0f);
682     GetMeter(METER_DEFENSE)->BackPropagate();
683     GetMeter(METER_SHIELD)->SetCurrent(0.0f);
684     GetMeter(METER_SHIELD)->BackPropagate();
685     GetMeter(METER_HAPPINESS)->SetCurrent(0.0f);
686     GetMeter(METER_HAPPINESS)->BackPropagate();
687     GetMeter(METER_DETECTION)->SetCurrent(0.0f);
688     GetMeter(METER_DETECTION)->BackPropagate();
689 }
690 
SetSpecies(const std::string & species_name)691 void Planet::SetSpecies(const std::string& species_name) {
692     if (SpeciesName().empty() && !species_name.empty())
693         m_turn_last_colonized = CurrentTurn();  // if setting species with an effect, not via Colonize, consider it a colonization when there was no previous species set
694     PopCenter::SetSpecies(species_name);
695 }
696 
Colonize(int empire_id,const std::string & species_name,double population)697 bool Planet::Colonize(int empire_id, const std::string& species_name, double population) {
698     const Species* species = nullptr;
699 
700     // if desired pop > 0, we want a colony, not an outpost, so we need to do some checks
701     if (population > 0.0) {
702         // check if specified species exists and get reference
703         species = GetSpecies(species_name);
704         if (!species) {
705             ErrorLogger() << "Planet::Colonize couldn't get species: " << species_name;
706             return false;
707         }
708         // check if specified species can colonize this planet
709         if (EnvironmentForSpecies(species_name) < PE_HOSTILE) {
710             ErrorLogger() << "Planet::Colonize: can't colonize planet with species " << species_name << " because planet is "
711                           << m_type << " which for that species is environment: " << EnvironmentForSpecies(species_name);
712             return false;
713         }
714     }
715 
716     // reset the planet to unowned/unpopulated
717     if (!OwnedBy(empire_id)) {
718         Reset();
719     } else {
720         PopCenter::Reset();
721         for (const auto& building : Objects().find<Building>(m_buildings)) {
722             if (!building)
723                 continue;
724             building->Reset();
725         }
726         m_is_about_to_be_colonized = false;
727         m_is_about_to_be_invaded = false;
728         m_is_about_to_be_bombarded = false;
729         SetOwner(ALL_EMPIRES);
730     }
731 
732     // if desired pop > 0, we want a colony, not an outpost, so we have to set the colony species
733     if (population > 0.0)
734         SetSpecies(species_name);
735     m_turn_last_colonized = CurrentTurn();  // may be redundant with same in SetSpecies, but here occurrs always, whereas in SetSpecies is only done if species is initially empty
736 
737     // find a default focus. use first defined available focus.
738     // AvailableFoci function should return a vector of all names of
739     // available foci.
740     auto available_foci = AvailableFoci();
741     if (species && !available_foci.empty()) {
742         bool found_preference = false;
743         for (const auto& focus : available_foci) {
744             if (!focus.empty() && focus == species->PreferredFocus()) {
745                 SetFocus(focus);
746                 found_preference = true;
747                 break;
748             }
749         }
750 
751         if (!found_preference)
752             SetFocus(*available_foci.begin());
753     } else {
754         DebugLogger() << "Planet::Colonize unable to find a focus to set for species " << species_name;
755     }
756 
757     // set colony population
758     GetMeter(METER_POPULATION)->SetCurrent(population);
759     GetMeter(METER_TARGET_POPULATION)->SetCurrent(population);
760     BackPropagateMeters();
761 
762 
763     // set specified empire as owner
764     SetOwner(empire_id);
765 
766     // if there are buildings on the planet, set the specified empire as their owner too
767     for (auto& building : Objects().find<Building>(BuildingIDs()))
768         building->SetOwner(empire_id);
769 
770     return true;
771 }
772 
SetIsAboutToBeColonized(bool b)773 void Planet::SetIsAboutToBeColonized(bool b) {
774     bool initial_status = m_is_about_to_be_colonized;
775     if (b == initial_status) return;
776     m_is_about_to_be_colonized = b;
777     StateChangedSignal();
778 }
779 
ResetIsAboutToBeColonized()780 void Planet::ResetIsAboutToBeColonized()
781 { SetIsAboutToBeColonized(false); }
782 
SetIsAboutToBeInvaded(bool b)783 void Planet::SetIsAboutToBeInvaded(bool b) {
784     bool initial_status = m_is_about_to_be_invaded;
785     if (b == initial_status) return;
786     m_is_about_to_be_invaded = b;
787     StateChangedSignal();
788 }
789 
ResetIsAboutToBeInvaded()790 void Planet::ResetIsAboutToBeInvaded()
791 { SetIsAboutToBeInvaded(false); }
792 
SetIsAboutToBeBombarded(bool b)793 void Planet::SetIsAboutToBeBombarded(bool b) {
794     bool initial_status = m_is_about_to_be_bombarded;
795     if (b == initial_status) return;
796     m_is_about_to_be_bombarded = b;
797     StateChangedSignal();
798 }
799 
ResetIsAboutToBeBombarded()800 void Planet::ResetIsAboutToBeBombarded()
801 { SetIsAboutToBeBombarded(false); }
802 
SetGiveToEmpire(int empire_id)803 void Planet::SetGiveToEmpire(int empire_id) {
804     if (empire_id == m_ordered_given_to_empire_id) return;
805     m_ordered_given_to_empire_id = empire_id;
806     StateChangedSignal();
807 }
808 
ClearGiveToEmpire()809 void Planet::ClearGiveToEmpire()
810 { SetGiveToEmpire(ALL_EMPIRES); }
811 
SetLastTurnAttackedByShip(int turn)812 void Planet::SetLastTurnAttackedByShip(int turn)
813 { m_last_turn_attacked_by_ship = turn; }
814 
SetSurfaceTexture(const std::string & texture)815 void Planet::SetSurfaceTexture(const std::string& texture) {
816     m_surface_texture = texture;
817     StateChangedSignal();
818 }
819 
PopGrowthProductionResearchPhase()820 void Planet::PopGrowthProductionResearchPhase() {
821     UniverseObject::PopGrowthProductionResearchPhase();
822     PopCenterPopGrowthProductionResearchPhase();
823 
824     // should be run after a meter update, but before a backpropagation, so check current, not initial, meter values
825 
826     // check for colonies without positive population, and change to outposts
827     if (!SpeciesName().empty() && GetMeter(METER_POPULATION)->Current() <= 0.0f) {
828         if (Empire* empire = GetEmpire(this->Owner())) {
829             empire->AddSitRepEntry(CreatePlanetDepopulatedSitRep(this->ID()));
830 
831             if (!HasTag(TAG_STAT_SKIP_DEPOP))
832                 empire->RecordPlanetDepopulated(*this);
833         }
834         // remove species
835         PopCenter::Reset();
836     }
837 
838     StateChangedSignal();
839 }
840 
ResetTargetMaxUnpairedMeters()841 void Planet::ResetTargetMaxUnpairedMeters() {
842     UniverseObject::ResetTargetMaxUnpairedMeters();
843     ResourceCenterResetTargetMaxUnpairedMeters();
844     PopCenterResetTargetMaxUnpairedMeters();
845 
846     GetMeter(METER_MAX_SUPPLY)->ResetCurrent();
847     GetMeter(METER_MAX_STOCKPILE)->ResetCurrent();
848     GetMeter(METER_MAX_SHIELD)->ResetCurrent();
849     GetMeter(METER_MAX_DEFENSE)->ResetCurrent();
850     GetMeter(METER_MAX_TROOPS)->ResetCurrent();
851     GetMeter(METER_REBEL_TROOPS)->ResetCurrent();
852     GetMeter(METER_DETECTION)->ResetCurrent();
853 }
854 
ClampMeters()855 void Planet::ClampMeters() {
856     UniverseObject::ClampMeters();
857     ResourceCenterClampMeters();
858     PopCenterClampMeters();
859 
860     UniverseObject::GetMeter(METER_MAX_SHIELD)->ClampCurrentToRange();
861     UniverseObject::GetMeter(METER_SHIELD)->ClampCurrentToRange(Meter::DEFAULT_VALUE, UniverseObject::GetMeter(METER_MAX_SHIELD)->Current());
862     UniverseObject::GetMeter(METER_MAX_DEFENSE)->ClampCurrentToRange();
863     UniverseObject::GetMeter(METER_DEFENSE)->ClampCurrentToRange(Meter::DEFAULT_VALUE, UniverseObject::GetMeter(METER_MAX_DEFENSE)->Current());
864     UniverseObject::GetMeter(METER_MAX_TROOPS)->ClampCurrentToRange();
865     UniverseObject::GetMeter(METER_TROOPS)->ClampCurrentToRange(Meter::DEFAULT_VALUE, UniverseObject::GetMeter(METER_MAX_TROOPS)->Current());
866     UniverseObject::GetMeter(METER_MAX_SUPPLY)->ClampCurrentToRange();
867     UniverseObject::GetMeter(METER_SUPPLY)->ClampCurrentToRange(Meter::DEFAULT_VALUE, UniverseObject::GetMeter(METER_MAX_SUPPLY)->Current());
868     UniverseObject::GetMeter(METER_MAX_STOCKPILE)->ClampCurrentToRange();
869     UniverseObject::GetMeter(METER_STOCKPILE)->ClampCurrentToRange(Meter::DEFAULT_VALUE, UniverseObject::GetMeter(METER_MAX_STOCKPILE)->Current());
870 
871     UniverseObject::GetMeter(METER_REBEL_TROOPS)->ClampCurrentToRange();
872     UniverseObject::GetMeter(METER_DETECTION)->ClampCurrentToRange();
873 }
874