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