1 #include "Ship.h"
2 
3 #include "../util/i18n.h"
4 #include "../util/Logger.h"
5 #include "../util/Random.h"
6 #include "../util/AppInterface.h"
7 #include "../util/GameRules.h"
8 #include "Fleet.h"
9 #include "Predicates.h"
10 #include "ShipDesign.h"
11 #include "ShipPart.h"
12 #include "ShipHull.h"
13 #include "Species.h"
14 #include "Universe.h"
15 #include "Enums.h"
16 #include "../Empire/Empire.h"
17 #include "../Empire/EmpireManager.h"
18 
19 #include <boost/lexical_cast.hpp>
20 
21 class Species;
22 const Species* GetSpecies(const std::string& name);
23 
Ship()24 Ship::Ship()
25 {}
26 
Ship(int empire_id,int design_id,const std::string & species_name,int produced_by_empire_id)27 Ship::Ship(int empire_id, int design_id, const std::string& species_name,
28            int produced_by_empire_id/* = ALL_EMPIRES*/) :
29     m_design_id(design_id),
30     m_species_name(species_name),
31     m_produced_by_empire_id(produced_by_empire_id),
32     m_arrived_on_turn(CurrentTurn()),
33     m_last_resupplied_on_turn(CurrentTurn())
34 {
35     if (!GetShipDesign(design_id))
36         throw std::invalid_argument("Attempted to construct a Ship with an invalid design id");
37 
38     if (!m_species_name.empty() && !GetSpecies(m_species_name))
39         DebugLogger() << "Ship created with invalid species name: " << m_species_name;
40 
41     SetOwner(empire_id);
42 
43     UniverseObject::Init();
44 
45     AddMeter(METER_FUEL);
46     AddMeter(METER_MAX_FUEL);
47     AddMeter(METER_SHIELD);
48     AddMeter(METER_MAX_SHIELD);
49     AddMeter(METER_DETECTION);
50     AddMeter(METER_STRUCTURE);
51     AddMeter(METER_MAX_STRUCTURE);
52     AddMeter(METER_SPEED);
53     AddMeter(METER_TARGET_INDUSTRY);
54     AddMeter(METER_INDUSTRY);
55     AddMeter(METER_TARGET_RESEARCH);
56     AddMeter(METER_RESEARCH);
57     AddMeter(METER_TARGET_TRADE);
58     AddMeter(METER_TRADE);
59 
60     const std::vector<std::string>& part_names = Design()->Parts();
61     for (const std::string& part_name : part_names) {
62         if (!part_name.empty()) {
63             const ShipPart* part = GetShipPart(part_name);
64             if (!part) {
65                 ErrorLogger() << "Ship::Ship couldn't get part with name " << part_name;
66                 continue;
67             }
68 
69             switch (part->Class()) {
70             case PC_COLONY:
71             case PC_TROOPS: {
72                 m_part_meters[{METER_CAPACITY, part->Name()}];
73                 break;
74             }
75             case PC_DIRECT_WEAPON:      // capacity is damage, secondary stat is shots per attack
76             case PC_FIGHTER_HANGAR: {   // capacity is how many fighters contained, secondary stat is damage per fighter attack
77                 m_part_meters[{METER_SECONDARY_STAT, part->Name()}];
78                 m_part_meters[{METER_MAX_SECONDARY_STAT, part->Name()}];
79                 // intentionally no break here
80             }
81             case PC_FIGHTER_BAY: {      // capacity is how many fighters launched per combat round
82                 m_part_meters[{METER_CAPACITY, part->Name()}];
83                 m_part_meters[{METER_MAX_CAPACITY, part->Name()}];
84                 break;
85             }
86             default:
87                 break;
88             }
89         }
90     }
91 }
92 
Clone(int empire_id) const93 Ship* Ship::Clone(int empire_id) const {
94     Visibility vis = GetUniverse().GetObjectVisibilityByEmpire(this->ID(), empire_id);
95 
96     if (!(vis >= VIS_BASIC_VISIBILITY && vis <= VIS_FULL_VISIBILITY))
97         return nullptr;
98 
99     Ship* retval = new Ship();
100     retval->Copy(shared_from_this(), empire_id);
101     return retval;
102 }
103 
Copy(std::shared_ptr<const UniverseObject> copied_object,int empire_id)104 void Ship::Copy(std::shared_ptr<const UniverseObject> copied_object, int empire_id) {
105     if (copied_object.get() == this)
106         return;
107     auto copied_ship = std::dynamic_pointer_cast<const Ship>(copied_object);
108     if (!copied_ship) {
109         ErrorLogger() << "Ship::Copy passed an object that wasn't a Ship";
110         return;
111     }
112 
113     int copied_object_id = copied_object->ID();
114     Visibility vis = GetUniverse().GetObjectVisibilityByEmpire(copied_object_id, empire_id);
115     auto visible_specials = GetUniverse().GetObjectVisibleSpecialsByEmpire(copied_object_id, empire_id);
116 
117     UniverseObject::Copy(copied_object, vis, visible_specials);;
118 
119     if (vis >= VIS_BASIC_VISIBILITY) {
120         if (this->m_fleet_id != copied_ship->m_fleet_id) {
121             // as with other containers, removal from the old container is triggered by the contained Object; removal from System is handled by UniverseObject::Copy
122             if (auto old_fleet = Objects().get<Fleet>(this->m_fleet_id))
123                 old_fleet->RemoveShips({this->ID()});
124             this->m_fleet_id = copied_ship->m_fleet_id; // as with other containers (Systems), actual insertion into fleet ships set is handled by the fleet
125         }
126 
127         if (vis >= VIS_PARTIAL_VISIBILITY) {
128             if (this->Unowned())
129                 this->m_name =                  copied_ship->m_name;
130 
131             this->m_design_id =                 copied_ship->m_design_id;
132             this->m_part_meters =               copied_ship->m_part_meters;
133             this->m_species_name =              copied_ship->m_species_name;
134 
135             this->m_last_turn_active_in_combat= copied_ship->m_last_turn_active_in_combat;
136             this->m_produced_by_empire_id =     copied_ship->m_produced_by_empire_id;
137             this->m_arrived_on_turn =           copied_ship->m_arrived_on_turn;
138             this->m_last_resupplied_on_turn =   copied_ship->m_last_resupplied_on_turn;
139 
140             if (vis >= VIS_FULL_VISIBILITY) {
141                 this->m_ordered_scrapped =          copied_ship->m_ordered_scrapped;
142                 this->m_ordered_colonize_planet_id= copied_ship->m_ordered_colonize_planet_id;
143                 this->m_ordered_invade_planet_id  = copied_ship->m_ordered_invade_planet_id;
144                 this->m_ordered_bombard_planet_id = copied_ship->m_ordered_bombard_planet_id;
145             }
146         }
147     }
148 }
149 
HostileToEmpire(int empire_id) const150 bool Ship::HostileToEmpire(int empire_id) const
151 {
152     if (OwnedBy(empire_id))
153         return false;
154     return empire_id == ALL_EMPIRES || Unowned() ||
155            Empires().GetDiplomaticStatus(Owner(), empire_id) == DIPLO_WAR;
156 }
157 
Tags() const158 std::set<std::string> Ship::Tags() const {
159     std::set<std::string> retval;
160 
161     const ShipDesign* design = GetShipDesign(m_design_id);
162     if (!design)
163         return retval;
164 
165     const ShipHull* hull = ::GetShipHull(design->Hull());
166     if (!hull)
167         return retval;
168     retval.insert(hull->Tags().begin(), hull->Tags().end());
169 
170     const std::vector<std::string>& parts = design->Parts();
171     if (parts.empty())
172         return retval;
173 
174     for (const std::string& part_name : parts) {
175         if (const ShipPart* part = GetShipPart(part_name)) {
176             retval.insert(part->Tags().begin(), part->Tags().end());
177         }
178     }
179 
180     return retval;
181 }
182 
HasTag(const std::string & name) const183 bool Ship::HasTag(const std::string& name) const {
184     const ShipDesign* design = GetShipDesign(m_design_id);
185     if (design) {
186         // check hull for tag
187         const ShipHull* hull = ::GetShipHull(design->Hull());
188         if (hull && hull->Tags().count(name))
189             return true;
190 
191         // check parts for tag
192         for (const std::string& part_name : design->Parts()) {
193             const ShipPart* part = GetShipPart(part_name);
194             if (part && part->Tags().count(name))
195                 return true;
196         }
197     }
198     // check species for tag
199     const Species* species = GetSpecies(SpeciesName());
200     if (species && species->Tags().count(name))
201         return true;
202 
203     return false;
204 }
205 
ObjectType() const206 UniverseObjectType Ship::ObjectType() const
207 { return OBJ_SHIP; }
208 
ContainedBy(int object_id) const209 bool Ship::ContainedBy(int object_id) const {
210     return object_id != INVALID_OBJECT_ID
211         && (    object_id == m_fleet_id
212             ||  object_id == this->SystemID());
213 }
214 
Dump(unsigned short ntabs) const215 std::string Ship::Dump(unsigned short ntabs) const {
216     std::stringstream os;
217     os << UniverseObject::Dump(ntabs);
218     os << " design id: " << m_design_id
219        << " fleet id: " << m_fleet_id
220        << " species name: " << m_species_name
221        << " produced by empire id: " << m_produced_by_empire_id
222        << " arrived on turn: " << m_arrived_on_turn
223        << " last resupplied on turn: " << m_last_resupplied_on_turn;
224     if (!m_part_meters.empty()) {
225         os << " part meters: ";
226         for (const auto& entry : m_part_meters) {
227             const std::string part_name = entry.first.second;
228             MeterType meter_type = entry.first.first;
229             const Meter& meter = entry.second;
230             os << part_name << " " << meter_type << ": " << meter.Current() << "  ";
231         }
232     }
233     return os.str();
234 }
235 
Design() const236 const ShipDesign* Ship::Design() const
237 { return GetShipDesign(m_design_id); }
238 
IsMonster() const239 bool Ship::IsMonster() const {
240     const ShipDesign* design = Design();
241     if (design)
242         return design->IsMonster();
243     else
244         return false;
245 }
246 
IsArmed() const247 bool Ship::IsArmed() const {
248     if (TotalWeaponsDamage(0.0f, false) > 0.0f)
249         return true;    // has non-fighter weapons
250     if (HasFighters() && (TotalWeaponsDamage(0.0f, true) > 0.0f))
251         return true;    // has no non-fighter weapons but has launchable fighters that do damage
252     return false;
253 }
254 
HasFighters() const255 bool Ship::HasFighters() const {
256     const ShipDesign* design = Design();
257     if (!design || !design->HasFighters())  // ensures ship has ability to launch fighters
258         return false;
259     return FighterCount() >= 1.0f;          // ensures ship currently has fighters to launch
260 }
261 
CanColonize() const262 bool Ship::CanColonize() const {
263     if (m_species_name.empty())
264         return false;
265     const Species* species = GetSpecies(m_species_name);
266     if (!species)
267         return false;
268     if (!species->CanColonize())
269         return false;
270     const ShipDesign* design = this->Design();
271     return design && design->CanColonize(); // use design->CanColonize because zero-capacity colony ships still count as outpost ships, can "can colonize" as far as order / the UI are concerned
272 }
273 
HasTroops() const274 bool Ship::HasTroops() const
275 { return this->TroopCapacity() > 0.0f; }
276 
CanBombard() const277 bool Ship::CanBombard() const {
278     const ShipDesign* design = Design();
279     return design && design->CanBombard();
280 }
281 
Speed() const282 float Ship::Speed() const
283 { return GetMeter(METER_SPEED)->Initial(); }
284 
ColonyCapacity() const285 float Ship::ColonyCapacity() const {
286     float retval = 0.0f;
287     // find which colony parts are present in design (one copy of name for each instance of a part, allowing duplicate names to appear)
288     const ShipDesign* design = Design();
289     if (!design)
290         return retval;
291 
292     for (const std::string& part_name : design->Parts()) {
293         if (part_name.empty())
294             continue;
295         const ShipPart* part = GetShipPart(part_name);
296         if (!part)
297             continue;
298         ShipPartClass part_class = part->Class();
299         if (part_class != PC_COLONY)
300             continue;
301         // add capacity for all instances of colony parts to accumulator
302         retval += this->InitialPartMeterValue(METER_CAPACITY, part_name);
303     }
304 
305     return retval;
306 }
307 
TroopCapacity() const308 float Ship::TroopCapacity() const {
309     float retval = 0.0f;
310     // find which troop parts are present in design (one copy of name for each instance of a part, allowing duplicate names to appear)
311     const ShipDesign* design = Design();
312     if (!design)
313         return retval;
314 
315     for (const std::string& part_name : design->Parts()) {
316         if (part_name.empty())
317             continue;
318         const ShipPart* part = GetShipPart(part_name);
319         if (!part)
320             continue;
321         ShipPartClass part_class = part->Class();
322         if (part_class != PC_TROOPS)
323             continue;
324         // add capacity for all instances of colony parts to accumulator
325         retval += this->InitialPartMeterValue(METER_CAPACITY, part_name);
326     }
327 
328     return retval;
329 }
330 
CanHaveTroops() const331 bool Ship::CanHaveTroops() const {
332     const ShipDesign* design = Design();
333     return design ? design->HasTroops() : false;
334 }
335 
PublicName(int empire_id) const336 const std::string& Ship::PublicName(int empire_id) const {
337     // Disclose real ship name only to fleet owners. Rationale: a player who
338     // doesn't know the design for a particular ship can easily guess it if the
339     // ship's name is "Scout"
340     // An exception is made for unowned monsters.
341     if (GetUniverse().AllObjectsVisible() || empire_id == ALL_EMPIRES || OwnedBy(empire_id) || (IsMonster() && Owner() == ALL_EMPIRES))
342         return Name();
343     const ShipDesign* design = Design();
344     if (design)
345         return design->Name();
346     else if (IsMonster())
347         return UserString("SM_MONSTER");
348     else if (!Unowned())
349         return UserString("FW_FOREIGN_SHIP");
350     else if (Unowned() && GetVisibility(empire_id) > VIS_NO_VISIBILITY)
351         return UserString("FW_ROGUE_SHIP");
352     else
353         return UserString("OBJ_SHIP");
354 }
355 
Accept(const UniverseObjectVisitor & visitor) const356 std::shared_ptr<UniverseObject> Ship::Accept(const UniverseObjectVisitor& visitor) const
357 { return visitor.Visit(std::const_pointer_cast<Ship>(std::static_pointer_cast<const Ship>(shared_from_this()))); }
358 
GetPartMeter(MeterType type,const std::string & part_name) const359 const Meter* Ship::GetPartMeter(MeterType type, const std::string& part_name) const
360 { return const_cast<Ship*>(this)->GetPartMeter(type, part_name); }
361 
GetPartMeter(MeterType type,const std::string & part_name)362 Meter* Ship::GetPartMeter(MeterType type, const std::string& part_name) {
363     Meter* retval = nullptr;
364     auto it = m_part_meters.find({type, part_name});
365     if (it != m_part_meters.end())
366         retval = &it->second;
367     return retval;
368 }
369 
CurrentPartMeterValue(MeterType type,const std::string & part_name) const370 float Ship::CurrentPartMeterValue(MeterType type, const std::string& part_name) const {
371     if (const Meter* meter = GetPartMeter(type, part_name))
372         return meter->Current();
373     return 0.0f;
374 }
375 
InitialPartMeterValue(MeterType type,const std::string & part_name) const376 float Ship::InitialPartMeterValue(MeterType type, const std::string& part_name) const {
377     if (const Meter* meter = GetPartMeter(type, part_name))
378         return meter->Initial();
379     return 0.0f;
380 }
381 
SumCurrentPartMeterValuesForPartClass(MeterType type,ShipPartClass part_class) const382 float Ship::SumCurrentPartMeterValuesForPartClass(MeterType type, ShipPartClass part_class) const {
383     float retval = 0.0f;
384 
385     const ShipDesign* design = GetShipDesign(m_design_id);
386     if (!design)
387         return retval;
388 
389     const auto& parts = design->Parts();
390     if (parts.empty())
391         return retval;
392 
393     std::map<std::string, int> part_counts;
394     for (const std::string& part : parts)
395         part_counts[part]++;
396 
397     for (const auto& part_meter : m_part_meters) {
398         if (part_meter.first.first != type)
399             continue;
400         const std::string& part_name = part_meter.first.second;
401         if (part_counts[part_name] < 1)
402             continue;
403         const ShipPart* part = GetShipPart(part_name);
404         if (!part)
405             continue;
406         if (part_class == part->Class())
407             retval += part_meter.second.Current() * part_counts[part_name];
408     }
409 
410     return retval;
411 }
412 
FighterCount() const413 float Ship::FighterCount() const {
414     float retval = 0.0f;
415     for (const auto& entry : m_part_meters) {
416         if (entry.first.first != METER_CAPACITY)
417             continue;
418         const ShipPart* part = GetShipPart(entry.first.second);
419         if (!part || part->Class() != PC_FIGHTER_HANGAR)
420             continue;
421         retval += entry.second.Current();
422     }
423 
424     return retval;
425 }
426 
FighterMax() const427 float Ship::FighterMax() const {
428     float retval = 0.0f;
429     for (const auto& entry : m_part_meters) {
430         //std::map<std::pair<MeterType, std::string>, Meter>
431         if (entry.first.first != METER_MAX_CAPACITY)
432             continue;
433         const ShipPart* part = GetShipPart(entry.first.second);
434         if (!part || part->Class() != PC_FIGHTER_HANGAR)
435             continue;
436         retval += entry.second.Current();
437     }
438 
439     return retval;
440 }
441 
TotalWeaponsDamage(float shield_DR,bool include_fighters) const442 float Ship::TotalWeaponsDamage(float shield_DR, bool include_fighters) const {
443     // sum up all individual weapons' attack strengths
444     float total_attack = 0.0f;
445     auto all_weapons_damage = AllWeaponsDamage(shield_DR, include_fighters);
446     for (float attack : all_weapons_damage)
447         total_attack += attack;
448     return total_attack;
449 }
450 
451 namespace {
WeaponDamageImpl(const Ship * ship,const ShipDesign * design,float DR,bool max,bool include_fighters)452     std::vector<float> WeaponDamageImpl(const Ship* ship, const ShipDesign* design,
453                                         float DR, bool max, bool include_fighters)
454     {
455         std::vector<float> retval;
456 
457         if (!ship || !design)
458             return retval;
459         const std::vector<std::string>& parts = design->Parts();
460         if (parts.empty())
461             return retval;
462 
463         MeterType METER = max ? METER_MAX_CAPACITY : METER_CAPACITY;
464         MeterType SECONDARY_METER = max ? METER_MAX_SECONDARY_STAT : METER_SECONDARY_STAT;
465 
466         float fighter_damage = 0.0f;
467         int fighter_launch_capacity = 0;
468         int available_fighters = 0;
469 
470         // for each weapon part, get its damage meter value
471         for (const std::string& part_name : parts) {
472             const ShipPart* part = GetShipPart(part_name);
473             if (!part)
474                 continue;
475             ShipPartClass part_class = part->Class();
476 
477             // get the attack power for each weapon part.
478             if (part_class == PC_DIRECT_WEAPON) {
479                 float part_attack = ship->CurrentPartMeterValue(METER, part_name);  // used within loop that updates meters, so need current, not initial values
480                 float part_shots = ship->CurrentPartMeterValue(SECONDARY_METER, part_name);
481                 if (part_attack > DR)
482                     retval.push_back((part_attack - DR)*part_shots);
483 
484             } else if (part_class == PC_FIGHTER_BAY && include_fighters) {
485                 // launch capacity determined by capacity of bay
486                 fighter_launch_capacity += static_cast<int>(ship->CurrentPartMeterValue(METER, part_name));
487 
488             } else if (part_class == PC_FIGHTER_HANGAR && include_fighters) {
489                 // attack strength of a ship's fighters determined by the hangar...
490                 fighter_damage = ship->CurrentPartMeterValue(SECONDARY_METER, part_name);  // assuming all hangars have the same damage...
491                 available_fighters = std::max(0, static_cast<int>(ship->CurrentPartMeterValue(METER, part_name)));  // stacked meter
492             }
493         }
494 
495         if (!include_fighters || fighter_damage <= 0.0f || available_fighters <= 0 || fighter_launch_capacity <= 0)
496             return retval;
497 
498         int fighter_shots = std::min(available_fighters, fighter_launch_capacity);  // how many fighters launched in bout 1
499         available_fighters -= fighter_shots;
500         int launched_fighters = fighter_shots;
501         int num_bouts = GetGameRules().Get<int>("RULE_NUM_COMBAT_ROUNDS");
502         int remaining_bouts = num_bouts - 2;  // no attack for first round, second round already added
503         while (remaining_bouts > 0) {
504             int fighters_launched_this_bout = std::min(available_fighters, fighter_launch_capacity);
505             available_fighters -= fighters_launched_this_bout;
506             launched_fighters += fighters_launched_this_bout;
507             fighter_shots += launched_fighters;
508             --remaining_bouts;
509         }
510 
511         // how much damage does a fighter shot do?
512         fighter_damage = std::max(0.0f, fighter_damage);
513 
514         retval.push_back(fighter_damage * fighter_shots / num_bouts);    // divide by bouts because fighter calculation is for a full combat, but direct firefor one attack
515 
516         return retval;
517     }
518 }
519 
AllWeaponsDamage(float shield_DR,bool include_fighters) const520 std::vector<float> Ship::AllWeaponsDamage(float shield_DR, bool include_fighters) const {
521     std::vector<float> retval;
522 
523     const ShipDesign* design = GetShipDesign(m_design_id);
524     if (!design)
525         return retval;
526 
527     return WeaponDamageImpl(this, design, shield_DR, false, include_fighters);
528 }
529 
AllWeaponsMaxDamage(float shield_DR,bool include_fighters) const530 std::vector<float> Ship::AllWeaponsMaxDamage(float shield_DR , bool include_fighters) const {
531     std::vector<float> retval;
532 
533     const ShipDesign* design = GetShipDesign(m_design_id);
534     if (!design)
535         return retval;
536 
537     return WeaponDamageImpl(this, design, shield_DR, true, include_fighters);
538 }
539 
SetFleetID(int fleet_id)540 void Ship::SetFleetID(int fleet_id) {
541     if (m_fleet_id != fleet_id) {
542         m_fleet_id = fleet_id;
543         StateChangedSignal();
544     }
545 }
546 
SetArrivedOnTurn(int turn)547 void Ship::SetArrivedOnTurn(int turn) {
548     if (m_arrived_on_turn != turn) {
549         m_arrived_on_turn = turn;
550         StateChangedSignal();
551     }
552 }
553 
BackPropagateMeters()554 void Ship::BackPropagateMeters() {
555     UniverseObject::BackPropagateMeters();
556 
557     // ship part meter back propagation, since base class function doesn't do this...
558     for (auto& entry : m_part_meters)
559         entry.second.BackPropagate();
560 }
561 
Resupply()562 void Ship::Resupply() {
563     m_last_resupplied_on_turn = CurrentTurn();
564 
565     Meter* fuel_meter = UniverseObject::GetMeter(METER_FUEL);
566     const Meter* max_fuel_meter = UniverseObject::GetMeter(METER_MAX_FUEL);
567     if (!fuel_meter || !max_fuel_meter) {
568         ErrorLogger() << "Ship::Resupply couldn't get fuel meters!";
569     } else {
570         fuel_meter->SetCurrent(max_fuel_meter->Current());
571         fuel_meter->BackPropagate();
572     }
573 
574     // set all part capacities equal to any associated max capacity
575     // this "upgrades" any direct-fire weapon parts to their latest-allowed
576     // strengths, and replaces any lost fighters
577     for (auto& entry : m_part_meters) {
578         const auto& part_name = entry.first.second;
579         MeterType meter_type = entry.first.first;
580         MeterType paired_meter_type = INVALID_METER_TYPE;
581         switch(meter_type) {
582         case METER_CAPACITY:        paired_meter_type = METER_MAX_CAPACITY;         break;
583         case METER_SECONDARY_STAT:  paired_meter_type = METER_MAX_SECONDARY_STAT;   break;
584         default:
585             break;
586         }
587         if (paired_meter_type == INVALID_METER_TYPE)
588             continue;
589         auto max_it = m_part_meters.find({paired_meter_type, part_name});
590         if (max_it == m_part_meters.end())
591             continue;
592 
593         const Meter& max_meter = max_it->second;
594 
595         entry.second.SetCurrent(max_meter.Current());
596         entry.second.BackPropagate();
597     }
598 }
599 
SetSpecies(const std::string & species_name)600 void Ship::SetSpecies(const std::string& species_name) {
601     if (!GetSpecies(species_name))
602         ErrorLogger() << "Ship::SetSpecies couldn't get species with name " << species_name;
603     m_species_name = species_name;
604 }
605 
SetOrderedScrapped(bool b)606 void Ship::SetOrderedScrapped(bool b) {
607     if (b == m_ordered_scrapped) return;
608     m_ordered_scrapped = b;
609     StateChangedSignal();
610 }
611 
SetColonizePlanet(int planet_id)612 void Ship::SetColonizePlanet(int planet_id) {
613     if (planet_id == m_ordered_colonize_planet_id) return;
614     m_ordered_colonize_planet_id = planet_id;
615     StateChangedSignal();
616 }
617 
ClearColonizePlanet()618 void Ship::ClearColonizePlanet()
619 { SetColonizePlanet(INVALID_OBJECT_ID); }
620 
SetInvadePlanet(int planet_id)621 void Ship::SetInvadePlanet(int planet_id) {
622     if (planet_id == m_ordered_invade_planet_id) return;
623     m_ordered_invade_planet_id = planet_id;
624     StateChangedSignal();
625 }
626 
ClearInvadePlanet()627 void Ship::ClearInvadePlanet()
628 { SetInvadePlanet(INVALID_OBJECT_ID); }
629 
SetBombardPlanet(int planet_id)630 void Ship::SetBombardPlanet(int planet_id) {
631     if (planet_id == m_ordered_bombard_planet_id) return;
632     m_ordered_bombard_planet_id = planet_id;
633     StateChangedSignal();
634 }
635 
ClearBombardPlanet()636 void Ship::ClearBombardPlanet()
637 { SetBombardPlanet(INVALID_OBJECT_ID); }
638 
ResetTargetMaxUnpairedMeters()639 void Ship::ResetTargetMaxUnpairedMeters() {
640     UniverseObject::ResetTargetMaxUnpairedMeters();
641 
642     UniverseObject::GetMeter(METER_MAX_FUEL)->ResetCurrent();
643     UniverseObject::GetMeter(METER_MAX_SHIELD)->ResetCurrent();
644     UniverseObject::GetMeter(METER_MAX_STRUCTURE)->ResetCurrent();
645     UniverseObject::GetMeter(METER_TARGET_INDUSTRY)->ResetCurrent();
646     UniverseObject::GetMeter(METER_TARGET_RESEARCH)->ResetCurrent();
647     UniverseObject::GetMeter(METER_TARGET_TRADE)->ResetCurrent();
648 
649     UniverseObject::GetMeter(METER_DETECTION)->ResetCurrent();
650     UniverseObject::GetMeter(METER_SPEED)->ResetCurrent();
651     //UniverseObject::GetMeter(METER_STEALTH)->ResetCurrent(); redundant with base class function
652 
653     // max meters are always treated as target/max meters.
654     // other meters may be unpaired if there is no associated max or target meter
655     for (auto& entry : m_part_meters) {
656         const auto& part_name = entry.first.second;
657         MeterType meter_type = entry.first.first;
658         MeterType paired_meter_type = INVALID_METER_TYPE;
659 
660         switch(meter_type) {
661         case METER_MAX_CAPACITY:
662         case METER_MAX_SECONDARY_STAT:
663             entry.second.ResetCurrent();
664             continue;
665             break;
666         case METER_CAPACITY:        paired_meter_type = METER_MAX_CAPACITY;         break;
667         case METER_SECONDARY_STAT:  paired_meter_type = METER_MAX_SECONDARY_STAT;   break;
668         default:
669             continue;
670             break;
671         }
672 
673         auto max_it = m_part_meters.find({paired_meter_type, part_name});
674         if (max_it != m_part_meters.end())
675             continue;   // is a max/target meter associated with the meter, so don't treat this a target/max
676 
677         // no associated target/max meter, so treat this meter as unpaired
678         entry.second.ResetCurrent();
679     }
680 }
681 
ResetPairedActiveMeters()682 void Ship::ResetPairedActiveMeters() {
683     UniverseObject::ResetPairedActiveMeters();
684 
685     // meters are paired only if they are not max/target meters, and there is an
686     // associated max/target meter
687     for (auto& entry : m_part_meters) {
688         const auto& part_name = entry.first.second;
689         MeterType meter_type = entry.first.first;
690         MeterType paired_meter_type = INVALID_METER_TYPE;
691 
692         switch(meter_type) {
693         case METER_MAX_CAPACITY:
694         case METER_MAX_SECONDARY_STAT:
695             continue;   // is a max/target meter
696             break;
697         case METER_CAPACITY:        paired_meter_type = METER_MAX_CAPACITY;         break;
698         case METER_SECONDARY_STAT:  paired_meter_type = METER_MAX_SECONDARY_STAT;   break;
699         default:
700             continue;   // no associated max/target meter
701             break;
702         }
703 
704         auto max_it = m_part_meters.find({paired_meter_type, part_name});
705         if (max_it == m_part_meters.end())
706             continue;   // no associated max/target meter
707 
708         // has an associated max/target meter.
709         //std::map<std::pair<MeterType, std::string>, Meter>::iterator
710         entry.second.SetCurrent(entry.second.Initial());
711     }
712 }
713 
SetShipMetersToMax()714 void Ship::SetShipMetersToMax() {
715     UniverseObject::GetMeter(METER_MAX_FUEL)->SetCurrent(Meter::LARGE_VALUE);
716     UniverseObject::GetMeter(METER_MAX_SHIELD)->SetCurrent(Meter::LARGE_VALUE);
717     UniverseObject::GetMeter(METER_MAX_STRUCTURE)->SetCurrent(Meter::LARGE_VALUE);
718     UniverseObject::GetMeter(METER_FUEL)->SetCurrent(Meter::LARGE_VALUE);
719     UniverseObject::GetMeter(METER_SHIELD)->SetCurrent(Meter::LARGE_VALUE);
720     UniverseObject::GetMeter(METER_STRUCTURE)->SetCurrent(Meter::LARGE_VALUE);
721 
722     // some part capacity meters may have an associated max capacity...
723     for (auto& entry : m_part_meters)
724         entry.second.SetCurrent(Meter::LARGE_VALUE);
725 }
726 
ClampMeters()727 void Ship::ClampMeters() {
728     UniverseObject::ClampMeters();
729 
730     UniverseObject::GetMeter(METER_MAX_FUEL)->ClampCurrentToRange();
731     UniverseObject::GetMeter(METER_FUEL)->ClampCurrentToRange(Meter::DEFAULT_VALUE, UniverseObject::GetMeter(METER_MAX_FUEL)->Current());
732     UniverseObject::GetMeter(METER_MAX_SHIELD)->ClampCurrentToRange();
733     UniverseObject::GetMeter(METER_SHIELD)->ClampCurrentToRange(Meter::DEFAULT_VALUE, UniverseObject::GetMeter(METER_MAX_SHIELD)->Current());
734     UniverseObject::GetMeter(METER_MAX_STRUCTURE)->ClampCurrentToRange();
735     UniverseObject::GetMeter(METER_STRUCTURE)->ClampCurrentToRange(Meter::DEFAULT_VALUE, UniverseObject::GetMeter(METER_MAX_STRUCTURE)->Current());
736     UniverseObject::GetMeter(METER_TARGET_INDUSTRY)->ClampCurrentToRange();
737     UniverseObject::GetMeter(METER_INDUSTRY)->ClampCurrentToRange();
738     UniverseObject::GetMeter(METER_TARGET_RESEARCH)->ClampCurrentToRange();
739     UniverseObject::GetMeter(METER_RESEARCH)->ClampCurrentToRange();
740     UniverseObject::GetMeter(METER_TARGET_TRADE)->ClampCurrentToRange();
741     UniverseObject::GetMeter(METER_TRADE)->ClampCurrentToRange();
742 
743     UniverseObject::GetMeter(METER_DETECTION)->ClampCurrentToRange();
744     UniverseObject::GetMeter(METER_SPEED)->ClampCurrentToRange();
745 
746     // clamp most part meters to basic range limits
747     for (auto& entry : m_part_meters) {
748         MeterType meter_type = entry.first.first;
749         switch(meter_type) {
750         case METER_MAX_CAPACITY:
751         case METER_MAX_SECONDARY_STAT:
752             entry.second.ClampCurrentToRange();
753         default:
754             break;
755         }
756     }
757 
758     // special case extra clamping for paired active meters dependent
759     // on their associated max meter...
760     for (auto& entry : m_part_meters) {
761         const auto& part_name = entry.first.second;
762         MeterType meter_type = entry.first.first;
763         MeterType paired_meter_type = INVALID_METER_TYPE;
764         switch(meter_type) {
765         case METER_CAPACITY:        paired_meter_type = METER_MAX_CAPACITY;         break;
766         case METER_SECONDARY_STAT:  paired_meter_type = METER_MAX_SECONDARY_STAT;   break;
767         default:
768             break;
769         }
770         if (paired_meter_type == INVALID_METER_TYPE)
771             continue;
772         auto max_it = m_part_meters.find({paired_meter_type, part_name});
773         if (max_it == m_part_meters.end())
774             continue;
775 
776         const Meter& max_meter = max_it->second;
777         entry.second.ClampCurrentToRange(Meter::DEFAULT_VALUE, max_meter.Current());
778     }
779 }
780 
781 ////////////////////
782 // Free Functions //
783 ////////////////////
NewMonsterName()784 std::string NewMonsterName() {
785     static std::vector<std::string> monster_names = UserStringList("MONSTER_NAMES");
786     static std::map<std::string, int> monster_names_used;
787 
788     if (monster_names.empty())
789         monster_names.push_back(UserString("MONSTER"));
790 
791     // select name randomly from list
792     int monster_name_index = RandSmallInt(0, static_cast<int>(monster_names.size()) - 1);
793     std::string result = monster_names[monster_name_index];
794     if (monster_names_used[result]++) {
795         result += " " + RomanNumber(monster_names_used[result]);
796     }
797     return result;
798 }
799