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