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