1 #include "ShipDesign.h"
2 
3 #include <boost/uuid/random_generator.hpp>
4 #include <boost/uuid/uuid_io.hpp>
5 #include "Condition.h"
6 #include "Effect.h"
7 #include "Planet.h"
8 #include "ScriptingContext.h"
9 #include "Ship.h"
10 #include "ShipHull.h"
11 #include "ShipPart.h"
12 #include "Species.h"
13 #include "ValueRef.h"
14 #include "../util/AppInterface.h"
15 #include "../util/CheckSums.h"
16 #include "../util/GameRules.h"
17 #include "../util/i18n.h"
18 
19 
20 extern FO_COMMON_API const int INVALID_DESIGN_ID = -1;
21 
22 //using boost::io::str;
23 
24 namespace {
AddRules(GameRules & rules)25     void AddRules(GameRules& rules) {
26         // makes all ships cost 1 PP and take 1 turn to produce
27         rules.Add<bool>("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION",
28                         "RULE_CHEAP_AND_FAST_SHIP_PRODUCTION_DESC",
29                         "", false, true);
30     }
31     bool temp_bool = RegisterGameRules(&AddRules);
32 
33     const std::string EMPTY_STRING;
34     const float ARBITRARY_LARGE_COST = 999999.9f;
35 
DesignsTheSame(const ShipDesign & one,const ShipDesign & two)36     bool DesignsTheSame(const ShipDesign& one, const ShipDesign& two) {
37         return (
38             one.Name()              == two.Name() &&
39             one.Description()       == two.Description() &&
40             one.DesignedOnTurn()    == two.DesignedOnTurn() &&
41             one.Hull()              == two.Hull() &&
42             one.Parts()             == two.Parts() &&
43             one.Icon()              == two.Icon() &&
44             one.Model()             == two.Model()
45         );
46         // not checking that IDs are the same, since the purpose of this is to
47         // check if a design that might be added to the universe (which doesn't
48         // have an ID yet) is the same as one that has already been added
49         // (which does have an ID)
50     }
51 }
52 
53 ////////////////////////////////////////////////
54 // Free Functions                             //
55 ////////////////////////////////////////////////
GetShipDesign(int ship_design_id)56 const ShipDesign* GetShipDesign(int ship_design_id)
57 { return GetUniverse().GetShipDesign(ship_design_id); }
58 
59 
60 ////////////////////////////////////////////////
61 // CommonParams
62 ////////////////////////////////////////////////
CommonParams()63 CommonParams::CommonParams() {}
64 
CommonParams(std::unique_ptr<ValueRef::ValueRef<double>> && production_cost_,std::unique_ptr<ValueRef::ValueRef<int>> && production_time_,bool producible_,const std::set<std::string> & tags_,std::unique_ptr<Condition::Condition> && location_,std::vector<std::unique_ptr<Effect::EffectsGroup>> && effects_,ConsumptionMap<MeterType> && production_meter_consumption_,ConsumptionMap<std::string> && production_special_consumption_,std::unique_ptr<Condition::Condition> && enqueue_location_)65 CommonParams::CommonParams(std::unique_ptr<ValueRef::ValueRef<double>>&& production_cost_,
66                            std::unique_ptr<ValueRef::ValueRef<int>>&& production_time_,
67                            bool producible_,
68                            const std::set<std::string>& tags_,
69                            std::unique_ptr<Condition::Condition>&& location_,
70                            std::vector<std::unique_ptr<Effect::EffectsGroup>>&& effects_,
71                            ConsumptionMap<MeterType>&& production_meter_consumption_,
72                            ConsumptionMap<std::string>&& production_special_consumption_,
73                            std::unique_ptr<Condition::Condition>&& enqueue_location_) :
74     production_cost(std::move(production_cost_)),
75     production_time(std::move(production_time_)),
76     producible(producible_),
77     production_meter_consumption(std::move(production_meter_consumption_)),
78     production_special_consumption(std::move(production_special_consumption_)),
79     location(std::move(location_)),
80     enqueue_location(std::move(enqueue_location_)),
81     effects(std::move(effects_))
82 {
83     for (const std::string& tag : tags_)
84         tags.insert(boost::to_upper_copy<std::string>(tag));
85 }
86 
~CommonParams()87 CommonParams::~CommonParams() {}
88 
89 
90 /////////////////////////////////////
91 // ParsedShipDesign     //
92 /////////////////////////////////////
ParsedShipDesign(const std::string & name,const std::string & description,int designed_on_turn,int designed_by_empire,const std::string & hull,const std::vector<std::string> & parts,const std::string & icon,const std::string & model,bool name_desc_in_stringtable,bool monster,const boost::uuids::uuid & uuid)93 ParsedShipDesign::ParsedShipDesign(
94     const std::string& name, const std::string& description,
95     int designed_on_turn, int designed_by_empire,
96     const std::string& hull,
97     const std::vector<std::string>& parts,
98     const std::string& icon, const std::string& model,
99     bool name_desc_in_stringtable, bool monster,
100     const boost::uuids::uuid& uuid) :
101     m_name(name),
102     m_description(description),
103     m_uuid(uuid),
104     m_designed_on_turn(designed_on_turn),
105     m_designed_by_empire(designed_by_empire),
106     m_hull(hull),
107     m_parts(parts),
108     m_is_monster(monster),
109     m_icon(icon),
110     m_3D_model(model),
111     m_name_desc_in_stringtable(name_desc_in_stringtable)
112 {}
113 
114 ////////////////////////////////////////////////
115 // ShipDesign
116 ////////////////////////////////////////////////
ShipDesign()117 ShipDesign::ShipDesign() :
118     m_uuid(boost::uuids::nil_generator()())
119 {}
120 
ShipDesign(const boost::optional<std::invalid_argument> & should_throw,const std::string & name,const std::string & description,int designed_on_turn,int designed_by_empire,const std::string & hull,const std::vector<std::string> & parts,const std::string & icon,const std::string & model,bool name_desc_in_stringtable,bool monster,const boost::uuids::uuid & uuid)121 ShipDesign::ShipDesign(const boost::optional<std::invalid_argument>& should_throw,
122                        const std::string& name, const std::string& description,
123                        int designed_on_turn, int designed_by_empire, const std::string& hull,
124                        const std::vector<std::string>& parts,
125                        const std::string& icon, const std::string& model,
126                        bool name_desc_in_stringtable, bool monster,
127                        const boost::uuids::uuid& uuid /*= boost::uuids::nil_uuid()*/) :
128     m_name(name),
129     m_description(description),
130     m_uuid(uuid),
131     m_designed_on_turn(designed_on_turn),
132     m_designed_by_empire(designed_by_empire),
133     m_hull(hull),
134     m_parts(parts),
135     m_is_monster(monster),
136     m_icon(icon),
137     m_3D_model(model),
138     m_name_desc_in_stringtable(name_desc_in_stringtable)
139 {
140     // Either force a valid design and log about it or just throw std::invalid_argument
141     ForceValidDesignOrThrow(should_throw, !should_throw);
142     BuildStatCaches();
143 }
144 
ShipDesign(const ParsedShipDesign & design)145 ShipDesign::ShipDesign(const ParsedShipDesign& design) :
146     ShipDesign(boost::none, design.m_name, design.m_description,
147                design.m_designed_on_turn, design.m_designed_by_empire,
148                design.m_hull, design.m_parts,
149                design.m_icon, design.m_3D_model, design.m_name_desc_in_stringtable,
150                design.m_is_monster, design.m_uuid)
151 {}
152 
Name(bool stringtable_lookup) const153 const std::string& ShipDesign::Name(bool stringtable_lookup /* = true */) const {
154     if (m_name_desc_in_stringtable && stringtable_lookup)
155         return UserString(m_name);
156     else
157         return m_name;
158 }
159 
SetName(const std::string & name)160 void ShipDesign::SetName(const std::string& name) {
161     if (!name.empty() && !m_name.empty()) {
162         m_name = name;
163     }
164 }
165 
SetUUID(const boost::uuids::uuid & uuid)166 void ShipDesign::SetUUID(const boost::uuids::uuid& uuid)
167 { m_uuid = uuid; }
168 
Description(bool stringtable_lookup) const169 const std::string& ShipDesign::Description(bool stringtable_lookup /* = true */) const {
170     if (m_name_desc_in_stringtable && stringtable_lookup)
171         return UserString(m_description);
172     else
173         return m_description;
174 }
175 
SetDescription(const std::string & description)176 void ShipDesign::SetDescription(const std::string& description)
177 { m_description = description; }
178 
ProductionCostTimeLocationInvariant() const179 bool ShipDesign::ProductionCostTimeLocationInvariant() const {
180     if (GetGameRules().Get<bool>("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION"))
181         return true;
182     // as seen in ShipDesign::ProductionCost, the production location is passed
183     // as the local candidate in the ScriptingContext
184 
185     // check hull and all parts
186     if (const ShipHull* hull = GetShipHull(m_hull))
187         if (!hull->ProductionCostTimeLocationInvariant())
188             return false;
189 
190     for (const std::string& part_name : m_parts)
191         if (const ShipPart* part = GetShipPart(part_name))
192             if (!part->ProductionCostTimeLocationInvariant())
193                 return false;
194 
195     // if hull and all parts are invariant, so is whole design
196     return true;
197 }
198 
ProductionCost(int empire_id,int location_id) const199 float ShipDesign::ProductionCost(int empire_id, int location_id) const {
200     if (GetGameRules().Get<bool>("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION"))
201         return 1.0f;
202 
203     float cost_accumulator = 0.0f;
204     if (const ShipHull* hull = GetShipHull(m_hull))
205         cost_accumulator += hull->ProductionCost(empire_id, location_id, m_id);
206 
207     int part_count = 0;
208     for (const std::string& part_name : m_parts) {
209         if (const ShipPart* part = GetShipPart(part_name)) {
210             cost_accumulator += part->ProductionCost(empire_id, location_id, m_id);
211             part_count++;
212         }
213     }
214 
215     // Assuming no reasonable combination of parts and hull will add up to more
216     // than ARBITRARY_LARGE_COST. Truncating cost here to return it to indicate
217     // an uncalculable cost (ie. due to lacking a valid location object)
218 
219     return std::min(std::max(0.0f, cost_accumulator), ARBITRARY_LARGE_COST);
220 }
221 
PerTurnCost(int empire_id,int location_id) const222 float ShipDesign::PerTurnCost(int empire_id, int location_id) const
223 { return ProductionCost(empire_id, location_id) / std::max(1, ProductionTime(empire_id, location_id)); }
224 
ProductionTime(int empire_id,int location_id) const225 int ShipDesign::ProductionTime(int empire_id, int location_id) const {
226     if (GetGameRules().Get<bool>("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION"))
227         return 1;
228 
229     int time_accumulator = 1;
230     if (const ShipHull* hull = GetShipHull(m_hull))
231         time_accumulator = std::max(time_accumulator, hull->ProductionTime(empire_id, location_id));
232 
233     for (const std::string& part_name : m_parts)
234         if (const ShipPart* part = GetShipPart(part_name))
235             time_accumulator = std::max(time_accumulator, part->ProductionTime(empire_id, location_id));
236 
237     // assuming that ARBITRARY_LARGE_TURNS is larger than any reasonable turns,
238     // so the std::max calls will preserve it be returned
239 
240     return std::max(1, time_accumulator);
241 }
242 
CanColonize() const243 bool ShipDesign::CanColonize() const {
244     for (const std::string& part_name : m_parts) {
245         if (part_name.empty())
246             continue;
247         if (const ShipPart* part = GetShipPart(part_name))
248             if (part->Class() == PC_COLONY)
249                 return true;
250     }
251     return false;
252 }
253 
Defense() const254 float ShipDesign::Defense() const {
255     // accumulate defense from defensive parts in design.
256     float total_defense = 0.0f;
257     const ShipPartManager& part_manager = GetShipPartManager();
258     for (const std::string& part_name : Parts()) {
259         const ShipPart* part = part_manager.GetShipPart(part_name);
260         if (part && (part->Class() == PC_SHIELD || part->Class() == PC_ARMOUR))
261             total_defense += part->Capacity();
262     }
263     return total_defense;
264 }
265 
Attack() const266 float ShipDesign::Attack() const {
267     // total damage against a target with the no shield.
268     return AdjustedAttack(0.0f);
269 }
270 
AdjustedAttack(float shield) const271 float ShipDesign::AdjustedAttack(float shield) const {
272     // total damage against a target with the given shield (damage reduction)
273     // assuming full load of fighters that are not destroyed during the battle
274     int available_fighters = 0;
275     int fighter_launch_capacity = 0;
276     float fighter_damage = 0.0f;
277     float direct_attack = 0.0f;
278 
279     for (const std::string& part_name : m_parts) {
280         const ShipPart* part = GetShipPart(part_name);
281         if (!part)
282             continue;
283         ShipPartClass part_class = part->Class();
284 
285         // direct weapon and fighter-related parts all handled differently...
286         if (part_class == PC_DIRECT_WEAPON) {
287             float part_attack = part->Capacity();
288             if (part_attack > shield)
289                 direct_attack += (part_attack - shield)*part->SecondaryStat();  // here, secondary stat is number of shots per round
290         } else if (part_class == PC_FIGHTER_HANGAR) {
291             available_fighters = part->Capacity();                              // stacked meter
292         } else if (part_class == PC_FIGHTER_BAY) {
293             fighter_launch_capacity += part->Capacity();
294             fighter_damage = part->SecondaryStat();                             // here, secondary stat is fighter damage per shot
295         }
296     }
297 
298     int fighter_shots = std::min(available_fighters, fighter_launch_capacity);  // how many fighters launched in bout 1
299     available_fighters -= fighter_shots;
300     int launched_fighters = fighter_shots;
301     int num_bouts = GetGameRules().Get<int>("RULE_NUM_COMBAT_ROUNDS");
302     int remaining_bouts = num_bouts - 2;  // no attack for first round, second round already added
303     while (remaining_bouts > 0) {
304         int fighters_launched_this_bout = std::min(available_fighters, fighter_launch_capacity);
305         available_fighters -= fighters_launched_this_bout;
306         launched_fighters += fighters_launched_this_bout;
307         fighter_shots += launched_fighters;
308         --remaining_bouts;
309     }
310 
311     // how much damage does a fighter shot do?
312     fighter_damage = std::max(0.0f, fighter_damage);
313 
314     return direct_attack + fighter_shots*fighter_damage/num_bouts;   // divide by bouts because fighter calculation is for a full combat, but direct firefor one attack
315 }
316 
Parts(ShipSlotType slot_type) const317 std::vector<std::string> ShipDesign::Parts(ShipSlotType slot_type) const {
318     std::vector<std::string> retval;
319 
320     const ShipHull* hull = GetShipHullManager().GetShipHull(m_hull);
321     if (!hull) {
322         ErrorLogger() << "Design hull not found: " << m_hull;
323         return retval;
324     }
325     const auto& slots = hull->Slots();
326 
327     if (m_parts.empty())
328         return retval;
329 
330     // add to output vector each part that is in a slot of the indicated ShipSlotType
331     for (unsigned int i = 0; i < m_parts.size(); ++i)
332         if (slots[i].type == slot_type)
333             retval.push_back(m_parts[i]);
334 
335     return retval;
336 }
337 
Weapons() const338 std::vector<std::string> ShipDesign::Weapons() const {
339     std::vector<std::string> retval;
340     retval.reserve(m_parts.size());
341     for (const auto& part_name : m_parts) {
342         const ShipPart* part = GetShipPart(part_name);
343         if (!part)
344             continue;
345         ShipPartClass part_class = part->Class();
346         if (part_class == PC_DIRECT_WEAPON || part_class == PC_FIGHTER_BAY)
347         { retval.push_back(part_name); }
348     }
349     return retval;
350 }
351 
PartCount() const352 int ShipDesign::PartCount() const {
353     int count = 0;
354     for (auto& entry : m_num_part_classes)
355          count += entry.second;
356     return count;
357 }
358 
ProductionLocation(int empire_id,int location_id) const359 bool ShipDesign::ProductionLocation(int empire_id, int location_id) const {
360     Empire* empire = GetEmpire(empire_id);
361     if (!empire) {
362         DebugLogger() << "ShipDesign::ProductionLocation: Unable to get pointer to empire " << empire_id;
363         return false;
364     }
365 
366     // must own the production location...
367     auto location = Objects().get(location_id);
368     if (!location) {
369         WarnLogger() << "ShipDesign::ProductionLocation unable to get location object with id " << location_id;
370         return false;
371     }
372     if (!location->OwnedBy(empire_id))
373         return false;
374 
375     auto planet = std::dynamic_pointer_cast<const Planet>(location);
376     std::shared_ptr<const Ship> ship;
377     if (!planet)
378         ship = std::dynamic_pointer_cast<const Ship>(location);
379     if (!planet && !ship)
380         return false;
381 
382     // ships can only be produced by species that are not planetbound
383     const std::string& species_name = planet ? planet->SpeciesName() : (ship ? ship->SpeciesName() : EMPTY_STRING);
384     if (species_name.empty())
385         return false;
386     const Species* species = GetSpecies(species_name);
387     if (!species)
388         return false;
389 
390     if (!species->CanProduceShips())
391         return false;
392     // also, species that can't colonize can't produce colony ships
393     if (this->CanColonize() && !species->CanColonize())
394         return false;
395 
396     // apply hull location conditions to potential location
397     const ShipHull* hull = GetShipHull(m_hull);
398     if (!hull) {
399         ErrorLogger() << "ShipDesign::ProductionLocation  ShipDesign couldn't get its own hull with name " << m_hull;
400         return false;
401     }
402     // evaluate using location as the source, as it should be an object owned by this empire.
403     ScriptingContext location_as_source_context(location, location);
404     if (!hull->Location()->Eval(location_as_source_context, location))
405         return false;
406 
407     // apply external and internal parts' location conditions to potential location
408     for (const std::string& part_name : m_parts) {
409         if (part_name.empty())
410             continue;       // empty slots don't limit build location
411 
412         const ShipPart* part = GetShipPart(part_name);
413         if (!part) {
414             ErrorLogger() << "ShipDesign::ProductionLocation  ShipDesign couldn't get part with name " << part_name;
415             return false;
416         }
417         if (!part->Location()->Eval(location_as_source_context, location))
418             return false;
419     }
420     // location matched all hull and part conditions, so is a valid build location
421     return true;
422 }
423 
SetID(int id)424 void ShipDesign::SetID(int id)
425 { m_id = id; }
426 
ValidDesign(const std::string & hull,const std::vector<std::string> & parts_in)427 bool ShipDesign::ValidDesign(const std::string& hull, const std::vector<std::string>& parts_in) {
428     auto parts = parts_in;
429     return !MaybeInvalidDesign(hull, parts, true);
430 }
431 
432 boost::optional<std::pair<std::string, std::vector<std::string>>>
MaybeInvalidDesign(const std::string & hull_in,std::vector<std::string> & parts_in,bool produce_log)433 ShipDesign::MaybeInvalidDesign(const std::string& hull_in,
434                                std::vector<std::string>& parts_in,
435                                bool produce_log)
436 {
437     bool is_valid = true;
438 
439     auto hull = hull_in;
440     auto parts = parts_in;
441 
442     // ensure hull type exists
443     auto ship_hull = GetShipHullManager().GetShipHull(hull);
444     if (!ship_hull) {
445         is_valid = false;
446         if (produce_log)
447             WarnLogger() << "Invalid ShipDesign hull not found: " << hull;
448 
449         const auto hull_it = GetShipHullManager().begin();
450         if (hull_it != GetShipHullManager().end()) {
451             hull = hull_it->first;
452             ship_hull = hull_it->second.get();
453             if (produce_log)
454                 WarnLogger() << "Invalid ShipDesign hull falling back to: " << hull;
455         } else {
456             if (produce_log)
457                 ErrorLogger() << "Invalid ShipDesign no available hulls ";
458             hull = "";
459             parts.clear();
460             return std::make_pair(hull, parts);
461         }
462     }
463 
464     // ensure hull type has at least enough slots for passed parts
465     if (parts.size() > ship_hull->NumSlots()) {
466         is_valid = false;
467         if (produce_log)
468             WarnLogger() << "Invalid ShipDesign given " << parts.size() << " parts for hull with "
469                          << ship_hull->NumSlots() << " slots.  Truncating last "
470                          << (parts.size() - ship_hull->NumSlots()) << " parts.";
471     }
472 
473     // If parts is smaller than the full hull size pad it and the incoming parts
474     if (parts.size() < ship_hull->NumSlots())
475         parts_in.resize(ship_hull->NumSlots(), "");
476 
477     // Truncate or pad with "" parts.
478     parts.resize(ship_hull->NumSlots(), "");
479 
480     const auto& slots = ship_hull->Slots();
481 
482     // check hull exclusions against all parts...
483     const auto& hull_exclusions = ship_hull->Exclusions();
484     for (auto& part_name : parts) {
485         if (part_name.empty())
486             continue;
487         if (hull_exclusions.count(part_name)) {
488             is_valid = false;
489             if (produce_log)
490                 WarnLogger() << "Invalid ShipDesign part \"" << part_name << "\" is excluded by \""
491                              << ship_hull->Name() << "\". Removing \"" << part_name <<"\"";
492             part_name.clear();
493         }
494     }
495 
496     // check part exclusions against other parts and hull
497     std::unordered_map<std::string, unsigned int> component_name_counts;
498     component_name_counts[hull] = 1;
499     for (auto part_name : parts)
500         component_name_counts[part_name]++;
501     component_name_counts.erase("");
502 
503     for (std::size_t ii = 0; ii < parts.size(); ++ii) {
504         const auto part_name = parts[ii];
505         // Ignore empty slots, which are valid.
506         if (part_name.empty())
507             continue;
508 
509         // Parts must exist...
510         const auto ship_part = GetShipPart(part_name);
511         if (!ship_part) {
512             if (produce_log)
513                 WarnLogger() << "Invalid ShipDesign part \"" << part_name << "\" not found"
514                              << ". Removing \"" << part_name <<"\"";
515             is_valid = false;
516             continue;
517         }
518 
519         for (const auto& excluded : ship_part->Exclusions()) {
520             // confict if a different excluded part is present, or if there are
521             // two or more of a part that excludes itself
522             if ((excluded == part_name && component_name_counts[excluded] > 1) ||
523                 (excluded != part_name && component_name_counts[excluded] > 0))
524             {
525                 is_valid = false;
526                 if (produce_log)
527                     WarnLogger() << "Invalid ShipDesign part " << part_name << " conflicts with \""
528                                  << excluded << "\". Removing \"" << part_name <<"\"";
529                 continue;
530             }
531         }
532 
533         // verify part can mount in indicated slot
534         const ShipSlotType& slot_type = slots[ii].type;
535 
536         if (!ship_part->CanMountInSlotType(slot_type)) {
537             if (produce_log)
538                 DebugLogger() << "Invalid ShipDesign part \"" << part_name << "\" can't be mounted in "
539                               << slot_type << " slot. Removing \"" << part_name <<"\"";
540             is_valid = false;
541             continue;
542         }
543     }
544 
545     if (is_valid)
546         return boost::none;
547     else
548         return std::make_pair(hull, parts);
549 }
550 
ForceValidDesignOrThrow(const boost::optional<std::invalid_argument> & should_throw,bool produce_log)551 void ShipDesign::ForceValidDesignOrThrow(const boost::optional<std::invalid_argument>& should_throw,
552                                          bool  produce_log)
553 {
554     auto force_valid = MaybeInvalidDesign(m_hull, m_parts, produce_log);
555     if (!force_valid)
556         return;
557 
558     if (!produce_log && should_throw)
559         throw std::invalid_argument("ShipDesign: Bad hull or parts");
560 
561     std::stringstream ss;
562 
563     bool no_hull_available = force_valid->first.empty();
564     if (no_hull_available)
565         ss << "ShipDesign has no valid hull and there are no other hulls available." << std::endl;
566 
567     ss << "Invalid ShipDesign:" << std::endl;
568     ss << Dump() << std::endl;
569 
570     std::tie(m_hull, m_parts) = *force_valid;
571 
572     ss << "ShipDesign was made valid as:" << std::endl;
573     ss << Dump() << std::endl;
574 
575     if (no_hull_available)
576         ErrorLogger() << ss.str();
577     else
578         WarnLogger() << ss.str();
579 
580     if (should_throw)
581         throw std::invalid_argument("ShipDesign: Bad hull or parts");
582 }
583 
BuildStatCaches()584 void ShipDesign::BuildStatCaches() {
585     const ShipHull* hull = GetShipHull(m_hull);
586     if (!hull) {
587         ErrorLogger() << "ShipDesign::BuildStatCaches couldn't get hull with name " << m_hull;
588         return;
589     }
590 
591     m_producible =      hull->Producible();
592     m_detection =       hull->Detection();
593     m_colony_capacity = hull->ColonyCapacity();
594     m_troop_capacity =  hull->TroopCapacity();
595     m_stealth =         hull->Stealth();
596     m_fuel =            hull->Fuel();
597     m_shields =         hull->Shields();
598     m_structure =       hull->Structure();
599     m_speed =           hull->Speed();
600 
601     bool has_fighter_bays = false;
602     bool has_fighter_hangars = false;
603     bool has_armed_fighters = false;
604     bool can_launch_fighters = false;
605 
606     for (const std::string& part_name : m_parts) {
607         if (part_name.empty())
608             continue;
609 
610         const ShipPart* part = GetShipPart(part_name);
611         if (!part) {
612             ErrorLogger() << "ShipDesign::BuildStatCaches couldn't get part with name " << part_name;
613             continue;
614         }
615 
616         if (!part->Producible())
617             m_producible = false;
618 
619         ShipPartClass part_class = part->Class();
620 
621         switch (part_class) {
622         case PC_DIRECT_WEAPON:
623             m_has_direct_weapons = true;
624             if (part->Capacity() > 0.0f)
625                 m_is_armed = true;
626             break;
627         case PC_FIGHTER_BAY:
628             has_fighter_bays = true;
629             if (part->Capacity() >= 1.0f)
630                 can_launch_fighters = true;
631             break;
632         case PC_FIGHTER_HANGAR:
633             has_fighter_hangars = true;
634             if (part->SecondaryStat() > 0.0f && part->Capacity() >= 1.0f)
635                 has_armed_fighters = true;
636             break;
637         case PC_COLONY:
638             m_colony_capacity += part->Capacity();
639             break;
640         case PC_TROOPS:
641             m_troop_capacity += part->Capacity();
642             break;
643         case PC_STEALTH:
644             m_stealth += part->Capacity();
645             break;
646         case PC_SPEED:
647             m_speed += part->Capacity();
648             break;
649         case PC_SHIELD:
650             m_shields += part->Capacity();
651             break;
652         case PC_FUEL:
653             m_fuel += part->Capacity();
654             break;
655         case PC_ARMOUR:
656             m_structure += part->Capacity();
657             break;
658         case PC_DETECTION:
659             m_detection += part->Capacity();
660             break;
661         case PC_BOMBARD:
662             m_can_bombard = true;
663             break;
664         case PC_RESEARCH:
665             m_research_generation += part->Capacity();
666             break;
667         case PC_INDUSTRY:
668             m_industry_generation += part->Capacity();
669             break;
670         case PC_TRADE:
671             m_trade_generation += part->Capacity();
672             break;
673         case PC_PRODUCTION_LOCATION:
674             m_is_production_location = true;
675             break;
676         default:
677             break;
678         }
679         m_has_fighters = has_fighter_bays && has_fighter_hangars;
680         m_is_armed = m_is_armed || (can_launch_fighters && has_armed_fighters);
681 
682         m_num_ship_parts[part_name]++;
683         if (part_class > INVALID_SHIP_PART_CLASS && part_class < NUM_SHIP_PART_CLASSES)
684             m_num_part_classes[part_class]++;
685     }
686 }
687 
Dump(unsigned short ntabs) const688 std::string ShipDesign::Dump(unsigned short ntabs) const {
689     std::string retval = DumpIndent(ntabs) + "ShipDesign\n";
690     retval += DumpIndent(ntabs+1) + "name = \"" + m_name + "\"\n";
691     retval += DumpIndent(ntabs+1) + "uuid = \"" + boost::uuids::to_string(m_uuid) + "\"\n";
692     retval += DumpIndent(ntabs+1) + "description = \"" + m_description + "\"\n";
693 
694     if (!m_name_desc_in_stringtable)
695         retval += DumpIndent(ntabs+1) + "NoStringtableLookup\n";
696     retval += DumpIndent(ntabs+1) + "hull = \"" + m_hull + "\"\n";
697     retval += DumpIndent(ntabs+1) + "parts = ";
698     if (m_parts.empty()) {
699         retval += "[]\n";
700     } else if (m_parts.size() == 1) {
701         retval += "\"" + *m_parts.begin() + "\"\n";
702     } else {
703         retval += "[\n";
704         for (const std::string& part_name : m_parts) {
705             retval += DumpIndent(ntabs+2) + "\"" + part_name + "\"\n";
706         }
707         retval += DumpIndent(ntabs+1) + "]\n";
708     }
709     if (!m_icon.empty())
710         retval += DumpIndent(ntabs+1) + "icon = \"" + m_icon + "\"\n";
711     retval += DumpIndent(ntabs+1) + "model = \"" + m_3D_model + "\"\n";
712     return retval;
713 }
714 
GetCheckSum() const715 unsigned int ShipDesign::GetCheckSum() const {
716     unsigned int retval{0};
717     CheckSums::CheckSumCombine(retval, m_id);
718     CheckSums::CheckSumCombine(retval, m_uuid);
719     CheckSums::CheckSumCombine(retval, m_name);
720     CheckSums::CheckSumCombine(retval, m_description);
721     CheckSums::CheckSumCombine(retval, m_designed_on_turn);
722     CheckSums::CheckSumCombine(retval, m_designed_by_empire);
723     CheckSums::CheckSumCombine(retval, m_hull);
724     CheckSums::CheckSumCombine(retval, m_parts);
725     CheckSums::CheckSumCombine(retval, m_is_monster);
726     CheckSums::CheckSumCombine(retval, m_icon);
727     CheckSums::CheckSumCombine(retval, m_3D_model);
728     CheckSums::CheckSumCombine(retval, m_name_desc_in_stringtable);
729 
730     return retval;
731 }
732 
operator ==(const ShipDesign & first,const ShipDesign & second)733 bool operator ==(const ShipDesign& first, const ShipDesign& second) {
734     if (first.Hull() != second.Hull())
735         return false;
736 
737     std::map<std::string, int> first_parts;
738     std::map<std::string, int> second_parts;
739 
740     // don't care if order is different, as long as the types and numbers of parts is the same
741     for (const std::string& part_name : first.Parts())
742     { ++first_parts[part_name]; }
743 
744     for (const std::string& part_name : second.Parts())
745     { ++second_parts[part_name]; }
746 
747     return first_parts == second_parts;
748 }
749 
750 /////////////////////////////////////
751 // PredefinedShipDesignManager     //
752 /////////////////////////////////////
753 // static(s)
754 PredefinedShipDesignManager* PredefinedShipDesignManager::s_instance = nullptr;
755 
PredefinedShipDesignManager()756 PredefinedShipDesignManager::PredefinedShipDesignManager() {
757     if (s_instance)
758         throw std::runtime_error("Attempted to create more than one PredefinedShipDesignManager.");
759 
760     // Only update the global pointer on sucessful construction.
761     s_instance = this;
762 }
763 
764 namespace {
AddDesignToUniverse(std::unordered_map<std::string,int> & design_generic_ids,const std::unique_ptr<ShipDesign> & design,bool monster)765     void AddDesignToUniverse(std::unordered_map<std::string, int>& design_generic_ids,
766                              const std::unique_ptr<ShipDesign>& design, bool monster)
767     {
768         if (!design)
769             return;
770 
771         Universe& universe = GetUniverse();
772         /* check if there already exists this same design in the universe. */
773         for (auto it = universe.beginShipDesigns();
774              it != universe.endShipDesigns(); ++it)
775         {
776             const ShipDesign* existing_design = it->second;
777             if (!existing_design) {
778                 ErrorLogger() << "PredefinedShipDesignManager::AddShipDesignsToUniverse found an invalid design in the Universe";
779                 continue;
780             }
781 
782             if (DesignsTheSame(*existing_design, *design)) {
783                 WarnLogger() << "AddShipDesignsToUniverse found an exact duplicate of ship design "
784                              << design->Name() << "to be added, so is not re-adding it";
785                 design_generic_ids[design->Name(false)] = existing_design->ID();
786                 return; // design already added; don't need to do so again
787             }
788         }
789 
790         // duplicate design to add to Universe
791         ShipDesign* copy = new ShipDesign(*design);
792 
793         bool success = universe.InsertShipDesign(copy);
794         if (!success) {
795             ErrorLogger() << "Empire::AddShipDesign Unable to add new design to universe";
796             delete copy;
797             return;
798         }
799 
800         auto new_design_id = copy->ID();
801         design_generic_ids[design->Name(false)] = new_design_id;
802         TraceLogger() << "AddShipDesignsToUniverse added ship design "
803                       << design->Name() << " to universe.";
804     };
805 }
806 
AddShipDesignsToUniverse() const807 void PredefinedShipDesignManager::AddShipDesignsToUniverse() const {
808     CheckPendingDesignsTypes();
809     m_design_generic_ids.clear();
810 
811     for (const auto& uuid : m_ship_ordering)
812         AddDesignToUniverse(m_design_generic_ids, m_designs.at(uuid), false);
813 
814     for (const auto& uuid : m_monster_ordering)
815         AddDesignToUniverse(m_design_generic_ids, m_designs.at(uuid), true);
816 }
817 
GetPredefinedShipDesignManager()818 PredefinedShipDesignManager& PredefinedShipDesignManager::GetPredefinedShipDesignManager() {
819     static PredefinedShipDesignManager manager;
820     return manager;
821 }
822 
823 
GetOrderedShipDesigns() const824 std::vector<const ShipDesign*> PredefinedShipDesignManager::GetOrderedShipDesigns() const {
825     CheckPendingDesignsTypes();
826     std::vector<const ShipDesign*> retval;
827     for (const auto& uuid : m_ship_ordering)
828         retval.push_back(m_designs.at(uuid).get());
829     return retval;
830 }
831 
GetOrderedMonsterDesigns() const832 std::vector<const ShipDesign*> PredefinedShipDesignManager::GetOrderedMonsterDesigns() const {
833     CheckPendingDesignsTypes();
834     std::vector<const ShipDesign*> retval;
835     for (const auto& uuid : m_monster_ordering)
836         retval.push_back(m_designs.at(uuid).get());
837     return retval;
838 }
839 
GetDesignID(const std::string & name) const840 int PredefinedShipDesignManager::GetDesignID(const std::string& name) const {
841     CheckPendingDesignsTypes();
842     const auto& it = m_design_generic_ids.find(name);
843     if (it == m_design_generic_ids.end())
844         return INVALID_DESIGN_ID;
845     return it->second;
846 }
847 
GetCheckSum() const848 unsigned int PredefinedShipDesignManager::GetCheckSum() const {
849     CheckPendingDesignsTypes();
850     unsigned int retval{0};
851 
852     auto build_checksum = [&retval, this](const std::vector<boost::uuids::uuid>& ordering){
853         for (auto const& uuid : ordering) {
854             auto it = m_designs.find(uuid);
855             if (it != m_designs.end())
856                 CheckSums::CheckSumCombine(retval, std::make_pair(it->second->Name(false), *it->second));
857         }
858         CheckSums::CheckSumCombine(retval, ordering.size());
859     };
860 
861     build_checksum(m_ship_ordering);
862     build_checksum(m_monster_ordering);
863 
864     DebugLogger() << "PredefinedShipDesignManager checksum: " << retval;
865     return retval;
866 }
867 
868 
SetShipDesignTypes(Pending::Pending<ParsedShipDesignsType> && pending_designs)869 void PredefinedShipDesignManager::SetShipDesignTypes(
870     Pending::Pending<ParsedShipDesignsType>&& pending_designs)
871 { m_pending_designs = std::move(pending_designs); }
872 
SetMonsterDesignTypes(Pending::Pending<ParsedShipDesignsType> && pending_designs)873 void PredefinedShipDesignManager::SetMonsterDesignTypes(
874     Pending::Pending<ParsedShipDesignsType>&& pending_designs)
875 { m_pending_monsters = std::move(pending_designs); }
876 
877 namespace {
878     template <typename Map1, typename Map2, typename Ordering>
FillDesignsOrderingAndNameTables(PredefinedShipDesignManager::ParsedShipDesignsType & parsed_designs,Map1 & designs,Ordering & ordering,Map2 & name_to_uuid)879     void FillDesignsOrderingAndNameTables(
880         PredefinedShipDesignManager::ParsedShipDesignsType& parsed_designs,
881         Map1& designs, Ordering& ordering, Map2& name_to_uuid)
882     {
883         // Remove the old designs
884         for (const auto& name_and_uuid: name_to_uuid)
885             designs.erase(name_and_uuid.second);
886         name_to_uuid.clear();
887 
888         auto inconsistent_and_map_and_order_ships =
889             LoadShipDesignsAndManifestOrderFromParseResults(parsed_designs);
890 
891         ordering = std::get<2>(inconsistent_and_map_and_order_ships);
892 
893         auto& disk_designs = std::get<1>(inconsistent_and_map_and_order_ships);
894 
895         for (auto& uuid_and_design : disk_designs) {
896             auto& design = uuid_and_design.second.first;
897 
898             if (designs.count(design->UUID())) {
899                 ErrorLogger() << design->Name() << " ship design does not have a unique UUID for "
900                               << "its type monster or pre-defined. "
901                               << designs[design->UUID()]->Name() << " has the same UUID.";
902                 continue;
903             }
904 
905             if (name_to_uuid.count(design->Name())) {
906                 ErrorLogger() << design->Name() << " ship design does not have a unique name for "
907                               << "its type monster or pre-defined.";
908                 continue;
909             }
910 
911             name_to_uuid.insert({design->Name(), design->UUID()});
912             designs[design->UUID()] = std::move(design);
913         }
914     }
915 
916     template <typename PendingShips, typename Map1, typename Map2, typename Ordering>
CheckPendingAndFillDesignsOrderingAndNameTables(PendingShips & pending,Map1 & designs,Ordering & ordering,Map2 & name_to_uuid,bool are_monsters)917     void CheckPendingAndFillDesignsOrderingAndNameTables(
918         PendingShips& pending, Map1& designs, Ordering& ordering, Map2& name_to_uuid, bool are_monsters)
919     {
920         if (!pending)
921             return;
922 
923         auto parsed = Pending::WaitForPending(pending);
924         if (!parsed)
925             return;
926 
927         DebugLogger() << "Populating pre-defined ships with "
928                       << std::string(are_monsters ? "monster" : "ship") << " designs.";
929 
930         FillDesignsOrderingAndNameTables(
931             *parsed, designs, ordering, name_to_uuid);
932 
933         // Make the monsters monstrous
934         if (are_monsters)
935             for (const auto& uuid : ordering)
936                 designs[uuid]->SetMonster(true);
937 
938         TraceLogger() << [&designs, name_to_uuid]() {
939             std::stringstream ss;
940             ss << "Predefined Ship Designs:";
941             for (const auto& entry : name_to_uuid)
942                 ss << " ... " << designs[entry.second]->Name();
943             return ss.str();
944         }();
945     }
946 }
947 
CheckPendingDesignsTypes() const948 void PredefinedShipDesignManager::CheckPendingDesignsTypes() const {
949     CheckPendingAndFillDesignsOrderingAndNameTables(
950         m_pending_designs, m_designs, m_ship_ordering, m_name_to_ship_design, false);
951 
952     CheckPendingAndFillDesignsOrderingAndNameTables(
953         m_pending_monsters, m_designs, m_monster_ordering, m_name_to_monster_design, true);
954  }
955 
956 ///////////////////////////////////////////////////////////
957 // Free Functions                                        //
958 ///////////////////////////////////////////////////////////
GetPredefinedShipDesignManager()959 PredefinedShipDesignManager& GetPredefinedShipDesignManager()
960 { return PredefinedShipDesignManager::GetPredefinedShipDesignManager(); }
961 
GetPredefinedShipDesign(const std::string & name)962 const ShipDesign* GetPredefinedShipDesign(const std::string& name)
963 { return GetUniverse().GetGenericShipDesign(name); }
964 
965 std::tuple<
966     bool,
967     std::unordered_map<boost::uuids::uuid,
968                        std::pair<std::unique_ptr<ShipDesign>, boost::filesystem::path>,
969                        boost::hash<boost::uuids::uuid>>,
970     std::vector<boost::uuids::uuid>>
LoadShipDesignsAndManifestOrderFromParseResults(PredefinedShipDesignManager::ParsedShipDesignsType & designs_paths_and_ordering)971 LoadShipDesignsAndManifestOrderFromParseResults(
972     PredefinedShipDesignManager::ParsedShipDesignsType& designs_paths_and_ordering)
973 {
974     std::unordered_map<boost::uuids::uuid,
975                        std::pair<std::unique_ptr<ShipDesign>,
976                                  boost::filesystem::path>,
977                        boost::hash<boost::uuids::uuid>>  saved_designs;
978 
979     auto& designs_and_paths = designs_paths_and_ordering.first;
980     auto& disk_ordering = designs_paths_and_ordering.second;
981 
982     for (auto&& design_and_path : designs_and_paths) {
983         auto design = std::make_unique<ShipDesign>(*design_and_path.first);
984 
985         // If the UUID is nil this is a legacy design that needs a new UUID
986         if(design->UUID() == boost::uuids::uuid{{0}}) {
987             design->SetUUID(boost::uuids::random_generator()());
988             DebugLogger() << "Converted legacy ship design file by adding  UUID " << design->UUID()
989                           << " for name " << design->Name();
990         }
991 
992         // Make sure the design is an out of universe object
993         // This should not be needed.
994         if(design->ID() != INVALID_OBJECT_ID) {
995             design->SetID(INVALID_OBJECT_ID);
996             ErrorLogger() << "Loaded ship design has an id implying it is in an ObjectMap for UUID "
997                           << design->UUID() << " for name " << design->Name();
998         }
999 
1000         if (!saved_designs.count(design->UUID())) {
1001             TraceLogger() << "Added saved design UUID " << design->UUID()
1002                           << " with name " << design->Name();
1003             auto uuid = design->UUID();
1004             saved_designs[uuid] = std::make_pair(std::move(design), design_and_path.second);
1005         } else {
1006             WarnLogger() << "Duplicate ship design UUID " << design->UUID()
1007                          << " found for ship design " << design->Name()
1008                          << " and " << saved_designs[design->UUID()].first->Name();
1009         }
1010     }
1011 
1012     // Verify that all UUIDs in ordering exist
1013     std::vector<boost::uuids::uuid> ordering;
1014     bool ship_manifest_inconsistent = false;
1015     for (auto& uuid: disk_ordering) {
1016         // Skip the nil UUID.
1017         if(uuid == boost::uuids::uuid{{0}})
1018             continue;
1019 
1020         if (!saved_designs.count(uuid)) {
1021             WarnLogger() << "UUID " << uuid << " is in ship design manifest for "
1022                          << "a ship design that does not exist.";
1023             ship_manifest_inconsistent = true;
1024             continue;
1025         }
1026         ordering.push_back(uuid);
1027     }
1028 
1029     // Verify that every design in saved_designs is in ordering.
1030     if (ordering.size() != saved_designs.size()) {
1031         // Add any missing designs in alphabetical order to the end of the list
1032         std::unordered_set<boost::uuids::uuid, boost::hash<boost::uuids::uuid>>
1033             uuids_in_ordering{ordering.begin(), ordering.end()};
1034         std::map<std::string, boost::uuids::uuid> missing_uuids_sorted_by_name;
1035         for (auto& uuid_to_design_and_filename: saved_designs) {
1036             if (uuids_in_ordering.count(uuid_to_design_and_filename.first))
1037                 continue;
1038             ship_manifest_inconsistent = true;
1039             missing_uuids_sorted_by_name.insert(
1040                 std::make_pair(uuid_to_design_and_filename.second.first->Name(),
1041                                uuid_to_design_and_filename.first));
1042         }
1043 
1044         for (auto& name_and_uuid: missing_uuids_sorted_by_name) {
1045             WarnLogger() << "Missing ship design " << name_and_uuid.second
1046                          << " called " << name_and_uuid.first
1047                          << " added to the manifest.";
1048             ordering.push_back(name_and_uuid.second);
1049         }
1050     }
1051 
1052     return std::make_tuple(ship_manifest_inconsistent, std::move(saved_designs), ordering);
1053 }
1054